# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0

.SUFFIXES:

# Explicitly ask for the bash shell
SHELL := bash

###############################################################################
# CONFIGURATION KNOBS

# Seed for instruction generator and RTL simulation
#
# By default, SEED is set to a different value on each run by picking a random
# value in the Makefile. For overnight testing, a sensible seed might be
# something like the output of "date +%y%m%d". For regression testing, you'll
# need to make sure that a the seed for a failed test "sticks" (so we don't
# start passing again without fixing the bug).
SEED                := $(shell echo $$RANDOM)

# Enable waveform dumping
WAVES               := 0
# Enable coverage dump
COV                 := 0
# RTL simulator (xlm, vcs, questa, dsim, )
SIMULATOR           := xlm
# ISS (spike, ovpsim)
ISS                 := spike
# Test name (default: full regression)
TEST                := all
TESTLIST            := riscv_dv_extension/testlist.yaml
# Verbose logging
VERBOSE             := 0
# Number of iterations for each test, assign a non-empty value to override the
# iteration count in the test list
ITERATIONS          :=
# Pass/fail signature address at the end of test (see riscv_dv handshake documentation)
SIGNATURE_ADDR      := 8ffffffc

### Ibex top level parameters ###
IBEX_CONFIG         := opentitan

###############################################################################

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 := dut

# 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?

###############################################################################

.PHONY: clean
clean:
	rm -f $(EXT_DIR)/riscv_core_setting.sv
	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)")

### 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 ###

###############################################################################
# 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
