Skip to content

Commit 11ca30e

Browse files
authored
Refactor netlist shortener (#452)
Instead of calculating the short path in the design crawl, this calculates it during netlist generation. Also changes the algorithm to eliminate path components with no footprint-containing siblings. Previously, it approximated this, but was brittle to sub-blocks even if they contained no leaf footprints. This is needed for compositional passive, where there are going to be nested pseudoblocks. Removes Pathname mode for netlisting. It's effectively superseded by PathnameAsValue mode. Reference netlists are unchanged.
1 parent a7e274c commit 11ca30e

File tree

9 files changed

+95
-112
lines changed

9 files changed

+95
-112
lines changed

edg/abstract_parts/test_kicad_import_netlist.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,6 @@ def test_netlist(self) -> None:
103103
"Sensor_Temperature:MCP9700AT-ETT",
104104
"MCP9700AT-ETT",
105105
["dut", "U1"],
106-
["dut", "U1"],
107106
[
108107
"edg.electronics_model.test_kicad_import_blackbox.KiCadBlackboxBlock",
109108
"edg.electronics_model.KiCadSchematicBlock.KiCadBlackbox",
@@ -119,7 +118,6 @@ def test_netlist(self) -> None:
119118
"Graphic:SYM_ESD_Small",
120119
"SYM_ESD_Small",
121120
["dut", "SYM1"],
122-
["dut", "SYM1"],
123121
[
124122
"edg.electronics_model.test_kicad_import_blackbox.KiCadBlackboxBlock",
125123
"edg.electronics_model.KiCadSchematicBlock.KiCadBlackbox",
@@ -134,7 +132,6 @@ def test_netlist(self) -> None:
134132
"",
135133
"",
136134
["dut", "res"],
137-
["dut", "res"],
138135
[
139136
"edg.electronics_model.test_kicad_import_blackbox.KiCadBlackboxBlock",
140137
"edg.abstract_parts.test_kicad_import_netlist.DummyResistor",

edg/electronics_model/NetlistBackend.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@ def run(self, design: CompiledDesign, args: Dict[str, str] = {}) -> List[Tuple[e
1414
if set(args.keys()) - {"RefdesMode"} != set():
1515
raise ValueError("Invalid argument found in args")
1616
refdes_mode_arg = args.get("RefdesMode", "refdesPathNameValue")
17-
if refdes_mode_arg == "pathName":
18-
refdes_mode = kicad.RefdesMode.Pathname
19-
elif refdes_mode_arg == "refdes":
17+
if refdes_mode_arg == "refdes":
2018
refdes_mode = kicad.RefdesMode.Conventional
2119
elif refdes_mode_arg == "refdesPathNameValue":
2220
refdes_mode = kicad.RefdesMode.PathnameAsValue

edg/electronics_model/NetlistGenerator.py

Lines changed: 42 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ class NetBlock(NamedTuple):
2222
part: str
2323
value: str # gets written directly to footprint
2424
full_path: TransformUtil.Path # full path to this footprint
25-
path: List[str] # short path to this footprint
26-
class_path: List[edgir.LibraryPath] # classes on short path to this footprint
25+
path_classes: List[edgir.LibraryPath] # all classes on the full path, index-aligned with full_path
2726

2827

2928
class NetPin(NamedTuple):
@@ -82,8 +81,6 @@ def flatten_port(path: TransformUtil.Path, port: edgir.PortLike) -> Iterable[Tra
8281
def __init__(self, design: CompiledDesign):
8382
self.all_scopes = [BoardScope.empty(TransformUtil.Path.empty())] # list of unique scopes
8483
self.scopes: Scopes = {TransformUtil.Path.empty(): self.all_scopes[0]}
85-
86-
self.short_paths: Dict[TransformUtil.Path, List[str]] = {TransformUtil.Path.empty(): []} # seed root
8784
self.class_paths: ClassPaths = {TransformUtil.Path.empty(): []} # seed root
8885

8986
self.design = design
@@ -103,33 +100,12 @@ def process_blocklike(
103100
for link_pair in block.links: # links considered to be the same scope as self
104101
self.scopes[path.append_link(link_pair.name)] = scope
105102

106-
# generate short paths for children first, for Blocks only
107-
main_internal_blocks: Dict[str, edgir.BlockLike] = {}
108-
other_internal_blocks: Dict[str, edgir.BlockLike] = {}
109-
110-
for block_pair in block.blocks:
111-
subblock = block_pair.value
112-
# ignore pseudoblocks like bridges and adapters that have no internals
113-
if not subblock.hierarchy.blocks and "fp_is_footprint" not in subblock.hierarchy.meta.members.node:
114-
other_internal_blocks[block_pair.name] = block_pair.value
115-
else:
116-
main_internal_blocks[block_pair.name] = block_pair.value
117-
118-
short_path = self.short_paths[path]
119103
class_path = self.class_paths[path]
104+
for block_pair in block.blocks:
105+
self.class_paths[path.append_block(block_pair.name)] = class_path + [
106+
block_pair.value.hierarchy.self_class
107+
]
120108

121-
if len(main_internal_blocks) == 1 and short_path: # never shorten top-level blocks
122-
name = list(main_internal_blocks.keys())[0]
123-
self.short_paths[path.append_block(name)] = short_path
124-
self.class_paths[path.append_block(name)] = class_path
125-
else:
126-
for name, subblock in main_internal_blocks.items():
127-
self.short_paths[path.append_block(name)] = short_path + [name]
128-
self.class_paths[path.append_block(name)] = class_path + [subblock.hierarchy.self_class]
129-
130-
for name, subblock in other_internal_blocks.items():
131-
self.short_paths[path.append_block(name)] = short_path + [name]
132-
self.class_paths[path.append_block(name)] = class_path + [subblock.hierarchy.self_class]
133109
elif isinstance(block, (edgir.Link, edgir.LinkArray)):
134110
for link_pair in block.links:
135111
self.scopes[path.append_link(link_pair.name)] = scope
@@ -185,9 +161,7 @@ def process_blocklike(
185161
part_comps = [part, f"({mfr})" if mfr else ""]
186162
part_str = " ".join(filter(None, part_comps))
187163
value_str = value if value else (part if part else "")
188-
scope.footprints[path] = NetBlock(
189-
footprint_name, refdes, part_str, value_str, path, self.short_paths[path], self.class_paths[path]
190-
)
164+
scope.footprints[path] = NetBlock(footprint_name, refdes, part_str, value_str, path, self.class_paths[path])
191165

192166
for pin_spec in footprint_pinning:
193167
assert isinstance(pin_spec, str)
@@ -370,3 +344,39 @@ def run(self) -> Netlist:
370344
self.transform_design(self.design.design)
371345

372346
return self.scope_to_netlist(self.all_scopes[0]) # TODO support multiple scopes
347+
348+
349+
class PathShortener:
350+
"""Given a bunch of blocks with full paths, determine path shortenings that eliminate
351+
path components that are the only footprint-containing internal block."""
352+
353+
def __init__(self, blocks: List[NetBlock]) -> None:
354+
# construct list of children for each path
355+
# note since this is created from a list of footprints, all paths are guaranteed to contain footprints
356+
self._block_children: Dict[Tuple[str, ...], List[str]] = {}
357+
for block in blocks:
358+
block_path = block.full_path.blocks
359+
for i in range(len(block_path)):
360+
parent_path = block_path[:i]
361+
path_component = block_path[i]
362+
parent_list = self._block_children.setdefault(parent_path, [])
363+
if path_component not in parent_list:
364+
parent_list.append(path_component)
365+
366+
def shorten(
367+
self, path: TransformUtil.Path, classes: List[edgir.LibraryPath]
368+
) -> Tuple[List[str], List[edgir.LibraryPath]]:
369+
assert len(path.blocks) == len(classes)
370+
new_blocks = []
371+
new_classes = []
372+
block_path = path.blocks
373+
for i, path_comp in enumerate(block_path):
374+
# test whether to add component i
375+
# always keep rootmost component
376+
full_parent_path = tuple(block_path[:i])
377+
if i > 0 and len(self._block_children.get(full_parent_path, [])) <= 1: # only one child, so can shorten
378+
continue
379+
else:
380+
new_blocks.append(path_comp)
381+
new_classes.append(classes[i])
382+
return new_blocks, new_classes

edg/electronics_model/footprint.py

Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@
22
from enum import Enum, auto
33
from typing import List
44

5-
from .NetlistGenerator import Netlist, NetBlock, Net
5+
from .NetlistGenerator import Netlist, NetBlock, Net, PathShortener
66
from .. import edgir
77

88

99
class RefdesMode(Enum):
1010
PathnameAsValue = auto() # conventional refdes w/ pathname as value, except for TPs
1111
Conventional = auto() # conventional refdes only, value passed through
12-
Pathname = auto() # pathname as refdes, value passed through
1312

1413

1514
###############################################################################################################################################################################################
@@ -26,23 +25,16 @@ def gen_header() -> str:
2625
"""2. Generating Blocks"""
2726

2827

29-
def block_name(block: NetBlock, refdes_mode: RefdesMode) -> str:
30-
if refdes_mode == RefdesMode.Pathname:
31-
return ".".join(block.path)
32-
else:
33-
return block.refdes # default is conventional refdes
34-
35-
3628
def gen_block_comp(block_name: str) -> str:
3729
return f'(comp (ref "{block_name}")'
3830

3931

40-
def gen_block_value(block: NetBlock, refdes_mode: RefdesMode) -> str:
32+
def gen_block_value(block: NetBlock, short_path: List[str], refdes_mode: RefdesMode) -> str:
4133
if refdes_mode == RefdesMode.PathnameAsValue:
4234
if "TP" in block.refdes: # test points keep their value
4335
return f'(value "{block.value}")'
4436
else:
45-
pathname = ".".join(block.path)
37+
pathname = ".".join(short_path)
4638
return f'(value "{pathname}")'
4739
else:
4840
return f'(value "{block.value}")'
@@ -83,18 +75,18 @@ def gen_block_prop_sheetfile(block_path: List[edgir.LibraryPath]) -> str:
8375
return f'(property (name "Sheetfile") (value "{value}"))'
8476

8577

86-
def gen_block_prop_edg(block: NetBlock) -> str:
78+
def gen_block_prop_edg(block: NetBlock, short_path: List[str]) -> str:
8779
return (
8880
f'(property (name "edg_path") (value "{".".join(block.full_path.to_tuple())}"))\n'
89-
+ f' (property (name "edg_short_path") (value "{".".join(block.path)}"))\n'
81+
+ f' (property (name "edg_short_path") (value "{".".join(short_path)}"))\n'
9082
+ f' (property (name "edg_refdes") (value "{block.refdes}"))\n'
9183
+ f' (property (name "edg_part") (value "{block.part}"))\n'
9284
+ f' (property (name "edg_value") (value "{block.value}"))'
9385
)
9486

9587

96-
def block_exp(blocks: List[NetBlock], refdes_mode: RefdesMode) -> str:
97-
"""Given a dictionary of block_names (strings) as keys and Blocks (namedtuples) as corresponding values
88+
def block_exp(blocks: List[NetBlock], shortener: PathShortener, refdes_mode: RefdesMode) -> str:
89+
"""Generate the blocks section of the netlist from a list of blocks.
9890
9991
Example:
10092
(components
@@ -109,30 +101,31 @@ def block_exp(blocks: List[NetBlock], refdes_mode: RefdesMode) -> str:
109101
"""
110102
result = "(components"
111103
for block in blocks:
104+
short_path, short_class = shortener.shorten(block.full_path, block.path_classes)
112105
result += (
113106
"\n"
114-
+ gen_block_comp(block_name(block, refdes_mode))
107+
+ gen_block_comp(block.refdes)
115108
+ "\n"
116109
+ " "
117-
+ gen_block_value(block, refdes_mode)
110+
+ gen_block_value(block, short_path, refdes_mode)
118111
+ "\n"
119112
+ " "
120113
+ gen_block_footprint(block.footprint)
121114
+ "\n"
122115
+ " "
123-
+ gen_block_prop_sheetname(block.path)
116+
+ gen_block_prop_sheetname(short_path)
124117
+ "\n"
125118
+ " "
126-
+ gen_block_prop_sheetfile(block.class_path)
119+
+ gen_block_prop_sheetfile(short_class)
127120
+ "\n"
128121
+ " "
129-
+ gen_block_prop_edg(block)
122+
+ gen_block_prop_edg(block, short_path)
130123
+ "\n"
131124
+ " "
132-
+ gen_block_sheetpath(block.path[:-1])
125+
+ gen_block_sheetpath(short_path[:-1])
133126
+ "\n"
134127
+ " "
135-
+ gen_block_tstamp(block.path)
128+
+ gen_block_tstamp(short_path)
136129
)
137130
return result + ")"
138131

@@ -150,7 +143,7 @@ def gen_net_pin(block_name: str, pin_name: str) -> str:
150143
return "(node (ref {}) (pin {}))".format(block_name, pin_name)
151144

152145

153-
def net_exp(nets: List[Net], blocks: List[NetBlock], refdes_mode: RefdesMode) -> str:
146+
def net_exp(nets: List[Net], blocks: List[NetBlock]) -> str:
154147
"""Given a dictionary of net names (strings) as keys and a list of connected Pins (namedtuples) as corresponding values
155148
156149
Example:
@@ -170,7 +163,7 @@ def net_exp(nets: List[Net], blocks: List[NetBlock], refdes_mode: RefdesMode) ->
170163
for i, net in enumerate(nets):
171164
result += "\n" + gen_net_header(i + 1, net.name)
172165
for pin in net.pins:
173-
result += "\n " + gen_net_pin(block_name(block_dict[pin.block_path], refdes_mode), pin.pin_name)
166+
result += "\n " + gen_net_pin(block_dict[pin.block_path].refdes, pin.pin_name)
174167
result += ")"
175168
return result + ")"
176169

@@ -181,12 +174,13 @@ def net_exp(nets: List[Net], blocks: List[NetBlock], refdes_mode: RefdesMode) ->
181174

182175

183176
def generate_netlist(netlist: Netlist, refdes_mode: RefdesMode) -> str:
177+
shortener = PathShortener(list(netlist.blocks))
184178
return (
185179
gen_header()
186180
+ "\n"
187-
+ block_exp(netlist.blocks, refdes_mode)
181+
+ block_exp(netlist.blocks, shortener, refdes_mode)
188182
+ "\n"
189-
+ net_exp(netlist.nets, netlist.blocks, refdes_mode)
183+
+ net_exp(netlist.nets, netlist.blocks)
190184
+ "\n"
191185
+ ")"
192186
)

edg/electronics_model/test_bundle_netlist.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,6 @@ def test_spi_netlist(self) -> None:
209209
"",
210210
"WeirdSpiController",
211211
["controller"],
212-
["controller"],
213212
["edg.electronics_model.test_bundle_netlist.TestFakeSpiController"],
214213
),
215214
net.blocks,
@@ -221,7 +220,6 @@ def test_spi_netlist(self) -> None:
221220
"",
222221
"WeirdSpiPeripheral",
223222
["peripheral1"],
224-
["peripheral1"],
225223
["edg.electronics_model.test_bundle_netlist.TestFakeSpiPeripheral"],
226224
),
227225
net.blocks,
@@ -233,7 +231,6 @@ def test_spi_netlist(self) -> None:
233231
"",
234232
"WeirdSpiPeripheral",
235233
["peripheral2"],
236-
["peripheral2"],
237234
["edg.electronics_model.test_bundle_netlist.TestFakeSpiPeripheral"],
238235
),
239236
net.blocks,
@@ -271,7 +268,6 @@ def test_uart_netlist(self) -> None:
271268
"",
272269
"1k",
273270
["a"],
274-
["a"],
275271
["edg.electronics_model.test_bundle_netlist.TestFakeUartBlock"],
276272
),
277273
net.blocks,
@@ -283,7 +279,6 @@ def test_uart_netlist(self) -> None:
283279
"",
284280
"1k",
285281
["b"],
286-
["b"],
287282
["edg.electronics_model.test_bundle_netlist.TestFakeUartBlock"],
288283
),
289284
net.blocks,
@@ -323,7 +318,6 @@ def test_can_netlist(self) -> None:
323318
"",
324319
"120",
325320
["node1"],
326-
["node1"],
327321
["edg.electronics_model.test_bundle_netlist.TestFakeCanBlock"],
328322
),
329323
net.blocks,
@@ -335,7 +329,6 @@ def test_can_netlist(self) -> None:
335329
"",
336330
"120",
337331
["node2"],
338-
["node2"],
339332
["edg.electronics_model.test_bundle_netlist.TestFakeCanBlock"],
340333
),
341334
net.blocks,
@@ -347,7 +340,6 @@ def test_can_netlist(self) -> None:
347340
"",
348341
"120",
349342
["node3"],
350-
["node3"],
351343
["edg.electronics_model.test_bundle_netlist.TestFakeCanBlock"],
352344
),
353345
net.blocks,

edg/electronics_model/test_multipack_netlist.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,6 @@ def test_packed_netlist(self) -> None:
123123
"",
124124
"1uF",
125125
["source"],
126-
["source"],
127126
["edg.electronics_model.test_netlist.TestFakeSource"],
128127
),
129128
net.blocks,
@@ -135,8 +134,10 @@ def test_packed_netlist(self) -> None:
135134
"",
136135
"1k",
137136
["sink", "device"],
138-
["sink"],
139-
["edg.electronics_model.test_multipack_netlist.TestPackedSink"],
137+
[
138+
"edg.electronics_model.test_multipack_netlist.TestPackedSink",
139+
"edg.electronics_model.test_netlist.TestFakeSink",
140+
],
140141
),
141142
net.blocks,
142143
)

0 commit comments

Comments
 (0)