Skip to content

Commit 6f63c4c

Browse files
committed
Adding Makefile, common.mk and Python script to generate run_local_regression.sh
Signed-off-by: Jinalben Patel <jinalben@google.com>
1 parent 250bd19 commit 6f63c4c

4 files changed

Lines changed: 317 additions & 2 deletions

File tree

.github/scripts/gen_ci_testlist.py

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
import yaml
4+
import itertools
5+
import os
6+
import glob
7+
import argparse
8+
9+
WORKFLOW_DIR = ".github/workflows"
10+
11+
def generate_local_regression_script(simulator="vcs", workflow_file=None):
12+
generated_commands = []
13+
14+
# 1. Verify directory exists
15+
if not os.path.exists(WORKFLOW_DIR):
16+
print(f"❌ ERROR: Directory '{WORKFLOW_DIR}' not found.")
17+
print(" Make sure you are running this script from the ROOT directory of your repository.")
18+
return
19+
20+
if workflow_file:
21+
if not os.path.exists(workflow_file):
22+
fallback_path = os.path.join(WORKFLOW_DIR, workflow_file)
23+
if os.path.exists(fallback_path):
24+
workflow_file = fallback_path
25+
else:
26+
print(f"❌ ERROR: Workflow file '{workflow_file}' not found.")
27+
return
28+
workflow_files = [workflow_file]
29+
else:
30+
# 2. Find workflow files
31+
workflow_files = glob.glob(os.path.join(WORKFLOW_DIR, "test-*.yml"))
32+
33+
if not workflow_files:
34+
print(f"❌ ERROR: No files matching 'test-*.yml' found in {WORKFLOW_DIR}.")
35+
return
36+
37+
# Sort workflow files to put 'risc' related workflows at the very end
38+
workflow_files.sort(key=lambda x: (1 if "risc" in os.path.basename(x).lower() else 0, x))
39+
40+
print(f"✅ Found {len(workflow_files)} test workflow files. Parsing matrices...")
41+
42+
for file_path in workflow_files:
43+
with open(file_path, 'r') as f:
44+
try:
45+
workflow = yaml.safe_load(f)
46+
except Exception as e:
47+
print(f" ⚠️ Skipping {file_path}: YAML parsing error: {e}")
48+
continue
49+
50+
if not isinstance(workflow, dict):
51+
continue
52+
53+
jobs = workflow.get('jobs', {})
54+
if not jobs:
55+
print(f" ⚠️ No 'jobs' block found in {file_path}")
56+
continue
57+
58+
for job_name, job_data in jobs.items():
59+
if not isinstance(job_data, dict):
60+
continue
61+
62+
if 'strategy' in job_data and 'matrix' in job_data['strategy']:
63+
matrix = job_data['strategy']['matrix']
64+
if not isinstance(matrix, dict):
65+
continue
66+
67+
# Extract matrix components safely
68+
base_matrix = {k: v for k, v in matrix.items() if k not in ('include', 'exclude')}
69+
70+
# Fix: Ensure all values are treated as lists
71+
for k, v in base_matrix.items():
72+
if not isinstance(v, list):
73+
base_matrix[k] = [v]
74+
75+
excludes = matrix.get('exclude', [])
76+
if not isinstance(excludes, list): excludes = [excludes]
77+
78+
includes = matrix.get('include', [])
79+
if not isinstance(includes, list): includes = [includes]
80+
81+
if not base_matrix:
82+
continue
83+
84+
# Generate Cartesian product of the base matrix
85+
keys, values = zip(*base_matrix.items())
86+
combinations = [dict(zip(keys, v)) for v in itertools.product(*values)]
87+
88+
# Filter out excluded combinations
89+
valid_combos = []
90+
for combo in combinations:
91+
# Skip unresolved GitHub actions syntax which causes bash 'bad substitution' errors
92+
if any("${{" in str(v) for v in combo.values()):
93+
continue
94+
95+
is_excluded = False
96+
for ex in excludes:
97+
if isinstance(ex, dict):
98+
# Check if all key-value pairs in the exclude rule match the combo
99+
if all(str(combo.get(k)) == str(v) for k, v in ex.items()):
100+
is_excluded = True
101+
break
102+
if not is_excluded:
103+
valid_combos.append(combo)
104+
105+
# Add explicitly included combinations
106+
for inc in includes:
107+
if isinstance(inc, dict):
108+
# Check for unresolved syntax in includes as well
109+
if not any("${{" in str(v) for v in inc.values()):
110+
valid_combos.append(inc)
111+
else:
112+
print(f" ⚠️ Skipping dynamic/unparseable include: {inc}")
113+
114+
# Format the bash commands
115+
print(f" -> Extracted {len(valid_combos)} combinations from {os.path.basename(file_path)} (Job: {job_name})")
116+
generated_commands.append(f"\n# =========================================")
117+
generated_commands.append(f"# {os.path.basename(file_path)} - Job: {job_name}")
118+
generated_commands.append(f"# =========================================")
119+
120+
for combo in valid_combos:
121+
# Provide standard script parameters based on your bash script inputs
122+
bus = combo.get('bus', 'axi')
123+
if bus == 'axi4':
124+
bus = 'axi'
125+
elif bus == 'ahb_lite':
126+
bus = 'ahb'
127+
test = combo.get('test', 'hello_world')
128+
coverage = combo.get('coverage', 'all')
129+
priv = combo.get('priv', '1')
130+
cache_waypack = combo.get('cache_waypack', '0')
131+
extra_args = combo.get('tb_extra_args', '')
132+
133+
env_vars = job_data.get('env', {})
134+
dcls_enable = env_vars.get('DCLS_ENABLE', '0')
135+
136+
# Build the command string intelligently
137+
env_prefix = ""
138+
if str(dcls_enable) != '0':
139+
env_prefix += f"DCLS_ENABLE={dcls_enable} "
140+
if extra_args:
141+
env_prefix += f"TB_EXTRA_ARGS='{extra_args}' "
142+
143+
config_str = ", ".join([f"{k}={v}" for k, v in combo.items()])
144+
log_name = f"{os.path.basename(file_path)} ({job_name}) - {config_str}"
145+
safe_log_name = log_name.replace('"', '\\"')
146+
147+
if "uarch" in os.path.basename(file_path):
148+
test_dir = str(test).replace('block/', '')
149+
args_dict = {'COVERAGE_TYPE': coverage}
150+
for k, v in combo.items():
151+
if k in ("tb_extra_args", "test", "coverage"):
152+
continue
153+
val = str(v).replace('block/', '')
154+
args_dict[k.upper()] = val
155+
if k == 'module':
156+
args_dict['COCOTB_RESULTS_FILE'] = f"{val}.xml"
157+
args = " ".join([f"{k}={v}" for k, v in args_dict.items()])
158+
args_str = f" {args}" if args else ""
159+
log_file = f"{test_dir}_{args_dict.get('MODULE', 'module')}.log"
160+
cmd = f"{env_prefix}make -f $RV_ROOT/Makefile run_block_test TEST={test_dir} {args_str} SIMULATOR={simulator} SHELL=/bin/bash | tee {log_file}"
161+
wrapped_cmd = f"{cmd}; if grep -q 'FAIL=0' {log_file} && grep -q 'TESTS=' {log_file}; then echo \"{safe_log_name}\" >> PASS; else echo \"{safe_log_name}\" >> FAIL; fi"
162+
else:
163+
cmd = (
164+
f"{env_prefix}$RV_ROOT/.github/scripts/run_regression_test.sh "
165+
f"./test_results {bus} {test} {coverage} {priv} {cache_waypack} {simulator}"
166+
)
167+
wrapped_cmd = f"{cmd} && echo \"{safe_log_name}\" >> PASS || echo \"{safe_log_name}\" >> FAIL"
168+
169+
generated_commands.append(wrapped_cmd)
170+
171+
# Write to script
172+
output_script = "run_local_regression.sh"
173+
with open(output_script, 'w') as f:
174+
f.write("#!/bin/bash\n")
175+
f.write("export RV_ROOT=`pwd`\n")
176+
f.write("export PATH=/opt/verilator/bin:$PATH\n")
177+
f.write("TIMESTAMP=$(date +\"%Y%m%d_%H%M%S\")\n")
178+
f.write("TARGET_DIR=\"run_regress_$TIMESTAMP\"\n")
179+
f.write("mkdir -p \"$TARGET_DIR\"\n")
180+
f.write("cd \"$TARGET_DIR\"\n")
181+
f.write("\nrm -f PASS FAIL\n")
182+
f.write("touch PASS FAIL\n")
183+
if not generated_commands:
184+
f.write("\n# NO TESTS EXTRACTED. PLEASE CHECK PYTHON SCRIPT LOGS.\n")
185+
else:
186+
f.write("\n".join(generated_commands))
187+
188+
f.write("\n\n# =========================================\n")
189+
f.write("# Regression Summary\n")
190+
f.write("# =========================================\n")
191+
f.write("{\n")
192+
f.write("echo \"\"\n")
193+
f.write("echo \"=========================================\"\n")
194+
f.write("echo \"Regression Summary:\"\n")
195+
f.write("echo \"=========================================\"\n")
196+
f.write("echo \"Passed tests: $(wc -l < PASS)\"\n")
197+
f.write("echo \"Failed tests: $(wc -l < FAIL)\"\n")
198+
f.write("echo \"=========================================\"\n")
199+
f.write("if [ -s FAIL ]; then\n")
200+
f.write(" echo \"The following tests failed:\"\n")
201+
f.write(" cat FAIL\n")
202+
f.write("else\n")
203+
f.write(" echo \"All tests passed!\"\n")
204+
f.write("fi\n")
205+
f.write("} | tee summary.log\n")
206+
f.write("if [ -s FAIL ]; then\n")
207+
f.write(" if [[ \"${BASH_SOURCE[0]}\" == \"${0}\" ]]; then exit 1; else return 1; fi\n")
208+
f.write("else\n")
209+
f.write(" if [[ \"${BASH_SOURCE[0]}\" == \"${0}\" ]]; then exit 0; else return 0; fi\n")
210+
f.write("fi\n")
211+
212+
os.chmod(output_script, 0o755)
213+
print(f"\n✅ Done! Generated local regression script: {output_script}")
214+
215+
if __name__ == "__main__":
216+
parser = argparse.ArgumentParser(description="Generate local regression script")
217+
parser.add_argument("--simulator", default="vcs", help="Simulator to use (default: vcs)")
218+
parser.add_argument("--workflow", default=None, help="Specific workflow file to parse (optional)")
219+
args = parser.parse_args()
220+
generate_local_regression_script(args.simulator, args.workflow)

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ obj_dir
77
*.exe
88
*.swp
99
*.sym
10+
*.fsdb
11+
ucli.key
1012
verilator-build
1113
program.hex
1214
snapshots

