Adding files

This commit is contained in:
alexmr09
2024-07-19 13:30:31 +03:00
commit 08fb8ef728
7245 changed files with 3055662 additions and 0 deletions
+202
View File
@@ -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.
+117
View File
@@ -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 }}
+97
View File
@@ -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
View File
@@ -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"
+43
View File
@@ -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 &reg_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 &reg_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_, &params_)),
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 &reg : *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 &reg : *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, &register_map_));
register_map_.push_back(
std::make_unique<NonImpRegister>(kCSRMSeccfgh, &register_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, &register_map_));
} else {
register_map_.push_back(
std::make_unique<NonImpRegister>(reg_addr, &register_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, &register_map_));
} else {
register_map_.push_back(
std::make_unique<NonImpRegister>(reg_addr, &register_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, &register_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, &register_map_, 0xFFFFFFFF, 0x1 << (i - 3)));
} else {
register_map_.push_back(
std::make_unique<NonImpRegister>(reg_addr, &register_map_));
}
}
// mcycle
register_map_.push_back(
std::make_unique<BaseRegister>(0xB00, &register_map_));
// minstret
register_map_.push_back(
std::make_unique<BaseRegister>(0xB02, &register_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, &register_map_, mhpmcounter_mask_low, 0));
} else {
register_map_.push_back(
std::make_unique<NonImpRegister>(reg_addr, &register_map_));
}
}
// mcycleh
register_map_.push_back(
std::make_unique<BaseRegister>(0xB80, &register_map_));
// minstreth
register_map_.push_back(
std::make_unique<BaseRegister>(0xB82, &register_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, &register_map_, mhpmcounter_mask_high, 0));
} else {
register_map_.push_back(
std::make_unique<NonImpRegister>(reg_addr, &register_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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
}
}
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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