Directed test flow

* First implementation of a directed_test framework, which aims to re-use as much
as possible from the existing riscvdv generation
* Fixed directed test flow to cleanly end the test
* clang-format is off for assembly header files
This commit is contained in:
Saad Khalid 2022-12-13 10:56:53 +00:00 committed by Greg Chadwick
parent 911a6735b9
commit 552836cf4a
20 changed files with 4704 additions and 531 deletions

View file

@ -1,12 +1,15 @@
# 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
@ -29,7 +32,8 @@ SIMULATOR := xlm
ISS := spike
# Test name (default: full regression)
TEST := all
TESTLIST := riscv_dv_extension/testlist.yaml
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
@ -46,60 +50,24 @@ DUT_COV_RTL_PATH := "ibex_top"
###############################################################################
all: collect_results $(if $(filter 1,$(COV)),merge_cov,)
# Build Stages
.PHONY: core_config
.PHONY: instr_gen_build
.PHONY: instr_gen_run
.PHONY: instr_gen_compile
.PHONY: rtl_tb_compile
.PHONY: rtl_sim_run
.PHONY: check_logs
.PHONY: riscv_dv_fcov
.PHONY: merge_cov
.PHONY: collect_results
###############################################################################
# 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 /.
OUT-DIR := $(dir $(OUT)/)
BUILD-DIR := $(OUT-DIR)build
RUN-DIR := $(OUT-DIR)run
TESTS-DIR := $(RUN-DIR)/tests
METADATA-DIR = $(OUT-DIR)metadata
# This is a list of directories that are automatically generated by some
# targets. To ensure the directory has been built, add an order-only dependency
# (with the pipe symbol before it) on the directory name and add the directory
# to this list.
gen-dirs := $(BUILD-DIR)
$(gen-dirs): %:
mkdir -p $@
###############################################################################
# Environment variables
GEN_DIR := $(realpath ../../../vendor/google_riscv-dv)
TOOLCHAIN := ${RISCV_TOOLCHAIN}
EXT_DIR := riscv_dv_extension
export IBEX_ROOT := $(realpath ../../../)
export PRJ_DIR := $(realpath ../../..)
export LOWRISC_IP_DIR := $(realpath ${PRJ_DIR}/vendor/lowrisc_ip)
# Needed for tcl files that are used with Cadence tools.
export dv_root := $(realpath ../../../vendor/lowrisc_ip/dv)
export DUT_TOP := ibex_top
# Setup the necessary paths for all python scripts to find all other relevant modules.
PYTHONPATH := $(shell python3 -c 'from scripts.setup_imports import get_pythonpath; get_pythonpath()')
# export PYTHONPATH := $(PYTHONPATH) ## Why doesn't this work?
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)
###############################################################################
@ -109,299 +77,15 @@ clean:
rm -rf $(OUT-DIR)
###############################################################################
# Setup the metadata for the regression, which can then be accessed by
# all python scripts and testcases
# It needs to run before anything else.
new-metadata-file := $(shell 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)")
### TODO ##
# Evaluate input variables to more-cleverly schedule partial-rebuilds
# This allows us to use Make to handle build scheduling and to calculate rebuilds,
# while keeping all the structured-data in the land of Python.
define get-metadata-variable
env PYTHONPATH=$(PYTHONPATH) python3 ./scripts/metadata.py \
--op "print_field" \
--dir-metadata $(METADATA-DIR) \
--field $(1)
endef
define get-meta
$(shell $(call get-metadata-variable, $(1)))
endef
# This is how you can get variables from the python metadata easily...
testvar := $(call get-meta,"ibex_root")
### TODO ###
# 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
###############################################################################
# Here we express the different build artifacts that the Makefile uses to
# establish the dependency tree, as well as which jobs depend on which
# top-level configuration knobs when deciding what to rebuild.
# Use build artifacts as targets where appropriate, otherwise use stamp-files.
tests-and-seeds := $(shell env PYTHONPATH=$(PYTHONPATH) python3 ./scripts/metadata.py \
--op "tests_and_seeds" \
--dir-metadata $(METADATA-DIR) $(new-metadata-file))
ts-dirs = $(foreach ts,$(tests-and-seeds),$(TESTS-DIR)/$(ts)/)
test-asms = $(addsuffix test.S,$(ts-dirs))
test-bins = $(addsuffix test.bin,$(ts-dirs))
rtl-sim-logs = $(addsuffix $(rtl-sim-logfile),$(ts-dirs))
comp-results = $(addsuffix trr.yaml,$(ts-dirs))
rtl-sim-logfile := rtl_sim.log
###
CORE-CONFIG-STAMP = $(METADATA-DIR)/core.config.stamp
core_config: $(CORE-CONFIG-STAMP)
core-config-var-deps := IBEX_CONFIG
INSTR-GEN-BUILD-STAMP = $(METADATA-DIR)/instr.gen.build.stamp
instr_gen_build: $(METADATA-DIR)/instr.gen.build.stamp
instr-gen-build-var-deps := SIMULATOR SIGNATURE_ADDR # Rebuild if these change
instr_gen_run: $(test-asms)
instr_gen_compile: $(test-bins)
TB-COMPILE-STAMP = $(METADATA-DIR)/tb.compile.stamp
rtl_tb_compile: $(METADATA-DIR)/tb.compile.stamp
rtl-tb-compile-var-deps := SIMULATOR COV WAVES # Rebuild if these change
rtl_sim_run: $(rtl-sim-logs)
check_logs: $(comp-results)
FCOV-STAMP = $(METADATA-DIR)/fcov.stamp
riscv_dv_fcov: $(METADATA-DIR)/fcov.stamp
MERGE-COV-STAMP = $(METADATA-DIR)/merge.cov.stamp
merge_cov: $(METADATA-DIR)/merge.cov.stamp
REGR-LOG-STAMP = $(METADATA-DIR)/regr.log.stamp
collect_results: $(METADATA-DIR)/regr.log.stamp
###############################################################################
# Other groups of files we may depend on are...
riscv-dv-files := $(shell find $(GEN_DIR) -type f)
# A variable containing a file list for the riscv-dv vendored-in module.
# Depending on these files gives a safe over-approximation that will ensure we
# rebuild things if that module changes.
all-verilog = \
$(shell find ../../../rtl -name '*.v' -o -name '*.sv' -o -name '*.svh') \
$(shell find ../.. -name '*.v' -o -name '*.sv' -o -name '*.svh')
all-cpp = \
$(shell find ../.. -name '*.cc' -o -name '*.h')
# The compiled ibex testbench (obviously!) also depends on the design and the
# DV code. The clever way of doing this would be to look at a dependency
# listing generated by the simulator as a side-effect of doing the compile (a
# bit like using the -M flags with a C compiler). Unfortunately, that doesn't
# look like it's particularly easy, so we'll just depend on every .v, .sv or
# .svh file in the dv or rtl directories. Note that this variable is set with
# '=', rather than ':='. This means that we don't bother running the find
# commands unless we need the compiled testbench.
###############################################################################
###############################################################################
# Build the Random Instruction Generator
#
###############################################################################
include util.mk # VARIABLE DUMPING UTILS
###############################################################################
######## EXAMPLE OF VARIABLE DUMPING ############
# This target depends on the vendored in code in $(GEN_DIR). It also depends on the
# values of the following Makefile variables (we want to regenerate things if,
# for example, the simulator changes).
# To achieve this variable tracking, we dump each of the variables to a Makefile
# fragment and try to load it up the next time around. This done with the
# utility function "dump-vars" at the end of the recipe.
#
# To create the dependency, we must do the following two things before each
# target:
#
# First, load up the saved variable values from the last time around. If this
# fails, it's no problem: we'll assume that the previous run either doesn't
# exist or something went wrong.
ig-build-vars-path := $(BUILD-DIR)/.instr_gen.vars.mk
-include $(ig-build-vars-path)
# Next, compare the current variables to those we just loaded. This uses the
# utility function "vars-prereq". It creates a variable which evaluates to the
# (phony) FORCE if the two sets of variables do not match.
#
# Note that we define it with '=', not ':=', so we don't evaluate if we're not
# trying to run the instr_gen_build target.
instr-gen-build-vars-prereq = \
$(call vars-prereq, \
gen, \
building instruction generator, \
$(instr-gen-build-var-deps))
# Finally, $(instr-gen-build-vars-prereq) becomes a dependency of our target.
################## END EXAMPLE ###################
core-config-vars-path := $(BUILD-DIR)/.cc.vars.mk
-include $(core-config-vars-path)
core-config-var-prereq = $(call vars-prereq,gen,Generate core configuration file,$(core-config-var-deps))
$(CORE-CONFIG-STAMP): \
$(core-config-var-prereq) ./riscv_dv_extension/riscv_core_setting.tpl.sv \
scripts/render_config_template.py \
| $(BUILD-DIR)
@echo Generating core configuration file
$(verb)env PYTHONPATH=$(PYTHONPATH) \
scripts/render_config_template.py \
--dir-metadata $(METADATA-DIR) \
$(EXT_DIR)/riscv_core_setting.tpl.sv > $(EXT_DIR)/riscv_core_setting.sv
$(call dump-vars,$(core-config-vars-path),gen,$(core-config-var-deps))
@touch $@
$(METADATA-DIR)/instr.gen.build.stamp: \
$(instr-gen-build-vars-prereq) $(riscv-dv-files) $(CORE-CONFIG-STAMP) \
scripts/build_instr_gen.py \
| $(BUILD-DIR)
@echo Building randomized test generator
$(verb)env PYTHONPATH=$(PYTHONPATH) \
scripts/build_instr_gen.py \
--dir-metadata $(METADATA-DIR)
$(call dump-vars,$(ig-build-vars-path),gen,$(instr-gen-build-var-deps))
@touch $@
###############################################################################
# Run the random instruction generator
#
# Make use of static-pattern rules
# https://www.gnu.org/software/make/manual/html_node/Static-Usage.html#Static-Usage
#
# targets …: target-pattern: prereq-patterns …
# recipe
# …
$(test-asms): \
$(TESTS-DIR)/%/test.S: \
$(INSTR-GEN-BUILD-STAMP) $(TESTLIST) scripts/run_instr_gen.py
@echo Running randomized test generator to create assembly file $@
$(verb)env PYTHONPATH=$(PYTHONPATH) \
scripts/run_instr_gen.py \
--dir-metadata $(METADATA-DIR) \
--test-dot-seed $*
###############################################################################
# Compile the generated assembly programs
#
# We don't explicitly track dependencies on the RISCV toolchain, so this
# doesn't depend on anything more than the instr_gen stage did.
#
# Note that the compilation step generates a .o file and then uses
# objcopy to create a .bin. The ISS run uses the .o and the RTL run
# uses the .bin. In the Makefile, we just track the .bin to represent
# both.
$(test-bins): \
$(TESTS-DIR)/%/test.bin: $(TESTS-DIR)/%/test.S \
scripts/compile_generated_test.py
@echo Compiling generated test assembly to create binary at $@
$(verb)env PYTHONPATH=$(PYTHONPATH) \
scripts/compile_generated_test.py \
--dir-metadata $(METADATA-DIR) \
--test-dot-seed $*
###############################################################################
# Compile ibex core TB
#
# Note that this doesn't depend on the seed: the DUT doesn't depend on which
# test we're running!
tb-compile-vars-path := $(BUILD-DIR)/.tb.vars.mk
-include $(tb-compile-vars-path)
tb-compile-vars-prereq = $(call vars-prereq,comp,compiling TB,$(rtl-tb-compile-var-deps))
$(METADATA-DIR)/tb.compile.stamp: \
$(tb-compile-vars-prereq) $(all-verilog) $(all-cpp) $(risc-dv-files) \
scripts/compile_tb.py yaml/rtl_simulation.yaml \
| $(BUILD-DIR)
@echo Building RTL testbench
$(verb)env PYTHONPATH=$(PYTHONPATH) \
scripts/compile_tb.py \
--dir-metadata $(METADATA-DIR)
$(call dump-vars,$(tb-compile-vars-path),comp,$(rtl-tb-compile-var-deps))
@touch $@
###############################################################################
# Run ibex RTL simulation with randomly-generated program and uvm stimulus
$(rtl-sim-logs): \
$(TESTS-DIR)/%/$(rtl-sim-logfile): \
$(TB-COMPILE-STAMP) $(TESTS-DIR)/%/test.bin scripts/run_rtl.py
@echo Running RTL simulation at $(@D)
$(verb)env PYTHONPATH=$(PYTHONPATH) \
scripts/run_rtl.py \
--dir-metadata $(METADATA-DIR) \
--test-dot-seed $*
###############################################################################
# Gather RTL sim results, and parse logs for errors
$(comp-results): \
$(TESTS-DIR)/%/trr.yaml: \
$(TESTS-DIR)/%/$(rtl-sim-logfile) scripts/check_logs.py
@echo Collecting simulation results and checking logs of testcase at $@
$(verb)env PYTHONPATH=$(PYTHONPATH) \
scripts/check_logs.py \
--dir-metadata $(METADATA-DIR) \
--test-dot-seed $*
###############################################################################
# Generate RISCV-DV functional coverage
# TODO(udi) - add B extension
$(METADATA-DIR)/fcov.stamp: $(comp-results) \
scripts/get_fcov.py
@echo Generating RISCV_DV functional coverage
$(verb)env PYTHONPATH=$(PYTHONPATH) \
scripts/get_fcov.py \
--dir-metadata $(METADATA-DIR)
@touch $@
###############################################################################
# Merge all output coverage directories
# Any coverage databases generated from the riscv_dv_fcov target will be merged
# as well.
$(METADATA-DIR)/merge.cov.stamp: $(FCOV-STAMP) \
scripts/merge_cov.py
@echo Merging all recorded coverage data into a single report
$(verb)env PYTHONPATH=$(PYTHONPATH) \
scripts/merge_cov.py \
--dir-metadata $(METADATA-DIR)
@touch $@
###############################################################################
# Generate the summarized regression log
$(METADATA-DIR)/regr.log.stamp: scripts/collect_results.py $(comp-results)
@echo Collecting up results of tests into report regr.log
$(verb)env PYTHONPATH=$(PYTHONPATH) \
./scripts/collect_results.py \
--dir-metadata $(METADATA-DIR)
@touch $@
###############################################################################
# Extras (for convenience)
.PHONY: prettify
prettify:
@./scripts/prettify.sh
.PHONY: dump
dump:
@./scripts/objdump.sh

