Skip to main content

vj_sim/covergroup/
coverpoint.rs

1//! One coverpoint instance: bins, sampling and its coverage (IEEE 1800-2023
2//! 19.5, 19.11).
3
4use std::collections::{HashMap, VecDeque};
5
6use super::bins::{Bin, Matcher, build_bins, fmt_real};
7use super::decl::{BinKind, CoverpointDecl};
8use super::options::{Level, Options, TypeOptions};
9use super::value::{CovError, Eval, Expr, TypeInfo, Val};
10
11/// `covered` bins out of `total` bins (the numerator and denominator the
12/// `get_coverage(ref int covered, ref int total)` overload reports, 19.8).
13#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
14pub struct Coverage {
15    pub covered: u64,
16    pub total: u64,
17}
18
19impl Coverage {
20    /// `100 * covered / total`; `None` when there is nothing to cover, which
21    /// the callers resolve per 19.11 (the item then does not contribute).
22    pub fn percent(&self) -> Option<f64> {
23        (self.total != 0).then(|| 100.0 * self.covered as f64 / self.total as f64)
24    }
25}
26
27/// Something a sample did besides counting.
28#[derive(Clone, Debug, PartialEq, Eq)]
29pub enum CovEvent {
30    /// A value hit an `illegal_bins` bin: a run-time error (19.5.6).
31    IllegalBin {
32        coverpoint: String,
33        bin: String,
34        value: String,
35    },
36}
37
38#[derive(Debug)]
39pub struct Coverpoint {
40    pub name: String,
41    pub ty: TypeInfo,
42    pub options: Options,
43    pub type_options: TypeOptions,
44    expr: Expr,
45    iff: Option<Expr>,
46    ctor: Vec<Val>,
47    pub bins: Vec<Bin>,
48    /// Non-default bins matching exactly one integral value.
49    singles: HashMap<i128, Vec<usize>>,
50    /// Every other non-default, non-empty-set bin.
51    multi: Vec<usize>,
52    /// Transition bins (19.5.2), in declaration order.
53    trans: Vec<usize>,
54    /// `default sequence` bins.
55    default_seqs: Vec<usize>,
56    /// A previous sample exists, so this sample completes a transition.
57    has_prev: bool,
58    /// The latest sampled values, for illegal-transition reports.
59    recent: VecDeque<i128>,
60    recent_cap: usize,
61    /// How many times the coverpoint was sampled (its `iff` held).
62    pub samples: u64,
63    /// The last sample produced a value that a bin could take (its `iff` held
64    /// and the value was not x/z): a cross can form a tuple from it.
65    last_sampled: bool,
66    /// Ascending indices of the normal bins the last sample counted, for the
67    /// crosses over this coverpoint (19.6). Empty when the value was ignored,
68    /// illegal or only in the default bin: those make no cross products.
69    last_hits: Vec<usize>,
70}
71
72/// A sampled value normalised to the coverpoint type.
73#[derive(Clone, Copy)]
74enum Sample {
75    Int(i128),
76    Real(f64),
77}
78
79pub(super) fn truthy(v: Val, what: &str) -> Result<bool, CovError> {
80    match v {
81        Val::Int(i) => Ok(i != 0),
82        Val::Real(r) => Ok(r != 0.0),
83        // an x/z condition is false (12.4)
84        Val::Unknown | Val::Pat { .. } => Ok(false),
85        other => Err(CovError::BadValue {
86            ctx: what.to_string(),
87            got: format!("{other:?}"),
88        }),
89    }
90}
91
92impl Coverpoint {
93    /// Instantiates `decl` for a covergroup whose options are `cg_opts` /
94    /// `cg_topts`, constructed with `ctor` arguments. All construction-time
95    /// expressions are evaluated through `ev` now.
96    pub fn new(
97        decl: &CoverpointDecl,
98        cg_opts: &Options,
99        cg_topts: &TypeOptions,
100        ev: &mut dyn Eval,
101        ctor: &[Val],
102    ) -> Result<(Coverpoint, Vec<String>), CovError> {
103        let mut options = cg_opts.inherit();
104        for (n, e) in &decl.options {
105            let v = e.eval(ev, ctor)?;
106            options.set(Level::Coverpoint, n, &v)?;
107        }
108        let mut type_options = cg_topts.inherit();
109        // Table 19-4 makes `distribute_first` a covergroup-only option, but it
110        // governs how the bins of every coverpoint in the group are built
111        // (19.5.1.1), so the coverpoint carries the group's value.
112        type_options.distribute_first = cg_topts.distribute_first;
113        for (n, e) in &decl.type_options {
114            let v = e.eval(ev, ctor)?;
115            type_options.set(Level::Coverpoint, n, &v)?;
116        }
117        let (bins, warnings) = build_bins(decl, &options, &type_options, ev, ctor)?;
118        let mut singles: HashMap<i128, Vec<usize>> = HashMap::new();
119        let mut multi = Vec::new();
120        let mut trans = Vec::new();
121        let mut default_seqs = Vec::new();
122        let mut recent_cap = 0usize;
123        for (i, b) in bins.iter().enumerate() {
124            match &b.matcher {
125                Matcher::Default => {}
126                Matcher::Trans(t) => {
127                    trans.push(i);
128                    if b.kind == BinKind::Illegal {
129                        recent_cap = recent_cap.max(t.max_len);
130                    }
131                }
132                Matcher::DefaultSeq => {
133                    default_seqs.push(i);
134                    if b.kind == BinKind::Illegal {
135                        recent_cap = recent_cap.max(2);
136                    }
137                }
138                Matcher::Ints(s) if s.is_empty() => {}
139                Matcher::Ints(s) if s.count() == 1 => {
140                    singles.entry(s.intervals()[0].0).or_default().push(i)
141                }
142                _ => multi.push(i),
143            }
144        }
145        Ok((
146            Coverpoint {
147                name: decl.name.clone(),
148                ty: decl.ty,
149                options,
150                type_options,
151                expr: decl.expr.clone(),
152                iff: decl.iff.clone(),
153                ctor: ctor.to_vec(),
154                bins,
155                singles,
156                multi,
157                trans,
158                default_seqs,
159                has_prev: false,
160                recent: VecDeque::new(),
161                recent_cap,
162                samples: 0,
163                last_sampled: false,
164                last_hits: Vec::new(),
165            },
166            warnings,
167        ))
168    }
169
170    fn args(&self, sargs: &[Val]) -> Vec<Val> {
171        let mut a = self.ctor.clone();
172        a.extend_from_slice(sargs);
173        a
174    }
175
176    /// Samples the coverpoint: evaluates its `iff` and expression with
177    /// `sargs` (the `sample()` arguments) and counts the value.
178    pub fn sample(&mut self, ev: &mut dyn Eval, sargs: &[Val]) -> Result<Vec<CovEvent>, CovError> {
179        self.last_sampled = false;
180        self.last_hits.clear();
181        let args = self.args(sargs);
182        if let Some(iff) = &self.iff
183            && !truthy(iff.eval(ev, &args)?, "coverpoint iff")?
184        {
185            return Ok(Vec::new());
186        }
187        let v = self.expr.eval(ev, &args)?;
188        self.sample_value(&v, ev, sargs)
189    }
190
191    /// Counts an already-evaluated value (the coverpoint `iff` held).
192    pub fn sample_value(
193        &mut self,
194        v: &Val,
195        ev: &mut dyn Eval,
196        sargs: &[Val],
197    ) -> Result<Vec<CovEvent>, CovError> {
198        self.last_sampled = false;
199        self.last_hits.clear();
200        self.samples += 1;
201        let s = match (v, self.ty.real) {
202            // an x/z value matches no bin, not even default (19.5.4, 19.5.7),
203            // and no sequence continues through it
204            (Val::Unknown | Val::Pat { .. }, _) => {
205                self.break_trans();
206                return Ok(Vec::new());
207            }
208            (Val::Int(i), false) => Sample::Int(self.ty.value_of(self.ty.bits_of(*i))),
209            (Val::Int(i), true) => Sample::Real(*i as f64),
210            (Val::Real(r), true) => Sample::Real(*r),
211            (other, _) => {
212                return Err(CovError::BadValue {
213                    ctx: format!("coverpoint {} sample", self.name),
214                    got: format!("{other:?}"),
215                });
216            }
217        };
218        let args = self.args(sargs);
219        self.last_sampled = true;
220        let mut events = self.count_value(s, ev, &args)?;
221        if let Sample::Int(i) = s {
222            events.extend(self.step_trans(i, ev, &args)?);
223        }
224        self.last_hits.sort_unstable();
225        Ok(events)
226    }
227
228    /// Whether the last sample yielded a value the bins could take.
229    pub fn last_sampled(&self) -> bool {
230        self.last_sampled
231    }
232
233    /// Indices of the normal bins the last sample counted (see the field).
234    pub fn last_hits(&self) -> &[usize] {
235        &self.last_hits
236    }
237
238    /// Indices of the bins a cross over this coverpoint takes tuples from:
239    /// normal, non-default, non-empty ones (19.6: "no cross coverage bins
240    /// shall be created for coverpoint bins that are specified as default,
241    /// ignored, or illegal bins").
242    pub fn cross_bins(&self) -> Vec<usize> {
243        self.bins
244            .iter()
245            .enumerate()
246            .filter(|(_, b)| {
247                b.kind == BinKind::Normal && !b.is_default() && !b.is_default_seq() && !b.empty
248            })
249            .map(|(i, _)| i)
250            .collect()
251    }
252
253    /// Forgets every sequence in flight (a sample no bin can use).
254    fn break_trans(&mut self) {
255        self.has_prev = false;
256        self.recent.clear();
257        for &i in &self.trans {
258            if let Matcher::Trans(t) = &mut self.bins[i].matcher {
259                t.clear();
260            }
261        }
262    }
263
264    /// Advances every transition bin by the sampled value `v` (19.5.2).
265    fn step_trans(
266        &mut self,
267        v: i128,
268        ev: &mut dyn Eval,
269        args: &[Val],
270    ) -> Result<Vec<CovEvent>, CovError> {
271        let mut events = Vec::new();
272        if self.trans.is_empty() && self.default_seqs.is_empty() {
273            return Ok(events);
274        }
275        if self.recent_cap > 0 {
276            self.recent.push_back(v);
277            while self.recent.len() > self.recent_cap {
278                self.recent.pop_front();
279            }
280        }
281        let mut matched = Vec::new();
282        let mut pending = false;
283        for k in 0..self.trans.len() {
284            let i = self.trans[k];
285            let Matcher::Trans(t) = &mut self.bins[i].matcher else {
286                unreachable!("trans lists only transition bins");
287            };
288            let st = t.step(v);
289            pending |= st.pending;
290            if st.matched {
291                matched.push(i);
292            }
293        }
294        // 19.5.2: `default sequence` counts a transition that no other
295        // transition bin took and no earlier attempt is still waiting on.
296        let default_hit = self.has_prev && matched.is_empty() && !pending;
297        self.has_prev = true;
298        let text = self
299            .recent
300            .iter()
301            .map(|v| v.to_string())
302            .collect::<Vec<_>>();
303        let text = text.join("=>");
304        let mut hit: Vec<usize> = matched;
305        if default_hit {
306            hit.extend(self.default_seqs.iter().copied());
307        }
308        for i in hit {
309            if !self.bin_iff(i, ev, args)? {
310                continue;
311            }
312            let bin = &mut self.bins[i];
313            match bin.kind {
314                // 19.5.5: an ignored sequence is not counted anywhere
315                BinKind::Ignore => {}
316                BinKind::Normal => {
317                    bin.hits = bin.hits.saturating_add(1);
318                    // `default sequence` makes no cross products (19.6)
319                    if !bin.is_default_seq() {
320                        self.last_hits.push(i);
321                    }
322                }
323                BinKind::Illegal => {
324                    bin.hits = bin.hits.saturating_add(1);
325                    events.push(CovEvent::IllegalBin {
326                        coverpoint: self.name.clone(),
327                        bin: bin.name.clone(),
328                        value: text.clone(),
329                    });
330                }
331            }
332        }
333        Ok(events)
334    }
335
336    /// Counts a value in the value bins (19.5.1, 19.5.5-19.5.7).
337    fn count_value(
338        &mut self,
339        s: Sample,
340        ev: &mut dyn Eval,
341        args: &[Val],
342    ) -> Result<Vec<CovEvent>, CovError> {
343        let matched = self.matching(s);
344        let text = match s {
345            Sample::Int(i) => i.to_string(),
346            Sample::Real(r) => fmt_real(r),
347        };
348        let mut events = Vec::new();
349
350        // 19.5.6: an illegal value is an error even if another bin holds it.
351        let mut excluded = false;
352        for &i in &matched {
353            if self.bins[i].kind != BinKind::Illegal {
354                continue;
355            }
356            excluded = true;
357            if self.bin_iff(i, ev, args)? {
358                self.bins[i].hits = self.bins[i].hits.saturating_add(1);
359                events.push(CovEvent::IllegalBin {
360                    coverpoint: self.name.clone(),
361                    bin: self.bins[i].name.clone(),
362                    value: text.clone(),
363                });
364            }
365        }
366        // 19.5.5: ignored values take no part in any other bin.
367        excluded |= matched
368            .iter()
369            .any(|&i| self.bins[i].kind == BinKind::Ignore);
370        if excluded {
371            return Ok(events);
372        }
373
374        for &i in &matched {
375            if self.bin_iff(i, ev, args)? {
376                self.bins[i].hits = self.bins[i].hits.saturating_add(1);
377                self.last_hits.push(i);
378            }
379        }
380        // default: values of no defined bin (ignore/illegal bins are defined).
381        if matched.is_empty() {
382            for i in 0..self.bins.len() {
383                if !self.bins[i].is_default() || !self.bin_iff(i, ev, args)? {
384                    continue;
385                }
386                let bin = &mut self.bins[i];
387                bin.hits = bin.hits.saturating_add(1);
388                if bin.default_array
389                    && let Sample::Int(v) = s
390                {
391                    let c = bin.default_values.entry(v).or_insert(0);
392                    *c = c.saturating_add(1);
393                }
394                if bin.kind == BinKind::Illegal {
395                    events.push(CovEvent::IllegalBin {
396                        coverpoint: self.name.clone(),
397                        bin: bin.name.clone(),
398                        value: text.clone(),
399                    });
400                }
401            }
402        }
403        Ok(events)
404    }
405
406    fn bin_iff(&self, i: usize, ev: &mut dyn Eval, args: &[Val]) -> Result<bool, CovError> {
407        match &self.bins[i].iff {
408            None => Ok(true),
409            Some(e) => truthy(e.eval(ev, args)?, "bin iff"),
410        }
411    }
412
413    /// Indices (ascending) of the non-default bins holding the value.
414    fn matching(&self, s: Sample) -> Vec<usize> {
415        let mut out = Vec::new();
416        match s {
417            Sample::Int(v) => {
418                if let Some(ix) = self.singles.get(&v) {
419                    out.extend(ix.iter().copied());
420                }
421                for &i in &self.multi {
422                    let hit = match &self.bins[i].matcher {
423                        Matcher::Ints(set) => set.contains(v),
424                        Matcher::Pats(pats) => {
425                            let bits = self.ty.bits_of(v);
426                            pats.iter().any(|&(pb, care)| (bits ^ pb) & care == 0)
427                        }
428                        Matcher::Reals(_)
429                        | Matcher::Default
430                        | Matcher::Trans(_)
431                        | Matcher::DefaultSeq => false,
432                    };
433                    if hit {
434                        out.push(i);
435                    }
436                }
437            }
438            Sample::Real(r) => {
439                for &i in &self.multi {
440                    if let Matcher::Reals(items) = &self.bins[i].matcher
441                        && items.iter().any(|it| it.contains(r))
442                    {
443                        out.push(i);
444                    }
445                }
446            }
447        }
448        out.sort_unstable();
449        out
450    }
451
452    /// The coverpoint's coverage: covered / total over the non-default,
453    /// non-empty `bins` bins (19.11). Default, ignore and illegal bins and
454    /// empty bins are excluded.
455    pub fn coverage(&self) -> Coverage {
456        let mut c = Coverage::default();
457        for b in &self.bins {
458            if b.kind != BinKind::Normal || b.is_default() || b.is_default_seq() || b.empty {
459                continue;
460            }
461            c.total += 1;
462            if b.hits >= self.options.at_least {
463                c.covered += 1;
464            }
465        }
466        c
467    }
468
469    /// The bins the coverage is measured over, with their hit counts, in
470    /// declaration order: what a merged (type) coverage unions by name
471    /// (19.11.3).
472    pub fn coverage_bins(&self) -> Vec<(String, u64)> {
473        self.bins
474            .iter()
475            .filter(|b| {
476                b.kind == BinKind::Normal && !b.is_default() && !b.is_default_seq() && !b.empty
477            })
478            .map(|b| (b.name.clone(), b.hits))
479            .collect()
480    }
481
482    /// A sample event this coverpoint did not take part in (it is stopped, 19.8):
483    /// no cross tuple comes from it and every sequence in flight is dropped,
484    /// because a gap in sampling breaks consecutiveness.
485    pub fn skip(&mut self) {
486        self.last_sampled = false;
487        self.last_hits.clear();
488        self.break_trans();
489    }
490
491    /// Hit count of the bin called `name`.
492    pub fn hits(&self, name: &str) -> Option<u64> {
493        self.bins.iter().find(|b| b.name == name).map(|b| b.hits)
494    }
495
496    /// Zeroes every bin (a covergroup `stop`/re-arm is not a reset; this is
497    /// for `$set_coverage`-style clears and tests).
498    pub fn reset(&mut self) {
499        self.samples = 0;
500        self.last_sampled = false;
501        self.last_hits.clear();
502        self.break_trans();
503        for b in &mut self.bins {
504            b.hits = 0;
505            b.default_values.clear();
506        }
507    }
508}