diff --git a/.gitignore b/.gitignore index 0805338..6135d38 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,8 @@ __pycache__/ /.pdm-build /.venv /pdm.lock +/overrides.txt + +# simulation +*.gtkw +*.vcd diff --git a/chipflow_digital_ip/io/_glasgow_i2c.py b/chipflow_digital_ip/io/_glasgow_i2c.py index c1419ef..8fd746f 100644 --- a/chipflow_digital_ip/io/_glasgow_i2c.py +++ b/chipflow_digital_ip/io/_glasgow_i2c.py @@ -1,4 +1,4 @@ -from amaranth import * +from amaranth import * from amaranth.lib.cdc import FFSynchronizer diff --git a/chipflow_digital_ip/io/_glasgow_iostream.py b/chipflow_digital_ip/io/_glasgow_iostream.py index cd3c4a3..cca7561 100644 --- a/chipflow_digital_ip/io/_glasgow_iostream.py +++ b/chipflow_digital_ip/io/_glasgow_iostream.py @@ -1,9 +1,13 @@ -from amaranth import * +from amaranth import * from amaranth.lib import data, wiring, stream, io from amaranth.lib.wiring import In, Out +from amaranth_types.types import ShapeLike + + __all__ = ["IOStreamer", "PortGroup"] + class PortGroup: """Group of Amaranth library I/O ports. @@ -118,7 +122,7 @@ class IOStreamer(wiring.Component): """ @staticmethod - def o_stream_signature(ioshape, /, *, ratio=1, meta_layout=0): + def o_stream_signature(ioshape, /, *, ratio=1, meta_layout: ShapeLike = 0): return stream.Signature(data.StructLayout({ "port": _map_ioshape("o", ioshape, lambda width: data.StructLayout({ "o": width if ratio == 1 else data.ArrayLayout(width, ratio), @@ -129,7 +133,7 @@ def o_stream_signature(ioshape, /, *, ratio=1, meta_layout=0): })) @staticmethod - def i_stream_signature(ioshape, /, *, ratio=1, meta_layout=0): + def i_stream_signature(ioshape, /, *, ratio=1, meta_layout: ShapeLike = 0): return stream.Signature(data.StructLayout({ "port": _map_ioshape("i", ioshape, lambda width: data.StructLayout({ "i": width if ratio == 1 else data.ArrayLayout(width, ratio), @@ -137,8 +141,8 @@ def i_stream_signature(ioshape, /, *, ratio=1, meta_layout=0): "meta": meta_layout, })) - def __init__(self, ioshape, ports, /, *, ratio=1, init=None, meta_layout=0): - assert isinstance(ioshape, (int, dict)) + def __init__(self, ioshape: dict, ports, /, *, ratio=1, init=None, meta_layout: ShapeLike = 0): + assert isinstance(ioshape, dict) assert ratio in (1, 2) self._ioshape = ioshape @@ -161,7 +165,7 @@ def elaborate(self, platform): buffer_cls, latency = SimulatableDDRBuffer, 3 if isinstance(self._ports, io.PortLike): - m.submodules.buffer = buffer = buffer_cls("io", self._ports) + m.submodules.buffer = buffer = buffer_cls(io.Direction.Bidir, self._ports) if isinstance(self._ports, PortGroup): buffer = {} for name, sub_port in self._ports.__dict__.items(): @@ -231,7 +235,7 @@ def delay(value, name): class IOClocker(wiring.Component): @staticmethod - def i_stream_signature(ioshape, /, *, _ratio=1, meta_layout=0): + def i_stream_signature(ioshape, /, *, _ratio=1, meta_layout: ShapeLike = 0): # Currently the only supported ratio is 1, but this will change in the future for # interfaces like HyperBus. return stream.Signature(data.StructLayout({ @@ -245,10 +249,10 @@ def i_stream_signature(ioshape, /, *, _ratio=1, meta_layout=0): })) @staticmethod - def o_stream_signature(ioshape, /, *, ratio=1, meta_layout=0): + def o_stream_signature(ioshape, /, *, ratio=1, meta_layout: ShapeLike = 0): return IOStreamer.o_stream_signature(ioshape, ratio=ratio, meta_layout=meta_layout) - def __init__(self, ioshape, *, clock, o_ratio=1, meta_layout=0, divisor_width=16): + def __init__(self, ioshape, *, clock, o_ratio=1, meta_layout: ShapeLike = 0, divisor_width=16): assert isinstance(ioshape, dict) assert isinstance(clock, str) assert o_ratio in (1, 2) diff --git a/chipflow_digital_ip/io/_gpio.py b/chipflow_digital_ip/io/_gpio.py index 79f0f22..ba5d056 100644 --- a/chipflow_digital_ip/io/_gpio.py +++ b/chipflow_digital_ip/io/_gpio.py @@ -1,4 +1,3 @@ - from amaranth import Module, unsigned from amaranth.lib import wiring from amaranth.lib.wiring import In, Out, flipped, connect diff --git a/chipflow_digital_ip/io/_rfc_uart.py b/chipflow_digital_ip/io/_rfc_uart.py index e08f13e..337d8a8 100644 --- a/chipflow_digital_ip/io/_rfc_uart.py +++ b/chipflow_digital_ip/io/_rfc_uart.py @@ -2,17 +2,26 @@ The Amaranth SoC RFC UART from https://github.com/ChipFlow/chipflow-digital-ip """ +from typing import Generic, TypeVar + from amaranth import * from amaranth.lib import stream, wiring from amaranth.lib.wiring import In, Out, flipped, connect +from amaranth.hdl import ValueCastable + +from amaranth_types.types import HasElaborate, ShapeLike, ValueLike from amaranth_soc import csr __all__ = ["RxPhySignature", "TxPhySignature", "RxPeripheral", "TxPeripheral", "Peripheral"] +_T_ValueOrValueCastable = TypeVar("_T_ValueOrValueCastable", bound=Value | ValueCastable, covariant=True) +_T_ShapeLike = TypeVar("_T_ShapeLike", bound=ShapeLike, covariant=True) +_T_Symbol_ShapeLike = TypeVar("_T_Symbol_ShapeLike", bound=ShapeLike, covariant=True) + -class RxPhySignature(wiring.Signature): +class RxPhySignature(wiring.Signature, Generic[_T_ShapeLike, _T_Symbol_ShapeLike]): """Receiver PHY signature. Parameters @@ -38,7 +47,7 @@ class RxPhySignature(wiring.Signature): Receiver error flag. Pulsed for one clock cycle in case of an implementation-specific error (e.g. wrong parity bit). """ - def __init__(self, phy_config_shape, symbol_shape): + def __init__(self, phy_config_shape: _T_ShapeLike, symbol_shape: _T_Symbol_ShapeLike): super().__init__({ "rst": Out(1), "config": Out(phy_config_shape), @@ -48,7 +57,7 @@ def __init__(self, phy_config_shape, symbol_shape): }) -class TxPhySignature(wiring.Signature): +class TxPhySignature(wiring.Signature, Generic[_T_ShapeLike, _T_Symbol_ShapeLike]): """Transmitter PHY signature. Parameters @@ -68,7 +77,7 @@ class TxPhySignature(wiring.Signature): symbols : :py:`Out(stream.Signature(symbol_shape))` Symbol stream. The shape of its payload is given by the `symbol_shape` parameter. """ - def __init__(self, phy_config_shape, symbol_shape): + def __init__(self, phy_config_shape: _T_ShapeLike, symbol_shape: _T_Symbol_ShapeLike): super().__init__({ "rst": Out(1), "config": Out(phy_config_shape), @@ -98,7 +107,7 @@ def elaborate(self, platform): return m -class RxPeripheral(wiring.Component): +class RxPeripheral(wiring.Component, Generic[_T_ShapeLike, _T_ValueOrValueCastable, _T_Symbol_ShapeLike]): class Config(csr.Register, access="rw"): """Peripheral configuration register. @@ -141,7 +150,7 @@ class PhyConfig(csr.Register, access="rw"): phy_config_init : :class:`int` Initial value of the PHY configuration word. """ - def __init__(self, phy_config_shape, phy_config_init): + def __init__(self, phy_config_shape: _T_ShapeLike, phy_config_init: _T_ValueOrValueCastable): super().__init__(csr.Field(_PhyConfigFieldAction, phy_config_shape, init=phy_config_init)) @@ -199,7 +208,7 @@ class Data(csr.Register, access="r"): symbol_shape : :ref:`shape-like ` Shape of a symbol. """ - def __init__(self, symbol_shape): + def __init__(self, symbol_shape: _T_Symbol_ShapeLike): super().__init__(csr.Field(csr.action.R, symbol_shape)) """UART receiver peripheral. @@ -224,8 +233,8 @@ def __init__(self, symbol_shape): phy : :py:`Out(RxPhySignature(phy_config_shape, symbol_shape))` Interface between the peripheral and its PHY. """ - def __init__(self, *, addr_width, data_width, phy_config_shape=unsigned(16), - phy_config_init=0, symbol_shape=unsigned(8)): + def __init__(self, *, addr_width, data_width, phy_config_shape:_T_ShapeLike = unsigned(16), + phy_config_init: _T_ValueOrValueCastable = Value.cast(0), symbol_shape: _T_Symbol_ShapeLike = unsigned(8)): regs = csr.Builder(addr_width=addr_width, data_width=data_width) self._config = regs.add("Config", self.Config()) @@ -298,7 +307,7 @@ def elaborate(self, platform): return m -class TxPeripheral(wiring.Component): +class TxPeripheral(wiring.Component, Generic[_T_ShapeLike, _T_Symbol_ShapeLike, _T_ValueOrValueCastable]): class Config(csr.Register, access="rw"): """Peripheral configuration register. @@ -341,7 +350,7 @@ class PhyConfig(csr.Register, access="rw"): phy_config_init : :class:`int` Initial value of the PHY configuration word. """ - def __init__(self, phy_config_shape, phy_config_init): + def __init__(self, phy_config_shape: _T_ShapeLike, phy_config_init: _T_ValueOrValueCastable): super().__init__(csr.Field(_PhyConfigFieldAction, phy_config_shape, init=phy_config_init)) @@ -391,7 +400,7 @@ class Data(csr.Register, access="w"): symbol_shape : :ref:`shape-like ` Shape of a symbol. """ - def __init__(self, symbol_shape): + def __init__(self, symbol_shape: _T_Symbol_ShapeLike): super().__init__(csr.Field(csr.action.W, symbol_shape)) """UART transmitter peripheral. @@ -416,8 +425,8 @@ def __init__(self, symbol_shape): phy : :py:`Out(TxPhySignature(phy_config_shape, symbol_shape))` Interface between the peripheral and its PHY. """ - def __init__(self, *, addr_width, data_width=8, phy_config_shape=unsigned(16), - phy_config_init=0, symbol_shape=unsigned(8)): + def __init__(self, *, addr_width, data_width=8, phy_config_shape: _T_ShapeLike = unsigned(16), + phy_config_init: _T_ValueOrValueCastable = Value.cast(0), symbol_shape: _T_Symbol_ShapeLike = unsigned(8)): regs = csr.Builder(addr_width=addr_width, data_width=data_width) self._config = regs.add("Config", self.Config()) @@ -487,7 +496,7 @@ def elaborate(self, platform): return m -class Peripheral(wiring.Component): +class Peripheral(wiring.Component, Generic[_T_ShapeLike, _T_Symbol_ShapeLike, _T_ValueOrValueCastable]): """UART transceiver peripheral. This peripheral is composed of two subordinate peripherals. A :class:`RxPeripheral` occupies @@ -522,8 +531,8 @@ class Peripheral(wiring.Component): :exc:`TypeError` If ``addr_width`` is not a positive integer. """ - def __init__(self, *, addr_width, data_width=8, phy_config_shape=unsigned(16), - phy_config_init=0, symbol_shape=unsigned(8)): + def __init__(self, *, addr_width, data_width=8, phy_config_shape: _T_ShapeLike = unsigned(16), + phy_config_init: _T_ValueOrValueCastable = Value.cast(0), symbol_shape: _T_Symbol_ShapeLike = unsigned(8)): if not isinstance(addr_width, int) or addr_width <= 0: raise TypeError(f"Address width must be a positive integer, not {addr_width!r}") diff --git a/chipflow_digital_ip/io/_spi.py b/chipflow_digital_ip/io/_spi.py index e0c890a..2cc20b3 100644 --- a/chipflow_digital_ip/io/_spi.py +++ b/chipflow_digital_ip/io/_spi.py @@ -1,4 +1,4 @@ -from amaranth import Module, Signal, Cat, C, unsigned +from amaranth import * from amaranth.lib import wiring from amaranth.lib.wiring import In, Out, connect, flipped diff --git a/chipflow_digital_ip/io/_svtest.py b/chipflow_digital_ip/io/_svtest.py new file mode 100644 index 0000000..5e23108 --- /dev/null +++ b/chipflow_digital_ip/io/_svtest.py @@ -0,0 +1,124 @@ +import os +import sys +import tomli + +from enum import StrEnum, auto +from pathlib import Path +from typing import Dict, Optional, List, Literal, Self + +from amaranth.lib import wiring + +from pydantic import ( + BaseModel, ImportString, JsonValue, ValidationError, + model_validator + ) + +from chipflow import ChipFlowError + + +class Files(BaseModel): + module: Optional[ImportString] = None + path: Optional[Path] = None + + @model_validator(mode="after") + def verify_module_or_path(self) -> Self: + print(self.module) + print(self.path) + if (self.module and self.path) or (not self.module and not self.path): + raise ValueError("You must set `module` or `path`.") + return self + + +class GenerateSpinalHDL(BaseModel): + + scala_class: str + options: List[str] = [] + + def generate(self, source_path: Path, dest_path: Path, name: str, parameters: Dict[str, JsonValue]): + gen_args = [o.format(**parameters) for o in self.options] + path = source_path / "ext" / "SpinalHDL" + args=" ".join(gen_args + [f'--netlist-directory={dest_path.absolute()}', f'--netlist-name={name}']) + cmd = f'cd {path} && sbt -J--enable-native-access=ALL-UNNAMED -v "lib/runMain {self.scala_class} {args}"' + os.environ["GRADLE_OPTS"] = "--enable-native-access=ALL-UNNAMED" + print("!!! " + cmd) + if os.system(cmd) != 0: + raise OSError('Failed to run sbt') + return [f'{name}.v'] + + +class Generators(StrEnum): + SPINALHDL = auto() + VERILOG = auto() + + +class Generate(BaseModel): + parameters: Optional[Dict[str, JsonValue]] = None + generator: Generators + spinalhdl: Optional[GenerateSpinalHDL] = None + + +class Port(BaseModel): + interface: str # ImportString + params: Optional[Dict[str, JsonValue]] = None + vars: Optional[Dict[str, Literal["int"]]] = None + map: str | Dict[str, Dict[str, str] | str] + + +class ExternalWrap(BaseModel): + name: str + files: Files + generate: Optional[Generate] = None + clocks: Dict[str, str] = {} + resets: Dict[str, str] = {} + ports: Dict[str,Port] = {} + pins: Dict[str, Port] = {} + + +if __name__ == "__main__": + with open(sys.argv[1], "rb") as f: + wrapper = tomli.load(f) + + try: + # Validate with Pydantic + wrap = ExternalWrap.model_validate(wrapper) # Valiate + print(wrap) + + source = Path() + if wrap.files.module: + source = Path(wrap.files.module.data_location) + elif wrap.files.path: + source = wrap.files.path + else: + assert True + + if wrap.generate: + dest = Path("./build/verilog") + dest.mkdir(parents=True, exist_ok=True) + files = getattr(wrap.generate, wrap.generate.generator.value).generate(source, Path(dest), wrap.name, wrap.generate.parameters) + print(f'Generated files: {files}') + + def init(self, **kwargs): + for name, value in kwargs.items(): + setattr(self, name, value) + + attr = { + '__init__': init + } + _class = type(wrap.name, (wiring.Component,), attr) + + + + + except ValidationError as e: + # Format Pydantic validation errors in a user-friendly way + error_messages = [] + for error in e.errors(): + location = ".".join(str(loc) for loc in error["loc"]) + message = error["msg"] + error_messages.append(f"Error at '{location}': {message}") + + error_str = "\n".join(error_messages) + raise ChipFlowError(f"Validation error in chipflow.toml:\n{error_str}") + + + diff --git a/chipflow_digital_ip/io/usb_ohci.toml b/chipflow_digital_ip/io/usb_ohci.toml new file mode 100644 index 0000000..65b9ff3 --- /dev/null +++ b/chipflow_digital_ip/io/usb_ohci.toml @@ -0,0 +1,99 @@ +name = 'UsbOhciPeripheral' + +[files] +module = 'pythondata_misc_usb_ohci' + +[generate] +parameters.nusb = 1 +parameters.dma_data_width = 32 +parameters.usb_clk_freq = 48e6 + +generator = 'spinalhdl' + +[generate.spinalhdl] +options = [ '--port-count {nusb}', + '--phy-frequency {usb_clk_freq:.0f}', + '--dma-width {dma_data_width}', + ] +scala_class = 'spinal.lib.com.usb.ohci.UsbOhciWishbone' + +[clocks] +usb = 'i_phy_clk' +sys = 'i_ctrl_clk' + +[resets] +usb = 'i_phy_reset' +sys = 'i_ctrl_reset' + +[ports.wb_ctl] +interface = 'amaranth_soc.wishbone.Interface' + +[ports.wb_ctl.params] +data_width=32 +address_width=32 +addressing='word' + +[ports.wb_ctl.map] +cyc = 'i_io_ctrl_CYC' +stb = 'i_io_ctrl_STB' +ack = 'o_io_ctrl_ACK' +we = 'i_io_ctrl_WE' +adr = 'i_io_ctrl_ADR' +dat.r = 'o_io_ctrl_DAT_MISO' +dat.w = 'i_io_ctrl_DAT_MOSI' +sel = 'i_io_ctrl_SEL' + +[ports.wb_dma] +interface = 'amaranth_soc.wishbone.Interface' + +[ports.wb_dma.params] +data_width=32 +address_width='{dma_data_width}' +addressing='word' + +[ports.wb_dma.map] +cyc = 'o_io_dma_CYC' +stb = 'o_io_dma_STB' +ack = 'i_io_dma_ACK' +we = 'o_io_dma_WE' +adr = 'o_io_dma_ADR' +dat.r = 'i_io_dma_DAT_MISO' +dat.w = 'o_io_dma_DAT_MOSI' +sel = 'o_io_dma_SEL' +err = 'i_io_dma_ERR' +cti = 'o_io_dma_CTI' +bte = 'o_io_dma_BTE' + +[ports.interrupt] +interface = 'amaranth.lib.wiring.Out(1)' +map = 'o_io_interrupt' + +[pins.usb] +interface = 'chipflow_lib.platforms.I2CSignature' + +[pins.usb.vars] +n = 'int' + +[pins.usb.map] +dp.i = 'i_io_usb_{n}_dp_read' +dp.o = 'o_io_usb_{n}_dp_write' +dp.oe = 'o_io_usb_{n}_dp_writeEnable' +dm.i = 'i_io_usb_{n}_dm_read' +dm.o = 'o_io_usb_{n}_dm_write' +dm.oe = 'o_io_usb_{n}_dm_writeEnable' + +[drivers.uboot] +CONFIG_USB_OHCI_NEW = true +CONFIG_SYS_USB_OHCI_REGS_BASE = "{regs_base}" + +[drivers.zephyr] +CONFIG_UHC_NXP_OHCI = true + +[drivers.dtsi] +# reg, interrupts, enabled automatically added +compatible = "nxp,uhc-ohci" +maximum-speed = "full-speed" + +[drivers.raw] +c_files = ['drivers/ohci_generic.c', 'drivers/ohci_hcd.c'] +h_files = ['ohci.h'] diff --git a/chipflow_digital_ip/processors/__init__.py b/chipflow_digital_ip/processors/__init__.py index e540e0e..9312d33 100644 --- a/chipflow_digital_ip/processors/__init__.py +++ b/chipflow_digital_ip/processors/__init__.py @@ -1,3 +1,6 @@ from ._openhw.cv32e40p import CV32E40P, OBIDebugModule +from minerva.core import Minerva -__all__ = ['CV32E40P', 'OBIDebugModule'] +__all__ = ['CV32E40P', 'OBIDebugModule', + 'Minerva' + ] diff --git a/pyproject.toml b/pyproject.toml index a4912c9..5b220bc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,9 +16,11 @@ license-files = [ requires-python = ">=3.11" dependencies = [ "amaranth>=0.5,<0.6", - "chipflow-lib @ git+https://github.com/ChipFlow/chipflow-lib.git", + "chipflow @ git+https://github.com/ChipFlow/chipflow-lib.git", "amaranth-soc @ git+https://github.com/amaranth-lang/amaranth-soc", "amaranth-stdio @ git+https://github.com/amaranth-lang/amaranth-stdio", + "minerva @ git+https://github.com/minerva-cpu/minerva", + "pythondata-misc-usb_ohci @ git+https://github.com/robtaylor/pythondata-misc-usb_ohci@update-spinalhdl", ] # Build system configuration @@ -46,11 +48,12 @@ purelib = [ # Development workflow configuration [tool.pyright] -diagnosticMode=false -typeCheckingMode = "off" reportInvalidTypeForm = false reportMissingImports = false reportUnboundVariable = false +reportAttributeAccessIssue = false +reportWildcardImportFromLibrary = false +ignore = [ "tests", "vendor" ] [tool.ruff.lint] select = ['E4', 'E7', 'E9', 'F', 'W291', 'W293'] @@ -75,4 +78,6 @@ dev = [ "ruff>=0.9.2", "pytest>=7.2.0", "pytest-cov>=0.6", + "pyright>=1.1.407", + "amaranth-stubs>=0.1.1", ]