commit 9bf5cd1e6833af41b332b5a4e48337ab34d9133d
Author: Arnav Sacheti <36746504+arnavsacheti@users.noreply.github.com>
Date: Fri Oct 10 22:28:36 2025 -0700
Initial Commit - Forked from PeakRDL-regblock @ a440cc19769069be831d267505da4f3789a26695
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..18f9e56
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,26 @@
+---
+name: Bug report
+about: The tool is not doing what I expected
+title: "[BUG]"
+labels: ''
+assignees: ''
+
+---
+
+- [ ] I have reviewed this project's [contribution guidelines](https://github.com/SystemRDL/PeakRDL-regblock/blob/main/CONTRIBUTING.md)
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+Details like these can be helpful:
+* Sample SystemRDL code
+* Error message, simulation waveform, etc.
+* Version numbers for the tool, Python, and OS
+
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..34f370a
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,22 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: "[FEATURE]"
+labels: ''
+assignees: ''
+
+---
+
+- [ ] I have reviewed this project's [contribution guidelines](https://github.com/SystemRDL/PeakRDL-regblock/blob/main/CONTRIBUTING.md)
+
+**Describe the problem/limitation you think should be addressed**
+A clear and concise description of what the problem is.
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or about the feature request here.
diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md
new file mode 100644
index 0000000..1f7917d
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/question.md
@@ -0,0 +1,10 @@
+---
+name: Question
+about: I have a question
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+Please consider using the discussion board for more open-ended questions: https://github.com/orgs/SystemRDL/discussions
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000..6f0db28
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,11 @@
+# Description of change
+
+Describe what bug or feature your pull request addresses.
+If applicable, provide a link to the relevant issue ticket or discussion about
+this change.
+
+# Checklist
+
+- [ ] I have reviewed this project's [contribution guidelines](https://github.com/SystemRDL/PeakRDL-regblock/blob/main/CONTRIBUTING.md)
+- [ ] This change has been tested and does not break any of the existing unit tests. (if unable to run the tests, let us know)
+- [ ] If this change adds new features, I have added new unit tests that cover them.
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..1111ec1
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,178 @@
+name: build
+
+on:
+ push:
+ branches:
+ - main
+ - 'dev/**'
+ pull_request:
+ branches: [ main ]
+ release:
+ types:
+ - published
+
+ # Allows you to run this workflow manually from the Actions tab
+ workflow_dispatch:
+
+jobs:
+ test:
+ strategy:
+ matrix:
+ python-version:
+ - "3.7"
+ - "3.8"
+ - "3.9"
+ - "3.10"
+ - "3.11"
+ - "3.12"
+ include:
+ - os: ubuntu-latest
+
+ # older versions need older OS
+ - python-version: "3.7"
+ os: ubuntu-22.04
+
+ - python-version: "3.8"
+ os: ubuntu-22.04
+
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v4
+ with:
+ python-version: ${{ matrix.python-version }}
+
+ - name: Install dependencies
+ run: |
+ python -m pip install -r tests/requirements.txt
+
+ - name: Install
+ run: |
+ python -m pip install ".[cli]"
+
+ - name: Test
+ run: |
+ cd tests
+ pytest --cov=peakrdl_regblock --synth-tool skip --sim-tool stub
+
+ - name: Coveralls
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ COVERALLS_PARALLEL: true
+ run: |
+ cd tests
+ coveralls --service=github
+
+ finish_coveralls:
+ needs: test
+ runs-on: ubuntu-latest
+ steps:
+ - name: Setup Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: 3.x
+
+ - name: Coveralls
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ COVERALLS_PARALLEL: true
+ run: |
+ python -m pip install coveralls>=3.0.0
+ coveralls --service=github --finish
+
+#-------------------------------------------------------------------------------
+ lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - name: Set up Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: "3.10"
+
+ - name: Install dependencies
+ run: |
+ python -m pip install -r tests/requirements.txt
+
+ - name: Install
+ run: |
+ python -m pip install ".[cli]"
+
+ - name: Run Lint
+ run: |
+ pylint --rcfile tests/pylint.rc peakrdl_regblock
+
+#-------------------------------------------------------------------------------
+ mypy:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - name: Set up Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: "3.10"
+
+ - name: Install dependencies
+ run: |
+ python -m pip install -r tests/requirements.txt
+
+ - name: Install
+ run: |
+ python -m pip install ".[cli]"
+
+ - name: Type Check
+ run: |
+ mypy --config-file tests/mypy.ini src/peakrdl_regblock
+
+#-------------------------------------------------------------------------------
+ build:
+ needs:
+ - test
+ - lint
+ - mypy
+ name: Build distributions
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+
+ - uses: actions/setup-python@v4
+ name: Install Python
+ with:
+ python-version: "3.10"
+
+ - name: Install dependencies
+ run: |
+ python -m pip install build
+
+ - name: Build sdist
+ run: python -m build
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: dist
+ path: |
+ dist/*.tar.gz
+ dist/*.whl
+
+#-------------------------------------------------------------------------------
+ deploy:
+ needs:
+ - build
+
+ runs-on: ubuntu-latest
+ environment: release
+ permissions:
+ id-token: write
+
+ # Only publish when a GitHub Release is created.
+ if: github.event_name == 'release'
+ steps:
+ - uses: actions/download-artifact@v4
+ with:
+ name: dist
+ path: dist
+
+ - uses: pypa/gh-action-pypi-publish@release/v1
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ac5e70c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,19 @@
+**/__pycache__
+**/.vscode
+**/.venv
+**/.coverage
+**/*.rpt
+**/.pytest_cache
+**/_build
+**/*.out
+**/transcript
+**/htmlcov
+**/*.log
+**/*.pb
+**/.Xil
+**/.coverage.*
+
+build/
+dist/
+*.egg-info/
+.eggs/
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
new file mode 100644
index 0000000..354c013
--- /dev/null
+++ b/.readthedocs.yaml
@@ -0,0 +1,17 @@
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
+
+version: 2
+
+build:
+ os: ubuntu-22.04
+ tools:
+ python: "3.11"
+
+sphinx:
+ configuration: docs/conf.py
+
+python:
+ install:
+ - requirements: docs/requirements.txt
+ - method: pip
+ path: .
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..b82b0d8
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,53 @@
+# Contributing to the PeakRDL-regblock code generator
+We love your input! We want to make contributing to this project as easy and
+transparent as possible, whether it's:
+
+- Reporting a bug
+- Discussing the current state of the code
+- Submitting a fix
+- Proposing new features
+- Becoming a maintainer
+
+
+## Open an issue using the [Issue Tracker](https://github.com/SystemRDL/PeakRDL-regblock/issues)
+Talking to us is the easiest way to contribute! Report a bug or feature request by
+[opening a new issue](https://github.com/SystemRDL/PeakRDL-regblock/issues).
+
+Issue submission expectations:
+* Please keep each issue submission limited to one topic. This helps us stay organized.
+* Before opening an issue, check if one already exists for your topic. It may have already been discussed.
+* If submitting a bug, provide enough details so we can reproduce it on our end. (version number, example SystemRDL, etc...)
+* If submitting a feature request, please make sure ...
+ * ... it does not violate the semantics of the SystemRDL standard.
+ Submissions that would change the interpretation of the SystemRDL language
+ and are not faithful to the [Accellera SystemRDL specification](http://accellera.org/downloads/standards/systemrdl) will be rejected.
+ Additional notes on the spec's interpretation can be found in [our unofficial errata page](https://systemrdl-compiler.readthedocs.io/en/latest/dev_notes/rdl_spec_errata.html).
+* Please be patient! This project is run by volunteers that are passionate about
+ improving the state of register automation. Much of the work is done in their free time.
+
+
+## Contribute code using a pull request
+Pull requests are the best way to propose changes to the codebase. We actively
+welcome your pull requests. To maximize the chance of your pull request getting accepted,
+please review the expectations below.
+
+Pull request expectations:
+* Before starting a pull request, please consider discussing the change with us
+ first by **opening an issue ticket**. Unfortunately many of the PRs that get rejected
+ are because they implement changes that do not align with the mission of this
+ compiler project.
+* PRs shall only contain only one feature/bug/concept change. **Bulk PRs that change numerous unrelated things will be rejected**.
+* Your PR should provide proof that it works correctly and does not break the existing unit tests.
+* Use meaningful commit messages, squash commits as appropriate.
+
+How to submit a PR:
+1. Fork the repo and create your feature/bugfix branch from `main`.
+2. If you've added code that should be tested, add tests.
+3. Ensure the test suite passes.
+4. Submit the pull request!
+
+
+## Any contributions you make will be under the GNU LGPL-3.0 Software License
+In short, when you submit code changes, your submissions are understood to be
+under the same [LGPL-3.0 License](https://choosealicense.com/licenses/lgpl-3.0/) that
+covers this project. Feel free to contact the maintainers if that's a concern.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..2a7f2ad
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..eafe2d0
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,2 @@
+recursive-include src/peakrdl_regblock *.sv
+prune tests
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8b554c1
--- /dev/null
+++ b/README.md
@@ -0,0 +1,12 @@
+[](http://peakrdl-regblock.readthedocs.io)
+[](https://github.com/SystemRDL/PeakRDL-regblock/actions?query=workflow%3Abuild+branch%3Amain)
+[](https://coveralls.io/github/SystemRDL/PeakRDL-regblock?branch=main)
+[](https://pypi.org/project/peakrdl-regblock)
+
+# PeakRDL-regblock
+Compile SystemRDL into a SystemVerilog control/status register (CSR) block.
+
+For the command line tool, see the [PeakRDL project](https://peakrdl.readthedocs.io).
+
+## Documentation
+See the [PeakRDL-regblock Documentation](https://peakrdl-regblock.readthedocs.io) for more details
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 0000000..d4bb2cb
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS ?=
+SPHINXBUILD ?= sphinx-build
+SOURCEDIR = .
+BUILDDIR = _build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/docs/api.rst b/docs/api.rst
new file mode 100644
index 0000000..69b809d
--- /dev/null
+++ b/docs/api.rst
@@ -0,0 +1,50 @@
+Exporter API
+============
+
+If you are not using the `PeakRDL command-line tool `_,
+you can still generate regblocks programmatically using the exporter API:
+
+.. autoclass:: peakrdl_regblock.RegblockExporter
+ :members:
+
+Example
+-------
+Below is a simple example that demonstrates how to generate a SystemVerilog
+implementation from SystemRDL source.
+
+.. code-block:: python
+ :emphasize-lines: 2-4, 29-33
+
+ from systemrdl import RDLCompiler, RDLCompileError
+ from peakrdl_regblock import RegblockExporter
+ from peakrdl_regblock.cpuif.axi4lite import AXI4Lite_Cpuif
+ from peakrdl_regblock.udps import ALL_UDPS
+
+ input_files = [
+ "PATH/TO/my_register_block.rdl"
+ ]
+
+ # Create an instance of the compiler
+ rdlc = RDLCompiler()
+
+ # Register all UDPs that 'regblock' requires
+ for udp in ALL_UDPS:
+ rdlc.register_udp(udp)
+
+ try:
+ # Compile your RDL files
+ for input_file in input_files:
+ rdlc.compile_file(input_file)
+
+ # Elaborate the design
+ root = rdlc.elaborate()
+ except RDLCompileError:
+ # A compilation error occurred. Exit with error code
+ sys.exit(1)
+
+ # Export a SystemVerilog implementation
+ exporter = RegblockExporter()
+ exporter.export(
+ root, "path/to/output_dir",
+ cpuif_cls=AXI4Lite_Cpuif
+ )
diff --git a/docs/architecture.rst b/docs/architecture.rst
new file mode 100644
index 0000000..daa88f8
--- /dev/null
+++ b/docs/architecture.rst
@@ -0,0 +1,59 @@
+Register Block Architecture
+===========================
+
+The generated register block RTL is organized into several sections.
+Each section is automatically generated based on the source register model and
+is rendered into the output register block SystemVerilog RTL.
+
+.. figure:: diagrams/arch.png
+
+Although it is not completely necessary to know the inner workings of the
+generated RTL, it can be helpful to understand the implications of various
+exporter configuration options.
+
+
+CPU Interface
+-------------
+The CPU interface logic layer provides an abstraction between the
+application-specific bus protocol and the internal register file logic.
+This logic layer normalizes external CPU read & write transactions into a common
+:ref:`cpuif_protocol` that is used to interact with the register file.
+
+
+Address Decode
+--------------
+A common address decode operation is generated which computes individual access
+strobes for each software-accessible register in the design.
+This operation is performed completely combinationally.
+
+
+Field Logic
+-----------
+This layer of the register block implements the storage elements and state-change
+logic for every field in the design. Field state is updated based on address
+decode strobes from software read/write actions, as well as events from the
+hardware interface input struct.
+This section also assigns any hardware interface outputs.
+
+
+Readback
+--------
+The readback layer aggregates and reduces all readable registers into a single
+read response. During a read operation, the same address decode strobes are used
+to select the active register that is being accessed.
+This allows for a simple OR-reduction operation to be used to compute the read
+data response.
+
+For designs with a large number of software-readable registers, an optional
+fanin re-timing stage can be enabled. This stage is automatically inserted at a
+balanced point in the read-data reduction so that fanin and logic-levels are
+optimally reduced.
+
+.. figure:: diagrams/readback.png
+ :width: 65%
+ :align: center
+
+A second optional read response retiming register can be enabled in-line with the
+path back to the CPU interface layer. This can be useful if the CPU interface protocol
+used has a fully combinational response path, and the design's complexity requires
+this path to be retimed further.
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 0000000..6a3f472
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,89 @@
+# Configuration file for the Sphinx documentation builder.
+#
+# This file only contains a selection of the most common options. For a full
+# list see the documentation:
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+
+# -- Path setup --------------------------------------------------------------
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+import os
+import sys
+sys.path.insert(0, os.path.abspath('../src/'))
+
+import datetime
+
+# -- Project information -----------------------------------------------------
+
+project = 'PeakRDL-regblock'
+copyright = '%d, Alex Mykyta' % datetime.datetime.now().year
+author = 'Alex Mykyta'
+
+
+# -- General configuration ---------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+ 'sphinx.ext.autodoc',
+ 'sphinx.ext.napoleon',
+ "sphinxcontrib.wavedrom",
+]
+render_using_wavedrompy = True
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path.
+exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+
+
+# -- Options for HTML output -------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+#
+html_theme = "sphinx_book_theme"
+
+html_theme_options = {
+ "repository_url": "https://github.com/SystemRDL/PeakRDL-regblock",
+ "path_to_docs": "docs",
+ "use_download_button": False,
+ "use_source_button": True,
+ "use_repository_button": True,
+ "use_issues_button": True,
+}
+
+
+# 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 = []
+
+
+rst_epilog = """
+.. |iERR| image:: /img/err.svg
+ :width: 18px
+ :class: no-scaled-link
+
+.. |iWARN| image:: /img/warn.svg
+ :width: 18px
+ :class: no-scaled-link
+
+.. |iOK| image:: /img/ok.svg
+ :width: 18px
+ :class: no-scaled-link
+
+.. |NO| replace:: |iERR| Not Supported
+
+.. |EX| replace:: |iWARN| Experimental
+
+.. |OK| replace:: |iOK| Supported
+
+"""
diff --git a/docs/configuring.rst b/docs/configuring.rst
new file mode 100644
index 0000000..6be6f63
--- /dev/null
+++ b/docs/configuring.rst
@@ -0,0 +1,45 @@
+.. _peakrdl_cfg:
+
+Configuring PeakRDL-regblock
+============================
+
+If using the `PeakRDL command line tool `_,
+some aspects of the ``regblock`` command have additional configuration options
+available via the PeakRDL TOML file.
+
+All regblock-specific options are defined under the ``[regblock]`` TOML heading.
+
+.. data:: cpuifs
+
+ Mapping of additional CPU Interface implementation classes to load.
+ The mapping's key indicates the cpuif's name.
+ The value is a string that describes the import path and cpuif class to
+ load.
+
+ For example:
+
+ .. code-block:: toml
+
+ [regblock]
+ cpuifs.my-cpuif-name = "my_cpuif_module:MyCPUInterfaceClass"
+
+
+.. data:: default_reset
+
+ Choose the default style of reset signal if not explicitly
+ specified by the SystemRDL design. If unspecified, the default reset
+ is active-high and synchronous.
+
+ Choice of:
+
+ * ``rst`` (default)
+ * ``rst_n``
+ * ``arst``
+ * ``arst_n``
+
+ For example:
+
+ .. code-block:: toml
+
+ [regblock]
+ default_reset = "arst"
diff --git a/docs/cpuif/apb.rst b/docs/cpuif/apb.rst
new file mode 100644
index 0000000..efec7d8
--- /dev/null
+++ b/docs/cpuif/apb.rst
@@ -0,0 +1,59 @@
+AMBA APB
+========
+
+Both APB3 and APB4 standards are supported.
+
+.. warning::
+ Some IP vendors will incorrectly implement the address signalling
+ assuming word-addresses. (that each increment of ``PADDR`` is the next word)
+
+ For this exporter, values on the interface's ``PADDR`` input are interpreted
+ as byte-addresses. (an APB interface with 32-bit wide data increments
+ ``PADDR`` in steps of 4 for every word). Even though APB protocol does not
+ allow for unaligned transfers, this is in accordance to the official AMBA
+ specification.
+
+ Be sure to double-check the interpretation of your interconnect IP. A simple
+ bit-shift operation can be used to correct this if necessary.
+
+
+APB3
+----
+
+Implements the register block using an
+`AMBA 3 APB `_
+CPU interface.
+
+The APB3 CPU interface comes in two i/o port flavors:
+
+SystemVerilog Interface
+ * Command line: ``--cpuif apb3``
+ * Interface Definition: :download:`apb3_intf.sv <../../hdl-src/apb3_intf.sv>`
+ * Class: :class:`peakrdl_regblock.cpuif.apb3.APB3_Cpuif`
+
+Flattened inputs/outputs
+ Flattens the interface into discrete input and output ports.
+
+ * Command line: ``--cpuif apb3-flat``
+ * Class: :class:`peakrdl_regblock.cpuif.apb3.APB3_Cpuif_flattened`
+
+
+APB4
+----
+
+Implements the register block using an
+`AMBA 4 APB `_
+CPU interface.
+
+The APB4 CPU interface comes in two i/o port flavors:
+
+SystemVerilog Interface
+ * Command line: ``--cpuif apb4``
+ * Interface Definition: :download:`apb4_intf.sv <../../hdl-src/apb4_intf.sv>`
+ * Class: :class:`peakrdl_regblock.cpuif.apb4.APB4_Cpuif`
+
+Flattened inputs/outputs
+ Flattens the interface into discrete input and output ports.
+
+ * Command line: ``--cpuif apb4-flat``
+ * Class: :class:`peakrdl_regblock.cpuif.apb4.APB4_Cpuif_flattened`
diff --git a/docs/cpuif/avalon.rst b/docs/cpuif/avalon.rst
new file mode 100644
index 0000000..2ae8a04
--- /dev/null
+++ b/docs/cpuif/avalon.rst
@@ -0,0 +1,33 @@
+Intel Avalon
+============
+
+Implements the register block using an
+`Intel Avalon MM `_
+CPU interface.
+
+The Avalon interface comes in two i/o port flavors:
+
+SystemVerilog Interface
+ * Command line: ``--cpuif avalon-mm``
+ * Interface Definition: :download:`avalon_mm_intf.sv <../../hdl-src/avalon_mm_intf.sv>`
+ * Class: :class:`peakrdl_regblock.cpuif.avalon.Avalon_Cpuif`
+
+Flattened inputs/outputs
+ Flattens the interface into discrete input and output ports.
+
+ * Command line: ``--cpuif avalon-mm-flat``
+ * Class: :class:`peakrdl_regblock.cpuif.avalon.Avalon_Cpuif_flattened`
+
+
+Implementation Details
+----------------------
+This implementation of the Avalon protocol has the following features:
+
+* Interface uses word addressing.
+* Supports `pipelined transfers `_
+* Responses may have variable latency
+
+ In most cases, latency is fixed and is determined by how many retiming
+ stages are enabled in your design.
+ However if your design contains external components, access latency is
+ not guaranteed to be uniform.
diff --git a/docs/cpuif/axi4lite.rst b/docs/cpuif/axi4lite.rst
new file mode 100644
index 0000000..255be14
--- /dev/null
+++ b/docs/cpuif/axi4lite.rst
@@ -0,0 +1,32 @@
+.. _cpuif_axi4lite:
+
+AMBA AXI4-Lite
+==============
+
+Implements the register block using an
+`AMBA AXI4-Lite `_
+CPU interface.
+
+The AXI4-Lite CPU interface comes in two i/o port flavors:
+
+SystemVerilog Interface
+ * Command line: ``--cpuif axi4-lite``
+ * Interface Definition: :download:`axi4lite_intf.sv <../../hdl-src/axi4lite_intf.sv>`
+ * Class: :class:`peakrdl_regblock.cpuif.axi4lite.AXI4Lite_Cpuif`
+
+Flattened inputs/outputs
+ Flattens the interface into discrete input and output ports.
+
+ * Command line: ``--cpuif axi4-lite-flat``
+ * Class: :class:`peakrdl_regblock.cpuif.axi4lite.AXI4Lite_Cpuif_flattened`
+
+
+Pipelined Performance
+---------------------
+This implementation of the AXI4-Lite interface supports transaction pipelining
+which can significantly improve performance of back-to-back transfers.
+
+In order to support transaction pipelining, the CPU interface will accept multiple
+concurrent transactions. The number of outstanding transactions allowed is automatically
+determined based on the register file pipeline depth (affected by retiming options),
+and influences the depth of the internal transaction response skid buffer.
diff --git a/docs/cpuif/customizing.rst b/docs/cpuif/customizing.rst
new file mode 100644
index 0000000..dc806c5
--- /dev/null
+++ b/docs/cpuif/customizing.rst
@@ -0,0 +1,114 @@
+Customizing the CPU interface
+=============================
+
+Use your own existing SystemVerilog interface definition
+--------------------------------------------------------
+
+This exporter comes pre-bundled with its own SystemVerilog interface declarations.
+What if you already have your own SystemVerilog interface declaration that you prefer?
+
+Not a problem! As long as your interface definition is similar enough, it is easy
+to customize and existing CPUIF definition.
+
+
+As an example, let's use the SystemVerilog interface definition for
+:ref:`cpuif_axi4lite` that is bundled with this project. This interface uses
+the following style and naming conventions:
+
+* SystemVerilog interface type name is ``axi4lite_intf``
+* Defines modports named ``master`` and ``slave``
+* Interface signals are all upper-case: ``AWREADY``, ``AWVALID``, etc...
+
+Lets assume your preferred SV interface definition uses a slightly different naming convention:
+
+* SystemVerilog interface type name is ``axi4_lite_interface``
+* Modports are capitalized and use suffixes ``Master_mp`` and ``Slave_mp``
+* Interface signals are all lower-case: ``awready``, ``awvalid``, etc...
+
+Rather than rewriting a new CPU interface definition, you can extend and adjust the existing one:
+
+.. code-block:: python
+
+ from peakrdl_regblock.cpuif.axi4lite import AXI4Lite_Cpuif
+
+ class My_AXI4Lite(AXI4Lite_Cpuif):
+ @property
+ def port_declaration(self) -> str:
+ # Override the port declaration text to use the alternate interface name and modport style
+ return "axi4_lite_interface.Slave_mp s_axil"
+
+ def signal(self, name:str) -> str:
+ # Override the signal names to be lowercase instead
+ return "s_axil." + name.lower()
+
+Then use your custom CPUIF during export:
+
+.. code-block:: python
+
+ exporter = RegblockExporter()
+ exporter.export(
+ root, "path/to/output_dir",
+ cpuif_cls=My_AXI4Lite
+ )
+
+
+
+Custom CPU Interface Protocol
+-----------------------------
+
+If you require a CPU interface protocol that is not included in this project,
+you can define your own.
+
+1. Create a SystemVerilog CPUIF implementation template file.
+
+ This contains the SystemVerilog implementation of the bus protocol. The logic
+ in this shall implement a translation between your custom protocol and the
+ :ref:`cpuif_protocol`.
+
+ Reminder that this template will be preprocessed using
+ `Jinja `_, so you can use
+ some templating tags to dynamically render content. See the implementations of
+ existing CPU interfaces as an example.
+
+2. Create a Python class that defines your CPUIF
+
+ Extend your class from :class:`peakrdl_regblock.cpuif.CpuifBase`.
+ Define the port declaration string, and provide a reference to your template file.
+
+3. Use your new CPUIF definition when exporting.
+4. If you think the CPUIF protocol is something others might find useful, let me
+ know and I can add it to PeakRDL!
+
+
+Loading into the PeakRDL command line tool
+------------------------------------------
+There are two ways to make your custom CPUIF class visible to the
+`PeakRDL command-line tool `_.
+
+Via the PeakRDL TOML
+^^^^^^^^^^^^^^^^^^^^
+The easiest way to add your cpuif is via the TOML config file. See the
+:ref:`peakrdl_cfg` section for more details.
+
+Via a package's entry point definition
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+If you are publishing a collection of PeakRDL plugins as an installable Python
+package, you can advertise them to PeakRDL using an entry point.
+This advertises your custom CPUIF class to the PeakRDL-regblock tool as a plugin
+that should be loaded, and made available as a command-line option in PeakRDL.
+
+.. code-block:: toml
+
+ [project.entry-points."peakrdl_regblock.cpuif"]
+ my-cpuif = "my_package.my_module:MyCPUIF"
+
+
+* ``my_package``: The name of your installable Python module
+* ``peakrdl-regblock.cpuif``: This is the namespace that PeakRDL-regblock will
+ search. Any cpuif plugins you create must be enclosed in this namespace in
+ order to be discovered.
+* ``my_package.my_module:MyCPUIF``: This is the import path that
+ points to your CPUIF class definition.
+* ``my-cpuif``: The lefthand side of the assignment is your cpuif's name. This
+ text is what the end-user uses in the command line interface to select your
+ CPUIF implementation.
diff --git a/docs/cpuif/internal_protocol.rst b/docs/cpuif/internal_protocol.rst
new file mode 100644
index 0000000..75f4515
--- /dev/null
+++ b/docs/cpuif/internal_protocol.rst
@@ -0,0 +1,232 @@
+.. _cpuif_protocol:
+
+Internal CPUIF Protocol
+=======================
+
+Internally, the regblock generator uses a common CPU interface handshake
+protocol. This strobe-based protocol is designed to add minimal overhead to the
+regblock implementation, while also being flexible enough to support advanced
+features of a variety of bus interface standards.
+
+
+Signal Descriptions
+-------------------
+
+Request
+^^^^^^^
+cpuif_req
+ When asserted, a read or write transfer will be initiated.
+ Denotes that the following signals are valid: ``cpuif_addr``,
+ ``cpuif_req_is_wr``, and ``cpuif_wr_data``.
+
+ A transfer will only initiate if the relevant stall signal is not asserted.
+ If stalled, the request shall be held until accepted. A request's parameters
+ (type, address, etc) shall remain static throughout the stall.
+
+cpuif_addr
+ Byte-address of the transfer.
+
+cpuif_req_is_wr
+ If ``1``, denotes that the current transfer is a write. Otherwise transfer is
+ a read.
+
+cpuif_wr_data
+ Data to be written for the write transfer. This signal is ignored for read
+ transfers.
+
+cpuif_wr_biten
+ Active-high bit-level write-enable strobes.
+ Only asserted bit positions will change the register value during a write
+ transfer.
+
+cpuif_req_stall_rd
+ If asserted, and the next pending request is a read operation, then the
+ transfer will not be accepted until this signal is deasserted.
+
+cpuif_req_stall_wr
+ If asserted, and the next pending request is a write operation, then the
+ transfer will not be accepted until this signal is deasserted.
+
+
+Read Response
+^^^^^^^^^^^^^
+cpuif_rd_ack
+ Single-cycle strobe indicating a read transfer has completed.
+ Qualifies that the following signals are valid: ``cpuif_rd_err`` and
+ ``cpuif_rd_data``
+
+cpuif_rd_err
+ If set, indicates that the read transaction failed and the CPUIF logic
+ should return an error response if possible.
+
+cpuif_rd_data
+ Read data. Is sampled on the same cycle that ``cpuif_rd_ack`` is asserted.
+
+Write Response
+^^^^^^^^^^^^^^
+cpuif_wr_ack
+ Single-cycle strobe indicating a write transfer has completed.
+ Qualifies that the ``cpuif_wr_err`` signal is valid.
+
+cpuif_wr_err
+ If set, indicates that the write transaction failed and the CPUIF logic
+ should return an error response if possible.
+
+
+Transfers
+---------
+
+Transfers have the following characteristics:
+
+* Only one transfer can be initiated per clock-cycle. This is implicit as there
+ is only one set of request signals.
+* The register block implementation shall guarantee that only one response can be
+ asserted in a given clock cycle. Only one ``cpuif_*_ack`` signal can be
+ asserted at a time.
+* Responses shall arrive in the same order as their corresponding request was
+ dispatched.
+
+
+Basic Transfer
+^^^^^^^^^^^^^^
+
+Depending on the configuration of the exported register block, transfers can be
+fully combinational or they may require one or more clock cycles to complete.
+Both are valid and CPU interface logic shall be designed to anticipate either.
+
+.. wavedrom::
+
+ {
+ "signal": [
+ {"name": "clk", "wave": "p...."},
+ {"name": "cpuif_req", "wave": "010.."},
+ {"name": "cpuif_req_is_wr", "wave": "x2x.."},
+ {"name": "cpuif_addr", "wave": "x2x..", "data": ["A"]},
+ {},
+ {"name": "cpuif_*_ack", "wave": "010.."},
+ {"name": "cpuif_*_err", "wave": "x2x.."}
+ ],
+ "foot": {
+ "text": "Zero-latency transfer"
+ }
+ }
+
+.. wavedrom::
+
+ {
+ "signal": [
+ {"name": "clk", "wave": "p..|..."},
+ {"name": "cpuif_req", "wave": "010|..."},
+ {"name": "cpuif_req_is_wr", "wave": "x2x|..."},
+ {"name": "cpuif_addr", "wave": "x2x|...", "data": ["A"]},
+ {},
+ {"name": "cpuif_*_ack", "wave": "0..|10."},
+ {"name": "cpuif_*_err", "wave": "x..|2x."}
+ ],
+ "foot": {
+ "text": "Transfer with non-zero latency"
+ }
+ }
+
+
+Read & Write Transactions
+-------------------------
+
+Waveforms below show the timing relationship of simple read/write transactions.
+For brevity, only showing non-zero latency transfers.
+
+.. wavedrom::
+
+ {
+ "signal": [
+ {"name": "clk", "wave": "p..|..."},
+ {"name": "cpuif_req", "wave": "010|..."},
+ {"name": "cpuif_req_is_wr", "wave": "x0x|..."},
+ {"name": "cpuif_addr", "wave": "x3x|...", "data": ["A"]},
+ {},
+ {"name": "cpuif_rd_ack", "wave": "0..|10."},
+ {"name": "cpuif_rd_err", "wave": "x..|0x."},
+ {"name": "cpuif_rd_data", "wave": "x..|5x.", "data": ["D"]}
+ ],
+ "foot": {
+ "text": "Read Transaction"
+ }
+ }
+
+
+.. wavedrom::
+
+ {
+ "signal": [
+ {"name": "clk", "wave": "p..|..."},
+ {"name": "cpuif_req", "wave": "010|..."},
+ {"name": "cpuif_req_is_wr", "wave": "x1x|..."},
+ {"name": "cpuif_addr", "wave": "x3x|...", "data": ["A"]},
+ {"name": "cpuif_wr_data", "wave": "x5x|...", "data": ["D"]},
+ {},
+ {"name": "cpuif_wr_ack", "wave": "0..|10."},
+ {"name": "cpuif_wr_err", "wave": "x..|0x."}
+ ],
+ "foot": {
+ "text": "Write Transaction"
+ }
+ }
+
+
+Transaction Pipelining & Stalls
+-------------------------------
+If the CPU interface supports it, read and write operations can be pipelined.
+
+.. wavedrom::
+
+ {
+ "signal": [
+ {"name": "clk", "wave": "p......"},
+ {"name": "cpuif_req", "wave": "01..0.."},
+ {"name": "cpuif_req_is_wr", "wave": "x0..x.."},
+ {"name": "cpuif_addr", "wave": "x333x..", "data": ["A1", "A2", "A3"]},
+ {},
+ {"name": "cpuif_rd_ack", "wave": "0.1..0."},
+ {"name": "cpuif_rd_err", "wave": "x.0..x."},
+ {"name": "cpuif_rd_data", "wave": "x.555x.", "data": ["D1", "D2", "D3"]}
+ ]
+ }
+
+It is very likely that the transfer latency of a read transaction will not
+be the same as a write for a given register block configuration. Typically read
+operations will be more deeply pipelined. This latency asymmetry would create a
+hazard for response collisions.
+
+In order to eliminate this hazard, additional stall signals (``cpuif_req_stall_rd``
+and ``cpuif_req_stall_wr``) are provided to delay the next incoming transfer
+request if necessary. When asserted, the CPU interface shall hold the next pending
+request until the stall is cleared.
+
+For non-pipelined CPU interfaces that only allow one outstanding transaction at a time,
+these stall signals can be safely ignored.
+
+In the following example, the regblock is configured such that:
+
+* A read transaction takes 1 clock cycle to complete
+* A write transaction takes 0 clock cycles to complete
+
+.. wavedrom::
+
+ {
+ "signal": [
+ {"name": "clk", "wave": "p......."},
+ {"name": "cpuif_req", "wave": "01.....0"},
+ {"name": "cpuif_req_is_wr", "wave": "x1.0.1.x"},
+ {"name": "cpuif_addr", "wave": "x33443.x", "data": ["W1", "W2", "R1", "R2", "W3"]},
+ {"name": "cpuif_req_stall_wr", "wave": "0...1.0."},
+ {},
+ {"name": "cpuif_rd_ack", "wave": "0...220.", "data": ["R1", "R2"]},
+ {"name": "cpuif_wr_ack", "wave": "0220..20", "data": ["W1", "W2", "W3"]}
+ ]
+ }
+
+In the above waveform, observe that:
+
+* The ``R2`` read request is not affected by the assertion of the write stall,
+ since the write stall only applies to write requests.
+* The ``W3`` write request is stalled for one cycle, and is accepted once the stall is cleared.
diff --git a/docs/cpuif/introduction.rst b/docs/cpuif/introduction.rst
new file mode 100644
index 0000000..126bc0e
--- /dev/null
+++ b/docs/cpuif/introduction.rst
@@ -0,0 +1,36 @@
+Introduction
+============
+
+The CPU interface logic layer provides an abstraction between the
+application-specific bus protocol and the internal register file logic.
+When exporting a design, you can select from a variety of popular CPU interface
+protocols. These are described in more detail in the pages that follow.
+
+
+Bus Width
+^^^^^^^^^
+The CPU interface bus width is automatically determined from the contents of the
+design being exported. The bus width is equal to the widest ``accesswidth``
+encountered in the design.
+
+
+Addressing
+^^^^^^^^^^
+
+The regblock exporter will always generate its address decoding logic using local
+address offsets. The absolute address offset of your device shall be
+handled by your system interconnect, and present addresses to the regblock that
+only include the local offset.
+
+For example, consider a fictional AXI4-Lite device that:
+
+- Consumes 4 kB of address space (``0x000``-``0xFFF``).
+- The device is instantiated in your system at global address range ``0x30_0000 - 0x50_0FFF``.
+- After decoding transactions destined to the device, the system interconnect shall
+ ensure that AxADDR values are presented to the device as relative addresses - within
+ the range of ``0x000``-``0xFFF``.
+- If care is taken to align the global address offset to the size of the device,
+ creating a relative address is as simple as pruning down address bits.
+
+By default, the bit-width of the address bus will be the minimum size to span the contents
+of the register block. If needed, the address width can be overridden to a larger range.
diff --git a/docs/cpuif/passthrough.rst b/docs/cpuif/passthrough.rst
new file mode 100644
index 0000000..0bfb2d7
--- /dev/null
+++ b/docs/cpuif/passthrough.rst
@@ -0,0 +1,10 @@
+CPUIF Passthrough
+=================
+
+This CPUIF mode bypasses the protocol converter stage and directly exposes the
+internal CPUIF handshake signals to the user.
+
+* Command line: ``--cpuif passthrough``
+* Class: :class:`peakrdl_regblock.cpuif.passthrough.PassthroughCpuif`
+
+For more details on the protocol itself, see: :ref:`cpuif_protocol`.
diff --git a/docs/dev_notes/Alpha-Beta Versioning b/docs/dev_notes/Alpha-Beta Versioning
new file mode 100644
index 0000000..c8a089d
--- /dev/null
+++ b/docs/dev_notes/Alpha-Beta Versioning
@@ -0,0 +1,10 @@
+Holy smokes this is complicated
+
+Keep this exporter in Alpha/Beta for a while
+Add some text in the readme or somewhere:
+ - No guarantees of correctness! This is always true with open source software,
+ but even more here!
+ Be sure to do your own validation before using this in production.
+ - Alpha means the implementation may change drastically!
+ Unlike official sem-ver, I am not making any guarantees on compatibility
+ - I need your help! Validating, finding edge cases, etc...
diff --git a/docs/dev_notes/Hierarchy-and-Indexing b/docs/dev_notes/Hierarchy-and-Indexing
new file mode 100644
index 0000000..9819b31
--- /dev/null
+++ b/docs/dev_notes/Hierarchy-and-Indexing
@@ -0,0 +1,67 @@
+--------------------------------------------------------------------------------
+Preserve Hierarchy
+--------------------------------------------------------------------------------
+I *reaaaally* want to be able to make deferred RDL parameters a reality in the
+future. (https://github.com/SystemRDL/systemrdl-compiler/issues/58)
+
+Proactively design templates to retain "real" hierarchy. This means:
+- Do not flatten/unroll signals. Use SV structs & arrays
+- Do not flatten/unroll logic. Use nested for loops
+
+Sticking to the above should make adding parameter support somewhat less painful.
+
+--------------------------------------------------------------------------------
+Indexing & references
+--------------------------------------------------------------------------------
+Need to define a consistent scheme for referencing hierarchical elements.
+
+When inside a nesting of for loops, and array indexes are intended to increment,
+always use an incrementing indexing scheme when generating iterators:
+ i0, i1, i2, i3, ... i9, i10, i11, etc...
+For example:
+ access_strb.2d_array[i0][i1].array[i3]
+
+Sometimes, an RDL input may create the need to reference an element with
+partially constant indexes.
+For example, given this RDL:
+
+ addrmap top {
+ regfile {
+ reg {
+ field {} f1;
+ } x[8];
+
+ reg {
+ field {} f2;
+ } y;
+
+ y.f2->next = x[3].f1;
+
+ } rf_loop[16];
+ };
+
+The 'next' assignment will have a reference that has the following hierarchical
+path:
+ top.rf_loop[].x[3].f1
+ | |
+ | +--- known index
+ +--- unknown index
+
+It is provable that any RDL references will always follow these truths:
+ - a reference may have a mix of known/unknown indexes in its path
+ - unknown indexes (if any) will always precede known indexes
+ - unknown indexes are not actually part of the relative reference path, and
+ represent replication of the reference.
+ It is impossible for the reference itself to introduce unknown indexes.
+
+When generating SystemVerilog, be sure to generate code such that "unknown" indexes
+are always implicitly known due to the reference being used from within a for loop.
+For example:
+
+ for(int i0=0; i0<16; i0++) begin : rf_loop_array
+ top.rf_loop[i0].y.f2 = top.rf_loop[i0].x[3].f1
+ end
+
+This should be a reasonable thing to accomplish, since unknown indexes should
+only show up in situations where the consumer of the reference is being
+replicated as well, and is therefore implicitly going to be inside a for loop.
diff --git a/docs/dev_notes/Program Flow b/docs/dev_notes/Program Flow
new file mode 100644
index 0000000..a07ff23
--- /dev/null
+++ b/docs/dev_notes/Program Flow
@@ -0,0 +1,23 @@
+
+1. Scan design. Collect information
+ - Check for unsupported constructs. Throw errors as appropriate
+ - Uniform regwidth, accesswidth, etc.
+
+ - Collect reset signals
+ cpuif_reset, field_reset
+ explicitly assigned to field->resetsignal
+
+ - Collect any other misc user signals that are referenced in the design
+
+ - Top-level interrupts
+ Collect X & Y:
+ X = set of all registers that have an interrupt field
+ Y = set of all interrupt registers that are referenced by a field
+ Top level interrupt registers are the set in X, but not in Y
+ (and probably other caveats. See notes)
+
+2. Create intermediate template objects
+
+3. Render top-level IO struct package (if applicable)
+
+4. Render top-level module template
diff --git a/docs/dev_notes/Resets b/docs/dev_notes/Resets
new file mode 100644
index 0000000..2b7ad14
--- /dev/null
+++ b/docs/dev_notes/Resets
@@ -0,0 +1,11 @@
+================================================================================
+Resets
+================================================================================
+use whatever is defined in RDL based on cpuif_reset and field_reset signals
+Otherwise, provide configuration that defines what the default is:
+ a single reset that is active high/low, or sync/async
+
+If cpuif_reset is specified, what do fields use?
+ I assume they still use the default reset separately?
+ YES. Agnisys appears to be wrong.
+ cpuif_reset has no influence on the fields' reset according to the spec
diff --git a/docs/dev_notes/Signal Dereferencer b/docs/dev_notes/Signal Dereferencer
new file mode 100644
index 0000000..6b8922c
--- /dev/null
+++ b/docs/dev_notes/Signal Dereferencer
@@ -0,0 +1,22 @@
+I need some sort of signal "dereferencer" that can be easily used to translate references
+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')
+ y = dereferencer.get(x)
+and trust that I'll get a value/identifier/whatever that accurately represents
+the value being referenced
+
+Values:
+ If X is a field reference:
+ ... that implements storage, return its DFF value reference
+ ... no storage, but has a hw input, grab from the hwif input
+ ... no storage, and no hw input, return its constant reset value?
+ If X is a property reference... do whats right...
+ my_field->anded === (&path.to.my_field)
+ if X is a static value, return the literal
+
+
+See `Hierarchy and Indexing` on details on how to build path references to stuff
diff --git a/docs/dev_notes/Validation Needed b/docs/dev_notes/Validation Needed
new file mode 100644
index 0000000..6377ec2
--- /dev/null
+++ b/docs/dev_notes/Validation Needed
@@ -0,0 +1,183 @@
+
+================================================================================
+Things that need validation by the compiler
+================================================================================
+Many of these are probably already covered, but being paranoid.
+Make a list of things as I think of them.
+Keep them here in case I forget and re-think of them.
+
+Mark these as follows:
+ X = Yes, confirmed that the compiler covers this
+ ! = No! Confirmed that the compiler does not check this, and should.
+ ? = TBD
+
+--------------------------------------------------------------------------------
+
+X resetsignal width
+ reset signals shall have width of 1
+
+X Field has no knowable value
+ - does not implement storage
+ - hw is not writable
+ - sw is readable
+ - No reset value specified
+
+ --> emit a warning?
+
+X References to a component or component property must use unambiguous array indexing
+ For example, if "array_o_regs" is an array...
+ The following is illegal:
+ my_reg.my_field->next = array_o_regs.thing
+ my_reg.my_field->next = array_o_regs.thing->anded
+ This is ok:
+ my_reg.my_field->next = array_o_regs[2].thing
+ my_reg.my_field->next = array_o_regs[2].thing->anded
+
+ NEVERMIND - compiler does not allow indefinite array references at all!
+ References are guaranteed to be unambiguous:
+ "Incompatible number of index dimensions after 'CTRL'. Expected 1, found 0."
+
+X Clause 10.6.1-f (wide registers cannot have access side-effects)
+
+X multiple field_reset in the same hierarchy
+ there can only be one signal declared with field_reset
+ in a given hierarchy
+
+X multiple cpuif_reset in the same hierarchy
+ there can only be one signal declared with cpuif_reset
+ in a given hierarchy
+
+X Mutually-exclusive property checking
+ --> Yes. compiler now auto-clears mutex partners on assign, so it is
+ implicitly handled
+
+X incrwidth/incrvalue & decrvalue/decrwidth
+ these pairs are mutually exclusive.
+ Make sure they are not both set after elaboration
+ Compiler checks for mutex within the same scope, but
+ i dont think I check for mutexes post-elaborate
+
+ ... or, make these properties clear each-other on assignment
+
+X Illegal property references:
+ - reference any of the counter property references to something that isn't a counter
+ decrsaturate / incrsaturate / saturate
+ overflow / underflow
+ - reference hwclr or hwset, but the owner node has them set to False
+ means that the actual inferred signal doesnt exist!
+ - reference swwe/swwel or we/wel, but the owner node has them, AND their complement set to False
+ means that the actual inferred signal doesnt exist!
+ - only valid to reference if owner has this prop set
+ enable/mask
+ haltenable/haltmask
+ hwenable
+ hwmask
+ decr/incr, decr../incrthreshold/..value
+ - others references that may not always make sense:
+ intr/halt - target must contain interrupt/halt fields
+ next
+ is this ever illegal?
+
+X If a node ispresent=true, and any of its properties are a reference,
+ then those references' ispresent shall also be true
+ This is an explicit clause in the spec: 5.3.1-i
+
+X Flag illegal sw actions if not readable/writable
+ The following combinations dont get flagged currently:
+ sw=w; rclr;
+ sw=w; rset;
+ sw=r; woset;
+ sw=r; woclr;
+ 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 applies to signals referenced by resetsignal
+
+X incrvalue/decrvalue needs to be the same or narrower than counter itself
+
+X field shall be hw writable if "next" is assigned.
+
+X sticky=true + "(posedge|negedge|bothedge) intr"
+ Edge-sensitivty doesnt make sense for full-field stickiness
+
+X we/wel + implied or explicit "sticky"/"stickybit"
+ we/wel modifier doesn't make sense here.
+
+X sticky/stickybit shall be hw writable
+
+X Illegal to use enable/mask/haltenable/haltmask on non-intr fields
+
+X incrwidth/decrwidth must be between 1 and the width of the counter
+
+X counter field that saturates should not set overflow
+ counter; incrsaturate; overflow;
+ counter; decrsaturate; underflow;
+
+ Flag this as an error on the overflow/underflow property.
+ overflow/underflow property is meaningless since it can never happen.
+
+ Same goes to prop references to overflow/underflow
+
+! 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
+
+================================================================================
+Things that need validation by this exporter
+================================================================================
+List of stuff in case I forget.
+ X = Yes! I already implemented this.
+ ! = No! exporter does not enforce this yet
+
+--------------------------------------------------------------------------------
+
+X Contents of target are all internal. No external regs
+
+X Does not contain any mem components
+
+X Warn/error on any signal with cpuif_reset set, that is not in the top-level
+ addrmap. At the very least, warn that it will be ignored
+
+
+X "bridge" addrmap not supported
+ export shall refuse to process an addrmap marked as a "bridge"
+ Only need to check top-level. Compiler will enforce that child nodes arent bridges
+
+X regwidth/accesswidth is sane
+ X accesswidth == regwidth
+ Enforce this for now. Dont feel like supporting fancy modes yet
+ X regwidth < accesswidth
+ This is illegal and is enforced by the compiler.
+ X regwidth > accesswidth
+ Need to extend address decode strobes to have multiple bits
+ this is where looking at endianness matters to determine field placement
+ Dont feel like supporting this yet
+ X constant regwidth?
+ For now, probably limit to only allow the same regwidth everywhere?
+
+
+X Do not allow unaligned addresses
+ All offsets & strides shall be a multiple of the regwidth used
+
+ X each reg needs to be aligned to its width
+ X each regfile/addrmap/stride shall be aligned to the largest regwidth it encloses
+
+X Error if a property is a reference to something that is external, or enclosed
+ in an external component.
+ Limit this check to child nodes inside the export hierarchy
+
+! async data signals
+ Only supporting async signals if they are exclusively used in resets.
+ Anything 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
+ outside the export hierarchy
+
+! Add warning for sticky race condition
+ stickybit and other similar situations generally should use hw precedence.
+ Emit a warning as appropriate
+ Or should this be a compiler warning??
diff --git a/docs/dev_notes/template-layers/1-port-declaration b/docs/dev_notes/template-layers/1-port-declaration
new file mode 100644
index 0000000..f4df391
--- /dev/null
+++ b/docs/dev_notes/template-layers/1-port-declaration
@@ -0,0 +1,51 @@
+--------------------------------------------------------------------------------
+Port Declaration
+--------------------------------------------------------------------------------
+Generates the port declaration of the module:
+ - Parameters
+ - rd/wr error response/data behavior
+ Do missed accesses cause a SLVERR?
+ Do reads respond with a magic value?
+ - Pipeline enables
+ Enable reg stages in various places
+
+ - RDL-derived Parameters:
+ Someday in the future if i ever get around to this: https://github.com/SystemRDL/systemrdl-compiler/issues/58
+
+ - Clock/Reset
+ Single clk
+ One or more resets
+
+ - CPU Bus Interface
+ Given the bus interface object, emits the IO
+ This can be flattened ports, or a SV Interface
+ Regardless, it shall be malleable so that the user can use their favorite
+ declaration style
+
+ - Hardware interface
+ Two options:
+ - 2-port struct interface
+ Everything is rolled into two unpacked structs - inputs and outputs
+ - Flattened --> NOT DOING
+ Flatten/Unroll everything
+ No. not doing. I hate this and dont want to waste time implementing this.
+ This will NEVER be able to support parameterized regmaps, and just
+ creates a ton of corner cases i dont care to deal with.
+
+Other IO Signals I need to be aware of:
+ any signals declared, and used in any references:
+ field.resetsignal
+ field.next
+ ... etc ...
+ any signals declared and marked as cpuif_reset, or field_reset
+ These override the default rst
+ If both are defined, be sure to not emit the default
+ Pretty straightforward (see 17.1)
+ Also have some notes on this in my general Logbook
+ Will have to make a call on how these propagate if multiple defined
+ in different hierarchies
+ interrupt/halt outputs
+ See "Interrupts" logbook for explanation
+ addrmap.errextbus, regfile.errextbus, reg.errextbus
+ ???
+ Apparently these are inputs
diff --git a/docs/dev_notes/template-layers/1.1.hardware-interface b/docs/dev_notes/template-layers/1.1.hardware-interface
new file mode 100644
index 0000000..e23442a
--- /dev/null
+++ b/docs/dev_notes/template-layers/1.1.hardware-interface
@@ -0,0 +1,103 @@
+================================================================================
+Summary
+================================================================================
+
+RTL interface that provides access to per-field context signals
+
+Regarding signals:
+ RDL-declared signals are part of the hwif input structure.
+ Only include them if they are referenced by the design (need to scan the
+ full design anyways, so may as well filter out unreferenced ones)
+
+ It is possible to use signals declared in a parent scope.
+ This means that not all signals will be discovered by a hierarchical listener alone
+ Need to scan ALL assigned properties for signal references too.
+ - get signal associated with top node's cpuif_reset helper property, if any
+ - collect all field_resets
+ X check all signal instances in the hier tree
+ - search parents of top node for the first field_reset signal, if any
+ This is WAY less expensive than querying EACH field's resetsignal property
+ X Check all explicitly assigned properties
+ only need to do this for fields
+ Collect all of these into the following:
+ - If inside the hier, add to a list of paths
+ - if outside the hier, add to a dict of path:SignalNode
+ These are all the signals in-use by the design
+
+ Pass list into the hwif generator
+ If the hwif generator encounters a signal during traversal:
+ check if it exists in the signal path list
+
+ out-of-hier signals are inserted outside of the hwif_in as standalone signals.
+ For now, just use their plain inst names. If I need to uniquify them i can add that later.
+ I should at least check against a list of known "dirty words". Seems very likely someone will choose
+ a signal called "rst".
+ Prefix with usersig_ if needed
+
+
+
+
+================================================================================
+Naming Scheme
+================================================================================
+
+hwif_out
+ .my_regblock
+ .my_reg[X][Y]
+ .my_field
+ .value
+ .anded
+
+hwif_in
+ .my_regblock
+ .my_reg[X][Y]
+ .my_field
+ .value
+ .we
+ .my_signal
+ .my_fieldreset_signal
+
+================================================================================
+Flattened mode? --> NO
+================================================================================
+If user wants a flattened list of ports,
+still use the same hwif_in/out struct internally.
+Rather than declaring hwif_in and hwif_out in the port list, declare it internally
+
+Add a mapping layer in the body of the module that performs a ton of assign statements
+to map flat signals <-> struct
+
+Alternatively, don't do this at all.
+If I want to add a flattened mode, generate a wrapper module instead.
+
+Marking this as YAGNI for now.
+
+
+================================================================================
+IO Signals
+================================================================================
+
+Outputs:
+ field value
+ If hw readable
+ bitwise reductions
+ if anded, ored, xored == True, output a signal
+ swmod/swacc
+ event strobes
+
+Inputs:
+ field value
+ If hw writable
+ we/wel
+ if either is boolean, and true
+ not part of external hwif if reference
+ mutually exclusive
+ hwclr/hwset
+ if either is boolean, and true
+ not part of external hwif if reference
+ incr/decr
+ if counter=true, generate BOTH
+ incrvalue/decrvalue
+ if either incrwidth/decrwidth are set
+ signals!
+ any signal instances instantiated in the scope
diff --git a/docs/dev_notes/template-layers/2-CPUIF b/docs/dev_notes/template-layers/2-CPUIF
new file mode 100644
index 0000000..6eb025e
--- /dev/null
+++ b/docs/dev_notes/template-layers/2-CPUIF
@@ -0,0 +1,72 @@
+--------------------------------------------------------------------------------
+CPU Bus interface layer
+--------------------------------------------------------------------------------
+Provides an abstraction layer between the outside SoC's bus interface, and the
+internal register block's implementation.
+Converts a user-selectable bus protocol to generic register file signals.
+
+Upstream Signals:
+ Signal names are defined in the bus interface class and shall be malleable
+ to the user.
+ User can choose a flat signal interface, or a SV interface.
+ SV interface shall be easy to tweak since various orgs will use different
+ naming conventions in their library of interface definitions
+
+Downstream Signals:
+ - cpuif_req
+ - Single-cycle pulse
+ - Qualifies the following child signals:
+ - cpuif_req_is_wr
+ 1 denotes this is a write transfer
+ - cpuif_addr
+ Byte address
+ - cpuif_wr_data
+ - cpuif_wr_biten
+ per-bit strobes
+ some protocols may opt to tie this to all 1's
+ - cpuif_rd_ack
+ - Single-cycle pulse
+ - Qualifies the following child signals:
+ - cpuif_rd_data
+ - cpuif_rd_err
+
+ - cpuif_wr_ack
+ - Single-cycle pulse
+ - Qualifies the following child signals:
+ - cpuif_wr_err
+
+
+Misc thoughts
+- Internal cpuif_* signals use a strobe-based protocol:
+ - Unknown, but fixed latency
+ - Makes for easy pipelining if needed
+- Decided to keep cpuif_req signals common for read write:
+ This will allow address decode logic to be shared for read/write
+ Downside is split protocols like axi-lite can't have totally separate rd/wr
+ access lanes, but who cares?
+- separate response strobes
+ Not necessary to use, but this lets me independently pipeline read/write paths.
+ read path will need more time if readback mux is large
+- On multiple outstanding transactions
+ Currently, cpuif doesnt really support this. Goal was to make it easily pipelineable
+ without having to backfeed stall logic.
+ Could still be possible to do a "fly-by" pipeline with a more intelligent cpuif layer
+ Not worrying about this now.
+
+
+Implementation:
+ Implement this mainly as a Jinja template.
+ Upstream bus intf signals are fetched via busif class properties. Ex:
+ {{busif.signal('pready')}} <= '1;
+ This allows the actual SV or flattened signal to be emitted
+
+What protocols do I care about?
+ - AXI4 Lite
+ - Ignore AxPROT?
+ - APB3
+ - APB4
+ - Ignore pprot?
+ - AHB?
+ - Wishbone
+ - Generic
+ breakout the above signals as-is (reassign with a prefix or something)
diff --git a/docs/dev_notes/template-layers/3-address-decode b/docs/dev_notes/template-layers/3-address-decode
new file mode 100644
index 0000000..b835c1c
--- /dev/null
+++ b/docs/dev_notes/template-layers/3-address-decode
@@ -0,0 +1,51 @@
+
+--------------------------------------------------------------------------------
+Address Decode layer
+--------------------------------------------------------------------------------
+A bunch of combinational address decodes that generate individual register
+req strobes
+
+Possible decode logic styles:
+ - Big case statement
+ + Probably more sim-efficient
+ - Hard to do loop parameterization
+ - More annoying to do multiple regs per address
+ - Big always_comb + One if/else chain
+ + Easy to nest loops & parameterize if needed
+ - sim has a lot to evaluate each time
+ - More annoying to do multiple regs per address
+ - implies precedence? Synth tools should be smart enough?
+ - Big always_comb + inline conditionals <---- DO THIS
+ + Easy to nest loops & parameterize if needed
+ - sim has a lot to evaluate each time
+ + Multiple regs per address possible
+ + implies address decode parallelism.
+ ?? Should I try using generate loops + assigns?
+ This would be more explicit parallelism, however some tools may
+ get upset at multiple assignments to a common struct
+
+Implementation:
+ Jinja is inappropriate here
+ Very logic-heavy. Jinja may end up being annoying
+ Also, not much need for customization here
+ This may even make sense as a visitor that dumps lines
+ - visit each reg
+ - upon entering an array, create for loops
+ - upon exiting an array, emit 'end'
+ Make the strobe struct declared locally
+ No need for it to leave the block
+ Error handling
+ If no strobe generated, respond w error?
+ This is actually pretty expensive to do for writes.
+ Hold off on this for now.
+ Reads get this effectively for free in the readback mux.
+ Implement write response strobes back upstream to cpuif
+ Eventually allow for optional register stage for strobe struct
+ Will need to also pipeline the other cpuif signals
+ ok to discard the cpuif_addr. no longer needed
+
+
+Downstream Signals:
+ - access strobes
+ Encase these into a struct datatype
+ - is_write + wr_data/wr_bitstrobe
diff --git a/docs/dev_notes/template-layers/4-fields b/docs/dev_notes/template-layers/4-fields
new file mode 100644
index 0000000..3721266
--- /dev/null
+++ b/docs/dev_notes/template-layers/4-fields
@@ -0,0 +1,163 @@
+--------------------------------------------------------------------------------
+Field storage / next value layer
+--------------------------------------------------------------------------------
+
+Where all the magic happens!!
+
+Any field that implements storage is defined here.
+Bigass struct that only contains storage elements
+
+Each field consists of:
+ - Entries in the storage element struct
+ - if implements storage - field value
+ - user extensible values?
+ - Entries in the combo struct
+ - if implements storage:
+ - Field's "next" value
+ - load-enable strobe
+ - If counter
+ various event strobes (overflow/overflow).
+ These are convenient to generate alongside the field next state logic
+ - user extensible values?
+ - an always_comb block:
+ - generates the "next value" combinational signal
+ - May generate other intermediate strobes?
+ incr/decr?
+ - series of if/else statements that assign the next value in the storage element
+ Think of this as a flat list of "next state" conditons, ranked by their precedence as follows:
+ - reset
+ Actually, handle this in the always_ff
+ - sw access (if sw precedence)
+ - onread/onwrite
+ - hw access
+ - Counter
+ beware of clear events and incr/decr events happening simultaneously
+ - next
+ - etc
+ - sw access (if hw precedence)
+ - onread/onwrite
+ - always_comb block to also generate write-enable strobes for the actual
+ storage element
+ This is better for low-power design
+ - an always_ff block
+ Implements the actual storage element
+ Also a tidy place to abstract the specifics of activehigh/activelow field reset
+ selection.
+
+TODO:
+ Scour the RDL spec.
+ Does this "next state" precedence model hold true in all situations?
+
+TODO:
+ Think about user-extensibility
+ Provide a mechanism for users to extend/override field behavior
+
+TODO:
+ Does the endianness the user sets matter anywhere?
+
+Implementation
+ Makes sense to use a listener class
+
+Be sure to skip alias registers
+
+--------------------------------------------------------------------------------
+
+NextStateConditional Class
+ Describes a single conditional action that determines the next state of a field
+ Provides information to generate the following content:
+ if() begin
+
+ end
+
+ - is_match(self, field: FieldNode) -> bool:
+ Returns True if this conditional is relevant to the field. If so,
+ it instructs the FieldBuider that code for this conditional shall be emitted
+ TODO: better name than "is_match"? More like "is this relevant"
+
+ - get_predicate(self, field: FieldNode) -> str:
+ Returns the rendered conditional text
+
+ - get_assignments(self, field: FieldNode) -> List[str]:
+ Returns a list of rendered assignment strings
+ This will basically always be two:
+ .next =
+ .load_next = '1;
+
+ - get_extra_combo_signals(self, field: FieldNode) -> List[TBD]:
+ Some conditionals will need to set some extra signals (eg. counter underflow/overflow strobes)
+ Compiler needs to know to:
+ - declare these inthe combo struct
+ - initialize them in the beginning of always_comb
+
+ Return something that denotes the following information: (namedtuple?)
+ - signal name: str
+ - width: int
+ - default value assignment: str
+
+ Multiple NextStateConditional can declare the same extra combo signal
+ as long as their definitions agree
+ --> Assert this
+
+
+FieldBuilder Class
+ Describes how to build fields
+
+ Contains NextStateConditional definitions
+ Nested inside the class namespace, define all the NextStateConditional classes
+ that apply
+ User can override definitions or add own to extend behavior
+
+ NextStateConditional objects are stored in a dictionary as follows:
+ _conditionals {
+ assignment_precedence: [
+ conditional_option_1,
+ conditional_option_2,
+ conditional_option_3,
+ ]
+ }
+
+ add_conditional(self, conditional, assignment_precedence):
+ Inserts the NextStateConditional into the given assignment precedence bin
+ The first one added to a precedence bin is first in that bin's search order
+
+ init_conditionals(self) -> None:
+ Called from __init__.
+ loads all possible conditionals into self.conditionals list
+ This function is to provide a hook for the user to add their own.
+
+ Do not do fancy class introspection. Load them explicitly by name like so:
+ self.add_conditional(MyNextState(), AssignmentPrecedence.SW_ACCESS)
+
+ If user wants to extend this class, they can pile onto the bins of conditionals freely!
+
+--------------------------------------------------------------------------------
+Misc
+--------------------------------------------------------------------------------
+What about complex behaviors like a read-clear counter?
+ if({{software read}})
+ next = 0
+ elif({{increment}})
+ next = prev + 1
+
+ --> Implement this by stacking multiple NextStateConditional in the same assignment precedence.
+ In this case, there would be a special action on software read that would be specific to read-clear counters
+ this would get inserted ahead of the search order.
+
+
+Precedence & Search order
+ There are two layers of priority I need to keep track of:
+ - Assignment Precedence
+ RTL precedence of the assignment conditional
+ - Search order (sp?)
+ Within an assignment precedence, order in which the NextStateConditional classes are
+ searched for a match
+
+ For assignment precedence, it makes sense to use an integer enumeration for this
+ since there really aren't too many precedence levels that apply here.
+ Space out the integer enumerations so that user can reliably insert their own actions, ie:
+ my_precedence = AssignmentPrecedence.SW_ACCESS + 1
+
+ For search order, provide a user API to load a NextStateConditional into
+ a precedence 'bin'. Pushing into a bin always inserts into the front of the search order
+ This makes sense since user overrides will always want to be highest priority - and
+ rule themselves out before falling back to builtin behavior
diff --git a/docs/dev_notes/template-layers/5-readback-mux b/docs/dev_notes/template-layers/5-readback-mux
new file mode 100644
index 0000000..fa53102
--- /dev/null
+++ b/docs/dev_notes/template-layers/5-readback-mux
@@ -0,0 +1,49 @@
+--------------------------------------------------------------------------------
+Readback mux layer
+--------------------------------------------------------------------------------
+
+Implementation:
+ - Big always_comb block
+ - Initialize default rd_data value
+ - Lotsa if statements that operate on reg strb to assign rd_data
+ - Merges all fields together into reg
+ - pulls value from storage element struct, or input struct
+ - Provision for optional flop stage?
+
+Mux Strategy:
+ Flat case statement:
+ -- Cant parameterize
+ + better performance?
+
+ Flat 1-hot array then OR reduce:
+ - Create a bus-wide flat array
+ eg: 32-bits x N readable registers
+ - Assign each element:
+ the readback value of each register
+ ... masked by the register's access strobe
+ - I could also stuff an extra bit into the array that denotes the read is valid
+ A missed read will OR reduce down to a 0
+ - Finally, OR reduce all the elements in the array down to a flat 32-bit bus
+ - Retiming the large OR fanin can be done by chopping up the array into stages
+ for 2 stages, sqrt(N) gives each stage's fanin size. Round to favor
+ more fanin on 2nd stage
+ 3 stages uses cube-root. etc...
+ - This has the benefit of re-using the address decode logic.
+ synth can choose to replicate logic if fanout is bad
+
+
+WARNING:
+ Beware of read/write flop stage asymmetry & race conditions.
+ Eg. If a field is rclr, dont want to sample it after it gets read:
+ addr --> strb --> clear
+ addr --> loooong...retime --> sample rd value
+ Should guarantee that read-sampling happens at the same cycle as any read-modify
+
+
+Forwards response strobe back up to cpu interface layer
+
+TODO:
+ Dont forget about alias registers here
+
+TODO:
+ Does the endinness the user sets matter anywhere?
diff --git a/docs/dev_notes/template-layers/6-output-port-mapping b/docs/dev_notes/template-layers/6-output-port-mapping
new file mode 100644
index 0000000..013330b
--- /dev/null
+++ b/docs/dev_notes/template-layers/6-output-port-mapping
@@ -0,0 +1,9 @@
+--------------------------------------------------------------------------------
+Output Port mapping layer
+--------------------------------------------------------------------------------
+
+Assign to output struct port
+
+Still TBD if this will actually be a distinct layer.
+Cosmetically, this might be nicer to interleave with the field section above
+Assign storage element & other derived values as requested by properties
diff --git a/docs/diagrams/arch.png b/docs/diagrams/arch.png
new file mode 100644
index 0000000..0aad955
Binary files /dev/null and b/docs/diagrams/arch.png differ
diff --git a/docs/diagrams/diagrams.odg b/docs/diagrams/diagrams.odg
new file mode 100644
index 0000000..5b42eb6
Binary files /dev/null and b/docs/diagrams/diagrams.odg differ
diff --git a/docs/diagrams/rbuf.png b/docs/diagrams/rbuf.png
new file mode 100644
index 0000000..34d7777
Binary files /dev/null and b/docs/diagrams/rbuf.png differ
diff --git a/docs/diagrams/readback.png b/docs/diagrams/readback.png
new file mode 100644
index 0000000..9508650
Binary files /dev/null and b/docs/diagrams/readback.png differ
diff --git a/docs/diagrams/wbuf.png b/docs/diagrams/wbuf.png
new file mode 100644
index 0000000..d4e5e54
Binary files /dev/null and b/docs/diagrams/wbuf.png differ
diff --git a/docs/faq.rst b/docs/faq.rst
new file mode 100644
index 0000000..751de34
--- /dev/null
+++ b/docs/faq.rst
@@ -0,0 +1,131 @@
+Frequently Asked Questions
+==========================
+
+Why isn't there an option for a flat non-struct hardware interface?
+-------------------------------------------------------------------
+SystemRDL is inherently a very hierarchical language.
+For small register blocks, flattening the hardware interface may be acceptable,
+but this ends up scaling very poorly as the design becomes larger and has more
+complex hierarchy.
+Using struct compositions for the hardware interface has the benefit of
+preserving conceptual hierarchy and arrays exactly as defined in the original
+SystemRDL.
+
+How do I know I connected everything? Structs are harder to review
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Initially this can be daunting, but fortunately the tool has an option to generate a
+flattened hardware interface report upon export. Try using the ``--hwif-report``
+command line option when exporting. This is the easiest way to quickly
+understand the structure of the hardware interface.
+
+
+
+Why does the tool generate un-packed structs? I prefer packed structs.
+----------------------------------------------------------------------
+Packed structs are great when describing vectors that have bit-level structure.
+In this tool, the use of un-packed structs is intentional since the hardware
+interface is not something that is meant to be bit-accessible. In the case of
+the hardware interface structs, using a packed struct is semantically inappropriate.
+
+... Then how can I initialize the struct?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+We get this request most often because designers want to initialize the ``hwif_in``
+struct with a simple assignment:
+
+.. code:: systemverilog
+
+ always_comb begin
+ hwif_in = '0;
+ end
+
+Of course since the struct actually is **unpacked**, this will result in a
+compile error which usually leads to the inappropriate assumption that it ought
+to be packed. (See this amusing blog post about `X/Y problems `_)
+
+If your goal is to initialize the packed struct, fortunately SystemVerilog already
+has syntax to do this:
+
+.. code:: systemverilog
+
+ always_comb begin
+ hwif_in = '{default: '0};
+ end
+
+This is lesser-known syntax, but still very well supported by synthesis
+tools, and is the recommended way to handle this.
+
+... What if I want to assign it to a bit-vector?
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Assigning the hwif struct to a bit-vector is strongly discouraged. This tool makes
+no guarantees regarding the field ordering of the hwif structure, so doing so
+should be considered functionally dangerous.
+
+That said, if you still need to do this, it is still trivially possible to
+without requiring packed structs. Instead, use the SystemVerilog streaming operator:
+
+.. code:: systemverilog
+
+ my_packed_vector = {<<{hwif_out}};
+
+
+... Why are unpacked structs preferred?
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+In the case of the hardware interface ports, unpacked structs help prevent
+mistakes that are very easy to make.
+Consider the following situation - a designer has a field that sets the following
+properties: ``sw=rw; hw=rw; we;``, and wants to assign the hardware input value,
+so they erroneously do the following assignment in Verilog:
+
+.. code:: systemverilog
+
+ assign hwif_in.my_register.my_field = ;
+
+This is actually a bug since the ``my_field`` member is actually a struct that
+has two members: ``we`` and ``next``. If this were a packed struct, this would
+silently compile and you would potentially have a bug that may not be noticed
+(depending on how thorough the test campaign is).
+With an unpacked struct, this gets flagged immediately as a compile error since
+the assignment is invalid.
+
+The designer may have simply forgotten that the field is an aggregate of multiple
+members and intended to do the following:
+
+.. code:: systemverilog
+
+ assign hwif.my_register.my_field.next = ;
+ assign hwif.my_register.my_field.we = ;
+
+
+The generated output does not match our organization's coding style
+-------------------------------------------------------------------
+SystemVerilog coding styles vary wildly, and unfortunately there is little
+consensus on this topic within the digital design community.
+
+The output generated by PeakRDL-regblock strives to be as human-readable as possible,
+and follow consistent indentation and styling. We do our best to use the most
+widely accepted coding style, but since this is a very opinionated space, it is
+impossible to satisfy everyone.
+
+In general, we strive to follow the
+`SystemVerilog style guide by lowRISC `_,
+but may deviate in some areas if not practical or would impose excessive complexity on the code generator.
+
+
+The lint tool I am using is flagging violations in generated code
+-----------------------------------------------------------------
+Code linting tools are a great way to check for user-error, flag inconsistencies,
+and enforce best-practices within an organization. In many cases, linter tools
+may be configured to also enforce stylistic preferences.
+Unfortunately just like coding styles, lint rules can often be more
+opinionated than practical.
+
+In general, we will not address lint violations unless they flag actual
+structural issues or semantically dangerous code.
+Stylistic violations that pose no actual danger to the correctness of the design
+will rarely be addressed, especially if the change would add unreasonable
+complexity to the tool.
+
+If you encounter a lint violation, please carefully review and consider waiving
+it if it does not pose an actual danger. If you still believe it is a problem,
+please let us know by `submitting an issue `_
+that describes the problem.
diff --git a/docs/hwif.rst b/docs/hwif.rst
new file mode 100644
index 0000000..3bf50db
--- /dev/null
+++ b/docs/hwif.rst
@@ -0,0 +1,61 @@
+Hardware Interface
+------------------
+
+The generated register block will present the entire hardware interface to the user
+using two struct ports:
+
+* ``hwif_in``
+* ``hwif_out``
+
+All field inputs and outputs as well as signals are consolidated into these
+struct ports. The presence of each depends on the specific contents of the design
+being exported.
+
+
+Using structs for the hardware interface has the following benefits:
+
+* Preserves register map component grouping, arrays, and hierarchy.
+* Avoids naming collisions and cumbersome signal name flattening.
+* Allows for more natural mapping and distribution of register block signals to a design's hardware components.
+* Use of unpacked arrays/structs prevents common assignment mistakes as they are enforced by the compiler.
+
+
+Structs are organized as follows: ``hwif_out..``
+
+For example, a simple design such as:
+
+.. code-block:: systemrdl
+
+ addrmap my_design {
+ reg {
+ field {
+ sw = rw;
+ hw = rw;
+ we;
+ } my_field;
+ } my_reg[2];
+ };
+
+... results in the following struct members:
+
+.. code-block:: text
+
+ hwif_out.my_reg[0].my_field.value
+ hwif_in.my_reg[0].my_field.next
+ hwif_in.my_reg[0].my_field.we
+ hwif_out.my_reg[1].my_field.value
+ hwif_in.my_reg[1].my_field.next
+ hwif_in.my_reg[1].my_field.we
+
+For brevity in this documentation, hwif features will be described using shorthand
+notation that omits the hierarchical path: ``hwif_out..``
+
+
+.. important::
+
+ The PeakRDL tool makes no guarantees on the field order of the hwif structs.
+ For this reason, it is strongly recommended to always access struct members
+ directly, by name.
+
+ If using the SystemVerilog streaming operator to assign the hwif struct to a
+ packed vector, be extremely careful to avoid assumptions on the resulting bit-position of a field.
diff --git a/docs/img/err.svg b/docs/img/err.svg
new file mode 100644
index 0000000..6ce297c
--- /dev/null
+++ b/docs/img/err.svg
@@ -0,0 +1,53 @@
+
+
diff --git a/docs/img/ok.svg b/docs/img/ok.svg
new file mode 100644
index 0000000..defc966
--- /dev/null
+++ b/docs/img/ok.svg
@@ -0,0 +1,53 @@
+
+
diff --git a/docs/img/warn.svg b/docs/img/warn.svg
new file mode 100644
index 0000000..1debe8a
--- /dev/null
+++ b/docs/img/warn.svg
@@ -0,0 +1,53 @@
+
+
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644
index 0000000..e21236a
--- /dev/null
+++ b/docs/index.rst
@@ -0,0 +1,94 @@
+Introduction
+============
+
+PeakRDL-regblock is a free and open-source control & status register (CSR) compiler.
+This code generator translates your SystemRDL register description into
+a synthesizable SystemVerilog RTL module that can be easily instantiated into
+your hardware design.
+
+* Generates fully synthesizable SystemVerilog RTL (IEEE 1800-2012)
+* Options for many popular CPU interface protocols (AMBA APB, AXI4-Lite, and more)
+* Configurable pipelining options for designs with fast clock rates.
+* Broad support for SystemRDL 2.0 features
+
+
+Quick Start
+-----------
+The easiest way to use PeakRDL-regblock is via the `PeakRDL command line tool `_:
+
+.. code-block:: bash
+
+ # Install PeakRDL-regblock along with the command-line tool
+ python3 -m pip install peakrdl-regblock[cli]
+
+ # Export!
+ peakrdl regblock atxmega_spi.rdl -o regblock/ --cpuif axi4-lite
+
+
+Looking for VHDL?
+-----------------
+This project generates SystemVerilog RTL. If you prefer using VHDL, check out
+the sister project which aims to be a feature-equivalent fork of
+PeakRDL-regblock: `PeakRDL-regblock-VHDL `_
+
+
+Links
+-----
+
+- `Source repository `_
+- `Release Notes `_
+- `Issue tracker `_
+- `PyPi `_
+- `SystemRDL Specification `_
+
+
+.. toctree::
+ :hidden:
+
+ self
+ architecture
+ hwif
+ configuring
+ limitations
+ faq
+ licensing
+ api
+
+.. toctree::
+ :hidden:
+ :caption: CPU Interfaces
+
+ cpuif/introduction
+ cpuif/apb
+ cpuif/axi4lite
+ cpuif/avalon
+ cpuif/passthrough
+ cpuif/internal_protocol
+ cpuif/customizing
+
+.. toctree::
+ :hidden:
+ :caption: SystemRDL Properties
+
+ props/field
+ props/reg
+ props/addrmap
+ props/signal
+ props/rhs_props
+
+.. toctree::
+ :hidden:
+ :caption: Other SystemRDL Features
+
+ rdl_features/external
+
+.. toctree::
+ :hidden:
+ :caption: Extended Properties
+
+ udps/intro
+ udps/read_buffering
+ udps/write_buffering
+ udps/extended_swacc
+ udps/signed
+ udps/fixedpoint
diff --git a/docs/licensing.rst b/docs/licensing.rst
new file mode 100644
index 0000000..c9424b6
--- /dev/null
+++ b/docs/licensing.rst
@@ -0,0 +1,50 @@
+Licensing
+=========
+
+Re-distribution of the PeakRDL-regblock code generator tool shall adhere to the
+terms outlined by the GNU LGPL v3 license. For a copy of the license, see:
+https://github.com/SystemRDL/PeakRDL-regblock/blob/main/LICENSE
+
+
+Why LGPLv3?
+-----------
+LGPLv3 was chosen because my intent is to promote a thriving ecosystem of free and
+open source register automation tools. The license terms discourage this tool from
+being bundled into some commercially sold closed-source software, as that would
+be contrary to this project's philosophy.
+
+
+What is covered by the LGPL v3 license?
+--------------------------------------
+The LGPL license is intended for the code generator itself. This includes all
+Python sources, Jinja template files, as well as testcase infrastructure not
+explicitly mentioned in the exemptions below.
+
+
+What is exempt from the LGPLv3 license?
+---------------------------------------
+Don't worry. Not everything that the PeakRDL-regblock project touches is
+considered LGPLv3 code.
+
+The following are exempt and are free to use with no restrictions:
+
+* Any code that is generated using PeakRDL-regblock is 100% yours. Since it
+ was derived from your regblock definition, it remains yours. You can
+ distribute it freely, use it in a proprietary ASIC, sell it as part of an
+ IP, whatever.
+* Any code snippets in this documentation can be freely copy/pasted. These are
+ examples that are intended for this purpose.
+* All reference files that are downloadable from this documentation, which are
+ also available in the `hdl-src folder in the repository `_
+
+
+Can I use this as part of my company's internally developed tools?
+------------------------------------------------------------------
+Absolutely!
+
+Sometimes it may be necessary to integrate this into a larger toolchain at your
+workplace. This is totally OK, as long as you don't start distributing it
+outside your workplace in ways that violate the LGPLv3 license.
+
+That said, I'd encourage you to check out the `PeakRDL command line tool `_.
+It may already do everything you need.
diff --git a/docs/limitations.rst b/docs/limitations.rst
new file mode 100644
index 0000000..2ae08b9
--- /dev/null
+++ b/docs/limitations.rst
@@ -0,0 +1,53 @@
+Known Limitations
+=================
+
+Not all SystemRDL features are supported by this exporter. For a listing of
+supported properties, see the appropriate property listing page in the sections
+that follow.
+
+
+Alias Registers
+---------------
+Registers instantiated using the ``alias`` keyword are not supported yet.
+
+
+Unaligned Registers
+-------------------
+All address offsets & strides shall be a multiple of the cpuif bus width used. Specifically:
+
+* Bus width is inferred by the maximum accesswidth used in the regblock.
+* Each component's address and array stride shall be aligned to the bus width.
+
+
+Uniform accesswidth
+-------------------
+All registers within a register block shall use the same accesswidth.
+
+One exception is that registers with regwidth that is narrower than the cpuif
+bus width are permitted, provided that their regwidth is equal to their accesswidth.
+
+For example:
+
+.. code-block:: systemrdl
+
+ // (Largest accesswidth used is 32, therefore the CPUIF bus width is 32)
+
+ reg {
+ regwidth = 32;
+ accesswidth = 32;
+ } reg_a @ 0x00; // OK. Regular 32-bit register
+
+ reg {
+ regwidth = 64;
+ accesswidth = 32;
+ } reg_b @ 0x08; // OK. "Wide" register of 64-bits, but is accessed using 32-bit subwords
+
+ reg {
+ regwidth = 8;
+ accesswidth = 8;
+ } reg_c @ 0x10; // OK. Is aligned to the cpuif bus width
+
+ reg {
+ regwidth = 32;
+ accesswidth = 8;
+ } bad_reg @ 0x14; // NOT OK. accesswidth conflicts with cpuif width
diff --git a/docs/props/addrmap.rst b/docs/props/addrmap.rst
new file mode 100644
index 0000000..770cd3f
--- /dev/null
+++ b/docs/props/addrmap.rst
@@ -0,0 +1,28 @@
+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|
+
+rsvdset
+-------
+|NO|
diff --git a/docs/props/field.rst b/docs/props/field.rst
new file mode 100644
index 0000000..fc8e35a
--- /dev/null
+++ b/docs/props/field.rst
@@ -0,0 +1,491 @@
+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
+^^^^^^^^^^^^^^
+
+All onread/onwrite actions are supported (except for ruser/wuser)
+
+rclr/rset
+^^^^^^^^^
+
+See ``onread``. These are effectively aliases of the onread property.
+
+singlepulse
+^^^^^^^^^^^
+
+If set, field will get cleared back to zero after being written.
+
+.. wavedrom::
+
+ {"signal": [
+ {"name": "clk", "wave": "p....."},
+ {"name": "", "wave": "0.10.."},
+ {"name": "hwif_out..value", "wave": "0..10."}
+ ]}
+
+sw
+^^^
+All sw access modes are supported except for ``w1`` and ``rw1``.
+
+swacc
+^^^^^
+
+If true, infers an output signal ``hwif_out..swacc`` that is asserted when
+accessed by software. Specifically, on the same clock cycle that the field is
+being sampled during a software read operation, or as it is being written.
+
+.. wavedrom::
+
+ {"signal": [
+ {"name": "clk", "wave": "p...."},
+ {"name": "hwif_in..next", "wave": "x.=x.", "data": ["D"]},
+ {"name": "hwif_out..swacc", "wave": "0.10."}
+ ]}
+
+
+swmod
+^^^^^
+
+If true, infers an output signal ``hwif_out..swmod`` that is asserted as the
+field is being modified by software. This can be due to a software write
+operation, or a software read operation that has clear/set side-effects.
+
+
+.. wavedrom::
+
+ {"signal": [
+ {"name": "clk", "wave": "p....."},
+ {"name": "hwif_out..value", "wave": "=..=..", "data": ["old", "new"]},
+ {"name": "hwif_out..swmod", "wave": "0.10.."}
+ ]}
+
+
+swwe/swwel
+^^^^^^^^^^
+
+Provides a mechanism that allows hardware to override whether the field is
+writable by software.
+
+boolean
+ If True, infers an input signal ``hwif_in..swwe`` or ``hwif_in..swwel``.
+
+reference
+ Single-bit reference controls field's behavior.
+
+
+woclr/woset
+^^^^^^^^^^^
+See ``onwrite``. These are effectively aliases of the onwrite property.
+
+--------------------------------------------------------------------------------
+
+Hardware Access Properties
+--------------------------
+
+anded/ored/xored
+^^^^^^^^^^^^^^^^
+If true, infers the existence of output signal: ``hwif_out..anded``,
+``hwif_out..ored``, ``hwif_out..xored``
+
+
+hw
+^^^
+Controls hardware access to the field.
+
+If readable, enables output signal ``hwif_out..value``. If writable, enables
+input ``hwif_in..next``.
+
+Hardware-writable fields can optionally define the ``next`` property which replaces
+the inferred ``hwif_in..next`` input with an alternate reference.
+
+
+hwclr/hwset
+^^^^^^^^^^^
+If both ``hwclr`` and ``hwset`` properties are used, and both are asserted at
+the same clock cycle, then ``hwset`` will take precedence.
+
+boolean
+ If true, infers the existence of input signal: ``hwif_in..hwclr``, ``hwif_in..hwset``
+
+reference
+ Reference to any single-bit internal object to drive this control.
+
+
+hwenable/hwmask
+^^^^^^^^^^^^^^^
+Reference to a component that provides bit-level control of hardware writeability.
+
+
+we/wel
+^^^^^^
+Write-enable control from hardware interface.
+
+If true, infers the existence of input signal: ``hwif_in..we``, ``hwif_in..wel``
+
+.. wavedrom::
+
+ {"signal": [
+ {"name": "clk", "wave": "p...."},
+ {"name": "hwif_in..next", "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
+ If true, infers the existence of input signal ``hwif_in..we`` or ``hwif_in..wel``
+
+reference
+ Reference to any single-bit internal object to drive this control.
+
+--------------------------------------------------------------------------------
+
+Counter Properties
+------------------
+
+counter
+^^^^^^^
+If true, marks this field as a counter. The counter direction is inferred based
+based on which properties are assigned. By default, an up-counter is implemented.
+If any of the properties associated with an up-counter are used, then up-counting
+capabilities will be implemented. The same is true for down-counters and up/down
+counters.
+
+Unless alternate control signals are specified, the existence of input signals
+``hwif_in..incr`` and ``hwif_in..decr`` will be inferred depending on the type
+of counter described.
+
+
+incr
+^^^^
+Assign a reference to an alternate control signal to increment the counter.
+If assigned, the inferred ``hwif_in..incr`` input will not be generated.
+
+incrsaturate/saturate
+^^^^^^^^^^^^^^^^^^^^^
+If assigned, indicates that the counter will saturate instead of wrapping.
+If an alternate saturation point is specified, the counter value will be
+adjusted so that it does not exceed that limit, even after non-increment actions.
+
+boolean
+ If true, saturation point is at the counter's maximum count value. (2^width - 1)
+
+integer
+ Specify a static saturation value.
+
+reference
+ Specify a dynamic saturation value.
+
+
+incrthreshold/threshold
+^^^^^^^^^^^^^^^^^^^^^^^
+If assigned, infers a ``hwif_out..incrthreshold`` output signal. This signal is
+asserted if the counter value is greater or equal to the threshold.
+
+.. wavedrom::
+
+ {
+ "signal": [
+ {"name": "clk", "wave": "p......"},
+ {"name": "hwif_in..incr", "wave": "01...0."},
+ {"name": "", "wave": "=.=3==..", "data": [4,5,6,7,8,9]},
+ {"name": "hwif_out..incrthreshold", "wave": "0..1...."}
+ ],
+ "foot": {
+ "text": "Example where incrthreshold = 6"
+ }
+ }
+
+
+boolean
+ If true, threshold is the counter's maximum count value. (2^width - 1)
+
+integer
+ Specify a static threshold value.
+
+reference
+ Specify a dynamic threshold value.
+
+
+incrvalue
+^^^^^^^^^
+Override the counter's increment step size.
+
+integer
+ Specify a static increment step size.
+
+reference
+ Reference a component that controls the step size.
+
+incrwidth
+^^^^^^^^^
+If assigned, infers an input signal ``hwif_in..incrvalue``. The value of this
+property defines the signal's width.
+
+
+overflow
+^^^^^^^^
+If true, infers an output signal ``hwif_out..overflow`` that is asserted when
+the counter is about to wrap.
+
+.. wavedrom::
+
+ {
+ "signal": [
+ {"name": "clk", "wave": "p......."},
+ {"name": "hwif_in..incr", "wave": "0101010."},
+ {"name": "", "wave": "=.=.=.=.", "data": [14,15,0,1]},
+ {"name": "hwif_out..overflow", "wave": "0..10..."}
+ ],
+ "foot": {
+ "text": "A 4-bit counter overflowing"
+ }
+ }
+
+
+decr
+^^^^
+Assign a reference to an alternate control signal to decrement the counter.
+If assigned, the inferred ``hwif_in..decr`` input will not be generated.
+
+
+decrsaturate
+^^^^^^^^^^^^
+If assigned, indicates that the counter will saturate instead of wrapping.
+If an alternate saturation point is specified, the counter value will be
+adjusted so that it does not exceed that limit, even after non-decrement actions.
+
+boolean
+ If true, saturation point is when the counter reaches 0.
+
+integer
+ Specify a static saturation value.
+
+reference
+ Specify a dynamic saturation value.
+
+
+decrthreshold
+^^^^^^^^^^^^^
+If assigned, infers a ``hwif_out..decrthreshold`` output signal. This signal is
+asserted if the counter value is less than or equal to the threshold.
+
+.. wavedrom::
+
+ {
+ "signal": [
+ {"name": "clk", "wave": "p......"},
+ {"name": "hwif_in..decr", "wave": "01...0."},
+ {"name": "", "wave": "=.=3==.", "data": [9,8,7,6,5,4]},
+ {"name": "hwif_out..decrthreshold", "wave": "0..1..."}
+ ],
+ "foot": {
+ "text": "Example where incrthreshold = 7"
+ }
+ }
+
+
+boolean
+ If true, threshold is 0.
+
+integer
+ Specify a static threshold value.
+
+reference
+ Specify a dynamic threshold value.
+
+
+decrvalue
+^^^^^^^^^
+Override the counter's decrement step size.
+
+integer
+ Specify a static step size.
+
+reference
+ Reference to a component that controls the step size.
+
+
+decrwidth
+^^^^^^^^^
+If assigned, infers an input signal ``hwif_in..decrvalue``. The value of this
+property defines the signal's width.
+
+
+underflow
+^^^^^^^^^
+If true, infers an output signal ``hwif_out..underflow`` that is asserted when
+the counter is about to wrap.
+
+.. wavedrom::
+
+ {
+ "signal": [
+ {"name": "clk", "wave": "p......."},
+ {"name": "hwif_in..decr", "wave": "0101010."},
+ {"name": "", "wave": "=.=.=.=.", "data": [1,0,15,14]},
+ {"name": "hwif_out..underflow", "wave": "0..10..."}
+ ],
+ "foot": {
+ "text": "A 4-bit counter underflowing"
+ }
+ }
+
+--------------------------------------------------------------------------------
+
+Interrupt Properties
+--------------------
+
+intr
+^^^^
+
+If set, this field becomes an interrupt field.
+The enclosing register infers an output signal ``hwif_out..intr`` which denotes
+that an interrupt is active. This is an or-reduction of all interrupt fields
+after applying the appropriate ``enable`` or ``mask`` to the field value.
+
+level (default)
+ Interrupt is level-sensitive. If a bit on the field's ``hwif_in..next`` input
+ is '1', it will trigger an interrupt event.
+
+posedge
+ If a bit on the field's ``hwif_in..next`` input transitions from '0' to '1',
+ it will trigger an interrupt event. This transition shall still be synchronous
+ to the register block's clock.
+
+negedge
+ If a bit on the field's ``hwif_in..next`` input transitions from '1' to '0',
+ it will trigger an interrupt event. This transition shall still be synchronous
+ to the register block's clock.
+
+bothedge
+ If a bit on the field's ``hwif_in..next`` input transitions from '0' to '1' or '1' to '0',
+ it will trigger an interrupt event. This transition shall still be synchronous
+ to the register block's clock.
+
+nonsticky
+ Interrupt event is not sticky.
+
+
+enable
+^^^^^^
+Reference to a field or signal that, if set to 1, define which bits in the field
+are used to assert an interrupt.
+
+
+mask
+^^^^
+Reference to a field or signal that, if set to 1, define which bits in the field
+are *not* used to assert an interrupt.
+
+
+haltenable
+^^^^^^^^^^
+Reference to a field or signal that, if set to 1, define which bits in the field
+are used to assert the halt output.
+
+If this property is set, the enclosing register will infer a ``hwif_out..halt`` output.
+
+
+haltmask
+^^^^^^^^
+Reference to a field or signal that, if set to 1, define which bits in the field
+are *not* used to assert the halt output.
+
+If this property is set, the enclosing register will infer a ``hwif_out..halt`` output.
+
+
+stickybit
+^^^^^^^^^
+When an interrupt trigger occurs, a stickybit field will set the corresponding
+bit to '1' and hold it until it is cleared by a software access.
+
+The interrupt trigger depends on the interrupt type. By default, interrupts are
+level-sensitive, but the interrupt modifiers allow for edge-sensitive triggers as
+well.
+
+The waveform below demonstrates a level-sensitive interrupt:
+
+.. wavedrom::
+
+ {
+ "signal": [
+ {"name": "clk", "wave": "p....."},
+ {"name": "hwif_in..next", "wave": "010..."},
+ {"name": "", "wave": "0.1..."}
+ ]
+ }
+
+
+sticky
+^^^^^^
+Unlike ``stickybit`` fields, a sticky field will latch an entire value. The
+value is latched as soon as ``hwif_in..next`` is nonzero, and is held until the
+field contents are cleared back to 0 by a software access.
+
+.. wavedrom::
+
+ {
+ "signal": [
+ {"name": "clk", "wave": "p....."},
+ {"name": "hwif_in..next", "wave": "23.22.", "data": [0,10,20,30]},
+ {"name": "", "wave": "2.3...", "data": [0, 10]}
+ ]
+ }
+
+
+--------------------------------------------------------------------------------
+
+Misc
+----
+
+encode
+^^^^^^
+If assigned a user-defined enumeration, the resulting package file will include
+its definition. Due to limitations from type-strictness rules in SystemVerilog,
+the field will remain as a ``logic`` datatype.
+
+
+next
+^^^^
+If assigned, replaces the inferred ``hwif_in..next`` input with an explicit reference.
+
+
+paritycheck
+^^^^^^^^^^^
+If set, enables parity checking for this field.
+
+Adds a ``parity_error`` output signal to the module.
+
+.. note::
+
+ If this field does not implement storage, the ``partycheck`` property is ignored.
+
+
+
+precedence
+^^^^^^^^^^
+Control whether hardware or software has precedence when field value update
+contention occurs. Software has precedence by default.
+
+reset
+^^^^^
+Control the reset value of the field's storage element.
+If not specified, the field will not be reset.
+
+integer
+ Static reset value
+
+reference
+ Reference to a dynamic reset value.
+
+resetsignal
+^^^^^^^^^^^
+Provide an alternate reset trigger for this field.
diff --git a/docs/props/reg.rst b/docs/props/reg.rst
new file mode 100644
index 0000000..bbef409
--- /dev/null
+++ b/docs/props/reg.rst
@@ -0,0 +1,14 @@
+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
+-----------
+Control the software access width. The register block's CPUIF bus width is
+determined by the maximum accesswidth encountered.
+
+regwidth
+--------
+Control the bit-width of the register.
diff --git a/docs/props/rhs_props.rst b/docs/props/rhs_props.rst
new file mode 100644
index 0000000..6234bd5
--- /dev/null
+++ b/docs/props/rhs_props.rst
@@ -0,0 +1,182 @@
+RHS Property References
+=======================
+
+SystemRDL allows some properties to be referenced in the righthand-side of
+property assignment expressions:
+
+ .. code-block:: systemrdl
+
+ some_property = my_reg.my_field -> some_property;
+
+The official SystemRDL spec refers to these as "Ref targets" in Table G1, but
+unfortunately does not describe their semantics in much detail.
+
+The text below describes the interpretations used for this exporter.
+
+--------------------------------------------------------------------------------
+
+Field
+-----
+
+field -> swacc
+^^^^^^^^^^^^^^
+Single-cycle strobe that indicates the field is being accessed by software
+(read or write).
+
+
+field -> swmod
+^^^^^^^^^^^^^^^
+Single-cycle strobe that indicates the field is being modified during a software
+access operation.
+
+
+field -> swwe/swwel
+^^^^^^^^^^^^^^^^^^^
+Represents the signal that controls the field's swwe/swwel behavior.
+
+
+field -> anded/ored/xored
+^^^^^^^^^^^^^^^^^^^^^^^^^
+Represents the current and/or/xor reduction of the field's value.
+
+
+field -> hwclr/hwset
+^^^^^^^^^^^^^^^^^^^^
+|EX|
+
+Represents the signal that controls the field's hwclr/hwset behavior.
+
+
+field -> hwenable/hwmask
+^^^^^^^^^^^^^^^^^^^^^^^^
+Represents the signal that controls the field's hwenable/hwmask behavior.
+
+field -> we/wel
+^^^^^^^^^^^^^^^
+Represents the signal that controls the field's we/wel behavior.
+
+field -> next
+^^^^^^^^^^^^^
+|EX|
+
+field -> reset
+^^^^^^^^^^^^^^
+Represents the value that was assigned to this property.
+
+field -> resetsignal
+^^^^^^^^^^^^^^^^^^^^
+Represents the value that was assigned to this property.
+
+--------------------------------------------------------------------------------
+
+Field Counter Properties
+------------------------
+
+field -> incr
+^^^^^^^^^^^^^
+Represents the signal that controls the field's counter increment control.
+
+
+field -> incrsaturate/saturate
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Represents the internal 1-bit event signal that indicates whether the counter is saturated
+at its saturation value.
+
+.. wavedrom::
+
+ {
+ "signal": [
+ {"name": "clk", "wave": "p......"},
+ {"name": "hwif_in..decr", "wave": "0101010"},
+ {"name": "", "wave": "=.=....", "data": [1,0]},
+ {"name": "", "wave": "0.1...."}
+ ],
+ "foot": {
+ "text": "A 4-bit counter saturating"
+ }
+ }
+
+
+field -> incrthreshold/threshold
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Represents the 1-bit event signal that indicates whether the counter has met or
+exceeded its incrthreshold.
+
+field -> incrvalue
+^^^^^^^^^^^^^^^^^^
+Represents the value that was assigned to this property.
+
+field -> overflow
+^^^^^^^^^^^^^^^^^
+Represents the event signal that is asserted when the counter is about to wrap.
+
+field -> decr
+^^^^^^^^^^^^^
+Represents the signal that controls the field's counter decrement control.
+
+field -> decrsaturate
+^^^^^^^^^^^^^^^^^^^^^
+Represents the internal 1-bit event signal that indicates whether the counter is saturated
+at its saturation value.
+
+.. wavedrom::
+
+ {
+ "signal": [
+ {"name": "clk", "wave": "p......"},
+ {"name": "hwif_in..incr", "wave": "0101010"},
+ {"name": "", "wave": "=.=....", "data": [14,15]},
+ {"name": "", "wave": "0.1...."}
+ ],
+ "foot": {
+ "text": "A 4-bit counter saturating"
+ }
+ }
+
+field -> decrthreshold
+^^^^^^^^^^^^^^^^^^^^^^
+Represents the 1-bit event signal that indicates whether the counter has met or
+exceeded its incrthreshold.
+
+field -> decrvalue
+^^^^^^^^^^^^^^^^^^
+Represents the value that was assigned to this property.
+
+field -> underflow
+^^^^^^^^^^^^^^^^^^
+Represents the event signal that is asserted when the counter is about to wrap.
+
+--------------------------------------------------------------------------------
+
+Field Interrupt Properties
+--------------------------
+
+field -> enable
+^^^^^^^^^^^^^^^
+Represents the value that was assigned to this property.
+
+field -> mask
+^^^^^^^^^^^^^
+Represents the value that was assigned to this property.
+
+field -> haltenable
+^^^^^^^^^^^^^^^^^^^
+Represents the value that was assigned to this property.
+
+field -> haltmask
+^^^^^^^^^^^^^^^^^
+Represents the value that was assigned to this property.
+
+
+--------------------------------------------------------------------------------
+
+Register
+--------
+
+reg -> intr
+^^^^^^^^^^^
+References the register's ``hwif_out..intr`` signal.
+
+reg -> halt
+^^^^^^^^^^^
+References the register's ``hwif_out..halt`` signal.
diff --git a/docs/props/signal.rst b/docs/props/signal.rst
new file mode 100644
index 0000000..7171019
--- /dev/null
+++ b/docs/props/signal.rst
@@ -0,0 +1,28 @@
+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
+--------------------
+Only relevant for signals used as resets. Defines the reset signal's polarity.
+
+
+sync/async
+----------
+Only supported for signals used as resets to infer edge-sensitive reset.
+Ignored in all other contexts.
+
+
+cpuif_reset
+-----------
+Specify that this signal shall be used as alternate reset signal for the CPU
+interface for this regblock.
+
+
+field_reset
+-----------
+Specify that this signal is used as an alternate reset signal for all fields
+instantiated in sub-hierarchies relative to this signal.
diff --git a/docs/rdl_features/external.rst b/docs/rdl_features/external.rst
new file mode 100644
index 0000000..15d275e
--- /dev/null
+++ b/docs/rdl_features/external.rst
@@ -0,0 +1,155 @@
+External Components
+===================
+SystemRDL allows some component instances to be defined as "external" elements
+of an address space definition. In the context of this regblock generator,
+the implementation of an external component is left up to the designer. When
+generating the RTL for a regblock, the implementations of external components
+are omitted and instead a user-interface is presented on the
+``hwif_in``/``hwif_out`` i/o structs.
+
+External component signals on the hardware interface closely follow the semantics
+of the :ref:`cpuif_protocol`.
+
+
+Things you should know
+----------------------
+
+* By default external ``hwif_out`` signals are driven combinationally. An
+ optional output retiming stage can be enabled if needed.
+* Due to the uncertain access latency of external components, the regblock will
+ only issue one outstanding transaction to an external component at a time.
+ This is enforced even if the CPUIF is capable of pipelined accesses such as
+ AXI4-Lite.
+
+
+External Registers
+------------------
+External registers can be useful if it is necessary to implement a register that
+cannot easily be expressed using SystemRDL semantics. This could be a unique
+access policy, or FIFO-like push/pop registers.
+
+External registers are annotated as such by using the ``external`` keyword:
+
+.. code-block:: systemrdl
+
+ // An internal register
+ my_reg int_reg;
+
+ // An external register
+ external my_reg ext_reg;
+
+Request
+^^^^^^^
+hwif_out..req
+ When asserted, a read or write transfer will be initiated.
+ Qualifies all other request signals.
+
+ If the register is wide (``regwidth`` > ``accesswidth``), then the
+ ``hwif_out..req`` will consist of multiple bits, representing the access
+ strobe for each sub-word of the register.
+
+ If the register does not contain any readable fields, this strobe will be
+ suppressed for read operations.
+
+ If the register does not contain any writable readable fields, this strobe
+ will be suppressed for write operations.
+
+hwif_out..req_is_wr
+ If ``1``, denotes that the current transfer is a write. Otherwise transfer is
+ a read.
+
+hwif_out..wr_data
+ Data to be written for the write transfer. This signal is ignored for read
+ transfers.
+
+ The bit-width of this signal always matches the CPUIF's bus width,
+ regardless of the regwidth.
+
+ If the register does not contain any writable fields, this signal is omitted.
+
+hwif_out..wr_biten
+ Active-high bit-level write-enable strobes.
+ Only asserted bit positions will change the register value during a write
+ transfer.
+
+ If the register does not contain any writable fields, this signal is omitted.
+
+
+Read Response
+^^^^^^^^^^^^^
+hwif_in..rd_ack
+ Single-cycle strobe indicating a read transfer has completed.
+ Qualifies all other read response signals.
+
+ If the transfer is always completed in the same cycle, it is acceptable to
+ tie this signal to ``hwif_out..req && !hwif_out..req_is_wr``.
+
+ If the register does not contain any readable fields, this signal is omitted.
+
+hwif_in..rd_data
+ Read response data.
+
+ If the register does not contain any readable fields, this signal is omitted.
+
+Write Response
+^^^^^^^^^^^^^^
+hwif_in..wr_ack
+ Single-cycle strobe indicating a write transfer has completed.
+
+ If the transfer is always completed in the same cycle, it is acceptable to
+ tie this signal to ``hwif_out..req && hwif_out..req_is_wr``.
+
+ If the register does not contain any writable fields, this signal is omitted.
+
+
+
+External Blocks
+---------------
+Broader external address regions can be represented by external block-like
+components such as ``addrmap``, ``regfile`` or ``mem`` elements.
+
+To ensure address decoding for external blocks is simple (only requires simple bit-pruning),
+blocks that are external to an exported regblock shall be aligned to their size.
+
+Request
+^^^^^^^
+hwif_out..req
+ When asserted, a read or write transfer will be initiated.
+ Qualifies all other request signals.
+
+hwif_out..addr
+ Byte-address of the transfer.
+
+ Address is always relative to the block's local addressing. i.e: The first
+ byte within an external block is represented as ``hwif_out..addr`` == 0,
+ regardless of the absolute address of the block.
+
+hwif_out..req_is_wr
+ If ``1``, denotes that the current transfer is a write. Otherwise transfer is
+ a read.
+
+hwif_out..wr_data
+ Data to be written for the write transfer. This signal is ignored for read
+ transfers.
+
+ The bit-width of this signal always matches the CPUIF's bus width,
+ regardless of the contents of the block.
+
+hwif_out..wr_biten
+ Active-high bit-level write-enable strobes.
+ Only asserted bit positions will change the register value during a write
+ transfer.
+
+Read Response
+^^^^^^^^^^^^^
+hwif_in..rd_ack
+ Single-cycle strobe indicating a read transfer has completed.
+ Qualifies all other read response signals.
+
+hwif_in..rd_data
+ Read response data.
+
+Write Response
+^^^^^^^^^^^^^^
+hwif_in..wr_ack
+ Single-cycle strobe indicating a write transfer has completed.
diff --git a/docs/requirements.txt b/docs/requirements.txt
new file mode 100644
index 0000000..d37f57f
--- /dev/null
+++ b/docs/requirements.txt
@@ -0,0 +1,3 @@
+pygments-systemrdl
+sphinxcontrib-wavedrom
+sphinx-book-theme
diff --git a/docs/udps/extended_swacc.rst b/docs/udps/extended_swacc.rst
new file mode 100644
index 0000000..f760275
--- /dev/null
+++ b/docs/udps/extended_swacc.rst
@@ -0,0 +1,49 @@
+.. _extended_swacc:
+
+Read/Write-specific swacc
+=========================
+
+SystemRDL defines the ``swacc`` property, but it does not distinguish between
+read and write operations - it is asserted on *all* software accesses.
+Similarly, the spec defines ``swmod`` which gets asserted on software writes,
+but can also get asserted if the field has on-read side-effects.
+
+What if you just wanted a plain and simple strobe that is asserted when software
+reads or writes a field? The ``rd_swacc`` and ``wr_swacc`` UDPs provide this
+functionality.
+
+
+Properties
+----------
+These UDP definitions, along with others supported by PeakRDL-regblock can be
+enabled by compiling the following file along with your design:
+:download:`regblock_udps.rdl <../../hdl-src/regblock_udps.rdl>`.
+
+.. describe:: rd_swacc
+
+ If true, infers an output signal ``hwif_out..rd_swacc`` that is asserted
+ when accessed by a software read operation. The output signal is asserted
+ on the same clock cycle that the field is being sampled during the software
+ read operation.
+
+ .. wavedrom::
+
+ {"signal": [
+ {"name": "clk", "wave": "p...."},
+ {"name": "hwif_in..next", "wave": "x.=x.", "data": ["D"]},
+ {"name": "hwif_out..rd_swacc", "wave": "0.10."}
+ ]}
+
+
+.. describe:: wr_swacc
+
+ If true, infers an output signal ``hwif_out..wr_swacc`` that is asserted
+ as the field is being modified by a software write operation.
+
+ .. wavedrom::
+
+ {"signal": [
+ {"name": "clk", "wave": "p....."},
+ {"name": "hwif_out..value", "wave": "=..=..", "data": ["old", "new"]},
+ {"name": "hwif_out..wr_swacc", "wave": "0.10.."}
+ ]}
diff --git a/docs/udps/fixedpoint.rst b/docs/udps/fixedpoint.rst
new file mode 100644
index 0000000..bbcd990
--- /dev/null
+++ b/docs/udps/fixedpoint.rst
@@ -0,0 +1,103 @@
+.. _fixedpoint:
+
+Fixed-Point Fields
+==================
+
+`Fixed-point `_ numbers
+can be used to efficiently represent real numbers using integers. Fixed-point
+numbers consist of some combination of integer bits and fractional bits. The
+number of integer/fractional bits is usually implicitly tracked (not stored)
+for each number, unlike for floating-point numbers.
+
+For this SystemVerilog exporter, these properties only affect the signal type in
+the the ``hwif`` structs. There is no special handling in the internals of
+the regblock.
+
+Properties
+----------
+Fields can be declared as fixed-point numbers using the following two properties:
+
+.. literalinclude:: ../../hdl-src/regblock_udps.rdl
+ :lines: 46-54
+
+The :ref:`is_signed` property can be used in conjunction with these
+properties to declare signed fixed-point fields.
+
+These UDP definitions, along with others supported by PeakRDL-regblock, can be
+enabled by compiling the following file along with your design:
+:download:`regblock_udps.rdl <../../hdl-src/regblock_udps.rdl>`.
+
+.. describe:: intwidth
+
+ * The ``intwidth`` property defines the number of integer bits in the
+ fixed-point representation (including the sign bit, if present).
+
+.. describe:: fracwidth
+
+ * The ``fracwidth`` property defines the number of fractional bits in the
+ fixed-point representation.
+
+Representable Numbers
+^^^^^^^^^^^^^^^^^^^^^
+
+The range of representable real numbers is summarized in the table below.
+
+.. list-table:: Representable Numbers
+ :header-rows: 1
+
+ * - Signedness
+ - Minimum Value
+ - Maximum Value
+ - Step Size
+
+ * - Unsigned
+ - :math:`0`
+ - :math:`2^{\mathrm{intwidth}} - 2^{-\mathrm{fracwidth}}`
+ - :math:`2^{-\mathrm{fracwidth}}`
+
+ * - Signed
+ - :math:`-2^{\mathrm{intwidth}-1}`
+ - :math:`2^{\mathrm{intwidth}-1} - 2^{-\mathrm{fracwidth}}`
+ - :math:`2^{-\mathrm{fracwidth}}`
+
+SystemVerilog Types
+^^^^^^^^^^^^^^^^^^^
+
+When either ``intwidth`` or ``fracwidth`` are defined for a field, that field's
+type in the generated SystemVerilog ``hwif`` struct is
+``logic (signed) [intwidth-1:-fracwidth]``. The bit at index :math:`i` contributes
+a weight of :math:`2^i` to the real number represented.
+
+Other Rules
+^^^^^^^^^^^
+* Only one of ``intwidth`` or ``fracwidth`` need be defined. The other is
+ inferred from the field bit width.
+* The bit width of the field shall be equal to ``intwidth`` + ``fracwidth``.
+* If both ``intwidth`` and ``fracwidth`` are defined for a field, it is an
+ error if their sum does not equal the bit width of the field.
+* Either ``fracwidth`` or ``intwidth`` can be a negative integer. Because
+ SystemRDL does not have a signed integer type, the only way to achieve
+ this is to define one of the widths as larger than the bit width of the
+ component so that the other width is inferred as a negative number.
+* The properties defined above are mutually exclusive with the ``counter``
+ property.
+* The properties defined above are mutually exclusive with the ``encode``
+ property.
+
+Examples
+--------
+
+A 12-bit signed fixed-point field with 4 integer bits and 8 fractional bits
+can be declared with
+
+.. code-block:: systemrdl
+ :emphasize-lines: 3, 4
+
+ field {
+ sw=rw; hw=r;
+ intwidth = 4;
+ is_signed;
+ } fixedpoint_num[11:0] = 0;
+
+This field can represent values from -8.0 to 7.99609375
+in steps of 0.00390625.
diff --git a/docs/udps/intro.rst b/docs/udps/intro.rst
new file mode 100644
index 0000000..63658ca
--- /dev/null
+++ b/docs/udps/intro.rst
@@ -0,0 +1,85 @@
+Introduction
+============
+
+Although the official SystemRDL spec defines numerous properties that allow you
+to define complex register map structures, sometimes they are not enough to
+accurately describe a necessary feature. Fortunately the SystemRDL spec allows
+the language to be extended using "User Defined Properties" (UDPs). The
+PeakRDL-regblock tool understands several UDPs that are described in this
+section.
+
+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
+
+ * - Name
+ - Component
+ - Type
+ - Description
+
+ * - buffer_reads
+ - reg
+ - boolean
+ - If set, reads from the register are double-buffered.
+
+ See: :ref:`read_buffering`.
+
+ * - rbuffer_trigger
+ - reg
+ - reference
+ - Defines the buffered read load trigger.
+
+ See: :ref:`read_buffering`.
+
+ * - buffer_writes
+ - reg
+ - boolean
+ - If set, writes to the register are double-buffered.
+
+ See: :ref:`write_buffering`.
+
+ * - wbuffer_trigger
+ - reg
+ - reference
+ - Defines the buffered write commit trigger.
+
+ See: :ref:`write_buffering`.
+
+ * - rd_swacc
+ - field
+ - boolean
+ - Enables an output strobe that is asserted on sw reads.
+
+ See: :ref:`extended_swacc`.
+
+ * - wr_swacc
+ - field
+ - boolean
+ - Enables an output strobe that is asserted on sw writes.
+
+ See: :ref:`extended_swacc`.
+
+ * - is_signed
+ - field
+ - boolean
+ - Defines the signedness of a field.
+
+ See: :ref:`signed`.
+
+ * - intwidth
+ - field
+ - unsigned integer
+ - Defines the number of integer bits in the fixed-point representation
+ of a field.
+
+ See: :ref:`fixedpoint`.
+
+ * - fracwidth
+ - field
+ - unsigned integer
+ - Defines the number of fractional bits in the fixed-point representation
+ of a field.
+
+ See: :ref:`fixedpoint`.
diff --git a/docs/udps/read_buffering.rst b/docs/udps/read_buffering.rst
new file mode 100644
index 0000000..41a175e
--- /dev/null
+++ b/docs/udps/read_buffering.rst
@@ -0,0 +1,164 @@
+.. _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
+
+These UDP definitions, along with others supported by PeakRDL-regblock can be
+enabled by compiling the following file along with your design:
+:download:`regblock_udps.rdl <../../hdl-src/regblock_udps.rdl>`.
+
+.. describe:: 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 its 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.
+
+.. describe:: 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
+register 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/signed.rst b/docs/udps/signed.rst
new file mode 100644
index 0000000..b2c710c
--- /dev/null
+++ b/docs/udps/signed.rst
@@ -0,0 +1,74 @@
+.. _signed:
+
+Signed Fields
+=============
+
+SystemRDL does not natively provide a way to mark fields as signed or unsigned.
+The ``is_signed`` user-defined property fills this need.
+
+For this SystemVerilog exporter, marking a field as signed only affects the
+signal type in the ``hwif`` structs. There is no special handling in the internals
+of the regblock.
+
+Properties
+----------
+A field can be marked as signed using the following user-defined property:
+
+.. literalinclude:: ../../hdl-src/regblock_udps.rdl
+ :lines: 40-44
+
+This UDP definition, along with others supported by PeakRDL-regblock, can be
+enabled by compiling the following file along with your design:
+:download:`regblock_udps.rdl <../../hdl-src/regblock_udps.rdl>`.
+
+.. describe:: is_signed
+
+ * Assigned value is a boolean.
+ * If true, the hardware interface field will have the type
+ ``logic signed [width-1:0]``.
+ * If false or not defined for a field, the hardware interface field will
+ have the type ``logic [width-1:0]``, which is unsigned by definition.
+
+Other Rules
+^^^^^^^^^^^
+
+* ``is_signed=true`` is mutually exclusive with the ``counter`` property.
+* ``is_signed=true`` is mutually exclusive with the ``encode`` property.
+
+Examples
+--------
+Below are some examples of fields with different signedness.
+
+Signed Fields
+^^^^^^^^^^^^^
+.. code-block:: systemrdl
+ :emphasize-lines: 3, 8
+
+ field {
+ sw=rw; hw=r;
+ is_signed;
+ } signed_num[63:0] = 0;
+
+ field {
+ sw=r; hw=w;
+ is_signed = true;
+ } another_signed_num[19:0] = 20'hFFFFF; // -1
+
+SystemRDL's own integer type is always unsigned. In order to specify a negative
+reset value, the two's complement value must be used as shown in the second
+example above.
+
+Unsigned Fields
+^^^^^^^^^^^^^^^
+.. code-block:: systemrdl
+ :emphasize-lines: 3, 8
+
+ field {
+ sw=rw; hw=r;
+ // fields are unsigned by default
+ } unsigned_num[63:0] = 0;
+
+ field {
+ sw=r; hw=w;
+ is_signed = false;
+ } another_unsigned_num[19:0] = 0;
diff --git a/docs/udps/write_buffering.rst b/docs/udps/write_buffering.rst
new file mode 100644
index 0000000..1d5d2ba
--- /dev/null
+++ b/docs/udps/write_buffering.rst
@@ -0,0 +1,183 @@
+.. _write_buffering:
+
+Write-buffered Registers
+========================
+
+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.
+
+If a register is write-buffered, a holding buffer stage is inserted between the
+decode logic and the field logic. This effectively defers any software write
+operations to that register until a trigger event occurs that releases it.
+Write buffering storage is unique to each register that enables it.
+If a register is not write buffered, this buffer stage is bypassed.
+
+.. figure:: ../diagrams/wbuf.png
+
+
+Properties
+----------
+The behavior of write-buffered registers is defined using the following two
+properties:
+
+.. literalinclude:: ../../hdl-src/regblock_udps.rdl
+ :lines: 20-28
+
+These UDP definitions, along with others supported by PeakRDL-regblock can be
+enabled by compiling the following file along with your design:
+:download:`regblock_udps.rdl <../../hdl-src/regblock_udps.rdl>`.
+
+.. describe:: 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.
+
+.. describe:: wbuffer_trigger
+
+ * Assigned value is a reference to a register, single-bit field, signal,
+ or single-bit property.
+ * Controls when the double-buffer commits the software write operation to
+ the register's fields.
+ * If reference is a single-bit value (signal, field, property reference),
+ then the assertion of that value triggers the buffer to be evicted.
+ * Signal references shall have either activehigh/activelow property set to
+ define the polarity.
+ * If the reference is a reg, then buffer is evicted when the register's
+ highest address is written.
+
+
+Other Rules
+^^^^^^^^^^^
+* It is an error to set ``buffer_writes`` if the register does not contain any
+ writable fields
+* If ``buffer_writes`` is false, then anything assigned to ``wbuffer_trigger``
+ is ignored.
+* The buffered register and the trigger reference shall both be within the
+ same internal device. ie: one cannot be in an external scope with respect to
+ the other.
+* Unless it is a register, the reference assigned to ``wbuffer_trigger`` shall
+ represent a single bit.
+* If a buffered register was not written, any trigger events are ignored.
+* It is valid for a buffered register to be partially written (either via
+ write strobes, or partial addressing).
+* The software write operation is not considered to take place until the
+ buffer is evicted by the trigger. This influences the behavior of properties
+ like ``swmod`` and ``swacc`` - they are not asserted until the register's
+ fields are actually written by the buffer.
+
+
+
+Examples
+--------
+Below are several examples of what you can do with registers that are
+write-buffered.
+
+Wide Atomic Register
+^^^^^^^^^^^^^^^^^^^^
+Without write-buffering, it is impossible to update the state of a 64-bit
+register using a 32-bit CPU interface in a single clock-cycle.
+In this example, it still requires two write-cycles to update the register, but
+the register's storage element is not updated until both sub-words are written.
+Upon writing the 2nd sub-word (the higher byte address), the write data for both
+write cycles are committed to the register's storage element together on the
+same clock cycle. The register is updated atomically.
+
+.. code-block:: systemrdl
+ :emphasize-lines: 4
+
+ reg {
+ regwidth = 64;
+ accesswidth = 32;
+ buffer_writes = true;
+ field {
+ sw=rw; hw=r;
+ } my_field[63:0] = 0;
+ };
+
+
+Atomic Group of Registers
+^^^^^^^^^^^^^^^^^^^^^^^^^
+Perhaps you have a group of registers that need their state to be updated
+atomically. Using the ``wbuffer_trigger`` property, you can define which
+register write operation triggers the group to be updated.
+
+
+.. code-block:: systemrdl
+ :emphasize-lines: 2, 18-20
+
+ reg my_buffered_reg {
+ buffer_writes = true;
+ field {
+ sw=rw; hw=r;
+ } my_field[31:0] = 0;
+ };
+
+ my_buffered_reg reg1;
+ my_buffered_reg reg2;
+ my_buffered_reg reg3;
+
+ reg {
+ field {
+ sw=rw; hw=r;
+ } my_field[31:0] = 0;
+ } reg4;
+
+ reg1->wbuffer_trigger = reg4;
+ reg2->wbuffer_trigger = reg4;
+ reg3->wbuffer_trigger = reg4;
+
+
+In this example software may pre-write information into reg1-reg3, but the
+register write operations do not take effect until software also writes to reg4.
+The write operation to reg4 triggers the buffered data to be committed to
+reg1-reg3. This is guaranteed to occur on the same clock-cycle.
+
+
+Externally Triggered Register Update
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Some applications may require precise timing for when a register (or group of
+registers) update their value. Often software cannot offer such timing
+precision.
+
+In this example, the trigger event is bound to an external signal. When
+asserted, any pending write operation the buffered register will be committed.
+The hwif_out value presents the new register state on the clock cycle after the
+trigger is asserted.
+
+.. code-block:: systemrdl
+ :emphasize-lines: 2, 11-13
+
+ reg my_buffered_reg {
+ buffer_writes = true;
+ field {
+ sw=rw; hw=r;
+ } my_field[31:0] = 0;
+ };
+
+ my_buffered_reg reg1;
+ my_buffered_reg reg2;
+
+ signal {
+ activehigh;
+ } trigger_signal;
+ reg1->wbuffer_trigger = trigger_signal;
+ reg2->wbuffer_trigger = trigger_signal;
+
+After software writes to ``reg1`` & ``reg2``, the written data is held back in
+the write buffer until ``hwif_in..trigger_signal`` is asserted by the hardware.
diff --git a/hdl-src/README.md b/hdl-src/README.md
new file mode 100644
index 0000000..f699d99
--- /dev/null
+++ b/hdl-src/README.md
@@ -0,0 +1,9 @@
+# HDL Source Files
+This folder contains some SystemVerilog definitions that are useful collateral
+to be used alongside this project.
+
+These reference files are free to use for any purpose and are not covered by
+this project's LGPLv3 license.
+
+If for whatever reason you feel the need to reference a license when using
+these, then lets go with the [MIT License](https://choosealicense.com/licenses/mit/)
diff --git a/hdl-src/apb3_intf.sv b/hdl-src/apb3_intf.sv
new file mode 100644
index 0000000..d18d3a0
--- /dev/null
+++ b/hdl-src/apb3_intf.sv
@@ -0,0 +1,40 @@
+interface apb3_intf #(
+ parameter DATA_WIDTH = 32,
+ parameter ADDR_WIDTH = 32
+);
+ // Command
+ logic PSEL;
+ logic PENABLE;
+ logic PWRITE;
+ logic [ADDR_WIDTH-1:0] PADDR;
+ logic [DATA_WIDTH-1:0] PWDATA;
+
+ // Response
+ logic [DATA_WIDTH-1:0] PRDATA;
+ logic PREADY;
+ logic PSLVERR;
+
+ modport master (
+ output PSEL,
+ output PENABLE,
+ output PWRITE,
+ output PADDR,
+ output PWDATA,
+
+ input PRDATA,
+ input PREADY,
+ input PSLVERR
+ );
+
+ modport slave (
+ input PSEL,
+ input PENABLE,
+ input PWRITE,
+ input PADDR,
+ input PWDATA,
+
+ output PRDATA,
+ output PREADY,
+ output PSLVERR
+ );
+endinterface
diff --git a/hdl-src/apb4_intf.sv b/hdl-src/apb4_intf.sv
new file mode 100644
index 0000000..4a554f8
--- /dev/null
+++ b/hdl-src/apb4_intf.sv
@@ -0,0 +1,46 @@
+interface apb4_intf #(
+ parameter DATA_WIDTH = 32,
+ parameter ADDR_WIDTH = 32
+);
+ // Command
+ logic PSEL;
+ logic PENABLE;
+ logic PWRITE;
+ logic [2:0] PPROT;
+ logic [ADDR_WIDTH-1:0] PADDR;
+ logic [DATA_WIDTH-1:0] PWDATA;
+ logic [DATA_WIDTH/8-1:0] PSTRB;
+
+ // Response
+ logic [DATA_WIDTH-1:0] PRDATA;
+ logic PREADY;
+ logic PSLVERR;
+
+ modport master (
+ output PSEL,
+ output PENABLE,
+ output PWRITE,
+ output PPROT,
+ output PADDR,
+ output PWDATA,
+ output PSTRB,
+
+ input PRDATA,
+ input PREADY,
+ input PSLVERR
+ );
+
+ modport slave (
+ input PSEL,
+ input PENABLE,
+ input PWRITE,
+ input PPROT,
+ input PADDR,
+ input PWDATA,
+ input PSTRB,
+
+ output PRDATA,
+ output PREADY,
+ output PSLVERR
+ );
+endinterface
diff --git a/hdl-src/avalon_mm_intf.sv b/hdl-src/avalon_mm_intf.sv
new file mode 100644
index 0000000..1d3d0c3
--- /dev/null
+++ b/hdl-src/avalon_mm_intf.sv
@@ -0,0 +1,46 @@
+interface avalon_mm_intf #(
+ parameter DATA_WIDTH = 32,
+ parameter ADDR_WIDTH = 32 // Important! Avalon uses word addressing
+);
+ // Command
+ logic read;
+ logic write;
+ logic waitrequest;
+ logic [ADDR_WIDTH-1:0] address;
+ logic [DATA_WIDTH-1:0] writedata;
+ logic [DATA_WIDTH/8-1:0] byteenable;
+
+ // Response
+ logic readdatavalid;
+ logic writeresponsevalid;
+ logic [DATA_WIDTH-1:0] readdata;
+ logic [1:0] response;
+
+ modport host (
+ output read,
+ output write,
+ input waitrequest,
+ output address,
+ output writedata,
+ output byteenable,
+
+ input readdatavalid,
+ input writeresponsevalid,
+ input readdata,
+ input response
+ );
+
+ modport agent (
+ input read,
+ input write,
+ output waitrequest,
+ input address,
+ input writedata,
+ input byteenable,
+
+ output readdatavalid,
+ output writeresponsevalid,
+ output readdata,
+ output response
+ );
+endinterface
diff --git a/hdl-src/axi4lite_intf.sv b/hdl-src/axi4lite_intf.sv
new file mode 100644
index 0000000..b0a232d
--- /dev/null
+++ b/hdl-src/axi4lite_intf.sv
@@ -0,0 +1,80 @@
+interface axi4lite_intf #(
+ parameter DATA_WIDTH = 32,
+ parameter ADDR_WIDTH = 32
+);
+ logic AWREADY;
+ logic AWVALID;
+ logic [ADDR_WIDTH-1:0] AWADDR;
+ logic [2:0] AWPROT;
+
+ logic WREADY;
+ logic WVALID;
+ logic [DATA_WIDTH-1:0] WDATA;
+ logic [DATA_WIDTH/8-1:0] WSTRB;
+
+ logic BREADY;
+ logic BVALID;
+ logic [1:0] BRESP;
+
+ logic ARREADY;
+ logic ARVALID;
+ logic [ADDR_WIDTH-1:0] ARADDR;
+ logic [2:0] ARPROT;
+
+ logic RREADY;
+ logic RVALID;
+ logic [DATA_WIDTH-1:0] RDATA;
+ logic [1:0] RRESP;
+
+ modport master (
+ input AWREADY,
+ output AWVALID,
+ output AWADDR,
+ output AWPROT,
+
+ input WREADY,
+ output WVALID,
+ output WDATA,
+ output WSTRB,
+
+ output BREADY,
+ input BVALID,
+ input BRESP,
+
+ input ARREADY,
+ output ARVALID,
+ output ARADDR,
+ output ARPROT,
+
+ output RREADY,
+ input RVALID,
+ input RDATA,
+ input RRESP
+ );
+
+ modport slave (
+ output AWREADY,
+ input AWVALID,
+ input AWADDR,
+ input AWPROT,
+
+ output WREADY,
+ input WVALID,
+ input WDATA,
+ input WSTRB,
+
+ input BREADY,
+ output BVALID,
+ output BRESP,
+
+ output ARREADY,
+ input ARVALID,
+ input ARADDR,
+ input ARPROT,
+
+ input RREADY,
+ output RVALID,
+ output RDATA,
+ output RRESP
+ );
+endinterface
diff --git a/hdl-src/regblock_udps.rdl b/hdl-src/regblock_udps.rdl
new file mode 100644
index 0000000..f6ded7b
--- /dev/null
+++ b/hdl-src/regblock_udps.rdl
@@ -0,0 +1,54 @@
+/*
+ * This file defines several property extensions that are understood by the
+ * PeakRDL-Regblock SystemVerilog code generator.
+ *
+ * Compile this file prior to your other SystemRDL sources.
+ *
+ * For more details, see: https://peakrdl-regblock.readthedocs.io/en/latest/udps/intro.html
+ */
+
+property buffer_reads {
+ component = reg;
+ type = boolean;
+};
+
+property rbuffer_trigger {
+ component = reg;
+ type = ref;
+};
+
+property buffer_writes {
+ component = reg;
+ type = boolean;
+};
+
+property wbuffer_trigger {
+ component = reg;
+ type = ref;
+};
+
+property rd_swacc {
+ component = field;
+ type = boolean;
+};
+
+property wr_swacc {
+ component = field;
+ type = boolean;
+};
+
+property is_signed {
+ type = boolean;
+ component = field;
+ default = true;
+};
+
+property intwidth {
+ type = longint unsigned;
+ component = field;
+};
+
+property fracwidth {
+ type = longint unsigned;
+ component = field;
+};
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..db6f4f1
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,51 @@
+[build-system]
+requires = ["setuptools", "setuptools-scm"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "peakrdl-regblock"
+dynamic = ["version"]
+requires-python = ">=3.7"
+dependencies = [
+ "systemrdl-compiler ~= 1.29",
+ "Jinja2>=2.11",
+]
+
+authors = [
+ {name="Alex Mykyta"},
+]
+description = "Compile SystemRDL into a SystemVerilog control/status register (CSR) block"
+readme = "README.md"
+license = {text = "LGPLv3"}
+keywords = [
+ "SystemRDL", "PeakRDL", "CSR", "compiler", "tool", "registers", "generator",
+ "Verilog", "SystemVerilog", "register abstraction layer",
+ "FPGA", "ASIC",
+]
+classifiers = [
+ "Development Status :: 5 - Production/Stable",
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3 :: Only",
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
+ "Operating System :: OS Independent",
+ "Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)",
+]
+
+[project.optional-dependencies]
+cli = [
+ "peakrdl-cli >= 1.2.3",
+]
+
+[project.urls]
+Source = "https://github.com/SystemRDL/PeakRDL-regblock"
+Tracker = "https://github.com/SystemRDL/PeakRDL-regblock/issues"
+Changelog = "https://github.com/SystemRDL/PeakRDL-regblock/releases"
+Documentation = "https://peakrdl-regblock.readthedocs.io/"
+
+[tool.setuptools.dynamic]
+version = {attr = "peakrdl_regblock.__about__.__version__"}
+
+[project.entry-points."peakrdl.exporters"]
+regblock = "peakrdl_regblock.__peakrdl__:Exporter"
diff --git a/src/peakrdl_regblock/__about__.py b/src/peakrdl_regblock/__about__.py
new file mode 100644
index 0000000..edebc2e
--- /dev/null
+++ b/src/peakrdl_regblock/__about__.py
@@ -0,0 +1,2 @@
+version_info = (1, 1, 1)
+__version__ = ".".join([str(n) for n in version_info])
diff --git a/src/peakrdl_regblock/__init__.py b/src/peakrdl_regblock/__init__.py
new file mode 100644
index 0000000..c64c756
--- /dev/null
+++ b/src/peakrdl_regblock/__init__.py
@@ -0,0 +1,3 @@
+from .__about__ import __version__
+
+from .exporter import RegblockExporter
diff --git a/src/peakrdl_regblock/__peakrdl__.py b/src/peakrdl_regblock/__peakrdl__.py
new file mode 100644
index 0000000..c67bdb5
--- /dev/null
+++ b/src/peakrdl_regblock/__peakrdl__.py
@@ -0,0 +1,205 @@
+from typing import TYPE_CHECKING, Dict, Type
+import functools
+import sys
+
+from peakrdl.plugins.exporter import ExporterSubcommandPlugin
+from peakrdl.config import schema
+from peakrdl.plugins.entry_points import get_entry_points
+
+from .exporter import RegblockExporter
+from .cpuif import CpuifBase, apb3, apb4, axi4lite, passthrough, avalon
+from .udps import ALL_UDPS
+
+if TYPE_CHECKING:
+ import argparse
+ from systemrdl.node import AddrmapNode
+
+class Exporter(ExporterSubcommandPlugin):
+ short_desc = "Generate a SystemVerilog control/status register (CSR) block"
+
+ udp_definitions = ALL_UDPS
+
+ cfg_schema = {
+ "cpuifs": {"*": schema.PythonObjectImport()},
+ "default_reset": schema.Choice(["rst", "rst_n", "arst", "arst_n"]),
+ }
+
+ @functools.lru_cache()
+ def get_cpuifs(self) -> Dict[str, Type[CpuifBase]]:
+
+ # All built-in CPUIFs
+ cpuifs = {
+ "passthrough": passthrough.PassthroughCpuif,
+ "apb3": apb3.APB3_Cpuif,
+ "apb3-flat": apb3.APB3_Cpuif_flattened,
+ "apb4": apb4.APB4_Cpuif,
+ "apb4-flat": apb4.APB4_Cpuif_flattened,
+ "axi4-lite": axi4lite.AXI4Lite_Cpuif,
+ "axi4-lite-flat": axi4lite.AXI4Lite_Cpuif_flattened,
+ "avalon-mm": avalon.Avalon_Cpuif,
+ "avalon-mm-flat": avalon.Avalon_Cpuif_flattened,
+ }
+
+ # Load any cpuifs specified via entry points
+ for ep, dist in get_entry_points("peakrdl_regblock.cpuif"):
+ name = ep.name
+ cpuif = ep.load()
+ if name in cpuifs:
+ raise RuntimeError(f"A plugin for 'peakrdl-regblock' tried to load cpuif '{name}' but it already exists")
+ if not issubclass(cpuif, CpuifBase):
+ raise RuntimeError(f"A plugin for 'peakrdl-regblock' tried to load cpuif '{name}' but it not a CpuifBase class")
+ cpuifs[name] = cpuif
+
+ # Load any CPUIFs via config import
+ for name, cpuif in self.cfg['cpuifs'].items():
+ if name in cpuifs:
+ raise RuntimeError(f"A plugin for 'peakrdl-regblock' tried to load cpuif '{name}' but it already exists")
+ if not issubclass(cpuif, CpuifBase):
+ raise RuntimeError(f"A plugin for 'peakrdl-regblock' tried to load cpuif '{name}' but it not a CpuifBase class")
+ cpuifs[name] = cpuif
+
+ return cpuifs
+
+
+ def add_exporter_arguments(self, arg_group: 'argparse._ActionsContainer') -> None:
+ cpuifs = self.get_cpuifs()
+
+ arg_group.add_argument(
+ "--cpuif",
+ choices=cpuifs.keys(),
+ default="apb3",
+ help="Select the CPU interface protocol to use [apb3]"
+ )
+
+ arg_group.add_argument(
+ "--module-name",
+ metavar="NAME",
+ default=None,
+ help="Override the SystemVerilog module name"
+ )
+
+ arg_group.add_argument(
+ "--package-name",
+ metavar="NAME",
+ default=None,
+ help="Override the SystemVerilog package name"
+ )
+
+ arg_group.add_argument(
+ "--type-style",
+ dest="type_style",
+ choices=['lexical', 'hier'],
+ default="lexical",
+ help="""Choose how HWIF struct type names are generated.
+ The 'lexical' style will use RDL lexical scope & type names where
+ possible and attempt to re-use equivalent type definitions.
+ The 'hier' style uses component's hierarchy as the struct type name. [lexical]
+ """
+ )
+
+ arg_group.add_argument(
+ "--hwif-report",
+ action="store_true",
+ default=False,
+ help="Generate a HWIF report file"
+ )
+
+ arg_group.add_argument(
+ "--addr-width",
+ type=int,
+ default=None,
+ help="""Override the CPU interface's address width. By default,
+ address width is sized to the contents of the regblock.
+ """
+ )
+
+ arg_group.add_argument(
+ "--rt-read-fanin",
+ action="store_true",
+ default=False,
+ help="Enable additional read path retiming. Good for register blocks with large readback fan-in"
+ )
+ arg_group.add_argument(
+ "--rt-read-response",
+ action="store_true",
+ default=False,
+ help="Enable additional retiming stage between readback fan-in and cpu interface"
+ )
+ arg_group.add_argument(
+ "--rt-external",
+ help="Retime outputs to external components. Specify a comma-separated list of: reg,regfile,mem,addrmap,all"
+ )
+
+ arg_group.add_argument(
+ "--default-reset",
+ choices=["rst", "rst_n", "arst", "arst_n"],
+ default=None,
+ help="""Choose the default style of reset signal if not explicitly
+ specified by the SystemRDL design. If unspecified, the default reset
+ is active-high and synchronous [rst]"""
+ )
+
+
+ def do_export(self, top_node: 'AddrmapNode', options: 'argparse.Namespace') -> None:
+ cpuifs = self.get_cpuifs()
+
+ retime_external_reg = False
+ retime_external_regfile = False
+ retime_external_mem = False
+ retime_external_addrmap = False
+ if options.rt_external:
+ for key in options.rt_external.split(","):
+ key = key.strip().lower()
+ if key == "reg":
+ retime_external_reg = True
+ elif key == "regfile":
+ retime_external_regfile = True
+ elif key == "mem":
+ retime_external_mem = True
+ elif key == "addrmap":
+ retime_external_addrmap = True
+ elif key == "all":
+ retime_external_reg = True
+ retime_external_regfile = True
+ retime_external_mem = True
+ retime_external_addrmap = True
+ else:
+ print("error: invalid option for --rt-external: '%s'" % key, file=sys.stderr)
+
+ # Get default reset. Favor command-line over cfg. Fall back to 'rst'
+ default_rst = options.default_reset or self.cfg['default_reset'] or "rst"
+ if default_rst == "rst":
+ default_reset_activelow = False
+ default_reset_async = False
+ elif default_rst == "rst_n":
+ default_reset_activelow = True
+ default_reset_async = False
+ elif default_rst == "arst":
+ default_reset_activelow = False
+ default_reset_async = True
+ elif default_rst == "arst_n":
+ default_reset_activelow = True
+ default_reset_async = True
+ else:
+ raise RuntimeError
+
+
+ x = RegblockExporter()
+ x.export(
+ top_node,
+ options.output,
+ cpuif_cls=cpuifs[options.cpuif],
+ module_name=options.module_name,
+ package_name=options.package_name,
+ reuse_hwif_typedefs=(options.type_style == "lexical"),
+ retime_read_fanin=options.rt_read_fanin,
+ retime_read_response=options.rt_read_response,
+ retime_external_reg=retime_external_reg,
+ retime_external_regfile=retime_external_regfile,
+ retime_external_mem=retime_external_mem,
+ retime_external_addrmap=retime_external_addrmap,
+ generate_hwif_report=options.hwif_report,
+ address_width=options.addr_width,
+ default_reset_activelow=default_reset_activelow,
+ default_reset_async=default_reset_async,
+ )
diff --git a/src/peakrdl_regblock/addr_decode.py b/src/peakrdl_regblock/addr_decode.py
new file mode 100644
index 0000000..1390192
--- /dev/null
+++ b/src/peakrdl_regblock/addr_decode.py
@@ -0,0 +1,219 @@
+from typing import TYPE_CHECKING, Union, List, Optional
+
+from systemrdl.node import FieldNode, RegNode
+from systemrdl.walker import WalkerAction
+
+from .utils import get_indexed_path
+from .struct_generator import RDLStructGenerator
+from .forloop_generator import RDLForLoopGenerator
+from .identifier_filter import kw_filter as kwf
+from .sv_int import SVInt
+
+if TYPE_CHECKING:
+ from .exporter import RegblockExporter
+ from systemrdl.node import AddrmapNode, AddressableNode
+ from systemrdl.node import RegfileNode, MemNode
+
+class AddressDecode:
+ def __init__(self, exp:'RegblockExporter'):
+ self.exp = exp
+
+ @property
+ def top_node(self) -> 'AddrmapNode':
+ return self.exp.ds.top_node
+
+ def get_strobe_struct(self) -> str:
+ struct_gen = DecodeStructGenerator()
+ s = struct_gen.get_struct(self.top_node, "decoded_reg_strb_t")
+ assert s is not None # guaranteed to have at least one reg
+ return s
+
+ def get_implementation(self) -> str:
+ gen = DecodeLogicGenerator(self)
+ s = gen.get_content(self.top_node)
+ assert s is not None
+ return s
+
+ def get_access_strobe(self, node: Union[RegNode, FieldNode], reduce_substrobes: bool=True) -> str:
+ """
+ Returns the Verilog string that represents the register/field's access strobe.
+ """
+ if isinstance(node, FieldNode):
+ field = node
+ path = get_indexed_path(self.top_node, node.parent)
+
+ regwidth = node.parent.get_property('regwidth')
+ accesswidth = node.parent.get_property('accesswidth')
+ if regwidth > accesswidth:
+ # Is wide register.
+ # Determine the substrobe(s) relevant to this field
+ sidx_hi = field.msb // accesswidth
+ sidx_lo = field.lsb // accesswidth
+ if sidx_hi == sidx_lo:
+ suffix = f"[{sidx_lo}]"
+ else:
+ suffix = f"[{sidx_hi}:{sidx_lo}]"
+ path += suffix
+
+ if sidx_hi != sidx_lo and reduce_substrobes:
+ return "|decoded_reg_strb." + path
+
+ else:
+ path = get_indexed_path(self.top_node, node)
+
+ return "decoded_reg_strb." + path
+
+ def get_external_block_access_strobe(self, node: 'AddressableNode') -> str:
+ assert node.external
+ assert not isinstance(node, RegNode)
+ path = get_indexed_path(self.top_node, node)
+ return "decoded_reg_strb." + path
+
+
+class DecodeStructGenerator(RDLStructGenerator):
+
+ def _enter_external_block(self, node: 'AddressableNode') -> None:
+ self.add_member(
+ kwf(node.inst_name),
+ array_dimensions=node.array_dimensions,
+ )
+
+ def enter_Addrmap(self, node: 'AddrmapNode') -> Optional[WalkerAction]:
+ assert node.external
+ self._enter_external_block(node)
+ return WalkerAction.SkipDescendants
+
+ def exit_Addrmap(self, node: 'AddrmapNode') -> None:
+ assert node.external
+
+ def enter_Regfile(self, node: 'RegfileNode') -> Optional[WalkerAction]:
+ if node.external:
+ self._enter_external_block(node)
+ return WalkerAction.SkipDescendants
+ super().enter_Regfile(node)
+ return WalkerAction.Continue
+
+ def exit_Regfile(self, node: 'RegfileNode') -> None:
+ if node.external:
+ return
+ super().exit_Regfile(node)
+
+ def enter_Mem(self, node: 'MemNode') -> Optional[WalkerAction]:
+ assert node.external
+ self._enter_external_block(node)
+ return WalkerAction.SkipDescendants
+
+ def exit_Mem(self, node: 'MemNode') -> None:
+ assert node.external
+
+ def enter_Reg(self, node: 'RegNode') -> None:
+ # if register is "wide", expand the strobe to be able to access the sub-words
+ n_subwords = node.get_property("regwidth") // node.get_property("accesswidth")
+
+ self.add_member(
+ kwf(node.inst_name),
+ width=n_subwords,
+ array_dimensions=node.array_dimensions,
+ )
+
+ # Stub out
+ def exit_Reg(self, node: 'RegNode') -> None:
+ pass
+ def enter_Field(self, node: 'FieldNode') -> None:
+ pass
+
+
+class DecodeLogicGenerator(RDLForLoopGenerator):
+
+ def __init__(self, addr_decode: AddressDecode) -> None:
+ self.addr_decode = addr_decode
+ super().__init__()
+
+ # List of address strides for each dimension
+ self._array_stride_stack = [] # type: List[int]
+
+
+ def enter_AddressableComponent(self, node: 'AddressableNode') -> Optional[WalkerAction]:
+ super().enter_AddressableComponent(node)
+
+ if node.array_dimensions:
+ assert node.array_stride is not None
+ # Collect strides for each array dimension
+ current_stride = node.array_stride
+ strides = []
+ for dim in reversed(node.array_dimensions):
+ strides.append(current_stride)
+ current_stride *= dim
+ strides.reverse()
+ self._array_stride_stack.extend(strides)
+
+ if node.external and not isinstance(node, RegNode):
+ # Is an external block
+ addr_str = self._get_address_str(node)
+ strb = self.addr_decode.get_external_block_access_strobe(node)
+ rhs = f"cpuif_req_masked & (cpuif_addr >= {addr_str}) & (cpuif_addr <= {addr_str} + {SVInt(node.size - 1, self.addr_decode.exp.ds.addr_width)})"
+ self.add_content(f"{strb} = {rhs};")
+ self.add_content(f"is_external |= {rhs};")
+ return WalkerAction.SkipDescendants
+
+ return WalkerAction.Continue
+
+
+ def _get_address_str(self, node: 'AddressableNode', subword_offset: int=0) -> str:
+ expr_width = self.addr_decode.exp.ds.addr_width
+ a = str(SVInt(
+ node.raw_absolute_address - self.addr_decode.top_node.raw_absolute_address + subword_offset,
+ expr_width
+ ))
+ for i, stride in enumerate(self._array_stride_stack):
+ a += f" + ({expr_width})'(i{i}) * {SVInt(stride, expr_width)}"
+ return a
+
+
+ def enter_Reg(self, node: RegNode) -> None:
+ regwidth = node.get_property('regwidth')
+ accesswidth = node.get_property('accesswidth')
+
+ if regwidth == accesswidth:
+ rhs = f"cpuif_req_masked & (cpuif_addr == {self._get_address_str(node)})"
+ s = f"{self.addr_decode.get_access_strobe(node)} = {rhs};"
+ self.add_content(s)
+ if node.external:
+ readable = node.has_sw_readable
+ writable = node.has_sw_writable
+ if readable and writable:
+ self.add_content(f"is_external |= {rhs};")
+ elif readable and not writable:
+ self.add_content(f"is_external |= {rhs} & !cpuif_req_is_wr;")
+ elif not readable and writable:
+ self.add_content(f"is_external |= {rhs} & cpuif_req_is_wr;")
+ else:
+ raise RuntimeError
+ else:
+ # Register is wide. Create a substrobe for each subword
+ n_subwords = regwidth // accesswidth
+ subword_stride = accesswidth // 8
+ for i in range(n_subwords):
+ rhs = f"cpuif_req_masked & (cpuif_addr == {self._get_address_str(node, subword_offset=(i*subword_stride))})"
+ s = f"{self.addr_decode.get_access_strobe(node)}[{i}] = {rhs};"
+ self.add_content(s)
+ if node.external:
+ readable = node.has_sw_readable
+ writable = node.has_sw_writable
+ if readable and writable:
+ self.add_content(f"is_external |= {rhs};")
+ elif readable and not writable:
+ self.add_content(f"is_external |= {rhs} & !cpuif_req_is_wr;")
+ elif not readable and writable:
+ self.add_content(f"is_external |= {rhs} & cpuif_req_is_wr;")
+ else:
+ raise RuntimeError
+
+ def exit_AddressableComponent(self, node: 'AddressableNode') -> None:
+ super().exit_AddressableComponent(node)
+
+ if not node.array_dimensions:
+ return
+
+ for _ in node.array_dimensions:
+ self._array_stride_stack.pop()
diff --git a/src/peakrdl_regblock/cpuif/__init__.py b/src/peakrdl_regblock/cpuif/__init__.py
new file mode 100644
index 0000000..08b2adc
--- /dev/null
+++ b/src/peakrdl_regblock/cpuif/__init__.py
@@ -0,0 +1 @@
+from .base import CpuifBase
diff --git a/src/peakrdl_regblock/cpuif/apb3/__init__.py b/src/peakrdl_regblock/cpuif/apb3/__init__.py
new file mode 100644
index 0000000..4b1adc1
--- /dev/null
+++ b/src/peakrdl_regblock/cpuif/apb3/__init__.py
@@ -0,0 +1,33 @@
+from ..base import CpuifBase
+
+class APB3_Cpuif(CpuifBase):
+ template_path = "apb3_tmpl.sv"
+ is_interface = True
+
+ @property
+ def port_declaration(self) -> str:
+ return "apb3_intf.slave s_apb"
+
+ def signal(self, name:str) -> str:
+ return "s_apb." + name.upper()
+
+
+class APB3_Cpuif_flattened(APB3_Cpuif):
+ is_interface = False
+
+ @property
+ def port_declaration(self) -> str:
+ lines = [
+ "input wire " + self.signal("psel"),
+ "input wire " + self.signal("penable"),
+ "input wire " + self.signal("pwrite"),
+ f"input wire [{self.addr_width-1}:0] " + self.signal("paddr"),
+ f"input wire [{self.data_width-1}:0] " + self.signal("pwdata"),
+ "output logic " + self.signal("pready"),
+ f"output logic [{self.data_width-1}:0] " + self.signal("prdata"),
+ "output logic " + self.signal("pslverr"),
+ ]
+ return ",\n".join(lines)
+
+ def signal(self, name:str) -> str:
+ return "s_apb_" + name
diff --git a/src/peakrdl_regblock/cpuif/apb3/apb3_tmpl.sv b/src/peakrdl_regblock/cpuif/apb3/apb3_tmpl.sv
new file mode 100644
index 0000000..33a3663
--- /dev/null
+++ b/src/peakrdl_regblock/cpuif/apb3/apb3_tmpl.sv
@@ -0,0 +1,48 @@
+{%- if cpuif.is_interface -%}
+`ifndef SYNTHESIS
+ initial begin
+ assert_bad_addr_width: assert($bits({{cpuif.signal("paddr")}}) >= {{ds.package_name}}::{{ds.module_name.upper()}}_MIN_ADDR_WIDTH)
+ else $error("Interface address width of %0d is too small. Shall be at least %0d bits", $bits({{cpuif.signal("paddr")}}), {{ds.package_name}}::{{ds.module_name.upper()}}_MIN_ADDR_WIDTH);
+ assert_bad_data_width: assert($bits({{cpuif.signal("pwdata")}}) == {{ds.package_name}}::{{ds.module_name.upper()}}_DATA_WIDTH)
+ else $error("Interface data width of %0d is incorrect. Shall be %0d bits", $bits({{cpuif.signal("pwdata")}}), {{ds.package_name}}::{{ds.module_name.upper()}}_DATA_WIDTH);
+ end
+`endif
+
+{% endif -%}
+
+// Request
+logic is_active;
+always_ff {{get_always_ff_event(cpuif.reset)}} begin
+ if({{get_resetsignal(cpuif.reset)}}) begin
+ is_active <= '0;
+ cpuif_req <= '0;
+ cpuif_req_is_wr <= '0;
+ cpuif_addr <= '0;
+ cpuif_wr_data <= '0;
+ end else begin
+ if(~is_active) begin
+ if({{cpuif.signal("psel")}}) begin
+ is_active <= '1;
+ cpuif_req <= '1;
+ cpuif_req_is_wr <= {{cpuif.signal("pwrite")}};
+ {%- if cpuif.data_width_bytes == 1 %}
+ cpuif_addr <= {{cpuif.signal("paddr")}}[{{cpuif.addr_width-1}}:0];
+ {%- else %}
+ cpuif_addr <= { {{-cpuif.signal("paddr")}}[{{cpuif.addr_width-1}}:{{clog2(cpuif.data_width_bytes)}}], {{clog2(cpuif.data_width_bytes)}}'b0};
+ {%- endif %}
+ cpuif_wr_data <= {{cpuif.signal("pwdata")}};
+ end
+ end else begin
+ cpuif_req <= '0;
+ if(cpuif_rd_ack || cpuif_wr_ack) begin
+ is_active <= '0;
+ end
+ end
+ end
+end
+assign cpuif_wr_biten = '1;
+
+// Response
+assign {{cpuif.signal("pready")}} = cpuif_rd_ack | cpuif_wr_ack;
+assign {{cpuif.signal("prdata")}} = cpuif_rd_data;
+assign {{cpuif.signal("pslverr")}} = cpuif_rd_err | cpuif_wr_err;
diff --git a/src/peakrdl_regblock/cpuif/apb4/__init__.py b/src/peakrdl_regblock/cpuif/apb4/__init__.py
new file mode 100644
index 0000000..45b2961
--- /dev/null
+++ b/src/peakrdl_regblock/cpuif/apb4/__init__.py
@@ -0,0 +1,35 @@
+from ..base import CpuifBase
+
+class APB4_Cpuif(CpuifBase):
+ template_path = "apb4_tmpl.sv"
+ is_interface = True
+
+ @property
+ def port_declaration(self) -> str:
+ return "apb4_intf.slave s_apb"
+
+ def signal(self, name:str) -> str:
+ return "s_apb." + name.upper()
+
+
+class APB4_Cpuif_flattened(APB4_Cpuif):
+ is_interface = False
+
+ @property
+ def port_declaration(self) -> str:
+ lines = [
+ "input wire " + self.signal("psel"),
+ "input wire " + self.signal("penable"),
+ "input wire " + self.signal("pwrite"),
+ "input wire [2:0] " + self.signal("pprot"),
+ f"input wire [{self.addr_width-1}:0] " + self.signal("paddr"),
+ f"input wire [{self.data_width-1}:0] " + self.signal("pwdata"),
+ f"input wire [{self.data_width_bytes-1}:0] " + self.signal("pstrb"),
+ "output logic " + self.signal("pready"),
+ f"output logic [{self.data_width-1}:0] " + self.signal("prdata"),
+ "output logic " + self.signal("pslverr"),
+ ]
+ return ",\n".join(lines)
+
+ def signal(self, name:str) -> str:
+ return "s_apb_" + name
diff --git a/src/peakrdl_regblock/cpuif/apb4/apb4_tmpl.sv b/src/peakrdl_regblock/cpuif/apb4/apb4_tmpl.sv
new file mode 100644
index 0000000..4293bec
--- /dev/null
+++ b/src/peakrdl_regblock/cpuif/apb4/apb4_tmpl.sv
@@ -0,0 +1,51 @@
+{%- if cpuif.is_interface -%}
+`ifndef SYNTHESIS
+ initial begin
+ assert_bad_addr_width: assert($bits({{cpuif.signal("paddr")}}) >= {{ds.package_name}}::{{ds.module_name.upper()}}_MIN_ADDR_WIDTH)
+ else $error("Interface address width of %0d is too small. Shall be at least %0d bits", $bits({{cpuif.signal("paddr")}}), {{ds.package_name}}::{{ds.module_name.upper()}}_MIN_ADDR_WIDTH);
+ assert_bad_data_width: assert($bits({{cpuif.signal("pwdata")}}) == {{ds.package_name}}::{{ds.module_name.upper()}}_DATA_WIDTH)
+ else $error("Interface data width of %0d is incorrect. Shall be %0d bits", $bits({{cpuif.signal("pwdata")}}), {{ds.package_name}}::{{ds.module_name.upper()}}_DATA_WIDTH);
+ end
+`endif
+
+{% endif -%}
+
+// Request
+logic is_active;
+always_ff {{get_always_ff_event(cpuif.reset)}} begin
+ if({{get_resetsignal(cpuif.reset)}}) begin
+ is_active <= '0;
+ cpuif_req <= '0;
+ cpuif_req_is_wr <= '0;
+ cpuif_addr <= '0;
+ cpuif_wr_data <= '0;
+ cpuif_wr_biten <= '0;
+ end else begin
+ if(~is_active) begin
+ if({{cpuif.signal("psel")}}) begin
+ is_active <= '1;
+ cpuif_req <= '1;
+ cpuif_req_is_wr <= {{cpuif.signal("pwrite")}};
+ {%- if cpuif.data_width_bytes == 1 %}
+ cpuif_addr <= {{cpuif.signal("paddr")}}[{{cpuif.addr_width-1}}:0];
+ {%- else %}
+ cpuif_addr <= { {{-cpuif.signal("paddr")}}[{{cpuif.addr_width-1}}:{{clog2(cpuif.data_width_bytes)}}], {{clog2(cpuif.data_width_bytes)}}'b0};
+ {%- endif %}
+ cpuif_wr_data <= {{cpuif.signal("pwdata")}};
+ for(int i=0; i<{{cpuif.data_width_bytes}}; i++) begin
+ cpuif_wr_biten[i*8 +: 8] <= {8{ {{-cpuif.signal("pstrb")}}[i]}};
+ end
+ end
+ end else begin
+ cpuif_req <= '0;
+ if(cpuif_rd_ack || cpuif_wr_ack) begin
+ is_active <= '0;
+ end
+ end
+ end
+end
+
+// Response
+assign {{cpuif.signal("pready")}} = cpuif_rd_ack | cpuif_wr_ack;
+assign {{cpuif.signal("prdata")}} = cpuif_rd_data;
+assign {{cpuif.signal("pslverr")}} = cpuif_rd_err | cpuif_wr_err;
diff --git a/src/peakrdl_regblock/cpuif/avalon/__init__.py b/src/peakrdl_regblock/cpuif/avalon/__init__.py
new file mode 100644
index 0000000..20d8a59
--- /dev/null
+++ b/src/peakrdl_regblock/cpuif/avalon/__init__.py
@@ -0,0 +1,40 @@
+from ..base import CpuifBase
+from ...utils import clog2
+
+class Avalon_Cpuif(CpuifBase):
+ template_path = "avalon_tmpl.sv"
+ is_interface = True
+
+ @property
+ def port_declaration(self) -> str:
+ return "avalon_mm_intf.agent avalon"
+
+ def signal(self, name:str) -> str:
+ return "avalon." + name
+
+ @property
+ def word_addr_width(self) -> int:
+ # Avalon agents use word addressing, therefore address width is reduced
+ return self.addr_width - clog2(self.data_width_bytes)
+
+class Avalon_Cpuif_flattened(Avalon_Cpuif):
+ is_interface = False
+
+ @property
+ def port_declaration(self) -> str:
+ lines = [
+ "input wire " + self.signal("read"),
+ "input wire " + self.signal("write"),
+ "output logic " + self.signal("waitrequest"),
+ f"input wire [{self.word_addr_width-1}:0] " + self.signal("address"),
+ f"input wire [{self.data_width-1}:0] " + self.signal("writedata"),
+ f"input wire [{self.data_width_bytes-1}:0] " + self.signal("byteenable"),
+ "output logic " + self.signal("readdatavalid"),
+ "output logic " + self.signal("writeresponsevalid"),
+ f"output logic [{self.data_width-1}:0] " + self.signal("readdata"),
+ "output logic [1:0] " + self.signal("response"),
+ ]
+ return ",\n".join(lines)
+
+ def signal(self, name:str) -> str:
+ return "avalon_" + name
diff --git a/src/peakrdl_regblock/cpuif/avalon/avalon_tmpl.sv b/src/peakrdl_regblock/cpuif/avalon/avalon_tmpl.sv
new file mode 100644
index 0000000..fe59b23
--- /dev/null
+++ b/src/peakrdl_regblock/cpuif/avalon/avalon_tmpl.sv
@@ -0,0 +1,41 @@
+{%- if cpuif.is_interface -%}
+`ifndef SYNTHESIS
+ initial begin
+ assert_bad_addr_width: assert($bits({{cpuif.signal("address")}}) >= {{cpuif.word_addr_width}})
+ else $error("Interface address width of %0d is too small. Shall be at least %0d bits", $bits({{cpuif.signal("address")}}), {{cpuif.word_addr_width}});
+ assert_bad_data_width: assert($bits({{cpuif.signal("writedata")}}) == {{ds.package_name}}::{{ds.module_name.upper()}}_DATA_WIDTH)
+ else $error("Interface data width of %0d is incorrect. Shall be %0d bits", $bits({{cpuif.signal("writedata")}}), {{ds.package_name}}::{{ds.module_name.upper()}}_DATA_WIDTH);
+ end
+`endif
+
+{% endif -%}
+
+// Request
+always_comb begin
+ cpuif_req = {{cpuif.signal("read")}} | {{cpuif.signal("write")}};
+ cpuif_req_is_wr = {{cpuif.signal("write")}};
+ {%- if cpuif.data_width_bytes == 1 %}
+ cpuif_addr = {{cpuif.signal("address")}};
+ {%- else %}
+ cpuif_addr = { {{-cpuif.signal("address")}}, {{clog2(cpuif.data_width_bytes)}}'b0};
+ {%- endif %}
+ cpuif_wr_data = {{cpuif.signal("writedata")}};
+ for(int i=0; i<{{cpuif.data_width_bytes}}; i++) begin
+ cpuif_wr_biten[i*8 +: 8] = {8{ {{-cpuif.signal("byteenable")}}[i]}};
+ end
+ {{cpuif.signal("waitrequest")}} = (cpuif_req_stall_rd & {{cpuif.signal("read")}}) | (cpuif_req_stall_wr & {{cpuif.signal("write")}});
+end
+
+// Response
+always_comb begin
+ {{cpuif.signal("readdatavalid")}} = cpuif_rd_ack;
+ {{cpuif.signal("writeresponsevalid")}} = cpuif_wr_ack;
+ {{cpuif.signal("readdata")}} = cpuif_rd_data;
+ if(cpuif_rd_err || cpuif_wr_err) begin
+ // SLVERR
+ {{cpuif.signal("response")}} = 2'b10;
+ end else begin
+ // OK
+ {{cpuif.signal("response")}} = 2'b00;
+ end
+end
diff --git a/src/peakrdl_regblock/cpuif/axi4lite/__init__.py b/src/peakrdl_regblock/cpuif/axi4lite/__init__.py
new file mode 100644
index 0000000..65dead5
--- /dev/null
+++ b/src/peakrdl_regblock/cpuif/axi4lite/__init__.py
@@ -0,0 +1,70 @@
+from ..base import CpuifBase
+
+class AXI4Lite_Cpuif(CpuifBase):
+ template_path = "axi4lite_tmpl.sv"
+ is_interface = True
+
+ @property
+ def port_declaration(self) -> str:
+ return "axi4lite_intf.slave s_axil"
+
+ def signal(self, name:str) -> str:
+ return "s_axil." + name.upper()
+
+ @property
+ def regblock_latency(self) -> int:
+ return max(self.exp.ds.min_read_latency, self.exp.ds.min_write_latency)
+
+ @property
+ def max_outstanding(self) -> int:
+ """
+ Best pipelined performance is when the max outstanding transactions
+ is the design's latency + 2.
+ Anything beyond that does not have any effect, aside from adding unnecessary
+ logic and additional buffer-bloat latency.
+ """
+ return self.regblock_latency + 2
+
+ @property
+ def resp_buffer_size(self) -> int:
+ """
+ Response buffer size must be greater or equal to max outstanding
+ transactions to prevent response overrun.
+ """
+ return self.max_outstanding
+
+
+class AXI4Lite_Cpuif_flattened(AXI4Lite_Cpuif):
+ is_interface = False
+
+ @property
+ def port_declaration(self) -> str:
+ lines = [
+ "output logic " + self.signal("awready"),
+ "input wire " + self.signal("awvalid"),
+ f"input wire [{self.addr_width-1}:0] " + self.signal("awaddr"),
+ "input wire [2:0] " + self.signal("awprot"),
+
+ "output logic " + self.signal("wready"),
+ "input wire " + self.signal("wvalid"),
+ f"input wire [{self.data_width-1}:0] " + self.signal("wdata"),
+ f"input wire [{self.data_width_bytes-1}:0]" + self.signal("wstrb"),
+
+ "input wire " + self.signal("bready"),
+ "output logic " + self.signal("bvalid"),
+ "output logic [1:0] " + self.signal("bresp"),
+
+ "output logic " + self.signal("arready"),
+ "input wire " + self.signal("arvalid"),
+ f"input wire [{self.addr_width-1}:0] " + self.signal("araddr"),
+ "input wire [2:0] " + self.signal("arprot"),
+
+ "input wire " + self.signal("rready"),
+ "output logic " + self.signal("rvalid"),
+ f"output logic [{self.data_width-1}:0] " + self.signal("rdata"),
+ "output logic [1:0] " + self.signal("rresp"),
+ ]
+ return ",\n".join(lines)
+
+ def signal(self, name:str) -> str:
+ return "s_axil_" + name
diff --git a/src/peakrdl_regblock/cpuif/axi4lite/axi4lite_tmpl.sv b/src/peakrdl_regblock/cpuif/axi4lite/axi4lite_tmpl.sv
new file mode 100644
index 0000000..d89113e
--- /dev/null
+++ b/src/peakrdl_regblock/cpuif/axi4lite/axi4lite_tmpl.sv
@@ -0,0 +1,254 @@
+{%- if cpuif.is_interface -%}
+`ifndef SYNTHESIS
+ initial begin
+ assert_bad_addr_width: assert($bits({{cpuif.signal("araddr")}}) >= {{ds.package_name}}::{{ds.module_name.upper()}}_MIN_ADDR_WIDTH)
+ else $error("Interface address width of %0d is too small. Shall be at least %0d bits", $bits({{cpuif.signal("araddr")}}), {{ds.package_name}}::{{ds.module_name.upper()}}_MIN_ADDR_WIDTH);
+ assert_bad_data_width: assert($bits({{cpuif.signal("wdata")}}) == {{ds.package_name}}::{{ds.module_name.upper()}}_DATA_WIDTH)
+ else $error("Interface data width of %0d is incorrect. Shall be %0d bits", $bits({{cpuif.signal("wdata")}}), {{ds.package_name}}::{{ds.module_name.upper()}}_DATA_WIDTH);
+ end
+`endif
+
+{% endif -%}
+
+// Max Outstanding Transactions: {{cpuif.max_outstanding}}
+logic [{{clog2(cpuif.max_outstanding+1)-1}}:0] axil_n_in_flight;
+logic axil_prev_was_rd;
+logic axil_arvalid;
+logic [{{cpuif.addr_width-1}}:0] axil_araddr;
+logic axil_ar_accept;
+logic axil_awvalid;
+logic [{{cpuif.addr_width-1}}:0] axil_awaddr;
+logic axil_wvalid;
+logic [{{cpuif.data_width-1}}:0] axil_wdata;
+logic [{{cpuif.data_width_bytes-1}}:0] axil_wstrb;
+logic axil_aw_accept;
+logic axil_resp_acked;
+
+// Transaction request acceptance
+always_ff {{get_always_ff_event(cpuif.reset)}} begin
+ if({{get_resetsignal(cpuif.reset)}}) begin
+ axil_prev_was_rd <= '0;
+ axil_arvalid <= '0;
+ axil_araddr <= '0;
+ axil_awvalid <= '0;
+ axil_awaddr <= '0;
+ axil_wvalid <= '0;
+ axil_wdata <= '0;
+ axil_wstrb <= '0;
+ axil_n_in_flight <= '0;
+ end else begin
+ // AR* acceptance register
+ if(axil_ar_accept) begin
+ axil_prev_was_rd <= '1;
+ axil_arvalid <= '0;
+ end
+ if({{cpuif.signal("arvalid")}} && {{cpuif.signal("arready")}}) begin
+ axil_arvalid <= '1;
+ axil_araddr <= {{cpuif.signal("araddr")}};
+ end
+
+ // AW* & W* acceptance registers
+ if(axil_aw_accept) begin
+ axil_prev_was_rd <= '0;
+ axil_awvalid <= '0;
+ axil_wvalid <= '0;
+ end
+ if({{cpuif.signal("awvalid")}} && {{cpuif.signal("awready")}}) begin
+ axil_awvalid <= '1;
+ axil_awaddr <= {{cpuif.signal("awaddr")}};
+ end
+ if({{cpuif.signal("wvalid")}} && {{cpuif.signal("wready")}}) begin
+ axil_wvalid <= '1;
+ axil_wdata <= {{cpuif.signal("wdata")}};
+ axil_wstrb <= {{cpuif.signal("wstrb")}};
+ end
+
+ // Keep track of in-flight transactions
+ if((axil_ar_accept || axil_aw_accept) && !axil_resp_acked) begin
+ axil_n_in_flight <= axil_n_in_flight + 1'b1;
+ end else if(!(axil_ar_accept || axil_aw_accept) && axil_resp_acked) begin
+ axil_n_in_flight <= axil_n_in_flight - 1'b1;
+ end
+ end
+end
+
+always_comb begin
+ {{cpuif.signal("arready")}} = (!axil_arvalid || axil_ar_accept);
+ {{cpuif.signal("awready")}} = (!axil_awvalid || axil_aw_accept);
+ {{cpuif.signal("wready")}} = (!axil_wvalid || axil_aw_accept);
+end
+
+// Request dispatch
+always_comb begin
+ cpuif_wr_data = axil_wdata;
+ for(int i=0; i<{{cpuif.data_width_bytes}}; i++) begin
+ cpuif_wr_biten[i*8 +: 8] = {8{axil_wstrb[i]}};
+ end
+ cpuif_req = '0;
+ cpuif_req_is_wr = '0;
+ cpuif_addr = '0;
+ axil_ar_accept = '0;
+ axil_aw_accept = '0;
+
+ if(axil_n_in_flight < {{clog2(cpuif.max_outstanding+1)}}'d{{cpuif.max_outstanding}}) begin
+ // Can safely issue more transactions without overwhelming response buffer
+ if(axil_arvalid && !axil_prev_was_rd) begin
+ cpuif_req = '1;
+ cpuif_req_is_wr = '0;
+ {%- if cpuif.data_width_bytes == 1 %}
+ cpuif_addr = axil_araddr;
+ {%- else %}
+ cpuif_addr = {axil_araddr[{{cpuif.addr_width-1}}:{{clog2(cpuif.data_width_bytes)}}], {{clog2(cpuif.data_width_bytes)}}'b0};
+ {%- endif %}
+ if(!cpuif_req_stall_rd) axil_ar_accept = '1;
+ end else if(axil_awvalid && axil_wvalid) begin
+ cpuif_req = '1;
+ cpuif_req_is_wr = '1;
+ {%- if cpuif.data_width_bytes == 1 %}
+ cpuif_addr = axil_awaddr;
+ {%- else %}
+ cpuif_addr = {axil_awaddr[{{cpuif.addr_width-1}}:{{clog2(cpuif.data_width_bytes)}}], {{clog2(cpuif.data_width_bytes)}}'b0};
+ {%- endif %}
+ if(!cpuif_req_stall_wr) axil_aw_accept = '1;
+ end else if(axil_arvalid) begin
+ cpuif_req = '1;
+ cpuif_req_is_wr = '0;
+ {%- if cpuif.data_width_bytes == 1 %}
+ cpuif_addr = axil_araddr;
+ {%- else %}
+ cpuif_addr = {axil_araddr[{{cpuif.addr_width-1}}:{{clog2(cpuif.data_width_bytes)}}], {{clog2(cpuif.data_width_bytes)}}'b0};
+ {%- endif %}
+ if(!cpuif_req_stall_rd) axil_ar_accept = '1;
+ end
+ end
+end
+
+
+// AXI4-Lite Response Logic
+{%- if cpuif.resp_buffer_size == 1 %}
+always_ff {{get_always_ff_event(cpuif.reset)}} begin
+ if({{get_resetsignal(cpuif.reset)}}) begin
+ {{cpuif.signal("rvalid")}} <= '0;
+ {{cpuif.signal("rresp")}} <= '0;
+ {{cpuif.signal("rdata")}} <= '0;
+ {{cpuif.signal("bvalid")}} <= '0;
+ {{cpuif.signal("bresp")}} <= '0;
+ end else begin
+ if({{cpuif.signal("rvalid")}} && {{cpuif.signal("rready")}}) begin
+ {{cpuif.signal("rvalid")}} <= '0;
+ end
+
+ if({{cpuif.signal("bvalid")}} && {{cpuif.signal("bready")}}) begin
+ {{cpuif.signal("bvalid")}} <= '0;
+ end
+
+ if(cpuif_rd_ack) begin
+ {{cpuif.signal("rvalid")}} <= '1;
+ {{cpuif.signal("rdata")}} <= cpuif_rd_data;
+ if(cpuif_rd_err) {{cpuif.signal("rresp")}} <= 2'b10; // SLVERR
+ else {{cpuif.signal("rresp")}} <= 2'b00; // OKAY
+ end
+
+ if(cpuif_wr_ack) begin
+ {{cpuif.signal("bvalid")}} <= '1;
+ if(cpuif_wr_err) {{cpuif.signal("bresp")}} <= 2'b10; // SLVERR
+ else {{cpuif.signal("bresp")}} <= 2'b00; // OKAY
+ end
+ end
+end
+
+always_comb begin
+ axil_resp_acked = '0;
+ if({{cpuif.signal("rvalid")}} && {{cpuif.signal("rready")}}) axil_resp_acked = '1;
+ if({{cpuif.signal("bvalid")}} && {{cpuif.signal("bready")}}) axil_resp_acked = '1;
+end
+
+{%- else %}
+struct {
+ logic is_wr;
+ logic err;
+ logic [{{cpuif.data_width-1}}:0] rdata;
+} axil_resp_buffer[{{roundup_pow2(cpuif.resp_buffer_size)}}];
+{%- if not is_pow2(cpuif.resp_buffer_size) %}
+// axil_resp_buffer is intentionally padded to the next power of two despite
+// only requiring {{cpuif.resp_buffer_size}} entries.
+// This is to avoid quirks in some tools that cannot handle indexing into a non-power-of-2 array.
+// Unused entries are expected to be optimized away
+{% endif %}
+
+logic [{{clog2(cpuif.resp_buffer_size)}}:0] axil_resp_wptr;
+logic [{{clog2(cpuif.resp_buffer_size)}}:0] axil_resp_rptr;
+
+always_ff {{get_always_ff_event(cpuif.reset)}} begin
+ if({{get_resetsignal(cpuif.reset)}}) begin
+ for(int i=0; i<{{cpuif.resp_buffer_size}}; i++) begin
+ axil_resp_buffer[i].is_wr <= '0;
+ axil_resp_buffer[i].err <= '0;
+ axil_resp_buffer[i].rdata <= '0;
+ end
+ axil_resp_wptr <= '0;
+ axil_resp_rptr <= '0;
+ end else begin
+ // Store responses in buffer until AXI response channel accepts them
+ if(cpuif_rd_ack || cpuif_wr_ack) begin
+ if(cpuif_rd_ack) begin
+ axil_resp_buffer[axil_resp_wptr[{{clog2(cpuif.resp_buffer_size)-1}}:0]].is_wr <= '0;
+ axil_resp_buffer[axil_resp_wptr[{{clog2(cpuif.resp_buffer_size)-1}}:0]].err <= cpuif_rd_err;
+ axil_resp_buffer[axil_resp_wptr[{{clog2(cpuif.resp_buffer_size)-1}}:0]].rdata <= cpuif_rd_data;
+
+ end else if(cpuif_wr_ack) begin
+ axil_resp_buffer[axil_resp_wptr[{{clog2(cpuif.resp_buffer_size)-1}}:0]].is_wr <= '1;
+ axil_resp_buffer[axil_resp_wptr[{{clog2(cpuif.resp_buffer_size)-1}}:0]].err <= cpuif_wr_err;
+ end
+ {%- if is_pow2(cpuif.resp_buffer_size) %}
+ axil_resp_wptr <= axil_resp_wptr + 1'b1;
+ {%- else %}
+ if(axil_resp_wptr[{{clog2(cpuif.resp_buffer_size)-1}}:0] == {{cpuif.resp_buffer_size-1}}) begin
+ axil_resp_wptr[{{clog2(cpuif.resp_buffer_size)-1}}:0] <= '0;
+ axil_resp_wptr[{{clog2(cpuif.resp_buffer_size)}}] <= ~axil_resp_wptr[{{clog2(cpuif.resp_buffer_size)}}];
+ end else begin
+ axil_resp_wptr[{{clog2(cpuif.resp_buffer_size)-1}}:0] <= axil_resp_wptr[{{clog2(cpuif.resp_buffer_size)-1}}:0] + 1'b1;
+ end
+ {%- endif %}
+ end
+
+ // Advance read pointer when acknowledged
+ if(axil_resp_acked) begin
+ {%- if is_pow2(cpuif.resp_buffer_size) %}
+ axil_resp_rptr <= axil_resp_rptr + 1'b1;
+ {%- else %}
+ if(axil_resp_rptr[{{clog2(cpuif.resp_buffer_size)-1}}:0] == {{cpuif.resp_buffer_size-1}}) begin
+ axil_resp_rptr[{{clog2(cpuif.resp_buffer_size)-1}}:0] <= '0;
+ axil_resp_rptr[{{clog2(cpuif.resp_buffer_size)}}] <= ~axil_resp_rptr[{{clog2(cpuif.resp_buffer_size)}}];
+ end else begin
+ axil_resp_rptr[{{clog2(cpuif.resp_buffer_size)-1}}:0] <= axil_resp_rptr[{{clog2(cpuif.resp_buffer_size)-1}}:0] + 1'b1;
+ end
+ {%- endif %}
+ end
+ end
+end
+
+always_comb begin
+ axil_resp_acked = '0;
+ {{cpuif.signal("bvalid")}} = '0;
+ {{cpuif.signal("rvalid")}} = '0;
+ if(axil_resp_rptr != axil_resp_wptr) begin
+ if(axil_resp_buffer[axil_resp_rptr[{{clog2(cpuif.resp_buffer_size)-1}}:0]].is_wr) begin
+ {{cpuif.signal("bvalid")}} = '1;
+ if({{cpuif.signal("bready")}}) axil_resp_acked = '1;
+ end else begin
+ {{cpuif.signal("rvalid")}} = '1;
+ if({{cpuif.signal("rready")}}) axil_resp_acked = '1;
+ end
+ end
+
+ {{cpuif.signal("rdata")}} = axil_resp_buffer[axil_resp_rptr[{{clog2(cpuif.resp_buffer_size)-1}}:0]].rdata;
+ if(axil_resp_buffer[axil_resp_rptr[{{clog2(cpuif.resp_buffer_size)-1}}:0]].err) begin
+ {{cpuif.signal("bresp")}} = 2'b10;
+ {{cpuif.signal("rresp")}} = 2'b10;
+ end else begin
+ {{cpuif.signal("bresp")}} = 2'b00;
+ {{cpuif.signal("rresp")}} = 2'b00;
+ end
+end
+{%- endif %}
diff --git a/src/peakrdl_regblock/cpuif/base.py b/src/peakrdl_regblock/cpuif/base.py
new file mode 100644
index 0000000..2031a71
--- /dev/null
+++ b/src/peakrdl_regblock/cpuif/base.py
@@ -0,0 +1,76 @@
+from typing import TYPE_CHECKING, List
+import inspect
+import os
+
+import jinja2 as jj
+
+from ..utils import clog2, is_pow2, roundup_pow2
+
+if TYPE_CHECKING:
+ from ..exporter import RegblockExporter
+
+class CpuifBase:
+
+ # Path is relative to the location of the class that assigns this variable
+ template_path = ""
+
+ def __init__(self, exp:'RegblockExporter'):
+ self.exp = exp
+ self.reset = exp.ds.top_node.cpuif_reset
+
+ @property
+ def addr_width(self) -> int:
+ return self.exp.ds.addr_width
+
+ @property
+ def data_width(self) -> int:
+ return self.exp.ds.cpuif_data_width
+
+ @property
+ def data_width_bytes(self) -> int:
+ return self.data_width // 8
+
+ @property
+ def port_declaration(self) -> str:
+ raise NotImplementedError()
+
+ @property
+ def parameters(self) -> List[str]:
+ """
+ Optional list of additional parameters this CPU interface provides to
+ the module's definition
+ """
+ return []
+
+ def _get_template_path_class_dir(self) -> str:
+ """
+ Traverse up the MRO and find the first class that explicitly assigns
+ template_path. Returns the directory that contains the class definition.
+ """
+ for cls in inspect.getmro(self.__class__):
+ if "template_path" in cls.__dict__:
+ class_dir = os.path.dirname(inspect.getfile(cls))
+ return class_dir
+ raise RuntimeError
+
+
+ def get_implementation(self) -> str:
+ class_dir = self._get_template_path_class_dir()
+ loader = jj.FileSystemLoader(class_dir)
+ jj_env = jj.Environment(
+ loader=loader,
+ undefined=jj.StrictUndefined,
+ )
+
+ context = {
+ "cpuif": self,
+ "get_always_ff_event": self.exp.dereferencer.get_always_ff_event,
+ "get_resetsignal": self.exp.dereferencer.get_resetsignal,
+ "clog2": clog2,
+ "is_pow2": is_pow2,
+ "roundup_pow2": roundup_pow2,
+ "ds": self.exp.ds,
+ }
+
+ template = jj_env.get_template(self.template_path)
+ return template.render(context)
diff --git a/src/peakrdl_regblock/cpuif/passthrough/__init__.py b/src/peakrdl_regblock/cpuif/passthrough/__init__.py
new file mode 100644
index 0000000..0363860
--- /dev/null
+++ b/src/peakrdl_regblock/cpuif/passthrough/__init__.py
@@ -0,0 +1,22 @@
+from ..base import CpuifBase
+
+class PassthroughCpuif(CpuifBase):
+ template_path = "passthrough_tmpl.sv"
+
+ @property
+ def port_declaration(self) -> str:
+ lines = [
+ "input wire s_cpuif_req",
+ "input wire s_cpuif_req_is_wr",
+ f"input wire [{self.addr_width-1}:0] s_cpuif_addr",
+ f"input wire [{self.data_width-1}:0] s_cpuif_wr_data",
+ f"input wire [{self.data_width-1}:0] s_cpuif_wr_biten",
+ "output wire s_cpuif_req_stall_wr",
+ "output wire s_cpuif_req_stall_rd",
+ "output wire s_cpuif_rd_ack",
+ "output wire s_cpuif_rd_err",
+ f"output wire [{self.data_width-1}:0] s_cpuif_rd_data",
+ "output wire s_cpuif_wr_ack",
+ "output wire s_cpuif_wr_err",
+ ]
+ return ",\n".join(lines)
diff --git a/src/peakrdl_regblock/cpuif/passthrough/passthrough_tmpl.sv b/src/peakrdl_regblock/cpuif/passthrough/passthrough_tmpl.sv
new file mode 100644
index 0000000..8e5bb70
--- /dev/null
+++ b/src/peakrdl_regblock/cpuif/passthrough/passthrough_tmpl.sv
@@ -0,0 +1,12 @@
+assign cpuif_req = s_cpuif_req;
+assign cpuif_req_is_wr = s_cpuif_req_is_wr;
+assign cpuif_addr = s_cpuif_addr;
+assign cpuif_wr_data = s_cpuif_wr_data;
+assign cpuif_wr_biten = s_cpuif_wr_biten;
+assign s_cpuif_req_stall_wr = cpuif_req_stall_wr;
+assign s_cpuif_req_stall_rd = cpuif_req_stall_rd;
+assign s_cpuif_rd_ack = cpuif_rd_ack;
+assign s_cpuif_rd_err = cpuif_rd_err;
+assign s_cpuif_rd_data = cpuif_rd_data;
+assign s_cpuif_wr_ack = cpuif_wr_ack;
+assign s_cpuif_wr_err = cpuif_wr_err;
diff --git a/src/peakrdl_regblock/dereferencer.py b/src/peakrdl_regblock/dereferencer.py
new file mode 100644
index 0000000..d80e6ed
--- /dev/null
+++ b/src/peakrdl_regblock/dereferencer.py
@@ -0,0 +1,264 @@
+from typing import TYPE_CHECKING, Union, Optional
+from systemrdl.node import AddrmapNode, FieldNode, SignalNode, RegNode, AddressableNode
+from systemrdl.rdltypes import PropertyReference
+
+from .sv_int import SVInt
+
+if TYPE_CHECKING:
+ from .exporter import RegblockExporter, DesignState
+ from .hwif import Hwif
+ from .field_logic import FieldLogic
+ from .addr_decode import AddressDecode
+
+class Dereferencer:
+ """
+ This class provides an interface to convert conceptual SystemRDL references
+ into Verilog identifiers
+ """
+ def __init__(self, exp:'RegblockExporter'):
+ self.exp = exp
+
+ @property
+ def hwif(self) -> 'Hwif':
+ return self.exp.hwif
+
+ @property
+ def address_decode(self) -> 'AddressDecode':
+ return self.exp.address_decode
+
+ @property
+ def field_logic(self) -> 'FieldLogic':
+ return self.exp.field_logic
+
+ @property
+ def ds(self) -> 'DesignState':
+ return self.exp.ds
+
+ @property
+ def top_node(self) -> AddrmapNode:
+ return self.exp.ds.top_node
+
+ def get_value(self, obj: Union[int, FieldNode, SignalNode, PropertyReference], width: Optional[int] = None) -> Union[SVInt, str]:
+ """
+ Returns the Verilog string that represents the readable value associated
+ with the object.
+
+ If given a simple scalar value, then the corresponding Verilog literal is returned.
+
+ If obj references a structural systemrdl object, then the corresponding Verilog
+ expression is returned that represents its value.
+
+ The optional width argument can be provided to hint at the expression's desired bitwidth.
+ """
+ if isinstance(obj, int):
+ # Is a simple scalar value
+ return SVInt(obj, width)
+
+ if isinstance(obj, FieldNode):
+ if obj.implements_storage:
+ return self.field_logic.get_storage_identifier(obj)
+
+ if self.hwif.has_value_input(obj):
+ return self.hwif.get_input_identifier(obj, width)
+
+ # Field does not have a storage element, nor does it have a HW input
+ # must be a constant value as defined by its reset value
+ reset_value = obj.get_property('reset')
+ if reset_value is not None:
+ return self.get_value(reset_value, obj.width)
+ else:
+ # No reset value defined!
+ obj.env.msg.warning(
+ f"Field '{obj.inst_name}' is a constant but does not have a known value (missing reset). Assigning it a value of X.",
+ obj.inst.inst_src_ref
+ )
+ return "'X"
+
+ if isinstance(obj, SignalNode):
+ # Signals are always inputs from the hwif
+ return self.hwif.get_input_identifier(obj, width)
+
+ if isinstance(obj, PropertyReference):
+ if isinstance(obj.node, FieldNode):
+ return self.get_field_propref_value(obj.node, obj.name, width)
+ elif isinstance(obj.node, RegNode):
+ return self.get_reg_propref_value(obj.node, obj.name)
+ else:
+ raise RuntimeError
+
+ raise RuntimeError(f"Unhandled reference to: {obj}")
+
+
+ def get_field_propref_value(
+ self,
+ field: FieldNode,
+ prop_name: str,
+ width: Optional[int] = None,
+ ) -> Union[SVInt, str]:
+ # Value reduction properties.
+ # Wrap with the appropriate Verilog reduction operator
+ if prop_name == "anded":
+ val = self.get_value(field)
+ return f"&({val})"
+ elif prop_name == "ored":
+ val = self.get_value(field)
+ return f"|({val})"
+ elif prop_name == "xored":
+ val = self.get_value(field)
+ return f"^({val})"
+
+ # references that directly access a property value
+ if prop_name in {
+ 'decrvalue',
+ 'enable',
+ 'haltenable',
+ 'haltmask',
+ 'hwenable',
+ 'hwmask',
+ 'incrvalue',
+ 'mask',
+ 'reset',
+ 'resetsignal',
+ }:
+ return self.get_value(field.get_property(prop_name), width)
+
+ # Field Next
+ if prop_name == "next":
+ prop_value = field.get_property(prop_name)
+ if prop_value is None:
+ # unset by the user, points to the implied internal signal
+ return self.field_logic.get_field_combo_identifier(field, "next")
+ else:
+ return self.get_value(prop_value, width)
+
+ # References to another component value, or an implied input
+ if prop_name in {'hwclr', 'hwset'}:
+ prop_value = field.get_property(prop_name)
+ if prop_value is True:
+ # Points to inferred hwif input
+ return self.hwif.get_implied_prop_input_identifier(field, prop_name)
+ elif prop_value is False:
+ # This should never happen, as this is checked by the compiler's validator
+ raise RuntimeError
+ else:
+ return self.get_value(prop_value)
+
+ # References to another component value, or an implied input
+ # May have a complementary partner property
+ complementary_pairs = {
+ "we": "wel",
+ "wel": "we",
+ "swwe": "swwel",
+ "swwel": "swwe",
+ }
+ if prop_name in complementary_pairs:
+ prop_value = field.get_property(prop_name)
+ if prop_value is True:
+ # Points to inferred hwif input
+ return self.hwif.get_implied_prop_input_identifier(field, prop_name)
+ elif prop_value is False:
+ # Try complementary property
+ prop_value = field.get_property(complementary_pairs[prop_name])
+ if prop_value is True:
+ # Points to inferred hwif input
+ return f"!({self.hwif.get_implied_prop_input_identifier(field, complementary_pairs[prop_name])})"
+ elif prop_value is False:
+ # This should never happen, as this is checked by the compiler's validator
+ raise RuntimeError
+ else:
+ return f"!({self.get_value(prop_value)})"
+ else:
+ return self.get_value(prop_value, width)
+
+ if prop_name == "swacc":
+ return self.field_logic.get_swacc_identifier(field)
+ if prop_name == "swmod":
+ return self.field_logic.get_swmod_identifier(field)
+
+
+ # translate aliases
+ aliases = {
+ "saturate": "incrsaturate",
+ "threshold": "incrthreshold",
+ }
+ prop_name = aliases.get(prop_name, prop_name)
+
+ # Counter properties
+ if prop_name == 'incr':
+ return self.field_logic.get_counter_incr_strobe(field)
+ if prop_name == 'decr':
+ return self.field_logic.get_counter_decr_strobe(field)
+
+ if prop_name in {
+ 'decrsaturate',
+ 'decrthreshold',
+ 'incrsaturate',
+ 'incrthreshold',
+ 'overflow',
+ 'underflow',
+ }:
+ return self.field_logic.get_field_combo_identifier(field, prop_name)
+
+ raise RuntimeError(f"Unhandled reference to: {field}->{prop_name}")
+
+
+ def get_reg_propref_value(self, reg: RegNode, prop_name: str) -> str:
+ if prop_name in {'halt', 'intr'}:
+ return self.hwif.get_implied_prop_output_identifier(reg, prop_name)
+ raise NotImplementedError
+
+
+ def get_access_strobe(self, obj: Union[RegNode, FieldNode], reduce_substrobes: bool=True) -> str:
+ """
+ Returns the Verilog string that represents the register's access strobe
+ """
+ return self.address_decode.get_access_strobe(obj, reduce_substrobes)
+
+ def get_external_block_access_strobe(self, obj: 'AddressableNode') -> str:
+ """
+ Returns the Verilog string that represents the external block's access strobe
+ """
+ return self.address_decode.get_external_block_access_strobe(obj)
+
+ @property
+ def default_resetsignal_name(self) -> str:
+ s = "rst"
+ if self.ds.default_reset_async:
+ s = f"a{s}"
+ if self.ds.default_reset_activelow:
+ s = f"{s}_n"
+ return s
+
+
+ def get_resetsignal(self, obj: Optional[SignalNode] = None) -> str:
+ """
+ Returns a normalized active-high reset signal
+ """
+ if isinstance(obj, SignalNode):
+ s = self.get_value(obj)
+ if obj.get_property('activehigh'):
+ return str(s)
+ else:
+ return f"~{s}"
+
+ # No explicit reset signal specified. Fall back to default reset signal
+ s = self.default_resetsignal_name
+ if self.ds.default_reset_activelow:
+ s = f"~{s}"
+ return s
+
+ def get_always_ff_event(self, resetsignal: Optional[SignalNode] = None) -> str:
+ if resetsignal is None:
+ # No explicit reset signal specified. Fall back to default reset signal
+ if self.ds.default_reset_async:
+ if self.ds.default_reset_activelow:
+ return f"@(posedge clk or negedge {self.default_resetsignal_name})"
+ else:
+ return f"@(posedge clk or posedge {self.default_resetsignal_name})"
+ else:
+ return "@(posedge clk)"
+ elif resetsignal.get_property('async') and resetsignal.get_property('activehigh'):
+ return f"@(posedge clk or posedge {self.get_value(resetsignal)})"
+ elif resetsignal.get_property('async') and not resetsignal.get_property('activehigh'):
+ return f"@(posedge clk or negedge {self.get_value(resetsignal)})"
+ return "@(posedge clk)"
diff --git a/src/peakrdl_regblock/exporter.py b/src/peakrdl_regblock/exporter.py
new file mode 100644
index 0000000..b2c5f29
--- /dev/null
+++ b/src/peakrdl_regblock/exporter.py
@@ -0,0 +1,288 @@
+import os
+from typing import TYPE_CHECKING, Union, Any, Type, Optional, Set, List
+from collections import OrderedDict
+
+import jinja2 as jj
+from systemrdl.node import AddrmapNode, RootNode
+
+from .addr_decode import AddressDecode
+from .field_logic import FieldLogic
+from .dereferencer import Dereferencer
+from .readback import Readback
+from .identifier_filter import kw_filter as kwf
+from .utils import clog2
+from .scan_design import DesignScanner
+from .validate_design import DesignValidator
+from .cpuif import CpuifBase
+from .cpuif.apb4 import APB4_Cpuif
+from .hwif import Hwif
+from .write_buffering import WriteBuffering
+from .read_buffering import ReadBuffering
+from .external_acks import ExternalWriteAckGenerator, ExternalReadAckGenerator
+from .parity import ParityErrorReduceGenerator
+from .sv_int import SVInt
+
+if TYPE_CHECKING:
+ from systemrdl.node import SignalNode
+ from systemrdl.rdltypes import UserEnum
+
+class RegblockExporter:
+ hwif: Hwif
+ cpuif: CpuifBase
+ address_decode: AddressDecode
+ field_logic: FieldLogic
+ readback: Readback
+ write_buffering: WriteBuffering
+ read_buffering: ReadBuffering
+ dereferencer: Dereferencer
+ ds: 'DesignState'
+
+ def __init__(self, **kwargs: Any) -> None:
+ # Check for stray kwargs
+ if kwargs:
+ raise TypeError(f"got an unexpected keyword argument '{list(kwargs.keys())[0]}'")
+
+
+ loader = jj.ChoiceLoader([
+ jj.FileSystemLoader(os.path.dirname(__file__)),
+ jj.PrefixLoader({
+ 'base': jj.FileSystemLoader(os.path.dirname(__file__)),
+ }, delimiter=":")
+ ])
+
+ self.jj_env = jj.Environment(
+ loader=loader,
+ undefined=jj.StrictUndefined,
+ )
+
+
+ def export(self, node: Union[RootNode, AddrmapNode], output_dir:str, **kwargs: Any) -> None:
+ """
+ Parameters
+ ----------
+ node: AddrmapNode
+ Top-level SystemRDL node to export.
+ output_dir: str
+ Path to the output directory where generated SystemVerilog will be written.
+ Output includes two files: a module definition and package definition.
+ cpuif_cls: :class:`peakrdl_regblock.cpuif.CpuifBase`
+ Specify the class type that implements the CPU interface of your choice.
+ Defaults to AMBA APB4.
+ module_name: str
+ Override the SystemVerilog module name. By default, the module name
+ is the top-level node's name.
+ package_name: str
+ Override the SystemVerilog package name. By default, the package name
+ is the top-level node's name with a "_pkg" suffix.
+ reuse_hwif_typedefs: bool
+ By default, the exporter will attempt to re-use hwif struct definitions for
+ nodes that are equivalent. This allows for better modularity and type reuse.
+ Struct type names are derived using the SystemRDL component's type
+ name and declared lexical scope path.
+
+ If this is not desireable, override this parameter to ``False`` and structs
+ will be generated more naively using their hierarchical paths.
+ retime_read_fanin: bool
+ Set this to ``True`` to enable additional read path retiming.
+ For large register blocks that operate at demanding clock rates, this
+ may be necessary in order to manage large readback fan-in.
+
+ The retiming flop stage is automatically placed in the most optimal point in the
+ readback path so that logic-levels and fanin are minimized.
+
+ Enabling this option will increase read transfer latency by 1 clock cycle.
+ retime_read_response: bool
+ Set this to ``True`` to enable an additional retiming flop stage between
+ the readback mux and the CPU interface response logic.
+ This option may be beneficial for some CPU interfaces that implement the
+ response logic fully combinationally. Enabling this stage can better
+ isolate timing paths in the register file from the rest of your system.
+
+ Enabling this when using CPU interfaces that already implement the
+ response path sequentially may not result in any meaningful timing improvement.
+
+ Enabling this option will increase read transfer latency by 1 clock cycle.
+ retime_external_reg: bool
+ Retime outputs to external ``reg`` components.
+ retime_external_regfile: bool
+ Retime outputs to external ``regfile`` components.
+ retime_external_mem: bool
+ Retime outputs to external ``mem`` components.
+ retime_external_addrmap: bool
+ Retime outputs to external ``addrmap`` components.
+ generate_hwif_report: bool
+ If set, generates a hwif report that can help designers understand
+ the contents of the ``hwif_in`` and ``hwif_out`` structures.
+ address_width: int
+ Override the CPU interface's address width. By default, address width
+ is sized to the contents of the regblock.
+ default_reset_activelow: bool
+ If overriden to True, default reset is active-low instead of active-high.
+ default_reset_async: bool
+ If overriden to True, default reset is asynchronous instead of synchronous.
+ """
+ # If it is the root node, skip to top addrmap
+ if isinstance(node, RootNode):
+ top_node = node.top
+ else:
+ top_node = node
+
+ self.ds = DesignState(top_node, kwargs)
+
+ cpuif_cls = kwargs.pop("cpuif_cls", None) or APB4_Cpuif # type: Type[CpuifBase]
+ generate_hwif_report = kwargs.pop("generate_hwif_report", False) # type: bool
+
+ # Check for stray kwargs
+ if kwargs:
+ raise TypeError(f"got an unexpected keyword argument '{list(kwargs.keys())[0]}'")
+
+ if generate_hwif_report:
+ path = os.path.join(output_dir, f"{self.ds.module_name}_hwif.rpt")
+ hwif_report_file = open(path, "w", encoding='utf-8') # pylint: disable=consider-using-with
+ else:
+ hwif_report_file = None
+
+ # Construct exporter components
+ self.cpuif = cpuif_cls(self)
+ self.hwif = Hwif(self, hwif_report_file=hwif_report_file)
+ self.readback = Readback(self)
+ self.address_decode = AddressDecode(self)
+ self.field_logic = FieldLogic(self)
+ self.write_buffering = WriteBuffering(self)
+ self.read_buffering = ReadBuffering(self)
+ self.dereferencer = Dereferencer(self)
+ ext_write_acks = ExternalWriteAckGenerator(self)
+ ext_read_acks = ExternalReadAckGenerator(self)
+ parity = ParityErrorReduceGenerator(self)
+
+ # Validate that there are no unsupported constructs
+ DesignValidator(self).do_validate()
+
+ # Compute readback implementation early.
+ # Readback has the capability to disable retiming if the fanin is tiny.
+ # This affects the rest of the design's implementation, and must be known
+ # before any other templates are rendered
+ readback_implementation = self.readback.get_implementation()
+
+ # Build Jinja template context
+ context = {
+ "cpuif": self.cpuif,
+ "hwif": self.hwif,
+ "write_buffering": self.write_buffering,
+ "read_buffering": self.read_buffering,
+ "get_resetsignal": self.dereferencer.get_resetsignal,
+ "default_resetsignal_name": self.dereferencer.default_resetsignal_name,
+ "address_decode": self.address_decode,
+ "field_logic": self.field_logic,
+ "readback_implementation": readback_implementation,
+ "ext_write_acks": ext_write_acks,
+ "ext_read_acks": ext_read_acks,
+ "parity": parity,
+ "get_always_ff_event": self.dereferencer.get_always_ff_event,
+ "ds": self.ds,
+ "kwf": kwf,
+ "SVInt" : SVInt,
+ }
+
+ # Write out design
+ os.makedirs(output_dir, exist_ok=True)
+ package_file_path = os.path.join(output_dir, self.ds.package_name + ".sv")
+ template = self.jj_env.get_template("package_tmpl.sv")
+ stream = template.stream(context)
+ stream.dump(package_file_path)
+
+ module_file_path = os.path.join(output_dir, self.ds.module_name + ".sv")
+ template = self.jj_env.get_template("module_tmpl.sv")
+ stream = template.stream(context)
+ stream.dump(module_file_path)
+
+ if hwif_report_file:
+ hwif_report_file.close()
+
+
+class DesignState:
+ """
+ Dumping ground for all sorts of variables that are relevant to a particular
+ design.
+ """
+
+ def __init__(self, top_node: AddrmapNode, kwargs: Any) -> None:
+ self.top_node = top_node
+ msg = top_node.env.msg
+
+ #------------------------
+ # Extract compiler args
+ #------------------------
+ self.reuse_hwif_typedefs = kwargs.pop("reuse_hwif_typedefs", True) # type: bool
+ self.module_name = kwargs.pop("module_name", None) or kwf(self.top_node.inst_name) # type: str
+ self.package_name = kwargs.pop("package_name", None) or (self.module_name + "_pkg") # type: str
+ user_addr_width = kwargs.pop("address_width", None) # type: Optional[int]
+
+ # Pipelining options
+ self.retime_read_fanin = kwargs.pop("retime_read_fanin", False) # type: bool
+ self.retime_read_response = kwargs.pop("retime_read_response", False) # type: bool
+ self.retime_external_reg = kwargs.pop("retime_external_reg", False) # type: bool
+ self.retime_external_regfile = kwargs.pop("retime_external_regfile", False) # type: bool
+ self.retime_external_mem = kwargs.pop("retime_external_mem", False) # type: bool
+ self.retime_external_addrmap = kwargs.pop("retime_external_addrmap", False) # type: bool
+
+ # Default reset type
+ self.default_reset_activelow = kwargs.pop("default_reset_activelow", False) # type: bool
+ self.default_reset_async = kwargs.pop("default_reset_async", False) # type: bool
+
+ #------------------------
+ # Info about the design
+ #------------------------
+ self.cpuif_data_width = 0
+
+ # Collections of signals that were actually referenced by the design
+ self.in_hier_signal_paths = set() # type: Set[str]
+ self.out_of_hier_signals = OrderedDict() # type: OrderedDict[str, SignalNode]
+
+ self.has_writable_msb0_fields = False
+ self.has_buffered_write_regs = False
+ self.has_buffered_read_regs = False
+
+ self.has_external_block = False
+ self.has_external_addressable = False
+
+ self.has_paritycheck = False
+
+ # Track any referenced enums
+ self.user_enums = [] # type: List[Type[UserEnum]]
+
+ # Scan the design to fill in above variables
+ DesignScanner(self).do_scan()
+
+ if self.cpuif_data_width == 0:
+ # Scanner did not find any registers in the design being exported,
+ # so the width is not known.
+ # Assume 32-bits
+ msg.warning(
+ "Addrmap being exported only contains external components. Unable to infer the CPUIF bus width. Assuming 32-bits.",
+ self.top_node.inst.def_src_ref
+ )
+ self.cpuif_data_width = 32
+
+ #------------------------
+ # Min address width encloses the total size AND at least 1 useful address bit
+ self.addr_width = max(clog2(self.top_node.size), clog2(self.cpuif_data_width//8) + 1)
+
+ if user_addr_width is not None:
+ if user_addr_width < self.addr_width:
+ msg.fatal(f"User-specified address width shall be greater than or equal to {self.addr_width}.")
+ self.addr_width = user_addr_width
+
+ @property
+ def min_read_latency(self) -> int:
+ n = 0
+ if self.retime_read_fanin:
+ n += 1
+ if self.retime_read_response:
+ n += 1
+ return n
+
+ @property
+ def min_write_latency(self) -> int:
+ n = 0
+ return n
diff --git a/src/peakrdl_regblock/external_acks.py b/src/peakrdl_regblock/external_acks.py
new file mode 100644
index 0000000..9a6c044
--- /dev/null
+++ b/src/peakrdl_regblock/external_acks.py
@@ -0,0 +1,54 @@
+from typing import TYPE_CHECKING
+
+from systemrdl.walker import WalkerAction
+from systemrdl.node import RegNode
+
+from .forloop_generator import RDLForLoopGenerator
+
+if TYPE_CHECKING:
+ from .exporter import RegblockExporter
+ from systemrdl.node import AddressableNode
+
+
+class ExternalWriteAckGenerator(RDLForLoopGenerator):
+ def __init__(self, exp: 'RegblockExporter') -> None:
+ super().__init__()
+ self.exp = exp
+
+ def get_implementation(self) -> str:
+ content = self.get_content(self.exp.ds.top_node)
+ if content is None:
+ return ""
+ return content
+
+ def enter_AddressableComponent(self, node: 'AddressableNode') -> WalkerAction:
+ super().enter_AddressableComponent(node)
+
+ if node.external:
+ if not isinstance(node, RegNode) or node.has_sw_writable:
+ self.add_content(f"wr_ack |= {self.exp.hwif.get_external_wr_ack(node)};")
+ return WalkerAction.SkipDescendants
+
+ return WalkerAction.Continue
+
+
+class ExternalReadAckGenerator(RDLForLoopGenerator):
+ def __init__(self, exp: 'RegblockExporter') -> None:
+ super().__init__()
+ self.exp = exp
+
+ def get_implementation(self) -> str:
+ content = self.get_content(self.exp.ds.top_node)
+ if content is None:
+ return ""
+ return content
+
+ def enter_AddressableComponent(self, node: 'AddressableNode') -> WalkerAction:
+ super().enter_AddressableComponent(node)
+
+ if node.external:
+ if not isinstance(node, RegNode) or node.has_sw_readable:
+ self.add_content(f"rd_ack |= {self.exp.hwif.get_external_rd_ack(node)};")
+ return WalkerAction.SkipDescendants
+
+ return WalkerAction.Continue
diff --git a/src/peakrdl_regblock/field_logic/__init__.py b/src/peakrdl_regblock/field_logic/__init__.py
new file mode 100644
index 0000000..b44c7f6
--- /dev/null
+++ b/src/peakrdl_regblock/field_logic/__init__.py
@@ -0,0 +1,501 @@
+from typing import TYPE_CHECKING, Union
+
+from systemrdl.rdltypes import PrecedenceType, InterruptType
+
+from .bases import AssignmentPrecedence, NextStateConditional
+from . import sw_onread
+from . import sw_onwrite
+from . import sw_singlepulse
+from . import hw_write
+from . import hw_set_clr
+from . import hw_interrupts
+from . import hw_interrupts_with_write
+
+from ..utils import get_indexed_path
+from ..sv_int import SVInt
+
+from .generators import CombinationalStructGenerator, FieldStorageStructGenerator, FieldLogicGenerator
+
+if TYPE_CHECKING:
+ from typing import Dict, List
+ from systemrdl.node import AddrmapNode, FieldNode
+ from ..exporter import RegblockExporter, DesignState
+
+class FieldLogic:
+ def __init__(self, exp:'RegblockExporter'):
+ self.exp = exp
+
+ self._hw_conditionals = {} # type: Dict[int, List[NextStateConditional]]
+ self._sw_conditionals = {} # type: Dict[int, List[NextStateConditional]]
+
+ self.init_conditionals()
+
+ @property
+ def ds(self) -> 'DesignState':
+ return self.exp.ds
+
+ @property
+ def top_node(self) -> 'AddrmapNode':
+ return self.exp.ds.top_node
+
+ def get_storage_struct(self) -> str:
+ struct_gen = FieldStorageStructGenerator(self)
+ s = struct_gen.get_struct(self.top_node, "field_storage_t")
+
+ # Only declare the storage struct if it exists
+ if s is None:
+ return ""
+
+ return s + "\nfield_storage_t field_storage;"
+
+ def get_combo_struct(self) -> str:
+ struct_gen = CombinationalStructGenerator(self)
+ s = struct_gen.get_struct(self.top_node, "field_combo_t")
+
+ # Only declare the storage struct if it exists
+ if s is None:
+ return ""
+
+ return s + "\nfield_combo_t field_combo;"
+
+ def get_implementation(self) -> str:
+ gen = FieldLogicGenerator(self)
+ s = gen.get_content(self.top_node)
+ if s is None:
+ return ""
+ return s
+
+ #---------------------------------------------------------------------------
+ # Field utility functions
+ #---------------------------------------------------------------------------
+ def get_storage_identifier(self, field: 'FieldNode') -> str:
+ """
+ Returns the Verilog string that represents the storage register element
+ for the referenced field
+ """
+ assert field.implements_storage
+ path = get_indexed_path(self.top_node, field)
+ return f"field_storage.{path}.value"
+
+ def get_next_q_identifier(self, field: 'FieldNode') -> str:
+ """
+ Returns the Verilog string that represents the storage register element
+ for the delayed 'next' input value
+ """
+ assert field.implements_storage
+ path = get_indexed_path(self.top_node, field)
+ return f"field_storage.{path}.next_q"
+
+ def get_field_combo_identifier(self, field: 'FieldNode', name: str) -> str:
+ """
+ Returns a Verilog string that represents a field's internal combinational
+ signal.
+ """
+ assert field.implements_storage
+ path = get_indexed_path(self.top_node, field)
+ return f"field_combo.{path}.{name}"
+
+ def get_counter_incr_strobe(self, field: 'FieldNode') -> str:
+ """
+ Return the Verilog string that represents the field's incr strobe signal.
+ """
+ prop_value = field.get_property('incr')
+ if prop_value:
+ return str(self.exp.dereferencer.get_value(prop_value))
+
+ # unset by the user, points to the implied input signal
+ return self.exp.hwif.get_implied_prop_input_identifier(field, "incr")
+
+ def get_counter_incrvalue(self, field: 'FieldNode') -> Union[SVInt, str]:
+ """
+ Return the string that represents the field's increment value
+ """
+ incrvalue = field.get_property('incrvalue')
+ if incrvalue is not None:
+ return self.exp.dereferencer.get_value(incrvalue, field.width)
+ if field.get_property('incrwidth'):
+ return self.exp.hwif.get_implied_prop_input_identifier(field, "incrvalue")
+ return "1'b1"
+
+ def get_counter_incrsaturate_value(self, field: 'FieldNode') -> Union[SVInt, str]:
+ prop_value = field.get_property('incrsaturate')
+ if prop_value is True:
+ return self.exp.dereferencer.get_value(2**field.width - 1, field.width)
+ return self.exp.dereferencer.get_value(prop_value, field.width)
+
+ def counter_incrsaturates(self, field: 'FieldNode') -> bool:
+ """
+ Returns True if the counter saturates
+ """
+ return field.get_property('incrsaturate') is not False
+
+ def get_counter_incrthreshold_value(self, field: 'FieldNode') -> Union[SVInt, str]:
+ prop_value = field.get_property('incrthreshold')
+ if isinstance(prop_value, bool):
+ # No explicit value set. use max
+ return self.exp.dereferencer.get_value(2**field.width - 1, field.width)
+ return self.exp.dereferencer.get_value(prop_value, field.width)
+
+ def get_counter_decr_strobe(self, field: 'FieldNode') -> str:
+ """
+ Return the Verilog string that represents the field's incr strobe signal.
+ """
+ prop_value = field.get_property('decr')
+ if prop_value:
+ return str(self.exp.dereferencer.get_value(prop_value))
+
+ # unset by the user, points to the implied input signal
+ return self.exp.hwif.get_implied_prop_input_identifier(field, "decr")
+
+ def get_counter_decrvalue(self, field: 'FieldNode') -> Union[SVInt, str]:
+ """
+ Return the string that represents the field's decrement value
+ """
+ decrvalue = field.get_property('decrvalue')
+ if decrvalue is not None:
+ return self.exp.dereferencer.get_value(decrvalue, field.width)
+ if field.get_property('decrwidth'):
+ return self.exp.hwif.get_implied_prop_input_identifier(field, "decrvalue")
+ return "1'b1"
+
+ def get_counter_decrsaturate_value(self, field: 'FieldNode') -> Union[SVInt, str]:
+ prop_value = field.get_property('decrsaturate')
+ if prop_value is True:
+ return f"{field.width}'d0"
+ return self.exp.dereferencer.get_value(prop_value, field.width)
+
+ def counter_decrsaturates(self, field: 'FieldNode') -> bool:
+ """
+ Returns True if the counter saturates
+ """
+ return field.get_property('decrsaturate') is not False
+
+ def get_counter_decrthreshold_value(self, field: 'FieldNode') -> Union[SVInt, str]:
+ prop_value = field.get_property('decrthreshold')
+ if isinstance(prop_value, bool):
+ # No explicit value set. use min
+ return f"{field.width}'d0"
+ return self.exp.dereferencer.get_value(prop_value, field.width)
+
+ def get_swacc_identifier(self, field: 'FieldNode') -> str:
+ """
+ Asserted when field is software accessed (read or write)
+ """
+ buffer_reads = field.parent.get_property('buffer_reads')
+ buffer_writes = field.parent.get_property('buffer_writes')
+ if buffer_reads and buffer_writes:
+ rstrb = self.exp.read_buffering.get_trigger(field.parent)
+ wstrb = self.exp.write_buffering.get_write_strobe(field)
+ return f"{rstrb} || {wstrb}"
+ elif buffer_reads and not buffer_writes:
+ strb = self.exp.dereferencer.get_access_strobe(field)
+ rstrb = self.exp.read_buffering.get_trigger(field.parent)
+ return f"{rstrb} || ({strb} && decoded_req_is_wr)"
+ elif not buffer_reads and buffer_writes:
+ strb = self.exp.dereferencer.get_access_strobe(field)
+ wstrb = self.exp.write_buffering.get_write_strobe(field)
+ return f"{wstrb} || ({strb} && !decoded_req_is_wr)"
+ else:
+ strb = self.exp.dereferencer.get_access_strobe(field)
+ return strb
+
+ def get_rd_swacc_identifier(self, field: 'FieldNode') -> str:
+ """
+ Asserted when field is software accessed (read)
+ """
+ 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_wr_swacc_identifier(self, field: 'FieldNode') -> str:
+ """
+ Asserted when field is software accessed (write)
+ """
+ buffer_writes = field.parent.get_property('buffer_writes')
+ if buffer_writes:
+ wstrb = self.exp.write_buffering.get_write_strobe(field)
+ return wstrb
+ else:
+ strb = self.exp.dereferencer.get_access_strobe(field)
+ return f"{strb} && decoded_req_is_wr"
+
+ def get_swmod_identifier(self, field: 'FieldNode') -> str:
+ """
+ Asserted when field is modified by software (written or read with a
+ set or clear side effect).
+ """
+ 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')
+ accesswidth = field.parent.get_property("accesswidth")
+
+
+ astrb = self.exp.dereferencer.get_access_strobe(field)
+
+ conditions = []
+ if r_modifiable:
+ if buffer_reads:
+ rstrb = self.exp.read_buffering.get_trigger(field.parent)
+ else:
+ rstrb = f"{astrb} && !decoded_req_is_wr"
+ conditions.append(rstrb)
+
+ if w_modifiable:
+ if buffer_writes:
+ wstrb = self.exp.write_buffering.get_write_strobe(field)
+ else:
+ wstrb = f"{astrb} && decoded_req_is_wr"
+
+ # Due to 10.6.1-f, it is impossible for a field that is sw-writable to
+ # be split across subwords.
+ # Therefore it is ok to get the subword idx from only one of the bit offsets
+ # in order to compute the biten range
+ sidx = field.low // accesswidth
+ biten = self.get_wr_biten(field, sidx)
+ wstrb += f" && |({biten})"
+
+ conditions.append(wstrb)
+
+ if not conditions:
+ # Not sw modifiable
+ return "1'b0"
+ else:
+ return " || ".join(conditions)
+
+
+ def get_parity_identifier(self, field: 'FieldNode') -> str:
+ """
+ Returns the identifier for the stored 'golden' parity value of the field
+ """
+ path = get_indexed_path(self.top_node, field)
+ return f"field_storage.{path}.parity"
+
+ def get_parity_error_identifier(self, field: 'FieldNode') -> str:
+ """
+ Returns the identifier for whether the field currently has a parity error
+ """
+ path = get_indexed_path(self.top_node, field)
+ return f"field_combo.{path}.parity_error"
+
+ def has_next_q(self, field: 'FieldNode') -> bool:
+ """
+ Some fields require a delayed version of their 'next' input signal in
+ order to do edge-detection.
+
+ Returns True if this is the case.
+ """
+ if field.get_property('intr type') in {
+ InterruptType.posedge,
+ InterruptType.negedge,
+ InterruptType.bothedge
+ }:
+ return True
+
+ return False
+
+ def get_wbus_bitslice(self, field: 'FieldNode', subword_idx: int = 0) -> str:
+ """
+ Get the bitslice range string of the internal cpuif's data/biten bus
+ that corresponds to this field
+ """
+ if field.parent.get_property('buffer_writes'):
+ # register is buffered.
+ # write buffer is the full width of the register. no need to deal with subwords
+ high = field.high
+ low = field.low
+ if field.msb < field.lsb:
+ # slice is for an msb0 field.
+ # mirror it
+ regwidth = field.parent.get_property('regwidth')
+ low = regwidth - 1 - low
+ high = regwidth - 1 - high
+ low, high = high, low
+ else:
+ # Regular non-buffered register
+ # For normal fields this ends up passing-through the field's low/high
+ # values unchanged.
+ # For fields within a wide register (accesswidth < regwidth), low/high
+ # may be shifted down and clamped depending on which sub-word is being accessed
+ accesswidth = field.parent.get_property('accesswidth')
+
+ # Shift based on subword
+ high = field.high - (subword_idx * accesswidth)
+ low = field.low - (subword_idx * accesswidth)
+
+ # clamp to accesswidth
+ high = max(min(high, accesswidth), 0)
+ low = max(min(low, accesswidth), 0)
+
+ if field.msb < field.lsb:
+ # slice is for an msb0 field.
+ # mirror it
+ bus_width = self.exp.cpuif.data_width
+ low = bus_width - 1 - low
+ high = bus_width - 1 - high
+ low, high = high, low
+
+ return f"[{high}:{low}]"
+
+ def get_wr_biten(self, field: 'FieldNode', subword_idx: int=0) -> str:
+ """
+ Get the bit-enable slice that corresponds to this field
+ """
+ if field.parent.get_property('buffer_writes'):
+ # Is buffered. Use value from write buffer
+ # No need to check msb0 ordering. Bus is pre-swapped, and bitslice
+ # accounts for it
+ bslice = self.get_wbus_bitslice(field)
+ wbuf_prefix = self.exp.write_buffering.get_wbuf_prefix(field)
+ return wbuf_prefix + ".biten" + bslice
+ else:
+ # Regular non-buffered register
+ bslice = self.get_wbus_bitslice(field, subword_idx)
+
+ if field.msb < field.lsb:
+ # Field gets bitswapped since it is in [low:high] orientation
+ value = "decoded_wr_biten_bswap" + bslice
+ else:
+ value = "decoded_wr_biten" + bslice
+ return value
+
+ def get_wr_data(self, field: 'FieldNode', subword_idx: int=0) -> str:
+ """
+ Get the write data slice that corresponds to this field
+ """
+ if field.parent.get_property('buffer_writes'):
+ # Is buffered. Use value from write buffer
+ # No need to check msb0 ordering. Bus is pre-swapped, and bitslice
+ # accounts for it
+ bslice = self.get_wbus_bitslice(field)
+ wbuf_prefix = self.exp.write_buffering.get_wbuf_prefix(field)
+ return wbuf_prefix + ".data" + bslice
+ else:
+ # Regular non-buffered register
+ bslice = self.get_wbus_bitslice(field, subword_idx)
+
+ if field.msb < field.lsb:
+ # Field gets bitswapped since it is in [low:high] orientation
+ value = "decoded_wr_data_bswap" + bslice
+ else:
+ value = "decoded_wr_data" + bslice
+ return value
+
+ #---------------------------------------------------------------------------
+ # Field Logic Conditionals
+ #---------------------------------------------------------------------------
+ def add_hw_conditional(self, conditional: NextStateConditional, precedence: AssignmentPrecedence) -> None:
+ """
+ Register a NextStateConditional action for hardware-triggered field updates.
+ Categorizing conditionals correctly by hw/sw ensures that the RDL precedence
+ property can be reliably honored.
+
+ The ``precedence`` argument determines the conditional assignment's priority over
+ other assignments of differing precedence.
+
+ If multiple conditionals of the same precedence are registered, they are
+ searched sequentially and only the first to match the given field is used.
+ """
+ if precedence not in self._hw_conditionals:
+ self._hw_conditionals[precedence] = []
+ self._hw_conditionals[precedence].append(conditional)
+
+
+ def add_sw_conditional(self, conditional: NextStateConditional, precedence: AssignmentPrecedence) -> None:
+ """
+ Register a NextStateConditional action for software-triggered field updates.
+ Categorizing conditionals correctly by hw/sw ensures that the RDL precedence
+ property can be reliably honored.
+
+ The ``precedence`` argument determines the conditional assignment's priority over
+ other assignments of differing precedence.
+
+ If multiple conditionals of the same precedence are registered, they are
+ searched sequentially and only the first to match the given field is used.
+ """
+ if precedence not in self._sw_conditionals:
+ self._sw_conditionals[precedence] = []
+ self._sw_conditionals[precedence].append(conditional)
+
+
+ def init_conditionals(self) -> None:
+ """
+ Initialize all possible conditionals here.
+
+ Remember: The order in which conditionals are added matters within the
+ same assignment precedence.
+ """
+
+ self.add_sw_conditional(sw_onread.ClearOnRead(self.exp), AssignmentPrecedence.SW_ONREAD)
+ self.add_sw_conditional(sw_onread.SetOnRead(self.exp), AssignmentPrecedence.SW_ONREAD)
+
+ self.add_sw_conditional(sw_onwrite.Write(self.exp), AssignmentPrecedence.SW_ONWRITE)
+ self.add_sw_conditional(sw_onwrite.WriteSet(self.exp), AssignmentPrecedence.SW_ONWRITE)
+ self.add_sw_conditional(sw_onwrite.WriteClear(self.exp), AssignmentPrecedence.SW_ONWRITE)
+ self.add_sw_conditional(sw_onwrite.WriteZeroToggle(self.exp), AssignmentPrecedence.SW_ONWRITE)
+ self.add_sw_conditional(sw_onwrite.WriteZeroClear(self.exp), AssignmentPrecedence.SW_ONWRITE)
+ self.add_sw_conditional(sw_onwrite.WriteZeroSet(self.exp), AssignmentPrecedence.SW_ONWRITE)
+ self.add_sw_conditional(sw_onwrite.WriteOneToggle(self.exp), AssignmentPrecedence.SW_ONWRITE)
+ self.add_sw_conditional(sw_onwrite.WriteOneClear(self.exp), AssignmentPrecedence.SW_ONWRITE)
+ self.add_sw_conditional(sw_onwrite.WriteOneSet(self.exp), AssignmentPrecedence.SW_ONWRITE)
+
+ self.add_sw_conditional(sw_singlepulse.Singlepulse(self.exp), AssignmentPrecedence.SW_SINGLEPULSE)
+
+ self.add_hw_conditional(hw_interrupts_with_write.PosedgeStickybitWE(self.exp), AssignmentPrecedence.HW_WRITE)
+ self.add_hw_conditional(hw_interrupts_with_write.PosedgeStickybitWEL(self.exp), AssignmentPrecedence.HW_WRITE)
+ self.add_hw_conditional(hw_interrupts_with_write.NegedgeStickybitWE(self.exp), AssignmentPrecedence.HW_WRITE)
+ self.add_hw_conditional(hw_interrupts_with_write.NegedgeStickybitWEL(self.exp), AssignmentPrecedence.HW_WRITE)
+ self.add_hw_conditional(hw_interrupts_with_write.BothedgeStickybitWE(self.exp), AssignmentPrecedence.HW_WRITE)
+ self.add_hw_conditional(hw_interrupts_with_write.BothedgeStickybitWEL(self.exp), AssignmentPrecedence.HW_WRITE)
+ self.add_hw_conditional(hw_interrupts_with_write.StickyWE(self.exp), AssignmentPrecedence.HW_WRITE)
+ self.add_hw_conditional(hw_interrupts_with_write.StickyWEL(self.exp), AssignmentPrecedence.HW_WRITE)
+ self.add_hw_conditional(hw_interrupts_with_write.StickybitWE(self.exp), AssignmentPrecedence.HW_WRITE)
+ self.add_hw_conditional(hw_interrupts_with_write.StickybitWEL(self.exp), AssignmentPrecedence.HW_WRITE)
+ self.add_hw_conditional(hw_interrupts.PosedgeStickybit(self.exp), AssignmentPrecedence.HW_WRITE)
+ self.add_hw_conditional(hw_interrupts.NegedgeStickybit(self.exp), AssignmentPrecedence.HW_WRITE)
+ self.add_hw_conditional(hw_interrupts.BothedgeStickybit(self.exp), AssignmentPrecedence.HW_WRITE)
+ self.add_hw_conditional(hw_interrupts.Sticky(self.exp), AssignmentPrecedence.HW_WRITE)
+ self.add_hw_conditional(hw_interrupts.Stickybit(self.exp), AssignmentPrecedence.HW_WRITE)
+ self.add_hw_conditional(hw_write.WEWrite(self.exp), AssignmentPrecedence.HW_WRITE)
+ self.add_hw_conditional(hw_write.WELWrite(self.exp), AssignmentPrecedence.HW_WRITE)
+ self.add_hw_conditional(hw_write.AlwaysWrite(self.exp), AssignmentPrecedence.HW_WRITE)
+
+ self.add_hw_conditional(hw_set_clr.HWClear(self.exp), AssignmentPrecedence.HWCLR)
+
+ self.add_hw_conditional(hw_set_clr.HWSet(self.exp), AssignmentPrecedence.HWSET)
+
+
+ def _get_X_conditionals(self, conditionals: 'Dict[int, List[NextStateConditional]]', field: 'FieldNode') -> 'List[NextStateConditional]':
+ result = []
+ precedences = sorted(conditionals.keys(), reverse=True)
+ for precedence in precedences:
+ for conditional in conditionals[precedence]:
+ if conditional.is_match(field):
+ result.append(conditional)
+ break
+ return result
+
+
+ def get_conditionals(self, field: 'FieldNode') -> 'List[NextStateConditional]':
+ """
+ Get a list of NextStateConditional objects that apply to the given field.
+
+ The returned list is sorted in priority order - the conditional with highest
+ precedence is first in the list.
+ """
+ sw_precedence = field.get_property('precedence') == PrecedenceType.sw
+ result = []
+
+ if sw_precedence:
+ result.extend(self._get_X_conditionals(self._sw_conditionals, field))
+
+ result.extend(self._get_X_conditionals(self._hw_conditionals, field))
+
+ if not sw_precedence:
+ result.extend(self._get_X_conditionals(self._sw_conditionals, field))
+
+ return result
diff --git a/src/peakrdl_regblock/field_logic/bases.py b/src/peakrdl_regblock/field_logic/bases.py
new file mode 100644
index 0000000..2a13676
--- /dev/null
+++ b/src/peakrdl_regblock/field_logic/bases.py
@@ -0,0 +1,114 @@
+from typing import TYPE_CHECKING, List
+import enum
+
+from ..utils import get_indexed_path
+
+if TYPE_CHECKING:
+ from systemrdl.node import FieldNode
+
+ from ..exporter import RegblockExporter
+
+class AssignmentPrecedence(enum.IntEnum):
+ """
+ Enumeration of standard assignment precedence groups.
+ Each value represents the precedence of a single conditional assignment
+ category that determines a field's next state.
+
+ Higher value denotes higher precedence
+
+ Important: If inserting custom intermediate assignment rules, do not rely on the absolute
+ value of the enumeration. Insert your rules relative to an existing precedence:
+ FieldBuilder.add_hw_conditional(MyConditional, HW_WE + 1)
+ """
+
+ # Software access assignment groups
+ SW_ONREAD = 5000
+ SW_ONWRITE = 4000
+ SW_SINGLEPULSE = 3000
+
+ # Hardware access assignment groups
+ HW_WRITE = 3000
+ HWSET = 2000
+ HWCLR = 1000
+ COUNTER_INCR_DECR = 0
+
+
+
+
+class SVLogic:
+ """
+ Represents a SystemVerilog logic signal
+ """
+ def __init__(self, name: str, width: int, default_assignment: str) -> None:
+ self.name = name
+ self.width = width
+ self.default_assignment = default_assignment
+
+ def __eq__(self, o: object) -> bool:
+ if not isinstance(o, SVLogic):
+ return False
+
+ return (
+ o.name == self.name
+ and o.width == self.width
+ and o.default_assignment == self.default_assignment
+ )
+
+
+class NextStateConditional:
+ """
+ Describes a single conditional action that determines the next state of a field
+ Provides information to generate the following content:
+ if() begin
+
+ end
+ """
+
+ # Optional comment to emit next to the conditional
+ comment = ""
+
+ def __init__(self, exp:'RegblockExporter'):
+ self.exp = exp
+
+ def is_match(self, field: 'FieldNode') -> bool:
+ """
+ Returns True if this conditional is relevant to the field. If so,
+ it instructs the FieldBuilder that Verilog for this conditional shall
+ be emitted
+ """
+ raise NotImplementedError
+
+ def get_field_path(self, field:'FieldNode') -> str:
+ return get_indexed_path(self.exp.ds.top_node, field)
+
+ def get_predicate(self, field: 'FieldNode') -> str:
+ """
+ Returns the rendered conditional text
+ """
+ raise NotImplementedError
+
+
+ def get_assignments(self, field: 'FieldNode') -> List[str]:
+ """
+ Returns a list of rendered assignment strings
+ This will basically always be two:
+ .next =
+ .load_next = '1;
+ """
+ raise NotImplementedError
+
+ def get_extra_combo_signals(self, field: 'FieldNode') -> List[SVLogic]:
+ """
+ Return any additional combinational signals that this conditional
+ will assign if present.
+ """
+ return []
+
+class NextStateUnconditional(NextStateConditional):
+ """
+ Use this class if predicate can never evaluate to false.
+ This will be generated as an 'else' clause, or a direct assignment
+ """
+
+ # Explanation text for use in error message about conflicts
+ unconditional_explanation = ""
diff --git a/src/peakrdl_regblock/field_logic/generators.py b/src/peakrdl_regblock/field_logic/generators.py
new file mode 100644
index 0000000..04e474f
--- /dev/null
+++ b/src/peakrdl_regblock/field_logic/generators.py
@@ -0,0 +1,393 @@
+from typing import TYPE_CHECKING, List, Optional
+
+from collections import OrderedDict
+
+from systemrdl.walker import WalkerAction
+from systemrdl.node import RegNode, RegfileNode, MemNode, AddrmapNode
+
+from ..struct_generator import RDLStructGenerator
+from ..forloop_generator import RDLForLoopGenerator
+from ..utils import get_indexed_path, clog2
+from ..identifier_filter import kw_filter as kwf
+from .bases import NextStateUnconditional
+
+if TYPE_CHECKING:
+ from . import FieldLogic
+ from systemrdl.node import FieldNode, AddressableNode
+ from .bases import SVLogic
+
+class CombinationalStructGenerator(RDLStructGenerator):
+
+ def __init__(self, field_logic: 'FieldLogic'):
+ super().__init__()
+ self.field_logic = field_logic
+
+ def enter_AddressableComponent(self, node: 'AddressableNode') -> Optional[WalkerAction]:
+ super().enter_AddressableComponent(node)
+
+ if node.external:
+ return WalkerAction.SkipDescendants
+ return WalkerAction.Continue
+
+ def enter_Field(self, node: 'FieldNode') -> None:
+ # If a field doesn't implement storage, it is not relevant here
+ if not node.implements_storage:
+ return
+
+ # collect any extra combo signals that this field requires
+ extra_combo_signals = OrderedDict() # type: OrderedDict[str, SVLogic]
+ for conditional in self.field_logic.get_conditionals(node):
+ for signal in conditional.get_extra_combo_signals(node):
+ if signal.name in extra_combo_signals:
+ # Assert that subsequent declarations of the same signal
+ # are identical
+ assert signal == extra_combo_signals[signal.name]
+ else:
+ extra_combo_signals[signal.name] = signal
+
+ self.push_struct(kwf(node.inst_name))
+ self.add_member("next", node.width)
+ self.add_member("load_next")
+ for signal in extra_combo_signals.values():
+ self.add_member(signal.name, signal.width)
+ if node.is_up_counter:
+ self.add_up_counter_members(node)
+ if node.is_down_counter:
+ self.add_down_counter_members(node)
+ if node.get_property('paritycheck'):
+ self.add_member("parity_error")
+ self.pop_struct()
+
+ def add_up_counter_members(self, node: 'FieldNode') -> None:
+ self.add_member('incrthreshold')
+ if self.field_logic.counter_incrsaturates(node):
+ self.add_member('incrsaturate')
+ else:
+ self.add_member('overflow')
+
+ def add_down_counter_members(self, node: 'FieldNode') -> None:
+ self.add_member('decrthreshold')
+ if self.field_logic.counter_decrsaturates(node):
+ self.add_member('decrsaturate')
+ else:
+ self.add_member('underflow')
+
+
+class FieldStorageStructGenerator(RDLStructGenerator):
+
+ def __init__(self, field_logic: 'FieldLogic') -> None:
+ super().__init__()
+ self.field_logic = field_logic
+
+ def enter_AddressableComponent(self, node: 'AddressableNode') -> Optional[WalkerAction]:
+ super().enter_AddressableComponent(node)
+
+ if node.external:
+ return WalkerAction.SkipDescendants
+ return WalkerAction.Continue
+
+ def enter_Field(self, node: 'FieldNode') -> None:
+ self.push_struct(kwf(node.inst_name))
+
+ if node.implements_storage:
+ self.add_member("value", node.width)
+ if node.get_property('paritycheck'):
+ self.add_member("parity")
+
+ if self.field_logic.has_next_q(node):
+ self.add_member("next_q", node.width)
+
+ self.pop_struct()
+
+
+class FieldLogicGenerator(RDLForLoopGenerator):
+ i_type = "genvar"
+ def __init__(self, field_logic: 'FieldLogic') -> None:
+ super().__init__()
+ self.field_logic = field_logic
+ self.exp = field_logic.exp
+ self.ds = self.exp.ds
+ self.field_storage_template = self.exp.jj_env.get_template(
+ "field_logic/templates/field_storage.sv"
+ )
+ self.external_reg_template = self.exp.jj_env.get_template(
+ "field_logic/templates/external_reg.sv"
+ )
+ self.external_block_template = self.exp.jj_env.get_template(
+ "field_logic/templates/external_block.sv"
+ )
+ self.intr_fields = [] # type: List[FieldNode]
+ self.halt_fields = [] # type: List[FieldNode]
+ self.msg = self.ds.top_node.env.msg
+
+
+ def enter_AddressableComponent(self, node: 'AddressableNode') -> Optional[WalkerAction]:
+ super().enter_AddressableComponent(node)
+
+ if node.external and not isinstance(node, RegNode):
+ # Is an external block
+ self.assign_external_block_outputs(node)
+
+ # Do not recurse
+ return WalkerAction.SkipDescendants
+
+ return WalkerAction.Continue
+
+ def enter_Reg(self, node: 'RegNode') -> Optional[WalkerAction]:
+ self.intr_fields = []
+ self.halt_fields = []
+
+ if node.external:
+ self.assign_external_reg_outputs(node)
+ # Do not recurse to fields
+ return WalkerAction.SkipDescendants
+
+ return WalkerAction.Continue
+
+
+ def enter_Field(self, node: 'FieldNode') -> None:
+ if node.implements_storage:
+ self.generate_field_storage(node)
+
+ self.assign_field_outputs(node)
+
+ if node.get_property('intr'):
+ self.intr_fields.append(node)
+ if node.get_property('haltenable') or node.get_property('haltmask'):
+ self.halt_fields.append(node)
+
+
+ def exit_Reg(self, node: 'RegNode') -> None:
+ # Assign register's intr output
+ if self.intr_fields:
+ strs = []
+ for field in self.intr_fields:
+ enable = field.get_property('enable')
+ mask = field.get_property('mask')
+ F = self.exp.dereferencer.get_value(field)
+ if enable:
+ E = self.exp.dereferencer.get_value(enable)
+ s = f"|({F} & {E})"
+ elif mask:
+ M = self.exp.dereferencer.get_value(mask)
+ s = f"|({F} & ~{M})"
+ else:
+ s = f"|{F}"
+ strs.append(s)
+
+ self.add_content(
+ f"assign {self.exp.hwif.get_implied_prop_output_identifier(node, 'intr')} ="
+ )
+ self.add_content(
+ " "
+ + "\n || ".join(strs)
+ + ";"
+ )
+
+ # Assign register's halt output
+ if self.halt_fields:
+ strs = []
+ for field in self.halt_fields:
+ enable = field.get_property('haltenable')
+ mask = field.get_property('haltmask')
+ F = self.exp.dereferencer.get_value(field)
+ if enable:
+ E = self.exp.dereferencer.get_value(enable)
+ s = f"|({F} & {E})"
+ elif mask:
+ M = self.exp.dereferencer.get_value(mask)
+ s = f"|({F} & ~{M})"
+ else:
+ s = f"|{F}"
+ strs.append(s)
+
+ self.add_content(
+ f"assign {self.exp.hwif.get_implied_prop_output_identifier(node, 'halt')} ="
+ )
+ self.add_content(
+ " "
+ + "\n || ".join(strs)
+ + ";"
+ )
+
+
+ def generate_field_storage(self, node: 'FieldNode') -> None:
+ conditionals = self.field_logic.get_conditionals(node)
+ extra_combo_signals = OrderedDict()
+ unconditional: Optional[NextStateUnconditional] = None
+ new_conditionals = []
+ for conditional in conditionals:
+ for signal in conditional.get_extra_combo_signals(node):
+ extra_combo_signals[signal.name] = signal
+
+ if isinstance(conditional, NextStateUnconditional):
+ if unconditional is not None:
+ # Too inconvenient to validate this early. Easier to validate here in-place generically
+ self.msg.fatal(
+ "Field has multiple conflicting properties that unconditionally set its state:\n"
+ f" * {conditional.unconditional_explanation}\n"
+ f" * {unconditional.unconditional_explanation}",
+ node.inst.inst_src_ref
+ )
+ unconditional = conditional
+ else:
+ new_conditionals.append(conditional)
+ conditionals = new_conditionals
+
+ resetsignal = node.get_property('resetsignal')
+
+ reset_value = node.get_property('reset')
+ if reset_value is not None:
+ reset_value_str = self.exp.dereferencer.get_value(reset_value, node.width)
+ else:
+ # 5.9.1-g: If no reset value given, the field is not reset, even if it has a resetsignal.
+ reset_value_str = None
+ resetsignal = None
+
+ context = {
+ 'node': node,
+ 'reset': reset_value_str,
+ 'field_logic': self.field_logic,
+ 'extra_combo_signals': extra_combo_signals,
+ 'conditionals': conditionals,
+ 'unconditional': unconditional,
+ 'resetsignal': resetsignal,
+ 'get_always_ff_event': self.exp.dereferencer.get_always_ff_event,
+ 'get_value': self.exp.dereferencer.get_value,
+ 'get_resetsignal': self.exp.dereferencer.get_resetsignal,
+ 'get_input_identifier': self.exp.hwif.get_input_identifier,
+ 'ds': self.ds,
+ }
+ self.add_content(self.field_storage_template.render(context))
+
+
+ def assign_field_outputs(self, node: 'FieldNode') -> None:
+ # 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} = {value};"
+ )
+
+ # Inferred logical reduction outputs
+ 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'):
+ 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'):
+ 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};"
+ )
+
+ # Software access strobes
+ 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'):
+ output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "swacc")
+ value = self.field_logic.get_swacc_identifier(node)
+ self.add_content(
+ f"assign {output_identifier} = {value};"
+ )
+ if node.get_property('rd_swacc'):
+ output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "rd_swacc")
+ value = self.field_logic.get_rd_swacc_identifier(node)
+ self.add_content(
+ f"assign {output_identifier} = {value};"
+ )
+ if node.get_property('wr_swacc'):
+ output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "wr_swacc")
+ value = self.field_logic.get_wr_swacc_identifier(node)
+ self.add_content(
+ f"assign {output_identifier} = {value};"
+ )
+
+ # Counter thresholds
+ if node.get_property('incrthreshold') is not False: # (explicitly not False. Not 0)
+ output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "incrthreshold")
+ value = self.field_logic.get_field_combo_identifier(node, 'incrthreshold')
+ self.add_content(
+ f"assign {output_identifier} = {value};"
+ )
+ if node.get_property('decrthreshold') is not False: # (explicitly not False. Not 0)
+ output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "decrthreshold")
+ value = self.field_logic.get_field_combo_identifier(node, 'decrthreshold')
+ self.add_content(
+ f"assign {output_identifier} = {value};"
+ )
+
+ # Counter events
+ if node.get_property('overflow'):
+ output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "overflow")
+ value = self.field_logic.get_field_combo_identifier(node, 'overflow')
+ self.add_content(
+ f"assign {output_identifier} = {value};"
+ )
+ if node.get_property('underflow'):
+ output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "underflow")
+ value = self.field_logic.get_field_combo_identifier(node, 'underflow')
+ self.add_content(
+ f"assign {output_identifier} = {value};"
+ )
+
+
+ def assign_external_reg_outputs(self, node: 'RegNode') -> None:
+ prefix = "hwif_out." + get_indexed_path(self.exp.ds.top_node, node)
+ strb = self.exp.dereferencer.get_access_strobe(node)
+
+ width = min(self.exp.cpuif.data_width, node.get_property('regwidth'))
+ if width != self.exp.cpuif.data_width:
+ bslice = f"[{width - 1}:0]"
+ else:
+ bslice = ""
+
+ context = {
+ "has_sw_writable": node.has_sw_writable,
+ "has_sw_readable": node.has_sw_readable,
+ "prefix": prefix,
+ "strb": strb,
+ "bslice": bslice,
+ "retime": self.ds.retime_external_reg,
+ 'get_always_ff_event': self.exp.dereferencer.get_always_ff_event,
+ "get_resetsignal": self.exp.dereferencer.get_resetsignal,
+ "resetsignal": self.exp.ds.top_node.cpuif_reset,
+ }
+ self.add_content(self.external_reg_template.render(context))
+
+ def assign_external_block_outputs(self, node: 'AddressableNode') -> None:
+ prefix = "hwif_out." + get_indexed_path(self.exp.ds.top_node, node)
+ strb = self.exp.dereferencer.get_external_block_access_strobe(node)
+ addr_width = clog2(node.size)
+
+ retime = False
+ if isinstance(node, RegfileNode):
+ retime = self.ds.retime_external_regfile
+ elif isinstance(node, MemNode):
+ retime = self.ds.retime_external_mem
+ elif isinstance(node, AddrmapNode):
+ retime = self.ds.retime_external_addrmap
+
+ context = {
+ "prefix": prefix,
+ "strb": strb,
+ "addr_width": addr_width,
+ "retime": retime,
+ 'get_always_ff_event': self.exp.dereferencer.get_always_ff_event,
+ "get_resetsignal": self.exp.dereferencer.get_resetsignal,
+ "resetsignal": self.exp.ds.top_node.cpuif_reset,
+ }
+ self.add_content(self.external_block_template.render(context))
diff --git a/src/peakrdl_regblock/field_logic/hw_interrupts.py b/src/peakrdl_regblock/field_logic/hw_interrupts.py
new file mode 100644
index 0000000..dbebdd3
--- /dev/null
+++ b/src/peakrdl_regblock/field_logic/hw_interrupts.py
@@ -0,0 +1,162 @@
+from typing import TYPE_CHECKING, List
+
+from systemrdl.rdltypes import InterruptType
+
+from .bases import NextStateConditional
+
+if TYPE_CHECKING:
+ from systemrdl.node import FieldNode
+
+
+class Sticky(NextStateConditional):
+ """
+ Normal multi-bit sticky
+ """
+ comment = "multi-bit sticky"
+ def is_match(self, field: 'FieldNode') -> bool:
+ return (
+ field.is_hw_writable
+ and field.get_property('sticky')
+ )
+
+ def get_predicate(self, field: 'FieldNode') -> str:
+ I = self.exp.hwif.get_input_identifier(field)
+ R = self.exp.field_logic.get_storage_identifier(field)
+ return f"({R} == '0) && ({I} != '0)"
+
+ def get_assignments(self, field: 'FieldNode') -> List[str]:
+ I = self.exp.hwif.get_input_identifier(field)
+ return [
+ f"next_c = {I};",
+ "load_next_c = '1;",
+ ]
+
+
+class Stickybit(NextStateConditional):
+ """
+ Normal stickybit
+ """
+ comment = "stickybit"
+ def is_match(self, field: 'FieldNode') -> bool:
+ return (
+ field.is_hw_writable
+ and field.get_property('stickybit')
+ and field.get_property('intr type') in {None, InterruptType.level}
+ )
+
+ def get_predicate(self, field: 'FieldNode') -> str:
+ F = self.exp.hwif.get_input_identifier(field)
+ if field.width == 1:
+ return str(F)
+ else:
+ return f"{F} != '0"
+
+ def get_assignments(self, field: 'FieldNode') -> List[str]:
+ if field.width == 1:
+ return [
+ "next_c = '1;",
+ "load_next_c = '1;",
+ ]
+ else:
+ I = self.exp.hwif.get_input_identifier(field)
+ R = self.exp.field_logic.get_storage_identifier(field)
+ return [
+ f"next_c = {R} | {I};",
+ "load_next_c = '1;",
+ ]
+
+class PosedgeStickybit(NextStateConditional):
+ """
+ Positive edge stickybit
+ """
+ comment = "posedge stickybit"
+ def is_match(self, field: 'FieldNode') -> bool:
+ return (
+ field.is_hw_writable
+ and field.get_property('stickybit')
+ and field.get_property('intr type') == InterruptType.posedge
+ )
+
+ def get_predicate(self, field: 'FieldNode') -> str:
+ I = self.exp.hwif.get_input_identifier(field)
+ Iq = self.exp.field_logic.get_next_q_identifier(field)
+ return f"(~{Iq} & {I}) != '0"
+
+ def get_assignments(self, field: 'FieldNode') -> List[str]:
+ if field.width == 1:
+ return [
+ "next_c = '1;",
+ "load_next_c = '1;",
+ ]
+ else:
+ I = self.exp.hwif.get_input_identifier(field)
+ Iq = self.exp.field_logic.get_next_q_identifier(field)
+ R = self.exp.field_logic.get_storage_identifier(field)
+ return [
+ f"next_c = {R} | (~{Iq} & {I});",
+ "load_next_c = '1;",
+ ]
+
+class NegedgeStickybit(NextStateConditional):
+ """
+ Negative edge stickybit
+ """
+ comment = "negedge stickybit"
+ def is_match(self, field: 'FieldNode') -> bool:
+ return (
+ field.is_hw_writable
+ and field.get_property('stickybit')
+ and field.get_property('intr type') == InterruptType.negedge
+ )
+
+ def get_predicate(self, field: 'FieldNode') -> str:
+ I = self.exp.hwif.get_input_identifier(field)
+ Iq = self.exp.field_logic.get_next_q_identifier(field)
+ return f"({Iq} & ~{I}) != '0"
+
+ def get_assignments(self, field: 'FieldNode') -> List[str]:
+ if field.width == 1:
+ return [
+ "next_c = '1;",
+ "load_next_c = '1;",
+ ]
+ else:
+ I = self.exp.hwif.get_input_identifier(field)
+ Iq = self.exp.field_logic.get_next_q_identifier(field)
+ R = self.exp.field_logic.get_storage_identifier(field)
+ return [
+ f"next_c = {R} | ({Iq} & ~{I});",
+ "load_next_c = '1;",
+ ]
+
+class BothedgeStickybit(NextStateConditional):
+ """
+ edge-sensitive stickybit
+ """
+ comment = "bothedge stickybit"
+ def is_match(self, field: 'FieldNode') -> bool:
+ return (
+ field.is_hw_writable
+ and field.get_property('stickybit')
+ and field.get_property('intr type') == InterruptType.bothedge
+ )
+
+ def get_predicate(self, field: 'FieldNode') -> str:
+ I = self.exp.hwif.get_input_identifier(field)
+ Iq = self.exp.field_logic.get_next_q_identifier(field)
+ return f"{Iq} != {I}"
+
+ def get_assignments(self, field: 'FieldNode') -> List[str]:
+ if field.width == 1:
+ return [
+ "next_c = '1;",
+ "load_next_c = '1;",
+ ]
+ else:
+ I = self.exp.hwif.get_input_identifier(field)
+ Iq = self.exp.field_logic.get_next_q_identifier(field)
+ R = self.exp.field_logic.get_storage_identifier(field)
+ return [
+ f"next_c = {R} | ({Iq} ^ {I});",
+ "load_next_c = '1;",
+ ]
diff --git a/src/peakrdl_regblock/field_logic/hw_interrupts_with_write.py b/src/peakrdl_regblock/field_logic/hw_interrupts_with_write.py
new file mode 100644
index 0000000..7545533
--- /dev/null
+++ b/src/peakrdl_regblock/field_logic/hw_interrupts_with_write.py
@@ -0,0 +1,187 @@
+from typing import List, TYPE_CHECKING
+
+from .hw_interrupts import (
+ Sticky, Stickybit,
+ PosedgeStickybit, NegedgeStickybit, BothedgeStickybit
+)
+from .hw_write import WEWrite, WELWrite
+
+if TYPE_CHECKING:
+ from systemrdl.node import FieldNode
+
+
+class StickyWE(Sticky, WEWrite):
+ """
+ Normal multi-bit sticky with write enable
+ """
+ comment = "multi-bit sticky with WE"
+ def is_match(self, field: 'FieldNode') -> bool:
+ return (
+ Sticky.is_match(self, field)
+ and WEWrite.is_match(self, field)
+ )
+
+ def get_predicate(self, field: 'FieldNode') -> str:
+ BASE = Sticky.get_predicate(self, field)
+ WE = WEWrite.get_predicate(self, field)
+ return f"{BASE} && {WE}"
+
+ def get_assignments(self, field: 'FieldNode') -> List[str]:
+ return Sticky.get_assignments(self, field)
+
+class StickyWEL(Sticky, WELWrite):
+ """
+ Normal multi-bit sticky with write enable low
+ """
+ comment = "multi-bit sticky with WEL"
+ def is_match(self, field: 'FieldNode') -> bool:
+ return (
+ Sticky.is_match(self, field)
+ and WELWrite.is_match(self, field)
+ )
+
+ def get_predicate(self, field: 'FieldNode') -> str:
+ BASE = Sticky.get_predicate(self, field)
+ WEL = WELWrite.get_predicate(self, field)
+ return f"{BASE} && {WEL}"
+
+ def get_assignments(self, field: 'FieldNode') -> List[str]:
+ return Sticky.get_assignments(self, field)
+
+class StickybitWE(Stickybit, WEWrite):
+ """
+ Normal stickybiti with write enable
+ """
+ comment = "stickybit with WE"
+ def is_match(self, field: 'FieldNode') -> bool:
+ return (
+ Stickybit.is_match(self, field)
+ and WEWrite.is_match(self, field)
+ )
+
+ def get_predicate(self, field: 'FieldNode') -> str:
+ BASE = Stickybit.get_predicate(self, field)
+ WE = WEWrite.get_predicate(self, field)
+ return f"{BASE} && {WE}"
+
+ def get_assignments(self, field: 'FieldNode') -> List[str]:
+ return Stickybit.get_assignments(self, field)
+
+class StickybitWEL(Stickybit, WELWrite):
+ """
+ Normal stickybiti with write enable low
+ """
+ comment = "stickybit with WEL"
+ def is_match(self, field: 'FieldNode') -> bool:
+ return Stickybit.is_match(self, field) \
+ and WELWrite.is_match(self, field)
+
+ def get_predicate(self, field: 'FieldNode') -> str:
+ BASE = Stickybit.get_predicate(self, field)
+ WEL = WELWrite.get_predicate(self, field)
+ return f"{BASE} && {WEL}"
+
+ def get_assignments(self, field: 'FieldNode') -> List[str]:
+ return Stickybit.get_assignments(self, field)
+
+class PosedgeStickybitWE(PosedgeStickybit, WEWrite):
+ """
+ Positive edge stickybit with write enable
+ """
+ comment = "posedge stickybit with WE"
+ def is_match(self, field: 'FieldNode') -> bool:
+ return PosedgeStickybit.is_match(self, field) \
+ and WEWrite.is_match(self, field)
+
+ def get_predicate(self, field: 'FieldNode') -> str:
+ BASE = PosedgeStickybit.get_predicate(self, field)
+ WE = WEWrite.get_predicate(self, field)
+ return f"{BASE} && {WE}"
+
+ def get_assignments(self, field: 'FieldNode') -> List[str]:
+ return PosedgeStickybit.get_assignments(self, field)
+
+class PosedgeStickybitWEL(PosedgeStickybit, WELWrite):
+ """
+ Positive edge stickybit with write enable low
+ """
+ comment = "posedge stickybit with WEL"
+ def is_match(self, field: 'FieldNode') -> bool:
+ return PosedgeStickybit.is_match(self, field) \
+ and WELWrite.is_match(self, field)
+
+ def get_predicate(self, field: 'FieldNode') -> str:
+ BASE = PosedgeStickybit.get_predicate(self, field)
+ WEL = WELWrite.get_predicate(self, field)
+ return f"{BASE} && {WEL}"
+
+ def get_assignments(self, field: 'FieldNode') -> List[str]:
+ return PosedgeStickybit.get_assignments(self, field)
+
+class NegedgeStickybitWE(NegedgeStickybit, WEWrite):
+ """
+ Negative edge stickybit with write enable
+ """
+ comment = "negedge stickybit with WE"
+ def is_match(self, field: 'FieldNode') -> bool:
+ return NegedgeStickybit.is_match(self, field) \
+ and WEWrite.is_match(self, field)
+
+ def get_predicate(self, field: 'FieldNode') -> str:
+ BASE = NegedgeStickybit.get_predicate(self, field)
+ WE = WEWrite.get_predicate(self, field)
+ return f"{BASE} && {WE}"
+
+ def get_assignments(self, field: 'FieldNode') -> List[str]:
+ return NegedgeStickybit.get_assignments(self, field)
+
+class NegedgeStickybitWEL(NegedgeStickybit, WELWrite):
+ """
+ Negative edge stickybit with write enable low
+ """
+ comment = "negedge stickybit with WEL"
+ def is_match(self, field: 'FieldNode') -> bool:
+ return NegedgeStickybit.is_match(self, field) \
+ and WELWrite.is_match(self, field)
+
+ def get_predicate(self, field: 'FieldNode') -> str:
+ BASE = NegedgeStickybit.get_predicate(self, field)
+ WEL = WELWrite.get_predicate(self, field)
+ return f"{BASE} && {WEL}"
+
+ def get_assignments(self, field: 'FieldNode') -> List[str]:
+ return NegedgeStickybit.get_assignments(self, field)
+
+class BothedgeStickybitWE(BothedgeStickybit, WEWrite):
+ """
+ edge-sensitive stickybit with write enable
+ """
+ comment = "bothedge stickybit with WE"
+ def is_match(self, field: 'FieldNode') -> bool:
+ return BothedgeStickybit.is_match(self, field) \
+ and WEWrite.is_match(self, field)
+
+ def get_predicate(self, field: 'FieldNode') -> str:
+ BASE = BothedgeStickybit.get_predicate(self, field)
+ WE = WEWrite.get_predicate(self, field)
+ return f"{BASE} && {WE}"
+
+ def get_assignments(self, field: 'FieldNode') -> List[str]:
+ return BothedgeStickybit.get_assignments(self, field)
+
+class BothedgeStickybitWEL(BothedgeStickybit, WELWrite):
+ """
+ edge-sensitive stickybit with write enable low
+ """
+ comment = "bothedge stickybit with WEL"
+ def is_match(self, field: 'FieldNode') -> bool:
+ return BothedgeStickybit.is_match(self, field) \
+ and WELWrite.is_match(self, field)
+
+ def get_predicate(self, field: 'FieldNode') -> str:
+ BASE = BothedgeStickybit.get_predicate(self, field)
+ WEL = WELWrite.get_predicate(self, field)
+ return f"{BASE} && {WEL}"
+
+ def get_assignments(self, field: 'FieldNode') -> List[str]:
+ return BothedgeStickybit.get_assignments(self, field)
diff --git a/src/peakrdl_regblock/field_logic/hw_set_clr.py b/src/peakrdl_regblock/field_logic/hw_set_clr.py
new file mode 100644
index 0000000..982f1b3
--- /dev/null
+++ b/src/peakrdl_regblock/field_logic/hw_set_clr.py
@@ -0,0 +1,72 @@
+from typing import TYPE_CHECKING, List
+
+from .bases import NextStateConditional
+
+if TYPE_CHECKING:
+ from systemrdl.node import FieldNode
+
+
+class HWSet(NextStateConditional):
+ comment = "HW Set"
+ def is_match(self, field: 'FieldNode') -> bool:
+ return bool(field.get_property('hwset'))
+
+ def get_predicate(self, field: 'FieldNode') -> str:
+ prop = field.get_property('hwset')
+ if isinstance(prop, bool):
+ identifier = self.exp.hwif.get_implied_prop_input_identifier(field, "hwset")
+ else:
+ # signal or field
+ identifier = str(self.exp.dereferencer.get_value(prop))
+ return identifier
+
+ def get_assignments(self, field: 'FieldNode') -> List[str]:
+ hwmask = field.get_property('hwmask')
+ hwenable = field.get_property('hwenable')
+ R = self.exp.field_logic.get_storage_identifier(field)
+ if hwmask is not None:
+ M = self.exp.dereferencer.get_value(hwmask)
+ next_val = f"{R} | ~{M}"
+ elif hwenable is not None:
+ E = self.exp.dereferencer.get_value(hwenable)
+ next_val = f"{R} | {E}"
+ else:
+ next_val = "'1"
+
+ return [
+ f"next_c = {next_val};",
+ "load_next_c = '1;",
+ ]
+
+
+class HWClear(NextStateConditional):
+ comment = "HW Clear"
+ def is_match(self, field: 'FieldNode') -> bool:
+ return bool(field.get_property('hwclr'))
+
+ def get_predicate(self, field: 'FieldNode') -> str:
+ prop = field.get_property('hwclr')
+ if isinstance(prop, bool):
+ identifier = self.exp.hwif.get_implied_prop_input_identifier(field, "hwclr")
+ else:
+ # signal or field
+ identifier = str(self.exp.dereferencer.get_value(prop))
+ return identifier
+
+ def get_assignments(self, field: 'FieldNode') -> List[str]:
+ hwmask = field.get_property('hwmask')
+ hwenable = field.get_property('hwenable')
+ R = self.exp.field_logic.get_storage_identifier(field)
+ if hwmask is not None:
+ M = self.exp.dereferencer.get_value(hwmask)
+ next_val = f"{R} & {M}"
+ elif hwenable is not None:
+ E = self.exp.dereferencer.get_value(hwenable)
+ next_val = f"{R} & ~{E}"
+ else:
+ next_val = "'0"
+
+ return [
+ f"next_c = {next_val};",
+ "load_next_c = '1;",
+ ]
diff --git a/src/peakrdl_regblock/field_logic/hw_write.py b/src/peakrdl_regblock/field_logic/hw_write.py
new file mode 100644
index 0000000..5a629ce
--- /dev/null
+++ b/src/peakrdl_regblock/field_logic/hw_write.py
@@ -0,0 +1,95 @@
+from typing import TYPE_CHECKING, List
+
+from .bases import NextStateConditional, NextStateUnconditional
+
+if TYPE_CHECKING:
+ from systemrdl.node import FieldNode
+
+
+class AlwaysWrite(NextStateUnconditional):
+ """
+ hw writable, without any qualifying we/wel
+ """
+ comment = "HW Write"
+ unconditional_explanation = "A hardware-writable field without a write-enable (we/wel) will always update the field value"
+
+ def is_match(self, field: 'FieldNode') -> bool:
+ return (
+ field.is_hw_writable
+ and not field.get_property('we')
+ and not field.get_property('wel')
+ )
+
+ def get_assignments(self, field: 'FieldNode') -> List[str]:
+ hwmask = field.get_property('hwmask')
+ hwenable = field.get_property('hwenable')
+ I = str(self.exp.hwif.get_input_identifier(field))
+ R = self.exp.field_logic.get_storage_identifier(field)
+ if hwmask is not None:
+ M = self.exp.dereferencer.get_value(hwmask)
+ next_val = f"{I} & ~{M} | {R} & {M}"
+ elif hwenable is not None:
+ E = self.exp.dereferencer.get_value(hwenable)
+ next_val = f"{I} & {E} | {R} & ~{E}"
+ else:
+ next_val = I
+
+ return [
+ f"next_c = {next_val};",
+ "load_next_c = '1;",
+ ]
+
+
+class _QualifiedWrite(NextStateConditional):
+ def get_assignments(self, field: 'FieldNode') -> List[str]:
+ hwmask = field.get_property('hwmask')
+ hwenable = field.get_property('hwenable')
+ I = str(self.exp.hwif.get_input_identifier(field))
+ R = self.exp.field_logic.get_storage_identifier(field)
+ if hwmask is not None:
+ M = self.exp.dereferencer.get_value(hwmask)
+ next_val = f"{I} & ~{M} | {R} & {M}"
+ elif hwenable is not None:
+ E = self.exp.dereferencer.get_value(hwenable)
+ next_val = f"{I} & {E} | {R} & ~{E}"
+ else:
+ next_val = I
+
+ return [
+ f"next_c = {next_val};",
+ "load_next_c = '1;",
+ ]
+
+class WEWrite(_QualifiedWrite):
+ comment = "HW Write - we"
+ def is_match(self, field: 'FieldNode') -> bool:
+ return (
+ field.is_hw_writable
+ and bool(field.get_property('we'))
+ )
+
+ def get_predicate(self, field: 'FieldNode') -> str:
+ prop = field.get_property('we')
+ if isinstance(prop, bool):
+ identifier = self.exp.hwif.get_implied_prop_input_identifier(field, "we")
+ else:
+ # signal or field
+ identifier = str(self.exp.dereferencer.get_value(prop))
+ return identifier
+
+class WELWrite(_QualifiedWrite):
+ comment = "HW Write - wel"
+ def is_match(self, field: 'FieldNode') -> bool:
+ return (
+ field.is_hw_writable
+ and bool(field.get_property('wel'))
+ )
+
+ def get_predicate(self, field: 'FieldNode') -> str:
+ prop = field.get_property('wel')
+ if isinstance(prop, bool):
+ identifier = self.exp.hwif.get_implied_prop_input_identifier(field, "wel")
+ else:
+ # signal or field
+ identifier = str(self.exp.dereferencer.get_value(prop))
+ return f"!{identifier}"
diff --git a/src/peakrdl_regblock/field_logic/sw_onread.py b/src/peakrdl_regblock/field_logic/sw_onread.py
new file mode 100644
index 0000000..ba998c7
--- /dev/null
+++ b/src/peakrdl_regblock/field_logic/sw_onread.py
@@ -0,0 +1,45 @@
+from typing import TYPE_CHECKING, List
+
+from systemrdl.rdltypes import OnReadType
+
+from .bases import NextStateConditional
+
+if TYPE_CHECKING:
+ from systemrdl.node import FieldNode
+
+class _OnRead(NextStateConditional):
+ onreadtype = None # type: OnReadType
+ def is_match(self, field: 'FieldNode') -> bool:
+ return field.get_property('onread') == self.onreadtype
+
+ def get_predicate(self, field: 'FieldNode') -> str:
+ 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):
+ comment = "SW clear on read"
+ onreadtype = OnReadType.rclr
+
+ def get_assignments(self, field: 'FieldNode') -> List[str]:
+ return [
+ "next_c = '0;",
+ "load_next_c = '1;",
+ ]
+
+
+class SetOnRead(_OnRead):
+ comment = "SW set on read"
+ onreadtype = OnReadType.rset
+
+ def get_assignments(self, field: 'FieldNode') -> List[str]:
+ return [
+ "next_c = '1;",
+ "load_next_c = '1;",
+ ]
diff --git a/src/peakrdl_regblock/field_logic/sw_onwrite.py b/src/peakrdl_regblock/field_logic/sw_onwrite.py
new file mode 100644
index 0000000..7d11945
--- /dev/null
+++ b/src/peakrdl_regblock/field_logic/sw_onwrite.py
@@ -0,0 +1,129 @@
+from typing import TYPE_CHECKING, List, Optional
+
+from systemrdl.rdltypes import OnWriteType
+
+from .bases import NextStateConditional
+
+if TYPE_CHECKING:
+ from systemrdl.node import FieldNode
+
+# TODO: implement sw=w1 "write once" fields
+
+class _OnWrite(NextStateConditional):
+ onwritetype: Optional[OnWriteType] = None
+ def is_match(self, field: 'FieldNode') -> bool:
+ return field.is_sw_writable and field.get_property('onwrite') == self.onwritetype
+
+ def get_predicate(self, field: 'FieldNode') -> str:
+ if field.parent.get_property('buffer_writes'):
+ # Is buffered write. Use alternate strobe
+ wstrb = self.exp.write_buffering.get_write_strobe(field)
+
+ if field.get_property('swwe') or field.get_property('swwel'):
+ # dereferencer will wrap swwel complement if necessary
+ qualifier = self.exp.dereferencer.get_field_propref_value(field, 'swwe')
+ return f"{wstrb} && {qualifier}"
+
+ return wstrb
+ else:
+ # is regular register
+ strb = self.exp.dereferencer.get_access_strobe(field)
+
+ if field.get_property('swwe') or field.get_property('swwel'):
+ # dereferencer will wrap swwel complement if necessary
+ qualifier = self.exp.dereferencer.get_field_propref_value(field, 'swwe')
+ return f"{strb} && decoded_req_is_wr && {qualifier}"
+
+ return f"{strb} && decoded_req_is_wr"
+
+ def get_assignments(self, field: 'FieldNode') -> List[str]:
+ accesswidth = field.parent.get_property("accesswidth")
+
+ # Due to 10.6.1-f, it is impossible for a field with an onwrite action to
+ # be split across subwords.
+ # Therefore it is ok to get the subword idx from only one of the bit offsets
+ sidx = field.low // accesswidth
+
+ # field does not get split between subwords
+ R = self.exp.field_logic.get_storage_identifier(field)
+ D = self.exp.field_logic.get_wr_data(field, sidx)
+ S = self.exp.field_logic.get_wr_biten(field, sidx)
+ lines = [
+ f"next_c = {self.get_onwrite_rhs(R, D, S)};",
+ "load_next_c = '1;",
+ ]
+ return lines
+
+ def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str:
+ raise NotImplementedError
+
+
+#-------------------------------------------------------------------------------
+class WriteOneSet(_OnWrite):
+ comment = "SW write 1 set"
+ onwritetype = OnWriteType.woset
+
+ def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str:
+ return f"{reg} | ({data} & {strb})"
+
+class WriteOneClear(_OnWrite):
+ comment = "SW write 1 clear"
+ onwritetype = OnWriteType.woclr
+
+ def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str:
+ return f"{reg} & ~({data} & {strb})"
+
+class WriteOneToggle(_OnWrite):
+ comment = "SW write 1 toggle"
+ onwritetype = OnWriteType.wot
+
+ def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str:
+ return f"{reg} ^ ({data} & {strb})"
+
+class WriteZeroSet(_OnWrite):
+ comment = "SW write 0 set"
+ onwritetype = OnWriteType.wzs
+
+ def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str:
+ return f"{reg} | (~{data} & {strb})"
+
+class WriteZeroClear(_OnWrite):
+ comment = "SW write 0 clear"
+ onwritetype = OnWriteType.wzc
+
+ def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str:
+ return f"{reg} & ({data} | ~{strb})"
+
+class WriteZeroToggle(_OnWrite):
+ comment = "SW write 0 toggle"
+ onwritetype = OnWriteType.wzt
+
+ def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str:
+ return f"{reg} ^ (~{data} & {strb})"
+
+class WriteClear(_OnWrite):
+ comment = "SW write clear"
+ onwritetype = OnWriteType.wclr
+
+ def get_assignments(self, field: 'FieldNode') -> List[str]:
+ return [
+ "next_c = '0;",
+ "load_next_c = '1;",
+ ]
+
+class WriteSet(_OnWrite):
+ comment = "SW write set"
+ onwritetype = OnWriteType.wset
+
+ def get_assignments(self, field: 'FieldNode') -> List[str]:
+ return [
+ "next_c = '1;",
+ "load_next_c = '1;",
+ ]
+
+class Write(_OnWrite):
+ comment = "SW write"
+ onwritetype = None
+
+ def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str:
+ return f"({reg} & ~{strb}) | ({data} & {strb})"
diff --git a/src/peakrdl_regblock/field_logic/sw_singlepulse.py b/src/peakrdl_regblock/field_logic/sw_singlepulse.py
new file mode 100644
index 0000000..9ed476b
--- /dev/null
+++ b/src/peakrdl_regblock/field_logic/sw_singlepulse.py
@@ -0,0 +1,19 @@
+from typing import TYPE_CHECKING, List
+
+from .bases import NextStateUnconditional
+
+if TYPE_CHECKING:
+ from systemrdl.node import FieldNode
+
+class Singlepulse(NextStateUnconditional):
+ comment = "singlepulse clears back to 0"
+ unconditional_explanation = "The 'singlepulse' property unconditionally clears a field when not written"
+
+ def is_match(self, field: 'FieldNode') -> bool:
+ return field.get_property('singlepulse')
+
+ def get_assignments(self, field: 'FieldNode') -> List[str]:
+ return [
+ "next_c = '0;",
+ "load_next_c = '1;",
+ ]
diff --git a/src/peakrdl_regblock/field_logic/templates/counter_macros.sv b/src/peakrdl_regblock/field_logic/templates/counter_macros.sv
new file mode 100644
index 0000000..2714bce
--- /dev/null
+++ b/src/peakrdl_regblock/field_logic/templates/counter_macros.sv
@@ -0,0 +1,48 @@
+{% macro up_counter(field) -%}
+ if({{field_logic.get_counter_incr_strobe(node)}}) begin // increment
+ {%- if field_logic.counter_incrsaturates(node) %}
+ if((({{node.width+1}})'(next_c) + {{field_logic.get_counter_incrvalue(node)}}) > {{field_logic.get_counter_incrsaturate_value(node)}}) begin // up-counter saturated
+ next_c = {{field_logic.get_counter_incrsaturate_value(node)}};
+ end else begin
+ next_c = next_c + {{field_logic.get_counter_incrvalue(node)}};
+ end
+ {%- else %}
+ {{field_logic.get_field_combo_identifier(node, "overflow")}} = ((({{node.width+1}})'(next_c) + {{field_logic.get_counter_incrvalue(node)}}) > {{get_value(2**node.width - 1, node.width)}});
+ next_c = next_c + {{field_logic.get_counter_incrvalue(node)}};
+ {%- endif %}
+ load_next_c = '1;
+ {%- if not field_logic.counter_incrsaturates(node) %}
+ end else begin
+ {{field_logic.get_field_combo_identifier(node, "overflow")}} = '0;
+ {%- endif %}
+ end
+ {{field_logic.get_field_combo_identifier(node, "incrthreshold")}} = ({{field_logic.get_storage_identifier(node)}} >= {{field_logic.get_counter_incrthreshold_value(node)}});
+ {%- if field_logic.counter_incrsaturates(node) %}
+ {{field_logic.get_field_combo_identifier(node, "incrsaturate")}} = ({{field_logic.get_storage_identifier(node)}} >= {{field_logic.get_counter_incrsaturate_value(node)}});
+ {%- endif %}
+{%- endmacro %}
+
+
+{% macro down_counter(field) -%}
+ if({{field_logic.get_counter_decr_strobe(node)}}) begin // decrement
+ {%- if field_logic.counter_decrsaturates(node) %}
+ if(({{node.width+1}})'(next_c) < ({{field_logic.get_counter_decrvalue(node)}} + {{field_logic.get_counter_decrsaturate_value(node)}})) begin // down-counter saturated
+ next_c = {{field_logic.get_counter_decrsaturate_value(node)}};
+ end else begin
+ next_c = next_c - {{field_logic.get_counter_decrvalue(node)}};
+ end
+ {%- else %}
+ {{field_logic.get_field_combo_identifier(node, "underflow")}} = (next_c < ({{field_logic.get_counter_decrvalue(node)}}));
+ next_c = next_c - {{field_logic.get_counter_decrvalue(node)}};
+ {%- endif %}
+ load_next_c = '1;
+ {%- if not field_logic.counter_decrsaturates(node) %}
+ end else begin
+ {{field_logic.get_field_combo_identifier(node, "underflow")}} = '0;
+ {%- endif %}
+ end
+ {{field_logic.get_field_combo_identifier(node, "decrthreshold")}} = ({{field_logic.get_storage_identifier(node)}} <= {{field_logic.get_counter_decrthreshold_value(node)}});
+ {%- if field_logic.counter_decrsaturates(node) %}
+ {{field_logic.get_field_combo_identifier(node, "decrsaturate")}} = ({{field_logic.get_storage_identifier(node)}} <= {{field_logic.get_counter_decrsaturate_value(node)}});
+ {%- endif %}
+{%- endmacro %}
diff --git a/src/peakrdl_regblock/field_logic/templates/external_block.sv b/src/peakrdl_regblock/field_logic/templates/external_block.sv
new file mode 100644
index 0000000..5c5914a
--- /dev/null
+++ b/src/peakrdl_regblock/field_logic/templates/external_block.sv
@@ -0,0 +1,31 @@
+{% if retime -%}
+
+
+always_ff {{get_always_ff_event(resetsignal)}} begin
+ if({{get_resetsignal(resetsignal)}}) begin
+ {{prefix}}.req <= '0;
+ {{prefix}}.addr <= '0;
+ {{prefix}}.req_is_wr <= '0;
+ {{prefix}}.wr_data <= '0;
+ {{prefix}}.wr_biten <= '0;
+ end else begin
+ {{prefix}}.req <= {{strb}};
+ {{prefix}}.addr <= decoded_addr[{{addr_width-1}}:0];
+ {{prefix}}.req_is_wr <= decoded_req_is_wr;
+ {{prefix}}.wr_data <= decoded_wr_data;
+ {{prefix}}.wr_biten <= decoded_wr_biten;
+ end
+end
+
+
+{%- else -%}
+
+
+assign {{prefix}}.req = {{strb}};
+assign {{prefix}}.addr = decoded_addr[{{addr_width-1}}:0];
+assign {{prefix}}.req_is_wr = decoded_req_is_wr;
+assign {{prefix}}.wr_data = decoded_wr_data;
+assign {{prefix}}.wr_biten = decoded_wr_biten;
+
+
+{%- endif %}
diff --git a/src/peakrdl_regblock/field_logic/templates/external_reg.sv b/src/peakrdl_regblock/field_logic/templates/external_reg.sv
new file mode 100644
index 0000000..1a47515
--- /dev/null
+++ b/src/peakrdl_regblock/field_logic/templates/external_reg.sv
@@ -0,0 +1,46 @@
+{% if retime -%}
+
+
+always_ff {{get_always_ff_event(resetsignal)}} begin
+ if({{get_resetsignal(resetsignal)}}) begin
+ {{prefix}}.req <= '0;
+ {{prefix}}.req_is_wr <= '0;
+ {%- if has_sw_writable %}
+ {{prefix}}.wr_data <= '0;
+ {{prefix}}.wr_biten <= '0;
+ {%- endif %}
+ end else begin
+ {%- if has_sw_readable and has_sw_writable %}
+ {{prefix}}.req <= {{strb}};
+ {%- elif has_sw_readable and not has_sw_writable %}
+ {{prefix}}.req <= !decoded_req_is_wr ? {{strb}} : '0;
+ {%- elif not has_sw_readable and has_sw_writable %}
+ {{prefix}}.req <= decoded_req_is_wr ? {{strb}} : '0;
+ {%- endif %}
+ {{prefix}}.req_is_wr <= decoded_req_is_wr;
+ {%- if has_sw_writable %}
+ {{prefix}}.wr_data <= decoded_wr_data{{bslice}};
+ {{prefix}}.wr_biten <= decoded_wr_biten{{bslice}};
+ {%- endif %}
+ end
+end
+
+
+{%- else -%}
+
+
+{%- if has_sw_readable and has_sw_writable %}
+assign {{prefix}}.req = {{strb}};
+{%- elif has_sw_readable and not has_sw_writable %}
+assign {{prefix}}.req = !decoded_req_is_wr ? {{strb}} : '0;
+{%- elif not has_sw_readable and has_sw_writable %}
+assign {{prefix}}.req = decoded_req_is_wr ? {{strb}} : '0;
+{%- endif %}
+assign {{prefix}}.req_is_wr = decoded_req_is_wr;
+{%- if has_sw_writable %}
+assign {{prefix}}.wr_data = decoded_wr_data{{bslice}};
+assign {{prefix}}.wr_biten = decoded_wr_biten{{bslice}};
+{%- endif %}
+
+
+{%- endif %}
diff --git a/src/peakrdl_regblock/field_logic/templates/field_storage.sv b/src/peakrdl_regblock/field_logic/templates/field_storage.sv
new file mode 100644
index 0000000..4e2dca0
--- /dev/null
+++ b/src/peakrdl_regblock/field_logic/templates/field_storage.sv
@@ -0,0 +1,86 @@
+{%- import 'field_logic/templates/counter_macros.sv' as counter_macros with context -%}
+// Field: {{node.get_path()}}
+always_comb begin
+ automatic logic [{{node.width-1}}:0] next_c;
+ automatic logic load_next_c;
+ next_c = {{field_logic.get_storage_identifier(node)}};
+ load_next_c = '0;
+
+ {%- for signal in extra_combo_signals %}
+ {{field_logic.get_field_combo_identifier(node, signal.name)}} = {{signal.default_assignment}};
+ {%- endfor %}
+ {% for conditional in conditionals %}
+ {%- if not loop.first %} else {% endif %}if({{conditional.get_predicate(node)}}) begin // {{conditional.comment}}
+ {%- for assignment in conditional.get_assignments(node) %}
+ {{assignment|indent}}
+ {%- endfor %}
+ end
+ {%- endfor %}
+ {%- if unconditional %}
+ {%- if conditionals %} else begin // {{unconditional.comment}}
+ {%- for assignment in unconditional.get_assignments(node) %}
+ {{assignment|indent}}
+ {%- endfor %}
+ end
+ {%- else %}
+ // {{unconditional.comment}}
+ {%- for assignment in unconditional.get_assignments(node) %}
+ {{assignment|indent}}
+ {%- endfor %}
+ {%- endif %}
+ {%- endif %}
+
+ {%- if node.is_up_counter %}
+ {{counter_macros.up_counter(node)}}
+ {%- endif %}
+
+ {%- if node.is_down_counter %}
+ {{counter_macros.down_counter(node)}}
+ {%- endif %}
+ {{field_logic.get_field_combo_identifier(node, "next")}} = next_c;
+ {{field_logic.get_field_combo_identifier(node, "load_next")}} = load_next_c;
+
+ {%- if node.get_property('paritycheck') %}
+ {{field_logic.get_parity_error_identifier(node)}} = ({{field_logic.get_parity_identifier(node)}} != ^{{field_logic.get_storage_identifier(node)}});
+ {%- endif %}
+end
+
+
+
+{%- if reset is not none %}
+always_ff {{get_always_ff_event(resetsignal)}} begin
+ if({{get_resetsignal(resetsignal)}}) begin
+ {{field_logic.get_storage_identifier(node)}} <= {{reset}};
+ {%- if node.get_property('paritycheck') %}
+ {{field_logic.get_parity_identifier(node)}} <= ^{{reset}};
+ {%- endif %}
+ {%- if field_logic.has_next_q(node) %}
+ {{field_logic.get_next_q_identifier(node)}} <= {{reset}};
+ {%- endif %}
+ end else begin
+ if({{field_logic.get_field_combo_identifier(node, "load_next")}}) begin
+ {{field_logic.get_storage_identifier(node)}} <= {{field_logic.get_field_combo_identifier(node, "next")}};
+ {%- if node.get_property('paritycheck') %}
+ {{field_logic.get_parity_identifier(node)}} <= ^{{field_logic.get_field_combo_identifier(node, "next")}};
+ {%- endif %}
+ end
+ {%- if field_logic.has_next_q(node) %}
+ {{field_logic.get_next_q_identifier(node)}} <= {{get_input_identifier(node)}};
+ {%- endif %}
+ end
+end
+
+
+{%- else %}
+always_ff @(posedge clk) begin
+ if({{field_logic.get_field_combo_identifier(node, "load_next")}}) begin
+ {{field_logic.get_storage_identifier(node)}} <= {{field_logic.get_field_combo_identifier(node, "next")}};
+ {%- if node.get_property('paritycheck') %}
+ {{field_logic.get_parity_identifier(node)}} <= ^{{field_logic.get_field_combo_identifier(node, "next")}};
+ {%- endif %}
+ end
+ {%- if field_logic.has_next_q(node) %}
+ {{field_logic.get_next_q_identifier(node)}} <= {{get_input_identifier(node)}};
+ {%- endif %}
+end
+{%- endif %}
diff --git a/src/peakrdl_regblock/forloop_generator.py b/src/peakrdl_regblock/forloop_generator.py
new file mode 100644
index 0000000..7ff0f4e
--- /dev/null
+++ b/src/peakrdl_regblock/forloop_generator.py
@@ -0,0 +1,98 @@
+from typing import TYPE_CHECKING, Optional, List, Union
+import textwrap
+
+from systemrdl.walker import RDLListener, RDLWalker, WalkerAction
+
+if TYPE_CHECKING:
+ from systemrdl.node import AddressableNode, Node
+
+class Body:
+
+ def __init__(self) -> None:
+ self.children = [] # type: List[Union[str, Body]]
+
+ def __str__(self) -> str:
+ s = '\n'.join((str(x) for x in self.children))
+ return s
+
+class LoopBody(Body):
+ def __init__(self, dim: int, iterator: str, i_type: str) -> None:
+ super().__init__()
+ self.dim = dim
+ self.iterator = iterator
+ self.i_type = i_type
+
+ def __str__(self) -> str:
+ s = super().__str__()
+ return (
+ f"for({self.i_type} {self.iterator}=0; {self.iterator}<{self.dim}; {self.iterator}++) begin\n"
+ + textwrap.indent(s, " ")
+ + "\nend"
+ )
+
+
+class ForLoopGenerator:
+ i_type = "int"
+ loop_body_cls = LoopBody
+
+ def __init__(self) -> None:
+ self._loop_level = 0
+ self._stack = [] # type: List[Body]
+
+ @property
+ def current_loop(self) -> Body:
+ return self._stack[-1]
+
+ def push_loop(self, dim: int) -> None:
+ i = f"i{self._loop_level}"
+ b = self.loop_body_cls(dim, i, self.i_type)
+ self._stack.append(b)
+ self._loop_level += 1
+
+ def add_content(self, s: str) -> None:
+ self.current_loop.children.append(s)
+
+ def pop_loop(self) -> None:
+ b = self._stack.pop()
+
+ if b.children:
+ # Loop body is not empty. Attach it to the parent
+ self.current_loop.children.append(b)
+ self._loop_level -= 1
+
+ def start(self) -> None:
+ assert not self._stack
+ b = Body()
+ self._stack.append(b)
+
+ def finish(self) -> Optional[str]:
+ b = self._stack.pop()
+ assert not self._stack
+
+ if not b.children:
+ return None
+ return str(b)
+
+class RDLForLoopGenerator(ForLoopGenerator, RDLListener):
+
+ def get_content(self, node: 'Node') -> Optional[str]:
+ self.start()
+ walker = RDLWalker()
+ walker.walk(node, self, skip_top=True)
+ return self.finish()
+
+ def enter_AddressableComponent(self, node: 'AddressableNode') -> Optional[WalkerAction]:
+ if not node.array_dimensions:
+ return None
+
+ for dim in node.array_dimensions:
+ self.push_loop(dim)
+ return None
+
+ def exit_AddressableComponent(self, node: 'AddressableNode') -> Optional[WalkerAction]:
+ if not node.array_dimensions:
+ return None
+
+ for _ in node.array_dimensions:
+ self.pop_loop()
+ return None
diff --git a/src/peakrdl_regblock/hwif/__init__.py b/src/peakrdl_regblock/hwif/__init__.py
new file mode 100644
index 0000000..1462264
--- /dev/null
+++ b/src/peakrdl_regblock/hwif/__init__.py
@@ -0,0 +1,249 @@
+from typing import TYPE_CHECKING, Union, Optional, TextIO
+
+from systemrdl.node import AddrmapNode, SignalNode, FieldNode, RegNode, AddressableNode
+from systemrdl.rdltypes import PropertyReference
+
+from ..utils import get_indexed_path
+from ..identifier_filter import kw_filter as kwf
+from ..sv_int import SVInt
+
+from .generators import InputStructGenerator_Hier, OutputStructGenerator_Hier
+from .generators import InputStructGenerator_TypeScope, OutputStructGenerator_TypeScope
+from .generators import EnumGenerator
+
+if TYPE_CHECKING:
+ from ..exporter import RegblockExporter, DesignState
+
+class Hwif:
+ """
+ Defines how the hardware input/output signals are generated:
+ - Field outputs
+ - Field inputs
+ - Signal inputs (except those that are promoted to the top)
+ """
+
+ def __init__(
+ self, exp: 'RegblockExporter',
+ hwif_report_file: Optional[TextIO]
+ ):
+ self.exp = exp
+
+ self.has_input_struct = False
+ self.has_output_struct = False
+
+ self.hwif_report_file = hwif_report_file
+
+ if not self.ds.reuse_hwif_typedefs:
+ self._gen_in_cls = InputStructGenerator_Hier
+ self._gen_out_cls = OutputStructGenerator_Hier
+ else:
+ self._gen_in_cls = InputStructGenerator_TypeScope
+ self._gen_out_cls = OutputStructGenerator_TypeScope
+
+ @property
+ def ds(self) -> 'DesignState':
+ return self.exp.ds
+
+ @property
+ def top_node(self) -> AddrmapNode:
+ return self.exp.ds.top_node
+
+
+ def get_extra_package_params(self) -> str:
+ lines = [""]
+
+ for param in self.top_node.inst.parameters:
+ value = param.get_value()
+ if isinstance(value, int):
+ lines.append(
+ f"localparam {param.name} = {SVInt(value)};"
+ )
+ elif isinstance(value, str):
+ lines.append(
+ f"localparam {param.name} = {value};"
+ )
+
+ return "\n".join(lines)
+
+
+ def get_package_contents(self) -> str:
+ """
+ If this hwif requires a package, generate the string
+ """
+ lines = [""]
+
+ gen_in = self._gen_in_cls(self)
+ structs_in = gen_in.get_struct(
+ self.top_node,
+ f"{self.top_node.inst_name}__in_t"
+ )
+ if structs_in is not None:
+ self.has_input_struct = True
+ lines.append(structs_in)
+ else:
+ self.has_input_struct = False
+
+ gen_out = self._gen_out_cls(self)
+ structs_out = gen_out.get_struct(
+ self.top_node,
+ f"{self.top_node.inst_name}__out_t"
+ )
+ if structs_out is not None:
+ self.has_output_struct = True
+ lines.append(structs_out)
+ else:
+ self.has_output_struct = False
+
+ gen_enum = EnumGenerator()
+ enums = gen_enum.get_enums(self.ds.user_enums)
+ if enums is not None:
+ lines.append(enums)
+
+ return "\n\n".join(lines)
+
+
+ @property
+ def port_declaration(self) -> str:
+ """
+ Returns the declaration string for all I/O ports in the hwif group
+ """
+
+ # Assume get_package_declaration() is always called prior to this
+ assert self.has_input_struct is not None
+ assert self.has_output_struct is not None
+
+ lines = []
+ if self.has_input_struct:
+ type_name = f"{self.top_node.inst_name}__in_t"
+ lines.append(f"input {self.ds.package_name}::{type_name} hwif_in")
+ if self.has_output_struct:
+ type_name = f"{self.top_node.inst_name}__out_t"
+ lines.append(f"output {self.ds.package_name}::{type_name} hwif_out")
+
+ return ",\n".join(lines)
+
+ #---------------------------------------------------------------------------
+ # hwif utility functions
+ #---------------------------------------------------------------------------
+ def has_value_input(self, obj: Union[FieldNode, SignalNode]) -> bool:
+ """
+ Returns True if the object infers an input wire in the hwif
+ """
+ if isinstance(obj, FieldNode):
+ return obj.is_hw_writable
+ elif isinstance(obj, SignalNode):
+ # Signals are implicitly always inputs
+ return True
+ else:
+ raise RuntimeError
+
+
+ def has_value_output(self, obj: FieldNode) -> bool:
+ """
+ Returns True if the object infers an output wire in the hwif
+ """
+ return obj.is_hw_readable
+
+
+ def get_input_identifier(
+ self,
+ obj: Union[FieldNode, SignalNode, PropertyReference],
+ width: Optional[int] = None,
+ ) -> Union[SVInt, str]:
+ """
+ Returns the identifier string that best represents the input object.
+
+ if obj is:
+ Field: the fields hw input value port
+ Signal: signal input value
+ Prop reference:
+ could be an implied hwclr/hwset/swwe/swwel/we/wel input
+
+ raises an exception if obj is invalid
+ """
+ if isinstance(obj, FieldNode):
+ next_value = obj.get_property('next')
+ if next_value is not None:
+ # 'next' property replaces the inferred input signal
+ return self.exp.dereferencer.get_value(next_value, width)
+ # Otherwise, use inferred
+ path = get_indexed_path(self.top_node, obj)
+ return "hwif_in." + path + ".next"
+ elif isinstance(obj, SignalNode):
+ if obj.get_path() in self.ds.out_of_hier_signals:
+ return kwf(obj.inst_name)
+ path = get_indexed_path(self.top_node, obj)
+ return "hwif_in." + path
+ elif isinstance(obj, PropertyReference):
+ assert isinstance(obj.node, FieldNode)
+ return self.get_implied_prop_input_identifier(obj.node, obj.name)
+
+ raise RuntimeError(f"Unhandled reference to: {obj}")
+
+ def get_external_rd_data(self, node: AddressableNode) -> str:
+ """
+ Returns the identifier string for an external component's rd_data signal
+ """
+ path = get_indexed_path(self.top_node, node)
+ return "hwif_in." + path + ".rd_data"
+
+ def get_external_rd_ack(self, node: AddressableNode) -> str:
+ """
+ Returns the identifier string for an external component's rd_ack signal
+ """
+ path = get_indexed_path(self.top_node, node)
+ return "hwif_in." + path + ".rd_ack"
+
+ def get_external_wr_ack(self, node: AddressableNode) -> str:
+ """
+ Returns the identifier string for an external component's wr_ack signal
+ """
+ path = get_indexed_path(self.top_node, node)
+ return "hwif_in." + path + ".wr_ack"
+
+ def get_implied_prop_input_identifier(self, field: FieldNode, prop: str) -> str:
+ assert prop in {
+ 'hwclr', 'hwset', 'swwe', 'swwel', 'we', 'wel',
+ 'incr', 'decr', 'incrvalue', 'decrvalue'
+ }
+ path = get_indexed_path(self.top_node, field)
+ return "hwif_in." + path + "." + prop
+
+
+ def get_output_identifier(self, obj: Union[FieldNode, PropertyReference]) -> str:
+ """
+ Returns the identifier string that best represents the output object.
+
+ if obj is:
+ Field: the fields hw output value port
+ Property ref: this is also part of the struct
+
+ raises an exception if obj is invalid
+ """
+ if isinstance(obj, FieldNode):
+ path = get_indexed_path(self.top_node, obj)
+ return "hwif_out." + path + ".value"
+ elif isinstance(obj, PropertyReference):
+ # TODO: this might be dead code.
+ # not sure when anything would call this function with a prop ref
+ # when dereferencer's get_value is more useful here
+ assert obj.node.get_property(obj.name)
+ assert isinstance(obj.node, (RegNode, FieldNode))
+ return self.get_implied_prop_output_identifier(obj.node, obj.name)
+
+ raise RuntimeError(f"Unhandled reference to: {obj}")
+
+
+ def get_implied_prop_output_identifier(self, node: Union[FieldNode, RegNode], prop: str) -> str:
+ if isinstance(node, FieldNode):
+ assert prop in {
+ "anded", "ored", "xored", "swmod", "swacc",
+ "incrthreshold", "decrthreshold", "overflow", "underflow",
+ "rd_swacc", "wr_swacc",
+ }
+ elif isinstance(node, RegNode):
+ assert prop in {
+ "intr", "halt",
+ }
+ path = get_indexed_path(self.top_node, node)
+ return "hwif_out." + path + "." + prop
diff --git a/src/peakrdl_regblock/hwif/generators.py b/src/peakrdl_regblock/hwif/generators.py
new file mode 100644
index 0000000..e53f0fe
--- /dev/null
+++ b/src/peakrdl_regblock/hwif/generators.py
@@ -0,0 +1,385 @@
+from typing import TYPE_CHECKING, Optional, List, Type
+
+from systemrdl.node import FieldNode, RegNode, AddrmapNode, MemNode
+from systemrdl.walker import WalkerAction
+
+from ..struct_generator import RDLFlatStructGenerator
+from ..identifier_filter import kw_filter as kwf
+from ..sv_int import SVInt
+from ..utils import clog2
+
+if TYPE_CHECKING:
+ from systemrdl.node import Node, SignalNode, AddressableNode, RegfileNode
+ from . import Hwif
+ from systemrdl.rdltypes import UserEnum
+
+class HWIFStructGenerator(RDLFlatStructGenerator):
+ def __init__(self, hwif: 'Hwif', hwif_name: str) -> None:
+ super().__init__()
+ self.hwif = hwif
+ self.top_node = hwif.top_node
+
+ self.hwif_report_stack = [hwif_name]
+
+ def push_struct(self, type_name: str, inst_name: str, array_dimensions: Optional[List[int]] = None, packed: bool = False) -> None: # type: ignore
+ super().push_struct(type_name, inst_name, array_dimensions, packed)
+
+ if array_dimensions:
+ array_suffix = "".join([f"[0:{dim-1}]" for dim in array_dimensions])
+ segment = inst_name + array_suffix
+ else:
+ segment = inst_name
+ self.hwif_report_stack.append(segment)
+
+ def pop_struct(self) -> None:
+ super().pop_struct()
+ self.hwif_report_stack.pop()
+
+ def add_member(self, name: str, width: int = 1, *, lsb: int = 0, signed: bool = False) -> None: # type: ignore # pylint: disable=arguments-differ
+ super().add_member(name, width, lsb=lsb, signed=signed)
+
+ if width > 1 or lsb != 0:
+ suffix = f"[{lsb+width-1}:{lsb}]"
+ else:
+ suffix = ""
+
+ path = ".".join(self.hwif_report_stack)
+ if self.hwif.hwif_report_file:
+ self.hwif.hwif_report_file.write(f"{path}.{name}{suffix}\n")
+
+#-------------------------------------------------------------------------------
+
+class InputStructGenerator_Hier(HWIFStructGenerator):
+ def __init__(self, hwif: 'Hwif') -> None:
+ super().__init__(hwif, "hwif_in")
+
+ def get_typdef_name(self, node:'Node', suffix: str = "") -> str:
+ base = node.get_rel_path(
+ self.top_node.parent,
+ hier_separator="__",
+ array_suffix="x",
+ empty_array_suffix="x"
+ )
+ return f'{base}{suffix}__in_t'
+
+ def enter_Signal(self, node: 'SignalNode') -> None:
+ # only emit the signal if design scanner detected it is actually being used
+ path = node.get_path()
+ if path in self.hwif.ds.in_hier_signal_paths:
+ self.add_member(kwf(node.inst_name), node.width)
+
+ def _add_external_block_members(self, node: 'AddressableNode') -> None:
+ self.add_member("rd_ack")
+ self.add_member("rd_data", self.hwif.ds.cpuif_data_width)
+ self.add_member("wr_ack")
+
+ def enter_Addrmap(self, node: 'AddrmapNode') -> Optional[WalkerAction]:
+ super().enter_Addrmap(node)
+ assert node.external
+ self._add_external_block_members(node)
+ return WalkerAction.SkipDescendants
+
+ def enter_Regfile(self, node: 'RegfileNode') -> Optional[WalkerAction]:
+ super().enter_Regfile(node)
+ if node.external:
+ self._add_external_block_members(node)
+ return WalkerAction.SkipDescendants
+ return WalkerAction.Continue
+
+ def enter_Mem(self, node: 'MemNode') -> Optional[WalkerAction]:
+ super().enter_Mem(node)
+ assert node.external
+ self._add_external_block_members(node)
+ return WalkerAction.SkipDescendants
+
+ def enter_Reg(self, node: 'RegNode') -> Optional[WalkerAction]:
+ super().enter_Reg(node)
+ if node.external:
+ width = min(self.hwif.ds.cpuif_data_width, node.get_property('regwidth'))
+ n_subwords = node.get_property("regwidth") // node.get_property("accesswidth")
+ if node.has_sw_readable:
+ self.add_member("rd_ack")
+ self.add_external_reg_rd_data(node, width, n_subwords)
+ if node.has_sw_writable:
+ self.add_member("wr_ack")
+ return WalkerAction.SkipDescendants
+
+ return WalkerAction.Continue
+
+ def add_external_reg_rd_data(self, node: 'RegNode', width: int, n_subwords: int) -> None:
+ if n_subwords == 1:
+ # External reg is 1 sub-word. Add a packed struct to represent it
+ type_name = self.get_typdef_name(node, "__fields")
+ self.push_struct(type_name, "rd_data", packed=True)
+ current_bit = width - 1
+ for field in reversed(list(node.fields())):
+ if not field.is_sw_readable:
+ continue
+ if field.high < current_bit:
+ # Add padding
+ self.add_member(
+ f"_reserved_{current_bit}_{field.high + 1}",
+ current_bit - field.high
+ )
+ self.add_member(
+ kwf(field.inst_name),
+ field.width
+ )
+ current_bit = field.low - 1
+
+ # Add end padding if needed
+ if current_bit != -1:
+ self.add_member(
+ f"_reserved_{current_bit}_0",
+ current_bit + 1
+ )
+ self.pop_struct()
+ else:
+ # Multiple sub-words. Cannot generate a struct
+ self.add_member("rd_data", width)
+
+ def enter_Field(self, node: 'FieldNode') -> None:
+ type_name = self.get_typdef_name(node)
+ self.push_struct(type_name, kwf(node.inst_name))
+
+ # Provide input to field's next value if it is writable by hw, and it
+ # was not overridden by the 'next' property
+ if node.is_hw_writable and node.get_property('next') is None:
+ # Get the field's LSB index (can be nonzero for fixed-point values)
+ fracwidth = node.get_property("fracwidth")
+ lsb = 0 if fracwidth is None else -fracwidth
+
+ # get the signedness of the field
+ signed = node.get_property("is_signed")
+
+ self.add_member("next", node.width, lsb=lsb, signed=signed)
+
+ # Generate implied inputs
+ for prop_name in ["we", "wel", "swwe", "swwel", "hwclr", "hwset"]:
+ # if property is boolean and true, implies a corresponding input signal on the hwif
+ if node.get_property(prop_name) is True:
+ self.add_member(prop_name)
+
+ # Generate any implied counter inputs
+ if node.is_up_counter:
+ if not node.get_property('incr'):
+ # User did not provide their own incr component reference.
+ # Imply an input
+ self.add_member('incr')
+
+ width = node.get_property('incrwidth')
+ if width:
+ # Implies a corresponding incrvalue input
+ self.add_member('incrvalue', width)
+
+ if node.is_down_counter:
+ if not node.get_property('decr'):
+ # User did not provide their own decr component reference.
+ # Imply an input
+ self.add_member('decr')
+
+ width = node.get_property('decrwidth')
+ if width:
+ # Implies a corresponding decrvalue input
+ self.add_member('decrvalue', width)
+
+ def exit_Field(self, node: 'FieldNode') -> None:
+ self.pop_struct()
+
+
+class OutputStructGenerator_Hier(HWIFStructGenerator):
+ def __init__(self, hwif: 'Hwif') -> None:
+ super().__init__(hwif, "hwif_out")
+
+ def get_typdef_name(self, node:'Node', suffix: str = "") -> str:
+ base = node.get_rel_path(
+ self.top_node.parent,
+ hier_separator="__",
+ array_suffix="x",
+ empty_array_suffix="x"
+ )
+ return f'{base}{suffix}__out_t'
+
+ def _add_external_block_members(self, node: 'AddressableNode') -> None:
+ self.add_member("req")
+ self.add_member("addr", clog2(node.size))
+ self.add_member("req_is_wr")
+ self.add_member("wr_data", self.hwif.ds.cpuif_data_width)
+ self.add_member("wr_biten", self.hwif.ds.cpuif_data_width)
+
+ def enter_Addrmap(self, node: 'AddrmapNode') -> Optional[WalkerAction]:
+ super().enter_Addrmap(node)
+ assert node.external
+ self._add_external_block_members(node)
+ return WalkerAction.SkipDescendants
+
+ def enter_Regfile(self, node: 'RegfileNode') -> Optional[WalkerAction]:
+ super().enter_Regfile(node)
+ if node.external:
+ self._add_external_block_members(node)
+ return WalkerAction.SkipDescendants
+ return WalkerAction.Continue
+
+ def enter_Mem(self, node: 'MemNode') -> Optional[WalkerAction]:
+ super().enter_Mem(node)
+ assert node.external
+ self._add_external_block_members(node)
+ return WalkerAction.SkipDescendants
+
+ def enter_Reg(self, node: 'RegNode') -> Optional[WalkerAction]:
+ super().enter_Reg(node)
+ if node.external:
+ width = min(self.hwif.ds.cpuif_data_width, node.get_property('regwidth'))
+ n_subwords = node.get_property("regwidth") // node.get_property("accesswidth")
+ self.add_member("req", n_subwords)
+ self.add_member("req_is_wr")
+ if node.has_sw_writable:
+ self.add_external_reg_wr_data("wr_data", node, width, n_subwords)
+ self.add_external_reg_wr_data("wr_biten", node, width, n_subwords)
+ return WalkerAction.SkipDescendants
+
+ return WalkerAction.Continue
+
+ def add_external_reg_wr_data(self, name: str, node: 'RegNode', width: int, n_subwords: int) -> None:
+ if n_subwords == 1:
+ # External reg is 1 sub-word. Add a packed struct to represent it
+ type_name = self.get_typdef_name(node, "__fields")
+ self.push_struct(type_name, name, packed=True)
+ current_bit = width - 1
+ for field in reversed(list(node.fields())):
+ if not field.is_sw_writable:
+ continue
+ if field.high < current_bit:
+ # Add padding
+ self.add_member(
+ f"_reserved_{current_bit}_{field.high + 1}",
+ current_bit - field.high
+ )
+ self.add_member(
+ kwf(field.inst_name),
+ field.width
+ )
+ current_bit = field.low - 1
+
+ # Add end padding if needed
+ if current_bit != -1:
+ self.add_member(
+ f"_reserved_{current_bit}_0",
+ current_bit + 1
+ )
+ self.pop_struct()
+ else:
+ # Multiple sub-words. Cannot generate a struct
+ self.add_member(name, width)
+
+ def enter_Field(self, node: 'FieldNode') -> None:
+ type_name = self.get_typdef_name(node)
+ self.push_struct(type_name, kwf(node.inst_name))
+
+ # Expose field's value if it is readable by hw
+ if node.is_hw_readable:
+ # Get the node's LSB index (can be nonzero for fixed-point values)
+ fracwidth = node.get_property("fracwidth")
+ lsb = 0 if fracwidth is None else -fracwidth
+
+ # get the signedness of the field
+ signed = node.get_property("is_signed")
+
+ self.add_member("value", node.width, lsb=lsb, signed=signed)
+
+ # Generate output bit signals enabled via property
+ for prop_name in ["anded", "ored", "xored", "swmod", "swacc", "overflow", "underflow", "rd_swacc", "wr_swacc"]:
+ if node.get_property(prop_name):
+ self.add_member(prop_name)
+
+ if node.get_property('incrthreshold') is not False: # (explicitly not False. Not 0)
+ self.add_member('incrthreshold')
+ if node.get_property('decrthreshold') is not False: # (explicitly not False. Not 0)
+ self.add_member('decrthreshold')
+
+ def exit_Field(self, node: 'FieldNode') -> None:
+ self.pop_struct()
+
+ def exit_Reg(self, node: 'RegNode') -> None:
+ if node.is_interrupt_reg:
+ self.add_member('intr')
+ if node.is_halt_reg:
+ self.add_member('halt')
+ super().exit_Reg(node)
+
+#-------------------------------------------------------------------------------
+class InputStructGenerator_TypeScope(InputStructGenerator_Hier):
+ def get_typdef_name(self, node:'Node', suffix: str = "") -> str:
+ scope_path = node.get_global_type_name("__")
+ if scope_path is None:
+ # Unable to determine a reusable type name. Fall back to hierarchical path
+ # Add prefix to prevent collision when mixing namespace methods
+ scope_path = "xtern__" + super().get_typdef_name(node)
+
+ if node.external:
+ # Node generates alternate external signals
+ extra_suffix = "__external"
+ else:
+ extra_suffix = ""
+
+ return f'{scope_path}{extra_suffix}{suffix}__in_t'
+
+class OutputStructGenerator_TypeScope(OutputStructGenerator_Hier):
+ def get_typdef_name(self, node:'Node', suffix: str = "") -> str:
+ scope_path = node.get_global_type_name("__")
+ if scope_path is None:
+ # Unable to determine a reusable type name. Fall back to hierarchical path
+ # Add prefix to prevent collision when mixing namespace methods
+ scope_path = "xtern__" + super().get_typdef_name(node)
+
+ if node.external:
+ # Node generates alternate external signals
+ extra_suffix = "__external"
+ else:
+ extra_suffix = ""
+
+ return f'{scope_path}{extra_suffix}{suffix}__out_t'
+
+#-------------------------------------------------------------------------------
+class EnumGenerator:
+ """
+ Generator for user-defined enum definitions
+ """
+
+ def get_enums(self, user_enums: List[Type['UserEnum']]) -> Optional[str]:
+ if not user_enums:
+ return None
+
+ lines = []
+ for user_enum in user_enums:
+ lines.append(self._enum_typedef(user_enum))
+
+ return '\n\n'.join(lines)
+
+ @staticmethod
+ def _get_prefix(user_enum: Type['UserEnum']) -> str:
+ scope = user_enum.get_scope_path("__")
+ if scope:
+ return f"{scope}__{user_enum.type_name}"
+ else:
+ return user_enum.type_name
+
+ def _enum_typedef(self, user_enum: Type['UserEnum']) -> str:
+ prefix = self._get_prefix(user_enum)
+
+ lines = []
+ max_value = 1
+ for enum_member in user_enum:
+ lines.append(f" {prefix}__{enum_member.name} = {SVInt(enum_member.value)}")
+ max_value = max(max_value, enum_member.value)
+
+ if max_value.bit_length() == 1:
+ datatype = "logic"
+ else:
+ datatype = f"logic [{max_value.bit_length() - 1}:0]"
+
+ return (
+ f"typedef enum {datatype} {{\n"
+ + ",\n".join(lines)
+ + f"\n}} {prefix}_e;"
+ )
diff --git a/src/peakrdl_regblock/identifier_filter.py b/src/peakrdl_regblock/identifier_filter.py
new file mode 100644
index 0000000..f701a28
--- /dev/null
+++ b/src/peakrdl_regblock/identifier_filter.py
@@ -0,0 +1,52 @@
+
+# All SystemVerilog 2017 keywords
+SV_KEYWORDS = {
+ 'accept_on', 'alias', 'always', 'always_comb', 'always_ff', 'always_latch',
+ 'and', 'assert', 'assign', 'assume', 'automatic', 'before', 'begin', 'bind',
+ 'bins', 'binsof', 'bit', 'break', 'buf', 'bufif0', 'bufif1', 'byte', 'case',
+ 'casex', 'casez', 'cell', 'chandle', 'checker', 'class', 'clocking', 'cmos',
+ 'config', 'const', 'constraint', 'context', 'continue', 'cover', 'covergroup',
+ 'coverpoint', 'cross', 'deassign', 'default', 'defparam', 'design', 'disable',
+ 'dist', 'do', 'edge', 'else', 'end', 'endcase', 'endchecker', 'endclass',
+ 'endclocking', 'endconfig', 'endfunction', 'endgenerate', 'endgroup',
+ 'endinterface', 'endmodule', 'endpackage', 'endprimitive', 'endprogram',
+ 'endproperty', 'endspecify', 'endsequence', 'endtable', 'endtask', 'enum',
+ 'event', 'eventually', 'expect', 'export', 'extends', 'extern', 'final',
+ 'first_match', 'for', 'force', 'foreach', 'forever', 'fork', 'forkjoin',
+ 'function', 'generate', 'genvar', 'global', 'highz0', 'highz1', 'if', 'iff',
+ 'ifnone', 'ignore_bins', 'illegal_bins', 'implements', 'implies', 'import',
+ 'incdir', 'include', 'initial', 'inout', 'input', 'inside', 'instance',
+ 'int', 'integer', 'interconnect', 'interface', 'intersect', 'join',
+ 'join_any', 'join_none', 'large', 'let', 'liblist', 'library', 'local',
+ 'localparam', 'logic', 'longint', 'macromodule', 'matches', 'medium',
+ 'modport', 'module', 'nand', 'negedge', 'nettype', 'new', 'nexttime', 'nmos',
+ 'nor', 'noshowcancelled', 'not', 'notif0', 'notif1', 'null', 'or', 'output',
+ 'package', 'packed', 'parameter', 'pmos', 'posedge', 'primitive', 'priority',
+ 'program', 'property', 'protected', 'pull0', 'pull1', 'pulldown', 'pullup',
+ 'pulsestyle_ondetect', 'pulsestyle_onevent', 'pure', 'rand', 'randc',
+ 'randcase', 'randsequence', 'rcmos', 'real', 'realtime', 'ref', 'reg',
+ 'reject_on', 'release', 'repeat', 'restrict', 'return', 'rnmos', 'rpmos',
+ 'rtran', 'rtranif0', 'rtranif1', 's_always', 's_eventually', 's_nexttime',
+ 's_until', 's_until_with', 'scalared', 'sequence', 'shortint', 'shortreal',
+ 'showcancelled', 'signed', 'small', 'soft', 'solve', 'specify', 'specparam',
+ 'static', 'string', 'strong', 'strong0', 'strong1', 'struct', 'super',
+ 'supply0', 'supply1', 'sync_accept_on', 'sync_reject_on', 'table', 'tagged',
+ 'task', 'this', 'throughout', 'time', 'timeprecision', 'timeunit', 'tran',
+ 'tranif0', 'tranif1', 'tri', 'tri0', 'tri1', 'triand', 'trior', 'trireg',
+ 'type', 'typedef', 'union', 'unique', 'unique0', 'unsigned', 'until',
+ 'until_with', 'untyped', 'use', 'uwire', 'var', 'vectored', 'virtual', 'void',
+ 'wait', 'wait_order', 'wand', 'weak', 'weak0', 'weak1', 'while', 'wildcard',
+ 'wire', 'with', 'within', 'wor', 'xnor', 'xor'
+}
+
+
+def kw_filter(s: str) -> str:
+ """
+ Make all user identifiers 'safe' and ensure they do not collide with
+ SystemVerilog keywords.
+
+ If an SV keyword is encountered, add an underscore suffix
+ """
+ if s in SV_KEYWORDS:
+ s += "_"
+ return s
diff --git a/src/peakrdl_regblock/module_tmpl.sv b/src/peakrdl_regblock/module_tmpl.sv
new file mode 100644
index 0000000..bad1c21
--- /dev/null
+++ b/src/peakrdl_regblock/module_tmpl.sv
@@ -0,0 +1,293 @@
+// Generated by PeakRDL-regblock - A free and open-source SystemVerilog generator
+// https://github.com/SystemRDL/PeakRDL-regblock
+
+module {{ds.module_name}}
+ {%- if cpuif.parameters %} #(
+ {{",\n ".join(cpuif.parameters)}}
+ ) {%- endif %} (
+ input wire clk,
+ input wire {{default_resetsignal_name}},
+
+ {%- for signal in ds.out_of_hier_signals.values() %}
+ {%- if signal.width == 1 %}
+ input wire {{kwf(signal.inst_name)}},
+ {%- else %}
+ input wire [{{signal.width-1}}:0] {{kwf(signal.inst_name)}},
+ {%- endif %}
+ {%- endfor %}
+
+ {%- if ds.has_paritycheck %}
+
+ output logic parity_error,
+ {%- endif %}
+
+ {{cpuif.port_declaration|indent(8)}}
+ {%- if hwif.has_input_struct or hwif.has_output_struct %},{% endif %}
+
+ {{hwif.port_declaration|indent(8)}}
+ );
+
+ //--------------------------------------------------------------------------
+ // CPU Bus interface logic
+ //--------------------------------------------------------------------------
+ logic cpuif_req;
+ logic cpuif_req_is_wr;
+ logic [{{cpuif.addr_width-1}}:0] cpuif_addr;
+ logic [{{cpuif.data_width-1}}:0] cpuif_wr_data;
+ logic [{{cpuif.data_width-1}}:0] cpuif_wr_biten;
+ logic cpuif_req_stall_wr;
+ logic cpuif_req_stall_rd;
+
+ logic cpuif_rd_ack;
+ logic cpuif_rd_err;
+ logic [{{cpuif.data_width-1}}:0] cpuif_rd_data;
+
+ logic cpuif_wr_ack;
+ logic cpuif_wr_err;
+
+ {{cpuif.get_implementation()|indent}}
+
+ logic cpuif_req_masked;
+{%- if ds.has_external_addressable %}
+ logic external_req;
+ logic external_pending;
+ logic external_wr_ack;
+ logic external_rd_ack;
+ always_ff {{get_always_ff_event(cpuif.reset)}} begin
+ if({{get_resetsignal(cpuif.reset)}}) begin
+ external_pending <= '0;
+ end else begin
+ if(external_req & ~external_wr_ack & ~external_rd_ack) external_pending <= '1;
+ else if(external_wr_ack | external_rd_ack) external_pending <= '0;
+ `ifndef SYNTHESIS
+ assert_bad_ext_wr_ack: assert(!external_wr_ack || (external_pending | external_req))
+ else $error("An external wr_ack strobe was asserted when no external request was active");
+ assert_bad_ext_rd_ack: assert(!external_rd_ack || (external_pending | external_req))
+ else $error("An external rd_ack strobe was asserted when no external request was active");
+ `endif
+ end
+ end
+{%- endif %}
+{% if ds.min_read_latency == ds.min_write_latency %}
+ // Read & write latencies are balanced. Stalls not required
+ {%- if ds.has_external_addressable %}
+ // except if external
+ assign cpuif_req_stall_rd = external_pending;
+ assign cpuif_req_stall_wr = external_pending;
+ {%- else %}
+ assign cpuif_req_stall_rd = '0;
+ assign cpuif_req_stall_wr = '0;
+ {%- endif %}
+{%- elif ds.min_read_latency > ds.min_write_latency %}
+ // Read latency > write latency. May need to delay next write that follows a read
+ logic [{{ds.min_read_latency - ds.min_write_latency - 1}}:0] cpuif_req_stall_sr;
+ always_ff {{get_always_ff_event(cpuif.reset)}} begin
+ if({{get_resetsignal(cpuif.reset)}}) begin
+ cpuif_req_stall_sr <= '0;
+ end else if(cpuif_req && !cpuif_req_is_wr) begin
+ cpuif_req_stall_sr <= '1;
+ end else begin
+ cpuif_req_stall_sr <= (cpuif_req_stall_sr >> 'd1);
+ end
+ end
+ {%- if ds.has_external_addressable %}
+ assign cpuif_req_stall_rd = external_pending;
+ assign cpuif_req_stall_wr = cpuif_req_stall_sr[0] | external_pending;
+ {%- else %}
+ assign cpuif_req_stall_rd = '0;
+ assign cpuif_req_stall_wr = cpuif_req_stall_sr[0];
+ {%- endif %}
+{%- else %}
+ // Write latency > read latency. May need to delay next read that follows a write
+ logic [{{ds.min_write_latency - ds.min_read_latency - 1}}:0] cpuif_req_stall_sr;
+ always_ff {{get_always_ff_event(cpuif.reset)}} begin
+ if({{get_resetsignal(cpuif.reset)}}) begin
+ cpuif_req_stall_sr <= '0;
+ end else if(cpuif_req && cpuif_req_is_wr) begin
+ cpuif_req_stall_sr <= '1;
+ end else begin
+ cpuif_req_stall_sr <= (cpuif_req_stall_sr >> 'd1);
+ end
+ end
+ {%- if ds.has_external_addressable %}
+ assign cpuif_req_stall_rd = cpuif_req_stall_sr[0] | external_pending;
+ assign cpuif_req_stall_wr = external_pending;
+ {%- else %}
+ assign cpuif_req_stall_rd = cpuif_req_stall_sr[0];
+ assign cpuif_req_stall_wr = '0;
+ {%- endif %}
+{%- endif %}
+ assign cpuif_req_masked = cpuif_req
+ & !(!cpuif_req_is_wr & cpuif_req_stall_rd)
+ & !(cpuif_req_is_wr & cpuif_req_stall_wr);
+
+ //--------------------------------------------------------------------------
+ // Address Decode
+ //--------------------------------------------------------------------------
+ {{address_decode.get_strobe_struct()|indent}}
+ decoded_reg_strb_t decoded_reg_strb;
+{%- if ds.has_external_addressable %}
+ logic decoded_strb_is_external;
+{% endif %}
+{%- if ds.has_external_block %}
+ logic [{{cpuif.addr_width-1}}:0] decoded_addr;
+{% endif %}
+ logic decoded_req;
+ logic decoded_req_is_wr;
+ logic [{{cpuif.data_width-1}}:0] decoded_wr_data;
+ logic [{{cpuif.data_width-1}}:0] decoded_wr_biten;
+
+ always_comb begin
+ {%- if ds.has_external_addressable %}
+ automatic logic is_external;
+ is_external = '0;
+ {%- endif %}
+ {{address_decode.get_implementation()|indent(8)}}
+ {%- if ds.has_external_addressable %}
+ decoded_strb_is_external = is_external;
+ external_req = is_external;
+ {%- endif %}
+ end
+
+ // Pass down signals to next stage
+{%- if ds.has_external_block %}
+ assign decoded_addr = cpuif_addr;
+{% endif %}
+ assign decoded_req = cpuif_req_masked;
+ assign decoded_req_is_wr = cpuif_req_is_wr;
+ assign decoded_wr_data = cpuif_wr_data;
+ assign decoded_wr_biten = cpuif_wr_biten;
+{% if ds.has_writable_msb0_fields %}
+ // bitswap for use by fields with msb0 ordering
+ logic [{{cpuif.data_width-1}}:0] decoded_wr_data_bswap;
+ logic [{{cpuif.data_width-1}}:0] decoded_wr_biten_bswap;
+ assign decoded_wr_data_bswap = {<<{decoded_wr_data}};
+ assign decoded_wr_biten_bswap = {<<{decoded_wr_biten}};
+{%- endif %}
+
+{%- if ds.has_buffered_write_regs %}
+
+ //--------------------------------------------------------------------------
+ // Write double-buffers
+ //--------------------------------------------------------------------------
+ {{write_buffering.get_storage_struct()|indent}}
+
+ {{write_buffering.get_implementation()|indent}}
+{%- endif %}
+ //--------------------------------------------------------------------------
+ // Field logic
+ //--------------------------------------------------------------------------
+ {{field_logic.get_combo_struct()|indent}}
+
+ {{field_logic.get_storage_struct()|indent}}
+
+ {{field_logic.get_implementation()|indent}}
+
+{%- if ds.has_paritycheck %}
+
+ //--------------------------------------------------------------------------
+ // Parity Error
+ //--------------------------------------------------------------------------
+ always_ff {{get_always_ff_event(cpuif.reset)}} begin
+ if({{get_resetsignal(cpuif.reset)}}) begin
+ parity_error <= '0;
+ end else begin
+ automatic logic err;
+ err = '0;
+ {{parity.get_implementation()|indent(12)}}
+ parity_error <= err;
+ end
+ end
+{%- endif %}
+
+{%- if ds.has_buffered_read_regs %}
+
+ //--------------------------------------------------------------------------
+ // Read double-buffers
+ //--------------------------------------------------------------------------
+ {{read_buffering.get_storage_struct()|indent}}
+
+ {{read_buffering.get_implementation()|indent}}
+{%- endif %}
+
+ //--------------------------------------------------------------------------
+ // Write response
+ //--------------------------------------------------------------------------
+{%- if ds.has_external_addressable %}
+ always_comb begin
+ automatic logic wr_ack;
+ wr_ack = '0;
+ {{ext_write_acks.get_implementation()|indent(8)}}
+ external_wr_ack = wr_ack;
+ end
+ assign cpuif_wr_ack = external_wr_ack | (decoded_req & decoded_req_is_wr & ~decoded_strb_is_external);
+{%- else %}
+ assign cpuif_wr_ack = decoded_req & decoded_req_is_wr;
+{%- endif %}
+ // Writes are always granted with no error response
+ assign cpuif_wr_err = '0;
+
+ //--------------------------------------------------------------------------
+ // Readback
+ //--------------------------------------------------------------------------
+{%- if ds.has_external_addressable %}
+ logic readback_external_rd_ack_c;
+ always_comb begin
+ automatic logic rd_ack;
+ rd_ack = '0;
+ {{ext_read_acks.get_implementation()|indent(8)}}
+ readback_external_rd_ack_c = rd_ack;
+ end
+
+ logic readback_external_rd_ack;
+ {%- if ds.retime_read_fanin %}
+ always_ff {{get_always_ff_event(cpuif.reset)}} begin
+ if({{get_resetsignal(cpuif.reset)}}) begin
+ readback_external_rd_ack <= '0;
+ end else begin
+ readback_external_rd_ack <= readback_external_rd_ack_c;
+ end
+ end
+
+ {%- else %}
+
+ assign readback_external_rd_ack = readback_external_rd_ack_c;
+ {%- endif %}
+{%- endif %}
+
+ logic readback_err;
+ logic readback_done;
+ logic [{{cpuif.data_width-1}}:0] readback_data;
+{{readback_implementation|indent}}
+{% if ds.retime_read_response %}
+ always_ff {{get_always_ff_event(cpuif.reset)}} begin
+ if({{get_resetsignal(cpuif.reset)}}) begin
+ cpuif_rd_ack <= '0;
+ cpuif_rd_data <= '0;
+ cpuif_rd_err <= '0;
+ {%- if ds.has_external_addressable %}
+ external_rd_ack <= '0;
+ {%- endif %}
+ end else begin
+ {%- if ds.has_external_addressable %}
+ external_rd_ack <= readback_external_rd_ack;
+ cpuif_rd_ack <= readback_done | readback_external_rd_ack;
+ {%- else %}
+ cpuif_rd_ack <= readback_done;
+ {%- endif %}
+ cpuif_rd_data <= readback_data;
+ cpuif_rd_err <= readback_err;
+ end
+ end
+{% else %}
+ {%- if ds.has_external_addressable %}
+ assign external_rd_ack = readback_external_rd_ack;
+ assign cpuif_rd_ack = readback_done | readback_external_rd_ack;
+ {%- else %}
+ assign cpuif_rd_ack = readback_done;
+ {%- endif %}
+ assign cpuif_rd_data = readback_data;
+ assign cpuif_rd_err = readback_err;
+{%- endif %}
+endmodule
+{# (eof newline anchor) #}
diff --git a/src/peakrdl_regblock/package_tmpl.sv b/src/peakrdl_regblock/package_tmpl.sv
new file mode 100644
index 0000000..b665495
--- /dev/null
+++ b/src/peakrdl_regblock/package_tmpl.sv
@@ -0,0 +1,14 @@
+// Generated by PeakRDL-regblock - A free and open-source SystemVerilog generator
+// https://github.com/SystemRDL/PeakRDL-regblock
+
+package {{ds.package_name}};
+
+ localparam {{ds.module_name.upper()}}_DATA_WIDTH = {{ds.cpuif_data_width}};
+ localparam {{ds.module_name.upper()}}_MIN_ADDR_WIDTH = {{ds.addr_width}};
+ localparam {{ds.module_name.upper()}}_SIZE = {{SVInt(ds.top_node.size)}};
+
+ {{-hwif.get_extra_package_params()|indent}}
+
+ {{-hwif.get_package_contents()|indent}}
+endpackage
+{# (eof newline anchor) #}
diff --git a/src/peakrdl_regblock/parity.py b/src/peakrdl_regblock/parity.py
new file mode 100644
index 0000000..980a2c7
--- /dev/null
+++ b/src/peakrdl_regblock/parity.py
@@ -0,0 +1,34 @@
+from typing import TYPE_CHECKING
+
+from systemrdl.walker import WalkerAction
+
+
+from .forloop_generator import RDLForLoopGenerator
+
+if TYPE_CHECKING:
+ from .exporter import RegblockExporter
+ from systemrdl.node import FieldNode, AddressableNode
+
+
+class ParityErrorReduceGenerator(RDLForLoopGenerator):
+ def __init__(self, exp: 'RegblockExporter') -> None:
+ super().__init__()
+ self.exp = exp
+
+ def get_implementation(self) -> str:
+ content = self.get_content(self.exp.ds.top_node)
+ if content is None:
+ return ""
+ return content
+
+ def enter_AddressableComponent(self, node: 'AddressableNode') -> WalkerAction:
+ super().enter_AddressableComponent(node)
+ if node.external:
+ return WalkerAction.SkipDescendants
+ return WalkerAction.Continue
+
+ def enter_Field(self, node: 'FieldNode') -> None:
+ if node.get_property('paritycheck') and node.implements_storage:
+ self.add_content(
+ f"err |= {self.exp.field_logic.get_parity_error_identifier(node)};"
+ )
diff --git a/src/peakrdl_regblock/read_buffering/__init__.py b/src/peakrdl_regblock/read_buffering/__init__.py
new file mode 100644
index 0000000..65ccc94
--- /dev/null
+++ b/src/peakrdl_regblock/read_buffering/__init__.py
@@ -0,0 +1,59 @@
+from typing import TYPE_CHECKING, Union
+
+from systemrdl.node import AddrmapNode, RegNode, SignalNode
+
+from .storage_generator import RBufStorageStructGenerator
+from .implementation_generator import RBufLogicGenerator
+from ..utils import get_indexed_path
+from ..sv_int import SVInt
+
+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.ds.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 = trigger.get_property('regwidth')
+ accesswidth = trigger.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 str(s)
+ else:
+ return f"~{s}"
+ else:
+ # Trigger is a field or propref bit
+ return str(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..ea36d8b
--- /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/__init__.py b/src/peakrdl_regblock/readback/__init__.py
new file mode 100644
index 0000000..dafb1e0
--- /dev/null
+++ b/src/peakrdl_regblock/readback/__init__.py
@@ -0,0 +1,72 @@
+from typing import TYPE_CHECKING
+import math
+
+from .generators import ReadbackAssignmentGenerator
+
+if TYPE_CHECKING:
+ from ..exporter import RegblockExporter, DesignState
+ from systemrdl.node import AddrmapNode
+
+class Readback:
+ def __init__(self, exp:'RegblockExporter'):
+ self.exp = exp
+
+ @property
+ def ds(self) -> 'DesignState':
+ return self.exp.ds
+
+ @property
+ def top_node(self) -> 'AddrmapNode':
+ return self.exp.ds.top_node
+
+ def get_implementation(self) -> str:
+ gen = ReadbackAssignmentGenerator(self.exp)
+ array_assignments = gen.get_content(self.top_node)
+ array_size = gen.current_offset
+
+ # Enabling the fanin stage doesnt make sense if readback fanin is
+ # small. This also avoids pesky corner cases
+ if array_size < 4:
+ self.ds.retime_read_fanin = False
+
+ context = {
+ "array_assignments" : array_assignments,
+ "array_size" : array_size,
+ 'get_always_ff_event': self.exp.dereferencer.get_always_ff_event,
+ 'get_resetsignal': self.exp.dereferencer.get_resetsignal,
+ "cpuif": self.exp.cpuif,
+ "ds": self.ds,
+ }
+
+ if self.ds.retime_read_fanin:
+ # If adding a fanin pipeline stage, goal is to try to
+ # split the fanin path in the middle so that fanin into the stage
+ # and the following are roughly balanced.
+ fanin_target = math.sqrt(array_size)
+
+ # Size of fanin group to consume per fanin element
+ fanin_stride = math.floor(fanin_target)
+
+ # Number of array elements to reduce to.
+ # Round up to an extra element in case there is some residual
+ fanin_array_size = math.ceil(array_size / fanin_stride)
+
+ # leftovers are handled in an extra array element
+ fanin_residual_stride = array_size % fanin_stride
+
+ if fanin_residual_stride != 0:
+ # If there is a partial fanin element, reduce the number of
+ # loops performed in the bulk fanin stage
+ fanin_loop_iter = fanin_array_size - 1
+ else:
+ fanin_loop_iter = fanin_array_size
+
+ context['fanin_stride'] = fanin_stride
+ context['fanin_array_size'] = fanin_array_size
+ context['fanin_residual_stride'] = fanin_residual_stride
+ context['fanin_loop_iter'] = fanin_loop_iter
+
+ template = self.exp.jj_env.get_template(
+ "readback/templates/readback.sv"
+ )
+ return template.render(context)
diff --git a/src/peakrdl_regblock/readback/generators.py b/src/peakrdl_regblock/readback/generators.py
new file mode 100644
index 0000000..87f2969
--- /dev/null
+++ b/src/peakrdl_regblock/readback/generators.py
@@ -0,0 +1,381 @@
+from typing import TYPE_CHECKING, List
+
+from systemrdl.node import RegNode, AddressableNode
+from systemrdl.walker import WalkerAction
+
+from ..forloop_generator import RDLForLoopGenerator, LoopBody
+
+from ..utils import do_bitswap, do_slice
+
+if TYPE_CHECKING:
+ from ..exporter import RegblockExporter
+
+class ReadbackLoopBody(LoopBody):
+ def __init__(self, dim: int, iterator: str, i_type: str) -> None:
+ super().__init__(dim, iterator, i_type)
+ self.n_regs = 0
+
+ def __str__(self) -> str:
+ # replace $i#sz token when stringifying
+ s = super().__str__()
+ token = f"${self.iterator}sz"
+ s = s.replace(token, str(self.n_regs))
+ return s
+
+class ReadbackAssignmentGenerator(RDLForLoopGenerator):
+ i_type = "genvar"
+ loop_body_cls = ReadbackLoopBody
+
+ def __init__(self, exp:'RegblockExporter') -> None:
+ super().__init__()
+ self.exp = exp
+
+ # The readback array collects all possible readback values into a flat
+ # array. The array width is equal to the CPUIF bus width. Each entry in
+ # the array represents an aligned read access.
+ self.current_offset = 0
+ self.start_offset_stack = [] # type: List[int]
+ self.dim_stack = [] # type: List[int]
+
+ @property
+ def current_offset_str(self) -> str:
+ """
+ Derive a string that represents the current offset being assigned.
+ This consists of:
+ - The current integer offset
+ - multiplied index of any enclosing loop
+
+ The integer offset from "current_offset" is static and is monotonically
+ incremented as more register assignments are processed.
+
+ The component of the offset from loops is added by multiplying the current
+ loop index by the loop size.
+ Since the loop's size is not known at this time, it is emitted as a
+ placeholder token like: $i0sz, $i1sz, $i2sz, etc
+ These tokens can be replaced once the loop body has been completed and the
+ size of its contents is known.
+ """
+ offset_parts = []
+ for i in range(self._loop_level):
+ offset_parts.append(f"i{i} * $i{i}sz")
+ offset_parts.append(str(self.current_offset))
+ return " + ".join(offset_parts)
+
+ def push_loop(self, dim: int) -> None:
+ super().push_loop(dim)
+ self.start_offset_stack.append(self.current_offset)
+ self.dim_stack.append(dim)
+
+ def pop_loop(self) -> None:
+ start_offset = self.start_offset_stack.pop()
+ dim = self.dim_stack.pop()
+
+ # Number of registers enclosed in this loop
+ n_regs = self.current_offset - start_offset
+ self.current_loop.n_regs = n_regs # type: ignore
+
+ super().pop_loop()
+
+ # Advance current scope's offset to account for loop's contents
+ self.current_offset = start_offset + n_regs * dim
+
+
+ def enter_AddressableComponent(self, node: 'AddressableNode') -> WalkerAction:
+ super().enter_AddressableComponent(node)
+
+ if node.external and not isinstance(node, RegNode):
+ # External block
+ strb = self.exp.hwif.get_external_rd_ack(node)
+ data = self.exp.hwif.get_external_rd_data(node)
+ self.add_content(f"assign readback_array[{self.current_offset_str}] = {strb} ? {data} : '0;")
+ self.current_offset += 1
+ return WalkerAction.SkipDescendants
+
+ return WalkerAction.Continue
+
+ def enter_Reg(self, node: RegNode) -> WalkerAction:
+ if not node.has_sw_readable:
+ return WalkerAction.SkipDescendants
+
+ if node.external:
+ self.process_external_reg(node)
+ return WalkerAction.SkipDescendants
+
+ accesswidth = node.get_property('accesswidth')
+ regwidth = node.get_property('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)
+
+ return WalkerAction.SkipDescendants
+
+ def process_external_reg(self, node: RegNode) -> None:
+ strb = self.exp.hwif.get_external_rd_ack(node)
+ data = self.exp.hwif.get_external_rd_data(node)
+ regwidth = node.get_property('regwidth')
+ if regwidth < self.exp.cpuif.data_width:
+ self.add_content(f"assign readback_array[{self.current_offset_str}][{self.exp.cpuif.data_width-1}:{regwidth}] = '0;")
+ self.add_content(f"assign readback_array[{self.current_offset_str}][{regwidth-1}:0] = {strb} ? {data} : '0;")
+ else:
+ self.add_content(f"assign readback_array[{self.current_offset_str}] = {strb} ? {data} : '0;")
+
+ self.current_offset += 1
+
+ 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
+ for field in node.fields():
+ if not field.is_sw_readable:
+ continue
+
+ # insert reserved assignment before this field if needed
+ 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 = do_bitswap(value)
+
+ self.add_content(f"assign readback_array[{self.current_offset_str}][{field.high}:{field.low}] = {rd_strb} ? {value} : '0;")
+
+ current_bit = field.high + 1
+
+ # Insert final reserved assignment if needed
+ bus_width = self.exp.cpuif.data_width
+ if current_bit < bus_width:
+ self.add_content(f"assign readback_array[{self.current_offset_str}][{bus_width-1}:{current_bit}] = '0;")
+
+ self.current_offset += 1
+
+
+ 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 = do_bitswap(do_slice(self.exp.dereferencer.get_value(field), f_high, f_low))
+ else:
+ value = do_slice(self.exp.dereferencer.get_value(field), 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 = do_bitswap(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
+ current_bit = 0 # Bit-offset within the wide register
+ access_strb = self.exp.dereferencer.get_access_strobe(node, reduce_substrobes=False)
+ # Fields are sorted by ascending low bit
+ for field in node.fields():
+ if not field.is_sw_readable:
+ continue
+
+ # insert zero assignment before this field if needed
+ if field.low >= accesswidth*(subword_idx+1):
+ # field does not start in this subword
+ if current_bit > accesswidth * subword_idx:
+ # current subword had content. Assign remainder
+ low = current_bit % accesswidth
+ high = bus_width - 1
+ self.add_content(f"assign readback_array[{self.current_offset_str}][{high}:{low}] = '0;")
+ self.current_offset += 1
+
+ # Advance to subword that contains the start of the field
+ subword_idx = field.low // accesswidth
+ current_bit = accesswidth * subword_idx
+
+ if current_bit != field.low:
+ # assign zero up to start of this field
+ low = current_bit % accesswidth
+ high = (field.low % accesswidth) - 1
+ self.add_content(f"assign readback_array[{self.current_offset_str}][{high}:{low}] = '0;")
+ current_bit = field.low
+
+
+ # Assign field
+ # loop until the entire field's assignments have been generated
+ field_pos = field.low
+ while current_bit <= field.high:
+ # Assign the field
+ rd_strb = f"({access_strb}[{subword_idx}] && !decoded_req_is_wr)"
+ if (field_pos == field.low) and (field.high < accesswidth*(subword_idx+1)):
+ # entire field fits into this subword
+ 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 = do_bitswap(value)
+
+ self.add_content(f"assign readback_array[{self.current_offset_str}][{high}:{low}] = {rd_strb} ? {value} : '0;")
+
+ current_bit = field.high + 1
+
+ if current_bit == accesswidth*(subword_idx+1):
+ # Field ends at the subword boundary
+ subword_idx += 1
+ self.current_offset += 1
+ elif field.high >= accesswidth*(subword_idx+1):
+ # only a subset of the field can fit into this subword
+ # high end gets truncated
+
+ # assignment slice
+ r_low = field_pos - accesswidth * subword_idx
+ r_high = accesswidth - 1
+
+ # field slice
+ f_low = field_pos - field.low
+ f_high = accesswidth * (subword_idx + 1) - 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 = do_bitswap(do_slice(self.exp.dereferencer.get_value(field), f_high, f_low))
+ else:
+ value = do_slice(self.exp.dereferencer.get_value(field), f_high, f_low)
+
+ self.add_content(f"assign readback_array[{self.current_offset_str}][{r_high}:{r_low}] = {rd_strb} ? {value} : '0;")
+
+ # advance to the next subword
+ subword_idx += 1
+ current_bit = accesswidth * subword_idx
+ field_pos = current_bit
+ self.current_offset += 1
+ else:
+ # only a subset of the field can fit into this subword
+ # finish field
+
+ # assignment slice
+ r_low = field_pos - accesswidth * subword_idx
+ r_high = field.high - accesswidth * subword_idx
+
+ # field slice
+ f_low = field_pos - field.low
+ f_high = field.high - 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 = do_bitswap(do_slice(self.exp.dereferencer.get_value(field), f_high, f_low))
+ else:
+ value = do_slice(self.exp.dereferencer.get_value(field), f_high, f_low)
+
+ self.add_content(f"assign readback_array[{self.current_offset_str}][{r_high}:{r_low}] = {rd_strb} ? {value} : '0;")
+
+ current_bit = field.high + 1
+ if current_bit == accesswidth*(subword_idx+1):
+ # Field ends at the subword boundary
+ subword_idx += 1
+ self.current_offset += 1
+
+ # insert zero assignment after the last field if needed
+ if current_bit > accesswidth * subword_idx:
+ # current subword had content. Assign remainder
+ low = current_bit % accesswidth
+ high = bus_width - 1
+ self.add_content(f"assign readback_array[{self.current_offset_str}][{high}:{low}] = '0;")
+ self.current_offset += 1
diff --git a/src/peakrdl_regblock/readback/templates/readback.sv b/src/peakrdl_regblock/readback/templates/readback.sv
new file mode 100644
index 0000000..e44c9ed
--- /dev/null
+++ b/src/peakrdl_regblock/readback/templates/readback.sv
@@ -0,0 +1,79 @@
+{% if array_assignments is not none %}
+// Assign readback values to a flattened array
+logic [{{cpuif.data_width-1}}:0] readback_array[{{array_size}}];
+{{array_assignments}}
+
+
+{%- if ds.retime_read_fanin %}
+
+// fanin stage
+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 [{{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;
+ end
+end
+{%- if fanin_residual_stride == 1 %}
+assign readback_array_c[{{fanin_array_size-1}}] = readback_array[{{array_size-1}}];
+{%- elif fanin_residual_stride > 1 %}
+always_comb begin
+ 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}}; i++) readback_data_var |= readback_array[i];
+ readback_array_c[{{fanin_array_size-1}}] = readback_data_var;
+end
+{%- endif %}
+
+logic [{{cpuif.data_width-1}}:0] readback_array_r[{{fanin_array_size}}];
+logic readback_done_r;
+always_ff {{get_always_ff_event(cpuif.reset)}} begin
+ if({{get_resetsignal(cpuif.reset)}}) begin
+ for(int i=0; i<{{fanin_array_size}}; i++) readback_array_r[i] <= '0;
+ readback_done_r <= '0;
+ end else begin
+ readback_array_r <= readback_array_c;
+ {%- if ds.has_external_addressable %}
+ readback_done_r <= decoded_req & ~decoded_req_is_wr & ~decoded_strb_is_external;
+ {%- else %}
+ readback_done_r <= decoded_req & ~decoded_req_is_wr;
+ {%- endif %}
+ end
+end
+
+// Reduce the array
+always_comb begin
+ 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];
+ readback_data = readback_data_var;
+end
+
+{%- else %}
+
+// Reduce the array
+always_comb begin
+ automatic logic [{{cpuif.data_width-1}}:0] readback_data_var;
+ {%- if ds.has_external_addressable %}
+ readback_done = decoded_req & ~decoded_req_is_wr & ~decoded_strb_is_external;
+ {%- else %}
+ readback_done = decoded_req & ~decoded_req_is_wr;
+ {%- endif %}
+ readback_err = '0;
+ readback_data_var = '0;
+ for(int i=0; i<{{array_size}}; i++) readback_data_var |= readback_array[i];
+ readback_data = readback_data_var;
+end
+{%- endif %}
+
+
+
+{%- else %}
+assign readback_done = decoded_req & ~decoded_req_is_wr;
+assign readback_data = '0;
+assign readback_err = '0;
+{% endif %}
diff --git a/src/peakrdl_regblock/scan_design.py b/src/peakrdl_regblock/scan_design.py
new file mode 100644
index 0000000..c3bea91
--- /dev/null
+++ b/src/peakrdl_regblock/scan_design.py
@@ -0,0 +1,119 @@
+from typing import TYPE_CHECKING, Optional
+
+from systemrdl.walker import RDLListener, RDLWalker, WalkerAction
+from systemrdl.node import SignalNode, RegNode
+
+if TYPE_CHECKING:
+ from systemrdl.node import Node, FieldNode, AddressableNode, AddrmapNode
+ from .exporter import DesignState
+
+
+class DesignScanner(RDLListener):
+ """
+ Scans through the register model and validates that any unsupported features
+ are not present.
+
+ Also collects any information that is required prior to the start of the export process.
+ """
+ def __init__(self, ds:'DesignState') -> None:
+ self.ds = ds
+ self.msg = self.top_node.env.msg
+
+ @property
+ def top_node(self) -> 'AddrmapNode':
+ return self.ds.top_node
+
+ def _get_out_of_hier_field_reset(self) -> None:
+ current_node: Optional[Node]
+ current_node = self.top_node.parent
+ while current_node is not None:
+ for signal in current_node.signals():
+ if signal.get_property('field_reset'):
+ path = signal.get_path()
+ self.ds.out_of_hier_signals[path] = signal
+ return
+ current_node = current_node.parent
+
+ def do_scan(self) -> None:
+ # Collect cpuif reset, if any.
+ cpuif_reset = self.top_node.cpuif_reset
+ if cpuif_reset is not None:
+ path = cpuif_reset.get_path()
+ rel_path = cpuif_reset.get_rel_path(self.top_node)
+ if rel_path.startswith("^"):
+ self.ds.out_of_hier_signals[path] = cpuif_reset
+ else:
+ self.ds.in_hier_signal_paths.add(path)
+
+ # collect out-of-hier field_reset, if any
+ self._get_out_of_hier_field_reset()
+
+ # Ensure addrmap is not a bridge. This concept does not make sense for
+ # terminal components.
+ if self.top_node.get_property('bridge'):
+ self.msg.error(
+ "Regblock generator does not support exporting bridge address maps",
+ self.top_node.inst.property_src_ref.get('bridge', self.top_node.inst.inst_src_ref)
+ )
+
+ RDLWalker().walk(self.top_node, self)
+ if self.msg.had_error:
+ self.msg.fatal(
+ "Unable to export due to previous errors"
+ )
+
+ def enter_Component(self, node: 'Node') -> Optional[WalkerAction]:
+ if node.external and (node != self.top_node):
+ # Do not inspect external components. None of my business
+ return WalkerAction.SkipDescendants
+
+ # Collect any signals that are referenced by a property
+ for prop_name in node.list_properties():
+ value = node.get_property(prop_name)
+ if isinstance(value, SignalNode):
+ path = value.get_path()
+ rel_path = value.get_rel_path(self.top_node)
+ if rel_path.startswith("^"):
+ self.ds.out_of_hier_signals[path] = value
+ else:
+ self.ds.in_hier_signal_paths.add(path)
+
+ if prop_name == "encode":
+ if value not in self.ds.user_enums:
+ self.ds.user_enums.append(value)
+
+ return WalkerAction.Continue
+
+ def enter_AddressableComponent(self, node: 'AddressableNode') -> None:
+ if node.external and node != self.top_node:
+ self.ds.has_external_addressable = True
+ if not isinstance(node, RegNode):
+ self.ds.has_external_block = True
+
+ def enter_Reg(self, node: 'RegNode') -> None:
+ # The CPUIF's bus width is sized according to the largest accesswidth in the design
+ accesswidth = node.get_property('accesswidth')
+ self.ds.cpuif_data_width = max(self.ds.cpuif_data_width, accesswidth)
+
+ self.ds.has_buffered_write_regs = self.ds.has_buffered_write_regs or bool(node.get_property('buffer_writes'))
+ self.ds.has_buffered_read_regs = self.ds.has_buffered_read_regs or bool(node.get_property('buffer_reads'))
+
+ def enter_Signal(self, node: 'SignalNode') -> None:
+ if node.get_property('field_reset'):
+ path = node.get_path()
+ self.ds.in_hier_signal_paths.add(path)
+
+ def enter_Field(self, node: 'FieldNode') -> None:
+ if node.is_sw_writable and (node.msb < node.lsb):
+ self.ds.has_writable_msb0_fields = True
+
+ if node.get_property('paritycheck') and node.implements_storage:
+ self.ds.has_paritycheck = True
+
+ if node.get_property('reset') is None:
+ self.msg.warning(
+ f"Field '{node.inst_name}' includes parity check logic, but "
+ "its reset value was not defined. Will result in an undefined "
+ "value on the module's 'parity_error' output.",
+ self.top_node.inst.property_src_ref.get('paritycheck', self.top_node.inst.inst_src_ref)
+ )
diff --git a/src/peakrdl_regblock/struct_generator.py b/src/peakrdl_regblock/struct_generator.py
new file mode 100644
index 0000000..50dcf48
--- /dev/null
+++ b/src/peakrdl_regblock/struct_generator.py
@@ -0,0 +1,292 @@
+from typing import TYPE_CHECKING, Optional, List
+import textwrap
+from collections import OrderedDict
+
+from systemrdl.walker import RDLListener, RDLWalker, WalkerAction
+
+from .identifier_filter import kw_filter as kwf
+
+if TYPE_CHECKING:
+ from typing import Union
+
+ from systemrdl.node import AddrmapNode, RegfileNode, RegNode, FieldNode, Node, MemNode
+
+
+class _StructBase:
+ def __init__(self) -> None:
+ self.children = [] # type: List[Union[str, _StructBase]]
+
+ def __str__(self) -> str:
+ s = '\n'.join((str(x) for x in self.children))
+ return textwrap.indent(s, " ")
+
+
+class _AnonymousStruct(_StructBase):
+ def __init__(self, inst_name: str, array_dimensions: Optional[List[int]] = None):
+ super().__init__()
+ self.inst_name = inst_name
+ self.array_dimensions = array_dimensions
+
+ def __str__(self) -> str:
+ if self.array_dimensions:
+ suffix = "[" + "][".join((str(n) for n in self.array_dimensions)) + "]"
+ else:
+ suffix = ""
+
+ return (
+ "struct {\n"
+ + super().__str__()
+ + f"\n}} {self.inst_name}{suffix};"
+ )
+
+
+class _TypedefStruct(_StructBase):
+ def __init__(self, type_name: str, inst_name: Optional[str] = None, array_dimensions: Optional[List[int]] = None, packed: bool = False):
+ super().__init__()
+ self.type_name = type_name
+ self.inst_name = inst_name
+ self.array_dimensions = array_dimensions
+ self.packed = packed
+
+ def __str__(self) -> str:
+ if self.packed:
+ return (
+ "typedef struct packed {\n"
+ + super().__str__()
+ + f"\n}} {self.type_name};"
+ )
+ else:
+ return (
+ "typedef struct {\n"
+ + super().__str__()
+ + f"\n}} {self.type_name};"
+ )
+
+ @property
+ def instantiation(self) -> str:
+ if self.array_dimensions:
+ suffix = "[" + "][".join((str(n) for n in self.array_dimensions)) + "]"
+ else:
+ suffix = ""
+
+ return f"{self.type_name} {self.inst_name}{suffix};"
+
+#-------------------------------------------------------------------------------
+
+class StructGenerator:
+
+ def __init__(self) -> None:
+ self._struct_stack = [] # type: List[_StructBase]
+
+ @property
+ def current_struct(self) -> _StructBase:
+ return self._struct_stack[-1]
+
+
+ def push_struct(self, inst_name: str, array_dimensions: Optional[List[int]] = None) -> None:
+ s = _AnonymousStruct(inst_name, array_dimensions)
+ self._struct_stack.append(s)
+
+
+ def add_member(
+ self,
+ name: str,
+ width: int = 1,
+ array_dimensions: Optional[List[int]] = None,
+ *,
+ lsb: int = 0,
+ signed: bool = False,
+ ) -> None:
+ if array_dimensions:
+ suffix = "[" + "][".join((str(n) for n in array_dimensions)) + "]"
+ else:
+ suffix = ""
+
+ if signed:
+ sign = "signed "
+ else:
+ # the default 'logic' type is unsigned per SV LRM 6.11.3
+ sign = ""
+
+ if width == 1 and lsb == 0:
+ m = f"logic {sign}{name}{suffix};"
+ else:
+ m = f"logic {sign}[{lsb+width-1}:{lsb}] {name}{suffix};"
+ self.current_struct.children.append(m)
+
+
+ def pop_struct(self) -> None:
+ s = self._struct_stack.pop()
+
+ if s.children:
+ # struct is not empty. Attach it to the parent
+ self.current_struct.children.append(s)
+
+
+ def start(self, type_name: str) -> None:
+ assert not self._struct_stack
+ s = _TypedefStruct(type_name)
+ self._struct_stack.append(s)
+
+ def finish(self) -> Optional[str]:
+ s = self._struct_stack.pop()
+ assert not self._struct_stack
+
+ if not s.children:
+ return None
+ return str(s)
+
+
+class RDLStructGenerator(StructGenerator, RDLListener):
+ """
+ Struct generator that naively translates an RDL node tree into a single
+ struct typedef containing nested anonymous structs
+
+ This can be extended to add more intelligent behavior
+ """
+
+ def get_struct(self, node: 'Node', type_name: str) -> Optional[str]:
+ self.start(type_name)
+
+ walker = RDLWalker()
+ walker.walk(node, self, skip_top=True)
+
+ return self.finish()
+
+
+ def enter_Addrmap(self, node: 'AddrmapNode') -> Optional[WalkerAction]:
+ self.push_struct(kwf(node.inst_name), node.array_dimensions)
+ return WalkerAction.Continue
+
+ def exit_Addrmap(self, node: 'AddrmapNode') -> Optional[WalkerAction]:
+ self.pop_struct()
+ return WalkerAction.Continue
+
+ def enter_Regfile(self, node: 'RegfileNode') -> Optional[WalkerAction]:
+ self.push_struct(kwf(node.inst_name), node.array_dimensions)
+ return WalkerAction.Continue
+
+ def exit_Regfile(self, node: 'RegfileNode') -> Optional[WalkerAction]:
+ self.pop_struct()
+ return WalkerAction.Continue
+
+ def enter_Mem(self, node: 'MemNode') -> Optional[WalkerAction]:
+ self.push_struct(kwf(node.inst_name), node.array_dimensions)
+ return WalkerAction.Continue
+
+ def exit_Mem(self, node: 'MemNode') -> Optional[WalkerAction]:
+ self.pop_struct()
+ return WalkerAction.Continue
+
+ def enter_Reg(self, node: 'RegNode') -> Optional[WalkerAction]:
+ self.push_struct(kwf(node.inst_name), node.array_dimensions)
+ return WalkerAction.Continue
+
+ def exit_Reg(self, node: 'RegNode') -> Optional[WalkerAction]:
+ self.pop_struct()
+ return WalkerAction.Continue
+
+ def enter_Field(self, node: 'FieldNode') -> Optional[WalkerAction]:
+ self.add_member(kwf(node.inst_name), node.width)
+ return WalkerAction.Continue
+
+#-------------------------------------------------------------------------------
+
+class FlatStructGenerator(StructGenerator):
+
+ def __init__(self) -> None:
+ super().__init__()
+ self.typedefs = OrderedDict() # type: OrderedDict[str, _TypedefStruct]
+
+ def push_struct(self, type_name: str, inst_name: str, array_dimensions: Optional[List[int]] = None, packed = False) -> None: # type: ignore # pylint: disable=arguments-renamed
+ s = _TypedefStruct(type_name, inst_name, array_dimensions, packed)
+ self._struct_stack.append(s)
+
+ def pop_struct(self) -> None:
+ s = self._struct_stack.pop()
+ assert isinstance(s, _TypedefStruct)
+
+ if s.children:
+ # struct is not empty. Attach it to the parent
+ self.current_struct.children.append(s.instantiation)
+
+ # Add to collection of struct definitions
+ if s.type_name not in self.typedefs:
+ self.typedefs[s.type_name] = s
+
+ def finish(self) -> Optional[str]:
+ s = self._struct_stack.pop()
+ assert isinstance(s, _TypedefStruct)
+ assert not self._struct_stack
+
+ # no children, no struct.
+ if not s.children:
+ return None
+
+ # Add to collection of struct definitions
+ if s.type_name not in self.typedefs:
+ self.typedefs[s.type_name] = s
+
+ all_structs = [str(s) for s in self.typedefs.values()]
+
+ return "\n\n".join(all_structs)
+
+
+class RDLFlatStructGenerator(FlatStructGenerator, RDLListener):
+ """
+ Struct generator that naively translates an RDL node tree into a flat list
+ of typedefs
+
+ This can be extended to add more intelligent behavior
+ """
+
+ def get_typdef_name(self, node:'Node') -> str:
+ raise NotImplementedError
+
+ def get_struct(self, node: 'Node', type_name: str) -> Optional[str]:
+ self.start(type_name)
+
+ walker = RDLWalker()
+ walker.walk(node, self, skip_top=True)
+
+ return self.finish()
+
+ def enter_Addrmap(self, node: 'AddrmapNode') -> Optional[WalkerAction]:
+ type_name = self.get_typdef_name(node)
+ self.push_struct(type_name, kwf(node.inst_name), node.array_dimensions)
+ return WalkerAction.Continue
+
+ def exit_Addrmap(self, node: 'AddrmapNode') -> Optional[WalkerAction]:
+ self.pop_struct()
+ return WalkerAction.Continue
+
+ def enter_Regfile(self, node: 'RegfileNode') -> Optional[WalkerAction]:
+ type_name = self.get_typdef_name(node)
+ self.push_struct(type_name, kwf(node.inst_name), node.array_dimensions)
+ return WalkerAction.Continue
+
+ def exit_Regfile(self, node: 'RegfileNode') -> Optional[WalkerAction]:
+ self.pop_struct()
+ return WalkerAction.Continue
+
+ def enter_Mem(self, node: 'MemNode') -> Optional[WalkerAction]:
+ type_name = self.get_typdef_name(node)
+ self.push_struct(type_name, kwf(node.inst_name), node.array_dimensions)
+ return WalkerAction.Continue
+
+ def exit_Mem(self, node: 'MemNode') -> Optional[WalkerAction]:
+ self.pop_struct()
+ return WalkerAction.Continue
+
+ def enter_Reg(self, node: 'RegNode') -> Optional[WalkerAction]:
+ type_name = self.get_typdef_name(node)
+ self.push_struct(type_name, kwf(node.inst_name), node.array_dimensions)
+ return WalkerAction.Continue
+
+ def exit_Reg(self, node: 'RegNode') -> Optional[WalkerAction]:
+ self.pop_struct()
+ return WalkerAction.Continue
+
+ def enter_Field(self, node: 'FieldNode') -> Optional[WalkerAction]:
+ self.add_member(kwf(node.inst_name), node.width)
+ return WalkerAction.Continue
diff --git a/src/peakrdl_regblock/sv_int.py b/src/peakrdl_regblock/sv_int.py
new file mode 100644
index 0000000..496eacf
--- /dev/null
+++ b/src/peakrdl_regblock/sv_int.py
@@ -0,0 +1,17 @@
+from typing import Optional
+
+class SVInt:
+ def __init__(self, value: int, width: Optional[int] = None) -> None:
+ self.value = value
+ self.width = width
+
+ def __str__(self) -> str:
+ if self.width is not None:
+ # Explicit width
+ return f"{self.width}'h{self.value:x}"
+ elif self.value.bit_length() > 32:
+ # SV standard only enforces that unsized literals shall be at least 32-bits
+ # To support larger literals, they need to be sized explicitly
+ return f"{self.value.bit_length()}'h{self.value:x}"
+ else:
+ return f"'h{self.value:x}"
diff --git a/src/peakrdl_regblock/udps/__init__.py b/src/peakrdl_regblock/udps/__init__.py
new file mode 100644
index 0000000..2eb9c20
--- /dev/null
+++ b/src/peakrdl_regblock/udps/__init__.py
@@ -0,0 +1,17 @@
+from .rw_buffering import BufferWrites, WBufferTrigger
+from .rw_buffering import BufferReads, RBufferTrigger
+from .extended_swacc import ReadSwacc, WriteSwacc
+from .fixedpoint import IntWidth, FracWidth
+from .signed import IsSigned
+
+ALL_UDPS = [
+ BufferWrites,
+ WBufferTrigger,
+ BufferReads,
+ RBufferTrigger,
+ ReadSwacc,
+ WriteSwacc,
+ IntWidth,
+ FracWidth,
+ IsSigned,
+]
diff --git a/src/peakrdl_regblock/udps/extended_swacc.py b/src/peakrdl_regblock/udps/extended_swacc.py
new file mode 100644
index 0000000..c982eda
--- /dev/null
+++ b/src/peakrdl_regblock/udps/extended_swacc.py
@@ -0,0 +1,23 @@
+from typing import TYPE_CHECKING, Any
+
+from systemrdl.udp import UDPDefinition
+from systemrdl.component import Field
+
+if TYPE_CHECKING:
+ from systemrdl.node import Node
+
+class ReadSwacc(UDPDefinition):
+ name = "rd_swacc"
+ valid_components = {Field}
+ valid_type = bool
+
+ def get_unassigned_default(self, node: 'Node') -> Any:
+ return False
+
+class WriteSwacc(UDPDefinition):
+ name = "wr_swacc"
+ valid_components = {Field}
+ valid_type = bool
+
+ def get_unassigned_default(self, node: 'Node') -> Any:
+ return False
diff --git a/src/peakrdl_regblock/udps/fixedpoint.py b/src/peakrdl_regblock/udps/fixedpoint.py
new file mode 100644
index 0000000..cf47534
--- /dev/null
+++ b/src/peakrdl_regblock/udps/fixedpoint.py
@@ -0,0 +1,73 @@
+from typing import Any
+
+from systemrdl.component import Field
+from systemrdl.node import Node, FieldNode
+from systemrdl.udp import UDPDefinition
+
+
+class _FixedpointWidth(UDPDefinition):
+ valid_components = {Field}
+ valid_type = int
+
+ def validate(self, node: "Node", value: Any) -> None:
+ assert isinstance(node, FieldNode)
+
+ intwidth = node.get_property("intwidth")
+ fracwidth = node.get_property("fracwidth")
+ assert intwidth is not None
+ assert fracwidth is not None
+ prop_ref = node.inst.property_src_ref.get(self.name)
+
+ # incompatible with "counter" fields
+ if node.get_property("counter"):
+ self.msg.error(
+ "Fixed-point representations are not supported for counter fields.",
+ prop_ref
+ )
+
+ # incompatible with "encode" fields
+ if node.get_property("encode") is not None:
+ self.msg.error(
+ "Fixed-point representations are not supported for fields encoded as an enum.",
+ prop_ref
+ )
+
+ # ensure node width = fracwidth + intwidth
+ if intwidth + fracwidth != node.width:
+ self.msg.error(
+ f"Number of integer bits ({intwidth}) plus number of fractional bits ({fracwidth})"
+ f" must be equal to the width of the component ({node.width}).",
+ prop_ref
+ )
+
+
+class IntWidth(_FixedpointWidth):
+ name = "intwidth"
+
+ def get_unassigned_default(self, node: "Node") -> Any:
+ """
+ If 'fracwidth' is defined, 'intwidth' is inferred from the node width.
+ """
+ assert isinstance(node, FieldNode)
+ fracwidth = node.get_property("fracwidth", default=None)
+ if fracwidth is not None:
+ return node.width - fracwidth
+ else:
+ # not a fixed-point number
+ return None
+
+
+class FracWidth(_FixedpointWidth):
+ name = "fracwidth"
+
+ def get_unassigned_default(self, node: "Node") -> Any:
+ """
+ If 'intwidth' is defined, 'fracwidth' is inferred from the node width.
+ """
+ assert isinstance(node, FieldNode)
+ intwidth = node.get_property("intwidth", default=None)
+ if intwidth is not None:
+ return node.width - intwidth
+ else:
+ # not a fixed-point number
+ return None
diff --git a/src/peakrdl_regblock/udps/rw_buffering.py b/src/peakrdl_regblock/udps/rw_buffering.py
new file mode 100644
index 0000000..37ba6ce
--- /dev/null
+++ b/src/peakrdl_regblock/udps/rw_buffering.py
@@ -0,0 +1,130 @@
+from typing import Any
+
+from systemrdl.udp import UDPDefinition
+from systemrdl.component import Reg
+from systemrdl.rdltypes.references import RefType, PropertyReference
+from systemrdl.rdltypes import NoValue
+from systemrdl.node import Node, RegNode, VectorNode, SignalNode, FieldNode
+
+
+class xBufferTrigger(UDPDefinition):
+ valid_components = {Reg}
+ valid_type = RefType
+
+ def validate(self, node: Node, value: Any) -> None:
+ # TODO: Reference shall not cross an internal/external boundary
+
+ if value is NoValue:
+ self.msg.error(
+ "Double-buffer trigger property is missing a value assignment",
+ self.get_src_ref(node)
+ )
+ elif isinstance(value, VectorNode):
+ # Trigger can reference a vector, but only if it is a single-bit
+ if value.width != 1:
+ self.msg.error(
+ "%s '%s' references %s '%s' but its width is not 1"
+ % (
+ type(node.inst).__name__.lower(), node.inst_name,
+ type(value.inst).__name__.lower(), value.inst_name
+ ),
+ self.get_src_ref(node)
+ )
+
+ if isinstance(value, SignalNode):
+ if not value.get_property('activehigh') and not value.get_property('activelow'):
+ self.msg.error(
+ "Trigger was asigned a signal, but it does not specify whether it is activehigh/activelow",
+ self.get_src_ref(node)
+ )
+
+ elif isinstance(value, PropertyReference) and value.width is not None:
+ # Trigger can reference a property, but only if it is a single-bit
+ if value.width != 1:
+ self.msg.error(
+ "%s '%s' references property '%s->%s' but its width is not 1"
+ % (
+ type(node.inst).__name__.lower(), node.inst_name,
+ value.node.inst_name, value.name,
+ ),
+ self.get_src_ref(node)
+ )
+ elif isinstance(value, RegNode):
+ # Trigger can reference a register, which implies access of the
+ # 'correct half' of the register is the trigger.
+ # For buffered writes, it is the upper-half.
+ # For buffered reads, it is the lower-half.
+ pass
+ else:
+ # All other reference types are invalid
+ self.msg.error(
+ "Reference to a %s component is incompatible with the '%s' property."
+ % (type(node.inst).__name__.lower(), self.name),
+ self.get_src_ref(node)
+ )
+
+#-------------------------------------------------------------------------------
+class BufferWrites(UDPDefinition):
+ name = "buffer_writes"
+ valid_components = {Reg}
+ valid_type = bool
+
+ def validate(self, node: 'Node', value: Any) -> None:
+ assert isinstance(node, RegNode)
+ if value:
+ if not node.has_sw_writable:
+ self.msg.error(
+ "'buffer_writes' is set to true, but this register does not contain any writable fields.",
+ self.get_src_ref(node)
+ )
+
+ def get_unassigned_default(self, node: 'Node') -> Any:
+ return False
+
+
+class WBufferTrigger(xBufferTrigger):
+ name = "wbuffer_trigger"
+
+ def get_unassigned_default(self, node: 'Node') -> Any:
+ # If buffering is enabled, trigger is the register itself
+ if node.get_property('buffer_writes'):
+ return node
+ return None
+
+ def validate(self, node: Node, value: Any) -> None:
+ super().validate(node, value)
+
+ if isinstance(value, FieldNode):
+ if value.parent == node:
+ self.msg.error(
+ "Trigger for a write-buffered register cannot be a field "
+ "within the same register since the buffering makes it impossible to trigger."
+ )
+
+
+class BufferReads(UDPDefinition):
+ name = "buffer_reads"
+ valid_components = {Reg}
+ valid_type = bool
+
+ def validate(self, node: 'Node', value: Any) -> None:
+ assert isinstance(node, RegNode)
+ if value:
+ if not node.has_sw_readable:
+ self.msg.error(
+ "'buffer_reads' is set to true, but this register does not contain any readable fields.",
+ self.get_src_ref(node)
+ )
+
+ def get_unassigned_default(self, node: 'Node') -> Any:
+ return False
+
+
+class RBufferTrigger(xBufferTrigger):
+ name = "rbuffer_trigger"
+
+ def get_unassigned_default(self, node: 'Node') -> Any:
+ # If buffering is enabled, trigger is the register itself
+ if node.get_property('buffer_reads'):
+ return node
+ return None
diff --git a/src/peakrdl_regblock/udps/signed.py b/src/peakrdl_regblock/udps/signed.py
new file mode 100644
index 0000000..b342986
--- /dev/null
+++ b/src/peakrdl_regblock/udps/signed.py
@@ -0,0 +1,33 @@
+from typing import Any
+
+from systemrdl.component import Field
+from systemrdl.node import Node
+from systemrdl.udp import UDPDefinition
+
+
+class IsSigned(UDPDefinition):
+ name = "is_signed"
+ valid_components = {Field}
+ valid_type = bool
+ default_assignment = True
+
+ def validate(self, node: "Node", value: Any) -> None:
+ # "counter" fields can not be signed
+ if value and node.get_property("counter"):
+ self.msg.error(
+ "The property is_signed=true is not supported for counter fields.",
+ node.inst.property_src_ref["is_signed"]
+ )
+
+ # incompatible with "encode" fields
+ if value and node.get_property("encode") is not None:
+ self.msg.error(
+ "The property is_signed=true is not supported for fields encoded as an enum.",
+ node.inst.property_src_ref["is_signed"]
+ )
+
+ def get_unassigned_default(self, node: "Node") -> Any:
+ """
+ Unsigned by default if not specified.
+ """
+ return False
diff --git a/src/peakrdl_regblock/utils.py b/src/peakrdl_regblock/utils.py
new file mode 100644
index 0000000..b9d7a7f
--- /dev/null
+++ b/src/peakrdl_regblock/utils.py
@@ -0,0 +1,104 @@
+import re
+from typing import Match, Union, Optional
+
+from systemrdl.rdltypes.references import PropertyReference
+from systemrdl.node import Node, AddrmapNode
+
+from .identifier_filter import kw_filter as kwf
+from .sv_int import SVInt
+
+def get_indexed_path(top_node: Node, target_node: Node) -> str:
+ """
+ TODO: Add words about indexing and why i'm doing this. Copy from logbook
+ """
+ path = target_node.get_rel_path(top_node, empty_array_suffix="[!]")
+
+ # replace unknown indexes with incrementing iterators i0, i1, ...
+ class ReplaceUnknown:
+ def __init__(self) -> None:
+ self.i = 0
+ def __call__(self, match: Match) -> str:
+ s = f'i{self.i}'
+ self.i += 1
+ return s
+ path = re.sub(r'!', ReplaceUnknown(), path)
+
+ # Sanitize any SV keywords
+ def kw_filter_repl(m: Match) -> str:
+ return kwf(m.group(0))
+ path = re.sub(r'\w+', kw_filter_repl, path)
+
+ return path
+
+def clog2(n: int) -> int:
+ return (n-1).bit_length()
+
+def is_pow2(x: int) -> bool:
+ return (x > 0) and ((x & (x - 1)) == 0)
+
+def roundup_pow2(x: int) -> int:
+ return 1<<(x-1).bit_length()
+
+def ref_is_internal(top_node: AddrmapNode, ref: Union[Node, PropertyReference]) -> bool:
+ """
+ Determine whether the reference is internal to the top node.
+
+ For the sake of this exporter, root signals are treated as internal.
+ """
+ current_node: Optional[Node]
+ if isinstance(ref, Node):
+ current_node = ref
+ elif isinstance(ref, PropertyReference):
+ current_node = ref.node
+ else:
+ raise RuntimeError
+
+ while current_node is not None:
+ if current_node == top_node:
+ # reached top node without finding any external components
+ # is internal!
+ return True
+
+ if current_node.external:
+ # not internal!
+ return False
+
+ current_node = current_node.parent
+
+ # A root signal was referenced, which dodged the top addrmap
+ # This is considered internal for this exporter
+ return True
+
+
+def do_slice(value: Union[SVInt, str], high: int, low: int) -> Union[SVInt, str]:
+ if isinstance(value, str):
+ # If string, assume this is an identifier. Append bit-slice
+ if high == low:
+ return f"{value}[{low}]"
+ else:
+ return f"{value}[{high}:{low}]"
+ else:
+ # it is an SVInt literal. Slice it down
+ mask = (1 << (high + 1)) - 1
+ v = (value.value & mask) >> low
+
+ if value.width is not None:
+ w = high - low + 1
+ else:
+ w = None
+
+ return SVInt(v, w)
+
+def do_bitswap(value: Union[SVInt, str]) -> Union[SVInt, str]:
+ if isinstance(value, str):
+ # If string, assume this is an identifier. Wrap in a streaming operator
+ return "{<<{" + value + "}}"
+ else:
+ # it is an SVInt literal. bitswap it
+ assert value.width is not None # width must be known!
+ v = value.value
+ vswap = 0
+ for _ in range(value.width):
+ vswap = (vswap << 1) + (v & 1)
+ v >>= 1
+ return SVInt(vswap, value.width)
diff --git a/src/peakrdl_regblock/validate_design.py b/src/peakrdl_regblock/validate_design.py
new file mode 100644
index 0000000..7b8e9f8
--- /dev/null
+++ b/src/peakrdl_regblock/validate_design.py
@@ -0,0 +1,207 @@
+from typing import TYPE_CHECKING, Optional, List, Union
+
+from systemrdl.walker import RDLListener, RDLWalker, WalkerAction
+from systemrdl.rdltypes import PropertyReference
+from systemrdl.node import Node, RegNode, FieldNode, SignalNode, AddressableNode
+from systemrdl.node import RegfileNode, AddrmapNode
+
+from .utils import roundup_pow2, is_pow2
+
+from .utils import ref_is_internal
+
+if TYPE_CHECKING:
+ from .exporter import RegblockExporter
+
+class DesignValidator(RDLListener):
+ """
+ Performs additional rule-checks on the design that check for limitations
+ imposed by this exporter.
+ """
+ def __init__(self, exp:'RegblockExporter') -> None:
+ self.exp = exp
+ self.ds = exp.ds
+ self.msg = self.top_node.env.msg
+
+ self._contains_external_block_stack = [] # type: List[bool]
+ self.contains_external_block = False
+
+ @property
+ def top_node(self) -> 'AddrmapNode':
+ return self.exp.ds.top_node
+
+ def do_validate(self) -> None:
+ RDLWalker().walk(self.top_node, self)
+ if self.msg.had_error:
+ self.msg.fatal(
+ "Unable to export due to previous errors"
+ )
+
+ def enter_Component(self, node: 'Node') -> Optional[WalkerAction]:
+ if node.external and (node != self.top_node):
+ # Do not inspect external components. None of my business
+ return WalkerAction.SkipDescendants
+
+ # Check if any property references reach across the internal/external boundary
+ for prop_name in node.list_properties():
+ value = node.get_property(prop_name)
+ if isinstance(value, (PropertyReference, Node)):
+ if not ref_is_internal(self.top_node, value):
+ if isinstance(value, PropertyReference):
+ src_ref = value.src_ref
+ else:
+ src_ref = node.inst.property_src_ref.get(prop_name, node.inst.inst_src_ref)
+ self.msg.error(
+ "Property is assigned a reference that points to a component not internal to the regblock being exported.",
+ src_ref
+ )
+ return None
+
+ def enter_Signal(self, node: 'SignalNode') -> None:
+ # If encountering a CPUIF reset that is nested within the register model,
+ # warn that it will be ignored.
+ # Only cpuif resets in the top-level node or above will be honored
+ if node.get_property('cpuif_reset') and (node.parent != self.top_node):
+ self.msg.warning(
+ "Only cpuif_reset signals that are instantiated in the top-level "
+ "addrmap or above will be honored. Any cpuif_reset signals nested "
+ "within children of the addrmap being exported will be ignored.",
+ node.inst.inst_src_ref
+ )
+
+ def enter_AddressableComponent(self, node: 'AddressableNode') -> None:
+ # All registers must be aligned to the internal data bus width
+ alignment = self.exp.cpuif.data_width_bytes
+ if (node.raw_address_offset % alignment) != 0:
+ self.msg.error(
+ "Unaligned registers are not supported. Address offset of "
+ f"instance '{node.inst_name}' must be a multiple of {alignment}",
+ node.inst.inst_src_ref
+ )
+ if node.is_array and (node.array_stride % alignment) != 0: # type: ignore # is_array implies stride is not none
+ self.msg.error(
+ "Unaligned registers are not supported. Address stride of "
+ f"instance array '{node.inst_name}' must be a multiple of {alignment}",
+ node.inst.inst_src_ref
+ )
+
+ if not isinstance(node, RegNode):
+ # Entering a block-like node
+ if node == self.top_node:
+ # Ignore top addrmap's external property when entering
+ self._contains_external_block_stack.append(False)
+ else:
+ self._contains_external_block_stack.append(node.external)
+
+ def enter_Regfile(self, node: RegfileNode) -> None:
+ self._check_sharedextbus(node)
+
+ def enter_Addrmap(self, node: AddrmapNode) -> None:
+ self._check_sharedextbus(node)
+
+ def _check_sharedextbus(self, node: Union[RegfileNode, AddrmapNode]) -> None:
+ if node.get_property('sharedextbus'):
+ self.msg.error(
+ "This exporter does not support enabling the 'sharedextbus' property yet.",
+ node.inst.property_src_ref.get('sharedextbus', node.inst.inst_src_ref)
+ )
+
+ def enter_Reg(self, node: 'RegNode') -> None:
+ # accesswidth of wide registers must be consistent within the register block
+ accesswidth = node.get_property('accesswidth')
+ regwidth = node.get_property('regwidth')
+
+ if accesswidth < regwidth:
+ # register is 'wide'
+ if accesswidth != self.exp.cpuif.data_width:
+ self.msg.error(
+ f"Multi-word registers that have an accesswidth ({accesswidth}) "
+ "that are inconsistent with this regblock's CPU bus width "
+ f"({self.exp.cpuif.data_width}) are not supported.",
+ node.inst.inst_src_ref
+ )
+
+
+ def enter_Field(self, node: 'FieldNode') -> None:
+ parent_accesswidth = node.parent.get_property('accesswidth')
+ parent_regwidth = node.parent.get_property('regwidth')
+ if (
+ (parent_accesswidth < parent_regwidth)
+ and (node.lsb // parent_accesswidth) != (node.msb // parent_accesswidth)
+ ):
+ # field spans multiple sub-words
+
+ if node.is_sw_writable and not node.parent.get_property('buffer_writes'):
+ # ... and is writable without the protection of double-buffering
+ # Enforce 10.6.1-f
+ self.msg.error(
+ f"Software-writable field '{node.inst_name}' shall not span"
+ " multiple software-accessible subwords. Consider enabling"
+ " write double-buffering.\n"
+ "For more details, see: https://peakrdl-regblock.readthedocs.io/en/latest/udps/write_buffering.html",
+ node.inst.inst_src_ref
+ )
+
+ if node.get_property('onread') is not None and not node.parent.get_property('buffer_reads'):
+ # ... is modified by an onread action without the atomicity of read buffering
+ # Enforce 10.6.1-f
+ self.msg.error(
+ f"The field '{node.inst_name}' spans multiple software-accessible"
+ " subwords and is modified on-read, making it impossible to"
+ " access its value correctly. Consider enabling read"
+ " double-buffering. \n"
+ "For more details, see: https://peakrdl-regblock.readthedocs.io/en/latest/udps/read_buffering.html",
+ node.inst.inst_src_ref
+ )
+
+ # Check for unsynthesizable reset
+ reset = node.get_property("reset")
+ if not (reset is None or isinstance(reset, int)):
+ # Has reset that is not a constant value
+ resetsignal = node.get_property("resetsignal")
+ if resetsignal:
+ is_async_reset = resetsignal.get_property("async")
+ else:
+ is_async_reset = self.ds.default_reset_async
+
+ if is_async_reset:
+ self.msg.error(
+ "A field that uses an asynchronous reset cannot use a dynamic reset value. This is not synthesizable.",
+ node.inst.inst_src_ref
+ )
+
+
+ def exit_AddressableComponent(self, node: AddressableNode) -> None:
+ if not isinstance(node, RegNode):
+ # Exiting block-like node
+ contains_external_block = self._contains_external_block_stack.pop()
+
+ if self._contains_external_block_stack:
+ # Still in the design. Update stack
+ self._contains_external_block_stack[-1] |= contains_external_block
+ else:
+ # Exiting top addrmap. Resolve final answer
+ self.contains_external_block = contains_external_block
+
+ if contains_external_block:
+ # Check that addressing follows strict alignment rules to allow
+ # for simplified address bit-pruning
+ if node.external:
+ err_suffix = "is external"
+ else:
+ err_suffix = "contains an external addrmap/regfile/mem"
+
+ req_align = roundup_pow2(node.size)
+ if (node.raw_address_offset % req_align) != 0:
+ self.msg.error(
+ f"Address offset +0x{node.raw_address_offset:x} of instance '{node.inst_name}' is not a power of 2 multiple of its size 0x{node.size:x}. "
+ f"This is required by the regblock exporter if a component {err_suffix}.",
+ node.inst.inst_src_ref
+ )
+ if node.is_array:
+ assert node.array_stride is not None
+ if not is_pow2(node.array_stride):
+ self.msg.error(
+ f"Address stride of instance array '{node.inst_name}' is not a power of 2"
+ f"This is required by the regblock exporter if a component {err_suffix}.",
+ node.inst.inst_src_ref
+ )
diff --git a/src/peakrdl_regblock/write_buffering/__init__.py b/src/peakrdl_regblock/write_buffering/__init__.py
new file mode 100644
index 0000000..a03ddcb
--- /dev/null
+++ b/src/peakrdl_regblock/write_buffering/__init__.py
@@ -0,0 +1,81 @@
+from typing import TYPE_CHECKING, Union
+
+from systemrdl.node import AddrmapNode, RegNode, FieldNode, SignalNode
+
+from .storage_generator import WBufStorageStructGenerator
+from .implementation_generator import WBufLogicGenerator
+from ..utils import get_indexed_path
+from ..sv_int import SVInt
+
+if TYPE_CHECKING:
+ from ..exporter import RegblockExporter
+
+
+class WriteBuffering:
+ def __init__(self, exp:'RegblockExporter'):
+ self.exp = exp
+
+ @property
+ def top_node(self) -> 'AddrmapNode':
+ return self.exp.ds.top_node
+
+
+ def get_storage_struct(self) -> str:
+ struct_gen = WBufStorageStructGenerator(self)
+ s = struct_gen.get_struct(self.top_node, "wbuf_storage_t")
+ assert s is not None
+ return s + "\nwbuf_storage_t wbuf_storage;"
+
+
+ def get_implementation(self) -> str:
+ gen = WBufLogicGenerator(self)
+ s = gen.get_content(self.top_node)
+ assert s is not None
+ return s
+
+ def get_wbuf_prefix(self, node: Union[RegNode, FieldNode]) -> str:
+ if isinstance(node, FieldNode):
+ node = node.parent
+ wbuf_prefix = "wbuf_storage." + get_indexed_path(self.top_node, node)
+ return wbuf_prefix
+
+ def get_write_strobe(self, node: Union[RegNode, FieldNode]) -> str:
+ prefix = self.get_wbuf_prefix(node)
+ return f"{prefix}.pending && {self.get_trigger(node)}"
+
+ def get_raw_trigger(self, node: 'RegNode') -> Union[SVInt, str]:
+ trigger = node.get_property('wbuffer_trigger')
+
+ if isinstance(trigger, RegNode):
+ # Trigger is a register.
+ # trigger when uppermost address of the register is written
+ regwidth = trigger.get_property('regwidth')
+ accesswidth = trigger.get_property('accesswidth')
+ strb_prefix = self.exp.dereferencer.get_access_strobe(trigger, reduce_substrobes=False)
+
+ if accesswidth < regwidth:
+ n_subwords = regwidth // accesswidth
+ return f"{strb_prefix}[{n_subwords-1}] && decoded_req_is_wr"
+ else:
+ return f"{strb_prefix} && decoded_req_is_wr"
+ elif isinstance(trigger, SignalNode):
+ s = self.exp.dereferencer.get_value(trigger)
+ if trigger.get_property('activehigh'):
+ return s
+ else:
+ return f"~{s}"
+ else:
+ # Trigger is a field or propref bit
+ return self.exp.dereferencer.get_value(trigger)
+
+ def get_trigger(self, node: Union[RegNode, FieldNode]) -> Union[SVInt, str]:
+ if isinstance(node, FieldNode):
+ node = node.parent
+ trigger = node.get_property('wbuffer_trigger')
+
+ if isinstance(trigger, RegNode) and trigger == node:
+ # register is its own trigger
+ # use the delayed trigger signal
+ return self.get_wbuf_prefix(node) + ".trigger_q"
+ else:
+ return self.get_raw_trigger(node)
diff --git a/src/peakrdl_regblock/write_buffering/implementation_generator.py b/src/peakrdl_regblock/write_buffering/implementation_generator.py
new file mode 100644
index 0000000..c2dad95
--- /dev/null
+++ b/src/peakrdl_regblock/write_buffering/implementation_generator.py
@@ -0,0 +1,59 @@
+from typing import TYPE_CHECKING
+from collections import namedtuple
+
+from systemrdl.component import Reg
+from systemrdl.node import RegNode
+
+from ..forloop_generator import RDLForLoopGenerator
+
+if TYPE_CHECKING:
+ from . import WriteBuffering
+
+class WBufLogicGenerator(RDLForLoopGenerator):
+ i_type = "genvar"
+ def __init__(self, wbuf: 'WriteBuffering') -> None:
+ super().__init__()
+ self.wbuf = wbuf
+ self.exp = wbuf.exp
+ self.template = self.exp.jj_env.get_template(
+ "write_buffering/template.sv"
+ )
+
+ def enter_Reg(self, node: 'RegNode') -> None:
+ super().enter_Reg(node)
+ assert isinstance(node.inst, Reg)
+
+ if not node.get_property('buffer_writes'):
+ return
+
+ regwidth = node.get_property('regwidth')
+ accesswidth = node.get_property('accesswidth')
+ strb_prefix = self.exp.dereferencer.get_access_strobe(node, reduce_substrobes=False)
+ Segment = namedtuple("Segment", ["strobe", "bslice"])
+ segments = []
+ if accesswidth < regwidth:
+ n_subwords = regwidth // accesswidth
+ for i in range(n_subwords):
+ strobe = strb_prefix + f"[{i}]"
+ if node.inst.is_msb0_order:
+ bslice = f"[{regwidth - (accesswidth * i) - 1}: {regwidth - (accesswidth * (i+1))}]"
+ else:
+ bslice = f"[{(accesswidth * (i + 1)) - 1}:{accesswidth * i}]"
+ segments.append(Segment(strobe, bslice))
+ else:
+ segments.append(Segment(strb_prefix, ""))
+
+ trigger = node.get_property('wbuffer_trigger')
+ is_own_trigger = (isinstance(trigger, RegNode) and trigger == node)
+
+ context = {
+ 'wbuf': self.wbuf,
+ 'wbuf_prefix': self.wbuf.get_wbuf_prefix(node),
+ 'segments': segments,
+ 'node': node,
+ 'cpuif': self.exp.cpuif,
+ 'get_resetsignal': self.exp.dereferencer.get_resetsignal,
+ 'get_always_ff_event': self.exp.dereferencer.get_always_ff_event,
+ 'is_own_trigger': is_own_trigger,
+ }
+ self.add_content(self.template.render(context))
diff --git a/src/peakrdl_regblock/write_buffering/storage_generator.py b/src/peakrdl_regblock/write_buffering/storage_generator.py
new file mode 100644
index 0000000..6b48472
--- /dev/null
+++ b/src/peakrdl_regblock/write_buffering/storage_generator.py
@@ -0,0 +1,32 @@
+from typing import TYPE_CHECKING
+
+from systemrdl.node import FieldNode, RegNode
+
+from ..struct_generator import RDLStructGenerator
+
+if TYPE_CHECKING:
+ from . import WriteBuffering
+
+class WBufStorageStructGenerator(RDLStructGenerator):
+ def __init__(self, wbuf: 'WriteBuffering') -> None:
+ super().__init__()
+ self.wbuf = wbuf
+
+ def enter_Field(self, node: FieldNode) -> None:
+ # suppress parent class's field behavior
+ pass
+
+ def enter_Reg(self, node: RegNode) -> None:
+ super().enter_Reg(node)
+
+ if not node.get_property('buffer_writes'):
+ return
+
+ regwidth = node.get_property('regwidth')
+ self.add_member("data", regwidth)
+ self.add_member("biten", regwidth)
+ self.add_member("pending")
+
+ trigger = node.get_property('wbuffer_trigger')
+ if isinstance(trigger, RegNode) and trigger == node:
+ self.add_member("trigger_q")
diff --git a/src/peakrdl_regblock/write_buffering/template.sv b/src/peakrdl_regblock/write_buffering/template.sv
new file mode 100644
index 0000000..78bbf46
--- /dev/null
+++ b/src/peakrdl_regblock/write_buffering/template.sv
@@ -0,0 +1,31 @@
+always_ff {{get_always_ff_event(cpuif.reset)}} begin
+ if({{get_resetsignal(cpuif.reset)}}) begin
+ {{wbuf_prefix}}.pending <= '0;
+ {{wbuf_prefix}}.data <= '0;
+ {{wbuf_prefix}}.biten <= '0;
+ {%- if is_own_trigger %}
+ {{wbuf_prefix}}.trigger_q <= '0;
+ {%- endif %}
+ end else begin
+ if({{wbuf.get_trigger(node)}}) begin
+ {{wbuf_prefix}}.pending <= '0;
+ {{wbuf_prefix}}.data <= '0;
+ {{wbuf_prefix}}.biten <= '0;
+ end
+ {%- for segment in segments %}
+ if({{segment.strobe}} && decoded_req_is_wr) begin
+ {{wbuf_prefix}}.pending <= '1;
+ {%- if node.inst.is_msb0_order %}
+ {{wbuf_prefix}}.data{{segment.bslice}} <= ({{wbuf_prefix}}.data{{segment.bslice}} & ~decoded_wr_biten_bswap) | (decoded_wr_data_bswap & decoded_wr_biten_bswap);
+ {{wbuf_prefix}}.biten{{segment.bslice}} <= {{wbuf_prefix}}.biten{{segment.bslice}} | decoded_wr_biten_bswap;
+ {%- else %}
+ {{wbuf_prefix}}.data{{segment.bslice}} <= ({{wbuf_prefix}}.data{{segment.bslice}} & ~decoded_wr_biten) | (decoded_wr_data & decoded_wr_biten);
+ {{wbuf_prefix}}.biten{{segment.bslice}} <= {{wbuf_prefix}}.biten{{segment.bslice}} | decoded_wr_biten;
+ {%- endif %}
+ end
+ {%- endfor %}
+ {%- if is_own_trigger %}
+ {{wbuf_prefix}}.trigger_q <= {{wbuf.get_raw_trigger(node)}};
+ {%- endif %}
+ end
+end
diff --git a/tests/.coveragerc b/tests/.coveragerc
new file mode 100644
index 0000000..05e3012
--- /dev/null
+++ b/tests/.coveragerc
@@ -0,0 +1,22 @@
+[run]
+branch = True
+#relative_files = True
+
+omit =
+ # to be covered elsewhere
+ */__peakrdl__.py
+
+[paths]
+source =
+ ../src/peakrdl_regblock/
+ */site-packages/*/peakrdl_regblock
+ */site-packages/peakrdl_regblock
+
+[report]
+exclude_lines =
+ pragma: no cover
+ raise RuntimeError
+ raise NotImplementedError
+ if TYPE_CHECKING:
+
+precision = 1
diff --git a/tests/README.md b/tests/README.md
new file mode 100644
index 0000000..bb3996c
--- /dev/null
+++ b/tests/README.md
@@ -0,0 +1,119 @@
+
+# Test Dependencies
+
+## Questa
+
+Testcases require an installation of the Questa simulator, and for `vlog` & `vsim`
+commands to be visible via the PATH environment variable.
+
+*Questa - Intel FPGA Starter Edition* can be downloaded for free from Intel:
+* Go to https://www.intel.com/content/www/us/en/collections/products/fpga/software/downloads.html?edition=pro&q=questa&s=Relevancy
+* Select latest version of Questa
+* Download Questa files.
+* Install
+ * Be sure to choose "Starter Edition" for the free version.
+* Create an account on https://licensing.intel.com
+ * press "Enroll" to register
+ * After you confirm your email, go back to this page and press "Enroll" again to finish enrollment
+* Go to https://licensing.intel.com/psg/s/sales-signup-evaluationlicenses
+* Generate a free *Starter Edition* license file for Questa
+ * Easiest to use a *fixed* license using your NIC ID (MAC address of your network card via `ifconfig`)
+* Download the license file and point the `LM_LICENSE_FILE` environment variable to the folder which contains it.
+* (optional) Delete Intel libraries to save some disk space
+ * Delete `/questa_fse/intel`
+ * Edit `/questa_fse/modelsim.ini` and remove lines that reference the `intel` libraries
+
+
+## Vivado (optional)
+
+To run synthesis tests, Vivado needs to be installed and visible via the PATH environment variable.
+
+Vivado can be downloaded for free from: https://www.xilinx.com/support/download.html
+
+
+
+## Python Packages
+Install dependencies required for running tests
+
+```bash
+python3 -m pip install -r tests/requirements.txt
+```
+
+
+
+# Running tests
+
+Tests can be launched from the test directory using `pytest`.
+Use `pytest --workers auto` to run tests in parallel.
+
+To run all tests:
+```bash
+python3 setup.py install
+pytest tests
+```
+
+You can also run a specific testcase. For example:
+```bash
+pytest tests/test_hw_access
+```
+
+Command-line arguments can be used to explicitly select which simulator/synthesis tools are used
+If unspecified, the tool will be selected automatically based on what you have installed.
+```bash
+pytest --sim-tool questa --synth-tool vivado
+```
+
+
+Alternatively, launch tests using the helper script. This handles installing
+dependencies into a virtual environment automatically.
+```bash
+cd tests
+./run.sh
+```
+
+
+
+# Test organization
+
+The goal for this test infrastructure is to make it easy to add small-standalone
+testcases, with minimal repetition/boilerplate code that is usually present in
+SystemVerilog testbenches.
+
+To accomplish this, Jinja templates are used extensively to generate the
+resulting `tb.sv` file, as well as assist in dynamic testcase parameterization.
+
+
+
+## CPU Interfaces
+Each CPU interface type is described in its own folder as follows:
+
+`lib/cpuifs//__init__.py`
+: Definitions for CPU Interface test mode classes.
+
+`lib/cpuifs//tb_inst.sv`
+: Jinja template that defines how the CPU interface is declared & instantiated in the testbench file.
+
+`lib/cpuifs//*.sv`
+: Any other files required for compilation.
+
+
+
+## Testcase
+Each testcase group has its own folder and contains the following:
+
+`test_*/__init__.py`
+: Empty file required for test discovery.
+
+`test_*/regblock.rdl`
+: Testcase RDL file. Testcase infrastructure will automatically compile this and generate the regblock output SystemVerilog.
+
+`test_*/tb_template.sv`
+: Jinja template that defines the testcase-specific sequence.
+
+`test_*/testcase.py`
+: Defines Python unittest testcase entry point.
+
+
+
+## Parameterization
+Testcase classes can be parameterized using the [parameterized](https://github.com/wolever/parameterized) extension. This allows the same testcase to be run against multiple permutations of regblock export modes such as CPU interfaces, retiming flop stages, or even RDL parameterizations.
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000..13dcb5d
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,48 @@
+def pytest_addoption(parser):
+ parser.addoption(
+ "--sim-tool",
+ choices=["questa", "xsim", "stub", "skip", "auto"],
+ default="auto",
+ help="""
+ Select the simulator to use.
+
+ stub: run the testcase using a no-op simulator stub
+ skip: skip all the simulation tests
+ auto: choose the best simulator based on what is installed
+ """
+ )
+
+ parser.addoption(
+ "--gui",
+ default=False,
+ action="store_true",
+ help=""",
+ Launch sim tool in GUI mode
+
+ Only use this option when running a single test
+ """
+ )
+
+
+ parser.addoption(
+ "--rerun",
+ default=False,
+ action="store_true",
+ help=""",
+ Re-run simulation in-place without re-exporting regblock
+
+ Useful if hand-editing a testcase interactively.
+ """
+ )
+
+ parser.addoption(
+ "--synth-tool",
+ choices=["vivado", "skip", "auto"],
+ default="auto",
+ help="""
+ Select the synthesis tool to use.
+
+ skip: skip all the simulation tests
+ auto: choose the best tool based on what is installed
+ """
+ )
diff --git a/tests/lib/__init__.py b/tests/lib/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/lib/base_testcase.py b/tests/lib/base_testcase.py
new file mode 100644
index 0000000..6c9d4b8
--- /dev/null
+++ b/tests/lib/base_testcase.py
@@ -0,0 +1,140 @@
+from typing import Optional
+import unittest
+import os
+import glob
+import shutil
+import inspect
+import pathlib
+
+import pytest
+from systemrdl import RDLCompiler
+
+from peakrdl_regblock import RegblockExporter
+from peakrdl_regblock.udps import ALL_UDPS
+
+from .cpuifs.base import CpuifTestMode
+from .cpuifs.apb4 import APB4
+
+
+class BaseTestCase(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 = APB4() # type: CpuifTestMode
+
+ # Other exporter args:
+ retime_read_fanin = False
+ retime_read_response = False
+ reuse_hwif_typedefs = True
+ retime_external = False
+ default_reset_activelow = False
+ default_reset_async = False
+
+ #: this gets auto-loaded via the _load_request autouse fixture
+ request = None # type: pytest.FixtureRequest
+
+ exporter = RegblockExporter()
+
+ @pytest.fixture(autouse=True)
+ def _load_request(self, request):
+ self.request = request
+
+ @property
+ def rerun(self) -> bool:
+ """
+ Re-run without deleting and re-generating prior output directory.
+ """
+ return self.request.config.getoption("--rerun")
+
+ def get_testcase_dir(self) -> str:
+ class_dir = os.path.dirname(inspect.getfile(self.__class__))
+ return class_dir
+
+ def get_run_dir(self) -> str:
+ this_dir = self.get_testcase_dir()
+ run_dir = os.path.join(this_dir, "run.out", self.__class__.__name__)
+ return run_dir
+
+ def _write_params(self) -> 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(self.get_run_dir(), "params.txt")
+
+ with open(path, 'w') as f:
+ for k, v in self.__class__.__dict__.items():
+ if k.startswith("_") or callable(v):
+ continue
+ f.write(f"{k}: {repr(v)}\n")
+
+
+ def export_regblock(self):
+ """
+ Call the peakrdl_regblock exporter to generate the DUT
+ """
+ this_dir = self.get_testcase_dir()
+
+ if self.rdl_file:
+ rdl_file = os.path.join(this_dir, self.rdl_file)
+ else:
+ # Find any *.rdl file in testcase dir
+ rdl_file = glob.glob(os.path.join(this_dir, "*.rdl"))[0]
+
+ rdlc = RDLCompiler()
+
+ # Load the UDPs
+ for udp in ALL_UDPS:
+ rdlc.register_udp(udp)
+ # ... including the definition
+ udp_file = os.path.join(this_dir, "../../hdl-src/regblock_udps.rdl")
+ rdlc.compile_file(udp_file)
+
+ rdlc.compile_file(rdl_file)
+ root = rdlc.elaborate(self.rdl_elab_target, "regblock", self.rdl_elab_params)
+
+ self.exporter.export(
+ root,
+ self.get_run_dir(),
+ module_name="regblock",
+ package_name="regblock_pkg",
+ cpuif_cls=self.cpuif.cpuif_cls,
+ retime_read_fanin=self.retime_read_fanin,
+ retime_read_response=self.retime_read_response,
+ reuse_hwif_typedefs=self.reuse_hwif_typedefs,
+ retime_external_reg=self.retime_external,
+ retime_external_regfile=self.retime_external,
+ retime_external_mem=self.retime_external,
+ retime_external_addrmap=self.retime_external,
+ default_reset_activelow=self.default_reset_activelow,
+ default_reset_async=self.default_reset_async,
+ )
+
+ def delete_run_dir(self) -> None:
+ run_dir = self.get_run_dir()
+ if os.path.exists(run_dir):
+ shutil.rmtree(run_dir)
+
+ def setUp(self) -> None:
+ if self.rerun:
+ return
+
+ # Create fresh build dir
+ run_dir = self.get_run_dir()
+ self.delete_run_dir()
+ pathlib.Path(run_dir).mkdir(parents=True, exist_ok=True)
+
+ self._write_params()
+
+ # Convert testcase RDL file --> SV
+ self.export_regblock()
diff --git a/tests/lib/cpuifs/__init__.py b/tests/lib/cpuifs/__init__.py
new file mode 100644
index 0000000..6ea672d
--- /dev/null
+++ b/tests/lib/cpuifs/__init__.py
@@ -0,0 +1,17 @@
+from .passthrough import Passthrough
+from .apb3 import APB3, FlatAPB3
+from .apb4 import APB4, FlatAPB4
+from .axi4lite import AXI4Lite, FlatAXI4Lite
+from .avalon import Avalon, FlatAvalon
+
+ALL_CPUIF = [
+ Passthrough(),
+ APB3(),
+ FlatAPB3(),
+ APB4(),
+ FlatAPB4(),
+ AXI4Lite(),
+ FlatAXI4Lite(),
+ Avalon(),
+ FlatAvalon(),
+]
diff --git a/tests/lib/cpuifs/apb3/__init__.py b/tests/lib/cpuifs/apb3/__init__.py
new file mode 100644
index 0000000..fffb098
--- /dev/null
+++ b/tests/lib/cpuifs/apb3/__init__.py
@@ -0,0 +1,18 @@
+from ..base import CpuifTestMode
+
+from peakrdl_regblock.cpuif.apb3 import APB3_Cpuif, APB3_Cpuif_flattened
+
+class APB3(CpuifTestMode):
+ cpuif_cls = APB3_Cpuif
+ rtl_files = [
+ "../../../../hdl-src/apb3_intf.sv",
+ ]
+ tb_files = [
+ "../../../../hdl-src/apb3_intf.sv",
+ "apb3_intf_driver.sv",
+ ]
+ tb_template = "tb_inst.sv"
+
+class FlatAPB3(APB3):
+ cpuif_cls = APB3_Cpuif_flattened
+ rtl_files = []
diff --git a/tests/lib/cpuifs/apb3/apb3_intf_driver.sv b/tests/lib/cpuifs/apb3/apb3_intf_driver.sv
new file mode 100644
index 0000000..5533f27
--- /dev/null
+++ b/tests/lib/cpuifs/apb3/apb3_intf_driver.sv
@@ -0,0 +1,116 @@
+interface apb3_intf_driver #(
+ parameter DATA_WIDTH = 32,
+ parameter ADDR_WIDTH = 32
+ )(
+ input wire clk,
+ input wire rst,
+ apb3_intf.master m_apb
+ );
+
+ timeunit 1ps;
+ timeprecision 1ps;
+
+ logic PSEL;
+ logic PENABLE;
+ logic PWRITE;
+ logic [ADDR_WIDTH-1:0] PADDR;
+ logic [DATA_WIDTH-1:0] PWDATA;
+ logic [DATA_WIDTH-1:0] PRDATA;
+ logic PREADY;
+ logic PSLVERR;
+
+ assign m_apb.PSEL = PSEL;
+ assign m_apb.PENABLE = PENABLE;
+ assign m_apb.PWRITE = PWRITE;
+ assign m_apb.PADDR = PADDR;
+ assign m_apb.PWDATA = PWDATA;
+ assign PRDATA = m_apb.PRDATA;
+ assign PREADY = m_apb.PREADY;
+ assign PSLVERR = m_apb.PSLVERR;
+
+ default clocking cb @(posedge clk);
+ default input #1step output #1;
+ output PSEL;
+ output PENABLE;
+ output PWRITE;
+ output PADDR;
+ output PWDATA;
+ input PRDATA;
+ input PREADY;
+ input PSLVERR;
+ endclocking
+
+ task automatic reset();
+ cb.PSEL <= '0;
+ cb.PENABLE <= '0;
+ cb.PWRITE <= '0;
+ cb.PADDR <= '0;
+ cb.PWDATA <= '0;
+ endtask
+
+ semaphore txn_mutex = new(1);
+
+ task automatic write(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] data);
+ txn_mutex.get();
+ ##0;
+
+ // Initiate transfer
+ cb.PSEL <= '1;
+ cb.PENABLE <= '0;
+ cb.PWRITE <= '1;
+ cb.PADDR <= addr;
+ cb.PWDATA <= data;
+ @(cb);
+
+ // active phase
+ cb.PENABLE <= '1;
+ @(cb);
+
+ // Wait for response
+ while(cb.PREADY !== 1'b1) @(cb);
+ reset();
+ txn_mutex.put();
+ endtask
+
+ task automatic read(logic [ADDR_WIDTH-1:0] addr, output logic [DATA_WIDTH-1:0] data);
+ txn_mutex.get();
+ ##0;
+
+ // Initiate transfer
+ cb.PSEL <= '1;
+ cb.PENABLE <= '0;
+ cb.PWRITE <= '0;
+ cb.PADDR <= addr;
+ cb.PWDATA <= '0;
+ @(cb);
+
+ // active phase
+ cb.PENABLE <= '1;
+ @(cb);
+
+ // 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();
+ txn_mutex.put();
+ endtask
+
+ task automatic assert_read(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] expected_data, logic [DATA_WIDTH-1:0] mask = '1);
+ logic [DATA_WIDTH-1:0] data;
+ read(addr, data);
+ data &= mask;
+ 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/tests/lib/cpuifs/apb3/tb_inst.sv b/tests/lib/cpuifs/apb3/tb_inst.sv
new file mode 100644
index 0000000..aec79b1
--- /dev/null
+++ b/tests/lib/cpuifs/apb3/tb_inst.sv
@@ -0,0 +1,32 @@
+{% sv_line_anchor %}
+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") %}
+{% sv_line_anchor %}
+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/tests/lib/cpuifs/apb4/__init__.py b/tests/lib/cpuifs/apb4/__init__.py
new file mode 100644
index 0000000..4a93b67
--- /dev/null
+++ b/tests/lib/cpuifs/apb4/__init__.py
@@ -0,0 +1,18 @@
+from ..base import CpuifTestMode
+
+from peakrdl_regblock.cpuif.apb4 import APB4_Cpuif, APB4_Cpuif_flattened
+
+class APB4(CpuifTestMode):
+ cpuif_cls = APB4_Cpuif
+ rtl_files = [
+ "../../../../hdl-src/apb4_intf.sv",
+ ]
+ tb_files = [
+ "../../../../hdl-src/apb4_intf.sv",
+ "apb4_intf_driver.sv",
+ ]
+ tb_template = "tb_inst.sv"
+
+class FlatAPB4(APB4):
+ cpuif_cls = APB4_Cpuif_flattened
+ rtl_files = []
diff --git a/tests/lib/cpuifs/apb4/apb4_intf_driver.sv b/tests/lib/cpuifs/apb4/apb4_intf_driver.sv
new file mode 100644
index 0000000..cf5258f
--- /dev/null
+++ b/tests/lib/cpuifs/apb4/apb4_intf_driver.sv
@@ -0,0 +1,128 @@
+interface apb4_intf_driver #(
+ parameter DATA_WIDTH = 32,
+ parameter ADDR_WIDTH = 32
+ )(
+ input wire clk,
+ input wire rst,
+ apb4_intf.master m_apb
+ );
+
+ timeunit 1ps;
+ timeprecision 1ps;
+
+ logic PSEL;
+ logic PENABLE;
+ logic PWRITE;
+ logic [2:0] PPROT;
+ logic [ADDR_WIDTH-1:0] PADDR;
+ logic [DATA_WIDTH-1:0] PWDATA;
+ logic [DATA_WIDTH/8-1:0] PSTRB;
+ logic [DATA_WIDTH-1:0] PRDATA;
+ logic PREADY;
+ logic PSLVERR;
+
+ assign m_apb.PSEL = PSEL;
+ assign m_apb.PENABLE = PENABLE;
+ assign m_apb.PWRITE = PWRITE;
+ assign m_apb.PPROT = PPROT;
+ assign m_apb.PADDR = PADDR;
+ assign m_apb.PWDATA = PWDATA;
+ assign m_apb.PSTRB = PSTRB;
+ assign PRDATA = m_apb.PRDATA;
+ assign PREADY = m_apb.PREADY;
+ assign PSLVERR = m_apb.PSLVERR;
+
+ default clocking cb @(posedge clk);
+ default input #1step output #1;
+ output PSEL;
+ output PENABLE;
+ output PWRITE;
+ output PPROT;
+ output PADDR;
+ output PWDATA;
+ output PSTRB;
+ input PRDATA;
+ input PREADY;
+ input PSLVERR;
+ endclocking
+
+ task automatic reset();
+ cb.PSEL <= '0;
+ cb.PENABLE <= '0;
+ cb.PWRITE <= '0;
+ cb.PPROT <= '0;
+ cb.PADDR <= '0;
+ cb.PWDATA <= '0;
+ cb.PSTRB <= '0;
+ endtask
+
+ semaphore txn_mutex = new(1);
+
+ task automatic write(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] data, logic [DATA_WIDTH/8-1:0] strb = '1);
+ txn_mutex.get();
+ ##0;
+
+ // Initiate transfer
+ cb.PSEL <= '1;
+ cb.PENABLE <= '0;
+ cb.PWRITE <= '1;
+ cb.PPROT <= '0;
+ cb.PADDR <= addr;
+ cb.PWDATA <= data;
+ cb.PSTRB <= strb;
+ @(cb);
+
+ // active phase
+ cb.PENABLE <= '1;
+ @(cb);
+
+ // Wait for response
+ while(cb.PREADY !== 1'b1) @(cb);
+ reset();
+ txn_mutex.put();
+ endtask
+
+ task automatic read(logic [ADDR_WIDTH-1:0] addr, output logic [DATA_WIDTH-1:0] data);
+ txn_mutex.get();
+ ##0;
+
+ // Initiate transfer
+ cb.PSEL <= '1;
+ cb.PENABLE <= '0;
+ cb.PWRITE <= '0;
+ cb.PPROT <= '0;
+ cb.PADDR <= addr;
+ cb.PWDATA <= '0;
+ cb.PSTRB <= '0;
+ @(cb);
+
+ // active phase
+ cb.PENABLE <= '1;
+ @(cb);
+
+ // 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();
+ txn_mutex.put();
+ endtask
+
+ task automatic assert_read(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] expected_data, logic [DATA_WIDTH-1:0] mask = '1);
+ logic [DATA_WIDTH-1:0] data;
+ read(addr, data);
+ data &= mask;
+ 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/tests/lib/cpuifs/apb4/tb_inst.sv b/tests/lib/cpuifs/apb4/tb_inst.sv
new file mode 100644
index 0000000..d769854
--- /dev/null
+++ b/tests/lib/cpuifs/apb4/tb_inst.sv
@@ -0,0 +1,36 @@
+{% sv_line_anchor %}
+apb4_intf #(
+ .DATA_WIDTH({{exporter.cpuif.data_width}}),
+ .ADDR_WIDTH({{exporter.cpuif.addr_width}})
+) s_apb();
+apb4_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") %}
+{% sv_line_anchor %}
+wire s_apb_psel;
+wire s_apb_penable;
+wire s_apb_pwrite;
+wire [2:0] s_apb_pprot;
+wire [{{exporter.cpuif.addr_width - 1}}:0] s_apb_paddr;
+wire [{{exporter.cpuif.data_width - 1}}:0] s_apb_pwdata;
+wire [{{exporter.cpuif.data_width_bytes - 1}}:0] s_apb_pstrb;
+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_pprot = s_apb.PPROT;
+assign s_apb_paddr = s_apb.PADDR;
+assign s_apb_pwdata = s_apb.PWDATA;
+assign s_apb_pstrb = s_apb.PSTRB;
+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/tests/lib/cpuifs/avalon/__init__.py b/tests/lib/cpuifs/avalon/__init__.py
new file mode 100644
index 0000000..79672ab
--- /dev/null
+++ b/tests/lib/cpuifs/avalon/__init__.py
@@ -0,0 +1,18 @@
+from ..base import CpuifTestMode
+
+from peakrdl_regblock.cpuif.avalon import Avalon_Cpuif, Avalon_Cpuif_flattened
+
+class Avalon(CpuifTestMode):
+ cpuif_cls = Avalon_Cpuif
+ rtl_files = [
+ "../../../../hdl-src/avalon_mm_intf.sv",
+ ]
+ tb_files = [
+ "../../../../hdl-src/avalon_mm_intf.sv",
+ "avalon_mm_intf_driver.sv",
+ ]
+ tb_template = "tb_inst.sv"
+
+class FlatAvalon(Avalon):
+ cpuif_cls = Avalon_Cpuif_flattened
+ rtl_files = []
diff --git a/tests/lib/cpuifs/avalon/avalon_mm_intf_driver.sv b/tests/lib/cpuifs/avalon/avalon_mm_intf_driver.sv
new file mode 100644
index 0000000..173be6a
--- /dev/null
+++ b/tests/lib/cpuifs/avalon/avalon_mm_intf_driver.sv
@@ -0,0 +1,138 @@
+interface avalon_mm_intf_driver #(
+ parameter DATA_WIDTH = 32,
+ parameter ADDR_WIDTH = 32
+ )(
+ input wire clk,
+ input wire rst,
+ avalon_mm_intf.host avalon
+ );
+ timeunit 1ps;
+ timeprecision 1ps;
+
+ localparam ADDR_PAD = $clog2(DATA_WIDTH/8);
+ localparam WORD_ADDR_WIDTH = ADDR_WIDTH - ADDR_PAD;
+
+ logic av_read;
+ logic av_write;
+ logic av_waitrequest;
+ logic [WORD_ADDR_WIDTH-1:0] av_address;
+ logic [DATA_WIDTH-1:0] av_writedata;
+ logic [DATA_WIDTH/8-1:0] av_byteenable;
+ logic av_readdatavalid;
+ logic av_writeresponsevalid;
+ logic [DATA_WIDTH-1:0] av_readdata;
+ logic [1:0] av_response;
+
+ assign avalon.read = av_read;
+ assign avalon.write = av_write;
+ assign av_waitrequest = avalon.waitrequest;
+ assign avalon.address = av_address;
+ assign avalon.writedata = av_writedata;
+ assign avalon.byteenable = av_byteenable;
+ assign av_readdatavalid = avalon.readdatavalid;
+ assign av_writeresponsevalid = avalon.writeresponsevalid;
+ assign av_readdata = avalon.readdata;
+ assign av_response = avalon.response;
+
+ default clocking cb @(posedge clk);
+ default input #1step output #1;
+ output av_read;
+ output av_write;
+ input av_waitrequest;
+ output av_address;
+ output av_writedata;
+ output av_byteenable;
+ input av_readdatavalid;
+ input av_writeresponsevalid;
+ input av_readdata;
+ input av_response;
+ endclocking
+
+ task automatic reset();
+ cb.av_read <= '0;
+ cb.av_write <= '0;
+ cb.av_address <= '0;
+ cb.av_writedata <= '0;
+ cb.av_byteenable <= '0;
+ endtask
+
+ semaphore req_mutex = new(1);
+ semaphore resp_mutex = new(1);
+
+ task automatic write(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] data, logic [DATA_WIDTH/8-1:0] strb = '1);
+ fork
+ begin
+ req_mutex.get();
+ ##0;
+ // Initiate transfer
+ cb.av_write <= '1;
+ cb.av_address <= (addr >> ADDR_PAD);
+ cb.av_writedata <= data;
+ cb.av_byteenable <= strb;
+ @(cb);
+
+ // Wait for transfer to be accepted
+ while(cb.av_waitrequest == 1'b1) @(cb);
+ reset();
+ req_mutex.put();
+ end
+
+ begin
+ resp_mutex.get();
+ @cb;
+ // Wait for response
+ while(cb.av_writeresponsevalid !== 1'b1) @(cb);
+ assert(!$isunknown(cb.av_response)) else $error("Read from 0x%0x returned X's on av_response", addr);
+ resp_mutex.put();
+ end
+ join
+ endtask
+
+ task automatic read(logic [ADDR_WIDTH-1:0] addr, output logic [DATA_WIDTH-1:0] data);
+ fork
+ begin
+ req_mutex.get();
+ ##0;
+ // Initiate transfer
+ cb.av_read <= '1;
+ cb.av_address <= (addr >> ADDR_PAD);
+ @(cb);
+
+ // Wait for transfer to be accepted
+ while(cb.av_waitrequest == 1'b1) @(cb);
+ reset();
+ req_mutex.put();
+ end
+
+ begin
+ resp_mutex.get();
+ @cb;
+ // Wait for response
+ while(cb.av_readdatavalid !== 1'b1) @(cb);
+ assert(!$isunknown(cb.av_readdata)) else $error("Read from 0x%0x returned X's on av_response", av_readdata);
+ assert(!$isunknown(cb.av_response)) else $error("Read from 0x%0x returned X's on av_response", addr);
+ data = cb.av_readdata;
+ resp_mutex.put();
+ end
+ join
+ endtask
+
+ task automatic assert_read(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] expected_data, logic [DATA_WIDTH-1:0] mask = '1);
+ logic [DATA_WIDTH-1:0] data;
+ read(addr, data);
+ data &= mask;
+ 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.av_waitrequest)) else $error("Saw X on av_waitrequest!");
+ if(!rst) assert(!$isunknown(cb.av_readdatavalid)) else $error("Saw X on av_readdatavalid!");
+ if(!rst) assert(!$isunknown(cb.av_writeresponsevalid)) else $error("Saw X on av_writeresponsevalid!");
+ end
+
+endinterface
diff --git a/tests/lib/cpuifs/avalon/tb_inst.sv b/tests/lib/cpuifs/avalon/tb_inst.sv
new file mode 100644
index 0000000..e5b2303
--- /dev/null
+++ b/tests/lib/cpuifs/avalon/tb_inst.sv
@@ -0,0 +1,36 @@
+{% sv_line_anchor %}
+avalon_mm_intf #(
+ .DATA_WIDTH({{exporter.cpuif.data_width}}),
+ .ADDR_WIDTH({{exporter.cpuif.word_addr_width}})
+) avalon();
+avalon_mm_intf_driver #(
+ .DATA_WIDTH({{exporter.cpuif.data_width}}),
+ .ADDR_WIDTH({{exporter.cpuif.addr_width}})
+) cpuif (
+ .clk(clk),
+ .rst(rst),
+ .avalon(avalon)
+);
+{% if type(cpuif).__name__.startswith("Flat") %}
+{% sv_line_anchor %}
+wire avalon_read;
+wire avalon_write;
+wire avalon_waitrequest;
+wire [{{exporter.cpuif.word_addr_width - 1}}:0] avalon_address;
+wire [{{exporter.cpuif.data_width - 1}}:0] avalon_writedata;
+wire [{{exporter.cpuif.data_width_bytes - 1}}:0] avalon_byteenable;
+wire avalon_readdatavalid;
+wire avalon_writeresponsevalid;
+wire [{{exporter.cpuif.data_width - 1}}:0] avalon_readdata;
+wire [1:0] avalon_response;
+assign avalon_read = avalon.read;
+assign avalon_write = avalon.write;
+assign avalon.waitrequest = avalon_waitrequest;
+assign avalon_address = avalon.address;
+assign avalon_writedata = avalon.writedata;
+assign avalon_byteenable = avalon.byteenable;
+assign avalon.readdatavalid = avalon_readdatavalid;
+assign avalon.writeresponsevalid = avalon_writeresponsevalid;
+assign avalon.readdata = avalon_readdata;
+assign avalon.response = avalon_response;
+{% endif %}
diff --git a/tests/lib/cpuifs/axi4lite/__init__.py b/tests/lib/cpuifs/axi4lite/__init__.py
new file mode 100644
index 0000000..ef3147c
--- /dev/null
+++ b/tests/lib/cpuifs/axi4lite/__init__.py
@@ -0,0 +1,18 @@
+from ..base import CpuifTestMode
+
+from peakrdl_regblock.cpuif.axi4lite import AXI4Lite_Cpuif, AXI4Lite_Cpuif_flattened
+
+class AXI4Lite(CpuifTestMode):
+ cpuif_cls = AXI4Lite_Cpuif
+ rtl_files = [
+ "../../../../hdl-src/axi4lite_intf.sv",
+ ]
+ tb_files = [
+ "../../../../hdl-src/axi4lite_intf.sv",
+ "axi4lite_intf_driver.sv",
+ ]
+ tb_template = "tb_inst.sv"
+
+class FlatAXI4Lite(AXI4Lite):
+ cpuif_cls = AXI4Lite_Cpuif_flattened
+ rtl_files = []
diff --git a/tests/lib/cpuifs/axi4lite/axi4lite_intf_driver.sv b/tests/lib/cpuifs/axi4lite/axi4lite_intf_driver.sv
new file mode 100644
index 0000000..856048a
--- /dev/null
+++ b/tests/lib/cpuifs/axi4lite/axi4lite_intf_driver.sv
@@ -0,0 +1,263 @@
+interface axi4lite_intf_driver #(
+ parameter DATA_WIDTH = 32,
+ parameter ADDR_WIDTH = 32
+ )(
+ input wire clk,
+ input wire rst,
+ axi4lite_intf.master m_axil
+ );
+
+ timeunit 1ps;
+ timeprecision 1ps;
+
+ logic AWREADY;
+ logic AWVALID;
+ logic [ADDR_WIDTH-1:0] AWADDR;
+ logic [2:0] AWPROT;
+
+ logic WREADY;
+ logic WVALID;
+ logic [DATA_WIDTH-1:0] WDATA;
+ logic [DATA_WIDTH/8-1:0] WSTRB;
+
+ logic BREADY;
+ logic BVALID;
+ logic [1:0] BRESP;
+
+ logic ARREADY;
+ logic ARVALID;
+ logic [ADDR_WIDTH-1:0] ARADDR;
+ logic [2:0] ARPROT;
+
+ logic RREADY;
+ logic RVALID;
+ logic [DATA_WIDTH-1:0] RDATA;
+ logic [1:0] RRESP;
+
+ assign AWREADY = m_axil.AWREADY;
+ assign m_axil.AWVALID = AWVALID;
+ assign m_axil.AWADDR = AWADDR;
+ assign m_axil.AWPROT = AWPROT;
+ assign WREADY = m_axil.WREADY;
+ assign m_axil.WVALID = WVALID;
+ assign m_axil.WDATA = WDATA;
+ assign m_axil.WSTRB = WSTRB;
+ assign m_axil.BREADY = BREADY;
+ assign BVALID = m_axil.BVALID;
+ assign BRESP = m_axil.BRESP;
+ assign ARREADY = m_axil.ARREADY;
+ assign m_axil.ARVALID = ARVALID;
+ assign m_axil.ARADDR = ARADDR;
+ assign m_axil.ARPROT = ARPROT;
+ assign m_axil.RREADY = RREADY;
+ assign RVALID = m_axil.RVALID;
+ assign RDATA = m_axil.RDATA;
+ assign RRESP = m_axil.RRESP;
+
+ default clocking cb @(posedge clk);
+ default input #1step output #1;
+ input AWREADY;
+ output AWVALID;
+ output AWADDR;
+ output AWPROT;
+ input WREADY;
+ output WVALID;
+ output WDATA;
+ output WSTRB;
+ inout BREADY;
+ input BVALID;
+ input BRESP;
+ input ARREADY;
+ output ARVALID;
+ output ARADDR;
+ output ARPROT;
+ inout RREADY;
+ input RVALID;
+ input RDATA;
+ input RRESP;
+ endclocking
+
+ task automatic reset();
+ cb.AWVALID <= '0;
+ cb.AWADDR <= '0;
+ cb.AWPROT <= '0;
+ cb.WVALID <= '0;
+ cb.WDATA <= '0;
+ cb.WSTRB <= '0;
+ cb.ARVALID <= '0;
+ cb.ARADDR <= '0;
+ cb.ARPROT <= '0;
+ endtask
+
+ initial forever begin
+ cb.RREADY <= $urandom_range(1, 0);
+ cb.BREADY <= $urandom_range(1, 0);
+ @cb;
+ end
+
+ //--------------------------------------------------------------------------
+ typedef struct {
+ logic [1:0] bresp;
+ } write_response_t;
+
+ class write_request_t;
+ mailbox #(write_response_t) response_mbx;
+ logic [ADDR_WIDTH-1:0] addr;
+ logic [DATA_WIDTH-1:0] data;
+ logic [DATA_WIDTH/8-1:0] strb;
+ function new();
+ this.response_mbx = new();
+ endfunction
+ endclass
+
+ mailbox #(write_request_t) aw_mbx = new();
+ mailbox #(write_request_t) w_mbx = new();
+ write_request_t write_queue[$];
+
+ // Issue AW transfers
+ initial forever begin
+ write_request_t req;
+ aw_mbx.get(req);
+ ##0;
+ repeat($urandom_range(2,0)) @cb;
+ cb.AWVALID <= '1;
+ cb.AWADDR <= req.addr;
+ cb.AWPROT <= '0;
+ @(cb);
+ while(cb.AWREADY !== 1'b1) @(cb);
+ cb.AWVALID <= '0;
+ end
+
+ // Issue W transfers
+ initial forever begin
+ write_request_t req;
+ w_mbx.get(req);
+ ##0;
+ repeat($urandom_range(2,0)) @cb;
+ cb.WVALID <= '1;
+ cb.WDATA <= req.data;
+ cb.WSTRB <= req.strb;
+ @(cb);
+ while(cb.WREADY !== 1'b1) @(cb);
+ cb.WVALID <= '0;
+ cb.WSTRB <= '0;
+ end
+
+ // Listen for R responses
+ initial forever begin
+ @cb;
+ while(rst || !(cb.BREADY === 1'b1 && cb.BVALID === 1'b1)) @cb;
+ if(write_queue.size() != 0) begin
+ // Can match this response with an existing request.
+ // Send response to requestor
+ write_request_t req;
+ write_response_t resp;
+ req = write_queue.pop_front();
+ resp.bresp = cb.BRESP;
+ req.response_mbx.put(resp);
+ end else begin
+ $error("Got unmatched write response");
+ end
+ end
+
+ task automatic write(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] data, logic [DATA_WIDTH/8-1:0] strb = '1);
+ write_request_t req;
+ write_response_t resp;
+
+ req = new();
+ req.addr = addr;
+ req.data = data;
+ req.strb = strb;
+
+ aw_mbx.put(req);
+ w_mbx.put(req);
+ write_queue.push_back(req);
+
+ // Wait for response
+ req.response_mbx.get(resp);
+ assert(!$isunknown(resp.bresp)) else $error("Read from 0x%0x returned X's on BRESP", addr);
+ endtask
+
+ //--------------------------------------------------------------------------
+ typedef struct {
+ logic [DATA_WIDTH-1: 0] rdata;
+ logic [1:0] rresp;
+ } read_response_t;
+
+ class read_request_t;
+ mailbox #(read_response_t) response_mbx;
+ function new();
+ this.response_mbx = new();
+ endfunction
+ endclass
+
+ semaphore txn_ar_mutex = new(1);
+ read_request_t read_queue[$];
+
+ // Listen for R responses
+ initial forever begin
+ @cb;
+ while(rst || !(cb.RREADY === 1'b1 && cb.RVALID === 1'b1)) @cb;
+ if(read_queue.size() != 0) begin
+ // Can match this response with an existing request.
+ // Send response to requestor
+ read_request_t req;
+ read_response_t resp;
+ req = read_queue.pop_front();
+ resp.rdata = cb.RDATA;
+ resp.rresp = cb.RRESP;
+ req.response_mbx.put(resp);
+ end else begin
+ $error("Got unmatched read response");
+ end
+ end
+
+ task automatic read(logic [ADDR_WIDTH-1:0] addr, output logic [DATA_WIDTH-1:0] data);
+ read_request_t req;
+ read_response_t resp;
+
+ txn_ar_mutex.get();
+ // Issue read request
+ ##0;
+ cb.ARVALID <= '1;
+ cb.ARADDR <= addr;
+ cb.ARPROT <= '0;
+ @(cb);
+ while(cb.ARREADY !== 1'b1) @(cb);
+ cb.ARVALID <= '0;
+
+ // Push new request into queue
+ req = new();
+ read_queue.push_back(req);
+ txn_ar_mutex.put();
+
+ // Wait for response
+ req.response_mbx.get(resp);
+
+ assert(!$isunknown(resp.rdata)) else $error("Read from 0x%0x returned X's on RDATA", addr);
+ assert(!$isunknown(resp.rresp)) else $error("Read from 0x%0x returned X's on RRESP", addr);
+ data = resp.rdata;
+ endtask
+
+ task automatic assert_read(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] expected_data, logic [DATA_WIDTH-1:0] mask = '1);
+ logic [DATA_WIDTH-1:0] data;
+ read(addr, data);
+ data &= mask;
+ 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.AWREADY)) else $error("Saw X on AWREADY!");
+ if(!rst) assert(!$isunknown(cb.WREADY)) else $error("Saw X on WREADY!");
+ if(!rst) assert(!$isunknown(cb.BVALID)) else $error("Saw X on BVALID!");
+ if(!rst) assert(!$isunknown(cb.ARREADY)) else $error("Saw X on ARREADY!");
+ if(!rst) assert(!$isunknown(cb.RVALID)) else $error("Saw X on RVALID!");
+ end
+
+endinterface
diff --git a/tests/lib/cpuifs/axi4lite/tb_inst.sv b/tests/lib/cpuifs/axi4lite/tb_inst.sv
new file mode 100644
index 0000000..5b3f6fa
--- /dev/null
+++ b/tests/lib/cpuifs/axi4lite/tb_inst.sv
@@ -0,0 +1,54 @@
+{% sv_line_anchor %}
+axi4lite_intf #(
+ .DATA_WIDTH({{exporter.cpuif.data_width}}),
+ .ADDR_WIDTH({{exporter.cpuif.addr_width}})
+) s_axil();
+axi4lite_intf_driver #(
+ .DATA_WIDTH({{exporter.cpuif.data_width}}),
+ .ADDR_WIDTH({{exporter.cpuif.addr_width}})
+) cpuif (
+ .clk(clk),
+ .rst(rst),
+ .m_axil(s_axil)
+);
+{% if type(cpuif).__name__.startswith("Flat") %}
+{% sv_line_anchor %}
+wire s_axil_awready;
+wire s_axil_awvalid;
+wire [{{exporter.cpuif.addr_width - 1}}:0] s_axil_awaddr;
+wire [2:0] s_axil_awprot;
+wire s_axil_wready;
+wire s_axil_wvalid;
+wire [{{exporter.cpuif.data_width - 1}}:0] s_axil_wdata;
+wire [{{exporter.cpuif.data_width_bytes - 1}}:0] s_axil_wstrb;
+wire s_axil_bready;
+wire s_axil_bvalid;
+wire [1:0] s_axil_bresp;
+wire s_axil_arready;
+wire s_axil_arvalid;
+wire [{{exporter.cpuif.addr_width - 1}}:0] s_axil_araddr;
+wire [2:0] s_axil_arprot;
+wire s_axil_rready;
+wire s_axil_rvalid;
+wire [{{exporter.cpuif.data_width - 1}}:0] s_axil_rdata;
+wire [1:0] s_axil_rresp;
+assign s_axil.AWREADY = s_axil_awready;
+assign s_axil_awvalid = s_axil.AWVALID;
+assign s_axil_awaddr = s_axil.AWADDR;
+assign s_axil_awprot = s_axil.AWPROT;
+assign s_axil.WREADY = s_axil_wready;
+assign s_axil_wvalid = s_axil.WVALID;
+assign s_axil_wdata = s_axil.WDATA;
+assign s_axil_wstrb = s_axil.WSTRB;
+assign s_axil_bready = s_axil.BREADY;
+assign s_axil.BVALID = s_axil_bvalid;
+assign s_axil.BRESP = s_axil_bresp;
+assign s_axil.ARREADY = s_axil_arready;
+assign s_axil_arvalid = s_axil.ARVALID;
+assign s_axil_araddr = s_axil.ARADDR;
+assign s_axil_arprot = s_axil.ARPROT;
+assign s_axil_rready = s_axil.RREADY;
+assign s_axil.RVALID = s_axil_rvalid;
+assign s_axil.RDATA = s_axil_rdata;
+assign s_axil.RRESP = s_axil_rresp;
+{% endif %}
diff --git a/tests/lib/cpuifs/base.py b/tests/lib/cpuifs/base.py
new file mode 100644
index 0000000..c53358f
--- /dev/null
+++ b/tests/lib/cpuifs/base.py
@@ -0,0 +1,87 @@
+from typing import List, TYPE_CHECKING
+import os
+import inspect
+
+import jinja2 as jj
+
+from peakrdl_regblock.cpuif.base import CpuifBase
+
+from ..sv_line_anchor import SVLineAnchor
+
+if TYPE_CHECKING:
+ from peakrdl_regblock import RegblockExporter
+ from ..sim_testcase import SimTestCase
+
+class CpuifTestMode:
+ cpuif_cls = None # type: CpuifBase
+
+ # Files required by the DUT
+ # Paths are relative to the class that assigns this
+ rtl_files = [] # type: List[str]
+
+ # Files required by the sim testbench
+ # Paths are relative to the class that assigns this
+ tb_files = [] # type: List[str]
+
+ # Path is relative to the class that assigns this
+ tb_template = ""
+
+
+ def _get_class_dir_of_variable(self, varname:str) -> str:
+ """
+ Traverse up the MRO and find the first class that explicitly assigns
+ the variable of name varname. Returns the directory that contains the
+ class definition.
+ """
+ for cls in inspect.getmro(self.__class__):
+ if varname in cls.__dict__:
+ class_dir = os.path.dirname(inspect.getfile(cls))
+ return class_dir
+ raise RuntimeError
+
+
+ def _get_file_paths(self, varname:str) -> List[str]:
+ class_dir = self._get_class_dir_of_variable(varname)
+ files = getattr(self, varname)
+ cwd = os.getcwd()
+
+ new_files = []
+ for file in files:
+ relpath = os.path.relpath(
+ os.path.join(class_dir, file),
+ cwd
+ )
+ new_files.append(relpath)
+ return new_files
+
+
+ def get_sim_files(self) -> List[str]:
+ files = self._get_file_paths("rtl_files") + self._get_file_paths("tb_files")
+ unique_files = []
+ [unique_files.append(f) for f in files if f not in unique_files]
+ return unique_files
+
+
+ def get_synth_files(self) -> List[str]:
+ return self._get_file_paths("rtl_files")
+
+
+ def get_tb_inst(self, testcase: 'SimTestCase', exporter: 'RegblockExporter') -> str:
+ class_dir = self._get_class_dir_of_variable("tb_template")
+ loader = jj.FileSystemLoader(class_dir)
+ jj_env = jj.Environment(
+ loader=loader,
+ undefined=jj.StrictUndefined,
+ extensions=[SVLineAnchor],
+ )
+
+ context = {
+ "cpuif": self,
+ "testcase": testcase,
+ "exporter": exporter,
+ "type": type,
+ }
+
+ template = jj_env.get_template(self.tb_template)
+
+ return template.render(context)
diff --git a/tests/lib/cpuifs/passthrough/__init__.py b/tests/lib/cpuifs/passthrough/__init__.py
new file mode 100644
index 0000000..04ee62f
--- /dev/null
+++ b/tests/lib/cpuifs/passthrough/__init__.py
@@ -0,0 +1,11 @@
+from ..base import CpuifTestMode
+
+from peakrdl_regblock.cpuif.passthrough import PassthroughCpuif
+
+class Passthrough(CpuifTestMode):
+ cpuif_cls = PassthroughCpuif
+ rtl_files = []
+ tb_files = [
+ "passthrough_driver.sv",
+ ]
+ tb_template = "tb_inst.sv"
diff --git a/tests/lib/cpuifs/passthrough/passthrough_driver.sv b/tests/lib/cpuifs/passthrough/passthrough_driver.sv
new file mode 100644
index 0000000..9145172
--- /dev/null
+++ b/tests/lib/cpuifs/passthrough/passthrough_driver.sv
@@ -0,0 +1,123 @@
+interface passthrough_driver #(
+ parameter DATA_WIDTH = 32,
+ parameter ADDR_WIDTH = 32
+ )(
+ input wire clk,
+ input wire rst,
+
+ output logic m_cpuif_req,
+ output logic m_cpuif_req_is_wr,
+ output logic [ADDR_WIDTH-1:0] m_cpuif_addr,
+ output logic [DATA_WIDTH-1:0] m_cpuif_wr_data,
+ output logic [DATA_WIDTH-1:0] m_cpuif_wr_biten,
+ input wire m_cpuif_req_stall_wr,
+ input wire m_cpuif_req_stall_rd,
+ input wire m_cpuif_rd_ack,
+ input wire m_cpuif_rd_err,
+ input wire [DATA_WIDTH-1:0] m_cpuif_rd_data,
+ input wire m_cpuif_wr_ack,
+ input wire m_cpuif_wr_err
+ );
+
+ timeunit 1ps;
+ timeprecision 1ps;
+
+ default clocking cb @(posedge clk);
+ default input #1step output #1;
+ output m_cpuif_req;
+ output m_cpuif_req_is_wr;
+ output m_cpuif_addr;
+ output m_cpuif_wr_data;
+ output m_cpuif_wr_biten;
+ input m_cpuif_req_stall_wr;
+ input m_cpuif_req_stall_rd;
+ input m_cpuif_rd_ack;
+ input m_cpuif_rd_err;
+ input m_cpuif_rd_data;
+ input m_cpuif_wr_ack;
+ input m_cpuif_wr_err;
+ endclocking
+
+ task automatic reset();
+ cb.m_cpuif_req <= '0;
+ cb.m_cpuif_req_is_wr <= '0;
+ cb.m_cpuif_addr <= '0;
+ cb.m_cpuif_wr_data <= '0;
+ cb.m_cpuif_wr_biten <= '0;
+ endtask
+
+ semaphore txn_req_mutex = new(1);
+ semaphore txn_resp_mutex = new(1);
+
+ task automatic write(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] data, logic [DATA_WIDTH-1:0] biten = '1);
+ fork
+ begin
+ // Initiate transfer
+ txn_req_mutex.get();
+ ##0;
+ cb.m_cpuif_req <= '1;
+ cb.m_cpuif_req_is_wr <= '1;
+ cb.m_cpuif_addr <= addr;
+ cb.m_cpuif_wr_data <= data;
+ cb.m_cpuif_wr_biten <= biten;
+ @(cb);
+ while(cb.m_cpuif_req_stall_wr !== 1'b0) @(cb);
+ reset();
+ txn_req_mutex.put();
+ end
+
+ begin
+ // Wait for response
+ txn_resp_mutex.get();
+ @cb;
+ while(cb.m_cpuif_wr_ack !== 1'b1) @(cb);
+ txn_resp_mutex.put();
+ end
+ join
+ endtask
+
+ task automatic read(logic [ADDR_WIDTH-1:0] addr, output logic [DATA_WIDTH-1:0] data);
+ fork
+ begin
+ // Initiate transfer
+ txn_req_mutex.get();
+ ##0;
+ cb.m_cpuif_req <= '1;
+ cb.m_cpuif_req_is_wr <= '0;
+ cb.m_cpuif_addr <= addr;
+ @(cb);
+ while(cb.m_cpuif_req_stall_rd !== 1'b0) @(cb);
+ reset();
+ txn_req_mutex.put();
+ end
+
+ begin
+ // Wait for response
+ txn_resp_mutex.get();
+ @cb;
+ while(cb.m_cpuif_rd_ack !== 1'b1) @(cb);
+ assert(!$isunknown(cb.m_cpuif_rd_data)) else $error("Read from 0x%0x returned X's on m_cpuif_rd_data", addr);
+ data = cb.m_cpuif_rd_data;
+ txn_resp_mutex.put();
+ end
+ join
+ endtask
+
+ task automatic assert_read(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] expected_data, logic [DATA_WIDTH-1:0] mask = '1);
+ logic [DATA_WIDTH-1:0] data;
+ read(addr, data);
+ data &= mask;
+ 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.m_cpuif_rd_ack)) else $error("Saw X on m_cpuif_rd_ack!");
+ if(!rst) assert(!$isunknown(cb.m_cpuif_wr_ack)) else $error("Saw X on m_cpuif_wr_ack!");
+ end
+
+endinterface
diff --git a/tests/lib/cpuifs/passthrough/tb_inst.sv b/tests/lib/cpuifs/passthrough/tb_inst.sv
new file mode 100644
index 0000000..e33b9fe
--- /dev/null
+++ b/tests/lib/cpuifs/passthrough/tb_inst.sv
@@ -0,0 +1,32 @@
+{% sv_line_anchor %}
+wire s_cpuif_req;
+wire s_cpuif_req_is_wr;
+wire [{{exporter.cpuif.addr_width-1}}:0] s_cpuif_addr;
+wire [{{exporter.cpuif.data_width-1}}:0] s_cpuif_wr_data;
+wire [{{exporter.cpuif.data_width-1}}:0] s_cpuif_wr_biten;
+wire s_cpuif_req_stall_wr;
+wire s_cpuif_req_stall_rd;
+wire s_cpuif_rd_ack;
+wire s_cpuif_rd_err;
+wire [{{exporter.cpuif.data_width-1}}:0] s_cpuif_rd_data;
+wire s_cpuif_wr_ack;
+wire s_cpuif_wr_err;
+passthrough_driver #(
+ .DATA_WIDTH({{exporter.cpuif.data_width}}),
+ .ADDR_WIDTH({{exporter.cpuif.addr_width}})
+) cpuif (
+ .clk(clk),
+ .rst(rst),
+ .m_cpuif_req(s_cpuif_req),
+ .m_cpuif_req_is_wr(s_cpuif_req_is_wr),
+ .m_cpuif_addr(s_cpuif_addr),
+ .m_cpuif_wr_data(s_cpuif_wr_data),
+ .m_cpuif_wr_biten(s_cpuif_wr_biten),
+ .m_cpuif_req_stall_wr(s_cpuif_req_stall_wr),
+ .m_cpuif_req_stall_rd(s_cpuif_req_stall_rd),
+ .m_cpuif_rd_ack(s_cpuif_rd_ack),
+ .m_cpuif_rd_err(s_cpuif_rd_err),
+ .m_cpuif_rd_data(s_cpuif_rd_data),
+ .m_cpuif_wr_ack(s_cpuif_wr_ack),
+ .m_cpuif_wr_err(s_cpuif_wr_err)
+);
diff --git a/tests/lib/external_block.sv b/tests/lib/external_block.sv
new file mode 100644
index 0000000..a1c75c9
--- /dev/null
+++ b/tests/lib/external_block.sv
@@ -0,0 +1,73 @@
+module external_block #(
+ parameter WIDTH = 32,
+ parameter ADDR_WIDTH = 8
+)(
+ input wire clk,
+ input wire rst,
+
+ input wire req,
+ input wire req_is_wr,
+ input wire [ADDR_WIDTH-1:0] addr,
+ input wire [WIDTH-1:0] wr_data,
+ input wire [WIDTH-1:0] wr_biten,
+ output logic rd_ack,
+ output logic [WIDTH-1:0] rd_data,
+ output logic wr_ack
+);
+timeunit 1ps;
+timeprecision 1ps;
+
+localparam ADDR_SHIFT = $clog2(WIDTH/8);
+localparam N_ENTRIES = 2**(ADDR_WIDTH - ADDR_SHIFT);
+logic [WIDTH-1:0] mem[N_ENTRIES];
+
+
+task do_write(int idx, logic [WIDTH-1:0] data, logic [WIDTH-1:0] biten);
+ automatic int delay;
+ // Random delay
+ delay = $urandom_range(3,0);
+ repeat(delay) @(posedge clk)
+ $info("Write delay: %d", delay);
+
+ for(int b=0; b> ADDR_SHIFT, wr_data, wr_biten);
+ else do_read(addr >> ADDR_SHIFT);
+ end
+ end
+end
+
+endmodule
diff --git a/tests/lib/external_reg.sv b/tests/lib/external_reg.sv
new file mode 100644
index 0000000..9a51395
--- /dev/null
+++ b/tests/lib/external_reg.sv
@@ -0,0 +1,79 @@
+module external_reg #(
+ parameter WIDTH = 32,
+ parameter SUBWORDS = 1
+)(
+ input wire clk,
+ input wire rst,
+
+ input wire [SUBWORDS-1:0] req,
+ input wire req_is_wr,
+ input wire [WIDTH-1:0] wr_data,
+ input wire [WIDTH-1:0] wr_biten,
+ output logic rd_ack,
+ output logic [WIDTH-1:0] rd_data,
+ output logic wr_ack
+);
+timeunit 1ps;
+timeprecision 1ps;
+logic [SUBWORDS-1:0][WIDTH-1:0] value;
+
+
+task do_write(logic [SUBWORDS-1:0] strb, logic [WIDTH-1:0] data, logic [WIDTH-1:0] biten);
+ automatic int delay;
+ // Random delay
+ delay = $urandom_range(3,0);
+ repeat(delay) @(posedge clk)
+ $info("Write delay: %d", delay);
+
+ for(int i=0; i List[str]:
+ paths = []
+ for path in self.extra_tb_files:
+ path = os.path.join(self.get_testcase_dir(), path)
+ paths.append(path)
+ return paths
+
+ def _generate_tb(self):
+ """
+ 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,
+ extensions=[SVLineAnchor],
+ )
+
+ context = {
+ "testcase": self,
+ "exporter": self.exporter,
+ }
+
+ # template path needs to be relative to the Jinja loader root
+ template_path = os.path.join(self.get_testcase_dir(), self.tb_template_file)
+ template_path = os.path.relpath(template_path, template_root_path)
+ template = jj_env.get_template(template_path)
+
+ output_path = os.path.join(self.get_run_dir(), "tb.sv")
+ stream = template.stream(context)
+ stream.dump(output_path)
+
+
+ def setUp(self):
+ name = self.request.config.getoption("--sim-tool")
+ if name in self.incompatible_sim_tools:
+ pytest.skip()
+ simulator_cls = get_simulator_cls(name)
+ if simulator_cls is None:
+ pytest.skip()
+
+ super().setUp()
+
+ # Create testbench from template
+ if not self.rerun:
+ self._generate_tb()
+
+ simulator = simulator_cls(self)
+
+ # cd into the build directory
+ cwd = os.getcwd()
+ os.chdir(self.get_run_dir())
+ try:
+ simulator.compile()
+ finally:
+ # cd back
+ os.chdir(cwd)
+
+
+ def run_test(self, plusargs:List[str] = None) -> None:
+ name = self.request.config.getoption("--sim-tool")
+ simulator_cls = get_simulator_cls(name)
+ simulator = simulator_cls(self)
+
+ # cd into the build directory
+ cwd = os.getcwd()
+ os.chdir(self.get_run_dir())
+
+ try:
+ simulator.run(plusargs)
+ finally:
+ # cd back
+ os.chdir(cwd)
diff --git a/tests/lib/simulators/__init__.py b/tests/lib/simulators/__init__.py
new file mode 100644
index 0000000..f14227b
--- /dev/null
+++ b/tests/lib/simulators/__init__.py
@@ -0,0 +1,39 @@
+from typing import Type, Optional, List
+import functools
+
+from .base import Simulator
+from .questa import Questa
+from .xilinx import XilinxXSIM
+from .xcelium import Xcelium
+from .stub import StubSimulator
+
+ALL_SIMULATORS: List[Simulator]
+ALL_SIMULATORS = [
+ Questa,
+ XilinxXSIM,
+ Xcelium,
+ StubSimulator,
+]
+
+@functools.lru_cache()
+def get_simulator_cls(name: str) -> Optional[Type[Simulator]]:
+ if name == "skip":
+ return None
+
+ if name == "auto":
+ # Find the first simulator that is installed
+ for sim_cls in ALL_SIMULATORS:
+ if sim_cls is StubSimulator:
+ # Never offer the stub as an automatic option
+ continue
+ if sim_cls.is_installed():
+ return sim_cls
+ raise ValueError("Could not find any installed simulators")
+
+ # Look up which explicit simulator name was specified
+ for sim_cls in ALL_SIMULATORS:
+ if sim_cls.name == name:
+ if not sim_cls.is_installed():
+ raise ValueError("Simulator '%s' is not installed" % sim_cls.name)
+ return sim_cls
+ raise RuntimeError
diff --git a/tests/lib/simulators/base.py b/tests/lib/simulators/base.py
new file mode 100644
index 0000000..115f6d5
--- /dev/null
+++ b/tests/lib/simulators/base.py
@@ -0,0 +1,35 @@
+from typing import TYPE_CHECKING, List
+
+if TYPE_CHECKING:
+ from ..sim_testcase import SimTestCase
+
+class Simulator:
+ name = ""
+
+ @classmethod
+ def is_installed(cls) -> bool:
+ raise NotImplementedError
+
+ def __init__(self, testcase: 'SimTestCase' = None) -> None:
+ self.testcase = testcase
+
+ @property
+ def gui_mode(self) -> bool:
+ return self.testcase.request.config.getoption("--gui")
+
+ @property
+ def tb_files(self) -> List[str]:
+ files = []
+ files.extend(self.testcase.cpuif.get_sim_files())
+ files.extend(self.testcase.get_extra_tb_files())
+ files.append("regblock_pkg.sv")
+ files.append("regblock.sv")
+ files.append("tb.sv")
+
+ return files
+
+ def compile(self) -> None:
+ raise NotImplementedError
+
+ def run(self, plusargs:List[str] = None) -> None:
+ raise NotImplementedError
diff --git a/tests/lib/simulators/questa.py b/tests/lib/simulators/questa.py
new file mode 100644
index 0000000..cf97a47
--- /dev/null
+++ b/tests/lib/simulators/questa.py
@@ -0,0 +1,84 @@
+from typing import List
+import subprocess
+import os
+import shutil
+
+from .base import Simulator
+
+class Questa(Simulator):
+ name = "questa"
+
+ @classmethod
+ def is_installed(cls) -> bool:
+ return (
+ shutil.which("vlog") is not None
+ and shutil.which("vsim") is not None
+ )
+
+ def compile(self) -> None:
+ cmd = [
+ "vlog", "-sv", "-quiet", "-l", "build.log",
+
+ "+incdir+%s" % os.path.join(os.path.dirname(__file__), ".."),
+
+ # Use strict LRM conformance
+ "-svinputport=net",
+
+ # all warnings are errors
+ "-warning", "error",
+
+ # Ignore noisy warning about vopt-time checking of always_comb/always_latch
+ "-suppress", "2583",
+
+ # Suppress "error" about use of the `line directive
+ "-suppress", "13465",
+ ]
+
+ # Add source files
+ cmd.extend(self.tb_files)
+
+ # Run command!
+ subprocess.run(cmd, check=True)
+
+
+ def run(self, plusargs:List[str] = None) -> None:
+ plusargs = plusargs or []
+
+ test_name = self.testcase.request.node.name
+
+ # call vsim
+ cmd = [
+ "vsim", "-quiet",
+ "-voptargs=+acc",
+ "-msgmode", "both",
+ "-l", "%s.log" % test_name,
+ "-wlf", "%s.wlf" % test_name,
+ "tb",
+ "-do", "set WildcardFilter [lsearch -not -all -inline $WildcardFilter Memory]",
+ "-do", "log -r /*;",
+ ]
+
+ if self.gui_mode:
+ cmd.append("-i")
+ else:
+ cmd.extend([
+ "-do", "run -all; exit;",
+ "-c",
+ ])
+
+ for plusarg in plusargs:
+ cmd.append("+" + plusarg)
+ subprocess.run(cmd, check=True)
+
+ self.assertSimLogPass("%s.log" % test_name)
+
+
+ def assertSimLogPass(self, path: str):
+ self.testcase.assertTrue(os.path.isfile(path))
+
+ with open(path, encoding="utf-8") as f:
+ for line in f:
+ if line.startswith("# ** Error"):
+ self.testcase.fail(line)
+ elif line.startswith("# ** Fatal"):
+ self.testcase.fail(line)
diff --git a/tests/lib/simulators/stub.py b/tests/lib/simulators/stub.py
new file mode 100644
index 0000000..6e54425
--- /dev/null
+++ b/tests/lib/simulators/stub.py
@@ -0,0 +1,17 @@
+from typing import List
+
+from .base import Simulator
+
+class StubSimulator(Simulator):
+ name = "stub"
+
+ @classmethod
+ def is_installed(cls) -> bool:
+ # Always available!
+ return True
+
+ def compile(self) -> None:
+ pass
+
+ def run(self, plusargs: List[str] = None) -> None:
+ pass
diff --git a/tests/lib/simulators/xcelium.py b/tests/lib/simulators/xcelium.py
new file mode 100644
index 0000000..fe00e27
--- /dev/null
+++ b/tests/lib/simulators/xcelium.py
@@ -0,0 +1,86 @@
+from typing import List
+import subprocess
+import os
+import shutil
+from .base import Simulator
+
+class Xcelium(Simulator):
+ """
+ Don't use the Xcelium simulator, it is unable to compile & run any testcases.
+
+ As observed in 25.03.006:
+ - Using unpacked structs with more than 2 levels of nesting in clocking blocks is not
+ supported.
+ """
+ name = "xcelium"
+
+ @classmethod
+ def is_installed(cls) -> bool:
+ return (
+ shutil.which("xrun") is not None
+ )
+
+ def compile(self) -> None:
+ # Compile and elaborate into a snapshot
+ cmd = [
+ "xrun",
+ "-64bit",
+ "-elaborate",
+ "-sv",
+ "-log build.log",
+ "-timescale 10ps/1ps",
+ "-ENABLE_DS_UNPS", # Allow ".*" DUT connection with unpacked structs
+ "-nowarn LNDER6", # Suppress warning about the `line 2 "lib/tb_base.sv" 0 file location
+ "-nowarn SPDUSD", # Suppress warning about unused include directory
+ "-incdir %s" % os.path.join(os.path.dirname(__file__), "..")
+ ]
+
+ if self.gui_mode:
+ cmd.append("-access +rwc")
+
+ # Add source files
+ cmd.extend(self.tb_files)
+
+ # Run command!
+ subprocess.run(cmd, check=True)
+
+
+ def run(self, plusargs:List[str] = None) -> None:
+ plusargs = plusargs or []
+
+ test_name = self.testcase.request.node.name
+
+ # Call xrun on the elaborated snapshot
+ cmd = [
+ "xrun",
+ "-64bit",
+ "-log %s.log" % test_name,
+ "-r worklib.tb:sv"
+ ]
+
+ if self.gui_mode:
+ cmd.append("-gui")
+ cmd.append('-input "@database -open waves -into waves.shm -shm -default -event"')
+ cmd.append('-input "@probe -create tb -depth all -tasks -functions -all -packed 4k \
+ -unpacked 16k -memories -dynamic -variables -database waves"')
+ else:
+ cmd.extend([
+ "-input", "@run",
+ ])
+
+ for plusarg in plusargs:
+ cmd.append("+" + plusarg)
+ subprocess.run(cmd, check=True)
+
+ self.assertSimLogPass("%s.log" % test_name)
+
+
+ def assertSimLogPass(self, path: str):
+ self.testcase.assertTrue(os.path.isfile(path))
+
+ with open(path, encoding="utf-8") as f:
+ for line in f:
+ if line.startswith("xmsim: *E"):
+ self.testcase.fail(line)
+ elif line.startswith("xmsim: *F"):
+ self.testcase.fail(line)
diff --git a/tests/lib/simulators/xilinx.py b/tests/lib/simulators/xilinx.py
new file mode 100644
index 0000000..ea69781
--- /dev/null
+++ b/tests/lib/simulators/xilinx.py
@@ -0,0 +1,74 @@
+from typing import List
+import subprocess
+import os
+import shutil
+
+from .base import Simulator
+
+class XilinxXSIM(Simulator):
+ name = "xsim"
+
+ @classmethod
+ def is_installed(cls) -> bool:
+ return (
+ shutil.which("xvlog") is not None
+ and shutil.which("xelab") is not None
+ and shutil.which("xsim") is not None
+ )
+
+ def compile(self) -> None:
+ cmd = [
+ "xvlog", "--sv",
+ "--log", "compile.log",
+ "--include", os.path.join(os.path.dirname(__file__), ".."),
+ "--define", "XILINX_XSIM",
+ ]
+ cmd.extend(self.tb_files)
+ subprocess.run(cmd, check=True)
+
+ cmd = [
+ "xelab",
+ "--log", "elaborate.log",
+ "--timescale", "1ps/1ps",
+ "--debug", "all",
+ "tb",
+ ]
+ subprocess.run(cmd, check=True)
+
+
+ def run(self, plusargs:List[str] = None) -> None:
+ plusargs = plusargs or []
+
+ test_name = self.testcase.request.node.name
+
+ # call xsim
+ cmd = ["xsim"]
+ if self.gui_mode:
+ cmd.append("--gui")
+ else:
+ cmd.append("-R")
+
+ cmd.extend([
+ "--log", "%s.log" % test_name,
+ "tb",
+ ])
+
+ for plusarg in plusargs:
+ cmd.append("--testplusarg")
+ cmd.append(plusarg)
+ subprocess.run(cmd, check=True)
+
+ self.assertSimLogPass("%s.log" % test_name)
+
+
+ def assertSimLogPass(self, path: str):
+ self.testcase.assertTrue(os.path.isfile(path))
+
+ with open(path, encoding="utf-8") as f:
+ for line in f:
+ if line.startswith("Error:"):
+ self.testcase.fail(line)
+ elif line.startswith("Fatal:"):
+ self.testcase.fail(line)
+ elif line.startswith("FATAL_ERROR:"):
+ self.testcase.fail(line)
diff --git a/tests/lib/sv_line_anchor.py b/tests/lib/sv_line_anchor.py
new file mode 100644
index 0000000..1f09ec1
--- /dev/null
+++ b/tests/lib/sv_line_anchor.py
@@ -0,0 +1,10 @@
+from jinja2_simple_tags import StandaloneTag
+
+class SVLineAnchor(StandaloneTag):
+ """
+ Define a custom Jinja tag that emits a SystemVerilog `line directive so that
+ assertion messages can get properly back-annotated
+ """
+ tags = {"sv_line_anchor"}
+ def render(self):
+ return f'`line {self.lineno + 1} "{self.template}" 0'
diff --git a/tests/lib/synth_testcase.py b/tests/lib/synth_testcase.py
new file mode 100644
index 0000000..552902d
--- /dev/null
+++ b/tests/lib/synth_testcase.py
@@ -0,0 +1,39 @@
+from typing import List
+import os
+
+import pytest
+
+from .base_testcase import BaseTestCase
+from .synthesizers import get_synthesizer_cls
+
+class SynthTestCase(BaseTestCase):
+
+ def _get_synth_files(self) -> List[str]:
+ files = []
+ files.extend(self.cpuif.get_synth_files())
+ files.append("regblock_pkg.sv")
+ files.append("regblock.sv")
+
+ return files
+
+ def setUp(self) -> None:
+ name = self.request.config.getoption("--synth-tool")
+ synth_cls = get_synthesizer_cls(name)
+ if synth_cls is None:
+ pytest.skip()
+ super().setUp()
+
+ def run_synth(self) -> None:
+ name = self.request.config.getoption("--synth-tool")
+ synth_cls = get_synthesizer_cls(name)
+ synth = synth_cls(self)
+
+ # cd into the build directory
+ cwd = os.getcwd()
+ os.chdir(self.get_run_dir())
+
+ try:
+ synth.run()
+ finally:
+ # cd back
+ os.chdir(cwd)
diff --git a/tests/lib/synthesizers/__init__.py b/tests/lib/synthesizers/__init__.py
new file mode 100644
index 0000000..58d1c7e
--- /dev/null
+++ b/tests/lib/synthesizers/__init__.py
@@ -0,0 +1,30 @@
+from typing import List, Optional, Type
+import functools
+
+from .base import Synthesizer
+from .vivado import Vivado
+
+ALL_SYNTHESIZERS: List[Synthesizer]
+ALL_SYNTHESIZERS = [
+ Vivado,
+]
+
+@functools.lru_cache()
+def get_synthesizer_cls(name: str) -> Optional[Type[Synthesizer]]:
+ if name == "skip":
+ return None
+
+ if name == "auto":
+ # Find the first tool that is installed
+ for synth_cls in ALL_SYNTHESIZERS:
+ if synth_cls.is_installed():
+ return synth_cls
+ raise ValueError("Could not find any installed synthesis tools")
+
+ # Look up which explicit synth tool name was specified
+ for synth_cls in ALL_SYNTHESIZERS:
+ if synth_cls.name == name:
+ if not synth_cls.is_installed():
+ raise ValueError("Synthesis tool '%s' is not installed" % synth_cls.name)
+ return synth_cls
+ raise RuntimeError
diff --git a/tests/lib/synthesizers/base.py b/tests/lib/synthesizers/base.py
new file mode 100644
index 0000000..5a3e36e
--- /dev/null
+++ b/tests/lib/synthesizers/base.py
@@ -0,0 +1,17 @@
+from typing import TYPE_CHECKING, List
+
+if TYPE_CHECKING:
+ from ..synth_testcase import SynthTestCase
+
+class Synthesizer:
+ name = ""
+
+ @classmethod
+ def is_installed(cls) -> bool:
+ raise NotImplementedError
+
+ def __init__(self, testcase: 'SynthTestCase' = None) -> None:
+ self.testcase = testcase
+
+ def run(self) -> None:
+ raise NotImplementedError
diff --git a/tests/lib/synthesizers/vivado.py b/tests/lib/synthesizers/vivado.py
new file mode 100644
index 0000000..857fac0
--- /dev/null
+++ b/tests/lib/synthesizers/vivado.py
@@ -0,0 +1,29 @@
+import os
+import subprocess
+import shutil
+
+from .base import Synthesizer
+
+class Vivado(Synthesizer):
+ name = "vivado"
+
+ @classmethod
+ def is_installed(cls) -> bool:
+ return shutil.which("vivado") is not None
+
+ def run(self) -> None:
+ script = os.path.join(
+ os.path.dirname(__file__),
+ "vivado_scripts/run.tcl"
+ )
+
+ cmd = [
+ "vivado", "-nojournal", "-notrace",
+ "-mode", "batch",
+ "-log", "out.log",
+ "-source", script,
+ "-tclargs"
+ ]
+ cmd.extend(self.testcase._get_synth_files())
+
+ subprocess.run(cmd, check=True)
diff --git a/tests/lib/synthesizers/vivado_scripts/constr.xdc b/tests/lib/synthesizers/vivado_scripts/constr.xdc
new file mode 100644
index 0000000..3c063c1
--- /dev/null
+++ b/tests/lib/synthesizers/vivado_scripts/constr.xdc
@@ -0,0 +1,8 @@
+
+create_clock -period 10.000 -name clk [get_ports clk]
+
+set_input_delay -clock [get_clocks clk] -min -add_delay 0.000 [get_ports -filter {(DIRECTION == IN) && (NAME != clk)}]
+set_input_delay -clock [get_clocks clk] -max -add_delay 0.000 [get_ports -filter {(DIRECTION == IN) && (NAME != clk)}]
+
+set_output_delay -clock [get_clocks clk] -min -add_delay 0.000 [get_ports -filter {DIRECTION == OUT}]
+set_output_delay -clock [get_clocks clk] -max -add_delay 0.000 [get_ports -filter {DIRECTION == OUT}]
diff --git a/tests/lib/synthesizers/vivado_scripts/run.tcl b/tests/lib/synthesizers/vivado_scripts/run.tcl
new file mode 100644
index 0000000..db793b7
--- /dev/null
+++ b/tests/lib/synthesizers/vivado_scripts/run.tcl
@@ -0,0 +1,34 @@
+set this_dir [file dirname [file normalize [info script]]]
+set files $argv
+
+
+# Multi-driven
+set_msg_config -id {[Synth 8-6858]} -new_severity "ERROR"
+set_msg_config -id {[Synth 8-6859]} -new_severity "ERROR"
+
+# Implicit net
+set_msg_config -id {[Synth 8-992]} -new_severity "ERROR"
+
+# Non-combo always_comb
+set_msg_config -id {[Synth 8-87]} -new_severity "ERROR"
+
+# Latch
+set_msg_config -id {[Synth 8-327]} -new_severity "ERROR"
+
+# Timing loop
+set_msg_config -id {[Synth 8-295]} -new_severity "ERROR"
+
+# Promote all critical warnings to errors
+set_msg_config -severity {CRITICAL WARNING} -new_severity "ERROR"
+
+
+set_part [lindex [get_parts] 0]
+read_verilog -sv $files
+read_xdc $this_dir/constr.xdc
+synth_design -top regblock -mode out_of_context
+
+#write_checkpoint -force synth.dcp
+
+if {[get_msg_config -count -severity {CRITICAL WARNING}] || [get_msg_config -count -severity ERROR]} {
+ error "Encountered errors"
+}
diff --git a/tests/lib/tb_base.sv b/tests/lib/tb_base.sv
new file mode 100644
index 0000000..a6c93d6
--- /dev/null
+++ b/tests/lib/tb_base.sv
@@ -0,0 +1,128 @@
+{% sv_line_anchor %}
+module tb;
+ timeunit 10ps;
+ timeprecision 1ps;
+
+ class bitswap_cls #(W=1);
+ static function logic [W-1:0] bitswap(logic [W-1:0] x);
+ logic [W-1:0] result;
+ result = {<<{x}};
+ return result;
+ endfunction
+ endclass
+ `define bitswap(x) (bitswap_cls#($bits(x))::bitswap(x))
+
+ logic rst = '1;
+ logic clk = '0;
+ initial forever begin
+ #5ns;
+ clk = ~clk;
+ end
+
+ logic rst_n, arst, arst_n;
+ assign rst_n = ~rst;
+ assign arst = rst;
+ assign arst_n = ~rst;
+
+ //--------------------------------------------------------------------------
+ // 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 %}
+
+{%- if exporter.ds.has_paritycheck %}
+ logic parity_error;
+{%- endif %}
+
+
+{%- block declarations %}
+{%- endblock %}
+
+ //--------------------------------------------------------------------------
+ // Clocking
+ //--------------------------------------------------------------------------
+ default clocking cb @(posedge clk);
+ default input #1step output #1;
+ output rst;
+{%- if exporter.hwif.has_input_struct and testcase.clocking_hwif_in %}
+ output hwif_in;
+{%- endif %}
+
+{%- if exporter.hwif.has_output_struct and testcase.clocking_hwif_out %}
+ input hwif_out;
+{%- endif %}
+
+{%- if exporter.ds.has_paritycheck %}
+ input parity_error;
+{%- endif %}
+
+{%- filter indent %}
+{%- block clocking_dirs %}
+{%- endblock %}
+{%- endfilter %}
+ endclocking
+
+ //--------------------------------------------------------------------------
+ // CPUIF
+ //--------------------------------------------------------------------------
+ {{testcase.cpuif.get_tb_inst(testcase, exporter)|indent}}
+
+ //--------------------------------------------------------------------------
+ // DUT
+ //--------------------------------------------------------------------------
+ {% sv_line_anchor %}
+ regblock dut (.*);
+
+{%- if exporter.hwif.has_output_struct %}
+ {% sv_line_anchor %}
+ initial begin
+ logic [$bits(hwif_out)-1:0] tmp;
+ forever begin
+ ##1;
+ tmp = {>>{hwif_out}}; // Workaround for Xilinx's xsim - assign to tmp variable
+ if(!rst) assert(!$isunknown(tmp)) else $error("hwif_out has X's!");
+ {%- if exporter.ds.has_paritycheck %}
+ if(!rst) assert(!$isunknown(parity_error)) else $error("parity_error has X's!");
+ {%- endif %}
+ end
+ end
+{%- endif %}
+ {% sv_line_anchor %}
+
+{%- block dut_support %}
+{%- endblock %}
+
+ //--------------------------------------------------------------------------
+ // Test Sequence
+ //--------------------------------------------------------------------------
+ initial begin
+ cb.rst <= '1;
+ {%- if exporter.hwif.has_input_struct and testcase.init_hwif_in %}
+ cb.hwif_in <= '{default: '0};
+ {%- endif %}
+
+ begin
+ {%- filter indent(8) %}
+ {%- block seq %}
+ {%- endblock %}
+ {%- endfilter %}
+ end
+ {% sv_line_anchor %}
+ ##5;
+ $finish();
+ end
+
+ //--------------------------------------------------------------------------
+ // Monitor for timeout
+ //--------------------------------------------------------------------------
+ initial begin
+ ##{{testcase.timeout_clk_cycles}};
+ $fatal(1, "Test timed out after {{testcase.timeout_clk_cycles}} clock cycles");
+ end
+
+endmodule
diff --git a/tests/lib/test_params.py b/tests/lib/test_params.py
new file mode 100644
index 0000000..3615561
--- /dev/null
+++ b/tests/lib/test_params.py
@@ -0,0 +1,7 @@
+from itertools import product
+
+def get_permutations(spec):
+ param_list = []
+ for v in product(*spec.values()):
+ param_list.append(dict(zip(spec, v)))
+ return param_list
diff --git a/tests/mypy.ini b/tests/mypy.ini
new file mode 100644
index 0000000..052d30e
--- /dev/null
+++ b/tests/mypy.ini
@@ -0,0 +1,7 @@
+[mypy]
+disallow_incomplete_defs = True
+disallow_untyped_defs = True
+warn_unused_configs = True
+warn_unused_ignores = True
+warn_unreachable = True
+disallow_untyped_calls = True
diff --git a/tests/pylint.rc b/tests/pylint.rc
new file mode 100644
index 0000000..b3eb902
--- /dev/null
+++ b/tests/pylint.rc
@@ -0,0 +1,571 @@
+[MASTER]
+
+# A comma-separated list of package or module names from where C extensions may
+# be loaded. Extensions are loading into the active Python interpreter and may
+# run arbitrary code.
+extension-pkg-allow-list=
+
+# A comma-separated list of package or module names from where C extensions may
+# be loaded. Extensions are loading into the active Python interpreter and may
+# run arbitrary code. (This is an alternative name to extension-pkg-allow-list
+# for backward compatibility.)
+extension-pkg-whitelist=
+
+# Return non-zero exit code if any of these messages/categories are detected,
+# even if score is above --fail-under value. Syntax same as enable. Messages
+# specified are enabled, while categories only check already-enabled messages.
+fail-on=
+
+# Specify a score threshold to be exceeded before program exits with error.
+fail-under=10.0
+
+# Files or directories to be skipped. They should be base names, not paths.
+ignore=CVS, parser, docs, test
+
+# Add files or directories matching the regex patterns to the ignore-list. The
+# regex matches against paths.
+ignore-paths=
+
+# Files or directories matching the regex patterns are skipped. The regex
+# matches against base names, not paths.
+ignore-patterns=
+
+# Python code to execute, usually for sys.path manipulation such as
+# pygtk.require().
+#init-hook=
+
+# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
+# number of processors available to use.
+jobs=0
+
+# Control the amount of potential inferred values when inferring a single
+# object. This can help the performance when dealing with large functions or
+# complex, nested conditions.
+limit-inference-results=100
+
+# List of plugins (as comma separated values of python module names) to load,
+# usually to register additional checkers.
+load-plugins=
+
+# Pickle collected data for later comparisons.
+persistent=yes
+
+# When enabled, pylint would attempt to guess common misconfiguration and emit
+# user-friendly hints instead of false-positive error messages.
+suggestion-mode=yes
+
+# Allow loading of arbitrary C extensions. Extensions are imported into the
+# active Python interpreter and may run arbitrary code.
+unsafe-load-any-extension=no
+
+
+[MESSAGES CONTROL]
+
+# Only show warnings with the listed confidence levels. Leave empty to show
+# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED.
+confidence=
+
+# Disable the message, report, category or checker with the given id(s). You
+# can either give multiple identifiers separated by comma (,) or put this
+# option multiple times (only on the command line, not in the configuration
+# file where it should appear only once). You can also use "--disable=all" to
+# disable everything first and then reenable specific checks. For example, if
+# you want to run only the similarities checker, you can use "--disable=all
+# --enable=similarities". If you want to run only the classes checker, but have
+# no Warning level messages displayed, use "--disable=all --enable=classes
+# --disable=W".
+disable=
+ # Disable for now during development
+ fixme,
+
+ # User ignored limits
+ too-many-lines,
+ too-many-locals,
+ too-many-branches,
+ too-many-return-statements,
+ too-few-public-methods,
+ too-many-public-methods,
+ too-many-statements,
+ too-many-instance-attributes,
+ too-many-function-args,
+ line-too-long,
+
+ # Noise / Don't care
+ no-else-return,
+ unused-variable,
+ invalid-name,
+ missing-docstring,
+ abstract-method,
+ protected-access,
+ duplicate-code,
+ unused-argument,
+ consider-using-f-string
+
+# Enable the message, report, category or checker with the given id(s). You can
+# either give multiple identifier separated by comma (,) or put this option
+# multiple time (only on the command line, not in the configuration file where
+# it should appear only once). See also the "--disable" option for examples.
+enable=c-extension-no-member
+
+
+[REPORTS]
+
+# Python expression which should return a score less than or equal to 10. You
+# have access to the variables 'error', 'warning', 'refactor', and 'convention'
+# which contain the number of messages in each category, as well as 'statement'
+# which is the total number of statements analyzed. This score is used by the
+# global evaluation report (RP0004).
+evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+
+# Template used to display messages. This is a python new-style format string
+# used to format the message information. See doc for all details.
+#msg-template=
+
+# Set the output format. Available formats are text, parseable, colorized, json
+# and msvs (visual studio). You can also give a reporter class, e.g.
+# mypackage.mymodule.MyReporterClass.
+output-format=text
+
+# Tells whether to display a full report or only the messages.
+reports=no
+
+# Activate the evaluation score.
+score=no
+
+
+[REFACTORING]
+
+# Maximum number of nested blocks for function / method body
+max-nested-blocks=5
+
+# Complete name of functions that never returns. When checking for
+# inconsistent-return-statements if a never returning function is called then
+# it will be considered as an explicit return statement and no message will be
+# printed.
+never-returning-functions=sys.exit,argparse.parse_error
+
+
+[STRING]
+
+# This flag controls whether inconsistent-quotes generates a warning when the
+# character used as a quote delimiter is used inconsistently within a module.
+check-quote-consistency=no
+
+# This flag controls whether the implicit-str-concat should generate a warning
+# on implicit string concatenation in sequences defined over several lines.
+check-str-concat-over-line-jumps=no
+
+
+[SPELLING]
+
+# Limits count of emitted suggestions for spelling mistakes.
+max-spelling-suggestions=4
+
+# Spelling dictionary name. Available dictionaries: en_GB (aspell), en_AU
+# (aspell), en_US (hunspell), en (aspell), en_CA (aspell).
+spelling-dict=
+
+# List of comma separated words that should be considered directives if they
+# appear and the beginning of a comment and should not be checked.
+spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:
+
+# List of comma separated words that should not be checked.
+spelling-ignore-words=
+
+# A path to a file that contains the private dictionary; one word per line.
+spelling-private-dict-file=
+
+# Tells whether to store unknown words to the private dictionary (see the
+# --spelling-private-dict-file option) instead of raising a message.
+spelling-store-unknown-words=no
+
+
+[LOGGING]
+
+# The type of string formatting that logging methods do. `old` means using %
+# formatting, `new` is for `{}` formatting.
+logging-format-style=old
+
+# Logging modules to check that the string format arguments are in logging
+# function parameter format.
+logging-modules=logging
+
+
+[VARIABLES]
+
+# List of additional names supposed to be defined in builtins. Remember that
+# you should avoid defining new builtins when possible.
+additional-builtins=
+
+# Tells whether unused global variables should be treated as a violation.
+allow-global-unused-variables=yes
+
+# List of names allowed to shadow builtins
+allowed-redefined-builtins=
+
+# List of strings which can identify a callback function by name. A callback
+# name must start or end with one of those strings.
+callbacks=cb_,
+ _cb
+
+# A regular expression matching the name of dummy variables (i.e. expected to
+# not be used).
+dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
+
+# Argument names that match this expression will be ignored. Default to name
+# with leading underscore.
+ignored-argument-names=_.*|^ignored_|^unused_
+
+# Tells whether we should check for unused import in __init__ files.
+init-import=no
+
+# List of qualified module names which can have objects that can redefine
+# builtins.
+redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
+
+
+[SIMILARITIES]
+
+# Comments are removed from the similarity computation
+ignore-comments=yes
+
+# Docstrings are removed from the similarity computation
+ignore-docstrings=yes
+
+# Imports are removed from the similarity computation
+ignore-imports=no
+
+# Signatures are removed from the similarity computation
+ignore-signatures=no
+
+# Minimum lines number of a similarity.
+min-similarity-lines=10
+
+
+[TYPECHECK]
+
+# List of decorators that produce context managers, such as
+# contextlib.contextmanager. Add to this list to register other decorators that
+# produce valid context managers.
+contextmanager-decorators=contextlib.contextmanager
+
+# List of members which are set dynamically and missed by pylint inference
+# system, and so shouldn't trigger E1101 when accessed. Python regular
+# expressions are accepted.
+generated-members=
+
+# Tells whether missing members accessed in mixin class should be ignored. A
+# mixin class is detected if its name ends with "mixin" (case insensitive).
+ignore-mixin-members=yes
+
+# Tells whether to warn about missing members when the owner of the attribute
+# is inferred to be None.
+ignore-none=yes
+
+# This flag controls whether pylint should warn about no-member and similar
+# checks whenever an opaque object is returned when inferring. The inference
+# can return multiple potential results while evaluating a Python object, but
+# some branches might not be evaluated, which results in partial inference. In
+# that case, it might be useful to still emit no-member and other checks for
+# the rest of the inferred objects.
+ignore-on-opaque-inference=yes
+
+# List of class names for which member attributes should not be checked (useful
+# for classes with dynamically set attributes). This supports the use of
+# qualified names.
+ignored-classes=optparse.Values,thread._local,_thread._local
+
+# List of module names for which member attributes should not be checked
+# (useful for modules/projects where namespaces are manipulated during runtime
+# and thus existing member attributes cannot be deduced by static analysis). It
+# supports qualified module names, as well as Unix pattern matching.
+ignored-modules=
+
+# Show a hint with possible names when a member name was not found. The aspect
+# of finding the hint is based on edit distance.
+missing-member-hint=yes
+
+# The minimum edit distance a name should have in order to be considered a
+# similar match for a missing member name.
+missing-member-hint-distance=1
+
+# The total number of similar names that should be taken in consideration when
+# showing a hint for a missing member.
+missing-member-max-choices=1
+
+# List of decorators that change the signature of a decorated function.
+signature-mutators=
+
+
+[FORMAT]
+
+# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
+expected-line-ending-format=
+
+# Regexp for a line that is allowed to be longer than the limit.
+ignore-long-lines=^\s*(# )??$
+
+# Number of spaces of indent required inside a hanging or continued line.
+indent-after-paren=4
+
+# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
+# tab).
+indent-string=' '
+
+# Maximum number of characters on a single line.
+max-line-length=110
+
+# Maximum number of lines in a module.
+max-module-lines=2000
+
+# Allow the body of a class to be on the same line as the declaration if body
+# contains single statement.
+single-line-class-stmt=no
+
+# Allow the body of an if to be on the same line as the test if there is no
+# else.
+single-line-if-stmt=no
+
+
+[MISCELLANEOUS]
+
+# List of note tags to take in consideration, separated by a comma.
+notes=FIXME,
+ XXX,
+ TODO
+
+
+[BASIC]
+
+# Naming style matching correct argument names.
+argument-naming-style=snake_case
+
+# Regular expression matching correct argument names. Overrides argument-
+# naming-style.
+#argument-rgx=
+
+# Naming style matching correct attribute names.
+attr-naming-style=snake_case
+
+# Regular expression matching correct attribute names. Overrides attr-naming-
+# style.
+#attr-rgx=
+
+# Bad variable names which should always be refused, separated by a comma.
+bad-names=foo,
+ bar,
+ baz,
+ toto,
+ tutu,
+ tata
+
+# Bad variable names regexes, separated by a comma. If names match any regex,
+# they will always be refused
+bad-names-rgxs=
+
+# Naming style matching correct class attribute names.
+class-attribute-naming-style=any
+
+# Regular expression matching correct class attribute names. Overrides class-
+# attribute-naming-style.
+#class-attribute-rgx=
+
+# Naming style matching correct class constant names.
+class-const-naming-style=UPPER_CASE
+
+# Regular expression matching correct class constant names. Overrides class-
+# const-naming-style.
+#class-const-rgx=
+
+# Naming style matching correct class names.
+class-naming-style=PascalCase
+
+# Regular expression matching correct class names. Overrides class-naming-
+# style.
+#class-rgx=
+
+# Naming style matching correct constant names.
+const-naming-style=UPPER_CASE
+
+# Regular expression matching correct constant names. Overrides const-naming-
+# style.
+#const-rgx=
+
+# Minimum line length for functions/classes that require docstrings, shorter
+# ones are exempt.
+docstring-min-length=-1
+
+# Naming style matching correct function names.
+function-naming-style=snake_case
+
+# Regular expression matching correct function names. Overrides function-
+# naming-style.
+#function-rgx=
+
+# Good variable names which should always be accepted, separated by a comma.
+good-names=i,
+ j,
+ k,
+ ex,
+ Run,
+ _
+
+# Good variable names regexes, separated by a comma. If names match any regex,
+# they will always be accepted
+good-names-rgxs=
+
+# Include a hint for the correct naming format with invalid-name.
+include-naming-hint=no
+
+# Naming style matching correct inline iteration names.
+inlinevar-naming-style=any
+
+# Regular expression matching correct inline iteration names. Overrides
+# inlinevar-naming-style.
+#inlinevar-rgx=
+
+# Naming style matching correct method names.
+method-naming-style=snake_case
+
+# Regular expression matching correct method names. Overrides method-naming-
+# style.
+#method-rgx=
+
+# Naming style matching correct module names.
+module-naming-style=snake_case
+
+# Regular expression matching correct module names. Overrides module-naming-
+# style.
+#module-rgx=
+
+# Colon-delimited sets of names that determine each other's naming style when
+# the name regexes allow several styles.
+name-group=
+
+# Regular expression which should only match function or class names that do
+# not require a docstring.
+no-docstring-rgx=^_
+
+# List of decorators that produce properties, such as abc.abstractproperty. Add
+# to this list to register other decorators that produce valid properties.
+# These decorators are taken in consideration only for invalid-name.
+property-classes=abc.abstractproperty
+
+# Naming style matching correct variable names.
+variable-naming-style=snake_case
+
+# Regular expression matching correct variable names. Overrides variable-
+# naming-style.
+#variable-rgx=
+
+
+[CLASSES]
+
+# Warn about protected attribute access inside special methods
+check-protected-access-in-special-methods=no
+
+# List of method names used to declare (i.e. assign) instance attributes.
+defining-attr-methods=__init__,
+ __new__,
+ setUp,
+ __post_init__
+
+# List of member names, which should be excluded from the protected access
+# warning.
+exclude-protected=_asdict,
+ _fields,
+ _replace,
+ _source,
+ _make
+
+# List of valid names for the first argument in a class method.
+valid-classmethod-first-arg=cls
+
+# List of valid names for the first argument in a metaclass class method.
+valid-metaclass-classmethod-first-arg=cls
+
+
+[DESIGN]
+
+# List of qualified class names to ignore when countint class parents (see
+# R0901)
+ignored-parents=
+
+# Maximum number of arguments for function / method.
+max-args=16
+
+# Maximum number of attributes for a class (see R0902).
+max-attributes=7
+
+# Maximum number of boolean expressions in an if statement (see R0916).
+max-bool-expr=5
+
+# Maximum number of branch for function / method body.
+max-branches=12
+
+# Maximum number of locals for function / method body.
+max-locals=15
+
+# Maximum number of parents for a class (see R0901).
+max-parents=7
+
+# Maximum number of public methods for a class (see R0904).
+max-public-methods=20
+
+# Maximum number of return / yield for function / method body.
+max-returns=6
+
+# Maximum number of statements in function / method body.
+max-statements=50
+
+# Minimum number of public methods for a class (see R0903).
+min-public-methods=2
+
+
+[IMPORTS]
+
+# List of modules that can be imported at any level, not just the top level
+# one.
+allow-any-import-level=
+
+# Allow wildcard imports from modules that define __all__.
+allow-wildcard-with-all=no
+
+# Analyse import fallback blocks. This can be used to support both Python 2 and
+# 3 compatible code, which means that the block might have code that exists
+# only in one or another interpreter, leading to false positives when analysed.
+analyse-fallback-blocks=no
+
+# Deprecated modules which should not be used, separated by a comma.
+deprecated-modules=
+
+# Output a graph (.gv or any supported image format) of external dependencies
+# to the given file (report RP0402 must not be disabled).
+ext-import-graph=
+
+# Output a graph (.gv or any supported image format) of all (i.e. internal and
+# external) dependencies to the given file (report RP0402 must not be
+# disabled).
+import-graph=
+
+# Output a graph (.gv or any supported image format) of internal dependencies
+# to the given file (report RP0402 must not be disabled).
+int-import-graph=
+
+# Force import order to recognize a module as part of the standard
+# compatibility libraries.
+known-standard-library=
+
+# Force import order to recognize a module as part of a third party library.
+known-third-party=enchant
+
+# Couples of modules and preferred modules, separated by a comma.
+preferred-modules=
+
+
+[EXCEPTIONS]
+
+# Exceptions that will emit a warning when being caught. Defaults to
+# "BaseException, Exception".
+overgeneral-exceptions=builtin.BaseException,
+ builtin.Exception
diff --git a/tests/pytest.ini b/tests/pytest.ini
new file mode 100644
index 0000000..afd91e9
--- /dev/null
+++ b/tests/pytest.ini
@@ -0,0 +1,2 @@
+[pytest]
+python_files = test_*.py testcase.py
diff --git a/tests/requirements.txt b/tests/requirements.txt
new file mode 100644
index 0000000..440db54
--- /dev/null
+++ b/tests/requirements.txt
@@ -0,0 +1,11 @@
+# hold back: https://github.com/kevlened/pytest-parallel/issues/118
+pytest<7.2
+
+parameterized
+pytest-parallel # TODO: deprecated. migrate to pytest-xdist
+jinja2-simple-tags
+pylint
+mypy
+pytest-cov
+coveralls>=3.0.0
+types-setuptools
diff --git a/tests/run.sh b/tests/run.sh
new file mode 100755
index 0000000..b8106f7
--- /dev/null
+++ b/tests/run.sh
@@ -0,0 +1,28 @@
+#!/bin/bash
+
+set -e
+
+cd "$(dirname "$0")"
+
+# Initialize venv
+rm -rf .venv
+python3 -m venv .venv
+source .venv/bin/activate
+
+# Install test dependencies
+pip install -r requirements.txt
+
+# Install dut
+pip install -e "../[cli]"
+
+# Run lint
+pylint --rcfile pylint.rc ../src/peakrdl_regblock
+
+# Run static type checking
+mypy ../src/peakrdl_regblock
+
+# Run unit tests
+pytest --workers auto --cov=peakrdl_regblock --synth-tool skip
+
+# Generate coverage report
+coverage html -i -d htmlcov
diff --git a/tests/test_bitwise_reduce/__init__.py b/tests/test_bitwise_reduce/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_bitwise_reduce/regblock.rdl b/tests/test_bitwise_reduce/regblock.rdl
new file mode 100644
index 0000000..6d770e1
--- /dev/null
+++ b/tests/test_bitwise_reduce/regblock.rdl
@@ -0,0 +1,8 @@
+addrmap top {
+ reg {
+ field {
+ sw=rw; hw=r;
+ anded; ored; xored;
+ } f[7:0] = 0;
+ } r1;
+};
diff --git a/tests/test_bitwise_reduce/tb_template.sv b/tests/test_bitwise_reduce/tb_template.sv
new file mode 100644
index 0000000..9222491
--- /dev/null
+++ b/tests/test_bitwise_reduce/tb_template.sv
@@ -0,0 +1,44 @@
+{% extends "lib/tb_base.sv" %}
+
+{% block seq %}
+ {% sv_line_anchor %}
+ ##1;
+ cb.rst <= '0;
+ ##1;
+
+ cpuif.write('h0, 'h00);
+ @cb;
+ assert(cb.hwif_out.r1.f.anded == 1'b0);
+ assert(cb.hwif_out.r1.f.ored == 1'b0);
+ assert(cb.hwif_out.r1.f.xored == 1'b0);
+
+ cpuif.write('h0, 'h01);
+ @cb;
+ assert(cb.hwif_out.r1.f.anded == 1'b0);
+ assert(cb.hwif_out.r1.f.ored == 1'b1);
+ assert(cb.hwif_out.r1.f.xored == 1'b1);
+
+ cpuif.write('h0, 'h02);
+ @cb;
+ assert(cb.hwif_out.r1.f.anded == 1'b0);
+ assert(cb.hwif_out.r1.f.ored == 1'b1);
+ assert(cb.hwif_out.r1.f.xored == 1'b1);
+
+ cpuif.write('h0, 'h03);
+ @cb;
+ assert(cb.hwif_out.r1.f.anded == 1'b0);
+ assert(cb.hwif_out.r1.f.ored == 1'b1);
+ assert(cb.hwif_out.r1.f.xored == 1'b0);
+
+ cpuif.write('h0, 'hFE);
+ @cb;
+ assert(cb.hwif_out.r1.f.anded == 1'b0);
+ assert(cb.hwif_out.r1.f.ored == 1'b1);
+ assert(cb.hwif_out.r1.f.xored == 1'b1);
+
+ cpuif.write('h0, 'hFF);
+ @cb;
+ assert(cb.hwif_out.r1.f.anded == 1'b1);
+ assert(cb.hwif_out.r1.f.ored == 1'b1);
+ assert(cb.hwif_out.r1.f.xored == 1'b0);
+{% endblock %}
diff --git a/tests/test_bitwise_reduce/testcase.py b/tests/test_bitwise_reduce/testcase.py
new file mode 100644
index 0000000..835b5ef
--- /dev/null
+++ b/tests/test_bitwise_reduce/testcase.py
@@ -0,0 +1,5 @@
+from ..lib.sim_testcase import SimTestCase
+
+class Test(SimTestCase):
+ def test_dut(self):
+ self.run_test()
diff --git a/tests/test_buffered_swacc_swmod/__init__.py b/tests/test_buffered_swacc_swmod/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_buffered_swacc_swmod/regblock.rdl b/tests/test_buffered_swacc_swmod/regblock.rdl
new file mode 100644
index 0000000..f4105ec
--- /dev/null
+++ b/tests/test_buffered_swacc_swmod/regblock.rdl
@@ -0,0 +1,60 @@
+addrmap top {
+ default regwidth = 16;
+ default accesswidth = 8;
+
+ reg {
+ buffer_reads;
+ field {
+ sw=r; hw=w;
+ swacc;
+ } f[16];
+ } r1;
+
+ reg {
+ buffer_reads;
+ buffer_writes;
+ field {
+ sw=rw; hw=r;
+ swmod;
+ } f[16] = 0x4020;
+ } r2;
+
+ reg {
+ buffer_reads;
+ buffer_writes;
+ field {
+ sw=rw; hw=r;
+ swmod;
+ rclr;
+ } f[16] = 0x1030;
+ } r3;
+
+ reg {
+ buffer_reads;
+ buffer_writes;
+ field {
+ sw=rw; hw=r;
+ swacc;
+ swmod;
+ } f[16] = 0x1234;
+ } r4;
+
+ reg {
+ buffer_writes;
+ field {
+ sw=rw; hw=r;
+ swacc;
+ swmod;
+ } f[16] = 0xABCD;
+ } r5;
+
+ reg {
+ buffer_reads;
+ field {
+ sw=r; hw=rw;
+ we;
+ swmod;
+ rclr;
+ } f[16] = 0x1030;
+ } r6;
+};
diff --git a/tests/test_buffered_swacc_swmod/tb_template.sv b/tests/test_buffered_swacc_swmod/tb_template.sv
new file mode 100644
index 0000000..019c669
--- /dev/null
+++ b/tests/test_buffered_swacc_swmod/tb_template.sv
@@ -0,0 +1,197 @@
+{% extends "lib/tb_base.sv" %}
+
+{% block seq %}
+ {% sv_line_anchor %}
+ logic [15:0] counter;
+ logic [7:0] rd_data_l;
+ logic [7:0] rd_data_h;
+ logic [15:0] latched_data;
+ int event_count;
+ bit fired;
+ latched_data = 'x;
+
+ ##1;
+ cb.rst <= '0;
+ ##1;
+
+ // Verify that hwif gets sampled at the same cycle as swacc strobe
+ counter = 'h10;
+ cb.hwif_in.r1.f.next <= counter;
+ @cb;
+ event_count = 0;
+ fork
+ begin
+ ##0;
+ forever begin
+ counter++;
+ cb.hwif_in.r1.f.next <= counter;
+ @cb;
+ if(cb.hwif_out.r1.f.swacc) begin
+ latched_data = counter;
+ event_count++;
+ end
+ end
+ end
+
+ begin
+ cpuif.read('h0, rd_data_l);
+ cpuif.read('h1, rd_data_h);
+ repeat(3) @cb;
+ end
+ join_any
+ disable fork;
+ assert({rd_data_h, rd_data_l} == latched_data) else $error("Read returned 0x%0x but swacc strobed during 0x%0x", {rd_data_h, rd_data_l}, latched_data);
+ assert(event_count == 1) else $error("Observed excess swacc events: %0d", event_count);
+
+
+ // Verify that hwif changes 1 cycle after swmod
+ fired = 0;
+ fork
+ begin
+ ##0;
+ forever begin
+ assert(cb.hwif_out.r2.f.value == 'h4020);
+ if(cb.hwif_out.r2.f.swmod) break;
+ @cb;
+ end
+ fired = 1;
+ @cb;
+ forever begin
+ assert(cb.hwif_out.r2.f.value == 'h4221);
+ assert(cb.hwif_out.r2.f.swmod == 0);
+ @cb;
+ end
+ end
+
+ begin
+ cpuif.write('h2, 'h21);
+ cpuif.write('h3, 'h42);
+ repeat(3) @cb;
+ end
+ join_any
+ disable fork;
+ assert(fired);
+
+ // Verify that hwif changes 1 cycle after swmod
+ fired = 0;
+ fork
+ begin
+ ##0;
+ forever begin
+ assert(cb.hwif_out.r3.f.value == 'h1030);
+ if(cb.hwif_out.r3.f.swmod) break;
+ @cb;
+ end
+ fired = 1;
+ @cb;
+ forever begin
+ assert(cb.hwif_out.r3.f.value == 0);
+ assert(cb.hwif_out.r3.f.swmod == 0);
+ @cb;
+ end
+ end
+
+ begin
+ cpuif.assert_read('h4, 'h30);
+ cpuif.assert_read('h5, 'h10);
+ repeat(3) @cb;
+ end
+ join_any
+ disable fork;
+ assert(fired);
+
+ // Verify swacc and swmod assert when written
+ fired = 0;
+ fork
+ begin
+ ##0;
+ forever begin
+ assert(cb.hwif_out.r4.f.value == 'h1234);
+ if(cb.hwif_out.r4.f.swmod || cb.hwif_out.r4.f.swacc) begin
+ assert(cb.hwif_out.r4.f.swmod == 1);
+ assert(cb.hwif_out.r4.f.swacc == 1);
+ break;
+ end
+ @cb;
+ end
+ fired = 1;
+ @cb;
+ forever begin
+ assert(cb.hwif_out.r4.f.value == 'h4567);
+ assert(cb.hwif_out.r4.f.swmod == 0);
+ assert(cb.hwif_out.r4.f.swacc == 0);
+ @cb;
+ end
+ end
+
+ begin
+ cpuif.write('h6, 'h67);
+ cpuif.write('h7, 'h45);
+ repeat(3) @cb;
+ end
+ join_any
+ disable fork;
+ assert(fired);
+
+ // Verify swacc and swmod assert when written
+ fired = 0;
+ fork
+ begin
+ ##0;
+ forever begin
+ assert(cb.hwif_out.r5.f.value == 'hABCD);
+ if(cb.hwif_out.r5.f.swmod || cb.hwif_out.r5.f.swacc) begin
+ assert(cb.hwif_out.r5.f.swmod == 1);
+ assert(cb.hwif_out.r5.f.swacc == 1);
+ break;
+ end
+ @cb;
+ end
+ fired = 1;
+ @cb;
+ forever begin
+ assert(cb.hwif_out.r5.f.value == 'hEF12);
+ assert(cb.hwif_out.r5.f.swmod == 0);
+ assert(cb.hwif_out.r5.f.swacc == 0);
+ @cb;
+ end
+ end
+
+ begin
+ cpuif.write('h8, 'h12);
+ cpuif.write('h9, 'hEF);
+ repeat(3) @cb;
+ end
+ join_any
+ disable fork;
+ assert(fired);
+
+ // Verify that hwif changes 1 cycle after swmod
+ fired = 0;
+ fork
+ begin
+ ##0;
+ forever begin
+ assert(cb.hwif_out.r6.f.value == 'h1030);
+ if(cb.hwif_out.r6.f.swmod) break;
+ @cb;
+ end
+ fired = 1;
+ @cb;
+ forever begin
+ assert(cb.hwif_out.r6.f.value == 0);
+ assert(cb.hwif_out.r6.f.swmod == 0);
+ @cb;
+ end
+ end
+
+ begin
+ cpuif.assert_read('ha, 'h30);
+ cpuif.assert_read('hb, 'h10);
+ repeat(3) @cb;
+ end
+ join_any
+ disable fork;
+ assert(fired);
+
+{% endblock %}
diff --git a/tests/test_buffered_swacc_swmod/testcase.py b/tests/test_buffered_swacc_swmod/testcase.py
new file mode 100644
index 0000000..835b5ef
--- /dev/null
+++ b/tests/test_buffered_swacc_swmod/testcase.py
@@ -0,0 +1,5 @@
+from ..lib.sim_testcase import SimTestCase
+
+class Test(SimTestCase):
+ def test_dut(self):
+ self.run_test()
diff --git a/tests/test_counter_basics/__init__.py b/tests/test_counter_basics/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_counter_basics/regblock.rdl b/tests/test_counter_basics/regblock.rdl
new file mode 100644
index 0000000..db8286d
--- /dev/null
+++ b/tests/test_counter_basics/regblock.rdl
@@ -0,0 +1,69 @@
+addrmap top {
+ reg {
+ field {
+ sw=r; hw=na; counter;
+ } implied_up[3:0] = 0xD;
+ field {
+ sw=r; hw=na; counter;
+ incrvalue=1;
+ } up[7:4] = 0xD;
+ field {
+ sw=r; hw=na; counter;
+ decrvalue=1;
+ } down[11:8] = 0x4;
+ field {
+ sw=r; hw=r; counter;
+ incrvalue=1;
+ decrvalue=1;
+ overflow; underflow;
+ } updown[15:12] = 0;
+
+ field {
+ sw=r; hw=na; counter;
+ incrvalue=3;
+ decrvalue=3;
+ } updown2[19:16] = 0;
+
+ field {
+ sw=r; hw=na; counter;
+ incrwidth=2;
+ decrwidth=2;
+ } updown3[23:20] = 0;
+
+ field {
+ sw=r; hw=na; counter;
+ } updown4[27:24] = 0;
+
+ field {
+ sw=rw; hw=na;
+ } step[29:28] = 0;
+ updown4->incrvalue = step;
+ updown4->decrvalue = step;
+
+ field {
+ sw=w; hw=r; singlepulse;
+ } do_count_up[30:30] = 0;
+ field {
+ sw=w; hw=r; singlepulse;
+ } do_count_down[31:31] = 0;
+ updown2->incr = do_count_up;
+ updown2->decr = do_count_down;
+ updown3->incr = do_count_up;
+ updown3->decr = do_count_down;
+ updown4->incr = updown3->incr;
+ updown4->decr = updown3->decr;
+ } simple @ 0x0;
+
+
+ reg {
+ field {
+ sw=r; hw=na; rclr; counter;
+ } overflow_count[8] = 0;
+
+ field {
+ sw=r; hw=na; rclr; counter;
+ } underflow_count[8] = 0;
+ } wrap_counter @ 0x4;
+ wrap_counter.overflow_count->incr = simple.updown3->overflow;
+ wrap_counter.underflow_count->incr = simple.updown3->underflow;
+};
diff --git a/tests/test_counter_basics/tb_template.sv b/tests/test_counter_basics/tb_template.sv
new file mode 100644
index 0000000..0629271
--- /dev/null
+++ b/tests/test_counter_basics/tb_template.sv
@@ -0,0 +1,147 @@
+{% extends "lib/tb_base.sv" %}
+
+{% block seq %}
+ {% sv_line_anchor %}
+ ##1;
+ cb.rst <= '0;
+ ##1;
+
+ //--------------------------------------------------------------------------
+ // Test simple counters
+ //--------------------------------------------------------------------------
+ // up
+ cpuif.assert_read('h0, 'hD, 'h000F);
+ cb.hwif_in.simple.implied_up.incr <= '1;
+ repeat(4) @cb;
+ cb.hwif_in.simple.implied_up.incr <= '0;
+ cpuif.assert_read('h0, 'h1, 'h000F);
+
+ // up
+ cpuif.assert_read('h0, 'hD0, 'h00F0);
+ cb.hwif_in.simple.up.incr <= '1;
+ repeat(4) @cb;
+ cb.hwif_in.simple.up.incr <= '0;
+ cpuif.assert_read('h0, 'h10, 'h00F0);
+
+ // down
+ cpuif.assert_read('h0, 'h400, 'h0F00);
+ cb.hwif_in.simple.down.decr <= '1;
+ repeat(6) @cb;
+ cb.hwif_in.simple.down.decr <= '0;
+ cpuif.assert_read('h0, 'hE00, 'h0F00);
+
+ // up/down via hw
+ cpuif.assert_read('h0, 'h0000, 'hF000);
+ cb.hwif_in.simple.updown.incr <= '1;
+ repeat(6) @cb;
+ cb.hwif_in.simple.updown.incr <= '0;
+ cpuif.assert_read('h0, 'h6000, 'hF000);
+ cb.hwif_in.simple.updown.decr <= '1;
+ repeat(6) @cb;
+ cb.hwif_in.simple.updown.decr <= '0;
+ cpuif.assert_read('h0, 'h0000, 'hF000);
+ cb.hwif_in.simple.updown.decr <= '1;
+ repeat(6) @cb;
+ cb.hwif_in.simple.updown.decr <= '0;
+ cpuif.assert_read('h0, 'hA000, 'hF000);
+ cb.hwif_in.simple.updown.incr <= '1;
+ repeat(6) @cb;
+ cb.hwif_in.simple.updown.incr <= '0;
+ cpuif.assert_read('h0, 'h0000, 'hF000);
+
+ // up/down external underflow
+ fork
+ begin
+ ##0;
+ forever begin
+ assert(cb.hwif_out.simple.updown.value == 0);
+ if(cb.hwif_out.simple.updown.underflow) break;
+ @cb;
+ end
+ @cb;
+ forever begin
+ assert(cb.hwif_out.simple.updown.value == 15);
+ assert(cb.hwif_out.simple.updown.underflow == 0);
+ @cb;
+ end
+ end
+
+ begin
+ repeat(2) @cb;
+ cb.hwif_in.simple.updown.decr <= '1;
+ @cb;
+ cb.hwif_in.simple.updown.decr <= '0;
+ repeat(2) @cb;
+ end
+ join_any
+ disable fork;
+
+ // up/down external overflow
+ fork
+ begin
+ ##0;
+ forever begin
+ assert(cb.hwif_out.simple.updown.value == 15);
+ if(cb.hwif_out.simple.updown.overflow) break;
+ @cb;
+ end
+ @cb;
+ forever begin
+ assert(cb.hwif_out.simple.updown.value == 0);
+ assert(cb.hwif_out.simple.updown.overflow == 0);
+ @cb;
+ end
+ end
+
+ begin
+ repeat(2) @cb;
+ cb.hwif_in.simple.updown.incr <= '1;
+ @cb;
+ cb.hwif_in.simple.updown.incr <= '0;
+ repeat(2) @cb;
+ end
+ join_any
+ disable fork;
+
+
+ // up/down via sw
+ cpuif.assert_read('h0, 'h00000, 'hF0000);
+ repeat(3) cpuif.write('h0, 'h4000_0000); // incr
+ cpuif.assert_read('h0, 'h90000, 'hF0000);
+ repeat(3) cpuif.write('h0, 'h8000_0000); // decr
+ cpuif.assert_read('h0, 'h00000, 'hF0000);
+ repeat(3) cpuif.write('h0, 'h8000_0000); // decr
+ cpuif.assert_read('h0, 'h70000, 'hF0000);
+ repeat(3) cpuif.write('h0, 'h4000_0000); // incr
+ cpuif.assert_read('h0, 'h00000, 'hF0000);
+
+ // up/down via hw + external dynamic stepsize
+ cpuif.assert_read('h0, 'h000000, 'hF00000);
+ cb.hwif_in.simple.updown3.incrvalue <= 'h2;
+ repeat(3) cpuif.write('h0, 'h4000_0000); // incr
+ cpuif.assert_read('h0, 'h600000, 'hF00000);
+ cpuif.assert_read('h4, 'h00_00); // no overflows or underflows
+ cb.hwif_in.simple.updown3.decrvalue <= 'h3;
+ repeat(3) cpuif.write('h0, 'h8000_0000); // decr
+ cpuif.assert_read('h0, 'hD00000, 'hF00000);
+ cpuif.assert_read('h4, 'h01_00); // one underflow
+ cb.hwif_in.simple.updown3.incrvalue <= 'h1;
+ repeat(2) cpuif.write('h0, 'h4000_0000); // incr
+ cpuif.assert_read('h0, 'hF00000, 'hF00000);
+ cpuif.assert_read('h4, 'h00_00); // no overflows or underflows
+ repeat(1) cpuif.write('h0, 'h4000_0000); // incr
+ cpuif.assert_read('h0, 'h000000, 'hF00000);
+ cpuif.assert_read('h4, 'h00_01); // one overflow
+ repeat(32) cpuif.write('h0, 'h4000_0000); // incr
+ cpuif.assert_read('h0, 'h000000, 'hF00000);
+ cpuif.assert_read('h4, 'h00_02); // overflow
+
+ // up/down via hw + referenced dynamic stepsize
+ cpuif.assert_read('h0, 'h0000000, 'hF000000);
+ repeat(4) cpuif.write('h0, 'h4000_0000 + (2'h3 << 28)); // + 3
+ cpuif.assert_read('h0, 'hC000000, 'hF000000);
+ repeat(4) cpuif.write('h0, 'h8000_0000 + (2'h1 << 28)); // - 1
+ cpuif.assert_read('h0, 'h8000000, 'hF000000);
+ repeat(2) cpuif.write('h0, 'h8000_0000 + (2'h3 << 28)); // - 3
+ cpuif.assert_read('h0, 'h2000000, 'hF000000);
+{% endblock %}
diff --git a/tests/test_counter_basics/testcase.py b/tests/test_counter_basics/testcase.py
new file mode 100644
index 0000000..835b5ef
--- /dev/null
+++ b/tests/test_counter_basics/testcase.py
@@ -0,0 +1,5 @@
+from ..lib.sim_testcase import SimTestCase
+
+class Test(SimTestCase):
+ def test_dut(self):
+ self.run_test()
diff --git a/tests/test_counter_saturate/__init__.py b/tests/test_counter_saturate/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_counter_saturate/regblock.rdl b/tests/test_counter_saturate/regblock.rdl
new file mode 100644
index 0000000..8987372
--- /dev/null
+++ b/tests/test_counter_saturate/regblock.rdl
@@ -0,0 +1,88 @@
+addrmap top {
+ field strobe_t {
+ sw=w; hw=r; singlepulse;
+ };
+
+ reg {
+ field {
+ sw=r; hw=r; counter;
+ incrsaturate;
+ decrsaturate;
+ } count[8] = 0;
+
+ strobe_t increment[9:9] = 0;
+ strobe_t decrement[10:10] = 0;
+ strobe_t clear[11:11] = 0;
+ strobe_t set[12:12] = 0;
+
+ field {
+ sw=rw; hw=na;
+ } step[23:16] = 1;
+
+ count->incr = increment;
+ count->decr = decrement;
+ count->hwclr = clear;
+ count->hwset = set;
+ count->incrvalue = step;
+ count->decrvalue = step;
+ } saturate_via_bool @ 0x0;
+
+
+ reg {
+ field {
+ sw=r; hw=r; counter;
+ incrsaturate = 250;
+ decrsaturate = 5;
+ } count[8] = 0;
+
+ strobe_t increment[9:9] = 0;
+ strobe_t decrement[10:10] = 0;
+ strobe_t clear[11:11] = 0;
+ strobe_t set[12:12] = 0;
+
+ field {
+ sw=rw; hw=na;
+ } step[23:16] = 1;
+
+ count->incr = increment;
+ count->decr = decrement;
+ count->hwclr = clear;
+ count->hwset = set;
+ count->decrvalue = step;
+ count->incrvalue = count->decrvalue;
+ } saturate_via_const @ 0x4;
+
+
+ reg {
+ field {
+ sw=r; hw=r; counter;
+ } count[8] = 0;
+
+ strobe_t increment[9:9] = 0;
+ strobe_t decrement[10:10] = 0;
+ strobe_t clear[11:11] = 0;
+ strobe_t set[12:12] = 0;
+
+ field {
+ sw=rw; hw=na;
+ } step[23:16] = 1;
+
+ count->incr = increment;
+ count->decr = decrement;
+ count->hwclr = clear;
+ count->hwset = set;
+ count->incrvalue = step;
+ count->decrvalue = count->incrvalue;
+ } saturate_via_ref @ 0x8;
+
+ reg {
+ field {
+ sw=rw; hw=na;
+ } min[8] = 0x00;
+ field {
+ sw=rw; hw=na;
+ } max[8] = 0xFF;
+ } saturate_control @ 0xC;
+ saturate_via_ref.count -> decrsaturate = saturate_control.min;
+ saturate_via_ref.count -> incrsaturate = saturate_control.max;
+};
diff --git a/tests/test_counter_saturate/tb_template.sv b/tests/test_counter_saturate/tb_template.sv
new file mode 100644
index 0000000..7158c07
--- /dev/null
+++ b/tests/test_counter_saturate/tb_template.sv
@@ -0,0 +1,214 @@
+{% extends "lib/tb_base.sv" %}
+
+{% block seq %}
+ {% sv_line_anchor %}
+ ##1;
+ cb.rst <= '0;
+ ##1;
+
+ // counter controls are the same for each sub-test
+ `define incr (1<<9)
+ `define decr (1<<10)
+ `define clr (1<<11)
+ `define set (1<<12)
+ `define step(n) (n<<16)
+
+ //--------------------------------------------------------------------------
+ // Test incrsaturate = true; decrsaturate = true;
+ //--------------------------------------------------------------------------
+ cpuif.assert_read('h0, 'h00, 'hFF);
+
+ // incrsaturate via +1
+ cpuif.write('h0, `set);
+ cpuif.assert_read('h0, 'hFF, 'hFF);
+ cpuif.write('h0, `decr + `step(1));
+ cpuif.assert_read('h0, 'hFE, 'hFF);
+ cpuif.write('h0, `incr + `step(1));
+ cpuif.assert_read('h0, 'hFF, 'hFF);
+ cpuif.write('h0, `incr + `step(1));
+ cpuif.assert_read('h0, 'hFF, 'hFF);
+
+ // decrsaturate via +1
+ cpuif.write('h0, `clr);
+ cpuif.assert_read('h0, 'h00, 'hFF);
+ cpuif.write('h0, `incr + `step(1));
+ cpuif.assert_read('h0, 'h01, 'hFF);
+ cpuif.write('h0, `decr + `step(1));
+ cpuif.assert_read('h0, 'h00, 'hFF);
+ cpuif.write('h0, `decr + `step(1));
+ cpuif.assert_read('h0, 'h00, 'hFF);
+
+ // incrsaturate via larger steps
+ cpuif.write('h0, `set);
+ cpuif.assert_read('h0, 'hFF, 'hFF);
+ cpuif.write('h0, `decr + `step(1));
+ cpuif.assert_read('h0, 'hFE, 'hFF);
+ cpuif.write('h0, `incr + `step(2));
+ cpuif.assert_read('h0, 'hFF, 'hFF);
+ cpuif.write('h0, `incr + `step(3));
+ cpuif.assert_read('h0, 'hFF, 'hFF);
+ cpuif.write('h0, `incr + `step(255));
+ cpuif.assert_read('h0, 'hFF, 'hFF);
+
+ // decrsaturate via larger steps
+ cpuif.write('h0, `clr);
+ cpuif.assert_read('h0, 'h00, 'hFF);
+ cpuif.write('h0, `incr + `step(1));
+ cpuif.assert_read('h0, 'h01, 'hFF);
+ cpuif.write('h0, `decr + `step(2));
+ cpuif.assert_read('h0, 'h00, 'hFF);
+ cpuif.write('h0, `decr + `step(3));
+ cpuif.assert_read('h0, 'h00, 'hFF);
+ cpuif.write('h0, `decr + `step(255));
+ cpuif.assert_read('h0, 'h00, 'hFF);
+
+ //--------------------------------------------------------------------------
+ // Test incrsaturate = 250; decrsaturate = 5;
+ //--------------------------------------------------------------------------
+ cpuif.assert_read('h4, 'h00, 'hFF);
+
+ // incrsaturate via +1
+ cpuif.write('h4, `set);
+ cpuif.assert_read('h4, 'hFF, 'hFF);
+ cpuif.write('h4, `decr + `step(1));
+ cpuif.assert_read('h4, 'hFE, 'hFF);
+ cpuif.write('h4, `incr + `step(1));
+ cpuif.assert_read('h4, 'hFA, 'hFF);
+ cpuif.write('h4, `incr + `step(1));
+ cpuif.assert_read('h4, 'hFA, 'hFF);
+
+ // decrsaturate via +1
+ cpuif.write('h4, `clr);
+ cpuif.assert_read('h4, 'h00, 'hFF);
+ cpuif.write('h4, `incr + `step(1));
+ cpuif.assert_read('h4, 'h01, 'hFF);
+ cpuif.write('h4, `decr + `step(1));
+ cpuif.assert_read('h4, 'h05, 'hFF);
+ cpuif.write('h4, `decr + `step(1));
+ cpuif.assert_read('h4, 'h05, 'hFF);
+
+ // incrsaturate via larger steps
+ cpuif.write('h4, `set);
+ cpuif.assert_read('h4, 'hFF, 'hFF);
+ cpuif.write('h4, `decr + `step(1));
+ cpuif.assert_read('h4, 'hFE, 'hFF);
+ cpuif.write('h4, `incr + `step(2));
+ cpuif.assert_read('h4, 'hFA, 'hFF);
+ cpuif.write('h4, `incr + `step(3));
+ cpuif.assert_read('h4, 'hFA, 'hFF);
+ cpuif.write('h4, `incr + `step(255));
+ cpuif.assert_read('h4, 'hFA, 'hFF);
+
+ // decrsaturate via larger steps
+ cpuif.write('h4, `clr);
+ cpuif.assert_read('h4, 'h00, 'hFF);
+ cpuif.write('h4, `incr + `step(1));
+ cpuif.assert_read('h4, 'h01, 'hFF);
+ cpuif.write('h4, `decr + `step(2));
+ cpuif.assert_read('h4, 'h05, 'hFF);
+ cpuif.write('h4, `decr + `step(3));
+ cpuif.assert_read('h4, 'h05, 'hFF);
+ cpuif.write('h4, `decr + `step(255));
+ cpuif.assert_read('h4, 'h05, 'hFF);
+
+ //--------------------------------------------------------------------------
+ // Test incrsaturate = [; decrsaturate = ][;
+ //--------------------------------------------------------------------------
+ cpuif.assert_read('h8, 'h00, 'hFF);
+
+ // incrsaturate via +1
+ cpuif.write('h8, `set);
+ cpuif.assert_read('h8, 'hFF, 'hFF);
+ cpuif.write('h8, `decr + `step(1));
+ cpuif.assert_read('h8, 'hFE, 'hFF);
+ cpuif.write('h8, `incr + `step(1));
+ cpuif.assert_read('h8, 'hFF, 'hFF);
+ cpuif.write('h8, `incr + `step(1));
+ cpuif.assert_read('h8, 'hFF, 'hFF);
+
+ // decrsaturate via +1
+ cpuif.write('h8, `clr);
+ cpuif.assert_read('h8, 'h00, 'hFF);
+ cpuif.write('h8, `incr + `step(1));
+ cpuif.assert_read('h8, 'h01, 'hFF);
+ cpuif.write('h8, `decr + `step(1));
+ cpuif.assert_read('h8, 'h00, 'hFF);
+ cpuif.write('h8, `decr + `step(1));
+ cpuif.assert_read('h8, 'h00, 'hFF);
+
+ // incrsaturate via larger steps
+ cpuif.write('h8, `set);
+ cpuif.assert_read('h8, 'hFF, 'hFF);
+ cpuif.write('h8, `decr + `step(1));
+ cpuif.assert_read('h8, 'hFE, 'hFF);
+ cpuif.write('h8, `incr + `step(2));
+ cpuif.assert_read('h8, 'hFF, 'hFF);
+ cpuif.write('h8, `incr + `step(3));
+ cpuif.assert_read('h8, 'hFF, 'hFF);
+ cpuif.write('h8, `incr + `step(255));
+ cpuif.assert_read('h8, 'hFF, 'hFF);
+
+ // decrsaturate via larger steps
+ cpuif.write('h8, `clr);
+ cpuif.assert_read('h8, 'h00, 'hFF);
+ cpuif.write('h8, `incr + `step(1));
+ cpuif.assert_read('h8, 'h01, 'hFF);
+ cpuif.write('h8, `decr + `step(2));
+ cpuif.assert_read('h8, 'h00, 'hFF);
+ cpuif.write('h8, `decr + `step(3));
+ cpuif.assert_read('h8, 'h00, 'hFF);
+ cpuif.write('h8, `decr + `step(255));
+ cpuif.assert_read('h8, 'h00, 'hFF);
+
+ //--------------------------------------------------------------------------
+ // Test incrsaturate = ][; decrsaturate = ][;
+ //--------------------------------------------------------------------------
+ cpuif.write('hc, 'hFA_05);
+
+ cpuif.assert_read('h4, 'h05, 'hFF);
+
+ // incrsaturate via +1
+ cpuif.write('h8, `set);
+ cpuif.assert_read('h8, 'hFF, 'hFF);
+ cpuif.write('h8, `decr + `step(1));
+ cpuif.assert_read('h8, 'hFE, 'hFF);
+ cpuif.write('h8, `incr + `step(1));
+ cpuif.assert_read('h8, 'hFA, 'hFF);
+ cpuif.write('h8, `incr + `step(1));
+ cpuif.assert_read('h8, 'hFA, 'hFF);
+
+ // decrsaturate via +1
+ cpuif.write('h8, `clr);
+ cpuif.assert_read('h8, 'h00, 'hFF);
+ cpuif.write('h8, `incr + `step(1));
+ cpuif.assert_read('h8, 'h01, 'hFF);
+ cpuif.write('h8, `decr + `step(1));
+ cpuif.assert_read('h8, 'h05, 'hFF);
+ cpuif.write('h8, `decr + `step(1));
+ cpuif.assert_read('h8, 'h05, 'hFF);
+
+ // incrsaturate via larger steps
+ cpuif.write('h8, `set);
+ cpuif.assert_read('h8, 'hFF, 'hFF);
+ cpuif.write('h8, `decr + `step(1));
+ cpuif.assert_read('h8, 'hFE, 'hFF);
+ cpuif.write('h8, `incr + `step(2));
+ cpuif.assert_read('h8, 'hFA, 'hFF);
+ cpuif.write('h8, `incr + `step(3));
+ cpuif.assert_read('h8, 'hFA, 'hFF);
+ cpuif.write('h8, `incr + `step(255));
+ cpuif.assert_read('h8, 'hFA, 'hFF);
+
+ // decrsaturate via larger steps
+ cpuif.write('h8, `clr);
+ cpuif.assert_read('h8, 'h00, 'hFF);
+ cpuif.write('h8, `incr + `step(1));
+ cpuif.assert_read('h8, 'h01, 'hFF);
+ cpuif.write('h8, `decr + `step(2));
+ cpuif.assert_read('h8, 'h05, 'hFF);
+ cpuif.write('h8, `decr + `step(3));
+ cpuif.assert_read('h8, 'h05, 'hFF);
+ cpuif.write('h8, `decr + `step(255));
+ cpuif.assert_read('h8, 'h05, 'hFF);
+
+{% endblock %}
diff --git a/tests/test_counter_saturate/testcase.py b/tests/test_counter_saturate/testcase.py
new file mode 100644
index 0000000..835b5ef
--- /dev/null
+++ b/tests/test_counter_saturate/testcase.py
@@ -0,0 +1,5 @@
+from ..lib.sim_testcase import SimTestCase
+
+class Test(SimTestCase):
+ def test_dut(self):
+ self.run_test()
diff --git a/tests/test_counter_threshold/__init__.py b/tests/test_counter_threshold/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_counter_threshold/regblock.rdl b/tests/test_counter_threshold/regblock.rdl
new file mode 100644
index 0000000..4c5e8f8
--- /dev/null
+++ b/tests/test_counter_threshold/regblock.rdl
@@ -0,0 +1,36 @@
+addrmap top {
+ reg {
+ field {
+ sw=r; hw=r; counter;
+ incrthreshold;
+ decrthreshold;
+ } count[4] = 0;
+ } threshold_via_bool @ 0x0;
+
+
+ reg {
+ field {
+ sw=r; hw=r; counter;
+ incrthreshold = 10;
+ decrthreshold = 5;
+ } count[4] = 0;
+ } threshold_via_const @ 0x4;
+
+
+ reg {
+ field {
+ sw=r; hw=r; counter;
+ } count[4] = 0;
+ } threshold_via_ref @ 0x8;
+
+ reg {
+ field {
+ sw=rw; hw=na;
+ } min[4] = 0x4;
+ field {
+ sw=rw; hw=na;
+ } max[4] = 0xB;
+ } threshold_control @ 0xC;
+ threshold_via_ref.count -> decrthreshold = threshold_control.min;
+ threshold_via_ref.count -> incrthreshold = threshold_control.max;
+};
diff --git a/tests/test_counter_threshold/tb_template.sv b/tests/test_counter_threshold/tb_template.sv
new file mode 100644
index 0000000..145bc6e
--- /dev/null
+++ b/tests/test_counter_threshold/tb_template.sv
@@ -0,0 +1,115 @@
+{% extends "lib/tb_base.sv" %}
+
+{% block seq %}
+ {% sv_line_anchor %}
+ ##1;
+ cb.rst <= '0;
+ ##1;
+
+ //--------------------------------------------------------------------------
+ // Test incrthreshold = true; incrthreshold = true;
+ //--------------------------------------------------------------------------
+ cpuif.assert_read('h0, 'h0);
+
+ fork
+ begin
+ forever begin
+ @cb;
+ if(cb.hwif_out.threshold_via_bool.count.value == 0)
+ assert(cb.hwif_out.threshold_via_bool.count.decrthreshold == 1'b1);
+ else
+ assert(cb.hwif_out.threshold_via_bool.count.decrthreshold == 1'b0);
+
+ if(cb.hwif_out.threshold_via_bool.count.value == 15)
+ assert(cb.hwif_out.threshold_via_bool.count.incrthreshold == 1'b1);
+ else
+ assert(cb.hwif_out.threshold_via_bool.count.incrthreshold == 1'b0);
+ end
+ end
+
+ begin
+ @cb;
+ cb.hwif_in.threshold_via_bool.count.incr <= '1;
+ repeat(32) @cb;
+ cb.hwif_in.threshold_via_bool.count.incr <= '0;
+ cb.hwif_in.threshold_via_bool.count.decr <= '1;
+ repeat(32) @cb;
+ cb.hwif_in.threshold_via_bool.count.decr <= '0;
+ @cb;
+ @cb;
+ end
+ join_any
+ disable fork;
+
+ //--------------------------------------------------------------------------
+ // Test incrthreshold = 10; incrthreshold = 5;
+ //--------------------------------------------------------------------------
+ cpuif.assert_read('h4, 'h0);
+
+ fork
+ begin
+ forever begin
+ @cb;
+ if(cb.hwif_out.threshold_via_const.count.value <= 5)
+ assert(cb.hwif_out.threshold_via_const.count.decrthreshold == 1'b1);
+ else
+ assert(cb.hwif_out.threshold_via_const.count.decrthreshold == 1'b0);
+
+ if(cb.hwif_out.threshold_via_const.count.value >= 10)
+ assert(cb.hwif_out.threshold_via_const.count.incrthreshold == 1'b1);
+ else
+ assert(cb.hwif_out.threshold_via_const.count.incrthreshold == 1'b0);
+ end
+ end
+
+ begin
+ @cb;
+ cb.hwif_in.threshold_via_const.count.incr <= '1;
+ repeat(32) @cb;
+ cb.hwif_in.threshold_via_const.count.incr <= '0;
+ cb.hwif_in.threshold_via_const.count.decr <= '1;
+ repeat(32) @cb;
+ cb.hwif_in.threshold_via_const.count.decr <= '0;
+ @cb;
+ @cb;
+ end
+ join_any
+ disable fork;
+
+ //--------------------------------------------------------------------------
+ // Test incrthreshold = ref; incrthreshold =ref;
+ //--------------------------------------------------------------------------
+ cpuif.assert_read('h8, 'h0);
+
+ fork
+ begin
+ forever begin
+ @cb;
+ if(cb.hwif_out.threshold_via_ref.count.value <= 4)
+ assert(cb.hwif_out.threshold_via_ref.count.decrthreshold == 1'b1);
+ else
+ assert(cb.hwif_out.threshold_via_ref.count.decrthreshold == 1'b0);
+
+ if(cb.hwif_out.threshold_via_ref.count.value >= 11)
+ assert(cb.hwif_out.threshold_via_ref.count.incrthreshold == 1'b1);
+ else
+ assert(cb.hwif_out.threshold_via_ref.count.incrthreshold == 1'b0);
+ end
+ end
+
+ begin
+ @cb;
+ cb.hwif_in.threshold_via_ref.count.incr <= '1;
+ repeat(32) @cb;
+ cb.hwif_in.threshold_via_ref.count.incr <= '0;
+ cb.hwif_in.threshold_via_ref.count.decr <= '1;
+ repeat(32) @cb;
+ cb.hwif_in.threshold_via_ref.count.decr <= '0;
+ @cb;
+ @cb;
+ end
+ join_any
+ disable fork;
+
+
+{% endblock %}
diff --git a/tests/test_counter_threshold/testcase.py b/tests/test_counter_threshold/testcase.py
new file mode 100644
index 0000000..835b5ef
--- /dev/null
+++ b/tests/test_counter_threshold/testcase.py
@@ -0,0 +1,5 @@
+from ..lib.sim_testcase import SimTestCase
+
+class Test(SimTestCase):
+ def test_dut(self):
+ self.run_test()
diff --git a/tests/test_enum/__init__.py b/tests/test_enum/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_enum/regblock.rdl b/tests/test_enum/regblock.rdl
new file mode 100644
index 0000000..adb4fe4
--- /dev/null
+++ b/tests/test_enum/regblock.rdl
@@ -0,0 +1,12 @@
+addrmap top {
+ enum my_enum {
+ val_1 = 3 {name = "Value 1";};
+ val_2 = 4 {desc = "Second value";};
+ };
+ reg {
+ field {
+ encode = my_enum;
+ sw=rw; hw=na;
+ } f[2:0] = my_enum::val_2;
+ } r0;
+};
diff --git a/tests/test_enum/tb_template.sv b/tests/test_enum/tb_template.sv
new file mode 100644
index 0000000..457bd2b
--- /dev/null
+++ b/tests/test_enum/tb_template.sv
@@ -0,0 +1,22 @@
+{% extends "lib/tb_base.sv" %}
+
+{% block seq %}
+ {% sv_line_anchor %}
+ ##1;
+ cb.rst <= '0;
+ ##1;
+
+ // check enum values
+ assert(regblock_pkg::top__my_enum__val_1 == 'd3);
+ assert(regblock_pkg::top__my_enum__val_2 == 'd4);
+
+ // check initial conditions
+ cpuif.assert_read('h0, regblock_pkg::top__my_enum__val_2);
+
+ //---------------------------------
+ // set r0 = val_1
+ cpuif.write('h0, regblock_pkg::top__my_enum__val_1);
+
+ cpuif.assert_read('h0, regblock_pkg::top__my_enum__val_1);
+
+{% endblock %}
diff --git a/tests/test_enum/testcase.py b/tests/test_enum/testcase.py
new file mode 100644
index 0000000..835b5ef
--- /dev/null
+++ b/tests/test_enum/testcase.py
@@ -0,0 +1,5 @@
+from ..lib.sim_testcase import SimTestCase
+
+class Test(SimTestCase):
+ def test_dut(self):
+ self.run_test()
diff --git a/tests/test_extended_swacc_swmod/__init__.py b/tests/test_extended_swacc_swmod/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_extended_swacc_swmod/regblock.rdl b/tests/test_extended_swacc_swmod/regblock.rdl
new file mode 100644
index 0000000..de8be5c
--- /dev/null
+++ b/tests/test_extended_swacc_swmod/regblock.rdl
@@ -0,0 +1,17 @@
+addrmap top {
+ default regwidth = 8;
+
+ reg {
+ field {
+ sw=r; hw=w;
+ rd_swacc;
+ } f[8];
+ } r1;
+
+ reg {
+ field {
+ sw=rw; hw=r;
+ wr_swacc;
+ } f[8] = 20;
+ } r2;
+};
diff --git a/tests/test_extended_swacc_swmod/tb_template.sv b/tests/test_extended_swacc_swmod/tb_template.sv
new file mode 100644
index 0000000..ba724e6
--- /dev/null
+++ b/tests/test_extended_swacc_swmod/tb_template.sv
@@ -0,0 +1,68 @@
+{% extends "lib/tb_base.sv" %}
+
+{% block seq %}
+ {% sv_line_anchor %}
+ logic [7:0] counter;
+ 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 rd_swacc strobe
+ counter = 'h10;
+ cb.hwif_in.r1.f.next <= counter;
+ @cb;
+ event_count = 0;
+ fork
+ begin
+ ##0;
+ forever begin
+ counter++;
+ cb.hwif_in.r1.f.next <= counter;
+ @cb;
+ if(cb.hwif_out.r1.f.rd_swacc) begin
+ latched_data = counter;
+ 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 rd_swacc strobed during 0x%0x", rd_data, latched_data);
+ assert(event_count == 1) else $error("Observed excess rd_swacc events: %0d", event_count);
+
+
+ // Verify that hwif changes 1 cycle after wr_swacc
+ fork
+ begin
+ ##0;
+ forever begin
+ assert(cb.hwif_out.r2.f.value == 20);
+ if(cb.hwif_out.r2.f.wr_swacc) break;
+ @cb;
+ end
+ @cb;
+ forever begin
+ assert(cb.hwif_out.r2.f.value == 21);
+ assert(cb.hwif_out.r2.f.wr_swacc == 0);
+ @cb;
+ end
+ end
+
+ begin
+ cpuif.write('h1, 21);
+ @cb;
+ end
+ join_any
+ disable fork;
+
+{% endblock %}
diff --git a/tests/test_extended_swacc_swmod/testcase.py b/tests/test_extended_swacc_swmod/testcase.py
new file mode 100644
index 0000000..835b5ef
--- /dev/null
+++ b/tests/test_extended_swacc_swmod/testcase.py
@@ -0,0 +1,5 @@
+from ..lib.sim_testcase import SimTestCase
+
+class Test(SimTestCase):
+ def test_dut(self):
+ self.run_test()
diff --git a/tests/test_external/__init__.py b/tests/test_external/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_external/regblock.rdl b/tests/test_external/regblock.rdl
new file mode 100644
index 0000000..dd6fbd4
--- /dev/null
+++ b/tests/test_external/regblock.rdl
@@ -0,0 +1,56 @@
+addrmap top {
+ reg my_reg {
+ field {sw=rw; hw=r;} whatever[32] = 0;
+ };
+ reg my_reg_alt {
+ field {sw=r; hw=w;} whatever_a[3:2] = 0;
+ field {sw=w; hw=r;} whatever_b[4:4] = 0;
+ field {sw=rw; hw=r;} whatever_c[15:8] = 0;
+ };
+ reg my_wide_reg {
+ regwidth = 64;
+ accesswidth = 32;
+ field {sw=rw; hw=r;} whatever = 0;
+ };
+
+ external my_reg_alt ext_reg @ 0x00;
+ my_reg int_reg @ 0x04;
+ external my_wide_reg wide_ext_reg @ 0x10;
+ external my_reg ext_reg_array[32] @ 0x100 += 4;
+
+
+ external regfile {
+ my_reg placeholder @ 8*4-4;
+ } rf @ 0x1000;
+
+ addrmap {
+ my_reg placeholder @ 8*4-4;
+ } am @ 0x2000;
+
+ external mem {
+ memwidth = 32;
+ mementries = 8;
+ } mm @ 0x3000;
+
+ reg my_ro_reg {
+ field {sw=r; hw=w;} whatever[32] = 0;
+ };
+ reg my_wo_reg {
+ field {sw=w; hw=r;} whatever[32] = 0;
+ };
+ external my_ro_reg ro_reg @ 0x4000;
+ external my_wo_reg wo_reg @ 0x4004;
+
+ reg my_wide_ro_reg {
+ regwidth = 64;
+ accesswidth = 32;
+ field {sw=r; hw=w;} whatever[32] = 0;
+ };
+ reg my_wide_wo_reg {
+ regwidth = 64;
+ accesswidth = 32;
+ field {sw=w; hw=r;} whatever[32] = 0;
+ };
+ external my_wide_ro_reg wide_ro_reg @ 0x4010;
+ external my_wide_wo_reg wide_wo_reg @ 0x4018;
+};
diff --git a/tests/test_external/tb_template.sv b/tests/test_external/tb_template.sv
new file mode 100644
index 0000000..f5a047e
--- /dev/null
+++ b/tests/test_external/tb_template.sv
@@ -0,0 +1,311 @@
+{% extends "lib/tb_base.sv" %}
+
+
+
+{%- block dut_support %}
+ {% sv_line_anchor %}
+
+ external_reg ext_reg_inst (
+ .clk(clk),
+ .rst(rst),
+
+ .req(hwif_out.ext_reg.req),
+ .req_is_wr(hwif_out.ext_reg.req_is_wr),
+ .wr_data(hwif_out.ext_reg.wr_data),
+ .wr_biten(hwif_out.ext_reg.wr_biten),
+ .rd_ack(hwif_in.ext_reg.rd_ack),
+ .rd_data(hwif_in.ext_reg.rd_data),
+ .wr_ack(hwif_in.ext_reg.wr_ack)
+ );
+
+ external_reg #(
+ .SUBWORDS(2)
+ ) wide_ext_reg_inst (
+ .clk(clk),
+ .rst(rst),
+
+ .req(hwif_out.wide_ext_reg.req),
+ .req_is_wr(hwif_out.wide_ext_reg.req_is_wr),
+ .wr_data(hwif_out.wide_ext_reg.wr_data),
+ .wr_biten(hwif_out.wide_ext_reg.wr_biten),
+ .rd_ack(hwif_in.wide_ext_reg.rd_ack),
+ .rd_data(hwif_in.wide_ext_reg.rd_data),
+ .wr_ack(hwif_in.wide_ext_reg.wr_ack)
+ );
+
+ for(genvar i=0; i<32; i++) begin : array
+ external_reg ext_reg_inst (
+ .clk(clk),
+ .rst(rst),
+
+ .req(hwif_out.ext_reg_array[i].req),
+ .req_is_wr(hwif_out.ext_reg_array[i].req_is_wr),
+ .wr_data(hwif_out.ext_reg_array[i].wr_data),
+ .wr_biten(hwif_out.ext_reg_array[i].wr_biten),
+ .rd_ack(hwif_in.ext_reg_array[i].rd_ack),
+ .rd_data(hwif_in.ext_reg_array[i].rd_data),
+ .wr_ack(hwif_in.ext_reg_array[i].wr_ack)
+ );
+ end
+
+ external_block #(
+ .ADDR_WIDTH(5)
+ ) rf_inst (
+ .clk(clk),
+ .rst(rst),
+
+ .req(hwif_out.rf.req),
+ .req_is_wr(hwif_out.rf.req_is_wr),
+ .addr(hwif_out.rf.addr),
+ .wr_data(hwif_out.rf.wr_data),
+ .wr_biten(hwif_out.rf.wr_biten),
+ .rd_ack(hwif_in.rf.rd_ack),
+ .rd_data(hwif_in.rf.rd_data),
+ .wr_ack(hwif_in.rf.wr_ack)
+ );
+
+ external_block #(
+ .ADDR_WIDTH(5)
+ ) am_inst (
+ .clk(clk),
+ .rst(rst),
+
+ .req(hwif_out.am.req),
+ .req_is_wr(hwif_out.am.req_is_wr),
+ .addr(hwif_out.am.addr),
+ .wr_data(hwif_out.am.wr_data),
+ .wr_biten(hwif_out.am.wr_biten),
+ .rd_ack(hwif_in.am.rd_ack),
+ .rd_data(hwif_in.am.rd_data),
+ .wr_ack(hwif_in.am.wr_ack)
+ );
+
+ external_block #(
+ .ADDR_WIDTH(5)
+ ) mm_inst (
+ .clk(clk),
+ .rst(rst),
+
+ .req(hwif_out.mm.req),
+ .req_is_wr(hwif_out.mm.req_is_wr),
+ .addr(hwif_out.mm.addr),
+ .wr_data(hwif_out.mm.wr_data),
+ .wr_biten(hwif_out.mm.wr_biten),
+ .rd_ack(hwif_in.mm.rd_ack),
+ .rd_data(hwif_in.mm.rd_data),
+ .wr_ack(hwif_in.mm.wr_ack)
+ );
+
+ external_reg wo_reg_inst (
+ .clk(clk),
+ .rst(rst),
+
+ .req(hwif_out.wo_reg.req),
+ .req_is_wr(hwif_out.wo_reg.req_is_wr),
+ .wr_data(hwif_out.wo_reg.wr_data),
+ .wr_biten(hwif_out.wo_reg.wr_biten),
+ .rd_ack(),
+ .rd_data(),
+ .wr_ack(hwif_in.wo_reg.wr_ack)
+ );
+
+ external_reg ro_reg_inst (
+ .clk(clk),
+ .rst(rst),
+
+ .req(hwif_out.ro_reg.req),
+ .req_is_wr(hwif_out.ro_reg.req_is_wr),
+ .wr_data(32'b0),
+ .wr_biten(32'b0),
+ .rd_ack(hwif_in.ro_reg.rd_ack),
+ .rd_data(hwif_in.ro_reg.rd_data),
+ .wr_ack()
+ );
+
+ external_reg #(
+ .SUBWORDS(2)
+ ) wide_wo_reg_inst (
+ .clk(clk),
+ .rst(rst),
+
+ .req(hwif_out.wide_wo_reg.req),
+ .req_is_wr(hwif_out.wide_wo_reg.req_is_wr),
+ .wr_data(hwif_out.wide_wo_reg.wr_data),
+ .wr_biten(hwif_out.wide_wo_reg.wr_biten),
+ .rd_ack(),
+ .rd_data(),
+ .wr_ack(hwif_in.wide_wo_reg.wr_ack)
+ );
+
+ external_reg #(
+ .SUBWORDS(2)
+ ) wide_ro_reg_inst (
+ .clk(clk),
+ .rst(rst),
+
+ .req(hwif_out.wide_ro_reg.req),
+ .req_is_wr(hwif_out.wide_ro_reg.req_is_wr),
+ .wr_data(32'b0),
+ .wr_biten(32'b0),
+ .rd_ack(hwif_in.wide_ro_reg.rd_ack),
+ .rd_data(hwif_in.wide_ro_reg.rd_data),
+ .wr_ack()
+ );
+{%- endblock %}
+
+
+
+{% block seq %}
+ logic [31:0] x;
+ {% sv_line_anchor %}
+ ##1;
+ cb.rst <= '0;
+ ##1;
+
+ //--------------------------------------------------------------------------
+ // Simple read/write tests
+ //--------------------------------------------------------------------------
+
+ repeat(20) begin
+ x = $urandom();
+ cpuif.write('h00, x);
+ cpuif.assert_read('h00, x);
+ assert(ext_reg_inst.value == x);
+ end
+
+ for(int i=0; i<2; i++) begin
+ repeat(20) begin
+ x = $urandom();
+ cpuif.write('h10 + i*4, x);
+ cpuif.assert_read('h10 + i*4, x);
+ assert(wide_ext_reg_inst.value[i] == x);
+ end
+ end
+
+ for(int i=0; i<32; i++) begin
+ repeat(20) begin
+ x = $urandom();
+ cpuif.write('h100 + i*4, x);
+ cpuif.assert_read('h100 + i*4, x);
+ end
+ end
+
+ for(int i=0; i<8; i++) begin
+ repeat(20) begin
+ x = $urandom();
+ cpuif.write('h1000 + i*4, x);
+ cpuif.assert_read('h1000 + i*4, x);
+ assert(rf_inst.mem[i] == x);
+ end
+ end
+
+ for(int i=0; i<8; i++) begin
+ repeat(20) begin
+ x = $urandom();
+ cpuif.write('h2000 + i*4, x);
+ cpuif.assert_read('h2000 + i*4, x);
+ assert(am_inst.mem[i] == x);
+ end
+ end
+
+ for(int i=0; i<8; i++) begin
+ repeat(20) begin
+ x = $urandom();
+ cpuif.write('h3000 + i*4, x);
+ cpuif.assert_read('h3000 + i*4, x);
+ assert(mm_inst.mem[i] == x);
+ end
+ end
+
+ repeat(20) begin
+ x = $urandom();
+ ro_reg_inst.value <= x;
+ cpuif.write('h4000, ~x);
+ cpuif.assert_read('h4000, x);
+ assert(ro_reg_inst.value == x);
+ end
+
+ repeat(20) begin
+ x = $urandom();
+ cpuif.write('h4004, x);
+ cpuif.assert_read('h4004, 0);
+ assert(wo_reg_inst.value == x);
+ end
+
+ for(int i=0; i<2; i++) begin
+ repeat(20) begin
+ x = $urandom();
+ wide_ro_reg_inst.value[i] <= x;
+ cpuif.write('h4010 + i*4, ~x);
+ cpuif.assert_read('h4010 + i*4, x);
+ assert(wide_ro_reg_inst.value[i] == x);
+ end
+ end
+
+ for(int i=0; i<2; i++) begin
+ repeat(20) begin
+ x = $urandom();
+ cpuif.write('h4018 + i*4, x);
+ cpuif.assert_read('h4018 + i*4, 0);
+ assert(wide_wo_reg_inst.value[i] == x);
+ end
+ end
+
+ //--------------------------------------------------------------------------
+ // Pipelined access
+ //--------------------------------------------------------------------------
+ // init array with unique known value
+ cpuif.write('h4, 'h1234);
+ for(int i=0; i<32; i++) begin
+ cpuif.write('h100 + i*4, 'h100 + i);
+ end
+ for(int i=0; i<8; i++) begin
+ cpuif.write('h1000 + i*4, 'h1000 + i);
+ cpuif.write('h2000 + i*4, 'h2000 + i);
+ cpuif.write('h3000 + i*4, 'h3000 + i);
+ end
+
+ // random pipelined read/writes
+ repeat(256) begin
+ fork
+ begin
+ automatic int i, j;
+ i = $urandom_range(31, 0);
+ j = $urandom_range(7, 0);
+ case($urandom_range(9,0))
+ // external reg
+ 0: cpuif.write('h100 + i*4, 'h100 + i);
+ 1: cpuif.assert_read('h100 + i*4, 'h100 + i);
+ // internal reg
+ 2: cpuif.write('h4, 'h1234);
+ 3: cpuif.assert_read('h4, 'h1234);
+ // external regfile
+ 4: cpuif.write('h1000 + j*4, 'h1000 + j);
+ 5: cpuif.assert_read('h1000 + j*4, 'h1000 + j);
+ // external addrmap
+ 6: cpuif.write('h2000 + j*4, 'h2000 + j);
+ 7: cpuif.assert_read('h2000 + j*4, 'h2000 + j);
+ // external mem
+ 8: cpuif.write('h3000 + j*4, 'h3000 + j);
+ 9: cpuif.assert_read('h3000 + j*4, 'h3000 + j);
+ endcase
+ end
+ join_none
+ end
+ wait fork;
+
+ // Check register struct bit-order
+ repeat(32) begin
+ regblock_pkg::top__my_reg_alt__external__fields__in_t fields_in;
+ regblock_pkg::top__my_reg_alt__external__fields__out_t fields_out;
+ fields_in = $urandom();
+ fields_out = $urandom();
+
+ assert(fields_in.whatever_a == fields_in[3:2]);
+ assert(fields_in.whatever_c == fields_in[15:8]);
+
+ assert(fields_out.whatever_b == fields_out[4]);
+ assert(fields_out.whatever_c == fields_out[15:8]);
+ end
+
+{% endblock %}
diff --git a/tests/test_external/testcase.py b/tests/test_external/testcase.py
new file mode 100644
index 0000000..623ad67
--- /dev/null
+++ b/tests/test_external/testcase.py
@@ -0,0 +1,29 @@
+from parameterized import parameterized_class
+
+from ..lib.sim_testcase import SimTestCase
+from ..lib.test_params import get_permutations
+from ..lib.cpuifs.apb4 import APB4
+from ..lib.cpuifs.axi4lite import AXI4Lite
+from ..lib.cpuifs.passthrough import Passthrough
+
+@parameterized_class(get_permutations({
+ "cpuif": [
+ APB4(),
+ AXI4Lite(),
+ Passthrough(),
+ ],
+ "retime_read_fanin": [True, False],
+ "retime_read_response": [True, False],
+ "retime_external": [True, False],
+}))
+class Test(SimTestCase):
+ extra_tb_files = [
+ "../lib/external_reg.sv",
+ "../lib/external_block.sv",
+ ]
+ init_hwif_in = False
+ clocking_hwif_in = False
+ timeout_clk_cycles = 30000
+
+ def test_dut(self):
+ self.run_test()
diff --git a/tests/test_field_types/__init__.py b/tests/test_field_types/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_field_types/regblock.rdl b/tests/test_field_types/regblock.rdl
new file mode 100644
index 0000000..0557159
--- /dev/null
+++ b/tests/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/tests/test_field_types/tb_template.sv b/tests/test_field_types/tb_template.sv
new file mode 100644
index 0000000..159c115
--- /dev/null
+++ b/tests/test_field_types/tb_template.sv
@@ -0,0 +1,131 @@
+{% extends "lib/tb_base.sv" %}
+
+{% block seq %}
+ {% sv_line_anchor %}
+ 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.next <= 9;
+ cpuif.assert_read('h0, 11);
+ assert(cb.hwif_out.r1.f.value == 11);
+ cb.hwif_in.r1.f.next <= 12;
+ cb.hwif_in.r1.f.we <= 1;
+ @cb;
+ cb.hwif_in.r1.f.next <= 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.next <= 29;
+ cpuif.assert_read('h2, 31);
+ cb.hwif_in.r3.f.next <= 32;
+ cb.hwif_in.r3.f.wel <= 0;
+ @cb;
+ cb.hwif_in.r3.f.next <= 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.next <= 9;
+ cpuif.assert_read('h4, 50);
+ assert(cb.hwif_out.r5.f.value == 50);
+ cb.hwif_in.r5.f.next <= 52;
+ cb.hwif_in.r5.f.we <= 1;
+ @cb;
+ cb.hwif_in.r5.f.next <= 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.next <= 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.next <= 89;
+ cpuif.assert_read('h8, 0);
+ assert(cb.hwif_out.r9.f.value == 91);
+ cb.hwif_in.r9.f.next <= 92;
+ cb.hwif_in.r9.f.we <= 1;
+ @cb;
+ cb.hwif_in.r9.f.next <= 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/tests/test_field_types/testcase.py b/tests/test_field_types/testcase.py
new file mode 100644
index 0000000..835b5ef
--- /dev/null
+++ b/tests/test_field_types/testcase.py
@@ -0,0 +1,5 @@
+from ..lib.sim_testcase import SimTestCase
+
+class Test(SimTestCase):
+ def test_dut(self):
+ self.run_test()
diff --git a/tests/test_fixedpoint/__init__.py b/tests/test_fixedpoint/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_fixedpoint/regblock.rdl b/tests/test_fixedpoint/regblock.rdl
new file mode 100644
index 0000000..42f0f48
--- /dev/null
+++ b/tests/test_fixedpoint/regblock.rdl
@@ -0,0 +1,39 @@
+addrmap top {
+ default accesswidth = 64;
+ default regwidth = 64;
+ reg {
+ field {
+ sw = rw; hw = r;
+ intwidth = 8;
+ fracwidth = 8;
+ } f_Q8_8[16] = 0;
+ field {
+ sw = r; hw = w;
+ intwidth = 32;
+ } f_Q32_n12[20];
+ field {
+ sw = rw; hw = r;
+ fracwidth = 32;
+ is_signed;
+ } f_SQn8_32[24] = 0;
+ field {
+ sw = rw; hw = r;
+ fracwidth = 7;
+ is_signed;
+ } f_SQn6_7 = 0;
+ } r1 @ 0x0;
+
+ reg {
+ field {
+ sw = r; hw = w;
+ is_signed;
+ } f_signed[16];
+ field {
+ sw = rw; hw = r;
+ is_signed = false;
+ } f_unsigned[16] = 0;
+ field {
+ sw = r; hw = w;
+ } f_no_sign[16];
+ } r2 @ 0x8;
+};
diff --git a/tests/test_fixedpoint/tb_template.sv b/tests/test_fixedpoint/tb_template.sv
new file mode 100644
index 0000000..3386efb
--- /dev/null
+++ b/tests/test_fixedpoint/tb_template.sv
@@ -0,0 +1,77 @@
+{% extends "lib/tb_base.sv" %}
+
+{% block seq %}
+ {% sv_line_anchor %}
+ ##1;
+ cb.rst <= '0;
+ ##1;
+
+ // set all fields to all 1s
+ cb.hwif_in.r1.f_Q32_n12.next <= '1;
+ cb.hwif_in.r2.f_signed.next <= '1;
+ cb.hwif_in.r2.f_no_sign.next <= '1;
+ cpuif.write('h0, 64'hFFFF_FFFF_FFFF_FFFF);
+ cpuif.write('h8, 64'hFFFF_FFFF_FFFF_FFFF);
+ @cb;
+
+ // Q8.8
+ // verify bit range
+ assert(cb.hwif_out.r1.f_Q8_8.value[7:-8] == '1);
+ // verify bit width
+ assert($size(hwif_out.r1.f_Q8_8.value) == 16);
+ // verify unsigned
+ assert(cb.hwif_out.r1.f_Q8_8.value > 0);
+
+ // Q32.-12
+ // verify bit range
+ assert(hwif_in.r1.f_Q32_n12.next[31:12] == '1);
+ // verify bit width
+ assert($size(hwif_in.r1.f_Q32_n12.next) == 20);
+ // verify unsigned
+ assert(hwif_in.r1.f_Q32_n12.next > 0);
+
+ // SQ-8.32
+ // verify bit range
+ assert(cb.hwif_out.r1.f_SQn8_32.value[-9:-32] == '1);
+ // verify bit width
+ assert($size(hwif_out.r1.f_SQn8_32.value) == 24);
+ // verify signed
+ assert(cb.hwif_out.r1.f_SQn8_32.value < 0);
+
+ // SQ-6.7
+ // verify bit range
+ assert(cb.hwif_out.r1.f_SQn6_7.value[-7:-7] == '1);
+ // verify bit width
+ assert($size(hwif_out.r1.f_SQn6_7.value) == 1);
+ // verify signed
+ assert(cb.hwif_out.r1.f_SQn6_7.value < 0);
+
+ // 16-bit signed integer
+ // verify bit range
+ assert(hwif_in.r2.f_signed.next[15:0] == '1);
+ // verify bit width
+ assert($size(hwif_in.r2.f_signed.next) == 16);
+ // verify signed
+ assert(hwif_in.r2.f_signed.next < 0);
+
+ // 16-bit unsigned integer
+ // verify bit range
+ assert(cb.hwif_out.r2.f_unsigned.value[15:0] == '1);
+ // verify bit width
+ assert($size(hwif_out.r2.f_unsigned.value) == 16);
+ // verify unsigned
+ assert(cb.hwif_out.r2.f_unsigned.value > 0);
+
+ // 16-bit field (no sign)
+ // verify bit range
+ assert(hwif_in.r2.f_no_sign.next[15:0] == '1);
+ // verify bit width
+ assert($size(hwif_in.r2.f_no_sign.next) == 16);
+ // verify unsigned (logic is unsigned in SV)
+ assert(hwif_in.r2.f_no_sign.next > 0);
+
+ // verify readback
+ cpuif.assert_read('h0, 64'h1FFF_FFFF_FFFF_FFFF);
+ cpuif.assert_read('h8, 64'h0000_FFFF_FFFF_FFFF);
+
+{% endblock %}
diff --git a/tests/test_fixedpoint/testcase.py b/tests/test_fixedpoint/testcase.py
new file mode 100644
index 0000000..835b5ef
--- /dev/null
+++ b/tests/test_fixedpoint/testcase.py
@@ -0,0 +1,5 @@
+from ..lib.sim_testcase import SimTestCase
+
+class Test(SimTestCase):
+ def test_dut(self):
+ self.run_test()
diff --git a/tests/test_hw_access/__init__.py b/tests/test_hw_access/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_hw_access/regblock.rdl b/tests/test_hw_access/regblock.rdl
new file mode 100644
index 0000000..8784840
--- /dev/null
+++ b/tests/test_hw_access/regblock.rdl
@@ -0,0 +1,73 @@
+addrmap top {
+ reg {
+ field {
+ sw=rw; hw=na;
+ } hw_enable[7:0] = 0xFF;
+
+ field {
+ sw=rw; hw=na;
+ } hw_mask[15:8] = 0x00;
+
+ field {
+ sw=rw; hw=na;
+ } hw_clr[16:16] = 0;
+
+ field {
+ sw=rw; hw=na;
+ } hw_set[17:17] = 0;
+
+ field {
+ sw=rw; hw=na;
+ } hw_we[18:18] = 0;
+
+ field {
+ sw=rw; hw=na;
+ } hw_wel[20:20] = 1;
+ } hw_ctrl;
+
+
+ reg {
+ field {
+ sw=r; hw=w;
+ we; hwclr; hwset;
+ } f[7:0] = 0x11;
+ } r1;
+ r1.f->hwenable = hw_ctrl.hw_enable;
+
+
+ reg {
+ field {
+ sw=r; hw=w;
+ we; hwclr; hwset;
+ } f[7:0] = 0x22;
+ } r2;
+ r2.f->hwmask = hw_ctrl.hw_mask;
+
+
+ reg {
+ field {
+ sw=rw; hw=w;
+ } f[7:0] = 0x33;
+ } r3;
+ r3.f->hwenable = hw_ctrl.hw_enable;
+ r3.f->hwclr = hw_ctrl.hw_clr;
+ r3.f->hwset = hw_ctrl.hw_set;
+ r3.f->we = hw_ctrl.hw_we;
+
+ reg {
+ field {
+ sw=rw; hw=w;
+ } f[7:0] = 0x44;
+ } r4;
+ r4.f->wel = hw_ctrl.hw_wel;
+
+ reg {
+ signal {} f_next_value[8];
+ signal {} f_we;
+ field {
+ sw=rw; hw=w;
+ next = f_next_value;
+ we = f_we;
+ } f[7:0] = 0x55;
+ } r5;
+};
diff --git a/tests/test_hw_access/tb_template.sv b/tests/test_hw_access/tb_template.sv
new file mode 100644
index 0000000..d2cca1d
--- /dev/null
+++ b/tests/test_hw_access/tb_template.sv
@@ -0,0 +1,105 @@
+{% extends "lib/tb_base.sv" %}
+
+{% block seq %}
+ {% sv_line_anchor %}
+ ##1;
+ cb.rst <= '0;
+ ##1;
+
+ // check initial conditions
+ cpuif.assert_read('h4, 'h11);
+ cpuif.assert_read('h8, 'h22);
+ cpuif.assert_read('hC, 'h33);
+
+ //---------------------------------
+ // set hwenable = F0
+ cpuif.write('h0, 'h00_F0);
+
+ // test hwenable + we
+ cb.hwif_in.r1.f.next <= 'hAB;
+ cb.hwif_in.r1.f.we <= '1;
+ @cb;
+ cb.hwif_in.r1.f.we <= '0;
+ cpuif.assert_read('h4, 'hA1);
+
+ // test hwenable + hwclr
+ cb.hwif_in.r1.f.hwclr <= '1;
+ @cb;
+ cb.hwif_in.r1.f.hwclr <= '0;
+ cpuif.assert_read('h4, 'h01);
+
+ // test hwenable + hwset
+ cb.hwif_in.r1.f.hwset <= '1;
+ @cb;
+ cb.hwif_in.r1.f.hwset <= '0;
+ cpuif.assert_read('h4, 'hF1);
+
+
+ //---------------------------------
+ // set hwmask = F0
+ cpuif.write('h0, 'hF0_00);
+
+ // test hwmask + we
+ cb.hwif_in.r2.f.next <= 'hAB;
+ cb.hwif_in.r2.f.we <= '1;
+ @cb;
+ cb.hwif_in.r2.f.we <= '0;
+ cpuif.assert_read('h8, 'h2B);
+
+ // test hwmask + hwclr
+ cb.hwif_in.r2.f.hwclr <= '1;
+ @cb;
+ cb.hwif_in.r2.f.hwclr <= '0;
+ cpuif.assert_read('h8, 'h20);
+
+ // test hwmask + hwset
+ cb.hwif_in.r2.f.hwset <= '1;
+ @cb;
+ cb.hwif_in.r2.f.hwset <= '0;
+ cpuif.assert_read('h8, 'h2F);
+
+ //---------------------------------
+ // test hwenable + hwclr via reference
+ // toggle hwenable = F0, hwclr=1
+ cpuif.write('h0, 'h1_00_F0);
+ cpuif.write('h0, 'h0_00_00);
+ cpuif.assert_read('hC, 'h03);
+
+ // test hwenable + hwset via reference
+ // toggle hwenable = 0F, hwset=1
+ cpuif.write('h0, 'h2_00_0F);
+ cpuif.write('h0, 'h0_00_00);
+ cpuif.assert_read('hC, 'h0F);
+
+ // test hwenable + we via reference
+ cb.hwif_in.r3.f.next <= 'hAA;
+ // toggle hwenable = 0F, we=1
+ cpuif.write('h0, 'h4_00_0F);
+ cpuif.write('h0, 'h0_00_00);
+ cpuif.assert_read('hC, 'h0A);
+
+ //---------------------------------
+ // test wel via reference
+ cb.hwif_in.r4.f.next <= 'hBB;
+ // toggle wel
+ cpuif.write('h0, 'h10_00_00);
+ cpuif.write('h0, 'h00_00_00);
+ cpuif.assert_read('h10, 'hBB);
+
+ cb.hwif_in.r4.f.next <= 'hCC;
+ // toggle wel
+ cpuif.write('h0, 'h10_00_00);
+ cpuif.write('h0, 'h00_00_00);
+ cpuif.assert_read('h10, 'hCC);
+
+ //---------------------------------
+ // test we and next via reference
+ cb.hwif_in.r5.f_next_value <= 'h54;
+ cpuif.assert_read('h14, 'h55);
+ cb.hwif_in.r5.f_next_value <= 'h56;
+ cb.hwif_in.r5.f_we <= '1;
+ @cb;
+ cb.hwif_in.r5.f_next_value <= '0;
+ cb.hwif_in.r5.f_we <= '0;
+ cpuif.assert_read('h14, 'h56);
+{% endblock %}
diff --git a/tests/test_hw_access/testcase.py b/tests/test_hw_access/testcase.py
new file mode 100644
index 0000000..835b5ef
--- /dev/null
+++ b/tests/test_hw_access/testcase.py
@@ -0,0 +1,5 @@
+from ..lib.sim_testcase import SimTestCase
+
+class Test(SimTestCase):
+ def test_dut(self):
+ self.run_test()
diff --git a/tests/test_interrupts/__init__.py b/tests/test_interrupts/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_interrupts/regblock.rdl b/tests/test_interrupts/regblock.rdl
new file mode 100644
index 0000000..687ba88
--- /dev/null
+++ b/tests/test_interrupts/regblock.rdl
@@ -0,0 +1,229 @@
+addrmap top {
+ //---------------------------------
+ reg {
+ field ctrl_t {
+ sw=rw; hw=na;
+ };
+ ctrl_t irq0[8] = 0;
+ ctrl_t irq1[1] = 0;
+ }
+ ctrl_enable @ 0x100,
+ ctrl_mask @ 0x104,
+ ctrl_haltenable @ 0x108,
+ ctrl_haltmask @ 0x10c;
+ reg {
+ field ctrl_t {
+ sw=rw; hw=na;
+ };
+ ctrl_t irq0[1] = 0;
+ ctrl_t irq1[1] = 0;
+ }
+ ctrl_we @ 0x110,
+ ctrl_wel @ 0x114;
+ //---------------------------------
+
+ reg {
+ field intr_t {
+ sw=rw; hw=w;
+ level intr;
+ woclr;
+ };
+
+ intr_t irq0[8] = 0;
+ intr_t irq1[1] = 0;
+ }
+ level_irqs_1 @ 0x0,
+ level_irqs_2 @ 0x4,
+ level_irqs_3 @ 0x8;
+
+ level_irqs_2.irq0->enable = ctrl_enable.irq0;
+ level_irqs_2.irq1->enable = ctrl_enable.irq1;
+ level_irqs_2.irq0->haltenable = ctrl_haltenable.irq0;
+ level_irqs_2.irq1->haltenable = ctrl_haltenable.irq1;
+ level_irqs_3.irq0->mask = ctrl_mask.irq0;
+ level_irqs_3.irq1->mask = ctrl_mask.irq1;
+ level_irqs_3.irq0->haltmask = ctrl_haltmask.irq0;
+ level_irqs_3.irq1->haltmask = ctrl_haltmask.irq1;
+
+ reg {
+ field intr_t {
+ sw=rw; hw=w;
+ level intr;
+ woclr;
+ we;
+ };
+
+ intr_t irq0[8] = 0;
+ intr_t irq1[1] = 0;
+ } level_irqs_we @ 0x10;
+
+ reg {
+ field intr_t {
+ sw=rw; hw=w;
+ level intr;
+ woclr;
+ wel;
+ };
+
+ intr_t irq0[8] = 0;
+ intr_t irq1[1] = 0;
+ } level_irqs_wel @ 0x14;
+
+ level_irqs_we.irq0->we = ctrl_we.irq0;
+ level_irqs_we.irq1->we = ctrl_we.irq1;
+ level_irqs_wel.irq0->wel = ctrl_wel.irq0;
+ level_irqs_wel.irq1->wel = ctrl_wel.irq1;
+
+ //---------------------------------
+
+ reg {
+ field intr_t {
+ sw=rw; hw=w;
+ posedge intr;
+ woclr;
+ };
+
+ intr_t irq0[8] = 0;
+ intr_t irq1[1] = 0;
+ } posedge_irqs @ 0x20;
+
+ reg {
+ field intr_t {
+ sw=rw; hw=w;
+ posedge intr;
+ woclr;
+ };
+
+ intr_t irq0[8] = 0;
+ intr_t irq1[1] = 0;
+ } posedge_we_irqs @ 0x24;
+
+ posedge_we_irqs.irq0->we = ctrl_we.irq0;
+ posedge_we_irqs.irq1->we = ctrl_we.irq1;
+
+ reg {
+ field intr_t {
+ sw=rw; hw=w;
+ posedge intr;
+ woclr;
+ };
+
+ intr_t irq0[8] = 0;
+ intr_t irq1[1] = 0;
+ } posedge_wel_irqs @ 0x28;
+
+ posedge_wel_irqs.irq0->wel = ctrl_wel.irq0;
+ posedge_wel_irqs.irq1->wel = ctrl_wel.irq1;
+
+ //---------------------------------
+
+ reg {
+ field intr_t {
+ sw=rw; hw=w;
+ negedge intr;
+ woclr;
+ };
+
+ intr_t irq0[8] = 0;
+ intr_t irq1[1] = 0;
+ } negedge_irqs @ 0x30;
+
+ reg {
+ field intr_t {
+ sw=rw; hw=w;
+ negedge intr;
+ woclr;
+ };
+
+ intr_t irq0[8] = 0;
+ intr_t irq1[1] = 0;
+ } negedge_we_irqs @ 0x34;
+
+ negedge_we_irqs.irq0->we = ctrl_we.irq0;
+ negedge_we_irqs.irq1->we = ctrl_we.irq1;
+
+ reg {
+ field intr_t {
+ sw=rw; hw=w;
+ negedge intr;
+ woclr;
+ };
+
+ intr_t irq0[8] = 0;
+ intr_t irq1[1] = 0;
+ } negedge_wel_irqs @ 0x38;
+
+ negedge_wel_irqs.irq0->wel = ctrl_wel.irq0;
+ negedge_wel_irqs.irq1->wel = ctrl_wel.irq1;
+
+ //---------------------------------
+
+ reg {
+ field intr_t {
+ sw=rw; hw=w;
+ bothedge intr;
+ woclr;
+ };
+
+ intr_t irq0[8] = 0;
+ intr_t irq1[1] = 0;
+ } bothedge_irqs @ 0x40;
+
+ reg {
+ field intr_t {
+ sw=rw; hw=w;
+ bothedge intr;
+ woclr;
+ };
+
+ intr_t irq0[8] = 0;
+ intr_t irq1[1] = 0;
+ } bothedge_we_irqs @ 0x44;
+
+ bothedge_we_irqs.irq0->we = ctrl_we.irq0;
+ bothedge_we_irqs.irq1->we = ctrl_we.irq1;
+
+ reg {
+ field intr_t {
+ sw=rw; hw=w;
+ bothedge intr;
+ woclr;
+ };
+
+ intr_t irq0[8] = 0;
+ intr_t irq1[1] = 0;
+ } bothedge_wel_irqs @ 0x48;
+
+ bothedge_wel_irqs.irq0->wel = ctrl_wel.irq0;
+ bothedge_wel_irqs.irq1->wel = ctrl_wel.irq1;
+
+ //---------------------------------
+
+ reg {
+ field intr_t {
+ sw=r; hw=w;
+ nonsticky intr;
+ };
+
+ intr_t level_active[1];
+ intr_t posedge_active[1];
+ intr_t negedge_active[1];
+ intr_t bothedge_active[1];
+ intr_t level_halt_active[1];
+ } top_irq @ 0x50;
+
+ top_irq.level_active->next = level_irqs_1->intr;
+ top_irq.posedge_active->next = posedge_irqs->intr;
+ top_irq.negedge_active->next = negedge_irqs->intr;
+ top_irq.bothedge_active->next = bothedge_irqs->intr;
+ top_irq.level_halt_active->next = level_irqs_2->halt;
+
+ //---------------------------------
+ reg {
+ field {
+ sw=rw; hw=w;
+ sticky;
+ } stickyfield[8] = 0;
+ } stickyreg @ 0x60;
+
+};
diff --git a/tests/test_interrupts/tb_template.sv b/tests/test_interrupts/tb_template.sv
new file mode 100644
index 0000000..87112ba
--- /dev/null
+++ b/tests/test_interrupts/tb_template.sv
@@ -0,0 +1,288 @@
+{% extends "lib/tb_base.sv" %}
+
+{% block seq %}
+ {% sv_line_anchor %}
+ ##1;
+ cb.rst <= '0;
+ ##1;
+
+ // Enable all interrupts
+ cpuif.write('h100, 'h1FF); // ctrl_enable
+ cpuif.write('h104, 'h000); // ctrl_mask
+ cpuif.write('h108, 'h1FF); // ctrl_haltenable
+ cpuif.write('h10C, 'h000); // ctrl_haltmask
+ cpuif.write('h110, 'h0); // ctrl_we
+ cpuif.write('h114, 'h3); // ctrl_wel
+
+ //--------------------------------------------------------------------------
+ // Test level_irqs_1
+ cpuif.assert_read('h0, 'h000);
+ assert(cb.hwif_out.level_irqs_1.intr == 1'b0);
+ cb.hwif_in.level_irqs_1.irq0.next <= 'h0F;
+ @cb;
+ cb.hwif_in.level_irqs_1.irq0.next <= 'h00;
+ cpuif.assert_read('h0, 'h00F);
+ assert(cb.hwif_out.level_irqs_1.intr == 1'b1);
+ cpuif.write('h0, 'h3);
+ cpuif.assert_read('h0, 'h00C);
+ assert(cb.hwif_out.level_irqs_1.intr == 1'b1);
+ cpuif.write('h0, 'hC);
+ cpuif.assert_read('h0, 'h000);
+ assert(cb.hwif_out.level_irqs_1.intr == 1'b0);
+
+ cb.hwif_in.level_irqs_1.irq1.next <= 'b1;
+ @cb;
+ cb.hwif_in.level_irqs_1.irq1.next <= 'b0;
+ cpuif.assert_read('h0, 'h100);
+ assert(cb.hwif_out.level_irqs_1.intr == 1'b1);
+ cpuif.write('h0, 'h100);
+ @cb;
+ assert(cb.hwif_out.level_irqs_1.intr == 1'b0);
+ cpuif.assert_read('h0, 'h0);
+
+ cb.hwif_in.level_irqs_1.irq1.next <= 'b1;
+ cpuif.assert_read('h0, 'h100);
+ assert(cb.hwif_out.level_irqs_1.intr == 1'b1);
+ cpuif.write('h0, 'h100);
+ cpuif.assert_read('h0, 'h100);
+ assert(cb.hwif_out.level_irqs_1.intr == 1'b1);
+ cb.hwif_in.level_irqs_1.irq1.next <= 'b0;
+ cpuif.assert_read('h0, 'h100);
+ assert(cb.hwif_out.level_irqs_1.intr == 1'b1);
+ cpuif.write('h0, 'h100);
+ cpuif.assert_read('h0, 'h000);
+ assert(cb.hwif_out.level_irqs_1.intr == 1'b0);
+
+ //--------------------------------------------------------------------------
+ // Test level_irqs_2
+ cpuif.assert_read('h4, 'h000);
+ assert(cb.hwif_out.level_irqs_2.intr == 1'b0);
+ assert(cb.hwif_out.level_irqs_2.halt == 1'b0);
+ cb.hwif_in.level_irqs_2.irq0.next <= 'h0F;
+ @cb;
+ cb.hwif_in.level_irqs_2.irq0.next <= 'h00;
+ cpuif.assert_read('h4, 'h00F);
+ assert(cb.hwif_out.level_irqs_2.intr == 1'b1);
+ assert(cb.hwif_out.level_irqs_2.halt == 1'b1);
+ cpuif.write('h100, 'h0); // ctrl_enable
+ @cb;
+ assert(cb.hwif_out.level_irqs_2.intr == 1'b0);
+ assert(cb.hwif_out.level_irqs_2.halt == 1'b1);
+ cpuif.write('h108, 'h0); // ctrl_haltenable
+ @cb;
+ assert(cb.hwif_out.level_irqs_2.intr == 1'b0);
+ assert(cb.hwif_out.level_irqs_2.halt == 1'b0);
+ cpuif.write('h100, 'h1FF); // ctrl_enable
+ cpuif.write('h108, 'h1FF); // ctrl_haltenable
+ @cb;
+ assert(cb.hwif_out.level_irqs_2.intr == 1'b1);
+ assert(cb.hwif_out.level_irqs_2.halt == 1'b1);
+ cpuif.write('h4, 'h1FF);
+ @cb;
+ assert(cb.hwif_out.level_irqs_2.intr == 1'b0);
+ assert(cb.hwif_out.level_irqs_2.halt == 1'b0);
+
+ //--------------------------------------------------------------------------
+ // Test level_irqs_3
+ cpuif.assert_read('h8, 'h000);
+ assert(cb.hwif_out.level_irqs_3.intr == 1'b0);
+ assert(cb.hwif_out.level_irqs_3.halt == 1'b0);
+ cb.hwif_in.level_irqs_3.irq0.next <= 'h0F;
+ @cb;
+ cb.hwif_in.level_irqs_3.irq0.next <= 'h00;
+ cpuif.assert_read('h8, 'h00F);
+ assert(cb.hwif_out.level_irqs_3.intr == 1'b1);
+ assert(cb.hwif_out.level_irqs_3.halt == 1'b1);
+ cpuif.write('h104, 'h0F); // ctrl_mask
+ @cb;
+ assert(cb.hwif_out.level_irqs_3.intr == 1'b0);
+ assert(cb.hwif_out.level_irqs_3.halt == 1'b1);
+ cpuif.write('h10C, 'hF); // ctrl_haltmask
+ @cb;
+ assert(cb.hwif_out.level_irqs_3.intr == 1'b0);
+ assert(cb.hwif_out.level_irqs_3.halt == 1'b0);
+ cpuif.write('h104, 'h0); // ctrl_mask
+ cpuif.write('h10C, 'h0); // ctrl_haltmask
+ @cb;
+ assert(cb.hwif_out.level_irqs_3.intr == 1'b1);
+ assert(cb.hwif_out.level_irqs_3.halt == 1'b1);
+
+ //--------------------------------------------------------------------------
+ // Test level_irqs with we
+ cpuif.assert_read('h10, 'h000);
+ assert(cb.hwif_out.level_irqs_we.intr == 1'b0);
+ cb.hwif_in.level_irqs_we.irq0.next <= 'h0F;
+ @cb;
+ cb.hwif_in.level_irqs_we.irq0.next <= 'h00;
+ assert(cb.hwif_out.level_irqs_we.intr == 1'b0);
+ cpuif.assert_read('h10, 'h000);
+ cpuif.write('h110, 'h1); // enable ctrl_we
+ @cb;
+ cpuif.assert_read('h110, 'h1);
+ assert(cb.hwif_out.level_irqs_we.intr == 1'b0);
+ cb.hwif_in.level_irqs_we.irq0.next <= 'h0F;
+ @cb;
+ cpuif.assert_read('h10, 'h00F);
+ assert(cb.hwif_out.level_irqs_we.intr == 1'b1);
+ cpuif.write('h110, 'h0); // disable ctrl_we
+ cpuif.write('h10, 'h1FF);
+ @cb;
+ assert(cb.hwif_out.level_irqs_we.intr == 1'b0);
+ cpuif.assert_read('h10, 'h000);
+ cb.hwif_in.level_irqs_we.irq0.next <= 'h00;
+
+ //--------------------------------------------------------------------------
+ // Test level_irqs with wel
+ cpuif.assert_read('h14, 'h000);
+ assert(cb.hwif_out.level_irqs_wel.intr == 1'b0);
+ cb.hwif_in.level_irqs_wel.irq0.next <= 'h0F;
+ @cb;
+ cb.hwif_in.level_irqs_wel.irq0.next <= 'h00;
+ cpuif.assert_read('h14, 'h000);
+ assert(cb.hwif_out.level_irqs_wel.intr == 1'b0);
+ cpuif.write('h114, 'h2); // enable ctrl_we
+ @cb;
+ cpuif.assert_read('h14, 'h000);
+ assert(cb.hwif_out.level_irqs_wel.intr == 1'b0);
+ cb.hwif_in.level_irqs_wel.irq0.next <= 'h0F;
+ @cb;
+ cpuif.assert_read('h14, 'h00F);
+ assert(cb.hwif_out.level_irqs_wel.intr == 1'b1);
+ cpuif.write('h114, 'h3); // disable ctrl_we
+ cpuif.write('h14, 'h1FF);
+ @cb;
+ assert(cb.hwif_out.level_irqs_wel.intr == 1'b0);
+ cpuif.assert_read('h14, 'h000);
+ cb.hwif_in.level_irqs_wel.irq0.next <= 'h00;
+
+ //--------------------------------------------------------------------------
+ // Test posedge_irqs
+ cpuif.assert_read('h20, 'h000);
+ assert(cb.hwif_out.posedge_irqs.intr == 1'b0);
+ cb.hwif_in.posedge_irqs.irq1.next <= 1'b1;
+ @cb;
+ cpuif.assert_read('h20, 'h100);
+ assert(cb.hwif_out.posedge_irqs.intr == 1'b1);
+ cpuif.write('h20, 'h100);
+ cpuif.assert_read('h20, 'h000);
+ assert(cb.hwif_out.posedge_irqs.intr == 1'b0);
+ cpuif.assert_read('h20, 'h000);
+
+ cb.hwif_in.posedge_irqs.irq1.next <= 1'b0;
+ cpuif.assert_read('h20, 'h000);
+ assert(cb.hwif_out.posedge_irqs.intr == 1'b0);
+
+ //--------------------------------------------------------------------------
+ // Test negedge_irqs
+ cpuif.assert_read('h30, 'h000);
+ assert(cb.hwif_out.negedge_irqs.intr == 1'b0);
+ cb.hwif_in.negedge_irqs.irq1.next <= 1'b1;
+ @cb;
+ cpuif.assert_read('h30, 'h000);
+ assert(cb.hwif_out.negedge_irqs.intr == 1'b0);
+ cb.hwif_in.negedge_irqs.irq1.next <= 1'b0;
+ cpuif.assert_read('h30, 'h100);
+ assert(cb.hwif_out.negedge_irqs.intr == 1'b1);
+ cpuif.write('h30, 'h100);
+ cpuif.assert_read('h30, 'h000);
+ assert(cb.hwif_out.negedge_irqs.intr == 1'b0);
+ cpuif.assert_read('h30, 'h000);
+
+ //--------------------------------------------------------------------------
+ // Test bothedge_irqs
+ cpuif.assert_read('h40, 'h000);
+ assert(cb.hwif_out.bothedge_irqs.intr == 1'b0);
+
+ cb.hwif_in.bothedge_irqs.irq1.next <= 1'b1;
+ cpuif.assert_read('h40, 'h100);
+ assert(cb.hwif_out.bothedge_irqs.intr == 1'b1);
+ cpuif.write('h40, 'h100);
+ cpuif.assert_read('h40, 'h000);
+ assert(cb.hwif_out.bothedge_irqs.intr == 1'b0);
+ cpuif.assert_read('h40, 'h000);
+
+ cb.hwif_in.bothedge_irqs.irq1.next <= 1'b0;
+ cpuif.assert_read('h40, 'h100);
+ assert(cb.hwif_out.bothedge_irqs.intr == 1'b1);
+ cpuif.write('h40, 'h100);
+ cpuif.assert_read('h40, 'h000);
+ assert(cb.hwif_out.bothedge_irqs.intr == 1'b0);
+ cpuif.assert_read('h40, 'h000);
+
+
+ //--------------------------------------------------------------------------
+ // Test top_irq
+ cpuif.assert_read('h50, 'h000);
+ assert(cb.hwif_out.top_irq.intr == 1'b0);
+
+ cb.hwif_in.level_irqs_1.irq0.next <= 'h01;
+ @cb;
+ cb.hwif_in.level_irqs_1.irq0.next <= 'h00;
+ cpuif.assert_read('h50, 'b0001);
+ assert(cb.hwif_out.top_irq.intr == 1'b1);
+ cpuif.write('h0, 'h01);
+ cpuif.assert_read('h50, 'b0000);
+ assert(cb.hwif_out.top_irq.intr == 1'b0);
+
+ cb.hwif_in.posedge_irqs.irq0.next <= 'h01;
+ @cb;
+ cb.hwif_in.posedge_irqs.irq0.next <= 'h00;
+ cpuif.assert_read('h50, 'b0010);
+ assert(cb.hwif_out.top_irq.intr == 1'b1);
+ cpuif.write('h20, 'h01);
+ cpuif.assert_read('h50, 'b0000);
+ assert(cb.hwif_out.top_irq.intr == 1'b0);
+
+ cb.hwif_in.negedge_irqs.irq0.next <= 'h01;
+ @cb;
+ cb.hwif_in.negedge_irqs.irq0.next <= 'h00;
+ @cb;
+ cpuif.assert_read('h50, 'b0100);
+ assert(cb.hwif_out.top_irq.intr == 1'b1);
+ cpuif.write('h30, 'h01);
+ cpuif.assert_read('h50, 'b0000);
+ assert(cb.hwif_out.top_irq.intr == 1'b0);
+
+ cb.hwif_in.bothedge_irqs.irq0.next <= 'h01;
+ @cb;
+ cb.hwif_in.bothedge_irqs.irq0.next <= 'h00;
+ cpuif.assert_read('h50, 'b1000);
+ assert(cb.hwif_out.top_irq.intr == 1'b1);
+ cpuif.write('h40, 'h01);
+ cpuif.assert_read('h50, 'b0000);
+ assert(cb.hwif_out.top_irq.intr == 1'b0);
+
+ cpuif.write('h108, 'h000); // ctrl_haltenable
+ cb.hwif_in.level_irqs_2.irq0.next <= 'h01;
+ @cb;
+ cb.hwif_in.level_irqs_2.irq0.next <= 'h00;
+ @cb;
+ cpuif.assert_read('h50, 'b00000);
+ assert(cb.hwif_out.top_irq.intr == 1'b0);
+
+ cpuif.write('h108, 'h001); // ctrl_haltenable
+ cpuif.assert_read('h50, 'b10000);
+ assert(cb.hwif_out.top_irq.intr == 1'b1);
+
+ cpuif.write('h4, 'h01);
+ cpuif.assert_read('h50, 'b00000);
+ assert(cb.hwif_out.top_irq.intr == 1'b0);
+
+ //--------------------------------------------------------------------------
+ // Test multi-bit sticky reg
+ cpuif.assert_read('h60, 'h00);
+ cb.hwif_in.stickyreg.stickyfield.next <= 'h12;
+ @cb;
+ cb.hwif_in.stickyreg.stickyfield.next <= 'h34;
+ @cb;
+ cb.hwif_in.stickyreg.stickyfield.next <= 'h56;
+ @cb;
+ cpuif.assert_read('h60, 'h12);
+ cpuif.write('h60, 'h00);
+ @cb;
+ cb.hwif_in.stickyreg.stickyfield.next <= 'h78;
+ @cb;
+ cpuif.assert_read('h60, 'h56);
+
+
+{% endblock %}
diff --git a/tests/test_interrupts/testcase.py b/tests/test_interrupts/testcase.py
new file mode 100644
index 0000000..835b5ef
--- /dev/null
+++ b/tests/test_interrupts/testcase.py
@@ -0,0 +1,5 @@
+from ..lib.sim_testcase import SimTestCase
+
+class Test(SimTestCase):
+ def test_dut(self):
+ self.run_test()
diff --git a/tests/test_map_size/__init__.py b/tests/test_map_size/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_map_size/regblock.rdl b/tests/test_map_size/regblock.rdl
new file mode 100644
index 0000000..046949a
--- /dev/null
+++ b/tests/test_map_size/regblock.rdl
@@ -0,0 +1,5 @@
+addrmap top {
+ reg {
+ field {} f1[32] = 0;
+ } my_reg;
+};
diff --git a/tests/test_map_size/tb_template.sv b/tests/test_map_size/tb_template.sv
new file mode 100644
index 0000000..d86780a
--- /dev/null
+++ b/tests/test_map_size/tb_template.sv
@@ -0,0 +1,12 @@
+{% extends "lib/tb_base.sv" %}
+
+{% block seq %}
+ {% sv_line_anchor %}
+ ##1;
+ cb.rst <= '0;
+ ##1;
+
+ // check block size
+ assert(regblock_pkg::REGBLOCK_SIZE == {{exporter.ds.top_node.size}});
+
+{% endblock %}
diff --git a/tests/test_map_size/testcase.py b/tests/test_map_size/testcase.py
new file mode 100644
index 0000000..835b5ef
--- /dev/null
+++ b/tests/test_map_size/testcase.py
@@ -0,0 +1,5 @@
+from ..lib.sim_testcase import SimTestCase
+
+class Test(SimTestCase):
+ def test_dut(self):
+ self.run_test()
diff --git a/tests/test_onread_onwrite/__init__.py b/tests/test_onread_onwrite/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_onread_onwrite/regblock.rdl b/tests/test_onread_onwrite/regblock.rdl
new file mode 100644
index 0000000..de196e5
--- /dev/null
+++ b/tests/test_onread_onwrite/regblock.rdl
@@ -0,0 +1,61 @@
+addrmap top {
+
+ reg {
+ field {
+ sw=rw; hw=na;
+ onread = rclr;
+ } f1[7:0] = 0xF0;
+
+ field {
+ sw=rw; hw=na;
+ onread = rset;
+ } f2[15:8] = 0x0F;
+ } r1;
+
+
+ reg {
+ field {
+ sw=rw; hw=na;
+ onwrite = woset;
+ } f1[3:0] = 0x0;
+
+ field {
+ sw=rw; hw=na;
+ onwrite = woclr;
+ } f2[7:4] = 0xF;
+
+ field {
+ sw=rw; hw=na;
+ onwrite = wot;
+ } f3[11:8] = 0x0;
+ } r2;
+
+ reg {
+ field {
+ sw=rw; hw=na;
+ onwrite = wzs;
+ } f1[3:0] = 0x0;
+
+ field {
+ sw=rw; hw=na;
+ onwrite = wzc;
+ } f2[7:4] = 0xF;
+
+ field {
+ sw=rw; hw=na;
+ onwrite = wzt;
+ } f3[11:8] = 0x0;
+ } r3;
+
+ reg {
+ field {
+ sw=rw; hw=na;
+ onwrite = wclr;
+ } f1[7:0] = 0xF0;
+
+ field {
+ sw=rw; hw=na;
+ onwrite = wset;
+ } f2[15:8] = 0x0F;
+ } r4;
+};
diff --git a/tests/test_onread_onwrite/tb_template.sv b/tests/test_onread_onwrite/tb_template.sv
new file mode 100644
index 0000000..f3308e7
--- /dev/null
+++ b/tests/test_onread_onwrite/tb_template.sv
@@ -0,0 +1,30 @@
+{% extends "lib/tb_base.sv" %}
+
+{% block seq %}
+ {% sv_line_anchor %}
+ ##1;
+ cb.rst <= '0;
+ ##1;
+
+ cpuif.assert_read('h0, 'h0F_F0);
+ cpuif.assert_read('h0, 'hFF_00);
+ cpuif.write ('h0, 'h00_FF);
+ cpuif.assert_read('h0, 'h00_FF);
+ cpuif.assert_read('h0, 'hFF_00);
+
+ cpuif.assert_read('h4, 'h0_F_0);
+ cpuif.write ('h4, 'h1_1_1);
+ cpuif.assert_read('h4, 'h1_E_1);
+ cpuif.write ('h4, 'h1_2_2);
+ cpuif.assert_read('h4, 'h0_C_3);
+
+ cpuif.assert_read('h8, 'h0_F_0);
+ cpuif.write ('h8, 'hE_E_E);
+ cpuif.assert_read('h8, 'h1_E_1);
+ cpuif.write ('h8, 'hE_D_D);
+ cpuif.assert_read('h8, 'h0_C_3);
+
+ cpuif.assert_read('hC, 'h0F_F0);
+ cpuif.write ('hC, 'h12_34);
+ cpuif.assert_read('hC, 'hFF_00);
+{% endblock %}
diff --git a/tests/test_onread_onwrite/testcase.py b/tests/test_onread_onwrite/testcase.py
new file mode 100644
index 0000000..835b5ef
--- /dev/null
+++ b/tests/test_onread_onwrite/testcase.py
@@ -0,0 +1,5 @@
+from ..lib.sim_testcase import SimTestCase
+
+class Test(SimTestCase):
+ def test_dut(self):
+ self.run_test()
diff --git a/tests/test_parity/__init__.py b/tests/test_parity/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_parity/regblock.rdl b/tests/test_parity/regblock.rdl
new file mode 100644
index 0000000..7056baf
--- /dev/null
+++ b/tests/test_parity/regblock.rdl
@@ -0,0 +1,14 @@
+addrmap top {
+ default paritycheck;
+ default sw=rw;
+ default hw=na;
+
+ reg my_reg {
+ field {} f1[16] = 0;
+ field {} f2[8] = 0;
+ field {} f3 = 0;
+ };
+
+ my_reg r1 @ 0x000;
+ my_reg r2[8] @ 0x1000;
+};
diff --git a/tests/test_parity/tb_template.sv b/tests/test_parity/tb_template.sv
new file mode 100644
index 0000000..392a23a
--- /dev/null
+++ b/tests/test_parity/tb_template.sv
@@ -0,0 +1,37 @@
+{% extends "lib/tb_base.sv" %}
+
+{% block seq %}
+ {% sv_line_anchor %}
+ ##1;
+ cb.rst <= '0;
+ ##1;
+
+ fork
+ begin
+ repeat(50) begin
+ automatic int i = $urandom_range(7,0);
+ cpuif.write('h0, $urandom());
+ cpuif.write('h1000 + i*4, $urandom());
+ end
+ end
+ begin
+ forever begin
+ assert(cb.parity_error != 1'b1);
+ @cb;
+ end
+ end
+ join_any
+ disable fork;
+
+ cpuif.write('h0, 'd0);
+ assign dut.field_storage.r1.f1.value = 16'd1;
+ deassign dut.field_storage.r1.f1.value;
+ @cb;
+ @cb;
+ assert(cb.parity_error == 1'b1);
+ cpuif.write('h0, 'd0);
+ @cb;
+ @cb;
+ assert(cb.parity_error == 1'b0);
+
+{% endblock %}
diff --git a/tests/test_parity/testcase.py b/tests/test_parity/testcase.py
new file mode 100644
index 0000000..835b5ef
--- /dev/null
+++ b/tests/test_parity/testcase.py
@@ -0,0 +1,5 @@
+from ..lib.sim_testcase import SimTestCase
+
+class Test(SimTestCase):
+ def test_dut(self):
+ self.run_test()
diff --git a/tests/test_pipelined_cpuif/__init__.py b/tests/test_pipelined_cpuif/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_pipelined_cpuif/regblock.rdl b/tests/test_pipelined_cpuif/regblock.rdl
new file mode 100644
index 0000000..a208e43
--- /dev/null
+++ b/tests/test_pipelined_cpuif/regblock.rdl
@@ -0,0 +1,8 @@
+addrmap regblock {
+ default sw=rw;
+ default hw=r;
+
+ reg {
+ field {} x[31:0] = 0;
+ } x[64] @ 0 += 4;
+};
diff --git a/tests/test_pipelined_cpuif/tb_template.sv b/tests/test_pipelined_cpuif/tb_template.sv
new file mode 100644
index 0000000..cfee40a
--- /dev/null
+++ b/tests/test_pipelined_cpuif/tb_template.sv
@@ -0,0 +1,50 @@
+{% extends "lib/tb_base.sv" %}
+
+{% block seq %}
+ {% sv_line_anchor %}
+ ##1;
+ cb.rst <= '0;
+ ##1;
+
+ // Write all regs in parallel burst
+ for(int i=0; i<64; i++) begin
+ fork
+ automatic int i_fk = i;
+ begin
+ cpuif.write(i_fk*4, i_fk + 32'h12340000);
+ end
+ join_none
+ end
+ wait fork;
+
+ // Verify HW value
+ @cb;
+ for(int i=0; i<64; i++) begin
+ assert(cb.hwif_out.x[i].x.value == i + 32'h12340000)
+ else $error("hwif_out.x[i] == 0x%0x. Expected 0x%0x", cb.hwif_out.x[i].x.value, i + 32'h12340000);
+ end
+
+ // Read all regs in parallel burst
+ for(int i=0; i<64; i++) begin
+ fork
+ automatic int i_fk = i;
+ begin
+ cpuif.assert_read(i_fk*4, i_fk + 32'h12340000);
+ end
+ join_none
+ end
+ wait fork;
+
+ // Mix read/writes
+ for(int i=0; i<8; i++) begin
+ fork
+ automatic int i_fk = i;
+ begin
+ cpuif.write(i_fk*4, i_fk + 32'h56780000);
+ cpuif.assert_read(i_fk*4, i_fk + 32'h56780000);
+ end
+ join_none
+ end
+ wait fork;
+
+{% endblock %}
diff --git a/tests/test_pipelined_cpuif/testcase.py b/tests/test_pipelined_cpuif/testcase.py
new file mode 100644
index 0000000..27fe9ae
--- /dev/null
+++ b/tests/test_pipelined_cpuif/testcase.py
@@ -0,0 +1,14 @@
+from parameterized import parameterized_class
+
+from ..lib.sim_testcase import SimTestCase
+from ..lib.test_params import get_permutations
+from ..lib.cpuifs import ALL_CPUIF
+
+@parameterized_class(get_permutations({
+ "cpuif": ALL_CPUIF,
+ "retime_read_fanin": [True, False],
+ "retime_read_response": [True, False],
+}))
+class Test(SimTestCase):
+ def test_dut(self):
+ self.run_test()
diff --git a/tests/test_pkg_params/__init__.py b/tests/test_pkg_params/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_pkg_params/regblock.rdl b/tests/test_pkg_params/regblock.rdl
new file mode 100644
index 0000000..09b7418
--- /dev/null
+++ b/tests/test_pkg_params/regblock.rdl
@@ -0,0 +1,11 @@
+addrmap top #(
+ longint N_REGS = 1,
+ longint REGWIDTH = 32,
+ string NAME = "abcd"
+) {
+ reg reg_t {
+ regwidth = REGWIDTH;
+ field {sw=rw; hw=r;} f[REGWIDTH] = 1;
+ };
+ reg_t regs[N_REGS];
+};
diff --git a/tests/test_pkg_params/tb_template.sv b/tests/test_pkg_params/tb_template.sv
new file mode 100644
index 0000000..b33b3d2
--- /dev/null
+++ b/tests/test_pkg_params/tb_template.sv
@@ -0,0 +1,8 @@
+{% extends "lib/tb_base.sv" %}
+
+{% block seq %}
+ {% sv_line_anchor %}
+ assert(regblock_pkg::N_REGS == {{testcase.n_regs}});
+ assert(regblock_pkg::REGWIDTH == {{testcase.regwidth}});
+ assert(regblock_pkg::NAME == "{{testcase.name}}");
+{% endblock %}
diff --git a/tests/test_pkg_params/testcase.py b/tests/test_pkg_params/testcase.py
new file mode 100644
index 0000000..790b21b
--- /dev/null
+++ b/tests/test_pkg_params/testcase.py
@@ -0,0 +1,27 @@
+from parameterized import parameterized_class
+
+from ..lib.sim_testcase import SimTestCase
+from ..lib.test_params import get_permutations
+
+PARAMS = get_permutations({
+ "n_regs" : [1, 2],
+ "regwidth" : [8, 16],
+ "name" : ["hello", "world"],
+})
+@parameterized_class(PARAMS)
+class TestRetimedFanin(SimTestCase):
+ n_regs = 20
+ regwidth = 32
+ name = "xyz"
+
+ @classmethod
+ def setUpClass(cls):
+ cls.rdl_elab_params = {
+ "N_REGS": cls.n_regs,
+ "REGWIDTH": cls.regwidth,
+ "NAME": f'"{cls.name}"',
+ }
+ super().setUpClass()
+
+ def test_dut(self):
+ self.run_test()
diff --git a/tests/test_precedence/__init__.py b/tests/test_precedence/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_precedence/regblock.rdl b/tests/test_precedence/regblock.rdl
new file mode 100644
index 0000000..8affd3e
--- /dev/null
+++ b/tests/test_precedence/regblock.rdl
@@ -0,0 +1,26 @@
+addrmap top {
+ reg {
+ field {
+ sw=rw;
+ hw=w; we;
+ precedence=sw;
+ } f_sw = 0;
+ field {
+ sw=rw;
+ hw=w; we;
+ precedence=hw;
+ } f_hw = 0;
+ } r1 @ 0x0;
+
+ reg {
+ default counter;
+ default sw=r;
+ default hw=na;
+
+ field {} f_sw_count[3:0] = 0;
+ field {} f_hw_count[7:4] = 0;
+ } r1_events @ 0x4;
+
+ r1_events.f_sw_count->incr = r1.f_sw;
+ r1_events.f_hw_count->incr = r1.f_hw;
+};
diff --git a/tests/test_precedence/tb_template.sv b/tests/test_precedence/tb_template.sv
new file mode 100644
index 0000000..b870bef
--- /dev/null
+++ b/tests/test_precedence/tb_template.sv
@@ -0,0 +1,26 @@
+{% extends "lib/tb_base.sv" %}
+
+{% block seq %}
+ {% sv_line_anchor %}
+ ##1;
+ cb.rst <= '0;
+ ##1;
+
+ // Always write both fields from hardware
+ cb.hwif_in.r1.f_sw.next <= '0;
+ cb.hwif_in.r1.f_sw.we <= '1;
+ cb.hwif_in.r1.f_hw.next <= '0;
+ cb.hwif_in.r1.f_hw.we <= '1;
+ @cb;
+ @cb;
+
+ cpuif.assert_read('h0, 'b00);
+ cpuif.assert_read('h4, 'h00);
+
+ cpuif.write('h0, 'b11);
+ cpuif.write('h0, 'b11);
+ cpuif.write('h0, 'b11);
+ cpuif.assert_read('h0, 'h00);
+ cpuif.assert_read('h4, 'h03);
+
+{% endblock %}
diff --git a/tests/test_precedence/testcase.py b/tests/test_precedence/testcase.py
new file mode 100644
index 0000000..835b5ef
--- /dev/null
+++ b/tests/test_precedence/testcase.py
@@ -0,0 +1,5 @@
+from ..lib.sim_testcase import SimTestCase
+
+class Test(SimTestCase):
+ def test_dut(self):
+ self.run_test()
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()
diff --git a/tests/test_read_fanin/__init__.py b/tests/test_read_fanin/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_read_fanin/regblock.rdl b/tests/test_read_fanin/regblock.rdl
new file mode 100644
index 0000000..5d6bffd
--- /dev/null
+++ b/tests/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/tests/test_read_fanin/tb_template.sv b/tests/test_read_fanin/tb_template.sv
new file mode 100644
index 0000000..1154c3f
--- /dev/null
+++ b/tests/test_read_fanin/tb_template.sv
@@ -0,0 +1,34 @@
+{% extends "lib/tb_base.sv" %}
+
+{%- block declarations %}
+ {% sv_line_anchor %}
+ localparam REGWIDTH = {{testcase.regwidth}};
+ localparam STRIDE = REGWIDTH/8;
+ localparam N_REGS = {{testcase.n_regs}};
+{%- endblock %}
+
+
+{% block seq %}
+ {% sv_line_anchor %}
+ 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; iresetsignal = f2_reset;
+ r2.f2->resetsignal = f2_reset;
+ r3.f2->resetsignal = f2_reset;
+ r4.f2->resetsignal = f2_reset;
+ r5.f2->resetsignal = f2_reset;
+};
diff --git a/tests/test_reset_signals/tb_template.sv b/tests/test_reset_signals/tb_template.sv
new file mode 100644
index 0000000..5429b9e
--- /dev/null
+++ b/tests/test_reset_signals/tb_template.sv
@@ -0,0 +1,128 @@
+{% extends "lib/tb_base.sv" %}
+
+{%- block declarations %}
+ logic root_cpuif_reset;
+ logic [15:0] r5f2_resetvalue;
+{%- endblock %}
+
+{%- block clocking_dirs %}
+ output root_cpuif_reset;
+ output r5f2_resetvalue;
+{%- endblock %}
+
+{% block seq %}
+ {% sv_line_anchor %}
+ cb.root_cpuif_reset <= '1;
+ cb.hwif_in.r2.my_reset <= '1;
+ cb.hwif_in.r3.my_areset <= '1;
+ cb.hwif_in.r4.my_reset_n <= '0;
+ cb.hwif_in.r5.my_areset_n <= '0;
+ cb.hwif_in.f2_reset <= '1;
+ cb.r5f2_resetvalue <= 'hABCD;
+ ##2;
+ cb.rst <= '0;
+ cb.root_cpuif_reset <= '0;
+ cb.hwif_in.r2.my_reset <= '0;
+ cb.hwif_in.r3.my_areset <= '0;
+ cb.hwif_in.r4.my_reset_n <= '1;
+ cb.hwif_in.r5.my_areset_n <= '1;
+ cb.hwif_in.f2_reset <= '0;
+ ##1;
+
+
+ cpuif.assert_read('h00, 'h5678_1234);
+ cpuif.assert_read('h04, 'h5678_1234);
+ cpuif.assert_read('h08, 'h5678_1234);
+ cpuif.assert_read('h0c, 'h5678_1234);
+ cpuif.assert_read('h10, 'hABCD_1234);
+
+ for(int i=0; i<5; i++) cpuif.write(i*4, 0);
+
+ cpuif.assert_read('h00, 'h0000_0000);
+ cpuif.assert_read('h04, 'h0000_0000);
+ cpuif.assert_read('h08, 'h0000_0000);
+ cpuif.assert_read('h0c, 'h0000_0000);
+ cpuif.assert_read('h10, 'h0000_0000);
+
+ cb.rst <= '1;
+ @cb;
+ cb.rst <= '0;
+ @cb;
+
+ cpuif.assert_read('h00, 'h0000_1234);
+ cpuif.assert_read('h04, 'h0000_0000);
+ cpuif.assert_read('h08, 'h0000_0000);
+ cpuif.assert_read('h0c, 'h0000_0000);
+ cpuif.assert_read('h10, 'h0000_0000);
+
+ for(int i=0; i<5; i++) cpuif.write(i*4, 0);
+
+ cb.hwif_in.r2.my_reset <= '1;
+ @cb;
+ cb.hwif_in.r2.my_reset <= '0;
+ @cb;
+
+ cpuif.assert_read('h00, 'h0000_0000);
+ cpuif.assert_read('h04, 'h0000_1234);
+ cpuif.assert_read('h08, 'h0000_0000);
+ cpuif.assert_read('h0c, 'h0000_0000);
+ cpuif.assert_read('h10, 'h0000_0000);
+
+ for(int i=0; i<5; i++) cpuif.write(i*4, 0);
+
+ ##1;
+ #2ns;
+ hwif_in.r3.my_areset = '1;
+ #1ns;
+ hwif_in.r3.my_areset = '0;
+ ##1;
+
+ cpuif.assert_read('h00, 'h0000_0000);
+ cpuif.assert_read('h04, 'h0000_0000);
+ cpuif.assert_read('h08, 'h0000_1234);
+ cpuif.assert_read('h0c, 'h0000_0000);
+ cpuif.assert_read('h10, 'h0000_0000);
+
+ for(int i=0; i<5; i++) cpuif.write(i*4, 0);
+
+ cb.hwif_in.r4.my_reset_n <= '0;
+ @cb;
+ cb.hwif_in.r4.my_reset_n <= '1;
+ @cb;
+
+ cpuif.assert_read('h00, 'h0000_0000);
+ cpuif.assert_read('h04, 'h0000_0000);
+ cpuif.assert_read('h08, 'h0000_0000);
+ cpuif.assert_read('h0c, 'h0000_1234);
+ cpuif.assert_read('h10, 'h0000_0000);
+
+ for(int i=0; i<5; i++) cpuif.write(i*4, 0);
+
+ ##1;
+ #2ns;
+ hwif_in.r5.my_areset_n = '0;
+ #1ns;
+ hwif_in.r5.my_areset_n = '1;
+ ##1;
+
+ cpuif.assert_read('h00, 'h0000_0000);
+ cpuif.assert_read('h04, 'h0000_0000);
+ cpuif.assert_read('h08, 'h0000_0000);
+ cpuif.assert_read('h0c, 'h0000_0000);
+ cpuif.assert_read('h10, 'h0000_1234);
+
+ for(int i=0; i<5; i++) cpuif.write(i*4, 0);
+
+ @cb;
+ cb.hwif_in.f2_reset <= '1;
+ cb.r5f2_resetvalue <= 'h3210;
+ @cb;
+ cb.hwif_in.f2_reset <= '0;
+
+ cpuif.assert_read('h00, 'h5678_0000);
+ cpuif.assert_read('h04, 'h5678_0000);
+ cpuif.assert_read('h08, 'h5678_0000);
+ cpuif.assert_read('h0c, 'h5678_0000);
+ cpuif.assert_read('h10, 'h3210_0000);
+
+{% endblock %}
diff --git a/tests/test_reset_signals/testcase.py b/tests/test_reset_signals/testcase.py
new file mode 100644
index 0000000..835b5ef
--- /dev/null
+++ b/tests/test_reset_signals/testcase.py
@@ -0,0 +1,5 @@
+from ..lib.sim_testcase import SimTestCase
+
+class Test(SimTestCase):
+ def test_dut(self):
+ self.run_test()
diff --git a/tests/test_singlepulse/__init__.py b/tests/test_singlepulse/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_singlepulse/regblock.rdl b/tests/test_singlepulse/regblock.rdl
new file mode 100644
index 0000000..82ff060
--- /dev/null
+++ b/tests/test_singlepulse/regblock.rdl
@@ -0,0 +1,8 @@
+addrmap top {
+ reg {
+ field {
+ sw=rw; hw=r;
+ singlepulse;
+ } f[0:0] = 0;
+ } r1;
+};
diff --git a/tests/test_singlepulse/tb_template.sv b/tests/test_singlepulse/tb_template.sv
new file mode 100644
index 0000000..27ff6dd
--- /dev/null
+++ b/tests/test_singlepulse/tb_template.sv
@@ -0,0 +1,56 @@
+{% extends "lib/tb_base.sv" %}
+
+{% block seq %}
+ {% sv_line_anchor %}
+ int event_count;
+
+ ##1;
+ cb.rst <= '0;
+ ##1;
+
+ // No pulse if writing zero
+ event_count = 0;
+ fork
+ begin
+ ##0;
+ forever begin
+ @cb;
+ if(cb.hwif_out.r1.f.value) begin
+ event_count++;
+ end
+ end
+ end
+
+ begin
+ cpuif.write('h0, 'h0);
+ repeat(5) @cb;
+ end
+ join_any
+ disable fork;
+ assert(event_count == 0) else $error("Observed excess singlepulse events: %0d", event_count);
+
+ // single pulse
+ event_count = 0;
+ fork
+ begin
+ ##0;
+ forever begin
+ @cb;
+ if(cb.hwif_out.r1.f.value) begin
+ event_count++;
+ end
+ end
+ end
+
+ begin
+ cpuif.write('h0, 'h1);
+ repeat(5) @cb;
+ end
+ join_any
+ disable fork;
+ assert(event_count == 1) else $error("Observed incorrect number of singlepulse events: %0d", event_count);
+
+ // auto-clears
+ cpuif.assert_read('h0, 'h0);
+
+{% endblock %}
diff --git a/tests/test_singlepulse/testcase.py b/tests/test_singlepulse/testcase.py
new file mode 100644
index 0000000..835b5ef
--- /dev/null
+++ b/tests/test_singlepulse/testcase.py
@@ -0,0 +1,5 @@
+from ..lib.sim_testcase import SimTestCase
+
+class Test(SimTestCase):
+ def test_dut(self):
+ self.run_test()
diff --git a/tests/test_structural_sw_rw/__init__.py b/tests/test_structural_sw_rw/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_structural_sw_rw/regblock.rdl b/tests/test_structural_sw_rw/regblock.rdl
new file mode 100644
index 0000000..30488d1
--- /dev/null
+++ b/tests/test_structural_sw_rw/regblock.rdl
@@ -0,0 +1,44 @@
+addrmap regblock {
+ default sw=rw;
+ default hw=r;
+
+ reg my_reg {
+ field {} a[8] = 0x23;
+ field {} b = 0;
+ field {} c[31:31] = 1;
+ };
+
+ my_reg r0 @0x000;
+ r0.a->reset = 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;
+
+ reg {
+ field {} f1[19:12] = 0;
+ field {} f2[30:20] = 0;
+ } rw_reg @ 0x3000;
+
+ reg {
+ field {} f1[12:19] = 0;
+ field {} f2[20:30] = 0;
+ } rw_reg_lsb0 @ 0x3004;
+};
diff --git a/tests/test_structural_sw_rw/tb_template.sv b/tests/test_structural_sw_rw/tb_template.sv
new file mode 100644
index 0000000..591bfe4
--- /dev/null
+++ b/tests/test_structural_sw_rw/tb_template.sv
@@ -0,0 +1,80 @@
+{% extends "lib/tb_base.sv" %}
+
+{% block seq %}
+ {% sv_line_anchor %}
+ ##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(cb.hwif_out.r0.a.value == 'h42);
+ assert(cb.hwif_out.r0.b.value == 'h0);
+ assert(cb.hwif_out.r0.c.value == 'h1);
+ foreach(cb.hwif_out.r1[x, y, z]) begin
+ assert(cb.hwif_out.r1[x][y][z].a.value == 'h23);
+ assert(cb.hwif_out.r1[x][y][z].b.value == 'h0);
+ assert(cb.hwif_out.r1[x][y][z].c.value == 'h1);
+ end
+ assert(cb.hwif_out.r2.a.value == 'h11);
+ assert(cb.hwif_out.r2.b.value == 'h0);
+ assert(cb.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(cb.hwif_out.r0.a.value == 'h02);
+ assert(cb.hwif_out.r0.b.value == 'h0);
+ assert(cb.hwif_out.r0.c.value == 'h1);
+ foreach(cb.hwif_out.r1[x, y, z]) begin
+ assert(cb.hwif_out.r1[x][y][z].a.value == x*12+y*4+z+10);
+ assert(cb.hwif_out.r1[x][y][z].b.value == 'h1);
+ assert(cb.hwif_out.r1[x][y][z].c.value == 'h0);
+ end
+ assert(cb.hwif_out.r2.a.value == 'h0);
+ assert(cb.hwif_out.r2.b.value == 'h0);
+ assert(cb.hwif_out.r2.c.value == 'h0);
+
+ // rw_reg
+ cpuif.assert_read('h3000, 0);
+ cpuif.write('h3000, 'h4DEAB000);
+ @cb;
+ assert(cb.hwif_out.rw_reg.f1.value == 8'hAB);
+ assert(cb.hwif_out.rw_reg.f2.value == 11'h4DE);
+ cpuif.assert_read('h3000, 'h4DEAB000);
+
+ // rw_reg_lsb0
+ cpuif.assert_read('h3004, 0);
+ cpuif.write('h3004, 'h4DEAB000);
+ @cb;
+ assert(`bitswap(cb.hwif_out.rw_reg_lsb0.f1.value) == 8'hAB);
+ assert(`bitswap(cb.hwif_out.rw_reg_lsb0.f2.value) == 11'h4DE);
+ cpuif.assert_read('h3004, 'h4DEAB000);
+{% endblock %}
diff --git a/tests/test_structural_sw_rw/testcase.py b/tests/test_structural_sw_rw/testcase.py
new file mode 100644
index 0000000..afb1d82
--- /dev/null
+++ b/tests/test_structural_sw_rw/testcase.py
@@ -0,0 +1,51 @@
+import os
+
+from parameterized import parameterized_class
+
+from ..lib.sim_testcase import SimTestCase
+from ..lib.synth_testcase import SynthTestCase
+from ..lib.test_params import get_permutations
+from ..lib.cpuifs import ALL_CPUIF
+
+
+
+
+@parameterized_class(get_permutations({
+ "cpuif": ALL_CPUIF,
+ "retime_read_fanin": [True, False],
+ "retime_read_response": [True, False],
+}))
+class TestCPUIFS(SimTestCase):
+ def test_dut(self):
+ self.run_test()
+
+
+
+@parameterized_class(get_permutations({
+ "reuse_hwif_typedefs": [True, False],
+}))
+class TestTypedefs(SimTestCase):
+ def test_dut(self):
+ self.run_test()
+
+
+
+@parameterized_class(get_permutations({
+ "default_reset_activelow": [True, False],
+ "default_reset_async": [True, False],
+}))
+class TestDefaultResets(SimTestCase):
+ def test_dut(self):
+ self.run_test()
+
+
+
+@parameterized_class(get_permutations({
+ "cpuif": ALL_CPUIF,
+ "retime_read_fanin": [True, False],
+ "retime_read_response": [True, False],
+ "reuse_hwif_typedefs": [True, False],
+}))
+class TestSynth(SynthTestCase):
+ def test_dut(self):
+ self.run_synth()
diff --git a/tests/test_swacc_swmod/__init__.py b/tests/test_swacc_swmod/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_swacc_swmod/regblock.rdl b/tests/test_swacc_swmod/regblock.rdl
new file mode 100644
index 0000000..877098b
--- /dev/null
+++ b/tests/test_swacc_swmod/regblock.rdl
@@ -0,0 +1,43 @@
+addrmap top {
+ default regwidth = 8;
+
+ reg {
+ field {
+ sw=r; hw=w;
+ swacc;
+ swmod;
+ } f[8];
+ } r1;
+
+ reg {
+ field {
+ sw=rw; hw=r;
+ swmod;
+ } f[8] = 20;
+ } r2;
+
+ reg {
+ field {
+ sw=rw; hw=r;
+ swmod;
+ rclr;
+ } f[8] = 30;
+ } r3;
+
+ reg {
+ field {
+ sw=rw; hw=r;
+ swacc;
+ swmod;
+ } f[8] = 0x12;
+ } r4;
+
+ reg {
+ field {
+ sw=r; hw=rw;
+ we;
+ swmod;
+ rclr;
+ } f[8] = 30;
+ } r5;
+};
diff --git a/tests/test_swacc_swmod/tb_template.sv b/tests/test_swacc_swmod/tb_template.sv
new file mode 100644
index 0000000..5a9fef9
--- /dev/null
+++ b/tests/test_swacc_swmod/tb_template.sv
@@ -0,0 +1,198 @@
+{% extends "lib/tb_base.sv" %}
+
+{% block seq %}
+ {% sv_line_anchor %}
+ logic [7:0] counter;
+ logic [7:0] rd_data;
+ logic [7:0] latched_data;
+ int event_count;
+ bit fired;
+ latched_data = 'x;
+
+ ##1;
+ cb.rst <= '0;
+ ##1;
+
+ // Verify that hwif gets sampled at the same cycle as swacc strobe
+ counter = 'h10;
+ cb.hwif_in.r1.f.next <= counter;
+ @cb;
+ event_count = 0;
+ fork
+ begin
+ ##0;
+ forever begin
+ counter++;
+ cb.hwif_in.r1.f.next <= counter;
+ @cb;
+ if(cb.hwif_out.r1.f.swacc) begin
+ latched_data = counter;
+ event_count++;
+ end
+ end
+ end
+
+ begin
+ cpuif.read('h0, rd_data);
+ repeat(4) @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 writing a read-only register with no side effects never asserts swmod
+ cb.hwif_in.r1.f.next <= 'h99;
+ @cb;
+ fork
+ begin
+ ##0;
+ forever begin
+ assert(cb.hwif_out.r1.f.swmod == 0);
+ @cb;
+ end
+ end
+ begin
+ cpuif.write('h0, 'h0);
+ cpuif.assert_read('h0, 'h99);
+ repeat(4) @cb;
+ end
+ join_any
+ disable fork;
+
+
+ // Verify that hwif changes 1 cycle after swmod
+ fired = 0;
+ fork
+ begin
+ ##0;
+ forever begin
+ assert(cb.hwif_out.r2.f.value == 20);
+ if(cb.hwif_out.r2.f.swmod) break;
+ @cb;
+ end
+ fired = 1;
+ @cb;
+ forever begin
+ assert(cb.hwif_out.r2.f.value == 21);
+ assert(cb.hwif_out.r2.f.swmod == 0);
+ @cb;
+ end
+ end
+
+ begin
+ cpuif.write('h1, 21);
+ repeat(4) @cb;
+ end
+ join_any
+ disable fork;
+ assert(fired);
+
+ // Verify that swmod does NOT trigger if strobes not set
+ fired = 0;
+ fork
+ begin
+ ##0;
+ forever begin
+ assert(cb.hwif_out.r2.f.value == 21);
+ if(cb.hwif_out.r2.f.swmod) break;
+ @cb;
+ end
+ fired = 1;
+ end
+
+ begin
+ cpuif.write('h1, 22, 0);
+ repeat(4) @cb;
+ end
+ join_any
+ disable fork;
+ assert(!fired);
+
+ // Verify that hwif changes 1 cycle after swmod
+ fired = 0;
+ fork
+ begin
+ ##0;
+ forever begin
+ assert(cb.hwif_out.r3.f.value == 30);
+ if(cb.hwif_out.r3.f.swmod) break;
+ @cb;
+ end
+ fired = 1;
+ @cb;
+ forever begin
+ assert(cb.hwif_out.r3.f.value == 0);
+ assert(cb.hwif_out.r3.f.swmod == 0);
+ @cb;
+ end
+ end
+
+ begin
+ cpuif.assert_read('h2, 30);
+ repeat(4) @cb;
+ end
+ join_any
+ disable fork;
+ assert(fired);
+
+ // Verify swacc and swmod assert when written
+ fired = 0;
+ fork
+ begin
+ ##0;
+ forever begin
+ assert(cb.hwif_out.r4.f.value == 'h12);
+ if(cb.hwif_out.r4.f.swmod || cb.hwif_out.r4.f.swacc) begin
+ assert(cb.hwif_out.r4.f.swmod == 1);
+ assert(cb.hwif_out.r4.f.swacc == 1);
+ break;
+ end
+ @cb;
+ end
+ fired = 1;
+ @cb;
+ forever begin
+ assert(cb.hwif_out.r4.f.value == 'h34);
+ assert(cb.hwif_out.r4.f.swmod == 0);
+ @cb;
+ end
+ end
+
+ begin
+ cpuif.write('h3, 'h34);
+ repeat(4) @cb;
+ end
+ join_any
+ disable fork;
+ assert(fired);
+
+
+ // Verify that hwif changes 1 cycle after swmod
+ fired = 0;
+ fork
+ begin
+ ##0;
+ forever begin
+ assert(cb.hwif_out.r5.f.value == 30);
+ if(cb.hwif_out.r5.f.swmod) break;
+ @cb;
+ end
+ fired = 1;
+ @cb;
+ forever begin
+ assert(cb.hwif_out.r5.f.value == 0);
+ assert(cb.hwif_out.r5.f.swmod == 0);
+ @cb;
+ end
+ end
+
+ begin
+ cpuif.assert_read('h4, 30);
+ repeat(4) @cb;
+ end
+ join_any
+ disable fork;
+ assert(fired);
+
+{% endblock %}
diff --git a/tests/test_swacc_swmod/testcase.py b/tests/test_swacc_swmod/testcase.py
new file mode 100644
index 0000000..835b5ef
--- /dev/null
+++ b/tests/test_swacc_swmod/testcase.py
@@ -0,0 +1,5 @@
+from ..lib.sim_testcase import SimTestCase
+
+class Test(SimTestCase):
+ def test_dut(self):
+ self.run_test()
diff --git a/tests/test_swwe/__init__.py b/tests/test_swwe/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_swwe/regblock.rdl b/tests/test_swwe/regblock.rdl
new file mode 100644
index 0000000..c31fc29
--- /dev/null
+++ b/tests/test_swwe/regblock.rdl
@@ -0,0 +1,66 @@
+addrmap top {
+ default regwidth = 8;
+
+ reg {
+ field {
+ sw=rw; hw=na;
+ } r3_swwe[0:0] = 1;
+
+ field {
+ sw=rw; hw=na;
+ } r4_swwel[1:1] = 0;
+ } lock;
+
+ //---------------------------------
+ // via inferred signal
+ //---------------------------------
+
+ reg {
+ field {
+ sw=rw; hw=na;
+ swwe;
+ } f[8] = 0x11;
+ } r1;
+
+ reg {
+ field {
+ sw=rw; hw=na;
+ swwel;
+ } f[8] = 0x22;
+ } r2;
+
+ //---------------------------------
+ // via lock register
+ //---------------------------------
+
+ reg {
+ field {
+ sw=rw; hw=na;
+ } f[8] = 0x33;
+ } r3;
+ r3.f->swwe = lock.r3_swwe;
+
+ reg {
+ field {
+ sw=rw; hw=na;
+ } f[8] = 0x44;
+ } r4;
+ r4.f->swwel = lock.r4_swwel;
+
+ //---------------------------------
+ // via prop ref chaining
+ //---------------------------------
+ reg {
+ field {
+ sw=rw; hw=na;
+ } f[8] = 0x55;
+ } r5;
+ r5.f->swwe = r3.f->swwe;
+
+ reg {
+ field {
+ sw=rw; hw=na;
+ } f[8] = 0x66;
+ } r6;
+ r6.f->swwe = r4.f->swwel; // intentionally opposite!
+};
diff --git a/tests/test_swwe/tb_template.sv b/tests/test_swwe/tb_template.sv
new file mode 100644
index 0000000..114816f
--- /dev/null
+++ b/tests/test_swwe/tb_template.sv
@@ -0,0 +1,63 @@
+{% extends "lib/tb_base.sv" %}
+
+{% block seq %}
+ {% sv_line_anchor %}
+ ##1;
+ cb.rst <= '0;
+ ##1;
+
+ // r1 swwe = true
+ cpuif.assert_read('h1, 'h11);
+ cb.hwif_in.r1.f.swwe <= '0;
+ cpuif.write ('h1, 'h12);
+ cpuif.assert_read('h1, 'h11);
+ cb.hwif_in.r1.f.swwe <= '1;
+ cpuif.write ('h1, 'h13);
+ cpuif.assert_read('h1, 'h13);
+
+ // r2 swwel = true
+ cpuif.assert_read('h2, 'h22);
+ cb.hwif_in.r2.f.swwel <= '1;
+ cpuif.write ('h2, 'h23);
+ cpuif.assert_read('h2, 'h22);
+ cb.hwif_in.r2.f.swwel <= '0;
+ cpuif.write ('h2, 'h24);
+ cpuif.assert_read('h2, 'h24);
+
+ // r3 swwe = lock.r3_swwe
+ cpuif.assert_read('h3, 'h33);
+ cpuif.write ('h0, 'h0);
+ cpuif.write ('h3, 'h32);
+ cpuif.assert_read('h3, 'h33);
+ cpuif.write ('h0, 'h1);
+ cpuif.write ('h3, 'h34);
+ cpuif.assert_read('h3, 'h34);
+
+ // r4 swwel = lock.r4_swwel
+ cpuif.assert_read('h4, 'h44);
+ cpuif.write ('h0, 'h2);
+ cpuif.write ('h4, 'h40);
+ cpuif.assert_read('h4, 'h44);
+ cpuif.write ('h0, 'h0);
+ cpuif.write ('h4, 'h45);
+ cpuif.assert_read('h4, 'h45);
+
+ // r5 swwe = r3->swwe = lock.r3_swwe
+ cpuif.assert_read('h5, 'h55);
+ cpuif.write ('h0, 'h0);
+ cpuif.write ('h5, 'h52);
+ cpuif.assert_read('h5, 'h55);
+ cpuif.write ('h0, 'h1);
+ cpuif.write ('h5, 'h54);
+ cpuif.assert_read('h5, 'h54);
+
+ // r6 swwe = r4->swwel = lock.r4_swwel
+ cpuif.assert_read('h6, 'h66);
+ cpuif.write ('h0, 'h0);
+ cpuif.write ('h6, 'h60);
+ cpuif.assert_read('h6, 'h66);
+ cpuif.write ('h0, 'h2);
+ cpuif.write ('h6, 'h65);
+ cpuif.assert_read('h6, 'h65);
+
+{% endblock %}
diff --git a/tests/test_swwe/testcase.py b/tests/test_swwe/testcase.py
new file mode 100644
index 0000000..835b5ef
--- /dev/null
+++ b/tests/test_swwe/testcase.py
@@ -0,0 +1,5 @@
+from ..lib.sim_testcase import SimTestCase
+
+class Test(SimTestCase):
+ def test_dut(self):
+ self.run_test()
diff --git a/tests/test_user_cpuif/__init__.py b/tests/test_user_cpuif/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_user_cpuif/regblock.rdl b/tests/test_user_cpuif/regblock.rdl
new file mode 100644
index 0000000..edc1829
--- /dev/null
+++ b/tests/test_user_cpuif/regblock.rdl
@@ -0,0 +1,7 @@
+addrmap top {
+ reg {
+ field {
+ sw=rw; hw=r;
+ } f = 0;
+ } r1;
+};
diff --git a/tests/test_user_cpuif/testcase.py b/tests/test_user_cpuif/testcase.py
new file mode 100644
index 0000000..3e8465e
--- /dev/null
+++ b/tests/test_user_cpuif/testcase.py
@@ -0,0 +1,49 @@
+import os
+
+from peakrdl_regblock.cpuif.apb3 import APB3_Cpuif
+from ..lib.cpuifs.apb3 import APB3
+from ..lib.base_testcase import BaseTestCase
+
+
+#-------------------------------------------------------------------------------
+
+class ClassOverride_Cpuif(APB3_Cpuif):
+ @property
+ def port_declaration(self) -> str:
+ return "user_apb3_intf.slave s_apb"
+
+class ClassOverride_cpuiftestmode(APB3):
+ cpuif_cls = ClassOverride_Cpuif
+
+
+class Test_class_override(BaseTestCase):
+ cpuif = ClassOverride_cpuiftestmode()
+
+ def test_override_success(self):
+ output_file = os.path.join(self.get_run_dir(), "regblock.sv")
+ with open(output_file, "r") as f:
+ self.assertIn(
+ "user_apb3_intf.slave s_apb",
+ f.read()
+ )
+
+#-------------------------------------------------------------------------------
+
+class TemplateOverride_Cpuif(APB3_Cpuif):
+ # contains the text "USER TEMPLATE OVERRIDE"
+ template_path = "user_apb3_tmpl.sv"
+
+class TemplateOverride_cpuiftestmode(APB3):
+ cpuif_cls = TemplateOverride_Cpuif
+
+
+class Test_template_override(BaseTestCase):
+ cpuif = TemplateOverride_cpuiftestmode()
+
+ def test_override_success(self):
+ output_file = os.path.join(self.get_run_dir(), "regblock.sv")
+ with open(output_file, "r") as f:
+ self.assertIn(
+ "USER TEMPLATE OVERRIDE",
+ f.read()
+ )
diff --git a/tests/test_user_cpuif/user_apb3_tmpl.sv b/tests/test_user_cpuif/user_apb3_tmpl.sv
new file mode 100644
index 0000000..bea1976
--- /dev/null
+++ b/tests/test_user_cpuif/user_apb3_tmpl.sv
@@ -0,0 +1 @@
+// USER TEMPLATE OVERRIDE
diff --git a/tests/test_validation_errors/__init__.py b/tests/test_validation_errors/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_validation_errors/external_ref.rdl b/tests/test_validation_errors/external_ref.rdl
new file mode 100644
index 0000000..99778b6
--- /dev/null
+++ b/tests/test_validation_errors/external_ref.rdl
@@ -0,0 +1,15 @@
+addrmap sub {
+ reg {
+ field {} f;
+ } x;
+};
+
+addrmap top {
+ reg {
+ field {} f;
+ } x;
+
+ sub sub;
+
+ x.f->reset = sub.x.f;
+};
diff --git a/tests/test_validation_errors/fixedpoint_counter.rdl b/tests/test_validation_errors/fixedpoint_counter.rdl
new file mode 100644
index 0000000..426d70e
--- /dev/null
+++ b/tests/test_validation_errors/fixedpoint_counter.rdl
@@ -0,0 +1,9 @@
+addrmap top {
+ reg {
+ field {
+ sw = rw; hw = r;
+ intwidth = 4;
+ counter;
+ } fixedpoint_counter[8] = 0;
+ } r1;
+};
diff --git a/tests/test_validation_errors/fixedpoint_enum.rdl b/tests/test_validation_errors/fixedpoint_enum.rdl
new file mode 100644
index 0000000..8d3fe84
--- /dev/null
+++ b/tests/test_validation_errors/fixedpoint_enum.rdl
@@ -0,0 +1,15 @@
+addrmap top {
+ reg {
+ enum test_enum {
+ zero = 2'b00;
+ one = 2'b01;
+ two = 2'b10;
+ three = 2'b11;
+ };
+ field {
+ sw = rw; hw = r;
+ fracwidth = 0;
+ encode = test_enum;
+ } fixedpoint_enum[2] = 0;
+ } r1;
+};
diff --git a/tests/test_validation_errors/fixedpoint_inconsistent_width.rdl b/tests/test_validation_errors/fixedpoint_inconsistent_width.rdl
new file mode 100644
index 0000000..dd49956
--- /dev/null
+++ b/tests/test_validation_errors/fixedpoint_inconsistent_width.rdl
@@ -0,0 +1,9 @@
+addrmap top {
+ reg {
+ field {
+ sw = rw; hw = r;
+ intwidth = 4;
+ fracwidth = 5;
+ } num[8] = 0;
+ } r1;
+};
diff --git a/tests/test_validation_errors/inconsistent_accesswidth.rdl b/tests/test_validation_errors/inconsistent_accesswidth.rdl
new file mode 100644
index 0000000..9a3057b
--- /dev/null
+++ b/tests/test_validation_errors/inconsistent_accesswidth.rdl
@@ -0,0 +1,9 @@
+addrmap top {
+ reg {
+ field {}f;
+ } x;
+ reg {
+ accesswidth = 16;
+ field {}f;
+ } y;
+};
diff --git a/tests/test_validation_errors/multiple_unconditional_assigns.rdl b/tests/test_validation_errors/multiple_unconditional_assigns.rdl
new file mode 100644
index 0000000..a087018
--- /dev/null
+++ b/tests/test_validation_errors/multiple_unconditional_assigns.rdl
@@ -0,0 +1,40 @@
+addrmap top {
+ reg {
+ field {
+ sw = rw;
+ hw = w;
+ singlepulse = true;
+ } a = 0;
+ field {
+ sw = rw;
+ hw = w;
+ singlepulse = true;
+ posedge intr;
+ stickybit = false;
+ } b = 0;
+ field {
+ sw = rw;
+ hw = w;
+ singlepulse = true;
+ negedge intr;
+ stickybit = false;
+ } c = 0;
+ field {
+ sw = rw;
+ hw = w;
+ singlepulse = true;
+ bothedge intr;
+ stickybit = false;
+ } d = 0;
+ } x;
+};
+
+/*
+stickybit=false + intr posedge
+stickybit=false + intr negedge
+stickybit=false + intr bothedge
+hw=w wel = false
+singlepulse
+
+
+*/
diff --git a/tests/test_validation_errors/sharedextbus.rdl b/tests/test_validation_errors/sharedextbus.rdl
new file mode 100644
index 0000000..de14091
--- /dev/null
+++ b/tests/test_validation_errors/sharedextbus.rdl
@@ -0,0 +1,7 @@
+addrmap top {
+ sharedextbus;
+
+ reg {
+ field {}f;
+ } x;
+};
diff --git a/tests/test_validation_errors/signed_counter.rdl b/tests/test_validation_errors/signed_counter.rdl
new file mode 100644
index 0000000..9910a81
--- /dev/null
+++ b/tests/test_validation_errors/signed_counter.rdl
@@ -0,0 +1,14 @@
+addrmap top {
+ reg {
+ field {
+ sw = rw; hw = r;
+ is_signed = false;
+ counter;
+ } unsigned_counter[8] = 0;
+ field {
+ sw = rw; hw = r;
+ is_signed;
+ counter;
+ } signed_counter[8] = 0;
+ } r1;
+};
diff --git a/tests/test_validation_errors/signed_enum.rdl b/tests/test_validation_errors/signed_enum.rdl
new file mode 100644
index 0000000..2b91450
--- /dev/null
+++ b/tests/test_validation_errors/signed_enum.rdl
@@ -0,0 +1,20 @@
+addrmap top {
+ reg {
+ enum test_enum {
+ zero = 2'b00;
+ one = 2'b01;
+ two = 2'b10;
+ three = 2'b11;
+ };
+ field {
+ sw = rw; hw = r;
+ is_signed = false;
+ encode = test_enum;
+ } unsigned_enum[2] = 0;
+ field {
+ sw = rw; hw = r;
+ is_signed;
+ encode = test_enum;
+ } signed_enum[2] = 0;
+ } r1;
+};
diff --git a/tests/test_validation_errors/testcase.py b/tests/test_validation_errors/testcase.py
new file mode 100644
index 0000000..3267dac
--- /dev/null
+++ b/tests/test_validation_errors/testcase.py
@@ -0,0 +1,121 @@
+import io
+import contextlib
+
+from systemrdl.messages import RDLCompileError
+
+from ..lib.base_testcase import BaseTestCase
+
+class TestValidationErrors(BaseTestCase):
+ def setUp(self) -> None:
+ # Stub usual pre-test setup
+ pass
+
+ def tearDown(self):
+ # Delete any cruft that may get generated
+ self.delete_run_dir()
+
+ def assert_validate_error(self, rdl_file: str, err_regex: str) -> None:
+ self.rdl_file = rdl_file
+ f = io.StringIO()
+ with contextlib.redirect_stderr(f):
+ with self.assertRaises(RDLCompileError):
+ self.export_regblock()
+ stderr = f.getvalue()
+ self.assertRegex(stderr, err_regex)
+
+
+ def test_unaligned_reg(self) -> None:
+ self.assert_validate_error(
+ "unaligned_reg.rdl",
+ "Unaligned registers are not supported. Address offset of instance 'x' must be a multiple of 4",
+ )
+
+ def test_unaligned_stride(self) -> None:
+ self.assert_validate_error(
+ "unaligned_stride.rdl",
+ "Unaligned registers are not supported. Address stride of instance array 'x' must be a multiple of 4",
+ )
+
+ def test_bad_external_ref(self) -> None:
+ self.assert_validate_error(
+ "external_ref.rdl",
+ "Property is assigned a reference that points to a component not internal to the regblock being exported",
+ )
+
+ def test_sharedextbus_not_supported(self) -> None:
+ self.assert_validate_error(
+ "sharedextbus.rdl",
+ "This exporter does not support enabling the 'sharedextbus' property yet",
+ )
+
+ def test_inconsistent_accesswidth(self) -> None:
+ self.assert_validate_error(
+ "inconsistent_accesswidth.rdl",
+ r"Multi-word registers that have an accesswidth \(16\) that are inconsistent with this regblock's CPU bus width \(32\) are not supported",
+ )
+
+ def test_unbuffered_wide_w_fields(self) -> None:
+ self.assert_validate_error(
+ "unbuffered_wide_fields.rdl",
+ "Software-writable field 'xf' shall not span"
+ " multiple software-accessible subwords. Consider enabling"
+ " write double-buffering",
+ )
+
+ def test_unbuffered_wide_r_fields(self) -> None:
+ self.assert_validate_error(
+ "unbuffered_wide_fields.rdl",
+ "The field 'yf' spans multiple software-accessible"
+ " subwords and is modified on-read, making it impossible to"
+ " access its value correctly. Consider enabling read"
+ " double-buffering.",
+ )
+
+ def test_multiple_unconditional_assigns(self) -> None:
+ self.assert_validate_error(
+ "multiple_unconditional_assigns.rdl",
+ "Field has multiple conflicting properties that unconditionally set its state",
+ )
+
+ def test_unsynth_reset1(self) -> None:
+ self.assert_validate_error(
+ "unsynth_reset1.rdl",
+ "A field that uses an asynchronous reset cannot use a dynamic reset value. This is not synthesizable.",
+ )
+
+ def test_unsynth_reset2(self) -> None:
+ self.default_reset_async = True
+ self.assert_validate_error(
+ "unsynth_reset2.rdl",
+ "A field that uses an asynchronous reset cannot use a dynamic reset value. This is not synthesizable.",
+ )
+
+ def test_fixedpoint_counter(self) -> None:
+ self.assert_validate_error(
+ "fixedpoint_counter.rdl",
+ "Fixed-point representations are not supported for counter fields.",
+ )
+
+ def test_fixedpoint_enum(self) -> None:
+ self.assert_validate_error(
+ "fixedpoint_enum.rdl",
+ "Fixed-point representations are not supported for fields encoded as an enum.",
+ )
+
+ def test_fixedpoint_inconsistent_width(self) -> None:
+ self.assert_validate_error(
+ "fixedpoint_inconsistent_width.rdl",
+ r"Number of integer bits \(4\) plus number of fractional bits \(5\) must be equal to the width of the component \(8\).",
+ )
+
+ def test_signed_counter(self) -> None:
+ self.assert_validate_error(
+ "signed_counter.rdl",
+ "The property is_signed=true is not supported for counter fields.",
+ )
+
+ def test_signed_enum(self) -> None:
+ self.assert_validate_error(
+ "signed_enum.rdl",
+ "The property is_signed=true is not supported for fields encoded as an enum."
+ )
diff --git a/tests/test_validation_errors/unaligned_reg.rdl b/tests/test_validation_errors/unaligned_reg.rdl
new file mode 100644
index 0000000..c30dac2
--- /dev/null
+++ b/tests/test_validation_errors/unaligned_reg.rdl
@@ -0,0 +1,8 @@
+addrmap top {
+ default regwidth = 32;
+ default accesswidth = 32;
+
+ reg {
+ field {}f;
+ } x @ 1;
+};
diff --git a/tests/test_validation_errors/unaligned_stride.rdl b/tests/test_validation_errors/unaligned_stride.rdl
new file mode 100644
index 0000000..da5fb24
--- /dev/null
+++ b/tests/test_validation_errors/unaligned_stride.rdl
@@ -0,0 +1,8 @@
+addrmap top {
+ default regwidth = 32;
+ default accesswidth = 32;
+
+ reg {
+ field {}f;
+ } x[4] @ 0 += 5;
+};
diff --git a/tests/test_validation_errors/unbuffered_wide_fields.rdl b/tests/test_validation_errors/unbuffered_wide_fields.rdl
new file mode 100644
index 0000000..e349a9c
--- /dev/null
+++ b/tests/test_validation_errors/unbuffered_wide_fields.rdl
@@ -0,0 +1,21 @@
+addrmap top {
+ reg {
+ regwidth = 64;
+ accesswidth = 32;
+ field {
+ sw=w;
+ hw=r;
+ } xf[64];
+ } x;
+
+ reg {
+ regwidth = 64;
+ accesswidth = 32;
+ field {
+ sw=r;
+ hw=w;
+ we;
+ onread=rclr;
+ } yf[64];
+ } y;
+};
diff --git a/tests/test_validation_errors/unsynth_reset1.rdl b/tests/test_validation_errors/unsynth_reset1.rdl
new file mode 100644
index 0000000..df1e4a4
--- /dev/null
+++ b/tests/test_validation_errors/unsynth_reset1.rdl
@@ -0,0 +1,19 @@
+signal {
+ field_reset;
+ async;
+ activehigh;
+} foo;
+
+addrmap top {
+ reg {
+ field {
+ sw=rw; hw=na;
+ } f1;
+
+ field {
+ sw=rw; hw=na;
+ } f2;
+
+ f1->reset = f2;
+ } x;
+};
diff --git a/tests/test_validation_errors/unsynth_reset2.rdl b/tests/test_validation_errors/unsynth_reset2.rdl
new file mode 100644
index 0000000..9cec0c3
--- /dev/null
+++ b/tests/test_validation_errors/unsynth_reset2.rdl
@@ -0,0 +1,13 @@
+addrmap top {
+ reg {
+ field {
+ sw=rw; hw=na;
+ } f1;
+
+ field {
+ sw=rw; hw=na;
+ } f2;
+
+ f1->reset = f2;
+ } x;
+};
diff --git a/tests/test_wide_regs/__init__.py b/tests/test_wide_regs/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_wide_regs/regblock.rdl b/tests/test_wide_regs/regblock.rdl
new file mode 100644
index 0000000..b3d4ca4
--- /dev/null
+++ b/tests/test_wide_regs/regblock.rdl
@@ -0,0 +1,111 @@
+addrmap top {
+
+ reg {
+ regwidth = 64;
+ accesswidth = 16;
+ default sw=rw;
+ default hw=r;
+
+ field {} f1[7:0] = 0;
+ field {} f2[14:12] = 0;
+ field {} f3[36:36] = 0;
+ field {} f4[47:40] = 0;
+ } rw_reg1;
+
+ reg {
+ regwidth = 64;
+ accesswidth = 16;
+ default sw=rw;
+ default hw=r;
+
+ field {} f1[19:16] = 0;
+ field {} f2[63:48] = 0;
+ } rw_reg2;
+
+ reg {
+ regwidth = 64;
+ accesswidth = 16;
+ default sw=rw;
+ default hw=r;
+
+ field {} f1[0:7] = 0;
+ field {} f2[12:14] = 0;
+ field {} f3[36:36] = 0;
+ field {} f4[40:47] = 0;
+ } rw_reg1_lsb0;
+
+ reg {
+ regwidth = 64;
+ accesswidth = 16;
+ default sw=rw;
+ default hw=r;
+
+ field {} f1[16:19] = 0;
+ field {} f2[48:63] = 0;
+ } rw_reg2_lsb0;
+
+ reg {
+ regwidth = 32;
+ accesswidth = 16;
+ default sw=r;
+ default hw=w;
+
+ field {
+ sw=w; hw=r;
+ } f0[3:3] = 0;
+ field {} f1[19:12];
+ field {} f2[30:20];
+ } r_reg;
+
+ reg {
+ regwidth = 32;
+ accesswidth = 16;
+ default sw=r;
+ default hw=w;
+
+ field {} f1[12:19];
+ field {} f2[20:30];
+ } r_reg_lsb0;
+
+ reg {
+ regwidth = 64;
+ accesswidth = 16;
+ default sw=r;
+ default hw=w;
+
+ field {} f1[31:12];
+ field {} f2[49:48];
+ } r_reg2;
+
+ reg {
+ regwidth=16;
+ field {
+ sw=r; hw=na;
+ counter;
+ } f1_cnt[7:0] = 0;
+ field {
+ sw=r; hw=na;
+ counter;
+ } f2_cnt[15:8] = 0;
+ } counter_reg;
+ counter_reg.f1_cnt->incr = r_reg2.f1->swacc;
+ counter_reg.f2_cnt->incr = r_reg2.f2->swacc;
+
+ reg {
+ regwidth = 32;
+ accesswidth = 16;
+ default sw=r;
+ default hw=r;
+
+ field {} f1[31:0] = 0x1234_5678;
+ } r_reg3;
+
+ reg {
+ regwidth = 32;
+ accesswidth = 16;
+ default sw=r;
+ default hw=r;
+
+ field {} f1[0:31] = 0x1234_5678;
+ } r_reg4;
+};
diff --git a/tests/test_wide_regs/tb_template.sv b/tests/test_wide_regs/tb_template.sv
new file mode 100644
index 0000000..8c261c6
--- /dev/null
+++ b/tests/test_wide_regs/tb_template.sv
@@ -0,0 +1,121 @@
+{% extends "lib/tb_base.sv" %}
+
+{% block seq %}
+ {% sv_line_anchor %}
+ ##1;
+ cb.rst <= '0;
+ ##1;
+
+ // rw_reg1
+ assert(cb.hwif_out.rw_reg1.f1.value == 0);
+ assert(cb.hwif_out.rw_reg1.f2.value == 0);
+ assert(cb.hwif_out.rw_reg1.f3.value == 0);
+ assert(cb.hwif_out.rw_reg1.f4.value == 0);
+ cpuif.write('h0, 'h1234);
+ cpuif.write('h2, 'h5678);
+ cpuif.write('h4, 'h9ABC);
+ cpuif.write('h6, 'hDEF1);
+ @cb;
+ assert(cb.hwif_out.rw_reg1.f1.value == 8'h34);
+ assert(cb.hwif_out.rw_reg1.f2.value == 3'h1);
+ assert(cb.hwif_out.rw_reg1.f3.value == 1'h1);
+ assert(cb.hwif_out.rw_reg1.f4.value == 8'h9A);
+ cpuif.assert_read('h0, 'h1034);
+ cpuif.assert_read('h2, 'h0000);
+ cpuif.assert_read('h4, 'h9A10);
+ cpuif.assert_read('h6, 'h0000);
+
+ // rw_reg2
+ assert(cb.hwif_out.rw_reg2.f1.value == 0);
+ assert(cb.hwif_out.rw_reg2.f2.value == 0);
+ cpuif.write('h8, 'h1234);
+ cpuif.write('hA, 'h5678);
+ cpuif.write('hC, 'h9ABC);
+ cpuif.write('hE, 'hDEF1);
+ @cb;
+ assert(cb.hwif_out.rw_reg2.f1.value == 4'h8);
+ assert(cb.hwif_out.rw_reg2.f2.value == 16'hDEF1);
+ cpuif.assert_read('h8, 'h0000);
+ cpuif.assert_read('hA, 'h0008);
+ cpuif.assert_read('hC, 'h0000);
+ cpuif.assert_read('hE, 'hDEF1);
+
+ // rw_reg1_lsb0
+ assert(cb.hwif_out.rw_reg1_lsb0.f1.value == 0);
+ assert(cb.hwif_out.rw_reg1_lsb0.f2.value == 0);
+ assert(cb.hwif_out.rw_reg1_lsb0.f3.value == 0);
+ assert(cb.hwif_out.rw_reg1_lsb0.f4.value == 0);
+ cpuif.write('h10, 'h1234);
+ cpuif.write('h12, 'h5678);
+ cpuif.write('h14, 'h9ABC);
+ cpuif.write('h16, 'hDEF1);
+ @cb;
+ assert(`bitswap(cb.hwif_out.rw_reg1_lsb0.f1.value) == 8'h34);
+ assert(`bitswap(cb.hwif_out.rw_reg1_lsb0.f2.value) == 3'h1);
+ assert(`bitswap(cb.hwif_out.rw_reg1_lsb0.f3.value) == 1'h1);
+ assert(`bitswap(cb.hwif_out.rw_reg1_lsb0.f4.value) == 8'h9A);
+ cpuif.assert_read('h10, 'h1034);
+ cpuif.assert_read('h12, 'h0000);
+ cpuif.assert_read('h14, 'h9A10);
+ cpuif.assert_read('h16, 'h0000);
+
+ // rw_reg2_lsb0
+ assert(cb.hwif_out.rw_reg2_lsb0.f1.value == 0);
+ assert(cb.hwif_out.rw_reg2_lsb0.f2.value == 0);
+ cpuif.write('h18, 'h1234);
+ cpuif.write('h1A, 'h5678);
+ cpuif.write('h1C, 'h9ABC);
+ cpuif.write('h1E, 'hDEF1);
+ @cb;
+ assert(`bitswap(cb.hwif_out.rw_reg2_lsb0.f1.value) == 4'h8);
+ assert(`bitswap(cb.hwif_out.rw_reg2_lsb0.f2.value) == 16'hDEF1);
+ cpuif.assert_read('h18, 'h0000);
+ cpuif.assert_read('h1A, 'h0008);
+ cpuif.assert_read('h1C, 'h0000);
+ cpuif.assert_read('h1E, 'hDEF1);
+
+ // r_reg
+ cpuif.assert_read('h20, 0);
+ cpuif.assert_read('h22, 0);
+ cb.hwif_in.r_reg.f1.next <= 8'hAB;
+ cb.hwif_in.r_reg.f2.next <= 11'h4DE;
+ @cb;
+ cpuif.assert_read('h20, 'hB000);
+ cpuif.assert_read('h22, 'h4DEA);
+
+ // r_reg_lsb0
+ cpuif.assert_read('h24, 0);
+ cpuif.assert_read('h26, 0);
+ cb.hwif_in.r_reg_lsb0.f1.next <= {<<{8'hAB}};
+ cb.hwif_in.r_reg_lsb0.f2.next <= {<<{11'h4DE}};
+ @cb;
+ cpuif.assert_read('h24, 'hB000);
+ cpuif.assert_read('h26, 'h4DEA);
+
+ // r_reg2
+ cpuif.assert_read('h28, 0);
+ cpuif.assert_read('h2a, 0);
+ cpuif.assert_read('h2c, 0);
+ cpuif.assert_read('h2e, 0);
+ cb.hwif_in.r_reg2.f1.next <= 20'hABCDE;
+ cb.hwif_in.r_reg2.f2.next <= 2'h3;
+ @cb;
+ cpuif.assert_read('h28, 'hE000);
+ cpuif.assert_read('h2a, 'hABCD);
+ cpuif.assert_read('h2c, 'h0000);
+ cpuif.assert_read('h2e, 'h0003);
+
+ // counter_reg
+ cpuif.assert_read('h30, 16'h0204);
+
+ // r_reg3
+ cpuif.assert_read('h34, 16'h5678);
+ cpuif.assert_read('h36, 16'h1234);
+ assert(cb.hwif_out.r_reg3.f1.value == 32'h12345678);
+
+ // r_reg4
+ cpuif.assert_read('h38, 16'h2C48);
+ cpuif.assert_read('h3A, 16'h1E6A);
+ assert(cb.hwif_out.r_reg4.f1.value == 32'h12345678);
+
+{% endblock %}
diff --git a/tests/test_wide_regs/testcase.py b/tests/test_wide_regs/testcase.py
new file mode 100644
index 0000000..835b5ef
--- /dev/null
+++ b/tests/test_wide_regs/testcase.py
@@ -0,0 +1,5 @@
+from ..lib.sim_testcase import SimTestCase
+
+class Test(SimTestCase):
+ def test_dut(self):
+ self.run_test()
diff --git a/tests/test_write_buffer/__init__.py b/tests/test_write_buffer/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_write_buffer/regblock.rdl b/tests/test_write_buffer/regblock.rdl
new file mode 100644
index 0000000..ca267bc
--- /dev/null
+++ b/tests/test_write_buffer/regblock.rdl
@@ -0,0 +1,106 @@
+addrmap top {
+ default regwidth = 16;
+ default accesswidth = 16;
+ default sw=rw;
+ default hw=r;
+ //--------------------------------------------------------------------------
+ // Wide registers
+ //--------------------------------------------------------------------------
+ reg {
+ regwidth = 64;
+ buffer_writes = true;
+ field {} f1[63:0] = 0;
+ } reg1;
+
+ reg {
+ regwidth = 64;
+ buffer_writes = true;
+ field {} f1[0:63] = 0;
+ } reg1_msb0;
+
+ reg {
+ regwidth = 32;
+ buffer_writes = true;
+ field {} f1[19:8] = 0;
+ field {} f2[23:20] = 0;
+ } reg2;
+
+ reg {
+ regwidth = 32;
+ buffer_writes = true;
+ field {} f1[8:19] = 0;
+ field {} f2[20:23] = 0;
+ } reg2_msb0;
+
+ //--------------------------------------------------------------------------
+ // Alternate Triggers
+ //--------------------------------------------------------------------------
+ reg myreg {
+ buffer_writes;
+ field {} f1[15:0] = 0;
+ };
+
+ // Trigger via another register
+ myreg g1_r1;
+ myreg g1_r2;
+ g1_r1->buffer_writes = false;
+ g1_r2->wbuffer_trigger = g1_r1;
+
+ // triger from signal
+ signal {
+ activehigh;
+ } trigger_sig;
+ signal {
+ activelow;
+ } trigger_sig_n;
+ myreg g2_r1;
+ myreg g2_r2;
+ g2_r1->wbuffer_trigger = trigger_sig;
+ g2_r2->wbuffer_trigger = trigger_sig_n;
+
+ // trigger from field
+ myreg g3_r1;
+ reg {
+ field {
+ sw=w; hw=r; singlepulse;
+ } trig = 0;
+ } g3_trig;
+ g3_r1->wbuffer_trigger = g3_trig.trig;
+
+ // trigger from propref
+ myreg g4_r1;
+ reg {
+ field {
+ hw=na;
+ } trig_vec[3:0] = 0;
+ } g4_trig;
+ g4_r1->wbuffer_trigger = g4_trig.trig_vec->anded;
+
+ //--------------------------------------------------------------------------
+ // swmod behavior
+ //--------------------------------------------------------------------------
+ myreg g5_r1;
+ g5_r1->wbuffer_trigger = trigger_sig;
+ reg {
+ field{
+ sw=rw;
+ hw=na;
+ counter;
+ } c[3:0] = 0;
+ } g5_modcount;
+ g5_modcount.c->incr = g5_r1.f1->swmod;
+
+ myreg g6_r1;
+ g6_r1.f1->rclr;
+ g6_r1->wbuffer_trigger = trigger_sig;
+ reg {
+ field{
+ sw=rw;
+ hw=na;
+ counter;
+ } c[3:0] = 0;
+ } g6_modcount;
+ g6_modcount.c->incr = g6_r1.f1->swmod;
+
+
+};
diff --git a/tests/test_write_buffer/tb_template.sv b/tests/test_write_buffer/tb_template.sv
new file mode 100644
index 0000000..4c281eb
--- /dev/null
+++ b/tests/test_write_buffer/tb_template.sv
@@ -0,0 +1,303 @@
+{% extends "lib/tb_base.sv" %}
+
+{% block seq %}
+ {% sv_line_anchor %}
+ cb.hwif_in.trigger_sig_n <= '1;
+ ##1;
+ cb.rst <= '0;
+ ##1;
+
+ //--------------------------------------------------------------------------
+ // Wide registers
+ //--------------------------------------------------------------------------
+
+ // reg1
+ cpuif.assert_read('h0, 'h0);
+ cpuif.assert_read('h2, 'h0);
+ cpuif.assert_read('h4, 'h0);
+ cpuif.assert_read('h6, 'h0);
+ assert(cb.hwif_out.reg1.f1.value == 0);
+ cpuif.write('h0, 'h1234);
+ cpuif.assert_read('h0, 'h0);
+ cpuif.assert_read('h2, 'h0);
+ cpuif.assert_read('h4, 'h0);
+ cpuif.assert_read('h6, 'h0);
+ assert(cb.hwif_out.reg1.f1.value == 0);
+ cpuif.write('h2, 'h5678);
+ cpuif.assert_read('h0, 'h0);
+ cpuif.assert_read('h2, 'h0);
+ cpuif.assert_read('h4, 'h0);
+ cpuif.assert_read('h6, 'h0);
+ assert(cb.hwif_out.reg1.f1.value == 0);
+ cpuif.write('h4, 'h9ABC);
+ cpuif.assert_read('h0, 'h0);
+ cpuif.assert_read('h2, 'h0);
+ cpuif.assert_read('h4, 'h0);
+ cpuif.assert_read('h6, 'h0);
+ assert(cb.hwif_out.reg1.f1.value == 0);
+ cpuif.write('h6, 'hDEF1);
+ @cb; @cb;
+ assert(cb.hwif_out.reg1.f1.value == 64'hDEF19ABC56781234);
+ cpuif.assert_read('h0, 'h1234);
+ cpuif.assert_read('h2, 'h5678);
+ cpuif.assert_read('h4, 'h9ABC);
+ cpuif.assert_read('h6, 'hDEF1);
+
+ // reg1_msb0
+ cpuif.assert_read('h8, 'h0);
+ cpuif.assert_read('hA, 'h0);
+ cpuif.assert_read('hC, 'h0);
+ cpuif.assert_read('hE, 'h0);
+ assert(cb.hwif_out.reg1_msb0.f1.value == 0);
+ cpuif.write('h8, 'h1234);
+ cpuif.assert_read('h8, 'h0);
+ cpuif.assert_read('hA, 'h0);
+ cpuif.assert_read('hC, 'h0);
+ cpuif.assert_read('hE, 'h0);
+ assert(cb.hwif_out.reg1_msb0.f1.value == 0);
+ cpuif.write('hA, 'h5678);
+ cpuif.assert_read('h8, 'h0);
+ cpuif.assert_read('hA, 'h0);
+ cpuif.assert_read('hC, 'h0);
+ cpuif.assert_read('hE, 'h0);
+ assert(cb.hwif_out.reg1_msb0.f1.value == 0);
+ cpuif.write('hC, 'h9ABC);
+ cpuif.assert_read('h8, 'h0);
+ cpuif.assert_read('hA, 'h0);
+ cpuif.assert_read('hC, 'h0);
+ cpuif.assert_read('hE, 'h0);
+ assert(cb.hwif_out.reg1_msb0.f1.value == 0);
+ cpuif.write('hE, 'hDEF1);
+ @cb; @cb;
+ assert(`bitswap(cb.hwif_out.reg1_msb0.f1.value) == 64'hDEF19ABC56781234);
+ cpuif.assert_read('h8, 'h1234);
+ cpuif.assert_read('hA, 'h5678);
+ cpuif.assert_read('hC, 'h9ABC);
+ cpuif.assert_read('hE, 'hDEF1);
+
+ // reg2
+ cpuif.assert_read('h10, 'h0);
+ cpuif.assert_read('h12, 'h0);
+ assert(cb.hwif_out.reg2.f1.value == 0);
+ assert(cb.hwif_out.reg2.f2.value == 0);
+ cpuif.write('h10, 'h34AA);
+ cpuif.assert_read('h10, 'h0);
+ cpuif.assert_read('h12, 'h0);
+ assert(cb.hwif_out.reg2.f1.value == 0);
+ assert(cb.hwif_out.reg2.f2.value == 0);
+ cpuif.write('h12, 'hAA12);
+ @cb; @cb;
+ assert(cb.hwif_out.reg2.f1.value == 12'h234);
+ assert(cb.hwif_out.reg2.f2.value == 4'h1);
+ cpuif.assert_read('h10, 'h3400);
+ cpuif.assert_read('h12, 'h0012);
+
+ // reg2_msb0
+ cpuif.assert_read('h14, 'h0);
+ cpuif.assert_read('h16, 'h0);
+ assert(cb.hwif_out.reg2_msb0.f1.value == 0);
+ assert(cb.hwif_out.reg2_msb0.f2.value == 0);
+ cpuif.write('h14, 'h34AA);
+ cpuif.assert_read('h14, 'h0);
+ cpuif.assert_read('h16, 'h0);
+ assert(cb.hwif_out.reg2_msb0.f1.value == 0);
+ assert(cb.hwif_out.reg2_msb0.f2.value == 0);
+ cpuif.write('h16, 'hAA12);
+ @cb; @cb;
+ assert(`bitswap(cb.hwif_out.reg2_msb0.f1.value) == 12'h234);
+ assert(`bitswap(cb.hwif_out.reg2_msb0.f2.value) == 4'h1);
+ cpuif.assert_read('h14, 'h3400);
+ cpuif.assert_read('h16, 'h0012);
+
+ //--------------------------------------------------------------------------
+ // Alternate Triggers
+ //--------------------------------------------------------------------------
+
+ // g1
+ cpuif.assert_read('h18, 'h0);
+ cpuif.assert_read('h1A, 'h0);
+ assert(cb.hwif_out.g1_r1.f1.value == 0);
+ assert(cb.hwif_out.g1_r2.f1.value == 0);
+ cpuif.write('h1A, 'h1234);
+ cpuif.assert_read('h18, 'h0);
+ cpuif.assert_read('h1A, 'h0);
+ assert(cb.hwif_out.g1_r1.f1.value == 0);
+ assert(cb.hwif_out.g1_r2.f1.value == 0);
+ cpuif.write('h18, 'hABCD);
+ @cb;
+ assert(cb.hwif_out.g1_r1.f1.value == 'hABCD);
+ assert(cb.hwif_out.g1_r2.f1.value == 'h1234);
+
+ // g2
+ cpuif.assert_read('h1C, 'h0);
+ cpuif.assert_read('h1E, 'h0);
+ assert(cb.hwif_out.g2_r1.f1.value == 0);
+ assert(cb.hwif_out.g2_r2.f1.value == 0);
+ cpuif.write('h1C, 'h5678);
+ cpuif.write('h1E, 'h9876);
+ cpuif.assert_read('h1C, 'h0);
+ cpuif.assert_read('h1E, 'h0);
+ assert(cb.hwif_out.g2_r1.f1.value == 0);
+ assert(cb.hwif_out.g2_r2.f1.value == 0);
+ cb.hwif_in.trigger_sig <= '1;
+ cb.hwif_in.trigger_sig_n <= '0;
+ @cb;
+ cb.hwif_in.trigger_sig <= '0;
+ cb.hwif_in.trigger_sig_n <= '1;
+ @cb;
+ assert(cb.hwif_out.g2_r1.f1.value == 'h5678);
+ assert(cb.hwif_out.g2_r2.f1.value == 'h9876);
+
+ // g3
+ cpuif.assert_read('h20, 'h0);
+ assert(cb.hwif_out.g3_r1.f1.value == 0);
+ cpuif.write('h20, 'hFEDC);
+ @cb; @cb;
+ assert(cb.hwif_out.g3_r1.f1.value == 0);
+ cpuif.assert_read('h20, 'h0);
+ cpuif.write('h22, 'h0000);
+ @cb; @cb;
+ assert(cb.hwif_out.g3_r1.f1.value == 0);
+ cpuif.assert_read('h20, 'h0);
+ cpuif.write('h22, 'h0001);
+ @cb; @cb;
+ assert(cb.hwif_out.g3_r1.f1.value == 'hFEDC);
+ cpuif.assert_read('h20, 'hFEDC);
+
+ // g4
+ cpuif.assert_read('h24, 'h0);
+ assert(cb.hwif_out.g4_r1.f1.value == 0);
+ cpuif.write('h24, 'hCAFE);
+ @cb; @cb;
+ assert(cb.hwif_out.g4_r1.f1.value == 0);
+ cpuif.assert_read('h24, 'h0);
+ cpuif.write('h26, 'h0000);
+ @cb; @cb;
+ assert(cb.hwif_out.g4_r1.f1.value == 0);
+ cpuif.assert_read('h24, 'h0);
+ cpuif.write('h26, 'h000E);
+ @cb; @cb;
+ assert(cb.hwif_out.g4_r1.f1.value == 0);
+ cpuif.assert_read('h24, 'h0);
+ cpuif.write('h26, 'h000F);
+ @cb; @cb;
+ assert(cb.hwif_out.g4_r1.f1.value == 'hCAFE);
+ cpuif.assert_read('h24, 'hCAFE);
+
+ //--------------------------------------------------------------------------
+ // swmod behavior
+ //--------------------------------------------------------------------------
+ // g5
+ cpuif.assert_read('h28, 'h0);
+ cpuif.assert_read('h2A, 'h0);
+ cpuif.write('h28, 'h1234);
+ cpuif.write('h28, 'h5678);
+ cpuif.assert_read('h28, 'h0);
+ cpuif.assert_read('h2A, 'h0);
+ cb.hwif_in.trigger_sig <= '1;
+ @cb;
+ cb.hwif_in.trigger_sig <= '0;
+ cpuif.assert_read('h28, 'h5678);
+ cpuif.assert_read('h2A, 'h1);
+
+ // g6
+ cpuif.assert_read('h2E, 'h0);
+ cpuif.assert_read('h2C, 'h0);
+ cpuif.assert_read('h2E, 'h1);
+ cpuif.write('h2C, 'h5678);
+ cpuif.write('h2C, 'h1234);
+ cpuif.assert_read('h2E, 'h1);
+ cpuif.assert_read('h2C, 'h0);
+ cpuif.assert_read('h2E, 'h2);
+ cb.hwif_in.trigger_sig <= '1;
+ @cb;
+ cb.hwif_in.trigger_sig <= '0;
+ cpuif.assert_read('h2E, 'h3);
+ cpuif.assert_read('h2C, 'h1234);
+ cpuif.assert_read('h2E, 'h4);
+
+ //--------------------------------------------------------------------------
+ // strobes
+ //--------------------------------------------------------------------------
+ // reg1
+ // reset field to known state
+ cpuif.write('h0, 'h0000);
+ cpuif.write('h2, 'h0000);
+ cpuif.write('h4, 'h0000);
+ cpuif.write('h6, 'h0000);
+ @cb;
+ cpuif.assert_read('h0, 'h0);
+ cpuif.assert_read('h2, 'h0);
+ cpuif.assert_read('h4, 'h0);
+ cpuif.assert_read('h6, 'h0);
+ assert(cb.hwif_out.reg1.f1.value == 0);
+
+ cpuif.write('h0, 'hABCD, 'hF000);
+ cpuif.write('h2, 'h1234, 'h0F00);
+ cpuif.write('h4, 'h5678, 'h00F0);
+ cpuif.write('h6, 'hEF12, 'h000F);
+ @cb;
+ cpuif.assert_read('h0, 'hA000);
+ cpuif.assert_read('h2, 'h0200);
+ cpuif.assert_read('h4, 'h0070);
+ cpuif.assert_read('h6, 'h0002);
+ assert(cb.hwif_out.reg1.f1.value == 'h0002_0070_0200_A000);
+
+ // Check that strobes are cumulative
+ cpuif.write('h0, 'h0030, 'h00F0);
+ cpuif.write('h2, 'h0070, 'h00F0);
+ cpuif.write('h4, 'h000D, 'h000F);
+ cpuif.write('h4, 'hA000, 'hF000);
+ cpuif.write('h2, 'h0008, 'h000F);
+ cpuif.write('h0, 'h0200, 'h0F00);
+ cpuif.write('h6, 'hA000, 'hF000);
+ cpuif.write('h6, 'h0F00, 'h0F00);
+ @cb;
+ cpuif.assert_read('h0, 'hA230);
+ cpuif.assert_read('h2, 'h0278);
+ cpuif.assert_read('h4, 'hA07D);
+ cpuif.assert_read('h6, 'hAF02);
+ assert(cb.hwif_out.reg1.f1.value == 'hAF02_A07D_0278_A230);
+
+ // reg1_msb0
+ // reset field to known state
+ cpuif.write('h8, 'h0000);
+ cpuif.write('hA, 'h0000);
+ cpuif.write('hC, 'h0000);
+ cpuif.write('hE, 'h0000);
+ @cb;
+ cpuif.assert_read('h8, 'h0);
+ cpuif.assert_read('hA, 'h0);
+ cpuif.assert_read('hC, 'h0);
+ cpuif.assert_read('hE, 'h0);
+ assert(cb.hwif_out.reg1_msb0.f1.value == 0);
+
+ cpuif.write('h8, 'hABCD, 'hF000);
+ cpuif.write('hA, 'h1234, 'h0F00);
+ cpuif.write('hC, 'h5678, 'h00F0);
+ cpuif.write('hE, 'hEF12, 'h000F);
+ @cb;
+ cpuif.assert_read('h8, 'hA000);
+ cpuif.assert_read('hA, 'h0200);
+ cpuif.assert_read('hC, 'h0070);
+ cpuif.assert_read('hE, 'h0002);
+ assert(`bitswap(cb.hwif_out.reg1_msb0.f1.value) == 'h0002_0070_0200_A000);
+
+ // Check that strobes are cumulative
+ cpuif.write('h8, 'h0030, 'h00F0);
+ cpuif.write('hA, 'h0070, 'h00F0);
+ cpuif.write('hC, 'h000D, 'h000F);
+ cpuif.write('hC, 'hA000, 'hF000);
+ cpuif.write('hA, 'h0008, 'h000F);
+ cpuif.write('h8, 'h0200, 'h0F00);
+ cpuif.write('hE, 'hA000, 'hF000);
+ cpuif.write('hE, 'h0F00, 'h0F00);
+ @cb;
+ cpuif.assert_read('h8, 'hA230);
+ cpuif.assert_read('hA, 'h0278);
+ cpuif.assert_read('hC, 'hA07D);
+ cpuif.assert_read('hE, 'hAF02);
+ assert(`bitswap(cb.hwif_out.reg1_msb0.f1.value) == 'hAF02_A07D_0278_A230);
+
+
+{% endblock %}
diff --git a/tests/test_write_buffer/testcase.py b/tests/test_write_buffer/testcase.py
new file mode 100644
index 0000000..4c1be35
--- /dev/null
+++ b/tests/test_write_buffer/testcase.py
@@ -0,0 +1,8 @@
+from ..lib.sim_testcase import SimTestCase
+from ..lib.cpuifs.passthrough import Passthrough
+
+class Test(SimTestCase):
+ cpuif = Passthrough() # test with bit strobes
+
+ def test_dut(self):
+ self.run_test()
diff --git a/tests/test_write_strobes/__init__.py b/tests/test_write_strobes/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_write_strobes/regblock.rdl b/tests/test_write_strobes/regblock.rdl
new file mode 100644
index 0000000..bfa31bd
--- /dev/null
+++ b/tests/test_write_strobes/regblock.rdl
@@ -0,0 +1,54 @@
+addrmap top {
+
+ reg {
+ field {
+ sw=rw; hw=na;
+ onwrite = woset;
+ } f1[3:0] = 0x0;
+
+ field {
+ sw=rw; hw=na;
+ onwrite = woclr;
+ } f2[7:4] = 0xF;
+
+ field {
+ sw=rw; hw=na;
+ onwrite = wot;
+ } f3[11:8] = 0x0;
+ } r1;
+
+ reg {
+ field {
+ sw=rw; hw=na;
+ onwrite = wzs;
+ } f1[3:0] = 0x0;
+
+ field {
+ sw=rw; hw=na;
+ onwrite = wzc;
+ } f2[7:4] = 0xF;
+
+ field {
+ sw=rw; hw=na;
+ onwrite = wzt;
+ } f3[11:8] = 0x0;
+ } r2;
+
+ reg {
+ field {
+ sw=rw; hw=na;
+ onwrite = wclr;
+ } f1[7:0] = 0xF0;
+
+ field {
+ sw=rw; hw=na;
+ onwrite = wset;
+ } f2[15:8] = 0x0F;
+ } r3;
+
+ reg {
+ field {
+ sw=rw; hw=na;
+ } f3[7:0] = 0x00;
+ } r4;
+};
diff --git a/tests/test_write_strobes/tb_template.sv b/tests/test_write_strobes/tb_template.sv
new file mode 100644
index 0000000..12e8e57
--- /dev/null
+++ b/tests/test_write_strobes/tb_template.sv
@@ -0,0 +1,31 @@
+{% extends "lib/tb_base.sv" %}
+
+{% block seq %}
+ {% sv_line_anchor %}
+ ##1;
+ cb.rst <= '0;
+ ##1;
+
+ cpuif.assert_read('h0, 'h0_F_0);
+ cpuif.write ('h0, 'h5_5_5, 'h3_3_3);
+ cpuif.assert_read('h0, 'h1_E_1);
+ cpuif.write ('h0, 'h5_A_A, 'h3_3_3);
+ cpuif.assert_read('h0, 'h0_C_3);
+
+ cpuif.assert_read('h4, 'h0_F_0);
+ cpuif.write ('h4, 'hA_A_A, 'h3_3_3);
+ cpuif.assert_read('h4, 'h1_E_1);
+ cpuif.write ('h4, 'hA_5_5, 'h3_3_3);
+ cpuif.assert_read('h4, 'h0_C_3);
+
+ cpuif.assert_read('h8, 'h0F_F0);
+ cpuif.write ('h8, 'h12_34, 'hFF_00);
+ cpuif.assert_read('h8, 'hFF_00);
+
+ cpuif.assert_read('hC, 'h00);
+ cpuif.write ('hC, 'hFF, 'hF0);
+ cpuif.assert_read('hC, 'hF0);
+ cpuif.write ('hC, 'h00, 'h3C);
+ cpuif.assert_read('hC, 'hC0);
+
+{% endblock %}
diff --git a/tests/test_write_strobes/testcase.py b/tests/test_write_strobes/testcase.py
new file mode 100644
index 0000000..b4c56ed
--- /dev/null
+++ b/tests/test_write_strobes/testcase.py
@@ -0,0 +1,9 @@
+from ..lib.sim_testcase import SimTestCase
+
+from ..lib.cpuifs.passthrough import Passthrough
+
+class Test(SimTestCase):
+ cpuif = Passthrough()
+
+ def test_dut(self):
+ self.run_test()
]