<gh_stars>10-100
# SPDX-FileCopyrightText: 2020 Efabless Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# SPDX-License-Identifier: Apache-2.0

import re

from PySpice.Spice import Parser
from pyverilog.vparser.parser import ParseError, parse


class NetlistParserNotFound(Exception):
    pass


class DataError(Exception):
    pass


class Port:
    """A verilog module port.
    
    Attributes:
        name: Port name. 
        type: Port type (Input, Output, Inout).
        lsb: Least significant bit of the port (None if it is a scalar port)
        msb: Most significant bit of the port (None if it is a scalar port)
    """

    def __init__(self, name, type, lsb=None, msb=None):
        self.name = name
        self.type = type
        self.lsb = lsb
        self.msb = msb

    @property
    def width(self):
        if self.lsb is not None:
            return int(self.msb) - int(self.lsb)
        else:  # Return None if it is a scalar port
            return None

    def split(self):
        """Split a vector port to scalar ports.

        Returns a list of scalar ports. Example:
        "a[3:0]" is split to [a[0], a[1], a[2], a[3]]
        """
        if self.width is None:  # if a scalar port, then just return the port name as is in a list
            port_split = [self.name]
        else:  # if not a scalar port, then split
            port_split = [f"{self.name}[{i}]" for i in range(int(self.lsb), int(self.msb) + 1)]
        return port_split


class NetlistParser:
    """Base class for netlist parsers. 

    Arguments:
        netlist: Netlist file path. 
        top_module: Top module name.

    Attributes: 
        top_module: Netlist top module name. 
        behavoiral: True if netlist contains behavoiral constructs, False otherwise. 
        instances: List of instance names.
        modules: List of module names. 
        ports: List of top module ports. 
        nodes: List of the parser abstract syntax tree nodes.
    """

    def __init__(self, netlist, top_module):
        """Create NetlistParser instance."""
        self.top_module = top_module
        self.behavoiral = False
        self.instances = []
        self.modules = []
        self.ports = []
        self.nodes = []

    def get_instances(self):
        """Get list of instance names in the top module"""
        return self.instances

    def get_modules(self):
        """Get list of module names in the top module"""
        return self.modules

    def get_ports(self):
        """Get list of ports of the top module"""
        return self.ports

    def get_instance_name(self, module_name):
        """Get instance name for the given module name"""
        try:
            indx = self.modules.index(module_name)
        except ValueError:
            return ''
        return self.instances[indx]

    def get_num_of_instances(self):
        """Get the number of instances in the top module"""
        length = len(self.instances)
        return length

    def get_hooks(self, module_name):
        """Get instance port connection for the given module name. 
        
        It returns a dictionary of the module instance connection. The 
        dictionary keys represent the port names, and the values represent 
        the port argument name. Example, for the given module instance:          
        a_module a_instance (.x(in1), .y(out1)) The returned dict is { x: in1, y: out1 }
        """
        pass

    def find_instance(self, module_name):
        """Look for an instance with the given module name. Returns True 
        if the module instance is found, False otherwise
        """
        found = module_name in self.modules
        return found

    def is_behavoiral(self):
        """Check if the parsed netlist contains behavoiral code or not."""
        return self.behavoiral

    def is_globally_connected(self, nets, ignored_instances=[]):
        """Check if any of the given nets is connected to all instances 
        minus the given ignored instances.
        
        Return True if at least one of the given nets is connected, False otherwise. 
        """
        pass


