Skip to content

Commit 2f9f8b6

Browse files
committed
wip
1 parent ee1fbd2 commit 2f9f8b6

File tree

3 files changed

+93
-60
lines changed

3 files changed

+93
-60
lines changed

chipflow_lib/__init__.py

Lines changed: 9 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def _ensure_chipflow_root():
2929
sys.path.append(os.environ["CHIPFLOW_ROOT"])
3030
return os.environ["CHIPFLOW_ROOT"]
3131

32-
32+
# TODO: convert to pydantic
3333
config_schema = {
3434
"$schema": "https://json-schema.org/draft/2020-12/schema",
3535
"$id": "https://chipflow.io/meta/chipflow.toml.schema.json",
@@ -56,6 +56,12 @@ def _ensure_chipflow_root():
5656
"steps": {
5757
"type": "object",
5858
},
59+
"clocks": {
60+
"type": "object",
61+
"patternPropertues": {
62+
".+": { "type": "string"}
63+
},
64+
},
5965
"silicon": {
6066
"type": "object",
6167
"required": [
@@ -72,33 +78,8 @@ def _ensure_chipflow_root():
7278
"enum": ["caravel", "cf20", "pga144"]
7379
},
7480
"pads": { "$ref": "#/$defs/pin" },
75-
"clocks": {
76-
"type": "object",
77-
"patternPropertues": {
78-
".+": { "type": "string"}
79-
},
80-
},
8181
"reset": { "type": "string"},
82-
"power": {
83-
"type": "object",
84-
"additionalProperties": False,
85-
"patternProperties": {
86-
".+": {
87-
"type": "object",
88-
"required": [
89-
"loc",
90-
],
91-
"additionalProperties": False,
92-
"properties": {
93-
"loc": {
94-
"type": "string",
95-
"pattern": "^[NSWE]?[0-9]+$"
96-
},
97-
}
98-
}
99-
}
100-
},
101-
82+
"power": { "$ref": "#/$defs/pin" },
10283
},
10384
},
10485
},
@@ -119,7 +100,7 @@ def _ensure_chipflow_root():
119100
"additionalProperties": False,
120101
"properties": {
121102
"type": {
122-
"enum": ["io", "i", "o", "oe", "clk"]
103+
"enum": ["io", "i", "o", "oe", "clock", "reset", "source", "drain"]
123104
},
124105
"loc": {
125106
"type": "string",

chipflow_lib/pin_lock.py

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from chipflow_lib import _parse_config
99
from chipflow_lib.platforms import PACKAGE_DEFINITIONS, PIN_ANNOTATION_SCHEMA, top_interfaces
10-
from chipflow_lib.platforms.utils import LockFile, Package, Interface, Port
10+
from chipflow_lib.platforms.utils import LockFile, Package, PortMap
1111

1212
# logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
1313
logger = logging.getLogger(__name__)
@@ -84,8 +84,8 @@ def check_pins(name, member, old_map, new_map):
8484
def lock_pins():
8585
config = _parse_config()
8686
used_pins = set()
87-
newlock = LockFile()
8887
oldlock = None
88+
8989
lockfile = Path('pins.lock')
9090
if lockfile.exists():
9191
json_string = lockfile.read_text()
@@ -96,52 +96,51 @@ def lock_pins():
9696

9797
if package_name not in PACKAGE_DEFINITIONS:
9898
logger.debug(f"Package '{package_name} is unknown")
99-
package = PACKAGE_DEFINITIONS[package_name]
99+
package_type = PACKAGE_DEFINITIONS[package_name]
100100

101-
newlock.package.package_name = package_name
101+
package = Package(package_type=package_type)
102102
for d in ("pads", "power"):
103103
logger.debug(f"Checking [chipflow.silicon.{d}]:")
104104
_map = {}
105105
for k, v in config["chipflow"]["silicon"][d].items():
106106
pin = str(v['loc'])
107107
used_pins.add(pin)
108-
if d in old_map and k in old_map[d] and old_map[d][k]['pins'] != [pin]:
108+
port = oldlock.package.check_pad(k,v) if oldlock else None
109+
if port and port.pins != [pin]:
109110
print(f"chipflow.toml conflicts with pins.lock: "
110-
f"{k} had pin {old_map[k]['pins']}, now {[pin]}.")
111+
f"{k} had pin {port.pins}, now {[pin]}.")
111112
exit(1)
112-
_map[k] = {
113-
'pins': [pin],
114-
'type': v['type'] if 'type' in v else d}
115-
pin_map[d] = _map
113+
package.add_pad(k, v)
116114

117115

118116
logger.debug(f'Pins in use: {sorted(used_pins)}')
119117

120-
unallocated = package.pins - used_pins
118+
unallocated = package_type.pins - used_pins
121119

122120
logger.debug(f"unallocated pins = {sorted(unallocated)}")
123121

124122
_, interfaces = top_interfaces(config)
125123

126124
logger.debug(f"All interfaces: {[v.keys() for i in interfaces]}")
127125

126+
port_map = PortMap({})
128127
# we try to keep pins together for each interface
129128
for component, iface in interfaces.items():
130-
pin_map[component] = {}
131129
for k,v in iface.items():
132130
logger.debug(f"Interface {component}.{k}:")
133131
logger.debug(pformat(v))
134132
width = count_pins(k, v)
135133
print(f" {k}: total {width} pins")
136-
if component in old_map and k in old_map[component]:
134+
old_ports = oldlock.port_map.get_ports(component,k)
135+
if old_ports:
137136
print(f" {component}.{k} found in pins.lock, reusing")
138-
logger.debug(pformat(old_map[component][k]))
139-
old_width = count_old_pins(old_map[k])
137+
logger.debug(pformat(port))
138+
old_width = count_old_pins(old_ports)
140139
if old_width != width:
141140
# TODO: option to allocate new pins nonconsecutively
142141
print(f"top level interface has changed size. Old size = {old_width}, new size = {width}")
143142
exit(1)
144-
pin_map[component][k] = old_map[component][k]
143+
port_map.add_ports(old_ports)
145144
else:
146145
pins = package.allocate(unallocated, width)
147146
if len(pins) == 0:
@@ -150,14 +149,12 @@ def lock_pins():
150149
logger.debug(f"allocated range: {pins}")
151150
unallocated = unallocated - set(pins)
152151
_map, _ = allocate_pins(k, v, pins)
153-
pin_map[component][k] = _map
152+
port_map.add_ports(_map)
154153

155-
with open('pins.lock', 'w') as f:
156-
newlock = {'map': pin_map,
157-
'metadata': interfaces}
154+
newlock = LockFile(package=package, port_map=port_map, metadata=interfaces)
158155

159-
assert _LockFile.model_validate(newlock)
160-
json.dump(newlock, f, indent=2, sort_keys=True)
156+
with open('pins.lock', 'w') as f:
157+
json.dump(newlock.dump_json(), f, indent=2, sort_keys=True)
161158

162159
class PinCommand:
163160
def __init__(self, config):

chipflow_lib/platforms/utils.py

Lines changed: 65 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import pathlib
44
import pydantic
55

6-
from dataclasses import dataclass
76
from pprint import pformat
87
from typing import Set, List, Dict, Optional
98

@@ -16,7 +15,7 @@
1615

1716
__all__ = ['PIN_ANNOTATION_SCHEMA', 'PinSignature',
1817
'OutputPinSignature', 'InputPinSignature', 'BidirPinSignature',
19-
'load_pinlock', "PACKAGE_DEFINITIONS"]
18+
'load_pinlock', "PACKAGE_DEFINITIONS", 'top_interfaces']
2019

