///|
/// Reusable stimulus patterns for deterministic waveform fixtures.
pub enum SynthPattern {
ConstantLow
ConstantHigh
Toggle
PulseTrain
Counter
RampReal
} derive(Debug, Eq)
///|
/// A signal stimulus declaration.
pub struct SynthSignal {
id : String
name : String
width : Int
pattern : SynthPattern
period : Int64
phase : Int64
seed : Int64
} derive(Debug)
///|
/// A complete synthetic trace plan.
pub struct SynthPlan {
end_time : Int64
signals : Array[SynthSignal]
timescale : String
} derive(Debug)
///|
/// List deterministic stimulus patterns.
pub fn synth_pattern_catalog() -> Array[SynthPattern] {
[ConstantLow, ConstantHigh, Toggle, PulseTrain, Counter, RampReal]
}
///|
/// Create an empty synthetic plan.
pub fn synth_plan(end_time : Int64) -> SynthPlan {
{ end_time: Int64::max(0L, end_time), signals: [], timescale: "1ns" }
}
///|
/// Add a signal stimulus to a plan.
pub fn SynthPlan::with_signal(
self : SynthPlan,
id : String,
name : String,
width : Int,
pattern : SynthPattern,
period : Int64,
) -> SynthPlan {
let signals = self.signals.copy()
signals.push({
id,
name,
width: Int::max(1, width),
pattern,
period: Int64::max(1L, period),
phase: 0L,
seed: 0L,
})
{ ..self, signals, }
}
///|
/// Add a phase offset and initial seed to the last signal.
pub fn SynthPlan::with_last_signal_seed(
self : SynthPlan,
phase : Int64,
seed : Int64,
) -> SynthPlan {
if self.signals.length() == 0 {
return self
}
let signals = self.signals.copy()
let index = signals.length() - 1
let signal = signals[index]
signals[index] = { ..signal, phase, seed }
{ ..self, signals, }
}
///|
/// Generate a valid trace from a synthetic plan.
pub fn synthesize_trace(plan : SynthPlan) -> Trace {
let signals = plan.signals.map(fn(item) {
{ id: item.id, name: item.name, width: item.width, kind: "wire", scope: [] }
})
let changes : Array[Change] = []
for signal in plan.signals {
let mut time = Int64::max(0L, signal.phase)
while time <= plan.end_time {
let value = synth_value(signal, time)
changes.push({ time, id: signal.id, value })
time += signal.period
}
}
let ordered = sort_trace_changes({
header: {
date: None,
version: Some("synth"),
timescale: Some(plan.timescale),
comment: None,
},
signals,
changes,
end_time: plan.end_time,
})
ordered
}
///|
/// Generate a pulse fixture with clock and payload signals.
pub fn synthesize_pulse_fixture(end_time : Int64, period : Int64) -> Trace {
synthesize_trace(
synth_plan(end_time)
.with_signal("clk", "clk", 1, Toggle, period)
.with_signal("pulse", "pulse", 1, PulseTrain, period * 4L),
)
}
///|
/// Render a plan as a compact text description.
pub fn SynthPlan::to_text(self : SynthPlan) -> String {
"end=" +
self.end_time.to_string() +
" signals=" +
self.signals.length().to_string() +
" timescale=" +
self.timescale
}
///|
/// Render a synthetic plan as JSON.
pub fn SynthPlan::to_json(self : SynthPlan) -> String {
let signals = self.signals.map(synth_signal_json).join(",")
"{\"end_time\":" +
self.end_time.to_string() +
",\"timescale\":\"" +
self.timescale +
"\",\"signals\":[" +
signals +
"]}"
}
///|
/// Produce one value at a synthetic timestamp.
fn synth_value(signal : SynthSignal, time : Int64) -> Value {
let phase_time = Int64::max(0L, time - signal.phase)
match signal.pattern {
ConstantLow => Scalar('0')
ConstantHigh => Scalar('1')
Toggle =>
Scalar(if phase_time / signal.period % 2L == 0L { '0' } else { '1' })
PulseTrain =>
Scalar(if phase_time % signal.period == 0L { '1' } else { '0' })
Counter =>
Vector(
synth_counter_bits(
signal.seed + phase_time / signal.period,
signal.width,
),
)
RampReal =>
Real(
signal.seed.to_double() +
phase_time.to_double() / signal.period.to_double(),
)
}
}
///|
/// Render a counter value with a fixed width.
fn synth_counter_bits(value : Int64, width : Int) -> String {
let result : Array[String] = []
for index in 0..> index) & 1L
result.insert(0, if bit == 1L { "1" } else { "0" })
}
result.join("")
}
///|
/// Encode one synthetic signal.
fn synth_signal_json(item : SynthSignal) -> String {
"{\"id\":\"" +
escape_text(item.id) +
"\",\"name\":\"" +
escape_text(item.name) +
"\",\"width\":" +
item.width.to_string() +
",\"pattern\":\"" +
synth_pattern_name(item.pattern) +
"\",\"period\":" +
item.period.to_string() +
"}"
}
///|
/// Name a synthetic pattern.
pub fn synth_pattern_name(value : SynthPattern) -> String {
match value {
ConstantLow => "constant-low"
ConstantHigh => "constant-high"
Toggle => "toggle"
PulseTrain => "pulse-train"
Counter => "counter"
RampReal => "ramp-real"
}
}