Makefile

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#
2+
# SPDX-License-Identifier: Apache-2.0
3+
#
4+
#Example make command to run single test without run_regress_test.sh#
5+
#====================================================================
6+
#make snapshot="caliptra-core" TEST=ecc debug=1 SIMULATOR=vcs
7+
#
8+
#Example make command to run block test#
9+
#====================================================================
10+
#make run_block_test TEST=dcls DEBUG=1 SIMULATOR=vcs
11+
#
12+
#Example make command to run single test(testbench/tests/$TEST)
13+
#with $RV_ROOT/.github/scripts/run_regression_test.sh#
14+
#====================================================================
15+
#make run_reg_test TEST=dcls COVERAGE=all SIMULATOR=vcs
16+
#
17+
#Example make command to run local regression with specific workflow#
18+
#====================================================================
19+
#make run_regression SIMULATOR=vcs WORKFLOW=test-uarch.yml
20+
21+
# Allow snapshot override
22+
target = default
23+
snapshot = $(target)
24+
25+
# Allow tool override
26+
27+
SIMULATOR ?= verilator## Supported: verilator, vcs, xrun, qverilog, riviera
28+
USER_MODE ?= 0
29+
TEST ?= hello_world
30+
COVERAGE ?= 0 ## Supported: all, branch, toggle, functional
31+
DEBUG ?= 0
32+
BUS ?= axi
33+
RESULT_DIR ?= run_reg_test_${SIMULATOR}/$(shell date +'%Y%m%d-%H%M%S')
34+
35+
all: $(SIMULATOR)
36+
37+
# Define BUILD_TEST_DIR if TEST is set
38+
ifdef TEST
39+
BUILD_TEST_DIR := build/$(TEST)/$(shell date +'%Y%m%d-%H%M%S')
40+
endif
41+
42+
clean:
43+
rm -rf *.log *.s *.hex *.dis *.tbl irun* vcs* simv* *.map snapshots \
44+
verilator* *.exe obj* *.o *.sym ucli.key vc_hdrs.h csrc *.csv work \
45+
dataset.asdb library.cfg vsimsa.cfg riviera-build wave.asdb
46+
47+
help:
48+
@echo Make sure the environment variable RV_ROOT is set.
49+
@echo Possible targets: verilator vcs irun vlog riviera help clean all verilator-build irun-build vcs-build riviera-build program.hex
50+
51+
.PHONY: help clean all verilator vcs xrun qverilog riviera irun vlog
52+
53+
# Automatic build directory creation based on TEST
54+
ifdef TEST
55+
ifneq ($(notdir $(CURDIR)),$(BUILD_TEST_DIR))
56+
verilator vcs xrun qverilog riviera irun vlog:
57+
echo $(BUILD_TEST_DIR)
58+
mkdir -p $(BUILD_TEST_DIR)
59+
cd $(BUILD_TEST_DIR) && exec $(MAKE) -f "${RV_ROOT}/tools/Makefile" snapshot=$(snapshot) TEST=$(TEST) USER_MODE=$(USER_MODE) debug=$(DEBUG) COVERAGE=$(COVERAGE) $@
60+
61+
endif
62+
endif
63+
64+
run_reg_test:
65+
.github/scripts/run_regression_test.sh ${RESULT_DIR} ${BUS} $(TEST) $(COVERAGE) $(USER_MODE) 0 ${SIMULATOR}
66+
67+
BUILD_BLOCK_TEST_DIR := run_block/$(TEST)_$(shell date +'%Y%m%d-%H%M%S')
68+
COVERAGE_TYPE := ${COVERAGE}
69+
70+
run_block_test:
71+
echo "starting block test $(TEST)"
72+
mkdir -p $(BUILD_BLOCK_TEST_DIR)
73+
cd $(BUILD_BLOCK_TEST_DIR) && ln -sf $(RV_ROOT)/verification/block/$(TEST) .
74+
$(MAKE) -C $(BUILD_BLOCK_TEST_DIR)/$(TEST) SIM=${SIMULATOR} WAVES=$(DEBUG) COVERAGE_TYPE=$(COVERAGE_TYPE)
75+
76+
BUILD_RESGRESS := run_regress_$(shell date +'%Y%m%d_%H%M%S')
77+
WORKFLOW :=
78+
79+
run_regression:
80+
touch ${BUILD_RESGRESS}.log
81+
echo "starting regression with $(SIMULATOR)" >> ${BUILD_RESGRESS}.log
82+
python3 ${RV_ROOT}/.github/scripts/gen_ci_testlist.py --simulator ${SIMULATOR} $(if $(WORKFLOW),--workflow $(WORKFLOW)) >> ${BUILD_RESGRESS}.log
83+
./run_local_regression.sh |tee run.log
84+
cat run.log >> ${BUILD_RESGRESS}.log
85+
rm run.log

