Skip to content

Commit 12257ad

Browse files
committed
feat: Interface for determining power, clock, reset, jtag and heartbeat pins
1 parent 0c724be commit 12257ad

File tree

5 files changed

+475
-26
lines changed

5 files changed

+475
-26
lines changed

chipflow_lib/config_models.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,16 @@ def validate_pad_dict(cls, v: dict, info: ValidationInfo):
3636
return v
3737

3838

39+
Voltage = float
40+
3941
class SiliconConfig(BaseModel):
4042
"""Configuration for silicon in chipflow.toml."""
4143
process: Process
4244
package: Literal["caravel", "cf20", "pga144"]
43-
pads: Dict[str, PadConfig] = {}
44-
power: Dict[str, PadConfig] = {}
45+
power: Dict[str, Voltage] = {}
4546
debug: Optional[Dict[str, bool]] = None
47+
# This is still kept around to allow forcing pad locations.
48+
pads: Optional[Dict[str, PadConfig]] = {}
4649

4750
@field_validator('pads', 'power', mode='before')
4851
@classmethod

chipflow_lib/pin_lock.py

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -114,27 +114,31 @@ def lock_pins() -> None:
114114

115115
package = Package(package_type=package_type)
116116

117-
# Process pads and power configurations using Pydantic models
118-
for d in ("pads", "power"):
119-
logger.debug(f"Checking [chipflow.silicon.{d}]:")
120-
silicon_config = getattr(config_model.chipflow.silicon, d, {})
121-
for k, v in silicon_config.items():
122-
pin = str(v.loc)
123-
used_pins.add(pin)
124-
125-
# Convert Pydantic model to dict for backward compatibility
126-
v_dict = {"type": v.type, "loc": v.loc}
127-
port = oldlock.package.check_pad(k, v_dict) if oldlock else None
128-
129-
if port and port.pins != [pin]:
130-
raise ChipFlowError(
131-
f"chipflow.toml conflicts with pins.lock: "
132-
f"{k} had pin {port.pins}, now {[pin]}."
133-
)
134-
135-
# Add pad to package
136-
package.add_pad(k, v_dict)
137-
117+
# Initialize standard pins from package type
118+
package.initialize_from_package_type()
119+
120+
# Process user-defined pads
121+
logger.debug(f"Checking [chipflow.silicon.pads]:") if hasattr(config_model.chipflow.silicon, "pads") else ...
122+
silicon_config = getattr(config_model.chipflow.silicon, "pads", {})
123+
for k, v in silicon_config.items():
124+
pin = str(v.loc)
125+
used_pins.add(pin)
126+
127+
# Convert Pydantic model to dict for backward compatibility
128+
v_dict = {"type": v.type, "loc": v.loc}
129+
port = oldlock.package.check_pad(k, v_dict) if oldlock else None
130+
131+
if port and port.pins != [pin]:
132+
raise ChipFlowError(
133+
f"chipflow.toml conflicts with pins.lock: "
134+
f"{k} had pin {port.pins}, now {[pin]}."
135+
)
136+
137+
# Add pad to package
138+
package.add_pad(k, v_dict)
139+
140+
# TODO: power pins
141+
#
138142
logger.debug(f'Pins in use: {package_type.sortpins(used_pins)}')
139143

140144
unallocated = package_type.pins - used_pins

chipflow_lib/platforms/utils.py

Lines changed: 205 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,16 @@ def BidirPinSignature(width, **kwargs):
172172
PinList = List[Pin]
173173
Pins = Union[PinSet, PinList]
174174

175+
class PowerType(enum.Enum):
176+
POWER = "power"
177+
GROUND = "ground"
178+
179+
class JTAGWireName(enum.Enum):
180+
TRST = "trst"
181+
TCK = "tck"
182+
TMS = "tms"
183+
TDI = "tdi"
184+
TDO = "tdo"
175185

176186
class _Side(enum.IntEnum):
177187
N = 1
@@ -264,12 +274,62 @@ class _BasePackageDef(pydantic.BaseModel, abc.ABC):
264274
@property
265275
@abc.abstractmethod
266276
def pins(self) -> PinSet:
277+
"Returns the full set of pins for the package"
267278
...
268279

