diff --git a/.ci/Memora.yml b/.ci/Memora.yml index f3c331574..c8fd967de 100644 --- a/.ci/Memora.yml +++ b/.ci/Memora.yml @@ -103,6 +103,20 @@ artifacts: outputs: - build/axi_dw_upsizer-%.tested + axi_fifo-%: + inputs: + - Bender.yml + - include + - scripts/run_vsim.sh + - src/axi_pkg.sv + - src/axi_intf.sv + - src/axi_test.sv + - src/axi_fifo.sv + - test/tb_axi_fifo.sv + outputs: + - build/axi_fifo-%.tested + + axi_isolate-%: inputs: - Bender.yml @@ -135,6 +149,32 @@ artifacts: outputs: - build/axi_iw_converter-%.tested + axi_lite_dw_converter-%: + inputs: + - Bender.yml + - include + - scripts/run_vsim.sh + - src/axi_pkg.sv + - src/axi_intf.sv + - src/axi_test.sv + - src/axi_lite_dw_converter.sv + - test/tb_axi_lite_dw_converter.sv + outputs: + - build/axi_lite_dw_converter-%.tested + + axi_lite_mailbox-%: + inputs: + - Bender.yml + - include + - scripts/run_vsim.sh + - src/axi_pkg.sv + - src/axi_intf.sv + - src/axi_test.sv + - src/axi_lite_mailbox.sv + - test/tb_axi_lite_mailbox.sv + outputs: + - build/axi_lite_mailbox-%.tested + axi_lite_regs-%: inputs: - Bender.yml @@ -174,19 +214,6 @@ artifacts: outputs: - build/axi_lite_to_axi-%.tested - axi_lite_mailbox-%: - inputs: - - Bender.yml - - include - - scripts/run_vsim.sh - - src/axi_pkg.sv - - src/axi_intf.sv - - src/axi_test.sv - - src/axi_lite_mailbox.sv - - test/tb_axi_lite_mailbox.sv - outputs: - - build/axi_lite_mailbox-%.tested - axi_lite_xbar-%: inputs: - Bender.yml @@ -258,6 +285,22 @@ artifacts: outputs: - build/axi_to_axi_lite-%.tested + axi_to_mem_banked-%: + inputs: + - Bender.yml + - include + - scripts/run_vsim.sh + - src/axi_pkg.sv + - src/axi_intf.sv + - src/axi_test.sv + - src/axi_demux.sv + - src/axi_to_detailed_mem.sv + - src/axi_to_mem.sv + - src/axi_to_mem_banked.sv + - test/tb_axi_to_mem_banked.sv + outputs: + - build/axi_to_mem_banked-%.tested + axi_xbar-%: inputs: - Bender.yml diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml index 0fe61952e..2996e519b 100644 --- a/.github/workflows/doc.yml +++ b/.github/workflows/doc.yml @@ -17,42 +17,26 @@ jobs: runs-on: ubuntu-latest # will fail) steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: persist-credentials: false - - name: Cache cargo registry - uses: actions/cache@v1 + - name: Install Bender + uses: pulp-platform/pulp-actions/bender-install@v2 with: - path: ~/.cargo/registry - key: ubuntu-latest-cargo-registry-${{ hashFiles('.github/workflows/doc.yml') }} + version: 0.27.2 - - name: Cache cargo index - uses: actions/cache@v1 - with: - path: ~/.cargo/git - key: ubuntu-latest-cargo-index-${{ hashFiles('.github/workflows/doc.yml') }} - - - name: Cache cargo binaries - uses: actions/cache@v1 - with: - path: ~/.cargo/bin - key: ubuntu-latest-cargo-binaries-${{ hashFiles('.github/workflows/doc.yml') }} - - - name: Install Bender and Morty + - name: Install Morty run: | - rustup update stable --no-self-update && rustup default stable - if ! $(which bender); then - cargo install bender --version 0.23.1 - fi - if ! $(which morty); then - cargo install --git https://github.com/zarubaf/morty --rev 4855119c1378d45d9ac35cfa525725d2786e68f3 - fi - shell: bash + sudo mkdir -p /tools/morty && sudo chmod 777 /tools/morty + cd /tools/morty && curl --proto '=https' --tlsv1.2 https://pulp-platform.github.io/morty/init -sSf | bash -s -- 0.9.0 + echo "PATH=/tools/morty:$PATH" >> ${GITHUB_ENV} - name: Build documentation run: | mkdir -p docs + cp doc/axi_demux.png docs/module.axi_demux.png + cp doc/svg/axi_id_remap_table.svg docs/axi_id_remap_table.svg morty -I include -I $(bender path common_cells)/include src/*.sv -d docs shell: bash @@ -74,14 +58,15 @@ jobs: echo "DOC_TARGET=$DOC_TARGET" >> $GITHUB_ENV - name: Deploy documentation - uses: JamesIves/github-pages-deploy-action@releases/v3 + uses: JamesIves/github-pages-deploy-action@v4 if: > - github.event_name == 'push' - || github.event.pull_request.head.repo.full_name == github.repository + (github.event_name == 'push' + || github.event.pull_request.head.repo.full_name == github.repository) && + github.ref == 'refs/heads/master' with: - ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} - BRANCH: gh-pages # The branch the action should deploy to. - FOLDER: docs # The folder the action should deploy. - TARGET_FOLDER: ${{ env.DOC_TARGET }} - CLEAN: true # remove files from `TARGET_FOLDER` that are not in `FOLDER` + token: ${{ secrets.GH_PAGES }} + branch: gh-pages # The branch the action should deploy to. + folder: docs # The folder the action should deploy. + target-folder: ${{ env.DOC_TARGET }} + clean: true # remove files from `TARGET_FOLDER` that are not in `FOLDER` # (`rsync --delete`) diff --git a/.github/workflows/gitlab-ci.yml b/.github/workflows/gitlab-ci.yml index 4118a2a44..96456f43e 100644 --- a/.github/workflows/gitlab-ci.yml +++ b/.github/workflows/gitlab-ci.yml @@ -1,81 +1,22 @@ -name: Retrieve CI result from GitLab +name: Internal CI on: push: branches-ignore: - gh-pages # deployment target branch (this workflow should not exist on that branch anyway) - v** # such branch names conflict with tags - pull_request: - branches-ignore: - - gh-pages # deployment target branch (this workflow should not exist on that branch anyway) - - v** # such branch names conflict with tags + workflow_dispatch: jobs: gitlab-ci: - if: github.repository == 'pulp-platform/axi' # do not run this job on forks (because Gitlab CI - runs-on: ubuntu-latest # will not trigger on forks) - timeout-minutes: 190 + runs-on: ubuntu-latest + timeout-minutes: 310 steps: - - name: Checkout - uses: actions/checkout@v2 + - name: Check Gitlab CI + uses: pulp-platform/pulp-actions/gitlab-ci@v2 + # Skip on forks or pull requests from forks due to missing secrets. + if: github.repository == 'pulp-platform/axi' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) with: - persist-credentials: false - # Checkout pull request HEAD commit instead of merge commit, because CI runs against HEAD - # commit. - ref: ${{ github.event.pull_request.head.sha }} - - - name: Wait for synchronization (every 5 minutes) - run: | - while [ $(($(date -d "+1 minute" +%-M) % 5)) -ne 0 ]; do - # "+1 minute" because if the current minute is divisible by 5, we likely already missed - # the synchronization. - sleep 10 - done - sleep 90 # the minute above plus 30 seconds to leave some time for the synchronization - shell: bash - - - name: Obtain CI result - run: | - if [ "$GITHUB_EVENT_NAME" == "pull_request" ]; then - BRANCH_NAME="$GITHUB_HEAD_REF" - elif [ "$GITHUB_EVENT_NAME" == "push" ]; then - if echo $GITHUB_REF | grep -qE '^refs/heads'; then - BRANCH_NAME="$(echo $GITHUB_REF | cut -d '/' -f3-)" - else - echo "Error: Could not derive branch name from ref '$GITHUB_REF'!" - exit 1 - fi - else - echo "Error: Unsupported event: '$GITHUB_EVENT_NAME'!" - exit 1 - fi - while true; do - resp="$(curl --fail --silent --show-error \ - https://akurth.net/usrv/ig/shields/pipeline/akurth/axi/$BRANCH_NAME)" - if [ $? -ne 0 ]; then - echo "Error: Failed to obtain CI status!" - exit 1 - fi - status="$(echo $resp | jq -r .message)" - if [ "$status" == "passing" ]; then - sha="$(echo $resp | jq -r .sha)" - if [ "$GITHUB_EVENT_NAME" == "pull_request" ]; then - github_sha="$(cat "$GITHUB_EVENT_PATH" | jq -r .pull_request.head.sha)" - else - github_sha="$GITHUB_SHA" - fi - if [ "$sha" == "$github_sha" ]; then - echo "CI passed." - exit 0 - else - echo "Error: CI passed, but on a different SHA: '$sha'!" - exit 1 - fi - elif [ "$status" == "running" ]; then - echo "CI is running, waiting .." - else - echo "Error: Unknown or failing status: '$status'!" - exit 1 - fi - sleep 10 - done - shell: bash + domain: iis-git.ee.ethz.ch + repo: github-mirror/axi + token: ${{ secrets.GITLAB_TOKEN }} + poll-count: 1800 diff --git a/.gitignore b/.gitignore index c9eb4f507..10c4aecdd 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ /build /Bender.lock /Bender.local +*.log +*.wlf diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f00e47d15..1ac12d887 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,5 +1,5 @@ variables: - SYNOPSYS_DC: synopsys-2019.12 dc_shell -64bit + SYNOPSYS_DC: synopsys-2022.03 dcnxt_shell before_script: - export PATH=~/.cargo/bin:$PATH @@ -28,7 +28,7 @@ vsim: fi parallel: matrix: - - VSIM_VER: ['10.7b', '10.7e', '2020.1', '2021.1'] + - VSIM_VER: ['10.7b', '2022.3'] synopsys_dc: stage: build @@ -39,6 +39,18 @@ synopsys_dc: $CI_PROJECT_DIR/.gitlab-ci.d/memora_retry.sh insert synopsys_dc fi +fuse_xsim: + stage: build + allow_failure: true + timeout: 5 minutes + script: + - bender sources + - /usr/local/anaconda3/bin/python3 -m pip install fusesoc --user + - mkdir fusesoc && cd fusesoc + - $HOME/.local/bin/fusesoc library add axi .. + - $HOME/.local/bin/fusesoc core list + - vitis-2022.1-zr $HOME/.local/bin/fusesoc run --tool xsim --target sim --no-export pulp-platform.org::axi:$(cat ../VERSION | sed s/-/./g) + .run_vsim: &run_vsim stage: test script: @@ -61,7 +73,7 @@ synopsys_dc: fi parallel: matrix: - - VSIM_VER: ['10.7b', '10.7e', '2020.1', '2021.1'] + - VSIM_VER: ['10.7b', '2022.3'] axi_addr_test: <<: *run_vsim @@ -93,6 +105,11 @@ axi_dw_upsizer: variables: TEST_MODULE: axi_dw_upsizer +axi_fifo: + <<: *run_vsim + variables: + TEST_MODULE: axi_fifo + axi_isolate: <<: *run_vsim variables: @@ -103,6 +120,16 @@ axi_iw_converter: variables: TEST_MODULE: axi_iw_converter +axi_lite_dw_converter: + <<: *run_vsim + variables: + TEST_MODULE: axi_lite_dw_converter + +axi_lite_mailbox: + <<: *run_vsim + variables: + TEST_MODULE: axi_lite_mailbox + axi_lite_regs: <<: *run_vsim variables: @@ -118,11 +145,6 @@ axi_lite_to_axi: variables: TEST_MODULE: axi_lite_to_axi -axi_lite_mailbox: - <<: *run_vsim - variables: - TEST_MODULE: axi_lite_mailbox - axi_lite_xbar: <<: *run_vsim variables: @@ -148,6 +170,11 @@ axi_to_axi_lite: variables: TEST_MODULE: axi_to_axi_lite +axi_to_mem_banked: + <<: *run_vsim + variables: + TEST_MODULE: axi_to_mem_banked + axi_xbar: <<: *run_vsim variables: diff --git a/Bender.yml b/Bender.yml index e88148e32..d5cc6d008 100644 --- a/Bender.yml +++ b/Bender.yml @@ -1,15 +1,27 @@ package: name: axi authors: - - "Andreas Kurth " # current maintainer + # Alphabetically ordered by last name (maintainers first) + - "Thomas Benz " # current maintainer + - "Michael Rogenmoser " # current maintainer + - "Matheus Cavalcante " + - "Tim Fischer " + - "Noah Huetter " + - "Cyril Koenig " + - "Andreas Kurth " + - "Stefan Mach " + - "Samuel Riedel " + - "Wolfgang Rönninger " + - "Paul Scheffler " - "Fabian Schuiki " + - "Luca Valente " + - "Nils Wistoff " - "Florian Zaruba " - - "Matheus Cavalcante " - - "Wolfgang Roenninger " dependencies: - common_cells: { git: "https://github.com/pulp-platform/common_cells.git", version: 1.21.0 } - common_verification: { git: "https://github.com/pulp-platform/common_verification.git", version: 0.2.0 } + common_cells: { git: "https://github.com/pulp-platform/common_cells.git", version: 1.31.1 } + common_verification: { git: "https://github.com/pulp-platform/common_verification.git", version: 0.2.3 } + tech_cells_generic: { git: "https://github.com/pulp-platform/tech_cells_generic.git", version: 0.2.2 } export_include_dirs: - include @@ -25,19 +37,24 @@ sources: # Level 2 - src/axi_atop_filter.sv - src/axi_burst_splitter.sv + - src/axi_bus_compare.sv - src/axi_cdc_dst.sv - src/axi_cdc_src.sv - src/axi_cut.sv - src/axi_delayer.sv - - src/axi_demux.sv + - src/axi_demux_simple.sv - src/axi_dw_downsizer.sv - src/axi_dw_upsizer.sv + - src/axi_fifo.sv - src/axi_id_remap.sv - src/axi_id_prepend.sv - src/axi_isolate.sv - src/axi_join.sv - src/axi_lite_demux.sv + - src/axi_lite_dw_converter.sv + - src/axi_lite_from_mem.sv - src/axi_lite_join.sv + - src/axi_lite_lfsr.sv - src/axi_lite_mailbox.sv - src/axi_lite_mux.sv - src/axi_lite_regs.sv @@ -45,18 +62,32 @@ sources: - src/axi_lite_to_axi.sv - src/axi_modify_address.sv - src/axi_mux.sv + - src/axi_rw_join.sv + - src/axi_rw_split.sv - src/axi_serializer.sv + - src/axi_slave_compare.sv + - src/axi_throttle.sv + - src/axi_to_detailed_mem.sv # Level 3 - src/axi_cdc.sv + - src/axi_demux.sv - src/axi_err_slv.sv - src/axi_dw_converter.sv + - src/axi_from_mem.sv - src/axi_id_serialize.sv + - src/axi_lfsr.sv - src/axi_multicut.sv - src/axi_to_axi_lite.sv + - src/axi_to_mem.sv # Level 4 - src/axi_iw_converter.sv - src/axi_lite_xbar.sv - src/axi_xbar.sv + - src/axi_to_mem_banked.sv + - src/axi_to_mem_interleaved.sv + - src/axi_to_mem_split.sv + # Level 5 + - src/axi_xp.sv - target: synth_test files: @@ -64,6 +95,8 @@ sources: - target: simulation files: + - src/axi_chan_compare.sv + - src/axi_dumper.sv - src/axi_sim_mem.sv - src/axi_test.sv @@ -75,11 +108,14 @@ sources: # Level 1 - test/tb_axi_addr_test.sv - test/tb_axi_atop_filter.sv + - test/tb_axi_bus_compare.sv - test/tb_axi_cdc.sv - test/tb_axi_delayer.sv - test/tb_axi_dw_downsizer.sv - test/tb_axi_dw_upsizer.sv + - test/tb_axi_fifo.sv - test/tb_axi_isolate.sv + - test/tb_axi_lite_dw_converter.sv - test/tb_axi_lite_mailbox.sv - test/tb_axi_lite_regs.sv - test/tb_axi_iw_converter.sv @@ -89,5 +125,7 @@ sources: - test/tb_axi_modify_address.sv - test/tb_axi_serializer.sv - test/tb_axi_sim_mem.sv + - test/tb_axi_slave_compare.sv - test/tb_axi_to_axi_lite.sv + - test/tb_axi_to_mem_banked.sv - test/tb_axi_xbar.sv diff --git a/CHANGELOG.md b/CHANGELOG.md index a9f5e91f8..33d9a0783 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,11 +7,136 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## Unreleased +## 0.39.1 - 2023-09-05 + +### Added +- `axi_cdc`: Add `SyncStages` parameter. +- `axi_to_mem_interleaved`: Add interface variant. +- `axi_burst_splitter`: Expose `id_queue`'s `FULL_BW` parameter. +- `axi_chan_compare`: Add parameter to allow reordered transactions. +- Add `AXI_HIGHLIGHT` macro to highlight AXI signals. +- Add flat port instantiation macros. + +### Fixed +- `axi_test`: Avoid false negatives for misaligned reads in `axi_scoreboard`. +- `axi_to_detailed_mem`: Ensure proper propagation or `err` and `exokay` signals. + +## 0.39.0 - 2023-07-20 + +### Added +- Synthesizable IPs: + - `axi_bus_compare` and `axi_slave_compare`; two synthesizable verification IPs meant to be used + to compare two AXI buses on an FPGA. + - `axi_lite_from_mem` and `axi_from_mem` acting like SRAMs making AXI4 requests downstream. + - `axi_lite_dw_converter`: Convert the data width of AXI4-Lite transactions. Emits the + appropriate amount of downstream transactions to perform the whole requested access. + - `axi_rw_join` and `axi_rw_split` to split/join the read and write channels of an AXI bus. +- `CT`-macros allowing to instantiate AXI structs with custom channel type names. +- `axi_pkg': Add documentation to `xbar_cfg_t`. +- Testbench IPs: + - `axi_chan_compare.sv`: Non-synthesizable module comparing two AXI channels of the same type + - Add `axi_file_master` to `axi_test`, allowing file-based AXI verification approaches. + - Add `#_width` functions to `axi_test` returning the width of the AXI channels. + +### Changed +- Synthesizable IPs: + - `axi_demux`: Replace FIFO between AW and W channel by a register plus a counter. This prevents + AWs from being issued to one master port while Ws from another burst are ongoing to another + master port. This is required to prevents deadlocks due to circular waits downstream. Removes + `FallThrough` parameter from `axi_demux`. + - Split the `axi_demux` logic and timing decoupling. A new module called `axi_demux_simple` contains + the core logic. + - `axi_dw_downsizer` uses `axi_pkg::RESP_EXOKAY` as a default value. + - Simplify the `casez` in `axi_id_remap`. + - Add optional explicit mapping to the `axi_id_serialize` module. + - Expand `axi_to_mem` to `axi_to_detailed_mem` exposing all of AXI's side-signals; namely `id`, `user`, + `cache`, `prot`, `qos`, `region`, `atop`. Add possibility to inject `err` and `exokay`. + - `axi_xbar`: Add parameter `PipelineStages` to `axi_pkg::xbar_cfg_t`. This adds `axi_multicuts` + in the crossed connections in the `xbar` between the *demuxes* and *muxes*. Improve inline + documentation. + - Move `mem_to_banks` to `common_cells`. +- `axi_pkg`: Improve for better compatibility with *Vivado*. +- `axi_test: + - `axi_lite_rand_slave`: `R` response field is now randomized. + - Remove excessive prints from random master and slave. + - Properly size-align the address. +- `axi_pkg`: Define `localparams` to define AXI type widths. +- Update `common_cells` from version `v1.26.0` to `v1.27.0`. +- Tooling: + - Use `pulp-platform/pulp-actions/gitlab-ci@v2` in the GitHub CI to communicate with the internal CI. + - Bump `DC Shell version` from `2019.12` to `2022.03` + - No longer check *ModelSim* versions `10.7e` and `2021.3`, add `2022.3`. + - More thorough verification runs for the `xbar`. + - Start transitioning from shell script to Makefile to run simulations. +- Use `scripts/update_authors` to update authors, slight manual fixes performed. + +### Fixed +- `axi_to_mem_banked`: Reduce hardware by properly setting `UniqueIds`. +- `axi_to_mem_interleaved` and `axi_to_mem_split` properly instantiates a demultiplexer now. + Adds `test_i` port for DFT. + +### Breaking Changes +There are breaking changes between `v0.38.0` and `v0.39.0`: +- `axi_demux`: `FallThrough` parameter was removed. +- `axi_xbar`: `axi_pkg::xbar_cfg_t` added `PipelineStages` parameter. +- `axi_to_mem_interleaved` and `axi_to_mem_split`: Added `test_i` input port. + +## 0.38.0 - 2022-09-28 + +### Added +- Add `axi_dumper` and `axi_dumper_interpret` script to dump log from an AXI bus for debugging + purposes. +- Add FuseSoC and Vivado XSIM limited test to CI +- `assign.svh`: Add macros to assign flat buses using the Vivado naming style. +- `axi_lfsr` and `axi_lite_lfsr`: Add AXI4 and AXI4 Lite LFSR Subordinate devices. +- `axi_xp`: Add crosspoint with homomorphous slave and master ports. + +### Changed +- Improve compatibility with FuseSoC +- Improve compatibility with Vivado XSIM +- Performance improvements to `axi_to_mem` +- Use `scripts/update_authors` to update authors, slight manual fixes performed. + +`v0.38.0` is fully **backward-compatible** to `v0.36.0` and `v0.37.0`. + + +## 0.37.0 - 2022-08-30 + ### Added +- `axi_fifo`: Inserts a FIFO into all 5 AXI4 channels; add module and its testbench +- `axi_test`: Add `mapped` mode to the random classes as well as additional functionality to the + scoreboard class. +- `axi_throttle`: Add a module that limits the maximum number of outstanding transfers sent to the + downstream logic. +- `axi_to_mem`: AXI4+ATOP slave to control on chip memory. +- `axi_to_mem_banked`: AXI4+ATOP slave to control on chip memory, with banking support, higher + throughput than `axi_to_mem`. +- `axi_to_mem_interleaved`: AXI4+ATOP slave to control on chip memory, interleaved to prevent + deadlocks. +- `axi_to_mem_split`: AXI4+ATOP slave to control memory protocol interconnect. +- `Bender`: Add dependency `tech_cells_generic` `v0.2.2` for generic SRAM macro for simulation. ### Changed +- `axi_demux`: Add module docstring +- `axi_sim_mem`: Add the capability to emit read and write errors +- `Bender`: Update dependency `common_cells` to `v1.26.0` from `v1.21.0` (required by + `axi_throttle`) +- Remove `docs` directory, move content to `doc` folder. `docs` is automatically created and + populated during the CI run. +- Update vsim version to `2021.3` in CI, drop test for `2020.1` and `2021.1` ### Fixed +- `axi_lite_demux`: Improve compatibility with vsim version 10.7b. +- `axi_lite_mux`: Reduce complexity of W channel at master port by removing an unnecessary + multiplexer. + +`v0.37.0` is fully **backward-compatible** to `v0.36.0`. + + +## 0.36.0 - 2022-07-07 + +### Added +- Add Monitor modport to `AXI_BUS`, `AXI_LITE`, and `AXI_LITE_DV` interfaces. ## 0.35.3 - 2022-05-03 diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..5d97a9542 --- /dev/null +++ b/Makefile @@ -0,0 +1,90 @@ +# Copyright and related rights are licensed under the Solderpad Hardware +# License, Version 0.51 (the "License"); you may not use this file except in +# compliance with the License. You may obtain a copy of the License at +# http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +# or agreed to in writing, software, hardware and materials distributed under +# this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# +# Authors: +# - Thomas Benz + +# select IIS-internal tool commands if we run on IIS machines +ifneq (,$(wildcard /etc/iis.version)) + VSIM ?= questa-2022.3 vsim + SYNOPSYS_DC ?= synopsys-2022.03 dcnxt_shell +else + VSIM ?= vsim + SYNOPSYS_DC ?= dc_shell +endif + +TBS ?= axi_addr_test \ + axi_atop_filter \ + axi_cdc axi_delayer \ + axi_dw_downsizer \ + axi_dw_upsizer \ + axi_fifo \ + axi_isolate \ + axi_iw_converter \ + axi_lite_regs \ + axi_lite_to_apb \ + axi_lite_to_axi \ + axi_lite_mailbox \ + axi_lite_xbar \ + axi_modify_address \ + axi_serializer \ + axi_sim_mem \ + axi_to_axi_lite \ + axi_to_mem_banked \ + axi_xbar + +SIM_TARGETS := $(addsuffix .log,$(addprefix sim-,$(TBS))) + + +.SHELL: bash + +.PHONY: help all sim_all clean + + +help: + @echo "" + @echo "elab.log: elaborates all files using Synopsys DC" + @echo "compile.log: compile files using Questasim" + @echo "sim-#TB#.log: simulates a given testbench, available TBs are:" + @echo "$(addprefix ###############-#,$(TBS))" | sed -e 's/ /\n/g' | sed -e 's/#/ /g' + @echo "sim_all: simulates all available testbenches" + @echo "" + @echo "clean: cleans generated files" + @echo "" + + +all: compile.log elab.log sim_all + + +sim_all: $(SIM_TARGETS) + + +build: + mkdir -p $@ + + +elab.log: Bender.yml | build + export SYNOPSYS_DC="$(SYNOPSYS_DC)"; cd build && ../scripts/synth.sh | tee ../$@ + (! grep -n "Error:" $@) + + +compile.log: Bender.yml | build + export VSIM="$(VSIM)"; cd build && ../scripts/compile_vsim.sh | tee ../$@ + (! grep -n "Error:" $@) + + +sim-%.log: compile.log + export VSIM="$(VSIM)"; cd build && ../scripts/run_vsim.sh --random-seed $* | tee ../$@ + (! grep -n "Error:" $@) + (! grep -n "Fatal:" $@) + + +clean: + rm -rf build + rm -f *.log diff --git a/README.md b/README.md index 702089d7c..962185172 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # AXI SystemVerilog Modules for High-Performance On-Chip Communication -[![CI status](https://akurth.net/usrv/ig/shields/pipeline/akurth/axi/master.svg)](https://iis-git.ee.ethz.ch/akurth/axi/commits/master) +[![CI status](https://github.com/pulp-platform/axi/actions/workflows/gitlab-ci.yml/badge.svg?branch=master)](https://github.com/pulp-platform/axi/actions/workflows/gitlab-ci.yml?query=branch%3Amaster) [![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/pulp-platform/axi?color=blue&label=current&sort=semver)](CHANGELOG.md) [![SHL-0.51 license](https://img.shields.io/badge/license-SHL--0.51-green)](LICENSE) @@ -19,41 +19,63 @@ The **design and microarchitecture** of the modules in this repository is descri In addition to the documents linked in the following table, we are setting up [documentation auto-generated from inline docstrings](https://pulp-platform.github.io/axi/master). (Replace `master` in that URL with a tag to get the documentation for a specific version.) -| Name | Description | Doc | -|------------------------------------------------------|---------------------------------------------------------------------------------------------------|--------------------------------| -| [`axi_atop_filter`](src/axi_atop_filter.sv) | Filters atomic operations (ATOPs), i.e., write transactions that have a non-zero `aw_atop` value. | | -| [`axi_burst_splitter`](src/axi_burst_splitter.sv) | Split AXI4 burst transfers into single-beat transactions. | | -| [`axi_cdc`](src/axi_cdc.sv) | AXI clock domain crossing based on a Gray FIFO implementation. | | -| [`axi_cut`](src/axi_cut.sv) | Breaks all combinatorial paths between its input and output. | | -| [`axi_delayer`](src/axi_delayer.sv) | Synthesizable module which can (randomly) delays AXI channels. | | -| [`axi_demux`](src/axi_demux.sv) | Demultiplexes an AXI bus from one slave port to multiple master ports. | [Doc](doc/axi_demux.md) | -| [`axi_dw_converter`](src/axi_dw_converter.sv) | A data width converter between AXI interfaces of any data width. | | -| [`axi_dw_downsizer`](src/axi_dw_downsizer.sv) | A data width converter between a wide AXI master and a narrower AXI slave. | | -| [`axi_dw_upsizer`](src/axi_dw_upsizer.sv) | A data width converter between a narrow AXI master and a wider AXI slave. | | -| [`axi_err_slv`](src/axi_err_slv.sv) | Always responds with an AXI decode/slave error for transactions which are sent to it. | | -| [`axi_id_prepend`](src/axi_id_prepend.sv) | This module prepends/strips the MSB from the AXI IDs. | | -| [`axi_id_remap`](src/axi_id_remap.sv) | Remap AXI IDs from wide IDs at the slave port to narrower IDs at the master port. | [Doc][doc.axi_id_remap] | -| [`axi_id_serialize`](src/axi_id_serialize.sv) | Reduce AXI IDs by serializing transactions when necessary. | [Doc][doc.axi_id_serialize] | -| [`axi_intf`](src/axi_intf.sv) | This file defines the interfaces we support. | | -| [`axi_isolate`](src/axi_isolate.sv) | A module that can isolate downstream slaves from receiving new AXI4 transactions. | | -| [`axi_iw_converter`](src/axi_iw_converter.sv) | Convert between any two AXI ID widths. | [Doc][doc.axi_iw_converter] | -| [`axi_join`](src/axi_join.sv) | A connector that joins two AXI interfaces. | | -| [`axi_lite_demux`](src/axi_lite_demux.sv) | Demultiplexes an AXI4-Lite bus from one slave port to multiple master ports. | [Doc](doc/axi_lite_demux.md) | -| [`axi_lite_join`](src/axi_lite_join.sv) | A connector that joins two AXI-Lite interfaces. | | -| [`axi_lite_mailbox`](src/axi_lite_mailbox.sv) | A AXI4-Lite Mailbox with two slave ports and usage triggered irq. | [Doc](doc/axi_lite_mailbox.md) | -| [`axi_lite_mux`](src/axi_lite_mux.sv) | Multiplexes AXI4-Lite slave ports down to one master port. | [Doc](doc/axi_lite_mux.md) | -| [`axi_lite_regs`](src/axi_lite_regs.sv) | AXI4-Lite registers with optional read-only and protection features. | [Doc][doc.axi_lite_regs] | -| [`axi_lite_to_apb`](src/axi_lite_to_apb.sv) | AXI4-Lite to APB4 protocol converter. | | -| [`axi_lite_to_axi`](src/axi_lite_to_axi.sv) | AXI4-Lite to AXI4 protocol converter. | | -| [`axi_lite_xbar`](src/axi_lite_xbar.sv) | Fully-connected AXI4-Lite crossbar with an arbitrary number of slave and master ports. | [Doc](doc/axi_lite_xbar.md) | -| [`axi_modify_address`](src/axi_modify_address.sv) | A connector that allows addresses of AXI requests to be changed. | | -| [`axi_multicut`](src/axi_multicut.sv) | AXI register which can be used to relax timing pressure on long AXI buses. | | -| [`axi_mux`](src/axi_mux.sv) | Multiplexes the AXI4 slave ports down to one master port. | [Doc](doc/axi_mux.md) | -| [`axi_pkg`](src/axi_pkg.sv) | Contains AXI definitions, common structs, and useful helper functions. | | -| [`axi_serializer`](src/axi_serializer.sv) | Serializes transactions with different IDs to the same ID. | | -| [`axi_test`](src/axi_test.sv) | A set of testbench utilities for AXI interfaces. | | -| [`axi_to_axi_lite`](src/axi_to_axi_lite.sv) | AXI4 to AXI4-Lite protocol converter. | | -| [`axi_xbar`](src/axi_xbar.sv) | Fully-connected AXI4+ATOP crossbar with an arbitrary number of slave and master ports. | [Doc](doc/axi_xbar.md) | +| Name | Description | Doc | +|---------------------------------------------------------|------------------------------------------------------------------------------------------------------|----------------------------------| +| [`axi_atop_filter`](src/axi_atop_filter.sv) | Filters atomic operations (ATOPs), i.e., write transactions that have a non-zero `aw_atop` value. | | +| [`axi_burst_splitter`](src/axi_burst_splitter.sv) | Split AXI4 burst transfers into single-beat transactions. | | +| [`axi_cdc`](src/axi_cdc.sv) | AXI clock domain crossing based on a Gray FIFO implementation. | | +| [`axi_cut`](src/axi_cut.sv) | Breaks all combinatorial paths between its input and output. | | +| [`axi_delayer`](src/axi_delayer.sv) | Synthesizable module which can (randomly) delays AXI channels. | | +| [`axi_demux_simple`](src/axi_demux_simple.sv) | Demux without spill registers. | [Doc](doc/axi_demux.md) | +| [`axi_demux`](src/axi_demux.sv) | Demultiplexes an AXI bus from one slave port to multiple master ports. | [Doc](doc/axi_demux.md) | +| [`axi_dw_converter`](src/axi_dw_converter.sv) | A data width converter between AXI interfaces of any data width. | | +| [`axi_dw_downsizer`](src/axi_dw_downsizer.sv) | A data width converter between a wide AXI master and a narrower AXI slave. | | +| [`axi_dw_upsizer`](src/axi_dw_upsizer.sv) | A data width converter between a narrow AXI master and a wider AXI slave. | | +| [`axi_err_slv`](src/axi_err_slv.sv) | Always responds with an AXI decode/slave error for transactions which are sent to it. | | +| [`axi_fifo`](src/axi_fifo.sv) | A Fifo for each AXI4 channel to buffer requests. | | +| [`axi_from_mem`](src/axi_from_mem.sv) | This module acts like an SRAM and makes AXI4 requests downstream. | | +| [`axi_id_prepend`](src/axi_id_prepend.sv) | This module prepends/strips the MSB from the AXI IDs. | | +| [`axi_id_remap`](src/axi_id_remap.sv) | Remap AXI IDs from wide IDs at the slave port to narrower IDs at the master port. | [Doc][doc.axi_id_remap] | +| [`axi_id_serialize`](src/axi_id_serialize.sv) | Reduce AXI IDs by serializing transactions when necessary. | [Doc][doc.axi_id_serialize] | +| [`axi_intf`](src/axi_intf.sv) | This file defines the interfaces we support. | | +| [`axi_isolate`](src/axi_isolate.sv) | A module that can isolate downstream slaves from receiving new AXI4 transactions. | | +| [`axi_iw_converter`](src/axi_iw_converter.sv) | Convert between any two AXI ID widths. | [Doc][doc.axi_iw_converter] | +| [`axi_join`](src/axi_join.sv) | A connector that joins two AXI interfaces. | | +| [`axi_lfsr`](src/axi_lfsr.sv) | AXI4-attached LFSR; read returns pseudo-random data, writes are compressed into a checksum. | | +| [`axi_lite_demux`](src/axi_lite_demux.sv) | Demultiplexes an AXI4-Lite bus from one slave port to multiple master ports. | [Doc](doc/axi_lite_demux.md) | +| [`axi_lite_dw_converter`](src/axi_lite_dw_converter.sv) | A data width converter between two AXI-Lite busses | [Doc][doc.axi_lite_dw_converter] | +| [`axi_lite_from_mem`](src/axi_lite_from_mem.sv) | This module acts like an SRAM and makes AXI4-Lite requests downstream. | | +| [`axi_lite_join`](src/axi_lite_join.sv) | A connector that joins two AXI-Lite interfaces. | | +| [`axi_lite_lfsr`](src/axi_lite_lfsr.sv) | AXI4-Lite-attached LFSR; read returns pseudo-random data, writes are compressed into a checksum. | | +| [`axi_lite_mailbox`](src/axi_lite_mailbox.sv) | A AXI4-Lite Mailbox with two slave ports and usage triggered irq. | [Doc](doc/axi_lite_mailbox.md) | +| [`axi_lite_mux`](src/axi_lite_mux.sv) | Multiplexes AXI4-Lite slave ports down to one master port. | [Doc](doc/axi_lite_mux.md) | +| [`axi_lite_regs`](src/axi_lite_regs.sv) | AXI4-Lite registers with optional read-only and protection features. | [Doc][doc.axi_lite_regs] | +| [`axi_lite_to_apb`](src/axi_lite_to_apb.sv) | AXI4-Lite to APB4 protocol converter. | | +| [`axi_lite_to_axi`](src/axi_lite_to_axi.sv) | AXI4-Lite to AXI4 protocol converter. | | +| [`axi_lite_xbar`](src/axi_lite_xbar.sv) | Fully-connected AXI4-Lite crossbar with an arbitrary number of slave and master ports. | [Doc](doc/axi_lite_xbar.md) | +| [`axi_modify_address`](src/axi_modify_address.sv) | A connector that allows addresses of AXI requests to be changed. | | +| [`axi_multicut`](src/axi_multicut.sv) | AXI register which can be used to relax timing pressure on long AXI buses. | | +| [`axi_mux`](src/axi_mux.sv) | Multiplexes the AXI4 slave ports down to one master port. | [Doc](doc/axi_mux.md) | +| [`axi_pkg`](src/axi_pkg.sv) | Contains AXI definitions, common structs, and useful helper functions. | | +| [`axi_rw_join`](src/axi_rw_join.sv) | Joins a read and a write slave into one single read / write master. | | +| [`axi_rw_split`](src/axi_rw_split.sv) | Splits a single read / write slave into one read and one write master. | | +| [`axi_serializer`](src/axi_serializer.sv) | Serializes transactions with different IDs to the same ID. | | +| [`axi_slave_compare`](src/axi_slave_compare.sv) | Compares two slave devices. | | +| [`axi_throttle`](src/axi_throttle.sv) | Limits the maximum number of outstanding transfers sent to the downstream logic. | | +| [`axi_test`](src/axi_test.sv) | A set of testbench utilities for AXI interfaces. | | +| [`axi_to_axi_lite`](src/axi_to_axi_lite.sv) | AXI4 to AXI4-Lite protocol converter. | | +| [`axi_to_mem`](src/axi_to_mem.sv) | AXI4 to memory protocol (req, gnt, rvalid) converter. Additional banked, interleaved, split variant. | | +| [`axi_xbar`](src/axi_xbar.sv) | Fully-connected AXI4+ATOP crossbar with an arbitrary number of slave and master ports. | [Doc](doc/axi_xbar.md) | +| [`axi_xp`](src/axi_xp.sv) | AXI Crosspoint (XP) with homomorphous slave and master ports. | | + +## Synthesizable Verification Modules + +The following modules are meant to be used for verification purposes only but are synthesizable to be used in FPGA environments. + +| Name | Description | +|------------------------------------------------------|---------------------------------------------------------------------------------------------------------| +| [`axi_bus_compare`](src/axi_bus_compare.sv) | Compares two buses of the same type (and in the same clock domain), returns events on mismatch. | +| [`axi_slave_compare`](src/axi_slave_compare.sv) | Compares two slave devices of the same type (and in the same clock domain), returns events on mismatch. | ### Simulation-Only Modules @@ -61,8 +83,11 @@ In addition to the modules above, which are available in synthesis and simulatio | Name | Description | |------------------------------------------------------|--------------------------------------------------------------------------------------------------------| +| [`axi_chan_compare`](src/axi_chan_compare.sv) | Non-synthesizable module comparing two AXI channels of the same type | | [`axi_chan_logger`](src/axi_test.sv) | Logs the transactions of an AXI4(+ATOPs) port to files. | | [`axi_driver`](src/axi_test.sv) | Low-level driver for AXI4(+ATOPs) that can send and receive individual beats on any channel. | +| [`axi_dumper`](src/axi_dumper.sv) | Dumps log to file to be interpreted by `axi_dumper_interpret` script for debugging purposes. | +| [`axi_file_master`](src/axi_test.sv) | AXI4 master for file-based testbenches | | [`axi_lite_driver`](src/axi_test.sv) | Low-level driver for AXI4-Lite that can send and receive individual beats on any channel. | | [`axi_lite_rand_master`](src/axi_test.sv) | AXI4-Lite master component that issues random transactions within user-defined constraints. | | [`axi_lite_rand_slave`](src/axi_test.sv) | AXI4-Lite slave component that responds to transactions with constrainable random delays and data. | @@ -98,6 +123,9 @@ We aim to be compatible with a wide range of EDA tools. For this reason, we str - the workaround does not break functionality in other tools, and - the workaround does not significantly complicate code or add maintenance overhead. +In addition, we suggest to report issues with the SystemVerilog language support directly to the EDA vendor. Our code is fully open and +can / should be shared with the EDA vendor as a testcase for any language problem encountered. + All code in each release and on the default branch is tested on a recent version of at least one industry-standard RTL simulator and synthesizer. You can examine the [CI settings](./.gitlab-ci.yml) to find out which version of which tool we are running. diff --git a/VERSION b/VERSION index cd1220447..d2e2400ee 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.35.4-dev +0.39.1 diff --git a/axi.core b/axi.core index fa2adb017..9b7ebac2f 100644 --- a/axi.core +++ b/axi.core @@ -1,6 +1,6 @@ CAPI=2: -name : pulp-platform.org::axi:0.35.4.dev +name : pulp-platform.org::axi:0.39.1 filesets: rtl: @@ -17,19 +17,24 @@ filesets: # Level 2 - src/axi_atop_filter.sv - src/axi_burst_splitter.sv + - src/axi_bus_compare.sv - src/axi_cdc_dst.sv - src/axi_cdc_src.sv - src/axi_cut.sv - src/axi_delayer.sv - - src/axi_demux.sv + - src/axi_demux_simple.sv - src/axi_dw_downsizer.sv - src/axi_dw_upsizer.sv + - src/axi_fifo.sv - src/axi_id_remap.sv - src/axi_id_prepend.sv - src/axi_isolate.sv - src/axi_join.sv - src/axi_lite_demux.sv + - src/axi_lite_dw_converter.sv + - src/axi_lite_from_mem.sv - src/axi_lite_join.sv + - src/axi_lite_lfsr.sv - src/axi_lite_mailbox.sv - src/axi_lite_mux.sv - src/axi_lite_regs.sv @@ -37,22 +42,39 @@ filesets: - src/axi_lite_to_axi.sv - src/axi_modify_address.sv - src/axi_mux.sv + - src/axi_rw_join.sv + - src/axi_rw_split.sv - src/axi_serializer.sv + - src/axi_slave_compare.sv + - src/axi_throttle.sv + - src/axi_to_detailed_mem.sv # Level 3 - src/axi_cdc.sv + - src/axi_demux.sv - src/axi_err_slv.sv - src/axi_dw_converter.sv + - src/axi_from_mem.sv - src/axi_id_serialize.sv + - src/axi_lfsr.sv - src/axi_multicut.sv - src/axi_to_axi_lite.sv + - src/axi_to_mem.sv # Level 4 - src/axi_iw_converter.sv - src/axi_lite_xbar.sv - src/axi_xbar.sv + - src/axi_to_mem_banked.sv + - src/axi_to_mem_interleaved.sv + - src/axi_to_mem_split.sv + - src/axi_chan_compare.sv + - src/axi_dumper.sv - src/axi_sim_mem.sv + #- src/axi_test.sv # this is not synthesizable + # Level 5 + - src/axi_xp.sv file_type : systemVerilogSource depend : - - ">=pulp-platform.org::common_cells:1.24.0" + - ">=pulp-platform.org::common_cells:1.31.1" benchs: files: - test/tb_axi_dw_pkg.sv @@ -60,12 +82,15 @@ filesets: - test/axi_synth_bench.sv - test/tb_axi_addr_test.sv - test/tb_axi_atop_filter.sv + - test/tb_axi_bus_compare.sv - test/tb_axi_cdc.sv - test/tb_axi_delayer.sv - test/tb_axi_dw_downsizer.sv - test/tb_axi_dw_upsizer.sv + - test/tb_axi_fifo.sv - test/tb_axi_isolate.sv - test/tb_axi_iw_converter.sv + - test/tb_axi_lite_dw_converter.sv - test/tb_axi_lite_mailbox.sv - test/tb_axi_lite_regs.sv - test/tb_axi_lite_to_apb.sv @@ -74,11 +99,13 @@ filesets: - test/tb_axi_modify_address.sv - test/tb_axi_serializer.sv - test/tb_axi_sim_mem.sv + - test/tb_axi_slave_compare.sv - test/tb_axi_to_axi_lite.sv + - test/tb_axi_to_mem_banked.sv - test/tb_axi_xbar.sv file_type : systemVerilogSource depend : - - ">=pulp-platform.org::common_verification:0.2.1" + - ">=pulp-platform.org::common_verification:0.2.3" generators: axi_intercon_gen: diff --git a/axi_test.core b/axi_test.core index 328ad4805..78619d557 100644 --- a/axi_test.core +++ b/axi_test.core @@ -11,6 +11,7 @@ filesets: file_type : systemVerilogSource depend : - ">=pulp-platform.org::common_verification:0.2.1" + - pulp-platform.org::axi:0.39.1 targets: default: filesets : [bench] diff --git a/docs/axi_id_remap_table.svg b/doc/svg/axi_id_remap_table.svg similarity index 100% rename from docs/axi_id_remap_table.svg rename to doc/svg/axi_id_remap_table.svg diff --git a/include/axi/assign.svh b/include/axi/assign.svh index 14bb1944c..739038f6c 100644 --- a/include/axi/assign.svh +++ b/include/axi/assign.svh @@ -11,7 +11,7 @@ // // Authors: // - Andreas Kurth -// - Wolfgang Roenninger +// - Nils Wistoff // Macros to assign AXI Interfaces and Structs @@ -538,4 +538,160 @@ //////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Macros for assigning flattened AXI ports to req/resp AXI structs +// Flat AXI ports are required by the Vivado IP Integrator. Vivado naming convention is followed. +// +// Usage Example: +// `AXI_ASSIGN_MASTER_TO_FLAT("my_bus", my_req_struct, my_rsp_struct) +`define AXI_ASSIGN_MASTER_TO_FLAT(pat, req, rsp) \ + assign m_axi_``pat``_awvalid = req.aw_valid; \ + assign m_axi_``pat``_awid = req.aw.id; \ + assign m_axi_``pat``_awaddr = req.aw.addr; \ + assign m_axi_``pat``_awlen = req.aw.len; \ + assign m_axi_``pat``_awsize = req.aw.size; \ + assign m_axi_``pat``_awburst = req.aw.burst; \ + assign m_axi_``pat``_awlock = req.aw.lock; \ + assign m_axi_``pat``_awcache = req.aw.cache; \ + assign m_axi_``pat``_awprot = req.aw.prot; \ + assign m_axi_``pat``_awqos = req.aw.qos; \ + assign m_axi_``pat``_awregion = req.aw.region; \ + assign m_axi_``pat``_awuser = req.aw.user; \ + \ + assign m_axi_``pat``_wvalid = req.w_valid; \ + assign m_axi_``pat``_wdata = req.w.data; \ + assign m_axi_``pat``_wstrb = req.w.strb; \ + assign m_axi_``pat``_wlast = req.w.last; \ + assign m_axi_``pat``_wuser = req.w.user; \ + \ + assign m_axi_``pat``_bready = req.b_ready; \ + \ + assign m_axi_``pat``_arvalid = req.ar_valid; \ + assign m_axi_``pat``_arid = req.ar.id; \ + assign m_axi_``pat``_araddr = req.ar.addr; \ + assign m_axi_``pat``_arlen = req.ar.len; \ + assign m_axi_``pat``_arsize = req.ar.size; \ + assign m_axi_``pat``_arburst = req.ar.burst; \ + assign m_axi_``pat``_arlock = req.ar.lock; \ + assign m_axi_``pat``_arcache = req.ar.cache; \ + assign m_axi_``pat``_arprot = req.ar.prot; \ + assign m_axi_``pat``_arqos = req.ar.qos; \ + assign m_axi_``pat``_arregion = req.ar.region; \ + assign m_axi_``pat``_aruser = req.ar.user; \ + \ + assign m_axi_``pat``_rready = req.r_ready; \ + \ + assign rsp.aw_ready = m_axi_``pat``_awready; \ + assign rsp.ar_ready = m_axi_``pat``_arready; \ + assign rsp.w_ready = m_axi_``pat``_wready; \ + \ + assign rsp.b_valid = m_axi_``pat``_bvalid; \ + assign rsp.b.id = m_axi_``pat``_bid; \ + assign rsp.b.resp = m_axi_``pat``_bresp; \ + assign rsp.b.user = m_axi_``pat``_buser; \ + \ + assign rsp.r_valid = m_axi_``pat``_rvalid; \ + assign rsp.r.id = m_axi_``pat``_rid; \ + assign rsp.r.data = m_axi_``pat``_rdata; \ + assign rsp.r.resp = m_axi_``pat``_rresp; \ + assign rsp.r.last = m_axi_``pat``_rlast; \ + assign rsp.r.user = m_axi_``pat``_ruser; + +`define AXI_ASSIGN_SLAVE_TO_FLAT(pat, req, rsp) \ + assign req.aw_valid = s_axi_``pat``_awvalid; \ + assign req.aw.id = s_axi_``pat``_awid; \ + assign req.aw.addr = s_axi_``pat``_awaddr; \ + assign req.aw.len = s_axi_``pat``_awlen; \ + assign req.aw.size = s_axi_``pat``_awsize; \ + assign req.aw.burst = s_axi_``pat``_awburst; \ + assign req.aw.lock = s_axi_``pat``_awlock; \ + assign req.aw.cache = s_axi_``pat``_awcache; \ + assign req.aw.prot = s_axi_``pat``_awprot; \ + assign req.aw.qos = s_axi_``pat``_awqos; \ + assign req.aw.region = s_axi_``pat``_awregion; \ + assign req.aw.user = s_axi_``pat``_awuser; \ + \ + assign req.w_valid = s_axi_``pat``_wvalid; \ + assign req.w.data = s_axi_``pat``_wdata; \ + assign req.w.strb = s_axi_``pat``_wstrb; \ + assign req.w.last = s_axi_``pat``_wlast; \ + assign req.w.user = s_axi_``pat``_wuser; \ + \ + assign req.b_ready = s_axi_``pat``_bready; \ + \ + assign req.ar_valid = s_axi_``pat``_arvalid; \ + assign req.ar.id = s_axi_``pat``_arid; \ + assign req.ar.addr = s_axi_``pat``_araddr; \ + assign req.ar.len = s_axi_``pat``_arlen; \ + assign req.ar.size = s_axi_``pat``_arsize; \ + assign req.ar.burst = s_axi_``pat``_arburst; \ + assign req.ar.lock = s_axi_``pat``_arlock; \ + assign req.ar.cache = s_axi_``pat``_arcache; \ + assign req.ar.prot = s_axi_``pat``_arprot; \ + assign req.ar.qos = s_axi_``pat``_arqos; \ + assign req.ar.region = s_axi_``pat``_arregion; \ + assign req.ar.user = s_axi_``pat``_aruser; \ + \ + assign req.r_ready = s_axi_``pat``_rready; \ + \ + assign s_axi_``pat``_awready = rsp.aw_ready; \ + assign s_axi_``pat``_arready = rsp.ar_ready; \ + assign s_axi_``pat``_wready = rsp.w_ready; \ + \ + assign s_axi_``pat``_bvalid = rsp.b_valid; \ + assign s_axi_``pat``_bid = rsp.b.id; \ + assign s_axi_``pat``_bresp = rsp.b.resp; \ + assign s_axi_``pat``_buser = rsp.b.user; \ + \ + assign s_axi_``pat``_rvalid = rsp.r_valid; \ + assign s_axi_``pat``_rid = rsp.r.id; \ + assign s_axi_``pat``_rdata = rsp.r.data; \ + assign s_axi_``pat``_rresp = rsp.r.resp; \ + assign s_axi_``pat``_rlast = rsp.r.last; \ + assign s_axi_``pat``_ruser = rsp.r.user; +//////////////////////////////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Macros instantiating signal highlighter from common verification +// https://github.com/pulp-platform/common_verification.git +// `AXI_HIGHLIGHT(__name, __aw_t, __w_t, __b_t, __ar_t, __r_t, __req, __resp) +`define AXI_HIGHLIGHT(__name, __aw_t, __w_t, __b_t, __ar_t, __r_t, __req, __resp) \ + signal_highlighter #( \ + .T ( __aw_t ) \ + ) i_signal_highlighter_``__name``_aw ( \ + .ready_i ( __resp.aw_ready ), \ + .valid_i ( __req.aw_valid ), \ + .data_i ( __req.aw ) \ + ); \ + signal_highlighter #( \ + .T ( __w_t ) \ + ) i_signal_highlighter_``__name``_w ( \ + .ready_i ( __resp.w_ready ), \ + .valid_i ( __req.w_valid ), \ + .data_i ( __req.w ) \ + ); \ + signal_highlighter #( \ + .T ( __b_t ) \ + ) i_signal_highlighter_``__name``_b ( \ + .ready_i ( __req.b_ready ), \ + .valid_i ( __resp.b_valid ), \ + .data_i ( __resp.b ) \ + ); \ + signal_highlighter #( \ + .T ( __ar_t ) \ + ) i_signal_highlighter_``__name``_ar ( \ + .ready_i ( __resp.ar_ready ), \ + .valid_i ( __req.ar_valid ), \ + .data_i ( __req.ar ) \ + ); \ + signal_highlighter #( \ + .T ( __r_t ) \ + ) i_signal_highlighter_``__name``_r ( \ + .ready_i ( __req.r_ready ), \ + .valid_i ( __resp.r_valid ), \ + .data_i ( __resp.r ) \ + ); +//////////////////////////////////////////////////////////////////////////////////////////////////// + `endif diff --git a/include/axi/port.svh b/include/axi/port.svh new file mode 100644 index 000000000..c3d7a4a7f --- /dev/null +++ b/include/axi/port.svh @@ -0,0 +1,120 @@ +// Copyright (c) 2014-2023 ETH Zurich, University of Bologna +// +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the "License"); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. +// +// Authors: +// - Thomas Benz + +// Macros to add AXI ports + +`ifndef AXI_PORT_SVH_ +`define AXI_PORT_SVH_ + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Macros creating flat AXI ports +// `AXI_M_PORT(__name, __addr_t, __data_t, __strb_t, __id_t, __aw_user_t, __w_user_t, __b_user_t, __ar_user_t, __r_user_t) +`define AXI_M_PORT(__name, __addr_t, __data_t, __strb_t, __id_t, __aw_user_t, __w_user_t, __b_user_t, __ar_user_t, __r_user_t) \ + output logic m_axi_``__name``_awvalid, \ + output __id_t m_axi_``__name``_awid, \ + output __addr_t m_axi_``__name``_awaddr, \ + output axi_pkg::len_t m_axi_``__name``_awlen, \ + output axi_pkg::size_t m_axi_``__name``_awsize, \ + output axi_pkg::burst_t m_axi_``__name``_awburst, \ + output logic m_axi_``__name``_awlock, \ + output axi_pkg::cache_t m_axi_``__name``_awcache, \ + output axi_pkg::prot_t m_axi_``__name``_awprot, \ + output axi_pkg::qos_t m_axi_``__name``_awqos, \ + output axi_pkg::region_t m_axi_``__name``_awregion, \ + output __aw_user_t m_axi_``__name``_awuser, \ + output logic m_axi_``__name``_wvalid, \ + output __data_t m_axi_``__name``_wdata, \ + output __strb_t m_axi_``__name``_wstrb, \ + output logic m_axi_``__name``_wlast, \ + output __w_user_t m_axi_``__name``_wuser, \ + output logic m_axi_``__name``_bready, \ + output logic m_axi_``__name``_arvalid, \ + output __id_t m_axi_``__name``_arid, \ + output __addr_t m_axi_``__name``_araddr, \ + output axi_pkg::len_t m_axi_``__name``_arlen, \ + output axi_pkg::size_t m_axi_``__name``_arsize, \ + output axi_pkg::burst_t m_axi_``__name``_arburst, \ + output logic m_axi_``__name``_arlock, \ + output axi_pkg::cache_t m_axi_``__name``_arcache, \ + output axi_pkg::prot_t m_axi_``__name``_arprot, \ + output axi_pkg::qos_t m_axi_``__name``_arqos, \ + output axi_pkg::region_t m_axi_``__name``_arregion, \ + output __ar_user_t m_axi_``__name``_aruser, \ + output logic m_axi_``__name``_rready, \ + input logic m_axi_``__name``_awready, \ + input logic m_axi_``__name``_arready, \ + input logic m_axi_``__name``_wready, \ + input logic m_axi_``__name``_bvalid, \ + input __id_t m_axi_``__name``_bid, \ + input axi_pkg::resp_t m_axi_``__name``_bresp, \ + input __b_user_t m_axi_``__name``_buser, \ + input logic m_axi_``__name``_rvalid, \ + input __id_t m_axi_``__name``_rid, \ + input __data_t m_axi_``__name``_rdata, \ + input axi_pkg::resp_t m_axi_``__name``_rresp, \ + input logic m_axi_``__name``_rlast, \ + input __r_user_t m_axi_``__name``_ruser, \ +//////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Macros creating flat AXI ports +// `AXI_S_PORT(__name, __addr_t, __data_t, __strb_t, __id_t, __aw_user_t, __w_user_t, __b_user_t, __ar_user_t, __r_user_t) +`define AXI_S_PORT(__name, __addr_t, __data_t, __strb_t, __id_t, __aw_user_t, __w_user_t, __b_user_t, __ar_user_t, __r_user_t) \ + input logic s_axi_``__name``_awvalid, \ + input __id_t s_axi_``__name``_awid, \ + input __addr_t s_axi_``__name``_awaddr, \ + input axi_pkg::len_t s_axi_``__name``_awlen, \ + input axi_pkg::size_t s_axi_``__name``_awsize, \ + input axi_pkg::burst_t s_axi_``__name``_awburst, \ + input logic s_axi_``__name``_awlock, \ + input axi_pkg::cache_t s_axi_``__name``_awcache, \ + input axi_pkg::prot_t s_axi_``__name``_awprot, \ + input axi_pkg::qos_t s_axi_``__name``_awqos, \ + input axi_pkg::region_t s_axi_``__name``_awregion, \ + input __aw_user_t s_axi_``__name``_awuser, \ + input logic s_axi_``__name``_wvalid, \ + input __data_t s_axi_``__name``_wdata, \ + input __strb_t s_axi_``__name``_wstrb, \ + input logic s_axi_``__name``_wlast, \ + input __w_user_t s_axi_``__name``_wuser, \ + input logic s_axi_``__name``_bready, \ + input logic s_axi_``__name``_arvalid, \ + input __id_t s_axi_``__name``_arid, \ + input __addr_t s_axi_``__name``_araddr, \ + input axi_pkg::len_t s_axi_``__name``_arlen, \ + input axi_pkg::size_t s_axi_``__name``_arsize, \ + input axi_pkg::burst_t s_axi_``__name``_arburst, \ + input logic s_axi_``__name``_arlock, \ + input axi_pkg::cache_t s_axi_``__name``_arcache, \ + input axi_pkg::prot_t s_axi_``__name``_arprot, \ + input axi_pkg::qos_t s_axi_``__name``_arqos, \ + input axi_pkg::region_t s_axi_``__name``_arregion, \ + input __ar_user_t s_axi_``__name``_aruser, \ + input logic s_axi_``__name``_rready, \ + output logic s_axi_``__name``_awready, \ + output logic s_axi_``__name``_arready, \ + output logic s_axi_``__name``_wready, \ + output logic s_axi_``__name``_bvalid, \ + output __id_t s_axi_``__name``_bid, \ + output axi_pkg::resp_t s_axi_``__name``_bresp, \ + output __b_user_t s_axi_``__name``_buser, \ + output logic s_axi_``__name``_rvalid, \ + output __id_t s_axi_``__name``_rid, \ + output __data_t s_axi_``__name``_rdata, \ + output axi_pkg::resp_t s_axi_``__name``_rresp, \ + output logic s_axi_``__name``_rlast, \ + output __r_user_t s_axi_``__name``_ruser, \ +//////////////////////////////////////////////////////////////////////////////////////////////////// + +`endif diff --git a/include/axi/typedef.svh b/include/axi/typedef.svh index a2a860e50..648c3fed7 100644 --- a/include/axi/typedef.svh +++ b/include/axi/typedef.svh @@ -11,6 +11,7 @@ // // Authors: // - Andreas Kurth +// - Thomas Benz // - Florian Zaruba // - Wolfgang Roenninger @@ -104,6 +105,28 @@ //////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// +// All AXI4+ATOP Channels and Request/Response Structs in One Macro - Custom Type Name Version +// +// This can be used whenever the user is not interested in "precise" control of the naming of the +// individual channels. +// +// Usage Example: +// `AXI_TYPEDEF_ALL_CT(axi, axi_req_t, axi_rsp_t, addr_t, id_t, data_t, strb_t, user_t) +// +// This defines `axi_req_t` and `axi_rsp_t` request/response structs as well as `axi_aw_chan_t`, +// `axi_w_chan_t`, `axi_b_chan_t`, `axi_ar_chan_t`, and `axi_r_chan_t` channel structs. +`define AXI_TYPEDEF_ALL_CT(__name, __req, __rsp, __addr_t, __id_t, __data_t, __strb_t, __user_t) \ + `AXI_TYPEDEF_AW_CHAN_T(__name``_aw_chan_t, __addr_t, __id_t, __user_t) \ + `AXI_TYPEDEF_W_CHAN_T(__name``_w_chan_t, __data_t, __strb_t, __user_t) \ + `AXI_TYPEDEF_B_CHAN_T(__name``_b_chan_t, __id_t, __user_t) \ + `AXI_TYPEDEF_AR_CHAN_T(__name``_ar_chan_t, __addr_t, __id_t, __user_t) \ + `AXI_TYPEDEF_R_CHAN_T(__name``_r_chan_t, __data_t, __id_t, __user_t) \ + `AXI_TYPEDEF_REQ_T(__req, __name``_aw_chan_t, __name``_w_chan_t, __name``_ar_chan_t) \ + `AXI_TYPEDEF_RESP_T(__rsp, __name``_b_chan_t, __name``_r_chan_t) +//////////////////////////////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////////////////////////////// // All AXI4+ATOP Channels and Request/Response Structs in One Macro // @@ -115,14 +138,8 @@ // // This defines `axi_req_t` and `axi_resp_t` request/response structs as well as `axi_aw_chan_t`, // `axi_w_chan_t`, `axi_b_chan_t`, `axi_ar_chan_t`, and `axi_r_chan_t` channel structs. -`define AXI_TYPEDEF_ALL(__name, __addr_t, __id_t, __data_t, __strb_t, __user_t) \ - `AXI_TYPEDEF_AW_CHAN_T(__name``_aw_chan_t, __addr_t, __id_t, __user_t) \ - `AXI_TYPEDEF_W_CHAN_T(__name``_w_chan_t, __data_t, __strb_t, __user_t) \ - `AXI_TYPEDEF_B_CHAN_T(__name``_b_chan_t, __id_t, __user_t) \ - `AXI_TYPEDEF_AR_CHAN_T(__name``_ar_chan_t, __addr_t, __id_t, __user_t) \ - `AXI_TYPEDEF_R_CHAN_T(__name``_r_chan_t, __data_t, __id_t, __user_t) \ - `AXI_TYPEDEF_REQ_T(__name``_req_t, __name``_aw_chan_t, __name``_w_chan_t, __name``_ar_chan_t) \ - `AXI_TYPEDEF_RESP_T(__name``_resp_t, __name``_b_chan_t, __name``_r_chan_t) +`define AXI_TYPEDEF_ALL(__name, __addr_t, __id_t, __data_t, __strb_t, __user_t) \ + `AXI_TYPEDEF_ALL_CT(__name, __name``_req_t, __name``_resp_t, __addr_t, __id_t, __data_t, __strb_t, __user_t) //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -185,6 +202,29 @@ //////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// +// All AXI4-Lite Channels and Request/Response Structs in One Macro - Custom Type Name Version +// +// This can be used whenever the user is not interested in "precise" control of the naming of the +// individual channels. +// +// Usage Example: +// `AXI_LITE_TYPEDEF_ALL_CT(axi_lite, axi_lite_req_t, axi_lite_rsp_t, addr_t, data_t, strb_t) +// +// This defines `axi_lite_req_t` and `axi_lite_resp_t` request/response structs as well as +// `axi_lite_aw_chan_t`, `axi_lite_w_chan_t`, `axi_lite_b_chan_t`, `axi_lite_ar_chan_t`, and +// `axi_lite_r_chan_t` channel structs. +`define AXI_LITE_TYPEDEF_ALL_CT(__name, __req, __rsp, __addr_t, __data_t, __strb_t) \ + `AXI_LITE_TYPEDEF_AW_CHAN_T(__name``_aw_chan_t, __addr_t) \ + `AXI_LITE_TYPEDEF_W_CHAN_T(__name``_w_chan_t, __data_t, __strb_t) \ + `AXI_LITE_TYPEDEF_B_CHAN_T(__name``_b_chan_t) \ + `AXI_LITE_TYPEDEF_AR_CHAN_T(__name``_ar_chan_t, __addr_t) \ + `AXI_LITE_TYPEDEF_R_CHAN_T(__name``_r_chan_t, __data_t) \ + `AXI_LITE_TYPEDEF_REQ_T(__req, __name``_aw_chan_t, __name``_w_chan_t, __name``_ar_chan_t) \ + `AXI_LITE_TYPEDEF_RESP_T(__rsp, __name``_b_chan_t, __name``_r_chan_t) +//////////////////////////////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////////////////////////////// // All AXI4-Lite Channels and Request/Response Structs in One Macro // @@ -197,14 +237,8 @@ // This defines `axi_lite_req_t` and `axi_lite_resp_t` request/response structs as well as // `axi_lite_aw_chan_t`, `axi_lite_w_chan_t`, `axi_lite_b_chan_t`, `axi_lite_ar_chan_t`, and // `axi_lite_r_chan_t` channel structs. -`define AXI_LITE_TYPEDEF_ALL(__name, __addr_t, __data_t, __strb_t) \ - `AXI_LITE_TYPEDEF_AW_CHAN_T(__name``_aw_chan_t, __addr_t) \ - `AXI_LITE_TYPEDEF_W_CHAN_T(__name``_w_chan_t, __data_t, __strb_t) \ - `AXI_LITE_TYPEDEF_B_CHAN_T(__name``_b_chan_t) \ - `AXI_LITE_TYPEDEF_AR_CHAN_T(__name``_ar_chan_t, __addr_t) \ - `AXI_LITE_TYPEDEF_R_CHAN_T(__name``_r_chan_t, __data_t) \ - `AXI_LITE_TYPEDEF_REQ_T(__name``_req_t, __name``_aw_chan_t, __name``_w_chan_t, __name``_ar_chan_t) \ - `AXI_LITE_TYPEDEF_RESP_T(__name``_resp_t, __name``_b_chan_t, __name``_r_chan_t) +`define AXI_LITE_TYPEDEF_ALL(__name, __addr_t, __data_t, __strb_t) \ + `AXI_LITE_TYPEDEF_ALL_CT(__name, __name``_req_t, __name``_resp_t, __addr_t, __data_t, __strb_t) //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/ips_list.yml b/ips_list.yml index c8afeda72..7d797dea5 100644 --- a/ips_list.yml +++ b/ips_list.yml @@ -1,7 +1,11 @@ common_cells: - commit: v1.21.0 + commit: v1.31.1 group: pulp-platform common_verification: - commit: v0.2.0 + commit: v0.2.3 + group: pulp-platform + +tech_cells_generic: + commit: v0.2.2 group: pulp-platform diff --git a/scripts/axi_dumper_interpret.py b/scripts/axi_dumper_interpret.py new file mode 100644 index 000000000..59541741e --- /dev/null +++ b/scripts/axi_dumper_interpret.py @@ -0,0 +1,264 @@ +# Copyright (c) 2020 ETH Zurich, University of Bologna +# All rights reserved. +# +# Copyright and related rights are licensed under the Solderpad Hardware +# License, Version 0.51 (the "License"); you may not use this file except in +# compliance with the License. You may obtain a copy of the License at +# http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +# or agreed to in writing, software, hardware and materials distributed under +# this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. + +# Interpretation script for axi_dumper log files. +# This script can be used to help with finding issues in the AXI transactions along a BUS. + +import ast +import argparse + + +# Expand strobe to bits from bytes +def expand_strb(strb, num_bytes): + output = 0 + for i in range(num_bytes): + if strb & 1 << i: + output |= 0xFF << (8 * i) + return output + + +# Split burst into individual transactions +def burst_splitter(stat_list): + new_list = [] + data_sum = 0 + for stat in stat_list: + data_sum += len(stat['data']) + for i in range(stat['len'] + 1): + newstat = { 'start' : stat['start'], + 'end' : stat['end'], + 'addr' : stat['addr'] + (2**stat['size'])*i, + 'data' : stat['data'][i], + } + if 'strb' in stat.keys(): + newstat['strb'] = stat['strb'][i] + new_list.append(newstat) + return new_list + + +# Recombine subsequent identical requests with different strobes +def recombine_identical(stat_list, num_bytes): + new_list = [] + for stat in stat_list: + if len(new_list) > 0 and new_list[-1]['addr'] == stat['addr']: + if 'strb' in stat.keys(): + if ~(new_list[-1]['strb'] & stat['strb']): + new_list[-1]['data'] = (new_list[-1]['data'] & expand_strb(new_list[-1]['strb'], num_bytes)) | (stat['data'] & expand_strb(stat['strb'], num_bytes)) + new_list[-1]['strb'] = new_list[-1]['strb'] | stat['strb'] + else: + new_list.append(stat) + else: + continue + else: + new_list.append(stat) + # TODO: shrink also based on size? + return new_list + + +# Validate all reads based on AR, and R +def validate_read(ar_list, r_list): + stat_list = [] + + r_index_list = {} + for ar_index in range(len(ar_list)): + ar = ar_list[ar_index] + stat = { 'start' : 0, + 'end' : 0, + 'addr' : 0, + 'len' : 0, + 'size' : 0, + 'data' : [], + } + stat['start'] = ar['time'] + stat['len'] = ar['len'] + stat['addr'] = ar['addr'] + stat['size'] = ar['size'] + stat['data'] = [] + + ar_id = ar['id'] + if ar_id in r_index_list: + r_index = r_index_list[ar_id] + else: + r_index = 0 + for i in range(stat['len'] + 1): + while r_list[r_index]['id'] != ar_id: + r_index += 1 + if (r_index >= len(r_list)): + print("No R remaining for {}".format(ar)) + print("Current state: {}".format(stat)) + print("{} AR remaining".format(len(ar_list)-ar_index-1)) + return stat_list + stat['data'].append(r_list[r_index]['data']) + if (r_list[r_index]['last'] and (i != stat['len'])) or \ + ((i == stat['len']) and (not r_list[r_index]['last'])): + print("R last and length mismatch for {}".format(ar)) + print("Current state: ".format(stat)) + print("ARs") + for j in range(-2, 3): + print(ar_list[ar_index + j]) + print("Rs") + for j in range(-19, 20): + print(r_list[r_index + j]) + return stat_list + if (r_list[r_index]['last'] and (i == stat['len'])): + stat['end'] = r_list[r_index]['time'] + r_index += 1 + if (r_index >= len(r_list)): + print("No R remaining for {}".format(ar)) + print("Current state: {}".format(stat)) + print("{} AR remaining".format(len(ar_list)-ar_index-1)) + return stat_list + stat_list.append(stat) + r_index_list[ar_id] = r_index + return stat_list + + +# Validate all writes based on AW, W, and B +def validate_write(aw_list, w_list, b_list): + stat_list = [] + + w_index = 0 # W are always in order of AW as no ID available + b_index_list = {} # To sort for ID + for aw_index in range(len(aw_list)): + aw = aw_list[aw_index] + stat = { 'start' : 0, + 'end' : 0, + 'addr' : 0, + 'len' : 0, + 'size' : 0, + 'data' : [], + 'strb' : [], + } + stat['start'] = aw['time'] + stat['len'] = aw['len'] + stat['addr'] = aw['addr'] + stat['size'] = aw['size'] + stat['data'] = [] + aw_id = aw['id'] + if aw_id in b_index_list: + b_index = b_index_list[aw_id] + else: + b_index = 0 + for i in range(stat['len'] + 1): + if (w_index >= len(w_list)): + print("No W remaining for {}".format(aw)) + print("Current state: {}".format(stat)) + print("{} AW remaining".format(len(aw_list)-aw_index-1)) + return stat_list + stat['data'].append(w_list[w_index]['data']) + stat['strb'].append(w_list[w_index]['strb']) + if (w_list[w_index]['last'] and (i != stat['len'])) or \ + ((i == stat['len']) and (not w_list[w_index]['last'])): + print("W last and length mismatch for {}".format(aw)) + print("Current state: {}".format(stat)) + return stat_list + if w_list[w_index]['last'] and (i == stat['len']): + while b_list[b_index]['id'] != aw_id: + b_index += 1 + if (b_index >= len(b_list)): + print("No B remaining for {}".format(aw)) + print("Current state: {}".format(stat)) + print("{} AW remaining".format(len(aw_list)-aw_index-1)) + return stat_list + stat['end'] = b_list[b_index]['time'] + b_index += 1 + w_index += 1 + b_index_list[aw_id] = b_index + stat_list.append(stat) + return stat_list + + +# funtion to trace file +def trace_file(filename, num_bytes): + axi_dict = { 'type' : '', + 'time' : 0, + 'id' : 0, + 'addr' : 0, + 'len' : 0, + 'size' : 0, + 'burst' : 0, + 'lock' : 0, + 'cache' : 0, + 'prot' : 0, + 'qos' : 0, + 'region' : 0, + 'atop' : 0, + 'user' : 0, + 'data' : 0, + 'strb' : 0, + 'last' : 0, + 'resp' : 0, + } + + aw_list = [] + ar_list = [] + w_list = [] + r_list = [] + b_list = [] + + aw_hex = 0x4157 # AW in ASCII + ar_hex = 0x4152 # AR in ASCII + w_hex = 0x57 # W in ASCII + r_hex = 0x52 # R in ASCII + b_hex = 0x42 # B in ASCII + + with open(filename, 'r') as trace_file: + i = 0 + for line in trace_file: + i += 1 + try: + trace_dict = ast.literal_eval(line) + except: + print("dict parsing failed") + break + + if trace_dict["type"] == aw_hex or trace_dict["type"] == "AW": + aw_list.append(trace_dict) + elif trace_dict["type"] == ar_hex or trace_dict["type"] == "AR": + ar_list.append(trace_dict) + elif trace_dict["type"] == w_hex or trace_dict["type"] == "W": + w_list.append(trace_dict) + elif trace_dict["type"] == r_hex or trace_dict["type"] == "R": + r_list.append(trace_dict) + elif trace_dict["type"] == b_hex or trace_dict["type"] == "B": + b_list.append(trace_dict) + else: + print("type ERROR") + break + # if (i > 4000000): break + # print(trace_dict) + # TODO: split reads per ID + read_stats = validate_read(ar_list, r_list) + write_stats = validate_write(aw_list, w_list, b_list) + split_reads = burst_splitter(read_stats) + split_writes = burst_splitter(write_stats) + recomb_reads = recombine_identical(split_reads, num_bytes) + recomb_writes = recombine_identical(split_writes, num_bytes) + print("Successfully processed:") + print("\treads: {} ARs, {} Rs, {} shrunk accesses".format(len(read_stats), len(split_reads), len(recomb_reads))) + print("\twrites: {} AWs, {} Ws, {} shrunk accesses".format(len(write_stats), len(split_writes), len(recomb_writes))) + print("Total in list:") + print("\treads: {} ARs, {} Rs".format(len(ar_list), len(r_list))) + print("\twrites: {} AWs, {} Ws, {} Bs".format(len(aw_list), len(w_list), len(b_list))) + return (recomb_reads, recomb_writes) + + +if __name__ == '__main__': + + parser = argparse.ArgumentParser(description='Analyze an axi trace.') + parser.add_argument('axi_trace_log', metavar='axi_trace_log', type=str, + help='the raw log file emitted by axi dumper.') + parser.add_argument('num_bytes', metavar='num_bytes', type=int, default=8, + help='number of bytes in a data word') + + args = parser.parse_args() + + trace_file(args.axi_trace_log, args.num_bytes) diff --git a/scripts/compile_vsim.sh b/scripts/compile_vsim.sh index 9ed8b84d1..7109858f9 100755 --- a/scripts/compile_vsim.sh +++ b/scripts/compile_vsim.sh @@ -18,7 +18,7 @@ set -e [ ! -z "$VSIM" ] || VSIM=vsim -bender script vsim -t test \ +bender script vsim -t test -t rtl \ --vlog-arg="-svinputport=compat" \ --vlog-arg="-override_timescale 1ns/1ps" \ --vlog-arg="-suppress 2583" \ diff --git a/scripts/run_verilator.sh b/scripts/run_verilator.sh index a65cd091a..dd9be3c0b 100755 --- a/scripts/run_verilator.sh +++ b/scripts/run_verilator.sh @@ -9,6 +9,12 @@ # this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR # CONDITIONS OF ANY KIND, either express or implied. See the License for the # specific language governing permissions and limitations under the License. +# +# Authors: +# - Fabian Schuiki +# - Florian Zaruba +# - Andreas Kurth +# - Thomas Benz set -e ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) diff --git a/scripts/run_vsim.sh b/scripts/run_vsim.sh index c72be8e8d..9812c5ed5 100755 --- a/scripts/run_vsim.sh +++ b/scripts/run_vsim.sh @@ -13,7 +13,7 @@ # Authors: # - Andreas Kurth # - Fabian Schuiki -# - Matheus Cavalcante +# - Wolfgang Roenninger set -euo pipefail ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) @@ -72,6 +72,14 @@ exec_test() { done done ;; + axi_fifo) + for DEPTH in 0 1 16; do + for FALL_THROUGH in 0 1; do + call_vsim tb_axi_fifo -gDepth=$DEPTH \ + -gFallThrough=$FALL_THROUGH + done + done + ;; axi_iw_converter) for SLV_PORT_IW in 1 2 3 4 8; do MAX_SLV_PORT_IDS=$((2**SLV_PORT_IW)) @@ -165,7 +173,7 @@ exec_test() { for Atop in 0 1; do for Exclusive in 0 1; do for UniqueIds in 0 1; do - call_vsim tb_axi_xbar -gTbNumMst=$NumMst -gTbNumSlv=$NumSlv \ + call_vsim tb_axi_xbar -gTbNumMasters=$NumMst -gTbNumSlaves=$NumSlv \ -gTbEnAtop=$Atop -gTbEnExcl=$Exclusive \ -gTbUniqueIds=$UniqueIds done @@ -174,6 +182,57 @@ exec_test() { done done ;; + axi_to_mem_banked) + for MEM_LAT in 1 2; do + for BANK_FACTOR in 1 2; do + for NUM_BANKS in 1 2 ; do + for AXI_DATA_WIDTH in 64 256 ; do + ACT_BANKS=$((2*$BANK_FACTOR*$NUM_BANKS)) + MEM_DATA_WIDTH=$(($AXI_DATA_WIDTH/$NUM_BANKS)) + call_vsim tb_axi_to_mem_banked \ + -voptargs="+acc +cover=bcesfx" \ + -gTbAxiDataWidth=$AXI_DATA_WIDTH \ + -gTbNumWords=2048 \ + -gTbNumBanks=$ACT_BANKS \ + -gTbMemDataWidth=$MEM_DATA_WIDTH \ + -gTbMemLatency=$MEM_LAT \ + -gTbNumWrites=2000 \ + -gTbNumReads=2000 + done + done + done + done + ;; + axi_xbar) + for GEN_ATOP in 0 1; do + for NUM_MST in 1 6; do + for NUM_SLV in 2 9; do + for MST_ID_USE in 3 5; do + MST_ID=5 + for DATA_WIDTH in 64 256; do + for PIPE in 0 1; do + call_vsim tb_axi_xbar -t 1ns -voptargs="+acc" \ + -gTbNumMasters=$NUM_MST \ + -gTbNumSlaves=$NUM_SLV \ + -gTbAxiIdWidthMasters=$MST_ID \ + -gTbAxiIdUsed=$MST_ID_USE \ + -gTbAxiDataWidth=$DATA_WIDTH \ + -gTbPipeline=$PIPE \ + -gTbEnAtop=$GEN_ATOP + done + done + done + done + done + done + ;; + axi_lite_dw_converter) + for DWSLV in 32 64 128; do + for DWMST in 16 32 64; do + call_vsim tb_axi_lite_dw_converter -gTbAxiDataWidthSlv=$DWSLV -gTbAxiDataWidthMst=$DWMST + done + done + ;; *) call_vsim tb_$1 -t 1ns -coverage -voptargs="+acc +cover=bcesfx" ;; diff --git a/scripts/update_authors b/scripts/update_authors index 30ca502aa..79fcc24eb 100755 --- a/scripts/update_authors +++ b/scripts/update_authors @@ -19,12 +19,21 @@ declare -A hide=( \ # Map each author name to an email address. declare -A emails=( \ ["Andreas Kurth"]="akurth@iis.ee.ethz.ch" \ + ["Cyril Koenig"]="cykoenig@iis.ee.ethz.ch" \ ["Fabian Schuiki"]="fschuiki@iis.ee.ethz.ch" \ ["Florian Zaruba"]="zarubaf@iis.ee.ethz.ch" \ ["Matheus Cavalcante"]="matheusd@iis.ee.ethz.ch" \ ["Samuel Riedel"]="sriedel@iis.ee.ethz.ch" \ ["Stefan Mach"]="smach@iis.ee.ethz.ch" \ ["Wolfgang Rönninger"]="wroennin@iis.ee.ethz.ch" \ + ["Thomas Benz"]="tbenz@iis.ee.ethz.ch" \ + ["Michael Rogenmoser"]="michaero@iis.ee.ethz.ch" \ + ["Luca Valente"]="luca.valente@unibo.it" \ + ["Noah Huetter"]="huettern@ethz.ch" \ + ["Nils Wistoff"]="nwistoff@iis.ee.ethz.ch" \ + ["Nicole Narr"]="narrn@ethz.ch" \ + ["Paul Scheffler"]="paulsc@iis.ee.ethz.ch" \ + ["Tim Fischer"]="fischeti@iis.ee.ethz.ch" \ ) # Iterate over source files (see `done` line for which files are included). diff --git a/src/axi_burst_splitter.sv b/src/axi_burst_splitter.sv index 3ff13ed26..2e2ebf682 100644 --- a/src/axi_burst_splitter.sv +++ b/src/axi_burst_splitter.sv @@ -30,6 +30,8 @@ module axi_burst_splitter #( parameter int unsigned MaxReadTxns = 32'd0, // Maximum number of AXI write bursts outstanding at the same time parameter int unsigned MaxWriteTxns = 32'd0, + // Internal ID queue can work in two bandwidth modes: see id_queue.sv for details + parameter bit FullBW = 0, // AXI Bus Types parameter int unsigned AddrWidth = 32'd0, parameter int unsigned DataWidth = 32'd0, @@ -78,7 +80,6 @@ module axi_burst_splitter #( .NoMstPorts ( 2 ), .MaxTrans ( MaxTxns ), .AxiLookBits ( IdWidth ), - .FallThrough ( 1'b1 ), .SpillAw ( 1'b0 ), .SpillW ( 1'b0 ), .SpillB ( 1'b0 ), @@ -140,7 +141,8 @@ module axi_burst_splitter #( axi_burst_splitter_ax_chan #( .chan_t ( aw_chan_t ), .IdWidth ( IdWidth ), - .MaxTxns ( MaxWriteTxns ) + .MaxTxns ( MaxWriteTxns ), + .FullBW ( FullBW ) ) i_axi_burst_splitter_aw_chan ( .clk_i, .rst_ni, @@ -234,7 +236,8 @@ module axi_burst_splitter #( axi_burst_splitter_ax_chan #( .chan_t ( ar_chan_t ), .IdWidth ( IdWidth ), - .MaxTxns ( MaxReadTxns ) + .MaxTxns ( MaxReadTxns ), + .FullBW ( FullBW ) ) i_axi_burst_splitter_ar_chan ( .clk_i, .rst_ni, @@ -349,6 +352,7 @@ module axi_burst_splitter_ax_chan #( parameter type chan_t = logic, parameter int unsigned IdWidth = 0, parameter int unsigned MaxTxns = 0, + parameter bit FullBW = 0, parameter type id_t = logic[IdWidth-1:0] ) ( input logic clk_i, @@ -374,6 +378,7 @@ module axi_burst_splitter_ax_chan #( logic cnt_alloc_req, cnt_alloc_gnt; axi_burst_splitter_counters #( .MaxTxns ( MaxTxns ), + .FullBW ( FullBW ), .IdWidth ( IdWidth ) ) i_axi_burst_splitter_counters ( .clk_i, @@ -462,6 +467,7 @@ endmodule /// Internal module of [`axi_burst_splitter`](module.axi_burst_splitter) to order transactions. module axi_burst_splitter_counters #( parameter int unsigned MaxTxns = 0, + parameter bit FullBW = 0, parameter int unsigned IdWidth = 0, parameter type id_t = logic [IdWidth-1:0] ) ( @@ -520,7 +526,8 @@ module axi_burst_splitter_counters #( id_queue #( .ID_WIDTH ( $bits(id_t) ), .CAPACITY ( MaxTxns ), - .data_t ( cnt_idx_t ) + .data_t ( cnt_idx_t ), + .FULL_BW ( FullBW ) ) i_idq ( .clk_i, .rst_ni, diff --git a/src/axi_bus_compare.sv b/src/axi_bus_compare.sv new file mode 100644 index 000000000..b92e6159f --- /dev/null +++ b/src/axi_bus_compare.sv @@ -0,0 +1,585 @@ +// Copyright (c) 2019-2020 ETH Zurich, University of Bologna +// +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the "License"); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. +// +// Authors: +// - Thomas Benz + +`include "axi/assign.svh" +/// Synthesizable test module comparing two AXI channels of the same type +/// This module is meant to be used in FPGA-based verification. +module axi_bus_compare #( + /// ID width of the AXI4+ATOP interface + parameter int unsigned AxiIdWidth = 32'd0, + /// FIFO depth + parameter int unsigned FifoDepth = 32'd0, + /// AW channel type of the AXI4+ATOP interface + parameter type axi_aw_chan_t = logic, + /// W channel type of the AXI4+ATOP interface + parameter type axi_w_chan_t = logic, + /// B channel type of the AXI4+ATOP interface + parameter type axi_b_chan_t = logic, + /// AR channel type of the AXI4+ATOP interface + parameter type axi_ar_chan_t = logic, + /// R channel type of the AXI4+ATOP interface + parameter type axi_r_chan_t = logic, + /// Request struct type of the AXI4+ATOP slave port + parameter type axi_req_t = logic, + /// Response struct type of the AXI4+ATOP slave port + parameter type axi_rsp_t = logic, + /// ID type (*do not overwrite*) + parameter type id_t = logic [2**AxiIdWidth-1:0] +)( + /// Clock + input logic clk_i, + /// Asynchronous reset, active low + input logic rst_ni, + /// Testmode + input logic testmode_i, + /// AXI4+ATOP A channel request in + input axi_req_t axi_a_req_i, + /// AXI4+ATOP A channel response out + output axi_rsp_t axi_a_rsp_o, + /// AXI4+ATOP A channel request out + output axi_req_t axi_a_req_o, + /// AXI4+ATOP A channel response in + input axi_rsp_t axi_a_rsp_i, + /// AXI4+ATOP B channel request in + input axi_req_t axi_b_req_i, + /// AXI4+ATOP B channel response out + output axi_rsp_t axi_b_rsp_o, + /// AXI4+ATOP B channel request out + output axi_req_t axi_b_req_o, + /// AXI4+ATOP B channel response in + input axi_rsp_t axi_b_rsp_i, + /// AW mismatch + output id_t aw_mismatch_o, + /// W mismatch + output logic w_mismatch_o, + /// B mismatch + output id_t b_mismatch_o, + /// AR mismatch + output id_t ar_mismatch_o, + /// R mismatch + output id_t r_mismatch_o, + /// General mismatch + output logic mismatch_o, + /// Unit is busy + output logic busy_o +); + + + //----------------------------------- + // Channel Signals + //----------------------------------- + // assign request payload A + + `AXI_ASSIGN_AW_STRUCT(axi_a_req_o.aw, axi_a_req_i.aw) + `AXI_ASSIGN_W_STRUCT(axi_a_req_o.w, axi_a_req_i.w) + `AXI_ASSIGN_AR_STRUCT(axi_a_req_o.ar, axi_a_req_i.ar) + + // assign response payload A + `AXI_ASSIGN_R_STRUCT(axi_a_rsp_o.r, axi_a_rsp_i.r) + `AXI_ASSIGN_B_STRUCT(axi_a_rsp_o.b, axi_a_rsp_i.b) + + // assign request payload B + `AXI_ASSIGN_AW_STRUCT(axi_b_req_o.aw, axi_b_req_i.aw) + `AXI_ASSIGN_W_STRUCT(axi_b_req_o.w, axi_b_req_i.w) + `AXI_ASSIGN_AR_STRUCT(axi_b_req_o.ar, axi_b_req_i.ar) + + // assign response payload B + `AXI_ASSIGN_R_STRUCT(axi_b_rsp_o.r, axi_b_rsp_i.r) + `AXI_ASSIGN_B_STRUCT(axi_b_rsp_o.b, axi_b_rsp_i.b) + + // fifo handshaking signals A + id_t fifo_valid_aw_a, fifo_ready_aw_a; + id_t fifo_valid_b_a, fifo_ready_b_a; + id_t fifo_valid_ar_a, fifo_ready_ar_a; + id_t fifo_valid_r_a, fifo_ready_r_a; + + logic fifo_sel_valid_aw_a, fifo_sel_ready_aw_a; + logic fifo_sel_valid_w_a, fifo_sel_ready_w_a; + logic fifo_sel_valid_b_a, fifo_sel_ready_b_a; + logic fifo_sel_valid_ar_a, fifo_sel_ready_ar_a; + logic fifo_sel_valid_r_a, fifo_sel_ready_r_a; + + // fifo handshaking signals B + id_t fifo_valid_aw_b, fifo_ready_aw_b; + id_t fifo_valid_b_b, fifo_ready_b_b; + id_t fifo_valid_ar_b, fifo_ready_ar_b; + id_t fifo_valid_r_b, fifo_ready_r_b; + + logic fifo_sel_valid_aw_b, fifo_sel_ready_aw_b; + logic fifo_sel_valid_w_b, fifo_sel_ready_w_b; + logic fifo_sel_valid_b_b, fifo_sel_ready_b_b; + logic fifo_sel_valid_ar_b, fifo_sel_ready_ar_b; + logic fifo_sel_valid_r_b, fifo_sel_ready_r_b; + + + //----------------------------------- + // FIFO Output Signals + //----------------------------------- + id_t fifo_cmp_valid_aw_a; + logic fifo_cmp_valid_w_a; + id_t fifo_cmp_valid_b_a; + id_t fifo_cmp_valid_ar_a; + id_t fifo_cmp_valid_r_a; + + id_t fifo_cmp_valid_aw_b; + logic fifo_cmp_valid_w_b; + id_t fifo_cmp_valid_b_b; + id_t fifo_cmp_valid_ar_b; + id_t fifo_cmp_valid_r_b; + + axi_aw_chan_t [2**AxiIdWidth-1:0] fifo_cmp_data_aw_a; + axi_w_chan_t fifo_cmp_data_w_a; + axi_b_chan_t [2**AxiIdWidth-1:0] fifo_cmp_data_b_a; + axi_ar_chan_t [2**AxiIdWidth-1:0] fifo_cmp_data_ar_a; + axi_r_chan_t [2**AxiIdWidth-1:0] fifo_cmp_data_r_a; + + axi_aw_chan_t [2**AxiIdWidth-1:0] fifo_cmp_data_aw_b; + axi_w_chan_t fifo_cmp_data_w_b; + axi_b_chan_t [2**AxiIdWidth-1:0] fifo_cmp_data_b_b; + axi_ar_chan_t [2**AxiIdWidth-1:0] fifo_cmp_data_ar_b; + axi_r_chan_t [2**AxiIdWidth-1:0] fifo_cmp_data_r_b; + + + //----------------------------------- + // Channel A stream forks + //----------------------------------- + stream_fork #( + .N_OUP ( 32'd2 ) + ) i_stream_fork_aw_a ( + .clk_i, + .rst_ni, + .valid_i ( axi_a_req_i.aw_valid ), + .ready_o ( axi_a_rsp_o.aw_ready ), + .valid_o ( {fifo_sel_valid_aw_a, axi_a_req_o.aw_valid} ), + .ready_i ( {fifo_sel_ready_aw_a, axi_a_rsp_i.aw_ready} ) + ); + + stream_fork #( + .N_OUP ( 32'd2 ) + ) i_stream_fork_w_a ( + .clk_i, + .rst_ni, + .valid_i ( axi_a_req_i.w_valid ), + .ready_o ( axi_a_rsp_o.w_ready ), + .valid_o ( {fifo_sel_valid_w_a, axi_a_req_o.w_valid} ), + .ready_i ( {fifo_sel_ready_w_a, axi_a_rsp_i.w_ready} ) + ); + + stream_fork #( + .N_OUP ( 32'd2 ) + ) i_stream_fork_b_a ( + .clk_i, + .rst_ni, + .valid_i ( axi_a_rsp_i.b_valid ), + .ready_o ( axi_a_req_o.b_ready ), + .valid_o ( {fifo_sel_valid_b_a, axi_a_rsp_o.b_valid} ), + .ready_i ( {fifo_sel_ready_b_a, axi_a_req_i.b_ready} ) + ); + + stream_fork #( + .N_OUP ( 32'd2 ) + ) i_stream_fork_ar_a ( + .clk_i, + .rst_ni, + .valid_i ( axi_a_req_i.ar_valid ), + .ready_o ( axi_a_rsp_o.ar_ready ), + .valid_o ( {fifo_sel_valid_ar_a, axi_a_req_o.ar_valid} ), + .ready_i ( {fifo_sel_ready_ar_a, axi_a_rsp_i.ar_ready} ) + ); + + stream_fork #( + .N_OUP ( 32'd2 ) + ) i_stream_fork_r_a ( + .clk_i, + .rst_ni, + .valid_i ( axi_a_rsp_i.r_valid ), + .ready_o ( axi_a_req_o.r_ready ), + .valid_o ( {fifo_sel_valid_r_a, axi_a_rsp_o.r_valid} ), + .ready_i ( {fifo_sel_ready_r_a, axi_a_req_i.r_ready} ) + ); + + + //----------------------------------- + // Channel A FIFOs + //----------------------------------- + for (genvar id = 0; id < 2**AxiIdWidth; id++) begin : gen_fifos_a + + stream_fifo #( + .FALL_THROUGH ( 1'b0 ), + .DATA_WIDTH ( 1'b0 ), + .DEPTH ( FifoDepth ), + .T ( axi_aw_chan_t ) + ) i_stream_fifo_aw_a ( + .clk_i, + .rst_ni, + .testmode_i, + .flush_i ( 1'b0 ), + .usage_o ( /* NOT CONNECTED */ ), + .data_i ( axi_a_req_i.aw ), + .valid_i ( fifo_valid_aw_a [id] ), + .ready_o ( fifo_ready_aw_a [id] ), + .data_o ( fifo_cmp_data_aw_a [id] ), + .valid_o ( fifo_cmp_valid_aw_a [id] ), + .ready_i ( fifo_cmp_valid_aw_a [id] & fifo_cmp_valid_aw_b [id] ) + ); + + stream_fifo #( + .FALL_THROUGH ( 1'b0 ), + .DATA_WIDTH ( 1'b0 ), + .DEPTH ( FifoDepth ), + .T ( axi_b_chan_t ) + ) i_stream_fifo_b_a ( + .clk_i, + .rst_ni, + .testmode_i, + .flush_i ( 1'b0 ), + .usage_o ( /* NOT CONNECTED */ ), + .data_i ( axi_a_rsp_i.b ), + .valid_i ( fifo_valid_b_a [id] ), + .ready_o ( fifo_ready_b_a [id] ), + .data_o ( fifo_cmp_data_b_a [id] ), + .valid_o ( fifo_cmp_valid_b_a [id] ), + .ready_i ( fifo_cmp_valid_b_a [id] & fifo_cmp_valid_b_b [id] ) + ); + + stream_fifo #( + .FALL_THROUGH ( 1'b0 ), + .DATA_WIDTH ( 1'b0 ), + .DEPTH ( FifoDepth ), + .T ( axi_ar_chan_t ) + ) i_stream_fifo_ar_a ( + .clk_i, + .rst_ni, + .testmode_i, + .flush_i ( 1'b0 ), + .usage_o ( /* NOT CONNECTED */ ), + .data_i ( axi_a_req_i.ar ), + .valid_i ( fifo_valid_ar_a [id] ), + .ready_o ( fifo_ready_ar_a [id] ), + .data_o ( fifo_cmp_data_ar_a [id] ), + .valid_o ( fifo_cmp_valid_ar_a [id] ), + .ready_i ( fifo_cmp_valid_ar_a [id] & fifo_cmp_valid_ar_b [id] ) + ); + + stream_fifo #( + .FALL_THROUGH ( 1'b0 ), + .DATA_WIDTH ( 1'b0 ), + .DEPTH ( FifoDepth ), + .T ( axi_r_chan_t ) + ) i_stream_fifo_r_a ( + .clk_i, + .rst_ni, + .testmode_i, + .flush_i ( 1'b0 ), + .usage_o ( /* NOT CONNECTED */ ), + .data_i ( axi_a_rsp_i.r ), + .valid_i ( fifo_valid_r_a [id] ), + .ready_o ( fifo_ready_r_a [id] ), + .data_o ( fifo_cmp_data_r_a [id] ), + .valid_o ( fifo_cmp_valid_r_a [id] ), + .ready_i ( fifo_cmp_valid_r_a [id] & fifo_cmp_valid_r_b [id] ) + ); + end + + stream_fifo #( + .FALL_THROUGH ( 1'b0 ), + .DATA_WIDTH ( 1'b0 ), + .DEPTH ( FifoDepth ), + .T ( axi_w_chan_t ) + ) i_stream_fifo_w_a ( + .clk_i, + .rst_ni, + .testmode_i, + .flush_i ( 1'b0 ), + .usage_o ( /* NOT CONNECTED */ ), + .data_i ( axi_a_req_i.w ), + .valid_i ( fifo_sel_valid_w_a ), + .ready_o ( fifo_sel_ready_w_a ), + .data_o ( fifo_cmp_data_w_a ), + .valid_o ( fifo_cmp_valid_w_a ), + .ready_i ( fifo_cmp_valid_w_a & fifo_cmp_valid_w_b ) + ); + + + //----------------------------------- + // Input Handshaking A + //----------------------------------- + always_comb begin : gen_handshaking_a + // aw + // defaults + fifo_valid_aw_a = '0; + fifo_sel_ready_aw_a = '0; + // assign according id + fifo_valid_aw_a [axi_a_req_i.aw.id] = fifo_sel_valid_aw_a; + fifo_sel_ready_aw_a = fifo_ready_aw_a[axi_a_req_i.aw.id]; + + + // b + // defaults + fifo_valid_b_a = '0; + fifo_sel_ready_b_a = '0; + // assign according id + fifo_valid_b_a [axi_a_rsp_i.b.id] = fifo_sel_valid_b_a; + fifo_sel_ready_b_a = fifo_ready_b_a[axi_a_rsp_i.b.id]; + + // ar + // defaults + fifo_valid_ar_a = '0; + fifo_sel_ready_ar_a = '0; + // assign according id + fifo_valid_ar_a [axi_a_req_i.ar.id] = fifo_sel_valid_ar_a; + fifo_sel_ready_ar_a = fifo_ready_ar_a[axi_a_req_i.ar.id]; + + // b + // defaults + fifo_valid_r_a = '0; + fifo_sel_ready_r_a = '0; + // assign according id + fifo_valid_r_a [axi_a_rsp_i.r.id] = fifo_sel_valid_r_a; + fifo_sel_ready_r_a = fifo_ready_r_a[axi_a_rsp_i.r.id]; + end + + + //----------------------------------- + // Channel B stream forks + //----------------------------------- + stream_fork #( + .N_OUP ( 32'd2 ) + ) i_stream_fork_aw_b ( + .clk_i, + .rst_ni, + .valid_i ( axi_b_req_i.aw_valid ), + .ready_o ( axi_b_rsp_o.aw_ready ), + .valid_o ( {fifo_sel_valid_aw_b, axi_b_req_o.aw_valid} ), + .ready_i ( {fifo_sel_ready_aw_b, axi_b_rsp_i.aw_ready} ) + ); + + stream_fork #( + .N_OUP ( 32'd2 ) + ) i_stream_fork_w_b ( + .clk_i, + .rst_ni, + .valid_i ( axi_b_req_i.w_valid ), + .ready_o ( axi_b_rsp_o.w_ready ), + .valid_o ( {fifo_sel_valid_w_b, axi_b_req_o.w_valid} ), + .ready_i ( {fifo_sel_ready_w_b, axi_b_rsp_i.w_ready} ) + ); + + stream_fork #( + .N_OUP ( 32'd2 ) + ) i_stream_fork_b_b ( + .clk_i, + .rst_ni, + .valid_i ( axi_b_rsp_i.b_valid ), + .ready_o ( axi_b_req_o.b_ready ), + .valid_o ( {fifo_sel_valid_b_b, axi_b_rsp_o.b_valid} ), + .ready_i ( {fifo_sel_ready_b_b, axi_b_req_i.b_ready} ) + ); + + stream_fork #( + .N_OUP ( 32'd2 ) + ) i_stream_fork_ar_b ( + .clk_i, + .rst_ni, + .valid_i ( axi_b_req_i.ar_valid ), + .ready_o ( axi_b_rsp_o.ar_ready ), + .valid_o ( {fifo_sel_valid_ar_b, axi_b_req_o.ar_valid} ), + .ready_i ( {fifo_sel_ready_ar_b, axi_b_rsp_i.ar_ready} ) + ); + + stream_fork #( + .N_OUP ( 32'd2 ) + ) i_stream_fork_r_b ( + .clk_i, + .rst_ni, + .valid_i ( axi_b_rsp_i.r_valid ), + .ready_o ( axi_b_req_o.r_ready ), + .valid_o ( {fifo_sel_valid_r_b, axi_b_rsp_o.r_valid} ), + .ready_i ( {fifo_sel_ready_r_b, axi_b_req_i.r_ready} ) + ); + + + //----------------------------------- + // Channel B FIFOs + //----------------------------------- + for (genvar id = 0; id < 2**AxiIdWidth; id++) begin : gen_fifos_b + + stream_fifo #( + .FALL_THROUGH ( 1'b0 ), + .DATA_WIDTH ( 1'b0 ), + .DEPTH ( FifoDepth ), + .T ( axi_aw_chan_t ) + ) i_stream_fifo_aw_b ( + .clk_i, + .rst_ni, + .testmode_i, + .flush_i ( 1'b0 ), + .usage_o ( /* NOT CONNECTED */ ), + .data_i ( axi_b_req_i.aw ), + .valid_i ( fifo_valid_aw_b [id] ), + .ready_o ( fifo_ready_aw_b [id] ), + .data_o ( fifo_cmp_data_aw_b [id] ), + .valid_o ( fifo_cmp_valid_aw_b [id] ), + .ready_i ( fifo_cmp_valid_aw_a [id] & fifo_cmp_valid_aw_b [id] ) + ); + + stream_fifo #( + .FALL_THROUGH ( 1'b0 ), + .DATA_WIDTH ( 1'b0 ), + .DEPTH ( FifoDepth ), + .T ( axi_b_chan_t ) + ) i_stream_fifo_b_b ( + .clk_i, + .rst_ni, + .testmode_i, + .flush_i ( 1'b0 ), + .usage_o ( /* NOT CONNECTED */ ), + .data_i ( axi_b_rsp_i.b ), + .valid_i ( fifo_valid_b_b [id] ), + .ready_o ( fifo_ready_b_b [id] ), + .data_o ( fifo_cmp_data_b_b [id] ), + .valid_o ( fifo_cmp_valid_b_b [id] ), + .ready_i ( fifo_cmp_valid_b_a [id] & fifo_cmp_valid_b_b [id] ) + ); + + stream_fifo #( + .FALL_THROUGH ( 1'b0 ), + .DATA_WIDTH ( 1'b0 ), + .DEPTH ( FifoDepth ), + .T ( axi_ar_chan_t ) + ) i_stream_fifo_ar_b ( + .clk_i, + .rst_ni, + .testmode_i, + .flush_i ( 1'b0 ), + .usage_o ( /* NOT CONNECTED */ ), + .data_i ( axi_b_req_i.ar ), + .valid_i ( fifo_valid_ar_b [id] ), + .ready_o ( fifo_ready_ar_b [id] ), + .data_o ( fifo_cmp_data_ar_b [id] ), + .valid_o ( fifo_cmp_valid_ar_b [id] ), + .ready_i ( fifo_cmp_valid_ar_a [id] & fifo_cmp_valid_ar_b [id] ) + ); + + stream_fifo #( + .FALL_THROUGH ( 1'b0 ), + .DATA_WIDTH ( 1'b0 ), + .DEPTH ( FifoDepth ), + .T ( axi_r_chan_t ) + ) i_stream_fifo_r_b ( + .clk_i, + .rst_ni, + .testmode_i, + .flush_i ( 1'b0 ), + .usage_o ( /* NOT CONNECTED */ ), + .data_i ( axi_b_rsp_i.r ), + .valid_i ( fifo_valid_r_b [id] ), + .ready_o ( fifo_ready_r_b [id] ), + .data_o ( fifo_cmp_data_r_b [id] ), + .valid_o ( fifo_cmp_valid_r_b [id] ), + .ready_i ( fifo_cmp_valid_r_a [id] & fifo_cmp_valid_r_b [id] ) + ); + end + + stream_fifo #( + .FALL_THROUGH ( 1'b0 ), + .DATA_WIDTH ( 1'b0 ), + .DEPTH ( FifoDepth ), + .T ( axi_w_chan_t ) + ) i_stream_fifo_w_b ( + .clk_i, + .rst_ni, + .testmode_i, + .flush_i ( 1'b0 ), + .usage_o ( /* NOT CONNECTED */ ), + .data_i ( axi_b_req_i.w ), + .valid_i ( fifo_sel_valid_w_b ), + .ready_o ( fifo_sel_ready_w_b ), + .data_o ( fifo_cmp_data_w_b ), + .valid_o ( fifo_cmp_valid_w_b ), + .ready_i ( fifo_cmp_valid_w_a & fifo_cmp_valid_w_b ) + ); + + + //----------------------------------- + // Input Handshaking B + //----------------------------------- + always_comb begin : gen_handshaking_b + // aw + // defaults + fifo_valid_aw_b = '0; + fifo_sel_ready_aw_b = '0; + // assign according id + fifo_valid_aw_b [axi_b_req_i.aw.id] = fifo_sel_valid_aw_b; + fifo_sel_ready_aw_b = fifo_ready_aw_b[axi_b_req_i.aw.id]; + + + // b + // defaults + fifo_valid_b_b = '0; + fifo_sel_ready_b_b = '0; + // assign according id + fifo_valid_b_b [axi_b_rsp_i.b.id] = fifo_sel_valid_b_b; + fifo_sel_ready_b_b = fifo_ready_b_b[axi_b_rsp_i.b.id]; + + // ar + // defaults + fifo_valid_ar_b = '0; + fifo_sel_ready_ar_b = '0; + // assign according id + fifo_valid_ar_b [axi_b_req_i.ar.id] = fifo_sel_valid_ar_b; + fifo_sel_ready_ar_b = fifo_ready_ar_b[axi_b_req_i.ar.id]; + + // b + // defaults + fifo_valid_r_b = '0; + fifo_sel_ready_r_b = '0; + // assign according id + fifo_valid_r_b [axi_b_rsp_i.r.id] = fifo_sel_valid_r_b; + fifo_sel_ready_r_b = fifo_ready_r_b[axi_b_rsp_i.r.id]; + end + + + //----------------------------------- + // Comparison + //----------------------------------- + for (genvar id = 0; id < 2**AxiIdWidth; id++) begin : gen_cmp + assign aw_mismatch_o [id] = (fifo_cmp_valid_aw_a [id] & fifo_cmp_valid_aw_b [id]) ? + fifo_cmp_data_aw_a [id] == fifo_cmp_data_aw_b [id] : '0; + assign b_mismatch_o [id] = (fifo_cmp_valid_b_a [id] & fifo_cmp_valid_b_b [id]) ? + fifo_cmp_data_b_a [id] == fifo_cmp_data_b_b [id] : '0; + assign ar_mismatch_o [id] = (fifo_cmp_valid_ar_a [id] & fifo_cmp_valid_ar_b [id]) ? + fifo_cmp_data_ar_a [id] == fifo_cmp_data_ar_b [id] : '0; + assign r_mismatch_o [id] = (fifo_cmp_valid_r_a [id] & fifo_cmp_valid_r_b [id]) ? + fifo_cmp_data_r_a [id] == fifo_cmp_data_r_b [id] : '0; + end + + assign w_mismatch_o = (fifo_cmp_valid_w_a & fifo_cmp_valid_w_b ) ? + fifo_cmp_data_w_a != fifo_cmp_data_w_b : '0; + + + //----------------------------------- + // Outputs + //----------------------------------- + assign busy_o = (|fifo_cmp_valid_aw_a) | (|fifo_cmp_valid_aw_b) | + (|fifo_cmp_valid_w_a) | (|fifo_cmp_valid_w_b) | + (|fifo_cmp_valid_b_a) | (|fifo_cmp_valid_b_b) | + (|fifo_cmp_valid_ar_a) | (|fifo_cmp_valid_ar_b) | + (|fifo_cmp_valid_r_a) | (|fifo_cmp_valid_r_b); + + + assign mismatch_o = (|aw_mismatch_o) | (|w_mismatch_o) | (|b_mismatch_o) | + (|ar_mismatch_o) | (|r_mismatch_o); + +endmodule diff --git a/src/axi_cdc.sv b/src/axi_cdc.sv index 1e422ed72..81ad58818 100644 --- a/src/axi_cdc.sv +++ b/src/axi_cdc.sv @@ -11,10 +11,8 @@ // // Authors: // - Andreas Kurth -// - Fabian Schuiki -// - Florian Zaruba +// - Luca Valente // - Wolfgang Roenninger -// - Luca Valente `include "axi/assign.svh" @@ -32,7 +30,9 @@ module axi_cdc #( parameter type axi_req_t = logic, // encapsulates request channels parameter type axi_resp_t = logic, // encapsulates request channels /// Depth of the FIFO crossing the clock domain, given as 2**LOG_DEPTH. - parameter int unsigned LogDepth = 1 + parameter int unsigned LogDepth = 1, + /// Number of synchronization registers to insert on the async pointers + parameter int unsigned SyncStages = 2 ) ( // slave side - clocked by `src_clk_i` input logic src_clk_i, @@ -65,7 +65,8 @@ module axi_cdc #( .r_chan_t ( r_chan_t ), .axi_req_t ( axi_req_t ), .axi_resp_t ( axi_resp_t ), - .LogDepth ( LogDepth ) + .LogDepth ( LogDepth ), + .SyncStages ( SyncStages ) ) i_axi_cdc_src ( .src_clk_i, .src_rst_ni, @@ -96,7 +97,8 @@ module axi_cdc #( .r_chan_t ( r_chan_t ), .axi_req_t ( axi_req_t ), .axi_resp_t ( axi_resp_t ), - .LogDepth ( LogDepth ) + .LogDepth ( LogDepth ), + .SyncStages ( SyncStages ) ) i_axi_cdc_dst ( .dst_clk_i, .dst_rst_ni, @@ -131,7 +133,9 @@ module axi_cdc_intf #( parameter int unsigned AXI_DATA_WIDTH = 0, parameter int unsigned AXI_USER_WIDTH = 0, /// Depth of the FIFO crossing the clock domain, given as 2**LOG_DEPTH. - parameter int unsigned LOG_DEPTH = 1 + parameter int unsigned LOG_DEPTH = 1, + /// Number of synchronization registers to insert on the async pointers + parameter int unsigned SYNC_STAGES = 2 ) ( // slave side - clocked by `src_clk_i` input logic src_clk_i, @@ -173,7 +177,8 @@ module axi_cdc_intf #( .r_chan_t ( r_chan_t ), .axi_req_t ( req_t ), .axi_resp_t ( resp_t ), - .LogDepth ( LOG_DEPTH ) + .LogDepth ( LOG_DEPTH ), + .SyncStages ( SYNC_STAGES ) ) i_axi_cdc ( .src_clk_i, .src_rst_ni, @@ -191,7 +196,9 @@ module axi_lite_cdc_intf #( parameter int unsigned AXI_ADDR_WIDTH = 0, parameter int unsigned AXI_DATA_WIDTH = 0, /// Depth of the FIFO crossing the clock domain, given as 2**LOG_DEPTH. - parameter int unsigned LOG_DEPTH = 1 + parameter int unsigned LOG_DEPTH = 1, + /// Number of synchronization registers to insert on the async pointers + parameter int unsigned SYNC_STAGES = 2 ) ( // slave side - clocked by `src_clk_i` input logic src_clk_i, @@ -231,7 +238,8 @@ module axi_lite_cdc_intf #( .r_chan_t ( r_chan_t ), .axi_req_t ( req_t ), .axi_resp_t ( resp_t ), - .LogDepth ( LOG_DEPTH ) + .LogDepth ( LOG_DEPTH ), + .SyncStages ( SYNC_STAGES ) ) i_axi_cdc ( .src_clk_i, .src_rst_ni, diff --git a/src/axi_cdc_dst.sv b/src/axi_cdc_dst.sv index d631e7e85..c3e76bfc1 100644 --- a/src/axi_cdc_dst.sv +++ b/src/axi_cdc_dst.sv @@ -10,10 +10,8 @@ // specific language governing permissions and limitations under the License. // // Authors: +// - Luca Valente // - Andreas Kurth -// - Fabian Schuiki -// - Florian Zaruba -// - Luca Valente `include "axi/assign.svh" `include "axi/typedef.svh" @@ -26,6 +24,8 @@ module axi_cdc_dst #( /// Depth of the FIFO crossing the clock domain, given as 2**LOG_DEPTH. parameter int unsigned LogDepth = 1, + /// Number of synchronization registers to insert on the async pointers + parameter int unsigned SyncStages = 2, parameter type aw_chan_t = logic, parameter type w_chan_t = logic, parameter type b_chan_t = logic, @@ -65,7 +65,8 @@ module axi_cdc_dst #( // Other tools, such as VCS, have problems with type parameters constructed through `$bits()`. .T ( aw_chan_t ), `endif - .LOG_DEPTH ( LogDepth ) + .LOG_DEPTH ( LogDepth ), + .SYNC_STAGES ( SyncStages ) ) i_cdc_fifo_gray_dst_aw ( .async_data_i ( async_data_slave_aw_data_i ), .async_wptr_i ( async_data_slave_aw_wptr_i ), @@ -83,7 +84,8 @@ module axi_cdc_dst #( `else .T ( w_chan_t ), `endif - .LOG_DEPTH ( LogDepth ) + .LOG_DEPTH ( LogDepth ), + .SYNC_STAGES ( SyncStages ) ) i_cdc_fifo_gray_dst_w ( .async_data_i ( async_data_slave_w_data_i ), .async_wptr_i ( async_data_slave_w_wptr_i ), @@ -101,7 +103,8 @@ module axi_cdc_dst #( `else .T ( b_chan_t ), `endif - .LOG_DEPTH ( LogDepth ) + .LOG_DEPTH ( LogDepth ), + .SYNC_STAGES ( SyncStages ) ) i_cdc_fifo_gray_src_b ( .src_clk_i ( dst_clk_i ), .src_rst_ni ( dst_rst_ni ), @@ -119,7 +122,8 @@ module axi_cdc_dst #( `else .T ( ar_chan_t ), `endif - .LOG_DEPTH ( LogDepth ) + .LOG_DEPTH ( LogDepth ), + .SYNC_STAGES ( SyncStages ) ) i_cdc_fifo_gray_dst_ar ( .dst_clk_i, .dst_rst_ni, @@ -137,7 +141,8 @@ module axi_cdc_dst #( `else .T ( r_chan_t ), `endif - .LOG_DEPTH ( LogDepth ) + .LOG_DEPTH ( LogDepth ), + .SYNC_STAGES ( SyncStages ) ) i_cdc_fifo_gray_src_r ( .src_clk_i ( dst_clk_i ), .src_rst_ni ( dst_rst_ni ), @@ -158,7 +163,9 @@ module axi_cdc_dst_intf #( parameter int unsigned AXI_DATA_WIDTH = 0, parameter int unsigned AXI_USER_WIDTH = 0, /// Depth of the FIFO crossing the clock domain, given as 2**LOG_DEPTH. - parameter int unsigned LOG_DEPTH = 1 + parameter int unsigned LOG_DEPTH = 1, + /// Number of synchronization registers to insert on the async pointers + parameter int unsigned SYNC_STAGES = 2 ) ( // asynchronous slave port AXI_BUS_ASYNC_GRAY.Slave src, @@ -185,14 +192,15 @@ module axi_cdc_dst_intf #( resp_t dst_resp; axi_cdc_dst #( - .aw_chan_t ( aw_chan_t ), - .w_chan_t ( w_chan_t ), - .b_chan_t ( b_chan_t ), - .ar_chan_t ( ar_chan_t ), - .r_chan_t ( r_chan_t ), - .axi_req_t ( req_t ), - .axi_resp_t ( resp_t ), - .LogDepth ( LOG_DEPTH ) + .aw_chan_t ( aw_chan_t ), + .w_chan_t ( w_chan_t ), + .b_chan_t ( b_chan_t ), + .ar_chan_t ( ar_chan_t ), + .r_chan_t ( r_chan_t ), + .axi_req_t ( req_t ), + .axi_resp_t ( resp_t ), + .LogDepth ( LOG_DEPTH ), + .SyncStages ( SYNC_STAGES ) ) i_axi_cdc_dst ( .async_data_slave_aw_data_i ( src.aw_data ), .async_data_slave_aw_wptr_i ( src.aw_wptr ), @@ -225,7 +233,9 @@ module axi_lite_cdc_dst_intf #( parameter int unsigned AXI_ADDR_WIDTH = 0, parameter int unsigned AXI_DATA_WIDTH = 0, /// Depth of the FIFO crossing the clock domain, given as 2**LOG_DEPTH. - parameter int unsigned LOG_DEPTH = 1 + parameter int unsigned LOG_DEPTH = 1, + /// Number of synchronization registers to insert on the async pointers + parameter int unsigned SYNC_STAGES = 2 ) ( // asynchronous slave port AXI_LITE_ASYNC_GRAY.Slave src, @@ -250,14 +260,15 @@ module axi_lite_cdc_dst_intf #( resp_t dst_resp; axi_cdc_dst #( - .aw_chan_t ( aw_chan_t ), - .w_chan_t ( w_chan_t ), - .b_chan_t ( b_chan_t ), - .ar_chan_t ( ar_chan_t ), - .r_chan_t ( r_chan_t ), - .axi_req_t ( req_t ), - .axi_resp_t ( resp_t ), - .LogDepth ( LOG_DEPTH ) + .aw_chan_t ( aw_chan_t ), + .w_chan_t ( w_chan_t ), + .b_chan_t ( b_chan_t ), + .ar_chan_t ( ar_chan_t ), + .r_chan_t ( r_chan_t ), + .axi_req_t ( req_t ), + .axi_resp_t ( resp_t ), + .LogDepth ( LOG_DEPTH ), + .SyncStages ( SYNC_STAGES ) ) i_axi_cdc_dst ( .async_data_slave_aw_data_i ( src.aw_data ), .async_data_slave_aw_wptr_i ( src.aw_wptr ), diff --git a/src/axi_cdc_src.sv b/src/axi_cdc_src.sv index 65dcbe497..36e4e6119 100644 --- a/src/axi_cdc_src.sv +++ b/src/axi_cdc_src.sv @@ -10,10 +10,8 @@ // specific language governing permissions and limitations under the License. // // Authors: +// - Luca Valente // - Andreas Kurth -// - Fabian Schuiki -// - Florian Zaruba -// - Luca Valente `include "axi/assign.svh" `include "axi/typedef.svh" @@ -25,7 +23,9 @@ /// the FIFO; see the header of `cdc_fifo_gray` for instructions. module axi_cdc_src #( /// Depth of the FIFO crossing the clock domain, given as 2**LOG_DEPTH. - parameter int unsigned LogDepth = 1, + parameter int unsigned LogDepth = 1, + /// Number of synchronization registers to insert on the async pointers + parameter int unsigned SyncStages = 2, parameter type aw_chan_t = logic, parameter type w_chan_t = logic, parameter type b_chan_t = logic, @@ -64,7 +64,8 @@ module axi_cdc_src #( `else .T ( aw_chan_t ), `endif - .LOG_DEPTH ( LogDepth ) + .LOG_DEPTH ( LogDepth ), + .SYNC_STAGES ( SyncStages ) ) i_cdc_fifo_gray_src_aw ( .src_clk_i, .src_rst_ni, @@ -82,7 +83,8 @@ module axi_cdc_src #( `else .T ( w_chan_t ), `endif - .LOG_DEPTH ( LogDepth ) + .LOG_DEPTH ( LogDepth ), + .SYNC_STAGES ( SyncStages ) ) i_cdc_fifo_gray_src_w ( .src_clk_i, .src_rst_ni, @@ -100,7 +102,8 @@ module axi_cdc_src #( `else .T ( b_chan_t ), `endif - .LOG_DEPTH ( LogDepth ) + .LOG_DEPTH ( LogDepth ), + .SYNC_STAGES ( SyncStages ) ) i_cdc_fifo_gray_dst_b ( .dst_clk_i ( src_clk_i ), .dst_rst_ni ( src_rst_ni ), @@ -118,7 +121,8 @@ module axi_cdc_src #( `else .T ( ar_chan_t ), `endif - .LOG_DEPTH ( LogDepth ) + .LOG_DEPTH ( LogDepth ), + .SYNC_STAGES ( SyncStages ) ) i_cdc_fifo_gray_src_ar ( .src_clk_i, .src_rst_ni, @@ -136,7 +140,8 @@ module axi_cdc_src #( `else .T ( r_chan_t ), `endif - .LOG_DEPTH ( LogDepth ) + .LOG_DEPTH ( LogDepth ), + .SYNC_STAGES ( SyncStages ) ) i_cdc_fifo_gray_dst_r ( .dst_clk_i ( src_clk_i ), .dst_rst_ni ( src_rst_ni ), @@ -157,7 +162,9 @@ module axi_cdc_src_intf #( parameter int unsigned AXI_DATA_WIDTH = 0, parameter int unsigned AXI_USER_WIDTH = 0, /// Depth of the FIFO crossing the clock domain, given as 2**LOG_DEPTH. - parameter int unsigned LOG_DEPTH = 1 + parameter int unsigned LOG_DEPTH = 1, + /// Number of synchronization registers to insert on the async pointers + parameter int unsigned SYNC_STAGES = 2 ) ( // synchronous slave port - clocked by `src_clk_i` input logic src_clk_i, @@ -187,14 +194,15 @@ module axi_cdc_src_intf #( `AXI_ASSIGN_FROM_RESP(src, src_resp) axi_cdc_src #( - .aw_chan_t ( aw_chan_t ), - .w_chan_t ( w_chan_t ), - .b_chan_t ( b_chan_t ), - .ar_chan_t ( ar_chan_t ), - .r_chan_t ( r_chan_t ), - .axi_req_t ( req_t ), - .axi_resp_t ( resp_t ), - .LogDepth ( LOG_DEPTH ) + .aw_chan_t ( aw_chan_t ), + .w_chan_t ( w_chan_t ), + .b_chan_t ( b_chan_t ), + .ar_chan_t ( ar_chan_t ), + .r_chan_t ( r_chan_t ), + .axi_req_t ( req_t ), + .axi_resp_t ( resp_t ), + .LogDepth ( LOG_DEPTH ), + .SyncStages ( SYNC_STAGES ) ) i_axi_cdc_src ( .src_clk_i, .src_rst_ni, @@ -224,7 +232,9 @@ module axi_lite_cdc_src_intf #( parameter int unsigned AXI_ADDR_WIDTH = 0, parameter int unsigned AXI_DATA_WIDTH = 0, /// Depth of the FIFO crossing the clock domain, given as 2**LOG_DEPTH. - parameter int unsigned LOG_DEPTH = 1 + parameter int unsigned LOG_DEPTH = 1, + /// Number of synchronization registers to insert on the async pointers + parameter int unsigned SYNC_STAGES = 2 ) ( // synchronous slave port - clocked by `src_clk_i` input logic src_clk_i, @@ -252,14 +262,15 @@ module axi_lite_cdc_src_intf #( `AXI_LITE_ASSIGN_FROM_RESP(src, src_resp) axi_cdc_src #( - .aw_chan_t ( aw_chan_t ), - .w_chan_t ( w_chan_t ), - .b_chan_t ( b_chan_t ), - .ar_chan_t ( ar_chan_t ), - .r_chan_t ( r_chan_t ), - .axi_req_t ( req_t ), - .axi_resp_t ( resp_t ), - .LogDepth ( LOG_DEPTH ) + .aw_chan_t ( aw_chan_t ), + .w_chan_t ( w_chan_t ), + .b_chan_t ( b_chan_t ), + .ar_chan_t ( ar_chan_t ), + .r_chan_t ( r_chan_t ), + .axi_req_t ( req_t ), + .axi_resp_t ( resp_t ), + .LogDepth ( LOG_DEPTH ), + .SyncStages ( SYNC_STAGES ) ) i_axi_cdc_src ( .src_clk_i, .src_rst_ni, diff --git a/src/axi_chan_compare.sv b/src/axi_chan_compare.sv new file mode 100644 index 000000000..1e4e82347 --- /dev/null +++ b/src/axi_chan_compare.sv @@ -0,0 +1,266 @@ +// Copyright 2018 ETH Zurich and University of Bologna. +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the "License"); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. +// +// Authors: +// - Thomas Benz +// - Paul Scheffler +// - Tim Fischer + +/// Non-synthesizable module comparing two AXI channels of the same type +module axi_chan_compare #( + /// Ignore ID field if it was remapped + parameter bit IgnoreId = 1'b0, + /// Allow reordered responses of different IDs, + /// not compatible with `IgnoreId` + parameter bit AllowReordering = 1'b0, + /// AXI ID Width + parameter int unsigned IdWidth = 1, + parameter type aw_chan_t = logic, + parameter type w_chan_t = logic, + parameter type b_chan_t = logic, + parameter type ar_chan_t = logic, + parameter type r_chan_t = logic, + parameter type req_t = logic, + parameter type resp_t = logic +)( + input logic clk_a_i, + input logic clk_b_i, + input req_t axi_a_req, + input resp_t axi_a_res, + input req_t axi_b_req, + input resp_t axi_b_res +); + + function automatic void print_aw ( + input aw_chan_t aw_expected, + input aw_chan_t aw_received + ); + // verilog_lint: waive-start line-length + $display("AW | expected | received "); + $display("--------|------------------------------------------------------------------|-----------------------------------------------------------------"); + $display("id: | %64d | %64d", aw_expected.id, aw_received.id); + $display("addr: | %64x | %64x", aw_expected.addr, aw_received.addr); + $display("len: | %64d | %64d", aw_expected.len, aw_received.len); + $display("size: | %64d | %64d", aw_expected.size, aw_received.size); + $display("burst: | %64d | %64d", aw_expected.burst, aw_received.burst); + $display("lock: | %64d | %64d", aw_expected.lock, aw_received.lock); + $display("cache: | %64d | %64d", aw_expected.cache, aw_received.cache); + $display("prot: | %64d | %64d", aw_expected.prot, aw_received.prot); + $display("qos: | %64d | %64d", aw_expected.qos, aw_received.qos); + $display("region: | %64d | %64d", aw_expected.region, aw_received.region); + $display("user: | %64d | %64d", aw_expected.user, aw_received.user); + $display("atop: | %64d | %64d", aw_expected.atop, aw_received.atop); + $display("--------|------------------------------------------------------------------|-----------------------------------------------------------------"); + // verilog_lint: waive-stop line-length + endfunction + + function automatic void print_ar ( + input ar_chan_t ar_expected, + input ar_chan_t ar_received + ); + // verilog_lint: waive-start line-length + $display("AR | expected | received "); + $display("--------|------------------------------------------------------------------|-----------------------------------------------------------------"); + $display("id: | %64d | %64d", ar_expected.id, ar_received.id); + $display("addr: | %64x | %64x", ar_expected.addr, ar_received.addr); + $display("len: | %64d | %64d", ar_expected.len, ar_received.len); + $display("size: | %64d | %64d", ar_expected.size, ar_received.size); + $display("burst: | %64d | %64d", ar_expected.burst, ar_received.burst); + $display("lock: | %64d | %64d", ar_expected.lock, ar_received.lock); + $display("cache: | %64d | %64d", ar_expected.cache, ar_received.cache); + $display("prot: | %64d | %64d", ar_expected.prot, ar_received.prot); + $display("qos: | %64d | %64d", ar_expected.qos, ar_received.qos); + $display("region: | %64d | %64d", ar_expected.region, ar_received.region); + $display("user: | %64d | %64d", ar_expected.user, ar_received.user); + $display("--------|------------------------------------------------------------------|-----------------------------------------------------------------"); + // verilog_lint: waive-stop line-length + endfunction + + function automatic void print_w ( + input w_chan_t w_expected, + input w_chan_t w_received + ); + // verilog_lint: waive-start line-length + $display("W | expected | received "); + $display("--------|------------------------------------------------------------------|-----------------------------------------------------------------"); + $display("data: | %64x | %64x", w_expected.data, w_received.data); + $display("strb: | %64d | %64d", w_expected.strb, w_received.strb); + $display("last: | %64d | %64d", w_expected.last, w_received.last); + $display("user: | %64d | %64d", w_expected.user, w_received.user); + $display("--------|------------------------------------------------------------------|-----------------------------------------------------------------"); + // verilog_lint: waive-stop line-length + endfunction + + function automatic void print_b ( + input b_chan_t b_expected, + input b_chan_t b_received + ); + // verilog_lint: waive-start line-length + $display("B | expected | received "); + $display("--------|------------------------------------------------------------------|-----------------------------------------------------------------"); + $display("id: | %64d | %64d", b_expected.id, b_received.id); + $display("resp: | %64d | %64d", b_expected.resp, b_received.resp); + $display("user: | %64d | %64d", b_expected.user, b_received.user); + $display("--------|------------------------------------------------------------------|-----------------------------------------------------------------"); + // verilog_lint: waive-stop line-length + endfunction + + function automatic void print_r ( + input r_chan_t r_expected, + input r_chan_t r_received + ); + // verilog_lint: waive-start line-length + $display("R | expected | received "); + $display("--------|------------------------------------------------------------------|-----------------------------------------------------------------"); + $display("id: | %64d | %64d", r_expected.id, r_received.id); + $display("data: | %64x | %64x", r_expected.data, r_received.data); + $display("resp: | %64d | %64d", r_expected.resp, r_received.resp); + $display("last: | %64d | %64d", r_expected.last, r_received.last); + $display("user: | %64d | %64d", r_expected.user, r_received.user); + $display("--------|------------------------------------------------------------------|-----------------------------------------------------------------"); + // verilog_lint: waive-stop line-length + endfunction + + localparam NumIds = (AllowReordering)? 2**IdWidth : 1; + + // queues + aw_chan_t aw_queue [NumIds-1:0][$]; + w_chan_t w_queue [$]; + b_chan_t b_queue [NumIds-1:0][$]; + ar_chan_t ar_queue [NumIds-1:0][$]; + r_chan_t r_queue [NumIds-1:0][$]; + + // requests generated at axi A: enqueue elements + always_ff @(posedge clk_a_i) begin : proc_enqueue_a + // aw + if (axi_a_req.aw_valid & axi_a_res.aw_ready) + if (AllowReordering) aw_queue[axi_a_req.aw.id].push_back(axi_a_req.aw); + else aw_queue[0].push_back(axi_a_req.aw); + // w + if (axi_a_req.w_valid & axi_a_res.w_ready) + w_queue.push_back(axi_a_req.w); + // ar + if (axi_a_req.ar_valid & axi_a_res.ar_ready) + if (AllowReordering) ar_queue[axi_a_req.ar.id].push_back(axi_a_req.ar); + else ar_queue[0].push_back(axi_a_req.ar); + end + + // responses generated at axi B: enqueue elements + always_ff @(posedge clk_b_i) begin : proc_enqueue_b + // b + if (axi_b_res.b_valid & axi_b_req.b_ready) + if (AllowReordering) b_queue[axi_b_res.b.id].push_back(axi_b_res.b); + else b_queue[0].push_back(axi_b_res.b); + // r + if (axi_b_res.r_valid & axi_b_req.r_ready) + if (AllowReordering) r_queue[axi_b_res.r.id].push_back(axi_b_res.r); + else r_queue[0].push_back(axi_b_res.r); + end + + // requests arriving at axi B from A: dequeue elements and check + always_ff @(posedge clk_b_i) begin : proc_dequeue_and_check_b + // aw + if (axi_b_req.aw_valid & axi_b_res.aw_ready) begin + automatic aw_chan_t aw_exp, aw_recv; + if (AllowReordering) begin + if (aw_queue[axi_b_req.aw.id].size() == 0) $error("AW queue is empty!"); + aw_exp = aw_queue[axi_b_req.aw.id].pop_front(); // verilog_lint: waive always-ff-non-blocking + end else begin + if (aw_queue[0].size() == 0) $error("AW queue is empty!"); + aw_exp = aw_queue[0].pop_front(); // verilog_lint: waive always-ff-non-blocking + end + aw_recv = axi_b_req.aw; + if (IgnoreId) begin + aw_exp.id = 'X; + aw_recv.id = 'X; + end + if (aw_exp !== aw_recv) begin + $error("AW mismatch!"); + print_aw(aw_exp, aw_recv); + end + end + // w + if (axi_b_req.w_valid & axi_b_res.w_ready) begin + automatic w_chan_t w_exp, w_recv; + if (w_queue.size() == 0) $error("W queue is empty!"); + w_exp = w_queue.pop_front(); // verilog_lint: waive always-ff-non-blocking + w_recv = axi_b_req.w; + if (w_exp !== w_recv) begin + $error("W mismatch!"); + print_w(w_exp, w_recv); + end + end + // ar + if (axi_b_req.ar_valid & axi_b_res.ar_ready) begin + automatic ar_chan_t ar_exp, ar_recv; + if (AllowReordering) begin + if (ar_queue[axi_b_req.ar.id].size() == 0) $error("AR queue is empty!"); + ar_exp = ar_queue[axi_b_req.ar.id].pop_front(); // verilog_lint: waive always-ff-non-blocking + end else begin + if (ar_queue[0].size() == 0) $error("AR queue is empty!"); + ar_exp = ar_queue[0].pop_front(); // verilog_lint: waive always-ff-non-blocking + end + ar_recv = axi_b_req.ar; + if (IgnoreId) begin + ar_exp.id = 'X; + ar_recv.id = 'X; + end + if (ar_exp !== ar_recv) begin + $error("AR mismatch!"); + print_ar(ar_exp, ar_recv); + end + end + end + + // responses arriving at axi A from B: dequeue elements and check + always_ff @(posedge clk_a_i) begin : proc_dequeue_and_check_a + // b + if (axi_a_res.b_valid & axi_a_req.b_ready) begin + automatic b_chan_t b_exp, b_recv; + if (AllowReordering) begin + if (b_queue[axi_a_res.b.id].size() == 0) $error("B queue is empty!"); + b_exp = b_queue[axi_a_res.b.id].pop_front(); // verilog_lint: waive always-ff-non-blocking + end else begin + if (b_queue[0].size() == 0) $error("B queue is empty!"); + b_exp = b_queue[0].pop_front(); // verilog_lint: waive always-ff-non-blocking + end + b_recv = axi_a_res.b; + if (IgnoreId) begin + b_exp.id = 'X; + b_recv.id = 'X; + end + if (b_exp !== b_recv) begin + $error("B mismatch!"); + print_b(b_exp, b_recv); + end + end + // r + if (axi_a_res.r_valid & axi_a_req.r_ready) begin + automatic r_chan_t r_exp, r_recv; + if (AllowReordering) begin + if (r_queue[axi_a_res.r.id].size() == 0) $error("R queue is empty!"); + r_exp = r_queue[axi_a_res.r.id].pop_front(); // verilog_lint: waive always-ff-non-blocking + end else begin + if (r_queue[0].size() == 0) $error("R queue is empty!"); + r_exp = r_queue[0].pop_front(); // verilog_lint: waive always-ff-non-blocking + end + r_recv = axi_a_res.r; + if (IgnoreId) begin + r_exp.id = 'X; + r_recv.id = 'X; + end + if (r_exp !== r_recv) begin + $error("R mismatch!"); + print_r(r_exp, r_recv); + end + end + end + +endmodule : axi_chan_compare diff --git a/src/axi_demux.sv b/src/axi_demux.sv index adc7b6bb7..899d83521 100644 --- a/src/axi_demux.sv +++ b/src/axi_demux.sv @@ -9,7 +9,9 @@ // specific language governing permissions and limitations under the License. // // Authors: +// - Michael Rogenmoser // - Wolfgang Roenninger +// - Thomas Benz // - Andreas Kurth `include "common_cells/assertions.svh" @@ -21,8 +23,22 @@ `define TARGET_VSIM `endif -// axi_demux: Demultiplex an AXI bus from one slave port to multiple master ports. -// See `doc/axi_demux.md` for the documentation, including the definition of parameters and ports. +/// Demultiplex one AXI4+ATOP slave port to multiple AXI4+ATOP master ports. +/// +/// The AW and AR slave channels each have a `select` input to determine to which master port the +/// current request is sent. The `select` can, for example, be driven by an address decoding module +/// to map address ranges to different AXI slaves. +/// +/// ## Design overview +/// +/// ![Block diagram](module.axi_demux.png "Block diagram") +/// +/// Beats on the W channel are routed by demultiplexer according to the selection for the +/// corresponding AW beat. This relies on the AXI property that W bursts must be sent in the same +/// order as AW beats and beats from different W bursts may not be interleaved. +/// +/// Beats on the B and R channel are multiplexed from the master ports to the slave port with +/// a round-robin arbitration tree. module axi_demux #( parameter int unsigned AxiIdWidth = 32'd0, parameter bit AtopSupport = 1'b1, @@ -37,7 +53,6 @@ module axi_demux #( parameter int unsigned MaxTrans = 32'd8, parameter int unsigned AxiLookBits = 32'd3, parameter bit UniqueIds = 1'b0, - parameter bit FallThrough = 1'b0, parameter bit SpillAw = 1'b1, parameter bit SpillW = 1'b0, parameter bit SpillB = 1'b0, @@ -60,716 +75,139 @@ module axi_demux #( input axi_resp_t [NoMstPorts-1:0] mst_resps_i ); - localparam int unsigned IdCounterWidth = MaxTrans > 1 ? $clog2(MaxTrans) : 1; - - //-------------------------------------- - // Typedefs for the FIFOs / Queues - //-------------------------------------- - typedef struct packed { - aw_chan_t aw_chan; - select_t aw_select; - } aw_chan_select_t; - typedef struct packed { - ar_chan_t ar_chan; - select_t ar_select; - } ar_chan_select_t; - - // pass through if only one master port - if (NoMstPorts == 32'h1) begin : gen_no_demux - spill_register #( - .T ( aw_chan_t ), - .Bypass ( ~SpillAw ) - ) i_aw_spill_reg ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .valid_i ( slv_req_i.aw_valid ), - .ready_o ( slv_resp_o.aw_ready ), - .data_i ( slv_req_i.aw ), - .valid_o ( mst_reqs_o[0].aw_valid ), - .ready_i ( mst_resps_i[0].aw_ready ), - .data_o ( mst_reqs_o[0].aw ) - ); - spill_register #( - .T ( w_chan_t ), - .Bypass ( ~SpillW ) - ) i_w_spill_reg ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .valid_i ( slv_req_i.w_valid ), - .ready_o ( slv_resp_o.w_ready ), - .data_i ( slv_req_i.w ), - .valid_o ( mst_reqs_o[0].w_valid ), - .ready_i ( mst_resps_i[0].w_ready ), - .data_o ( mst_reqs_o[0].w ) - ); - spill_register #( - .T ( b_chan_t ), - .Bypass ( ~SpillB ) - ) i_b_spill_reg ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .valid_i ( mst_resps_i[0].b_valid ), - .ready_o ( mst_reqs_o[0].b_ready ), - .data_i ( mst_resps_i[0].b ), - .valid_o ( slv_resp_o.b_valid ), - .ready_i ( slv_req_i.b_ready ), - .data_o ( slv_resp_o.b ) - ); - spill_register #( - .T ( ar_chan_t ), - .Bypass ( ~SpillAr ) - ) i_ar_spill_reg ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .valid_i ( slv_req_i.ar_valid ), - .ready_o ( slv_resp_o.ar_ready ), - .data_i ( slv_req_i.ar ), - .valid_o ( mst_reqs_o[0].ar_valid ), - .ready_i ( mst_resps_i[0].ar_ready ), - .data_o ( mst_reqs_o[0].ar ) - ); - spill_register #( - .T ( r_chan_t ), - .Bypass ( ~SpillR ) - ) i_r_spill_reg ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .valid_i ( mst_resps_i[0].r_valid ), - .ready_o ( mst_reqs_o[0].r_ready ), - .data_i ( mst_resps_i[0].r ), - .valid_o ( slv_resp_o.r_valid ), - .ready_i ( slv_req_i.r_ready ), - .data_o ( slv_resp_o.r ) - ); - - // other non degenerate cases - end else begin : gen_demux - - //-------------------------------------- - //-------------------------------------- - // Signal Declarations - //-------------------------------------- - //-------------------------------------- - - //-------------------------------------- - // Write Transaction - //-------------------------------------- - // comes from spill register at input - aw_chan_select_t slv_aw_chan_select; - logic slv_aw_valid, slv_aw_ready; - - // AW ID counter - select_t lookup_aw_select; - logic aw_select_occupied, aw_id_cnt_full; - logic aw_push; - // Upon an ATOP load, inject IDs from the AW into the AR channel - logic atop_inject; - - // W FIFO: stores the decision to which master W beats should go - logic w_fifo_pop; - logic w_fifo_full, w_fifo_empty; - select_t w_select; - - // Register which locks the AW valid signal - logic lock_aw_valid_d, lock_aw_valid_q, load_aw_lock; - logic aw_valid, aw_ready; - - // W channel from spill reg - w_chan_t slv_w_chan; - logic slv_w_valid, slv_w_ready; - - // B channles input into the arbitration - b_chan_t [NoMstPorts-1:0] mst_b_chans; - logic [NoMstPorts-1:0] mst_b_valids, mst_b_readies; - - // B channel to spill register - b_chan_t slv_b_chan; - logic slv_b_valid, slv_b_ready; - - //-------------------------------------- - // Read Transaction - //-------------------------------------- - // comes from spill register at input - ar_chan_select_t slv_ar_chan_select; - logic slv_ar_valid, slv_ar_ready; - - // AR ID counter - select_t lookup_ar_select; - logic ar_select_occupied, ar_id_cnt_full; - logic ar_push; - - // Register which locks the AR valid signel - logic lock_ar_valid_d, lock_ar_valid_q, load_ar_lock; - logic ar_valid, ar_ready; - - // R channles input into the arbitration - r_chan_t [NoMstPorts-1:0] mst_r_chans; - logic [NoMstPorts-1:0] mst_r_valids, mst_r_readies; - - // R channel to spill register - r_chan_t slv_r_chan; - logic slv_r_valid, slv_r_ready; - - //-------------------------------------- - //-------------------------------------- - // Channel Control - //-------------------------------------- - //-------------------------------------- - - //-------------------------------------- - // AW Channel - //-------------------------------------- - // spill register at the channel input - `ifdef TARGET_VSIM - // Workaround for bug in Questa 2020.2 and 2021.1: Flatten the struct into a logic vector before - // instantiating `spill_register`. - typedef logic [$bits(aw_chan_select_t)-1:0] aw_chan_select_flat_t; - `else - // Other tools, such as VCS, have problems with `$bits()`, so the workaround cannot be used - // generally. - typedef aw_chan_select_t aw_chan_select_flat_t; - `endif - aw_chan_select_flat_t slv_aw_chan_select_in_flat, - slv_aw_chan_select_out_flat; - assign slv_aw_chan_select_in_flat = {slv_req_i.aw, slv_aw_select_i}; - spill_register #( - .T ( aw_chan_select_flat_t ), - .Bypass ( ~SpillAw ) // because module param indicates if we want a spill reg - ) i_aw_spill_reg ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .valid_i ( slv_req_i.aw_valid ), - .ready_o ( slv_resp_o.aw_ready ), - .data_i ( slv_aw_chan_select_in_flat ), - .valid_o ( slv_aw_valid ), - .ready_i ( slv_aw_ready ), - .data_o ( slv_aw_chan_select_out_flat ) - ); - assign slv_aw_chan_select = slv_aw_chan_select_out_flat; - - // Control of the AW handshake - always_comb begin - // AXI Handshakes - slv_aw_ready = 1'b0; - aw_valid = 1'b0; - // `lock_aw_valid`, used to be protocol conform as it is not allowed to deassert - // a valid if there was no corresponding ready. As this process has to be able to inject - // an AXI ID into the counter of the AR channel on an ATOP, there could be a case where - // this process waits on `aw_ready` but in the mean time on the AR channel the counter gets - // full. - lock_aw_valid_d = lock_aw_valid_q; - load_aw_lock = 1'b0; - // AW ID counter and W FIFO - aw_push = 1'b0; - // ATOP injection into ar counter - atop_inject = 1'b0; - // we had an arbitration decision, the valid is locked, wait for the transaction - if (lock_aw_valid_q) begin - aw_valid = 1'b1; - // transaction - if (aw_ready) begin - slv_aw_ready = 1'b1; - lock_aw_valid_d = 1'b0; - load_aw_lock = 1'b1; - // inject the ATOP if necessary - atop_inject = slv_aw_chan_select.aw_chan.atop[axi_pkg::ATOP_R_RESP] & AtopSupport; - end - end else begin - // An AW can be handled if `i_aw_id_counter` and `i_w_fifo` are not full. An ATOP that - // requires an R response can be handled if additionally `i_ar_id_counter` is not full (this - // only applies if ATOPs are supported at all). - if (!aw_id_cnt_full && !w_fifo_full && - (!(ar_id_cnt_full && slv_aw_chan_select.aw_chan.atop[axi_pkg::ATOP_R_RESP]) || - !AtopSupport)) begin - // there is a valid AW vector make the id lookup and go further, if it passes - if (slv_aw_valid && (!aw_select_occupied || - (slv_aw_chan_select.aw_select == lookup_aw_select))) begin - // connect the handshake - aw_valid = 1'b1; - // push arbitration to the W FIFO regardless, do not wait for the AW transaction - aw_push = 1'b1; - // on AW transaction - if (aw_ready) begin - slv_aw_ready = 1'b1; - atop_inject = slv_aw_chan_select.aw_chan.atop[axi_pkg::ATOP_R_RESP] & AtopSupport; - // no AW transaction this cycle, lock the decision - end else begin - lock_aw_valid_d = 1'b1; - load_aw_lock = 1'b1; - end - end - end - end - end - - // lock the valid signal, as the selection gets pushed into the W FIFO on first assertion, - // prevent further pushing - `FFLARN(lock_aw_valid_q, lock_aw_valid_d, load_aw_lock, '0, clk_i, rst_ni) - - if (UniqueIds) begin : gen_unique_ids_aw - // If the `UniqueIds` parameter is set, each write transaction has an ID that is unique among - // all in-flight write transactions, or all write transactions with a given ID target the same - // master port as all write transactions with the same ID, or both. This means that the - // signals that are driven by the ID counters if this parameter is not set can instead be - // derived from existing signals. The ID counters can therefore be omitted. - assign lookup_aw_select = slv_aw_chan_select.aw_select; - assign aw_select_occupied = 1'b0; - assign aw_id_cnt_full = 1'b0; - end else begin : gen_aw_id_counter - axi_demux_id_counters #( - .AxiIdBits ( AxiLookBits ), - .CounterWidth ( IdCounterWidth ), - .mst_port_select_t ( select_t ) - ) i_aw_id_counter ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .lookup_axi_id_i ( slv_aw_chan_select.aw_chan.id[0+:AxiLookBits] ), - .lookup_mst_select_o ( lookup_aw_select ), - .lookup_mst_select_occupied_o ( aw_select_occupied ), - .full_o ( aw_id_cnt_full ), - .inject_axi_id_i ( '0 ), - .inject_i ( 1'b0 ), - .push_axi_id_i ( slv_aw_chan_select.aw_chan.id[0+:AxiLookBits] ), - .push_mst_select_i ( slv_aw_chan_select.aw_select ), - .push_i ( aw_push ), - .pop_axi_id_i ( slv_b_chan.id[0+:AxiLookBits] ), - .pop_i ( slv_b_valid & slv_b_ready ) - ); - // pop from ID counter on outward transaction - end - - // FIFO to save W selection - fifo_v3 #( - .FALL_THROUGH ( FallThrough ), - .DEPTH ( MaxTrans ), - .dtype ( select_t ) - ) i_w_fifo ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .flush_i ( 1'b0 ), - .testmode_i( test_i ), - .full_o ( w_fifo_full ), - .empty_o ( w_fifo_empty ), - .usage_o ( ), - .data_i ( slv_aw_chan_select.aw_select ), - .push_i ( aw_push ), // controlled from proc_aw_chan - .data_o ( w_select ), // where the w beat should go - .pop_i ( w_fifo_pop ) // controlled from proc_w_chan - ); - - //-------------------------------------- - // W Channel - //-------------------------------------- - spill_register #( - .T ( w_chan_t ), - .Bypass ( ~SpillW ) - ) i_w_spill_reg( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .valid_i ( slv_req_i.w_valid ), - .ready_o ( slv_resp_o.w_ready ), - .data_i ( slv_req_i.w ), - .valid_o ( slv_w_valid ), - .ready_i ( slv_w_ready ), - .data_o ( slv_w_chan ) - ); - - //-------------------------------------- - // B Channel - //-------------------------------------- - // optional spill register - spill_register #( - .T ( b_chan_t ), - .Bypass ( ~SpillB ) - ) i_b_spill_reg ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .valid_i ( slv_b_valid ), - .ready_o ( slv_b_ready ), - .data_i ( slv_b_chan ), - .valid_o ( slv_resp_o.b_valid ), - .ready_i ( slv_req_i.b_ready ), - .data_o ( slv_resp_o.b ) - ); - - // Arbitration of the different B responses - rr_arb_tree #( - .NumIn ( NoMstPorts ), - .DataType ( b_chan_t ), - .AxiVldRdy( 1'b1 ), - .LockIn ( 1'b1 ) - ) i_b_mux ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .flush_i( 1'b0 ), - .rr_i ( '0 ), - .req_i ( mst_b_valids ), - .gnt_o ( mst_b_readies ), - .data_i ( mst_b_chans ), - .gnt_i ( slv_b_ready ), - .req_o ( slv_b_valid ), - .data_o ( slv_b_chan ), - .idx_o ( ) - ); - - //-------------------------------------- - // AR Channel - //-------------------------------------- - // Workaround for bug in Questa (see comments on AW channel for details). - `ifdef TARGET_VSIM - typedef logic [$bits(ar_chan_select_t)-1:0] ar_chan_select_flat_t; - `else - typedef ar_chan_select_t ar_chan_select_flat_t; - `endif - ar_chan_select_flat_t slv_ar_chan_select_in_flat, - slv_ar_chan_select_out_flat; - assign slv_ar_chan_select_in_flat = {slv_req_i.ar, slv_ar_select_i}; - spill_register #( - .T ( ar_chan_select_flat_t ), - .Bypass ( ~SpillAr ) - ) i_ar_spill_reg ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .valid_i ( slv_req_i.ar_valid ), - .ready_o ( slv_resp_o.ar_ready ), - .data_i ( slv_ar_chan_select_in_flat ), - .valid_o ( slv_ar_valid ), - .ready_i ( slv_ar_ready ), - .data_o ( slv_ar_chan_select_out_flat ) - ); - assign slv_ar_chan_select = slv_ar_chan_select_out_flat; - - // control of the AR handshake - always_comb begin - // AXI Handshakes - slv_ar_ready = 1'b0; - ar_valid = 1'b0; - // `lock_ar_valid`: Used to be protocol conform as it is not allowed to deassert `ar_valid` - // if there was no corresponding `ar_ready`. There is the possibility that an injection - // of a R response from an `atop` from the AW channel can change the occupied flag of the - // `i_ar_id_counter`, even if it was previously empty. This FF prevents the deassertion. - lock_ar_valid_d = lock_ar_valid_q; - load_ar_lock = 1'b0; - // AR id counter - ar_push = 1'b0; - // The process had an arbitration decision in a previous cycle, the valid is locked, - // wait for the AR transaction. - if (lock_ar_valid_q) begin - ar_valid = 1'b1; - // transaction - if (ar_ready) begin - slv_ar_ready = 1'b1; - ar_push = 1'b1; - lock_ar_valid_d = 1'b0; - load_ar_lock = 1'b1; - end - end else begin - // The process can start handling AR transaction if `i_ar_id_counter` has space. - if (!ar_id_cnt_full) begin - // There is a valid AR, so look the ID up. - if (slv_ar_valid && (!ar_select_occupied || - (slv_ar_chan_select.ar_select == lookup_ar_select))) begin - // connect the AR handshake - ar_valid = 1'b1; - // on transaction - if (ar_ready) begin - slv_ar_ready = 1'b1; - ar_push = 1'b1; - // no transaction this cycle, lock the valid decision! - end else begin - lock_ar_valid_d = 1'b1; - load_ar_lock = 1'b1; - end - end - end - end - end - - // this ff is needed so that ar does not get de-asserted if an atop gets injected - `FFLARN(lock_ar_valid_q, lock_ar_valid_d, load_ar_lock, '0, clk_i, rst_ni) - - if (UniqueIds) begin : gen_unique_ids_ar - // If the `UniqueIds` parameter is set, each read transaction has an ID that is unique among - // all in-flight read transactions, or all read transactions with a given ID target the same - // master port as all read transactions with the same ID, or both. This means that the - // signals that are driven by the ID counters if this parameter is not set can instead be - // derived from existing signals. The ID counters can therefore be omitted. - assign lookup_ar_select = slv_ar_chan_select.ar_select; - assign ar_select_occupied = 1'b0; - assign ar_id_cnt_full = 1'b0; - end else begin : gen_ar_id_counter - axi_demux_id_counters #( - .AxiIdBits ( AxiLookBits ), - .CounterWidth ( IdCounterWidth ), - .mst_port_select_t ( select_t ) - ) i_ar_id_counter ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .lookup_axi_id_i ( slv_ar_chan_select.ar_chan.id[0+:AxiLookBits] ), - .lookup_mst_select_o ( lookup_ar_select ), - .lookup_mst_select_occupied_o ( ar_select_occupied ), - .full_o ( ar_id_cnt_full ), - .inject_axi_id_i ( slv_aw_chan_select.aw_chan.id[0+:AxiLookBits] ), - .inject_i ( atop_inject ), - .push_axi_id_i ( slv_ar_chan_select.ar_chan.id[0+:AxiLookBits] ), - .push_mst_select_i ( slv_ar_chan_select.ar_select ), - .push_i ( ar_push ), - .pop_axi_id_i ( slv_r_chan.id[0+:AxiLookBits] ), - .pop_i ( slv_r_valid & slv_r_ready & slv_r_chan.last ) - ); - end - - //-------------------------------------- - // R Channel - //-------------------------------------- - // optional spill register - spill_register #( - .T ( r_chan_t ), - .Bypass ( ~SpillR ) - ) i_r_spill_reg ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .valid_i ( slv_r_valid ), - .ready_o ( slv_r_ready ), - .data_i ( slv_r_chan ), - .valid_o ( slv_resp_o.r_valid ), - .ready_i ( slv_req_i.r_ready ), - .data_o ( slv_resp_o.r ) - ); - - // Arbitration of the different r responses - rr_arb_tree #( - .NumIn ( NoMstPorts ), - .DataType ( r_chan_t ), - .AxiVldRdy( 1'b1 ), - .LockIn ( 1'b1 ) - ) i_r_mux ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .flush_i( 1'b0 ), - .rr_i ( '0 ), - .req_i ( mst_r_valids ), - .gnt_o ( mst_r_readies ), - .data_i ( mst_r_chans ), - .gnt_i ( slv_r_ready ), - .req_o ( slv_r_valid ), - .data_o ( slv_r_chan ), - .idx_o ( ) - ); - - assign ar_ready = ar_valid & mst_resps_i[slv_ar_chan_select.ar_select].ar_ready; - assign aw_ready = aw_valid & mst_resps_i[slv_aw_chan_select.aw_select].aw_ready; - - // process that defines the individual demuxes and assignments for the arbitration - // as mst_reqs_o has to be drivem from the same always comb block! - always_comb begin - // default assignments - mst_reqs_o = '0; - slv_w_ready = 1'b0; - w_fifo_pop = 1'b0; - - for (int unsigned i = 0; i < NoMstPorts; i++) begin - // AW channel - mst_reqs_o[i].aw = slv_aw_chan_select.aw_chan; - mst_reqs_o[i].aw_valid = 1'b0; - if (aw_valid && (slv_aw_chan_select.aw_select == i)) begin - mst_reqs_o[i].aw_valid = 1'b1; - end - - // W channel - mst_reqs_o[i].w = slv_w_chan; - mst_reqs_o[i].w_valid = 1'b0; - if (!w_fifo_empty && (w_select == i)) begin - mst_reqs_o[i].w_valid = slv_w_valid; - slv_w_ready = mst_resps_i[i].w_ready; - w_fifo_pop = slv_w_valid & mst_resps_i[i].w_ready & slv_w_chan.last; - end - - // B channel - mst_reqs_o[i].b_ready = mst_b_readies[i]; - - // AR channel - mst_reqs_o[i].ar = slv_ar_chan_select.ar_chan; - mst_reqs_o[i].ar_valid = 1'b0; - if (ar_valid && (slv_ar_chan_select.ar_select == i)) begin - mst_reqs_o[i].ar_valid = 1'b1; - end - - // R channel - mst_reqs_o[i].r_ready = mst_r_readies[i]; - end - end - // unpack the response B and R channels for the arbitration - for (genvar i = 0; i < NoMstPorts; i++) begin : gen_b_channels - assign mst_b_chans[i] = mst_resps_i[i].b; - assign mst_b_valids[i] = mst_resps_i[i].b_valid; - assign mst_r_chans[i] = mst_resps_i[i].r; - assign mst_r_valids[i] = mst_resps_i[i].r_valid; - end - - -// Validate parameters. -// pragma translate_off -`ifndef VERILATOR -`ifndef XILINX_SIMULATOR - initial begin: validate_params - no_mst_ports: assume (NoMstPorts > 0) else - $fatal(1, "The Number of slaves (NoMstPorts) has to be at least 1"); - AXI_ID_BITS: assume (AxiIdWidth >= AxiLookBits) else - $fatal(1, "AxiIdBits has to be equal or smaller than AxiIdWidth."); - end - default disable iff (!rst_ni); - aw_select: assume property( @(posedge clk_i) (slv_req_i.aw_valid |-> - (slv_aw_select_i < NoMstPorts))) else - $fatal(1, "slv_aw_select_i is %d: AW has selected a slave that is not defined.\ - NoMstPorts: %d", slv_aw_select_i, NoMstPorts); - ar_select: assume property( @(posedge clk_i) (slv_req_i.ar_valid |-> - (slv_ar_select_i < NoMstPorts))) else - $fatal(1, "slv_ar_select_i is %d: AR has selected a slave that is not defined.\ - NoMstPorts: %d", slv_ar_select_i, NoMstPorts); - aw_valid_stable: assert property( @(posedge clk_i) (aw_valid && !aw_ready) |=> aw_valid) else - $fatal(1, "aw_valid was deasserted, when aw_ready = 0 in last cycle."); - ar_valid_stable: assert property( @(posedge clk_i) - (ar_valid && !ar_ready) |=> ar_valid) else - $fatal(1, "ar_valid was deasserted, when ar_ready = 0 in last cycle."); - aw_stable: assert property( @(posedge clk_i) (aw_valid && !aw_ready) - |=> $stable(slv_aw_chan_select)) else - $fatal(1, "slv_aw_chan_select unstable with valid set."); - ar_stable: assert property( @(posedge clk_i) (ar_valid && !ar_ready) - |=> $stable(slv_ar_chan_select)) else - $fatal(1, "slv_aw_chan_select unstable with valid set."); - internal_ar_select: assert property( @(posedge clk_i) - (ar_valid |-> slv_ar_chan_select.ar_select < NoMstPorts)) - else $fatal(1, "slv_ar_chan_select.ar_select illegal while ar_valid."); - internal_aw_select: assert property( @(posedge clk_i) - (aw_valid |-> slv_aw_chan_select.aw_select < NoMstPorts)) - else $fatal(1, "slv_aw_chan_select.aw_select illegal while aw_valid."); - `ASSUME(NoAtopAllowed, !AtopSupport && slv_req_i.aw_valid |-> slv_req_i.aw.atop == '0) -`endif -`endif -// pragma translate_on - end -endmodule - -module axi_demux_id_counters #( - // the lower bits of the AXI ID that should be considered, results in 2**AXI_ID_BITS counters - parameter int unsigned AxiIdBits = 2, - parameter int unsigned CounterWidth = 4, - parameter type mst_port_select_t = logic -) ( - input clk_i, // Clock - input rst_ni, // Asynchronous reset active low - // lookup - input logic [AxiIdBits-1:0] lookup_axi_id_i, - output mst_port_select_t lookup_mst_select_o, - output logic lookup_mst_select_occupied_o, - // push - output logic full_o, - input logic [AxiIdBits-1:0] push_axi_id_i, - input mst_port_select_t push_mst_select_i, - input logic push_i, - // inject ATOPs in AR channel - input logic [AxiIdBits-1:0] inject_axi_id_i, - input logic inject_i, - // pop - input logic [AxiIdBits-1:0] pop_axi_id_i, - input logic pop_i -); - localparam int unsigned NoCounters = 2**AxiIdBits; - typedef logic [CounterWidth-1:0] cnt_t; - - // registers, each gets loaded when push_en[i] - mst_port_select_t [NoCounters-1:0] mst_select_q; - - // counter signals - logic [NoCounters-1:0] push_en, inject_en, pop_en, occupied, cnt_full; + axi_req_t slv_req_cut; + axi_resp_t slv_resp_cut; + + logic slv_aw_ready_chan, slv_aw_ready_sel; + logic slv_aw_valid_chan, slv_aw_valid_sel; + + logic slv_ar_ready_chan, slv_ar_ready_sel; + logic slv_ar_valid_chan, slv_ar_valid_sel; + + select_t slv_aw_select, slv_ar_select; + + spill_register #( + .T ( aw_chan_t ), + .Bypass ( ~SpillAw ) + ) i_aw_spill_reg ( + .clk_i, + .rst_ni, + .valid_i ( slv_req_i.aw_valid ), + .ready_o ( slv_aw_ready_chan ), + .data_i ( slv_req_i.aw ), + .valid_o ( slv_aw_valid_chan ), + .ready_i ( slv_resp_cut.aw_ready ), + .data_o ( slv_req_cut.aw ) + ); + spill_register #( + .T ( select_t ), + .Bypass ( ~SpillAw ) + ) i_aw_select_spill_reg ( + .clk_i, + .rst_ni, + .valid_i ( slv_req_i.aw_valid ), + .ready_o ( slv_aw_ready_sel ), + .data_i ( slv_aw_select_i ), + .valid_o ( slv_aw_valid_sel ), + .ready_i ( slv_resp_cut.aw_ready ), + .data_o ( slv_aw_select ) + ); - //----------------------------------- - // Lookup - //----------------------------------- - assign lookup_mst_select_o = mst_select_q[lookup_axi_id_i]; - assign lookup_mst_select_occupied_o = occupied[lookup_axi_id_i]; - //----------------------------------- - // Push and Pop - //----------------------------------- - assign push_en = (push_i) ? (1 << push_axi_id_i) : '0; - assign inject_en = (inject_i) ? (1 << inject_axi_id_i) : '0; - assign pop_en = (pop_i) ? (1 << pop_axi_id_i) : '0; - assign full_o = |cnt_full; - // counters - for (genvar i = 0; i < NoCounters; i++) begin : gen_counters - logic cnt_en, cnt_down, overflow; - cnt_t cnt_delta, in_flight; - always_comb begin - unique case ({push_en[i], inject_en[i], pop_en[i]}) - 3'b001 : begin // pop_i = -1 - cnt_en = 1'b1; - cnt_down = 1'b1; - cnt_delta = cnt_t'(1); - end - 3'b010 : begin // inject_i = +1 - cnt_en = 1'b1; - cnt_down = 1'b0; - cnt_delta = cnt_t'(1); - end - // 3'b011, inject_i & pop_i = 0 --> use default - 3'b100 : begin // push_i = +1 - cnt_en = 1'b1; - cnt_down = 1'b0; - cnt_delta = cnt_t'(1); - end - // 3'b101, push_i & pop_i = 0 --> use default - 3'b110 : begin // push_i & inject_i = +2 - cnt_en = 1'b1; - cnt_down = 1'b0; - cnt_delta = cnt_t'(2); - end - 3'b111 : begin // push_i & inject_i & pop_i = +1 - cnt_en = 1'b1; - cnt_down = 1'b0; - cnt_delta = cnt_t'(1); - end - default : begin // do nothing to the counters - cnt_en = 1'b0; - cnt_down = 1'b0; - cnt_delta = cnt_t'(0); - end - endcase - end + assign slv_resp_o.aw_ready = slv_aw_ready_chan & slv_aw_ready_sel; + assign slv_req_cut.aw_valid = slv_aw_valid_chan & slv_aw_valid_sel; + + spill_register #( + .T ( w_chan_t ), + .Bypass ( ~SpillW ) + ) i_w_spill_reg ( + .clk_i, + .rst_ni, + .valid_i ( slv_req_i.w_valid ), + .ready_o ( slv_resp_o.w_ready ), + .data_i ( slv_req_i.w ), + .valid_o ( slv_req_cut.w_valid ), + .ready_i ( slv_resp_cut.w_ready ), + .data_o ( slv_req_cut.w ) + ); + spill_register #( + .T ( ar_chan_t ), + .Bypass ( ~SpillAr ) + ) i_ar_spill_reg ( + .clk_i, + .rst_ni, + .valid_i ( slv_req_i.ar_valid ), + .ready_o ( slv_ar_ready_chan ), + .data_i ( slv_req_i.ar ), + .valid_o ( slv_ar_valid_chan ), + .ready_i ( slv_resp_cut.ar_ready ), + .data_o ( slv_req_cut.ar ) + ); + spill_register #( + .T ( select_t ), + .Bypass ( ~SpillAr ) + ) i_ar_sel_spill_reg ( + .clk_i, + .rst_ni, + .valid_i ( slv_req_i.ar_valid ), + .ready_o ( slv_ar_ready_sel ), + .data_i ( slv_ar_select_i ), + .valid_o ( slv_ar_valid_sel ), + .ready_i ( slv_resp_cut.ar_ready ), + .data_o ( slv_ar_select ) + ); - delta_counter #( - .WIDTH ( CounterWidth ), - .STICKY_OVERFLOW ( 1'b0 ) - ) i_in_flight_cnt ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .clear_i ( 1'b0 ), - .en_i ( cnt_en ), - .load_i ( 1'b0 ), - .down_i ( cnt_down ), - .delta_i ( cnt_delta ), - .d_i ( '0 ), - .q_o ( in_flight ), - .overflow_o ( overflow ) - ); - assign occupied[i] = |in_flight; - assign cnt_full[i] = overflow | (&in_flight); + assign slv_resp_o.ar_ready = slv_ar_ready_chan & slv_ar_ready_sel; + assign slv_req_cut.ar_valid = slv_ar_valid_chan & slv_ar_valid_sel; + + spill_register #( + .T ( b_chan_t ), + .Bypass ( ~SpillB ) + ) i_b_spill_reg ( + .clk_i, + .rst_ni, + .valid_i ( slv_resp_cut.b_valid ), + .ready_o ( slv_req_cut.b_ready ), + .data_i ( slv_resp_cut.b ), + .valid_o ( slv_resp_o.b_valid ), + .ready_i ( slv_req_i.b_ready ), + .data_o ( slv_resp_o.b ) + ); + spill_register #( + .T ( r_chan_t ), + .Bypass ( ~SpillR ) + ) i_r_spill_reg ( + .clk_i, + .rst_ni, + .valid_i ( slv_resp_cut.r_valid ), + .ready_o ( slv_req_cut.r_ready ), + .data_i ( slv_resp_cut.r ), + .valid_o ( slv_resp_o.r_valid ), + .ready_i ( slv_req_i.r_ready ), + .data_o ( slv_resp_o.r ) + ); - // holds the selection signal for this id - `FFLARN(mst_select_q[i], push_mst_select_i, push_en[i], '0, clk_i, rst_ni) + axi_demux_simple #( + .AxiIdWidth ( AxiIdWidth ), + .AtopSupport( AtopSupport ), + .axi_req_t ( axi_req_t ), + .axi_resp_t ( axi_resp_t ), + .NoMstPorts ( NoMstPorts ), + .MaxTrans ( MaxTrans ), + .AxiLookBits( AxiLookBits ), + .UniqueIds ( UniqueIds ) + ) i_demux_simple ( + .clk_i, + .rst_ni, + .test_i, + + .slv_req_i ( slv_req_cut ), + .slv_aw_select_i ( slv_aw_select ), + .slv_ar_select_i ( slv_ar_select ), + .slv_resp_o ( slv_resp_cut ), + .mst_reqs_o ( mst_reqs_o ), + .mst_resps_i ( mst_resps_i ) + ); -// pragma translate_off -`ifndef VERILATOR -`ifndef XILINX_SIMULATOR - // Validate parameters. - cnt_underflow: assert property( - @(posedge clk_i) disable iff (~rst_ni) (pop_en[i] |=> !overflow)) else - $fatal(1, "axi_demux_id_counters > Counter: %0d underflowed.\ - The reason is probably a faulty AXI response.", i); -`endif -`endif -// pragma translate_on - end endmodule // interface wrapper @@ -785,7 +223,6 @@ module axi_demux_intf #( parameter int unsigned MAX_TRANS = 32'd8, parameter int unsigned AXI_LOOK_BITS = 32'd3, parameter bit UNIQUE_IDS = 1'b0, - parameter bit FALL_THROUGH = 1'b0, parameter bit SPILL_AW = 1'b1, parameter bit SPILL_W = 1'b0, parameter bit SPILL_B = 1'b0, @@ -844,7 +281,6 @@ module axi_demux_intf #( .MaxTrans ( MAX_TRANS ), .AxiLookBits ( AXI_LOOK_BITS ), .UniqueIds ( UNIQUE_IDS ), - .FallThrough ( FALL_THROUGH ), .SpillAw ( SPILL_AW ), .SpillW ( SPILL_W ), .SpillB ( SPILL_B ), diff --git a/src/axi_demux_simple.sv b/src/axi_demux_simple.sv new file mode 100644 index 000000000..33851d4c3 --- /dev/null +++ b/src/axi_demux_simple.sv @@ -0,0 +1,633 @@ +// Copyright (c) 2019 ETH Zurich and University of Bologna. +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the "License"); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. +// +// Authors: +// - Wolfgang Roenninger +// - Michael Rogenmoser +// - Thomas Benz +// - Andreas Kurth + +`include "common_cells/assertions.svh" +`include "common_cells/registers.svh" +`include "axi/assign.svh" + +`ifdef QUESTA +// Derive `TARGET_VSIM`, which is used for tool-specific workarounds in this file, from `QUESTA`, +// which is automatically set in Questa. +`define TARGET_VSIM +`endif + +/// Demultiplex one AXI4+ATOP slave port to multiple AXI4+ATOP master ports. +/// +/// The AW and AR slave channels each have a `select` input to determine to which master port the +/// current request is sent. The `select` can, for example, be driven by an address decoding module +/// to map address ranges to different AXI slaves. +/// +/// ## Design overview +/// +/// ![Block diagram](module.axi_demux.png "Block diagram") +/// +/// Beats on the W channel are routed by demultiplexer according to the selection for the +/// corresponding AW beat. This relies on the AXI property that W bursts must be sent in the same +/// order as AW beats and beats from different W bursts may not be interleaved. +/// +/// Beats on the B and R channel are multiplexed from the master ports to the slave port with +/// a round-robin arbitration tree. +module axi_demux_simple #( + parameter int unsigned AxiIdWidth = 32'd0, + parameter bit AtopSupport = 1'b1, + parameter type axi_req_t = logic, + parameter type axi_resp_t = logic, + parameter int unsigned NoMstPorts = 32'd0, + parameter int unsigned MaxTrans = 32'd8, + parameter int unsigned AxiLookBits = 32'd3, + parameter bit UniqueIds = 1'b0, + // Dependent parameters, DO NOT OVERRIDE! + parameter int unsigned SelectWidth = (NoMstPorts > 32'd1) ? $clog2(NoMstPorts) : 32'd1, + parameter type select_t = logic [SelectWidth-1:0] +) ( + input logic clk_i, + input logic rst_ni, + input logic test_i, + // Slave Port + input axi_req_t slv_req_i, + input select_t slv_aw_select_i, + input select_t slv_ar_select_i, + output axi_resp_t slv_resp_o, + // Master Ports + output axi_req_t [NoMstPorts-1:0] mst_reqs_o, + input axi_resp_t [NoMstPorts-1:0] mst_resps_i +); + + localparam int unsigned IdCounterWidth = cf_math_pkg::idx_width(MaxTrans); + typedef logic [IdCounterWidth-1:0] id_cnt_t; + + // pass through if only one master port + if (NoMstPorts == 32'h1) begin : gen_no_demux + `AXI_ASSIGN_REQ_STRUCT(mst_reqs_o[0], slv_req_i) + `AXI_ASSIGN_RESP_STRUCT(slv_resp_o, mst_resps_i[0]) + end else begin + + //-------------------------------------- + //-------------------------------------- + // Signal Declarations + //-------------------------------------- + //-------------------------------------- + + //-------------------------------------- + // Write Transaction + //-------------------------------------- + + // Register which locks the AW valid signal + logic lock_aw_valid_d, lock_aw_valid_q, load_aw_lock; + logic aw_valid, aw_ready; + + // AW ID counter + select_t lookup_aw_select; + logic aw_select_occupied, aw_id_cnt_full; + // Upon an ATOP load, inject IDs from the AW into the AR channel + logic atop_inject; + + // W select counter: stores the decision to which master W beats should go + select_t w_select, w_select_q; + logic w_select_valid; + id_cnt_t w_open; + logic w_cnt_up, w_cnt_down; + + // B channles input into the arbitration + logic [NoMstPorts-1:0] mst_b_valids, mst_b_readies; + + //-------------------------------------- + // Read Transaction + //-------------------------------------- + + // AR ID counter + select_t lookup_ar_select; + logic ar_select_occupied, ar_id_cnt_full; + logic ar_push; + + // Register which locks the AR valid signel + logic lock_ar_valid_d, lock_ar_valid_q, load_ar_lock; + logic ar_valid, ar_ready; + + logic [NoMstPorts-1:0] mst_r_valids, mst_r_readies; + + + + + + + + //-------------------------------------- + // Channel Control + //-------------------------------------- + //-------------------------------------- + + //-------------------------------------- + // AW Channel + //-------------------------------------- + + // Control of the AW handshake + always_comb begin + // AXI Handshakes + slv_resp_o.aw_ready = 1'b0; + aw_valid = 1'b0; + // `lock_aw_valid`, used to be protocol conform as it is not allowed to deassert + // a valid if there was no corresponding ready. As this process has to be able to inject + // an AXI ID into the counter of the AR channel on an ATOP, there could be a case where + // this process waits on `aw_ready` but in the mean time on the AR channel the counter gets + // full. + lock_aw_valid_d = lock_aw_valid_q; + load_aw_lock = 1'b0; + // AW ID counter and W FIFO + w_cnt_up = 1'b0; + // ATOP injection into ar counter + atop_inject = 1'b0; + // we had an arbitration decision, the valid is locked, wait for the transaction + if (lock_aw_valid_q) begin + aw_valid = 1'b1; + // transaction + if (aw_ready) begin + slv_resp_o.aw_ready = 1'b1; + lock_aw_valid_d = 1'b0; + load_aw_lock = 1'b1; + // inject the ATOP if necessary + atop_inject = slv_req_i.aw.atop[axi_pkg::ATOP_R_RESP] & AtopSupport; + end + end else begin + // An AW can be handled if `i_aw_id_counter` and `i_counter_open_w` are not full. An ATOP that + // requires an R response can be handled if additionally `i_ar_id_counter` is not full (this + // only applies if ATOPs are supported at all). + if (!aw_id_cnt_full && (w_open != {IdCounterWidth{1'b1}}) && + (!(ar_id_cnt_full && slv_req_i.aw.atop[axi_pkg::ATOP_R_RESP]) || + !AtopSupport)) begin + // There is a valid AW vector make the id lookup and go further, if it passes. + // Also stall if previous transmitted AWs still have active W's in flight. + // This prevents deadlocking of the W channel. The counters are there for the + // Handling of the B responses. + if (slv_req_i.aw_valid && + ((w_open == '0) || (w_select == slv_aw_select_i)) && + (!aw_select_occupied || (slv_aw_select_i == lookup_aw_select))) begin + // connect the handshake + aw_valid = 1'b1; + // push arbitration to the W FIFO regardless, do not wait for the AW transaction + w_cnt_up = 1'b1; + // on AW transaction + if (aw_ready) begin + slv_resp_o.aw_ready = 1'b1; + atop_inject = slv_req_i.aw.atop[axi_pkg::ATOP_R_RESP] & AtopSupport; + // no AW transaction this cycle, lock the decision + end else begin + lock_aw_valid_d = 1'b1; + load_aw_lock = 1'b1; + end + end + end + end + end + + // lock the valid signal, as the selection gets pushed into the W FIFO on first assertion, + // prevent further pushing + `FFLARN(lock_aw_valid_q, lock_aw_valid_d, load_aw_lock, '0, clk_i, rst_ni) + + if (UniqueIds) begin : gen_unique_ids_aw + // If the `UniqueIds` parameter is set, each write transaction has an ID that is unique among + // all in-flight write transactions, or all write transactions with a given ID target the same + // master port as all write transactions with the same ID, or both. This means that the + // signals that are driven by the ID counters if this parameter is not set can instead be + // derived from existing signals. The ID counters can therefore be omitted. + assign lookup_aw_select = slv_aw_select_i; + assign aw_select_occupied = 1'b0; + assign aw_id_cnt_full = 1'b0; + end else begin : gen_aw_id_counter + axi_demux_id_counters #( + .AxiIdBits ( AxiLookBits ), + .CounterWidth ( IdCounterWidth ), + .mst_port_select_t ( select_t ) + ) i_aw_id_counter ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .lookup_axi_id_i ( slv_req_i.aw.id[0+:AxiLookBits] ), + .lookup_mst_select_o ( lookup_aw_select ), + .lookup_mst_select_occupied_o ( aw_select_occupied ), + .full_o ( aw_id_cnt_full ), + .inject_axi_id_i ( '0 ), + .inject_i ( 1'b0 ), + .push_axi_id_i ( slv_req_i.aw.id[0+:AxiLookBits] ), + .push_mst_select_i ( slv_aw_select_i ), + .push_i ( w_cnt_up ), + .pop_axi_id_i ( slv_resp_o.b.id[0+:AxiLookBits] ), + .pop_i ( slv_resp_o.b_valid & slv_req_i.b_ready ) + ); + // pop from ID counter on outward transaction + end + + // This counter steers the demultiplexer of the W channel. + // `w_select` determines, which handshaking is connected. + // AWs are only forwarded, if the counter is empty, or `w_select_q` is the same as + // `slv_aw_select_i`. + counter #( + .WIDTH ( IdCounterWidth ), + .STICKY_OVERFLOW ( 1'b0 ) + ) i_counter_open_w ( + .clk_i, + .rst_ni, + .clear_i ( 1'b0 ), + .en_i ( w_cnt_up ^ w_cnt_down ), + .load_i ( 1'b0 ), + .down_i ( w_cnt_down ), + .d_i ( '0 ), + .q_o ( w_open ), + .overflow_o ( /*not used*/ ) + ); + + `FFLARN(w_select_q, slv_aw_select_i, w_cnt_up, select_t'(0), clk_i, rst_ni) + assign w_select = (|w_open) ? w_select_q : slv_aw_select_i; + assign w_select_valid = w_cnt_up | (|w_open); + + //-------------------------------------- + // W Channel + //-------------------------------------- + + //-------------------------------------- + // B Channel + //-------------------------------------- + logic [cf_math_pkg::idx_width(NoMstPorts)-1:0] b_idx; + + // Arbitration of the different B responses + rr_arb_tree #( + .NumIn ( NoMstPorts ), + .DataType ( logic ), + .AxiVldRdy( 1'b1 ), + .LockIn ( 1'b1 ) + ) i_b_mux ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .flush_i( 1'b0 ), + .rr_i ( '0 ), + .req_i ( mst_b_valids ), + .gnt_o ( mst_b_readies ), + .data_i ( '0 ), + .gnt_i ( slv_req_i.b_ready ), + .req_o ( slv_resp_o.b_valid ), + .data_o ( ), + .idx_o ( b_idx ) + ); + + always_comb begin + if (slv_resp_o.b_valid) begin + `AXI_SET_B_STRUCT(slv_resp_o.b, mst_resps_i[b_idx].b) + end else begin + slv_resp_o.b = '0; + end + end + + //-------------------------------------- + // AR Channel + //-------------------------------------- + + // control of the AR handshake + always_comb begin + // AXI Handshakes + slv_resp_o.ar_ready = 1'b0; + ar_valid = 1'b0; + // `lock_ar_valid`: Used to be protocol conform as it is not allowed to deassert `ar_valid` + // if there was no corresponding `ar_ready`. There is the possibility that an injection + // of a R response from an `atop` from the AW channel can change the occupied flag of the + // `i_ar_id_counter`, even if it was previously empty. This FF prevents the deassertion. + lock_ar_valid_d = lock_ar_valid_q; + load_ar_lock = 1'b0; + // AR id counter + ar_push = 1'b0; + // The process had an arbitration decision in a previous cycle, the valid is locked, + // wait for the AR transaction. + if (lock_ar_valid_q) begin + ar_valid = 1'b1; + // transaction + if (ar_ready) begin + slv_resp_o.ar_ready = 1'b1; + ar_push = 1'b1; + lock_ar_valid_d = 1'b0; + load_ar_lock = 1'b1; + end + end else begin + // The process can start handling AR transaction if `i_ar_id_counter` has space. + if (!ar_id_cnt_full) begin + // There is a valid AR, so look the ID up. + if (slv_req_i.ar_valid && (!ar_select_occupied || + (slv_ar_select_i == lookup_ar_select))) begin + // connect the AR handshake + ar_valid = 1'b1; + // on transaction + if (ar_ready) begin + slv_resp_o.ar_ready = 1'b1; + ar_push = 1'b1; + // no transaction this cycle, lock the valid decision! + end else begin + lock_ar_valid_d = 1'b1; + load_ar_lock = 1'b1; + end + end + end + end + end + + // this ff is needed so that ar does not get de-asserted if an atop gets injected + `FFLARN(lock_ar_valid_q, lock_ar_valid_d, load_ar_lock, '0, clk_i, rst_ni) + + if (UniqueIds) begin : gen_unique_ids_ar + // If the `UniqueIds` parameter is set, each read transaction has an ID that is unique among + // all in-flight read transactions, or all read transactions with a given ID target the same + // master port as all read transactions with the same ID, or both. This means that the + // signals that are driven by the ID counters if this parameter is not set can instead be + // derived from existing signals. The ID counters can therefore be omitted. + assign lookup_ar_select = slv_ar_select_i; + assign ar_select_occupied = 1'b0; + assign ar_id_cnt_full = 1'b0; + end else begin : gen_ar_id_counter + axi_demux_id_counters #( + .AxiIdBits ( AxiLookBits ), + .CounterWidth ( IdCounterWidth ), + .mst_port_select_t ( select_t ) + ) i_ar_id_counter ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .lookup_axi_id_i ( slv_req_i.ar.id[0+:AxiLookBits] ), + .lookup_mst_select_o ( lookup_ar_select ), + .lookup_mst_select_occupied_o ( ar_select_occupied ), + .full_o ( ar_id_cnt_full ), + .inject_axi_id_i ( slv_req_i.aw.id[0+:AxiLookBits] ), + .inject_i ( atop_inject ), + .push_axi_id_i ( slv_req_i.ar.id[0+:AxiLookBits] ), + .push_mst_select_i ( slv_ar_select_i ), + .push_i ( ar_push ), + .pop_axi_id_i ( slv_resp_o.r.id[0+:AxiLookBits] ), + .pop_i ( slv_resp_o.r_valid & slv_req_i.r_ready & slv_resp_o.r.last ) + ); + end + + //-------------------------------------- + // R Channel + //-------------------------------------- + + logic [cf_math_pkg::idx_width(NoMstPorts)-1:0] r_idx; + + // Arbitration of the different r responses + rr_arb_tree #( + .NumIn ( NoMstPorts ), + .DataType ( logic ), + .AxiVldRdy( 1'b1 ), + .LockIn ( 1'b1 ) + ) i_r_mux ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .flush_i( 1'b0 ), + .rr_i ( '0 ), + .req_i ( mst_r_valids ), + .gnt_o ( mst_r_readies ), + .data_i ( '0 ), + .gnt_i ( slv_req_i.r_ready ), + .req_o ( slv_resp_o.r_valid ), + .data_o (), + .idx_o ( r_idx ) + ); + + always_comb begin + if (slv_resp_o.r_valid) begin + `AXI_SET_R_STRUCT(slv_resp_o.r, mst_resps_i[r_idx].r) + end else begin + slv_resp_o.r = '0; + end + end + + assign ar_ready = ar_valid & mst_resps_i[slv_ar_select_i].ar_ready; + assign aw_ready = aw_valid & mst_resps_i[slv_aw_select_i].aw_ready; + + // process that defines the individual demuxes and assignments for the arbitration + // as mst_reqs_o has to be drivem from the same always comb block! + always_comb begin + // default assignments + mst_reqs_o = '0; + slv_resp_o.w_ready = 1'b0; + w_cnt_down = 1'b0; + + for (int unsigned i = 0; i < NoMstPorts; i++) begin + // AW channel + mst_reqs_o[i].aw = slv_req_i.aw; + mst_reqs_o[i].aw_valid = 1'b0; + if (aw_valid && (slv_aw_select_i == i)) begin + mst_reqs_o[i].aw_valid = 1'b1; + end + + // W channel + mst_reqs_o[i].w = slv_req_i.w; + mst_reqs_o[i].w_valid = 1'b0; + if (w_select_valid && (w_select == i)) begin + mst_reqs_o[i].w_valid = slv_req_i.w_valid; + slv_resp_o.w_ready = mst_resps_i[i].w_ready; + w_cnt_down = slv_req_i.w_valid & mst_resps_i[i].w_ready & slv_req_i.w.last; + end + + // B channel + mst_reqs_o[i].b_ready = mst_b_readies[i]; + + // AR channel + mst_reqs_o[i].ar = slv_req_i.ar; + mst_reqs_o[i].ar_valid = 1'b0; + if (ar_valid && (slv_ar_select_i == i)) begin + mst_reqs_o[i].ar_valid = 1'b1; + end + + // R channel + mst_reqs_o[i].r_ready = mst_r_readies[i]; + end + end + // unpack the response B and R channels for the arbitration + for (genvar i = 0; i < NoMstPorts; i++) begin : gen_b_channels + // assign mst_b_chans[i] = mst_resps_i[i].b; + assign mst_b_valids[i] = mst_resps_i[i].b_valid; + // assign mst_r_chans[i] = mst_resps_i[i].r; + assign mst_r_valids[i] = mst_resps_i[i].r_valid; + end + +// Validate parameters. +// pragma translate_off +`ifndef VERILATOR + initial begin: validate_params + no_mst_ports: assume (NoMstPorts > 0) else + $fatal(1, "The Number of slaves (NoMstPorts) has to be at least 1"); + AXI_ID_BITS: assume (AxiIdWidth >= AxiLookBits) else + $fatal(1, "AxiIdBits has to be equal or smaller than AxiIdWidth."); + end +`ifndef XILINX_SIMULATOR + default disable iff (!rst_ni); +`endif + aw_select: assume property( @(posedge clk_i) (slv_req_i.aw_valid |-> + (slv_aw_select_i < NoMstPorts))) else + $fatal(1, "slv_aw_select_i is %d: AW has selected a slave that is not defined.\ + NoMstPorts: %d", slv_aw_select_i, NoMstPorts); + ar_select: assume property( @(posedge clk_i) (slv_req_i.ar_valid |-> + (slv_ar_select_i < NoMstPorts))) else + $fatal(1, "slv_ar_select_i is %d: AR has selected a slave that is not defined.\ + NoMstPorts: %d", slv_ar_select_i, NoMstPorts); + aw_valid_stable: assert property( @(posedge clk_i) (aw_valid && !aw_ready) |=> aw_valid) else + $fatal(1, "aw_valid was deasserted, when aw_ready = 0 in last cycle."); + ar_valid_stable: assert property( @(posedge clk_i) + (ar_valid && !ar_ready) |=> ar_valid) else + $fatal(1, "ar_valid was deasserted, when ar_ready = 0 in last cycle."); + slv_aw_chan_stable: assert property( @(posedge clk_i) (aw_valid && !aw_ready) + |=> $stable(slv_req_i.aw)) else + $fatal(1, "slv_aw_chan unstable with valid set."); + slv_aw_select_stable: assert property( @(posedge clk_i) (aw_valid && !aw_ready) + |=> $stable(slv_aw_select_i)) else + $fatal(1, "slv_aw_select_i unstable with valid set."); + slv_ar_chan_stable: assert property( @(posedge clk_i) (ar_valid && !ar_ready) + |=> $stable(slv_req_i.ar)) else + $fatal(1, "slv_ar_chan unstable with valid set."); + slv_ar_select_stable: assert property( @(posedge clk_i) (ar_valid && !ar_ready) + |=> $stable(slv_ar_select_i)) else + $fatal(1, "slv_ar_select_i unstable with valid set."); + internal_ar_select: assert property( @(posedge clk_i) + (ar_valid |-> slv_ar_select_i < NoMstPorts)) + else $fatal(1, "slv_ar_select_i illegal while ar_valid."); + internal_aw_select: assert property( @(posedge clk_i) + (aw_valid |-> slv_aw_select_i < NoMstPorts)) + else $fatal(1, "slv_aw_select_i illegal while aw_valid."); + w_underflow: assert property( @(posedge clk_i) + ((w_open == '0) && (w_cnt_up ^ w_cnt_down) |-> !w_cnt_down)) else + $fatal(1, "W counter underflowed!"); + `ASSUME(NoAtopAllowed, !AtopSupport && slv_req_i.aw_valid |-> slv_req_i.aw.atop == '0) +`endif +// pragma translate_on + end +endmodule + + +module axi_demux_id_counters #( + // the lower bits of the AXI ID that should be considered, results in 2**AXI_ID_BITS counters + parameter int unsigned AxiIdBits = 2, + parameter int unsigned CounterWidth = 4, + parameter type mst_port_select_t = logic +) ( + input clk_i, // Clock + input rst_ni, // Asynchronous reset active low + // lookup + input logic [AxiIdBits-1:0] lookup_axi_id_i, + output mst_port_select_t lookup_mst_select_o, + output logic lookup_mst_select_occupied_o, + // push + output logic full_o, + input logic [AxiIdBits-1:0] push_axi_id_i, + input mst_port_select_t push_mst_select_i, + input logic push_i, + // inject ATOPs in AR channel + input logic [AxiIdBits-1:0] inject_axi_id_i, + input logic inject_i, + // pop + input logic [AxiIdBits-1:0] pop_axi_id_i, + input logic pop_i +); + localparam int unsigned NoCounters = 2**AxiIdBits; + typedef logic [CounterWidth-1:0] cnt_t; + + // registers, each gets loaded when push_en[i] + mst_port_select_t [NoCounters-1:0] mst_select_q; + + // counter signals + logic [NoCounters-1:0] push_en, inject_en, pop_en, occupied, cnt_full; + + //----------------------------------- + // Lookup + //----------------------------------- + assign lookup_mst_select_o = mst_select_q[lookup_axi_id_i]; + assign lookup_mst_select_occupied_o = occupied[lookup_axi_id_i]; + //----------------------------------- + // Push and Pop + //----------------------------------- + assign push_en = (push_i) ? (1 << push_axi_id_i) : '0; + assign inject_en = (inject_i) ? (1 << inject_axi_id_i) : '0; + assign pop_en = (pop_i) ? (1 << pop_axi_id_i) : '0; + assign full_o = |cnt_full; + // counters + for (genvar i = 0; i < NoCounters; i++) begin : gen_counters + logic cnt_en, cnt_down, overflow; + cnt_t cnt_delta, in_flight; + always_comb begin + unique case ({push_en[i], inject_en[i], pop_en[i]}) + 3'b001 : begin // pop_i = -1 + cnt_en = 1'b1; + cnt_down = 1'b1; + cnt_delta = cnt_t'(1); + end + 3'b010 : begin // inject_i = +1 + cnt_en = 1'b1; + cnt_down = 1'b0; + cnt_delta = cnt_t'(1); + end + // 3'b011, inject_i & pop_i = 0 --> use default + 3'b100 : begin // push_i = +1 + cnt_en = 1'b1; + cnt_down = 1'b0; + cnt_delta = cnt_t'(1); + end + // 3'b101, push_i & pop_i = 0 --> use default + 3'b110 : begin // push_i & inject_i = +2 + cnt_en = 1'b1; + cnt_down = 1'b0; + cnt_delta = cnt_t'(2); + end + 3'b111 : begin // push_i & inject_i & pop_i = +1 + cnt_en = 1'b1; + cnt_down = 1'b0; + cnt_delta = cnt_t'(1); + end + default : begin // do nothing to the counters + cnt_en = 1'b0; + cnt_down = 1'b0; + cnt_delta = cnt_t'(0); + end + endcase + end + + delta_counter #( + .WIDTH ( CounterWidth ), + .STICKY_OVERFLOW ( 1'b0 ) + ) i_in_flight_cnt ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .clear_i ( 1'b0 ), + .en_i ( cnt_en ), + .load_i ( 1'b0 ), + .down_i ( cnt_down ), + .delta_i ( cnt_delta ), + .d_i ( '0 ), + .q_o ( in_flight ), + .overflow_o ( overflow ) + ); + assign occupied[i] = |in_flight; + assign cnt_full[i] = overflow | (&in_flight); + + // holds the selection signal for this id + `FFLARN(mst_select_q[i], push_mst_select_i, push_en[i], '0, clk_i, rst_ni) + +// pragma translate_off +`ifndef VERILATOR +`ifndef XSIM + // Validate parameters. + cnt_underflow: assert property( + @(posedge clk_i) disable iff (~rst_ni) (pop_en[i] |=> !overflow)) else + $fatal(1, "axi_demux_id_counters > Counter: %0d underflowed.\ + The reason is probably a faulty AXI response.", i); +`endif +`endif +// pragma translate_on + end +endmodule + diff --git a/src/axi_dumper.sv b/src/axi_dumper.sv new file mode 100644 index 000000000..c3031ea5c --- /dev/null +++ b/src/axi_dumper.sv @@ -0,0 +1,215 @@ +// Copyright (c) 2019 ETH Zurich, University of Bologna +// +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the "License"); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. +// +// Authors: +// - Michael Rogenmoser + +/// Simulation-Only dumper for AXI transactions +/// +/// This module writes all handshaked AXI beats to a log file. To use in simulation, +/// ensure `TARGET_SIMULATION` is defined. +module axi_dumper #( + parameter BusName = "axi_bus", + parameter bit LogAW = 1'b1, + parameter bit LogAR = 1'b1, + parameter bit LogW = 1'b0, + parameter bit LogB = 1'b0, + parameter bit LogR = 1'b0, + parameter type axi_req_t = logic, + parameter type axi_resp_t = logic +) ( + input logic clk_i, + input logic rst_ni, + input axi_req_t axi_req_i, + input axi_resp_t axi_resp_i +); + +`ifdef TARGET_SIMULATION + string fn; + integer f; + initial begin + #1; + $sformat(fn, "axi_trace_%s.log", BusName); + f = $fopen(fn, "w"); + $display("[Tracer] Logging axi accesses to %s", fn); + end + + always_ff @(posedge clk_i) begin : proc_tracer + automatic string aw_data [string]; + automatic string ar_data [string]; + automatic string w_data [string]; + automatic string b_data [string]; + automatic string r_data [string]; + + automatic string aw_string; + automatic string ar_string; + automatic string w_string; + automatic string b_string; + automatic string r_string; + + if (rst_ni) begin + aw_data = '{ + "type" : "\"AW\"", + "time" : $sformatf("%d", $time()), + "id" : $sformatf("0x%0x", axi_req_i.aw.id), + "addr" : $sformatf("0x%0x", axi_req_i.aw.addr), + "len" : $sformatf("0x%0x", axi_req_i.aw.len), + "size" : $sformatf("0x%0x", axi_req_i.aw.size), + "burst" : $sformatf("0x%0x", axi_req_i.aw.burst), + "lock" : $sformatf("0x%0x", axi_req_i.aw.lock), + "cache" : $sformatf("0x%0x", axi_req_i.aw.cache), + "prot" : $sformatf("0x%0x", axi_req_i.aw.prot), + "qos" : $sformatf("0x%0x", axi_req_i.aw.qos), + "region" : $sformatf("0x%0x", axi_req_i.aw.region), + "atop" : $sformatf("0x%0x", axi_req_i.aw.atop), + "user" : $sformatf("0x%0x", axi_req_i.aw.user) + }; + ar_data = '{ + "type" : "\"AR\"", + "time" : $sformatf("%d", $time()), + "id" : $sformatf("0x%0x", axi_req_i.ar.id), + "addr" : $sformatf("0x%0x", axi_req_i.ar.addr), + "len" : $sformatf("0x%0x", axi_req_i.ar.len), + "size" : $sformatf("0x%0x", axi_req_i.ar.size), + "burst" : $sformatf("0x%0x", axi_req_i.ar.burst), + "lock" : $sformatf("0x%0x", axi_req_i.ar.lock), + "cache" : $sformatf("0x%0x", axi_req_i.ar.cache), + "prot" : $sformatf("0x%0x", axi_req_i.ar.prot), + "qos" : $sformatf("0x%0x", axi_req_i.ar.qos), + "region" : $sformatf("0x%0x", axi_req_i.ar.region), + "user" : $sformatf("0x%0x", axi_req_i.ar.user) + }; + w_data = '{ + "type" : "\"W\"", + "time" : $sformatf("%d", $time()), + "data" : $sformatf("0x%0x", axi_req_i.w.data), + "strb" : $sformatf("0x%0x", axi_req_i.w.strb), + "last" : $sformatf("0x%0x", axi_req_i.w.last), + "user" : $sformatf("0x%0x", axi_req_i.w.user) + }; + b_data = '{ + "type" : "\"B\"", + "time" : $sformatf("%d", $time()), + "id" : $sformatf("0x%0x", axi_resp_i.b.id), + "resp" : $sformatf("0x%0x", axi_resp_i.b.resp), + "user" : $sformatf("0x%0x", axi_resp_i.b.user) + }; + r_data = '{ + "type" : "\"R\"", + "time" : $sformatf("%d", $time()), + "id" : $sformatf("0x%0x", axi_resp_i.r.id), + "data" : $sformatf("0x%0x", axi_resp_i.r.data), + "resp" : $sformatf("0x%0x", axi_resp_i.r.resp), + "last" : $sformatf("0x%0x", axi_resp_i.r.last), + "user" : $sformatf("0x%0x", axi_resp_i.r.user) + }; + if (LogAW && axi_req_i.aw_valid && axi_resp_i.aw_ready) begin + aw_string = "{"; + foreach(aw_data[key]) aw_string = $sformatf("%s'%s': %s, ", aw_string, key, aw_data[key]); + aw_string = $sformatf("%s}", aw_string); + $fwrite(f, aw_string); + $fwrite(f, "\n"); + end + if (LogAR && axi_req_i.ar_valid && axi_resp_i.ar_ready) begin + ar_string = "{"; + foreach(ar_data[key]) ar_string = $sformatf("%s'%s': %s, ", ar_string, key, ar_data[key]); + ar_string = $sformatf("%s}", ar_string); + $fwrite(f, ar_string); + $fwrite(f, "\n"); + end + if (LogW && axi_req_i.w_valid && axi_resp_i.w_ready) begin + w_string = "{"; + foreach(w_data[key]) w_string = $sformatf("%s'%s': %s, ", w_string, key, w_data[key]); + w_string = $sformatf("%s}", w_string); + $fwrite(f, w_string); + $fwrite(f, "\n"); + end + if (LogB && axi_resp_i.b_valid && axi_req_i.b_ready) begin + b_string = "{"; + foreach(b_data[key]) b_string = $sformatf("%s'%s': %s, ", b_string, key, b_data[key]); + b_string = $sformatf("%s}", b_string); + $fwrite(f, b_string); + $fwrite(f, "\n"); + end + if (LogR && axi_resp_i.r_valid && axi_req_i.r_ready) begin + r_string = "{"; + foreach(r_data[key]) r_string = $sformatf("%s'%s': %s, ", r_string, key, r_data[key]); + r_string = $sformatf("%s}", r_string); + $fwrite(f, r_string); + $fwrite(f, "\n"); + end + end + end + final begin + $fclose(f); + end + +`endif + +endmodule + + +`include "axi/assign.svh" +`include "axi/typedef.svh" + +module axi_dumper_intf #( + parameter BusName = "axi_bus", + parameter bit LogAW = 1'b1, + parameter bit LogAR = 1'b1, + parameter bit LogW = 1'b0, + parameter bit LogB = 1'b0, + parameter bit LogR = 1'b0, + parameter int unsigned AXI_ID_WIDTH = 32'd0, + parameter int unsigned AXI_ADDR_WIDTH = 32'd0, + parameter int unsigned AXI_DATA_WIDTH = 32'd0, + parameter int unsigned AXI_USER_WIDTH = 32'd0 +) ( + input logic clk_i, + input logic rst_ni, + AXI_BUS_DV.Monitor axi_bus +); + + typedef logic [AXI_ID_WIDTH-1:0] id_t; + typedef logic [AXI_ADDR_WIDTH-1:0] addr_t; + typedef logic [AXI_DATA_WIDTH-1:0] data_t; + typedef logic [AXI_DATA_WIDTH/8-1:0] strb_t; + typedef logic [AXI_USER_WIDTH-1:0] user_t; + `AXI_TYPEDEF_AW_CHAN_T(aw_chan_t, addr_t, id_t, user_t) + `AXI_TYPEDEF_W_CHAN_T(w_chan_t, data_t, strb_t, user_t) + `AXI_TYPEDEF_B_CHAN_T(b_chan_t, id_t, user_t) + `AXI_TYPEDEF_AR_CHAN_T(ar_chan_t, addr_t, id_t, user_t) + `AXI_TYPEDEF_R_CHAN_T(r_chan_t, data_t, id_t, user_t) + `AXI_TYPEDEF_REQ_T(axi_req_t, aw_chan_t, w_chan_t, ar_chan_t) + `AXI_TYPEDEF_RESP_T(axi_resp_t, b_chan_t, r_chan_t) + + axi_req_t axi_req; + axi_resp_t axi_resp; + + `AXI_ASSIGN_TO_REQ(axi_req, axi_bus) + `AXI_ASSIGN_TO_RESP(axi_resp, axi_bus) + + axi_dumper #( + .BusName ( BusName ), + .LogAW ( LogAW ), + .LogAR ( LogAR ), + .LogW ( LogW ), + .LogB ( LogB ), + .LogR ( LogR ), + .axi_req_t ( axi_req_t ), + .axi_resp_t ( axi_resp_t ) + ) i_axi_dumper ( + .clk_i, + .rst_ni, + .axi_req_i ( axi_req ), + .axi_resp_i ( axi_resp ) + ); + +endmodule diff --git a/src/axi_dw_downsizer.sv b/src/axi_dw_downsizer.sv index 78b053d99..88d774ad0 100644 --- a/src/axi_dw_downsizer.sv +++ b/src/axi_dw_downsizer.sv @@ -397,11 +397,12 @@ module axi_dw_downsizer #( r_state_d = R_PASSTHROUGH; // Save beat - r_req_d.ar = slv_req_i.ar ; - r_req_d.ar_valid = 1'b1 ; - r_req_d.burst_len = slv_req_i.ar.len ; - r_req_d.orig_ar_size = slv_req_i.ar.size; - r_req_d.injected_aw = 1'b0 ; + r_req_d.ar = slv_req_i.ar ; + r_req_d.ar_valid = 1'b1 ; + r_req_d.burst_len = slv_req_i.ar.len ; + r_req_d.orig_ar_size = slv_req_i.ar.size ; + r_req_d.injected_aw = 1'b0 ; + r_req_d.r.resp = axi_pkg::RESP_EXOKAY; case (r_req_d.ar.burst) axi_pkg::BURST_INCR : begin @@ -476,6 +477,7 @@ module axi_dw_downsizer #( r_req_d.burst_len = w_req_q.orig_aw_len ; r_req_d.orig_ar_size = w_req_q.orig_aw_size ; r_req_d.injected_aw = 1'b1 ; + r_req_d.r.resp = axi_pkg::RESP_EXOKAY ; // Default state r_state_d = R_PASSTHROUGH; @@ -794,10 +796,10 @@ module axi_dw_downsizer #( // Can start a new request as soon as w_state_d is W_IDLE if (w_state_d == W_IDLE) begin // Reset channels - w_req_d.aw = '0 ; - w_req_d.aw_valid = 1'b0 ; - w_req_d.aw_throw_error = 1'b0 ; - w_req_d.burst_resp = axi_pkg::RESP_OKAY; + w_req_d.aw = '0 ; + w_req_d.aw_valid = 1'b0 ; + w_req_d.aw_throw_error = 1'b0 ; + w_req_d.burst_resp = axi_pkg::RESP_EXOKAY; if (!forward_b_beat_full) begin if (slv_req_i.aw_valid && slv_req_i.aw.atop[axi_pkg::ATOP_R_RESP]) begin // ATOP with an R response diff --git a/src/axi_fifo.sv b/src/axi_fifo.sv new file mode 100644 index 000000000..8e4cbdfb1 --- /dev/null +++ b/src/axi_fifo.sv @@ -0,0 +1,256 @@ +// Copyright (c) 2014-2022 ETH Zurich, University of Bologna +// +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the "License"); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. +// +// Authors: +// - Noah Huetter +// - Florian Zaruba +// - Fabian Schuiki + +// AXI4 Fifo +// +// Can be used to buffer transactions + +module axi_fifo #( + parameter int unsigned Depth = 32'd1, // Number of FiFo slots. + parameter bit FallThrough = 1'b0, // fifos are in fall-through mode + // AXI channel structs + parameter type aw_chan_t = logic, + parameter type w_chan_t = logic, + parameter type b_chan_t = logic, + parameter type ar_chan_t = logic, + parameter type r_chan_t = logic, + // AXI request & response structs + parameter type axi_req_t = logic, + parameter type axi_resp_t = logic +) ( + input logic clk_i, // Clock + input logic rst_ni, // Asynchronous reset active low + input logic test_i, + // slave port + input axi_req_t slv_req_i, + output axi_resp_t slv_resp_o, + // master port + output axi_req_t mst_req_o, + input axi_resp_t mst_resp_i +); + + if (Depth == '0) begin : gen_no_fifo + // degenerate case, connect input to output + assign mst_req_o = slv_req_i; + assign slv_resp_o = mst_resp_i; + end else begin : gen_axi_fifo + logic aw_fifo_empty, ar_fifo_empty, w_fifo_empty, r_fifo_empty, b_fifo_empty; + logic aw_fifo_full, ar_fifo_full, w_fifo_full, r_fifo_full, b_fifo_full; + + assign mst_req_o.aw_valid = ~aw_fifo_empty; + assign mst_req_o.ar_valid = ~ar_fifo_empty; + assign mst_req_o.w_valid = ~w_fifo_empty; + assign slv_resp_o.r_valid = ~r_fifo_empty; + assign slv_resp_o.b_valid = ~b_fifo_empty; + + assign slv_resp_o.aw_ready = ~aw_fifo_full; + assign slv_resp_o.ar_ready = ~ar_fifo_full; + assign slv_resp_o.w_ready = ~w_fifo_full; + assign mst_req_o.r_ready = ~r_fifo_full; + assign mst_req_o.b_ready = ~b_fifo_full; + + // A FiFo for each channel + fifo_v3 #( + .dtype(aw_chan_t), + .DEPTH(Depth), + .FALL_THROUGH(FallThrough) + ) i_aw_fifo ( + .clk_i, + .rst_ni, + .flush_i (1'b0), + .testmode_i(test_i), + .full_o (aw_fifo_full), + .empty_o (aw_fifo_empty), + .usage_o (), + .data_i (slv_req_i.aw), + .push_i (slv_req_i.aw_valid && slv_resp_o.aw_ready), + .data_o (mst_req_o.aw), + .pop_i (mst_req_o.aw_valid && mst_resp_i.aw_ready) + ); + fifo_v3 #( + .dtype(ar_chan_t), + .DEPTH(Depth), + .FALL_THROUGH(FallThrough) + ) i_ar_fifo ( + .clk_i, + .rst_ni, + .flush_i (1'b0), + .testmode_i(test_i), + .full_o (ar_fifo_full), + .empty_o (ar_fifo_empty), + .usage_o (), + .data_i (slv_req_i.ar), + .push_i (slv_req_i.ar_valid && slv_resp_o.ar_ready), + .data_o (mst_req_o.ar), + .pop_i (mst_req_o.ar_valid && mst_resp_i.ar_ready) + ); + fifo_v3 #( + .dtype(w_chan_t), + .DEPTH(Depth), + .FALL_THROUGH(FallThrough) + ) i_w_fifo ( + .clk_i, + .rst_ni, + .flush_i (1'b0), + .testmode_i(test_i), + .full_o (w_fifo_full), + .empty_o (w_fifo_empty), + .usage_o (), + .data_i (slv_req_i.w), + .push_i (slv_req_i.w_valid && slv_resp_o.w_ready), + .data_o (mst_req_o.w), + .pop_i (mst_req_o.w_valid && mst_resp_i.w_ready) + ); + fifo_v3 #( + .dtype(r_chan_t), + .DEPTH(Depth), + .FALL_THROUGH(FallThrough) + ) i_r_fifo ( + .clk_i, + .rst_ni, + .flush_i (1'b0), + .testmode_i(test_i), + .full_o (r_fifo_full), + .empty_o (r_fifo_empty), + .usage_o (), + .data_i (mst_resp_i.r), + .push_i (mst_resp_i.r_valid && mst_req_o.r_ready), + .data_o (slv_resp_o.r), + .pop_i (slv_resp_o.r_valid && slv_req_i.r_ready) + ); + fifo_v3 #( + .dtype(b_chan_t), + .DEPTH(Depth), + .FALL_THROUGH(FallThrough) + ) i_b_fifo ( + .clk_i, + .rst_ni, + .flush_i (1'b0), + .testmode_i(test_i), + .full_o (b_fifo_full), + .empty_o (b_fifo_empty), + .usage_o (), + .data_i (mst_resp_i.b), + .push_i (mst_resp_i.b_valid && mst_req_o.b_ready), + .data_o (slv_resp_o.b), + .pop_i (slv_resp_o.b_valid && slv_req_i.b_ready) + ); + end + + // Check the invariants + // pragma translate_off +`ifndef VERILATOR + initial begin + assert (Depth >= 0); + end +`endif + // pragma translate_on +endmodule + +`include "axi/assign.svh" +`include "axi/typedef.svh" + +// interface wrapper +module axi_fifo_intf #( + parameter int unsigned ADDR_WIDTH = 0, // The address width. + parameter int unsigned DATA_WIDTH = 0, // The data width. + parameter int unsigned ID_WIDTH = 0, // The ID width. + parameter int unsigned USER_WIDTH = 0, // The user data width. + parameter int unsigned DEPTH = 0, // The number of FiFo slots. + parameter int unsigned FALL_THROUGH = 0 // FiFo in fall-through mode +) ( + input logic clk_i, + input logic rst_ni, + input logic test_i, + AXI_BUS.Slave slv, + AXI_BUS.Master mst +); + + typedef logic [ID_WIDTH-1:0] id_t; + typedef logic [ADDR_WIDTH-1:0] addr_t; + typedef logic [DATA_WIDTH-1:0] data_t; + typedef logic [DATA_WIDTH/8-1:0] strb_t; + typedef logic [USER_WIDTH-1:0] user_t; + + `AXI_TYPEDEF_AW_CHAN_T(aw_chan_t, addr_t, id_t, user_t) + `AXI_TYPEDEF_W_CHAN_T(w_chan_t, data_t, strb_t, user_t) + `AXI_TYPEDEF_B_CHAN_T(b_chan_t, id_t, user_t) + `AXI_TYPEDEF_AR_CHAN_T(ar_chan_t, addr_t, id_t, user_t) + `AXI_TYPEDEF_R_CHAN_T(r_chan_t, data_t, id_t, user_t) + `AXI_TYPEDEF_REQ_T(axi_req_t, aw_chan_t, w_chan_t, ar_chan_t) + `AXI_TYPEDEF_RESP_T(axi_resp_t, b_chan_t, r_chan_t) + + axi_req_t slv_req, mst_req; + axi_resp_t slv_resp, mst_resp; + + `AXI_ASSIGN_TO_REQ(slv_req, slv) + `AXI_ASSIGN_FROM_RESP(slv, slv_resp) + + `AXI_ASSIGN_FROM_REQ(mst, mst_req) + `AXI_ASSIGN_TO_RESP(mst_resp, mst) + + axi_fifo #( + .Depth (DEPTH), + .FallThrough(FALL_THROUGH), + .aw_chan_t (aw_chan_t), + .w_chan_t (w_chan_t), + .b_chan_t (b_chan_t), + .ar_chan_t (ar_chan_t), + .r_chan_t (r_chan_t), + .axi_req_t (axi_req_t), + .axi_resp_t (axi_resp_t) + ) i_axi_fifo ( + .clk_i, + .rst_ni, + .test_i, + .slv_req_i (slv_req), + .slv_resp_o(slv_resp), + .mst_req_o (mst_req), + .mst_resp_i(mst_resp) + ); + + // Check the invariants. + // pragma translate_off +`ifndef VERILATOR + initial begin + assert (ADDR_WIDTH > 0) + else $fatal(1, "Wrong addr width parameter"); + assert (DATA_WIDTH > 0) + else $fatal(1, "Wrong data width parameter"); + assert (ID_WIDTH > 0) + else $fatal(1, "Wrong id width parameter"); + assert (USER_WIDTH > 0) + else $fatal(1, "Wrong user width parameter"); + assert (slv.AXI_ADDR_WIDTH == ADDR_WIDTH) + else $fatal(1, "Wrong interface definition"); + assert (slv.AXI_DATA_WIDTH == DATA_WIDTH) + else $fatal(1, "Wrong interface definition"); + assert (slv.AXI_ID_WIDTH == ID_WIDTH) + else $fatal(1, "Wrong interface definition"); + assert (slv.AXI_USER_WIDTH == USER_WIDTH) + else $fatal(1, "Wrong interface definition"); + assert (mst.AXI_ADDR_WIDTH == ADDR_WIDTH) + else $fatal(1, "Wrong interface definition"); + assert (mst.AXI_DATA_WIDTH == DATA_WIDTH) + else $fatal(1, "Wrong interface definition"); + assert (mst.AXI_ID_WIDTH == ID_WIDTH) + else $fatal(1, "Wrong interface definition"); + assert (mst.AXI_USER_WIDTH == USER_WIDTH) + else $fatal(1, "Wrong interface definition"); + end +`endif + // pragma translate_on +endmodule diff --git a/src/axi_from_mem.sv b/src/axi_from_mem.sv new file mode 100644 index 000000000..466a384e3 --- /dev/null +++ b/src/axi_from_mem.sv @@ -0,0 +1,124 @@ +// Copyright 2022 ETH Zurich and University of Bologna. +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the "License"); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. +// +// Authors: +// - Christopher Reinwardt +// - Nicole Narr + +`include "axi/typedef.svh" + +/// Protocol adapter which translates memory requests to the AXI4 protocol. +/// +/// This module acts like an SRAM and makes AXI4 requests downstream. +/// +/// Supports multiple outstanding requests and will have responses for reads **and** writes. +/// Response latency is not fixed and for sure **not 1** and depends on the AXI4 memory system. +/// The `mem_rsp_valid_o` can have multiple cycles of latency from the corresponding `mem_gnt_o`. +module axi_from_mem #( + /// Memory request address width. + parameter int unsigned MemAddrWidth = 32'd0, + /// AXI4-Lite address width. + parameter int unsigned AxiAddrWidth = 32'd0, + /// Data width in bit of the memory request data **and** the Axi4-Lite data channels. + parameter int unsigned DataWidth = 32'd0, + /// How many requests can be in flight at the same time. (Depth of the response mux FIFO). + parameter int unsigned MaxRequests = 32'd0, + /// Protection signal the module should emit on the AXI4 transactions. + parameter axi_pkg::prot_t AxiProt = 3'b000, + /// AXI4 request struct definition. + parameter type axi_req_t = logic, + /// AXI4 response struct definition. + parameter type axi_rsp_t = logic +) ( + /// Clock input, positive edge triggered. + input logic clk_i, + /// Asynchronous reset, active low. + input logic rst_ni, + /// Memory slave port, request is active. + input logic mem_req_i, + /// Memory slave port, request address. + /// + /// Byte address, will be extended or truncated to match `AxiAddrWidth`. + input logic [MemAddrWidth-1:0] mem_addr_i, + /// Memory slave port, request is a write. + /// + /// `0`: Read request. + /// `1`: Write request. + input logic mem_we_i, + /// Memory salve port, write data for request. + input logic [DataWidth-1:0] mem_wdata_i, + /// Memory slave port, write byte enable for request. + /// + /// Active high. + input logic [DataWidth/8-1:0] mem_be_i, + /// Memory request is granted. + output logic mem_gnt_o, + /// Memory slave port, response is valid. For each request, regardless if read or write, + /// this will be active once for one cycle. + output logic mem_rsp_valid_o, + /// Memory slave port, response read data. This is forwarded directly from the AXI4-Lite + /// `R` channel. Only valid for responses generated by a read request. + output logic [DataWidth-1:0] mem_rsp_rdata_o, + /// Memory request encountered an error. This is forwarded from the AXI4-Lite error response. + output logic mem_rsp_error_o, + /// AXI4 master port, slave aw cache signal + input axi_pkg::cache_t slv_aw_cache_i, + /// AXI4 master port, slave ar cache signal + input axi_pkg::cache_t slv_ar_cache_i, + /// AXI4 master port, request output. + output axi_req_t axi_req_o, + /// AXI4 master port, response input. + input axi_rsp_t axi_rsp_i +); + + `AXI_LITE_TYPEDEF_ALL(axi_lite, logic [AxiAddrWidth-1:0], logic [DataWidth-1:0], logic [DataWidth/8-1:0]) + axi_lite_req_t axi_lite_req; + axi_lite_resp_t axi_lite_rsp; + + axi_lite_from_mem #( + .MemAddrWidth ( MemAddrWidth ), + .AxiAddrWidth ( AxiAddrWidth ), + .DataWidth ( DataWidth ), + .MaxRequests ( MaxRequests ), + .AxiProt ( AxiProt ), + .axi_req_t ( axi_lite_req_t ), + .axi_rsp_t ( axi_lite_resp_t ) + ) i_axi_lite_from_mem ( + .clk_i, + .rst_ni, + .mem_req_i, + .mem_addr_i, + .mem_we_i, + .mem_wdata_i, + .mem_be_i, + .mem_gnt_o, + .mem_rsp_valid_o, + .mem_rsp_rdata_o, + .mem_rsp_error_o, + .axi_req_o ( axi_lite_req ), + .axi_rsp_i ( axi_lite_rsp ) + ); + + axi_lite_to_axi #( + .AxiDataWidth ( DataWidth ), + .req_lite_t ( axi_lite_req_t ), + .resp_lite_t ( axi_lite_resp_t ), + .axi_req_t ( axi_req_t ), + .axi_resp_t ( axi_rsp_t ) + ) i_axi_lite_to_axi ( + .slv_req_lite_i ( axi_lite_req ), + .slv_resp_lite_o ( axi_lite_rsp ), + .slv_aw_cache_i, + .slv_ar_cache_i, + .mst_req_o ( axi_req_o ), + .mst_resp_i ( axi_rsp_i ) + ); + +endmodule diff --git a/src/axi_id_remap.sv b/src/axi_id_remap.sv index 677bc14f1..85dc66e0d 100644 --- a/src/axi_id_remap.sv +++ b/src/axi_id_remap.sv @@ -9,9 +9,10 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. // -// Andreas Kurth -// Florian Zaruba -// Wolfgang Roenninger +// Authors: +// - Andreas Kurth +// - Wolfgang Roenninger +// - Florian Zaruba `include "common_cells/registers.svh" @@ -284,13 +285,16 @@ module axi_id_remap #( aw_id_d = wr_push_oup_id; end end - priority casez ({mst_req_o.ar_valid, mst_resp_i.ar_ready, - mst_req_o.aw_valid, mst_resp_i.aw_ready}) - 4'b1010: state_d = HoldAx; - 4'b10??: state_d = HoldAR; - 4'b??10: state_d = HoldAW; - default: state_d = Ready; - endcase + if ({mst_req_o.ar_valid, mst_resp_i.ar_ready, + mst_req_o.aw_valid, mst_resp_i.aw_ready} == 4'b1010) begin + state_d = HoldAx; + end else if ({mst_req_o.ar_valid, mst_resp_i.ar_ready} == 2'b10) begin + state_d = HoldAR; + end else if ({mst_req_o.aw_valid, mst_resp_i.aw_ready} == 2'b10) begin + state_d = HoldAW; + end else begin + state_d = Ready; + end if (mst_req_o.ar_valid && mst_resp_i.ar_ready) begin ar_prio_d = 1'b0; // Reset AR priority, because handshake was successful in this cycle. diff --git a/src/axi_id_serialize.sv b/src/axi_id_serialize.sv index 4e81a9017..9a787dd25 100644 --- a/src/axi_id_serialize.sv +++ b/src/axi_id_serialize.sv @@ -10,8 +10,8 @@ // specific language governing permissions and limitations under the License. // // Authors: -// - Wolfgang Roenninger -// - Andreas Kurth +// - Andreas Kurth +// - Paul Scheffler `include "axi/assign.svh" `include "axi/typedef.svh" @@ -47,6 +47,8 @@ module axi_id_serialize #( parameter int unsigned AxiDataWidth = 32'd0, /// User width of both AXI4+ATOP ports parameter int unsigned AxiUserWidth = 32'd0, + /// Enable support for AXI4+ATOP atomics + parameter bit AtopSupport = 1'b1, /// Request struct type of the AXI4+ATOP slave port parameter type slv_req_t = logic, /// Response struct type of the AXI4+ATOP slave port @@ -54,7 +56,17 @@ module axi_id_serialize #( /// Request struct type of the AXI4+ATOP master port parameter type mst_req_t = logic, /// Response struct type of the AXI4+ATOP master port - parameter type mst_resp_t = logic + parameter type mst_resp_t = logic, + /// A custom offset (modulo `AxiMstPortMaxUniqIds`, ignored for input IDs remapped through + /// `IdMap`) for the assigned output IDs. + parameter int unsigned MstIdBaseOffset = 32'd0, + /// Explicit input-output ID map. If an input ID `id` does not appear in this mapping (default), + /// it is simply mapped to the output ID `id % AxiMstPortMaxUniqIds`. If `id` appears in more + /// than one entry, it is matched to the *last* matching entry's output ID. + /// Number of Entries in the explicit ID map (default: None) + parameter int unsigned IdMapNumEntries = 32'd0, + /// Explicit ID map; index [0] in each entry is the input ID to match, index [1] the output ID. + parameter int unsigned IdMap [IdMapNumEntries-1:0][0:1] = '{default: {32'b0, 32'b0}} ) ( /// Rising-edge clock of both ports input logic clk_i, @@ -142,9 +154,27 @@ module axi_id_serialize #( /// R channel at master port `AXI_TYPEDEF_R_CHAN_T(mst_r_t, data_t, mst_id_t, user_t) + /// Type for slave ID map + typedef mst_id_t [2**AxiSlvPortIdWidth-1:0] slv_id_map_t; + + /// Resolve target output ID for each possible input ID as a parameter + function automatic slv_id_map_t map_slv_ids(); + slv_id_map_t ret = '0; + // Populate output with default mapping, including `MstIdBaseOffset` + for (int unsigned i = 0; i < 2**AxiSlvPortIdWidth; ++i) + ret[i] = (i + MstIdBaseOffset) % AxiMstPortMaxUniqIds; + // For each explicitly mapped input ID, set the desired output ID + for (int unsigned i = 0; i < IdMapNumEntries; ++i) + ret[IdMap[i][0]] = IdMap[i][1]; + return ret; + endfunction + + /// Input-to-output ID map used + localparam slv_id_map_t SlvIdMap = map_slv_ids(); + select_t slv_aw_select, slv_ar_select; - assign slv_aw_select = select_t'(slv_req_i.aw.id % AxiMstPortMaxUniqIds); // TODO: customizable base - assign slv_ar_select = select_t'(slv_req_i.ar.id % AxiMstPortMaxUniqIds); + assign slv_aw_select = select_t'(SlvIdMap[slv_req_i.aw.id]); + assign slv_ar_select = select_t'(SlvIdMap[slv_req_i.ar.id]); slv_req_t [AxiMstPortMaxUniqIds-1:0] to_serializer_reqs; slv_resp_t [AxiMstPortMaxUniqIds-1:0] to_serializer_resps; @@ -161,7 +191,7 @@ module axi_id_serialize #( .NoMstPorts ( AxiMstPortMaxUniqIds ), .MaxTrans ( AxiSlvPortMaxTxns ), .AxiLookBits ( AxiSlvPortIdWidth ), - .FallThrough ( 1'b1 ), + .AtopSupport ( AtopSupport ), .SpillAw ( 1'b1 ), .SpillW ( 1'b0 ), .SpillB ( 1'b0 ), diff --git a/src/axi_intf.sv b/src/axi_intf.sv index 0d43ffbae..c0257f21c 100644 --- a/src/axi_intf.sv +++ b/src/axi_intf.sv @@ -98,6 +98,14 @@ interface AXI_BUS #( output r_id, r_data, r_resp, r_last, r_user, r_valid, input r_ready ); + modport Monitor ( + input aw_id, aw_addr, aw_len, aw_size, aw_burst, aw_lock, aw_cache, aw_prot, aw_qos, aw_region, aw_atop, aw_user, aw_valid, aw_ready, + w_data, w_strb, w_last, w_user, w_valid, w_ready, + b_id, b_resp, b_user, b_valid, b_ready, + ar_id, ar_addr, ar_len, ar_size, ar_burst, ar_lock, ar_cache, ar_prot, ar_qos, ar_region, ar_user, ar_valid, ar_ready, + r_id, r_data, r_resp, r_last, r_user, r_valid, r_ready + ); + endinterface @@ -441,6 +449,14 @@ interface AXI_LITE #( output r_data, r_resp, r_valid, input r_ready ); + modport Monitor ( + input aw_addr, aw_prot, aw_valid, aw_ready, + w_data, w_strb, w_valid, w_ready, + b_resp, b_valid, b_ready, + ar_addr, ar_prot, ar_valid, ar_ready, + r_data, r_resp, r_valid, r_ready + ); + endinterface @@ -499,6 +515,14 @@ interface AXI_LITE_DV #( output r_data, r_resp, r_valid, input r_ready ); + modport Monitor ( + input aw_addr, aw_prot, aw_valid, aw_ready, + w_data, w_strb, w_valid, w_ready, + b_resp, b_valid, b_ready, + ar_addr, ar_prot, ar_valid, ar_ready, + r_data, r_resp, r_valid, r_ready + ); + endinterface diff --git a/src/axi_isolate.sv b/src/axi_isolate.sv index da6bbc8bc..6385b2633 100644 --- a/src/axi_isolate.sv +++ b/src/axi_isolate.sv @@ -46,13 +46,13 @@ module axi_isolate #( /// Support atomic operations (ATOPs) parameter bit AtopSupport = 1'b1, /// Address width of all AXI4+ATOP ports - parameter int unsigned AxiAddrWidth = 32'd0, + parameter int signed AxiAddrWidth = 32'd0, /// Data width of all AXI4+ATOP ports - parameter int unsigned AxiDataWidth = 32'd0, + parameter int signed AxiDataWidth = 32'd0, /// ID width of all AXI4+ATOP ports - parameter int unsigned AxiIdWidth = 32'd0, + parameter int signed AxiIdWidth = 32'd0, /// User signal width of all AXI4+ATOP ports - parameter int unsigned AxiUserWidth = 32'd0, + parameter int signed AxiUserWidth = 32'd0, /// Request struct type of all AXI4+ATOP ports parameter type axi_req_t = logic, /// Response struct type of all AXI4+ATOP ports @@ -107,7 +107,6 @@ module axi_isolate #( // We don't need many bits here as the common case will be to go for the pass-through. .AxiLookBits ( 1 ), .UniqueIds ( 1'b0 ), - .FallThrough ( 1'b1 ), .SpillAw ( 1'b0 ), .SpillW ( 1'b0 ), .SpillB ( 1'b0 ), diff --git a/src/axi_iw_converter.sv b/src/axi_iw_converter.sv index e9c5ebb82..f29425eb1 100644 --- a/src/axi_iw_converter.sv +++ b/src/axi_iw_converter.sv @@ -9,9 +9,9 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. // -// Andreas Kurth -// Florian Zaruba -// Wolfgang Roenninger +// Authors: +// - Andreas Kurth +// - Wolfgang Roenninger `include "axi/typedef.svh" diff --git a/src/axi_lfsr.sv b/src/axi_lfsr.sv new file mode 100644 index 000000000..2085a76fe --- /dev/null +++ b/src/axi_lfsr.sv @@ -0,0 +1,121 @@ +// Copyright 2022 ETH Zurich and University of Bologna. +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the "License"); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. +// +// Authors: +// - Thomas Benz + +`include "axi/typedef.svh" + +/// AXI4 LFSR Subordinate device. Responds with a pseudo random answer. Serial interface to +/// set the internal state. +module axi_lfsr #( + /// AXI4 Data Width + parameter int unsigned DataWidth = 32'd0, + /// AXI4 Addr Width + parameter int unsigned AddrWidth = 32'd0, + /// AXI4 Id Width + parameter int unsigned IdWidth = 32'd0, + /// AXI4 User Width + parameter int unsigned UserWidth = 32'd0, + /// AXI4 request struct definition + parameter type axi_req_t = logic, + /// AXI4 response struct definition + parameter type axi_rsp_t = logic +)( + /// Rising-edge clock + input logic clk_i, + /// Active-low reset + input logic rst_ni, + /// Testmode + input logic testmode_i, + /// AXI4 request struct + input axi_req_t req_i, + /// AXI4 response struct + output axi_rsp_t rsp_o, + /// Serial shift data in (write) + input logic w_ser_data_i, + /// Serial shift data out (write) + output logic w_ser_data_o, + /// Serial shift enable (write) + input logic w_ser_en_i, + /// Serial shift data in (read) + input logic r_ser_data_i, + /// Serial shift data out (read) + output logic r_ser_data_o, + /// Serial shift enable (read) + input logic r_ser_en_i +); + + /// AXI4 Strobe Width + localparam int unsigned StrbWidth = DataWidth / 8; + + /// Address Type + typedef logic [AddrWidth-1:0] addr_t; + /// Data type + typedef logic [DataWidth-1:0] data_t; + /// Strobe Type + typedef logic [StrbWidth-1:0] strb_t; + + // AXI Lite typedef + `AXI_LITE_TYPEDEF_AW_CHAN_T(axi_lite_aw_chan_t, addr_t) + `AXI_LITE_TYPEDEF_W_CHAN_T(axi_lite_w_chan_t, data_t, strb_t) + `AXI_LITE_TYPEDEF_B_CHAN_T(axi_lite_b_chan_t) + + `AXI_LITE_TYPEDEF_AR_CHAN_T(axi_lite_ar_chan_t, addr_t) + `AXI_LITE_TYPEDEF_R_CHAN_T(axi_lite_r_chan_t, data_t) + + `AXI_LITE_TYPEDEF_REQ_T(axi_lite_req_t, axi_lite_aw_chan_t, axi_lite_w_chan_t, axi_lite_ar_chan_t) + `AXI_LITE_TYPEDEF_RESP_T(axi_lite_rsp_t, axi_lite_b_chan_t, axi_lite_r_chan_t) + + // AXI Lite buses + axi_lite_req_t axi_lite_req; + axi_lite_rsp_t axi_lite_rsp; + + axi_to_axi_lite #( + .AxiAddrWidth ( AddrWidth ), + .AxiDataWidth ( DataWidth ), + .AxiIdWidth ( IdWidth ), + .AxiUserWidth ( UserWidth ), + .AxiMaxWriteTxns ( 'd2 ), // We only have 1 cycle latency; 2 is enough + .AxiMaxReadTxns ( 'd2 ), // We only have 1 cycle latency; 2 is enough + .FallThrough ( 1'b0 ), + .full_req_t ( axi_req_t ), + .full_resp_t ( axi_rsp_t ), + .lite_req_t ( axi_lite_req_t ), + .lite_resp_t ( axi_lite_rsp_t ) + ) i_axi_to_axi_lite ( + .clk_i, + .rst_ni, + .test_i ( testmode_i ), + .slv_req_i ( req_i ), + .slv_resp_o ( rsp_o ), + .mst_req_o ( axi_lite_req ), + .mst_resp_i ( axi_lite_rsp ) + ); + + axi_lite_lfsr #( + .DataWidth ( DataWidth ), + .axi_lite_req_t ( axi_lite_req_t ), + .axi_lite_rsp_t ( axi_lite_rsp_t ) + ) i_axi_lite_lfsr ( + .clk_i, + .rst_ni, + .testmode_i, + .w_ser_data_i, + .w_ser_data_o, + .w_ser_en_i, + .r_ser_data_i, + .r_ser_data_o, + .r_ser_en_i, + .req_i ( axi_lite_req ), + .rsp_o ( axi_lite_rsp ) + ); + +endmodule : axi_lfsr diff --git a/src/axi_lite_demux.sv b/src/axi_lite_demux.sv index 510b7d0f0..132ea1911 100644 --- a/src/axi_lite_demux.sv +++ b/src/axi_lite_demux.sv @@ -426,8 +426,15 @@ module axi_lite_demux #( ); // connect the response if the FIFO has valid data in it - assign slv_r_chan = (!r_fifo_empty) ? mst_resps_i[r_select].r : '0; - assign slv_r_valid = ~r_fifo_empty & mst_resps_i[r_select].r_valid; + always_comb begin + slv_r_chan = '0; + slv_r_valid = '0; + if (!r_fifo_empty) begin + slv_r_chan = mst_resps_i[r_select].r; + slv_r_valid = mst_resps_i[r_select].r_valid; + end + end + for (genvar i = 0; i < NoMstPorts; i++) begin : gen_mst_r assign mst_reqs_o[i].r_ready = ~r_fifo_empty & slv_r_ready & (r_select == select_t'(i)); end @@ -465,8 +472,8 @@ module axi_lite_demux #( // pragma translate_off `ifndef VERILATOR initial begin: p_assertions - NoPorts: assert (NoMstPorts > 0) else $fatal("Number of master ports must be at least 1!"); - MaxTnx: assert (MaxTrans > 0) else $fatal("Number of transactions must be at least 1!"); + NoPorts: assert (NoMstPorts > 0) else $fatal(2,"Number of master ports must be at least 1!"); + MaxTnx: assert (MaxTrans > 0) else $fatal(2,"Number of transactions must be at least 1!"); end `endif // pragma translate_on @@ -554,8 +561,8 @@ module axi_lite_demux_intf #( // pragma translate_off `ifndef VERILATOR initial begin: p_assertions - AddrWidth: assert (AxiAddrWidth > 0) else $fatal("Axi Parmeter has to be > 0!"); - DataWidth: assert (AxiDataWidth > 0) else $fatal("Axi Parmeter has to be > 0!"); + AddrWidth: assert (AxiAddrWidth > 0) else $fatal(2,"Axi Parmeter has to be > 0!"); + DataWidth: assert (AxiDataWidth > 0) else $fatal(2,"Axi Parmeter has to be > 0!"); end `endif // pragma translate_on diff --git a/src/axi_lite_dw_converter.sv b/src/axi_lite_dw_converter.sv new file mode 100644 index 000000000..d8055481f --- /dev/null +++ b/src/axi_lite_dw_converter.sv @@ -0,0 +1,567 @@ +// Copyright 2020 ETH Zurich and University of Bologna. +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the "License"); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +// Authors: +// - Wolfgang Roenninger + +/// # AXI4-Lite data width downsize module. +/// +/// ## Down Conversion +/// +/// The module will be in this mode if `AxiSlvPortDataWidth > AxiMstPortDataWidth`. +/// The module does multiple transactions on the master port for each transaction of the salve port. +/// +/// The address on the master port will be aligned to the bus width, regardless what the input +/// address was. The number of transactions generated on the master port is equal to the +/// `DownsizeFactor = AxiSlvPortDataWidth / AxiMstPortDataWidth`. +/// +/// Eg: `AxiAddrWidth == 32'd16`, `AxiSlvPortDataWidth == 32'64`, `AxiMstPortDataWidth == 32'd32' +/// This is for write transactions. Reads are accessing the whole width of the slave port. +/// +/// +/// | EG NUM | SLV ADDR | SLV W DATA | SLV W STRB | MST ADDR | MST W DATA | MST W STRB | +/// |--------|----------|--------------------|------------|----------|------------|------------| +/// | 1 | 0x0000 | 0xBEEFBEEFAAAABBBB | 0xAB | 0x0000 | 0xAAAABBBB | 0xB | +/// | 1 | | | | 0x0004 | 0xBEEFBEEF | 0xA | +/// | | | | | | | | +/// | 2 | 0x0000 | 0xBEEFBEEFAAAABBBB | 0xF0 | 0x0000 | 0xAAAABBBB | 0x0 | +/// | 2 | | | | 0x0004 | 0xBEEFBEEF | 0xF | +/// | | | | | | | | +/// | 3 | 0x0004 | 0xBEEFBEEFAAAABBBB | 0xF0 | 0x0000 | 0xAAAABBBB | 0x0 | +/// | 3 | | | | 0x0004 | 0xBEEFBEEF | 0xF | +/// | | | | | | | | +/// | 4 | 0x0004 | 0xBEEFBEEFAAAABBBB | 0x0F | 0x0000 | 0xAAAABBBB | 0xF | +/// | 4 | | | | 0x0004 | 0xBEEFBEEF | 0x0 | +/// | | | | | | | | +/// | 5 | 0x0005 | 0xBEEFBE0000000000 | 0xE0 | 0x0000 | 0x00000000 | 0x0 | +/// | 5 | | | | 0x0004 | 0xBEEFBE00 | 0xE | +/// +/// Response field is aggregated (OR'ed) between the multiple requests made on the master port. +/// If one of the requests on the master port errors, the error response of the request +/// on the slave port will also signal an error. +/// +/// ## Up conversion +/// +/// The module will be in this mode if `AxiSlvPortDataWidth < AxiMstPortDataWidth`. +/// This mode will generate the same amount of transactions on the master port as on the slave port. +/// Data is replicated to match the bus width. Write strobes are silenced for the byte lanes not +/// written. +/// +/// ## Pass Through +/// +/// The module will be in this mode if `AxiSlvPortDataWidth == AxiMstPortDataWidth`. +/// Here the module passes through the slave port to the master port. +`include "common_cells/registers.svh" +module axi_lite_dw_converter #( + /// AXI4-Lite address width of the ports. + parameter int unsigned AxiAddrWidth = 32'd0, + /// AXI4-Lite data width of the slave port. + parameter int unsigned AxiSlvPortDataWidth = 32'd0, + /// AXI4-Lite data width of the master port. + parameter int unsigned AxiMstPortDataWidth = 32'd0, + /// AXI4-Lite AW channel struct type. This is for both ports the same. + parameter type axi_lite_aw_t = logic, + /// AXI4-Lite W channel struct type of the slave port. + parameter type axi_lite_slv_w_t = logic, + /// AXI4-Lite W channel struct type of the master port. + parameter type axi_lite_mst_w_t = logic, + /// AXI4-Lite B channel struct type. This is for both ports. + parameter type axi_lite_b_t = logic, + /// AXI4-Lite AR channel struct type. This is for both ports. + parameter type axi_lite_ar_t = logic, + /// AXI4-Lite R channel struct type of the slave port. + parameter type axi_lite_slv_r_t = logic, + /// AXI4-Lite R channel struct type of the master port. + parameter type axi_lite_mst_r_t = logic, + /// AXI4-Lite request struct of the slave port. + parameter type axi_lite_slv_req_t = logic, + /// AXI4-Lite response struct of the slave port. + parameter type axi_lite_slv_res_t = logic, + /// AXI4-Lite request struct of the master port. + parameter type axi_lite_mst_req_t = logic, + /// AXI4-Lite response struct of the master port. + parameter type axi_lite_mst_res_t = logic +) ( + /// Clock, positive edge triggered. + input logic clk_i, + /// Asynchrounous reset, active low. + input logic rst_ni, + /// Salve port, AXI4-Lite request. + input axi_lite_slv_req_t slv_req_i, + /// Salve port, AXI4-Lite response. + output axi_lite_slv_res_t slv_res_o, + /// Master port, AXI4-Lite request. + output axi_lite_mst_req_t mst_req_o, + /// Master port, AXI4-Lite response. + input axi_lite_mst_res_t mst_res_i +); + // Strobe parameter for the two AXI4-Lite ports. + localparam int unsigned AxiSlvPortStrbWidth = AxiSlvPortDataWidth / 32'd8; + localparam int unsigned AxiMstPortStrbWidth = AxiMstPortDataWidth / 32'd8; + typedef logic [AxiAddrWidth-1:0] addr_t; + + // AXI4-Lite downsizer + if (AxiSlvPortDataWidth > AxiMstPortDataWidth) begin : gen_downsizer + // The Downsize factor determines how often the data channel has to be multiplexed. + localparam int unsigned DownsizeFactor = AxiSlvPortDataWidth / AxiMstPortDataWidth; + // Selection width for choosing the byte lanes. + localparam int unsigned SelWidth = $clog2(DownsizeFactor); + // Type for the selection signal. + typedef logic [SelWidth-1:0] sel_t; + // Offset determines, which part of the address corresponds to the `w_chan_sel` signal. + localparam int unsigned SelOffset = $clog2(AxiMstPortStrbWidth); + + // Calculate the output address for the master port. + // `address`: The address as seen on the salve port. + // `sel`: T The current selection. + // `l_zero`: If set, the lowest bits are zero, for all generated addresses after the first. + function automatic addr_t out_address(input addr_t address, input sel_t sel); + out_address = address; + out_address[SelOffset+:SelWidth] = sel; + out_address[SelOffset-1:0] = SelOffset'(0); + endfunction : out_address + + // Write channels. + // Input spill register of the AW channel. + axi_lite_aw_t aw_chan_spill; + logic aw_chan_spill_valid, aw_chan_spill_ready; + + spill_register #( + .T ( axi_lite_aw_t ), + .Bypass ( 1'b0 ) + ) i_spill_register_aw ( + .clk_i, + .rst_ni, + .valid_i ( slv_req_i.aw_valid ), + .ready_o ( slv_res_o.aw_ready ), + .data_i ( slv_req_i.aw ), + .valid_o ( aw_chan_spill_valid ), + .ready_i ( aw_chan_spill_ready ), + .data_o ( aw_chan_spill ) + ); + + sel_t aw_sel_q, aw_sel_d; + logic aw_sel_load; + // AW channel output assignment + always_comb begin : proc_aw_chan_oup + mst_req_o.aw = aw_chan_spill; + mst_req_o.aw.addr = out_address(aw_chan_spill.addr, aw_sel_q); + end + // Slave port aw is valid, if there is something in the spill register. + assign mst_req_o.aw_valid = aw_chan_spill_valid; + assign aw_chan_spill_ready = mst_res_i.aw_ready & (&aw_sel_q); + + assign aw_sel_load = mst_req_o.aw_valid & mst_res_i.aw_ready; + assign aw_sel_d = sel_t'(aw_sel_q + 1'b1); + `FFLARN(aw_sel_q, aw_sel_d, aw_sel_load, '0, clk_i, rst_ni) + + // Input spill register of the W channel. + axi_lite_slv_w_t w_chan_spill; + logic w_chan_spill_valid, w_chan_spill_ready; + spill_register #( + .T ( axi_lite_slv_w_t ), + .Bypass ( 1'b0 ) + ) i_spill_register_w ( + .clk_i, + .rst_ni, + .valid_i ( slv_req_i.w_valid ), + .ready_o ( slv_res_o.w_ready ), + .data_i ( slv_req_i.w ), + .valid_o ( w_chan_spill_valid ), + .ready_i ( w_chan_spill_ready ), + .data_o ( w_chan_spill ) + ); + + // Data multiplexer on the W channel + sel_t w_sel_q, w_sel_d; + logic w_sel_load; + // W channel output assignment + assign mst_req_o.w = axi_lite_mst_w_t'{ + data: w_chan_spill.data[w_sel_q*AxiMstPortDataWidth+:AxiMstPortDataWidth], + strb: w_chan_spill.strb[w_sel_q*AxiMstPortStrbWidth+:AxiMstPortStrbWidth], + default: '0 + }; + assign mst_req_o.w_valid = w_chan_spill_valid; + assign w_chan_spill_ready = mst_res_i.w_ready & (&w_sel_q); + + assign w_sel_load = mst_req_o.w_valid & mst_res_i.w_ready; + assign w_sel_d = sel_t'(w_sel_q + 1'b1); + `FFLARN(w_sel_q, w_sel_d, w_sel_load, '0, clk_i, rst_ni) + + // B response aggregation + // Slave port B output is the aggregated error of the last few B responses. + sel_t b_sel_q, b_sel_d; + axi_pkg::resp_t b_resp_q, b_resp_d; + logic b_resp_load; + + assign slv_res_o.b = axi_lite_b_t'{ + resp: b_resp_q | mst_res_i.b.resp, + default: '0 + }; + // Output is valid, if it is the last b response for the wide W, we have something + // in the B FIFO and the B response is valid from the master port. + assign slv_res_o.b_valid = mst_res_i.b_valid & (&b_sel_q); + + // Assign the b_channel ready output. The master port is ready if something is in the + // B FIFO. Except, if it is the last one which should do a response on the slave port. + assign mst_req_o.b_ready = (&b_sel_q) ? slv_req_i.b_ready : 1'b1; + // B channel error response retention FF + assign b_sel_d = sel_t'(b_sel_q + 1'b1); + assign b_resp_d = (&b_sel_q) ? axi_pkg::RESP_OKAY : (b_resp_q | mst_res_i.b.resp); + assign b_resp_load = mst_res_i.b_valid & mst_req_o.b_ready; + `FFLARN(b_sel_q, b_sel_d, b_resp_load, '0, clk_i, rst_ni) + `FFLARN(b_resp_q, b_resp_d, b_resp_load, axi_pkg::RESP_OKAY, clk_i, rst_ni) + + // Read channels. + // Input spill register of the AW channel. + axi_lite_ar_t ar_chan_spill; + logic ar_chan_spill_valid, ar_chan_spill_ready; + + spill_register #( + .T ( axi_lite_ar_t ), + .Bypass ( 1'b0 ) + ) i_spill_register_ar ( + .clk_i, + .rst_ni, + .valid_i ( slv_req_i.ar_valid ), + .ready_o ( slv_res_o.ar_ready ), + .data_i ( slv_req_i.ar ), + .valid_o ( ar_chan_spill_valid ), + .ready_i ( ar_chan_spill_ready ), + .data_o ( ar_chan_spill ) + ); + + sel_t ar_sel_q, ar_sel_d; + logic ar_sel_load; + // AR channel output assignment + always_comb begin : proc_ar_chan_oup + mst_req_o.ar = ar_chan_spill; + mst_req_o.ar.addr = out_address(ar_chan_spill.addr, ar_sel_q); + end + // Slave port aw is valid, if there is something in the spill register. + assign mst_req_o.ar_valid = ar_chan_spill_valid; + assign ar_chan_spill_ready = mst_res_i.ar_ready & (&ar_sel_q); + + assign ar_sel_load = mst_req_o.ar_valid & mst_res_i.ar_ready; + assign ar_sel_d = sel_t'(ar_sel_q + 1'b1); + `FFLARN(ar_sel_q, ar_sel_d, ar_sel_load, '0, clk_i, rst_ni) + + // Responses have to be aggregated, one FF less, as the last data is feed directly through. + sel_t r_sel_q, r_sel_d; + logic r_sel_load; + axi_lite_mst_r_t [DownsizeFactor-2:0] r_chan_mst_q; + logic [DownsizeFactor-2:0] r_chan_mst_load; + for (genvar i = 0; unsigned'(i) < (DownsizeFactor-1); i++) begin : gen_r_chan_ff + assign r_chan_mst_load[i] = (sel_t'(i) == r_sel_q) & mst_res_i.r_valid & mst_req_o.r_ready; + `FFLARN(r_chan_mst_q[i], mst_res_i.r, r_chan_mst_load[i], axi_lite_mst_r_t'{default: '0}, clk_i, rst_ni) + end + assign r_sel_load = mst_res_i.r_valid & mst_req_o.r_ready; + assign r_sel_d = sel_t'(r_sel_q + 1'b1); + `FFLARN(r_sel_q, r_sel_d, r_sel_load, '0, clk_i, rst_ni) + + always_comb begin : proc_r_chan_oup + slv_res_o.r = axi_lite_slv_r_t'{ + resp: mst_res_i.r.resp, + default: '0 + }; + // Response is the OR of all responses + for (int unsigned i = 0; i < (DownsizeFactor-1); i++) begin + slv_res_o.r.resp = slv_res_o.r.resp | r_chan_mst_q[i].resp; + slv_res_o.r.data[i*AxiMstPortDataWidth+:AxiMstPortDataWidth] = r_chan_mst_q[i].data; + end + // The highest bits of the data can be directly the master port. + slv_res_o.r.data[(DownsizeFactor-1)*AxiMstPortDataWidth+:AxiMstPortDataWidth] = + mst_res_i.r.data; + end + + assign slv_res_o.r_valid = (&r_sel_q) ? mst_res_i.r_valid : 1'b0; + assign mst_req_o.r_ready = (&r_sel_q) ? slv_req_i.r_ready : 1'b1; + + end else if (AxiMstPortDataWidth > AxiSlvPortDataWidth) begin : gen_upsizer + // The upsize factor determines the amount of replication. + localparam int unsigned UpsizeFactor = AxiMstPortDataWidth / AxiSlvPortDataWidth; + + // Selection type and offset for the address + localparam int unsigned SelOffset = $clog2(AxiSlvPortStrbWidth); + localparam int unsigned SelWidth = $clog2(UpsizeFactor); + typedef logic [SelWidth-1:0] sel_t; + + // AW channel can be passed through, however block handshake if FIFO is full. + assign mst_req_o.aw = slv_req_i.aw; + // Lock the valid on the master port if it has been given. + logic lock_aw_q, lock_aw_d, load_aw_lock; + // W channel needs a FIFO to determine the silencing of the strobe signal. + logic w_full, w_empty, w_push, w_pop; + sel_t aw_sel, w_sel; + + // AW channel handshake control + always_comb begin : proc_aw_handshake + // default assignment + load_aw_lock = 1'b0; // the FF is toggling back and forth when loaded. + mst_req_o.aw_valid = 1'b0; + slv_res_o.aw_ready = 1'b0; + w_push = 1'b0; + + if (lock_aw_q) begin + mst_req_o.aw_valid = 1'b1; + slv_res_o.aw_ready = mst_res_i.aw_ready; + if (mst_res_i.aw_ready) begin + load_aw_lock = 1'b1; + end + end else begin + // Only connect handshake if there is space in the FIFO + if (!w_full) begin + mst_req_o.aw_valid = slv_req_i.aw_valid; + slv_res_o.aw_ready = mst_res_i.aw_ready; + // If there is a valid on the slave port, push the FIFO + if (slv_req_i.aw_valid) begin + w_push = 1'b1; + // When no transaction, lock AW + if (!mst_res_i.aw_ready) begin + load_aw_lock = 1'b1; + end + end + end + end + end + assign lock_aw_d = ~lock_aw_q; + `FFLARN(lock_aw_q, lock_aw_d, load_aw_lock, 1'b0, clk_i, rst_ni) + + // The selection comes from part of the AW address. + assign aw_sel = sel_t'(slv_req_i.aw.addr >> SelOffset); + + fifo_v3 #( + .FALL_THROUGH ( 1'b1 ), + .DEPTH ( UpsizeFactor ), + .dtype ( sel_t ) + ) i_fifo_w_sel ( + .clk_i, + .rst_ni, + .flush_i ( 1'b0 ), + .testmode_i ( 1'b0 ), + .full_o ( w_full ), + .empty_o ( w_empty ), + .usage_o ( /*not used*/ ), + .data_i ( aw_sel ), + .push_i ( w_push ), + .data_o ( w_sel ), + .pop_i ( w_pop ) + ); + // Pop if there is a W transaction on the master port. + assign w_pop = mst_req_o.w_valid & mst_res_i.w_ready; + + // Replicate Data but silence strobe signal. + assign mst_req_o.w = axi_lite_mst_w_t'{ + data: {UpsizeFactor{slv_req_i.w.data}}, + strb: {AxiMstPortStrbWidth{1'b0}} | (slv_req_i.w.strb << (w_sel * AxiSlvPortStrbWidth)), + default: '0 + }; + + // Connect W handshake if the selection is in the FIFO + assign mst_req_o.w_valid = slv_req_i.w_valid & ~w_empty; + assign slv_res_o.w_ready = mst_res_i.w_ready & ~w_empty; + + + // B channel can be passed through + assign slv_res_o.b = mst_res_i.b; + assign slv_res_o.b_valid = mst_res_i.b_valid; + assign mst_req_o.b_ready = slv_req_i.b_ready; + + + // AR channel can be passed through, however block handshake if FIFO is full. + assign mst_req_o.ar = slv_req_i.ar; + // Lock the valid on the master port if it has been given. + logic lock_ar_q, lock_ar_d, load_ar_lock; + // W channel needs a FIFO to determine the silencing of the strobe signal. + logic r_full, r_empty, r_push, r_pop; + sel_t ar_sel, r_sel; + + // AW channel handshake control + always_comb begin : proc_ar_handshake + // default assignment + load_ar_lock = 1'b0; // the FF is toggling back and forth when loaded. + mst_req_o.ar_valid = 1'b0; + slv_res_o.ar_ready = 1'b0; + r_push = 1'b0; + + if (lock_ar_q) begin + mst_req_o.ar_valid = 1'b1; + slv_res_o.ar_ready = mst_res_i.ar_ready; + if (mst_res_i.ar_ready) begin + load_ar_lock = 1'b1; + end + end else begin + // Only connect handshake if there is space in the FIFO + if (!r_full) begin + mst_req_o.ar_valid = slv_req_i.ar_valid; + slv_res_o.ar_ready = mst_res_i.ar_ready; + // If there is a valid on the slave port, push the FIFO + if (slv_req_i.ar_valid) begin + r_push = 1'b1; + // When no transaction, lock AW + if (!mst_res_i.ar_ready) begin + load_ar_lock = 1'b1; + end + end + end + end + end + assign lock_ar_d = ~lock_ar_q; + `FFLARN(lock_ar_q, lock_ar_d, load_ar_lock, 1'b0, clk_i, rst_ni) + + // The selection comes from part of the AW address. + assign ar_sel = sel_t'(slv_req_i.ar.addr >> SelOffset); + + fifo_v3 #( + .FALL_THROUGH ( 1'b1 ), + .DEPTH ( UpsizeFactor ), + .dtype ( sel_t ) + ) i_fifo_r_sel ( + .clk_i, + .rst_ni, + .flush_i ( 1'b0 ), + .testmode_i ( 1'b0 ), + .full_o ( r_full ), + .empty_o ( r_empty ), + .usage_o ( /*not used*/ ), + .data_i ( ar_sel ), + .push_i ( r_push ), + .data_o ( r_sel ), + .pop_i ( r_pop ) + ); + // Pop if there is a R transaction on the slave port. + assign r_pop = slv_res_o.r_valid & slv_req_i.r_ready; + + // R channel has to be cut out + assign slv_res_o.r = axi_lite_slv_r_t'{ + data: mst_res_i.r.data[(r_sel*AxiSlvPortDataWidth)+:AxiSlvPortDataWidth], + resp: mst_res_i.r.resp, + default: '0 + }; + // Connect R handshake if there is something in the FIFO. + assign slv_res_o.r_valid = mst_res_i.r_valid & ~r_empty; + assign mst_req_o.r_ready = slv_req_i.r_ready & ~r_empty; + + end else begin : gen_passthrough + assign mst_req_o = slv_req_i; + assign slv_res_o = mst_res_i; + end + + // Assertions, check params + // pragma translate_off + `ifndef VERILATOR + initial begin + assume (AxiAddrWidth > 0) else $fatal(1, "AXI address width has to be > 0"); + assume (AxiSlvPortDataWidth > 8) else $fatal(1, "AxiSlvPortDataWidth has to be > 8"); + assume (AxiMstPortDataWidth > 8) else $fatal(1, "AxiMstPortDataWidth has to be > 8"); + assume ($onehot(AxiSlvPortDataWidth)) else $fatal(1, "AxiSlvPortDataWidth must be power of 2"); + assume ($onehot(AxiMstPortDataWidth)) else $fatal(1, "AxiMstPortDataWidth must be power of 2"); + end + default disable iff (~rst_ni); + stable_aw: assert property (@(posedge clk_i) + (mst_req_o.aw_valid && !mst_res_i.aw_ready) |=> $stable(mst_req_o.aw)) else + $fatal(1, "AW must remain stable until handshake happened."); + stable_w: assert property (@(posedge clk_i) + (mst_req_o.w_valid && !mst_res_i.w_ready) |=> $stable(mst_req_o.w)) else + $fatal(1, "W must remain stable until handshake happened."); + stable_b: assert property (@(posedge clk_i) + (slv_res_o.b_valid && !slv_req_i.b_ready) |=> $stable(slv_res_o.b)) else + $fatal(1, "B must remain stable until handshake happened."); + stable_ar: assert property (@(posedge clk_i) + (mst_req_o.ar_valid && !mst_res_i.ar_ready) |=> $stable(mst_req_o.ar)) else + $fatal(1, "AR must remain stable until handshake happened."); + stable_r: assert property (@(posedge clk_i) + (slv_res_o.r_valid && !slv_req_i.r_ready) |=> $stable(slv_res_o.r)) else + $fatal(1, "R must remain stable until handshake happened."); + `endif + // pragma translate_on +endmodule + +/// Interface wrapper for `axi_lite_dw_converter`. +`include "axi/typedef.svh" +`include "axi/assign.svh" +module axi_lite_dw_converter_intf #( + /// AXI4-Lite address width of the ports. + parameter int unsigned AXI_ADDR_WIDTH = 32'd0, + /// AXI4-Lite data width of the slave port. + parameter int unsigned AXI_SLV_PORT_DATA_WIDTH = 32'd0, + /// AXI4-Lite data width of the master port. + parameter int unsigned AXI_MST_PORT_DATA_WIDTH = 32'd0 +) ( + /// Clock, positive edge triggered. + input logic clk_i, + /// Asynchrounous reset, active low. + input logic rst_ni, + /// Slave port interface. + AXI_LITE.Slave slv, + /// Master port interface. + AXI_LITE.Master mst +); + // AXI configuration + localparam int unsigned AxiStrbWidthSlv = AXI_SLV_PORT_DATA_WIDTH / 32'd8; + localparam int unsigned AxiStrbWidthMst = AXI_MST_PORT_DATA_WIDTH / 32'd8; + // Type definitions + typedef logic [AXI_ADDR_WIDTH-1:0] lite_addr_t; + typedef logic [AXI_SLV_PORT_DATA_WIDTH-1:0] lite_data_slv_t; + typedef logic [AxiStrbWidthSlv-1:0] lite_strb_slv_t; + typedef logic [AXI_MST_PORT_DATA_WIDTH-1:0] lite_data_mst_t; + typedef logic [AxiStrbWidthMst-1:0] lite_strb_mst_t; + + + `AXI_LITE_TYPEDEF_AW_CHAN_T(aw_chan_lite_t, lite_addr_t) + `AXI_LITE_TYPEDEF_W_CHAN_T(w_chan_lite_slv_t, lite_data_slv_t, lite_strb_slv_t) + `AXI_LITE_TYPEDEF_W_CHAN_T(w_chan_lite_mst_t, lite_data_mst_t, lite_strb_mst_t) + `AXI_LITE_TYPEDEF_B_CHAN_T(b_chan_lite_t) + + `AXI_LITE_TYPEDEF_AR_CHAN_T(ar_chan_lite_t, lite_addr_t) + `AXI_LITE_TYPEDEF_R_CHAN_T(r_chan_lite_slv_t, lite_data_slv_t) + `AXI_LITE_TYPEDEF_R_CHAN_T(r_chan_lite_mst_t, lite_data_mst_t) + + + `AXI_LITE_TYPEDEF_REQ_T(req_lite_slv_t, aw_chan_lite_t, w_chan_lite_slv_t, ar_chan_lite_t) + `AXI_LITE_TYPEDEF_RESP_T(res_lite_slv_t, b_chan_lite_t, r_chan_lite_slv_t) + + `AXI_LITE_TYPEDEF_REQ_T(req_lite_mst_t, aw_chan_lite_t, w_chan_lite_mst_t, ar_chan_lite_t) + `AXI_LITE_TYPEDEF_RESP_T(res_lite_mst_t, b_chan_lite_t, r_chan_lite_mst_t) + + req_lite_slv_t slv_req; + res_lite_slv_t slv_res; + req_lite_mst_t mst_req; + res_lite_mst_t mst_res; + + `AXI_LITE_ASSIGN_TO_REQ(slv_req, slv) + `AXI_LITE_ASSIGN_FROM_RESP(slv, slv_res) + `AXI_LITE_ASSIGN_FROM_REQ(mst, mst_req) + `AXI_LITE_ASSIGN_TO_RESP(mst_res, mst) + + axi_lite_dw_converter #( + .AxiAddrWidth ( AXI_ADDR_WIDTH ), + .AxiSlvPortDataWidth ( AXI_SLV_PORT_DATA_WIDTH ), + .AxiMstPortDataWidth ( AXI_MST_PORT_DATA_WIDTH ), + .axi_lite_aw_t ( aw_chan_lite_t ), + .axi_lite_slv_w_t ( w_chan_lite_slv_t ), + .axi_lite_mst_w_t ( w_chan_lite_mst_t ), + .axi_lite_b_t ( b_chan_lite_t ), + .axi_lite_ar_t ( ar_chan_lite_t ), + .axi_lite_slv_r_t ( r_chan_lite_slv_t ), + .axi_lite_mst_r_t ( r_chan_lite_mst_t ), + .axi_lite_slv_req_t ( req_lite_slv_t ), + .axi_lite_slv_res_t ( res_lite_slv_t ), + .axi_lite_mst_req_t ( req_lite_mst_t ), + .axi_lite_mst_res_t ( res_lite_mst_t ) + ) i_axi_lite_dw_converter ( + .clk_i, + .rst_ni, + .slv_req_i ( slv_req ), + .slv_res_o ( slv_res ), + .mst_req_o ( mst_req ), + .mst_res_i ( mst_res ) + ); +endmodule diff --git a/src/axi_lite_from_mem.sv b/src/axi_lite_from_mem.sv new file mode 100644 index 000000000..e13793b37 --- /dev/null +++ b/src/axi_lite_from_mem.sv @@ -0,0 +1,247 @@ +// Copyright 2022 ETH Zurich and University of Bologna. +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the "License"); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. +// +// Authors: +// - Wolfgang Roenninger +// - Nicole Narr + +/// Protocol adapter which translates memory requests to the AXI4-Lite protocol. +/// +/// This module acts like an SRAM and makes AXI4-Lite requests downstream. +/// +/// Supports multiple outstanding requests and will have responses for reads **and** writes. +/// Response latency is not fixed and for sure **not 1** and depends on the AXI4-Lite memory system. +/// The `mem_rsp_valid_o` can have multiple cycles of latency from the corresponding `mem_gnt_o`. +/// (Was called `mem_to_axi_lite` - originating from https://github.com/pulp-platform/snitch) +module axi_lite_from_mem #( + /// Memory request address width. + parameter int unsigned MemAddrWidth = 32'd0, + /// AXI4-Lite address width. + parameter int unsigned AxiAddrWidth = 32'd0, + /// Data width in bit of the memory request data **and** the Axi4-Lite data channels. + parameter int unsigned DataWidth = 32'd0, + /// How many requests can be in flight at the same time. (Depth of the response mux FIFO). + parameter int unsigned MaxRequests = 32'd0, + /// Protection signal the module should emit on the AXI4-Lite transactions. + parameter axi_pkg::prot_t AxiProt = 3'b000, + /// AXI4-Lite request struct definition. + parameter type axi_req_t = logic, + /// AXI4-Lite response struct definition. + parameter type axi_rsp_t = logic, + /// Dependent parameter do **not** overwrite! + /// + /// Memory address type, derived from `MemAddrWidth`. + parameter type mem_addr_t = logic[MemAddrWidth-1:0], + /// Dependent parameter do **not** overwrite! + /// + /// AXI4-Lite address type, derived from `AxiAddrWidth`. + parameter type axi_addr_t = logic[AxiAddrWidth-1:0], + /// Dependent parameter do **not** overwrite! + /// + /// Data type for read and write data, derived from `DataWidth`. + /// This is the same for the memory request side **and** the AXI4-Lite `W` and `R` channels. + parameter type data_t = logic[DataWidth-1:0], + /// Dependent parameter do **not** overwrite! + /// + /// Byte enable / AXI4-Lite strobe type, derived from `DataWidth`. + parameter type strb_t = logic[DataWidth/8-1:0] +) ( + /// Clock input, positive edge triggered. + input logic clk_i, + /// Asynchronous reset, active low. + input logic rst_ni, + /// Memory slave port, request is active. + input logic mem_req_i, + /// Memory slave port, request address. + /// + /// Byte address, will be extended or truncated to match `AxiAddrWidth`. + input mem_addr_t mem_addr_i, + /// Memory slave port, request is a write. + /// + /// `0`: Read request. + /// `1`: Write request. + input logic mem_we_i, + /// Memory salve port, write data for request. + input data_t mem_wdata_i, + /// Memory slave port, write byte enable for request. + /// + /// Active high. + input strb_t mem_be_i, + /// Memory request is granted. + output logic mem_gnt_o, + /// Memory slave port, response is valid. For each request, regardless if read or write, + /// this will be active once for one cycle. + output logic mem_rsp_valid_o, + /// Memory slave port, response read data. This is forwarded directly from the AXI4-Lite + /// `R` channel. Only valid for responses generated by a read request. + output data_t mem_rsp_rdata_o, + /// Memory request encountered an error. This is forwarded from the AXI4-Lite error response. + output logic mem_rsp_error_o, + /// AXI4-Lite master port, request output. + output axi_req_t axi_req_o, + /// AXI4-Lite master port, response input. + input axi_rsp_t axi_rsp_i +); + `include "common_cells/registers.svh" + + // Response FIFO control signals. + logic fifo_full, fifo_empty; + // Bookkeeping for sent write beats. + logic aw_sent_q, aw_sent_d; + logic w_sent_q, w_sent_d; + + // Control for translating request to the AXI4-Lite `AW`, `W` and `AR` channels. + always_comb begin + // Default assignments. + axi_req_o.aw = '0; + axi_req_o.aw.addr = axi_addr_t'(mem_addr_i); + axi_req_o.aw.prot = AxiProt; + axi_req_o.aw_valid = 1'b0; + axi_req_o.w = '0; + axi_req_o.w.data = mem_wdata_i; + axi_req_o.w.strb = mem_be_i; + axi_req_o.w_valid = 1'b0; + axi_req_o.ar = '0; + axi_req_o.ar.addr = axi_addr_t'(mem_addr_i); + axi_req_o.ar.prot = AxiProt; + axi_req_o.ar_valid = 1'b0; + // This is also the push signal for the response FIFO. + mem_gnt_o = 1'b0; + // Bookkeeping about sent write channels. + aw_sent_d = aw_sent_q; + w_sent_d = w_sent_q; + + // Control for Request to AXI4-Lite translation. + if (mem_req_i && !fifo_full) begin + if (!mem_we_i) begin + // It is a read request. + axi_req_o.ar_valid = 1'b1; + mem_gnt_o = axi_rsp_i.ar_ready; + end else begin + // Is is a write request, decouple `AW` and `W` channels. + unique case ({aw_sent_q, w_sent_q}) + 2'b00 : begin + // None of the AXI4-Lite writes have been sent jet. + axi_req_o.aw_valid = 1'b1; + axi_req_o.w_valid = 1'b1; + unique case ({axi_rsp_i.aw_ready, axi_rsp_i.w_ready}) + 2'b01 : begin // W is sent, still needs AW. + w_sent_d = 1'b1; + end + 2'b10 : begin // AW is sent, still needs W. + aw_sent_d = 1'b1; + end + 2'b11 : begin // Both are transmitted, grant the write request. + mem_gnt_o = 1'b1; + end + default : /* do nothing */; + endcase + end + 2'b10 : begin + // W has to be sent. + axi_req_o.w_valid = 1'b1; + if (axi_rsp_i.w_ready) begin + aw_sent_d = 1'b0; + mem_gnt_o = 1'b1; + end + end + 2'b01 : begin + // AW has to be sent. + axi_req_o.aw_valid = 1'b1; + if (axi_rsp_i.aw_ready) begin + w_sent_d = 1'b0; + mem_gnt_o = 1'b1; + end + end + default : begin + // Failsafe go to IDLE. + aw_sent_d = 1'b0; + w_sent_d = 1'b0; + end + endcase + end + end + end + + `FFARN(aw_sent_q, aw_sent_d, 1'b0, clk_i, rst_ni) + `FFARN(w_sent_q, w_sent_d, 1'b0, clk_i, rst_ni) + + // Select which response should be forwarded. `1` write response, `0` read response. + logic rsp_sel; + + fifo_v3 #( + .FALL_THROUGH ( 1'b0 ), // No fallthrough for one cycle delay before ready on AXI. + .DEPTH ( MaxRequests ), + .dtype ( logic ) + ) i_fifo_rsp_mux ( + .clk_i, + .rst_ni, + .flush_i ( 1'b0 ), + .testmode_i ( 1'b0 ), + .full_o ( fifo_full ), + .empty_o ( fifo_empty ), + .usage_o ( /*not used*/ ), + .data_i ( mem_we_i ), + .push_i ( mem_gnt_o ), + .data_o ( rsp_sel ), + .pop_i ( mem_rsp_valid_o ) + ); + + // Response selection control. + // If something is in the FIFO, the corresponding channel is ready. + assign axi_req_o.b_ready = !fifo_empty && rsp_sel; + assign axi_req_o.r_ready = !fifo_empty && !rsp_sel; + // Read data is directly forwarded. + assign mem_rsp_rdata_o = axi_rsp_i.r.data; + // Error is taken from the respective channel. + assign mem_rsp_error_o = rsp_sel ? + (axi_rsp_i.b.resp inside {axi_pkg::RESP_SLVERR, axi_pkg::RESP_DECERR}) : + (axi_rsp_i.r.resp inside {axi_pkg::RESP_SLVERR, axi_pkg::RESP_DECERR}); + // Mem response is valid if the handshaking on the respective channel occurs. + // Can not happen at the same time as ready is set from the FIFO. + // This serves as the pop signal for the FIFO. + assign mem_rsp_valid_o = (axi_rsp_i.b_valid && axi_req_o.b_ready) || + (axi_rsp_i.r_valid && axi_req_o.r_ready); + + // pragma translate_off + `ifndef SYNTHESIS + `ifndef VERILATOR + initial begin : proc_assert + assert (MemAddrWidth > 32'd0) else $fatal(1, "MemAddrWidth has to be greater than 0!"); + assert (AxiAddrWidth > 32'd0) else $fatal(1, "AxiAddrWidth has to be greater than 0!"); + assert (DataWidth inside {32'd32, 32'd64}) else + $fatal(1, "DataWidth has to be either 32 or 64 bit!"); + assert (MaxRequests > 32'd0) else $fatal(1, "MaxRequests has to be greater than 0!"); + assert (AxiAddrWidth == $bits(axi_req_o.aw.addr)) else + $fatal(1, "AxiAddrWidth has to match axi_req_o.aw.addr!"); + assert (AxiAddrWidth == $bits(axi_req_o.ar.addr)) else + $fatal(1, "AxiAddrWidth has to match axi_req_o.ar.addr!"); + assert (DataWidth == $bits(axi_req_o.w.data)) else + $fatal(1, "DataWidth has to match axi_req_o.w.data!"); + assert (DataWidth/8 == $bits(axi_req_o.w.strb)) else + $fatal(1, "DataWidth / 8 has to match axi_req_o.w.strb!"); + assert (DataWidth == $bits(axi_rsp_i.r.data)) else + $fatal(1, "DataWidth has to match axi_rsp_i.r.data!"); + end + default disable iff (~rst_ni); + assert property (@(posedge clk_i) (mem_req_i && !mem_gnt_o) |=> mem_req_i) else + $fatal(1, "It is not allowed to deassert the request if it was not granted!"); + assert property (@(posedge clk_i) (mem_req_i && !mem_gnt_o) |=> $stable(mem_addr_i)) else + $fatal(1, "mem_addr_i has to be stable if request is not granted!"); + assert property (@(posedge clk_i) (mem_req_i && !mem_gnt_o) |=> $stable(mem_we_i)) else + $fatal(1, "mem_we_i has to be stable if request is not granted!"); + assert property (@(posedge clk_i) (mem_req_i && !mem_gnt_o) |=> $stable(mem_wdata_i)) else + $fatal(1, "mem_wdata_i has to be stable if request is not granted!"); + assert property (@(posedge clk_i) (mem_req_i && !mem_gnt_o) |=> $stable(mem_be_i)) else + $fatal(1, "mem_be_i has to be stable if request is not granted!"); + `endif + `endif + // pragma translate_on +endmodule diff --git a/src/axi_lite_lfsr.sv b/src/axi_lite_lfsr.sv new file mode 100644 index 000000000..bf8e0f5e5 --- /dev/null +++ b/src/axi_lite_lfsr.sv @@ -0,0 +1,223 @@ +// Copyright 2022 ETH Zurich and University of Bologna. +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the "License"); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. +// +// Authors: +// - Thomas Benz + +`include "common_cells/registers.svh" + +/// AXI4 Lite LFSR Subordinate device. Responds with a pseudo random answer. Serial interface to +/// set the internal state. +module axi_lite_lfsr #( + /// AXI4 Lite Data Width + parameter int unsigned DataWidth = 32'd0, + /// AXI4 Lite request struct definition + parameter type axi_lite_req_t = logic, + /// AXI4 Lite response struct definition + parameter type axi_lite_rsp_t = logic +)( + /// Rising-edge clock + input logic clk_i, + /// Active-low reset + input logic rst_ni, + /// Testmode + input logic testmode_i, + /// AXI4 Lite request struct + input axi_lite_req_t req_i, + /// AXI4 Lite response struct + output axi_lite_rsp_t rsp_o, + /// Serial shift data in (write) + input logic w_ser_data_i, + /// Serial shift data out (write) + output logic w_ser_data_o, + /// Serial shift enable (write) + input logic w_ser_en_i, + /// Serial shift data in (read) + input logic r_ser_data_i, + /// Serial shift data out (read) + output logic r_ser_data_o, + /// Serial shift enable (read) + input logic r_ser_en_i +); + + /// AXI4 Strobe Width + localparam int unsigned StrbWidth = DataWidth / 8; + + logic w_lfsr_en; + logic r_lfsr_en; + + logic w_b_fifo_ready; + + // LFSR outputs + logic [DataWidth-1:0] w_data_in, w_data_out; + + // AW (ignored) + assign rsp_o.aw_ready = !w_ser_en_i; + + // W + axi_opt_lfsr #( + .Width ( DataWidth ) + ) i_axi_opt_lfsr_w ( + .clk_i, + .rst_ni, + .en_i ( w_lfsr_en ), + .ser_data_i ( w_ser_data_i ), + .ser_data_o ( w_ser_data_o ), + .ser_en_i ( w_ser_en_i ), + .inp_en_i ( w_lfsr_en ), + .data_i ( w_data_in ), + .data_o ( w_data_out ) + ); + assign w_lfsr_en = req_i.w_valid & rsp_o.w_ready; + assign rsp_o.w_ready = !w_ser_en_i & w_b_fifo_ready; + + // only write bytes with strobe signal enabled + always_comb begin : gen_data_strb_connect + for (int unsigned i = 0; i < StrbWidth; i++) begin : gen_strb_en + if (req_i.w.strb[i] == 1'b0) begin + w_data_in[i*8+:8] = w_data_out[i*8+:8]; + end else if (req_i.w.strb[i] == 1'b1) begin + w_data_in[i*8+:8] = req_i.w.data[i*8+:8]; + end else begin + w_data_in[i*8+:8] = 'x; + end + end + end + + // B + stream_fifo #( + .FALL_THROUGH ( 1'b0 ), + .DATA_WIDTH ( 'd1 ), + .DEPTH ( 'd2 ) + ) i_stream_fifo_w_b ( + .clk_i, + .rst_ni, + .testmode_i, + .flush_i ( 1'b0 ), + .usage_o ( /* NOT CONNECTED */ ), + .data_i ( 1'b0 ), + .valid_i ( req_i.w_valid ), + .ready_o ( w_b_fifo_ready ), + .data_o ( /* NOT CONNECTED */ ), + .valid_o ( w_b_fifo_valid ), + .ready_i ( req_i.b_ready ) + ); + assign rsp_o.b.resp = axi_pkg::RESP_OKAY; + assign rsp_o.b_valid = w_b_fifo_valid; + + // AR (ignored) + assign rsp_o.ar_ready = !w_ser_en_i; + + // R + axi_opt_lfsr #( + .Width ( DataWidth ) + ) i_axi_opt_lfsr_r ( + .clk_i, + .rst_ni, + .en_i ( r_lfsr_en ), + .ser_data_i ( r_ser_data_i ), + .ser_data_o ( r_ser_data_o ), + .ser_en_i ( r_ser_en_i ), + .inp_en_i ( 1'b0 ), + .data_i ( /* NOT CONNECTED */ ), + .data_o ( rsp_o.r.data ) + ); + assign rsp_o.r.resp = axi_pkg::RESP_OKAY; + assign r_lfsr_en = req_i.r_ready & rsp_o.r_valid; + assign rsp_o.r_valid = !r_ser_en_i; + +endmodule : axi_lite_lfsr + + +/// XOR LFSR with tabs based on the [lfsr_table](https://datacipy.cz/lfsr_table.pdf). LFSR has +/// a serial interface to set the initial state +module axi_opt_lfsr #( + parameter int unsigned Width = 32'd0 +) ( + /// Rising-edge clock + input logic clk_i, + /// Active-low reset + input logic rst_ni, + input logic en_i, + input logic ser_data_i, + output logic ser_data_o, + input logic ser_en_i, + input logic inp_en_i, + input logic [Width-1:0] data_i, + output logic [Width-1:0] data_o +); + + /// Number of bits required to hold the LFSR tab configuration + localparam int unsigned LfsrIdxWidth = cf_math_pkg::idx_width(Width); + /// Maximum number of tabs + localparam int unsigned MaxNumTabs = 4; + + /// Type specifying the tap positions + typedef logic [LfsrIdxWidth:0] xnor_entry_t [MaxNumTabs-1:0]; + xnor_entry_t XnorFeedback; + + // the shift register + logic [Width-1:0] reg_d, reg_q; + + // the feedback signal + logic xnor_feedback; + + always_comb begin : gen_register + + // get the parameters + case (Width) + 'd8 : XnorFeedback = { 'd8, 'd6, 'd5, 'd4 }; + 'd16 : XnorFeedback = { 'd16, 'd14, 'd13, 'd11 }; + 'd32 : XnorFeedback = { 'd32, 'd30, 'd26, 'd25 }; + 'd64 : XnorFeedback = { 'd64, 'd63, 'd61, 'd60 }; + 'd128 : XnorFeedback = { 'd128, 'd127, 'd126, 'd119 }; + 'd256 : XnorFeedback = { 'd256, 'd256, 'd521, 'd246 }; + 'd512 : XnorFeedback = { 'd512, 'd510, 'd507, 'd504 }; + 'd1024 : XnorFeedback = { 'd1024, 'd1015, 'd1002, 'd1001 }; + default : XnorFeedback = { 'x, 'x, 'x, 'x }; + endcase + + // shift register functionality + // compression mode + if (inp_en_i) begin + for (int unsigned i = 0; i < Width - 1; i++) begin : gen_comp_conection + reg_d[i] = reg_q[i+1] ^ data_i[i]; + end + // generation mode + end else begin + for (int unsigned i = 0; i < Width - 1; i++) begin : gen_gen_conection + reg_d[i] = reg_q[i+1]; + end + end + // serial access mode + if (ser_en_i) begin + // new head element + reg_d[Width-1] = ser_data_i; + // LFSR mode + end else begin + xnor_feedback = reg_q[XnorFeedback[MaxNumTabs-1]-1]; + for (int unsigned t = 0; t < MaxNumTabs - 1; t++) begin : gen_feedback_path + xnor_feedback = xnor_feedback; + if (XnorFeedback[t] != 0) begin + xnor_feedback = xnor_feedback ^ reg_q[XnorFeedback[t]-1]; + end + end + reg_d[Width-1] = inp_en_i ? xnor_feedback ^ data_i[Width-1] : xnor_feedback; + end + end + + // connect outputs + assign ser_data_o = reg_q[0]; + assign data_o = reg_q; + + // state + `FFL(reg_q, reg_d, en_i | ser_en_i, '1, clk_i, rst_ni) + +endmodule : axi_opt_lfsr diff --git a/src/axi_lite_mux.sv b/src/axi_lite_mux.sv index 75a9a256c..577280279 100644 --- a/src/axi_lite_mux.sv +++ b/src/axi_lite_mux.sv @@ -283,7 +283,7 @@ module axi_lite_mux #( // W Channel //-------------------------------------- // multiplexer - assign mst_w_chan = (!w_fifo_empty && !b_fifo_full) ? slv_reqs_i[w_select].w : '0; + assign mst_w_chan = slv_reqs_i[w_select].w; assign mst_w_valid = (!w_fifo_empty && !b_fifo_full) ? slv_reqs_i[w_select].w_valid : 1'b0; for (genvar i = 0; i < NoSlvPorts; i++) begin : gen_slv_w_ready assign slv_resps_o[i].w_ready = mst_w_ready & ~w_fifo_empty & diff --git a/src/axi_multicut.sv b/src/axi_multicut.sv index 1e42c2da5..64a354a4c 100644 --- a/src/axi_multicut.sv +++ b/src/axi_multicut.sv @@ -11,8 +11,8 @@ // // Authors: // - Wolfgang Roenninger -// - Fabian Schuiki // - Andreas Kurth +// - Fabian Schuiki // - Stefan Mach // Multiple AXI4 cuts. diff --git a/src/axi_pkg.sv b/src/axi_pkg.sv index 92ede558c..dd60c451e 100644 --- a/src/axi_pkg.sv +++ b/src/axi_pkg.sv @@ -12,33 +12,58 @@ // Authors: // - Andreas Kurth // - Florian Zaruba +// - Thomas Benz // - Wolfgang Roenninger // - Fabian Schuiki +// - Cyril Koenig // - Matheus Cavalcante //! AXI Package /// Contains all necessary type definitions, constants, and generally useful functions. package axi_pkg; - /// AXI Transaction Burst Type. - typedef logic [1:0] burst_t; + /// AXI Transaction Burst Width. + parameter int unsigned BurstWidth = 32'd2; + /// AXI Transaction Response Width. + parameter int unsigned RespWidth = 32'd2; + /// AXI Transaction Cacheability Width. + parameter int unsigned CacheWidth = 32'd4; + /// AXI Transaction Protection Width. + parameter int unsigned ProtWidth = 32'd3; + /// AXI Transaction Quality of Service Width. + parameter int unsigned QosWidth = 32'd4; + /// AXI Transaction Region Width. + parameter int unsigned RegionWidth = 32'd4; + /// AXI Transaction Length Width. + parameter int unsigned LenWidth = 32'd8; + /// AXI Transaction Size Width. + parameter int unsigned SizeWidth = 32'd3; + /// AXI Lock Width. + parameter int unsigned LockWidth = 32'd1; + /// AXI5 Atomic Operation Width. + parameter int unsigned AtopWidth = 32'd6; + /// AXI5 Non-Secure Address Identifier. + parameter int unsigned NsaidWidth = 32'd4; + + /// AXI Transaction Burst Width. + typedef logic [1:0] burst_t; /// AXI Transaction Response Type. - typedef logic [1:0] resp_t; + typedef logic [1:0] resp_t; /// AXI Transaction Cacheability Type. - typedef logic [3:0] cache_t; + typedef logic [3:0] cache_t; /// AXI Transaction Protection Type. - typedef logic [2:0] prot_t; + typedef logic [2:0] prot_t; /// AXI Transaction Quality of Service Type. - typedef logic [3:0] qos_t; + typedef logic [3:0] qos_t; /// AXI Transaction Region Type. typedef logic [3:0] region_t; /// AXI Transaction Length Type. - typedef logic [7:0] len_t; + typedef logic [7:0] len_t; /// AXI Transaction Size Type. - typedef logic [2:0] size_t; + typedef logic [2:0] size_t; /// AXI5 Atomic Operation Type. - typedef logic [5:0] atop_t; // atomic operations + typedef logic [5:0] atop_t; // atomic operations /// AXI5 Non-Secure Address Identifier. - typedef logic [3:0] nsaid_t; + typedef logic [3:0] nsaid_t; /// In a fixed burst: /// - The address is the same for every transfer in the burst. @@ -175,7 +200,7 @@ package axi_pkg; beat_lower_byte(largest_addr_t addr, size_t size, len_t len, burst_t burst, shortint unsigned strobe_width, shortint unsigned i_beat); largest_addr_t _addr = beat_addr(addr, size, len, burst, i_beat); - return _addr - (_addr / strobe_width) * strobe_width; + return shortint'(_addr - (_addr / strobe_width) * strobe_width); endfunction /// Index of highest byte in beat (see A3-51). @@ -290,6 +315,65 @@ package axi_pkg; endcase endfunction + /// AW Width: Returns the width of the AW channel payload + function automatic int unsigned aw_width(int unsigned addr_width, int unsigned id_width, + int unsigned user_width ); + // Sum the individual bit widths of the signals + return (id_width + addr_width + LenWidth + SizeWidth + BurstWidth + LockWidth + CacheWidth + + ProtWidth + QosWidth + RegionWidth + AtopWidth + user_width ); + endfunction + + /// W Width: Returns the width of the W channel payload + function automatic int unsigned w_width(int unsigned data_width, int unsigned user_width ); + // Sum the individual bit widths of the signals + return (data_width + data_width / 32'd8 + 32'd1 + user_width); + // ^- StrobeWidth ^- LastWidth + endfunction + + /// B Width: Returns the width of the B channel payload + function automatic int unsigned b_width(int unsigned id_width, int unsigned user_width ); + // Sum the individual bit widths of the signals + return (id_width + RespWidth + user_width); + endfunction + + /// AR Width: Returns the width of the AR channel payload + function automatic int unsigned ar_width(int unsigned addr_width, int unsigned id_width, + int unsigned user_width ); + // Sum the individual bit widths of the signals + return (id_width + addr_width + LenWidth + SizeWidth + BurstWidth + LockWidth + CacheWidth + + ProtWidth + QosWidth + RegionWidth + user_width ); + endfunction + + /// R Width: Returns the width of the R channel payload + function automatic int unsigned r_width(int unsigned data_width, int unsigned id_width, + int unsigned user_width ); + // Sum the individual bit widths of the signals + return (id_width + data_width + RespWidth + 32'd1 + user_width); + // ^- LastWidth + endfunction + + /// Request Width: Returns the width of the request channel + function automatic int unsigned req_width(int unsigned addr_width, int unsigned data_width, + int unsigned id_width, int unsigned aw_user_width, + int unsigned ar_user_width, int unsigned w_user_width ); + // Sum the individual bit widths of the signals and their handshakes + // v- valids + return (aw_width(addr_width, id_width, aw_user_width) + 32'd1 + + w_width(data_width, w_user_width) + 32'd1 + + ar_width(addr_width, id_width, ar_user_width) + 32'd1 + 32'd1 + 32'd1 ); + // ^- R, ^- B ready + endfunction + + /// Response Width: Returns the width of the response channel + function automatic int unsigned rsp_width(int unsigned data_width, int unsigned id_width, + int unsigned r_user_width, int unsigned b_user_width ); + // Sum the individual bit widths of the signals and their handshakes + // v- valids + return (r_width(data_width, id_width, r_user_width) + 32'd1 + + b_width(id_width, b_user_width) + 32'd1 + 32'd1 + 32'd1 + 32'd1); + // ^- AW, ^- AR, ^- W ready + endfunction + // ATOP[5:0] /// - Sends a single data value with an address. /// - The target swaps the value at the addressed location with the data value that is supplied in @@ -393,17 +477,44 @@ package axi_pkg; /// Configuration for `axi_xbar`. typedef struct packed { + /// Number of slave ports of the crossbar. + /// This many master modules are connected to it. int unsigned NoSlvPorts; + /// Number of master ports of the crossbar. + /// This many slave modules are connected to it. int unsigned NoMstPorts; + /// Maximum number of open transactions each master connected to the crossbar can have in + /// flight at the same time. int unsigned MaxMstTrans; + /// Maximum number of open transactions each slave connected to the crossbar can have in + /// flight at the same time. int unsigned MaxSlvTrans; + /// Determine if the internal FIFOs of the crossbar are instantiated in fallthrough mode. + /// 0: No fallthrough + /// 1: Fallthrough bit FallThrough; + /// The Latency mode of the xbar. This determines if the channels on the ports have + /// a spill register instantiated. + /// Example configurations are provided with the enum `xbar_latency_e`. xbar_latency_e LatencyMode; + /// This is the number of `axi_multicut` stages instantiated in the line cross of the channels. + /// Having multiple stages can potentially add a large number of FFs! + int unsigned PipelineStages; + /// AXI ID width of the salve ports. The ID width of the master ports is determined + /// Automatically. See `axi_mux` for details. int unsigned AxiIdWidthSlvPorts; + /// The used ID portion to determine if a different salve is used for the same ID. + /// See `axi_demux` for details. int unsigned AxiIdUsedSlvPorts; + /// Are IDs unique? bit UniqueIds; + /// AXI4+ATOP address field width. int unsigned AxiAddrWidth; + /// AXI4+ATOP data field width. int unsigned AxiDataWidth; + /// The number of address rules defined for routing of the transactions. + /// Each master port can have multiple rules, should have however at least one. + /// If a transaction can not be routed the xbar will answer with an `axi_pkg::RESP_DECERR`. int unsigned NoAddrRules; } xbar_cfg_t; @@ -420,4 +531,5 @@ package axi_pkg; logic [31:0] start_addr; logic [31:0] end_addr; } xbar_rule_32_t; + endpackage diff --git a/src/axi_rw_join.sv b/src/axi_rw_join.sv new file mode 100644 index 000000000..6b7191b38 --- /dev/null +++ b/src/axi_rw_join.sv @@ -0,0 +1,110 @@ +// Copyright (c) 2022 ETH Zurich, University of Bologna +// +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the "License"); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. +// +// Authors: +// - Tobias Senti + +`include "axi/assign.svh" +`include "common_cells/assertions.svh" + +/// Joins a read and a write slave into one single read / write master +/// +/// Connects the ar and r channel of the read slave to the read / write master +/// and the aw, w and b channel of the write slave to the read / write master +module axi_rw_join #( + parameter type axi_req_t = logic, + parameter type axi_resp_t = logic +) ( + input logic clk_i, + input logic rst_ni, + // Read Slave + input axi_req_t slv_read_req_i, + output axi_resp_t slv_read_resp_o, + + // Write Slave + input axi_req_t slv_write_req_i, + output axi_resp_t slv_write_resp_o, + + // Read / Write Master + output axi_req_t mst_req_o, + input axi_resp_t mst_resp_i +); + + //-------------------------------------- + // Read channel data + //-------------------------------------- + + // Assign Read Structs + `AXI_ASSIGN_AR_STRUCT ( mst_req_o.ar , slv_read_req_i.ar ) + `AXI_ASSIGN_R_STRUCT ( slv_read_resp_o.r , mst_resp_i.r ) + + // Read B channel data + assign slv_read_resp_o.b = '0; + + + //-------------------------------------- + // Read channel handshakes + //-------------------------------------- + + // Read AR channel handshake + assign mst_req_o.ar_valid = slv_read_req_i.ar_valid; + assign slv_read_resp_o.ar_ready = mst_resp_i.ar_ready; + + // Read R channel handshake + assign slv_read_resp_o.r_valid = mst_resp_i.r_valid; + assign mst_req_o.r_ready = slv_read_req_i.r_ready; + + // Read AW, W and B handshake + assign slv_read_resp_o.aw_ready = 1'b0; + assign slv_read_resp_o.w_ready = 1'b0; + assign slv_read_resp_o.b_valid = 1'b0; + + // check for AW and W never to be valid + `ASSERT_NEVER(slv_read_req_aw_valid, slv_read_req_i.aw_valid, clk_i, !rst_ni) + `ASSERT_NEVER(slv_read_req_w_valid, slv_read_req_i.w_valid, clk_i, !rst_ni) + + //-------------------------------------- + // Write channel data + //-------------------------------------- + + // Assign Write Structs + `AXI_ASSIGN_AW_STRUCT ( mst_req_o.aw , slv_write_req_i.aw ) + `AXI_ASSIGN_W_STRUCT ( mst_req_o.w , slv_write_req_i.w ) + `AXI_ASSIGN_B_STRUCT ( slv_write_resp_o.b , mst_resp_i.b ) + + // Write R channel data + assign slv_write_resp_o.r = '0; + + + //-------------------------------------- + // Write channel handshakes + //-------------------------------------- + + // Write AR and R channel handshake + assign slv_write_resp_o.ar_ready = 1'b0; + assign slv_write_resp_o.r_valid = 1'b0; + + // check for AR to never be valid + `ASSERT_NEVER(slv_write_req_ar_valid, slv_write_req_i.ar_valid, clk_i, !rst_ni) + + // Write AW channel handshake + assign mst_req_o.aw_valid = slv_write_req_i.aw_valid; + assign slv_write_resp_o.aw_ready = mst_resp_i.aw_ready; + + // Write W channel handshake + assign mst_req_o.w_valid = slv_write_req_i.w_valid; + assign slv_write_resp_o.w_ready = mst_resp_i.w_ready; + + // Write B channel handshake + assign slv_write_resp_o.b_valid = mst_resp_i.b_valid; + assign mst_req_o.b_ready = slv_write_req_i.b_ready; + +endmodule : axi_rw_join diff --git a/src/axi_rw_split.sv b/src/axi_rw_split.sv new file mode 100644 index 000000000..6fa96dfa9 --- /dev/null +++ b/src/axi_rw_split.sv @@ -0,0 +1,111 @@ +// Copyright (c) 2022 ETH Zurich, University of Bologna +// +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the "License"); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. +// +// Authors: +// - Tobias Senti + +`include "axi/assign.svh" +`include "common_cells/assertions.svh" + +/// Splits a single read / write slave into one read and one write master +/// +/// Connects the ar and r channel of the read / write slave to the read master +/// and the aw, w and b channel of the read / write slave to the write master +module axi_rw_split #( + parameter type axi_req_t = logic, + parameter type axi_resp_t = logic +) ( + input logic clk_i, + input logic rst_ni, + // Read / Write Slave + input axi_req_t slv_req_i, + output axi_resp_t slv_resp_o, + + // Read Master + output axi_req_t mst_read_req_o, + input axi_resp_t mst_read_resp_i, + + // Write Master + output axi_req_t mst_write_req_o, + input axi_resp_t mst_write_resp_i +); + + //-------------------------------------- + // Read channel data + //-------------------------------------- + + // Assign Read channel structs + `AXI_ASSIGN_AR_STRUCT ( mst_read_req_o.ar , slv_req_i.ar ) + `AXI_ASSIGN_R_STRUCT ( slv_resp_o.r , mst_read_resp_i.r ) + + // Read AW and W channel data + assign mst_read_req_o.aw = '0; + assign mst_read_req_o.w = '0; + + + //-------------------------------------- + // Read channel handshakes + //-------------------------------------- + + // Read AR channel handshake + assign mst_read_req_o.ar_valid = slv_req_i.ar_valid; + assign slv_resp_o.ar_ready = mst_read_resp_i.ar_ready; + + // Read R channel handshake + assign slv_resp_o.r_valid = mst_read_resp_i.r_valid; + assign mst_read_req_o.r_ready = slv_req_i.r_ready; + + // Read AW, W and B handshake + assign mst_read_req_o.aw_valid = 1'b0; + assign mst_read_req_o.w_valid = 1'b0; + assign mst_read_req_o.b_ready = 1'b0; + + // check for B never to be valid + `ASSERT_NEVER(mst_read_resp_b_valid, mst_read_resp_i.b_valid, clk_i, !rst_ni) + + + //-------------------------------------- + // Write channel data + //-------------------------------------- + + // Assign Write channel structs + `AXI_ASSIGN_AW_STRUCT ( mst_write_req_o.aw , slv_req_i.aw ) + `AXI_ASSIGN_W_STRUCT ( mst_write_req_o.w , slv_req_i.w ) + `AXI_ASSIGN_B_STRUCT ( slv_resp_o.b , mst_write_resp_i.b ) + + // Write AR channel data + assign mst_write_req_o.ar = '0; + + + //-------------------------------------- + // Write channel handshakes + //-------------------------------------- + + // Write AR and R channel handshake + assign mst_write_req_o.ar_valid = 1'b0; + assign mst_write_req_o.r_ready = 1'b0; + + // check for R never to be valid + `ASSERT_NEVER(mst_read_resp_r_valid, mst_read_resp_i.r_valid, clk_i, !rst_ni) + + // Write AW channel handshake + assign mst_write_req_o.aw_valid = slv_req_i.aw_valid; + assign slv_resp_o.aw_ready = mst_write_resp_i.aw_ready; + + // Write W channel handshake + assign mst_write_req_o.w_valid = slv_req_i.w_valid; + assign slv_resp_o.w_ready = mst_write_resp_i.w_ready; + + // Write B channel handshake + assign slv_resp_o.b_valid = mst_write_resp_i.b_valid; + assign mst_write_req_o.b_ready = slv_req_i.b_ready; + +endmodule : axi_rw_split diff --git a/src/axi_sim_mem.sv b/src/axi_sim_mem.sv index a518be9a0..b8b6b12c0 100644 --- a/src/axi_sim_mem.sv +++ b/src/axi_sim_mem.sv @@ -3,6 +3,9 @@ // // Authors: // - Andreas Kurth +// - Samuel Riedel +// - Michael Rogenmoser +// - Thomas Benz `include "axi/typedef.svh" @@ -14,6 +17,8 @@ /// axi_sim_mem #( ... ) i_sim_mem ( ... ); /// initial begin /// $readmemh("file_with_memory_addrs_and_data.mem", i_sim_mem.mem); +/// $readmemh("file_with_memory_addrs_and_read_errors.mem", i_sim_mem.rerr); +/// $readmemh("file_with_memory_addrs_and_write_errors.mem", i_sim_mem.werr); /// end /// ``` /// `mem` is addressed (or indexed) byte-wise with `AddrWidth`-wide addresses. @@ -34,6 +39,8 @@ module axi_sim_mem #( parameter type axi_rsp_t = logic, /// Warn on accesses to uninitialized bytes parameter bit WarnUninitialized = 1'b0, + /// Clear error on access + parameter bit ClearErrOnAccess = 1'b0, /// Application delay (measured after rising clock edge) parameter time ApplDelay = 0ps, /// Acquisition delay (measured after rising clock edge) @@ -104,7 +111,12 @@ module axi_sim_mem #( } monitor_t; monitor_t mon_w, mon_r; - logic [7:0] mem[addr_t]; + logic [7:0] mem[addr_t]; + axi_pkg::resp_t rerr[addr_t] = '{default: axi_pkg::RESP_OKAY}; + axi_pkg::resp_t werr[addr_t] = '{default: axi_pkg::RESP_OKAY}; + + // error happened in write burst + axi_pkg::resp_t error_happened = axi_pkg::RESP_OKAY; initial begin automatic ar_t ar_queue[$]; @@ -156,16 +168,20 @@ module axi_sim_mem #( if (axi_req_i.w.strb[i_byte]) begin automatic addr_t byte_addr = (addr / StrbWidth) * StrbWidth + i_byte; mem[byte_addr] = axi_req_i.w.data[i_byte*8+:8]; + error_happened = axi_pkg::resp_precedence(werr[byte_addr], error_happened); + if (ClearErrOnAccess) + werr[byte_addr] = axi_pkg::RESP_OKAY; end end if (w_cnt == aw_queue[0].len) begin automatic b_t b_beat = '0; assert (axi_req_i.w.last) else $error("Expected last beat of W burst!"); b_beat.id = aw_queue[0].id; - b_beat.resp = axi_pkg::RESP_OKAY; + b_beat.resp = error_happened; b_queue.push_back(b_beat); w_cnt = 0; mon_w.last = 1'b1; + error_happened = axi_pkg::RESP_OKAY; void'(aw_queue.pop_front()); end else begin assert (!axi_req_i.w.last) else $error("Did not expect last beat of W burst!"); @@ -211,6 +227,7 @@ module axi_sim_mem #( automatic axi_pkg::size_t size = ar_queue[0].size; automatic addr_t addr = axi_pkg::beat_addr(ar_queue[0].addr, size, len, burst, r_cnt); automatic r_t r_beat = '0; + automatic data_t r_data = 'x; // compatibility reasons r_beat.data = 'x; r_beat.id = ar_queue[0].id; r_beat.resp = axi_pkg::RESP_OKAY; @@ -224,11 +241,16 @@ module axi_sim_mem #( $warning("Access to non-initialized byte at address 0x%016x by ID 0x%x.", byte_addr, r_beat.id); end - r_beat.data[i_byte*8+:8] = 'x; + r_data[i_byte*8+:8] = 'x; end else begin - r_beat.data[i_byte*8+:8] = mem[byte_addr]; + r_data[i_byte*8+:8] = mem[byte_addr]; + end + r_beat.resp = axi_pkg::resp_precedence(rerr[byte_addr], r_beat.resp); + if (ClearErrOnAccess & axi_req_i.r_ready) begin + rerr[byte_addr] = axi_pkg::RESP_OKAY; end end + r_beat.data = r_data; if (r_cnt == ar_queue[0].len) begin r_beat.last = 1'b1; mon_r.last = 1'b1; @@ -320,6 +342,7 @@ module axi_sim_mem_intf #( parameter int unsigned AXI_ID_WIDTH = 32'd0, parameter int unsigned AXI_USER_WIDTH = 32'd0, parameter bit WARN_UNINITIALIZED = 1'b0, + parameter bit ClearErrOnAccess = 1'b0, parameter time APPL_DELAY = 0ps, parameter time ACQ_DELAY = 0ps ) ( @@ -363,6 +386,7 @@ module axi_sim_mem_intf #( .axi_req_t (axi_req_t), .axi_rsp_t (axi_resp_t), .WarnUninitialized (WARN_UNINITIALIZED), + .ClearErrOnAccess (ClearErrOnAccess), .ApplDelay (APPL_DELAY), .AcqDelay (ACQ_DELAY) ) i_sim_mem ( diff --git a/src/axi_slave_compare.sv b/src/axi_slave_compare.sv new file mode 100644 index 000000000..a4442985c --- /dev/null +++ b/src/axi_slave_compare.sv @@ -0,0 +1,185 @@ +// Copyright (c) 2019-2020 ETH Zurich, University of Bologna +// +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the "License"); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. +// +// Authors: +// - Thomas Benz + +`include "axi/assign.svh" +/// Synthesizable test module comparing two AXI slaves of the same type. +/// The reference response is always passed to the master, whereas the test response +/// is discarded after handshaking. +/// This module is meant to be used in FPGA-based verification. +module axi_slave_compare #( + /// ID width of the AXI4+ATOP interface + parameter int unsigned AxiIdWidth = 32'd0, + /// FIFO depth + parameter int unsigned FifoDepth = 32'd0, + /// AW channel type of the AXI4+ATOP interface + parameter type axi_aw_chan_t = logic, + /// W channel type of the AXI4+ATOP interface + parameter type axi_w_chan_t = logic, + /// B channel type of the AXI4+ATOP interface + parameter type axi_b_chan_t = logic, + /// AR channel type of the AXI4+ATOP interface + parameter type axi_ar_chan_t = logic, + /// R channel type of the AXI4+ATOP interface + parameter type axi_r_chan_t = logic, + /// Request struct type of the AXI4+ATOP slave port + parameter type axi_req_t = logic, + /// Response struct type of the AXI4+ATOP slave port + parameter type axi_rsp_t = logic, + /// ID type (*do not overwrite*) + parameter type id_t = logic [2**AxiIdWidth-1:0] +)( + /// Clock + input logic clk_i, + /// Asynchronous reset, active low + input logic rst_ni, + /// Testmode + input logic testmode_i, + /// AXI4+ATOP channel request in + input axi_req_t axi_mst_req_i, + /// AXI4+ATOP channel response out + output axi_rsp_t axi_mst_rsp_o, + /// AXI4+ATOP reference channel request out + output axi_req_t axi_ref_req_o, + /// AXI4+ATOP reference channel response in + input axi_rsp_t axi_ref_rsp_i, + /// AXI4+ATOP test channel request out + output axi_req_t axi_test_req_o, + /// AXI4+ATOP test channel response in + input axi_rsp_t axi_test_rsp_i, + /// AW mismatch + output id_t aw_mismatch_o, + /// W mismatch + output logic w_mismatch_o, + /// B mismatch + output id_t b_mismatch_o, + /// AR mismatch + output id_t ar_mismatch_o, + /// R mismatch + output id_t r_mismatch_o, + /// General mismatch + output logic mismatch_o, + /// Unit is busy + output logic busy_o +); + + axi_req_t axi_ref_req_in, axi_test_req_in; + axi_rsp_t axi_ref_rsp_in, axi_test_rsp_in; + + logic aw_valid_ref, aw_ready_ref; + logic w_valid_ref, w_ready_ref; + logic ar_valid_ref, ar_ready_ref; + + logic aw_valid_test, aw_ready_test; + logic w_valid_test, w_ready_test; + logic ar_valid_test, ar_ready_test; + + logic aw_ready_mst; + logic w_ready_mst; + logic ar_ready_mst; + + stream_fork #( + .N_OUP ( 32'd2 ) + ) i_stream_fork_aw ( + .clk_i, + .rst_ni, + .valid_i ( axi_mst_req_i.aw_valid ), + .ready_o ( aw_ready_mst ), + .valid_o ( { aw_valid_ref, aw_valid_test } ), + .ready_i ( { aw_ready_ref, aw_ready_test } ) + ); + + stream_fork #( + .N_OUP ( 32'd2 ) + ) i_stream_fork_ar ( + .clk_i, + .rst_ni, + .valid_i ( axi_mst_req_i.ar_valid ), + .ready_o ( ar_ready_mst ), + .valid_o ( { ar_valid_ref, ar_valid_test } ), + .ready_i ( { ar_ready_ref, ar_ready_test } ) + ); + + stream_fork #( + .N_OUP ( 32'd2 ) + ) i_stream_fork_w ( + .clk_i, + .rst_ni, + .valid_i ( axi_mst_req_i.w_valid ), + .ready_o ( w_ready_mst ), + .valid_o ( { w_valid_ref, w_valid_test } ), + .ready_i ( { w_ready_ref, w_ready_test } ) + ); + + // assemble buses + always_comb begin + // request + `AXI_SET_REQ_STRUCT(axi_ref_req_in, axi_mst_req_i) + `AXI_SET_REQ_STRUCT(axi_test_req_in, axi_mst_req_i) + // overwrite valids in requests + axi_ref_req_in.aw_valid = aw_valid_ref; + axi_ref_req_in.ar_valid = ar_valid_ref; + axi_ref_req_in.w_valid = w_valid_ref; + axi_test_req_in.aw_valid = aw_valid_test; + axi_test_req_in.ar_valid = ar_valid_test; + axi_test_req_in.w_valid = w_valid_test; + // get readies + aw_ready_ref = axi_ref_rsp_in.aw_ready; + ar_ready_ref = axi_ref_rsp_in.ar_ready; + w_ready_ref = axi_ref_rsp_in.w_ready; + aw_ready_test = axi_test_rsp_in.aw_ready; + ar_ready_test = axi_test_rsp_in.ar_ready; + w_ready_test = axi_test_rsp_in.w_ready; + // response + `AXI_SET_RESP_STRUCT(axi_mst_rsp_o, axi_ref_rsp_in) + // overwrite readies + axi_mst_rsp_o.aw_ready = aw_ready_mst; + axi_mst_rsp_o.w_ready = w_ready_mst; + axi_mst_rsp_o.ar_ready = ar_ready_mst; + // b interface is not used + axi_test_req_in.r_ready = '1; + axi_test_req_in.b_ready = '1; + end + + axi_bus_compare #( + .AxiIdWidth ( AxiIdWidth ), + .FifoDepth ( FifoDepth ), + .axi_aw_chan_t ( axi_aw_chan_t ), + .axi_w_chan_t ( axi_w_chan_t ), + .axi_b_chan_t ( axi_b_chan_t ), + .axi_ar_chan_t ( axi_ar_chan_t ), + .axi_r_chan_t ( axi_r_chan_t ), + .axi_req_t ( axi_req_t ), + .axi_rsp_t ( axi_rsp_t ) + ) i_axi_bus_compare ( + .clk_i, + .rst_ni, + .testmode_i, + .aw_mismatch_o, + .w_mismatch_o, + .b_mismatch_o, + .ar_mismatch_o, + .r_mismatch_o, + .mismatch_o, + .busy_o, + .axi_a_req_i ( axi_ref_req_in ), + .axi_a_rsp_o ( axi_ref_rsp_in ), + .axi_a_req_o ( axi_ref_req_o ), + .axi_a_rsp_i ( axi_ref_rsp_i ), + .axi_b_req_i ( axi_test_req_in ), + .axi_b_rsp_o ( axi_test_rsp_in ), + .axi_b_req_o ( axi_test_req_o ), + .axi_b_rsp_i ( axi_test_rsp_i ) + ); + +endmodule diff --git a/src/axi_test.sv b/src/axi_test.sv index 1cac11c88..cc0dbeeaa 100644 --- a/src/axi_test.sv +++ b/src/axi_test.sv @@ -10,10 +10,10 @@ // specific language governing permissions and limitations under the License. // // Authors: -// - Wolfgang Roenninger // - Andreas Kurth +// - Wolfgang Roenninger // - Fabian Schuiki -// - Florian Zaruba +// - Thomas Benz // - Matheus Cavalcante @@ -699,6 +699,7 @@ package axi_test; parameter int RESP_MIN_WAIT_CYCLES = 0, parameter int RESP_MAX_WAIT_CYCLES = 20, // AXI feature usage + parameter int SIZE_ALIGN = 0, parameter int AXI_MAX_BURST_LEN = 0, // maximum number of beats in burst; 0 = AXI max (256) parameter int TRAFFIC_SHAPING = 0, parameter bit AXI_EXCLS = 1'b0, @@ -927,7 +928,7 @@ package axi_test; end end - ax_beat.ax_addr = addr; + ax_beat.ax_addr = axi_pkg::aligned_addr(addr, axi_pkg::size_t'(SIZE_ALIGN) ); rand_success = std::randomize(id); assert(rand_success); rand_success = std::randomize(qos); assert(rand_success); // The random ID *must* be legalized with `legalize_id()` before the beat is sent! This is @@ -1190,13 +1191,16 @@ package axi_test; static logic rand_success; wait (w_queue.size() > 0 || (aw_done && w_queue.size() == 0)); aw_beat = w_queue.pop_front(); - addr = aw_beat.ax_addr; for (int unsigned i = 0; i < aw_beat.ax_len + 1; i++) begin automatic w_beat_t w_beat = new; automatic int unsigned begin_byte, end_byte, n_bytes; automatic logic [AXI_STRB_WIDTH-1:0] rand_strb, strb_mask; + addr = axi_pkg::beat_addr(aw_beat.ax_addr, aw_beat.ax_size, aw_beat.ax_len, + aw_beat.ax_burst, i); `ifdef XILINX_SIMULATOR - rand_success = std::randomize(w_beat); assert (rand_success); + // std::randomize(w_beat) may behave differently to w_beat.randomize() wrt. limited ranges + // Keeping alternate implementation for XSIM only + rand_success = std::randomize(w_beat); assert (rand_success); `else rand_success = w_beat.randomize(); assert (rand_success); `endif @@ -1214,8 +1218,6 @@ package axi_test; w_beat.w_last = (i == aw_beat.ax_len); rand_wait(W_MIN_WAIT_CYCLES, W_MAX_WAIT_CYCLES); drv.send_w(w_beat); - if (aw_beat.ax_burst == axi_pkg::BURST_INCR) - addr += n_bytes; end end endtask @@ -1273,7 +1275,11 @@ package axi_test; parameter int R_MIN_WAIT_CYCLES = 0, parameter int R_MAX_WAIT_CYCLES = 5, parameter int RESP_MIN_WAIT_CYCLES = 0, - parameter int RESP_MAX_WAIT_CYCLES = 20 + parameter int RESP_MAX_WAIT_CYCLES = 20, + /// This parameter eneables an internal memory, which gets randomly initialized, if it is read + /// and retains written data. This mode does currently not support `axi_pkg::BURST_WRAP`! + /// All responses are `axi_pkg::RESP_OKAY` when in this mode. + parameter bit MAPPED = 1'b0 ); typedef axi_test::axi_driver #( .AW(AW), .DW(DW), .IW(IW), .UW(UW), .TA(TA), .TT(TT) @@ -1287,11 +1293,17 @@ package axi_test; typedef axi_driver_t::r_beat_t r_beat_t; typedef axi_driver_t::w_beat_t w_beat_t; + typedef logic [AW-1:0] addr_t; + typedef logic [7:0] byte_t; + axi_driver_t drv; rand_ax_beat_queue_t ar_queue; ax_beat_t aw_queue[$]; int unsigned b_wait_cnt; + // Memory array for when the `MAPPED` parameter is set. + byte_t memory_q[addr_t]; + function new( virtual AXI_BUS_DV #( .AXI_ADDR_WIDTH(AW), @@ -1307,7 +1319,8 @@ package axi_test; endfunction function void reset(); - drv.reset_slave(); + this.drv.reset_slave(); + this.memory_q.delete(); endfunction // TODO: The `rand_wait` task exists in `rand_verif_pkg`, but that task cannot be called with @@ -1327,6 +1340,10 @@ package axi_test; automatic ax_beat_t ar_beat; rand_wait(AX_MIN_WAIT_CYCLES, AX_MAX_WAIT_CYCLES); drv.recv_ar(ar_beat); + if (MAPPED) begin + assert (ar_beat.ax_burst != axi_pkg::BURST_WRAP) else + $error("axi_pkg::BURST_WRAP not supported in MAPPED mode."); + end ar_queue.push(ar_beat.ax_id, ar_beat); end endtask @@ -1335,14 +1352,30 @@ package axi_test; forever begin automatic logic rand_success; automatic ax_beat_t ar_beat; - automatic r_beat_t r_beat = new; + automatic r_beat_t r_beat = new; + automatic addr_t byte_addr; wait (ar_queue.size > 0); - ar_beat = ar_queue.peek(); + ar_beat = ar_queue.peek(); + byte_addr = axi_pkg::aligned_addr(ar_beat.ax_addr, axi_pkg::size_t'($clog2(DW/8))); `ifdef XILINX_SIMULATOR - rand_success = std::randomize(r_beat); assert (rand_success); + // std::randomize(r_beat) may behave differently to r_beat.randomize() wrt. limited ranges + // Keeping alternate implementation for XSIM only + rand_success = std::randomize(r_beat); assert(rand_success); `else - rand_success = r_beat.randomize(); assert (rand_success); + rand_success = r_beat.randomize(); assert(rand_success); `endif + if (MAPPED) begin + // Either use the actual data, or save the random generated. + for (int unsigned i = 0; i < (DW/8); i++) begin + if (this.memory_q.exists(byte_addr)) begin + r_beat.r_data[i*8+:8] = this.memory_q[byte_addr]; + end else begin + this.memory_q[byte_addr] = r_beat.r_data[i*8+:8]; + end + byte_addr++; + end + r_beat.r_resp = axi_pkg::RESP_OKAY; + end r_beat.r_id = ar_beat.ax_id; if (RAND_RESP && !ar_beat.ax_atop[axi_pkg::ATOP_R_RESP]) r_beat.r_resp[1] = $random(); @@ -1353,6 +1386,10 @@ package axi_test; r_beat.r_last = 1'b1; void'(ar_queue.pop_id(ar_beat.ax_id)); end else begin + if ((ar_beat.ax_burst == axi_pkg::BURST_INCR) && MAPPED) begin + ar_beat.ax_addr = axi_pkg::aligned_addr(ar_beat.ax_addr, ar_beat.ax_size) + + 2**ar_beat.ax_size; + end ar_beat.ax_len--; ar_queue.set(ar_beat.ax_id, ar_beat); end @@ -1365,6 +1402,12 @@ package axi_test; automatic ax_beat_t aw_beat; rand_wait(AX_MIN_WAIT_CYCLES, AX_MAX_WAIT_CYCLES); drv.recv_aw(aw_beat); + if (MAPPED) begin + assert (aw_beat.ax_atop == '0) else + $error("ATOP not supported in MAPPED mode."); + assert (aw_beat.ax_burst != axi_pkg::BURST_WRAP) else + $error("axi_pkg::BURST_WRAP not supported in MAPPED mode."); + end aw_queue.push_back(aw_beat); // Atomic{Load,Swap,Compare}s require an R response. if (aw_beat.ax_atop[axi_pkg::ATOP_R_RESP]) begin @@ -1376,10 +1419,30 @@ package axi_test; task recv_ws(); forever begin automatic ax_beat_t aw_beat; + automatic addr_t byte_addr; forever begin automatic w_beat_t w_beat; rand_wait(RESP_MIN_WAIT_CYCLES, RESP_MAX_WAIT_CYCLES); drv.recv_w(w_beat); + if (MAPPED) begin + wait (aw_queue.size() > 0); + aw_beat = aw_queue[0]; + byte_addr = axi_pkg::aligned_addr(aw_beat.ax_addr, $clog2(DW/8)); + + // Write Data if the strobe is defined + for (int unsigned i = 0; i < (DW/8); i++) begin + if (w_beat.w_strb[i]) begin + this.memory_q[byte_addr] = w_beat.w_data[i*8+:8]; + end + byte_addr++; + end + // Update address in beat + if (aw_beat.ax_burst == axi_pkg::BURST_INCR) begin + aw_beat.ax_addr = axi_pkg::aligned_addr(aw_beat.ax_addr, aw_beat.ax_size) + + 2**aw_beat.ax_size; + end + aw_queue[0] = aw_beat; + end if (w_beat.w_last) break; end @@ -1395,7 +1458,9 @@ package axi_test; wait (b_wait_cnt > 0 && (aw_queue.size() != 0)); aw_beat = aw_queue.pop_front(); `ifdef XILINX_SIMULATOR - rand_success = std::randomize(b_beat); assert (rand_success); + // std::randomize(b_beat) may behave differently to b_beat.randomize() wrt. limited ranges + // Keeping alternate implementation for XSIM only + rand_success = std::randomize(b_beat); assert (rand_success); `else rand_success = b_beat.randomize(); assert (rand_success); `endif @@ -1406,6 +1471,9 @@ package axi_test; b_beat.b_resp[0]= $random(); end rand_wait(RESP_MIN_WAIT_CYCLES, RESP_MAX_WAIT_CYCLES); + if (MAPPED) begin + b_beat.b_resp = axi_pkg::RESP_OKAY; + end drv.send_b(b_beat); b_wait_cnt--; end @@ -1494,7 +1562,7 @@ package axi_test; ar_addr = addr_t'($urandom_range(MIN_ADDR, MAX_ADDR)); ar_prot = prot_t'($urandom()); this.ar_queue.push_back(ar_addr); - $display("%0t %s> Send AR with ADDR: %h PROT: %b", $time(), this.name, ar_addr, ar_prot); + // $display("%0t %s> Send AR with ADDR: %h PROT: %b", $time(), this.name, ar_addr, ar_prot); drv.send_ar(ar_addr, ar_prot); end endtask : send_ars @@ -1508,7 +1576,7 @@ package axi_test; ar_addr = this.ar_queue.pop_front(); rand_wait(RESP_MIN_WAIT_CYCLES, RESP_MAX_WAIT_CYCLES); drv.recv_r(r_data, r_resp); - $display("%0t %s> Recv R with DATA: %h RESP: %0h", $time(), this.name, r_data, r_resp); + // $display("%0t %s> Recv R with DATA: %h RESP: %0h", $time(), this.name, r_data, r_resp); end endtask : recv_rs @@ -1520,7 +1588,7 @@ package axi_test; aw_addr = addr_t'($urandom_range(MIN_ADDR, MAX_ADDR)); aw_prot = prot_t'($urandom()); this.aw_queue.push_back(aw_addr); - $display("%0t %s> Send AW with ADDR: %h PROT: %b", $time(), this.name, aw_addr, aw_prot); + // $display("%0t %s> Send AW with ADDR: %h PROT: %b", $time(), this.name, aw_addr, aw_prot); this.drv.send_aw(aw_addr, aw_prot); this.b_queue.push_back(1'b1); end @@ -1537,7 +1605,7 @@ package axi_test; aw_addr = aw_queue.pop_front(); rand_success = std::randomize(w_data); assert(rand_success); rand_success = std::randomize(w_strb); assert(rand_success); - $display("%0t %s> Send W with DATA: %h STRB: %h", $time(), this.name, w_data, w_strb); + // $display("%0t %s> Send W with DATA: %h STRB: %h", $time(), this.name, w_data, w_strb); this.drv.send_w(w_data, w_strb); w_queue.push_back(1'b1); end @@ -1552,7 +1620,7 @@ package axi_test; go_b = this.w_queue.pop_front(); rand_wait(RESP_MIN_WAIT_CYCLES, RESP_MAX_WAIT_CYCLES); this.drv.recv_b(b_resp); - $display("%0t %s> Recv B with RESP: %h", $time(), this.name, b_resp); + // $display("%0t %s> Recv B with RESP: %h", $time(), this.name, b_resp); end endtask : recv_bs @@ -1570,26 +1638,26 @@ package axi_test; // write data to a specific address task automatic write(input addr_t w_addr, input prot_t w_prot = prot_t'(0), input data_t w_data, input strb_t w_strb, output axi_pkg::resp_t b_resp); - $display("%0t %s> Write to ADDR: %h, PROT: %b DATA: %h, STRB: %h", - $time(), this.name, w_addr, w_prot, w_data, w_strb); + // $display("%0t %s> Write to ADDR: %h, PROT: %b DATA: %h, STRB: %h", + // $time(), this.name, w_addr, w_prot, w_data, w_strb); fork this.drv.send_aw(w_addr, w_prot); this.drv.send_w(w_data, w_strb); join this.drv.recv_b(b_resp); - $display("%0t %s> Received write response from ADDR: %h RESP: %h", - $time(), this.name, w_addr, b_resp); + // $display("%0t %s> Received write response from ADDR: %h RESP: %h", + // $time(), this.name, w_addr, b_resp); endtask : write // read data from a specific location task automatic read(input addr_t r_addr, input prot_t r_prot = prot_t'(0), output data_t r_data, output axi_pkg::resp_t r_resp); - $display("%0t %s> Read from ADDR: %h PROT: %b", - $time(), this.name, r_addr, r_prot); + // $display("%0t %s> Read from ADDR: %h PROT: %b", + // $time(), this.name, r_addr, r_prot); this.drv.send_ar(r_addr, r_prot); this.drv.recv_r(r_data, r_resp); - $display("%0t %s> Recieved read response from ADDR: %h DATA: %h RESP: %h", - $time(), this.name, r_addr, r_data, r_resp); + // $display("%0t %s> Recieved read response from ADDR: %h DATA: %h RESP: %h", + // $time(), this.name, r_addr, r_data, r_resp); endtask : read endclass @@ -1655,22 +1723,24 @@ package axi_test; automatic prot_t ar_prot; rand_wait(AX_MIN_WAIT_CYCLES, AX_MAX_WAIT_CYCLES); this.drv.recv_ar(ar_addr, ar_prot); - $display("%0t %s> Recv AR with ADDR: %h PROT: %b", $time(), this.name, ar_addr, ar_prot); + // $display("%0t %s> Recv AR with ADDR: %h PROT: %b", $time(), this.name, ar_addr, ar_prot); this.ar_queue.push_back(ar_addr); end endtask : recv_ars task automatic send_rs(); forever begin - automatic logic rand_success; - automatic addr_t ar_addr; - automatic data_t r_data; + automatic logic rand_success; + automatic addr_t ar_addr; + automatic data_t r_data; + automatic axi_pkg::resp_t r_resp; wait (ar_queue.size() > 0); ar_addr = this.ar_queue.pop_front(); rand_success = std::randomize(r_data); assert(rand_success); + rand_success = std::randomize(r_resp); assert(rand_success); rand_wait(R_MIN_WAIT_CYCLES, R_MAX_WAIT_CYCLES); - $display("%0t %s> Send R with DATA: %h", $time(), this.name, r_data); - this.drv.send_r(r_data, axi_pkg::RESP_OKAY); + // $display("%0t %s> Send R with DATA: %h RESP: %h", $time(), this.name, r_data, r_resp); + this.drv.send_r(r_data, r_resp); end endtask : send_rs @@ -1680,7 +1750,7 @@ package axi_test; automatic prot_t aw_prot; rand_wait(AX_MIN_WAIT_CYCLES, AX_MAX_WAIT_CYCLES); this.drv.recv_aw(aw_addr, aw_prot); - $display("%0t %s> Recv AW with ADDR: %h PROT: %b", $time(), this.name, aw_addr, aw_prot); + // $display("%0t %s> Recv AW with ADDR: %h PROT: %b", $time(), this.name, aw_addr, aw_prot); this.aw_queue.push_back(aw_addr); end endtask : recv_aws @@ -1691,7 +1761,7 @@ package axi_test; automatic strb_t w_strb; rand_wait(RESP_MIN_WAIT_CYCLES, RESP_MAX_WAIT_CYCLES); this.drv.recv_w(w_data, w_strb); - $display("%0t %s> Recv W with DATA: %h SRTB: %h", $time(), this.name, w_data, w_strb); + // $display("%0t %s> Recv W with DATA: %h SRTB: %h", $time(), this.name, w_data, w_strb); this.b_queue.push_back(1'b1); end endtask : recv_ws @@ -1707,7 +1777,7 @@ package axi_test; go_b = this.b_queue.pop_front(); rand_wait(RESP_MIN_WAIT_CYCLES, RESP_MAX_WAIT_CYCLES); rand_success = std::randomize(b_resp); assert(rand_success); - $display("%0t %s> Send B with RESP: %h", $time(), this.name, b_resp); + // $display("%0t %s> Send B with RESP: %h", $time(), this.name, b_resp); this.drv.send_b(b_resp); end endtask : send_bs @@ -1951,8 +2021,6 @@ package axi_test; b_beat = b_sample[id].pop_front(); if (check_en[BRespCheck]) begin assert (b_beat.b_id == id); - assert (b_beat.b_resp == axi_pkg::RESP_OKAY) else - $warning("Behavior for b_resp != axi_pkg::RESP_OKAY not modeled."); end // pop all accessed memory locations by this beat for (int unsigned i = 0; i <= aw_beat.ax_len; i++) begin @@ -1960,7 +2028,11 @@ package axi_test; axi_pkg::beat_addr(aw_beat.ax_addr, aw_beat.ax_size, aw_beat.ax_len, aw_beat.ax_burst, i), BUS_SIZE); for (int j = 0; j < axi_pkg::num_bytes(BUS_SIZE); j++) begin - memory_q[bus_address+j].delete(0); + if (b_beat.b_resp inside {axi_pkg::RESP_OKAY, axi_pkg::RESP_EXOKAY}) begin + memory_q[bus_address+j].delete(0); + end else begin + memory_q[bus_address+j].delete(memory_q[bus_address+j].size() - 1); + end end end end @@ -1974,6 +2046,7 @@ package axi_test; byte_t act_data; byte_t exp_data[$]; byte_t tst_data[$]; + int first_byte_to_check; forever begin wait (this.ar_sample[id].size() > 0); ar_beat = this.ar_sample[id].pop_front(); @@ -1988,28 +2061,39 @@ package axi_test; ar_beat.ax_burst, i); beat_address = axi_pkg::aligned_addr(beat_address, ar_beat.ax_size); bus_address = axi_pkg::aligned_addr(beat_address, BUS_SIZE); + if(i!=0) + first_byte_to_check = 0; + else + first_byte_to_check = ar_beat.ax_addr - beat_address; if (!this.memory_q.exists(bus_address)) begin for (int unsigned j = 0; j < axi_pkg::num_bytes(BUS_SIZE); j++) begin this.memory_q[bus_address+j].push_back(8'bxxxxxxxx); end end // Assert that the correct data is read. - if (this.check_en[ReadCheck]) begin - for (int unsigned j = 0; j < axi_pkg::num_bytes(ar_beat.ax_size); j++) begin + if (this.check_en[ReadCheck] && + (r_beat.r_resp inside {axi_pkg::RESP_OKAY, axi_pkg::RESP_EXOKAY})) begin + for (int unsigned j = first_byte_to_check; j < axi_pkg::num_bytes(ar_beat.ax_size); j++) begin idx_data = 8*BUS_SIZE'(beat_address+j); act_data = r_beat.r_data[idx_data+:8]; exp_data = this.memory_q[beat_address+j]; - tst_data = exp_data.find with (item === 8'hxx || item === act_data); - assert (tst_data.size() > 0) else begin - $warning("Unexpected RData ID: %0h Addr: %0h Byte Idx: %0h Exp Data : %0h Data: %h", - r_beat.r_id, beat_address+j, idx_data, exp_data, act_data); + if (exp_data.size() > 0) begin + tst_data = exp_data.find with (item === 8'hxx || item === act_data); + assert (tst_data.size() > 0) else begin + $warning("Unexpected RData ID: %0h \n \ + Addr: %h \n \ + Byte Idx: %h \n \ + Exp Data: %h \n \ + Act Data: %h \n \ + BeatData: %h", + r_beat.r_id, beat_address+j, idx_data, exp_data, act_data, r_beat.r_data); + end end end end end if (this.check_en[RRespCheck]) begin assert (r_beat.r_id == id); - assert (r_beat.r_resp == axi_pkg::RESP_OKAY); assert (r_beat.r_last); end end @@ -2194,8 +2278,267 @@ package axi_test; assert(this.b_queue[i].size() == 0); end endtask : reset + + /// Check that the byte in memory_q is the same as check_data. + task automatic check_byte(axi_addr_t check_addr, byte_t check_data); + assert(this.memory_q[check_addr][0] === check_data) else + $warning("Byte at ADDR: %h does not match: memory_q: %h check_data: %h", + check_addr, this.memory_q[check_addr][0], check_data); + endtask : check_byte + + /// Clear a byte from memoy. Can be used to partially delete mem space. + task clear_byte(axi_addr_t clear_addr); + if (this.memory_q.exists(clear_addr)) begin + this.memory_q.delete(clear_addr); + end + endtask : clear_byte + + /// Clear a memory range. + /// The end address alo gets cleared. + task automatic clear_range(axi_addr_t clear_start_addr, clear_end_addr); + axi_addr_t curr_addr = clear_start_addr; + while (curr_addr <= clear_end_addr) begin + this.clear_byte(curr_addr); + curr_addr++; + end + endtask : clear_range + + /// Get a byte from the modeled memory. + task automatic get_byte(input axi_addr_t byte_addr, output byte_t byte_data); + if (this.memory_q.exists(byte_addr)) begin + byte_data = this.memory_q[byte_addr][0]; + end else begin + byte_data = 8'hxx; + end + endtask : get_byte + endclass : axi_scoreboard + + class axi_file_master #( + // AXI interface parameters + parameter int AW = 32, + parameter int DW = 32, + parameter int IW = 8, + parameter int UW = 1, + // Stimuli application and test time + parameter time TA = 0ps, + parameter time TT = 0ps + ); + + typedef axi_test::axi_driver #( + .AW(AW), .DW(DW), .IW(IW), .UW(UW), .TA(TA), .TT(TT) + ) axi_driver_t; + + typedef axi_driver_t::ax_beat_t ax_beat_t; + typedef axi_driver_t::b_beat_t b_beat_t; + typedef axi_driver_t::r_beat_t r_beat_t; + typedef axi_driver_t::w_beat_t w_beat_t; + + axi_driver_t drv; + + int read_fd, write_fd; + + // store holding read/write transactions between aw-b and ar-r + logic b_outst[$]; + logic r_outst[$]; + + // proper decoupling: populate queues from file + ax_beat_t aw_queue[$]; + w_beat_t w_queue[$]; + ax_beat_t ar_queue[$]; + + // populated by read file function + int num_reads; + int num_writes; + + function new( + virtual AXI_BUS_DV #( + .AXI_ADDR_WIDTH(AW), + .AXI_DATA_WIDTH(DW), + .AXI_ID_WIDTH(IW), + .AXI_USER_WIDTH(UW) + ) axi + ); + this.drv = new(axi); + this.reset(); + endfunction + + function void reset(); + drv.reset_master(); + endfunction + + function void parse_write(); + // parsing works + int parse_ok; + + // populate according to file + while (!$feof(this.write_fd)) begin + automatic ax_beat_t current_aw = new; + parse_ok = 1; + parse_ok = parse_ok & ($fscanf(this.write_fd, "%d\n", current_aw.ax_id ) != -1); + parse_ok = parse_ok & ($fscanf(this.write_fd, "0x%x\n", current_aw.ax_addr ) != -1); + parse_ok = parse_ok & ($fscanf(this.write_fd, "%d\n", current_aw.ax_len ) != -1); + parse_ok = parse_ok & ($fscanf(this.write_fd, "%d\n", current_aw.ax_size ) != -1); + parse_ok = parse_ok & ($fscanf(this.write_fd, "%d\n", current_aw.ax_burst ) != -1); + parse_ok = parse_ok & ($fscanf(this.write_fd, "%d\n", current_aw.ax_lock ) != -1); + parse_ok = parse_ok & ($fscanf(this.write_fd, "%d\n", current_aw.ax_cache ) != -1); + parse_ok = parse_ok & ($fscanf(this.write_fd, "%d\n", current_aw.ax_prot ) != -1); + parse_ok = parse_ok & ($fscanf(this.write_fd, "%d\n", current_aw.ax_qos ) != -1); + parse_ok = parse_ok & ($fscanf(this.write_fd, "%d\n", current_aw.ax_region) != -1); + parse_ok = parse_ok & ($fscanf(this.write_fd, "%d\n", current_aw.ax_atop ) != -1); + parse_ok = parse_ok & ($fscanf(this.write_fd, "%d\n", current_aw.ax_user ) != -1); + if (parse_ok) begin + this.aw_queue.push_back(current_aw); + this.b_outst.push_back(1'b1); + // $display("%p", current_aw); + end else begin + $warning("Issue parsing AW: %p", current_aw); + end + + // get write data + strobe + for (int i = 0; i <= current_aw.ax_len; i++) begin + automatic w_beat_t current_w = new; + parse_ok = parse_ok & ($fscanf(this.write_fd, "0x%x 0x%x %d\n", current_w.w_data, current_w.w_strb, current_w.w_user) != -1); + current_w.w_last = 1'b0; + if (i == current_aw.ax_len) begin + current_w.w_last = 1'b1; + end + if (parse_ok) begin + this.w_queue.push_back(current_w); + // $display("%p", current_w); + end else begin + $warning("Issue parsing W: %p of AW: %p", current_w, current_aw); + end + end + end + + // debug: print queues + // $display("%p", this.aw_queue); + // $display("%p", this.w_queue); + endfunction + + function void parse_read(); + // parsing works + int parse_ok; + + // populate according to file + while (!$feof(this.read_fd)) begin + automatic ax_beat_t current_ar = new; + parse_ok = 1; + parse_ok = parse_ok & ($fscanf(this.read_fd, "%d\n", current_ar.ax_id ) != -1); + parse_ok = parse_ok & ($fscanf(this.read_fd, "0x%x\n", current_ar.ax_addr ) != -1); + parse_ok = parse_ok & ($fscanf(this.read_fd, "%d\n", current_ar.ax_len ) != -1); + parse_ok = parse_ok & ($fscanf(this.read_fd, "%d\n", current_ar.ax_size ) != -1); + parse_ok = parse_ok & ($fscanf(this.read_fd, "%d\n", current_ar.ax_burst ) != -1); + parse_ok = parse_ok & ($fscanf(this.read_fd, "%d\n", current_ar.ax_lock ) != -1); + parse_ok = parse_ok & ($fscanf(this.read_fd, "%d\n", current_ar.ax_cache ) != -1); + parse_ok = parse_ok & ($fscanf(this.read_fd, "%d\n", current_ar.ax_prot ) != -1); + parse_ok = parse_ok & ($fscanf(this.read_fd, "%d\n", current_ar.ax_qos ) != -1); + parse_ok = parse_ok & ($fscanf(this.read_fd, "%d\n", current_ar.ax_region) != -1); + parse_ok = parse_ok & ($fscanf(this.read_fd, "%d\n", current_ar.ax_user ) != -1); + if (parse_ok) begin + this.ar_queue.push_back(current_ar); + this.r_outst.push_back(1'b1); + // $display("%p", current_ar); + end else begin + $warning("Issue parsing AR: %p", current_ar); + end + end + + // debug: print queues + // $display("%p", this.ar_queue); + endfunction + + function void load_files( + string read_file_name, + string write_file_name + ); + this.read_fd = $fopen(read_file_name, "r"); + this.write_fd = $fopen(write_file_name, "r"); + + // check if files are opened + if (this.read_fd) begin + $info("File %s opened successfully as %d", read_file_name, this.read_fd); + end else begin + $fatal(1, "File %s not found", read_file_name); + end + if (this.write_fd) begin + $info("File %s opened successfully as %d", write_file_name, this.write_fd); + end else begin + $fatal(1, "File %s not found", write_file_name); + end + + // read files + this.parse_read(); + this.parse_write(); + + // update status + this.num_reads = this.ar_queue.size(); + this.num_writes = this.aw_queue.size(); + endfunction + + task run_aw(); + // send aws while there are some left + while (this.aw_queue.size() > 0) begin + // display("Sending AW: %p", this.aw_queue[0]); + drv.send_aw(this.aw_queue[0]); + void'(this.aw_queue.pop_front()); + end + endtask + + task run_w(); + // send ws while there are some left + while (this.w_queue.size() > 0) begin + // $display("Sending W: %p", this.w_queue[0]); + drv.send_w(this.w_queue[0]); + void'(this.w_queue.pop_front()); + end + endtask + + task run_ar(); + // send ars while there are some left + while (this.ar_queue.size() > 0) begin + // $display("Sending AR: %p", this.ar_queue[0]); + drv.send_ar(this.ar_queue[0]); + void'(this.ar_queue.pop_front()); + end + endtask + + task wait_b(); + automatic b_beat_t b_beat = new; + // wait for bs while there are some left + while (this.b_outst.size() > 0) begin + // $display("Waiting B"); + drv.recv_b(b_beat); + void'(this.b_outst.pop_front()); + end + endtask + + task wait_r(); + automatic r_beat_t r_beat = new; + // wait for rs while there are some left + while (this.r_outst.size() > 0) begin + // $display("Waiting R"); + do begin + drv.recv_r(r_beat); + end while (r_beat.r_last !== 1'b1); + void'(this.r_outst.pop_front()); + end + endtask + + task run(); + fork + this.run_aw(); + this.run_w (); + this.run_ar(); + this.wait_b(); + this.wait_r(); + join + endtask + + endclass + endpackage // non synthesisable axi logger module diff --git a/src/axi_throttle.sv b/src/axi_throttle.sv new file mode 100644 index 000000000..3e7973a08 --- /dev/null +++ b/src/axi_throttle.sv @@ -0,0 +1,102 @@ +// Copyright 2022 ETH Zurich and University of Bologna. +// Solderpad Hardware License, Version 0.51, see LICENSE for details. +// SPDX-License-Identifier: SHL-0.51 +// +// Authors: +// - Thomas Benz + +/// Throttles an AXI4+ATOP bus. The maximum number of outstanding transfers have to +/// be set as a compile-time parameter, whereas the number of outstanding transfers can be set +/// during runtime. This module assumes either in-order processing of the requests or +/// indistinguishability of the request/responses (all ARs and AWs have the same ID respectively). +module axi_throttle #( + /// The maximum amount of allowable outstanding write requests + parameter int unsigned MaxNumAwPending = 1, + /// The maximum amount of allowable outstanding read requests + parameter int unsigned MaxNumArPending = 1, + /// AXI4+ATOP request type + parameter type axi_req_t = logic, + /// AXI4+ATOP response type + parameter type axi_rsp_t = logic, + /// The width of the write credit counter (*DO NOT OVERWRITE*) + parameter int unsigned WCntWidth = cf_math_pkg::idx_width(MaxNumAwPending), + /// The width of the read credit counter (*DO NOT OVERWRITE*) + parameter int unsigned RCntWidth = cf_math_pkg::idx_width(MaxNumArPending), + /// The type of the write credit counter (*DO NOT OVERWRITE*) + parameter type w_credit_t = logic [WCntWidth-1:0], + /// The type of the read credit counter (*DO NOT OVERWRITE*) + parameter type r_credit_t = logic [RCntWidth-1:0] +) ( + /// Clock + input logic clk_i, + /// Asynchronous reset, active low + input logic rst_ni, + + /// AXI4+ATOP request in + input axi_req_t req_i, + /// AXI4+ATOP response out + output axi_rsp_t rsp_o, + /// AXI4+ATOP request out + output axi_req_t req_o, + /// AXI4+ATOP response in + input axi_rsp_t rsp_i, + + /// Amount of write credit (number of outstanding write transfers) + input w_credit_t w_credit_i, + /// Amount of read credit (number of outstanding read transfers) + input r_credit_t r_credit_i +); + + // ax throttled valids + logic throttled_aw_valid; + logic throttled_ar_valid; + + // ax throttled readies + logic throttled_aw_ready; + logic throttled_ar_ready; + + // limit Aw requests -> wait for b + stream_throttle #( + .MaxNumPending ( MaxNumAwPending ) + ) i_stream_throttle_aw ( + .clk_i, + .rst_ni, + .req_valid_i ( req_i.aw_valid ), + .req_valid_o ( throttled_aw_valid ), + .req_ready_i ( rsp_i.aw_ready ), + .req_ready_o ( throttled_aw_ready ), + .rsp_valid_i ( rsp_i.b_valid ), + .rsp_ready_i ( req_i.b_ready ), + .credit_i ( w_credit_i ) + ); + + // limit Ar requests -> wait for r.last + stream_throttle #( + .MaxNumPending ( MaxNumArPending ) + ) i_stream_throttle_ar ( + .clk_i, + .rst_ni, + .req_valid_i ( req_i.ar_valid ), + .req_valid_o ( throttled_ar_valid ), + .req_ready_i ( rsp_i.ar_ready ), + .req_ready_o ( throttled_ar_ready ), + .rsp_valid_i ( rsp_i.r_valid & rsp_i.r.last ), + .rsp_ready_i ( req_i.r_ready ), + .credit_i ( r_credit_i ) + ); + + // connect the throttled request bus (its a through connection - except for the ax valids) + always_comb begin : gen_throttled_req_conn + req_o = req_i; + req_o.aw_valid = throttled_aw_valid; + req_o.ar_valid = throttled_ar_valid; + end + + // connect the throttled response bus (its a through connection - except for the ax readies) + always_comb begin : gen_throttled_rsp_conn + rsp_o = rsp_i; + rsp_o.aw_ready = throttled_aw_ready; + rsp_o.ar_ready = throttled_ar_ready; + end + +endmodule : axi_throttle diff --git a/src/axi_to_axi_lite.sv b/src/axi_to_axi_lite.sv index d610ab99d..ad2e41414 100644 --- a/src/axi_to_axi_lite.sv +++ b/src/axi_to_axi_lite.sv @@ -13,7 +13,6 @@ // - Wolfgang Roenninger // - Andreas Kurth // - Fabian Schuiki -// - Florian Zaruba /// An AXI4+ATOP to AXI4-Lite converter with atomic transaction and burst support. module axi_to_axi_lite #( @@ -23,6 +22,7 @@ module axi_to_axi_lite #( parameter int unsigned AxiUserWidth = 32'd0, parameter int unsigned AxiMaxWriteTxns = 32'd0, parameter int unsigned AxiMaxReadTxns = 32'd0, + parameter bit FullBW = 0, // ID Queue in Full BW mode in axi_burst_splitter parameter bit FallThrough = 1'b1, // FIFOs in Fall through mode in ID reflect parameter type full_req_t = logic, parameter type full_resp_t = logic, @@ -62,6 +62,7 @@ module axi_to_axi_lite #( axi_burst_splitter #( .MaxReadTxns ( AxiMaxReadTxns ), .MaxWriteTxns ( AxiMaxWriteTxns ), + .FullBW ( FullBW ), .AddrWidth ( AxiAddrWidth ), .DataWidth ( AxiDataWidth ), .IdWidth ( AxiIdWidth ), @@ -256,7 +257,8 @@ module axi_to_axi_lite_intf #( parameter int unsigned AXI_MAX_WRITE_TXNS = 32'd1, /// Maximum number of outstanding reads. parameter int unsigned AXI_MAX_READ_TXNS = 32'd1, - parameter bit FALL_THROUGH = 1'b1 + parameter bit FALL_THROUGH = 1'b1, + parameter bit FULL_BW = 0 ) ( input logic clk_i, input logic rst_ni, @@ -305,6 +307,7 @@ module axi_to_axi_lite_intf #( .AxiMaxWriteTxns ( AXI_MAX_WRITE_TXNS ), .AxiMaxReadTxns ( AXI_MAX_READ_TXNS ), .FallThrough ( FALL_THROUGH ), // FIFOs in Fall through mode in ID reflect + .FullBW ( FULL_BW ), .full_req_t ( full_req_t ), .full_resp_t ( full_resp_t ), .lite_req_t ( lite_req_t ), diff --git a/src/axi_to_detailed_mem.sv b/src/axi_to_detailed_mem.sv new file mode 100644 index 000000000..8f5c11d21 --- /dev/null +++ b/src/axi_to_detailed_mem.sv @@ -0,0 +1,746 @@ +// Copyright 2020 ETH Zurich and University of Bologna. +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the "License"); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +// Authors: +// - Michael Rogenmoser +// - Thomas Benz + +`include "common_cells/registers.svh" +/// AXI4+ATOP slave module which translates AXI bursts into a memory stream. +/// If both read and write channels of the AXI4+ATOP are active, both will have an +/// utilization of 50%. +module axi_to_detailed_mem #( + /// AXI4+ATOP request type. See `include/axi/typedef.svh`. + parameter type axi_req_t = logic, + /// AXI4+ATOP response type. See `include/axi/typedef.svh`. + parameter type axi_resp_t = logic, + /// Address width, has to be less or equal than the width off the AXI address field. + /// Determines the width of `mem_addr_o`. Has to be wide enough to emit the memory region + /// which should be accessible. + parameter int unsigned AddrWidth = 0, + /// AXI4+ATOP data width. + parameter int unsigned DataWidth = 0, + /// AXI4+ATOP ID width. + parameter int unsigned IdWidth = 0, + /// AXI4+ATOP user width. + parameter int unsigned UserWidth = 0, + /// Number of banks at output, must evenly divide `DataWidth`. + parameter int unsigned NumBanks = 0, + /// Depth of memory response buffer. This should be equal to the memory response latency. + parameter int unsigned BufDepth = 1, + /// Hide write requests if the strb == '0 + parameter bit HideStrb = 1'b0, + /// Depth of output fifo/fall_through_register. Increase for asymmetric backpressure (contention) on banks. + parameter int unsigned OutFifoDepth = 1, + /// Dependent parameter, do not override. Memory address type. + localparam type addr_t = logic [AddrWidth-1:0], + /// Dependent parameter, do not override. Memory data type. + localparam type mem_data_t = logic [DataWidth/NumBanks-1:0], + /// Dependent parameter, do not override. Memory write strobe type. + localparam type mem_strb_t = logic [DataWidth/NumBanks/8-1:0], + /// Dependent parameter, do not override. Memory id type. + localparam type mem_id_t = logic [IdWidth-1:0], + /// Dependent parameter, do not override. Memory user type. + localparam type mem_user_t = logic [UserWidth-1:0] +) ( + /// Clock input. + input logic clk_i, + /// Asynchronous reset, active low. + input logic rst_ni, + /// The unit is busy handling an AXI4+ATOP request. + output logic busy_o, + /// AXI4+ATOP slave port, request input. + input axi_req_t axi_req_i, + /// AXI4+ATOP slave port, response output. + output axi_resp_t axi_resp_o, + /// Memory stream master, request is valid for this bank. + output logic [NumBanks-1:0] mem_req_o, + /// Memory stream master, request can be granted by this bank. + input logic [NumBanks-1:0] mem_gnt_i, + /// Memory stream master, byte address of the request. + output addr_t [NumBanks-1:0] mem_addr_o, + /// Memory stream master, write data for this bank. Valid when `mem_req_o`. + output mem_data_t [NumBanks-1:0] mem_wdata_o, + /// Memory stream master, byte-wise strobe (byte enable). + output mem_strb_t [NumBanks-1:0] mem_strb_o, + /// Memory stream master, `axi_pkg::atop_t` signal associated with this request. + output axi_pkg::atop_t [NumBanks-1:0] mem_atop_o, + /// Memory stream master, lock signal. + output logic [NumBanks-1:0] mem_lock_o, + /// Memory stream master, write enable. Then asserted store of `mem_w_data` is requested. + output logic [NumBanks-1:0] mem_we_o, + /// Memory stream master, ID. Response ID is managed internally, ensure in-order responses. + output mem_id_t [NumBanks-1:0] mem_id_o, + /// Memory stream master, user signal. Ax channel user bits used. + output mem_user_t [NumBanks-1:0] mem_user_o, + /// Memory stream master, cache signal. + output axi_pkg::cache_t [NumBanks-1:0] mem_cache_o, + /// Memory stream master, protection signal. + output axi_pkg::prot_t [NumBanks-1:0] mem_prot_o, + /// Memory stream master, QOS signal. + output axi_pkg::qos_t [NumBanks-1:0] mem_qos_o, + /// Memory stream master, region signal. + output axi_pkg::region_t [NumBanks-1:0] mem_region_o, + /// Memory stream master, response is valid. This module expects always a response valid for a + /// request regardless if the request was a write or a read. + input logic [NumBanks-1:0] mem_rvalid_i, + /// Memory stream master, read response data. + input mem_data_t [NumBanks-1:0] mem_rdata_i, + /// Memory stream master, error response. + input logic [NumBanks-1:0] mem_err_i, + /// Memory stream master, read response exclusive access OK. + input logic [NumBanks-1:0] mem_exokay_i +); + + typedef logic [DataWidth-1:0] axi_data_t; + typedef logic [DataWidth/8-1:0] axi_strb_t; + typedef logic [IdWidth-1:0] axi_id_t; + + typedef struct packed { + addr_t addr; + axi_pkg::atop_t atop; + logic lock; + axi_strb_t strb; + axi_data_t wdata; + logic we; + mem_id_t id; + mem_user_t user; + axi_pkg::cache_t cache; + axi_pkg::prot_t prot; + axi_pkg::qos_t qos; + axi_pkg::region_t region; + } mem_req_t; + + typedef struct packed { + addr_t addr; + axi_pkg::atop_t atop; + logic lock; + axi_strb_t strb; + axi_id_t id; + logic last; + axi_pkg::qos_t qos; + axi_pkg::size_t size; + logic write; + mem_user_t user; + axi_pkg::cache_t cache; + axi_pkg::prot_t prot; + axi_pkg::region_t region; + } meta_t; + + typedef struct packed { + axi_data_t data; + logic [NumBanks-1:0] err; + logic [NumBanks-1:0] exokay; + } mem_rsp_t; + + mem_rsp_t mem_rdata, + m2s_resp; + axi_pkg::len_t r_cnt_d, r_cnt_q, + w_cnt_d, w_cnt_q; + logic arb_valid, arb_ready, + rd_valid, rd_ready, + wr_valid, wr_ready, + sel_b, sel_buf_b, + sel_r, sel_buf_r, + sel_valid, sel_ready, + sel_buf_valid, sel_buf_ready, + sel_lock_d, sel_lock_q, + meta_valid, meta_ready, + meta_buf_valid, meta_buf_ready, + meta_sel_d, meta_sel_q, + m2s_req_valid, m2s_req_ready, + m2s_resp_valid, m2s_resp_ready, + mem_req_valid, mem_req_ready, + mem_rvalid; + mem_req_t m2s_req, + mem_req; + meta_t rd_meta, + rd_meta_d, rd_meta_q, + wr_meta, + wr_meta_d, wr_meta_q, + meta, meta_buf; + + assign busy_o = axi_req_i.aw_valid | axi_req_i.ar_valid | axi_req_i.w_valid | + axi_resp_o.b_valid | axi_resp_o.r_valid | + (r_cnt_q > 0) | (w_cnt_q > 0); + + // Handle reads. + always_comb begin + // Default assignments + axi_resp_o.ar_ready = 1'b0; + rd_meta_d = rd_meta_q; + rd_meta = meta_t'{default: '0}; + rd_valid = 1'b0; + r_cnt_d = r_cnt_q; + // Handle R burst in progress. + if (r_cnt_q > '0) begin + rd_meta_d.last = (r_cnt_q == 8'd1); + rd_meta = rd_meta_d; + rd_meta.addr = rd_meta_q.addr + axi_pkg::num_bytes(rd_meta_q.size); + rd_valid = 1'b1; + if (rd_ready) begin + r_cnt_d--; + rd_meta_d.addr = rd_meta.addr; + end + // Handle new AR if there is one. + end else if (axi_req_i.ar_valid) begin + rd_meta_d = '{ + addr: addr_t'(axi_pkg::aligned_addr(axi_req_i.ar.addr, axi_req_i.ar.size)), + atop: '0, + lock: axi_req_i.ar.lock, + strb: '0, + id: axi_req_i.ar.id, + last: (axi_req_i.ar.len == '0), + qos: axi_req_i.ar.qos, + size: axi_req_i.ar.size, + write: 1'b0, + user: axi_req_i.ar.user, + cache: axi_req_i.ar.cache, + prot: axi_req_i.ar.prot, + region: axi_req_i.ar.region + }; + rd_meta = rd_meta_d; + rd_meta.addr = addr_t'(axi_req_i.ar.addr); + rd_valid = 1'b1; + if (rd_ready) begin + r_cnt_d = axi_req_i.ar.len; + axi_resp_o.ar_ready = 1'b1; + end + end + end + + // Handle writes. + always_comb begin + // Default assignments + axi_resp_o.aw_ready = 1'b0; + axi_resp_o.w_ready = 1'b0; + wr_meta_d = wr_meta_q; + wr_meta = meta_t'{default: '0}; + wr_valid = 1'b0; + w_cnt_d = w_cnt_q; + // Handle W bursts in progress. + if (w_cnt_q > '0) begin + wr_meta_d.last = (w_cnt_q == 8'd1); + wr_meta = wr_meta_d; + wr_meta.addr = wr_meta_q.addr + axi_pkg::num_bytes(wr_meta_q.size); + if (axi_req_i.w_valid) begin + wr_valid = 1'b1; + wr_meta.strb = axi_req_i.w.strb; + if (wr_ready) begin + axi_resp_o.w_ready = 1'b1; + w_cnt_d--; + wr_meta_d.addr = wr_meta.addr; + end + end + // Handle new AW if there is one. + end else if (axi_req_i.aw_valid && axi_req_i.w_valid) begin + wr_meta_d = '{ + addr: addr_t'(axi_pkg::aligned_addr(axi_req_i.aw.addr, axi_req_i.aw.size)), + atop: axi_req_i.aw.atop, + lock: axi_req_i.aw.lock, + strb: axi_req_i.w.strb, + id: axi_req_i.aw.id, + last: (axi_req_i.aw.len == '0), + qos: axi_req_i.aw.qos, + size: axi_req_i.aw.size, + write: 1'b1, + user: axi_req_i.aw.user, + cache: axi_req_i.aw.cache, + prot: axi_req_i.aw.prot, + region: axi_req_i.aw.region + }; + wr_meta = wr_meta_d; + wr_meta.addr = addr_t'(axi_req_i.aw.addr); + wr_valid = 1'b1; + if (wr_ready) begin + w_cnt_d = axi_req_i.aw.len; + axi_resp_o.aw_ready = 1'b1; + axi_resp_o.w_ready = 1'b1; + end + end + end + + // Arbitrate between reads and writes. + stream_mux #( + .DATA_T ( meta_t ), + .N_INP ( 32'd2 ) + ) i_ax_mux ( + .inp_data_i ({wr_meta, rd_meta }), + .inp_valid_i ({wr_valid, rd_valid}), + .inp_ready_o ({wr_ready, rd_ready}), + .inp_sel_i ( meta_sel_d ), + .oup_data_o ( meta ), + .oup_valid_o ( arb_valid ), + .oup_ready_i ( arb_ready ) + ); + always_comb begin + meta_sel_d = meta_sel_q; + sel_lock_d = sel_lock_q; + if (sel_lock_q) begin + meta_sel_d = meta_sel_q; + if (arb_valid && arb_ready) begin + sel_lock_d = 1'b0; + end + end else begin + if (wr_valid ^ rd_valid) begin + // If either write or read is valid but not both, select the valid one. + meta_sel_d = wr_valid; + end else if (wr_valid && rd_valid) begin + // If both write and read are valid, decide according to QoS then burst properties. + // Prioritize higher QoS. + if (wr_meta.qos > rd_meta.qos) begin + meta_sel_d = 1'b1; + end else if (rd_meta.qos > wr_meta.qos) begin + meta_sel_d = 1'b0; + // Decide requests with identical QoS. + end else if (wr_meta.qos == rd_meta.qos) begin + // 1. Prioritize individual writes over read bursts. + // Rationale: Read bursts can be interleaved on AXI but write bursts cannot. + if (wr_meta.last && !rd_meta.last) begin + meta_sel_d = 1'b1; + // 2. Prioritize ongoing burst. + // Rationale: Stalled bursts create back-pressure or require costly buffers. + end else if (w_cnt_q > '0) begin + meta_sel_d = 1'b1; + end else if (r_cnt_q > '0) begin + meta_sel_d = 1'b0; + // 3. Otherwise arbitrate round robin to prevent starvation. + end else begin + meta_sel_d = ~meta_sel_q; + end + end + end + // Lock arbitration if valid but not yet ready. + if (arb_valid && !arb_ready) begin + sel_lock_d = 1'b1; + end + end + end + + // Fork arbitrated stream to meta data, memory requests, and R/B channel selection. + stream_fork #( + .N_OUP ( 32'd3 ) + ) i_fork ( + .clk_i, + .rst_ni, + .valid_i ( arb_valid ), + .ready_o ( arb_ready ), + .valid_o ({sel_valid, meta_valid, m2s_req_valid}), + .ready_i ({sel_ready, meta_ready, m2s_req_ready}) + ); + + assign sel_b = meta.write & meta.last; + assign sel_r = ~meta.write | meta.atop[5]; + + stream_fifo #( + .FALL_THROUGH ( 1'b1 ), + .DEPTH ( 32'd1 + BufDepth ), + .T ( logic[1:0] ) + ) i_sel_buf ( + .clk_i, + .rst_ni, + .flush_i ( 1'b0 ), + .testmode_i ( 1'b0 ), + .data_i ({sel_b, sel_r }), + .valid_i ( sel_valid ), + .ready_o ( sel_ready ), + .data_o ({sel_buf_b, sel_buf_r}), + .valid_o ( sel_buf_valid ), + .ready_i ( sel_buf_ready ), + .usage_o ( /* unused */ ) + ); + + stream_fifo #( + .FALL_THROUGH ( 1'b1 ), + .DEPTH ( 32'd1 + BufDepth ), + .T ( meta_t ) + ) i_meta_buf ( + .clk_i, + .rst_ni, + .flush_i ( 1'b0 ), + .testmode_i ( 1'b0 ), + .data_i ( meta ), + .valid_i ( meta_valid ), + .ready_o ( meta_ready ), + .data_o ( meta_buf ), + .valid_o ( meta_buf_valid ), + .ready_i ( meta_buf_ready ), + .usage_o ( /* unused */ ) + ); + + // Assemble the actual memory request from meta information and write data. + assign m2s_req = mem_req_t'{ + addr: meta.addr, + atop: meta.atop, + lock: meta.lock, + strb: axi_req_i.w.strb, + wdata: axi_req_i.w.data, + we: meta.write, + id: meta.id, + user: meta.user, + cache: meta.cache, + prot: meta.prot, + qos: meta.qos, + region: meta.region + }; + + // Interface memory as stream. + stream_to_mem #( + .mem_req_t ( mem_req_t ), + .mem_resp_t ( mem_rsp_t ), + .BufDepth ( BufDepth ) + ) i_stream_to_mem ( + .clk_i, + .rst_ni, + .req_i ( m2s_req ), + .req_valid_i ( m2s_req_valid ), + .req_ready_o ( m2s_req_ready ), + .resp_o ( m2s_resp ), + .resp_valid_o ( m2s_resp_valid ), + .resp_ready_i ( m2s_resp_ready ), + .mem_req_o ( mem_req ), + .mem_req_valid_o ( mem_req_valid ), + .mem_req_ready_i ( mem_req_ready ), + .mem_resp_i ( mem_rdata ), + .mem_resp_valid_i ( mem_rvalid ) + ); + + typedef struct packed { + axi_pkg::atop_t atop; + logic lock; + mem_id_t id; + mem_user_t user; + axi_pkg::cache_t cache; + axi_pkg::prot_t prot; + axi_pkg::qos_t qos; + axi_pkg::region_t region; + } tmp_atop_t; + + tmp_atop_t mem_req_atop; + tmp_atop_t [NumBanks-1:0] banked_req_atop; + + assign mem_req_atop = '{ + atop: mem_req.atop, + lock: mem_req.lock, + id: mem_req.id, + user: mem_req.user, + cache: mem_req.cache, + prot: mem_req.prot, + qos: mem_req.qos, + region: mem_req.region + }; + + for (genvar i = 0; i < NumBanks; i++) begin + assign mem_atop_o [i] = banked_req_atop[i].atop; + assign mem_lock_o [i] = banked_req_atop[i].lock; + assign mem_id_o [i] = banked_req_atop[i].id; + assign mem_user_o [i] = banked_req_atop[i].user; + assign mem_cache_o [i] = banked_req_atop[i].cache; + assign mem_prot_o [i] = banked_req_atop[i].prot; + assign mem_qos_o [i] = banked_req_atop[i].qos; + assign mem_region_o[i] = banked_req_atop[i].region; + end + + logic [NumBanks-1:0][1:0] tmp_ersp; + logic [NumBanks-1:0][1:0] bank_ersp; + for (genvar i = 0; i < NumBanks; i++) begin + assign mem_rdata.err[i] = tmp_ersp[i][0]; + assign mem_rdata.exokay[i] = tmp_ersp[i][1]; + assign bank_ersp[i][0] = mem_err_i[i]; + assign bank_ersp[i][1] = mem_exokay_i[i]; + end + + // Split single memory request to desired number of banks. + mem_to_banks_detailed #( + .AddrWidth ( AddrWidth ), + .DataWidth ( DataWidth ), + .RUserWidth ( 2 ), + .NumBanks ( NumBanks ), + .HideStrb ( HideStrb ), + .MaxTrans ( BufDepth ), + .FifoDepth ( OutFifoDepth ), + .WUserWidth ( $bits(tmp_atop_t) ) + ) i_mem_to_banks ( + .clk_i, + .rst_ni, + .req_i ( mem_req_valid ), + .gnt_o ( mem_req_ready ), + .addr_i ( mem_req.addr ), + .wdata_i ( mem_req.wdata ), + .strb_i ( mem_req.strb ), + .wuser_i ( mem_req_atop ), + .we_i ( mem_req.we ), + .rvalid_o ( mem_rvalid ), + .rdata_o ( mem_rdata.data ), + .ruser_o ( tmp_ersp ), + .bank_req_o ( mem_req_o ), + .bank_gnt_i ( mem_gnt_i ), + .bank_addr_o ( mem_addr_o ), + .bank_wdata_o ( mem_wdata_o ), + .bank_strb_o ( mem_strb_o ), + .bank_wuser_o ( banked_req_atop ), + .bank_we_o ( mem_we_o ), + .bank_rvalid_i ( mem_rvalid_i ), + .bank_rdata_i ( mem_rdata_i ), + .bank_ruser_i ( bank_ersp ) + ); + + // Join memory read data and meta data stream. + logic mem_join_valid, mem_join_ready; + stream_join #( + .N_INP ( 32'd2 ) + ) i_join ( + .inp_valid_i ({m2s_resp_valid, meta_buf_valid}), + .inp_ready_o ({m2s_resp_ready, meta_buf_ready}), + .oup_valid_o ( mem_join_valid ), + .oup_ready_i ( mem_join_ready ) + ); + + // Dynamically fork the joined stream to B and R channels. + stream_fork_dynamic #( + .N_OUP ( 32'd2 ) + ) i_fork_dynamic ( + .clk_i, + .rst_ni, + .valid_i ( mem_join_valid ), + .ready_o ( mem_join_ready ), + .sel_i ({sel_buf_b, sel_buf_r }), + .sel_valid_i ( sel_buf_valid ), + .sel_ready_o ( sel_buf_ready ), + .valid_o ({axi_resp_o.b_valid, axi_resp_o.r_valid}), + .ready_i ({axi_req_i.b_ready, axi_req_i.r_ready }) + ); + + localparam NumBytesPerBank = DataWidth/NumBanks/8; + + logic [NumBanks-1:0] meta_buf_bank_strb, meta_buf_size_enable; + logic resp_b_err, resp_b_exokay, resp_r_err, resp_r_exokay; + + // Collect `err` and `exokay` from all banks + // To ensure correct propagation, `err` is grouped with `OR` and `exokay` is grouped with `AND`. + for (genvar i = 0; i < NumBanks; i++) begin + // Set active write banks based on strobe + assign meta_buf_bank_strb[i] = |meta_buf.strb[i*NumBytesPerBank +: NumBytesPerBank]; + // Set active read banks based on size and address offset: (bank.end > addr) && (bank.start < addr+size) + assign meta_buf_size_enable[i] = ((i*NumBytesPerBank + NumBytesPerBank) > (meta_buf.addr % DataWidth/8)) && + ((i*NumBytesPerBank) < ((meta_buf.addr % DataWidth/8) + 1< $stable(axi_req_i.ar)) + else $error("AR must remain stable until handshake has happened!"); + assert property (@(posedge clk_i) + axi_resp_o.r_valid && !axi_req_i.r_ready |=> $stable(axi_resp_o.r)) + else $error("R must remain stable until handshake has happened!"); + assume property (@(posedge clk_i) + axi_req_i.aw_valid && !axi_resp_o.aw_ready |=> $stable(axi_req_i.aw)) + else $error("AW must remain stable until handshake has happened!"); + assume property (@(posedge clk_i) + axi_req_i.w_valid && !axi_resp_o.w_ready |=> $stable(axi_req_i.w)) + else $error("W must remain stable until handshake has happened!"); + assert property (@(posedge clk_i) + axi_resp_o.b_valid && !axi_req_i.b_ready |=> $stable(axi_resp_o.b)) + else $error("B must remain stable until handshake has happened!"); + assert property (@(posedge clk_i) axi_req_i.ar_valid && axi_req_i.ar.len > 0 |-> + axi_req_i.ar.burst == axi_pkg::BURST_INCR) + else $error("Non-incrementing bursts are not supported!"); + assert property (@(posedge clk_i) axi_req_i.aw_valid && axi_req_i.aw.len > 0 |-> + axi_req_i.aw.burst == axi_pkg::BURST_INCR) + else $error("Non-incrementing bursts are not supported!"); + assert property (@(posedge clk_i) meta_valid && meta.atop != '0 |-> meta.write) + else $warning("Unexpected atomic operation on read."); + `endif + // pragma translate_on +endmodule + + +`include "axi/assign.svh" +`include "axi/typedef.svh" +/// Interface wrapper for module `axi_to_mem`. +module axi_to_detailed_mem_intf #( + /// See `axi_to_mem`, parameter `AddrWidth`. + parameter int unsigned ADDR_WIDTH = 32'd0, + /// See `axi_to_mem`, parameter `DataWidth`. + parameter int unsigned DATA_WIDTH = 32'd0, + /// AXI4+ATOP ID width. + parameter int unsigned ID_WIDTH = 32'd0, + /// AXI4+ATOP user width. + parameter int unsigned USER_WIDTH = 32'd0, + /// See `axi_to_mem`, parameter `NumBanks`. + parameter int unsigned NUM_BANKS = 32'd0, + /// See `axi_to_mem`, parameter `BufDepth`. + parameter int unsigned BUF_DEPTH = 32'd1, + /// Hide write requests if the strb == '0 + parameter bit HIDE_STRB = 1'b0, + /// Depth of output fifo/fall_through_register. Increase for asymmetric backpressure (contention) on banks. + parameter int unsigned OUT_FIFO_DEPTH = 32'd1, + /// Dependent parameter, do not override. See `axi_to_mem`, parameter `addr_t`. + localparam type addr_t = logic [ADDR_WIDTH-1:0], + /// Dependent parameter, do not override. See `axi_to_mem`, parameter `mem_data_t`. + localparam type mem_data_t = logic [DATA_WIDTH/NUM_BANKS-1:0], + /// Dependent parameter, do not override. See `axi_to_mem`, parameter `mem_strb_t`. + localparam type mem_strb_t = logic [DATA_WIDTH/NUM_BANKS/8-1:0] +) ( + /// Clock input. + input logic clk_i, + /// Asynchronous reset, active low. + input logic rst_ni, + /// See `axi_to_mem`, port `busy_o`. + output logic busy_o, + /// AXI4+ATOP slave interface port. + AXI_BUS.Slave slv, + /// See `axi_to_mem`, port `mem_req_o`. + output logic [NUM_BANKS-1:0] mem_req_o, + /// See `axi_to_mem`, port `mem_gnt_i`. + input logic [NUM_BANKS-1:0] mem_gnt_i, + /// See `axi_to_mem`, port `mem_addr_o`. + output addr_t [NUM_BANKS-1:0] mem_addr_o, + /// See `axi_to_mem`, port `mem_wdata_o`. + output mem_data_t [NUM_BANKS-1:0] mem_wdata_o, + /// See `axi_to_mem`, port `mem_strb_o`. + output mem_strb_t [NUM_BANKS-1:0] mem_strb_o, + /// See `axi_to_mem`, port `mem_atop_o`. + output axi_pkg::atop_t [NUM_BANKS-1:0] mem_atop_o, + /// See `axi_to_mem`, port `mem_lock_o`. + output logic [NUM_BANKS-1:0] mem_lock_o, + /// See `axi_to_mem`, port `mem_we_o`. + output logic [NUM_BANKS-1:0] mem_we_o, + /// See `axi_to_mem`, port `mem_id_o`. + output logic [NUM_BANKS-1:0] mem_id_o, + /// See `axi_to_mem`, port `mem_user_o`. + output logic [NUM_BANKS-1:0] mem_user_o, + /// See `axi_to_mem`, port `mem_cache_o`. + output axi_pkg::cache_t [NUM_BANKS-1:0] mem_cache_o, + /// See `axi_to_mem`, port `mem_prot_o`. + output axi_pkg::prot_t [NUM_BANKS-1:0] mem_prot_o, + /// See `axi_to_mem`, port `mem_qos_o`. + output axi_pkg::qos_t [NUM_BANKS-1:0] mem_qos_o, + /// See `axi_to_mem`, port `mem_region_o`. + output axi_pkg::region_t [NUM_BANKS-1:0] mem_region_o, + /// See `axi_to_mem`, port `mem_rvalid_i`. + input logic [NUM_BANKS-1:0] mem_rvalid_i, + /// See `axi_to_mem`, port `mem_rdata_i`. + input mem_data_t [NUM_BANKS-1:0] mem_rdata_i, + /// See `axi_to_mem`, port `mem_err_i`. + input logic [NUM_BANKS-1:0] mem_err_i, + /// See `axi_to_mem`, port `mem_exokay_i`. + input logic [NUM_BANKS-1:0] mem_exokay_i +); + typedef logic [ID_WIDTH-1:0] id_t; + typedef logic [DATA_WIDTH-1:0] data_t; + typedef logic [DATA_WIDTH/8-1:0] strb_t; + typedef logic [USER_WIDTH-1:0] user_t; + `AXI_TYPEDEF_AW_CHAN_T(aw_chan_t, addr_t, id_t, user_t) + `AXI_TYPEDEF_W_CHAN_T(w_chan_t, data_t, strb_t, user_t) + `AXI_TYPEDEF_B_CHAN_T(b_chan_t, id_t, user_t) + `AXI_TYPEDEF_AR_CHAN_T(ar_chan_t, addr_t, id_t, user_t) + `AXI_TYPEDEF_R_CHAN_T(r_chan_t, data_t, id_t, user_t) + `AXI_TYPEDEF_REQ_T(req_t, aw_chan_t, w_chan_t, ar_chan_t) + `AXI_TYPEDEF_RESP_T(resp_t, b_chan_t, r_chan_t) + req_t req; + resp_t resp; + `AXI_ASSIGN_TO_REQ(req, slv) + `AXI_ASSIGN_FROM_RESP(slv, resp) + axi_to_detailed_mem #( + .axi_req_t ( req_t ), + .axi_resp_t ( resp_t ), + .AddrWidth ( ADDR_WIDTH ), + .DataWidth ( DATA_WIDTH ), + .IdWidth ( ID_WIDTH ), + .UserWidth ( USER_WIDTH ), + .NumBanks ( NUM_BANKS ), + .BufDepth ( BUF_DEPTH ), + .HideStrb ( HIDE_STRB ), + .OutFifoDepth ( OUT_FIFO_DEPTH ) + ) i_axi_to_detailed_mem ( + .clk_i, + .rst_ni, + .busy_o, + .axi_req_i ( req ), + .axi_resp_o ( resp ), + .mem_req_o, + .mem_gnt_i, + .mem_addr_o, + .mem_wdata_o, + .mem_strb_o, + .mem_atop_o, + .mem_lock_o, + .mem_we_o, + .mem_id_o, + .mem_user_o, + .mem_cache_o, + .mem_prot_o, + .mem_qos_o, + .mem_region_o, + .mem_rvalid_i, + .mem_rdata_i, + .mem_err_i, + .mem_exokay_i + ); +endmodule diff --git a/src/axi_to_mem.sv b/src/axi_to_mem.sv new file mode 100644 index 000000000..37992d449 --- /dev/null +++ b/src/axi_to_mem.sv @@ -0,0 +1,213 @@ +// Copyright 2020 ETH Zurich and University of Bologna. +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the "License"); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +// Authors: +// - Michael Rogenmoser +// - Thomas Benz + +`include "common_cells/registers.svh" +/// AXI4+ATOP slave module which translates AXI bursts into a memory stream. +/// If both read and write channels of the AXI4+ATOP are active, both will have an +/// utilization of 50%. +module axi_to_mem #( + /// AXI4+ATOP request type. See `include/axi/typedef.svh`. + parameter type axi_req_t = logic, + /// AXI4+ATOP response type. See `include/axi/typedef.svh`. + parameter type axi_resp_t = logic, + /// Address width, has to be less or equal than the width off the AXI address field. + /// Determines the width of `mem_addr_o`. Has to be wide enough to emit the memory region + /// which should be accessible. + parameter int unsigned AddrWidth = 0, + /// AXI4+ATOP data width. + parameter int unsigned DataWidth = 0, + /// AXI4+ATOP ID width. + parameter int unsigned IdWidth = 0, + /// Number of banks at output, must evenly divide `DataWidth`. + parameter int unsigned NumBanks = 0, + /// Depth of memory response buffer. This should be equal to the memory response latency. + parameter int unsigned BufDepth = 1, + /// Hide write requests if the strb == '0 + parameter bit HideStrb = 1'b0, + /// Depth of output fifo/fall_through_register. Increase for asymmetric backpressure (contention) on banks. + parameter int unsigned OutFifoDepth = 1, + /// Dependent parameter, do not override. Memory address type. + localparam type addr_t = logic [AddrWidth-1:0], + /// Dependent parameter, do not override. Memory data type. + localparam type mem_data_t = logic [DataWidth/NumBanks-1:0], + /// Dependent parameter, do not override. Memory write strobe type. + localparam type mem_strb_t = logic [DataWidth/NumBanks/8-1:0] +) ( + /// Clock input. + input logic clk_i, + /// Asynchronous reset, active low. + input logic rst_ni, + /// The unit is busy handling an AXI4+ATOP request. + output logic busy_o, + /// AXI4+ATOP slave port, request input. + input axi_req_t axi_req_i, + /// AXI4+ATOP slave port, response output. + output axi_resp_t axi_resp_o, + /// Memory stream master, request is valid for this bank. + output logic [NumBanks-1:0] mem_req_o, + /// Memory stream master, request can be granted by this bank. + input logic [NumBanks-1:0] mem_gnt_i, + /// Memory stream master, byte address of the request. + output addr_t [NumBanks-1:0] mem_addr_o, + /// Memory stream master, write data for this bank. Valid when `mem_req_o`. + output mem_data_t [NumBanks-1:0] mem_wdata_o, + /// Memory stream master, byte-wise strobe (byte enable). + output mem_strb_t [NumBanks-1:0] mem_strb_o, + /// Memory stream master, `axi_pkg::atop_t` signal associated with this request. + output axi_pkg::atop_t [NumBanks-1:0] mem_atop_o, + /// Memory stream master, write enable. Then asserted store of `mem_w_data` is requested. + output logic [NumBanks-1:0] mem_we_o, + /// Memory stream master, response is valid. This module expects always a response valid for a + /// request regardless if the request was a write or a read. + input logic [NumBanks-1:0] mem_rvalid_i, + /// Memory stream master, read response data. + input mem_data_t [NumBanks-1:0] mem_rdata_i +); + + axi_to_detailed_mem #( + .axi_req_t ( axi_req_t ), + .axi_resp_t ( axi_resp_t ), + .AddrWidth ( AddrWidth ), + .DataWidth ( DataWidth ), + .IdWidth ( IdWidth ), + .UserWidth ( 1 ), + .NumBanks ( NumBanks ), + .BufDepth ( BufDepth ), + .HideStrb ( HideStrb ), + .OutFifoDepth ( OutFifoDepth ) + ) i_axi_to_detailed_mem ( + .clk_i, + .rst_ni, + .busy_o, + .axi_req_i ( axi_req_i ), + .axi_resp_o ( axi_resp_o ), + .mem_req_o ( mem_req_o ), + .mem_gnt_i ( mem_gnt_i ), + .mem_addr_o ( mem_addr_o ), + .mem_wdata_o ( mem_wdata_o ), + .mem_strb_o ( mem_strb_o ), + .mem_atop_o ( mem_atop_o ), + .mem_lock_o (), + .mem_we_o ( mem_we_o ), + .mem_id_o (), + .mem_user_o (), + .mem_cache_o (), + .mem_prot_o (), + .mem_qos_o (), + .mem_region_o (), + .mem_rvalid_i ( mem_rvalid_i ), + .mem_rdata_i ( mem_rdata_i ), + .mem_err_i ('0), + .mem_exokay_i ('0) + ); + +endmodule + + +`include "axi/assign.svh" +`include "axi/typedef.svh" +/// Interface wrapper for module `axi_to_mem`. +module axi_to_mem_intf #( + /// See `axi_to_mem`, parameter `AddrWidth`. + parameter int unsigned ADDR_WIDTH = 32'd0, + /// See `axi_to_mem`, parameter `DataWidth`. + parameter int unsigned DATA_WIDTH = 32'd0, + /// AXI4+ATOP ID width. + parameter int unsigned ID_WIDTH = 32'd0, + /// AXI4+ATOP user width. + parameter int unsigned USER_WIDTH = 32'd0, + /// See `axi_to_mem`, parameter `NumBanks`. + parameter int unsigned NUM_BANKS = 32'd0, + /// See `axi_to_mem`, parameter `BufDepth`. + parameter int unsigned BUF_DEPTH = 32'd1, + /// Hide write requests if the strb == '0 + parameter bit HIDE_STRB = 1'b0, + /// Depth of output fifo/fall_through_register. Increase for asymmetric backpressure (contention) on banks. + parameter int unsigned OUT_FIFO_DEPTH = 32'd1, + /// Dependent parameter, do not override. See `axi_to_mem`, parameter `addr_t`. + localparam type addr_t = logic [ADDR_WIDTH-1:0], + /// Dependent parameter, do not override. See `axi_to_mem`, parameter `mem_data_t`. + localparam type mem_data_t = logic [DATA_WIDTH/NUM_BANKS-1:0], + /// Dependent parameter, do not override. See `axi_to_mem`, parameter `mem_strb_t`. + localparam type mem_strb_t = logic [DATA_WIDTH/NUM_BANKS/8-1:0] +) ( + /// Clock input. + input logic clk_i, + /// Asynchronous reset, active low. + input logic rst_ni, + /// See `axi_to_mem`, port `busy_o`. + output logic busy_o, + /// AXI4+ATOP slave interface port. + AXI_BUS.Slave slv, + /// See `axi_to_mem`, port `mem_req_o`. + output logic [NUM_BANKS-1:0] mem_req_o, + /// See `axi_to_mem`, port `mem_gnt_i`. + input logic [NUM_BANKS-1:0] mem_gnt_i, + /// See `axi_to_mem`, port `mem_addr_o`. + output addr_t [NUM_BANKS-1:0] mem_addr_o, + /// See `axi_to_mem`, port `mem_wdata_o`. + output mem_data_t [NUM_BANKS-1:0] mem_wdata_o, + /// See `axi_to_mem`, port `mem_strb_o`. + output mem_strb_t [NUM_BANKS-1:0] mem_strb_o, + /// See `axi_to_mem`, port `mem_atop_o`. + output axi_pkg::atop_t [NUM_BANKS-1:0] mem_atop_o, + /// See `axi_to_mem`, port `mem_we_o`. + output logic [NUM_BANKS-1:0] mem_we_o, + /// See `axi_to_mem`, port `mem_rvalid_i`. + input logic [NUM_BANKS-1:0] mem_rvalid_i, + /// See `axi_to_mem`, port `mem_rdata_i`. + input mem_data_t [NUM_BANKS-1:0] mem_rdata_i +); + typedef logic [ID_WIDTH-1:0] id_t; + typedef logic [DATA_WIDTH-1:0] data_t; + typedef logic [DATA_WIDTH/8-1:0] strb_t; + typedef logic [USER_WIDTH-1:0] user_t; + `AXI_TYPEDEF_AW_CHAN_T(aw_chan_t, addr_t, id_t, user_t) + `AXI_TYPEDEF_W_CHAN_T(w_chan_t, data_t, strb_t, user_t) + `AXI_TYPEDEF_B_CHAN_T(b_chan_t, id_t, user_t) + `AXI_TYPEDEF_AR_CHAN_T(ar_chan_t, addr_t, id_t, user_t) + `AXI_TYPEDEF_R_CHAN_T(r_chan_t, data_t, id_t, user_t) + `AXI_TYPEDEF_REQ_T(req_t, aw_chan_t, w_chan_t, ar_chan_t) + `AXI_TYPEDEF_RESP_T(resp_t, b_chan_t, r_chan_t) + req_t req; + resp_t resp; + `AXI_ASSIGN_TO_REQ(req, slv) + `AXI_ASSIGN_FROM_RESP(slv, resp) + axi_to_mem #( + .axi_req_t ( req_t ), + .axi_resp_t ( resp_t ), + .AddrWidth ( ADDR_WIDTH ), + .DataWidth ( DATA_WIDTH ), + .IdWidth ( ID_WIDTH ), + .NumBanks ( NUM_BANKS ), + .BufDepth ( BUF_DEPTH ), + .HideStrb ( HIDE_STRB ), + .OutFifoDepth ( OUT_FIFO_DEPTH ) + ) i_axi_to_mem ( + .clk_i, + .rst_ni, + .busy_o, + .axi_req_i ( req ), + .axi_resp_o ( resp ), + .mem_req_o, + .mem_gnt_i, + .mem_addr_o, + .mem_wdata_o, + .mem_strb_o, + .mem_atop_o, + .mem_we_o, + .mem_rvalid_i, + .mem_rdata_i + ); +endmodule diff --git a/src/axi_to_mem_banked.sv b/src/axi_to_mem_banked.sv new file mode 100644 index 000000000..548339f37 --- /dev/null +++ b/src/axi_to_mem_banked.sv @@ -0,0 +1,435 @@ +// Copyright 2020 ETH Zurich and University of Bologna. +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the "License"); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +// Authors: +// - Wolfgang Rönninger +// - Michael Rogenmoser + +/// AXI4+ATOP to banked SRAM memory slave. Allows for parallel read and write transactions. +/// Has higher throughput than `axi_to_mem`, however needs more hardware. +/// +/// The used address space starts at 0x0 and ends at the capacity of all memory banks combined. +/// The higher address bits are ignored for accesses. +module axi_to_mem_banked #( + /// AXI4+ATOP ID width + parameter int unsigned AxiIdWidth = 32'd0, + /// AXI4+ATOP address width + parameter int unsigned AxiAddrWidth = 32'd0, + /// AXI4+ATOP data width + parameter int unsigned AxiDataWidth = 32'd0, + /// AXI4+ATOP AW channel struct + parameter type axi_aw_chan_t = logic, + /// AXI4+ATOP W channel struct + parameter type axi_w_chan_t = logic, + /// AXI4+ATOP B channel struct + parameter type axi_b_chan_t = logic, + /// AXI4+ATOP AR channel struct + parameter type axi_ar_chan_t = logic, + /// AXI4+ATOP R channel struct + parameter type axi_r_chan_t = logic, + /// AXI4+ATOP request struct + parameter type axi_req_t = logic, + /// AXI4+ATOP response struct + parameter type axi_resp_t = logic, + /// Number of memory banks / macros + /// Has to satisfy: + /// - MemNumBanks >= 2 * AxiDataWidth / MemDataWidth + /// - MemNumBanks is a power of 2. + parameter int unsigned MemNumBanks = 32'd4, + /// Address width of an individual memory bank. This is treated as a word address. + parameter int unsigned MemAddrWidth = 32'd11, + /// Data width of the memory macros. + /// Has to satisfy: + /// - AxiDataWidth % MemDataWidth = 0 + parameter int unsigned MemDataWidth = 32'd32, + /// Read latency of the connected memory in cycles + parameter int unsigned MemLatency = 32'd1, + /// Hide write requests if the strb == '0 + parameter bit HideStrb = 1'b0, + /// Depth of output fifo/fall_through_register. Increase for asymmetric backpressure (contention) on banks. + parameter int unsigned OutFifoDepth = 1, + /// DEPENDENT PARAMETER, DO NOT OVERWRITE! Address type of the memory request. + parameter type mem_addr_t = logic [MemAddrWidth-1:0], + /// DEPENDENT PARAMETER, DO NOT OVERWRITE! Atomic operation type for the memory request. + parameter type mem_atop_t = axi_pkg::atop_t, + /// DEPENDENT PARAMETER, DO NOT OVERWRITE! Data type for the memory request. + parameter type mem_data_t = logic [MemDataWidth-1:0], + /// DEPENDENT PARAMETER, DO NOT OVERWRITE! Byte strobe/enable signal for the memory request. + parameter type mem_strb_t = logic [MemDataWidth/8-1:0] +) ( + /// Clock + input logic clk_i, + /// Asynchronous reset, active low + input logic rst_ni, + /// Testmode enable + input logic test_i, + /// AXI4+ATOP slave port, request struct + input axi_req_t axi_req_i, + /// AXI4+ATOP slave port, response struct + output axi_resp_t axi_resp_o, + /// Memory bank request + output logic [MemNumBanks-1:0] mem_req_o, + /// Memory request grant + input logic [MemNumBanks-1:0] mem_gnt_i, + /// Request address + output mem_addr_t [MemNumBanks-1:0] mem_add_o, + /// Write request enable, active high + output logic [MemNumBanks-1:0] mem_we_o, + /// Write data + output mem_data_t [MemNumBanks-1:0] mem_wdata_o, + /// Write data byte enable, active high + output mem_strb_t [MemNumBanks-1:0] mem_be_o, + /// Atomic operation + output mem_atop_t [MemNumBanks-1:0] mem_atop_o, + /// Read data response + input mem_data_t [MemNumBanks-1:0] mem_rdata_i, + /// Status output, busy flag of `axi_to_mem` + output logic [1:0] axi_to_mem_busy_o +); + /// This specifies the number of banks needed to have the full data bandwidth of one + /// AXI data channel. + localparam int unsigned BanksPerAxiChannel = AxiDataWidth / MemDataWidth; + /// Offset of the byte address from AXI to determine, where the selection signal for the + /// memory bank should start. + localparam int unsigned BankSelOffset = $clog2(MemDataWidth / 32'd8); + /// Selection signal width of the xbar. This is the reason for power of two banks, otherwise + /// There are holes in the address mapping. + localparam int unsigned BankSelWidth = cf_math_pkg::idx_width(MemNumBanks); + typedef logic [BankSelWidth-1:0] xbar_sel_t; + + // Typedef for defining the channels + typedef enum logic { + ReadAccess = 1'b0, + WriteAccess = 1'b1 + } access_type_e; + typedef logic [AxiAddrWidth-1:0] axi_addr_t; + + /// Payload definition which is sent over the xbar between the macros and the read/write unit. + typedef struct packed { + /// Address for the memory access + mem_addr_t addr; + /// Write enable, active high + logic we; + /// Write data + mem_data_t wdata; + /// Strobe signal, byte enable + mem_strb_t wstrb; + /// Atomic operation, from AXI + mem_atop_t atop; + } xbar_payload_t; + + /// Read data definition for the shift register, which samples the read response data + typedef struct packed { + /// Selection signal for response routing + xbar_sel_t sel; + /// Selection is valid + logic valid; + } read_sel_t; + + axi_req_t [1:0] mem_axi_reqs; + axi_resp_t [1:0] mem_axi_resps; + + // Fixed select `axi_demux` to split reads and writes to the two `axi_to_mem` + axi_demux #( + .AxiIdWidth ( AxiIdWidth ), + .AtopSupport ( 1'b1 ), + .aw_chan_t ( axi_aw_chan_t ), + .w_chan_t ( axi_w_chan_t ), + .b_chan_t ( axi_b_chan_t ), + .ar_chan_t ( axi_ar_chan_t ), + .r_chan_t ( axi_r_chan_t ), + .axi_req_t ( axi_req_t ), + .axi_resp_t ( axi_resp_t ), + .NoMstPorts ( 32'd2 ), + .MaxTrans ( MemLatency+2 ), // allow multiple Ax vectors to not starve W channel + .AxiLookBits ( 32'd1 ), // select is fixed, do not need it + .UniqueIds ( 1'b1 ), // Can be set as ports are statically selected -> reduces HW + .SpillAw ( 1'b1 ), + .SpillW ( 1'b1 ), + .SpillB ( 1'b1 ), + .SpillAr ( 1'b1 ), + .SpillR ( 1'b1 ) + ) i_axi_demux ( + .clk_i, + .rst_ni, + .test_i, + .slv_req_i ( axi_req_i ), + .slv_aw_select_i ( WriteAccess ), + .slv_ar_select_i ( ReadAccess ), + .slv_resp_o ( axi_resp_o ), + .mst_reqs_o ( mem_axi_reqs ), + .mst_resps_i ( mem_axi_resps ) + ); + + xbar_payload_t [1:0][BanksPerAxiChannel-1:0] inter_payload; + xbar_sel_t [1:0][BanksPerAxiChannel-1:0] inter_sel; + logic [1:0][BanksPerAxiChannel-1:0] inter_valid, inter_ready; + + // axi_to_mem protocol converter + for (genvar i = 0; i < 2; i++) begin : gen_axi_to_mem + axi_addr_t [BanksPerAxiChannel-1:0] req_addr; // This is a byte address + mem_data_t [BanksPerAxiChannel-1:0] req_wdata, res_rdata; + mem_strb_t [BanksPerAxiChannel-1:0] req_wstrb; + mem_atop_t [BanksPerAxiChannel-1:0] req_atop; + + logic [BanksPerAxiChannel-1:0] req_we, res_valid; + + // Careful, request / grant + // Only assert grant, if there is a ready + axi_to_mem #( + .axi_req_t ( axi_req_t ), + .axi_resp_t ( axi_resp_t ), + .AddrWidth ( AxiAddrWidth ), + .DataWidth ( AxiDataWidth ), + .IdWidth ( AxiIdWidth ), + .NumBanks ( BanksPerAxiChannel ), + .BufDepth ( MemLatency ), + .HideStrb ( HideStrb ), + .OutFifoDepth ( OutFifoDepth ) + ) i_axi_to_mem ( + .clk_i, + .rst_ni, + .busy_o ( axi_to_mem_busy_o[i] ), + .axi_req_i ( mem_axi_reqs[i] ), + .axi_resp_o ( mem_axi_resps[i] ), + .mem_req_o ( inter_valid[i] ), + .mem_gnt_i ( inter_ready[i] & inter_valid[i] ), // convert valid/ready to req/gnt + .mem_addr_o ( req_addr ), + .mem_wdata_o ( req_wdata ), + .mem_strb_o ( req_wstrb ), + .mem_atop_o ( req_atop ), + .mem_we_o ( req_we ), + .mem_rvalid_i ( res_valid ), + .mem_rdata_i ( res_rdata ) + ); + // Pack the payload data together + for (genvar j = 0; unsigned'(j) < BanksPerAxiChannel; j++) begin : gen_response_mux + // Cut out the bank selection signal. + assign inter_sel[i][j] = req_addr[j][BankSelOffset+:BankSelWidth]; + + // Assign the xbar payload. + assign inter_payload[i][j] = xbar_payload_t'{ + // Cut out the word address for the banks. + addr: req_addr[j][(BankSelOffset+BankSelWidth)+:MemAddrWidth], + we: req_we[j], + wdata: req_wdata[j], + wstrb: req_wstrb[j], + atop: req_atop[j], + default: '0 + }; + + // Cut out the portion of the address for the bank selection, each bank is word addressed! + read_sel_t r_shift_inp, r_shift_oup; + // Pack the selection into the shift register + assign r_shift_inp = read_sel_t'{ + sel: inter_sel[i][j], // Selection for response multiplexer + valid: inter_valid[i][j] & inter_ready[i][j], // Valid when req to SRAM + default: '0 + }; + + // Select the right read response data. + // Writes should also generate a `response`. + assign res_valid[j] = r_shift_oup.valid; + assign res_rdata[j] = mem_rdata_i[r_shift_oup.sel]; + + // Connect for the response data `MemLatency` cycles after a request was made to the xbar. + shift_reg #( + .dtype ( read_sel_t ), + .Depth ( MemLatency ) + ) i_shift_reg_rdata_mux ( + .clk_i, + .rst_ni, + .d_i ( r_shift_inp ), + .d_o ( r_shift_oup ) + ); + end + end + + // Xbar to arbitrate data over the different memory banks + xbar_payload_t [MemNumBanks-1:0] mem_payload; + + stream_xbar #( + .NumInp ( 32'd2 * BanksPerAxiChannel ), + .NumOut ( MemNumBanks ), + .payload_t ( xbar_payload_t ), + .OutSpillReg ( 1'b0 ), + .ExtPrio ( 1'b0 ), + .AxiVldRdy ( 1'b1 ), + .LockIn ( 1'b1 ) + ) i_stream_xbar ( + .clk_i, + .rst_ni, + .flush_i ( 1'b0 ), + .rr_i ( '0 ), + .data_i ( inter_payload ), + .sel_i ( inter_sel ), + .valid_i ( inter_valid ), + .ready_o ( inter_ready ), + .data_o ( mem_payload ), + .idx_o ( /*not used*/ ), + .valid_o ( mem_req_o ), + .ready_i ( mem_gnt_i ) + ); + + // Memory request output assignment + for (genvar i = 0; unsigned'(i) < MemNumBanks; i++) begin : gen_mem_outp + assign mem_add_o[i] = mem_payload[i].addr; + assign mem_we_o[i] = mem_payload[i].we; + assign mem_wdata_o[i] = mem_payload[i].wdata; + assign mem_be_o[i] = mem_payload[i].wstrb; + assign mem_atop_o[i] = mem_payload[i].atop; + end + +// pragma translate_off +`ifndef VERILATOR + initial begin: p_assertions + assert (AxiIdWidth >= 32'd1) else $fatal(1, "AxiIdWidth must be at least 1!"); + assert (AxiAddrWidth >= 32'd1) else $fatal(1, "AxiAddrWidth must be at least 1!"); + assert (AxiDataWidth >= 32'd1) else $fatal(1, "AxiDataWidth must be at least 1!"); + assert (MemNumBanks >= 32'd2 * AxiDataWidth / MemDataWidth) else + $fatal(1, "MemNumBanks has to be >= 2 * AxiDataWidth / MemDataWidth"); + assert (MemLatency >= 32'd1) else $fatal(1, "MemLatency has to be at least 1!"); + assert ($onehot(MemNumBanks)) else $fatal(1, "MemNumBanks has to be a power of 2."); + assert (MemAddrWidth >= 32'd1) else $fatal(1, "MemAddrWidth must be at least 1!"); + assert (MemDataWidth >= 32'd1) else $fatal(1, "MemDataWidth must be at least 1!"); + assert (AxiDataWidth % MemDataWidth == 0) else + $fatal(1, "MemDataWidth has to be a divisor of AxiDataWidth."); + end +`endif +// pragma translate_on +endmodule + +`include "axi/typedef.svh" +`include "axi/assign.svh" +/// AXI4+ATOP interface wrapper for `axi_to_mem` +module axi_to_mem_banked_intf #( + /// AXI4+ATOP ID width + parameter int unsigned AXI_ID_WIDTH = 32'd0, + /// AXI4+ATOP address width + parameter int unsigned AXI_ADDR_WIDTH = 32'd0, + /// AXI4+ATOP data width + parameter int unsigned AXI_DATA_WIDTH = 32'd0, + /// AXI4+ATOP user width + parameter int unsigned AXI_USER_WIDTH = 32'd0, + /// Number of memory banks / macros + /// Has to satisfy: + /// - MemNumBanks >= 2 * AxiDataWidth / MemDataWidth + /// - MemNumBanks is a power of 2. + parameter int unsigned MEM_NUM_BANKS = 32'd4, + /// Address width of an individual memory bank. + parameter int unsigned MEM_ADDR_WIDTH = 32'd11, + /// Data width of the memory macros. + /// Has to satisfy: + /// - AxiDataWidth % MemDataWidth = 0 + parameter int unsigned MEM_DATA_WIDTH = 32'd32, + /// Read latency of the connected memory in cycles + parameter int unsigned MEM_LATENCY = 32'd1, + /// Hide write requests if the strb == '0 + parameter bit HIDE_STRB = 1'b0, + /// Depth of output fifo/fall_through_register. Increase for asymmetric backpressure (contention) on banks. + parameter int unsigned OUT_FIFO_DEPTH = 32'd1, + // DEPENDENT PARAMETERS, DO NOT OVERWRITE! + parameter type mem_addr_t = logic [MEM_ADDR_WIDTH-1:0], + parameter type mem_atop_t = logic [5:0], + parameter type mem_data_t = logic [MEM_DATA_WIDTH-1:0], + parameter type mem_strb_t = logic [MEM_DATA_WIDTH/8-1:0] +) ( + /// Clock + input logic clk_i, + /// Asynchronous reset, active low + input logic rst_ni, + /// Testmode enable + input logic test_i, + /// AXI4+ATOP slave port + AXI_BUS.Slave slv, + /// Memory bank request + output logic [MEM_NUM_BANKS-1:0] mem_req_o, + /// Memory request grant + input logic [MEM_NUM_BANKS-1:0] mem_gnt_i, + /// Request address + output mem_addr_t [MEM_NUM_BANKS-1:0] mem_add_o, + /// Write request enable, active high + output logic [MEM_NUM_BANKS-1:0] mem_we_o, + /// Write data + output mem_data_t [MEM_NUM_BANKS-1:0] mem_wdata_o, + /// Write data byte enable, active high + output mem_strb_t [MEM_NUM_BANKS-1:0] mem_be_o, + /// Atomic operation + output mem_atop_t [MEM_NUM_BANKS-1:0] mem_atop_o, + /// Read data response + input mem_data_t [MEM_NUM_BANKS-1:0] mem_rdata_i, + /// Status output, busy flag of `axi_to_mem` + output logic [1:0] axi_to_mem_busy_o +); + typedef logic [AXI_ID_WIDTH-1:0] id_t; + typedef logic [AXI_ADDR_WIDTH-1:0] addr_t; + typedef logic [AXI_DATA_WIDTH-1:0] data_t; + typedef logic [AXI_DATA_WIDTH/8-1:0] strb_t; + typedef logic [AXI_USER_WIDTH-1:0] user_t; + `AXI_TYPEDEF_AW_CHAN_T(aw_chan_t, addr_t, id_t, user_t) + `AXI_TYPEDEF_W_CHAN_T(w_chan_t, data_t, strb_t, user_t) + `AXI_TYPEDEF_B_CHAN_T(b_chan_t, id_t, user_t) + `AXI_TYPEDEF_AR_CHAN_T(ar_chan_t, addr_t, id_t, user_t) + `AXI_TYPEDEF_R_CHAN_T(r_chan_t, data_t, id_t, user_t) + `AXI_TYPEDEF_REQ_T(axi_req_t, aw_chan_t, w_chan_t, ar_chan_t) + `AXI_TYPEDEF_RESP_T(axi_resp_t, b_chan_t, r_chan_t) + + axi_req_t mem_axi_req; + axi_resp_t mem_axi_resp; + + `AXI_ASSIGN_TO_REQ(mem_axi_req, slv) + `AXI_ASSIGN_FROM_RESP(slv, mem_axi_resp) + + axi_to_mem_banked #( + .AxiIdWidth ( AXI_ID_WIDTH ), + .AxiAddrWidth ( AXI_ADDR_WIDTH ), + .AxiDataWidth ( AXI_DATA_WIDTH ), + .axi_aw_chan_t ( aw_chan_t ), + .axi_w_chan_t ( w_chan_t ), + .axi_b_chan_t ( b_chan_t ), + .axi_ar_chan_t ( ar_chan_t ), + .axi_r_chan_t ( r_chan_t ), + .axi_req_t ( axi_req_t ), + .axi_resp_t ( axi_resp_t ), + .MemNumBanks ( MEM_NUM_BANKS ), + .MemAddrWidth ( MEM_ADDR_WIDTH ), + .MemDataWidth ( MEM_DATA_WIDTH ), + .MemLatency ( MEM_LATENCY ), + .HideStrb ( HIDE_STRB ), + .OutFifoDepth ( OUT_FIFO_DEPTH ) + ) i_axi_to_mem_banked ( + .clk_i, + .rst_ni, + .test_i, + .axi_to_mem_busy_o, + .axi_req_i ( mem_axi_req ), + .axi_resp_o ( mem_axi_resp ), + .mem_req_o, + .mem_gnt_i, + .mem_add_o, + .mem_wdata_o, + .mem_be_o, + .mem_atop_o, + .mem_we_o, + .mem_rdata_i + ); + +// pragma translate_off +`ifndef VERILATOR + initial begin: p_assertions + assert (AXI_ADDR_WIDTH >= 1) else $fatal(1, "AXI address width must be at least 1!"); + assert (AXI_DATA_WIDTH >= 1) else $fatal(1, "AXI data width must be at least 1!"); + assert (AXI_ID_WIDTH >= 1) else $fatal(1, "AXI ID width must be at least 1!"); + assert (AXI_USER_WIDTH >= 1) else $fatal(1, "AXI user width must be at least 1!"); + end +`endif +// pragma translate_on +endmodule + diff --git a/src/axi_to_mem_interleaved.sv b/src/axi_to_mem_interleaved.sv new file mode 100644 index 000000000..9a5f87805 --- /dev/null +++ b/src/axi_to_mem_interleaved.sv @@ -0,0 +1,365 @@ +// Copyright 2018 ETH Zurich and University of Bologna. +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the "License"); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +// Authors: +// - Wolfgang Roenninger +// - Thomas Benz +// - Michael Rogenmoser + +/// AXI4+ATOP to SRAM memory slave. Allows for parallel read and write transactions. +/// Allows reads to bypass writes, in contrast to `axi_to_mem`, however needs more hardware. +module axi_to_mem_interleaved #( + /// AXI4+ATOP request type. See `include/axi/typedef.svh`. + parameter type axi_req_t = logic, + /// AXI4+ATOP response type. See `include/axi/typedef.svh`. + parameter type axi_resp_t = logic, + /// Address width, has to be less or equal than the width off the AXI address field. + /// Determines the width of `mem_addr_o`. Has to be wide enough to emit the memory region + /// which should be accessible. + parameter int unsigned AddrWidth = 0, + /// AXI4+ATOP data width. + parameter int unsigned DataWidth = 0, + /// AXI4+ATOP ID width. + parameter int unsigned IdWidth = 0, + /// Number of banks at output, must evenly divide `DataWidth`. + parameter int unsigned NumBanks = 0, + /// Depth of memory response buffer. This should be equal to the memory response latency. + parameter int unsigned BufDepth = 1, + /// Hide write requests if the strb == '0 + parameter bit HideStrb = 1'b0, + /// Depth of output fifo/fall_through_register. Increase for asymmetric backpressure (contention) on banks. + parameter int unsigned OutFifoDepth = 1, + /// Dependent parameter, do not override. Memory address type. + parameter type addr_t = logic [AddrWidth-1:0], + /// Dependent parameter, do not override. Memory data type. + parameter type mem_data_t = logic [DataWidth/NumBanks-1:0], + /// Dependent parameter, do not override. Memory write strobe type. + parameter type mem_strb_t = logic [DataWidth/NumBanks/8-1:0] +) ( + /// Clock input. + input logic clk_i, + /// Asynchronous reset, active low. + input logic rst_ni, + /// Testmode enable + input logic test_i, + /// The unit is busy handling an AXI4+ATOP request. + output logic busy_o, + /// AXI4+ATOP slave port, request input. + input axi_req_t axi_req_i, + /// AXI4+ATOP slave port, response output. + output axi_resp_t axi_resp_o, + /// Memory stream master, request is valid for this bank. + output logic [NumBanks-1:0] mem_req_o, + /// Memory stream master, request can be granted by this bank. + input logic [NumBanks-1:0] mem_gnt_i, + /// Memory stream master, byte address of the request. + output addr_t [NumBanks-1:0] mem_addr_o, + /// Memory stream master, write data for this bank. Valid when `mem_req_o`. + output mem_data_t [NumBanks-1:0] mem_wdata_o, + /// Memory stream master, byte-wise strobe (byte enable). + output mem_strb_t [NumBanks-1:0] mem_strb_o, + /// Memory stream master, `axi_pkg::atop_t` signal associated with this request. + output axi_pkg::atop_t [NumBanks-1:0] mem_atop_o, + /// Memory stream master, write enable. Then asserted store of `mem_w_data` is requested. + output logic [NumBanks-1:0] mem_we_o, + /// Memory stream master, response is valid. This module expects always a response valid for a + /// request regardless if the request was a write or a read. + input logic [NumBanks-1:0] mem_rvalid_i, + /// Memory stream master, read response data. + input mem_data_t [NumBanks-1:0] mem_rdata_i +); + + // internal signals + logic w_busy, r_busy; + logic [NumBanks-1:0] arb_outcome, arb_outcome_head; + + // internal AXI buses + axi_req_t r_axi_req, w_axi_req; + axi_resp_t r_axi_resp, w_axi_resp; + + // internal TCDM buses + logic [NumBanks-1:0] r_mem_req, w_mem_req; + logic [NumBanks-1:0] r_mem_gnt, w_mem_gnt; + addr_t [NumBanks-1:0] r_mem_addr, w_mem_addr; + mem_data_t [NumBanks-1:0] r_mem_wdata, w_mem_wdata; + mem_strb_t [NumBanks-1:0] r_mem_strb, w_mem_strb; + axi_pkg::atop_t [NumBanks-1:0] r_mem_atop, w_mem_atop; + logic [NumBanks-1:0] r_mem_we, w_mem_we; + logic [NumBanks-1:0] r_mem_rvalid, w_mem_rvalid; + mem_data_t [NumBanks-1:0] r_mem_rdata, w_mem_rdata; + + // split AXI bus in read and write + axi_demux_simple #( + .AxiIdWidth ( IdWidth ), + .AtopSupport ( 1'b1 ), + .axi_req_t ( axi_req_t ), + .axi_resp_t ( axi_resp_t ), + .NoMstPorts ( 2 ), + .MaxTrans ( BufDepth ), + .AxiLookBits ( 1 ), // select is fixed, do not need it + .UniqueIds ( 1'b1 ) // Can be set as ports are statically selected -> reduces HW + ) i_split_read_write ( + .clk_i, + .rst_ni, + .test_i, + .slv_req_i ( axi_req_i ), + .slv_ar_select_i ( 1'b0 ), + .slv_aw_select_i ( 1'b1 ), + .slv_resp_o ( axi_resp_o ), + .mst_reqs_o ( {w_axi_req, r_axi_req} ), + .mst_resps_i ( {w_axi_resp, r_axi_resp} ) + ); + + axi_to_mem #( + .axi_req_t ( axi_req_t ), + .axi_resp_t ( axi_resp_t ), + .AddrWidth ( AddrWidth ), + .DataWidth ( DataWidth ), + .IdWidth ( IdWidth ), + .NumBanks ( NumBanks ), + .BufDepth ( BufDepth ), + .HideStrb ( HideStrb ), + .OutFifoDepth( OutFifoDepth ) + ) i_axi_to_mem_write ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .busy_o ( w_busy ), + .axi_req_i ( w_axi_req ), + .axi_resp_o ( w_axi_resp ), + .mem_req_o ( w_mem_req ), + .mem_gnt_i ( w_mem_gnt ), + .mem_addr_o ( w_mem_addr ), + .mem_wdata_o ( w_mem_wdata ), + .mem_strb_o ( w_mem_strb ), + .mem_atop_o ( w_mem_atop ), + .mem_we_o ( w_mem_we ), + .mem_rvalid_i ( w_mem_rvalid ), + .mem_rdata_i ( w_mem_rdata ) + ); + + axi_to_mem #( + .axi_req_t ( axi_req_t ), + .axi_resp_t ( axi_resp_t ), + .AddrWidth ( AddrWidth ), + .DataWidth ( DataWidth ), + .IdWidth ( IdWidth ), + .NumBanks ( NumBanks ), + .BufDepth ( BufDepth ), + .HideStrb ( HideStrb ), + .OutFifoDepth ( OutFifoDepth ) + ) i_axi_to_mem_read ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .busy_o ( r_busy ), + .axi_req_i ( r_axi_req ), + .axi_resp_o ( r_axi_resp ), + .mem_req_o ( r_mem_req ), + .mem_gnt_i ( r_mem_gnt ), + .mem_addr_o ( r_mem_addr ), + .mem_wdata_o ( r_mem_wdata ), + .mem_strb_o ( r_mem_strb ), + .mem_atop_o ( r_mem_atop ), + .mem_we_o ( r_mem_we ), + .mem_rvalid_i ( r_mem_rvalid ), + .mem_rdata_i ( r_mem_rdata ) + ); + + // create a struct for the rr-arb-tree + typedef struct packed { + addr_t addr; + mem_data_t wdata; + mem_strb_t strb; + logic we; + axi_pkg::atop_t atop; + } mem_req_payload_t; + + mem_req_payload_t [NumBanks-1:0] r_payload, w_payload, payload; + + for (genvar i = 0; i < NumBanks; i++) begin + // pack the mem + assign r_payload[i].addr = r_mem_addr[i]; + assign r_payload[i].wdata = r_mem_wdata[i]; + assign r_payload[i].strb = r_mem_strb[i]; + assign r_payload[i].we = r_mem_we[i]; + assign r_payload[i].atop = r_mem_atop[i]; + + assign w_payload[i].addr = w_mem_addr[i]; + assign w_payload[i].wdata = w_mem_wdata[i]; + assign w_payload[i].strb = w_mem_strb[i]; + assign w_payload[i].we = w_mem_we[i]; + assign w_payload[i].atop = w_mem_atop[i]; + + assign mem_addr_o [i] = payload[i].addr; + assign mem_wdata_o[i] = payload[i].wdata; + assign mem_strb_o [i] = payload[i].strb; + assign mem_we_o [i] = payload[i].we; + assign mem_atop_o [i] = payload[i].atop; + + // route data back to both channels + assign w_mem_rdata[i] = mem_rdata_i[i]; + assign r_mem_rdata[i] = mem_rdata_i[i]; + + assign w_mem_rvalid[i] = mem_rvalid_i[i] & !arb_outcome_head[i]; + assign r_mem_rvalid[i] = mem_rvalid_i[i] & arb_outcome_head[i]; + + // fine-grain arbitration + rr_arb_tree #( + .NumIn ( 2 ), + .DataType ( mem_req_payload_t ) + ) i_rr_arb_tree ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .flush_i ( 1'b0 ), + .rr_i ( '0 ), + .req_i ( { r_mem_req[i], w_mem_req[i] } ), + .gnt_o ( { r_mem_gnt[i], w_mem_gnt[i] } ), + .data_i ( { r_payload[i], w_payload[i] } ), + .req_o ( mem_req_o[i] ), + .gnt_i ( mem_gnt_i[i] ), + .data_o ( payload[i] ), + .idx_o ( arb_outcome[i] ) + ); + + // back-routing store + fifo_v3 #( + .DATA_WIDTH ( 1 ), + .DEPTH ( BufDepth + 1 ) + ) i_fifo_v3_response_trgt_store ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .flush_i ( 1'b0 ), + .testmode_i ( 1'b0 ), + .full_o ( ), + .empty_o ( ), + .usage_o ( ), + .data_i ( arb_outcome[i] ), + .push_i ( mem_req_o[i] & mem_gnt_i[i] ), + .data_o ( arb_outcome_head[i] ), + .pop_i ( mem_rvalid_i[i] ) + ); + end + +endmodule : axi_to_mem_interleaved + +`include "axi/typedef.svh" +`include "axi/assign.svh" +/// AXI4+ATOP interface wrapper for `axi_to_mem_interleaved` +module axi_to_mem_interleaved_intf #( + /// AXI4+ATOP ID width + parameter int unsigned AXI_ID_WIDTH = 32'd0, + /// AXI4+ATOP address width + parameter int unsigned AXI_ADDR_WIDTH = 32'd0, + /// AXI4+ATOP data width + parameter int unsigned AXI_DATA_WIDTH = 32'd0, + /// AXI4+ATOP user width + parameter int unsigned AXI_USER_WIDTH = 32'd0, + /// Number of memory banks / macros + parameter int unsigned MEM_NUM_BANKS = 32'd4, + /// Read latency of the connected memory in cycles + parameter int unsigned BUF_DEPTH = 32'd1, + /// Hide write requests if the strb == '0 + parameter bit HIDE_STRB = 1'b0, + /// Depth of output fifo/fall_through_register. Increase for asymmetric backpressure (contention) on banks. + parameter int unsigned OUT_FIFO_DEPTH = 32'd1, + + /// Dependent parameter, do not override. Memory address type. + parameter type mem_addr_t = logic [AXI_ADDR_WIDTH-1:0], + /// Dependent parameter, do not override. Memory atomic type. + parameter type mem_atop_t = axi_pkg::atop_t, + /// Dependent parameter, do not override. Memory data type. + parameter type mem_data_t = logic [AXI_DATA_WIDTH/MEM_NUM_BANKS-1:0], + /// Dependent parameter, do not override. Memory write strobe type. + parameter type mem_strb_t = logic [AXI_DATA_WIDTH/MEM_NUM_BANKS/8-1:0] +) ( + /// Clock + input logic clk_i, + /// Asynchronous reset, active low + input logic rst_ni, + /// Status output, busy flag of `axi_to_mem` + output logic busy_o, + /// AXI4+ATOP slave port + AXI_BUS.Slave slv, + /// Memory bank request + output logic [MEM_NUM_BANKS-1:0] mem_req_o, + /// Memory request grant + input logic [MEM_NUM_BANKS-1:0] mem_gnt_i, + /// Request address + output mem_addr_t [MEM_NUM_BANKS-1:0] mem_addr_o, + /// Write data + output mem_data_t [MEM_NUM_BANKS-1:0] mem_wdata_o, + /// Write data byte enable, active high + output mem_strb_t [MEM_NUM_BANKS-1:0] mem_strb_o, + /// Atomic operation + output mem_atop_t [MEM_NUM_BANKS-1:0] mem_atop_o, + /// Write request enable, active high + output logic [MEM_NUM_BANKS-1:0] mem_we_o, + /// Read data valid response, active high + input logic [MEM_NUM_BANKS-1:0] mem_rvalid_i, + /// Read data response + input mem_data_t [MEM_NUM_BANKS-1:0] mem_rdata_i +); + typedef logic [AXI_ID_WIDTH-1:0] id_t; + typedef logic [AXI_ADDR_WIDTH-1:0] addr_t; + typedef logic [AXI_DATA_WIDTH-1:0] data_t; + typedef logic [AXI_DATA_WIDTH/8-1:0] strb_t; + typedef logic [AXI_USER_WIDTH-1:0] user_t; + `AXI_TYPEDEF_AW_CHAN_T(aw_chan_t, addr_t, id_t, user_t) + `AXI_TYPEDEF_W_CHAN_T(w_chan_t, data_t, strb_t, user_t) + `AXI_TYPEDEF_B_CHAN_T(b_chan_t, id_t, user_t) + `AXI_TYPEDEF_AR_CHAN_T(ar_chan_t, addr_t, id_t, user_t) + `AXI_TYPEDEF_R_CHAN_T(r_chan_t, data_t, id_t, user_t) + `AXI_TYPEDEF_REQ_T(axi_req_t, aw_chan_t, w_chan_t, ar_chan_t) + `AXI_TYPEDEF_RESP_T(axi_resp_t, b_chan_t, r_chan_t) + + axi_req_t mem_axi_req; + axi_resp_t mem_axi_resp; + + `AXI_ASSIGN_TO_REQ(mem_axi_req, slv) + `AXI_ASSIGN_FROM_RESP(slv, mem_axi_resp) + + axi_to_mem_interleaved #( + .axi_req_t ( axi_req_t ), + .axi_resp_t ( axi_resp_t ), + .AddrWidth ( AXI_ADDR_WIDTH ), + .DataWidth ( AXI_DATA_WIDTH ), + .IdWidth ( AXI_ID_WIDTH ), + .NumBanks ( MEM_NUM_BANKS ), + .BufDepth ( BUF_DEPTH ), + .HideStrb ( HIDE_STRB ), + .OutFifoDepth ( OUT_FIFO_DEPTH ) + ) i_axi_to_mem_interleaved ( + .clk_i, + .rst_ni, + .busy_o, + .axi_req_i ( mem_axi_req ), + .axi_resp_o ( mem_axi_resp ), + .mem_req_o, + .mem_gnt_i, + .mem_addr_o, + .mem_wdata_o, + .mem_strb_o, + .mem_atop_o, + .mem_we_o, + .mem_rvalid_i, + .mem_rdata_i + ); + +// pragma translate_off +`ifndef VERILATOR + initial begin: p_assertions + assert (AXI_ADDR_WIDTH >= 1) else $fatal(1, "AXI address width must be at least 1!"); + assert (AXI_DATA_WIDTH >= 1) else $fatal(1, "AXI data width must be at least 1!"); + assert (AXI_ID_WIDTH >= 1) else $fatal(1, "AXI ID width must be at least 1!"); + assert (AXI_USER_WIDTH >= 1) else $fatal(1, "AXI user width must be at least 1!"); + end +`endif +// pragma translate_on + +endmodule // axi_to_mem_interleaved_intf diff --git a/src/axi_to_mem_split.sv b/src/axi_to_mem_split.sv new file mode 100644 index 000000000..28ce40831 --- /dev/null +++ b/src/axi_to_mem_split.sv @@ -0,0 +1,261 @@ +// Copyright 2022 ETH Zurich and University of Bologna. +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the "License"); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. +// +// Authors: +// - Michael Rogenmoser +// - Thomas Benz + +`include "axi/assign.svh" +/// AXI4+ATOP to memory-protocol interconnect. Completely separates the read and write channel to +/// individual mem ports. This can only be used when addresses for the same bank are accessible +/// from different memory ports. +module axi_to_mem_split #( + /// AXI4+ATOP request type. See `include/axi/typedef.svh`. + parameter type axi_req_t = logic, + /// AXI4+ATOP response type. See `include/axi/typedef.svh`. + parameter type axi_resp_t = logic, + /// Address width, has to be less or equal than the width off the AXI address field. + /// Determines the width of `mem_addr_o`. Has to be wide enough to emit the memory region + /// which should be accessible. + parameter int unsigned AddrWidth = 0, + /// AXI4+ATOP data width. + parameter int unsigned AxiDataWidth = 0, + /// AXI4+ATOP ID width. + parameter int unsigned IdWidth = 0, + /// Memory data width, must evenly divide `DataWidth`. + parameter int unsigned MemDataWidth = 0, // must divide `AxiDataWidth` without remainder + /// Depth of memory response buffer. This should be equal to the memory response latency. + parameter int unsigned BufDepth = 0, + /// Hide write requests if the strb == '0 + parameter bit HideStrb = 1'b0, + /// Depth of output fifo/fall_through_register. Increase for asymmetric backpressure (contention) on banks. + parameter int unsigned OutFifoDepth = 1, + /// Dependent parameters, do not override. Number of memory ports. + parameter int unsigned NumMemPorts = 2*AxiDataWidth/MemDataWidth, + /// Dependent parameter, do not override. Memory address type. + parameter type addr_t = logic [AddrWidth-1:0], + /// Dependent parameter, do not override. Memory data type. + parameter type mem_data_t = logic [MemDataWidth-1:0], + /// Dependent parameter, do not override. Memory write strobe type. + parameter type mem_strb_t = logic [MemDataWidth/8-1:0] +) ( + /// Clock input. + input logic clk_i, + /// Asynchronous reset, active low. + input logic rst_ni, + /// Testmode enable + input logic test_i, + /// The unit is busy handling an AXI4+ATOP request. + output logic busy_o, + /// AXI4+ATOP slave port, request input. + input axi_req_t axi_req_i, + /// AXI4+ATOP slave port, response output. + output axi_resp_t axi_resp_o, + /// Memory stream master, request is valid for this bank. + output logic [NumMemPorts-1:0] mem_req_o, + /// Memory stream master, request can be granted by this bank. + input logic [NumMemPorts-1:0] mem_gnt_i, + /// Memory stream master, byte address of the request. + output addr_t [NumMemPorts-1:0] mem_addr_o, // byte address + /// Memory stream master, write data for this bank. Valid when `mem_req_o`. + output mem_data_t [NumMemPorts-1:0] mem_wdata_o, // write data + /// Memory stream master, byte-wise strobe (byte enable). + output mem_strb_t [NumMemPorts-1:0] mem_strb_o, // byte-wise strobe + /// Memory stream master, `axi_pkg::atop_t` signal associated with this request. + output axi_pkg::atop_t [NumMemPorts-1:0] mem_atop_o, // atomic operation + /// Memory stream master, write enable. Then asserted store of `mem_w_data` is requested. + output logic [NumMemPorts-1:0] mem_we_o, // write enable + /// Memory stream master, response is valid. This module expects always a response valid for a + /// request regardless if the request was a write or a read. + input logic [NumMemPorts-1:0] mem_rvalid_i, // response valid + /// Memory stream master, read response data. + input mem_data_t [NumMemPorts-1:0] mem_rdata_i // read data +); + + axi_req_t axi_read_req, axi_write_req; + axi_resp_t axi_read_resp, axi_write_resp; + + logic read_busy, write_busy; + + // split AXI bus in read and write + axi_demux_simple #( + .AxiIdWidth ( IdWidth ), + .AtopSupport ( 1'b1 ), + .axi_req_t ( axi_req_t ), + .axi_resp_t ( axi_resp_t ), + .NoMstPorts ( 2 ), + .MaxTrans ( BufDepth ), + .AxiLookBits ( 1 ), // select is fixed, do not need it + .UniqueIds ( 1'b1 ) // Can be set as ports are statically selected -> reduces HW + ) i_split_read_write ( + .clk_i, + .rst_ni, + .test_i, + .slv_req_i ( axi_req_i ), + .slv_ar_select_i ( 1'b0 ), + .slv_aw_select_i ( 1'b1 ), + .slv_resp_o ( axi_resp_o ), + .mst_reqs_o ( {axi_write_req, axi_read_req} ), + .mst_resps_i ( {axi_write_resp, axi_read_resp} ) + ); + + assign busy_o = read_busy || write_busy; + + axi_to_mem #( + .axi_req_t ( axi_req_t ), + .axi_resp_t ( axi_resp_t ), + .AddrWidth ( AddrWidth ), + .DataWidth ( AxiDataWidth ), + .IdWidth ( IdWidth ), + .NumBanks ( NumMemPorts/2 ), + .BufDepth ( BufDepth ), + .HideStrb ( 1'b0 ), + .OutFifoDepth ( OutFifoDepth ) + ) i_axi_to_mem_read ( + .clk_i, + .rst_ni, + .busy_o ( read_busy ), + .axi_req_i ( axi_read_req ), + .axi_resp_o ( axi_read_resp ), + .mem_req_o ( mem_req_o [NumMemPorts/2-1:0] ), + .mem_gnt_i ( mem_gnt_i [NumMemPorts/2-1:0] ), + .mem_addr_o ( mem_addr_o [NumMemPorts/2-1:0] ), + .mem_wdata_o ( mem_wdata_o [NumMemPorts/2-1:0] ), + .mem_strb_o ( mem_strb_o [NumMemPorts/2-1:0] ), + .mem_atop_o ( mem_atop_o [NumMemPorts/2-1:0] ), + .mem_we_o ( mem_we_o [NumMemPorts/2-1:0] ), + .mem_rvalid_i ( mem_rvalid_i [NumMemPorts/2-1:0] ), + .mem_rdata_i ( mem_rdata_i [NumMemPorts/2-1:0] ) + ); + + axi_to_mem #( + .axi_req_t ( axi_req_t ), + .axi_resp_t ( axi_resp_t ), + .AddrWidth ( AddrWidth ), + .DataWidth ( AxiDataWidth ), + .IdWidth ( IdWidth ), + .NumBanks ( NumMemPorts/2 ), + .BufDepth ( BufDepth ), + .HideStrb ( HideStrb ), + .OutFifoDepth ( OutFifoDepth ) + ) i_axi_to_mem_write ( + .clk_i, + .rst_ni, + .busy_o ( write_busy ), + .axi_req_i ( axi_write_req ), + .axi_resp_o ( axi_write_resp ), + .mem_req_o ( mem_req_o [NumMemPorts-1:NumMemPorts/2] ), + .mem_gnt_i ( mem_gnt_i [NumMemPorts-1:NumMemPorts/2] ), + .mem_addr_o ( mem_addr_o [NumMemPorts-1:NumMemPorts/2] ), + .mem_wdata_o ( mem_wdata_o [NumMemPorts-1:NumMemPorts/2] ), + .mem_strb_o ( mem_strb_o [NumMemPorts-1:NumMemPorts/2] ), + .mem_atop_o ( mem_atop_o [NumMemPorts-1:NumMemPorts/2] ), + .mem_we_o ( mem_we_o [NumMemPorts-1:NumMemPorts/2] ), + .mem_rvalid_i ( mem_rvalid_i [NumMemPorts-1:NumMemPorts/2] ), + .mem_rdata_i ( mem_rdata_i [NumMemPorts-1:NumMemPorts/2] ) + ); + +endmodule + +`include "axi/typedef.svh" +/// AXI4+ATOP interface wrapper for `axi_to_mem_split` +module axi_to_mem_split_intf #( + /// AXI4+ATOP ID width + parameter int unsigned AXI_ID_WIDTH = 32'b0, + /// AXI4+ATOP address width + parameter int unsigned AXI_ADDR_WIDTH = 32'b0, + /// AXI4+ATOP data width + parameter int unsigned AXI_DATA_WIDTH = 32'b0, + /// AXI4+ATOP user width + parameter int unsigned AXI_USER_WIDTH = 32'b0, + /// Memory data width, must evenly divide `DataWidth`. + parameter int unsigned MEM_DATA_WIDTH = 32'b0, + /// See `axi_to_mem`, parameter `BufDepth`. + parameter int unsigned BUF_DEPTH = 0, + /// Hide write requests if the strb == '0 + parameter bit HIDE_STRB = 1'b0, + /// Depth of output fifo/fall_through_register. Increase for asymmetric backpressure (contention) on banks. + parameter int unsigned OUT_FIFO_DEPTH = 32'd1, + /// Dependent parameters, do not override. Number of memory ports. + parameter int unsigned NUM_MEM_PORTS = 2*AXI_DATA_WIDTH/MEM_DATA_WIDTH, + /// Dependent parameter, do not override. See `axi_to_mem`, parameter `addr_t`. + parameter type addr_t = logic [AXI_ADDR_WIDTH-1:0], + /// Dependent parameter, do not override. See `axi_to_mem`, parameter `mem_data_t`. + parameter type mem_data_t = logic [MEM_DATA_WIDTH-1:0], + /// Dependent parameter, do not override. See `axi_to_mem`, parameter `mem_strb_t`. + parameter type mem_strb_t = logic [MEM_DATA_WIDTH/8-1:0] +) ( + /// Clock input. + input logic clk_i, + /// Asynchronous reset, active low. + input logic rst_ni, + /// See `axi_to_mem_split`, port `busy_o`. + output logic busy_o, + /// AXI4+ATOP slave interface port. + AXI_BUS.Slave axi_bus, + /// See `axi_to_mem_split`, port `mem_req_o`. + output logic [NUM_MEM_PORTS-1:0] mem_req_o, + /// See `axi_to_mem_split`, port `mem_gnt_i`. + input logic [NUM_MEM_PORTS-1:0] mem_gnt_i, + /// See `axi_to_mem_split`, port `mem_addr_o`. + output addr_t [NUM_MEM_PORTS-1:0] mem_addr_o, + /// See `axi_to_mem_split`, port `mem_wdata_o`. + output mem_data_t [NUM_MEM_PORTS-1:0] mem_wdata_o, + /// See `axi_to_mem_split`, port `mem_strb_o`. + output mem_strb_t [NUM_MEM_PORTS-1:0] mem_strb_o, + /// See `axi_to_mem_split`, port `mem_atop_o`. + output axi_pkg::atop_t [NUM_MEM_PORTS-1:0] mem_atop_o, + /// See `axi_to_mem_split`, port `mem_we_o`. + output logic [NUM_MEM_PORTS-1:0] mem_we_o, + /// See `axi_to_mem_split`, port `mem_rvalid_i`. + input logic [NUM_MEM_PORTS-1:0] mem_rvalid_i, + /// See `axi_to_mem_split`, port `mem_rdata_i`. + input mem_data_t [NUM_MEM_PORTS-1:0] mem_rdata_i +); + + typedef logic [AXI_ID_WIDTH-1:0] id_t; + typedef logic [AXI_DATA_WIDTH-1:0] data_t; + typedef logic [AXI_DATA_WIDTH/8-1:0] strb_t; + typedef logic [AXI_USER_WIDTH-1:0] user_t; + `AXI_TYPEDEF_ALL(axi, addr_t, id_t, data_t, strb_t, user_t) + + axi_req_t axi_req; + axi_resp_t axi_resp; + `AXI_ASSIGN_TO_REQ(axi_req, axi_bus) + `AXI_ASSIGN_FROM_RESP(axi_bus, axi_resp) + + axi_to_mem_split #( + .axi_req_t ( axi_req_t ), + .axi_resp_t ( axi_resp_t ), + .AxiDataWidth ( AXI_DATA_WIDTH ), + .AddrWidth ( AXI_ADDR_WIDTH ), + .IdWidth ( AXI_ID_WIDTH ), + .MemDataWidth ( MEM_DATA_WIDTH ), // must divide `AxiDataWidth` without remainder + .BufDepth ( BUF_DEPTH ), + .HideStrb ( HIDE_STRB ), + .OutFifoDepth ( OUT_FIFO_DEPTH ) + ) i_axi_to_mem_split ( + .clk_i, + .rst_ni, + .busy_o, + .axi_req_i (axi_req), + .axi_resp_o (axi_resp), + .mem_req_o, + .mem_gnt_i, + .mem_addr_o, + .mem_wdata_o, + .mem_strb_o, + .mem_atop_o, + .mem_we_o, + .mem_rvalid_i, + .mem_rdata_i + ); + +endmodule diff --git a/src/axi_xbar.sv b/src/axi_xbar.sv index 98831547b..43a7c9fe3 100644 --- a/src/axi_xbar.sv +++ b/src/axi_xbar.sv @@ -13,50 +13,94 @@ // - Andreas Kurth // - Florian Zaruba -// axi_xbar: Fully-connected AXI4+ATOP crossbar with an arbitrary number of slave and master ports. -// See `doc/axi_xbar.md` for the documentation, including the definition of parameters and ports. +/// axi_xbar: Fully-connected AXI4+ATOP crossbar with an arbitrary number of slave and master ports. +/// See `doc/axi_xbar.md` for the documentation, including the definition of parameters and ports. module axi_xbar import cf_math_pkg::idx_width; #( + /// Configuration struct for the crossbar see `axi_pkg` for fields and definitions. parameter axi_pkg::xbar_cfg_t Cfg = '0, + /// Enable atomic operations support. parameter bit ATOPs = 1'b1, + /// Connectivity matrix parameter bit [Cfg.NoSlvPorts-1:0][Cfg.NoMstPorts-1:0] Connectivity = '1, + /// AXI4+ATOP AW channel struct type for the slave ports. parameter type slv_aw_chan_t = logic, + /// AXI4+ATOP AW channel struct type for the master ports. parameter type mst_aw_chan_t = logic, + /// AXI4+ATOP W channel struct type for all ports. parameter type w_chan_t = logic, + /// AXI4+ATOP B channel struct type for the slave ports. parameter type slv_b_chan_t = logic, + /// AXI4+ATOP B channel struct type for the master ports. parameter type mst_b_chan_t = logic, + /// AXI4+ATOP AR channel struct type for the slave ports. parameter type slv_ar_chan_t = logic, + /// AXI4+ATOP AR channel struct type for the master ports. parameter type mst_ar_chan_t = logic, + /// AXI4+ATOP R channel struct type for the slave ports. parameter type slv_r_chan_t = logic, + /// AXI4+ATOP R channel struct type for the master ports. parameter type mst_r_chan_t = logic, + /// AXI4+ATOP request struct type for the slave ports. parameter type slv_req_t = logic, + /// AXI4+ATOP response struct type for the slave ports. parameter type slv_resp_t = logic, + /// AXI4+ATOP request struct type for the master ports. parameter type mst_req_t = logic, + /// AXI4+ATOP response struct type for the master ports parameter type mst_resp_t = logic, + /// Address rule type for the address decoders from `common_cells:addr_decode`. + /// Example types are provided in `axi_pkg`. + /// Required struct fields: + /// ``` + /// typedef struct packed { + /// int unsigned idx; + /// axi_addr_t start_addr; + /// axi_addr_t end_addr; + /// } rule_t; + /// ``` parameter type rule_t = axi_pkg::xbar_rule_64_t `ifdef VCS , localparam int unsigned MstPortsIdxWidth = (Cfg.NoMstPorts == 32'd1) ? 32'd1 : unsigned'($clog2(Cfg.NoMstPorts)) `endif ) ( + /// Clock, positive edge triggered. input logic clk_i, + /// Asynchronous reset, active low. input logic rst_ni, + /// Testmode enable, active high. input logic test_i, + /// AXI4+ATOP requests to the slave ports. input slv_req_t [Cfg.NoSlvPorts-1:0] slv_ports_req_i, + /// AXI4+ATOP responses of the slave ports. output slv_resp_t [Cfg.NoSlvPorts-1:0] slv_ports_resp_o, + /// AXI4+ATOP requests of the master ports. output mst_req_t [Cfg.NoMstPorts-1:0] mst_ports_req_o, + /// AXI4+ATOP responses to the master ports. input mst_resp_t [Cfg.NoMstPorts-1:0] mst_ports_resp_i, + /// Address map array input for the crossbar. This map is global for the whole module. + /// It is used for routing the transactions to the respective master ports. + /// Each master port can have multiple different rules. input rule_t [Cfg.NoAddrRules-1:0] addr_map_i, + /// Enable default master port. input logic [Cfg.NoSlvPorts-1:0] en_default_mst_port_i, `ifdef VCS + /// Enables a default master port for each slave port. When this is enabled unmapped + /// transactions get issued at the master port given by `default_mst_port_i`. + /// When not used, tie to `'0`. input logic [Cfg.NoSlvPorts-1:0][MstPortsIdxWidth-1:0] default_mst_port_i `else + /// Enables a default master port for each slave port. When this is enabled unmapped + /// transactions get issued at the master port given by `default_mst_port_i`. + /// When not used, tie to `'0`. input logic [Cfg.NoSlvPorts-1:0][idx_width(Cfg.NoMstPorts)-1:0] default_mst_port_i `endif ); - typedef logic [Cfg.AxiAddrWidth-1:0] addr_t; + // Address tpye for inidvidual address signals + typedef logic [Cfg.AxiAddrWidth-1:0] addr_t; // to account for the decoding error slave `ifdef VCS localparam int unsigned MstPortsIdxWidthOne = @@ -164,7 +208,6 @@ import cf_math_pkg::idx_width; .MaxTrans ( Cfg.MaxMstTrans ), .AxiLookBits ( Cfg.AxiIdUsedSlvPorts ), .UniqueIds ( Cfg.UniqueIds ), - .FallThrough ( Cfg.FallThrough ), .SpillAw ( Cfg.LatencyMode[9] ), .SpillW ( Cfg.LatencyMode[8] ), .SpillB ( Cfg.LatencyMode[7] ), @@ -205,8 +248,23 @@ import cf_math_pkg::idx_width; for (genvar i = 0; i < Cfg.NoSlvPorts; i++) begin : gen_xbar_slv_cross for (genvar j = 0; j < Cfg.NoMstPorts; j++) begin : gen_xbar_mst_cross if (Connectivity[i][j]) begin : gen_connection - assign mst_reqs[j][i] = slv_reqs[i][j]; - assign slv_resps[i][j] = mst_resps[j][i]; + axi_multicut #( + .NoCuts ( Cfg.PipelineStages ), + .aw_chan_t ( slv_aw_chan_t ), + .w_chan_t ( w_chan_t ), + .b_chan_t ( slv_b_chan_t ), + .ar_chan_t ( slv_ar_chan_t ), + .r_chan_t ( slv_r_chan_t ), + .axi_req_t ( slv_req_t ), + .axi_resp_t ( slv_resp_t ) + ) i_axi_multicut_xbar_pipeline ( + .clk_i, + .rst_ni, + .slv_req_i ( slv_reqs[i][j] ), + .slv_resp_o ( slv_resps[i][j] ), + .mst_req_o ( mst_reqs[j][i] ), + .mst_resp_i ( mst_resps[j][i] ) + ); end else begin : gen_no_connection assign mst_reqs[j][i] = '0; diff --git a/src/axi_xp.sv b/src/axi_xp.sv new file mode 100644 index 000000000..5ccbfb9ff --- /dev/null +++ b/src/axi_xp.sv @@ -0,0 +1,256 @@ +// Copyright (c) 2020 ETH Zurich, University of Bologna +// +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the "License"); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. +// +// Authors: +// - Tim Fischer +// - Andreas Kurth +// - Vikram Jain + +`include "axi/typedef.svh" + +/// AXI Crosspoint (XP) with homomorphous slave and master ports. +module axi_xp #( + // Atomic operations settings + parameter bit ATOPs = 1'b1, + // xbar configuration + parameter axi_pkg::xbar_cfg_t Cfg = '0, + /// Number of slave ports. + parameter int unsigned NumSlvPorts = 32'd0, + /// Number of master ports. + parameter int unsigned NumMstPorts = 32'd0, + /// Connectivity from a slave port to the master ports. A `1'b1` in `Connectivity[i][j]` means + /// that slave port `i` is connected to master port `j`. By default, all slave ports are + /// connected to all master ports. + parameter bit [NumSlvPorts-1:0][NumMstPorts-1:0] Connectivity = '1, + /// Address width of all ports. + parameter int unsigned AxiAddrWidth = 32'd0, + /// Data width of all ports. + parameter int unsigned AxiDataWidth = 32'd0, + /// ID width of all ports. + parameter int unsigned AxiIdWidth = 32'd0, + /// User signal width of all ports. + parameter int unsigned AxiUserWidth = 32'd0, + /// Maximum number of different IDs that can be in flight at each slave port. Reads and writes + /// are counted separately (except for ATOPs, which count as both read and write). + /// + /// It is legal for upstream to have transactions with more unique IDs than the maximum given by + /// this parameter in flight, but a transaction exceeding the maximum will be stalled until all + /// transactions of another ID complete. + parameter int unsigned AxiSlvPortMaxUniqIds = 32'd0, + /// Maximum number of in-flight transactions with the same ID at the slave port. + /// + /// This parameter is only relevant if `AxiSlvPortMaxUniqIds <= 2**AxiMstPortIdWidth`. In that + /// case, this parameter is passed to [`axi_id_remap` as `AxiMaxTxnsPerId` + /// parameter](module.axi_id_remap#parameter.AxiMaxTxnsPerId). + parameter int unsigned AxiSlvPortMaxTxnsPerId = 32'd0, + /// Maximum number of in-flight transactions at the slave port. Reads and writes are counted + /// separately (except for ATOPs, which count as both read and write). + /// + /// This parameter is only relevant if `AxiSlvPortMaxUniqIds > 2**AxiMstPortIdWidth`. In that + /// case, this parameter is passed to + /// [`axi_id_serialize`](module.axi_id_serialize#parameter.AxiSlvPortMaxTxns). + parameter int unsigned AxiSlvPortMaxTxns = 32'd0, + /// Maximum number of different IDs that can be in flight at the master port. Reads and writes + /// are counted separately (except for ATOPs, which count as both read and write). + /// + /// This parameter is only relevant if `AxiSlvPortMaxUniqIds > 2**AxiMstPortIdWidth`. In that + /// case, this parameter is passed to + /// [`axi_id_serialize`](module.axi_id_serialize#parameter.AxiMstPortMaxUniqIds). + parameter int unsigned AxiMstPortMaxUniqIds = 32'd0, + /// Maximum number of in-flight transactions with the same ID at the master port. + /// + /// This parameter is only relevant if `AxiSlvPortMaxUniqIds > 2**AxiMstPortIdWidth`. In that + /// case, this parameter is passed to + /// [`axi_id_serialize`](module.axi_id_serialize#parameter.AxiMstPortMaxTxnsPerId). + parameter int unsigned AxiMstPortMaxTxnsPerId = 32'd0, + /// Number of rules in the address map. + parameter int unsigned NumAddrRules = 32'd0, + /// Request struct type of the AXI4+ATOP + parameter type axi_req_t = logic, + /// Response struct type of the AXI4+ATOP + parameter type axi_resp_t = logic, + /// Rule type (see documentation of `axi_xbar` for details). + parameter type rule_t = axi_pkg::xbar_rule_64_t +) ( + /// Rising-edge clock of all ports + input logic clk_i, + /// Asynchronous reset, active low + input logic rst_ni, + /// Test mode enable + input logic test_en_i, + /// Slave ports request + input axi_req_t [NumSlvPorts-1:0] slv_req_i, + /// Slave ports response + output axi_resp_t [NumSlvPorts-1:0] slv_resp_o, + /// Master ports request + output axi_req_t [NumMstPorts-1:0] mst_req_o, + /// Master ports response + input axi_resp_t [NumMstPorts-1:0] mst_resp_i, + /// Address map for transferring transactions from slave to master ports + input rule_t [NumAddrRules-1:0] addr_map_i +); + + // The master port of the Xbar has a different ID width than the slave ports. + parameter int unsigned AxiXbarIdWidth = AxiIdWidth + $clog2(NumSlvPorts); + typedef logic [AxiAddrWidth-1:0] addr_t; + typedef logic [AxiDataWidth-1:0] data_t; + typedef logic [AxiIdWidth-1:0] id_t; + typedef logic [AxiXbarIdWidth-1:0] xbar_id_t; + typedef logic [AxiDataWidth/8-1:0] strb_t; + typedef logic [AxiUserWidth-1:0] user_t; + + + `AXI_TYPEDEF_ALL(xp, addr_t, id_t, data_t, strb_t, user_t) + `AXI_TYPEDEF_ALL(xbar, addr_t, xbar_id_t, data_t, strb_t, user_t) + + xbar_req_t [NumMstPorts-1:0] xbar_req; + xbar_resp_t [NumMstPorts-1:0] xbar_resp; + + axi_xbar #( + .Cfg ( Cfg ), + .ATOPs ( ATOPs ), + .Connectivity ( Connectivity ), + .slv_aw_chan_t ( xp_aw_chan_t ), + .mst_aw_chan_t ( xbar_aw_chan_t ), + .w_chan_t ( xp_w_chan_t ), + .slv_b_chan_t ( xp_b_chan_t ), + .mst_b_chan_t ( xbar_b_chan_t ), + .slv_ar_chan_t ( xp_ar_chan_t ), + .mst_ar_chan_t ( xbar_ar_chan_t ), + .slv_r_chan_t ( xp_r_chan_t ), + .mst_r_chan_t ( xbar_r_chan_t ), + .slv_req_t ( axi_req_t ), + .slv_resp_t ( axi_resp_t ), + .mst_req_t ( xbar_req_t ), + .mst_resp_t ( xbar_resp_t ), + .rule_t ( rule_t ) + ) i_xbar ( + .clk_i, + .rst_ni, + .test_i ( test_en_i ), + .slv_ports_req_i ( slv_req_i ), + .slv_ports_resp_o ( slv_resp_o ), + .mst_ports_req_o ( xbar_req ), + .mst_ports_resp_i ( xbar_resp ), + .addr_map_i, + .en_default_mst_port_i ( '0 ), + .default_mst_port_i ( '0 ) + ); + + for (genvar i = 0; i < NumMstPorts; i++) begin : gen_remap + axi_id_remap #( + .AxiSlvPortIdWidth ( AxiXbarIdWidth ), + .AxiSlvPortMaxUniqIds ( AxiSlvPortMaxUniqIds ), + .AxiMaxTxnsPerId ( AxiSlvPortMaxTxnsPerId ), + .AxiMstPortIdWidth ( AxiIdWidth ), + .slv_req_t ( xbar_req_t ), + .slv_resp_t ( xbar_resp_t ), + .mst_req_t ( axi_req_t ), + .mst_resp_t ( axi_resp_t ) + ) i_axi_id_remap ( + .clk_i, + .rst_ni, + .slv_req_i ( xbar_req[i] ), + .slv_resp_o ( xbar_resp[i] ), + .mst_req_o ( mst_req_o[i] ), + .mst_resp_i ( mst_resp_i[i] ) + ); + end + +endmodule + +`include "axi/assign.svh" +`include "axi/typedef.svh" + +module axi_xp_intf +import cf_math_pkg::idx_width; +#( + parameter bit ATOPs = 1'b1, + parameter axi_pkg::xbar_cfg_t Cfg = '0, + parameter int unsigned NumSlvPorts = 32'd0, + parameter int unsigned NumMstPorts = 32'd0, + parameter bit [NumSlvPorts-1:0][NumMstPorts-1:0] Connectivity = '1, + parameter int unsigned AxiAddrWidth = 32'd0, + parameter int unsigned AxiDataWidth = 32'd0, + parameter int unsigned AxiIdWidth = 32'd0, + parameter int unsigned AxiUserWidth = 32'd0, + parameter int unsigned AxiSlvPortMaxUniqIds = 32'd0, + parameter int unsigned AxiSlvPortMaxTxnsPerId = 32'd0, + parameter int unsigned AxiSlvPortMaxTxns = 32'd0, + parameter int unsigned AxiMstPortMaxUniqIds = 32'd0, + parameter int unsigned AxiMstPortMaxTxnsPerId = 32'd0, + parameter int unsigned NumAddrRules = 32'd0, + parameter type rule_t = axi_pkg::xbar_rule_64_t +) ( + input logic clk_i, + input logic rst_ni, + input logic test_en_i, + AXI_BUS.Slave slv_ports [NumSlvPorts-1:0], + AXI_BUS.Master mst_ports [NumMstPorts-1:0], + input rule_t [NumAddrRules-1:0] addr_map_i +); + + // localparam int unsigned AxiIdWidthMstPorts = AxiIdWidth + $clog2(NoSlvPorts); + + typedef logic [AxiIdWidth -1:0] id_t; + typedef logic [AxiAddrWidth -1:0] addr_t; + typedef logic [AxiDataWidth -1:0] data_t; + typedef logic [AxiDataWidth/8 -1:0] strb_t; + typedef logic [AxiUserWidth -1:0] user_t; + + `AXI_TYPEDEF_ALL(axi, addr_t, id_t, data_t, strb_t, user_t) + + axi_req_t [NumMstPorts-1:0] mst_reqs; + axi_resp_t [NumMstPorts-1:0] mst_resps; + axi_req_t [NumSlvPorts-1:0] slv_reqs; + axi_resp_t [NumSlvPorts-1:0] slv_resps; + + for (genvar i = 0; i < NumMstPorts; i++) begin : gen_assign_mst + `AXI_ASSIGN_FROM_REQ(mst_ports[i], mst_reqs[i]) + `AXI_ASSIGN_TO_RESP(mst_resps[i], mst_ports[i]) + end + + for (genvar i = 0; i < NumSlvPorts; i++) begin : gen_assign_slv + `AXI_ASSIGN_TO_REQ(slv_reqs[i], slv_ports[i]) + `AXI_ASSIGN_FROM_RESP(slv_ports[i], slv_resps[i]) + end + + axi_xp #( + .ATOPs ( ATOPs ), + .Cfg ( Cfg ), + .NumSlvPorts ( NumSlvPorts ), + .NumMstPorts ( NumMstPorts ), + .Connectivity ( Connectivity ), + .AxiAddrWidth ( AxiAddrWidth ), + .AxiDataWidth ( AxiDataWidth ), + .AxiIdWidth ( AxiIdWidth ), + .AxiUserWidth ( AxiUserWidth ), + .AxiSlvPortMaxUniqIds ( AxiSlvPortMaxUniqIds ), + .AxiSlvPortMaxTxnsPerId ( AxiSlvPortMaxTxnsPerId ), + .AxiSlvPortMaxTxns ( AxiSlvPortMaxTxns ), + .AxiMstPortMaxUniqIds ( AxiMstPortMaxUniqIds ), + .AxiMstPortMaxTxnsPerId ( AxiMstPortMaxTxnsPerId ), + .NumAddrRules ( NumAddrRules ), + .axi_req_t ( axi_req_t ), + .axi_resp_t ( axi_resp_t ), + .rule_t ( rule_t ) + ) i_xp ( + .clk_i, + .rst_ni, + .test_en_i, + .slv_req_i (slv_reqs ), + .slv_resp_o (slv_resps), + .mst_req_o (mst_reqs ), + .mst_resp_i (mst_resps), + .addr_map_i + ); + +endmodule diff --git a/src_files.yml b/src_files.yml index 752fd90df..16cc1ce00 100644 --- a/src_files.yml +++ b/src_files.yml @@ -16,19 +16,24 @@ axi: # Level 2 - src/axi_atop_filter.sv - src/axi_burst_splitter.sv + - src/axi_bus_compare.sv - src/axi_cdc_dst.sv - src/axi_cdc_src.sv - src/axi_cut.sv - src/axi_delayer.sv - - src/axi_demux.sv + - src/axi_demux_simple.sv - src/axi_dw_downsizer.sv - src/axi_dw_upsizer.sv + - src/axi_fifo.sv - src/axi_id_remap.sv - src/axi_id_prepend.sv - src/axi_isolate.sv - src/axi_join.sv - src/axi_lite_demux.sv + - src/axi_lite_dw_converter.sv + - src/axi_lite_from_mem.sv - src/axi_lite_join.sv + - src/axi_lite_lfsr.sv - src/axi_lite_mailbox.sv - src/axi_lite_mux.sv - src/axi_lite_regs.sv @@ -36,21 +41,37 @@ axi: - src/axi_lite_to_axi.sv - src/axi_modify_address.sv - src/axi_mux.sv + - src/axi_rw_join.sv + - src/axi_rw_split.sv - src/axi_serializer.sv + - src/axi_slave_compare.sv + - src/axi_throttle.sv + - src/axi_to_detailed_mem.sv # Level 3 - src/axi_cdc.sv + - src/axi_demux.sv - src/axi_err_slv.sv - src/axi_dw_converter.sv + - src/axi_from_mem.sv - src/axi_id_serialize.sv + - src/axi_lfsr.sv - src/axi_multicut.sv - src/axi_to_axi_lite.sv + - src/axi_to_mem.sv # Level 4 - src/axi_iw_converter.sv - src/axi_lite_xbar.sv - src/axi_xbar.sv + - src/axi_to_mem_banked.sv + - src/axi_to_mem_interleaved.sv + - src/axi_to_mem_split.sv + # Level 5 + - src/axi_xp.sv axi_sim: files: + - src/axi_chan_compare.sv + - src/axi_dumper.sv - src/axi_sim_mem.sv - src/axi_test.sv flags: diff --git a/test/axi_synth_bench.sv b/test/axi_synth_bench.sv index 6b95cfe0c..334846abc 100644 --- a/test/axi_synth_bench.sv +++ b/test/axi_synth_bench.sv @@ -12,6 +12,7 @@ // - Wolfgang Roenninger // - Andreas Kurth // - Fabian Schuiki +// - Michael Rogenmoser /// A synthesis test bench which instantiates various adapter variants. module axi_synth_bench ( @@ -174,6 +175,36 @@ module axi_synth_bench ( end end + // AXI4+ATOP on chip memory slave banked + for (genvar i = 0; i < 5; i++) begin : gen_axi_to_mem_banked_data + for (genvar j = 0; j < 4; j++) begin : gen_axi_to_mem_banked_bank_num + for (genvar k = 0; k < 2; k++) begin : gen_axi_to_mem_banked_bank_addr + localparam int unsigned DATA_WIDTH_AXI[5] = {32'd32, 32'd64, 32'd128, 32'd256, 32'd512}; + localparam int unsigned NUM_BANKS[4] = {32'd2, 32'd4, 32'd6, 32'd8}; + localparam int unsigned ADDR_WIDTH_BANKS[2] = {32'd5, 32'd11}; + + synth_axi_to_mem_banked #( + .AxiDataWidth ( DATA_WIDTH_AXI[i] ), + .BankNum ( NUM_BANKS[j] ), + .BankAddrWidth ( ADDR_WIDTH_BANKS[k] ) + ) i_axi_to_mem_banked (.*); + end + end + end + + // AXI4-Lite DW converter + for (genvar i = 0; i < 3; i++) begin + for (genvar j = 0; j < 3; j++) begin + localparam int unsigned SLV_DW[3] = {32, 64, 128}; + localparam int unsigned MST_DW[3] = {16, 32, 64}; + + synth_axi_lite_dw_converter #( + .AXI_SLV_PORT_DATA_WIDTH (SLV_DW[i]), + .AXI_MST_PORT_DATA_WIDTH (MST_DW[j]) + ) i_axi_lite_dw_converter (.*); + end + end + endmodule @@ -659,3 +690,106 @@ module synth_axi_iw_converter # ( .mst ( downstream ) ); endmodule + + +module synth_axi_to_mem_banked #( + parameter int unsigned AxiDataWidth = 32'd0, + parameter int unsigned BankNum = 32'd0, + parameter int unsigned BankAddrWidth = 32'd0 +) ( + input logic clk_i, + input logic rst_ni +); + localparam int unsigned AxiIdWidth = 32'd10; + localparam int unsigned AxiAddrWidth = 32'd64; + localparam int unsigned AxiStrbWidth = AxiDataWidth / 32'd8; + localparam int unsigned AxiUserWidth = 32'd8; + localparam int unsigned BankDataWidth = 32'd2 * AxiDataWidth / BankNum; + localparam int unsigned BankStrbWidth = BankDataWidth / 32'd8; + localparam int unsigned BankLatency = 32'd1; + + typedef logic [BankAddrWidth-1:0] mem_addr_t; + typedef logic [BankDataWidth-1:0] mem_data_t; + typedef logic [BankStrbWidth-1:0] mem_strb_t; + + AXI_BUS #( + .AXI_ADDR_WIDTH ( AxiIdWidth ), + .AXI_DATA_WIDTH ( AxiAddrWidth ), + .AXI_ID_WIDTH ( AxiDataWidth ), + .AXI_USER_WIDTH ( AxiUserWidth ) + ) axi (); + + // Misc signals + logic test; + logic [1:0] axi_to_mem_busy; + // Signals for mem macros + logic [BankNum-1:0] mem_req; + logic [BankNum-1:0] mem_gnt; + mem_addr_t [BankNum-1:0] mem_addr; + logic [BankNum-1:0] mem_we; + mem_data_t [BankNum-1:0] mem_wdata; + mem_strb_t [BankNum-1:0] mem_be; + axi_pkg::atop_t [BankNum-1:0] mem_atop; + mem_data_t [BankNum-1:0] mem_rdata; + + + axi_to_mem_banked_intf #( + .AXI_ID_WIDTH ( AxiIdWidth ), + .AXI_ADDR_WIDTH ( AxiAddrWidth ), + .AXI_DATA_WIDTH ( AxiDataWidth ), + .AXI_USER_WIDTH ( AxiUserWidth ), + .MEM_NUM_BANKS ( BankNum ), + .MEM_ADDR_WIDTH ( BankAddrWidth ), + .MEM_DATA_WIDTH ( BankDataWidth ), + .MEM_LATENCY ( BankLatency ) + ) i_axi_to_mem_banked_intf ( + .clk_i, + .rst_ni, + .test_i ( test ), + .slv ( axi ), + .mem_req_o ( mem_req ), + .mem_gnt_i ( mem_gnt ), + .mem_add_o ( mem_addr ), + .mem_we_o ( mem_we ), + .mem_wdata_o ( mem_wdata ), + .mem_be_o ( mem_be ), + .mem_atop_o ( mem_atop ), + .mem_rdata_i ( mem_rdata ), + .axi_to_mem_busy_o ( axi_to_mem_busy ) + ); + +endmodule + + +module synth_axi_lite_dw_converter #( + parameter int unsigned AXI_SLV_PORT_DATA_WIDTH = 32'd0, + parameter int unsigned AXI_MST_PORT_DATA_WIDTH = 32'd0 +) ( + input logic clk_i, + input logic rst_ni +); + + localparam int unsigned AXI_ADDR_WIDTH = 32'd64; + + AXI_LITE #( + .AXI_ADDR_WIDTH ( AXI_ADDR_WIDTH ), + .AXI_DATA_WIDTH ( AXI_SLV_PORT_DATA_WIDTH ) + ) slv_intf (); + + AXI_LITE #( + .AXI_ADDR_WIDTH ( AXI_ADDR_WIDTH ), + .AXI_DATA_WIDTH ( AXI_MST_PORT_DATA_WIDTH ) + ) mst_intf (); + + axi_lite_dw_converter_intf #( + .AXI_ADDR_WIDTH ( AXI_ADDR_WIDTH ), + .AXI_SLV_PORT_DATA_WIDTH ( AXI_SLV_PORT_DATA_WIDTH ), + .AXI_MST_PORT_DATA_WIDTH ( AXI_MST_PORT_DATA_WIDTH ) + ) i_axi_lite_dw_converter_intf ( + .clk_i, + .rst_ni, + .slv ( slv_intf ), + .mst ( mst_intf ) + ); + +endmodule diff --git a/test/tb_axi_bus_compare.sv b/test/tb_axi_bus_compare.sv new file mode 100644 index 000000000..c8cd1c91c --- /dev/null +++ b/test/tb_axi_bus_compare.sv @@ -0,0 +1,310 @@ +// Copyright (c) 2020 ETH Zurich and University of Bologna +// SPDX-License-Identifier: SHL-0.51 +// +// Authors: +// - Thomas Benz + +`include "axi/assign.svh" +`include "axi/typedef.svh" + +/// Testbench for `axi_bus_compare` +module tb_axi_bus_compare #( + // TB Parameters + parameter time TbTclk = 10ns, + // Module Parameters + parameter int unsigned TbAddrWidth = 32'd64, + parameter int unsigned TbDataWidth = 32'd128, + parameter int unsigned TbIdWidth = 32'd6, + parameter int unsigned TbUserWidth = 32'd2, + parameter bit TbWarnUninitialized = 1'b0, + parameter time TbApplDelay = 2ns, + parameter time TbAcqDelay = 8ns +); + + logic clk, + rst_n; + clk_rst_gen #( + .ClkPeriod (TbTclk), + .RstClkCycles (5) + ) i_clk_rst_gen ( + .clk_o (clk), + .rst_no (rst_n) + ); + + localparam int unsigned StrbWidth = TbDataWidth / 8; + typedef logic [TbAddrWidth-1:0] addr_t; + typedef logic [TbDataWidth-1:0] data_t; + typedef logic [TbIdWidth-1:0] id_t; + typedef logic [StrbWidth-1:0] strb_t; + typedef logic [TbUserWidth-1:0] user_t; + + AXI_BUS #( + .AXI_ADDR_WIDTH (TbAddrWidth), + .AXI_DATA_WIDTH (TbDataWidth), + .AXI_ID_WIDTH (TbIdWidth), + .AXI_USER_WIDTH (TbUserWidth) + ) axi (); + + AXI_BUS_DV #( + .AXI_ADDR_WIDTH (TbAddrWidth), + .AXI_DATA_WIDTH (TbDataWidth), + .AXI_ID_WIDTH (TbIdWidth), + .AXI_USER_WIDTH (TbUserWidth) + ) axi_dv (clk); + typedef axi_test::axi_driver #( + .AW(TbAddrWidth), .DW(TbDataWidth), .IW(TbIdWidth), .UW(TbUserWidth), + .TA(1ns), .TT(6ns) + ) drv_t; + drv_t drv = new(axi_dv); + + `AXI_ASSIGN (axi, axi_dv) + + `AXI_TYPEDEF_ALL(axi, addr_t, id_t, data_t, strb_t, user_t) + + axi_req_t axi_req, axi_req_a_in, axi_req_b_in, axi_req_a_out, axi_req_b_out, axi_req_b_dly; + axi_resp_t axi_rsp, axi_rsp_a_in, axi_rsp_b_in, axi_rsp_a_out, axi_rsp_b_out, axi_rsp_b_dly; + + `AXI_ASSIGN_TO_REQ(axi_req, axi) + `AXI_ASSIGN_FROM_RESP(axi, axi_rsp) + + logic aw_valid_a, aw_ready_a; + logic w_valid_a, w_ready_a; + logic ar_valid_a, ar_ready_a; + + logic aw_valid_b, aw_ready_b; + logic w_valid_b, w_ready_b; + logic ar_valid_b, ar_ready_b; + + logic aw_ready; + logic w_ready; + logic ar_ready; + + stream_fork #(.N_OUP(32'd2)) i_stream_fork_aw ( + .clk_i ( clk ), + .rst_ni ( rst_n ), + .valid_i ( axi_req.aw_valid ), + .ready_o ( aw_ready ), + .valid_o ( { aw_valid_a, aw_valid_b } ), + .ready_i ( { aw_ready_a, aw_ready_b } ) + ); + + stream_fork #(.N_OUP(32'd2)) i_stream_fork_ar ( + .clk_i ( clk ), + .rst_ni ( rst_n ), + .valid_i ( axi_req.ar_valid ), + .ready_o ( ar_ready ), + .valid_o ( { ar_valid_a, ar_valid_b } ), + .ready_i ( { ar_ready_a, ar_ready_b } ) + ); + + stream_fork #(.N_OUP(32'd2)) i_stream_fork_w ( + .clk_i ( clk ), + .rst_ni ( rst_n ), + .valid_i ( axi_req.w_valid ), + .ready_o ( w_ready ), + .valid_o ( { w_valid_a, w_valid_b } ), + .ready_i ( { w_ready_a, w_ready_b } ) + ); + + // assemble buses + always_comb begin + // request + `AXI_SET_REQ_STRUCT(axi_req_a_in, axi_req) + `AXI_SET_REQ_STRUCT(axi_req_b_in, axi_req) + // overwrite valids in requests + axi_req_a_in.aw_valid = aw_valid_a; + axi_req_a_in.ar_valid = ar_valid_a; + axi_req_a_in.w_valid = w_valid_a; + axi_req_b_in.aw_valid = aw_valid_b; + axi_req_b_in.ar_valid = ar_valid_b; + axi_req_b_in.w_valid = w_valid_b; + // get readies + aw_ready_a = axi_rsp_a_in.aw_ready; + ar_ready_a = axi_rsp_a_in.ar_ready; + w_ready_a = axi_rsp_a_in.w_ready; + aw_ready_b = axi_rsp_b_in.aw_ready; + ar_ready_b = axi_rsp_b_in.ar_ready; + w_ready_b = axi_rsp_b_in.w_ready; + // response + `AXI_SET_RESP_STRUCT(axi_rsp, axi_rsp_a_in) + // overwrite readies + axi_rsp.aw_ready = aw_ready; + axi_rsp.w_ready = w_ready; + axi_rsp.ar_ready = ar_ready; + // b interface is not used + axi_req_b_in.r_ready = '1; + axi_req_b_in.b_ready = '1; + end + + axi_bus_compare #( + .AxiIdWidth ( TbIdWidth ), + .FifoDepth ( 32'd16 ), + .axi_aw_chan_t ( axi_aw_chan_t ), + .axi_w_chan_t ( axi_w_chan_t ), + .axi_b_chan_t ( axi_b_chan_t ), + .axi_ar_chan_t ( axi_ar_chan_t ), + .axi_r_chan_t ( axi_r_chan_t ), + .axi_req_t ( axi_req_t ), + .axi_rsp_t ( axi_resp_t ) + ) i_axi_bus_compare ( + .clk_i ( clk ), + .rst_ni ( rst_n ), + .testmode_i ( 1'b0 ), + .axi_a_req_i ( axi_req_a_in ), + .axi_a_rsp_o ( axi_rsp_a_in ), + .axi_a_req_o ( axi_req_a_out ), + .axi_a_rsp_i ( axi_rsp_a_out ), + .axi_b_req_i ( axi_req_b_in ), + .axi_b_rsp_o ( axi_rsp_b_in ), + .axi_b_req_o ( axi_req_b_out ), + .axi_b_rsp_i ( axi_rsp_b_out ), + .aw_mismatch_o ( ), + .w_mismatch_o ( ), + .b_mismatch_o ( ), + .ar_mismatch_o ( ), + .r_mismatch_o ( ), + .mismatch_o ( ), + .busy_o ( ) + ); + + axi_sim_mem #( + .AddrWidth(TbAddrWidth), + .DataWidth(TbDataWidth), + .IdWidth (TbIdWidth), + .UserWidth(TbUserWidth), + .axi_req_t(axi_req_t), + .axi_rsp_t(axi_resp_t), + .ApplDelay(TbApplDelay), + .AcqDelay (TbAcqDelay) + ) i_axi_sim_mem_a ( + .clk_i ( clk ), + .rst_ni ( rst_n ), + .axi_req_i ( axi_req_a_out ), + .axi_rsp_o ( axi_rsp_a_out ), + .mon_w_valid_o ( ), + .mon_w_addr_o ( ), + .mon_w_data_o ( ), + .mon_w_id_o ( ), + .mon_w_user_o ( ), + .mon_w_beat_count_o( ), + .mon_w_last_o ( ), + .mon_r_valid_o ( ), + .mon_r_addr_o ( ), + .mon_r_data_o ( ), + .mon_r_id_o ( ), + .mon_r_user_o ( ), + .mon_r_beat_count_o( ), + .mon_r_last_o ( ) + ); + + axi_multicut #( + .NoCuts (8), + .aw_chan_t (axi_aw_chan_t), + .w_chan_t (axi_w_chan_t), + .b_chan_t (axi_b_chan_t), + .ar_chan_t (axi_ar_chan_t), + .r_chan_t (axi_r_chan_t), + .axi_req_t (axi_req_t), + .axi_resp_t(axi_resp_t) + ) i_axi_multicut ( + .clk_i ( clk ), + .rst_ni ( rst_n ), + .slv_req_i ( axi_req_b_out ), + .slv_resp_o ( axi_rsp_b_out ), + .mst_req_o ( axi_req_b_dly ), + .mst_resp_i ( axi_rsp_b_dly ) + ); + + axi_sim_mem #( + .AddrWidth(TbAddrWidth), + .DataWidth(TbDataWidth), + .IdWidth (TbIdWidth), + .UserWidth(TbUserWidth), + .axi_req_t(axi_req_t), + .axi_rsp_t(axi_resp_t), + .ApplDelay(TbApplDelay), + .AcqDelay (TbAcqDelay) + ) i_axi_sim_mem_b ( + .clk_i ( clk ), + .rst_ni ( rst_n ), + .axi_req_i ( axi_req_b_dly ), + .axi_rsp_o ( axi_rsp_b_dly ), + .mon_w_valid_o ( ), + .mon_w_addr_o ( ), + .mon_w_data_o ( ), + .mon_w_id_o ( ), + .mon_w_user_o ( ), + .mon_w_beat_count_o( ), + .mon_w_last_o ( ), + .mon_r_valid_o ( ), + .mon_r_addr_o ( ), + .mon_r_data_o ( ), + .mon_r_id_o ( ), + .mon_r_user_o ( ), + .mon_r_beat_count_o( ), + .mon_r_last_o ( ) + ); + + + // Simply read and write a random memory region. + initial begin + automatic logic rand_success; + automatic data_t exp_data[$]; + automatic drv_t::ax_beat_t aw_beat = new, ar_beat = new; + automatic drv_t::w_beat_t w_beat = new; + automatic drv_t::b_beat_t b_beat; + automatic drv_t::r_beat_t r_beat; + drv.reset_master(); + wait (rst_n); + // AW +`ifdef XSIM + // std::randomize(aw_beat) may behave differently to aw_beat.randomize() wrt. limited ranges + // Keeping alternate implementation for XSIM only + rand_success = std::randomize(aw_beat); assert (rand_success); +`else + rand_success = aw_beat.randomize(); assert (rand_success); +`endif + aw_beat.ax_addr >>= $clog2(StrbWidth); // align address with data width + aw_beat.ax_addr <<= $clog2(StrbWidth); + aw_beat.ax_len = $urandom(); + aw_beat.ax_size = $clog2(StrbWidth); + aw_beat.ax_burst = axi_pkg::BURST_INCR; + drv.send_aw(aw_beat); + // W beats + for (int unsigned i = 0; i <= aw_beat.ax_len; i++) begin +`ifdef XSIM + // std::randomize(w_beat) may behave differently to w_beat.randomize() wrt. limited ranges + // Keeping alternate implementation for XSIM only + rand_success = std::randomize(w_beat); assert (rand_success); +`else + rand_success = w_beat.randomize(); assert (rand_success); +`endif + w_beat.w_strb = '1; + if (i == aw_beat.ax_len) begin + w_beat.w_last = 1'b1; + end + drv.send_w(w_beat); + exp_data.push_back(w_beat.w_data); + end + // B + drv.recv_b(b_beat); + assert(b_beat.b_resp == axi_pkg::RESP_OKAY); + // AR + ar_beat.ax_addr = aw_beat.ax_addr; + ar_beat.ax_len = aw_beat.ax_len; + ar_beat.ax_size = aw_beat.ax_size; + ar_beat.ax_burst = aw_beat.ax_burst; + drv.send_ar(ar_beat); + // R beats + for (int unsigned i = 0; i <= ar_beat.ax_len; i++) begin + automatic data_t exp = exp_data.pop_front(); + drv.recv_r(r_beat); + assert(r_beat.r_data == exp) else + $error("Received 0x%h != expected 0x%h!", r_beat.r_data, exp); + end + // Done. + #(TbTclk); + $finish(); + end + +endmodule diff --git a/test/tb_axi_delayer.sv b/test/tb_axi_delayer.sv index 64b4670da..5b9c15dc4 100644 --- a/test/tb_axi_delayer.sv +++ b/test/tb_axi_delayer.sv @@ -83,7 +83,13 @@ module tb_axi_delayer; @(posedge clk); repeat (200) begin @(posedge clk); +`ifdef XSIM + // std::randomize(ax_beat) may behave differently to ax_beat.randomize() wrt. limited ranges + // Keeping alternate implementation for XSIM only + rand_success = std::randomize(ax_beat); assert(rand_success); +`else rand_success = ax_beat.randomize(); assert(rand_success); +`endif axi_master_drv.send_aw(ax_beat); w_beat.w_data = 'hcafebabe; axi_master_drv.send_w(w_beat); diff --git a/test/tb_axi_dw_downsizer.sv b/test/tb_axi_dw_downsizer.sv index ff076ba8c..1edd3b16f 100644 --- a/test/tb_axi_dw_downsizer.sv +++ b/test/tb_axi_dw_downsizer.sv @@ -10,6 +10,7 @@ // // Authors: // - Matheus Cavalcante +// - Andreas Kurth `include "axi/assign.svh" diff --git a/test/tb_axi_dw_upsizer.sv b/test/tb_axi_dw_upsizer.sv index 26f11f048..8a8e03392 100644 --- a/test/tb_axi_dw_upsizer.sv +++ b/test/tb_axi_dw_upsizer.sv @@ -10,6 +10,7 @@ // // Authors: // - Matheus Cavalcante +// - Andreas Kurth `include "axi/assign.svh" diff --git a/test/tb_axi_fifo.sv b/test/tb_axi_fifo.sv new file mode 100644 index 000000000..2e79044e0 --- /dev/null +++ b/test/tb_axi_fifo.sv @@ -0,0 +1,199 @@ +// Copyright (c) 2019 ETH Zurich and University of Bologna. +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the "License"); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. +// +// Authors: +// - Noah Huetter + +`include "axi/typedef.svh" +`include "axi/assign.svh" + +module tb_axi_fifo #( + parameter int unsigned Depth = 16, + parameter int unsigned FallThrough = 0, + parameter int unsigned NoWrites = 200, // How many writes per master + parameter int unsigned NoReads = 200 // How many reads per master +); + // Random Master Atomics + localparam int unsigned MaxAW = 30; + localparam int unsigned MaxAR = 30; + localparam bit EnAtop = 1'b1; + // timing parameters + localparam time CyclTime = 10ns; + localparam time ApplTime = 2ns; + localparam time TestTime = 8ns; + // AXI configuration + localparam int unsigned AxiIdWidth = 4; + localparam int unsigned AxiAddrWidth = 32; // Axi Address Width + localparam int unsigned AxiDataWidth = 64; // Axi Data Width + localparam int unsigned AxiUserWidth = 5; + // Sim print config, how many transactions + localparam int unsigned PrintTnx = 100; + + typedef axi_test::axi_rand_master#( + // AXI interface parameters + .AW (AxiAddrWidth), + .DW (AxiDataWidth), + .IW (AxiIdWidth), + .UW (AxiUserWidth), + // Stimuli application and test time + .TA (ApplTime), + .TT (TestTime), + // Maximum number of read and write transactions in flight + .MAX_READ_TXNS (MaxAR), + .MAX_WRITE_TXNS(MaxAW), + .AXI_ATOPS (EnAtop) + ) axi_rand_master_t; + typedef axi_test::axi_rand_slave#( + // AXI interface parameters + .AW(AxiAddrWidth), + .DW(AxiDataWidth), + .IW(AxiIdWidth), + .UW(AxiUserWidth), + // Stimuli application and test time + .TA(ApplTime), + .TT(TestTime) + ) axi_rand_slave_t; + + // ------------- + // DUT signals + // ------------- + logic clk; + logic rst_n; + logic end_of_sim; + + // interfaces + AXI_BUS #( + .AXI_ADDR_WIDTH(AxiAddrWidth), + .AXI_DATA_WIDTH(AxiDataWidth), + .AXI_ID_WIDTH (AxiIdWidth), + .AXI_USER_WIDTH(AxiUserWidth) + ) + master (), slave (); + AXI_BUS_DV #( + .AXI_ADDR_WIDTH(AxiAddrWidth), + .AXI_DATA_WIDTH(AxiDataWidth), + .AXI_ID_WIDTH (AxiIdWidth), + .AXI_USER_WIDTH(AxiUserWidth) + ) + master_dv (clk), slave_dv (clk); + + `AXI_ASSIGN(master, master_dv) + `AXI_ASSIGN(slave_dv, slave) + + //----------------------------------- + // Clock generator + //----------------------------------- + clk_rst_gen #( + .ClkPeriod (CyclTime), + .RstClkCycles(5) + ) i_clk_gen ( + .clk_o (clk), + .rst_no(rst_n) + ); + + //----------------------------------- + // DUT + //----------------------------------- + axi_fifo_intf #( + .DEPTH (Depth), // number of FiFo slots + .FALL_THROUGH(FallThrough), // FiFos in fall through mode + .ID_WIDTH (AxiIdWidth), // AXI ID width + .ADDR_WIDTH (AxiAddrWidth), // AXI address width + .DATA_WIDTH (AxiDataWidth), // AXI data width + .USER_WIDTH (AxiUserWidth) // AXI user width + ) i_dut ( + .clk_i (clk), // clock + .rst_ni(rst_n), // asynchronous reset active low + .test_i(1'b0), + .slv (master), // slave port + .mst (slave) // master port + ); + + initial begin : proc_axi_master + automatic axi_rand_master_t axi_rand_master = new(master_dv); + end_of_sim <= 1'b0; + axi_rand_master.add_memory_region(32'h0000_0000, 32'h1000_0000, axi_pkg::DEVICE_NONBUFFERABLE); + axi_rand_master.add_memory_region(32'h2000_0000, 32'h3000_0000, axi_pkg::WTHRU_NOALLOCATE); + axi_rand_master.add_memory_region(32'h4000_0000, 32'h5000_0000, axi_pkg::WBACK_RWALLOCATE); + axi_rand_master.reset(); + @(posedge rst_n); + axi_rand_master.run(NoReads, NoWrites); + end_of_sim <= 1'b1; + repeat (10000) @(posedge clk); + $stop(); + end + + initial begin : proc_axi_slave + automatic axi_rand_slave_t axi_rand_slave = new(slave_dv); + axi_rand_slave.reset(); + @(posedge rst_n); + axi_rand_slave.run(); + end + + initial begin : proc_sim_progress + automatic int unsigned aw = 0; + automatic int unsigned ar = 0; + automatic bit aw_printed = 1'b0; + automatic bit ar_printed = 1'b0; + + @(posedge rst_n); + + forever begin + @(posedge clk); + #TestTime; + if (master.aw_valid && master.aw_ready) begin + aw++; + end + if (master.ar_valid && master.ar_ready) begin + ar++; + end + + if ((aw % PrintTnx == 0) && !aw_printed) begin + $display("%t> Transmit AW %d of %d.", $time(), aw, NoWrites); + aw_printed = 1'b1; + end + if ((ar % PrintTnx == 0) && !ar_printed) begin + $display("%t> Transmit AR %d of %d.", $time(), ar, NoReads); + ar_printed = 1'b1; + end + + if (aw % PrintTnx == 1) begin + aw_printed = 1'b0; + end + if (ar % PrintTnx == 1) begin + ar_printed = 1'b0; + end + + if (end_of_sim) begin + $info("All transactions completed."); + break; + end + end + end + + + default disable iff (!rst_n); aw_unstable : + assert property (@(posedge clk) (slave.aw_valid && !slave.aw_ready) |=> $stable(slave.aw_addr)) + else $fatal(1, "AW is unstable."); + w_unstable : + assert property (@(posedge clk) (slave.w_valid && !slave.w_ready) |=> $stable(slave.w_data)) + else $fatal(1, "W is unstable."); + b_unstable : + assert property (@(posedge clk) (master.b_valid && !master.b_ready) |=> $stable(master.b_resp)) + else $fatal(1, "B is unstable."); + ar_unstable : + assert property (@(posedge clk) (slave.ar_valid && !slave.ar_ready) |=> $stable(slave.ar_addr)) + else $fatal(1, "AR is unstable."); + r_unstable : + assert property (@(posedge clk) (master.r_valid && !master.r_ready) |=> $stable(master.r_data)) + else $fatal(1, "R is unstable."); + + +endmodule diff --git a/test/tb_axi_fifo.wave.do b/test/tb_axi_fifo.wave.do new file mode 100644 index 000000000..bdf1d9e20 --- /dev/null +++ b/test/tb_axi_fifo.wave.do @@ -0,0 +1,201 @@ +onerror {resume} +quietly WaveActivateNextPane {} 0 +add wave -noupdate -label Clock /tb_axi_fifo/i_dut/clk_i +add wave -noupdate -label Reset /tb_axi_fifo/i_dut/rst_ni +add wave -noupdate -divider {Slave Ports} +add wave -noupdate -expand -group {Master AW} /tb_axi_fifo/master/aw_id +add wave -noupdate -expand -group {Master AW} /tb_axi_fifo/master/aw_addr +add wave -noupdate -expand -group {Master AW} /tb_axi_fifo/master/aw_len +add wave -noupdate -expand -group {Master AW} /tb_axi_fifo/master/aw_size +add wave -noupdate -expand -group {Master AW} /tb_axi_fifo/master/aw_burst +add wave -noupdate -expand -group {Master AW} /tb_axi_fifo/master/aw_lock +add wave -noupdate -expand -group {Master AW} /tb_axi_fifo/master/aw_cache +add wave -noupdate -expand -group {Master AW} /tb_axi_fifo/master/aw_prot +add wave -noupdate -expand -group {Master AW} /tb_axi_fifo/master/aw_qos +add wave -noupdate -expand -group {Master AW} /tb_axi_fifo/master/aw_region +add wave -noupdate -expand -group {Master AW} /tb_axi_fifo/master/aw_atop +add wave -noupdate -expand -group {Master AW} /tb_axi_fifo/master/aw_user +add wave -noupdate -expand -group {Master AW} /tb_axi_fifo/master/aw_valid +add wave -noupdate -expand -group {Master AW} /tb_axi_fifo/master/aw_ready +add wave -noupdate -group {Master W} /tb_axi_fifo/master/w_data +add wave -noupdate -group {Master W} /tb_axi_fifo/master/w_strb +add wave -noupdate -group {Master W} /tb_axi_fifo/master/w_last +add wave -noupdate -group {Master W} /tb_axi_fifo/master/w_user +add wave -noupdate -group {Master W} /tb_axi_fifo/master/w_valid +add wave -noupdate -group {Master W} /tb_axi_fifo/master/w_ready +add wave -noupdate -group {Master B} /tb_axi_fifo/master/b_id +add wave -noupdate -group {Master B} /tb_axi_fifo/master/b_resp +add wave -noupdate -group {Master B} /tb_axi_fifo/master/b_user +add wave -noupdate -group {Master B} /tb_axi_fifo/master/b_valid +add wave -noupdate -group {Master B} /tb_axi_fifo/master/b_ready +add wave -noupdate -expand -group {Master AR} /tb_axi_fifo/master/ar_id +add wave -noupdate -expand -group {Master AR} /tb_axi_fifo/master/ar_addr +add wave -noupdate -expand -group {Master AR} /tb_axi_fifo/master/ar_len +add wave -noupdate -expand -group {Master AR} /tb_axi_fifo/master/ar_size +add wave -noupdate -expand -group {Master AR} /tb_axi_fifo/master/ar_burst +add wave -noupdate -expand -group {Master AR} /tb_axi_fifo/master/ar_lock +add wave -noupdate -expand -group {Master AR} /tb_axi_fifo/master/ar_cache +add wave -noupdate -expand -group {Master AR} /tb_axi_fifo/master/ar_prot +add wave -noupdate -expand -group {Master AR} /tb_axi_fifo/master/ar_qos +add wave -noupdate -expand -group {Master AR} /tb_axi_fifo/master/ar_region +add wave -noupdate -expand -group {Master AR} /tb_axi_fifo/master/ar_user +add wave -noupdate -expand -group {Master AR} /tb_axi_fifo/master/ar_valid +add wave -noupdate -expand -group {Master AR} /tb_axi_fifo/master/ar_ready +add wave -noupdate -group {Master R} /tb_axi_fifo/master/r_id +add wave -noupdate -group {Master R} /tb_axi_fifo/master/r_data +add wave -noupdate -group {Master R} /tb_axi_fifo/master/r_resp +add wave -noupdate -group {Master R} /tb_axi_fifo/master/r_last +add wave -noupdate -group {Master R} /tb_axi_fifo/master/r_user +add wave -noupdate -group {Master R} /tb_axi_fifo/master/r_valid +add wave -noupdate -group {Master R} /tb_axi_fifo/master/r_ready +add wave -noupdate -divider {Master Ports} +add wave -noupdate -expand -group {Slave AW} /tb_axi_fifo/slave/aw_id +add wave -noupdate -expand -group {Slave AW} /tb_axi_fifo/slave/aw_addr +add wave -noupdate -expand -group {Slave AW} /tb_axi_fifo/slave/aw_len +add wave -noupdate -expand -group {Slave AW} /tb_axi_fifo/slave/aw_size +add wave -noupdate -expand -group {Slave AW} /tb_axi_fifo/slave/aw_burst +add wave -noupdate -expand -group {Slave AW} /tb_axi_fifo/slave/aw_lock +add wave -noupdate -expand -group {Slave AW} /tb_axi_fifo/slave/aw_cache +add wave -noupdate -expand -group {Slave AW} /tb_axi_fifo/slave/aw_prot +add wave -noupdate -expand -group {Slave AW} /tb_axi_fifo/slave/aw_qos +add wave -noupdate -expand -group {Slave AW} /tb_axi_fifo/slave/aw_region +add wave -noupdate -expand -group {Slave AW} /tb_axi_fifo/slave/aw_atop +add wave -noupdate -expand -group {Slave AW} /tb_axi_fifo/slave/aw_user +add wave -noupdate -expand -group {Slave AW} /tb_axi_fifo/slave/aw_valid +add wave -noupdate -expand -group {Slave AW} /tb_axi_fifo/slave/aw_ready +add wave -noupdate -group {Slave W} /tb_axi_fifo/slave/w_data +add wave -noupdate -group {Slave W} /tb_axi_fifo/slave/w_strb +add wave -noupdate -group {Slave W} /tb_axi_fifo/slave/w_last +add wave -noupdate -group {Slave W} /tb_axi_fifo/slave/w_user +add wave -noupdate -group {Slave W} /tb_axi_fifo/slave/w_valid +add wave -noupdate -group {Slave W} /tb_axi_fifo/slave/w_ready +add wave -noupdate -group {Slave B} /tb_axi_fifo/slave/b_id +add wave -noupdate -group {Slave B} /tb_axi_fifo/slave/b_resp +add wave -noupdate -group {Slave B} /tb_axi_fifo/slave/b_user +add wave -noupdate -group {Slave B} /tb_axi_fifo/slave/b_valid +add wave -noupdate -group {Slave B} /tb_axi_fifo/slave/b_ready +add wave -noupdate -expand -group {Slave AR} /tb_axi_fifo/slave/ar_id +add wave -noupdate -expand -group {Slave AR} /tb_axi_fifo/slave/ar_addr +add wave -noupdate -expand -group {Slave AR} /tb_axi_fifo/slave/ar_len +add wave -noupdate -expand -group {Slave AR} /tb_axi_fifo/slave/ar_size +add wave -noupdate -expand -group {Slave AR} /tb_axi_fifo/slave/ar_burst +add wave -noupdate -expand -group {Slave AR} /tb_axi_fifo/slave/ar_lock +add wave -noupdate -expand -group {Slave AR} /tb_axi_fifo/slave/ar_cache +add wave -noupdate -expand -group {Slave AR} /tb_axi_fifo/slave/ar_prot +add wave -noupdate -expand -group {Slave AR} /tb_axi_fifo/slave/ar_qos +add wave -noupdate -expand -group {Slave AR} /tb_axi_fifo/slave/ar_region +add wave -noupdate -expand -group {Slave AR} /tb_axi_fifo/slave/ar_user +add wave -noupdate -expand -group {Slave AR} /tb_axi_fifo/slave/ar_valid +add wave -noupdate -expand -group {Slave AR} /tb_axi_fifo/slave/ar_ready +add wave -noupdate -group {Slave R} /tb_axi_fifo/slave/r_id +add wave -noupdate -group {Slave R} /tb_axi_fifo/slave/r_data +add wave -noupdate -group {Slave R} /tb_axi_fifo/slave/r_resp +add wave -noupdate -group {Slave R} /tb_axi_fifo/slave/r_last +add wave -noupdate -group {Slave R} /tb_axi_fifo/slave/r_user +add wave -noupdate -group {Slave R} /tb_axi_fifo/slave/r_valid +add wave -noupdate -group {Slave R} /tb_axi_fifo/slave/r_ready +add wave -noupdate -divider Custom +add wave -noupdate -expand -group {AW FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_aw_fifo/clk_i +add wave -noupdate -expand -group {AW FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_aw_fifo/rst_ni +add wave -noupdate -expand -group {AW FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_aw_fifo/flush_i +add wave -noupdate -expand -group {AW FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_aw_fifo/testmode_i +add wave -noupdate -expand -group {AW FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_aw_fifo/full_o +add wave -noupdate -expand -group {AW FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_aw_fifo/empty_o +add wave -noupdate -expand -group {AW FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_aw_fifo/usage_o +add wave -noupdate -expand -group {AW FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_aw_fifo/data_i +add wave -noupdate -expand -group {AW FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_aw_fifo/push_i +add wave -noupdate -expand -group {AW FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_aw_fifo/data_o +add wave -noupdate -expand -group {AW FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_aw_fifo/pop_i +add wave -noupdate -expand -group {W FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_w_fifo/clk_i +add wave -noupdate -expand -group {W FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_w_fifo/rst_ni +add wave -noupdate -expand -group {W FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_w_fifo/flush_i +add wave -noupdate -expand -group {W FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_w_fifo/testmode_i +add wave -noupdate -expand -group {W FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_w_fifo/full_o +add wave -noupdate -expand -group {W FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_w_fifo/empty_o +add wave -noupdate -expand -group {W FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_w_fifo/usage_o +add wave -noupdate -expand -group {W FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_w_fifo/data_i +add wave -noupdate -expand -group {W FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_w_fifo/push_i +add wave -noupdate -expand -group {W FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_w_fifo/data_o +add wave -noupdate -expand -group {W FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_w_fifo/pop_i +add wave -noupdate -expand -group {B FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_b_fifo/clk_i +add wave -noupdate -expand -group {B FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_b_fifo/rst_ni +add wave -noupdate -expand -group {B FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_b_fifo/flush_i +add wave -noupdate -expand -group {B FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_b_fifo/testmode_i +add wave -noupdate -expand -group {B FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_b_fifo/full_o +add wave -noupdate -expand -group {B FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_b_fifo/empty_o +add wave -noupdate -expand -group {B FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_b_fifo/usage_o +add wave -noupdate -expand -group {B FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_b_fifo/data_i +add wave -noupdate -expand -group {B FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_b_fifo/push_i +add wave -noupdate -expand -group {B FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_b_fifo/data_o +add wave -noupdate -expand -group {B FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_b_fifo/pop_i +add wave -noupdate -expand -group {Ar FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_ar_fifo/clk_i +add wave -noupdate -expand -group {Ar FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_ar_fifo/rst_ni +add wave -noupdate -expand -group {Ar FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_ar_fifo/flush_i +add wave -noupdate -expand -group {Ar FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_ar_fifo/testmode_i +add wave -noupdate -expand -group {Ar FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_ar_fifo/full_o +add wave -noupdate -expand -group {Ar FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_ar_fifo/empty_o +add wave -noupdate -expand -group {Ar FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_ar_fifo/usage_o +add wave -noupdate -expand -group {Ar FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_ar_fifo/data_i +add wave -noupdate -expand -group {Ar FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_ar_fifo/push_i +add wave -noupdate -expand -group {Ar FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_ar_fifo/data_o +add wave -noupdate -expand -group {Ar FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_ar_fifo/pop_i +add wave -noupdate -expand -group {R FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_r_fifo/clk_i +add wave -noupdate -expand -group {R FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_r_fifo/rst_ni +add wave -noupdate -expand -group {R FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_r_fifo/flush_i +add wave -noupdate -expand -group {R FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_r_fifo/testmode_i +add wave -noupdate -expand -group {R FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_r_fifo/full_o +add wave -noupdate -expand -group {R FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_r_fifo/empty_o +add wave -noupdate -expand -group {R FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_r_fifo/usage_o +add wave -noupdate -expand -group {R FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_r_fifo/data_i +add wave -noupdate -expand -group {R FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_r_fifo/push_i +add wave -noupdate -expand -group {R FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_r_fifo/data_o +add wave -noupdate -expand -group {R FiFo} /tb_axi_fifo/i_dut/i_axi_fifo/gen_axi_fifo/i_r_fifo/pop_i +add wave -noupdate -divider {DUT Ports} +add wave -noupdate -expand -group {DUT slv AW} /tb_axi_fifo/i_dut/i_axi_fifo/slv_req_i.aw_valid +add wave -noupdate -expand -group {DUT slv AW} /tb_axi_fifo/i_dut/i_axi_fifo/slv_resp_o.aw_ready +add wave -noupdate -expand -group {DUT slv AW} /tb_axi_fifo/i_dut/i_axi_fifo/slv_req_i.aw +add wave -noupdate -expand -group {DUT slv W} /tb_axi_fifo/i_dut/i_axi_fifo/slv_req_i.w +add wave -noupdate -expand -group {DUT slv W} /tb_axi_fifo/i_dut/i_axi_fifo/slv_req_i.w_valid +add wave -noupdate -expand -group {DUT slv W} /tb_axi_fifo/i_dut/i_axi_fifo/slv_resp_o.w_ready +add wave -noupdate -expand -group {DUT slv B} /tb_axi_fifo/i_dut/i_axi_fifo/slv_resp_o.b_valid +add wave -noupdate -expand -group {DUT slv B} /tb_axi_fifo/i_dut/i_axi_fifo/slv_req_i.b_ready +add wave -noupdate -expand -group {DUT slv B} /tb_axi_fifo/i_dut/i_axi_fifo/slv_resp_o.b +add wave -noupdate -expand -group {DUT slv AR} /tb_axi_fifo/i_dut/i_axi_fifo/slv_req_i.ar_valid +add wave -noupdate -expand -group {DUT slv AR} /tb_axi_fifo/i_dut/i_axi_fifo/slv_resp_o.ar_ready +add wave -noupdate -expand -group {DUT slv AR} /tb_axi_fifo/i_dut/i_axi_fifo/slv_req_i.ar +add wave -noupdate -expand -group {DUT slv R} /tb_axi_fifo/i_dut/i_axi_fifo/slv_resp_o.r_valid +add wave -noupdate -expand -group {DUT slv R} /tb_axi_fifo/i_dut/i_axi_fifo/slv_req_i.r_ready +add wave -noupdate -expand -group {DUT slv R} /tb_axi_fifo/i_dut/i_axi_fifo/slv_resp_o.r +add wave -noupdate -expand -group {DUT mst AW} /tb_axi_fifo/i_dut/i_axi_fifo/mst_req_o.aw_valid +add wave -noupdate -expand -group {DUT mst AW} /tb_axi_fifo/i_dut/i_axi_fifo/mst_resp_i.aw_ready +add wave -noupdate -expand -group {DUT mst AW} /tb_axi_fifo/i_dut/i_axi_fifo/mst_req_o.aw +add wave -noupdate -expand -group {DUT mst W} /tb_axi_fifo/i_dut/i_axi_fifo/mst_req_o.w +add wave -noupdate -expand -group {DUT mst W} /tb_axi_fifo/i_dut/i_axi_fifo/mst_req_o.w_valid +add wave -noupdate -expand -group {DUT mst W} /tb_axi_fifo/i_dut/i_axi_fifo/mst_resp_i.w_ready +add wave -noupdate -expand -group {DUT mst B} /tb_axi_fifo/i_dut/i_axi_fifo/mst_resp_i.b_valid +add wave -noupdate -expand -group {DUT mst B} /tb_axi_fifo/i_dut/i_axi_fifo/mst_req_o.b_ready +add wave -noupdate -expand -group {DUT mst B} /tb_axi_fifo/i_dut/i_axi_fifo/mst_resp_i.b +add wave -noupdate -expand -group {DUT mst AR} /tb_axi_fifo/i_dut/i_axi_fifo/mst_req_o.ar_valid +add wave -noupdate -expand -group {DUT mst AR} /tb_axi_fifo/i_dut/i_axi_fifo/mst_resp_i.ar_ready +add wave -noupdate -expand -group {DUT mst AR} /tb_axi_fifo/i_dut/i_axi_fifo/mst_req_o.ar +add wave -noupdate -expand -group {DUT mst R} /tb_axi_fifo/i_dut/i_axi_fifo/mst_resp_i.r_valid +add wave -noupdate -expand -group {DUT mst R} /tb_axi_fifo/i_dut/i_axi_fifo/mst_req_o.r_ready +add wave -noupdate -expand -group {DUT mst R} /tb_axi_fifo/i_dut/i_axi_fifo/mst_resp_i.r +TreeUpdate [SetDefaultTree] +WaveRestoreCursors {{Cursor 1} {70 ns} 0} +quietly wave cursor active 1 +configure wave -namecolwidth 197 +configure wave -valuecolwidth 100 +configure wave -justifyvalue left +configure wave -signalnamewidth 1 +configure wave -snapdistance 10 +configure wave -datasetprefix 0 +configure wave -rowmargin 4 +configure wave -childrowmargin 2 +configure wave -gridoffset 0 +configure wave -gridperiod 1 +configure wave -griddelta 40 +configure wave -timeline 0 +configure wave -timelineunits ns +update +WaveRestoreZoom {0 ns} {841 ns} diff --git a/test/tb_axi_iw_converter.sv b/test/tb_axi_iw_converter.sv index 1b2d5249b..0f2f7893c 100644 --- a/test/tb_axi_iw_converter.sv +++ b/test/tb_axi_iw_converter.sv @@ -8,9 +8,9 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. // -// Author: Andreas Kurth -// Florian Zaruba -// Wolfgang Roennigner +// Authors: +// - Andreas Kurth +// - Wolfgang Roenninger `include "axi/assign.svh" `include "axi/typedef.svh" diff --git a/test/tb_axi_lite_dw_converter.sv b/test/tb_axi_lite_dw_converter.sv new file mode 100644 index 000000000..e376cb1d6 --- /dev/null +++ b/test/tb_axi_lite_dw_converter.sv @@ -0,0 +1,452 @@ +// Copyright (c) 2020 ETH Zurich and University of Bologna. +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the "License"); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +// Authors: +// - Wolfgang Roenninger +// - Michael Rogenmoser + + +`include "axi/typedef.svh" +`include "axi/assign.svh" +/// Testbench for the AXI4-Lite data width conversion module. +module tb_axi_lite_dw_converter #( + /// Address width of the AXI4-Lite ports. + parameter int unsigned TbAxiAddrWidth = 32'd32, + /// Data Width of the slave port. + parameter int unsigned TbAxiDataWidthSlv = 32'd32, + /// Data Width of the master port. + parameter int unsigned TbAxiDataWidthMst = 32'd128, + /// Number of write transactions. + parameter int unsigned NumWrites = 32'd10000, + /// Number of read tranactions. + parameter int unsigned NumReads = 32'd10000 +); + // Random master no Transactions + // timing parameters + localparam time CyclTime = 10ns; + localparam time ApplTime = 2ns; + localparam time TestTime = 8ns; + // AXI configuration + localparam int unsigned TbAxiStrbWidthSlv = TbAxiDataWidthSlv / 32'd8; + localparam int unsigned TbAxiStrbWidthMst = TbAxiDataWidthMst / 32'd8; + // Type definitions + typedef logic [TbAxiAddrWidth-1:0] addr_t; + typedef logic [TbAxiDataWidthSlv-1:0] data_slv_t; + typedef logic [TbAxiStrbWidthSlv-1:0] strb_slv_t; + typedef logic [TbAxiDataWidthMst-1:0] data_mst_t; + typedef logic [TbAxiStrbWidthMst-1:0] strb_mst_t; + + + `AXI_LITE_TYPEDEF_AW_CHAN_T(aw_chan_lite_t, addr_t) + `AXI_LITE_TYPEDEF_W_CHAN_T(w_chan_lite_slv_t, data_slv_t, strb_slv_t) + `AXI_LITE_TYPEDEF_W_CHAN_T(w_chan_lite_mst_t, data_mst_t, strb_mst_t) + `AXI_LITE_TYPEDEF_B_CHAN_T(b_chan_lite_t) + + `AXI_LITE_TYPEDEF_AR_CHAN_T(ar_chan_lite_t, addr_t) + `AXI_LITE_TYPEDEF_R_CHAN_T(r_chan_lite_slv_t, data_slv_t) + `AXI_LITE_TYPEDEF_R_CHAN_T(r_chan_lite_mst_t, data_mst_t) + + + `AXI_LITE_TYPEDEF_REQ_T(req_lite_slv_t, aw_chan_lite_t, w_chan_lite_slv_t, ar_chan_lite_t) + `AXI_LITE_TYPEDEF_RESP_T(res_lite_slv_t, b_chan_lite_t, r_chan_lite_slv_t) + + `AXI_LITE_TYPEDEF_REQ_T(req_lite_mst_t, aw_chan_lite_t, w_chan_lite_mst_t, ar_chan_lite_t) + `AXI_LITE_TYPEDEF_RESP_T(res_lite_mst_t, b_chan_lite_t, r_chan_lite_mst_t) + + + typedef axi_test::axi_lite_rand_master #( + // AXI interface parameters + .AW ( TbAxiAddrWidth ), + .DW ( TbAxiDataWidthSlv ), + // Stimuli application and test time + .TA ( ApplTime ), + .TT ( TestTime ), + .MIN_ADDR ( 0 ), + .MAX_ADDR ( '1 ), + .MAX_READ_TXNS ( 100 ), + .MAX_WRITE_TXNS ( 100 ) + ) rand_lite_master_t; + typedef axi_test::axi_lite_rand_slave #( + // AXI interface parameters + .AW ( TbAxiAddrWidth ), + .DW ( TbAxiDataWidthMst ), + // Stimuli application and test time + .TA ( ApplTime ), + .TT ( TestTime ) + ) rand_lite_slave_t; + + // ------------- + // DUT signals + // ------------- + logic clk; + // DUT signals + logic rst_n; + logic end_of_sim; + + // master structs + req_lite_slv_t master_req; + res_lite_slv_t master_res; + + // slave structs + req_lite_mst_t slave_req; + res_lite_mst_t slave_res; + + // ------------------------------- + // AXI Interfaces + // ------------------------------- + AXI_LITE #( + .AXI_ADDR_WIDTH ( TbAxiAddrWidth ), + .AXI_DATA_WIDTH ( TbAxiDataWidthSlv ) + ) master (); + AXI_LITE_DV #( + .AXI_ADDR_WIDTH ( TbAxiAddrWidth ), + .AXI_DATA_WIDTH ( TbAxiDataWidthSlv ) + ) master_dv (clk); + `AXI_LITE_ASSIGN(master, master_dv) + `AXI_LITE_ASSIGN_TO_REQ(master_req, master) + `AXI_LITE_ASSIGN_TO_RESP(master_res, master) + + AXI_LITE #( + .AXI_ADDR_WIDTH ( TbAxiAddrWidth ), + .AXI_DATA_WIDTH ( TbAxiDataWidthMst ) + ) slave (); + AXI_LITE_DV #( + .AXI_ADDR_WIDTH ( TbAxiAddrWidth ), + .AXI_DATA_WIDTH ( TbAxiDataWidthMst ) + ) slave_dv (clk); + `AXI_LITE_ASSIGN(slave_dv, slave) + `AXI_LITE_ASSIGN_TO_REQ(slave_req, slave) + `AXI_LITE_ASSIGN_TO_RESP(slave_res, slave) + // ------------------------------- + // AXI Rand Masters and Slaves + // ------------------------------- + // Masters control simulation run time + initial begin : proc_generate_traffic + automatic rand_lite_master_t lite_axi_master = new ( master_dv, "MST_0"); + automatic data_slv_t data = '0; + automatic axi_pkg::resp_t resp = '0; + end_of_sim <= 1'b0; + lite_axi_master.reset(); + @(posedge rst_n); + lite_axi_master.write(32'h0000_1100, axi_pkg::prot_t'('0), data_slv_t'(64'hDEADBEEFDEADBEEF), + strb_slv_t'(8'hFF), resp); + lite_axi_master.read(32'h0000_e100, axi_pkg::prot_t'('0), data, resp); + lite_axi_master.run(NumReads, NumWrites); + end_of_sim <= 1'b1; + end + + initial begin : proc_recieve_traffic + automatic rand_lite_slave_t lite_axi_slave = new( slave_dv , "SLV_0"); + lite_axi_slave.reset(); + @(posedge rst_n); + lite_axi_slave.run(); + end + + // FIFOs for sampling the channels + // Slave Port + aw_chan_lite_t fifo_slv_aw[$]; + w_chan_lite_slv_t fifo_slv_w[$]; + b_chan_lite_t fifo_slv_b[$]; + ar_chan_lite_t fifo_slv_ar[$]; + r_chan_lite_slv_t fifo_slv_r[$]; + + aw_chan_lite_t fifo_mst_aw[$]; + w_chan_lite_mst_t fifo_mst_w[$]; + b_chan_lite_t fifo_mst_b[$]; + ar_chan_lite_t fifo_mst_ar[$]; + r_chan_lite_mst_t fifo_mst_r[$]; + // Sampling processes + initial begin : proc_sample + forever begin + @(posedge clk); + #TestTime; + if (rst_n) begin + if (master_req.aw_valid && master_res.aw_ready) begin + fifo_slv_aw.push_back(master_req.aw); + end + if (master_req.w_valid && master_res.w_ready) begin + fifo_slv_w.push_back(master_req.w); + end + if (master_res.b_valid && master_req.b_ready) begin + fifo_slv_b.push_back(master_res.b); + end + if (master_req.ar_valid && master_res.ar_ready) begin + fifo_slv_ar.push_back(master_req.ar); + end + if (master_res.r_valid && master_req.r_ready) begin + fifo_slv_r.push_back(master_res.r); + end + if (slave_req.aw_valid && slave_res.aw_ready) begin + fifo_mst_aw.push_back(slave_req.aw); + end + if (slave_req.w_valid && slave_res.w_ready) begin + fifo_mst_w.push_back(slave_req.w); + end + if (slave_res.b_valid && slave_req.b_ready) begin + fifo_mst_b.push_back(slave_res.b); + end + if (slave_req.ar_valid && slave_res.ar_ready) begin + fifo_mst_ar.push_back(slave_req.ar); + end + if (slave_res.r_valid && slave_req.r_ready) begin + fifo_mst_r.push_back(slave_res.r); + end + end + end + end + + if (TbAxiDataWidthMst < TbAxiDataWidthSlv) begin : gen_down_gm + localparam int unsigned DataDivFactor = TbAxiDataWidthSlv / TbAxiDataWidthMst; + localparam int unsigned AddrShift = $clog2(TbAxiDataWidthMst/8); + localparam int unsigned SelWidth = $clog2(DataDivFactor); + typedef logic [SelWidth-1:0] sel_t; + aw_chan_lite_t fifo_exp_aw[$]; + w_chan_lite_mst_t fifo_exp_w[$]; + ar_chan_lite_t fifo_exp_ar[$]; + + // Golden model for down conversion + initial begin : proc_write_exp_gen + automatic aw_chan_lite_t chan_aw, exp_aw; + automatic w_chan_lite_slv_t chan_w; + automatic w_chan_lite_mst_t exp_w; + automatic int unsigned num_vectors; // Number of expected transactions generated + automatic addr_t exp_addr; + automatic sel_t w_sel; + + forever begin + wait((fifo_slv_aw.size() > 0) && (fifo_slv_w.size() > 0)); + chan_aw = fifo_slv_aw.pop_front(); + chan_w = fifo_slv_w.pop_front(); + exp_addr = chan_aw.addr; + exp_addr = exp_addr >> (AddrShift + SelWidth); + for (int unsigned i = 0; i < DataDivFactor; i++) begin + // Generate expected AW vector + exp_aw = aw_chan_lite_t'{ + addr: ((exp_addr << SelWidth) + i) << AddrShift, + prot: chan_aw.prot + }; + fifo_exp_aw.push_back(exp_aw); + // Generate expected W vector + exp_w = w_chan_lite_mst_t'{ + data: chan_w.data[i*TbAxiDataWidthMst+:TbAxiDataWidthMst], + strb: chan_w.strb[i*TbAxiStrbWidthMst+:TbAxiStrbWidthMst] + }; + fifo_exp_w.push_back(exp_w); + end + end + end + + initial begin : proc_aw_exp_check + automatic aw_chan_lite_t chan_aw, exp_aw; + forever begin + wait((fifo_exp_aw.size() > 0) && (fifo_mst_aw.size() > 0)); + chan_aw = fifo_mst_aw.pop_front(); + exp_aw = fifo_exp_aw.pop_front(); + assert (chan_aw.addr == exp_aw.addr) else + $error("Master port> AW.addr is not expected: EXP: %h ACT:%h", + exp_aw.addr, chan_aw.addr); + assert (chan_aw.prot == exp_aw.prot) else + $error("Master port> AW.prot is not expected: EXP: %h ACT:%h", + exp_aw.prot, chan_aw.prot); + end + end + initial begin : proc_w_exp_check + automatic w_chan_lite_mst_t chan_w, exp_w; + forever begin + wait((fifo_exp_w.size() > 0) && (fifo_mst_w.size() > 0)); + chan_w = fifo_mst_w.pop_front(); + exp_w = fifo_exp_w.pop_front(); + assert (chan_w.data == exp_w.data) else + $error("Master port> W.data is not expected: EXP: %h ACT:%h", + exp_w.data, chan_w.data); + assert (chan_w.strb == exp_w.strb) else + $error("Master port> W.strb is not expected: EXP: %h ACT:%h", + exp_w.strb, chan_w.strb); + end + end + initial begin : proc_b_exp_check + automatic b_chan_lite_t exp_b, act_b; + forever begin + // wait until there are this many B responses in the sampling FIFO + wait(fifo_mst_b.size() >= DataDivFactor); + exp_b = axi_pkg::RESP_OKAY; + for (int unsigned i = 0; i < DataDivFactor; i++) begin + act_b = fifo_mst_b.pop_front(); + exp_b = exp_b | act_b; + end + // Do the check + wait(fifo_slv_b.size() > 0); + act_b = fifo_slv_b.pop_front(); + assert(exp_b.resp == act_b.resp) else + $error("Slave port> B.resp is not expected: EXP: %h ACT: %h", exp_b.resp, act_b.resp); + end + end + initial begin : proc_ar_exp_check + automatic ar_chan_lite_t chan_ar, exp_ar; + forever begin + wait((fifo_exp_ar.size() > 0) && (fifo_mst_ar.size() > 0)); + chan_ar = fifo_mst_ar.pop_front(); + exp_ar = fifo_exp_ar.pop_front(); + assert (chan_ar.addr == exp_ar.addr) else + $error("Master port> AR.addr is not expected: EXP: %h ACT:%h", + exp_ar.addr, chan_ar.addr); + assert (chan_ar.prot == exp_ar.prot) else + $error("Master port> AR.prot is not expected: EXP: %h ACT:%h", + exp_ar.prot, chan_ar.prot); + end + end + initial begin : proc_r_exp_check + automatic r_chan_lite_slv_t exp_r, act_r; + automatic r_chan_lite_mst_t mst_r; + forever begin + wait(fifo_slv_r.size() > 0); + act_r = fifo_slv_r.pop_front(); + exp_r = r_chan_lite_slv_t'{default: '0}; + // Build the expected R response from the master port fifo. + for (int unsigned i = 0; i < DataDivFactor; i++) begin + mst_r = fifo_mst_r.pop_front(); + exp_r.data[i*TbAxiDataWidthMst+:TbAxiDataWidthMst] = mst_r.data; + exp_r.resp = exp_r.resp | mst_r.resp; + end + assert (act_r.data == exp_r.data) else + $error("Slave port> R.data is not expected: EXP: %h ACT:%h", + exp_r.data, act_r.data); + assert (act_r.resp == exp_r.resp) else + $error("Slave port> R.resp is not expected: EXP: %h ACT:%h", + exp_r.resp, act_r.resp); + end + end + end else if (TbAxiDataWidthMst == TbAxiDataWidthSlv) begin + initial begin : proc_passthrough_check + forever begin + @(posedge clk); + #TestTime; + if (rst_n) begin + assert (master_req == slave_req); + assert (master_res == slave_res); + end + end + end + end else begin + // Upsizer + localparam int unsigned DataMultFactor = TbAxiDataWidthMst / TbAxiDataWidthSlv; + localparam int unsigned AddrShift = $clog2(TbAxiDataWidthSlv/8); + localparam int unsigned SelWidth = $clog2(DataMultFactor); + typedef logic [SelWidth-1:0] sel_t; + + sel_t fifo_exp_r_offs[$]; + + initial begin : proc_aw_w_check + automatic aw_chan_lite_t mst_aw_chan, slv_aw_chan; + automatic w_chan_lite_slv_t slv_w_chan; + automatic w_chan_lite_mst_t mst_w_chan; + automatic addr_t exp_addr; + automatic sel_t w_sel; + automatic logic [TbAxiDataWidthMst/8-1:0] slv_w_strb_concat; + + forever begin + wait((fifo_slv_aw.size() > 0) && (fifo_mst_aw.size() > 0) && (fifo_slv_w.size() > 0) && (fifo_mst_w.size() > 0)); + slv_aw_chan = fifo_slv_aw.pop_front(); + slv_w_chan = fifo_slv_w.pop_front(); + mst_aw_chan = fifo_mst_aw.pop_front(); + mst_w_chan = fifo_mst_w.pop_front(); + w_sel = slv_aw_chan.addr[AddrShift+SelWidth-1:AddrShift]; + + assert (slv_aw_chan == mst_aw_chan) else $error("Master port> AW is not expected: EXP: %h ACT: %h", + slv_aw_chan, mst_aw_chan); + assert (slv_w_chan.data == mst_w_chan.data[TbAxiDataWidthSlv-1:0]) else $error("Master port> W.data is not expected: EXP: %h ACT: %h", + slv_w_chan.data, mst_w_chan.data); + + slv_w_strb_concat = slv_w_chan.strb << (w_sel*TbAxiDataWidthSlv/8); + assert (slv_w_strb_concat == mst_w_chan.strb) else $error("Master port> W.strb is not expected: EXP: %h ACT: %h", + slv_w_chan.strb, mst_w_chan.strb); + end + end + + initial begin : proc_b_check + automatic b_chan_lite_t b_act, b_exp; + forever begin + wait((fifo_slv_b.size() > 0) && (fifo_mst_b.size() > 0)); + b_act = fifo_slv_b.pop_front(); + b_exp = fifo_mst_b.pop_front(); + assert (b_act == b_exp) else $error("Slave port> B.resp is not expected: EXP: %h ACT:%h", + b_exp.resp, b_act.resp); + end + end + + initial begin : proc_ar_check + automatic ar_chan_lite_t mst_ar_chan, slv_ar_chan; + automatic sel_t r_sel; + + forever begin + wait((fifo_slv_ar.size() > 0) && (fifo_mst_ar.size() > 0)); + slv_ar_chan = fifo_slv_ar.pop_front(); + mst_ar_chan = fifo_mst_ar.pop_front(); + r_sel = slv_ar_chan.addr[AddrShift+SelWidth-1:AddrShift]; + fifo_exp_r_offs.push_back(r_sel); + + assert (slv_ar_chan == mst_ar_chan) else $error("Master port> AR is not expected: EXP: %h ACT: %h", + slv_ar_chan, mst_ar_chan); + end + end + + initial begin : proc_r_check + automatic r_chan_lite_slv_t slv_r_chan; + automatic r_chan_lite_mst_t mst_r_chan; + automatic sel_t r_sel; + + forever begin + wait((fifo_slv_r.size() > 0) && (fifo_mst_r.size() > 0) && (fifo_exp_r_offs.size() > 0)); + slv_r_chan = fifo_slv_r.pop_front(); + mst_r_chan = fifo_mst_r.pop_front(); + r_sel = fifo_exp_r_offs.pop_front(); + + assert (slv_r_chan.resp == mst_r_chan.resp) else $error("Slave port> R.resp is not expected: EXP: %h, ACT: %h", + mst_r_chan.resp, slv_r_chan.resp); + assert (slv_r_chan.data == data_slv_t'(mst_r_chan.data >> (r_sel*TbAxiDataWidthSlv))) else $error("Slave port> R.data is not expected: EXP: %h, ACT: %h", + mst_r_chan.data, slv_r_chan.data); + end + end + + end + + + initial begin : proc_stop_sim + wait (end_of_sim); + repeat (1000) @(posedge clk); + $display("Simulation stopped as all Masters transferred their data, Success.",); + $stop(); + end + + //----------------------------------- + // Clock generator + //----------------------------------- + clk_rst_gen #( + .ClkPeriod ( CyclTime ), + .RstClkCycles( 5 ) + ) i_clk_gen ( + .clk_o (clk), + .rst_no(rst_n) + ); + + //----------------------------------- + // DUT + //----------------------------------- + axi_lite_dw_converter_intf #( + .AXI_ADDR_WIDTH ( TbAxiAddrWidth ), + .AXI_SLV_PORT_DATA_WIDTH ( TbAxiDataWidthSlv ), + .AXI_MST_PORT_DATA_WIDTH ( TbAxiDataWidthMst ) + ) i_axi_lite_dw_downsizer_dut ( + .clk_i ( clk ), + .rst_ni ( rst_n ), + .slv ( master ), + .mst ( slave ) + ); +endmodule diff --git a/test/tb_axi_lite_regs.sv b/test/tb_axi_lite_regs.sv index 0b90bcac3..32168d483 100644 --- a/test/tb_axi_lite_regs.sv +++ b/test/tb_axi_lite_regs.sv @@ -10,6 +10,7 @@ // // Authors: // - Wolfgang Roenninger +// - Andreas Kurth // Directed Random Verification Testbench for `axi_lite_regs`. diff --git a/test/tb_axi_lite_to_axi.sv b/test/tb_axi_lite_to_axi.sv index 497ce893e..50a9b81fc 100644 --- a/test/tb_axi_lite_to_axi.sv +++ b/test/tb_axi_lite_to_axi.sv @@ -9,8 +9,8 @@ // specific language governing permissions and limitations under the License. // // Authors: -// - Fabian Schuiki // - Andreas Kurth +// - Fabian Schuiki // - Florian Zaruba `include "axi/assign.svh" diff --git a/test/tb_axi_sim_mem.sv b/test/tb_axi_sim_mem.sv index 0e5f51822..74774e14b 100644 --- a/test/tb_axi_sim_mem.sv +++ b/test/tb_axi_sim_mem.sv @@ -3,6 +3,7 @@ // // Authors: // - Andreas Kurth +// - Michael Rogenmoser `include "axi/assign.svh" `include "axi/typedef.svh" @@ -85,6 +86,8 @@ module tb_axi_sim_mem #( wait (rst_n); // AW `ifdef XILINX_SIMULATOR + // std::randomize(aw_beat) may behave differently to aw_beat.randomize() wrt. limited ranges + // Keeping alternate implementation for XSIM only rand_success = std::randomize(aw_beat); assert (rand_success); `else rand_success = aw_beat.randomize(); assert (rand_success); @@ -98,6 +101,8 @@ module tb_axi_sim_mem #( // W beats for (int unsigned i = 0; i <= aw_beat.ax_len; i++) begin `ifdef XILINX_SIMULATOR + // std::randomize(w_beat) may behave differently to w_beat.randomize() wrt. limited ranges + // Keeping alternate implementation for XSIM only rand_success = std::randomize(w_beat); assert (rand_success); `else rand_success = w_beat.randomize(); assert (rand_success); diff --git a/test/tb_axi_slave_compare.sv b/test/tb_axi_slave_compare.sv new file mode 100644 index 000000000..2fa47bdc7 --- /dev/null +++ b/test/tb_axi_slave_compare.sv @@ -0,0 +1,241 @@ +// Copyright (c) 2020 ETH Zurich and University of Bologna +// SPDX-License-Identifier: SHL-0.51 +// +// Authors: +// - Andreas Kurth +// - Thomas Benz +// - Michael Rogenmoser + +`include "axi/assign.svh" +`include "axi/typedef.svh" + +/// Testbench for `axi_slave_compare` +module tb_axi_slave_compare #( + // TB Parameters + parameter time TbTclk = 10ns, + // Module Parameters + parameter int unsigned TbAddrWidth = 32'd64, + parameter int unsigned TbDataWidth = 32'd128, + parameter int unsigned TbIdWidth = 32'd6, + parameter int unsigned TbUserWidth = 32'd2, + parameter bit TbWarnUninitialized = 1'b0, + parameter time TbApplDelay = 2ns, + parameter time TbAcqDelay = 8ns +); + + logic clk, + rst_n; + clk_rst_gen #( + .ClkPeriod (TbTclk), + .RstClkCycles (5) + ) i_clk_rst_gen ( + .clk_o (clk), + .rst_no (rst_n) + ); + + localparam int unsigned StrbWidth = TbDataWidth / 8; + typedef logic [TbAddrWidth-1:0] addr_t; + typedef logic [TbDataWidth-1:0] data_t; + typedef logic [TbIdWidth-1:0] id_t; + typedef logic [StrbWidth-1:0] strb_t; + typedef logic [TbUserWidth-1:0] user_t; + + AXI_BUS #( + .AXI_ADDR_WIDTH (TbAddrWidth), + .AXI_DATA_WIDTH (TbDataWidth), + .AXI_ID_WIDTH (TbIdWidth), + .AXI_USER_WIDTH (TbUserWidth) + ) axi (); + + AXI_BUS_DV #( + .AXI_ADDR_WIDTH (TbAddrWidth), + .AXI_DATA_WIDTH (TbDataWidth), + .AXI_ID_WIDTH (TbIdWidth), + .AXI_USER_WIDTH (TbUserWidth) + ) axi_dv (clk); + typedef axi_test::axi_driver #( + .AW(TbAddrWidth), .DW(TbDataWidth), .IW(TbIdWidth), .UW(TbUserWidth), + .TA(1ns), .TT(6ns) + ) drv_t; + drv_t drv = new(axi_dv); + + `AXI_ASSIGN (axi, axi_dv) + + `AXI_TYPEDEF_ALL(axi, addr_t, id_t, data_t, strb_t, user_t) + + axi_req_t axi_req, axi_req_a_out, axi_req_b_out, axi_req_b_dly; + axi_resp_t axi_rsp, axi_rsp_a_out, axi_rsp_b_out, axi_rsp_b_dly; + + `AXI_ASSIGN_TO_REQ(axi_req, axi) + `AXI_ASSIGN_FROM_RESP(axi, axi_rsp) + + axi_slave_compare #( + .AxiIdWidth ( TbIdWidth ), + .FifoDepth ( 32'd16 ), + .axi_aw_chan_t ( axi_aw_chan_t ), + .axi_w_chan_t ( axi_w_chan_t ), + .axi_b_chan_t ( axi_b_chan_t ), + .axi_ar_chan_t ( axi_ar_chan_t ), + .axi_r_chan_t ( axi_r_chan_t ), + .axi_req_t ( axi_req_t ), + .axi_rsp_t ( axi_resp_t ) + ) i_axi_bus_compare ( + .clk_i ( clk ), + .rst_ni ( rst_n ), + .testmode_i ( 1'b0 ), + .axi_mst_req_i ( axi_req ), + .axi_mst_rsp_o ( axi_rsp ), + .axi_ref_req_o ( axi_req_a_out ), + .axi_ref_rsp_i ( axi_rsp_a_out ), + .axi_test_req_o ( axi_req_b_out ), + .axi_test_rsp_i ( axi_rsp_b_out ), + .aw_mismatch_o ( ), + .w_mismatch_o ( ), + .b_mismatch_o ( ), + .ar_mismatch_o ( ), + .r_mismatch_o ( ), + .mismatch_o ( ), + .busy_o ( ) + ); + + axi_sim_mem #( + .AddrWidth(TbAddrWidth), + .DataWidth(TbDataWidth), + .IdWidth (TbIdWidth), + .UserWidth(TbUserWidth), + .axi_req_t(axi_req_t), + .axi_rsp_t(axi_resp_t), + .ApplDelay(TbApplDelay), + .AcqDelay (TbAcqDelay) + ) i_axi_sim_mem_a ( + .clk_i ( clk ), + .rst_ni ( rst_n ), + .axi_req_i ( axi_req_a_out ), + .axi_rsp_o ( axi_rsp_a_out ), + .mon_w_valid_o ( ), + .mon_w_addr_o ( ), + .mon_w_data_o ( ), + .mon_w_id_o ( ), + .mon_w_user_o ( ), + .mon_w_beat_count_o( ), + .mon_w_last_o ( ), + .mon_r_valid_o ( ), + .mon_r_addr_o ( ), + .mon_r_data_o ( ), + .mon_r_id_o ( ), + .mon_r_user_o ( ), + .mon_r_beat_count_o( ), + .mon_r_last_o ( ) + ); + + axi_multicut #( + .NoCuts (8), + .aw_chan_t (axi_aw_chan_t), + .w_chan_t (axi_w_chan_t), + .b_chan_t (axi_b_chan_t), + .ar_chan_t (axi_ar_chan_t), + .r_chan_t (axi_r_chan_t), + .axi_req_t (axi_req_t), + .axi_resp_t(axi_resp_t) + ) i_axi_multicut ( + .clk_i ( clk ), + .rst_ni ( rst_n ), + .slv_req_i ( axi_req_b_out ), + .slv_resp_o ( axi_rsp_b_out ), + .mst_req_o ( axi_req_b_dly ), + .mst_resp_i ( axi_rsp_b_dly ) + ); + + axi_sim_mem #( + .AddrWidth(TbAddrWidth), + .DataWidth(TbDataWidth), + .IdWidth (TbIdWidth), + .UserWidth(TbUserWidth), + .axi_req_t(axi_req_t), + .axi_rsp_t(axi_resp_t), + .ApplDelay(TbApplDelay), + .AcqDelay (TbAcqDelay) + ) i_axi_sim_mem_b ( + .clk_i ( clk ), + .rst_ni ( rst_n ), + .axi_req_i ( axi_req_b_dly ), + .axi_rsp_o ( axi_rsp_b_dly ), + .mon_w_valid_o ( ), + .mon_w_addr_o ( ), + .mon_w_data_o ( ), + .mon_w_id_o ( ), + .mon_w_user_o ( ), + .mon_w_beat_count_o( ), + .mon_w_last_o ( ), + .mon_r_valid_o ( ), + .mon_r_addr_o ( ), + .mon_r_data_o ( ), + .mon_r_id_o ( ), + .mon_r_user_o ( ), + .mon_r_beat_count_o( ), + .mon_r_last_o ( ) + ); + + + // Simply read and write a random memory region. + initial begin + automatic logic rand_success; + automatic data_t exp_data[$]; + automatic drv_t::ax_beat_t aw_beat = new, ar_beat = new; + automatic drv_t::w_beat_t w_beat = new; + automatic drv_t::b_beat_t b_beat; + automatic drv_t::r_beat_t r_beat; + drv.reset_master(); + wait (rst_n); + // AW +`ifdef XSIM + // std::randomize(aw_beat) may behave differently to aw_beat.randomize() wrt. limited ranges + // Keeping alternate implementation for XSIM only + rand_success = std::randomize(aw_beat); assert (rand_success); +`else + rand_success = aw_beat.randomize(); assert (rand_success); +`endif + aw_beat.ax_addr >>= $clog2(StrbWidth); // align address with data width + aw_beat.ax_addr <<= $clog2(StrbWidth); + aw_beat.ax_len = $urandom(); + aw_beat.ax_size = $clog2(StrbWidth); + aw_beat.ax_burst = axi_pkg::BURST_INCR; + drv.send_aw(aw_beat); + // W beats + for (int unsigned i = 0; i <= aw_beat.ax_len; i++) begin +`ifdef XSIM + // std::randomize(w_beat) may behave differently to w_beat.randomize() wrt. limited ranges + // Keeping alternate implementation for XSIM only + rand_success = std::randomize(w_beat); assert (rand_success); +`else + rand_success = w_beat.randomize(); assert (rand_success); +`endif + w_beat.w_strb = '1; + if (i == aw_beat.ax_len) begin + w_beat.w_last = 1'b1; + end + drv.send_w(w_beat); + exp_data.push_back(w_beat.w_data); + end + // B + drv.recv_b(b_beat); + assert(b_beat.b_resp == axi_pkg::RESP_OKAY); + // AR + ar_beat.ax_addr = aw_beat.ax_addr; + ar_beat.ax_len = aw_beat.ax_len; + ar_beat.ax_size = aw_beat.ax_size; + ar_beat.ax_burst = aw_beat.ax_burst; + drv.send_ar(ar_beat); + // R beats + for (int unsigned i = 0; i <= ar_beat.ax_len; i++) begin + automatic data_t exp = exp_data.pop_front(); + drv.recv_r(r_beat); + assert(r_beat.r_data == exp) else + $error("Received 0x%h != expected 0x%h!", r_beat.r_data, exp); + end + // Done. + #(TbTclk); + $finish(); + end + +endmodule diff --git a/test/tb_axi_to_mem_banked.sv b/test/tb_axi_to_mem_banked.sv new file mode 100644 index 000000000..64667998d --- /dev/null +++ b/test/tb_axi_to_mem_banked.sv @@ -0,0 +1,418 @@ +// Copyright 2020 ETH Zurich and University of Bologna. +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the "License"); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +// Authors: +// - Wolfgang Roenninger +// - Michael Rogenmoser + +`include "axi/typedef.svh" +`include "axi/assign.svh" +`include "common_cells/registers.svh" + +/// Testbench for axi_to_mem_banked. Monitors the performance for random accesses. +module tb_axi_to_mem_banked #( + /// Data Width of the AXI4+ATOP channels. + parameter int unsigned TbAxiDataWidth = 32'd256, + /// Number of words of an individual memory bank. + /// Determines the address width of the request output. + parameter int unsigned TbNumWords = 32'd8192, + /// Number of connected memory banks. + parameter int unsigned TbNumBanks = 32'd8, + /// Data width of an individual memory bank. + parameter int unsigned TbMemDataWidth = 32'd64, + /// Latancy in cycles of a memory bank. + parameter int unsigned TbMemLatency = 32'd2, + /// Number of writes performed by the testbench. + parameter int unsigned TbNumWrites = 32'd10000, + /// Number of writes performed by the testbench. + parameter int unsigned TbNumReads = 32'd10000 +); + // test bench params + localparam time CyclTime = 10ns; + localparam time ApplTime = 2ns; + localparam time TestTime = 8ns; + + // localparam and typedefs for AXI4+ATOP + localparam int unsigned AxiIdWidth = 32'd6; + localparam int unsigned AxiAddrWidth = 32'd64; + localparam int unsigned AxiStrbWidth = TbAxiDataWidth / 8; + localparam int unsigned AxiUserWidth = 32'd4; + + typedef logic [AxiAddrWidth-1:0] axi_addr_t; + + // AXI test defines + typedef axi_test::axi_rand_master #( + // AXI interface parameters + .AW ( AxiAddrWidth ), + .DW ( TbAxiDataWidth ), + .IW ( AxiIdWidth ), + .UW ( AxiUserWidth ), + // Stimuli application and test time + .TA ( ApplTime ), + .TT ( TestTime ), + // Maximum number of read and write transactions in flight + .MAX_READ_TXNS ( 20 ), + .MAX_WRITE_TXNS ( 20 ), + // Upper and lower bounds on wait cycles on Ax, W, and resp (R and B) channels + .AX_MIN_WAIT_CYCLES ( 0 ), + .AX_MAX_WAIT_CYCLES ( 0 ), + .W_MIN_WAIT_CYCLES ( 0 ), + .W_MAX_WAIT_CYCLES ( 0 ), + .RESP_MIN_WAIT_CYCLES ( 0 ), + .RESP_MAX_WAIT_CYCLES ( 0 ), + // AXI feature usage + .AXI_MAX_BURST_LEN ( 0 ), // maximum number of beats in burst; 0 = AXI max (256) + .TRAFFIC_SHAPING ( 0 ), + .AXI_EXCLS ( 1'b0 ), + .AXI_ATOPS ( 1'b0 ), + .AXI_BURST_FIXED ( 1'b0 ), + .AXI_BURST_INCR ( 1'b1 ), + .AXI_BURST_WRAP ( 1'b0 ) + ) axi_rand_master_t; + + // memory defines + localparam int unsigned MemAddrWidth = $clog2(TbNumWords); + + localparam int unsigned MemBufDepth = 1; + // addresses + localparam axi_addr_t StartAddr = axi_addr_t'(64'h0); + localparam axi_addr_t EndAddr = axi_addr_t'(StartAddr + 32'd2 * TbNumWords * TbAxiDataWidth/32'd8); + + typedef logic [MemAddrWidth-1:0] mem_addr_t; + typedef logic [5:0] mem_atop_t; + typedef logic [TbMemDataWidth-1:0] mem_data_t; + typedef logic [TbMemDataWidth/8-1:0] mem_strb_t; + + // sim signals + logic end_of_sim; + + // dut signals + logic clk, rst_n, one_dut_active; + + logic [1:0] dut_busy; + logic [TbNumBanks-1:0] mem_req; + logic [TbNumBanks-1:0] mem_gnt; + mem_addr_t [TbNumBanks-1:0] mem_addr; + mem_data_t [TbNumBanks-1:0] mem_wdata; + mem_strb_t [TbNumBanks-1:0] mem_strb; + logic [TbNumBanks-1:0] mem_we; + mem_atop_t [TbNumBanks-1:0] mem_atop; + logic [TbNumBanks-1:0] mem_rvalid; + mem_data_t [TbNumBanks-1:0] mem_rdata; + + assign one_dut_active = |dut_busy; + + AXI_BUS_DV #( + .AXI_ADDR_WIDTH ( AxiAddrWidth ), + .AXI_DATA_WIDTH ( TbAxiDataWidth ), + .AXI_ID_WIDTH ( AxiIdWidth ), + .AXI_USER_WIDTH ( AxiUserWidth ) + ) mem_axi_dv (clk); + + AXI_BUS #( + .AXI_ADDR_WIDTH ( AxiAddrWidth ), + .AXI_DATA_WIDTH ( TbAxiDataWidth ), + .AXI_ID_WIDTH ( AxiIdWidth ), + .AXI_USER_WIDTH ( AxiUserWidth ) + ) mem_axi (); + `AXI_ASSIGN(mem_axi, mem_axi_dv) + + // stimuli generation + initial begin : proc_axi_master + static axi_rand_master_t axi_rand_master = new ( mem_axi_dv ); + end_of_sim <= 1'b0; + axi_rand_master.add_memory_region(StartAddr, EndAddr, axi_pkg::DEVICE_NONBUFFERABLE); + axi_rand_master.reset(); + @(posedge rst_n); + @(posedge clk); + @(posedge clk); + + axi_rand_master.run(TbNumReads, TbNumWrites); + end_of_sim <= 1'b1; + end + + // memory banks + for (genvar i = 0; i < TbNumBanks; i++) begin : gen_tc_sram + tc_sram #( + .NumWords ( TbNumWords ), + .DataWidth ( TbMemDataWidth ), + .ByteWidth ( 32'd8 ), + .NumPorts ( 32'd1 ), + .Latency ( TbMemLatency ), + .SimInit ( "none" ), + .PrintSimCfg ( 1'b1 ) + ) i_tc_sram_bank ( + .clk_i ( clk ), + .rst_ni ( rst_n ), + .req_i ( mem_req[i] ), + .we_i ( mem_we[i] ), + .addr_i ( mem_addr[i] ), + .wdata_i ( mem_wdata[i] ), + .be_i ( mem_strb[i] ), + .rdata_o ( mem_rdata[i] ) + ); + // always be ready + assign mem_gnt[i] = 1'b1; + // generate mem_rvalid signal + if (TbMemLatency == 0) begin : gen_no_mem__lat + assign mem_rvalid[i] = mem_req[i]; + end else begin : gen_mem_lat + logic [TbMemLatency-1:0] mem_lat_q, mem_lat_d; + `FFARN(mem_lat_q, mem_lat_d, '0, clk, rst_n) + assign mem_lat_d[TbMemLatency-1] = mem_req[i]; + if (TbMemLatency > 1) begin + for (genvar lat_i = 0; lat_i < TbMemLatency - 1; lat_i++) begin + assign mem_lat_d[lat_i] = mem_lat_q[lat_i+1]; + end + end + assign mem_rvalid[i] = mem_lat_q[0]; + end + end + + // Clock generator + clk_rst_gen #( + .ClkPeriod ( CyclTime ), + .RstClkCycles ( 5 ) + ) i_clk_rst_gen ( + .clk_o ( clk ), + .rst_no ( rst_n ) + ); + + // Design under test + axi_to_mem_banked_intf #( + .AXI_ID_WIDTH ( AxiIdWidth ), + .AXI_ADDR_WIDTH ( AxiAddrWidth ), + .AXI_DATA_WIDTH ( TbAxiDataWidth ), + .AXI_USER_WIDTH ( AxiUserWidth ), + .MEM_NUM_BANKS ( TbNumBanks ), + .MEM_ADDR_WIDTH ( MemAddrWidth ), + .MEM_DATA_WIDTH ( TbMemDataWidth ), + .MEM_LATENCY ( TbMemLatency ) + ) i_axi_to_mem_banked_dut ( + .clk_i ( clk ), + .rst_ni ( rst_n ), + .test_i ( 1'b0 ), + .axi_to_mem_busy_o ( dut_busy ), + .slv ( mem_axi ), + .mem_req_o ( mem_req ), + .mem_gnt_i ( mem_gnt ), + .mem_add_o ( mem_addr ), // byte address + .mem_wdata_o ( mem_wdata ), // write data + .mem_be_o ( mem_strb ), // byte-wise strobe + .mem_atop_o ( mem_atop ), // atomic operation + .mem_we_o ( mem_we ), // write enable + .mem_rdata_i ( mem_rdata ) // read data + ); + + // monitoring + logic aw_beat, aw_stall, w_beat, b_beat, ar_beat, ar_stall, r_beat; + assign aw_beat = mem_axi.aw_valid & mem_axi.aw_ready; + assign aw_stall = mem_axi.aw_valid & !mem_axi.aw_ready; + assign w_beat = mem_axi.w_valid & mem_axi.w_ready; + assign b_beat = mem_axi.b_valid & mem_axi.b_ready; + assign ar_beat = mem_axi.ar_valid & mem_axi.ar_ready; + assign ar_stall = mem_axi.ar_valid & !mem_axi.ar_ready; + assign r_beat = mem_axi.r_valid & mem_axi.r_ready; + + int unsigned aw_open; + int unsigned ar_open; + + initial begin : proc_monitor + automatic bit aw_new = 1; + automatic bit w_new = 1; + automatic bit b_new = 1; + automatic bit ar_new = 1; + automatic bit r_new = 1; + + + automatic longint wc_cnt = 0; + automatic longint rc_cnt = 0; + automatic longint w_cnt = 0; + automatic longint r_cnt = 0; + + automatic longint busy_cnt; + automatic longint dut_busy_cnt [TbNumBanks]; + automatic real bank_busy_percent; + automatic real axi_busy_percent; + automatic real tmp; + + aw_open = 0; + ar_open = 0; + bank_busy_percent = 0; + axi_busy_percent = 0; + for (int i = 0; i < TbNumBanks; i++) begin + dut_busy_cnt[i] = 0; + end + $display("###############################################################################"); + $display("Sim Parameter:"); + $display("###############################################################################"); + $display("TbAxiDataWidth: %0d", TbAxiDataWidth); + $display("TbMemDataWidth: %0d", TbMemDataWidth); + $display("TbNumBanks: %0d", TbNumBanks); + $display("TbMemLatency: %0d", TbMemLatency); + $display("###############################################################################"); + + @(posedge rst_n); + forever begin + @(posedge clk); + + #TestTime; + // determine the first valid of an AW transaction + if (mem_axi.aw_valid) begin + if (aw_new) begin + aw_open++; + if (!mem_axi.aw_ready) begin + aw_new = 0; + end + end else begin + if (mem_axi.aw_ready) begin + aw_new = 1; + end + end + end + + // determine the first valid of an AR transaction + if (mem_axi.ar_valid) begin + if (ar_new) begin + ar_open++; + if (!mem_axi.ar_ready) begin + ar_new = 0; + end + end else begin + if (mem_axi.ar_ready) begin + ar_new = 1; + end + end + end + + if (b_beat) begin + aw_open--; + end + if (r_beat && mem_axi.r_last) begin + ar_open--; + end + + if (aw_open > 0) begin + wc_cnt++; + end + if (ar_open > 0) begin + rc_cnt++; + end + + if (w_beat) begin + w_cnt++; + end + if (r_beat) begin + r_cnt++; + end + + if ((aw_open > 0) || (ar_open > 0)) begin + busy_cnt++; + end + + for (int unsigned i = 0; i < TbNumBanks; i++) begin + if (mem_req[i]) begin + dut_busy_cnt[i]++; + end + end + + + if (end_of_sim) begin + @(posedge clk); + $display("###############################################################################"); + $display("Statistics:"); + $display("###############################################################################"); + $display("Writes:"); + $display("Cycles Open write tnx: %0d", wc_cnt); + $display("Write beat count: %0d", w_cnt); + $display("Write utilization: %0f", real'(w_cnt) / real'(wc_cnt) * 100); + axi_busy_percent += real'(w_cnt) / real'(wc_cnt) * 100; + $display("###############################################################################"); + $display("Reads:"); + $display("Cycles Open read tnx: %0d", rc_cnt); + $display("Read beat count: %0d", r_cnt); + $display("Read utilization: %0f", real'(r_cnt) / real'(rc_cnt) * 100); + axi_busy_percent += real'(r_cnt) / real'(rc_cnt) * 100; + $display("###############################################################################"); + for (int unsigned i = 0; i < TbNumBanks; i++) begin + bank_busy_percent += real'(dut_busy_cnt[i]) / real'(busy_cnt) * 100; + $display("Bank %0d utilization: %0f", i, real'(dut_busy_cnt[i]) / real'(busy_cnt) * 100); + tmp = dut_busy_cnt[i]; + $display("Bank %0d requests: %0f", i, tmp); + tmp = busy_cnt; + $display("Bank %0d busy cycles: %0f", i, tmp); + end + $display("Sum bank utilization: %0f", bank_busy_percent); + $display("Sum axi utilization: %0f", axi_busy_percent); + $display("###############################################################################"); + $stop(); + end + end + end + + initial begin : proc_sim_progress + longint unsigned ActAwTnx; + longint unsigned ActArTnx; + automatic int unsigned PrintInterv = 100; + + ActAwTnx = 0; + ActArTnx = 0; + $display("Start Addr: %0h", StartAddr); + $display("End addr: %0h", EndAddr); + + @(posedge rst_n); + forever begin + @(posedge clk); + #TestTime; + + if (aw_beat) begin + if (ActAwTnx % PrintInterv == 0) begin + $display("%t > AW Transaction %d of %d ", $time(), ActAwTnx, TbNumWrites); + end + ActAwTnx++; + end + if (ar_beat) begin + if (ActArTnx % PrintInterv == 0) begin + $display("%t > AR Transaction %d of %d ", $time(), ActArTnx, TbNumReads); + end + ActArTnx++; + end + + if (end_of_sim) begin + break; + end + end + end + + AXI_BUS_DV #( + .AXI_ADDR_WIDTH ( AxiAddrWidth ), + .AXI_DATA_WIDTH ( TbAxiDataWidth ), + .AXI_ID_WIDTH ( AxiIdWidth ), + .AXI_USER_WIDTH ( AxiUserWidth ) + ) monitor_dv (clk); + + `AXI_ASSIGN_MONITOR(monitor_dv, mem_axi) + + typedef axi_test::axi_scoreboard #( + .IW ( AxiIdWidth ), + .AW ( AxiAddrWidth ), + .DW ( TbAxiDataWidth ), + .UW ( AxiUserWidth ), + .TT ( TestTime ) + ) axi_scoreboard_t; + axi_scoreboard_t axi_scoreboard = new(monitor_dv); + initial begin : proc_scoreboard + axi_scoreboard.enable_all_checks(); + @(posedge rst_n); + axi_scoreboard.monitor(); + wait (end_of_sim); + end + +endmodule diff --git a/test/tb_axi_xbar.sv b/test/tb_axi_xbar.sv index d8d75900d..6056be919 100644 --- a/test/tb_axi_xbar.sv +++ b/test/tb_axi_xbar.sv @@ -9,6 +9,7 @@ // specific language governing permissions and limitations under the License. // // Authors: +// - Wolfgang Roenninger // - Florian Zaruba // - Andreas Kurth @@ -22,51 +23,68 @@ `include "axi/typedef.svh" `include "axi/assign.svh" +/// Testbench for the module `axi_xbar`. module tb_axi_xbar #( - parameter bit TbEnAtop = 1'b1, // enable atomic operations (ATOPs) - parameter bit TbEnExcl = 1'b0, // enable exclusive accesses - parameter bit TbUniqueIds = 1'b0, // restrict to only unique IDs - parameter int unsigned TbNumMst = 32'd6, // how many AXI masters there are - parameter int unsigned TbNumSlv = 32'd8 // how many AXI slaves there are + /// Number of AXI masters connected to the xbar. (Number of slave ports) + parameter int unsigned TbNumMasters = 32'd6, + /// Number of AXI slaves connected to the xbar. (Number of master ports) + parameter int unsigned TbNumSlaves = 32'd8, + /// Number of write transactions per master. + parameter int unsigned TbNumWrites = 32'd200, + /// Number of read transactions per master. + parameter int unsigned TbNumReads = 32'd200, + /// AXI4+ATOP ID width of the masters connected to the slave ports of the DUT. + /// The ID width of the slaves is calculated depending on the xbar configuration. + parameter int unsigned TbAxiIdWidthMasters = 32'd5, + /// The used ID width of the DUT. + /// Has to be `TbAxiIdWidthMasters >= TbAxiIdUsed`. + parameter int unsigned TbAxiIdUsed = 32'd3, + /// Data width of the AXI channels. + parameter int unsigned TbAxiDataWidth = 32'd64, + /// Pipeline stages in the xbar itself (between demux and mux). + parameter int unsigned TbPipeline = 32'd1, + /// Enable ATOP generation + parameter bit TbEnAtop = 1'b1, + /// Enable exclusive accesses + parameter bit TbEnExcl = 1'b0, + /// Restrict to only unique IDs + parameter bit TbUniqueIds = 1'b0 + ); - // Random master no Transactions - localparam int unsigned NoWrites = 80; // How many writes per master - localparam int unsigned NoReads = 80; // How many reads per master - // timing parameters + + // TB timing parameters localparam time CyclTime = 10ns; localparam time ApplTime = 2ns; localparam time TestTime = 8ns; - // axi configuration - localparam int unsigned AxiIdWidthMasters = 4; - localparam int unsigned AxiIdUsed = 3; // Has to be <= AxiIdWidthMasters - localparam int unsigned AxiIdWidthSlaves = AxiIdWidthMasters + $clog2(TbNumMst); - localparam int unsigned AxiAddrWidth = 32; // Axi Address Width - localparam int unsigned AxiDataWidth = 64; // Axi Data Width - localparam int unsigned AxiStrbWidth = AxiDataWidth / 8; - localparam int unsigned AxiUserWidth = 5; - // in the bench can change this variables which are set here freely + // AXI configuration which is automatically derived. + localparam int unsigned TbAxiIdWidthSlaves = TbAxiIdWidthMasters + $clog2(TbNumMasters); + localparam int unsigned TbAxiAddrWidth = 32'd32; + localparam int unsigned TbAxiStrbWidth = TbAxiDataWidth / 8; + localparam int unsigned TbAxiUserWidth = 5; + // In the bench can change this variables which are set here freely, localparam axi_pkg::xbar_cfg_t xbar_cfg = '{ - NoSlvPorts: TbNumMst, - NoMstPorts: TbNumSlv, + NoSlvPorts: TbNumMasters, + NoMstPorts: TbNumSlaves, MaxMstTrans: 10, MaxSlvTrans: 6, FallThrough: 1'b0, LatencyMode: axi_pkg::CUT_ALL_AX, - AxiIdWidthSlvPorts: AxiIdWidthMasters, - AxiIdUsedSlvPorts: AxiIdUsed, + PipelineStages: TbPipeline, + AxiIdWidthSlvPorts: TbAxiIdWidthMasters, + AxiIdUsedSlvPorts: TbAxiIdUsed, UniqueIds: TbUniqueIds, - AxiAddrWidth: AxiAddrWidth, - AxiDataWidth: AxiDataWidth, - NoAddrRules: 8 + AxiAddrWidth: TbAxiAddrWidth, + AxiDataWidth: TbAxiDataWidth, + NoAddrRules: TbNumSlaves }; - typedef logic [AxiIdWidthMasters-1:0] id_mst_t; - typedef logic [AxiIdWidthSlaves-1:0] id_slv_t; - typedef logic [AxiAddrWidth-1:0] addr_t; - typedef axi_pkg::xbar_rule_32_t rule_t; // Has to be the same width as axi addr - typedef logic [AxiDataWidth-1:0] data_t; - typedef logic [AxiStrbWidth-1:0] strb_t; - typedef logic [AxiUserWidth-1:0] user_t; + typedef logic [TbAxiIdWidthMasters-1:0] id_mst_t; + typedef logic [TbAxiIdWidthSlaves-1:0] id_slv_t; + typedef logic [TbAxiAddrWidth-1:0] addr_t; + typedef axi_pkg::xbar_rule_32_t rule_t; // Has to be the same width as axi addr + typedef logic [TbAxiDataWidth-1:0] data_t; + typedef logic [TbAxiStrbWidth-1:0] strb_t; + typedef logic [TbAxiUserWidth-1:0] user_t; `AXI_TYPEDEF_AW_CHAN_T(aw_chan_mst_t, addr_t, id_mst_t, user_t) `AXI_TYPEDEF_AW_CHAN_T(aw_chan_slv_t, addr_t, id_slv_t, user_t) @@ -84,42 +102,45 @@ module tb_axi_xbar #( `AXI_TYPEDEF_REQ_T(slv_req_t, aw_chan_slv_t, w_chan_t, ar_chan_slv_t) `AXI_TYPEDEF_RESP_T(slv_resp_t, b_chan_slv_t, r_chan_slv_t) - localparam rule_t [xbar_cfg.NoAddrRules-1:0] AddrMap = '{ - '{idx: 32'd7 % TbNumSlv, start_addr: 32'h0001_0000, end_addr: 32'h0001_1000}, - '{idx: 32'd6 % TbNumSlv, start_addr: 32'h0000_9000, end_addr: 32'h0001_0000}, - '{idx: 32'd5 % TbNumSlv, start_addr: 32'h0000_8000, end_addr: 32'h0000_9000}, - '{idx: 32'd4 % TbNumSlv, start_addr: 32'h0000_7000, end_addr: 32'h0000_8000}, - '{idx: 32'd3 % TbNumSlv, start_addr: 32'h0000_6300, end_addr: 32'h0000_7000}, - '{idx: 32'd2 % TbNumSlv, start_addr: 32'h0000_4000, end_addr: 32'h0000_6300}, - '{idx: 32'd1 % TbNumSlv, start_addr: 32'h0000_3000, end_addr: 32'h0000_4000}, - '{idx: 32'd0 % TbNumSlv, start_addr: 32'h0000_0000, end_addr: 32'h0000_3000} - }; + // Each slave has its own address range: + localparam rule_t [xbar_cfg.NoAddrRules-1:0] AddrMap = addr_map_gen(); + + function rule_t [xbar_cfg.NoAddrRules-1:0] addr_map_gen (); + for (int unsigned i = 0; i < xbar_cfg.NoAddrRules; i++) begin + addr_map_gen[i] = rule_t'{ + idx: unsigned'(i), + start_addr: i * 32'h0000_2000, + end_addr: (i+1) * 32'h0000_2000, + default: '0 + }; + end + endfunction typedef axi_test::axi_rand_master #( // AXI interface parameters - .AW ( AxiAddrWidth ), - .DW ( AxiDataWidth ), - .IW ( AxiIdWidthMasters ), - .UW ( AxiUserWidth ), + .AW ( TbAxiAddrWidth ), + .DW ( TbAxiDataWidth ), + .IW ( TbAxiIdWidthMasters ), + .UW ( TbAxiUserWidth ), // Stimuli application and test time - .TA ( ApplTime ), - .TT ( TestTime ), + .TA ( ApplTime ), + .TT ( TestTime ), // Maximum number of read and write transactions in flight - .MAX_READ_TXNS ( 20 ), - .MAX_WRITE_TXNS ( 20 ), - .AXI_EXCLS ( TbEnExcl ), - .AXI_ATOPS ( TbEnAtop ), + .MAX_READ_TXNS ( 20 ), + .MAX_WRITE_TXNS ( 20 ), + .AXI_EXCLS ( TbEnExcl ), + .AXI_ATOPS ( TbEnAtop ), .UNIQUE_IDS ( TbUniqueIds ) ) axi_rand_master_t; typedef axi_test::axi_rand_slave #( // AXI interface parameters - .AW ( AxiAddrWidth ), - .DW ( AxiDataWidth ), - .IW ( AxiIdWidthSlaves ), - .UW ( AxiUserWidth ), + .AW ( TbAxiAddrWidth ), + .DW ( TbAxiDataWidth ), + .IW ( TbAxiIdWidthSlaves ), + .UW ( TbAxiUserWidth ), // Stimuli application and test time - .TA ( ApplTime ), - .TT ( TestTime ) + .TA ( ApplTime ), + .TT ( TestTime ) ) axi_rand_slave_t; // ------------- @@ -128,62 +149,62 @@ module tb_axi_xbar #( logic clk; // DUT signals logic rst_n; - logic [TbNumMst-1:0] end_of_sim; + logic [TbNumMasters-1:0] end_of_sim; // master structs - mst_req_t [TbNumMst-1:0] masters_req; - mst_resp_t [TbNumMst-1:0] masters_resp; + mst_req_t [TbNumMasters-1:0] masters_req; + mst_resp_t [TbNumMasters-1:0] masters_resp; // slave structs - slv_req_t [TbNumSlv-1:0] slaves_req; - slv_resp_t [TbNumSlv-1:0] slaves_resp; + slv_req_t [TbNumSlaves-1:0] slaves_req; + slv_resp_t [TbNumSlaves-1:0] slaves_resp; // ------------------------------- // AXI Interfaces // ------------------------------- AXI_BUS #( - .AXI_ADDR_WIDTH ( AxiAddrWidth ), - .AXI_DATA_WIDTH ( AxiDataWidth ), - .AXI_ID_WIDTH ( AxiIdWidthMasters ), - .AXI_USER_WIDTH ( AxiUserWidth ) - ) master [TbNumMst-1:0] (); + .AXI_ADDR_WIDTH ( TbAxiAddrWidth ), + .AXI_DATA_WIDTH ( TbAxiDataWidth ), + .AXI_ID_WIDTH ( TbAxiIdWidthMasters ), + .AXI_USER_WIDTH ( TbAxiUserWidth ) + ) master [TbNumMasters-1:0] (); AXI_BUS_DV #( - .AXI_ADDR_WIDTH ( AxiAddrWidth ), - .AXI_DATA_WIDTH ( AxiDataWidth ), - .AXI_ID_WIDTH ( AxiIdWidthMasters ), - .AXI_USER_WIDTH ( AxiUserWidth ) - ) master_dv [TbNumMst-1:0] (clk); + .AXI_ADDR_WIDTH ( TbAxiAddrWidth ), + .AXI_DATA_WIDTH ( TbAxiDataWidth ), + .AXI_ID_WIDTH ( TbAxiIdWidthMasters ), + .AXI_USER_WIDTH ( TbAxiUserWidth ) + ) master_dv [TbNumMasters-1:0] (clk); AXI_BUS_DV #( - .AXI_ADDR_WIDTH ( AxiAddrWidth ), - .AXI_DATA_WIDTH ( AxiDataWidth ), - .AXI_ID_WIDTH ( AxiIdWidthMasters ), - .AXI_USER_WIDTH ( AxiUserWidth ) - ) master_monitor_dv [TbNumMst-1:0] (clk); - for (genvar i = 0; i < TbNumMst; i++) begin : gen_conn_dv_masters + .AXI_ADDR_WIDTH ( TbAxiAddrWidth ), + .AXI_DATA_WIDTH ( TbAxiDataWidth ), + .AXI_ID_WIDTH ( TbAxiIdWidthMasters ), + .AXI_USER_WIDTH ( TbAxiUserWidth ) + ) master_monitor_dv [TbNumMasters-1:0] (clk); + for (genvar i = 0; i < TbNumMasters; i++) begin : gen_conn_dv_masters `AXI_ASSIGN (master[i], master_dv[i]) `AXI_ASSIGN_TO_REQ(masters_req[i], master[i]) `AXI_ASSIGN_TO_RESP(masters_resp[i], master[i]) end AXI_BUS #( - .AXI_ADDR_WIDTH ( AxiAddrWidth ), - .AXI_DATA_WIDTH ( AxiDataWidth ), - .AXI_ID_WIDTH ( AxiIdWidthSlaves ), - .AXI_USER_WIDTH ( AxiUserWidth ) - ) slave [TbNumSlv-1:0] (); + .AXI_ADDR_WIDTH ( TbAxiAddrWidth ), + .AXI_DATA_WIDTH ( TbAxiDataWidth ), + .AXI_ID_WIDTH ( TbAxiIdWidthSlaves ), + .AXI_USER_WIDTH ( TbAxiUserWidth ) + ) slave [TbNumSlaves-1:0] (); AXI_BUS_DV #( - .AXI_ADDR_WIDTH ( AxiAddrWidth ), - .AXI_DATA_WIDTH ( AxiDataWidth ), - .AXI_ID_WIDTH ( AxiIdWidthSlaves ), - .AXI_USER_WIDTH ( AxiUserWidth ) - ) slave_dv [TbNumSlv-1:0](clk); + .AXI_ADDR_WIDTH ( TbAxiAddrWidth ), + .AXI_DATA_WIDTH ( TbAxiDataWidth ), + .AXI_ID_WIDTH ( TbAxiIdWidthSlaves ), + .AXI_USER_WIDTH ( TbAxiUserWidth ) + ) slave_dv [TbNumSlaves-1:0](clk); AXI_BUS_DV #( - .AXI_ADDR_WIDTH ( AxiAddrWidth ), - .AXI_DATA_WIDTH ( AxiDataWidth ), - .AXI_ID_WIDTH ( AxiIdWidthSlaves ), - .AXI_USER_WIDTH ( AxiUserWidth ) - ) slave_monitor_dv [TbNumSlv-1:0](clk); - for (genvar i = 0; i < TbNumSlv; i++) begin : gen_conn_dv_slaves + .AXI_ADDR_WIDTH ( TbAxiAddrWidth ), + .AXI_DATA_WIDTH ( TbAxiDataWidth ), + .AXI_ID_WIDTH ( TbAxiIdWidthSlaves ), + .AXI_USER_WIDTH ( TbAxiUserWidth ) + ) slave_monitor_dv [TbNumSlaves-1:0](clk); + for (genvar i = 0; i < TbNumSlaves; i++) begin : gen_conn_dv_slaves `AXI_ASSIGN(slave_dv[i], slave[i]) `AXI_ASSIGN_TO_REQ(slaves_req[i], slave[i]) `AXI_ASSIGN_TO_RESP(slaves_resp[i], slave[i]) @@ -192,8 +213,8 @@ module tb_axi_xbar #( // AXI Rand Masters and Slaves // ------------------------------- // Masters control simulation run time - axi_rand_master_t axi_rand_master [TbNumMst]; - for (genvar i = 0; i < TbNumMst; i++) begin : gen_rand_master + axi_rand_master_t axi_rand_master [TbNumMasters]; + for (genvar i = 0; i < TbNumMasters; i++) begin : gen_rand_master initial begin axi_rand_master[i] = new( master_dv[i] ); end_of_sim[i] <= 1'b0; @@ -202,13 +223,13 @@ module tb_axi_xbar #( axi_pkg::DEVICE_NONBUFFERABLE); axi_rand_master[i].reset(); @(posedge rst_n); - axi_rand_master[i].run(NoReads, NoWrites); + axi_rand_master[i].run(TbNumReads, TbNumWrites); end_of_sim[i] <= 1'b1; end end - axi_rand_slave_t axi_rand_slave [TbNumSlv]; - for (genvar i = 0; i < TbNumSlv; i++) begin : gen_rand_slave + axi_rand_slave_t axi_rand_slave [TbNumSlaves]; + for (genvar i = 0; i < TbNumSlaves; i++) begin : gen_rand_slave initial begin axi_rand_slave[i] = new( slave_dv[i] ); axi_rand_slave[i].reset(); @@ -219,13 +240,13 @@ module tb_axi_xbar #( initial begin : proc_monitor static tb_axi_xbar_pkg::axi_xbar_monitor #( - .AxiAddrWidth ( AxiAddrWidth ), - .AxiDataWidth ( AxiDataWidth ), - .AxiIdWidthMasters ( AxiIdWidthMasters ), - .AxiIdWidthSlaves ( AxiIdWidthSlaves ), - .AxiUserWidth ( AxiUserWidth ), - .NoMasters ( TbNumMst ), - .NoSlaves ( TbNumSlv ), + .AxiAddrWidth ( TbAxiAddrWidth ), + .AxiDataWidth ( TbAxiDataWidth ), + .AxiIdWidthMasters ( TbAxiIdWidthMasters ), + .AxiIdWidthSlaves ( TbAxiIdWidthSlaves ), + .AxiUserWidth ( TbAxiUserWidth ), + .NoMasters ( TbNumMasters ), + .NoSlaves ( TbNumSlaves ), .NoAddrRules ( xbar_cfg.NoAddrRules ), .rule_t ( rule_t ), .AddrMap ( AddrMap ), @@ -259,9 +280,9 @@ module tb_axi_xbar #( // DUT //----------------------------------- axi_xbar_intf #( - .AXI_USER_WIDTH ( AxiUserWidth ), - .Cfg ( xbar_cfg ), - .rule_t ( rule_t ) + .AXI_USER_WIDTH ( TbAxiUserWidth ), + .Cfg ( xbar_cfg ), + .rule_t ( rule_t ) ) i_xbar_dut ( .clk_i ( clk ), .rst_ni ( rst_n ), @@ -274,7 +295,7 @@ module tb_axi_xbar #( ); // logger for master modules - for (genvar i = 0; i < TbNumMst; i++) begin : gen_master_logger + for (genvar i = 0; i < TbNumMasters; i++) begin : gen_master_logger axi_chan_logger #( .TestTime ( TestTime ), // Time after clock, where sampling happens .LoggerName( $sformatf("axi_logger_master_%0d", i)), @@ -310,7 +331,7 @@ module tb_axi_xbar #( ); end // logger for slave modules - for (genvar i = 0; i < TbNumSlv; i++) begin : gen_slave_logger + for (genvar i = 0; i < TbNumSlaves; i++) begin : gen_slave_logger axi_chan_logger #( .TestTime ( TestTime ), // Time after clock, where sampling happens .LoggerName( $sformatf("axi_logger_slave_%0d",i)), @@ -347,7 +368,7 @@ module tb_axi_xbar #( end - for (genvar i = 0; i < TbNumMst; i++) begin : gen_connect_master_monitor + for (genvar i = 0; i < TbNumMasters; i++) begin : gen_connect_master_monitor assign master_monitor_dv[i].aw_id = master[i].aw_id ; assign master_monitor_dv[i].aw_addr = master[i].aw_addr ; assign master_monitor_dv[i].aw_len = master[i].aw_len ; @@ -394,7 +415,7 @@ module tb_axi_xbar #( assign master_monitor_dv[i].r_valid = master[i].r_valid ; assign master_monitor_dv[i].r_ready = master[i].r_ready ; end - for (genvar i = 0; i < TbNumSlv; i++) begin : gen_connect_slave_monitor + for (genvar i = 0; i < TbNumSlaves; i++) begin : gen_connect_slave_monitor assign slave_monitor_dv[i].aw_id = slave[i].aw_id ; assign slave_monitor_dv[i].aw_addr = slave[i].aw_addr ; assign slave_monitor_dv[i].aw_len = slave[i].aw_len ;