Skip to content

Commit dbbde97

Browse files
committed
[src] Integrate DVPlan tool commands
- Integrate DVPlan subcommands. These commands will be run only if: - DVPlan tool is available - Coverage is enabled - A "".vplan" file is found in the cuurent sim_cfg.hjson file - When the condition are met, at the end the regression/simulation, the DVPlan tool will: - Check the coverage of the specification - Generate a derived vPlan in the simulation directory - Perform back-annotation of the vPlan with the coverage data - Compute the coverage of the vPlan - Generate a report based on obtained annotated vPlan Signed-off-by: martin-velay <mvelay@lowrisc.org>
1 parent 5a2c7f7 commit dbbde97

7 files changed

Lines changed: 153 additions & 2 deletions

File tree

src/dvsim/job/deploy.py

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@
3030
from dvsim.modes import BuildMode
3131
from dvsim.sim.flow import SimCfg
3232

33-
3433
__all__ = (
3534
"CompileSim",
35+
"CovVPlan",
3636
"Deploy",
3737
)
3838

@@ -1028,3 +1028,88 @@ def _set_attrs(self) -> None:
10281028
self.qual_name = self.target
10291029
self.full_name = f"{self.sim_cfg.name}{self._variant_suffix}:{self.qual_name}"
10301030
self.input_dirs += [self.cov_merge_db_dir]
1031+
1032+
1033+
class CovVPlan(Deploy):
1034+
"""Abstraction for generating a Verification Plan (vPlan) report using DVPlan."""
1035+
1036+
target = "cov_vplan"
1037+
weight = 10
1038+
1039+
def __init__(self, cov_report_job, sim_cfg) -> None:
1040+
self.report_job = cov_report_job
1041+
super().__init__(sim_cfg)
1042+
self.dependencies.append(cov_report_job)
1043+
1044+
def _define_attrs(self) -> None:
1045+
super()._define_attrs()
1046+
self.mandatory_cmd_attrs.update(
1047+
{
1048+
"proj_root": False,
1049+
"vplan": False,
1050+
}
1051+
)
1052+
self.mandatory_misc_attrs.update(
1053+
{
1054+
"dut_instance": False,
1055+
}
1056+
)
1057+
1058+
def _set_attrs(self) -> None:
1059+
self.cov_vplan_dir = f"{self.sim_cfg.scratch_path}/{self.target}"
1060+
1061+
super()._set_attrs()
1062+
self.qual_name = self.target
1063+
self.full_name = f"{self.sim_cfg.name}{self._variant_suffix}:{self.qual_name}"
1064+
1065+
self.prepare_opts = self.sim_cfg.cov_vplan_prepare_opts
1066+
self.process_opts = self.sim_cfg.cov_vplan_process_opts
1067+
1068+
# Calculate IP root and derive the stem used to name output files.
1069+
vplan_path = Path(self.vplan)
1070+
self.ip_root = str(vplan_path.parent.parent)
1071+
vplan_stem = vplan_path.stem
1072+
1073+
# Determine paths for the intermediate HJSON and the final HTML report.
1074+
# Use the vplan filename stem so the outputs are named after the plan
1075+
# (e.g. "rv_timer_vplan_annotated.hjson") rather than the sim name.
1076+
self.annotated_hjson = f"{self.odir}/{vplan_stem}_annotated.hjson"
1077+
self.gen_html = f"{self.odir}/{vplan_stem}_annotated.html"
1078+
self.output_dirs = [self.odir]
1079+
1080+
def _construct_cmd(self) -> str:
1081+
"""Construct the pure bash shell command, bypassing the base Makefile assumption."""
1082+
import shlex
1083+
import shutil
1084+
1085+
if shutil.which("dvplan") is None:
1086+
fallback = (
1087+
"echo 'WARNING: dvplan tool not installed in PATH. Skipping vPlan generation.'"
1088+
)
1089+
return f"/usr/bin/env bash -c {shlex.quote(fallback)}"
1090+
1091+
plugin = get_sim_tool_plugin(tool=self.sim_cfg.tool)
1092+
1093+
def format_opts(opts):
1094+
return " ".join(opts) if isinstance(opts, list) else str(opts)
1095+
1096+
prepare_opts_str = format_opts(self.prepare_opts)
1097+
process_opts_str = format_opts(self.process_opts)
1098+
1099+
prepare_cmd = f"dvplan prepare_vplan {prepare_opts_str} {self.ip_root} {self.vplan} {self.annotated_hjson}"
1100+
prepare_cmd = " ".join(prepare_cmd.split())
1101+
1102+
vendor_tool = f"{self.sim_cfg.tool}_report"
1103+
cov_report_txt = getattr(self.report_job, "cov_report_txt", "")
1104+
report_path = plugin.get_dvplan_report_path(
1105+
cov_report_dir=self.report_job.cov_report_dir, cov_report_txt=cov_report_txt
1106+
)
1107+
1108+
process_cmd = (
1109+
f"dvplan process_results {process_opts_str} --coverage {vendor_tool} {report_path} "
1110+
f"-R {self.gen_html} -s {self.sim_cfg.name} {self.dut_instance} {self.annotated_hjson}"
1111+
)
1112+
process_cmd = " ".join(process_cmd.split())
1113+
1114+
full_command = f"set -e; mkdir -p {self.odir}; {prepare_cmd} && {process_cmd}"
1115+
return f"/usr/bin/env bash -c {shlex.quote(full_command)}"

