diff --git a/docs/cpuif/internal_protocol.rst b/docs/cpuif/internal_protocol.rst index a49c558..dc9fc68 100644 --- a/docs/cpuif/internal_protocol.rst +++ b/docs/cpuif/internal_protocol.rst @@ -36,7 +36,8 @@ cpuif_wr_data cpuif_wr_biten Active-high bit-level write-enable strobes. - Only asserted bit positions will change the register value during a write transfer. + Only asserted bit positions will change the register value during a write + transfer. cpuif_req_stall_rd If asserted, and the next pending request is a read operation, then the diff --git a/docs/index.rst b/docs/index.rst index 11ccfdf..b34824a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -31,15 +31,17 @@ Install from `PyPi`_ using pip -Quick Start ------------ +Quick Start - PeakRDL +--------------------- The easiest way is to use the `PeakRDL command line tool `_: .. code-block:: bash - peakrdl regblock atxmega_spi.rdl -o regblock/ --cpuif apb3-flat + peakrdl regblock atxmega_spi.rdl -o regblock/ --cpuif axi4-lite +Quick Start - API +----------------- Otherwise if you want, there is a Python API. Below is a simple example that demonstrates how to generate a SystemVerilog implementation from SystemRDL source. @@ -49,7 +51,7 @@ implementation from SystemRDL source. from systemrdl import RDLCompiler, RDLCompileError from peakrdl_regblock import RegblockExporter - from peakrdl_regblock.cpuif.apb3 import APB3_Cpuif + from peakrdl_regblock.cpuif.axi4lite import AXI4Lite_Cpuif from peakrdl_regblock.udps import ALL_UDPS input_files = [ @@ -78,7 +80,7 @@ implementation from SystemRDL source. exporter = RegblockExporter() exporter.export( root, "path/to/output_dir", - cpuif_cls=APB3_Cpuif + cpuif_cls=AXI4Lite_Cpuif ) @@ -99,8 +101,8 @@ Links self architecture hwif - api configuring + api limitations licensing @@ -125,6 +127,12 @@ Links props/signal props/rhs_props +.. toctree:: + :hidden: + :caption: Other SystemRDL Features + + rdl_features/external + .. toctree:: :hidden: :caption: Extended Properties diff --git a/docs/licensing.rst b/docs/licensing.rst index 94ab1e1..71d9c16 100644 --- a/docs/licensing.rst +++ b/docs/licensing.rst @@ -47,4 +47,4 @@ workplace. This is totally OK, as long as you don't start distributing it outside your workplace in ways that violate the GPL v3 license. That said, I'd encourage you to check out the `PeakRDL command line tool `_. -It might already do everything you need. +It may already do everything you need. diff --git a/docs/limitations.rst b/docs/limitations.rst index bdd97b3..994ca24 100644 --- a/docs/limitations.rst +++ b/docs/limitations.rst @@ -6,11 +6,6 @@ supported properties, see the appropriate property listing page in the following sections. -External Components -------------------- -Regfiles, registers & fields instantiated using the ``external`` keyword are not supported yet. - - Alias Registers --------------- Registers instantiated using the ``alias`` keyword are not supported yet. diff --git a/docs/rdl_features/external.rst b/docs/rdl_features/external.rst new file mode 100644 index 0000000..aea8e13 --- /dev/null +++ b/docs/rdl_features/external.rst @@ -0,0 +1,133 @@ +External Components +=================== +SystemRDL allows some component instances to be defined as "external" elements +of an address space definition. In the context of this regblock generator, +the implementation of an external component is left up to the designer. When +generating the RTL for a regblock, the implementations of external components +are omitted and instead a user-interface is presented on the +``hwif_in``/``hwif_out`` i/o structs. + +External component signals on the hardware interface closely follow the semantics +of the :ref:`cpuif_protocol`. + + +Things you should know +---------------------- + +* By default external ``hwif_out`` signals are driven combinationally. An + optional output retiming stage can be enabled if needed. +* Due to the uncertain access latency of external components, the regblock will + always enforce that only one outstanding transaction to an external component + at a time. This is enforced even if the CPUIF is capable of pipelined accesses + such as AXI4-Lite. + + +External Registers +------------------ +External registers can be useful if it is necessary to implement a register that +cannot easily be expressed using SystemRDL semantics. This could be a unique +access policy, or FIFO-like push/pop registers. + +External registers are annotated as such by using the ``external`` keyword: + +.. code-block:: systemrdl + + # An internal register + my_reg int_reg; + + # An external register + external my_reg ext_reg; + +Request +^^^^^^^ +hwif_out..req + When asserted, a read or write transfer will be initiated. + Qualifies all other request signals. + + If a register is wide (``regwidth`` > ``accesswidth``), then the + ``hwif_out..req`` will consist of multiple bits, representing the access + strobe for each sub-word of the register. + +hwif_out..req_is_wr + If ``1``, denotes that the current transfer is a write. Otherwise transfer is + a read. + +hwif_out..wr_data + Data to be written for the write transfer. This signal is ignored for read + transfers. + + The bit-width of this signal always matches the CPUIF's bus width, + regardless of the regwidth. + +hwif_out..wr_biten + Active-high bit-level write-enable strobes. + Only asserted bit positions will change the register value during a write + transfer. + + +Read Response +^^^^^^^^^^^^^ +hwif_in..rd_ack + Single-cycle strobe indicating a read transfer has completed. + Qualifies all other read response signals. + +hwif_in..rd_data + Read response data. + +Write Response +^^^^^^^^^^^^^^ +hwif_in..wr_ack + Single-cycle strobe indicating a write transfer has completed. + + + +External Blocks +--------------- +Broader external address regions can be represented by external block-like +components such as ``addrmap``, ``regfile`` or ``mem`` elements. + +To ensure address decoding for external blocks is simple (only requires simple bit-pruning), +blocks that are external to an exported regblock shall be aligned to their size. + +Request +^^^^^^^ +hwif_out..req + When asserted, a read or write transfer will be initiated. + Qualifies all other request signals. + +hwif_out..addr + Byte-address of the transfer. + + Address is always relative to the block's local addressing. i.e: The first + byte within an external block is represented as ``hwif_out..addr`` == 0, + regardless of the absolute address of the block. + +hwif_out..req_is_wr + If ``1``, denotes that the current transfer is a write. Otherwise transfer is + a read. + +hwif_out..wr_data + Data to be written for the write transfer. This signal is ignored for read + transfers. + + The bit-width of this signal always matches the CPUIF's bus width, + regardless of the contents of the block. + +hwif_out..wr_biten + Active-high bit-level write-enable strobes. + Only asserted bit positions will change the register value during a write + transfer. + +Read Response +^^^^^^^^^^^^^ +hwif_in..rd_ack + Single-cycle strobe indicating a read transfer has completed. + Qualifies all other read response signals. + +hwif_in..rd_data + Read response data. + +Write Response +^^^^^^^^^^^^^^ +hwif_in..wr_ack + Single-cycle strobe indicating a write transfer has completed. diff --git a/docs/udps/extended_swacc.rst b/docs/udps/extended_swacc.rst index 0c03197..7280a77 100644 --- a/docs/udps/extended_swacc.rst +++ b/docs/udps/extended_swacc.rst @@ -8,7 +8,7 @@ read and write operations - it is asserted on *all* software accesses. Similarly, the spec defines ``swmod`` which gets asserted on software writes, but can also get asserted if the field has on-read side-effects. -What if you just wanted a plan and simple strobe that is asserted when software +What if you just wanted a plain and simple strobe that is asserted when software reads or writes a field? The ``rd_swacc`` and ``wr_swacc`` UDPs provide this functionality. diff --git a/src/peakrdl_regblock/__peakrdl__.py b/src/peakrdl_regblock/__peakrdl__.py index 967475f..30dadc2 100644 --- a/src/peakrdl_regblock/__peakrdl__.py +++ b/src/peakrdl_regblock/__peakrdl__.py @@ -1,5 +1,6 @@ from typing import TYPE_CHECKING, Dict, Type import functools +import sys from peakrdl.plugins.exporter import ExporterSubcommandPlugin #pylint: disable=import-error from peakrdl.config import schema #pylint: disable=import-error @@ -94,6 +95,10 @@ class Exporter(ExporterSubcommandPlugin): default=False, help="Enable additional retiming stage between readback fan-in and cpu interface" ) + arg_group.add_argument( + "--rt-external", + help="Retime outputs to external components. Specify a comma-separated list of: reg,regfile,mem,addrmap,all" + ) arg_group.add_argument( "--type-style", dest="type_style", @@ -125,6 +130,29 @@ class Exporter(ExporterSubcommandPlugin): def do_export(self, top_node: 'AddrmapNode', options: 'argparse.Namespace') -> None: cpuifs = self.get_cpuifs() + retime_external_reg = False + retime_external_regfile = False + retime_external_mem = False + retime_external_addrmap = False + if options.rt_external: + for key in options.rt_external.split(","): + key = key.strip().lower() + if key == "reg": + retime_external_reg = True + elif key == "regfile": + retime_external_regfile = True + elif key == "mem": + retime_external_mem = True + elif key == "addrmap": + retime_external_addrmap = True + elif key == "all": + retime_external_reg = True + retime_external_regfile = True + retime_external_mem = True + retime_external_addrmap = True + else: + print("error: invalid option for --rt-external: '%s'" % key, file=sys.stderr) + x = RegblockExporter() x.export( top_node, @@ -135,6 +163,10 @@ class Exporter(ExporterSubcommandPlugin): reuse_hwif_typedefs=(options.type_style == "lexical"), retime_read_fanin=options.rt_read_fanin, retime_read_response=options.rt_read_response, + retime_external_reg=retime_external_reg, + retime_external_regfile=retime_external_regfile, + retime_external_mem=retime_external_mem, + retime_external_addrmap=retime_external_addrmap, generate_hwif_report=options.hwif_report, address_width=options.addr_width, ) diff --git a/src/peakrdl_regblock/addr_decode.py b/src/peakrdl_regblock/addr_decode.py index 48361d3..b38bf0d 100644 --- a/src/peakrdl_regblock/addr_decode.py +++ b/src/peakrdl_regblock/addr_decode.py @@ -1,6 +1,7 @@ -from typing import TYPE_CHECKING, Union, List +from typing import TYPE_CHECKING, Union, List, Optional -from systemrdl.node import AddrmapNode, AddressableNode, RegNode, FieldNode +from systemrdl.node import FieldNode, RegNode +from systemrdl.walker import WalkerAction from .utils import get_indexed_path from .struct_generator import RDLStructGenerator @@ -9,13 +10,15 @@ from .identifier_filter import kw_filter as kwf if TYPE_CHECKING: from .exporter import RegblockExporter + from systemrdl.node import AddrmapNode, AddressableNode + from systemrdl.node import RegfileNode, MemNode class AddressDecode: def __init__(self, exp:'RegblockExporter'): self.exp = exp @property - def top_node(self) -> AddrmapNode: + def top_node(self) -> 'AddrmapNode': return self.exp.top_node def get_strobe_struct(self) -> str: @@ -59,9 +62,57 @@ class AddressDecode: return "decoded_reg_strb." + path + def get_external_block_access_strobe(self, node: 'AddressableNode') -> str: + assert node.external + assert not isinstance(node, RegNode) + path = get_indexed_path(self.top_node, node) + return "decoded_reg_strb." + path + class DecodeStructGenerator(RDLStructGenerator): + def _enter_external_block(self, node: 'AddressableNode') -> None: + self.add_member( + kwf(node.inst_name), + array_dimensions=node.array_dimensions, + ) + + def enter_Addrmap(self, node: 'AddrmapNode') -> Optional[WalkerAction]: + if node.external: + self._enter_external_block(node) + return WalkerAction.SkipDescendants + super().enter_Addrmap(node) + return WalkerAction.Continue + + def exit_Addrmap(self, node: 'AddrmapNode') -> None: + if node.external: + return + super().exit_Addrmap(node) + + def enter_Regfile(self, node: 'RegfileNode') -> Optional[WalkerAction]: + if node.external: + self._enter_external_block(node) + return WalkerAction.SkipDescendants + super().enter_Regfile(node) + return WalkerAction.Continue + + def exit_Regfile(self, node: 'RegfileNode') -> None: + if node.external: + return + super().exit_Regfile(node) + + def enter_Mem(self, node: 'MemNode') -> Optional[WalkerAction]: + if node.external: + self._enter_external_block(node) + return WalkerAction.SkipDescendants + super().enter_Mem(node) + return WalkerAction.Continue + + def exit_Mem(self, node: 'MemNode') -> None: + if node.external: + return + super().exit_Mem(node) + def enter_Reg(self, node: 'RegNode') -> None: # if register is "wide", expand the strobe to be able to access the sub-words n_subwords = node.get_property("regwidth") // node.get_property("accesswidth") @@ -89,23 +140,32 @@ class DecodeLogicGenerator(RDLForLoopGenerator): self._array_stride_stack = [] # type: List[List[int]] - def enter_AddressableComponent(self, node: 'AddressableNode') -> None: + def enter_AddressableComponent(self, node: 'AddressableNode') -> Optional[WalkerAction]: super().enter_AddressableComponent(node) - if not node.is_array: - return + if node.is_array: + # Collect strides for each array dimension + current_stride = node.array_stride + strides = [] + for dim in reversed(node.array_dimensions): + strides.append(current_stride) + current_stride *= dim + strides.reverse() + self._array_stride_stack.extend(strides) - # Collect strides for each array dimension - current_stride = node.array_stride - strides = [] - for dim in reversed(node.array_dimensions): - strides.append(current_stride) - current_stride *= dim - strides.reverse() - self._array_stride_stack.extend(strides) + if node.external and not isinstance(node, RegNode): + # Is an external block + addr_str = self._get_address_str(node) + strb = self.addr_decode.get_external_block_access_strobe(node) + rhs = f"cpuif_req_masked & (cpuif_addr >= {addr_str}) & (cpuif_addr <= {addr_str} + 'h{(node.size - 1):x})" + self.add_content(f"{strb} = {rhs};") + self.add_content(f"is_external |= {rhs};") + return WalkerAction.SkipDescendants + + return WalkerAction.Continue - def _get_address_str(self, node:AddressableNode, subword_offset: int=0) -> str: + def _get_address_str(self, node: 'AddressableNode', subword_offset: int=0) -> str: a = f"'h{(node.raw_absolute_address - self.addr_decode.top_node.raw_absolute_address + subword_offset):x}" for i, stride in enumerate(self._array_stride_stack): a += f" + i{i}*'h{stride:x}" @@ -117,16 +177,21 @@ class DecodeLogicGenerator(RDLForLoopGenerator): accesswidth = node.get_property('accesswidth') if regwidth == accesswidth: - s = f"{self.addr_decode.get_access_strobe(node)} = cpuif_req_masked & (cpuif_addr == {self._get_address_str(node)});" + rhs = f"cpuif_req_masked & (cpuif_addr == {self._get_address_str(node)})" + s = f"{self.addr_decode.get_access_strobe(node)} = {rhs};" self.add_content(s) + if node.external: + self.add_content(f"is_external |= {rhs};") else: # Register is wide. Create a substrobe for each subword n_subwords = regwidth // accesswidth subword_stride = accesswidth // 8 for i in range(n_subwords): - s = f"{self.addr_decode.get_access_strobe(node)}[{i}] = cpuif_req_masked & (cpuif_addr == {self._get_address_str(node, subword_offset=(i*subword_stride))});" + rhs = f"cpuif_req_masked & (cpuif_addr == {self._get_address_str(node, subword_offset=(i*subword_stride))})" + s = f"{self.addr_decode.get_access_strobe(node)}[{i}] = {rhs};" self.add_content(s) - + if node.external: + self.add_content(f"is_external |= {rhs};") def exit_AddressableComponent(self, node: 'AddressableNode') -> None: super().exit_AddressableComponent(node) diff --git a/src/peakrdl_regblock/dereferencer.py b/src/peakrdl_regblock/dereferencer.py index d2538c3..3c17ebf 100644 --- a/src/peakrdl_regblock/dereferencer.py +++ b/src/peakrdl_regblock/dereferencer.py @@ -1,5 +1,5 @@ from typing import TYPE_CHECKING, Union, Optional -from systemrdl.node import AddrmapNode, FieldNode, SignalNode, RegNode +from systemrdl.node import AddrmapNode, FieldNode, SignalNode, RegNode, AddressableNode from systemrdl.rdltypes import PropertyReference if TYPE_CHECKING: @@ -205,6 +205,12 @@ class Dereferencer: """ return self.address_decode.get_access_strobe(obj, reduce_substrobes) + def get_external_block_access_strobe(self, obj: 'AddressableNode') -> str: + """ + Returns the Verilog string that represents the external block's access strobe + """ + return self.address_decode.get_external_block_access_strobe(obj) + def get_resetsignal(self, obj: Optional[SignalNode]) -> str: """ Returns a normalized active-high reset signal diff --git a/src/peakrdl_regblock/exporter.py b/src/peakrdl_regblock/exporter.py index 8f0dafa..dc748fa 100644 --- a/src/peakrdl_regblock/exporter.py +++ b/src/peakrdl_regblock/exporter.py @@ -18,6 +18,7 @@ from .cpuif.apb4 import APB4_Cpuif from .hwif import Hwif from .write_buffering import WriteBuffering from .read_buffering import ReadBuffering +from .external_acks import ExternalWriteAckGenerator, ExternalReadAckGenerator class RegblockExporter: def __init__(self, **kwargs: Any) -> None: @@ -29,12 +30,12 @@ class RegblockExporter: self.top_node = None # type: AddrmapNode self.hwif = None # type: Hwif self.cpuif = None # type: CpuifBase - self.address_decode = AddressDecode(self) - self.field_logic = FieldLogic(self) + self.address_decode = None # type: AddressDecode + self.field_logic = None # type: FieldLogic self.readback = None # type: Readback - self.write_buffering = WriteBuffering(self) - self.read_buffering = ReadBuffering(self) - self.dereferencer = Dereferencer(self) + self.write_buffering = None # type: WriteBuffering + self.read_buffering = None # type: ReadBuffering + self.dereferencer = None # type: Dereferencer self.min_read_latency = 0 self.min_write_latency = 0 @@ -97,6 +98,14 @@ class RegblockExporter: response path sequentially may not result in any meaningful timing improvement. Enabling this option will increase read transfer latency by 1 clock cycle. + retime_external_reg: bool + Retime outputs to external ``reg`` components. + retime_external_regfile: bool + Retime outputs to external ``regfile`` components. + retime_external_mem: bool + Retime outputs to external ``mem`` components. + retime_external_addrmap: bool + Retime outputs to external ``addrmap`` components. generate_hwif_report: bool If set, generates a hwif report that can help designers understand the contents of the ``hwif_in`` and ``hwif_out`` structures. @@ -121,7 +130,11 @@ class RegblockExporter: # Pipelining options retime_read_fanin = kwargs.pop("retime_read_fanin", False) # type: bool - retime_read_response = kwargs.pop("retime_read_response", True) # type: bool + retime_read_response = kwargs.pop("retime_read_response", False) # type: bool + retime_external_reg = kwargs.pop("retime_external_reg", False) # type: bool + retime_external_regfile = kwargs.pop("retime_external_regfile", False) # type: bool + retime_external_mem = kwargs.pop("retime_external_mem", False) # type: bool + retime_external_addrmap = kwargs.pop("retime_external_addrmap", False) # type: bool # Check for stray kwargs if kwargs: @@ -164,11 +177,26 @@ class RegblockExporter: user_enums=scanner.user_enums, reuse_typedefs=reuse_hwif_typedefs, hwif_report_file=hwif_report_file, + data_width=scanner.cpuif_data_width, ) self.readback = Readback( self, - retime_read_fanin + retime_read_fanin, + scanner.has_external_addressable ) + self.address_decode = AddressDecode(self) + self.field_logic = FieldLogic( + self, + retime_external_reg=retime_external_reg, + retime_external_regfile=retime_external_regfile, + retime_external_mem=retime_external_mem, + retime_external_addrmap=retime_external_addrmap, + ) + self.write_buffering = WriteBuffering(self) + self.read_buffering = ReadBuffering(self) + self.dereferencer = Dereferencer(self) + ext_write_acks = ExternalWriteAckGenerator(self) + ext_read_acks = ExternalReadAckGenerator(self) # Validate that there are no unsupported constructs validator = DesignValidator(self) @@ -181,6 +209,8 @@ class RegblockExporter: "has_writable_msb0_fields": scanner.has_writable_msb0_fields, "has_buffered_write_regs": scanner.has_buffered_write_regs, "has_buffered_read_regs": scanner.has_buffered_read_regs, + "has_external_addressable": scanner.has_external_addressable, + "has_external_block": scanner.has_external_block, "cpuif": self.cpuif, "hwif": self.hwif, "write_buffering": self.write_buffering, @@ -189,8 +219,11 @@ class RegblockExporter: "address_decode": self.address_decode, "field_logic": self.field_logic, "readback": self.readback, + "ext_write_acks": ext_write_acks, + "ext_read_acks": ext_read_acks, "get_always_ff_event": lambda resetsignal : get_always_ff_event(self.dereferencer, resetsignal), "retime_read_response": retime_read_response, + "retime_read_fanin": retime_read_fanin, "min_read_latency": self.min_read_latency, "min_write_latency": self.min_write_latency, "kwf": kwf, diff --git a/src/peakrdl_regblock/external_acks.py b/src/peakrdl_regblock/external_acks.py new file mode 100644 index 0000000..57b4eb8 --- /dev/null +++ b/src/peakrdl_regblock/external_acks.py @@ -0,0 +1,51 @@ +from typing import TYPE_CHECKING + +from systemrdl.walker import WalkerAction + +from .forloop_generator import RDLForLoopGenerator + +if TYPE_CHECKING: + from .exporter import RegblockExporter + from systemrdl.node import AddressableNode + + +class ExternalWriteAckGenerator(RDLForLoopGenerator): + def __init__(self, exp: 'RegblockExporter') -> None: + super().__init__() + self.exp = exp + + def get_implementation(self) -> str: + content = self.get_content(self.exp.top_node) + if content is None: + return "" + return content + + def enter_AddressableComponent(self, node: 'AddressableNode') -> WalkerAction: + super().enter_AddressableComponent(node) + + if node.external: + self.add_content(f"wr_ack |= {self.exp.hwif.get_external_wr_ack(node)};") + return WalkerAction.SkipDescendants + + return WalkerAction.Continue + + +class ExternalReadAckGenerator(RDLForLoopGenerator): + def __init__(self, exp: 'RegblockExporter') -> None: + super().__init__() + self.exp = exp + + def get_implementation(self) -> str: + content = self.get_content(self.exp.top_node) + if content is None: + return "" + return content + + def enter_AddressableComponent(self, node: 'AddressableNode') -> WalkerAction: + super().enter_AddressableComponent(node) + + if node.external: + self.add_content(f"rd_ack |= {self.exp.hwif.get_external_rd_ack(node)};") + return WalkerAction.SkipDescendants + + return WalkerAction.Continue diff --git a/src/peakrdl_regblock/field_logic/__init__.py b/src/peakrdl_regblock/field_logic/__init__.py index 701321d..304170b 100644 --- a/src/peakrdl_regblock/field_logic/__init__.py +++ b/src/peakrdl_regblock/field_logic/__init__.py @@ -20,8 +20,19 @@ if TYPE_CHECKING: from ..exporter import RegblockExporter class FieldLogic: - def __init__(self, exp:'RegblockExporter'): + def __init__( + self, + exp:'RegblockExporter', + retime_external_reg: bool, + retime_external_regfile: bool, + retime_external_mem: bool, + retime_external_addrmap: bool, + ): self.exp = exp + self.retime_external_reg = retime_external_reg + self.retime_external_regfile = retime_external_regfile + self.retime_external_mem = retime_external_mem + self.retime_external_addrmap = retime_external_addrmap self._hw_conditionals = {} # type: Dict[int, List[NextStateConditional]] self._sw_conditionals = {} # type: Dict[int, List[NextStateConditional]] diff --git a/src/peakrdl_regblock/field_logic/generators.py b/src/peakrdl_regblock/field_logic/generators.py index 4243964..96ec3a6 100644 --- a/src/peakrdl_regblock/field_logic/generators.py +++ b/src/peakrdl_regblock/field_logic/generators.py @@ -1,15 +1,18 @@ -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING, List, Optional from collections import OrderedDict +from systemrdl.walker import WalkerAction +from systemrdl.node import RegNode, RegfileNode, MemNode, AddrmapNode + from ..struct_generator import RDLStructGenerator from ..forloop_generator import RDLForLoopGenerator -from ..utils import get_always_ff_event +from ..utils import get_always_ff_event, get_indexed_path from ..identifier_filter import kw_filter as kwf if TYPE_CHECKING: from . import FieldLogic - from systemrdl.node import FieldNode, RegNode + from systemrdl.node import FieldNode, AddressableNode from .bases import SVLogic class CombinationalStructGenerator(RDLStructGenerator): @@ -18,6 +21,12 @@ class CombinationalStructGenerator(RDLStructGenerator): super().__init__() self.field_logic = field_logic + def enter_AddressableComponent(self, node: 'AddressableNode') -> Optional[WalkerAction]: + super().enter_AddressableComponent(node) + + if node.external: + return WalkerAction.SkipDescendants + return WalkerAction.Continue def enter_Field(self, node: 'FieldNode') -> None: # If a field doesn't implement storage, it is not relevant here @@ -67,6 +76,13 @@ class FieldStorageStructGenerator(RDLStructGenerator): super().__init__() self.field_logic = field_logic + def enter_AddressableComponent(self, node: 'AddressableNode') -> Optional[WalkerAction]: + super().enter_AddressableComponent(node) + + if node.external: + return WalkerAction.SkipDescendants + return WalkerAction.Continue + def enter_Field(self, node: 'FieldNode') -> None: self.push_struct(kwf(node.inst_name)) @@ -88,14 +104,39 @@ class FieldLogicGenerator(RDLForLoopGenerator): self.field_storage_template = self.exp.jj_env.get_template( "field_logic/templates/field_storage.sv" ) + self.external_reg_template = self.exp.jj_env.get_template( + "field_logic/templates/external_reg.sv" + ) + self.external_block_template = self.exp.jj_env.get_template( + "field_logic/templates/external_block.sv" + ) self.intr_fields = [] # type: List[FieldNode] self.halt_fields = [] # type: List[FieldNode] - def enter_Reg(self, node: 'RegNode') -> None: + def enter_AddressableComponent(self, node: 'AddressableNode') -> Optional[WalkerAction]: + super().enter_AddressableComponent(node) + + if node.external and not isinstance(node, RegNode): + # Is an external block + self.assign_external_block_outputs(node) + + # Do not recurse + return WalkerAction.SkipDescendants + + return WalkerAction.Continue + + def enter_Reg(self, node: 'RegNode') -> Optional[WalkerAction]: self.intr_fields = [] self.halt_fields = [] + if node.external: + self.assign_external_reg_outputs(node) + # Do not recurse to fields + return WalkerAction.SkipDescendants + + return WalkerAction.Continue + def enter_Field(self, node: 'FieldNode') -> None: if node.implements_storage: @@ -277,3 +318,49 @@ class FieldLogicGenerator(RDLForLoopGenerator): self.add_content( f"assign {output_identifier} = {value};" ) + + + def assign_external_reg_outputs(self, node: 'RegNode') -> None: + prefix = "hwif_out." + get_indexed_path(self.exp.top_node, node) + strb = self.exp.dereferencer.get_access_strobe(node) + + width = min(self.exp.cpuif.data_width, node.get_property('regwidth')) + if width != self.exp.cpuif.data_width: + bslice = f"[{width - 1}:0]" + else: + bslice = "" + + context = { + "prefix": prefix, + "strb": strb, + "bslice": bslice, + "retime": self.field_logic.retime_external_reg, + "get_always_ff_event": lambda resetsignal : get_always_ff_event(self.exp.dereferencer, resetsignal), + "get_resetsignal": self.exp.dereferencer.get_resetsignal, + "resetsignal": self.exp.top_node.cpuif_reset, + } + self.add_content(self.external_reg_template.render(context)) + + def assign_external_block_outputs(self, node: 'AddressableNode') -> None: + prefix = "hwif_out." + get_indexed_path(self.exp.top_node, node) + strb = self.exp.dereferencer.get_external_block_access_strobe(node) + addr_width = node.size.bit_length() + + retime = False + if isinstance(node, RegfileNode): + retime = self.field_logic.retime_external_regfile + elif isinstance(node, MemNode): + retime = self.field_logic.retime_external_mem + elif isinstance(node, AddrmapNode): + retime = self.field_logic.retime_external_addrmap + + context = { + "prefix": prefix, + "strb": strb, + "addr_width": addr_width, + "retime": retime, + "get_always_ff_event": lambda resetsignal : get_always_ff_event(self.exp.dereferencer, resetsignal), + "get_resetsignal": self.exp.dereferencer.get_resetsignal, + "resetsignal": self.exp.top_node.cpuif_reset, + } + self.add_content(self.external_block_template.render(context)) diff --git a/src/peakrdl_regblock/field_logic/templates/external_block.sv b/src/peakrdl_regblock/field_logic/templates/external_block.sv new file mode 100644 index 0000000..5c5914a --- /dev/null +++ b/src/peakrdl_regblock/field_logic/templates/external_block.sv @@ -0,0 +1,31 @@ +{% if retime -%} + + +always_ff {{get_always_ff_event(resetsignal)}} begin + if({{get_resetsignal(resetsignal)}}) begin + {{prefix}}.req <= '0; + {{prefix}}.addr <= '0; + {{prefix}}.req_is_wr <= '0; + {{prefix}}.wr_data <= '0; + {{prefix}}.wr_biten <= '0; + end else begin + {{prefix}}.req <= {{strb}}; + {{prefix}}.addr <= decoded_addr[{{addr_width-1}}:0]; + {{prefix}}.req_is_wr <= decoded_req_is_wr; + {{prefix}}.wr_data <= decoded_wr_data; + {{prefix}}.wr_biten <= decoded_wr_biten; + end +end + + +{%- else -%} + + +assign {{prefix}}.req = {{strb}}; +assign {{prefix}}.addr = decoded_addr[{{addr_width-1}}:0]; +assign {{prefix}}.req_is_wr = decoded_req_is_wr; +assign {{prefix}}.wr_data = decoded_wr_data; +assign {{prefix}}.wr_biten = decoded_wr_biten; + + +{%- endif %} diff --git a/src/peakrdl_regblock/field_logic/templates/external_reg.sv b/src/peakrdl_regblock/field_logic/templates/external_reg.sv new file mode 100644 index 0000000..d8646de --- /dev/null +++ b/src/peakrdl_regblock/field_logic/templates/external_reg.sv @@ -0,0 +1,28 @@ +{% if retime -%} + + +always_ff {{get_always_ff_event(resetsignal)}} begin + if({{get_resetsignal(resetsignal)}}) begin + {{prefix}}.req <= '0; + {{prefix}}.req_is_wr <= '0; + {{prefix}}.wr_data <= '0; + {{prefix}}.wr_biten <= '0; + end else begin + {{prefix}}.req <= {{strb}}; + {{prefix}}.req_is_wr <= decoded_req_is_wr; + {{prefix}}.wr_data <= decoded_wr_data{{bslice}}; + {{prefix}}.wr_biten <= decoded_wr_biten{{bslice}}; + end +end + + +{%- else -%} + + +assign {{prefix}}.req = {{strb}}; +assign {{prefix}}.req_is_wr = decoded_req_is_wr; +assign {{prefix}}.wr_data = decoded_wr_data{{bslice}}; +assign {{prefix}}.wr_biten = decoded_wr_biten{{bslice}}; + + +{%- endif %} diff --git a/src/peakrdl_regblock/hwif/__init__.py b/src/peakrdl_regblock/hwif/__init__.py index 179ecba..272273e 100644 --- a/src/peakrdl_regblock/hwif/__init__.py +++ b/src/peakrdl_regblock/hwif/__init__.py @@ -1,6 +1,6 @@ from typing import TYPE_CHECKING, Union, Set, Dict, Optional, TextIO, Type, List -from systemrdl.node import AddrmapNode, SignalNode, FieldNode, RegNode +from systemrdl.node import AddrmapNode, SignalNode, FieldNode, RegNode, AddressableNode from systemrdl.rdltypes import PropertyReference, UserEnum from ..utils import get_indexed_path @@ -25,7 +25,8 @@ class Hwif: self, exp: 'RegblockExporter', package_name: str, user_enums: List[Type[UserEnum]], in_hier_signal_paths: Set[str], out_of_hier_signals: Dict[str, SignalNode], - reuse_typedefs: bool, hwif_report_file: Optional[TextIO] + reuse_typedefs: bool, hwif_report_file: Optional[TextIO], + data_width: int ): self.exp = exp self.package_name = package_name @@ -39,6 +40,8 @@ class Hwif: self.hwif_report_file = hwif_report_file + self.data_width = data_width + if reuse_typedefs: self._gen_in_cls = InputStructGenerator_TypeScope self._gen_out_cls = OutputStructGenerator_TypeScope @@ -162,6 +165,26 @@ class Hwif: raise RuntimeError(f"Unhandled reference to: {obj}") + def get_external_rd_data(self, node: AddressableNode) -> str: + """ + Returns the identifier string for an external component's rd_data signal + """ + path = get_indexed_path(self.top_node, node) + return "hwif_in." + path + ".rd_data" + + def get_external_rd_ack(self, node: AddressableNode) -> str: + """ + Returns the identifier string for an external component's rd_ack signal + """ + path = get_indexed_path(self.top_node, node) + return "hwif_in." + path + ".rd_ack" + + def get_external_wr_ack(self, node: AddressableNode) -> str: + """ + Returns the identifier string for an external component's wr_ack signal + """ + path = get_indexed_path(self.top_node, node) + return "hwif_in." + path + ".wr_ack" def get_implied_prop_input_identifier(self, field: FieldNode, prop: str) -> str: assert prop in { diff --git a/src/peakrdl_regblock/hwif/generators.py b/src/peakrdl_regblock/hwif/generators.py index eac1e4b..52ee238 100644 --- a/src/peakrdl_regblock/hwif/generators.py +++ b/src/peakrdl_regblock/hwif/generators.py @@ -1,12 +1,13 @@ from typing import TYPE_CHECKING, Optional, List, Type -from systemrdl.node import FieldNode +from systemrdl.node import FieldNode, RegNode, AddrmapNode, MemNode +from systemrdl.walker import WalkerAction from ..struct_generator import RDLFlatStructGenerator from ..identifier_filter import kw_filter as kwf if TYPE_CHECKING: - from systemrdl.node import Node, SignalNode, RegNode + from systemrdl.node import Node, SignalNode, AddressableNode, RegfileNode from . import Hwif from systemrdl.rdltypes import UserEnum @@ -65,6 +66,43 @@ class InputStructGenerator_Hier(HWIFStructGenerator): if path in self.hwif.in_hier_signal_paths: self.add_member(kwf(node.inst_name), node.width) + def _add_external_block_members(self, node: 'AddressableNode') -> None: + self.add_member("rd_ack") + self.add_member("rd_data", self.hwif.data_width) + self.add_member("wr_ack") + + def enter_Addrmap(self, node: 'AddrmapNode') -> None: + super().enter_Addrmap(node) + if node.external: + self._add_external_block_members(node) + return WalkerAction.SkipDescendants + return WalkerAction.Continue + + def enter_Regfile(self, node: 'RegfileNode') -> None: + super().enter_Regfile(node) + if node.external: + self._add_external_block_members(node) + return WalkerAction.SkipDescendants + return WalkerAction.Continue + + def enter_Mem(self, node: 'MemNode') -> Optional[WalkerAction]: + super().enter_Mem(node) + if node.external: + self._add_external_block_members(node) + return WalkerAction.SkipDescendants + return WalkerAction.Continue + + def enter_Reg(self, node: 'RegNode') -> Optional[WalkerAction]: + super().enter_Reg(node) + if node.external: + width = min(self.hwif.data_width, node.get_property('regwidth')) + self.add_member("rd_ack") + self.add_member("rd_data", width) + self.add_member("wr_ack") + return WalkerAction.SkipDescendants + + return WalkerAction.Continue + def enter_Field(self, node: 'FieldNode') -> None: type_name = self.get_typdef_name(node) self.push_struct(type_name, kwf(node.inst_name)) @@ -120,6 +158,47 @@ class OutputStructGenerator_Hier(HWIFStructGenerator): ) return f'{base}__out_t' + def _add_external_block_members(self, node: 'AddressableNode') -> None: + self.add_member("req") + self.add_member("addr", (node.size - 1).bit_length()) + self.add_member("req_is_wr") + self.add_member("wr_data", self.hwif.data_width) + self.add_member("wr_biten", self.hwif.data_width) + + def enter_Addrmap(self, node: 'AddrmapNode') -> None: + super().enter_Addrmap(node) + if node.external: + self._add_external_block_members(node) + return WalkerAction.SkipDescendants + return WalkerAction.Continue + + def enter_Regfile(self, node: 'RegfileNode') -> None: + super().enter_Regfile(node) + if node.external: + self._add_external_block_members(node) + return WalkerAction.SkipDescendants + return WalkerAction.Continue + + def enter_Mem(self, node: 'MemNode') -> Optional[WalkerAction]: + super().enter_Mem(node) + if node.external: + self._add_external_block_members(node) + return WalkerAction.SkipDescendants + return WalkerAction.Continue + + def enter_Reg(self, node: 'RegNode') -> Optional[WalkerAction]: + super().enter_Reg(node) + if node.external: + width = min(self.hwif.data_width, node.get_property('regwidth')) + n_subwords = node.get_property("regwidth") // node.get_property("accesswidth") + self.add_member("req", n_subwords) + self.add_member("req_is_wr") + self.add_member("wr_data", width) + self.add_member("wr_biten", width) + return WalkerAction.SkipDescendants + + return WalkerAction.Continue + def enter_Field(self, node: 'FieldNode') -> None: type_name = self.get_typdef_name(node) self.push_struct(type_name, kwf(node.inst_name)) @@ -162,6 +241,10 @@ class InputStructGenerator_TypeScope(InputStructGenerator_Hier): else: extra_suffix = "" + if node.external: + # Node generates alternate external signals + extra_suffix += "__external" + return f'{scope_path}__{node.type_name}{extra_suffix}__in_t' class OutputStructGenerator_TypeScope(OutputStructGenerator_Hier): @@ -177,6 +260,10 @@ class OutputStructGenerator_TypeScope(OutputStructGenerator_Hier): else: extra_suffix = "" + if node.external: + # Node generates alternate external signals + extra_suffix += "__external" + return f'{scope_path}__{node.type_name}{extra_suffix}__out_t' #------------------------------------------------------------------------------- diff --git a/src/peakrdl_regblock/module_tmpl.sv b/src/peakrdl_regblock/module_tmpl.sv index feee65c..9eba320 100644 --- a/src/peakrdl_regblock/module_tmpl.sv +++ b/src/peakrdl_regblock/module_tmpl.sv @@ -40,11 +40,30 @@ module {{module_name}} ( {{cpuif.get_implementation()|indent}} logic cpuif_req_masked; +{%- if has_external_addressable %} + logic external_req; + logic external_pending; + logic external_wr_ack; + logic external_rd_ack; + always_ff {{get_always_ff_event(cpuif.reset)}} begin + if({{get_resetsignal(cpuif.reset)}}) begin + external_pending <= '0; + end else begin + if(external_req & ~external_wr_ack & ~external_rd_ack) external_pending <= '1; + else if(external_wr_ack | external_rd_ack) external_pending <= '0; + end + end +{%- endif %} {% if min_read_latency == min_write_latency %} // Read & write latencies are balanced. Stalls not required + {%- if has_external_addressable %} + // except if external + assign cpuif_req_stall_rd = external_pending; + assign cpuif_req_stall_wr = external_pending; + {%- else %} assign cpuif_req_stall_rd = '0; assign cpuif_req_stall_wr = '0; - assign cpuif_req_masked = cpuif_req; + {%- endif %} {%- elif min_read_latency > min_write_latency %} // Read latency > write latency. May need to delay next write that follows a read logic [{{min_read_latency - min_write_latency - 1}}:0] cpuif_req_stall_sr; @@ -57,9 +76,13 @@ module {{module_name}} ( cpuif_req_stall_sr <= (cpuif_req_stall_sr >> 'd1); end end + {%- if has_external_addressable %} + assign cpuif_req_stall_rd = external_pending; + assign cpuif_req_stall_wr = cpuif_req_stall_sr[0] | external_pending; + {%- else %} assign cpuif_req_stall_rd = '0; assign cpuif_req_stall_wr = cpuif_req_stall_sr[0]; - assign cpuif_req_masked = cpuif_req & !(cpuif_req_is_wr & cpuif_req_stall_wr); + {%- endif %} {%- else %} // Write latency > read latency. May need to delay next read that follows a write logic [{{min_write_latency - min_read_latency - 1}}:0] cpuif_req_stall_sr; @@ -72,26 +95,49 @@ module {{module_name}} ( cpuif_req_stall_sr <= (cpuif_req_stall_sr >> 'd1); end end + {%- if has_external_addressable %} + assign cpuif_req_stall_rd = cpuif_req_stall_sr[0] | external_pending; + assign cpuif_req_stall_wr = external_pending; + {%- else %} assign cpuif_req_stall_rd = cpuif_req_stall_sr[0]; assign cpuif_req_stall_wr = '0; - assign cpuif_req_masked = cpuif_req & !(!cpuif_req_is_wr & cpuif_req_stall_rd); + {%- endif %} {%- endif %} + assign cpuif_req_masked = cpuif_req + & !(!cpuif_req_is_wr & cpuif_req_stall_rd) + & !(cpuif_req_is_wr & cpuif_req_stall_wr); //-------------------------------------------------------------------------- // Address Decode //-------------------------------------------------------------------------- {{address_decode.get_strobe_struct()|indent}} decoded_reg_strb_t decoded_reg_strb; +{%- if has_external_addressable %} + logic decoded_strb_is_external; +{% endif %} +{%- if has_external_block %} + logic [{{cpuif.addr_width-1}}:0] decoded_addr; +{% endif %} logic decoded_req; logic decoded_req_is_wr; logic [{{cpuif.data_width-1}}:0] decoded_wr_data; logic [{{cpuif.data_width-1}}:0] decoded_wr_biten; always_comb begin + {%- if has_external_addressable %} + automatic logic is_external = '0; + {% endif %} {{address_decode.get_implementation()|indent(8)}} + {%- if has_external_addressable %} + decoded_strb_is_external = is_external; + external_req = is_external; + {% endif %} end // Pass down signals to next stage +{%- if has_external_block %} + assign decoded_addr = cpuif_addr; +{% endif %} assign decoded_req = cpuif_req_masked; assign decoded_req_is_wr = cpuif_req_is_wr; assign decoded_wr_data = cpuif_wr_data; @@ -104,10 +150,6 @@ module {{module_name}} ( assign decoded_wr_biten_bswap = {<<{decoded_wr_biten}}; {%- endif %} - // Writes are always granted with no error response - assign cpuif_wr_ack = decoded_req & decoded_req_is_wr; - assign cpuif_wr_err = '0; - {%- if has_buffered_write_regs %} //-------------------------------------------------------------------------- @@ -135,9 +177,52 @@ module {{module_name}} ( {{read_buffering.get_implementation()|indent}} {%- endif %} + + //-------------------------------------------------------------------------- + // Write response + //-------------------------------------------------------------------------- +{%- if has_external_addressable %} + always_comb begin + automatic logic wr_ack; + wr_ack = '0; + {{ext_write_acks.get_implementation()|indent(8)}} + external_wr_ack = wr_ack; + end + assign cpuif_wr_ack = external_wr_ack | (decoded_req & decoded_req_is_wr & ~decoded_strb_is_external); +{%- else %} + assign cpuif_wr_ack = decoded_req & decoded_req_is_wr; +{%- endif %} + // Writes are always granted with no error response + assign cpuif_wr_err = '0; + //-------------------------------------------------------------------------- // Readback //-------------------------------------------------------------------------- +{%- if has_external_addressable %} + logic readback_external_rd_ack_c; + always_comb begin + automatic logic rd_ack; + rd_ack = '0; + {{ext_read_acks.get_implementation()|indent(8)}} + readback_external_rd_ack_c = rd_ack; + end + + logic readback_external_rd_ack; + {%- if retime_read_fanin %} + always_ff {{get_always_ff_event(cpuif.reset)}} begin + if({{get_resetsignal(cpuif.reset)}}) begin + readback_external_rd_ack <= '0; + end else begin + readback_external_rd_ack <= readback_external_rd_ack_c; + end + end + + {%- else %} + + assign readback_external_rd_ack = readback_external_rd_ack_c; + {%- endif %} +{%- endif %} + logic readback_err; logic readback_done; logic [{{cpuif.data_width-1}}:0] readback_data; @@ -148,14 +233,27 @@ module {{module_name}} ( cpuif_rd_ack <= '0; cpuif_rd_data <= '0; cpuif_rd_err <= '0; + {%- if has_external_addressable %} + external_rd_ack <= '0; + {%- endif %} end else begin + {%- if has_external_addressable %} + external_rd_ack <= readback_external_rd_ack; + cpuif_rd_ack <= readback_done | readback_external_rd_ack; + {%- else %} cpuif_rd_ack <= readback_done; + {%- endif %} cpuif_rd_data <= readback_data; cpuif_rd_err <= readback_err; end end {% else %} + {%- if has_external_addressable %} + assign external_rd_ack = readback_external_rd_ack; + assign cpuif_rd_ack = readback_done | readback_external_rd_ack; + {%- else %} assign cpuif_rd_ack = readback_done; + {%- endif %} assign cpuif_rd_data = readback_data; assign cpuif_rd_err = readback_err; {%- endif %} diff --git a/src/peakrdl_regblock/readback/__init__.py b/src/peakrdl_regblock/readback/__init__.py index 4494229..8054a5e 100644 --- a/src/peakrdl_regblock/readback/__init__.py +++ b/src/peakrdl_regblock/readback/__init__.py @@ -9,9 +9,10 @@ if TYPE_CHECKING: from systemrdl.node import AddrmapNode class Readback: - def __init__(self, exp:'RegblockExporter', do_fanin_stage: bool): + def __init__(self, exp:'RegblockExporter', do_fanin_stage: bool, has_external_addressable: bool): self.exp = exp self.do_fanin_stage = do_fanin_stage + self.has_external_addressable = has_external_addressable @property def top_node(self) -> 'AddrmapNode': @@ -33,6 +34,7 @@ class Readback: "get_always_ff_event": lambda resetsignal : get_always_ff_event(self.exp.dereferencer, resetsignal), "cpuif": self.exp.cpuif, "do_fanin_stage": self.do_fanin_stage, + "has_external_addressable": self.has_external_addressable, } if self.do_fanin_stage: diff --git a/src/peakrdl_regblock/readback/generators.py b/src/peakrdl_regblock/readback/generators.py index 7f9e418..df8d064 100644 --- a/src/peakrdl_regblock/readback/generators.py +++ b/src/peakrdl_regblock/readback/generators.py @@ -1,6 +1,7 @@ from typing import TYPE_CHECKING, List -from systemrdl.node import RegNode +from systemrdl.node import RegNode, AddressableNode +from systemrdl.walker import WalkerAction from ..forloop_generator import RDLForLoopGenerator, LoopBody @@ -77,9 +78,26 @@ class ReadbackAssignmentGenerator(RDLForLoopGenerator): self.current_offset = start_offset + n_regs * dim - def enter_Reg(self, node: RegNode) -> None: + def enter_AddressableComponent(self, node: 'AddressableNode') -> WalkerAction: + super().enter_AddressableComponent(node) + + if node.external and not isinstance(node, RegNode): + # External block + strb = self.exp.hwif.get_external_rd_ack(node) + data = self.exp.hwif.get_external_rd_data(node) + self.add_content(f"assign readback_array[{self.current_offset_str}] = {strb} ? {data} : '0;") + self.current_offset += 1 + return WalkerAction.SkipDescendants + + return WalkerAction.Continue + + def enter_Reg(self, node: RegNode) -> WalkerAction: + if node.external: + self.process_external_reg(node) + return WalkerAction.SkipDescendants + if not node.has_sw_readable: - return + return WalkerAction.SkipDescendants accesswidth = node.get_property('accesswidth') regwidth = node.get_property('regwidth') @@ -100,6 +118,19 @@ class ReadbackAssignmentGenerator(RDLForLoopGenerator): else: self.process_reg(node) + return WalkerAction.SkipDescendants + + def process_external_reg(self, node: RegNode) -> None: + strb = self.exp.hwif.get_external_rd_ack(node) + data = self.exp.hwif.get_external_rd_data(node) + regwidth = node.get_property('regwidth') + if regwidth < self.exp.cpuif.data_width: + self.add_content(f"assign readback_array[{self.current_offset_str}][{self.exp.cpuif.data_width-1}:{regwidth}] = '0;") + self.add_content(f"assign readback_array[{self.current_offset_str}][{regwidth-1}:0] = {strb} ? {data} : '0;") + else: + self.add_content(f"assign readback_array[{self.current_offset_str}] = {strb} ? {data} : '0;") + + self.current_offset += 1 def process_reg(self, node: RegNode) -> None: current_bit = 0 diff --git a/src/peakrdl_regblock/readback/templates/readback.sv b/src/peakrdl_regblock/readback/templates/readback.sv index 29ca77d..a90b0b8 100644 --- a/src/peakrdl_regblock/readback/templates/readback.sv +++ b/src/peakrdl_regblock/readback/templates/readback.sv @@ -35,7 +35,11 @@ always_ff @(posedge clk) begin readback_done_r <= '0; end else begin readback_array_r <= readback_array_c; + {%- if has_external_addressable %} + readback_done_r <= decoded_req & ~decoded_req_is_wr & ~decoded_strb_is_external; + {%- else %} readback_done_r <= decoded_req & ~decoded_req_is_wr; + {%- endif %} end end @@ -54,7 +58,11 @@ end // Reduce the array always_comb begin automatic logic [{{cpuif.data_width-1}}:0] readback_data_var; + {%- if has_external_addressable %} + readback_done = decoded_req & ~decoded_req_is_wr & ~decoded_strb_is_external; + {%- else %} readback_done = decoded_req & ~decoded_req_is_wr; + {%- endif %} readback_err = '0; readback_data_var = '0; for(int i=0; i<{{array_size}}; i++) readback_data_var |= readback_array[i]; diff --git a/src/peakrdl_regblock/scan_design.py b/src/peakrdl_regblock/scan_design.py index 693adb4..bb96ab9 100644 --- a/src/peakrdl_regblock/scan_design.py +++ b/src/peakrdl_regblock/scan_design.py @@ -2,10 +2,10 @@ from typing import TYPE_CHECKING, Set, Optional, Type, List from collections import OrderedDict from systemrdl.walker import RDLListener, RDLWalker, WalkerAction -from systemrdl.node import SignalNode +from systemrdl.node import SignalNode, RegNode if TYPE_CHECKING: - from systemrdl.node import Node, RegNode, FieldNode + from systemrdl.node import Node, FieldNode, AddressableNode from .exporter import RegblockExporter from systemrdl.rdltypes import UserEnum @@ -30,6 +30,9 @@ class DesignScanner(RDLListener): self.has_buffered_write_regs = False self.has_buffered_read_regs = False + self.has_external_block = False + self.has_external_addressable = False + # Track any referenced enums self.user_enums = [] # type: List[Type[UserEnum]] @@ -91,7 +94,13 @@ class DesignScanner(RDLListener): if value not in self.user_enums: self.user_enums.append(value) - return None + return WalkerAction.Continue + + def enter_AddressableComponent(self, node: 'AddressableNode') -> None: + if node.external and node != self.exp.top_node: + self.has_external_addressable = True + if not isinstance(node, RegNode): + self.has_external_block = True def enter_Reg(self, node: 'RegNode') -> None: # The CPUIF's bus width is sized according to the largest accesswidth in the design diff --git a/src/peakrdl_regblock/struct_generator.py b/src/peakrdl_regblock/struct_generator.py index 9ec9d89..67fdcfd 100644 --- a/src/peakrdl_regblock/struct_generator.py +++ b/src/peakrdl_regblock/struct_generator.py @@ -9,7 +9,7 @@ from .identifier_filter import kw_filter as kwf if TYPE_CHECKING: from typing import Union - from systemrdl.node import AddrmapNode, RegfileNode, RegNode, FieldNode, Node + from systemrdl.node import AddrmapNode, RegfileNode, RegNode, FieldNode, Node, MemNode class _StructBase: @@ -144,6 +144,12 @@ class RDLStructGenerator(StructGenerator, RDLListener): def exit_Regfile(self, node: 'RegfileNode') -> None: self.pop_struct() + def enter_Mem(self, node: 'MemNode') -> None: + self.push_struct(kwf(node.inst_name), node.array_dimensions) + + def exit_Mem(self, node: 'MemNode') -> None: + self.pop_struct() + def enter_Reg(self, node: 'RegNode') -> None: self.push_struct(kwf(node.inst_name), node.array_dimensions) @@ -228,6 +234,13 @@ class RDLFlatStructGenerator(FlatStructGenerator, RDLListener): def exit_Regfile(self, node: 'RegfileNode') -> None: self.pop_struct() + def enter_Mem(self, node: 'MemNode') -> None: + type_name = self.get_typdef_name(node) + self.push_struct(type_name, kwf(node.inst_name), node.array_dimensions) + + def exit_Mem(self, node: 'MemNode') -> None: + self.pop_struct() + def enter_Reg(self, node: 'RegNode') -> None: type_name = self.get_typdef_name(node) self.push_struct(type_name, kwf(node.inst_name), node.array_dimensions) diff --git a/src/peakrdl_regblock/validate_design.py b/src/peakrdl_regblock/validate_design.py index 2e3ac50..f3e5898 100644 --- a/src/peakrdl_regblock/validate_design.py +++ b/src/peakrdl_regblock/validate_design.py @@ -1,13 +1,15 @@ -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Optional, List from systemrdl.walker import RDLListener, RDLWalker, WalkerAction from systemrdl.rdltypes import PropertyReference -from systemrdl.node import Node +from systemrdl.node import Node, RegNode, FieldNode, SignalNode, AddressableNode +from systemrdl.node import RegfileNode, AddrmapNode + +from .utils import roundup_pow2, is_pow2 from .utils import ref_is_internal if TYPE_CHECKING: - from systemrdl.node import RegNode, FieldNode, SignalNode, AddressableNode from .exporter import RegblockExporter class DesignValidator(RDLListener): @@ -19,6 +21,9 @@ class DesignValidator(RDLListener): self.exp = exp self.msg = exp.top_node.env.msg + self._contains_external_block_stack = [] # type: List[bool] + self.contains_external_block = False + def do_validate(self) -> None: RDLWalker().walk(self.exp.top_node, self) if self.msg.had_error: @@ -28,10 +33,6 @@ class DesignValidator(RDLListener): def enter_Component(self, node: 'Node') -> Optional[WalkerAction]: if node.external and (node != self.exp.top_node): - self.msg.error( - "Exporter does not support external components", - node.inst.inst_src_ref - ) # Do not inspect external components. None of my business return WalkerAction.SkipDescendants @@ -48,8 +49,6 @@ class DesignValidator(RDLListener): "Property is assigned a reference that points to a component not internal to the regblock being exported.", src_ref ) - - return None def enter_Signal(self, node: 'SignalNode') -> None: @@ -80,6 +79,27 @@ class DesignValidator(RDLListener): node.inst.inst_src_ref ) + if not isinstance(node, RegNode): + # Entering a block-like node + if node == self.exp.top_node: + # Ignore top addrmap's external property when entering + self._contains_external_block_stack.append(False) + else: + self._contains_external_block_stack.append(node.external) + + def enter_Regfile(self, node: RegfileNode) -> None: + self._check_sharedextbus(node) + + def enter_Addrmap(self, node: AddrmapNode) -> None: + self._check_sharedextbus(node) + + def _check_sharedextbus(self, node: Node) -> None: + if node.get_property('sharedextbus'): + self.msg.error( + "This exporter does not support enabling the 'sharedextbus' property yet.", + node.inst.property_src_ref.get('sharedextbus', node.inst.inst_src_ref) + ) + def enter_Reg(self, node: 'RegNode') -> None: # accesswidth of wide registers must be consistent within the register block accesswidth = node.get_property('accesswidth') @@ -104,6 +124,16 @@ class DesignValidator(RDLListener): and (node.lsb // parent_accesswidth) != (node.msb // parent_accesswidth) ): # field spans multiple sub-words + if node.external: + # External fields that span multiple subwords is not supported + self.msg.error( + "External fields that span multiple software-accessible " + "subwords are not supported.", + node.inst.inst_src_ref + ) + # Skip remaining validation rules for external fields + return + if node.is_sw_writable and not node.parent.get_property('buffer_writes'): # ... and is writable without the protection of double-buffering # Enforce 10.6.1-f @@ -126,3 +156,38 @@ class DesignValidator(RDLListener): "For more details, see: https://peakrdl-regblock.readthedocs.io/en/latest/udps/read_buffering.html", node.inst.inst_src_ref ) + + def exit_AddressableComponent(self, node: AddressableNode) -> None: + if not isinstance(node, RegNode): + # Exiting block-like node + contains_external_block = self._contains_external_block_stack.pop() + + if self._contains_external_block_stack: + # Still in the design. Update stack + self._contains_external_block_stack[-1] |= contains_external_block + else: + # Exiting top addrmap. Resolve final answer + self.contains_external_block = contains_external_block + + if contains_external_block: + # Check that addressing follows strict alignment rules to allow + # for simplified address bit-pruning + if node.external: + err_suffix = "is external" + else: + err_suffix = "contains an external addrmap/regfile/mem" + + req_align = roundup_pow2(node.size) + if (node.raw_address_offset % req_align) != 0: + self.msg.error( + f"Address offset +0x{node.raw_address_offset:x} of instance '{node.inst_name}' is not a power of 2 multiple of its size 0x{node.size:x}. " + f"This is required by the regblock exporter if a component {err_suffix}.", + node.inst.inst_src_ref + ) + if node.is_array: + if not is_pow2(node.array_stride): + self.msg.error( + f"Address stride of instance array '{node.inst_name}' is not a power of 2" + f"This is required by the regblock exporter if a component {err_suffix}.", + node.inst.inst_src_ref + ) diff --git a/tests/lib/base_testcase.py b/tests/lib/base_testcase.py index 7bb6489..d0579ea 100644 --- a/tests/lib/base_testcase.py +++ b/tests/lib/base_testcase.py @@ -36,6 +36,7 @@ class BaseTestCase(unittest.TestCase): retime_read_fanin = False retime_read_response = False reuse_hwif_typedefs = True + retime_external = False #: this gets auto-loaded via the _load_request autouse fixture request = None # type: pytest.FixtureRequest @@ -105,7 +106,11 @@ class BaseTestCase(unittest.TestCase): cpuif_cls=cls.cpuif.cpuif_cls, retime_read_fanin=cls.retime_read_fanin, retime_read_response=cls.retime_read_response, - reuse_hwif_typedefs=cls.reuse_hwif_typedefs + reuse_hwif_typedefs=cls.reuse_hwif_typedefs, + retime_external_reg=cls.retime_external, + retime_external_regfile=cls.retime_external, + retime_external_mem=cls.retime_external, + retime_external_addrmap=cls.retime_external, ) @classmethod diff --git a/tests/lib/external_block.sv b/tests/lib/external_block.sv new file mode 100644 index 0000000..ffde251 --- /dev/null +++ b/tests/lib/external_block.sv @@ -0,0 +1,73 @@ +module external_block #( + parameter WIDTH = 32, + parameter ADDR_WIDTH = 8 +)( + input wire clk, + input wire rst, + + input wire req, + input wire req_is_wr, + input wire [ADDR_WIDTH-1:0] addr, + input wire [WIDTH-1:0] wr_data, + input wire [WIDTH-1:0] wr_biten, + output logic rd_ack, + output logic [WIDTH-1:0] rd_data, + output logic wr_ack +); +timeunit 1ns; +timeprecision 1ps; + +localparam ADDR_SHIFT = $clog2(WIDTH/8); +localparam N_ENTRIES = 2**(ADDR_WIDTH - ADDR_SHIFT); +logic [WIDTH-1:0] mem[N_ENTRIES]; + + +task do_write(int idx, logic [WIDTH-1:0] data, logic [WIDTH-1:0] biten); + automatic int delay; + // Random delay + delay = $urandom_range(3,0); + repeat(delay) @(posedge clk) + $info("Write delay: %d", delay); + + for(int b=0; b> ADDR_SHIFT, wr_data, wr_biten); + else do_read(addr >> ADDR_SHIFT); + end + end +end + +endmodule diff --git a/tests/lib/external_reg.sv b/tests/lib/external_reg.sv new file mode 100644 index 0000000..b7a09f5 --- /dev/null +++ b/tests/lib/external_reg.sv @@ -0,0 +1,78 @@ +module external_reg #( + parameter WIDTH = 32, + parameter SUBWORDS = 1 +)( + input wire clk, + input wire rst, + + input wire [SUBWORDS-1:0] req, + input wire req_is_wr, + input wire [WIDTH-1:0] wr_data, + input wire [WIDTH-1:0] wr_biten, + output logic rd_ack, + output logic [WIDTH-1:0] rd_data, + output logic wr_ack +); +timeunit 1ns; +timeprecision 1ps; +logic [SUBWORDS-1:0][WIDTH-1:0] value; + + +task do_write(logic [SUBWORDS-1:0] strb, logic [WIDTH-1:0] data, logic [WIDTH-1:0] biten); + automatic int delay; + // Random delay + delay = $urandom_range(3,0); + repeat(delay) @(posedge clk) + $info("Write delay: %d", delay); + + for(int i=0; i List[str]: + paths = [] + for path in cls.extra_tb_files: + path = os.path.join(cls.get_testcase_dir(), path) + paths.append(path) + return paths + @classmethod def _generate_tb(cls): """ diff --git a/tests/lib/simulators/__init__.py b/tests/lib/simulators/__init__.py index 92a946e..b0079e9 100644 --- a/tests/lib/simulators/__init__.py +++ b/tests/lib/simulators/__init__.py @@ -13,6 +13,7 @@ class Simulator: def tb_files(self) -> List[str]: files = [] files.extend(self.testcase_cls.cpuif.get_sim_files()) + files.extend(self.testcase_cls.get_extra_tb_files()) files.append("regblock_pkg.sv") files.append("regblock.sv") files.append("tb.sv") diff --git a/tests/lib/tb_base.sv b/tests/lib/tb_base.sv index 1b3fb3a..db71b47 100644 --- a/tests/lib/tb_base.sv +++ b/tests/lib/tb_base.sv @@ -30,11 +30,11 @@ module tb; default clocking cb @(posedge clk); default input #1step output #1; output rst; -{%- if exporter.hwif.has_input_struct %} +{%- if exporter.hwif.has_input_struct and cls.clocking_hwif_in %} output hwif_in; {%- endif %} -{%- if exporter.hwif.has_output_struct %} +{%- if exporter.hwif.has_output_struct and cls.clocking_hwif_out %} input hwif_out; {%- endif %} @@ -68,12 +68,15 @@ module tb; {%- endif %} {% sv_line_anchor %} +{%- block dut_support %} +{%- endblock %} + //-------------------------------------------------------------------------- // Test Sequence //-------------------------------------------------------------------------- initial begin cb.rst <= '1; - {%- if exporter.hwif.has_input_struct %} + {%- if exporter.hwif.has_input_struct and cls.init_hwif_in %} cb.hwif_in <= '{default: '0}; {%- endif %} diff --git a/tests/pylint.rc b/tests/pylint.rc index 264b127..b3eb902 100644 --- a/tests/pylint.rc +++ b/tests/pylint.rc @@ -492,7 +492,7 @@ valid-metaclass-classmethod-first-arg=cls ignored-parents= # Maximum number of arguments for function / method. -max-args=8 +max-args=16 # Maximum number of attributes for a class (see R0902). max-attributes=7 diff --git a/tests/test_external/__init__.py b/tests/test_external/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_external/regblock.rdl b/tests/test_external/regblock.rdl new file mode 100644 index 0000000..ba40aad --- /dev/null +++ b/tests/test_external/regblock.rdl @@ -0,0 +1,29 @@ +addrmap top { + reg my_reg { + field {sw=rw; hw=r;} whatever[32] = 0; + }; + reg my_wide_reg { + regwidth = 64; + accesswidth = 32; + field {sw=rw; hw=r;} whatever = 0; + }; + + external my_reg ext_reg @ 0x00; + my_reg int_reg @ 0x04; + external my_wide_reg wide_ext_reg @ 0x10; + external my_reg ext_reg_array[32] @ 0x100 += 4; + + + external regfile { + my_reg placeholder @ 8*4-4; + } rf @ 0x1000; + + addrmap { + my_reg placeholder @ 8*4-4; + } am @ 0x2000; + + external mem { + memwidth = 32; + mementries = 8; + } mm @ 0x3000; +}; diff --git a/tests/test_external/tb_template.sv b/tests/test_external/tb_template.sv new file mode 100644 index 0000000..5f8318e --- /dev/null +++ b/tests/test_external/tb_template.sv @@ -0,0 +1,207 @@ +{% extends "lib/tb_base.sv" %} + + + +{%- block dut_support %} + {% sv_line_anchor %} + + external_reg ext_reg_inst ( + .clk(clk), + .rst(rst), + + .req(hwif_out.ext_reg.req), + .req_is_wr(hwif_out.ext_reg.req_is_wr), + .wr_data(hwif_out.ext_reg.wr_data), + .wr_biten(hwif_out.ext_reg.wr_biten), + .rd_ack(hwif_in.ext_reg.rd_ack), + .rd_data(hwif_in.ext_reg.rd_data), + .wr_ack(hwif_in.ext_reg.wr_ack) + ); + + external_reg #( + .SUBWORDS(2) + ) wide_ext_reg_inst ( + .clk(clk), + .rst(rst), + + .req(hwif_out.wide_ext_reg.req), + .req_is_wr(hwif_out.wide_ext_reg.req_is_wr), + .wr_data(hwif_out.wide_ext_reg.wr_data), + .wr_biten(hwif_out.wide_ext_reg.wr_biten), + .rd_ack(hwif_in.wide_ext_reg.rd_ack), + .rd_data(hwif_in.wide_ext_reg.rd_data), + .wr_ack(hwif_in.wide_ext_reg.wr_ack) + ); + + for(genvar i=0; i<32; i++) begin : array + external_reg ext_reg_inst ( + .clk(clk), + .rst(rst), + + .req(hwif_out.ext_reg_array[i].req), + .req_is_wr(hwif_out.ext_reg_array[i].req_is_wr), + .wr_data(hwif_out.ext_reg_array[i].wr_data), + .wr_biten(hwif_out.ext_reg_array[i].wr_biten), + .rd_ack(hwif_in.ext_reg_array[i].rd_ack), + .rd_data(hwif_in.ext_reg_array[i].rd_data), + .wr_ack(hwif_in.ext_reg_array[i].wr_ack) + ); + end + + external_block #( + .ADDR_WIDTH(5) + ) rf_inst ( + .clk(clk), + .rst(rst), + + .req(hwif_out.rf.req), + .req_is_wr(hwif_out.rf.req_is_wr), + .addr(hwif_out.rf.addr), + .wr_data(hwif_out.rf.wr_data), + .wr_biten(hwif_out.rf.wr_biten), + .rd_ack(hwif_in.rf.rd_ack), + .rd_data(hwif_in.rf.rd_data), + .wr_ack(hwif_in.rf.wr_ack) + ); + + external_block #( + .ADDR_WIDTH(5) + ) am_inst ( + .clk(clk), + .rst(rst), + + .req(hwif_out.am.req), + .req_is_wr(hwif_out.am.req_is_wr), + .addr(hwif_out.am.addr), + .wr_data(hwif_out.am.wr_data), + .wr_biten(hwif_out.am.wr_biten), + .rd_ack(hwif_in.am.rd_ack), + .rd_data(hwif_in.am.rd_data), + .wr_ack(hwif_in.am.wr_ack) + ); + + external_block #( + .ADDR_WIDTH(5) + ) mm_inst ( + .clk(clk), + .rst(rst), + + .req(hwif_out.mm.req), + .req_is_wr(hwif_out.mm.req_is_wr), + .addr(hwif_out.mm.addr), + .wr_data(hwif_out.mm.wr_data), + .wr_biten(hwif_out.mm.wr_biten), + .rd_ack(hwif_in.mm.rd_ack), + .rd_data(hwif_in.mm.rd_data), + .wr_ack(hwif_in.mm.wr_ack) + ); +{%- endblock %} + + + +{% block seq %} + logic [31:0] x; + {% sv_line_anchor %} + ##1; + cb.rst <= '0; + ##1; + + //-------------------------------------------------------------------------- + // Simple read/write tests + //-------------------------------------------------------------------------- + + repeat(20) begin + x = $urandom(); + cpuif.write('h00, x); + cpuif.assert_read('h00, x); + assert(ext_reg_inst.value == x); + end + + for(int i=0; i<2; i++) begin + repeat(20) begin + x = $urandom(); + cpuif.write('h10 + i*4, x); + cpuif.assert_read('h10 + i*4, x); + assert(wide_ext_reg_inst.value[i] == x); + end + end + + for(int i=0; i<32; i++) begin + repeat(20) begin + x = $urandom(); + cpuif.write('h100 + i*4, x); + cpuif.assert_read('h100 + i*4, x); + end + end + + for(int i=0; i<8; i++) begin + repeat(20) begin + x = $urandom(); + cpuif.write('h1000 + i*4, x); + cpuif.assert_read('h1000 + i*4, x); + assert(rf_inst.mem[i] == x); + end + end + + for(int i=0; i<8; i++) begin + repeat(20) begin + x = $urandom(); + cpuif.write('h2000 + i*4, x); + cpuif.assert_read('h2000 + i*4, x); + assert(am_inst.mem[i] == x); + end + end + + for(int i=0; i<8; i++) begin + repeat(20) begin + x = $urandom(); + cpuif.write('h3000 + i*4, x); + cpuif.assert_read('h3000 + i*4, x); + assert(mm_inst.mem[i] == x); + end + end + + //-------------------------------------------------------------------------- + // Pipelined access + //-------------------------------------------------------------------------- + // init array with unique known value + cpuif.write('h4, 'h1234); + for(int i=0; i<32; i++) begin + cpuif.write('h100 + i*4, 'h100 + i); + end + for(int i=0; i<8; i++) begin + cpuif.write('h1000 + i*4, 'h1000 + i); + cpuif.write('h2000 + i*4, 'h2000 + i); + cpuif.write('h3000 + i*4, 'h3000 + i); + end + + // random pipelined read/writes + repeat(256) begin + fork + begin + automatic int i, j; + i = $urandom_range(31, 0); + j = $urandom_range(7, 0); + case($urandom_range(9,0)) + // external reg + 0: cpuif.write('h100 + i*4, 'h100 + i); + 1: cpuif.assert_read('h100 + i*4, 'h100 + i); + // internal reg + 2: cpuif.write('h4, 'h1234); + 3: cpuif.assert_read('h4, 'h1234); + // external regfile + 4: cpuif.write('h1000 + j*4, 'h1000 + j); + 5: cpuif.assert_read('h1000 + j*4, 'h1000 + j); + // external addrmap + 6: cpuif.write('h2000 + j*4, 'h2000 + j); + 7: cpuif.assert_read('h2000 + j*4, 'h2000 + j); + // external mem + 8: cpuif.write('h3000 + j*4, 'h3000 + j); + 9: cpuif.assert_read('h3000 + j*4, 'h3000 + j); + endcase + end + join_none + end + wait fork; + +{% endblock %} diff --git a/tests/test_external/testcase.py b/tests/test_external/testcase.py new file mode 100644 index 0000000..e5b3969 --- /dev/null +++ b/tests/test_external/testcase.py @@ -0,0 +1,31 @@ +from parameterized import parameterized_class + +from ..lib.sim_testcase import SimTestCase +from ..lib.test_params import get_permutations +from ..lib.cpuifs.apb4 import APB4 +from ..lib.cpuifs.axi4lite import AXI4Lite +from ..lib.cpuifs.passthrough import Passthrough + +TEST_PARAMS = get_permutations({ + "cpuif": [ + APB4(), + AXI4Lite(), + Passthrough(), + ], + "retime_read_fanin": [True, False], + "retime_read_response": [True, False], + "retime_external": [True, False], +}) + +@parameterized_class(TEST_PARAMS) +class Test(SimTestCase): + extra_tb_files = [ + "../lib/external_reg.sv", + "../lib/external_block.sv", + ] + init_hwif_in = False + clocking_hwif_in = False + timeout_clk_cycles = 30000 + + def test_dut(self): + self.run_test()