Skip to content

Commit daca799

Browse files
committed
Introduces the ability to automatically attach simulation models to
ports. This allows port signatures to be annotated as to the kind and behaviour of that port (e.g. its SPI, its I2C..) to allow the automatic assembling of a simulation model for the IC design.
1 parent 988ac5c commit daca799

File tree

6 files changed

+89
-89
lines changed

6 files changed

+89
-89
lines changed

chipflow_lib/platforms/__init__.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,17 @@
1414
)
1515
from ._packages import PACKAGE_DEFINITIONS
1616
from ._sky130 import Sky130DriveMode
17-
from ._signatures import JTAGSignature, SPISignature, I2CSignature, UARTSignature, GPIOSignature, QSPIFlashSignature
17+
from ._signatures import (
18+
JTAGSignature, SPISignature, I2CSignature, UARTSignature, GPIOSignature, QSPIFlashSignature,
19+
attach_simulation_data
20+
)
1821

1922
__all__ = ['IO_ANNOTATION_SCHEMA', 'IOSignature',
2023
'IOModel', 'IOModelOptions', 'IOTripPoint',
2124
'OutputIOSignature', 'InputIOSignature', 'BidirIOSignature',
2225
'SiliconPlatformPort', 'SiliconPlatform',
2326
'SimPlatform',
24-
'JTAGSignature', 'SPISignature', 'I2CSignature', 'UARTSignature', 'GPIOSignature',
27+
'JTAGSignature', 'SPISignature', 'I2CSignature', 'UARTSignature', 'GPIOSignature', 'QSPIFlashSignature',
28+
'attach_simulation_data',
2529
'Sky130DriveMode',
2630
'PACKAGE_DEFINITIONS']

chipflow_lib/platforms/_signatures.py

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,33 @@
88
from amaranth.lib.wiring import Out
99

1010
from .. import ChipFlowError
11-
from ._utils import InputIOSignature, OutputIOSignature, BidirIOSignature, IOModelOptions, _chipflow_schema_uri, amaranth_annotate
11+
from ._utils import InputIOSignature, OutputIOSignature, BidirIOSignature, IOModelOptions, _chipflow_schema_uri
12+
from ._annotate import amaranth_annotate
1213

13-
SIM_ANNOTATION_SCHEMA = str(_chipflow_schema_uri("sim-interface", 0))
14+
SIM_ANNOTATION_SCHEMA = str(_chipflow_schema_uri("simulatable-interface", 0))
15+
SIM_DATA_SCHEMA = str(_chipflow_schema_uri("simulatable-data", 0))
1416

1517
class SimInterface(TypedDict):
1618
uid: str
1719
parameters: List[Tuple[str, Any]]
1820

21+
class SimData(TypedDict):
22+
file_name: str
23+
offset: int
24+
1925
_VALID_UID = re.compile('[a-zA-Z_.]').search
2026

27+
def _unpack_dict(d: dict) -> str:
28+
params = [ f"{k}={repr(v)}" for k,v in d.items()]
29+
return ', '.join(params)
30+
2131
"""
2232
Attributes:
2333
__chipflow_parameters__: list of tuples (name, value).
2434
It is expected that a model that takes parameters is implmemted as a template, with the parameters in the order
2535
given.
2636
"""
27-
def sim_annotate(base="com.chipflow.chipflow_lib"):
37+
def simulatable_interface(base="com.chipflow.chipflow_lib"):
2838
def decorate(klass):
2939
assert _VALID_UID(base)
3040
dec = amaranth_annotate(SimInterface, SIM_ANNOTATION_SCHEMA)
@@ -37,16 +47,21 @@ def new_init(self,*args, **kwargs):
3747
"parameters": self.__chipflow_parameters__(),
3848
}
3949

50+
def repr(self) -> str:
51+
return f"{klass.__name__}({_unpack_dict(self.__chipflow_parameters__())}, {_unpack_dict(self._options)})"
52+
4053
original_init = klass.__init__
4154
klass.__init__ = new_init
4255
klass.__chipflow_uid__ = f"{base}.{klass.__name__}"
4356
if not hasattr(klass, '__chipflow_parameters__'):
4457
klass.__chipflow_parameters__ = lambda self: []
58+
if not klass.__repr__:
59+
klass.__repr__ = repr
4560
return klass
4661
return decorate
4762

4863

49-
@sim_annotate()
64+
@simulatable_interface()
5065
class JTAGSignature(wiring.Signature):
5166
def __init__(self, **kwargs: Unpack[IOModelOptions]):
5267
super().__init__({
@@ -58,7 +73,7 @@ def __init__(self, **kwargs: Unpack[IOModelOptions]):
5873
})
5974

