Skip to content

Commit bee7995

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 fd3b565 commit bee7995

File tree

8 files changed

+504
-54
lines changed

8 files changed

+504
-54
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: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@
1414
)
1515
from ._packages import PACKAGE_DEFINITIONS
1616
from ._sky130 import Sky130DriveMode
17+
from ._signatures import JTAGSignature, SPISignature, I2CSignature, UARTSignature, GPIOSignature, QSPIFlashSignature
1718

1819
__all__ = ['IO_ANNOTATION_SCHEMA', 'IOSignature',
1920
'IOModel', 'IOModelOptions', 'IOTripPoint',
2021
'OutputIOSignature', 'InputIOSignature', 'BidirIOSignature',
2122
'SiliconPlatformPort', 'SiliconPlatform',
2223
'SimPlatform',
24+
'JTAGSignature', 'SPISignature', 'I2CSignature', 'UARTSignature', 'GPIOSignature',
2325
'Sky130DriveMode',
2426
'PACKAGE_DEFINITIONS']
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
from types import MethodType
2+
import pydantic
3+
from amaranth.lib import meta
4+
from typing import Any, Annotated, NamedTuple, Self, TypeVar
5+
from typing_extensions import TypedDict, is_typeddict
6+
_T_TypedDict = TypeVar('_T_TypedDict')
7+
8+
def amaranth_annotate(modeltype: type['_T_TypedDict'], schema_id: str, member='__chipflow_annotation__', decorate_object = False):
9+
if not is_typeddict(modeltype):
10+
raise TypeError(f'''amaranth_annotate must be passed a TypedDict, not {modeltype}''')
11+
12+
# interesting pydantic issue gets hit if arbitrary_types_allowed is False
13+
if hasattr(modeltype, '__pydantic_config__'):
14+
config = getattr(modeltype, '__pydantic_config__')
15+
config['arbitrary_types_allowed'] = True
16+
else:
17+
config = pydantic.ConfigDict()
18+
config['arbitrary_types_allowed'] = True
19+
setattr(modeltype, '__pydantic_config__', config)
20+
PydanticModel = pydantic.TypeAdapter(modeltype)
21+
22+
def annotation_schema():
23+
schema = PydanticModel.json_schema()
24+
schema['$schema'] = 'https://json-schema.org/draft/2020-12/schema'
25+
schema['$id'] = schema_id
26+
return schema
27+
28+
class Annotation:
29+
'Generated annotation class'
30+
schema = annotation_schema()
31+
32+
def __init__(self, parent):
33+
self.parent = parent
34+
35+
def origin(self):
36+
return self.parent
37+
38+
def as_json(self):
39+
return PydanticModel.dump_python(getattr(self.parent, member))
40+
41+
def decorate_class(klass):
42+
if hasattr(klass, 'annotations'):
43+
old_annotations = klass.annotations
44+
else:
45+
old_annotations = None
46+
47+
def annotations(self, obj):
48+
if old_annotations:
49+
annotations = old_annotations(self, obj)
50+
else:
51+
annotations = super(klass, obj).annotations(obj)
52+
annotation = Annotation(self)
53+
return annotations + (annotation,)
54+
55+
klass.annotations = annotations
56+
return klass
57+
58+
def decorate_obj(obj):
59+
if hasattr(obj, 'annotations'):
60+
old_annotations = obj.annotations
61+
else:
62+
old_annotations = None
63+
64+
def annotations(self = None, origin = None):
65+
if old_annotations:
66+
annotations = old_annotations(origin)
67+
else:
68+
annotations = super(obj.__class__, obj).annotations(obj)
69+
annotation = Annotation(self)
70+
return annotations + (annotation,)
71+
72+
setattr(obj, 'annotations', MethodType(annotations, obj))
73+
return obj
74+
75+
if decorate_object:
76+
return decorate_obj
77+
else:
78+
return decorate_class
79+
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
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, amaranth_annotate
12+
13+
SIM_ANNOTATION_SCHEMA = str(_chipflow_schema_uri("sim-interface", 0))
14+
15+
class SimInterface(TypedDict):
16+
uid: str
17+
parameters: List[Tuple[str, Any]]
18+
19+
_VALID_UID = re.compile('[a-zA-Z_.]').search
20+
21+
def sim_annotate(base="com.chipflow.chipflow_lib"):
22+
def decorate(klass):
23+
assert _VALID_UID(base)
24+
dec = amaranth_annotate(SimInterface, SIM_ANNOTATION_SCHEMA)
25+
klass = dec(klass)
26+
27+
def new_init(self,*args, **kwargs):
28+
print("called new_init")
29+
original_init(self, *args, **kwargs)
30+
self.__chipflow_annotation__ = {
31+
"uid": klass.__chipflow_uid__,
32+
"parameters": self.__chipflow_parameters__(),
33+
}
34+
35+
original_init = klass.__init__
36+
klass.__init__ = new_init
37+
klass.__chipflow_uid__ = f"{base}.{klass.__name__}"
38+
if not hasattr(klass, '__chipflow_parameters__'):
39+
klass.__chipflow_parameters__ = lambda self: {}
40+
return klass
41+
return decorate
42+
43+
44+
@sim_annotate()
45+
class JTAGSignature(wiring.Signature):
46+
def __init__(self, **kwargs: Unpack[IOModelOptions]):
47+
super().__init__({
48+
"trst": Out(InputIOSignature(1)),
49+
"tck": Out(InputIOSignature(1)),
50+
"tms": Out(InputIOSignature(1)),
51+
"tdi": Out(InputIOSignature(1)),
52+
"tdo": Out(OutputIOSignature(1)),
53+
})
54+
55+
56+
@sim_annotate()
57+
class SPISignature(wiring.Signature):
58+
def __init__(self, **kwargs: Unpack[IOModelOptions]):
59+
super().__init__({
60+
"sck": Out(OutputIOSignature(1)),
61+
"copi": Out(OutputIOSignature(1)),
62+
"cipo": Out(InputIOSignature(1)),
63+
"csn": Out(OutputIOSignature(1)),
64+
})
65+
66+
@sim_annotate()
67+
class QSPIFlashSignature(wiring.Signature):
68+
def __init__(self, **kwargs: Unpack[IOModelOptions]):
69+
super().__init__({
70+
"clk": Out(OutputIOSignature(1)),
71+
"csn": Out(OutputIOSignature(1)),
72+
"d": Out(BidirIOSignature(4, individual_oe=True)),
73+
})
74+
75+
@sim_annotate()
76+
class UARTSignature(wiring.Signature):
77+
def __init__(self, **kwargs: Unpack[IOModelOptions]):
78+
super().__init__({
79+
"tx": Out(OutputIOSignature(1)),
80+
"rx": Out(InputIOSignature(1)),
81+
})
82+
83+
@sim_annotate()
84+
class I2CSignature(wiring.Signature):
85+
def __init__(self, **kwargs: Unpack[IOModelOptions]):
86+
super().__init__({
87+
"scl": Out(BidirIOSignature(1)),
88+
"sda": Out(BidirIOSignature(1))
89+
})
90+
91+
@sim_annotate()
92+
class GPIOSignature(wiring.Signature):
93+
94+
def __init__(self, pin_count=1, **kwargs: Unpack[IOModelOptions]):
95+
if pin_count > 32:
96+
raise ValueError(f"Pin pin_count must be lesser than or equal to 32, not {pin_count}")
97+
self._pin_count = pin_count
98+
kwargs['individual_oe'] = True
99+
super().__init__({
100+
"gpio": Out(BidirIOSignature(pin_count, **kwargs))
101+
})
102+
103+
def __chipflow_parameters__(self):
104+
print("called GPIOSignature.__chipflow_parameters__")
105+
return {'pin_count': self._pin_count}
106+
107+
def __repr__(self) -> str:
108+
return f"GPIOSignature(pin_count={self._pin_count}, {dict(self.members.items())})"
109+
110+
111+
class SimulationCanLoadData:
112+
"""
113+
Inherit from this in your object's Signature if you want a simulation model
114+
to be able to load data from your object
115+
"""
116+
@classmethod
117+
def __init_submodule__(cls, /, *args, **kwargs):
118+
if wiring.Signature not in cls.mro():
119+
raise ChipFlowError("SimulationCanLoadData can only be used with ``wiring.Signature`` classes")
120+
original_annotations = getattr(cls, 'annotations')
121+
#def annotations(self, obj, /):
122+
#cls.annotate

0 commit comments

Comments
 (0)