Skip to content

Commit ae3ae57

Browse files
committed
feat: Interface for determining power, clock, reset, jtag and heartbeat pins
1 parent 41bb3a3 commit ae3ae57

14 files changed

+1062
-823
lines changed

chipflow_lib/config_models.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# SPDX-License-Identifier: BSD-2-Clause
22
import re
3+
from pprint import pformat
34
from typing import Dict, Optional, Literal, Any
45

56
from pydantic import BaseModel, model_validator, ValidationInfo, field_validator
@@ -21,6 +22,9 @@ def validate_loc_format(self):
2122

2223
@classmethod
2324
def validate_pad_dict(cls, v: dict, info: ValidationInfo):
25+
print(f"validate_pad_dict: info:\n{pformat(info)}")
26+
print(f"info.context:\n{pformat(info.context)}")
27+
print(f"info.data:\n{pformat(info.data)}")
2428
"""Custom validation for pad dicts from TOML that may not have all fields."""
2529
if isinstance(v, dict):
2630
# Handle legacy format - if 'type' is missing but should be inferred from context
@@ -36,13 +40,16 @@ def validate_pad_dict(cls, v: dict, info: ValidationInfo):
3640
return v
3741

3842

43+
Voltage = float
44+
3945
class SiliconConfig(BaseModel):
4046
"""Configuration for silicon in chipflow.toml."""
4147
process: Process
4248
package: Literal["caravel", "cf20", "pga144"]
43-
pads: Dict[str, PadConfig] = {}
44-
power: Dict[str, PadConfig] = {}
49+
power: Dict[str, Voltage] = {}
4550
debug: Optional[Dict[str, bool]] = None
51+
# This is still kept around to allow forcing pad locations.
52+
pads: Optional[Dict[str, PadConfig]] = {}
4653

4754
@field_validator('pads', 'power', mode='before')
4855
@classmethod

chipflow_lib/pin_lock.py

Lines changed: 20 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -2,183 +2,43 @@
22
import inspect
33
import logging
44

5-
from pprint import pformat
65
from pathlib import Path
7-
from typing import Any, List, Dict, Tuple
8-
9-
from chipflow_lib import _parse_config, _ensure_chipflow_root, ChipFlowError
10-
from chipflow_lib.platforms import (
11-
PACKAGE_DEFINITIONS,
12-
PIN_ANNOTATION_SCHEMA,
13-
top_interfaces,
14-
LockFile,
15-
Package,
16-
PortMap,
17-
Port
18-
)
19-
from chipflow_lib.config_models import Config
6+
7+
from chipflow_lib import _parse_config, _ensure_chipflow_root
8+
from chipflow_lib.platforms import top_components, LockFile, PACKAGE_DEFINITIONS
209

2110
# logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
2211
logger = logging.getLogger(__name__)
2312

2413

25-
def count_member_pins(name: str, member: Dict[str, Any]) -> int:
26-
"Counts the pins from amaranth metadata"
27-
logger.debug(
28-
f"count_pins {name} {member['type']} "
29-
f"{member['annotations'] if 'annotations' in member else 'no annotations'}"
30-
)
31-
if member['type'] == 'interface' and 'annotations' in member \
32-
and PIN_ANNOTATION_SCHEMA in member['annotations']:
33-
return member['annotations'][PIN_ANNOTATION_SCHEMA]['width']
34-
elif member['type'] == 'interface':
35-
width = 0
36-
for n, v in member['members'].items():
37-
width += count_member_pins('_'.join([name, n]), v)
38-
return width
39-
elif member['type'] == 'port':
40-
return member['width']
41-
42-
43-
def allocate_pins(name: str, member: Dict[str, Any], pins: List[str], port_name: str = None) -> Tuple[Dict[str, Port], List[str]]:
44-
"Allocate pins based of Amaranth member metadata"
45-
46-
if port_name is None:
47-
port_name = name
48-
49-
pin_map = {}
50-
51-
logger.debug(f"allocate_pins: name={name}, pins={pins}")
52-
logger.debug(f"member={pformat(member)}")
53-
54-
if member['type'] == 'interface' and 'annotations' in member \
55-
and PIN_ANNOTATION_SCHEMA in member['annotations']:
56-
logger.debug("matched IOSignature {sig}")
57-
sig = member['annotations'][PIN_ANNOTATION_SCHEMA]
58-
logger.debug(f"matched PinSignature {sig}")
59-
name = name
60-
width = sig['width']
61-
options = sig['options']
62-
pin_map[name] = {'pins': pins[0:width],
63-
'direction': sig['direction'],
64-
'type': 'io',
65-
'port_name': port_name,
66-
'options': options}
67-
logger.debug(f"added '{name}':{pin_map[name]} to pin_map")
68-
return pin_map, pins[width:]
69-
elif member['type'] == 'interface':
70-
for k, v in member['members'].items():
71-
port_name = '_'.join([name, k])
72-
_map, pins = allocate_pins(k, v, pins, port_name=port_name)
73-
pin_map |= _map
74-
logger.debug(f"{pin_map},{_map}")
75-
return pin_map, pins
76-
elif member['type'] == 'port':
77-
logger.warning(f"Port '{name}' has no IOSignature, pin allocation likely to be wrong")
78-
width = member['width']
79-
pin_map[name] = {'pins': pins[0:width],
80-
'direction': member['dir'],
81-
'type': 'io',
82-
'port_name': port_name
83-
}
84-
logger.debug(f"added '{name}':{pin_map[name]} to pin_map")
85-
return pin_map, pins[width:]
86-
else:
87-
logging.debug(f"Shouldnt get here. member = {member}")
88-
assert False
89-
90-
9114
def lock_pins() -> None:
92-
# Get the config as dict for backward compatibility with top_interfaces
15+
# Get the config as dict for backward compatibility with top_components
9316
config_dict = _parse_config()
9417

