Skip to content

Commit 6e1b13b

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 611050c commit 6e1b13b

File tree

6 files changed

+89
-92
lines changed

6 files changed

+89
-92
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 & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8,46 +8,60 @@
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)
3141
klass = dec(klass)
3242

3343
def new_init(self,*args, **kwargs):
34-
print("called new_init")
3544
original_init(self, *args, **kwargs)
3645
self.__chipflow_annotation__ = {
3746
"uid": klass.__chipflow_uid__,
3847
"parameters": self.__chipflow_parameters__(),
3948
}
4049

50+
def repr(self) -> str:
51+
return f"{klass.__name__}({_unpack_dict(self.__chipflow_parameters__())}, {_unpack_dict(self._options)})"
52+
4153
original_init = klass.__init__
4254
klass.__init__ = new_init
4355
klass.__chipflow_uid__ = f"{base}.{klass.__name__}"
4456
if not hasattr(klass, '__chipflow_parameters__'):
4557
klass.__chipflow_parameters__ = lambda self: []
58+
if not klass.__repr__:
59+
klass.__repr__ = repr
4660
return klass
4761
return decorate
4862

4963

50-
@sim_annotate()
64+
@simulatable_interface()
5165
class JTAGSignature(wiring.Signature):
5266
def __init__(self, **kwargs: Unpack[IOModelOptions]):
5367
super().__init__({
@@ -59,7 +73,7 @@ def __init__(self, **kwargs: Unpack[IOModelOptions]):
5973
})
6074

6175

62-
@sim_annotate()
76+
@simulatable_interface()
6377
class SPISignature(wiring.Signature):
6478
def __init__(self, **kwargs: Unpack[IOModelOptions]):
6579
super().__init__({
@@ -69,7 +83,7 @@ def __init__(self, **kwargs: Unpack[IOModelOptions]):
6983
"csn": Out(OutputIOSignature(1)),
7084
})
7185

72-
@sim_annotate()
86+
@simulatable_interface()
7387
class QSPIFlashSignature(wiring.Signature):
7488
def __init__(self, **kwargs: Unpack[IOModelOptions]):
7589
super().__init__({
@@ -78,51 +92,40 @@ def __init__(self, **kwargs: Unpack[IOModelOptions]):
7892
"d": Out(BidirIOSignature(4, individual_oe=True)),
7993
})
8094

81-
@sim_annotate()
95+
@simulatable_interface()
8296
class UARTSignature(wiring.Signature):
8397
def __init__(self, **kwargs: Unpack[IOModelOptions]):
8498
super().__init__({
8599
"tx": Out(OutputIOSignature(1)),
86100
"rx": Out(InputIOSignature(1)),
87101
})
88102

89-
@sim_annotate()
103+
@simulatable_interface()
90104
class I2CSignature(wiring.Signature):
91105
def __init__(self, **kwargs: Unpack[IOModelOptions]):
92106
super().__init__({
93-
"scl": Out(BidirIOSignature(1)),
94-
"sda": Out(BidirIOSignature(1))
95-
})
107+
"scl": Out(BidirIOSignature(1)),
108+
"sda": Out(BidirIOSignature(1))
109+
})
110+
self._options = kwargs
111+
96112

97-
@sim_annotate()
113+
@simulatable_interface()
98114
class GPIOSignature(wiring.Signature):
99115

100116
def __init__(self, pin_count=1, **kwargs: Unpack[IOModelOptions]):
101-
if pin_count > 32:
102-
raise ValueError(f"Pin pin_count must be lesser than or equal to 32, not {pin_count}")
103117
self._pin_count = pin_count
118+
self._options = kwargs
104119
kwargs['individual_oe'] = True
105120
super().__init__({
106121
"gpio": Out(BidirIOSignature(pin_count, **kwargs))
107122
})
108123

109124
def __chipflow_parameters__(self):
110-
print("called GPIOSignature.__chipflow_parameters__")
111125
return [('pin_count',self._pin_count)]
112126

