Skip to content

Commit 3fecb02

Browse files
committed
wip
1 parent aa6da99 commit 3fecb02

File tree

3 files changed

+168
-18
lines changed

3 files changed

+168
-18
lines changed

chipflow_lib/_signatures.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# SPDX-License-Identifier: BSD-2-Clause
2+
3+
from typing_extensions import Unpack, TypedDict
4+
5+
from amaranth.lib import wiring
6+
from amaranth.lib.wiring import Out
7+
8+
from .platforms._utils import InputIOSignature, OutputIOSignature, BidirIOSignature, IOModelOptions, _chipflow_schema_uri, amaranth_annotate
9+
10+
SIM_ANNOTATION_SCHEMA = str(_chipflow_schema_uri("sim-interface", 0))
11+
12+
def sim_annotate(klass):
13+
class Model(TypedDict):
14+
sim_interface: str
15+
16+
original_init_subclass = klass.__init_subclass__
17+
@classmethod
18+
def new_init_subclass(cls, /, **kwargs):
19+
if original_init_subclass:
20+
original_init_subclass(**kwargs)
21+
cls._model = {"sim_interface": cls.__name__}
22+
23+
dec = amaranth_annotate(Model, SIM_ANNOTATION_SCHEMA)
24+
klass = dec(klass)
25+
klass.__init_subclass__ = new_init_subclass
26+
return klass
27+
28+
29+
@sim_annotate
30+
class SimulatableSignature(wiring.Signature):
31+
...
32+
33+
34+
SPISignature = SimulatableSignature({
35+
"sck": Out(OutputIOSignature(1)),
36+
"copi": Out(OutputIOSignature(1)),
37+
"cipo": Out(InputIOSignature(1)),
38+
"csn": Out(OutputIOSignature(1)),
39+
})
40+
41+
UARTSignature = SimulatableSignature({
42+
"tx": Out(OutputIOSignature(1)),
43+
"rx": Out(InputIOSignature(1)),
44+
})
45+
46+
I2CSignature = SimulatableSignature({
47+
"scl": Out(BidirIOSignature(1)),
48+
"sda": Out(BidirIOSignature(1))
49+
})
50+
51+
class GPIOSignature(SimulatableSignature):
52+
def __init__(self, pin_count=1, **kwargs: Unpack[IOModelOptions]):
53+
if pin_count > 32:
54+
raise ValueError(f"Pin pin_count must be lesser than or equal to 32, not {pin_count}")
55+
self._pin_count = pin_count
56+
super().__init__({
57+
"gpio": Out(BidirIOSignature(pin_count, individual_oe=True, **kwargs))
58+
})
59+
def get_sim_parameters(self):
60+
return {'pin_count': self._pin_count}
61+
62+
def __repr__(self) -> str:
63+
return f"GPIOSignature(pin_count={self._pin_count}, {dict(self.members.items())})"

