From f27f447119cbb84f81e40606d8295b7389c3f0b2 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Fri, 13 Mar 2026 01:09:13 -0700 Subject: [PATCH 1/7] Update NetlistGenerator.py --- edg/electronics_model/NetlistGenerator.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/edg/electronics_model/NetlistGenerator.py b/edg/electronics_model/NetlistGenerator.py index 6d6375f75..998ec6d2e 100644 --- a/edg/electronics_model/NetlistGenerator.py +++ b/edg/electronics_model/NetlistGenerator.py @@ -88,6 +88,22 @@ def __init__(self, design: CompiledDesign): self.design = design + @classmethod + def _is_pseudoblock(cls, name: str, block: edgir.HierarchyBlock) -> bool: + """Returns whether a block is a pseudoblock, where: + - its name starts with (), + - all sub-block names (recursively) start with () + - it contains no footprints, recursively + """ + if not name.startswith('('): + return False + if "fp_is_footprint" in block.meta.members.node: + return False + for block_pair in block.blocks: + if not cls._is_pseudoblock(block_pair.name, block_pair.value.hierarchy): + return False + return True + def process_blocklike( self, path: TransformUtil.Path, block: Union[edgir.Link, edgir.LinkArray, edgir.HierarchyBlock] ) -> None: @@ -108,9 +124,8 @@ def process_blocklike( other_internal_blocks: Dict[str, edgir.BlockLike] = {} for block_pair in block.blocks: - subblock = block_pair.value # ignore pseudoblocks like bridges and adapters that have no internals - if not subblock.hierarchy.blocks and "fp_is_footprint" not in subblock.hierarchy.meta.members.node: + if self._is_pseudoblock(block_pair.name, block_pair.value.hierarchy): other_internal_blocks[block_pair.name] = block_pair.value else: main_internal_blocks[block_pair.name] = block_pair.value From a435f15e185543cdf218d52909c86530eefc4c41 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 14 Mar 2026 16:00:58 -0700 Subject: [PATCH 2/7] eliminate pathname refdes mode, separate shortening phase --- edg/electronics_model/NetlistBackend.py | 4 +- edg/electronics_model/NetlistGenerator.py | 66 +++++++++++++---------- edg/electronics_model/footprint.py | 45 +++++++--------- 3 files changed, 57 insertions(+), 58 deletions(-) diff --git a/edg/electronics_model/NetlistBackend.py b/edg/electronics_model/NetlistBackend.py index 2d205c64c..dc64f564c 100644 --- a/edg/electronics_model/NetlistBackend.py +++ b/edg/electronics_model/NetlistBackend.py @@ -14,9 +14,7 @@ def run(self, design: CompiledDesign, args: Dict[str, str] = {}) -> List[Tuple[e if set(args.keys()) - {"RefdesMode"} != set(): raise ValueError("Invalid argument found in args") refdes_mode_arg = args.get("RefdesMode", "refdesPathNameValue") - if refdes_mode_arg == "pathName": - refdes_mode = kicad.RefdesMode.Pathname - elif refdes_mode_arg == "refdes": + if refdes_mode_arg == "refdes": refdes_mode = kicad.RefdesMode.Conventional elif refdes_mode_arg == "refdesPathNameValue": refdes_mode = kicad.RefdesMode.PathnameAsValue diff --git a/edg/electronics_model/NetlistGenerator.py b/edg/electronics_model/NetlistGenerator.py index 998ec6d2e..2c54e3f6d 100644 --- a/edg/electronics_model/NetlistGenerator.py +++ b/edg/electronics_model/NetlistGenerator.py @@ -22,8 +22,7 @@ class NetBlock(NamedTuple): part: str value: str # gets written directly to footprint full_path: TransformUtil.Path # full path to this footprint - path: List[str] # short path to this footprint - class_path: List[edgir.LibraryPath] # classes on short path to this footprint + path_classes: List[edgir.LibraryPath] # all classes on the full path, index-aligned with full_path class NetPin(NamedTuple): @@ -82,8 +81,6 @@ def flatten_port(path: TransformUtil.Path, port: edgir.PortLike) -> Iterable[Tra def __init__(self, design: CompiledDesign): self.all_scopes = [BoardScope.empty(TransformUtil.Path.empty())] # list of unique scopes self.scopes: Scopes = {TransformUtil.Path.empty(): self.all_scopes[0]} - - self.short_paths: Dict[TransformUtil.Path, List[str]] = {TransformUtil.Path.empty(): []} # seed root self.class_paths: ClassPaths = {TransformUtil.Path.empty(): []} # seed root self.design = design @@ -119,32 +116,10 @@ def process_blocklike( for link_pair in block.links: # links considered to be the same scope as self self.scopes[path.append_link(link_pair.name)] = scope - # generate short paths for children first, for Blocks only - main_internal_blocks: Dict[str, edgir.BlockLike] = {} - other_internal_blocks: Dict[str, edgir.BlockLike] = {} - - for block_pair in block.blocks: - # ignore pseudoblocks like bridges and adapters that have no internals - if self._is_pseudoblock(block_pair.name, block_pair.value.hierarchy): - other_internal_blocks[block_pair.name] = block_pair.value - else: - main_internal_blocks[block_pair.name] = block_pair.value - - short_path = self.short_paths[path] class_path = self.class_paths[path] + for block_pair in block.blocks: + self.class_paths[path.append_block(block_pair.name)] = class_path + [block_pair.value.hierarchy.self_class] - if len(main_internal_blocks) == 1 and short_path: # never shorten top-level blocks - name = list(main_internal_blocks.keys())[0] - self.short_paths[path.append_block(name)] = short_path - self.class_paths[path.append_block(name)] = class_path - else: - for name, subblock in main_internal_blocks.items(): - self.short_paths[path.append_block(name)] = short_path + [name] - self.class_paths[path.append_block(name)] = class_path + [subblock.hierarchy.self_class] - - for name, subblock in other_internal_blocks.items(): - self.short_paths[path.append_block(name)] = short_path + [name] - self.class_paths[path.append_block(name)] = class_path + [subblock.hierarchy.self_class] elif isinstance(block, (edgir.Link, edgir.LinkArray)): for link_pair in block.links: self.scopes[path.append_link(link_pair.name)] = scope @@ -201,7 +176,7 @@ def process_blocklike( part_str = " ".join(filter(None, part_comps)) value_str = value if value else (part if part else "") scope.footprints[path] = NetBlock( - footprint_name, refdes, part_str, value_str, path, self.short_paths[path], self.class_paths[path] + footprint_name, refdes, part_str, value_str, path, self.class_paths[path] ) for pin_spec in footprint_pinning: @@ -385,3 +360,36 @@ def run(self) -> Netlist: self.transform_design(self.design.design) return self.scope_to_netlist(self.all_scopes[0]) # TODO support multiple scopes + + +class PathShortener: + """Given a bunch of blocks with full paths, determine path shortenings that eliminate + path components that are the only footprint-containing internal block.""" + def __init__(self, blocks: List[NetBlock]) -> None: + # construct list of children for each path + # note since this is created from a list of footprints, all paths are guaranteed to contain footprints + self._block_children: Dict[Tuple[str, ...], List[str]] = {} + for block in blocks: + block_path = block.full_path.blocks + for i in range(len(block_path)): + parent_path = block_path[:i] + path_component = block_path[i] + parent_list = self._block_children.setdefault(parent_path, []) + if path_component not in parent_list: + parent_list.append(path_component) + + def shorten(self, path: TransformUtil.Path, classes: List[edgir.LibraryPath]) -> Tuple[List[str], List[edgir.LibraryPath]]: + assert len(path.blocks) == len(classes) + new_blocks = [] + new_classes = [] + block_path = path.blocks + for i, path_comp in enumerate(block_path): + # test whether to add component i + # always keep rootmost component + full_parent_path = tuple(block_path[:i]) + if i > 0 and len(self._block_children.get(full_parent_path, [])) <= 1: # only one child, so can shorten + continue + else: + new_blocks.append(path_comp) + new_classes.append(classes[i]) + return new_blocks, new_classes diff --git a/edg/electronics_model/footprint.py b/edg/electronics_model/footprint.py index f17c32f57..c6556cd4d 100644 --- a/edg/electronics_model/footprint.py +++ b/edg/electronics_model/footprint.py @@ -2,14 +2,13 @@ from enum import Enum, auto from typing import List -from .NetlistGenerator import Netlist, NetBlock, Net +from .NetlistGenerator import Netlist, NetBlock, Net, PathShortener from .. import edgir class RefdesMode(Enum): PathnameAsValue = auto() # conventional refdes w/ pathname as value, except for TPs Conventional = auto() # conventional refdes only, value passed through - Pathname = auto() # pathname as refdes, value passed through ############################################################################################################################################################################################### @@ -25,24 +24,16 @@ def gen_header() -> str: """2. Generating Blocks""" - -def block_name(block: NetBlock, refdes_mode: RefdesMode) -> str: - if refdes_mode == RefdesMode.Pathname: - return ".".join(block.path) - else: - return block.refdes # default is conventional refdes - - def gen_block_comp(block_name: str) -> str: return f'(comp (ref "{block_name}")' -def gen_block_value(block: NetBlock, refdes_mode: RefdesMode) -> str: +def gen_block_value(block: NetBlock, short_path: List[str], refdes_mode: RefdesMode) -> str: if refdes_mode == RefdesMode.PathnameAsValue: if "TP" in block.refdes: # test points keep their value return f'(value "{block.value}")' else: - pathname = ".".join(block.path) + pathname = ".".join(short_path) return f'(value "{pathname}")' else: return f'(value "{block.value}")' @@ -83,17 +74,17 @@ def gen_block_prop_sheetfile(block_path: List[edgir.LibraryPath]) -> str: return f'(property (name "Sheetfile") (value "{value}"))' -def gen_block_prop_edg(block: NetBlock) -> str: +def gen_block_prop_edg(block: NetBlock, short_path: List[str]) -> str: return ( f'(property (name "edg_path") (value "{".".join(block.full_path.to_tuple())}"))\n' - + f' (property (name "edg_short_path") (value "{".".join(block.path)}"))\n' + + f' (property (name "edg_short_path") (value "{".".join(short_path)}"))\n' + f' (property (name "edg_refdes") (value "{block.refdes}"))\n' + f' (property (name "edg_part") (value "{block.part}"))\n' + f' (property (name "edg_value") (value "{block.value}"))' ) -def block_exp(blocks: List[NetBlock], refdes_mode: RefdesMode) -> str: +def block_exp(blocks: List[NetBlock], shortener: PathShortener, refdes_mode: RefdesMode) -> str: """Given a dictionary of block_names (strings) as keys and Blocks (namedtuples) as corresponding values Example: @@ -109,30 +100,31 @@ def block_exp(blocks: List[NetBlock], refdes_mode: RefdesMode) -> str: """ result = "(components" for block in blocks: + short_path, short_class = shortener.shorten(block.full_path, block.path_classes) result += ( "\n" - + gen_block_comp(block_name(block, refdes_mode)) + + gen_block_comp(block.refdes) + "\n" + " " - + gen_block_value(block, refdes_mode) + + gen_block_value(block, short_path, refdes_mode) + "\n" + " " + gen_block_footprint(block.footprint) + "\n" + " " - + gen_block_prop_sheetname(block.path) + + gen_block_prop_sheetname(short_path) + "\n" + " " - + gen_block_prop_sheetfile(block.class_path) + + gen_block_prop_sheetfile(short_class) + "\n" + " " - + gen_block_prop_edg(block) + + gen_block_prop_edg(block, short_path) + "\n" + " " - + gen_block_sheetpath(block.path[:-1]) + + gen_block_sheetpath(short_path[:-1]) + "\n" + " " - + gen_block_tstamp(block.path) + + gen_block_tstamp(short_path) ) return result + ")" @@ -150,7 +142,7 @@ def gen_net_pin(block_name: str, pin_name: str) -> str: return "(node (ref {}) (pin {}))".format(block_name, pin_name) -def net_exp(nets: List[Net], blocks: List[NetBlock], refdes_mode: RefdesMode) -> str: +def net_exp(nets: List[Net], blocks: List[NetBlock], shortener: PathShortener, refdes_mode: RefdesMode) -> str: """Given a dictionary of net names (strings) as keys and a list of connected Pins (namedtuples) as corresponding values Example: @@ -170,7 +162,7 @@ def net_exp(nets: List[Net], blocks: List[NetBlock], refdes_mode: RefdesMode) -> for i, net in enumerate(nets): result += "\n" + gen_net_header(i + 1, net.name) for pin in net.pins: - result += "\n " + gen_net_pin(block_name(block_dict[pin.block_path], refdes_mode), pin.pin_name) + result += "\n " + gen_net_pin(block_dict[pin.block_path].refdes, pin.pin_name) result += ")" return result + ")" @@ -181,12 +173,13 @@ def net_exp(nets: List[Net], blocks: List[NetBlock], refdes_mode: RefdesMode) -> def generate_netlist(netlist: Netlist, refdes_mode: RefdesMode) -> str: + shortener = PathShortener(list(netlist.blocks)) return ( gen_header() + "\n" - + block_exp(netlist.blocks, refdes_mode) + + block_exp(netlist.blocks, shortener, refdes_mode) + "\n" - + net_exp(netlist.nets, netlist.blocks, refdes_mode) + + net_exp(netlist.nets, netlist.blocks, shortener, refdes_mode) + "\n" + ")" ) From d58cc2b06126c74bb0f54350922c0097318c292a Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 14 Mar 2026 16:08:17 -0700 Subject: [PATCH 3/7] clean up tests --- .../test_kicad_import_netlist.py | 3 --- edg/electronics_model/test_bundle_netlist.py | 8 ------ .../test_multipack_netlist.py | 4 +-- edg/electronics_model/test_netlist.py | 19 ++----------- edg/electronics_model/test_netlist_wrapper.py | 1 - edg/electronics_model/test_partplacer.py | 27 +++++++------------ 6 files changed, 12 insertions(+), 50 deletions(-) diff --git a/edg/abstract_parts/test_kicad_import_netlist.py b/edg/abstract_parts/test_kicad_import_netlist.py index fc7489c25..b88bd11ed 100644 --- a/edg/abstract_parts/test_kicad_import_netlist.py +++ b/edg/abstract_parts/test_kicad_import_netlist.py @@ -103,7 +103,6 @@ def test_netlist(self) -> None: "Sensor_Temperature:MCP9700AT-ETT", "MCP9700AT-ETT", ["dut", "U1"], - ["dut", "U1"], [ "edg.electronics_model.test_kicad_import_blackbox.KiCadBlackboxBlock", "edg.electronics_model.KiCadSchematicBlock.KiCadBlackbox", @@ -119,7 +118,6 @@ def test_netlist(self) -> None: "Graphic:SYM_ESD_Small", "SYM_ESD_Small", ["dut", "SYM1"], - ["dut", "SYM1"], [ "edg.electronics_model.test_kicad_import_blackbox.KiCadBlackboxBlock", "edg.electronics_model.KiCadSchematicBlock.KiCadBlackbox", @@ -134,7 +132,6 @@ def test_netlist(self) -> None: "", "", ["dut", "res"], - ["dut", "res"], [ "edg.electronics_model.test_kicad_import_blackbox.KiCadBlackboxBlock", "edg.abstract_parts.test_kicad_import_netlist.DummyResistor", diff --git a/edg/electronics_model/test_bundle_netlist.py b/edg/electronics_model/test_bundle_netlist.py index 1f7919708..79915449e 100644 --- a/edg/electronics_model/test_bundle_netlist.py +++ b/edg/electronics_model/test_bundle_netlist.py @@ -209,7 +209,6 @@ def test_spi_netlist(self) -> None: "", "WeirdSpiController", ["controller"], - ["controller"], ["edg.electronics_model.test_bundle_netlist.TestFakeSpiController"], ), net.blocks, @@ -221,7 +220,6 @@ def test_spi_netlist(self) -> None: "", "WeirdSpiPeripheral", ["peripheral1"], - ["peripheral1"], ["edg.electronics_model.test_bundle_netlist.TestFakeSpiPeripheral"], ), net.blocks, @@ -233,7 +231,6 @@ def test_spi_netlist(self) -> None: "", "WeirdSpiPeripheral", ["peripheral2"], - ["peripheral2"], ["edg.electronics_model.test_bundle_netlist.TestFakeSpiPeripheral"], ), net.blocks, @@ -271,7 +268,6 @@ def test_uart_netlist(self) -> None: "", "1k", ["a"], - ["a"], ["edg.electronics_model.test_bundle_netlist.TestFakeUartBlock"], ), net.blocks, @@ -283,7 +279,6 @@ def test_uart_netlist(self) -> None: "", "1k", ["b"], - ["b"], ["edg.electronics_model.test_bundle_netlist.TestFakeUartBlock"], ), net.blocks, @@ -323,7 +318,6 @@ def test_can_netlist(self) -> None: "", "120", ["node1"], - ["node1"], ["edg.electronics_model.test_bundle_netlist.TestFakeCanBlock"], ), net.blocks, @@ -335,7 +329,6 @@ def test_can_netlist(self) -> None: "", "120", ["node2"], - ["node2"], ["edg.electronics_model.test_bundle_netlist.TestFakeCanBlock"], ), net.blocks, @@ -347,7 +340,6 @@ def test_can_netlist(self) -> None: "", "120", ["node3"], - ["node3"], ["edg.electronics_model.test_bundle_netlist.TestFakeCanBlock"], ), net.blocks, diff --git a/edg/electronics_model/test_multipack_netlist.py b/edg/electronics_model/test_multipack_netlist.py index ba652dae1..8eafd616f 100644 --- a/edg/electronics_model/test_multipack_netlist.py +++ b/edg/electronics_model/test_multipack_netlist.py @@ -123,7 +123,6 @@ def test_packed_netlist(self) -> None: "", "1uF", ["source"], - ["source"], ["edg.electronics_model.test_netlist.TestFakeSource"], ), net.blocks, @@ -135,8 +134,7 @@ def test_packed_netlist(self) -> None: "", "1k", ["sink", "device"], - ["sink"], - ["edg.electronics_model.test_multipack_netlist.TestPackedSink"], + ["edg.electronics_model.test_multipack_netlist.TestPackedSink", "edg.electronics_model.test_netlist.TestFakeSink"], ), net.blocks, ) diff --git a/edg/electronics_model/test_netlist.py b/edg/electronics_model/test_netlist.py index 39b305843..578684a2c 100644 --- a/edg/electronics_model/test_netlist.py +++ b/edg/electronics_model/test_netlist.py @@ -19,7 +19,7 @@ def NetPin(block_path: List[str], pin_name: str) -> RawNetPin: def NetBlock( - footprint: str, refdes: str, part: str, value: str, full_path: List[str], path: List[str], class_path: List[str] + footprint: str, refdes: str, part: str, value: str, full_path: List[str], class_path: List[str] ) -> RawNetBlock: return RawNetBlock( footprint, @@ -27,7 +27,6 @@ def NetBlock( part, value, TransformUtil.Path(tuple(full_path), (), (), ()), - path, [edgir.libpath(cls) for cls in class_path], ) @@ -214,7 +213,6 @@ def test_single_netlist(self) -> None: "", "1uF", ["source"], - ["source"], ["edg.electronics_model.test_netlist.TestFakeSource"], ), net.blocks, @@ -252,7 +250,6 @@ def test_basic_netlist(self) -> None: "", "1uF", ["source"], - ["source"], ["edg.electronics_model.test_netlist.TestFakeSource"], ), net.blocks, @@ -264,7 +261,6 @@ def test_basic_netlist(self) -> None: "", "1k", ["sink"], - ["sink"], ["edg.electronics_model.test_netlist.TestFakeSink"], ), net.blocks, @@ -304,7 +300,6 @@ def test_multisink_netlist(self) -> None: "", "1uF", ["source"], - ["source"], ["edg.electronics_model.test_netlist.TestFakeSource"], ), net.blocks, @@ -316,7 +311,6 @@ def test_multisink_netlist(self) -> None: "", "1k", ["sink1"], - ["sink1"], ["edg.electronics_model.test_netlist.TestFakeSink"], ), net.blocks, @@ -328,7 +322,6 @@ def test_multisink_netlist(self) -> None: "", "1k", ["sink2"], - ["sink2"], ["edg.electronics_model.test_netlist.TestFakeSink"], ), net.blocks, @@ -378,7 +371,6 @@ def test_multinet_netlist(self) -> None: "", "1uF", ["source"], - ["source"], ["edg.electronics_model.test_netlist.TestFakeSource"], ), net.blocks, @@ -390,7 +382,6 @@ def test_multinet_netlist(self) -> None: "", "LD1117V33", ["adapter"], - ["adapter"], ["edg.electronics_model.test_netlist.TestFakeAdapter"], ), net.blocks, @@ -402,7 +393,6 @@ def test_multinet_netlist(self) -> None: "", "1k", ["sink"], - ["sink"], ["edg.electronics_model.test_netlist.TestFakeSink"], ), net.blocks, @@ -442,7 +432,6 @@ def test_hierarchy_netlist(self) -> None: "", "1uF", ["source"], - ["source"], ["edg.electronics_model.test_netlist.TestFakeSource"], ), net.blocks, @@ -454,8 +443,7 @@ def test_hierarchy_netlist(self) -> None: "", "1k", ["sink", "block"], - ["sink"], - ["edg.electronics_model.test_netlist.TestFakeSinkHierarchy"], + ["edg.electronics_model.test_netlist.TestFakeSinkHierarchy", "edg.electronics_model.test_netlist.TestFakeSink"], ), net.blocks, ) @@ -496,7 +484,6 @@ def test_dual_hierarchy_netlist(self) -> None: "", "1uF", ["source"], - ["source"], ["edg.electronics_model.test_netlist.TestFakeSource"], ), net.blocks, @@ -508,7 +495,6 @@ def test_dual_hierarchy_netlist(self) -> None: "", "1k", ["sink", "block1"], - ["sink", "block1"], [ "edg.electronics_model.test_netlist.TestFakeDualSinkHierarchy", "edg.electronics_model.test_netlist.TestFakeSink", @@ -523,7 +509,6 @@ def test_dual_hierarchy_netlist(self) -> None: "", "1k", ["sink", "block2"], - ["sink", "block2"], [ "edg.electronics_model.test_netlist.TestFakeDualSinkHierarchy", "edg.electronics_model.test_netlist.TestFakeSink", diff --git a/edg/electronics_model/test_netlist_wrapper.py b/edg/electronics_model/test_netlist_wrapper.py index 5bfa6df78..e83375ad5 100644 --- a/edg/electronics_model/test_netlist_wrapper.py +++ b/edg/electronics_model/test_netlist_wrapper.py @@ -56,7 +56,6 @@ def test_warpper_netlist(self) -> None: "", "100", ["sink"], - ["sink"], ["edg.electronics_model.test_netlist_wrapper.SinkWrapperBlock"], ), net.blocks, diff --git a/edg/electronics_model/test_partplacer.py b/edg/electronics_model/test_partplacer.py index efc54f9d4..528a4e9aa 100644 --- a/edg/electronics_model/test_partplacer.py +++ b/edg/electronics_model/test_partplacer.py @@ -13,8 +13,7 @@ def test_placement(self) -> None: part="", value="", full_path=TransformUtil.Path.empty().append_block("U1"), - path=[], - class_path=[], + path_classes=[], ) r1 = NetBlock( footprint="Resistor_SMD:R_0603_1608Metric", @@ -22,8 +21,7 @@ def test_placement(self) -> None: part="", value="", full_path=TransformUtil.Path.empty().append_block("R1"), - path=[], - class_path=[], + path_classes=[], ) r2 = NetBlock( footprint="Resistor_SMD:R_0603_1608Metric", @@ -31,8 +29,7 @@ def test_placement(self) -> None: part="", value="", full_path=TransformUtil.Path.empty().append_block("R2"), - path=[], - class_path=[], + path_classes=[], ) arranged = arrange_blocks([u1, r1, r2]) self.assertEqual(arranged.elts[0][0], TransformUtil.Path.empty().append_block("U1")) @@ -52,8 +49,7 @@ def test_placement_hierarchical(self) -> None: part="", value="", full_path=TransformUtil.Path.empty().append_block("A").append_block("U1"), - path=[], - class_path=[], + path_classes=[], ) r1 = NetBlock( footprint="Resistor_SMD:R_0603_1608Metric", @@ -61,8 +57,7 @@ def test_placement_hierarchical(self) -> None: part="", value="", full_path=TransformUtil.Path.empty().append_block("A").append_block("R1"), - path=[], - class_path=[], + path_classes=[], ) r2 = NetBlock( footprint="Resistor_SMD:R_0603_1608Metric", @@ -70,8 +65,7 @@ def test_placement_hierarchical(self) -> None: part="", value="", full_path=TransformUtil.Path.empty().append_block("A").append_block("R2"), - path=[], - class_path=[], + path_classes=[], ) r3 = NetBlock( footprint="Resistor_SMD:R_0603_1608Metric", @@ -79,8 +73,7 @@ def test_placement_hierarchical(self) -> None: part="", value="", full_path=TransformUtil.Path.empty().append_block("B").append_block("R3"), - path=[], - class_path=[], + path_classes=[], ) arranged = arrange_blocks([u1, r1, r2, r3]) @@ -139,8 +132,7 @@ def test_placement_bbox(self) -> None: part="", value="", full_path=TransformUtil.Path.empty().append_block("R1"), - path=[], - class_path=[], + path_classes=[], ) r2 = NetBlock( footprint="Resistor_SMD:R_0603_1608Metric", @@ -148,8 +140,7 @@ def test_placement_bbox(self) -> None: part="", value="", full_path=TransformUtil.Path.empty().append_block("R2"), - path=[], - class_path=[], + path_classes=[], ) arranged = arrange_blocks( [r1, r2], [BlackBoxBlock(TransformUtil.Path.empty().append_block("box"), (-5, -5, 5, 5))] From 5c379581341058436930c60d294164a61a949276 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 14 Mar 2026 16:19:18 -0700 Subject: [PATCH 4/7] blacken and cleaning --- edg/electronics_model/NetlistGenerator.py | 29 +++++-------------- edg/electronics_model/footprint.py | 1 + .../test_multipack_netlist.py | 5 +++- edg/electronics_model/test_netlist.py | 5 +++- 4 files changed, 17 insertions(+), 23 deletions(-) diff --git a/edg/electronics_model/NetlistGenerator.py b/edg/electronics_model/NetlistGenerator.py index 2c54e3f6d..d03843359 100644 --- a/edg/electronics_model/NetlistGenerator.py +++ b/edg/electronics_model/NetlistGenerator.py @@ -85,22 +85,6 @@ def __init__(self, design: CompiledDesign): self.design = design - @classmethod - def _is_pseudoblock(cls, name: str, block: edgir.HierarchyBlock) -> bool: - """Returns whether a block is a pseudoblock, where: - - its name starts with (), - - all sub-block names (recursively) start with () - - it contains no footprints, recursively - """ - if not name.startswith('('): - return False - if "fp_is_footprint" in block.meta.members.node: - return False - for block_pair in block.blocks: - if not cls._is_pseudoblock(block_pair.name, block_pair.value.hierarchy): - return False - return True - def process_blocklike( self, path: TransformUtil.Path, block: Union[edgir.Link, edgir.LinkArray, edgir.HierarchyBlock] ) -> None: @@ -118,7 +102,9 @@ def process_blocklike( class_path = self.class_paths[path] for block_pair in block.blocks: - self.class_paths[path.append_block(block_pair.name)] = class_path + [block_pair.value.hierarchy.self_class] + self.class_paths[path.append_block(block_pair.name)] = class_path + [ + block_pair.value.hierarchy.self_class + ] elif isinstance(block, (edgir.Link, edgir.LinkArray)): for link_pair in block.links: @@ -175,9 +161,7 @@ def process_blocklike( part_comps = [part, f"({mfr})" if mfr else ""] part_str = " ".join(filter(None, part_comps)) value_str = value if value else (part if part else "") - scope.footprints[path] = NetBlock( - footprint_name, refdes, part_str, value_str, path, self.class_paths[path] - ) + scope.footprints[path] = NetBlock(footprint_name, refdes, part_str, value_str, path, self.class_paths[path]) for pin_spec in footprint_pinning: assert isinstance(pin_spec, str) @@ -365,6 +349,7 @@ def run(self) -> Netlist: class PathShortener: """Given a bunch of blocks with full paths, determine path shortenings that eliminate path components that are the only footprint-containing internal block.""" + def __init__(self, blocks: List[NetBlock]) -> None: # construct list of children for each path # note since this is created from a list of footprints, all paths are guaranteed to contain footprints @@ -378,7 +363,9 @@ def __init__(self, blocks: List[NetBlock]) -> None: if path_component not in parent_list: parent_list.append(path_component) - def shorten(self, path: TransformUtil.Path, classes: List[edgir.LibraryPath]) -> Tuple[List[str], List[edgir.LibraryPath]]: + def shorten( + self, path: TransformUtil.Path, classes: List[edgir.LibraryPath] + ) -> Tuple[List[str], List[edgir.LibraryPath]]: assert len(path.blocks) == len(classes) new_blocks = [] new_classes = [] diff --git a/edg/electronics_model/footprint.py b/edg/electronics_model/footprint.py index c6556cd4d..2ed24458f 100644 --- a/edg/electronics_model/footprint.py +++ b/edg/electronics_model/footprint.py @@ -24,6 +24,7 @@ def gen_header() -> str: """2. Generating Blocks""" + def gen_block_comp(block_name: str) -> str: return f'(comp (ref "{block_name}")' diff --git a/edg/electronics_model/test_multipack_netlist.py b/edg/electronics_model/test_multipack_netlist.py index 8eafd616f..e446bec84 100644 --- a/edg/electronics_model/test_multipack_netlist.py +++ b/edg/electronics_model/test_multipack_netlist.py @@ -134,7 +134,10 @@ def test_packed_netlist(self) -> None: "", "1k", ["sink", "device"], - ["edg.electronics_model.test_multipack_netlist.TestPackedSink", "edg.electronics_model.test_netlist.TestFakeSink"], + [ + "edg.electronics_model.test_multipack_netlist.TestPackedSink", + "edg.electronics_model.test_netlist.TestFakeSink", + ], ), net.blocks, ) diff --git a/edg/electronics_model/test_netlist.py b/edg/electronics_model/test_netlist.py index 578684a2c..0a07408c7 100644 --- a/edg/electronics_model/test_netlist.py +++ b/edg/electronics_model/test_netlist.py @@ -443,7 +443,10 @@ def test_hierarchy_netlist(self) -> None: "", "1k", ["sink", "block"], - ["edg.electronics_model.test_netlist.TestFakeSinkHierarchy", "edg.electronics_model.test_netlist.TestFakeSink"], + [ + "edg.electronics_model.test_netlist.TestFakeSinkHierarchy", + "edg.electronics_model.test_netlist.TestFakeSink", + ], ), net.blocks, ) From 1befb22278eca597cf2b85d9e1f6705e1e22f2d4 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 14 Mar 2026 16:39:45 -0700 Subject: [PATCH 5/7] cleaning --- edg/electronics_model/footprint.py | 4 ++-- edg/electronics_model/test_netlist.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/edg/electronics_model/footprint.py b/edg/electronics_model/footprint.py index 2ed24458f..3c4e72bda 100644 --- a/edg/electronics_model/footprint.py +++ b/edg/electronics_model/footprint.py @@ -143,7 +143,7 @@ def gen_net_pin(block_name: str, pin_name: str) -> str: return "(node (ref {}) (pin {}))".format(block_name, pin_name) -def net_exp(nets: List[Net], blocks: List[NetBlock], shortener: PathShortener, refdes_mode: RefdesMode) -> str: +def net_exp(nets: List[Net], blocks: List[NetBlock]) -> str: """Given a dictionary of net names (strings) as keys and a list of connected Pins (namedtuples) as corresponding values Example: @@ -180,7 +180,7 @@ def generate_netlist(netlist: Netlist, refdes_mode: RefdesMode) -> str: + "\n" + block_exp(netlist.blocks, shortener, refdes_mode) + "\n" - + net_exp(netlist.nets, netlist.blocks, shortener, refdes_mode) + + net_exp(netlist.nets, netlist.blocks) + "\n" + ")" ) diff --git a/edg/electronics_model/test_netlist.py b/edg/electronics_model/test_netlist.py index 0a07408c7..45afb74a6 100644 --- a/edg/electronics_model/test_netlist.py +++ b/edg/electronics_model/test_netlist.py @@ -19,7 +19,7 @@ def NetPin(block_path: List[str], pin_name: str) -> RawNetPin: def NetBlock( - footprint: str, refdes: str, part: str, value: str, full_path: List[str], class_path: List[str] + footprint: str, refdes: str, part: str, value: str, full_path: List[str], path_classes: List[str] ) -> RawNetBlock: return RawNetBlock( footprint, @@ -27,7 +27,7 @@ def NetBlock( part, value, TransformUtil.Path(tuple(full_path), (), (), ()), - [edgir.libpath(cls) for cls in class_path], + [edgir.libpath(cls) for cls in path_classes], ) From 25b2ddf39518854d4358b75e653db203ff95cd86 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 14 Mar 2026 16:40:31 -0700 Subject: [PATCH 6/7] Update footprint.py --- edg/electronics_model/footprint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edg/electronics_model/footprint.py b/edg/electronics_model/footprint.py index 3c4e72bda..cabb24dbe 100644 --- a/edg/electronics_model/footprint.py +++ b/edg/electronics_model/footprint.py @@ -86,7 +86,7 @@ def gen_block_prop_edg(block: NetBlock, short_path: List[str]) -> str: def block_exp(blocks: List[NetBlock], shortener: PathShortener, refdes_mode: RefdesMode) -> str: - """Given a dictionary of block_names (strings) as keys and Blocks (namedtuples) as corresponding values + """Generate the blocks section of the netlist from a list of blocks. Example: (components From 94f5dbd10b6268752ba474b85c004dac67466c3b Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 14 Mar 2026 16:54:06 -0700 Subject: [PATCH 7/7] cleaning --- edg/electronics_model/test_partplacer.py | 31 +++++++++++++++++------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/edg/electronics_model/test_partplacer.py b/edg/electronics_model/test_partplacer.py index 528a4e9aa..fc79ccfe6 100644 --- a/edg/electronics_model/test_partplacer.py +++ b/edg/electronics_model/test_partplacer.py @@ -3,6 +3,7 @@ from . import TransformUtil from .NetlistGenerator import NetBlock from .SvgPcbBackend import arrange_blocks, flatten_packed_block, PlacedBlock, BlackBoxBlock +from .. import edgir class PartPlacerTestCase(unittest.TestCase): @@ -13,7 +14,7 @@ def test_placement(self) -> None: part="", value="", full_path=TransformUtil.Path.empty().append_block("U1"), - path_classes=[], + path_classes=[edgir.LibraryPath()], ) r1 = NetBlock( footprint="Resistor_SMD:R_0603_1608Metric", @@ -21,7 +22,7 @@ def test_placement(self) -> None: part="", value="", full_path=TransformUtil.Path.empty().append_block("R1"), - path_classes=[], + path_classes=[edgir.LibraryPath()], ) r2 = NetBlock( footprint="Resistor_SMD:R_0603_1608Metric", @@ -29,7 +30,7 @@ def test_placement(self) -> None: part="", value="", full_path=TransformUtil.Path.empty().append_block("R2"), - path_classes=[], + path_classes=[edgir.LibraryPath()], ) arranged = arrange_blocks([u1, r1, r2]) self.assertEqual(arranged.elts[0][0], TransformUtil.Path.empty().append_block("U1")) @@ -49,7 +50,10 @@ def test_placement_hierarchical(self) -> None: part="", value="", full_path=TransformUtil.Path.empty().append_block("A").append_block("U1"), - path_classes=[], + path_classes=[ + edgir.LibraryPath(), + edgir.LibraryPath(), + ], ) r1 = NetBlock( footprint="Resistor_SMD:R_0603_1608Metric", @@ -57,7 +61,10 @@ def test_placement_hierarchical(self) -> None: part="", value="", full_path=TransformUtil.Path.empty().append_block("A").append_block("R1"), - path_classes=[], + path_classes=[ + edgir.LibraryPath(), + edgir.LibraryPath(), + ], ) r2 = NetBlock( footprint="Resistor_SMD:R_0603_1608Metric", @@ -65,7 +72,10 @@ def test_placement_hierarchical(self) -> None: part="", value="", full_path=TransformUtil.Path.empty().append_block("A").append_block("R2"), - path_classes=[], + path_classes=[ + edgir.LibraryPath(), + edgir.LibraryPath(), + ], ) r3 = NetBlock( footprint="Resistor_SMD:R_0603_1608Metric", @@ -73,7 +83,10 @@ def test_placement_hierarchical(self) -> None: part="", value="", full_path=TransformUtil.Path.empty().append_block("B").append_block("R3"), - path_classes=[], + path_classes=[ + edgir.LibraryPath(), + edgir.LibraryPath(), + ], ) arranged = arrange_blocks([u1, r1, r2, r3]) @@ -132,7 +145,7 @@ def test_placement_bbox(self) -> None: part="", value="", full_path=TransformUtil.Path.empty().append_block("R1"), - path_classes=[], + path_classes=[edgir.LibraryPath()], ) r2 = NetBlock( footprint="Resistor_SMD:R_0603_1608Metric", @@ -140,7 +153,7 @@ def test_placement_bbox(self) -> None: part="", value="", full_path=TransformUtil.Path.empty().append_block("R2"), - path_classes=[], + path_classes=[edgir.LibraryPath()], ) arranged = arrange_blocks( [r1, r2], [BlackBoxBlock(TransformUtil.Path.empty().append_block("box"), (-5, -5, 5, 5))]