src/dvsim/sim/data.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,8 @@ class SimFlowResults(BaseModel):
192192
"""Coverage metrics."""
193193
cov_report_page: Path | None
194194
"""Optional path linking to the generated coverage report dashboard page."""
195+
vplan_report_page: Path | None
196+
"""Optional path linking to the generated verification plan (vPlan) reports."""
195197

196198
failed_jobs: BucketedFailures
197199
"""Bucketed failed job overview."""

src/dvsim/sim/flow.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
CovMerge,
2323
CovReport,
2424
CovUnr,
25+
CovVPlan,
2526
RunTest,
2627
)
2728
from dvsim.job.status import JobStatus
@@ -157,6 +158,13 @@ def __init__(self, flow_cfg_file, hjson_data, args, mk_config) -> None:
157158
self.cov_report_dir = ""
158159
self.cov_report_page = ""
159160

161+
# Options for vPlan processing
162+
# dut_instance is the hierarchical testbench path to the DUT (e.g. "tb.dut"),
163+
# distinct from `name`/`qual_name` which identify the sim config itself.
164+
self.dut_instance = ""
165+
self.cov_vplan_prepare_opts = []
166+
self.cov_vplan_process_opts = []
167+
160168
# Options from tools - for building and running tests
161169
self.build_cmd = ""
162170
self.flist_gen_cmd = ""
@@ -550,6 +558,10 @@ def _create_deploy_objects(self) -> None:
550558
self.cov_report_deploy = CovReport(self.cov_merge_deploy, self)
551559
self.deploy += [self.cov_merge_deploy, self.cov_report_deploy]
552560

561+
if getattr(self, "vplan", False):
562+
self.cov_vplan_deploy = CovVPlan(self.cov_report_deploy, self)
563+
self.deploy.append(self.cov_vplan_deploy)
564+
553565
def _cov_analyze(self) -> None:
554566
"""Open GUI tool for coverage analysis.
555567
@@ -821,6 +833,13 @@ def make_test_result(tr) -> TestResult | None:
821833
cov_report_dir = self.cov_report_dir or "cov_report"
822834
cov_report_page = Path(cov_report_dir, self.cov_report_page)
823835

836+
vplan_report_page = None
837+
if getattr(self, "cov_vplan_deploy", None):
838+
vplan_stem = Path(self.vplan).stem
839+
vplan_report_page = (
840+
Path(self.scratch_path) / CovVPlan.target / f"{vplan_stem}_annotated.html"
841+
)
842+
824843
failures = BucketedFailures.from_job_status(results=run_results)
825844
if failures.buckets:
826845
self.errors_seen = True
@@ -835,6 +854,7 @@ def make_test_result(tr) -> TestResult | None:
835854
stages=stages,
836855
coverage=coverage_model,
837856
cov_report_page=cov_report_page,
857+
vplan_report_page=vplan_report_page,
838858
failed_jobs=failures,
839859
passed=total_passed,
840860
total=total_runs,

src/dvsim/sim/report.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,10 +329,12 @@ def render_coverage_table(self, results: SimFlowResults) -> str:
329329
for k, v in results.coverage.flattened().items()
330330
if v is not None
331331
}
332-
if not cov_results and not results.cov_report_page:
332+
if not cov_results and not results.cov_report_page and not results.vplan_report_page:
333333
return ""
334334

335335
report_md = "## Coverage Results"
336+
if results.vplan_report_page:
337+
report_md += f"\n### [vPlan Dashboard]({results.vplan_report_page})"
336338
if results.cov_report_page:
337339
report_md += f"\n### [Coverage Dashboard]({results.cov_report_page})"
338340
if cov_results:

src/dvsim/sim/tool/base.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,17 @@ def set_additional_attrs(deploy: "Deploy") -> None:
9595
9696
"""
9797
...
98+
99+
@staticmethod
100+
def get_dvplan_report_path(cov_report_dir: str, cov_report_txt: str) -> str:
101+
"""Return the path to the coverage report required by DVPlan.
102+
103+
Args:
104+
cov_report_dir: Path to the coverage report directory.
105+
cov_report_txt: Path to the text-based coverage report.
106+
107+
Returns:
108+
Path to the target coverage report file or directory.
109+
110+
"""
111+
...

src/dvsim/sim/tool/vcs.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,17 @@ def set_additional_attrs(deploy: "Deploy") -> None:
145145
deploy: the deploy object to mutate.
146146
147147
"""
148+
149+
@staticmethod
150+
def get_dvplan_report_path(cov_report_dir: str, cov_report_txt: str) -> str:
151+
"""Return the path to the coverage report required by DVPlan.
152+
153+
Args:
154+
cov_report_dir: Path to the coverage report directory.
155+
cov_report_txt: Path to the text-based coverage report.
156+
157+
Returns:
158+
Path to the target coverage report file or directory.
159+
160+
"""
161+
return cov_report_dir

src/dvsim/sim/tool/xcelium.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,3 +170,17 @@ def set_additional_attrs(deploy: "Deploy") -> None:
170170
deploy: the deploy object to mutate.
171171
172172
"""
173+
174+
@staticmethod
175+
def get_dvplan_report_path(cov_report_dir: str, cov_report_txt: str) -> str:
176+
"""Return the path to the coverage report required by DVPlan.
177+
178+
Args:
179+
cov_report_dir: Path to the coverage report directory.
180+
cov_report_txt: Path to the text-based coverage report.
181+
182+
Returns:
183+
Path to the target coverage report file or directory.
184+
185+
"""
186+
return cov_report_dir

0 commit comments

Comments
 (0)