use crate::circuit::Circuit;
use crate::circuit_view::CircuitView;
use std::collections::BTreeSet;
use std::fmt::Debug;
pub struct Testbench<'a> {
circuit: &'a dyn CircuitView,
cycles: Option<usize>,
period_ps: Option<f64>,
stimuli: Vec<StimulusSpec>,
observe: Vec<String>,
}
pub(crate) struct NormalizedTestbench<'a> {
pub(crate) name: &'a str,
pub(crate) circuit: &'a dyn CircuitView,
pub(crate) cycles: usize,
pub(crate) period_ps: f64,
pub(crate) signals: Vec<SignalPattern>,
pub(crate) observe: Vec<String>,
}
#[derive(Clone)]
pub(crate) struct SignalPattern {
pub(crate) name: String,
pub(crate) values: Vec<u8>,
pub(crate) phase: f64,
}
enum StimulusSpec {
Signal {
name: String,
values: Vec<u64>,
phase: f64,
},
Signals {
names: Vec<String>,
values: Vec<u64>,
phase: f64,
},
Constant {
name: String,
value: u64,
phase: f64,
},
Pulse {
name: String,
cycles: Vec<usize>,
phase: f64,
},
Toggle {
name: String,
cycles: Vec<usize>,
phase: f64,
},
}
pub trait TestbenchBackend {
fn generate(&self, testbench: &Testbench<'_>) -> String;
}
impl<'a> Testbench<'a> {
pub fn new<const N_I: usize, const N_CI: usize, const N_O: usize, const N_CO: usize>(
circuit: &'a Circuit<N_I, N_CI, N_O, N_CO>,
) -> Self {
Self {
circuit,
cycles: None,
period_ps: None,
stimuli: Vec::new(),
observe: Vec::new(),
}
}
pub fn cycles(mut self, cycles: usize) -> Self {
assert!(cycles > 0, "testbench cycles must be greater than zero");
self.cycles = Some(cycles);
self
}
pub fn period_ps(mut self, period_ps: f64) -> Self {
assert!(
period_ps.is_finite() && period_ps > 0.0,
"testbench period_ps must be a positive finite number"
);
self.period_ps = Some(period_ps);
self
}
pub fn signal<I, V>(mut self, name: &str, values: I, phase: f64) -> Self
where
I: IntoIterator<Item = V>,
V: TryInto<u64>,
V::Error: Debug,
{
self.stimuli.push(StimulusSpec::Signal {
name: name.to_string(),
values: collect_numbers(values),
phase,
});
self
}
pub fn signals<N, I, V>(mut self, names: N, values: I, phase: f64) -> Self
where
N: IntoIterator,
N::Item: AsRef<str>,
I: IntoIterator<Item = V>,
V: TryInto<u64>,
V::Error: Debug,
{
self.stimuli.push(StimulusSpec::Signals {
names: names
.into_iter()
.map(|name| name.as_ref().to_string())
.collect(),
values: collect_numbers(values),
phase,
});
self
}
pub fn constant<V>(mut self, name: &str, value: V, phase: f64) -> Self
where
V: TryInto<u64>,
V::Error: Debug,
{
self.stimuli.push(StimulusSpec::Constant {
name: name.to_string(),
value: value
.try_into()
.expect("constant value must be convertible to u64"),
phase,
});
self
}
pub fn pulse<I>(mut self, name: &str, cycles: I, phase: f64) -> Self
where
I: IntoIterator<Item = usize>,
{
self.stimuli.push(StimulusSpec::Pulse {
name: name.to_string(),
cycles: cycles.into_iter().collect(),
phase,
});
self
}
pub fn toggle<I>(mut self, name: &str, cycles: I, phase: f64) -> Self
where
I: IntoIterator<Item = usize>,
{
self.stimuli.push(StimulusSpec::Toggle {
name: name.to_string(),
cycles: cycles.into_iter().collect(),
phase,
});
self
}
pub fn observe<N>(mut self, names: N) -> Self
where
N: IntoIterator,
N::Item: AsRef<str>,
{
self.observe
.extend(names.into_iter().map(|name| name.as_ref().to_string()));
self
}
pub fn generate<B: TestbenchBackend>(&self, backend: B) -> String {
backend.generate(self)
}
pub fn print<B: TestbenchBackend>(&self, backend: B) {
println!("{}", self.generate(backend));
}
pub(crate) fn normalize(&self) -> NormalizedTestbench<'_> {
let cycles = self
.cycles
.expect("testbench cycles must be specified before generation");
let input_ports = self.circuit.in_ports();
let input_set: BTreeSet<&str> = input_ports.iter().copied().collect();
let mut signals = Vec::new();
let mut specified_signals = BTreeSet::new();
for stimulus in &self.stimuli {
match stimulus {
StimulusSpec::Signal {
name,
values,
phase,
} => {
assert_input_name(name, &input_set);
insert_signal(
&mut signals,
&mut specified_signals,
SignalPattern {
name: name.clone(),
values: expand_signal(values, cycles),
phase: validate_phase(*phase),
},
);
}
StimulusSpec::Signals {
names,
values,
phase,
} => {
assert!(!names.is_empty(), "signals must include at least one name");
for name in names {
assert_input_name(name, &input_set);
}
let expanded = expand_signals(names, values, cycles);
let phase = validate_phase(*phase);
for (name, values) in expanded {
insert_signal(
&mut signals,
&mut specified_signals,
SignalPattern {
name,
values,
phase,
},
);
}
}
StimulusSpec::Constant { name, value, phase } => {
assert_input_name(name, &input_set);
let value = validate_bit(*value);
insert_signal(
&mut signals,
&mut specified_signals,
SignalPattern {
name: name.clone(),
values: vec![value; cycles],
phase: validate_phase(*phase),
},
);
}
StimulusSpec::Pulse {
name,
cycles: pulse_cycles,
phase,
} => {
assert_input_name(name, &input_set);
let mut values = vec![0; cycles];
for cycle in pulse_cycles {
assert!(
*cycle < cycles,
"pulse cycle {} is outside testbench length {}",
cycle,
cycles
);
values[*cycle] = 1;
}
insert_signal(
&mut signals,
&mut specified_signals,
SignalPattern {
name: name.clone(),
values,
phase: validate_phase(*phase),
},
);
}
StimulusSpec::Toggle {
name,
cycles: toggle_cycles,
phase,
} => {
assert_input_name(name, &input_set);
let toggle_set: BTreeSet<usize> = toggle_cycles.iter().copied().collect();
for cycle in &toggle_set {
assert!(
*cycle < cycles,
"toggle cycle {} is outside testbench length {}",
cycle,
cycles
);
}
let mut values = Vec::with_capacity(cycles);
let mut current = 0;
for cycle in 0..cycles {
if toggle_set.contains(&cycle) {
current ^= 1;
}
values.push(current);
}
insert_signal(
&mut signals,
&mut specified_signals,
SignalPattern {
name: name.clone(),
values,
phase: validate_phase(*phase),
},
);
}
}
}
for port in input_ports {
assert!(
specified_signals.contains(port),
"input port `{}` has no testbench stimulus",
port
);
}
let mut observe = Vec::new();
let mut observed = BTreeSet::new();
for signal in &signals {
if observed.insert(signal.name.clone()) {
observe.push(signal.name.clone());
}
}
for name in &self.observe {
if observed.insert(name.clone()) {
observe.push(name.clone());
}
}
NormalizedTestbench {
name: self.circuit.name(),
circuit: self.circuit,
cycles,
period_ps: self
.period_ps
.expect("testbench period_ps must be specified before generation"),
signals,
observe,
}
}
}
fn collect_numbers<I, V>(values: I) -> Vec<u64>
where
I: IntoIterator<Item = V>,
V: TryInto<u64>,
V::Error: Debug,
{
values
.into_iter()
.map(|value| value.try_into().expect("value must be convertible to u64"))
.collect()
}
fn assert_input_name(name: &str, input_set: &BTreeSet<&str>) {
assert!(
input_set.contains(name),
"testbench stimulus `{}` is not a circuit input port",
name
);
}
fn validate_phase(phase: f64) -> f64 {
assert!(
phase.is_finite() && (0.0..1.0).contains(&phase),
"testbench phase must satisfy 0.0 <= phase < 1.0"
);
phase
}
fn validate_bit(value: u64) -> u8 {
assert!(value <= 1, "1-bit stimulus value must be 0 or 1");
value as u8
}
fn insert_signal(
signals: &mut Vec<SignalPattern>,
specified_signals: &mut BTreeSet<String>,
signal: SignalPattern,
) {
assert!(
specified_signals.insert(signal.name.clone()),
"testbench stimulus for `{}` is specified more than once",
signal.name
);
signals.push(signal);
}
fn expand_signal(values: &[u64], cycles: usize) -> Vec<u8> {
assert!(
values.len() <= cycles,
"signal stimulus has {} values, but testbench has only {} cycles",
values.len(),
cycles
);
let mut expanded: Vec<u8> = values.iter().map(|value| validate_bit(*value)).collect();
expanded.resize(cycles, 0);
expanded
}
fn expand_signals(names: &[String], values: &[u64], cycles: usize) -> Vec<(String, Vec<u8>)> {
assert!(
values.len() <= cycles,
"signals stimulus has {} values, but testbench has only {} cycles",
values.len(),
cycles
);
let width = names.len();
assert!(
width < u64::BITS as usize,
"signals width {} is too large for u64 values",
width
);
let max_value = (1u64 << width) - 1;
let mut expanded: Vec<(String, Vec<u8>)> = names
.iter()
.map(|name| (name.clone(), Vec::new()))
.collect();
for value in values {
assert!(
*value <= max_value,
"signals value {} does not fit in {} bits",
value,
width
);
for (index, (_, bits)) in expanded.iter_mut().enumerate() {
let shift = width - 1 - index;
bits.push(((value >> shift) & 1) as u8);
}
}
for (_, bits) in &mut expanded {
bits.resize(cycles, 0);
}
expanded
}