Skip to content

Commit 126bd42

Browse files
robtaylorclaude
andcommitted
Add warning about custom model API stability
Note that the custom simulation model interface is subject to change in future releases, while built-in models remain stable. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 28f67f4 commit 126bd42

File tree

3 files changed

+45
-42
lines changed

3 files changed

+45
-42
lines changed

chipflow_lib/packaging/base.py

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
import abc
1111
from collections import defaultdict
12-
from typing import TYPE_CHECKING, Dict, List, Set
12+
from typing import TYPE_CHECKING, Dict, Generic, List, Set, TypeVar
1313

1414
import pydantic
1515
from amaranth.lib import wiring, io
@@ -24,8 +24,11 @@
2424
if TYPE_CHECKING:
2525
from ..config_models import Config, Process
2626

27+
# Type variable for pin types (int for linear allocation, GAPin for grid arrays, etc.)
28+
PinType = TypeVar('PinType')
2729

28-
class BasePackageDef(pydantic.BaseModel, abc.ABC):
30+
31+
class BasePackageDef(pydantic.BaseModel, Generic[PinType], abc.ABC):
2932
"""
3033
Abstract base class for the definition of a package.
3134
@@ -45,6 +48,7 @@ def model_post_init(self, __context):
4548
"""Initialize internal tracking structures"""
4649
self._interfaces: Dict[str, dict] = {}
4750
self._components: Dict[str, wiring.Component] = {}
51+
self._ordered_pins = None # Subclasses should set this
4852
return super().model_post_init(__context)
4953

5054
def register_component(self, name: str, component: wiring.Component) -> None:
@@ -130,14 +134,17 @@ def _allocate_bringup(self, config: 'Config') -> Component:
130134

131135
return {'bringup_pins': d}
132136

133-
@abc.abstractmethod
134137
def allocate_pins(self, config: 'Config', process: 'Process', lockfile: LockFile | None) -> LockFile:
135138
"""
136139
Allocate package pins to the registered components.
137140
138141
Pins should be allocated in the most usable way for users
139142
of the packaged IC.
140143
144+
This default implementation uses _linear_allocate_components with
145+
self._allocate for the allocation strategy. Subclasses can override
146+
if they need completely different allocation logic.
147+
141148
Args:
142149
config: ChipFlow configuration
143150
process: Semiconductor process
@@ -149,6 +156,35 @@ def allocate_pins(self, config: 'Config', process: 'Process', lockfile: LockFile
149156
Raises:
150157
UnableToAllocate: If the ports cannot be allocated
151158
"""
159+
assert self._ordered_pins is not None, "Subclass must set self._ordered_pins in model_post_init"
160+
portmap = _linear_allocate_components(
161+
self._interfaces,
162+
lockfile,
163+
self._allocate,
164+
set(self._ordered_pins)
165+
)
166+
bringup_pins = self._allocate_bringup(config)
167+
portmap.ports['_core'] = bringup_pins
168+
package = self._get_package()
169+
return LockFile(package=package, process=process, metadata=self._interfaces, port_map=portmap)
170+
171+
@abc.abstractmethod
172+
def _allocate(self, available: Set[PinType], width: int) -> List[PinType]:
173+
"""
174+
Allocate pins from available set.
175+
176+
Subclasses must implement this to define their allocation strategy.
177+
178+
Args:
179+
available: Set of available pins (type depends on package)
180+
width: Number of pins needed
181+
182+
Returns:
183+
List of allocated pins
184+
185+
Raises:
186+
UnableToAllocate: If allocation fails
187+
"""
152188
...
153189

154190
@property
@@ -173,7 +209,7 @@ def _sortpins(self, pins: Pins) -> PinList:
173209
return sorted(list(pins))
174210

175211

176-
class LinearAllocPackageDef(BasePackageDef):
212+
class LinearAllocPackageDef(BasePackageDef[int]):
177213
"""
178214
Base class for package types with linear pin/pad allocation.
179215
@@ -186,24 +222,6 @@ class LinearAllocPackageDef(BasePackageDef):
186222
Not directly serializable - use concrete subclasses.
187223
"""
188224

189-
def __init__(self, **kwargs):
190-
self._ordered_pins = None
191-
super().__init__(**kwargs)
192-
193-
def allocate_pins(self, config: 'Config', process: 'Process', lockfile: LockFile | None) -> LockFile:
194-
"""Allocate pins linearly from the ordered pin list"""
195-
assert self._ordered_pins
196-
portmap = _linear_allocate_components(
197-
self._interfaces,
198-
lockfile,
199-
self._allocate,
200-
set(self._ordered_pins)
201-
)
202-
bringup_pins = self._allocate_bringup(config)
203-
portmap.ports['_core'] = bringup_pins
204-
package = self._get_package()
205-
return LockFile(package=package, process=process, metadata=self._interfaces, port_map=portmap)
206-
207225
def _allocate(self, available: Set[int], width: int) -> List[int]:
208226
"""
209227
Allocate pins from available set.

chipflow_lib/packaging/grid_array.py

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,10 @@
99
import logging
1010
from enum import StrEnum, auto
1111
from math import ceil, floor
12-
from typing import Dict, List, Literal, NamedTuple, Optional, Set, Tuple, TYPE_CHECKING
12+
from typing import Dict, List, Literal, NamedTuple, Optional, Set, Tuple
1313

1414
from .base import BasePackageDef
1515
from .pins import PowerPins, JTAGPins, BringupPins
16-
from .lockfile import LockFile
17-
from .allocation import _linear_allocate_components
18-
19-
if TYPE_CHECKING:
20-
from ..config_models import Config, Process
2116

2217
logger = logging.getLogger(__name__)
2318

@@ -41,7 +36,7 @@ class GALayout(StrEnum):
4136
ISLAND = auto() # Perimeter + center island
4237

4338

44-
class GAPackageDef(BasePackageDef):
39+
class GAPackageDef(BasePackageDef[GAPin]):
4540
"""
4641
Definition of a grid array package.
4742
@@ -187,19 +182,6 @@ def sort_by_quadrant(pins: Set[GAPin]) -> List[GAPin]:
187182

188183
return super().model_post_init(__context)
189184

190-
def allocate_pins(self, config: 'Config', process: 'Process', lockfile: LockFile | None) -> LockFile:
191-
"""Allocate pins from the grid array"""
192-
portmap = _linear_allocate_components(
193-
self._interfaces,
194-
lockfile,
195-
self._allocate,
196-
set(self._ordered_pins)
197-
)
198-
bringup_pins = self._allocate_bringup(config)
199-
portmap.ports['_core'] = bringup_pins
200-
package = self._get_package()
201-
return LockFile(package=package, process=process, metadata=self._interfaces, port_map=portmap)
202-
203185
def _allocate(self, available: Set[GAPin], width: int) -> List[GAPin]:
204186
"""Allocate pins from available grid array pins"""
205187
from .allocation import _find_contiguous_sequence

docs/simulation-guide.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,9 @@ Adding Custom Models
372372

373373
ChipFlow's built-in simulation models cover common peripherals (UART, SPI, I2C, GPIO, QSPI Flash). For custom peripherals, you'll need to write C++ models that interact with the CXXRTL-generated design.
374374

375+
.. warning::
376+
The custom simulation model interface is subject to change. Model APIs may be updated in future ChipFlow releases. Built-in models (UART, SPI, etc.) are stable, but custom model registration and integration mechanisms may evolve.
377+
375378
**Learning Resources:**
376379

377380
1. **Study existing models**: The best way to learn is to examine ChipFlow's built-in implementations:

0 commit comments

Comments
 (0)