@@ -37,9 +37,23 @@ class Netlist(NamedTuple):
3737 nets : List [Net ]
3838
3939
40- Blocks = Dict [TransformUtil .Path , NetBlock ] # Path -> Block
41- Edges = Dict [TransformUtil .Path , List [TransformUtil .Path ]] # Port Path -> connected Port Paths
42- AssertConnected = List [Tuple [TransformUtil .Path , TransformUtil .Path ]]
40+ # The concept of board scopes is footprints associated with a scope (defined as a path) that is a board,
41+ # to support multi-board assemblies and virtual components.
42+ # The default (top-level) board has TransformUtil.Path.empty().
43+ # None board scope means the footprints do not exist (virtual component), eg the associated blocks were for modeling.
44+ class BoardScope (NamedTuple ):
45+ path : TransformUtil .Path # root path
46+ footprints : Dict [TransformUtil .Path , NetBlock ] # Path -> Block footprint
47+ edges : Dict [TransformUtil .Path , List [TransformUtil .Path ]] # Port Path -> connected Port Paths
48+ pins : Dict [TransformUtil .Path , List [NetPin ]] # mapping from Port to pad
49+ assert_connected : List [Tuple [TransformUtil .Path , TransformUtil .Path ]]
50+
51+ @classmethod
52+ def empty (cls , path : TransformUtil .Path ): # returns a fresh, empty BordScope
53+ return BoardScope (path , {}, {}, {}, [])
54+
55+
56+ Scopes = Dict [TransformUtil .Path , Optional [BoardScope ]] # Block -> board scope (reference, aliased across entries)
4357ClassPaths = Dict [TransformUtil .Path , List [edgir .LibraryPath ]] # Path -> class names corresponding to shortened path name
4458class NetlistTransform (TransformUtil .Transform ):
4559 @staticmethod
@@ -53,28 +67,38 @@ def flatten_port(path: TransformUtil.Path, port: edgir.PortLike) -> Iterable[Tra
5367 raise ValueError (f"don't know how to flatten netlistable port { port } " )
5468
5569 def __init__ (self , design : CompiledDesign ):
56- self .blocks : Blocks = {}
57- self .edges : Edges = {} # as port Paths, including intermediates
58- self .pins : Dict [TransformUtil .Path , List [NetPin ]] = {} # mapping from Port to pad
59- self .assert_connected : AssertConnected = []
70+ self .all_scopes = [BoardScope .empty (TransformUtil .Path .empty ())] # list of unique scopes
71+ self .scopes : Scopes = {TransformUtil .Path .empty (): self .all_scopes [0 ]}
72+
6073 self .short_paths : Dict [TransformUtil .Path , List [str ]] = {TransformUtil .Path .empty (): []} # seed root
6174 self .class_paths : ClassPaths = {TransformUtil .Path .empty (): []} # seed root
6275
6376 self .design = design
6477
6578 def process_blocklike (self , path : TransformUtil .Path , block : Union [edgir .Link , edgir .LinkArray , edgir .HierarchyBlock ]) -> None :
79+ # TODO may need rethought to support multi-board assemblies
80+ scope = self .scopes [path ] # including footprint and exports, and everything within a link
81+ internal_scope = scope # for internal blocks
82+
6683 if isinstance (block , edgir .HierarchyBlock ):
84+ if 'fp_is_wrapper' in block .meta .members .node : # wrapper internal blocks ignored
85+ internal_scope = None
86+ for block_pair in block .blocks :
87+ self .scopes [path .append_block (block_pair .name )] = internal_scope
88+ for link_pair in block .links : # links considered to be the same scope as self
89+ self .scopes [path .append_link (link_pair .name )] = scope
90+
6791 # generate short paths for children first, for Blocks only
6892 main_internal_blocks : Dict [str , edgir .BlockLike ] = {}
6993 other_internal_blocks : Dict [str , edgir .BlockLike ] = {}
70- if isinstance ( block , edgir . HierarchyBlock ):
71- for block_pair in block .blocks :
72- subblock = block_pair .value
73- # ignore pseudoblocks like bridges and adapters that have no internals
74- if not subblock .hierarchy .blocks and 'fp_is_footprint' not in subblock .hierarchy .meta .members .node :
75- other_internal_blocks [block_pair .name ] = block_pair .value
76- else :
77- main_internal_blocks [block_pair .name ] = block_pair .value
94+
95+ for block_pair in block .blocks :
96+ subblock = block_pair .value
97+ # ignore pseudoblocks like bridges and adapters that have no internals
98+ if not subblock .hierarchy .blocks and 'fp_is_footprint' not in subblock .hierarchy .meta .members .node :
99+ other_internal_blocks [block_pair .name ] = block_pair .value
100+ else :
101+ main_internal_blocks [block_pair .name ] = block_pair .value
78102
79103 short_path = self .short_paths [path ]
80104 class_path = self .class_paths [path ]
@@ -91,17 +115,20 @@ def process_blocklike(self, path: TransformUtil.Path, block: Union[edgir.Link, e
91115 for (name , subblock ) in other_internal_blocks .items ():
92116 self .short_paths [path .append_block (name )] = short_path + [name ]
93117 self .class_paths [path .append_block (name )] = class_path + [subblock .hierarchy .self_class ]
118+ elif isinstance (block , (edgir .Link , edgir .LinkArray )):
119+ for link_pair in block .links :
120+ self .scopes [path .append_link (link_pair .name )] = scope
94121
95- if 'nets' in block .meta .members .node :
122+ if 'nets' in block .meta .members .node and scope is not None :
96123 # add self as a net
97124 # list conversion to deal with iterable-once
98125 flat_ports = list (chain (* [self .flatten_port (path .append_port (port_pair .name ), port_pair .value )
99126 for port_pair in block .ports ]))
100- self .edges .setdefault (path , []).extend (flat_ports )
127+ scope .edges .setdefault (path , []).extend (flat_ports )
101128 for port_path in flat_ports :
102- self .edges .setdefault (port_path , []).append (path )
129+ scope .edges .setdefault (port_path , []).append (path )
103130
104- if 'nets_packed' in block .meta .members .node :
131+ if 'nets_packed' in block .meta .members .node and scope is not None :
105132 # this connects the first source to all destinations, then asserts all the sources are equal
106133 # this leaves the sources unconnected, to be connected externally and checked at the end
107134 src_port_name = block .meta .members .node ['nets_packed' ].members .node ['src' ].text_leaf
@@ -110,13 +137,13 @@ def process_blocklike(self, path: TransformUtil.Path, block: Union[edgir.Link, e
110137 flat_dsts = list (self .flatten_port (path .append_port (dst_port_name ), edgir .pair_get (block .ports , dst_port_name )))
111138 assert flat_srcs , "missing source port(s) for packed net"
112139 for dst_path in flat_dsts :
113- self .edges .setdefault (flat_srcs [0 ], []).append (dst_path )
114- self .edges .setdefault (dst_path , []).append (flat_srcs [0 ])
140+ scope .edges .setdefault (flat_srcs [0 ], []).append (dst_path )
141+ scope .edges .setdefault (dst_path , []).append (flat_srcs [0 ])
115142 for src_path in flat_srcs : # assert all sources connected
116143 for dst_path in flat_srcs :
117- self .assert_connected .append ((src_path , dst_path ))
144+ scope .assert_connected .append ((src_path , dst_path ))
118145
119- if 'fp_is_footprint' in block .meta .members .node :
146+ if 'fp_is_footprint' in block .meta .members .node and scope is not None :
120147 footprint_name = self .design .get_value (path .to_tuple () + ('fp_footprint' ,))
121148 footprint_pinning = self .design .get_value (path .to_tuple () + ('fp_pinning' ,))
122149 mfr = self .design .get_value (path .to_tuple () + ('fp_mfr' ,))
@@ -139,14 +166,14 @@ def process_blocklike(self, path: TransformUtil.Path, block: Union[edgir.Link, e
139166 ]
140167 part_str = " " .join (filter (None , part_comps ))
141168 value_str = value if value else (part if part else '' )
142- self . blocks [path ] = NetBlock (
169+ scope . footprints [path ] = NetBlock (
143170 footprint_name ,
144171 refdes ,
145172 part_str ,
146173 value_str ,
147174 path ,
148175 self .short_paths [path ],
149- self .class_paths [path ],
176+ self .class_paths [path ]
150177 )
151178
152179 for pin_spec in footprint_pinning :
@@ -157,56 +184,63 @@ def process_blocklike(self, path: TransformUtil.Path, block: Union[edgir.Link, e
157184 pin_port_path = edgir .LocalPathList (pin_spec_split [1 ].split ('.' ))
158185
159186 src_path = path .follow (pin_port_path , block )[0 ]
160- self .edges .setdefault (src_path , []) # make sure there is a port entry so single-pin nets are named
161- self .pins .setdefault (src_path , []).append (NetPin (path , pin_name ))
187+ scope .edges .setdefault (src_path , []) # make sure there is a port entry so single-pin nets are named
188+ scope .pins .setdefault (src_path , []).append (NetPin (path , pin_name ))
162189
163190 for constraint_pair in block .constraints :
164- if constraint_pair .value .HasField ('connected' ):
165- self .process_connected (path , block , constraint_pair .value .connected )
166- elif constraint_pair .value .HasField ('exported' ):
167- self .process_exported (path , block , constraint_pair .value .exported )
168- elif constraint_pair .value .HasField ('exportedTunnel' ):
169- self .process_exported (path , block , constraint_pair .value .exportedTunnel )
170- elif constraint_pair .value .HasField ('connectedArray' ):
171- for expanded_connect in constraint_pair .value .connectedArray .expanded :
172- self .process_connected (path , block , expanded_connect )
173- elif constraint_pair .value .HasField ('exportedArray' ):
174- for expanded_export in constraint_pair .value .exportedArray .expanded :
175- self .process_exported (path , block , expanded_export )
176-
177- def process_connected (self , path : TransformUtil .Path , current : edgir .EltTypes , constraint : edgir .ConnectedExpr ) -> None :
191+ if scope is not None :
192+ if constraint_pair .value .HasField ('connected' ):
193+ self .process_connected (path , block , scope , constraint_pair .value .connected )
194+ elif constraint_pair .value .HasField ('connectedArray' ):
195+ for expanded_connect in constraint_pair .value .connectedArray .expanded :
196+ self .process_connected (path , block , scope , expanded_connect )
197+ elif constraint_pair .value .HasField ('exported' ):
198+ self .process_exported (path , block , scope , constraint_pair .value .exported )
199+ elif constraint_pair .value .HasField ('exportedArray' ):
200+ for expanded_export in constraint_pair .value .exportedArray .expanded :
201+ self .process_exported (path , block , scope , expanded_export )
202+ elif constraint_pair .value .HasField ('exportedTunnel' ):
203+ self .process_exported (path , block , scope , constraint_pair .value .exportedTunnel )
204+
205+ def process_connected (self , path : TransformUtil .Path , current : edgir .EltTypes , scope : BoardScope ,
206+ constraint : edgir .ConnectedExpr ) -> None :
178207 if constraint .expanded :
179208 assert len (constraint .expanded ) == 1
180- self .process_connected (path , current , constraint .expanded [0 ])
209+ self .process_connected (path , current , scope , constraint .expanded [0 ])
181210 return
182211 assert constraint .block_port .HasField ('ref' )
183212 assert constraint .link_port .HasField ('ref' )
184213 self .connect_ports (
214+ scope ,
185215 path .follow (constraint .block_port .ref , current ),
186216 path .follow (constraint .link_port .ref , current ))
187217
188- def process_exported (self , path : TransformUtil .Path , current : edgir .EltTypes , constraint : edgir .ExportedExpr ) -> None :
218+ def process_exported (self , path : TransformUtil .Path , current : edgir .EltTypes , scope : BoardScope ,
219+ constraint : edgir .ExportedExpr ) -> None :
189220 if constraint .expanded :
190221 assert len (constraint .expanded ) == 1
191- self .process_exported (path , current , constraint .expanded [0 ])
222+ self .process_exported (path , current , scope , constraint .expanded [0 ])
192223 return
193224 assert constraint .internal_block_port .HasField ('ref' )
194225 assert constraint .exterior_port .HasField ('ref' )
195226 self .connect_ports (
227+ scope ,
196228 path .follow (constraint .internal_block_port .ref , current ),
197229 path .follow (constraint .exterior_port .ref , current ))
198230
199- def connect_ports (self , elt1 : Tuple [TransformUtil .Path , edgir .EltTypes ], elt2 : Tuple [TransformUtil .Path , edgir .EltTypes ]) -> None :
231+ def connect_ports (self , scope : BoardScope , elt1 : Tuple [TransformUtil .Path , edgir .EltTypes ],
232+ elt2 : Tuple [TransformUtil .Path , edgir .EltTypes ]) -> None :
200233 """Recursively connect ports as applicable"""
201234 if isinstance (elt1 [1 ], edgir .Port ) and isinstance (elt2 [1 ], edgir .Port ):
202- self .edges .setdefault (elt1 [0 ], []).append (elt2 [0 ])
203- self .edges .setdefault (elt2 [0 ], []).append (elt1 [0 ])
235+ scope .edges .setdefault (elt1 [0 ], []).append (elt2 [0 ])
236+ scope .edges .setdefault (elt2 [0 ], []).append (elt1 [0 ])
204237 elif isinstance (elt1 [1 ], edgir .Bundle ) and isinstance (elt2 [1 ], edgir .Bundle ):
205238 elt1_names = list (map (lambda pair : pair .name , elt1 [1 ].ports ))
206239 elt2_names = list (map (lambda pair : pair .name , elt2 [1 ].ports ))
207240 assert elt1_names == elt2_names , f"mismatched bundle types { elt1 } , { elt2 } "
208241 for key in elt2_names :
209242 self .connect_ports (
243+ scope ,
210244 (elt1 [0 ].append_port (key ), edgir .resolve_portlike (edgir .pair_get (elt1 [1 ].ports , key ))),
211245 (elt2 [0 ].append_port (key ), edgir .resolve_portlike (edgir .pair_get (elt2 [1 ].ports , key ))))
212246 # don't need to create the bundle connect, since Bundles can't be CircuitPorts
@@ -255,14 +289,12 @@ def pin_name_goodness(pin1: TransformUtil.Path, pin2: TransformUtil.Path) -> int
255289
256290 return net_prefix + str (best_path )
257291
258- def run (self ) -> Netlist :
259- self .transform_design (self .design .design )
260-
292+ def scope_to_netlist (self , scope : BoardScope ) -> Netlist :
261293 # Convert to the netlist format
262294 seen : Set [TransformUtil .Path ] = set ()
263295 nets : List [List [TransformUtil .Path ]] = [] # lists preserve ordering
264296
265- for port , conns in self .edges .items ():
297+ for port , conns in scope .edges .items ():
266298 if port not in seen :
267299 curr_net : List [TransformUtil .Path ] = []
268300 frontier : List [TransformUtil .Path ] = [port ] # use BFS to maintain ordering instead of simpler DFS
@@ -271,15 +303,15 @@ def run(self) -> Netlist:
271303 if pin not in seen :
272304 seen .add (pin )
273305 curr_net .append (pin )
274- frontier .extend (self .edges [pin ])
306+ frontier .extend (scope .edges [pin ])
275307 nets .append (curr_net )
276308
277309 pin_to_net : Dict [TransformUtil .Path , List [TransformUtil .Path ]] = {} # values share reference to nets
278310 for net in nets :
279311 for pin in net :
280312 pin_to_net [pin ] = net
281313
282- for (connected1 , connected2 ) in self .assert_connected :
314+ for (connected1 , connected2 ) in scope .assert_connected :
283315 if pin_to_net [connected1 ] is not pin_to_net [connected2 ]:
284316 raise InvalidPackingException (f"packed pins { connected1 } , { connected2 } not connected" )
285317
@@ -294,10 +326,16 @@ def run(self) -> Netlist:
294326 def port_ignored_paths (path : TransformUtil .Path ) -> bool : # ignore link ports for netlisting
295327 return bool (path .links ) or any ([block .startswith ('(adapter)' ) or block .startswith ('(bridge)' ) for block in path .blocks ])
296328
297- netlist_blocks = [block for path , block in self . blocks .items ()]
329+ netlist_footprints = [footprint for path , footprint in scope . footprints .items ()]
298330 netlist_nets = [Net (name ,
299- list (chain (* [self .pins [port ] for port in net if port in self .pins ])),
331+ list (chain (* [scope .pins [port ] for port in net if port in scope .pins ])),
300332 [port for port in net if not port_ignored_paths (port )])
301333 for name , net in named_nets .items ()]
334+ netlist_nets = [net for net in netlist_nets if net .pins ] # prune empty nets
335+
336+ return Netlist (netlist_footprints , netlist_nets )
337+
338+ def run (self ) -> Netlist :
339+ self .transform_design (self .design .design )
302340
303- return Netlist ( netlist_blocks , netlist_nets )
341+ return self . scope_to_netlist ( self . all_scopes [ 0 ]) # TODO support multiple scopes
0 commit comments