diff --git a/constants/ecp5_platforms.py b/constants/ecp5_platforms.py index 7230ae70a..9aade96d5 100644 --- a/constants/ecp5_platforms.py +++ b/constants/ecp5_platforms.py @@ -2,7 +2,7 @@ from itertools import chain from typing import TypeAlias from amaranth.build.dsl import Subsignal -from amaranth.vendor.lattice_ecp5 import LatticeECP5Platform +from amaranth.vendor import LatticeECP5Platform from amaranth.build import Resource, Attrs, Pins, Clock, PinsN from constants.ecp5_pinout import ecp5_bg756_pins, ecp5_bg756_pclk diff --git a/coreblocks/cache/icache.py b/coreblocks/cache/icache.py index aadc0dfc6..7b54b9675 100644 --- a/coreblocks/cache/icache.py +++ b/coreblocks/cache/icache.py @@ -2,7 +2,7 @@ import operator from amaranth import * -from amaranth.utils import log2_int +from amaranth.utils import exact_log2 from transactron.core import def_method, Priority, TModule from transactron import Method, Transaction @@ -52,7 +52,7 @@ def _(addr: Value) -> None: m.d.sync += req_addr.eq(addr) self.bus_master.request_read( m, - addr=addr >> log2_int(self.params.word_width_bytes), + addr=addr >> exact_log2(self.params.word_width_bytes), sel=C(1).replicate(self.bus_master.params.data_width // self.bus_master.params.granularity), ) @@ -350,7 +350,7 @@ def elaborate(self, platform): # We address the data RAM using machine words, so we have to # discard a few least significant bits from the address. - redundant_offset_bits = log2_int(self.params.word_width_bytes) + redundant_offset_bits = exact_log2(self.params.word_width_bytes) rd_addr = Cat(self.data_rd_addr.offset, self.data_rd_addr.index)[redundant_offset_bits:] wr_addr = Cat(self.data_wr_addr.offset, self.data_wr_addr.index)[redundant_offset_bits:] diff --git a/coreblocks/cache/refiller.py b/coreblocks/cache/refiller.py index 674044d5e..e8a261e26 100644 --- a/coreblocks/cache/refiller.py +++ b/coreblocks/cache/refiller.py @@ -1,10 +1,11 @@ +from amaranth import * from coreblocks.cache.icache import CacheRefillerInterface from coreblocks.params import ICacheLayouts, ICacheParameters from coreblocks.peripherals.bus_adapter import BusMasterInterface from transactron.core import Transaction -from transactron.lib import C, Cat, Elaboratable, Forwarder, Method, Signal, TModule, def_method +from transactron.lib import Forwarder, Method, TModule, def_method -from amaranth.utils import log2_int +from amaranth.utils import exact_log2 __all__ = ["SimpleCommonBusCacheRefiller"] @@ -62,7 +63,7 @@ def _(): address_fwd.write(m, word_counter=next_word_counter, refill_address=refill_address) return { - "addr": Cat(C(0, log2_int(self.params.word_width_bytes)), word_counter, refill_address), + "addr": Cat(C(0, exact_log2(self.params.word_width_bytes)), word_counter, refill_address), "data": fetched.data, "error": fetched.err, "last": last, diff --git a/coreblocks/frontend/fetch.py b/coreblocks/frontend/fetch.py index 1fdad5091..33a1a2129 100644 --- a/coreblocks/frontend/fetch.py +++ b/coreblocks/frontend/fetch.py @@ -38,9 +38,6 @@ def __init__(self, gen_params: GenParams, icache: CacheInterface, cont: Method) # ExceptionCauseRegister uses separate Transaction for it, so performace is not affected. self.stall_exception.add_conflict(self.resume, Priority.LEFT) - # PC of the last fetched instruction. For now only used in tests. - self.pc = Signal(self.gen_params.isa.xlen) - def elaborate(self, platform): m = TModule() @@ -91,7 +88,6 @@ def stall(exception=False): with m.If(unsafe_instr): stall() - m.d.sync += self.pc.eq(target.addr) m.d.comb += instr.eq(res.instr) self.cont(m, instr=instr, pc=target.addr, access_fault=fetch_error, rvc=0) @@ -138,9 +134,6 @@ def __init__(self, gen_params: GenParams, icache: CacheInterface, cont: Method) self.perf_rvc = HwCounter("frontend.ifu.rvc", "Number of decompressed RVC instructions") - # PC of the last fetched instruction. For now only used in tests. - self.pc = Signal(self.gen_params.isa.xlen) - def elaborate(self, platform) -> TModule: m = TModule() @@ -231,7 +224,6 @@ def elaborate(self, platform) -> TModule: m.d.sync += stalled_unsafe.eq(1) m.d.sync += flushing.eq(1) - m.d.sync += self.pc.eq(current_pc) with m.If(~cache_resp.error): m.d.sync += current_pc.eq(current_pc + Mux(is_rvc, C(2, 3), C(4, 3))) diff --git a/coreblocks/frontend/instr_description.py b/coreblocks/frontend/instr_description.py index 0e9fe6994..632d436cc 100644 --- a/coreblocks/frontend/instr_description.py +++ b/coreblocks/frontend/instr_description.py @@ -164,20 +164,23 @@ class Encoding: Encoding(Opcode.OP, Funct3.MIN, Funct7.MIN), Encoding(Opcode.OP, Funct3.MINU, Funct7.MIN), Encoding(Opcode.OP, Funct3.ORN, Funct7.ORN), + Encoding(Opcode.OP, Funct3.XNOR, Funct7.XNOR), + ], + OpType.BIT_ROTATION: [ Encoding(Opcode.OP, Funct3.ROL, Funct7.ROL), Encoding(Opcode.OP, Funct3.ROR, Funct7.ROR), Encoding(Opcode.OP_IMM, Funct3.ROR, Funct7.ROR), - Encoding(Opcode.OP, Funct3.XNOR, Funct7.XNOR), ], OpType.UNARY_BIT_MANIPULATION_1: [ - Encoding(Opcode.OP_IMM, Funct3.ORCB, funct12=Funct12.ORCB), Encoding(Opcode.OP_IMM, Funct3.REV8, funct12=Funct12.REV8_32), Encoding(Opcode.OP_IMM, Funct3.SEXTB, funct12=Funct12.SEXTB), Encoding(Opcode.OP, Funct3.ZEXTH, funct12=Funct12.ZEXTH), ], - # Instructions SEXTH, SEXTHB, CPOP, CLZ and CTZ cannot be distiguished by their Funct7 code + # Instructions SEXTH, SEXTHB, CPOP, CLZ and CTZ cannot be distiguished by their Funct7 code + # ORCB is here because of optimization to not lookup Funct7 in UNARY_BIT_MANIPULATION_1 OpType.UNARY_BIT_MANIPULATION_2: [ Encoding(Opcode.OP_IMM, Funct3.SEXTH, funct12=Funct12.SEXTH), + Encoding(Opcode.OP_IMM, Funct3.ORCB, funct12=Funct12.ORCB), ], OpType.UNARY_BIT_MANIPULATION_3: [ Encoding(Opcode.OP_IMM, Funct3.CLZ, funct12=Funct12.CLZ), diff --git a/coreblocks/fu/alu.py b/coreblocks/fu/alu.py index bc5bf72b5..114e367ce 100644 --- a/coreblocks/fu/alu.py +++ b/coreblocks/fu/alu.py @@ -82,14 +82,14 @@ def get_instructions(self) -> Sequence[tuple]: (self.Fn.MAXU, OpType.BIT_MANIPULATION, Funct3.MAXU, Funct7.MAX), (self.Fn.MIN, OpType.BIT_MANIPULATION, Funct3.MIN, Funct7.MIN), (self.Fn.MINU, OpType.BIT_MANIPULATION, Funct3.MINU, Funct7.MIN), - (self.Fn.ORCB, OpType.UNARY_BIT_MANIPULATION_1, Funct3.ORCB, Funct7.ORCB), - (self.Fn.REV8, OpType.UNARY_BIT_MANIPULATION_1, Funct3.REV8, Funct7.REV8), - (self.Fn.SEXTB, OpType.UNARY_BIT_MANIPULATION_1, Funct3.SEXTB, Funct7.SEXTB), - (self.Fn.ZEXTH, OpType.UNARY_BIT_MANIPULATION_1, Funct3.ZEXTH, Funct7.ZEXTH), - (self.Fn.CPOP, OpType.UNARY_BIT_MANIPULATION_5, Funct3.CPOP, Funct7.CPOP), - (self.Fn.SEXTH, OpType.UNARY_BIT_MANIPULATION_2, Funct3.SEXTH, Funct7.SEXTH), - (self.Fn.CLZ, OpType.UNARY_BIT_MANIPULATION_3, Funct3.CLZ, Funct7.CLZ), - (self.Fn.CTZ, OpType.UNARY_BIT_MANIPULATION_4, Funct3.CTZ, Funct7.CTZ), + (self.Fn.REV8, OpType.UNARY_BIT_MANIPULATION_1, Funct3.REV8), + (self.Fn.SEXTB, OpType.UNARY_BIT_MANIPULATION_1, Funct3.SEXTB), + (self.Fn.ZEXTH, OpType.UNARY_BIT_MANIPULATION_1, Funct3.ZEXTH), + (self.Fn.ORCB, OpType.UNARY_BIT_MANIPULATION_2, Funct3.ORCB), + (self.Fn.SEXTH, OpType.UNARY_BIT_MANIPULATION_2, Funct3.SEXTH), + (self.Fn.CLZ, OpType.UNARY_BIT_MANIPULATION_3, Funct3.CLZ), + (self.Fn.CTZ, OpType.UNARY_BIT_MANIPULATION_4, Funct3.CTZ), + (self.Fn.CPOP, OpType.UNARY_BIT_MANIPULATION_5, Funct3.CPOP), ] * self.zbb_enable ) diff --git a/coreblocks/fu/shift_unit.py b/coreblocks/fu/shift_unit.py index f0ce3dc2d..0df08b73c 100644 --- a/coreblocks/fu/shift_unit.py +++ b/coreblocks/fu/shift_unit.py @@ -34,8 +34,8 @@ def get_instructions(self) -> Sequence[tuple]: (self.Fn.SRL, OpType.SHIFT, Funct3.SR, Funct7.SL), (self.Fn.SRA, OpType.SHIFT, Funct3.SR, Funct7.SA), ] + [ - (self.Fn.ROR, OpType.BIT_MANIPULATION, Funct3.ROR, Funct7.ROR), - (self.Fn.ROL, OpType.BIT_MANIPULATION, Funct3.ROL, Funct7.ROL), + (self.Fn.ROR, OpType.BIT_ROTATION, Funct3.ROR), + (self.Fn.ROL, OpType.BIT_ROTATION, Funct3.ROL), ] * self.zbb_enable diff --git a/coreblocks/lsu/dummyLsu.py b/coreblocks/lsu/dummyLsu.py index fceef8344..3b8edd4a4 100644 --- a/coreblocks/lsu/dummyLsu.py +++ b/coreblocks/lsu/dummyLsu.py @@ -73,7 +73,7 @@ def postprocess_load_data(self, m: ModuleLike, funct3: Value, raw_data: Value, a m.d.av_comb += data.eq(tmp.as_signed()) with m.Else(): m.d.av_comb += data.eq(tmp) - with m.Case(): + with m.Default(): m.d.av_comb += data.eq(raw_data) return data @@ -84,7 +84,7 @@ def prepare_data_to_save(self, m: ModuleLike, funct3: Value, raw_data: Value, ad m.d.av_comb += data.eq(raw_data[0:8] << (addr[0:2] << 3)) with m.Case(Funct3.H): m.d.av_comb += data.eq(raw_data[0:16] << (addr[1] << 4)) - with m.Case(): + with m.Default(): m.d.av_comb += data.eq(raw_data) return data @@ -95,7 +95,7 @@ def check_align(self, m: TModule, funct3: Value, addr: Value): m.d.av_comb += aligned.eq(addr[0:2] == 0) with m.Case(Funct3.H, Funct3.HU): m.d.av_comb += aligned.eq(addr[0] == 0) - with m.Case(): + with m.Default(): m.d.av_comb += aligned.eq(1) return aligned diff --git a/coreblocks/params/genparams.py b/coreblocks/params/genparams.py index 709b46b82..3691d02ca 100644 --- a/coreblocks/params/genparams.py +++ b/coreblocks/params/genparams.py @@ -1,6 +1,6 @@ from __future__ import annotations -from amaranth.utils import log2_int +from amaranth.utils import exact_log2 from .isa import ISA, gen_isa_string from .icache_params import ICacheParameters @@ -36,7 +36,7 @@ def __init__(self, cfg: CoreConfiguration): bytes_in_word = self.isa.xlen // 8 self.wb_params = WishboneParameters( - data_width=self.isa.xlen, addr_width=self.isa.xlen - log2_int(bytes_in_word) + data_width=self.isa.xlen, addr_width=self.isa.xlen - exact_log2(bytes_in_word) ) self.icache_params = ICacheParameters( diff --git a/coreblocks/params/instr.py b/coreblocks/params/instr.py index 7bf830436..efaab82cb 100644 --- a/coreblocks/params/instr.py +++ b/coreblocks/params/instr.py @@ -1,6 +1,6 @@ from abc import abstractmethod, ABC -from amaranth.hdl.ast import ValueCastable +from amaranth.hdl import ValueCastable from amaranth import * from transactron.utils import ValueLike diff --git a/coreblocks/params/optypes.py b/coreblocks/params/optypes.py index 72ca461b8..60fd52c19 100644 --- a/coreblocks/params/optypes.py +++ b/coreblocks/params/optypes.py @@ -34,6 +34,7 @@ class OpType(IntEnum): SINGLE_BIT_MANIPULATION = auto() ADDRESS_GENERATION = auto() BIT_MANIPULATION = auto() + BIT_ROTATION = auto() UNARY_BIT_MANIPULATION_1 = auto() UNARY_BIT_MANIPULATION_2 = auto() UNARY_BIT_MANIPULATION_3 = auto() @@ -88,6 +89,7 @@ class OpType(IntEnum): ], Extension.ZBB: [ OpType.BIT_MANIPULATION, + OpType.BIT_ROTATION, OpType.UNARY_BIT_MANIPULATION_1, OpType.UNARY_BIT_MANIPULATION_2, OpType.UNARY_BIT_MANIPULATION_3, diff --git a/requirements.txt b/requirements.txt index 80015ce36..43714219e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ amaranth-yosys==0.35.0.0.post81 -git+https://github.com/amaranth-lang/amaranth@94c504afc7d81738ecdc9523a2615ef43ecbf51a +git+https://github.com/amaranth-lang/amaranth@115954b4d957b4ba642ad056ab1670bf5d185fb6 dataclasses-json==0.6.3 diff --git a/stubs/amaranth/_toolchain/yosys.pyi b/stubs/amaranth/_toolchain/yosys.pyi new file mode 100644 index 000000000..46cff1055 --- /dev/null +++ b/stubs/amaranth/_toolchain/yosys.pyi @@ -0,0 +1,144 @@ +""" +This type stub file was generated by pyright. +""" + +__all__ = ["YosysError", "YosysBinary", "find_yosys"] +from typing import Optional +from pathlib import Path + + +class YosysError(Exception): + ... + + +class YosysWarning(Warning): + ... + + +class YosysBinary: + @classmethod + def available(cls) -> bool: + """Check for Yosys availability. + + Returns + ------- + available : bool + ``True`` if Yosys is installed, ``False`` otherwise. Installed binary may still not + be runnable, or might be too old to be useful. + """ + ... + + @classmethod + def version(cls) -> Optional[tuple[int, int, int]]: + """Get Yosys version. + + Returns + ------- + ``None`` if version number could not be determined, or a 3-tuple ``(major, minor, distance)`` if it could. + + major : int + Major version. + minor : int + Minor version. + distance : int + Distance to last tag per ``git describe``. May not be exact for system Yosys. + """ + ... + + @classmethod + def data_dir(cls) -> pathlib.Path: + """Get Yosys data directory. + + Returns + ------- + data_dir : pathlib.Path + Yosys data directory (also known as "datdir"). + """ + ... + + @classmethod + def run(cls, args: list[str], stdin: str=...) -> str: + """Run Yosys process. + + Parameters + ---------- + args : list of str + Arguments, not including the program name. + stdin : str + Standard input. + + Returns + ------- + stdout : str + Standard output. + + Exceptions + ---------- + YosysError + Raised if Yosys returns a non-zero code. The exception message is the standard error + output. + """ + ... + + + +class _BuiltinYosys(YosysBinary): + YOSYS_PACKAGE = ... + @classmethod + def available(cls): # -> bool: + ... + + @classmethod + def version(cls): # -> tuple[int, int, int]: + ... + + @classmethod + def data_dir(cls): # -> Traversable: + ... + + @classmethod + def run(cls, args, stdin=..., *, ignore_warnings=..., src_loc_at=...): + ... + + + +class _SystemYosys(YosysBinary): + YOSYS_BINARY = ... + @classmethod + def available(cls): # -> bool: + ... + + @classmethod + def version(cls): # -> tuple[int, int, int] | None: + ... + + @classmethod + def data_dir(cls): # -> Path: + ... + + @classmethod + def run(cls, args, stdin=..., *, ignore_warnings=..., src_loc_at=...): + ... + + + +def find_yosys(requirement): + """Find an available Yosys executable of required version. + + Parameters + ---------- + requirement : function + Version check. Should return ``True`` if the version is acceptable, ``False`` otherwise. + + Returns + ------- + yosys_binary : subclass of YosysBinary + Proxy for running the requested version of Yosys. + + Exceptions + ---------- + YosysError + Raised if required Yosys version is not found. + """ + ... + diff --git a/stubs/amaranth/_unused.pyi b/stubs/amaranth/_unused.pyi new file mode 100644 index 000000000..9af961a4a --- /dev/null +++ b/stubs/amaranth/_unused.pyi @@ -0,0 +1,13 @@ +import sys +import warnings + +__all__ = ["UnusedMustUse", "MustUse"] + + +class UnusedMustUse(Warning): + pass + + +class MustUse: + _MustUse__silence : bool + _MustUse__warning : UnusedMustUse diff --git a/stubs/amaranth/back/__init__.pyi b/stubs/amaranth/back/__init__.pyi new file mode 100644 index 000000000..006bc2749 --- /dev/null +++ b/stubs/amaranth/back/__init__.pyi @@ -0,0 +1,4 @@ +""" +This type stub file was generated by pyright. +""" + diff --git a/stubs/amaranth/back/verilog.pyi b/stubs/amaranth/back/verilog.pyi new file mode 100644 index 000000000..2850050a3 --- /dev/null +++ b/stubs/amaranth/back/verilog.pyi @@ -0,0 +1,14 @@ +""" +This type stub file was generated by pyright. +""" + +from .._toolchain.yosys import * +from ..hdl.ast import SignalDict + +__all__ = ["YosysError", "convert", "convert_fragment"] +def convert_fragment(*args, strip_internal_attrs=..., **kwargs) -> tuple[str, SignalDict]: + ... + +def convert(elaboratable, name=..., platform=..., *, ports=..., emit_src=..., strip_internal_attrs=..., **kwargs) -> str: + ... + diff --git a/stubs/amaranth/build/res.pyi b/stubs/amaranth/build/res.pyi index 41b734ca9..83a09d440 100644 --- a/stubs/amaranth/build/res.pyi +++ b/stubs/amaranth/build/res.pyi @@ -3,7 +3,7 @@ This type stub file was generated by pyright. """ from typing import Any -from ..hdl.ast import * +from ..hdl._ast import * from ..hdl.rec import * from ..lib.io import * from .dsl import * diff --git a/stubs/amaranth/hdl/__init__.pyi b/stubs/amaranth/hdl/__init__.pyi index 78c4f551c..c44fa4755 100644 --- a/stubs/amaranth/hdl/__init__.pyi +++ b/stubs/amaranth/hdl/__init__.pyi @@ -1,13 +1,29 @@ -""" -This type stub file was generated by pyright. -""" - -from .ast import Array, C, Cat, ClockSignal, Const, Mux, Repl, ResetSignal, Shape, Signal, Value, signed, unsigned -from .dsl import Module -from .cd import ClockDomain -from .ir import Elaboratable, Fragment, Instance -from .mem import Memory +from ._ast import Shape, unsigned, signed, ShapeCastable, ShapeLike +from ._ast import Value, ValueCastable, ValueLike +from ._ast import Const, C, Mux, Cat, Array, Signal, ClockSignal, ResetSignal +from ._dsl import SyntaxError, SyntaxWarning, Module +from ._cd import DomainError, ClockDomain +from ._ir import UnusedElaboratable, Elaboratable, DriverConflict, Fragment, Instance +from ._mem import Memory, ReadPort, WritePort, DummyPort from .rec import Record -from .xfrm import DomainRenamer, EnableInserter, ResetInserter +from ._xfrm import DomainRenamer, ResetInserter, EnableInserter + -__all__ = ["Shape", "unsigned", "signed", "Value", "Const", "C", "Mux", "Cat", "Repl", "Array", "Signal", "ClockSignal", "ResetSignal", "Module", "ClockDomain", "Elaboratable", "Fragment", "Instance", "Memory", "Record", "DomainRenamer", "ResetInserter", "EnableInserter"] +__all__ = [ + # _ast + "Shape", "unsigned", "signed", "ShapeCastable", "ShapeLike", + "Value", "ValueCastable", "ValueLike", + "Const", "C", "Mux", "Cat", "Array", "Signal", "ClockSignal", "ResetSignal", + # _dsl + "SyntaxError", "SyntaxWarning", "Module", + # _cd + "DomainError", "ClockDomain", + # _ir + "UnusedElaboratable", "Elaboratable", "DriverConflict", "Fragment", "Instance", + # _mem + "Memory", "ReadPort", "WritePort", "DummyPort", + # _rec + "Record", + # _xfrm + "DomainRenamer", "ResetInserter", "EnableInserter", +] diff --git a/stubs/amaranth/hdl/ast.pyi b/stubs/amaranth/hdl/_ast.pyi similarity index 100% rename from stubs/amaranth/hdl/ast.pyi rename to stubs/amaranth/hdl/_ast.pyi diff --git a/stubs/amaranth/hdl/cd.pyi b/stubs/amaranth/hdl/_cd.pyi similarity index 100% rename from stubs/amaranth/hdl/cd.pyi rename to stubs/amaranth/hdl/_cd.pyi diff --git a/stubs/amaranth/hdl/dsl.pyi b/stubs/amaranth/hdl/_dsl.pyi similarity index 100% rename from stubs/amaranth/hdl/dsl.pyi rename to stubs/amaranth/hdl/_dsl.pyi diff --git a/stubs/amaranth/hdl/ir.pyi b/stubs/amaranth/hdl/_ir.pyi similarity index 88% rename from stubs/amaranth/hdl/ir.pyi rename to stubs/amaranth/hdl/_ir.pyi index 63acd1e3c..556a0f679 100644 --- a/stubs/amaranth/hdl/ir.pyi +++ b/stubs/amaranth/hdl/_ir.pyi @@ -3,13 +3,17 @@ This type stub file was generated by pyright. """ from abc import abstractmethod -from .ast import * -from .cd import * +from ._ast import * +from ._cd import * +from .. import _unused from transactron.utils import HasElaborate -__all__ = ["Elaboratable", "DriverConflict", "Fragment", "Instance"] +__all__ = ["UnusedElaboratable", "Elaboratable", "DriverConflict", "Fragment", "Instance"] +class UnusedElaboratable(_unused.UnusedMustUse): + ... + class Elaboratable(): @abstractmethod def elaborate(self, platform) -> HasElaborate: diff --git a/stubs/amaranth/hdl/mem.pyi b/stubs/amaranth/hdl/_mem.pyi similarity index 60% rename from stubs/amaranth/hdl/mem.pyi rename to stubs/amaranth/hdl/_mem.pyi index e7ed09e43..ddd629e06 100644 --- a/stubs/amaranth/hdl/mem.pyi +++ b/stubs/amaranth/hdl/_mem.pyi @@ -4,13 +4,33 @@ This type stub file was generated by pyright. from typing import Optional from .ast import * -from .ir import Elaboratable +from .ir import Elaboratable, Fragment -__all__ = ["Memory", "ReadPort", "WritePort", "DummyPort"] +__all__ = ["Memory", "ReadPort", "WritePort", "DummyPort", "MemoryInstance"] class Memory: """A word addressable storage. - - """ + Parameters + ---------- + width : int + Access granularity. Each storage element of this memory is ``width`` bits in size. + depth : int + Word count. This memory contains ``depth`` storage elements. + init : list of int + Initial values. At power on, each storage element in this memory is initialized to + the corresponding element of ``init``, if any, or to zero otherwise. + Uninitialized memories are not currently supported. + name : str + Name hint for this memory. If ``None`` (default) the name is inferred from the variable + name this ``Signal`` is assigned to. + attrs : dict + Dictionary of synthesis attributes. + Attributes + ---------- + width : int + depth : int + init : list of int + attrs : dict + """ width: int depth: int attrs: dict @@ -90,4 +110,10 @@ class DummyPort: ... - +class MemoryInstance(Fragment): + memory: Memory + read_ports: list[ReadPort] + write_ports: list[WritePort] + attrs: dict + def __init__(self, memory: Memory, read_ports: list[ReadPort], write_ports: list[WritePort]) -> None: + ... diff --git a/stubs/amaranth/hdl/xfrm.pyi b/stubs/amaranth/hdl/_xfrm.pyi similarity index 100% rename from stubs/amaranth/hdl/xfrm.pyi rename to stubs/amaranth/hdl/_xfrm.pyi diff --git a/stubs/amaranth/lib/data.pyi b/stubs/amaranth/lib/data.pyi index 038fc0607..52c5c0180 100644 --- a/stubs/amaranth/lib/data.pyi +++ b/stubs/amaranth/lib/data.pyi @@ -6,7 +6,7 @@ from abc import ABCMeta, abstractmethod from collections.abc import Iterator, Mapping from typing import TypeVar, Generic, Self from amaranth.hdl import * -from amaranth.hdl.ast import Assign, ShapeCastable, ValueCastable +from amaranth.hdl._ast import Assign, ShapeCastable, ValueCastable from transactron.utils._typing import ShapeLike, ValueLike __all__ = ["Field", "Layout", "StructLayout", "UnionLayout", "ArrayLayout", "FlexibleLayout", "View", "Struct", "Union"] diff --git a/stubs/amaranth/lib/enum.pyi b/stubs/amaranth/lib/enum.pyi index 4c1d669a8..4ef5262f2 100644 --- a/stubs/amaranth/lib/enum.pyi +++ b/stubs/amaranth/lib/enum.pyi @@ -5,7 +5,7 @@ This type stub file was generated by pyright. import enum as py_enum from typing import Generic, Optional, TypeVar, Self, overload from amaranth import * -from ..hdl.ast import Assign, ValueCastable, ShapeCastable, ValueLike +from ..hdl._ast import Assign, ValueCastable, ShapeCastable, ValueLike __all__ = ['EnumMeta', 'Enum', 'IntEnum', 'Flag', 'IntFlag', 'EnumView', 'FlagView', 'auto', 'unique'] diff --git a/stubs/amaranth/lib/fifo.pyi b/stubs/amaranth/lib/fifo.pyi index a64c5e8e5..e799e7223 100644 --- a/stubs/amaranth/lib/fifo.pyi +++ b/stubs/amaranth/lib/fifo.pyi @@ -11,7 +11,6 @@ __all__ = ["FIFOInterface", "SyncFIFO", "SyncFIFOBuffered", "AsyncFIFO", "AsyncF class FIFOInterface: width: int depth: int - fwft: bool w_data: Signal w_rdy: Signal w_en: Signal @@ -20,13 +19,13 @@ class FIFOInterface: r_rdy: Signal r_en: Signal r_level: Signal - def __init__(self, *, width: int, depth: int, fwft: bool) -> None: + def __init__(self, *, width: int, depth: int) -> None: ... class SyncFIFO(Elaboratable, FIFOInterface): - def __init__(self, *, width: int, depth: int, fwft: bool = ...) -> None: + def __init__(self, *, width: int, depth: int) -> None: ... def elaborate(self, platform) -> HasElaborate: diff --git a/stubs/amaranth/sim/core.pyi b/stubs/amaranth/sim/core.pyi index 23fef3472..cee8e0f2e 100644 --- a/stubs/amaranth/sim/core.pyi +++ b/stubs/amaranth/sim/core.pyi @@ -3,8 +3,8 @@ This type stub file was generated by pyright. """ from .._utils import deprecated -from ..hdl.cd import * -from ..hdl.ir import * +from ..hdl._cd import * +from ..hdl._ir import * __all__ = ["Settle", "Delay", "Tick", "Passive", "Active", "Simulator"] class Command: diff --git a/stubs/amaranth/utils.pyi b/stubs/amaranth/utils.pyi index 0da04bf79..6ca424a08 100644 --- a/stubs/amaranth/utils.pyi +++ b/stubs/amaranth/utils.pyi @@ -2,10 +2,22 @@ This type stub file was generated by pyright. """ -__all__ = ["log2_int", "bits_for"] -def log2_int(n:int, need_pow2:bool=...) -> int: - ... +__all__ = ["ceil_log2", "exact_log2", "bits_for"] def bits_for(n:int, require_sign_bit:bool=...) -> int: ... + +def ceil_log2(n : int) -> int: + """Returns the integer log2 of the smallest power-of-2 greater than or equal to ``n``. + + Raises a ``ValueError`` for negative inputs. + """ + ... + +def exact_log2(n : int) -> int: + """Returns the integer log2 of ``n``, which must be an exact power of two. + + Raises a ``ValueError`` if ``n`` is not a power of two. + """ + ... diff --git a/stubs/amaranth/vendor/__init__.pyi b/stubs/amaranth/vendor/__init__.pyi new file mode 100644 index 000000000..d9c2463ca --- /dev/null +++ b/stubs/amaranth/vendor/__init__.pyi @@ -0,0 +1,8 @@ +""" +This type stub file was generated by pyright. +""" + +from ._lattice_ecp5 import LatticeECP5Platform +from ._lattice_ice40 import LatticeICE40Platform + +__all__ = ["LatticeECP5Platform", "LatticeICE40Platform"] diff --git a/stubs/amaranth/vendor/_lattice_ecp5.pyi b/stubs/amaranth/vendor/_lattice_ecp5.pyi new file mode 100644 index 000000000..b043637c0 --- /dev/null +++ b/stubs/amaranth/vendor/_lattice_ecp5.pyi @@ -0,0 +1,129 @@ +""" +This type stub file was generated by pyright. +""" + +from ..hdl import * +from ..build import * + +class LatticeECP5Platform(TemplatedPlatform): + """ + .. rubric:: Trellis toolchain + + Required tools: + * ``yosys`` + * ``nextpnr-ecp5`` + * ``ecppack`` + + The environment is populated by running the script specified in the environment variable + ``AMARANTH_ENV_TRELLIS``, if present. + + Available overrides: + * ``verbose``: enables logging of informational messages to standard error. + * ``read_verilog_opts``: adds options for ``read_verilog`` Yosys command. + * ``synth_opts``: adds options for ``synth_ecp5`` Yosys command. + * ``script_after_read``: inserts commands after ``read_ilang`` in Yosys script. + * ``script_after_synth``: inserts commands after ``synth_ecp5`` in Yosys script. + * ``yosys_opts``: adds extra options for ``yosys``. + * ``nextpnr_opts``: adds extra options for ``nextpnr-ecp5``. + * ``ecppack_opts``: adds extra options for ``ecppack``. + * ``add_preferences``: inserts commands at the end of the LPF file. + + Build products: + * ``{{name}}.rpt``: Yosys log. + * ``{{name}}.json``: synthesized RTL. + * ``{{name}}.tim``: nextpnr log. + * ``{{name}}.config``: ASCII bitstream. + * ``{{name}}.bit``: binary bitstream. + * ``{{name}}.svf``: JTAG programming vector. + + .. rubric:: Diamond toolchain + + Required tools: + * ``pnmainc`` + * ``ddtcmd`` + + The environment is populated by running the script specified in the environment variable + ``AMARANTH_ENV_DIAMOND``, if present. On Linux, diamond_env as provided by Diamond + itself is a good candidate. On Windows, the following script (named ``diamond_env.bat``, + for instance) is known to work:: + + @echo off + set PATH=C:\\lscc\\diamond\\%DIAMOND_VERSION%\\bin\\nt64;%PATH% + + Available overrides: + * ``script_project``: inserts commands before ``prj_project save`` in Tcl script. + * ``script_after_export``: inserts commands after ``prj_run Export`` in Tcl script. + * ``add_preferences``: inserts commands at the end of the LPF file. + * ``add_constraints``: inserts commands at the end of the XDC file. + + Build products: + * ``{{name}}_impl/{{name}}_impl.htm``: consolidated log. + * ``{{name}}.bit``: binary bitstream. + * ``{{name}}.svf``: JTAG programming vector. + """ + toolchain = ... + device = ... + package = ... + speed = ... + grade = ... + _nextpnr_device_options = ... + _nextpnr_package_options = ... + _trellis_required_tools = ... + _trellis_file_templates = ... + _trellis_command_templates = ... + _diamond_required_tools = ... + _diamond_file_templates = ... + _diamond_command_templates = ... + def __init__(self, *, toolchain=...) -> None: + ... + + @property + def required_tools(self): # -> list[str]: + ... + + @property + def file_templates(self): # -> dict[str, str]: + ... + + @property + def command_templates(self): # -> list[str]: + ... + + @property + def default_clk_constraint(self): # -> Clock: + ... + + def create_missing_domain(self, name): # -> Module | None: + ... + + _single_ended_io_types = ... + _differential_io_types = ... + def should_skip_port_component(self, port, attrs, component): # -> bool: + ... + + def get_input(self, pin, port, attrs, invert): # -> Module: + ... + + def get_output(self, pin, port, attrs, invert): # -> Module: + ... + + def get_tristate(self, pin, port, attrs, invert): # -> Module: + ... + + def get_input_output(self, pin, port, attrs, invert): # -> Module: + ... + + def get_diff_input(self, pin, port, attrs, invert): # -> Module: + ... + + def get_diff_output(self, pin, port, attrs, invert): # -> Module: + ... + + def get_diff_tristate(self, pin, port, attrs, invert): # -> Module: + ... + + def get_diff_input_output(self, pin, port, attrs, invert): # -> Module: + ... + + + diff --git a/stubs/amaranth/vendor/_lattice_ice40.pyi b/stubs/amaranth/vendor/_lattice_ice40.pyi new file mode 100644 index 000000000..2f72ba554 --- /dev/null +++ b/stubs/amaranth/vendor/_lattice_ice40.pyi @@ -0,0 +1,125 @@ +""" +This type stub file was generated by pyright. +""" + +from ..hdl import * +from ..build import * + +class LatticeICE40Platform(TemplatedPlatform): + """ + .. rubric:: IceStorm toolchain + + Required tools: + * ``yosys`` + * ``nextpnr-ice40`` + * ``icepack`` + + The environment is populated by running the script specified in the environment variable + ``AMARANTH_ENV_ICESTORM``, if present. + + Available overrides: + * ``verbose``: enables logging of informational messages to standard error. + * ``read_verilog_opts``: adds options for ``read_verilog`` Yosys command. + * ``synth_opts``: adds options for ``synth_ice40`` Yosys command. + * ``script_after_read``: inserts commands after ``read_ilang`` in Yosys script. + * ``script_after_synth``: inserts commands after ``synth_ice40`` in Yosys script. + * ``yosys_opts``: adds extra options for ``yosys``. + * ``nextpnr_opts``: adds extra options for ``nextpnr-ice40``. + * ``add_pre_pack``: inserts commands at the end in pre-pack Python script. + * ``add_constraints``: inserts commands at the end in the PCF file. + + Build products: + * ``{{name}}.rpt``: Yosys log. + * ``{{name}}.json``: synthesized RTL. + * ``{{name}}.tim``: nextpnr log. + * ``{{name}}.asc``: ASCII bitstream. + * ``{{name}}.bin``: binary bitstream. + + .. rubric:: iCECube2 toolchain + + This toolchain comes in two variants: ``LSE-iCECube2`` and ``Synplify-iCECube2``. + + Required tools: + * iCECube2 toolchain + * ``tclsh`` + + The environment is populated by setting the necessary environment variables based on + ``AMARANTH_ENV_ICECUBE2``, which must point to the root of the iCECube2 installation, and + is required. + + Available overrides: + * ``verbose``: enables logging of informational messages to standard error. + * ``lse_opts``: adds options for LSE. + * ``script_after_add``: inserts commands after ``add_file`` in Synplify Tcl script. + * ``script_after_options``: inserts commands after ``set_option`` in Synplify Tcl script. + * ``add_constraints``: inserts commands in SDC file. + * ``script_after_flow``: inserts commands after ``run_sbt_backend_auto`` in SBT + Tcl script. + + Build products: + * ``{{name}}_lse.log`` (LSE) or ``{{name}}_design/{{name}}.htm`` (Synplify): synthesis log. + * ``sbt/outputs/router/{{name}}_timing.rpt``: timing report. + * ``{{name}}.edf``: EDIF netlist. + * ``{{name}}.bin``: binary bitstream. + """ + toolchain = ... + device = ... + package = ... + _nextpnr_device_options = ... + _nextpnr_package_options = ... + _icestorm_required_tools = ... + _icestorm_file_templates = ... + _icestorm_command_templates = ... + _icecube2_required_tools = ... + _icecube2_file_templates = ... + _lse_icecube2_command_templates = ... + _synplify_icecube2_command_templates = ... + def __init__(self, *, toolchain=...) -> None: + ... + + @property + def family(self): # -> Literal['iCE40', 'iCE5']: + ... + + @property + def required_tools(self): # -> list[str]: + ... + + @property + def file_templates(self): # -> dict[str, str]: + ... + + @property + def command_templates(self): # -> list[str]: + ... + + @property + def default_clk_constraint(self): # -> Clock: + ... + + def create_missing_domain(self, name): # -> Module | None: + ... + + def should_skip_port_component(self, port, attrs, component): # -> bool: + ... + + def get_input(self, pin, port, attrs, invert): # -> Module: + ... + + def get_output(self, pin, port, attrs, invert): # -> Module: + ... + + def get_tristate(self, pin, port, attrs, invert): # -> Module: + ... + + def get_input_output(self, pin, port, attrs, invert): # -> Module: + ... + + def get_diff_input(self, pin, port, attrs, invert): # -> Module: + ... + + def get_diff_output(self, pin, port, attrs, invert): # -> Module: + ... + + + diff --git a/test/cache/test_icache.py b/test/cache/test_icache.py index 8ff52f092..2afeff6db 100644 --- a/test/cache/test_icache.py +++ b/test/cache/test_icache.py @@ -4,7 +4,7 @@ from amaranth import Elaboratable, Module from amaranth.sim import Passive, Settle -from amaranth.utils import log2_int +from amaranth.utils import exact_log2 from transactron.lib import AdapterTrans, Adapter from coreblocks.cache.icache import ICache, ICacheBypass, CacheRefillerInterface @@ -98,7 +98,7 @@ def wishbone_slave(self): yield from self.test_module.wb_ctrl.slave_wait() # Wishbone is addressing words, so we need to shift it a bit to get the real address. - addr = (yield self.test_module.wb_ctrl.wb.adr) << log2_int(self.cp.word_width_bytes) + addr = (yield self.test_module.wb_ctrl.wb.adr) << exact_log2(self.cp.word_width_bytes) yield while random.random() < 0.5: @@ -213,7 +213,7 @@ def wishbone_slave(self): yield from self.m.wb_ctrl.slave_wait() # Wishbone is addressing words, so we need to shift it a bit to get the real address. - addr = (yield self.m.wb_ctrl.wb.adr) << log2_int(self.cp.word_width_bytes) + addr = (yield self.m.wb_ctrl.wb.adr) << exact_log2(self.cp.word_width_bytes) while random.random() < 0.5: yield @@ -319,7 +319,7 @@ def init_module(self, ways, sets) -> None: test_core_config.replace( xlen=self.isa_xlen, icache_ways=ways, - icache_sets_bits=log2_int(sets), + icache_sets_bits=exact_log2(sets), icache_block_size_bits=self.block_size, ) ) diff --git a/test/external/riscof/coreblocks/coreblocks_isa.yaml b/test/external/riscof/coreblocks/coreblocks_isa.yaml index 483e8b41f..8b9623298 100644 --- a/test/external/riscof/coreblocks/coreblocks_isa.yaml +++ b/test/external/riscof/coreblocks/coreblocks_isa.yaml @@ -1,6 +1,6 @@ hart_ids: [0] hart0: - ISA: RV32I + ISA: RV32IMCZba_Zbb_Zbc_Zbs physical_addr_sz: 32 User_Spec_Version: '2.3' diff --git a/test/external/riscof/coreblocks/riscof_coreblocks.py b/test/external/riscof/coreblocks/riscof_coreblocks.py index 549e24934..ae6e63268 100644 --- a/test/external/riscof/coreblocks/riscof_coreblocks.py +++ b/test/external/riscof/coreblocks/riscof_coreblocks.py @@ -97,24 +97,9 @@ def build(self, isa_yaml, platform_yaml): # will be useful in setting integer value in the compiler string (if not already hardcoded); self.xlen = "64" if 64 in ispec["supported_xlen"] else "32" - # for coreblocks start building the '--isa' argument. the self.isa is dut specific and may not be - # useful for all DUTs - self.isa = "rv" + self.xlen - if "I" in ispec["ISA"]: - self.isa += "i" - if "M" in ispec["ISA"]: - self.isa += "m" - if "F" in ispec["ISA"]: - self.isa += "f" - if "D" in ispec["ISA"]: - self.isa += "d" - if "C" in ispec["ISA"]: - self.isa += "c" - if "B" in ispec["ISA"]: - self.isa += "b" - - # TODO: The following assumes you are using the riscv-gcc toolchain. If - # not please change appropriately + self.isa = ispec["ISA"].lower() + + # The following assumes you are using the riscv-gcc toolchain. self.compile_cmd = self.compile_cmd + " -mabi=" + ("lp64 " if 64 in ispec["supported_xlen"] else "ilp32 ") def runTests(self, testList): # noqa: N802 N803 @@ -168,6 +153,11 @@ def runTests(self, testList): # noqa: N802 N803 target_build = "cd {0}; {1};".format(testentry["work_dir"], buildcmd) target_run = "mkdir -p {0}; cd {1}; {2};".format(testentry["work_dir"], self.work_dir, simcmd) + # for some reason C extension enables priv tests. Disable them for now. Not ready yet! + if "privilege" in test_dir: + print("SKIP generating", test_dir, test) + continue + # create a target. The makeutil will create a target with the name "TARGET" where num # starts from 0 and increments automatically for each new target that is added make_build.add_target(target_build) diff --git a/test/external/riscof/spike_simple/riscof_spike_simple.py b/test/external/riscof/spike_simple/riscof_spike_simple.py index 5e06de990..427fb5e37 100644 --- a/test/external/riscof/spike_simple/riscof_spike_simple.py +++ b/test/external/riscof/spike_simple/riscof_spike_simple.py @@ -54,19 +54,14 @@ def initialise(self, suite, work_dir, compliance_env): def build(self, isa_yaml, platform_yaml): ispec = utils.load_yaml(isa_yaml)['hart0'] self.xlen = ('64' if 64 in ispec['supported_xlen'] else '32') - self.isa = 'rv' + self.xlen if "64I" in ispec["ISA"]: self.compile_cmd = self.compile_cmd+' -mabi='+'lp64 ' elif "32I" in ispec["ISA"]: self.compile_cmd = self.compile_cmd+' -mabi='+'ilp32 ' elif "32E" in ispec["ISA"]: self.compile_cmd = self.compile_cmd+' -mabi='+'ilp32e ' - if "I" in ispec["ISA"]: - self.isa += 'i' - if "M" in ispec["ISA"]: - self.isa += 'm' - if "C" in ispec["ISA"]: - self.isa += 'c' + self.isa = ispec["ISA"].lower() + compiler = "riscv64-unknown-elf-gcc".format(self.xlen) if shutil.which(compiler) is None: logger.error(compiler+": executable not found. Please check environment setup.") diff --git a/test/external/riscof/spike_simple/spike_simple_isa.yaml b/test/external/riscof/spike_simple/spike_simple_isa.yaml index dad55a4f1..302439c34 100644 --- a/test/external/riscof/spike_simple/spike_simple_isa.yaml +++ b/test/external/riscof/spike_simple/spike_simple_isa.yaml @@ -1,6 +1,6 @@ hart_ids: [0] hart0: - ISA: RV32IMCZicsr_Zifencei + ISA: RV32IMCBZicsr_Zifencei_Zba_Zbb_Zbc_Zbs physical_addr_sz: 32 User_Spec_Version: '2.3' supported_xlen: [32] diff --git a/test/frontend/test_instr_decoder.py b/test/frontend/test_instr_decoder.py index f843bc4f6..4c0a0b4b6 100644 --- a/test/frontend/test_instr_decoder.py +++ b/test/frontend/test_instr_decoder.py @@ -365,7 +365,7 @@ def test_decoded_distinguishable(self): Encoding(Opcode.OP_IMM, Funct3.BSET, Funct7.BSET), Encoding(Opcode.OP_IMM, Funct3.BINV, Funct7.BINV), }, - OpType.BIT_MANIPULATION: { + OpType.BIT_ROTATION: { Encoding(Opcode.OP_IMM, Funct3.ROR, Funct7.ROR), }, } diff --git a/test/fu/test_alu.py b/test/fu/test_alu.py index f350af49c..7e973fc92 100644 --- a/test/fu/test_alu.py +++ b/test/fu/test_alu.py @@ -28,14 +28,14 @@ class AluUnitTest(FunctionalUnitTestCase[AluFn.Fn]): AluFn.Fn.MAXU: ExecFn(OpType.BIT_MANIPULATION, Funct3.MAXU, Funct7.MAX), AluFn.Fn.MIN: ExecFn(OpType.BIT_MANIPULATION, Funct3.MIN, Funct7.MIN), AluFn.Fn.MINU: ExecFn(OpType.BIT_MANIPULATION, Funct3.MINU, Funct7.MIN), - AluFn.Fn.CPOP: ExecFn(OpType.UNARY_BIT_MANIPULATION_5, Funct3.CPOP, Funct7.CPOP), - AluFn.Fn.SEXTB: ExecFn(OpType.UNARY_BIT_MANIPULATION_1, Funct3.SEXTB, Funct7.SEXTB), - AluFn.Fn.ZEXTH: ExecFn(OpType.UNARY_BIT_MANIPULATION_1, Funct3.ZEXTH, Funct7.ZEXTH), - AluFn.Fn.SEXTH: ExecFn(OpType.UNARY_BIT_MANIPULATION_2, Funct3.SEXTH, Funct7.SEXTH), - AluFn.Fn.ORCB: ExecFn(OpType.UNARY_BIT_MANIPULATION_1, Funct3.ORCB, Funct7.ORCB), - AluFn.Fn.REV8: ExecFn(OpType.UNARY_BIT_MANIPULATION_1, Funct3.REV8, Funct7.REV8), - AluFn.Fn.CLZ: ExecFn(OpType.UNARY_BIT_MANIPULATION_3, Funct3.CLZ, Funct7.CLZ), - AluFn.Fn.CTZ: ExecFn(OpType.UNARY_BIT_MANIPULATION_4, Funct3.CTZ, Funct7.CTZ), + AluFn.Fn.SEXTB: ExecFn(OpType.UNARY_BIT_MANIPULATION_1, Funct3.SEXTB), + AluFn.Fn.ZEXTH: ExecFn(OpType.UNARY_BIT_MANIPULATION_1, Funct3.ZEXTH), + AluFn.Fn.REV8: ExecFn(OpType.UNARY_BIT_MANIPULATION_1, Funct3.REV8), + AluFn.Fn.SEXTH: ExecFn(OpType.UNARY_BIT_MANIPULATION_2, Funct3.SEXTH), + AluFn.Fn.ORCB: ExecFn(OpType.UNARY_BIT_MANIPULATION_2, Funct3.ORCB), + AluFn.Fn.CLZ: ExecFn(OpType.UNARY_BIT_MANIPULATION_3, Funct3.CLZ), + AluFn.Fn.CTZ: ExecFn(OpType.UNARY_BIT_MANIPULATION_4, Funct3.CTZ), + AluFn.Fn.CPOP: ExecFn(OpType.UNARY_BIT_MANIPULATION_5, Funct3.CPOP), } @staticmethod diff --git a/test/fu/test_shift_unit.py b/test/fu/test_shift_unit.py index ba2de99e4..20eed6d55 100644 --- a/test/fu/test_shift_unit.py +++ b/test/fu/test_shift_unit.py @@ -12,8 +12,8 @@ class ShiftUnitTest(FunctionalUnitTestCase[ShiftUnitFn.Fn]): ShiftUnitFn.Fn.SLL: ExecFn(OpType.SHIFT, Funct3.SLL), ShiftUnitFn.Fn.SRL: ExecFn(OpType.SHIFT, Funct3.SR, Funct7.SL), ShiftUnitFn.Fn.SRA: ExecFn(OpType.SHIFT, Funct3.SR, Funct7.SA), - ShiftUnitFn.Fn.ROL: ExecFn(OpType.BIT_MANIPULATION, Funct3.ROL, Funct7.ROL), - ShiftUnitFn.Fn.ROR: ExecFn(OpType.BIT_MANIPULATION, Funct3.ROR, Funct7.ROR), + ShiftUnitFn.Fn.ROL: ExecFn(OpType.BIT_ROTATION, Funct3.ROL, Funct7.ROL), + ShiftUnitFn.Fn.ROR: ExecFn(OpType.BIT_ROTATION, Funct3.ROR, Funct7.ROR), } @staticmethod diff --git a/test/regression/cocotb.py b/test/regression/cocotb.py index bc3d6114f..87c043688 100644 --- a/test/regression/cocotb.py +++ b/test/regression/cocotb.py @@ -189,6 +189,17 @@ async def profile_handler(self, clock, profile: Profile): await clock_edge_event # type: ignore + async def assert_handler(self, clock): + clock_edge_event = FallingEdge(clock) + + while True: + for assert_info in self.gen_info.asserts: + assert_val = self.get_cocotb_handle(assert_info.location) + n, i = assert_info.src_loc + assert assert_val.value, f"Assertion at {n}:{i}" + + await clock_edge_event # type: ignore + async def run(self, mem_model: CoreMemoryModel, timeout_cycles: int = 5000) -> SimulationExecutionResult: clk = Clock(self.dut.clk, 1, "ns") cocotb.start_soon(clk.start()) @@ -209,6 +220,8 @@ async def run(self, mem_model: CoreMemoryModel, timeout_cycles: int = 5000) -> S profile.transactions_and_methods = self.gen_info.profile_data.transactions_and_methods cocotb.start_soon(self.profile_handler(self.dut.clk, profile)) + cocotb.start_soon(self.assert_handler(self.dut.clk)) + success = True try: await with_timeout(self.finish_event.wait(), timeout_cycles, "ns") diff --git a/test/regression/pysim.py b/test/regression/pysim.py index c408439c0..424d83d8e 100644 --- a/test/regression/pysim.py +++ b/test/regression/pysim.py @@ -2,7 +2,7 @@ import os from amaranth.sim import Passive, Settle -from amaranth.utils import log2_int +from amaranth.utils import exact_log2 from amaranth import * from transactron.core import TransactionManagerKey @@ -43,7 +43,7 @@ def f(): word_width_bytes = self.gp.isa.xlen // 8 # Wishbone is addressing words, so we need to shift it a bit to get the real address. - addr = (yield wb_ctrl.wb.adr) << log2_int(word_width_bytes) + addr = (yield wb_ctrl.wb.adr) << exact_log2(word_width_bytes) sel = yield wb_ctrl.wb.sel dat_w = yield wb_ctrl.wb.dat_w diff --git a/test/test_core.py b/test/test_core.py index 1522fa3a6..8bf5c8f1b 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -10,27 +10,15 @@ from coreblocks.params.configurations import CoreConfiguration, basic_core_config, full_core_config from coreblocks.peripherals.wishbone import WishboneBus, WishboneMemorySlave -from typing import Optional, cast +from typing import Optional import random import subprocess import tempfile from parameterized import parameterized_class from riscvmodel.insn import ( InstructionADDI, - InstructionSLTI, - InstructionSLTIU, - InstructionXORI, - InstructionORI, - InstructionANDI, - InstructionSLLI, - InstructionSRLI, - InstructionSRAI, InstructionLUI, - InstructionJAL, ) -from riscvmodel.model import Model -from riscvmodel.isa import Instruction, InstructionRType, get_insns -from riscvmodel.variant import RV32I class CoreTestElaboratable(Elaboratable): @@ -58,14 +46,12 @@ def elaborate(self, platform): ) self.core = Core(gen_params=self.gen_params, wb_instr_bus=wb_instr_bus, wb_data_bus=wb_data_bus) self.io_in = TestbenchIO(AdapterTrans(self.core.fetch_continue.method)) - self.rf_write = TestbenchIO(AdapterTrans(self.core.RF.write)) self.interrupt = TestbenchIO(AdapterTrans(self.core.interrupt_controller.report_interrupt)) m.submodules.wb_mem_slave = self.wb_mem_slave m.submodules.wb_mem_slave_data = self.wb_mem_slave_data m.submodules.c = self.core m.submodules.io_in = self.io_in - m.submodules.rf_write = self.rf_write m.submodules.interrupt = self.interrupt m.d.comb += wb_instr_bus.connect(self.wb_mem_slave.bus) @@ -74,53 +60,19 @@ def elaborate(self, platform): return m -def gen_riscv_add_instr(dst, src1, src2): - return 0b0110011 | dst << 7 | src1 << 15 | src2 << 20 - - -def gen_riscv_lui_instr(dst, imm): - return 0b0110111 | dst << 7 | imm << 12 - - class TestCoreBase(TestCaseWithSimulator): gen_params: GenParams m: CoreTestElaboratable - def check_RAT_alloc(self, rat, expected_alloc_count=None): # noqa: N802 - allocated = [] - for i in range(self.m.gen_params.isa.reg_cnt): - allocated.append((yield rat.entries[i])) - filtered_zeros = list(filter(lambda x: x != 0, allocated)) - - # check if 0th register is set to 0 - self.assertEqual(allocated[0], 0) - # check if there are no duplicate physical registers allocated for two different architectural registers - self.assertEqual(len(filtered_zeros), len(set(filtered_zeros))) - # check if the expected number of allocated registers matches reality - if expected_alloc_count: - self.assertEqual(len(filtered_zeros), expected_alloc_count) - def get_phys_reg_rrat(self, reg_id): return (yield self.m.core.RRAT.entries[reg_id]) - def get_phys_reg_frat(self, reg_id): - return (yield self.m.core.FRAT.entries[reg_id]) - def get_arch_reg_val(self, reg_id): return (yield self.m.core.RF.entries[(yield from self.get_phys_reg_rrat(reg_id))].reg_val) - def get_phys_reg_val(self, reg_id): - return (yield self.m.core.RF.entries[reg_id].reg_val) - def push_instr(self, opcode): yield from self.m.io_in.call(instr=opcode) - def compare_core_states(self, sw_core): - for i in range(self.gen_params.isa.reg_cnt): - reg_val = sw_core.state.intreg.regs[i].value - unsigned_val = reg_val & 0xFFFFFFFF - self.assertEqual((yield from self.get_arch_reg_val(i)), unsigned_val) - def push_register_load_imm(self, reg_id, val): addi_imm = signed_to_int(val & 0xFFF, 12) lui_imm = (val & 0xFFFFF000) >> 12 @@ -132,123 +84,6 @@ def push_register_load_imm(self, reg_id, val): yield from self.push_instr(InstructionADDI(reg_id, reg_id, addi_imm).encode()) -class TestCoreSimple(TestCoreBase): - def simple_test(self): - # this test first provokes allocation of physical registers, - # then sets the values in those registers, and finally runs - # an actual computation. - - # The test sets values in the reg file by hand - - # provoking allocation of physical register - for i in range(self.m.gen_params.isa.reg_cnt - 1): - yield from self.push_instr(gen_riscv_add_instr(i + 1, 0, 0)) - - # waiting for the retirement rat to be set - for i in range(100): - yield - - # checking if all registers have been allocated - yield from self.check_RAT_alloc(self.m.core.FRAT, 31) - yield from self.check_RAT_alloc(self.m.core.RRAT, 31) - - # writing values to physical registers - yield from self.m.rf_write.call(reg_id=(yield from self.get_phys_reg_rrat(1)), reg_val=1) - yield from self.m.rf_write.call(reg_id=(yield from self.get_phys_reg_rrat(2)), reg_val=2) - yield from self.m.rf_write.call(reg_id=(yield from self.get_phys_reg_rrat(3)), reg_val=3) - - # waiting for potential conflicts on rf_write - for i in range(10): - yield - - self.assertEqual((yield from self.get_arch_reg_val(1)), 1) - self.assertEqual((yield from self.get_arch_reg_val(2)), 2) - self.assertEqual((yield from self.get_arch_reg_val(3)), 3) - - # issuing actual instructions for the test - yield from self.push_instr(gen_riscv_add_instr(4, 1, 2)) - yield from self.push_instr(gen_riscv_add_instr(4, 3, 4)) - yield from self.push_instr(gen_riscv_lui_instr(5, 1)) - - # waiting for the instructions to be processed - for i in range(50): - yield - - self.assertEqual((yield from self.get_arch_reg_val(1)), 1) - self.assertEqual((yield from self.get_arch_reg_val(2)), 2) - self.assertEqual((yield from self.get_arch_reg_val(3)), 3) - # 1 + 2 + 3 = 6 - self.assertEqual((yield from self.get_arch_reg_val(4)), 6) - self.assertEqual((yield from self.get_arch_reg_val(5)), 1 << 12) - - def test_simple(self): - self.gen_params = GenParams(basic_core_config) - m = CoreTestElaboratable(self.gen_params) - self.m = m - - with self.run_simulation(m) as sim: - sim.add_sync_process(self.simple_test) - - -class TestCoreRandomized(TestCoreBase): - def randomized_input(self): - infloop_addr = (len(self.instr_mem) - 1) * 4 - # wait for PC to go past all instruction - while (yield self.m.core.fetch.pc) != infloop_addr: - yield - - # finish calculations - yield from self.tick(50) - - yield from self.compare_core_states(self.software_core) - - def test_randomized(self): - self.gen_params = GenParams(basic_core_config) - self.instr_count = 300 - random.seed(42) - - # cast is there to avoid stubbing riscvmodel - instructions = cast(list[type[Instruction]], get_insns(cls=InstructionRType, variant=RV32I)) - instructions += [ - InstructionADDI, - InstructionSLTI, - InstructionSLTIU, - InstructionXORI, - InstructionORI, - InstructionANDI, - InstructionSLLI, - InstructionSRLI, - InstructionSRAI, - InstructionLUI, - ] - - # allocate some random values for registers - init_instr_list = list( - InstructionADDI(rd=i, rs1=0, imm=random.randint(-(2**11), 2**11 - 1)) - for i in range(self.gen_params.isa.reg_cnt) - ) - - # generate random instruction stream - instr_list = list(random.choice(instructions)() for _ in range(self.instr_count)) - for instr in instr_list: - instr.randomize(RV32I) - - self.software_core = Model(RV32I) - self.software_core.execute(init_instr_list) - self.software_core.execute(instr_list) - - # We add JAL instruction at the end to effectively create a infinite loop at the end of the program. - all_instr = init_instr_list + instr_list + [InstructionJAL(rd=0, imm=0)] - - self.instr_mem = list(map(lambda x: x.encode(), all_instr)) - - m = CoreTestElaboratable(self.gen_params, instr_mem=self.instr_mem) - self.m = m - - with self.run_simulation(m) as sim: - sim.add_sync_process(self.randomized_input) - - class TestCoreAsmSourceBase(TestCoreBase): base_dir: str = "test/asm/" diff --git a/test/transactions/test_assign.py b/test/transactions/test_assign.py index 88ad6a412..47f72800b 100644 --- a/test/transactions/test_assign.py +++ b/test/transactions/test_assign.py @@ -1,7 +1,7 @@ from typing import Callable from amaranth import * from amaranth.lib import data -from amaranth.hdl.ast import ArrayProxy, Slice +from amaranth.hdl._ast import ArrayProxy, Slice from transactron.utils._typing import LayoutLike from transactron.utils import AssignType, assign diff --git a/test/transactron/testing/test_assertion.py b/test/transactron/testing/test_assertion.py index c5bc1284b..4becf3062 100644 --- a/test/transactron/testing/test_assertion.py +++ b/test/transactron/testing/test_assertion.py @@ -14,7 +14,7 @@ def elaborate(self, platform): m.d.comb += self.output.eq(self.input & ~self.input) - assertion(self.input == self.output) + assertion(m, self.input == self.output) return m diff --git a/transactron/core.py b/transactron/core.py index 3861b0a18..e85627437 100644 --- a/transactron/core.py +++ b/transactron/core.py @@ -21,7 +21,7 @@ from amaranth import * from amaranth import tracer from itertools import count, chain, filterfalse, product -from amaranth.hdl.dsl import FSM +from amaranth.hdl._dsl import FSM from transactron.utils.assign import AssignArg diff --git a/transactron/graph.py b/transactron/graph.py index 4cd51d067..024e9bb0b 100644 --- a/transactron/graph.py +++ b/transactron/graph.py @@ -6,7 +6,7 @@ from collections import defaultdict from typing import Literal, Optional, Protocol -from amaranth.hdl.ir import Elaboratable, Fragment +from amaranth import Elaboratable, Fragment from .tracing import TracingFragment diff --git a/transactron/lib/connectors.py b/transactron/lib/connectors.py index 98c839246..b9a6eb204 100644 --- a/transactron/lib/connectors.py +++ b/transactron/lib/connectors.py @@ -61,8 +61,6 @@ def elaborate(self, platform): m.submodules.fifo = fifo = self.fifoType(width=self.width, depth=self.depth) - assert fifo.fwft # the read method requires FWFT behavior - @def_method(m, self.write, ready=fifo.w_rdy) def _(arg): m.d.comb += fifo.w_en.eq(1) diff --git a/transactron/lib/reqres.py b/transactron/lib/reqres.py index 80b752c49..518d53443 100644 --- a/transactron/lib/reqres.py +++ b/transactron/lib/reqres.py @@ -147,7 +147,7 @@ def __init__( self.depth = depth - self.id_layout = [("id", log2_int(self.port_count))] + self.id_layout = [("id", exact_log2(self.port_count))] self.clear = Method() self.serialize_in = [ diff --git a/transactron/testing/assertion.py b/transactron/testing/assertion.py index 8ae9bdf0d..19c5a4149 100644 --- a/transactron/testing/assertion.py +++ b/transactron/testing/assertion.py @@ -2,21 +2,18 @@ from typing import Any from amaranth.sim import Passive, Tick from transactron.utils import assert_bit, assert_bits -from transactron.utils.dependencies import DependencyContext __all__ = ["make_assert_handler"] def make_assert_handler(my_assert: Callable[[int, str], Any]): - dependency_manager = DependencyContext.get() - def assert_handler(): yield Passive() while True: yield Tick("sync_neg") - if not (yield assert_bit(dependency_manager)): - for v, (n, i) in assert_bits(dependency_manager): + if not (yield assert_bit()): + for v, (n, i) in assert_bits(): my_assert((yield v), f"Assertion at {n}:{i}") yield diff --git a/transactron/testing/functions.py b/transactron/testing/functions.py index a8d64af8e..7d5bcfb92 100644 --- a/transactron/testing/functions.py +++ b/transactron/testing/functions.py @@ -1,5 +1,4 @@ from amaranth import * -from amaranth.hdl.ast import Statement from amaranth.lib.data import Layout, StructLayout, View from amaranth.sim.core import Command from typing import TypeVar, Any, Generator, TypeAlias, TYPE_CHECKING, Union @@ -7,11 +6,12 @@ if TYPE_CHECKING: + from amaranth.hdl._ast import Statement from .infrastructure import CoreblocksCommand T = TypeVar("T") -TestGen: TypeAlias = Generator[Union[Command, Value, Statement, "CoreblocksCommand", None], Any, T] +TestGen: TypeAlias = Generator[Union[Command, Value, "Statement", "CoreblocksCommand", None], Any, T] def set_inputs(values: RecordValueDict, field: View) -> TestGen[None]: diff --git a/transactron/tracing.py b/transactron/tracing.py index 036044aed..f418915cb 100644 --- a/transactron/tracing.py +++ b/transactron/tracing.py @@ -4,15 +4,19 @@ import warnings -from amaranth.hdl.ir import Elaboratable, Fragment, Instance -from amaranth.hdl.xfrm import FragmentTransformer -from amaranth.hdl import dsl, ir, mem, xfrm +from amaranth.hdl import Elaboratable, Fragment, Instance +from amaranth.hdl._xfrm import FragmentTransformer +from amaranth.hdl import _dsl, _ir, _mem, _xfrm from transactron.utils import HasElaborate from . import core # generic tuple because of aggressive monkey-patching -modules_with_fragment: tuple = core, ir, dsl, mem, xfrm +modules_with_fragment: tuple = core, _ir, _dsl, _mem, _xfrm +# List of Fragment subclasses which should be patched to inherit from TracingFragment. +# The first element of the tuple is a subclass name to patch, and the second element +# of the tuple is tuple with modules in which the patched subclass should be installed. +fragment_subclasses_to_patch = [("MemoryInstance", (_mem, _xfrm))] DIAGNOSTICS = False orig_on_fragment = FragmentTransformer.on_fragment @@ -22,13 +26,34 @@ class TracingEnabler: def __enter__(self): self.orig_fragment_get = Fragment.get self.orig_on_fragment = FragmentTransformer.on_fragment - self.orig_fragment_class = ir.Fragment - self.orig_instance_class = ir.Instance + self.orig_fragment_class = _ir.Fragment + self.orig_instance_class = _ir.Instance + self.orig_patched_fragment_subclasses = [] Fragment.get = TracingFragment.get FragmentTransformer.on_fragment = TracingFragmentTransformer.on_fragment for mod in modules_with_fragment: mod.Fragment = TracingFragment mod.Instance = TracingInstance + for class_name, modules in fragment_subclasses_to_patch: + orig_fragment_subclass = getattr(modules[0], class_name) + # `type` is used to declare new class dynamicaly. There is passed `orig_fragment_subclass` as a first + # base class to allow `super()` to work. Calls to `super` without arguments are syntax sugar and are + # extended on compile/interpretation (not execution!) phase to the `super(OriginalClass, self)`, + # so they are hardcoded on execution time to look for the original class + # (see: https://docs.python.org/3/library/functions.html#super). + # This cause that OriginalClass has to be in `__mro__` of the newly created class, because else an + # TypeError will be raised (see: https://stackoverflow.com/a/40819403). Adding OriginalClass to the + # bases of patched class allows us to fix the TypeError. Everything works correctly because `super` + # starts search of `__mro__` from the class right after the first argument. In our case the first + # checked class will be `TracingFragment` as we want. + newclass = type( + class_name, + (orig_fragment_subclass, TracingFragment, ), + dict(orig_fragment_subclass.__dict__) + ) + for mod in modules: + setattr(mod, class_name, newclass) + self.orig_patched_fragment_subclasses.append((class_name, orig_fragment_subclass, modules)) def __exit__(self, tp, val, tb): Fragment.get = self.orig_fragment_get @@ -36,6 +61,9 @@ def __exit__(self, tp, val, tb): for mod in modules_with_fragment: mod.Fragment = self.orig_fragment_class mod.Instance = self.orig_instance_class + for class_name, orig_fragment_subclass, modules in self.orig_patched_fragment_subclasses: + for mod in modules: + setattr(mod, class_name, orig_fragment_subclass) class TracingFragmentTransformer(FragmentTransformer): diff --git a/transactron/utils/_typing.py b/transactron/utils/_typing.py index 806fab6df..8f42c1910 100644 --- a/transactron/utils/_typing.py +++ b/transactron/utils/_typing.py @@ -12,16 +12,21 @@ runtime_checkable, Union, Any, + TYPE_CHECKING, ) from collections.abc import Iterable, Mapping, Sequence from contextlib import AbstractContextManager from enum import Enum from amaranth import * from amaranth.lib.data import StructLayout, View -from amaranth.hdl.ast import ShapeCastable, Statement, ValueCastable -from amaranth.hdl.dsl import _ModuleBuilderSubmodules, _ModuleBuilderDomainSet, _ModuleBuilderDomain, FSM +from amaranth.hdl import ShapeCastable, ValueCastable from amaranth.hdl.rec import Direction, Layout +if TYPE_CHECKING: + from amaranth.hdl._ast import Statement + from amaranth.hdl._dsl import _ModuleBuilderSubmodules, _ModuleBuilderDomainSet, _ModuleBuilderDomain + import amaranth.hdl._dsl + __all__ = [ "FragmentLike", "ValueLike", @@ -53,7 +58,7 @@ FragmentLike: TypeAlias = Fragment | Elaboratable ValueLike: TypeAlias = Value | int | Enum | ValueCastable ShapeLike: TypeAlias = Shape | ShapeCastable | int | range | type[Enum] -StatementLike: TypeAlias = Statement | Iterable["StatementLike"] +StatementLike: TypeAlias = Union["Statement", Iterable["StatementLike"]] LayoutLike: TypeAlias = ( Layout | Sequence[tuple[str, "ShapeLike | LayoutLike"] | tuple[str, "ShapeLike | LayoutLike", Direction]] ) @@ -82,16 +87,16 @@ class _ModuleBuilderDomainsLike(Protocol): - def __getattr__(self, name: str) -> _ModuleBuilderDomain: + def __getattr__(self, name: str) -> "_ModuleBuilderDomain": ... - def __getitem__(self, name: str) -> _ModuleBuilderDomain: + def __getitem__(self, name: str) -> "_ModuleBuilderDomain": ... - def __setattr__(self, name: str, value: _ModuleBuilderDomain) -> None: + def __setattr__(self, name: str, value: "_ModuleBuilderDomain") -> None: ... - def __setitem__(self, name: str, value: _ModuleBuilderDomain) -> None: + def __setitem__(self, name: str, value: "_ModuleBuilderDomain") -> None: ... @@ -99,8 +104,8 @@ def __setitem__(self, name: str, value: _ModuleBuilderDomain) -> None: class ModuleLike(Protocol, Generic[_T_ModuleBuilderDomains]): - submodules: _ModuleBuilderSubmodules - domains: _ModuleBuilderDomainSet + submodules: "_ModuleBuilderSubmodules" + domains: "_ModuleBuilderDomainSet" d: _T_ModuleBuilderDomains def If(self, cond: ValueLike) -> AbstractContextManager[None]: # noqa: N802 @@ -123,7 +128,7 @@ def Default(self) -> AbstractContextManager[None]: # noqa: N802 def FSM( # noqa: N802 self, reset: Optional[str] = ..., domain: str = ..., name: str = ... - ) -> AbstractContextManager[FSM]: + ) -> AbstractContextManager["amaranth.hdl._dsl.FSM"]: ... def State(self, name: str) -> AbstractContextManager[None]: # noqa: N802 diff --git a/transactron/utils/amaranth_ext/elaboratables.py b/transactron/utils/amaranth_ext/elaboratables.py index b60232e43..3af4ded98 100644 --- a/transactron/utils/amaranth_ext/elaboratables.py +++ b/transactron/utils/amaranth_ext/elaboratables.py @@ -44,7 +44,7 @@ def OneHotSwitch(m: ModuleLike, test: Value): @contextmanager def case(n: Optional[int] = None): if n is None: - with m.Case(): + with m.Default(): yield else: # find the index of the least significant bit set diff --git a/transactron/utils/amaranth_ext/functions.py b/transactron/utils/amaranth_ext/functions.py index fba8bb8ed..d09c7b53b 100644 --- a/transactron/utils/amaranth_ext/functions.py +++ b/transactron/utils/amaranth_ext/functions.py @@ -1,5 +1,5 @@ from amaranth import * -from amaranth.utils import bits_for, log2_int +from amaranth.utils import bits_for, exact_log2 from amaranth.lib import data from collections.abc import Iterable, Mapping from transactron.utils._typing import SignalBundle @@ -54,7 +54,7 @@ def iter(s: Value, step: int) -> Value: return result try: - xlen_log = log2_int(len(s)) + xlen_log = exact_log2(len(s)) except ValueError: raise NotImplementedError("CountLeadingZeros - only sizes aligned to power of 2 are supperted") @@ -71,7 +71,7 @@ def iter(s: Value, step: int) -> Value: def count_trailing_zeros(s: Value) -> Value: try: - log2_int(len(s)) + exact_log2(len(s)) except ValueError: raise NotImplementedError("CountTrailingZeros - only sizes aligned to power of 2 are supperted") diff --git a/transactron/utils/assertion.py b/transactron/utils/assertion.py index 537fbd0b5..b79a74fef 100644 --- a/transactron/utils/assertion.py +++ b/transactron/utils/assertion.py @@ -4,17 +4,18 @@ import operator from dataclasses import dataclass from transactron.utils import SrcLoc -from transactron.utils.dependencies import DependencyContext, DependencyManager, ListKey +from transactron.utils._typing import ModuleLike, ValueLike +from transactron.utils.dependencies import DependencyContext, ListKey __all__ = ["AssertKey", "assertion", "assert_bit", "assert_bits"] @dataclass(frozen=True) -class AssertKey(ListKey[tuple[Value, SrcLoc]]): +class AssertKey(ListKey[tuple[Signal, SrcLoc]]): pass -def assertion(value: Value, *, src_loc_at: int = 0): +def assertion(m: ModuleLike, value: ValueLike, *, src_loc_at: int = 0): """Define an assertion. This function might help find some hardware bugs which might otherwise be @@ -24,6 +25,8 @@ def assertion(value: Value, *, src_loc_at: int = 0): Parameters ---------- + m: Module + Module in which the assertion is defined. value : Value If the value of this Amaranth expression is false, the assertion will fail. @@ -32,35 +35,26 @@ def assertion(value: Value, *, src_loc_at: int = 0): identify the failing assertion. """ src_loc = get_src_loc(src_loc_at) + sig = Signal() + m.d.comb += sig.eq(value) dependencies = DependencyContext.get() - dependencies.add_dependency(AssertKey(), (value, src_loc)) + dependencies.add_dependency(AssertKey(), (sig, src_loc)) -def assert_bits(dependencies: DependencyManager) -> list[tuple[Value, SrcLoc]]: +def assert_bits() -> list[tuple[Signal, SrcLoc]]: """Gets assertion bits. This function returns all the assertion signals created by `assertion`, together with their source locations. - - Parameters - ---------- - dependencies : DependencyManager - The assertion feature uses the `DependencyManager` to store - assertions. """ + dependencies = DependencyContext.get() return dependencies.get_dependency(AssertKey()) -def assert_bit(dependencies: DependencyManager) -> Value: +def assert_bit() -> Signal: """Gets assertion bit. The signal returned by this function is false if and only if there exists a false signal among assertion bits created by `assertion`. - - Parameters - ---------- - dependencies : DependencyManager - The assertion feature uses the `DependencyManager` to store - assertions. """ - return reduce(operator.and_, [a[0] for a in assert_bits(dependencies)], C(1)) + return reduce(operator.and_, [a[0] for a in assert_bits()], C(1)) diff --git a/transactron/utils/assign.py b/transactron/utils/assign.py index e2b528828..b3bee191e 100644 --- a/transactron/utils/assign.py +++ b/transactron/utils/assign.py @@ -1,11 +1,14 @@ from enum import Enum -from typing import Optional, TypeAlias, cast +from typing import Optional, TypeAlias, cast, TYPE_CHECKING from collections.abc import Iterable, Mapping from amaranth import * -from amaranth.hdl.ast import Assign, ArrayProxy +from amaranth.hdl._ast import ArrayProxy from amaranth.lib import data from ._typing import ValueLike +if TYPE_CHECKING: + from amaranth.hdl._ast import Assign + __all__ = [ "AssignType", "assign", @@ -50,7 +53,7 @@ def assign_arg_fields(val: AssignArg) -> Optional[set[str]]: def assign( lhs: AssignArg, rhs: AssignArg, *, fields: AssignFields = AssignType.RHS, lhs_strict=False, rhs_strict=False -) -> Iterable[Assign]: +) -> Iterable["Assign"]: """Safe structured assignment. This function recursively generates assignment statements for diff --git a/transactron/utils/gen.py b/transactron/utils/gen.py index 2258255ee..9674ae5ad 100644 --- a/transactron/utils/gen.py +++ b/transactron/utils/gen.py @@ -3,18 +3,25 @@ from amaranth import * from amaranth.back import verilog -from amaranth.hdl import ir -from amaranth.hdl.ast import SignalDict +from amaranth.hdl import Fragment from transactron.core import TransactionManager, MethodMap, TransactionManagerKey from transactron.lib.metrics import HardwareMetricsManager from transactron.utils.dependencies import DependencyContext from transactron.utils.idgen import IdGenerator from transactron.profiler import ProfileData +from transactron.utils._typing import SrcLoc +from transactron.utils.assertion import assert_bits + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from amaranth.hdl._ast import SignalDict __all__ = [ "MetricLocation", + "AssertLocation", "GenerationInfo", "generate_verilog", ] @@ -50,6 +57,25 @@ class MethodSignalsLocation: run: list[str] +@dataclass_json +@dataclass +class AssertLocation: + """Information about an assert signal in the generated Verilog code. + + Attributes + ---------- + location : list[str] + The location of the assert signal. The location is a list of Verilog + identifiers that denote a path consisting of module names (and the + signal name at the end) leading to the signal wire. + src_loc : SrcLoc + Source location of the assertion. + """ + + location: list[str] + src_loc: SrcLoc + + @dataclass_json @dataclass class GenerationInfo: @@ -60,12 +86,16 @@ class GenerationInfo: metrics_location : dict[str, MetricInfo] Mapping from a metric name to an object storing Verilog locations of its registers. + asserts : list[AssertLocation] + Locations and metadata for assertion signals. """ metrics_location: dict[str, MetricLocation] transaction_signals_location: dict[int, TransactionSignalsLocation] method_signals_location: dict[int, MethodSignalsLocation] profile_data: ProfileData + metrics_location: dict[str, MetricLocation] + asserts: list[AssertLocation] def encode(self, file_name: str): """ @@ -108,7 +138,7 @@ def escape_verilog_identifier(identifier: str) -> str: return identifier -def get_signal_location(signal: Signal, name_map: SignalDict) -> list[str]: +def get_signal_location(signal: Signal, name_map: "SignalDict") -> list[str]: raw_location = name_map[signal] # Amaranth escapes identifiers when generating Verilog code, but returns non-escaped identifiers @@ -116,7 +146,7 @@ def get_signal_location(signal: Signal, name_map: SignalDict) -> list[str]: return [escape_verilog_identifier(component) for component in raw_location] -def collect_metric_locations(name_map: SignalDict) -> dict[str, MetricLocation]: +def collect_metric_locations(name_map: "SignalDict") -> dict[str, MetricLocation]: metrics_location: dict[str, MetricLocation] = {} # Collect information about the location of metric registers in the generated code. @@ -134,7 +164,7 @@ def collect_metric_locations(name_map: SignalDict) -> dict[str, MetricLocation]: def collect_transaction_method_signals( - transaction_manager: TransactionManager, name_map: SignalDict + transaction_manager: TransactionManager, name_map: "SignalDict" ) -> tuple[dict[int, TransactionSignalsLocation], dict[int, MethodSignalsLocation]]: transaction_signals_location: dict[int, TransactionSignalsLocation] = {} method_signals_location: dict[int, MethodSignalsLocation] = {} @@ -157,10 +187,19 @@ def collect_transaction_method_signals( return (transaction_signals_location, method_signals_location) +def collect_asserts(name_map: "SignalDict") -> list[AssertLocation]: + asserts: list[AssertLocation] = [] + + for v, src_loc in assert_bits(): + asserts.append(AssertLocation(get_signal_location(v, name_map), src_loc)) + + return asserts + + def generate_verilog( top_module: Elaboratable, ports: list[Signal], top_name: str = "top" ) -> tuple[str, GenerationInfo]: - fragment = ir.Fragment.get(top_module, platform=None).prepare(ports=ports) + fragment = Fragment.get(top_module, platform=None).prepare(ports=ports) verilog_text, name_map = verilog.convert_fragment(fragment, name=top_name, emit_src=True, strip_internal_attrs=True) transaction_manager = DependencyContext.get().get_dependency(TransactionManagerKey()) @@ -173,6 +212,7 @@ def generate_verilog( transaction_signals_location=transaction_signals, method_signals_location=method_signals, profile_data=profile_data, + asserts=collect_asserts(name_map), ) return verilog_text, gen_info