Implement read buffering. (#22)

This commit is contained in:
Alex Mykyta
2022-11-06 23:28:07 -08:00
parent 279a3c5788
commit 9e76a712a7
19 changed files with 813 additions and 77 deletions

View File

@@ -121,4 +121,5 @@ Links
:caption: Extended Properties
udps/intro
udps/read_buffering
udps/write_buffering

View File

@@ -8,6 +8,8 @@ the language to be extended using "User Defined Properties" (UDPs). The
PeakRDL-regblock tool understands several UDPs that are described in this
section.
To enable these UDPs, compile this RDL file prior to the rest of your design:
:download:`regblock_udps.rdl <../../hdl-src/regblock_udps.rdl>`.
.. list-table:: Summary of UDPs
:header-rows: 1
@@ -17,6 +19,20 @@ section.
- Type
- Description
* - buffer_reads
- reg
- boolean
- If set, reads from the register are double-buffered.
See details here: :ref:`read_buffering`.
* - rbuffer_trigger
- reg
- reference
- Defines the buffered read load trigger.
See details here: :ref:`read_buffering`.
* - buffer_writes
- reg
- boolean

View File

@@ -0,0 +1,158 @@
.. _read_buffering:
Read-buffered Registers
=======================
Read buffering is a mechanism that allows for software accesses to read a
snapshot of one or more registers atomically. When enabled on a register, a
read buffer will latch the state of its fields when triggered such that software
can read a coherent snapshot of one or more registers' value.
Some examples of when this is useful:
* A wide 64-bit status register needs to be read atomically, but the CPU
interface is only 32-bits.
* Software needs to be able to read the state of multiple registers
atomically.
* A hardware event latches the software-visible state of one or more
registers.
.. figure:: ../diagrams/rbuf.png
Properties
----------
The behavior of read-buffered registers is defined using the following two
properties:
.. literalinclude:: ../../hdl-src/regblock_udps.rdl
:lines: 10-18
``buffer_reads``
* Assigned value is a boolean.
* If true, enables double-buffering of software reads of this register.
* The read buffer will load the register's field values when it's trigger
event is asserted.
* Unless specified otherwise, the buffer trigger occurs when the lowest
address of the buffered register is read.
* When read by software the data returned is from the buffer contents, not
directly from the register's fields.
``rbuffer_trigger``
* Assigned value is a reference to a register, single-bit field, signal, or
single-bit property.
* Controls when the double-buffer loads the register's field vaues into the
buffer storage element.
* 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 loaded when the register's
lowest address is read.
Other Rules
^^^^^^^^^^^
* It is an error to set ``buffer_reads`` if the register does not contain any
readable fields
* If ``buffer_reads`` is false, then anything assigned to ``rbuffer_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 ``rbuffer_trigger`` shall
represent a single bit.
* The software read operation considered to take place when the buffer is loaded
This influences the behavior of properties like ``swmod`` and ``swacc`` -
they are not asserted until the register's fields are actually sampled by the
buffer.
* If a read-buffered register is wide (accesswidth < regwidth) and is its own
trigger, the first sub-word's buffer is bypassed to ensure the first read
operation is atomically coherent with the rest of the sampled register.
Examples
--------
Below are several examples of what you can do with registers that are
read-buffered.
Wide Atomic Register
^^^^^^^^^^^^^^^^^^^^
In this example, a wide 64-bit read-clear counter is implemented.
Without read-buffering, it is impossible to coherently read the state of the
counter using a 32-bit CPU interface without risking a discontinuity. With
read-buffering enabled, the read of the lower half of the register will trigger
the upper half's value to be latched. A subsequent software access can then
coherently read the rest of the register's buffered value.
.. code-block:: systemrdl
:emphasize-lines: 4
reg {
regwidth = 64;
accesswidth = 32;
buffer_reads = true;
field {
sw=r; hw=na;
counter;
incr;
} my_counter[63:0] = 0;
};
Atomic Group of Registers
^^^^^^^^^^^^^^^^^^^^^^^^^
Perhaps you have a group of registers that monitor some rapidly-changing state
within your design. Using the ``rbuffer_trigger`` property, you can define which
reagister read operation triggers the buffered registers' values to be latched.
.. code-block:: systemrdl
:emphasize-lines: 11-14
reg my_status_reg {
field {
sw=r; hw=w;
} value[31:0];
};
my_status_reg status1;
my_status_reg status2;
my_status_reg status3;
status2->buffer_reads = true;
status2->rbuffer_trigger = status1;
status3->buffer_reads = true;
status3->rbuffer_trigger = status1;
In this example, when software reads status1, this triggers status2-status3
registers to latch their values into their respective read buffers. Subsequent
reads to status2 and status3 return the value that these registers contained at
the moment that status1 was read. This makes it possible for software to read
the state of multiple registers atomically.
Externally Triggered Register Sampling
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If needed, an external trigger can be used to load a read buffer.
This can be useful if precise timing of software's view of the register state is
required.
.. code-block:: systemrdl
:emphasize-lines: 14-15
reg my_status_reg {
buffer_reads = true;
field {
sw=r; hw=w;
} value[31:0];
};
my_status_reg status1;
my_status_reg status2;
signal {
activehigh;
} trigger_signal;
status1->rbuffer_trigger = trigger_signal;
status2->rbuffer_trigger = trigger_signal;
When ``hwif_in..trigger_signal`` is asserted, the state of registers ``status1``
and ``status2`` is buffered.

View File

@@ -3,20 +3,18 @@
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.
In order to support larger software write accesses that are atomic, the
regblock generator understands several UDPs that implement write-buffering to
specific registers. This causes 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.
* 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
@@ -32,43 +30,50 @@ Properties
The behavior of write-buffered registers is defined using the following two
properties:
.. literalinclude:: ../../hdl-src/regblock_udps.rdl
:lines: 20-28
``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.
* 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
* 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.
* 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.
@@ -105,8 +110,8 @@ 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.
atomically. Using the ``wbuffer_trigger`` property, you can define which
register write operation triggers the group to be updated.
.. code-block:: systemrdl
@@ -136,17 +141,18 @@ write operation triggers the group to be updated.
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.
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.
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.
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.
@@ -169,5 +175,5 @@ trigger is asserted.
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.
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.

View File

@@ -7,6 +7,16 @@
* For more details, see: https://peakrdl-regblock.readthedocs.io/en/latest/udps/intro.html
*/
property buffer_reads {
component = reg;
type = boolean;
};
property rbuffer_trigger {
component = reg;
type = ref;
};
property buffer_writes {
component = reg;
type = boolean;

View File

@@ -17,6 +17,7 @@ from .cpuif import CpuifBase
from .cpuif.apb4 import APB4_Cpuif
from .hwif import Hwif
from .write_buffering import WriteBuffering
from .read_buffering import ReadBuffering
class RegblockExporter:
def __init__(self, **kwargs: Any) -> None:
@@ -31,7 +32,8 @@ class RegblockExporter:
self.address_decode = AddressDecode(self)
self.field_logic = FieldLogic(self)
self.readback = None # type: Readback
self.write_buffering = None # type: WriteBuffering
self.write_buffering = WriteBuffering(self)
self.read_buffering = ReadBuffering(self)
self.dereferencer = Dereferencer(self)
self.min_read_latency = 0
self.min_write_latency = 0
@@ -145,7 +147,6 @@ class RegblockExporter:
self,
retime_read_fanin
)
self.write_buffering = WriteBuffering(self)
# Validate that there are no unsupported constructs
validator = DesignValidator(self)
@@ -161,6 +162,7 @@ class RegblockExporter:
"cpuif": self.cpuif,
"hwif": self.hwif,
"write_buffering": self.write_buffering,
"read_buffering": self.read_buffering,
"get_resetsignal": self.dereferencer.get_resetsignal,
"address_decode": self.address_decode,
"field_logic": self.field_logic,

View File

@@ -175,8 +175,13 @@ class FieldLogic:
"""
Asserted when field is software accessed (read)
"""
strb = self.exp.dereferencer.get_access_strobe(field)
return f"{strb} && !decoded_req_is_wr"
buffer_reads = field.parent.get_property('buffer_reads')
if buffer_reads:
rstrb = self.exp.read_buffering.get_trigger(field.parent)
return rstrb
else:
strb = self.exp.dereferencer.get_access_strobe(field)
return f"{strb} && !decoded_req_is_wr"
def get_swmod_identifier(self, field: 'FieldNode') -> str:
"""
@@ -186,6 +191,7 @@ class FieldLogic:
w_modifiable = field.is_sw_writable
r_modifiable = (field.get_property('onread') is not None)
buffer_writes = field.parent.get_property('buffer_writes')
buffer_reads = field.parent.get_property('buffer_reads')
if w_modifiable and not r_modifiable:
# assert swmod only on sw write
@@ -200,10 +206,18 @@ class FieldLogic:
if w_modifiable and r_modifiable:
# 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"
astrb = self.exp.dereferencer.get_access_strobe(field)
if buffer_writes or buffer_reads:
if buffer_reads:
rstrb = self.exp.read_buffering.get_trigger(field.parent)
else:
rstrb = f"{astrb} && !decoded_req_is_wr"
if buffer_writes:
wstrb = self.exp.write_buffering.get_write_strobe(field)
else:
wstrb = f"{astrb} && decoded_req_is_wr"
return f"{wstrb} || {rstrb}"
else:
# Unbuffered. Use decoder strobe directly
@@ -213,7 +227,11 @@ class FieldLogic:
if not w_modifiable and r_modifiable:
# assert swmod only on sw read
astrb = self.exp.dereferencer.get_access_strobe(field)
return f"{astrb} && !decoded_req_is_wr"
if buffer_reads:
rstrb = self.exp.read_buffering.get_trigger(field.parent)
else:
rstrb = f"{astrb} && !decoded_req_is_wr"
return rstrb
# Not sw modifiable
return "1'b0"

View File

@@ -13,8 +13,14 @@ class _OnRead(NextStateConditional):
return field.get_property('onread') == self.onreadtype
def get_predicate(self, field: 'FieldNode') -> str:
strb = self.exp.dereferencer.get_access_strobe(field)
return f"{strb} && !decoded_req_is_wr"
if field.parent.get_property('buffer_reads'):
# Is buffered read. Use alternate strobe
rstrb = self.exp.read_buffering.get_trigger(field.parent)
return rstrb
else:
# is regular register
strb = self.exp.dereferencer.get_access_strobe(field)
return f"{strb} && !decoded_req_is_wr"
class ClearOnRead(_OnRead):

View File

@@ -109,6 +109,7 @@ module {{module_name}} (
assign cpuif_wr_err = '0;
{%- if has_buffered_write_regs %}
//--------------------------------------------------------------------------
// Write double-buffers
//--------------------------------------------------------------------------
@@ -116,7 +117,6 @@ module {{module_name}} (
{{write_buffering.get_implementation()|indent}}
{%- endif %}
//--------------------------------------------------------------------------
// Field logic
//--------------------------------------------------------------------------
@@ -126,6 +126,15 @@ module {{module_name}} (
{{field_logic.get_implementation()|indent}}
{%- if has_buffered_read_regs %}
//--------------------------------------------------------------------------
// Read double-buffers
//--------------------------------------------------------------------------
{{read_buffering.get_storage_struct()|indent}}
{{read_buffering.get_implementation()|indent}}
{%- endif %}
//--------------------------------------------------------------------------
// Readback
//--------------------------------------------------------------------------

View File

@@ -0,0 +1,58 @@
from typing import TYPE_CHECKING
from systemrdl.node import AddrmapNode, RegNode, FieldNode, SignalNode
from .storage_generator import RBufStorageStructGenerator
from .implementation_generator import RBufLogicGenerator
from ..utils import get_indexed_path
if TYPE_CHECKING:
from ..exporter import RegblockExporter
class ReadBuffering:
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 = RBufStorageStructGenerator()
s = struct_gen.get_struct(self.top_node, "rbuf_storage_t")
assert s is not None
return s + "\nrbuf_storage_t rbuf_storage;"
def get_implementation(self) -> str:
gen = RBufLogicGenerator(self)
s = gen.get_content(self.top_node)
assert s is not None
return s
def get_trigger(self, node: RegNode) -> str:
trigger = node.get_property('rbuffer_trigger')
if isinstance(trigger, RegNode):
# Trigger is a register.
# trigger when lowermost 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:
return f"{strb_prefix}[0] && !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_rbuf_data(self, node: RegNode) -> str:
return "rbuf_storage." + get_indexed_path(self.top_node, node) + ".data"

View File

@@ -0,0 +1,59 @@
from typing import TYPE_CHECKING
from systemrdl.component import Reg
from systemrdl.node import RegNode
from ..forloop_generator import RDLForLoopGenerator
if TYPE_CHECKING:
from . import ReadBuffering
class RBufLogicGenerator(RDLForLoopGenerator):
i_type = "genvar"
def __init__(self, rbuf: 'ReadBuffering') -> None:
super().__init__()
self.rbuf = rbuf
self.exp = rbuf.exp
self.template = self.exp.jj_env.get_template(
"read_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_reads'):
return
context = {
'node': node,
'rbuf': self.rbuf,
'get_assignments': self.get_assignments,
}
self.add_content(self.template.render(context))
def get_assignments(self, node: RegNode) -> str:
data = self.rbuf.get_rbuf_data(node)
bidx = 0
s = []
for field in node.fields():
if bidx < field.low:
# zero padding before field
s.append(f"{data}[{field.low-1}:{bidx}] = '0;")
value = self.exp.dereferencer.get_value(field)
if field.msb < field.lsb:
# Field gets bitswapped since it is in [low:high] orientation
value = f"{{<<{{{value}}}}}"
s.append(f"{data}[{field.high}:{field.low}] = {value};")
bidx = field.high + 1
regwidth = node.get_property('regwidth')
if bidx < regwidth:
# zero padding after last field
s.append(f"{data}[{regwidth-1}:{bidx}] = '0;")
return "\n".join(s)

View File

@@ -0,0 +1,18 @@
from systemrdl.node import FieldNode, RegNode
from ..struct_generator import RDLStructGenerator
class RBufStorageStructGenerator(RDLStructGenerator):
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_reads'):
return
regwidth = node.get_property('regwidth')
self.add_member("data", regwidth)

View File

@@ -0,0 +1,5 @@
always_ff @(posedge clk) begin
if({{rbuf.get_trigger(node)}}) begin
{{get_assignments(node)|indent(8)}}
end
end

View File

@@ -1,10 +1,11 @@
from typing import TYPE_CHECKING, List
from systemrdl.node import RegNode
from ..forloop_generator import RDLForLoopGenerator, LoopBody
if TYPE_CHECKING:
from ..exporter import RegblockExporter
from systemrdl.node import RegNode
class ReadbackLoopBody(LoopBody):
def __init__(self, dim: int, iterator: str, i_type: str) -> None:
@@ -76,19 +77,31 @@ class ReadbackAssignmentGenerator(RDLForLoopGenerator):
self.current_offset = start_offset + n_regs * dim
def enter_Reg(self, node: 'RegNode') -> None:
def enter_Reg(self, node: RegNode) -> None:
if not node.has_sw_readable:
return
accesswidth = node.get_property('accesswidth')
regwidth = node.get_property('regwidth')
if accesswidth < regwidth:
rbuf = node.get_property('buffer_reads')
if rbuf:
trigger = node.get_property('rbuffer_trigger')
is_own_trigger = (isinstance(trigger, RegNode) and trigger == node)
if is_own_trigger:
if accesswidth < regwidth:
self.process_buffered_reg_with_bypass(node, regwidth, accesswidth)
else:
# bypass cancels out. Behaves like a normal reg
self.process_reg(node)
else:
self.process_buffered_reg(node, regwidth, accesswidth)
elif accesswidth < regwidth:
self.process_wide_reg(node, accesswidth)
else:
self.process_reg(node)
def process_reg(self, node: 'RegNode') -> None:
def process_reg(self, node: RegNode) -> None:
current_bit = 0
rd_strb = f"({self.exp.dereferencer.get_access_strobe(node)} && !decoded_req_is_wr)"
# Fields are sorted by ascending low bit
@@ -100,11 +113,10 @@ class ReadbackAssignmentGenerator(RDLForLoopGenerator):
if field.low != current_bit:
self.add_content(f"assign readback_array[{self.current_offset_str}][{field.low-1}:{current_bit}] = '0;")
value = self.exp.dereferencer.get_value(field)
if field.msb < field.lsb:
# Field gets bitswapped since it is in [low:high] orientation
value = f"{{<<{{{self.exp.dereferencer.get_value(field)}}}}}"
else:
value = self.exp.dereferencer.get_value(field)
value = f"{{<<{{{value}}}}}"
self.add_content(f"assign readback_array[{self.current_offset_str}][{field.high}:{field.low}] = {rd_strb} ? {value} : '0;")
@@ -118,7 +130,98 @@ class ReadbackAssignmentGenerator(RDLForLoopGenerator):
self.current_offset += 1
def process_wide_reg(self, node: 'RegNode', accesswidth: int) -> None:
def process_buffered_reg(self, node: RegNode, regwidth: int, accesswidth: int) -> None:
rbuf = self.exp.read_buffering.get_rbuf_data(node)
if accesswidth < regwidth:
# Is wide reg
n_subwords = regwidth // accesswidth
astrb = self.exp.dereferencer.get_access_strobe(node, reduce_substrobes=False)
for i in range(n_subwords):
rd_strb = f"({astrb}[{i}] && !decoded_req_is_wr)"
bslice = f"[{(i + 1) * accesswidth - 1}:{i*accesswidth}]"
self.add_content(f"assign readback_array[{self.current_offset_str}] = {rd_strb} ? {rbuf}{bslice} : '0;")
self.current_offset += 1
else:
# Is regular reg
rd_strb = f"({self.exp.dereferencer.get_access_strobe(node)} && !decoded_req_is_wr)"
self.add_content(f"assign readback_array[{self.current_offset_str}][{regwidth-1}:0] = {rd_strb} ? {rbuf} : '0;")
bus_width = self.exp.cpuif.data_width
if regwidth < bus_width:
self.add_content(f"assign readback_array[{self.current_offset_str}][{bus_width-1}:{regwidth}] = '0;")
self.current_offset += 1
def process_buffered_reg_with_bypass(self, node: RegNode, regwidth: int, accesswidth: int) -> None:
"""
Special case for a buffered register when the register is its own trigger.
First sub-word shall bypass the read buffer and assign directly.
Subsequent subwords assign from the buffer.
Caller guarantees this is a wide reg
"""
astrb = self.exp.dereferencer.get_access_strobe(node, reduce_substrobes=False)
# Generate assignments for first sub-word
bidx = 0
rd_strb = f"({astrb}[0] && !decoded_req_is_wr)"
for field in node.fields():
if not field.is_sw_readable:
continue
if field.low >= accesswidth:
# field is not in this subword.
break
if bidx < field.low:
# insert padding before
self.add_content(f"assign readback_array[{self.current_offset_str}][{field.low - 1}:{bidx}] = '0;")
if field.high >= accesswidth:
# field gets truncated
r_low = field.low
r_high = accesswidth - 1
f_low = 0
f_high = accesswidth - 1 - field.low
if field.msb < field.lsb:
# Field gets bitswapped since it is in [low:high] orientation
# Mirror the low/high indexes
f_low = field.width - 1 - f_low
f_high = field.width - 1 - f_high
f_low, f_high = f_high, f_low
value = f"{{<<{{{self.exp.dereferencer.get_value(field)}[{f_high}:{f_low}]}}}}"
else:
value = self.exp.dereferencer.get_value(field) + f"[{f_high}:{f_low}]"
self.add_content(f"assign readback_array[{self.current_offset_str}][{r_high}:{r_low}] = {rd_strb} ? {value} : '0;")
bidx = accesswidth
else:
# field fits in subword
value = self.exp.dereferencer.get_value(field)
if field.msb < field.lsb:
# Field gets bitswapped since it is in [low:high] orientation
value = f"{{<<{{{value}}}}}"
self.add_content(f"assign readback_array[{self.current_offset_str}][{field.high}:{field.low}] = {rd_strb} ? {value} : '0;")
bidx = field.high + 1
# pad up remainder of subword
if bidx < accesswidth:
self.add_content(f"assign readback_array[{self.current_offset_str}][{accesswidth-1}:{bidx}] = '0;")
self.current_offset += 1
# Assign remainder of subwords from read buffer
n_subwords = regwidth // accesswidth
rbuf = self.exp.read_buffering.get_rbuf_data(node)
for i in range(1, n_subwords):
rd_strb = f"({astrb}[{i}] && !decoded_req_is_wr)"
bslice = f"[{(i + 1) * accesswidth - 1}:{i*accesswidth}]"
self.add_content(f"assign readback_array[{self.current_offset_str}] = {rd_strb} ? {rbuf}{bslice} : '0;")
self.current_offset += 1
def process_wide_reg(self, node: RegNode, accesswidth: int) -> None:
bus_width = self.exp.cpuif.data_width
subword_idx = 0
@@ -162,11 +265,10 @@ class ReadbackAssignmentGenerator(RDLForLoopGenerator):
low = field.low - accesswidth * subword_idx
high = field.high - accesswidth * subword_idx
value = self.exp.dereferencer.get_value(field)
if field.msb < field.lsb:
# Field gets bitswapped since it is in [low:high] orientation
value = f"{{<<{{{self.exp.dereferencer.get_value(field)}}}}}"
else:
value = self.exp.dereferencer.get_value(field)
value = f"{{<<{{{value}}}}}"
self.add_content(f"assign readback_array[{self.current_offset_str}][{high}:{low}] = {rd_strb} ? {value} : '0;")

View File

@@ -90,8 +90,8 @@ class DesignScanner(RDLListener):
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')
self.has_buffered_write_regs = self.has_buffered_write_regs or bool(node.get_property('buffer_writes'))
self.has_buffered_read_regs = self.has_buffered_read_regs or bool(node.get_property('buffer_reads'))
def enter_Signal(self, node: 'SignalNode') -> None:
if node.get_property('field_reset'):

View File

View File

@@ -0,0 +1,117 @@
addrmap top {
default regwidth = 8;
default accesswidth = 8;
default sw=r;
default hw=r;
signal {} incr_en;
//--------------------------------------------------------------------------
// Wide registers
//--------------------------------------------------------------------------
reg {
regwidth = 32;
default counter;
default incr = incr_en;
buffer_reads;
field {} f1[3] = 0;
field {} f2[3] = 0;
field {} f3[3] = 0;
field {} f4[3] = 0;
field {} f5[3] = 0;
field {} f6[3] = 0;
field {} f7[3] = 0;
field {} f8[3] = 0;
field {} f9[3] = 0;
field {} fa[3] = 0;
} reg1;
reg {
regwidth = 32;
default counter;
default incr = incr_en;
buffer_reads;
field {} f1[28:30] = 0;
field {} f2[3] = 0;
field {} f3[3] = 0;
field {} f4[3] = 0;
field {} f5[3] = 0;
field {} f6[3] = 0;
field {} f7[3] = 0;
field {} f8[3] = 0;
field {} f9[3] = 0;
field {} fa[3] = 0;
} reg1_msb0;
reg {
regwidth = 32;
default counter;
default incr = incr_en;
default rclr;
buffer_reads;
field {} f1[4:0] = 0;
field {} f2[14:10] = 0;
field {} f3[26:22] = 0;
field {} f4[31:27] = 0;
} reg2;
//--------------------------------------------------------------------------
// Alternate Triggers
//--------------------------------------------------------------------------
reg myreg {
buffer_reads;
default counter;
default incr = incr_en;
field {} f1[7:0] = 0;
};
reg myreg_wide {
buffer_reads;
default counter;
default incr = incr_en;
regwidth = 16;
field {} f1[15:0] = 0xAAAA;
};
// Trigger via another register
myreg g1_r1;
myreg g1_r2;
g1_r2->rbuffer_trigger = g1_r1;
myreg_wide g2_r1 @ 0x10;
myreg_wide g2_r2;
g2_r2->rbuffer_trigger = g2_r1;
// triger from signal
signal {
activehigh;
} trigger_sig;
signal {
activelow;
} trigger_sig_n;
reg ro_reg {
buffer_reads;
field {
hw=w;
} f1[7:0];
};
ro_reg g3_r1;
ro_reg g3_r2;
g3_r1->rbuffer_trigger = trigger_sig;
g3_r2->rbuffer_trigger = trigger_sig_n;
// trigger from field/propref
reg {
field {
sw=w; hw=r; singlepulse;
} trig = 0;
} g4_trig;
myreg g4_r1;
myreg g4_r2;
g4_r1->rbuffer_trigger = g4_trig.trig;
g4_r2->rbuffer_trigger = g4_trig.trig->swmod;
};

View File

@@ -0,0 +1,146 @@
{% extends "lib/tb_base.sv" %}
{% block seq %}
{% sv_line_anchor %}
cb.hwif_in.incr_en <= '1;
cb.hwif_in.trigger_sig_n <= '1;
##1;
cb.rst <= '0;
##1;
//--------------------------------------------------------------------------
// Wide registers
//--------------------------------------------------------------------------
// reg1
// expect to read all counter values atomically
begin
logic [7:0] subword;
logic [31:0] rdata;
logic [2:0] fdata;
cpuif.read('h0, subword);
fdata = subword[2:0];
rdata = {10{fdata}};
assert(subword == rdata[7:0]);
cpuif.assert_read('h1, rdata[15:8]);
cpuif.assert_read('h2, rdata[23:16]);
cpuif.assert_read('h3, rdata[31:24]);
end
// reg1_msb0
// expect to read all counter values atomically
begin
logic [7:0] subword;
logic [31:0] rdata;
logic [2:0] fdata;
cpuif.read('h4, subword);
fdata = subword[3:1];
rdata = {10{fdata}} << 1;
assert(subword == rdata[7:0]);
cpuif.assert_read('h5, rdata[15:8]);
cpuif.assert_read('h6, rdata[23:16]);
cpuif.assert_read('h7, rdata[31:24]);
end
cb.hwif_in.incr_en <= '0;
@cb;
// check that msb0 ordering is correct
begin
logic [7:0] subword;
logic [31:0] rdata;
logic [2:0] fdata;
cpuif.read('h0, subword);
fdata = subword[2:0];
rdata = {10{fdata}};
assert(subword == rdata[7:0]);
cpuif.assert_read('h1, rdata[15:8]);
cpuif.assert_read('h2, rdata[23:16]);
cpuif.assert_read('h3, rdata[31:24]);
fdata = {<<{fdata}};
rdata = {10{fdata}} << 1;
cpuif.assert_read('h4, rdata[7:0]);
cpuif.assert_read('h5, rdata[15:8]);
cpuif.assert_read('h6, rdata[23:16]);
cpuif.assert_read('h7, rdata[31:24]);
end
cb.hwif_in.incr_en <= '1;
// reg2
// read-clear
repeat(2) begin
logic [7:0] subword;
logic [4:0] fdata;
logic [31:0] rdata;
cpuif.read('h8, subword);
rdata[7:0] = subword;
cpuif.read('h9, subword);
rdata[15:8] = subword;
cpuif.read('hA, subword);
rdata[23:16] = subword;
cpuif.read('hB, subword);
rdata[31:24] = subword;
fdata = rdata[4:0];
assert(rdata[14:10] == fdata);
assert(rdata[26:22] == fdata);
assert(rdata[31:27] == fdata);
end
//--------------------------------------------------------------------------
// Alternate Triggers
//--------------------------------------------------------------------------
// Trigger via another register
// g1
begin
logic [7:0] rdata;
cpuif.read('hC, rdata);
cpuif.assert_read('hD, rdata);
end
// g2
begin
logic [7:0] rdata1;
logic [7:0] rdata2;
cpuif.read('h10, rdata1);
cpuif.read('h11, rdata2);
cpuif.assert_read('h12, rdata1);
cpuif.assert_read('h13, rdata2);
end
// triger from signal
// g3
cb.hwif_in.g3_r1.f1.next <= 'hAB;
cb.hwif_in.g3_r2.f1.next <= 'hCD;
cb.hwif_in.trigger_sig <= '1;
cb.hwif_in.trigger_sig_n <= '0;
@cb;
cb.hwif_in.g3_r1.f1.next <= 'h00;
cb.hwif_in.g3_r2.f1.next <= 'h00;
cb.hwif_in.trigger_sig <= '0;
cb.hwif_in.trigger_sig_n <= '1;
@cb;
cpuif.assert_read('h14, 'hAB);
cpuif.assert_read('h15, 'hCD);
// trigger from field/propref
// g4
begin
logic [7:0] rdata;
cpuif.write('h16, 'h1);
repeat(5) @cb;
cpuif.read('h17, rdata);
repeat(5) @cb;
cpuif.assert_read('h18, rdata - 1); // swmod happens one cycle earlier, so count is -1
end
{% endblock %}

View File

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