[dv regr tool] Coverage collection and reporting
- This PR enables coverage collection as a part of the regression run
when --cov switch is passed
- If there are multiple builds as a part of the same DUT, it merges the
coverage across them
- It also merges coverage from previous regressions if the
--cov-merge-previous switch is passed
- Finally, it extracts the high level summary coverage from the VCS
coverage dashboard and prints it as a part of the regression report
Another major update in this PR is - all percentages indicated in a
report table indicated with a '%' sign is automatically colored in the
html report as a heat map, from red for low percentages to green
approaching 100%. This is enabled for regression results as well as
coverage results.
Signed-off-by: Srikrishna Iyer <sriyer@google.com>
diff --git a/util/dvsim/SimCfg.py b/util/dvsim/SimCfg.py
index 8849436..20aa1a4 100644
--- a/util/dvsim/SimCfg.py
+++ b/util/dvsim/SimCfg.py
@@ -44,6 +44,7 @@
self.dump = args.dump
self.max_waves = args.max_waves
self.cov = args.cov
+ self.cov_merge_previous = args.cov_merge_previous
self.profile = args.profile
self.xprop_off = args.xprop_off
self.no_rerun = args.no_rerun
@@ -93,6 +94,12 @@
self.build_list = []
self.run_list = []
self.deploy = []
+ self.cov_merge_deploy = None
+ self.cov_report_deploy = None
+
+ # If is_master_cfg is set, then each cfg will have its own cov_deploy.
+ # Maintain an array of those in cov_deploys.
+ self.cov_deploys = []
# Parse the cfg_file file tree
self.parse_flow_cfg(flow_cfg_file)
@@ -110,7 +117,8 @@
# Make substitutions, while ignoring the following wildcards
# TODO: Find a way to set these in sim cfg instead
ignored_wildcards = [
- "build_mode", "index", "test", "seed", "uvm_test", "uvm_test_seq"
+ "build_mode", "index", "test", "seed", "uvm_test", "uvm_test_seq",
+ "cov_db_dirs"
]
self.__dict__ = find_and_substitute_wildcards(self.__dict__,
self.__dict__,
@@ -343,20 +351,54 @@
build_map[test.build_mode].sub.append(item)
runs.append(item)
+ self.builds = builds
+ self.runs = runs
if self.run_only is True:
self.deploy = runs
else:
self.deploy = builds
+ # Create cov_merge and cov_report objects
+ if self.cov:
+ self.cov_merge_deploy = CovMerge(self)
+ self.cov_report_deploy = CovReport(self)
+ # Generate reports only if merge was successful; add it as a dependency
+ # of merge.
+ self.cov_merge_deploy.sub.append(self.cov_report_deploy)
+
# Create initial set of directories before kicking off the regression.
self._create_dirs()
+ def create_deploy_objects(self):
+ '''Public facing API for _create_deploy_objects().
+ '''
+ super().create_deploy_objects()
+
+ # Also, create cov_deploys
+ if self.cov:
+ for item in self.cfgs:
+ self.cov_deploys.append(item.cov_merge_deploy)
+
+ # deploy additional commands as needed. We do this separated for coverage
+ # since that needs to happen at the end.
+ def deploy_objects(self):
+ '''This is a public facing API, so we use "self.cfgs" instead of self.
+ '''
+ # Invoke the base class method to run the regression.
+ super().deploy_objects()
+
+ # If coverage is enabled, then deploy the coverage tasks.
+ if self.cov:
+ Deploy.deploy(self.cov_deploys)
+
def _gen_results(self):
'''
- The function is called after the regression has completed. It collates the status of
- all run targets and generates a dict. It parses the testplan and maps the generated
- result to the testplan entries to generate a final table (list). It uses the fmt arg
- to dump the final result as a markdown or html.
+ The function is called after the regression has completed. It collates the
+ status of all run targets and generates a dict. It parses the testplan and
+ maps the generated result to the testplan entries to generate a final table
+ (list). It also prints the full list of failures for debug / triage. If cov
+ is enabled, then the summary coverage report is also generated. The final
+ result is in markdown format.
'''
# TODO: add support for html
@@ -393,8 +435,10 @@
regr_results = []
fail_msgs = ""
- (regr_results, fail_msgs) = gen_results_sub(self.deploy, regr_results,
- fail_msgs)
+ deployed_items = self.deploy
+ if self.cov: deployed_items.append(self.cov_merge_deploy)
+ (regr_results, fail_msgs) = gen_results_sub(deployed_items,
+ regr_results, fail_msgs)
# Add title if there are indeed failures
if fail_msgs != "":
@@ -420,6 +464,12 @@
map_full_testplan=self.map_full_testplan)
results_str += "\n"
+ # Append coverage results of coverage was enabled.
+ if self.cov and self.cov_report_deploy.status == "P":
+ results_str += "\n## Coverage Results\n"
+ results_str += "\n### [Coverage Dashboard](cov_report/dashboard.html)\n\n"
+ results_str += self.cov_report_deploy.cov_results
+
# Append failures for triage
self.results_md = results_str + fail_msgs
@@ -432,3 +482,45 @@
# Return only the tables
return results_str
+
+ def _cov_analyze(self):
+ '''Use the last regression coverage data to open up the GUI tool to
+ analyze the coverage.
+ '''
+ cov_analyze_deploy = CovAnalyze(self)
+ try:
+ proc = subprocess.Popen(args=cov_analyze_deploy.cmd,
+ shell=True,
+ close_fds=True)
+ except Exception as e:
+ log.fatal("Failed to run coverage analysis cmd:\n\"%s\"\n%s",
+ cov_analyze_deploy.cmd, e)
+ sys.exit(1)
+
+ def cov_analyze(self):
+ '''Public facing API for analyzing coverage.
+ '''
+ for item in self.cfgs:
+ item._cov_analyze()
+
+ def _publish_results(self):
+ '''Publish coverage results to the opentitan web server.'''
+ super()._publish_results()
+
+ if self.cov:
+ results_server_dir_url = self.results_server_dir.replace(
+ self.results_server_prefix, self.results_server_url_prefix)
+
+ log.info("Publishing coverage results to %s",
+ results_server_dir_url)
+ cmd = self.results_server_cmd + " -m cp -R " + \
+ self.cov_report_deploy.cov_report_dir + " " + self.results_server_dir
+ try:
+ cmd_output = subprocess.run(args=cmd,
+ shell=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT)
+ log.log(VERBOSE, cmd_output.stdout.decode("utf-8"))
+ except Exception as e:
+ log.error("%s: Failed to publish results:\n\"%s\"", e,
+ str(cmd))