class SpiceParser(NetlistParser):
    """Spice (ngspice/xyce) netlist parser
    
    Arguments:
        netlist: Netlist file path. 
        top_module: Top module name.
    
    Attributes: 
        Parent class attributes
        subcircuits: List of subcircuit definition nodes. 
    """

    def __init__(self, netlist, top_module):
        """Create SpiParser instance."""
        super().__init__(netlist=netlist, top_module=top_module)
        # List of subcircuit definitions
        self.subcircuits = []

        try:
            parser = Parser.SpiceParser(path=netlist, end_of_line_comment=('$', '*', '//', ';'))
        except Parser.ParseError as e:
            raise DataError(f"Netlist {netlist} fails parsing because {str(e)}")

        self.subcircuits = parser.subcircuits

        found = False
        for subcircuit in self.subcircuits:
            if subcircuit.name == top_module:
                found = True
                self.ports = subcircuit.nodes
                self.nodes = subcircuit._statements
                self.instances = [instance.name for instance in subcircuit]
                self.modules = [instance._parameters[0] for instance in subcircuit]
                break

        if not found:
            raise DataError(f"Top module {top_module} not found in {netlist}.")

        # TODO: make sure that all instantiated modules have a subcircuit definition

    def find_subcircuit(self, subcircuit):
        """Look for a subcircuit definition"""
        names = [subcircuit.name for subcircuit in self.subcircuits]
        found = subcircuit in names
        return found

    def get_hooks(self, module_name):
        hooks = dict()
        subckt_instance = None
        for instance in self.nodes:
            if instance._parameters[0] == module_name:
                subckt_instance = instance
                break

        if not subckt_instance:
            raise DataError(f"Module instance {module_name} not found.")

        subckt_definition = None
        for subcircuit in self.subcircuits:
            if subcircuit.name == module_name:
                subckt_definition = subcircuit
                break

        if not subckt_definition:
            raise DataError(f"Module definition {module_name} not found.")

        ports = subckt_definition._nodes
        conn = subckt_instance._nodes

        if len(conn) < len(ports):
            print("{{Warning}}: Not all ports are connected")

        hooks = {ports[i]: conn[i] for i in range(0, len(conn))}
        hooks.update({ports[i]: None for i in range(len(conn), len(ports))})

        return hooks

    def is_globally_connected(self, nets, ignored_instances=[]):
        connected = False
        for instance in self.nodes:
            if instance._parameters[0] not in ignored_instances:
                locally_connected = [hook in nets for hook in instance._nodes]
                connected = any(locally_connected)
                if not connected:
                    print(f"Instance {instance._parameters[0]} isn't connected to any of the nets: {nets} .")
                    break

        return connected