View file

@ -0,0 +1,32 @@
#include "riscv_test.h"
#include "test_macros.h"
RVTEST_RV64M
RVTEST_CODE_BEGIN
# setting the PMP region
la t0, pmp_region_start
srli t1, t0, PMP_SHIFT
csrw pmpaddr0, t1
la t1, pmp_region_end
srli t1, t1, PMP_SHIFT
csrw pmpaddr1, t1
li t1, (PMP_L | PMP_TOR | PMP_R | PMP_W | PMP_X) << 8
csrw pmpcfg0, t1
# access across the boundary between PMP and non-PMP
lw t1, -2(t0)
TEST_PASSFAIL
RVTEST_CODE_END
.data
RVTEST_DATA_BEGIN
.balign 0x1000
pmp_region_start: .zero 0x1000
pmp_region_end:
TEST_DATA
RVTEST_DATA_END

View file

@ -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
##########################################################
# This file largely copies the formatting of the testlist.yaml
# used by riscv-dv, but only specifies directed tests.
##########################################################
- test: empty
desc: >
Empty directed test
iterations: 1
test_srcs: empty/empty.S
ld_script: "link.ld"
includes: "."
gcc_opts: "-static -mcmodel=medany -fvisibility=hidden -nostdlib -nostartfiles"
rtl_test: core_ibex_base_test
rtl_params:
PMPEnable: 1
timeout_s: 300

View file

@ -0,0 +1,22 @@
#include "riscv_test.h"
#include "test_macros.h"
RVTEST_RV64M
RVTEST_CODE_BEGIN
j pass
RVTEST_CODE_END
pass:
RVTEST_PASS
fail:
RVTEST_FAIL
.data
RVTEST_DATA_BEGIN
TEST_DATA
RVTEST_DATA_END

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,17 @@
OUTPUT_ARCH( "riscv" )
ENTRY(_start)
SECTIONS
{
. = 0x80000000;
.text.init : { *(.text.init) }
. = ALIGN(0x1000);
.tohost : { *(.tohost) }
. = ALIGN(0x1000);
.text : { *(.text) }
. = ALIGN(0x1000);
.data : { *(.data) }
.bss : { *(.bss) }
_end = .;
}

View file

@ -0,0 +1,274 @@
// See LICENSE for license details.
// clang-format off
#ifndef _ENV_PHYSICAL_SINGLE_CORE_H
#define _ENV_PHYSICAL_SINGLE_CORE_H
#include "encoding.h"
//-----------------------------------------------------------------------
// Begin Macro
//-----------------------------------------------------------------------
#define RVTEST_RV64U \
.macro init; \
.endm
#define RVTEST_RV64UF \
.macro init; \
RVTEST_FP_ENABLE; \
.endm
#define RVTEST_RV64UV \
.macro init; \
RVTEST_VECTOR_ENABLE; \
.endm
#define RVTEST_RV32U \
.macro init; \
.endm
#define RVTEST_RV32UF \
.macro init; \
RVTEST_FP_ENABLE; \
.endm
#define RVTEST_RV32UV \
.macro init; \
RVTEST_VECTOR_ENABLE; \
.endm
#define RVTEST_RV64M \
.macro init; \
RVTEST_ENABLE_MACHINE; \
.endm
#define RVTEST_RV64S \
.macro init; \
RVTEST_ENABLE_SUPERVISOR; \
.endm
#define RVTEST_RV32M \
.macro init; \
RVTEST_ENABLE_MACHINE; \
.endm
#define RVTEST_RV32S \
.macro init; \
RVTEST_ENABLE_SUPERVISOR; \
.endm
#if __riscv_xlen == 64
# define CHECK_XLEN li a0, 1; slli a0, a0, 31; bgez a0, 1f; RVTEST_PASS; 1:
#else
# define CHECK_XLEN li a0, 1; slli a0, a0, 31; bltz a0, 1f; RVTEST_PASS; 1:
#endif
#define INIT_XREG \
li x1, 0; \
li x2, 0; \
li x3, 0; \
li x4, 0; \
li x5, 0; \
li x6, 0; \
li x7, 0; \
li x8, 0; \
li x9, 0; \
li x10, 0; \
li x11, 0; \
li x12, 0; \
li x13, 0; \
li x14, 0; \
li x15, 0; \
li x16, 0; \
li x17, 0; \
li x18, 0; \
li x19, 0; \
li x20, 0; \
li x21, 0; \
li x22, 0; \
li x23, 0; \
li x24, 0; \
li x25, 0; \
li x26, 0; \
li x27, 0; \
li x28, 0; \
li x29, 0; \
li x30, 0; \
li x31, 0;
#define INIT_PMP \
la t0, 1f; \
csrw mtvec, t0; \
/* Set up a PMP to permit all accesses */ \
li t0, (1 << (31 + (__riscv_xlen / 64) * (53 - 31))) - 1; \
csrw pmpaddr0, t0; \
li t0, PMP_NAPOT | PMP_R | PMP_W | PMP_X; \
csrw pmpcfg0, t0; \
.align 2; \
1:
#define INIT_SATP \
la t0, 1f; \
csrw mtvec, t0; \
csrwi satp, 0; \
.align 2; \
1:
#define DELEGATE_NO_TRAPS \
csrwi mie, 0; \
la t0, 1f; \
csrw mtvec, t0; \
csrwi medeleg, 0; \
csrwi mideleg, 0; \
.align 2; \
1:
#define RVTEST_ENABLE_SUPERVISOR \
li a0, MSTATUS_MPP & (MSTATUS_MPP >> 1); \
csrs mstatus, a0; \
li a0, SIP_SSIP | SIP_STIP; \
csrs mideleg, a0; \
#define RVTEST_ENABLE_MACHINE \
li a0, MSTATUS_MPP; \
csrs mstatus, a0; \
#define RVTEST_FP_ENABLE \
li a0, MSTATUS_FS & (MSTATUS_FS >> 1); \
csrs mstatus, a0; \
csrwi fcsr, 0
#define RVTEST_VECTOR_ENABLE \
li a0, (MSTATUS_VS & (MSTATUS_VS >> 1)) | \
(MSTATUS_FS & (MSTATUS_FS >> 1)); \
csrs mstatus, a0; \
csrwi fcsr, 0; \
csrwi vcsr, 0;
#define RISCV_MULTICORE_DISABLE \
csrr a0, mhartid; \
1: bnez a0, 1b
#define EXTRA_TVEC_USER
#define EXTRA_TVEC_MACHINE
#define EXTRA_INIT
#define EXTRA_INIT_TIMER
#define INTERRUPT_HANDLER j other_exception /* No interrupts should occur */
#define RVTEST_CODE_BEGIN \
.section .text.init; \
.align 6; \
.weak stvec_handler; \
.weak mtvec_handler; \
.globl _start; \
_start: \
/* reset vector */ \
j reset_vector; \
.align 2; \
trap_vector: \
/* test whether the test came from pass/fail */ \
csrr t5, mcause; \
li t6, CAUSE_USER_ECALL; \
beq t5, t6, write_tohost; \
li t6, CAUSE_SUPERVISOR_ECALL; \
beq t5, t6, write_tohost; \
li t6, CAUSE_MACHINE_ECALL; \
beq t5, t6, write_tohost; \
/* if an mtvec_handler is defined, jump to it */ \
la t5, mtvec_handler; \
beqz t5, 1f; \
jr t5; \
/* was it an interrupt or an exception? */ \
1: csrr t5, mcause; \
bgez t5, handle_exception; \
INTERRUPT_HANDLER; \
handle_exception: \
/* we don't know how to handle whatever the exception was */ \
other_exception: \
/* some unhandlable exception occurred */ \
1: ori TESTNUM, TESTNUM, 1337; \
write_tohost: \
sw TESTNUM, tohost, t5; \
j write_tohost; \
reset_vector: \
INIT_XREG; \
RISCV_MULTICORE_DISABLE; \
/*INIT_SATP; Ibex doesn't support supervisor mode */ \
INIT_PMP; \
/*DELEGATE_NO_TRAPS; Ibex doesn't support supervisor mode */ \
li TESTNUM, 0; \
la t0, trap_vector; \
csrw mtvec, t0; \
CHECK_XLEN; \
/* if an stvec_handler is defined, delegate exceptions to it */ \
la t0, stvec_handler; \
beqz t0, 1f; \
csrw stvec, t0; \
li t0, (1 << CAUSE_LOAD_PAGE_FAULT) | \
(1 << CAUSE_STORE_PAGE_FAULT) | \
(1 << CAUSE_FETCH_PAGE_FAULT) | \
(1 << CAUSE_MISALIGNED_FETCH) | \
(1 << CAUSE_USER_ECALL) | \
(1 << CAUSE_BREAKPOINT); \
/*csrw medeleg, t0; Ibex doesn't support supervisor mode */ \
1: csrwi mstatus, 0; \
init; \
EXTRA_INIT; \
EXTRA_INIT_TIMER; \
la t0, 1f; \
csrw mepc, t0; \
csrr a0, mhartid; \
mret; \
1:
//-----------------------------------------------------------------------
// End Macro
//-----------------------------------------------------------------------
#define RVTEST_CODE_END \
unimp
//-----------------------------------------------------------------------
// Pass/Fail Macro
//-----------------------------------------------------------------------
#define RVTEST_PASS \
fence; \
li x2, SIGNATURE_ADDR; \
li x1, (FINISHED_IRQ << 8) | CORE_STATUS; \
sw x1, 0(x2); \
li x1, (TEST_PASS << 8) | TEST_RESULT; \
sw x1, 0(x2); \
2:; \
j 2b;
#define TESTNUM gp
#define RVTEST_FAIL \
fence; \
li x2, SIGNATURE_ADDR; \
li x1, (FINISHED_IRQ << 8) | CORE_STATUS; \
sw x1, 0(x2); \
li x1, (TEST_FAIL << 8) | TEST_RESULT; \
sw x1, 0(x2); \
2:; \
j 2b;
//-----------------------------------------------------------------------
// Data Section Macro
//-----------------------------------------------------------------------
#define EXTRA_DATA
#define RVTEST_DATA_BEGIN \
EXTRA_DATA \
.pushsection .tohost,"aw",@progbits; \
.align 6; .global tohost; tohost: .dword 0; \
.align 6; .global fromhost; fromhost: .dword 0; \
.popsection; \
.align 4; .global begin_signature; begin_signature:
#define RVTEST_DATA_END .align 4; .global end_signature; end_signature:
#endif

