diff --git a/docs/diagrams/diagrams.odg b/docs/diagrams/diagrams.odg index fdc1d82..5b42eb6 100644 Binary files a/docs/diagrams/diagrams.odg and b/docs/diagrams/diagrams.odg differ diff --git a/docs/diagrams/rbuf.png b/docs/diagrams/rbuf.png new file mode 100644 index 0000000..34d7777 Binary files /dev/null and b/docs/diagrams/rbuf.png differ diff --git a/docs/diagrams/wbuf.png b/docs/diagrams/wbuf.png new file mode 100644 index 0000000..d4e5e54 Binary files /dev/null and b/docs/diagrams/wbuf.png differ diff --git a/docs/index.rst b/docs/index.rst index 60b2da1..7713cdc 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -115,3 +115,10 @@ Links props/addrmap props/signal props/rhs_props + +.. toctree:: + :hidden: + :caption: Extended Properties + + udps/intro + udps/write_buffering diff --git a/docs/props/field.rst b/docs/props/field.rst index 7e3fbf3..7010f3d 100644 --- a/docs/props/field.rst +++ b/docs/props/field.rst @@ -60,7 +60,9 @@ swmod |OK| If true, infers an output signal ``hwif_out..swmod`` that is asserted as the -field is being modified by software. +field is being modified by software. This can be due to a software write +operation, or a software read operation that has clear/set side-effects. + .. wavedrom:: diff --git a/docs/udps/intro.rst b/docs/udps/intro.rst new file mode 100644 index 0000000..c35e710 --- /dev/null +++ b/docs/udps/intro.rst @@ -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`. diff --git a/docs/udps/write_buffering.rst b/docs/udps/write_buffering.rst new file mode 100644 index 0000000..69fd5a5 --- /dev/null +++ b/docs/udps/write_buffering.rst @@ -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. diff --git a/hdl-src/regblock_udps.rdl b/hdl-src/regblock_udps.rdl new file mode 100644 index 0000000..a85d9cd --- /dev/null +++ b/hdl-src/regblock_udps.rdl @@ -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; +}; diff --git a/setup.py b/setup.py index eaec68a..d91cb73 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ setuptools.setup( include_package_data=True, python_requires='>=3.6', install_requires=[ - "systemrdl-compiler>=1.25.0", + "systemrdl-compiler>=1.25.1", "Jinja2>=2.11", ], entry_points = { @@ -42,6 +42,7 @@ setuptools.setup( "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3 :: Only", "Intended Audience :: Developers", "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", diff --git a/src/peakrdl_regblock/addr_decode.py b/src/peakrdl_regblock/addr_decode.py index 79eb656..48361d3 100644 --- a/src/peakrdl_regblock/addr_decode.py +++ b/src/peakrdl_regblock/addr_decode.py @@ -120,6 +120,7 @@ class DecodeLogicGenerator(RDLForLoopGenerator): s = f"{self.addr_decode.get_access_strobe(node)} = cpuif_req_masked & (cpuif_addr == {self._get_address_str(node)});" self.add_content(s) else: + # Register is wide. Create a substrobe for each subword n_subwords = regwidth // accesswidth subword_stride = accesswidth // 8 for i in range(n_subwords): diff --git a/src/peakrdl_regblock/exporter.py b/src/peakrdl_regblock/exporter.py index 76a07cc..78152e0 100644 --- a/src/peakrdl_regblock/exporter.py +++ b/src/peakrdl_regblock/exporter.py @@ -10,12 +10,13 @@ from .dereferencer import Dereferencer from .readback import Readback from .identifier_filter import kw_filter as kwf -from .cpuif import CpuifBase -from .cpuif.apb4 import APB4_Cpuif -from .hwif import Hwif from .utils import get_always_ff_event from .scan_design import DesignScanner from .validate_design import DesignValidator +from .cpuif import CpuifBase +from .cpuif.apb4 import APB4_Cpuif +from .hwif import Hwif +from .write_buffering import WriteBuffering class RegblockExporter: def __init__(self, **kwargs: Any) -> None: @@ -30,6 +31,7 @@ class RegblockExporter: self.address_decode = AddressDecode(self) self.field_logic = FieldLogic(self) self.readback = None # type: Readback + self.write_buffering = None # type: WriteBuffering self.dereferencer = Dereferencer(self) self.min_read_latency = 0 self.min_write_latency = 0 @@ -143,6 +145,7 @@ class RegblockExporter: self, retime_read_fanin ) + self.write_buffering = WriteBuffering(self) # Validate that there are no unsupported constructs validator = DesignValidator(self) @@ -153,8 +156,11 @@ class RegblockExporter: "module_name": module_name, "user_out_of_hier_signals": scanner.out_of_hier_signals.values(), "has_writable_msb0_fields": scanner.has_writable_msb0_fields, + "has_buffered_write_regs": scanner.has_buffered_write_regs, + "has_buffered_read_regs": scanner.has_buffered_read_regs, "cpuif": self.cpuif, "hwif": self.hwif, + "write_buffering": self.write_buffering, "get_resetsignal": self.dereferencer.get_resetsignal, "address_decode": self.address_decode, "field_logic": self.field_logic, diff --git a/src/peakrdl_regblock/field_logic/__init__.py b/src/peakrdl_regblock/field_logic/__init__.py index 38da2a4..87afb80 100644 --- a/src/peakrdl_regblock/field_logic/__init__.py +++ b/src/peakrdl_regblock/field_logic/__init__.py @@ -1,7 +1,6 @@ from typing import TYPE_CHECKING -from systemrdl.rdltypes import PropertyReference, PrecedenceType, InterruptType -from systemrdl.node import Node +from systemrdl.rdltypes import PrecedenceType, InterruptType from .bases import AssignmentPrecedence, NextStateConditional from . import sw_onread @@ -186,19 +185,35 @@ class FieldLogic: """ w_modifiable = field.is_sw_writable r_modifiable = (field.get_property('onread') is not None) - strb = self.exp.dereferencer.get_access_strobe(field) + buffer_writes = field.parent.get_property('buffer_writes') if w_modifiable and not r_modifiable: # assert swmod only on sw write - return f"{strb} && decoded_req_is_wr" + if buffer_writes: + # Write strobe arrives from buffer layer instead + wstrb = self.exp.write_buffering.get_write_strobe(field) + return wstrb + else: + # Unbuffered. Use decoder strobe directly + astrb = self.exp.dereferencer.get_access_strobe(field) + return f"{astrb} && decoded_req_is_wr" if w_modifiable and r_modifiable: - # assert swmod on all sw actions - return strb + # assert swmod on both sw read and write + if buffer_writes: + astrb = self.exp.dereferencer.get_access_strobe(field) + wstrb = self.exp.write_buffering.get_write_strobe(field) + rstrb = f"{astrb} && !decoded_req_is_wr" + return f"{wstrb} || {rstrb}" + else: + # Unbuffered. Use decoder strobe directly + astrb = self.exp.dereferencer.get_access_strobe(field) + return astrb if not w_modifiable and r_modifiable: # assert swmod only on sw read - return f"{strb} && !decoded_req_is_wr" + astrb = self.exp.dereferencer.get_access_strobe(field) + return f"{astrb} && !decoded_req_is_wr" # Not sw modifiable return "1'b0" diff --git a/src/peakrdl_regblock/field_logic/generators.py b/src/peakrdl_regblock/field_logic/generators.py index a84b70f..a163c10 100644 --- a/src/peakrdl_regblock/field_logic/generators.py +++ b/src/peakrdl_regblock/field_logic/generators.py @@ -85,7 +85,7 @@ class FieldLogicGenerator(RDLForLoopGenerator): super().__init__() self.field_logic = field_logic self.exp = field_logic.exp - self.field_storage_template = self.field_logic.exp.jj_env.get_template( + self.field_storage_template = self.exp.jj_env.get_template( "field_logic/templates/field_storage.sv" ) self.intr_fields = [] # type: List[FieldNode] diff --git a/src/peakrdl_regblock/field_logic/sw_onwrite.py b/src/peakrdl_regblock/field_logic/sw_onwrite.py index df2ad3d..1a22ffa 100644 --- a/src/peakrdl_regblock/field_logic/sw_onwrite.py +++ b/src/peakrdl_regblock/field_logic/sw_onwrite.py @@ -15,60 +15,104 @@ class _OnWrite(NextStateConditional): return field.is_sw_writable and field.get_property('onwrite') == self.onwritetype 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'): - # 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}" + if field.get_property('swwe') or field.get_property('swwel'): + # dereferencer will wrap swwel complement if necessary + qualifier = self.exp.dereferencer.get_field_propref_value(field, 'swwe') + return f"{wstrb} && {qualifier}" - return 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 - # 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') + if field.parent.get_property('buffer_writes'): + # register is buffered. + # write buffer is the full width of the register. no need to deal with subwords + high = field.high + low = field.low + if field.msb < field.lsb: + # slice is for an msb0 field. + # mirror it + regwidth = field.parent.get_property('regwidth') + low = regwidth - 1 - low + high = regwidth - 1 - high + low, high = high, low + else: + # Regular non-buffered register + # For normal fields this ends up passing-through the field's low/high + # values unchanged. + # For fields within a wide register (accesswidth < regwidth), low/high + # may be shifted down and clamped depending on which sub-word is being accessed + accesswidth = field.parent.get_property('accesswidth') - # Shift based on subword - high = field.high - (subword_idx * accesswidth) - low = field.low - (subword_idx * accesswidth) + # Shift based on subword + high = field.high - (subword_idx * accesswidth) + low = field.low - (subword_idx * accesswidth) - # clamp to accesswidth - high = max(min(high, accesswidth), 0) - low = max(min(low, accesswidth), 0) + # clamp to accesswidth + high = max(min(high, accesswidth), 0) + low = max(min(low, accesswidth), 0) - if field.msb < field.lsb: - # slice is for an msb0 field. - # mirror it - bus_width = self.exp.cpuif.data_width - low = bus_width - 1 - low - high = bus_width - 1 - high - low, high = high, low + if field.msb < field.lsb: + # slice is for an msb0 field. + # mirror it + bus_width = self.exp.cpuif.data_width + low = bus_width - 1 - low + high = bus_width - 1 - high + low, high = high, low return f"[{high}:{low}]" def _wr_data(self, field: 'FieldNode', subword_idx: int=0) -> str: - bslice = self._wbus_bitslice(field, subword_idx) - - if field.msb < field.lsb: - # Field gets bitswapped since it is in [low:high] orientation - value = f"decoded_wr_data_bswap{bslice}" + if field.parent.get_property('buffer_writes'): + # Is buffered. Use value from write buffer + # No need to check msb0 ordering. Bus is pre-swapped, and bitslice + # accounts for it + bslice = self._wbus_bitslice(field) + wbuf_prefix = self.exp.write_buffering.get_wbuf_prefix(field) + return wbuf_prefix + ".data" + bslice else: - value = f"decoded_wr_data{bslice}" - return value + # Regular non-buffered register + bslice = self._wbus_bitslice(field, subword_idx) + + if field.msb < field.lsb: + # Field gets bitswapped since it is in [low:high] orientation + value = "decoded_wr_data_bswap" + bslice + else: + value = "decoded_wr_data" + bslice + return value def _wr_biten(self, field: 'FieldNode', subword_idx: int=0) -> str: - bslice = self._wbus_bitslice(field, subword_idx) - - if field.msb < field.lsb: - # Field gets bitswapped since it is in [low:high] orientation - value = f"decoded_wr_biten_bswap{bslice}" + if field.parent.get_property('buffer_writes'): + # Is buffered. Use value from write buffer + # No need to check msb0 ordering. Bus is pre-swapped, and bitslice + # accounts for it + bslice = self._wbus_bitslice(field) + wbuf_prefix = self.exp.write_buffering.get_wbuf_prefix(field) + return wbuf_prefix + ".biten" + bslice else: - value = f"decoded_wr_biten{bslice}" - return value + # Regular non-buffered register + bslice = self._wbus_bitslice(field, subword_idx) + + if field.msb < field.lsb: + # Field gets bitswapped since it is in [low:high] orientation + value = "decoded_wr_biten_bswap" + bslice + else: + value = "decoded_wr_biten" + bslice + return value def get_assignments(self, field: 'FieldNode') -> List[str]: accesswidth = field.parent.get_property("accesswidth") @@ -92,6 +136,7 @@ class _OnWrite(NextStateConditional): raise NotImplementedError +#------------------------------------------------------------------------------- class WriteOneSet(_OnWrite): comment = "SW write 1 set" onwritetype = OnWriteType.woset diff --git a/src/peakrdl_regblock/module_tmpl.sv b/src/peakrdl_regblock/module_tmpl.sv index c9bd456..3c0207b 100644 --- a/src/peakrdl_regblock/module_tmpl.sv +++ b/src/peakrdl_regblock/module_tmpl.sv @@ -108,6 +108,15 @@ module {{module_name}} ( assign cpuif_wr_ack = decoded_req & decoded_req_is_wr; assign cpuif_wr_err = '0; +{%- if has_buffered_write_regs %} + //-------------------------------------------------------------------------- + // Write double-buffers + //-------------------------------------------------------------------------- + {{write_buffering.get_storage_struct()|indent}} + + {{write_buffering.get_implementation()|indent}} +{%- endif %} + //-------------------------------------------------------------------------- // Field logic //-------------------------------------------------------------------------- @@ -124,7 +133,6 @@ module {{module_name}} ( logic readback_done; logic [{{cpuif.data_width-1}}:0] readback_data; {{readback.get_implementation()|indent}} - {% if retime_read_response %} always_ff {{get_always_ff_event(cpuif.reset)}} begin if({{get_resetsignal(cpuif.reset)}}) begin @@ -141,6 +149,5 @@ module {{module_name}} ( assign cpuif_rd_ack = readback_done; assign cpuif_rd_data = readback_data; assign cpuif_rd_err = readback_err; -{% endif %} - +{%- endif %} endmodule diff --git a/src/peakrdl_regblock/readback/templates/readback.sv b/src/peakrdl_regblock/readback/templates/readback.sv index fc706c8..29ca77d 100644 --- a/src/peakrdl_regblock/readback/templates/readback.sv +++ b/src/peakrdl_regblock/readback/templates/readback.sv @@ -3,7 +3,9 @@ logic [{{cpuif.data_width-1}}:0] readback_array[{{array_size}}]; {{array_assignments}} -{% if do_fanin_stage %} + +{%- if do_fanin_stage %} + // fanin stage logic [{{cpuif.data_width-1}}:0] readback_array_c[{{fanin_array_size}}]; for(genvar g=0; g<{{fanin_loop_iter}}; g++) begin @@ -48,6 +50,7 @@ always_comb begin end {%- else %} + // Reduce the array always_comb begin automatic logic [{{cpuif.data_width-1}}:0] readback_data_var; diff --git a/src/peakrdl_regblock/scan_design.py b/src/peakrdl_regblock/scan_design.py index f2960bf..946334a 100644 --- a/src/peakrdl_regblock/scan_design.py +++ b/src/peakrdl_regblock/scan_design.py @@ -26,6 +26,8 @@ class DesignScanner(RDLListener): self.out_of_hier_signals = OrderedDict() # type: OrderedDict[str, SignalNode] self.has_writable_msb0_fields = False + self.has_buffered_write_regs = False + self.has_buffered_read_regs = False def _get_out_of_hier_field_reset(self) -> None: current_node = self.exp.top_node.parent @@ -69,19 +71,7 @@ class DesignScanner(RDLListener): if node.external and (node != self.exp.top_node): # Do not inspect external components. None of my business return WalkerAction.SkipDescendants - return None - def enter_Reg(self, node: 'RegNode') -> None: - # The CPUIF's bus width is sized according to the largest accesswidth in the design - accesswidth = node.get_property('accesswidth') - self.cpuif_data_width = max(self.cpuif_data_width, accesswidth) - - def enter_Signal(self, node: 'SignalNode') -> None: - if node.get_property('field_reset'): - path = node.get_path() - self.in_hier_signal_paths.add(path) - - def enter_Field(self, node: 'FieldNode') -> None: # Collect any signals that are referenced by a property for prop_name in node.list_properties(): value = node.get_property(prop_name) @@ -93,5 +83,21 @@ class DesignScanner(RDLListener): else: self.in_hier_signal_paths.add(path) + return None + + def enter_Reg(self, node: 'RegNode') -> None: + # The CPUIF's bus width is sized according to the largest accesswidth in the design + accesswidth = node.get_property('accesswidth') + self.cpuif_data_width = max(self.cpuif_data_width, accesswidth) + + self.has_buffered_write_regs = self.has_buffered_write_regs or node.get_property('buffer_writes') + self.has_buffered_read_regs = self.has_buffered_read_regs or node.get_property('buffer_reads') + + def enter_Signal(self, node: 'SignalNode') -> None: + if node.get_property('field_reset'): + path = node.get_path() + self.in_hier_signal_paths.add(path) + + def enter_Field(self, node: 'FieldNode') -> None: if node.is_sw_writable and (node.msb < node.lsb): self.has_writable_msb0_fields = True diff --git a/src/peakrdl_regblock/udps.py b/src/peakrdl_regblock/udps.py index e06de80..dc754a5 100644 --- a/src/peakrdl_regblock/udps.py +++ b/src/peakrdl_regblock/udps.py @@ -3,7 +3,8 @@ from typing import Any from systemrdl.udp import UDPDefinition from systemrdl.component import Reg from systemrdl.rdltypes.references import RefType, PropertyReference -from systemrdl.node import Node, RegNode, VectorNode +from systemrdl.rdltypes import NoValue +from systemrdl.node import Node, RegNode, VectorNode, SignalNode class xBufferTrigger(UDPDefinition): @@ -13,7 +14,12 @@ class xBufferTrigger(UDPDefinition): def validate(self, node: Node, value: Any) -> None: # TODO: Reference shall not cross an internal/external boundary - if isinstance(value, VectorNode): + if value is NoValue: + self.msg.error( + "Double-buffer trigger property is missing a value assignment", + self.get_src_ref(node) + ) + elif isinstance(value, VectorNode): # Trigger can reference a vector, but only if it is a single-bit if value.width != 1: self.msg.error( @@ -24,6 +30,14 @@ class xBufferTrigger(UDPDefinition): ), self.get_src_ref(node) ) + + if isinstance(value, SignalNode): + if not value.get_property('activehigh') and not value.get_property('activelow'): + self.msg.error( + "Trigger was asigned a signal, but it does not specify whether it is activehigh/activelow", + self.get_src_ref(node) + ) + elif isinstance(value, PropertyReference) and value.width is not None: # Trigger can reference a property, but only if it is a single-bit if value.width != 1: @@ -54,7 +68,6 @@ class BufferWrites(UDPDefinition): name = "buffer_writes" valid_components = {Reg} valid_type = bool - default_assignment = True def validate(self, node: 'Node', value: Any) -> None: assert isinstance(node, RegNode) @@ -64,7 +77,6 @@ class BufferWrites(UDPDefinition): "'buffer_writes' is set to true, but this register does not contain any writable fields.", self.get_src_ref(node) ) - # TODO: Should I limit the use of other properties on double-buffered registers? def get_unassigned_default(self, node: 'Node') -> Any: return False @@ -84,7 +96,6 @@ class BufferReads(UDPDefinition): name = "buffer_reads" valid_components = {Reg} valid_type = bool - default_assignment = True def validate(self, node: 'Node', value: Any) -> None: assert isinstance(node, RegNode) @@ -95,8 +106,6 @@ class BufferReads(UDPDefinition): self.get_src_ref(node) ) - # TODO: Should I limit the use of other properties on double-buffered registers? - def get_unassigned_default(self, node: 'Node') -> Any: return False diff --git a/src/peakrdl_regblock/validate_design.py b/src/peakrdl_regblock/validate_design.py index 377d585..94ac16c 100644 --- a/src/peakrdl_regblock/validate_design.py +++ b/src/peakrdl_regblock/validate_design.py @@ -77,23 +77,30 @@ class DesignValidator(RDLListener): def enter_Field(self, node: 'FieldNode') -> None: - # 10.6.1-f: Any field that is software-writable or clear on read shall - # not span multiple software accessible sub-words (e.g., a 64-bit - # register with a 32-bit access width may not have a writable field with - # bits in both the upper and lower half of the register). - # - # Interpreting this further - this rule applies any time a field is - # software-modifiable by any means, including rclr, rset, ruser - # TODO: suppress this check for registers that have the appropriate - # buffer_writes/buffer_reads UDP set parent_accesswidth = node.parent.get_property('accesswidth') parent_regwidth = node.parent.get_property('regwidth') - if ((parent_accesswidth < parent_regwidth) - and (node.lsb // parent_accesswidth) != (node.msb // parent_accesswidth) - and (node.is_sw_writable or node.get_property('onread') is not None)): - # Field spans across sub-words - self.msg.error( - f"Software-modifiable field '{node.inst_name}' shall not span " - "multiple software-accessible subwords.", - node.inst.inst_src_ref - ) + if ( + (parent_accesswidth < parent_regwidth) + and (node.lsb // parent_accesswidth) != (node.msb // parent_accesswidth) + ): + # field spans multiple sub-words + if node.is_sw_writable and not node.parent.get_property('buffer_writes'): + # ... and is writable without the protection of double-buffering + # Enforce 10.6.1-f + self.msg.error( + f"Software-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 + ) diff --git a/src/peakrdl_regblock/write_buffering/__init__.py b/src/peakrdl_regblock/write_buffering/__init__.py new file mode 100644 index 0000000..c71ab48 --- /dev/null +++ b/src/peakrdl_regblock/write_buffering/__init__.py @@ -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) diff --git a/src/peakrdl_regblock/write_buffering/implementation_generator.py b/src/peakrdl_regblock/write_buffering/implementation_generator.py new file mode 100644 index 0000000..808b9d3 --- /dev/null +++ b/src/peakrdl_regblock/write_buffering/implementation_generator.py @@ -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)) diff --git a/src/peakrdl_regblock/write_buffering/storage_generator.py b/src/peakrdl_regblock/write_buffering/storage_generator.py new file mode 100644 index 0000000..6b48472 --- /dev/null +++ b/src/peakrdl_regblock/write_buffering/storage_generator.py @@ -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") diff --git a/src/peakrdl_regblock/write_buffering/template.sv b/src/peakrdl_regblock/write_buffering/template.sv new file mode 100644 index 0000000..b2d01cd --- /dev/null +++ b/src/peakrdl_regblock/write_buffering/template.sv @@ -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 diff --git a/tests/.coveragerc b/tests/.coveragerc index f664bca..05e3012 100644 --- a/tests/.coveragerc +++ b/tests/.coveragerc @@ -2,6 +2,10 @@ branch = True #relative_files = True +omit = + # to be covered elsewhere + */__peakrdl__.py + [paths] source = ../src/peakrdl_regblock/ diff --git a/tests/lib/base_testcase.py b/tests/lib/base_testcase.py index 97582e8..7bb6489 100644 --- a/tests/lib/base_testcase.py +++ b/tests/lib/base_testcase.py @@ -86,8 +86,13 @@ class BaseTestCase(unittest.TestCase): rdl_file = glob.glob(os.path.join(this_dir, "*.rdl"))[0] rdlc = RDLCompiler() + + # Load the UDPs for udp in ALL_UDPS: rdlc.register_udp(udp) + # ... including the definition + udp_file = os.path.join(this_dir, "../../hdl-src/regblock_udps.rdl") + rdlc.compile_file(udp_file) rdlc.compile_file(rdl_file) root = rdlc.elaborate(cls.rdl_elab_target, "regblock", cls.rdl_elab_params) diff --git a/tests/test_write_buffer/__init__.py b/tests/test_write_buffer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_write_buffer/regblock.rdl b/tests/test_write_buffer/regblock.rdl new file mode 100644 index 0000000..ca267bc --- /dev/null +++ b/tests/test_write_buffer/regblock.rdl @@ -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; + + +}; diff --git a/tests/test_write_buffer/tb_template.sv b/tests/test_write_buffer/tb_template.sv new file mode 100644 index 0000000..c202570 --- /dev/null +++ b/tests/test_write_buffer/tb_template.sv @@ -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 %} diff --git a/tests/test_write_buffer/testcase.py b/tests/test_write_buffer/testcase.py new file mode 100644 index 0000000..835b5ef --- /dev/null +++ b/tests/test_write_buffer/testcase.py @@ -0,0 +1,5 @@ +from ..lib.sim_testcase import SimTestCase + +class Test(SimTestCase): + def test_dut(self): + self.run_test()