use vj_sdf::ast::{DelVal, DeltaMode, PathPulse, Rvalue};
use vj_sim::netlist::specify::PathDelaySet;
pub(crate) use vj_exec::timing::sdf_update::{SlotUpdate, Slots, apply_update, merge_one};
#[derive(Clone, Copy)]
enum Cell {
V(usize),
Min(usize, usize),
Max(usize, usize),
}
use Cell::{Max, Min, V};
const COLUMNS: [usize; 5] = [1, 2, 3, 6, 12];
#[rustfmt::skip]
const TABLE_32_4: [[Cell; 5]; 12] = [
[V(1), V(1), V(1), V(1), V(1)], [V(1), V(2), V(2), V(2), V(2)], [V(1), V(1), V(3), V(3), V(3)], [V(1), V(1), V(1), V(4), V(4)], [V(1), V(2), V(3), V(5), V(5)], [V(1), V(2), V(2), V(6), V(6)], [V(1), V(1), Min(1, 3), Min(1, 3), V(7)], [V(1), V(1), V(1), Max(1, 4), V(8)], [V(1), V(2), Min(2, 3), Min(2, 5), V(9)], [V(1), V(2), V(2), Max(2, 6), V(10)], [V(1), Max(1, 2), V(3), Max(3, 5), V(11)], [V(1), Min(1, 2), Min(1, 2), Min(4, 6), V(12)], ];
pub(crate) fn expand_table(v: &[Option<i64>]) -> Result<Slots, String> {
let Some(col) = COLUMNS.iter().position(|&n| n == v.len()) else {
return Err(format!(
"an SDF delay list of {} values (LRM Table 32-4 defines only 1, 2, 3, 6 and 12)",
v.len()
));
};
let mut out: Slots = [None; 12];
for (slot, row) in out.iter_mut().zip(&TABLE_32_4) {
*slot = match row[col] {
V(a) => v[a - 1],
Min(a, b) => v[a - 1].zip(v[b - 1]).map(|(x, y)| x.min(y)),
Max(a, b) => v[a - 1].zip(v[b - 1]).map(|(x, y)| x.max(y)),
};
}
Ok(out)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Mtm {
Min,
Typ,
Max,
}
impl Mtm {
fn index(self) -> usize {
match self {
Mtm::Min => 0,
Mtm::Typ => 1,
Mtm::Max => 2,
}
}
pub(crate) fn parse(spec: Option<&str>, tool_control: Mtm) -> Result<Mtm, String> {
let Some(s) = spec else {
return Ok(tool_control);
};
match s.to_ascii_uppercase().as_str() {
"MINIMUM" => Ok(Mtm::Min),
"MAXIMUM" => Ok(Mtm::Max),
"TYPICAL" => Ok(Mtm::Typ),
"TOOL_CONTROL" => Ok(tool_control),
_ => Err(format!(
"mtm_spec \"{s}\" is not MAXIMUM, MINIMUM, TYPICAL or TOOL_CONTROL (LRM Table 32-5)"
)),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum ScaleType {
Max,
Min,
Mtm,
Typ,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct Scale {
pub(crate) factors: [f64; 3],
pub(crate) kind: ScaleType,
}
impl Scale {
pub(crate) const IDENTITY: Scale = Scale {
factors: [1.0; 3],
kind: ScaleType::Mtm,
};
pub(crate) fn parse(factors: Option<&str>, kind: Option<&str>) -> Result<Scale, String> {
let mut scale = Scale::IDENTITY;
if let Some(f) = factors {
let parts: Vec<&str> = f.split(':').collect();
let [a, b, c] = parts.as_slice() else {
return Err(format!(
"scale_factors \"{f}\" is not three colon-separated numbers (min:typ:max)"
));
};
for (slot, text) in scale.factors.iter_mut().zip([a, b, c]) {
*slot = text
.trim()
.parse::<f64>()
.ok()
.filter(|v| v.is_finite() && *v >= 0.0)
.ok_or_else(|| {
format!("scale_factors \"{f}\": \"{text}\" is not a non-negative number")
})?;
}
}
if let Some(k) = kind {
scale.kind = match k.to_ascii_uppercase().as_str() {
"FROM_MAXIMUM" => ScaleType::Max,
"FROM_MINIMUM" => ScaleType::Min,
"FROM_MTM" => ScaleType::Mtm,
"FROM_TYPICAL" => ScaleType::Typ,
_ => {
return Err(format!(
"scale_type \"{k}\" is not FROM_MAXIMUM, FROM_MINIMUM, FROM_MTM or \
FROM_TYPICAL (LRM Table 32-6)"
));
}
};
}
Ok(scale)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct Selector {
pub(crate) mtm: Mtm,
pub(crate) scale: Scale,
}
impl Selector {
pub(crate) fn value(&self, rv: Rvalue) -> Option<f64> {
let triple = match rv {
Rvalue::Empty => return None,
Rvalue::Single(v) => [Some(v); 3],
Rvalue::Triple { min, typ, max } => [min, typ, max],
};
let want = self.mtm.index();
let base = match self.scale.kind {
ScaleType::Mtm => want,
ScaleType::Min => 0,
ScaleType::Typ => 1,
ScaleType::Max => 2,
};
triple[base].map(|v| v * self.scale.factors[want])
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct PulsePercent {
pub(crate) reject: f64,
pub(crate) error: f64,
}
impl PulsePercent {
pub(crate) const DEFAULT: PulsePercent = PulsePercent {
reject: 100.0,
error: 100.0,
};
}
pub(crate) fn to_ticks(v: f64, sdf_unit_exp: i32, precision_exp: i32, tick_exp: i32) -> i64 {
let in_precision = (v * 10f64.powi(sdf_unit_exp - precision_exp)).round();
(in_precision * 10f64.powi(precision_exp - tick_exp)).round() as i64
}
fn percent_of(v: i64, pct: f64) -> i64 {
(v as f64 * pct / 100.0).round() as i64
}
pub(crate) fn iopath_update(
delvals: &[DelVal],
sel: &Selector,
pct: PulsePercent,
convert: &dyn Fn(f64) -> i64,
) -> Result<SlotUpdate, String> {
let mut delays = Vec::with_capacity(delvals.len());
let mut rejects = Vec::with_capacity(delvals.len());
let mut errors = Vec::with_capacity(delvals.len());
for dv in delvals {
let delay = sel.value(dv.delay).map(convert);
let (reject, error) = match dv.pulse {
Some((r, e)) => (sel.value(r).map(convert), sel.value(e).map(convert)),
None => (
delay.map(|d| percent_of(d, pct.reject)),
delay.map(|d| percent_of(d, pct.error)),
),
};
delays.push(delay);
rejects.push(reject);
errors.push(error);
}
Ok(SlotUpdate {
delay: expand_table(&delays)?,
reject: expand_table(&rejects)?,
error: expand_table(&errors)?,
})
}
pub(crate) fn pathpulse_update(
pp: &PathPulse,
sel: &Selector,
convert: &dyn Fn(f64) -> i64,
delays: &PathDelaySet,
) -> SlotUpdate {
let mut up = SlotUpdate::default();
let current = delays.transitions();
for (limit, rv) in [(&mut up.reject, pp.reject), (&mut up.error, pp.error)] {
let Some(v) = sel.value(rv) else { continue };
for (i, slot) in limit.iter_mut().enumerate() {
*slot = Some(if pp.percent {
percent_of(i64::try_from(current[i]).unwrap_or(i64::MAX), v)
} else {
convert(v)
});
}
}
up
}
pub(crate) fn device_delays(cur: [u64; 3], upd: &Slots, mode: DeltaMode) -> [u64; 3] {
let off = upd[2].zip(upd[4]).map(|(a, b)| a.min(b));
let mut out = cur;
for (slot, u) in out.iter_mut().zip([upd[0], upd[1], off]) {
*slot = merge_one(*slot, u, mode);
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::specify::PathTimes;
use vj_abi::bit4::Bit4;
use vj_sdf::ast::{DelVal, DeltaMode, PathPulse, Rvalue};
use vj_sim::netlist::specify::TRANSITIONS;
fn some(v: &[i64]) -> Vec<Option<i64>> {
v.iter().map(|&x| Some(x)).collect()
}
fn slots(v: [i64; 12]) -> Slots {
v.map(Some)
}
const V: [i64; 12] = [5, 3, 9, 2, 8, 6, 11, 4, 12, 1, 10, 7];
#[test]
fn table_32_4_one_value_column() {
assert_eq!(expand_table(&some(&V[..1])).unwrap(), slots([5; 12]));
}
#[test]
fn table_32_4_two_value_column() {
assert_eq!(
expand_table(&some(&V[..2])).unwrap(),
slots([
5, 3, 5, 5, 3, 3, 5, 5, 3, 3, 5,
3
])
);
}
#[test]
fn table_32_4_three_value_column() {
assert_eq!(
expand_table(&some(&V[..3])).unwrap(),
slots([
5, 3, 9, 5, 9, 3, 5,
5,
3,
3,
9,
3,
])
);
}
#[test]
fn table_32_4_six_value_column() {
assert_eq!(
expand_table(&some(&V[..6])).unwrap(),
slots([
5, 3, 9, 2, 8, 6, 5,
5,
3,
6,
9,
2,
])
);
}
#[test]
fn table_32_4_twelve_value_column_is_the_identity() {
assert_eq!(expand_table(&some(&V)).unwrap(), slots(V));
}
#[test]
fn table_32_4_agrees_with_the_source_side_table_30_2_and_30_3() {
for n in [1usize, 2, 3, 6, 12] {
let vals: Vec<u64> = V[..n].iter().map(|&v| v as u64).collect();
let from_source = PathDelaySet::from_list(&vals).unwrap().transitions();
let from_sdf = expand_table(&some(&V[..n])).unwrap();
for (i, t) in TRANSITIONS.iter().enumerate() {
assert_eq!(
from_sdf[i],
Some(from_source[i] as i64),
"{n} values, transition {t:?}"
);
}
}
}
#[test]
fn table_32_4_rejects_a_list_length_with_no_column() {
for n in [0usize, 4, 5, 7, 11, 13] {
let err = expand_table(&vec![Some(1); n]).unwrap_err();
assert!(err.contains(&format!("{n} values")), "{err}");
}
}
#[test]
fn a_held_operand_holds_the_combined_transition() {
let out = expand_table(&[Some(5), None]).unwrap();
let held: Vec<(Bit4, Bit4)> = TRANSITIONS
.iter()
.zip(out)
.filter(|(_, v)| v.is_none())
.map(|(t, _)| *t)
.collect();
assert_eq!(
held,
vec![
(Bit4::One, Bit4::Zero),
(Bit4::One, Bit4::Z),
(Bit4::Z, Bit4::Zero),
(Bit4::One, Bit4::X),
(Bit4::X, Bit4::Zero),
(Bit4::X, Bit4::Z),
(Bit4::Z, Bit4::X),
]
);
assert_eq!(out[0], Some(5), "0->1 is v1");
}
fn sel(mtm: Mtm, factors: [f64; 3], kind: ScaleType) -> Selector {
Selector {
mtm,
scale: Scale { factors, kind },
}
}
const TRIPLE: Rvalue = Rvalue::Triple {
min: Some(1.0),
typ: Some(2.0),
max: Some(4.0),
};
#[test]
fn mtm_spec_picks_the_member() {
let s = |m| sel(m, [1.0; 3], ScaleType::Mtm).value(TRIPLE);
assert_eq!(s(Mtm::Min), Some(1.0));
assert_eq!(s(Mtm::Typ), Some(2.0));
assert_eq!(s(Mtm::Max), Some(4.0));
}
#[test]
fn a_single_value_is_every_member_and_empty_holds() {
let s = sel(Mtm::Max, [1.0; 3], ScaleType::Mtm);
assert_eq!(s.value(Rvalue::Single(7.0)), Some(7.0));
assert_eq!(s.value(Rvalue::Empty), None);
}
#[test]
fn a_missing_selected_member_holds() {
let s = sel(Mtm::Typ, [1.0; 3], ScaleType::Mtm);
let rv = Rvalue::Triple {
min: Some(1.0),
typ: None,
max: Some(3.0),
};
assert_eq!(s.value(rv), None);
assert_eq!(sel(Mtm::Max, [1.0; 3], ScaleType::Mtm).value(rv), Some(3.0));
}
#[test]
fn scale_types_follow_table_32_6() {
let f = [1.6, 1.4, 1.2];
let near = |a: Option<f64>, b: f64| {
let a = a.expect("a value");
assert!((a - b).abs() < 1e-9, "{a} != {b}");
};
near(sel(Mtm::Min, f, ScaleType::Mtm).value(TRIPLE), 1.0 * 1.6);
near(sel(Mtm::Typ, f, ScaleType::Mtm).value(TRIPLE), 2.0 * 1.4);
near(sel(Mtm::Max, f, ScaleType::Mtm).value(TRIPLE), 4.0 * 1.2);
near(sel(Mtm::Max, f, ScaleType::Min).value(TRIPLE), 1.0 * 1.2);
near(sel(Mtm::Min, f, ScaleType::Typ).value(TRIPLE), 2.0 * 1.6);
near(sel(Mtm::Typ, f, ScaleType::Max).value(TRIPLE), 4.0 * 1.4);
}
#[test]
fn option_strings_parse_and_reject_garbage() {
assert_eq!(Mtm::parse(None, Mtm::Typ).unwrap(), Mtm::Typ);
assert_eq!(
Mtm::parse(Some("TOOL_CONTROL"), Mtm::Typ).unwrap(),
Mtm::Typ
);
assert_eq!(Mtm::parse(Some("minimum"), Mtm::Typ).unwrap(), Mtm::Min);
assert!(
Mtm::parse(Some("BEST"), Mtm::Typ)
.unwrap_err()
.contains("mtm_spec")
);
}
#[test]
fn tool_control_follows_the_invocation_option_and_nothing_else_does() {
for tc in [Mtm::Min, Mtm::Typ, Mtm::Max] {
assert_eq!(Mtm::parse(None, tc).unwrap(), tc);
assert_eq!(Mtm::parse(Some("TOOL_CONTROL"), tc).unwrap(), tc);
assert_eq!(Mtm::parse(Some("tool_control"), tc).unwrap(), tc);
assert_eq!(Mtm::parse(Some("MINIMUM"), tc).unwrap(), Mtm::Min);
assert_eq!(Mtm::parse(Some("TYPICAL"), tc).unwrap(), Mtm::Typ);
assert_eq!(Mtm::parse(Some("MAXIMUM"), tc).unwrap(), Mtm::Max);
}
}
#[test]
fn scale_options_parse_and_reject_garbage() {
let s = Scale::parse(Some("1.6:1.4:1.2"), Some("FROM_TYPICAL")).unwrap();
assert_eq!(s.factors, [1.6, 1.4, 1.2]);
assert_eq!(s.kind, ScaleType::Typ);
assert_eq!(Scale::parse(None, None).unwrap(), Scale::IDENTITY);
assert!(Scale::parse(Some("1:2"), None).is_err());
assert!(Scale::parse(Some("1:x:3"), None).is_err());
assert!(Scale::parse(Some("1:-2:3"), None).is_err());
assert!(Scale::parse(None, Some("FROM_NOWHERE")).is_err());
}
#[test]
fn ticks_round_to_the_precision_then_scale_to_the_tick() {
assert_eq!(to_ticks(3.5, -9, -12, -12), 3500);
assert_eq!(to_ticks(3.5, -9, -9, -12), 4000);
assert_eq!(to_ticks(-3.5, -9, -9, -12), -4000);
assert_eq!(to_ticks(2.0, -9, -9, -9), 2);
}
fn dv(delay: Rvalue) -> DelVal {
DelVal { delay, pulse: None }
}
fn dvp(delay: Rvalue, r: Rvalue, e: Rvalue) -> DelVal {
DelVal {
delay,
pulse: Some((r, e)),
}
}
fn one(v: f64) -> Rvalue {
Rvalue::Single(v)
}
fn tens(v: f64) -> i64 {
(v * 10.0).round() as i64
}
const SEL: Selector = Selector {
mtm: Mtm::Typ,
scale: Scale::IDENTITY,
};
fn times(delay: u64, reject: u64, error: u64) -> PathTimes {
PathTimes {
delays: PathDelaySet::uniform(delay),
reject: PathDelaySet::uniform(reject),
error: PathDelaySet::uniform(error),
floor: 0,
}
}
fn rise_fall(t: &PathTimes) -> ((u64, u64, u64), (u64, u64, u64)) {
(
t.limits(Bit4::Zero, Bit4::One),
t.limits(Bit4::One, Bit4::Zero),
)
}
fn pulse(reject: f64, error: f64) -> PathPulse {
PathPulse {
percent: false,
input: None,
output: None,
reject: one(reject),
error: one(error),
}
}
fn io(delvals: &[DelVal], pct: PulsePercent) -> SlotUpdate {
iopath_update(delvals, &SEL, pct, &tens).unwrap()
}
#[test]
fn lrm_32_5_iopath_after_pathpulse_overwrites_the_limits() {
let mut t = times(0, 0, 0);
let pp = pathpulse_update(&pulse(2.1, 3.4), &SEL, &tens, &t.delays);
apply_update(&mut t, &pp, DeltaMode::Absolute);
assert_eq!(t.reject.transitions(), [21; 12]);
assert_eq!(t.error.transitions(), [34; 12]);
let up = io(&[dv(one(3.5)), dv(one(6.1))], PulsePercent::DEFAULT);
apply_update(&mut t, &up, DeltaMode::Absolute);
assert_eq!(rise_fall(&t), ((35, 35, 35), (61, 61, 61)));
}
#[test]
fn lrm_32_5_empty_parentheses_hold_the_pulse_limits() {
let mut t = times(0, 0, 0);
let pp = pathpulse_update(&pulse(2.1, 3.4), &SEL, &tens, &t.delays);
apply_update(&mut t, &pp, DeltaMode::Absolute);
let e = Rvalue::Empty;
let up = io(
&[dvp(one(3.5), e, e), dvp(one(6.1), e, e)],
PulsePercent::DEFAULT,
);
apply_update(&mut t, &up, DeltaMode::Absolute);
assert_eq!(t.delays.of(Bit4::Zero, Bit4::One), 35);
assert_eq!(t.delays.of(Bit4::One, Bit4::Zero), 61);
assert_eq!(rise_fall(&t), ((35, 21, 34), (61, 21, 34)));
}
#[test]
fn lrm_32_5_the_combined_form_gives_the_same_result() {
let mut t = times(0, 0, 0);
let up = io(
&[
dvp(one(3.5), one(2.1), one(3.4)),
dvp(one(6.1), one(2.1), one(3.4)),
],
PulsePercent::DEFAULT,
);
apply_update(&mut t, &up, DeltaMode::Absolute);
assert_eq!(rise_fall(&t), ((35, 21, 34), (61, 21, 34)));
}
#[test]
fn lrm_32_7_default_limits_use_the_percentage_settings() {
let mut t = times(0, 0, 0);
let pct = PulsePercent {
reject: 40.0,
error: 80.0,
};
apply_update(&mut t, &io(&[dv(one(5.0))], pct), DeltaMode::Absolute);
assert_eq!(rise_fall(&t), ((50, 20, 40), (50, 20, 40)));
let mut t = times(0, 0, 0);
apply_update(
&mut t,
&io(&[dv(one(5.0))], PulsePercent::DEFAULT),
DeltaMode::Absolute,
);
assert_eq!(rise_fall(&t), ((50, 50, 50), (50, 50, 50)));
}
#[test]
fn lrm_32_7_empty_pulse_values_on_a_zero_delay_give_zero_limits() {
let mut t = times(0, 0, 0);
let e = Rvalue::Empty;
apply_update(
&mut t,
&io(&[dvp(one(5.0), e, e)], PulsePercent::DEFAULT),
DeltaMode::Absolute,
);
assert_eq!(rise_fall(&t), ((50, 0, 0), (50, 0, 0)));
}
#[test]
fn lrm_32_7_increment_cannot_take_a_limit_below_zero() {
let mut t = PathTimes {
delays: PathDelaySet::uniform(100),
reject: PathDelaySet::uniform(30),
error: PathDelaySet::uniform(30),
floor: 0,
};
let e = Rvalue::Empty;
let up = io(&[dvp(e, one(-4.0), one(-5.0))], PulsePercent::DEFAULT);
apply_update(&mut t, &up, DeltaMode::Increment);
assert_eq!(
t.delays.transitions(),
[100; 12],
"the held delay is untouched"
);
assert_eq!(t.reject.transitions(), [0; 12]);
assert_eq!(t.error.transitions(), [0; 12]);
}
#[test]
fn lrm_32_7_pathpulse_above_the_delay_acts_as_the_delay() {
let mut t = times(30, 30, 30);
let pp = pathpulse_update(&pulse(5.0, 9.0), &SEL, &tens, &t.delays);
apply_update(&mut t, &pp, DeltaMode::Absolute);
assert_eq!(
t.delays.transitions(),
[30; 12],
"PATHPULSE does not touch the delay"
);
assert_eq!(t.reject.transitions(), [50; 12]);
assert_eq!(t.limits(Bit4::Zero, Bit4::One), (30, 30, 30));
let up = io(&[dv(one(4.0))], PulsePercent::DEFAULT);
apply_update(&mut t, &up, DeltaMode::Increment);
assert_eq!(t.delays.of(Bit4::Zero, Bit4::One), 70);
assert_eq!(t.limits(Bit4::Zero, Bit4::One), (70, 70, 70));
}
#[test]
fn pathpulse_percent_is_a_percentage_of_the_current_delay() {
let mut t = times(50, 50, 50);
let pp = PathPulse {
percent: true,
input: None,
output: None,
reject: one(40.0),
error: one(80.0),
};
let up = pathpulse_update(&pp, &SEL, &tens, &t.delays);
apply_update(&mut t, &up, DeltaMode::Absolute);
assert_eq!(t.reject.transitions(), [20; 12]);
assert_eq!(t.error.transitions(), [40; 12]);
assert_eq!(t.delays.transitions(), [50; 12]);
}
#[test]
fn a_held_pathpulse_value_changes_nothing() {
let mut t = times(50, 11, 22);
let pp = PathPulse {
percent: false,
input: None,
output: None,
reject: Rvalue::Empty,
error: Rvalue::Empty,
};
let up = pathpulse_update(&pp, &SEL, &tens, &t.delays);
apply_update(&mut t, &up, DeltaMode::Absolute);
assert_eq!(t.reject.transitions(), [11; 12]);
assert_eq!(t.error.transitions(), [22; 12]);
}
#[test]
fn increment_adds_to_the_delay_and_a_negative_result_is_zero() {
let mut t = times(20, 20, 20);
apply_update(
&mut t,
&io(&[dv(one(1.0))], PulsePercent::DEFAULT),
DeltaMode::Increment,
);
assert_eq!(t.delays.transitions(), [30; 12]);
apply_update(
&mut t,
&io(&[dv(one(-9.0))], PulsePercent::DEFAULT),
DeltaMode::Increment,
);
assert_eq!(t.delays.transitions(), [0; 12], "30 - 90 clamps at 0");
assert_eq!(t.reject.transitions(), [0; 12]);
}
#[test]
fn a_negative_absolute_delay_is_zero() {
let mut t = times(20, 20, 20);
apply_update(
&mut t,
&io(&[dv(one(-3.0))], PulsePercent::DEFAULT),
DeltaMode::Absolute,
);
assert_eq!(t.delays.transitions(), [0; 12]);
}
#[test]
fn the_negative_timing_floor_is_kept() {
let mut t = times(20, 20, 20);
t.floor = 40;
apply_update(
&mut t,
&io(&[dv(one(1.0))], PulsePercent::DEFAULT),
DeltaMode::Absolute,
);
assert_eq!(
t.delays.transitions(),
[40; 12],
"raised to the floor, LRM 31.9.1"
);
}
#[test]
fn the_error_limit_is_never_below_the_reject_limit() {
let mut t = times(100, 100, 100);
let up = io(&[dvp(one(10.0), one(8.0), one(3.0))], PulsePercent::DEFAULT);
apply_update(&mut t, &up, DeltaMode::Absolute);
assert_eq!(t.reject.transitions(), [80; 12]);
assert_eq!(t.error.transitions(), [80; 12]);
}
#[test]
fn min_typ_max_values_go_through_mtm_spec_and_scaling() {
let rv = Rvalue::Triple {
min: Some(1.0),
typ: Some(2.0),
max: Some(3.0),
};
let sel = Selector {
mtm: Mtm::Max,
scale: Scale {
factors: [1.0, 1.0, 2.0],
kind: ScaleType::Mtm,
},
};
let up = iopath_update(&[dv(rv)], &sel, PulsePercent::DEFAULT, &tens).unwrap();
assert_eq!(up.delay, [Some(60); 12], "max 3 x2 = 6 units of 10 ticks");
}
#[test]
fn an_iopath_with_a_bad_value_count_is_an_error() {
let err = iopath_update(
&[dv(one(1.0)), dv(one(2.0)), dv(one(3.0)), dv(one(4.0))],
&SEL,
PulsePercent::DEFAULT,
&tens,
)
.unwrap_err();
assert!(err.contains("4 values"), "{err}");
}
fn device(vals: &[f64], cur: [u64; 3], mode: DeltaMode) -> [u64; 3] {
let dvs: Vec<DelVal> = vals.iter().map(|&v| dv(one(v))).collect();
let up = iopath_update(&dvs, &SEL, PulsePercent::DEFAULT, &tens).unwrap();
device_delays(cur, &up.delay, mode)
}
#[test]
fn device_maps_one_two_and_three_values_onto_rise_fall_turn_off() {
let abs = DeltaMode::Absolute;
assert_eq!(device(&[2.0], [7, 7, 7], abs), [20, 20, 20]);
assert_eq!(device(&[5.0, 3.0], [7, 7, 7], abs), [50, 30, 30]);
assert_eq!(device(&[3.0, 5.0], [7, 7, 7], abs), [30, 50, 30]);
assert_eq!(device(&[5.0, 3.0, 4.0], [7, 7, 7], abs), [50, 30, 40]);
}
#[test]
fn device_increment_adds_to_each_delay_and_floors_at_zero() {
let inc = DeltaMode::Increment;
assert_eq!(device(&[1.0, 2.0, 3.0], [10, 20, 30], inc), [20, 40, 60]);
assert_eq!(device(&[-5.0], [10, 20, 30], inc), [0, 0, 0]);
}
#[test]
fn device_holds_a_delay_whose_value_is_empty_parentheses() {
let up = iopath_update(
&[dv(one(5.0)), dv(Rvalue::Empty), dv(one(4.0))],
&SEL,
PulsePercent::DEFAULT,
&tens,
)
.unwrap();
assert_eq!(
device_delays([7, 8, 9], &up.delay, DeltaMode::Absolute),
[50, 8, 40]
);
let up = iopath_update(
&[dv(one(5.0)), dv(Rvalue::Empty)],
&SEL,
PulsePercent::DEFAULT,
&tens,
)
.unwrap();
assert_eq!(
device_delays([7, 8, 9], &up.delay, DeltaMode::Absolute),
[50, 8, 9]
);
}
}