use super::value::{CovError, Val};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Level {
Covergroup,
Coverpoint,
Cross,
}
impl Level {
pub fn name(self) -> &'static str {
match self {
Level::Covergroup => "covergroup",
Level::Coverpoint => "coverpoint",
Level::Cross => "cross",
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Options {
pub name: String,
pub weight: i64,
pub goal: i64,
pub comment: String,
pub at_least: u64,
pub auto_bin_max: u64,
pub cross_num_print_missing: u64,
pub cross_retain_auto_bins: bool,
pub detect_overlap: bool,
pub per_instance: bool,
pub get_inst_coverage: bool,
}
impl Default for Options {
fn default() -> Self {
Options {
name: String::new(),
weight: 1,
goal: 100,
comment: String::new(),
at_least: 1,
auto_bin_max: 64,
cross_num_print_missing: 0,
cross_retain_auto_bins: true,
detect_overlap: false,
per_instance: false,
get_inst_coverage: false,
}
}
}
fn as_int(option: &str, v: &Val) -> Result<i128, CovError> {
match v {
Val::Int(i) => Ok(*i),
_ => Err(CovError::OptionType {
option: option.to_string(),
}),
}
}
fn as_unsigned(option: &str, v: &Val) -> Result<u64, CovError> {
let i = as_int(option, v)?;
u64::try_from(i).map_err(|_| CovError::OptionValue {
option: option.to_string(),
detail: format!("{i} is negative"),
})
}
fn as_bit(option: &str, v: &Val) -> Result<bool, CovError> {
Ok(as_int(option, v)? & 1 == 1)
}
fn as_weight(option: &str, v: &Val) -> Result<i64, CovError> {
let i = as_int(option, v)?;
i64::try_from(i)
.ok()
.filter(|w| *w >= 0)
.ok_or_else(|| CovError::OptionValue {
option: option.to_string(),
detail: format!("{i} is not a non-negative int"),
})
}
fn as_string(option: &str, v: &Val) -> Result<String, CovError> {
match v {
Val::Str(s) => Ok(s.clone()),
_ => Err(CovError::OptionType {
option: option.to_string(),
}),
}
}
fn level_err(option: &str, level: Level) -> CovError {
CovError::OptionLevel {
option: option.to_string(),
level: level.name(),
}
}
impl Options {
pub fn inherit(&self) -> Options {
Options {
at_least: self.at_least,
auto_bin_max: self.auto_bin_max,
cross_num_print_missing: self.cross_num_print_missing,
cross_retain_auto_bins: self.cross_retain_auto_bins,
detect_overlap: self.detect_overlap,
..Options::default()
}
}
pub fn set(&mut self, level: Level, name: &str, v: &Val) -> Result<(), CovError> {
use Level::*;
let allowed: &[Level] = match name {
"name" | "per_instance" | "get_inst_coverage" => &[Covergroup],
"weight" | "goal" | "comment" | "at_least" => &[Covergroup, Coverpoint, Cross],
"auto_bin_max" | "detect_overlap" => &[Covergroup, Coverpoint],
"cross_num_print_missing" | "cross_retain_auto_bins" => &[Covergroup, Cross],
_ => return Err(CovError::UnknownOption(name.to_string())),
};
if !allowed.contains(&level) {
return Err(level_err(name, level));
}
match name {
"name" => self.name = as_string(name, v)?,
"weight" => self.weight = as_weight(name, v)?,
"goal" => {
self.goal = i64::try_from(as_int(name, v)?).map_err(|_| CovError::OptionValue {
option: name.to_string(),
detail: "out of int range".to_string(),
})?
}
"comment" => self.comment = as_string(name, v)?,
"at_least" => self.at_least = as_unsigned(name, v)?,
"auto_bin_max" => {
let n = as_unsigned(name, v)?;
if n == 0 {
return Err(CovError::OptionValue {
option: name.to_string(),
detail: "must be positive".to_string(),
});
}
self.auto_bin_max = n;
}
"cross_num_print_missing" => self.cross_num_print_missing = as_unsigned(name, v)?,
"cross_retain_auto_bins" => self.cross_retain_auto_bins = as_bit(name, v)?,
"detect_overlap" => self.detect_overlap = as_bit(name, v)?,
"per_instance" => self.per_instance = as_bit(name, v)?,
"get_inst_coverage" => self.get_inst_coverage = as_bit(name, v)?,
other => return Err(CovError::UnknownOption(other.to_string())),
}
Ok(())
}
pub fn get(&self, level: Level, name: &str) -> Result<Val, CovError> {
let mut probe = Options::default();
let sample = match name {
"name" | "comment" => Val::Str(String::new()),
_ => Val::Int(1),
};
probe.set(level, name, &sample)?;
Ok(match name {
"name" => Val::Str(self.name.clone()),
"weight" => Val::Int(self.weight.into()),
"goal" => Val::Int(self.goal.into()),
"comment" => Val::Str(self.comment.clone()),
"at_least" => Val::Int(self.at_least.into()),
"auto_bin_max" => Val::Int(self.auto_bin_max.into()),
"cross_num_print_missing" => Val::Int(self.cross_num_print_missing.into()),
"cross_retain_auto_bins" => Val::Int(self.cross_retain_auto_bins.into()),
"detect_overlap" => Val::Int(self.detect_overlap.into()),
"per_instance" => Val::Int(self.per_instance.into()),
"get_inst_coverage" => Val::Int(self.get_inst_coverage.into()),
other => return Err(CovError::UnknownOption(other.to_string())),
})
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct TypeOptions {
pub weight: i64,
pub goal: i64,
pub comment: String,
pub strobe: bool,
pub merge_instances: bool,
pub distribute_first: bool,
pub real_interval: f64,
}
impl Default for TypeOptions {
fn default() -> Self {
TypeOptions {
weight: 1,
goal: 100,
comment: String::new(),
strobe: false,
merge_instances: false,
distribute_first: false,
real_interval: 1.0,
}
}
}
impl TypeOptions {
pub fn inherit(&self) -> TypeOptions {
TypeOptions {
real_interval: self.real_interval,
..TypeOptions::default()
}
}
pub fn set(&mut self, level: Level, name: &str, v: &Val) -> Result<(), CovError> {
use Level::*;
let allowed: &[Level] = match name {
"weight" | "goal" | "comment" => &[Covergroup, Coverpoint, Cross],
"strobe" | "merge_instances" | "distribute_first" => &[Covergroup],
"real_interval" => &[Covergroup, Coverpoint],
_ => return Err(CovError::UnknownOption(name.to_string())),
};
if !allowed.contains(&level) {
return Err(level_err(name, level));
}
match name {
"weight" => self.weight = as_weight(name, v)?,
"goal" => {
self.goal = i64::try_from(as_int(name, v)?).map_err(|_| CovError::OptionValue {
option: name.to_string(),
detail: "out of int range".to_string(),
})?
}
"comment" => self.comment = as_string(name, v)?,
"strobe" => self.strobe = as_bit(name, v)?,
"merge_instances" => self.merge_instances = as_bit(name, v)?,
"distribute_first" => self.distribute_first = as_bit(name, v)?,
"real_interval" => {
let r = match v {
Val::Real(r) => *r,
Val::Int(i) => *i as f64,
_ => {
return Err(CovError::OptionType {
option: name.to_string(),
});
}
};
if r.is_nan() || r <= 0.0 || r.is_infinite() {
return Err(CovError::OptionValue {
option: name.to_string(),
detail: format!("{r} is not a positive finite real"),
});
}
self.real_interval = r;
}
other => return Err(CovError::UnknownOption(other.to_string())),
}
Ok(())
}
pub fn get(&self, level: Level, name: &str) -> Result<Val, CovError> {
let mut probe = TypeOptions::default();
let sample = match name {
"comment" => Val::Str(String::new()),
_ => Val::Int(1),
};
probe.set(level, name, &sample)?;
Ok(match name {
"weight" => Val::Int(self.weight.into()),
"goal" => Val::Int(self.goal.into()),
"comment" => Val::Str(self.comment.clone()),
"strobe" => Val::Int(self.strobe.into()),
"merge_instances" => Val::Int(self.merge_instances.into()),
"distribute_first" => Val::Int(self.distribute_first.into()),
"real_interval" => Val::Real(self.real_interval),
other => return Err(CovError::UnknownOption(other.to_string())),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defaults_match_table_19_1_and_19_3() {
let o = Options::default();
assert_eq!(o.weight, 1);
assert_eq!(o.goal, 100);
assert_eq!(o.comment, "");
assert_eq!(o.at_least, 1);
assert_eq!(o.auto_bin_max, 64);
assert_eq!(o.cross_num_print_missing, 0);
assert!(o.cross_retain_auto_bins);
assert!(!o.detect_overlap);
assert!(!o.per_instance);
assert!(!o.get_inst_coverage);
let t = TypeOptions::default();
assert_eq!((t.weight, t.goal), (1, 100));
assert!(!t.strobe && !t.merge_instances && !t.distribute_first);
assert_eq!(t.real_interval, 1.0);
}
#[test]
fn covergroup_level_defaults_flow_down_but_weight_does_not() {
let mut cg = Options::default();
cg.set(Level::Covergroup, "at_least", &Val::Int(3)).unwrap();
cg.set(Level::Covergroup, "auto_bin_max", &Val::Int(8))
.unwrap();
cg.set(Level::Covergroup, "detect_overlap", &Val::Int(1))
.unwrap();
cg.set(Level::Covergroup, "weight", &Val::Int(5)).unwrap();
cg.set(Level::Covergroup, "goal", &Val::Int(70)).unwrap();
cg.set(Level::Covergroup, "comment", &Val::Str("c".into()))
.unwrap();
let cp = cg.inherit();
assert_eq!(
(cp.at_least, cp.auto_bin_max, cp.detect_overlap),
(3, 8, true)
);
assert_eq!((cp.weight, cp.goal, cp.comment.as_str()), (1, 100, ""));
}
#[test]
fn level_legality_follows_table_19_2() {
let mut o = Options::default();
let one = Val::Int(1);
for (name, ok) in [
("name", [true, false, false]),
("weight", [true, true, true]),
("goal", [true, true, true]),
("comment", [true, true, true]),
("at_least", [true, true, true]),
("auto_bin_max", [true, true, false]),
("cross_num_print_missing", [true, false, true]),
("cross_retain_auto_bins", [true, false, true]),
("detect_overlap", [true, true, false]),
("per_instance", [true, false, false]),
("get_inst_coverage", [true, false, false]),
] {
let v = if name == "name" || name == "comment" {
Val::Str("x".into())
} else {
one.clone()
};
for (i, level) in [Level::Covergroup, Level::Coverpoint, Level::Cross]
.into_iter()
.enumerate()
{
assert_eq!(o.set(level, name, &v).is_ok(), ok[i], "{name} at {level:?}");
}
}
}
#[test]
fn rejects_bad_option_values() {
let mut o = Options::default();
assert!(matches!(
o.set(Level::Covergroup, "bogus", &Val::Int(1)),
Err(CovError::UnknownOption(_))
));
assert!(matches!(
o.set(Level::Covergroup, "weight", &Val::Str("a".into())),
Err(CovError::OptionType { .. })
));
assert!(matches!(
o.set(Level::Covergroup, "weight", &Val::Int(-1)),
Err(CovError::OptionValue { .. })
));
assert!(matches!(
o.set(Level::Covergroup, "at_least", &Val::Int(-2)),
Err(CovError::OptionValue { .. })
));
assert!(matches!(
o.set(Level::Covergroup, "auto_bin_max", &Val::Int(0)),
Err(CovError::OptionValue { .. })
));
}
#[test]
fn get_reads_back_and_enforces_level() {
let mut o = Options::default();
o.set(Level::Covergroup, "weight", &Val::Int(4)).unwrap();
assert_eq!(o.get(Level::Covergroup, "weight"), Ok(Val::Int(4)));
assert!(o.get(Level::Cross, "auto_bin_max").is_err());
}
#[test]
fn type_option_levels_and_inheritance() {
let mut t = TypeOptions::default();
t.set(Level::Covergroup, "real_interval", &Val::Real(0.5))
.unwrap();
t.set(Level::Covergroup, "strobe", &Val::Int(1)).unwrap();
t.set(Level::Covergroup, "merge_instances", &Val::Int(1))
.unwrap();
t.set(Level::Covergroup, "distribute_first", &Val::Int(1))
.unwrap();
t.set(Level::Covergroup, "weight", &Val::Int(2)).unwrap();
assert!(t.set(Level::Coverpoint, "strobe", &Val::Int(1)).is_err());
assert!(
t.set(Level::Cross, "real_interval", &Val::Real(1.0))
.is_err()
);
assert!(
t.set(Level::Coverpoint, "real_interval", &Val::Real(0.0))
.is_err()
);
let cp = t.inherit();
assert_eq!(cp.real_interval, 0.5);
assert!(!cp.strobe && !cp.merge_instances && !cp.distribute_first);
assert_eq!(cp.weight, 1);
assert_eq!(
t.get(Level::Covergroup, "real_interval"),
Ok(Val::Real(0.5))
);
}
}