View file

@ -0,0 +1,754 @@
// See LICENSE for license details.
// clang-format off
#ifndef __TEST_MACROS_SCALAR_H
#define __TEST_MACROS_SCALAR_H
#-----------------------------------------------------------------------
# Helper macros
#-----------------------------------------------------------------------
#define MASK_XLEN(x) ((x) & ((1 << (__riscv_xlen - 1) << 1) - 1))
#define TEST_CASE( testnum, testreg, correctval, code... ) \
test_ ## testnum: \
li TESTNUM, testnum; \
code; \
li x7, MASK_XLEN(correctval); \
bne testreg, x7, fail;
# We use a macro hack to simpify code generation for various numbers
# of bubble cycles.
#define TEST_INSERT_NOPS_0
#define TEST_INSERT_NOPS_1 nop; TEST_INSERT_NOPS_0
#define TEST_INSERT_NOPS_2 nop; TEST_INSERT_NOPS_1
#define TEST_INSERT_NOPS_3 nop; TEST_INSERT_NOPS_2
#define TEST_INSERT_NOPS_4 nop; TEST_INSERT_NOPS_3
#define TEST_INSERT_NOPS_5 nop; TEST_INSERT_NOPS_4
#define TEST_INSERT_NOPS_6 nop; TEST_INSERT_NOPS_5
#define TEST_INSERT_NOPS_7 nop; TEST_INSERT_NOPS_6
#define TEST_INSERT_NOPS_8 nop; TEST_INSERT_NOPS_7
#define TEST_INSERT_NOPS_9 nop; TEST_INSERT_NOPS_8
#define TEST_INSERT_NOPS_10 nop; TEST_INSERT_NOPS_9
#-----------------------------------------------------------------------
# RV64UI MACROS
#-----------------------------------------------------------------------
#-----------------------------------------------------------------------
# Tests for instructions with immediate operand
#-----------------------------------------------------------------------
#define SEXT_IMM(x) ((x) | (-(((x) >> 11) & 1) << 11))
#define TEST_IMM_OP( testnum, inst, result, val1, imm ) \
TEST_CASE( testnum, x14, result, \
li x1, MASK_XLEN(val1); \
inst x14, x1, SEXT_IMM(imm); \
)
#define TEST_IMM_SRC1_EQ_DEST( testnum, inst, result, val1, imm ) \
TEST_CASE( testnum, x1, result, \
li x1, MASK_XLEN(val1); \
inst x1, x1, SEXT_IMM(imm); \
)
#define TEST_IMM_DEST_BYPASS( testnum, nop_cycles, inst, result, val1, imm ) \
TEST_CASE( testnum, x6, result, \
li x4, 0; \
1: li x1, MASK_XLEN(val1); \
inst x14, x1, SEXT_IMM(imm); \
TEST_INSERT_NOPS_ ## nop_cycles \
addi x6, x14, 0; \
addi x4, x4, 1; \
li x5, 2; \
bne x4, x5, 1b \
)
#define TEST_IMM_SRC1_BYPASS( testnum, nop_cycles, inst, result, val1, imm ) \
TEST_CASE( testnum, x14, result, \
li x4, 0; \
1: li x1, MASK_XLEN(val1); \
TEST_INSERT_NOPS_ ## nop_cycles \
inst x14, x1, SEXT_IMM(imm); \
addi x4, x4, 1; \
li x5, 2; \
bne x4, x5, 1b \
)
#define TEST_IMM_ZEROSRC1( testnum, inst, result, imm ) \
TEST_CASE( testnum, x1, result, \
inst x1, x0, SEXT_IMM(imm); \
)
#define TEST_IMM_ZERODEST( testnum, inst, val1, imm ) \
TEST_CASE( testnum, x0, 0, \
li x1, MASK_XLEN(val1); \
inst x0, x1, SEXT_IMM(imm); \
)
#-----------------------------------------------------------------------
# Tests for an instruction with register operands
#-----------------------------------------------------------------------
#define TEST_R_OP( testnum, inst, result, val1 ) \
TEST_CASE( testnum, x14, result, \
li x1, val1; \
inst x14, x1; \
)
#define TEST_R_SRC1_EQ_DEST( testnum, inst, result, val1 ) \
TEST_CASE( testnum, x1, result, \
li x1, val1; \
inst x1, x1; \
)
#define TEST_R_DEST_BYPASS( testnum, nop_cycles, inst, result, val1 ) \
TEST_CASE( testnum, x6, result, \
li x4, 0; \
1: li x1, val1; \
inst x14, x1; \
TEST_INSERT_NOPS_ ## nop_cycles \
addi x6, x14, 0; \
addi x4, x4, 1; \
li x5, 2; \
bne x4, x5, 1b \
)
#-----------------------------------------------------------------------
# Tests for an instruction with register-register operands
#-----------------------------------------------------------------------
#define TEST_RR_OP( testnum, inst, result, val1, val2 ) \
TEST_CASE( testnum, x14, result, \
li x1, MASK_XLEN(val1); \
li x2, MASK_XLEN(val2); \
inst x14, x1, x2; \
)
#define TEST_RR_SRC1_EQ_DEST( testnum, inst, result, val1, val2 ) \
TEST_CASE( testnum, x1, result, \
li x1, MASK_XLEN(val1); \
li x2, MASK_XLEN(val2); \
inst x1, x1, x2; \
)
#define TEST_RR_SRC2_EQ_DEST( testnum, inst, result, val1, val2 ) \
TEST_CASE( testnum, x2, result, \
li x1, MASK_XLEN(val1); \
li x2, MASK_XLEN(val2); \
inst x2, x1, x2; \
)
#define TEST_RR_SRC12_EQ_DEST( testnum, inst, result, val1 ) \
TEST_CASE( testnum, x1, result, \
li x1, MASK_XLEN(val1); \
inst x1, x1, x1; \
)
#define TEST_RR_DEST_BYPASS( testnum, nop_cycles, inst, result, val1, val2 ) \
TEST_CASE( testnum, x6, result, \
li x4, 0; \
1: li x1, MASK_XLEN(val1); \
li x2, MASK_XLEN(val2); \
inst x14, x1, x2; \
TEST_INSERT_NOPS_ ## nop_cycles \
addi x6, x14, 0; \
addi x4, x4, 1; \
li x5, 2; \
bne x4, x5, 1b \
)
#define TEST_RR_SRC12_BYPASS( testnum, src1_nops, src2_nops, inst, result, val1, val2 ) \
TEST_CASE( testnum, x14, result, \
li x4, 0; \
1: li x1, MASK_XLEN(val1); \
TEST_INSERT_NOPS_ ## src1_nops \
li x2, MASK_XLEN(val2); \
TEST_INSERT_NOPS_ ## src2_nops \
inst x14, x1, x2; \
addi x4, x4, 1; \
li x5, 2; \
bne x4, x5, 1b \
)
#define TEST_RR_SRC21_BYPASS( testnum, src1_nops, src2_nops, inst, result, val1, val2 ) \
TEST_CASE( testnum, x14, result, \
li x4, 0; \
1: li x2, MASK_XLEN(val2); \
TEST_INSERT_NOPS_ ## src1_nops \
li x1, MASK_XLEN(val1); \
TEST_INSERT_NOPS_ ## src2_nops \
inst x14, x1, x2; \
addi x4, x4, 1; \
li x5, 2; \
bne x4, x5, 1b \
)
#define TEST_RR_ZEROSRC1( testnum, inst, result, val ) \
TEST_CASE( testnum, x2, result, \
li x1, MASK_XLEN(val); \
inst x2, x0, x1; \
)
#define TEST_RR_ZEROSRC2( testnum, inst, result, val ) \
TEST_CASE( testnum, x2, result, \
li x1, MASK_XLEN(val); \
inst x2, x1, x0; \
)
#define TEST_RR_ZEROSRC12( testnum, inst, result ) \
TEST_CASE( testnum, x1, result, \
inst x1, x0, x0; \
)
#define TEST_RR_ZERODEST( testnum, inst, val1, val2 ) \
TEST_CASE( testnum, x0, 0, \
li x1, MASK_XLEN(val1); \
li x2, MASK_XLEN(val2); \
inst x0, x1, x2; \
)
#-----------------------------------------------------------------------
# Test memory instructions
#-----------------------------------------------------------------------
#define TEST_LD_OP( testnum, inst, result, offset, base ) \
TEST_CASE( testnum, x14, result, \
li x15, result; /* Tell the exception handler the expected result. */ \
la x1, base; \
inst x14, offset(x1); \
)
#define TEST_ST_OP( testnum, load_inst, store_inst, result, offset, base ) \
TEST_CASE( testnum, x14, result, \
la x1, base; \
li x2, result; \
la x15, 7f; /* Tell the exception handler how to skip this test. */ \
store_inst x2, offset(x1); \
load_inst x14, offset(x1); \
j 8f; \
7: \
/* Set up the correct result for TEST_CASE(). */ \
mv x14, x2; \
8: \
)
#define TEST_LD_DEST_BYPASS( testnum, nop_cycles, inst, result, offset, base ) \
test_ ## testnum: \
li TESTNUM, testnum; \
li x4, 0; \
1: la x1, base; \
inst x14, offset(x1); \
TEST_INSERT_NOPS_ ## nop_cycles \
addi x6, x14, 0; \
li x7, result; \
bne x6, x7, fail; \
addi x4, x4, 1; \
li x5, 2; \
bne x4, x5, 1b; \
#define TEST_LD_SRC1_BYPASS( testnum, nop_cycles, inst, result, offset, base ) \
test_ ## testnum: \
li TESTNUM, testnum; \
li x4, 0; \
1: la x1, base; \
TEST_INSERT_NOPS_ ## nop_cycles \
inst x14, offset(x1); \
li x7, result; \
bne x14, x7, fail; \
addi x4, x4, 1; \
li x5, 2; \
bne x4, x5, 1b \
#define TEST_ST_SRC12_BYPASS( testnum, src1_nops, src2_nops, load_inst, store_inst, result, offset, base ) \
test_ ## testnum: \
li TESTNUM, testnum; \
li x4, 0; \
1: li x1, result; \
TEST_INSERT_NOPS_ ## src1_nops \
la x2, base; \
TEST_INSERT_NOPS_ ## src2_nops \
store_inst x1, offset(x2); \
load_inst x14, offset(x2); \
li x7, result; \
bne x14, x7, fail; \
addi x4, x4, 1; \
li x5, 2; \
bne x4, x5, 1b \
#define TEST_ST_SRC21_BYPASS( testnum, src1_nops, src2_nops, load_inst, store_inst, result, offset, base ) \
test_ ## testnum: \
li TESTNUM, testnum; \
li x4, 0; \
1: la x2, base; \
TEST_INSERT_NOPS_ ## src1_nops \
li x1, result; \
TEST_INSERT_NOPS_ ## src2_nops \
store_inst x1, offset(x2); \
load_inst x14, offset(x2); \
li x7, result; \
bne x14, x7, fail; \
addi x4, x4, 1; \
li x5, 2; \
bne x4, x5, 1b \
#define TEST_BR2_OP_TAKEN( testnum, inst, val1, val2 ) \
test_ ## testnum: \
li TESTNUM, testnum; \
li x1, val1; \
li x2, val2; \
inst x1, x2, 2f; \
bne x0, TESTNUM, fail; \
1: bne x0, TESTNUM, 3f; \
2: inst x1, x2, 1b; \
bne x0, TESTNUM, fail; \
3:
#define TEST_BR2_OP_NOTTAKEN( testnum, inst, val1, val2 ) \
test_ ## testnum: \
li TESTNUM, testnum; \
li x1, val1; \
li x2, val2; \
inst x1, x2, 1f; \
bne x0, TESTNUM, 2f; \
1: bne x0, TESTNUM, fail; \
2: inst x1, x2, 1b; \
3:
#define TEST_BR2_SRC12_BYPASS( testnum, src1_nops, src2_nops, inst, val1, val2 ) \
test_ ## testnum: \
li TESTNUM, testnum; \
li x4, 0; \
1: li x1, val1; \
TEST_INSERT_NOPS_ ## src1_nops \
li x2, val2; \
TEST_INSERT_NOPS_ ## src2_nops \
inst x1, x2, fail; \
addi x4, x4, 1; \
li x5, 2; \
bne x4, x5, 1b \
#define TEST_BR2_SRC21_BYPASS( testnum, src1_nops, src2_nops, inst, val1, val2 ) \
test_ ## testnum: \
li TESTNUM, testnum; \
li x4, 0; \
1: li x2, val2; \
TEST_INSERT_NOPS_ ## src1_nops \
li x1, val1; \
TEST_INSERT_NOPS_ ## src2_nops \
inst x1, x2, fail; \
addi x4, x4, 1; \
li x5, 2; \
bne x4, x5, 1b \
#-----------------------------------------------------------------------
# Test jump instructions
#-----------------------------------------------------------------------
#define TEST_JR_SRC1_BYPASS( testnum, nop_cycles, inst ) \
test_ ## testnum: \
li TESTNUM, testnum; \
li x4, 0; \
1: la x6, 2f; \
TEST_INSERT_NOPS_ ## nop_cycles \
inst x6; \
bne x0, TESTNUM, fail; \
2: addi x4, x4, 1; \
li x5, 2; \
bne x4, x5, 1b \
#define TEST_JALR_SRC1_BYPASS( testnum, nop_cycles, inst ) \
test_ ## testnum: \
li TESTNUM, testnum; \
li x4, 0; \
1: la x6, 2f; \
TEST_INSERT_NOPS_ ## nop_cycles \
inst x13, x6, 0; \
bne x0, TESTNUM, fail; \
2: addi x4, x4, 1; \
li x5, 2; \
bne x4, x5, 1b \
#-----------------------------------------------------------------------
# RV64UF MACROS
#-----------------------------------------------------------------------
#-----------------------------------------------------------------------
# Tests floating-point instructions
#-----------------------------------------------------------------------
#define qNaNh 0h:7e00
#define sNaNh 0h:7c01
#define qNaNf 0f:7fc00000
#define sNaNf 0f:7f800001
#define qNaN 0d:7ff8000000000000
#define sNaN 0d:7ff0000000000001
#define TEST_FP_OP_H_INTERNAL( testnum, flags, result, val1, val2, val3, code... ) \
test_ ## testnum: \
li TESTNUM, testnum; \
la a0, test_ ## testnum ## _data ;\
flh f0, 0(a0); \
flh f1, 2(a0); \
flh f2, 4(a0); \
lh a3, 6(a0); \
code; \
fsflags a1, x0; \
li a2, flags; \
bne a0, a3, fail; \
bne a1, a2, fail; \
.pushsection .data; \
.align 1; \
test_ ## testnum ## _data: \
.float16 val1; \
.float16 val2; \
.float16 val3; \
.result; \
.popsection
#define TEST_FP_OP_S_INTERNAL( testnum, flags, result, val1, val2, val3, code... ) \
test_ ## testnum: \
li TESTNUM, testnum; \
la a0, test_ ## testnum ## _data ;\
flw f0, 0(a0); \
flw f1, 4(a0); \
flw f2, 8(a0); \
lw a3, 12(a0); \
code; \
fsflags a1, x0; \
li a2, flags; \
bne a0, a3, fail; \
bne a1, a2, fail; \
.pushsection .data; \
.align 2; \
test_ ## testnum ## _data: \
.float val1; \
.float val2; \
.float val3; \
.result; \
.popsection
#define TEST_FP_OP_D_INTERNAL( testnum, flags, result, val1, val2, val3, code... ) \
test_ ## testnum: \
li TESTNUM, testnum; \
la a0, test_ ## testnum ## _data ;\
fld f0, 0(a0); \
fld f1, 8(a0); \
fld f2, 16(a0); \
ld a3, 24(a0); \
code; \
fsflags a1, x0; \
li a2, flags; \
bne a0, a3, fail; \
bne a1, a2, fail; \
.pushsection .data; \
.align 3; \
test_ ## testnum ## _data: \
.double val1; \
.double val2; \
.double val3; \
.result; \
.popsection
// TODO: assign a separate mem location for the comparison address?
#define TEST_FP_OP_D32_INTERNAL( testnum, flags, result, val1, val2, val3, code... ) \
test_ ## testnum: \
li TESTNUM, testnum; \
la a0, test_ ## testnum ## _data ;\
fld f0, 0(a0); \
fld f1, 8(a0); \
fld f2, 16(a0); \
lw a3, 24(a0); \
lw t1, 28(a0); \
code; \
fsflags a1, x0; \
li a2, flags; \
bne a0, a3, fail; \
bne t1, t2, fail; \
bne a1, a2, fail; \
.pushsection .data; \
.align 3; \
test_ ## testnum ## _data: \
.double val1; \
.double val2; \
.double val3; \
.result; \
.popsection
#define TEST_FCVT_S_D32( testnum, result, val1 ) \
TEST_FP_OP_D32_INTERNAL( testnum, 0, double result, val1, 0.0, 0.0, \
fcvt.s.d f3, f0; fcvt.d.s f3, f3; fsd f3, 0(a0); lw t2, 4(a0); lw a0, 0(a0))
#define TEST_FCVT_S_D( testnum, result, val1 ) \
TEST_FP_OP_D_INTERNAL( testnum, 0, double result, val1, 0.0, 0.0, \
fcvt.s.d f3, f0; fcvt.d.s f3, f3; fmv.x.d a0, f3)
#define TEST_FCVT_D_S( testnum, result, val1 ) \
TEST_FP_OP_S_INTERNAL( testnum, 0, float result, val1, 0.0, 0.0, \
fcvt.d.s f3, f0; fcvt.s.d f3, f3; fmv.x.s a0, f3)
#define TEST_FCVT_H_S( testnum, result, val1 ) \
TEST_FP_OP_H_INTERNAL( testnum, 0, float16 result, val1, 0.0, 0.0, \
fcvt.s.h f3, f0; fcvt.h.s f3, f3; fmv.x.h a0, f3)
#define TEST_FCVT_H_D( testnum, result, val1 ) \
TEST_FP_OP_H_INTERNAL( testnum, 0, float16 result, val1, 0.0, 0.0, \
fcvt.d.h f3, f0; fcvt.h.d f3, f3; fmv.x.h a0, f3)
#define TEST_FP_OP1_H( testnum, inst, flags, result, val1 ) \
TEST_FP_OP_H_INTERNAL( testnum, flags, float16 result, val1, 0.0, 0.0, \
inst f3, f0; fmv.x.h a0, f3;)
#define TEST_FP_OP1_S( testnum, inst, flags, result, val1 ) \
TEST_FP_OP_S_INTERNAL( testnum, flags, float result, val1, 0.0, 0.0, \
inst f3, f0; fmv.x.s a0, f3)
#define TEST_FP_OP1_D32( testnum, inst, flags, result, val1 ) \
TEST_FP_OP_D32_INTERNAL( testnum, flags, double result, val1, 0.0, 0.0, \
inst f3, f0; fsd f3, 0(a0); lw t2, 4(a0); lw a0, 0(a0))
// ^: store computation result in address from a0, load high-word into t2
#define TEST_FP_OP1_D( testnum, inst, flags, result, val1 ) \
TEST_FP_OP_D_INTERNAL( testnum, flags, double result, val1, 0.0, 0.0, \
inst f3, f0; fmv.x.d a0, f3)
#define TEST_FP_OP1_S_DWORD_RESULT( testnum, inst, flags, result, val1 ) \
TEST_FP_OP_S_INTERNAL( testnum, flags, dword result, val1, 0.0, 0.0, \
inst f3, f0; fmv.x.s a0, f3)
#define TEST_FP_OP1_H_DWORD_RESULT( testnum, inst, flags, result, val1 ) \
TEST_FP_OP_H_INTERNAL( testnum, flags, word result, val1, 0.0, 0.0, \
inst f3, f0; fmv.x.h a0, f3)
#define TEST_FP_OP1_D32_DWORD_RESULT( testnum, inst, flags, result, val1 ) \
TEST_FP_OP_D32_INTERNAL( testnum, flags, dword result, val1, 0.0, 0.0, \
inst f3, f0; fsd f3, 0(a0); lw t2, 4(a0); lw a0, 0(a0))
// ^: store computation result in address from a0, load high-word into t2
#define TEST_FP_OP1_D_DWORD_RESULT( testnum, inst, flags, result, val1 ) \
TEST_FP_OP_D_INTERNAL( testnum, flags, dword result, val1, 0.0, 0.0, \
inst f3, f0; fmv.x.d a0, f3)
#define TEST_FP_OP2_S( testnum, inst, flags, result, val1, val2 ) \
TEST_FP_OP_S_INTERNAL( testnum, flags, float result, val1, val2, 0.0, \
inst f3, f0, f1; fmv.x.s a0, f3)
#define TEST_FP_OP2_H( testnum, inst, flags, result, val1, val2 ) \
TEST_FP_OP_H_INTERNAL( testnum, flags, float16 result, val1, val2, 0.0, \
inst f3, f0, f1; fmv.x.h a0, f3)
#define TEST_FP_OP2_D32( testnum, inst, flags, result, val1, val2 ) \
TEST_FP_OP_D32_INTERNAL( testnum, flags, double result, val1, val2, 0.0, \
inst f3, f0, f1; fsd f3, 0(a0); lw t2, 4(a0); lw a0, 0(a0))
// ^: store computation result in address from a0, load high-word into t2
#define TEST_FP_OP2_D( testnum, inst, flags, result, val1, val2 ) \
TEST_FP_OP_D_INTERNAL( testnum, flags, double result, val1, val2, 0.0, \
inst f3, f0, f1; fmv.x.d a0, f3)
#define TEST_FP_OP3_S( testnum, inst, flags, result, val1, val2, val3 ) \
TEST_FP_OP_S_INTERNAL( testnum, flags, float result, val1, val2, val3, \
inst f3, f0, f1, f2; fmv.x.s a0, f3)
#define TEST_FP_OP3_H( testnum, inst, flags, result, val1, val2, val3 ) \
TEST_FP_OP_H_INTERNAL( testnum, flags, float16 result, val1, val2, val3, \
inst f3, f0, f1, f2; fmv.x.h a0, f3)
#define TEST_FP_OP3_D32( testnum, inst, flags, result, val1, val2, val3 ) \
TEST_FP_OP_D32_INTERNAL( testnum, flags, double result, val1, val2, val3, \
inst f3, f0, f1, f2; fsd f3, 0(a0); lw t2, 4(a0); lw a0, 0(a0))
// ^: store computation result in address from a0, load high-word into t2
#define TEST_FP_OP3_D( testnum, inst, flags, result, val1, val2, val3 ) \
TEST_FP_OP_D_INTERNAL( testnum, flags, double result, val1, val2, val3, \
inst f3, f0, f1, f2; fmv.x.d a0, f3)
#define TEST_FP_INT_OP_S( testnum, inst, flags, result, val1, rm ) \
TEST_FP_OP_S_INTERNAL( testnum, flags, word result, val1, 0.0, 0.0, \
inst a0, f0, rm)
#define TEST_FP_INT_OP_H( testnum, inst, flags, result, val1, rm ) \
TEST_FP_OP_H_INTERNAL( testnum, flags, word result, val1, 0.0, 0.0, \
inst a0, f0, rm)
#define TEST_FP_INT_OP_D32( testnum, inst, flags, result, val1, rm ) \
TEST_FP_OP_D32_INTERNAL( testnum, flags, dword result, val1, 0.0, 0.0, \
inst a0, f0, f1; li t2, 0)
#define TEST_FP_INT_OP_D( testnum, inst, flags, result, val1, rm ) \
TEST_FP_OP_D_INTERNAL( testnum, flags, dword result, val1, 0.0, 0.0, \
inst a0, f0, rm)
#define TEST_FP_CMP_OP_S( testnum, inst, flags, result, val1, val2 ) \
TEST_FP_OP_S_INTERNAL( testnum, flags, word result, val1, val2, 0.0, \
inst a0, f0, f1)
#define TEST_FP_CMP_OP_H( testnum, inst, flags, result, val1, val2 ) \
TEST_FP_OP_H_INTERNAL( testnum, flags, hword result, val1, val2, 0.0, \
inst a0, f0, f1)
#define TEST_FP_CMP_OP_D32( testnum, inst, flags, result, val1, val2 ) \
TEST_FP_OP_D32_INTERNAL( testnum, flags, dword result, val1, val2, 0.0, \
inst a0, f0, f1; li t2, 0)
#define TEST_FP_CMP_OP_D( testnum, inst, flags, result, val1, val2 ) \
TEST_FP_OP_D_INTERNAL( testnum, flags, dword result, val1, val2, 0.0, \
inst a0, f0, f1)
#define TEST_FCLASS_S(testnum, correct, input) \
TEST_CASE(testnum, a0, correct, li a0, input; fmv.s.x fa0, a0; \
fclass.s a0, fa0)
#define TEST_FCLASS_D32(testnum, correct, input) \
TEST_CASE(testnum, a0, correct, \
la a0, test_ ## testnum ## _data ;\
fld fa0, 0(a0); \
fclass.d a0, fa0) \
.pushsection .data; \
.align 3; \
test_ ## testnum ## _data: \
.dword input; \
.popsection
#define TEST_FCLASS_D(testnum, correct, input) \
TEST_CASE(testnum, a0, correct, li a0, input; fmv.d.x fa0, a0; \
fclass.d a0, fa0)
#define TEST_INT_FP_OP_S( testnum, inst, result, val1 ) \
test_ ## testnum: \
li TESTNUM, testnum; \
la a0, test_ ## testnum ## _data ;\
lw a3, 0(a0); \
li a0, val1; \
inst f0, a0; \
fsflags x0; \
fmv.x.s a0, f0; \
bne a0, a3, fail; \
.pushsection .data; \
.align 2; \
test_ ## testnum ## _data: \
.float result; \
.popsection
#define TEST_INT_FP_OP_H( testnum, inst, result, val1 ) \
test_ ## testnum: \
li TESTNUM, testnum; \
la a0, test_ ## testnum ## _data ;\
lh a3, 0(a0); \
li a0, val1; \
inst f0, a0; \
fsflags x0; \
fmv.x.h a0, f0; \
bne a0, a3, fail; \
.pushsection .data; \
.align 1; \
test_ ## testnum ## _data: \
.float16 result; \
.popsection
#define TEST_INT_FP_OP_D32( testnum, inst, result, val1 ) \
test_ ## testnum: \
li TESTNUM, testnum; \
la a0, test_ ## testnum ## _data ;\
lw a3, 0(a0); \
lw a4, 4(a0); \
li a1, val1; \
inst f0, a1; \
\
fsd f0, 0(a0); \
lw a1, 4(a0); \
lw a0, 0(a0); \
\
fsflags x0; \
bne a0, a3, fail; \
bne a1, a4, fail; \
.pushsection .data; \
.align 3; \
test_ ## testnum ## _data: \
.double result; \
.popsection
#define TEST_INT_FP_OP_D( testnum, inst, result, val1 ) \
test_ ## testnum: \
li TESTNUM, testnum; \
la a0, test_ ## testnum ## _data ;\
ld a3, 0(a0); \
li a0, val1; \
inst f0, a0; \
fsflags x0; \
fmv.x.d a0, f0; \
bne a0, a3, fail; \
.pushsection .data; \
.align 3; \
test_ ## testnum ## _data: \
.double result; \
.popsection
// We need some special handling here to allow 64-bit comparison in 32-bit arch
// TODO: find a better name and clean up when intended for general usage?
#define TEST_CASE_D32( testnum, testreg1, testreg2, correctval, code... ) \
test_ ## testnum: \
code; \
la x15, test_ ## testnum ## _data ; \
lw x7, 0(x15); \
lw x15, 4(x15); \
li TESTNUM, testnum; \
bne testreg1, x7, fail;\
bne testreg2, x15, fail;\
.pushsection .data; \
.align 3; \
test_ ## testnum ## _data: \
.dword correctval; \
.popsection
// ^ x14 is used in some other macros, to avoid issues we use x15 for upper word
#define MISALIGNED_LOAD_HANDLER \
li t0, CAUSE_MISALIGNED_LOAD; \
csrr t1, mcause; \
bne t0, t1, fail; \
\
/* We got a misaligned exception. Pretend we handled it in software */ \
/* by loading the correct result here. */ \
mv a4, a5; \
\
/* And skip this instruction */ \
csrr t0, mepc; \
addi t0, t0, 4; \
csrw mepc, t0; \
mret
#define MISALIGNED_STORE_HANDLER \
li t0, CAUSE_MISALIGNED_STORE; \
csrr t1, mcause; \
bne t0, t1, fail; \
\
/* We got a misaligned exception. Skip this test. */ \
csrw mepc, x15; \
mret
#-----------------------------------------------------------------------
# Pass and fail code (assumes test num is in TESTNUM)
#-----------------------------------------------------------------------
#define TEST_PASSFAIL \
bne x0, TESTNUM, pass; \
fail: \
RVTEST_FAIL; \
pass: \
RVTEST_PASS \
#-----------------------------------------------------------------------
# Test data section
#-----------------------------------------------------------------------
#define TEST_DATA
#endif

