Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 86 additions & 1 deletion src/dvsim/job/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

"""Job deployment mechanism."""

import os

Check failure on line 7 in src/dvsim/job/deploy.py

View workflow job for this annotation

GitHub Actions / lint

ruff (F401)

src/dvsim/job/deploy.py:7:8: F401 `os` imported but unused help: Remove unused import: `os`
import pprint
import random
import shlex
Expand All @@ -28,9 +29,9 @@
from dvsim.flow.sim import SimCfg
from dvsim.modes import BuildMode


__all__ = (
"CompileSim",
"CovVPlan",
"Deploy",
)

Expand Down Expand Up @@ -876,3 +877,87 @@
self.qual_name = self.target
self.full_name = f"{self.sim_cfg.name}{self._variant_suffix}:{self.qual_name}"
self.input_dirs += [self.cov_merge_db_dir]


class CovVPlan(Deploy):
"""Abstraction for generating a Verification Plan (vPlan) report using DVPlan."""

target = "cov_vplan"
weight = 10

def __init__(self, cov_report_job, sim_cfg) -> None:
self.report_job = cov_report_job
super().__init__(sim_cfg)
self.dependencies.append(cov_report_job)

def _define_attrs(self) -> None:
super()._define_attrs()
self.mandatory_cmd_attrs.update(
{
"proj_root": False,
"vplan": False,
}
)
self.mandatory_misc_attrs.update(
{
"dut_instance": False,
}
)

def _set_attrs(self) -> None:
self.cov_vplan_dir = self.sim_cfg.cov_vplan_dir
if not self.cov_vplan_dir:
self.cov_vplan_dir = f"{self.sim_cfg.scratch_path}/{self.target}"

super()._set_attrs()
self.qual_name = self.target
self.full_name = f"{self.sim_cfg.name}{self._variant_suffix}:{self.qual_name}"

self.prepare_opts = self.sim_cfg.cov_vplan_prepare_opts
self.process_opts = self.sim_cfg.cov_vplan_process_opts

# Determine paths for the intermediate HJSON and the final HTML report
self.annotated_hjson = f"{self.odir}/{self.sim_cfg.name}_annotated_vplan.hjson"
self.gen_html = f"{self.odir}/{self.sim_cfg.name}_annotated_vplan.html"
self.output_dirs = [self.odir]

# Calculate IP root
vplan_path = Path(self.vplan)
self.ip_root = str(vplan_path.parent.parent)

def _construct_cmd(self) -> str:
"""Construct the pure bash shell command, bypassing the base Makefile assumption."""
import shlex
import shutil

if shutil.which("dvplan") is None:
fallback = (
"echo 'WARNING: dvplan tool not installed in PATH. Skipping vPlan generation.'"
)
return f"/bin/bash -c {shlex.quote(fallback)}"

plugin = get_sim_tool_plugin(tool=self.sim_cfg.tool)

def format_opts(opts):
return " ".join(opts) if isinstance(opts, list) else str(opts)

prepare_opts_str = format_opts(self.prepare_opts)
process_opts_str = format_opts(self.process_opts)

prepare_cmd = f"dvplan prepare_vplan {prepare_opts_str} {self.ip_root} {self.vplan} {self.annotated_hjson}"
prepare_cmd = " ".join(prepare_cmd.split())

vendor_tool = plugin.get_dvplan_parser_name()
cov_report_txt = getattr(self.report_job, "cov_report_txt", "")
report_path = plugin.get_dvplan_report_path(
cov_report_dir=self.report_job.cov_report_dir, cov_report_txt=cov_report_txt
)

process_cmd = (
f"dvplan process_results {process_opts_str} --coverage {vendor_tool} {report_path} "
f"-R {self.gen_html} -s {self.sim_cfg.name} {self.dut_instance} {self.annotated_hjson}"
)
process_cmd = " ".join(process_cmd.split())

full_command = f"set -e; mkdir -p {self.odir}; {prepare_cmd} && {process_cmd}"
return f"/bin/bash -c {shlex.quote(full_command)}"
2 changes: 2 additions & 0 deletions src/dvsim/sim/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ class SimFlowResults(BaseModel):
"""Coverage metrics."""
cov_report_page: Path | None
"""Optional path linking to the generated coverage report dashboard page."""
vplan_report_page: Path | None
"""Optional path linking to the generated verification plan (vPlan) reports."""

failed_jobs: BucketedFailures
"""Bucketed failed job overview."""
Expand Down
16 changes: 16 additions & 0 deletions src/dvsim/sim/flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
CovMerge,
CovReport,
CovUnr,
CovVPlan,
RunTest,
)
from dvsim.job.status import JobStatus
Expand Down Expand Up @@ -158,6 +159,12 @@ def __init__(self, flow_cfg_file, hjson_data, args, mk_config) -> None:
self.cov_report_dir = ""
self.cov_report_page = ""