2120

2221
logger = logging.getLogger(__name__)
@@ -179,7 +178,7 @@ def allocate(self, available: Set[str], width:int) -> List[str]:
179178
return [str(i) for i in ret]
180179

181180
PACKAGE_DEFINITIONS = {
182-
"pga144": _PGAPackageDef("pga144", 144)
181+
"pga144": _PGAPackageDef(name="pga144", size=144)
183182
}
184183

185184

@@ -188,24 +187,80 @@ class Port(pydantic.BaseModel):
188187
pins: List[str]
189188
direction: Optional[str] = None
190189

191-
Interface = Dict[str, Dict[str, Port]]
192190

193191
class Package(pydantic.BaseModel):
194192
package_type: PackageDef
195-
power: Dict[str, Port]
196-
clocks: Dict[str, Port]
197-
resets: Dict[str, Port]
193+
power: Dict[str, Port] = {}
194+
clocks: Dict[str, Port] = {}
195+
resets: Dict[str, Port] = {}
196+
197+
def check_pad(self, name: str, defn: dict):
198+
match defn:
199+
case {"type": "clock"}:
200+
return self.clocks[name] if name in self.clocks else None
201+
case {"type": "reset"}:
202+
return self.resets[name] if name in self.clocks else None
203+
case {"type": "source"}:
204+
return self.power[name] if name in self.clocks else None
205+
case {"type": "drain"}:
206+
return self.power[name] if name in self.clocks else None
207+
case _:
208+
return None
209+
210+
def add_pad(self, name:str, defn: dict):
211+
match defn:
212+
case {"type": "clock", "loc":loc}:
213+
self.clocks[name] = Port(type="clock", pins=[loc], direction=io.Direction.Input)
214+
case {"type": "reset", "loc":loc}:
215+
self.resets[name] = Port(type="reset", pins=[loc], direction=io.Direction.Input)
216+
case {"type": "source", "loc":loc}:
217+
self.power[name] = Port(type="source", pins=[loc])
218+
case {"type": "drain", "loc":loc}:
219+
self.power[name] = Port(type="drain", pins=[loc])
220+
case _:
221+
pass
222+
223+
224+
class PortMap(pydantic.RootModel[Dict[str, Dict[str, Dict[str, Port]]]]):
225+
def add_port(self, component:str, interface: str, port_name:str, port: Port):
226+
if component not in self:
227+
self[component] = {}
228+
if interface not in self[component]:
229+
self[component][interface] = {}
230+
self[component][interface][port_name] = port
231+
232+
def add_ports(self, component:str, interface: str, ports: Dict[str,Port]):
233+
if component not in self:
234+
self[component] = {}
235+
self[component][interface] = ports
236+
237+
def get_ports(self, component:str, name: str) -> Dict[str, Port]:
238+
if component not in self:
239+
return None
240+
return self[component][name]
241+
198242

199243
class LockFile(pydantic.BaseModel):
244+
"""
245+
Representation of a pin lock file.
246+
247+
Attributes:
248+
package: Information about package, power, clocks, reset etc
249+
port_map: Mapping of components to interfaces to port
250+
metadata: Amaranth metadata, for reference
251+
"""
200252
package: Package
201-
port_map: Interface
253+
port_map: PortMap
202254
metadata: dict
203255

204256

205257
def load_pinlock():
206258
chipflow_root = _ensure_chipflow_root()
207-
json = pathlib.Path(chipflow_root, 'pins.lock').read_text()
208-
return LockFile.model_validate_json(json)
259+
lockfile = pathlib.Path(chipflow_root, 'pins.lock')
260+
if lockfile.exists():
261+
json = lockfile.read_text()
262+
return LockFile.model_validate_json(json)
263+
raise ChipFlowError("Lockfile pins.lock not found. Run `chipflow pin lock`")
209264

210265

211266
def top_interfaces(config):

0 commit comments

Comments
 (0)