269280
@abc.abstractmethod
270281
def allocate(self, available: PinSet, width: int) -> PinList:
282+
"""
283+
Allocates pins as close to each other as possible from available pins.
284+
285+
Args:
286+
available: set of available pins
287+
width: number of pins to allocate
288+
289+
Returns:
290+
An ordered list of pins
291+
"""
292+
...
293+
294+
@property
295+
@abc.abstractmethod
296+
def power(self) -> Dict[PowerType, Pin]:
297+
"""
298+
The set of power pins for the package
299+
"""
300+
...
301+
302+
@property
303+
@abc.abstractmethod
304+
def resets(self) -> Dict[int, Pin]:
305+
"""
306+
Numbered set of reset pins for the package
307+
"""
308+
...
309+
310+
@property
311+
@abc.abstractmethod
312+
def clocks(self) -> Dict[int, Pin]:
313+
"""
314+
Numbered set of clock pins for the package
315+
"""
316+
...
317+
318+
@property
319+
@abc.abstractmethod
320+
def jtag(self) -> Dict[JTAGWireName, Pin]:
321+
"""
322+
Map of JTAG pins for the package
323+
"""
271324
...
272325

326+
@property
327+
@abc.abstractmethod
328+
def heartbeat(self) -> Dict[int, Pin]:
329+
"""
330+
Numbered set of heartbeat pins for the package
331+
"""
332+
273333
def to_string(pins: Pins):
274334
return [''.join(map(str, t)) for t in pins]
275335

@@ -306,6 +366,55 @@ def allocate(self, available: PinSet, width: int) -> PinList:
306366
assert len(ret) == width
307367
return ret
308368

369+
@property
370+
def power(self) -> Dict[PowerType, Pin]:
371+
"""
372+
The set of power pins for the package
373+
"""
374+
# Default implementation - to be customized for specific package types
375+
return {
376+
PowerType.POWER: (_Side.N, 0), # North side, pin 0
377+
PowerType.GROUND: (_Side.S, 0) # South side, pin 0
378+
}
379+
380+
@property
381+
def resets(self) -> Dict[int, Pin]:
382+
"""
383+
Numbered set of reset pins for the package
384+
"""
385+
# Default implementation with one reset pin
386+
return {0: (_Side.N, 1)} # North side, pin 1
387+
388+
@property
389+
def clocks(self) -> Dict[int, Pin]:
390+
"""
391+
Numbered set of clock pins for the package
392+
"""
393+
# Default implementation with one clock pin
394+
return {0: (_Side.N, 2)} # North side, pin 2
395+
396+
@property
397+
def jtag(self) -> Dict[JTAGWireName, Pin]:
398+
"""
399+
Map of JTAG pins for the package
400+
"""
401+
# Default JTAG pin allocations
402+
return {
403+
JTAGWireName.TRST: (_Side.W, 0), # West side, pin 0
404+
JTAGWireName.TCK: (_Side.W, 1), # West side, pin 1
405+
JTAGWireName.TMS: (_Side.W, 2), # West side, pin 2
406+
JTAGWireName.TDI: (_Side.W, 3), # West side, pin 3
407+
JTAGWireName.TDO: (_Side.W, 4) # West side, pin 4
408+
}
409+
410+
@property
411+
def heartbeat(self) -> Dict[int, Pin]:
412+
"""
413+
Numbered set of heartbeat pins for the package
414+
"""
415+
# Default implementation with one heartbeat pin
416+
return {0: (_Side.S, 1)} # South side, pin 1
417+
309418

