Add support for external components. (#4 & #36)

This commit is contained in:
Alex Mykyta
2023-05-03 21:57:25 -07:00
parent f1a75f8d38
commit ca9185dac7
35 changed files with 1341 additions and 78 deletions

View File

@@ -36,7 +36,8 @@ cpuif_wr_data
cpuif_wr_biten cpuif_wr_biten
Active-high bit-level write-enable strobes. 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 cpuif_req_stall_rd
If asserted, and the next pending request is a read operation, then the If asserted, and the next pending request is a read operation, then the

View File

@@ -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 <https://peakrdl.readthedocs.io/>`_: The easiest way is to use the `PeakRDL command line tool <https://peakrdl.readthedocs.io/>`_:
.. code-block:: bash .. 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. Otherwise if you want, there is a Python API.
Below is a simple example that demonstrates how to generate a SystemVerilog Below is a simple example that demonstrates how to generate a SystemVerilog
implementation from SystemRDL source. implementation from SystemRDL source.
@@ -49,7 +51,7 @@ implementation from SystemRDL source.
from systemrdl import RDLCompiler, RDLCompileError from systemrdl import RDLCompiler, RDLCompileError
from peakrdl_regblock import RegblockExporter 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 from peakrdl_regblock.udps import ALL_UDPS
input_files = [ input_files = [
@@ -78,7 +80,7 @@ implementation from SystemRDL source.
exporter = RegblockExporter() exporter = RegblockExporter()
exporter.export( exporter.export(
root, "path/to/output_dir", root, "path/to/output_dir",
cpuif_cls=APB3_Cpuif cpuif_cls=AXI4Lite_Cpuif
) )
@@ -99,8 +101,8 @@ Links
self self
architecture architecture
hwif hwif
api
configuring configuring
api
limitations limitations
licensing licensing
@@ -125,6 +127,12 @@ Links
props/signal props/signal
props/rhs_props props/rhs_props
.. toctree::
:hidden:
:caption: Other SystemRDL Features
rdl_features/external
.. toctree:: .. toctree::
:hidden: :hidden:
:caption: Extended Properties :caption: Extended Properties

View File

@@ -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. 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 <https://peakrdl.readthedocs.io/>`_. That said, I'd encourage you to check out the `PeakRDL command line tool <https://peakrdl.readthedocs.io/>`_.
It might already do everything you need. It may already do everything you need.

View File

@@ -6,11 +6,6 @@ supported properties, see the appropriate property listing page in the following
sections. sections.
External Components
-------------------
Regfiles, registers & fields instantiated using the ``external`` keyword are not supported yet.
Alias Registers Alias Registers
--------------- ---------------
Registers instantiated using the ``alias`` keyword are not supported yet. Registers instantiated using the ``alias`` keyword are not supported yet.

View File

@@ -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.

View File

@@ -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, Similarly, the spec defines ``swmod`` which gets asserted on software writes,
but can also get asserted if the field has on-read side-effects. 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 reads or writes a field? The ``rd_swacc`` and ``wr_swacc`` UDPs provide this
functionality. functionality.

View File

@@ -1,5 +1,6 @@
from typing import TYPE_CHECKING, Dict, Type from typing import TYPE_CHECKING, Dict, Type
import functools import functools
import sys
from peakrdl.plugins.exporter import ExporterSubcommandPlugin #pylint: disable=import-error from peakrdl.plugins.exporter import ExporterSubcommandPlugin #pylint: disable=import-error
from peakrdl.config import schema #pylint: disable=import-error from peakrdl.config import schema #pylint: disable=import-error
@@ -94,6 +95,10 @@ class Exporter(ExporterSubcommandPlugin):
default=False, default=False,
help="Enable additional retiming stage between readback fan-in and cpu interface" 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( arg_group.add_argument(
"--type-style", "--type-style",
dest="type_style", dest="type_style",
@@ -125,6 +130,29 @@ class Exporter(ExporterSubcommandPlugin):
def do_export(self, top_node: 'AddrmapNode', options: 'argparse.Namespace') -> None: def do_export(self, top_node: 'AddrmapNode', options: 'argparse.Namespace') -> None:
cpuifs = self.get_cpuifs() 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 = RegblockExporter()
x.export( x.export(
top_node, top_node,
@@ -135,6 +163,10 @@ class Exporter(ExporterSubcommandPlugin):
reuse_hwif_typedefs=(options.type_style == "lexical"), reuse_hwif_typedefs=(options.type_style == "lexical"),
retime_read_fanin=options.rt_read_fanin, retime_read_fanin=options.rt_read_fanin,
retime_read_response=options.rt_read_response, 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, generate_hwif_report=options.hwif_report,
address_width=options.addr_width, address_width=options.addr_width,
) )

View File

@@ -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 .utils import get_indexed_path
from .struct_generator import RDLStructGenerator from .struct_generator import RDLStructGenerator
@@ -9,13 +10,15 @@ from .identifier_filter import kw_filter as kwf
if TYPE_CHECKING: if TYPE_CHECKING:
from .exporter import RegblockExporter from .exporter import RegblockExporter
from systemrdl.node import AddrmapNode, AddressableNode
from systemrdl.node import RegfileNode, MemNode
class AddressDecode: class AddressDecode:
def __init__(self, exp:'RegblockExporter'): def __init__(self, exp:'RegblockExporter'):
self.exp = exp self.exp = exp
@property @property
def top_node(self) -> AddrmapNode: def top_node(self) -> 'AddrmapNode':
return self.exp.top_node return self.exp.top_node
def get_strobe_struct(self) -> str: def get_strobe_struct(self) -> str:
@@ -59,9 +62,57 @@ class AddressDecode:
return "decoded_reg_strb." + path 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): 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: def enter_Reg(self, node: 'RegNode') -> None:
# if register is "wide", expand the strobe to be able to access the sub-words # 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") 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]] 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) super().enter_AddressableComponent(node)
if not node.is_array: if node.is_array:
return # 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 if node.external and not isinstance(node, RegNode):
current_stride = node.array_stride # Is an external block
strides = [] addr_str = self._get_address_str(node)
for dim in reversed(node.array_dimensions): strb = self.addr_decode.get_external_block_access_strobe(node)
strides.append(current_stride) rhs = f"cpuif_req_masked & (cpuif_addr >= {addr_str}) & (cpuif_addr <= {addr_str} + 'h{(node.size - 1):x})"
current_stride *= dim self.add_content(f"{strb} = {rhs};")
strides.reverse() self.add_content(f"is_external |= {rhs};")
self._array_stride_stack.extend(strides) 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}" 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): for i, stride in enumerate(self._array_stride_stack):
a += f" + i{i}*'h{stride:x}" a += f" + i{i}*'h{stride:x}"
@@ -117,16 +177,21 @@ class DecodeLogicGenerator(RDLForLoopGenerator):
accesswidth = node.get_property('accesswidth') accesswidth = node.get_property('accesswidth')
if regwidth == 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) self.add_content(s)
if node.external:
self.add_content(f"is_external |= {rhs};")
else: else:
# Register is wide. Create a substrobe for each subword # Register is wide. Create a substrobe for each subword
n_subwords = regwidth // accesswidth n_subwords = regwidth // accesswidth
subword_stride = accesswidth // 8 subword_stride = accesswidth // 8
for i in range(n_subwords): 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) self.add_content(s)
if node.external:
self.add_content(f"is_external |= {rhs};")
def exit_AddressableComponent(self, node: 'AddressableNode') -> None: def exit_AddressableComponent(self, node: 'AddressableNode') -> None:
super().exit_AddressableComponent(node) super().exit_AddressableComponent(node)

View File

@@ -1,5 +1,5 @@
from typing import TYPE_CHECKING, Union, Optional 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 from systemrdl.rdltypes import PropertyReference
if TYPE_CHECKING: if TYPE_CHECKING:
@@ -205,6 +205,12 @@ class Dereferencer:
""" """
return self.address_decode.get_access_strobe(obj, reduce_substrobes) 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: def get_resetsignal(self, obj: Optional[SignalNode]) -> str:
""" """
Returns a normalized active-high reset signal Returns a normalized active-high reset signal

View File

@@ -18,6 +18,7 @@ from .cpuif.apb4 import APB4_Cpuif
from .hwif import Hwif from .hwif import Hwif
from .write_buffering import WriteBuffering from .write_buffering import WriteBuffering
from .read_buffering import ReadBuffering from .read_buffering import ReadBuffering
from .external_acks import ExternalWriteAckGenerator, ExternalReadAckGenerator
class RegblockExporter: class RegblockExporter:
def __init__(self, **kwargs: Any) -> None: def __init__(self, **kwargs: Any) -> None:
@@ -29,12 +30,12 @@ class RegblockExporter:
self.top_node = None # type: AddrmapNode self.top_node = None # type: AddrmapNode
self.hwif = None # type: Hwif self.hwif = None # type: Hwif
self.cpuif = None # type: CpuifBase self.cpuif = None # type: CpuifBase
self.address_decode = AddressDecode(self) self.address_decode = None # type: AddressDecode
self.field_logic = FieldLogic(self) self.field_logic = None # type: FieldLogic
self.readback = None # type: Readback self.readback = None # type: Readback
self.write_buffering = WriteBuffering(self) self.write_buffering = None # type: WriteBuffering
self.read_buffering = ReadBuffering(self) self.read_buffering = None # type: ReadBuffering
self.dereferencer = Dereferencer(self) self.dereferencer = None # type: Dereferencer
self.min_read_latency = 0 self.min_read_latency = 0
self.min_write_latency = 0 self.min_write_latency = 0
@@ -97,6 +98,14 @@ class RegblockExporter:
response path sequentially may not result in any meaningful timing improvement. response path sequentially may not result in any meaningful timing improvement.
Enabling this option will increase read transfer latency by 1 clock cycle. 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 generate_hwif_report: bool
If set, generates a hwif report that can help designers understand If set, generates a hwif report that can help designers understand
the contents of the ``hwif_in`` and ``hwif_out`` structures. the contents of the ``hwif_in`` and ``hwif_out`` structures.
@@ -121,7 +130,11 @@ class RegblockExporter:
# Pipelining options # Pipelining options
retime_read_fanin = kwargs.pop("retime_read_fanin", False) # type: bool 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 # Check for stray kwargs
if kwargs: if kwargs:
@@ -164,11 +177,26 @@ class RegblockExporter:
user_enums=scanner.user_enums, user_enums=scanner.user_enums,
reuse_typedefs=reuse_hwif_typedefs, reuse_typedefs=reuse_hwif_typedefs,
hwif_report_file=hwif_report_file, hwif_report_file=hwif_report_file,
data_width=scanner.cpuif_data_width,
) )
self.readback = Readback( self.readback = Readback(
self, 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 # Validate that there are no unsupported constructs
validator = DesignValidator(self) validator = DesignValidator(self)
@@ -181,6 +209,8 @@ class RegblockExporter:
"has_writable_msb0_fields": scanner.has_writable_msb0_fields, "has_writable_msb0_fields": scanner.has_writable_msb0_fields,
"has_buffered_write_regs": scanner.has_buffered_write_regs, "has_buffered_write_regs": scanner.has_buffered_write_regs,
"has_buffered_read_regs": scanner.has_buffered_read_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, "cpuif": self.cpuif,
"hwif": self.hwif, "hwif": self.hwif,
"write_buffering": self.write_buffering, "write_buffering": self.write_buffering,
@@ -189,8 +219,11 @@ class RegblockExporter:
"address_decode": self.address_decode, "address_decode": self.address_decode,
"field_logic": self.field_logic, "field_logic": self.field_logic,
"readback": self.readback, "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), "get_always_ff_event": lambda resetsignal : get_always_ff_event(self.dereferencer, resetsignal),
"retime_read_response": retime_read_response, "retime_read_response": retime_read_response,
"retime_read_fanin": retime_read_fanin,
"min_read_latency": self.min_read_latency, "min_read_latency": self.min_read_latency,
"min_write_latency": self.min_write_latency, "min_write_latency": self.min_write_latency,
"kwf": kwf, "kwf": kwf,

View File

@@ -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

View File

@@ -20,8 +20,19 @@ if TYPE_CHECKING:
from ..exporter import RegblockExporter from ..exporter import RegblockExporter
class FieldLogic: 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.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._hw_conditionals = {} # type: Dict[int, List[NextStateConditional]]
self._sw_conditionals = {} # type: Dict[int, List[NextStateConditional]] self._sw_conditionals = {} # type: Dict[int, List[NextStateConditional]]

View File

@@ -1,15 +1,18 @@
from typing import TYPE_CHECKING, List from typing import TYPE_CHECKING, List, Optional
from collections import OrderedDict from collections import OrderedDict
from systemrdl.walker import WalkerAction
from systemrdl.node import RegNode, RegfileNode, MemNode, AddrmapNode
from ..struct_generator import RDLStructGenerator from ..struct_generator import RDLStructGenerator
from ..forloop_generator import RDLForLoopGenerator 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 from ..identifier_filter import kw_filter as kwf
if TYPE_CHECKING: if TYPE_CHECKING:
from . import FieldLogic from . import FieldLogic
from systemrdl.node import FieldNode, RegNode from systemrdl.node import FieldNode, AddressableNode
from .bases import SVLogic from .bases import SVLogic
class CombinationalStructGenerator(RDLStructGenerator): class CombinationalStructGenerator(RDLStructGenerator):
@@ -18,6 +21,12 @@ class CombinationalStructGenerator(RDLStructGenerator):
super().__init__() super().__init__()
self.field_logic = field_logic 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: def enter_Field(self, node: 'FieldNode') -> None:
# If a field doesn't implement storage, it is not relevant here # If a field doesn't implement storage, it is not relevant here
@@ -67,6 +76,13 @@ class FieldStorageStructGenerator(RDLStructGenerator):
super().__init__() super().__init__()
self.field_logic = field_logic 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: def enter_Field(self, node: 'FieldNode') -> None:
self.push_struct(kwf(node.inst_name)) self.push_struct(kwf(node.inst_name))
@@ -88,14 +104,39 @@ class FieldLogicGenerator(RDLForLoopGenerator):
self.field_storage_template = self.exp.jj_env.get_template( self.field_storage_template = self.exp.jj_env.get_template(
"field_logic/templates/field_storage.sv" "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.intr_fields = [] # type: List[FieldNode]
self.halt_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.intr_fields = []
self.halt_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: def enter_Field(self, node: 'FieldNode') -> None:
if node.implements_storage: if node.implements_storage:
@@ -277,3 +318,49 @@ class FieldLogicGenerator(RDLForLoopGenerator):
self.add_content( self.add_content(
f"assign {output_identifier} = {value};" 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))

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -1,6 +1,6 @@
from typing import TYPE_CHECKING, Union, Set, Dict, Optional, TextIO, Type, List 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 systemrdl.rdltypes import PropertyReference, UserEnum
from ..utils import get_indexed_path from ..utils import get_indexed_path
@@ -25,7 +25,8 @@ class Hwif:
self, exp: 'RegblockExporter', package_name: str, self, exp: 'RegblockExporter', package_name: str,
user_enums: List[Type[UserEnum]], user_enums: List[Type[UserEnum]],
in_hier_signal_paths: Set[str], out_of_hier_signals: Dict[str, SignalNode], 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.exp = exp
self.package_name = package_name self.package_name = package_name
@@ -39,6 +40,8 @@ class Hwif:
self.hwif_report_file = hwif_report_file self.hwif_report_file = hwif_report_file
self.data_width = data_width
if reuse_typedefs: if reuse_typedefs:
self._gen_in_cls = InputStructGenerator_TypeScope self._gen_in_cls = InputStructGenerator_TypeScope
self._gen_out_cls = OutputStructGenerator_TypeScope self._gen_out_cls = OutputStructGenerator_TypeScope
@@ -162,6 +165,26 @@ class Hwif:
raise RuntimeError(f"Unhandled reference to: {obj}") 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: def get_implied_prop_input_identifier(self, field: FieldNode, prop: str) -> str:
assert prop in { assert prop in {

View File

@@ -1,12 +1,13 @@
from typing import TYPE_CHECKING, Optional, List, Type 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 ..struct_generator import RDLFlatStructGenerator
from ..identifier_filter import kw_filter as kwf from ..identifier_filter import kw_filter as kwf
if TYPE_CHECKING: if TYPE_CHECKING:
from systemrdl.node import Node, SignalNode, RegNode from systemrdl.node import Node, SignalNode, AddressableNode, RegfileNode
from . import Hwif from . import Hwif
from systemrdl.rdltypes import UserEnum from systemrdl.rdltypes import UserEnum
@@ -65,6 +66,43 @@ class InputStructGenerator_Hier(HWIFStructGenerator):
if path in self.hwif.in_hier_signal_paths: if path in self.hwif.in_hier_signal_paths:
self.add_member(kwf(node.inst_name), node.width) 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: def enter_Field(self, node: 'FieldNode') -> None:
type_name = self.get_typdef_name(node) type_name = self.get_typdef_name(node)
self.push_struct(type_name, kwf(node.inst_name)) self.push_struct(type_name, kwf(node.inst_name))
@@ -120,6 +158,47 @@ class OutputStructGenerator_Hier(HWIFStructGenerator):
) )
return f'{base}__out_t' 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: def enter_Field(self, node: 'FieldNode') -> None:
type_name = self.get_typdef_name(node) type_name = self.get_typdef_name(node)
self.push_struct(type_name, kwf(node.inst_name)) self.push_struct(type_name, kwf(node.inst_name))
@@ -162,6 +241,10 @@ class InputStructGenerator_TypeScope(InputStructGenerator_Hier):
else: else:
extra_suffix = "" extra_suffix = ""
if node.external:
# Node generates alternate external signals
extra_suffix += "__external"
return f'{scope_path}__{node.type_name}{extra_suffix}__in_t' return f'{scope_path}__{node.type_name}{extra_suffix}__in_t'
class OutputStructGenerator_TypeScope(OutputStructGenerator_Hier): class OutputStructGenerator_TypeScope(OutputStructGenerator_Hier):
@@ -177,6 +260,10 @@ class OutputStructGenerator_TypeScope(OutputStructGenerator_Hier):
else: else:
extra_suffix = "" extra_suffix = ""
if node.external:
# Node generates alternate external signals
extra_suffix += "__external"
return f'{scope_path}__{node.type_name}{extra_suffix}__out_t' return f'{scope_path}__{node.type_name}{extra_suffix}__out_t'
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------

View File

@@ -40,11 +40,30 @@ module {{module_name}} (
{{cpuif.get_implementation()|indent}} {{cpuif.get_implementation()|indent}}
logic cpuif_req_masked; 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 %} {% if min_read_latency == min_write_latency %}
// Read & write latencies are balanced. Stalls not required // 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_rd = '0;
assign cpuif_req_stall_wr = '0; assign cpuif_req_stall_wr = '0;
assign cpuif_req_masked = cpuif_req; {%- endif %}
{%- elif min_read_latency > min_write_latency %} {%- elif min_read_latency > min_write_latency %}
// Read latency > write latency. May need to delay next write that follows a read // 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; 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); cpuif_req_stall_sr <= (cpuif_req_stall_sr >> 'd1);
end end
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_rd = '0;
assign cpuif_req_stall_wr = cpuif_req_stall_sr[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 %} {%- else %}
// Write latency > read latency. May need to delay next read that follows a write // 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; 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); cpuif_req_stall_sr <= (cpuif_req_stall_sr >> 'd1);
end end
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_rd = cpuif_req_stall_sr[0];
assign cpuif_req_stall_wr = '0; assign cpuif_req_stall_wr = '0;
assign cpuif_req_masked = cpuif_req & !(!cpuif_req_is_wr & cpuif_req_stall_rd); {%- endif %}
{%- 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
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
{{address_decode.get_strobe_struct()|indent}} {{address_decode.get_strobe_struct()|indent}}
decoded_reg_strb_t decoded_reg_strb; 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;
logic decoded_req_is_wr; logic decoded_req_is_wr;
logic [{{cpuif.data_width-1}}:0] decoded_wr_data; logic [{{cpuif.data_width-1}}:0] decoded_wr_data;
logic [{{cpuif.data_width-1}}:0] decoded_wr_biten; logic [{{cpuif.data_width-1}}:0] decoded_wr_biten;
always_comb begin always_comb begin
{%- if has_external_addressable %}
automatic logic is_external = '0;
{% endif %}
{{address_decode.get_implementation()|indent(8)}} {{address_decode.get_implementation()|indent(8)}}
{%- if has_external_addressable %}
decoded_strb_is_external = is_external;
external_req = is_external;
{% endif %}
end end
// Pass down signals to next stage // 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 = cpuif_req_masked;
assign decoded_req_is_wr = cpuif_req_is_wr; assign decoded_req_is_wr = cpuif_req_is_wr;
assign decoded_wr_data = cpuif_wr_data; assign decoded_wr_data = cpuif_wr_data;
@@ -104,10 +150,6 @@ module {{module_name}} (
assign decoded_wr_biten_bswap = {<<{decoded_wr_biten}}; assign decoded_wr_biten_bswap = {<<{decoded_wr_biten}};
{%- endif %} {%- 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 %} {%- if has_buffered_write_regs %}
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
@@ -135,9 +177,52 @@ module {{module_name}} (
{{read_buffering.get_implementation()|indent}} {{read_buffering.get_implementation()|indent}}
{%- endif %} {%- 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 // 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_err;
logic readback_done; logic readback_done;
logic [{{cpuif.data_width-1}}:0] readback_data; logic [{{cpuif.data_width-1}}:0] readback_data;
@@ -148,14 +233,27 @@ module {{module_name}} (
cpuif_rd_ack <= '0; cpuif_rd_ack <= '0;
cpuif_rd_data <= '0; cpuif_rd_data <= '0;
cpuif_rd_err <= '0; cpuif_rd_err <= '0;
{%- if has_external_addressable %}
external_rd_ack <= '0;
{%- endif %}
end else begin 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; cpuif_rd_ack <= readback_done;
{%- endif %}
cpuif_rd_data <= readback_data; cpuif_rd_data <= readback_data;
cpuif_rd_err <= readback_err; cpuif_rd_err <= readback_err;
end end
end end
{% else %} {% 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; assign cpuif_rd_ack = readback_done;
{%- endif %}
assign cpuif_rd_data = readback_data; assign cpuif_rd_data = readback_data;
assign cpuif_rd_err = readback_err; assign cpuif_rd_err = readback_err;
{%- endif %} {%- endif %}

View File

@@ -9,9 +9,10 @@ if TYPE_CHECKING:
from systemrdl.node import AddrmapNode from systemrdl.node import AddrmapNode
class Readback: 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.exp = exp
self.do_fanin_stage = do_fanin_stage self.do_fanin_stage = do_fanin_stage
self.has_external_addressable = has_external_addressable
@property @property
def top_node(self) -> 'AddrmapNode': 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), "get_always_ff_event": lambda resetsignal : get_always_ff_event(self.exp.dereferencer, resetsignal),
"cpuif": self.exp.cpuif, "cpuif": self.exp.cpuif,
"do_fanin_stage": self.do_fanin_stage, "do_fanin_stage": self.do_fanin_stage,
"has_external_addressable": self.has_external_addressable,
} }
if self.do_fanin_stage: if self.do_fanin_stage:

View File

@@ -1,6 +1,7 @@
from typing import TYPE_CHECKING, List 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 from ..forloop_generator import RDLForLoopGenerator, LoopBody
@@ -77,9 +78,26 @@ class ReadbackAssignmentGenerator(RDLForLoopGenerator):
self.current_offset = start_offset + n_regs * dim 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: if not node.has_sw_readable:
return return WalkerAction.SkipDescendants
accesswidth = node.get_property('accesswidth') accesswidth = node.get_property('accesswidth')
regwidth = node.get_property('regwidth') regwidth = node.get_property('regwidth')
@@ -100,6 +118,19 @@ class ReadbackAssignmentGenerator(RDLForLoopGenerator):
else: else:
self.process_reg(node) 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: def process_reg(self, node: RegNode) -> None:
current_bit = 0 current_bit = 0

View File

@@ -35,7 +35,11 @@ always_ff @(posedge clk) begin
readback_done_r <= '0; readback_done_r <= '0;
end else begin end else begin
readback_array_r <= readback_array_c; 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; readback_done_r <= decoded_req & ~decoded_req_is_wr;
{%- endif %}
end end
end end
@@ -54,7 +58,11 @@ end
// Reduce the array // Reduce the array
always_comb begin always_comb begin
automatic logic [{{cpuif.data_width-1}}:0] readback_data_var; 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; readback_done = decoded_req & ~decoded_req_is_wr;
{%- endif %}
readback_err = '0; readback_err = '0;
readback_data_var = '0; readback_data_var = '0;
for(int i=0; i<{{array_size}}; i++) readback_data_var |= readback_array[i]; for(int i=0; i<{{array_size}}; i++) readback_data_var |= readback_array[i];

View File

@@ -2,10 +2,10 @@ from typing import TYPE_CHECKING, Set, Optional, Type, List
from collections import OrderedDict from collections import OrderedDict
from systemrdl.walker import RDLListener, RDLWalker, WalkerAction from systemrdl.walker import RDLListener, RDLWalker, WalkerAction
from systemrdl.node import SignalNode from systemrdl.node import SignalNode, RegNode
if TYPE_CHECKING: if TYPE_CHECKING:
from systemrdl.node import Node, RegNode, FieldNode from systemrdl.node import Node, FieldNode, AddressableNode
from .exporter import RegblockExporter from .exporter import RegblockExporter
from systemrdl.rdltypes import UserEnum from systemrdl.rdltypes import UserEnum
@@ -30,6 +30,9 @@ class DesignScanner(RDLListener):
self.has_buffered_write_regs = False self.has_buffered_write_regs = False
self.has_buffered_read_regs = False self.has_buffered_read_regs = False
self.has_external_block = False
self.has_external_addressable = False
# Track any referenced enums # Track any referenced enums
self.user_enums = [] # type: List[Type[UserEnum]] self.user_enums = [] # type: List[Type[UserEnum]]
@@ -91,7 +94,13 @@ class DesignScanner(RDLListener):
if value not in self.user_enums: if value not in self.user_enums:
self.user_enums.append(value) 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: def enter_Reg(self, node: 'RegNode') -> None:
# The CPUIF's bus width is sized according to the largest accesswidth in the design # The CPUIF's bus width is sized according to the largest accesswidth in the design

View File

@@ -9,7 +9,7 @@ from .identifier_filter import kw_filter as kwf
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Union 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: class _StructBase:
@@ -144,6 +144,12 @@ class RDLStructGenerator(StructGenerator, RDLListener):
def exit_Regfile(self, node: 'RegfileNode') -> None: def exit_Regfile(self, node: 'RegfileNode') -> None:
self.pop_struct() 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: def enter_Reg(self, node: 'RegNode') -> None:
self.push_struct(kwf(node.inst_name), node.array_dimensions) 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: def exit_Regfile(self, node: 'RegfileNode') -> None:
self.pop_struct() 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: def enter_Reg(self, node: 'RegNode') -> None:
type_name = self.get_typdef_name(node) type_name = self.get_typdef_name(node)
self.push_struct(type_name, kwf(node.inst_name), node.array_dimensions) self.push_struct(type_name, kwf(node.inst_name), node.array_dimensions)

View File

@@ -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.walker import RDLListener, RDLWalker, WalkerAction
from systemrdl.rdltypes import PropertyReference 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 from .utils import ref_is_internal
if TYPE_CHECKING: if TYPE_CHECKING:
from systemrdl.node import RegNode, FieldNode, SignalNode, AddressableNode
from .exporter import RegblockExporter from .exporter import RegblockExporter
class DesignValidator(RDLListener): class DesignValidator(RDLListener):
@@ -19,6 +21,9 @@ class DesignValidator(RDLListener):
self.exp = exp self.exp = exp
self.msg = exp.top_node.env.msg 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: def do_validate(self) -> None:
RDLWalker().walk(self.exp.top_node, self) RDLWalker().walk(self.exp.top_node, self)
if self.msg.had_error: if self.msg.had_error:
@@ -28,10 +33,6 @@ class DesignValidator(RDLListener):
def enter_Component(self, node: 'Node') -> Optional[WalkerAction]: def enter_Component(self, node: 'Node') -> Optional[WalkerAction]:
if node.external and (node != self.exp.top_node): 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 # Do not inspect external components. None of my business
return WalkerAction.SkipDescendants 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.", "Property is assigned a reference that points to a component not internal to the regblock being exported.",
src_ref src_ref
) )
return None return None
def enter_Signal(self, node: 'SignalNode') -> None: def enter_Signal(self, node: 'SignalNode') -> None:
@@ -80,6 +79,27 @@ class DesignValidator(RDLListener):
node.inst.inst_src_ref 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: def enter_Reg(self, node: 'RegNode') -> None:
# accesswidth of wide registers must be consistent within the register block # accesswidth of wide registers must be consistent within the register block
accesswidth = node.get_property('accesswidth') accesswidth = node.get_property('accesswidth')
@@ -104,6 +124,16 @@ class DesignValidator(RDLListener):
and (node.lsb // parent_accesswidth) != (node.msb // parent_accesswidth) and (node.lsb // parent_accesswidth) != (node.msb // parent_accesswidth)
): ):
# field spans multiple sub-words # 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'): if node.is_sw_writable and not node.parent.get_property('buffer_writes'):
# ... and is writable without the protection of double-buffering # ... and is writable without the protection of double-buffering
# Enforce 10.6.1-f # 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", "For more details, see: https://peakrdl-regblock.readthedocs.io/en/latest/udps/read_buffering.html",
node.inst.inst_src_ref 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
)

View File

@@ -36,6 +36,7 @@ class BaseTestCase(unittest.TestCase):
retime_read_fanin = False retime_read_fanin = False
retime_read_response = False retime_read_response = False
reuse_hwif_typedefs = True reuse_hwif_typedefs = True
retime_external = False
#: this gets auto-loaded via the _load_request autouse fixture #: this gets auto-loaded via the _load_request autouse fixture
request = None # type: pytest.FixtureRequest request = None # type: pytest.FixtureRequest
@@ -105,7 +106,11 @@ class BaseTestCase(unittest.TestCase):
cpuif_cls=cls.cpuif.cpuif_cls, cpuif_cls=cls.cpuif.cpuif_cls,
retime_read_fanin=cls.retime_read_fanin, retime_read_fanin=cls.retime_read_fanin,
retime_read_response=cls.retime_read_response, 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 @classmethod

View File

@@ -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<WIDTH; b++) begin
if(biten[b]) mem[idx][b] <= data[b];
end
wr_ack <= '1;
endtask
task do_read(int idx);
automatic int delay;
// Random delay
delay = $urandom_range(3,0);
repeat(delay) @(posedge clk)
$info("Read delay: %d", delay);
rd_data <= mem[idx];
rd_ack <= '1;
endtask;
initial begin
rd_ack <= '0;
rd_data <= '0;
wr_ack <= '0;
for(int i=0; i<N_ENTRIES; i++) mem[i] <= '0;
forever begin
// Wait for next clock edge
@(posedge clk);
rd_ack <= '0;
rd_data <= '0;
wr_ack <= '0;
// wait slightly longer to "peek" at the current cycle's state
#1ns;
if(!rst && req) begin
if(req_is_wr) do_write(addr >> ADDR_SHIFT, wr_data, wr_biten);
else do_read(addr >> ADDR_SHIFT);
end
end
end
endmodule

78
tests/lib/external_reg.sv Normal file
View File

@@ -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<SUBWORDS; i++) begin
if(strb[i]) begin
for(int b=0; b<WIDTH; b++) begin
if(biten[b]) value[i][b] <= data[b];
end
end
end
wr_ack <= '1;
endtask
task do_read(logic [SUBWORDS-1:0] strb);
automatic int delay;
// Random delay
delay = $urandom_range(3,0);
repeat(delay) @(posedge clk)
$info("Read delay: %d", delay);
for(int i=0; i<SUBWORDS; i++) begin
if(strb[i]) begin
rd_data <= value[i];
end
end
rd_ack <= '1;
endtask;
initial begin
rd_ack <= '0;
rd_data <= '0;
wr_ack <= '0;
value <= '0;
forever begin
// Wait for next clock edge
@(posedge clk);
rd_ack <= '0;
rd_data <= '0;
wr_ack <= '0;
// wait slightly longer to "peek" at the current cycle's state
#1ns;
if(!rst && req) begin
if(req_is_wr) do_write(req, wr_data, wr_biten);
else do_read(req);
end
end
end
endmodule

View File

@@ -22,6 +22,25 @@ class SimTestCase(BaseTestCase):
tb_template_file = "tb_template.sv" tb_template_file = "tb_template.sv"
# Paths are relative to the testcase dir
extra_tb_files = [] # type: List[str]
# Whether to initialize the hwif_in struct at test startup
init_hwif_in = True
# Control whether to include in clocking block
clocking_hwif_in = True
clocking_hwif_out = True
@classmethod
def get_extra_tb_files(cls) -> 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 @classmethod
def _generate_tb(cls): def _generate_tb(cls):
""" """

View File

@@ -13,6 +13,7 @@ class Simulator:
def tb_files(self) -> List[str]: def tb_files(self) -> List[str]:
files = [] files = []
files.extend(self.testcase_cls.cpuif.get_sim_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_pkg.sv")
files.append("regblock.sv") files.append("regblock.sv")
files.append("tb.sv") files.append("tb.sv")

View File

@@ -30,11 +30,11 @@ module tb;
default clocking cb @(posedge clk); default clocking cb @(posedge clk);
default input #1step output #1; default input #1step output #1;
output rst; output rst;
{%- if exporter.hwif.has_input_struct %} {%- if exporter.hwif.has_input_struct and cls.clocking_hwif_in %}
output hwif_in; output hwif_in;
{%- endif %} {%- endif %}
{%- if exporter.hwif.has_output_struct %} {%- if exporter.hwif.has_output_struct and cls.clocking_hwif_out %}
input hwif_out; input hwif_out;
{%- endif %} {%- endif %}
@@ -68,12 +68,15 @@ module tb;
{%- endif %} {%- endif %}
{% sv_line_anchor %} {% sv_line_anchor %}
{%- block dut_support %}
{%- endblock %}
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// Test Sequence // Test Sequence
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
initial begin initial begin
cb.rst <= '1; 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}; cb.hwif_in <= '{default: '0};
{%- endif %} {%- endif %}

View File

@@ -492,7 +492,7 @@ valid-metaclass-classmethod-first-arg=cls
ignored-parents= ignored-parents=
# Maximum number of arguments for function / method. # Maximum number of arguments for function / method.
max-args=8 max-args=16
# Maximum number of attributes for a class (see R0902). # Maximum number of attributes for a class (see R0902).
max-attributes=7 max-attributes=7

View File

View File

@@ -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;
};

View File

@@ -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 %}

View File

@@ -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()