class VerilogParser(NetlistParser):
    """Verilog HDL netlist parser
    
    Arguments:
        netlist: Netlist file path. 
        top_module: Top module name.
        include_files: List of extra .v files to include with the netlist. 
        preprocess_define: List of macro defines to pass to the preprocessor (iverilog). 
        
    Attributes: 
        Parent class attributes
    """

    def __init__(self, netlist, top_module, **kwargs):
        """Create VerilogParser instance."""
        super().__init__(netlist=netlist, top_module=top_module)
        include_files = kwargs.get('include_files', [])
        preprocess_define = kwargs.get('preprocess_define', None)
        # Pyverilog specific types for IO ports and verilog keywords 
        io_port_types = ['Inout', 'Input', 'Output']
        behavioral_keywords = [
            'Always', 'Case', 'CaseStatement', 'DelayStatement', 'EventStatement',
            'ForeverStatement', 'ForStatement', 'Function', 'FunctionCall', 'IfStatement', 'Initial',
            'Reg', 'Repeat', 'Task', 'TaskCall', 'WaitStatement', 'WhileStatement'
        ]

        netlists = include_files + [netlist]
        try:
            root_ast, _ = parse(filelist=netlists, preprocess_define=preprocess_define, debug=False)
        except ParseError as e:
            raise DataError(f"Parsing netlist {netlist} failed because {str(e)}")

        # Look for the top module definition node in the abstract syntax tree
        top_definition = None
        for definition in root_ast.description.definitions:
            def_type = type(definition).__name__
            if def_type == 'ModuleDef':
                if definition.name == top_module:
                    top_definition = definition
                    break

        if not top_definition:
            raise DataError(f"Top module {top_module} not found in {netlist}.")

        # Loop over each node under the top module definition 
        for item in top_definition.items:
            item_type = type(item).__name__
            if item_type == 'InstanceList':  # Module instances
                instance = item.instances[0]
                self.nodes.append(instance)
                self.instances.append(instance.name)
                self.modules.append(instance.module)
            elif item_type in behavioral_keywords:
                self.behavoiral = True
            elif item_type == 'Decl' and type(item.list[0]).__name__ in io_port_types:  # IO Port statements
                decl = item.list[0]
                if decl.width is not None:
                    lsb = min(decl.width.lsb.value, decl.width.msb.value)
                    msb = max(decl.width.lsb.value, decl.width.msb.value)
                else:
                    lsb = None
                    msb = None

                port = Port(name=decl.name, lsb=lsb, msb=msb, type=type(item.list[0]).__name__)
                self.ports.append(port)

        # If the port decleration is part of the header, for example: 
        # module (input clk, input rst, ...), then it won't be parsed by the previous loop
        if not self.ports:
            portlist = top_definition.portlist.ports
            for port in portlist:
                if port.first.width is not None:
                    lsb = self._evaluate_expr(port.first.width.lsb)
                    msb = self._evaluate_expr(port.first.width.msb)
                else:
                    lsb = None
                    msb = None

                port = Port(name=port.first.name, lsb=lsb, msb=msb, type=type(port.first).__name__)
                self.ports.append(port)

    def get_ports(self):
        """Get list of port names"""
        names = [element for port in self.ports for element in port.split()]
        return names

    def get_port_types(self, split_bus=True):
        """Get port types, it returns a dictionary. The
        dictionary keys are the port names, and the dictionary 
        values are the port type (Input, Output, Inout)
        """
        ports = dict()
        if split_bus:
            for port in self.ports:
                split_port = port.split()
                for element in split_port:
                    ports[element] = port.type
        else:
            for port in self.ports:
                ports[port.name] = port.type

        return ports

        # TODO: Needs to be with instance name (can have more than on instance with the same module)

    def get_hooks(self, module_name):
        hooks = dict()
        try:
            node_idx = self.modules.index(module_name)
        except ValueError:
            raise DataError(f"Module {module_name} not found.")

        node = self.nodes[node_idx]
        if node.module == module_name:
            for hook in node.portlist:
                argname_type = type(hook.argname).__name__
                if argname_type == 'Concat':
                    for i in range(0, len(hook.argname.list)):
                        portname = f"{hook.portname}[{i}]"
                        hooks[portname] = hook.argname.list[i]
                else:
                    hooks[hook.portname] = str(hook.argname)

        return hooks

    def is_globally_connected(self, nets, ignored_instances=[]):
        connected = False
        for node in self.nodes:
            if node.name not in ignored_instances:
                locally_connected = [str(hook.argname) in nets if type(hook.argname).__name__ != 'Concat'
                                     else len(set(list(hook.argname.list)) & set(nets)) > 0 for hook in node.portlist]
                connected = any(locally_connected)
                if not connected:
                    print(f"Instance {node.name} isn't connected to any of the nets: {nets} .")
                    break

        return connected

    def _evaluate_expr(self, expr):
        # TODO: Hanlde all supported pyverilog operators 
        operators = {
            'Plus': lambda a, b: a + b,
            'Minus': lambda a, b: a - b,
            'Times': lambda a, b: a * b,
            'Divide': lambda a, b: a / b,
            'Mod': lambda a, b: a % b,
            'Power': lambda a, b: a ** b,
            'Sll': lambda a, b: a >> b
        }
        expr_type = type(expr).__name__
        if expr_type in list(operators.keys()):
            left = self._evaluate_expr(expr.left)
            right = self._evaluate_expr(expr.right)
            value = operators[expr_type](left, right)
        elif expr_type == 'IntConst':
            value = expr.value
        else:
            raise DataError(f"Got an unknown expression type {expr_type} in netlist .")

        return int(value)

    @staticmethod
    def remove_cells(input_netlist, output_netlist, cells):
        def filter_out_cell(cell, content):
            regex = fr'\s+sky130_fd_sc_.*__{cell}_[\d]+\s+.*\s+\([\s\S]*?(?:\;)'
            return re.sub(regex, '', content)

        with open(input_netlist) as f:
            lines = f.read()
            for cell in cells:
                lines = filter_out_cell(cell, lines)

        if not lines:
            raise DataError(f"File {input_netlist} is empty.")

        with open(output_netlist, 'w+') as f:
            f.write(lines)