chipflow_lib/platforms/_utils.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from enum import Enum, IntEnum, StrEnum, auto
1414
from math import ceil, floor
1515
from typing import (
16-
Any, Annotated, NamedTuple, Self, Iterator, Type,
16+
Any, Annotated, NamedTuple, Self, Type,
1717
TYPE_CHECKING
1818
)
1919
from typing_extensions import (
@@ -157,19 +157,17 @@ def as_json(self): # type: ignore
157157

158158
def annotations(self, *args): # type: ignore
159159
annotations = wiring.Signature.annotations(self, *args) # type: ignore
160-
161-
io_annotation = Annotation(self._model)
162-
return annotations + (io_annotation,) # type: ignore
163-
164-
165-
160+
print(f"annotating {self} with {self._model}")
161+
annotation = Annotation(self._model)
162+
print(f"returning {annotations + (annotation,)}")
163+
return annotations + (annotation,) # type: ignore
166164

167165
def decorator(klass):
168166
klass.annotations = annotations
169-
klass.__repr__
170167
return klass
171168
return decorator
172169

170+
173171
@amaranth_annotate(IOModel, IO_ANNOTATION_SCHEMA)
174172
class IOSignature(wiring.Signature):
175173
"""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.

chipflow_lib/platforms/sim.py

Lines changed: 99 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,115 @@
33
import logging
44
import os
55
import sys
6+
7+
from dataclasses import dataclass
8+
from enum import StrEnum, auto
69
from pathlib import Path
10+
from pprint import pformat
11+
from typing import List, Optional, TypedDict, Optional, Unpack, Union, Type
712

813
from amaranth import Module, ClockDomain, ClockSignal, ResetSignal
9-
from amaranth.lib import io
14+
from amaranth.lib import io, meta, wiring
15+
from amaranth.lib.wiring import In, Out
1016
from amaranth.back import rtlil # type: ignore[reportAttributeAccessIssue]
17+
from amaranth.hdl import _ir, _ast
1118
from amaranth.hdl._ir import PortDirection
1219
from amaranth.lib.cdc import FFSynchronizer
20+
from pydantic import BaseModel, ConfigDict
1321

14-
from ._utils import load_pinlock
22+
from .. import ChipFlowError
23+
from .._signatures import I2CSignature, GPIOSignature, UARTSignature, SPISignature
24+
from ._utils import load_pinlock, _chipflow_schema_uri, amaranth_annotate, InputIOSignature, OutputIOSignature, BidirIOSignature
1525

1626

17-
__all__ = ["SimPlatform"]
1827
logger = logging.getLogger(__name__)
28+
__all__ = ["SimPlatform", "BuildObject", "BasicCxxBuild"]
29+
30+
31+
class SimModelCapability(StrEnum):
32+
LOAD_DATA = "load-data"
33+
34+
35+
@dataclass
36+
class SimModel:
37+
"""
38+
Description of a model available from a BuildObject
39+
Attributes:
40+
name: the model name
41+
capabilities: List of capabilities of the model.
42+
signature: the wiring connection of the model. This is also used to match with interfaces.
43+
"""
44+
name: str
45+
signature: Type[wiring.Signature]
46+
capabilities: Optional[List[SimModelCapability]] = None
47+
48+
49+
class BuildObject(BaseModel):
50+
"""
51+
Represents an object built from a compiled language
52+
53+
Attributes:
54+
capabilities: arbitary list of capability identifiers
55+
"""
56+
kind: str = "base"
57+
models: List[SimModel]
58+
59+
@classmethod
60+
def get_subclasses(cls):
61+
return tuple(cls.__subclasses__())
62+
63+
def model_for_signature(self, signature: wiring.Signature) -> SimModel | None:
64+
"""
65+
Checks if this build object has a model for the given signature (matching by type equality)
66+
67+
Returns:
68+
A tuple of the model and a dict of parameters from the signature
69+
None on failure to find a matching model
70+
"""
71+
for m in self.models:
72+
if isinstance(signature, m.signature):
73+
return m
74+
75+
76+
77+
class BasicCxxBuild(BuildObject):
78+
"""
79+
Represents an object built from C++, where the compilation is simply done with a collection of
80+
cpp and hpp files, simply compiled and linked together with no dependencies
81+
82+
Assumes model name corresponds to the c++ class name and that the class constructors take
83+
a name followed by the wires of the interface.
84+
85+
Attributes:
86+
cpp_files: C++ files used to define the model
87+
hpp_files: C++ header files to define the model interfaces
88+
"""
89+
kind: str = "basic-c++"
90+
cpp_files: List[Path]
91+
hpp_files: Optional[List[Path]] = None
92+
hpp_dirs: Optional[List[Path]] = None
93+
94+
95+
_COMMON_MODELS = BasicCxxBuild(
96+
models=[
97+
SimModel('spiflash_model', SPISignature, [SimModelCapability.LOAD_DATA]),
98+
SimModel('uart_model', UARTSignature),
99+
SimModel('i2c_model', I2CSignature),
100+
SimModel('gpio_model', GPIOSignature),
101+
],
102+
cpp_files=[ Path('common','sim','models.cc') ],
103+
hpp_files=[ Path('common','sim','models.h') ],
104+
)
19105

20106

21107
class SimPlatform:
22-
23108
def __init__(self, config):
24109
self.build_dir = os.path.join(os.environ['CHIPFLOW_ROOT'], 'build', 'sim')
25110
self.extra_files = dict()
26111
self.sim_boxes = dict()
27112
self._ports = {}
28113
self._config = config
114+
self._top_sim = {}
29115

30116
def add_file(self, filename, content):
31117
if not isinstance(content, (str, bytes)):
@@ -67,6 +153,10 @@ def build(self, e):
67153
print("read_rtlil sim_soc.il", file=yosys_file)
68154
print("hierarchy -top sim_top", file=yosys_file)
69155
print("write_cxxrtl -header sim_soc.cc", file=yosys_file)
156+
main = Path(self.build_dir) / "main.cc"
157+
with open(main, "w") as main_file:
158+
for p in self._ports:
159+
print(p, file=main_file)
70160

71161
def instantiate_ports(self, m: Module):
72162
if hasattr(self, "_pinlock"):
@@ -76,10 +166,11 @@ def instantiate_ports(self, m: Module):
76166
for component, iface in pinlock.port_map.ports.items():
77167
for k, v in iface.items():
78168
for name, port_desc in v.items():
79-
logger.debug(f"Instantiating port {port_desc.port_name}: {port_desc}")
80-
invert = port_desc.invert if port_desc.invert else False
81-
self._ports[port_desc.port_name] = io.SimulationPort(port_desc.direction, port_desc.width, invert=invert, name=port_desc.port_name)
82-
169+
logger.debug(f"Instantiating port {port_desc.port_name}: {port_desc}")
170+
invert = port_desc.invert if port_desc.invert else False
171+
self._ports[port_desc.port_name] = io.SimulationPort(port_desc.direction, port_desc.width, invert=invert, name=port_desc.port_name)
172+
# TODO, allow user to add models
173+
#self._port_model = model_for_signature(port_desc.
83174
for clock in pinlock.port_map.get_clocks():
84175
assert 'clock_domain' in clock.iomodel
85176
domain = clock.iomodel['clock_domain']
@@ -123,8 +214,6 @@ def instantiate_ports(self, m: Module):
123214
"{OUTPUT_DIR}/sim_soc.cc",
124215
"{OUTPUT_DIR}/sim_soc.h",
125216
"{SOURCE_DIR}/main.cc",
126-
"{COMMON_DIR}/models.cc",
127-
"{COMMON_DIR}/models.h",
128217
"{COMMON_DIR}/vendor/nlohmann/json.hpp",
129218
"{COMMON_DIR}/vendor/cxxrtl/cxxrtl_server.h",
130219
],

0 commit comments

Comments
 (0)