2626from amaranth .lib .wiring import In , Out
2727from pydantic import (
2828 ConfigDict , TypeAdapter , PlainSerializer ,
29- WithJsonSchema , WrapValidator
29+ WithJsonSchema , WrapValidator , Field
3030 )
3131
3232
3333from .. import ChipFlowError , _ensure_chipflow_root , _get_cls_by_reference
34+ from .._appresponse import AppResponseModel , OmitIfNone
3435
3536if TYPE_CHECKING :
3637 from .._config_models import Config
@@ -48,17 +49,15 @@ def chipflow_schema_uri(name: str, version: int) -> str:
4849 ]
4950
5051
51- @dataclass
52- class VoltageRange :
53- min : Optional [Voltage ] = None
54- max : Optional [Voltage ] = None
55- typical : Optional [Voltage ] = None
52+ class VoltageRange (AppResponseModel ):
53+ min : Annotated [Optional [Voltage ], OmitIfNone ()] = None
54+ max : Annotated [Optional [Voltage ], OmitIfNone ()] = None
55+ typical : Annotated [Optional [Voltage ], OmitIfNone ()] = None
5656
5757
58- @dataclass
59- class PowerDomain :
58+ class PowerDomain (AppResponseModel ):
6059 voltage : Voltage | VoltageRange
61- type : Optional [str ] = None
60+ type : Annotated [ Optional [str ], OmitIfNone () ] = None
6261
6362
6463# TODO: validation checks
@@ -67,12 +66,18 @@ class PowerDomain:
6766PowerDomainName = str
6867InterfacePowerDomainName = str
6968
69+
70+ @dataclass
71+ class PowerConfigDomains :
72+ pads : Optional [Dict [PowerDomainName , PowerDomain ]] = None
73+
74+
7075@dataclass
7176class PowerConfig :
72- domains : Optional [ Dict [ PowerDomainName , PowerDomain ]] = None
73- map : Optional [Dict [ComponentName ,
74- Dict [InterfaceName , dict | str ]
75- ]
77+ domains : PowerConfigDomains
78+ allocation : Optional [Dict [ComponentName ,
79+ Dict [InterfaceName , dict | str ]
80+ ]
7681 ] = None
7782
7883
@@ -100,7 +105,7 @@ class PowerConfig:
100105class IOModelOptions (TypedDict ):
101106 invert : NotRequired [bool | Tuple [bool , ...]]
102107 all_have_oe : NotRequired [bool ]
103- interface_power_domains : NotRequired [List [str ]]
108+ interface_power_domains : NotRequired [List [InterfacePowerDomainName ]]
104109 clock_domain : NotRequired [str ]
105110 init : NotRequired [Annotated [Const , ConstSerializer , ConstSchema ]]
106111
@@ -128,6 +133,7 @@ class IOModel(IOModelOptions):
128133 width : int
129134 direction : Annotated [io .Direction , PlainSerializer (lambda x : x .value )]
130135
136+
131137Pin = Tuple [Any ,...] | str | int
132138PinSet = Set [Pin ]
133139PinList = List [Pin ]
@@ -167,16 +173,22 @@ def _to_set(self) -> Set[Pin]:
167173 self .core_jtag ._to_set ()
168174
169175
170- class Port (pydantic . BaseModel ):
176+ class Port (AppResponseModel ):
171177 type : str
172178 pins : List [Pin ] | None # None implies must be allocated at end
173179 port_name : str
174180 iomodel : IOModel
175- power_map : Dict [InterfacePowerDomainName , PowerDomainName ] = {}
176- power_domain : Optional [str ] = None
181+ power_allocation : Dict [InterfacePowerDomainName , PowerDomainName ] = {}
182+ power_domain : Annotated [ Optional [str ], OmitIfNone () ] = None
177183
178184 def model_post_init (self , __context ):
179185 logger .debug (f"Instantiating port { self .port_name } : { self } " )
186+ # every interface gets a default domain for the pad
187+ if 'interface_power_domain' not in self .iomodel :
188+ self .iomodel ['interface_power_domains' ] = ['default' ]
189+ elif 'default' not in self .iomodel ['interface_power_domains' ]:
190+ self .iomodel ['interface_power_domains' ] = ['default' ].extend (self .iomodel ['interface_power_domain' ])
191+
180192 return super ().model_post_init (__context )
181193
182194 @property
@@ -202,15 +214,19 @@ def invert(self) -> Iterable[bool] | None:
202214 @property
203215 def interface_power_domains (self ) -> List [str ]:
204216 if 'interface_power_domains' in self .iomodel :
205- assert isinstance (self .iomodel ['interface_power_domains' ], list )
206217 return self .iomodel ['interface_power_domains' ]
207218 else :
208219 return []
209220
210221
211- def create_ports (name : str , member : Dict [str , Any ], port_name : str ) -> Dict [str , Port ]:
222+ Interface = OrderedDict [str , Port ]
223+ Component = OrderedDict [str , Interface ]
224+ AllocateFunc = Callable [[PinSet , int ], PinList ]
225+
226+
227+ def create_ports (name : str , member : Dict [str , Any ], port_name : str ) -> Interface :
212228 "Allocate pins based of Amaranth member metadata"
213- pin_map : Dict [ str , Port ] = {}
229+ pin_map = Interface ()
214230
215231 logger .debug (f"create_ports: name={ name } " )
216232 logger .debug (f"member={ pformat (member )} " )
@@ -232,7 +248,7 @@ def create_ports(name: str, member: Dict[str, Any], port_name: str) -> Dict[str,
232248 elif member ['type' ] == 'port' :
233249 logger .warning (f"Port '{ name } ' has no IOSignature, pin allocation likely to be wrong" )
234250 width = member ['width' ]
235- model = IOModel (width = width , direction = io .Direction (member ['dir' ]))
251+ model = IOModel (width = int ( width ) , direction = io .Direction (member ['dir' ]))
236252 pin_map [name ] = Port (type = 'io' , port_name = port_name , iomodel = model , pins = None )
237253 logger .debug (f"added '{ name } ':{ pin_map [name ]} to pin_map" )
238254 return pin_map
@@ -241,14 +257,9 @@ def create_ports(name: str, member: Dict[str, Any], port_name: str) -> Dict[str,
241257 assert False
242258
243259
244-
245- Interface = Dict [str , Port ]
246- Component = Dict [str , Interface ]
247- AllocateFunc = Callable [[PinSet , int ], PinList ]
248-
249260class PortMap (pydantic .BaseModel ):
250- ports : Dict [str , Component ] = {}
251- port_power_domains : Dict [PowerDomainName , PowerDomain ] = {}
261+ ports : OrderedDict [str , Component ] = OrderedDict ()
262+ pad_power_domains : Dict [PowerDomainName , PowerDomain ] = {}
252263
253264 def get_ports (self , component : str , interface : str ) -> Interface :
254265 "List the ports allocated in this PortMap for the given `Component` and `Interface`"
@@ -277,15 +288,15 @@ def get_resets(self) -> List[Port]:
277288 def add_port (self , component : str , interface : str , port_name : str , port : Port ):
278289 "Internally used by a `PackageDef`"
279290 if component not in self .ports :
280- self .ports [component ] = {}
291+ self .ports [component ] = Component ()
281292 if interface not in self .ports [component ]:
282- self .ports [component ][interface ] = {}
293+ self .ports [component ][interface ] = Interface ()
283294 self .ports [component ][interface ][port_name ] = port
284295
285296 def add_ports (self , component : str , interface : str , ports : Interface ):
286297 "Internally used by a `PackageDef`"
287298 if component not in self .ports :
288- self .ports [component ] = {}
299+ self .ports [component ] = Component ()
289300 self .ports [component ][interface ] = ports
290301
291302 def populate (self , interfaces : dict , lockfile : Optional ['LockFile' ] = None ):
@@ -310,19 +321,19 @@ def populate(self, interfaces: dict, lockfile: Optional['LockFile'] = None):
310321 else :
311322 print (f"Creating ports for component: { component } , interface: { interface } " )
312323 if component not in self .ports :
313- self .ports [component ] = {}
324+ self .ports [component ] = Component ()
314325 self .ports [component ][interface ] = create_ports (interface , v , interface )
315326
316327 def create_power_ports (self , component , interface ):
317- power_map :Dict [InterfacePowerDomainName , PowerDomainName ] = {}
328+ power_allocation :Dict [InterfacePowerDomainName , PowerDomainName ] = {}
318329 for pn , p in self .ports [component ][interface ].items ():
319- if p .power_map :
320- logger .debug (f"Allocating power pins for { component } .{ interface } .{ pn } " )
321- power_map |= p .power_map
322- for ipd , ppd in power_map .items ():
330+ if p .power_allocation :
331+ logger .debug (f"Creating power ports for { component } .{ interface } .{ pn } " )
332+ power_allocation |= p .power_allocation
333+ for ipd , ppd in power_allocation .items ():
323334 prefix = f"_power_{ ipd } _{ ppd } "
324335 power_port = prefix + "_vdd"
325- power_domain = f"{ component } . { interface } . { ipd } "
336+ power_domain = f"{ ppd } "
326337 if power_port not in self .ports [component ][interface ]:
327338 self .ports [component ][interface ][power_port ] = Port (type = 'power' , pins = None , port_name = power_port ,
328339 power_domain = power_domain , iomodel = IOModel (width = 1 , direction = io .Direction .Input ))
@@ -332,70 +343,73 @@ def create_power_ports(self, component, interface):
332343 self .ports [component ][interface ][power_port ] = Port (type = 'power' , pins = None , port_name = power_port ,
333344 power_domain = power_domain , iomodel = IOModel (width = 1 , direction = io .Direction .Input ))
334345
346+ def check_core_power (self , core_domain , _type , voltage ):
347+ if core_domain not in self .pad_power_domains :
348+ self .pad_power_domains [core_domain ] = PowerDomain (type = _type , voltage = voltage )
349+ else :
350+ if self .pad_power_domains [core_domain ].type != _type :
351+ raise ChipFlowError ("Default core_domain power domain must be type 'io" )
335352
336353 def allocate_power (self , config : 'Config' ):
337354 "Allocate power domains to top level ports"
338- if not config .chipflow .power :
339- return
340-
341- # TODO: allow for shell-defined power domains
342- if not config .chipflow .power .domains :
343- return
344355
345356 # instantiate port-side power domains
346- self .port_power_domains = config .chipflow .power .domains
357+ if config .chipflow .power and config .chipflow .power .domains and config .chipflow .power .domains .pads :
358+ self .pad_power_domains = config .chipflow .power .domains .pads
359+ else :
360+ self .pad_power_domains = {}
347361
348- if not config .chipflow .power .map :
349- logger .warning ("[chipflow.power.domains] is defined, but no [chipflow.power.map] found" )
350- return
362+ self .check_core_power ('_io' , 'io' , 3.3 )
363+ self .check_core_power ('_core' , 'core' , 1.8 )
351364
352- # special handling for core
353- # for c, v in self.ports['_core'].items():
354- # for i, p in v.items():
355- # p.power= '_core'
365+ # ensure default mappings exist
366+ if config .chipflow .power and config .chipflow .power .allocation :
367+ allocation_config = config .chipflow .power .allocation
368+ else :
369+ allocation_config = {}
356370
357371 # convert nested dict structure into a mapping ic_power_domain:[component, interface, {port,} {ip power domain}]
358- map_config = config .chipflow .power .map
359-
360372 def map_ports (c , i , * , port_power_domain , port_name = None , interface_power_domain = None ):
361373 # a bit verbose but easier to understand, I hope..
362- if port_power_domain not in self .port_power_domains :
374+ if port_power_domain not in self .pad_power_domains :
363375 raise ChipFlowError (f"'{ port_power_domain } ' is not a known power domain in { c } .{ i } { '.' + port_name if port_name else '' } { '.' + interface_power_domain if interface_power_domain else '' } = '{ port_power_domain } ')" )
364376 if interface_power_domain :
365377 if port_name :
366378 p = self .ports [c ][i ][port_name ]
367379 if interface_power_domain not in p .interface_power_domains :
368380 raise ChipFlowError (f"{ interface_power_domain } not found in { c } .{ i } .{ p } power domains ({ p .interface_power_domains } " )
369381 p .port_power_domain = port_power_domain
370- self .interface_power_map [interface_power_domain ] = port_power_domain
382+ self .interface_power_allocation [interface_power_domain ] = port_power_domain
371383 else :
372384 for p in self .ports [c ][i ].values ():
385+ if p .type != io :
386+ continue
373387 if interface_power_domain in p .interface_power_domains :
374- if interface_power_domain in p .power_map \
375- and p .power_map [interface_power_domain ] != port_power_domain :
376- raise ChipFlowError (f"Clash in power domain mappings for { c } .{ i } .{ p } : { port_power_domain } differs from previously set { p .power_map [interface_power_domain ]} for { interface_power_domain } " )
388+ if interface_power_domain in p .power_allocation \
389+ and p .power_allocation [interface_power_domain ] != port_power_domain :
390+ raise ChipFlowError (f"Clash in power domain mappings for { c } .{ i } .{ p } : { port_power_domain } differs from previously set { p .power_allocation [interface_power_domain ]} for { interface_power_domain } " )
377391
378- p .power_map [interface_power_domain ] = port_power_domain
392+ p .power_allocation [interface_power_domain ] = port_power_domain
379393 else :
380394 if port_name :
381395 # first one considered default
382396 p = self .ports [c ][i ][port_name ]
383397 if not p .interface_power_domains :
384398 raise ChipFlowError (f"No power domains available for { c } .{ i } .{ port_name } " )
385399 interface_power_domain = p .interface_power_domains [0 ]
386- p .power_map [interface_power_domain ] = port_power_domain
400+ p .power_allocation [interface_power_domain ] = port_power_domain
387401 else :
388402 for p in self .ports [c ][i ].values ():
389403 if p .interface_power_domains :
390404 interface_power_domain = p .interface_power_domains [0 ]
391- p .power_map [interface_power_domain ] = port_power_domain
405+ p .power_allocation [interface_power_domain ] = port_power_domain
392406
393407 # must be an easier way to do this...
394- for c , v in map_config .items ():
408+ for c , v in allocation_config .items ():
395409 if c not in self .ports :
396- raise ChipFlowError (f"In [chipflow.power.map ], '{ c } ' is not a top level component" )
410+ raise ChipFlowError (f"In [chipflow.power.allocation ], '{ c } ' is not a top level component" )
397411 if not isinstance (v , dict ):
398- raise ChipFlowError (f"Malformed [chipflow.power.map ] section: { c } = { v } " )
412+ raise ChipFlowError (f"Malformed [chipflow.power.allocation ] section: { c } = { v } " )
399413 for i , v in v .items ():
400414 if i not in self .ports [c ]:
401415 raise ChipFlowError (f"In [chipflow.silicon.power], '{ i } ' is not an interface of { c } " )
@@ -412,31 +426,53 @@ def map_ports(c, i, *, port_power_domain, port_name=None, interface_power_domain
412426 for y , v in v .items ():
413427 map_ports (c , i , port_name = x , interface_power_domain = y , port_power_domain = v )
414428 else :
415- raise ChipFlowError (f"Malformed [chipflow.power.map ] section: { c } .{ i } .{ x } = { v } ('{ v } ' is not a valid power domain)" )
429+ raise ChipFlowError (f"Malformed [chipflow.power.allocation ] section: { c } .{ i } .{ x } = { v } ('{ v } ' is not a valid power domain)" )
416430 # is x an interface-side power domain?
417431 elif any (x in p .interface_power_domains for p in self .ports [c ][i ].values () if p .interface_power_domains ):
418432 if not isinstance (v , str ):
419- raise ChipFlowError (f"Malformed [chipflow.power.map ] section: { c } .{ i } .{ x } = { v } , ('{ x } ' isnot a valid power domain)" )
433+ raise ChipFlowError (f"Malformed [chipflow.power.allocation ] section: { c } .{ i } .{ x } = { v } , ('{ x } ' isnot a valid power domain)" )
420434 else :
421435 # map interface-side power domain x to port-side power domain v
422436 map_ports (c , i , interface_power_domain = x , port_power_domain = v )
423437 else :
424- raise ChipFlowError (f"Malformed [chipflow.power.map] section: { c } .{ i } .{ x } = { v } (unable to interpret '{ x } ')" )
438+ raise ChipFlowError (f"Malformed [chipflow.power.allocation] section: { c } .{ i } .{ x } = { v } (unable to interpret '{ x } ')" )
439+
440+ self .create_power_ports (c , i )
441+ # apply default case
442+ for c , v in self .ports .items ():
443+ for i , v in v .items ():
444+ for p in v .values ():
445+ if not p .power_allocation :
446+ p .power_allocation = {'default' : '_io' }
447+ elif 'default' not in p .power_allocation :
448+ p .power_allocation |= {'default' : '_io' }
425449
426450 self .create_power_ports (c , i )
427451
428452 def allocate_pins (self , config : 'Config' , allocate : AllocateFunc , unallocated : Set [Pin ]) -> None :
429453 logger .debug ("Allocating pins" )
430- for c ,v in self .ports .items ():
431- for i ,v in v .items ():
432- for n , port in v .items ():
433- if not port .pins :
434- pins = allocate (unallocated , port .width )
435- if len (pins ) == 0 : # TODO turn this into an exception?
436- raise ChipFlowError ("No pins were allocated" )
437- # logger.debug(f"allocated range: {pins}")
438- unallocated = unallocated - set (pins )
439- port .pins = pins
454+ def flatten_dict (d , parent_key = '' , sep = '.' ):
455+ items = []
456+ for k , v in d .items ():
457+ new_key = f"{ parent_key } { sep } { k } " if parent_key else k
458+ if isinstance (v , dict ):
459+ items .extend (flatten_dict (v , new_key , sep = sep ).items ())
460+ else :
461+ items .append ((new_key , v ))
462+ return dict (items )
463+ # group by pad power domains
464+ ports = flatten_dict (self .ports )
465+
466+ for pd in self .pad_power_domains :
467+ for name , port in ports .items ():
468+ if port .pad_power_domain == pd :
469+ logger .debug (f"Allocating pins for { name } : { port } " )
470+ pins = allocate (unallocated , port .width )
471+ if len (pins ) == 0 : # TODO turn this into an exception?
472+ raise ChipFlowError ("No pins were allocated" )
473+ # logger.debug(f"allocated range: {pins}")
474+ unallocated = unallocated - set (pins )
475+ port .pins = pins
440476
441477def construct_portmap (config : 'Config' , interfaces : dict , lockfile : Optional ['LockFile' ], core : Component , allocate : AllocateFunc , unallocated : Set [Pin ]) -> PortMap :
442478 portmap = PortMap ()
@@ -956,9 +992,9 @@ def _allocate_pins(self, config: 'Config', process: 'Process', lockfile: LockFil
956992
957993 def _allocate (self , available : PinSet , width : int ) -> List [Pin ]:
958994 avail_n : List [Pin ] = sorted (available )
959- logger .debug (f"QuadPackageDef.allocate { width } from { len (avail_n )} remaining: { available } " )
995+ # logger.debug(f"QuadPackageDef.allocate {width} from {len(avail_n)} remaining: {available}")
960996 ret = find_contiguous_sequence (self ._ordered_pins , avail_n , width )
961- logger .debug (f"QuadPackageDef.returned { ret } " )
997+ # logger.debug(f"QuadPackageDef.returned {ret}")
962998 assert len (ret ) == width
963999 return ret
9641000
0 commit comments