def get_netlist_parser(netlist, top_module, netlist_type, **kwargs):
    if netlist_type == 'spice':
        return SpiceParser(netlist, top_module)
    elif netlist_type == 'verilog':
        return VerilogParser(netlist, top_module, **kwargs)
    else:
        raise NetlistParserNotFound()

# SPDX-FileCopyrightText: 2020 Efabless Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# SPDX-License-Identifier: Apache-2.0

import re

from PySpice.Spice import Parser
from pyverilog.vparser.parser import ParseError, parse


class NetlistParserNotFound(Exception):
    pass


class DataError(Exception):
    pass


class Port:
    """A verilog module port.
    
    Attributes:
        name: Port name. 
        type: Port type (Input, Output, Inout).
        lsb: Least significant bit of the port (None if it is a scalar port)
        msb: Most significant bit of the port (None if it is a scalar port)
    """

    def __init__(self, name, type, lsb=None, msb=None):
        self.name = name
        self.type = type
        self.lsb = lsb
        self.msb = msb

    @property
    def width(self):
        if self.lsb is not None:
            return int(self.msb) - int(self.lsb)
        else:  # Return None if it is a scalar port
            return None

    def split(self):
        """Split a vector port to scalar ports.

        Returns a list of scalar ports. Example:
        "a[3:0]" is split to [a[0], a[1], a[2], a[3]]
        """
        if self.width is None:  # if a scalar port, then just return the port name as is in a list
            port_split = [self.name]
        else:  # if not a scalar port, then split
            port_split = [f"{self.name}[{i}]" for i in range(int(self.lsb), int(self.msb) + 1)]
        return port_split


class NetlistParser:
    """Base class for netlist parsers. 

    Arguments:
        netlist: Netlist file path. 
        top_module: Top module name.

    Attributes: 
        top_module: Netlist top module name. 
        behavoiral: True if netlist contains behavoiral constructs, False otherwise. 
        instances: List of instance names.
        modules: List of module names. 
        ports: List of top module ports. 
        nodes: List of the parser abstract syntax tree nodes.
    """

    def __init__(self, netlist, top_module):
        """Create NetlistParser instance."""
        self.top_module = top_module
        self.behavoiral = False
        self.instances = []
        self.modules = []
        self.ports = []
        self.nodes = []

    def get_instances(self):
        """Get list of instance names in the top module"""
        return self.instances

    def get_modules(self):
        """Get list of module names in the top module"""
        return self.modules

    def get_ports(self):
        """Get list of ports of the top module"""
        return self.ports

    def get_instance_name(self, module_name):
        """Get instance name for the given module name"""
        try:
            indx = self.modules.index(module_name)
        except ValueError:
            return ''
        return self.instances[indx]

    def get_num_of_instances(self):
        """Get the number of instances in the top module"""
        length = len(self.instances)
        return length

    def get_hooks(self, module_name):
        """Get instance port connection for the given module name. 
        
        It returns a dictionary of the module instance connection. The 
        dictionary keys represent the port names, and the values represent 
        the port argument name. Example, for the given module instance:          
        a_module a_instance (.x(in1), .y(out1)) The returned dict is { x: in1, y: out1 }
        """
        pass

    def find_instance(self, module_name):
        """Look for an instance with the given module name. Returns True 
        if the module instance is found, False otherwise
        """
        found = module_name in self.modules
        return found

    def is_behavoiral(self):
        """Check if the parsed netlist contains behavoiral code or not."""
        return self.behavoiral

    def is_globally_connected(self, nets, ignored_instances=[]):
        """Check if any of the given nets is connected to all instances 
        minus the given ignored instances.
        
        Return True if at least one of the given nets is connected, False otherwise. 
        """
        pass


