1use super::value::{CovError, Val};
11
12#[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#[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 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 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 pub fn get(&self, level: Level, name: &str) -> Result<Val, CovError> {
172 let mut probe = Options::default();
173 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#[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 pub fn inherit(&self) -> TypeOptions {
226 TypeOptions {
227 real_interval: self.real_interval,
228 ..TypeOptions::default()
229 }
230 }
231
232 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}