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/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 6d6375f75..d03843359 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 @@ -103,33 +100,12 @@ 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: - 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: - 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 @@ -185,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.short_paths[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) @@ -370,3 +344,39 @@ 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..cabb24dbe 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 ############################################################################################################################################################################################### @@ -26,23 +25,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,18 +75,18 @@ 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: - """Given a dictionary of block_names (strings) as keys and Blocks (namedtuples) as corresponding values +def block_exp(blocks: List[NetBlock], shortener: PathShortener, refdes_mode: RefdesMode) -> str: + """Generate the blocks section of the netlist from a list of blocks. Example: (components @@ -109,30 +101,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 +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], 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: @@ -170,7 +163,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 +174,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) + "\n" + ")" ) 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..e446bec84 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,10 @@ 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..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], 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,8 +27,7 @@ def NetBlock( part, value, TransformUtil.Path(tuple(full_path), (), (), ()), - path, - [edgir.libpath(cls) for cls in class_path], + [edgir.libpath(cls) for cls in path_classes], ) @@ -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,10 @@ 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 +487,6 @@ def test_dual_hierarchy_netlist(self) -> None: "", "1uF", ["source"], - ["source"], ["edg.electronics_model.test_netlist.TestFakeSource"], ), net.blocks, @@ -508,7 +498,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 +512,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..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,8 +14,7 @@ def test_placement(self) -> None: part="", value="", full_path=TransformUtil.Path.empty().append_block("U1"), - path=[], - class_path=[], + path_classes=[edgir.LibraryPath()], ) r1 = NetBlock( footprint="Resistor_SMD:R_0603_1608Metric", @@ -22,8 +22,7 @@ def test_placement(self) -> None: part="", value="", full_path=TransformUtil.Path.empty().append_block("R1"), - path=[], - class_path=[], + path_classes=[edgir.LibraryPath()], ) r2 = NetBlock( footprint="Resistor_SMD:R_0603_1608Metric", @@ -31,8 +30,7 @@ def test_placement(self) -> None: part="", value="", full_path=TransformUtil.Path.empty().append_block("R2"), - path=[], - class_path=[], + path_classes=[edgir.LibraryPath()], ) arranged = arrange_blocks([u1, r1, r2]) self.assertEqual(arranged.elts[0][0], TransformUtil.Path.empty().append_block("U1")) @@ -52,8 +50,10 @@ def test_placement_hierarchical(self) -> None: part="", value="", full_path=TransformUtil.Path.empty().append_block("A").append_block("U1"), - path=[], - class_path=[], + path_classes=[ + edgir.LibraryPath(), + edgir.LibraryPath(), + ], ) r1 = NetBlock( footprint="Resistor_SMD:R_0603_1608Metric", @@ -61,8 +61,10 @@ def test_placement_hierarchical(self) -> None: part="", value="", full_path=TransformUtil.Path.empty().append_block("A").append_block("R1"), - path=[], - class_path=[], + path_classes=[ + edgir.LibraryPath(), + edgir.LibraryPath(), + ], ) r2 = NetBlock( footprint="Resistor_SMD:R_0603_1608Metric", @@ -70,8 +72,10 @@ def test_placement_hierarchical(self) -> None: part="", value="", full_path=TransformUtil.Path.empty().append_block("A").append_block("R2"), - path=[], - class_path=[], + path_classes=[ + edgir.LibraryPath(), + edgir.LibraryPath(), + ], ) r3 = NetBlock( footprint="Resistor_SMD:R_0603_1608Metric", @@ -79,8 +83,10 @@ def test_placement_hierarchical(self) -> None: part="", value="", full_path=TransformUtil.Path.empty().append_block("B").append_block("R3"), - path=[], - class_path=[], + path_classes=[ + edgir.LibraryPath(), + edgir.LibraryPath(), + ], ) arranged = arrange_blocks([u1, r1, r2, r3]) @@ -139,8 +145,7 @@ def test_placement_bbox(self) -> None: part="", value="", full_path=TransformUtil.Path.empty().append_block("R1"), - path=[], - class_path=[], + path_classes=[edgir.LibraryPath()], ) r2 = NetBlock( footprint="Resistor_SMD:R_0603_1608Metric", @@ -148,8 +153,7 @@ def test_placement_bbox(self) -> None: part="", value="", full_path=TransformUtil.Path.empty().append_block("R2"), - path=[], - class_path=[], + path_classes=[edgir.LibraryPath()], ) arranged = arrange_blocks( [r1, r2], [BlackBoxBlock(TransformUtil.Path.empty().append_block("box"), (-5, -5, 5, 5))]