Skip to content

Interface for determining package pins #38

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions chipflow_lib/config_models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: BSD-2-Clause
import re
from pprint import pformat
from typing import Dict, Optional, Literal, Any

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

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


Voltage = float

class SiliconConfig(BaseModel):
"""Configuration for silicon in chipflow.toml."""
process: Process
package: Literal["caravel", "cf20", "pga144"]
pads: Dict[str, PadConfig] = {}
power: Dict[str, PadConfig] = {}
power: Dict[str, Voltage] = {}
debug: Optional[Dict[str, bool]] = None
# This is still kept around to allow forcing pad locations.
pads: Optional[Dict[str, PadConfig]] = {}

@field_validator('pads', 'power', mode='before')
@classmethod
Expand Down
41 changes: 23 additions & 18 deletions chipflow_lib/pin_lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,9 @@ def allocate_pins(name: str, member: Dict[str, Any], pins: List[str], port_name:

if member['type'] == 'interface' and 'annotations' in member \
and PIN_ANNOTATION_SCHEMA in member['annotations']:
logger.debug("matched PinSignature {sig}")
sig = member['annotations'][PIN_ANNOTATION_SCHEMA]
logger.debug(f"matched PinSignature {sig}")
name = name
width = sig['width']
options = sig['options']
pin_map[name] = {'pins': pins[0:width],
Expand Down Expand Up @@ -113,27 +114,31 @@ def lock_pins() -> None:

package = Package(package_type=package_type)

# Process pads and power configurations using Pydantic models
for d in ("pads", "power"):
logger.debug(f"Checking [chipflow.silicon.{d}]:")
silicon_config = getattr(config_model.chipflow.silicon, d, {})
for k, v in silicon_config.items():
pin = str(v.loc)
used_pins.add(pin)
# Initialize standard pins from package type
package.initialize_from_package_type()

# Convert Pydantic model to dict for backward compatibility
v_dict = {"type": v.type, "loc": v.loc}
port = oldlock.package.check_pad(k, v_dict) if oldlock else None
# Process user-defined pads
logger.debug(f"Checking [chipflow.silicon.pads]:") if hasattr(config_model.chipflow.silicon, "pads") else ...
silicon_config = getattr(config_model.chipflow.silicon, "pads", {})
for k, v in silicon_config.items():
pin = str(v.loc)
used_pins.add(pin)

if port and port.pins != [pin]:
raise ChipFlowError(
f"chipflow.toml conflicts with pins.lock: "
f"{k} had pin {port.pins}, now {[pin]}."
)
# Convert Pydantic model to dict for backward compatibility
v_dict = {"type": v.type, "loc": v.loc}
port = oldlock.package.check_pad(k, v_dict) if oldlock else None

# Add pad to package
package.add_pad(k, v_dict)
if port and port.pins != [pin]:
raise ChipFlowError(
f"chipflow.toml conflicts with pins.lock: "
f"{k} had pin {port.pins}, now {[pin]}."
)

# Add pad to package
package.add_pad(k, v_dict)

# TODO: power pins
#
logger.debug(f'Pins in use: {package_type.sortpins(used_pins)}')

unallocated = package_type.pins - used_pins
Expand Down
216 changes: 209 additions & 7 deletions chipflow_lib/platforms/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,16 @@ def BidirPinSignature(width, **kwargs):
PinList = List[Pin]
Pins = Union[PinSet, PinList]

class PowerType(enum.Enum):
POWER = "power"
GROUND = "ground"

class JTAGWireName(enum.Enum):
TRST = "trst"
TCK = "tck"
TMS = "tms"
TDI = "tdi"
TDO = "tdo"

class _Side(enum.IntEnum):
N = 1
Expand Down Expand Up @@ -264,12 +274,62 @@ class _BasePackageDef(pydantic.BaseModel, abc.ABC):
@property
@abc.abstractmethod
def pins(self) -> PinSet:
"Returns the full set of pins for the package"
...

@abc.abstractmethod
def allocate(self, available: PinSet, width: int) -> PinList:
"""
Allocates pins as close to each other as possible from available pins.

Args:
available: set of available pins
width: number of pins to allocate

Returns:
An ordered list of pins
"""
...

@property
@abc.abstractmethod
def power(self) -> Dict[PowerType, Pin]:
"""
The set of power pins for the package
"""
...

@property
@abc.abstractmethod
def resets(self) -> Dict[int, Pin]:
"""
Numbered set of reset pins for the package
"""
...

@property
@abc.abstractmethod
def clocks(self) -> Dict[int, Pin]:
"""
Numbered set of clock pins for the package
"""
...

@property
@abc.abstractmethod
def jtag(self) -> Dict[JTAGWireName, Pin]:
"""
Map of JTAG pins for the package
"""
...

@property
@abc.abstractmethod
def heartbeat(self) -> Dict[int, Pin]:
"""
Numbered set of heartbeat pins for the package
"""

def to_string(pins: Pins):
return [''.join(map(str, t)) for t in pins]

Expand Down Expand Up @@ -306,8 +366,57 @@ def allocate(self, available: PinSet, width: int) -> PinList:
assert len(ret) == width
return ret

@property
def power(self) -> Dict[PowerType, Pin]:
"""
The set of power pins for the package
"""
# Default implementation - to be customized for specific package types
return {
PowerType.POWER: (_Side.N, 0), # North side, pin 0
PowerType.GROUND: (_Side.S, 0) # South side, pin 0
}

@property
def resets(self) -> Dict[int, Pin]:
"""
Numbered set of reset pins for the package
"""
# Default implementation with one reset pin
return {0: (_Side.N, 1)} # North side, pin 1

@property
def clocks(self) -> Dict[int, Pin]:
"""
Numbered set of clock pins for the package
"""
# Default implementation with one clock pin
return {0: (_Side.N, 2)} # North side, pin 2

class _QuadPackageDef(_BasePackageDef):
@property
def jtag(self) -> Dict[JTAGWireName, Pin]:
"""
Map of JTAG pins for the package
"""
# Default JTAG pin allocations
return {
JTAGWireName.TRST: (_Side.W, 0), # West side, pin 0
JTAGWireName.TCK: (_Side.W, 1), # West side, pin 1
JTAGWireName.TMS: (_Side.W, 2), # West side, pin 2
JTAGWireName.TDI: (_Side.W, 3), # West side, pin 3
JTAGWireName.TDO: (_Side.W, 4) # West side, pin 4
}

@property
def heartbeat(self) -> Dict[int, Pin]:
"""
Numbered set of heartbeat pins for the package
"""
# Default implementation with one heartbeat pin
return {0: (_Side.S, 1)} # South side, pin 1


class _PGAPackageDef(_BasePackageDef):
"""Definiton of a PGA package with `size` pins

This is package with `size` pins, numbered, with the assumption that adjacent pins
Expand All @@ -332,24 +441,79 @@ def pins(self) -> PinSet:

def allocate(self, available: Set[str], width: int) -> List[str]:
avail_n = sorted(available)
logger.debug(f"QuadPackageDef.allocate {width} from {len(avail_n)} remaining: {available}")
logger.debug(f"PGAPackageDef.allocate {width} from {len(avail_n)} remaining: {available}")
ret = _find_contiguous_sequence(self._ordered_pins, avail_n, width)
logger.debug(f"QuadPackageDef.returned {ret}")
logger.debug(f"PGAPackageDef.returned {ret}")
assert len(ret) == width
return ret

def sortpins(self, pins: Union[List[str], Set[str]]) -> List[str]:
return sorted(list(pins), key=int)

@property
def power(self) -> Dict[PowerType, Pin]:
"""
The set of power pins for the package
"""
# Default implementation for a PGA package
# Use pin numbers for the corners of the package
total_pins = self.width * 2 + self.height * 2
return {
PowerType.POWER: str(1), # First pin
PowerType.GROUND: str(total_pins // 2) # Middle pin
}

@property
def resets(self) -> Dict[int, Pin]:
"""
Numbered set of reset pins for the package
"""
# Default implementation with one reset pin
# Use a pin near the beginning of the package
return {0: str(2)} # Second pin

@property
def clocks(self) -> Dict[int, Pin]:
"""
Numbered set of clock pins for the package
"""
# Default implementation with one clock pin
# Use a pin near the beginning of the package
return {0: str(3)} # Third pin

@property
def jtag(self) -> Dict[JTAGWireName, Pin]:
"""
Map of JTAG pins for the package
"""
# Default JTAG pin allocations
# Use consecutive pins in the middle of the package
mid_pin = (self.width * 2 + self.height * 2) // 4
return {
JTAGWireName.TRST: str(mid_pin),
JTAGWireName.TCK: str(mid_pin + 1),
JTAGWireName.TMS: str(mid_pin + 2),
JTAGWireName.TDI: str(mid_pin + 3),
JTAGWireName.TDO: str(mid_pin + 4)
}

@property
def heartbeat(self) -> Dict[int, Pin]:
"""
Numbered set of heartbeat pins for the package
"""
# Default implementation with one heartbeat pin
# Use the last pin in the package
return {0: str(self.width * 2 + self.height * 2 - 1)}


# Add any new package types to both PACKAGE_DEFINITIONS and the PackageDef union
PACKAGE_DEFINITIONS = {
"pga144": _QuadPackageDef(name="pga144", width=36, height=36),
"pga144": _PGAPackageDef(name="pga144", width=36, height=36),
"cf20": _BareDiePackageDef(name="cf20", width=7, height=3)
}

PackageDef = Union[_QuadPackageDef, _BareDiePackageDef]

PackageDef = Union[_PGAPackageDef, _BareDiePackageDef, _BasePackageDef]

class Port(pydantic.BaseModel):
type: str
Expand All @@ -368,13 +532,15 @@ class Package(pydantic.BaseModel):
power: Dict[str, Port] = {}
clocks: Dict[str, Port] = {}
resets: Dict[str, Port] = {}
jtag: Dict[str, Port] = {}
heartbeat: Dict[str, Port] = {}

def check_pad(self, name: str, defn: dict):
match defn:
case {"type": "clock"}:
return self.clocks[name] if name in self.clocks else None
case {"type": "reset"}:
return self.resets[name] if name in self.clocks else None
return self.resets[name] if name in self.resets else None
case {"type": "power"}:
return self.power[name] if name in self.power else None
case {"type": "ground"}:
Expand All @@ -392,9 +558,45 @@ def add_pad(self, name: str, defn: dict):
self.power[name] = Port(type="power", pins=[loc], port_name=name)
case {"type": "ground", "loc": loc}:
self.power[name] = Port(type="ground", pins=[loc], port_name=name)
case {"type": "power", "name": name, "voltage": voltage}:
# Support for new power pin format
# First, get the default pin from the package type
power_pin = self.package_type.power[PowerType.POWER]
self.power[name] = Port(type="power", pins=[str(power_pin)], options={"voltage": voltage})
case {"type": "ground", "name": name}:
# Support for new ground pin format
ground_pin = self.package_type.power[PowerType.GROUND]
self.power[name] = Port(type="ground", pins=[str(ground_pin)])
case _:
pass

def initialize_from_package_type(self):
"""Initialize standard pins from package type definitions"""
# Set up clocks
for clock_id, pin in self.package_type.clocks.items():
name = f"clock_{clock_id}"
if name not in self.clocks:
self.clocks[name] = Port(type="clock", pins=[str(pin)], direction=io.Direction.Input)

# Set up resets
for reset_id, pin in self.package_type.resets.items():
name = f"reset_{reset_id}"
if name not in self.resets:
self.resets[name] = Port(type="reset", pins=[str(pin)], direction=io.Direction.Input)

# Set up heartbeat pins
for hb_id, pin in self.package_type.heartbeat.items():
name = f"heartbeat_{hb_id}"
if name not in self.heartbeat:
self.heartbeat[name] = Port(type="heartbeat", pins=[str(pin)], direction=io.Direction.Output)

# Set up JTAG pins
for jtag_name, pin in self.package_type.jtag.items():
name = f"jtag_{jtag_name.value}"
direction = io.Direction.Output if jtag_name == JTAGWireName.TDO else io.Direction.Input
if name not in self.jtag:
self.jtag[name] = Port(type="jtag", pins=[str(pin)], direction=direction)


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

Expand Down
Loading