113-
def __repr__(self) -> str:
114-
return f"GPIOSignature(pin_count={self._pin_count}, {dict(self.members.items())})"
115-
116-
117-
class SimulationCanLoadData:
118-
"""
119-
Inherit from this in your object's Signature if you want a simulation model
120-
to be able to load data from your object
121-
"""
122-
@classmethod
123-
def __init_submodule__(cls, /, *args, **kwargs):
124-
if wiring.Signature not in cls.mro():
125-
raise ChipFlowError("SimulationCanLoadData can only be used with ``wiring.Signature`` classes")
126-
original_annotations = getattr(cls, 'annotations')
127-
#def annotations(self, obj, /):
128-
#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]):
@@ -640,6 +603,7 @@ def register_component(self, name: str, component: wiring.Component) -> None:
640603
component: Amaranth `wiring.Component` to allocate
641604
642605
"""
606+
print(f"registering {component}")
643607
self._components[name] = component
644608
self._interfaces[name] = component.metadata.as_json()
645609

chipflow_lib/platforms/sim.py

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from .. import ChipFlowError, _ensure_chipflow_root
2424
from ._signatures import (
2525
I2CSignature, GPIOSignature, UARTSignature, SPISignature, QSPIFlashSignature,
26-
SIM_ANNOTATION_SCHEMA, SimInterface
26+
SIM_ANNOTATION_SCHEMA, SIM_DATA_SCHEMA, SimInterface
2727
)
2828
from ._utils import load_pinlock, Interface
2929

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

146+
146147
_COMMON_BUILDER = BasicCxxBuilder(
147148
models=[
148149
SimModel('spi_model', SPISignature),
@@ -164,17 +165,18 @@ def __init__(self, config):
164165
self.sim_boxes = dict()
165166
self._ports: Dict[str, io.SimulationPort] = {}
166167
self._config = config
167-
self._builders: List[BasicCxxBuilder] = [ _COMMON_BUILDER ]
168-
self._top_sim = {}
169168
self._clocks = {}
170169
self._resets = {}
170+
self._builders: List[BasicCxxBuilder] = [ _COMMON_BUILDER ]
171+
self._top_sim = {}
172+
self._sim_data = {}
171173

172174
def add_file(self, filename, content):
173175
if not isinstance(content, (str, bytes)):
174176
content = content.read()
175177
self.extra_files[filename] = content
176178

177-
def build(self, e):
179+
def build(self, e, top):
178180
Path(self.build_dir).mkdir(parents=True, exist_ok=True)
179181

180182
ports = []
@@ -186,7 +188,7 @@ def build(self, e):
186188
if port.direction is io.Direction.Bidir:
187189
ports.append((f"io${port_name}$oe", port.oe, PortDirection.Output))
188190

189-
print("elaborating design")
191+
print("Generating RTLIL from design")
190192
output = rtlil.convert(e, name="sim_top", ports=ports, platform=self)
191193

192194
top_rtlil = Path(self.build_dir) / "sim_soc.il"
@@ -211,19 +213,39 @@ def build(self, e):
211213
print("write_cxxrtl -header sim_soc.cc", file=yosys_file)
212214
main = Path(self.build_dir) / "main.cc"
213215

216+
metadata = {}
217+
for key in top.keys():
218+
metadata[key] = getattr(e.submodules, key).metadata.as_json()
219+
for component, iface in metadata.items():
220+
for interface, interface_desc in iface['interface']['members'].items():
221+
annotations = interface_desc['annotations']
222+
223+
if SIM_DATA_SCHEMA in annotations:
224+
self._sim_data[interface] = annotations[SIM_DATA_SCHEMA]
225+
226+
data_load = []
227+
for i,d in self._sim_data.items():
228+
args = [f"0x{d['offset']:X}U"]
229+
p = Path(d['file_name'])
230+
if not p.is_absolute():
231+
p = _ensure_chipflow_root() / p
232+
data_load.append({'model_name': i, 'file_name': p, 'args': args})
233+
234+
214235
env = Environment(
215236
loader=PackageLoader("chipflow_lib", "common/sim"),
216237
autoescape=select_autoescape()
217238
)
218239
template = env.get_template("main.cc.jinja")
240+
219241
with main.open("w") as main_file:
220242
print(template.render(
221243
includes = [hpp for b in self._builders if b.hpp_files for hpp in b.hpp_files ],
222244
initialisers = [exp for exp in self._top_sim.values()],
223245
interfaces = [exp for exp in self._top_sim.keys()],
224246
clocks = [cxxrtlmangle(f"io${clk}$i") for clk in self._clocks.keys()],
225247
resets = [cxxrtlmangle(f"io${rst}$i") for rst in self._resets.keys()],
226-
data_load = [{'model_name': 'flash', 'file_name':_ensure_chipflow_root() / 'build'/ 'software'/'software.bin', 'args':[ '0x00100000U' ]}]
248+
data_load = data_load
227249
),
228250
file=main_file)
229251

@@ -239,9 +261,12 @@ def instantiate_ports(self, m: Module):
239261
logger.debug(f"Instantiating port {port_desc.port_name}: {port_desc}")
240262
invert = port_desc.invert if port_desc.invert else False
241263
self._ports[port_desc.port_name] = io.SimulationPort(port_desc.direction, port_desc.width, invert=invert, name=port_desc.port_name)
242-
if not component.startswith('_') \
243-
and pinlock.metadata[component]['interface']['members'][interface]['annotations']:
244-
sim_interface = pinlock.metadata[component]['interface']['members'][interface]['annotations'][SIM_ANNOTATION_SCHEMA]
264+
if component.startswith('_'):
265+
continue
266+
annotations = pinlock.metadata[component]['interface']['members'][interface]['annotations']
267+
268+
if SIM_ANNOTATION_SCHEMA in annotations:
269+
sim_interface = annotations[SIM_ANNOTATION_SCHEMA]
245270
builder = find_builder(self._builders, sim_interface)
246271
if builder:
247272
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)