View file

@ -1,101 +0,0 @@
#!/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
import argparse
import os
import shlex
import sys
import tempfile
import pathlib3x as pathlib
from scripts_lib import run_one, format_to_cmd
import riscvdv_interface
from test_entry import read_test_dot_seed
from metadata import RegressionMetadata
from test_run_result import TestRunResult
def _main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument('--dir-metadata', type=pathlib.Path, required=True)
parser.add_argument('--test-dot-seed', type=read_test_dot_seed, required=True)
args = parser.parse_args()
tds = args.test_dot_seed
md = RegressionMetadata.construct_from_metadata_dir(args.dir_metadata)
trr = TestRunResult.construct_from_metadata_dir(args.dir_metadata, f"{tds[0]}.{tds[1]}")
# Run riscv-dv to get a list of commands that it would run to try to
# compile and convert the files in question. These will need some massaging
# to match our paths, but we can't generate the commands by hand because
# there are several test-specific options that might appear.
with tempfile.TemporaryDirectory() as td:
placeholder = os.path.join(td, '@@PLACEHOLDER@@')
orig_list = os.path.join(td, 'orig-cmds.list')
cmd = (riscvdv_interface.get_run_cmd(bool(md.verbose)) +
['--verbose',
'--output', placeholder,
'--steps=gcc_compile',
'--test', trr.testname,
'--start_seed', str(trr.seed),
'--iterations', '1',
'--isa', md.isa_ibex,
'--debug', orig_list])
trr.compile_asm_gen_log = trr.dir_test / 'compile_gen.riscv-dv.log'
trr.compile_asm_gen_cmds = [format_to_cmd(cmd)]
dv_ret = run_one(md.verbose, trr.compile_asm_gen_cmds[0],
redirect_stdstreams=trr.compile_asm_gen_log)
if dv_ret:
return dv_ret
orig_cmds = []
with open(orig_list) as orig_file:
for line in orig_file:
line = line.strip()
if not line:
continue
orig_cmds.append(shlex.split(line))
# Do the massaging. We intentionally used "@@PLACEHOLDER@@" as a path in
# our call to riscv-dv, which should let us find all the things that matter
# easily.
trr.objectfile = trr.dir_test / 'test.o'
trr.binary = trr.dir_test / 'test.bin'
rewrites = [
(f"{placeholder}/asm_test/{trr.testname}_0.S", str(trr.assembly)),
(f"{placeholder}/asm_test/{trr.testname}_0.o", str(trr.objectfile)),
(f"{placeholder}/asm_test/{trr.testname}_0.bin", str(trr.binary))
]
new_cmds = []
for cmd in orig_cmds:
new_cmd = []
for word in cmd:
for old, new in rewrites:
word = word.replace(old, new)
if placeholder in word:
raise RuntimeError("Couldn't replace every copy of "
f"placeholder in {cmd}")
new_cmd.append(word)
new_cmds.append(new_cmd)
# Finally, run all the commands
trr.compile_asm_log = trr.dir_test / 'compile.riscv-dv.log'
trr.compile_asm_cmds = [format_to_cmd(cmd) for cmd in new_cmds]
trr.export(write_yaml=True)
for cmd in trr.compile_asm_cmds:
ret = run_one(md.verbose, cmd)
if ret != 0:
return ret
if __name__ == '__main__':
sys.exit(_main())

