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 %}
|
||||||