diff --git a/.gitignore b/.gitignore index e222d71..d8c037d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,10 @@ **/.venv **/.coverage **/*.rpt +**/.pytest_cache +**/_build +**/*.out +**/transcript build/ dist/ diff --git a/README.md b/README.md index fb060c2..c751e28 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,5 @@ -[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/peakrdl-regblock.svg)](https://pypi.org/project/peakrdl-regblock) - # PeakRDL-regblock Generate SystemVerilog RTL that implements a register block from compiled SystemRDL input. ## Installing -Install from [PyPi](https://pypi.org/project/peakrdl-regblock) using pip: - - python3 -m pip install peakrdl-regblock +(Not published to PyPi yet) diff --git a/doc/conf.py b/doc/conf.py index 8704de7..d4eb3e3 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -29,7 +29,9 @@ author = 'Alex Mykyta' # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ + "sphinxcontrib.wavedrom", ] +render_using_wavedrompy = True # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -50,4 +52,26 @@ html_theme = "sphinx_rtd_theme" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = [] + + +rst_epilog = """ +.. |iNO| image:: /img/err.svg + :width: 18px + :class: no-scaled-link + +.. |iEX| image:: /img/warn.svg + :width: 18px + :class: no-scaled-link + +.. |iOK| image:: /img/ok.svg + :width: 18px + :class: no-scaled-link + +.. |NO| replace:: |iNO| Not Supported + +.. |EX| replace:: |iEX| Experimental + +.. |OK| replace:: |iOK| Supported + +""" diff --git a/doc/img/err.svg b/doc/img/err.svg new file mode 100644 index 0000000..6ce297c --- /dev/null +++ b/doc/img/err.svg @@ -0,0 +1,53 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/doc/img/ok.svg b/doc/img/ok.svg new file mode 100644 index 0000000..defc966 --- /dev/null +++ b/doc/img/ok.svg @@ -0,0 +1,53 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/doc/img/warn.svg b/doc/img/warn.svg new file mode 100644 index 0000000..1debe8a --- /dev/null +++ b/doc/img/warn.svg @@ -0,0 +1,53 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/doc/index.rst b/doc/index.rst index d6bcb56..147bc38 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -1,20 +1,14 @@ -.. PeakRDL-regblock documentation master file, created by - sphinx-quickstart on Tue Nov 16 23:25:58 2021. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. Welcome to PeakRDL-regblock's documentation! ============================================ .. toctree:: - :maxdepth: 2 - :caption: Contents: + :hidden: + :caption: Property Support - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` + props/field + props/reg + props/addrmap + props/signal + props/rhs_props + limitations diff --git a/doc/limitations.rst b/doc/limitations.rst new file mode 100644 index 0000000..7543746 --- /dev/null +++ b/doc/limitations.rst @@ -0,0 +1,27 @@ +Known Issues & Limitations +========================== + +Not all SystemRDL features are supported by this exporter. For a listing of +supported properties, see the appropriate property listing page in the previous +sections. + + + +External Components +------------------- +Regfiles, registers & fields instantiated using the ``external`` keyword are not supported yet. + + + +Alias Registers +--------------- +Registers instantiated using the ``alias`` keyword are not supported yet. + + + +Unaligned Registers +------------------- +All address offsets & strides shall be a multiple of the regwidth used. Specifically: + +* Each register's address and array stride shall be aligned to it's regwidth. +* Each regfile or addrmap shall use an offset and stride that is a multiple of the largest regwidth it encloses. diff --git a/doc/logbooks/000-Main-Logbook b/doc/logbooks/000-Main-Logbook index 825a364..a8d60d3 100644 --- a/doc/logbooks/000-Main-Logbook +++ b/doc/logbooks/000-Main-Logbook @@ -76,6 +76,60 @@ Write about this in the SystemRDL errata? Maybe add some words to the "clarifications" section +================================================================================ +Unit Testing +================================================================================ +I NEED to start building a suite of unit-tests! +Goal: +- Small easy-to-understand testcases +- Parameterized testcases to rerun testcases with different cpuifs, etc. +- coverage + +Split it into the following components: + Common testbench SV infrastructure + Make a generic SV framework that can be re-used everywhere + Use SV interfaces/classes and even `include preprocessor tricks to make it possible + to swap out for specific testcases: + - cpuif abstraction layer + Need to be able to swap out to different CPU interfaces easily + - Clocks/resets + - DUT instantiation + Will need to account for minor variations in port list somehow + Maybe a good time to use the .* method? + - Helper functions, assertion library, etc. + + Testcase-specific + SV sequence file that issues transactions and asserts things + +Dispatch tests completely through pytest + - Each testcase has its own folder with: + testcase-specific SV file(s) + RDL file + pytest entry point .py file + - build up py utility functions that will: + Export the testcase-specific RDL --> SV + Compile and run the simulation + need to deal with timeouts if the RTL deadlocks somehow. Limit of how many uS to run? + Query sim result for pass/fail + - Each testcase folder will likely have multiple subtests + - Variations to RDL export: + - different cpuif + - pipe stages + - etc. + - Different test sequences + may be necessary to test the same compilation in different ways + I can imagine it may not be possible to do everything from a single test sequence. + May require the sim to reset to T-0 for fresh-slate. + - Handle these variations using pytest testcases & parameterizations as appropriate. + Possibly something like: + - Each pytest class --> unique compilation/elaboration + pytest parameters to expand this for export variations + - Each pytest class method --> simulation sequence + - Collect coverage! + install the tool in a venv, collect exporter coverage, etc. + TBD if i want to deal with SV coverage (is that even allowed in modelsim free?) + + ================================================================================ Dev Todo list ================================================================================ diff --git a/doc/logbooks/Signal Dereferencer b/doc/logbooks/Signal Dereferencer index 1c626e1..2c427f8 100644 --- a/doc/logbooks/Signal Dereferencer +++ b/doc/logbooks/Signal Dereferencer @@ -4,7 +4,7 @@ to stuff via a normalized interface. For example, if RDL defines: my_field->next = my_other_field Then in Python (or a template) I could do: - x = my_field.get_property("next") + x = my_field.get_property('next') y = dereferencer.get(x) and trust that I'll get a value/identifier/whatever that accurately represents the value being referenced diff --git a/doc/logbooks/Validation Needed b/doc/logbooks/Validation Needed index 53fe48a..d252a31 100644 --- a/doc/logbooks/Validation Needed +++ b/doc/logbooks/Validation Needed @@ -82,7 +82,7 @@ X If a node ispresent=true, and any of it's properties are a reference, then those references' ispresent shall also be true This is an explicit clause in the spec: 5.3.1-i -! Flag illegal sw actions if not readable/writable +X Flag illegal sw actions if not readable/writable The following combinations dont get flagged currently: sw=w; rclr; sw=w; rset; @@ -91,15 +91,15 @@ X If a node ispresent=true, and any of it's properties are a reference, their counterparts do get flagged. such as: sw=w; onread=rclr; +X Signals marked as field_reset or cpuif_reset need to have activehigh/activelow + specified. (8.2.1-d states that activehigh/low does not have an implied default state if unset!) + Also aplies to signals referenced by resetsignal + ! hwclr/hwset/we/wel probably shouldn't be able to reference itself y->hwclr = y; y->we = y; ... it works, but should it be allowed? Seems like user-error -! Signals marked as field_reset or cpuif_reset need to have activehigh/activelow - specified. (8.2.1-d states that activehigh/low does not have an implied default state if unset!) - Also aplies to signals referenced by fieldreset - @@ -126,7 +126,7 @@ X Warn/error on any signal with cpuif_reset set, that is not in the top-level ! async data signals Only supporting async signals if they are exclusively used in resets. - Anyhting else declared as "async" shall be an error + Anyhting else declared as "async" shall emit a warning that it is ignored I have zero interest in implementing resynchronizers ! Error if a property references a non-signal component, or property reference from diff --git a/doc/props/addrmap.rst b/doc/props/addrmap.rst new file mode 100644 index 0000000..41d2964 --- /dev/null +++ b/doc/props/addrmap.rst @@ -0,0 +1,31 @@ +Addrmap/Regfile Properties +========================== + +.. note:: Any properties not explicitly listed here are either implicitly supported, + or are not relevant to the regblock exporter and are ignored. + + +errextbus +--------- +|NO| + +sharedextbus +------------ +|NO| + + + +Addrmap Properties +================== + +bigendian/littleendian +---------------------- +|NO| + +bridge +------ +|NO| + +rsvdset +------- +|NO| diff --git a/doc/props/field.rst b/doc/props/field.rst new file mode 100644 index 0000000..0af7c70 --- /dev/null +++ b/doc/props/field.rst @@ -0,0 +1,276 @@ +Field Properties +================ + +.. note:: Any properties not explicitly listed here are either implicitly supported, + or are not relevant to the regblock exporter and are ignored. + +Software Access Properties +-------------------------- + +onread/onwrite +^^^^^^^^^^^^^^ +|EX| + +rclr/rset +^^^^^^^^^ +See ``onread`` + +singlepulse +^^^^^^^^^^^ +|NO| + +sw +^^^ +|OK| + +swacc +^^^^^ +|EX| + +If true, infers an output signal ``swacc`` that is asserted as the field is sampled for a software read operation. + +.. wavedrom:: + + {signal: [ + {name: 'clk', wave: 'p....'}, + {name: 'hwif_in..value', wave: 'x.=x.', data: ['D']}, + {name: 'hwif_out..swacc', wave: '0.10.'} + ]} + + +swmod +^^^^^ +|EX| + +If true, infers an output signal ``swmod`` that is asserted as the field is being modified by software. + +.. wavedrom:: + + {signal: [ + {name: 'clk', wave: 'p.....'}, + {name: 'hwif_out..value', wave: '=..=..', data: ['old', 'new']}, + {name: 'hwif_out..swmod', wave: '0.10..'} + ]} + + +swwe/swwel +^^^^^^^^^^ + +TODO: Describe result + +boolean + |NO| + +bit + |NO| + +reference + |NO| + +woclr/woset +^^^^^^^^^^^ +See ``onwrite`` + + +Hardware Access Properties +-------------------------- + +anded/ored/xored +^^^^^^^^^^^^^^^^ +|EX| + + +hw +^^^ +|OK| + +hwclr/hwset +^^^^^^^^^^^ +boolean + |EX| + +reference + |EX| + +hwenable/hwmask +^^^^^^^^^^^^^^^ +|EX| + +we/wel +^^^^^^ +Write-enable control from hardware interface + +.. wavedrom:: + + {signal: [ + {name: 'clk', wave: 'p....'}, + {name: 'hwif_in..value', wave: 'x.=x.', data: ['D']}, + {name: 'hwif_in..we', wave: '0.10.',}, + {name: 'hwif_in..wel', wave: '1.01.',}, + {name: '', wave: 'x..=.', data: ['D']} + ]} + +boolean + |OK| + + if set, infers the existence of input signal ``hwif_in..we`` or ``hwif_in..wel`` + +reference + |EX| + + +Counter Properties +------------------ + +counter +^^^^^^^ +|NO| + +decr +^^^^ +reference + |NO| + +decrthreshold +^^^^^^^^^^^^^ +boolean + |NO| + +bit + |NO| + +reference + |NO| + +decrsaturate +^^^^^^^^^^^^ +boolean + |NO| + +bit + |NO| + +reference + |NO| + +decrvalue +^^^^^^^^^ +bit + |NO| + +reference + |NO| + +decrwidth +^^^^^^^^^ +|NO| + +incr +^^^^ +|NO| + +incrsaturate/saturate +^^^^^^^^^^^^^^^^^^^^^ +boolean + |NO| + +bit + |NO| + +reference + |NO| + +incrthreshold/threshold +^^^^^^^^^^^^^^^^^^^^^^^ +boolean + |NO| + +bit + |NO| + +reference + |NO| + +incrvalue +^^^^^^^^^ +bit + |NO| + +reference + |NO| + +incrwidth +^^^^^^^^^ +|NO| + +overflow +^^^^^^^^ +|NO| + +underflow +^^^^^^^^^ +|NO| + + +Interrupt Properties +-------------------- + +enable +^^^^^^ +|NO| + +haltenable +^^^^^^^^^^ +|NO| + +haltmask +^^^^^^^^ +|NO| + +intr +^^^^ +|NO| + +mask +^^^^ +|NO| + +sticky +^^^^^^ +|NO| + +stickybit +^^^^^^^^^ +|NO| + + +Misc +---- + +encode +^^^^^^ +|NO| + +next +^^^^ +|NO| + +paritycheck +^^^^^^^^^^^ +|NO| + +precedence +^^^^^^^^^^ +|EX| + +reset +^^^^^ +bit + |OK| + +reference + |EX| + +resetsignal +^^^^^^^^^^^ +|EX| diff --git a/doc/props/reg.rst b/doc/props/reg.rst new file mode 100644 index 0000000..98e287f --- /dev/null +++ b/doc/props/reg.rst @@ -0,0 +1,19 @@ +Register Properties +=================== + +.. note:: Any properties not explicitly listed here are either implicitly supported, + or are not relevant to the regblock exporter and are ignored. + +accesswidth +----------- +|NO| + +Only ``accesswidth`` that is equal to the ``regwidth`` is supported (default if unset) + +regwidth +-------- +|OK| + +shared +------ +|NO| diff --git a/doc/props/rhs_props.rst b/doc/props/rhs_props.rst new file mode 100644 index 0000000..10a6779 --- /dev/null +++ b/doc/props/rhs_props.rst @@ -0,0 +1,114 @@ +RHS Property References +======================= + +Field +----- + +swacc +^^^^^ +|EX| + +swmod +^^^^^ +|EX| + +swwe/swwel +^^^^^^^^^^ +|EX| + +anded/ored/xored +^^^^^^^^^^^^^^^^ +|EX| + +hwclr/hwset +^^^^^^^^^^^ +|EX| + +hwenable/hwmask +^^^^^^^^^^^^^^^ +|EX| + +we/wel +^^^^^^ +|EX| + +decr +^^^^ +|NO| + +decrthreshold +^^^^^^^^^^^^^ +|NO| + +decrsaturate +^^^^^^^^^^^^ +|NO| + +decrvalue +^^^^^^^^^ +|EX| + +incr +^^^^ +|NO| + +incrsaturate/saturate +^^^^^^^^^^^^^^^^^^^^^ +|NO| + +incrthreshold/threshold +^^^^^^^^^^^^^^^^^^^^^^^ +|NO| + +incrvalue +^^^^^^^^^ +|EX| + +overflow +^^^^^^^^ +|NO| + +underflow +^^^^^^^^^ +|NO| + +enable +^^^^^^ +|EX| + +haltenable +^^^^^^^^^^ +|EX| + +haltmask +^^^^^^^^ +|EX| + +mask +^^^^ +|EX| + +next +^^^^ +|EX| + +reset +^^^^^ +|EX| + +resetsignal +^^^^^^^^^^^ +|EX| + + + +Register +-------- + +intr +^^^^ +|NO| + +halt +^^^^ +|NO| diff --git a/doc/props/signal.rst b/doc/props/signal.rst new file mode 100644 index 0000000..c644f08 --- /dev/null +++ b/doc/props/signal.rst @@ -0,0 +1,25 @@ +Signal Properties +================= + +.. note:: Any properties not explicitly listed here are either implicitly supported, + or are not relevant to the regblock exporter and are ignored. + + +activehigh/activelow +-------------------- +|EX| + +sync/async +---------- +|EX| + +Only supported for signals used as resets to infer edge-sensitive reset. +Ignored in all other contexts. + +cpuif_reset +----------- +|EX| + +field_reset +----------- +|EX| diff --git a/doc/requirements.txt b/doc/requirements.txt index 60ba612..807facd 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1 +1,2 @@ pygments-systemrdl +sphinxcontrib-wavedrom diff --git a/export.py b/export.py deleted file mode 100755 index 3232fd0..0000000 --- a/export.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env python3 -import sys - -from systemrdl import RDLCompiler, RDLCompileError -from peakrdl.regblock import RegblockExporter - -input_files = sys.argv[1:] - -rdlc = RDLCompiler() - -try: - for input_file in input_files: - rdlc.compile_file(input_file) - root = rdlc.elaborate() -except RDLCompileError: - sys.exit(1) - - -R = RegblockExporter() - -R.export(root, ".") diff --git a/peakrdl/regblock/cpuif/apb3/__init__.py b/peakrdl/regblock/cpuif/apb3/__init__.py index 8110a7b..6674d6c 100644 --- a/peakrdl/regblock/cpuif/apb3/__init__.py +++ b/peakrdl/regblock/cpuif/apb3/__init__.py @@ -14,7 +14,6 @@ class APB3_Cpuif(CpuifBase): class APB3_Cpuif_flattened(APB3_Cpuif): @property def port_declaration(self) -> str: - # TODO: Reference data/addr width from verilog parameter perhaps? lines = [ "input wire " + self.signal("psel"), "input wire " + self.signal("penable"), diff --git a/peakrdl/regblock/dereferencer.py b/peakrdl/regblock/dereferencer.py index 49852ec..2fdea7b 100644 --- a/peakrdl/regblock/dereferencer.py +++ b/peakrdl/regblock/dereferencer.py @@ -57,11 +57,11 @@ class Dereferencer: # must be a constant value as defined by its reset value reset_value = obj.get_property('reset') if reset_value is not None: - return f"'h{reset_value:x}" + return self.get_value(reset_value) else: # No reset value defined! - # Fall back to a value of 0 - return "'h0" + # Callers shall ensure this is impossible + raise RuntimeError if isinstance(obj, SignalNode): # Signals are always inputs from the hwif diff --git a/peakrdl/regblock/exporter.py b/peakrdl/regblock/exporter.py index b03ba0e..9a4f191 100644 --- a/peakrdl/regblock/exporter.py +++ b/peakrdl/regblock/exporter.py @@ -72,8 +72,8 @@ class RegblockExporter: package_name = kwargs.pop("package_name", module_name + "_pkg") # Pipelining options - retime_read_response = kwargs.pop("retime_read_response", True) retime_read_fanin = kwargs.pop("retime_read_fanin", False) + retime_read_response = kwargs.pop("retime_read_response", True) # Check for stray kwargs if kwargs: diff --git a/peakrdl/regblock/field_logic/__init__.py b/peakrdl/regblock/field_logic/__init__.py index f8f0e18..463ed6d 100644 --- a/peakrdl/regblock/field_logic/__init__.py +++ b/peakrdl/regblock/field_logic/__init__.py @@ -109,7 +109,7 @@ class FieldLogic: set or clear side effect). """ 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) if w_modifiable and not r_modifiable: diff --git a/peakrdl/regblock/field_logic/generators.py b/peakrdl/regblock/field_logic/generators.py index 8c819e5..c60b624 100644 --- a/peakrdl/regblock/field_logic/generators.py +++ b/peakrdl/regblock/field_logic/generators.py @@ -74,13 +74,13 @@ class FieldLogicGenerator(RDLForLoopGenerator): for signal in conditional.get_extra_combo_signals(node): extra_combo_signals[signal.name] = signal - sig = node.get_property("resetsignal") + sig = node.get_property('resetsignal') if sig is not None: resetsignal = RDLSignal(sig) else: resetsignal = self.exp.default_resetsignal - reset_value = node.get_property("reset") + reset_value = node.get_property('reset') if reset_value is not None: reset_value_str = self.exp.dereferencer.get_value(reset_value) else: @@ -106,38 +106,39 @@ class FieldLogicGenerator(RDLForLoopGenerator): # Field value output if self.exp.hwif.has_value_output(node): output_identifier = self.exp.hwif.get_output_identifier(node) + value = self.exp.dereferencer.get_value(node) self.add_content( - f"assign {output_identifier} = field_storage.{field_path};" + f"assign {output_identifier} = {value};" ) # Inferred logical reduction outputs - if node.get_property("anded"): + if node.get_property('anded'): output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "anded") value = self.exp.dereferencer.get_field_propref_value(node, "anded") self.add_content( f"assign {output_identifier} = {value};" ) - if node.get_property("ored"): + if node.get_property('ored'): output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "ored") value = self.exp.dereferencer.get_field_propref_value(node, "ored") self.add_content( f"assign {output_identifier} = {value};" ) - if node.get_property("xored"): + if node.get_property('xored'): output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "xored") value = self.exp.dereferencer.get_field_propref_value(node, "xored") self.add_content( f"assign {output_identifier} = {value};" ) - if node.get_property("swmod"): + if node.get_property('swmod'): output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "swmod") value = self.field_logic.get_swmod_identifier(node) self.add_content( f"assign {output_identifier} = {value};" ) - if node.get_property("swacc"): + if node.get_property('swacc'): output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "swacc") value = self.field_logic.get_swacc_identifier(node) self.add_content( diff --git a/peakrdl/regblock/field_logic/sw_onread.py b/peakrdl/regblock/field_logic/sw_onread.py index b14b606..02e20b1 100644 --- a/peakrdl/regblock/field_logic/sw_onread.py +++ b/peakrdl/regblock/field_logic/sw_onread.py @@ -10,7 +10,7 @@ if TYPE_CHECKING: class _OnRead(NextStateConditional): onreadtype = None def is_match(self, field: 'FieldNode') -> bool: - return field.get_property("onread") == self.onreadtype + return field.get_property('onread') == self.onreadtype def get_predicate(self, field: 'FieldNode') -> str: strb = self.exp.dereferencer.get_access_strobe(field) diff --git a/peakrdl/regblock/field_logic/sw_onwrite.py b/peakrdl/regblock/field_logic/sw_onwrite.py index 3d675e8..b204ceb 100644 --- a/peakrdl/regblock/field_logic/sw_onwrite.py +++ b/peakrdl/regblock/field_logic/sw_onwrite.py @@ -13,7 +13,7 @@ if TYPE_CHECKING: class _OnWrite(NextStateConditional): onwritetype = None def is_match(self, field: 'FieldNode') -> bool: - return 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: strb = self.exp.dereferencer.get_access_strobe(field) diff --git a/peakrdl/regblock/hwif.py b/peakrdl/regblock/hwif.py index 2e288fa..faafbe0 100644 --- a/peakrdl/regblock/hwif.py +++ b/peakrdl/regblock/hwif.py @@ -80,7 +80,7 @@ class Hwif: empty_array_suffix="x" ) if is_input: - return f'{base}_in_t' + return f'{base}__in_t' return f'{base}__out_t' def _do_struct_addressable(self, lines:list, node:AddressableNode, is_input:bool = True) -> bool: @@ -159,22 +159,22 @@ class Hwif: contents.append(f"logic {prop_name};") # Generate any implied counter inputs - if node.get_property("counter"): - if not node.get_property("incr"): + if node.get_property('counter'): + if not node.get_property('incr'): # User did not provide their own incr component reference. # Imply an input contents.append("logic incr;") - if not node.get_property("decr"): + if not node.get_property('decr'): # User did not provide their own decr component reference. # Imply an input contents.append("logic decr;") - width = node.get_property("incrwidth") + width = node.get_property('incrwidth') if width: # Implies a corresponding incrvalue input contents.append(f"logic [{width-1}:0] incrvalue;") - width = node.get_property("decrwidth") + width = node.get_property('decrwidth') if width: # Implies a corresponding decrvalue input contents.append(f"logic [{width-1}:0] decrvalue;") diff --git a/peakrdl/regblock/readback/generators.py b/peakrdl/regblock/readback/generators.py index 9ccd55b..f772300 100644 --- a/peakrdl/regblock/readback/generators.py +++ b/peakrdl/regblock/readback/generators.py @@ -104,4 +104,4 @@ class ReadbackAssignmentGenerator(RDLForLoopGenerator): super().pop_loop() # Advance current scope's offset to account for loop's contents - self.current_offset += n_regs * dim - 1 + self.current_offset = start_offset + n_regs * dim diff --git a/peakrdl/regblock/readback/templates/readback.sv b/peakrdl/regblock/readback/templates/readback.sv index 2e8a770..fc706c8 100644 --- a/peakrdl/regblock/readback/templates/readback.sv +++ b/peakrdl/regblock/readback/templates/readback.sv @@ -5,10 +5,10 @@ logic [{{cpuif.data_width-1}}:0] readback_array[{{array_size}}]; {% if do_fanin_stage %} // fanin stage -logic [31: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 always_comb begin - automatic logic [31:0] readback_data_var; + automatic logic [{{cpuif.data_width-1}}:0] readback_data_var; readback_data_var = '0; for(int i=g*{{fanin_stride}}; i<((g+1)*{{fanin_stride}}); i++) readback_data_var |= readback_array[i]; readback_array_c[g] = readback_data_var; @@ -18,14 +18,14 @@ end assign readback_array_c[{{fanin_array_size-1}}] = readback_array[{{array_size-1}}]; {%- elif fanin_residual_stride > 1 %} always_comb begin - automatic logic [31:0] readback_data_var; + automatic logic [{{cpuif.data_width-1}}:0] readback_data_var; readback_data_var = '0; - for(int i={{(fanin_array_size-1) * fanin_stride}}; i<{{array_size-1}}; i++) readback_data_var |= readback_array[i]; + for(int i={{(fanin_array_size-1) * fanin_stride}}; i<{{array_size}}; i++) readback_data_var |= readback_array[i]; readback_array_c[{{fanin_array_size-1}}] = readback_data_var; end {%- endif %} -logic [31:0] readback_array_r[{{fanin_array_size}}]; +logic [{{cpuif.data_width-1}}:0] readback_array_r[{{fanin_array_size}}]; logic readback_done_r; always_ff @(posedge clk) begin if(rst) begin @@ -39,8 +39,8 @@ end // Reduce the array always_comb begin - automatic logic [31:0] readback_data_var; - readback_done = decoded_req & ~decoded_req_is_wr; + automatic logic [{{cpuif.data_width-1}}:0] readback_data_var; + readback_done = readback_done_r; readback_err = '0; readback_data_var = '0; for(int i=0; i<{{fanin_array_size}}; i++) readback_data_var |= readback_array_r[i]; diff --git a/peakrdl/regblock/scan_design.py b/peakrdl/regblock/scan_design.py index cde9135..8bb4e1c 100644 --- a/peakrdl/regblock/scan_design.py +++ b/peakrdl/regblock/scan_design.py @@ -22,7 +22,7 @@ class DesignScanner(RDLListener): def enter_Reg(self, node: 'RegNode') -> None: # The CPUIF's bus width is sized according to the largest register in the design - self.cpuif_data_width = max(self.cpuif_data_width, node.get_property("regwidth")) + self.cpuif_data_width = max(self.cpuif_data_width, node.get_property('regwidth')) # TODO: Collect any references to signals that lie outside of the hierarchy # These will be added as top-level signals diff --git a/peakrdl/regblock/signals.py b/peakrdl/regblock/signals.py index 7706074..7abd586 100644 --- a/peakrdl/regblock/signals.py +++ b/peakrdl/regblock/signals.py @@ -50,11 +50,11 @@ class RDLSignal(SignalBase): @property def is_async(self) -> bool: - return self.rdl_signal.get_property("async") + return self.rdl_signal.get_property('async') @property def is_activehigh(self) -> bool: - return self.rdl_signal.get_property("activehigh") + return self.rdl_signal.get_property('activehigh') @property def width(self) -> int: diff --git a/test/.gitignore b/test/.gitignore deleted file mode 100644 index a7c92e9..0000000 --- a/test/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -work -transcript -*.wlf diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..7a11e2b --- /dev/null +++ b/test/README.md @@ -0,0 +1,17 @@ + +ModelSim +-------- + +Testcases require an installation of ModelSim/QuestaSim, and for `vlog` & `vsim` +commands to be visible via the PATH environment variable. + +ModelSim - Intel FPGA Edition can be downloaded for free from https://fpgasoftware.intel.com/ and is sufficient to run unit tests. + +Running tests +------------- + +``` +cd test/ +python3 -m pip install requirements.txt +pytest -n auto +``` diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/lib/__init__.py b/test/lib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/lib/cpuifs/__init__.py b/test/lib/cpuifs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/lib/cpuifs/apb3/__init__.py b/test/lib/cpuifs/apb3/__init__.py new file mode 100644 index 0000000..e9d9ceb --- /dev/null +++ b/test/lib/cpuifs/apb3/__init__.py @@ -0,0 +1,14 @@ +from ..base import CpuifTestMode + +from peakrdl.regblock.cpuif.apb3 import APB3_Cpuif, APB3_Cpuif_flattened + +class APB3(CpuifTestMode): + cpuif_cls = APB3_Cpuif + tb_files = [ + "apb3_intf.sv", + "apb3_intf_driver.sv", + ] + tb_template = "tb_inst.sv" + +class FlatAPB3(APB3): + cpuif_cls = APB3_Cpuif_flattened diff --git a/test/interfaces/apb3_intf.sv b/test/lib/cpuifs/apb3/apb3_intf.sv similarity index 100% rename from test/interfaces/apb3_intf.sv rename to test/lib/cpuifs/apb3/apb3_intf.sv diff --git a/test/drivers/apb3_intf_driver.sv b/test/lib/cpuifs/apb3/apb3_intf_driver.sv similarity index 76% rename from test/drivers/apb3_intf_driver.sv rename to test/lib/cpuifs/apb3/apb3_intf_driver.sv index 240c5d1..082907b 100644 --- a/test/drivers/apb3_intf_driver.sv +++ b/test/lib/cpuifs/apb3/apb3_intf_driver.sv @@ -3,6 +3,7 @@ interface apb3_intf_driver #( parameter ADDR_WIDTH = 32 )( input wire clk, + input wire rst, apb3_intf.master m_apb ); @@ -84,12 +85,25 @@ interface apb3_intf_driver #( // Wait for response while(cb.PREADY !== 1'b1) @(cb); + assert(!$isunknown(cb.PRDATA)) else $error("Read from 0x%0x returned X's on PRDATA", addr); + assert(!$isunknown(cb.PSLVERR)) else $error("Read from 0x%0x returned X's on PSLVERR", addr); data = cb.PRDATA; reset(); endtask + task assert_read(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] expected_data); + logic [DATA_WIDTH-1:0] data; + read(addr, data); + assert(data == expected_data) else $error("Read from 0x%x returned 0x%x. Expected 0x%x", addr, data, expected_data); + endtask + initial begin reset(); end + initial forever begin + @cb; + if(!rst) assert(!$isunknown(cb.PREADY)) else $error("Saw X on PREADY!"); + end + endinterface diff --git a/test/lib/cpuifs/apb3/tb_inst.sv b/test/lib/cpuifs/apb3/tb_inst.sv new file mode 100644 index 0000000..6504608 --- /dev/null +++ b/test/lib/cpuifs/apb3/tb_inst.sv @@ -0,0 +1,30 @@ +apb3_intf #( + .DATA_WIDTH({{exporter.cpuif.data_width}}), + .ADDR_WIDTH({{exporter.cpuif.addr_width}}) +) s_apb(); +apb3_intf_driver #( + .DATA_WIDTH({{exporter.cpuif.data_width}}), + .ADDR_WIDTH({{exporter.cpuif.addr_width}}) +) cpuif( + .clk(clk), + .rst(rst), + .m_apb(s_apb) +); +{% if type(cpuif).__name__.startswith("Flat") %} +wire s_apb_psel; +wire s_apb_penable; +wire s_apb_pwrite; +wire [{{exporter.cpuif.addr_width - 1}}:0] s_apb_paddr; +wire [{{exporter.cpuif.data_width - 1}}:0] s_apb_pwdata; +wire s_apb_pready; +wire [{{exporter.cpuif.data_width - 1}}:0] s_apb_prdata; +wire s_apb_pslverr; +assign s_apb_psel = s_apb.PSEL; +assign s_apb_penable = s_apb.PENABLE; +assign s_apb_pwrite = s_apb.PWRITE; +assign s_apb_paddr = s_apb.PADDR; +assign s_apb_pwdata = s_apb.PWDATA; +assign s_apb.PREADY = s_apb_pready; +assign s_apb.PRDATA = s_apb_prdata; +assign s_apb.PSLVERR = s_apb_pslverr; +{% endif %} diff --git a/test/lib/cpuifs/base.py b/test/lib/cpuifs/base.py new file mode 100644 index 0000000..ad5de69 --- /dev/null +++ b/test/lib/cpuifs/base.py @@ -0,0 +1,50 @@ +from typing import List, TYPE_CHECKING +import os +import inspect + +import jinja2 as jj + +from peakrdl.regblock.cpuif.base import CpuifBase + +if TYPE_CHECKING: + from peakrdl.regblock import RegblockExporter + from ..regblock_testcase import RegblockTestCase + +class CpuifTestMode: + cpuif_cls = None # type: CpuifBase + tb_files = [] # type: List[str] + tb_template = "" + + def get_tb_files(self) -> List[str]: + class_dir = os.path.dirname(inspect.getfile(self.__class__)) + cwd = os.getcwd() + + tb_files = [] + for file in self.tb_files: + relpath = os.path.relpath( + os.path.join(class_dir, file), + cwd + ) + tb_files.append(relpath) + return tb_files + + + def get_tb_inst(self, tb_cls: 'RegblockTestCase', exporter: 'RegblockExporter') -> str: + class_dir = os.path.dirname(inspect.getfile(self.__class__)) + loader = jj.FileSystemLoader( + os.path.join(class_dir) + ) + jj_env = jj.Environment( + loader=loader, + undefined=jj.StrictUndefined, + ) + + context = { + "cpuif": self, + "cls": tb_cls, + "exporter": exporter, + "type": type, + } + + template = jj_env.get_template(self.tb_template) + return template.render(context) diff --git a/test/lib/regblock_testcase.py b/test/lib/regblock_testcase.py new file mode 100644 index 0000000..547ba7b --- /dev/null +++ b/test/lib/regblock_testcase.py @@ -0,0 +1,232 @@ +from typing import Optional, List +import unittest +import os +import glob +import shutil +import subprocess +import inspect + +import pytest +import jinja2 as jj +from systemrdl import RDLCompiler + +from peakrdl.regblock import RegblockExporter +from .cpuifs.base import CpuifTestMode +from .cpuifs.apb3 import APB3 + + +class RegblockTestCase(unittest.TestCase): + #: Path to the testcase's RDL file. + #: Relative to the testcase's dir. If unset, the first RDL file found in the + #: testcase dir will be used + rdl_file = None # type: Optional[str] + + #: RDL type name to elaborate. If unset, compiler will automatically choose + #: the top. + rdl_elab_target = None # type: Optional[str] + + #: Parameters to pass into RDL elaboration + rdl_elab_params = {} + + #: Define what CPUIF to use for this testcase + cpuif = APB3() # type: CpuifTestMode + + # Other exporter args: + retime_read_fanin = False + retime_read_response = False + + #: Abort test if it exceeds this number of clock cycles + timeout_clk_cycles = 1000 + + #: this gets auto-loaded via the _load_request autouse fixture + request = None # type: pytest.FixtureRequest + + @pytest.fixture(autouse=True) + def _load_request(self, request): + self.request = request + + @classmethod + def get_testcase_dir(cls) -> str: + class_dir = os.path.dirname(inspect.getfile(cls)) + return class_dir + + @classmethod + def get_build_dir(cls) -> str: + this_dir = cls.get_testcase_dir() + build_dir = os.path.join(this_dir, cls.__name__ + ".out") + return build_dir + + @classmethod + def _write_params(cls) -> None: + """ + Write out the class parameters to a file so that it is easier to debug + how a testcase was parameterized + """ + path = os.path.join(cls.get_build_dir(), "params.txt") + + with open(path, 'w') as f: + for k, v in cls.__dict__.items(): + if k.startswith("_") or callable(v): + continue + f.write(f"{k}: {repr(v)}\n") + + + @classmethod + def _export_regblock(cls) -> RegblockExporter: + """ + Call the peakrdl.regblock exporter to generate the DUT + """ + this_dir = cls.get_testcase_dir() + + if cls.rdl_file: + rdl_file = cls.rdl_file + else: + # Find any *.rdl file in testcase dir + rdl_file = glob.glob(os.path.join(this_dir, "*.rdl"))[0] + + rdlc = RDLCompiler() + rdlc.compile_file(rdl_file) + root = rdlc.elaborate(cls.rdl_elab_target, "regblock", cls.rdl_elab_params) + + exporter = RegblockExporter() + exporter.export( + root, + cls.get_build_dir(), + module_name="regblock", + package_name="regblock_pkg", + cpuif_cls=cls.cpuif.cpuif_cls, + retime_read_fanin=cls.retime_read_fanin, + retime_read_response=cls.retime_read_response, + ) + + return exporter + + @classmethod + def _generate_tb(cls, exporter: RegblockExporter): + """ + Render the testbench template into actual tb.sv + """ + template_root_path = os.path.join(os.path.dirname(__file__), "..") + loader = jj.FileSystemLoader( + template_root_path + ) + jj_env = jj.Environment( + loader=loader, + undefined=jj.StrictUndefined, + ) + + context = { + "cls": cls, + "exporter": exporter, + } + + # template path needs to be relative to the Jinja loader root + template_path = os.path.join(cls.get_testcase_dir(), "tb.sv") + template_path = os.path.relpath(template_path, template_root_path) + template = jj_env.get_template(template_path) + + output_path = os.path.join(cls.get_build_dir(), "tb.sv") + stream = template.stream(context) + stream.dump(output_path) + + + @classmethod + def _compile_tb(cls): + # CD into the build directory + cwd = os.getcwd() + os.chdir(cls.get_build_dir()) + + cmd = [ + "vlog", "-sv", "-quiet", "-l", "build.log", + + # Free version of ModelSim throws errors if generate/endgenerate + # blocks are not used. + # These have been made optional long ago. Modern versions of SystemVerilog do + # not require them and I prefer not to add them. + "-suppress", "2720", + + # Ignore noisy warning about vopt-time checking of always_comb/always_latch + "-suppress", "2583", + ] + + # Add CPUIF sources + cmd.extend(cls.cpuif.get_tb_files()) + + # Add DUT sources + cmd.append("regblock_pkg.sv") + cmd.append("regblock.sv") + + # Add TB + cmd.append("tb.sv") + + # Run command! + try: + subprocess.run(cmd, check=True) + finally: + # cd back + os.chdir(cwd) + + + @classmethod + def setUpClass(cls): + # Create fresh build dir + build_dir = cls.get_build_dir() + if os.path.exists(build_dir): + shutil.rmtree(build_dir) + os.mkdir(build_dir) + + cls._write_params() + + # Convert testcase RDL file --> SV + exporter = cls._export_regblock() + + # Create testbench from template + cls._generate_tb(exporter) + + cls._compile_tb() + + + def setUp(self) -> None: + # cd into the build directory + self.original_cwd = os.getcwd() + os.chdir(self.get_build_dir()) + + + def run_test(self, plusargs:List[str] = None) -> None: + plusargs = plusargs or [] + + test_name = self.request.node.name + + # call vsim + cmd = [ + "vsim", "-quiet", + "-msgmode", "both", + "-do", "set WildcardFilter [lsearch -not -all -inline $WildcardFilter Memory]", + "-do", "log -r /*;", + "-do", "run -all; exit;", + "-c", + "-l", "%s.log" % test_name, + "-wlf", "%s.wlf" % test_name, + "tb", + ] + for plusarg in plusargs: + cmd.append("+" + plusarg) + subprocess.run(cmd, check=True) + + self.assertSimLogPass("%s.log" % test_name) + + + def tearDown(self) -> None: + # cd back + os.chdir(self.original_cwd) + + + def assertSimLogPass(self, path: str): + self.assertTrue(os.path.isfile(path)) + + with open(path, encoding="utf-8") as f: + for line in f: + if line.startswith("# ** Error"): + self.fail(line) + elif line.startswith("# ** Fatal"): + self.fail(line) diff --git a/test/lib/templates/tb_base.sv b/test/lib/templates/tb_base.sv new file mode 100644 index 0000000..151c382 --- /dev/null +++ b/test/lib/templates/tb_base.sv @@ -0,0 +1,90 @@ +module tb; + timeunit 1ns; + timeprecision 1ps; + + logic rst = '1; + logic clk = '0; + initial forever begin + #10ns; + clk = ~clk; + end + + //-------------------------------------------------------------------------- + // DUT Signal declarations + //-------------------------------------------------------------------------- +{%- if exporter.hwif.has_input_struct %} + regblock_pkg::regblock__in_t hwif_in; +{%- endif %} + +{%- if exporter.hwif.has_output_struct %} + regblock_pkg::regblock__out_t hwif_out; +{%- endif %} + +{%- block declarations %} +{%- endblock %} + + //-------------------------------------------------------------------------- + // Clocking + //-------------------------------------------------------------------------- + default clocking cb @(posedge clk); + default input #1step output #1; + output rst; +{%- if exporter.hwif.has_input_struct %} + output hwif_in; +{%- endif %} + +{%- if exporter.hwif.has_output_struct %} + input hwif_out; +{%- endif %} + +{%- filter indent %} +{%- block clocking_dirs %} +{%- endblock %} +{%- endfilter %} + endclocking + + //-------------------------------------------------------------------------- + // CPUIF + //-------------------------------------------------------------------------- + {{cls.cpuif.get_tb_inst(cls, exporter)|indent}} + + //-------------------------------------------------------------------------- + // DUT + //-------------------------------------------------------------------------- + regblock dut (.*); + +{%- if exporter.hwif.has_output_struct %} + initial forever begin + ##1; if(!rst) assert(!$isunknown({>>{hwif_out}})) else $error("hwif_out has X's!"); + end +{%- endif %} + + //-------------------------------------------------------------------------- + // Test Sequence + //-------------------------------------------------------------------------- + initial begin + cb.rst <= '1; + {%- if exporter.hwif.has_input_struct %} + cb.hwif_in <= '{default: '0}; + {%- endif %} + + begin + {%- filter indent(8) %} + {%- block seq %} + {%- endblock %} + {%- endfilter %} + end + + ##5; + $finish(); + end + + //-------------------------------------------------------------------------- + // Monitor for timeout + //-------------------------------------------------------------------------- + initial begin + ##{{cls.timeout_clk_cycles}}; + $fatal(1, "Test timed out after {{cls.timeout_clk_cycles}} clock cycles"); + end + +endmodule diff --git a/test/lib/test_params.py b/test/lib/test_params.py new file mode 100644 index 0000000..627e546 --- /dev/null +++ b/test/lib/test_params.py @@ -0,0 +1,23 @@ +from itertools import product + +from .cpuifs.apb3 import APB3, FlatAPB3 + + +all_cpuif = [ + APB3(), + FlatAPB3(), +] + +def get_permutations(spec): + param_list = [] + for v in product(*spec.values()): + param_list.append(dict(zip(spec, v))) + return param_list + +#------------------------------------------------------------------------------- +# TODO: this wont scale well. Create groups of permutatuions. not necessary to permute everything all the time. +TEST_PARAMS = get_permutations({ + "cpuif": all_cpuif, + "retime_read_fanin": [True, False], + "retime_read_response": [True, False], +}) diff --git a/test/pytest.ini b/test/pytest.ini new file mode 100644 index 0000000..afd91e9 --- /dev/null +++ b/test/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +python_files = test_*.py testcase.py diff --git a/test/requirements.txt b/test/requirements.txt new file mode 100644 index 0000000..28ef6f4 --- /dev/null +++ b/test/requirements.txt @@ -0,0 +1,3 @@ +pytest +parameterized +pytest-xdist diff --git a/test/run.sh b/test/run.sh deleted file mode 100755 index af3f8df..0000000 --- a/test/run.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -set -e - - -../export.py test_regblock.rdl - -vlog -sv -f vlog_args.f -f src.f -vsim -c -quiet tb -do "log -r /*; run -all; exit;" diff --git a/test/src.f b/test/src.f deleted file mode 100644 index 6d6faa0..0000000 --- a/test/src.f +++ /dev/null @@ -1,5 +0,0 @@ -interfaces/apb3_intf.sv -drivers/apb3_intf_driver.sv -test_regblock_pkg.sv -test_regblock.sv -tb.sv diff --git a/test/tb.sv b/test/tb.sv deleted file mode 100644 index a130dee..0000000 --- a/test/tb.sv +++ /dev/null @@ -1,51 +0,0 @@ -module tb; - timeunit 1ns; - timeprecision 1ps; - - logic rst = '1; - logic clk = '0; - initial forever begin - #10ns; - clk = ~clk; - end - - apb3_intf apb(); - - apb3_intf_driver driver( - .clk(clk), - .m_apb(apb) - ); - - - test_regblock dut ( - .clk(clk), - .rst(rst), - .s_apb(apb), - .hwif_out() - ); - - - initial begin - logic [31:0] rd_data; - - repeat(5) @(posedge clk); - rst = '0; - repeat(5) @(posedge clk); - - driver.read('h000, rd_data); - driver.write('h000, 'h0); - driver.read('h000, rd_data); - - driver.read('h100, rd_data); - driver.write('h100, 'h0); - driver.read('h100, rd_data); - - driver.read('h000, rd_data); - driver.write('h000, 'hFFFF_FFFF); - driver.read('h000, rd_data); - - repeat(5) @(posedge clk); - $finish(); - end - -endmodule diff --git a/test/test_field_types/__init__.py b/test/test_field_types/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/test_field_types/regblock.rdl b/test/test_field_types/regblock.rdl new file mode 100644 index 0000000..0557159 --- /dev/null +++ b/test/test_field_types/regblock.rdl @@ -0,0 +1,64 @@ +addrmap top { + default regwidth = 8; + + // All the valid combinations from Table 12 + reg { + field { + sw=rw; hw=rw; we; // Storage element + } f[8] = 10; + } r1; + + reg { + field { + sw=rw; hw=r; // Storage element + } f[8] = 20; + } r2; + + reg { + field { + sw=rw; hw=w; wel; // Storage element + } f[8] = 30; + } r3; + + reg { + field { + sw=rw; hw=na; // Storage element + } f[8] = 40; + } r4; + + reg { + field { + sw=r; hw=rw; we; // Storage element + } f[8] = 50; + } r5; + + reg { + field { + sw=r; hw=r; // Wire/Bus - constant value + } f[8] = 60; + } r6; + + reg { + field { + sw=r; hw=w; // Wire/Bus - hardware assigns value + } f[8]; + } r7; + + reg { + field { + sw=r; hw=na; // Wire/Bus - constant value + } f[8] = 80; + } r8; + + reg { + field { + sw=w; hw=rw; we; // Storage element + } f[8] = 90; + } r9; + + reg { + field { + sw=w; hw=r; // Storage element + } f[8] = 100; + } r10; +}; diff --git a/test/test_field_types/tb.sv b/test/test_field_types/tb.sv new file mode 100644 index 0000000..1d8dd00 --- /dev/null +++ b/test/test_field_types/tb.sv @@ -0,0 +1,130 @@ +{% extends "lib/templates/tb_base.sv" %} + +{% block seq %} + cb.hwif_in.r3.f.wel <= 1; + ##1; + cb.rst <= '0; + ##1; + + // r1 - sw=rw; hw=rw; we; // Storage element + cpuif.assert_read('h0, 10); + assert(cb.hwif_out.r1.f.value == 10); + + cpuif.write('h0, 11); + cpuif.assert_read('h0, 11); + assert(cb.hwif_out.r1.f.value == 11); + + cb.hwif_in.r1.f.value <= 9; + cpuif.assert_read('h0, 11); + assert(cb.hwif_out.r1.f.value == 11); + cb.hwif_in.r1.f.value <= 12; + cb.hwif_in.r1.f.we <= 1; + @cb; + cb.hwif_in.r1.f.value <= 0; + cb.hwif_in.r1.f.we <= 0; + cpuif.assert_read('h0, 12); + assert(cb.hwif_out.r1.f.value == 12); + + + // r2 - sw=rw; hw=r; // Storage element + cpuif.assert_read('h1, 20); + assert(cb.hwif_out.r2.f.value == 20); + + cpuif.write('h1, 21); + cpuif.assert_read('h1, 21); + assert(cb.hwif_out.r2.f.value == 21); + + + // r3 - sw=rw; hw=w; wel; // Storage element + cpuif.assert_read('h2, 30); + + cpuif.write('h2, 31); + cpuif.assert_read('h2, 31); + + cb.hwif_in.r3.f.value <= 29; + cpuif.assert_read('h2, 31); + cb.hwif_in.r3.f.value <= 32; + cb.hwif_in.r3.f.wel <= 0; + @cb; + cb.hwif_in.r3.f.value <= 0; + cb.hwif_in.r3.f.wel <= 1; + cpuif.assert_read('h2, 32); + + + // r4 - sw=rw; hw=na; // Storage element + cpuif.assert_read('h3, 40); + cpuif.write('h3, 41); + cpuif.assert_read('h3, 41); + + + // r5 - sw=r; hw=rw; we; // Storage element + cpuif.assert_read('h4, 50); + assert(cb.hwif_out.r5.f.value == 50); + + cpuif.write('h4, 51); + cpuif.assert_read('h4, 50); + assert(cb.hwif_out.r5.f.value == 50); + + cb.hwif_in.r5.f.value <= 9; + cpuif.assert_read('h4, 50); + assert(cb.hwif_out.r5.f.value == 50); + cb.hwif_in.r5.f.value <= 52; + cb.hwif_in.r5.f.we <= 1; + @cb; + cb.hwif_in.r5.f.value <= 0; + cb.hwif_in.r5.f.we <= 0; + cpuif.assert_read('h4, 52); + assert(cb.hwif_out.r5.f.value == 52); + + + // r6 - sw=r; hw=r; // Wire/Bus - constant value + cpuif.assert_read('h5, 60); + assert(cb.hwif_out.r6.f.value == 60); + cpuif.write('h5, 61); + cpuif.assert_read('h5, 60); + assert(cb.hwif_out.r6.f.value == 60); + + + // r7 - sw=r; hw=w; // Wire/Bus - hardware assigns value + cpuif.assert_read('h6, 0); + cb.hwif_in.r7.f.value = 70; + cpuif.assert_read('h6, 70); + cpuif.write('h6, 71); + cpuif.assert_read('h6, 70); + + + // r8 - sw=r; hw=na; // Wire/Bus - constant value + cpuif.assert_read('h7, 80); + cpuif.write('h7, 81); + cpuif.assert_read('h7, 80); + + + // r9 - sw=w; hw=rw; we; // Storage element + cpuif.assert_read('h8, 0); + assert(cb.hwif_out.r9.f.value == 90); + + cpuif.write('h8, 91); + cpuif.assert_read('h8, 0); + assert(cb.hwif_out.r9.f.value == 91); + + cb.hwif_in.r9.f.value <= 89; + cpuif.assert_read('h8, 0); + assert(cb.hwif_out.r9.f.value == 91); + cb.hwif_in.r9.f.value <= 92; + cb.hwif_in.r9.f.we <= 1; + @cb; + cb.hwif_in.r9.f.value <= 0; + cb.hwif_in.r9.f.we <= 0; + cpuif.assert_read('h8, 0); + assert(cb.hwif_out.r9.f.value == 92); + + + // r10 - sw=w; hw=r; // Storage element + cpuif.assert_read('h9, 0); + assert(cb.hwif_out.r10.f.value == 100); + + cpuif.write('h9, 101); + cpuif.assert_read('h9, 0); + assert(cb.hwif_out.r10.f.value == 101); + +{% endblock %} diff --git a/test/test_field_types/testcase.py b/test/test_field_types/testcase.py new file mode 100644 index 0000000..b458d3c --- /dev/null +++ b/test/test_field_types/testcase.py @@ -0,0 +1,5 @@ +from ..lib.regblock_testcase import RegblockTestCase + +class Test(RegblockTestCase): + def test_dut(self): + self.run_test() diff --git a/test/test_read_fanin/__init__.py b/test/test_read_fanin/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/test_read_fanin/regblock.rdl b/test/test_read_fanin/regblock.rdl new file mode 100644 index 0000000..5d6bffd --- /dev/null +++ b/test/test_read_fanin/regblock.rdl @@ -0,0 +1,10 @@ +addrmap top #( + longint N_REGS = 1, + longint REGWIDTH = 32 +) { + reg reg_t { + regwidth = REGWIDTH; + field {sw=rw; hw=na;} f[REGWIDTH] = 1; + }; + reg_t regs[N_REGS]; +}; diff --git a/test/test_read_fanin/tb.sv b/test/test_read_fanin/tb.sv new file mode 100644 index 0000000..07d7531 --- /dev/null +++ b/test/test_read_fanin/tb.sv @@ -0,0 +1,31 @@ +{% extends "lib/templates/tb_base.sv" %} + +{%- block declarations %} + localparam REGWIDTH = {{cls.regwidth}}; + localparam STRIDE = REGWIDTH/8; + localparam N_REGS = {{cls.n_regs}}; +{%- endblock %} + +{% block seq %} + bit [REGWIDTH-1:0] data[N_REGS]; + + ##1; + cb.rst <= '0; + ##1; + + foreach(data[i]) data[i] = {$urandom(), $urandom(), $urandom(), $urandom()}; + + for(int i=0; ireset = 0x42; + + my_reg r1[2][3][4] @0x10 += 8; + + my_reg r2 @0x1000; + r2.a->reset = 0x11; + + + reg subreg { + field {} x[7:4] = 1; + }; + regfile subrf { + subreg r1[4] @ 0x0 += 4; + regfile { + subreg r1 @ 0x0; + subreg r2[2] @ 0x4 += 4; + subreg r3 @ 0xc; + } sub[2] @ 0x10 += 0x10; + subreg r2[4] @ 0x30 += 4; + }; + subrf sub2[2] @ 0x2000 += 0x40; + subreg r3 @ 0x2080; +}; diff --git a/test/test_structural_sw_rw/tb.sv b/test/test_structural_sw_rw/tb.sv new file mode 100644 index 0000000..a51581c --- /dev/null +++ b/test/test_structural_sw_rw/tb.sv @@ -0,0 +1,63 @@ +{% extends "lib/templates/tb_base.sv" %} + +{% block seq %} + ##1; + cb.rst <= '0; + ##1; + + // Assert value via frontdoor + cpuif.assert_read(0, 32'h8000_0042); + for(int i=0; i<2*3*4; i++) begin + cpuif.assert_read('h10+i*8, 32'h8000_0023); + end + cpuif.assert_read('h1000, 32'h8000_0011); + for(int i=0; i<33; i++) begin + cpuif.assert_read('h2000 +i*4, 32'h0000_0010); + end + + // Assert via hwif + assert(hwif_out.r0.a.value == 'h42); + assert(hwif_out.r0.b.value == 'h0); + assert(hwif_out.r0.c.value == 'h1); + foreach(hwif_out.r1[x, y, z]) begin + assert(hwif_out.r1[x][y][z].a.value == 'h23); + assert(hwif_out.r1[x][y][z].b.value == 'h0); + assert(hwif_out.r1[x][y][z].c.value == 'h1); + end + assert(hwif_out.r2.a.value == 'h11); + assert(hwif_out.r2.b.value == 'h0); + assert(hwif_out.r2.c.value == 'h1); + + // Write values + cpuif.write(0, 32'h8000_0002); + for(int i=0; i<2*3*4; i++) begin + cpuif.write('h10+i*8, i+'h110a); + end + cpuif.write('h1000, 32'h0000_0000); + for(int i=0; i<33; i++) begin + cpuif.write('h2000 +i*4, i << 4); + end + + // Assert value via frontdoor + cpuif.assert_read(0, 32'h8000_0002); + for(int i=0; i<2*3*4; i++) begin + cpuif.assert_read('h10+i*8, i+'h10a); + end + cpuif.assert_read('h1000, 32'h0000_0000); + for(int i=0; i<33; i++) begin + cpuif.assert_read('h2000 +i*4, (i << 4) & 'hF0); + end + + // Assert via hwif + assert(hwif_out.r0.a.value == 'h02); + assert(hwif_out.r0.b.value == 'h0); + assert(hwif_out.r0.c.value == 'h1); + foreach(hwif_out.r1[x, y, z]) begin + assert(hwif_out.r1[x][y][z].a.value == x*12+y*4+z+10); + assert(hwif_out.r1[x][y][z].b.value == 'h1); + assert(hwif_out.r1[x][y][z].c.value == 'h0); + end + assert(hwif_out.r2.a.value == 'h0); + assert(hwif_out.r2.b.value == 'h0); + assert(hwif_out.r2.c.value == 'h0); +{% endblock %} diff --git a/test/test_structural_sw_rw/testcase.py b/test/test_structural_sw_rw/testcase.py new file mode 100644 index 0000000..a0e39d0 --- /dev/null +++ b/test/test_structural_sw_rw/testcase.py @@ -0,0 +1,9 @@ +from parameterized import parameterized_class + +from ..lib.regblock_testcase import RegblockTestCase +from ..lib.test_params import TEST_PARAMS + +@parameterized_class(TEST_PARAMS) +class Test(RegblockTestCase): + def test_dut(self): + self.run_test() diff --git a/test/test_swacc_swmod/__init__.py b/test/test_swacc_swmod/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/test_swacc_swmod/regblock.rdl b/test/test_swacc_swmod/regblock.rdl new file mode 100644 index 0000000..0bccf2c --- /dev/null +++ b/test/test_swacc_swmod/regblock.rdl @@ -0,0 +1,17 @@ +addrmap top { + default regwidth = 8; + + reg { + field { + sw=r; hw=w; + swacc; + } f[8]; + } r1; + + reg { + field { + sw=rw; hw=r; + swmod; + } f[8] = 20; + } r2; +}; diff --git a/test/test_swacc_swmod/tb.sv b/test/test_swacc_swmod/tb.sv new file mode 100644 index 0000000..28d1c2a --- /dev/null +++ b/test/test_swacc_swmod/tb.sv @@ -0,0 +1,66 @@ +{% extends "lib/templates/tb_base.sv" %} + +{% block seq %} + logic [7:0] rd_data; + logic [7:0] latched_data; + int event_count; + latched_data = 'x; + + ##1; + cb.rst <= '0; + ##1; + + // Verify that hwif gets sampled at the same cycle as swacc strobe + cb.hwif_in.r1.f.value <= 'h10; + @cb; + event_count = 0; + fork + begin + ##0; + forever begin + cb.hwif_in.r1.f.value <= cb.hwif_in.r1.f.value + 1; + @cb; + if(cb.hwif_out.r1.f.swacc) begin + latched_data = cb.hwif_in.r1.f.value; + event_count++; + end + end + end + + begin + cpuif.read('h0, rd_data); + @cb; + end + join_any + disable fork; + assert(rd_data == latched_data) else $error("Read returned 0x%0x but swacc strobed during 0x%0x", rd_data, latched_data); + assert(event_count == 1) else $error("Observed excess swacc events: %0d", event_count); + + + // Verify that hwif changes 1 cycle after swmod + fork + begin + ##0; + forever begin + assert(hwif_out.r2.f.value == 20); + if(hwif_out.r2.f.swmod) break; + @cb; + end + @cb; + forever begin + assert(hwif_out.r2.f.value == 21); + assert(hwif_out.r2.f.swmod == 0); + @cb; + end + end + + begin + cpuif.write('h1, 21); + @cb; + end + join_any + disable fork; + + // TODO: verify some other atypical swmod (onread actions) + +{% endblock %} diff --git a/test/test_swacc_swmod/testcase.py b/test/test_swacc_swmod/testcase.py new file mode 100644 index 0000000..b458d3c --- /dev/null +++ b/test/test_swacc_swmod/testcase.py @@ -0,0 +1,5 @@ +from ..lib.regblock_testcase import RegblockTestCase + +class Test(RegblockTestCase): + def test_dut(self): + self.run_test() diff --git a/test/vlog_args.f b/test/vlog_args.f deleted file mode 100644 index 12ea952..0000000 --- a/test/vlog_args.f +++ /dev/null @@ -1,9 +0,0 @@ --quiet - -# Free version of ModelSim errors if generate statements are not used. -# These have been made optional long ago. Modern versions of SystemVerilog do -# not require them. --suppress 2720 - -# Ignore warning about vopt-time checking of always_comb/always_latch --suppress 2583 diff --git a/test/wave.do b/test/wave.do deleted file mode 100644 index 28083b2..0000000 --- a/test/wave.do +++ /dev/null @@ -1,45 +0,0 @@ -onerror {resume} -quietly WaveActivateNextPane {} 0 -add wave -noupdate /tb/rst -add wave -noupdate /tb/clk -add wave -noupdate /tb/apb/PSEL -add wave -noupdate /tb/apb/PENABLE -add wave -noupdate /tb/apb/PWRITE -add wave -noupdate /tb/apb/PADDR -add wave -noupdate /tb/apb/PWDATA -add wave -noupdate /tb/apb/PRDATA -add wave -noupdate /tb/apb/PREADY -add wave -noupdate /tb/apb/PSLVERR -add wave -noupdate -divider DUT -add wave -noupdate /tb/dut/cpuif_req -add wave -noupdate /tb/dut/cpuif_req_is_wr -add wave -noupdate /tb/dut/cpuif_addr -add wave -noupdate /tb/dut/cpuif_wr_data -add wave -noupdate /tb/dut/cpuif_wr_biten -add wave -noupdate /tb/dut/cpuif_rd_ack -add wave -noupdate /tb/dut/cpuif_rd_data -add wave -noupdate /tb/dut/cpuif_rd_err -add wave -noupdate /tb/dut/cpuif_wr_ack -add wave -noupdate /tb/dut/cpuif_wr_err -add wave -noupdate -divider Storage -add wave -noupdate -radix hexadecimal -childformat {{/tb/dut/field_storage.r0 -radix hexadecimal -childformat {{/tb/dut/field_storage.r0.a -radix hexadecimal} {/tb/dut/field_storage.r0.b -radix hexadecimal} {/tb/dut/field_storage.r0.c -radix hexadecimal}}} {/tb/dut/field_storage.r1 -radix hexadecimal -childformat {{/tb/dut/field_storage.r1.a -radix hexadecimal} {/tb/dut/field_storage.r1.b -radix hexadecimal} {/tb/dut/field_storage.r1.c -radix hexadecimal}}} {/tb/dut/field_storage.r2 -radix hexadecimal -childformat {{/tb/dut/field_storage.r2.a -radix hexadecimal} {/tb/dut/field_storage.r2.b -radix hexadecimal} {/tb/dut/field_storage.r2.c -radix hexadecimal}}}} -expand -subitemconfig {/tb/dut/field_storage.r0 {-height 17 -radix hexadecimal -childformat {{/tb/dut/field_storage.r0.a -radix hexadecimal} {/tb/dut/field_storage.r0.b -radix hexadecimal} {/tb/dut/field_storage.r0.c -radix hexadecimal}} -expand} /tb/dut/field_storage.r0.a {-height 17 -radix hexadecimal} /tb/dut/field_storage.r0.b {-height 17 -radix hexadecimal} /tb/dut/field_storage.r0.c {-height 17 -radix hexadecimal} /tb/dut/field_storage.r1 {-height 17 -radix hexadecimal -childformat {{/tb/dut/field_storage.r1.a -radix hexadecimal} {/tb/dut/field_storage.r1.b -radix hexadecimal} {/tb/dut/field_storage.r1.c -radix hexadecimal}} -expand} /tb/dut/field_storage.r1.a {-height 17 -radix hexadecimal} /tb/dut/field_storage.r1.b {-height 17 -radix hexadecimal} /tb/dut/field_storage.r1.c {-height 17 -radix hexadecimal} /tb/dut/field_storage.r2 {-height 17 -radix hexadecimal -childformat {{/tb/dut/field_storage.r2.a -radix hexadecimal} {/tb/dut/field_storage.r2.b -radix hexadecimal} {/tb/dut/field_storage.r2.c -radix hexadecimal}} -expand} /tb/dut/field_storage.r2.a {-height 17 -radix hexadecimal} /tb/dut/field_storage.r2.b {-height 17 -radix hexadecimal} /tb/dut/field_storage.r2.c {-height 17 -radix hexadecimal}} /tb/dut/field_storage -add wave -noupdate -divider HWIF -add wave -noupdate -radix hexadecimal -childformat {{/tb/dut/hwif_out.r0 -radix hexadecimal -childformat {{/tb/dut/hwif_out.r0.a -radix hexadecimal -childformat {{/tb/dut/hwif_out.r0.a.value -radix hexadecimal} {/tb/dut/hwif_out.r0.a.anded -radix hexadecimal}}} {/tb/dut/hwif_out.r0.b -radix hexadecimal -childformat {{/tb/dut/hwif_out.r0.b.value -radix hexadecimal} {/tb/dut/hwif_out.r0.b.ored -radix hexadecimal}}} {/tb/dut/hwif_out.r0.c -radix hexadecimal -childformat {{/tb/dut/hwif_out.r0.c.value -radix hexadecimal} {/tb/dut/hwif_out.r0.c.swmod -radix hexadecimal}}}}} {/tb/dut/hwif_out.r1 -radix hexadecimal -childformat {{/tb/dut/hwif_out.r1.a -radix hexadecimal} {/tb/dut/hwif_out.r1.b -radix hexadecimal} {/tb/dut/hwif_out.r1.c -radix hexadecimal}}} {/tb/dut/hwif_out.r2 -radix hexadecimal -childformat {{/tb/dut/hwif_out.r2.a -radix hexadecimal} {/tb/dut/hwif_out.r2.b -radix hexadecimal} {/tb/dut/hwif_out.r2.c -radix hexadecimal}}}} -expand -subitemconfig {/tb/dut/hwif_out.r0 {-height 17 -radix hexadecimal -childformat {{/tb/dut/hwif_out.r0.a -radix hexadecimal -childformat {{/tb/dut/hwif_out.r0.a.value -radix hexadecimal} {/tb/dut/hwif_out.r0.a.anded -radix hexadecimal}}} {/tb/dut/hwif_out.r0.b -radix hexadecimal -childformat {{/tb/dut/hwif_out.r0.b.value -radix hexadecimal} {/tb/dut/hwif_out.r0.b.ored -radix hexadecimal}}} {/tb/dut/hwif_out.r0.c -radix hexadecimal -childformat {{/tb/dut/hwif_out.r0.c.value -radix hexadecimal} {/tb/dut/hwif_out.r0.c.swmod -radix hexadecimal}}}} -expand} /tb/dut/hwif_out.r0.a {-height 17 -radix hexadecimal -childformat {{/tb/dut/hwif_out.r0.a.value -radix hexadecimal} {/tb/dut/hwif_out.r0.a.anded -radix hexadecimal}} -expand} /tb/dut/hwif_out.r0.a.value {-height 17 -radix hexadecimal} /tb/dut/hwif_out.r0.a.anded {-height 17 -radix hexadecimal} /tb/dut/hwif_out.r0.b {-height 17 -radix hexadecimal -childformat {{/tb/dut/hwif_out.r0.b.value -radix hexadecimal} {/tb/dut/hwif_out.r0.b.ored -radix hexadecimal}} -expand} /tb/dut/hwif_out.r0.b.value {-height 17 -radix hexadecimal} /tb/dut/hwif_out.r0.b.ored {-height 17 -radix hexadecimal} /tb/dut/hwif_out.r0.c {-height 17 -radix hexadecimal -childformat {{/tb/dut/hwif_out.r0.c.value -radix hexadecimal} {/tb/dut/hwif_out.r0.c.swmod -radix hexadecimal}} -expand} /tb/dut/hwif_out.r0.c.value {-height 17 -radix hexadecimal} /tb/dut/hwif_out.r0.c.swmod {-height 17 -radix hexadecimal} /tb/dut/hwif_out.r1 {-height 17 -radix hexadecimal -childformat {{/tb/dut/hwif_out.r1.a -radix hexadecimal} {/tb/dut/hwif_out.r1.b -radix hexadecimal} {/tb/dut/hwif_out.r1.c -radix hexadecimal}} -expand} /tb/dut/hwif_out.r1.a {-height 17 -radix hexadecimal} /tb/dut/hwif_out.r1.b {-height 17 -radix hexadecimal} /tb/dut/hwif_out.r1.c {-height 17 -radix hexadecimal} /tb/dut/hwif_out.r2 {-height 17 -radix hexadecimal -childformat {{/tb/dut/hwif_out.r2.a -radix hexadecimal} {/tb/dut/hwif_out.r2.b -radix hexadecimal} {/tb/dut/hwif_out.r2.c -radix hexadecimal}} -expand} /tb/dut/hwif_out.r2.a {-height 17 -radix hexadecimal} /tb/dut/hwif_out.r2.b {-height 17 -radix hexadecimal} /tb/dut/hwif_out.r2.c {-height 17 -radix hexadecimal}} /tb/dut/hwif_out -TreeUpdate [SetDefaultTree] -WaveRestoreCursors {{Cursor 1} {650000 ps} 0} -quietly wave cursor active 1 -configure wave -namecolwidth 150 -configure wave -valuecolwidth 100 -configure wave -justifyvalue left -configure wave -signalnamewidth 0 -configure wave -snapdistance 10 -configure wave -datasetprefix 0 -configure wave -rowmargin 4 -configure wave -childrowmargin 2 -configure wave -gridoffset 0 -configure wave -gridperiod 1 -configure wave -griddelta 40 -configure wave -timeline 0 -configure wave -timelineunits ps -update -WaveRestoreZoom {252900 ps} {755184 ps}