Skip to main content

vj_sim/covergroup/
options.rs

1//! Instance options (IEEE 1800-2023 Table 19-1) and type options (Table 19-3),
2//! with the per-level legality of Table 19-2 and Table 19-4.
3//!
4//! `at_least`, `auto_bin_max`, `cross_num_print_missing`,
5//! `cross_retain_auto_bins`, `detect_overlap` (and the type option
6//! `real_interval`) assigned at covergroup level are the defaults for the
7//! coverpoints and crosses below it; `weight`, `goal`, `comment` and
8//! `per_instance` are not inherited (19.7).
9
10use super::value::{CovError, Val};
11
12/// The syntactic level an option is assigned at.
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub enum Level {
15    Covergroup,
16    Coverpoint,
17    Cross,
18}
19
20impl Level {
21    pub fn name(self) -> &'static str {
22        match self {
23            Level::Covergroup => "covergroup",
24            Level::Coverpoint => "coverpoint",
25            Level::Cross => "cross",
26        }
27    }
28}
29
30/// `option.<name>` of a covergroup, coverpoint or cross.
31#[derive(Clone, Debug, PartialEq)]
32pub struct Options {
33    pub name: String,
34    pub weight: i64,
35    pub goal: i64,
36    pub comment: String,
37    pub at_least: u64,
38    pub auto_bin_max: u64,
39    pub cross_num_print_missing: u64,
40    pub cross_retain_auto_bins: bool,
41    pub detect_overlap: bool,
42    pub per_instance: bool,
43    pub get_inst_coverage: bool,
44}
45
46impl Default for Options {
47    fn default() -> Self {
48        Options {
49            name: String::new(),
50            weight: 1,
51            goal: 100,
52            comment: String::new(),
53            at_least: 1,
54            auto_bin_max: 64,
55            cross_num_print_missing: 0,
56            cross_retain_auto_bins: true,
57            detect_overlap: false,
58            per_instance: false,
59            get_inst_coverage: false,
60        }
61    }
62}
63
64fn as_int(option: &str, v: &Val) -> Result<i128, CovError> {
65    match v {
66        Val::Int(i) => Ok(*i),
67        _ => Err(CovError::OptionType {
68            option: option.to_string(),
69        }),
70    }
71}
72
73fn as_unsigned(option: &str, v: &Val) -> Result<u64, CovError> {
74    let i = as_int(option, v)?;
75    u64::try_from(i).map_err(|_| CovError::OptionValue {
76        option: option.to_string(),
77        detail: format!("{i} is negative"),
78    })
79}
80
81fn as_bit(option: &str, v: &Val) -> Result<bool, CovError> {
82    Ok(as_int(option, v)? & 1 == 1)
83}
84
85fn as_weight(option: &str, v: &Val) -> Result<i64, CovError> {
86    let i = as_int(option, v)?;
87    i64::try_from(i)
88        .ok()
89        .filter(|w| *w >= 0)
90        .ok_or_else(|| CovError::OptionValue {
91            option: option.to_string(),
92            detail: format!("{i} is not a non-negative int"),
93        })
94}
95
96fn as_string(option: &str, v: &Val) -> Result<String, CovError> {
97    match v {
98        Val::Str(s) => Ok(s.clone()),
99        _ => Err(CovError::OptionType {
100            option: option.to_string(),
101        }),
102    }
103}
104
105fn level_err(option: &str, level: Level) -> CovError {
106    CovError::OptionLevel {
107        option: option.to_string(),
108        level: level.name(),
109    }
110}
111
112impl Options {
113    /// Defaults for a coverpoint or cross declared inside a covergroup whose
114    /// options are `self`.
115    pub fn inherit(&self) -> Options {
116        Options {
117            at_least: self.at_least,
118            auto_bin_max: self.auto_bin_max,
119            cross_num_print_missing: self.cross_num_print_missing,
120            cross_retain_auto_bins: self.cross_retain_auto_bins,
121            detect_overlap: self.detect_overlap,
122            ..Options::default()
123        }
124    }
125
126    /// `option.<name> = <v>` at `level` (Table 19-2).
127    pub fn set(&mut self, level: Level, name: &str, v: &Val) -> Result<(), CovError> {
128        use Level::*;
129        let allowed: &[Level] = match name {
130            "name" | "per_instance" | "get_inst_coverage" => &[Covergroup],
131            "weight" | "goal" | "comment" | "at_least" => &[Covergroup, Coverpoint, Cross],
132            "auto_bin_max" | "detect_overlap" => &[Covergroup, Coverpoint],
133            "cross_num_print_missing" | "cross_retain_auto_bins" => &[Covergroup, Cross],
134            _ => return Err(CovError::UnknownOption(name.to_string())),
135        };
136        if !allowed.contains(&level) {
137            return Err(level_err(name, level));
138        }
139        match name {
140            "name" => self.name = as_string(name, v)?,
141            "weight" => self.weight = as_weight(name, v)?,
142            "goal" => {
143                self.goal = i64::try_from(as_int(name, v)?).map_err(|_| CovError::OptionValue {
144                    option: name.to_string(),
145                    detail: "out of int range".to_string(),
146                })?
147            }
148            "comment" => self.comment = as_string(name, v)?,
149            "at_least" => self.at_least = as_unsigned(name, v)?,
150            "auto_bin_max" => {
151                let n = as_unsigned(name, v)?;
152                if n == 0 {
153                    return Err(CovError::OptionValue {
154                        option: name.to_string(),
155                        detail: "must be positive".to_string(),
156                    });
157                }
158                self.auto_bin_max = n;
159            }
160            "cross_num_print_missing" => self.cross_num_print_missing = as_unsigned(name, v)?,
161            "cross_retain_auto_bins" => self.cross_retain_auto_bins = as_bit(name, v)?,
162            "detect_overlap" => self.detect_overlap = as_bit(name, v)?,
163            "per_instance" => self.per_instance = as_bit(name, v)?,
164            "get_inst_coverage" => self.get_inst_coverage = as_bit(name, v)?,
165            other => return Err(CovError::UnknownOption(other.to_string())),
166        }
167        Ok(())
168    }
169
170    /// `option.<name>` read back (the structs of 19.10).
171    pub fn get(&self, level: Level, name: &str) -> Result<Val, CovError> {
172        let mut probe = Options::default();
173        // Reuse the legality table: a name is readable where it is writable.
174        let sample = match name {
175            "name" | "comment" => Val::Str(String::new()),
176            _ => Val::Int(1),
177        };
178        probe.set(level, name, &sample)?;
179        Ok(match name {
180            "name" => Val::Str(self.name.clone()),
181            "weight" => Val::Int(self.weight.into()),
182            "goal" => Val::Int(self.goal.into()),
183            "comment" => Val::Str(self.comment.clone()),
184            "at_least" => Val::Int(self.at_least.into()),
185            "auto_bin_max" => Val::Int(self.auto_bin_max.into()),
186            "cross_num_print_missing" => Val::Int(self.cross_num_print_missing.into()),
187            "cross_retain_auto_bins" => Val::Int(self.cross_retain_auto_bins.into()),
188            "detect_overlap" => Val::Int(self.detect_overlap.into()),
189            "per_instance" => Val::Int(self.per_instance.into()),
190            "get_inst_coverage" => Val::Int(self.get_inst_coverage.into()),
191            other => return Err(CovError::UnknownOption(other.to_string())),
192        })
193    }
194}
195
196/// `type_option.<name>` of a covergroup, coverpoint or cross.
197#[derive(Clone, Debug, PartialEq)]
198pub struct TypeOptions {
199    pub weight: i64,
200    pub goal: i64,
201    pub comment: String,
202    pub strobe: bool,
203    pub merge_instances: bool,
204    pub distribute_first: bool,
205    pub real_interval: f64,
206}
207
208impl Default for TypeOptions {
209    fn default() -> Self {
210        TypeOptions {
211            weight: 1,
212            goal: 100,
213            comment: String::new(),
214            strobe: false,
215            merge_instances: false,
216            distribute_first: false,
217            real_interval: 1.0,
218        }
219    }
220}
221
222impl TypeOptions {
223    /// Defaults for a coverpoint declared inside a covergroup whose type
224    /// options are `self` (Table 19-4: only `real_interval` is inherited).
225    pub fn inherit(&self) -> TypeOptions {
226        TypeOptions {
227            real_interval: self.real_interval,
228            ..TypeOptions::default()
229        }
230    }
231
232    /// `type_option.<name> = <v>` at `level` (Table 19-4).
233    pub fn set(&mut self, level: Level, name: &str, v: &Val) -> Result<(), CovError> {
234        use Level::*;
235        let allowed: &[Level] = match name {
236            "weight" | "goal" | "comment" => &[Covergroup, Coverpoint, Cross],
237            "strobe" | "merge_instances" | "distribute_first" => &[Covergroup],
238            "real_interval" => &[Covergroup, Coverpoint],
239            _ => return Err(CovError::UnknownOption(name.to_string())),
240        };
241        if !allowed.contains(&level) {
242            return Err(level_err(name, level));
243        }
244        match name {
245            "weight" => self.weight = as_weight(name, v)?,
246            "goal" => {
247                self.goal = i64::try_from(as_int(name, v)?).map_err(|_| CovError::OptionValue {
248                    option: name.to_string(),
249                    detail: "out of int range".to_string(),
250                })?
251            }
252            "comment" => self.comment = as_string(name, v)?,
253            "strobe" => self.strobe = as_bit(name, v)?,
254            "merge_instances" => self.merge_instances = as_bit(name, v)?,
255            "distribute_first" => self.distribute_first = as_bit(name, v)?,
256            "real_interval" => {
257                let r = match v {
258                    Val::Real(r) => *r,
259                    Val::Int(i) => *i as f64,
260                    _ => {
261                        return Err(CovError::OptionType {
262                            option: name.to_string(),
263                        });
264                    }
265                };
266                if r.is_nan() || r <= 0.0 || r.is_infinite() {
267                    return Err(CovError::OptionValue {
268                        option: name.to_string(),
269                        detail: format!("{r} is not a positive finite real"),
270                    });
271                }
272                self.real_interval = r;
273            }
274            other => return Err(CovError::UnknownOption(other.to_string())),
275        }
276        Ok(())
277    }
278
279    pub fn get(&self, level: Level, name: &str) -> Result<Val, CovError> {
280        let mut probe = TypeOptions::default();
281        let sample = match name {
282            "comment" => Val::Str(String::new()),
283            _ => Val::Int(1),
284        };
285        probe.set(level, name, &sample)?;
286        Ok(match name {
287            "weight" => Val::Int(self.weight.into()),
288            "goal" => Val::Int(self.goal.into()),
289            "comment" => Val::Str(self.comment.clone()),
290            "strobe" => Val::Int(self.strobe.into()),
291            "merge_instances" => Val::Int(self.merge_instances.into()),
292            "distribute_first" => Val::Int(self.distribute_first.into()),
293            "real_interval" => Val::Real(self.real_interval),
294            other => return Err(CovError::UnknownOption(other.to_string())),
295        })
296    }
297}
298
299#[cfg(test)]
300mod tests {
301    use super::*;
302
303    #[test]
304    fn defaults_match_table_19_1_and_19_3() {
305        let o = Options::default();
306        assert_eq!(o.weight, 1);
307        assert_eq!(o.goal, 100);
308        assert_eq!(o.comment, "");
309        assert_eq!(o.at_least, 1);
310        assert_eq!(o.auto_bin_max, 64);
311        assert_eq!(o.cross_num_print_missing, 0);
312        assert!(o.cross_retain_auto_bins);
313        assert!(!o.detect_overlap);
314        assert!(!o.per_instance);
315        assert!(!o.get_inst_coverage);
316        let t = TypeOptions::default();
317        assert_eq!((t.weight, t.goal), (1, 100));
318        assert!(!t.strobe && !t.merge_instances && !t.distribute_first);
319        assert_eq!(t.real_interval, 1.0);
320    }
321
322    #[test]
323    fn covergroup_level_defaults_flow_down_but_weight_does_not() {
324        let mut cg = Options::default();
325        cg.set(Level::Covergroup, "at_least", &Val::Int(3)).unwrap();
326        cg.set(Level::Covergroup, "auto_bin_max", &Val::Int(8))
327            .unwrap();
328        cg.set(Level::Covergroup, "detect_overlap", &Val::Int(1))
329            .unwrap();
330        cg.set(Level::Covergroup, "weight", &Val::Int(5)).unwrap();
331        cg.set(Level::Covergroup, "goal", &Val::Int(70)).unwrap();
332        cg.set(Level::Covergroup, "comment", &Val::Str("c".into()))
333            .unwrap();
334        let cp = cg.inherit();
335        assert_eq!(
336            (cp.at_least, cp.auto_bin_max, cp.detect_overlap),
337            (3, 8, true)
338        );
339        assert_eq!((cp.weight, cp.goal, cp.comment.as_str()), (1, 100, ""));
340    }
341
342    #[test]
343    fn level_legality_follows_table_19_2() {
344        let mut o = Options::default();
345        let one = Val::Int(1);
346        for (name, ok) in [
347            ("name", [true, false, false]),
348            ("weight", [true, true, true]),
349            ("goal", [true, true, true]),
350            ("comment", [true, true, true]),
351            ("at_least", [true, true, true]),
352            ("auto_bin_max", [true, true, false]),
353            ("cross_num_print_missing", [true, false, true]),
354            ("cross_retain_auto_bins", [true, false, true]),
355            ("detect_overlap", [true, true, false]),
356            ("per_instance", [true, false, false]),
357            ("get_inst_coverage", [true, false, false]),
358        ] {
359            let v = if name == "name" || name == "comment" {
360                Val::Str("x".into())
361            } else {
362                one.clone()
363            };
364            for (i, level) in [Level::Covergroup, Level::Coverpoint, Level::Cross]
365                .into_iter()
366                .enumerate()
367            {
368                assert_eq!(o.set(level, name, &v).is_ok(), ok[i], "{name} at {level:?}");
369            }
370        }
371    }
372
373    #[test]
374    fn rejects_bad_option_values() {
375        let mut o = Options::default();
376        assert!(matches!(
377            o.set(Level::Covergroup, "bogus", &Val::Int(1)),
378            Err(CovError::UnknownOption(_))
379        ));
380        assert!(matches!(
381            o.set(Level::Covergroup, "weight", &Val::Str("a".into())),
382            Err(CovError::OptionType { .. })
383        ));
384        assert!(matches!(
385            o.set(Level::Covergroup, "weight", &Val::Int(-1)),
386            Err(CovError::OptionValue { .. })
387        ));
388        assert!(matches!(
389            o.set(Level::Covergroup, "at_least", &Val::Int(-2)),
390            Err(CovError::OptionValue { .. })
391        ));
392        assert!(matches!(
393            o.set(Level::Covergroup, "auto_bin_max", &Val::Int(0)),
394            Err(CovError::OptionValue { .. })
395        ));
396    }
397
398    #[test]
399    fn get_reads_back_and_enforces_level() {
400        let mut o = Options::default();
401        o.set(Level::Covergroup, "weight", &Val::Int(4)).unwrap();
402        assert_eq!(o.get(Level::Covergroup, "weight"), Ok(Val::Int(4)));
403        assert!(o.get(Level::Cross, "auto_bin_max").is_err());
404    }
405
406    #[test]
407    fn type_option_levels_and_inheritance() {
408        let mut t = TypeOptions::default();
409        t.set(Level::Covergroup, "real_interval", &Val::Real(0.5))
410            .unwrap();
411        t.set(Level::Covergroup, "strobe", &Val::Int(1)).unwrap();
412        t.set(Level::Covergroup, "merge_instances", &Val::Int(1))
413            .unwrap();
414        t.set(Level::Covergroup, "distribute_first", &Val::Int(1))
415            .unwrap();
416        t.set(Level::Covergroup, "weight", &Val::Int(2)).unwrap();
417        assert!(t.set(Level::Coverpoint, "strobe", &Val::Int(1)).is_err());
418        assert!(
419            t.set(Level::Cross, "real_interval", &Val::Real(1.0))
420                .is_err()
421        );
422        assert!(
423            t.set(Level::Coverpoint, "real_interval", &Val::Real(0.0))
424                .is_err()
425        );
426        let cp = t.inherit();
427        assert_eq!(cp.real_interval, 0.5);
428        assert!(!cp.strobe && !cp.merge_instances && !cp.distribute_first);
429        assert_eq!(cp.weight, 1);
430        assert_eq!(
431            t.get(Level::Covergroup, "real_interval"),
432            Ok(Val::Real(0.5))
433        );
434    }
435}