class SpiceParser(NetlistParser):
    """Spice (ngspice/xyce) netlist parser
    
    Arguments:
        netlist: Netlist file path. 
        top_module: Top module name.
    
    Attributes: 
        Parent class attributes
        subcircuits: List of subcircuit definition nodes. 
    """

    def __init__(self, netlist, top_module):
        """Create SpiParser instance."""
        super().__init__(netlist=netlist, top_module=top_module)
        # List of subcircuit definitions
        self.subcircuits = []

        try:
            parser = Parser.SpiceParser(path=netlist, end_of_line_comment=('$', '*', '//', ';'))
        except Parser.ParseError as e:
            raise DataError(f"Netlist {netlist} fails parsing because {str(e)}")

        self.subcircuits = parser.subcircuits

        found = False
        for subcircuit in self.subcircuits:
            if subcircuit.name == top_module:
                found = True
                self.ports = subcircuit.nodes
                self.nodes = subcircuit._statements
                self.instances = [instance.name for instance in subcircuit]
                self.modules = [instance._parameters[0] for instance in subcircuit]
                break

        if not found:
            raise DataError(f"Top module {top_module} not found in {netlist}.")

        # TODO: make sure that all instantiated modules have a subcircuit definition

    def find_subcircuit(self, subcircuit):
        """Look for a subcircuit definition"""
        names = [subcircuit.name for subcircuit in self.subcircuits]
        found = subcircuit in names
        return found

    def get_hooks(self, module_name):
        hooks = dict()
        subckt_instance = None
        for instance in self.nodes:
            if instance._parameters[0] == module_name:
                subckt_instance = instance
                break

        if not subckt_instance:
            raise DataError(f"Module instance {module_name} not found.")

        subckt_definition = None
        for subcircuit in self.subcircuits:
            if subcircuit.name == module_name:
                subckt_definition = subcircuit
                break

        if not subckt_definition:
            raise DataError(f"Module definition {module_name} not found.")

        ports = subckt_definition._nodes
        conn = subckt_instance._nodes

        if len(conn) < len(ports):
            print("{{Warning}}: Not all ports are connected")

        hooks = {ports[i]: conn[i] for i in range(0, len(conn))}
        hooks.update({ports[i]: None for i in range(len(conn), len(ports))})

        return hooks

    def is_globally_connected(self, nets, ignored_instances=[]):
        connected = False
        for instance in self.nodes:
            if instance._parameters[0] not in ignored_instances:
                locally_connected = [hook in nets for hook in instance._nodes]
                connected = any(locally_connected)
                if not connected:
                    print(f"Instance {instance._parameters[0]} isn't connected to any of the nets: {nets} .")
                    break

        return connected


