@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
133
docs/rdl_features/external.rst
Normal file
133
docs/rdl_features/external.rst
Normal 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.
|
||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
51
src/peakrdl_regblock/external_acks.py
Normal file
51
src/peakrdl_regblock/external_acks.py
Normal 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
|
||||||
@@ -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]]
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
31
src/peakrdl_regblock/field_logic/templates/external_block.sv
Normal file
31
src/peakrdl_regblock/field_logic/templates/external_block.sv
Normal 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 %}
|
||||||
28
src/peakrdl_regblock/field_logic/templates/external_reg.sv
Normal file
28
src/peakrdl_regblock/field_logic/templates/external_reg.sv
Normal 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 %}
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|
||||||
#-------------------------------------------------------------------------------
|
#-------------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -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 %}
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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];
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
73
tests/lib/external_block.sv
Normal file
73
tests/lib/external_block.sv
Normal 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
78
tests/lib/external_reg.sv
Normal 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
|
||||||
@@ -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):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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 %}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
0
tests/test_external/__init__.py
Normal file
0
tests/test_external/__init__.py
Normal file
29
tests/test_external/regblock.rdl
Normal file
29
tests/test_external/regblock.rdl
Normal 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;
|
||||||
|
};
|
||||||
207
tests/test_external/tb_template.sv
Normal file
207
tests/test_external/tb_template.sv
Normal 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 %}
|
||||||
31
tests/test_external/testcase.py
Normal file
31
tests/test_external/testcase.py
Normal 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()
|
||||||
Reference in New Issue
Block a user