mirror of
https://github.com/lowRISC/ibex.git
synced 2025-04-24 05:47:36 -04:00
Avoid unneccessary rebuilding in dv/uvm/core_ibex/Makefile
Before this patch, running the Makefile's default target deleted everything and then ran the whole flow. This sometimes does unnecessary work (if I've just changed the design, there's no need to rebuild and re-run the instruction generator). It also definitely won't work with Make's -j flag, since it depends on the targets being built in order. This patch keeps the same stages in the Makefile, but makes each stage generate a stamp file, adding dependencies between the stages. This way, you can make a small change to the design and re-run the simulation without having to generate the random inputs again. This doesn't make much difference if you're running lots of tests with no LSF (since VCS is very slow, its runtime for simulation completely dominates), but it can make a significant difference if you're debugging a single test, have made a change to the design and want to re-run. One significant change is that running 'make' doesn't automatically delete existing files any more. To make this possible (and useful!), we generate random data and test results in a directory keyed by the seed. For example make SEED=123 will generate results in out/seed-123/regr.log (rather than out/regr.log as before). To make sure we rebuild things properly if you change something like the number of iterations or the tests to run, we dump some variables describing the mode in which we were running. If these don't match the nnext time around, we'll rebuild stuff if necessary. Advanced (or hurried) users of the existing Makefile might have done things like change the design and then run make SEED=123 compile rtl_sim Now, the rtl_sim target depends on its logical dependencies. On the plus side, this means that you won't accidentally simulate out-of-date code. On the minus side, cunning tricks to avoid having to re-run stuff after touching a design file won't work. (If you're feeling really determined to do something like that, it's still possible with make -t). The seed-specific stamp files and dumped Make variables go into $(OUT-SEED)/.metadata directory, rather than $(OUT-SEED)/instr_gen or $(OUT-SEED)/rtl_sim. This is because of a review comment (to avoid extra clutter in the output directories).
This commit is contained in:
parent
91d7721fa9
commit
7df14341ef
1 changed files with 257 additions and 49 deletions
|
@ -5,7 +5,22 @@
|
|||
DV_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
|
||||
GEN_DIR := $(realpath ${DV_DIR}/../../../vendor/google_riscv-dv)
|
||||
TOOLCHAIN := ${RISCV_TOOLCHAIN}
|
||||
OUT := "${DV_DIR}/out"
|
||||
|
||||
# Seed for instruction generator and RTL simulation
|
||||
#
|
||||
# By default, SEED is set to a different value on each run by picking a random
|
||||
# value in the Makefile. For overnight testing, a sensible seed might be
|
||||
# something like the output of "date +%y%m%d". For regression testing, you'll
|
||||
# need to make sure that a the seed for a failed test "sticks" (so we don't
|
||||
# start passing again without fixing the bug).
|
||||
SEED := $(shell echo $$RANDOM)
|
||||
|
||||
# This is the top-level output directory. Everything we generate goes in
|
||||
# here. Most generated stuff actually goes in $(OUT)/seed-$(SEED), which allows
|
||||
# us to run multiple times without deleting existing results.
|
||||
OUT := ${DV_DIR}/out
|
||||
OUT-SEED := $(OUT)/seed-$(SEED)
|
||||
|
||||
# Run time options for the instruction generator
|
||||
GEN_OPTS :=
|
||||
# Compile time options for ibex RTL simulation
|
||||
|
@ -26,9 +41,7 @@ ISS_OPTS :=
|
|||
ISA := rv32imc
|
||||
# Test name (default: full regression)
|
||||
TEST := all
|
||||
# Seed for instruction generator and RTL simulation
|
||||
TESTLIST := ${DV_DIR}/riscv_dv_extension/testlist.yaml
|
||||
SEED := -1
|
||||
# Verbose logging
|
||||
VERBOSE :=
|
||||
# Number of iterations for each test, assign a non-zero value to override the
|
||||
|
@ -68,29 +81,28 @@ endif
|
|||
|
||||
SHELL=/bin/bash
|
||||
|
||||
export PRJ_DIR:= $(realpath ${DV_DIR}/../../../../)
|
||||
export PRJ_DIR:= $(realpath ${DV_DIR}/../../../..)
|
||||
|
||||
.PHONY: rtl_sim clean gcc_compile iss_sim
|
||||
all: sim
|
||||
|
||||
all: clean gen gcc_compile iss_sim compile rtl_sim post_compare
|
||||
instr: iss_sim
|
||||
|
||||
instr: gen gcc_compile iss_sim
|
||||
|
||||
sim: compile rtl_sim post_compare
|
||||
sim: post_compare
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf ${OUT}
|
||||
|
||||
# Common options for all targets
|
||||
COMMON_OPTS:=--seed=${SEED} \
|
||||
COMMON_OPTS := $(if $(call equal,$(VERBOSE),1),--verbose,)
|
||||
|
||||
# Options for all targets that depend on the tests we're running.
|
||||
TEST_OPTS := $(COMMON_OPTS) \
|
||||
--seed=${SEED} \
|
||||
--test"=${TEST}" \
|
||||
--testlist=${TESTLIST} \
|
||||
--iterations=${ITERATIONS}
|
||||
|
||||
ifeq ($(VERBOSE), 1)
|
||||
COMMON_OPTS+=--verbose
|
||||
endif
|
||||
|
||||
# Options used for privileged CSR test generation
|
||||
CSR_OPTS=--csr_yaml=${CSR_FILE} \
|
||||
--isa="${ISA}" \
|
||||
|
@ -100,45 +112,208 @@ RISCV_DV_OPTS=--custom_target=${DV_DIR}/riscv_dv_extension \
|
|||
--isa="${ISA}" \
|
||||
--mabi=ilp32 \
|
||||
|
||||
# To avoid cluttering the output directory with stamp files, we place them in
|
||||
# $(metadata).
|
||||
metadata := $(OUT-SEED)/.metadata
|
||||
|
||||
# This is a list of directories that are automatically generated by some
|
||||
# targets. To ensure the directory has been built, add a order-only dependency
|
||||
# (with the pipe symbol before it) on the directory name and add the directory
|
||||
# to this list.
|
||||
gen-dirs := $(OUT) $(OUT-SEED) $(metadata) $(OUT)/rtl_sim
|
||||
|
||||
$(gen-dirs): %:
|
||||
mkdir -p $@
|
||||
|
||||
###############################################################################
|
||||
# Utility functions.
|
||||
#
|
||||
# If VS is a list of variable names, P is a path and X is a string, then $(call
|
||||
# dump-vars,P,X,VS) will expand to a list of 'file' commands that write each
|
||||
# variable to P in Makefile syntax, but with "last-X-" prepended. At the start
|
||||
# of the file, we also define last-X-vars-loaded to 1. You can use this to
|
||||
# check whether there was a dump file at all.
|
||||
#
|
||||
# Note that this doesn't work by expanding to a command. Instead, *evaluating*
|
||||
# dump-vars causes the variables to be dumped.
|
||||
dump-var = $(file >>$(1),last-$(2)-$(3) := $($(3)))
|
||||
dump-vars = $(file >$(1),last-$(2)-vars-loaded := .) \
|
||||
$(foreach name,$(3),$(call dump-var,$(1),$(2),$(name)))
|
||||
|
||||
# equal checks whether two strings are equal, evaluating to '.' if they are and
|
||||
# '' otherwise.
|
||||
both-empty = $(if $(1),,$(if $(2),,.))
|
||||
find-find = $(if $(and $(findstring $(1),$(2)),$(findstring $(2),$(1))),.,)
|
||||
equal = $(or $(call both-empty,$(1),$(2)),$(call find-find,$(1),$(2)))
|
||||
|
||||
# var-differs is used to check whether a variable has changed since it was
|
||||
# dumped. If it has changed, the function evaluates to '.' (with some
|
||||
# whitespace) and prints a message to the console; if not, it evaluates to ''.
|
||||
#
|
||||
# Call it as $(call var-differs,X,TGT,V).
|
||||
var-differs = \
|
||||
$(if $(call equal,$(strip $($(3))),$(strip $(last-$(1)-$(3)))),,\
|
||||
.$(info Repeating $(2) because variable $(3) has changed value.))
|
||||
|
||||
# vars-differ is used to check whether several variables have the same value as
|
||||
# they had when they were dumped. If we haven't loaded the dumpfile, it
|
||||
# silently evaluates to '!'. Otherwise, if all the variables match, it
|
||||
# evaluates to '.'. If not, it evaluates to '.' and prints some messages to the
|
||||
# console explaining why a rebuild is happening.
|
||||
#
|
||||
# Call it as $(call vars-differ,X,TGT,VS).
|
||||
vars-differ-lst = $(foreach v,$(3),$(call var-differs,$(1),$(2),$(v)))
|
||||
vars-differ-sp = \
|
||||
$(if $(last-$(1)-vars-loaded),\
|
||||
$(if $(strip $(call vars-differ-lst,$(1),$(2),$(3))),.,),\
|
||||
!)
|
||||
vars-differ = $(strip $(call vars-differ-sp,$(1),$(2),$(3)))
|
||||
|
||||
# A phony target which can be used to force recompilation.
|
||||
.PHONY: FORCE
|
||||
FORCE:
|
||||
|
||||
# vars-prereq is empty if every variable in VS matches the last run (loaded
|
||||
# with tag X), otherwise it is set to FORCE (which will force a recompile and
|
||||
# might print a message to the console explaining why we're rebuilding TGT).
|
||||
#
|
||||
# Call it as $(call vars-prereq,X,TGT,VS)
|
||||
vars-prereq = $(if $(call vars-differ,$(1),$(2),$(3)),FORCE,)
|
||||
|
||||
###############################################################################
|
||||
# Generate random instructions
|
||||
.SILENT gen:
|
||||
mkdir -p ${OUT}
|
||||
python3 ${GEN_DIR}/run.py \
|
||||
--output=${OUT}/instr_gen ${GEN_OPTS} \
|
||||
#
|
||||
# This depends on the vendored in code in $(GEN_DIR). It also depends on the
|
||||
# values of some variables (we want to regenerate things if, for example, the
|
||||
# simulator changes). Since we're writing out to $(OUT-SEED), we don't have to
|
||||
# depend on the value of SEED. However, we do have to make sure that the
|
||||
# variables whose names are listed in $(gen-var-deps) haven't changed.
|
||||
#
|
||||
# To do this variable tracking, we dump each of the variables to a Makefile
|
||||
# fragment and try to load it up the next time around.
|
||||
gen-var-deps := GEN_OPTS SIMULATOR RISCV_DV_OPTS CSR_OPTS \
|
||||
SIGNATURE_ADDR PMP_REGIONS PMP_GRANULARITY TEST_OPTS
|
||||
|
||||
# Load up the generation stage's saved variable values. If this fails, that's
|
||||
# no problem: we'll assume that the previous run either doesn't exist or
|
||||
# something went wrong.
|
||||
-include $(metadata)/gen-vars.mk
|
||||
|
||||
# gen-vars-prereq is empty if every variable in gen-var-deps matches the last run,
|
||||
# otherwise it is set to FORCE (which will force a recompile). Note that we
|
||||
# define it with '=', not ':=', so we don't evaluate it if we're not trying to
|
||||
# run the gen target.
|
||||
gen-vars-prereq = \
|
||||
$(call vars-prereq,gen,building instruction generator,$(gen-var-deps))
|
||||
|
||||
# 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.
|
||||
#
|
||||
# Note that this is defined with ":=". As a result, we'll always run the find
|
||||
# command exactly once. Wasteful if we're trying to make clean, but much better
|
||||
# than running it for every target otherwise.
|
||||
risc-dv-files := $(shell find $(GEN_DIR) -type f)
|
||||
|
||||
# This actually runs the instruction generator. Note that the rule depends on
|
||||
# the (phony) FORCE target if any variables have changed. If the rule actually
|
||||
# runs, it starts by deleting any existing contents of $(OUT-SEED)/instr_gen.
|
||||
$(metadata)/instr_gen.gen.stamp: \
|
||||
$(gen-vars-prereq) $(risc-dv-files) | $(metadata)
|
||||
@rm -rf $(OUT-SEED)/instr_gen
|
||||
@python3 ${GEN_DIR}/run.py \
|
||||
--output=$(OUT-SEED)/instr_gen ${GEN_OPTS} \
|
||||
--steps=gen \
|
||||
--gen_timeout=${TIMEOUT} \
|
||||
--lsf_cmd="${LSF_CMD}" \
|
||||
--simulator="${SIMULATOR}" \
|
||||
${RISCV_DV_OPTS} \
|
||||
${COMMON_OPTS} \
|
||||
${TEST_OPTS} \
|
||||
${CSR_OPTS} \
|
||||
--sim_opts="+uvm_set_inst_override=riscv_asm_program_gen,ibex_asm_program_gen,"uvm_test_top.asm_gen" \
|
||||
+signature_addr=${SIGNATURE_ADDR} +pmp_num_regions=${PMP_REGIONS} \
|
||||
+pmp_granularity=${PMP_GRANULARITY}";
|
||||
+pmp_granularity=${PMP_GRANULARITY}"
|
||||
$(call dump-vars,$(metadata)/gen-vars.mk,gen,$(gen-var-deps))
|
||||
@touch $@
|
||||
|
||||
# Compile the generated assmebly programs to ELF/BIN
|
||||
gcc_compile:
|
||||
python3 ${GEN_DIR}/run.py \
|
||||
--o=${OUT}/instr_gen ${GEN_OPTS} \
|
||||
.PHONY: gen
|
||||
gen: $(metadata)/instr_gen.gen.stamp
|
||||
|
||||
###############################################################################
|
||||
# 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.
|
||||
$(metadata)/instr_gen.compile.stamp: $(metadata)/instr_gen.gen.stamp
|
||||
@python3 ${GEN_DIR}/run.py \
|
||||
--o=$(OUT-SEED)/instr_gen ${GEN_OPTS} \
|
||||
--steps=gcc_compile \
|
||||
${COMMON_OPTS} \
|
||||
${TEST_OPTS} \
|
||||
--gcc_opts=-mno-strict-align \
|
||||
${RISCV_DV_OPTS} \
|
||||
${RISCV_DV_OPTS} && \
|
||||
touch $@
|
||||
|
||||
# ISS simulation
|
||||
iss_sim:
|
||||
python3 ${GEN_DIR}/run.py \
|
||||
--o=${OUT}/instr_gen ${GEN_OPTS} \
|
||||
.PHONY: gcc_compile
|
||||
gcc_compile: $(metadata)/instr_gen.compile.stamp
|
||||
|
||||
###############################################################################
|
||||
# Run the instruction set simulator
|
||||
#
|
||||
# This (obviously) depends on having compiled the generated programs, so we
|
||||
# don't have to worry about variables that affect the 'gen' stage. However, the
|
||||
# ISS and ISS_OPTS variables do affect the output, so we need to dump them. See
|
||||
# the 'gen' stage for more verbose explanations of how this works.
|
||||
iss-var-deps := ISS ISS_OPTS
|
||||
-include $(metadata)/iss-vars.mk
|
||||
iss-vars-prereq = $(call vars-prereq,iss,running ISS,$(iss-var-deps))
|
||||
|
||||
$(metadata)/instr_gen.iss.stamp: \
|
||||
$(iss-vars-prereq) $(metadata)/instr_gen.compile.stamp
|
||||
@python3 ${GEN_DIR}/run.py \
|
||||
--o=$(OUT-SEED)/instr_gen ${GEN_OPTS} \
|
||||
--steps=iss_sim \
|
||||
${COMMON_OPTS} \
|
||||
${TEST_OPTS} \
|
||||
--iss="${ISS}" \
|
||||
--iss_opts="${ISS_OPTS}" \
|
||||
${RISCV_DV_OPTS} \
|
||||
${RISCV_DV_OPTS}
|
||||
$(call dump-vars,$(metadata)/iss-vars.mk,iss,$(iss-var-deps))
|
||||
@touch $@
|
||||
|
||||
.PHONY: iss_sim
|
||||
iss_sim: $(metadata)/instr_gen.iss.stamp
|
||||
|
||||
|
||||
###############################################################################
|
||||
# Compile ibex core TB
|
||||
compile:
|
||||
mkdir -p ${OUT}/rtl_sim;
|
||||
python3 ./sim.py \
|
||||
#
|
||||
# Note that (unlike everything else) this doesn't depend on the seed: the DUT
|
||||
# doesn't depend on which test we're running!
|
||||
#
|
||||
# It does, however, depend on various variables. These are listed in
|
||||
# compile-var-deps. See the 'gen' stage for more verbose explanations of how
|
||||
# the variable dumping works.
|
||||
#
|
||||
# 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.
|
||||
all-verilog = \
|
||||
$(shell find ../../../rtl -name '*.v' -o -name '*.sv' -o -name '*.svh') \
|
||||
$(shell find ../.. -name '*.v' -o -name '*.sv' -o -name '*.svh')
|
||||
|
||||
compile-var-deps := COMMON_OPTS SIMULATOR COV WAVES COMPILE_OPTS
|
||||
-include $(OUT)/rtl_sim/.compile-vars.mk
|
||||
compile-vars-prereq = $(call vars-prereq,comp,compiling TB,$(compile-var-deps))
|
||||
|
||||
$(call dump-vars-match,$(compile-var-deps),comp)
|
||||
|
||||
$(OUT)/rtl_sim/.compile.stamp: \
|
||||
$(compile-vars-prereq) $(all-verilog) $(risc-dv-files) | $(OUT)/rtl_sim
|
||||
@python3 ./sim.py \
|
||||
--o=${OUT} \
|
||||
--riscv_dv_root=${GEN_DIR} \
|
||||
--steps=compile \
|
||||
|
@ -146,34 +321,67 @@ compile:
|
|||
--simulator="${SIMULATOR}" \
|
||||
--en_cov=${COV} \
|
||||
--en_wave=${WAVES} \
|
||||
--cmp_opts="${COMPILE_OPTS}" \
|
||||
--cmp_opts="${COMPILE_OPTS}"
|
||||
$(call dump-vars,$(OUT)/rtl_sim/.compile-vars.mk,comp,$(compile-var-deps))
|
||||
@touch $@
|
||||
|
||||
# Run ibex RTL simulation with random instructions
|
||||
rtl_sim:
|
||||
mkdir -p ${OUT}/rtl_sim
|
||||
python3 ./sim.py \
|
||||
--o=${OUT} \
|
||||
.PHONY: compile
|
||||
compile: $(OUT)/rtl_sim/.compile.stamp
|
||||
|
||||
###############################################################################
|
||||
# Run ibex RTL simulation with generated programs
|
||||
#
|
||||
# Because we compile a TB once rather than for each seed, we have to copy in
|
||||
# that directory before we start. We make this step (rather than actually
|
||||
# running the test) dependent on having the right variables. That way, we'll
|
||||
# correctly delete the sim directory and re-copy it if necessary.
|
||||
#
|
||||
# Note that the variables we depend on are gen-vars-prereq. We also depend on
|
||||
# COV and WAVES, but these dependencies will come for free from the dependency
|
||||
# on the compiled TB.
|
||||
$(metadata)/rtl_sim.compile.stamp: \
|
||||
$(gen-vars-prereq) $(risc-dv-files) $(OUT)/rtl_sim/.compile.stamp
|
||||
rm -rf $(OUT-SEED)/rtl_sim
|
||||
cp -r $(OUT)/rtl_sim $(OUT-SEED)
|
||||
@touch $@
|
||||
|
||||
# This rule actually runs the simulation. It depends on the copied-in testbench
|
||||
# and also on us having already compiled the test programs.
|
||||
$(metadata)/rtl_sim.run.stamp: \
|
||||
$(metadata)/rtl_sim.compile.stamp $(metadata)/instr_gen.compile.stamp
|
||||
@python3 ./sim.py \
|
||||
--o=$(OUT-SEED) \
|
||||
--riscv_dv_root=${GEN_DIR} \
|
||||
--steps=sim \
|
||||
${COMMON_OPTS} \
|
||||
${TEST_OPTS} \
|
||||
--simulator="${SIMULATOR}" \
|
||||
--en_cov ${COV} \
|
||||
--en_wave ${WAVES} \
|
||||
--lsf_cmd="${LSF_CMD}" \
|
||||
--sim_opts="+signature_addr=${SIGNATURE_ADDR}" \
|
||||
${SIM_OPTS}
|
||||
@touch $@
|
||||
|
||||
# Compare the regression result between ISS and RTL sim
|
||||
post_compare:
|
||||
rm -rf ${OUT}/regr.log
|
||||
python3 ./sim.py \
|
||||
--o=${OUT} \
|
||||
.PHONY: rtl_sim
|
||||
rtl_sim: $(metadata)/rtl_sim.run.stamp
|
||||
|
||||
###############################################################################
|
||||
# Compare ISS and RTL sim results
|
||||
$(OUT-SEED)/regr.log: \
|
||||
$(metadata)/instr_gen.iss.stamp \
|
||||
$(metadata)/rtl_sim.run.stamp
|
||||
@rm -f $@
|
||||
@python3 ./sim.py \
|
||||
--o=$(OUT-SEED) \
|
||||
--steps=compare \
|
||||
${COMMON_OPTS} \
|
||||
${TEST_OPTS} \
|
||||
--simulator="${SIMULATOR}" \
|
||||
--iss="${ISS}"
|
||||
|
||||
.PHONY: post_compare
|
||||
post_compare: $(OUT-SEED)/regr.log
|
||||
|
||||
###############################################################################
|
||||
# Generate functional coverage
|
||||
fcov:
|
||||
python3 ${GEN_DIR}/cov.py \
|
||||
|
@ -190,5 +398,5 @@ cov_vcs:
|
|||
cov_ius:
|
||||
if [ ! -d "${OUT}/rtl_sim/cov_work/scope/merged_cov" ]; \
|
||||
then imc -execcmd "merge -out ${OUT}/rtl_sim/cov_work/scope/merged_cov ${OUT}/rtl_sim/cov_work/scope/test_*"; \
|
||||
fi
|
||||
fi
|
||||
imc -load ${OUT}/rtl_sim/cov_work/scope/merged_cov &
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue