Skip to content

Commit f70d056

Browse files
committed
wip
1 parent 0442ce8 commit f70d056

File tree

1 file changed

+87
-61
lines changed

1 file changed

+87
-61
lines changed

chipflow_lib/platforms/_utils.py

Lines changed: 87 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ class PowerDomain:
6565
ComponentName = str
6666
InterfaceName = str
6767
PowerDomainName = str
68-
68+
InterfacePowerDomainName = str
6969

7070
@dataclass
7171
class PowerConfig:
@@ -100,7 +100,7 @@ class PowerConfig:
100100
class 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

210211
def 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

248249
class 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

419441
def 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

Comments
 (0)