310419
class _QuadPackageDef(_BasePackageDef):
311420
"""Definiton of a PGA package with `size` pins
@@ -341,6 +450,62 @@ def allocate(self, available: Set[str], width: int) -> List[str]:
341450
def sortpins(self, pins: Union[List[str], Set[str]]) -> List[str]:
342451
return sorted(list(pins), key=int)
343452

453+
@property
454+
def power(self) -> Dict[PowerType, Pin]:
455+
"""
456+
The set of power pins for the package
457+
"""
458+
# Default implementation for a PGA package
459+
# Use pin numbers for the corners of the package
460+
total_pins = self.width * 2 + self.height * 2
461+
return {
462+
PowerType.POWER: str(1), # First pin
463+
PowerType.GROUND: str(total_pins // 2) # Middle pin
464+
}
465+
466+
@property
467+
def resets(self) -> Dict[int, Pin]:
468+
"""
469+
Numbered set of reset pins for the package
470+
"""
471+
# Default implementation with one reset pin
472+
# Use a pin near the beginning of the package
473+
return {0: str(2)} # Second pin
474+
475+
@property
476+
def clocks(self) -> Dict[int, Pin]:
477+
"""
478+
Numbered set of clock pins for the package
479+
"""
480+
# Default implementation with one clock pin
481+
# Use a pin near the beginning of the package
482+
return {0: str(3)} # Third pin
483+
484+
@property
485+
def jtag(self) -> Dict[JTAGWireName, Pin]:
486+
"""
487+
Map of JTAG pins for the package
488+
"""
489+
# Default JTAG pin allocations
490+
# Use consecutive pins in the middle of the package
491+
mid_pin = (self.width * 2 + self.height * 2) // 4
492+
return {
493+
JTAGWireName.TRST: str(mid_pin),
494+
JTAGWireName.TCK: str(mid_pin + 1),
495+
JTAGWireName.TMS: str(mid_pin + 2),
496+
JTAGWireName.TDI: str(mid_pin + 3),
497+
JTAGWireName.TDO: str(mid_pin + 4)
498+
}
499+
500+
@property
501+
def heartbeat(self) -> Dict[int, Pin]:
502+
"""
503+
Numbered set of heartbeat pins for the package
504+
"""
505+
# Default implementation with one heartbeat pin
506+
# Use the last pin in the package
507+
return {0: str(self.width * 2 + self.height * 2 - 1)}
508+
344509

345510
# Add any new package types to both PACKAGE_DEFINITIONS and the PackageDef union
346511
PACKAGE_DEFINITIONS = {
@@ -350,7 +515,6 @@ def sortpins(self, pins: Union[List[str], Set[str]]) -> List[str]:
350515

351516
PackageDef = Union[_QuadPackageDef, _BareDiePackageDef]
352517

353-
354518
class Port(pydantic.BaseModel):
355519
type: str
356520
pins: List[str]
@@ -368,13 +532,15 @@ class Package(pydantic.BaseModel):
368532
power: Dict[str, Port] = {}
369533
clocks: Dict[str, Port] = {}
370534
resets: Dict[str, Port] = {}
535+
jtag: Dict[str, Port] = {}
536+
heartbeat: Dict[str, Port] = {}
371537

372538
def check_pad(self, name: str, defn: dict):
373539
match defn:
374540
case {"type": "clock"}:
375541
return self.clocks[name] if name in self.clocks else None
376542
case {"type": "reset"}:
377-
return self.resets[name] if name in self.clocks else None
543+
return self.resets[name] if name in self.resets else None
378544
case {"type": "power"}:
379545
return self.power[name] if name in self.power else None
380546
case {"type": "ground"}:
@@ -391,10 +557,46 @@ def add_pad(self, name: str, defn: dict):
391557
case {"type": "power", "loc": loc}:
392558
self.power[name] = Port(type="power", pins=[loc], port_name=name)
393559
case {"type": "ground", "loc": loc}:
394-
self.power[name] = Port(type="ground", pins=[loc], port_name=name)
560+
self.power[name] = Port(type="ground", pins=[loc])
561+
case {"type": "power", "name": name, "voltage": voltage}:
562+
# Support for new power pin format
563+
# First, get the default pin from the package type
564+
power_pin = self.package_type.power[PowerType.POWER]
565+
self.power[name] = Port(type="power", pins=[str(power_pin)], options={"voltage": voltage})
566+
case {"type": "ground", "name": name}:
567+
# Support for new ground pin format
568+
ground_pin = self.package_type.power[PowerType.GROUND]
569+
self.power[name] = Port(type="ground", pins=[str(ground_pin)])
395570
case _:
396571
pass
397572

573+
def initialize_from_package_type(self):
574+
"""Initialize standard pins from package type definitions"""
575+
# Set up clocks
576+
for clock_id, pin in self.package_type.clocks.items():
577+
name = f"clock_{clock_id}"
578+
if name not in self.clocks:
579+
self.clocks[name] = Port(type="clock", pins=[str(pin)], direction=io.Direction.Input)
580+
581+
# Set up resets
582+
for reset_id, pin in self.package_type.resets.items():
583+
name = f"reset_{reset_id}"
584+
if name not in self.resets:
585+
self.resets[name] = Port(type="reset", pins=[str(pin)], direction=io.Direction.Input)
586+
587+
# Set up heartbeat pins
588+
for hb_id, pin in self.package_type.heartbeat.items():
589+
name = f"heartbeat_{hb_id}"
590+
if name not in self.heartbeat:
591+
self.heartbeat[name] = Port(type="heartbeat", pins=[str(pin)], direction=io.Direction.Output)
592+
593+
# Set up JTAG pins
594+
for jtag_name, pin in self.package_type.jtag.items():
595+
name = f"jtag_{jtag_name.value}"
596+
direction = io.Direction.Output if jtag_name == JTAGWireName.TDO else io.Direction.Input
597+
if name not in self.jtag:
598+
self.jtag[name] = Port(type="jtag", pins=[str(pin)], direction=direction)
599+
398600

399601
_Interface = Dict[str, Dict[str, Port]]
400602

0 commit comments

Comments
 (0)