Initial Commit - Forked from PeakRDL-regblock @ a440cc19769069be831d267505da4f3789a26695
26
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -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.
|
||||
22
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -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.
|
||||
10
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
@@ -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
|
||||
11
.github/pull_request_template.md
vendored
Normal file
@@ -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.
|
||||
178
.github/workflows/build.yml
vendored
Normal file
@@ -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
|
||||
19
.gitignore
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
**/__pycache__
|
||||
**/.vscode
|
||||
**/.venv
|
||||
**/.coverage
|
||||
**/*.rpt
|
||||
**/.pytest_cache
|
||||
**/_build
|
||||
**/*.out
|
||||
**/transcript
|
||||
**/htmlcov
|
||||
**/*.log
|
||||
**/*.pb
|
||||
**/.Xil
|
||||
**/.coverage.*
|
||||
|
||||
build/
|
||||
dist/
|
||||
*.egg-info/
|
||||
.eggs/
|
||||
17
.readthedocs.yaml
Normal file
@@ -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: .
|
||||
53
CONTRIBUTING.md
Normal file
@@ -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.
|
||||
165
LICENSE
Normal file
@@ -0,0 +1,165 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
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.
|
||||
2
MANIFEST.in
Normal file
@@ -0,0 +1,2 @@
|
||||
recursive-include src/peakrdl_regblock *.sv
|
||||
prune tests
|
||||
12
README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
[](http://peakrdl-regblock.readthedocs.io)
|
||||
[](https://github.com/SystemRDL/PeakRDL-regblock/actions?query=workflow%3Abuild+branch%3Amain)
|
||||
[](https://coveralls.io/github/SystemRDL/PeakRDL-regblock?branch=main)
|
||||
[](https://pypi.org/project/peakrdl-regblock)
|
||||
|
||||
# PeakRDL-regblock
|
||||
Compile SystemRDL into a SystemVerilog control/status register (CSR) block.
|
||||
|
||||
For the command line tool, see the [PeakRDL project](https://peakrdl.readthedocs.io).
|
||||
|
||||
## Documentation
|
||||
See the [PeakRDL-regblock Documentation](https://peakrdl-regblock.readthedocs.io) for more details
|
||||
20
docs/Makefile
Normal file
@@ -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)
|
||||
50
docs/api.rst
Normal file
@@ -0,0 +1,50 @@
|
||||
Exporter API
|
||||
============
|
||||
|
||||
If you are not using the `PeakRDL command-line tool <https://peakrdl.readthedocs.io>`_,
|
||||
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
|
||||
)
|
||||
59
docs/architecture.rst
Normal file
@@ -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.
|
||||
89
docs/conf.py
Normal file
@@ -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
|
||||
|
||||
"""
|
||||
45
docs/configuring.rst
Normal file
@@ -0,0 +1,45 @@
|
||||
.. _peakrdl_cfg:
|
||||
|
||||
Configuring PeakRDL-regblock
|
||||
============================
|
||||
|
||||
If using the `PeakRDL command line tool <https://peakrdl.readthedocs.io/>`_,
|
||||
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"
|
||||
59
docs/cpuif/apb.rst
Normal file
@@ -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 <https://developer.arm.com/documentation/ihi0024/b/Introduction/About-the-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 <https://developer.arm.com/documentation/ihi0024/d/?lang=en>`_
|
||||
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`
|
||||
33
docs/cpuif/avalon.rst
Normal file
@@ -0,0 +1,33 @@
|
||||
Intel Avalon
|
||||
============
|
||||
|
||||
Implements the register block using an
|
||||
`Intel Avalon MM <https://www.intel.com/content/www/us/en/docs/programmable/683091/22-3/memory-mapped-interfaces.html>`_
|
||||
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 <https://www.intel.com/content/www/us/en/docs/programmable/683091/22-3/pipelined-transfers.html>`_
|
||||
* 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.
|
||||
32
docs/cpuif/axi4lite.rst
Normal file
@@ -0,0 +1,32 @@
|
||||
.. _cpuif_axi4lite:
|
||||
|
||||
AMBA AXI4-Lite
|
||||
==============
|
||||
|
||||
Implements the register block using an
|
||||
`AMBA AXI4-Lite <https://developer.arm.com/documentation/ihi0022/e/AMBA-AXI4-Lite-Interface-Specification>`_
|
||||
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.
|
||||
114
docs/cpuif/customizing.rst
Normal file
@@ -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 <https://jinja.palletsprojects.com>`_, 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 <https://peakrdl.readthedocs.io>`_.
|
||||
|
||||
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.
|
||||
232
docs/cpuif/internal_protocol.rst
Normal file
@@ -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.
|
||||
36
docs/cpuif/introduction.rst
Normal file
@@ -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.
|
||||
10
docs/cpuif/passthrough.rst
Normal file
@@ -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`.
|
||||
10
docs/dev_notes/Alpha-Beta Versioning
Normal file
@@ -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...
|
||||
67
docs/dev_notes/Hierarchy-and-Indexing
Normal file
@@ -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.
|
||||
23
docs/dev_notes/Program Flow
Normal file
@@ -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
|
||||
11
docs/dev_notes/Resets
Normal file
@@ -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
|
||||
22
docs/dev_notes/Signal Dereferencer
Normal file
@@ -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
|
||||
183
docs/dev_notes/Validation Needed
Normal file
@@ -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??
|
||||
51
docs/dev_notes/template-layers/1-port-declaration
Normal file
@@ -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
|
||||
103
docs/dev_notes/template-layers/1.1.hardware-interface
Normal file
@@ -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
|
||||
72
docs/dev_notes/template-layers/2-CPUIF
Normal file
@@ -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)
|
||||
51
docs/dev_notes/template-layers/3-address-decode
Normal file
@@ -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
|
||||
163
docs/dev_notes/template-layers/4-fields
Normal file
@@ -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(<conditional>) begin
|
||||
<assignments>
|
||||
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:
|
||||
<field>.next = <next value>
|
||||
<field>.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
|
||||
49
docs/dev_notes/template-layers/5-readback-mux
Normal file
@@ -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?
|
||||
9
docs/dev_notes/template-layers/6-output-port-mapping
Normal file
@@ -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
|
||||
BIN
docs/diagrams/arch.png
Normal file
|
After Width: | Height: | Size: 148 KiB |
BIN
docs/diagrams/diagrams.odg
Normal file
BIN
docs/diagrams/rbuf.png
Normal file
|
After Width: | Height: | Size: 122 KiB |
BIN
docs/diagrams/readback.png
Normal file
|
After Width: | Height: | Size: 89 KiB |
BIN
docs/diagrams/wbuf.png
Normal file
|
After Width: | Height: | Size: 123 KiB |
131
docs/faq.rst
Normal file
@@ -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 <https://xyproblem.info>`_)
|
||||
|
||||
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 = <some value>;
|
||||
|
||||
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 = <some value>;
|
||||
assign hwif.my_register.my_field.we = <some control signal>;
|
||||
|
||||
|
||||
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 <https://github.com/lowRISC/style-guides/blob/master/VerilogCodingStyle.md>`_,
|
||||
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 <https://github.com/SystemRDL/PeakRDL-regblock/issues>`_
|
||||
that describes the problem.
|
||||
61
docs/hwif.rst
Normal file
@@ -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.<heir_path>.<feature>``
|
||||
|
||||
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..<feature>``
|
||||
|
||||
|
||||
.. 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.
|
||||
53
docs/img/err.svg
Normal file
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
viewBox="0 0 512 512"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="times-circle.svg"
|
||||
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
|
||||
<metadata
|
||||
id="metadata10">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="983"
|
||||
id="namedview6"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.4609375"
|
||||
inkscape:cx="18.440678"
|
||||
inkscape:cy="245.15254"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) -->
|
||||
<path
|
||||
d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm121.6 313.1c4.7 4.7 4.7 12.3 0 17L338 377.6c-4.7 4.7-12.3 4.7-17 0L256 312l-65.1 65.6c-4.7 4.7-12.3 4.7-17 0L134.4 338c-4.7-4.7-4.7-12.3 0-17l65.6-65-65.6-65.1c-4.7-4.7-4.7-12.3 0-17l39.6-39.6c4.7-4.7 12.3-4.7 17 0l65 65.7 65.1-65.6c4.7-4.7 12.3-4.7 17 0l39.6 39.6c4.7 4.7 4.7 12.3 0 17L312 256l65.6 65.1z"
|
||||
id="path2"
|
||||
style="fill:#b40000;fill-opacity:1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
53
docs/img/ok.svg
Normal file
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
viewBox="0 0 512 512"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="check-circle.svg"
|
||||
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
|
||||
<metadata
|
||||
id="metadata10">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="983"
|
||||
id="namedview6"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.4609375"
|
||||
inkscape:cx="-402.44068"
|
||||
inkscape:cy="247.32203"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) -->
|
||||
<path
|
||||
d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z"
|
||||
id="path2"
|
||||
style="fill:#00b405;fill-opacity:1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
53
docs/img/warn.svg
Normal file
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
viewBox="0 0 576 512"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="exclamation-triangle.svg"
|
||||
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
|
||||
<metadata
|
||||
id="metadata10">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="983"
|
||||
id="namedview6"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.4609375"
|
||||
inkscape:cx="46.101695"
|
||||
inkscape:cy="256"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) -->
|
||||
<path
|
||||
d="M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.467-32.009 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"
|
||||
id="path2"
|
||||
style="fill:#ffa705;fill-opacity:1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
94
docs/index.rst
Normal file
@@ -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 <https://peakrdl.readthedocs.io/>`_:
|
||||
|
||||
.. 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 <https://peakrdl-regblock-vhdl.readthedocs.io>`_
|
||||
|
||||
|
||||
Links
|
||||
-----
|
||||
|
||||
- `Source repository <https://github.com/SystemRDL/PeakRDL-regblock>`_
|
||||
- `Release Notes <https://github.com/SystemRDL/PeakRDL-regblock/releases>`_
|
||||
- `Issue tracker <https://github.com/SystemRDL/PeakRDL-regblock/issues>`_
|
||||
- `PyPi <https://pypi.org/project/peakrdl-regblock>`_
|
||||
- `SystemRDL Specification <http://accellera.org/downloads/standards/systemrdl>`_
|
||||
|
||||
|
||||
.. 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
|
||||
50
docs/licensing.rst
Normal file
@@ -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 <https://github.com/SystemRDL/PeakRDL-regblock/tree/main/hdl-src>`_
|
||||
|
||||
|
||||
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 <https://peakrdl.readthedocs.io/>`_.
|
||||
It may already do everything you need.
|
||||
53
docs/limitations.rst
Normal file
@@ -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
|
||||
28
docs/props/addrmap.rst
Normal file
@@ -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|
|
||||
491
docs/props/field.rst
Normal file
@@ -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": "<swmod>", "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": "<field value>", "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": "<counter>", "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": "<counter>", "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": "<counter>", "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": "<counter>", "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": "<field value>", "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": "<field value>", "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.
|
||||
14
docs/props/reg.rst
Normal file
@@ -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.
|
||||
182
docs/props/rhs_props.rst
Normal file
@@ -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": "<counter>", "wave": "=.=....", "data": [1,0]},
|
||||
{"name": "<decrsaturate>", "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": "<counter>", "wave": "=.=....", "data": [14,15]},
|
||||
{"name": "<incrsaturate>", "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.
|
||||
28
docs/props/signal.rst
Normal file
@@ -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.
|
||||
155
docs/rdl_features/external.rst
Normal file
@@ -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.
|
||||
3
docs/requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
pygments-systemrdl
|
||||
sphinxcontrib-wavedrom
|
||||
sphinx-book-theme
|
||||
49
docs/udps/extended_swacc.rst
Normal file
@@ -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.."}
|
||||
]}
|
||||
103
docs/udps/fixedpoint.rst
Normal file
@@ -0,0 +1,103 @@
|
||||
.. _fixedpoint:
|
||||
|
||||
Fixed-Point Fields
|
||||
==================
|
||||
|
||||
`Fixed-point <https://en.wikipedia.org/wiki/Fixed-point_arithmetic>`_ 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<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.
|
||||
85
docs/udps/intro.rst
Normal file
@@ -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`.
|
||||
164
docs/udps/read_buffering.rst
Normal file
@@ -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.
|
||||
74
docs/udps/signed.rst
Normal file
@@ -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;
|
||||
183
docs/udps/write_buffering.rst
Normal file
@@ -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.
|
||||
9
hdl-src/README.md
Normal file
@@ -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/)
|
||||
40
hdl-src/apb3_intf.sv
Normal file
@@ -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
|
||||
46
hdl-src/apb4_intf.sv
Normal file
@@ -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
|
||||
46
hdl-src/avalon_mm_intf.sv
Normal file
@@ -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
|
||||
80
hdl-src/axi4lite_intf.sv
Normal file
@@ -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
|
||||
54
hdl-src/regblock_udps.rdl
Normal file
@@ -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;
|
||||
};
|
||||
51
pyproject.toml
Normal file
@@ -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"
|
||||
2
src/peakrdl_regblock/__about__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
version_info = (1, 1, 1)
|
||||
__version__ = ".".join([str(n) for n in version_info])
|
||||
3
src/peakrdl_regblock/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .__about__ import __version__
|
||||
|
||||
from .exporter import RegblockExporter
|
||||
205
src/peakrdl_regblock/__peakrdl__.py
Normal file
@@ -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,
|
||||
)
|
||||
219
src/peakrdl_regblock/addr_decode.py
Normal file
@@ -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()
|
||||
1
src/peakrdl_regblock/cpuif/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .base import CpuifBase
|
||||
33
src/peakrdl_regblock/cpuif/apb3/__init__.py
Normal file
@@ -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
|
||||
48
src/peakrdl_regblock/cpuif/apb3/apb3_tmpl.sv
Normal file
@@ -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;
|
||||
35
src/peakrdl_regblock/cpuif/apb4/__init__.py
Normal file
@@ -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
|
||||
51
src/peakrdl_regblock/cpuif/apb4/apb4_tmpl.sv
Normal file
@@ -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;
|
||||
40
src/peakrdl_regblock/cpuif/avalon/__init__.py
Normal file
@@ -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
|
||||
41
src/peakrdl_regblock/cpuif/avalon/avalon_tmpl.sv
Normal file
@@ -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
|
||||
70
src/peakrdl_regblock/cpuif/axi4lite/__init__.py
Normal file
@@ -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
|
||||
254
src/peakrdl_regblock/cpuif/axi4lite/axi4lite_tmpl.sv
Normal file
@@ -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 %}
|
||||
76
src/peakrdl_regblock/cpuif/base.py
Normal file
@@ -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)
|
||||
22
src/peakrdl_regblock/cpuif/passthrough/__init__.py
Normal file
@@ -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)
|
||||
12
src/peakrdl_regblock/cpuif/passthrough/passthrough_tmpl.sv
Normal file
@@ -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;
|
||||
264
src/peakrdl_regblock/dereferencer.py
Normal file
@@ -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)"
|
||||
288
src/peakrdl_regblock/exporter.py
Normal file
@@ -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
|
||||
54
src/peakrdl_regblock/external_acks.py
Normal file
@@ -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
|
||||
501
src/peakrdl_regblock/field_logic/__init__.py
Normal file
@@ -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
|
||||
114
src/peakrdl_regblock/field_logic/bases.py
Normal file
@@ -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(<conditional>) begin
|
||||
<assignments>
|
||||
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:
|
||||
<field>.next = <next value>
|
||||
<field>.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 = ""
|
||||
393
src/peakrdl_regblock/field_logic/generators.py
Normal file
@@ -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))
|
||||
162
src/peakrdl_regblock/field_logic/hw_interrupts.py
Normal file
@@ -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;",
|
||||
]
|
||||
187
src/peakrdl_regblock/field_logic/hw_interrupts_with_write.py
Normal file
@@ -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)
|
||||
72
src/peakrdl_regblock/field_logic/hw_set_clr.py
Normal file
@@ -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;",
|
||||
]
|
||||
95
src/peakrdl_regblock/field_logic/hw_write.py
Normal file
@@ -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}"
|
||||
45
src/peakrdl_regblock/field_logic/sw_onread.py
Normal file
@@ -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;",
|
||||
]
|
||||
129
src/peakrdl_regblock/field_logic/sw_onwrite.py
Normal file
@@ -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})"
|
||||
19
src/peakrdl_regblock/field_logic/sw_singlepulse.py
Normal file
@@ -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;",
|
||||
]
|
||||
48
src/peakrdl_regblock/field_logic/templates/counter_macros.sv
Normal file
@@ -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 %}
|
||||
31
src/peakrdl_regblock/field_logic/templates/external_block.sv
Normal file
@@ -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 %}
|
||||