use std::collections::{HashMap, HashSet};
use std::fmt;
use std::io::{BufRead, Seek, Write};
use std::path::Path;
use crossbeam_channel as channel;
use fst_reader::{FstFilter, FstSignalValue};
use crate::{names_only, next_vcd_change, NameOptions, NameTree, SignalMap, SignalNames, WaveHierarchy, WaveReader};
const CHANNEL_BOUND: usize = 64;
const BATCH_SIZE: usize = 4096;
#[derive(Debug, Clone)]
enum OwnedSignalValue {
String(Vec<u8>),
Real(f64),
}
impl PartialEq for OwnedSignalValue {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(OwnedSignalValue::String(a), OwnedSignalValue::String(b)) => a == b,
(OwnedSignalValue::Real(a), OwnedSignalValue::Real(b)) => a == b,
_ => self.to_string() == other.to_string(),
}
}
}
impl fmt::Display for OwnedSignalValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
OwnedSignalValue::String(s) => {
write!(f, "{}", std::str::from_utf8(s).unwrap_or("<invalid utf8>"))
}
OwnedSignalValue::Real(r) => write!(f, "{r}"),
}
}
}
impl OwnedSignalValue {
fn from_fst_value(value: FstSignalValue) -> Self {
match value {
FstSignalValue::String(s) => OwnedSignalValue::String(s.to_vec()),
FstSignalValue::Real(r) => OwnedSignalValue::Real(r),
}
}
fn approx_eq(&self, other: &Self, real_epsilon: Option<f64>) -> bool {
match (self, other, real_epsilon) {
(OwnedSignalValue::Real(a), OwnedSignalValue::Real(b), Some(eps)) => {
(a - b).abs() <= eps
}
(_, _, Some(eps)) => {
if self == other {
return true;
}
if let (Ok(a), Ok(b)) = (
self.to_string().parse::<f64>(),
other.to_string().parse::<f64>(),
) {
(a - b).abs() <= eps
} else {
false
}
}
_ => self == other,
}
}
}
pub fn compare_signal_names(
hier1: &WaveHierarchy,
hier2: &WaveHierarchy,
) -> (HashSet<String>, HashSet<String>) {
let set1: HashSet<String> = hier1
.signal_map
.values()
.flat_map(|info| info.vars.iter().map(|v| hier1.names.format_path(v.name)))
.collect();
let set2: HashSet<String> = hier2
.signal_map
.values()
.flat_map(|info| info.vars.iter().map(|v| hier2.names.format_path(v.name)))
.collect();
let only_in_1: HashSet<String> = set1.difference(&set2).cloned().collect();
let only_in_2: HashSet<String> = set2.difference(&set1).cloned().collect();
(only_in_1, only_in_2)
}
fn build_name_id_to_handles(map: &SignalMap) -> HashMap<crate::NameId, Vec<usize>> {
let mut result: HashMap<crate::NameId, Vec<usize>> = HashMap::new();
for (&handle, info) in map {
for var in &info.vars {
result.entry(var.name).or_default().push(handle);
}
}
result
}
fn build_handle_mapping(
map_a: &SignalMap,
tree_a: &NameTree,
map_b: &SignalMap,
tree_b: &NameTree,
) -> HashMap<usize, Vec<usize>> {
let name_id_to_handles_b = build_name_id_to_handles(map_b);
let mut handle_mapping: HashMap<usize, Vec<usize>> = HashMap::new();
for (&handle_a, info) in map_a {
let mut handles_b = Vec::new();
for var in &info.vars {
let segments = tree_a.segments(var.name);
if let Some(b_name_id) = tree_b.find(&segments) {
if let Some(b_handles) = name_id_to_handles_b.get(&b_name_id) {
handles_b.extend(b_handles);
}
}
}
if !handles_b.is_empty() {
handle_mapping.insert(handle_a, handles_b);
}
}
handle_mapping
}
#[derive(Debug)]
struct SignalChange {
handle: usize,
value: OwnedSignalValue,
}
#[derive(Debug)]
struct TimeBatch {
time: u64,
changes: Vec<SignalChange>,
}
fn flush_batch(tx: &channel::Sender<TimeBatch>, time: u64, changes: &mut Vec<SignalChange>) {
let full = std::mem::replace(changes, Vec::with_capacity(BATCH_SIZE));
let _ = tx.send(TimeBatch { time, changes: full });
}
fn read_and_send_signals<R: BufRead + Seek>(
mut fst_reader: fst_reader::FstReader<R>,
filter: fst_reader::FstFilter,
handle_offset: usize,
tx: channel::Sender<TimeBatch>,
) {
let mut batch = Vec::with_capacity(BATCH_SIZE);
let mut batch_time: u64 = 0;
let _ = fst_reader.read_signals(&filter, |time, handle, value| {
if time < filter.start {
return;
}
if let Some(e) = filter.end {
if time > e {
return;
}
}
if !batch.is_empty() && (time != batch_time || batch.len() >= BATCH_SIZE) {
flush_batch(&tx, batch_time, &mut batch);
}
batch_time = time;
batch.push(SignalChange {
handle: handle.get_index() + handle_offset,
value: OwnedSignalValue::from_fst_value(value),
});
});
if !batch.is_empty() {
let _ = tx.send(TimeBatch { time: batch_time, changes: batch });
}
}
fn compare_signal_channels<W: Write>(
writer: &mut W,
rx1: channel::Receiver<TimeBatch>,
rx2: channel::Receiver<TimeBatch>,
handle_mapping: &HashMap<usize, Vec<usize>>,
handle_to_names1: &SignalNames,
handle_to_names2: &SignalNames,
real_epsilon: Option<f64>,
) -> std::io::Result<bool> {
let mut has_differences = false;
let mut buffered2: HashMap<(u64, usize), OwnedSignalValue> = HashMap::new();
let mut matched_at_current_time: HashSet<usize> = HashSet::new();
let mut prev_time: Option<u64> = None;
let mut source2_ended = false;
for batch1 in &rx1 {
if let Some(pt) = prev_time {
if pt != batch1.time {
for &handle in &matched_at_current_time {
buffered2.remove(&(pt, handle));
}
matched_at_current_time.clear();
}
}
prev_time = Some(batch1.time);
let t1 = batch1.time;
for change1 in batch1.changes {
if let Some(handles2) = handle_mapping.get(&change1.handle) {
for &handle2 in handles2 {
let mut found = buffered2.get(&(t1, handle2)).cloned();
let mut saw_time_in_source2 = false;
if found.is_none() && !source2_ended {
loop {
match rx2.recv() {
Ok(batch2) => {
let t2 = batch2.time;
if t2 == t1 {
saw_time_in_source2 = true;
}
let mut found_in_batch = false;
for c in batch2.changes {
if t2 == t1 && c.handle == handle2 && found.is_none() {
found = Some(c.value.clone());
found_in_batch = true;
}
buffered2.insert((t2, c.handle), c.value);
}
if found_in_batch || t2 > t1 {
break;
}
}
Err(_) => {
source2_ended = true;
break;
}
}
}
}
if let Some(value2) = found {
matched_at_current_time.insert(handle2);
if !change1.value.approx_eq(&value2, real_epsilon) {
has_differences = true;
if let Some(names) = handle_to_names1.get(&change1.handle) {
for name in names {
writeln!(writer, "{} {} {} != {}", t1, name, change1.value, value2)?;
}
}
}
} else {
has_differences = true;
if let Some(names) = handle_to_names1.get(&change1.handle) {
for name in names {
let msg = if saw_time_in_source2 {
"(not in file2)"
} else {
"(missing time in file2)"
};
writeln!(writer, "{} {} {} {}", t1, name, change1.value, msg)?;
}
}
}
}
}
}
}
if let Some(pt) = prev_time {
for &handle in &matched_at_current_time {
buffered2.remove(&(pt, handle));
}
}
for ((time, handle), value) in &buffered2 {
has_differences = true;
if let Some(names) = handle_to_names2.get(handle) {
for name in names {
writeln!(writer, "{} {} {} (only in file2)", time, name, value)?;
}
}
}
if !source2_ended {
for batch2 in &rx2 {
has_differences = true;
for change2 in &batch2.changes {
if let Some(names) = handle_to_names2.get(&change2.handle) {
for name in names {
writeln!(
writer,
"{} {} {} (only in file2)",
batch2.time, name, change2.value
)?;
}
}
}
}
}
Ok(has_differences)
}
fn send_wave_changes(
reader: WaveReader,
handle_offset: usize,
start: u64,
end: Option<u64>,
tx: channel::Sender<TimeBatch>,
) {
match reader {
WaveReader::Fst(fst_reader) => {
let filter = FstFilter {
start,
end,
include: None,
};
read_and_send_signals(*fst_reader, filter, handle_offset, tx);
}
WaveReader::Vcd(mut vcd_data) => {
let mut batch = Vec::with_capacity(BATCH_SIZE);
let mut batch_time: u64 = 0;
while let Some((time, handle, value_str)) = next_vcd_change(&mut vcd_data) {
if time < start {
continue;
}
if let Some(e) = end {
if time > e {
break;
}
}
if !batch.is_empty() && (time != batch_time || batch.len() >= BATCH_SIZE) {
flush_batch(&tx, batch_time, &mut batch);
}
batch_time = time;
batch.push(SignalChange {
handle: handle + handle_offset,
value: OwnedSignalValue::String(value_str.into_bytes()),
});
}
if !batch.is_empty() {
let _ = tx.send(TimeBatch { time: batch_time, changes: batch });
}
}
}
}
fn send_merged_wave_changes(
readers: Vec<WaveReader>,
offsets: &[usize],
start: u64,
end: Option<u64>,
tx: channel::Sender<TimeBatch>,
) {
if readers.len() == 1 {
send_wave_changes(
readers.into_iter().next().unwrap(),
offsets[0],
start,
end,
tx,
);
return;
}
let mut inner_rxs = Vec::with_capacity(readers.len());
let mut threads = Vec::new();
for (reader, &offset) in readers.into_iter().zip(offsets.iter()) {
let (inner_tx, inner_rx) = channel::bounded(CHANNEL_BOUND);
threads.push(std::thread::spawn(move || {
send_wave_changes(reader, offset, start, end, inner_tx);
}));
inner_rxs.push(inner_rx);
}
let mut heads: Vec<Option<TimeBatch>> = inner_rxs.iter().map(|rx| rx.recv().ok()).collect();
loop {
let min_idx = heads
.iter()
.enumerate()
.filter_map(|(i, opt)| opt.as_ref().map(|b| (i, b.time)))
.min_by_key(|&(_, t)| t)
.map(|(i, _)| i);
match min_idx {
Some(idx) => {
let _ = tx.send(heads[idx].take().unwrap());
heads[idx] = inner_rxs[idx].recv().ok();
}
None => break,
}
}
for t in threads {
t.join().unwrap();
}
}
pub fn compare_signal_meta(
hier1: &WaveHierarchy,
hier2: &WaveHierarchy,
) -> Vec<String> {
let mut diffs = Vec::new();
let entries1: HashMap<String, &crate::VarEntry> = hier1
.signal_map
.values()
.flat_map(|info| info.vars.iter().map(|v| (hier1.names.format_path(v.name), v)))
.collect();
let entries2: HashMap<String, &crate::VarEntry> = hier2
.signal_map
.values()
.flat_map(|info| info.vars.iter().map(|v| (hier2.names.format_path(v.name), v)))
.collect();
let mut common: Vec<&String> = entries1.keys().filter(|n| entries2.contains_key(*n)).collect();
common.sort();
for name in common {
let v1 = entries1[name];
let v2 = entries2[name];
if v1.meta.var_type != v2.meta.var_type {
diffs.push(format!("{}: type {} != {}", name, v1.meta.var_type, v2.meta.var_type));
}
if v1.meta.size != v2.meta.size {
diffs.push(format!("{}: size {} != {}", name, v1.meta.size, v2.meta.size));
}
if v1.meta.direction != crate::IMPLICIT_DIRECTION
&& v2.meta.direction != crate::IMPLICIT_DIRECTION
&& v1.meta.direction != v2.meta.direction
{
diffs.push(format!(
"{}: direction {} != {}",
name, v1.meta.direction, v2.meta.direction
));
}
if v1.attrs != v2.attrs {
let a1 = if v1.attrs.is_empty() { "(none)".to_string() } else { v1.attrs.join("; ") };
let a2 = if v2.attrs.is_empty() { "(none)".to_string() } else { v2.attrs.join("; ") };
diffs.push(format!("{}: attrs [{}] != [{}]", name, a1, a2));
}
}
diffs
}
pub fn open_and_read_waves<P1: AsRef<Path>, P2: AsRef<Path>>(
path1: P1,
path2: P2,
options: &NameOptions,
) -> Result<(WaveReader, WaveHierarchy, WaveReader, WaveHierarchy), String> {
let (r1, h1) = crate::open_wave_file(path1.as_ref(), options)?;
let (r2, h2) = crate::open_wave_file(path2.as_ref(), options)?;
Ok((r1, h1, r2, h2))
}
#[allow(clippy::too_many_arguments)]
pub fn diff_waves<W: Write>(
writer: &mut W,
reader1: WaveReader,
hier1: &WaveHierarchy,
reader2: WaveReader,
hier2: &WaveHierarchy,
start: u64,
end: Option<u64>,
real_epsilon: Option<f64>,
) -> std::io::Result<bool> {
diff_wave_sets(
writer,
vec![reader1],
hier1,
&[0],
vec![reader2],
hier2,
&[0],
start,
end,
real_epsilon,
)
}
#[allow(clippy::too_many_arguments)]
pub fn diff_wave_sets<W: Write>(
writer: &mut W,
readers1: Vec<WaveReader>,
hier1: &WaveHierarchy,
offsets1: &[usize],
readers2: Vec<WaveReader>,
hier2: &WaveHierarchy,
offsets2: &[usize],
start: u64,
end: Option<u64>,
real_epsilon: Option<f64>,
) -> std::io::Result<bool> {
let handle_to_names1 = names_only(&hier1.signal_map, &hier1.names);
let handle_to_names2 = names_only(&hier2.signal_map, &hier2.names);
let handle_mapping = build_handle_mapping(
&hier1.signal_map,
&hier1.names,
&hier2.signal_map,
&hier2.names,
);
let (tx1, rx1) = channel::bounded(CHANNEL_BOUND);
let (tx2, rx2) = channel::bounded(CHANNEL_BOUND);
let offsets1 = offsets1.to_vec();
let offsets2 = offsets2.to_vec();
let thread1 = std::thread::spawn(move || {
send_merged_wave_changes(readers1, &offsets1, start, end, tx1);
});
let thread2 = std::thread::spawn(move || {
send_merged_wave_changes(readers2, &offsets2, start, end, tx2);
});
let result = compare_signal_channels(
writer,
rx1,
rx2,
&handle_mapping,
&handle_to_names1,
&handle_to_names2,
real_epsilon,
);
thread1.join().unwrap();
thread2.join().unwrap();
result
}
#[allow(clippy::type_complexity)]
pub fn open_and_read_wave_sets(
paths1: &[&Path],
paths2: &[&Path],
options: &NameOptions,
) -> Result<
(
Vec<WaveReader>,
WaveHierarchy,
Vec<usize>,
Vec<WaveReader>,
WaveHierarchy,
Vec<usize>,
),
String,
> {
let (r1, h1, o1) = crate::open_wave_files(paths1, options, None)?;
let (r2, h2, o2) = crate::open_wave_files(paths2, options, None)?;
Ok((r1, h1, o1, r2, h2, o2))
}