View file

@ -0,0 +1,155 @@
#!/usr/bin/env python3
"""Compile the different test sources to create binaries, ready for simulation."""
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
import argparse
from typing import Tuple, Dict, List
import os
import shlex
import sys
import tempfile
import pathlib3x as pathlib
from scripts_lib import run_one, format_to_cmd, read_yaml
import riscvdv_interface
from test_entry import read_test_dot_seed
from metadata import RegressionMetadata
from test_run_result import TestRunResult, TestType
import logging
logger = logging.getLogger(__name__)
def get_riscvdv_compile_cmds(md: RegressionMetadata, trr: TestRunResult) -> List[str]:
"""Run riscv-dv to get a list of build/compilation commands.
These will need some massaging to match our paths, but we can't generate the
commands by hand because there are several test-specific options that might appear.
"""
with tempfile.TemporaryDirectory() as td_fd:
td = pathlib.Path(td_fd)
placeholder = td/'@@PLACEHOLDER@@'
orig_list = td/'orig-cmds.list'
cmd = (riscvdv_interface.get_run_cmd(bool(md.verbose)) +
['--verbose',
'--output', placeholder,
'--steps=gcc_compile',
'--test', trr.testname,
'--start_seed', str(trr.seed),
'--iterations', '1',
'--isa', md.isa_ibex,
'--debug', orig_list]) # Use the --debug output to capture the original commands
trr.compile_asm_gen_log = trr.dir_test / 'compile_gen.riscv-dv.log'
trr.compile_asm_gen_cmds = [format_to_cmd(cmd)]
dv_ret = run_one(verbose=md.verbose,
cmd=trr.compile_asm_gen_cmds[0],
redirect_stdstreams=trr.compile_asm_gen_log)
if dv_ret:
return dv_ret
orig_cmds = []
with open(orig_list) as fd:
for line in fd:
line = line.strip()
if line:
orig_cmds.append(shlex.split(line))
# Do the massaging. We intentionally used "@@PLACEHOLDER@@" as a path in
# our call to riscv-dv, which should let us find all the things that matter
# easily.
trr.objectfile = trr.dir_test/'test.o'
trr.binary = trr.dir_test/'test.bin'
rewrites = [
(f"{placeholder}/asm_test/{trr.testname}_0.S", str(trr.assembly)),
(f"{placeholder}/asm_test/{trr.testname}_0.o", str(trr.objectfile)),
(f"{placeholder}/asm_test/{trr.testname}_0.bin", str(trr.binary))
]
new_cmds = []
for cmd in orig_cmds:
new_cmd = []
for word in cmd:
for old, new in rewrites:
word = word.replace(old, new)
if str(placeholder) in word:
raise RuntimeError("Couldn't replace every copy of "
f"placeholder in {cmd}")
new_cmd.append(word)
new_cmds.append(new_cmd)
return new_cmds
def get_directed_compile_cmds(md: RegressionMetadata, trr: TestRunResult) -> List[str]:
"""Construct the build/compilation commands from the directed_testlist data."""
env = os.environ.copy()
for e in ['RISCV_TOOLCHAIN', 'RISCV_GCC', 'RISCV_OBJCOPY']:
if e not in env:
raise RuntimeError("Missing required environment variables for the RISCV TOOLCHAIN")
# Get the data from the directed test yaml that we need to construct the command.
directed_data = read_yaml(md.directed_test_data)
trr.directed_data = next(filter(lambda item: (item.get('test') == trr.testname), directed_data), None)
directed_dir = md.directed_test_dir
includes = directed_dir/(pathlib.Path(trr.directed_data.get('includes')))
ld = directed_dir/(pathlib.Path(trr.directed_data.get('ld_script')))
trr.assembly = directed_dir/trr.directed_data.get('test_srcs')
trr.objectfile = trr.dir_test/'test.o'
trr.binary = trr.dir_test/'test.bin'
# Compose the compilation command
riscv_gcc_arg = trr.directed_data.get('gcc_opts') + \
f" -I{includes}" + \
f" -T{ld}"
riscv_gcc_cmd = " ".join([env.get('RISCV_GCC'),
riscv_gcc_arg,
f"-o {trr.objectfile}",
f"{trr.assembly}"])
riscv_gcc_bin_cmd = " ".join([env.get('RISCV_OBJCOPY'),
f"-O binary {trr.objectfile}",
f"{trr.binary}"])
return [shlex.split(riscv_gcc_cmd), shlex.split(riscv_gcc_bin_cmd)]
def _main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument('--dir-metadata', type=pathlib.Path, required=True)
parser.add_argument('--test-dot-seed', type=read_test_dot_seed, required=False)
parser.add_argument('--bin', type=pathlib.Path, required=False)
args = parser.parse_args()
tds = args.test_dot_seed
md = RegressionMetadata.construct_from_metadata_dir(args.dir_metadata)
trr = TestRunResult.construct_from_metadata_dir(args.dir_metadata, f"{tds[0]}.{tds[1]}")
if trr.testtype == TestType.RISCVDV:
cmds = get_riscvdv_compile_cmds(md, trr)
trr.compile_asm_log = trr.dir_test/'compile.riscvdv.log'
if trr.testtype == TestType.DIRECTED:
cmds = get_directed_compile_cmds(md, trr)
trr.compile_asm_log = trr.dir_test/'compile.directed.log'
# Finally, run all the commands
trr.compile_asm_cmds = [format_to_cmd(cmd) for cmd in cmds]
trr.export(write_yaml=True)
for cmd in trr.compile_asm_cmds:
ret = run_one(md.verbose, cmd)
if ret != 0:
return ret
if __name__ == '__main__':
sys.exit(_main())

