Skip to content

Commit 37f3a62

Browse files
committed
Script line coverage reports for core/pipeline
Signed-off-by: Daniel Bates <Daniel.Bates@cl.cam.ac.uk>
1 parent a058c19 commit 37f3a62

File tree

9 files changed

+170
-27
lines changed

9 files changed

+170
-27
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ jobs:
4747
curl -Ls https://download.opensuse.org/repositories/home:phiwag:edatools/xUbuntu_20.04/Release.key | sudo apt-key add -
4848
sudo sh -c "echo 'deb http://download.opensuse.org/repositories/home:/phiwag:/edatools/xUbuntu_20.04/ /' > /etc/apt/sources.list.d/edatools.list"
4949
sudo apt-get update
50-
sudo apt-get install verilator-4.040
50+
sudo apt-get install verilator-4.200
5151
5252
- name: Install Python dependencies
5353
run: pip3 install setuptools wheel

flows/muntjac_core_tb.core

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ targets:
115115
- "-o muntjac_core"
116116
- "--trace --trace-structs"
117117
- "--assert"
118+
- "--coverage-user --coverage-line"
119+
# coverage-toggle is also available, but is very slow for little gain
118120
- "-O3" # Verilator optimisation
119121
- "-CFLAGS -O3" # compiler optimisation
120122
- "-CFLAGS -DFST_ENABLE" # Either VCD_ENABLE or FST_ENABLE

flows/muntjac_pipeline_tb.core

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ targets:
109109
- "-o muntjac_pipeline"
110110
- "--trace --trace-structs"
111111
- "--assert"
112+
- "--coverage-user --coverage-line"
113+
# coverage-toggle is also available, but is very slow for little gain
112114
- "-O3" # Verilator optimisation
113115
- "-CFLAGS -O3" # compiler optimisation
114116
- "-CFLAGS -DFST_ENABLE" # Either VCD_ENABLE or FST_ENABLE

