Skip to main content

vj_sim/covergroup/
compute.rs

1//! Coverage computation (IEEE 1800-2023 19.11): the weighted average, the
2//! zero-denominator rules and the merge of instances by bin name.
3
4use std::collections::BTreeMap;
5
6use super::coverpoint::Coverage;
7
8/// The result of one `get_coverage` / `get_inst_coverage` query.
9#[derive(Clone, Copy, Debug, PartialEq)]
10pub struct Score {
11    /// The returned coverage number, 0.0 to 100.0.
12    pub percent: f64,
13    /// The `covered` output of the two-argument forms.
14    pub covered: u64,
15    /// The `total` output of the two-argument forms.
16    pub total: u64,
17    /// The item has a nonzero denominator: it takes part in its parent's
18    /// weighted average. Otherwise it is excluded from numerator and
19    /// denominator alike (19.11).
20    pub contributes: bool,
21}
22
23/// What a query returns when its denominator is zero (19.11, rules b and c):
24/// 0.0 for a nonzero weight, 100.0 for a zero weight; both outputs 0 (rule d).
25pub fn empty_score(weight: i64) -> Score {
26    Score {
27        percent: if weight != 0 { 0.0 } else { 100.0 },
28        covered: 0,
29        total: 0,
30        contributes: false,
31    }
32}
33
34/// A coverpoint or cross measured by its own bins (19.11.1, 19.11.2).
35pub fn item_score(weight: i64, cov: Coverage) -> Score {
36    match cov.percent() {
37        Some(percent) => Score {
38            percent,
39            covered: cov.covered,
40            total: cov.total,
41            contributes: true,
42        },
43        None => empty_score(weight),
44    }
45}
46
47/// The weighted average `sum(Wi * Ci) / sum(Wi)` of `parts` (19.11), where
48/// `weight` is the weight of the thing being averaged (a covergroup, or a type)
49/// that decides the zero-denominator value.
50///
51/// Parts that do not contribute are left out entirely; when no contributing
52/// part has a nonzero weight the denominator is zero. `covered` and `total` are
53/// the plain sums over the contributing parts: 19.8 calls them "aggregated
54/// numbers of bins", so weights scale only the percentage.
55pub fn weighted_score(weight: i64, parts: &[(i64, Score)]) -> Score {
56    let mut num = 0.0f64;
57    let mut den = 0.0f64;
58    let mut covered = 0u64;
59    let mut total = 0u64;
60    for (w, s) in parts.iter().filter(|(_, s)| s.contributes) {
61        num += *w as f64 * s.percent;
62        den += *w as f64;
63        covered = covered.saturating_add(s.covered);
64        total = total.saturating_add(s.total);
65    }
66    if den == 0.0 {
67        return empty_score(weight);
68    }
69    Score {
70        percent: num / den,
71        covered,
72        total,
73        contributes: true,
74    }
75}
76
77/// The coverage of one item merged over instances (19.11.3,
78/// `type_option.merge_instances`). `views` holds, per instance, its `at_least`
79/// and its bins with hit counts. Bins are the same bin when their names are:
80/// the hit counts add up, and a bin is covered when the sum reaches the
81/// largest `at_least` of all instances (19.11.1).
82pub fn merge_bins(views: &[(u64, Vec<(String, u64)>)]) -> Coverage {
83    let at_least = views.iter().map(|(a, _)| *a).max().unwrap_or(1);
84    let mut union: BTreeMap<&str, u64> = BTreeMap::new();
85    for (_, bins) in views {
86        for (name, hits) in bins {
87            let h = union.entry(name.as_str()).or_insert(0);
88            *h = h.saturating_add(*hits);
89        }
90    }
91    Coverage {
92        covered: union.values().filter(|&&h| h >= at_least).count() as u64,
93        total: union.len() as u64,
94    }
95}