Source code for fabulous.fabric_cad.timing_model.hdlnx.verilog_gate_level

"""Convert a Verilog gate-level netlist into a timing graph.

It uses the SDFTimingGraph class to parse the SDF file and generate a NetworkX directed
graph representing the timing relationships. It will call an external STA tool to
generate the SDF file from the Verilog netlist.
"""

import re

import networkx as nx

from fabulous.fabric_cad.timing_model.hdlnx.sdfnx.sdf_to_graph import SDFTimingGraph
from fabulous.fabric_cad.timing_model.models import (
    DelayType,
)
from fabulous.fabric_cad.timing_model.tools.specification import StaTool


[docs] class VerilogGateLevelTimingGraph(SDFTimingGraph): """Class to represent a timing graph from a Verilog gate-level netlist. Initializes the VerilogGateLevelTimingGraph by generating an SDF file from the provided Verilog netlist using the specified STA tool, and then initializing the parent SDFTimingGraph with the generated SDF file. It extends SDFTimingGraph to include functionality for generating the SDF file using an external static timing analysis (STA) tool. Parameters ---------- top_name : str Name of the top-level module in the Verilog netlist. sta_tool : StaTool Instance of the STA tool used for timing analysis. delay_type_str : DelayType, optional Type of delay to consider (e.g., DelayType.MAX_ALL). Default is DelayType.MAX_ALL. debug : bool, optional Flag to enable debug mode. Default is False. """ def __init__( self, top_name: str, sta_tool: StaTool, delay_type_str: DelayType = DelayType.MAX_ALL, debug: bool = False, ) -> None: self.top_name: str = top_name self.delay_type_str: DelayType = delay_type_str self.debug: bool = debug self.sta_tool: StaTool = sta_tool self.sta_tool.sta_analyze() super().__init__(self.sta_tool.sta_sdf_file, self.delay_type_str) self.sta_tool.sta_clean_up() ### Public methods ###
[docs] def get_raw_verilog_netlist_data(self) -> str: """Return the raw Verilog netlist content as a string. Returns ------- str The content of the Verilog netlist file. """ return self.verilog_netlist_content
[docs] def resolve_hier_pin(self, hier_pin_path: str) -> list[str]: """Resolve hierarchical pin path to leaf pins. Parse a structural Verilog netlist and resolve a hierarchical pin path like "inst1/inst2/A0" down to all leaf std-cell pins connected to it. Returns a list of hierarchical pin paths (strings). Limitations ----------- - Expects gate-level, structural Verilog: module/endmodule + simple instances of the form: CellType inst_name ( .PIN(net), ... ); - Ignores assign statements, generate blocks, functions, etc. - Assumes module port names are used as net names inside the module. Parameters ---------- hier_pin_path : str Hierarchical pin path in the format "inst1/inst2/pin". Returns ------- list[str] List of resolved leaf pin paths. Raises ------ ValueError If the hierarchical pin path is invalid or if the top module or instances are not found. KeyError If the target pin is not found on the last instance in the path. """ sep = self.hier_sep hier_pin_path: str = f"{self.top_name}{sep}{hier_pin_path}" verilog_src: str = self.verilog_netlist_content # ------------------------------------------------------------------ # Strip comments: /* ... */ and // ... # ------------------------------------------------------------------ src_no_block = re.sub(r"/\*.*?\*/", "", verilog_src, flags=re.DOTALL) src_clean = re.sub(r"//.*?$", "", src_no_block, flags=re.MULTILINE) # ------------------------------------------------------------------ # Parse modules and their instances (type + name + pin connections) # ------------------------------------------------------------------ modules = {} module_pattern = re.compile( r"\bmodule\b\s+([A-Za-z_][\w$]*)\b(.*?)\bendmodule\b", flags=re.DOTALL ) # Very simple instance pattern: CellType inst_name ( .PIN(net), ... ); # This will also match the module header "module name (...);" # but we filter it out. inst_pattern = re.compile( r"([A-Za-z_][\w$]*)\s+([A-Za-z_][\w$]*)\s*\((.*?)\);\s*", flags=re.DOTALL ) reserved_types = { "module", "input", "output", "inout", "wire", "reg", "tri", "tri0", "tri1", "supply0", "supply1", "parameter", "localparam", "assign", "always", "initial", "generate", "endgenerate", "if", "for", "case", "function", "task", } pin_net_pattern = re.compile(r"\.\s*([\w$]+)\s*\(\s*([^)]+?)\s*\)") for m in module_pattern.finditer(src_clean): mod_name = m.group(1) mod_body = m.group(2) instances = [] for im in inst_pattern.finditer(mod_body): cell_type, inst_name, conn_str = im.groups() # Skip "instances" that are actually keywords, e.g. the module header if cell_type in reserved_types: continue conns = {} for pin, net in pin_net_pattern.findall(conn_str): net = net.strip() conns[pin] = net instances.append( { "type": cell_type, "name": inst_name, "conns": conns, } ) modules[mod_name] = {"instances": instances} # ------------------------------------------------------------------ # Parse hierarchical pin path: Top/inst1/inst2/.../pin # ------------------------------------------------------------------ parts = hier_pin_path.split(sep) if len(parts) < 3: raise ValueError( f"Hierarchical pin path must be " f"'Top{sep}inst{sep}...{sep}pin', got: {hier_pin_path!r}" ) top_module = parts[0] inst_chain = parts[1:-1] target_pin = parts[-1] if top_module not in modules: raise KeyError(f"Top module {top_module!r} not found in netlist") # ------------------------------------------------------------------ # Walk down the instance chain to find: # - the parent module of the last instance # - the last instance itself # - the child module type of that instance # ------------------------------------------------------------------ curr_module = top_module prev_module = None last_inst = None hier_prefix = top_module for inst_name in inst_chain: prev_module = curr_module inst_list = modules.get(curr_module, {}).get("instances", []) last_inst = None for inst in inst_list: if inst["name"] == inst_name: last_inst = inst break if last_inst is None: raise KeyError( f"Instance {inst_name!r} not found in module {curr_module!r}" ) curr_module = last_inst["type"] hier_prefix += f"{sep}" + inst_name if last_inst is None or prev_module is None: raise ValueError( f"Path {hier_pin_path!r} does not contain a valid instance chain" ) # last_inst is the instance whose pin we are addressing if target_pin not in last_inst["conns"]: raise KeyError( f"Pin {target_pin!r} not found on instance {last_inst['name']!r} " f"in module {prev_module!r}" ) child_module = curr_module # type of the last instance child_net = target_pin # assume port name == internal net name inside child # If the instance itself is already a leaf std-cell (no module definition), # then the endpoint is just that pin. if child_module not in modules: prefix = top_module + f"{sep}" # strip top module name from hierarchical prefix pp = hier_prefix.removeprefix(prefix) return [f"{pp}{sep}{target_pin}"] # ------------------------------------------------------------------ # Recursively traverse down the hierarchy from (child_module, child_net) # ------------------------------------------------------------------ visited = set() def traverse(mod_name: str, net_name: str, prefix: str) -> list[str]: """Traverse down the hierarchy from (mod_name, net_name) with prefix. In module `mod_name`, find all instance pins that are connected to `net_name`. For std-cell instances (type not in modules), record leaf paths. For submodules (type in modules), recurse into that submodule, using the pin name as the net name inside the child. """ key = (mod_name, net_name, prefix) if key in visited: return [] visited.add(key) results = [] inst_list = modules.get(mod_name, {}).get("instances", []) for inst in inst_list: inst_type = inst["type"] inst_name = inst["name"] for pin, net in inst["conns"].items(): if net != net_name: continue new_prefix = f"{prefix}{sep}{inst_name}" # Leaf std-cell: no module definition for its type if inst_type not in modules: results.append(f"{new_prefix}{sep}{pin}") else: # Submodule: descend, assuming port name == internal net name results.extend(traverse(inst_type, pin, new_prefix)) return results leaf_pins = traverse(child_module, child_net, hier_prefix) # strip top module name prefix = top_module + f"{sep}" return [p.removeprefix(prefix) for p in leaf_pins]
[docs] def find_verilog_modules_regex(self, name_pattern: str) -> list[str]: """Find Verilog module names matching a regex pattern. Parse a Verilog netlist and return all module names that match the given regex pattern. Parameters ---------- name_pattern : str Regular expression pattern to match module names. Returns ------- list[str] List of module names matching the regex pattern. """ verilog_src: str = self.verilog_netlist_content # Strip block and line comments src = re.sub(r"/\*.*?\*/", "", verilog_src, flags=re.DOTALL) # /* ... */ src = re.sub(r"//.*?$", "", src, flags=re.MULTILINE) # // ... # Regex for module declaration: # module <name> [#(...)] ( module_decl_re = re.compile( r"^\s*module\s+([A-Za-z_]\w*)\s*" # module name (group 1) r"(?:#\s*\([^()]*\))?" # optional parameter list #(...) r"\s*\(", # opening parenthesis of port list flags=re.MULTILINE, ) name_re = re.compile(name_pattern) found: list[str] = [] for m in module_decl_re.finditer(src): name = m.group(1) if name_re.search(name): found.append(name) return found
[docs] def find_instance_paths_by_regex( self, inst_regex: str, filter_regex: str | None = None ) -> list[str]: """Find hierarchical instance paths matching a regex. Parse a structural Verilog netlist, walk the hierarchy from `top_module`, and return all hierarchical instance paths (without the top module name) whose path matches `inst_regex`. Parameters ---------- inst_regex : str Regular expression to match hierarchical instance paths. filter_regex : str | None Optional regular expression to filter the matched instance paths. Returns ------- list[str] List of hierarchical instance paths matching the regex. Raises ------ KeyError If the top module specified by `top_name` is not found in the netlist. """ top_module = self.top_name sep = self.hier_sep verilog_src: str = self.verilog_netlist_content # ------------------------------------------------------------- # Remove comments: /* ... */ and // ... # ------------------------------------------------------------- src_no_block = re.sub(r"/\*.*?\*/", "", verilog_src, flags=re.DOTALL) src_clean = re.sub(r"//.*?$", "", src_no_block, flags=re.MULTILINE) # ------------------------------------------------------------- # Parse modules and their instances (type + name only) # ------------------------------------------------------------- modules = {} module_pattern = re.compile( r"\bmodule\b\s+([A-Za-z_][\w$]*)\b(.*?)\bendmodule\b", flags=re.DOTALL, ) # Simple instance pattern: CellType inst_name ( ... ); inst_pattern = re.compile( r"([A-Za-z_][\w$]*)\s+([A-Za-z_][\w$]*)\s*\(", flags=re.DOTALL, ) reserved_types = { "module", "input", "output", "inout", "wire", "reg", "tri", "tri0", "tri1", "supply0", "supply1", "parameter", "localparam", "assign", "always", "initial", "generate", "endgenerate", "if", "for", "case", "function", "task", } for m in module_pattern.finditer(src_clean): mod_name = m.group(1) mod_body = m.group(2) instances = [] for im in inst_pattern.finditer(mod_body): cell_type, inst_name = im.groups() # Skip things that look like keywords / non-instances if cell_type in reserved_types: continue instances.append( { "type": cell_type, "name": inst_name, } ) modules[mod_name] = {"instances": instances} # ------------------------------------------------------------- # Check that top_module exists # ------------------------------------------------------------- if top_module not in modules: raise KeyError(f"Top module {top_module!r} not found in netlist") # ------------------------------------------------------------- # DFS from top_module, build hierarchical instance paths # (without including the top module name itself) # ------------------------------------------------------------- pattern = re.compile(inst_regex) results = [] def dfs(mod_name: str, path_parts: list[str]) -> None: """Depth-first search to build hierarchical instance paths. mod_name: current module type. path_parts: list of instance names from *below* top, e.g. ["inst_sw_matrix", "inst_cus_mux81_buf_NN4BEG0"] """ inst_list = modules.get(mod_name, {}).get("instances", []) for inst in inst_list: inst_name = inst["name"] new_parts = path_parts + [inst_name] hier_path = sep.join(new_parts) # WITHOUT the top module name # Regex is matched on this hierarchical path if pattern.search(hier_path): results.append(hier_path) # If this instance's type is another module, recurse into it inst_type = inst["type"] if inst_type in modules: dfs(inst_type, new_parts) dfs(top_module, []) if filter_regex is not None: filter_pattern = re.compile(filter_regex) results = [p for p in results if filter_pattern.search(p)] return results
[docs] def find_instances_with_all_nets( self, module_name: str, nets: list[str] ) -> list[str]: """All nets must be on the instance. Scan a Verilog netlist and return instance names inside `module_name` that have *all* nets in `nets` connected to any of their pins. - Only looks at direct instances inside the given module (no hierarchy). - Assumes gate-level style instantiations like cell_type inst_name ( .A0(net1), .A1(net2), ... ); Parameters ---------- module_name : str Name of the module to search in. nets : list[str] List of net names that must all appear on the instance. Returns ------- list[str] List of instance names (strings). Raises ------ ValueError If the specified module is not found in the netlist. """ text = self.verilog_netlist_content # Extract the reference module body: module <name> ... endmodule mod_pattern = re.compile( rf"module\s+{re.escape(module_name)}\b(.*?endmodule)", re.DOTALL ) m = mod_pattern.search(text) if not m: raise ValueError(f"Module {module_name!r} not found in netlist content") module_body = m.group(1) # Remove simple // comments to avoid bogus matches module_body = re.sub(r"//.*?$", "", module_body, flags=re.MULTILINE) # Find instantiations: cell_type inst_name ( ... ); inst_pattern = re.compile( r""" (?P<cell>\w+) # cell type \s+ (?P<inst>\w+) # instance name \s* \( (?P<pins>[^;]*?) # pin connections until the semicolon \) \s*; """, re.DOTALL | re.VERBOSE, ) # Pin connection pattern: .PIN_NAME(NET_NAME) pin_conn_pattern = re.compile(r"\.\s*\w+\s*\(\s*([^\s\)]+)\s*\)") target_nets = set(nets) matching_instances: list[str] = [] for im in inst_pattern.finditer(module_body): inst_name = im.group("inst") pins_block = im.group("pins") # Collect all nets connected on this instance nets_on_inst = set(pin_conn_pattern.findall(pins_block)) if target_nets.issubset(nets_on_inst): matching_instances.append(inst_name) return matching_instances
[docs] def find_instances_paths_with_all_nets( self, module_name: str, nets: list[str], filter_regex: str | None = None ) -> list[str]: """Find hierarchical instance paths with all nets. Combines `find_instances_with_all_nets` and `find_instance_paths_by_regex` to return hierarchical instance paths (without top module name) for instances inside `module_name` that have *all* nets in `nets` connected to any of their pins. Parameters ---------- module_name : str Name of the module to search in. nets : list[str] List of net names that must all appear on the instance. filter_regex : str | None Optional regular expression to filter the matched instance paths. Returns ------- list[str] List of hierarchical instance paths (strings). """ inst_hier_paths = [] insts = self.find_instances_with_all_nets(module_name, nets) for inst in insts: paths = self.find_instance_paths_by_regex( rf"{re.escape(inst)}$", filter_regex=filter_regex ) inst_hier_paths.extend(paths) return inst_hier_paths
[docs] def net_to_pin_paths_for_instance(self, hier_inst_path: str) -> dict[str, str]: """Paths from nets to hierarchical pins for an instance. Given a hierarchical instance path like: "Inst_LUT4AB_switch_matrix/inst_cus_mux161_buf_JE2BEG3" and a gate-level Verilog netlist, return a mapping: net_name -> "hier_inst_path/pin_name" Only the leaf instance is resolved (no further hierarchy). Example output: { "N1END2": "Inst_LUT4AB_switch_matrix/inst_cus_mux161_buf_JE2BEG3/A0", "N2END4": "Inst_LUT4AB_switch_matrix/inst_cus_mux161_buf_JE2BEG3/A1", ... } Parameters ---------- hier_inst_path : str Hierarchical instance path. Returns ------- dict[str, str] Mapping from net names to pins keep hierarchy (no further hierarchy). Raises ------ ValueError If the top module or instance is not found in the netlist. RuntimeError If an unexpected error occurs during hierarchy resolution. """ text = self.verilog_netlist_content top_module = self.top_name sep = self.hier_sep # Collect all modules: name -> body text module_pattern = re.compile( r"module\s+(\w+)\b(.*?endmodule)", re.DOTALL, ) modules = {} for m in module_pattern.finditer(text): name = m.group(1) body = m.group(2) modules[name] = body if top_module not in modules: raise ValueError(f"Top module {top_module!r} not found in netlist") # Pattern for instances inside a module # Handles: cell_type [#(...)] inst_name ( ... ); inst_pattern = re.compile( r""" (?P<cell>\w+) # cell/module type \s+ (?P<inst>\w+) # instance name \s* \( (?P<pins>[^;]*?) # pin connections until ';' \) \s*; """, re.DOTALL | re.VERBOSE, ) # Pattern for pin connections: .PIN(NET) pin_conn_pattern = re.compile( r"\.\s*(?P<pin>\w+)\s*\(\s*(?P<net>[^\s\)]+)\s*\)" ) # Walk the hierarchy down to the leaf instance segments = hier_inst_path.split(sep) current_module = top_module for depth, inst_name in enumerate(segments): body = modules.get(current_module) if body is None: raise ValueError( f"Module {current_module!r} not found while " f"resolving {hier_inst_path!r}" ) found = False cell_type = None pins_block = None for m in inst_pattern.finditer(body): if m.group("inst") == inst_name: cell_type = m.group("cell") pins_block = m.group("pins") found = True break if not found: raise ValueError( f"Instance {inst_name!r} not found inside module {current_module!r}" ) # If not yet at the leaf, descend into the instance's module type if depth < len(segments) - 1: current_module = cell_type else: # Leaf instance: parse its pin connections net_to_path: dict[str, str] = {} for pm in pin_conn_pattern.finditer(pins_block): pin = pm.group("pin") net = pm.group("net") # Note: if the same net appears on multiple pins, last one wins net_to_path[net] = f"{hier_inst_path}{sep}{pin}" return net_to_path raise RuntimeError("Unexpected end of hierarchy resolution")
[docs] def net_to_pin_paths_for_instance_resolved( self, hier_inst_path: str ) -> dict[str, list[str]]: """Resolve hierarchical instance pin paths to leaf pins. Given a hierarchical instance path like: "Inst_LUT4AB_switch_matrix/inst_cus_mux161_buf_JE2BEG3" and a gate-level Verilog netlist, return a mapping: net_name -> [ "full_hier_pin_path1", "full_hier_pin_path2", ... ] where each full hierarchical pin path is resolved down to leaf std-cell pins. Parameters ---------- hier_inst_path : str Hierarchical instance path. Returns ------- dict[str, list[str]] Mapping from net names to lists of resolved leaf pin paths. """ resolved_paths: dict[str, list[str]] = {} net_to_pin = self.net_to_pin_paths_for_instance(hier_inst_path) for net, pin_path in net_to_pin.items(): leaf_pins = self.resolve_hier_pin(pin_path) resolved_paths[net] = leaf_pins return resolved_paths
[docs] def nearest_port_from_pin( self, hier_pin_path: str, reverse: bool = False, num_ports: int = 1 ) -> list[str]: """Nearest port from pin. Given a hierarchical pin path like "inst1/inst2/A0", find the nearest top- level port connected to the same net as that pin. Depending on `reverse`, the search is done towards input ports (reverse=True) or output ports (reverse=False). Parameters ---------- hier_pin_path : str Hierarchical pin path. reverse : bool If True, search towards input ports; if False, towards output ports. num_ports : int Number of nearest ports to return. if less ports are found, return all found, which can be less than `num_ports`. Returns ------- list[str] Hierarchical paths of the nearest top-level ports. Raises ------ ValueError If num_ports is less than 1. """ # Use NetworkX shortest path to find nearest port # Depending on `reverse`, search towards inputs or outputs # If reverse=True, search towards inputs (for setup analysis) # If reverse=False, search towards outputs (for hold analysis) if num_ports < 1: raise ValueError("num_ports must be at least 1") if num_ports == 1: path_without_sentinel, closest_target = ( self.path_to_nearest_target_sentinel( hier_pin_path, self.input_ports if reverse else self.output_ports, reverse=reverse, ) ) return [closest_target] if closest_target is not None else [] if reverse: dist = nx.single_source_shortest_path_length( self.reverse_graph, hier_pin_path ) leaf_dists = [(v, d) for v, d in dist.items() if v in self.input_ports] else: dist = nx.single_source_shortest_path_length(self.graph, hier_pin_path) leaf_dists = [(v, d) for v, d in dist.items() if v in self.output_ports] if len(leaf_dists) == 0: return [] # already sorted by distance from NetworkX return [leaf_dists[i][0] for i in range(min(num_ports, len(leaf_dists)))]
[docs] def nearest_ports_from_instance_pin_nets( self, inst_path: str, reverse: bool = False, num_ports: int = 1 ) -> tuple[dict[str, list[str]], list[str]]: """Nearest ports from instance pin nets. Given a hierarchical instance path like "inst1/inst2", find the nearest top- level ports connected to the same nets as the instance's pins. Depending on `reverse`, the search is done towards input ports (reverse=True) or output ports (reverse=False). Parameters ---------- inst_path : str Hierarchical instance path. reverse : bool If True, search towards input ports; if False, towards output ports. num_ports : int Number of nearest ports to return per pin. if less ports are found, return all found, which can be less than `num_ports`. Returns ------- tuple[dict[str, list[str]], list[str]] Mapping from instance net names to lists of nearest top-level port paths. The list is sorted starting from the nearest ports. """ net_to_pin: dict[str, list[str]] = self.net_to_pin_paths_for_instance_resolved( inst_path ) pin_to_nearest_ports: dict[str, list[str]] = {} pin_to_nearest_ports_list: list[str] = [] for net, pin_paths in net_to_pin.items(): if pin_paths is None or len(pin_paths) == 0: continue # Maybe not only use the first pin path for nearest port search nearest_ports = self.nearest_port_from_pin( pin_paths[0], reverse=reverse, num_ports=num_ports ) pin_to_nearest_ports_list.extend(nearest_ports) pin_to_nearest_ports[net] = nearest_ports # Remove duplicates while preserving order return pin_to_nearest_ports, list(dict.fromkeys(pin_to_nearest_ports_list))
[docs] def get_instance_pins(self, hier_inst_path: str) -> list[str]: """Pin names connected to an instance. Given a hierarchical instance path like: "Inst_LUT4AB_switch_matrix/inst_cus_mux161_buf_JE2BEG3" and a gate-level Verilog netlist, return a list of pin names connected to that instance, in the order they appear in the instantiation. Parameters ---------- hier_inst_path : str Hierarchical instance path. Returns ------- list[str] List of pin names connected to the instance. Raises ------ ValueError If the top module or instance is not found in the netlist. RuntimeError If an unexpected error occurs during hierarchy resolution. """ verilog_src: str = self.verilog_netlist_content top_module: str = self.top_name sep: str = self.hier_sep # Strip comments to avoid bogus matches --- # Remove block comments /* ... */ text = re.sub(r"/\*.*?\*/", "", verilog_src, flags=re.DOTALL) # Remove line comments // ... text = re.sub(r"//.*?$", "", text, flags=re.MULTILINE) # --- 1) Collect all modules: name -> body text --- module_pattern = re.compile( r"module\s+(\w+)\b(.*?endmodule)", re.DOTALL, ) modules = {} for m in module_pattern.finditer(text): name = m.group(1) body = m.group(2) modules[name] = body if top_module not in modules: raise ValueError(f"Top module {top_module!r} not found") # Pattern for instances inside a module --- # Handles: cell_type [#(...)] inst_name ( ... ); inst_pattern = re.compile( r""" (?P<cell>\w+) # cell/module type \s+ (?P<inst>\w+) # instance name \s* \( (?P<pins>[^;]*?) # pin connections until ';' \) \s*; """, re.DOTALL | re.VERBOSE, ) # Pattern for pin connections: .PIN(NET) --- pin_conn_pattern = re.compile( r"\.\s*(?P<pin>\w+)\s*\(\s*(?P<net>[^\s\)]+)\s*\)" ) # Walk the hierarchy down to the leaf instance --- segments = hier_inst_path.split(sep) current_module = top_module for depth, inst_name in enumerate(segments): body = modules.get(current_module) if body is None: raise ValueError( f"Module {current_module!r} not found while " f"resolving {hier_inst_path!r}" ) found = False cell_type = None pins_block = None # find the instance in the current module for m in inst_pattern.finditer(body): if m.group("inst") == inst_name: cell_type = m.group("cell") pins_block = m.group("pins") found = True break if not found: raise ValueError( f"Instance {inst_name!r} not found inside module {current_module!r}" ) # If not yet at the leaf, descend into the instance's module type if depth < len(segments) - 1: current_module = cell_type else: # Leaf instance: parse its pin connections pins_in_order: list[str] = [] for pm in pin_conn_pattern.finditer(pins_block): pin_name = pm.group("pin") pins_in_order.append(pin_name) return pins_in_order # Should never get here raise RuntimeError("Hierarchy resolution fell through unexpectedly")
[docs] def get_module_instance_nets(self, module_name: str) -> dict[str, list[str]]: """Extract, for a module, all inst names and nets connected to each instance. Parameters ---------- module_name : str Name of the module to inspect. Returns ------- dict[str, list[str]] Mapping: instance_name -> [net1, net2, net3, ...] where each list contains all nets connected to that instance, order is the order of the pin connections in the instantiation. Raises ------ ValueError If the specified module is not found in the Verilog source. """ verilog_src: str = self.verilog_netlist_content # Strip comments so patterns don't get confused --- # Remove block comments /* ... */ text = re.sub(r"/\*.*?\*/", "", verilog_src, flags=re.DOTALL) # Remove line comments // ... text = re.sub(r"//.*?$", "", text, flags=re.MULTILINE) # Extract the body of the target module: module <name> ... endmodule module_pattern = re.compile( r"module\s+(\w+)\b(.*?endmodule)", re.DOTALL, ) target_body = None for m in module_pattern.finditer(text): name = m.group(1) body = m.group(2) if name == module_name: target_body = body break if target_body is None: raise ValueError( f"Module {module_name!r} not found in provided Verilog source" ) # Instance pattern inside a module # Matches: # cell_type inst_name ( ... ); inst_pattern = re.compile( r""" (?P<cell>\w+) # cell/module type \s+ (?P<inst>\w+) # instance name \s* \( (?P<pins>[^;]*?) # pin connections up to ';' \) \s*; """, re.DOTALL | re.VERBOSE, ) # Pin connection pattern: .PIN(NET) # NET can be e.g. N1END3, JE2BEG0, ConfigBits[328], etc. pin_conn_pattern = re.compile( r"\.\s*(?P<pin>\w+)\s*\(\s*(?P<net>[^\s\)]+)\s*\)" ) instance_to_nets: dict[str, list[str]] = {} # Find all instantiations and their nets for m in inst_pattern.finditer(target_body): inst_name = m.group("inst") pins_block = m.group("pins") nets: list[str] = [] for pm in pin_conn_pattern.finditer(pins_block): net = pm.group("net") nets.append(net) instance_to_nets[inst_name] = nets return instance_to_nets