verification/block/common.mk

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ $(info $(shell cocotb-config --makefiles))
55

66
TOPLEVEL_LANG = verilog
77
SIM ?= verilator
8-
WAVES ?= 1
8+
WAVES ?= 0
99

1010
# Paths
1111
CURDIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
@@ -59,7 +59,15 @@ else ifeq ($(SIM), vcs)
5959
ifneq ($(CM_FILE),)
6060
EXTRA_ARGS += -cm_hier $(TEST_DIR)/$(CM_FILE)
6161
endif
62-
EXTRA_ARGS += +incdir+$(CFGDIR) +incdir+$(SRCDIR)/include -assert svaext -cm line+cond+fsm+tgl+branch +vcs+lic+wait
62+
EXTRA_ARGS += +define+FCOV +incdir+$(CFGDIR) +incdir+$(SRCDIR)/include -assert svaext -cm line+cond+fsm+tgl+branch -cg_coverage_control=1 +vcs+lic+wait
63+
COMPILE_ARGS += -kdb
64+
COMPILE_ARGS += -debug_access+all +vcs+fsdbon +vcs+fsdbdumpvars
65+
COMPILE_ARGS += -l vcs.log
66+
67+
ifeq ($(WAVES), 1)
68+
SIM_ARGS += +vcs+fsdbon +vcs+fsdbdumpvars
69+
SIM_ARGS += +fsdbfile+dump.fsdb +fsdb+all=on +fsdb+mda=on
70+
endif
6371
endif
6472

6573
# Produces verilog.dump VCD file

0 commit comments

Comments
 (0)