View file

@ -0,0 +1,12 @@
define get-metadata-variable
env PYTHONPATH=$(PYTHONPATH) python3 ./scripts/metadata.py \
--op "print_field" \
--dir-metadata $(METADATA-DIR) \
--field $(1)
endef
define get-meta
$(shell $(call get-metadata-variable,$(1)))
endef
# This is how you can get variables from the python metadata easily...
# testvar := $(call get-meta,"ibex_root")

View file

@ -0,0 +1,99 @@
###############################################################################
TB-COMPILE-STAMP = $(METADATA-DIR)/tb.compile.stamp
rtl_tb_compile: $(METADATA-DIR)/tb.compile.stamp
rtl-tb-compile-var-deps := SIMULATOR COV WAVES # Rebuild if these change
rtl_sim_run: $(rtl-sim-logs)
check_logs: $(comp-results)
FCOV-STAMP = $(METADATA-DIR)/fcov.stamp
riscv_dv_fcov: $(METADATA-DIR)/fcov.stamp
MERGE-COV-STAMP = $(METADATA-DIR)/merge.cov.stamp
merge_cov: $(METADATA-DIR)/merge.cov.stamp
REGR-LOG-STAMP = $(METADATA-DIR)/regr.log.stamp
collect_results: $(METADATA-DIR)/regr.log.stamp
rtl-sim-logs +=
comp-results +=
###############################################################################
# Compile ibex core TB
#
# Note that this doesn't depend on the seed: the DUT doesn't depend on which
# test we're running!
tb-compile-vars-path := $(BUILD-DIR)/.tb.vars.mk
-include $(tb-compile-vars-path)
tb-compile-vars-prereq = $(call vars-prereq,comp,compiling TB,$(rtl-tb-compile-var-deps))
$(METADATA-DIR)/tb.compile.stamp: \
$(tb-compile-vars-prereq) $(all-verilog) $(all-cpp) $(risc-dv-files) \
scripts/compile_tb.py yaml/rtl_simulation.yaml \
| $(BUILD-DIR)
@echo Building RTL testbench
$(verb)env PYTHONPATH=$(PYTHONPATH) \
scripts/compile_tb.py \
--dir-metadata $(METADATA-DIR)
$(call dump-vars,$(tb-compile-vars-path),comp,$(rtl-tb-compile-var-deps))
@touch $@
###############################################################################
# Run ibex RTL simulation with randomly-generated program and uvm stimulus
$(rtl-sim-logs): $(TESTS-DIR)/%/$(rtl-sim-logfile): \
$(TB-COMPILE-STAMP) $(TESTS-DIR)/%/test.bin scripts/run_rtl.py
@echo Running RTL simulation at $(@D)
$(verb)env PYTHONPATH=$(PYTHONPATH) \
scripts/run_rtl.py \
--dir-metadata $(METADATA-DIR) \
--test-dot-seed $*
###############################################################################
# Gather RTL sim results, and parse logs for errors
$(comp-results): $(TESTS-DIR)/%/trr.yaml: \
$(TESTS-DIR)/%/$(rtl-sim-logfile) scripts/check_logs.py
@echo Collecting simulation results and checking logs of testcase at $@
$(verb)env PYTHONPATH=$(PYTHONPATH) \
scripts/check_logs.py \
--dir-metadata $(METADATA-DIR) \
--test-dot-seed $*
###############################################################################
# Generate RISCV-DV functional coverage
# TODO(udi) - add B extension
$(METADATA-DIR)/fcov.stamp: $(comp-results) \
scripts/get_fcov.py
@echo Generating RISCV_DV functional coverage
$(verb)env PYTHONPATH=$(PYTHONPATH) \
scripts/get_fcov.py \
--dir-metadata $(METADATA-DIR)
@touch $@
###############################################################################
# Merge all output coverage directories
# Any coverage databases generated from the riscv_dv_fcov target will be merged
# as well.
$(METADATA-DIR)/merge.cov.stamp: $(FCOV-STAMP) \
scripts/merge_cov.py
@echo Merging all recorded coverage data into a single report
$(verb)env PYTHONPATH=$(PYTHONPATH) \
scripts/merge_cov.py \
--dir-metadata $(METADATA-DIR)
@touch $@
###############################################################################
# Generate the summarized regression log
$(METADATA-DIR)/regr.log.stamp: scripts/collect_results.py $(comp-results)
@echo Collecting up results of tests into report regr.log
$(verb)env PYTHONPATH=$(PYTHONPATH) \
./scripts/collect_results.py \
--dir-metadata $(METADATA-DIR)
@touch $@

View file