class VerilogParser(NetlistParser):
    """Verilog HDL netlist parser
    
    Arguments:
        netlist: Netlist file path. 
        top_module: Top module name.
        include_files: List of extra .v files to include with the netlist. 
        preprocess_define: List of macro defines to pass to the preprocessor (iverilog). 
        
    Attributes: 
        Parent class attributes
    """

    def __init__(self, netlist, top_module, **kwargs):
        """Create VerilogParser instance."""
        super().__init__(netlist=netlist, top_module=top_module)
        include_files = kwargs.get('include_files', [])
        preprocess_define = kwargs.get('preprocess_define', None)
        # Pyverilog specific types for IO ports and verilog keywords 
        io_port_types = ['Inout', 'Input', 'Output']
        behavioral_keywords = [
            'Always', 'Case', 'CaseStatement', 'DelayStatement', 'EventStatement',
            'ForeverStatement', 'ForStatement', 'Function', 'FunctionCall', 'IfStatement', 'Initial',
            'Reg', 'Repeat', 'Task', 'TaskCall', 'WaitStatement', 'WhileStatement'
        ]

        netlists = include_files + [netlist]
        try:
            root_ast, _ = parse(filelist=netlists, preprocess_define=preprocess_define, debug=False)
        except ParseError as e:
            raise DataError(f"Parsing netlist {netlist} failed because {str(e)}")

        # Look for the top module definition node in the abstract syntax tree
        top_definition = None
        for definition in root_ast.description.definitions:
            def_type = type(definition).__name__
            if def_type == 'ModuleDef':
                if definition.name == top_module:
                    top_definition = definition
                    break

        if not top_definition:
            raise DataError(f"Top module {top_module} not found in {netlist}.")

        # Loop over each node under the top module definition 
        for item in top_definition.items:
            item_type = type(item).__name__
            if item_type == 'InstanceList':  # Module instances
                instance = item.instances[0]
                self.nodes.append(instance)
                self.instances.append(instance.name)
                self.modules.append(instance.module)
            elif item_type in behavioral_keywords:
                self.behavoiral = True
            elif item_type == 'Decl' and type(item.list[0]).__name__ in io_port_types:  # IO Port statements
                decl = item.list[0]
                if decl.width is not None:
                    lsb = min(decl.width.lsb.value, decl.width.msb.value)
                    msb = max(decl.width.lsb.value, decl.width.msb.value)
                else:
                    lsb = None
                    msb = None

                port = Port(name=decl.name, lsb=lsb, msb=msb, type=type(item.list[0]).__name__)
                self.ports.append(port)

        # If the port decleration is part of the header, for example: 
        # module (input clk, input rst, ...), then it won't be parsed by the previous loop
        if not self.ports:
            portlist = top_definition.portlist.ports
            for port in portlist:
                if port.first.width is not None:
                    lsb = self._evaluate_expr(port.first.width.lsb)
                    msb = self._evaluate_expr(port.first.width.msb)
                else:
                    lsb = None
                    msb = None

                port = Port(name=port.first.name, lsb=lsb, msb=msb, type=type(port.first).__name__)
                self.ports.append(port)

    def get_ports(self):
        """Get list of port names"""
        names = [element for port in self.ports for element in port.split()]
        return names

    def get_port_types(self, split_bus=True):
        """Get port types, it returns a dictionary. The
        dictionary keys are the port names, and the dictionary 
        values are the port type (Input, Output, Inout)
        """
        ports = dict()
        if split_bus:
            for port in self.ports:
                split_port = port.split()
                for element in split_port:
                    ports[element] = port.type
        else:
            for port in self.ports:
                ports[port.name] = port.type

        return ports

        # TODO: Needs to be with instance name (can have more than on instance with the same module)

    def get_hooks(self, module_name):
        hooks = dict()
        try:
            node_idx = self.modules.index(module_name)
        except ValueError:
            raise DataError(f"Module {module_name} not found.")

        node = self.nodes[node_idx]
        if node.module == module_name:
            for hook in node.portlist:
                argname_type = type(hook.argname).__name__
                if argname_type == 'Concat':
                    for i in range(0, len(hook.argname.list)):
                        portname = f"{hook.portname}[{i}]"
                        hooks[portname] = hook.argname.list[i]
                else:
                    hooks[hook.portname] = str(hook.argname)

        return hooks

    def is_globally_connected(self, nets, ignored_instances=[]):
        connected = False
        for node in self.nodes:
            if node.name not in ignored_instances:
                locally_connected = [str(hook.argname) in nets if type(hook.argname).__name__ != 'Concat'
                                     else len(set(list(hook.argname.list)) & set(nets)) > 0 for hook in node.portlist]
                connected = any(locally_connected)
                if not connected:
                    print(f"Instance {node.name} isn't connected to any of the nets: {nets} .")
                    break

        return connected

    def _evaluate_expr(self, expr):
        # TODO: Hanlde all supported pyverilog operators 
        operators = {
            'Plus': lambda a, b: a + b,
            'Minus': lambda a, b: a - b,
            'Times': lambda a, b: a * b,
            'Divide': lambda a, b: a / b,
            'Mod': lambda a, b: a % b,
            'Power': lambda a, b: a ** b,
            'Sll': lambda a, b: a >> b
        }
        expr_type = type(expr).__name__
        if expr_type in list(operators.keys()):
            left = self._evaluate_expr(expr.left)
            right = self._evaluate_expr(expr.right)
            value = operators[expr_type](left, right)
        elif expr_type == 'IntConst':
            value = expr.value
        else:
            raise DataError(f"Got an unknown expression type {expr_type} in netlist .")

        return int(value)

    @staticmethod
    def remove_cells(input_netlist, output_netlist, cells):
        def filter_out_cell(cell, content):
            regex = fr'\s+sky130_fd_sc_.*__{cell}_[\d]+\s+.*\s+\([\s\S]*?(?:\;)'
            return re.sub(regex, '', content)

        with open(input_netlist) as f:
            lines = f.read()
            for cell in cells:
                lines = filter_out_cell(cell, lines)

        if not lines:
            raise DataError(f"File {input_netlist} is empty.")

        with open(output_netlist, 'w+') as f:
            f.write(lines)


