From a9a5d371ca0c48101fdc499e46de57af686da9ca Mon Sep 17 00:00:00 2001 From: James McCorrie Date: Thu, 8 Jan 2026 12:04:10 +0000 Subject: [PATCH 1/3] fix: hacky workaround for sim centric code It seems there sim flow specific assumptions in the deploy object base class, as well as the launcher base class. This issue is a lot broader than the regressions fixed in this commit. The code should be refactored to separate out the sim specific parts from the generic DVSim mechanism. Signed-off-by: James McCorrie --- src/dvsim/job/deploy.py | 4 ++++ src/dvsim/launcher/base.py | 33 ++++++++++++++++++--------------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/dvsim/job/deploy.py b/src/dvsim/job/deploy.py index 83f949b..9a55d8b 100644 --- a/src/dvsim/job/deploy.py +++ b/src/dvsim/job/deploy.py @@ -74,6 +74,10 @@ def __init__(self, sim_cfg: "SimCfg") -> None: # Cross ref the whole cfg object for ease. self.sim_cfg = sim_cfg self.flow = sim_cfg.name + + if not hasattr(self.sim_cfg, "variant"): + self.sim_cfg.variant = "" + self._variant_suffix = f"_{self.sim_cfg.variant}" if self.sim_cfg.variant else "" # A list of jobs on which this job depends. diff --git a/src/dvsim/launcher/base.py b/src/dvsim/launcher/base.py index db42c8b..f361591 100644 --- a/src/dvsim/launcher/base.py +++ b/src/dvsim/launcher/base.py @@ -339,25 +339,28 @@ def _find_patterns(patterns: Sequence[str], line: str) -> Sequence[str] | None: # since it is devoid of the delays incurred due to infrastructure and # setup overhead. - plugin = get_sim_tool_plugin(tool=self.job_spec.tool.name) + time = self.job_runtime_secs + unit = "s" + + if self.job_spec.job_type in [ + "CompileSim", + "RunTest", + "CovUnr", + "CovMerge", + "CovReport", + "CovAnalyze", + ]: + plugin = get_sim_tool_plugin(tool=self.job_spec.tool.name) - try: - time, unit = plugin.get_job_runtime(log_text=lines) - self.job_runtime.set(time, unit) - - except RuntimeError as e: - log.warning( - f"{self.job_spec.full_name}: {e} Using dvsim-maintained job_runtime instead." - ) - self.job_runtime.set(self.job_runtime_secs, "s") - - if self.job_spec.job_type == "RunTest": try: - time, unit = plugin.get_simulated_time(log_text=lines) - self.simulated_time.set(time, unit) + time, unit = plugin.get_job_runtime(log_text=lines) except RuntimeError as e: - log.debug(f"{self.job_spec.full_name}: {e}") + log.warning( + f"{self.job_spec.full_name}: {e} Using dvsim-maintained job_runtime instead." + ) + + self.job_runtime.set(time, unit) if chk_failed or chk_passed: for cnt, line in enumerate(lines): From af9b20d630933df6d579d08f26b5807de3a74e04 Mon Sep 17 00:00:00 2001 From: James McCorrie Date: Thu, 8 Jan 2026 12:50:37 +0000 Subject: [PATCH 2/3] refactor: move sim related modules to top level package Improve the clarity on what is simulation flow specific and what is common. This makes way to fix the linting and formal flows, and creates more clarity on what is required to be implemented to add a new flow. Signed-off-by: James McCorrie --- src/dvsim/cli/admin.py | 6 +- src/dvsim/flow/base.py | 67 +---- src/dvsim/flow/factory.py | 2 +- src/dvsim/flow/one_shot.py | 10 + src/dvsim/report/data.py | 202 +------------- src/dvsim/sim/__init__.py | 5 + src/dvsim/sim/data.py | 259 ++++++++++++++++++ src/dvsim/{flow/sim.py => sim/flow.py} | 92 ++++++- .../{report/generate.py => sim/report.py} | 8 +- src/dvsim/sim/tool/__init__.py | 5 + src/dvsim/{tool/sim.py => sim/tool/base.py} | 2 +- src/dvsim/{ => sim}/tool/vcs.py | 2 +- src/dvsim/{ => sim}/tool/xcelium.py | 2 +- src/dvsim/tool/utils.py | 6 +- tests/tool/test_utils.py | 2 +- 15 files changed, 383 insertions(+), 287 deletions(-) create mode 100644 src/dvsim/sim/__init__.py create mode 100644 src/dvsim/sim/data.py rename src/dvsim/{flow/sim.py => sim/flow.py} (90%) rename src/dvsim/{report/generate.py => sim/report.py} (89%) create mode 100644 src/dvsim/sim/tool/__init__.py rename src/dvsim/{tool/sim.py => sim/tool/base.py} (98%) rename src/dvsim/{ => sim}/tool/vcs.py (98%) rename src/dvsim/{ => sim}/tool/xcelium.py (98%) diff --git a/src/dvsim/cli/admin.py b/src/dvsim/cli/admin.py index 4725f07..0ced939 100644 --- a/src/dvsim/cli/admin.py +++ b/src/dvsim/cli/admin.py @@ -35,9 +35,9 @@ def report() -> None: ) def gen(json_path: Path, output_dir: Path) -> None: """Generate a report from a existing results JSON.""" - from dvsim.report.data import ResultsSummary - from dvsim.report.generate import gen_reports + from dvsim.sim.data import SimResultsSummary + from dvsim.sim.report import gen_reports - results: ResultsSummary = ResultsSummary.load(path=json_path) + results: SimResultsSummary = SimResultsSummary.load(path=json_path) gen_reports(summary=results, path=output_dir) diff --git a/src/dvsim/flow/base.py b/src/dvsim/flow/base.py index f5c5b86..7cbd59e 100644 --- a/src/dvsim/flow/base.py +++ b/src/dvsim/flow/base.py @@ -10,7 +10,6 @@ import sys from abc import ABC, abstractmethod from collections.abc import Mapping, Sequence -from datetime import datetime, timezone from pathlib import Path from typing import TYPE_CHECKING, ClassVar @@ -20,15 +19,12 @@ from dvsim.job.data import CompletedJobStatus from dvsim.launcher.factory import get_launcher_cls from dvsim.logging import log -from dvsim.report.data import FlowResults, IPMeta, ResultsSummary -from dvsim.report.generate import gen_block_report, gen_reports from dvsim.scheduler import Scheduler from dvsim.utils import ( find_and_substitute_wildcards, rm_path, subst_wildcards, ) -from dvsim.utils.git import git_commit_hash if TYPE_CHECKING: from dvsim.job.deploy import Deploy @@ -447,6 +443,7 @@ def deploy_objects(self) -> Sequence[CompletedJobStatus]: interactive=self.interactive, ).run() + @abstractmethod def gen_results(self, results: Sequence[CompletedJobStatus]) -> None: """Generate flow results. @@ -454,68 +451,6 @@ def gen_results(self, results: Sequence[CompletedJobStatus]) -> None: results: completed job status objects. """ - reports_dir = Path(self.scratch_base_path) / "reports" - commit = git_commit_hash(path=Path(self.proj_root)) - url = f"https://github.com/lowrisc/opentitan/tree/{commit}" - - all_flow_results: Mapping[str, FlowResults] = {} - - for item in self.cfgs: - item_results = [ - res - for res in results - if res.block.name == item.name and res.block.variant == item.variant - ] - - flow_results: FlowResults = item._gen_json_results( - run_results=item_results, - commit=commit, - url=url, - ) - - # Convert to lowercase to match filename - block_result_index = ( - f"{item.name}_{item.variant}" if item.variant else item.name - ).lower() - - all_flow_results[block_result_index] = flow_results - - # Generate the block's JSON/HTML reports to the report area. - gen_block_report( - results=flow_results, - path=reports_dir, - ) - - self.errors_seen |= item.errors_seen - - if self.is_primary_cfg: - # The timestamp for this run has been taken with `utcnow()` and is - # stored in a custom format. Store it in standard ISO format with - # explicit timezone annotation. - timestamp = ( - datetime.strptime(self.timestamp, "%Y%m%d_%H%M%S") - .replace(tzinfo=timezone.utc) - .isoformat() - ) - - results_summary = ResultsSummary( - top=IPMeta( - name=self.name, - variant=self.variant, - commit=commit, - branch=self.branch, - url=url, - ), - timestamp=timestamp, - flow_results=all_flow_results, - report_path=reports_dir, - ) - - # Generate all the JSON/HTML reports to the report area. - gen_reports( - summary=results_summary, - path=reports_dir, - ) def has_errors(self) -> bool: """Return error state.""" diff --git a/src/dvsim/flow/factory.py b/src/dvsim/flow/factory.py index 11b88a2..166db15 100644 --- a/src/dvsim/flow/factory.py +++ b/src/dvsim/flow/factory.py @@ -11,9 +11,9 @@ from dvsim.flow.hjson import load_hjson from dvsim.flow.lint import LintCfg from dvsim.flow.rdc import RdcCfg -from dvsim.flow.sim import SimCfg from dvsim.flow.syn import SynCfg from dvsim.logging import log +from dvsim.sim.flow import SimCfg FLOW_HANDLERS = { "cdc": CdcCfg, diff --git a/src/dvsim/flow/one_shot.py b/src/dvsim/flow/one_shot.py index ea8dc15..2142790 100644 --- a/src/dvsim/flow/one_shot.py +++ b/src/dvsim/flow/one_shot.py @@ -6,8 +6,10 @@ import pathlib from collections import OrderedDict +from collections.abc import Sequence from dvsim.flow.base import FlowCfg +from dvsim.job.data import CompletedJobStatus from dvsim.job.deploy import CompileOneShot from dvsim.logging import log from dvsim.modes import BuildMode, Mode @@ -149,3 +151,11 @@ def _create_deploy_objects(self) -> None: # Create initial set of directories before kicking off the regression. self._create_dirs() + + def gen_results(self, results: Sequence[CompletedJobStatus]) -> None: + """Generate flow results. + + Args: + results: completed job status objects. + + """ diff --git a/src/dvsim/report/data.py b/src/dvsim/report/data.py index f4a518c..829d20a 100644 --- a/src/dvsim/report/data.py +++ b/src/dvsim/report/data.py @@ -4,17 +4,11 @@ """Report data models.""" -from collections.abc import Mapping -from datetime import datetime -from pathlib import Path - from pydantic import BaseModel, ConfigDict -from dvsim.sim_results import BucketedFailures - __all__ = ( "IPMeta", - "ResultsSummary", + "ToolMeta", ) @@ -45,197 +39,3 @@ class ToolMeta(BaseModel): """Name of the tool.""" version: str """Version of the tool.""" - - -class TestResult(BaseModel): - """Test result.""" - - model_config = ConfigDict(frozen=True, extra="forbid") - - max_time: float - """Run time.""" - sim_time: float - """Simulation time.""" - - passed: int - """Number of tests passed.""" - total: int - """Total number of tests run.""" - percent: float - """Percentage test pass rate.""" - - -class Testpoint(BaseModel): - """Testpoint.""" - - model_config = ConfigDict(frozen=True, extra="forbid") - - tests: Mapping[str, TestResult] - """Test results.""" - - passed: int - """Number of tests passed.""" - total: int - """Total number of tests run.""" - percent: float - """Percentage test pass rate.""" - - -class TestStage(BaseModel): - """Test stages.""" - - model_config = ConfigDict(frozen=True, extra="forbid") - - testpoints: Mapping[str, Testpoint] - """Results by test point.""" - - passed: int - """Number of tests passed.""" - total: int - """Total number of tests run.""" - percent: float - """Percentage test pass rate.""" - - -class CodeCoverageMetrics(BaseModel): - """CodeCoverage metrics.""" - - model_config = ConfigDict(frozen=True, extra="forbid") - - block: float | None - """Block Coverage (%) - did this part of the code execute?""" - line_statement: float | None - """Line/Statement Coverage (%) - did this part of the code execute?""" - branch: float | None - """Branch Coverage (%) - did this if/case take all paths?""" - condition_expression: float | None - """Condition/Expression Coverage (%) - did the logic evaluate to 0 & 1?""" - toggle: float | None - """Toggle Coverage (%) - did the signal wiggle?""" - fsm: float | None - """FSM Coverage (%) - did the state machine transition?""" - - @property - def average(self) -> float | None: - """Average code coverage (%).""" - all_cov = [ - c - for c in [ - self.line_statement, - self.branch, - self.condition_expression, - self.toggle, - self.fsm, - ] - if c is not None - ] - - if len(all_cov) == 0: - return None - - return sum(all_cov) / len(all_cov) - - -class CoverageMetrics(BaseModel): - """Coverage metrics.""" - - code: CodeCoverageMetrics | None - """Code Coverage.""" - assertion: float | None - """Assertion Coverage.""" - functional: float | None - """Functional coverage.""" - - @property - def average(self) -> float | None: - """Average code coverage (%) or None if there is no coverage.""" - code = self.code.average if self.code is not None else None - all_cov = [ - c - for c in [ - code, - self.assertion, - self.functional, - ] - if c is not None - ] - - if len(all_cov) == 0: - return None - - return sum(all_cov) / len(all_cov) - - -class FlowResults(BaseModel): - """Flow results data.""" - - model_config = ConfigDict(frozen=True, extra="forbid") - - block: IPMeta - """IP block metadata.""" - tool: ToolMeta - """Tool used in the simulation run.""" - timestamp: datetime - """Timestamp for when the test ran.""" - - stages: Mapping[str, TestStage] - """Results per test stage.""" - coverage: CoverageMetrics | None - """Coverage metrics.""" - - failed_jobs: BucketedFailures - """Bucketed failed job overview.""" - - passed: int - """Number of tests passed.""" - total: int - """Total number of tests run.""" - percent: float - """Percentage test pass rate.""" - - @staticmethod - def load(path: Path) -> "FlowResults": - """Load results from JSON file. - - Transform the fields of the loaded JSON into a more useful schema for - report generation. - - Args: - path: to the json file to load. - - """ - return FlowResults.model_validate_json(path.read_text()) - - -class ResultsSummary(BaseModel): - """Summary of results.""" - - model_config = ConfigDict(frozen=True, extra="forbid") - - top: IPMeta - """Meta data for the top level config.""" - - timestamp: datetime - """Run time stamp.""" - - flow_results: Mapping[str, FlowResults] - """Flow results.""" - - report_path: Path - """Path to the report JSON file.""" - - @staticmethod - def load(path: Path) -> "ResultsSummary": - """Load results from JSON file. - - Transform the fields of the loaded JSON into a more useful schema for - report generation. - - Args: - path: to the json file to load. - - Returns: - The loaded ResultsSummary from JSON. - - """ - return ResultsSummary.model_validate_json(path.read_text()) diff --git a/src/dvsim/sim/__init__.py b/src/dvsim/sim/__init__.py new file mode 100644 index 0000000..4d8f282 --- /dev/null +++ b/src/dvsim/sim/__init__.py @@ -0,0 +1,5 @@ +# Copyright lowRISC contributors (OpenTitan project). +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +"""Simulation workflow.""" diff --git a/src/dvsim/sim/data.py b/src/dvsim/sim/data.py new file mode 100644 index 0000000..da6beef --- /dev/null +++ b/src/dvsim/sim/data.py @@ -0,0 +1,259 @@ +# Copyright lowRISC contributors (OpenTitan project). +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +"""Simulation data models.""" + +from collections.abc import Mapping +from datetime import datetime +from pathlib import Path + +from pydantic import BaseModel, ConfigDict + +from dvsim.report.data import IPMeta, ToolMeta +from dvsim.sim_results import BucketedFailures + +__all__ = ( + "CodeCoverageMetrics", + "CoverageMetrics", + "SimFlowResults", + "SimResultsSummary", + "TestResult", + "TestStage", + "Testpoint", +) + + +class TestResult(BaseModel): + """Test result.""" + + model_config = ConfigDict(frozen=True, extra="forbid") + + max_time: float + """Run time.""" + sim_time: float + """Simulation time.""" + + passed: int + """Number of tests passed.""" + total: int + """Total number of tests run.""" + percent: float + """Percentage test pass rate.""" + + +class Testpoint(BaseModel): + """Testpoint.""" + + model_config = ConfigDict(frozen=True, extra="forbid") + + tests: Mapping[str, TestResult] + """Test results.""" + + passed: int + """Number of tests passed.""" + total: int + """Total number of tests run.""" + percent: float + """Percentage test pass rate.""" + + +class TestStage(BaseModel): + """Test stages.""" + + model_config = ConfigDict(frozen=True, extra="forbid") + + testpoints: Mapping[str, Testpoint] + """Results by test point.""" + + passed: int + """Number of tests passed.""" + total: int + """Total number of tests run.""" + percent: float + """Percentage test pass rate.""" + + +class CodeCoverageMetrics(BaseModel): + """CodeCoverage metrics.""" + + model_config = ConfigDict(frozen=True, extra="forbid") + + block: float | None + """Block Coverage (%) - did this part of the code execute?""" + line_statement: float | None + """Line/Statement Coverage (%) - did this part of the code execute?""" + branch: float | None + """Branch Coverage (%) - did this if/case take all paths?""" + condition_expression: float | None + """Condition/Expression Coverage (%) - did the logic evaluate to 0 & 1?""" + toggle: float | None + """Toggle Coverage (%) - did the signal wiggle?""" + fsm: float | None + """FSM Coverage (%) - did the state machine transition?""" + + @property + def average(self) -> float | None: + """Average code coverage (%).""" + all_cov = [ + c + for c in [ + self.line_statement, + self.branch, + self.condition_expression, + self.toggle, + self.fsm, + ] + if c is not None + ] + + if len(all_cov) == 0: + return None + + return sum(all_cov) / len(all_cov) + + +class CoverageMetrics(BaseModel): + """Coverage metrics.""" + + code: CodeCoverageMetrics | None + """Code Coverage.""" + assertion: float | None + """Assertion Coverage.""" + functional: float | None + """Functional coverage.""" + + @property + def average(self) -> float | None: + """Average code coverage (%) or None if there is no coverage.""" + code = self.code.average if self.code is not None else None + all_cov = [ + c + for c in [ + code, + self.assertion, + self.functional, + ] + if c is not None + ] + + if len(all_cov) == 0: + return None + + return sum(all_cov) / len(all_cov) + + +class FlowResults(BaseModel): + """Flow results data.""" + + model_config = ConfigDict(frozen=True, extra="forbid") + + block: IPMeta + """IP block metadata.""" + tool: ToolMeta + """Tool used in the simulation run.""" + timestamp: datetime + """Timestamp for when the test ran.""" + + stages: Mapping[str, TestStage] + """Results per test stage.""" + coverage: CoverageMetrics | None + """Coverage metrics.""" + + failed_jobs: BucketedFailures + """Bucketed failed job overview.""" + + passed: int + """Number of tests passed.""" + total: int + """Total number of tests run.""" + percent: float + """Percentage test pass rate.""" + + @staticmethod + def load(path: Path) -> "FlowResults": + """Load results from JSON file. + + Transform the fields of the loaded JSON into a more useful schema for + report generation. + + Args: + path: to the json file to load. + + """ + return FlowResults.model_validate_json(path.read_text()) + + +class SimFlowResults(BaseModel): + """Flow results data.""" + + model_config = ConfigDict(frozen=True, extra="forbid") + + block: IPMeta + """IP block metadata.""" + tool: ToolMeta + """Tool used in the simulation run.""" + timestamp: datetime + """Timestamp for when the test ran.""" + + stages: Mapping[str, TestStage] + """Results per test stage.""" + coverage: CoverageMetrics | None + """Coverage metrics.""" + + failed_jobs: BucketedFailures + """Bucketed failed job overview.""" + + passed: int + """Number of tests passed.""" + total: int + """Total number of tests run.""" + percent: float + """Percentage test pass rate.""" + + @staticmethod + def load(path: Path) -> "FlowResults": + """Load results from JSON file. + + Transform the fields of the loaded JSON into a more useful schema for + report generation. + + Args: + path: to the json file to load. + + """ + return FlowResults.model_validate_json(path.read_text()) + + +class SimResultsSummary(BaseModel): + """Summary of results.""" + + model_config = ConfigDict(frozen=True, extra="forbid") + + top: IPMeta + """Meta data for the top level config.""" + + timestamp: datetime + """Run time stamp.""" + + flow_results: Mapping[str, SimFlowResults] + """Flow results.""" + + report_path: Path + """Path to the report JSON file.""" + + @staticmethod + def load(path: Path) -> "SimResultsSummary": + """Load results from JSON file. + + Transform the fields of the loaded JSON into a more useful schema for + report generation. + + Args: + path: to the json file to load. + + Returns: + The loaded ResultsSummary from JSON. + + """ + return SimResultsSummary.model_validate_json(path.read_text()) diff --git a/src/dvsim/flow/sim.py b/src/dvsim/sim/flow.py similarity index 90% rename from src/dvsim/flow/sim.py rename to src/dvsim/sim/flow.py index 5dde9a3..d9e3f86 100644 --- a/src/dvsim/flow/sim.py +++ b/src/dvsim/sim/flow.py @@ -7,7 +7,7 @@ import fnmatch import sys from collections import OrderedDict, defaultdict -from collections.abc import Sequence +from collections.abc import Mapping, Sequence from datetime import datetime, timezone from pathlib import Path from typing import ClassVar @@ -25,12 +25,24 @@ from dvsim.logging import log from dvsim.modes import BuildMode, Mode, RunMode, find_mode from dvsim.regression import Regression -from dvsim.report.data import FlowResults, IPMeta, Testpoint, TestResult, TestStage, ToolMeta +from dvsim.sim.data import ( + IPMeta, + SimFlowResults, + SimResultsSummary, + Testpoint, + TestResult, + TestStage, + ToolMeta, +) +from dvsim.sim.report import gen_block_report, gen_reports from dvsim.sim_results import BucketedFailures, SimResults from dvsim.test import Test from dvsim.testplan import Testplan from dvsim.tool.utils import get_sim_tool_plugin from dvsim.utils import TS_FORMAT, rm_path +from dvsim.utils.git import git_commit_hash + +__all__ = ("SimCfg",) # This affects the bucketizer failure report. _MAX_UNIQUE_TESTS = 5 @@ -574,13 +586,83 @@ def cov_unr(self) -> None: for item in self.cfgs: item._cov_unr() + def gen_results(self, results: Sequence[CompletedJobStatus]) -> None: + """Generate flow results. + + Args: + results: completed job status objects. + + """ + reports_dir = Path(self.scratch_base_path) / "reports" + commit = git_commit_hash(path=Path(self.proj_root)) + url = f"https://github.com/lowrisc/opentitan/tree/{commit}" + + all_flow_results: Mapping[str, SimFlowResults] = {} + + for item in self.cfgs: + item_results = [ + res + for res in results + if res.block.name == item.name and res.block.variant == item.variant + ] + + flow_results: SimFlowResults = item._gen_json_results( + run_results=item_results, + commit=commit, + url=url, + ) + + # Convert to lowercase to match filename + block_result_index = ( + f"{item.name}_{item.variant}" if item.variant else item.name + ).lower() + + all_flow_results[block_result_index] = flow_results + + # Generate the block's JSON/HTML reports to the report area. + gen_block_report( + results=flow_results, + path=reports_dir, + ) + + self.errors_seen |= item.errors_seen + + if self.is_primary_cfg: + # The timestamp for this run has been taken with `utcnow()` and is + # stored in a custom format. Store it in standard ISO format with + # explicit timezone annotation. + timestamp = ( + datetime.strptime(self.timestamp, "%Y%m%d_%H%M%S") + .replace(tzinfo=timezone.utc) + .isoformat() + ) + + results_summary = SimResultsSummary( + top=IPMeta( + name=self.name, + variant=self.variant, + commit=commit, + branch=self.branch, + url=url, + ), + timestamp=timestamp, + flow_results=all_flow_results, + report_path=reports_dir, + ) + + # Generate all the JSON/HTML reports to the report area. + gen_reports( + summary=results_summary, + path=reports_dir, + ) + def _gen_json_results( self, run_results: Sequence[CompletedJobStatus], commit: str, url: str, - ) -> FlowResults: - """Generate structured FlowResults from simulation run data. + ) -> SimFlowResults: + """Generate structured SimFlowResults from simulation run data. Args: run_results: completed job status. @@ -700,7 +782,7 @@ def make_test_result(tr) -> TestResult | None: failures = BucketedFailures.from_job_status(results=run_results) # --- Final result --- - return FlowResults( + return SimFlowResults( block=block, tool=tool, timestamp=timestamp, diff --git a/src/dvsim/report/generate.py b/src/dvsim/sim/report.py similarity index 89% rename from src/dvsim/report/generate.py rename to src/dvsim/sim/report.py index fca0794..435f74d 100644 --- a/src/dvsim/report/generate.py +++ b/src/dvsim/sim/report.py @@ -7,7 +7,7 @@ from pathlib import Path from dvsim.logging import log -from dvsim.report.data import FlowResults, ResultsSummary +from dvsim.sim.data import SimFlowResults, SimResultsSummary from dvsim.templates.render import render_static, render_template __all__ = ( @@ -17,7 +17,7 @@ ) -def gen_block_report(results: FlowResults, path: Path) -> None: +def gen_block_report(results: SimFlowResults, path: Path) -> None: """Generate a block report. Args: @@ -47,7 +47,7 @@ def gen_block_report(results: FlowResults, path: Path) -> None: ) -def gen_summary_report(summary: ResultsSummary, path: Path) -> None: +def gen_summary_report(summary: SimResultsSummary, path: Path) -> None: """Generate a summary report. Args: @@ -88,7 +88,7 @@ def gen_summary_report(summary: ResultsSummary, path: Path) -> None: ) -def gen_reports(summary: ResultsSummary, path: Path) -> None: +def gen_reports(summary: SimResultsSummary, path: Path) -> None: """Generate a full set of reports for the given regression run. Args: diff --git a/src/dvsim/sim/tool/__init__.py b/src/dvsim/sim/tool/__init__.py new file mode 100644 index 0000000..f5f58ab --- /dev/null +++ b/src/dvsim/sim/tool/__init__.py @@ -0,0 +1,5 @@ +# Copyright lowRISC contributors (OpenTitan project). +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +"""Simulation Tools.""" diff --git a/src/dvsim/tool/sim.py b/src/dvsim/sim/tool/base.py similarity index 98% rename from src/dvsim/tool/sim.py rename to src/dvsim/sim/tool/base.py index 2807dbb..896ac20 100644 --- a/src/dvsim/tool/sim.py +++ b/src/dvsim/sim/tool/base.py @@ -8,7 +8,7 @@ from pathlib import Path from typing import Protocol, runtime_checkable -from dvsim.report.data import CoverageMetrics +from dvsim.sim.data import CoverageMetrics __all__ = ("SimTool",) diff --git a/src/dvsim/tool/vcs.py b/src/dvsim/sim/tool/vcs.py similarity index 98% rename from src/dvsim/tool/vcs.py rename to src/dvsim/sim/tool/vcs.py index b40cc6e..4948416 100644 --- a/src/dvsim/tool/vcs.py +++ b/src/dvsim/sim/tool/vcs.py @@ -8,7 +8,7 @@ from collections.abc import Mapping, Sequence from pathlib import Path -from dvsim.report.data import CodeCoverageMetrics, CoverageMetrics +from dvsim.sim.data import CodeCoverageMetrics, CoverageMetrics __all__ = ("VCS",) diff --git a/src/dvsim/tool/xcelium.py b/src/dvsim/sim/tool/xcelium.py similarity index 98% rename from src/dvsim/tool/xcelium.py rename to src/dvsim/sim/tool/xcelium.py index b5e62ad..0362f48 100644 --- a/src/dvsim/tool/xcelium.py +++ b/src/dvsim/sim/tool/xcelium.py @@ -9,7 +9,7 @@ from collections.abc import Mapping, Sequence from pathlib import Path -from dvsim.report.data import CodeCoverageMetrics, CoverageMetrics +from dvsim.sim.data import CodeCoverageMetrics, CoverageMetrics __all__ = ("Xcelium",) diff --git a/src/dvsim/tool/utils.py b/src/dvsim/tool/utils.py index 0997f85..eeb3b55 100644 --- a/src/dvsim/tool/utils.py +++ b/src/dvsim/tool/utils.py @@ -5,9 +5,9 @@ """EDA Tool base.""" from dvsim.logging import log -from dvsim.tool.sim import SimTool -from dvsim.tool.vcs import VCS -from dvsim.tool.xcelium import Xcelium +from dvsim.sim.tool.base import SimTool +from dvsim.sim.tool.vcs import VCS +from dvsim.sim.tool.xcelium import Xcelium __all__ = ("get_sim_tool_plugin",) diff --git a/tests/tool/test_utils.py b/tests/tool/test_utils.py index 8389eb5..28c211d 100644 --- a/tests/tool/test_utils.py +++ b/tests/tool/test_utils.py @@ -7,7 +7,7 @@ import pytest from hamcrest import assert_that, equal_to, instance_of -from dvsim.tool.sim import SimTool +from dvsim.sim.tool.base import SimTool from dvsim.tool.utils import _SUPPORTED_SIM_TOOLS, get_sim_tool_plugin __all__ = ("TestEDAToolPlugins",) From 102e7a35e5e950a5173c66a535c06cfc203a26bb Mon Sep 17 00:00:00 2001 From: James McCorrie Date: Thu, 8 Jan 2026 17:16:06 +0000 Subject: [PATCH 3/3] fix: restore the lint flow old style report Signed-off-by: James McCorrie --- src/dvsim/flow/lint.py | 5 +++- src/dvsim/flow/one_shot.py | 51 +++++++++++++++++++++++++++++++++----- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/src/dvsim/flow/lint.py b/src/dvsim/flow/lint.py index 66f7243..a342f67 100644 --- a/src/dvsim/flow/lint.py +++ b/src/dvsim/flow/lint.py @@ -76,7 +76,10 @@ def gen_results_summary(self): keys = self.totals.get_keys(self.report_severities) for cfg in self.cfgs: - name_with_link = cfg._get_results_page_link(self.results_dir) + link_text = self.name.upper() + relative_link = Path(self.results_dir) / self.results_page + + name_with_link = f"[{link_text}]({relative_link})" row = [name_with_link] row += cfg.result_summary.get_counts_md(keys) diff --git a/src/dvsim/flow/one_shot.py b/src/dvsim/flow/one_shot.py index 2142790..c73b3ea 100644 --- a/src/dvsim/flow/one_shot.py +++ b/src/dvsim/flow/one_shot.py @@ -4,9 +4,10 @@ """Class describing a one-shot build configuration object.""" -import pathlib +from abc import abstractmethod from collections import OrderedDict from collections.abc import Sequence +from pathlib import Path from dvsim.flow.base import FlowCfg from dvsim.job.data import CompletedJobStatus @@ -17,9 +18,7 @@ class OneShotCfg(FlowCfg): - """Simple one-shot build flow for non-simulation targets like - linting, synthesis and FPV. - """ + """Simple one-shot build flow for non-simulation targets like linting, synthesis and FPV.""" ignored_wildcards = [*FlowCfg.ignored_wildcards, "build_mode", "index", "test"] @@ -136,8 +135,10 @@ def _print_list(self) -> None: def _create_dirs(self) -> None: """Create initial set of directories.""" for link in self.links: - rm_path(self.links[link]) - pathlib.Path(self.links[link]).mkdir(parents=True) + link_path = Path(self.links[link]) + + rm_path(link_path) + link_path.mkdir(parents=True) def _create_deploy_objects(self) -> None: """Create deploy objects from build modes.""" @@ -152,6 +153,14 @@ def _create_deploy_objects(self) -> None: # Create initial set of directories before kicking off the regression. self._create_dirs() + @abstractmethod + def _gen_results(self): + """Generate results for this config.""" + + @abstractmethod + def gen_results_summary(self): + """Gathers the aggregated results from all sub configs.""" + def gen_results(self, results: Sequence[CompletedJobStatus]) -> None: """Generate flow results. @@ -159,3 +168,33 @@ def gen_results(self, results: Sequence[CompletedJobStatus]) -> None: results: completed job status objects. """ + for item in self.cfgs: + project = item.name + + item_results = [ + res + for res in results + if res.block.name == item.name and res.block.variant == item.variant + ] + + result = item._gen_results(item_results) + + log.info("[results]: [%s]:\n%s\n", project, result) + log.info("[scratch_path]: [%s] [%s]", project, item.scratch_path) + + # TODO: Implement HTML report using templates + + results_dir = Path(self.results_dir) + results_dir.mkdir(exist_ok=True, parents=True) + + # (results_dir / self.results_html_name).write_text( + # md_results_to_html(self.results_title, self.css_file, item.results_md) + # ) + + log.verbose("[report]: [%s] [%s/report.html]", project, item.results_dir) + + self.errors_seen |= item.errors_seen + + if self.is_primary_cfg: + self.gen_results_summary() + # self.write_results(self.results_html_name, self.results_summary_md)