Implement write buffering (#22)

This commit is contained in:
Alex Mykyta
2022-10-24 21:49:47 -07:00
parent 808067fac9
commit 279a3c5788
29 changed files with 968 additions and 93 deletions

Binary file not shown.

BIN
docs/diagrams/rbuf.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

BIN
docs/diagrams/wbuf.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

View File

@@ -115,3 +115,10 @@ Links
props/addrmap
props/signal
props/rhs_props
.. toctree::
:hidden:
:caption: Extended Properties
udps/intro
udps/write_buffering

View File

@@ -60,7 +60,9 @@ swmod
|OK|
If true, infers an output signal ``hwif_out..swmod`` that is asserted as the
field is being modified by software.
field is being modified by software. This can be due to a software write
operation, or a software read operation that has clear/set side-effects.
.. wavedrom::

32
docs/udps/intro.rst Normal file
View File

@@ -0,0 +1,32 @@
Introduction
============
Although the official SystemRDL spec defines numerous properties that allow you
to define complex register map structures, sometimes they are not enough to
accurately describe a necessary feature. Fortunately the SystemRDL spec allows
the language to be extended using "User Defined Properties" (UDPs). The
PeakRDL-regblock tool understands several UDPs that are described in this
section.
.. list-table:: Summary of UDPs
:header-rows: 1
* - Name
- Component
- Type
- Description
* - buffer_writes
- reg
- boolean
- If set, writes to the register are double-buffered.
See details here: :ref:`write_buffering`.
* - wbuffer_trigger
- reg
- reference
- Defines the buffered write commit trigger.
See details here: :ref:`write_buffering`.

View File

@@ -0,0 +1,173 @@
.. _write_buffering:
Write-buffered Registers
========================
In some situations, your hardware design may requre that fields be
updated atomically (on the same clock cycle), but your cpuif is not wide enough
to do so natively. Using some UDP extensions, this regblock generator implements
a way for you to add write-buffering to specific registers, which allows the
regblock to delay the effect of a software write operation until a defined
trigger event.
Some examples of when this is useful:
* You need to have software update a wide 64-bit register atomically, but
the CPU interface is only 32-bits.
* Software needs to be able to write multiple registers such that the
hardware is updated atomically.
* Software can pre-load one or more registers with their next value, and
trigger the update via an external hardware signal.
If a register is write-buffered, a holding buffer stage is inserted between the
decode logic and the field logic. This effectively defers any software write
operations to that register until a trigger event occurs that releases it.
Write buffering storage is unique to each register that enables it.
If a register is not write buffered, this buffer stage is bypassed.
.. figure:: ../diagrams/wbuf.png
Properties
----------
The behavior of write-buffered registers is defined using the following two
properties:
``buffer_writes``
* Assigned value is a boolean.
* If true, enables double-buffering of writes to this register.
* Any software write operation to a buffered register is held back in a storage element
unique to the register.
* The software write operation is committed to the register once triggered to do so.
* Unless specified otherwise, the buffer trigger occurs when the highest
address of the buffered register is written.
``wbuffer_trigger``
* Assigned value is a reference to a register, single-bit field, signal, or single-bit property.
* Controls when the double-buffer commits the software write operation to the register's fields.
* If reference is a single-bit value (signal, field, property reference),
then the assertion of that value triggers the buffer to be evicted.
* Signal references shall have either activehigh/activelow property set to define the polarity.
* If the reference is a reg, then buffer is evicted when the register's
highest address is written
Other Rules
^^^^^^^^^^^
* It is an error to set ``buffer_writes`` if the register does not contain any
writable fields
* If ``buffer_writes`` is false, then anything assigned to ``wbuffer_trigger``
is ignored.
* The buffered register and the trigger reference shall both be within the same
internal device. ie: one cannot be in an external scope with respect to the
other.
* Unless it is a register, the reference assigned to ``wbuffer_trigger`` shall
represent a single bit.
* If a buffered register was not written, any trigger events are ignored.
* It is valid for a buffered register to be partially written (either via write
strobes, or partial addressing).
* The software write operation is not considered to take place until the buffer
is evicted by the trigger. This influences the behavior of properties like
``swmod`` and ``swacc`` - they are not asserted until the register's fields
are actually written by the buffer.
Examples
--------
Below are several examples of what you can do with registers that are
write-buffered.
Wide Atomic Register
^^^^^^^^^^^^^^^^^^^^
Without write-buffering, it is impossible to update the state of a 64-bit
register using a 32-bit CPU interface in a single clock-cycle.
In this example, it still requires two write-cycles to update the register, but
the register's storage element is not updated until both sub-words are written.
Upon writing the 2nd sub-word (the higher byte address), the write data for both
write cycles are committed to the register's storage element together on the
same clock cycle. The register is updated atomically.
.. code-block:: systemrdl
:emphasize-lines: 4
reg {
regwidth = 64;
accesswidth = 32;
buffer_writes = true;
field {
sw=rw; hw=r;
} my_field[63:0] = 0;
};
Atomic Group of Registers
^^^^^^^^^^^^^^^^^^^^^^^^^
Perhaps you have a group of registers that need their state to be updated
atomically. Using the ``wbuffer_trigger`` property, you can define which register
write operation triggers the group to be updated.
.. code-block:: systemrdl
:emphasize-lines: 2, 18-20
reg my_buffered_reg {
buffer_writes = true;
field {
sw=rw; hw=r;
} my_field[31:0] = 0;
};
my_buffered_reg reg1;
my_buffered_reg reg2;
my_buffered_reg reg3;
reg {
field {
sw=rw; hw=r;
} my_field[31:0] = 0;
} reg4;
reg1->wbuffer_trigger = reg4;
reg2->wbuffer_trigger = reg4;
reg3->wbuffer_trigger = reg4;
In this example software may pre-write information into reg1-reg3, but the
register write operations do not take effect until software also writes to reg4.
The write operation to reg4 triggers the buffered data to be committed to reg1-reg3.
This is guaranteed to occur on the same clock-cycle.
Externally Triggered Register Update
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Some applications may require precise timing for when a register (or group of registers)
update their value. Often software cannot offer such timing precision.
In this example, the trigger event is bound to an external signal. When asserted,
any pending write operation the buffered register will be committed.
The hwif_out value presents the new register state on the clock cycle after the
trigger is asserted.
.. code-block:: systemrdl
:emphasize-lines: 2, 11-13
reg my_buffered_reg {
buffer_writes = true;
field {
sw=rw; hw=r;
} my_field[31:0] = 0;
};
my_buffered_reg reg1;
my_buffered_reg reg2;
signal {
activehigh;
} trigger_signal;
reg1->wbuffer_trigger = trigger_signal;
reg2->wbuffer_trigger = trigger_signal;
After software writes to ``reg1`` & ``reg2``, the written data is held back in the write
buffer until ``hwif_in..trigger_signal`` is asserted by the hardware.

18
hdl-src/regblock_udps.rdl Normal file
View File

@@ -0,0 +1,18 @@
/*
* This file defines several property extensions that are understood by the
* PeakRDL-Regblock SystemVerilog code generator.
*
* Compile this file prior to your other SystemRDL sources.
*
* For more details, see: https://peakrdl-regblock.readthedocs.io/en/latest/udps/intro.html
*/
property buffer_writes {
component = reg;
type = boolean;
};
property wbuffer_trigger {
component = reg;
type = ref;
};

View File

@@ -24,7 +24,7 @@ setuptools.setup(
include_package_data=True,
python_requires='>=3.6',
install_requires=[
"systemrdl-compiler>=1.25.0",
"systemrdl-compiler>=1.25.1",
"Jinja2>=2.11",
],
entry_points = {
@@ -42,6 +42,7 @@ setuptools.setup(
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3 :: Only",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",

View File

@@ -120,6 +120,7 @@ class DecodeLogicGenerator(RDLForLoopGenerator):
s = f"{self.addr_decode.get_access_strobe(node)} = cpuif_req_masked & (cpuif_addr == {self._get_address_str(node)});"
self.add_content(s)
else:
# Register is wide. Create a substrobe for each subword
n_subwords = regwidth // accesswidth
subword_stride = accesswidth // 8
for i in range(n_subwords):

View File

@@ -10,12 +10,13 @@ from .dereferencer import Dereferencer
from .readback import Readback
from .identifier_filter import kw_filter as kwf
from .cpuif import CpuifBase
from .cpuif.apb4 import APB4_Cpuif
from .hwif import Hwif
from .utils import get_always_ff_event
from .scan_design import DesignScanner
from .validate_design import DesignValidator
from .cpuif import CpuifBase
from .cpuif.apb4 import APB4_Cpuif
from .hwif import Hwif
from .write_buffering import WriteBuffering
class RegblockExporter:
def __init__(self, **kwargs: Any) -> None:
@@ -30,6 +31,7 @@ class RegblockExporter:
self.address_decode = AddressDecode(self)
self.field_logic = FieldLogic(self)
self.readback = None # type: Readback
self.write_buffering = None # type: WriteBuffering
self.dereferencer = Dereferencer(self)
self.min_read_latency = 0
self.min_write_latency = 0
@@ -143,6 +145,7 @@ class RegblockExporter:
self,
retime_read_fanin
)
self.write_buffering = WriteBuffering(self)
# Validate that there are no unsupported constructs
validator = DesignValidator(self)
@@ -153,8 +156,11 @@ class RegblockExporter:
"module_name": module_name,
"user_out_of_hier_signals": scanner.out_of_hier_signals.values(),
"has_writable_msb0_fields": scanner.has_writable_msb0_fields,
"has_buffered_write_regs": scanner.has_buffered_write_regs,
"has_buffered_read_regs": scanner.has_buffered_read_regs,
"cpuif": self.cpuif,
"hwif": self.hwif,
"write_buffering": self.write_buffering,
"get_resetsignal": self.dereferencer.get_resetsignal,
"address_decode": self.address_decode,
"field_logic": self.field_logic,

View File

@@ -1,7 +1,6 @@
from typing import TYPE_CHECKING
from systemrdl.rdltypes import PropertyReference, PrecedenceType, InterruptType
from systemrdl.node import Node
from systemrdl.rdltypes import PrecedenceType, InterruptType
from .bases import AssignmentPrecedence, NextStateConditional
from . import sw_onread
@@ -186,19 +185,35 @@ class FieldLogic:
"""
w_modifiable = field.is_sw_writable
r_modifiable = (field.get_property('onread') is not None)
strb = self.exp.dereferencer.get_access_strobe(field)
buffer_writes = field.parent.get_property('buffer_writes')
if w_modifiable and not r_modifiable:
# assert swmod only on sw write
return f"{strb} && decoded_req_is_wr"
if buffer_writes:
# Write strobe arrives from buffer layer instead
wstrb = self.exp.write_buffering.get_write_strobe(field)
return wstrb
else:
# Unbuffered. Use decoder strobe directly
astrb = self.exp.dereferencer.get_access_strobe(field)
return f"{astrb} && decoded_req_is_wr"
if w_modifiable and r_modifiable:
# assert swmod on all sw actions
return strb
# assert swmod on both sw read and write
if buffer_writes:
astrb = self.exp.dereferencer.get_access_strobe(field)
wstrb = self.exp.write_buffering.get_write_strobe(field)
rstrb = f"{astrb} && !decoded_req_is_wr"
return f"{wstrb} || {rstrb}"
else:
# Unbuffered. Use decoder strobe directly
astrb = self.exp.dereferencer.get_access_strobe(field)
return astrb
if not w_modifiable and r_modifiable:
# assert swmod only on sw read
return f"{strb} && !decoded_req_is_wr"
astrb = self.exp.dereferencer.get_access_strobe(field)
return f"{astrb} && !decoded_req_is_wr"
# Not sw modifiable
return "1'b0"

View File

@@ -85,7 +85,7 @@ class FieldLogicGenerator(RDLForLoopGenerator):
super().__init__()
self.field_logic = field_logic
self.exp = field_logic.exp
self.field_storage_template = self.field_logic.exp.jj_env.get_template(
self.field_storage_template = self.exp.jj_env.get_template(
"field_logic/templates/field_storage.sv"
)
self.intr_fields = [] # type: List[FieldNode]

View File

@@ -15,6 +15,18 @@ class _OnWrite(NextStateConditional):
return field.is_sw_writable and field.get_property('onwrite') == self.onwritetype
def get_predicate(self, field: 'FieldNode') -> str:
if field.parent.get_property('buffer_writes'):
# Is buffered write. Use alternate strobe
wstrb = self.exp.write_buffering.get_write_strobe(field)
if field.get_property('swwe') or field.get_property('swwel'):
# dereferencer will wrap swwel complement if necessary
qualifier = self.exp.dereferencer.get_field_propref_value(field, 'swwe')
return f"{wstrb} && {qualifier}"
return wstrb
else:
# is regular register
strb = self.exp.dereferencer.get_access_strobe(field)
if field.get_property('swwe') or field.get_property('swwel'):
@@ -24,8 +36,22 @@ class _OnWrite(NextStateConditional):
return f"{strb} && decoded_req_is_wr"
def _wbus_bitslice(self, field: 'FieldNode', subword_idx: int) -> str:
def _wbus_bitslice(self, field: 'FieldNode', subword_idx: int = 0) -> str:
# Get the source bitslice range from the internal cpuif's data bus
if field.parent.get_property('buffer_writes'):
# register is buffered.
# write buffer is the full width of the register. no need to deal with subwords
high = field.high
low = field.low
if field.msb < field.lsb:
# slice is for an msb0 field.
# mirror it
regwidth = field.parent.get_property('regwidth')
low = regwidth - 1 - low
high = regwidth - 1 - high
low, high = high, low
else:
# Regular non-buffered register
# For normal fields this ends up passing-through the field's low/high
# values unchanged.
# For fields within a wide register (accesswidth < regwidth), low/high
@@ -51,23 +77,41 @@ class _OnWrite(NextStateConditional):
return f"[{high}:{low}]"
def _wr_data(self, field: 'FieldNode', subword_idx: int=0) -> str:
if field.parent.get_property('buffer_writes'):
# Is buffered. Use value from write buffer
# No need to check msb0 ordering. Bus is pre-swapped, and bitslice
# accounts for it
bslice = self._wbus_bitslice(field)
wbuf_prefix = self.exp.write_buffering.get_wbuf_prefix(field)
return wbuf_prefix + ".data" + bslice
else:
# Regular non-buffered register
bslice = self._wbus_bitslice(field, subword_idx)
if field.msb < field.lsb:
# Field gets bitswapped since it is in [low:high] orientation
value = f"decoded_wr_data_bswap{bslice}"
value = "decoded_wr_data_bswap" + bslice
else:
value = f"decoded_wr_data{bslice}"
value = "decoded_wr_data" + bslice
return value
def _wr_biten(self, field: 'FieldNode', subword_idx: int=0) -> str:
if field.parent.get_property('buffer_writes'):
# Is buffered. Use value from write buffer
# No need to check msb0 ordering. Bus is pre-swapped, and bitslice
# accounts for it
bslice = self._wbus_bitslice(field)
wbuf_prefix = self.exp.write_buffering.get_wbuf_prefix(field)
return wbuf_prefix + ".biten" + bslice
else:
# Regular non-buffered register
bslice = self._wbus_bitslice(field, subword_idx)
if field.msb < field.lsb:
# Field gets bitswapped since it is in [low:high] orientation
value = f"decoded_wr_biten_bswap{bslice}"
value = "decoded_wr_biten_bswap" + bslice
else:
value = f"decoded_wr_biten{bslice}"
value = "decoded_wr_biten" + bslice
return value
def get_assignments(self, field: 'FieldNode') -> List[str]:
@@ -92,6 +136,7 @@ class _OnWrite(NextStateConditional):
raise NotImplementedError
#-------------------------------------------------------------------------------
class WriteOneSet(_OnWrite):
comment = "SW write 1 set"
onwritetype = OnWriteType.woset

View File

@@ -108,6 +108,15 @@ module {{module_name}} (
assign cpuif_wr_ack = decoded_req & decoded_req_is_wr;
assign cpuif_wr_err = '0;
{%- if has_buffered_write_regs %}
//--------------------------------------------------------------------------
// Write double-buffers
//--------------------------------------------------------------------------
{{write_buffering.get_storage_struct()|indent}}
{{write_buffering.get_implementation()|indent}}
{%- endif %}
//--------------------------------------------------------------------------
// Field logic
//--------------------------------------------------------------------------
@@ -124,7 +133,6 @@ module {{module_name}} (
logic readback_done;
logic [{{cpuif.data_width-1}}:0] readback_data;
{{readback.get_implementation()|indent}}
{% if retime_read_response %}
always_ff {{get_always_ff_event(cpuif.reset)}} begin
if({{get_resetsignal(cpuif.reset)}}) begin
@@ -141,6 +149,5 @@ module {{module_name}} (
assign cpuif_rd_ack = readback_done;
assign cpuif_rd_data = readback_data;
assign cpuif_rd_err = readback_err;
{% endif %}
{%- endif %}
endmodule

View File

@@ -3,7 +3,9 @@
logic [{{cpuif.data_width-1}}:0] readback_array[{{array_size}}];
{{array_assignments}}
{% if do_fanin_stage %}
{%- if do_fanin_stage %}
// fanin stage
logic [{{cpuif.data_width-1}}:0] readback_array_c[{{fanin_array_size}}];
for(genvar g=0; g<{{fanin_loop_iter}}; g++) begin
@@ -48,6 +50,7 @@ always_comb begin
end
{%- else %}
// Reduce the array
always_comb begin
automatic logic [{{cpuif.data_width-1}}:0] readback_data_var;

View File

@@ -26,6 +26,8 @@ class DesignScanner(RDLListener):
self.out_of_hier_signals = OrderedDict() # type: OrderedDict[str, SignalNode]
self.has_writable_msb0_fields = False
self.has_buffered_write_regs = False
self.has_buffered_read_regs = False
def _get_out_of_hier_field_reset(self) -> None:
current_node = self.exp.top_node.parent
@@ -69,19 +71,7 @@ class DesignScanner(RDLListener):
if node.external and (node != self.exp.top_node):
# Do not inspect external components. None of my business
return WalkerAction.SkipDescendants
return None
def enter_Reg(self, node: 'RegNode') -> None:
# The CPUIF's bus width is sized according to the largest accesswidth in the design
accesswidth = node.get_property('accesswidth')
self.cpuif_data_width = max(self.cpuif_data_width, accesswidth)
def enter_Signal(self, node: 'SignalNode') -> None:
if node.get_property('field_reset'):
path = node.get_path()
self.in_hier_signal_paths.add(path)
def enter_Field(self, node: 'FieldNode') -> None:
# Collect any signals that are referenced by a property
for prop_name in node.list_properties():
value = node.get_property(prop_name)
@@ -93,5 +83,21 @@ class DesignScanner(RDLListener):
else:
self.in_hier_signal_paths.add(path)
return None
def enter_Reg(self, node: 'RegNode') -> None:
# The CPUIF's bus width is sized according to the largest accesswidth in the design
accesswidth = node.get_property('accesswidth')
self.cpuif_data_width = max(self.cpuif_data_width, accesswidth)
self.has_buffered_write_regs = self.has_buffered_write_regs or node.get_property('buffer_writes')
self.has_buffered_read_regs = self.has_buffered_read_regs or node.get_property('buffer_reads')
def enter_Signal(self, node: 'SignalNode') -> None:
if node.get_property('field_reset'):
path = node.get_path()
self.in_hier_signal_paths.add(path)
def enter_Field(self, node: 'FieldNode') -> None:
if node.is_sw_writable and (node.msb < node.lsb):
self.has_writable_msb0_fields = True

View File

@@ -3,7 +3,8 @@ from typing import Any
from systemrdl.udp import UDPDefinition
from systemrdl.component import Reg
from systemrdl.rdltypes.references import RefType, PropertyReference
from systemrdl.node import Node, RegNode, VectorNode
from systemrdl.rdltypes import NoValue
from systemrdl.node import Node, RegNode, VectorNode, SignalNode
class xBufferTrigger(UDPDefinition):
@@ -13,7 +14,12 @@ class xBufferTrigger(UDPDefinition):
def validate(self, node: Node, value: Any) -> None:
# TODO: Reference shall not cross an internal/external boundary
if isinstance(value, VectorNode):
if value is NoValue:
self.msg.error(
"Double-buffer trigger property is missing a value assignment",
self.get_src_ref(node)
)
elif isinstance(value, VectorNode):
# Trigger can reference a vector, but only if it is a single-bit
if value.width != 1:
self.msg.error(
@@ -24,6 +30,14 @@ class xBufferTrigger(UDPDefinition):
),
self.get_src_ref(node)
)
if isinstance(value, SignalNode):
if not value.get_property('activehigh') and not value.get_property('activelow'):
self.msg.error(
"Trigger was asigned a signal, but it does not specify whether it is activehigh/activelow",
self.get_src_ref(node)
)
elif isinstance(value, PropertyReference) and value.width is not None:
# Trigger can reference a property, but only if it is a single-bit
if value.width != 1:
@@ -54,7 +68,6 @@ class BufferWrites(UDPDefinition):
name = "buffer_writes"
valid_components = {Reg}
valid_type = bool
default_assignment = True
def validate(self, node: 'Node', value: Any) -> None:
assert isinstance(node, RegNode)
@@ -64,7 +77,6 @@ class BufferWrites(UDPDefinition):
"'buffer_writes' is set to true, but this register does not contain any writable fields.",
self.get_src_ref(node)
)
# TODO: Should I limit the use of other properties on double-buffered registers?
def get_unassigned_default(self, node: 'Node') -> Any:
return False
@@ -84,7 +96,6 @@ class BufferReads(UDPDefinition):
name = "buffer_reads"
valid_components = {Reg}
valid_type = bool
default_assignment = True
def validate(self, node: 'Node', value: Any) -> None:
assert isinstance(node, RegNode)
@@ -95,8 +106,6 @@ class BufferReads(UDPDefinition):
self.get_src_ref(node)
)
# TODO: Should I limit the use of other properties on double-buffered registers?
def get_unassigned_default(self, node: 'Node') -> Any:
return False

View File

@@ -77,23 +77,30 @@ class DesignValidator(RDLListener):
def enter_Field(self, node: 'FieldNode') -> None:
# 10.6.1-f: Any field that is software-writable or clear on read shall
# not span multiple software accessible sub-words (e.g., a 64-bit
# register with a 32-bit access width may not have a writable field with
# bits in both the upper and lower half of the register).
#
# Interpreting this further - this rule applies any time a field is
# software-modifiable by any means, including rclr, rset, ruser
# TODO: suppress this check for registers that have the appropriate
# buffer_writes/buffer_reads UDP set
parent_accesswidth = node.parent.get_property('accesswidth')
parent_regwidth = node.parent.get_property('regwidth')
if ((parent_accesswidth < parent_regwidth)
if (
(parent_accesswidth < parent_regwidth)
and (node.lsb // parent_accesswidth) != (node.msb // parent_accesswidth)
and (node.is_sw_writable or node.get_property('onread') is not None)):
# Field spans across sub-words
):
# field spans multiple sub-words
if node.is_sw_writable and not node.parent.get_property('buffer_writes'):
# ... and is writable without the protection of double-buffering
# Enforce 10.6.1-f
self.msg.error(
f"Software-modifiable field '{node.inst_name}' shall not span "
"multiple software-accessible subwords.",
f"Software-writable field '{node.inst_name}' shall not span"
" multiple software-accessible subwords. Consider enabling"
" write double-buffering.",
node.inst.inst_src_ref
)
if node.get_property('onread') is not None and not node.parent.get_property('buffer_reads'):
# ... is modified by an onread action without the atomicity of read buffering
# Enforce 10.6.1-f
self.msg.error(
f"The field '{node.inst_name}' spans multiple software-accessible"
" subwords and is modified on-read, making it impossible to"
" access its value correctly. Consider enabling read"
" double-buffering.",
node.inst.inst_src_ref
)

View File

@@ -0,0 +1,80 @@
from typing import TYPE_CHECKING, Union
from systemrdl.node import AddrmapNode, RegNode, FieldNode, SignalNode
from .storage_generator import WBufStorageStructGenerator
from .implementation_generator import WBufLogicGenerator
from ..utils import get_indexed_path
if TYPE_CHECKING:
from ..exporter import RegblockExporter
class WriteBuffering:
def __init__(self, exp:'RegblockExporter'):
self.exp = exp
@property
def top_node(self) -> 'AddrmapNode':
return self.exp.top_node
def get_storage_struct(self) -> str:
struct_gen = WBufStorageStructGenerator(self)
s = struct_gen.get_struct(self.top_node, "wbuf_storage_t")
assert s is not None
return s + "\nwbuf_storage_t wbuf_storage;"
def get_implementation(self) -> str:
gen = WBufLogicGenerator(self)
s = gen.get_content(self.top_node)
assert s is not None
return s
def get_wbuf_prefix(self, node: Union[RegNode, FieldNode]) -> str:
if isinstance(node, FieldNode):
node = node.parent
wbuf_prefix = "wbuf_storage." + get_indexed_path(self.top_node, node)
return wbuf_prefix
def get_write_strobe(self, node: Union[RegNode, FieldNode]) -> str:
prefix = self.get_wbuf_prefix(node)
return f"{prefix}.pending && {self.get_trigger(node)}"
def get_raw_trigger(self, node: 'RegNode') -> str:
trigger = node.get_property('wbuffer_trigger')
if isinstance(trigger, RegNode):
# Trigger is a register.
# trigger when uppermost address of the register is written
regwidth = node.get_property('regwidth')
accesswidth = node.get_property('accesswidth')
strb_prefix = self.exp.dereferencer.get_access_strobe(trigger, reduce_substrobes=False)
if accesswidth < regwidth:
n_subwords = regwidth // accesswidth
return f"{strb_prefix}[{n_subwords-1}] && decoded_req_is_wr"
else:
return f"{strb_prefix} && decoded_req_is_wr"
elif isinstance(trigger, SignalNode):
s = self.exp.dereferencer.get_value(trigger)
if trigger.get_property('activehigh'):
return s
else:
return f"~{s}"
else:
# Trigger is a field or propref bit
return self.exp.dereferencer.get_value(trigger)
def get_trigger(self, node: Union[RegNode, FieldNode]) -> str:
if isinstance(node, FieldNode):
node = node.parent
trigger = node.get_property('wbuffer_trigger')
if isinstance(trigger, RegNode) and trigger == node:
# register is its own trigger
# use the delayed trigger signal
return self.get_wbuf_prefix(node) + ".trigger_q"
else:
return self.get_raw_trigger(node)

View File

@@ -0,0 +1,60 @@
from typing import TYPE_CHECKING
from collections import namedtuple
from systemrdl.component import Reg
from systemrdl.node import RegNode
from ..forloop_generator import RDLForLoopGenerator
from ..utils import get_always_ff_event
if TYPE_CHECKING:
from . import WriteBuffering
class WBufLogicGenerator(RDLForLoopGenerator):
i_type = "genvar"
def __init__(self, wbuf: 'WriteBuffering') -> None:
super().__init__()
self.wbuf = wbuf
self.exp = wbuf.exp
self.template = self.exp.jj_env.get_template(
"write_buffering/template.sv"
)
def enter_Reg(self, node: 'RegNode') -> None:
super().enter_Reg(node)
assert isinstance(node.inst, Reg)
if not node.get_property('buffer_writes'):
return
regwidth = node.get_property('regwidth')
accesswidth = node.get_property('accesswidth')
strb_prefix = self.exp.dereferencer.get_access_strobe(node, reduce_substrobes=False)
Segment = namedtuple("Segment", ["strobe", "bslice"])
segments = []
if accesswidth < regwidth:
n_subwords = regwidth // accesswidth
for i in range(n_subwords):
strobe = strb_prefix + f"[{i}]"
if node.inst.is_msb0_order:
bslice = f"[{regwidth - (accesswidth * i) - 1}: {regwidth - (accesswidth * (i+1))}]"
else:
bslice = f"[{(accesswidth * (i + 1)) - 1}:{accesswidth * i}]"
segments.append(Segment(strobe, bslice))
else:
segments.append(Segment(strb_prefix, ""))
trigger = node.get_property('wbuffer_trigger')
is_own_trigger = (isinstance(trigger, RegNode) and trigger == node)
context = {
'wbuf': self.wbuf,
'wbuf_prefix': self.wbuf.get_wbuf_prefix(node),
'segments': segments,
'node': node,
'cpuif': self.exp.cpuif,
'get_resetsignal': self.exp.dereferencer.get_resetsignal,
'get_always_ff_event': lambda resetsignal : get_always_ff_event(self.exp.dereferencer, resetsignal),
'is_own_trigger': is_own_trigger,
}
self.add_content(self.template.render(context))

View File

@@ -0,0 +1,32 @@
from typing import TYPE_CHECKING
from systemrdl.node import FieldNode, RegNode
from ..struct_generator import RDLStructGenerator
if TYPE_CHECKING:
from . import WriteBuffering
class WBufStorageStructGenerator(RDLStructGenerator):
def __init__(self, wbuf: 'WriteBuffering') -> None:
super().__init__()
self.wbuf = wbuf
def enter_Field(self, node: FieldNode) -> None:
# suppress parent class's field behavior
pass
def enter_Reg(self, node: RegNode) -> None:
super().enter_Reg(node)
if not node.get_property('buffer_writes'):
return
regwidth = node.get_property('regwidth')
self.add_member("data", regwidth)
self.add_member("biten", regwidth)
self.add_member("pending")
trigger = node.get_property('wbuffer_trigger')
if isinstance(trigger, RegNode) and trigger == node:
self.add_member("trigger_q")

View File

@@ -0,0 +1,31 @@
always_ff {{get_always_ff_event(cpuif.reset)}} begin
if({{get_resetsignal(cpuif.reset)}}) begin
{{wbuf_prefix}}.pending <= '0;
{{wbuf_prefix}}.data <= '0;
{{wbuf_prefix}}.biten <= '0;
{%- if is_own_trigger %}
{{wbuf_prefix}}.trigger_q <= '0;
{%- endif %}
end else begin
{%- for segment in segments %}
if({{segment.strobe}} && decoded_req_is_wr) begin
{{wbuf_prefix}}.pending <= '1;
{%- if node.inst.is_msb0_order %}
{{wbuf_prefix}}.data{{segment.bslice}} <= decoded_wr_data_bswap;
{{wbuf_prefix}}.biten{{segment.bslice}} <= decoded_wr_biten_bswap;
{%- else %}
{{wbuf_prefix}}.data{{segment.bslice}} <= decoded_wr_data;
{{wbuf_prefix}}.biten{{segment.bslice}} <= decoded_wr_biten;
{%- endif %}
end
{%- endfor %}
{% if is_own_trigger %}
{{wbuf_prefix}}.trigger_q <= {{wbuf.get_raw_trigger(node)}};
{%- endif %}
if({{wbuf.get_trigger(node)}}) begin
{{wbuf_prefix}}.pending <= '0;
{{wbuf_prefix}}.data <= '0;
{{wbuf_prefix}}.biten <= '0;
end
end
end

View File

@@ -2,6 +2,10 @@
branch = True
#relative_files = True
omit =
# to be covered elsewhere
*/__peakrdl__.py
[paths]
source =
../src/peakrdl_regblock/

View File

@@ -86,8 +86,13 @@ class BaseTestCase(unittest.TestCase):
rdl_file = glob.glob(os.path.join(this_dir, "*.rdl"))[0]
rdlc = RDLCompiler()
# Load the UDPs
for udp in ALL_UDPS:
rdlc.register_udp(udp)
# ... including the definition
udp_file = os.path.join(this_dir, "../../hdl-src/regblock_udps.rdl")
rdlc.compile_file(udp_file)
rdlc.compile_file(rdl_file)
root = rdlc.elaborate(cls.rdl_elab_target, "regblock", cls.rdl_elab_params)

View File

View File

@@ -0,0 +1,106 @@
addrmap top {
default regwidth = 16;
default accesswidth = 16;
default sw=rw;
default hw=r;
//--------------------------------------------------------------------------
// Wide registers
//--------------------------------------------------------------------------
reg {
regwidth = 64;
buffer_writes = true;
field {} f1[63:0] = 0;
} reg1;
reg {
regwidth = 64;
buffer_writes = true;
field {} f1[0:63] = 0;
} reg1_msb0;
reg {
regwidth = 32;
buffer_writes = true;
field {} f1[19:8] = 0;
field {} f2[23:20] = 0;
} reg2;
reg {
regwidth = 32;
buffer_writes = true;
field {} f1[8:19] = 0;
field {} f2[20:23] = 0;
} reg2_msb0;
//--------------------------------------------------------------------------
// Alternate Triggers
//--------------------------------------------------------------------------
reg myreg {
buffer_writes;
field {} f1[15:0] = 0;
};
// Trigger via another register
myreg g1_r1;
myreg g1_r2;
g1_r1->buffer_writes = false;
g1_r2->wbuffer_trigger = g1_r1;
// triger from signal
signal {
activehigh;
} trigger_sig;
signal {
activelow;
} trigger_sig_n;
myreg g2_r1;
myreg g2_r2;
g2_r1->wbuffer_trigger = trigger_sig;
g2_r2->wbuffer_trigger = trigger_sig_n;
// trigger from field
myreg g3_r1;
reg {
field {
sw=w; hw=r; singlepulse;
} trig = 0;
} g3_trig;
g3_r1->wbuffer_trigger = g3_trig.trig;
// trigger from propref
myreg g4_r1;
reg {
field {
hw=na;
} trig_vec[3:0] = 0;
} g4_trig;
g4_r1->wbuffer_trigger = g4_trig.trig_vec->anded;
//--------------------------------------------------------------------------
// swmod behavior
//--------------------------------------------------------------------------
myreg g5_r1;
g5_r1->wbuffer_trigger = trigger_sig;
reg {
field{
sw=rw;
hw=na;
counter;
} c[3:0] = 0;
} g5_modcount;
g5_modcount.c->incr = g5_r1.f1->swmod;
myreg g6_r1;
g6_r1.f1->rclr;
g6_r1->wbuffer_trigger = trigger_sig;
reg {
field{
sw=rw;
hw=na;
counter;
} c[3:0] = 0;
} g6_modcount;
g6_modcount.c->incr = g6_r1.f1->swmod;
};

View File

@@ -0,0 +1,220 @@
{% extends "lib/tb_base.sv" %}
{% block seq %}
{% sv_line_anchor %}
cb.hwif_in.trigger_sig_n <= '1;
##1;
cb.rst <= '0;
##1;
//--------------------------------------------------------------------------
// Wide registers
//--------------------------------------------------------------------------
// reg1
cpuif.assert_read('h0, 'h0);
cpuif.assert_read('h2, 'h0);
cpuif.assert_read('h4, 'h0);
cpuif.assert_read('h6, 'h0);
assert(cb.hwif_out.reg1.f1.value == 0);
cpuif.write('h0, 'h1234);
cpuif.assert_read('h0, 'h0);
cpuif.assert_read('h2, 'h0);
cpuif.assert_read('h4, 'h0);
cpuif.assert_read('h6, 'h0);
assert(cb.hwif_out.reg1.f1.value == 0);
cpuif.write('h2, 'h5678);
cpuif.assert_read('h0, 'h0);
cpuif.assert_read('h2, 'h0);
cpuif.assert_read('h4, 'h0);
cpuif.assert_read('h6, 'h0);
assert(cb.hwif_out.reg1.f1.value == 0);
cpuif.write('h4, 'h9ABC);
cpuif.assert_read('h0, 'h0);
cpuif.assert_read('h2, 'h0);
cpuif.assert_read('h4, 'h0);
cpuif.assert_read('h6, 'h0);
assert(cb.hwif_out.reg1.f1.value == 0);
cpuif.write('h6, 'hDEF1);
@cb; @cb;
assert(cb.hwif_out.reg1.f1.value == 64'hDEF19ABC56781234);
cpuif.assert_read('h0, 'h1234);
cpuif.assert_read('h2, 'h5678);
cpuif.assert_read('h4, 'h9ABC);
cpuif.assert_read('h6, 'hDEF1);
// reg1_msb0
cpuif.assert_read('h8, 'h0);
cpuif.assert_read('hA, 'h0);
cpuif.assert_read('hC, 'h0);
cpuif.assert_read('hE, 'h0);
assert(cb.hwif_out.reg1_msb0.f1.value == 0);
cpuif.write('h8, 'h1234);
cpuif.assert_read('h8, 'h0);
cpuif.assert_read('hA, 'h0);
cpuif.assert_read('hC, 'h0);
cpuif.assert_read('hE, 'h0);
assert(cb.hwif_out.reg1_msb0.f1.value == 0);
cpuif.write('hA, 'h5678);
cpuif.assert_read('h8, 'h0);
cpuif.assert_read('hA, 'h0);
cpuif.assert_read('hC, 'h0);
cpuif.assert_read('hE, 'h0);
assert(cb.hwif_out.reg1_msb0.f1.value == 0);
cpuif.write('hC, 'h9ABC);
cpuif.assert_read('h8, 'h0);
cpuif.assert_read('hA, 'h0);
cpuif.assert_read('hC, 'h0);
cpuif.assert_read('hE, 'h0);
assert(cb.hwif_out.reg1_msb0.f1.value == 0);
cpuif.write('hE, 'hDEF1);
@cb; @cb;
assert({<<{cb.hwif_out.reg1_msb0.f1.value}} == 64'hDEF19ABC56781234);
cpuif.assert_read('h8, 'h1234);
cpuif.assert_read('hA, 'h5678);
cpuif.assert_read('hC, 'h9ABC);
cpuif.assert_read('hE, 'hDEF1);
// reg2
cpuif.assert_read('h10, 'h0);
cpuif.assert_read('h12, 'h0);
assert(cb.hwif_out.reg2.f1.value == 0);
assert(cb.hwif_out.reg2.f2.value == 0);
cpuif.write('h10, 'h34AA);
cpuif.assert_read('h10, 'h0);
cpuif.assert_read('h12, 'h0);
assert(cb.hwif_out.reg2.f1.value == 0);
assert(cb.hwif_out.reg2.f2.value == 0);
cpuif.write('h12, 'hAA12);
@cb; @cb;
assert(cb.hwif_out.reg2.f1.value == 12'h234);
assert(cb.hwif_out.reg2.f2.value == 4'h1);
cpuif.assert_read('h10, 'h3400);
cpuif.assert_read('h12, 'h0012);
// reg2_msb0
cpuif.assert_read('h14, 'h0);
cpuif.assert_read('h16, 'h0);
assert(cb.hwif_out.reg2_msb0.f1.value == 0);
assert(cb.hwif_out.reg2_msb0.f2.value == 0);
cpuif.write('h14, 'h34AA);
cpuif.assert_read('h14, 'h0);
cpuif.assert_read('h16, 'h0);
assert(cb.hwif_out.reg2_msb0.f1.value == 0);
assert(cb.hwif_out.reg2_msb0.f2.value == 0);
cpuif.write('h16, 'hAA12);
@cb; @cb;
assert({<<{cb.hwif_out.reg2_msb0.f1.value}} == 12'h234);
assert({<<{cb.hwif_out.reg2_msb0.f2.value}} == 4'h1);
cpuif.assert_read('h14, 'h3400);
cpuif.assert_read('h16, 'h0012);
//--------------------------------------------------------------------------
// Alternate Triggers
//--------------------------------------------------------------------------
// g1
cpuif.assert_read('h18, 'h0);
cpuif.assert_read('h1A, 'h0);
assert(cb.hwif_out.g1_r1.f1.value == 0);
assert(cb.hwif_out.g1_r2.f1.value == 0);
cpuif.write('h1A, 'h1234);
cpuif.assert_read('h18, 'h0);
cpuif.assert_read('h1A, 'h0);
assert(cb.hwif_out.g1_r1.f1.value == 0);
assert(cb.hwif_out.g1_r2.f1.value == 0);
cpuif.write('h18, 'hABCD);
@cb;
assert(cb.hwif_out.g1_r1.f1.value == 'hABCD);
assert(cb.hwif_out.g1_r2.f1.value == 'h1234);
// g2
cpuif.assert_read('h1C, 'h0);
cpuif.assert_read('h1E, 'h0);
assert(cb.hwif_out.g2_r1.f1.value == 0);
assert(cb.hwif_out.g2_r2.f1.value == 0);
cpuif.write('h1C, 'h5678);
cpuif.write('h1E, 'h9876);
cpuif.assert_read('h1C, 'h0);
cpuif.assert_read('h1E, 'h0);
assert(cb.hwif_out.g2_r1.f1.value == 0);
assert(cb.hwif_out.g2_r2.f1.value == 0);
cb.hwif_in.trigger_sig <= '1;
cb.hwif_in.trigger_sig_n <= '0;
@cb;
cb.hwif_in.trigger_sig <= '0;
cb.hwif_in.trigger_sig_n <= '1;
@cb;
assert(cb.hwif_out.g2_r1.f1.value == 'h5678);
assert(cb.hwif_out.g2_r2.f1.value == 'h9876);
// g3
cpuif.assert_read('h20, 'h0);
assert(cb.hwif_out.g3_r1.f1.value == 0);
cpuif.write('h20, 'hFEDC);
@cb; @cb;
assert(cb.hwif_out.g3_r1.f1.value == 0);
cpuif.assert_read('h20, 'h0);
cpuif.write('h22, 'h0000);
@cb; @cb;
assert(cb.hwif_out.g3_r1.f1.value == 0);
cpuif.assert_read('h20, 'h0);
cpuif.write('h22, 'h0001);
@cb; @cb;
assert(cb.hwif_out.g3_r1.f1.value == 'hFEDC);
cpuif.assert_read('h20, 'hFEDC);
// g4
cpuif.assert_read('h24, 'h0);
assert(cb.hwif_out.g4_r1.f1.value == 0);
cpuif.write('h24, 'hCAFE);
@cb; @cb;
assert(cb.hwif_out.g4_r1.f1.value == 0);
cpuif.assert_read('h24, 'h0);
cpuif.write('h26, 'h0000);
@cb; @cb;
assert(cb.hwif_out.g4_r1.f1.value == 0);
cpuif.assert_read('h24, 'h0);
cpuif.write('h26, 'h000E);
@cb; @cb;
assert(cb.hwif_out.g4_r1.f1.value == 0);
cpuif.assert_read('h24, 'h0);
cpuif.write('h26, 'h000F);
@cb; @cb;
assert(cb.hwif_out.g4_r1.f1.value == 'hCAFE);
cpuif.assert_read('h24, 'hCAFE);
//--------------------------------------------------------------------------
// swmod behavior
//--------------------------------------------------------------------------
// g5
cpuif.assert_read('h28, 'h0);
cpuif.assert_read('h2A, 'h0);
cpuif.write('h28, 'h1234);
cpuif.write('h28, 'h5678);
cpuif.assert_read('h28, 'h0);
cpuif.assert_read('h2A, 'h0);
cb.hwif_in.trigger_sig <= '1;
@cb;
cb.hwif_in.trigger_sig <= '0;
cpuif.assert_read('h28, 'h5678);
cpuif.assert_read('h2A, 'h1);
// g6
cpuif.assert_read('h2E, 'h0);
cpuif.assert_read('h2C, 'h0);
cpuif.assert_read('h2E, 'h1);
cpuif.write('h2C, 'h5678);
cpuif.write('h2C, 'h1234);
cpuif.assert_read('h2E, 'h1);
cpuif.assert_read('h2C, 'h0);
cpuif.assert_read('h2E, 'h2);
cb.hwif_in.trigger_sig <= '1;
@cb;
cb.hwif_in.trigger_sig <= '0;
cpuif.assert_read('h2E, 'h3);
cpuif.assert_read('h2C, 'h1234);
cpuif.assert_read('h2E, 'h4);
{% endblock %}

View File

@@ -0,0 +1,5 @@
from ..lib.sim_testcase import SimTestCase
class Test(SimTestCase):
def test_dut(self):
self.run_test()