Adding files
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the 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.
|
||||
@@ -0,0 +1,117 @@
|
||||
IBEX_CONFIG ?= small
|
||||
|
||||
FUSESOC_CONFIG_OPTS = $(shell ./util/ibex_config.py $(IBEX_CONFIG) fusesoc_opts)
|
||||
|
||||
all: help
|
||||
|
||||
.PHONY: help
|
||||
help:
|
||||
@echo "This is a short hand for running popular tasks."
|
||||
@echo "Please check the documentation on how to get started"
|
||||
@echo "or how to set-up the different environments."
|
||||
|
||||
# Use a parallel run (make -j N) for a faster build
|
||||
build-all: build-riscv-compliance build-simple-system build-arty-100 \
|
||||
build-csr-test
|
||||
|
||||
|
||||
# RISC-V compliance
|
||||
.PHONY: build-riscv-compliance
|
||||
build-riscv-compliance:
|
||||
fusesoc --cores-root=. run --target=sim --setup --build \
|
||||
lowrisc:ibex:ibex_riscv_compliance \
|
||||
$(FUSESOC_CONFIG_OPTS)
|
||||
|
||||
|
||||
# Simple system
|
||||
# Use the following targets:
|
||||
# - "build-simple-system"
|
||||
# - "run-simple-system"
|
||||
.PHONY: build-simple-system
|
||||
build-simple-system:
|
||||
fusesoc --cores-root=. run --target=sim --setup --build \
|
||||
lowrisc:ibex:ibex_simple_system \
|
||||
$(FUSESOC_CONFIG_OPTS)
|
||||
|
||||
simple-system-program = examples/sw/simple_system/hello_test/hello_test.vmem
|
||||
sw-simple-hello: $(simple-system-program)
|
||||
|
||||
.PHONY: $(simple-system-program)
|
||||
$(simple-system-program):
|
||||
cd examples/sw/simple_system/hello_test && $(MAKE)
|
||||
|
||||
Vibex_simple_system = \
|
||||
build/lowrisc_ibex_ibex_simple_system_0/sim-verilator/Vibex_simple_system
|
||||
$(Vibex_simple_system):
|
||||
@echo "$@ not found"
|
||||
@echo "Run \"make build-simple-system\" to create the dependency"
|
||||
@false
|
||||
|
||||
run-simple-system: sw-simple-hello | $(Vibex_simple_system)
|
||||
build/lowrisc_ibex_ibex_simple_system_0/sim-verilator/Vibex_simple_system \
|
||||
--raminit=$(simple-system-program)
|
||||
|
||||
|
||||
# Arty A7 FPGA example
|
||||
# Use the following targets (depending on your hardware):
|
||||
# - "build-arty-35"
|
||||
# - "build-arty-100"
|
||||
# - "program-arty"
|
||||
arty-sw-program = examples/sw/led/led.vmem
|
||||
sw-led: $(arty-sw-program)
|
||||
|
||||
.PHONY: $(arty-sw-program)
|
||||
$(arty-sw-program):
|
||||
cd examples/sw/led && $(MAKE)
|
||||
|
||||
.PHONY: build-arty-35
|
||||
build-arty-35: sw-led
|
||||
fusesoc --cores-root=. run --target=synth --setup --build \
|
||||
lowrisc:ibex:top_artya7 --part xc7a35ticsg324-1L
|
||||
|
||||
.PHONY: build-arty-100
|
||||
build-arty-100: sw-led
|
||||
fusesoc --cores-root=. run --target=synth --setup --build \
|
||||
lowrisc:ibex:top_artya7 --part xc7a100tcsg324-1
|
||||
|
||||
.PHONY: program-arty
|
||||
program-arty:
|
||||
fusesoc --cores-root=. run --target=synth --run \
|
||||
lowrisc:ibex:top_artya7
|
||||
|
||||
|
||||
# Lint check
|
||||
.PHONY: lint-core-tracing
|
||||
lint-core-tracing:
|
||||
fusesoc --cores-root . run --target=lint lowrisc:ibex:ibex_core_tracing \
|
||||
$(FUSESOC_CONFIG_OPTS)
|
||||
|
||||
|
||||
# CS Registers testbench
|
||||
# Use the following targets:
|
||||
# - "build-csr-test"
|
||||
# - "run-csr-test"
|
||||
.PHONY: build-csr-test
|
||||
build-csr-test:
|
||||
fusesoc --cores-root=. run --target=sim --setup --build \
|
||||
--tool=verilator lowrisc:ibex:tb_cs_registers
|
||||
Vtb_cs_registers = \
|
||||
build/lowrisc_ibex_tb_cs_registers_0/sim-verilator/Vtb_cs_registers
|
||||
$(Vtb_cs_registers):
|
||||
@echo "$@ not found"
|
||||
@echo "Run \"make build-csr-test\" to create the dependency"
|
||||
@false
|
||||
|
||||
.PHONY: run-csr-test
|
||||
run-csr-test: | $(Vtb_cs_registers)
|
||||
fusesoc --cores-root=. run --target=sim --run \
|
||||
--tool=verilator lowrisc:ibex:tb_cs_registers
|
||||
|
||||
# Echo the parameters passed to fusesoc for the chosen IBEX_CONFIG
|
||||
.PHONY: test-cfg
|
||||
test-cfg:
|
||||
@echo $(FUSESOC_CONFIG_OPTS)
|
||||
|
||||
.PHONY: python-lint
|
||||
python-lint:
|
||||
$(MAKE) -C util lint
|
||||
@@ -0,0 +1,129 @@
|
||||
# Copyright lowRISC contributors.
|
||||
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# Azure Pipelines CI build configuration
|
||||
# Documentation at https://aka.ms/yaml
|
||||
|
||||
variables:
|
||||
- template: ci/vars.yml
|
||||
|
||||
trigger:
|
||||
batch: true
|
||||
branches:
|
||||
include:
|
||||
- '*'
|
||||
tags:
|
||||
include:
|
||||
- '*'
|
||||
pr:
|
||||
branches:
|
||||
include:
|
||||
- '*'
|
||||
|
||||
# Note: All tests run as part of one job to avoid copying intermediate build
|
||||
# artifacts around (e.g. Verilator and toolchain builds). Once more builds/tests
|
||||
# are added, we need to re-evaluate this decision to parallelize jobs and
|
||||
# improve end-to-end CI times.
|
||||
|
||||
jobs:
|
||||
- job: lint_dv
|
||||
displayName: Run quality checks (Lint and DV)
|
||||
pool:
|
||||
vmImage: "ubuntu-20.04"
|
||||
steps:
|
||||
- bash: |
|
||||
ci/install-build-deps.sh
|
||||
displayName: Install build dependencies
|
||||
|
||||
- bash: |
|
||||
echo $PATH
|
||||
python3 --version
|
||||
echo -n "fusesoc "
|
||||
fusesoc --version
|
||||
verilator --version
|
||||
riscv32-unknown-elf-gcc --version
|
||||
verible-verilog-lint --version
|
||||
displayName: Display environment
|
||||
|
||||
# Verible format is experimental so only run on default config for now,
|
||||
# will eventually become part of the per-config CI
|
||||
- bash: |
|
||||
fusesoc --cores-root . run --no-export --target=format --tool=veribleformat lowrisc:ibex:ibex_top_tracing
|
||||
if [ $? != 0 ]; then
|
||||
echo -n "##vso[task.logissue type=error]"
|
||||
echo "Verilog format with Verible failed. Run 'fusesoc --cores-root . run --no-export --target=format --tool=veribleformat lowrisc:ibex:ibex_top_tracing' to check and fix all errors."
|
||||
echo "This flow is currently experimental and failures can be ignored."
|
||||
fi
|
||||
# Show diff of what verilog_format would have changed, and then revert.
|
||||
git diff
|
||||
git reset --hard HEAD
|
||||
continueOnError: true
|
||||
displayName: Format all source code with Verible format (experimental)
|
||||
|
||||
- bash: |
|
||||
fork_origin=$(git merge-base --fork-point origin/master)
|
||||
changed_files=$(git diff --name-only $fork_origin | grep -v '^vendor' | grep -E '\.(cpp|cc|c|h)$')
|
||||
test -z "$changed_files" || git diff -U0 $fork_origin $changed_files | clang-format-diff -p1 | tee clang-format-output
|
||||
if [ -s clang-format-output ]; then
|
||||
echo -n "##vso[task.logissue type=error]"
|
||||
echo "C/C++ lint failed. Use 'git clang-format' with appropriate options to reformat the changed code."
|
||||
exit 1
|
||||
fi
|
||||
# This check is not idempotent, but checks changes to a base branch.
|
||||
# Run it only on pull requests.
|
||||
condition: eq(variables['Build.Reason'], 'PullRequest')
|
||||
displayName: 'Use clang-format to check C/C++ coding style'
|
||||
|
||||
- bash: |
|
||||
# Build and run CSR testbench, chosen Ibex configuration does not effect
|
||||
# this so doesn't need to be part of per-config CI
|
||||
fusesoc --cores-root=. run --target=sim --tool=verilator lowrisc:ibex:tb_cs_registers
|
||||
displayName: Build and run CSR testbench with Verilator
|
||||
|
||||
- bash: |
|
||||
cd build
|
||||
git clone https://github.com/riscv/riscv-compliance.git
|
||||
cd riscv-compliance
|
||||
git checkout "$RISCV_COMPLIANCE_GIT_VERSION"
|
||||
displayName: Get RISC-V Compliance test suite
|
||||
|
||||
- bash: |
|
||||
# Build CoreMark without performance counter dump for co-simulation testing
|
||||
make -C ./examples/sw/benchmarks/coremark SUPPRESS_PCOUNT_DUMP=1
|
||||
make -C ./examples/sw/simple_system/pmp_smoke_test
|
||||
make -C ./examples/sw/simple_system/dit_test
|
||||
make -C ./examples/sw/simple_system/dummy_instr_test
|
||||
displayName: Build tests for verilator co-simulation
|
||||
|
||||
# Run Ibex RTL CI per supported configuration
|
||||
- template : ci/ibex-rtl-ci-steps.yml
|
||||
parameters:
|
||||
ibex_configs:
|
||||
# Note: Try to keep the list of configurations in sync with the one used
|
||||
# in Private CI.
|
||||
- small
|
||||
- opentitan
|
||||
- maxperf
|
||||
- maxperf-pmp-bmbalanced
|
||||
- maxperf-pmp-bmfull
|
||||
- experimental-branch-predictor
|
||||
|
||||
# Run lint on simple system
|
||||
- bash: |
|
||||
fusesoc --cores-root . run --target=lint --tool=verilator lowrisc:ibex:ibex_simple_system
|
||||
if [ $? != 0 ]; then
|
||||
echo -n "##vso[task.logissue type=error]"
|
||||
echo "Verilog lint with Verilator failed. Run 'fusesoc --cores-root . run --target=lint --tool=verilator lowrisc:ibex:ibex_simple_system' to check and fix all errors."
|
||||
exit 1
|
||||
fi
|
||||
displayName: Run Verilator lint on simple system
|
||||
|
||||
- bash: |
|
||||
fusesoc --cores-root . run --target=lint --tool=veriblelint lowrisc:ibex:ibex_simple_system
|
||||
if [ $? != 0 ]; then
|
||||
echo -n "##vso[task.logissue type=error]"
|
||||
echo "Verilog lint with Verible failed. Run 'fusesoc --cores-root . run --target=lint --tool=veriblelint lowrisc:ibex:ibex_simple_system' to check and fix all errors."
|
||||
exit 1
|
||||
fi
|
||||
displayName: Run Verible lint on simple system
|
||||
@@ -0,0 +1,31 @@
|
||||
CAPI=2:
|
||||
# Copyright lowRISC contributors.
|
||||
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
name: "lowrisc:tool:check_tool_requirements:0.1"
|
||||
description: "Check tool requirements"
|
||||
|
||||
filesets:
|
||||
files_check_tool_requirements:
|
||||
files:
|
||||
- ./util/check_tool_requirements.py : { copyto: util/check_tool_requirements.py }
|
||||
- ./tool_requirements.py : { copyto: tool_requirements.py }
|
||||
|
||||
scripts:
|
||||
check_tool_requirements:
|
||||
cmd:
|
||||
- python3
|
||||
- util/check_tool_requirements.py
|
||||
# TODO: Use this syntax once https://github.com/olofk/fusesoc/issues/353 is
|
||||
# fixed. Remove the filesets from the default target, and also remove the
|
||||
# copyto.
|
||||
#filesets:
|
||||
# - files_check_tool_requirements
|
||||
|
||||
targets:
|
||||
default:
|
||||
filesets:
|
||||
- files_check_tool_requirements
|
||||
hooks:
|
||||
pre_build:
|
||||
- tool_verilator ? (check_tool_requirements)
|
||||
@@ -0,0 +1,31 @@
|
||||
# Copyright lowRISC contributors.
|
||||
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Private CI trigger. Used to run tooling that can't currently be shared
|
||||
# publicly.
|
||||
|
||||
trigger:
|
||||
batch: true
|
||||
branches:
|
||||
include:
|
||||
- '*'
|
||||
tags:
|
||||
include:
|
||||
- "*"
|
||||
pr:
|
||||
branches:
|
||||
include:
|
||||
- '*'
|
||||
|
||||
# The runner used for private CI enforces the use of the template below. All
|
||||
# build steps need to be placed into the template.
|
||||
resources:
|
||||
repositories:
|
||||
- repository: lowrisc-private-ci
|
||||
type: github
|
||||
endpoint: lowRISC
|
||||
name: lowrisc/lowrisc-private-ci
|
||||
|
||||
extends:
|
||||
template: jobs-ibex.yml@lowrisc-private-ci
|
||||
@@ -0,0 +1,90 @@
|
||||
parameters:
|
||||
ibex_configs: []
|
||||
|
||||
steps:
|
||||
- ${{ each config in parameters.ibex_configs }}:
|
||||
# ibex_config.py will exit with error code 1 on any error which will cause
|
||||
# the CI to fail if there's an issue with the configuration file or an
|
||||
# incorrect configuration name being used
|
||||
- bash: |
|
||||
set -e
|
||||
IBEX_CONFIG_OPTS=`./util/ibex_config.py ${{ config }} fusesoc_opts`
|
||||
echo $IBEX_CONFIG_OPTS
|
||||
echo "##vso[task.setvariable variable=ibex_config_opts]" $IBEX_CONFIG_OPTS
|
||||
displayName: Test and display fusesoc config for ${{ config }}
|
||||
|
||||
- bash: |
|
||||
fusesoc --cores-root . run --target=lint --tool=verilator lowrisc:ibex:ibex_top_tracing $IBEX_CONFIG_OPTS
|
||||
if [ $? != 0 ]; then
|
||||
echo -n "##vso[task.logissue type=error]"
|
||||
echo "Verilog lint failed. Run 'fusesoc --cores-root . run --target=lint --tool=verilator lowrisc:ibex:ibex_top_tracing $IBEX_CONFIG_OPTS' to check and fix all errors."
|
||||
exit 1
|
||||
fi
|
||||
displayName: Lint Verilog source files with Verilator for ${{ config }}
|
||||
|
||||
- bash: |
|
||||
fusesoc --cores-root . run --target=lint --tool=veriblelint lowrisc:ibex:ibex_top_tracing $IBEX_CONFIG_OPTS
|
||||
if [ $? != 0 ]; then
|
||||
echo -n "##vso[task.logissue type=error]"
|
||||
echo "Verilog lint failed. Run 'fusesoc --cores-root . run --target=lint --tool=veriblelint lowrisc:ibex:ibex_top_tracing $IBEX_CONFIG_OPTS' to check and fix all errors."
|
||||
exit 1
|
||||
fi
|
||||
displayName: Lint Verilog source files with Verible Verilog Lint for ${{ config }}
|
||||
|
||||
- bash: |
|
||||
# Build simulation model of Ibex
|
||||
fusesoc --cores-root=. run --target=sim --setup --build lowrisc:ibex:ibex_riscv_compliance $IBEX_CONFIG_OPTS
|
||||
if [ $? != 0 ]; then
|
||||
echo -n "##vso[task.logissue type=error]"
|
||||
echo "Unable to build Verilator model of Ibex for compliance testing."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Run compliance test suite
|
||||
export TARGET_SIM=$PWD/build/lowrisc_ibex_ibex_riscv_compliance_0.1/sim-verilator/Vibex_riscv_compliance
|
||||
export RISCV_PREFIX=riscv32-unknown-elf-
|
||||
export RISCV_TARGET=ibex
|
||||
export RISCV_DEVICE=rv32imc
|
||||
fail=0
|
||||
for isa in rv32i rv32im rv32imc rv32Zicsr rv32Zifencei; do
|
||||
make -C build/riscv-compliance RISCV_ISA=$isa 2>&1 | tee run.log
|
||||
if [ ${PIPESTATUS[0]} != 0 ]; then
|
||||
echo -n "##vso[task.logissue type=error]"
|
||||
echo "The RISC-V compliance test suite failed for $isa"
|
||||
|
||||
# There's no easy way to get the test results in machine-readable
|
||||
# form to properly exclude known-failing tests. Going with an
|
||||
# approximate solution for now.
|
||||
if [ $isa == rv32i ] && grep -q 'FAIL: 4/48' run.log; then
|
||||
echo -n "##vso[task.logissue type=error]"
|
||||
echo "Expected failure for rv32i, see lowrisc/ibex#100 more more information."
|
||||
else
|
||||
fail=1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
exit $fail
|
||||
displayName: Run RISC-V Compliance test for Ibex RV32IMC for ${{ config }}
|
||||
|
||||
- bash: |
|
||||
set -e
|
||||
source ci/setup-cosim.sh
|
||||
# Build simple system with co-simulation
|
||||
fusesoc --cores-root=. run --target=sim --setup --build lowrisc:ibex:ibex_simple_system_cosim $IBEX_CONFIG_OPTS
|
||||
|
||||
# Run directed tests against simple system co-simulation
|
||||
./ci/run-cosim-test.sh --skip-pass-check CoreMark examples/sw/benchmarks/coremark/coremark.elf
|
||||
|
||||
if ./util/ibex_config.py ${{ config }} query_fields PMPEnable | grep -q 'PMPEnable=1'; then
|
||||
./ci/run-cosim-test.sh --skip-pass-check pmp_smoke examples/sw/simple_system/pmp_smoke_test/pmp_smoke_test.elf
|
||||
else
|
||||
echo "PMP not supported on ${{ config }}, skipping pmp_smoke_test"
|
||||
fi
|
||||
|
||||
if ./util/ibex_config.py ${{ config }} query_fields SecureIbex | grep -q 'SecureIbex=1'; then
|
||||
./ci/run-cosim-test.sh dit_test examples/sw/simple_system/dit_test/dit_test.elf
|
||||
./ci/run-cosim-test.sh dummy_instr_test examples/sw/simple_system/dummy_instr_test/dummy_instr_test.elf
|
||||
else
|
||||
echo "Security features not supported on ${{ config }}, skipping security feature tests"
|
||||
fi
|
||||
displayName: Run Verilator co-sim tests for for ${{ config }}
|
||||
@@ -0,0 +1,97 @@
|
||||
#!/bin/bash
|
||||
# Copyright lowRISC contributors.
|
||||
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Install development build dependencies for different Linux distributions
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
[ -f /etc/os-release ] || (echo "/etc/os-release doesn't exist."; exit 1)
|
||||
. /etc/os-release
|
||||
|
||||
[ -n "$VERILATOR_VERSION" ] || (echo "VERILATOR_VERSION must be set."; exit 1)
|
||||
[ -n "$VERIBLE_VERSION" ] || (echo "VERIBLE_VERSION must be set."; exit 1)
|
||||
[ -n "$RISCV_TOOLCHAIN_TAR_VERSION" ] || (echo "RISCV_TOOLCHAIN_TAR_VERSION must be set."; exit 1)
|
||||
[ -n "$RISCV_TOOLCHAIN_TAR_VARIANT" ] || (echo "RISCV_TOOLCHAIN_TAR_VARIANT must be set."; exit 1)
|
||||
|
||||
SUDO_CMD=""
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
SUDO_CMD="sudo "
|
||||
fi
|
||||
|
||||
case "$ID-$VERSION_ID" in
|
||||
ubuntu-16.04|ubuntu-18.04|ubuntu-20.04)
|
||||
# Curl must be available to get the repo key below.
|
||||
$SUDO_CMD apt-get update
|
||||
$SUDO_CMD apt-get install -y curl
|
||||
|
||||
# Packaged dependencies
|
||||
# Install python3-yaml through apt to get a version with libyaml bindings,
|
||||
# which is significantly faster than the pure Python version.
|
||||
$SUDO_CMD apt-get install -y \
|
||||
device-tree-compiler \
|
||||
python3 \
|
||||
python3-pip \
|
||||
python3-setuptools \
|
||||
python3-wheel \
|
||||
python3-yaml \
|
||||
python3-dev \
|
||||
srecord \
|
||||
zlib1g-dev \
|
||||
git \
|
||||
make \
|
||||
autoconf \
|
||||
g++ \
|
||||
flex \
|
||||
bison \
|
||||
libelf-dev \
|
||||
clang-format \
|
||||
wget \
|
||||
xz-utils
|
||||
|
||||
wget https://storage.googleapis.com/ibex-cosim-builds/ibex-cosim-"$IBEX_COSIM_VERSION".tar.gz
|
||||
$SUDO_CMD mkdir -p /tools/riscv-isa-sim
|
||||
$SUDO_CMD chmod 777 /tools/riscv-isa-sim
|
||||
$SUDO_CMD tar -C /tools/riscv-isa-sim -xvzf ibex-cosim-"$IBEX_COSIM_VERSION".tar.gz --strip-components=1
|
||||
echo "##vso[task.prependpath]/tools/riscv-isa-sim/bin"
|
||||
|
||||
wget https://storage.googleapis.com/verilator-builds/verilator-"$VERILATOR_VERSION".tar.gz
|
||||
$SUDO_CMD mkdir -p /tools/verilator
|
||||
$SUDO_CMD chmod 777 /tools/verilator
|
||||
$SUDO_CMD tar -C /tools/verilator -xvzf verilator-"$VERILATOR_VERSION".tar.gz
|
||||
echo "##vso[task.prependpath]/tools/verilator/$VERILATOR_VERSION/bin"
|
||||
# Python dependencies
|
||||
#
|
||||
# Updating pip and setuptools is required to have these tools properly
|
||||
# parse Python-version metadata, which some packages uses to specify that
|
||||
# an older version of a package must be used for a certain Python version.
|
||||
# If that information is not read, pip installs the latest version, which
|
||||
# then fails to run.
|
||||
$SUDO_CMD pip3 install -U pip "setuptools<66.0.0"
|
||||
|
||||
$SUDO_CMD pip3 install -r python-requirements.txt
|
||||
|
||||
# Install Verible
|
||||
mkdir -p build/verible
|
||||
cd build/verible
|
||||
curl -Ls -o verible.tar.gz "https://github.com/google/verible/releases/download/$VERIBLE_VERSION/verible-$VERIBLE_VERSION-Ubuntu-$VERSION_ID-$VERSION_CODENAME-x86_64.tar.gz"
|
||||
$SUDO_CMD mkdir -p /tools/verible && $SUDO_CMD chmod 777 /tools/verible
|
||||
tar -C /tools/verible -xf verible.tar.gz --strip-components=1
|
||||
echo "##vso[task.prependpath]/tools/verible/bin"
|
||||
;;
|
||||
|
||||
*)
|
||||
echo Unknown distribution. Please extend this script!
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Install pre-compiled toolchain (for all distributions)
|
||||
TOOLCHAIN_URL="https://github.com/lowRISC/lowrisc-toolchains/releases/download/$RISCV_TOOLCHAIN_TAR_VERSION/$RISCV_TOOLCHAIN_TAR_VARIANT-$RISCV_TOOLCHAIN_TAR_VERSION.tar.xz"
|
||||
mkdir -p build/toolchain
|
||||
curl -Ls -o build/toolchain/rv32-toolchain.tar.xz "$TOOLCHAIN_URL"
|
||||
$SUDO_CMD mkdir -p /tools/riscv && $SUDO_CMD chmod 777 /tools/riscv
|
||||
tar -C /tools/riscv -xf build/toolchain/rv32-toolchain.tar.xz --strip-components=1
|
||||
echo "##vso[task.prependpath]/tools/riscv/bin"
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
#!/bin/bash
|
||||
# Copyright lowRISC contributors.
|
||||
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Run an elf against simple system co-simulation and check the UART output for
|
||||
# reported pass/fail reporting as appropriate for use in Azure pipelines
|
||||
|
||||
SKIP_PASS_CHECK=0
|
||||
|
||||
if [ $# -eq 3 ]; then
|
||||
if [ $1 == "--skip-pass-check" ]; then
|
||||
SKIP_PASS_CHECK=1
|
||||
fi
|
||||
|
||||
TEST_NAME=$2
|
||||
TEST_ELF=$3
|
||||
elif [ $# -eq 2 ]; then
|
||||
TEST_NAME=$1
|
||||
TEST_ELF=$2
|
||||
else
|
||||
echo "Usage: $0 [--skip-pass-check] test_name test_elf"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Running $TEST_NAME with co-simulation"
|
||||
build/lowrisc_ibex_ibex_simple_system_cosim_0/sim-verilator/Vibex_simple_system --meminit=ram,$TEST_ELF
|
||||
if [ $? != 0 ]; then
|
||||
echo "##vso[task.logissue type=error]Running % failed co-simulation testing"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
grep 'FAILURE' ibex_simple_system.log
|
||||
if [ $? != 1 ]; then
|
||||
echo "##vso[task.logissue type=error]Failure seen in $TEST_NAME log"
|
||||
echo "Log contents:"
|
||||
cat ibex_simple_system.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ $SKIP_PASS_CHECK != 1 ]; then
|
||||
grep 'PASS' ibex_simple_system.log
|
||||
if [ $? != 0 ]; then
|
||||
echo "##vso[task.logissue type=error]No pass seen in $TEST_NAME log"
|
||||
echo "Log contents:"
|
||||
cat ibex_simple_system.log
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "$TEST_NAME succeeded"
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
export PKG_CONFIG_PATH=/tools/riscv-isa-sim/lib/pkgconfig:$PATH
|
||||
@@ -0,0 +1,16 @@
|
||||
# Copyright lowRISC contributors.
|
||||
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# Pipeline variables, used by the public and private CI pipelines
|
||||
# Quote values to ensure they are parsed as string (version numbers might
|
||||
# end up as float otherwise).
|
||||
variables:
|
||||
VERILATOR_VERSION: "v4.104"
|
||||
IBEX_COSIM_VERSION: "15fbd568"
|
||||
RISCV_TOOLCHAIN_TAR_VERSION: "20220210-1"
|
||||
RISCV_TOOLCHAIN_TAR_VARIANT: "lowrisc-toolchain-gcc-rv32imcb"
|
||||
RISCV_COMPLIANCE_GIT_VERSION: "844c6660ef3f0d9b96957991109dfd80cc4938e2"
|
||||
VERIBLE_VERSION: "v0.0-2135-gb534c1fe"
|
||||
# lowRISC-internal version numbers of Ibex-specific Spike builds.
|
||||
SPIKE_IBEX_VERSION: "20220817-git-eccdcb15c3e51b4f7906c7b42fb824f24a4338a2"
|
||||
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright lowRISC contributors.
|
||||
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# Read an Azure Pipelines-compatible variables file, and convert it into
|
||||
# logging commands that Azure Pipelines understands, effectively setting the
|
||||
# variables at runtime.
|
||||
#
|
||||
# This script can be used as a workaround if variables cannot be included in the
|
||||
# Pipeline definition directly.
|
||||
#
|
||||
# See https://docs.microsoft.com/en-us/azure/devops/pipelines/scripts/logging-commands
|
||||
# for more information on logging commands.
|
||||
|
||||
import sys
|
||||
import yaml
|
||||
|
||||
def vars_to_logging_cmd(vars_file):
|
||||
data = {}
|
||||
print(vars_file)
|
||||
with open(vars_file, 'r', encoding="utf-8") as fp:
|
||||
data = yaml.load(fp, Loader=yaml.SafeLoader)
|
||||
|
||||
if not (isinstance(data, dict) and 'variables' in data):
|
||||
print("YAML file wasn't a dictionary with a 'variables' key. Got: {}"
|
||||
.format(data))
|
||||
|
||||
print("Setting variables from {}".format(vars_file))
|
||||
for key, value in data['variables'].items():
|
||||
# Note: These lines won't show up in the Azure Pipelines output unless
|
||||
# "System Diagnostics" are enabled (go to the Azure Pipelines web UI,
|
||||
# click on "Run pipeline" to manually run a pipeline, and check "Enable
|
||||
# system diagnostics".)
|
||||
print("##vso[task.setvariable variable={}]{}".format(key, value))
|
||||
|
||||
return 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: {} VARS_FILE".format(sys.argv[0]))
|
||||
sys.exit(1)
|
||||
sys.exit(vars_to_logging_cmd(sys.argv[1]))
|
||||
@@ -0,0 +1,19 @@
|
||||
CAPI=2:
|
||||
# Copyright lowRISC contributors.
|
||||
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
name: "lowrisc:dv:cosim"
|
||||
description: "Co-simulator framework"
|
||||
filesets:
|
||||
files_cpp:
|
||||
files:
|
||||
- cosim.h: { is_include_file: true }
|
||||
- spike_cosim.cc
|
||||
- spike_cosim.h: { is_include_file: true }
|
||||
file_type: cppSource
|
||||
|
||||
targets:
|
||||
default:
|
||||
filesets:
|
||||
- files_cpp
|
||||
@@ -0,0 +1,154 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifndef COSIM_H_
|
||||
#define COSIM_H_
|
||||
|
||||
// Information about a dside transaction observed on the DUT memory interface
|
||||
struct DSideAccessInfo {
|
||||
// set when the access is a store, in which case `data` is the store data from
|
||||
// the DUT. Otherwise `data` is the load data provided to the DUT.
|
||||
bool store;
|
||||
// `addr` is the address and must be 32-bit aligned. `data` and `be` are
|
||||
// aligned to the address. For example an 8-bit store of 0xff to 0x12 has
|
||||
// `data` as 0x00ff0000, `addr` as 0x10 and `be` as 0b0100.
|
||||
uint32_t data;
|
||||
uint32_t addr;
|
||||
uint32_t be;
|
||||
|
||||
// set if an error response to the transaction is seen.
|
||||
bool error;
|
||||
|
||||
// `misaligned_first` and `misaligned_second` are set when the transaction is
|
||||
// generated for a misaligned store or load instruction. `misaligned_first`
|
||||
// is true when the transaction is for the lower half and `misaligned_second`
|
||||
// is true when the transaction is for the upper half, if it exists.
|
||||
//
|
||||
// For example an unaligned 32-bit load to 0x3 produces a transaction with
|
||||
// `addr` as 0x0 and `misaligned_first` set to true, then a transaction with
|
||||
// `addr` as 0x4 and `misaligned_second` set to true. An unaligned 16-bit load
|
||||
// to 0x01 only produces a transaction with `addr` as 0x0 and
|
||||
// `misaligned_first` set to true, there is no second half.
|
||||
bool misaligned_first;
|
||||
bool misaligned_second;
|
||||
};
|
||||
|
||||
class Cosim {
|
||||
public:
|
||||
virtual ~Cosim() {}
|
||||
|
||||
// Add a memory to the co-simulator environment.
|
||||
//
|
||||
// Use `backdoor_write_mem`/`backdoor_read_mem` to access it from the
|
||||
// simulation environment.
|
||||
virtual void add_memory(uint32_t base_addr, size_t size) = 0;
|
||||
|
||||
// Write bytes to co-simulator memory.
|
||||
//
|
||||
// returns false if write fails (e.g. because no memory exists at the bytes
|
||||
// being written).
|
||||
virtual bool backdoor_write_mem(uint32_t addr, size_t len,
|
||||
const uint8_t *data_in) = 0;
|
||||
|
||||
// Read bytes from co-simulator memory.
|
||||
//
|
||||
// returns false if read fails (e.g. because no memory exists at the bytes
|
||||
// being read).
|
||||
virtual bool backdoor_read_mem(uint32_t addr, size_t len,
|
||||
uint8_t *data_out) = 0;
|
||||
|
||||
// Step the co-simulator, checking register write and PC of executed
|
||||
// instruction match the supplied values. `write_reg` gives the index of the
|
||||
// written register along with `write_reg_data` which provides the data. A
|
||||
// `write_reg` of 0 indicates no register write occurred.
|
||||
//
|
||||
// `sync_trap` is set to true when the instruction caused a synchronous trap.
|
||||
// In this case the instruction doesn't retire so no register write occurs (so
|
||||
// `write_reg` must be 0).
|
||||
//
|
||||
// Returns false if there are any errors; use `get_errors` to obtain details
|
||||
virtual bool step(uint32_t write_reg, uint32_t write_reg_data, uint32_t pc,
|
||||
bool sync_trap, bool suppress_reg_write) = 0;
|
||||
|
||||
// When more than one of `set_mip`, `set_nmi` or `set_debug_req` is called
|
||||
// before `step` which one takes effect is chosen by the co-simulator. Which
|
||||
// should take priority is architecturally defined by the RISC-V
|
||||
// specification.
|
||||
|
||||
// Set the value of MIP.
|
||||
//
|
||||
// At the next call of `step`, the MIP value will take effect (i.e. if it's a
|
||||
// new interrupt that is enabled it will step straight to that handler).
|
||||
virtual void set_mip(uint32_t mip) = 0;
|
||||
|
||||
// Set the state of the NMI (non-maskable interrupt) line.
|
||||
//
|
||||
// The NMI signal is a level triggered interrupt. When the NMI is taken the
|
||||
// CPU ignores the NMI line until an `mret` instruction is executed. If the
|
||||
// NMI is high following the `mret` (regardless of whether it has been low or
|
||||
// not whilst the first NMI is being handled) a new NMI is taken.
|
||||
//
|
||||
// When an NMI is due to be taken that will occur at the next call of `step`.
|
||||
virtual void set_nmi(bool nmi) = 0;
|
||||
|
||||
// Set the state of the internal NMI (non-maskable interrupt) line.
|
||||
// Behaviour wise this is almost as same as external NMI case explained at
|
||||
// set_nmi method. Difference is that this one is a response from Ibex rather
|
||||
// than an input.
|
||||
virtual void set_nmi_int(bool nmi_int) = 0;
|
||||
|
||||
// Set the debug request.
|
||||
//
|
||||
// When set to true the core will enter debug mode at the next step
|
||||
virtual void set_debug_req(bool debug_req) = 0;
|
||||
|
||||
// Set the value of mcycle.
|
||||
//
|
||||
// The co-simulation model doesn't alter the value of mcycle itself (other
|
||||
// than instructions that do a direct CSR write). mcycle should be set to the
|
||||
// correct value before any `step` call that may execute an instruction that
|
||||
// observes the value of mcycle.
|
||||
//
|
||||
// A full 64-bit value is provided setting both the mcycle and mcycleh CSRs.
|
||||
virtual void set_mcycle(uint64_t mcycle) = 0;
|
||||
|
||||
// Set the value of a CSR. This is used when it is needed to have direct
|
||||
// communication between DUT and Spike (e.g. Performance counters).
|
||||
virtual void set_csr(const int csr_num, const uint32_t new_val) = 0;
|
||||
|
||||
// Set the ICache scramble key valid bit that is visible in CPUCTRLSTS.
|
||||
virtual void set_ic_scr_key_valid(bool valid) = 0;
|
||||
|
||||
// Tell the co-simulation model about observed transactions on the dside
|
||||
// memory interface of the DUT. Accesses are notified once the response to a
|
||||
// transaction is seen.
|
||||
//
|
||||
// Observed transactions for the DUT are checked against accesses from the
|
||||
// co-simulation model when a memory access occurs during a `step`. If there
|
||||
// is a mismatch an error is reported which can be obtained via `get_errors`.
|
||||
virtual void notify_dside_access(const DSideAccessInfo &access_info) = 0;
|
||||
|
||||
// Tell the co-simulation model about an error response to an iside fetch.
|
||||
//
|
||||
// `addr` must be 32-bit aligned.
|
||||
//
|
||||
// The next step following a call to `set_iside_error` must produce an
|
||||
// instruction fault at the given address.
|
||||
virtual void set_iside_error(uint32_t addr) = 0;
|
||||
|
||||
// Get a vector of strings describing errors that have occurred during `step`
|
||||
virtual const std::vector<std::string> &get_errors() = 0;
|
||||
|
||||
// Clear internal vector of error descriptions
|
||||
virtual void clear_errors() = 0;
|
||||
|
||||
// Returns a count of instructions executed by co-simulator and DUT without
|
||||
// failures.
|
||||
virtual unsigned int get_insn_cnt() = 0;
|
||||
};
|
||||
|
||||
#endif // COSIM_H_
|
||||
@@ -0,0 +1,121 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include <svdpi.h>
|
||||
#include <cassert>
|
||||
|
||||
#include "cosim.h"
|
||||
#include "cosim_dpi.h"
|
||||
|
||||
int riscv_cosim_step(Cosim *cosim, const svBitVecVal *write_reg,
|
||||
const svBitVecVal *write_reg_data, const svBitVecVal *pc,
|
||||
svBit sync_trap, svBit suppress_reg_write) {
|
||||
assert(cosim);
|
||||
|
||||
return cosim->step(write_reg[0], write_reg_data[0], pc[0], sync_trap,
|
||||
suppress_reg_write)
|
||||
? 1
|
||||
: 0;
|
||||
}
|
||||
|
||||
void riscv_cosim_set_mip(Cosim *cosim, const svBitVecVal *mip) {
|
||||
assert(cosim);
|
||||
|
||||
cosim->set_mip(mip[0]);
|
||||
}
|
||||
|
||||
void riscv_cosim_set_nmi(Cosim *cosim, svBit nmi) {
|
||||
assert(cosim);
|
||||
|
||||
cosim->set_nmi(nmi);
|
||||
}
|
||||
|
||||
void riscv_cosim_set_nmi_int(Cosim *cosim, svBit nmi_int) {
|
||||
assert(cosim);
|
||||
|
||||
cosim->set_nmi_int(nmi_int);
|
||||
}
|
||||
void riscv_cosim_set_debug_req(Cosim *cosim, svBit debug_req) {
|
||||
assert(cosim);
|
||||
|
||||
cosim->set_debug_req(debug_req);
|
||||
}
|
||||
|
||||
void riscv_cosim_set_mcycle(Cosim *cosim, svBitVecVal *mcycle) {
|
||||
assert(cosim);
|
||||
|
||||
uint64_t mcycle_full = mcycle[0] | (uint64_t)mcycle[1] << 32;
|
||||
cosim->set_mcycle(mcycle_full);
|
||||
}
|
||||
|
||||
void riscv_cosim_set_csr(Cosim *cosim, const int csr_id,
|
||||
const svBitVecVal *csr_val) {
|
||||
assert(cosim);
|
||||
|
||||
cosim->set_csr(csr_id, (uint32_t)csr_val[0]);
|
||||
}
|
||||
|
||||
void riscv_cosim_set_ic_scr_key_valid(Cosim *cosim, svBit valid) {
|
||||
assert(cosim);
|
||||
|
||||
cosim->set_ic_scr_key_valid(valid);
|
||||
}
|
||||
|
||||
void riscv_cosim_notify_dside_access(Cosim *cosim, svBit store,
|
||||
svBitVecVal *addr, svBitVecVal *data,
|
||||
svBitVecVal *be, svBit error,
|
||||
svBit misaligned_first,
|
||||
svBit misaligned_second) {
|
||||
assert(cosim);
|
||||
|
||||
cosim->notify_dside_access(
|
||||
DSideAccessInfo{.store = store != 0,
|
||||
.data = data[0],
|
||||
.addr = addr[0],
|
||||
.be = be[0],
|
||||
.error = error != 0,
|
||||
.misaligned_first = misaligned_first != 0,
|
||||
.misaligned_second = misaligned_second != 0});
|
||||
}
|
||||
|
||||
void riscv_cosim_set_iside_error(Cosim *cosim, svBitVecVal *addr) {
|
||||
assert(cosim);
|
||||
|
||||
cosim->set_iside_error(addr[0]);
|
||||
}
|
||||
|
||||
int riscv_cosim_get_num_errors(Cosim *cosim) {
|
||||
assert(cosim);
|
||||
|
||||
return cosim->get_errors().size();
|
||||
}
|
||||
|
||||
const char *riscv_cosim_get_error(Cosim *cosim, int index) {
|
||||
assert(cosim);
|
||||
|
||||
if (index >= cosim->get_errors().size()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return cosim->get_errors()[index].c_str();
|
||||
}
|
||||
|
||||
void riscv_cosim_clear_errors(Cosim *cosim) {
|
||||
assert(cosim);
|
||||
|
||||
cosim->clear_errors();
|
||||
}
|
||||
|
||||
void riscv_cosim_write_mem_byte(Cosim *cosim, const svBitVecVal *addr,
|
||||
const svBitVecVal *d) {
|
||||
assert(cosim);
|
||||
uint8_t byte = d[0] & 0xff;
|
||||
cosim->backdoor_write_mem(addr[0], 1, &byte);
|
||||
}
|
||||
|
||||
unsigned int riscv_cosim_get_insn_cnt(Cosim *cosim) {
|
||||
assert(cosim);
|
||||
|
||||
return cosim->get_insn_cnt();
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
CAPI=2:
|
||||
# Copyright lowRISC contributors.
|
||||
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
name: "lowrisc:dv:cosim_dpi"
|
||||
description: "DPI wrapper for Co-simulator framework"
|
||||
filesets:
|
||||
files_cpp:
|
||||
depend:
|
||||
- lowrisc:dv:cosim
|
||||
files:
|
||||
- cosim_dpi.cc: { file_type: cppSource }
|
||||
- cosim_dpi.h: { file_type: cppSource, is_include_file: true }
|
||||
- cosim_dpi.svh: {file_type: systemVerilogSource }
|
||||
|
||||
targets:
|
||||
default:
|
||||
filesets:
|
||||
- files_cpp
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#ifndef COSIM_DPI_H_
|
||||
#define COSIM_DPI_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <svdpi.h>
|
||||
|
||||
// This adapts the C++ interface of the `Cosim` class to be used via DPI. See
|
||||
// the documentation in cosim.h for further details
|
||||
|
||||
extern "C" {
|
||||
int riscv_cosim_step(Cosim *cosim, const svBitVecVal *write_reg,
|
||||
const svBitVecVal *write_reg_data, const svBitVecVal *pc,
|
||||
svBit sync_trap, svBit suppress_reg_write);
|
||||
void riscv_cosim_set_mip(Cosim *cosim, const svBitVecVal *mip);
|
||||
void riscv_cosim_set_nmi(Cosim *cosim, svBit nmi);
|
||||
void riscv_cosim_set_nmi_int(Cosim *cosim, svBit nmi_int);
|
||||
void riscv_cosim_set_debug_req(Cosim *cosim, svBit debug_req);
|
||||
void riscv_cosim_set_mcycle(Cosim *cosim, svBitVecVal *mcycle);
|
||||
void riscv_cosim_set_csr(Cosim *cosim, const int csr_id,
|
||||
const svBitVecVal *csr_val);
|
||||
void riscv_cosim_set_ic_scr_key_valid(Cosim *cosim, svBit valid);
|
||||
void riscv_cosim_notify_dside_access(Cosim *cosim, svBit store,
|
||||
svBitVecVal *addr, svBitVecVal *data,
|
||||
svBitVecVal *be, svBit error,
|
||||
svBit misaligned_first,
|
||||
svBit misaligned_second);
|
||||
void riscv_cosim_set_iside_error(Cosim *cosim, svBitVecVal *addr);
|
||||
int riscv_cosim_get_num_errors(Cosim *cosim);
|
||||
const char *riscv_cosim_get_error(Cosim *cosim, int index);
|
||||
void riscv_cosim_clear_errors(Cosim *cosim);
|
||||
void riscv_cosim_write_mem_byte(Cosim *cosim, const svBitVecVal *addr,
|
||||
const svBitVecVal *d);
|
||||
unsigned int riscv_cosim_get_insn_cnt(Cosim *cosim);
|
||||
}
|
||||
|
||||
#endif // COSIM_DPI_H_
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// DPI interface to co-simulation model, see `cosim.h` for the interface description.
|
||||
|
||||
// Implemented as a header file as VCS needs `import` declarations included in each verilog file
|
||||
// that uses them.
|
||||
|
||||
`ifndef COSIM_DPI_SVH
|
||||
`define COSIM_DPI_SVH
|
||||
|
||||
import "DPI-C" function int riscv_cosim_step(chandle cosim_handle, bit [4:0] write_reg,
|
||||
bit [31:0] write_reg_data, bit [31:0] pc, bit sync_trap, bit suppress_reg_write);
|
||||
import "DPI-C" function void riscv_cosim_set_mip(chandle cosim_handle, bit [31:0] mip);
|
||||
import "DPI-C" function void riscv_cosim_set_nmi(chandle cosim_handle, bit nmi);
|
||||
import "DPI-C" function void riscv_cosim_set_nmi_int(chandle cosim_handle, bit nmi_int);
|
||||
import "DPI-C" function void riscv_cosim_set_debug_req(chandle cosim_handle, bit debug_req);
|
||||
import "DPI-C" function void riscv_cosim_set_mcycle(chandle cosim_handle, bit [63:0] mcycle);
|
||||
import "DPI-C" function void riscv_cosim_set_csr(chandle cosim_handle, int csr_id,
|
||||
bit [31:0] csr_val);
|
||||
import "DPI-C" function void riscv_cosim_set_ic_scr_key_valid(chandle cosim_handle, bit valid);
|
||||
import "DPI-C" function void riscv_cosim_notify_dside_access(chandle cosim_handle, bit store,
|
||||
bit [31:0] addr, bit [31:0] data, bit [3:0] be, bit error, bit misaligned_first,
|
||||
bit misaligned_second);
|
||||
import "DPI-C" function int riscv_cosim_set_iside_error(chandle cosim_handle, bit [31:0] addr);
|
||||
import "DPI-C" function int riscv_cosim_get_num_errors(chandle cosim_handle);
|
||||
import "DPI-C" function string riscv_cosim_get_error(chandle cosim_handle, int index);
|
||||
import "DPI-C" function void riscv_cosim_clear_errors(chandle cosim_handle);
|
||||
import "DPI-C" function void riscv_cosim_write_mem_byte(chandle cosim_handle, bit [31:0] addr,
|
||||
bit [7:0] d);
|
||||
import "DPI-C" function int unsigned riscv_cosim_get_insn_cnt(chandle cosim_handle);
|
||||
|
||||
`endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,133 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#ifndef SPIKE_COSIM_H_
|
||||
#define SPIKE_COSIM_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "cosim.h"
|
||||
#include "riscv/devices.h"
|
||||
#include "riscv/log_file.h"
|
||||
#include "riscv/processor.h"
|
||||
#include "riscv/simif.h"
|
||||
|
||||
#define IBEX_MARCHID 22
|
||||
|
||||
class SpikeCosim : public simif_t, public Cosim {
|
||||
private:
|
||||
std::unique_ptr<isa_parser_t> isa_parser;
|
||||
std::unique_ptr<processor_t> processor;
|
||||
std::unique_ptr<log_file_t> log;
|
||||
bus_t bus;
|
||||
std::vector<std::unique_ptr<mem_t>> mems;
|
||||
std::vector<std::string> errors;
|
||||
bool nmi_mode;
|
||||
|
||||
typedef struct {
|
||||
uint8_t mpp;
|
||||
bool mpie;
|
||||
uint32_t epc;
|
||||
uint32_t cause;
|
||||
} mstack_t;
|
||||
|
||||
mstack_t mstack;
|
||||
|
||||
void fixup_csr(int csr_num, uint32_t csr_val);
|
||||
|
||||
struct PendingMemAccess {
|
||||
DSideAccessInfo dut_access_info;
|
||||
uint32_t be_spike;
|
||||
};
|
||||
|
||||
std::vector<PendingMemAccess> pending_dside_accesses;
|
||||
|
||||
bool pending_iside_error;
|
||||
uint32_t pending_iside_err_addr;
|
||||
|
||||
typedef enum {
|
||||
kCheckMemOk, // Checks passed and access succeded in RTL
|
||||
kCheckMemCheckFailed, // Checks failed
|
||||
kCheckMemBusError // Checks passed, but access generated bus error in RTL
|
||||
} check_mem_result_e;
|
||||
|
||||
check_mem_result_e check_mem_access(bool store, uint32_t addr, size_t len,
|
||||
const uint8_t *bytes);
|
||||
|
||||
bool pc_is_mret(uint32_t pc);
|
||||
bool pc_is_load(uint32_t pc, uint32_t &rd_out);
|
||||
|
||||
bool pc_is_debug_ebreak(uint32_t pc);
|
||||
bool check_debug_ebreak(uint32_t write_reg, uint32_t pc, bool sync_trap);
|
||||
|
||||
bool check_gpr_write(const commit_log_reg_t::value_type ®_change,
|
||||
uint32_t write_reg, uint32_t write_reg_data);
|
||||
|
||||
bool check_suppress_reg_write(uint32_t write_reg, uint32_t pc,
|
||||
uint32_t &suppressed_write_reg);
|
||||
|
||||
void on_csr_write(const commit_log_reg_t::value_type ®_change);
|
||||
|
||||
void leave_nmi_mode();
|
||||
|
||||
bool change_cpuctrlsts_sync_exc_seen(bool flag);
|
||||
void set_cpuctrlsts_double_fault_seen();
|
||||
void handle_cpuctrl_exception_entry();
|
||||
|
||||
void initial_proc_setup(uint32_t start_pc, uint32_t start_mtvec,
|
||||
uint32_t mhpm_counter_num);
|
||||
|
||||
unsigned int insn_cnt;
|
||||
|
||||
public:
|
||||
SpikeCosim(const std::string &isa_string, uint32_t start_pc,
|
||||
uint32_t start_mtvec, const std::string &trace_log_path,
|
||||
bool secure_ibex, bool icache_en, uint32_t pmp_num_regions,
|
||||
uint32_t pmp_granularity, uint32_t mhpm_counter_num);
|
||||
|
||||
// simif_t implementation
|
||||
virtual char *addr_to_mem(reg_t addr) override;
|
||||
virtual bool mmio_load(reg_t addr, size_t len, uint8_t *bytes) override;
|
||||
virtual bool mmio_store(reg_t addr, size_t len,
|
||||
const uint8_t *bytes) override;
|
||||
virtual void proc_reset(unsigned id) override;
|
||||
virtual const char *get_symbol(uint64_t addr) override;
|
||||
|
||||
// Cosim implementation
|
||||
void add_memory(uint32_t base_addr, size_t size) override;
|
||||
bool backdoor_write_mem(uint32_t addr, size_t len,
|
||||
const uint8_t *data_in) override;
|
||||
bool backdoor_read_mem(uint32_t addr, size_t len, uint8_t *data_out) override;
|
||||
bool step(uint32_t write_reg, uint32_t write_reg_data, uint32_t pc,
|
||||
bool sync_trap, bool suppress_reg_write) override;
|
||||
|
||||
bool check_retired_instr(uint32_t write_reg, uint32_t write_reg_data,
|
||||
uint32_t dut_pc, bool suppress_reg_write);
|
||||
bool check_sync_trap(uint32_t write_reg, uint32_t pc,
|
||||
uint32_t initial_spike_pc);
|
||||
void set_mip(uint32_t mip) override;
|
||||
void set_nmi(bool nmi) override;
|
||||
void set_nmi_int(bool nmi_int) override;
|
||||
void set_debug_req(bool debug_req) override;
|
||||
void set_mcycle(uint64_t mcycle) override;
|
||||
void set_csr(const int csr_num, const uint32_t new_val) override;
|
||||
void set_ic_scr_key_valid(bool valid) override;
|
||||
void notify_dside_access(const DSideAccessInfo &access_info) override;
|
||||
// The spike co-simulator assumes iside and dside accesses within a step are
|
||||
// disjoint. If both access the same address within a step memory faults may
|
||||
// be incorrectly cause on one rather than the other or the access checking
|
||||
// will break.
|
||||
// TODO: Work on spike changes to remove this restriction
|
||||
void set_iside_error(uint32_t addr) override;
|
||||
const std::vector<std::string> &get_errors() override;
|
||||
void clear_errors() override;
|
||||
unsigned int get_insn_cnt() override;
|
||||
};
|
||||
|
||||
#endif // SPIKE_COSIM_H_
|
||||
@@ -0,0 +1,74 @@
|
||||
# Copyright lowRISC contributors.
|
||||
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
TOOL = verilator
|
||||
|
||||
SO_NAME = reg_dpi.so
|
||||
|
||||
BUILDDIR = build
|
||||
SRCEXT = cc
|
||||
OBJDIR = $(BUILDDIR)/obj
|
||||
BINDIR = $(BUILDDIR)/bin
|
||||
|
||||
SRCS := $(shell find . -name '*.$(SRCEXT)' ! -path './tb/*' ! -path './build/*')
|
||||
SRCDIRS := $(shell find . -name '*.$(SRCEXT)' ! -name 'tb*' ! -name 'build*' -exec dirname {} \; | uniq)
|
||||
OBJS := $(patsubst %.$(SRCEXT),$(OBJDIR)/%.o,$(SRCS))
|
||||
|
||||
DEBUG = -g
|
||||
INCLUDES = -I./env -I./rst_driver -I./reg_driver -I./model
|
||||
CFLAGS = -std=c++14 -m64 -fPIC -Wall -pedantic $(DEBUG) $(INCLUDES)
|
||||
LDFLAGS = -shared
|
||||
|
||||
CC = $(CXX)
|
||||
|
||||
# Add svdpi include
|
||||
TOOLDIR = $(subst bin/$(TOOL),,$(shell which $(TOOL)))
|
||||
ifeq ($(TOOL),vcs)
|
||||
INCLUDES += -I$(TOOLDIR)include
|
||||
else
|
||||
INCLUDES += -I$(TOOLDIR)share/verilator/include/vltstd
|
||||
endif
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: build
|
||||
|
||||
build: $(BINDIR)/$(SO_NAME)
|
||||
@echo "Build done"
|
||||
|
||||
$(BINDIR)/$(SO_NAME): buildrepo $(OBJS)
|
||||
@mkdir -p `dirname $@`
|
||||
@echo "Linking $@..."
|
||||
@$(CC) $(OBJS) $(LDFLAGS) -o $@
|
||||
|
||||
$(OBJDIR)/%.o: %.$(SRCEXT)
|
||||
@echo "Generating dependencies for $<..."
|
||||
@$(call make-depend,$<,$@,$(subst .o,.d,$@))
|
||||
@echo "Compiling $<..."
|
||||
@$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
clean:
|
||||
$(RM) -r $(BUILDDIR)
|
||||
|
||||
buildrepo:
|
||||
@$(call make-repo)
|
||||
|
||||
define make-repo
|
||||
for dir in $(SRCDIRS); \
|
||||
do \
|
||||
mkdir -p $(OBJDIR)/$$dir; \
|
||||
done
|
||||
endef
|
||||
|
||||
|
||||
# usage: $(call make-depend,source-file,object-file,depend-file)
|
||||
define make-depend
|
||||
$(CC) -MM \
|
||||
-MF $3 \
|
||||
-MP \
|
||||
-MT $2 \
|
||||
$(CFLAGS) \
|
||||
$1
|
||||
endef
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
Ibex simulation Control/Status Registers Testbench
|
||||
==================================================
|
||||
|
||||
This directory contains a basic sample testbench in C++ for testing correctness of some CS registers implemented in Ibex.
|
||||
|
||||
It is a work in progress and only tests a handful of registers, and is missing many features.
|
||||
|
||||
How to build and run the testbench
|
||||
----------------------------------
|
||||
|
||||
Verilator version:
|
||||
|
||||
```sh
|
||||
fusesoc --cores-root=. run --target=sim --tool=verilator lowrisc:ibex:tb_cs_registers
|
||||
```
|
||||
VCS version:
|
||||
|
||||
```sh
|
||||
fusesoc --cores-root=. run --target=sim --tool=vcs lowrisc:ibex:tb_cs_registers
|
||||
```
|
||||
|
||||
Testbench file structure
|
||||
------------------------
|
||||
|
||||
`tb/tb_cs_registers.sv` - Is the verilog top level, it instantiates the DUT and DPI calls
|
||||
|
||||
`tb/tb_cs_registers.cc` - Is the C++ top level, it sets up the testbench components
|
||||
|
||||
`driver/` - Contains components to generate and drive random register transactions
|
||||
|
||||
`env/` - Contains components to generate and drive other signals
|
||||
|
||||
`model/` - Contains a model of the register state and checking functions
|
||||
|
||||
DPI methodology
|
||||
---------------
|
||||
|
||||
The testbench relies on DPI calls to interface between the RTL and the C++ driver/checker components.
|
||||
This methodology allows the testbench to support both Verilator and commercial simulators.
|
||||
|
||||
Each DPI call has a function in SV (which is called in the SV top-level), and a corresponding C function.
|
||||
To support the instantiation of multiple instances of TB components, some DPI modules can register their objects referenced by name.
|
||||
Each DPI call from the RTL then calls into the correct instance by matching the name.
|
||||
|
||||
Testbench structure
|
||||
-------------------
|
||||
|
||||
The driver component contains one DPI "interface" to drive new random transactions into the DUT, and another to monitor issued transactions including the DUT's responses.
|
||||
Each time a new transaction is detected by the monitor component, it is captured and sent to the model.
|
||||
The model reads incoming transactions, updates its internal state and checks that the DUT outputs matches its own predictions.
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "register_environment.h"
|
||||
#include "register_types.h"
|
||||
|
||||
#include "svdpi.h"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
RegisterEnvironment *reg_env;
|
||||
|
||||
void env_initial(svBitVecVal *seed, svBit PMPEnable,
|
||||
svBitVecVal *PMPGranularity, svBitVecVal *PMPNumRegions,
|
||||
svBitVecVal *MHPMCounterNum, svBitVecVal *MHPMCounterWidth) {
|
||||
// Package up parameters
|
||||
CSRParams params;
|
||||
params.PMPEnable = PMPEnable;
|
||||
params.PMPGranularity = *PMPGranularity;
|
||||
params.PMPNumRegions = *PMPNumRegions;
|
||||
params.MHPMCounterNum = *MHPMCounterNum;
|
||||
params.MHPMCounterWidth = *MHPMCounterWidth;
|
||||
// Create TB environment
|
||||
reg_env = new RegisterEnvironment(params);
|
||||
|
||||
// Initial setup
|
||||
reg_env->OnInitial(*seed);
|
||||
}
|
||||
|
||||
void env_final() {
|
||||
reg_env->OnFinal();
|
||||
delete reg_env;
|
||||
}
|
||||
|
||||
void env_tick(svBit *stop_req, svBit *test_passed) {
|
||||
reg_env->GetStopReq(stop_req);
|
||||
reg_env->GetTestPass(test_passed);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package env_dpi;
|
||||
|
||||
import "DPI-C"
|
||||
function void env_initial(input bit [31:0] seed,
|
||||
input bit PMPEnable,
|
||||
input bit [31:0] PMPGranularity,
|
||||
input bit [31:0] PMPNumRegions,
|
||||
input bit [31:0] MHPMCounterNum,
|
||||
input bit [31:0] MHPMCounterWidth);
|
||||
|
||||
import "DPI-C"
|
||||
function void env_final();
|
||||
|
||||
import "DPI-C"
|
||||
function void env_tick(
|
||||
output bit stop_req,
|
||||
output bit test_passed);
|
||||
|
||||
endpackage
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "register_environment.h"
|
||||
|
||||
RegisterEnvironment::RegisterEnvironment(CSRParams params)
|
||||
: params_(params),
|
||||
simctrl_(new SimCtrl()),
|
||||
reg_model_(new RegisterModel(simctrl_, ¶ms_)),
|
||||
reg_driver_(new RegisterDriver("reg_driver", reg_model_, simctrl_)),
|
||||
rst_driver_(new ResetDriver("rstn_driver")) {}
|
||||
|
||||
void RegisterEnvironment::OnInitial(unsigned int seed) {
|
||||
rst_driver_->OnInitial(seed);
|
||||
reg_driver_->OnInitial(seed);
|
||||
}
|
||||
|
||||
void RegisterEnvironment::OnFinal() {
|
||||
reg_driver_->OnFinal();
|
||||
rst_driver_->OnFinal();
|
||||
simctrl_->OnFinal();
|
||||
delete rst_driver_;
|
||||
delete reg_driver_;
|
||||
delete reg_model_;
|
||||
delete simctrl_;
|
||||
}
|
||||
|
||||
void RegisterEnvironment::GetStopReq(unsigned char *stop_req) {
|
||||
*stop_req = simctrl_->StopRequested();
|
||||
}
|
||||
|
||||
void RegisterEnvironment::GetTestPass(unsigned char *test_passed) {
|
||||
*test_passed = simctrl_->TestPassed();
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#ifndef REGISTER_ENVIRONMENT_H_
|
||||
#define REGISTER_ENVIRONMENT_H_
|
||||
|
||||
#include "register_driver.h"
|
||||
#include "register_model.h"
|
||||
#include "register_types.h"
|
||||
#include "reset_driver.h"
|
||||
#include "simctrl.h"
|
||||
|
||||
/**
|
||||
* Class to instantiate all tb components
|
||||
*/
|
||||
class RegisterEnvironment {
|
||||
public:
|
||||
RegisterEnvironment(CSRParams params);
|
||||
|
||||
void OnInitial(unsigned int seed);
|
||||
void OnFinal();
|
||||
|
||||
void GetStopReq(unsigned char *stop_req);
|
||||
|
||||
void GetTestPass(unsigned char *test_passed);
|
||||
|
||||
private:
|
||||
CSRParams params_;
|
||||
SimCtrl *simctrl_;
|
||||
RegisterModel *reg_model_;
|
||||
RegisterDriver *reg_driver_;
|
||||
ResetDriver *rst_driver_;
|
||||
};
|
||||
|
||||
#endif // REGISTER_ENVIRONMENT_H_
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#ifndef REGISTER_TYPES_H_
|
||||
#define REGISTER_TYPES_H_
|
||||
|
||||
struct CSRParams {
|
||||
bool PMPEnable;
|
||||
unsigned int PMPGranularity;
|
||||
unsigned int PMPNumRegions;
|
||||
unsigned int MHPMCounterNum;
|
||||
unsigned int MHPMCounterWidth;
|
||||
};
|
||||
|
||||
#endif // REGISTER_TYPES_H_
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "simctrl.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
SimCtrl::SimCtrl() : stop_requested_(false), success_(true) {}
|
||||
|
||||
void SimCtrl::RequestStop(bool success) {
|
||||
stop_requested_ = true;
|
||||
success_ &= success;
|
||||
}
|
||||
|
||||
bool SimCtrl::StopRequested() { return stop_requested_; }
|
||||
|
||||
bool SimCtrl::TestPassed() { return success_; }
|
||||
|
||||
void SimCtrl::OnFinal() {
|
||||
std::cout << std::endl
|
||||
<< "//-------------//" << std::endl
|
||||
<< (success_ ? "// TEST PASSED //" : "// TEST FAILED //")
|
||||
<< std::endl
|
||||
<< "//-------------//" << std::endl;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#ifndef SIMCTRL_H_
|
||||
#define SIMCTRL_H_
|
||||
|
||||
class SimCtrl {
|
||||
public:
|
||||
SimCtrl();
|
||||
void RequestStop(bool success);
|
||||
bool StopRequested();
|
||||
bool TestPassed();
|
||||
void OnFinal();
|
||||
|
||||
private:
|
||||
bool stop_requested_;
|
||||
bool success_;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Lint waivers for processing simple_system RTL with Verilator
|
||||
//
|
||||
// This should be used for rules applying to things like testbench
|
||||
// top-levels. For rules that apply to the actual design (files in the
|
||||
// 'rtl' directory), see verilator_waiver_rtl.vlt in the same
|
||||
// directory.
|
||||
//
|
||||
// See https://www.veripool.org/projects/verilator/wiki/Manual-verilator#CONFIGURATION-FILES
|
||||
// for documentation.
|
||||
//
|
||||
// Important: This file must included *before* any other Verilog file is read.
|
||||
// Otherwise, only global waivers are applied, but not file-specific waivers.
|
||||
|
||||
`verilator_config
|
||||
|
||||
// We use boolean top-level parameters.
|
||||
// When building with fusesoc, these get set with defines like
|
||||
// -GRV32E=1 (rather than -GRV32E=1'b1), leading to warnings like:
|
||||
//
|
||||
// Operator VAR '<varname>' expects 1 bits on the Initial value, but
|
||||
// Initial value's CONST '32'h1' generates 32 bits.
|
||||
//
|
||||
// This signoff rule ignores errors like this. Note that it only
|
||||
// matches when you set a 1-bit value to a literal 1, so it won't hide
|
||||
// silly mistakes like setting it to 2.
|
||||
//
|
||||
lint_off -rule WIDTH -file "*/tb/tb_cs_registers.sv"
|
||||
-match "*expects 1 bits*Initial value's CONST '32'h1'*"
|
||||
@@ -0,0 +1,253 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "base_register.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
|
||||
BaseRegister::BaseRegister(
|
||||
uint32_t addr, std::vector<std::unique_ptr<BaseRegister>> *map_pointer)
|
||||
: register_value_(0), register_address_(addr), map_pointer_(map_pointer) {}
|
||||
|
||||
uint32_t BaseRegister::RegisterWrite(uint32_t newval) {
|
||||
uint32_t lock_mask = GetLockMask();
|
||||
uint32_t read_value = register_value_;
|
||||
register_value_ &= lock_mask;
|
||||
register_value_ |= (newval & ~lock_mask);
|
||||
return read_value;
|
||||
}
|
||||
|
||||
uint32_t BaseRegister::RegisterSet(uint32_t newval) {
|
||||
uint32_t lock_mask = GetLockMask();
|
||||
uint32_t read_value = register_value_;
|
||||
register_value_ |= (newval & ~lock_mask);
|
||||
return read_value;
|
||||
}
|
||||
|
||||
uint32_t BaseRegister::RegisterClear(uint32_t newval) {
|
||||
uint32_t lock_mask = GetLockMask();
|
||||
uint32_t read_value = register_value_;
|
||||
register_value_ &= (~newval | lock_mask);
|
||||
return read_value;
|
||||
}
|
||||
|
||||
bool BaseRegister::MatchAddr(uint32_t addr, uint32_t addr_mask) {
|
||||
return ((addr & addr_mask) == (register_address_ & addr_mask));
|
||||
}
|
||||
|
||||
bool BaseRegister::ProcessTransaction(bool *match, RegisterTransaction *trans) {
|
||||
uint32_t read_val;
|
||||
if (!MatchAddr(trans->csr_addr)) {
|
||||
return false;
|
||||
}
|
||||
*match = true;
|
||||
switch (trans->csr_op) {
|
||||
case kCSRRead:
|
||||
read_val = RegisterRead();
|
||||
break;
|
||||
case kCSRWrite:
|
||||
read_val = RegisterWrite(trans->csr_wdata);
|
||||
break;
|
||||
case kCSRSet:
|
||||
read_val = RegisterSet(trans->csr_wdata);
|
||||
break;
|
||||
case kCSRClear:
|
||||
read_val = RegisterClear(trans->csr_wdata);
|
||||
break;
|
||||
}
|
||||
if (trans->csr_addr == kCSRMCycle || trans->csr_addr == kCSRMCycleH) {
|
||||
// MCycle(H) can increment or even overflow without TB interaction
|
||||
if (trans->csr_rdata < read_val) {
|
||||
std::cout << "MCycle(H) overflow detected" << std::endl;
|
||||
}
|
||||
// else if (read_val != trans->csr_rdata) {
|
||||
// std::cout << "MCycle(H) incrementing as expected" << std::endl;
|
||||
//}
|
||||
// Don't panic about MCycle(H) incremeting, this is expected behavior as
|
||||
// the clock is always running. Silently ignore mismatches for MCycle(H).
|
||||
} else if (read_val != trans->csr_rdata) {
|
||||
std::cout << "Error, transaction:" << std::endl;
|
||||
trans->Print();
|
||||
std::cout << "Expected rdata: " << std::hex << read_val << std::dec
|
||||
<< std::endl;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void BaseRegister::RegisterReset() { register_value_ = 0; }
|
||||
|
||||
uint32_t BaseRegister::RegisterRead() { return register_value_; }
|
||||
|
||||
uint32_t BaseRegister::GetLockMask() { return 0; }
|
||||
|
||||
BaseRegister *BaseRegister::GetRegisterFromMap(uint32_t addr) {
|
||||
for (auto ® : *map_pointer_) {
|
||||
if (reg->MatchAddr(addr)) {
|
||||
return reg.get();
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MSeccfgRegister::MSeccfgRegister(
|
||||
uint32_t addr, std::vector<std::unique_ptr<BaseRegister>> *map_pointer)
|
||||
: BaseRegister(addr, map_pointer) {}
|
||||
|
||||
bool MSeccfgRegister::AnyPmpCfgsLocked() {
|
||||
for (auto ® : *map_pointer_) {
|
||||
// Iterate through PMPCfgX CSRs, returning true is any has a lock bit set
|
||||
if (reg->MatchAddr(kCSRPMPCfg0, 0xfffffffc)) {
|
||||
if ((reg->RegisterRead() & 0x80808080) != 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t MSeccfgRegister::GetLockMask() {
|
||||
uint32_t lock_mask = 0xFFFFFFF8;
|
||||
|
||||
// When RLB == 0 and any PMPCfgX has a lock bit set RLB must remain 0
|
||||
if (AnyPmpCfgsLocked() && ((register_value_ & kMSeccfgRlb) == 0)) {
|
||||
lock_mask |= kMSeccfgRlb;
|
||||
}
|
||||
|
||||
// Once set MMWP cannot be unset
|
||||
if (register_value_ & kMSeccfgMmwp) {
|
||||
lock_mask |= kMSeccfgMmwp;
|
||||
}
|
||||
|
||||
// Once set MML cannot be unset
|
||||
if (register_value_ & kMSeccfgMml) {
|
||||
lock_mask |= kMSeccfgMml;
|
||||
}
|
||||
|
||||
return lock_mask;
|
||||
}
|
||||
|
||||
uint32_t PmpCfgRegister::GetLockMask() {
|
||||
BaseRegister *mseccfg = GetRegisterFromMap(kCSRMSeccfg);
|
||||
assert(mseccfg);
|
||||
|
||||
if (mseccfg->RegisterRead() & kMSeccfgRlb) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t lock_mask = 0;
|
||||
if (register_value_ & 0x80)
|
||||
lock_mask |= 0xFF;
|
||||
if (register_value_ & 0x8000)
|
||||
lock_mask |= 0xFF00;
|
||||
if (register_value_ & 0x800000)
|
||||
lock_mask |= 0xFF0000;
|
||||
if (register_value_ & 0x80000000)
|
||||
lock_mask |= 0xFF000000;
|
||||
return lock_mask;
|
||||
}
|
||||
|
||||
uint32_t PmpCfgRegister::RegisterWrite(uint32_t newval) {
|
||||
uint32_t lock_mask = GetLockMask();
|
||||
uint32_t read_value = register_value_;
|
||||
|
||||
register_value_ &= lock_mask;
|
||||
register_value_ |= (newval & ~lock_mask);
|
||||
register_value_ = HandleReservedVals(register_value_);
|
||||
|
||||
return read_value;
|
||||
}
|
||||
|
||||
uint32_t PmpCfgRegister::RegisterSet(uint32_t newval) {
|
||||
uint32_t lock_mask = GetLockMask();
|
||||
uint32_t read_value = register_value_;
|
||||
|
||||
register_value_ |= (newval & ~lock_mask);
|
||||
register_value_ = HandleReservedVals(register_value_);
|
||||
|
||||
return read_value;
|
||||
}
|
||||
|
||||
uint32_t PmpCfgRegister::RegisterClear(uint32_t newval) {
|
||||
uint32_t lock_mask = GetLockMask();
|
||||
uint32_t read_value = register_value_;
|
||||
|
||||
register_value_ &= (~newval | lock_mask);
|
||||
register_value_ = HandleReservedVals(register_value_);
|
||||
|
||||
return read_value;
|
||||
}
|
||||
|
||||
uint32_t PmpCfgRegister::HandleReservedVals(uint32_t cfg_val) {
|
||||
BaseRegister *mseccfg = GetRegisterFromMap(kCSRMSeccfg);
|
||||
assert(mseccfg);
|
||||
|
||||
cfg_val &= raz_mask_;
|
||||
|
||||
if (mseccfg->RegisterRead() & kMSeccfgMml) {
|
||||
// No reserved L/R/W/X values when MML Set
|
||||
return cfg_val;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
// Reserved check, W = 1, R = 0
|
||||
if (((cfg_val >> (8 * i)) & 0x3) == 0x2) {
|
||||
cfg_val &= ~(0x3 << (8 * i));
|
||||
}
|
||||
}
|
||||
|
||||
return cfg_val;
|
||||
}
|
||||
|
||||
uint32_t PmpAddrRegister::GetLockMask() {
|
||||
// Calculate which region this is
|
||||
uint32_t pmp_region = (register_address_ & 0xF);
|
||||
// Form the address of the corresponding CFG register
|
||||
uint32_t pmp_cfg_addr = 0x3A0 + (pmp_region / 4);
|
||||
// Form the address of the CFG registerfor the next region
|
||||
// For region 15, this will point to a non-existant register, which is fine
|
||||
uint32_t pmp_cfg_plus1_addr = 0x3A0 + ((pmp_region + 1) / 4);
|
||||
uint32_t cfg_value = 0;
|
||||
uint32_t cfg_plus1_value = 0;
|
||||
// Find and read the two CFG registers
|
||||
for (auto it = map_pointer_->begin(); it != map_pointer_->end(); ++it) {
|
||||
if ((*it)->MatchAddr(pmp_cfg_addr)) {
|
||||
cfg_value = (*it)->RegisterRead();
|
||||
}
|
||||
if ((*it)->MatchAddr(pmp_cfg_plus1_addr)) {
|
||||
cfg_plus1_value = (*it)->RegisterRead();
|
||||
}
|
||||
}
|
||||
// Shift to the relevant bits in the CFG registers
|
||||
cfg_value >>= ((pmp_region & 0x3) * 8);
|
||||
cfg_plus1_value >>= (((pmp_region + 1) & 0x3) * 8);
|
||||
// Locked if the lock bit is set, or the next region is TOR
|
||||
if ((cfg_value & 0x80) || ((cfg_plus1_value & 0x18) == 0x8)) {
|
||||
return 0xFFFFFFFF;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t NonImpRegister::RegisterRead() { return 0; }
|
||||
|
||||
uint32_t NonImpRegister::RegisterWrite(uint32_t newval) { return 0; }
|
||||
|
||||
uint32_t NonImpRegister::RegisterSet(uint32_t newval) { return 0; }
|
||||
|
||||
uint32_t NonImpRegister::RegisterClear(uint32_t newval) { return 0; }
|
||||
|
||||
WARLRegister::WARLRegister(
|
||||
uint32_t addr, std::vector<std::unique_ptr<BaseRegister>> *map_pointer,
|
||||
uint32_t mask, uint32_t resval)
|
||||
: BaseRegister{addr, map_pointer},
|
||||
register_mask_(mask),
|
||||
register_value_reset_(resval) {}
|
||||
|
||||
void WARLRegister::RegisterReset() { register_value_ = register_value_reset_; }
|
||||
|
||||
uint32_t WARLRegister::GetLockMask() { return register_mask_; }
|
||||
@@ -0,0 +1,113 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#ifndef BASE_REGISTER_H_
|
||||
#define BASE_REGISTER_H_
|
||||
|
||||
#include "register_transaction.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* Base register class, can be specialized to add advanced functionality
|
||||
* required by different types of register
|
||||
*/
|
||||
class BaseRegister {
|
||||
public:
|
||||
BaseRegister(uint32_t addr,
|
||||
std::vector<std::unique_ptr<BaseRegister>> *map_pointer);
|
||||
virtual ~BaseRegister() = default;
|
||||
virtual void RegisterReset();
|
||||
virtual uint32_t RegisterWrite(uint32_t newval);
|
||||
virtual uint32_t RegisterSet(uint32_t newval);
|
||||
virtual uint32_t RegisterClear(uint32_t newval);
|
||||
virtual uint32_t RegisterRead();
|
||||
virtual bool ProcessTransaction(bool *match, RegisterTransaction *trans);
|
||||
virtual bool MatchAddr(uint32_t addr, uint32_t addr_mask = 0xFFFFFFFF);
|
||||
virtual uint32_t GetLockMask();
|
||||
|
||||
protected:
|
||||
uint32_t register_value_;
|
||||
uint32_t register_address_;
|
||||
std::vector<std::unique_ptr<BaseRegister>> *map_pointer_;
|
||||
BaseRegister *GetRegisterFromMap(uint32_t addr);
|
||||
};
|
||||
|
||||
/**
|
||||
* Machine Security Configuration class
|
||||
*
|
||||
* Has special behaviour for when bits are locked so requires a custom
|
||||
* `GetLockMask` implementation
|
||||
*/
|
||||
class MSeccfgRegister : public BaseRegister {
|
||||
public:
|
||||
MSeccfgRegister(uint32_t addr,
|
||||
std::vector<std::unique_ptr<BaseRegister>> *map_pointer);
|
||||
uint32_t GetLockMask();
|
||||
|
||||
private:
|
||||
bool AnyPmpCfgsLocked();
|
||||
};
|
||||
|
||||
/**
|
||||
* PMP configuration register class
|
||||
*/
|
||||
class PmpCfgRegister : public BaseRegister {
|
||||
using BaseRegister::BaseRegister;
|
||||
|
||||
public:
|
||||
uint32_t GetLockMask();
|
||||
uint32_t RegisterWrite(uint32_t newval);
|
||||
uint32_t RegisterSet(uint32_t newval);
|
||||
uint32_t RegisterClear(uint32_t newval);
|
||||
|
||||
private:
|
||||
uint32_t HandleReservedVals(uint32_t cfg_val);
|
||||
const uint32_t raz_mask_ = 0x9F9F9F9F;
|
||||
};
|
||||
|
||||
/**
|
||||
* PMP address register class
|
||||
*/
|
||||
class PmpAddrRegister : public BaseRegister {
|
||||
using BaseRegister::BaseRegister;
|
||||
|
||||
public:
|
||||
uint32_t GetLockMask();
|
||||
};
|
||||
|
||||
/**
|
||||
* Generic class to model non-implemented (read as zero) registers
|
||||
*/
|
||||
class NonImpRegister : public BaseRegister {
|
||||
using BaseRegister::BaseRegister;
|
||||
|
||||
public:
|
||||
uint32_t RegisterRead();
|
||||
uint32_t RegisterWrite(uint32_t newval);
|
||||
uint32_t RegisterSet(uint32_t newval);
|
||||
uint32_t RegisterClear(uint32_t newval);
|
||||
};
|
||||
|
||||
/**
|
||||
* Generic class of WARL registers
|
||||
*/
|
||||
class WARLRegister : public BaseRegister {
|
||||
using BaseRegister::BaseRegister;
|
||||
|
||||
protected:
|
||||
uint32_t register_mask_;
|
||||
uint32_t register_value_reset_;
|
||||
|
||||
public:
|
||||
WARLRegister(uint32_t addr,
|
||||
std::vector<std::unique_ptr<BaseRegister>> *map_pointer,
|
||||
uint32_t mask, uint32_t resval);
|
||||
uint32_t GetLockMask();
|
||||
void RegisterReset();
|
||||
};
|
||||
|
||||
#endif // BASE_REGISTER_H_
|
||||
@@ -0,0 +1,123 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "register_model.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
RegisterModel::RegisterModel(SimCtrl *sc, CSRParams *params) : simctrl_(sc) {
|
||||
register_map_.push_back(
|
||||
std::make_unique<MSeccfgRegister>(kCSRMSeccfg, ®ister_map_));
|
||||
register_map_.push_back(
|
||||
std::make_unique<NonImpRegister>(kCSRMSeccfgh, ®ister_map_));
|
||||
// Instantiate all the registers
|
||||
for (unsigned int i = 0; i < 4; i++) {
|
||||
uint32_t reg_addr = 0x3A0 + i;
|
||||
if (params->PMPEnable && (i < (params->PMPNumRegions / 4))) {
|
||||
register_map_.push_back(
|
||||
std::make_unique<PmpCfgRegister>(reg_addr, ®ister_map_));
|
||||
} else {
|
||||
register_map_.push_back(
|
||||
std::make_unique<NonImpRegister>(reg_addr, ®ister_map_));
|
||||
}
|
||||
}
|
||||
for (unsigned int i = 0; i < 16; i++) {
|
||||
uint32_t reg_addr = 0x3B0 + i;
|
||||
if (params->PMPEnable && (i < params->PMPNumRegions)) {
|
||||
register_map_.push_back(
|
||||
std::make_unique<PmpAddrRegister>(reg_addr, ®ister_map_));
|
||||
} else {
|
||||
register_map_.push_back(
|
||||
std::make_unique<NonImpRegister>(reg_addr, ®ister_map_));
|
||||
}
|
||||
}
|
||||
// mcountinhibit
|
||||
// - MSBs are always 0: unused counters cannot be inhibited
|
||||
// - Bit 1 is always 0: time cannot be disabled
|
||||
uint32_t mcountinhibit_mask =
|
||||
(~((0x1 << params->MHPMCounterNum) - 1) << 3) | 0x2;
|
||||
uint32_t mcountinhibit_resval = 0;
|
||||
register_map_.push_back(std::make_unique<WARLRegister>(
|
||||
0x320, ®ister_map_, mcountinhibit_mask, mcountinhibit_resval));
|
||||
// Performance counter setup
|
||||
for (unsigned int i = 3; i < 32; i++) {
|
||||
uint32_t reg_addr = 0x320 + i;
|
||||
if (i < (params->MHPMCounterNum + 3)) {
|
||||
register_map_.push_back(std::make_unique<WARLRegister>(
|
||||
reg_addr, ®ister_map_, 0xFFFFFFFF, 0x1 << (i - 3)));
|
||||
} else {
|
||||
register_map_.push_back(
|
||||
std::make_unique<NonImpRegister>(reg_addr, ®ister_map_));
|
||||
}
|
||||
}
|
||||
// mcycle
|
||||
register_map_.push_back(
|
||||
std::make_unique<BaseRegister>(0xB00, ®ister_map_));
|
||||
// minstret
|
||||
register_map_.push_back(
|
||||
std::make_unique<BaseRegister>(0xB02, ®ister_map_));
|
||||
// Generate masks from counter width parameter
|
||||
uint32_t mhpmcounter_mask_low, mhpmcounter_mask_high;
|
||||
if (params->MHPMCounterWidth >= 64) {
|
||||
mhpmcounter_mask_low = 0x0;
|
||||
mhpmcounter_mask_high = 0x0;
|
||||
} else {
|
||||
uint64_t mask = ~((0x1L << params->MHPMCounterWidth) - 1);
|
||||
mhpmcounter_mask_low = mask & 0xFFFFFFFF;
|
||||
mhpmcounter_mask_high = mask >> 32;
|
||||
}
|
||||
// Performance counter low word
|
||||
for (unsigned int i = 3; i < 32; i++) {
|
||||
uint32_t reg_addr = 0xB00 + i;
|
||||
if (i < (params->MHPMCounterNum + 3)) {
|
||||
register_map_.push_back(std::make_unique<WARLRegister>(
|
||||
reg_addr, ®ister_map_, mhpmcounter_mask_low, 0));
|
||||
} else {
|
||||
register_map_.push_back(
|
||||
std::make_unique<NonImpRegister>(reg_addr, ®ister_map_));
|
||||
}
|
||||
}
|
||||
// mcycleh
|
||||
register_map_.push_back(
|
||||
std::make_unique<BaseRegister>(0xB80, ®ister_map_));
|
||||
// minstreth
|
||||
register_map_.push_back(
|
||||
std::make_unique<BaseRegister>(0xB82, ®ister_map_));
|
||||
// Performance counter high word
|
||||
for (unsigned int i = 3; i < 32; i++) {
|
||||
uint32_t reg_addr = 0xB80 + i;
|
||||
if (i < (params->MHPMCounterNum + 3)) {
|
||||
register_map_.push_back(std::make_unique<WARLRegister>(
|
||||
reg_addr, ®ister_map_, mhpmcounter_mask_high, 0));
|
||||
} else {
|
||||
register_map_.push_back(
|
||||
std::make_unique<NonImpRegister>(reg_addr, ®ister_map_));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RegisterModel::RegisterReset() {
|
||||
for (auto it = register_map_.begin(); it != register_map_.end(); ++it) {
|
||||
(*it)->RegisterReset();
|
||||
}
|
||||
}
|
||||
|
||||
void RegisterModel::NewTransaction(std::unique_ptr<RegisterTransaction> trans) {
|
||||
// TODO add machine mode permissions to registers
|
||||
bool matched = false;
|
||||
for (auto it = register_map_.begin(); it != register_map_.end(); ++it) {
|
||||
if ((*it)->ProcessTransaction(&matched, trans.get())) {
|
||||
simctrl_->RequestStop(false);
|
||||
}
|
||||
}
|
||||
if (!matched) {
|
||||
// Non existant register
|
||||
if (!trans->illegal_csr) {
|
||||
std::cout << "Non-existant register:" << std::endl;
|
||||
trans->Print();
|
||||
std::cout << "Should have signalled an error." << std::endl;
|
||||
simctrl_->RequestStop(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#ifndef REGISTER_MODEL_H_
|
||||
#define REGISTER_MODEL_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "base_register.h"
|
||||
#include "register_transaction.h"
|
||||
#include "register_types.h"
|
||||
#include "simctrl.h"
|
||||
|
||||
/**
|
||||
* Class modelling CS register state and checking against RTL
|
||||
*/
|
||||
class RegisterModel {
|
||||
public:
|
||||
RegisterModel(SimCtrl *sc, CSRParams *params);
|
||||
|
||||
void NewTransaction(std::unique_ptr<RegisterTransaction> trans);
|
||||
void RegisterReset();
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<BaseRegister>> register_map_;
|
||||
SimCtrl *simctrl_;
|
||||
};
|
||||
|
||||
#endif // REGISTER_MODEL_H_
|
||||
@@ -0,0 +1,126 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// List all supported registers
|
||||
|
||||
#ifndef CSR
|
||||
#error Define CSR
|
||||
#endif
|
||||
|
||||
CSR(PMPCfg0, 0x3A0)
|
||||
CSR(PMPCfg1, 0x3A1)
|
||||
CSR(PMPCfg2, 0x3A2)
|
||||
CSR(PMPCfg3, 0x3A3)
|
||||
CSR(PMPAddr0, 0x3B0)
|
||||
CSR(PMPAddr1, 0x3B1)
|
||||
CSR(PMPAddr2, 0x3B2)
|
||||
CSR(PMPAddr3, 0x3B3)
|
||||
CSR(PMPAddr4, 0x3B4)
|
||||
CSR(PMPAddr5, 0x3B5)
|
||||
CSR(PMPAddr6, 0x3B6)
|
||||
CSR(PMPAddr7, 0x3B7)
|
||||
CSR(PMPAddr8, 0x3B8)
|
||||
CSR(PMPAddr9, 0x3B9)
|
||||
CSR(PMPAddr10, 0x3BA)
|
||||
CSR(PMPAddr11, 0x3BB)
|
||||
CSR(PMPAddr12, 0x3BC)
|
||||
CSR(PMPAddr13, 0x3BD)
|
||||
CSR(PMPAddr14, 0x3BE)
|
||||
CSR(PMPAddr15, 0x3BF)
|
||||
CSR(MCountInhibit, 0x320)
|
||||
CSR(MHPMEvent3, 0x323)
|
||||
CSR(MHPMEvent4, 0x324)
|
||||
CSR(MHPMEvent5, 0x325)
|
||||
CSR(MHPMEvent6, 0x326)
|
||||
CSR(MHPMEvent7, 0x327)
|
||||
CSR(MHPMEvent8, 0x328)
|
||||
CSR(MHPMEvent9, 0x329)
|
||||
CSR(MHPMEvent10, 0x32A)
|
||||
CSR(MHPMEvent11, 0x32B)
|
||||
CSR(MHPMEvent12, 0x32C)
|
||||
CSR(MHPMEvent13, 0x32D)
|
||||
CSR(MHPMEvent14, 0x32E)
|
||||
CSR(MHPMEvent15, 0x32F)
|
||||
CSR(MHPMEvent16, 0x330)
|
||||
CSR(MHPMEvent17, 0x331)
|
||||
CSR(MHPMEvent18, 0x332)
|
||||
CSR(MHPMEvent19, 0x333)
|
||||
CSR(MHPMEvent20, 0x334)
|
||||
CSR(MHPMEvent21, 0x335)
|
||||
CSR(MHPMEvent22, 0x336)
|
||||
CSR(MHPMEvent23, 0x337)
|
||||
CSR(MHPMEvent24, 0x338)
|
||||
CSR(MHPMEvent25, 0x339)
|
||||
CSR(MHPMEvent26, 0x33A)
|
||||
CSR(MHPMEvent27, 0x33B)
|
||||
CSR(MHPMEvent28, 0x33C)
|
||||
CSR(MHPMEvent29, 0x33D)
|
||||
CSR(MHPMEvent30, 0x33E)
|
||||
CSR(MHPMEvent31, 0x33F)
|
||||
CSR(MSeccfg, 0x747)
|
||||
CSR(MSeccfgh, 0x757)
|
||||
CSR(MCycle, 0xB00)
|
||||
CSR(MInstret, 0xB02)
|
||||
CSR(MHPMCounter3, 0xB03)
|
||||
CSR(MHPMCounter4, 0xB04)
|
||||
CSR(MHPMCounter5, 0xB05)
|
||||
CSR(MHPMCounter6, 0xB06)
|
||||
CSR(MHPMCounter7, 0xB07)
|
||||
CSR(MHPMCounter8, 0xB08)
|
||||
CSR(MHPMCounter9, 0xB09)
|
||||
CSR(MHPMCounter10, 0xB0A)
|
||||
CSR(MHPMCounter11, 0xB0B)
|
||||
CSR(MHPMCounter12, 0xB0C)
|
||||
CSR(MHPMCounter13, 0xB0D)
|
||||
CSR(MHPMCounter14, 0xB0E)
|
||||
CSR(MHPMCounter15, 0xB0F)
|
||||
CSR(MHPMCounter16, 0xB10)
|
||||
CSR(MHPMCounter17, 0xB11)
|
||||
CSR(MHPMCounter18, 0xB12)
|
||||
CSR(MHPMCounter19, 0xB13)
|
||||
CSR(MHPMCounter20, 0xB14)
|
||||
CSR(MHPMCounter21, 0xB15)
|
||||
CSR(MHPMCounter22, 0xB16)
|
||||
CSR(MHPMCounter23, 0xB17)
|
||||
CSR(MHPMCounter24, 0xB18)
|
||||
CSR(MHPMCounter25, 0xB19)
|
||||
CSR(MHPMCounter26, 0xB1A)
|
||||
CSR(MHPMCounter27, 0xB1B)
|
||||
CSR(MHPMCounter28, 0xB1C)
|
||||
CSR(MHPMCounter29, 0xB1D)
|
||||
CSR(MHPMCounter30, 0xB1E)
|
||||
CSR(MHPMCounter31, 0xB1F)
|
||||
CSR(MCycleH, 0xB80)
|
||||
CSR(MInstretH, 0xB82)
|
||||
CSR(MHPMCounter3H, 0xB83)
|
||||
CSR(MHPMCounter4H, 0xB84)
|
||||
CSR(MHPMCounter5H, 0xB85)
|
||||
CSR(MHPMCounter6H, 0xB86)
|
||||
CSR(MHPMCounter7H, 0xB87)
|
||||
CSR(MHPMCounter8H, 0xB88)
|
||||
CSR(MHPMCounter9H, 0xB89)
|
||||
CSR(MHPMCounter10H, 0xB8A)
|
||||
CSR(MHPMCounter11H, 0xB8B)
|
||||
CSR(MHPMCounter12H, 0xB8C)
|
||||
CSR(MHPMCounter13H, 0xB8D)
|
||||
CSR(MHPMCounter14H, 0xB8E)
|
||||
CSR(MHPMCounter15H, 0xB8F)
|
||||
CSR(MHPMCounter16H, 0xB90)
|
||||
CSR(MHPMCounter17H, 0xB91)
|
||||
CSR(MHPMCounter18H, 0xB92)
|
||||
CSR(MHPMCounter19H, 0xB93)
|
||||
CSR(MHPMCounter20H, 0xB94)
|
||||
CSR(MHPMCounter21H, 0xB95)
|
||||
CSR(MHPMCounter22H, 0xB96)
|
||||
CSR(MHPMCounter23H, 0xB97)
|
||||
CSR(MHPMCounter24H, 0xB98)
|
||||
CSR(MHPMCounter25H, 0xB99)
|
||||
CSR(MHPMCounter26H, 0xB9A)
|
||||
CSR(MHPMCounter27H, 0xB9B)
|
||||
CSR(MHPMCounter28H, 0xB9C)
|
||||
CSR(MHPMCounter29H, 0xB9D)
|
||||
CSR(MHPMCounter30H, 0xB9E)
|
||||
CSR(MHPMCounter31H, 0xB9F)
|
||||
|
||||
#undef CSR
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "register_driver.h"
|
||||
#include "svdpi.h"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
static std::map<std::string, RegisterDriver *> intfs;
|
||||
|
||||
void reg_register_intf(std::string name, RegisterDriver *intf) {
|
||||
intfs.insert({name, intf});
|
||||
}
|
||||
|
||||
void reg_deregister_intf(std::string name) { intfs.erase(name); }
|
||||
|
||||
void monitor_tick(const char *name, svBit rst_n, svBit illegal_csr,
|
||||
svBit csr_access, const svBitVecVal *csr_op, svBit csr_op_en,
|
||||
const svBitVecVal *csr_addr, const svBitVecVal *csr_wdata,
|
||||
const svBitVecVal *csr_rdata) {
|
||||
auto ptr = intfs.find(name);
|
||||
if (ptr != intfs.end()) {
|
||||
// Send inputs to monitor
|
||||
if ((csr_access && (csr_op_en || illegal_csr)) || !rst_n) {
|
||||
ptr->second->CaptureTransaction(rst_n, illegal_csr, *csr_op, *csr_addr,
|
||||
*csr_rdata, *csr_wdata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void driver_tick(const char *name, svBit *csr_access, svBitVecVal *csr_op,
|
||||
svBit *csr_op_en, svBitVecVal *csr_addr,
|
||||
svBitVecVal *csr_wdata) {
|
||||
auto ptr = intfs.find(name);
|
||||
if (ptr != intfs.end()) {
|
||||
// Call OnClock method
|
||||
ptr->second->OnClock();
|
||||
// Drive outputs
|
||||
ptr->second->DriveOutputs(csr_access, csr_op, csr_op_en, csr_addr,
|
||||
csr_wdata);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package reg_dpi;
|
||||
|
||||
import "DPI-C"
|
||||
function void monitor_tick (
|
||||
input string name,
|
||||
input bit rst_n,
|
||||
input bit illegal_csr,
|
||||
input bit csr_access,
|
||||
input bit [1:0] csr_op,
|
||||
input bit csr_op_en,
|
||||
input bit [11:0] csr_addr,
|
||||
input bit [31:0] csr_wdata,
|
||||
input bit [31:0] csr_rdata);
|
||||
|
||||
import "DPI-C"
|
||||
function void driver_tick (
|
||||
input string name,
|
||||
output bit csr_access,
|
||||
output bit [1:0] csr_op,
|
||||
output bit csr_op_en,
|
||||
output bit [11:0] csr_addr,
|
||||
output bit [31:0] csr_wdata);
|
||||
|
||||
endpackage
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "register_driver.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
extern "C" void reg_register_intf(std::string name, RegisterDriver *intf);
|
||||
extern "C" void reg_deregister_intf(std::string name);
|
||||
|
||||
RegisterDriver::RegisterDriver(std::string name, RegisterModel *model,
|
||||
SimCtrl *sc)
|
||||
: name_(name), reg_model_(model), simctrl_(sc) {}
|
||||
|
||||
void RegisterDriver::OnInitial(unsigned int seed) {
|
||||
transactions_driven_ = 0;
|
||||
delay_ = 1;
|
||||
reg_access_ = false;
|
||||
generator_.seed(seed);
|
||||
delay_dist_ = std::uniform_int_distribution<int>(1, 20);
|
||||
reg_register_intf(name_, this);
|
||||
}
|
||||
|
||||
void RegisterDriver::OnFinal() {
|
||||
reg_deregister_intf(name_);
|
||||
std::cout << "[Reg driver] drove: " << transactions_driven_
|
||||
<< " register transactions" << std::endl;
|
||||
}
|
||||
|
||||
void RegisterDriver::Randomize() {
|
||||
// generate new transaction
|
||||
next_transaction_.Randomize(generator_);
|
||||
// generate new delay
|
||||
delay_ = delay_dist_(generator_);
|
||||
|
||||
reg_access_ = true;
|
||||
}
|
||||
|
||||
void RegisterDriver::CaptureTransaction(unsigned char rst_n,
|
||||
unsigned char illegal_csr, uint32_t op,
|
||||
uint32_t addr, uint32_t rdata,
|
||||
uint32_t wdata) {
|
||||
if (!rst_n) {
|
||||
reg_model_->RegisterReset();
|
||||
} else {
|
||||
auto trans = std::make_unique<RegisterTransaction>();
|
||||
trans->illegal_csr = illegal_csr;
|
||||
trans->csr_op = (CSRegisterOperation)op;
|
||||
trans->csr_addr = addr;
|
||||
trans->csr_rdata = rdata;
|
||||
trans->csr_wdata = wdata;
|
||||
// Ownership of trans is passed to the model
|
||||
reg_model_->NewTransaction(std::move(trans));
|
||||
}
|
||||
}
|
||||
|
||||
void RegisterDriver::DriveOutputs(unsigned char *access, uint32_t *op,
|
||||
unsigned char *csr_op_en, uint32_t *addr,
|
||||
uint32_t *wdata) {
|
||||
*access = reg_access_;
|
||||
*csr_op_en = reg_access_;
|
||||
*op = next_transaction_.csr_op;
|
||||
*addr = next_transaction_.csr_addr;
|
||||
*wdata = next_transaction_.csr_wdata;
|
||||
}
|
||||
|
||||
void RegisterDriver::OnClock() {
|
||||
if (transactions_driven_ >= 10000) {
|
||||
simctrl_->RequestStop(true);
|
||||
}
|
||||
if (--delay_ == 0) {
|
||||
Randomize();
|
||||
++transactions_driven_;
|
||||
} else {
|
||||
reg_access_ = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#ifndef REGISTER_DRIVER_H_
|
||||
#define REGISTER_DRIVER_H_
|
||||
|
||||
#include "register_model.h"
|
||||
#include "register_transaction.h"
|
||||
#include "simctrl.h"
|
||||
|
||||
#include <random>
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* Class to randomize and drive CS register reads/writes
|
||||
*/
|
||||
class RegisterDriver {
|
||||
public:
|
||||
RegisterDriver(std::string name, RegisterModel *model, SimCtrl *sc);
|
||||
|
||||
void OnInitial(unsigned int seed);
|
||||
void OnClock();
|
||||
void OnFinal();
|
||||
|
||||
void CaptureTransaction(unsigned char rst_n, unsigned char illegal_csr,
|
||||
uint32_t op, uint32_t addr, uint32_t rdata,
|
||||
uint32_t wdata);
|
||||
void DriveOutputs(unsigned char *access, uint32_t *op, unsigned char *op_en,
|
||||
uint32_t *addr, uint32_t *wdata);
|
||||
|
||||
private:
|
||||
void Randomize();
|
||||
|
||||
std::default_random_engine generator_;
|
||||
int delay_;
|
||||
bool reg_access_;
|
||||
CSRegisterOperation reg_op_;
|
||||
std::uniform_int_distribution<int> delay_dist_;
|
||||
uint32_t reg_addr_;
|
||||
uint32_t reg_wdata_;
|
||||
int transactions_driven_;
|
||||
RegisterTransaction next_transaction_;
|
||||
|
||||
std::string name_;
|
||||
RegisterModel *reg_model_;
|
||||
SimCtrl *simctrl_;
|
||||
};
|
||||
|
||||
#endif // REGISTER_DRIVER_H_
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "register_transaction.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
void RegisterTransaction::Randomize(std::default_random_engine &gen) {
|
||||
std::uniform_int_distribution<int> addr_dist_ =
|
||||
std::uniform_int_distribution<int>(
|
||||
0, (sizeof(CSRAddresses) / sizeof(uint16_t)) - 1);
|
||||
std::uniform_int_distribution<int> wdata_dist_ =
|
||||
std::uniform_int_distribution<int>(0, 0xFFFFFFFF);
|
||||
std::uniform_int_distribution<int> operation_dist_ =
|
||||
std::uniform_int_distribution<int>(kCSRRead, kCSRClear);
|
||||
// Generate a random array index, and get the address
|
||||
csr_addr = CSRAddresses[addr_dist_(gen)];
|
||||
// Generate a random op type
|
||||
csr_op = static_cast<CSRegisterOperation>(operation_dist_(gen));
|
||||
if (csr_op != kCSRRead) {
|
||||
// Generate random wdata
|
||||
csr_wdata = wdata_dist_(gen);
|
||||
}
|
||||
}
|
||||
|
||||
void RegisterTransaction::Print() {
|
||||
std::cout << "Register transaction:" << std::endl
|
||||
<< "Operation: " << RegOpString() << std::endl
|
||||
<< "Address: " << RegAddrString() << std::endl;
|
||||
if (csr_op != kCSRRead) {
|
||||
std::cout << "Write data: " << std::hex << csr_wdata << std::endl;
|
||||
}
|
||||
std::cout << "Read data: " << std::hex << csr_rdata << std::dec << std::endl;
|
||||
}
|
||||
|
||||
std::string RegisterTransaction::RegOpString() {
|
||||
switch (csr_op) {
|
||||
case kCSRRead:
|
||||
return "CSR Read";
|
||||
case kCSRWrite:
|
||||
return "CSR Write";
|
||||
case kCSRSet:
|
||||
return "CSR Set";
|
||||
case kCSRClear:
|
||||
return "CSR Clear";
|
||||
default:
|
||||
return "Unknown op";
|
||||
}
|
||||
}
|
||||
|
||||
std::string RegisterTransaction::RegAddrString() {
|
||||
// String representation created automatically by macro
|
||||
switch (csr_addr) {
|
||||
#define CSR(reg, addr) \
|
||||
case kCSR##reg: \
|
||||
return #reg;
|
||||
#include "csr_listing.def"
|
||||
default:
|
||||
return "Undef reg: " + std::to_string(csr_addr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#ifndef REGISTER_TRANSACTION_H_
|
||||
#define REGISTER_TRANSACTION_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <random>
|
||||
#include <string>
|
||||
|
||||
// Enumerate the supported register types by macro expansion
|
||||
enum CSRegisterAddr : int {
|
||||
#define CSR(reg, addr) kCSR##reg = addr,
|
||||
#include "csr_listing.def"
|
||||
};
|
||||
|
||||
// Individual bits for MSECCFG CSR
|
||||
const int kMSeccfgMml = 0x1;
|
||||
const int kMSeccfgMmwp = 0x2;
|
||||
const int kMSeccfgRlb = 0x4;
|
||||
|
||||
// Create an indexable array of all CSR addresses
|
||||
static const uint16_t CSRAddresses[] = {
|
||||
#define CSR(reg, addr) addr,
|
||||
#include "csr_listing.def"
|
||||
};
|
||||
|
||||
// Enumerate the four register operation types
|
||||
enum CSRegisterOperation : int {
|
||||
kCSRRead = 0,
|
||||
kCSRWrite = 1,
|
||||
kCSRSet = 2,
|
||||
kCSRClear = 3
|
||||
};
|
||||
|
||||
struct RegisterTransaction {
|
||||
public:
|
||||
void Randomize(std::default_random_engine &gen);
|
||||
void Print();
|
||||
|
||||
CSRegisterOperation csr_op;
|
||||
bool illegal_csr;
|
||||
uint32_t csr_addr;
|
||||
uint32_t csr_rdata;
|
||||
uint32_t csr_wdata;
|
||||
|
||||
private:
|
||||
std::string RegOpString();
|
||||
std::string RegAddrString();
|
||||
};
|
||||
|
||||
#endif // REGISTER_TRANSACTION_H_
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "reset_driver.h"
|
||||
|
||||
extern "C" void rst_register_intf(std::string name, ResetDriver *intf);
|
||||
extern "C" void rst_deregister_intf(std::string name);
|
||||
|
||||
ResetDriver::ResetDriver(std::string name)
|
||||
: reset_delay_(1), reset_duration_(0), name_(name) {}
|
||||
|
||||
void ResetDriver::OnInitial(unsigned int seed) {
|
||||
generator_.seed(seed);
|
||||
// 100 to 1000 cycles between resets
|
||||
delay_dist_ = std::uniform_int_distribution<int>(100, 1000);
|
||||
rst_register_intf(name_, this);
|
||||
}
|
||||
|
||||
void ResetDriver::OnFinal() { rst_deregister_intf(name_); }
|
||||
|
||||
void ResetDriver::DriveReset(unsigned char *rst_n) {
|
||||
reset_delay_--;
|
||||
if (reset_delay_ == 0) {
|
||||
reset_delay_ = delay_dist_(generator_);
|
||||
reset_duration_ = 0;
|
||||
}
|
||||
if (reset_duration_ < 3) {
|
||||
reset_duration_++;
|
||||
*rst_n = false;
|
||||
} else {
|
||||
*rst_n = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#ifndef RESET_DRIVER_H_
|
||||
#define RESET_DRIVER_H_
|
||||
|
||||
#include <random>
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* Class to randomize and drive reset signals
|
||||
*/
|
||||
class ResetDriver {
|
||||
public:
|
||||
ResetDriver(std::string name);
|
||||
void OnInitial(unsigned int seed);
|
||||
void OnFinal();
|
||||
void DriveReset(unsigned char *rst_n);
|
||||
|
||||
private:
|
||||
int reset_delay_;
|
||||
int reset_duration_;
|
||||
std::string name_;
|
||||
std::default_random_engine generator_;
|
||||
std::uniform_int_distribution<int> delay_dist_;
|
||||
};
|
||||
|
||||
#endif // RESET_DRIVER_H_
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "register_environment.h"
|
||||
|
||||
#include "svdpi.h"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
static std::map<std::string, ResetDriver *> intfs;
|
||||
|
||||
void rst_register_intf(std::string name, ResetDriver *intf) {
|
||||
intfs.insert({name, intf});
|
||||
}
|
||||
|
||||
void rst_deregister_intf(std::string name) { intfs.erase(name); }
|
||||
|
||||
void rst_tick(const char *name, svBit *rst_n) {
|
||||
auto ptr = intfs.find(name);
|
||||
if (ptr != intfs.end()) {
|
||||
ptr->second->DriveReset(rst_n);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package rst_dpi;
|
||||
|
||||
import "DPI-C"
|
||||
function void rst_tick (
|
||||
input string name,
|
||||
output bit rst_n);
|
||||
|
||||
endpackage
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "verilated_toplevel.h"
|
||||
#include "verilator_sim_ctrl.h"
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
tb_cs_registers top;
|
||||
VerilatorSimCtrl &simctrl = VerilatorSimCtrl::GetInstance();
|
||||
simctrl.SetTop(&top, &top.clk_i, &top.in_rst_ni,
|
||||
VerilatorSimCtrlFlags::ResetPolarityNegative);
|
||||
|
||||
|
||||
// Get pass / fail from Verilator
|
||||
auto pr = simctrl.Exec(argc, argv);
|
||||
int ret_code = pr.first;
|
||||
bool ran_simulation = pr.second;
|
||||
|
||||
if (ret_code != 0 || !ran_simulation) {
|
||||
return ret_code;
|
||||
}
|
||||
|
||||
// Get pass / fail from testbench
|
||||
return !top.test_passed_o;
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
module tb_cs_registers #(
|
||||
parameter bit DbgTriggerEn = 1'b0,
|
||||
parameter bit ICache = 1'b0,
|
||||
parameter int unsigned MHPMCounterNum = 8,
|
||||
parameter int unsigned MHPMCounterWidth = 40,
|
||||
parameter bit PMPEnable = 1'b0,
|
||||
parameter int unsigned PMPGranularity = 0,
|
||||
parameter int unsigned PMPNumRegions = 4,
|
||||
parameter bit RV32E = 1'b0,
|
||||
parameter ibex_pkg::rv32m_e RV32M = ibex_pkg::RV32MFast,
|
||||
parameter ibex_pkg::rv32b_e RV32B = ibex_pkg::RV32BNone
|
||||
) (
|
||||
// Clock and Reset
|
||||
inout logic clk_i,
|
||||
inout logic in_rst_ni,
|
||||
output logic test_passed_o
|
||||
);
|
||||
|
||||
logic dpi_rst_ni;
|
||||
logic rst_ni;
|
||||
|
||||
// Interface to registers (SRAM like)
|
||||
logic csr_access_i;
|
||||
ibex_pkg::csr_num_e csr_addr_i;
|
||||
logic [31:0] csr_wdata_i;
|
||||
ibex_pkg::csr_op_e csr_op_i;
|
||||
logic csr_op_en_i;
|
||||
logic [31:0] csr_rdata_o;
|
||||
|
||||
logic illegal_csr_insn_o;
|
||||
|
||||
logic csr_access_d;
|
||||
ibex_pkg::csr_num_e csr_addr_d;
|
||||
logic [31:0] csr_wdata_d;
|
||||
ibex_pkg::csr_op_e csr_op_d;
|
||||
logic csr_op_en_d;
|
||||
//-----------------
|
||||
// Reset generation
|
||||
//-----------------
|
||||
// Allow reset to be toggled by the top-level (in Verilator)
|
||||
// or a DPI call
|
||||
assign rst_ni = in_rst_ni & dpi_rst_ni;
|
||||
|
||||
//----------------------------------------
|
||||
// Clock generation (not used in Verilator
|
||||
//----------------------------------------
|
||||
`ifndef VERILATOR
|
||||
logic local_clk_i;
|
||||
initial begin
|
||||
local_clk_i = 1'b0;
|
||||
while (1) begin
|
||||
#10
|
||||
local_clk_i = !local_clk_i;
|
||||
end
|
||||
end
|
||||
assign clk_i = local_clk_i;
|
||||
assign in_rst_ni = 1'b1;
|
||||
`endif
|
||||
|
||||
/* verilator lint_off PINMISSING */
|
||||
ibex_cs_registers #(
|
||||
.DbgTriggerEn (DbgTriggerEn),
|
||||
.ICache (ICache),
|
||||
.MHPMCounterNum (MHPMCounterNum),
|
||||
.MHPMCounterWidth (MHPMCounterWidth),
|
||||
.PMPEnable (PMPEnable),
|
||||
.PMPGranularity (PMPGranularity),
|
||||
.PMPNumRegions (PMPNumRegions),
|
||||
.RV32E (RV32E),
|
||||
.RV32M (RV32M),
|
||||
.RV32B (RV32B)
|
||||
) i_cs_regs (
|
||||
.clk_i (clk_i),
|
||||
.rst_ni (rst_ni),
|
||||
.csr_access_i (csr_access_i),
|
||||
.csr_addr_i (csr_addr_i),
|
||||
.csr_wdata_i (csr_wdata_i),
|
||||
.csr_op_i (csr_op_i),
|
||||
.csr_op_en_i (csr_op_en_i),
|
||||
.csr_rdata_o (csr_rdata_o),
|
||||
.illegal_csr_insn_o (illegal_csr_insn_o)
|
||||
);
|
||||
/* verilator lint_on PINMISSING */
|
||||
|
||||
// DPI calls
|
||||
bit stop_simulation;
|
||||
bit test_passed;
|
||||
bit [31:0] seed;
|
||||
|
||||
initial begin
|
||||
if (!$value$plusargs ("ntb_random_seed=%d", seed)) begin
|
||||
seed = 32'd0;
|
||||
end
|
||||
env_dpi::env_initial(seed,
|
||||
PMPEnable, PMPGranularity, PMPNumRegions,
|
||||
MHPMCounterNum, MHPMCounterWidth);
|
||||
end
|
||||
|
||||
final begin
|
||||
env_dpi::env_final();
|
||||
end
|
||||
|
||||
always_ff @(posedge clk_i) begin
|
||||
env_dpi::env_tick(stop_simulation, test_passed);
|
||||
rst_dpi::rst_tick("rstn_driver", dpi_rst_ni);
|
||||
if (stop_simulation) begin
|
||||
$finish();
|
||||
end
|
||||
end
|
||||
|
||||
// Signal test pass / fail as an output (Verilator sims can pick this up and use it as a
|
||||
// return code)
|
||||
assign test_passed_o = test_passed;
|
||||
|
||||
always_ff @(posedge clk_i or negedge rst_ni) begin
|
||||
reg_dpi::monitor_tick("reg_driver",
|
||||
rst_ni,
|
||||
illegal_csr_insn_o,
|
||||
csr_access_i,
|
||||
csr_op_i,
|
||||
csr_op_en_i,
|
||||
csr_addr_i,
|
||||
csr_wdata_i,
|
||||
csr_rdata_o);
|
||||
|
||||
reg_dpi::driver_tick("reg_driver",
|
||||
csr_access_d,
|
||||
csr_op_d,
|
||||
csr_op_en_d,
|
||||
csr_addr_d,
|
||||
csr_wdata_d);
|
||||
|
||||
// Use NBA to drive inputs to ensure correct scheduling.
|
||||
// This always_ff block will be executed on the positive edge of the clock with undefined order
|
||||
// vs all other always_ff triggered on the positive edge of the clock. If `driver_tick` drives
|
||||
// the inputs directly some of the always_ff blocks will see the old version of the inputs and
|
||||
// others will see the new version depending on scheduling order. This schedules all the inputs
|
||||
// to be NBA updates to avoid the race condition (in effect acting like any other always_ff
|
||||
// block with the _d values being computed via DPI rather than combinational logic).
|
||||
csr_access_i <= csr_access_d;
|
||||
csr_addr_i <= csr_addr_d;
|
||||
csr_wdata_i <= csr_wdata_d;
|
||||
csr_op_i <= csr_op_d;
|
||||
csr_op_en_i <= csr_op_en_d;
|
||||
end
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,127 @@
|
||||
CAPI=2:
|
||||
# Copyright lowRISC contributors.
|
||||
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
name: "lowrisc:ibex:tb_cs_registers"
|
||||
description: "CS registers testbench"
|
||||
filesets:
|
||||
files_so:
|
||||
files:
|
||||
- Makefile
|
||||
- rst_driver/rst_dpi.cc
|
||||
- rst_driver/reset_driver.cc
|
||||
- rst_driver/reset_driver.h
|
||||
- reg_driver/csr_listing.def
|
||||
- reg_driver/reg_dpi.cc
|
||||
- reg_driver/register_driver.cc
|
||||
- reg_driver/register_driver.h
|
||||
- reg_driver/register_transaction.cc
|
||||
- reg_driver/register_transaction.h
|
||||
- env/env_dpi.cc
|
||||
- env/register_environment.cc
|
||||
- env/register_environment.h
|
||||
- env/simctrl.cc
|
||||
- env/simctrl.h
|
||||
- env/register_types.h
|
||||
- model/base_register.cc
|
||||
- model/base_register.h
|
||||
- model/register_model.cc
|
||||
- model/register_model.h
|
||||
file_type: user
|
||||
|
||||
files_verilator:
|
||||
depend:
|
||||
- lowrisc:dv_verilator:simutil_verilator
|
||||
files:
|
||||
- tb/tb_cs_registers.cc: { file_type: cppSource }
|
||||
- lint/verilator_waiver.vlt: {file_type: vlt}
|
||||
|
||||
files_sim:
|
||||
depend:
|
||||
- lowrisc:ibex:ibex_core
|
||||
files:
|
||||
- env/env_dpi.sv
|
||||
- rst_driver/rst_dpi.sv
|
||||
- reg_driver/reg_dpi.sv
|
||||
- tb/tb_cs_registers.sv
|
||||
file_type: systemVerilogSource
|
||||
|
||||
# Call make to build C++ shared object (workaround until natively supported by
|
||||
# fusesoc) see olofk/fusesoc#311
|
||||
scripts:
|
||||
build_so:
|
||||
filesets:
|
||||
- files_so
|
||||
cmd:
|
||||
- make
|
||||
- -C
|
||||
- ../src/lowrisc_ibex_tb_cs_registers_0
|
||||
|
||||
parameters:
|
||||
PMPEnable:
|
||||
datatype: int
|
||||
paramtype: vlogparam
|
||||
default: 1
|
||||
description: PMP enabled [1/0]
|
||||
PMPNumRegions:
|
||||
datatype: int
|
||||
paramtype: vlogparam
|
||||
default: 4
|
||||
description: Number of implemented PMP regions [0/16]
|
||||
PMPGranularity:
|
||||
datatype: int
|
||||
paramtype: vlogparam
|
||||
default: 0
|
||||
description: Minimum PMP matching granularity [0/31]
|
||||
MHPMCounterNum:
|
||||
datatype: int
|
||||
paramtype: vlogparam
|
||||
default: 8
|
||||
description: Number of performance monitor event counters [0/29]
|
||||
MHPMCounterWidth:
|
||||
datatype: int
|
||||
paramtype: vlogparam
|
||||
default: 40
|
||||
description: Bit width of performance monitor event counters [32/64]
|
||||
|
||||
targets:
|
||||
sim:
|
||||
default_tool: verilator
|
||||
toplevel: tb_cs_registers
|
||||
filesets:
|
||||
- files_sim
|
||||
- tool_verilator ? (files_verilator)
|
||||
hooks:
|
||||
pre_build:
|
||||
- build_so
|
||||
parameters:
|
||||
- PMPEnable
|
||||
- PMPNumRegions
|
||||
- PMPGranularity
|
||||
- MHPMCounterNum
|
||||
- MHPMCounterWidth
|
||||
|
||||
tools:
|
||||
vcs:
|
||||
vcs_options:
|
||||
- '-xlrm uniq_prior_final'
|
||||
- '../src/lowrisc_ibex_tb_cs_registers_0/build/bin/reg_dpi.so'
|
||||
- '-debug_access+all'
|
||||
|
||||
verilator:
|
||||
mode: cc
|
||||
libs:
|
||||
- '../src/lowrisc_ibex_tb_cs_registers_0/build/bin/reg_dpi.so'
|
||||
verilator_options:
|
||||
# Disabling tracing reduces compile times but doesn't have a
|
||||
# huge influence on runtime performance.
|
||||
- '--trace'
|
||||
- '--trace-fst' # this requires -DVM_TRACE_FMT_FST in CFLAGS below!
|
||||
- '--trace-structs'
|
||||
- '--trace-params'
|
||||
- '--trace-max-array 1024'
|
||||
- '-CFLAGS "-std=c++14 -Wall -DTOPLEVEL_NAME=tb_cs_registers -DVM_TRACE_FMT_FST -g"'
|
||||
- '-LDFLAGS "-pthread -lutil -lelf"'
|
||||
- "-Wall"
|
||||
- '-Wno-fatal' # Do not fail on (style) issues, only warn about them.
|
||||
@@ -0,0 +1,104 @@
|
||||
Ibex simulation for RISC-V Compliance Testing
|
||||
=============================================
|
||||
|
||||
This directory contains a compiled simulation of Ibex to be used as target
|
||||
in the [RISC-V Compliance Test](https://github.com/riscv/riscv-compliance).
|
||||
In addition to Ibex itself, it contains a 64 kB RAM and a memory-mapped helper
|
||||
module to interact with the software, e.g. to dump out the test signature and to
|
||||
end the simulation.
|
||||
|
||||
The simulation is designed for Verilator, but can be adapted to other simulators
|
||||
if needed.
|
||||
|
||||
How to run RISC-V Compliance on Ibex
|
||||
------------------------------------
|
||||
|
||||
0. Check your prerequisites
|
||||
To compile the simulation and run the compliance test suite you need to
|
||||
have the following tools installed:
|
||||
- Verilator
|
||||
- fusesoc
|
||||
- srecord (for `srec_cat`)
|
||||
- A RV32 compiler
|
||||
|
||||
On Ubuntu/Debian, install the required tools like this:
|
||||
|
||||
```sh
|
||||
sudo apt-get install srecord python3-pip
|
||||
pip3 install --user -U fusesoc
|
||||
```
|
||||
|
||||
We recommend installing Verilator from source as versions from Linux
|
||||
distributions are often outdated. See
|
||||
https://www.veripool.org/projects/verilator/wiki/Installing for installation
|
||||
instructions.
|
||||
|
||||
1. Build a simulation of Ibex
|
||||
|
||||
```sh
|
||||
cd $IBEX_REPO_BASE
|
||||
fusesoc --cores-root=. run --target=sim --setup --build lowrisc:ibex:ibex_riscv_compliance --RV32E=0 --RV32M=ibex_pkg::RV32MNone
|
||||
```
|
||||
|
||||
You can use the two compile-time options `--RV32M` and `--RV32E` to
|
||||
enable/disable the M and E ISA extensions, respectively.
|
||||
|
||||
You can now find the compiled simulation at `build/lowrisc_ibex_ibex_riscv_compliance_0.1/sim-verilator/Vibex_riscv_compliance`.
|
||||
|
||||
2. Get the RISC-V Compliance test suite
|
||||
|
||||
The upstream RISC-V compliance test suite supports Ibex out of the box.
|
||||
|
||||
```
|
||||
git clone https://github.com/riscv/riscv-compliance.git
|
||||
cd riscv-compliance
|
||||
```
|
||||
|
||||
3. Run the test suite
|
||||
```sh
|
||||
cd $RISCV_COMPLIANCE_REPO_BASE
|
||||
# adjust to match your compiler name
|
||||
export RISCV_PREFIX=riscv32-unknown-elf-
|
||||
# give the absolute path to the simulation binary compiled in step 1
|
||||
export TARGET_SIM=/path/to/your/Vibex_riscv_compliance
|
||||
|
||||
export RISCV_DEVICE=rv32imc
|
||||
export RISCV_TARGET=ibex
|
||||
|
||||
# Note: rv32imc does not include the I and M extension tests
|
||||
make RISCV_ISA=rv32i && make RISCV_ISA=rv32im && make RISCV_ISA=rv32imc && \
|
||||
make RISCV_ISA=rv32Zicsr && make RISCV_ISA=rv32Zifencei
|
||||
```
|
||||
|
||||
Compliance test suite system
|
||||
----------------------------
|
||||
|
||||
This directory contains a system designed especially to run the compliance test
|
||||
suite. The system consists of
|
||||
|
||||
- an Ibex core,
|
||||
- a bus,
|
||||
- a single-port memory for data and instructions,
|
||||
- a bus-attached test utility.
|
||||
|
||||
The CPU core boots from SRAM at address 0x0.
|
||||
|
||||
The test utility is used by the software to end the simulation, and to inform
|
||||
the simulator of the memory region where the test signature is stored.
|
||||
The bus host reads the test signature from memory.
|
||||
|
||||
The memory map of the whole system is as follows:
|
||||
|
||||
| Start | End | Size | Device |
|
||||
|---------|---------|-------|--------------------------------|
|
||||
| 0x0 | 0xFFFF | 64 kB | shared instruction/data memory |
|
||||
| 0x20000 | 0x203FF | 1 kB | test utility |
|
||||
|
||||
|
||||
The test utility provides the following registers relative to the base address.
|
||||
|
||||
| Address | R/W | Description |
|
||||
|---------|-----|---------------------------------------------------------------------|
|
||||
| 0x0 | W | Write any value to dump the test signature and terminate simulation |
|
||||
| 0x4 | W | Start address of the test signature |
|
||||
| 0x8 | W | End address of the test signature |
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "verilated_toplevel.h"
|
||||
#include "verilator_memutil.h"
|
||||
#include "verilator_sim_ctrl.h"
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
ibex_riscv_compliance top;
|
||||
VerilatorMemUtil memutil;
|
||||
VerilatorSimCtrl &simctrl = VerilatorSimCtrl::GetInstance();
|
||||
simctrl.SetTop(&top, &top.IO_CLK, &top.IO_RST_N,
|
||||
VerilatorSimCtrlFlags::ResetPolarityNegative);
|
||||
|
||||
MemArea ram(
|
||||
"TOP.ibex_riscv_compliance.u_ram.u_ram.gen_generic.u_impl_generic",
|
||||
64 * 1024 / 4, 4);
|
||||
|
||||
memutil.RegisterMemoryArea("ram", 0x0, &ram);
|
||||
simctrl.RegisterExtension(&memutil);
|
||||
|
||||
return simctrl.Exec(argc, argv).first;
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
CAPI=2:
|
||||
# Copyright lowRISC contributors.
|
||||
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
name: "lowrisc:ibex:ibex_riscv_compliance:0.1"
|
||||
description: "Ibex simulation for RISC-V compliance testing (using Verilator)"
|
||||
filesets:
|
||||
files_sim:
|
||||
depend:
|
||||
- lowrisc:ibex:ibex_top_tracing
|
||||
- lowrisc:ibex:sim_shared
|
||||
files:
|
||||
- rtl/ibex_riscv_compliance.sv
|
||||
- rtl/riscv_testutil.sv
|
||||
file_type: systemVerilogSource
|
||||
|
||||
files_verilator:
|
||||
depend:
|
||||
- lowrisc:dv_verilator:memutil_verilator
|
||||
- lowrisc:dv_verilator:simutil_verilator
|
||||
files:
|
||||
- ibex_riscv_compliance.cc: { file_type: cppSource }
|
||||
- lint/verilator_waiver.vlt: {file_type: vlt}
|
||||
|
||||
parameters:
|
||||
RV32E:
|
||||
datatype: int
|
||||
paramtype: vlogparam
|
||||
default: 0
|
||||
description: "Enable the E ISA extension (reduced register set) [0/1]"
|
||||
|
||||
RV32M:
|
||||
datatype: str
|
||||
default: ibex_pkg::RV32MFast
|
||||
paramtype: vlogdefine
|
||||
description: "RV32M implementation parameter enum. See the ibex_pkg::rv32m_e enum in ibex_pkg.sv for permitted values."
|
||||
|
||||
RV32B:
|
||||
datatype: str
|
||||
default: ibex_pkg::RV32BNone
|
||||
paramtype: vlogdefine
|
||||
description: "Bitmanip implementation parameter enum. See the ibex_pkg::rv32b_e enum in ibex_pkg.sv for permitted values."
|
||||
|
||||
RegFile:
|
||||
datatype: str
|
||||
default: ibex_pkg::RegFileFF
|
||||
paramtype: vlogdefine
|
||||
description: "Register file implementation parameter enum. See the ibex_pkg::regfile_e enum in ibex_pkg.sv for permitted values."
|
||||
|
||||
ICache:
|
||||
datatype: int
|
||||
default: 0
|
||||
paramtype: vlogparam
|
||||
description: "Enable instruction cache"
|
||||
|
||||
ICacheECC:
|
||||
datatype: int
|
||||
default: 0
|
||||
paramtype: vlogparam
|
||||
description: "Enable ECC protection in instruction cache"
|
||||
|
||||
BranchTargetALU:
|
||||
datatype: int
|
||||
paramtype: vlogparam
|
||||
default: 0
|
||||
description: "Enables separate branch target ALU (increasing branch performance EXPERIMENTAL)"
|
||||
|
||||
WritebackStage:
|
||||
datatype: int
|
||||
paramtype: vlogparam
|
||||
default: 0
|
||||
description: "Enables third pipeline stage (EXPERIMENTAL)"
|
||||
|
||||
BranchPredictor:
|
||||
datatype: int
|
||||
paramtype: vlogparam
|
||||
default: 0
|
||||
description: "Enables static branch prediction (EXPERIMENTAL)"
|
||||
|
||||
DbgTriggerEn:
|
||||
datatype: int
|
||||
default: 0
|
||||
paramtype: vlogparam
|
||||
description: "Enable support for debug triggers. "
|
||||
|
||||
SecureIbex:
|
||||
datatype: int
|
||||
default: 0
|
||||
paramtype: vlogparam
|
||||
description: "Enables security hardening features (EXPERIMENTAL) [0/1]"
|
||||
|
||||
ICacheScramble:
|
||||
datatype: int
|
||||
default: 0
|
||||
paramtype: vlogparam
|
||||
description: "Enables ICache scrambling feature (EXPERIMENTAL) [0/1]"
|
||||
|
||||
PMPEnable:
|
||||
datatype: int
|
||||
default: 0
|
||||
paramtype: vlogparam
|
||||
description: "Enable PMP"
|
||||
|
||||
PMPGranularity:
|
||||
datatype: int
|
||||
default: 0
|
||||
paramtype: vlogparam
|
||||
description: "Granularity of NAPOT range, 0 = 4 byte, 1 = byte, 2 = 16 byte, 3 = 32 byte etc"
|
||||
|
||||
PMPNumRegions:
|
||||
datatype: int
|
||||
default: 4
|
||||
paramtype: vlogparam
|
||||
description: "Number of PMP regions"
|
||||
|
||||
MHPMCounterNum:
|
||||
datatype: int
|
||||
paramtype: vlogparam
|
||||
default: 0
|
||||
description: Number of performance monitor event counters [0/29]
|
||||
|
||||
MHPMCounterWidth:
|
||||
datatype: int
|
||||
paramtype: vlogparam
|
||||
default: 40
|
||||
description: Bit width of performance monitor event counters [32/64]
|
||||
|
||||
targets:
|
||||
sim:
|
||||
default_tool: verilator
|
||||
filesets:
|
||||
- files_sim
|
||||
- tool_verilator ? (files_verilator)
|
||||
parameters:
|
||||
- RV32E
|
||||
- RV32M
|
||||
- RV32B
|
||||
- RegFile
|
||||
- ICache
|
||||
- ICacheECC
|
||||
- BranchTargetALU
|
||||
- WritebackStage
|
||||
- BranchPredictor
|
||||
- DbgTriggerEn
|
||||
- SecureIbex
|
||||
- ICacheScramble
|
||||
- PMPEnable
|
||||
- PMPGranularity
|
||||
- PMPNumRegions
|
||||
- MHPMCounterNum
|
||||
- MHPMCounterWidth
|
||||
toplevel: ibex_riscv_compliance
|
||||
tools:
|
||||
verilator:
|
||||
mode: cc
|
||||
verilator_options:
|
||||
# Disabling tracing reduces compile times but doesn't have a
|
||||
# huge influence on runtime performance.
|
||||
- '--trace'
|
||||
- '--trace-fst' # this requires -DVM_TRACE_FMT_FST in CFLAGS below!
|
||||
- '--trace-structs'
|
||||
- '--trace-params'
|
||||
- '--trace-max-array 1024'
|
||||
- '-CFLAGS "-std=c++11 -Wall -DVM_TRACE_FMT_FST -DTOPLEVEL_NAME=ibex_riscv_compliance -g"'
|
||||
- '-LDFLAGS "-pthread -lutil -lelf"'
|
||||
- "-Wall"
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Lint waivers for processing riscv_compliance RTL with Verilator
|
||||
//
|
||||
// This should be used for rules applying to things like testbench
|
||||
// top-levels. For rules that apply to the actual design (files in the
|
||||
// 'rtl' directory), see verilator_waiver_rtl.vlt in the same
|
||||
// directory.
|
||||
//
|
||||
// See https://www.veripool.org/projects/verilator/wiki/Manual-verilator#CONFIGURATION-FILES
|
||||
// for documentation.
|
||||
//
|
||||
// Important: This file must included *before* any other Verilog file is read.
|
||||
// Otherwise, only global waivers are applied, but not file-specific waivers.
|
||||
|
||||
`verilator_config
|
||||
|
||||
// We have some boolean top-level parameters in e.g. simple_system.sv.
|
||||
// When building with fusesoc, these get set with defines like
|
||||
// -GRV32E=1 (rather than -GRV32E=1'b1), leading to warnings like:
|
||||
//
|
||||
// Operator VAR '<varname>' expects 1 bits on the Initial value, but
|
||||
// Initial value's CONST '32'h1' generates 32 bits.
|
||||
//
|
||||
// This signoff rule ignores errors like this. Note that it only
|
||||
// matches when you set a 1-bit value to a literal 1, so it won't hide
|
||||
// silly mistakes like setting it to 2.
|
||||
//
|
||||
lint_off -rule WIDTH -file "*/rtl/ibex_riscv_compliance.sv"
|
||||
-match "*expects 1 bits*Initial value's CONST '32'h1'*"
|
||||
@@ -0,0 +1,254 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* Ibex simulation to run the RISC-V compliance test on
|
||||
*
|
||||
* This is a toplevel wrapper for Ibex with helpers to run the RISC-V compliance
|
||||
* test. It is designed for Verilator, but should equally work for other
|
||||
* simulators (if the top-level clk and rst ports are replaced with a generated
|
||||
* clock).
|
||||
*/
|
||||
module ibex_riscv_compliance (
|
||||
input IO_CLK,
|
||||
input IO_RST_N
|
||||
);
|
||||
|
||||
parameter bit PMPEnable = 1'b0;
|
||||
parameter int unsigned PMPGranularity = 0;
|
||||
parameter int unsigned PMPNumRegions = 4;
|
||||
parameter int unsigned MHPMCounterNum = 0;
|
||||
parameter int unsigned MHPMCounterWidth = 40;
|
||||
parameter bit RV32E = 1'b0;
|
||||
parameter ibex_pkg::rv32m_e RV32M = ibex_pkg::RV32MFast;
|
||||
parameter ibex_pkg::rv32b_e RV32B = ibex_pkg::RV32BNone;
|
||||
parameter ibex_pkg::regfile_e RegFile = ibex_pkg::RegFileFF;
|
||||
parameter bit BranchTargetALU = 1'b0;
|
||||
parameter bit WritebackStage = 1'b0;
|
||||
parameter bit ICache = 1'b0;
|
||||
parameter bit ICacheECC = 1'b0;
|
||||
parameter bit BranchPredictor = 1'b0;
|
||||
parameter bit SecureIbex = 1'b0;
|
||||
parameter bit ICacheScramble = 1'b0;
|
||||
parameter bit DbgTriggerEn = 1'b0;
|
||||
|
||||
logic clk_sys, rst_sys_n;
|
||||
|
||||
assign clk_sys = IO_CLK;
|
||||
assign rst_sys_n = IO_RST_N;
|
||||
|
||||
// Bus hosts, ordered in decreasing priority
|
||||
typedef enum logic[1:0] {
|
||||
TestUtilHost,
|
||||
CoreD,
|
||||
CoreI
|
||||
} bus_host_e;
|
||||
|
||||
typedef enum logic {
|
||||
Ram,
|
||||
TestUtilDevice
|
||||
} bus_device_e;
|
||||
|
||||
localparam int unsigned NrDevices = 2;
|
||||
localparam int unsigned NrHosts = 3;
|
||||
// 64 kB RAM. Must be a power of 2. Check bus configuration below when changing.
|
||||
localparam int unsigned RamSizeWords = 64*1024/4;
|
||||
|
||||
// host and device signals
|
||||
logic host_req [NrHosts];
|
||||
logic host_gnt [NrHosts];
|
||||
logic [31:0] host_addr [NrHosts];
|
||||
logic host_we [NrHosts];
|
||||
logic [ 3:0] host_be [NrHosts];
|
||||
logic [31:0] host_wdata [NrHosts];
|
||||
logic host_rvalid [NrHosts];
|
||||
logic [31:0] host_rdata [NrHosts];
|
||||
logic host_err [NrHosts];
|
||||
|
||||
logic [6:0] ibex_data_rdata_intg;
|
||||
logic [6:0] ibex_instr_rdata_intg;
|
||||
|
||||
// devices (slaves)
|
||||
logic device_req [NrDevices];
|
||||
logic [31:0] device_addr [NrDevices];
|
||||
logic device_we [NrDevices];
|
||||
logic [ 3:0] device_be [NrDevices];
|
||||
logic [31:0] device_wdata [NrDevices];
|
||||
logic device_rvalid [NrDevices];
|
||||
logic [31:0] device_rdata [NrDevices];
|
||||
logic device_err [NrDevices];
|
||||
|
||||
// Device address mapping
|
||||
logic [31:0] cfg_device_addr_base [NrDevices];
|
||||
logic [31:0] cfg_device_addr_mask [NrDevices];
|
||||
assign cfg_device_addr_base[Ram] = 32'h0;
|
||||
assign cfg_device_addr_mask[Ram] = ~32'(RamSizeWords * 4 - 1);
|
||||
assign cfg_device_addr_base[TestUtilDevice] = 32'h20000;
|
||||
assign cfg_device_addr_mask[TestUtilDevice] = ~32'h3FF; // 1 kB
|
||||
|
||||
bus #(
|
||||
.NrDevices (NrDevices),
|
||||
.NrHosts (NrHosts ),
|
||||
.DataWidth (32 ),
|
||||
.AddressWidth(32 )
|
||||
) u_bus (
|
||||
.clk_i (clk_sys),
|
||||
.rst_ni (rst_sys_n),
|
||||
|
||||
.host_req_i (host_req ),
|
||||
.host_gnt_o (host_gnt ),
|
||||
.host_addr_i (host_addr ),
|
||||
.host_we_i (host_we ),
|
||||
.host_be_i (host_be ),
|
||||
.host_wdata_i (host_wdata ),
|
||||
.host_rvalid_o (host_rvalid ),
|
||||
.host_rdata_o (host_rdata ),
|
||||
.host_err_o (host_err ),
|
||||
|
||||
.device_req_o (device_req ),
|
||||
.device_addr_o (device_addr ),
|
||||
.device_we_o (device_we ),
|
||||
.device_be_o (device_be ),
|
||||
.device_wdata_o (device_wdata ),
|
||||
.device_rvalid_i (device_rvalid),
|
||||
.device_rdata_i (device_rdata ),
|
||||
.device_err_i (device_err ),
|
||||
|
||||
.cfg_device_addr_base,
|
||||
.cfg_device_addr_mask
|
||||
);
|
||||
|
||||
if (SecureIbex) begin : g_mem_rdata_ecc
|
||||
logic [31:0] unused_data_rdata;
|
||||
logic [31:0] unused_instr_rdata;
|
||||
|
||||
prim_secded_inv_39_32_enc u_data_rdata_intg_gen (
|
||||
.data_i (host_rdata[CoreD]),
|
||||
.data_o ({ibex_data_rdata_intg, unused_data_rdata})
|
||||
);
|
||||
|
||||
prim_secded_inv_39_32_enc u_instr_rdata_intg_gen (
|
||||
.data_i (host_rdata[CoreI]),
|
||||
.data_o ({ibex_instr_rdata_intg, unused_instr_rdata})
|
||||
);
|
||||
end else begin : g_no_mem_rdata_ecc
|
||||
assign ibex_data_rdata_intg = '0;
|
||||
assign ibex_instr_rdata_intg = '0;
|
||||
end
|
||||
|
||||
ibex_top_tracing #(
|
||||
.PMPEnable (PMPEnable ),
|
||||
.PMPGranularity (PMPGranularity ),
|
||||
.PMPNumRegions (PMPNumRegions ),
|
||||
.MHPMCounterNum (MHPMCounterNum ),
|
||||
.MHPMCounterWidth (MHPMCounterWidth ),
|
||||
.RV32E (RV32E ),
|
||||
.RV32M (RV32M ),
|
||||
.RV32B (RV32B ),
|
||||
.RegFile (RegFile ),
|
||||
.BranchTargetALU (BranchTargetALU ),
|
||||
.WritebackStage (WritebackStage ),
|
||||
.ICache (ICache ),
|
||||
.ICacheECC (ICacheECC ),
|
||||
.BranchPredictor (BranchPredictor ),
|
||||
.DbgTriggerEn (DbgTriggerEn ),
|
||||
.SecureIbex (SecureIbex ),
|
||||
.ICacheScramble (ICacheScramble ),
|
||||
.DmHaltAddr (32'h00000000 ),
|
||||
.DmExceptionAddr (32'h00000000 )
|
||||
) u_top (
|
||||
.clk_i (clk_sys ),
|
||||
.rst_ni (rst_sys_n ),
|
||||
|
||||
.test_en_i ('b0 ),
|
||||
.scan_rst_ni (1'b1 ),
|
||||
.ram_cfg_i ('b0 ),
|
||||
|
||||
.hart_id_i (32'b0 ),
|
||||
// First instruction executed is at 0x0 + 0x80
|
||||
.boot_addr_i (32'h00000000 ),
|
||||
|
||||
.instr_req_o (host_req[CoreI] ),
|
||||
.instr_gnt_i (host_gnt[CoreI] ),
|
||||
.instr_rvalid_i (host_rvalid[CoreI] ),
|
||||
.instr_addr_o (host_addr[CoreI] ),
|
||||
.instr_rdata_i (host_rdata[CoreI] ),
|
||||
.instr_rdata_intg_i (ibex_instr_rdata_intg),
|
||||
.instr_err_i (host_err[CoreI] ),
|
||||
|
||||
.data_req_o (host_req[CoreD] ),
|
||||
.data_gnt_i (host_gnt[CoreD] ),
|
||||
.data_rvalid_i (host_rvalid[CoreD] ),
|
||||
.data_we_o (host_we[CoreD] ),
|
||||
.data_be_o (host_be[CoreD] ),
|
||||
.data_addr_o (host_addr[CoreD] ),
|
||||
.data_wdata_o (host_wdata[CoreD] ),
|
||||
.data_wdata_intg_o ( ),
|
||||
.data_rdata_i (host_rdata[CoreD] ),
|
||||
.data_rdata_intg_i (ibex_data_rdata_intg ),
|
||||
.data_err_i (host_err[CoreD] ),
|
||||
|
||||
.irq_software_i (1'b0 ),
|
||||
.irq_timer_i (1'b0 ),
|
||||
.irq_external_i (1'b0 ),
|
||||
.irq_fast_i (15'b0 ),
|
||||
.irq_nm_i (1'b0 ),
|
||||
|
||||
.scramble_key_valid_i ('0 ),
|
||||
.scramble_key_i ('0 ),
|
||||
.scramble_nonce_i ('0 ),
|
||||
.scramble_req_o ( ),
|
||||
|
||||
.debug_req_i ('b0 ),
|
||||
.crash_dump_o ( ),
|
||||
.double_fault_seen_o ( ),
|
||||
|
||||
.fetch_enable_i (ibex_pkg::IbexMuBiOn ),
|
||||
.alert_minor_o ( ),
|
||||
.alert_major_internal_o ( ),
|
||||
.alert_major_bus_o ( ),
|
||||
.core_sleep_o ( )
|
||||
);
|
||||
|
||||
// SRAM block for instruction and data storage
|
||||
ram_1p #(
|
||||
.Depth(RamSizeWords)
|
||||
) u_ram (
|
||||
.clk_i (clk_sys ),
|
||||
.rst_ni (rst_sys_n ),
|
||||
.req_i (device_req[Ram] ),
|
||||
.we_i (device_we[Ram] ),
|
||||
.be_i (device_be[Ram] ),
|
||||
.addr_i (device_addr[Ram] ),
|
||||
.wdata_i (device_wdata[Ram] ),
|
||||
.rvalid_o (device_rvalid[Ram]),
|
||||
.rdata_o (device_rdata[Ram] )
|
||||
);
|
||||
|
||||
// RISC-V test utility, used by the RISC-V compliance test to interact with
|
||||
// the simulator.
|
||||
riscv_testutil
|
||||
u_riscv_testutil(
|
||||
.clk_i (clk_sys ),
|
||||
.rst_ni (rst_sys_n ),
|
||||
|
||||
// Device port
|
||||
.dev_req_i (device_req[TestUtilDevice] ),
|
||||
.dev_we_i (device_we[TestUtilDevice] ),
|
||||
.dev_addr_i (device_addr[TestUtilDevice] ),
|
||||
.dev_wdata_i (device_wdata[TestUtilDevice] ),
|
||||
.dev_rvalid_o (device_rvalid[TestUtilDevice]),
|
||||
.dev_rdata_o (device_rdata[TestUtilDevice] ),
|
||||
.dev_be_i (device_be[TestUtilDevice] ),
|
||||
.dev_err_o (device_err[TestUtilDevice] ),
|
||||
|
||||
// Host port
|
||||
.host_req_o (host_req[TestUtilHost] ),
|
||||
.host_gnt_i (host_gnt[TestUtilHost] ),
|
||||
.host_rvalid_i (host_rvalid[TestUtilHost] ),
|
||||
.host_addr_o (host_addr[TestUtilHost] ),
|
||||
.host_rdata_i (host_rdata[TestUtilHost] )
|
||||
);
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,172 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* RISC-V Compliance Test helper module (simulation only)
|
||||
*
|
||||
* This module lets the RISC-V compliance test software interact with the
|
||||
* "outside world".
|
||||
*
|
||||
* It consists of a bus device and a bus host.
|
||||
*
|
||||
* Through the bus device, the software can interact with the outside world and
|
||||
* configure this module. The following registers are supported:
|
||||
* 0x0: read the signature and halt the execution
|
||||
* 0x4: set the signature start address
|
||||
* 0x8: set the signature end address
|
||||
*
|
||||
* When register 0x0 is written with an arbitrary value, the test signature is
|
||||
* read through the bus device, and written to STDOUT, prefixed with
|
||||
* "SIGNATURE: ".
|
||||
*/
|
||||
module riscv_testutil (
|
||||
input clk_i,
|
||||
input rst_ni,
|
||||
|
||||
// bus device (slave) interface
|
||||
input dev_req_i,
|
||||
input dev_we_i,
|
||||
input [31:0] dev_addr_i,
|
||||
input [31:0] dev_wdata_i,
|
||||
input [ 3:0] dev_be_i,
|
||||
|
||||
output logic dev_rvalid_o,
|
||||
output logic [31:0] dev_rdata_o,
|
||||
output logic dev_err_o,
|
||||
|
||||
// bus host (master) interface
|
||||
output logic host_req_o,
|
||||
input host_gnt_i,
|
||||
input host_rvalid_i,
|
||||
output logic [31:0] host_addr_o,
|
||||
input [31:0] host_rdata_i
|
||||
);
|
||||
// ======= Bus device for interaction with software ======= //
|
||||
|
||||
localparam ADDR_HALT = 0;
|
||||
localparam ADDR_SET_BEGIN_SIGNATURE = 4;
|
||||
localparam ADDR_SET_END_SIGNATURE = 8;
|
||||
|
||||
// 1 kB address space for this peripheral
|
||||
logic [21:0] unused_addr;
|
||||
assign unused_addr = dev_addr_i[31:10];
|
||||
|
||||
logic [31:0] begin_signature_addr_d, begin_signature_addr_q;
|
||||
logic [31:0] end_signature_addr_d, end_signature_addr_q;
|
||||
|
||||
logic read_signature_and_terminate;
|
||||
always_comb begin
|
||||
read_signature_and_terminate = 1'b0;
|
||||
begin_signature_addr_d = begin_signature_addr_q;
|
||||
end_signature_addr_d = end_signature_addr_q;
|
||||
|
||||
if (dev_we_i && dev_req_i) begin
|
||||
case (dev_addr_i[9:0])
|
||||
ADDR_HALT: begin
|
||||
read_signature_and_terminate = 1'b1;
|
||||
end
|
||||
ADDR_SET_BEGIN_SIGNATURE: begin
|
||||
begin_signature_addr_d = dev_wdata_i;
|
||||
end
|
||||
ADDR_SET_END_SIGNATURE: begin
|
||||
end_signature_addr_d = dev_wdata_i;
|
||||
end
|
||||
default: ;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
always_ff @(posedge clk_i) begin
|
||||
begin_signature_addr_q <= begin_signature_addr_d;
|
||||
end_signature_addr_q <= end_signature_addr_d;
|
||||
end
|
||||
|
||||
// all responses are in the next cycle
|
||||
always_ff @(posedge clk_i or negedge rst_ni) begin
|
||||
if (!rst_ni) begin
|
||||
dev_rvalid_o <= 1'b0;
|
||||
end else begin
|
||||
dev_rvalid_o <= dev_req_i;
|
||||
end
|
||||
end
|
||||
|
||||
// The interface is write-only, and only supports 32-bit writes. If
|
||||
// either of these checks fails, raise dev_err_o on the next cycle.
|
||||
always_ff @(posedge clk_i or negedge rst_ni) begin
|
||||
if (!rst_ni) begin
|
||||
dev_err_o <= 1'b0;
|
||||
end else begin
|
||||
dev_err_o <= (~dev_we_i | dev_be_i != 4'hf) & dev_req_i;
|
||||
end
|
||||
end
|
||||
|
||||
// Since the interface is write-only, tie rdata to 0.
|
||||
assign dev_rdata_o = 32'h0;
|
||||
|
||||
|
||||
// ======= FSM: Read signature from memory and dump it to STDOUT ======= //
|
||||
|
||||
typedef enum logic [1:0] {
|
||||
WAIT, READ, READ_FINISH, TERMINATE
|
||||
} readsig_state_e;
|
||||
|
||||
readsig_state_e state_q, state_d;
|
||||
|
||||
logic [31:0] read_addr_d, read_addr_q;
|
||||
always_comb begin
|
||||
state_d = state_q;
|
||||
read_addr_d = read_addr_q;
|
||||
unique case (state_q)
|
||||
WAIT: begin
|
||||
if (read_signature_and_terminate) begin
|
||||
$display("Reading signature from 0x%x to 0x%x",
|
||||
begin_signature_addr_q, end_signature_addr_q);
|
||||
state_d = READ;
|
||||
read_addr_d = begin_signature_addr_q;
|
||||
end
|
||||
end
|
||||
|
||||
READ: begin
|
||||
if (host_gnt_i) begin
|
||||
read_addr_d = read_addr_q + 4;
|
||||
if (read_addr_d == end_signature_addr_q) begin
|
||||
state_d = READ_FINISH;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
READ_FINISH: begin
|
||||
if (host_rvalid_i) begin
|
||||
state_d = TERMINATE;
|
||||
end
|
||||
end
|
||||
|
||||
TERMINATE: begin
|
||||
$display("Terminating simulation by software request.");
|
||||
$finish;
|
||||
end
|
||||
|
||||
default: ;
|
||||
endcase
|
||||
end
|
||||
|
||||
// These are the address and read request bits, respectively of the
|
||||
// TestUtilHost master port.
|
||||
assign host_addr_o = read_addr_q;
|
||||
assign host_req_o = (state_q == READ);
|
||||
|
||||
always_ff @(posedge clk_i or negedge rst_ni) begin
|
||||
if (!rst_ni) begin
|
||||
state_q <= WAIT;
|
||||
read_addr_q <= 0;
|
||||
end else begin
|
||||
state_q <= state_d;
|
||||
read_addr_q <= read_addr_d;
|
||||
|
||||
if (host_rvalid_i) begin
|
||||
$display("SIGNATURE: 0x%x", host_rdata_i);
|
||||
end
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
@@ -0,0 +1,11 @@
|
||||
# Bus parameters for DV utilities
|
||||
|
||||
The Ibex DV code uses a `dv_utils` support library vendored from
|
||||
OpenTitan. This library needs some basic parameters: things like
|
||||
bus address and data widths and similar.
|
||||
|
||||
The `dv_utils` library is supposed to be parametric, in that it should
|
||||
work with any such widths, but a project that imports the library
|
||||
needs to supply them in a SystemVerilog package called
|
||||
`bus_params_pkg`. This directory contains that package, which can be
|
||||
imported with the fusesoc core `lowrisc:ibex:bus_params_pkg`.
|
||||
@@ -0,0 +1,24 @@
|
||||
CAPI=2:
|
||||
# Copyright lowRISC contributors.
|
||||
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#
|
||||
# An Ibex-specific replacement for OpenTitan's top_pkg. This should
|
||||
# *only* be used by Ibex DV code: it defines a (SystemVerilog) package
|
||||
# with the same name as OpenTitan's top_pkg, so things will get very
|
||||
# confused if both are used at once.
|
||||
#
|
||||
|
||||
name: "lowrisc:ibex:bus_params_pkg"
|
||||
description: "Top-level constants for Ibex, used by DV code"
|
||||
filesets:
|
||||
files_rtl:
|
||||
files:
|
||||
- bus_params_pkg.sv
|
||||
file_type: systemVerilogSource
|
||||
|
||||
targets:
|
||||
default:
|
||||
filesets:
|
||||
- files_rtl
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package bus_params_pkg;
|
||||
|
||||
// Bus address width
|
||||
localparam int BUS_AW = 32;
|
||||
|
||||
// Bus data width (must be a multiple of 8)
|
||||
localparam int BUS_DW = 32;
|
||||
|
||||
// Bus data mask width (number of byte lanes)
|
||||
localparam int BUS_DBW = (BUS_DW >> 3);
|
||||
|
||||
// Bus transfer size width (number of bits needed to select the number of bytes)
|
||||
localparam int BUS_SZW = $clog2($clog2(BUS_DBW) + 1);
|
||||
|
||||
// Bus address info (source) width
|
||||
localparam int BUS_AIW = 8;
|
||||
|
||||
// Bus data info (source) width
|
||||
localparam int BUS_DIW = 1;
|
||||
|
||||
// Bus data user width
|
||||
localparam int BUS_DUW = 16;
|
||||
|
||||
endpackage
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
{
|
||||
project: ibex
|
||||
|
||||
// These keys are expected by dvsim.py, so we have to set them to something.
|
||||
doc_server: bogus.doc.server
|
||||
results_server: bogus.results.server
|
||||
results_html_name: report.html
|
||||
|
||||
// Default directory structure for the output
|
||||
scratch_base_path: "{scratch_root}/{dut}.{flow}.{tool}"
|
||||
scratch_path: "{scratch_base_path}/{branch}"
|
||||
tool_srcs_dir: "{scratch_path}/{tool}"
|
||||
|
||||
// The current design level
|
||||
design_level: "ip"
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
# This is generated by VCS when running DV simulations with WAVE=1.
|
||||
ucli.key
|
||||
|
||||
# This is generated by UVM when running simulations and doesn't seem
|
||||
# to be something you can disable.
|
||||
tr_db.log
|
||||
|
||||
# This is the default output directory in dv/uvm/core_ibex and
|
||||
# contains auto-generated files from building and running tests.
|
||||
out
|
||||
|
||||
# This is generated by the Makefile based on the ibex configuration
|
||||
riscv_dv_extension/riscv_core_setting.sv
|
||||
|
||||
# This is generated by Xcelium when running DV simulations, even with WAVE=0
|
||||
waves.shm
|
||||
|
||||
# Log files generated by Cadence tools when running DV simulations
|
||||
xrun.history
|
||||
xrun.log
|
||||
xmsc.log
|
||||
|
||||
# Generated by coverage
|
||||
imc.key
|
||||
mdv.log
|
||||
@@ -0,0 +1,98 @@
|
||||
# Copyright lowRISC contributors.
|
||||
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# Top-Level Makefile
|
||||
###############################################################################
|
||||
|
||||
.SUFFIXES:
|
||||
export
|
||||
|
||||
# Explicitly ask for the bash shell
|
||||
SHELL := bash
|
||||
|
||||
# Build the 'all' target by default, override with e.g. GOAL=rtl_tb_compile
|
||||
GOAL ?= all
|
||||
|
||||
###############################################################################
|
||||
# CONFIGURATION KNOBS
|
||||
|
||||
# Seed for instruction generator and RTL simulation
|
||||
#
|
||||
# By default, SEED is set to a different value on each run by picking a random
|
||||
# value in the Makefile. For overnight testing, a sensible seed might be
|
||||
# something like the output of "date +%y%m%d". For regression testing, you'll
|
||||
# need to make sure that a the seed for a failed test "sticks" (so we don't
|
||||
# start passing again without fixing the bug).
|
||||
SEED := $(shell echo $$RANDOM)
|
||||
|
||||
# Enable waveform dumping
|
||||
WAVES := 0
|
||||
# Enable coverage dump
|
||||
COV := 0
|
||||
# RTL simulator (xlm, vcs, questa, dsim, )
|
||||
SIMULATOR := xlm
|
||||
# ISS (spike, ovpsim)
|
||||
ISS := spike
|
||||
# Test name (default: full regression)
|
||||
TEST := all
|
||||
RISCV-DV-TESTLIST := riscv_dv_extension/testlist.yaml
|
||||
DIRECTED-TESTLIST := directed_tests/directed_testlist.yaml
|
||||
# Verbose logging
|
||||
VERBOSE := 0
|
||||
# Number of iterations for each test, assign a non-empty value to override the
|
||||
# iteration count in the test list
|
||||
ITERATIONS :=
|
||||
# Pass/fail signature address at the end of test (see riscv_dv handshake documentation)
|
||||
SIGNATURE_ADDR := 8ffffffc
|
||||
|
||||
### Ibex top level parameters ###
|
||||
IBEX_CONFIG := opentitan
|
||||
|
||||
# Path to DUT used for coverage reports
|
||||
DUT_COV_RTL_PATH := "ibex_top"
|
||||
|
||||
###############################################################################
|
||||
|
||||
# Setup the necessary paths for all python scripts to find all other relevant modules.
|
||||
export PYTHONPATH := $(shell python3 -c 'from scripts.setup_imports import get_pythonpath; get_pythonpath()')
|
||||
|
||||
# We run the 'create_metadata' step in this top-level makefile, so the sub-make
|
||||
# invocations can query the generated metadata objects. Since the targets/dependencies
|
||||
# are extracted from this metadata, it must be query-able in the makefile 'immediate' stage.
|
||||
.PHONY: run
|
||||
run:
|
||||
@env PYTHONPATH=$(PYTHONPATH) python3 ./scripts/metadata.py \
|
||||
--op "create_metadata" \
|
||||
--dir-metadata $(METADATA-DIR) \
|
||||
--dir-out $(OUT-DIR) \
|
||||
--args-list "\
|
||||
SEED=$(SEED) WAVES=$(WAVES) COV=$(COV) SIMULATOR=$(SIMULATOR) \
|
||||
ISS=$(ISS) TEST=$(TEST) VERBOSE=$(VERBOSE) ITERATIONS=$(ITERATIONS) \
|
||||
SIGNATURE_ADDR=$(SIGNATURE_ADDR) IBEX_CONFIG=$(IBEX_CONFIG) \
|
||||
DUT_COV_RTL_PATH=$(DUT_COV_RTL_PATH)"
|
||||
@$(MAKE) --file wrapper.mk --environment-overrides --no-print-directory $(GOAL)
|
||||
|
||||
###############################################################################
|
||||
|
||||
# This is the top-level output directory. Everything we generate goes in
|
||||
# here.
|
||||
OUT := out
|
||||
|
||||
# Derived directories from $(OUT), used for stuff that's built once or
|
||||
# stuff that gets run for each seed, respectively. Using OUT-DIR on
|
||||
# the way avoids ugly double slashes if $(OUT) happens to end in a /.
|
||||
export OUT-DIR := $(dir $(OUT)/)
|
||||
export METADATA-DIR := $(OUT-DIR)metadata
|
||||
|
||||
# riscv-dv extension directory
|
||||
export EXT_DIR := riscv_dv_extension
|
||||
|
||||
###############################################################################
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf $(OUT-DIR)
|
||||
rm -f $(EXT_DIR)/riscv_core_setting.sv
|
||||
|
||||
###############################################################################
|
||||
@@ -0,0 +1,35 @@
|
||||
# DV for the ibex core
|
||||
|
||||
For detailed documention on how Ibex's verification works, please have a look at [the dedicated documentation page](https://ibex-core.readthedocs.io/en/latest/03_reference/verification.html).
|
||||
This README provides a quick start guide to get things running.
|
||||
|
||||
## Prerequisites
|
||||
You need to have Xcelium available on your machine.
|
||||
You can check whether you have it available by running: `xrun --verison`
|
||||
|
||||
You also need Spike to be able to compare to in the cosimulation.
|
||||
We use a lowRISC specific Spike which you can find [on its own GitHub page](https://github.com/lowRISC/riscv-isa-sim/tree/ibex_cosim).
|
||||
Some quick build instructions from within the `riscv-isa-sim` repo:
|
||||
```bash
|
||||
mkdir build
|
||||
cd build
|
||||
../configure --enable-commitlog --enable-misaligned --prefix=$SPIKE_INSTALL_DIR
|
||||
make
|
||||
make install
|
||||
export SPIKE_PATH=$SPIKE_INSTALL_DIR/bin
|
||||
export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$SPIKE_INSTALL_DIR/lib/pkgconfig
|
||||
```
|
||||
|
||||
You will need the [RISC-V toolchain](https://github.com/riscv-collab/riscv-gnu-toolchain).
|
||||
You'll need to add this to your path and then also set the following environment variables:
|
||||
```bash
|
||||
export RISCV_GCC=riscv32-unknown-elf-gcc
|
||||
export RISCV_OBJCOPY=riscv32-unknown-elf-objcopy
|
||||
```
|
||||
|
||||
## Running tests
|
||||
|
||||
To run tests you can make variations of the following command, where you replace `$TEST_NAME` with the test (or a series of comma-separated tests) that you would like to run as specified in `dv/uvm/core_ibex/riscv_dv_extension/testlist.yaml`:
|
||||
```bash
|
||||
make --keep-going IBEX_CONFIG=opentitan SIMULATOR=xlm ISS=spike ITERATIONS=1 SEED=1 TEST=$TEST_NAME WAVES=0 COV=0
|
||||
```
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include <time.h>
|
||||
long int get_unix_timestamp() { return time(NULL); }
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
`ifndef DATE_DPI_SVH
|
||||
`define DATE_DPI_SVH
|
||||
|
||||
import "DPI-C" function longint get_unix_timestamp();
|
||||
|
||||
`endif
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
interface core_ibex_ifetch_if(input logic clk);
|
||||
logic reset;
|
||||
|
||||
logic fetch_ready;
|
||||
logic fetch_valid;
|
||||
logic [31:0] fetch_rdata;
|
||||
logic [31:0] fetch_addr;
|
||||
logic fetch_err;
|
||||
logic fetch_err_plus2;
|
||||
|
||||
clocking monitor_cb @(posedge clk);
|
||||
input reset;
|
||||
input fetch_ready;
|
||||
input fetch_valid;
|
||||
input fetch_rdata;
|
||||
input fetch_addr;
|
||||
input fetch_err;
|
||||
input fetch_err_plus2;
|
||||
endclocking
|
||||
|
||||
task automatic wait_clks(input int num);
|
||||
repeat (num) @(posedge clk);
|
||||
endtask
|
||||
endinterface
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
interface core_ibex_ifetch_pmp_if(input logic clk);
|
||||
logic reset;
|
||||
|
||||
logic fetch_valid;
|
||||
logic [31:0] fetch_addr;
|
||||
logic fetch_pmp_err;
|
||||
|
||||
clocking monitor_cb @(posedge clk);
|
||||
input reset;
|
||||
input fetch_valid;
|
||||
input fetch_addr;
|
||||
input fetch_pmp_err;
|
||||
endclocking
|
||||
|
||||
task automatic wait_clks(input int num);
|
||||
repeat (num) @(posedge clk);
|
||||
endtask
|
||||
endinterface : core_ibex_ifetch_pmp_if
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
`include "cosim_dpi.svh"
|
||||
|
||||
class ibex_cosim_agent extends uvm_agent;
|
||||
ibex_rvfi_monitor rvfi_monitor;
|
||||
ibex_ifetch_monitor ifetch_monitor;
|
||||
ibex_ifetch_pmp_monitor ifetch_pmp_monitor;
|
||||
ibex_cosim_scoreboard scoreboard;
|
||||
|
||||
uvm_analysis_export#(ibex_mem_intf_seq_item) dmem_port;
|
||||
uvm_analysis_export#(ibex_mem_intf_seq_item) imem_port;
|
||||
|
||||
`uvm_component_utils(ibex_cosim_agent)
|
||||
|
||||
function new(string name="", uvm_component parent=null);
|
||||
super.new(name, parent);
|
||||
|
||||
dmem_port = new("dmem_port", this);
|
||||
imem_port = new("imem_port", this);
|
||||
endfunction
|
||||
|
||||
virtual function void build_phase(uvm_phase phase);
|
||||
super.build_phase(phase);
|
||||
|
||||
rvfi_monitor = ibex_rvfi_monitor::type_id::create("rvfi_monitor", this);
|
||||
scoreboard = ibex_cosim_scoreboard::type_id::create("scoreboard", this);
|
||||
ifetch_monitor = ibex_ifetch_monitor::type_id::create("ifetch_monitor", this);
|
||||
ifetch_pmp_monitor = ibex_ifetch_pmp_monitor::type_id::create("ifetch_pmp_monitor", this);
|
||||
endfunction: build_phase
|
||||
|
||||
virtual function void connect_phase(uvm_phase phase);
|
||||
super.connect_phase(phase);
|
||||
|
||||
rvfi_monitor.item_collected_port.connect(scoreboard.rvfi_port.analysis_export);
|
||||
ifetch_monitor.item_collected_port.connect(scoreboard.ifetch_port.analysis_export);
|
||||
ifetch_pmp_monitor.item_collected_port.connect(scoreboard.ifetch_pmp_port.analysis_export);
|
||||
dmem_port.connect(scoreboard.dmem_port.analysis_export);
|
||||
imem_port.connect(scoreboard.imem_port.analysis_export);
|
||||
endfunction: connect_phase
|
||||
|
||||
function void write_mem_byte(bit [31:0] addr, bit [7:0] d);
|
||||
riscv_cosim_write_mem_byte(scoreboard.cosim_handle, addr, d);
|
||||
endfunction
|
||||
|
||||
function void write_mem_word(bit [31:0] addr, bit [DATA_WIDTH-1:0] d);
|
||||
for (int i = 0; i < DATA_WIDTH / 8; i++) begin
|
||||
write_mem_byte(addr + i, d[7:0]);
|
||||
d = d >> 8;
|
||||
end
|
||||
endfunction
|
||||
|
||||
// Backdoor-load the test binary file into the cosim memory model
|
||||
function void load_binary_to_mem(bit[31:0] base_addr, string bin);
|
||||
bit [7:0] r8;
|
||||
bit [31:0] addr = base_addr;
|
||||
int bin_fd;
|
||||
bin_fd = $fopen(bin,"rb");
|
||||
if (!bin_fd)
|
||||
`uvm_fatal(get_full_name(), $sformatf("Cannot open file %0s", bin))
|
||||
while ($fread(r8,bin_fd)) begin
|
||||
`uvm_info(`gfn, $sformatf("Init mem [0x%h] = 0x%0h", addr, r8), UVM_FULL)
|
||||
write_mem_byte(addr, r8);
|
||||
addr++;
|
||||
end
|
||||
endfunction
|
||||
|
||||
function void reset();
|
||||
scoreboard.rvfi_port.flush();
|
||||
scoreboard.dmem_port.flush();
|
||||
scoreboard.imem_port.flush();
|
||||
scoreboard.ifetch_port.flush();
|
||||
scoreboard.ifetch_pmp_port.flush();
|
||||
|
||||
scoreboard.reset_e.trigger();
|
||||
endfunction : reset
|
||||
|
||||
endclass : ibex_cosim_agent
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package ibex_cosim_agent_pkg;
|
||||
import uvm_pkg::*;
|
||||
import ibex_mem_intf_pkg::*;
|
||||
|
||||
`include "uvm_macros.svh"
|
||||
|
||||
`include "ibex_cosim_cfg.sv"
|
||||
`include "ibex_rvfi_seq_item.sv"
|
||||
`include "ibex_rvfi_monitor.sv"
|
||||
`include "ibex_ifetch_seq_item.sv"
|
||||
`include "ibex_ifetch_monitor.sv"
|
||||
`include "ibex_ifetch_pmp_seq_item.sv"
|
||||
`include "ibex_ifetch_pmp_monitor.sv"
|
||||
`include "ibex_cosim_scoreboard.sv"
|
||||
`include "ibex_cosim_agent.sv"
|
||||
endpackage
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
class core_ibex_cosim_cfg extends uvm_object;
|
||||
string isa_string;
|
||||
bit [31:0] start_pc;
|
||||
bit [31:0] start_mtvec;
|
||||
bit probe_imem_for_errs;
|
||||
string log_file;
|
||||
bit [31:0] pmp_num_regions;
|
||||
bit [31:0] pmp_granularity;
|
||||
bit [31:0] mhpm_counter_num;
|
||||
bit relax_cosim_check;
|
||||
bit secure_ibex;
|
||||
bit icache;
|
||||
|
||||
`uvm_object_utils_begin(core_ibex_cosim_cfg)
|
||||
`uvm_field_string(isa_string, UVM_DEFAULT)
|
||||
`uvm_field_int(start_pc, UVM_DEFAULT)
|
||||
`uvm_field_int(start_mtvec, UVM_DEFAULT)
|
||||
`uvm_field_int(probe_imem_for_errs, UVM_DEFAULT)
|
||||
`uvm_field_string(log_file, UVM_DEFAULT)
|
||||
`uvm_field_int(pmp_num_regions, UVM_DEFAULT)
|
||||
`uvm_field_int(pmp_granularity, UVM_DEFAULT)
|
||||
`uvm_field_int(mhpm_counter_num, UVM_DEFAULT)
|
||||
`uvm_field_int(secure_ibex, UVM_DEFAULT)
|
||||
`uvm_field_int(icache, UVM_DEFAULT)
|
||||
`uvm_object_utils_end
|
||||
|
||||
`uvm_object_new
|
||||
endclass
|
||||
+344
@@ -0,0 +1,344 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
`include "spike_cosim_dpi.svh"
|
||||
`include "cosim_dpi.svh"
|
||||
|
||||
class ibex_cosim_scoreboard extends uvm_scoreboard;
|
||||
import ibex_pkg::*;
|
||||
chandle cosim_handle;
|
||||
|
||||
core_ibex_cosim_cfg cfg;
|
||||
|
||||
uvm_tlm_analysis_fifo #(ibex_rvfi_seq_item) rvfi_port;
|
||||
uvm_tlm_analysis_fifo #(ibex_mem_intf_seq_item) dmem_port;
|
||||
uvm_tlm_analysis_fifo #(ibex_mem_intf_seq_item) imem_port;
|
||||
uvm_tlm_analysis_fifo #(ibex_ifetch_seq_item) ifetch_port;
|
||||
uvm_tlm_analysis_fifo #(ibex_ifetch_pmp_seq_item) ifetch_pmp_port;
|
||||
|
||||
virtual core_ibex_instr_monitor_if instr_vif;
|
||||
virtual core_ibex_dut_probe_if dut_vif;
|
||||
|
||||
uvm_event reset_e;
|
||||
uvm_event check_inserted_iside_error_e;
|
||||
|
||||
bit failed_iside_accesses [bit[31:0]];
|
||||
bit iside_pmp_failure [bit[31:0]];
|
||||
|
||||
typedef struct {
|
||||
bit [63:0] order;
|
||||
bit [31:0] addr;
|
||||
} iside_err_t;
|
||||
|
||||
iside_err_t iside_error_queue [$];
|
||||
|
||||
`uvm_component_utils(ibex_cosim_scoreboard)
|
||||
|
||||
function new(string name="", uvm_component parent=null);
|
||||
super.new(name, parent);
|
||||
|
||||
rvfi_port = new("rvfi_port", this);
|
||||
dmem_port = new("dmem_port", this);
|
||||
imem_port = new("imem_port", this);
|
||||
ifetch_port = new("ifetch_port", this);
|
||||
ifetch_pmp_port = new("ifetch_pmp_port", this);
|
||||
cosim_handle = null;
|
||||
reset_e = new();
|
||||
check_inserted_iside_error_e = new();
|
||||
endfunction
|
||||
|
||||
function void build_phase(uvm_phase phase);
|
||||
super.build_phase(phase);
|
||||
|
||||
if (!uvm_config_db#(core_ibex_cosim_cfg)::get(this, "", "cosim_cfg", cfg)) begin
|
||||
`uvm_fatal(`gfn, "Cannot get cosim configuration")
|
||||
end
|
||||
|
||||
if (!uvm_config_db#(virtual core_ibex_instr_monitor_if)::get(null, "", "instr_monitor_if",
|
||||
instr_vif)) begin
|
||||
`uvm_fatal(`gfn, "Cannot get instr_monitor_if")
|
||||
end
|
||||
|
||||
if (!uvm_config_db#(virtual core_ibex_dut_probe_if)::get(null, "", "dut_if",
|
||||
dut_vif)) begin
|
||||
`uvm_fatal(`gfn, "Cannot get dut_probe_if")
|
||||
end
|
||||
|
||||
init_cosim();
|
||||
endfunction : build_phase
|
||||
|
||||
protected function void init_cosim();
|
||||
cleanup_cosim();
|
||||
|
||||
// TODO: Ensure log file on reset gets append rather than overwrite?
|
||||
cosim_handle = spike_cosim_init(cfg.isa_string, cfg.start_pc, cfg.start_mtvec, cfg.log_file,
|
||||
cfg.pmp_num_regions, cfg.pmp_granularity, cfg.mhpm_counter_num, cfg.secure_ibex, cfg.icache);
|
||||
|
||||
if (cosim_handle == null) begin
|
||||
`uvm_fatal(`gfn, "Could not initialise cosim")
|
||||
end
|
||||
endfunction
|
||||
|
||||
protected function void cleanup_cosim();
|
||||
if (cosim_handle) begin
|
||||
spike_cosim_release(cosim_handle);
|
||||
end
|
||||
cosim_handle = null;
|
||||
endfunction
|
||||
|
||||
virtual task run_phase(uvm_phase phase);
|
||||
forever begin
|
||||
@(negedge instr_vif.reset)
|
||||
fork : isolation_fork
|
||||
run_cosim_rvfi();
|
||||
run_cosim_dmem();
|
||||
run_cosim_imem_errors();
|
||||
run_cosim_prune_imem_errors();
|
||||
if (cfg.probe_imem_for_errs) begin
|
||||
run_cosim_imem();
|
||||
end else begin
|
||||
fork
|
||||
run_cosim_ifetch();
|
||||
run_cosim_ifetch_pmp();
|
||||
join_any
|
||||
end
|
||||
join_none
|
||||
reset_e.wait_trigger();
|
||||
disable fork;
|
||||
handle_reset();
|
||||
end // forever
|
||||
endtask : run_phase
|
||||
|
||||
task run_cosim_rvfi();
|
||||
ibex_rvfi_seq_item rvfi_instr;
|
||||
|
||||
forever begin
|
||||
rvfi_port.get(rvfi_instr);
|
||||
|
||||
if (iside_error_queue.size() > 0) begin
|
||||
// Remove entries from iside_error_queue where the instruction never reaches the RVFI
|
||||
// interface because it was flushed.
|
||||
while (iside_error_queue.size() > 0 && iside_error_queue[0].order < rvfi_instr.order) begin
|
||||
iside_error_queue.pop_front();
|
||||
end
|
||||
|
||||
// Check if the top of the iside_error_queue relates to the current RVFI instruction. If so
|
||||
// notify the cosim environment of an instruction error.
|
||||
if (iside_error_queue.size() !=0 && iside_error_queue[0].order == rvfi_instr.order) begin
|
||||
riscv_cosim_set_iside_error(cosim_handle, iside_error_queue[0].addr);
|
||||
iside_error_queue.pop_front();
|
||||
end
|
||||
end
|
||||
|
||||
riscv_cosim_set_nmi(cosim_handle, rvfi_instr.nmi);
|
||||
riscv_cosim_set_nmi_int(cosim_handle, rvfi_instr.nmi_int);
|
||||
riscv_cosim_set_mip(cosim_handle, rvfi_instr.mip);
|
||||
riscv_cosim_set_debug_req(cosim_handle, rvfi_instr.debug_req);
|
||||
riscv_cosim_set_mcycle(cosim_handle, rvfi_instr.mcycle);
|
||||
|
||||
// Set performance counters through a pseudo-backdoor write
|
||||
for (int i=0; i < 10; i++) begin
|
||||
riscv_cosim_set_csr(cosim_handle, CSR_MHPMCOUNTER3 + i, rvfi_instr.mhpmcounters[i]);
|
||||
riscv_cosim_set_csr(cosim_handle, CSR_MHPMCOUNTER3H + i, rvfi_instr.mhpmcountersh[i]);
|
||||
end
|
||||
|
||||
riscv_cosim_set_ic_scr_key_valid(cosim_handle, rvfi_instr.ic_scr_key_valid);
|
||||
|
||||
if (!riscv_cosim_step(cosim_handle, rvfi_instr.rd_addr, rvfi_instr.rd_wdata, rvfi_instr.pc,
|
||||
rvfi_instr.trap, rvfi_instr.rf_wr_suppress)) begin
|
||||
// cosim instruction step doesn't match rvfi captured instruction, report a fatal error
|
||||
// with the details
|
||||
if (cfg.relax_cosim_check) begin
|
||||
`uvm_info(`gfn, get_cosim_error_str(), UVM_LOW)
|
||||
end else begin
|
||||
`uvm_fatal(`gfn, get_cosim_error_str())
|
||||
end
|
||||
end
|
||||
end
|
||||
endtask: run_cosim_rvfi
|
||||
|
||||
task run_cosim_dmem();
|
||||
ibex_mem_intf_seq_item mem_op;
|
||||
|
||||
forever begin
|
||||
dmem_port.get(mem_op);
|
||||
// Notify the cosim of all dside accesses emitted by the RTL
|
||||
riscv_cosim_notify_dside_access(cosim_handle, mem_op.read_write == WRITE, mem_op.addr,
|
||||
mem_op.data, mem_op.be, mem_op.error, mem_op.misaligned_first, mem_op.misaligned_second);
|
||||
end
|
||||
endtask: run_cosim_dmem
|
||||
|
||||
task run_cosim_imem();
|
||||
ibex_mem_intf_seq_item mem_op;
|
||||
|
||||
forever begin
|
||||
// Take stream of transaction from imem monitor. Where an imem access has an error record it
|
||||
// in failed_iside_accesses. If an access has succeeded remove it from failed_imem_accesses if
|
||||
// it's there.
|
||||
// Note all transactions are 32-bit aligned.
|
||||
imem_port.get(mem_op);
|
||||
if (mem_op.error) begin
|
||||
failed_iside_accesses[mem_op.addr] = 1'b1;
|
||||
end else begin
|
||||
if (failed_iside_accesses.exists(mem_op.addr)) begin
|
||||
failed_iside_accesses.delete(mem_op.addr);
|
||||
end
|
||||
end
|
||||
end
|
||||
endtask: run_cosim_imem
|
||||
|
||||
task run_cosim_ifetch();
|
||||
ibex_ifetch_seq_item ifetch;
|
||||
bit [31:0] aligned_fetch_addr;
|
||||
bit [31:0] aligned_fetch_addr_next;
|
||||
|
||||
forever begin
|
||||
ifetch_port.get(ifetch);
|
||||
aligned_fetch_addr = {ifetch.fetch_addr[31:2], 2'b0};
|
||||
aligned_fetch_addr_next = aligned_fetch_addr + 32'd4;
|
||||
|
||||
if (ifetch.fetch_err) begin
|
||||
// Instruction error observed in fetch stage
|
||||
bit [31:0] failing_addr;
|
||||
|
||||
// Determine which address failed.
|
||||
if (ifetch.fetch_err_plus2) begin
|
||||
// Instruction crosses a 32-bit boundary and second half failed
|
||||
failing_addr = aligned_fetch_addr_next;
|
||||
end else begin
|
||||
failing_addr = aligned_fetch_addr;
|
||||
end
|
||||
|
||||
failed_iside_accesses[failing_addr] = 1'b1;
|
||||
end else begin
|
||||
if (ifetch.fetch_addr[1:0] != 0 && ifetch.fetch_rdata[1:0] == 2'b11) begin
|
||||
// Instruction crosses 32-bit boundary, so remove any failed accesses on the other side of
|
||||
// the 32-bit boundary.
|
||||
if (failed_iside_accesses.exists(aligned_fetch_addr_next)) begin
|
||||
failed_iside_accesses.delete(aligned_fetch_addr_next);
|
||||
end
|
||||
end
|
||||
|
||||
if (failed_iside_accesses.exists(aligned_fetch_addr)) begin
|
||||
failed_iside_accesses.delete(aligned_fetch_addr);
|
||||
end
|
||||
end
|
||||
end
|
||||
endtask: run_cosim_ifetch
|
||||
|
||||
task run_cosim_ifetch_pmp();
|
||||
ibex_ifetch_pmp_seq_item ifetch_pmp;
|
||||
|
||||
// Keep track of which addresses have seen PMP failures.
|
||||
forever begin
|
||||
ifetch_pmp_port.get(ifetch_pmp);
|
||||
|
||||
if (ifetch_pmp.fetch_pmp_err) begin
|
||||
iside_pmp_failure[ifetch_pmp.fetch_addr] = 1'b1;
|
||||
end else begin
|
||||
if (iside_pmp_failure.exists(ifetch_pmp.fetch_addr)) begin
|
||||
iside_pmp_failure.delete(ifetch_pmp.fetch_addr);
|
||||
end
|
||||
end
|
||||
end
|
||||
endtask
|
||||
|
||||
task run_cosim_imem_errors();
|
||||
bit [63:0] latest_order = 64'hffffffff_ffffffff;
|
||||
bit [31:0] aligned_addr;
|
||||
bit [31:0] aligned_next_addr;
|
||||
forever begin
|
||||
// Wait for new instruction to appear in ID stage
|
||||
wait (instr_vif.instr_cb.valid_id &&
|
||||
instr_vif.instr_cb.instr_new_id &&
|
||||
latest_order != instr_vif.instr_cb.rvfi_order_id);
|
||||
|
||||
latest_order = instr_vif.instr_cb.rvfi_order_id;
|
||||
|
||||
if (dut_vif.dut_cb.wb_exception)
|
||||
// If an exception in writeback occurs the instruction in ID will be flushed and hence not
|
||||
// produce an iside error so skip the rest of the loop. A writeback exception may occur
|
||||
// after this cycle before the instruction in ID moves out of the ID stage. The
|
||||
// `run_cosim_prune_imem_errors` task deals with this case.
|
||||
continue;
|
||||
|
||||
// Determine if the instruction comes from an address that has seen an error that wasn't a PMP
|
||||
// error (the icache records both PMP errors and fetch errors with the same error bits). If a
|
||||
// fetch error was seen add the instruction order ID and address to iside_error_queue.
|
||||
aligned_addr = instr_vif.instr_cb.pc_id & 32'hfffffffc;
|
||||
aligned_next_addr = aligned_addr + 32'd4;
|
||||
|
||||
if (failed_iside_accesses.exists(aligned_addr) && !iside_pmp_failure.exists(aligned_addr))
|
||||
begin
|
||||
iside_error_queue.push_back('{order : instr_vif.instr_cb.rvfi_order_id,
|
||||
addr : aligned_addr});
|
||||
check_inserted_iside_error_e.trigger();
|
||||
end else if (!instr_vif.instr_cb.is_compressed_id &&
|
||||
(instr_vif.instr_cb.pc_id & 32'h3) != 0 &&
|
||||
failed_iside_accesses.exists(aligned_next_addr) &&
|
||||
!iside_pmp_failure.exists(aligned_next_addr))
|
||||
begin
|
||||
// Where an instruction crosses a 32-bit boundary, check if an error was seen on the other
|
||||
// side of the boundary
|
||||
iside_error_queue.push_back('{order : instr_vif.instr_cb.rvfi_order_id,
|
||||
addr : aligned_next_addr});
|
||||
check_inserted_iside_error_e.trigger();
|
||||
end
|
||||
|
||||
end
|
||||
endtask: run_cosim_imem_errors;
|
||||
|
||||
task run_cosim_prune_imem_errors();
|
||||
// Errors are added to the iside error queue the first cycle the instruction that sees the error
|
||||
// is in the ID stage. Cycles following this the writeback stage may cause an exception flushing
|
||||
// the ID stage so the iside error never occurs. When this happens we need to pop the new iside
|
||||
// error off the queue.
|
||||
forever begin
|
||||
// Wait until the `run_cosim_imem_errors` task notifies us it's added a error to the queue
|
||||
check_inserted_iside_error_e.wait_ptrigger();
|
||||
// Wait for the next clock
|
||||
@(instr_vif.instr_cb);
|
||||
// Wait for a new instruction or a writeback exception. When a new instruction has entered the
|
||||
// ID stage and we haven't seen a writeback exception we know the instruction associated with the
|
||||
// error just added to the queue isn't getting flushed.
|
||||
wait (instr_vif.instr_cb.instr_new_id || dut_vif.dut_cb.wb_exception);
|
||||
|
||||
if (!instr_vif.instr_cb.instr_new_id && dut_vif.dut_cb.wb_exception) begin
|
||||
// If we hit a writeback exception without seeing a new instruction then the newly added
|
||||
// error relates to an instruction just flushed from the ID stage so pop it from the
|
||||
// queue.
|
||||
iside_error_queue.pop_back();
|
||||
end
|
||||
end
|
||||
endtask: run_cosim_prune_imem_errors
|
||||
|
||||
function string get_cosim_error_str();
|
||||
string error = "Cosim mismatch ";
|
||||
for (int i = 0; i < riscv_cosim_get_num_errors(cosim_handle); ++i) begin
|
||||
error = {error, riscv_cosim_get_error(cosim_handle, i), "\n"};
|
||||
end
|
||||
riscv_cosim_clear_errors(cosim_handle);
|
||||
|
||||
return error;
|
||||
endfunction : get_cosim_error_str
|
||||
|
||||
function void final_phase(uvm_phase phase);
|
||||
super.final_phase(phase);
|
||||
|
||||
`uvm_info(`gfn, $sformatf("Co-simulation matched %d instructions",
|
||||
riscv_cosim_get_insn_cnt(cosim_handle)), UVM_LOW)
|
||||
|
||||
cleanup_cosim();
|
||||
endfunction : final_phase
|
||||
|
||||
// If the UVM_EXIT action is triggered (such as by reaching max_quit_count), this callback is run.
|
||||
// This ensures proper cleanup, such as commiting the logfile to disk.
|
||||
function void pre_abort();
|
||||
cleanup_cosim();
|
||||
endfunction
|
||||
|
||||
task handle_reset();
|
||||
init_cosim();
|
||||
endtask
|
||||
endclass : ibex_cosim_scoreboard
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
class ibex_ifetch_monitor extends uvm_monitor;
|
||||
protected virtual core_ibex_ifetch_if vif;
|
||||
|
||||
uvm_analysis_port#(ibex_ifetch_seq_item) item_collected_port;
|
||||
|
||||
`uvm_component_utils(ibex_ifetch_monitor)
|
||||
`uvm_component_new
|
||||
|
||||
function void build_phase(uvm_phase phase);
|
||||
super.build_phase(phase);
|
||||
|
||||
item_collected_port = new("item_collected_port", this);
|
||||
|
||||
if(!uvm_config_db#(virtual core_ibex_ifetch_if)::get(this, "", "ifetch_if", vif)) begin
|
||||
`uvm_fatal("NOVIF", {"virtual interface must be set for: ", get_full_name(), ".vif"});
|
||||
end
|
||||
endfunction
|
||||
|
||||
virtual task run_phase(uvm_phase phase);
|
||||
ibex_ifetch_seq_item trans_collected;
|
||||
|
||||
wait (vif.monitor_cb.reset === 1'b0);
|
||||
|
||||
forever begin
|
||||
while(!(vif.monitor_cb.fetch_valid && vif.monitor_cb.fetch_ready)) vif.wait_clks(1);
|
||||
|
||||
trans_collected = ibex_ifetch_seq_item::type_id::create("trans_collected");
|
||||
trans_collected.fetch_rdata = vif.monitor_cb.fetch_rdata;
|
||||
trans_collected.fetch_addr = vif.monitor_cb.fetch_addr;
|
||||
trans_collected.fetch_err = vif.monitor_cb.fetch_err;
|
||||
trans_collected.fetch_err_plus2 = vif.monitor_cb.fetch_err_plus2;
|
||||
|
||||
`uvm_info(`gfn, $sformatf("Seen ifetch:\n%s", trans_collected.sprint()),
|
||||
UVM_HIGH)
|
||||
|
||||
item_collected_port.write(trans_collected);
|
||||
|
||||
vif.wait_clks(1);
|
||||
end
|
||||
endtask: run_phase
|
||||
endclass : ibex_ifetch_monitor
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
class ibex_ifetch_pmp_monitor extends uvm_monitor;
|
||||
protected virtual core_ibex_ifetch_pmp_if vif;
|
||||
|
||||
uvm_analysis_port#(ibex_ifetch_pmp_seq_item) item_collected_port;
|
||||
|
||||
`uvm_component_utils(ibex_ifetch_pmp_monitor)
|
||||
`uvm_component_new
|
||||
|
||||
function void build_phase(uvm_phase phase);
|
||||
super.build_phase(phase);
|
||||
|
||||
item_collected_port = new("item_collected_port", this);
|
||||
|
||||
if(!uvm_config_db#(virtual core_ibex_ifetch_pmp_if)::get(this, "", "ifetch_pmp_if", vif)) begin
|
||||
`uvm_fatal("NOVIF", {"virtual interface must be set for: ", get_full_name(), ".vif"});
|
||||
end
|
||||
endfunction
|
||||
|
||||
virtual task run_phase(uvm_phase phase);
|
||||
ibex_ifetch_pmp_seq_item trans_collected;
|
||||
|
||||
wait (vif.monitor_cb.reset === 1'b0);
|
||||
|
||||
forever begin
|
||||
while(!vif.monitor_cb.fetch_valid) vif.wait_clks(1);
|
||||
|
||||
trans_collected = ibex_ifetch_pmp_seq_item::type_id::create("trans_collected");
|
||||
trans_collected.fetch_addr = vif.monitor_cb.fetch_addr;
|
||||
trans_collected.fetch_pmp_err = vif.monitor_cb.fetch_pmp_err;
|
||||
|
||||
`uvm_info(`gfn, $sformatf("Seen ifetch:\n%s", trans_collected.sprint()),
|
||||
UVM_HIGH)
|
||||
|
||||
item_collected_port.write(trans_collected);
|
||||
|
||||
vif.wait_clks(1);
|
||||
end
|
||||
endtask: run_phase
|
||||
endclass : ibex_ifetch_pmp_monitor
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
class ibex_ifetch_pmp_seq_item extends uvm_sequence_item;
|
||||
bit [31:0] fetch_addr;
|
||||
bit fetch_pmp_err;
|
||||
|
||||
`uvm_object_utils_begin(ibex_ifetch_pmp_seq_item)
|
||||
`uvm_field_int (fetch_addr, UVM_DEFAULT)
|
||||
`uvm_field_int (fetch_pmp_err, UVM_DEFAULT)
|
||||
`uvm_object_utils_end
|
||||
|
||||
`uvm_object_new
|
||||
endclass : ibex_ifetch_pmp_seq_item
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
class ibex_ifetch_seq_item extends uvm_sequence_item;
|
||||
bit [31:0] fetch_rdata;
|
||||
bit [31:0] fetch_addr;
|
||||
bit fetch_err;
|
||||
bit fetch_err_plus2;
|
||||
|
||||
`uvm_object_utils_begin(ibex_ifetch_seq_item)
|
||||
`uvm_field_int (fetch_rdata, UVM_DEFAULT)
|
||||
`uvm_field_int (fetch_addr, UVM_DEFAULT)
|
||||
`uvm_field_int (fetch_err, UVM_DEFAULT)
|
||||
`uvm_field_int (fetch_err_plus2, UVM_DEFAULT)
|
||||
`uvm_object_utils_end
|
||||
|
||||
`uvm_object_new
|
||||
endclass : ibex_ifetch_seq_item
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
class ibex_rvfi_monitor extends uvm_monitor;
|
||||
protected virtual core_ibex_rvfi_if vif;
|
||||
|
||||
uvm_analysis_port#(ibex_rvfi_seq_item) item_collected_port;
|
||||
|
||||
`uvm_component_utils(ibex_rvfi_monitor)
|
||||
`uvm_component_new
|
||||
|
||||
function void build_phase(uvm_phase phase);
|
||||
super.build_phase(phase);
|
||||
|
||||
item_collected_port = new("item_collected_port", this);
|
||||
|
||||
if(!uvm_config_db#(virtual core_ibex_rvfi_if)::get(this, "", "rvfi_if", vif)) begin
|
||||
`uvm_fatal("NOVIF", {"virtual interface must be set for: ", get_full_name(), ".vif"});
|
||||
end
|
||||
endfunction: build_phase
|
||||
|
||||
virtual task run_phase(uvm_phase phase);
|
||||
ibex_rvfi_seq_item trans_collected;
|
||||
|
||||
wait (vif.monitor_cb.reset === 1'b0);
|
||||
|
||||
forever begin
|
||||
// Wait for a retired instruction
|
||||
while(!vif.monitor_cb.valid) vif.wait_clks(1);
|
||||
|
||||
// Read instruction details from RVFI interface
|
||||
trans_collected = ibex_rvfi_seq_item::type_id::create("trans_collected");
|
||||
trans_collected.trap = vif.monitor_cb.trap;
|
||||
trans_collected.pc = vif.monitor_cb.pc_rdata;
|
||||
trans_collected.rd_addr = vif.monitor_cb.rd_addr;
|
||||
trans_collected.rd_wdata = vif.monitor_cb.rd_wdata;
|
||||
trans_collected.order = vif.monitor_cb.order;
|
||||
trans_collected.mip = vif.monitor_cb.ext_mip;
|
||||
trans_collected.nmi = vif.monitor_cb.ext_nmi;
|
||||
trans_collected.nmi_int = vif.monitor_cb.ext_nmi_int;
|
||||
trans_collected.debug_req = vif.monitor_cb.ext_debug_req;
|
||||
trans_collected.rf_wr_suppress = vif.monitor_cb.ext_rf_wr_suppress;
|
||||
trans_collected.mcycle = vif.monitor_cb.ext_mcycle;
|
||||
trans_collected.ic_scr_key_valid = vif.monitor_cb.ext_ic_scr_key_valid;
|
||||
|
||||
for (int i=0; i < 10; i++) begin
|
||||
trans_collected.mhpmcounters[i] = vif.monitor_cb.ext_mhpmcounters[i];
|
||||
trans_collected.mhpmcountersh[i] = vif.monitor_cb.ext_mhpmcountersh[i];
|
||||
end
|
||||
|
||||
`uvm_info(get_full_name(), $sformatf("Seen instruction:\n%s", trans_collected.sprint()),
|
||||
UVM_HIGH)
|
||||
|
||||
item_collected_port.write(trans_collected);
|
||||
|
||||
vif.wait_clks(1);
|
||||
end
|
||||
endtask : run_phase
|
||||
endclass : ibex_rvfi_monitor
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
class ibex_rvfi_seq_item extends uvm_sequence_item;
|
||||
bit trap;
|
||||
bit [31:0] pc;
|
||||
bit [4:0] rd_addr;
|
||||
bit [31:0] rd_wdata;
|
||||
bit [63:0] order;
|
||||
bit [31:0] mip;
|
||||
bit nmi;
|
||||
bit nmi_int;
|
||||
bit debug_req;
|
||||
bit rf_wr_suppress;
|
||||
bit [63:0] mcycle;
|
||||
|
||||
bit [31:0] mhpmcounters [10];
|
||||
bit [31:0] mhpmcountersh [10];
|
||||
bit ic_scr_key_valid;
|
||||
|
||||
`uvm_object_utils_begin(ibex_rvfi_seq_item)
|
||||
`uvm_field_int (trap, UVM_DEFAULT)
|
||||
`uvm_field_int (pc, UVM_DEFAULT)
|
||||
`uvm_field_int (rd_addr, UVM_DEFAULT)
|
||||
`uvm_field_int (rd_wdata, UVM_DEFAULT)
|
||||
`uvm_field_int (order, UVM_DEFAULT)
|
||||
`uvm_field_int (mip, UVM_DEFAULT)
|
||||
`uvm_field_int (nmi, UVM_DEFAULT)
|
||||
`uvm_field_int (nmi_int, UVM_DEFAULT)
|
||||
`uvm_field_int (debug_req, UVM_DEFAULT)
|
||||
`uvm_field_int (rf_wr_suppress, UVM_DEFAULT)
|
||||
`uvm_field_int (mcycle, UVM_DEFAULT)
|
||||
`uvm_field_sarray_int (mhpmcounters, UVM_DEFAULT)
|
||||
`uvm_field_sarray_int (mhpmcountersh, UVM_DEFAULT)
|
||||
`uvm_field_int (ic_scr_key_valid, UVM_DEFAULT)
|
||||
`uvm_object_utils_end
|
||||
|
||||
`uvm_object_new
|
||||
|
||||
endclass : ibex_rvfi_seq_item
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include <svdpi.h>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "cosim.h"
|
||||
#include "spike_cosim.h"
|
||||
|
||||
extern "C" {
|
||||
void *spike_cosim_init(const char *isa_string, svBitVecVal *start_pc,
|
||||
svBitVecVal *start_mtvec, const char *log_file_path_cstr,
|
||||
svBitVecVal *pmp_num_regions,
|
||||
svBitVecVal *pmp_granularity,
|
||||
svBitVecVal *mhpm_counter_num, svBit secure_ibex,
|
||||
svBit icache) {
|
||||
assert(isa_string);
|
||||
|
||||
std::string log_file_path;
|
||||
|
||||
if (log_file_path_cstr) {
|
||||
log_file_path = log_file_path_cstr;
|
||||
}
|
||||
|
||||
SpikeCosim *cosim = new SpikeCosim(
|
||||
isa_string, start_pc[0], start_mtvec[0], log_file_path, secure_ibex,
|
||||
icache, pmp_num_regions[0], pmp_granularity[0], mhpm_counter_num[0]);
|
||||
cosim->add_memory(0x80000000, 0x80000000);
|
||||
cosim->add_memory(0x00000000, 0x80000000);
|
||||
return static_cast<Cosim *>(cosim);
|
||||
}
|
||||
|
||||
void spike_cosim_release(void *cosim_handle) {
|
||||
auto cosim = static_cast<Cosim *>(cosim_handle);
|
||||
|
||||
delete cosim;
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
`ifndef SPIKE_COSIM_DPI_SVH
|
||||
`define SPIKE_COSIM_DPI_SVH
|
||||
|
||||
import "DPI-C" function
|
||||
chandle spike_cosim_init(string isa_string,
|
||||
bit [31:0] start_pc,
|
||||
bit [31:0] start_mtvec,
|
||||
string log_file_path,
|
||||
bit [31:0] pmp_num_regions,
|
||||
bit [31:0] pmp_granularity,
|
||||
bit [31:0] mhpm_counter_num,
|
||||
bit secure_ibex,
|
||||
bit icache);
|
||||
|
||||
import "DPI-C" function void spike_cosim_release(chandle cosim_handle);
|
||||
|
||||
`endif
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
interface ibex_mem_intf#(
|
||||
parameter int ADDR_WIDTH = 32,
|
||||
parameter int DATA_WIDTH = 32,
|
||||
parameter int INTG_WIDTH = 7
|
||||
) (
|
||||
input clk
|
||||
);
|
||||
|
||||
wire reset;
|
||||
wire request;
|
||||
wire grant;
|
||||
wire [ADDR_WIDTH-1:0] addr;
|
||||
wire we;
|
||||
wire [DATA_WIDTH/8-1:0] be;
|
||||
wire rvalid;
|
||||
wire [DATA_WIDTH-1:0] wdata;
|
||||
wire [INTG_WIDTH-1:0] wintg;
|
||||
wire [DATA_WIDTH-1:0] rdata;
|
||||
wire [INTG_WIDTH-1:0] rintg;
|
||||
wire error;
|
||||
wire misaligned_first;
|
||||
wire misaligned_second;
|
||||
|
||||
clocking request_driver_cb @(posedge clk);
|
||||
input reset;
|
||||
output request;
|
||||
input grant;
|
||||
output addr;
|
||||
output we;
|
||||
output be;
|
||||
input rvalid;
|
||||
output wdata;
|
||||
output wintg;
|
||||
input rdata;
|
||||
input rintg;
|
||||
input error;
|
||||
endclocking
|
||||
|
||||
clocking response_driver_cb @(posedge clk);
|
||||
input reset;
|
||||
input request;
|
||||
output grant;
|
||||
input addr;
|
||||
input we;
|
||||
input be;
|
||||
output rvalid;
|
||||
input wdata;
|
||||
input wintg;
|
||||
output rdata;
|
||||
output rintg;
|
||||
output error;
|
||||
endclocking
|
||||
|
||||
clocking monitor_cb @(posedge clk);
|
||||
input reset;
|
||||
input request;
|
||||
input grant;
|
||||
input addr;
|
||||
input we;
|
||||
input be;
|
||||
input rvalid;
|
||||
input wdata;
|
||||
input wintg;
|
||||
input rdata;
|
||||
input rintg;
|
||||
input error;
|
||||
input misaligned_first;
|
||||
input misaligned_second;
|
||||
endclocking
|
||||
|
||||
task automatic wait_clks(input int num);
|
||||
repeat (num) @(posedge clk);
|
||||
endtask
|
||||
|
||||
task automatic wait_neg_clks(input int num);
|
||||
repeat (num) @(negedge clk);
|
||||
endtask
|
||||
|
||||
endinterface : ibex_mem_intf
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
CAPI=2:
|
||||
# Copyright lowRISC contributors.
|
||||
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
name: "lowrisc:dv:ibex_mem_intf_agent:0.1"
|
||||
description: "IBEX DV UVM environment"
|
||||
filesets:
|
||||
files_dv:
|
||||
depend:
|
||||
- lowrisc:dv:mem_model
|
||||
files:
|
||||
- ibex_mem_intf.sv
|
||||
- ibex_mem_intf_agent_pkg.sv
|
||||
- ibex_mem_intf_request_agent.sv: {is_include_file: true}
|
||||
- ibex_mem_intf_request_driver.sv: {is_include_file: true}
|
||||
- ibex_mem_intf_monitor.sv: {is_include_file: true}
|
||||
- ibex_mem_intf_seq_item.sv: {is_include_file: true}
|
||||
- ibex_mem_intf_response_agent.sv: {is_include_file: true}
|
||||
- ibex_mem_intf_response_driver.sv: {is_include_file: true}
|
||||
- ibex_mem_intf_response_seq_lib.sv: {is_include_file: true}
|
||||
- ibex_mem_intf_response_sequencer.sv: {is_include_file: true}
|
||||
file_type: systemVerilogSource
|
||||
|
||||
targets:
|
||||
default:
|
||||
filesets:
|
||||
- files_dv
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package ibex_mem_intf_agent_pkg;
|
||||
|
||||
import uvm_pkg::*;
|
||||
import ibex_mem_intf_pkg::*;
|
||||
import mem_model_pkg::*;
|
||||
import ibex_cosim_agent_pkg::*;
|
||||
|
||||
`include "uvm_macros.svh"
|
||||
|
||||
typedef uvm_sequencer#(ibex_mem_intf_seq_item) ibex_mem_intf_request_sequencer;
|
||||
|
||||
`include "ibex_mem_intf_monitor.sv"
|
||||
`include "ibex_mem_intf_response_agent_cfg.sv"
|
||||
`include "ibex_mem_intf_response_driver.sv"
|
||||
`include "ibex_mem_intf_response_sequencer.sv"
|
||||
`include "ibex_mem_intf_response_seq_lib.sv"
|
||||
`include "ibex_mem_intf_response_agent.sv"
|
||||
`include "ibex_mem_intf_request_driver.sv"
|
||||
`include "ibex_mem_intf_request_agent.sv"
|
||||
|
||||
endpackage
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// CLASS: ibex_mem_intf_monitor
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class ibex_mem_intf_monitor extends uvm_monitor;
|
||||
|
||||
protected virtual ibex_mem_intf vif;
|
||||
|
||||
mailbox #(ibex_mem_intf_seq_item) collect_response_queue;
|
||||
uvm_analysis_port#(ibex_mem_intf_seq_item) item_collected_port;
|
||||
uvm_analysis_port#(ibex_mem_intf_seq_item) addr_ph_port;
|
||||
|
||||
`uvm_component_utils(ibex_mem_intf_monitor)
|
||||
`uvm_component_new
|
||||
|
||||
function void build_phase(uvm_phase phase);
|
||||
super.build_phase(phase);
|
||||
item_collected_port = new("item_collected_port", this);
|
||||
addr_ph_port = new("addr_ph_port_monitor", this);
|
||||
collect_response_queue = new();
|
||||
if(!uvm_config_db#(virtual ibex_mem_intf)::get(this, "", "vif", vif)) begin
|
||||
`uvm_fatal("NOVIF",{"virtual interface must be set for: ",get_full_name(),".vif"});
|
||||
end
|
||||
endfunction: build_phase
|
||||
|
||||
virtual task run_phase(uvm_phase phase);
|
||||
wait (vif.monitor_cb.reset === 1'b0);
|
||||
forever begin
|
||||
fork begin : isolation_fork
|
||||
fork : check_mem_intf
|
||||
collect_address_phase();
|
||||
collect_response_phase();
|
||||
wait (vif.monitor_cb.reset === 1'b1);
|
||||
join_any
|
||||
// Will only reach this point when mid-test reset is asserted
|
||||
disable fork;
|
||||
end join
|
||||
handle_reset();
|
||||
end
|
||||
endtask : run_phase
|
||||
|
||||
virtual protected task handle_reset();
|
||||
ibex_mem_intf_seq_item mailbox_result;
|
||||
// Clear the mailbox of any content
|
||||
while (collect_response_queue.try_get(mailbox_result));
|
||||
wait (vif.monitor_cb.reset === 1'b0);
|
||||
endtask
|
||||
|
||||
virtual protected task collect_address_phase();
|
||||
ibex_mem_intf_seq_item trans_collected;
|
||||
forever begin
|
||||
trans_collected = ibex_mem_intf_seq_item::type_id::create("trans_collected");
|
||||
while(!(vif.monitor_cb.request && vif.monitor_cb.grant)) vif.wait_clks(1);
|
||||
trans_collected.addr = vif.monitor_cb.addr;
|
||||
trans_collected.be = vif.monitor_cb.be;
|
||||
trans_collected.misaligned_first = vif.monitor_cb.misaligned_first;
|
||||
trans_collected.misaligned_second = vif.monitor_cb.misaligned_second;
|
||||
`uvm_info(get_full_name(), $sformatf("Detect request with address: %0x",
|
||||
trans_collected.addr), UVM_HIGH)
|
||||
if(vif.monitor_cb.we) begin
|
||||
trans_collected.read_write = WRITE;
|
||||
trans_collected.data = vif.monitor_cb.wdata;
|
||||
trans_collected.intg = vif.monitor_cb.wintg;
|
||||
end else begin
|
||||
trans_collected.read_write = READ;
|
||||
end
|
||||
addr_ph_port.write(trans_collected);
|
||||
`uvm_info(get_full_name(),"Send through addr_ph_port", UVM_HIGH)
|
||||
collect_response_queue.put(trans_collected);
|
||||
vif.wait_clks(1);
|
||||
end
|
||||
endtask : collect_address_phase
|
||||
|
||||
virtual protected task collect_response_phase();
|
||||
ibex_mem_intf_seq_item trans_collected;
|
||||
forever begin
|
||||
collect_response_queue.get(trans_collected);
|
||||
do
|
||||
vif.wait_clks(1);
|
||||
while(vif.monitor_cb.rvalid === 0);
|
||||
|
||||
if (trans_collected.read_write == READ) begin
|
||||
trans_collected.data = vif.monitor_cb.rdata;
|
||||
trans_collected.intg = vif.monitor_cb.rintg;
|
||||
end
|
||||
|
||||
trans_collected.error = vif.monitor_cb.error;
|
||||
item_collected_port.write(trans_collected);
|
||||
end
|
||||
endtask : collect_response_phase
|
||||
|
||||
endclass : ibex_mem_intf_monitor
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package ibex_mem_intf_pkg;
|
||||
|
||||
import uvm_pkg::*;
|
||||
|
||||
parameter int DATA_WIDTH = 32;
|
||||
parameter int ADDR_WIDTH = 32;
|
||||
parameter int INTG_WIDTH = 7;
|
||||
|
||||
typedef enum { READ, WRITE } rw_e;
|
||||
|
||||
`include "uvm_macros.svh"
|
||||
`include "ibex_mem_intf_seq_item.sv"
|
||||
|
||||
endpackage
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// CLASS: ibex_mem_intf_request_agent
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class ibex_mem_intf_request_agent extends uvm_agent;
|
||||
|
||||
ibex_mem_intf_request_driver driver;
|
||||
ibex_mem_intf_request_sequencer sequencer;
|
||||
ibex_mem_intf_monitor monitor;
|
||||
|
||||
`uvm_component_utils(ibex_mem_intf_request_agent)
|
||||
`uvm_component_new
|
||||
|
||||
virtual function void build_phase(uvm_phase phase);
|
||||
super.build_phase(phase);
|
||||
monitor = ibex_mem_intf_monitor::type_id::create("monitor", this);
|
||||
if(get_is_active() == UVM_ACTIVE) begin
|
||||
driver = ibex_mem_intf_request_driver::type_id::create("driver", this);
|
||||
sequencer = ibex_mem_intf_request_sequencer::type_id::create("sequencer", this);
|
||||
end
|
||||
endfunction : build_phase
|
||||
|
||||
function void connect_phase(uvm_phase phase);
|
||||
super.connect_phase(phase);
|
||||
if(get_is_active() == UVM_ACTIVE) begin
|
||||
driver.seq_item_port.connect(sequencer.seq_item_export);
|
||||
end
|
||||
endfunction : connect_phase
|
||||
|
||||
endclass : ibex_mem_intf_request_agent
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// CLASS: ibex_mem_intf_request_driver
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class ibex_mem_intf_request_driver extends uvm_driver #(ibex_mem_intf_seq_item);
|
||||
|
||||
protected virtual ibex_mem_intf vif;
|
||||
|
||||
`uvm_component_utils(ibex_mem_intf_request_driver)
|
||||
`uvm_component_new
|
||||
|
||||
mailbox #(ibex_mem_intf_seq_item) rdata_queue;
|
||||
|
||||
function void build_phase(uvm_phase phase);
|
||||
super.build_phase(phase);
|
||||
rdata_queue = new();
|
||||
if(!uvm_config_db#(virtual ibex_mem_intf)::get(this, "", "vif", vif))
|
||||
`uvm_fatal("NOVIF",{"virtual interface must be set for: ",get_full_name(),".vif"});
|
||||
endfunction: build_phase
|
||||
|
||||
virtual task run_phase(uvm_phase phase);
|
||||
fork
|
||||
get_and_drive();
|
||||
reset_signals();
|
||||
collect_response();
|
||||
join
|
||||
endtask : run_phase
|
||||
|
||||
virtual protected task get_and_drive();
|
||||
@(negedge vif.request_driver_cb.reset);
|
||||
forever begin
|
||||
vif.wait_clks(1);
|
||||
seq_item_port.get_next_item(req);
|
||||
vif.wait_clks(req.req_delay);
|
||||
$cast(rsp, req.clone());
|
||||
rsp.set_id_info(req);
|
||||
drive_transfer(rsp);
|
||||
seq_item_port.item_done();
|
||||
end
|
||||
endtask : get_and_drive
|
||||
|
||||
virtual protected task reset_signals();
|
||||
forever begin
|
||||
@(posedge vif.request_driver_cb.reset);
|
||||
vif.request_driver_cb.request <= 'h0;
|
||||
vif.request_driver_cb.addr <= 'hz;
|
||||
vif.request_driver_cb.wdata <= 'hz;
|
||||
vif.request_driver_cb.wintg <= 'hz;
|
||||
vif.request_driver_cb.be <= 'bz;
|
||||
vif.request_driver_cb.we <= 'bz;
|
||||
end
|
||||
endtask : reset_signals
|
||||
|
||||
virtual protected task drive_transfer (ibex_mem_intf_seq_item trans);
|
||||
if (trans.req_delay > 0) begin
|
||||
vif.wait_clks(trans.req_delay);
|
||||
end
|
||||
vif.request_driver_cb.request <= 1'b1;
|
||||
vif.request_driver_cb.addr <= trans.addr;
|
||||
vif.request_driver_cb.be <= trans.be;
|
||||
vif.request_driver_cb.we <= trans.read_write;
|
||||
vif.request_driver_cb.wdata <= trans.data;
|
||||
vif.request_driver_cb.wintg <= trans.intg;
|
||||
wait (vif.request_driver_cb.grant === 1'b1);
|
||||
vif.wait_clks(1);
|
||||
vif.request_driver_cb.request <= 'h0;
|
||||
vif.request_driver_cb.addr <= 'hz;
|
||||
vif.request_driver_cb.wdata <= 'hz;
|
||||
vif.request_driver_cb.wintg <= 'hz;
|
||||
vif.request_driver_cb.be <= 'bz;
|
||||
vif.request_driver_cb.we <= 'bz;
|
||||
rdata_queue.put(trans);
|
||||
endtask : drive_transfer
|
||||
|
||||
virtual protected task collect_response();
|
||||
ibex_mem_intf_seq_item tr;
|
||||
forever begin
|
||||
rdata_queue.get(tr);
|
||||
vif.wait_clks(1);
|
||||
while(vif.rvalid !== 1'b1) vif.wait_clks(1);
|
||||
if(tr.read_write == READ)
|
||||
tr.data = vif.request_driver_cb.rdata;
|
||||
tr.intg = vif.request_driver_cb.rintg;
|
||||
seq_item_port.put_response(tr);
|
||||
end
|
||||
endtask : collect_response
|
||||
|
||||
endclass : ibex_mem_intf_request_driver
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// CLASS: ibex_mem_intf_response_agent
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class ibex_mem_intf_response_agent extends uvm_agent;
|
||||
|
||||
ibex_mem_intf_response_driver driver;
|
||||
ibex_mem_intf_response_sequencer sequencer;
|
||||
ibex_mem_intf_monitor monitor;
|
||||
ibex_mem_intf_response_agent_cfg cfg;
|
||||
|
||||
`uvm_component_utils(ibex_mem_intf_response_agent)
|
||||
`uvm_component_new
|
||||
|
||||
virtual function void build_phase(uvm_phase phase);
|
||||
bit secure_ibex;
|
||||
|
||||
super.build_phase(phase);
|
||||
monitor = ibex_mem_intf_monitor::type_id::create("monitor", this);
|
||||
if (cfg == null)
|
||||
if(!uvm_config_db #(ibex_mem_intf_response_agent_cfg)::get(this, "", "cfg", cfg))
|
||||
`uvm_fatal(`gfn, "Could not locate mem_intf cfg object in uvm_config_db!")
|
||||
|
||||
if(get_is_active() == UVM_ACTIVE) begin
|
||||
driver = ibex_mem_intf_response_driver::type_id::create("driver", this);
|
||||
sequencer = ibex_mem_intf_response_sequencer::type_id::create("sequencer", this);
|
||||
end
|
||||
if(!uvm_config_db#(virtual ibex_mem_intf)::get(this, "", "vif", cfg.vif))
|
||||
`uvm_fatal("NOVIF",{"virtual interface must be set for: ",get_full_name(),".vif"});
|
||||
|
||||
if (!uvm_config_db#(bit)::get(null, "", "SecureIbex", secure_ibex)) begin
|
||||
secure_ibex = 1'b0;
|
||||
end
|
||||
|
||||
cfg.fixed_data_write_response = secure_ibex;
|
||||
endfunction : build_phase
|
||||
|
||||
function void connect_phase(uvm_phase phase);
|
||||
super.connect_phase(phase);
|
||||
if(get_is_active() == UVM_ACTIVE) begin
|
||||
driver.seq_item_port.connect(sequencer.seq_item_export);
|
||||
monitor.addr_ph_port.connect(sequencer.addr_ph_port.analysis_export);
|
||||
end
|
||||
driver.cfg = cfg;
|
||||
sequencer.cfg = cfg;
|
||||
endfunction : connect_phase
|
||||
|
||||
function void reset();
|
||||
sequencer.reset();
|
||||
endfunction
|
||||
|
||||
endclass : ibex_mem_intf_response_agent
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// CLASS: ibex_mem_intf_response_agent_cfg
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class ibex_mem_intf_response_agent_cfg extends uvm_object;
|
||||
|
||||
// interface handle used by driver & monitor
|
||||
virtual ibex_mem_intf vif;
|
||||
|
||||
// When set write responses have a fixed 32'hffffffff for rdata and matching correct rintg. When
|
||||
// unset both rdata and rintg fields are x for write responses
|
||||
bit fixed_data_write_response = 1'b0;
|
||||
|
||||
// delay between request and grant
|
||||
int unsigned gnt_delay_min = 0;
|
||||
int unsigned gnt_delay_max = 10;
|
||||
// Pick the weight assigned to choosing medium and long gaps between request and grant
|
||||
int unsigned gnt_pick_medium_speed_weight = 1;
|
||||
int unsigned gnt_pick_slow_speed_weight = 1;
|
||||
|
||||
// delay between grant and rvalid
|
||||
int unsigned valid_delay_min = 0;
|
||||
int unsigned valid_delay_max = 20;
|
||||
// Pick the weight assigned to choosing medium and long gaps between grant and rvalid
|
||||
int unsigned valid_pick_medium_speed_weight = 1;
|
||||
int unsigned valid_pick_slow_speed_weight = 1;
|
||||
|
||||
// Enables/disable all protocol delays.
|
||||
rand bit zero_delays;
|
||||
|
||||
// Knob to enable percentage of zero delay in auto-response sequence.
|
||||
// Default set to 50% for zero delay to be picked
|
||||
int unsigned zero_delay_pct = 50;
|
||||
|
||||
// CONTROL_KNOB : enable/disable to generation of bad integrity upon uninit accesses
|
||||
bit enable_bad_intg_on_uninit_access = 0;
|
||||
|
||||
constraint zero_delays_c {
|
||||
zero_delays dist {1 :/ zero_delay_pct,
|
||||
0 :/ 100 - zero_delay_pct};
|
||||
}
|
||||
|
||||
`uvm_object_utils_begin(ibex_mem_intf_response_agent_cfg)
|
||||
`uvm_field_int(fixed_data_write_response, UVM_DEFAULT)
|
||||
`uvm_field_int(gnt_delay_min, UVM_DEFAULT)
|
||||
`uvm_field_int(gnt_delay_max, UVM_DEFAULT)
|
||||
`uvm_field_int(valid_delay_min, UVM_DEFAULT)
|
||||
`uvm_field_int(valid_delay_max, UVM_DEFAULT)
|
||||
`uvm_field_int(zero_delays, UVM_DEFAULT)
|
||||
`uvm_field_int(zero_delay_pct, UVM_DEFAULT)
|
||||
`uvm_object_utils_end
|
||||
|
||||
function new(string name = "");
|
||||
super.new(name);
|
||||
endfunction
|
||||
|
||||
endclass
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// CLASS: ibex_mem_intf_response_driver
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class ibex_mem_intf_response_driver extends uvm_driver #(ibex_mem_intf_seq_item);
|
||||
|
||||
ibex_mem_intf_response_agent_cfg cfg;
|
||||
|
||||
`uvm_component_utils(ibex_mem_intf_response_driver)
|
||||
`uvm_component_new
|
||||
|
||||
mailbox #(ibex_mem_intf_seq_item) rdata_queue;
|
||||
|
||||
function void build_phase(uvm_phase phase);
|
||||
super.build_phase(phase);
|
||||
rdata_queue = new();
|
||||
endfunction: build_phase
|
||||
|
||||
virtual task run_phase(uvm_phase phase);
|
||||
reset_signals();
|
||||
wait (cfg.vif.response_driver_cb.reset === 1'b0);
|
||||
forever begin
|
||||
fork begin : isolation_fork
|
||||
fork : drive_stimulus
|
||||
send_grant();
|
||||
get_and_drive();
|
||||
wait (cfg.vif.response_driver_cb.reset === 1'b1);
|
||||
join_any
|
||||
// Will only be reached after mid-test reset
|
||||
disable fork;
|
||||
end join
|
||||
handle_reset();
|
||||
end
|
||||
endtask : run_phase
|
||||
|
||||
virtual protected task handle_reset();
|
||||
ibex_mem_intf_seq_item req;
|
||||
// Clear mailbox
|
||||
while (rdata_queue.try_get(req));
|
||||
// Clear seq_item_port
|
||||
do begin
|
||||
seq_item_port.try_next_item(req);
|
||||
if (req != null) begin
|
||||
seq_item_port.item_done();
|
||||
end
|
||||
end while (req != null);
|
||||
reset_signals();
|
||||
wait (cfg.vif.response_driver_cb.reset === 1'b0);
|
||||
endtask
|
||||
|
||||
virtual protected task reset_signals();
|
||||
cfg.vif.response_driver_cb.rvalid <= 1'b0;
|
||||
cfg.vif.response_driver_cb.grant <= 1'b0;
|
||||
cfg.vif.response_driver_cb.rdata <= 'b0;
|
||||
cfg.vif.response_driver_cb.rintg <= 'b0;
|
||||
cfg.vif.response_driver_cb.error <= 1'b0;
|
||||
endtask : reset_signals
|
||||
|
||||
virtual protected task get_and_drive();
|
||||
wait (cfg.vif.response_driver_cb.reset === 1'b0);
|
||||
fork
|
||||
begin
|
||||
forever begin
|
||||
ibex_mem_intf_seq_item req, req_c;
|
||||
cfg.vif.wait_clks(1);
|
||||
seq_item_port.get_next_item(req);
|
||||
$cast(req_c, req.clone());
|
||||
if(~cfg.vif.response_driver_cb.reset) begin
|
||||
rdata_queue.put(req_c);
|
||||
end
|
||||
seq_item_port.item_done();
|
||||
end
|
||||
end
|
||||
begin
|
||||
send_read_data();
|
||||
end
|
||||
join
|
||||
endtask : get_and_drive
|
||||
|
||||
virtual protected task send_grant();
|
||||
int gnt_delay;
|
||||
forever begin
|
||||
while(cfg.vif.response_driver_cb.request !== 1'b1) begin
|
||||
cfg.vif.wait_neg_clks(1);
|
||||
end
|
||||
if(cfg.zero_delays) begin
|
||||
gnt_delay = 0;
|
||||
end else begin
|
||||
if (!std::randomize(gnt_delay) with {
|
||||
gnt_delay dist {
|
||||
cfg.gnt_delay_min :/ 10,
|
||||
[cfg.gnt_delay_min+1 : cfg.gnt_delay_max-1] :/ cfg.valid_pick_medium_speed_weight,
|
||||
cfg.gnt_delay_max :/ cfg.valid_pick_slow_speed_weight
|
||||
};
|
||||
}) begin
|
||||
`uvm_fatal(`gfn, $sformatf("Cannot randomize grant"))
|
||||
end
|
||||
end
|
||||
cfg.vif.wait_neg_clks(gnt_delay);
|
||||
if(~cfg.vif.response_driver_cb.reset) begin
|
||||
cfg.vif.response_driver_cb.grant <= 1'b1;
|
||||
cfg.vif.wait_neg_clks(1);
|
||||
cfg.vif.response_driver_cb.grant <= 1'b0;
|
||||
end
|
||||
end
|
||||
endtask : send_grant
|
||||
|
||||
virtual protected task send_read_data();
|
||||
ibex_mem_intf_seq_item tr;
|
||||
forever begin
|
||||
cfg.vif.wait_clks(1);
|
||||
cfg.vif.response_driver_cb.rvalid <= 1'b0;
|
||||
cfg.vif.response_driver_cb.rdata <= 'x;
|
||||
cfg.vif.response_driver_cb.rintg <= 'x;
|
||||
cfg.vif.response_driver_cb.error <= 'x;
|
||||
rdata_queue.get(tr);
|
||||
if(cfg.vif.response_driver_cb.reset) continue;
|
||||
cfg.vif.wait_clks(tr.rvalid_delay);
|
||||
if(~cfg.vif.response_driver_cb.reset) begin
|
||||
cfg.vif.response_driver_cb.rvalid <= 1'b1;
|
||||
cfg.vif.response_driver_cb.error <= tr.error;
|
||||
if (tr.read_write == READ) begin
|
||||
cfg.vif.response_driver_cb.rdata <= tr.data;
|
||||
cfg.vif.response_driver_cb.rintg <= tr.intg;
|
||||
end else begin
|
||||
// rdata and intg fields aren't relevant to write responses
|
||||
if (cfg.fixed_data_write_response) begin
|
||||
// when fixed_data_write_response is set, sequence item is responsible for producing
|
||||
// fixed values so just copy them across here.
|
||||
cfg.vif.response_driver_cb.rdata <= tr.data;
|
||||
cfg.vif.response_driver_cb.rintg <= tr.intg;
|
||||
end else begin
|
||||
// when fixed_data_write_response is not set, drive the irrelevant fields to x.
|
||||
cfg.vif.response_driver_cb.rdata <= 'x;
|
||||
cfg.vif.response_driver_cb.rintg <= 'x;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
endtask : send_read_data
|
||||
|
||||
endclass : ibex_mem_intf_response_driver
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// SEQUENCE: ibex_mem_intf_response_seq
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class ibex_mem_intf_response_seq extends uvm_sequence #(ibex_mem_intf_seq_item);
|
||||
|
||||
ibex_mem_intf_seq_item item;
|
||||
mem_model m_mem;
|
||||
ibex_cosim_agent cosim_agent;
|
||||
bit enable_intg_error = 1'b0;
|
||||
bit enable_error = 1'b0;
|
||||
// Used to ensure that whenever inject_error() is called, the very next transaction will inject an
|
||||
// error, and that enable_error will not be flipped back to 0 immediately
|
||||
bit error_synch = 1'b1;
|
||||
bit is_dmem_seq = 1'b0;
|
||||
bit suppress_error_on_exc = 1'b0;
|
||||
|
||||
`uvm_object_utils(ibex_mem_intf_response_seq)
|
||||
`uvm_declare_p_sequencer(ibex_mem_intf_response_sequencer)
|
||||
`uvm_object_new
|
||||
|
||||
virtual task body();
|
||||
virtual core_ibex_dut_probe_if ibex_dut_vif;
|
||||
|
||||
if (!uvm_config_db#(virtual core_ibex_dut_probe_if)::get(null, "", "dut_if",
|
||||
ibex_dut_vif)) begin
|
||||
`uvm_fatal(`gfn, "failed to get ibex dut_if from uvm_config_db")
|
||||
end
|
||||
|
||||
if (m_mem == null) `uvm_fatal(get_full_name(), "Cannot get memory model")
|
||||
`uvm_info(`gfn, $sformatf("is_dmem_seq: 0x%0x", is_dmem_seq), UVM_LOW)
|
||||
forever
|
||||
begin
|
||||
bit [ADDR_WIDTH-1:0] aligned_addr;
|
||||
bit [DATA_WIDTH-1:0] rand_data;
|
||||
bit [DATA_WIDTH-1:0] read_data;
|
||||
bit [INTG_WIDTH-1:0] read_intg;
|
||||
bit data_was_uninitialized = 1'b0;
|
||||
|
||||
p_sequencer.addr_ph_port.get(item);
|
||||
aligned_addr = {item.addr[DATA_WIDTH-1:2], 2'b0};
|
||||
|
||||
req = ibex_mem_intf_seq_item::type_id::create("req");
|
||||
error_synch = 1'b0;
|
||||
|
||||
if (suppress_error_on_exc &&
|
||||
(ibex_dut_vif.dut_cb.sync_exc_seen || ibex_dut_vif.dut_cb.irq_exc_seen)) begin
|
||||
enable_error = 1'b0;
|
||||
enable_intg_error = 1'b0;
|
||||
end
|
||||
|
||||
if (!req.randomize() with {
|
||||
addr == item.addr;
|
||||
read_write == item.read_write;
|
||||
data == item.data;
|
||||
intg == item.intg;
|
||||
be == item.be;
|
||||
if (p_sequencer.cfg.zero_delays) {
|
||||
rvalid_delay == 0;
|
||||
} else {
|
||||
rvalid_delay dist {
|
||||
p_sequencer.cfg.valid_delay_min :/ 5,
|
||||
[p_sequencer.cfg.valid_delay_min + 1 : p_sequencer.cfg.valid_delay_max / 2 - 1] :/ 3,
|
||||
[p_sequencer.cfg.valid_delay_max / 2 : p_sequencer.cfg.valid_delay_max - 1]
|
||||
:/ p_sequencer.cfg.valid_pick_medium_speed_weight,
|
||||
p_sequencer.cfg.valid_delay_max
|
||||
:/ p_sequencer.cfg.valid_pick_slow_speed_weight
|
||||
};
|
||||
}
|
||||
error == enable_error;
|
||||
}) begin
|
||||
`uvm_fatal(`gfn, "Cannot randomize response request")
|
||||
end
|
||||
error_synch = 1'b1;
|
||||
enable_error = 1'b0; // Disable after single inserted error.
|
||||
aligned_addr = {req.addr[DATA_WIDTH-1:2], 2'b0};
|
||||
// Do not inject any error to the handshake test_control_addr
|
||||
// TODO: Parametrize this. Until then, this needs to be changed manually.
|
||||
if (aligned_addr inside {32'h8ffffff8, 32'h8ffffffc}) begin
|
||||
req.error = 1'b0;
|
||||
enable_intg_error = 1'b0;
|
||||
end
|
||||
if (req.error) begin
|
||||
`DV_CHECK_STD_RANDOMIZE_FATAL(rand_data)
|
||||
req.data = rand_data;
|
||||
end else if(item.read_write == READ) begin
|
||||
// Get data from memory_model, handle uninit memory accesses.
|
||||
req.data = read(aligned_addr, data_was_uninitialized);
|
||||
end else if(item.read_write == WRITE) begin
|
||||
// Update memory_model
|
||||
write(aligned_addr, item.data);
|
||||
if (p_sequencer.cfg.fixed_data_write_response) begin
|
||||
// When fixed_data_write_response is set drive data in store response to fixed
|
||||
// 32'hffffffff value. Integrity is calculated below.
|
||||
req.data = 32'hffffffff;
|
||||
end
|
||||
end
|
||||
// Add integrity bits
|
||||
{req.intg, req.data} = prim_secded_pkg::prim_secded_inv_39_32_enc(req.data);
|
||||
|
||||
// If data_was_uninitialized is true then we want to force bad integrity bits: invert the
|
||||
// correct ones, which we know will break things for the codes we use.
|
||||
if ((p_sequencer.cfg.enable_bad_intg_on_uninit_access && data_was_uninitialized) || enable_intg_error) begin
|
||||
req.intg = ~req.intg;
|
||||
enable_intg_error = 1'b0;
|
||||
end
|
||||
|
||||
`uvm_info(get_full_name(), $sformatf("Response transfer:\n%0s", req.sprint()), UVM_HIGH)
|
||||
start_item(req);
|
||||
finish_item(req);
|
||||
|
||||
end
|
||||
endtask : body
|
||||
|
||||
virtual function void inject_error();
|
||||
this.enable_error = 1'b1;
|
||||
endfunction
|
||||
|
||||
virtual function void inject_intg_error();
|
||||
this.enable_intg_error = 1'b1;
|
||||
endfunction
|
||||
|
||||
virtual function bit get_error_synch();
|
||||
return this.error_synch;
|
||||
endfunction
|
||||
|
||||
// Read a word of DATA_WIDTH bits from addr.
|
||||
// Handle reads fromm uninit memory as follows:
|
||||
// - DMEM : return a random value
|
||||
// - IMEM : return {2{C.unimp}}
|
||||
protected function logic [DATA_WIDTH-1:0] read(bit [ADDR_WIDTH-1:0] addr,
|
||||
output bit did_access_uninit_mem);
|
||||
logic [DATA_WIDTH-1:0] data = '0;
|
||||
bit [7:0] byte_data = '0;
|
||||
bit byte_is_uninit = 1'b0;
|
||||
for (int i = (DATA_WIDTH / 8) - 1; i >= 0 ; i--) begin
|
||||
data = data << 8;
|
||||
byte_data = read_byte(addr + i, byte_is_uninit);
|
||||
if (byte_is_uninit) begin
|
||||
did_access_uninit_mem = 1'b1;
|
||||
// If any byte of the access comes back as uninit, bork the whole access.
|
||||
if (is_dmem_seq) begin
|
||||
// DMEM
|
||||
`DV_CHECK_STD_RANDOMIZE_FATAL(byte_data)
|
||||
// Update mem_model(s) with the randomized data.
|
||||
`uvm_info(`gfn,
|
||||
$sformatf("Addr is uninit! DMEM seq, returning random data 0x%0h", data),
|
||||
UVM_MEDIUM)
|
||||
m_mem.write_byte(addr + i, byte_data); // Update UVM mem_model
|
||||
cosim_agent.write_mem_byte(addr + i, byte_data); // Update cosim mem_model
|
||||
end else begin
|
||||
// IMEM
|
||||
`uvm_info(`gfn,
|
||||
$sformatf("Addr is uninit! IMEM seq, returning 0x0000 (c.unimp)"),
|
||||
UVM_MEDIUM)
|
||||
return {2{16'h0000}}; // 2x C.unimp instructions
|
||||
end
|
||||
end
|
||||
data[7:0] = byte_data;
|
||||
end
|
||||
return data;
|
||||
endfunction
|
||||
|
||||
// Write a word of DATA_WIDTH bits at addr.
|
||||
protected function void write(bit [ADDR_WIDTH-1:0] addr, bit [DATA_WIDTH-1:0] data);
|
||||
for (int i = 0; i < DATA_WIDTH / 8; i++) begin
|
||||
if (req.be[i])
|
||||
m_mem.write_byte(addr + i, data[7:0]);
|
||||
data = data >> 8;
|
||||
end
|
||||
endfunction
|
||||
|
||||
// Re-implement the read_byte function from mem_model.sv, but without the fatal assertion.
|
||||
function bit [7:0] read_byte(bit [ADDR_WIDTH-1:0] addr, output bit is_byte_uninit);
|
||||
bit [7:0] data = '0;
|
||||
if (!m_mem.addr_exists(addr)) begin
|
||||
`uvm_info(`gfn, $sformatf("Read from uninitialized addr 0x%0h", addr), UVM_MEDIUM)
|
||||
is_byte_uninit = 1'b1;
|
||||
end else begin
|
||||
data = m_mem.system_memory[addr];
|
||||
`uvm_info(`gfn, $sformatf("Read Mem : Addr[0x%0h], Data[0x%0h]", addr, data), UVM_HIGH)
|
||||
end
|
||||
return data;
|
||||
endfunction
|
||||
|
||||
endclass : ibex_mem_intf_response_seq
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// CLASS: ibex_mem_intf_response_sequencer
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class ibex_mem_intf_response_sequencer extends uvm_sequencer #(ibex_mem_intf_seq_item);
|
||||
|
||||
// TLM port to peek the address phase from the response monitor
|
||||
uvm_tlm_analysis_fifo #(ibex_mem_intf_seq_item) addr_ph_port;
|
||||
ibex_mem_intf_response_agent_cfg cfg;
|
||||
|
||||
`uvm_component_utils(ibex_mem_intf_response_sequencer)
|
||||
|
||||
function new (string name, uvm_component parent);
|
||||
super.new(name, parent);
|
||||
addr_ph_port = new("addr_ph_port_sequencer", this);
|
||||
endfunction : new
|
||||
|
||||
// On reset, empty the tlm fifo
|
||||
function void reset();
|
||||
addr_ph_port.flush();
|
||||
endfunction
|
||||
|
||||
endclass : ibex_mem_intf_response_sequencer
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// CLASS: ibex_mem_intf_seq_item
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class ibex_mem_intf_seq_item extends uvm_sequence_item;
|
||||
|
||||
rand bit [ADDR_WIDTH-1:0] addr;
|
||||
rand rw_e read_write;
|
||||
rand bit [DATA_WIDTH-1:0] data;
|
||||
rand bit [INTG_WIDTH-1:0] intg;
|
||||
rand bit [DATA_WIDTH/8-1:0] be;
|
||||
rand bit [3:0] gnt_delay;
|
||||
rand bit [3:0] req_delay;
|
||||
rand bit [5:0] rvalid_delay;
|
||||
rand bit error;
|
||||
bit misaligned_first;
|
||||
bit misaligned_second;
|
||||
|
||||
`uvm_object_utils_begin(ibex_mem_intf_seq_item)
|
||||
`uvm_field_int (addr, UVM_DEFAULT)
|
||||
`uvm_field_enum (rw_e, read_write, UVM_DEFAULT)
|
||||
`uvm_field_int (be, UVM_DEFAULT)
|
||||
`uvm_field_int (data, UVM_DEFAULT)
|
||||
`uvm_field_int (intg, UVM_DEFAULT)
|
||||
`uvm_field_int (gnt_delay, UVM_DEFAULT)
|
||||
`uvm_field_int (rvalid_delay, UVM_DEFAULT)
|
||||
`uvm_field_int (error, UVM_DEFAULT)
|
||||
`uvm_field_int (misaligned_first, UVM_DEFAULT)
|
||||
`uvm_field_int (misaligned_second, UVM_DEFAULT)
|
||||
`uvm_object_utils_end
|
||||
|
||||
`uvm_object_new
|
||||
|
||||
endclass : ibex_mem_intf_seq_item
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package irq_agent_pkg;
|
||||
|
||||
import uvm_pkg::*;
|
||||
|
||||
parameter int DATA_WIDTH = 32;
|
||||
parameter int ADDR_WIDTH = 32;
|
||||
|
||||
`include "uvm_macros.svh"
|
||||
`include "irq_seq_item.sv"
|
||||
|
||||
typedef uvm_sequencer#(irq_seq_item) irq_request_sequencer;
|
||||
|
||||
`include "irq_monitor.sv"
|
||||
`include "irq_request_driver.sv"
|
||||
`include "irq_request_agent.sv"
|
||||
|
||||
endpackage
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
interface irq_if(input clk);
|
||||
logic reset;
|
||||
logic irq_software;
|
||||
logic irq_timer;
|
||||
logic irq_external;
|
||||
logic [14:0] irq_fast;
|
||||
logic irq_nm; // non-maskeable interrupt
|
||||
|
||||
clocking driver_cb @(posedge clk);
|
||||
default output negedge;
|
||||
input reset;
|
||||
output irq_software;
|
||||
output irq_timer;
|
||||
output irq_external;
|
||||
output irq_fast;
|
||||
output irq_nm;
|
||||
endclocking
|
||||
|
||||
clocking monitor_cb @(posedge clk);
|
||||
input reset;
|
||||
input irq_software;
|
||||
input irq_timer;
|
||||
input irq_external;
|
||||
input irq_fast;
|
||||
input irq_nm;
|
||||
endclocking
|
||||
|
||||
task automatic wait_clks(input int num);
|
||||
repeat (num) @(posedge clk);
|
||||
endtask
|
||||
|
||||
task automatic wait_neg_clks(input int num);
|
||||
repeat (num) @(negedge clk);
|
||||
endtask
|
||||
|
||||
endinterface
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
class irq_monitor extends uvm_monitor;
|
||||
|
||||
protected virtual irq_if vif;
|
||||
|
||||
uvm_analysis_port#(irq_seq_item) irq_port;
|
||||
|
||||
`uvm_component_utils(irq_monitor)
|
||||
|
||||
function new(string name, uvm_component parent=null);
|
||||
super.new(name, parent);
|
||||
irq_port = new("irq_port", this);
|
||||
endfunction : new
|
||||
|
||||
function void build_phase(uvm_phase phase);
|
||||
if (!uvm_config_db#(virtual irq_if)::get(this, "", "vif", vif)) begin
|
||||
`uvm_fatal("NOVIF",{"virtual interface must be set for: ",get_full_name(),".vif"});
|
||||
end
|
||||
endfunction: build_phase
|
||||
|
||||
virtual task run_phase(uvm_phase phase);
|
||||
forever begin
|
||||
wait (vif.monitor_cb.reset === 1'b0);
|
||||
fork begin : isolation_fork
|
||||
fork : monitor_irq
|
||||
collect_irq();
|
||||
wait (vif.monitor_cb.reset === 1'b1);
|
||||
join_any
|
||||
// Will only reach here on mid-test reset
|
||||
disable fork;
|
||||
end join
|
||||
end
|
||||
endtask : run_phase
|
||||
|
||||
// We know that for Ibex, any given interrupt stimulus will be asserted until the core signals the
|
||||
// testbench that it has finished handling, and this stimulus will not change until the testbench
|
||||
// receives the signal, at which point it will drop.
|
||||
// Given this, as well as how the interrupt handshakes are designed, sending an irq_seq_item every
|
||||
// cycle is not useful at all.
|
||||
// In order to not send unnecessary sequence items, but to also send enough information that the
|
||||
// testbench can handle nested interrupt scenarios, the monitor will send out a sequence
|
||||
// item every time the interrupt lines change.
|
||||
virtual protected task collect_irq();
|
||||
irq_seq_item irq;
|
||||
bit[DATA_WIDTH-1:0] stored_irq_val = '0;
|
||||
bit[DATA_WIDTH-1:0] current_irq = '0;
|
||||
forever begin
|
||||
current_irq = {vif.monitor_cb.irq_nm,
|
||||
vif.monitor_cb.irq_fast,
|
||||
4'b0,
|
||||
vif.monitor_cb.irq_external,
|
||||
3'b0,
|
||||
vif.monitor_cb.irq_timer,
|
||||
3'b0,
|
||||
vif.monitor_cb.irq_software,
|
||||
3'b0};
|
||||
if (current_irq !== stored_irq_val) begin
|
||||
stored_irq_val = current_irq;
|
||||
irq = irq_seq_item::type_id::create("irq");
|
||||
irq.irq_software = vif.monitor_cb.irq_software;
|
||||
irq.irq_timer = vif.monitor_cb.irq_timer;
|
||||
irq.irq_external = vif.monitor_cb.irq_external;
|
||||
irq.irq_fast = vif.monitor_cb.irq_fast;
|
||||
irq.irq_nm = vif.monitor_cb.irq_nm;
|
||||
irq_port.write(irq);
|
||||
end
|
||||
vif.wait_clks(1);
|
||||
end
|
||||
endtask : collect_irq
|
||||
|
||||
endclass : irq_monitor
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
class irq_request_agent extends uvm_agent;
|
||||
|
||||
irq_request_driver driver;
|
||||
irq_request_sequencer sequencer;
|
||||
irq_monitor monitor;
|
||||
|
||||
`uvm_component_utils(irq_request_agent)
|
||||
`uvm_component_new
|
||||
|
||||
virtual function void build_phase(uvm_phase phase);
|
||||
super.build_phase(phase);
|
||||
monitor = irq_monitor::type_id::create("monitor", this);
|
||||
if (get_is_active() == UVM_ACTIVE) begin
|
||||
driver = irq_request_driver::type_id::create("driver", this);
|
||||
sequencer = irq_request_sequencer::type_id::create("sequencer", this);
|
||||
end
|
||||
endfunction : build_phase
|
||||
|
||||
function void connect_phase(uvm_phase phase);
|
||||
if (get_is_active() == UVM_ACTIVE) begin
|
||||
driver.seq_item_port.connect(sequencer.seq_item_export);
|
||||
end
|
||||
endfunction : connect_phase
|
||||
|
||||
endclass : irq_request_agent
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
class irq_request_driver extends uvm_driver #(irq_seq_item);
|
||||
|
||||
// The virtual interface used to drive and view HDL signals.
|
||||
protected virtual irq_if vif;
|
||||
`uvm_component_utils(irq_request_driver)
|
||||
`uvm_component_new
|
||||
|
||||
function void build_phase(uvm_phase phase);
|
||||
super.build_phase(phase);
|
||||
if (!uvm_config_db#(virtual irq_if)::get(this, "", "vif", vif)) begin
|
||||
`uvm_fatal("NOVIF",{"virtual interface must be set for: ",get_full_name(),".vif"});
|
||||
end
|
||||
endfunction: build_phase
|
||||
|
||||
virtual task run_phase(uvm_phase phase);
|
||||
reset_signals();
|
||||
wait (vif.driver_cb.reset === 1'b0);
|
||||
forever begin
|
||||
fork begin : isolation_fork
|
||||
fork : drive_irq
|
||||
// Setup a single get_REQ -> drive -> send_RSP long-running task.
|
||||
// This seq_item contains all signals on the interface at once.
|
||||
get_and_drive();
|
||||
wait (vif.driver_cb.reset === 1'b1);
|
||||
join_any
|
||||
// Will only reach here on mid-test reset
|
||||
disable fork;
|
||||
handle_reset();
|
||||
end join
|
||||
end
|
||||
endtask : run_phase
|
||||
|
||||
virtual protected task handle_reset();
|
||||
irq_seq_item req;
|
||||
// Clear seq_item_port
|
||||
do begin
|
||||
seq_item_port.try_next_item(req);
|
||||
if (req != null) begin
|
||||
seq_item_port.item_done();
|
||||
end
|
||||
end while (req != null);
|
||||
reset_signals();
|
||||
endtask
|
||||
|
||||
// Every cycle, check for a new REQ and drive it.
|
||||
// Simultaneously, return the same REQ as a RSP back to the sequence.
|
||||
virtual protected task get_and_drive();
|
||||
forever begin
|
||||
seq_item_port.try_next_item(req);
|
||||
if (req != null) begin
|
||||
$cast(rsp, req.clone());
|
||||
rsp.set_id_info(req);
|
||||
drive_seq_item(rsp);
|
||||
seq_item_port.item_done(rsp);
|
||||
end else begin
|
||||
vif.wait_neg_clks(1);
|
||||
end
|
||||
end
|
||||
endtask : get_and_drive
|
||||
|
||||
virtual protected task reset_signals();
|
||||
@(posedge vif.driver_cb.reset);
|
||||
drive_reset_value();
|
||||
endtask : reset_signals
|
||||
|
||||
virtual protected task drive_seq_item (irq_seq_item trans);
|
||||
vif.driver_cb.irq_software <= trans.irq_software;
|
||||
vif.driver_cb.irq_timer <= trans.irq_timer;
|
||||
vif.driver_cb.irq_external <= trans.irq_external;
|
||||
vif.driver_cb.irq_fast <= trans.irq_fast;
|
||||
vif.driver_cb.irq_nm <= trans.irq_nm;
|
||||
endtask : drive_seq_item
|
||||
|
||||
task drive_reset_value();
|
||||
vif.driver_cb.irq_software <= '0;
|
||||
vif.driver_cb.irq_timer <= '0;
|
||||
vif.driver_cb.irq_external <= '0;
|
||||
vif.driver_cb.irq_fast <= '0;
|
||||
vif.driver_cb.irq_nm <= '0;
|
||||
endtask : drive_reset_value
|
||||
|
||||
endclass : irq_request_driver
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
class irq_seq_item extends uvm_sequence_item;
|
||||
|
||||
rand bit irq_software;
|
||||
rand bit irq_timer;
|
||||
rand bit irq_external;
|
||||
rand bit [14:0] irq_fast;
|
||||
rand bit irq_nm;
|
||||
rand int num_of_interrupt;
|
||||
|
||||
constraint num_of_interrupt_c {
|
||||
num_of_interrupt inside {[0:DATA_WIDTH-1]};
|
||||
$countones({irq_software, irq_timer, irq_external, irq_fast, irq_nm}) == num_of_interrupt;
|
||||
}
|
||||
|
||||
constraint bring_up_c {
|
||||
soft num_of_interrupt == 1;
|
||||
}
|
||||
|
||||
`uvm_object_utils_begin(irq_seq_item)
|
||||
`uvm_field_int(irq_software, UVM_DEFAULT)
|
||||
`uvm_field_int(irq_timer, UVM_DEFAULT)
|
||||
`uvm_field_int(irq_external, UVM_DEFAULT)
|
||||
`uvm_field_int(irq_fast, UVM_DEFAULT)
|
||||
`uvm_field_int(irq_nm, UVM_DEFAULT)
|
||||
`uvm_object_utils_end
|
||||
|
||||
`uvm_object_new
|
||||
|
||||
endclass : irq_seq_item
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Abstract primitives wrapper.
|
||||
//
|
||||
// This file is a stop-gap until the DV file list is generated by FuseSoC.
|
||||
// Its contents are taken from the file which would be generated by FuseSoC.
|
||||
// https://github.com/lowRISC/ibex/issues/893
|
||||
|
||||
module prim_buf #(
|
||||
parameter int Width = 1
|
||||
) (
|
||||
input [Width-1:0] in_i,
|
||||
output logic [Width-1:0] out_o
|
||||
);
|
||||
|
||||
if (1) begin : gen_generic
|
||||
prim_generic_buf#(.Width(Width)) u_impl_generic (
|
||||
.*
|
||||
);
|
||||
end
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Abstract primitives wrapper.
|
||||
//
|
||||
// This file is a stop-gap until the DV file list is generated by FuseSoC.
|
||||
// Its contents are taken from the file which would be generated by FuseSoC.
|
||||
// https://github.com/lowRISC/ibex/issues/893
|
||||
|
||||
`ifndef PRIM_DEFAULT_IMPL
|
||||
`define PRIM_DEFAULT_IMPL prim_pkg::ImplGeneric
|
||||
`endif
|
||||
|
||||
module prim_clock_gating (
|
||||
input clk_i,
|
||||
input en_i,
|
||||
input test_en_i,
|
||||
output logic clk_o
|
||||
);
|
||||
parameter prim_pkg::impl_e Impl = `PRIM_DEFAULT_IMPL;
|
||||
|
||||
if (Impl == prim_pkg::ImplGeneric) begin : gen_generic
|
||||
prim_generic_clock_gating u_impl_generic (
|
||||
.*
|
||||
);
|
||||
end else if (Impl == prim_pkg::ImplXilinx) begin : gen_xilinx
|
||||
prim_xilinx_clock_gating u_impl_xilinx (
|
||||
.*
|
||||
);
|
||||
end else begin : gen_failure
|
||||
// TODO: Find code that works across tools and causes a compile failure
|
||||
end
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Abstract primitives wrapper.
|
||||
//
|
||||
// This file is a stop-gap until the DV file list is generated by FuseSoC.
|
||||
// Its contents are taken from the file which would be generated by FuseSoC.
|
||||
// https://github.com/lowRISC/ibex/issues/893
|
||||
|
||||
module prim_clock_mux2 #(
|
||||
parameter bit NoFpgaBufG = 1'b0
|
||||
) (
|
||||
input clk0_i,
|
||||
input clk1_i,
|
||||
input sel_i,
|
||||
output logic clk_o
|
||||
);
|
||||
|
||||
if (1) begin : gen_generic
|
||||
prim_generic_clock_mux2 #(
|
||||
.NoFpgaBufG(NoFpgaBufG)
|
||||
) u_impl_generic (
|
||||
.*
|
||||
);
|
||||
end
|
||||
|
||||
endmodule
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user