Implement read buffering. (#22)
This commit is contained in:
@@ -121,4 +121,5 @@ Links
|
|||||||
:caption: Extended Properties
|
:caption: Extended Properties
|
||||||
|
|
||||||
udps/intro
|
udps/intro
|
||||||
|
udps/read_buffering
|
||||||
udps/write_buffering
|
udps/write_buffering
|
||||||
|
|||||||
@@ -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
|
PeakRDL-regblock tool understands several UDPs that are described in this
|
||||||
section.
|
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
|
.. list-table:: Summary of UDPs
|
||||||
:header-rows: 1
|
:header-rows: 1
|
||||||
@@ -17,6 +19,20 @@ section.
|
|||||||
- Type
|
- Type
|
||||||
- Description
|
- 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
|
* - buffer_writes
|
||||||
- reg
|
- reg
|
||||||
- boolean
|
- boolean
|
||||||
|
|||||||
158
docs/udps/read_buffering.rst
Normal file
158
docs/udps/read_buffering.rst
Normal 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.
|
||||||
@@ -3,20 +3,18 @@
|
|||||||
Write-buffered Registers
|
Write-buffered Registers
|
||||||
========================
|
========================
|
||||||
|
|
||||||
In some situations, your hardware design may requre that fields be
|
In order to support larger software write accesses that are atomic, the
|
||||||
updated atomically (on the same clock cycle), but your cpuif is not wide enough
|
regblock generator understands several UDPs that implement write-buffering to
|
||||||
to do so natively. Using some UDP extensions, this regblock generator implements
|
specific registers. This causes the regblock to delay the effect of a software
|
||||||
a way for you to add write-buffering to specific registers, which allows the
|
write operation until a defined trigger event.
|
||||||
regblock to delay the effect of a software write operation until a defined
|
|
||||||
trigger event.
|
|
||||||
|
|
||||||
Some examples of when this is useful:
|
Some examples of when this is useful:
|
||||||
* You need to have software update a wide 64-bit register atomically, but
|
* You need to have software update a wide 64-bit register atomically, but
|
||||||
the CPU interface is only 32-bits.
|
the CPU interface is only 32-bits.
|
||||||
* Software needs to be able to write multiple registers such that the
|
* Software needs to be able to write multiple registers such that the
|
||||||
hardware is updated atomically.
|
hardware is updated atomically.
|
||||||
* Software can pre-load one or more registers with their next value, and
|
* Software can pre-load one or more registers with their next value, and
|
||||||
trigger the update via an external hardware signal.
|
trigger the update via an external hardware signal.
|
||||||
|
|
||||||
If a register is write-buffered, a holding buffer stage is inserted between the
|
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
|
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
|
The behavior of write-buffered registers is defined using the following two
|
||||||
properties:
|
properties:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../hdl-src/regblock_udps.rdl
|
||||||
|
:lines: 20-28
|
||||||
|
|
||||||
``buffer_writes``
|
``buffer_writes``
|
||||||
* Assigned value is a boolean.
|
* Assigned value is a boolean.
|
||||||
* If true, enables double-buffering of writes to this register.
|
* 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
|
* Any software write operation to a buffered register is held back in a
|
||||||
unique to the register.
|
storage element unique to the register.
|
||||||
* The software write operation is committed to the register once triggered to do so.
|
* The software write operation is committed to the register once triggered
|
||||||
* Unless specified otherwise, the buffer trigger occurs when the highest
|
to do so.
|
||||||
address of the buffered register is written.
|
* Unless specified otherwise, the buffer trigger occurs when the highest
|
||||||
|
address of the buffered register is written.
|
||||||
|
|
||||||
``wbuffer_trigger``
|
``wbuffer_trigger``
|
||||||
* Assigned value is a reference to a register, single-bit field, signal, or single-bit property.
|
* Assigned value is a reference to a register, single-bit field, signal,
|
||||||
* Controls when the double-buffer commits the software write operation to the register's fields.
|
or single-bit property.
|
||||||
* If reference is a single-bit value (signal, field, property reference),
|
* Controls when the double-buffer commits the software write operation to
|
||||||
then the assertion of that value triggers the buffer to be evicted.
|
the register's fields.
|
||||||
* Signal references shall have either activehigh/activelow property set to define the polarity.
|
* If reference is a single-bit value (signal, field, property reference),
|
||||||
* If the reference is a reg, then buffer is evicted when the register's
|
then the assertion of that value triggers the buffer to be evicted.
|
||||||
highest address is written
|
* 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
|
Other Rules
|
||||||
^^^^^^^^^^^
|
^^^^^^^^^^^
|
||||||
* It is an error to set ``buffer_writes`` if the register does not contain any
|
* It is an error to set ``buffer_writes`` if the register does not contain any
|
||||||
writable fields
|
writable fields
|
||||||
* If ``buffer_writes`` is false, then anything assigned to ``wbuffer_trigger``
|
* If ``buffer_writes`` is false, then anything assigned to ``wbuffer_trigger``
|
||||||
is ignored.
|
is ignored.
|
||||||
* The buffered register and the trigger reference shall both be within the same
|
* The buffered register and the trigger reference shall both be within the
|
||||||
internal device. ie: one cannot be in an external scope with respect to the
|
same internal device. ie: one cannot be in an external scope with respect to
|
||||||
other.
|
the other.
|
||||||
* Unless it is a register, the reference assigned to ``wbuffer_trigger`` shall
|
* Unless it is a register, the reference assigned to ``wbuffer_trigger`` shall
|
||||||
represent a single bit.
|
represent a single bit.
|
||||||
* If a buffered register was not written, any trigger events are ignored.
|
* 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
|
* It is valid for a buffered register to be partially written (either via
|
||||||
strobes, or partial addressing).
|
write strobes, or partial addressing).
|
||||||
* The software write operation is not considered to take place until the buffer
|
* The software write operation is not considered to take place until the
|
||||||
is evicted by the trigger. This influences the behavior of properties like
|
buffer is evicted by the trigger. This influences the behavior of properties
|
||||||
``swmod`` and ``swacc`` - they are not asserted until the register's fields
|
like ``swmod`` and ``swacc`` - they are not asserted until the register's
|
||||||
are actually written by the buffer.
|
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
|
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
|
atomically. Using the ``wbuffer_trigger`` property, you can define which
|
||||||
write operation triggers the group to be updated.
|
register write operation triggers the group to be updated.
|
||||||
|
|
||||||
|
|
||||||
.. code-block:: systemrdl
|
.. 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
|
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.
|
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.
|
The write operation to reg4 triggers the buffered data to be committed to
|
||||||
This is guaranteed to occur on the same clock-cycle.
|
reg1-reg3. This is guaranteed to occur on the same clock-cycle.
|
||||||
|
|
||||||
|
|
||||||
Externally Triggered Register Update
|
Externally Triggered Register Update
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
Some applications may require precise timing for when a register (or group of registers)
|
Some applications may require precise timing for when a register (or group of
|
||||||
update their value. Often software cannot offer such timing precision.
|
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,
|
In this example, the trigger event is bound to an external signal. When
|
||||||
any pending write operation the buffered register will be committed.
|
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
|
The hwif_out value presents the new register state on the clock cycle after the
|
||||||
trigger is asserted.
|
trigger is asserted.
|
||||||
|
|
||||||
@@ -169,5 +175,5 @@ trigger is asserted.
|
|||||||
reg1->wbuffer_trigger = trigger_signal;
|
reg1->wbuffer_trigger = trigger_signal;
|
||||||
reg2->wbuffer_trigger = trigger_signal;
|
reg2->wbuffer_trigger = trigger_signal;
|
||||||
|
|
||||||
After software writes to ``reg1`` & ``reg2``, the written data is held back in the write
|
After software writes to ``reg1`` & ``reg2``, the written data is held back in
|
||||||
buffer until ``hwif_in..trigger_signal`` is asserted by the hardware.
|
the write buffer until ``hwif_in..trigger_signal`` is asserted by the hardware.
|
||||||
|
|||||||
@@ -7,6 +7,16 @@
|
|||||||
* For more details, see: https://peakrdl-regblock.readthedocs.io/en/latest/udps/intro.html
|
* 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 {
|
property buffer_writes {
|
||||||
component = reg;
|
component = reg;
|
||||||
type = boolean;
|
type = boolean;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ from .cpuif import CpuifBase
|
|||||||
from .cpuif.apb4 import APB4_Cpuif
|
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
|
||||||
|
|
||||||
class RegblockExporter:
|
class RegblockExporter:
|
||||||
def __init__(self, **kwargs: Any) -> None:
|
def __init__(self, **kwargs: Any) -> None:
|
||||||
@@ -31,7 +32,8 @@ class RegblockExporter:
|
|||||||
self.address_decode = AddressDecode(self)
|
self.address_decode = AddressDecode(self)
|
||||||
self.field_logic = FieldLogic(self)
|
self.field_logic = FieldLogic(self)
|
||||||
self.readback = None # type: Readback
|
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.dereferencer = Dereferencer(self)
|
||||||
self.min_read_latency = 0
|
self.min_read_latency = 0
|
||||||
self.min_write_latency = 0
|
self.min_write_latency = 0
|
||||||
@@ -145,7 +147,6 @@ class RegblockExporter:
|
|||||||
self,
|
self,
|
||||||
retime_read_fanin
|
retime_read_fanin
|
||||||
)
|
)
|
||||||
self.write_buffering = WriteBuffering(self)
|
|
||||||
|
|
||||||
# Validate that there are no unsupported constructs
|
# Validate that there are no unsupported constructs
|
||||||
validator = DesignValidator(self)
|
validator = DesignValidator(self)
|
||||||
@@ -161,6 +162,7 @@ class RegblockExporter:
|
|||||||
"cpuif": self.cpuif,
|
"cpuif": self.cpuif,
|
||||||
"hwif": self.hwif,
|
"hwif": self.hwif,
|
||||||
"write_buffering": self.write_buffering,
|
"write_buffering": self.write_buffering,
|
||||||
|
"read_buffering": self.read_buffering,
|
||||||
"get_resetsignal": self.dereferencer.get_resetsignal,
|
"get_resetsignal": self.dereferencer.get_resetsignal,
|
||||||
"address_decode": self.address_decode,
|
"address_decode": self.address_decode,
|
||||||
"field_logic": self.field_logic,
|
"field_logic": self.field_logic,
|
||||||
|
|||||||
@@ -175,8 +175,13 @@ class FieldLogic:
|
|||||||
"""
|
"""
|
||||||
Asserted when field is software accessed (read)
|
Asserted when field is software accessed (read)
|
||||||
"""
|
"""
|
||||||
strb = self.exp.dereferencer.get_access_strobe(field)
|
buffer_reads = field.parent.get_property('buffer_reads')
|
||||||
return f"{strb} && !decoded_req_is_wr"
|
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:
|
def get_swmod_identifier(self, field: 'FieldNode') -> str:
|
||||||
"""
|
"""
|
||||||
@@ -186,6 +191,7 @@ class FieldLogic:
|
|||||||
w_modifiable = field.is_sw_writable
|
w_modifiable = field.is_sw_writable
|
||||||
r_modifiable = (field.get_property('onread') is not None)
|
r_modifiable = (field.get_property('onread') is not None)
|
||||||
buffer_writes = field.parent.get_property('buffer_writes')
|
buffer_writes = field.parent.get_property('buffer_writes')
|
||||||
|
buffer_reads = field.parent.get_property('buffer_reads')
|
||||||
|
|
||||||
if w_modifiable and not r_modifiable:
|
if w_modifiable and not r_modifiable:
|
||||||
# assert swmod only on sw write
|
# assert swmod only on sw write
|
||||||
@@ -200,10 +206,18 @@ class FieldLogic:
|
|||||||
|
|
||||||
if w_modifiable and r_modifiable:
|
if w_modifiable and r_modifiable:
|
||||||
# assert swmod on both sw read and write
|
# assert swmod on both sw read and write
|
||||||
if buffer_writes:
|
astrb = self.exp.dereferencer.get_access_strobe(field)
|
||||||
astrb = self.exp.dereferencer.get_access_strobe(field)
|
if buffer_writes or buffer_reads:
|
||||||
wstrb = self.exp.write_buffering.get_write_strobe(field)
|
if buffer_reads:
|
||||||
rstrb = f"{astrb} && !decoded_req_is_wr"
|
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}"
|
return f"{wstrb} || {rstrb}"
|
||||||
else:
|
else:
|
||||||
# Unbuffered. Use decoder strobe directly
|
# Unbuffered. Use decoder strobe directly
|
||||||
@@ -213,7 +227,11 @@ class FieldLogic:
|
|||||||
if not w_modifiable and r_modifiable:
|
if not w_modifiable and r_modifiable:
|
||||||
# assert swmod only on sw read
|
# assert swmod only on sw read
|
||||||
astrb = self.exp.dereferencer.get_access_strobe(field)
|
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
|
# Not sw modifiable
|
||||||
return "1'b0"
|
return "1'b0"
|
||||||
|
|||||||
@@ -13,8 +13,14 @@ class _OnRead(NextStateConditional):
|
|||||||
return field.get_property('onread') == self.onreadtype
|
return field.get_property('onread') == self.onreadtype
|
||||||
|
|
||||||
def get_predicate(self, field: 'FieldNode') -> str:
|
def get_predicate(self, field: 'FieldNode') -> str:
|
||||||
strb = self.exp.dereferencer.get_access_strobe(field)
|
if field.parent.get_property('buffer_reads'):
|
||||||
return f"{strb} && !decoded_req_is_wr"
|
# 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):
|
class ClearOnRead(_OnRead):
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ module {{module_name}} (
|
|||||||
assign cpuif_wr_err = '0;
|
assign cpuif_wr_err = '0;
|
||||||
|
|
||||||
{%- if has_buffered_write_regs %}
|
{%- if has_buffered_write_regs %}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
// Write double-buffers
|
// Write double-buffers
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
@@ -116,7 +117,6 @@ module {{module_name}} (
|
|||||||
|
|
||||||
{{write_buffering.get_implementation()|indent}}
|
{{write_buffering.get_implementation()|indent}}
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
// Field logic
|
// Field logic
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
@@ -126,6 +126,15 @@ module {{module_name}} (
|
|||||||
|
|
||||||
{{field_logic.get_implementation()|indent}}
|
{{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
|
// Readback
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
|
|||||||
58
src/peakrdl_regblock/read_buffering/__init__.py
Normal file
58
src/peakrdl_regblock/read_buffering/__init__.py
Normal 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"
|
||||||
@@ -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)
|
||||||
18
src/peakrdl_regblock/read_buffering/storage_generator.py
Normal file
18
src/peakrdl_regblock/read_buffering/storage_generator.py
Normal 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)
|
||||||
5
src/peakrdl_regblock/read_buffering/template.sv
Normal file
5
src/peakrdl_regblock/read_buffering/template.sv
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
always_ff @(posedge clk) begin
|
||||||
|
if({{rbuf.get_trigger(node)}}) begin
|
||||||
|
{{get_assignments(node)|indent(8)}}
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
from typing import TYPE_CHECKING, List
|
from typing import TYPE_CHECKING, List
|
||||||
|
|
||||||
|
from systemrdl.node import RegNode
|
||||||
|
|
||||||
from ..forloop_generator import RDLForLoopGenerator, LoopBody
|
from ..forloop_generator import RDLForLoopGenerator, LoopBody
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..exporter import RegblockExporter
|
from ..exporter import RegblockExporter
|
||||||
from systemrdl.node import RegNode
|
|
||||||
|
|
||||||
class ReadbackLoopBody(LoopBody):
|
class ReadbackLoopBody(LoopBody):
|
||||||
def __init__(self, dim: int, iterator: str, i_type: str) -> None:
|
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
|
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:
|
if not node.has_sw_readable:
|
||||||
return
|
return
|
||||||
|
|
||||||
accesswidth = node.get_property('accesswidth')
|
accesswidth = node.get_property('accesswidth')
|
||||||
regwidth = node.get_property('regwidth')
|
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)
|
self.process_wide_reg(node, accesswidth)
|
||||||
else:
|
else:
|
||||||
self.process_reg(node)
|
self.process_reg(node)
|
||||||
|
|
||||||
|
|
||||||
def process_reg(self, node: 'RegNode') -> None:
|
def process_reg(self, node: RegNode) -> None:
|
||||||
current_bit = 0
|
current_bit = 0
|
||||||
rd_strb = f"({self.exp.dereferencer.get_access_strobe(node)} && !decoded_req_is_wr)"
|
rd_strb = f"({self.exp.dereferencer.get_access_strobe(node)} && !decoded_req_is_wr)"
|
||||||
# Fields are sorted by ascending low bit
|
# Fields are sorted by ascending low bit
|
||||||
@@ -100,11 +113,10 @@ class ReadbackAssignmentGenerator(RDLForLoopGenerator):
|
|||||||
if field.low != current_bit:
|
if field.low != current_bit:
|
||||||
self.add_content(f"assign readback_array[{self.current_offset_str}][{field.low-1}:{current_bit}] = '0;")
|
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:
|
if field.msb < field.lsb:
|
||||||
# Field gets bitswapped since it is in [low:high] orientation
|
# Field gets bitswapped since it is in [low:high] orientation
|
||||||
value = f"{{<<{{{self.exp.dereferencer.get_value(field)}}}}}"
|
value = f"{{<<{{{value}}}}}"
|
||||||
else:
|
|
||||||
value = self.exp.dereferencer.get_value(field)
|
|
||||||
|
|
||||||
self.add_content(f"assign readback_array[{self.current_offset_str}][{field.high}:{field.low}] = {rd_strb} ? {value} : '0;")
|
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
|
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
|
bus_width = self.exp.cpuif.data_width
|
||||||
|
|
||||||
subword_idx = 0
|
subword_idx = 0
|
||||||
@@ -162,11 +265,10 @@ class ReadbackAssignmentGenerator(RDLForLoopGenerator):
|
|||||||
low = field.low - accesswidth * subword_idx
|
low = field.low - accesswidth * subword_idx
|
||||||
high = field.high - accesswidth * subword_idx
|
high = field.high - accesswidth * subword_idx
|
||||||
|
|
||||||
|
value = self.exp.dereferencer.get_value(field)
|
||||||
if field.msb < field.lsb:
|
if field.msb < field.lsb:
|
||||||
# Field gets bitswapped since it is in [low:high] orientation
|
# Field gets bitswapped since it is in [low:high] orientation
|
||||||
value = f"{{<<{{{self.exp.dereferencer.get_value(field)}}}}}"
|
value = f"{{<<{{{value}}}}}"
|
||||||
else:
|
|
||||||
value = self.exp.dereferencer.get_value(field)
|
|
||||||
|
|
||||||
self.add_content(f"assign readback_array[{self.current_offset_str}][{high}:{low}] = {rd_strb} ? {value} : '0;")
|
self.add_content(f"assign readback_array[{self.current_offset_str}][{high}:{low}] = {rd_strb} ? {value} : '0;")
|
||||||
|
|
||||||
|
|||||||
@@ -90,8 +90,8 @@ class DesignScanner(RDLListener):
|
|||||||
accesswidth = node.get_property('accesswidth')
|
accesswidth = node.get_property('accesswidth')
|
||||||
self.cpuif_data_width = max(self.cpuif_data_width, 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_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 node.get_property('buffer_reads')
|
self.has_buffered_read_regs = self.has_buffered_read_regs or bool(node.get_property('buffer_reads'))
|
||||||
|
|
||||||
def enter_Signal(self, node: 'SignalNode') -> None:
|
def enter_Signal(self, node: 'SignalNode') -> None:
|
||||||
if node.get_property('field_reset'):
|
if node.get_property('field_reset'):
|
||||||
|
|||||||
0
tests/test_read_buffer/__init__.py
Normal file
0
tests/test_read_buffer/__init__.py
Normal file
117
tests/test_read_buffer/regblock.rdl
Normal file
117
tests/test_read_buffer/regblock.rdl
Normal 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;
|
||||||
|
|
||||||
|
};
|
||||||
146
tests/test_read_buffer/tb_template.sv
Normal file
146
tests/test_read_buffer/tb_template.sv
Normal 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 %}
|
||||||
5
tests/test_read_buffer/testcase.py
Normal file
5
tests/test_read_buffer/testcase.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from ..lib.sim_testcase import SimTestCase
|
||||||
|
|
||||||
|
class Test(SimTestCase):
|
||||||
|
def test_dut(self):
|
||||||
|
self.run_test()
|
||||||
Reference in New Issue
Block a user