@ -20,18 +20,20 @@ from dataclasses import field
from typeguard import typechecked
import portalocker
import signal
import shutil
import setup_imports
import scripts_lib
import ibex_cmd
import ibex_config
import lib as riscvdv_lib
from test_run_result import TestRunResult
from test_run_result import TestRunResult, TestType
import logging
logger = logging.getLogger(__name__)
@typechecked
@dataclasses.dataclass
class RegressionMetadata(scripts_lib.testdata_cls):
@ -60,7 +62,7 @@ class RegressionMetadata(scripts_lib.testdata_cls):
ibex_config: str = ' '
dut_cov_rtl_path: str = ''
tests_and_counts: List[Tuple[str, int]] = field(default_factory=list)
tests_and_counts: List[Tuple[str, int, TestType]] = field(default_factory=list)
isa_ibex: Optional[str] = None
isa_iss: Optional[str] = None
run_rtl_timeout_s: int = 1800
@ -71,6 +73,8 @@ class RegressionMetadata(scripts_lib.testdata_cls):
ibex_riscvdv_customtarget : pathlib.Path = field(init=False, compare=False, default_factory=pathlib.Path)
ibex_riscvdv_testlist : pathlib.Path = field(init=False, compare=False, default_factory=pathlib.Path)
ibex_riscvdv_csr : pathlib.Path = field(init=False, compare=False, default_factory=pathlib.Path)
directed_test_dir : pathlib.Path = field(init=False, compare=False, default_factory=pathlib.Path)
directed_test_data : pathlib.Path = field(init=False, compare=False, default_factory=pathlib.Path)
# Build logs and commands
riscvdv_build_log : Optional[pathlib.Path] = None
@ -124,6 +128,8 @@ class RegressionMetadata(scripts_lib.testdata_cls):
self.ibex_riscvdv_customtarget = self.ibex_dv_root/'riscv_dv_extension'
self.ibex_riscvdv_testlist = self.ibex_riscvdv_customtarget/'testlist.yaml'
self.ibex_riscvdv_csr = self.ibex_riscvdv_customtarget/'csr_description.yaml'
self.directed_test_dir = self.ibex_dv_root/'directed_tests'
self.directed_test_data = self.directed_test_dir/'directed_testlist.yaml'
self.environment_variables = dict(os.environ)
@ -139,7 +145,6 @@ class RegressionMetadata(scripts_lib.testdata_cls):
cfg = ibex_cmd.get_config(self.ibex_config)
self.isa_ibex, self.isa_iss = ibex_cmd.get_isas_for_config(cfg)
self.tests_and_counts = self.get_tests_and_counts()
def _setup_directories(self):
@ -241,6 +246,11 @@ class RegressionMetadata(scripts_lib.testdata_cls):
# Fetch/set more derivative metadata specific to the ibex
md._get_ibex_metadata()
# Setup the tests/counts we are going to use, by parsing the
# riscv-dv/directed-test structured data.
# eg. testlist.yaml / directed_testlist.yaml
md.tests_and_counts = md.get_tests_and_counts()
return md
@classmethod
@ -251,7 +261,7 @@ class RegressionMetadata(scripts_lib.testdata_cls):
md = cls.construct_from_pickle(md_pickle)
return md
def get_tests_and_counts(self) -> List[Tuple[str, int]]:
def get_tests_and_counts(self) -> List[Tuple[str, int, TestType]]:
"""Get a list of tests and the number of iterations to run of each.
ibex_config should be the name of the Ibex configuration to be tested.
@ -263,59 +273,71 @@ class RegressionMetadata(scripts_lib.testdata_cls):
number of iterations for each test.
"""
rv_testlist = self.ibex_riscvdv_testlist
rv_test = self.test if self.test is not None else 'all'
rv_iterations = self.iterations or 0
# Get all the tests that match the test argument, scaling as necessary with
# the iterations argument.
matched_list = [] # type: _TestEntries
riscvdv_lib.process_regression_list(
testlist=rv_testlist,
test=rv_test,
iterations=rv_iterations,
matched_list=matched_list,
riscv_dv_root=self.riscvdv_root)
if not matched_list:
raise RuntimeError("Cannot find {} in {}".format(self.test, self.testlist))
riscvdv_matched_list: ibex_cmd._TestEntries = self.process_riscvdv_testlist()
directed_matched_list: ibex_cmd._TestEntries = self.process_directed_testlist()
if not (riscvdv_matched_list or directed_matched_list):
raise RuntimeError("No matching tests found in testlists.")
# Filter tests by the chosen ibex configuration
filtered_list = ibex_cmd.filter_tests_by_config(
riscvdv_filtered_list = ibex_cmd.filter_tests_by_config(
ibex_config.parse_config(self.ibex_config, str(self.ibex_configs)),
matched_list)
riscvdv_matched_list)
directed_filtered_list = ibex_cmd.filter_tests_by_config(
ibex_config.parse_config(self.ibex_config, str(self.ibex_configs)),
directed_matched_list)
# Convert to desired output format (and check for well-formedness)
ret = []
for test in filtered_list:
for test in riscvdv_filtered_list:
name = test['test']
iterations = test['iterations']
assert isinstance(name, str) and isinstance(iterations, int)
assert iterations > 0
ret.append((name, iterations))
ret.append((name, iterations, TestType.RISCVDV))
for test in directed_filtered_list:
name = test['test']
iterations = test['iterations']
assert isinstance(name, str) and isinstance(iterations, int)
assert iterations > 0
ret.append((name, iterations, TestType.DIRECTED))
return ret
def tds(self, give_tuple: bool = False) -> Union[List[str],
List[Tuple[str, int]]]:
"""Return the TEST.SEED strings for all the tests configured in the regression.
def process_riscvdv_testlist(self) -> [ibex_cmd._TestEntries]:
"""Extract test information from the riscvdv testlist yaml."""
matched_list: [ibex_cmd._TestEntries] = []
By default returns a list of strs which are TEST.SEED, but can return a list of
tuples as (TEST, SEED)
# Get all the tests from the 'testlist' that match the 'test' argument.
riscvdv_lib.process_regression_list(
testlist=self.ibex_riscvdv_testlist,
test=(self.test if self.test is not None else 'all'),
iterations=(self.iterations or 0),
matched_list=matched_list,
riscv_dv_root=self.riscvdv_root)
return matched_list
def process_directed_testlist(self) -> [ibex_cmd._TestEntries]:
"""Extract test information from the directed_test yaml.
Employ a similar format to the riscv-dv testlist structure to
define directed tests.
"""
if not self.tests_and_counts:
raise RuntimeError("self.tests_and_counts is empty, cant get TEST.SEED strings.")
tds_list = []
for test, count in self.tests_and_counts:
for i in range(count):
if give_tuple:
tds = (test, self.seed + i)
else:
tds = f"{test}.{self.seed + i}"
tds_list.append(tds)
matched_list: ibex_cmd._TestEntries = []
return tds_list
yaml_data = scripts_lib.read_yaml(self.directed_test_data)
mult_test = self.test.split(',')
for entry in yaml_data:
if (entry['test'] in mult_test) or (self.test == "all"):
if (self.iterations is not None) and (entry['iterations'] > 0):
entry['iterations'] = self.iterations
if entry['iterations'] > 0:
matched_list.append(entry)
return matched_list
class Ops(Enum):
@ -323,7 +345,6 @@ class Ops(Enum):
CREATE = 'create_metadata'
PRINT_FIELD = 'print_field'
TESTS_AND_SEEDS = 'tests_and_seeds'
def __str__(self):
return self.value
@ -353,8 +374,8 @@ def _main():
logger.error("Build metadata already exists, not recreating from scratch.")
return
md = RegressionMetadata.arg_list_initializer(dir_metadata=pathlib.Path(args.dir_metadata),
dir_out=pathlib.Path(args.dir_out),
md = RegressionMetadata.arg_list_initializer(dir_metadata=args.dir_metadata,
dir_out=args.dir_out,
args_list=args.args_list)
# Setup metadata objects for each of the tests to be run. Construct a list of these
@ -363,28 +384,32 @@ def _main():
# object that defines the wider regression.
md.tests_pickle_files = []
for test, seed in md.tds(give_tuple=True):
tds_str = f"{test}.{seed}"
trr_pickle_file = md.dir_metadata / (tds_str + ".pickle")
# Initialize TestRunResult object
trr = TestRunResult(
passed=None,
failure_message=None,
testdotseed=tds_str,
testname=test,
seed=seed,
rtl_simulator=md.simulator,
iss_cosim=md.iss,
dir_test=md.dir_tests / tds_str,
metadata_pickle_file=md.pickle_file,
pickle_file=trr_pickle_file,
yaml_file=(md.dir_tests / tds_str / 'trr.yaml'))
if not md.tests_and_counts:
raise RuntimeError("md.tests_and_counts is empty, cant get TEST.SEED strings.")
for test, count, testtype in md.tests_and_counts:
for testseed in range(md.seed, md.seed + count):
tds_str = f"{test}.{testseed}"
# Save the path into a list in the regression metadata object for later.
md.tests_pickle_files.append(trr.pickle_file)
# Export the trr structure to disk.
trr.export(write_yaml=True)
# Initialize TestRunResult object
trr = TestRunResult(
passed=None,
failure_message=None,
testtype=testtype,
testdotseed=tds_str,
testname=test,
seed=testseed,
rtl_simulator=md.simulator,
iss_cosim=md.iss,
dir_test=md.dir_tests/tds_str,
metadata_pickle_file=md.pickle_file,
pickle_file=md.dir_metadata/(tds_str + ".pickle"),
yaml_file=md.dir_tests/tds_str/'trr.yaml')
# Save the path into a list in the regression metadata object for later.
md.tests_pickle_files.append(trr.pickle_file)
# Export the trr structure to disk.
trr.export(write_yaml=True)
# Export here to commit new RegressionMetadata object to disk.
md.export(write_yaml=True)
@ -393,6 +418,24 @@ def _main():
md = RegressionMetadata.construct_from_metadata_dir(args.dir_metadata)
# We have some special fields that contain lists of tests, so check those first.
if (args.field == 'riscvdv_tds'):
tds_list = []
for test, count, testtype in md.tests_and_counts:
for testseed in range(md.seed, md.seed + count):
if testtype == TestType.RISCVDV:
tds_list.append(f"{test}.{testseed}")
print(" ".join(tds_list))
return
if (args.field == 'directed_tds'):
tds_list = []
for test, count, testtype in md.tests_and_counts:
for testseed in range(md.seed, md.seed + count):
if testtype == TestType.DIRECTED:
tds_list.append(f"{test}.{testseed}")
print(" ".join(tds_list))
return
value = getattr(md, args.field)
if value is None:
raise RuntimeError("Field requested is not present or not set in the regression metadata object")
@ -400,13 +443,6 @@ def _main():
logger.debug(f"Returning value of field {args.field} as {value}")
print(str(value)) # Captured into Makefile variable
if args.op == Ops.TESTS_AND_SEEDS:
"""Return a list of TEST.SEED for all the valid tests"""
md = RegressionMetadata.construct_from_metadata_dir(args.dir_metadata)
for tds in md.tds():
print(tds)
class LockedMetadata():
"""Construct instance of RegressionMetadata, while locking the on-disk file.

View file

@ -0,0 +1,80 @@
###############################################################################
CORE-CONFIG-STAMP = $(METADATA-DIR)/core.config.stamp
core_config: $(CORE-CONFIG-STAMP)
core-config-var-deps := IBEX_CONFIG
INSTR-GEN-BUILD-STAMP = $(METADATA-DIR)/instr.gen.build.stamp
instr_gen_build: $(METADATA-DIR)/instr.gen.build.stamp
instr-gen-build-var-deps := SIMULATOR SIGNATURE_ADDR # Rebuild if these change
instr_gen_run: $(riscvdv-test-asms)
riscvdv-test-asms +=
riscvdv-test-bins +=
###############################################################################
# Configure the templated file riscv-dv uses to know the parameterized features available
#
core-config-vars-path := $(BUILD-DIR)/.cc.vars.mk
-include $(core-config-vars-path)
core-config-var-prereq = \
$(call vars-prereq, \
gen, \
Generate core configuration file, \
$(core-config-var-deps))
$(CORE-CONFIG-STAMP): \
$(core-config-var-prereq) ./riscv_dv_extension/riscv_core_setting.tpl.sv \
scripts/render_config_template.py \
| $(BUILD-DIR)
@echo Generating core configuration file
$(verb)env PYTHONPATH=$(PYTHONPATH) \
scripts/render_config_template.py \
--dir-metadata $(METADATA-DIR) \
$(EXT_DIR)/riscv_core_setting.tpl.sv > $(EXT_DIR)/riscv_core_setting.sv
$(call dump-vars,$(core-config-vars-path),gen,$(core-config-var-deps))
@touch $@
###############################################################################
# Build the Random Instruction Generator
#
ig-build-vars-path := $(BUILD-DIR)/.instr_gen.vars.mk
-include $(ig-build-vars-path)
instr-gen-build-vars-prereq = \
$(call vars-prereq, \
gen, \
building instruction generator, \
$(instr-gen-build-var-deps))
$(METADATA-DIR)/instr.gen.build.stamp: \
$(instr-gen-build-vars-prereq) $(riscv-dv-files) $(CORE-CONFIG-STAMP) \
scripts/build_instr_gen.py \
| $(BUILD-DIR)
@echo Building randomized test generator
$(verb)env PYTHONPATH=$(PYTHONPATH) \
scripts/build_instr_gen.py \
--dir-metadata $(METADATA-DIR)
$(call dump-vars,$(ig-build-vars-path),gen,$(instr-gen-build-var-deps))
@touch $@
###############################################################################
# Run the random instruction generator
#
# Make use of static-pattern rules to extract the TDS
# https://www.gnu.org/software/make/manual/html_node/Static-Usage.html#Static-Usage
#
# targets …: target-pattern: prereq-patterns …
# recipe
# …
$(riscvdv-test-asms): $(TESTS-DIR)/%/$(asm-stem): \
$(INSTR-GEN-BUILD-STAMP) $(TESTLIST) scripts/run_instr_gen.py
@echo Running randomized test generator to create assembly file $@
$(verb)env PYTHONPATH=$(PYTHONPATH) \
scripts/run_instr_gen.py \
--dir-metadata $(METADATA-DIR) \
--test-dot-seed $*