# Options for vPlan processing
self.cov_vplan_dir = ""
self.dut_instance = ""
self.cov_vplan_prepare_opts = []
self.cov_vplan_process_opts = []

# Options from tools - for building and running tests
self.build_cmd = ""
self.flist_gen_cmd = ""
Expand Down Expand Up @@ -560,6 +567,10 @@ def _create_deploy_objects(self) -> None:
self.cov_report_deploy = CovReport(self.cov_merge_deploy, self)
self.deploy += [self.cov_merge_deploy, self.cov_report_deploy]

if hasattr(self, "vplan") and self.vplan:
self.cov_vplan_deploy = CovVPlan(self.cov_report_deploy, self)
self.deploy.append(self.cov_vplan_deploy)

# Create initial set of directories before kicking off the regression.
self._create_dirs()

Expand Down Expand Up @@ -839,6 +850,10 @@ def make_test_result(tr) -> TestResult | None:
cov_report_dir = self.cov_report_dir or "cov_report"
cov_report_page = Path(cov_report_dir, self.cov_report_page)

vplan_report_page = None
if getattr(self, "cov_vplan_deploy", None):
vplan_report_page = Path(self.cov_vplan_deploy.gen_html)

failures = BucketedFailures.from_job_status(results=run_results)
if failures.buckets:
self.errors_seen = True
Expand All @@ -853,6 +868,7 @@ def make_test_result(tr) -> TestResult | None:
stages=stages,
coverage=coverage_model,
cov_report_page=cov_report_page,
vplan_report_page=vplan_report_page,
failed_jobs=failures,
passed=total_passed,
total=total_runs,
Expand Down
4 changes: 3 additions & 1 deletion src/dvsim/sim/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,10 +329,12 @@ def render_coverage_table(self, results: SimFlowResults) -> str:
for k, v in results.coverage.flattened().items()
if v is not None
}
if not cov_results and not results.cov_report_page:
if not cov_results and not results.cov_report_page and not results.vplan_report_page:
return ""

report_md = "## Coverage Results"
if results.vplan_report_page:
report_md += f"\n### [vPlan Dashboard]({results.vplan_report_page})"
if results.cov_report_page:
report_md += f"\n### [Coverage Dashboard]({results.cov_report_page})"
if cov_results:
Expand Down
24 changes: 24 additions & 0 deletions src/dvsim/sim/tool/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,27 @@ def get_coverage_metrics(raw_metrics: Mapping[str, float | None] | None) -> Cove

"""
...

@staticmethod
def get_dvplan_parser_name() -> str:
"""Return the name of the DVPlan parser for this tool.

Returns:
The DVPlan coverage parser string.

"""
return "synopsys_report"

@staticmethod
def get_dvplan_report_path(cov_report_dir: str, cov_report_txt: str) -> str:
"""Return the path to the coverage report required by DVPlan.

Args:
cov_report_dir: Path to the coverage report directory.
cov_report_txt: Path to the text-based coverage report.

Returns:
Path to the target coverage report file or directory.

"""
return cov_report_txt or cov_report_dir
24 changes: 24 additions & 0 deletions src/dvsim/sim/tool/vcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,27 @@ def get_coverage_metrics(raw_metrics: Mapping[str, float | None] | None) -> Cove
fsm=raw_metrics.get("fsm"),
),
)

@staticmethod
def get_dvplan_parser_name() -> str:
"""Return the name of the DVPlan parser for this tool.

Returns:
The DVPlan coverage parser string.

"""
return "synopsys_report"

@staticmethod
def get_dvplan_report_path(cov_report_dir: str, cov_report_txt: str) -> str:
"""Return the path to the coverage report required by DVPlan.

Args:
cov_report_dir: Path to the coverage report directory.
cov_report_txt: Path to the text-based coverage report.

Returns:
Path to the target coverage report file or directory.

"""
return cov_report_dir
24 changes: 24 additions & 0 deletions src/dvsim/sim/tool/xcelium.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,27 @@ def get_coverage_metrics(raw_metrics: Mapping[str, float | None] | None) -> Cove
fsm=raw_metrics.get("fsm"),
),
)

@staticmethod
def get_dvplan_parser_name() -> str:
"""Return the name of the DVPlan parser for this tool.

Returns:
The DVPlan coverage parser string.

"""
return "cadence_report"

@staticmethod
def get_dvplan_report_path(cov_report_dir: str, cov_report_txt: str) -> str:
"""Return the path to the coverage report required by DVPlan.

Args:
cov_report_dir: Path to the coverage report directory.
cov_report_txt: Path to the text-based coverage report.

Returns:
Path to the target coverage report file or directory.

"""
return f"$(ls -t {cov_report_dir}/report_data/*.report | head -n 1)"
Loading