Skip to content
Merged
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
8 changes: 7 additions & 1 deletion src/dvsim/flow/sim.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from dvsim.sim_results import 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

# This affects the bucketizer failure report.
Expand Down Expand Up @@ -684,20 +685,25 @@ def make_test_result(tr) -> TestResult | None:

# --- Coverage ---
coverage: dict[str, float | None] = {}
coverage_model = None
if self.cov_report_deploy:
for k, v in self.cov_report_deploy.cov_results_dict.items():
try:
coverage[k.lower()] = float(v.rstrip("% "))
except (ValueError, TypeError, AttributeError):
coverage[k.lower()] = None

coverage_model = get_sim_tool_plugin(self.tool).get_coverage_metrics(
raw_metrics=coverage,
)

# --- Final result ---
return FlowResults(
block=block,
tool=tool,
timestamp=timestamp,
stages=stages,
coverage=coverage,
coverage=coverage_model,
passed=total_passed,
total=total_runs,
percent=100.0 * total_passed / total_runs if total_runs else 0.0,
Expand Down
71 changes: 70 additions & 1 deletion src/dvsim/report/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,75 @@ class TestStage(BaseModel):
"""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."""

Expand All @@ -109,7 +178,7 @@ class FlowResults(BaseModel):

stages: Mapping[str, TestStage]
"""Results per test stage."""
coverage: Mapping[str, float | None]
coverage: CoverageMetrics | None
"""Coverage metrics."""

passed: int
Expand Down
38 changes: 28 additions & 10 deletions src/dvsim/templates/reports/block_report.html
Original file line number Diff line number Diff line change
Expand Up @@ -91,21 +91,39 @@ <h2>Simulation Results: {{ block.name }}</h2>
</span>
</div>

{% macro coverage_stat(cov, kind, label) %}
{% if cov and cov|attr(kind) is not none %}
{% set value = cov|attr(kind) %}
<div class="col">
<ul class="list-group list-group-horizontal">
<li class="list-group-item list-group-item-secondary px-2 py-1">
{{ label }}
</li>
<li class="list-group-item py-1">{{ "%.2f"|format(value) }} %</li>
</ul>
</div>
{% endif %}
{% endmacro %}


{% if coverage %}
<div class="col-4 col-md-6">
<div class="text-center mb-2">Coverage statistics</div>
<div class="container small">
<div class="row g-0 row-cols-1 row-cols-md-2 row-cols-lg-3 row-cols-xl-4">
{% for name, val in coverage.items() %}
<div class="col">
<ul class="list-group list-group-horizontal">
<li class="list-group-item list-group-item-secondary px-2 py-1">
{{ name }}
</li>
<li class="list-group-item py-1">{{ val }}</li>
</ul>
</div>
{% endfor %}
{{ coverage_stat(coverage, "average", "Total") }}

{% set code = coverage.code %}
{{ coverage_stat(code, "average", "code") }}
{{ coverage_stat(coverage, "assertion", "assert") }}
{{ coverage_stat(coverage, "functional", "func") }}

{{ coverage_stat(code, "block", "block") }}
{{ coverage_stat(code, "line_statement", "line") }}
{{ coverage_stat(code, "branch", "branch") }}
{{ coverage_stat(code, "condition_expression", "cond") }}
{{ coverage_stat(code, "toggle", "toggle") }}
{{ coverage_stat(code, "fsm", "FSM") }}
</div>
</div>
</div>
Expand Down
63 changes: 46 additions & 17 deletions src/dvsim/templates/reports/summary_report.html
Original file line number Diff line number Diff line change
Expand Up @@ -88,16 +88,44 @@ <h2>Simulation Results: {{ top.name }}</h2>
</div>
</div>

{% macro coverage_cell(cov, kind) %}
{% if cov and cov|attr(kind) is not none %}
{% set value = cov|attr(kind) %}
<td class="c{{ (value / 10)|int }}">{{ "%.2f"|format(value) }}</td>
{% else %}
<td class="text-muted">-</td>
{% endif %}
{% endmacro %}

<div class="row py-3">
<div class="col">
<table class="table table-sm table-hover">
<thead>
<table class="table table-sm table-hover text-center">
<thead class="table-light align-middle">
<tr>
<th>Block</th>
<th rowspan="2">Block</th>
<th colspan="3">Tests</th>
<th colspan="4" class="bg-secondary text-white">Coverage Summary</th>
<th colspan="6">Code Coverage</th>
</tr>
<tr>
<!-- Tests sub-headers -->
<th>Pass</th>
<th>Total</th>
<th>%</th>
<th>Cov</th>

<!-- Coverage Summary sub-headers -->
<th class="bg-secondary text-white">Overall</th>
<th class="bg-secondary text-white">Code</th>
<th class="bg-secondary text-white">Functional</th>
<th class="bg-secondary text-white">Assertion</th>

<!-- Code Coverage sub-headers -->
<th>Block</th>
<th>Line</th>
<th>Branch</th>
<th>Condition</th>
<th>Toggle</th>
<th>FSM</th>
</tr>
</thead>
<tbody>
Expand All @@ -114,19 +142,20 @@ <h2>Simulation Results: {{ top.name }}</h2>
<td class="c{{ (flow.percent / 10)|int }}">
{{ "%.2f" | format(flow.percent) }}
</td>
<td class="
{% if flow.coverage and flow.coverage.get('score') is not none %}
c{{ (flow.coverage.score / 10)|int }}
{% else %}
text-muted
{% endif %}
">
{% if flow.coverage and flow.coverage.get('score') is not none %}
{{ "%.2f" | format(flow.coverage.score) }}
{% else %}
-
{% endif %}
</td>
{% set cov = flow.coverage %}
{{ coverage_cell(cov, "average") }}

{% set code = cov|attr("code") %}
{{ coverage_cell(code, "average") }}
{{ coverage_cell(cov, "functional") }}
{{ coverage_cell(cov, "assertion") }}

{{ coverage_cell(code, "block") }}
{{ coverage_cell(code, "line_statement") }}
{{ coverage_cell(code, "branch") }}
{{ coverage_cell(code, "condition_expression") }}
{{ coverage_cell(code, "toggle") }}
{{ coverage_cell(code, "fsm") }}
</tr>
{% endfor %}
</tbody>
Expand Down
17 changes: 16 additions & 1 deletion src/dvsim/tool/sim.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@

"""EDA simulation tool interface."""

from collections.abc import Sequence
from collections.abc import Mapping, Sequence
from pathlib import Path
from typing import Protocol, runtime_checkable

from dvsim.report.data import CoverageMetrics

__all__ = ("SimTool",)


Expand Down Expand Up @@ -67,3 +69,16 @@ def get_simulated_time(log_text: Sequence[str]) -> tuple[float, str]:

"""
...

@staticmethod
def get_coverage_metrics(raw_metrics: Mapping[str, float | None] | None) -> CoverageMetrics:
"""Get a CoverageMetrics model from raw coverage data.

Args:
raw_metrics: raw coverage metrics as parsed from the tool.

Returns:
CoverageMetrics model.

"""
...
31 changes: 30 additions & 1 deletion src/dvsim/tool/vcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
"""EDA tool plugin providing VCS support to DVSim."""

import re
from collections.abc import Sequence
from collections.abc import Mapping, Sequence
from pathlib import Path

from dvsim.report.data import CodeCoverageMetrics, CoverageMetrics

__all__ = ("VCS",)


Expand Down Expand Up @@ -103,3 +105,30 @@ def get_simulated_time(log_text: Sequence[str]) -> tuple[float, str]:

msg = "Simulated time not found in the log."
raise RuntimeError(msg)

@staticmethod
def get_coverage_metrics(raw_metrics: Mapping[str, float | None] | None) -> CoverageMetrics:
"""Get a CoverageMetrics model from raw coverage data.

Args:
raw_metrics: raw coverage metrics as parsed from the tool.

Returns:
CoverageMetrics model.

"""
if raw_metrics is None:
return CoverageMetrics(code=None, assertion=None, functional=None)

return CoverageMetrics(
functional=raw_metrics.get("group"),
assertion=raw_metrics.get("assert"),
code=CodeCoverageMetrics(
block=None,
line_statement=raw_metrics.get("line"),
branch=raw_metrics.get("branch"),
condition_expression=raw_metrics.get("cond"),
toggle=raw_metrics.get("toggle"),
fsm=raw_metrics.get("fsm"),
),
)
31 changes: 30 additions & 1 deletion src/dvsim/tool/xcelium.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@

import re
from collections import OrderedDict
from collections.abc import Sequence
from collections.abc import Mapping, Sequence
from pathlib import Path

from dvsim.report.data import CodeCoverageMetrics, CoverageMetrics

__all__ = ("Xcelium",)


Expand Down Expand Up @@ -128,3 +130,30 @@ def get_simulated_time(log_text: Sequence[str]) -> tuple[float, str]:

msg = "Simulated time not found in the log."
raise RuntimeError(msg)

@staticmethod
def get_coverage_metrics(raw_metrics: Mapping[str, float | None] | None) -> CoverageMetrics:
"""Get a CoverageMetrics model from raw coverage data.

Args:
raw_metrics: raw coverage metrics as parsed from the tool.

Returns:
CoverageMetrics model.

"""
if raw_metrics is None:
return CoverageMetrics(code=None, assertion=None, functional=None)

return CoverageMetrics(
functional=raw_metrics.get("covergroup"),
assertion=raw_metrics.get("assertion"),
code=CodeCoverageMetrics(
block=raw_metrics.get("block"),
line_statement=raw_metrics.get("statement"),
branch=raw_metrics.get("branch"),
condition_expression=raw_metrics.get("cond"),
toggle=raw_metrics.get("toggle"),
fsm=raw_metrics.get("fsm"),
),
)
Loading