def get_netlist_parser(netlist, top_module, netlist_type, **kwargs):
    if netlist_type == 'spice':
        return SpiceParser(netlist, top_module)
    elif netlist_type == 'verilog':
        return VerilogParser(netlist, top_module, **kwargs)
    else:
        raise NetlistParserNotFound()
# SPDX-FileCopyrightText: 2020 Efabless Corporation # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # #      http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # SPDX-License-Identifier: Apache-2.0 A verilog module port.          Attributes:         name: Port name.          type: Port type (Input, Output, Inout).         lsb: Least significant bit of the port (None if it is a scalar port)         msb: Most significant bit of the port (None if it is a scalar port) # Return None if it is a scalar port Split a vector port to scalar ports.          Returns a list of scalar ports. Example:         "a[3:0]" is split to [a[0], a[1], a[2], a[3]] # if a scalar port, then just return the port name as is in a list # if not a scalar port, then split Base class for netlist parsers.       Arguments:         netlist: Netlist file path.          top_module: Top module name.      Attributes:          top_module: Netlist top module name.          behavoiral: True if netlist contains behavoiral constructs, False otherwise.          instances: List of instance names.         modules: List of module names.          ports: List of top module ports.          nodes: List of the parser abstract syntax tree nodes. Create NetlistParser instance. Get list of instance names in the top module Get list of module names in the top module Get list of ports of the top module Get instance name for the given module name Get the number of instances in the top module Get instance port connection for the given module name.                   It returns a dictionary of the module instance connection. The          dictionary keys represent the port names, and the values represent          the port argument name. Example, for the given module instance:                   a_module a_instance (.x(in1), .y(out1)) The returned dict is { x: in1, y: out1 } Look for an instance with the given module name. Returns True          if the module instance is found, False otherwise Check if the parsed netlist contains behavoiral code or not. Check if any of the given nets is connected to all instances          minus the given ignored instances.                  Return True if at least one of the given nets is connected, False otherwise. Spice (ngspice/xyce) netlist parser          Arguments:         netlist: Netlist file path.          top_module: Top module name.          Attributes:          Parent class attributes         subcircuits: List of subcircuit definition nodes. Create SpiParser instance. # List of subcircuit definitions # TODO: make sure that all instantiated modules have a subcircuit definition Look for a subcircuit definition Verilog HDL netlist parser          Arguments:         netlist: Netlist file path.          top_module: Top module name.         include_files: List of extra .v files to include with the netlist.          preprocess_define: List of macro defines to pass to the preprocessor (iverilog).               Attributes:          Parent class attributes Create VerilogParser instance. # Pyverilog specific types for IO ports and verilog keywords # Look for the top module definition node in the abstract syntax tree # Loop over each node under the top module definition # Module instances # IO Port statements # If the port decleration is part of the header, for example: # module (input clk, input rst, ...), then it won't be parsed by the previous loop Get list of port names Get port types, it returns a dictionary. The         dictionary keys are the port names, and the dictionary          values are the port type (Input, Output, Inout) # TODO: Needs to be with instance name (can have more than on instance with the same module) # TODO: Hanlde all supported pyverilog operators