6075

61-
@sim_annotate()
76+
@simulatable_interface()
6277
class SPISignature(wiring.Signature):
6378
def __init__(self, **kwargs: Unpack[IOModelOptions]):
6479
super().__init__({
@@ -68,7 +83,7 @@ def __init__(self, **kwargs: Unpack[IOModelOptions]):
6883
"csn": Out(OutputIOSignature(1)),
6984
})
7085

71-
@sim_annotate()
86+
@simulatable_interface()
7287
class QSPIFlashSignature(wiring.Signature):
7388
def __init__(self, **kwargs: Unpack[IOModelOptions]):
7489
super().__init__({
@@ -77,29 +92,30 @@ def __init__(self, **kwargs: Unpack[IOModelOptions]):
7792
"d": Out(BidirIOSignature(4, individual_oe=True)),
7893
})
7994

80-
@sim_annotate()
95+
@simulatable_interface()
8196
class UARTSignature(wiring.Signature):
8297
def __init__(self, **kwargs: Unpack[IOModelOptions]):
8398
super().__init__({
8499
"tx": Out(OutputIOSignature(1)),
85100
"rx": Out(InputIOSignature(1)),
86101
})
87102

88-
@sim_annotate()
103+
@simulatable_interface()
89104
class I2CSignature(wiring.Signature):
90105
def __init__(self, **kwargs: Unpack[IOModelOptions]):
91106
super().__init__({
92-
"scl": Out(BidirIOSignature(1)),
93-
"sda": Out(BidirIOSignature(1))
94-
})
107+
"scl": Out(BidirIOSignature(1)),
108+
"sda": Out(BidirIOSignature(1))
109+
})
110+
self._options = kwargs
111+
95112

96-
@sim_annotate()
113+
@simulatable_interface()
97114
class GPIOSignature(wiring.Signature):
98115

