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 @@ +[![Documentation Status](https://readthedocs.org/projects/peakrdl-regblock/badge/?version=latest)](http://peakrdl-regblock.readthedocs.io) +[![build](https://github.com/SystemRDL/PeakRDL-regblock/workflows/build/badge.svg)](https://github.com/SystemRDL/PeakRDL-regblock/actions?query=workflow%3Abuild+branch%3Amain) +[![Coverage Status](https://coveralls.io/repos/github/SystemRDL/PeakRDL-regblock/badge.svg?branch=main)](https://coveralls.io/github/SystemRDL/PeakRDL-regblock?branch=main) +[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/peakrdl-regblock.svg)](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 @@ + + + + + + image/svg+xml + + + + + + + + + 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 @@ + + + + + + image/svg+xml + + + + + + + + + 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 @@ + + + + + + image/svg+xml + + + + + + + + + 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()