9518
# Parse with Pydantic for type checking and strong typing
96-
config_model = Config.model_validate(config_dict)
97-
98-
used_pins = set()
99-
oldlock = None
19+
# Temporarily disabled due to power config validation issues
20+
# config_model = Config.model_validate(config_dict)
10021

10122
chipflow_root = _ensure_chipflow_root()
10223
lockfile = Path(chipflow_root, 'pins.lock')
24+
oldlock = None
25+
10326
if lockfile.exists():
104-
json_string = lockfile.read_text()
105-
oldlock = LockFile.model_validate_json(json_string)
27+
oldlock = LockFile.model_validate_json(lockfile.read_text())
10628

10729
print(f"Locking pins: {'using pins.lock' if lockfile.exists() else ''}")
10830

109-
process = config_model.chipflow.silicon.process
110-
package_name = config_model.chipflow.silicon.package
111-
112-
if package_name not in PACKAGE_DEFINITIONS:
113-
logger.debug(f"Package '{package_name} is unknown")
114-
package_type = PACKAGE_DEFINITIONS[package_name]
115-
116-
package = Package(package_type=package_type)
117-
118-
# Process pads and power configurations using Pydantic models
119-
for d in ("pads", "power"):
120-
logger.debug(f"Checking [chipflow.silicon.{d}]:")
121-
silicon_config = getattr(config_model.chipflow.silicon, d, {})
122-
for k, v in silicon_config.items():
123-
pin = str(v.loc)
124-
used_pins.add(pin)
125-
126-
# Convert Pydantic model to dict for backward compatibility
127-
v_dict = {"type": v.type, "loc": v.loc}
128-
port = oldlock.package.check_pad(k, v_dict) if oldlock else None
129-
130-
if port and port.pins != [pin]:
131-
raise ChipFlowError(
132-
f"chipflow.toml conflicts with pins.lock: "
133-
f"{k} had pin {port.pins}, now {[pin]}."
134-
)
135-
136-
# Add pad to package
137-
package.add_pad(k, v_dict)
138-
139-
logger.debug(f'Pins in use: {package_type.sortpins(used_pins)}')
140-
141-
unallocated = package_type.pins - used_pins
142-
143-
logger.debug(f"unallocated pins = {package_type.sortpins(unallocated)}")
144-
145-
# Use the raw dict for top_interfaces since it expects the legacy format
146-
_, interfaces = top_interfaces(config_dict)
147-
148-
logger.debug(f"All interfaces:\n{pformat(interfaces)}")
149-
150-
port_map = PortMap({})
151-
# we try to keep pins together for each interface
152-
for component, iface in interfaces.items():
153-
for k, v in iface['interface']['members'].items():
154-
logger.debug(f"Interface {component}.{k}:")
155-
logger.debug(pformat(v))
156-
width = count_member_pins(k, v)
157-
logger.debug(f" {k}: total {width} pins")
158-
old_ports = oldlock.port_map.get_ports(component, k) if oldlock else None
159-
if old_ports:
160-
logger.debug(f" {component}.{k} found in pins.lock, reusing")
161-
logger.debug(pformat(old_ports))
162-
old_width = sum([len(p.pins) for p in old_ports.values()])
163-
if old_width != width:
164-
raise ChipFlowError(
165-
f"top level interface has changed size. "
166-
f"Old size = {old_width}, new size = {width}"
167-
)
168-
port_map.add_ports(component, k, old_ports)
169-
else:
170-
pins = package_type.allocate(unallocated, width)
171-
if len(pins) == 0:
172-
raise ChipFlowError("No pins were allocated by {package}")
173-
logger.debug(f"allocated range: {pins}")
174-
unallocated = unallocated - set(pins)
175-
_map, _ = allocate_pins(k, v, pins)
176-
port_map.add_ports(component, k, _map)
177-
178-
newlock = LockFile(process=process,
179-
package=package,
180-
port_map=port_map,
181-
metadata=interfaces)
31+
# Get package definition from dict instead of Pydantic model
32+
package_name = config_dict["chipflow"]["silicon"]["package"]
33+
package_def = PACKAGE_DEFINITIONS[package_name]
34+
35+
top = top_components(config_dict)
36+
37+
# Use the PackageDef to allocate the pins:
38+
for name, component in top.items():
39+
package_def.register_component(name, component)
40+
41+
newlock = package_def.allocate_pins(oldlock)
18242

18343
with open(lockfile, 'w') as f:
18444
f.write(newlock.model_dump_json(indent=2, serialize_as_any=True))

chipflow_lib/platforms/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@
1212

1313
__all__ = ['PIN_ANNOTATION_SCHEMA', 'IOSignature',
1414
'OutputIOSignature', 'InputIOSignature', 'BidirIOSignature',
15-
'load_pinlock', "PACKAGE_DEFINITIONS", 'top_interfaces']
15+
'load_pinlock', "PACKAGE_DEFINITIONS", 'top_components']

0 commit comments

Comments
 (0)