Implement write buffering (#22)
This commit is contained in:
Binary file not shown.
BIN
docs/diagrams/rbuf.png
Normal file
BIN
docs/diagrams/rbuf.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 122 KiB |
BIN
docs/diagrams/wbuf.png
Normal file
BIN
docs/diagrams/wbuf.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 123 KiB |
@@ -115,3 +115,10 @@ Links
|
|||||||
props/addrmap
|
props/addrmap
|
||||||
props/signal
|
props/signal
|
||||||
props/rhs_props
|
props/rhs_props
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:hidden:
|
||||||
|
:caption: Extended Properties
|
||||||
|
|
||||||
|
udps/intro
|
||||||
|
udps/write_buffering
|
||||||
|
|||||||
@@ -60,7 +60,9 @@ swmod
|
|||||||
|OK|
|
|OK|
|
||||||
|
|
||||||
If true, infers an output signal ``hwif_out..swmod`` that is asserted as the
|
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::
|
.. wavedrom::
|
||||||
|
|
||||||
|
|||||||
32
docs/udps/intro.rst
Normal file
32
docs/udps/intro.rst
Normal 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`.
|
||||||
173
docs/udps/write_buffering.rst
Normal file
173
docs/udps/write_buffering.rst
Normal 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
18
hdl-src/regblock_udps.rdl
Normal 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;
|
||||||
|
};
|
||||||
3
setup.py
3
setup.py
@@ -24,7 +24,7 @@ setuptools.setup(
|
|||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
python_requires='>=3.6',
|
python_requires='>=3.6',
|
||||||
install_requires=[
|
install_requires=[
|
||||||
"systemrdl-compiler>=1.25.0",
|
"systemrdl-compiler>=1.25.1",
|
||||||
"Jinja2>=2.11",
|
"Jinja2>=2.11",
|
||||||
],
|
],
|
||||||
entry_points = {
|
entry_points = {
|
||||||
@@ -42,6 +42,7 @@ setuptools.setup(
|
|||||||
"Programming Language :: Python :: 3.8",
|
"Programming Language :: Python :: 3.8",
|
||||||
"Programming Language :: Python :: 3.9",
|
"Programming Language :: Python :: 3.9",
|
||||||
"Programming Language :: Python :: 3.10",
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
"Programming Language :: Python :: 3 :: Only",
|
"Programming Language :: Python :: 3 :: Only",
|
||||||
"Intended Audience :: Developers",
|
"Intended Audience :: Developers",
|
||||||
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
||||||
|
|||||||
@@ -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)});"
|
s = f"{self.addr_decode.get_access_strobe(node)} = cpuif_req_masked & (cpuif_addr == {self._get_address_str(node)});"
|
||||||
self.add_content(s)
|
self.add_content(s)
|
||||||
else:
|
else:
|
||||||
|
# Register is wide. Create a substrobe for each subword
|
||||||
n_subwords = regwidth // accesswidth
|
n_subwords = regwidth // accesswidth
|
||||||
subword_stride = accesswidth // 8
|
subword_stride = accesswidth // 8
|
||||||
for i in range(n_subwords):
|
for i in range(n_subwords):
|
||||||
|
|||||||
@@ -10,12 +10,13 @@ from .dereferencer import Dereferencer
|
|||||||
from .readback import Readback
|
from .readback import Readback
|
||||||
from .identifier_filter import kw_filter as kwf
|
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 .utils import get_always_ff_event
|
||||||
from .scan_design import DesignScanner
|
from .scan_design import DesignScanner
|
||||||
from .validate_design import DesignValidator
|
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:
|
class RegblockExporter:
|
||||||
def __init__(self, **kwargs: Any) -> None:
|
def __init__(self, **kwargs: Any) -> None:
|
||||||
@@ -30,6 +31,7 @@ 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.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
|
||||||
@@ -143,6 +145,7 @@ 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)
|
||||||
@@ -153,8 +156,11 @@ class RegblockExporter:
|
|||||||
"module_name": module_name,
|
"module_name": module_name,
|
||||||
"user_out_of_hier_signals": scanner.out_of_hier_signals.values(),
|
"user_out_of_hier_signals": scanner.out_of_hier_signals.values(),
|
||||||
"has_writable_msb0_fields": scanner.has_writable_msb0_fields,
|
"has_writable_msb0_fields": scanner.has_writable_msb0_fields,
|
||||||
|
"has_buffered_write_regs": scanner.has_buffered_write_regs,
|
||||||
|
"has_buffered_read_regs": scanner.has_buffered_read_regs,
|
||||||
"cpuif": self.cpuif,
|
"cpuif": self.cpuif,
|
||||||
"hwif": self.hwif,
|
"hwif": self.hwif,
|
||||||
|
"write_buffering": self.write_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,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from systemrdl.rdltypes import PropertyReference, PrecedenceType, InterruptType
|
from systemrdl.rdltypes import PrecedenceType, InterruptType
|
||||||
from systemrdl.node import Node
|
|
||||||
|
|
||||||
from .bases import AssignmentPrecedence, NextStateConditional
|
from .bases import AssignmentPrecedence, NextStateConditional
|
||||||
from . import sw_onread
|
from . import sw_onread
|
||||||
@@ -186,19 +185,35 @@ 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)
|
||||||
strb = self.exp.dereferencer.get_access_strobe(field)
|
buffer_writes = field.parent.get_property('buffer_writes')
|
||||||
|
|
||||||
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
|
||||||
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:
|
if w_modifiable and r_modifiable:
|
||||||
# assert swmod on all sw actions
|
# assert swmod on both sw read and write
|
||||||
return strb
|
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:
|
if not w_modifiable and r_modifiable:
|
||||||
# assert swmod only on sw read
|
# 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
|
# Not sw modifiable
|
||||||
return "1'b0"
|
return "1'b0"
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ class FieldLogicGenerator(RDLForLoopGenerator):
|
|||||||
super().__init__()
|
super().__init__()
|
||||||
self.field_logic = field_logic
|
self.field_logic = field_logic
|
||||||
self.exp = field_logic.exp
|
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"
|
"field_logic/templates/field_storage.sv"
|
||||||
)
|
)
|
||||||
self.intr_fields = [] # type: List[FieldNode]
|
self.intr_fields = [] # type: List[FieldNode]
|
||||||
|
|||||||
@@ -15,60 +15,104 @@ class _OnWrite(NextStateConditional):
|
|||||||
return field.is_sw_writable and field.get_property('onwrite') == self.onwritetype
|
return field.is_sw_writable and field.get_property('onwrite') == self.onwritetype
|
||||||
|
|
||||||
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_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'):
|
if field.get_property('swwe') or field.get_property('swwel'):
|
||||||
# dereferencer will wrap swwel complement if necessary
|
# dereferencer will wrap swwel complement if necessary
|
||||||
qualifier = self.exp.dereferencer.get_field_propref_value(field, 'swwe')
|
qualifier = self.exp.dereferencer.get_field_propref_value(field, 'swwe')
|
||||||
return f"{strb} && decoded_req_is_wr && {qualifier}"
|
return f"{wstrb} && {qualifier}"
|
||||||
|
|
||||||
return f"{strb} && decoded_req_is_wr"
|
return wstrb
|
||||||
|
else:
|
||||||
|
# is regular register
|
||||||
|
strb = self.exp.dereferencer.get_access_strobe(field)
|
||||||
|
|
||||||
def _wbus_bitslice(self, field: 'FieldNode', subword_idx: int) -> str:
|
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"{strb} && decoded_req_is_wr && {qualifier}"
|
||||||
|
|
||||||
|
return f"{strb} && decoded_req_is_wr"
|
||||||
|
|
||||||
|
def _wbus_bitslice(self, field: 'FieldNode', subword_idx: int = 0) -> str:
|
||||||
# Get the source bitslice range from the internal cpuif's data bus
|
# Get the source bitslice range from the internal cpuif's data bus
|
||||||
# For normal fields this ends up passing-through the field's low/high
|
if field.parent.get_property('buffer_writes'):
|
||||||
# values unchanged.
|
# register is buffered.
|
||||||
# For fields within a wide register (accesswidth < regwidth), low/high
|
# write buffer is the full width of the register. no need to deal with subwords
|
||||||
# may be shifted down and clamped depending on which sub-word is being accessed
|
high = field.high
|
||||||
accesswidth = field.parent.get_property('accesswidth')
|
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
|
||||||
|
# may be shifted down and clamped depending on which sub-word is being accessed
|
||||||
|
accesswidth = field.parent.get_property('accesswidth')
|
||||||
|
|
||||||
# Shift based on subword
|
# Shift based on subword
|
||||||
high = field.high - (subword_idx * accesswidth)
|
high = field.high - (subword_idx * accesswidth)
|
||||||
low = field.low - (subword_idx * accesswidth)
|
low = field.low - (subword_idx * accesswidth)
|
||||||
|
|
||||||
# clamp to accesswidth
|
# clamp to accesswidth
|
||||||
high = max(min(high, accesswidth), 0)
|
high = max(min(high, accesswidth), 0)
|
||||||
low = max(min(low, accesswidth), 0)
|
low = max(min(low, accesswidth), 0)
|
||||||
|
|
||||||
if field.msb < field.lsb:
|
if field.msb < field.lsb:
|
||||||
# slice is for an msb0 field.
|
# slice is for an msb0 field.
|
||||||
# mirror it
|
# mirror it
|
||||||
bus_width = self.exp.cpuif.data_width
|
bus_width = self.exp.cpuif.data_width
|
||||||
low = bus_width - 1 - low
|
low = bus_width - 1 - low
|
||||||
high = bus_width - 1 - high
|
high = bus_width - 1 - high
|
||||||
low, high = high, low
|
low, high = high, low
|
||||||
|
|
||||||
return f"[{high}:{low}]"
|
return f"[{high}:{low}]"
|
||||||
|
|
||||||
def _wr_data(self, field: 'FieldNode', subword_idx: int=0) -> str:
|
def _wr_data(self, field: 'FieldNode', subword_idx: int=0) -> str:
|
||||||
bslice = self._wbus_bitslice(field, subword_idx)
|
if field.parent.get_property('buffer_writes'):
|
||||||
|
# Is buffered. Use value from write buffer
|
||||||
if field.msb < field.lsb:
|
# No need to check msb0 ordering. Bus is pre-swapped, and bitslice
|
||||||
# Field gets bitswapped since it is in [low:high] orientation
|
# accounts for it
|
||||||
value = f"decoded_wr_data_bswap{bslice}"
|
bslice = self._wbus_bitslice(field)
|
||||||
|
wbuf_prefix = self.exp.write_buffering.get_wbuf_prefix(field)
|
||||||
|
return wbuf_prefix + ".data" + bslice
|
||||||
else:
|
else:
|
||||||
value = f"decoded_wr_data{bslice}"
|
# Regular non-buffered register
|
||||||
return value
|
bslice = self._wbus_bitslice(field, subword_idx)
|
||||||
|
|
||||||
|
if field.msb < field.lsb:
|
||||||
|
# Field gets bitswapped since it is in [low:high] orientation
|
||||||
|
value = "decoded_wr_data_bswap" + bslice
|
||||||
|
else:
|
||||||
|
value = "decoded_wr_data" + bslice
|
||||||
|
return value
|
||||||
|
|
||||||
def _wr_biten(self, field: 'FieldNode', subword_idx: int=0) -> str:
|
def _wr_biten(self, field: 'FieldNode', subword_idx: int=0) -> str:
|
||||||
bslice = self._wbus_bitslice(field, subword_idx)
|
if field.parent.get_property('buffer_writes'):
|
||||||
|
# Is buffered. Use value from write buffer
|
||||||
if field.msb < field.lsb:
|
# No need to check msb0 ordering. Bus is pre-swapped, and bitslice
|
||||||
# Field gets bitswapped since it is in [low:high] orientation
|
# accounts for it
|
||||||
value = f"decoded_wr_biten_bswap{bslice}"
|
bslice = self._wbus_bitslice(field)
|
||||||
|
wbuf_prefix = self.exp.write_buffering.get_wbuf_prefix(field)
|
||||||
|
return wbuf_prefix + ".biten" + bslice
|
||||||
else:
|
else:
|
||||||
value = f"decoded_wr_biten{bslice}"
|
# Regular non-buffered register
|
||||||
return value
|
bslice = self._wbus_bitslice(field, subword_idx)
|
||||||
|
|
||||||
|
if field.msb < field.lsb:
|
||||||
|
# Field gets bitswapped since it is in [low:high] orientation
|
||||||
|
value = "decoded_wr_biten_bswap" + bslice
|
||||||
|
else:
|
||||||
|
value = "decoded_wr_biten" + bslice
|
||||||
|
return value
|
||||||
|
|
||||||
def get_assignments(self, field: 'FieldNode') -> List[str]:
|
def get_assignments(self, field: 'FieldNode') -> List[str]:
|
||||||
accesswidth = field.parent.get_property("accesswidth")
|
accesswidth = field.parent.get_property("accesswidth")
|
||||||
@@ -92,6 +136,7 @@ class _OnWrite(NextStateConditional):
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
#-------------------------------------------------------------------------------
|
||||||
class WriteOneSet(_OnWrite):
|
class WriteOneSet(_OnWrite):
|
||||||
comment = "SW write 1 set"
|
comment = "SW write 1 set"
|
||||||
onwritetype = OnWriteType.woset
|
onwritetype = OnWriteType.woset
|
||||||
|
|||||||
@@ -108,6 +108,15 @@ module {{module_name}} (
|
|||||||
assign cpuif_wr_ack = decoded_req & decoded_req_is_wr;
|
assign cpuif_wr_ack = decoded_req & decoded_req_is_wr;
|
||||||
assign cpuif_wr_err = '0;
|
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
|
// Field logic
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
@@ -124,7 +133,6 @@ module {{module_name}} (
|
|||||||
logic readback_done;
|
logic readback_done;
|
||||||
logic [{{cpuif.data_width-1}}:0] readback_data;
|
logic [{{cpuif.data_width-1}}:0] readback_data;
|
||||||
{{readback.get_implementation()|indent}}
|
{{readback.get_implementation()|indent}}
|
||||||
|
|
||||||
{% if retime_read_response %}
|
{% if retime_read_response %}
|
||||||
always_ff {{get_always_ff_event(cpuif.reset)}} begin
|
always_ff {{get_always_ff_event(cpuif.reset)}} begin
|
||||||
if({{get_resetsignal(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_ack = readback_done;
|
||||||
assign cpuif_rd_data = readback_data;
|
assign cpuif_rd_data = readback_data;
|
||||||
assign cpuif_rd_err = readback_err;
|
assign cpuif_rd_err = readback_err;
|
||||||
{% endif %}
|
{%- endif %}
|
||||||
|
|
||||||
endmodule
|
endmodule
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
logic [{{cpuif.data_width-1}}:0] readback_array[{{array_size}}];
|
logic [{{cpuif.data_width-1}}:0] readback_array[{{array_size}}];
|
||||||
{{array_assignments}}
|
{{array_assignments}}
|
||||||
|
|
||||||
{% if do_fanin_stage %}
|
|
||||||
|
{%- if do_fanin_stage %}
|
||||||
|
|
||||||
// fanin stage
|
// fanin stage
|
||||||
logic [{{cpuif.data_width-1}}:0] readback_array_c[{{fanin_array_size}}];
|
logic [{{cpuif.data_width-1}}:0] readback_array_c[{{fanin_array_size}}];
|
||||||
for(genvar g=0; g<{{fanin_loop_iter}}; g++) begin
|
for(genvar g=0; g<{{fanin_loop_iter}}; g++) begin
|
||||||
@@ -48,6 +50,7 @@ always_comb begin
|
|||||||
end
|
end
|
||||||
|
|
||||||
{%- else %}
|
{%- else %}
|
||||||
|
|
||||||
// Reduce the array
|
// Reduce the array
|
||||||
always_comb begin
|
always_comb begin
|
||||||
automatic logic [{{cpuif.data_width-1}}:0] readback_data_var;
|
automatic logic [{{cpuif.data_width-1}}:0] readback_data_var;
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ class DesignScanner(RDLListener):
|
|||||||
self.out_of_hier_signals = OrderedDict() # type: OrderedDict[str, SignalNode]
|
self.out_of_hier_signals = OrderedDict() # type: OrderedDict[str, SignalNode]
|
||||||
|
|
||||||
self.has_writable_msb0_fields = False
|
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:
|
def _get_out_of_hier_field_reset(self) -> None:
|
||||||
current_node = self.exp.top_node.parent
|
current_node = self.exp.top_node.parent
|
||||||
@@ -69,19 +71,7 @@ class DesignScanner(RDLListener):
|
|||||||
if node.external and (node != self.exp.top_node):
|
if node.external and (node != self.exp.top_node):
|
||||||
# Do not inspect external components. None of my business
|
# Do not inspect external components. None of my business
|
||||||
return WalkerAction.SkipDescendants
|
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
|
# Collect any signals that are referenced by a property
|
||||||
for prop_name in node.list_properties():
|
for prop_name in node.list_properties():
|
||||||
value = node.get_property(prop_name)
|
value = node.get_property(prop_name)
|
||||||
@@ -93,5 +83,21 @@ class DesignScanner(RDLListener):
|
|||||||
else:
|
else:
|
||||||
self.in_hier_signal_paths.add(path)
|
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):
|
if node.is_sw_writable and (node.msb < node.lsb):
|
||||||
self.has_writable_msb0_fields = True
|
self.has_writable_msb0_fields = True
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ from typing import Any
|
|||||||
from systemrdl.udp import UDPDefinition
|
from systemrdl.udp import UDPDefinition
|
||||||
from systemrdl.component import Reg
|
from systemrdl.component import Reg
|
||||||
from systemrdl.rdltypes.references import RefType, PropertyReference
|
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):
|
class xBufferTrigger(UDPDefinition):
|
||||||
@@ -13,7 +14,12 @@ class xBufferTrigger(UDPDefinition):
|
|||||||
def validate(self, node: Node, value: Any) -> None:
|
def validate(self, node: Node, value: Any) -> None:
|
||||||
# TODO: Reference shall not cross an internal/external boundary
|
# 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
|
# Trigger can reference a vector, but only if it is a single-bit
|
||||||
if value.width != 1:
|
if value.width != 1:
|
||||||
self.msg.error(
|
self.msg.error(
|
||||||
@@ -24,6 +30,14 @@ class xBufferTrigger(UDPDefinition):
|
|||||||
),
|
),
|
||||||
self.get_src_ref(node)
|
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:
|
elif isinstance(value, PropertyReference) and value.width is not None:
|
||||||
# Trigger can reference a property, but only if it is a single-bit
|
# Trigger can reference a property, but only if it is a single-bit
|
||||||
if value.width != 1:
|
if value.width != 1:
|
||||||
@@ -54,7 +68,6 @@ class BufferWrites(UDPDefinition):
|
|||||||
name = "buffer_writes"
|
name = "buffer_writes"
|
||||||
valid_components = {Reg}
|
valid_components = {Reg}
|
||||||
valid_type = bool
|
valid_type = bool
|
||||||
default_assignment = True
|
|
||||||
|
|
||||||
def validate(self, node: 'Node', value: Any) -> None:
|
def validate(self, node: 'Node', value: Any) -> None:
|
||||||
assert isinstance(node, RegNode)
|
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.",
|
"'buffer_writes' is set to true, but this register does not contain any writable fields.",
|
||||||
self.get_src_ref(node)
|
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:
|
def get_unassigned_default(self, node: 'Node') -> Any:
|
||||||
return False
|
return False
|
||||||
@@ -84,7 +96,6 @@ class BufferReads(UDPDefinition):
|
|||||||
name = "buffer_reads"
|
name = "buffer_reads"
|
||||||
valid_components = {Reg}
|
valid_components = {Reg}
|
||||||
valid_type = bool
|
valid_type = bool
|
||||||
default_assignment = True
|
|
||||||
|
|
||||||
def validate(self, node: 'Node', value: Any) -> None:
|
def validate(self, node: 'Node', value: Any) -> None:
|
||||||
assert isinstance(node, RegNode)
|
assert isinstance(node, RegNode)
|
||||||
@@ -95,8 +106,6 @@ class BufferReads(UDPDefinition):
|
|||||||
self.get_src_ref(node)
|
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:
|
def get_unassigned_default(self, node: 'Node') -> Any:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|||||||
@@ -77,23 +77,30 @@ class DesignValidator(RDLListener):
|
|||||||
|
|
||||||
|
|
||||||
def enter_Field(self, node: 'FieldNode') -> None:
|
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_accesswidth = node.parent.get_property('accesswidth')
|
||||||
parent_regwidth = node.parent.get_property('regwidth')
|
parent_regwidth = node.parent.get_property('regwidth')
|
||||||
if ((parent_accesswidth < parent_regwidth)
|
if (
|
||||||
and (node.lsb // parent_accesswidth) != (node.msb // parent_accesswidth)
|
(parent_accesswidth < parent_regwidth)
|
||||||
and (node.is_sw_writable or node.get_property('onread') is not None)):
|
and (node.lsb // parent_accesswidth) != (node.msb // parent_accesswidth)
|
||||||
# Field spans across sub-words
|
):
|
||||||
self.msg.error(
|
# field spans multiple sub-words
|
||||||
f"Software-modifiable field '{node.inst_name}' shall not span "
|
if node.is_sw_writable and not node.parent.get_property('buffer_writes'):
|
||||||
"multiple software-accessible subwords.",
|
# ... and is writable without the protection of double-buffering
|
||||||
node.inst.inst_src_ref
|
# Enforce 10.6.1-f
|
||||||
)
|
self.msg.error(
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|||||||
80
src/peakrdl_regblock/write_buffering/__init__.py
Normal file
80
src/peakrdl_regblock/write_buffering/__init__.py
Normal 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)
|
||||||
@@ -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))
|
||||||
32
src/peakrdl_regblock/write_buffering/storage_generator.py
Normal file
32
src/peakrdl_regblock/write_buffering/storage_generator.py
Normal 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")
|
||||||
31
src/peakrdl_regblock/write_buffering/template.sv
Normal file
31
src/peakrdl_regblock/write_buffering/template.sv
Normal 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
|
||||||
@@ -2,6 +2,10 @@
|
|||||||
branch = True
|
branch = True
|
||||||
#relative_files = True
|
#relative_files = True
|
||||||
|
|
||||||
|
omit =
|
||||||
|
# to be covered elsewhere
|
||||||
|
*/__peakrdl__.py
|
||||||
|
|
||||||
[paths]
|
[paths]
|
||||||
source =
|
source =
|
||||||
../src/peakrdl_regblock/
|
../src/peakrdl_regblock/
|
||||||
|
|||||||
@@ -86,8 +86,13 @@ class BaseTestCase(unittest.TestCase):
|
|||||||
rdl_file = glob.glob(os.path.join(this_dir, "*.rdl"))[0]
|
rdl_file = glob.glob(os.path.join(this_dir, "*.rdl"))[0]
|
||||||
|
|
||||||
rdlc = RDLCompiler()
|
rdlc = RDLCompiler()
|
||||||
|
|
||||||
|
# Load the UDPs
|
||||||
for udp in ALL_UDPS:
|
for udp in ALL_UDPS:
|
||||||
rdlc.register_udp(udp)
|
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)
|
rdlc.compile_file(rdl_file)
|
||||||
root = rdlc.elaborate(cls.rdl_elab_target, "regblock", cls.rdl_elab_params)
|
root = rdlc.elaborate(cls.rdl_elab_target, "regblock", cls.rdl_elab_params)
|
||||||
|
|||||||
0
tests/test_write_buffer/__init__.py
Normal file
0
tests/test_write_buffer/__init__.py
Normal file
106
tests/test_write_buffer/regblock.rdl
Normal file
106
tests/test_write_buffer/regblock.rdl
Normal 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;
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
220
tests/test_write_buffer/tb_template.sv
Normal file
220
tests/test_write_buffer/tb_template.sv
Normal 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 %}
|
||||||
5
tests/test_write_buffer/testcase.py
Normal file
5
tests/test_write_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