├── .github ├── verible-lint-matcher.json └── workflows │ └── gitlab-ci.yml ├── .gitignore ├── .gitlab-ci.yml ├── Bender.yml ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── Makefile.venv ├── README.md ├── gpio_regs.hjson ├── hal └── gpio_hal.h ├── requirements.txt ├── src ├── gpio.sv ├── gpio_apb_wrap.sv ├── gpio_axi_lite_wrap.sv ├── gpio_input_stage.sv ├── gpio_input_stage_no_clk_gates.sv ├── gpio_reg_pkg.sv └── gpio_reg_top.sv ├── test └── tb_gpio.sv └── util └── reggen ├── reggen ├── README.md ├── __init__.py ├── access.py ├── alert.py ├── bits.py ├── bus_interfaces.py ├── enum_entry.py ├── field.py ├── fpv_csr.sv.tpl ├── gen_cfg_html.py ├── gen_cheader.py ├── gen_dv.py ├── gen_fpv.py ├── gen_html.py ├── gen_json.py ├── gen_rtl.py ├── gen_selfdoc.py ├── html_helpers.py ├── inter_signal.py ├── ip_block.py ├── lib.py ├── multi_register.py ├── params.py ├── reg_base.py ├── reg_block.py ├── reg_html.css ├── reg_pkg.sv.tpl ├── reg_top.sv.tpl ├── register.py ├── signal.py ├── uvm_reg.sv.tpl ├── uvm_reg_base.sv.tpl ├── validate.py ├── version.py └── window.py ├── regtool.py └── topgen ├── __init__.py ├── c.py ├── gen_dv.py ├── intermodule.py ├── lib.py ├── merge.py ├── templates ├── README.md ├── chip_env_pkg__params.sv.tpl ├── chiplevel.sv.tpl ├── clang-format ├── tb__alert_handler_connect.sv.tpl ├── tb__xbar_connect.sv.tpl ├── toplevel.c.tpl ├── toplevel.h.tpl ├── toplevel.sv.tpl ├── toplevel_memory.h.tpl ├── toplevel_memory.ld.tpl ├── toplevel_pkg.sv.tpl ├── toplevel_rnd_cnst_pkg.sv.tpl └── xbar_env_pkg__params.sv.tpl ├── top.py ├── top_uvm_reg.sv.tpl └── validate.py /.github/verible-lint-matcher.json: -------------------------------------------------------------------------------- 1 | { 2 | "problemMatcher": [ 3 | { 4 | "owner": "verible-lint-matcher", 5 | "pattern": [ 6 | { 7 | "regexp": "^(.+):(\\d+):(\\d+):\\s(.+)$", 8 | "file": 1, 9 | "line": 2, 10 | "column": 3, 11 | "message": 4 12 | } 13 | ] 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 ETH Zurich and University of Bologna. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | # Based on https://github.com/pulp-platform/pulp-actions/tree/main/gitlab-ci#action-usage 6 | 7 | # Author: Nils Wistoff 8 | 9 | name: gitlab-ci 10 | 11 | on: [ push, pull_request, workflow_dispatch ] 12 | 13 | jobs: 14 | gitlab-ci: 15 | runs-on: ubuntu-latest 16 | if: github.repository == 'pulp-platform/gpio' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) 17 | steps: 18 | - name: Check Gitlab CI 19 | uses: pulp-platform/pulp-actions/gitlab-ci@v1 20 | with: 21 | domain: iis-git.ee.ethz.ch 22 | repo: github-mirror/gpio 23 | token: ${{ secrets.GITLAB_TOKEN }} 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/python 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=python 3 | 4 | ### Python ### 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | cover/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | db.sqlite3 66 | db.sqlite3-journal 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | 78 | # PyBuilder 79 | .pybuilder/ 80 | target/ 81 | 82 | # Jupyter Notebook 83 | .ipynb_checkpoints 84 | 85 | # IPython 86 | profile_default/ 87 | ipython_config.py 88 | 89 | # pyenv 90 | # For a library or package, you might want to ignore these files since the code is 91 | # intended to run in multiple environments; otherwise, check them in: 92 | # .python-version 93 | 94 | # pipenv 95 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 96 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 97 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 98 | # install all needed dependencies. 99 | #Pipfile.lock 100 | 101 | # poetry 102 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 103 | # This is especially recommended for binary packages to ensure reproducibility, and is more 104 | # commonly ignored for libraries. 105 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 106 | #poetry.lock 107 | 108 | # pdm 109 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 110 | #pdm.lock 111 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 112 | # in version control. 113 | # https://pdm.fming.dev/#use-with-ide 114 | .pdm.toml 115 | 116 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 117 | __pypackages__/ 118 | 119 | # Celery stuff 120 | celerybeat-schedule 121 | celerybeat.pid 122 | 123 | # SageMath parsed files 124 | *.sage.py 125 | 126 | # Environments 127 | .env 128 | .venv 129 | env/ 130 | venv/ 131 | ENV/ 132 | env.bak/ 133 | venv.bak/ 134 | 135 | # Spyder project settings 136 | .spyderproject 137 | .spyproject 138 | 139 | # Rope project settings 140 | .ropeproject 141 | 142 | # mkdocs documentation 143 | /site 144 | 145 | # mypy 146 | .mypy_cache/ 147 | .dmypy.json 148 | dmypy.json 149 | 150 | # Pyre type checker 151 | .pyre/ 152 | 153 | # pytype static type analyzer 154 | .pytype/ 155 | 156 | # Cython debug symbols 157 | cython_debug/ 158 | 159 | # PyCharm 160 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 161 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 162 | # and can be added to the global gitignore or merged into this file. For a more nuclear 163 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 164 | #.idea/ 165 | 166 | ### Python Patch ### 167 | # Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration 168 | poetry.toml 169 | 170 | 171 | # End of https://www.toptal.com/developers/gitignore/api/python -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 ETH Zurich and University of Bologna. 2 | # Solderpad Hardware License, Version 0.51, see LICENSE for details. 3 | # SPDX-License-Identifier: SHL-0.51 4 | 5 | 6 | variables: 7 | VSIM: questa-2022.3 vsim -64 8 | VLIB: questa-2022.3 vlib 9 | VMAP: questa-2022.3 vmap 10 | VCOM: questa-2022.3 vcom -64 11 | VLOG: questa-2022.3 vlog -64 12 | VOPT: questa-2022.3 vopt -64 13 | 14 | stages: 15 | - test 16 | 17 | sim: 18 | stage: test 19 | timeout: 5min 20 | script: 21 | - bender script vsim -t test > compile.tcl 22 | - $VSIM -c -do 'exit -code [source compile.tcl]' 23 | - $VSIM -c tb_gpio -do "run -all" 24 | - (! grep -n "Error:" transcript) 25 | -------------------------------------------------------------------------------- /Bender.yml: -------------------------------------------------------------------------------- 1 | package: 2 | name: gpio 3 | authors: 4 | - "Manuel Eggimann " 5 | 6 | dependencies: 7 | tech_cells_generic: { git: "https://github.com/pulp-platform/tech_cells_generic.git", version: 0.2.9 } 8 | common_cells: { git: "https://github.com/pulp-platform/common_cells.git", version: 1.21.0 } 9 | common_verification: { git: "https://github.com/pulp-platform/common_verification.git", version: 0.2.0} 10 | register_interface: { git: "https://github.com/pulp-platform/register_interface.git", version: 0.4.1} 11 | apb: { git: "https://github.com/pulp-platform/apb.git", version: 0.2.4 } # To be udpated once PR #6 got merged. 12 | axi: { git: "https://github.com/pulp-platform/axi.git", version: 0.39.0 } 13 | 14 | sources: 15 | - src/gpio_reg_pkg.sv 16 | - src/gpio_reg_top.sv 17 | - src/gpio.sv 18 | - src/gpio_axi_lite_wrap.sv 19 | - src/gpio_apb_wrap.sv 20 | - target: all(any(test, gpio_include_tb_files), not(gpio_exclude_tb_files)) 21 | files: 22 | - test/tb_gpio.sv 23 | - target: all(any(simulation, asic, gpio_with_clk_gates), not(gpio_no_clk_gates)) 24 | files: 25 | - src/gpio_input_stage.sv 26 | - target: all(any(fpga, gpio_no_clk_gates), not(gpio_with_clk_gates)) 27 | files: 28 | - src/gpio_input_stage_no_clk_gates.sv 29 | vendor_package: 30 | - name: reggen 31 | target_dir: "util" 32 | upstream: { git: "https://github.com/pulp-platform/register_interface.git", rev: "fe3cc459f02a75efed697cf0d6cb73df27763dbe"} 33 | mapping: 34 | - { from: 'vendor/lowrisc_opentitan/util', to: 'reggen'} 35 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 6 | 7 | ## 0.2.2 - 2023-06-12 8 | ### Added 9 | - Added simple CI 10 | ### Changed 11 | - Bump bender dependencies 12 | ### Fixed 13 | - Fix typos and errors in documentation 14 | 15 | ## 0.2.1 - 2022-12-15 16 | ### Changed 17 | - Simplified reconfiguration flow by using vendored-in regtool.py 18 | ### Fixed 19 | - Overlapping bitfield definition in hjson description of the CFG register. 20 | - Remove now obsolete Makefile dependency on bender checkout dir 21 | 22 | ## 0.2.0 - 2022-12-09 23 | ### Breaking Changes 24 | - Changed the module interface. ``interrupt_o`` was renamed to 25 | ``global_interrupt_o``. Additionally, the module also exposes pin level 26 | interrupt signals in addition to the single, globally multiplexed gpio 27 | interrupt signal. Existing RTL integrating this IP need to adapt the port 28 | list of their instantiations. 29 | ### Changed 30 | - Change default pad count from 56 to 32. 31 | - Use the clock gated input stage by default for simulation targets 32 | - Bump AXI Version 33 | ### Fixed 34 | - Fix warning about unconnected interface port 35 | 36 | ## 0.1.2 - 2022-12-04 37 | ### Changed 38 | - Added make dependencies to auto-setup python env for reconfiguration 39 | 40 | ### Fixed 41 | - Fix some small issues reported by linter 42 | 43 | ## 0.1.1 - 2022-10-07 44 | ### Changed 45 | - Bumped AXI version to v0.35.3 46 | - Added NumRepetitions to tb_gpio to choose test duration 47 | - Refactored TB 48 | 49 | ### Fixed 50 | - Fix tx_en inversion bug for open-drain mode 1 51 | - Fix bug in TB that caused open-drain misbehavior not to be catched 52 | 53 | 54 | ## 0.1.0 - 2022-04-14 55 | Initial release 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | SOLDERPAD HARDWARE LICENSE version 0.51 2 | 3 | This license is based closely on the Apache License Version 2.0, but is not 4 | approved or endorsed by the Apache Foundation. A copy of the non-modified 5 | Apache License 2.0 can be found at http://www.apache.org/licenses/LICENSE-2.0. 6 | 7 | As this license is not currently OSI or FSF approved, the Licensor permits any 8 | Work licensed under this License, at the option of the Licensee, to be treated 9 | as licensed under the Apache License Version 2.0 (which is so approved). 10 | 11 | This License is licensed under the terms of this License and in particular 12 | clause 7 below (Disclaimer of Warranties) applies in relation to its use. 13 | 14 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 15 | 16 | 1. Definitions. 17 | 18 | "License" shall mean the terms and conditions for use, reproduction, and 19 | distribution as defined by Sections 1 through 9 of this document. 20 | 21 | "Licensor" shall mean the Rights owner or entity authorized by the Rights owner 22 | that is granting the License. 23 | 24 | "Legal Entity" shall mean the union of the acting entity and all other entities 25 | that control, are controlled by, or are under common control with that entity. 26 | For the purposes of this definition, "control" means (i) the power, direct or 27 | indirect, to cause the direction or management of such entity, whether by 28 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 29 | outstanding shares, or (iii) beneficial ownership of such entity. 30 | 31 | "You" (or "Your") shall mean an individual or Legal Entity exercising 32 | permissions granted by this License. 33 | 34 | "Rights" means copyright and any similar right including design right (whether 35 | registered or unregistered), semiconductor topography (mask) rights and 36 | database rights (but excluding Patents and Trademarks). 37 | 38 | "Source" form shall mean the preferred form for making modifications, including 39 | but not limited to source code, net lists, board layouts, CAD files, 40 | documentation source, and configuration files. 41 | 42 | "Object" form shall mean any form resulting from mechanical transformation or 43 | translation of a Source form, including but not limited to compiled object 44 | code, generated documentation, the instantiation of a hardware design and 45 | conversions to other media types, including intermediate forms such as 46 | bytecodes, FPGA bitstreams, artwork and semiconductor topographies (mask 47 | works). 48 | 49 | "Work" shall mean the work of authorship, whether in Source form or other 50 | Object form, made available under the License, as indicated by a Rights notice 51 | that is included in or attached to the work (an example is provided in the 52 | Appendix below). 53 | 54 | "Derivative Works" shall mean any work, whether in Source or Object form, that 55 | is based on (or derived from) the Work and for which the editorial revisions, 56 | annotations, elaborations, or other modifications represent, as a whole, an 57 | original work of authorship. For the purposes of this License, Derivative Works 58 | shall not include works that remain separable from, or merely link (or bind by 59 | name) or physically connect to or interoperate with the interfaces of, the Work 60 | and Derivative Works thereof. 61 | 62 | "Contribution" shall mean any design or work of authorship, including the 63 | original version of the Work and any modifications or additions to that Work or 64 | Derivative Works thereof, that is intentionally submitted to Licensor for 65 | inclusion in the Work by the Rights owner or by an individual or Legal Entity 66 | authorized to submit on behalf of the Rights owner. For the purposes of this 67 | definition, "submitted" means any form of electronic, verbal, or written 68 | communication sent to the Licensor or its representatives, including but not 69 | limited to communication on electronic mailing lists, source code control 70 | systems, and issue tracking systems that are managed by, or on behalf of, the 71 | Licensor for the purpose of discussing and improving the Work, but excluding 72 | communication that is conspicuously marked or otherwise designated in writing 73 | by the Rights owner as "Not a Contribution." 74 | 75 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 76 | of whom a Contribution has been received by Licensor and subsequently 77 | incorporated within the Work. 78 | 79 | 2. Grant of License. Subject to the terms and conditions of this License, each 80 | Contributor hereby grants to You a perpetual, worldwide, non-exclusive, 81 | no-charge, royalty-free, irrevocable license under the Rights to reproduce, 82 | prepare Derivative Works of, publicly display, publicly perform, sublicense, 83 | and distribute the Work and such Derivative Works in Source or Object form and 84 | do anything in relation to the Work as if the Rights did not exist. 85 | 86 | 3. Grant of Patent License. Subject to the terms and conditions of this 87 | License, each Contributor hereby grants to You a perpetual, worldwide, 88 | non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this 89 | section) patent license to make, have made, use, offer to sell, sell, import, 90 | and otherwise transfer the Work, where such license applies only to those 91 | patent claims licensable by such Contributor that are necessarily infringed by 92 | their Contribution(s) alone or by combination of their Contribution(s) with the 93 | Work to which such Contribution(s) was submitted. If You institute patent 94 | litigation against any entity (including a cross-claim or counterclaim in a 95 | lawsuit) alleging that the Work or a Contribution incorporated within the Work 96 | constitutes direct or contributory patent infringement, then any patent 97 | licenses granted to You under this License for that Work shall terminate as of 98 | the date such litigation is filed. 99 | 100 | 4. Redistribution. You may reproduce and distribute copies of the Work or 101 | Derivative Works thereof in any medium, with or without modifications, and in 102 | Source or Object form, provided that You meet the following conditions: 103 | 104 | You must give any other recipients of the Work or Derivative Works a copy 105 | of this License; and 106 | 107 | You must cause any modified files to carry prominent notices stating that 108 | You changed the files; and 109 | 110 | You must retain, in the Source form of any Derivative Works that You 111 | distribute, all copyright, patent, trademark, and attribution notices from 112 | the Source form of the Work, excluding those notices that do not pertain to 113 | any part of the Derivative Works; and 114 | 115 | If the Work includes a "NOTICE" text file as part of its distribution, then 116 | any Derivative Works that You distribute must include a readable copy of 117 | the attribution notices contained within such NOTICE file, excluding those 118 | notices that do not pertain to any part of the Derivative Works, in at 119 | least one of the following places: within a NOTICE text file distributed as 120 | part of the Derivative Works; within the Source form or documentation, if 121 | provided along with the Derivative Works; or, within a display generated by 122 | the Derivative Works, if and wherever such third-party notices normally 123 | appear. The contents of the NOTICE file are for informational purposes only 124 | and do not modify the License. You may add Your own attribution notices 125 | within Derivative Works that You distribute, alongside or as an addendum to 126 | the NOTICE text from the Work, provided that such additional attribution 127 | notices cannot be construed as modifying the License. You may add Your own 128 | copyright statement to Your modifications and may provide additional or 129 | different license terms and conditions for use, reproduction, or 130 | distribution of Your modifications, or for any such Derivative Works as a 131 | whole, provided Your use, reproduction, and distribution of the Work 132 | otherwise complies with the conditions stated in this License. 133 | 134 | 5. Submission of Contributions. Unless You explicitly state otherwise, any 135 | Contribution intentionally submitted for inclusion in the Work by You to the 136 | Licensor shall be under the terms and conditions of this License, without any 137 | additional terms or conditions. Notwithstanding the above, nothing herein shall 138 | supersede or modify the terms of any separate license agreement you may have 139 | executed with Licensor regarding such Contributions. 140 | 141 | 6. Trademarks. This License does not grant permission to use the trade names, 142 | trademarks, service marks, or product names of the Licensor, except as required 143 | for reasonable and customary use in describing the origin of the Work and 144 | reproducing the content of the NOTICE file. 145 | 146 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in 147 | writing, Licensor provides the Work (and each Contributor provides its 148 | Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 149 | KIND, either express or implied, including, without limitation, any warranties 150 | or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 151 | PARTICULAR PURPOSE. You are solely responsible for determining the 152 | appropriateness of using or redistributing the Work and assume any risks 153 | associated with Your exercise of permissions under this License. 154 | 155 | 8. Limitation of Liability. In no event and under no legal theory, whether in 156 | tort (including negligence), contract, or otherwise, unless required by 157 | applicable law (such as deliberate and grossly negligent acts) or agreed to in 158 | writing, shall any Contributor be liable to You for damages, including any 159 | direct, indirect, special, incidental, or consequential damages of any 160 | character arising as a result of this License or out of the use or inability to 161 | use the Work (including but not limited to damages for loss of goodwill, work 162 | stoppage, computer failure or malfunction, or any and all other commercial 163 | damages or losses), even if such Contributor has been advised of the 164 | possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or 167 | Derivative Works thereof, You may choose to offer, and charge a fee for, 168 | acceptance of support, warranty, indemnity, or other liability obligations 169 | and/or rights consistent with this License. However, in accepting such 170 | obligations, You may act only on Your own behalf and on Your sole 171 | responsibility, not on behalf of any other Contributor, and only if You agree 172 | to indemnify, defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason of your 174 | accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := help 2 | 3 | VENVDIR?=$(WORKDIR)/.venv 4 | REQUIREMENTS_TXT?=$(wildcard requirements.txt) 5 | include Makefile.venv 6 | 7 | GPIOS ?= 32 8 | 9 | ## Regenerate the register file and HAL C-header for a different GPIO count. Usage: make reconfigure GPIOS=128 10 | reconfigure: | venv 11 | @echo Reconfiguring IP to use $(GPIOS) gpios... 12 | @sed -i -r 's/default: "[0-9]+"/default: "${GPIOS}"/g' gpio_regs.hjson 13 | ifeq ($(shell expr $(GPIOS) \<= 16), 1) 14 | @sed -i -r 's|(//)?`define ENABLE_LESS_THAN_16_GPIOS_REG_PKG_WORKAROUND|`define ENABLE_LESS_THAN_16_GPIOS_REG_PKG_WORKAROUND|g' test/tb_gpio.sv 15 | else 16 | @sed -i -r 's|(//)?`define ENABLE_LESS_THAN_16_GPIOS_REG_PKG_WORKAROUND|//`define ENABLE_LESS_THAN_16_GPIOS_REG_PKG_WORKAROUND|g' test/tb_gpio.sv 17 | endif 18 | ifeq ($(shell expr $(GPIOS) \<= 32), 1) 19 | @sed -i -r 's|(//)?`define ENABLE_LESS_THAN_32_GPIOS_REG_PKG_WORKAROUND|`define ENABLE_LESS_THAN_32_GPIOS_REG_PKG_WORKAROUND|g' test/tb_gpio.sv 20 | else 21 | @sed -i -r 's|(//)?`define ENABLE_LESS_THAN_32_GPIOS_REG_PKG_WORKAROUND|//`define ENABLE_LESS_THAN_32_GPIOS_REG_PKG_WORKAROUND|g' test/tb_gpio.sv 22 | endif 23 | $(VENV)/python util/reggen/regtool.py gpio_regs.hjson -r -t src -p GPIOCount=${GPIOS} 24 | $(VENV)/python util/reggen/regtool.py gpio_regs.hjson --cdefines -o hal/gpio_hal.h -p GPIOCount=${GPIOS}; 25 | @echo "Done" 26 | 27 | .PHONY: help 28 | help: Makefile 29 | @printf "GPIO Reconfiguration\n" 30 | @printf "Use this Makefile to regenerate the register file and HAL C-header for a different number GPIOs than the default one.\n\n" 31 | @printf "Usage: \n" 32 | @printf "make reconfigure GPIOS=\n\n" 33 | -------------------------------------------------------------------------------- /Makefile.venv: -------------------------------------------------------------------------------- 1 | # 2 | # SEAMLESSLY MANAGE PYTHON VIRTUAL ENVIRONMENT WITH A MAKEFILE 3 | # 4 | # https://github.com/sio/Makefile.venv v2022.07.20 5 | # 6 | # 7 | # Insert `include Makefile.venv` at the bottom of your Makefile to enable these 8 | # rules. 9 | # 10 | # When writing your Makefile use '$(VENV)/python' to refer to the Python 11 | # interpreter within virtual environment and '$(VENV)/executablename' for any 12 | # other executable in venv. 13 | # 14 | # This Makefile provides the following targets: 15 | # venv 16 | # Use this as a dependency for any target that requires virtual 17 | # environment to be created and configured 18 | # python, ipython 19 | # Use these to launch interactive Python shell within virtual environment 20 | # shell, bash, zsh 21 | # Launch interactive command line shell. "shell" target launches the 22 | # default shell Makefile executes its rules in (usually /bin/sh). 23 | # "bash" and "zsh" can be used to refer to the specific desired shell. 24 | # show-venv 25 | # Show versions of Python and pip, and the path to the virtual environment 26 | # clean-venv 27 | # Remove virtual environment 28 | # $(VENV)/executable_name 29 | # Install `executable_name` with pip. Only packages with names matching 30 | # the name of the corresponding executable are supported. 31 | # Use this as a lightweight mechanism for development dependencies 32 | # tracking. E.g. for one-off tools that are not required in every 33 | # developer's environment, therefore are not included into 34 | # requirements.txt or setup.py. 35 | # Note: 36 | # Rules using such target or dependency MUST be defined below 37 | # `include` directive to make use of correct $(VENV) value. 38 | # Example: 39 | # codestyle: $(VENV)/pyflakes 40 | # $(VENV)/pyflakes . 41 | # See `ipython` target below for another example. 42 | # 43 | # This Makefile can be configured via following variables: 44 | # PY 45 | # Command name for system Python interpreter. It is used only initially to 46 | # create the virtual environment 47 | # Default: python3 48 | # REQUIREMENTS_TXT 49 | # Space separated list of paths to requirements.txt files. 50 | # Paths are resolved relative to current working directory. 51 | # Default: requirements.txt 52 | # 53 | # Non-existent files are treated as hard dependencies, 54 | # recipes for creating such files must be provided by the main Makefile. 55 | # Providing empty value (REQUIREMENTS_TXT=) turns off processing of 56 | # requirements.txt even when the file exists. 57 | # SETUP_PY 58 | # Space separated list of paths to setup.py files. 59 | # Corresponding packages will be installed into venv in editable mode 60 | # along with all their dependencies 61 | # Default: setup.py 62 | # 63 | # Non-existent and empty values are treated in the same way as for REQUIREMENTS_TXT. 64 | # WORKDIR 65 | # Parent directory for the virtual environment. 66 | # Default: current working directory. 67 | # VENVDIR 68 | # Python virtual environment directory. 69 | # Default: $(WORKDIR)/.venv 70 | # 71 | # This Makefile was written for GNU Make and may not work with other make 72 | # implementations. 73 | # 74 | # 75 | # Copyright (c) 2019-2020 Vitaly Potyarkin 76 | # 77 | # Licensed under the Apache License, Version 2.0 78 | # 79 | # 80 | 81 | 82 | # 83 | # Configuration variables 84 | # 85 | 86 | WORKDIR?=. 87 | VENVDIR?=$(WORKDIR)/.venv 88 | REQUIREMENTS_TXT?=$(wildcard requirements.txt) # Multiple paths are supported (space separated) 89 | SETUP_PY?=$(wildcard setup.py) # Multiple paths are supported (space separated) 90 | SETUP_CFG?=$(foreach s,$(SETUP_PY),$(wildcard $(patsubst %setup.py,%setup.cfg,$(s)))) 91 | MARKER=.initialized-with-Makefile.venv 92 | 93 | 94 | # 95 | # Python interpreter detection 96 | # 97 | 98 | _PY_AUTODETECT_MSG=Detected Python interpreter: $(PY). Use PY environment variable to override 99 | 100 | ifeq (ok,$(shell test -e /dev/null 2>&1 && echo ok)) 101 | NULL_STDERR=2>/dev/null 102 | else 103 | NULL_STDERR=2>NUL 104 | endif 105 | 106 | ifndef PY 107 | _PY_OPTION:=python3 108 | ifeq (ok,$(shell $(_PY_OPTION) -c "print('ok')" $(NULL_STDERR))) 109 | PY=$(_PY_OPTION) 110 | endif 111 | endif 112 | 113 | ifndef PY 114 | _PY_OPTION:=$(VENVDIR)/bin/python 115 | ifeq (ok,$(shell $(_PY_OPTION) -c "print('ok')" $(NULL_STDERR))) 116 | PY=$(_PY_OPTION) 117 | $(info $(_PY_AUTODETECT_MSG)) 118 | endif 119 | endif 120 | 121 | ifndef PY 122 | _PY_OPTION:=$(subst /,\,$(VENVDIR)/Scripts/python) 123 | ifeq (ok,$(shell $(_PY_OPTION) -c "print('ok')" $(NULL_STDERR))) 124 | PY=$(_PY_OPTION) 125 | $(info $(_PY_AUTODETECT_MSG)) 126 | endif 127 | endif 128 | 129 | ifndef PY 130 | _PY_OPTION:=py -3 131 | ifeq (ok,$(shell $(_PY_OPTION) -c "print('ok')" $(NULL_STDERR))) 132 | PY=$(_PY_OPTION) 133 | $(info $(_PY_AUTODETECT_MSG)) 134 | endif 135 | endif 136 | 137 | ifndef PY 138 | _PY_OPTION:=python 139 | ifeq (ok,$(shell $(_PY_OPTION) -c "print('ok')" $(NULL_STDERR))) 140 | PY=$(_PY_OPTION) 141 | $(info $(_PY_AUTODETECT_MSG)) 142 | endif 143 | endif 144 | 145 | ifndef PY 146 | define _PY_AUTODETECT_ERR 147 | Could not detect Python interpreter automatically. 148 | Please specify path to interpreter via PY environment variable. 149 | endef 150 | $(error $(_PY_AUTODETECT_ERR)) 151 | endif 152 | 153 | 154 | # 155 | # Internal variable resolution 156 | # 157 | 158 | VENV=$(VENVDIR)/bin 159 | EXE= 160 | # Detect windows 161 | ifeq (win32,$(shell $(PY) -c "import __future__, sys; print(sys.platform)")) 162 | VENV=$(VENVDIR)/Scripts 163 | EXE=.exe 164 | endif 165 | 166 | touch=touch $(1) 167 | ifeq (,$(shell command -v touch $(NULL_STDERR))) 168 | # https://ss64.com/nt/touch.html 169 | touch=type nul >> $(subst /,\,$(1)) && copy /y /b $(subst /,\,$(1))+,, $(subst /,\,$(1)) 170 | endif 171 | 172 | RM?=rm -f 173 | ifeq (,$(shell command -v $(firstword $(RM)) $(NULL_STDERR))) 174 | RMDIR:=rd /s /q 175 | else 176 | RMDIR:=$(RM) -r 177 | endif 178 | 179 | 180 | # 181 | # Virtual environment 182 | # 183 | 184 | .PHONY: venv 185 | venv: $(VENV)/$(MARKER) 186 | 187 | .PHONY: clean-venv 188 | clean-venv: 189 | -$(RMDIR) "$(VENVDIR)" 190 | 191 | .PHONY: show-venv 192 | show-venv: venv 193 | @$(VENV)/python -c "import sys; print('Python ' + sys.version.replace('\n',''))" 194 | @$(VENV)/pip --version 195 | @echo venv: $(VENVDIR) 196 | 197 | .PHONY: debug-venv 198 | debug-venv: 199 | @echo "PATH (Shell)=$$PATH" 200 | @$(MAKE) --version 201 | $(info PATH (GNU Make)="$(PATH)") 202 | $(info SHELL="$(SHELL)") 203 | $(info PY="$(PY)") 204 | $(info REQUIREMENTS_TXT="$(REQUIREMENTS_TXT)") 205 | $(info SETUP_PY="$(SETUP_PY)") 206 | $(info SETUP_CFG="$(SETUP_CFG)") 207 | $(info VENVDIR="$(VENVDIR)") 208 | $(info VENVDEPENDS="$(VENVDEPENDS)") 209 | $(info WORKDIR="$(WORKDIR)") 210 | 211 | 212 | # 213 | # Dependencies 214 | # 215 | 216 | ifneq ($(strip $(REQUIREMENTS_TXT)),) 217 | VENVDEPENDS+=$(REQUIREMENTS_TXT) 218 | endif 219 | 220 | ifneq ($(strip $(SETUP_PY)),) 221 | VENVDEPENDS+=$(SETUP_PY) 222 | endif 223 | ifneq ($(strip $(SETUP_CFG)),) 224 | VENVDEPENDS+=$(SETUP_CFG) 225 | endif 226 | 227 | $(VENV): 228 | $(PY) -m venv $(VENVDIR) 229 | $(VENV)/python -m pip install --upgrade pip setuptools wheel 230 | 231 | $(VENV)/$(MARKER): $(VENVDEPENDS) | $(VENV) 232 | ifneq ($(strip $(REQUIREMENTS_TXT)),) 233 | $(VENV)/pip install $(foreach path,$(REQUIREMENTS_TXT),-r $(path)) 234 | endif 235 | ifneq ($(strip $(SETUP_PY)),) 236 | $(VENV)/pip install $(foreach path,$(SETUP_PY),-e $(dir $(path))) 237 | endif 238 | $(call touch,$(VENV)/$(MARKER)) 239 | 240 | 241 | # 242 | # Interactive shells 243 | # 244 | 245 | .PHONY: python 246 | python: venv 247 | exec $(VENV)/python 248 | 249 | .PHONY: ipython 250 | ipython: $(VENV)/ipython 251 | exec $(VENV)/ipython 252 | 253 | .PHONY: shell 254 | shell: venv 255 | . $(VENV)/activate && exec $(notdir $(SHELL)) 256 | 257 | .PHONY: bash zsh 258 | bash zsh: venv 259 | . $(VENV)/activate && exec $@ 260 | 261 | 262 | # 263 | # Commandline tools (wildcard rule, executable name must match package name) 264 | # 265 | 266 | ifneq ($(EXE),) 267 | $(VENV)/%: $(VENV)/%$(EXE) ; 268 | .PHONY: $(VENV)/% 269 | .PRECIOUS: $(VENV)/%$(EXE) 270 | endif 271 | 272 | $(VENV)/%$(EXE): $(VENV)/$(MARKER) 273 | $(VENV)/pip install --upgrade $* 274 | $(call touch,$@) 275 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | hjson==3.1.0 2 | Mako==1.2.4 3 | MarkupSafe==2.1.1 4 | PyYAML==6.0 5 | -------------------------------------------------------------------------------- /src/gpio_apb_wrap.sv: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Title : GPIO APB Wrapper 3 | //----------------------------------------------------------------------------- 4 | // File : gpio_apb_wrap.sv 5 | // Author : Manuel Eggimann 6 | // Created : 06.05.2021 7 | //----------------------------------------------------------------------------- 8 | // Description : 9 | // This file provides wrappers around the GPIO peripheral with an APB 10 | // interface. The file contains two versions of the module, one structs for the 11 | // APB interface and one using SystemVerilog Interfaces. 12 | //----------------------------------------------------------------------------- 13 | // Copyright (C) 2013-2021 ETH Zurich, University of Bologna 14 | // Copyright and related rights are licensed under the Solderpad Hardware 15 | // License, Version 0.51 (the "License"); you may not use this file except in 16 | // compliance with the License. You may obtain a copy of the License at 17 | // http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law 18 | // or agreed to in writing, software, hardware and materials distributed under 19 | // this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 20 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 21 | // specific language governing permissions and limitations under the License. 22 | //----------------------------------------------------------------------------- 23 | `include "apb/typedef.svh" 24 | `include "apb/assign.svh" 25 | 26 | module gpio_apb_wrap # ( 27 | /// ADDR_WIDTH of the APB interface 28 | parameter int unsigned ADDR_WIDTH = 32, 29 | /// DATA_WIDTH of the APB interface 30 | parameter int unsigned DATA_WIDTH = 32, 31 | /// APB request struct type. 32 | parameter type apb_req_t = logic, 33 | /// APB response struct type. 34 | parameter type apb_rsp_t = logic, 35 | localparam int unsigned NrGPIOs = gpio_reg_pkg::GPIOCount, 36 | localparam int unsigned STRB_WIDTH = DATA_WIDTH/8 37 | )( 38 | input logic clk_i, 39 | input logic rst_ni, 40 | input logic [NrGPIOs-1:0] gpio_in, 41 | output logic [NrGPIOs-1:0] gpio_out, 42 | output logic [NrGPIOs-1:0] gpio_tx_en_o, // 0 -> input, 1 -> output 43 | output logic [NrGPIOs-1:0] gpio_in_sync_o, // sampled and synchronized GPIO 44 | // input. 45 | output logic global_interrupt_o, 46 | output logic [NrGPIOs-1:0] pin_level_interrupts_o, 47 | input apb_req_t apb_req_i, 48 | output apb_rsp_t apb_rsp_o 49 | ); 50 | 51 | // Convert APB to reg_bus 52 | REG_BUS #(.ADDR_WIDTH(ADDR_WIDTH), .DATA_WIDTH(DATA_WIDTH)) s_reg_bus(.clk_i); 53 | apb_to_reg i_abp_to_reg ( 54 | .clk_i, 55 | .rst_ni, 56 | .penable_i ( apb_req_i.penable ), 57 | .pwrite_i ( apb_req_i.pwrite ), 58 | .paddr_i ( apb_req_i.paddr ), 59 | .psel_i ( apb_req_i.psel ), 60 | .pwdata_i ( apb_req_i.pwdata ), 61 | .prdata_o ( apb_rsp_o.prdata ), 62 | .pready_o ( apb_rsp_o.pready ), 63 | .pslverr_o ( apb_rsp_o.pslverr ), 64 | .reg_o ( s_reg_bus ) 65 | ); 66 | 67 | gpio_intf #( 68 | .ADDR_WIDTH ( ADDR_WIDTH ), 69 | .DATA_WIDTH ( DATA_WIDTH ) 70 | ) i_gpio ( 71 | .clk_i, 72 | .rst_ni, 73 | .gpio_in, 74 | .gpio_out, 75 | .gpio_tx_en_o, 76 | .gpio_in_sync_o, 77 | .global_interrupt_o, 78 | .pin_level_interrupts_o, 79 | .reg_bus(s_reg_bus) 80 | ); 81 | endmodule // gpio_apb_wrap 82 | 83 | 84 | module gpio_apb_wrap_intf 85 | import apb_pkg::*; 86 | # ( 87 | /// ADDR_WIDTH of the APB interface 88 | parameter int unsigned ADDR_WIDTH = 32, 89 | /// DATA_WIDTH of the APB interface 90 | parameter int unsigned DATA_WIDTH = 32, 91 | localparam int unsigned NrGPIOs = gpio_reg_pkg::GPIOCount, 92 | localparam int unsigned STRB_WIDTH = DATA_WIDTH/8 93 | )( 94 | input logic clk_i, 95 | input logic rst_ni, 96 | input logic [NrGPIOs-1:0] gpio_in, 97 | output logic [NrGPIOs-1:0] gpio_out, 98 | output logic [NrGPIOs-1:0] gpio_tx_en_o, // 0 -> input, 1 -> output 99 | output logic [NrGPIOs-1:0] gpio_in_sync_o, // sampled and synchronized GPIO 100 | // input. 101 | output logic global_interrupt_o, 102 | output logic [NrGPIOs-1:0] pin_level_interrupts_o, 103 | APB.Slave apb_slave 104 | ); 105 | 106 | // Convert SV Interface to structs 107 | typedef logic [ADDR_WIDTH-1:0] addr_t; 108 | typedef logic [DATA_WIDTH-1:0] data_t; 109 | typedef logic [DATA_WIDTH/8-1:0] strb_t; // The APB bus interface only 110 | // supports 8-bit strobe so we don't need to 111 | // check the strobe width of the intput bus. 112 | `APB_TYPEDEF_REQ_T(apb_req_t, addr_t, data_t, strb_t) 113 | `APB_TYPEDEF_RESP_T(apb_rsp_t, data_t) 114 | 115 | apb_req_t s_apb_req; 116 | apb_rsp_t s_apb_rsp; 117 | 118 | `APB_ASSIGN_TO_REQ(s_apb_req, apb_slave) 119 | `APB_ASSIGN_FROM_RESP(apb_slave, s_apb_rsp) 120 | 121 | gpio_apb_wrap #( 122 | .ADDR_WIDTH ( ADDR_WIDTH ), 123 | .DATA_WIDTH ( DATA_WIDTH ), 124 | .apb_req_t ( apb_req_t ), 125 | .apb_rsp_t ( apb_rsp_t ) 126 | ) i_gpio_apb_wrap ( 127 | .clk_i, 128 | .rst_ni, 129 | .gpio_in, 130 | .gpio_out, 131 | .gpio_tx_en_o, 132 | .gpio_in_sync_o, 133 | .global_interrupt_o, 134 | .pin_level_interrupts_o, 135 | .apb_req_i ( s_apb_req ), 136 | .apb_rsp_o ( s_apb_rsp ) 137 | ); 138 | 139 | endmodule 140 | -------------------------------------------------------------------------------- /src/gpio_axi_lite_wrap.sv: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Title : GPIO AXI Lite Wrapper 3 | //----------------------------------------------------------------------------- 4 | // File : gpio_axi_lite_wrap.sv 5 | // Author : Manuel Eggimann 6 | // Created : 06.05.2021 7 | //----------------------------------------------------------------------------- 8 | // Description : 9 | // This file provides a wrapper around the GPIO peripheral with an AXI4-lite 10 | // interface. The file contains two versions of the module, one structs for the 11 | // AXI-lite interface and one using SystemVerilog Interfaces. 12 | //----------------------------------------------------------------------------- 13 | // Copyright (C) 2013-2021 ETH Zurich, University of Bologna 14 | // Copyright and related rights are licensed under the Solderpad Hardware 15 | // License, Version 0.51 (the "License"); you may not use this file except in 16 | // compliance with the License. You may obtain a copy of the License at 17 | // http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law 18 | // or agreed to in writing, software, hardware and materials distributed under 19 | // this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 20 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 21 | // specific language governing permissions and limitations under the License. 22 | //----------------------------------------------------------------------------- 23 | 24 | 25 | `include "register_interface/typedef.svh" 26 | `include "register_interface/assign.svh" 27 | `include "axi/typedef.svh" 28 | `include "axi/assign.svh" 29 | 30 | module gpio_axi_lite_wrap # ( 31 | /// ADDR_WIDTH of the AXI lite interface 32 | parameter int unsigned ADDR_WIDTH = 32, 33 | /// DATA_WIDTH of the AXI lite interface 34 | parameter int unsigned DATA_WIDTH = 32, 35 | /// Whether the AXI-Lite W channel should be decoupled with a register. This 36 | /// can help break long paths at the expense of registers. 37 | parameter bit DECOUPLE_W = 1, 38 | /// AXI-Lite request struct type. 39 | parameter type axi_lite_req_t = logic, 40 | /// AXI-Lite response struct type. 41 | parameter type axi_lite_rsp_t = logic, 42 | localparam int unsigned NrGPIOs = gpio_reg_pkg::GPIOCount, 43 | localparam int unsigned STRB_WIDTH = DATA_WIDTH/8 44 | )( 45 | input logic clk_i, 46 | input logic rst_ni, 47 | input logic [NrGPIOs-1:0] gpio_in, 48 | output logic [NrGPIOs-1:0] gpio_out, 49 | output logic [NrGPIOs-1:0] gpio_tx_en_o, // 0 -> input, 1 -> output 50 | output logic [NrGPIOs-1:0] gpio_in_sync_o, // sampled and synchronized GPIO 51 | // input. 52 | output logic global_interrupt_o, 53 | output logic [NrGPIOs-1:0] pin_level_interrupts_o, 54 | input axi_lite_req_t axi_lite_req_i, 55 | output axi_lite_rsp_t axi_lite_rsp_o 56 | ); 57 | 58 | if (STRB_WIDTH != DATA_WIDTH/8) 59 | $error("Unsupported AXI strobe width (%d) The underlying register bus protocol does not support strobe widths other than 8-bit.", STRB_WIDTH); 60 | 61 | typedef logic [ADDR_WIDTH-1:0] addr_t; 62 | typedef logic [DATA_WIDTH-1:0] data_t; 63 | typedef logic [STRB_WIDTH-1:0] strb_t; 64 | `REG_BUS_TYPEDEF_ALL(reg_bus, addr_t, data_t, strb_t) 65 | 66 | reg_bus_req_t s_reg_req; 67 | reg_bus_rsp_t s_reg_rsp; 68 | 69 | axi_lite_to_reg #( 70 | .ADDR_WIDTH(ADDR_WIDTH), 71 | .DATA_WIDTH(DATA_WIDTH), 72 | .BUFFER_DEPTH(1), 73 | .DECOUPLE_W(0), 74 | .axi_lite_req_t(axi_lite_req_t), 75 | .axi_lite_rsp_t(axi_lite_rsp_t), 76 | .reg_req_t(reg_bus_req_t), 77 | .reg_rsp_t(reg_bus_rsp_t) 78 | ) i_axi_lite_to_reg ( 79 | .clk_i, 80 | .rst_ni, 81 | .axi_lite_req_i, 82 | .axi_lite_rsp_o, 83 | .reg_req_o(s_reg_req), 84 | .reg_rsp_i(s_reg_rsp) 85 | ); 86 | 87 | 88 | gpio #( 89 | .DATA_WIDTH ( DATA_WIDTH ), 90 | .reg_req_t ( reg_bus_req_t ), 91 | .reg_rsp_t ( reg_bus_rsp_t ) 92 | ) i_gpio ( 93 | .clk_i, 94 | .rst_ni, 95 | .gpio_in, 96 | .gpio_out, 97 | .gpio_tx_en_o, 98 | .gpio_in_sync_o, 99 | .global_interrupt_o, 100 | .pin_level_interrupts_o, 101 | .reg_req_i ( s_reg_req ), 102 | .reg_rsp_o ( s_reg_rsp ) 103 | ); 104 | 105 | endmodule 106 | 107 | module gpio_axi_lite_wrap_intf 108 | import axi_pkg::*; 109 | # ( 110 | /// ADDR_WIDTH of the AXI lite interface 111 | parameter int unsigned ADDR_WIDTH = 32, 112 | /// DATA_WIDTH of the AXI lite interface 113 | parameter int unsigned DATA_WIDTH = 32, 114 | /// Whether the AXI-Lite W channel should be decoupled with a register. This 115 | /// can help break long paths at the expense of registers. 116 | parameter bit DECOUPLE_W = 1, 117 | localparam int unsigned NrGPIOs = gpio_reg_pkg::GPIOCount, 118 | localparam int unsigned STRB_WIDTH = DATA_WIDTH/8 119 | )( 120 | input logic clk_i, 121 | input logic rst_ni, 122 | input logic [NrGPIOs-1:0] gpio_in, 123 | output logic [NrGPIOs-1:0] gpio_out, 124 | output logic [NrGPIOs-1:0] gpio_tx_en_o, // 0 -> input, 1 -> output 125 | output logic [NrGPIOs-1:0] gpio_in_sync_o, // sampled and synchronized GPIO 126 | // input. 127 | output logic global_interrupt_o, 128 | output logic [NrGPIOs-1:0] pin_level_interrupts_o, 129 | AXI_LITE.Slave axi_i 130 | ); 131 | 132 | // Convert SV interface to structs 133 | // Declare axi_lite structs 134 | typedef logic [ADDR_WIDTH-1:0] addr_t; 135 | typedef logic [DATA_WIDTH-1:0] data_t; 136 | typedef logic [STRB_WIDTH-1:0] strb_t; 137 | `AXI_LITE_TYPEDEF_ALL(axi_lite, addr_t, data_t, strb_t) 138 | // Declare axi_lit struct signals 139 | axi_lite_req_t s_axi_lite_req; 140 | axi_lite_resp_t s_axi_lite_rsp; 141 | // Connect SV interface to structs 142 | `AXI_LITE_ASSIGN_TO_REQ(s_axi_lite_req, axi_i) 143 | `AXI_LITE_ASSIGN_FROM_RESP(axi_i, s_axi_lite_rsp) 144 | 145 | gpio_axi_lite_wrap #( 146 | .ADDR_WIDTH ( ADDR_WIDTH ), 147 | .DATA_WIDTH ( DATA_WIDTH ), 148 | .DECOUPLE_W ( DECOUPLE_W ), 149 | .axi_lite_req_t ( axi_lite_req_t ), 150 | .axi_lite_rsp_t ( axi_lite_resp_t ) 151 | ) i_gpio_axi_lite_wrap ( 152 | .clk_i, 153 | .rst_ni, 154 | .gpio_in, 155 | .gpio_out, 156 | .gpio_tx_en_o, 157 | .gpio_in_sync_o, 158 | .global_interrupt_o, 159 | .pin_level_interrupts_o, 160 | .axi_lite_req_i ( s_axi_lite_req ), 161 | .axi_lite_rsp_o ( s_axi_lite_rsp ) 162 | ); 163 | 164 | endmodule 165 | -------------------------------------------------------------------------------- /src/gpio_input_stage.sv: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Title : GPIO Input Stage 3 | //----------------------------------------------------------------------------- 4 | // File : gpio_input_stage.sv 5 | // Author : Manuel Eggimann 6 | // Created : 14.04.2022 7 | //----------------------------------------------------------------------------- 8 | // Description : 9 | // 10 | // This module implements the input synchronization stage for a single GPIO. It 11 | // uses a two-stage synchronizer for meta-stability resolution. This version of 12 | // the input stage instantiates a clock gate to disable input sampling when the 13 | // corresponding GPIO is disabled. This clock gate instance (tc_clk_gating) is 14 | // behavioraly implemented in the `pulp-plaform/common_cells` (on GitHub) 15 | // repository. In case you want to tape-out this GPIO, you either have to map 16 | // this behavioral clock gate cell to a dedicated ICG of your std cell library 17 | // or use the alternative version (`gpio_input_stage_no_clk_gates.sv`) of the 18 | // input stage that does not include any clock gates. 19 | // 20 | //----------------------------------------------------------------------------- 21 | // Copyright (C) 2022 ETH Zurich, University of Bologna 22 | // Copyright and related rights are licensed under the Solderpad Hardware 23 | // License, Version 0.51 (the "License"); you may not use this file except in 24 | // compliance with the License. You may obtain a copy of the License at 25 | // http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law 26 | // or agreed to in writing, software, hardware and materials distributed under 27 | // this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 28 | // CONDITIONS OF ANY KIND, either express or implied. See the License for the 29 | // specific language governing permissions and limitations under the License. 30 | // SPDX-License-Identifier: SHL-0.51 31 | //----------------------------------------------------------------------------- 32 | 33 | 34 | module gpio_input_stage #( 35 | parameter NrSyncStages=2 36 | ) ( 37 | input logic clk_i, 38 | input logic rst_ni, 39 | input logic en_i, 40 | input logic serial_i, 41 | output logic r_edge_o, 42 | output logic f_edge_o, 43 | output logic serial_o 44 | ); 45 | 46 | logic clk; 47 | logic serial, serial_q; 48 | 49 | assign serial_o = serial_q; 50 | assign f_edge_o = (~serial) & serial_q; 51 | assign r_edge_o = serial & (~serial_q); 52 | 53 | tc_clk_gating #( 54 | .IS_FUNCTIONAL(0) // The clock gate is not required for proper 55 | // functionality. Just for power saving. 56 | ) i_clk_gate ( 57 | .clk_i, 58 | .en_i, 59 | .test_en_i ( 1'b0 ), 60 | .clk_o ( clk ) 61 | ); 62 | 63 | sync #( 64 | .STAGES (NrSyncStages) 65 | ) i_sync ( 66 | .clk_i(clk), 67 | .rst_ni, 68 | .serial_i, 69 | .serial_o ( serial ) 70 | ); 71 | 72 | always_ff @(posedge clk, negedge rst_ni) begin 73 | if (!rst_ni) begin 74 | serial_q <= 1'b0; 75 | end else begin 76 | serial_q <= serial; 77 | end 78 | end 79 | 80 | 81 | endmodule : gpio_input_stage 82 | -------------------------------------------------------------------------------- /src/gpio_input_stage_no_clk_gates.sv: -------------------------------------------------------------------------------- 1 | module gpio_input_stage #( 2 | parameter NrSyncStages=2 3 | ) ( 4 | input logic clk_i, 5 | input logic rst_ni, 6 | input logic en_i, 7 | input logic serial_i, 8 | output logic r_edge_o, 9 | output logic f_edge_o, 10 | output logic serial_o 11 | ); 12 | 13 | logic serial, serial_q; 14 | 15 | assign serial_o = serial_q; 16 | assign f_edge_o = (~serial) & serial_q; 17 | assign r_edge_o = serial & (~serial_q); 18 | 19 | sync #( 20 | .STAGES (NrSyncStages) 21 | ) i_sync ( 22 | .clk_i(clk_i), 23 | .rst_ni, 24 | .serial_i, 25 | .serial_o ( serial ) 26 | ); 27 | 28 | always_ff @(posedge clk_i, negedge rst_ni) begin 29 | if (!rst_ni) begin 30 | serial_q <= 1'b0; 31 | end else begin 32 | if (en_i) begin 33 | serial_q <= serial; 34 | end 35 | end 36 | end 37 | 38 | 39 | endmodule : gpio_input_stage 40 | -------------------------------------------------------------------------------- /src/gpio_reg_pkg.sv: -------------------------------------------------------------------------------- 1 | // Copyright lowRISC contributors. 2 | // Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | // Register Package auto-generated by `reggen` containing data structure 6 | 7 | package gpio_reg_pkg; 8 | 9 | // Param list 10 | parameter int GPIOCount = 32; 11 | 12 | // Address widths within the block 13 | parameter int BlockAw = 11; 14 | 15 | //////////////////////////// 16 | // Typedefs for registers // 17 | //////////////////////////// 18 | 19 | typedef struct packed { 20 | struct packed { 21 | logic q; 22 | } glbl_intrpt_mode; 23 | struct packed { 24 | logic q; 25 | } pin_lvl_intrpt_mode; 26 | } gpio_reg2hw_cfg_reg_t; 27 | 28 | typedef struct packed { 29 | logic [1:0] q; 30 | } gpio_reg2hw_gpio_mode_mreg_t; 31 | 32 | typedef struct packed { 33 | logic q; 34 | } gpio_reg2hw_gpio_en_mreg_t; 35 | 36 | typedef struct packed { 37 | logic q; 38 | } gpio_reg2hw_gpio_out_mreg_t; 39 | 40 | typedef struct packed { 41 | logic q; 42 | logic qe; 43 | } gpio_reg2hw_gpio_set_mreg_t; 44 | 45 | typedef struct packed { 46 | logic q; 47 | logic qe; 48 | } gpio_reg2hw_gpio_clear_mreg_t; 49 | 50 | typedef struct packed { 51 | logic q; 52 | logic qe; 53 | } gpio_reg2hw_gpio_toggle_mreg_t; 54 | 55 | typedef struct packed { 56 | logic q; 57 | } gpio_reg2hw_intrpt_rise_en_mreg_t; 58 | 59 | typedef struct packed { 60 | logic q; 61 | } gpio_reg2hw_intrpt_fall_en_mreg_t; 62 | 63 | typedef struct packed { 64 | logic q; 65 | } gpio_reg2hw_intrpt_lvl_high_en_mreg_t; 66 | 67 | typedef struct packed { 68 | logic q; 69 | } gpio_reg2hw_intrpt_lvl_low_en_mreg_t; 70 | 71 | typedef struct packed { 72 | logic q; 73 | logic qe; 74 | } gpio_reg2hw_intrpt_status_mreg_t; 75 | 76 | typedef struct packed { 77 | logic q; 78 | } gpio_reg2hw_intrpt_rise_status_mreg_t; 79 | 80 | typedef struct packed { 81 | logic q; 82 | } gpio_reg2hw_intrpt_fall_status_mreg_t; 83 | 84 | typedef struct packed { 85 | logic q; 86 | } gpio_reg2hw_intrpt_lvl_high_status_mreg_t; 87 | 88 | typedef struct packed { 89 | logic q; 90 | } gpio_reg2hw_intrpt_lvl_low_status_mreg_t; 91 | 92 | typedef struct packed { 93 | struct packed { 94 | logic [9:0] d; 95 | } gpio_cnt; 96 | struct packed { 97 | logic [9:0] d; 98 | } version; 99 | } gpio_hw2reg_info_reg_t; 100 | 101 | typedef struct packed { 102 | logic d; 103 | } gpio_hw2reg_gpio_in_mreg_t; 104 | 105 | typedef struct packed { 106 | logic d; 107 | logic de; 108 | } gpio_hw2reg_gpio_out_mreg_t; 109 | 110 | typedef struct packed { 111 | logic d; 112 | } gpio_hw2reg_intrpt_status_mreg_t; 113 | 114 | typedef struct packed { 115 | logic d; 116 | logic de; 117 | } gpio_hw2reg_intrpt_rise_status_mreg_t; 118 | 119 | typedef struct packed { 120 | logic d; 121 | logic de; 122 | } gpio_hw2reg_intrpt_fall_status_mreg_t; 123 | 124 | typedef struct packed { 125 | logic d; 126 | logic de; 127 | } gpio_hw2reg_intrpt_lvl_high_status_mreg_t; 128 | 129 | typedef struct packed { 130 | logic d; 131 | logic de; 132 | } gpio_hw2reg_intrpt_lvl_low_status_mreg_t; 133 | 134 | // Register -> HW type 135 | typedef struct packed { 136 | gpio_reg2hw_cfg_reg_t cfg; // [641:640] 137 | gpio_reg2hw_gpio_mode_mreg_t [31:0] gpio_mode; // [639:576] 138 | gpio_reg2hw_gpio_en_mreg_t [31:0] gpio_en; // [575:544] 139 | gpio_reg2hw_gpio_out_mreg_t [31:0] gpio_out; // [543:512] 140 | gpio_reg2hw_gpio_set_mreg_t [31:0] gpio_set; // [511:448] 141 | gpio_reg2hw_gpio_clear_mreg_t [31:0] gpio_clear; // [447:384] 142 | gpio_reg2hw_gpio_toggle_mreg_t [31:0] gpio_toggle; // [383:320] 143 | gpio_reg2hw_intrpt_rise_en_mreg_t [31:0] intrpt_rise_en; // [319:288] 144 | gpio_reg2hw_intrpt_fall_en_mreg_t [31:0] intrpt_fall_en; // [287:256] 145 | gpio_reg2hw_intrpt_lvl_high_en_mreg_t [31:0] intrpt_lvl_high_en; // [255:224] 146 | gpio_reg2hw_intrpt_lvl_low_en_mreg_t [31:0] intrpt_lvl_low_en; // [223:192] 147 | gpio_reg2hw_intrpt_status_mreg_t [31:0] intrpt_status; // [191:128] 148 | gpio_reg2hw_intrpt_rise_status_mreg_t [31:0] intrpt_rise_status; // [127:96] 149 | gpio_reg2hw_intrpt_fall_status_mreg_t [31:0] intrpt_fall_status; // [95:64] 150 | gpio_reg2hw_intrpt_lvl_high_status_mreg_t [31:0] intrpt_lvl_high_status; // [63:32] 151 | gpio_reg2hw_intrpt_lvl_low_status_mreg_t [31:0] intrpt_lvl_low_status; // [31:0] 152 | } gpio_reg2hw_t; 153 | 154 | // HW -> register type 155 | typedef struct packed { 156 | gpio_hw2reg_info_reg_t info; // [403:384] 157 | gpio_hw2reg_gpio_in_mreg_t [31:0] gpio_in; // [383:352] 158 | gpio_hw2reg_gpio_out_mreg_t [31:0] gpio_out; // [351:288] 159 | gpio_hw2reg_intrpt_status_mreg_t [31:0] intrpt_status; // [287:256] 160 | gpio_hw2reg_intrpt_rise_status_mreg_t [31:0] intrpt_rise_status; // [255:192] 161 | gpio_hw2reg_intrpt_fall_status_mreg_t [31:0] intrpt_fall_status; // [191:128] 162 | gpio_hw2reg_intrpt_lvl_high_status_mreg_t [31:0] intrpt_lvl_high_status; // [127:64] 163 | gpio_hw2reg_intrpt_lvl_low_status_mreg_t [31:0] intrpt_lvl_low_status; // [63:0] 164 | } gpio_hw2reg_t; 165 | 166 | // Register offsets 167 | parameter logic [BlockAw-1:0] GPIO_INFO_OFFSET = 11'h 0; 168 | parameter logic [BlockAw-1:0] GPIO_CFG_OFFSET = 11'h 4; 169 | parameter logic [BlockAw-1:0] GPIO_GPIO_MODE_0_OFFSET = 11'h 8; 170 | parameter logic [BlockAw-1:0] GPIO_GPIO_MODE_1_OFFSET = 11'h c; 171 | parameter logic [BlockAw-1:0] GPIO_GPIO_EN_OFFSET = 11'h 80; 172 | parameter logic [BlockAw-1:0] GPIO_GPIO_IN_OFFSET = 11'h 100; 173 | parameter logic [BlockAw-1:0] GPIO_GPIO_OUT_OFFSET = 11'h 180; 174 | parameter logic [BlockAw-1:0] GPIO_GPIO_SET_OFFSET = 11'h 200; 175 | parameter logic [BlockAw-1:0] GPIO_GPIO_CLEAR_OFFSET = 11'h 280; 176 | parameter logic [BlockAw-1:0] GPIO_GPIO_TOGGLE_OFFSET = 11'h 300; 177 | parameter logic [BlockAw-1:0] GPIO_INTRPT_RISE_EN_OFFSET = 11'h 380; 178 | parameter logic [BlockAw-1:0] GPIO_INTRPT_FALL_EN_OFFSET = 11'h 400; 179 | parameter logic [BlockAw-1:0] GPIO_INTRPT_LVL_HIGH_EN_OFFSET = 11'h 480; 180 | parameter logic [BlockAw-1:0] GPIO_INTRPT_LVL_LOW_EN_OFFSET = 11'h 500; 181 | parameter logic [BlockAw-1:0] GPIO_INTRPT_STATUS_OFFSET = 11'h 580; 182 | parameter logic [BlockAw-1:0] GPIO_INTRPT_RISE_STATUS_OFFSET = 11'h 600; 183 | parameter logic [BlockAw-1:0] GPIO_INTRPT_FALL_STATUS_OFFSET = 11'h 680; 184 | parameter logic [BlockAw-1:0] GPIO_INTRPT_LVL_HIGH_STATUS_OFFSET = 11'h 700; 185 | parameter logic [BlockAw-1:0] GPIO_INTRPT_LVL_LOW_STATUS_OFFSET = 11'h 780; 186 | 187 | // Reset values for hwext registers and their fields 188 | parameter logic [19:0] GPIO_INFO_RESVAL = 20'h 800; 189 | parameter logic [9:0] GPIO_INFO_VERSION_RESVAL = 10'h 2; 190 | parameter logic [31:0] GPIO_GPIO_IN_RESVAL = 32'h 0; 191 | parameter logic [31:0] GPIO_GPIO_SET_RESVAL = 32'h 0; 192 | parameter logic [31:0] GPIO_GPIO_CLEAR_RESVAL = 32'h 0; 193 | parameter logic [31:0] GPIO_GPIO_TOGGLE_RESVAL = 32'h 0; 194 | parameter logic [31:0] GPIO_INTRPT_STATUS_RESVAL = 32'h 0; 195 | 196 | // Register index 197 | typedef enum int { 198 | GPIO_INFO, 199 | GPIO_CFG, 200 | GPIO_GPIO_MODE_0, 201 | GPIO_GPIO_MODE_1, 202 | GPIO_GPIO_EN, 203 | GPIO_GPIO_IN, 204 | GPIO_GPIO_OUT, 205 | GPIO_GPIO_SET, 206 | GPIO_GPIO_CLEAR, 207 | GPIO_GPIO_TOGGLE, 208 | GPIO_INTRPT_RISE_EN, 209 | GPIO_INTRPT_FALL_EN, 210 | GPIO_INTRPT_LVL_HIGH_EN, 211 | GPIO_INTRPT_LVL_LOW_EN, 212 | GPIO_INTRPT_STATUS, 213 | GPIO_INTRPT_RISE_STATUS, 214 | GPIO_INTRPT_FALL_STATUS, 215 | GPIO_INTRPT_LVL_HIGH_STATUS, 216 | GPIO_INTRPT_LVL_LOW_STATUS 217 | } gpio_id_e; 218 | 219 | // Register width information to check illegal writes 220 | parameter logic [3:0] GPIO_PERMIT [19] = '{ 221 | 4'b 0111, // index[ 0] GPIO_INFO 222 | 4'b 0001, // index[ 1] GPIO_CFG 223 | 4'b 1111, // index[ 2] GPIO_GPIO_MODE_0 224 | 4'b 1111, // index[ 3] GPIO_GPIO_MODE_1 225 | 4'b 1111, // index[ 4] GPIO_GPIO_EN 226 | 4'b 1111, // index[ 5] GPIO_GPIO_IN 227 | 4'b 1111, // index[ 6] GPIO_GPIO_OUT 228 | 4'b 1111, // index[ 7] GPIO_GPIO_SET 229 | 4'b 1111, // index[ 8] GPIO_GPIO_CLEAR 230 | 4'b 1111, // index[ 9] GPIO_GPIO_TOGGLE 231 | 4'b 1111, // index[10] GPIO_INTRPT_RISE_EN 232 | 4'b 1111, // index[11] GPIO_INTRPT_FALL_EN 233 | 4'b 1111, // index[12] GPIO_INTRPT_LVL_HIGH_EN 234 | 4'b 1111, // index[13] GPIO_INTRPT_LVL_LOW_EN 235 | 4'b 1111, // index[14] GPIO_INTRPT_STATUS 236 | 4'b 1111, // index[15] GPIO_INTRPT_RISE_STATUS 237 | 4'b 1111, // index[16] GPIO_INTRPT_FALL_STATUS 238 | 4'b 1111, // index[17] GPIO_INTRPT_LVL_HIGH_STATUS 239 | 4'b 1111 // index[18] GPIO_INTRPT_LVL_LOW_STATUS 240 | }; 241 | 242 | endpackage 243 | 244 | -------------------------------------------------------------------------------- /util/reggen/reggen/README.md: -------------------------------------------------------------------------------- 1 | # Register generator `reggen` and `regtool` 2 | 3 | The utility script `regtool.py` and collateral under `reggen` are Python 4 | tools to read register descriptions in Hjson and generate various output 5 | formats. The tool can output HTML documentation, standard JSON, compact 6 | standard JSON (whitespace removed) and Hjson. The example commands assume 7 | `$REPO_TOP` is set to the toplevel directory of the repository. 8 | 9 | ### Setup 10 | 11 | If packages have not previously been installed you will need to set a 12 | few things up. First use `pip3` to install some required packages: 13 | 14 | ```console 15 | $ pip3 install --user hjson 16 | $ pip3 install --user mistletoe 17 | $ pip3 install --user mako 18 | ``` 19 | 20 | ### Register JSON Format 21 | 22 | For details on the register JSON format, see the 23 | [register tool documentation]({{< relref "doc/rm/register_tool/index.md" >}}). 24 | To ensure things stay up to date, the register JSON format information 25 | is documented by the tool itself. 26 | The documentation can be generated by running the following commands: 27 | 28 | ```console 29 | $ cd $REPO_TOP/util 30 | $ ./build_docs.py 31 | ``` 32 | Under the hood, the `build_docs.py` tool will automatically use the `reggen` 33 | tool to produce Markdown and processing that into HTML. 34 | 35 | ### Examples using standalone regtool 36 | 37 | Normally for documentation the `build_docs.py` tool will automatically 38 | use `reggen`. The script `regtool.py` provides a standalone way to run 39 | `reggen`. See the 40 | [register tool documentation]({{< relref "doc/rm/register_tool/index.md" >}}) 41 | for details about how to invoke the tool. 42 | 43 | The following shows an example of how to generate RTL from a register 44 | description: 45 | 46 | ```console 47 | $ cd $REPO_TOP/util 48 | $ mkdir /tmp/rtl 49 | $ ./regtool.py -r -t /tmp/rtl ../hw/ip/uart/data/uart.hjson 50 | $ ls /tmp/rtl 51 | uart_reg_pkg.sv uart_reg_top.sv 52 | ``` 53 | 54 | The following shows an example of how to generate a DV UVM class from 55 | a register description: 56 | 57 | ```console 58 | $ cd $REPO_TOP/util 59 | $ mkdir /tmp/dv 60 | $ ./regtool.py -s -t /tmp/dv ../hw/ip/uart/data/uart.hjson 61 | $ ls /tmp/dv 62 | uart_ral_pkg.sv 63 | ``` 64 | 65 | By default, the generated block, register and field models are derived from 66 | `dv_base_reg` classes provided at `hw/dv/sv/dv_base_reg`. If required, the user 67 | can supply the `--dv-base-prefix my_base` switch to have the models derive from 68 | a custom, user-defined RAL classes instead: 69 | 70 | ```console 71 | $ cd $REPO_TOP/util 72 | $ mkdir /tmp/dv 73 | $ ./regtool.py -s -t /tmp/dv ../hw/ip/uart/data/uart.hjson \ 74 | --dv-base-prefix my_base 75 | $ ls /tmp/dv 76 | uart_ral_pkg.sv 77 | ``` 78 | 79 | This makes the following assumptions: 80 | - A FuseSoC core file aggregating the `my_base` RAL classes with the VLNV 81 | name `lowrisc:dv:my_base_reg` is provided in the cores search path. 82 | - These custom classes are derived from the corresponding `dv_base_reg` classes 83 | and have the following names: 84 | - `my_base_reg_pkg.sv`: The RAL package that includes the below sources 85 | - `my_base_reg_block.sv`: The register block abstraction 86 | - `my_base_reg.sv`: The register abstraction 87 | - `my_base_reg_field.sv`: The register field abstraction 88 | - `my_base_mem.sv`: The memory abstraction 89 | - If any of the above class specializations is not needed, it can be 90 | `typedef`'ed in `my_base_reg_pkg`: 91 | ```systemverilog 92 | package my_base_reg_pkg; 93 | import dv_base_reg_pkg::*; 94 | typedef dv_base_reg_field my_base_reg_field; 95 | typedef dv_base_mem my_base_mem; 96 | `include "my_base_reg.sv" 97 | `include "my_base_reg_block.sv" 98 | endpackage 99 | ``` 100 | 101 | The following shows an example of how to generate a FPV csr read write assertion 102 | module from a register description: 103 | 104 | ```console 105 | $ cd $REPO_TOP/util 106 | $ mkdir /tmp/fpv/vip 107 | $ ./regtool.py -f -t /tmp/fpv/vip ../hw/ip/uart/data/uart.hjson 108 | $ ls /tmp/fpv 109 | uart_csr_assert_fpv.sv 110 | ``` 111 | 112 | If the target directory is not specified, the tool creates the DV file 113 | under the `hw/ip/{module}/dv/` directory. 114 | -------------------------------------------------------------------------------- /util/reggen/reggen/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulp-platform/gpio/8a9e71e64b588c49f66794fa655e35d4ccb038cb/util/reggen/reggen/__init__.py -------------------------------------------------------------------------------- /util/reggen/reggen/access.py: -------------------------------------------------------------------------------- 1 | # Copyright lowRISC contributors. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | """Enumerated types for fields 5 | Generated by validation, used by backends 6 | """ 7 | 8 | from enum import Enum 9 | 10 | from .lib import check_str 11 | 12 | 13 | class JsonEnum(Enum): 14 | def for_json(x) -> str: 15 | return str(x) 16 | 17 | 18 | class SwWrAccess(JsonEnum): 19 | WR = 1 20 | NONE = 2 21 | 22 | 23 | class SwRdAccess(JsonEnum): 24 | RD = 1 25 | RC = 2 # Special handling for port 26 | NONE = 3 27 | 28 | 29 | class SwAccess(JsonEnum): 30 | RO = 1 31 | RW = 2 32 | WO = 3 33 | W1C = 4 34 | W1S = 5 35 | W0C = 6 36 | RC = 7 37 | R0W1C = 8 38 | NONE = 9 39 | 40 | 41 | class HwAccess(JsonEnum): 42 | HRO = 1 43 | HRW = 2 44 | HWO = 3 45 | NONE = 4 # No access allowed 46 | 47 | 48 | # swaccess permitted values 49 | # text description, access enum, wr access enum, rd access enum, ok in window 50 | SWACCESS_PERMITTED = { 51 | 'none': ("No access", # noqa: E241 52 | SwAccess.NONE, SwWrAccess.NONE, SwRdAccess.NONE, False), # noqa: E241 53 | 'ro': ("Read Only", # noqa: E241 54 | SwAccess.RO, SwWrAccess.NONE, SwRdAccess.RD, True), # noqa: E241 55 | 'rc': ("Read Only, reading clears", # noqa: E241 56 | SwAccess.RC, SwWrAccess.WR, SwRdAccess.RC, False), # noqa: E241 57 | 'rw': ("Read/Write", # noqa: E241 58 | SwAccess.RW, SwWrAccess.WR, SwRdAccess.RD, True), # noqa: E241 59 | 'r0w1c': ("Read zero, Write with 1 clears", # noqa: E241 60 | SwAccess.W1C, SwWrAccess.WR, SwRdAccess.NONE, False), # noqa: E241 61 | 'rw1s': ("Read, Write with 1 sets", # noqa: E241 62 | SwAccess.W1S, SwWrAccess.WR, SwRdAccess.RD, False), # noqa: E241 63 | 'rw1c': ("Read, Write with 1 clears", # noqa: E241 64 | SwAccess.W1C, SwWrAccess.WR, SwRdAccess.RD, False), # noqa: E241 65 | 'rw0c': ("Read, Write with 0 clears", # noqa: E241 66 | SwAccess.W0C, SwWrAccess.WR, SwRdAccess.RD, False), # noqa: E241 67 | 'wo': ("Write Only", # noqa: E241 68 | SwAccess.WO, SwWrAccess.WR, SwRdAccess.NONE, True) # noqa: E241 69 | } 70 | 71 | # hwaccess permitted values 72 | HWACCESS_PERMITTED = { 73 | 'hro': ("Read Only", HwAccess.HRO), 74 | 'hrw': ("Read/Write", HwAccess.HRW), 75 | 'hwo': ("Write Only", HwAccess.HWO), 76 | 'none': ("No Access Needed", HwAccess.NONE) 77 | } 78 | 79 | 80 | class SWAccess: 81 | def __init__(self, where: str, raw: object): 82 | self.key = check_str(raw, 'swaccess for {}'.format(where)) 83 | try: 84 | self.value = SWACCESS_PERMITTED[self.key] 85 | except KeyError: 86 | raise ValueError('Unknown swaccess key, {}, for {}.' 87 | .format(self.key, where)) from None 88 | 89 | def dv_rights(self) -> str: 90 | if self.key in ['none', 'ro', 'rc']: 91 | return "RO" 92 | elif self.key in ['rw', 'r0w1c', 'rw1s', 'rw1c', 'rw0c']: 93 | return "RW" 94 | else: 95 | assert self.key == 'wo' 96 | return "WO" 97 | 98 | def swrd(self) -> SwRdAccess: 99 | return self.value[3] 100 | 101 | def allows_read(self) -> bool: 102 | return self.value[3] != SwRdAccess.NONE 103 | 104 | def allows_write(self) -> bool: 105 | return self.value[2] == SwWrAccess.WR 106 | 107 | 108 | class HWAccess: 109 | def __init__(self, where: str, raw: object): 110 | self.key = check_str(raw, 'hwaccess for {}'.format(where)) 111 | try: 112 | self.value = HWACCESS_PERMITTED[self.key] 113 | except KeyError: 114 | raise ValueError('Unknown hwaccess key, {}, for {}.' 115 | .format(self.key, where)) from None 116 | 117 | def allows_read(self) -> bool: 118 | return self.key in ['hro', 'hrw'] 119 | 120 | def allows_write(self) -> bool: 121 | return self.key in ['hrw', 'hwo'] 122 | -------------------------------------------------------------------------------- /util/reggen/reggen/alert.py: -------------------------------------------------------------------------------- 1 | # Copyright lowRISC contributors. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | from typing import Dict, List 6 | 7 | from .bits import Bits 8 | from .signal import Signal 9 | from .lib import check_keys, check_name, check_str, check_list 10 | 11 | 12 | class Alert(Signal): 13 | def __init__(self, name: str, desc: str, bit: int, fatal: bool): 14 | super().__init__(name, desc, Bits(bit, bit)) 15 | self.bit = bit 16 | self.fatal = fatal 17 | 18 | @staticmethod 19 | def from_raw(what: str, 20 | lsb: int, 21 | raw: object) -> 'Alert': 22 | rd = check_keys(raw, what, ['name', 'desc'], []) 23 | 24 | name = check_name(rd['name'], 'name field of ' + what) 25 | desc = check_str(rd['desc'], 'desc field of ' + what) 26 | 27 | # Make sense of the alert name, which should be prefixed with recov_ or 28 | # fatal_. 29 | pfx = name.split('_')[0] 30 | if pfx == 'recov': 31 | fatal = False 32 | elif pfx == 'fatal': 33 | fatal = True 34 | else: 35 | raise ValueError('Invalid name field of {}: alert names must be ' 36 | 'prefixed with "recov_" or "fatal_". Saw {!r}.' 37 | .format(what, name)) 38 | 39 | return Alert(name, desc, lsb, fatal) 40 | 41 | @staticmethod 42 | def from_raw_list(what: str, raw: object) -> List['Alert']: 43 | ret = [] 44 | for idx, entry in enumerate(check_list(raw, what)): 45 | entry_what = 'entry {} of {}'.format(idx, what) 46 | alert = Alert.from_raw(entry_what, idx, entry) 47 | ret.append(alert) 48 | return ret 49 | 50 | def _asdict(self) -> Dict[str, object]: 51 | return { 52 | 'name': self.name, 53 | 'desc': self.desc, 54 | } 55 | -------------------------------------------------------------------------------- /util/reggen/reggen/bits.py: -------------------------------------------------------------------------------- 1 | # Copyright lowRISC contributors. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | '''Support code for bit ranges in reggen''' 6 | 7 | from typing import Tuple 8 | 9 | from .lib import check_str 10 | from .params import ReggenParams 11 | 12 | 13 | class Bits: 14 | def __init__(self, msb: int, lsb: int): 15 | assert 0 <= lsb <= msb 16 | self.msb = msb 17 | self.lsb = lsb 18 | 19 | def bitmask(self) -> int: 20 | return (1 << (self.msb + 1)) - (1 << self.lsb) 21 | 22 | def width(self) -> int: 23 | return 1 + self.msb - self.lsb 24 | 25 | def max_value(self) -> int: 26 | return (1 << self.width()) - 1 27 | 28 | def extract_field(self, reg_val: int) -> int: 29 | return (reg_val & self.bitmask()) >> self.lsb 30 | 31 | @staticmethod 32 | def from_raw(where: str, 33 | reg_width: int, 34 | params: ReggenParams, 35 | raw: object) -> 'Bits': 36 | # Bits should be specified as msb:lsb or as just a single bit index. 37 | if isinstance(raw, int): 38 | msb = raw 39 | lsb = raw 40 | else: 41 | str_val = check_str(raw, 'bits field for {}'.format(where)) 42 | msb, lsb = Bits._parse_str(where, params, str_val) 43 | 44 | # Check that the bit indices look sensible 45 | if msb < lsb: 46 | raise ValueError('msb for {} is {}: less than {}, the msb.' 47 | .format(where, msb, lsb)) 48 | if lsb < 0: 49 | raise ValueError('lsb for {} is {}, which is negative.' 50 | .format(where, lsb)) 51 | if msb >= reg_width: 52 | raise ValueError("msb for {} is {}, which doesn't fit in {} bits." 53 | .format(where, msb, reg_width)) 54 | 55 | return Bits(msb, lsb) 56 | 57 | @staticmethod 58 | def _parse_str(where: str, 59 | params: ReggenParams, 60 | str_val: str) -> Tuple[int, int]: 61 | try: 62 | idx = int(str_val) 63 | return (idx, idx) 64 | except ValueError: 65 | # Doesn't look like an integer. Never mind: try msb:lsb 66 | pass 67 | 68 | parts = str_val.split(':') 69 | if len(parts) != 2: 70 | raise ValueError('bits field for {} is not an ' 71 | 'integer or of the form msb:lsb. Saw {!r}.' 72 | .format(where, str_val)) 73 | return (params.expand(parts[0], 74 | 'msb of bits field for {}'.format(where)), 75 | params.expand(parts[1], 76 | 'lsb of bits field for {}'.format(where))) 77 | 78 | def make_translated(self, bit_offset: int) -> 'Bits': 79 | assert 0 <= bit_offset 80 | return Bits(self.msb + bit_offset, self.lsb + bit_offset) 81 | 82 | def as_str(self) -> str: 83 | if self.lsb == self.msb: 84 | return str(self.lsb) 85 | else: 86 | assert self.lsb < self.msb 87 | return '{}:{}'.format(self.msb, self.lsb) 88 | -------------------------------------------------------------------------------- /util/reggen/reggen/bus_interfaces.py: -------------------------------------------------------------------------------- 1 | # Copyright lowRISC contributors. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | '''Code representing a list of bus interfaces for a block''' 6 | from enum import Enum 7 | from typing import Dict, List, Optional, Tuple 8 | 9 | from .inter_signal import InterSignal 10 | from .lib import check_list, check_keys, check_str, check_optional_str 11 | 12 | class BusProtocol(Enum): 13 | TLUL = "tlul" 14 | REG_IFACE = "reg_iface" 15 | 16 | @classmethod 17 | def has_value(cls, v): 18 | return v in cls._value2member_map_ 19 | 20 | 21 | class BusInterfaces: 22 | def __init__(self, 23 | has_unnamed_host: bool, 24 | named_hosts: List[str], 25 | has_unnamed_device: bool, 26 | named_devices: List[str], 27 | interface_list: List[Dict]): 28 | assert has_unnamed_device or named_devices 29 | assert len(named_hosts) == len(set(named_hosts)) 30 | assert len(named_devices) == len(set(named_devices)) 31 | 32 | self.has_unnamed_host = has_unnamed_host 33 | self.named_hosts = named_hosts 34 | self.has_unnamed_device = has_unnamed_device 35 | self.named_devices = named_devices 36 | self.interface_list = interface_list 37 | 38 | @staticmethod 39 | def from_raw(raw: object, where: str) -> 'BusInterfaces': 40 | has_unnamed_host = False 41 | named_hosts = [] 42 | interface_list = [] 43 | 44 | has_unnamed_device = False 45 | named_devices = [] 46 | 47 | for idx, raw_entry in enumerate(check_list(raw, where)): 48 | entry_what = 'entry {} of {}'.format(idx + 1, where) 49 | ed = check_keys(raw_entry, entry_what, 50 | ['protocol', 'direction'], 51 | ['name']) 52 | 53 | protocol = check_str(ed['protocol'], 54 | 'protocol field of ' + entry_what) 55 | if not BusProtocol.has_value(protocol): 56 | raise ValueError('Unknown protocol {!r} at {}' 57 | .format(protocol, entry_what)) 58 | 59 | direction = check_str(ed['direction'], 60 | 'direction field of ' + entry_what) 61 | if direction not in ['device', 'host']: 62 | raise ValueError('Unknown interface direction {!r} at {}' 63 | .format(direction, entry_what)) 64 | 65 | name = check_optional_str(ed.get('name'), 66 | 'name field of ' + entry_what) 67 | 68 | if direction == 'host': 69 | if name is None: 70 | if has_unnamed_host: 71 | raise ValueError('Multiple un-named host ' 72 | 'interfaces at {}' 73 | .format(where)) 74 | has_unnamed_host = True 75 | else: 76 | if name in named_hosts: 77 | raise ValueError('Duplicate host interface ' 78 | 'with name {!r} at {}' 79 | .format(name, where)) 80 | named_hosts.append(name) 81 | else: 82 | if name is None: 83 | if has_unnamed_device: 84 | raise ValueError('Multiple un-named device ' 85 | 'interfaces at {}' 86 | .format(where)) 87 | has_unnamed_device = True 88 | else: 89 | if name in named_devices: 90 | raise ValueError('Duplicate device interface ' 91 | 'with name {!r} at {}' 92 | .format(name, where)) 93 | named_devices.append(name) 94 | interface_list.append({'name': name, 'protocol': BusProtocol(protocol), 'is_host': direction=='host'}) 95 | 96 | if not (has_unnamed_device or named_devices): 97 | raise ValueError('No device interface at ' + where) 98 | 99 | return BusInterfaces(has_unnamed_host, named_hosts, 100 | has_unnamed_device, named_devices, interface_list) 101 | 102 | def has_host(self) -> bool: 103 | return bool(self.has_unnamed_host or self.named_hosts) 104 | 105 | def _interfaces(self) -> List[Tuple[bool, Optional[str]]]: 106 | ret = [] # type: List[Tuple[bool, Optional[str]]] 107 | if self.has_unnamed_host: 108 | ret.append((True, None)) 109 | for name in self.named_hosts: 110 | ret.append((True, name)) 111 | 112 | if self.has_unnamed_device: 113 | ret.append((False, None)) 114 | for name in self.named_devices: 115 | ret.append((False, name)) 116 | 117 | return ret 118 | 119 | @staticmethod 120 | def _if_dict(is_host: bool, name: Optional[str]) -> Dict[str, object]: 121 | ret = { 122 | 'protocol': 'tlul', 123 | 'direction': 'host' if is_host else 'device' 124 | } # type: Dict[str, object] 125 | 126 | if name is not None: 127 | ret['name'] = name 128 | 129 | return ret 130 | 131 | def as_dicts(self) -> List[Dict[str, object]]: 132 | return [BusInterfaces._if_dict(is_host, name) 133 | for is_host, name in self._interfaces()] 134 | 135 | def get_port_name(self, is_host: bool, name: Optional[str]) -> str: 136 | if is_host: 137 | tl_suffix = 'tl_h' 138 | else: 139 | tl_suffix = 'tl_d' if self.has_host() else 'tl' 140 | 141 | return (tl_suffix if name is None 142 | else '{}_{}'.format(name, tl_suffix)) 143 | 144 | def get_port_names(self, inc_hosts: bool, inc_devices: bool) -> List[str]: 145 | ret = [] 146 | for is_host, name in self._interfaces(): 147 | if not (inc_hosts if is_host else inc_devices): 148 | continue 149 | ret.append(self.get_port_name(is_host, name)) 150 | return ret 151 | 152 | def _if_inter_signal(self, 153 | is_host: bool, 154 | name: Optional[str]) -> InterSignal: 155 | return InterSignal(self.get_port_name(is_host, name), 156 | None, 'tl', 'tlul_pkg', 'req_rsp', 'rsp', 1, None) 157 | 158 | def inter_signals(self) -> List[InterSignal]: 159 | return [self._if_inter_signal(is_host, name) 160 | for is_host, name in self._interfaces()] 161 | 162 | def has_interface(self, is_host: bool, name: Optional[str]) -> bool: 163 | if is_host: 164 | if name is None: 165 | return self.has_unnamed_host 166 | else: 167 | return name in self.named_hosts 168 | else: 169 | if name is None: 170 | return self.has_unnamed_device 171 | else: 172 | return name in self.named_devices 173 | 174 | def find_port_name(self, is_host: bool, name: Optional[str]) -> str: 175 | '''Look up the given host/name pair and return its port name. 176 | 177 | Raises a KeyError if there is no match. 178 | 179 | ''' 180 | if not self.has_interface(is_host, name): 181 | called = ('with no name' 182 | if name is None else 'called {!r}'.format(name)) 183 | raise KeyError('There is no {} bus interface {}.' 184 | .format('host' if is_host else 'device', 185 | called)) 186 | 187 | return self.get_port_name(is_host, name) 188 | -------------------------------------------------------------------------------- /util/reggen/reggen/enum_entry.py: -------------------------------------------------------------------------------- 1 | # Copyright lowRISC contributors. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | from typing import Dict 6 | 7 | from .lib import check_keys, check_str, check_int 8 | 9 | REQUIRED_FIELDS = { 10 | 'name': ['s', "name of the member of the enum"], 11 | 'desc': ['t', "description when field has this value"], 12 | 'value': ['d', "value of this member of the enum"] 13 | } 14 | 15 | 16 | class EnumEntry: 17 | def __init__(self, where: str, max_val: int, raw: object): 18 | rd = check_keys(raw, where, 19 | list(REQUIRED_FIELDS.keys()), 20 | []) 21 | 22 | self.name = check_str(rd['name'], 'name field of {}'.format(where)) 23 | self.desc = check_str(rd['desc'], 'desc field of {}'.format(where)) 24 | self.value = check_int(rd['value'], 'value field of {}'.format(where)) 25 | if not (0 <= self.value <= max_val): 26 | raise ValueError("value for {} is {}, which isn't representable " 27 | "in the field (representable range: 0 .. {})." 28 | .format(where, self.value, max_val)) 29 | 30 | def _asdict(self) -> Dict[str, object]: 31 | return { 32 | 'name': self.name, 33 | 'desc': self.desc, 34 | 'value': str(self.value) 35 | } 36 | -------------------------------------------------------------------------------- /util/reggen/reggen/fpv_csr.sv.tpl: -------------------------------------------------------------------------------- 1 | // Copyright lowRISC contributors. 2 | // Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | // FPV CSR read and write assertions auto-generated by `reggen` containing data structure 6 | // Do Not Edit directly 7 | // TODO: This automation currently only support register without HW write access 8 | <% 9 | from reggen import (gen_fpv) 10 | from reggen.register import Register 11 | 12 | from topgen import lib 13 | 14 | lblock = block.name.lower() 15 | use_reg_iface = any([interface['protocol'] == BusProtocol.REG_IFACE and not interace['is_host'] for interface in block.bus_interfaces.interface_list]) 16 | 17 | # This template shouldn't be instantiated if the device interface 18 | # doesn't actually have any registers. 19 | assert rb.flat_regs 20 | 21 | %>\ 22 | <%def name="construct_classes(block)">\ 23 | 24 | % if use_reg_iface: 25 | `include "common_cells/assertions.svh" 26 | % else: 27 | `include "prim_assert.sv" 28 | % endif 29 | `ifdef UVM 30 | import uvm_pkg::*; 31 | `endif 32 | 33 | // Block: ${lblock} 34 | module ${mod_base}_csr_assert_fpv import tlul_pkg::*; 35 | import top_pkg::*;( 36 | input clk_i, 37 | input rst_ni, 38 | 39 | // tile link ports 40 | input tl_h2d_t h2d, 41 | input tl_d2h_t d2h 42 | ); 43 | <% 44 | addr_width = rb.get_addr_width() 45 | addr_msb = addr_width - 1 46 | hro_regs_list = [r for r in rb.flat_regs if not r.hwaccess.allows_write()] 47 | num_hro_regs = len(hro_regs_list) 48 | hro_map = {r.offset: (idx, r) for idx, r in enumerate(hro_regs_list)} 49 | %>\ 50 | 51 | // Currently FPV csr assertion only support HRO registers. 52 | % if num_hro_regs > 0: 53 | `ifndef VERILATOR 54 | `ifndef SYNTHESIS 55 | 56 | parameter bit[3:0] MAX_A_SOURCE = 10; // used for FPV only to reduce runtime 57 | 58 | typedef struct packed { 59 | logic [TL_DW-1:0] wr_data; 60 | logic [TL_AW-1:0] addr; 61 | logic wr_pending; 62 | logic rd_pending; 63 | } pend_item_t; 64 | 65 | bit disable_sva; 66 | 67 | // mask register to convert byte to bit 68 | logic [TL_DW-1:0] a_mask_bit; 69 | 70 | assign a_mask_bit[7:0] = h2d.a_mask[0] ? '1 : '0; 71 | assign a_mask_bit[15:8] = h2d.a_mask[1] ? '1 : '0; 72 | assign a_mask_bit[23:16] = h2d.a_mask[2] ? '1 : '0; 73 | assign a_mask_bit[31:24] = h2d.a_mask[3] ? '1 : '0; 74 | 75 | bit [${addr_msb}-2:0] hro_idx; // index for exp_vals 76 | bit [${addr_msb}:0] normalized_addr; 77 | 78 | // Map register address with hro_idx in exp_vals array. 79 | always_comb begin: decode_hro_addr_to_idx 80 | unique case (pend_trans[d2h.d_source].addr) 81 | % for idx, r in hro_map.values(): 82 | ${r.offset}: hro_idx <= ${idx}; 83 | % endfor 84 | // If the register is not a HRO register, the write data will all update to this default idx. 85 | default: hro_idx <= ${num_hro_regs + 1}; 86 | endcase 87 | end 88 | 89 | // store internal expected values for HW ReadOnly registers 90 | logic [TL_DW-1:0] exp_vals[${num_hro_regs + 1}]; 91 | 92 | `ifdef FPV_ON 93 | pend_item_t [MAX_A_SOURCE:0] pend_trans; 94 | `else 95 | pend_item_t [2**TL_AIW-1:0] pend_trans; 96 | `endif 97 | 98 | // normalized address only take the [${addr_msb}:2] address from the TLUL a_address 99 | assign normalized_addr = {h2d.a_address[${addr_msb}:2], 2'b0}; 100 | 101 | % if num_hro_regs > 0: 102 | // for write HRO registers, store the write data into exp_vals 103 | always_ff @(negedge clk_i or negedge rst_ni) begin 104 | if (!rst_ni) begin 105 | pend_trans <= '0; 106 | % for hro_reg in hro_regs_list: 107 | exp_vals[${hro_map.get(hro_reg.offset)[0]}] <= ${hro_reg.resval}; 108 | % endfor 109 | end else begin 110 | if (h2d.a_valid && d2h.a_ready) begin 111 | pend_trans[h2d.a_source].addr <= normalized_addr; 112 | if (h2d.a_opcode inside {PutFullData, PutPartialData}) begin 113 | pend_trans[h2d.a_source].wr_data <= h2d.a_data & a_mask_bit; 114 | pend_trans[h2d.a_source].wr_pending <= 1'b1; 115 | end else if (h2d.a_opcode == Get) begin 116 | pend_trans[h2d.a_source].rd_pending <= 1'b1; 117 | end 118 | end 119 | if (d2h.d_valid) begin 120 | if (pend_trans[d2h.d_source].wr_pending == 1) begin 121 | if (!d2h.d_error) begin 122 | exp_vals[hro_idx] <= pend_trans[d2h.d_source].wr_data; 123 | end 124 | pend_trans[d2h.d_source].wr_pending <= 1'b0; 125 | end 126 | if (h2d.d_ready && pend_trans[d2h.d_source].rd_pending == 1) begin 127 | pend_trans[d2h.d_source].rd_pending <= 1'b0; 128 | end 129 | end 130 | end 131 | end 132 | 133 | // for read HRO registers, assert read out values by access policy and exp_vals 134 | % for hro_reg in hro_regs_list: 135 | <% 136 | r_name = hro_reg.name.lower() 137 | reg_addr = hro_reg.offset 138 | reg_addr_hex = format(reg_addr, 'x') 139 | regwen = hro_reg.regwen 140 | reg_mask = 0 141 | 142 | for f in hro_reg.get_field_list(): 143 | f_access = f.swaccess.key.lower() 144 | if f_access == "rw" and regwen == None: 145 | reg_mask = reg_mask | f.bits.bitmask() 146 | %>\ 147 | % if reg_mask != 0: 148 | <% reg_mask_hex = format(reg_mask, 'x') %>\ 149 | `ASSERT(${r_name}_rd_A, d2h.d_valid && pend_trans[d2h.d_source].rd_pending && 150 | pend_trans[d2h.d_source].addr == ${addr_width}'h${reg_addr_hex} |-> 151 | d2h.d_error || 152 | (d2h.d_data & 'h${reg_mask_hex}) == (exp_vals[${hro_map.get(reg_addr)[0]}] & 'h${reg_mask_hex})) 153 | 154 | % endif 155 | % endfor 156 | % endif 157 | 158 | // This FPV only assumption is to reduce the FPV runtime. 159 | `ASSUME_FPV(TlulSource_M, h2d.a_source >= 0 && h2d.a_source <= MAX_A_SOURCE, clk_i, !rst_ni) 160 | 161 | `ifdef UVM 162 | initial forever begin 163 | bit csr_assert_en; 164 | uvm_config_db#(bit)::wait_modified(null, "%m", "csr_assert_en"); 165 | if (!uvm_config_db#(bit)::get(null, "%m", "csr_assert_en", csr_assert_en)) begin 166 | `uvm_fatal("csr_assert", "Can't find csr_assert_en") 167 | end 168 | disable_sva = !csr_assert_en; 169 | end 170 | `endif 171 | 172 | `endif 173 | `endif 174 | % endif 175 | endmodule 176 | \ 177 | ${construct_classes(block)} 178 | -------------------------------------------------------------------------------- /util/reggen/reggen/gen_cfg_html.py: -------------------------------------------------------------------------------- 1 | # Copyright lowRISC contributors. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | """ 5 | Generate HTML documentation from Block 6 | """ 7 | 8 | from typing import TextIO 9 | 10 | from .ip_block import IpBlock 11 | from .html_helpers import render_td 12 | from .signal import Signal 13 | 14 | 15 | def genout(outfile: TextIO, msg: str) -> None: 16 | outfile.write(msg) 17 | 18 | 19 | def name_width(x: Signal) -> str: 20 | if x.bits.width() == 1: 21 | return x.name 22 | 23 | return '{}[{}:0]'.format(x.name, x.bits.msb) 24 | 25 | 26 | def gen_kv(outfile: TextIO, key: str, value: str) -> None: 27 | genout(outfile, 28 | '

{}: {}

\n'.format(key, value)) 29 | 30 | 31 | def gen_cfg_html(cfgs: IpBlock, outfile: TextIO) -> None: 32 | rnames = cfgs.get_rnames() 33 | 34 | ot_server = 'https://docs.opentitan.org' 35 | comport_url = ot_server + '/doc/rm/comportability_specification' 36 | genout(outfile, 37 | '

Referring to the Comportable guideline for ' 38 | 'peripheral device functionality, the module ' 39 | '{mod_name} has the following hardware ' 40 | 'interfaces defined.

\n' 41 | .format(url=comport_url, mod_name=cfgs.name)) 42 | 43 | # clocks 44 | gen_kv(outfile, 45 | 'Primary Clock', 46 | '{}'.format(cfgs.clock_signals[0])) 47 | if len(cfgs.clock_signals) > 1: 48 | other_clocks = ['{}'.format(clk) 49 | for clk in cfgs.clock_signals[1:]] 50 | gen_kv(outfile, 'Other Clocks', ', '.join(other_clocks)) 51 | else: 52 | gen_kv(outfile, 'Other Clocks', 'none') 53 | 54 | # bus interfaces 55 | dev_ports = ['{}'.format(port) 56 | for port in cfgs.bus_interfaces.get_port_names(False, True)] 57 | assert dev_ports 58 | gen_kv(outfile, 'Bus Device Interfaces (TL-UL)', ', '.join(dev_ports)) 59 | 60 | host_ports = ['{}'.format(port) 61 | for port in cfgs.bus_interfaces.get_port_names(True, False)] 62 | if host_ports: 63 | gen_kv(outfile, 'Bus Host Interfaces (TL-UL)', ', '.join(host_ports)) 64 | else: 65 | gen_kv(outfile, 'Bus Host Interfaces (TL-UL)', 'none') 66 | 67 | # IO 68 | ios = ([('input', x) for x in cfgs.xputs[1]] + 69 | [('output', x) for x in cfgs.xputs[2]] + 70 | [('inout', x) for x in cfgs.xputs[0]]) 71 | if ios: 72 | genout(outfile, "

Peripheral Pins for Chip IO:

\n") 73 | genout( 74 | outfile, "" + 75 | "" + 76 | "\n") 77 | for direction, x in ios: 78 | genout(outfile, 79 | '{}' 80 | .format(name_width(x), 81 | direction, 82 | render_td(x.desc, rnames, None))) 83 | genout(outfile, "
Pin namedirectionDescription
{}{}
\n") 84 | else: 85 | genout(outfile, "

Peripheral Pins for Chip IO: none

\n") 86 | 87 | if not cfgs.interrupts: 88 | genout(outfile, "

Interrupts: none

\n") 89 | else: 90 | genout(outfile, "

Interrupts:

\n") 91 | genout( 92 | outfile, "" + 93 | "\n") 94 | for x in cfgs.interrupts: 95 | genout(outfile, 96 | '{}' 97 | .format(name_width(x), 98 | render_td(x.desc, rnames, None))) 99 | genout(outfile, "
Interrupt NameDescription
{}
\n") 100 | 101 | if not cfgs.alerts: 102 | genout(outfile, "

Security Alerts: none

\n") 103 | else: 104 | genout(outfile, "

Security Alerts:

\n") 105 | genout( 106 | outfile, "" + 107 | "\n") 108 | for x in cfgs.alerts: 109 | genout(outfile, 110 | '{}' 111 | .format(x.name, 112 | render_td(x.desc, rnames, None))) 113 | genout(outfile, "
Alert NameDescription
{}
\n") 114 | -------------------------------------------------------------------------------- /util/reggen/reggen/gen_dv.py: -------------------------------------------------------------------------------- 1 | # Copyright lowRISC contributors. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | '''Generate DV code for an IP block''' 5 | 6 | import logging as log 7 | import os 8 | from typing import List 9 | 10 | import yaml 11 | 12 | from mako import exceptions # type: ignore 13 | from mako.lookup import TemplateLookup # type: ignore 14 | from pkg_resources import resource_filename 15 | 16 | from .ip_block import IpBlock 17 | from .register import Register 18 | from .window import Window 19 | 20 | 21 | def bcname(esc_if_name: str) -> str: 22 | '''Get the name of the dv_base_reg_block subclass for this device interface''' 23 | return esc_if_name + "_reg_block" 24 | 25 | 26 | def rcname(esc_if_name: str, r: Register) -> str: 27 | '''Get the name of the dv_base_reg subclass for this register''' 28 | return '{}_reg_{}'.format(esc_if_name, r.name.lower()) 29 | 30 | 31 | def mcname(esc_if_name: str, m: Window) -> str: 32 | '''Get the name of the dv_base_mem subclass for this memory''' 33 | return '{}_mem_{}'.format(esc_if_name, m.name.lower()) 34 | 35 | 36 | def miname(m: Window) -> str: 37 | '''Get the lower-case name of a memory block''' 38 | return m.name.lower() 39 | 40 | 41 | def gen_core_file(outdir: str, 42 | lblock: str, 43 | dv_base_prefix: str, 44 | paths: List[str]) -> None: 45 | depends = ["lowrisc:dv:dv_base_reg"] 46 | if dv_base_prefix and dv_base_prefix != "dv_base": 47 | depends.append("lowrisc:dv:{}_reg".format(dv_base_prefix)) 48 | 49 | # Generate a fusesoc core file that points at the files we've just 50 | # generated. 51 | core_data = { 52 | 'name': "lowrisc:dv:{}_ral_pkg".format(lblock), 53 | 'filesets': { 54 | 'files_dv': { 55 | 'depend': depends, 56 | 'files': paths, 57 | 'file_type': 'systemVerilogSource' 58 | }, 59 | }, 60 | 'targets': { 61 | 'default': { 62 | 'filesets': [ 63 | 'files_dv', 64 | ], 65 | }, 66 | }, 67 | } 68 | core_file_path = os.path.join(outdir, lblock + '_ral_pkg.core') 69 | with open(core_file_path, 'w') as core_file: 70 | core_file.write('CAPI=2:\n') 71 | yaml.dump(core_data, core_file, encoding='utf-8') 72 | 73 | 74 | def gen_dv(block: IpBlock, dv_base_prefix: str, outdir: str) -> int: 75 | '''Generate DV files for an IpBlock''' 76 | 77 | lookup = TemplateLookup(directories=[resource_filename('reggen', '.')]) 78 | uvm_reg_tpl = lookup.get_template('uvm_reg.sv.tpl') 79 | 80 | # Generate the RAL package(s). For a device interface with no name we 81 | # generate the package "_ral_pkg" (writing to _ral_pkg.sv). 82 | # In any other case, we also need the interface name, giving 83 | # __ral_pkg. 84 | generated = [] 85 | 86 | lblock = block.name.lower() 87 | for if_name, rb in block.reg_blocks.items(): 88 | hier_path = '' if block.hier_path is None else block.hier_path + '.' 89 | if_suffix = '' if if_name is None else '_' + if_name.lower() 90 | mod_base = lblock + if_suffix 91 | reg_block_path = hier_path + 'u_reg' + if_suffix 92 | 93 | file_name = mod_base + '_ral_pkg.sv' 94 | generated.append(file_name) 95 | reg_top_path = os.path.join(outdir, file_name) 96 | with open(reg_top_path, 'w', encoding='UTF-8') as fout: 97 | try: 98 | fout.write(uvm_reg_tpl.render(rb=rb, 99 | block=block, 100 | esc_if_name=mod_base, 101 | reg_block_path=reg_block_path, 102 | dv_base_prefix=dv_base_prefix)) 103 | except: # noqa F722 for template Exception handling 104 | log.error(exceptions.text_error_template().render()) 105 | return 1 106 | 107 | gen_core_file(outdir, lblock, dv_base_prefix, generated) 108 | return 0 109 | -------------------------------------------------------------------------------- /util/reggen/reggen/gen_fpv.py: -------------------------------------------------------------------------------- 1 | # Copyright lowRISC contributors. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | # # Lint as: python3 5 | # 6 | """Generate FPV CSR read and write assertions from IpBlock 7 | """ 8 | 9 | import logging as log 10 | import os.path 11 | 12 | import yaml 13 | from mako import exceptions 14 | from mako.template import Template 15 | from pkg_resources import resource_filename 16 | 17 | from .ip_block import IpBlock 18 | 19 | 20 | def gen_fpv(block: IpBlock, outdir): 21 | # Read Register templates 22 | fpv_csr_tpl = Template( 23 | filename=resource_filename('reggen', 'fpv_csr.sv.tpl')) 24 | 25 | # Generate a module with CSR assertions for each device interface. For a 26 | # device interface with no name, we generate _csr_assert_fpv. For a 27 | # named interface, we generate __csr_assert_fpv. 28 | lblock = block.name.lower() 29 | generated = [] 30 | for if_name, rb in block.reg_blocks.items(): 31 | if not rb.flat_regs: 32 | # No registers to check! 33 | continue 34 | 35 | if if_name is None: 36 | mod_base = lblock 37 | else: 38 | mod_base = lblock + '_' + if_name.lower() 39 | 40 | mod_name = mod_base + '_csr_assert_fpv' 41 | filename = mod_name + '.sv' 42 | generated.append(filename) 43 | reg_top_path = os.path.join(outdir, filename) 44 | with open(reg_top_path, 'w', encoding='UTF-8') as fout: 45 | try: 46 | fout.write(fpv_csr_tpl.render(block=block, 47 | mod_base=mod_base, 48 | if_name=if_name, 49 | rb=rb)) 50 | except: # noqa F722 for template Exception handling 51 | log.error(exceptions.text_error_template().render()) 52 | return 1 53 | 54 | # Generate a fusesoc core file that points at the files we've just 55 | # generated. 56 | core_data = { 57 | 'name': "lowrisc:fpv:{}_csr_assert".format(lblock), 58 | 'filesets': { 59 | 'files_dv': { 60 | 'depend': [ 61 | "lowrisc:tlul:headers", 62 | "lowrisc:prim:assert", 63 | ], 64 | 'files': generated, 65 | 'file_type': 'systemVerilogSource' 66 | }, 67 | }, 68 | 'targets': { 69 | 'default': { 70 | 'filesets': [ 71 | 'files_dv', 72 | ], 73 | }, 74 | }, 75 | } 76 | core_file_path = os.path.join(outdir, lblock + '_csr_assert_fpv.core') 77 | with open(core_file_path, 'w') as core_file: 78 | core_file.write('CAPI=2:\n') 79 | yaml.dump(core_data, core_file, encoding='utf-8') 80 | 81 | return 0 82 | -------------------------------------------------------------------------------- /util/reggen/reggen/gen_json.py: -------------------------------------------------------------------------------- 1 | # Copyright lowRISC contributors. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | """Generate JSON/compact JSON/Hjson from register JSON tree 5 | """ 6 | 7 | import hjson 8 | 9 | 10 | def gen_json(obj, outfile, format): 11 | if format == 'json': 12 | hjson.dumpJSON(obj, 13 | outfile, 14 | ensure_ascii=False, 15 | use_decimal=True, 16 | indent=' ', 17 | for_json=True) 18 | elif format == 'compact': 19 | hjson.dumpJSON(obj, 20 | outfile, 21 | ensure_ascii=False, 22 | for_json=True, 23 | use_decimal=True, 24 | separators=(',', ':')) 25 | elif format == 'hjson': 26 | hjson.dump(obj, 27 | outfile, 28 | ensure_ascii=False, 29 | for_json=True, 30 | use_decimal=True) 31 | else: 32 | raise ValueError('Invalid JSON format ' + format) 33 | 34 | return 0 35 | -------------------------------------------------------------------------------- /util/reggen/reggen/gen_rtl.py: -------------------------------------------------------------------------------- 1 | # Copyright lowRISC contributors. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | """Generate SystemVerilog designs from IpBlock object""" 5 | 6 | import logging as log 7 | import os 8 | from typing import Dict, Optional, Tuple 9 | 10 | from mako import exceptions # type: ignore 11 | from mako.template import Template # type: ignore 12 | from pkg_resources import resource_filename 13 | 14 | from .ip_block import IpBlock 15 | from .multi_register import MultiRegister 16 | from .reg_base import RegBase 17 | from .register import Register 18 | 19 | 20 | def escape_name(name: str) -> str: 21 | return name.lower().replace(' ', '_') 22 | 23 | 24 | def make_box_quote(msg: str, indent: str = ' ') -> str: 25 | hr = indent + ('/' * (len(msg) + 6)) 26 | middle = indent + '// ' + msg + ' //' 27 | return '\n'.join([hr, middle, hr]) 28 | 29 | 30 | def _get_awparam_name(iface_name: Optional[str]) -> str: 31 | return (iface_name or 'Iface').capitalize() + 'Aw' 32 | 33 | 34 | def get_addr_widths(block: IpBlock) -> Dict[Optional[str], Tuple[str, int]]: 35 | '''Return the address widths for the device interfaces 36 | 37 | Returns a dictionary keyed by interface name whose values are pairs: 38 | (paramname, width) where paramname is IfaceAw for an unnamed interface and 39 | FooAw for an interface called foo. This is constructed in the same order as 40 | block.reg_blocks. 41 | 42 | If there is a single device interface and that interface is unnamed, use 43 | the more general parameter name "BlockAw". 44 | 45 | ''' 46 | assert block.reg_blocks 47 | if len(block.reg_blocks) == 1 and None in block.reg_blocks: 48 | return {None: ('BlockAw', block.reg_blocks[None].get_addr_width())} 49 | 50 | return {name: (_get_awparam_name(name), rb.get_addr_width()) 51 | for name, rb in block.reg_blocks.items()} 52 | 53 | 54 | def get_type_name_pfx(block: IpBlock, iface_name: Optional[str]) -> str: 55 | return block.name.lower() + ('' if iface_name is None 56 | else '_{}'.format(iface_name.lower())) 57 | 58 | 59 | def get_r0(reg: RegBase) -> Register: 60 | '''Get a Register representing an entry in the RegBase''' 61 | if isinstance(reg, Register): 62 | return reg 63 | else: 64 | assert isinstance(reg, MultiRegister) 65 | return reg.reg 66 | 67 | 68 | def get_iface_tx_type(block: IpBlock, 69 | iface_name: Optional[str], 70 | hw2reg: bool) -> str: 71 | x2x = 'hw2reg' if hw2reg else 'reg2hw' 72 | pfx = get_type_name_pfx(block, iface_name) 73 | return '_'.join([pfx, x2x, 't']) 74 | 75 | 76 | def get_reg_tx_type(block: IpBlock, reg: RegBase, hw2reg: bool) -> str: 77 | '''Get the name of the hw2reg or reg2hw type for reg''' 78 | if isinstance(reg, Register): 79 | r0 = reg 80 | type_suff = 'reg_t' 81 | else: 82 | assert isinstance(reg, MultiRegister) 83 | r0 = reg.reg 84 | type_suff = 'mreg_t' 85 | 86 | x2x = 'hw2reg' if hw2reg else 'reg2hw' 87 | return '_'.join([block.name.lower(), 88 | x2x, 89 | r0.name.lower(), 90 | type_suff]) 91 | 92 | 93 | def gen_rtl(block: IpBlock, outdir: str) -> int: 94 | # Read Register templates 95 | reg_top_tpl = Template( 96 | filename=resource_filename('reggen', 'reg_top.sv.tpl')) 97 | reg_pkg_tpl = Template( 98 | filename=resource_filename('reggen', 'reg_pkg.sv.tpl')) 99 | 100 | # Generate _reg_pkg.sv 101 | # 102 | # This defines the various types used to interface between the *_reg_top 103 | # module(s) and the block itself. 104 | reg_pkg_path = os.path.join(outdir, block.name.lower() + "_reg_pkg.sv") 105 | with open(reg_pkg_path, 'w', encoding='UTF-8') as fout: 106 | try: 107 | fout.write(reg_pkg_tpl.render(block=block)) 108 | except: # noqa F722 for template Exception handling 109 | log.error(exceptions.text_error_template().render()) 110 | return 1 111 | 112 | # Generate the register block implementation(s). For a device interface 113 | # with no name we generate the register module "_reg_top" (writing 114 | # to _reg_top.sv). In any other case, we also need the interface 115 | # name, giving __reg_top. 116 | lblock = block.name.lower() 117 | for if_name, rb in block.reg_blocks.items(): 118 | if if_name is None: 119 | mod_base = lblock 120 | else: 121 | mod_base = lblock + '_' + if_name.lower() 122 | 123 | mod_name = mod_base + '_reg_top' 124 | reg_top_path = os.path.join(outdir, mod_name + '.sv') 125 | with open(reg_top_path, 'w', encoding='UTF-8') as fout: 126 | try: 127 | fout.write(reg_top_tpl.render(block=block, 128 | mod_base=mod_base, 129 | mod_name=mod_name, 130 | if_name=if_name, 131 | rb=rb)) 132 | except: # noqa F722 for template Exception handling 133 | log.error(exceptions.text_error_template().render()) 134 | return 1 135 | 136 | return 0 137 | -------------------------------------------------------------------------------- /util/reggen/reggen/gen_selfdoc.py: -------------------------------------------------------------------------------- 1 | # Copyright lowRISC contributors. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | """ 5 | Generates the documentation for the register tool 6 | 7 | """ 8 | from .access import SWACCESS_PERMITTED, HWACCESS_PERMITTED 9 | from reggen import (validate, 10 | ip_block, enum_entry, field, 11 | register, multi_register, window) 12 | 13 | 14 | def genout(outfile, msg): 15 | outfile.write(msg) 16 | 17 | 18 | doc_intro = """ 19 | 20 | 21 | 22 | The tables describe each key and the type of the value. The following 23 | types are used: 24 | 25 | Type | Description 26 | ---- | ----------- 27 | """ 28 | 29 | swaccess_intro = """ 30 | 31 | Register fields are tagged using the swaccess key to describe the 32 | permitted access and side-effects. This key must have one of these 33 | values: 34 | 35 | """ 36 | 37 | hwaccess_intro = """ 38 | 39 | Register fields are tagged using the hwaccess key to describe the 40 | permitted access from hardware logic and side-effects. This key must 41 | have one of these values: 42 | 43 | """ 44 | 45 | top_example = """ 46 | The basic structure of a register definition file is thus: 47 | 48 | ```hjson 49 | { 50 | name: "GP", 51 | regwidth: "32", 52 | registers: [ 53 | // register definitions... 54 | ] 55 | } 56 | 57 | ``` 58 | 59 | """ 60 | 61 | register_example = """ 62 | 63 | The basic register definition group will follow this pattern: 64 | 65 | ```hjson 66 | { name: "REGA", 67 | desc: "Description of register", 68 | swaccess: "rw", 69 | resval: "42", 70 | fields: [ 71 | // bit field definitions... 72 | ] 73 | } 74 | ``` 75 | 76 | The name and brief description are required. If the swaccess key is 77 | provided it describes the access pattern that will be used by all 78 | bitfields in the register that do not override with their own swaccess 79 | key. This is a useful shortcut because in most cases a register will 80 | have the same access restrictions for all fields. The reset value of 81 | the register may also be provided here or in the individual fields. If 82 | it is provided in both places then they must match, if it is provided 83 | in neither place then the reset value defaults to zero for all except 84 | write-only fields when it defaults to x. 85 | 86 | """ 87 | 88 | field_example = """ 89 | 90 | Field names should be relatively short because they will be used 91 | frequently (and need to fit in the register layout picture!) The field 92 | description is expected to be longer and will most likely make use of 93 | the Hjson ability to include multi-line strings. An example with three 94 | fields: 95 | 96 | ```hjson 97 | fields: [ 98 | { bits: "15:0", 99 | name: "RXS", 100 | desc: ''' 101 | Last 16 oversampled values of RX. These are captured at 16x the baud 102 | rate clock. This is a shift register with the most recent bit in 103 | bit 0 and the oldest in bit 15. Only valid when ENRXS is set. 104 | ''' 105 | } 106 | { bits: "16", 107 | name: "ENRXS", 108 | desc: ''' 109 | If this bit is set the receive oversampled data is collected 110 | in the RXS field. 111 | ''' 112 | } 113 | {bits: "20:19", name: "TXILVL", 114 | desc: "Trigger level for TX interrupts", 115 | resval: "2", 116 | enum: [ 117 | { value: "0", name: "txlvl1", desc: "1 character" }, 118 | { value: "1", name: "txlvl4", desc: "4 characters" }, 119 | { value: "2", name: "txlvl8", desc: "8 characters" }, 120 | { value: "3", name: "txlvl16", desc: "16 characters" } 121 | ] 122 | } 123 | ] 124 | ``` 125 | 126 | In all of these the swaccess parameter is inherited from the register 127 | level, and will be added so this key is always available to the 128 | backend. The RXS and ENRXS will default to zero reset value (unless 129 | something different is provided for the register) and will have the 130 | key added, but TXILVL expicitly sets its reset value as 2. 131 | 132 | The missing bits 17 and 18 will be treated as reserved by the tool, as 133 | will any bits between 21 and the maximum in the register. 134 | 135 | The TXILVL is an example using an enumeration to specify all valid 136 | values for the field. In this case all possible values are described, 137 | if the list is incomplete then the field is marked with the rsvdenum 138 | key so the backend can take appropriate action. (If the enum field is 139 | more than 7 bits then the checking is not done.) 140 | 141 | """ 142 | 143 | offset_intro = """ 144 | 145 | """ 146 | 147 | multi_intro = """ 148 | 149 | The multireg expands on the register required fields and will generate 150 | a list of the generated registers (that contain all required and 151 | generated keys for an actual register). 152 | 153 | """ 154 | 155 | window_intro = """ 156 | 157 | A window defines an open region of the register space that can be used 158 | for things that are not registers (for example access to a buffer ram). 159 | 160 | """ 161 | 162 | regwen_intro = """ 163 | 164 | Registers can protect themselves from software writes by using the 165 | register attribute regwen. When not an emptry string (the default 166 | value), regwen indicates that another register must be true in order 167 | to allow writes to this register. This is useful for the prevention 168 | of software modification. The register-enable register (call it 169 | REGWEN) must be one bit in width, and should default to 1 and be rw1c 170 | for preferred security control. This allows all writes to proceed 171 | until at some point software disables future modifications by clearing 172 | REGWEN. An error is reported if REGWEN does not exist, contains more 173 | than one bit, is not `rw1c` or does not default to 1. One REGWEN can 174 | protect multiple registers. The REGWEN register must precede those 175 | registers that refer to it in the .hjson register list. An example: 176 | 177 | ```hjson 178 | { name: "REGWEN", 179 | desc: "Register write enable for a bank of registers", 180 | swaccess: "rw1c", 181 | fields: [ { bits: "0", resval: "1" } ] 182 | } 183 | { name: "REGA", 184 | swaccess: "rw", 185 | regwen: "REGWEN", 186 | ... 187 | } 188 | { name: "REGB", 189 | swaccess: "rw", 190 | regwen: "REGWEN", 191 | ... 192 | } 193 | ``` 194 | """ 195 | 196 | doc_tail = """ 197 | 198 | (end of output generated by `regtool.py --doc`) 199 | 200 | """ 201 | 202 | 203 | def doc_tbl_head(outfile, use): 204 | if use is not None: 205 | genout(outfile, "\nKey | Kind | Type | Description of Value\n") 206 | genout(outfile, "--- | ---- | ---- | --------------------\n") 207 | else: 208 | genout(outfile, "\nKey | Description\n") 209 | genout(outfile, "--- | -----------\n") 210 | 211 | 212 | def doc_tbl_line(outfile, key, use, desc): 213 | if use is not None: 214 | desc_key, desc_txt = desc 215 | val_type = (validate.val_types[desc_key][0] 216 | if desc_key is not None else None) 217 | else: 218 | assert isinstance(desc, str) 219 | val_type = None 220 | desc_txt = desc 221 | 222 | if val_type is not None: 223 | genout( 224 | outfile, '{} | {} | {} | {}\n'.format(key, validate.key_use[use], 225 | val_type, desc_txt)) 226 | else: 227 | genout(outfile, key + " | " + desc_txt + "\n") 228 | 229 | 230 | def document(outfile): 231 | genout(outfile, doc_intro) 232 | for x in validate.val_types: 233 | genout( 234 | outfile, 235 | validate.val_types[x][0] + " | " + validate.val_types[x][1] + "\n") 236 | 237 | genout(outfile, swaccess_intro) 238 | doc_tbl_head(outfile, None) 239 | for key, value in SWACCESS_PERMITTED.items(): 240 | doc_tbl_line(outfile, key, None, value[0]) 241 | 242 | genout(outfile, hwaccess_intro) 243 | doc_tbl_head(outfile, None) 244 | for key, value in HWACCESS_PERMITTED.items(): 245 | doc_tbl_line(outfile, key, None, value[0]) 246 | 247 | genout( 248 | outfile, "\n\nThe top level of the JSON is a group containing " 249 | "the following keys:\n") 250 | doc_tbl_head(outfile, 1) 251 | for k, v in ip_block.REQUIRED_FIELDS.items(): 252 | doc_tbl_line(outfile, k, 'r', v) 253 | for k, v in ip_block.OPTIONAL_FIELDS.items(): 254 | doc_tbl_line(outfile, k, 'o', v) 255 | genout(outfile, top_example) 256 | 257 | genout( 258 | outfile, "\n\nThe list of registers includes register definition " 259 | "groups containing the following keys:\n") 260 | doc_tbl_head(outfile, 1) 261 | for k, v in register.REQUIRED_FIELDS.items(): 262 | doc_tbl_line(outfile, k, 'r', v) 263 | for k, v in register.OPTIONAL_FIELDS.items(): 264 | doc_tbl_line(outfile, k, 'o', v) 265 | genout(outfile, register_example) 266 | 267 | genout( 268 | outfile, "\n\nIn the fields list each field definition is a group " 269 | "itself containing the following keys:\n") 270 | doc_tbl_head(outfile, 1) 271 | for k, v in field.REQUIRED_FIELDS.items(): 272 | doc_tbl_line(outfile, k, 'r', v) 273 | for k, v in field.OPTIONAL_FIELDS.items(): 274 | doc_tbl_line(outfile, k, 'o', v) 275 | genout(outfile, field_example) 276 | 277 | genout(outfile, "\n\nDefinitions in an enumeration group contain:\n") 278 | doc_tbl_head(outfile, 1) 279 | for k, v in enum_entry.REQUIRED_FIELDS.items(): 280 | doc_tbl_line(outfile, k, 'r', v) 281 | 282 | genout( 283 | outfile, "\n\nThe list of registers may include single entry groups " 284 | "to control the offset, open a window or generate registers:\n") 285 | doc_tbl_head(outfile, 1) 286 | for x in validate.list_optone: 287 | doc_tbl_line(outfile, x, 'o', validate.list_optone[x]) 288 | 289 | genout(outfile, offset_intro) 290 | genout(outfile, regwen_intro) 291 | 292 | genout(outfile, window_intro) 293 | doc_tbl_head(outfile, 1) 294 | for k, v in window.REQUIRED_FIELDS.items(): 295 | doc_tbl_line(outfile, k, 'r', v) 296 | for k, v in window.OPTIONAL_FIELDS.items(): 297 | doc_tbl_line(outfile, k, 'o', v) 298 | 299 | genout(outfile, multi_intro) 300 | doc_tbl_head(outfile, 1) 301 | for k, v in multi_register.REQUIRED_FIELDS.items(): 302 | doc_tbl_line(outfile, k, 'r', v) 303 | for k, v in multi_register.OPTIONAL_FIELDS.items(): 304 | doc_tbl_line(outfile, k, 'o', v) 305 | 306 | genout(outfile, doc_tail) 307 | -------------------------------------------------------------------------------- /util/reggen/reggen/html_helpers.py: -------------------------------------------------------------------------------- 1 | # Copyright lowRISC contributors. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | import logging as log 6 | import re 7 | from typing import List, Match, Optional, Set 8 | 9 | 10 | def expand_paras(s: str, rnames: Set[str]) -> List[str]: 11 | '''Expand a description field to HTML. 12 | 13 | This supports a sort of simple pseudo-markdown. Supported Markdown 14 | features: 15 | 16 | - Separate paragraphs on a blank line 17 | - **bold** and *italicised* text 18 | - Back-ticks for pre-formatted text 19 | 20 | We also generate links to registers when a name is prefixed with a double 21 | exclamation mark. For example, if there is a register FOO then !!FOO or 22 | !!FOO.field will generate a link to that register. 23 | 24 | Returns a list of rendered paragraphs 25 | 26 | ''' 27 | # Start by splitting into paragraphs. The regex matches a newline followed 28 | # by one or more lines that just contain whitespace. Then render each 29 | # paragraph with the _expand_paragraph worker function. 30 | paras = [_expand_paragraph(paragraph.strip(), rnames) 31 | for paragraph in re.split(r'\n(?:\s*\n)+', s)] 32 | 33 | # There will always be at least one paragraph (splitting an empty string 34 | # gives ['']) 35 | assert paras 36 | return paras 37 | 38 | 39 | def _expand_paragraph(s: str, rnames: Set[str]) -> str: 40 | '''Expand a single paragraph, as described in _get_desc_paras''' 41 | def fieldsub(match: Match[str]) -> str: 42 | base = match.group(1).partition('.')[0].lower() 43 | if base in rnames: 44 | if match.group(1)[-1] == ".": 45 | return ('' + 46 | match.group(1)[:-1] + '.') 47 | else: 48 | return ('' + 49 | match.group(1) + '') 50 | log.warn('!!' + match.group(1).partition('.')[0] + 51 | ' not found in register list.') 52 | return match.group(0) 53 | 54 | # Split out pre-formatted text. Because the call to re.split has a capture 55 | # group in the regex, we get an odd number of results. Elements with even 56 | # indices are "normal text". Those with odd indices are the captured text 57 | # between the back-ticks. 58 | code_split = re.split(r'`([^`]+)`', s) 59 | expanded_parts = [] 60 | 61 | for idx, part in enumerate(code_split): 62 | if idx & 1: 63 | # Text contained in back ticks 64 | expanded_parts.append('{}'.format(part)) 65 | continue 66 | 67 | part = re.sub(r"!!([A-Za-z0-9_.]+)", fieldsub, part) 68 | part = re.sub(r"(?s)\*\*(.+?)\*\*", r'\1', part) 69 | part = re.sub(r"\*([^*]+?)\*", r'\1', part) 70 | expanded_parts.append(part) 71 | 72 | return '

{}

'.format(''.join(expanded_parts)) 73 | 74 | 75 | def render_td(s: str, rnames: Set[str], td_class: Optional[str]) -> str: 76 | '''Expand a description field and put it in a . 77 | 78 | Returns a string. See _get_desc_paras for the format that gets expanded. 79 | 80 | ''' 81 | desc_paras = expand_paras(s, rnames) 82 | class_attr = '' if td_class is None else ' class="{}"'.format(td_class) 83 | return '{}'.format(class_attr, ''.join(desc_paras)) 84 | -------------------------------------------------------------------------------- /util/reggen/reggen/inter_signal.py: -------------------------------------------------------------------------------- 1 | # Copyright lowRISC contributors. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | from typing import Dict, Optional 6 | 7 | from .lib import (check_keys, check_name, 8 | check_str, check_optional_str, check_int) 9 | 10 | 11 | class InterSignal: 12 | def __init__(self, 13 | name: str, 14 | desc: Optional[str], 15 | struct: str, 16 | package: Optional[str], 17 | signal_type: str, 18 | act: str, 19 | width: int, 20 | default: Optional[str]): 21 | assert 0 < width 22 | self.name = name 23 | self.desc = desc 24 | self.struct = struct 25 | self.package = package 26 | self.signal_type = signal_type 27 | self.act = act 28 | self.width = width 29 | self.default = default 30 | 31 | @staticmethod 32 | def from_raw(what: str, raw: object) -> 'InterSignal': 33 | rd = check_keys(raw, what, 34 | ['name', 'struct', 'type', 'act'], 35 | ['desc', 'package', 'width', 'default']) 36 | 37 | name = check_name(rd['name'], 'name field of ' + what) 38 | 39 | r_desc = rd.get('desc') 40 | if r_desc is None: 41 | desc = None 42 | else: 43 | desc = check_str(r_desc, 'desc field of ' + what) 44 | 45 | struct = check_str(rd['struct'], 'struct field of ' + what) 46 | 47 | r_package = rd.get('package') 48 | if r_package is None or r_package == '': 49 | package = None 50 | else: 51 | package = check_name(r_package, 'package field of ' + what) 52 | 53 | signal_type = check_name(rd['type'], 'type field of ' + what) 54 | act = check_name(rd['act'], 'act field of ' + what) 55 | width = check_int(rd.get('width', 1), 'width field of ' + what) 56 | if width <= 0: 57 | raise ValueError('width field of {} is not positive.'.format(what)) 58 | 59 | default = check_optional_str(rd.get('default'), 60 | 'default field of ' + what) 61 | 62 | return InterSignal(name, desc, struct, package, 63 | signal_type, act, width, default) 64 | 65 | def _asdict(self) -> Dict[str, object]: 66 | ret = {'name': self.name} # type: Dict[str, object] 67 | if self.desc is not None: 68 | ret['desc'] = self.desc 69 | ret['struct'] = self.struct 70 | if self.package is not None: 71 | ret['package'] = self.package 72 | ret['type'] = self.signal_type 73 | ret['act'] = self.act 74 | ret['width'] = self.width 75 | if self.default is not None: 76 | ret['default'] = self.default 77 | 78 | return ret 79 | 80 | def as_dict(self) -> Dict[str, object]: 81 | return self._asdict() 82 | -------------------------------------------------------------------------------- /util/reggen/reggen/lib.py: -------------------------------------------------------------------------------- 1 | # Copyright lowRISC contributors. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | '''Parsing support code for reggen''' 6 | 7 | import re 8 | from typing import Dict, List, Optional, cast 9 | 10 | 11 | # Names that are prohibited (used as reserved keywords in systemverilog) 12 | _VERILOG_KEYWORDS = { 13 | 'alias', 'always', 'always_comb', 'always_ff', 'always_latch', 'and', 14 | 'assert', 'assign', 'assume', 'automatic', 'before', 'begin', 'bind', 15 | 'bins', 'binsof', 'bit', 'break', 'buf', 'bufif0', 'bufif1', 'byte', 16 | 'case', 'casex', 'casez', 'cell', 'chandle', 'class', 'clocking', 'cmos', 17 | 'config', 'const', 'constraint', 'context', 'continue', 'cover', 18 | 'covergroup', 'coverpoint', 'cross', 'deassign', 'default', 'defparam', 19 | 'design', 'disable', 'dist', 'do', 'edge', 'else', 'end', 'endcase', 20 | 'endclass', 'endclocking', 'endconfig', 'endfunction', 'endgenerate', 21 | 'endgroup', 'endinterface', 'endmodule', 'endpackage', 'endprimitive', 22 | 'endprogram', 'endproperty', 'endspecify', 'endsequence', 'endtable', 23 | 'endtask', 'enum', 'event', 'expect', 'export', 'extends', 'extern', 24 | 'final', 'first_match', 'for', 'force', 'foreach', 'forever', 'fork', 25 | 'forkjoin', 'function', 'generate', 'genvar', 'highz0', 'highz1', 'if', 26 | 'iff', 'ifnone', 'ignore_bins', 'illegal_bins', 'import', 'incdir', 27 | 'include', 'initial', 'inout', 'input', 'inside', 'instance', 'int', 28 | 'integer', 'interface', 'intersect', 'join', 'join_any', 'join_none', 29 | 'large', 'liblist', 'library', 'local', 'localparam', 'logic', 'longint', 30 | 'macromodule', 'matches', 'medium', 'modport', 'module', 'nand', 'negedge', 31 | 'new', 'nmos', 'nor', 'noshowcancelled', 'not', 'notif0', 'notif1', 'null', 32 | 'or', 'output', 'package', 'packed', 'parameter', 'pmos', 'posedge', 33 | 'primitive', 'priority', 'program', 'property', 'protected', 'pull0', 34 | 'pull1', 'pulldown', 'pullup', 'pulsestyle_onevent', 'pulsestyle_ondetect', 35 | 'pure', 'rand', 'randc', 'randcase', 'randsequence', 'rcmos', 'real', 36 | 'realtime', 'ref', 'reg', 'release', 'repeat', 'return', 'rnmos', 'rpmos', 37 | 'rtran', 'rtranif0', 'rtranif1', 'scalared', 'sequence', 'shortint', 38 | 'shortreal', 'showcancelled', 'signed', 'small', 'solve', 'specify', 39 | 'specparam', 'static', 'string', 'strong0', 'strong1', 'struct', 'super', 40 | 'supply0', 'supply1', 'table', 'tagged', 'task', 'this', 'throughout', 41 | 'time', 'timeprecision', 'timeunit', 'tran', 'tranif0', 'tranif1', 'tri', 42 | 'tri0', 'tri1', 'triand', 'trior', 'trireg', 'type', 'typedef', 'union', 43 | 'unique', 'unsigned', 'use', 'uwire', 'var', 'vectored', 'virtual', 'void', 44 | 'wait', 'wait_order', 'wand', 'weak0', 'weak1', 'while', 'wildcard', 45 | 'wire', 'with', 'within', 'wor', 'xnor', 'xor' 46 | } 47 | 48 | 49 | def check_str_dict(obj: object, what: str) -> Dict[str, object]: 50 | if not isinstance(obj, dict): 51 | raise ValueError("{} is expected to be a dict, but was actually a {}." 52 | .format(what, type(obj).__name__)) 53 | 54 | for key in obj: 55 | if not isinstance(key, str): 56 | raise ValueError('{} has a key {!r}, which is not a string.' 57 | .format(what, key)) 58 | 59 | return cast(Dict[str, object], obj) 60 | 61 | 62 | def check_keys(obj: object, 63 | what: str, 64 | required_keys: List[str], 65 | optional_keys: List[str]) -> Dict[str, object]: 66 | '''Check that obj is a dict object with the expected keys 67 | 68 | If not, raise a ValueError; the what argument names the object. 69 | 70 | ''' 71 | od = check_str_dict(obj, what) 72 | 73 | allowed = set() 74 | missing = [] 75 | for key in required_keys: 76 | assert key not in allowed 77 | allowed.add(key) 78 | if key not in od: 79 | missing.append(key) 80 | 81 | for key in optional_keys: 82 | assert key not in allowed 83 | allowed.add(key) 84 | 85 | unexpected = [] 86 | for key in od: 87 | if key not in allowed: 88 | unexpected.append(key) 89 | 90 | if missing or unexpected: 91 | mstr = ('The following required fields were missing: {}.' 92 | .format(', '.join(missing)) if missing else '') 93 | ustr = ('The following unexpected fields were found: {}.' 94 | .format(', '.join(unexpected)) if unexpected else '') 95 | raise ValueError("{} doesn't have the right keys. {}{}{}" 96 | .format(what, 97 | mstr, 98 | ' ' if mstr and ustr else '', 99 | ustr)) 100 | 101 | return od 102 | 103 | 104 | def check_str(obj: object, what: str) -> str: 105 | '''Check that the given object is a string 106 | 107 | If not, raise a ValueError; the what argument names the object. 108 | 109 | ''' 110 | if not isinstance(obj, str): 111 | raise ValueError('{} is of type {}, not a string.' 112 | .format(what, type(obj).__name__)) 113 | return obj 114 | 115 | 116 | def check_name(obj: object, what: str) -> str: 117 | '''Check that obj is a string that's a valid name. 118 | 119 | If not, raise a ValueError; the what argument names the object. 120 | 121 | ''' 122 | as_str = check_str(obj, what) 123 | 124 | # Allow the usual symbol constituents (alphanumeric plus underscore; no 125 | # leading numbers) 126 | if not re.match(r'[a-zA-Z_][a-zA-Z_0-9]*$', as_str): 127 | raise ValueError("{} is {!r}, which isn't a valid symbol in " 128 | "C / Verilog, so isn't allowed as a name." 129 | .format(what, as_str)) 130 | 131 | # Also check that this isn't a reserved word. 132 | if as_str in _VERILOG_KEYWORDS: 133 | raise ValueError("{} is {!r}, which is a reserved word in " 134 | "SystemVerilog, so isn't allowed as a name." 135 | .format(what, as_str)) 136 | 137 | return as_str 138 | 139 | 140 | def check_bool(obj: object, what: str) -> bool: 141 | '''Check that obj is a bool or a string that parses to a bool. 142 | 143 | If not, raise a ValueError; the what argument names the object. 144 | 145 | ''' 146 | if isinstance(obj, str): 147 | as_bool = { 148 | 'true': True, 149 | 'false': False, 150 | '1': True, 151 | '0': False 152 | }.get(obj.lower()) 153 | if as_bool is None: 154 | raise ValueError('{} is {!r}, which cannot be parsed as a bool.' 155 | .format(what, obj)) 156 | return as_bool 157 | 158 | if obj is True or obj is False: 159 | return obj 160 | 161 | raise ValueError('{} is of type {}, not a bool.' 162 | .format(what, type(obj).__name__)) 163 | 164 | 165 | def check_list(obj: object, what: str) -> List[object]: 166 | '''Check that the given object is a list 167 | 168 | If not, raise a ValueError; the what argument names the object. 169 | 170 | ''' 171 | if not isinstance(obj, list): 172 | raise ValueError('{} is of type {}, not a list.' 173 | .format(what, type(obj).__name__)) 174 | return obj 175 | 176 | 177 | def check_str_list(obj: object, what: str) -> List[str]: 178 | '''Check that the given object is a list of strings 179 | 180 | If not, raise a ValueError; the what argument names the object. 181 | 182 | ''' 183 | lst = check_list(obj, what) 184 | for idx, elt in enumerate(lst): 185 | if not isinstance(elt, str): 186 | raise ValueError('Element {} of {} is of type {}, ' 187 | 'not a string.' 188 | .format(idx, what, type(elt).__name__)) 189 | return cast(List[str], lst) 190 | 191 | 192 | def check_name_list(obj: object, what: str) -> List[str]: 193 | '''Check that the given object is a list of valid names 194 | 195 | If not, raise a ValueError; the what argument names the object. 196 | 197 | ''' 198 | lst = check_list(obj, what) 199 | for idx, elt in enumerate(lst): 200 | check_name(elt, 'Element {} of {}'.format(idx + 1, what)) 201 | 202 | return cast(List[str], lst) 203 | 204 | 205 | def check_int(obj: object, what: str) -> int: 206 | '''Check that obj is an integer or a string that parses to an integer. 207 | 208 | If not, raise a ValueError; the what argument names the object. 209 | 210 | ''' 211 | if isinstance(obj, int): 212 | return obj 213 | 214 | if isinstance(obj, str): 215 | try: 216 | return int(obj, 0) 217 | except ValueError: 218 | raise ValueError('{} is {!r}, which cannot be parsed as an int.' 219 | .format(what, obj)) from None 220 | 221 | raise ValueError('{} is of type {}, not an integer.' 222 | .format(what, type(obj).__name__)) 223 | 224 | 225 | def check_xint(obj: object, what: str) -> Optional[int]: 226 | '''Check that obj is an integer, a string that parses to an integer or "x". 227 | 228 | On success, return an integer value if there is one or None if the value 229 | was 'x'. On failure, raise a ValueError; the what argument names the 230 | object. 231 | 232 | ''' 233 | if isinstance(obj, int): 234 | return obj 235 | 236 | if isinstance(obj, str): 237 | if obj == 'x': 238 | return None 239 | try: 240 | return int(obj, 0) 241 | except ValueError: 242 | raise ValueError('{} is {!r}, which is not "x", ' 243 | 'nor can it be parsed as an int.' 244 | .format(what, obj)) from None 245 | 246 | raise ValueError('{} is of type {}, not an integer.' 247 | .format(what, type(obj).__name__)) 248 | 249 | 250 | def check_optional_str(obj: object, what: str) -> Optional[str]: 251 | '''Check that obj is a string or None''' 252 | return None if obj is None else check_str(obj, what) 253 | 254 | 255 | def get_basename(name: str) -> str: 256 | '''Strip trailing _number (used as multireg suffix) from name''' 257 | # TODO: This is a workaround, should solve this as part of parsing a 258 | # multi-reg. 259 | match = re.search(r'_[0-9]+$', name) 260 | assert match 261 | assert match.start() > 0 262 | return name[0:match.start()] 263 | -------------------------------------------------------------------------------- /util/reggen/reggen/multi_register.py: -------------------------------------------------------------------------------- 1 | # Copyright lowRISC contributors. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | from typing import Dict, List 6 | 7 | from reggen import register 8 | from .field import Field 9 | from .lib import check_keys, check_str, check_name, check_bool 10 | from .params import ReggenParams 11 | from .reg_base import RegBase 12 | from .register import Register 13 | 14 | REQUIRED_FIELDS = { 15 | 'name': ['s', "base name of the registers"], 16 | 'desc': ['t', "description of the registers"], 17 | 'count': [ 18 | 's', "number of instances to generate." 19 | " This field can be integer or string matching" 20 | " from param_list." 21 | ], 22 | 'cname': [ 23 | 's', "base name for each instance, mostly" 24 | " useful for referring to instance in messages." 25 | ], 26 | 'fields': [ 27 | 'l', "list of register field description" 28 | " groups. Describes bit positions used for" 29 | " base instance." 30 | ] 31 | } 32 | OPTIONAL_FIELDS = register.OPTIONAL_FIELDS.copy() 33 | OPTIONAL_FIELDS.update({ 34 | 'regwen_multi': [ 35 | 'pb', "If true, regwen term increments" 36 | " along with current multireg count." 37 | ], 38 | 'compact': [ 39 | 'pb', "If true, allow multireg compacting." 40 | "If false, do not compact." 41 | ] 42 | }) 43 | 44 | 45 | class MultiRegister(RegBase): 46 | def __init__(self, 47 | offset: int, 48 | addrsep: int, 49 | reg_width: int, 50 | params: ReggenParams, 51 | raw: object): 52 | super().__init__(offset) 53 | 54 | rd = check_keys(raw, 'multireg', 55 | list(REQUIRED_FIELDS.keys()), 56 | list(OPTIONAL_FIELDS.keys())) 57 | 58 | # Now that we've checked the schema of rd, we make a "reg" version of 59 | # it that removes any fields that are allowed by MultiRegister but 60 | # aren't allowed by Register. We'll pass that to the register factory 61 | # method. 62 | reg_allowed_keys = (set(register.REQUIRED_FIELDS.keys()) | 63 | set(register.OPTIONAL_FIELDS.keys())) 64 | reg_rd = {key: value 65 | for key, value in rd.items() 66 | if key in reg_allowed_keys} 67 | self.reg = Register.from_raw(reg_width, offset, params, reg_rd) 68 | 69 | self.cname = check_name(rd['cname'], 70 | 'cname field of multireg {}' 71 | .format(self.reg.name)) 72 | 73 | self.regwen_multi = check_bool(rd.get('regwen_multi', False), 74 | 'regwen_multi field of multireg {}' 75 | .format(self.reg.name)) 76 | 77 | default_compact = True if len(self.reg.fields) == 1 else False 78 | self.compact = check_bool(rd.get('compact', default_compact), 79 | 'compact field of multireg {}' 80 | .format(self.reg.name)) 81 | if self.compact and len(self.reg.fields) > 1: 82 | raise ValueError('Multireg {} sets the compact flag ' 83 | 'but has multiple fields.' 84 | .format(self.reg.name)) 85 | 86 | count_str = check_str(rd['count'], 87 | 'count field of multireg {}' 88 | .format(self.reg.name)) 89 | self.count = params.expand(count_str, 90 | 'count field of multireg ' + self.reg.name) 91 | if self.count <= 0: 92 | raise ValueError("Multireg {} has a count of {}, " 93 | "which isn't positive." 94 | .format(self.reg.name, self.count)) 95 | 96 | # Generate the registers that this multireg expands into. Here, a 97 | # "creg" is a "compacted register", which might contain multiple actual 98 | # registers. 99 | if self.compact: 100 | assert len(self.reg.fields) == 1 101 | width_per_reg = self.reg.fields[0].bits.msb + 1 102 | assert width_per_reg <= reg_width 103 | regs_per_creg = reg_width // width_per_reg 104 | else: 105 | regs_per_creg = 1 106 | 107 | self.regs = [] 108 | creg_count = (self.count + regs_per_creg - 1) // regs_per_creg 109 | for creg_idx in range(creg_count): 110 | min_reg_idx = regs_per_creg * creg_idx 111 | max_reg_idx = min(min_reg_idx + regs_per_creg, self.count) - 1 112 | creg_offset = offset + creg_idx * addrsep 113 | 114 | reg = self.reg.make_multi(reg_width, 115 | creg_offset, creg_idx, creg_count, 116 | self.regwen_multi, self.compact, 117 | min_reg_idx, max_reg_idx, self.cname) 118 | self.regs.append(reg) 119 | 120 | def next_offset(self, addrsep: int) -> int: 121 | return self.offset + len(self.regs) * addrsep 122 | 123 | def get_n_bits(self, bittype: List[str] = ["q"]) -> int: 124 | return sum(reg.get_n_bits(bittype) for reg in self.regs) 125 | 126 | def get_field_list(self) -> List[Field]: 127 | ret = [] 128 | for reg in self.regs: 129 | ret += reg.get_field_list() 130 | return ret 131 | 132 | def is_homogeneous(self) -> bool: 133 | return self.reg.is_homogeneous() 134 | 135 | def _asdict(self) -> Dict[str, object]: 136 | rd = self.reg._asdict() 137 | rd['count'] = str(self.count) 138 | rd['cname'] = self.cname 139 | rd['regwen_multi'] = str(self.regwen_multi) 140 | rd['compact'] = str(self.compact) 141 | 142 | return {'multireg': rd} 143 | -------------------------------------------------------------------------------- /util/reggen/reggen/reg_base.py: -------------------------------------------------------------------------------- 1 | # Copyright lowRISC contributors. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | from typing import List 6 | 7 | from .field import Field 8 | 9 | 10 | class RegBase: 11 | '''An abstract class inherited by Register and MultiRegister 12 | 13 | This represents a block of one or more registers with a base address. 14 | 15 | ''' 16 | def __init__(self, offset: int): 17 | self.offset = offset 18 | 19 | def get_n_bits(self, bittype: List[str]) -> int: 20 | '''Get the size of this register / these registers in bits 21 | 22 | See Field.get_n_bits() for the precise meaning of bittype. 23 | 24 | ''' 25 | raise NotImplementedError() 26 | 27 | def get_field_list(self) -> List[Field]: 28 | '''Get an ordered list of the fields in the register(s) 29 | 30 | Registers are ordered from low to high address. Within a register, 31 | fields are ordered as Register.fields: from LSB to MSB. 32 | 33 | ''' 34 | raise NotImplementedError() 35 | 36 | def is_homogeneous(self) -> bool: 37 | '''True if every field in the block is identical 38 | 39 | For a single register, this is true if it only has one field. For a 40 | multireg, it is true if the generating register has just one field. 41 | Note that if the compact flag is set, the generated registers might 42 | have multiple (replicated) fields. 43 | 44 | ''' 45 | raise NotImplementedError() 46 | -------------------------------------------------------------------------------- /util/reggen/reggen/reg_html.css: -------------------------------------------------------------------------------- 1 | /* Stylesheet for reggen HTML register output */ 2 | /* Copyright lowRISC contributors. */ 3 | /* Licensed under the Apache License, Version 2.0, see LICENSE for details. */ 4 | /* SPDX-License-Identifier: Apache-2.0 */ 5 | 6 | table.regpic { 7 | width: 95%; 8 | border-collapse: collapse; 9 | margin-left:auto; 10 | margin-right:auto; 11 | table-layout:fixed; 12 | } 13 | 14 | table.regdef { 15 | border: 1px solid black; 16 | width: 80%; 17 | border-collapse: collapse; 18 | margin-left:auto; 19 | margin-right:auto; 20 | table-layout:auto; 21 | } 22 | 23 | table.regdef th { 24 | border: 1px solid black; 25 | font-family: sans-serif; 26 | 27 | } 28 | 29 | td.bitnum { 30 | font-size: 60%; 31 | text-align: center; 32 | } 33 | 34 | td.unused { 35 | border: 1px solid black; 36 | background-color: gray; 37 | } 38 | 39 | td.fname { 40 | border: 1px solid black; 41 | text-align: center; 42 | font-family: sans-serif; 43 | } 44 | 45 | 46 | td.regbits, td.regperm, td.regrv { 47 | border: 1px solid black; 48 | text-align: center; 49 | font-family: sans-serif; 50 | } 51 | 52 | td.regde, td.regfn { 53 | border: 1px solid black; 54 | } 55 | 56 | table.cfgtable { 57 | border: 1px solid black; 58 | width: 80%; 59 | border-collapse: collapse; 60 | margin-left:auto; 61 | margin-right:auto; 62 | table-layout:auto; 63 | } 64 | 65 | table.cfgtable th { 66 | border: 1px solid black; 67 | font-family: sans-serif; 68 | font-weight: bold; 69 | } 70 | 71 | table.cfgtable td { 72 | border: 1px solid black; 73 | font-family: sans-serif; 74 | } 75 | -------------------------------------------------------------------------------- /util/reggen/reggen/reg_pkg.sv.tpl: -------------------------------------------------------------------------------- 1 | // Copyright lowRISC contributors. 2 | // Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | // Register Package auto-generated by `reggen` containing data structure 6 | <% 7 | from topgen import lib # TODO: Split lib to common lib module 8 | 9 | from reggen.access import HwAccess, SwRdAccess, SwWrAccess 10 | from reggen.register import Register 11 | from reggen.multi_register import MultiRegister 12 | 13 | from reggen import gen_rtl 14 | 15 | localparams = block.params.get_localparams() 16 | 17 | addr_widths = gen_rtl.get_addr_widths(block) 18 | 19 | lblock = block.name.lower() 20 | ublock = lblock.upper() 21 | 22 | def reg_pfx(reg): 23 | return '{}_{}'.format(ublock, reg.name.upper()) 24 | 25 | def reg_resname(reg): 26 | return '{}_RESVAL'.format(reg_pfx(reg)) 27 | 28 | def field_resname(reg, field): 29 | return '{}_{}_RESVAL'.format(reg_pfx(reg), field.name.upper()) 30 | 31 | %>\ 32 | <%def name="typedefs_for_iface(iface_name, iface_desc, for_iface, rb)">\ 33 | <% 34 | hdr = gen_rtl.make_box_quote('Typedefs for registers' + for_iface) 35 | %>\ 36 | % for r in rb.all_regs: 37 | % if r.get_n_bits(["q"]): 38 | % if hdr: 39 | 40 | ${hdr} 41 | % endif 42 | <% 43 | r0 = gen_rtl.get_r0(r) 44 | hdr = None 45 | %>\ 46 | 47 | typedef struct packed { 48 | % if r.is_homogeneous(): 49 | ## If we have a homogeneous register or multireg, there is just one field 50 | ## (possibly replicated many times). The typedef is for one copy of that 51 | ## field. 52 | <% 53 | field = r.get_field_list()[0] 54 | field_q_width = field.get_n_bits(r0.hwext, ['q']) 55 | field_q_bits = lib.bitarray(field_q_width, 2) 56 | %>\ 57 | logic ${field_q_bits} q; 58 | % if field.hwqe: 59 | logic qe; 60 | % endif 61 | % if field.hwre or (r0.shadowed and r0.hwext): 62 | logic re; 63 | % endif 64 | % if r0.shadowed and not r0.hwext: 65 | logic err_update; 66 | logic err_storage; 67 | % endif 68 | % else: 69 | ## We are inhomogeneous, which means there is more than one different 70 | ## field. Generate a reg2hw typedef that packs together all the fields of 71 | ## the register. 72 | % for f in r0.fields: 73 | % if f.get_n_bits(r0.hwext, ["q"]) >= 1: 74 | <% 75 | field_q_width = f.get_n_bits(r0.hwext, ['q']) 76 | field_q_bits = lib.bitarray(field_q_width, 2) 77 | 78 | struct_name = f.name.lower() 79 | %>\ 80 | struct packed { 81 | logic ${field_q_bits} q; 82 | % if f.hwqe: 83 | logic qe; 84 | % endif 85 | % if f.hwre or (r0.shadowed and r0.hwext): 86 | logic re; 87 | % endif 88 | % if r0.shadowed and not r0.hwext: 89 | logic err_update; 90 | logic err_storage; 91 | % endif 92 | } ${struct_name}; 93 | %endif 94 | %endfor 95 | %endif 96 | } ${gen_rtl.get_reg_tx_type(block, r, False)}; 97 | %endif 98 | % endfor 99 | % for r in rb.all_regs: 100 | % if r.get_n_bits(["d"]): 101 | % if hdr: 102 | 103 | ${hdr} 104 | % endif 105 | <% 106 | r0 = gen_rtl.get_r0(r) 107 | hdr = None 108 | %>\ 109 | 110 | typedef struct packed { 111 | % if r.is_homogeneous(): 112 | ## If we have a homogeneous register or multireg, there is just one field 113 | ## (possibly replicated many times). The typedef is for one copy of that 114 | ## field. 115 | <% 116 | field = r.get_field_list()[0] 117 | field_d_width = field.get_n_bits(r0.hwext, ['d']) 118 | field_d_bits = lib.bitarray(field_d_width, 2) 119 | %>\ 120 | logic ${field_d_bits} d; 121 | % if not r0.hwext: 122 | logic de; 123 | % endif 124 | % else: 125 | ## We are inhomogeneous, which means there is more than one different 126 | ## field. Generate a hw2reg typedef that packs together all the fields of 127 | ## the register. 128 | % for f in r0.fields: 129 | % if f.get_n_bits(r0.hwext, ["d"]) >= 1: 130 | <% 131 | field_d_width = f.get_n_bits(r0.hwext, ['d']) 132 | field_d_bits = lib.bitarray(field_d_width, 2) 133 | 134 | struct_name = f.name.lower() 135 | %>\ 136 | struct packed { 137 | logic ${field_d_bits} d; 138 | % if not r0.hwext: 139 | logic de; 140 | % endif 141 | } ${struct_name}; 142 | %endif 143 | %endfor 144 | %endif 145 | } ${gen_rtl.get_reg_tx_type(block, r, True)}; 146 | % endif 147 | % endfor 148 | \ 149 | <%def name="reg2hw_for_iface(iface_name, iface_desc, for_iface, rb)">\ 150 | <% 151 | nbits = rb.get_n_bits(["q", "qe", "re"]) 152 | packbit = 0 153 | %>\ 154 | % if nbits > 0: 155 | 156 | // Register -> HW type${for_iface} 157 | typedef struct packed { 158 | % for r in rb.all_regs: 159 | % if r.get_n_bits(["q"]): 160 | <% 161 | r0 = gen_rtl.get_r0(r) 162 | struct_type = gen_rtl.get_reg_tx_type(block, r, False) 163 | struct_width = r0.get_n_bits(['q', 'qe', 're']) 164 | 165 | if isinstance(r, MultiRegister): 166 | struct_type += " [{}:0]".format(r.count - 1) 167 | struct_width *= r.count 168 | 169 | msb = nbits - packbit - 1 170 | lsb = msb - struct_width + 1 171 | packbit += struct_width 172 | %>\ 173 | ${struct_type} ${r0.name.lower()}; // [${msb}:${lsb}] 174 | % endif 175 | % endfor 176 | } ${gen_rtl.get_iface_tx_type(block, iface_name, False)}; 177 | % endif 178 | \ 179 | <%def name="hw2reg_for_iface(iface_name, iface_desc, for_iface, rb)">\ 180 | <% 181 | nbits = rb.get_n_bits(["d", "de"]) 182 | packbit = 0 183 | %>\ 184 | % if nbits > 0: 185 | 186 | // HW -> register type${for_iface} 187 | typedef struct packed { 188 | % for r in rb.all_regs: 189 | % if r.get_n_bits(["d"]): 190 | <% 191 | r0 = gen_rtl.get_r0(r) 192 | struct_type = gen_rtl.get_reg_tx_type(block, r, True) 193 | struct_width = r0.get_n_bits(['d', 'de']) 194 | 195 | if isinstance(r, MultiRegister): 196 | struct_type += " [{}:0]".format(r.count - 1) 197 | struct_width *= r.count 198 | 199 | msb = nbits - packbit - 1 200 | lsb = msb - struct_width + 1 201 | packbit += struct_width 202 | %>\ 203 | ${struct_type} ${r0.name.lower()}; // [${msb}:${lsb}] 204 | % endif 205 | % endfor 206 | } ${gen_rtl.get_iface_tx_type(block, iface_name, True)}; 207 | % endif 208 | \ 209 | <%def name="offsets_for_iface(iface_name, iface_desc, for_iface, rb)">\ 210 | % if not rb.flat_regs: 211 | <% return STOP_RENDERING %> 212 | % endif 213 | 214 | // Register offsets${for_iface} 215 | <% 216 | aw_name, aw = addr_widths[iface_name] 217 | %>\ 218 | % for r in rb.flat_regs: 219 | <% 220 | value = "{}'h {:x}".format(aw, r.offset) 221 | %>\ 222 | parameter logic [${aw_name}-1:0] ${reg_pfx(r)}_OFFSET = ${value}; 223 | % endfor 224 | \ 225 | <%def name="hwext_resvals_for_iface(iface_name, iface_desc, for_iface, rb)">\ 226 | <% 227 | hwext_regs = [r for r in rb.flat_regs if r.hwext] 228 | %>\ 229 | % if hwext_regs: 230 | 231 | // Reset values for hwext registers and their fields${for_iface} 232 | % for reg in hwext_regs: 233 | <% 234 | reg_width = reg.get_width() 235 | reg_msb = reg_width - 1 236 | reg_resval = "{}'h {:x}".format(reg_width, reg.resval) 237 | %>\ 238 | parameter logic [${reg_msb}:0] ${reg_resname(reg)} = ${reg_resval}; 239 | % for field in reg.fields: 240 | % if field.resval is not None: 241 | <% 242 | field_width = field.bits.width() 243 | field_msb = field_width - 1 244 | field_resval = "{}'h {:x}".format(field_width, field.resval) 245 | %>\ 246 | parameter logic [${field_msb}:0] ${field_resname(reg, field)} = ${field_resval}; 247 | % endif 248 | % endfor 249 | % endfor 250 | % endif 251 | \ 252 | <%def name="windows_for_iface(iface_name, iface_desc, for_iface, rb)">\ 253 | % if rb.windows: 254 | <% 255 | aw_name, aw = addr_widths[iface_name] 256 | %>\ 257 | 258 | // Window parameters${for_iface} 259 | % for i,w in enumerate(rb.windows): 260 | <% 261 | win_pfx = '{}_{}'.format(ublock, w.name.upper()) 262 | base_txt_val = "{}'h {:x}".format(aw, w.offset) 263 | size_txt_val = "'h {:x}".format(w.size_in_bytes) 264 | 265 | offset_type = 'logic [{}-1:0]'.format(aw_name) 266 | size_type = 'int unsigned' 267 | max_type_len = max(len(offset_type), len(size_type)) 268 | 269 | offset_type += ' ' * (max_type_len - len(offset_type)) 270 | size_type += ' ' * (max_type_len - len(size_type)) 271 | 272 | %>\ 273 | parameter ${offset_type} ${win_pfx}_OFFSET = ${base_txt_val}; 274 | parameter ${size_type} ${win_pfx}_SIZE = ${size_txt_val}; 275 | % endfor 276 | % endif 277 | \ 278 | <%def name="reg_data_for_iface(iface_name, iface_desc, for_iface, rb)">\ 279 | % if rb.flat_regs: 280 | <% 281 | lpfx = gen_rtl.get_type_name_pfx(block, iface_name) 282 | upfx = lpfx.upper() 283 | idx_len = len("{}".format(len(rb.flat_regs) - 1)) 284 | %>\ 285 | 286 | // Register index${for_iface} 287 | typedef enum int { 288 | % for r in rb.flat_regs: 289 | ${ublock}_${r.name.upper()}${"" if loop.last else ","} 290 | % endfor 291 | } ${lpfx}_id_e; 292 | 293 | // Register width information to check illegal writes${for_iface} 294 | parameter logic [3:0] ${upfx}_PERMIT [${len(rb.flat_regs)}] = '{ 295 | % for i, r in enumerate(rb.flat_regs): 296 | <% 297 | index_str = "{}".format(i).rjust(idx_len) 298 | width = r.get_width() 299 | if width > 24: 300 | mask = '1111' 301 | elif width > 16: 302 | mask = '0111' 303 | elif width > 8: 304 | mask = '0011' 305 | else: 306 | mask = '0001' 307 | 308 | comma = ',' if i < len(rb.flat_regs) - 1 else ' ' 309 | %>\ 310 | 4'b ${mask}${comma} // index[${index_str}] ${ublock}_${r.name.upper()} 311 | % endfor 312 | }; 313 | % endif 314 | \ 315 | 316 | package ${lblock}_reg_pkg; 317 | % if localparams: 318 | 319 | // Param list 320 | % for param in localparams: 321 | parameter ${param.param_type} ${param.name} = ${param.value}; 322 | % endfor 323 | % endif 324 | 325 | // Address widths within the block 326 | % for param_name, width in addr_widths.values(): 327 | parameter int ${param_name} = ${width}; 328 | % endfor 329 | <% 330 | just_default = len(block.reg_blocks) == 1 and None in block.reg_blocks 331 | %>\ 332 | % for iface_name, rb in block.reg_blocks.items(): 333 | <% 334 | iface_desc = iface_name or 'default' 335 | for_iface = '' if just_default else ' for {} interface'.format(iface_desc) 336 | %>\ 337 | ${typedefs_for_iface(iface_name, iface_desc, for_iface, rb)}\ 338 | ${reg2hw_for_iface(iface_name, iface_desc, for_iface, rb)}\ 339 | ${hw2reg_for_iface(iface_name, iface_desc, for_iface, rb)}\ 340 | ${offsets_for_iface(iface_name, iface_desc, for_iface, rb)}\ 341 | ${hwext_resvals_for_iface(iface_name, iface_desc, for_iface, rb)}\ 342 | ${windows_for_iface(iface_name, iface_desc, for_iface, rb)}\ 343 | ${reg_data_for_iface(iface_name, iface_desc, for_iface, rb)}\ 344 | % endfor 345 | 346 | endpackage 347 | 348 | -------------------------------------------------------------------------------- /util/reggen/reggen/signal.py: -------------------------------------------------------------------------------- 1 | # Copyright lowRISC contributors. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | from typing import Dict, Sequence 6 | 7 | from .bits import Bits 8 | from .lib import check_keys, check_name, check_str, check_int, check_list 9 | 10 | 11 | class Signal: 12 | def __init__(self, name: str, desc: str, bits: Bits): 13 | self.name = name 14 | self.desc = desc 15 | self.bits = bits 16 | 17 | @staticmethod 18 | def from_raw(what: str, lsb: int, raw: object) -> 'Signal': 19 | rd = check_keys(raw, what, 20 | ['name', 'desc'], 21 | ['width']) 22 | 23 | name = check_name(rd['name'], 'name field of ' + what) 24 | desc = check_str(rd['desc'], 'desc field of ' + what) 25 | width = check_int(rd.get('width', 1), 'width field of ' + what) 26 | if width <= 0: 27 | raise ValueError('The width field of signal {} ({}) ' 28 | 'has value {}, but should be positive.' 29 | .format(name, what, width)) 30 | 31 | bits = Bits(lsb + width - 1, lsb) 32 | 33 | return Signal(name, desc, bits) 34 | 35 | @staticmethod 36 | def from_raw_list(what: str, raw: object) -> Sequence['Signal']: 37 | lsb = 0 38 | ret = [] 39 | for idx, entry in enumerate(check_list(raw, what)): 40 | entry_what = 'entry {} of {}'.format(idx, what) 41 | interrupt = Signal.from_raw(entry_what, lsb, entry) 42 | ret.append(interrupt) 43 | lsb += interrupt.bits.width() 44 | return ret 45 | 46 | def _asdict(self) -> Dict[str, object]: 47 | return { 48 | 'name': self.name, 49 | 'desc': self.desc, 50 | 'width': str(self.bits.width()) 51 | } 52 | 53 | def as_nwt_dict(self, type_field: str) -> Dict[str, object]: 54 | '''Return a view of the signal as a dictionary 55 | 56 | The dictionary has fields "name", "width" and "type", the last 57 | of which comes from the type_field argument. Used for topgen 58 | integration. 59 | 60 | ''' 61 | return {'name': self.name, 62 | 'width': self.bits.width(), 63 | 'type': type_field} 64 | -------------------------------------------------------------------------------- /util/reggen/reggen/uvm_reg.sv.tpl: -------------------------------------------------------------------------------- 1 | // Copyright lowRISC contributors. 2 | // Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | // UVM Registers auto-generated by `reggen` containing data structure 6 | ## 7 | ## 8 | ## We use functions from uvm_reg_base.sv.tpl to define 9 | ## per-device-interface code. 10 | ## 11 | <%namespace file="uvm_reg_base.sv.tpl" import="*"/>\ 12 | ## 13 | ## 14 | ${make_ral_pkg(dv_base_prefix, block.regwidth, reg_block_path, rb, esc_if_name)} 15 | -------------------------------------------------------------------------------- /util/reggen/reggen/validate.py: -------------------------------------------------------------------------------- 1 | # Copyright lowRISC contributors. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | """ 5 | Register JSON validation 6 | """ 7 | 8 | import logging as log 9 | 10 | 11 | # validating version of int(x, 0) 12 | # returns int value, error flag 13 | # if error flag is True value will be zero 14 | def check_int(x, err_prefix, suppress_err_msg=False): 15 | if isinstance(x, int): 16 | return x, False 17 | if x[0] == '0' and len(x) > 2: 18 | if x[1] in 'bB': 19 | validch = '01' 20 | elif x[1] in 'oO': 21 | validch = '01234567' 22 | elif x[1] in 'xX': 23 | validch = '0123456789abcdefABCDEF' 24 | else: 25 | if not suppress_err_msg: 26 | log.error(err_prefix + 27 | ": int must start digit, 0b, 0B, 0o, 0O, 0x or 0X") 28 | return 0, True 29 | for c in x[2:]: 30 | if c not in validch: 31 | if not suppress_err_msg: 32 | log.error(err_prefix + ": Bad character " + c + " in " + x) 33 | return 0, True 34 | else: 35 | if not x.isdecimal(): 36 | if not suppress_err_msg: 37 | log.error(err_prefix + ": Number not valid int " + x) 38 | return 0, True 39 | return int(x, 0), False 40 | 41 | 42 | def check_bool(x, err_prefix): 43 | """check_bool checks if input 'x' is one of the list: 44 | "true", "false" 45 | 46 | It returns value as Bool type and Error condition. 47 | """ 48 | if isinstance(x, bool): 49 | # if Bool returns as it is 50 | return x, False 51 | if not x.lower() in ["true", "false"]: 52 | log.error(err_prefix + ": Bad field value " + x) 53 | return False, True 54 | else: 55 | return (x.lower() == "true"), False 56 | 57 | 58 | def check_ln(obj, x, withwidth, err_prefix): 59 | error = 0 60 | if not isinstance(obj[x], list): 61 | log.error(err_prefix + ' element ' + x + ' not a list') 62 | return 1 63 | for y in obj[x]: 64 | error += check_keys(y, ln_required, ln_optional if withwidth else {}, 65 | {}, err_prefix + ' element ' + x) 66 | if withwidth: 67 | if 'width' in y: 68 | w, err = check_int(y['width'], err_prefix + ' width in ' + x) 69 | if err: 70 | error += 1 71 | w = 1 72 | else: 73 | w = 1 74 | y['width'] = str(w) 75 | 76 | return error 77 | 78 | 79 | def check_keys(obj, required_keys, optional_keys, added_keys, err_prefix): 80 | error = 0 81 | for x in required_keys: 82 | if x not in obj: 83 | error += 1 84 | log.error(err_prefix + " missing required key " + x) 85 | for x in obj: 86 | type = None 87 | if x in required_keys: 88 | type = required_keys[x][0] 89 | elif x in optional_keys: 90 | type = optional_keys[x][0] 91 | elif x not in added_keys: 92 | log.warning(err_prefix + " contains extra key " + x) 93 | if type is not None: 94 | if type[:2] == 'ln': 95 | error += check_ln(obj, x, type == 'lnw', err_prefix) 96 | 97 | return error 98 | 99 | 100 | val_types = { 101 | 'd': ["int", "integer (binary 0b, octal 0o, decimal, hex 0x)"], 102 | 'x': ["xint", "x for undefined otherwise int"], 103 | 'b': [ 104 | "bitrange", "bit number as decimal integer, " 105 | "or bit-range as decimal integers msb:lsb" 106 | ], 107 | 'l': ["list", "comma separated list enclosed in `[]`"], 108 | 'ln': [ 109 | "name list", 'comma separated list enclosed in `[]` of ' 110 | 'one or more groups that have just name and dscr keys.' 111 | ' e.g. `{ name: "name", desc: "description"}`' 112 | ], 113 | 'lnw': ["name list+", 'name list that optionally contains a width'], 114 | 'lp': ["parameter list", 'parameter list having default value optionally'], 115 | 'g': ["group", "comma separated group of key:value enclosed in `{}`"], 116 | 'lg': [ 117 | "list of group", "comma separated group of key:value enclosed in `{}`" 118 | " the second entry of the list is the sub group format" 119 | ], 120 | 's': ["string", "string, typically short"], 121 | 't': [ 122 | "text", "string, may be multi-line enclosed in `'''` " 123 | "may use `**bold**`, `*italic*` or `!!Reg` markup" 124 | ], 125 | 'T': ["tuple", "tuple enclosed in ()"], 126 | 'pi': ["python int", "Native Python type int (generated)"], 127 | 'pb': ["python Bool", "Native Python type Bool (generated)"], 128 | 'pl': ["python list", "Native Python type list (generated)"], 129 | 'pe': ["python enum", "Native Python type enum (generated)"] 130 | } 131 | 132 | # ln type has list of groups with only name and description 133 | # (was called "subunit" in cfg_validate) 134 | ln_required = { 135 | 'name': ['s', "name of the item"], 136 | 'desc': ['s', "description of the item"], 137 | } 138 | ln_optional = { 139 | 'width': ['d', "bit width of the item (if not 1)"], 140 | } 141 | 142 | # Registers list may have embedded keys 143 | list_optone = { 144 | 'reserved': ['d', "number of registers to reserve space for"], 145 | 'skipto': ['d', "set next register offset to value"], 146 | 'window': [ 147 | 'g', "group defining an address range " 148 | "for something other than standard registers" 149 | ], 150 | 'multireg': 151 | ['g', "group defining registers generated " 152 | "from a base instance."] 153 | } 154 | 155 | key_use = {'r': "required", 'o': "optional", 'a': "added by tool"} 156 | -------------------------------------------------------------------------------- /util/reggen/reggen/version.py: -------------------------------------------------------------------------------- 1 | # Copyright lowRISC contributors. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | r"""Standard version printing 5 | """ 6 | import os 7 | import subprocess 8 | import sys 9 | 10 | import pkg_resources # part of setuptools 11 | 12 | 13 | def show_and_exit(clitool, packages): 14 | util_path = os.path.dirname(os.path.realpath(clitool)) 15 | os.chdir(util_path) 16 | ver = subprocess.run( 17 | ["git", "describe", "--always", "--dirty", "--broken"], 18 | stdout=subprocess.PIPE).stdout.strip().decode('ascii') 19 | if (ver == ''): 20 | ver = 'not found (not in Git repository?)' 21 | sys.stderr.write(clitool + " Git version " + ver + '\n') 22 | for p in packages: 23 | sys.stderr.write(p + ' ' + pkg_resources.require(p)[0].version + '\n') 24 | exit(0) 25 | -------------------------------------------------------------------------------- /util/reggen/reggen/window.py: -------------------------------------------------------------------------------- 1 | # Copyright lowRISC contributors. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | from typing import Dict 6 | 7 | from .access import SWAccess 8 | from .lib import check_keys, check_str, check_bool, check_int 9 | from .params import ReggenParams 10 | 11 | 12 | REQUIRED_FIELDS = { 13 | 'name': ['s', "name of the window"], 14 | 'desc': ['t', "description of the window"], 15 | 'items': ['d', "size in fieldaccess width words of the window"], 16 | 'swaccess': ['s', "software access permitted"], 17 | } 18 | 19 | # TODO potential for additional optional to give more type info? 20 | # eg sram-hw-port: "none", "sync", "async" 21 | OPTIONAL_FIELDS = { 22 | 'data-intg-passthru': [ 23 | 's', "True if the window has data integrity pass through. " 24 | "Defaults to false if not present." 25 | ], 26 | 'byte-write': [ 27 | 's', "True if byte writes are supported. " 28 | "Defaults to false if not present." 29 | ], 30 | 'validbits': [ 31 | 'd', "Number of valid data bits within " 32 | "regwidth sized word. " 33 | "Defaults to regwidth. If " 34 | "smaller than the regwidth then in each " 35 | "word of the window bits " 36 | "[regwidth-1:validbits] are unused and " 37 | "bits [validbits-1:0] are valid." 38 | ], 39 | 'unusual': [ 40 | 's', "True if window has unusual parameters " 41 | "(set to prevent Unusual: errors)." 42 | "Defaults to false if not present." 43 | ] 44 | } 45 | 46 | 47 | class Window: 48 | '''A class representing a memory window''' 49 | def __init__(self, 50 | name: str, 51 | desc: str, 52 | unusual: bool, 53 | byte_write: bool, 54 | data_intg_passthru: bool, 55 | validbits: int, 56 | items: int, 57 | size_in_bytes: int, 58 | offset: int, 59 | swaccess: SWAccess): 60 | assert 0 < validbits 61 | assert 0 < items <= size_in_bytes 62 | 63 | self.name = name 64 | self.desc = desc 65 | self.unusual = unusual 66 | self.byte_write = byte_write 67 | self.data_intg_passthru = data_intg_passthru 68 | self.validbits = validbits 69 | self.items = items 70 | self.size_in_bytes = size_in_bytes 71 | self.offset = offset 72 | self.swaccess = swaccess 73 | 74 | # Check that offset has been adjusted so that the first item in the 75 | # window has all zeros in the low bits. 76 | po2_size = 1 << (self.size_in_bytes - 1).bit_length() 77 | assert not (offset & (po2_size - 1)) 78 | 79 | @staticmethod 80 | def from_raw(offset: int, 81 | reg_width: int, 82 | params: ReggenParams, 83 | raw: object) -> 'Window': 84 | rd = check_keys(raw, 'window', 85 | list(REQUIRED_FIELDS.keys()), 86 | list(OPTIONAL_FIELDS.keys())) 87 | 88 | wind_desc = 'window at offset {:#x}'.format(offset) 89 | name = check_str(rd['name'], wind_desc) 90 | wind_desc = '{!r} {}'.format(name, wind_desc) 91 | 92 | desc = check_str(rd['desc'], 'desc field for ' + wind_desc) 93 | 94 | unusual = check_bool(rd.get('unusual', False), 95 | 'unusual field for ' + wind_desc) 96 | byte_write = check_bool(rd.get('byte-write', False), 97 | 'byte-write field for ' + wind_desc) 98 | data_intg_passthru = check_bool(rd.get('data-intg-passthru', False), 99 | 'data-intg-passthru field for ' + wind_desc) 100 | 101 | validbits = check_int(rd.get('validbits', reg_width), 102 | 'validbits field for ' + wind_desc) 103 | if validbits <= 0: 104 | raise ValueError('validbits field for {} is not positive.' 105 | .format(wind_desc)) 106 | if validbits > reg_width: 107 | raise ValueError('validbits field for {} is {}, ' 108 | 'which is greater than {}, the register width.' 109 | .format(wind_desc, validbits, reg_width)) 110 | 111 | r_items = check_str(rd['items'], 'items field for ' + wind_desc) 112 | items = params.expand(r_items, 'items field for ' + wind_desc) 113 | if items <= 0: 114 | raise ValueError("Items field for {} is {}, " 115 | "which isn't positive." 116 | .format(wind_desc, items)) 117 | 118 | assert reg_width % 8 == 0 119 | size_in_bytes = items * (reg_width // 8) 120 | 121 | # Round size_in_bytes up to the next power of 2. The calculation is 122 | # like clog2 calculations in SystemVerilog, where we start with the 123 | # last index, rather than the number of elements. 124 | assert size_in_bytes > 0 125 | po2_size = 1 << (size_in_bytes - 1).bit_length() 126 | 127 | # A size that isn't a power of 2 is not allowed unless the unusual flag 128 | # is set. 129 | if po2_size != size_in_bytes and not unusual: 130 | raise ValueError('Items field for {} is {}, which gives a size of ' 131 | '{} bytes. This is not a power of 2 (next power ' 132 | 'of 2 is {}). If you want to do this even so, ' 133 | 'set the "unusual" flag.' 134 | .format(wind_desc, items, 135 | size_in_bytes, po2_size)) 136 | 137 | # Adjust offset if necessary to make sure the base address of the first 138 | # item in the window has all zeros in the low bits. 139 | addr_mask = po2_size - 1 140 | if offset & addr_mask: 141 | offset = (offset | addr_mask) + 1 142 | offset = offset 143 | 144 | swaccess = SWAccess(wind_desc, rd['swaccess']) 145 | if not (swaccess.value[4] or unusual): 146 | raise ValueError('swaccess field for {} is {}, which is an ' 147 | 'unusual access type for a window. If you want ' 148 | 'to do this, set the "unusual" flag.' 149 | .format(wind_desc, swaccess.key)) 150 | 151 | return Window(name, desc, unusual, byte_write, data_intg_passthru, 152 | validbits, items, size_in_bytes, offset, swaccess) 153 | 154 | def next_offset(self, addrsep: int) -> int: 155 | return self.offset + self.size_in_bytes 156 | 157 | def _asdict(self) -> Dict[str, object]: 158 | rd = { 159 | 'desc': self.desc, 160 | 'items': self.items, 161 | 'swaccess': self.swaccess.key, 162 | 'byte-write': self.byte_write, 163 | 'validbits': self.validbits, 164 | 'unusual': self.unusual 165 | } 166 | if self.name is not None: 167 | rd['name'] = self.name 168 | 169 | return {'window': rd} 170 | -------------------------------------------------------------------------------- /util/reggen/regtool.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright lowRISC contributors. 3 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 4 | # SPDX-License-Identifier: Apache-2.0 5 | r"""Command-line tool to validate and convert register hjson 6 | 7 | """ 8 | import argparse 9 | import logging as log 10 | import re 11 | import sys 12 | from pathlib import PurePath 13 | 14 | from reggen import (gen_cheader, gen_dv, gen_fpv, gen_html, 15 | gen_json, gen_rtl, gen_selfdoc, version) 16 | from reggen.ip_block import IpBlock 17 | 18 | DESC = """regtool, generate register info from Hjson source""" 19 | 20 | USAGE = ''' 21 | regtool [options] 22 | regtool [options] 23 | regtool (-h | --help) 24 | regtool (-V | --version) 25 | ''' 26 | 27 | 28 | def main(): 29 | verbose = 0 30 | 31 | parser = argparse.ArgumentParser( 32 | prog="regtool", 33 | formatter_class=argparse.RawDescriptionHelpFormatter, 34 | usage=USAGE, 35 | description=DESC) 36 | parser.add_argument('input', 37 | nargs='?', 38 | metavar='file', 39 | type=argparse.FileType('r'), 40 | default=sys.stdin, 41 | help='input file in Hjson type') 42 | parser.add_argument('-d', 43 | action='store_true', 44 | help='Output register documentation (html)') 45 | parser.add_argument('--cdefines', 46 | '-D', 47 | action='store_true', 48 | help='Output C defines header') 49 | parser.add_argument('--doc', 50 | action='store_true', 51 | help='Output source file documentation (gfm)') 52 | parser.add_argument('-j', 53 | action='store_true', 54 | help='Output as formatted JSON') 55 | parser.add_argument('-c', action='store_true', help='Output as JSON') 56 | parser.add_argument('-r', 57 | action='store_true', 58 | help='Output as SystemVerilog RTL') 59 | parser.add_argument('-s', 60 | action='store_true', 61 | help='Output as UVM Register class') 62 | parser.add_argument('-f', 63 | action='store_true', 64 | help='Output as FPV CSR rw assertion module') 65 | parser.add_argument('--outdir', 66 | '-t', 67 | help='Target directory for generated RTL; ' 68 | 'tool uses ../rtl if blank.') 69 | parser.add_argument('--dv-base-prefix', 70 | default='dv_base', 71 | help='Prefix for the DV register classes from which ' 72 | 'the register models are derived.') 73 | parser.add_argument('--outfile', 74 | '-o', 75 | type=argparse.FileType('w'), 76 | default=sys.stdout, 77 | help='Target filename for json, html, gfm.') 78 | parser.add_argument('--verbose', 79 | '-v', 80 | action='store_true', 81 | help='Verbose and run validate twice') 82 | parser.add_argument('--param', 83 | '-p', 84 | type=str, 85 | default="", 86 | help='''Change the Parameter values. 87 | Only integer value is supported. 88 | You can add multiple param arguments. 89 | 90 | Format: ParamA=ValA;ParamB=ValB 91 | ''') 92 | parser.add_argument('--version', 93 | '-V', 94 | action='store_true', 95 | help='Show version') 96 | parser.add_argument('--novalidate', 97 | action='store_true', 98 | help='Skip validate, just output json') 99 | 100 | args = parser.parse_args() 101 | 102 | if args.version: 103 | version.show_and_exit(__file__, ["Hjson", "Mako"]) 104 | 105 | verbose = args.verbose 106 | if (verbose): 107 | log.basicConfig(format="%(levelname)s: %(message)s", level=log.DEBUG) 108 | else: 109 | log.basicConfig(format="%(levelname)s: %(message)s") 110 | 111 | # Entries are triples of the form (arg, (format, dirspec)). 112 | # 113 | # arg is the name of the argument that selects the format. format is the 114 | # name of the format. dirspec is None if the output is a single file; if 115 | # the output needs a directory, it is a default path relative to the source 116 | # file (used when --outdir is not given). 117 | arg_to_format = [('j', ('json', None)), ('c', ('compact', None)), 118 | ('d', ('html', None)), ('doc', ('doc', None)), 119 | ('r', ('rtl', 'rtl')), ('s', ('dv', 'dv')), 120 | ('f', ('fpv', 'fpv/vip')), ('cdefines', ('cdh', None))] 121 | format = None 122 | dirspec = None 123 | for arg_name, spec in arg_to_format: 124 | if getattr(args, arg_name): 125 | if format is not None: 126 | log.error('Multiple output formats specified on ' 127 | 'command line ({} and {}).'.format(format, spec[0])) 128 | sys.exit(1) 129 | format, dirspec = spec 130 | if format is None: 131 | format = 'hjson' 132 | 133 | infile = args.input 134 | 135 | # Split parameters into key=value pairs. 136 | raw_params = args.param.split(';') if args.param else [] 137 | params = [] 138 | for idx, raw_param in enumerate(raw_params): 139 | tokens = raw_param.split('=') 140 | if len(tokens) != 2: 141 | raise ValueError('Entry {} in list of parameter defaults to ' 142 | 'apply is {!r}, which is not of the form ' 143 | 'param=value.' 144 | .format(idx, raw_param)) 145 | params.append((tokens[0], tokens[1])) 146 | 147 | # Define either outfile or outdir (but not both), depending on the output 148 | # format. 149 | outfile = None 150 | outdir = None 151 | if dirspec is None: 152 | if args.outdir is not None: 153 | log.error('The {} format expects an output file, ' 154 | 'not an output directory.'.format(format)) 155 | sys.exit(1) 156 | 157 | outfile = args.outfile 158 | else: 159 | if args.outfile is not sys.stdout: 160 | log.error('The {} format expects an output directory, ' 161 | 'not an output file.'.format(format)) 162 | sys.exit(1) 163 | 164 | if args.outdir is not None: 165 | outdir = args.outdir 166 | elif infile is not sys.stdin: 167 | outdir = str(PurePath(infile.name).parents[1].joinpath(dirspec)) 168 | else: 169 | # We're using sys.stdin, so can't infer an output directory name 170 | log.error( 171 | 'The {} format writes to an output directory, which ' 172 | 'cannot be inferred automatically if the input comes ' 173 | 'from stdin. Use --outdir to specify it manually.'.format( 174 | format)) 175 | sys.exit(1) 176 | 177 | if format == 'doc': 178 | with outfile: 179 | gen_selfdoc.document(outfile) 180 | exit(0) 181 | 182 | srcfull = infile.read() 183 | 184 | try: 185 | obj = IpBlock.from_text(srcfull, params, infile.name) 186 | except ValueError as err: 187 | log.error(str(err)) 188 | exit(1) 189 | 190 | if args.novalidate: 191 | with outfile: 192 | gen_json.gen_json(obj, outfile, format) 193 | outfile.write('\n') 194 | else: 195 | if format == 'rtl': 196 | return gen_rtl.gen_rtl(obj, outdir) 197 | if format == 'dv': 198 | return gen_dv.gen_dv(obj, args.dv_base_prefix, outdir) 199 | if format == 'fpv': 200 | return gen_fpv.gen_fpv(obj, outdir) 201 | src_lic = None 202 | src_copy = '' 203 | found_spdx = None 204 | found_lunder = None 205 | copy = re.compile(r'.*(copyright.*)|(.*\(c\).*)', re.IGNORECASE) 206 | spdx = re.compile(r'.*(SPDX-License-Identifier:.+)') 207 | lunder = re.compile(r'.*(Licensed under.+)', re.IGNORECASE) 208 | for line in srcfull.splitlines(): 209 | mat = copy.match(line) 210 | if mat is not None: 211 | src_copy += mat.group(1) 212 | mat = spdx.match(line) 213 | if mat is not None: 214 | found_spdx = mat.group(1) 215 | mat = lunder.match(line) 216 | if mat is not None: 217 | found_lunder = mat.group(1) 218 | if found_lunder: 219 | src_lic = found_lunder 220 | if found_spdx: 221 | if src_lic is None: 222 | src_lic = '\n' + found_spdx 223 | else: 224 | src_lic += '\n' + found_spdx 225 | 226 | with outfile: 227 | if format == 'html': 228 | return gen_html.gen_html(obj, outfile) 229 | elif format == 'cdh': 230 | return gen_cheader.gen_cdefines(obj, outfile, src_lic, src_copy) 231 | else: 232 | return gen_json.gen_json(obj, outfile, format) 233 | 234 | outfile.write('\n') 235 | 236 | 237 | if __name__ == '__main__': 238 | sys.exit(main()) 239 | -------------------------------------------------------------------------------- /util/reggen/topgen/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright lowRISC contributors. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | from .lib import get_hjsonobj_xbars, search_ips # noqa: F401 6 | # noqa: F401 These functions are used in topgen.py 7 | from .merge import amend_clocks, merge_top # noqa: F401 8 | from .validate import validate_top, check_flash # noqa: F401 9 | -------------------------------------------------------------------------------- /util/reggen/topgen/gen_dv.py: -------------------------------------------------------------------------------- 1 | # Copyright lowRISC contributors. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | import logging as log 6 | from typing import Optional, Tuple 7 | 8 | from mako import exceptions # type: ignore 9 | from mako.lookup import TemplateLookup # type: ignore 10 | from pkg_resources import resource_filename 11 | 12 | from reggen.gen_dv import gen_core_file 13 | 14 | from .top import Top 15 | 16 | 17 | def sv_base_addr(top: Top, if_name: Tuple[str, Optional[str]]) -> str: 18 | '''Get the base address of a device interface in SV syntax''' 19 | return "{}'h{:x}".format(top.regwidth, top.if_addrs[if_name]) 20 | 21 | 22 | def gen_dv(top: Top, 23 | dv_base_prefix: str, 24 | outdir: str) -> int: 25 | '''Generate DV RAL model for a Top''' 26 | # Read template 27 | lookup = TemplateLookup(directories=[resource_filename('topgen', '.'), 28 | resource_filename('reggen', '.')]) 29 | uvm_reg_tpl = lookup.get_template('top_uvm_reg.sv.tpl') 30 | 31 | # Expand template 32 | try: 33 | to_write = uvm_reg_tpl.render(top=top, 34 | dv_base_prefix=dv_base_prefix) 35 | except: # noqa: E722 36 | log.error(exceptions.text_error_template().render()) 37 | return 1 38 | 39 | # Dump to output file 40 | dest_path = '{}/chip_ral_pkg.sv'.format(outdir) 41 | with open(dest_path, 'w') as fout: 42 | fout.write(to_write) 43 | 44 | gen_core_file(outdir, 'chip', dv_base_prefix, ['chip_ral_pkg.sv']) 45 | 46 | return 0 47 | -------------------------------------------------------------------------------- /util/reggen/topgen/templates/README.md: -------------------------------------------------------------------------------- 1 | # OpenTitan topgen templates 2 | 3 | This directory contains templates used by topgen to assembly a chip toplevel. 4 | 5 | -------------------------------------------------------------------------------- /util/reggen/topgen/templates/chip_env_pkg__params.sv.tpl: -------------------------------------------------------------------------------- 1 | // Copyright lowRISC contributors. 2 | // Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | // Generated by topgen.py 6 | 7 | parameter string LIST_OF_ALERTS[] = { 8 | % for alert in top["alert"]: 9 | % if loop.last: 10 | "${alert["name"]}" 11 | % else: 12 | "${alert["name"]}", 13 | % endif 14 | % endfor 15 | }; 16 | 17 | parameter uint NUM_ALERTS = ${len(top["alert"])}; 18 | -------------------------------------------------------------------------------- /util/reggen/topgen/templates/clang-format: -------------------------------------------------------------------------------- 1 | # This disables clang-format on all files in the sw/autogen directory. 2 | # This is needed so that git-clang-format and similar scripts work. 3 | DisableFormat: true 4 | SortIncludes: false 5 | -------------------------------------------------------------------------------- /util/reggen/topgen/templates/tb__alert_handler_connect.sv.tpl: -------------------------------------------------------------------------------- 1 | // Copyright lowRISC contributors. 2 | // Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | // tb__alert_handler_connect.sv is auto-generated by `topgen.py` tool 6 | 7 | <% 8 | index = 0 9 | module_name = "" 10 | %>\ 11 | % for alert in top["alert"]: 12 | % if alert["module_name"] == module_name: 13 | <% index = index + 1 %>\ 14 | % else: 15 | <% 16 | module_name = alert["module_name"] 17 | index = 0 18 | %>\ 19 | % endif 20 | assign alert_if[${loop.index}].alert_tx = `CHIP_HIER.u_${module_name}.alert_tx_o[${index}]; 21 | % endfor 22 | -------------------------------------------------------------------------------- /util/reggen/topgen/templates/tb__xbar_connect.sv.tpl: -------------------------------------------------------------------------------- 1 | // Copyright lowRISC contributors. 2 | // Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | // tb__xbar_connect generated by `topgen.py` tool 6 | <% 7 | from collections import OrderedDict 8 | import topgen.lib as lib 9 | 10 | top_hier = 'tb.dut.top_' + top["name"] + '.' 11 | clk_hier = top_hier + top["clocks"]["hier_paths"]["top"] 12 | 13 | clk_src = OrderedDict() 14 | for xbar in top["xbar"]: 15 | for clk, src in xbar["clock_srcs"].items(): 16 | clk_src[clk] = src 17 | 18 | clk_freq = OrderedDict() 19 | for clock in top["clocks"]["srcs"] + top["clocks"]["derived_srcs"]: 20 | if clock["name"] in clk_src.values(): 21 | clk_freq[clock["name"]] = clock["freq"] 22 | 23 | hosts = OrderedDict() 24 | devices = OrderedDict() 25 | for xbar in top["xbar"]: 26 | for node in xbar["nodes"]: 27 | if node["type"] == "host" and not node["xbar"]: 28 | hosts[node["name"]] = "clk_" + clk_src[node["clock"]] 29 | elif node["type"] == "device" and not node["xbar"]: 30 | devices[node["name"]] = "clk_" + clk_src[node["clock"]] 31 | 32 | def escape_if_name(qual_if_name): 33 | return qual_if_name.replace('.', '__') 34 | 35 | %>\ 36 | <%text> 37 | `define DRIVE_CHIP_TL_HOST_IF(tl_name, inst_name, sig_name) \ 38 | force ``tl_name``_tl_if.d2h = dut.top_earlgrey.u_``inst_name``.``sig_name``_i; \ 39 | force dut.top_earlgrey.u_``inst_name``.``sig_name``_o = ``tl_name``_tl_if.h2d; \ 40 | force dut.top_earlgrey.u_``inst_name``.clk_i = 0; \ 41 | uvm_config_db#(virtual tl_if)::set(null, $sformatf("*%0s*", `"tl_name`"), "vif", \ 42 | ``tl_name``_tl_if); 43 | 44 | `define DRIVE_CHIP_TL_DEVICE_IF(tl_name, inst_name, sig_name) \ 45 | force ``tl_name``_tl_if.h2d = dut.top_earlgrey.u_``inst_name``.``sig_name``_i; \ 46 | force dut.top_earlgrey.u_``inst_name``.``sig_name``_o = ``tl_name``_tl_if.d2h; \ 47 | force dut.top_earlgrey.u_``inst_name``.clk_i = 0; \ 48 | uvm_config_db#(virtual tl_if)::set(null, $sformatf("*%0s*", `"tl_name`"), "vif", \ 49 | ``tl_name``_tl_if); 50 | 51 | `define DRIVE_CHIP_TL_EXT_DEVICE_IF(tl_name, port_name) \ 52 | force ``tl_name``_tl_if.h2d = dut.top_earlgrey.``port_name``_req_o; \ 53 | force dut.top_earlgrey.``port_name``_rsp_i = ``tl_name``_tl_if.d2h; \ 54 | uvm_config_db#(virtual tl_if)::set(null, $sformatf("*%0s*", `"tl_name`"), "vif", \ 55 | ``tl_name``_tl_if); 56 | \ 57 | 58 | % for c in clk_freq.keys(): 59 | wire clk_${c}; 60 | clk_rst_if clk_rst_if_${c}(.clk(clk_${c}), .rst_n(rst_n)); 61 | % endfor 62 | 63 | % for i, clk in hosts.items(): 64 | tl_if ${escape_if_name(i)}_tl_if(${clk}, rst_n); 65 | % endfor 66 | 67 | % for i, clk in devices.items(): 68 | tl_if ${escape_if_name(i)}_tl_if(${clk}, rst_n); 69 | % endfor 70 | 71 | initial begin 72 | bit xbar_mode; 73 | void'($value$plusargs("xbar_mode=%0b", xbar_mode)); 74 | if (xbar_mode) begin 75 | // only enable assertions in xbar as many pins are unconnected 76 | $assertoff(0, tb); 77 | % for xbar in top["xbar"]: 78 | $asserton(0, tb.dut.top_${top["name"]}.u_xbar_${xbar["name"]}); 79 | % endfor 80 | 81 | % for c in clk_freq.keys(): 82 | clk_rst_if_${c}.set_active(.drive_rst_n_val(0)); 83 | clk_rst_if_${c}.set_freq_khz(${clk_freq[c]} / 1000); 84 | % endfor 85 | 86 | // bypass clkmgr, force clocks directly 87 | % for xbar in top["xbar"]: 88 | % for clk, src in xbar["clock_srcs"].items(): 89 | force ${top_hier}u_xbar_${xbar["name"]}.${clk} = clk_${src}; 90 | % endfor 91 | % endfor 92 | 93 | // bypass rstmgr, force resets directly 94 | % for xbar in top["xbar"]: 95 | % for rst in xbar["reset_connections"]: 96 | force ${top_hier}u_xbar_${xbar["name"]}.${rst} = rst_n; 97 | % endfor 98 | % endfor 99 | 100 | % for xbar in top["xbar"]: 101 | % for node in xbar["nodes"]: 102 | <% 103 | clk = 'clk_' + clk_src[node["clock"]] 104 | esc_name = node['name'].replace('.', '__') 105 | inst_sig_list = lib.find_otherside_modules(top, xbar["name"], 'tl_' + esc_name) 106 | inst_name = inst_sig_list[0][1] 107 | sig_name = inst_sig_list[0][2] 108 | 109 | %>\ 110 | % if node["type"] == "host" and not node["xbar"]: 111 | `DRIVE_CHIP_TL_HOST_IF(${esc_name}, ${inst_name}, ${sig_name}) 112 | % elif node["type"] == "device" and not node["xbar"] and node["stub"]: 113 | `DRIVE_CHIP_TL_EXT_DEVICE_IF(${esc_name}, ${inst_name}_${sig_name}) 114 | % elif node["type"] == "device" and not node["xbar"]: 115 | `DRIVE_CHIP_TL_DEVICE_IF(${esc_name}, ${inst_name}, ${sig_name}) 116 | % endif 117 | % endfor 118 | % endfor 119 | end 120 | end 121 | 122 | `undef DRIVE_CHIP_TL_HOST_IF 123 | `undef DRIVE_CHIP_TL_DEVICE_IF 124 | `undef DRIVE_CHIP_TL_EXT_DEVICE_IF 125 | -------------------------------------------------------------------------------- /util/reggen/topgen/templates/toplevel.c.tpl: -------------------------------------------------------------------------------- 1 | // Copyright lowRISC contributors. 2 | // Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #include "${helper.header_path}" 6 | 7 | /** 8 | * PLIC Interrupt Source to Peripheral Map 9 | * 10 | * This array is a mapping from `${helper.plic_interrupts.name.as_c_type()}` to 11 | * `${helper.plic_sources.name.as_c_type()}`. 12 | */ 13 | ${helper.plic_mapping.render_definition()} 14 | 15 | /** 16 | * Alert Handler Alert Source to Peripheral Map 17 | * 18 | * This array is a mapping from `${helper.alert_alerts.name.as_c_type()}` to 19 | * `${helper.alert_sources.name.as_c_type()}`. 20 | */ 21 | ${helper.alert_mapping.render_definition()} 22 | -------------------------------------------------------------------------------- /util/reggen/topgen/templates/toplevel.h.tpl: -------------------------------------------------------------------------------- 1 | // Copyright lowRISC contributors. 2 | // Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #ifndef _TOP_${top["name"].upper()}_H_ 6 | #define _TOP_${top["name"].upper()}_H_ 7 | 8 | /** 9 | * @file 10 | * @brief Top-specific Definitions 11 | * 12 | * This file contains preprocessor and type definitions for use within the 13 | * device C/C++ codebase. 14 | * 15 | * These definitions are for information that depends on the top-specific chip 16 | * configuration, which includes: 17 | * - Device Memory Information (for Peripherals and Memory) 18 | * - PLIC Interrupt ID Names and Source Mappings 19 | * - Alert ID Names and Source Mappings 20 | * - Pinmux Pin/Select Names 21 | * - Power Manager Wakeups 22 | */ 23 | 24 | #ifdef __cplusplus 25 | extern "C" { 26 | #endif 27 | 28 | % for (inst_name, if_name), region in helper.devices(): 29 | <% 30 | if_desc = inst_name if if_name is None else '{} device on {}'.format(if_name, inst_name) 31 | hex_base_addr = "0x{:X}u".format(region.base_addr) 32 | hex_size_bytes = "0x{:X}u".format(region.size_bytes) 33 | 34 | base_addr_name = region.base_addr_name().as_c_define() 35 | size_bytes_name = region.size_bytes_name().as_c_define() 36 | 37 | %>\ 38 | /** 39 | * Peripheral base address for ${if_desc} in top ${top["name"]}. 40 | * 41 | * This should be used with #mmio_region_from_addr to access the memory-mapped 42 | * registers associated with the peripheral (usually via a DIF). 43 | */ 44 | #define ${base_addr_name} ${hex_base_addr} 45 | 46 | /** 47 | * Peripheral size for ${if_desc} in top ${top["name"]}. 48 | * 49 | * This is the size (in bytes) of the peripheral's reserved memory area. All 50 | * memory-mapped registers associated with this peripheral should have an 51 | * address between #${base_addr_name} and 52 | * `${base_addr_name} + ${size_bytes_name}`. 53 | */ 54 | #define ${size_bytes_name} ${hex_size_bytes} 55 | 56 | % endfor 57 | 58 | % for name, region in helper.memories(): 59 | <% 60 | hex_base_addr = "0x{:X}u".format(region.base_addr) 61 | hex_size_bytes = "0x{:X}u".format(region.size_bytes) 62 | 63 | base_addr_name = region.base_addr_name().as_c_define() 64 | size_bytes_name = region.size_bytes_name().as_c_define() 65 | 66 | %>\ 67 | /** 68 | * Memory base address for ${name} in top ${top["name"]}. 69 | */ 70 | #define ${base_addr_name} ${hex_base_addr} 71 | 72 | /** 73 | * Memory size for ${name} in top ${top["name"]}. 74 | */ 75 | #define ${size_bytes_name} ${hex_size_bytes} 76 | 77 | % endfor 78 | 79 | /** 80 | * PLIC Interrupt Source Peripheral. 81 | * 82 | * Enumeration used to determine which peripheral asserted the corresponding 83 | * interrupt. 84 | */ 85 | ${helper.plic_sources.render()} 86 | 87 | /** 88 | * PLIC Interrupt Source. 89 | * 90 | * Enumeration of all PLIC interrupt sources. The interrupt sources belonging to 91 | * the same peripheral are guaranteed to be consecutive. 92 | */ 93 | ${helper.plic_interrupts.render()} 94 | 95 | /** 96 | * PLIC Interrupt Source to Peripheral Map 97 | * 98 | * This array is a mapping from `${helper.plic_interrupts.name.as_c_type()}` to 99 | * `${helper.plic_sources.name.as_c_type()}`. 100 | */ 101 | ${helper.plic_mapping.render_declaration()} 102 | 103 | /** 104 | * PLIC Interrupt Target. 105 | * 106 | * Enumeration used to determine which set of IE, CC, threshold registers to 107 | * access for a given interrupt target. 108 | */ 109 | ${helper.plic_targets.render()} 110 | 111 | /** 112 | * Alert Handler Source Peripheral. 113 | * 114 | * Enumeration used to determine which peripheral asserted the corresponding 115 | * alert. 116 | */ 117 | ${helper.alert_sources.render()} 118 | 119 | /** 120 | * Alert Handler Alert Source. 121 | * 122 | * Enumeration of all Alert Handler Alert Sources. The alert sources belonging to 123 | * the same peripheral are guaranteed to be consecutive. 124 | */ 125 | ${helper.alert_alerts.render()} 126 | 127 | /** 128 | * Alert Handler Alert Source to Peripheral Map 129 | * 130 | * This array is a mapping from `${helper.alert_alerts.name.as_c_type()}` to 131 | * `${helper.alert_sources.name.as_c_type()}`. 132 | */ 133 | ${helper.alert_mapping.render_declaration()} 134 | 135 | #define PINMUX_MIO_PERIPH_INSEL_IDX_OFFSET 2 136 | 137 | // PERIPH_INSEL ranges from 0 to NUM_MIO_PADS + 2 -1} 138 | // 0 and 1 are tied to value 0 and 1 139 | #define NUM_MIO_PADS ${top["pinmux"]["io_counts"]["muxed"]["pads"]} 140 | #define NUM_DIO_PADS ${top["pinmux"]["io_counts"]["dedicated"]["inouts"] + \ 141 | top["pinmux"]["io_counts"]["dedicated"]["inputs"] + \ 142 | top["pinmux"]["io_counts"]["dedicated"]["outputs"] } 143 | 144 | #define PINMUX_PERIPH_OUTSEL_IDX_OFFSET 3 145 | 146 | /** 147 | * Pinmux Peripheral Input. 148 | */ 149 | ${helper.pinmux_peripheral_in.render()} 150 | 151 | /** 152 | * Pinmux MIO Input Selector. 153 | */ 154 | ${helper.pinmux_insel.render()} 155 | 156 | /** 157 | * Pinmux MIO Output. 158 | */ 159 | ${helper.pinmux_mio_out.render()} 160 | 161 | /** 162 | * Pinmux Peripheral Output Selector. 163 | */ 164 | ${helper.pinmux_outsel.render()} 165 | 166 | /** 167 | * Power Manager Wakeup Signals 168 | */ 169 | ${helper.pwrmgr_wakeups.render()} 170 | 171 | /** 172 | * Reset Manager Software Controlled Resets 173 | */ 174 | ${helper.rstmgr_sw_rsts.render()} 175 | 176 | /** 177 | * Power Manager Reset Request Signals 178 | */ 179 | ${helper.pwrmgr_reset_requests.render()} 180 | 181 | /** 182 | * Clock Manager Software-Controlled ("Gated") Clocks. 183 | * 184 | * The Software has full control over these clocks. 185 | */ 186 | ${helper.clkmgr_gateable_clocks.render()} 187 | 188 | /** 189 | * Clock Manager Software-Hinted Clocks. 190 | * 191 | * The Software has partial control over these clocks. It can ask them to stop, 192 | * but the clock manager is in control of whether the clock actually is stopped. 193 | */ 194 | ${helper.clkmgr_hintable_clocks.render()} 195 | 196 | // Header Extern Guard 197 | #ifdef __cplusplus 198 | } // extern "C" 199 | #endif 200 | 201 | #endif // _TOP_${top["name"].upper()}_H_ 202 | -------------------------------------------------------------------------------- /util/reggen/topgen/templates/toplevel_memory.h.tpl: -------------------------------------------------------------------------------- 1 | // Copyright lowRISC contributors. 2 | // Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #ifndef _TOP_${top["name"].upper()}_MEMORY_H_ 6 | #define _TOP_${top["name"].upper()}_MEMORY_H_ 7 | 8 | /** 9 | * @file 10 | * @brief Assembler-only Top-Specific Definitions. 11 | * 12 | * This file contains preprocessor definitions for use within assembly code. 13 | * 14 | * These are not shared with C/C++ code because these are only allowed to be 15 | * preprocessor definitions, no data or type declarations are allowed. The 16 | * assembler is also stricter about literals (not allowing suffixes for 17 | * signed/unsigned which are sensible to use for unsigned values in C/C++). 18 | */ 19 | 20 | // Include guard for assembler 21 | #ifdef __ASSEMBLER__ 22 | 23 | /** 24 | * Memory base address for rom in top earlgrey. 25 | */ 26 | #define TOP_EARLGREY_ROM_BASE_ADDR 0x00008000 27 | 28 | /** 29 | * Memory size for rom in top earlgrey. 30 | */ 31 | #define TOP_EARLGREY_ROM_SIZE_BYTES 0x4000 32 | 33 | % for m in top["memory"]: 34 | /** 35 | * Memory base address for ${m["name"]} in top ${top["name"]}. 36 | */ 37 | #define TOP_${top["name"].upper()}_${m["name"].upper()}_BASE_ADDR ${m["base_addr"]} 38 | 39 | /** 40 | * Memory size for ${m["name"]} in top ${top["name"]}. 41 | */ 42 | #define TOP_${top["name"].upper()}_${m["name"].upper()}_SIZE_BYTES ${m["size"]} 43 | 44 | % endfor 45 | 46 | % for (inst_name, if_name), region in helper.devices(): 47 | <% 48 | if_desc = inst_name if if_name is None else '{} device on {}'.format(if_name, inst_name) 49 | hex_base_addr = "0x{:X}".format(region.base_addr) 50 | base_addr_name = region.base_addr_name().as_c_define() 51 | %>\ 52 | /** 53 | * Peripheral base address for ${if_desc} in top ${top["name"]}. 54 | * 55 | * This should be used with #mmio_region_from_addr to access the memory-mapped 56 | * registers associated with the peripheral (usually via a DIF). 57 | */ 58 | #define ${base_addr_name} ${hex_base_addr} 59 | % endfor 60 | #endif // __ASSEMBLER__ 61 | 62 | #endif // _TOP_${top["name"].upper()}_MEMORY_H_ 63 | -------------------------------------------------------------------------------- /util/reggen/topgen/templates/toplevel_memory.ld.tpl: -------------------------------------------------------------------------------- 1 | /* Copyright lowRISC contributors. */ 2 | /* Licensed under the Apache License, Version 2.0, see LICENSE for details. */ 3 | /* SPDX-License-Identifier: Apache-2.0 */ 4 | <%! 5 | def memory_to_flags(memory): 6 | memory_type = memory["type"] 7 | memory_access = memory.get("swaccess", "rw") 8 | assert memory_access in ["ro", "rw"] 9 | 10 | flags_str = "" 11 | if memory_access == "ro": 12 | flags_str += "r" 13 | else: 14 | flags_str += "rw" 15 | 16 | if memory_type in ["rom", "eflash"]: 17 | flags_str += "x" 18 | 19 | return flags_str 20 | %>\ 21 | 22 | /** 23 | * Partial linker script for chip memory configuration. 24 | */ 25 | MEMORY { 26 | rom(rx) : ORIGIN = 0x00008000, LENGTH = 0x4000 27 | % for m in top["memory"]: 28 | ${m["name"]}(${memory_to_flags(m)}) : ORIGIN = ${m["base_addr"]}, LENGTH = ${m["size"]} 29 | % endfor 30 | } 31 | -------------------------------------------------------------------------------- /util/reggen/topgen/templates/toplevel_pkg.sv.tpl: -------------------------------------------------------------------------------- 1 | // Copyright lowRISC contributors. 2 | // Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | // SPDX-License-Identifier: Apache-2.0 4 | ${gencmd} 5 | <% 6 | import topgen.lib as lib 7 | %>\ 8 | package top_${top["name"]}_pkg; 9 | % for (inst_name, if_name), region in helper.devices(): 10 | <% 11 | if_desc = inst_name if if_name is None else '{} device on {}'.format(if_name, inst_name) 12 | hex_base_addr = "32'h{:X}".format(region.base_addr) 13 | hex_size_bytes = "32'h{:X}".format(region.size_bytes) 14 | %>\ 15 | /** 16 | * Peripheral base address for ${if_desc} in top ${top["name"]}. 17 | */ 18 | parameter int unsigned ${region.base_addr_name().as_c_define()} = ${hex_base_addr}; 19 | 20 | /** 21 | * Peripheral size in bytes for ${if_desc} in top ${top["name"]}. 22 | */ 23 | parameter int unsigned ${region.size_bytes_name().as_c_define()} = ${hex_size_bytes}; 24 | 25 | % endfor 26 | % for name, region in helper.memories(): 27 | <% 28 | hex_base_addr = "32'h{:x}".format(region.base_addr) 29 | hex_size_bytes = "32'h{:x}".format(region.size_bytes) 30 | %>\ 31 | /** 32 | * Memory base address for ${name} in top ${top["name"]}. 33 | */ 34 | parameter int unsigned ${region.base_addr_name().as_c_define()} = ${hex_base_addr}; 35 | 36 | /** 37 | * Memory size for ${name} in top ${top["name"]}. 38 | */ 39 | parameter int unsigned ${region.size_bytes_name().as_c_define()} = ${hex_size_bytes}; 40 | 41 | % endfor 42 | 43 | // Enumeration of IO power domains. 44 | // Only used in ASIC target. 45 | typedef enum logic [${len(top["pinout"]["banks"]).bit_length()-1}:0] { 46 | % for bank in top["pinout"]["banks"]: 47 | ${lib.Name(['io', 'bank', bank]).as_camel_case()} = ${loop.index}, 48 | % endfor 49 | IoBankCount = ${len(top["pinout"]["banks"])} 50 | } pwr_dom_e; 51 | 52 | // Enumeration for MIO signals on the top-level. 53 | typedef enum int unsigned { 54 | % for sig in top["pinmux"]["ios"]: 55 | % if sig['type'] in ['inout', 'input'] and sig['connection'] == 'muxed': 56 | ${lib.get_io_enum_literal(sig, 'mio_in')} = ${sig['glob_idx']}, 57 | % endif 58 | % endfor 59 | <% total = top["pinmux"]['io_counts']['muxed']['inouts'] + \ 60 | top["pinmux"]['io_counts']['muxed']['inputs'] %>\ 61 | ${lib.Name.from_snake_case("mio_in_count").as_camel_case()} = ${total} 62 | } mio_in_e; 63 | 64 | typedef enum { 65 | % for sig in top["pinmux"]["ios"]: 66 | % if sig['type'] in ['inout', 'output'] and sig['connection'] == 'muxed': 67 | ${lib.get_io_enum_literal(sig, 'mio_out')} = ${sig['glob_idx']}, 68 | % endif 69 | % endfor 70 | <% total = top["pinmux"]['io_counts']['muxed']['inouts'] + \ 71 | top["pinmux"]['io_counts']['muxed']['outputs'] %>\ 72 | ${lib.Name.from_snake_case("mio_out_count").as_camel_case()} = ${total} 73 | } mio_out_e; 74 | 75 | // Enumeration for DIO signals, used on both the top and chip-levels. 76 | typedef enum int unsigned { 77 | % for sig in top["pinmux"]["ios"]: 78 | % if sig['connection'] != 'muxed': 79 | ${lib.get_io_enum_literal(sig, 'dio')} = ${sig['glob_idx']}, 80 | % endif 81 | % endfor 82 | <% total = top["pinmux"]['io_counts']['dedicated']['inouts'] + \ 83 | top["pinmux"]['io_counts']['dedicated']['inputs'] + \ 84 | top["pinmux"]['io_counts']['dedicated']['outputs'] %>\ 85 | ${lib.Name.from_snake_case("dio_count").as_camel_case()} = ${total} 86 | } dio_e; 87 | 88 | // Raw MIO/DIO input array indices on chip-level. 89 | // TODO: Does not account for target specific stubbed/added pads. 90 | // Need to make a target-specific package for those. 91 | typedef enum int unsigned { 92 | % for pad in top["pinout"]["pads"]: 93 | % if pad["connection"] == "muxed": 94 | ${lib.Name.from_snake_case("mio_pad_" + pad["name"]).as_camel_case()} = ${pad["idx"]}, 95 | % endif 96 | % endfor 97 | ${lib.Name.from_snake_case("mio_pad_count").as_camel_case()} 98 | } mio_pad_e; 99 | 100 | typedef enum int unsigned { 101 | % for pad in top["pinout"]["pads"]: 102 | % if pad["connection"] != "muxed": 103 | ${lib.Name.from_snake_case("dio_pad_" + pad["name"]).as_camel_case()} = ${pad["idx"]}, 104 | % endif 105 | % endfor 106 | ${lib.Name.from_snake_case("dio_pad_count").as_camel_case()} 107 | } dio_pad_e; 108 | 109 | // TODO: Enumeration for PLIC Interrupt source peripheral. 110 | // TODO: Enumeration for PLIC Interrupt Ids. 111 | 112 | endpackage 113 | -------------------------------------------------------------------------------- /util/reggen/topgen/templates/toplevel_rnd_cnst_pkg.sv.tpl: -------------------------------------------------------------------------------- 1 | // Copyright lowRISC contributors. 2 | // Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | // SPDX-License-Identifier: Apache-2.0 4 | ${gencmd} 5 | <% 6 | def make_blocked_sv_literal(hexstr, randwidth): 7 | """This chops the random hexstring into manageable blocks of 64 chars such that the 8 | lines do not get too long. 9 | """ 10 | # Make all-caps and drop '0x' preamble 11 | hexstr = str(hexstr[2:]).upper() 12 | # Block width in hex chars 13 | blockwidth = 64 14 | remainder = randwidth % (4*blockwidth) 15 | numbits = remainder if remainder else 4*blockwidth 16 | idx = 0 17 | hexblocks = [] 18 | while randwidth > 0: 19 | hexstr = hexstr[idx:] 20 | randwidth -= numbits 21 | idx = (numbits + 3) // 4 22 | hexblocks.append(str(numbits) + "'h" + hexstr[0:idx]) 23 | numbits = 4*blockwidth 24 | return hexblocks 25 | %> 26 | package top_${top["name"]}_rnd_cnst_pkg; 27 | 28 | % for m in top["module"]: 29 | % for p in filter(lambda p: p.get("randtype") in ["data", "perm"], m["param_list"]): 30 | % if loop.first: 31 | //////////////////////////////////////////// 32 | // ${m['name']} 33 | //////////////////////////////////////////// 34 | % endif 35 | // ${p['desc']} 36 | parameter ${p["type"]} ${p["name_top"]} = { 37 | % for block in make_blocked_sv_literal(p["default"], p["randwidth"]): 38 | ${block}${"" if loop.last else ","} 39 | % endfor 40 | }; 41 | 42 | % endfor 43 | % endfor 44 | endpackage : top_${top["name"]}_rnd_cnst_pkg 45 | -------------------------------------------------------------------------------- /util/reggen/topgen/templates/xbar_env_pkg__params.sv.tpl: -------------------------------------------------------------------------------- 1 | // Copyright lowRISC contributors. 2 | // Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | // xbar_env_pkg__params generated by `topgen.py` tool 6 | 7 | <% 8 | from collections import OrderedDict 9 | 10 | def is_device_a_xbar(dev_name): 11 | for xbar in top["xbar"]: 12 | if xbar["name"] == dev_name: 13 | return 1 14 | return 0 15 | 16 | # recursively find all non-xbar devices under this xbar 17 | def get_xbar_edge_nodes(xbar_name): 18 | edge_devices = [] 19 | for xbar in top["xbar"]: 20 | if xbar["name"] == xbar_name: 21 | for host, devices in xbar["connections"].items(): 22 | for dev_name in devices: 23 | if is_device_a_xbar(dev_name): 24 | edge_devices.extend(get_xbar_edge_nodes()) 25 | else: 26 | edge_devices.append(dev_name) 27 | 28 | return edge_devices 29 | 30 | # find device xbar and assign all its device nodes to it: "peri" -> "uart, gpio, ..." 31 | xbar_device_dict = OrderedDict() 32 | 33 | for xbar in top["xbar"]: 34 | for n in xbar["nodes"]: 35 | if n["type"] == "device" and n["xbar"]: 36 | xbar_device_dict[n["name"]] = get_xbar_edge_nodes(n["name"]) 37 | 38 | # create the mapping: host with the corresponding devices map 39 | host_dev_map = OrderedDict() 40 | for host, devices in top["xbar"][0]["connections"].items(): 41 | dev_list = [] 42 | for dev in devices: 43 | if dev not in xbar_device_dict.keys(): 44 | dev_list.append(dev) 45 | else: 46 | dev_list.extend(xbar_device_dict[dev]) 47 | host_dev_map[host] = dev_list 48 | 49 | %>\ 50 | 51 | // List of Xbar device memory map 52 | tl_device_t xbar_devices[$] = '{ 53 | % for xbar in top["xbar"]: 54 | % for device in xbar["nodes"]: 55 | % if device["type"] == "device" and not device["xbar"]: 56 | '{"${device["name"].replace('.', '__')}", '{ 57 | % for addr in device["addr_range"]: 58 | <% 59 | start_addr = int(addr["base_addr"], 0) 60 | end_addr = start_addr + int(addr["size_byte"], 0) - 1 61 | %>\ 62 | '{32'h${"%08x" % start_addr}, 32'h${"%08x" % end_addr}}${"," if not loop.last else ""} 63 | % endfor 64 | }}${"," if not loop.last or xbar != top["xbar"][-1] else "};"} 65 | % endif 66 | % endfor 67 | % endfor 68 | 69 | // List of Xbar hosts 70 | tl_host_t xbar_hosts[$] = '{ 71 | % for host in host_dev_map.keys(): 72 | '{"${host}", ${loop.index}, '{ 73 | <% 74 | host_devices = host_dev_map[host]; 75 | %>\ 76 | % for device in host_devices: 77 | % if loop.last: 78 | "${device}"}} 79 | % else: 80 | "${device}", 81 | % endif 82 | % endfor 83 | % if loop.last: 84 | }; 85 | % else: 86 | , 87 | % endif 88 | % endfor 89 | -------------------------------------------------------------------------------- /util/reggen/topgen/top.py: -------------------------------------------------------------------------------- 1 | # Copyright lowRISC contributors. 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | '''Code representing the entire chip for reggen''' 6 | 7 | from typing import Dict, List, Optional, Tuple, Union 8 | 9 | from reggen.ip_block import IpBlock 10 | from reggen.params import ReggenParams 11 | from reggen.reg_block import RegBlock 12 | from reggen.window import Window 13 | 14 | _IFName = Tuple[str, Optional[str]] 15 | _Triple = Tuple[int, str, IpBlock] 16 | 17 | 18 | class Top: 19 | '''An object representing the entire chip, as seen by reggen. 20 | 21 | This contains instances of some blocks (possibly multiple instances of each 22 | block), starting at well-defined base addresses. It may also contain some 23 | windows. These are memories that don't have their own comportable IP (so 24 | aren't defined in a block), but still take up address space. 25 | 26 | ''' 27 | 28 | def __init__(self, 29 | regwidth: int, 30 | blocks: Dict[str, IpBlock], 31 | instances: Dict[str, str], 32 | if_addrs: Dict[Tuple[str, Optional[str]], int], 33 | windows: List[Window], 34 | attrs: Dict[str, str]): 35 | '''Class initializer. 36 | 37 | regwidth is the width of the registers (which must match for all the 38 | blocks) in bits. 39 | 40 | blocks is a map from block name to IpBlock object. 41 | 42 | instances is a map from instance name to the name of the block it 43 | instantiates. Every block name that appears in instances must be a key 44 | of blocks. 45 | 46 | if_addrs is a dictionary that maps the name of a device interface on 47 | some instance of some block to its base address. A key of the form (n, 48 | i) means "the device interface called i on an instance called n". If i 49 | is None, this is an unnamed device interface. Every instance name (n) 50 | that appears in connections must be a key of instances. 51 | 52 | windows is a list of windows (these contain base addresses already). 53 | 54 | attrs is a map from instance name to attr field of the block 55 | 56 | ''' 57 | 58 | self.regwidth = regwidth 59 | self.blocks = blocks 60 | self.instances = instances 61 | self.if_addrs = if_addrs 62 | self.attrs = attrs 63 | 64 | self.window_block = RegBlock(regwidth, ReggenParams()) 65 | 66 | # Generate one list of base addresses and objects (with each object 67 | # either a block name and interface name or a window). While we're at 68 | # it, construct inst_to_block_name and if_addrs. 69 | merged = [] # type: List[Tuple[int, Union[_IFName, Window]]] 70 | for full_if_name, addr in if_addrs.items(): 71 | merged.append((addr, full_if_name)) 72 | 73 | inst_name, if_name = full_if_name 74 | 75 | # The instance name must match some key in instances, whose value 76 | # should in turn match some key in blocks. 77 | assert inst_name in instances 78 | block_name = instances[inst_name] 79 | assert block_name in blocks 80 | 81 | # Check that if_name is indeed the name of a device interface for 82 | # that block. 83 | block = blocks[block_name] 84 | assert block.bus_interfaces.has_interface(False, if_name) 85 | 86 | for window in sorted(windows, key=lambda w: w.offset): 87 | merged.append((window.offset, window)) 88 | self.window_block.add_window(window) 89 | 90 | # A map from block name to the list of its instances. These instances 91 | # are listed in increasing order of the lowest base address of one of 92 | # their interfaces. The entries are added into the dict in the same 93 | # order, so an iteration over items() will give blocks ordered by their 94 | # first occurrence in the address map. 95 | self.block_instances = {} # type: Dict[str, List[str]] 96 | 97 | # Walk the merged list in order of increasing base address. Check for 98 | # overlaps and construct block_instances. 99 | offset = 0 100 | for base_addr, item in sorted(merged, key=lambda pr: pr[0]): 101 | # Make sure that this item doesn't overlap with the previous one 102 | assert offset <= base_addr, item 103 | 104 | if isinstance(item, Window): 105 | addrsep = (regwidth + 7) // 8 106 | offset = item.next_offset(addrsep) 107 | continue 108 | 109 | inst_name, if_name = item 110 | block_name = instances[inst_name] 111 | block = blocks[block_name] 112 | 113 | lst = self.block_instances.setdefault(block_name, []) 114 | if inst_name not in lst: 115 | lst.append(inst_name) 116 | 117 | # This should be guaranteed by the fact that we've already checked 118 | # the existence of a device interface. 119 | assert if_name in block.reg_blocks 120 | reg_block = block.reg_blocks[if_name] 121 | 122 | offset = base_addr + reg_block.offset 123 | -------------------------------------------------------------------------------- /util/reggen/topgen/top_uvm_reg.sv.tpl: -------------------------------------------------------------------------------- 1 | // Copyright lowRISC contributors. 2 | // Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | // UVM registers auto-generated by `reggen` containing UVM definitions for the entire top-level 6 | <%! 7 | from topgen.gen_dv import sv_base_addr 8 | from reggen.gen_dv import bcname, mcname, miname 9 | %> 10 | ## 11 | ## This template is used for chip-wide tests. It expects to be run with the 12 | ## following arguments 13 | ## 14 | ## top a Top object 15 | ## 16 | ## dv_base_prefix a string for the base register type. If it is FOO, then 17 | ## we will inherit from FOO_reg (assumed to be a subclass 18 | ## of uvm_reg). 19 | ## 20 | ## Like uvm_reg.sv.tpl, we use functions from uvm_reg_base.sv.tpl to define 21 | ## per-device-interface code. 22 | ## 23 | <%namespace file="uvm_reg_base.sv.tpl" import="*"/>\ 24 | ## 25 | ## 26 | ## Waive the package-filename check: we're going to be defining all sorts of 27 | ## packages in a single file. 28 | 29 | // verilog_lint: waive-start package-filename 30 | ## 31 | ## Iterate over the device interfaces of blocks in Top, constructing a package 32 | ## for each. Sorting items like this guarantees we'll work alphabetically in 33 | ## block name. 34 | % for block_name, block in sorted(top.blocks.items()): 35 | % for if_name, rb in block.reg_blocks.items(): 36 | <% 37 | if_suffix = '' if if_name is None else '_' + if_name 38 | esc_if_name = block_name.lower() + if_suffix 39 | if_desc = '' if if_name is None else '; interface {}'.format(if_name) 40 | reg_block_path = 'u_reg' + if_suffix 41 | reg_block_path = reg_block_path if block.hier_path is None else block.hier_path + "." + reg_block_path 42 | %>\ 43 | // Block: ${block_name.lower()}${if_desc} 44 | ${make_ral_pkg(dv_base_prefix, top.regwidth, reg_block_path, rb, esc_if_name)} 45 | % endfor 46 | % endfor 47 | ## 48 | ## 49 | ## Now that we've made the block-level packages, re-instate the 50 | ## package-filename check. The only package left is chip_ral_pkg, which should 51 | ## match the generated filename. 52 | 53 | // verilog_lint: waive-start package-filename 54 | 55 | // Block: chip 56 | package chip_ral_pkg; 57 | <% 58 | if_packages = [] 59 | for block_name, block in sorted(top.blocks.items()): 60 | for if_name in block.reg_blocks: 61 | if_suffix = '' if if_name is None else '_' + if_name 62 | if_packages.append('{}{}_ral_pkg'.format(block_name.lower(), if_suffix)) 63 | 64 | windows = top.window_block.windows 65 | %>\ 66 | ${make_ral_pkg_hdr(dv_base_prefix, if_packages)} 67 | ${make_ral_pkg_fwd_decls('chip', [], windows)} 68 | % for window in windows: 69 | 70 | ${make_ral_pkg_window_class(dv_base_prefix, 'chip', window)} 71 | % endfor 72 | 73 | class chip_reg_block extends ${dv_base_prefix}_reg_block; 74 | // sub blocks 75 | % for block_name, block in sorted(top.blocks.items()): 76 | % for inst_name in top.block_instances[block_name.lower()]: 77 | % for if_name, rb in block.reg_blocks.items(): 78 | <% 79 | if_suffix = '' if if_name is None else '_' + if_name 80 | esc_if_name = block_name.lower() + if_suffix 81 | if_inst = inst_name + if_suffix 82 | %>\ 83 | rand ${bcname(esc_if_name)} ${if_inst}; 84 | % endfor 85 | % endfor 86 | % endfor 87 | % if windows: 88 | // memories 89 | % for window in windows: 90 | rand ${mcname('chip', window)} ${miname(window)}; 91 | % endfor 92 | % endif 93 | 94 | `uvm_object_utils(chip_reg_block) 95 | 96 | function new(string name = "chip_reg_block", 97 | int has_coverage = UVM_NO_COVERAGE); 98 | super.new(name, has_coverage); 99 | endfunction : new 100 | 101 | virtual function void build(uvm_reg_addr_t base_addr, 102 | csr_excl_item csr_excl = null); 103 | // create default map 104 | this.default_map = create_map(.name("default_map"), 105 | .base_addr(base_addr), 106 | .n_bytes(${top.regwidth//8}), 107 | .endian(UVM_LITTLE_ENDIAN)); 108 | if (csr_excl == null) begin 109 | csr_excl = csr_excl_item::type_id::create("csr_excl"); 110 | this.csr_excl = csr_excl; 111 | end 112 | 113 | // create sub blocks and add their maps 114 | % for block_name, block in sorted(top.blocks.items()): 115 | % for inst_name in top.block_instances[block_name.lower()]: 116 | % for if_name, rb in block.reg_blocks.items(): 117 | <% 118 | if_suffix = '' if if_name is None else '_' + if_name 119 | esc_if_name = block_name.lower() + if_suffix 120 | if_inst = inst_name + if_suffix 121 | 122 | if top.attrs.get(inst_name) == 'reggen_only': 123 | hdl_path = 'tb.dut.u_' + inst_name 124 | else: 125 | hdl_path = 'tb.dut.top_earlgrey.u_' + inst_name 126 | qual_if_name = (inst_name, if_name) 127 | base_addr = top.if_addrs[qual_if_name] 128 | base_addr_txt = sv_base_addr(top, qual_if_name) 129 | 130 | hpr_indent = (len(if_inst) + len('.set_hdl_path_root(')) * ' ' 131 | %>\ 132 | ${if_inst} = ${bcname(esc_if_name)}::type_id::create("${if_inst}"); 133 | ${if_inst}.configure(.parent(this)); 134 | ${if_inst}.build(.base_addr(base_addr + ${base_addr_txt}), .csr_excl(csr_excl)); 135 | ${if_inst}.set_hdl_path_root("${hdl_path}", 136 | ${hpr_indent}"BkdrRegPathRtl"); 137 | ${if_inst}.set_hdl_path_root("${hdl_path}", 138 | ${hpr_indent}"BkdrRegPathRtlCommitted"); 139 | ${if_inst}.set_hdl_path_root("${hdl_path}", 140 | ${hpr_indent}"BkdrRegPathRtlShadow"); 141 | default_map.add_submap(.child_map(${if_inst}.default_map), 142 | .offset(base_addr + ${base_addr_txt})); 143 | % endfor 144 | % endfor 145 | % endfor 146 | ${make_ral_pkg_window_instances(top.regwidth, 'chip', top.window_block)} 147 | 148 | endfunction : build 149 | endclass : chip_reg_block 150 | 151 | endpackage 152 | --------------------------------------------------------------------------------