Skip to content

Commit 5e367d4

Browse files
committed
loading data to a simulation model now supported
1 parent 7f7152e commit 5e367d4

File tree

6 files changed

+77
-90
lines changed

6 files changed

+77
-90
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
@@ -7,46 +7,60 @@
77
from amaranth.lib.wiring import Out
88

99
from .. import ChipFlowError
10-
from ._utils import InputIOSignature, OutputIOSignature, BidirIOSignature, IOModelOptions, _chipflow_schema_uri, amaranth_annotate
10+
from ._utils import InputIOSignature, OutputIOSignature, BidirIOSignature, IOModelOptions, _chipflow_schema_uri
11+
from ._annotate import amaranth_annotate
1112

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

1416
class SimInterface(TypedDict):
1517
uid: str
1618
parameters: dict
1719

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

26+
def _unpack_dict(d: dict) -> str:
27+
params = [ f"{k}={repr(v)}" for k,v in d.items()]
28+
return ', '.join(params)
29+
2030
"""
2131
Attributes:
2232
__chipflow_parameters__: list of tuples (name, value).
2333
It is expected that a model that takes parameters is implmemted as a template, with the parameters in the order
2434
given.
2535
"""
26-
def sim_annotate(base="com.chipflow.chipflow_lib"):
36+
def simulatable_interface(base="com.chipflow.chipflow_lib"):
2737
def decorate(klass):
2838
assert _VALID_UID(base)
2939
dec = amaranth_annotate(SimInterface, SIM_ANNOTATION_SCHEMA)
3040
klass = dec(klass)
3141

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

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

4862

49-
@sim_annotate()
63+
@simulatable_interface()
5064
class JTAGSignature(wiring.Signature):
5165
def __init__(self, **kwargs: Unpack[IOModelOptions]):
5266
super().__init__({
@@ -58,7 +72,7 @@ def __init__(self, **kwargs: Unpack[IOModelOptions]):
5872
})
5973

6074

61-
@sim_annotate()
75+
@simulatable_interface()
6276
class SPISignature(wiring.Signature):
6377
def __init__(self, **kwargs: Unpack[IOModelOptions]):
6478
super().__init__({
@@ -68,7 +82,7 @@ def __init__(self, **kwargs: Unpack[IOModelOptions]):
6882
"csn": Out(OutputIOSignature(1)),
6983
})
7084

71-
@sim_annotate()
85+
@simulatable_interface()
7286
class QSPIFlashSignature(wiring.Signature):
7387
def __init__(self, **kwargs: Unpack[IOModelOptions]):
7488
super().__init__({
@@ -77,51 +91,40 @@ def __init__(self, **kwargs: Unpack[IOModelOptions]):
7791
"d": Out(BidirIOSignature(4, individual_oe=True)),
7892
})
7993

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

88-
@sim_annotate()
102+
@simulatable_interface()
89103
class I2CSignature(wiring.Signature):
90104
def __init__(self, **kwargs: Unpack[IOModelOptions]):
91105
super().__init__({
92-
"scl": Out(BidirIOSignature(1)),
93-
"sda": Out(BidirIOSignature(1))
94-
})
106+
"scl": Out(BidirIOSignature(1)),
107+
"sda": Out(BidirIOSignature(1))
108+
})
109+
self._options = kwargs
110+
95111

96-
@sim_annotate()
112+
@simulatable_interface()
97113
class GPIOSignature(wiring.Signature):
98114

99115
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}")
102116
self._pin_count = pin_count
117+
self._options = kwargs
103118
kwargs['individual_oe'] = True
104119
super().__init__({
105120
"gpio": Out(BidirIOSignature(pin_count, **kwargs))
106121
})
107122

108123
def __chipflow_parameters__(self):
109-
print("called GPIOSignature.__chipflow_parameters__")
110124
return [('pin_count',self._pin_count)]
111125

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

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: 23 additions & 8 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,10 +165,11 @@ 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._sim_data = {}
172+
self._top_sim = {}
171173

172174
def add_file(self, filename, content):
173175
if not isinstance(content, (str, bytes)):
@@ -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"
@@ -216,14 +218,21 @@ def build(self, e):
216218
autoescape=select_autoescape()
217219
)
218220
template = env.get_template("main.cc.jinja")
221+
data_load = []
222+
for i,d in self._sim_data.items():
223+
args = [f"0x{d['offset']:X}U"]
224+
p = Path(d['file_name'])
225+
if not p.is_absolute():
226+
p = _ensure_chipflow_root() / p
227+
data_load.append({'model_name': i, 'file_name': p, 'args': args})
219228
with main.open("w") as main_file:
220229
print(template.render(
221230
includes = [hpp for b in self._builders if b.hpp_files for hpp in b.hpp_files ],
222231
initialisers = [exp for exp in self._top_sim.values()],
223232
interfaces = [exp for exp in self._top_sim.keys()],
224233
clocks = [cxxrtlmangle(f"io${clk}$i") for clk in self._clocks.keys()],
225234
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' ]}]
235+
data_load = data_load
227236
),
228237
file=main_file)
229238

@@ -239,13 +248,19 @@ def instantiate_ports(self, m: Module):
239248
logger.debug(f"Instantiating port {port_desc.port_name}: {port_desc}")
240249
invert = port_desc.invert if port_desc.invert else False
241250
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]
251+
if component.startswith('_'):
252+
continue
253+
annotations = pinlock.metadata[component]['interface']['members'][interface]['annotations']
254+
255+
if SIM_ANNOTATION_SCHEMA in annotations:
256+
sim_interface = annotations[SIM_ANNOTATION_SCHEMA]
245257
builder = find_builder(self._builders, sim_interface)
246258
if builder:
247259
self._top_sim[interface] = builder.instantiate_model(interface, sim_interface, interface_desc, self._ports)
248260

261+
if SIM_DATA_SCHEMA in annotations:
262+
self._sim_data[interface] = annotations[SIM_DATA_SCHEMA]
263+
249264
print(f"ports = {pformat(self._ports)}")
250265
for clock in pinlock.port_map.get_clocks():
251266
assert 'clock_domain' in clock.iomodel

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)

chipflow_lib/steps/sim.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,4 +108,5 @@ def build(self, *args):
108108
for k,v in VARIABLES.items():
109109
context[k] = v.format(**context)
110110
print(f"substituting:\n{pformat(context)}")
111-
DoitMain(ContextTaskLoader(DOIT_CONFIG, TASKS, context)).run(["build_sim"])
111+
if DoitMain(ContextTaskLoader(DOIT_CONFIG, TASKS, context)).run(["build_sim"]) !=0:
112+
raise ChipFlowError("Failed building simulator")

0 commit comments

Comments
 (0)