Skip to content

Commit 0efa14f

Browse files
committed
wip
1 parent f70d056 commit 0efa14f

File tree

1 file changed

+116
-80
lines changed

1 file changed

+116
-80
lines changed

chipflow_lib/platforms/_utils.py

Lines changed: 116 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,12 @@
2626
from amaranth.lib.wiring import In, Out
2727
from pydantic import (
2828
ConfigDict, TypeAdapter, PlainSerializer,
29-
WithJsonSchema, WrapValidator
29+
WithJsonSchema, WrapValidator, Field
3030
)
3131

3232

3333
from .. import ChipFlowError, _ensure_chipflow_root, _get_cls_by_reference
34+
from .._appresponse import AppResponseModel, OmitIfNone
3435

3536
if 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:
6766
PowerDomainName = str
6867
InterfacePowerDomainName = str
6968

69+
70+
@dataclass
71+
class PowerConfigDomains:
72+
pads: Optional[Dict[PowerDomainName, PowerDomain]] = None
73+
74+
7075
@dataclass
7176
class 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:
100105
class 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+
131137
Pin = Tuple[Any,...] | str | int
132138
PinSet = Set[Pin]
133139
PinList = 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-
249260
class 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

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

Comments
 (0)