@@ -65,7 +65,7 @@ class PowerDomain:
6565ComponentName = str
6666InterfaceName = str
6767PowerDomainName = str
68-
68+ InterfacePowerDomainName = str
6969
7070@dataclass
7171class PowerConfig :
@@ -100,7 +100,7 @@ class PowerConfig:
100100class IOModelOptions (TypedDict ):
101101 invert : NotRequired [bool | Tuple [bool , ...]]
102102 all_have_oe : NotRequired [bool ]
103- interface_power_domain : NotRequired [str ]
103+ interface_power_domains : NotRequired [List [ str ] ]
104104 clock_domain : NotRequired [str ]
105105 init : NotRequired [Annotated [Const , ConstSerializer , ConstSchema ]]
106106
@@ -120,7 +120,7 @@ class IOModel(IOModelOptions):
120120 the entire port. If the value is an iterable of :class:`bool`, the iterable must have the
121121 same length as the width of :py:`io`, and the inversion is specified for individual wires.
122122 allocate_power: Whether a io power domain should be set on this interface. NB there is only one of these, so IO with multiple IO power domains must be split up.
123- interface_power_domain : the name of the power domain on the interface-side
123+ interface_power_domains : the name of the available power domains on the interface
124124 clock_domain: the name of the `Amaranth.ClockDomain` for this port. NB there is only one of these, so IO with multiple input clocks must be split up.
125125 init: a :ref:`Const` value for the initial values of the port
126126 """
@@ -172,7 +172,8 @@ class Port(pydantic.BaseModel):
172172 pins : List [Pin ] | None # None implies must be allocated at end
173173 port_name : str
174174 iomodel : IOModel
175- port_power_domain : Optional [PowerDomainName ] = None
175+ power_map : Dict [InterfacePowerDomainName , PowerDomainName ] = {}
176+ power_domain : Optional [str ] = None
176177
177178 def model_post_init (self , __context ):
178179 logger .debug (f"Instantiating port { self .port_name } : { self } " )
@@ -199,12 +200,12 @@ def invert(self) -> Iterable[bool] | None:
199200 return None
200201
201202 @property
202- def interface_power_domain (self ) -> str | None :
203- if 'interface_power_domain ' in self .iomodel :
204- assert isinstance (self .iomodel ['interface_power_domain ' ], str )
205- return self .iomodel ['interface_power_domain ' ]
203+ def interface_power_domains (self ) -> List [ str ] :
204+ if 'interface_power_domains ' in self .iomodel :
205+ assert isinstance (self .iomodel ['interface_power_domains ' ], list )
206+ return self .iomodel ['interface_power_domains ' ]
206207 else :
207- return None
208+ return []
208209
209210
210211def create_ports (name : str , member : Dict [str , Any ], port_name : str ) -> Dict [str , Port ]:
@@ -247,7 +248,7 @@ def create_ports(name: str, member: Dict[str, Any], port_name: str) -> Dict[str,
247248
248249class PortMap (pydantic .BaseModel ):
249250 ports : Dict [str , Component ] = {}
250- port_power_domains : Dict [str , PowerDomain ] = {}
251+ port_power_domains : Dict [PowerDomainName , PowerDomain ] = {}
251252
252253 def get_ports (self , component : str , interface : str ) -> Interface :
253254 "List the ports allocated in this PortMap for the given `Component` and `Interface`"
@@ -299,7 +300,7 @@ def populate(self, interfaces: dict, lockfile: Optional['LockFile'] = None):
299300 if old_ports :
300301 logger .debug (f" { component } .{ interface } found in pins.lock, reusing" )
301302 logger .debug (pformat (old_ports ))
302- old_width = sum ([len (p .pins ) for p in old_ports .values () if p .pins is not None ])
303+ old_width = sum ([len (p .pins ) for p in old_ports .values () if p .pins is not None and p . type == 'io' ])
303304 if old_width != width :
304305 raise ChipFlowError (
305306 f"Interface { component } .{ interface } has changed size. "
@@ -312,6 +313,26 @@ def populate(self, interfaces: dict, lockfile: Optional['LockFile'] = None):
312313 self .ports [component ] = {}
313314 self .ports [component ][interface ] = create_ports (interface , v , interface )
314315
316+ def create_power_ports (self , component , interface ):
317+ power_map :Dict [InterfacePowerDomainName , PowerDomainName ] = {}
318+ 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 ():
323+ prefix = f"_power_{ ipd } _{ ppd } "
324+ power_port = prefix + "_vdd"
325+ power_domain = f"{ component } .{ interface } .{ ipd } "
326+ if power_port not in self .ports [component ][interface ]:
327+ self .ports [component ][interface ][power_port ] = Port (type = 'power' , pins = None , port_name = power_port ,
328+ power_domain = power_domain , iomodel = IOModel (width = 1 , direction = io .Direction .Input ))
329+
330+ power_port = prefix + "_vss"
331+ if power_port not in self .ports [component ][interface ]:
332+ self .ports [component ][interface ][power_port ] = Port (type = 'power' , pins = None , port_name = power_port ,
333+ power_domain = power_domain , iomodel = IOModel (width = 1 , direction = io .Direction .Input ))
334+
335+
315336 def allocate_power (self , config : 'Config' ):
316337 "Allocate power domains to top level ports"
317338 if not config .chipflow .power :
@@ -329,9 +350,9 @@ def allocate_power(self, config: 'Config'):
329350 return
330351
331352 # special handling for core
332- for c , v in self .ports ['_core' ].items ():
333- for i , p in v .items ():
334- p . port_power_domain = '_core'
353+ # for c, v in self.ports['_core'].items():
354+ # for i, p in v.items():
355+ # p.power = '_core'
335356
336357 # convert nested dict structure into a mapping ic_power_domain:[component, interface, {port,} {ip power domain}]
337358 map_config = config .chipflow .power .map
@@ -340,19 +361,34 @@ def map_ports(c, i, *, port_power_domain, port_name=None, interface_power_domain
340361 # a bit verbose but easier to understand, I hope..
341362 if port_power_domain not in self .port_power_domains :
342363 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 } ')" )
343- if not port_name and interface_power_domain :
344- for p in self .ports [c ][i ].values ():
345- if p .interface_power_domain == interface_power_domain :
364+ if interface_power_domain :
365+ if port_name :
366+ p = self .ports [c ][i ][port_name ]
367+ if interface_power_domain not in p .interface_power_domains :
368+ raise ChipFlowError (f"{ interface_power_domain } not found in { c } .{ i } .{ p } power domains ({ p .interface_power_domains } " )
346369 p .port_power_domain = port_power_domain
347- elif not port_name :
348- for p in self .ports [c ][i ].values ():
349- p .port_power_domain = port_power_domain
350- elif port_power_domain and not interface_power_domain :
351- self .ports [c ][i ][port_name ].port_power_domain = port_power_domain
370+ self .interface_power_map [interface_power_domain ] = port_power_domain
371+ else :
372+ for p in self .ports [c ][i ].values ():
373+ 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 } " )
377+
378+ p .power_map [interface_power_domain ] = port_power_domain
352379 else :
353- p = self .ports [c ][i ][port_name ]
354- if p .interface_power_domain == interface_power_domain :
355- p .port_power_domain = port_power_domain
380+ if port_name :
381+ # first one considered default
382+ p = self .ports [c ][i ][port_name ]
383+ if not p .interface_power_domains :
384+ raise ChipFlowError (f"No power domains available for { c } .{ i } .{ port_name } " )
385+ interface_power_domain = p .interface_power_domains [0 ]
386+ p .power_map [interface_power_domain ] = port_power_domain
387+ else :
388+ for p in self .ports [c ][i ].values ():
389+ if p .interface_power_domains :
390+ interface_power_domain = p .interface_power_domains [0 ]
391+ p .power_map [interface_power_domain ] = port_power_domain
356392
357393 # must be an easier way to do this...
358394 for c , v in map_config .items ():
@@ -367,6 +403,7 @@ def map_ports(c, i, *, port_power_domain, port_name=None, interface_power_domain
367403 map_ports (c , i , port_power_domain = v )
368404 if isinstance (v , dict ):
369405 for x , v in v .items ():
406+ print ([p .interface_power_domains for p in self .ports [c ][i ].values () if p .interface_power_domains ])
370407 # is x a port name?
371408 if x in self .ports [c ][i ]:
372409 if isinstance (v , str ):
@@ -375,46 +412,31 @@ def map_ports(c, i, *, port_power_domain, port_name=None, interface_power_domain
375412 for y , v in v .items ():
376413 map_ports (c , i , port_name = x , interface_power_domain = y , port_power_domain = v )
377414 else :
378- raise ChipFlowError (f"Malformed [chipflow.power.map] section: { c } .{ i } .{ x } = { v } ('{ x } ' is a port )" )
415+ raise ChipFlowError (f"Malformed [chipflow.power.map] section: { c } .{ i } .{ x } = { v } ('{ v } ' is not a valid power domain )" )
379416 # is x an interface-side power domain?
380- elif any (x == p .interface_power_domain for p in self .ports [c ][i ].values ()):
417+ elif any (x in p .interface_power_domains for p in self .ports [c ][i ].values () if p . interface_power_domains ):
381418 if not isinstance (v , str ):
382- raise ChipFlowError (f"Malformed [chipflow.power.map] section: { c } .{ i } .{ x } = { v } ('{ x } ' is a power domaiwn )" )
419+ raise ChipFlowError (f"Malformed [chipflow.power.map] section: { c } .{ i } .{ x } = { v } , ('{ x } ' isnot a valid power domain )" )
383420 else :
384421 # map interface-side power domain x to port-side power domain v
385422 map_ports (c , i , interface_power_domain = x , port_power_domain = v )
386423 else :
387424 raise ChipFlowError (f"Malformed [chipflow.power.map] section: { c } .{ i } .{ x } = { v } (unable to interpret '{ x } ')" )
425+
426+ self .create_power_ports (c , i )
427+
388428 def allocate_pins (self , config : 'Config' , allocate : AllocateFunc , unallocated : Set [Pin ]) -> None :
389429 logger .debug ("Allocating pins" )
390430 for c ,v in self .ports .items ():
391431 for i ,v in v .items ():
392- power_domains = set ()
393432 for n , port in v .items ():
394433 if not port .pins :
395434 pins = allocate (unallocated , port .width )
396435 if len (pins ) == 0 : # TODO turn this into an exception?
397436 raise ChipFlowError ("No pins were allocated" )
398- logger .debug (f"allocated range: { pins } " )
437+ # logger.debug(f"allocated range: {pins}")
399438 unallocated = unallocated - set (pins )
400439 port .pins = pins
401- if port .interface_power_domain :
402- power_domains .add (port .interface_power_domain )
403- for pd in power_domains :
404- if pd == '_core' : # core power handled specially
405- continue
406- logger .debug (f"Allocating power pins for { c } .{ i } , domain { pd } " )
407- powerpins = allocate (unallocated , 2 )
408-
409- power_port = '_power_vdd_' + pd
410- if power_port not in self .ports [c ][i ]:
411- self .ports [c ][i ][power_port ] = Port (type = 'vdd' , pins = [powerpins [0 ]], port_name = power_port ,
412- iomodel = IOModel (width = 1 , direction = io .Direction .Input , interface_power_domain = pd ))
413-
414- power_port = '_power_vss_' + pd
415- if power_port not in self .ports [c ][i ]:
416- self .ports [c ][i ][power_port ] = Port (type = 'vss' , pins = [powerpins [1 ]], port_name = power_port ,
417- iomodel = IOModel (width = 1 , direction = io .Direction .Input , interface_power_domain = pd ))
418440
419441def construct_portmap (config : 'Config' , interfaces : dict , lockfile : Optional ['LockFile' ], core : Component , allocate : AllocateFunc , unallocated : Set [Pin ]) -> PortMap :
420442 portmap = PortMap ()
@@ -459,6 +481,8 @@ class IOSignature(wiring.Signature):
459481 """
460482
461483 def __init__ (self , ** kwargs : Unpack [IOModel ]):
484+ # runtime check..
485+ assert set (kwargs .keys ()).issubset (set (IOModel .__annotations__ .keys ()))
462486 model = IOModel (** kwargs )
463487 assert 'width' in model
464488 assert 'direction' in model
@@ -602,17 +626,17 @@ def group_consecutive_items(ordering: PinList, lst: PinList) -> OrderedDict[int,
602626 last = lst [0 ]
603627 current_group = [last ]
604628
605- logger .debug (f"_group_consecutive_items starting with { current_group } " )
629+ # logger.debug(f"_group_consecutive_items starting with {current_group}")
606630
607631 for item in lst [1 :]:
608632 idx = ordering .index (last )
609633 next = ordering [idx + 1 ] if idx < len (ordering ) - 1 else None
610- logger .debug (f"inspecting { item } , index { idx } , next { next } " )
634+ # logger.debug(f"inspecting {item}, index {idx}, next {next}")
611635 if item == next :
612636 current_group .append (item )
613- logger .debug ("found consecutive, adding to current group" )
637+ # logger.debug("found consecutive, adding to current group")
614638 else :
615- logger .debug ("found nonconsecutive, creating new group" )
639+ # logger.debug("found nonconsecutive, creating new group")
616640 grouped .append (current_group )
617641 current_group = [item ]
618642 last = item
@@ -755,13 +779,13 @@ def _allocate_bringup(self, config: 'Config') -> Component:
755779 pins = [self .bringup_pins .core_clock ],
756780 port_name = 'sync-clk' ,
757781 iomodel = IOModel (width = 1 , direction = io .Direction .Input ,
758- clock_domain = "sync" , interface_power_domain = '_core' )
782+ clock_domain = "sync" )
759783 ),
760784 'sync-rst' : Port (type = 'reset' ,
761785 pins = [self .bringup_pins .core_reset ],
762786 port_name = 'sync-rst' ,
763787 iomodel = IOModel (width = 1 , direction = io .Direction .Input ,
764- clock_domain = "sync" , interface_power_domain = '_core' )
788+ clock_domain = "sync" )
765789 )
766790 }
767791 vdd_pins = []
@@ -770,14 +794,16 @@ def _allocate_bringup(self, config: 'Config') -> Component:
770794 vdd_pins .append (pp .ground )
771795 vss_pins .append (pp .power )
772796
773- d |= {'vdd' : Port (type = 'vdd' ,
774- pins = vdd_pins ,
775- port_name = "vdd-core" ,
776- iomodel = IOModel (width = len (vdd_pins ), direction = io .Direction .Input , interface_power_domain = '_core' )),
777- 'vss' : Port (type = 'vss' ,
797+ d |= {'vdd' : Port (type = 'power' ,
798+ pins = vdd_pins ,
799+ port_name = "vdd-core" ,
800+ power_domain = "_core" ,
801+ iomodel = IOModel (width = len (vdd_pins ), direction = io .Direction .Input )),
802+ 'vss' : Port (type = 'power' ,
778803 pins = vss_pins ,
779804 port_name = "vss-core" ,
780- iomodel = IOModel (width = len (vss_pins ), direction = io .Direction .Input , interface_power_domain = '_core' ))
805+ power_domain = "_core" ,
806+ iomodel = IOModel (width = len (vss_pins ), direction = io .Direction .Input ))
781807 }
782808
783809
@@ -787,7 +813,7 @@ def _allocate_bringup(self, config: 'Config') -> Component:
787813 d ['heartbeat' ] = Port (type = 'heartbeat' ,
788814 pins = [self .bringup_pins .core_heartbeat ],
789815 port_name = 'heartbeat' ,
790- iomodel = IOModel (width = 1 , direction = io .Direction .Output , clock_domain = "sync" , interface_power_domain = '_core' )
816+ iomodel = IOModel (width = 1 , direction = io .Direction .Output , clock_domain = "sync" )
791817 )
792818 #TODO: JTAG
793819 return {'bringup_pins' : d }
@@ -984,7 +1010,7 @@ def _jtag(self) -> JTAGPins:
9841010 """
9851011 # Default JTAG pin allocations
9861012 # Use consecutive pins at the start of the package
987- start_pin = 2
1013+ start_pin = 1
9881014 return JTAGPins (
9891015 trst = start_pin ,
9901016 tck = start_pin + 1 ,
0 commit comments