diff --git a/docs/index.rst b/docs/index.rst index 7713cdc..9f90a74 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -121,4 +121,5 @@ Links :caption: Extended Properties udps/intro + udps/read_buffering udps/write_buffering diff --git a/docs/udps/intro.rst b/docs/udps/intro.rst index c35e710..aa50799 100644 --- a/docs/udps/intro.rst +++ b/docs/udps/intro.rst @@ -8,6 +8,8 @@ the language to be extended using "User Defined Properties" (UDPs). The PeakRDL-regblock tool understands several UDPs that are described in this section. +To enable these UDPs, compile this RDL file prior to the rest of your design: +:download:`regblock_udps.rdl <../../hdl-src/regblock_udps.rdl>`. .. list-table:: Summary of UDPs :header-rows: 1 @@ -17,6 +19,20 @@ section. - Type - Description + * - buffer_reads + - reg + - boolean + - If set, reads from the register are double-buffered. + + See details here: :ref:`read_buffering`. + + * - rbuffer_trigger + - reg + - reference + - Defines the buffered read load trigger. + + See details here: :ref:`read_buffering`. + * - buffer_writes - reg - boolean diff --git a/docs/udps/read_buffering.rst b/docs/udps/read_buffering.rst new file mode 100644 index 0000000..00962aa --- /dev/null +++ b/docs/udps/read_buffering.rst @@ -0,0 +1,158 @@ +.. _read_buffering: + +Read-buffered Registers +======================= + +Read buffering is a mechanism that allows for software accesses to read a +snapshot of one or more registers atomically. When enabled on a register, a +read buffer will latch the state of its fields when triggered such that software +can read a coherent snapshot of one or more registers' value. + +Some examples of when this is useful: + * A wide 64-bit status register needs to be read atomically, but the CPU + interface is only 32-bits. + * Software needs to be able to read the state of multiple registers + atomically. + * A hardware event latches the software-visible state of one or more + registers. + +.. figure:: ../diagrams/rbuf.png + + +Properties +---------- +The behavior of read-buffered registers is defined using the following two +properties: + +.. literalinclude:: ../../hdl-src/regblock_udps.rdl + :lines: 10-18 + +``buffer_reads`` + * Assigned value is a boolean. + * If true, enables double-buffering of software reads of this register. + * The read buffer will load the register's field values when it's trigger + event is asserted. + * Unless specified otherwise, the buffer trigger occurs when the lowest + address of the buffered register is read. + * When read by software the data returned is from the buffer contents, not + directly from the register's fields. + +``rbuffer_trigger`` + * Assigned value is a reference to a register, single-bit field, signal, or + single-bit property. + * Controls when the double-buffer loads the register's field vaues into the + buffer storage element. + * If reference is a single-bit value (signal, field, property reference), + then the assertion of that value triggers the buffer to be evicted. + * Signal references shall have either activehigh/activelow property set to + define the polarity. + * If the reference is a reg, then buffer is loaded when the register's + lowest address is read. + +Other Rules +^^^^^^^^^^^ +* It is an error to set ``buffer_reads`` if the register does not contain any + readable fields +* If ``buffer_reads`` is false, then anything assigned to ``rbuffer_trigger`` + is ignored. +* The buffered register and the trigger reference shall both be within the same + internal device. ie: one cannot be in an external scope with respect to the + other. +* Unless it is a register, the reference assigned to ``rbuffer_trigger`` shall + represent a single bit. +* The software read operation considered to take place when the buffer is loaded + This influences the behavior of properties like ``swmod`` and ``swacc`` - + they are not asserted until the register's fields are actually sampled by the + buffer. +* If a read-buffered register is wide (accesswidth < regwidth) and is its own + trigger, the first sub-word's buffer is bypassed to ensure the first read + operation is atomically coherent with the rest of the sampled register. + + +Examples +-------- +Below are several examples of what you can do with registers that are +read-buffered. + +Wide Atomic Register +^^^^^^^^^^^^^^^^^^^^ +In this example, a wide 64-bit read-clear counter is implemented. +Without read-buffering, it is impossible to coherently read the state of the +counter using a 32-bit CPU interface without risking a discontinuity. With +read-buffering enabled, the read of the lower half of the register will trigger +the upper half's value to be latched. A subsequent software access can then +coherently read the rest of the register's buffered value. + +.. code-block:: systemrdl + :emphasize-lines: 4 + + reg { + regwidth = 64; + accesswidth = 32; + buffer_reads = true; + field { + sw=r; hw=na; + counter; + incr; + } my_counter[63:0] = 0; + }; + + +Atomic Group of Registers +^^^^^^^^^^^^^^^^^^^^^^^^^ +Perhaps you have a group of registers that monitor some rapidly-changing state +within your design. Using the ``rbuffer_trigger`` property, you can define which +reagister read operation triggers the buffered registers' values to be latched. + +.. code-block:: systemrdl + :emphasize-lines: 11-14 + + reg my_status_reg { + field { + sw=r; hw=w; + } value[31:0]; + }; + + my_status_reg status1; + my_status_reg status2; + my_status_reg status3; + + status2->buffer_reads = true; + status2->rbuffer_trigger = status1; + status3->buffer_reads = true; + status3->rbuffer_trigger = status1; + +In this example, when software reads status1, this triggers status2-status3 +registers to latch their values into their respective read buffers. Subsequent +reads to status2 and status3 return the value that these registers contained at +the moment that status1 was read. This makes it possible for software to read +the state of multiple registers atomically. + + +Externally Triggered Register Sampling +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +If needed, an external trigger can be used to load a read buffer. +This can be useful if precise timing of software's view of the register state is +required. + +.. code-block:: systemrdl + :emphasize-lines: 14-15 + + reg my_status_reg { + buffer_reads = true; + field { + sw=r; hw=w; + } value[31:0]; + }; + + my_status_reg status1; + my_status_reg status2; + + signal { + activehigh; + } trigger_signal; + status1->rbuffer_trigger = trigger_signal; + status2->rbuffer_trigger = trigger_signal; + +When ``hwif_in..trigger_signal`` is asserted, the state of registers ``status1`` +and ``status2`` is buffered. diff --git a/docs/udps/write_buffering.rst b/docs/udps/write_buffering.rst index 69fd5a5..51ddd21 100644 --- a/docs/udps/write_buffering.rst +++ b/docs/udps/write_buffering.rst @@ -3,20 +3,18 @@ Write-buffered Registers ======================== -In some situations, your hardware design may requre that fields be -updated atomically (on the same clock cycle), but your cpuif is not wide enough -to do so natively. Using some UDP extensions, this regblock generator implements -a way for you to add write-buffering to specific registers, which allows the -regblock to delay the effect of a software write operation until a defined -trigger event. +In order to support larger software write accesses that are atomic, the +regblock generator understands several UDPs that implement write-buffering to +specific registers. This causes the regblock to delay the effect of a software +write operation until a defined trigger event. Some examples of when this is useful: - * You need to have software update a wide 64-bit register atomically, but - the CPU interface is only 32-bits. - * Software needs to be able to write multiple registers such that the - hardware is updated atomically. - * Software can pre-load one or more registers with their next value, and - trigger the update via an external hardware signal. + * You need to have software update a wide 64-bit register atomically, but + the CPU interface is only 32-bits. + * Software needs to be able to write multiple registers such that the + hardware is updated atomically. + * Software can pre-load one or more registers with their next value, and + trigger the update via an external hardware signal. If a register is write-buffered, a holding buffer stage is inserted between the decode logic and the field logic. This effectively defers any software write @@ -32,43 +30,50 @@ Properties The behavior of write-buffered registers is defined using the following two properties: +.. literalinclude:: ../../hdl-src/regblock_udps.rdl + :lines: 20-28 + ``buffer_writes`` - * Assigned value is a boolean. - * If true, enables double-buffering of writes to this register. - * Any software write operation to a buffered register is held back in a storage element - unique to the register. - * The software write operation is committed to the register once triggered to do so. - * Unless specified otherwise, the buffer trigger occurs when the highest - address of the buffered register is written. + * Assigned value is a boolean. + * If true, enables double-buffering of writes to this register. + * Any software write operation to a buffered register is held back in a + storage element unique to the register. + * The software write operation is committed to the register once triggered + to do so. + * Unless specified otherwise, the buffer trigger occurs when the highest + address of the buffered register is written. ``wbuffer_trigger`` - * Assigned value is a reference to a register, single-bit field, signal, or single-bit property. - * Controls when the double-buffer commits the software write operation to the register's fields. - * If reference is a single-bit value (signal, field, property reference), - then the assertion of that value triggers the buffer to be evicted. - * Signal references shall have either activehigh/activelow property set to define the polarity. - * If the reference is a reg, then buffer is evicted when the register's - highest address is written + * Assigned value is a reference to a register, single-bit field, signal, + or single-bit property. + * Controls when the double-buffer commits the software write operation to + the register's fields. + * If reference is a single-bit value (signal, field, property reference), + then the assertion of that value triggers the buffer to be evicted. + * Signal references shall have either activehigh/activelow property set to + define the polarity. + * If the reference is a reg, then buffer is evicted when the register's + highest address is written. Other Rules ^^^^^^^^^^^ -* It is an error to set ``buffer_writes`` if the register does not contain any - writable fields -* If ``buffer_writes`` is false, then anything assigned to ``wbuffer_trigger`` - is ignored. -* The buffered register and the trigger reference shall both be within the same - internal device. ie: one cannot be in an external scope with respect to the - other. -* Unless it is a register, the reference assigned to ``wbuffer_trigger`` shall - represent a single bit. -* If a buffered register was not written, any trigger events are ignored. -* It is valid for a buffered register to be partially written (either via write - strobes, or partial addressing). -* The software write operation is not considered to take place until the buffer - is evicted by the trigger. This influences the behavior of properties like - ``swmod`` and ``swacc`` - they are not asserted until the register's fields - are actually written by the buffer. +* It is an error to set ``buffer_writes`` if the register does not contain any + writable fields +* If ``buffer_writes`` is false, then anything assigned to ``wbuffer_trigger`` + is ignored. +* The buffered register and the trigger reference shall both be within the + same internal device. ie: one cannot be in an external scope with respect to + the other. +* Unless it is a register, the reference assigned to ``wbuffer_trigger`` shall + represent a single bit. +* If a buffered register was not written, any trigger events are ignored. +* It is valid for a buffered register to be partially written (either via + write strobes, or partial addressing). +* The software write operation is not considered to take place until the + buffer is evicted by the trigger. This influences the behavior of properties + like ``swmod`` and ``swacc`` - they are not asserted until the register's + fields are actually written by the buffer. @@ -105,8 +110,8 @@ Atomic Group of Registers ^^^^^^^^^^^^^^^^^^^^^^^^^ Perhaps you have a group of registers that need their state to be updated -atomically. Using the ``wbuffer_trigger`` property, you can define which register -write operation triggers the group to be updated. +atomically. Using the ``wbuffer_trigger`` property, you can define which +register write operation triggers the group to be updated. .. code-block:: systemrdl @@ -136,17 +141,18 @@ write operation triggers the group to be updated. In this example software may pre-write information into reg1-reg3, but the register write operations do not take effect until software also writes to reg4. -The write operation to reg4 triggers the buffered data to be committed to reg1-reg3. -This is guaranteed to occur on the same clock-cycle. +The write operation to reg4 triggers the buffered data to be committed to +reg1-reg3. This is guaranteed to occur on the same clock-cycle. Externally Triggered Register Update ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Some applications may require precise timing for when a register (or group of registers) -update their value. Often software cannot offer such timing precision. +Some applications may require precise timing for when a register (or group of +registers) update their value. Often software cannot offer such timing +precision. -In this example, the trigger event is bound to an external signal. When asserted, -any pending write operation the buffered register will be committed. +In this example, the trigger event is bound to an external signal. When +asserted, any pending write operation the buffered register will be committed. The hwif_out value presents the new register state on the clock cycle after the trigger is asserted. @@ -169,5 +175,5 @@ trigger is asserted. reg1->wbuffer_trigger = trigger_signal; reg2->wbuffer_trigger = trigger_signal; -After software writes to ``reg1`` & ``reg2``, the written data is held back in the write -buffer until ``hwif_in..trigger_signal`` is asserted by the hardware. +After software writes to ``reg1`` & ``reg2``, the written data is held back in +the write buffer until ``hwif_in..trigger_signal`` is asserted by the hardware. diff --git a/hdl-src/regblock_udps.rdl b/hdl-src/regblock_udps.rdl index a85d9cd..4d7cc0d 100644 --- a/hdl-src/regblock_udps.rdl +++ b/hdl-src/regblock_udps.rdl @@ -7,6 +7,16 @@ * For more details, see: https://peakrdl-regblock.readthedocs.io/en/latest/udps/intro.html */ +property buffer_reads { + component = reg; + type = boolean; +}; + +property rbuffer_trigger { + component = reg; + type = ref; +}; + property buffer_writes { component = reg; type = boolean; diff --git a/src/peakrdl_regblock/exporter.py b/src/peakrdl_regblock/exporter.py index 78152e0..c03e137 100644 --- a/src/peakrdl_regblock/exporter.py +++ b/src/peakrdl_regblock/exporter.py @@ -17,6 +17,7 @@ from .cpuif import CpuifBase from .cpuif.apb4 import APB4_Cpuif from .hwif import Hwif from .write_buffering import WriteBuffering +from .read_buffering import ReadBuffering class RegblockExporter: def __init__(self, **kwargs: Any) -> None: @@ -31,7 +32,8 @@ class RegblockExporter: self.address_decode = AddressDecode(self) self.field_logic = FieldLogic(self) self.readback = None # type: Readback - self.write_buffering = None # type: WriteBuffering + self.write_buffering = WriteBuffering(self) + self.read_buffering = ReadBuffering(self) self.dereferencer = Dereferencer(self) self.min_read_latency = 0 self.min_write_latency = 0 @@ -145,7 +147,6 @@ class RegblockExporter: self, retime_read_fanin ) - self.write_buffering = WriteBuffering(self) # Validate that there are no unsupported constructs validator = DesignValidator(self) @@ -161,6 +162,7 @@ class RegblockExporter: "cpuif": self.cpuif, "hwif": self.hwif, "write_buffering": self.write_buffering, + "read_buffering": self.read_buffering, "get_resetsignal": self.dereferencer.get_resetsignal, "address_decode": self.address_decode, "field_logic": self.field_logic, diff --git a/src/peakrdl_regblock/field_logic/__init__.py b/src/peakrdl_regblock/field_logic/__init__.py index 87afb80..346b74d 100644 --- a/src/peakrdl_regblock/field_logic/__init__.py +++ b/src/peakrdl_regblock/field_logic/__init__.py @@ -175,8 +175,13 @@ class FieldLogic: """ Asserted when field is software accessed (read) """ - strb = self.exp.dereferencer.get_access_strobe(field) - return f"{strb} && !decoded_req_is_wr" + buffer_reads = field.parent.get_property('buffer_reads') + if buffer_reads: + rstrb = self.exp.read_buffering.get_trigger(field.parent) + return rstrb + else: + strb = self.exp.dereferencer.get_access_strobe(field) + return f"{strb} && !decoded_req_is_wr" def get_swmod_identifier(self, field: 'FieldNode') -> str: """ @@ -186,6 +191,7 @@ class FieldLogic: w_modifiable = field.is_sw_writable r_modifiable = (field.get_property('onread') is not None) buffer_writes = field.parent.get_property('buffer_writes') + buffer_reads = field.parent.get_property('buffer_reads') if w_modifiable and not r_modifiable: # assert swmod only on sw write @@ -200,10 +206,18 @@ class FieldLogic: if w_modifiable and r_modifiable: # assert swmod on both sw read and write - if buffer_writes: - astrb = self.exp.dereferencer.get_access_strobe(field) - wstrb = self.exp.write_buffering.get_write_strobe(field) - rstrb = f"{astrb} && !decoded_req_is_wr" + astrb = self.exp.dereferencer.get_access_strobe(field) + if buffer_writes or buffer_reads: + if buffer_reads: + rstrb = self.exp.read_buffering.get_trigger(field.parent) + else: + rstrb = f"{astrb} && !decoded_req_is_wr" + + if buffer_writes: + wstrb = self.exp.write_buffering.get_write_strobe(field) + else: + wstrb = f"{astrb} && decoded_req_is_wr" + return f"{wstrb} || {rstrb}" else: # Unbuffered. Use decoder strobe directly @@ -213,7 +227,11 @@ class FieldLogic: if not w_modifiable and r_modifiable: # assert swmod only on sw read astrb = self.exp.dereferencer.get_access_strobe(field) - return f"{astrb} && !decoded_req_is_wr" + if buffer_reads: + rstrb = self.exp.read_buffering.get_trigger(field.parent) + else: + rstrb = f"{astrb} && !decoded_req_is_wr" + return rstrb # Not sw modifiable return "1'b0" diff --git a/src/peakrdl_regblock/field_logic/sw_onread.py b/src/peakrdl_regblock/field_logic/sw_onread.py index 86ba1e8..195cdd9 100644 --- a/src/peakrdl_regblock/field_logic/sw_onread.py +++ b/src/peakrdl_regblock/field_logic/sw_onread.py @@ -13,8 +13,14 @@ class _OnRead(NextStateConditional): return field.get_property('onread') == self.onreadtype def get_predicate(self, field: 'FieldNode') -> str: - strb = self.exp.dereferencer.get_access_strobe(field) - return f"{strb} && !decoded_req_is_wr" + if field.parent.get_property('buffer_reads'): + # Is buffered read. Use alternate strobe + rstrb = self.exp.read_buffering.get_trigger(field.parent) + return rstrb + else: + # is regular register + strb = self.exp.dereferencer.get_access_strobe(field) + return f"{strb} && !decoded_req_is_wr" class ClearOnRead(_OnRead): diff --git a/src/peakrdl_regblock/module_tmpl.sv b/src/peakrdl_regblock/module_tmpl.sv index 3c0207b..feee65c 100644 --- a/src/peakrdl_regblock/module_tmpl.sv +++ b/src/peakrdl_regblock/module_tmpl.sv @@ -109,6 +109,7 @@ module {{module_name}} ( assign cpuif_wr_err = '0; {%- if has_buffered_write_regs %} + //-------------------------------------------------------------------------- // Write double-buffers //-------------------------------------------------------------------------- @@ -116,7 +117,6 @@ module {{module_name}} ( {{write_buffering.get_implementation()|indent}} {%- endif %} - //-------------------------------------------------------------------------- // Field logic //-------------------------------------------------------------------------- @@ -126,6 +126,15 @@ module {{module_name}} ( {{field_logic.get_implementation()|indent}} +{%- if has_buffered_read_regs %} + + //-------------------------------------------------------------------------- + // Read double-buffers + //-------------------------------------------------------------------------- + {{read_buffering.get_storage_struct()|indent}} + + {{read_buffering.get_implementation()|indent}} +{%- endif %} //-------------------------------------------------------------------------- // Readback //-------------------------------------------------------------------------- diff --git a/src/peakrdl_regblock/read_buffering/__init__.py b/src/peakrdl_regblock/read_buffering/__init__.py new file mode 100644 index 0000000..396746c --- /dev/null +++ b/src/peakrdl_regblock/read_buffering/__init__.py @@ -0,0 +1,58 @@ +from typing import TYPE_CHECKING + +from systemrdl.node import AddrmapNode, RegNode, FieldNode, SignalNode + +from .storage_generator import RBufStorageStructGenerator +from .implementation_generator import RBufLogicGenerator +from ..utils import get_indexed_path + +if TYPE_CHECKING: + from ..exporter import RegblockExporter + + +class ReadBuffering: + def __init__(self, exp:'RegblockExporter'): + self.exp = exp + + @property + def top_node(self) -> 'AddrmapNode': + return self.exp.top_node + + def get_storage_struct(self) -> str: + struct_gen = RBufStorageStructGenerator() + s = struct_gen.get_struct(self.top_node, "rbuf_storage_t") + assert s is not None + return s + "\nrbuf_storage_t rbuf_storage;" + + def get_implementation(self) -> str: + gen = RBufLogicGenerator(self) + s = gen.get_content(self.top_node) + assert s is not None + return s + + def get_trigger(self, node: RegNode) -> str: + trigger = node.get_property('rbuffer_trigger') + + if isinstance(trigger, RegNode): + # Trigger is a register. + # trigger when lowermost address of the register is written + regwidth = node.get_property('regwidth') + accesswidth = node.get_property('accesswidth') + strb_prefix = self.exp.dereferencer.get_access_strobe(trigger, reduce_substrobes=False) + + if accesswidth < regwidth: + return f"{strb_prefix}[0] && !decoded_req_is_wr" + else: + return f"{strb_prefix} && !decoded_req_is_wr" + elif isinstance(trigger, SignalNode): + s = self.exp.dereferencer.get_value(trigger) + if trigger.get_property('activehigh'): + return s + else: + return f"~{s}" + else: + # Trigger is a field or propref bit + return self.exp.dereferencer.get_value(trigger) + + def get_rbuf_data(self, node: RegNode) -> str: + return "rbuf_storage." + get_indexed_path(self.top_node, node) + ".data" diff --git a/src/peakrdl_regblock/read_buffering/implementation_generator.py b/src/peakrdl_regblock/read_buffering/implementation_generator.py new file mode 100644 index 0000000..8d59cd2 --- /dev/null +++ b/src/peakrdl_regblock/read_buffering/implementation_generator.py @@ -0,0 +1,59 @@ +from typing import TYPE_CHECKING + +from systemrdl.component import Reg +from systemrdl.node import RegNode + +from ..forloop_generator import RDLForLoopGenerator + +if TYPE_CHECKING: + from . import ReadBuffering + +class RBufLogicGenerator(RDLForLoopGenerator): + i_type = "genvar" + def __init__(self, rbuf: 'ReadBuffering') -> None: + super().__init__() + self.rbuf = rbuf + self.exp = rbuf.exp + self.template = self.exp.jj_env.get_template( + "read_buffering/template.sv" + ) + + def enter_Reg(self, node: RegNode) -> None: + super().enter_Reg(node) + assert isinstance(node.inst, Reg) + + if not node.get_property('buffer_reads'): + return + + context = { + 'node': node, + 'rbuf': self.rbuf, + 'get_assignments': self.get_assignments, + } + self.add_content(self.template.render(context)) + + + + def get_assignments(self, node: RegNode) -> str: + data = self.rbuf.get_rbuf_data(node) + bidx = 0 + s = [] + for field in node.fields(): + if bidx < field.low: + # zero padding before field + s.append(f"{data}[{field.low-1}:{bidx}] = '0;") + + value = self.exp.dereferencer.get_value(field) + if field.msb < field.lsb: + # Field gets bitswapped since it is in [low:high] orientation + value = f"{{<<{{{value}}}}}" + s.append(f"{data}[{field.high}:{field.low}] = {value};") + + bidx = field.high + 1 + + regwidth = node.get_property('regwidth') + if bidx < regwidth: + # zero padding after last field + s.append(f"{data}[{regwidth-1}:{bidx}] = '0;") + + return "\n".join(s) diff --git a/src/peakrdl_regblock/read_buffering/storage_generator.py b/src/peakrdl_regblock/read_buffering/storage_generator.py new file mode 100644 index 0000000..c6a6d6d --- /dev/null +++ b/src/peakrdl_regblock/read_buffering/storage_generator.py @@ -0,0 +1,18 @@ +from systemrdl.node import FieldNode, RegNode + +from ..struct_generator import RDLStructGenerator + +class RBufStorageStructGenerator(RDLStructGenerator): + + def enter_Field(self, node: FieldNode) -> None: + # suppress parent class's field behavior + pass + + def enter_Reg(self, node: RegNode) -> None: + super().enter_Reg(node) + + if not node.get_property('buffer_reads'): + return + + regwidth = node.get_property('regwidth') + self.add_member("data", regwidth) diff --git a/src/peakrdl_regblock/read_buffering/template.sv b/src/peakrdl_regblock/read_buffering/template.sv new file mode 100644 index 0000000..a97b351 --- /dev/null +++ b/src/peakrdl_regblock/read_buffering/template.sv @@ -0,0 +1,5 @@ +always_ff @(posedge clk) begin + if({{rbuf.get_trigger(node)}}) begin + {{get_assignments(node)|indent(8)}} + end +end diff --git a/src/peakrdl_regblock/readback/generators.py b/src/peakrdl_regblock/readback/generators.py index 2ca0542..7f9e418 100644 --- a/src/peakrdl_regblock/readback/generators.py +++ b/src/peakrdl_regblock/readback/generators.py @@ -1,10 +1,11 @@ from typing import TYPE_CHECKING, List +from systemrdl.node import RegNode + from ..forloop_generator import RDLForLoopGenerator, LoopBody if TYPE_CHECKING: from ..exporter import RegblockExporter - from systemrdl.node import RegNode class ReadbackLoopBody(LoopBody): def __init__(self, dim: int, iterator: str, i_type: str) -> None: @@ -76,19 +77,31 @@ class ReadbackAssignmentGenerator(RDLForLoopGenerator): self.current_offset = start_offset + n_regs * dim - def enter_Reg(self, node: 'RegNode') -> None: + def enter_Reg(self, node: RegNode) -> None: if not node.has_sw_readable: return accesswidth = node.get_property('accesswidth') regwidth = node.get_property('regwidth') - if accesswidth < regwidth: + rbuf = node.get_property('buffer_reads') + if rbuf: + trigger = node.get_property('rbuffer_trigger') + is_own_trigger = (isinstance(trigger, RegNode) and trigger == node) + if is_own_trigger: + if accesswidth < regwidth: + self.process_buffered_reg_with_bypass(node, regwidth, accesswidth) + else: + # bypass cancels out. Behaves like a normal reg + self.process_reg(node) + else: + self.process_buffered_reg(node, regwidth, accesswidth) + elif accesswidth < regwidth: self.process_wide_reg(node, accesswidth) else: self.process_reg(node) - def process_reg(self, node: 'RegNode') -> None: + def process_reg(self, node: RegNode) -> None: current_bit = 0 rd_strb = f"({self.exp.dereferencer.get_access_strobe(node)} && !decoded_req_is_wr)" # Fields are sorted by ascending low bit @@ -100,11 +113,10 @@ class ReadbackAssignmentGenerator(RDLForLoopGenerator): if field.low != current_bit: self.add_content(f"assign readback_array[{self.current_offset_str}][{field.low-1}:{current_bit}] = '0;") + value = self.exp.dereferencer.get_value(field) if field.msb < field.lsb: # Field gets bitswapped since it is in [low:high] orientation - value = f"{{<<{{{self.exp.dereferencer.get_value(field)}}}}}" - else: - value = self.exp.dereferencer.get_value(field) + value = f"{{<<{{{value}}}}}" self.add_content(f"assign readback_array[{self.current_offset_str}][{field.high}:{field.low}] = {rd_strb} ? {value} : '0;") @@ -118,7 +130,98 @@ class ReadbackAssignmentGenerator(RDLForLoopGenerator): self.current_offset += 1 - def process_wide_reg(self, node: 'RegNode', accesswidth: int) -> None: + def process_buffered_reg(self, node: RegNode, regwidth: int, accesswidth: int) -> None: + rbuf = self.exp.read_buffering.get_rbuf_data(node) + + if accesswidth < regwidth: + # Is wide reg + n_subwords = regwidth // accesswidth + astrb = self.exp.dereferencer.get_access_strobe(node, reduce_substrobes=False) + for i in range(n_subwords): + rd_strb = f"({astrb}[{i}] && !decoded_req_is_wr)" + bslice = f"[{(i + 1) * accesswidth - 1}:{i*accesswidth}]" + self.add_content(f"assign readback_array[{self.current_offset_str}] = {rd_strb} ? {rbuf}{bslice} : '0;") + self.current_offset += 1 + + else: + # Is regular reg + rd_strb = f"({self.exp.dereferencer.get_access_strobe(node)} && !decoded_req_is_wr)" + self.add_content(f"assign readback_array[{self.current_offset_str}][{regwidth-1}:0] = {rd_strb} ? {rbuf} : '0;") + + bus_width = self.exp.cpuif.data_width + if regwidth < bus_width: + self.add_content(f"assign readback_array[{self.current_offset_str}][{bus_width-1}:{regwidth}] = '0;") + + self.current_offset += 1 + + + def process_buffered_reg_with_bypass(self, node: RegNode, regwidth: int, accesswidth: int) -> None: + """ + Special case for a buffered register when the register is its own trigger. + First sub-word shall bypass the read buffer and assign directly. + Subsequent subwords assign from the buffer. + Caller guarantees this is a wide reg + """ + astrb = self.exp.dereferencer.get_access_strobe(node, reduce_substrobes=False) + + # Generate assignments for first sub-word + bidx = 0 + rd_strb = f"({astrb}[0] && !decoded_req_is_wr)" + for field in node.fields(): + if not field.is_sw_readable: + continue + + if field.low >= accesswidth: + # field is not in this subword. + break + + if bidx < field.low: + # insert padding before + self.add_content(f"assign readback_array[{self.current_offset_str}][{field.low - 1}:{bidx}] = '0;") + + if field.high >= accesswidth: + # field gets truncated + r_low = field.low + r_high = accesswidth - 1 + f_low = 0 + f_high = accesswidth - 1 - field.low + + if field.msb < field.lsb: + # Field gets bitswapped since it is in [low:high] orientation + # Mirror the low/high indexes + f_low = field.width - 1 - f_low + f_high = field.width - 1 - f_high + f_low, f_high = f_high, f_low + value = f"{{<<{{{self.exp.dereferencer.get_value(field)}[{f_high}:{f_low}]}}}}" + else: + value = self.exp.dereferencer.get_value(field) + f"[{f_high}:{f_low}]" + + self.add_content(f"assign readback_array[{self.current_offset_str}][{r_high}:{r_low}] = {rd_strb} ? {value} : '0;") + bidx = accesswidth + else: + # field fits in subword + value = self.exp.dereferencer.get_value(field) + if field.msb < field.lsb: + # Field gets bitswapped since it is in [low:high] orientation + value = f"{{<<{{{value}}}}}" + self.add_content(f"assign readback_array[{self.current_offset_str}][{field.high}:{field.low}] = {rd_strb} ? {value} : '0;") + bidx = field.high + 1 + + # pad up remainder of subword + if bidx < accesswidth: + self.add_content(f"assign readback_array[{self.current_offset_str}][{accesswidth-1}:{bidx}] = '0;") + self.current_offset += 1 + + # Assign remainder of subwords from read buffer + n_subwords = regwidth // accesswidth + rbuf = self.exp.read_buffering.get_rbuf_data(node) + for i in range(1, n_subwords): + rd_strb = f"({astrb}[{i}] && !decoded_req_is_wr)" + bslice = f"[{(i + 1) * accesswidth - 1}:{i*accesswidth}]" + self.add_content(f"assign readback_array[{self.current_offset_str}] = {rd_strb} ? {rbuf}{bslice} : '0;") + self.current_offset += 1 + + def process_wide_reg(self, node: RegNode, accesswidth: int) -> None: bus_width = self.exp.cpuif.data_width subword_idx = 0 @@ -162,11 +265,10 @@ class ReadbackAssignmentGenerator(RDLForLoopGenerator): low = field.low - accesswidth * subword_idx high = field.high - accesswidth * subword_idx + value = self.exp.dereferencer.get_value(field) if field.msb < field.lsb: # Field gets bitswapped since it is in [low:high] orientation - value = f"{{<<{{{self.exp.dereferencer.get_value(field)}}}}}" - else: - value = self.exp.dereferencer.get_value(field) + value = f"{{<<{{{value}}}}}" self.add_content(f"assign readback_array[{self.current_offset_str}][{high}:{low}] = {rd_strb} ? {value} : '0;") diff --git a/src/peakrdl_regblock/scan_design.py b/src/peakrdl_regblock/scan_design.py index 946334a..9b793b8 100644 --- a/src/peakrdl_regblock/scan_design.py +++ b/src/peakrdl_regblock/scan_design.py @@ -90,8 +90,8 @@ class DesignScanner(RDLListener): accesswidth = node.get_property('accesswidth') self.cpuif_data_width = max(self.cpuif_data_width, accesswidth) - self.has_buffered_write_regs = self.has_buffered_write_regs or node.get_property('buffer_writes') - self.has_buffered_read_regs = self.has_buffered_read_regs or node.get_property('buffer_reads') + self.has_buffered_write_regs = self.has_buffered_write_regs or bool(node.get_property('buffer_writes')) + self.has_buffered_read_regs = self.has_buffered_read_regs or bool(node.get_property('buffer_reads')) def enter_Signal(self, node: 'SignalNode') -> None: if node.get_property('field_reset'): diff --git a/tests/test_read_buffer/__init__.py b/tests/test_read_buffer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_read_buffer/regblock.rdl b/tests/test_read_buffer/regblock.rdl new file mode 100644 index 0000000..60ffd66 --- /dev/null +++ b/tests/test_read_buffer/regblock.rdl @@ -0,0 +1,117 @@ +addrmap top { + default regwidth = 8; + default accesswidth = 8; + default sw=r; + default hw=r; + + signal {} incr_en; + //-------------------------------------------------------------------------- + // Wide registers + //-------------------------------------------------------------------------- + reg { + regwidth = 32; + default counter; + default incr = incr_en; + buffer_reads; + + field {} f1[3] = 0; + field {} f2[3] = 0; + field {} f3[3] = 0; + field {} f4[3] = 0; + field {} f5[3] = 0; + field {} f6[3] = 0; + field {} f7[3] = 0; + field {} f8[3] = 0; + field {} f9[3] = 0; + field {} fa[3] = 0; + } reg1; + + reg { + regwidth = 32; + default counter; + default incr = incr_en; + buffer_reads; + + field {} f1[28:30] = 0; + field {} f2[3] = 0; + field {} f3[3] = 0; + field {} f4[3] = 0; + field {} f5[3] = 0; + field {} f6[3] = 0; + field {} f7[3] = 0; + field {} f8[3] = 0; + field {} f9[3] = 0; + field {} fa[3] = 0; + } reg1_msb0; + + + reg { + regwidth = 32; + default counter; + default incr = incr_en; + default rclr; + buffer_reads; + + field {} f1[4:0] = 0; + field {} f2[14:10] = 0; + field {} f3[26:22] = 0; + field {} f4[31:27] = 0; + } reg2; + + //-------------------------------------------------------------------------- + // Alternate Triggers + //-------------------------------------------------------------------------- + reg myreg { + buffer_reads; + default counter; + default incr = incr_en; + field {} f1[7:0] = 0; + }; + + reg myreg_wide { + buffer_reads; + default counter; + default incr = incr_en; + regwidth = 16; + field {} f1[15:0] = 0xAAAA; + }; + + // Trigger via another register + myreg g1_r1; + myreg g1_r2; + g1_r2->rbuffer_trigger = g1_r1; + + myreg_wide g2_r1 @ 0x10; + myreg_wide g2_r2; + g2_r2->rbuffer_trigger = g2_r1; + + // triger from signal + signal { + activehigh; + } trigger_sig; + signal { + activelow; + } trigger_sig_n; + reg ro_reg { + buffer_reads; + field { + hw=w; + } f1[7:0]; + }; + ro_reg g3_r1; + ro_reg g3_r2; + g3_r1->rbuffer_trigger = trigger_sig; + g3_r2->rbuffer_trigger = trigger_sig_n; + + // trigger from field/propref + reg { + field { + sw=w; hw=r; singlepulse; + } trig = 0; + } g4_trig; + myreg g4_r1; + myreg g4_r2; + g4_r1->rbuffer_trigger = g4_trig.trig; + g4_r2->rbuffer_trigger = g4_trig.trig->swmod; + +}; diff --git a/tests/test_read_buffer/tb_template.sv b/tests/test_read_buffer/tb_template.sv new file mode 100644 index 0000000..b6b91d7 --- /dev/null +++ b/tests/test_read_buffer/tb_template.sv @@ -0,0 +1,146 @@ +{% extends "lib/tb_base.sv" %} + +{% block seq %} + {% sv_line_anchor %} + cb.hwif_in.incr_en <= '1; + cb.hwif_in.trigger_sig_n <= '1; + ##1; + cb.rst <= '0; + ##1; + + //-------------------------------------------------------------------------- + // Wide registers + //-------------------------------------------------------------------------- + + // reg1 + // expect to read all counter values atomically + begin + logic [7:0] subword; + logic [31:0] rdata; + logic [2:0] fdata; + cpuif.read('h0, subword); + fdata = subword[2:0]; + rdata = {10{fdata}}; + assert(subword == rdata[7:0]); + + cpuif.assert_read('h1, rdata[15:8]); + cpuif.assert_read('h2, rdata[23:16]); + cpuif.assert_read('h3, rdata[31:24]); + end + + // reg1_msb0 + // expect to read all counter values atomically + begin + logic [7:0] subword; + logic [31:0] rdata; + logic [2:0] fdata; + cpuif.read('h4, subword); + fdata = subword[3:1]; + rdata = {10{fdata}} << 1; + assert(subword == rdata[7:0]); + + cpuif.assert_read('h5, rdata[15:8]); + cpuif.assert_read('h6, rdata[23:16]); + cpuif.assert_read('h7, rdata[31:24]); + end + + cb.hwif_in.incr_en <= '0; + @cb; + + // check that msb0 ordering is correct + begin + logic [7:0] subword; + logic [31:0] rdata; + logic [2:0] fdata; + cpuif.read('h0, subword); + fdata = subword[2:0]; + rdata = {10{fdata}}; + assert(subword == rdata[7:0]); + + cpuif.assert_read('h1, rdata[15:8]); + cpuif.assert_read('h2, rdata[23:16]); + cpuif.assert_read('h3, rdata[31:24]); + + + fdata = {<<{fdata}}; + rdata = {10{fdata}} << 1; + cpuif.assert_read('h4, rdata[7:0]); + cpuif.assert_read('h5, rdata[15:8]); + cpuif.assert_read('h6, rdata[23:16]); + cpuif.assert_read('h7, rdata[31:24]); + end + + cb.hwif_in.incr_en <= '1; + + // reg2 + // read-clear + repeat(2) begin + logic [7:0] subword; + logic [4:0] fdata; + logic [31:0] rdata; + + cpuif.read('h8, subword); + rdata[7:0] = subword; + cpuif.read('h9, subword); + rdata[15:8] = subword; + cpuif.read('hA, subword); + rdata[23:16] = subword; + cpuif.read('hB, subword); + rdata[31:24] = subword; + + fdata = rdata[4:0]; + assert(rdata[14:10] == fdata); + assert(rdata[26:22] == fdata); + assert(rdata[31:27] == fdata); + end + + + //-------------------------------------------------------------------------- + // Alternate Triggers + //-------------------------------------------------------------------------- + + // Trigger via another register + // g1 + begin + logic [7:0] rdata; + cpuif.read('hC, rdata); + cpuif.assert_read('hD, rdata); + end + + // g2 + begin + logic [7:0] rdata1; + logic [7:0] rdata2; + cpuif.read('h10, rdata1); + cpuif.read('h11, rdata2); + cpuif.assert_read('h12, rdata1); + cpuif.assert_read('h13, rdata2); + end + + // triger from signal + // g3 + cb.hwif_in.g3_r1.f1.next <= 'hAB; + cb.hwif_in.g3_r2.f1.next <= 'hCD; + cb.hwif_in.trigger_sig <= '1; + cb.hwif_in.trigger_sig_n <= '0; + @cb; + cb.hwif_in.g3_r1.f1.next <= 'h00; + cb.hwif_in.g3_r2.f1.next <= 'h00; + cb.hwif_in.trigger_sig <= '0; + cb.hwif_in.trigger_sig_n <= '1; + @cb; + cpuif.assert_read('h14, 'hAB); + cpuif.assert_read('h15, 'hCD); + + // trigger from field/propref + // g4 + begin + logic [7:0] rdata; + cpuif.write('h16, 'h1); + repeat(5) @cb; + cpuif.read('h17, rdata); + repeat(5) @cb; + cpuif.assert_read('h18, rdata - 1); // swmod happens one cycle earlier, so count is -1 + end + +{% endblock %} diff --git a/tests/test_read_buffer/testcase.py b/tests/test_read_buffer/testcase.py new file mode 100644 index 0000000..835b5ef --- /dev/null +++ b/tests/test_read_buffer/testcase.py @@ -0,0 +1,5 @@ +from ..lib.sim_testcase import SimTestCase + +class Test(SimTestCase): + def test_dut(self): + self.run_test()