From 9d6672ee54bbee133578b42784e4a5ff72f6756e Mon Sep 17 00:00:00 2001 From: Rob Taylor Date: Tue, 22 Jul 2025 21:31:25 +0100 Subject: [PATCH 1/6] Generalise adding an Amaranth annotation via a TypedDict --- chipflow_lib/platforms/_utils.py | 61 ++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/chipflow_lib/platforms/_utils.py b/chipflow_lib/platforms/_utils.py index d15f6a12..2456be92 100644 --- a/chipflow_lib/platforms/_utils.py +++ b/chipflow_lib/platforms/_utils.py @@ -13,7 +13,7 @@ from enum import Enum, IntEnum, StrEnum, auto from math import ceil, floor from typing import ( - Any, Annotated, NamedTuple, Generic, Self, + Any, Annotated, NamedTuple, Iterator, Generic, Self, TYPE_CHECKING ) from typing_extensions import ( @@ -130,32 +130,47 @@ class IOModel(IOModelOptions): width: int direction: Annotated[io.Direction, PlainSerializer(lambda x: x.value)] -def io_annotation_schema(): - class Model(pydantic.BaseModel): - data_td: IOModel +def amaranth_annotate(model: Type[TypedDict], schema_id: str): + def annotation_schema(): + class Model(pydantic.BaseModel): + data_td: model - PydanticModel = TypeAdapter(IOModel) - schema = PydanticModel.json_schema() - schema['$schema'] = "https://json-schema.org/draft/2020-12/schema" - schema['$id'] = IO_ANNOTATION_SCHEMA - return schema + PydanticModel = TypeAdapter(model) + schema = PydanticModel.json_schema() + schema['$schema'] = "https://json-schema.org/draft/2020-12/schema" + schema['$id'] = schema_id + return schema + class Annotation(meta.Annotation): + "Generated annotation class" + schema = annotation_schema() -class _IOAnnotation(meta.Annotation): - "Infrastructure for `Amaranth annotations `" - schema = io_annotation_schema() + def __init__(self, model:IOModel): + self._model = model - def __init__(self, model:IOModel): - self._model = model + @property + def origin(self): # type: ignore + return self._model + + def as_json(self): # type: ignore + return TypeAdapter(IOModel).dump_python(self._model) + + def annotations(self, *args): # type: ignore + annotations = wiring.Signature.annotations(self, *args) # type: ignore + + io_annotation = Annotation(self._model) + return annotations + (io_annotation,) # type: ignore - @property - def origin(self): # type: ignore - return self._model - def as_json(self): # type: ignore - return TypeAdapter(IOModel).dump_python(self._model) + def decorator(klass): + klass.annotations = annotations + klass.__repr__ + return klass + return decorator + +@amaranth_annotate(IOModel, IO_ANNOTATION_SCHEMA) class IOSignature(wiring.Signature): """An :py:obj:`Amaranth Signature ` used to decorate wires that would usually be brought out onto a port on the package. This class is generally not directly used. Instead, you would typically utilize the more specific @@ -225,17 +240,9 @@ def options(self) -> IOModelOptions: """ return self._model - def annotations(self, *args): # type: ignore - annotations = wiring.Signature.annotations(self, *args) # type: ignore - - io_annotation = _IOAnnotation(self._model) - return annotations + (io_annotation,) # type: ignore - - def __repr__(self): return f"IOSignature({','.join('{0}={1!r}'.format(k,v) for k,v in self._model.items())})" - def OutputIOSignature(width: int, **kwargs: Unpack[IOModelOptions]): """This creates an :py:obj:`Amaranth Signature ` which is then used to decorate package output signals intended for connection to the physical pads of the integrated circuit package. From 8e6cc39644d023659fcbcfa5c1c47d7cda091003 Mon Sep 17 00:00:00 2001 From: Rob Taylor Date: Tue, 22 Jul 2025 21:31:57 +0100 Subject: [PATCH 2/6] 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. --- chipflow_lib/common/sim/main.cc.jinja | 77 +++++++++ chipflow_lib/common/sim/models.cc | 6 +- chipflow_lib/common/sim/models.h | 18 ++- chipflow_lib/platforms/__init__.py | 6 + chipflow_lib/platforms/_annotate.py | 78 +++++++++ chipflow_lib/platforms/_signatures.py | 131 +++++++++++++++ chipflow_lib/platforms/_utils.py | 55 ++----- chipflow_lib/platforms/sim.py | 223 +++++++++++++++++++++++--- chipflow_lib/software/soft_gen.py | 2 +- chipflow_lib/steps/sim.py | 10 +- chipflow_lib/steps/software.py | 2 +- 11 files changed, 526 insertions(+), 82 deletions(-) create mode 100644 chipflow_lib/common/sim/main.cc.jinja create mode 100644 chipflow_lib/platforms/_annotate.py create mode 100644 chipflow_lib/platforms/_signatures.py diff --git a/chipflow_lib/common/sim/main.cc.jinja b/chipflow_lib/common/sim/main.cc.jinja new file mode 100644 index 00000000..1a3b2f35 --- /dev/null +++ b/chipflow_lib/common/sim/main.cc.jinja @@ -0,0 +1,77 @@ +#undef NDEBUG + +#include +#include +#include "sim_soc.h" +{% for include in includes %} +#include "{{include}}" +{% endfor %} + +#include +#include + +using namespace cxxrtl::time_literals; +using namespace cxxrtl_design; + +int main(int argc, char **argv) { + p_sim__top top; + + {% for initialiser in initialisers %} + {{initialiser}}; + {% endfor %} + + cxxrtl::agent agent(cxxrtl::spool("spool.bin"), top); + if (getenv("DEBUG")) // can also be done when a condition is violated, etc + std::cerr << "Waiting for debugger on " << agent.start_debugging() << std::endl; + + open_event_log(BUILD_DIR "/sim/events.json"); + open_input_commands(PROJECT_ROOT "/design/tests/input.json"); + + unsigned timestamp = 0; + auto tick = [&]() { + {% for interface in interfaces %} + {{interface}}.step(timestamp); + {% endfor %} + + // FIXME: Currently we tick all clocks together, this need fixing.. + {% for clock in clocks %} + top.{{clock}}.set(false); + {% endfor %} + agent.step(); + agent.advance(1_us); + ++timestamp; + + {% for clock in clocks %} + top.{{clock}}.set(true); + {% endfor %} + agent.step(); + agent.advance(1_us); + ++timestamp; + + // if (timestamp == 10) + // agent.breakpoint(CXXRTL_LOCATION); + }; + + {% for data in data_load %} + {{data.model_name}}.load_data("{{data.file_name}}", {{data.args | join(', ')}}); + {% endfor %} + + agent.step(); + agent.advance(1_us); + + {% for reset in resets %} + top.{{reset}}.set(false); + {% endfor %} + + tick(); + + {% for reset in resets %} + top.{{reset}}.set(true); + {% endfor %} + + for (int i = 0; i < 3000000; i++) + tick(); + + close_event_log(); + return 0; +} diff --git a/chipflow_lib/common/sim/models.cc b/chipflow_lib/common/sim/models.cc index 171dc21b..d47aad0f 100644 --- a/chipflow_lib/common/sim/models.cc +++ b/chipflow_lib/common/sim/models.cc @@ -284,8 +284,8 @@ void gpio_model::step(unsigned timestamp) { if (action.event == "set") { auto bin = std::string(action.payload); input_data = 0; - for (unsigned i = 0; i < width; i++) { - if (bin.at((width - 1) - i) == '1') + for (unsigned i = 0; i < pin_count; i++) { + if (bin.at((pin_count - 1) - i) == '1') input_data |= (1U << i); } } @@ -293,7 +293,7 @@ void gpio_model::step(unsigned timestamp) { if (o_value != s.o_last || oe_value != s.oe_last) { std::string formatted_value; - for (int i = width - 1; i >= 0; i--) { + for (int i = pin_count - 1; i >= 0; i--) { if (oe_value & (1U << unsigned(i))) formatted_value += (o_value & (1U << unsigned(i))) ? '1' : '0'; else diff --git a/chipflow_lib/common/sim/models.h b/chipflow_lib/common/sim/models.h index 0af3ded8..a33a8775 100644 --- a/chipflow_lib/common/sim/models.h +++ b/chipflow_lib/common/sim/models.h @@ -83,17 +83,21 @@ struct uart_model { }; struct gpio_model { - static constexpr unsigned width = 8; std::string name; - gpio_model(const std::string &name, const value &o, const value &oe, value &i) : name(name), o(o), oe(oe), i(i) {}; + struct parameters { + unsigned pin_count; + }; + parameters parameters; + + gpio_model(const std::string &name, parameters, const value &o, const value &oe, value &i) : name(name), parameters(parameters), o(o), oe(oe), i(i) {}; void step(unsigned timestamp); private: uint32_t input_data = 0; - const value &o; - const value &oe; - value &i; + const value &o; + const value &oe; + value &i; struct { uint32_t o_last = 0; uint32_t oe_last = 0; @@ -103,7 +107,7 @@ struct gpio_model { struct spi_model { std::string name; - spi_model(const std::string &name, const value<1> &clk, const value<1> &csn, const value<1> &copi, value<1> &cipo) : + spi_model(const std::string &name, const value<1> &clk, const value<1> &copi, value<1> &cipo, const value<1> &csn) : name(name), clk(clk), csn(csn), copi(copi), cipo(cipo) { }; @@ -127,7 +131,7 @@ struct spi_model { struct i2c_model { std::string name; - 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) {}; + 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) {}; void step(unsigned timestamp); private: diff --git a/chipflow_lib/platforms/__init__.py b/chipflow_lib/platforms/__init__.py index f4f6fefa..4d98e554 100644 --- a/chipflow_lib/platforms/__init__.py +++ b/chipflow_lib/platforms/__init__.py @@ -14,11 +14,17 @@ ) from ._packages import PACKAGE_DEFINITIONS from ._sky130 import Sky130DriveMode +from ._signatures import ( + JTAGSignature, SPISignature, I2CSignature, UARTSignature, GPIOSignature, QSPIFlashSignature, + attach_simulation_data + ) __all__ = ['IO_ANNOTATION_SCHEMA', 'IOSignature', 'IOModel', 'IOModelOptions', 'IOTripPoint', 'OutputIOSignature', 'InputIOSignature', 'BidirIOSignature', 'SiliconPlatformPort', 'SiliconPlatform', 'SimPlatform', + 'JTAGSignature', 'SPISignature', 'I2CSignature', 'UARTSignature', 'GPIOSignature', 'QSPIFlashSignature', + 'attach_simulation_data', 'Sky130DriveMode', 'PACKAGE_DEFINITIONS'] diff --git a/chipflow_lib/platforms/_annotate.py b/chipflow_lib/platforms/_annotate.py new file mode 100644 index 00000000..2dbeef1f --- /dev/null +++ b/chipflow_lib/platforms/_annotate.py @@ -0,0 +1,78 @@ +from types import MethodType +import pydantic +from typing import TypeVar +from typing_extensions import is_typeddict +_T_TypedDict = TypeVar('_T_TypedDict') + +def amaranth_annotate(modeltype: type['_T_TypedDict'], schema_id: str, member='__chipflow_annotation__', decorate_object = False): + if not is_typeddict(modeltype): + raise TypeError(f'''amaranth_annotate must be passed a TypedDict, not {modeltype}''') + + # interesting pydantic issue gets hit if arbitrary_types_allowed is False + if hasattr(modeltype, '__pydantic_config__'): + config = getattr(modeltype, '__pydantic_config__') + config['arbitrary_types_allowed'] = True + else: + config = pydantic.ConfigDict() + config['arbitrary_types_allowed'] = True + setattr(modeltype, '__pydantic_config__', config) + PydanticModel = pydantic.TypeAdapter(modeltype) + + def annotation_schema(): + schema = PydanticModel.json_schema() + schema['$schema'] = 'https://json-schema.org/draft/2020-12/schema' + schema['$id'] = schema_id + return schema + + class Annotation: + 'Generated annotation class' + schema = annotation_schema() + + def __init__(self, parent): + self.parent = parent + + def origin(self): + return self.parent + + def as_json(self): + return PydanticModel.dump_python(getattr(self.parent, member)) + + def decorate_class(klass): + if hasattr(klass, 'annotations'): + old_annotations = klass.annotations + else: + old_annotations = None + + def annotations(self, obj): + if old_annotations: + annotations = old_annotations(self, obj) + else: + annotations = super(klass, obj).annotations(obj) + annotation = Annotation(self) + return annotations + (annotation,) + + klass.annotations = annotations + return klass + + def decorate_obj(obj): + if hasattr(obj, 'annotations'): + old_annotations = obj.annotations + else: + old_annotations = None + + def annotations(self = None, origin = None): + if old_annotations: + annotations = old_annotations(origin) + else: + annotations = super(obj.__class__, obj).annotations(obj) + annotation = Annotation(self) + return annotations + (annotation,) + + setattr(obj, 'annotations', MethodType(annotations, obj)) + return obj + + if decorate_object: + return decorate_obj + else: + return decorate_class + diff --git a/chipflow_lib/platforms/_signatures.py b/chipflow_lib/platforms/_signatures.py new file mode 100644 index 00000000..bc8a241e --- /dev/null +++ b/chipflow_lib/platforms/_signatures.py @@ -0,0 +1,131 @@ +# SPDX-License-Identifier: BSD-2-Clause + +import re +from typing import List, Tuple, Any +from typing_extensions import Unpack, TypedDict + +from amaranth.lib import wiring +from amaranth.lib.wiring import Out + +from .. import ChipFlowError +from ._utils import InputIOSignature, OutputIOSignature, BidirIOSignature, IOModelOptions, _chipflow_schema_uri +from ._annotate import amaranth_annotate + +SIM_ANNOTATION_SCHEMA = str(_chipflow_schema_uri("simulatable-interface", 0)) +SIM_DATA_SCHEMA = str(_chipflow_schema_uri("simulatable-data", 0)) + +class SimInterface(TypedDict): + uid: str + parameters: List[Tuple[str, Any]] + +class SimData(TypedDict): + file_name: str + offset: int + +_VALID_UID = re.compile('[a-zA-Z_.]').search + +def _unpack_dict(d: dict) -> str: + params = [ f"{k}={repr(v)}" for k,v in d.items()] + return ', '.join(params) + +""" +Attributes: + __chipflow_parameters__: list of tuples (name, value). + It is expected that a model that takes parameters is implmemted as a template, with the parameters in the order + given. +""" +def simulatable_interface(base="com.chipflow.chipflow_lib"): + def decorate(klass): + assert _VALID_UID(base) + dec = amaranth_annotate(SimInterface, SIM_ANNOTATION_SCHEMA) + klass = dec(klass) + + def new_init(self,*args, **kwargs): + original_init(self, *args, **kwargs) + self.__chipflow_annotation__ = { + "uid": klass.__chipflow_uid__, + "parameters": self.__chipflow_parameters__(), + } + + def repr(self) -> str: + return f"{klass.__name__}({_unpack_dict(self.__chipflow_parameters__())}, {_unpack_dict(self._options)})" + + original_init = klass.__init__ + klass.__init__ = new_init + klass.__chipflow_uid__ = f"{base}.{klass.__name__}" + if not hasattr(klass, '__chipflow_parameters__'): + klass.__chipflow_parameters__ = lambda self: [] + if not klass.__repr__: + klass.__repr__ = repr + return klass + return decorate + + +@simulatable_interface() +class JTAGSignature(wiring.Signature): + def __init__(self, **kwargs: Unpack[IOModelOptions]): + super().__init__({ + "trst": Out(InputIOSignature(1)), + "tck": Out(InputIOSignature(1)), + "tms": Out(InputIOSignature(1)), + "tdi": Out(InputIOSignature(1)), + "tdo": Out(OutputIOSignature(1)), + }) + + +@simulatable_interface() +class SPISignature(wiring.Signature): + def __init__(self, **kwargs: Unpack[IOModelOptions]): + super().__init__({ + "sck": Out(OutputIOSignature(1)), + "copi": Out(OutputIOSignature(1)), + "cipo": Out(InputIOSignature(1)), + "csn": Out(OutputIOSignature(1)), + }) + +@simulatable_interface() +class QSPIFlashSignature(wiring.Signature): + def __init__(self, **kwargs: Unpack[IOModelOptions]): + super().__init__({ + "clk": Out(OutputIOSignature(1)), + "csn": Out(OutputIOSignature(1)), + "d": Out(BidirIOSignature(4, individual_oe=True)), + }) + +@simulatable_interface() +class UARTSignature(wiring.Signature): + def __init__(self, **kwargs: Unpack[IOModelOptions]): + super().__init__({ + "tx": Out(OutputIOSignature(1)), + "rx": Out(InputIOSignature(1)), + }) + +@simulatable_interface() +class I2CSignature(wiring.Signature): + def __init__(self, **kwargs: Unpack[IOModelOptions]): + super().__init__({ + "scl": Out(BidirIOSignature(1)), + "sda": Out(BidirIOSignature(1)) + }) + self._options = kwargs + + +@simulatable_interface() +class GPIOSignature(wiring.Signature): + + def __init__(self, pin_count=1, **kwargs: Unpack[IOModelOptions]): + self._pin_count = pin_count + self._options = kwargs + kwargs['individual_oe'] = True + super().__init__({ + "gpio": Out(BidirIOSignature(pin_count, **kwargs)) + }) + + def __chipflow_parameters__(self): + return [('pin_count',self._pin_count)] + + +def attach_simulation_data(c: wiring.Component, **kwargs: Unpack[SimData]): + setattr(c.signature, '__chipflow_simulation_data__', kwargs) + amaranth_annotate(SimData, SIM_DATA_SCHEMA, '__chipflow_simulation_data__', decorate_object=True)(c.signature) + diff --git a/chipflow_lib/platforms/_utils.py b/chipflow_lib/platforms/_utils.py index 2456be92..1d61321f 100644 --- a/chipflow_lib/platforms/_utils.py +++ b/chipflow_lib/platforms/_utils.py @@ -13,7 +13,7 @@ from enum import Enum, IntEnum, StrEnum, auto from math import ceil, floor from typing import ( - Any, Annotated, NamedTuple, Iterator, Generic, Self, + Any, Annotated, NamedTuple, Generic, Self, TYPE_CHECKING ) from typing_extensions import ( @@ -31,6 +31,7 @@ from .. import ChipFlowError, _ensure_chipflow_root, _get_cls_by_reference from .._appresponse import AppResponseModel, OmitIfNone +from ._annotate import amaranth_annotate from ._sky130 import Sky130DriveMode if TYPE_CHECKING: @@ -83,7 +84,6 @@ class IOTripPoint(StrEnum): IO_ANNOTATION_SCHEMA = str(_chipflow_schema_uri("pin-annotation", 0)) -@pydantic.with_config(ConfigDict(arbitrary_types_allowed=True)) # type: ignore[reportCallIssue] class IOModelOptions(TypedDict): """ Options for an IO pad/pin. @@ -130,47 +130,8 @@ class IOModel(IOModelOptions): width: int direction: Annotated[io.Direction, PlainSerializer(lambda x: x.value)] -def amaranth_annotate(model: Type[TypedDict], schema_id: str): - def annotation_schema(): - class Model(pydantic.BaseModel): - data_td: model - - PydanticModel = TypeAdapter(model) - schema = PydanticModel.json_schema() - schema['$schema'] = "https://json-schema.org/draft/2020-12/schema" - schema['$id'] = schema_id - return schema - - class Annotation(meta.Annotation): - "Generated annotation class" - schema = annotation_schema() - - def __init__(self, model:IOModel): - self._model = model - - @property - def origin(self): # type: ignore - return self._model - - def as_json(self): # type: ignore - return TypeAdapter(IOModel).dump_python(self._model) - - def annotations(self, *args): # type: ignore - annotations = wiring.Signature.annotations(self, *args) # type: ignore - io_annotation = Annotation(self._model) - return annotations + (io_annotation,) # type: ignore - - - - - def decorator(klass): - klass.annotations = annotations - klass.__repr__ - return klass - return decorator - -@amaranth_annotate(IOModel, IO_ANNOTATION_SCHEMA) +@amaranth_annotate(IOModel, IO_ANNOTATION_SCHEMA, '_model') class IOSignature(wiring.Signature): """An :py:obj:`Amaranth Signature ` used to decorate wires that would usually be brought out onto a port on the package. This class is generally not directly used. Instead, you would typically utilize the more specific @@ -243,6 +204,7 @@ def options(self) -> IOModelOptions: def __repr__(self): return f"IOSignature({','.join('{0}={1!r}'.format(k,v) for k,v in self._model.items())})" + def OutputIOSignature(width: int, **kwargs: Unpack[IOModelOptions]): """This creates an :py:obj:`Amaranth Signature ` which is then used to decorate package output signals intended for connection to the physical pads of the integrated circuit package. @@ -379,17 +341,17 @@ def _group_consecutive_items(ordering: PinList, lst: PinList) -> OrderedDict[int last = lst[0] current_group = [last] - logger.debug(f"_group_consecutive_items starting with {current_group}") + #logger.debug(f"_group_consecutive_items starting with {current_group}") for item in lst[1:]: idx = ordering.index(last) next = ordering[idx + 1] if idx < len(ordering) - 1 else None - logger.debug(f"inspecting {item}, index {idx}, next {next}") + #logger.debug(f"inspecting {item}, index {idx}, next {next}") if item == next: current_group.append(item) - logger.debug("found consecutive, adding to current group") + #logger.debug("found consecutive, adding to current group") else: - logger.debug("found nonconsecutive, creating new group") + #logger.debug("found nonconsecutive, creating new group") grouped.append(current_group) current_group = [item] last = item @@ -631,6 +593,7 @@ def register_component(self, name: str, component: wiring.Component) -> None: component: Amaranth `wiring.Component` to allocate """ + print(f"registering {component}") self._components[name] = component self._interfaces[name] = component.metadata.as_json() diff --git a/chipflow_lib/platforms/sim.py b/chipflow_lib/platforms/sim.py index 56b5701f..f6856378 100644 --- a/chipflow_lib/platforms/sim.py +++ b/chipflow_lib/platforms/sim.py @@ -3,36 +3,175 @@ import logging import os import sys + +from dataclasses import dataclass +from enum import StrEnum from pathlib import Path +from typing import Dict, List, Optional, Type -from amaranth import Module, ClockDomain, ClockSignal, ResetSignal -from amaranth.lib import io +from amaranth import Module, ClockSignal, ResetSignal, ClockDomain +from amaranth.lib import io, wiring from amaranth.back import rtlil # type: ignore[reportAttributeAccessIssue] from amaranth.hdl._ir import PortDirection from amaranth.lib.cdc import FFSynchronizer +from jinja2 import Environment, PackageLoader, select_autoescape +from pydantic import BaseModel -from ._utils import load_pinlock +from .. import ChipFlowError, _ensure_chipflow_root +from ._signatures import ( + I2CSignature, GPIOSignature, UARTSignature, SPISignature, QSPIFlashSignature, + SIM_ANNOTATION_SCHEMA, SIM_DATA_SCHEMA, SimInterface + ) +from ._utils import load_pinlock, Interface -__all__ = ["SimPlatform"] logger = logging.getLogger(__name__) +__all__ = ["SimPlatform", "BasicCxxBuilder"] -class SimPlatform: +class SimModelCapability(StrEnum): + LOAD_DATA = "load-data" + + +@dataclass +class SimModel: + """ + Description of a model available from a Builder + Attributes: + name: the model name + signature: the wiring connection of the model. This is also used to match with interfaces. + capabilities: List of capabilities of the model. + """ + name: str + signature: Type[wiring.Signature] + capabilities: Optional[List[SimModelCapability]] = None + + def __post_init__(self): + if not hasattr(self.signature, '__chipflow_uid__'): + raise ChipFlowError(f"Signature {self.signature} must be decorated with `sim_annotate()` to use as a simulation model identifier") + + +def cxxrtlmangle(name, ispublic=True): + # RTLIL allows any characters in names other than whitespace. This presents an issue for generating C++ code + # because C++ identifiers may be only alphanumeric, cannot clash with C++ keywords, and cannot clash with cxxrtl + # identifiers. This issue can be solved with a name mangling scheme. We choose a name mangling scheme that results + # in readable identifiers, does not depend on an up-to-date list of C++ keywords, and is easy to apply. Its rules: + # 1. All generated identifiers start with `_`. + # 1a. Generated identifiers for public names (beginning with `\`) start with `p_`. + # 1b. Generated identifiers for internal names (beginning with `$`) start with `i_`. + # 2. An underscore is escaped with another underscore, i.e. `__`. + # 3. Any other non-alnum character is escaped with underscores around its lowercase hex code, e.g. `@` as `_40_`. + out = '' + if name.startswith('\\'): + out = 'p_' + name = name[1:] + elif name.startswith('$'): + out = 'i_' + name = name[1:] + elif ispublic: + out = 'p_' + for c in name: + if c.isalnum(): + out += c + elif c == '_': + out += '__' + else: + out += f'_{ord(c):x}_' + return out + + +class BasicCxxBuilder(BaseModel): + """ + Represents an object built from C++, where the compilation is simply done with a collection of + cpp and hpp files, simply compiled and linked together with no dependencies + + Assumes model name corresponds to the c++ class name and that the class constructors take + a name followed by the wires of the interface. + + Attributes: + cpp_files: C++ files used to define the model + hpp_files: C++ header files to define the model interfaces + """ + models: List[SimModel] + cpp_files: List[Path] + hpp_files: Optional[List[Path]] = None + hpp_dirs: Optional[List[Path]] = None + + def model_post_init(self, *args, **kwargs): + self._table = { getattr(m.signature,'__chipflow_uid__'): m for m in self.models } + + def uid_to_c(self, uid: str) -> str: + return uid.replace('.','__') + + def instantiate_model(self, interface: str, sim_interface: SimInterface, interface_desc: Interface, ports: Dict[str, io.SimulationPort]) -> str: + uid = sim_interface['uid'] + parameters = sim_interface['parameters'] + assert uid in self._table + + model = self._table[uid] + sig = model.signature(**dict(parameters)) + members = list(sig.flatten(sig.create())) + + sig_names = [ path for path, _, _ in members ] + port_names = { n: interface_desc[n].port_name for n in interface_desc.keys()} + + identifier_uid = self.uid_to_c(uid) + names = [f"\\io${port_names[str(n)]}${d}" for n,d in sig_names] + params = [f"top.{cxxrtlmangle(n)}" for n in names] + cpp_class = model.name + if len(parameters): + template_params = [] + for p,v in parameters: + template_params.append(f"{v}") + cpp_class = cpp_class + '<' + ', '.join(template_params) + '>' + out = f"{cpp_class} {interface}(\"{interface}\", " + out += ', '.join(list(params)) + out += ')\n' + return out + +def find_builder(builders: List[BasicCxxBuilder], sim_interface: SimInterface): + uid = sim_interface['uid'] + for b in builders: + if uid in b._table: + return b + logger.warn(f"Unable to find builder for '{uid}'") + return None + + +_COMMON_BUILDER = BasicCxxBuilder( + models=[ + SimModel('spi_model', SPISignature), + SimModel('spiflash_model', QSPIFlashSignature, [SimModelCapability.LOAD_DATA]), + SimModel('uart_model', UARTSignature), + SimModel('i2c_model', I2CSignature), + SimModel('gpio_model', GPIOSignature), + ], + cpp_files=[ Path('{COMMON_DIR}', 'models.cc') ], + hpp_files=[ Path('models.h') ], + hpp_dirs=[Path("{COMMON_DIR}")], + ) + + +class SimPlatform: def __init__(self, config): self.build_dir = os.path.join(os.environ['CHIPFLOW_ROOT'], 'build', 'sim') self.extra_files = dict() self.sim_boxes = dict() - self._ports = {} + self._ports: Dict[str, io.SimulationPort] = {} self._config = config + self._clocks = {} + self._resets = {} + self._builders: List[BasicCxxBuilder] = [ _COMMON_BUILDER ] + self._top_sim = {} + self._sim_data = {} def add_file(self, filename, content): if not isinstance(content, (str, bytes)): content = content.read() self.extra_files[filename] = content - def build(self, e): + def build(self, e, top): Path(self.build_dir).mkdir(parents=True, exist_ok=True) ports = [] @@ -44,7 +183,7 @@ def build(self, e): if port.direction is io.Direction.Bidir: ports.append((f"io${port_name}$oe", port.oe, PortDirection.Output)) - print("elaborating design") + print("Generating RTLIL from design") output = rtlil.convert(e, name="sim_top", ports=ports, platform=self) top_rtlil = Path(self.build_dir) / "sim_soc.il" @@ -67,6 +206,44 @@ def build(self, e): print("read_rtlil sim_soc.il", file=yosys_file) print("hierarchy -top sim_top", file=yosys_file) print("write_cxxrtl -header sim_soc.cc", file=yosys_file) + main = Path(self.build_dir) / "main.cc" + + metadata = {} + for key in top.keys(): + metadata[key] = getattr(e.submodules, key).metadata.as_json() + for component, iface in metadata.items(): + for interface, interface_desc in iface['interface']['members'].items(): + annotations = interface_desc['annotations'] + + if SIM_DATA_SCHEMA in annotations: + self._sim_data[interface] = annotations[SIM_DATA_SCHEMA] + + data_load = [] + for i,d in self._sim_data.items(): + args = [f"0x{d['offset']:X}U"] + p = Path(d['file_name']) + if not p.is_absolute(): + p = _ensure_chipflow_root() / p + data_load.append({'model_name': i, 'file_name': p, 'args': args}) + + + env = Environment( + loader=PackageLoader("chipflow_lib", "common/sim"), + autoescape=select_autoescape() + ) + template = env.get_template("main.cc.jinja") + + with main.open("w") as main_file: + print(template.render( + includes = [hpp for b in self._builders if b.hpp_files for hpp in b.hpp_files ], + initialisers = [exp for exp in self._top_sim.values()], + interfaces = [exp for exp in self._top_sim.keys()], + clocks = [cxxrtlmangle(f"io${clk}$i") for clk in self._clocks.keys()], + resets = [cxxrtlmangle(f"io${rst}$i") for rst in self._resets.keys()], + data_load = data_load + ), + file=main_file) + def instantiate_ports(self, m: Module): if hasattr(self, "_pinlock"): @@ -74,11 +251,20 @@ def instantiate_ports(self, m: Module): pinlock = load_pinlock() for component, iface in pinlock.port_map.ports.items(): - for k, v in iface.items(): - for name, port_desc in v.items(): - logger.debug(f"Instantiating port {port_desc.port_name}: {port_desc}") - invert = port_desc.invert if port_desc.invert else False - self._ports[port_desc.port_name] = io.SimulationPort(port_desc.direction, port_desc.width, invert=invert, name=port_desc.port_name) + for interface, interface_desc in iface.items(): + for name, port_desc in interface_desc.items(): + logger.debug(f"Instantiating port {port_desc.port_name}: {port_desc}") + invert = port_desc.invert if port_desc.invert else False + self._ports[port_desc.port_name] = io.SimulationPort(port_desc.direction, port_desc.width, invert=invert, name=port_desc.port_name) + if component.startswith('_'): + continue + annotations = pinlock.metadata[component]['interface']['members'][interface]['annotations'] + + if SIM_ANNOTATION_SCHEMA in annotations: + sim_interface = annotations[SIM_ANNOTATION_SCHEMA] + builder = find_builder(self._builders, sim_interface) + if builder: + self._top_sim[interface] = builder.instantiate_model(interface, sim_interface, interface_desc, self._ports) for clock in pinlock.port_map.get_clocks(): assert 'clock_domain' in clock.iomodel @@ -88,6 +274,7 @@ def instantiate_ports(self, m: Module): clk_buffer = io.Buffer(clock.direction, self._ports[clock.port_name]) setattr(m.submodules, "clk_buffer_" + clock.port_name, clk_buffer) m.d.comb += ClockSignal().eq(clk_buffer.i) # type: ignore[reportAttributeAccessIssue] + self._clocks[clock.port_name] = self._ports[clock.port_name] for reset in pinlock.port_map.get_resets(): assert 'clock_domain' in reset.iomodel @@ -97,6 +284,7 @@ def instantiate_ports(self, m: Module): setattr(m.submodules, reset.port_name, rst_buffer) ffsync = FFSynchronizer(rst_buffer.i, ResetSignal()) # type: ignore[reportAttributeAccessIssue] setattr(m.submodules, reset.port_name + "_sync", ffsync) + self._resets[reset.port_name] = self._ports[reset.port_name] self._pinlock = pinlock @@ -114,7 +302,8 @@ def instantiate_ports(self, m: Module): "name": "build_sim", "actions": [ "{ZIG_CXX} {CXXFLAGS} {INCLUDES} {DEFINES} -o {OUTPUT_DIR}/sim_soc{EXE} " - "{OUTPUT_DIR}/sim_soc.cc {SOURCE_DIR}/main.cc {COMMON_DIR}/models.cc" + "{OUTPUT_DIR}/sim_soc.cc {OUTPUT_DIR}/main.cc " + + " ".join([str(p) for p in _COMMON_BUILDER.cpp_files]) ], "targets": [ "{OUTPUT_DIR}/sim_soc{EXE}" @@ -122,12 +311,10 @@ def instantiate_ports(self, m: Module): "file_dep": [ "{OUTPUT_DIR}/sim_soc.cc", "{OUTPUT_DIR}/sim_soc.h", - "{SOURCE_DIR}/main.cc", - "{COMMON_DIR}/models.cc", - "{COMMON_DIR}/models.h", + "{OUTPUT_DIR}/main.cc", "{COMMON_DIR}/vendor/nlohmann/json.hpp", "{COMMON_DIR}/vendor/cxxrtl/cxxrtl_server.h", - ], + ] + [str(p) for p in _COMMON_BUILDER.cpp_files], } SIM_CXXRTL = { diff --git a/chipflow_lib/software/soft_gen.py b/chipflow_lib/software/soft_gen.py index 310f35ae..891e148a 100644 --- a/chipflow_lib/software/soft_gen.py +++ b/chipflow_lib/software/soft_gen.py @@ -11,7 +11,7 @@ def __init__(self, *, rom_start, rom_size, ram_start, ram_size): self.defines = [] self.periphs = [] self.extra_init = [] - print("initialed SoftwareGenerator") + print("initialised SoftwareGenerator") def generate(self, out_dir): Path(out_dir).mkdir(parents=True, exist_ok=True) diff --git a/chipflow_lib/steps/sim.py b/chipflow_lib/steps/sim.py index 02cba580..b496d875 100644 --- a/chipflow_lib/steps/sim.py +++ b/chipflow_lib/steps/sim.py @@ -4,7 +4,6 @@ from contextlib import contextmanager from pathlib import Path -from pprint import pformat from doit.cmd_base import TaskLoader2, loader from doit.doit_cmd import DoitMain @@ -68,7 +67,6 @@ def load_tasks(self, cmd, pos_args): d[k.format(**self.subs)] = [i.format(**self.subs) for i in v] case _: raise ChipFlowError("Unexpected task definition") - print(f"adding task: {pformat(d)}") task_list.append(dict_to_task(d)) return task_list @@ -78,7 +76,7 @@ def __init__(self, config): self._config = config def build(self, *args): - print("building sim") + print("Building simulation...") m = Module() self._platform.instantiate_ports(m) @@ -95,7 +93,7 @@ def build(self, *args): _wire_up_ports(m, top, self._platform) #FIXME: common source for build dir - self._platform.build(m) + self._platform.build(m, top) with common() as common_dir, source() as source_dir, runtime() as runtime_dir: context = { "COMMON_DIR": common_dir, @@ -107,5 +105,5 @@ def build(self, *args): } for k,v in VARIABLES.items(): context[k] = v.format(**context) - print(f"substituting:\n{pformat(context)}") - DoitMain(ContextTaskLoader(DOIT_CONFIG, TASKS, context)).run(["build_sim"]) + if DoitMain(ContextTaskLoader(DOIT_CONFIG, TASKS, context)).run(["build_sim"]) !=0: + raise ChipFlowError("Failed building simulator") diff --git a/chipflow_lib/steps/software.py b/chipflow_lib/steps/software.py index 5cf8475f..984daefb 100644 --- a/chipflow_lib/steps/software.py +++ b/chipflow_lib/steps/software.py @@ -27,5 +27,5 @@ def doit_build(self): def build(self, *args): "Build the software for your design" - print("building software") + print("Building software...") self.doit_build() From 33cf61b30912928c56dcd43c4e182a05f8cbff43 Mon Sep 17 00:00:00 2001 From: Rob Taylor Date: Mon, 28 Jul 2025 18:23:23 +0100 Subject: [PATCH 3/6] Enable c++ model parameterisation, apply to gpio --- chipflow_lib/common/sim/models.cc | 33 ---------------------- chipflow_lib/common/sim/models.h | 40 +++++++++++++++++++++++---- chipflow_lib/platforms/_signatures.py | 1 - 3 files changed, 35 insertions(+), 39 deletions(-) diff --git a/chipflow_lib/common/sim/models.cc b/chipflow_lib/common/sim/models.cc index d47aad0f..e0ecd721 100644 --- a/chipflow_lib/common/sim/models.cc +++ b/chipflow_lib/common/sim/models.cc @@ -274,39 +274,6 @@ void uart_model::step(unsigned timestamp) { } } -// GPIO - -void gpio_model::step(unsigned timestamp) { - uint32_t o_value = o.get(); - uint32_t oe_value = oe.get(); - - for (auto action : get_pending_actions(name)) { - if (action.event == "set") { - auto bin = std::string(action.payload); - input_data = 0; - for (unsigned i = 0; i < pin_count; i++) { - if (bin.at((pin_count - 1) - i) == '1') - input_data |= (1U << i); - } - } - } - - if (o_value != s.o_last || oe_value != s.oe_last) { - std::string formatted_value; - for (int i = pin_count - 1; i >= 0; i--) { - if (oe_value & (1U << unsigned(i))) - formatted_value += (o_value & (1U << unsigned(i))) ? '1' : '0'; - else - formatted_value += 'Z'; - } - log_event(timestamp, name, "change", json(formatted_value)); - } - - i.set((input_data & ~oe_value) | (o_value & oe_value)); - s.o_last = o_value; - s.oe_last = oe_value; -} - // Generic SPI model void spi_model::step(unsigned timestamp) { for (auto action : get_pending_actions(name)) { diff --git a/chipflow_lib/common/sim/models.h b/chipflow_lib/common/sim/models.h index a33a8775..30bc93e6 100644 --- a/chipflow_lib/common/sim/models.h +++ b/chipflow_lib/common/sim/models.h @@ -82,14 +82,11 @@ struct uart_model { } s; }; +template struct gpio_model { std::string name; - struct parameters { - unsigned pin_count; - }; - parameters parameters; - gpio_model(const std::string &name, parameters, const value &o, const value &oe, value &i) : name(name), parameters(parameters), o(o), oe(oe), i(i) {}; + gpio_model(const std::string &name, const value &o, const value &oe, value &i) : name(name), o(o), oe(oe), i(i) {}; void step(unsigned timestamp); @@ -105,6 +102,39 @@ struct gpio_model { }; +// GPIO +template +void gpio_model::step(unsigned timestamp) { + uint32_t o_value = o.template get(); + uint32_t oe_value = oe.template get(); + + for (auto action : get_pending_actions(name)) { + if (action.event == "set") { + auto bin = std::string(action.payload); + input_data = 0; + for (unsigned i = 0; i < pin_count; i++) { + if (bin.at((pin_count - 1) - i) == '1') + input_data |= (1U << i); + } + } + } + + if (o_value != s.o_last || oe_value != s.oe_last) { + std::string formatted_value; + for (int i = pin_count - 1; i >= 0; i--) { + if (oe_value & (1U << unsigned(i))) + formatted_value += (o_value & (1U << unsigned(i))) ? '1' : '0'; + else + formatted_value += 'Z'; + } + log_event(timestamp, name, "change", json(formatted_value)); + } + + i.set((input_data & ~oe_value) | (o_value & oe_value)); + s.o_last = o_value; + s.oe_last = oe_value; +} + struct spi_model { std::string name; spi_model(const std::string &name, const value<1> &clk, const value<1> &copi, value<1> &cipo, const value<1> &csn) : diff --git a/chipflow_lib/platforms/_signatures.py b/chipflow_lib/platforms/_signatures.py index bc8a241e..fdfda458 100644 --- a/chipflow_lib/platforms/_signatures.py +++ b/chipflow_lib/platforms/_signatures.py @@ -7,7 +7,6 @@ from amaranth.lib import wiring from amaranth.lib.wiring import Out -from .. import ChipFlowError from ._utils import InputIOSignature, OutputIOSignature, BidirIOSignature, IOModelOptions, _chipflow_schema_uri from ._annotate import amaranth_annotate From 3e9212a5ffc5a762a499b79921f3aebc2305124a Mon Sep 17 00:00:00 2001 From: Rob Taylor Date: Wed, 30 Jul 2025 23:07:09 +0100 Subject: [PATCH 4/6] Namespace simulation models --- chipflow_lib/common/sim/main.cc.jinja | 1 + chipflow_lib/common/sim/models.cc | 17 ++++++++------- chipflow_lib/common/sim/models.h | 30 +++++++++++++++------------ chipflow_lib/platforms/_utils.py | 4 ++-- chipflow_lib/platforms/silicon.py | 2 ++ chipflow_lib/platforms/sim.py | 17 +++++++-------- 6 files changed, 39 insertions(+), 32 deletions(-) diff --git a/chipflow_lib/common/sim/main.cc.jinja b/chipflow_lib/common/sim/main.cc.jinja index 1a3b2f35..752e2bf7 100644 --- a/chipflow_lib/common/sim/main.cc.jinja +++ b/chipflow_lib/common/sim/main.cc.jinja @@ -12,6 +12,7 @@ using namespace cxxrtl::time_literals; using namespace cxxrtl_design; +using namespace chipflow; int main(int argc, char **argv) { p_sim__top top; diff --git a/chipflow_lib/common/sim/models.cc b/chipflow_lib/common/sim/models.cc index e0ecd721..213aaeb6 100644 --- a/chipflow_lib/common/sim/models.cc +++ b/chipflow_lib/common/sim/models.cc @@ -9,7 +9,7 @@ #include #include "models.h" -namespace cxxrtl_design { +namespace chipflow { // Helper functions @@ -134,8 +134,10 @@ void close_event_log() { } } +namespace models { + // SPI flash -void spiflash_model::load_data(const std::string &filename, unsigned offset) { +void spiflash::load_data(const std::string &filename, unsigned offset) { std::ifstream in(filename, std::ifstream::binary); if (offset >= data.size()) { throw std::out_of_range("flash: offset beyond end"); @@ -145,7 +147,7 @@ void spiflash_model::load_data(const std::string &filename, unsigned offset) { } in.read(reinterpret_cast(data.data() + offset), (data.size() - offset)); } -void spiflash_model::step(unsigned timestamp) { +void spiflash::step(unsigned timestamp) { auto process_byte = [&]() { s.out_buffer = 0; if (s.byte_count == 0) { @@ -221,7 +223,7 @@ void spiflash_model::step(unsigned timestamp) { // UART -void uart_model::step(unsigned timestamp) { +void uart::step(unsigned timestamp) { for (auto action : get_pending_actions(name)) { if (action.event == "tx") { @@ -275,7 +277,7 @@ void uart_model::step(unsigned timestamp) { } // Generic SPI model -void spi_model::step(unsigned timestamp) { +void spi::step(unsigned timestamp) { for (auto action : get_pending_actions(name)) { if (action.event == "set_data") { s.out_buffer = s.send_data = uint32_t(action.payload); @@ -308,7 +310,7 @@ void spi_model::step(unsigned timestamp) { } // Generic I2C model -void i2c_model::step(unsigned timestamp) { +void i2c::step(unsigned timestamp) { bool sda = !bool(sda_oe), scl = !bool(scl_oe); for (auto action : get_pending_actions(name)) { @@ -370,4 +372,5 @@ void i2c_model::step(unsigned timestamp) { scl_i.set(scl); } -} +} //chipflow::models +} //chipflow diff --git a/chipflow_lib/common/sim/models.h b/chipflow_lib/common/sim/models.h index 30bc93e6..8147bddc 100644 --- a/chipflow_lib/common/sim/models.h +++ b/chipflow_lib/common/sim/models.h @@ -10,7 +10,7 @@ #include "vendor/nlohmann/json.hpp" -namespace cxxrtl_design { +namespace chipflow { using namespace cxxrtl; @@ -30,9 +30,12 @@ void log_event(unsigned timestamp, const std::string &peripheral, const std::str std::vector get_pending_actions(const std::string &peripheral); void close_event_log(); -struct spiflash_model { +namespace models { + + +struct spiflash { std::string name; - spiflash_model(const std::string &name, const value<1> &clk, const value<1> &csn, const value<4> &d_o, const value<4> &d_oe, value<4> &d_i) : + spiflash(const std::string &name, const value<1> &clk, const value<1> &csn, const value<4> &d_o, const value<4> &d_oe, value<4> &d_i) : name(name), clk(clk), csn(csn), d_o(d_o), d_oe(d_oe), d_i(d_i) { data.resize(16*1024*1024); std::fill(data.begin(), data.end(), 0xFF); // flash starting value @@ -61,9 +64,9 @@ struct spiflash_model { } s; }; -struct uart_model { +struct uart { std::string name; - uart_model(const std::string &name, const value<1> &tx, value<1> &rx, unsigned baud_div = 25000000/115200) : name(name), tx(tx), rx(rx), baud_div(baud_div) {}; + uart(const std::string &name, const value<1> &tx, value<1> &rx, unsigned baud_div = 25000000/115200) : name(name), tx(tx), rx(rx), baud_div(baud_div) {}; void step(unsigned timestamp); private: @@ -83,10 +86,10 @@ struct uart_model { }; template -struct gpio_model { +struct gpio { std::string name; - gpio_model(const std::string &name, const value &o, const value &oe, value &i) : name(name), o(o), oe(oe), i(i) {}; + gpio(const std::string &name, const value &o, const value &oe, value &i) : name(name), o(o), oe(oe), i(i) {}; void step(unsigned timestamp); @@ -104,7 +107,7 @@ struct gpio_model { // GPIO template -void gpio_model::step(unsigned timestamp) { +void gpio::step(unsigned timestamp) { uint32_t o_value = o.template get(); uint32_t oe_value = oe.template get(); @@ -135,9 +138,9 @@ void gpio_model::step(unsigned timestamp) { s.oe_last = oe_value; } -struct spi_model { +struct spi { std::string name; - spi_model(const std::string &name, const value<1> &clk, const value<1> &copi, value<1> &cipo, const value<1> &csn) : + spi(const std::string &name, const value<1> &clk, const value<1> &copi, value<1> &cipo, const value<1> &csn) : name(name), clk(clk), csn(csn), copi(copi), cipo(cipo) { }; @@ -159,9 +162,9 @@ struct spi_model { } s; }; -struct i2c_model { +struct i2c { std::string name; - 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) {}; + i2c(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) {}; void step(unsigned timestamp); private: @@ -184,6 +187,7 @@ struct i2c_model { }; -} +} //chipflow::simulation +} //chipflow #endif diff --git a/chipflow_lib/platforms/_utils.py b/chipflow_lib/platforms/_utils.py index 1d61321f..a1fd54d9 100644 --- a/chipflow_lib/platforms/_utils.py +++ b/chipflow_lib/platforms/_utils.py @@ -21,10 +21,10 @@ ) -from amaranth.lib import wiring, io, meta +from amaranth.lib import wiring, io from amaranth.lib.wiring import In, Out from pydantic import ( - ConfigDict, TypeAdapter, PlainSerializer, + ConfigDict, PlainSerializer, WrapValidator ) diff --git a/chipflow_lib/platforms/silicon.py b/chipflow_lib/platforms/silicon.py index b2261e67..d154f580 100644 --- a/chipflow_lib/platforms/silicon.py +++ b/chipflow_lib/platforms/silicon.py @@ -303,6 +303,8 @@ def instantiate_toplevel(self): def wire_up(self, m, wire): super().wire_up(m, wire) + # wire up drive mode bits + if hasattr(wire, 'drive_mode'): m.d.comb += self.drive_mode.eq(wire.drive_mode) diff --git a/chipflow_lib/platforms/sim.py b/chipflow_lib/platforms/sim.py index f6856378..0a23727c 100644 --- a/chipflow_lib/platforms/sim.py +++ b/chipflow_lib/platforms/sim.py @@ -43,6 +43,7 @@ class SimModel: capabilities: List of capabilities of the model. """ name: str + namespace: str signature: Type[wiring.Signature] capabilities: Optional[List[SimModelCapability]] = None @@ -100,9 +101,6 @@ class BasicCxxBuilder(BaseModel): def model_post_init(self, *args, **kwargs): self._table = { getattr(m.signature,'__chipflow_uid__'): m for m in self.models } - def uid_to_c(self, uid: str) -> str: - return uid.replace('.','__') - def instantiate_model(self, interface: str, sim_interface: SimInterface, interface_desc: Interface, ports: Dict[str, io.SimulationPort]) -> str: uid = sim_interface['uid'] parameters = sim_interface['parameters'] @@ -115,11 +113,10 @@ def instantiate_model(self, interface: str, sim_interface: SimInterface, interfa sig_names = [ path for path, _, _ in members ] port_names = { n: interface_desc[n].port_name for n in interface_desc.keys()} - identifier_uid = self.uid_to_c(uid) names = [f"\\io${port_names[str(n)]}${d}" for n,d in sig_names] params = [f"top.{cxxrtlmangle(n)}" for n in names] - cpp_class = model.name + cpp_class = f"{model.namespace}::{model.name}" if len(parameters): template_params = [] for p,v in parameters: @@ -141,11 +138,11 @@ def find_builder(builders: List[BasicCxxBuilder], sim_interface: SimInterface): _COMMON_BUILDER = BasicCxxBuilder( models=[ - SimModel('spi_model', SPISignature), - SimModel('spiflash_model', QSPIFlashSignature, [SimModelCapability.LOAD_DATA]), - SimModel('uart_model', UARTSignature), - SimModel('i2c_model', I2CSignature), - SimModel('gpio_model', GPIOSignature), + SimModel('spi', 'chipflow::models', SPISignature), + SimModel('spiflash', 'chipflow::models', QSPIFlashSignature, [SimModelCapability.LOAD_DATA]), + SimModel('uart', 'chipflow::models', UARTSignature), + SimModel('i2c', 'chipflow::models', I2CSignature), + SimModel('gpio', 'chipflow::models', GPIOSignature), ], cpp_files=[ Path('{COMMON_DIR}', 'models.cc') ], hpp_files=[ Path('models.h') ], From 6ce045f14d3adf361400e5b031923be821778549 Mon Sep 17 00:00:00 2001 From: Rob Taylor Date: Mon, 28 Jul 2025 18:42:18 +0100 Subject: [PATCH 5/6] gitignore docs/autoapi --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2376557b..26a4d23b 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,5 @@ __pycache__/ .doit.db docs/_build +docs/autoapi .cache From 2eb44e9b842d2daa72af4482a98198864adf16e3 Mon Sep 17 00:00:00 2001 From: Rob Taylor Date: Thu, 7 Aug 2025 12:02:48 +0100 Subject: [PATCH 6/6] Allow configuration of number of simulation steps --- chipflow_lib/common/sim/main.cc.jinja | 2 +- chipflow_lib/config_models.py | 3 +++ chipflow_lib/platforms/sim.py | 3 ++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/chipflow_lib/common/sim/main.cc.jinja b/chipflow_lib/common/sim/main.cc.jinja index 752e2bf7..cd72b861 100644 --- a/chipflow_lib/common/sim/main.cc.jinja +++ b/chipflow_lib/common/sim/main.cc.jinja @@ -70,7 +70,7 @@ int main(int argc, char **argv) { top.{{reset}}.set(true); {% endfor %} - for (int i = 0; i < 3000000; i++) + for (int i = 0; i < {{num_steps}}; i++) tick(); close_event_log(); diff --git a/chipflow_lib/config_models.py b/chipflow_lib/config_models.py index 8c1cd417..bbfc1f21 100644 --- a/chipflow_lib/config_models.py +++ b/chipflow_lib/config_models.py @@ -19,6 +19,8 @@ class SiliconConfig(BaseModel): debug: Optional[Dict[str, bool]] = None # This is still kept around to allow forcing pad locations. +class SimulationConfig(BaseModel): + num_steps: int = 3000000 class ChipFlowConfig(BaseModel): """Root configuration for chipflow.toml.""" @@ -26,6 +28,7 @@ class ChipFlowConfig(BaseModel): top: Dict[str, Any] = {} steps: Optional[Dict[str, str]] = None silicon: Optional[SiliconConfig] = None + simulation: SimulationConfig = SimulationConfig() clock_domains: Optional[List[str]] = None diff --git a/chipflow_lib/platforms/sim.py b/chipflow_lib/platforms/sim.py index 0a23727c..16892fb5 100644 --- a/chipflow_lib/platforms/sim.py +++ b/chipflow_lib/platforms/sim.py @@ -237,7 +237,8 @@ def build(self, e, top): interfaces = [exp for exp in self._top_sim.keys()], clocks = [cxxrtlmangle(f"io${clk}$i") for clk in self._clocks.keys()], resets = [cxxrtlmangle(f"io${rst}$i") for rst in self._resets.keys()], - data_load = data_load + data_load = data_load, + num_steps = self._config.chipflow.simulation.num_steps, ), file=main_file)