From 90d0882ebbbb5321d53a8e3665d3f1742a1b1c5c Mon Sep 17 00:00:00 2001 From: Maciej Kurc Date: Mon, 26 Jun 2023 18:08:30 +0200 Subject: [PATCH] Enable using OpenOCD for interactive debugging of simulated Caliptra core (#72) * Added third-party code for JTAG DPI and TCP server necessary for communication with OpenOCD Signed-off-by: Maciej Kurc * Integrated the JTAG DPI interface with the testbench Signed-off-by: Maciej Kurc * Added a dummy infinite_loop test to exercise interactive JTAG connections Signed-off-by: Maciej Kurc * Added OpenOCD interface and target scripts Signed-off-by: Maciej Kurc * Increased TCP buffer size Signed-off-by: Maciej Kurc * Added debugging capability to JTAG DPI via optional ASCII art waveform display Signed-off-by: Maciej Kurc * Enable debugging in Caliptra testbench Signed-off-by: Maciej Kurc * Updated the infinite loop program, tweaked OpenOCD config. Signed-off-by: Maciej Kurc * Interactive debugging test in CI Signed-off-by: Maciej Kurc * Updated OpenOCD config and test script Signed-off-by: Maciej Kurc * Updated Interactive test CI flow Signed-off-by: Maciej Kurc * Added a test that dumps core registers and compares them with a golden reference Signed-off-by: Maciej Kurc * Set OpenOCD memory access mode to abstract Signed-off-by: Tomasz Michalak * jtagdpi: Fix typo in README Signed-off-by: Tomasz Michalak * Added a delay to the GDB script to give the simulation time to react Signed-off-by: Maciej Kurc * Added FST waveform dump capability to Verilated simulation Signed-off-by: Maciej Kurc * Set RISC-V abstract access in OpenOCD target config as default, limited number of parallel jobs when building verilated simulation Signed-off-by: Maciej Kurc * Added tests for memory and peripheral access via JTAG (to the CI as well) Signed-off-by: Maciej Kurc * Tweaked JTAG simulation CI script Signed-off-by: Maciej Kurc * Add possibility to control Caliptra debug lock at the makefile invocation level Signed-off-by: Maciej Kurc * Unlocked debugging in interactive simulation CI Signed-off-by: Maciej Kurc * Switched to OpenOCD fork in CI Signed-off-by: Maciej Kurc * Adjusted the infinite_loop test to fit in the build system for the tests. Signed-off-by: Maciej Kurc * Added a makefile option to enforce reset on CPU Signed-off-by: Maciej Kurc * Added OpenOCD config files for the case when the CPU is held in reset Signed-off-by: Maciej Kurc * Separated bash utility functions for interactive JTAG debugging tests Signed-off-by: Maciej Kurc * Added OpenOCD without GDB test to the CI Signed-off-by: Maciej Kurc * Removed leftover comments. Signed-off-by: Maciej Kurc --------- Signed-off-by: Maciej Kurc Signed-off-by: Tomasz Michalak Co-authored-by: Tomasz Michalak --- .github/scripts/gdb_test.sh | 87 ++++ .github/scripts/openocd_test.sh | 68 +++ .github/scripts/utils.sh | 49 ++ .github/workflows/interactive-debugging.yml | 317 +++++++++++++ src/integration/config/caliptra_top_tb.vf | 1 + src/integration/rtl/caliptra_top.sv | 4 + src/integration/tb/caliptra_top_tb.sv | 28 +- src/integration/tb/test_caliptra_top_tb.cpp | 19 +- .../test_suites/infinite_loop/caliptra_isr.h | 90 ++++ .../infinite_loop/dump_and_compare.sh | 12 + .../infinite_loop/dump_registers.gdb | 10 + .../test_suites/infinite_loop/infinite_loop.s | 62 +++ .../infinite_loop/infinite_loop.yml | 17 + .../test_suites/infinite_loop/mem_access.gdb | 24 + .../test_suites/infinite_loop/mem_access.sh | 11 + .../infinite_loop/mem_access_golden.txt | 3 + .../infinite_loop/peripheral_access.gdb | 30 ++ .../infinite_loop/peripheral_access.sh | 11 + .../infinite_loop/peripheral_access.tcl | 69 +++ .../peripheral_access_golden.txt | 9 + .../infinite_loop/regdump_golden.txt | 28 ++ .../test_suites/libs/jtagdpi/README.md | 13 + .../test_suites/libs/jtagdpi/jtagdpi.c | 210 +++++++++ .../test_suites/libs/jtagdpi/jtagdpi.h | 56 +++ .../test_suites/libs/jtagdpi/jtagdpi.sv | 47 ++ .../test_suites/libs/tcp_server/tcp_server.c | 439 ++++++++++++++++++ .../test_suites/libs/tcp_server/tcp_server.h | 71 +++ tools/scripts/Makefile | 47 +- .../openocd/board/caliptra-verilator-rst.cfg | 5 + .../openocd/board/caliptra-verilator.cfg | 5 + .../scripts/openocd/interface/sim-jtagdpi.cfg | 11 + tools/scripts/openocd/target/veer-el2-rst.cfg | 35 ++ tools/scripts/openocd/target/veer-el2.cfg | 28 ++ 33 files changed, 1896 insertions(+), 20 deletions(-) create mode 100755 .github/scripts/gdb_test.sh create mode 100755 .github/scripts/openocd_test.sh create mode 100644 .github/scripts/utils.sh create mode 100644 .github/workflows/interactive-debugging.yml create mode 100644 src/integration/test_suites/infinite_loop/caliptra_isr.h create mode 100755 src/integration/test_suites/infinite_loop/dump_and_compare.sh create mode 100644 src/integration/test_suites/infinite_loop/dump_registers.gdb create mode 100644 src/integration/test_suites/infinite_loop/infinite_loop.s create mode 100755 src/integration/test_suites/infinite_loop/infinite_loop.yml create mode 100644 src/integration/test_suites/infinite_loop/mem_access.gdb create mode 100755 src/integration/test_suites/infinite_loop/mem_access.sh create mode 100644 src/integration/test_suites/infinite_loop/mem_access_golden.txt create mode 100644 src/integration/test_suites/infinite_loop/peripheral_access.gdb create mode 100755 src/integration/test_suites/infinite_loop/peripheral_access.sh create mode 100644 src/integration/test_suites/infinite_loop/peripheral_access.tcl create mode 100644 src/integration/test_suites/infinite_loop/peripheral_access_golden.txt create mode 100644 src/integration/test_suites/infinite_loop/regdump_golden.txt create mode 100644 src/integration/test_suites/libs/jtagdpi/README.md create mode 100644 src/integration/test_suites/libs/jtagdpi/jtagdpi.c create mode 100644 src/integration/test_suites/libs/jtagdpi/jtagdpi.h create mode 100644 src/integration/test_suites/libs/jtagdpi/jtagdpi.sv create mode 100644 src/integration/test_suites/libs/tcp_server/tcp_server.c create mode 100644 src/integration/test_suites/libs/tcp_server/tcp_server.h create mode 100644 tools/scripts/openocd/board/caliptra-verilator-rst.cfg create mode 100644 tools/scripts/openocd/board/caliptra-verilator.cfg create mode 100644 tools/scripts/openocd/interface/sim-jtagdpi.cfg create mode 100644 tools/scripts/openocd/target/veer-el2-rst.cfg create mode 100644 tools/scripts/openocd/target/veer-el2.cfg diff --git a/.github/scripts/gdb_test.sh b/.github/scripts/gdb_test.sh new file mode 100755 index 000000000..90eb5ac6b --- /dev/null +++ b/.github/scripts/gdb_test.sh @@ -0,0 +1,87 @@ +#!/bin/bash +# +# This script runs Verilator RTL simulation and OpenOCD in background, invokes +# the supplied test command and shuts everything down. + +SIM_LOG=`realpath sim.log` +OPENOCD_LOG=`realpath openocd.log` + +set +e + +if [ "$#" -lt 1 ]; then + echo "Usage: gdb_test.sh [args ...]" + exit 1 +fi + +# Utils +source `dirname ${BASH_SOURCE[0]}`/utils.sh + +terminate_all () { + terminate ${OPENOCD_PID} + terminate ${SIM_PID} +} + +print_logs () { + echo -e "${COLOR_WHITE}======== Simulation log ========${COLOR_OFF}" + cat ${SIM_LOG} || true + echo -e "${COLOR_WHITE}======== OpenOCD log ========${COLOR_OFF}" + cat ${OPENOCD_LOG} || true +} + +echo -e "${COLOR_WHITE}======== Launching interactive simulation ========${COLOR_OFF}" + +# Start the simulation +echo -e "Starting simulation..." +obj_dir/Vcaliptra_top_tb >"${SIM_LOG}" 2>&1 & +SIM_PID=$! + +# Wait +wait_for_phrase "${SIM_LOG}" "CLP: ROM Flow in progress..." +if [ $? -ne 0 ]; then + echo -e "${COLOR_RED}Failed to start the simulation!${COLOR_OFF}" + print_logs + terminate_all; exit -1 +fi +echo -e "Simulation running and ready (pid=${SIM_PID})" + +# Launch OpenOCD +echo -e "Launching OpenOCD..." +cd ${CALIPTRA_ROOT}/tools/scripts/openocd && openocd -d2 -f board/caliptra-verilator.cfg >"${OPENOCD_LOG}" 2>&1 & +OPENOCD_PID=$! + +# Wait +wait_for_phrase "${OPENOCD_LOG}" "Listening on port 3333 for gdb connections" +if [ $? -ne 0 ]; then + echo -e "${COLOR_RED}Failed to start OpenOCD!${COLOR_OFF}" + print_logs + terminate_all; exit -1 +fi +echo -e "OpenOCD running and ready (pid=${OPENOCD_PID})" + +# Wait a bit +sleep 1s + +# Run the test +echo -e "${COLOR_WHITE}======== Running test '$@' ========${COLOR_OFF}" + +bash -c "$(printf ' %q' "$@")" +EXITCODE=$? + +if [ ${EXITCODE} -eq 0 ]; then + echo -e "${COLOR_GREEN}[PASSED]${COLOR_OFF}" +else + echo -e "${COLOR_RED}[FAILED]${COLOR_OFF}" +fi + +sleep 1s + +# Terminate +echo -e "${COLOR_WHITE}Terminating...${COLOR_OFF}" +terminate_all + +# Display logs +print_logs + +# Honor the exitcode +exit ${EXITCODE} + diff --git a/.github/scripts/openocd_test.sh b/.github/scripts/openocd_test.sh new file mode 100755 index 000000000..75a96ba6a --- /dev/null +++ b/.github/scripts/openocd_test.sh @@ -0,0 +1,68 @@ +#!/bin/bash +# +# This script runs Verilator RTL simulation in background and invokes OpenOCD +# to perform JTAG access test + +SIM_LOG=`realpath sim.log` +OPENOCD_LOG=`realpath openocd.log` + +set +e + +if [ "$#" -lt 1 ]; then + echo "Usage: openocd_test.sh [openocd args ...]" + exit 1 +fi +OPENOCD_ARGS=$@ + +# Utils +source `dirname ${BASH_SOURCE[0]}`/utils.sh + +print_logs () { + echo -e "${COLOR_WHITE}======== Simulation log ========${COLOR_OFF}" + cat ${SIM_LOG} || true + echo -e "${COLOR_WHITE}======== OpenOCD log ========${COLOR_OFF}" + cat ${OPENOCD_LOG} || true +} + +echo -e "${COLOR_WHITE}======== Launching interactive simulation ========${COLOR_OFF}" + +# Start the simulation +echo -e "Starting simulation..." +obj_dir/Vcaliptra_top_tb >"${SIM_LOG}" 2>&1 & +SIM_PID=$! + +# Wait +wait_for_phrase "${SIM_LOG}" "CLP: ROM Flow in progress..." +if [ $? -ne 0 ]; then + echo -e "${COLOR_RED}Failed to start the simulation!${COLOR_OFF}" + print_logs + terminate ${SIM_PID}; exit -1 +fi +echo -e "Simulation running and ready (pid=${SIM_PID})" + +# Wait a bit +sleep 5s + +# Run the test +echo -e "${COLOR_WHITE}======== Running OpenOCD test '$@' ========${COLOR_OFF}" +cd ${CALIPTRA_ROOT}/tools/scripts/openocd && openocd -d2 ${OPENOCD_ARGS} >"${OPENOCD_LOG}" 2>&1 +EXITCODE=$? + +if [ ${EXITCODE} -eq 0 ]; then + echo -e "${COLOR_GREEN}[PASSED]${COLOR_OFF}" +else + echo -e "${COLOR_RED}[FAILED]${COLOR_OFF}" +fi + +sleep 1s + +# Terminate +echo -e "${COLOR_WHITE}Terminating...${COLOR_OFF}" +terminate ${SIM_PID} + +# Display logs +print_logs + +# Honor the exitcode +exit ${EXITCODE} + diff --git a/.github/scripts/utils.sh b/.github/scripts/utils.sh new file mode 100644 index 000000000..e5496afd9 --- /dev/null +++ b/.github/scripts/utils.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +# Colors +COLOR_OFF='\033[0m' +COLOR_RED='\033[31m' +COLOR_GREEN='\033[32m' +COLOR_WHITE='\033[1;37m' + +# Waits until the given phrase appears in a log file (actively written to) +# Usage: wait_for_phrase +wait_for_phrase () { + + # Check if the log exists + sleep 1s + if ! [ -f "$1" ]; then + echo -e "${COLOR_RED}Log file '$1' not found!${COLOR_OFF}" + return -1 + fi + + # Wait for the phrase + DEADLINE=$((${EPOCHSECONDS} + 30)) + while [ ${EPOCHSECONDS} -lt ${DEADLINE} ] + do + # Check for the phrase + grep "$2" "$1" >/dev/null + if [ $? -eq 0 ]; then + return 0 + fi + + # Sleep and retry + sleep 1s + done + + # Timeout + return -1 +} + +# Terminates a process. First via SIGINT and if this doesn't work after 10s +# retries with SIGKILL +# Usage: terminate +terminate () { + + local PID=$1 + + # Gently interrupt, wait some time and then kill + /bin/kill -s SIGINT ${PID} || true + sleep 10s + /bin/kill -s SIGKILL ${PID} || true +} diff --git a/.github/workflows/interactive-debugging.yml b/.github/workflows/interactive-debugging.yml new file mode 100644 index 000000000..7898f9e18 --- /dev/null +++ b/.github/workflows/interactive-debugging.yml @@ -0,0 +1,317 @@ +# docs: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions + +name: Interactive debugging + +on: + push: + branches: ["main", "dev-goog", "dev-msft", "dev-public"] + pull_request: + + workflow_dispatch: + +jobs: + build_verilator: + name: + runs-on: ubuntu-22.04 + + env: + CARGO_INCREMENTAL: 0 + SCCACHE_VERSION: 0.3.3 + # TODO: To update to 5.006, clean up lint errors + VERILATOR_VERSION: v5.002 + SCCACHE_GHA_CACHE_TO: sccache-verilator-10000 + SCCACHE_GHA_CACHE_FROM: sccache-verilator- + # Change this to a new random value if you suspect the cache is corrupted + SCCACHE_C_CUSTOM_CACHE_BUSTER: f3e6951f0c1d + + steps: + - name: Restore sccache binary + uses: actions/cache/restore@v3 + id: sccache_bin_restore + with: + path: ~/.cargo/bin/sccache + key: sccache-bin-${{ env.SCCACHE_VERSION }}-${{ env.SCCACHE_C_CUSTOM_CACHE_BUSTER }} + + - name: Install sccache + if: steps.sccache_bin_restore.outputs.cache-hit != 'true' + run: | + cargo install sccache --version ${SCCACHE_VERSION} --no-default-features --features=gha + + - name: Save sccache binary + uses: actions/cache/save@v3 + if: steps.sccache_bin_restore.outputs.cache-hit != 'true' + with: + path: ~/.cargo/bin/sccache + key: sccache-bin-${{ env.SCCACHE_VERSION }}-${{ env.SCCACHE_C_CUSTOM_CACHE_BUSTER }} + + - name: Configure sccache + uses: actions/github-script@v6 + with: + script: | + core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); + core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); + + - name: Restore verilator dir + uses: actions/cache/restore@v3 + id: verilator_restore + with: + path: /opt/verilator + key: verilator-${{ env.VERILATOR_VERSION }}-${{ env.SCCACHE_C_CUSTOM_CACHE_BUSTER }} + + - name: Install verilator + if: steps.verilator_restore.outputs.cache-hit != 'true' + run: | + sudo apt-get install flex bison libfl2 libfl-dev help2man + cd /tmp/ + git clone -b "${VERILATOR_VERSION}" https://github.com/verilator/verilator + cd verilator + autoconf + ./configure --prefix=/opt/verilator CC="sccache gcc" CXX="sccache g++" + make -j6 + sudo make install + + - name: Save verilator dir + uses: actions/cache/save@v3 + if: steps.verilator_restore.outputs.cache-hit != 'true' + with: + path: /opt/verilator + key: verilator-${{ env.VERILATOR_VERSION }}-${{ env.SCCACHE_C_CUSTOM_CACHE_BUSTER }} + + - name: Pack Verilator + run: | + cd /opt && tar -czvf verilator.tar.gz verilator/ + + - name: Store Verilator binaries + uses: actions/upload-artifact@v3 + with: + name: verilator + path: /opt/*.tar.gz + retention-days: 1 + + build_openocd: + name: + runs-on: ubuntu-22.04 + + env: + CARGO_INCREMENTAL: 0 + SCCACHE_VERSION: 0.3.3 + # A custom fork is needed to allow bypassing core examination and accessing + # peripherals regardless of core state. + OPENOCD_REPO: https://github.com/antmicro/openocd + OPENOCD_VERSION: riscv-nohalt + SCCACHE_GHA_CACHE_TO: sccache-openocd-10000 + SCCACHE_GHA_CACHE_FROM: sccache-openocd- + # Change this to a new random value if you suspect the cache is corrupted + SCCACHE_C_CUSTOM_CACHE_BUSTER: f3e6951f0c1e + + steps: + - name: Restore sccache binary + uses: actions/cache/restore@v3 + id: sccache_bin_restore + with: + path: ~/.cargo/bin/sccache + key: sccache-bin-${{ env.SCCACHE_VERSION }}-${{ env.SCCACHE_C_CUSTOM_CACHE_BUSTER }} + + - name: Install sccache + if: steps.sccache_bin_restore.outputs.cache-hit != 'true' + run: | + cargo install sccache --version ${SCCACHE_VERSION} --no-default-features --features=gha + + - name: Save sccache binary + uses: actions/cache/save@v3 + if: steps.sccache_bin_restore.outputs.cache-hit != 'true' + with: + path: ~/.cargo/bin/sccache + key: sccache-bin-${{ env.SCCACHE_VERSION }}-${{ env.SCCACHE_C_CUSTOM_CACHE_BUSTER }} + + - name: Configure sccache + uses: actions/github-script@v6 + with: + script: | + core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); + core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); + + - name: Restore OpenOCD dir + uses: actions/cache/restore@v3 + id: openocd_restore + with: + path: /opt/openocd + key: openocd-${{ env.OPENOCD_VERSION }}-${{ env.SCCACHE_C_CUSTOM_CACHE_BUSTER }} + + - name: Install OpenOCD + if: steps.openocd_restore.outputs.cache-hit != 'true' + run: | + sudo apt-get install make libtool pkg-config autoconf automake texinfo + cd /tmp/ + git clone -b "${OPENOCD_VERSION}" https://github.com/antmicro/openocd + cd openocd + ./bootstrap + ./configure --prefix=/opt/openocd --enable-remote-bitbang \ + CC="sccache gcc" CXX="sccache g++" \ + CFLAGS="-Wno-error=misleading-indentation -Wno-error=stringop-overflow" + make -j6 + sudo make install + + - name: Save OpenOCD dir + uses: actions/cache/save@v3 + if: steps.openocd_restore.outputs.cache-hit != 'true' + with: + path: /opt/openocd + key: openocd-${{ env.OPENOCD_VERSION }}-${{ env.SCCACHE_C_CUSTOM_CACHE_BUSTER }} + + - name: Pack OpenOCD + run: | + cd /opt && tar -czvf openocd.tar.gz openocd/ + + - name: Store OpenOCD binaries + uses: actions/upload-artifact@v3 + with: + name: openocd + path: /opt/*.tar.gz + retention-days: 1 + + gdb_tests: + name: Run GDB debugging tests + runs-on: ubuntu-22.04 + needs: [build_verilator, build_openocd] + + env: + CARGO_INCREMENTAL: 0 + PKG_CONFIG_PATH: /opt/verilator/share/pkgconfig + DEBIAN_FRONTEND: "noninteractive" + + steps: + + - name: Install Risc V Toolchain + run: | + # Building from source takes around 6.65 GB of disk and download size + wget -O toolchain.tar.gz https://github.com/stnolting/riscv-gcc-prebuilt/releases/download/rv64imc-3.0.0/riscv64-unknown-elf.gcc-12.1.0.tar.gz + mkdir /opt/riscv + tar -xzf toolchain.tar.gz -C /opt/riscv/ + + - name: Install dependencies + run: | + # For some reason GDB requires libpython3.8.so + sudo add-apt-repository ppa:deadsnakes/ppa -y + sudo apt update -qy && sudo apt install -qy --no-install-recommends \ + libpython3.8 + + - name: Download Verilator binaries + uses: actions/download-artifact@v3 + with: + name: verilator + path: /opt + + - name: Download OpenOCD binaries + uses: actions/download-artifact@v3 + with: + name: openocd + path: /opt + + - name: Unpack binaries + run: | + pushd /opt + tar -zxvf verilator.tar.gz + tar -zxvf openocd.tar.gz + popd + + - name: Setup path + run: | + echo /opt/riscv/bin:/opt/verilator/bin:/opt/openocd/bin >> $GITHUB_PATH + + - name: Clone repository + uses: actions/checkout@v3 + with: + submodules: 'true' + + - name: Build Verilated simulation + run: | + export CALIPTRA_ROOT=$(pwd) + mkdir run + make -C run -f ${CALIPTRA_ROOT}/tools/scripts/Makefile verilator-build TESTNAME=infinite_loop DEBUG_UNLOCKED=1 \ + OBJCACHE="" CC=gcc CXX=g++ LINK=g++ + make -C run -f ${CALIPTRA_ROOT}/tools/scripts/Makefile program.hex TESTNAME=infinite_loop + + - name: Test core register access + run: | + export CALIPTRA_ROOT=$(pwd) + cd run + ${CALIPTRA_ROOT}/.github/scripts/gdb_test.sh \ + /bin/bash -c 'cd ${CALIPTRA_ROOT}/src/integration/test_suites/infinite_loop && ./dump_and_compare.sh' + + - name: Test memory access + run: | + export CALIPTRA_ROOT=$(pwd) + cd run + ${CALIPTRA_ROOT}/.github/scripts/gdb_test.sh \ + /bin/bash -c 'cd ${CALIPTRA_ROOT}/src/integration/test_suites/infinite_loop && ./mem_access.sh' + + - name: Test peripheral access + run: | + export CALIPTRA_ROOT=$(pwd) + cd run + ${CALIPTRA_ROOT}/.github/scripts/gdb_test.sh \ + /bin/bash -c 'cd ${CALIPTRA_ROOT}/src/integration/test_suites/infinite_loop && ./peripheral_access.sh' + + openocd_tests: + name: Run OpenOCD debugging tests + runs-on: ubuntu-22.04 + needs: [build_verilator, build_openocd] + + env: + CARGO_INCREMENTAL: 0 + PKG_CONFIG_PATH: /opt/verilator/share/pkgconfig + DEBIAN_FRONTEND: "noninteractive" + + steps: + + - name: Install Risc V Toolchain + run: | + # Building from source takes around 6.65 GB of disk and download size + wget -O toolchain.tar.gz https://github.com/stnolting/riscv-gcc-prebuilt/releases/download/rv64imc-3.0.0/riscv64-unknown-elf.gcc-12.1.0.tar.gz + mkdir /opt/riscv + tar -xzf toolchain.tar.gz -C /opt/riscv/ + + - name: Download Verilator binaries + uses: actions/download-artifact@v3 + with: + name: verilator + path: /opt + + - name: Download OpenOCD binaries + uses: actions/download-artifact@v3 + with: + name: openocd + path: /opt + + - name: Unpack binaries + run: | + pushd /opt + tar -zxvf verilator.tar.gz + tar -zxvf openocd.tar.gz + popd + + - name: Setup path + run: | + echo /opt/riscv/bin:/opt/verilator/bin:/opt/openocd/bin >> $GITHUB_PATH + + - name: Clone repository + uses: actions/checkout@v3 + with: + submodules: 'true' + + - name: Build Verilated simulation + run: | + export CALIPTRA_ROOT=$(pwd) + mkdir run + make -C run -f ${CALIPTRA_ROOT}/tools/scripts/Makefile verilator-build TESTNAME=infinite_loop DEBUG_UNLOCKED=1 FORCE_CPU_RESET=1 \ + OBJCACHE="" CC=gcc CXX=g++ LINK=g++ + make -C run -f ${CALIPTRA_ROOT}/tools/scripts/Makefile program.hex TESTNAME=infinite_loop + + - name: Test peripheral access with core in reset + run: | + export CALIPTRA_ROOT=$(pwd) + cd run + ${CALIPTRA_ROOT}/.github/scripts/openocd_test.sh \ + -f board/caliptra-verilator-rst.cfg \ + -f ${CALIPTRA_ROOT}/src/integration/test_suites/infinite_loop/peripheral_access.tcl diff --git a/src/integration/config/caliptra_top_tb.vf b/src/integration/config/caliptra_top_tb.vf index 11e9829b5..6f992be2d 100644 --- a/src/integration/config/caliptra_top_tb.vf +++ b/src/integration/config/caliptra_top_tb.vf @@ -310,4 +310,5 @@ ${CALIPTRA_ROOT}/src/uart/rtl/uart_reg_top.sv ${CALIPTRA_ROOT}/src/uart/rtl/uart_rx.sv ${CALIPTRA_ROOT}/src/uart/rtl/uart.sv ${CALIPTRA_ROOT}/src/uart/rtl/uart_core.sv +${CALIPTRA_ROOT}/src/integration/test_suites/libs/jtagdpi/jtagdpi.sv ${CALIPTRA_ROOT}/src/integration/rtl/caliptra_top.sv diff --git a/src/integration/rtl/caliptra_top.sv b/src/integration/rtl/caliptra_top.sv index 69014b1bf..1577ccf85 100755 --- a/src/integration/rtl/caliptra_top.sv +++ b/src/integration/rtl/caliptra_top.sv @@ -395,7 +395,11 @@ always_comb begin end el2_veer_wrapper rvtop ( +`ifdef CALIPTRA_FORCE_CPU_RESET + .rst_l ( 1'b0 ), +`else .rst_l ( cptra_uc_rst_b), +`endif .dbg_rst_l ( cptra_pwrgood), .clk ( clk ), .rst_vec ( reset_vector[31:1]), diff --git a/src/integration/tb/caliptra_top_tb.sv b/src/integration/tb/caliptra_top_tb.sv index 3829c330c..89e8618b5 100755 --- a/src/integration/tb/caliptra_top_tb.sv +++ b/src/integration/tb/caliptra_top_tb.sv @@ -144,6 +144,11 @@ module caliptra_top_tb ( //device lifecycle security_state_t security_state; +`ifdef CALIPTRA_DEBUG_UNLOCKED + assign security_state = '{device_lifecycle: DEVICE_PRODUCTION, debug_locked: 1'b0}; // DebugUnlocked & Production +`else + assign security_state = '{device_lifecycle: DEVICE_PRODUCTION, debug_locked: 1'b1}; // DebugLocked & Production +`endif ras_test_ctrl_t ras_test_ctrl; logic [63:0] generic_input_wires; @@ -316,11 +321,6 @@ module caliptra_top_tb ( BootFSM_BrkPoint = 1'b1; //Set to 1 even before anything starts cptra_rst_b = 1'b0; start_apb_fuse_sequence = 1'b0; - //tie offs - jtag_tck = 1'b0; // JTAG clk - jtag_tms = 1'b0; // JTAG TMS - jtag_tdi = 1'b0; // JTAG tdi - jtag_trst_n = 1'b0; // JTAG Reset //TIE-OFF PPROT = '0; @@ -847,6 +847,20 @@ module caliptra_top_tb ( endcase end +// JTAG DPI +jtagdpi #( + .Name ("jtag0"), + .ListenPort (5000) +) jtagdpi ( + .clk_i (core_clk), + .rst_ni (cptra_rst_b), + .jtag_tck (jtag_tck), + .jtag_tms (jtag_tms), + .jtag_tdi (jtag_tdi), + .jtag_tdo (jtag_tdo), + .jtag_trst_n (jtag_trst_n), + .jtag_srst_n () +); //=========================================================================- // DUT instance @@ -923,8 +937,8 @@ caliptra_top caliptra_top_dut ( .generic_input_wires(generic_input_wires), .generic_output_wires(), - .security_state(security_state), //FIXME TIE-OFF - .scan_mode (scan_mode) //FIXME TIE-OFF + .security_state(security_state), + .scan_mode (scan_mode) ); diff --git a/src/integration/tb/test_caliptra_top_tb.cpp b/src/integration/tb/test_caliptra_top_tb.cpp index a838fbd53..05b7f47db 100644 --- a/src/integration/tb/test_caliptra_top_tb.cpp +++ b/src/integration/tb/test_caliptra_top_tb.cpp @@ -19,8 +19,11 @@ #include #include "Vcaliptra_top_tb.h" #include "verilated.h" +#if VM_TRACE_VCD #include "verilated_vcd_c.h" - +#elif VM_TRACE_FST +#include "verilated_fst_c.h" +#endif vluint64_t main_time = 0; @@ -37,13 +40,17 @@ int main(int argc, char** argv) { Vcaliptra_top_tb* tb = new Vcaliptra_top_tb; // init trace dump - VerilatedVcdC* tfp = NULL; - #if VM_TRACE Verilated::traceEverOn(true); - tfp = new VerilatedVcdC; - tb->trace (tfp, 24); - tfp->open ("sim.vcd"); + #if VM_TRACE_VCD + VerilatedVcdC* tfp = new VerilatedVcdC; + tb->trace (tfp, 24); + tfp->open ("sim.vcd"); + #elif VM_TRACE_FST + VerilatedFstC* tfp = new VerilatedFstC; + tb->trace (tfp, 24); + tfp->open ("sim.fst"); + #endif #endif // Simulate while(!Verilated::gotFinish()){ diff --git a/src/integration/test_suites/infinite_loop/caliptra_isr.h b/src/integration/test_suites/infinite_loop/caliptra_isr.h new file mode 100644 index 000000000..7bd443326 --- /dev/null +++ b/src/integration/test_suites/infinite_loop/caliptra_isr.h @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// 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. +// +// --------------------------------------------------------------------- +// File: caliptra_isr.h +// Description: +// Dummy ISRs +// --------------------------------------------------------------------- + +#ifndef CALIPTRA_ISR_H +#define CALIPTRA_ISR_H + +#include "caliptra_defines.h" +#include + +/* --------------- symbols/typedefs --------------- */ + +typedef struct { + uint32_t doe_error; + uint32_t doe_notif; + uint32_t ecc_error; + uint32_t ecc_notif; + uint32_t hmac_error; + uint32_t hmac_notif; + uint32_t kv_error; + uint32_t kv_notif; + uint32_t sha512_error; + uint32_t sha512_notif; + uint32_t sha256_error; + uint32_t sha256_notif; + uint32_t qspi_error; + uint32_t qspi_notif; + uint32_t uart_error; + uint32_t uart_notif; + uint32_t i3c_error; + uint32_t i3c_notif; + uint32_t soc_ifc_error; + uint32_t soc_ifc_notif; + uint32_t sha512_acc_error; + uint32_t sha512_acc_notif; +} caliptra_intr_received_s; + +////////////////////////////////////////////////////////////////////////////// +// Function Declarations + +inline void service_doe_error_intr() {return;} +inline void service_doe_notif_intr() {return;} + +inline void service_ecc_error_intr() {return;} +inline void service_ecc_notif_intr() {return;} + +inline void service_hmac_error_intr() {return;} +inline void service_hmac_notif_intr() {return;} + +inline void service_kv_error_intr() {return;} +inline void service_kv_notif_intr() {return;} + +inline void service_sha512_error_intr() {return;} +inline void service_sha512_notif_intr() {return;} + +inline void service_sha256_error_intr() {return;} +inline void service_sha256_notif_intr() {return;} + +inline void service_qspi_error_intr() {return;} +inline void service_qspi_notif_intr() {return;} + +inline void service_uart_error_intr() {return;} +inline void service_uart_notif_intr() {return;} + +inline void service_i3c_error_intr() {return;} +inline void service_i3c_notif_intr() {return;} + +inline void service_soc_ifc_error_intr() {return;} +inline void service_soc_ifc_notif_intr () {return;} + +inline void service_sha512_acc_error_intr() {return;} +inline void service_sha512_acc_notif_intr() {return;} + +#endif //CALIPTRA_ISR_H diff --git a/src/integration/test_suites/infinite_loop/dump_and_compare.sh b/src/integration/test_suites/infinite_loop/dump_and_compare.sh new file mode 100755 index 000000000..79980e51e --- /dev/null +++ b/src/integration/test_suites/infinite_loop/dump_and_compare.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -ex + +# Invoke GDB and dump core registers +riscv64-unknown-elf-gdb -n --batch -x dump_registers.gdb >gdb.log +# Parse the log, extract register values. Skip those which change as the +# program executes since we don't know at which point we tap in. +cat gdb.log | grep -E '^ra |^sp |^gp |^tp |^t[01256] |^s[0-9]+ |^a[0-9]+ |^\$[0-9]+' >regdump.txt + +# Compare the dumps +diff -E -y regdump_golden.txt regdump.txt + diff --git a/src/integration/test_suites/infinite_loop/dump_registers.gdb b/src/integration/test_suites/infinite_loop/dump_registers.gdb new file mode 100644 index 000000000..1ea981d4b --- /dev/null +++ b/src/integration/test_suites/infinite_loop/dump_registers.gdb @@ -0,0 +1,10 @@ +echo Connecting to OpenOCD...\n +set architecture riscv:rv32 +set remotetimeout 30 +target extended-remote :3333 + +echo Connected, waiting...\n +shell sleep 30s + +echo Dumping registers...\n +info registers diff --git a/src/integration/test_suites/infinite_loop/infinite_loop.s b/src/integration/test_suites/infinite_loop/infinite_loop.s new file mode 100644 index 000000000..804e144b5 --- /dev/null +++ b/src/integration/test_suites/infinite_loop/infinite_loop.s @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2019 Western Digital Corporation or its affiliates. +// +// 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. +// + +#include "caliptra_defines.h" + +.set mfdc, 0x7f9 + +// Code to execute +.section .text +.global _start +_start: + + // Enable Caches in MRAC + li x1, 0xaaaaaaaa + csrw 0x7c0, x1 + + li x3, 4 + csrw mfdc, x3 // disable store merging + + // Set some register values + li x1, 0x12345678 + li x2, 0xABCDEF00 + li x3, 0xCAFEBABA + li x4, 0xDEADBEEF + li x5, 0x05050505 + li x6, 0xA0A0A0A0 + li x7, 0x00FF00FF + li x8, 0xCC00CC00 + + // Simple infinite loop program with inner and outer loop + li t3, 0 +outer: + addi t3, t3, 1 + li t4, 123 +inner: + addi t4, t4, -1 + bne t4, zero, inner + jal x0, outer + +.section .dccm +.global stdout +stdout: .word STDOUT +.global verbosity_g +verbosity_g: .word 2 + +.global intr_count +intr_count: .word 0 +.global cptra_intr_rcv +cptra_intr_rcv: .word 0 diff --git a/src/integration/test_suites/infinite_loop/infinite_loop.yml b/src/integration/test_suites/infinite_loop/infinite_loop.yml new file mode 100755 index 000000000..89d86fb13 --- /dev/null +++ b/src/integration/test_suites/infinite_loop/infinite_loop.yml @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# 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. +# +--- +seed: 1 +testname: infinite_loop diff --git a/src/integration/test_suites/infinite_loop/mem_access.gdb b/src/integration/test_suites/infinite_loop/mem_access.gdb new file mode 100644 index 000000000..fb6670ddc --- /dev/null +++ b/src/integration/test_suites/infinite_loop/mem_access.gdb @@ -0,0 +1,24 @@ +echo Connecting to OpenOCD...\n +set architecture riscv:rv32 +set remotetimeout 30 +target extended-remote :3333 + +echo Connected, waiting...\n +shell sleep 5s + +echo Accessing DCCM...\n +set *(0x50000000) = 0xCAFEBABA +set *(0x50000004) = 0xDEADBEEF +set *(0x50000008) = 0xFEEDBACA +set *(0x5000000C) = 0xA5A5A5A5 +print/x *0x50000000@4 + +echo Accessing ICCM...\n +set *(0x40000100) = 0x01234567 +set *(0x40000104) = 0x89ABCDEF +set *(0x40000108) = 0x55555555 +set *(0x4000010C) = 0xAAAAAAAA +print/x *0x40000100@4 + +echo Accessing ROM...\n +print/x *0x00000000@8 diff --git a/src/integration/test_suites/infinite_loop/mem_access.sh b/src/integration/test_suites/infinite_loop/mem_access.sh new file mode 100755 index 000000000..dcae5cf75 --- /dev/null +++ b/src/integration/test_suites/infinite_loop/mem_access.sh @@ -0,0 +1,11 @@ +#!/bin/bash +set -ex + +# Invoke GDB +riscv64-unknown-elf-gdb -n --batch -x mem_access.gdb >gdb.log +# Parse the log +cat gdb.log | grep -E '^\$[0-9]+' >out.txt + +# Compare the dumps +diff -E -y mem_access_golden.txt out.txt + diff --git a/src/integration/test_suites/infinite_loop/mem_access_golden.txt b/src/integration/test_suites/infinite_loop/mem_access_golden.txt new file mode 100644 index 000000000..1f6b00925 --- /dev/null +++ b/src/integration/test_suites/infinite_loop/mem_access_golden.txt @@ -0,0 +1,3 @@ +$1 = {0xcafebaba, 0xdeadbeef, 0xfeedbaca, 0xa5a5a5a5} +$2 = {0x1234567, 0x89abcdef, 0x55555555, 0xaaaaaaaa} +$3 = {0xaaaab0b7, 0xaaa08093, 0x7c009073, 0x90734191, 0x50b77f91, 0x80931234, 0xf1376780, 0x7111abcd} diff --git a/src/integration/test_suites/infinite_loop/peripheral_access.gdb b/src/integration/test_suites/infinite_loop/peripheral_access.gdb new file mode 100644 index 000000000..93ed0f9af --- /dev/null +++ b/src/integration/test_suites/infinite_loop/peripheral_access.gdb @@ -0,0 +1,30 @@ +echo Connecting to OpenOCD...\n +set architecture riscv:rv32 +set remotetimeout 30 +target extended-remote :3333 + +echo Connected, waiting...\n +shell sleep 5s + +echo Accessing ECC...\n +print/x *0x10008000@2 +print/x *0x10008008@2 + +echo Accessing HMAC...\n +print/x *0x10010000@2 +print/x *0x10010008@2 + +echo Accessing SHA512...\n +print/x *0x10020000@2 +print/x *0x10020008@2 + +echo Accessing SHA256...\n +print/x *0x10028000@2 +print/x *0x10028008@2 + +echo Writing and reading DOE IV...\n +set *(0x10000000) = 0xCAFEBABA +set *(0x10000004) = 0xDEADBEEF +set *(0x10000008) = 0xD0ED0E00 +print/x *0x10000000@3 + diff --git a/src/integration/test_suites/infinite_loop/peripheral_access.sh b/src/integration/test_suites/infinite_loop/peripheral_access.sh new file mode 100755 index 000000000..b0119da2a --- /dev/null +++ b/src/integration/test_suites/infinite_loop/peripheral_access.sh @@ -0,0 +1,11 @@ +#!/bin/bash +set -ex + +# Invoke GDB +riscv64-unknown-elf-gdb -n --batch -x peripheral_access.gdb >gdb.log +# Parse the log +cat gdb.log | grep -E '^\$[0-9]+' >out.txt + +# Compare the dumps +diff -E -y peripheral_access_golden.txt out.txt + diff --git a/src/integration/test_suites/infinite_loop/peripheral_access.tcl b/src/integration/test_suites/infinite_loop/peripheral_access.tcl new file mode 100644 index 000000000..3d710ca19 --- /dev/null +++ b/src/integration/test_suites/infinite_loop/peripheral_access.tcl @@ -0,0 +1,69 @@ +init + +proc compare {x y} { + puts "'$x' vs. '$y'" + + if {[llength $y] != [llength $y]} { + puts "length mismatch!" + return -1 + } + + for {set i 0} {$i < [llength $x]} {incr i} { + if {[lindex $x $i] != [lindex $y $i]} { + puts "item $i mismatch!" + return -1 + } + } + + return 0 +} + +# Manually read dmstatus and check if the core is actually held in external +# reset. In the expected state bits anyunavail allrunning anyrunning allhalted +# and anyhalted should be cleared. +set val [riscv dmi_read 0x11] +puts "dmstatus: $val" + +if { ($val & 0x00000F00) != 0 } { + echo "The core is not held in reset!" + shutdown error +} + +echo "Accessing ECC..." +set golden { 0x63707365 0x38342d33 0x3030312e 0x0 } +set actual [ read_memory 0x10008000 32 4 phys ] +if {[compare $actual $golden] != 0} { + shutdown error +} + +echo "Accessing HMAC..." +set golden { 0x6163686d 0x61327368 0x3030312e 0x0 } +set actual [ read_memory 0x10010000 32 4 phys ] +if {[compare $actual $golden] != 0} { + shutdown error +} + +echo "Accessing SHA512..." +set golden { 0x61327368 0x31322d35 0x3830302e 0x0 } +set actual [ read_memory 0x10020000 32 4 phys ] +if {[compare $actual $golden] != 0} { + shutdown error +} + +echo "Accessing SHA256..." +set golden { 0x61327368 0x35362d32 0x3830312e 0x0 } +set actual [ read_memory 0x10028000 32 4 phys ] +if {[compare $actual $golden] != 0} { + shutdown error +} + +echo "Writing and reading DOE IV..." +set golden { 0xCAFEBABA 0xDEADBEEF 0xD0ED0E00 } +write_memory 0x10000000 32 $golden phys +set actual [ read_memory 0x10000000 32 3 phys ] +if {[compare $actual $golden] != 0} { + shutdown error +} + +# Success +shutdown diff --git a/src/integration/test_suites/infinite_loop/peripheral_access_golden.txt b/src/integration/test_suites/infinite_loop/peripheral_access_golden.txt new file mode 100644 index 000000000..2c9759e24 --- /dev/null +++ b/src/integration/test_suites/infinite_loop/peripheral_access_golden.txt @@ -0,0 +1,9 @@ +$1 = {0x63707365, 0x38342d33} +$2 = {0x3030312e, 0x0} +$3 = {0x6163686d, 0x61327368} +$4 = {0x3030312e, 0x0} +$5 = {0x61327368, 0x31322d35} +$6 = {0x3830302e, 0x0} +$7 = {0x61327368, 0x35362d32} +$8 = {0x3830312e, 0x0} +$9 = {0xcafebaba, 0xdeadbeef, 0xd0ed0e00} diff --git a/src/integration/test_suites/infinite_loop/regdump_golden.txt b/src/integration/test_suites/infinite_loop/regdump_golden.txt new file mode 100644 index 000000000..27196e7d5 --- /dev/null +++ b/src/integration/test_suites/infinite_loop/regdump_golden.txt @@ -0,0 +1,28 @@ +ra 0x12345678 0x12345678 +sp 0xabcdef00 0xabcdef00 +gp 0xcafebaba 0xcafebaba +tp 0xdeadbeef 0xdeadbeef +t0 0x5050505 84215045 +t1 0xa0a0a0a0 -1600085856 +t2 0xff00ff 16711935 +s1 0x0 0 +a0 0x0 0 +a1 0x0 0 +a2 0x0 0 +a3 0x0 0 +a4 0x0 0 +a5 0x0 0 +a6 0x0 0 +a7 0x0 0 +s2 0x0 0 +s3 0x0 0 +s4 0x0 0 +s5 0x0 0 +s6 0x0 0 +s7 0x0 0 +s8 0x0 0 +s9 0x0 0 +s10 0x0 0 +s11 0x0 0 +t5 0x0 0 +t6 0x0 0 diff --git a/src/integration/test_suites/libs/jtagdpi/README.md b/src/integration/test_suites/libs/jtagdpi/README.md new file mode 100644 index 000000000..52021ce1f --- /dev/null +++ b/src/integration/test_suites/libs/jtagdpi/README.md @@ -0,0 +1,13 @@ +JTAG DPI module for OpenOCD remote_bitbang driver +================================================= + +This DPI module provides a "virtual" JTAG connection between a simulated chip +and [OpenOCD](http://openocd.org/). It makes use of the `remote_bitbang` JTAG +driver shipped with OpenOCD, which forwards JTAG requests over TCP to a remote +server. The `jtagdpi` module is instantiated in the hardware simulation to +receive the JTAG requests from OpenOCD and drive the JTAG pins (TCK, TMS, TDI, +etc.) from it. + +The `remote_bitbang` protocol is documented in the OpenOCD source tree at +`doc/manual/jtag/drivers/remote_bitbang.txt`, or online at +https://repo.or.cz/openocd.git/blob/HEAD:/doc/manual/jtag/drivers/remote_bitbang.txt diff --git a/src/integration/test_suites/libs/jtagdpi/jtagdpi.c b/src/integration/test_suites/libs/jtagdpi/jtagdpi.c new file mode 100644 index 000000000..e0b270874 --- /dev/null +++ b/src/integration/test_suites/libs/jtagdpi/jtagdpi.c @@ -0,0 +1,210 @@ +// Copyright lowRISC contributors. +// Copyright 2023 Antmicro. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include "jtagdpi.h" + +#include +#include +#include +#include +#include + +#include "tcp_server.h" + +// Uncomment to enable JTAG DPI debugging. The code will print vertically +// oriented waveform for all JTAG signals. +//#define JTAGDPI_DEBUG + +struct jtagdpi_signals { + uint8_t tck; + uint8_t tms; + uint8_t tdi; + uint8_t tdo; + uint8_t trst_n; + uint8_t srst_n; +}; + +struct jtagdpi_ctx { + // Server context + struct tcp_server_ctx *sock; + // Signals + struct jtagdpi_signals curr; +#ifdef JTAGDPI_DEBUG + struct jtagdpi_signals prev; + uint8_t init; +#endif +}; + +/** + * Reset the JTAG signals to a "dongle unplugged" state + */ +static void reset_jtag_signals(struct jtagdpi_ctx *ctx) { + assert(ctx); + + // Set all to zero + memset(&ctx->curr, 0, sizeof(struct jtagdpi_signals)); +#ifdef JTAGDPI_DEBUG + memset(&ctx->prev, 0, sizeof(struct jtagdpi_signals)); +#endif + + // trst_n is pulled down (reset active) by default + // srst_n is pulled up (reset not active) by default + ctx->curr.srst_n = 1; +#ifdef JTAGDPI_DEBUG + ctx->prev.srst_n = 1; +#endif +} + +/** + * Update the JTAG signals in the context structure + */ +static void update_jtag_signals(struct jtagdpi_ctx *ctx) { + assert(ctx); + + /* + * Documentation pointer: + * The remote_bitbang protocol implemented below is documented in the OpenOCD + * source tree at doc/manual/jtag/drivers/remote_bitbang.txt, or online at + * https://repo.or.cz/openocd.git/blob/HEAD:/doc/manual/jtag/drivers/remote_bitbang.txt + */ + + // read a command byte + char cmd; + if (!tcp_server_read(ctx->sock, &cmd)) { + return; + } + + bool act_send_resp = false; + bool act_quit = false; + + // parse received command byte + if (cmd >= '0' && cmd <= '7') { + // JTAG write + char cmd_bit = cmd - '0'; + ctx->curr.tdi = (cmd_bit >> 0) & 0x1; + ctx->curr.tms = (cmd_bit >> 1) & 0x1; + ctx->curr.tck = (cmd_bit >> 2) & 0x1; + } else if (cmd >= 'r' && cmd <= 'u') { + // JTAG reset (active high from OpenOCD) + char cmd_bit = cmd - 'r'; + ctx->curr.srst_n = !((cmd_bit >> 0) & 0x1); + ctx->curr.trst_n = !((cmd_bit >> 1) & 0x1); + } else if (cmd == 'R') { + // JTAG read + act_send_resp = true; + } else if (cmd == 'B') { + // printf("%s: BLINK ON!\n", ctx->display_name); + } else if (cmd == 'b') { + // printf("%s: BLINK OFF!\n", ctx->display_name); + } else if (cmd == 'Q') { + // quit (client disconnect) + act_quit = true; + } else { + fprintf(stderr, + "JTAG DPI Protocol violation detected: unsupported command %c\n", + cmd); + exit(1); + } + + // send tdo as response + if (act_send_resp) { + char tdo_ascii = ctx->curr.tdo + '0'; + tcp_server_write(ctx->sock, tdo_ascii); + } + + if (act_quit) { + printf("JTAG DPI: Remote disconnected.\n"); + tcp_server_client_close(ctx->sock); + } +} + +void *jtagdpi_create(const char *display_name, int listen_port) { + struct jtagdpi_ctx *ctx = + (struct jtagdpi_ctx *)calloc(1, sizeof(struct jtagdpi_ctx)); + assert(ctx); + + // Create socket + ctx->sock = tcp_server_create(display_name, listen_port); +#ifdef JTAGDPI_DEBUG + ctx->init = 1; +#endif + + reset_jtag_signals(ctx); + + printf( + "\n" + "JTAG: Virtual JTAG interface %s is listening on port %d. Use\n" + "OpenOCD and the following configuration to connect:\n" + " interface remote_bitbang\n" + " remote_bitbang_host localhost\n" + " remote_bitbang_port %d\n", + display_name, listen_port, listen_port); + + return (void *)ctx; +} + +void jtagdpi_close(void *ctx_void) { + struct jtagdpi_ctx *ctx = (struct jtagdpi_ctx *)ctx_void; + if (!ctx) { + return; + } + tcp_server_close(ctx->sock); + free(ctx); +} + +#ifdef JTAGDPI_DEBUG +static void jtagdpi_dbg(struct jtagdpi_ctx *ctx) { + + uint8_t* curr = (uint8_t*)&ctx->curr; + uint8_t* prev = (uint8_t*)&ctx->prev; + + if (ctx->init) { + fprintf(stderr, "tck tms tdi tdo trst srst\n"); + ctx->init = 0; + } + + for (int i=0; i<6; ++i) { + if (!prev[i] && curr[i]) { + fprintf(stderr, "\\ "); + } + if ( prev[i] && curr[i]) { + fprintf(stderr, " | "); + } + if ( prev[i] && !curr[i]) { + fprintf(stderr, "/ "); + } + if (!prev[i] && !curr[i]) { + fprintf(stderr, "| "); + } + } + fprintf(stderr, "\n"); +} +#endif + +void jtagdpi_tick(void *ctx_void, svBit *tck, svBit *tms, svBit *tdi, + svBit *trst_n, svBit *srst_n, const svBit tdo) { + struct jtagdpi_ctx *ctx = (struct jtagdpi_ctx *)ctx_void; + + // Get TDO + ctx->curr.tdo = tdo; + + // TODO: Evaluate moving this functionality into a separate thread + if (ctx) { + update_jtag_signals(ctx); + } + +#ifdef JTAGDPI_DEBUG + if (memcmp(&ctx->curr, &ctx->prev, sizeof(struct jtagdpi_signals))) { + jtagdpi_dbg(ctx); + memcpy(&ctx->prev, &ctx->curr, sizeof(struct jtagdpi_signals)); + } +#endif + + *tdi = ctx->curr.tdi; + *tms = ctx->curr.tms; + *tck = ctx->curr.tck; + *srst_n = ctx->curr.srst_n; + *trst_n = ctx->curr.trst_n; +} diff --git a/src/integration/test_suites/libs/jtagdpi/jtagdpi.h b/src/integration/test_suites/libs/jtagdpi/jtagdpi.h new file mode 100644 index 000000000..9ecb2a104 --- /dev/null +++ b/src/integration/test_suites/libs/jtagdpi/jtagdpi.h @@ -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 + +#ifndef OPENTITAN_HW_DV_DPI_JTAGDPI_JTAGDPI_H_ +#define OPENTITAN_HW_DV_DPI_JTAGDPI_JTAGDPI_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct jtagdpi_ctx; + +/** + * Constructor: Create and initialize jtagdpi context object + * + * Call from a initial block. + * + * @param display_name Name of the JTAG interface (for display purposes only) + * @param listen_port Port to listen on + * @return an initialized struct jtagdpi_ctx context object + */ +void *jtagdpi_create(const char *display_name, int listen_port); + +/** + * Destructor: Close all connections and free all resources + * + * Call from a finish block. + * + * @param ctx_void a struct jtagdpi_ctx context object + */ +void jtagdpi_close(void *ctx_void); + +/** + * Drive JTAG signals + * + * Call this function from the simulation at every clock tick to read/write + * from/to the JTAG signals. + * + * @param ctx_void a struct jtagdpi_ctx context object + * @param tck JTAG test clock signal + * @param tms JTAG test mode select signal + * @param tdi JTAG test data input signal + * @param trst_n JTAG test reset signal (active low) + * @param srst_n JTAG system reset signal (active low) + * @param tdo JTAG test data out + */ +void jtagdpi_tick(void *ctx_void, svBit *tck, svBit *tms, svBit *tdi, + svBit *trst_n, svBit *srst_n, const svBit tdo); + +#ifdef __cplusplus +} // extern "C" +#endif +#endif // OPENTITAN_HW_DV_DPI_JTAGDPI_JTAGDPI_H_ diff --git a/src/integration/test_suites/libs/jtagdpi/jtagdpi.sv b/src/integration/test_suites/libs/jtagdpi/jtagdpi.sv new file mode 100644 index 000000000..e69d719b5 --- /dev/null +++ b/src/integration/test_suites/libs/jtagdpi/jtagdpi.sv @@ -0,0 +1,47 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +module jtagdpi #( + parameter string Name = "jtag0", // name of the JTAG interface (display only) + parameter int ListenPort = 44853 // TCP port to listen on +)( + input logic clk_i, + input logic rst_ni, + + output logic jtag_tck, + output logic jtag_tms, + output logic jtag_tdi, + input logic jtag_tdo, + output logic jtag_trst_n, + output logic jtag_srst_n +); + + import "DPI-C" + function chandle jtagdpi_create(input string name, input int listen_port); + + import "DPI-C" + function void jtagdpi_tick(input chandle ctx, output bit tck, output bit tms, + output bit tdi, output bit trst_n, + output bit srst_n, input bit tdo); + + import "DPI-C" + function void jtagdpi_close(input chandle ctx); + + chandle ctx; + + initial begin + ctx = jtagdpi_create(Name, ListenPort); + end + + final begin + jtagdpi_close(ctx); + ctx = null; + end + + always_ff @(posedge clk_i, negedge rst_ni) begin + jtagdpi_tick(ctx, jtag_tck, jtag_tms, jtag_tdi, jtag_trst_n, jtag_srst_n, + jtag_tdo); + end + +endmodule diff --git a/src/integration/test_suites/libs/tcp_server/tcp_server.c b/src/integration/test_suites/libs/tcp_server/tcp_server.c new file mode 100644 index 000000000..c0abb95c0 --- /dev/null +++ b/src/integration/test_suites/libs/tcp_server/tcp_server.c @@ -0,0 +1,439 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include "tcp_server.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * Simple buffer for passing data between TCP sockets and DPI modules + */ +#define BUFSIZE_BYTE 1024 // FIXME: This must be larger than the remote_bitbang + // buffer in OpenOCD. Otherwise deadlock occurs. + +struct tcp_buf { + unsigned int rptr; + unsigned int wptr; + char buf[BUFSIZE_BYTE]; +}; + +/** + * TCP Server thread context structure + */ +struct tcp_server_ctx { + // Writeable by the host thread + char *display_name; + uint16_t listen_port; + volatile bool socket_run; + // Writeable by the server thread + struct tcp_buf *buf_in; + struct tcp_buf *buf_out; + int sfd; // socket fd + int cfd; // client fd + pthread_t sock_thread; +}; + +static bool tcp_buffer_is_full(struct tcp_buf *buf) { + if (buf->wptr >= buf->rptr) { + return (buf->wptr - buf->rptr) == (BUFSIZE_BYTE - 1); + } else { + return (buf->rptr - buf->wptr) == 1; + } +} + +static bool tcp_buffer_is_empty(struct tcp_buf *buf) { + return (buf->wptr == buf->rptr); +} + +static void tcp_buffer_put_byte(struct tcp_buf *buf, char dat) { + bool done = false; + while (!done) { + if (!tcp_buffer_is_full(buf)) { + buf->buf[buf->wptr++] = dat; + buf->wptr %= BUFSIZE_BYTE; + done = true; + } + } +} + +static bool tcp_buffer_get_byte(struct tcp_buf *buf, char *dat) { + if (tcp_buffer_is_empty(buf)) { + return false; + } + *dat = buf->buf[buf->rptr++]; + buf->rptr %= BUFSIZE_BYTE; + return true; +} + +static struct tcp_buf *tcp_buffer_new(void) { + struct tcp_buf *buf_new; + buf_new = (struct tcp_buf *)malloc(sizeof(struct tcp_buf)); + buf_new->rptr = 0; + buf_new->wptr = 0; + return buf_new; +} + +static void tcp_buffer_free(struct tcp_buf **buf) { + free(*buf); + *buf = NULL; +} + +/** + * Start a TCP server + * + * This function creates attempts to create a new TCP socket instance. The + * socket is a non-blocking stream socket, with buffering disabled. + * + * @param ctx context object + * @return 0 on success, -1 in case of an error + */ +static int start(struct tcp_server_ctx *ctx) { + int rv; + + assert(ctx->sfd == 0 && "Server already started."); + + // create socket + int sfd = socket(AF_INET, SOCK_STREAM, 0); + if (sfd == -1) { + fprintf(stderr, "%s: Unable to create socket: %s (%d)\n", ctx->display_name, + strerror(errno), errno); + return -1; + } + + rv = fcntl(sfd, F_SETFL, O_NONBLOCK); + if (rv != 0) { + fprintf(stderr, "%s: Unable to make socket non-blocking: %s (%d)\n", + ctx->display_name, strerror(errno), errno); + return -1; + } + + // reuse existing socket (if existing) + int reuse_socket = 1; + rv = setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse_socket, sizeof(int)); + if (rv != 0) { + fprintf(stderr, "%s: Unable to set socket options: %s (%d)\n", + ctx->display_name, strerror(errno), errno); + return -1; + } + + // stop tcp socket from buffering (buffering prevents timely responses to + // OpenOCD which severly limits debugging performance) + int tcp_nodelay = 1; + rv = setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, &tcp_nodelay, sizeof(int)); + if (rv != 0) { + fprintf(stderr, "%s: Unable to set socket nodelay: %s (%d)\n", + ctx->display_name, strerror(errno), errno); + return -1; + } + + // bind server + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_port = htons(ctx->listen_port); + + rv = bind(sfd, (struct sockaddr *)&addr, sizeof(addr)); + if (rv != 0) { + fprintf(stderr, "%s: Failed to bind socket: %s (%d)\n", ctx->display_name, + strerror(errno), errno); + return -1; + } + + // listen for incoming connections + rv = listen(sfd, 1); + if (rv != 0) { + fprintf(stderr, "%s: Failed to listen on socket: %s (%d)\n", + ctx->display_name, strerror(errno), errno); + return -1; + } + + ctx->sfd = sfd; + assert(ctx->sfd > 0); + + return 0; +} + +/** + * Accept an incoming connection from a client (nonblocking) + * + * The resulting client fd is made non-blocking. + * + * @param ctx context object + * @return 0 on success, any other value indicates an error + */ +static int client_tryaccept(struct tcp_server_ctx *ctx) { + int rv; + + assert(ctx->sfd > 0); + assert(ctx->cfd == 0); + + int cfd = accept(ctx->sfd, NULL, NULL); + + if (cfd == -1 && errno == EAGAIN) { + return -EAGAIN; + } + + if (cfd == -1) { + fprintf(stderr, "%s: Unable to accept incoming connection: %s (%d)\n", + ctx->display_name, strerror(errno), errno); + return -1; + } + + rv = fcntl(cfd, F_SETFL, O_NONBLOCK); + if (rv != 0) { + fprintf(stderr, "%s: Unable to make client socket non-blocking: %s (%d)\n", + ctx->display_name, strerror(errno), errno); + return -1; + } + + ctx->cfd = cfd; + assert(ctx->cfd > 0); + + printf("%s: Accepted client connection\n", ctx->display_name); + + return 0; +} + +/** + * Stop the TCP server + * + * @param ctx context object + */ +static void stop(struct tcp_server_ctx *ctx) { + assert(ctx); + if (!ctx->sfd) { + return; + } + close(ctx->sfd); + ctx->sfd = 0; +} + +/** + * Receive a byte from a connected client + * + * @param ctx context object + * @param cmd byte received + * @return true if a byte was read + */ +static bool get_byte(struct tcp_server_ctx *ctx, char *cmd) { + assert(ctx); + + ssize_t num_read = read(ctx->cfd, cmd, 1); + + if (num_read == 0) { + return false; + } + if (num_read == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + return false; + } else if (errno == EBADF) { + // Possibly client went away? Accept a new connection. + fprintf(stderr, "%s: Client disappeared.\n", ctx->display_name); + tcp_server_client_close(ctx); + return false; + } else { + fprintf(stderr, "%s: Error while reading from client: %s (%d)\n", + ctx->display_name, strerror(errno), errno); + assert(0 && "Error reading from client"); + } + } + assert(num_read == 1); + return true; +} + +/** + * Send a byte to a connected client + * + * @param ctx context object + * @param cmd byte to send + */ +static void put_byte(struct tcp_server_ctx *ctx, char cmd) { + while (1) { + ssize_t num_written = send(ctx->cfd, &cmd, sizeof(cmd), MSG_NOSIGNAL); + if (num_written == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + continue; + } else if (errno == EPIPE) { + printf("%s: Remote disconnected.\n", ctx->display_name); + tcp_server_client_close(ctx); + break; + } else { + fprintf(stderr, "%s: Error while writing to client: %s (%d)\n", + ctx->display_name, strerror(errno), errno); + assert(0 && "Error writing to client."); + } + } + if (num_written >= 1) { + break; + } + } +} + +/** + * Cleanup server context + * + * @param ctx context object + */ +static void ctx_free(struct tcp_server_ctx *ctx) { + // Free the buffers + tcp_buffer_free(&ctx->buf_in); + tcp_buffer_free(&ctx->buf_out); + // Free the display name + free(ctx->display_name); + // Free the ctx + free(ctx); + ctx = NULL; +} + +/** + * Thread function to create a new server instance + * + * @param ctx_void context object + * @return Always returns NULL + */ +static void *server_create(void *ctx_void) { + // Cast to a server struct + struct tcp_server_ctx *ctx = (struct tcp_server_ctx *)ctx_void; + struct timeval timeout; + + // Start the server + int rv = start(ctx); + if (rv != 0) { + fprintf(stderr, "%s: Unable to create TCP server on port %d\n", + ctx->display_name, ctx->listen_port); + goto err_cleanup_return; + } + + // Initialise timeout + timeout.tv_sec = 0; + + // Initialise fd_set + + // Start waiting for connection / data + char xfer_data; + while (ctx->socket_run) { + // Initialise structure of fds + fd_set read_fds; + FD_ZERO(&read_fds); + if (ctx->sfd) { + FD_SET(ctx->sfd, &read_fds); + } + if (ctx->cfd) { + FD_SET(ctx->cfd, &read_fds); + } + // max fd num + int mfd = (ctx->cfd > ctx->sfd) ? ctx->cfd : ctx->sfd; + + // Set timeout - 50us gives good performance + timeout.tv_usec = 50; + + // Wait for socket activity or timeout + rv = select(mfd + 1, &read_fds, NULL, NULL, &timeout); + + if (rv < 0) { + printf("%s: Socket read failed, port: %d\n", ctx->display_name, + ctx->listen_port); + tcp_server_client_close(ctx); + } + + // New connection + if (FD_ISSET(ctx->sfd, &read_fds)) { + client_tryaccept(ctx); + } + + // New client data + if (FD_ISSET(ctx->cfd, &read_fds)) { + while (get_byte(ctx, &xfer_data)) { + tcp_buffer_put_byte(ctx->buf_in, xfer_data); + } + } + + if (ctx->cfd != 0) { + while (tcp_buffer_get_byte(ctx->buf_out, &xfer_data)) { + put_byte(ctx, xfer_data); + } + } + } + +err_cleanup_return: + + // Simulation done - clean up + tcp_server_client_close(ctx); + stop(ctx); + + return NULL; +} + +// Abstract interface functions +struct tcp_server_ctx *tcp_server_create(const char *display_name, + int listen_port) { + struct tcp_server_ctx *ctx = + (struct tcp_server_ctx *)calloc(1, sizeof(struct tcp_server_ctx)); + assert(ctx); + + // Create the buffers + struct tcp_buf *buf_in = tcp_buffer_new(); + struct tcp_buf *buf_out = tcp_buffer_new(); + assert(buf_in); + assert(buf_out); + + // Populate the struct with buffer pointers + ctx->buf_in = buf_in; + ctx->buf_out = buf_out; + + // Set up socket details + ctx->socket_run = true; + ctx->listen_port = listen_port; + ctx->display_name = strdup(display_name); + assert(ctx->display_name); + + if (pthread_create(&ctx->sock_thread, NULL, server_create, (void *)ctx) != + 0) { + fprintf(stderr, "%s: Unable to create TCP socket thread\n", + ctx->display_name); + ctx_free(ctx); + free(ctx); + return NULL; + } + return ctx; +} + +bool tcp_server_read(struct tcp_server_ctx *ctx, char *dat) { + return tcp_buffer_get_byte(ctx->buf_in, dat); +} + +void tcp_server_write(struct tcp_server_ctx *ctx, char dat) { + tcp_buffer_put_byte(ctx->buf_out, dat); +} + +void tcp_server_close(struct tcp_server_ctx *ctx) { + // Shut down the socket thread + ctx->socket_run = false; + pthread_join(ctx->sock_thread, NULL); + ctx_free(ctx); +} + +void tcp_server_client_close(struct tcp_server_ctx *ctx) { + assert(ctx); + + if (!ctx->cfd) { + return; + } + + close(ctx->cfd); + ctx->cfd = 0; +} diff --git a/src/integration/test_suites/libs/tcp_server/tcp_server.h b/src/integration/test_suites/libs/tcp_server/tcp_server.h new file mode 100644 index 000000000..5226322f9 --- /dev/null +++ b/src/integration/test_suites/libs/tcp_server/tcp_server.h @@ -0,0 +1,71 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#ifndef OPENTITAN_HW_DV_DPI_COMMON_TCP_SERVER_TCP_SERVER_H_ +#define OPENTITAN_HW_DV_DPI_COMMON_TCP_SERVER_TCP_SERVER_H_ + +/** + * Functions to create and interact with a threaded TCP server + * + * This is intended to be used by simulation add-on DPI modules to provide + * basic TCP socket communication between a host and simulated peripherals. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +struct tcp_server_ctx; + +/** + * Non-blocking read of a byte from a connected client + * + * @param ctx tcp server context object + * @param dat byte received + * @return true if a byte was read + */ +bool tcp_server_read(struct tcp_server_ctx *ctx, char *dat); + +/** + * Write a byte to a connected client + * + * The write is internally buffered and so does not block if the client is not + * ready to accept data, but does block if the buffer is full. + * + * @param ctx tcp server context object + * @param dat byte to send + */ +void tcp_server_write(struct tcp_server_ctx *ctx, char dat); + +/** + * Create a new TCP server instance + * + * @param display_name C string description of server + * @param listen_port On which port the server should listen + * @return A pointer to the created context struct + */ +struct tcp_server_ctx *tcp_server_create(const char *display_name, + int listen_port); + +/** + * Shut down the server and free all reserved memory + * + * @param ctx tcp server context object + */ +void tcp_server_close(struct tcp_server_ctx *ctx); + +/** + * Instruct the server to disconnect a client + * + * @param ctx tcp server context object + */ +void tcp_server_client_close(struct tcp_server_ctx *ctx); + +#ifdef __cplusplus +} // extern "C" +#endif +#endif // OPENTITAN_HW_DV_DPI_COMMON_TCP_SERVER_TCP_SERVER_H_ diff --git a/tools/scripts/Makefile b/tools/scripts/Makefile index 69e562876..1da0558ef 100755 --- a/tools/scripts/Makefile +++ b/tools/scripts/Makefile @@ -83,12 +83,21 @@ OFILES += printf.o # Always compile the lib files - for every target OFILES += $(foreach comp_lib_name, $(COMP_LIB_NAMES), $(comp_lib_name).o) +# Verilator trace format +VERILATOR_TRACE=vcd + ifdef debug DEBUG_PLUS = +dumpon IRUN_DEBUG = -access +rc IRUN_DEBUG_RUN = -input $(RV_ROOT)/testbench/input.tcl VCS_DEBUG = -debug_access - VERILATOR_DEBUG = --trace --trace-structs + ifeq ($(VERILATOR_TRACE), vcd) + VERILATOR_DEBUG = --trace --trace-structs + else ifeq ($(VERILATOR_TRACE), fst) + VERILATOR_DEBUG = --trace-fst --trace-structs --trace-threads 2 + else + $(error Unsupported Verilator trace format '$(VERILATOR_TRACE)') + endif RIVIERA_DEBUG = +access +r endif @@ -149,13 +158,38 @@ suppress = -Wno-WIDTH -Wno-UNOPTFLAT -Wno-LITENDIAN -Wno-CMPCONST -Wno-MULTIDRIV # CFLAGS for verilator generated Makefiles. Without -std=c++17 it # complains for `auto` variables -CFLAGS += "-std=c++17" +CFLAGS += -std=c++17 # Optimization for better performance; alternative is nothing for # slower runtime (faster compiles) -O2 for faster runtime (slower # compiles), or -O for balance. VERILATOR_MAKE_FLAGS = OPT_FAST="-Os" +# Testbench libs +VERILATOR_TB_LIBS = jtagdpi/jtagdpi.c \ + tcp_server/tcp_server.c + +# Testbench sources +VERILATOR_TB_SRCS = $(TBDIR)/test_caliptra_top_tb.cpp \ + $(addprefix $(CALIPTRA_ROOT)/src/integration/test_suites/libs/,$(VERILATOR_TB_LIBS)) + +# Testbench defs +VERILATOR_TB_DEFS = +define+CALIPTRA_INTERNAL_QSPI+CALIPTRA_INTERNAL_TRNG+CALIPTRA_INTERNAL_UART + +# By default debugging (JTAG) is locked in Caliptra. Add "DEBUG_UNLOCKED=1" to +# enable it. +ifdef DEBUG_UNLOCKED + VERILATOR_TB_DEFS += +define+CALIPTRA_DEBUG_UNLOCKED +endif + +# To enforce holding the RISC-V core in reset add "FORCE_CPU_RESET=1". +ifdef FORCE_CPU_RESET + VERILATOR_TB_DEFS += +define+CALIPTRA_FORCE_CPU_RESET +endif + +# Add testbench lib include paths +CFLAGS += $(addprefix -I$(CALIPTRA_ROOT)/src/integration/test_suites/libs/,$(dir $(VERILATOR_TB_LIBS))) + # Targets all: clean verilator @@ -167,8 +201,8 @@ clean: ############ Model Builds ############################### -verilator-build: $(TBFILES) $(INCLUDES_DIR)/defines.h test_caliptra_top_tb.cpp - $(VERILATOR) --cc -CFLAGS $(CFLAGS) \ +verilator-build: $(TBFILES) $(INCLUDES_DIR)/defines.h $(VERILATOR_TB_SRCS) + $(VERILATOR) $(VERILATOR_TB_SRCS) --cc -CFLAGS "$(CFLAGS)" \ +libext+.v+.sv +define+RV_OPENSOURCE \ --timescale 1ns/1ps \ --timing \ @@ -177,9 +211,8 @@ verilator-build: $(TBFILES) $(INCLUDES_DIR)/defines.h test_caliptra_top_tb.cpp -f $(TBDIR)/../config/caliptra_top_tb.vf --top-module caliptra_top_tb \ -f $(TBDIR)/../config/caliptra_top_tb.vlt \ -exe test_caliptra_top_tb.cpp --autoflush $(VERILATOR_DEBUG) \ - +define+CALIPTRA_INTERNAL_QSPI+CALIPTRA_INTERNAL_TRNG+CALIPTRA_INTERNAL_UART - cp $(TBDIR)/test_caliptra_top_tb.cpp obj_dir/ - $(MAKE) -j -e -C obj_dir/ -f Vcaliptra_top_tb.mk $(VERILATOR_MAKE_FLAGS) VM_PARALLEL_BUILDS=1 + $(VERILATOR_TB_DEFS) + $(MAKE) -j`nproc` -e -C obj_dir/ -f Vcaliptra_top_tb.mk $(VERILATOR_MAKE_FLAGS) VM_PARALLEL_BUILDS=1 touch verilator-build vcs-build: $(TBFILES) $(INCLUDES_DIR)/defines.h diff --git a/tools/scripts/openocd/board/caliptra-verilator-rst.cfg b/tools/scripts/openocd/board/caliptra-verilator-rst.cfg new file mode 100644 index 000000000..630df5369 --- /dev/null +++ b/tools/scripts/openocd/board/caliptra-verilator-rst.cfg @@ -0,0 +1,5 @@ +source [find interface/sim-jtagdpi.cfg] +source [find target/veer-el2-rst.cfg] + +# Increase timeouts in simulation +riscv set_command_timeout_sec 300 diff --git a/tools/scripts/openocd/board/caliptra-verilator.cfg b/tools/scripts/openocd/board/caliptra-verilator.cfg new file mode 100644 index 000000000..c91718677 --- /dev/null +++ b/tools/scripts/openocd/board/caliptra-verilator.cfg @@ -0,0 +1,5 @@ +source [find interface/sim-jtagdpi.cfg] +source [find target/veer-el2.cfg] + +# Increase timeouts in simulation +riscv set_command_timeout_sec 300 diff --git a/tools/scripts/openocd/interface/sim-jtagdpi.cfg b/tools/scripts/openocd/interface/sim-jtagdpi.cfg new file mode 100644 index 000000000..7d43a4ad5 --- /dev/null +++ b/tools/scripts/openocd/interface/sim-jtagdpi.cfg @@ -0,0 +1,11 @@ +# Copyright lowRISC contributors. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +# "JTAG adapter" for simulation, exposed to OpenOCD through a TCP socket +# speaking the remote_bitbang protocol. The adapter is implemented as +# SystemVerilog DPI module. + +adapter driver remote_bitbang +remote_bitbang port 5000 +remote_bitbang host localhost diff --git a/tools/scripts/openocd/target/veer-el2-rst.cfg b/tools/scripts/openocd/target/veer-el2-rst.cfg new file mode 100644 index 000000000..0f4c86fde --- /dev/null +++ b/tools/scripts/openocd/target/veer-el2-rst.cfg @@ -0,0 +1,35 @@ +if { [info exists CHIPNAME] } { + set _CHIPNAME $CHIPNAME +} else { + set _CHIPNAME riscv +} + +if { [info exists CPUTAPID ] } { + set _CPUTAPID $CPUTAPID +} else { + set _CPUTAPID 0x1000008b +} + +jtag newtap $_CHIPNAME tap -irlen 5 -expected-id $_CPUTAPID +set _TARGETNAME $_CHIPNAME.tap +target create $_TARGETNAME.0 riscv -chain-position $_TARGETNAME -rtos hwthread + +# Configure work area in on-chip SRAM +$_TARGETNAME.0 configure -work-area-phys 0x50001000 -work-area-size 0x1000 -work-area-backup 0 + +# Mem access mode +riscv set_mem_access sysbus + +# The following commands disable target examination and set explicitly the +# core parameters read from CSRs. These required a modified version of +# OpenOCD from https://github.com/antmicro/openocd/tree/riscv-nohalt +riscv set_nohalt on +riscv set_xlen 32 +riscv set_misa 0x40001104 + +# Be verbose about GDB errors +gdb_report_data_abort enable +gdb_report_register_access_error enable + +# Always use hardware breakpoints. +gdb_breakpoint_override hard diff --git a/tools/scripts/openocd/target/veer-el2.cfg b/tools/scripts/openocd/target/veer-el2.cfg new file mode 100644 index 000000000..45374886d --- /dev/null +++ b/tools/scripts/openocd/target/veer-el2.cfg @@ -0,0 +1,28 @@ +if { [info exists CHIPNAME] } { + set _CHIPNAME $CHIPNAME +} else { + set _CHIPNAME riscv +} + +if { [info exists CPUTAPID ] } { + set _CPUTAPID $CPUTAPID +} else { + set _CPUTAPID 0x1000008b +} + +jtag newtap $_CHIPNAME tap -irlen 5 -expected-id $_CPUTAPID +set _TARGETNAME $_CHIPNAME.tap +target create $_TARGETNAME.0 riscv -chain-position $_TARGETNAME -rtos hwthread + +# Configure work area in on-chip SRAM +$_TARGETNAME.0 configure -work-area-phys 0x50001000 -work-area-size 0x1000 -work-area-backup 0 + +# Mem access mode +riscv set_mem_access abstract + +# Be verbose about GDB errors +gdb_report_data_abort enable +gdb_report_register_access_error enable + +# Always use hardware breakpoints. +gdb_breakpoint_override hard