flows/verilator/src/simulation.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,11 @@ class Simulation {
3636
Simulation(string name) {
3737
this->name = name;
3838
timeout = 1000000;
39+
coverage_on = false;
3940
cycle = 0.0;
4041

4142
args.add_argument("--timeout", "Force end of simulation after fixed number of cycles", ArgumentParser::ARGS_ONE);
43+
args.add_argument("--coverage", "Dump coverage information to a file", ArgumentParser::ARGS_ONE);
4244
args.add_argument("-v", "Display basic logging information as simulation proceeds");
4345
args.add_argument("-vv", "Display detailed logging information as simulation proceeds");
4446
args.add_argument("--help", "Display this information and exit");
@@ -106,6 +108,8 @@ class Simulation {
106108
fst_trace.close();
107109
}
108110
#endif
111+
if (coverage_on)
112+
Verilated::threadContextp()->coveragep()->write(coverage_file.c_str());
109113
}
110114

111115
public:
@@ -137,6 +141,11 @@ class Simulation {
137141

138142
if (args.found_arg("--timeout"))
139143
timeout = std::stoi(args.get_arg("--timeout"));
144+
145+
if (this->args.found_arg("--coverage")) {
146+
coverage_on = true;
147+
coverage_file = this->args.get_arg("--coverage");
148+
}
140149

141150
#ifdef FST_ENABLE
142151
if (args.found_arg("--fst")) {
@@ -196,6 +205,10 @@ class Simulation {
196205
VerilatedFstC fst_trace;
197206
#endif
198207

208+
// Generate coverage report?
209+
bool coverage_on;
210+
string coverage_file;
211+
199212
};
200213

201214

test/coverage/Makefile

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Copyright lowRISC contributors.
2+
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
MUNTJAC_ROOT ?= ../..
6+
7+
SIM ?= $(MUNTJAC_ROOT)/bin/muntjac_core
8+
BUILD_DIR ?= $(MUNTJAC_ROOT)/build/lowrisc_muntjac_core_tb_0.1/sim-verilator
9+
10+
COVERAGE = $(addsuffix .cov, $(TESTS))
11+
COV_TOTAL = total.cov
12+
13+
# Source files we are interested in the coverage of.
14+
# Ignore axi (unused) and tilelink (tested separately).
15+
PIPELINE_SRC = $(notdir $(wildcard $(MUNTJAC_ROOT)/ip/pipeline/rtl/*.sv))
16+
CORE_SRC = $(notdir $(wildcard $(MUNTJAC_ROOT)/ip/core/rtl/*.sv))
17+
FPU_SRC = $(notdir $(wildcard $(MUNTJAC_ROOT)/ip/fpu/rtl/*.sv))
18+
ALL_SRC = $(PIPELINE_SRC) $(CORE_SRC) $(FPU_SRC)
19+
20+
ANNOTATION_DIR = coverage
21+
ANNOTATED_SRC = $(addprefix $(ANNOTATION_DIR)/, $(ALL_SRC))
22+
23+
.PHONY: pipeline_coverage core_coverage fpu_coverage coverage all
24+
all: coverage
25+
coverage: pipeline_coverage core_coverage fpu_coverage
26+
27+
# Simulate one test to generate one coverage report.
28+
# The name of the test is the name of the coverage report without ".cov".
29+
# Ignore errors.
30+
$(COVERAGE):
31+
-$(SIM) --coverage $@ $(basename $@)
32+
33+
# Merge coverage reports from all simulations.
34+
$(COV_TOTAL): $(COVERAGE)
35+
ifndef TESTS
36+
$(error Please set the TESTS variable to a list of binaries to execute)
37+
endif
38+
verilator_coverage -write $@ $^
39+
40+
# Annotate each line of source code with how many times it was reached.
41+
# verilator_coverage claims to have a command line option to tell it where the
42+
# Verilog source is, but I haven't found it, so this needs to run in the
43+
# directory where the simulator was originally built.
44+
# (Using PHONY annotate because we don't know that all files will be annotated.)
45+
.PHONY: annotate
46+
annotate: $(COV_TOTAL)
47+
cd $(BUILD_DIR) && \
48+
verilator_coverage $(CURDIR)/$< --annotate $(CURDIR)/$(ANNOTATION_DIR) \
49+
--annotate-all --annotate-min 1
50+
51+
pipeline_coverage: annotate
52+
@echo -n "ip/pipeline line coverage: "
53+
@python3 $(MUNTJAC_ROOT)/test/coverage/coverage_filter.py --annotation-dir \
54+
$(ANNOTATION_DIR) --files $(PIPELINE_SRC)
55+
56+
core_coverage: annotate
57+
@echo -n "ip/core line coverage: "
58+
@python3 $(MUNTJAC_ROOT)/test/coverage/coverage_filter.py --annotation-dir \
59+
$(ANNOTATION_DIR) --files $(CORE_SRC)
60+
61+
fpu_coverage: annotate
62+
@echo -n "ip/fpu line coverage: "
63+
@python3 $(MUNTJAC_ROOT)/test/coverage/coverage_filter.py --annotation-dir \
64+
$(ANNOTATION_DIR) --files $(FPU_SRC)
65+
66+
.PHONY: clean
67+
clean:
68+
rm -f $(COVERAGE) $(COV_TOTAL)
69+
rm -rf $(ANNOTATION_DIR)

test/coverage/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Coverage
2+
3+
Coverage is a useful way of tracking how well a design has been tested. It works by defining a set of *coverpoints*, and counting how many of them are reached during testing.
4+
5+
Verilator provides us with *line coverage*, where each significant line of SystemVerilog is tracked. We can also define our own *functional coverage* by specifying particular states which we would like to see. See the [TileLink testing](../tilelink) for an example of functional coverage.
6+
7+
Neither of these approaches is perfect: it is possible to execute every line of code while producing an incorrect result, and functional coverage is only as useful as the set of coverpoints we define.
8+
9+
To generate a coverage report for the Muntjac core across a range of test binaries, use:
10+
11+
```
12+
make coverage TESTS="test1 test2 test3"
13+
```
14+
15+
This will produce a report which looks like:
16+
17+
```
18+
ip/pipeline line coverage: 2099/2250 (93%)
19+
ip/core line coverage: 2568/2861 (90%)
20+
ip/fpu line coverage: 483/504 (96%)
21+
```
22+
23+
You can see which coverpoints were reached by inspecting the annotated source files in the `coverage` directory. Each line containing a coverpoint will be prefixed with a number indicating how many times that line was reached during testing.

test/coverage/coverage_filter.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Copyright lowRISC contributors.
2+
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
import argparse
6+
import re
7+
8+
def report_coverage(hit, total):
9+
if total == 0:
10+
print("No coverpoints found")
11+
else:
12+
print(f"{hit}/{total} ({hit/total:.0%})")
13+
14+
15+
def collect_coverage(annotation_dir, files):
16+
coverpoints_total = 0
17+
coverpoints_hit = 0
18+
19+
# All missed coverpoint lines start with "%000000".
20+
# Hit coverpoint lines start with e.g. " 173092".
21+
# Lines with no coverpoints start with " ".
22+
missed = re.compile("^%000000")
23+
hit = re.compile("^ [0-9]{6}")
24+
25+
for filename in files:
26+
try:
27+
with open(annotation_dir + "/" + filename, "r") as f:
28+
for line in f:
29+
if re.match(missed, line):
30+
coverpoints_total += 1
31+
elif re.match(hit, line):
32+
coverpoints_total += 1
33+
coverpoints_hit += 1
34+
except FileNotFoundError:
35+
# Verilator doesn't annotate a file if there are no coverpoints.
36+
# This is not an issue.
37+
pass
38+
39+
return coverpoints_hit, coverpoints_total
40+
41+
42+
def main():
43+
parser = argparse.ArgumentParser(description="Aggregate coverage information from a subset of the available files")
44+
parser.add_argument("--annotation-dir", type=str, required=True,
45+
help="Directory containing Verilator coverage annotations")
46+
parser.add_argument("--files", nargs="+", required=True,
47+
help="List of filenames to accumulate coverage data from")
48+
args = parser.parse_args()
49+
50+
results = collect_coverage(args.annotation_dir, args.files)
51+
report_coverage(*results)
52+
53+
54+
if __name__ == "__main__":
55+
main()

test/tilelink/Makefile

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,6 @@ define config_name
4545
$(word 2,$(subst -, ,$1))
4646
endef
4747

48-
# TODO: add a proper testing command which runs all simulations without coverage.
49-
5048
MUNTJAC_ROOT = ../..
5149
BUILD_DIR = build/lowrisc_tl_test_$(DUT)_0.1/sim-verilator
5250
FUSESOC ?= ~/.local/bin/fusesoc
@@ -99,21 +97,16 @@ $(FCOV_FILES): $(TOTAL_COV)
9997

10098
# Summarise functional coverage results.
10199
functional_coverage: $(FCOV_FILES)
102-
$(eval TOTAL := $(shell grep -Eh ^.[0-9]{6} $^ | wc -l))
103-
$(eval REACHED := $(shell grep -Eh ^.[0-9]{6} $^ | grep -v ^.000000 | wc -l))
104-
@echo "Functional coverage ($(REACHED)/$(TOTAL)) $$(( $(REACHED) * 100 / $(TOTAL) ))%"
100+
@echo -n "Functional coverage: "
101+
@python3 $(MUNTJAC_ROOT)/test/coverage/coverage_filter.py --annotation-dir $(ANNOTATION_DIR) --files $(COVERAGE_SRC)
105102

106103
# Summarise line coverage results.
107104
# Ignore coverage results for all files except the DUT. Depending on how the DUT
108105
# uses its subcomponents, it may not be possible to achieve a high coverage.
109106
# Assume that subcomponents will be tested in isolation elsewhere.
110-
# TODO: this can fail if there are no cover points in the DUT (i.e. it delegates
111-
# to subcomponents).
112107
line_coverage: $(FCOV_FILES)
113-
$(eval FILES := $(ANNOTATION_DIR)/$(DUT).sv)
114-
$(eval TOTAL := $(shell grep -Eh ^.[0-9]{6} $(FILES) | wc -l))
115-
$(eval REACHED := $(shell grep -Eh ^.[0-9]{6} $(FILES) | grep -v ^.000000 | wc -l))
116-
@echo "Line coverage, DUT only ($(REACHED)/$(TOTAL)) $$(( $(REACHED) * 100 / $(TOTAL) ))%"
108+
@echo -n "Line coverage, DUT only: "
109+
@python3 $(MUNTJAC_ROOT)/test/coverage/coverage_filter.py --annotation-dir $(ANNOTATION_DIR) --files $(DUT).sv
117110

118111
.PHONY: clean
119112
clean:

test/tilelink/src/tl_harness.h

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,13 @@ class TileLinkSimulation : public Simulation<DUT> {
3030
public:
3131
TileLinkSimulation(string name, vector<tl_test>& tests) :
3232
Simulation<DUT>(name),
33-
tests(tests),
34-
coverage_file("coverage.dat") {
35-
coverage_on = false;
33+
tests(tests) {
3634
sim_duration = 0;
3735
randomise = false;
3836

3937
this->args.set_description("Usage: " + name + " [simulator args] [tests to run]");
4038
this->args.add_argument("--list-tests", "List all available tests");
4139
this->args.add_argument("--config", "Load host/device configuration from a file", ArgumentParser::ARGS_ONE);
42-
this->args.add_argument("--coverage", "Dump coverage information to a file", ArgumentParser::ARGS_ONE);
4340
this->args.add_argument("--random-seed", "Set the random seed", ArgumentParser::ARGS_ONE);
4441
this->args.add_argument("--run", "Generate random traffic for the given duration (in cycles)", ArgumentParser::ARGS_ONE);
4542
}
@@ -135,9 +132,6 @@ class TileLinkSimulation : public Simulation<DUT> {
135132

136133
this->trace_close();
137134

138-
if (coverage_on)
139-
Verilated::threadContextp()->coveragep()->write(coverage_file.c_str());
140-
141135
cout << "No assertions triggered" << endl;
142136
}
143137

@@ -157,11 +151,6 @@ class TileLinkSimulation : public Simulation<DUT> {
157151
if (this->args.found_arg("--config"))
158152
config_file = this->args.get_arg("--config");
159153

160-
if (this->args.found_arg("--coverage")) {
161-
coverage_on = true;
162-
coverage_file = this->args.get_arg("--coverage");
163-
}
164-
165154
if (this->args.found_arg("--random-seed"))
166155
srand(std::stoi(this->args.get_arg("--random-seed")));
167156

@@ -237,9 +226,6 @@ class TileLinkSimulation : public Simulation<DUT> {
237226
// Configuration of hosts/devices connected to the TileLink network.
238227
string config_file = "configs/default/config.yaml";
239228

240-
bool coverage_on;
241-
string coverage_file;
242-
243229
vector<TileLinkHost*> hosts;
244230
vector<TileLinkDevice*> devices;
245231
};

0 commit comments

Comments
 (0)