99116
def __init__(self, pin_count=1, **kwargs: Unpack[IOModelOptions]):
100-
if pin_count > 32:
101-
raise ValueError(f"Pin pin_count must be lesser than or equal to 32, not {pin_count}")
102117
self._pin_count = pin_count
118+
self._options = kwargs
103119
kwargs['individual_oe'] = True
104120
super().__init__({
105121
"gpio": Out(BidirIOSignature(pin_count, **kwargs))
@@ -108,19 +124,8 @@ def __init__(self, pin_count=1, **kwargs: Unpack[IOModelOptions]):
108124
def __chipflow_parameters__(self):
109125
return [('pin_count',self._pin_count)]
110126

111-
def __repr__(self) -> str:
112-
return f"GPIOSignature(pin_count={self._pin_count}, {dict(self.members.items())})"
113-
114-
115-
class SimulationCanLoadData:
116-
"""
117-
Inherit from this in your object's Signature if you want a simulation model
118-
to be able to load data from your object
119-
"""
120-
@classmethod
121-
def __init_submodule__(cls, /, *args, **kwargs):
122-
if wiring.Signature not in cls.mro():
123-
raise ChipFlowError("SimulationCanLoadData can only be used with ``wiring.Signature`` classes")
124-
original_annotations = getattr(cls, 'annotations')
125-
#def annotations(self, obj, /):
126-
#cls.annotate
127+
128+
def attach_simulation_data(c: wiring.Component, **kwargs: Unpack[SimData]):
129+
setattr(c.signature, '__chipflow_simulation_data__', kwargs)
130+
amaranth_annotate(SimData, SIM_DATA_SCHEMA, '__chipflow_simulation_data__', decorate_object=True)(c.signature)
131+

chipflow_lib/platforms/_utils.py

Lines changed: 10 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
from .. import ChipFlowError, _ensure_chipflow_root, _get_cls_by_reference
3333
from .._appresponse import AppResponseModel, OmitIfNone
34+
from ._annotate import amaranth_annotate
3435
from ._sky130 import Sky130DriveMode
3536

3637
if TYPE_CHECKING:
@@ -83,7 +84,6 @@ class IOTripPoint(StrEnum):
8384
IO_ANNOTATION_SCHEMA = str(_chipflow_schema_uri("pin-annotation", 0))
8485

8586

86-
@pydantic.with_config(ConfigDict(arbitrary_types_allowed=True)) # type: ignore[reportCallIssue]
8787
class IOModelOptions(TypedDict):
8888
"""
8989
Options for an IO pad/pin.
@@ -130,45 +130,8 @@ class IOModel(IOModelOptions):
130130
width: int
131131
direction: Annotated[io.Direction, PlainSerializer(lambda x: x.value)]
132132

133-
def amaranth_annotate(modeltype: Type[TypedDict], schema_id: str):
134-
PydanticModel = TypeAdapter(modeltype)
135-
136-
def annotation_schema():
137-
class Model(pydantic.BaseModel):
138-
data_td: modeltype
139-
140-
schema = PydanticModel.json_schema()
141-
schema['$schema'] = "https://json-schema.org/draft/2020-12/schema"
142-
schema['$id'] = schema_id
143-
return schema
144-
145-
class Annotation(meta.Annotation):
146-
"Generated annotation class"
147-
schema = annotation_schema()
148-
149-
def __init__(self, model: modeltype):
150-
self.__chipflow_annotation__ = model
151-
152-
@property
153-
def origin(self): # type: ignore
154-
return self.__chipflow_annotation__
155-
156-
def as_json(self): # type: ignore
157-
return PydanticModel.dump_python(self.__chipflow_annotation__)
158-
159-
def decorator(klass):
160-
def annotations(self, *args): # type: ignore
161-
annotations = super(klass, self).annotations(*args) # type: ignore
162-
annotation = Annotation(self.__chipflow_annotation__)
163-
return annotations + (annotation,) # type: ignore
164-
165-
166-
klass.annotations = annotations
167-
return klass
168-
return decorator
169-
170133

171-
@amaranth_annotate(IOModel, IO_ANNOTATION_SCHEMA)
134+
@amaranth_annotate(IOModel, IO_ANNOTATION_SCHEMA, '_model')
172135
class IOSignature(wiring.Signature):
173136
"""An :py:obj:`Amaranth Signature <amaranth.lib.wiring.Signature>` used to decorate wires that would usually be brought out onto a port on the package.
174137
This class is generally not directly used. Instead, you would typically utilize the more specific
@@ -212,34 +175,34 @@ def __init__(self, **kwargs: Unpack[IOModel]):
212175
if 'clock_domain' not in model:
213176
model['clock_domain'] = 'sync'
214177

215-
self.__chipflow_annotation__ = model
178+
self._model = model
216179
super().__init__(sig)
217180

218181
@property
219182
def direction(self) -> io.Direction:
220183
"The direction of the IO port"
221-
return self.__chipflow_annotation__['direction']
184+
return self._model['direction']
222185

223186
@property
224187
def width(self) -> int:
225188
"The width of the IO port, in wires"
226-
return self.__chipflow_annotation__['width']
189+
return self._model['width']
227190

228191
@property
229192
def invert(self) -> Iterable[bool]:
230193
"A tuple as wide as the IO port, with a bool for the polarity inversion for each wire"
231-
assert type(self.__chipflow_annotation__['invert']) is tuple
232-
return self.__chipflow_annotation__['invert']
194+
assert type(self._model['invert']) is tuple
195+
return self._model['invert']
233196

234197
@property
235198
def options(self) -> IOModelOptions:
236199
"""
237200
Options set on the io port at construction
238201
"""
239-
return self.__chipflow_annotation__
202+
return self._model
240203

241204
def __repr__(self):
242-
return f"IOSignature({','.join('{0}={1!r}'.format(k,v) for k,v in self.__chipflow_annotation__.items())})"
205+
return f"IOSignature({','.join('{0}={1!r}'.format(k,v) for k,v in self._model.items())})"
243206

244207

245208
def OutputIOSignature(width: int, **kwargs: Unpack[IOModelOptions]):
@@ -630,6 +593,7 @@ def register_component(self, name: str, component: wiring.Component) -> None:
630593
component: Amaranth `wiring.Component` to allocate
631594
632595
"""
596+
print(f"registering {component}")
633597
self._components[name] = component
634598
self._interfaces[name] = component.metadata.as_json()
635599

chipflow_lib/platforms/sim.py

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from .. import ChipFlowError, _ensure_chipflow_root
2121
from ._signatures import (
2222
I2CSignature, GPIOSignature, UARTSignature, SPISignature, QSPIFlashSignature,
23-
SIM_ANNOTATION_SCHEMA, SimInterface
23+
SIM_ANNOTATION_SCHEMA, SIM_DATA_SCHEMA, SimInterface
2424
)
2525
from ._utils import load_pinlock, Interface
2626

@@ -139,6 +139,7 @@ def find_builder(builders: List[BasicCxxBuilder], sim_interface: SimInterface):
139139
logger.warn(f"Unable to find builder for '{uid}'")
140140
return None
141141

142+
142143
_COMMON_BUILDER = BasicCxxBuilder(
143144
models=[
144145
SimModel('spi_model', SPISignature),
@@ -160,17 +161,18 @@ def __init__(self, config):
160161
self.sim_boxes = dict()
161162
self._ports: Dict[str, io.SimulationPort] = {}
162163
self._config = config
163-
self._builders: List[BasicCxxBuilder] = [ _COMMON_BUILDER ]
164-
self._top_sim = {}
165164
self._clocks = {}
166165
self._resets = {}
166+
self._builders: List[BasicCxxBuilder] = [ _COMMON_BUILDER ]
167+
self._top_sim = {}
168+
self._sim_data = {}
167169

168170
def add_file(self, filename, content):
169171
if not isinstance(content, (str, bytes)):
170172
content = content.read()
171173
self.extra_files[filename] = content
172174

173-
def build(self, e):
175+
def build(self, e, top):
174176
Path(self.build_dir).mkdir(parents=True, exist_ok=True)
175177

176178
ports = []
@@ -182,6 +184,7 @@ def build(self, e):
182184
if port.direction is io.Direction.Bidir:
183185
ports.append((f"io${port_name}$oe", port.oe, PortDirection.Output))
184186

187+
print("Generating RTLIL from design")
185188
output = rtlil.convert(e, name="sim_top", ports=ports, platform=self)
186189

187190
top_rtlil = Path(self.build_dir) / "sim_soc.il"
@@ -206,19 +209,39 @@ def build(self, e):
206209
print("write_cxxrtl -header sim_soc.cc", file=yosys_file)
207210
main = Path(self.build_dir) / "main.cc"
208211

212+
metadata = {}
213+
for key in top.keys():
214+
metadata[key] = getattr(e.submodules, key).metadata.as_json()
215+
for component, iface in metadata.items():
216+
for interface, interface_desc in iface['interface']['members'].items():
217+
annotations = interface_desc['annotations']
218+
219+
if SIM_DATA_SCHEMA in annotations:
220+
self._sim_data[interface] = annotations[SIM_DATA_SCHEMA]
221+
222+
data_load = []
223+
for i,d in self._sim_data.items():
224+
args = [f"0x{d['offset']:X}U"]
225+
p = Path(d['file_name'])
226+
if not p.is_absolute():
227+
p = _ensure_chipflow_root() / p
228+
data_load.append({'model_name': i, 'file_name': p, 'args': args})
229+
230+
209231
env = Environment(
210232
loader=PackageLoader("chipflow_lib", "common/sim"),
211233
autoescape=select_autoescape()
212234
)
213235
template = env.get_template("main.cc.jinja")
236+
214237
with main.open("w") as main_file:
215238
print(template.render(
216239
includes = [hpp for b in self._builders if b.hpp_files for hpp in b.hpp_files ],
217240
initialisers = [exp for exp in self._top_sim.values()],
218241
interfaces = [exp for exp in self._top_sim.keys()],
219242
clocks = [cxxrtlmangle(f"io${clk}$i") for clk in self._clocks.keys()],
220243
resets = [cxxrtlmangle(f"io${rst}$i") for rst in self._resets.keys()],
221-
data_load = [{'model_name': 'flash', 'file_name':_ensure_chipflow_root() / 'build'/ 'software'/'software.bin', 'args':[ '0x00100000U' ]}]
244+
data_load = data_load
222245
),
223246
file=main_file)
224247

@@ -234,9 +257,12 @@ def instantiate_ports(self, m: Module):
234257
logger.debug(f"Instantiating port {port_desc.port_name}: {port_desc}")
235258
invert = port_desc.invert if port_desc.invert else False
236259
self._ports[port_desc.port_name] = io.SimulationPort(port_desc.direction, port_desc.width, invert=invert, name=port_desc.port_name)
237-
if not component.startswith('_') \
238-
and pinlock.metadata[component]['interface']['members'][interface]['annotations']:
239-
sim_interface = pinlock.metadata[component]['interface']['members'][interface]['annotations'][SIM_ANNOTATION_SCHEMA]
260+
if component.startswith('_'):
261+
continue
262+
annotations = pinlock.metadata[component]['interface']['members'][interface]['annotations']
263+
264+
if SIM_ANNOTATION_SCHEMA in annotations:
265+
sim_interface = annotations[SIM_ANNOTATION_SCHEMA]
240266
builder = find_builder(self._builders, sim_interface)
241267
if builder:
242268
self._top_sim[interface] = builder.instantiate_model(interface, sim_interface, interface_desc, self._ports)

chipflow_lib/software/soft_gen.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ def __init__(self, *, rom_start, rom_size, ram_start, ram_size):
1111
self.defines = []
1212
self.periphs = []
1313
self.extra_init = []
14-
print("initialed SoftwareGenerator")
14+
print("initialised SoftwareGenerator")
1515

1616
def generate(self, out_dir):
1717
Path(out_dir).mkdir(parents=True, exist_ok=True)

0 commit comments

Comments
 (0)