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:
Rupert Swarbrick 2020-02-26 17:29:56 +00:00 committed by Rupert Swarbrick
parent 91d7721fa9
commit 7df14341ef

View file

@ -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 &