Skip to content

Commit 8f5cc26

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 bf8126e commit 8f5cc26

File tree

11 files changed

+526
-82
lines changed

11 files changed

+526
-82
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#undef NDEBUG
2+
3+
#include <cxxrtl/cxxrtl.h>
4+
#include <cxxrtl/cxxrtl_server.h>
5+
#include "sim_soc.h"
6+
{% for include in includes %}
7+
#include "{{include}}"
8+
{% endfor %}
9+
10+
#include <fstream>
11+
#include <filesystem>
12+
13+
using namespace cxxrtl::time_literals;
14+
using namespace cxxrtl_design;
15+
16+
int main(int argc, char **argv) {
17+
p_sim__top top;
18+
19+
{% for initialiser in initialisers %}
20+
{{initialiser}};
21+
{% endfor %}
22+
23+
cxxrtl::agent agent(cxxrtl::spool("spool.bin"), top);
24+
if (getenv("DEBUG")) // can also be done when a condition is violated, etc
25+
std::cerr << "Waiting for debugger on " << agent.start_debugging() << std::endl;
26+
27+
open_event_log(BUILD_DIR "/sim/events.json");
28+
open_input_commands(PROJECT_ROOT "/design/tests/input.json");
29+
30+
unsigned timestamp = 0;
31+
auto tick = [&]() {
32+
{% for interface in interfaces %}
33+
{{interface}}.step(timestamp);
34+
{% endfor %}
35+
36+
// FIXME: Currently we tick all clocks together, this need fixing..
37+
{% for clock in clocks %}
38+
top.{{clock}}.set(false);
39+
{% endfor %}
40+
agent.step();
41+
agent.advance(1_us);
42+
++timestamp;
43+
44+
{% for clock in clocks %}
45+
top.{{clock}}.set(true);
46+
{% endfor %}
47+
agent.step();
48+
agent.advance(1_us);
49+
++timestamp;
50+
51+
// if (timestamp == 10)
52+
// agent.breakpoint(CXXRTL_LOCATION);
53+
};
54+
55+
{% for data in data_load %}
56+
{{data.model_name}}.load_data("{{data.file_name}}", {{data.args | join(', ')}});
57+
{% endfor %}
58+
59+
agent.step();
60+
agent.advance(1_us);
61+
62+
{% for reset in resets %}
63+
top.{{reset}}.set(false);
64+
{% endfor %}
65+
66+
tick();
67+
68+
{% for reset in resets %}
69+
top.{{reset}}.set(true);
70+
{% endfor %}
71+
72+
for (int i = 0; i < 3000000; i++)
73+
tick();
74+
75+
close_event_log();
76+
return 0;
77+
}

chipflow_lib/common/sim/models.cc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -284,16 +284,16 @@ void gpio_model::step(unsigned timestamp) {
284284
if (action.event == "set") {
285285
auto bin = std::string(action.payload);
286286
input_data = 0;
287-
for (unsigned i = 0; i < width; i++) {
288-
if (bin.at((width - 1) - i) == '1')
287+
for (unsigned i = 0; i < pin_count; i++) {
288+
if (bin.at((pin_count - 1) - i) == '1')
289289
input_data |= (1U << i);
290290
}
291291
}
292292
}
293293

294294
if (o_value != s.o_last || oe_value != s.oe_last) {
295295
std::string formatted_value;
296-
for (int i = width - 1; i >= 0; i--) {
296+
for (int i = pin_count - 1; i >= 0; i--) {
297297
if (oe_value & (1U << unsigned(i)))
298298
formatted_value += (o_value & (1U << unsigned(i))) ? '1' : '0';
299299
else

chipflow_lib/common/sim/models.h

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -83,17 +83,21 @@ struct uart_model {
8383
};
8484

8585
struct gpio_model {
86-
static constexpr unsigned width = 8;
8786
std::string name;
88-
gpio_model(const std::string &name, const value<width> &o, const value<width> &oe, value<width> &i) : name(name), o(o), oe(oe), i(i) {};
87+
struct parameters {
88+
unsigned pin_count;
89+
};
90+
parameters parameters;
91+
92+
gpio_model(const std::string &name, parameters, const value<paramters.pin_count> &o, const value<parameters.pin_count> &oe, value<parameters.pin_count> &i) : name(name), parameters(parameters), o(o), oe(oe), i(i) {};
8993

9094
void step(unsigned timestamp);
9195

9296
private:
9397
uint32_t input_data = 0;
94-
const value<width> &o;
95-
const value<width> &oe;
96-
value<width> &i;
98+
const value<pin_count> &o;
99+
const value<pin_count> &oe;
100+
value<pin_count> &i;
97101
struct {
98102
uint32_t o_last = 0;
99103
uint32_t oe_last = 0;
@@ -103,7 +107,7 @@ struct gpio_model {
103107

104108
struct spi_model {
105109
std::string name;
106-
spi_model(const std::string &name, const value<1> &clk, const value<1> &csn, const value<1> &copi, value<1> &cipo) :
110+
spi_model(const std::string &name, const value<1> &clk, const value<1> &copi, value<1> &cipo, const value<1> &csn) :
107111
name(name), clk(clk), csn(csn), copi(copi), cipo(cipo) {
108112
};
109113

@@ -127,7 +131,7 @@ struct spi_model {
127131

128132
struct i2c_model {
129133
std::string name;
130-
i2c_model(const std::string &name, const value<1> &sda_oe, value<1> &sda_i, const value<1> &scl_oe, value<1> &scl_i) : name(name), sda_oe(sda_oe), sda_i(sda_i), scl_oe(scl_oe), scl_i(scl_i) {};
134+
i2c_model(const std::string &name, const value<1> &scl_o, const value<1> &scl_oe, value<1> &scl_i, const value<1> &sda_o, const value<1> &sda_oe, value<1> &sda_i) : name(name), sda_oe(sda_oe), sda_i(sda_i), scl_oe(scl_oe), scl_i(scl_i) {};
131135

132136
void step(unsigned timestamp);
133137
private:

chipflow_lib/platforms/__init__.py

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

1822
__all__ = ['IO_ANNOTATION_SCHEMA', 'IOSignature',
1923
'IOModel', 'IOModelOptions', 'IOTripPoint',
2024
'OutputIOSignature', 'InputIOSignature', 'BidirIOSignature',
2125
'SiliconPlatformPort', 'SiliconPlatform',
2226
'SimPlatform',
27+
'JTAGSignature', 'SPISignature', 'I2CSignature', 'UARTSignature', 'GPIOSignature', 'QSPIFlashSignature',
28+
'attach_simulation_data',
2329
'Sky130DriveMode',
2430
'PACKAGE_DEFINITIONS']
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
from types import MethodType
2+
import pydantic
3+
from typing import TypeVar
4+
from typing_extensions import is_typeddict
5+
_T_TypedDict = TypeVar('_T_TypedDict')
6+
7+
def amaranth_annotate(modeltype: type['_T_TypedDict'], schema_id: str, member='__chipflow_annotation__', decorate_object = False):
8+
if not is_typeddict(modeltype):
9+
raise TypeError(f'''amaranth_annotate must be passed a TypedDict, not {modeltype}''')
10+
11+
# interesting pydantic issue gets hit if arbitrary_types_allowed is False
12+
if hasattr(modeltype, '__pydantic_config__'):
13+
config = getattr(modeltype, '__pydantic_config__')
14+
config['arbitrary_types_allowed'] = True
15+
else:
16+
config = pydantic.ConfigDict()
17+
config['arbitrary_types_allowed'] = True
18+
setattr(modeltype, '__pydantic_config__', config)
19+
PydanticModel = pydantic.TypeAdapter(modeltype)
20+
21+
def annotation_schema():
22+
schema = PydanticModel.json_schema()
23+
schema['$schema'] = 'https://json-schema.org/draft/2020-12/schema'
24+
schema['$id'] = schema_id
25+
return schema
26+
27+
class Annotation:
28+
'Generated annotation class'
29+
schema = annotation_schema()
30+
31+
def __init__(self, parent):
32+
self.parent = parent
33+
34+
def origin(self):
35+
return self.parent
36+
37+
def as_json(self):
38+
return PydanticModel.dump_python(getattr(self.parent, member))
39+
40+
def decorate_class(klass):
41+
if hasattr(klass, 'annotations'):
42+
old_annotations = klass.annotations
43+
else:
44+
old_annotations = None
45+
46+
def annotations(self, obj):
47+
if old_annotations:
48+
annotations = old_annotations(self, obj)
49+
else:
50+
annotations = super(klass, obj).annotations(obj)
51+
annotation = Annotation(self)
52+
return annotations + (annotation,)
53+
54+
klass.annotations = annotations
55+
return klass
56+
57+
def decorate_obj(obj):
58+
if hasattr(obj, 'annotations'):
59+
old_annotations = obj.annotations
60+
else:
61+
old_annotations = None
62+
63+
def annotations(self = None, origin = None):
64+
if old_annotations:
65+
annotations = old_annotations(origin)
66+
else:
67+
annotations = super(obj.__class__, obj).annotations(obj)
68+
annotation = Annotation(self)
69+
return annotations + (annotation,)
70+
71+
setattr(obj, 'annotations', MethodType(annotations, obj))
72+
return obj
73+
74+
if decorate_object:
75+
return decorate_obj
76+
else:
77+
return decorate_class
78+
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# SPDX-License-Identifier: BSD-2-Clause
2+
3+
import re
4+
from typing import List, Tuple, Any
5+
from typing_extensions import Unpack, TypedDict
6+
7+
from amaranth.lib import wiring
8+
from amaranth.lib.wiring import Out
9+
10+
from .. import ChipFlowError
11+
from ._utils import InputIOSignature, OutputIOSignature, BidirIOSignature, IOModelOptions, _chipflow_schema_uri
12+
from ._annotate import amaranth_annotate
13+
14+
SIM_ANNOTATION_SCHEMA = str(_chipflow_schema_uri("simulatable-interface", 0))
15+
SIM_DATA_SCHEMA = str(_chipflow_schema_uri("simulatable-data", 0))
16+
17+
class SimInterface(TypedDict):
18+
uid: str
19+
parameters: List[Tuple[str, Any]]
20+
21+
class SimData(TypedDict):
22+
file_name: str
23+
offset: int
24+
25+
_VALID_UID = re.compile('[a-zA-Z_.]').search
26+
27+
def _unpack_dict(d: dict) -> str:
28+
params = [ f"{k}={repr(v)}" for k,v in d.items()]
29+
return ', '.join(params)
30+
31+
"""
32+
Attributes:
33+
__chipflow_parameters__: list of tuples (name, value).
34+
It is expected that a model that takes parameters is implmemted as a template, with the parameters in the order
35+
given.
36+
"""
37+
def simulatable_interface(base="com.chipflow.chipflow_lib"):
38+
def decorate(klass):
39+
assert _VALID_UID(base)
40+
dec = amaranth_annotate(SimInterface, SIM_ANNOTATION_SCHEMA)
41+
klass = dec(klass)
42+
43+
def new_init(self,*args, **kwargs):
44+
original_init(self, *args, **kwargs)
45+
self.__chipflow_annotation__ = {
46+
"uid": klass.__chipflow_uid__,
47+
"parameters": self.__chipflow_parameters__(),
48+
}
49+
50+
def repr(self) -> str:
51+
return f"{klass.__name__}({_unpack_dict(self.__chipflow_parameters__())}, {_unpack_dict(self._options)})"
52+
53+
original_init = klass.__init__
54+
klass.__init__ = new_init
55+
klass.__chipflow_uid__ = f"{base}.{klass.__name__}"
56+
if not hasattr(klass, '__chipflow_parameters__'):
57+
klass.__chipflow_parameters__ = lambda self: []
58+
if not klass.__repr__:
59+
klass.__repr__ = repr
60+
return klass
61+
return decorate
62+
63+
64+
@simulatable_interface()
65+
class JTAGSignature(wiring.Signature):
66+
def __init__(self, **kwargs: Unpack[IOModelOptions]):
67+
super().__init__({
68+
"trst": Out(InputIOSignature(1)),
69+
"tck": Out(InputIOSignature(1)),
70+
"tms": Out(InputIOSignature(1)),
71+
"tdi": Out(InputIOSignature(1)),
72+
"tdo": Out(OutputIOSignature(1)),
73+
})
74+
75+
76+
@simulatable_interface()
77+
class SPISignature(wiring.Signature):
78+
def __init__(self, **kwargs: Unpack[IOModelOptions]):
79+
super().__init__({
80+
"sck": Out(OutputIOSignature(1)),
81+
"copi": Out(OutputIOSignature(1)),
82+
"cipo": Out(InputIOSignature(1)),
83+
"csn": Out(OutputIOSignature(1)),
84+
})
85+
86+
@simulatable_interface()
87+
class QSPIFlashSignature(wiring.Signature):
88+
def __init__(self, **kwargs: Unpack[IOModelOptions]):
89+
super().__init__({
90+
"clk": Out(OutputIOSignature(1)),
91+
"csn": Out(OutputIOSignature(1)),
92+
"d": Out(BidirIOSignature(4, individual_oe=True)),
93+
})
94+
95+
@simulatable_interface()
96+
class UARTSignature(wiring.Signature):
97+
def __init__(self, **kwargs: Unpack[IOModelOptions]):
98+
super().__init__({
99+
"tx": Out(OutputIOSignature(1)),
100+
"rx": Out(InputIOSignature(1)),
101+
})
102+
103+
@simulatable_interface()
104+
class I2CSignature(wiring.Signature):
105+
def __init__(self, **kwargs: Unpack[IOModelOptions]):
106+
super().__init__({
107+
"scl": Out(BidirIOSignature(1)),
108+
"sda": Out(BidirIOSignature(1))
109+
})
110+
self._options = kwargs
111+
112+
113+
@simulatable_interface()
114+
class GPIOSignature(wiring.Signature):
115+
116+
def __init__(self, pin_count=1, **kwargs: Unpack[IOModelOptions]):
117+
self._pin_count = pin_count
118+
self._options = kwargs
119+
kwargs['individual_oe'] = True
120+
super().__init__({
121+
"gpio": Out(BidirIOSignature(pin_count, **kwargs))
122+
})
123+
124+
def __chipflow_parameters__(self):
125+
return [('pin_count',self._pin_count)]
126+
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+

0 commit comments

Comments
 (0)