View file

@ -15,7 +15,7 @@ import riscvdv_interface
from scripts_lib import run_one, format_to_cmd
from test_entry import read_test_dot_seed, get_test_entry
from metadata import RegressionMetadata
from test_run_result import TestRunResult, Failure_Modes
from test_run_result import TestRunResult, Failure_Modes, TestType
import logging
logger = logging.getLogger(__name__)
@ -31,7 +31,10 @@ def _main() -> int:
md = RegressionMetadata.construct_from_metadata_dir(args.dir_metadata)
trr = TestRunResult.construct_from_metadata_dir(args.dir_metadata, f"{tds[0]}.{tds[1]}")
testopts = get_test_entry(trr.testname) # From testlist.yaml
testopts = get_test_entry(testname=trr.testname,
testlist=(md.ibex_riscvdv_testlist
if (trr.testtype == TestType.RISCVDV) else
md.directed_test_data))
if not os.path.exists(trr.binary):
raise RuntimeError(

View file

@ -8,6 +8,7 @@ import shlex
import subprocess
import sys
import pickle
import yaml
import pathlib3x as pathlib
from io import IOBase
from typing import Dict, TextIO, Optional, Union, List
@ -247,6 +248,17 @@ def format_to_str(arg: any) -> str:
raise TypeError("Couldn't format element to str!")
def read_yaml(yaml_file: pathlib.Path):
"""Read YAML file to a dictionary."""
with yaml_file.open("r") as f:
try:
yaml_data = yaml.safe_load(f)
except yaml.YAMLError as exc:
logging.error(exc)
sys.exit(1)
return yaml_data
class testdata_cls():
"""Baseclass for testdata to hold common methods....

View file

@ -4,13 +4,13 @@
import argparse
import re
import logging
from typing import Dict, List, Tuple
import pathlib3x as pathlib
# Import riscv_trace_csv and lib from _DV_SCRIPTS before putting sys.path back
# as it started.
from setup_imports import _CORE_IBEX_RISCV_DV_EXTENSION, _RISCV_DV
import lib as riscvdv_lib # type: ignore
import scripts_lib
import logging
logger = logging.getLogger(__name__)
TestEntry = Dict[str, object]
@ -30,13 +30,12 @@ def read_test_dot_seed(arg: str) -> TestAndSeed:
return (match.group(1), int(match.group(2), 10))
def get_test_entry(testname: str) -> TestEntry:
matched_list = [] # type: TestEntries
testlist = _CORE_IBEX_RISCV_DV_EXTENSION/'testlist.yaml'
def get_test_entry(testname: str, testlist: pathlib.Path) -> TestEntry:
riscvdv_lib.process_regression_list(testlist, 'all', 0, matched_list, _RISCV_DV)
yaml_data = scripts_lib.read_yaml(testlist)
for entry in matched_list:
for entry in yaml_data:
if entry['test'] == testname:
return entry
raise RuntimeError('No matching test entry for {!r}'.format(testname))

View file

@ -16,6 +16,13 @@ import logging
logger = logging.getLogger(__name__)
class TestType(Enum):
"""Type of the test."""
RISCVDV = 0
DIRECTED = 1
class Failure_Modes(Enum):
"""Descriptive enum for the mode in which a test fails"""
@ -43,6 +50,7 @@ class TestRunResult(scripts_lib.testdata_cls):
failure_message: Optional[str] = None
timeout_s: Optional[int] = None
testtype: Optional[TestType] = None
testdotseed: Optional[str] = None
testname: Optional[str] = None # Name of test
seed: Optional[int] = None # Seed of test
@ -56,6 +64,9 @@ class TestRunResult(scripts_lib.testdata_cls):
rtl_test: Optional[str] = None
sim_opts: Optional[str] = None
# Directed Test specific parameters
directed_data: Optional[dict] = None
dir_test: Optional[pathlib.Path] = None
assembly: Optional[pathlib.Path] = None # Path to assembly file
objectfile: Optional[pathlib.Path] = None

View file

@ -1,3 +1,37 @@
###############################################################################
######## EXAMPLE OF VARIABLE DUMPING ############
# This target depends on the vendored in code in $(GEN_DIR). It also depends on the
# values of the following Makefile variables (we want to regenerate things if,
# for example, the simulator changes).
# To achieve this variable tracking, we dump each of the variables to a Makefile
# fragment and try to load it up the next time around. This done with the
# utility function "dump-vars" at the end of the recipe.
#
# To create the dependency, we must do the following two things before each
# target:
#
# First, load up the saved variable values from the last time around. If this
# fails, it's no problem: we'll assume that the previous run either doesn't
# exist or something went wrong.
# ig-build-vars-path := $(BUILD-DIR)/.instr_gen.vars.mk
# -include $(ig-build-vars-path)
# Next, compare the current variables to those we just loaded. This uses the
# utility function "vars-prereq". It creates a variable which evaluates to the
# (phony) FORCE if the two sets of variables do not match.
#
# Note that we define it with '=', not ':=', so we don't evaluate if we're not
# trying to run the instr_gen_build target.
# instr-gen-build-vars-prereq = \
# $(call vars-prereq, \
# gen, \
# building instruction generator, \
# $(instr-gen-build-var-deps))
# Finally, $(instr-gen-build-vars-prereq) becomes a dependency of our target.
################## END EXAMPLE ###################
###############################################################################
# Utility functions.
#

149
dv/uvm/core_ibex/wrapper.mk Normal file
View file

@ -0,0 +1,149 @@
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
###############################################################################
all: collect_results $(if $(filter 1,$(COV)),merge_cov,)
# Build Stages
.PHONY: core_config # riscvdv
.PHONY: instr_gen_build
.PHONY: instr_gen_run
.PHONY: compile_riscvdv_tests
.PHONY: compile_directed_tests # directed
.PHONY: rtl_tb_compile # simulation
.PHONY: rtl_sim_run
.PHONY: check_logs # post-checks and coverage merging
.PHONY: riscv_dv_fcov
.PHONY: merge_cov
.PHONY: collect_results
###############################################################################
# Environment variables
TOOLCHAIN := ${RISCV_TOOLCHAIN}
EXT_DIR := riscv_dv_extension
export IBEX_ROOT := $(realpath ../../../)
export PRJ_DIR := $(realpath ../../..)
export LOWRISC_IP_DIR := $(realpath ${PRJ_DIR}/vendor/lowrisc_ip)
# Needed for tcl files that are used with Cadence tools.
export dv_root := $(realpath ../../../vendor/lowrisc_ip/dv)
export DUT_TOP := ibex_top
###############################################################################
# Here we express the different build artifacts that the Makefile uses to
# establish the dependency tree, as well as which jobs depend on which
# top-level configuration knobs when deciding what to rebuild.
# Use build artifacts as targets where appropriate, otherwise use stamp-files.
# TODO Evaluate input variables to more-cleverly schedule partial-rebuilds
# This would allow us to use Make to handle build scheduling and to calculate rebuilds,
# while keeping all the structured-data in the land of Python.
-include scripts/get_meta.mk
OUT-DIR := $(call get-meta,dir_out)
TESTS-DIR := $(call get-meta,dir_tests)
BUILD-DIR := $(call get-meta,dir_build)
RUN-DIR := $(call get-meta,dir_run)
METADATA-DIR := $(call get-meta,dir_metadata)
# This is a list of directories that are automatically generated by some
# targets. To ensure the directory has been built, add an order-only dependency
# (with the pipe symbol before it) on the directory name and add the directory
# to this list.
$(BUILD-DIR):
@mkdir -p $@
riscvdv-ts := $(call get-meta,riscvdv_tds)
directed-ts := $(call get-meta,directed_tds)
asm-stem := test.S
bin-stem := test.bin
rtl-sim-logfile := rtl_sim.log
trr-stem := trr.yaml
riscvdv-dirs = $(foreach ts,$(riscvdv-ts),$(TESTS-DIR)/$(ts)/)
riscvdv-test-asms = $(addsuffix $(asm-stem),$(riscvdv-dirs))
riscvdv-test-bins = $(addsuffix $(bin-stem),$(riscvdv-dirs))
directed-dirs = $(foreach ts,$(directed-ts),$(TESTS-DIR)/$(ts)/)
directed-test-bins = $(addsuffix $(bin-stem),$(directed-dirs))
test-bins := $(riscvdv-test-bins) $(directed-test-bins)
ts-dirs := $(riscvdv-dirs) $(directed-dirs)
rtl-sim-logs = $(addsuffix $(rtl-sim-logfile),$(ts-dirs))
comp-results = $(addsuffix $(trr-stem),$(ts-dirs))
###############################################################################
# Other groups of files we may depend on are...
# A variable containing a file list for the riscv-dv vendored-in module.
# Depending on these files gives a safe over-approximation that will ensure we
# rebuild things if that module changes.
GEN_DIR := $(realpath ../../../vendor/google_riscv-dv)
riscv-dv-files := \
$(shell find $(GEN_DIR) -type f)
all-verilog = \
$(shell find ../../../rtl -name '*.v' -o -name '*.sv' -o -name '*.svh') \
$(shell find ../.. -name '*.v' -o -name '*.sv' -o -name '*.svh')
all-cpp = \
$(shell find ../.. -name '*.cc' -o -name '*.h')
# The compiled ibex testbench (obviously!) also depends on the design and the
# DV code. The clever way of doing this would be to look at a dependency
# listing generated by the simulator as a side-effect of doing the compile (a
# bit like using the -M flags with a C compiler). Unfortunately, that doesn't
# look like it's particularly easy, so we'll just depend on every .v, .sv or
# .svh file in the dv or rtl directories. Note that this variable is set with
# '=', rather than ':='. This means that we don't bother running the find
# commands unless we need the compiled testbench.
-include scripts/util.mk # VARIABLE DUMPING UTILS (see file for example)
###############################################################################
###############################################################################
# Include steps to build riscv-dv, then run to generate test.S files
-include scripts/riscvdv.mk
###############################################################################
# Compile all test assembly/sources
# This has different targets/dependencies because the directed tests may not
# follow an identical test.S-in-the-test-dir format.
#
# We don't explicitly track dependencies on the RISCV toolchain here.
compile_riscvdv_tests: $(riscvdv-test-bins)
$(riscvdv-test-bins): $(TESTS-DIR)/%/test.bin: \
$(TESTS-DIR)/%/test.S scripts/compile_test.py
@echo Compiling riscvdv test assembly to create binary at $@
$(verb)env PYTHONPATH=$(PYTHONPATH) \
scripts/compile_test.py \
--dir-metadata $(METADATA-DIR) \
--test-dot-seed $*
# NB. The directed test builder does not (yet) depend on the directed sources
compile_directed_tests: $(directed-test-bins)
$(directed-test-bins): scripts/compile_test.py
@echo Compiling directed test to create binary at $@
$(verb)env PYTHONPATH=$(PYTHONPATH) \
scripts/compile_test.py \
--dir-metadata $(METADATA-DIR) \
--test-dot-seed $(shell basename $(dir $@))
###############################################################################
# Include rtl-simulation and logfile generation steps
-include scripts/ibex_sim.mk
###############################################################################
# Extras (for convenience)
.PHONY: prettify
prettify:
@./scripts/prettify.sh
.PHONY: dump
dump:
@./scripts/objdump.sh