From 190f2c9ce20dc0843b1898664abf833d807ba360 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20L=C3=A4ufer?= Date: Thu, 9 Nov 2023 14:31:49 -0500 Subject: [PATCH] diff: actually compare some value changes --- src/hierarchy.rs | 2 +- src/signals.rs | 75 ++++++++++++++++++++++++++++++++++++++++++--- tests/diff_tests.rs | 49 +++++++++++++++++++++++++---- 3 files changed, 114 insertions(+), 12 deletions(-) diff --git a/src/hierarchy.rs b/src/hierarchy.rs index 9e827d9..04a1d02 100644 --- a/src/hierarchy.rs +++ b/src/hierarchy.rs @@ -137,7 +137,7 @@ pub struct SignalRef(NonZeroU32); impl SignalRef { #[inline] - pub(crate) fn from_index(index: usize) -> Option { + pub fn from_index(index: usize) -> Option { match NonZeroU32::new(index as u32 + 1) { None => None, Some(value) => Some(Self(value)), diff --git a/src/signals.rs b/src/signals.rs index a9cc036..f0efd9e 100644 --- a/src/signals.rs +++ b/src/signals.rs @@ -8,6 +8,7 @@ use std::collections::HashMap; use std::fmt::{Debug, Display, Formatter}; pub type Time = u64; +pub type TimeTableIdx = u32; #[derive(Debug, Clone, Copy)] pub enum SignalValue<'a> { @@ -33,7 +34,7 @@ impl<'a> Display for SignalValue<'a> { } impl<'a> SignalValue<'a> { - fn to_bit_string(&self) -> Option { + pub fn to_bit_string(&self) -> Option { match &self { SignalValue::Binary(data, bits) => Some(two_state_to_bit_string(data, *bits)), SignalValue::FourValue(data, bits) => Some(four_state_to_bit_string(data, *bits)), @@ -115,14 +116,14 @@ pub(crate) enum SignalEncoding { pub(crate) struct Signal { idx: SignalRef, - time_indices: Vec, + time_indices: Vec, data: SignalChangeData, } impl Signal { pub(crate) fn new_fixed_len( idx: SignalRef, - time_indices: Vec, + time_indices: Vec, encoding: SignalEncoding, width: u32, bytes: Vec, @@ -142,7 +143,7 @@ impl Signal { pub(crate) fn new_var_len( idx: SignalRef, - time_indices: Vec, + time_indices: Vec, strings: Vec, ) -> Self { assert_eq!(time_indices.len(), strings.len()); @@ -156,7 +157,7 @@ impl Signal { pub(crate) fn size_in_memory(&self) -> usize { let base = std::mem::size_of::(); - let time = self.time_indices.len() * std::mem::size_of::(); + let time = self.time_indices.len() * std::mem::size_of::(); let data = match &self.data { SignalChangeData::FixedLength { bytes, .. } => bytes.len(), SignalChangeData::VariableLength(strings) => strings @@ -225,6 +226,41 @@ impl Waveform { pub fn get_signal_size_in_memory(&self, id: SignalRef) -> Option { Some((self.signals.get(&id)?).size_in_memory()) } + + pub fn get_signal_value_at( + &self, + signal_ref: SignalRef, + time_table_idx: TimeTableIdx, + ) -> SignalValue { + assert!((time_table_idx as usize) < self.time_table.len()); + let signal: &Signal = &self.signals[&signal_ref]; + let offset = find_offset_from_time_table_idx(&signal.time_indices, time_table_idx); + signal.data.get_value_at(offset) + } +} + +/// Finds the index that is the same or less than the needle and returns the position of it. +/// Note that `indices` needs to sorted from smallest to largest. +/// Essentially implements a binary search! +fn find_offset_from_time_table_idx(indices: &[TimeTableIdx], needle: TimeTableIdx) -> usize { + let mut lower_idx = 0usize; + let mut upper_idx = indices.len() - 1; + while lower_idx <= upper_idx { + let mid_idx = lower_idx + ((upper_idx - lower_idx) / 2); + + match indices[mid_idx].cmp(&needle) { + std::cmp::Ordering::Less => { + lower_idx = mid_idx + 1; + } + std::cmp::Ordering::Equal => { + return mid_idx; + } + std::cmp::Ordering::Greater => { + upper_idx = mid_idx - 1; + } + } + } + lower_idx - 1 } enum SignalChangeData { @@ -236,6 +272,35 @@ enum SignalChangeData { VariableLength(Vec), } +impl SignalChangeData { + fn get_value_at(&self, offset: usize) -> SignalValue { + match &self { + SignalChangeData::FixedLength { + encoding, + width, + bytes, + } => { + let start = offset * (*width as usize); + let data = &bytes[start..(start + (*width as usize))]; + match encoding { + SignalEncoding::Binary(bits) => SignalValue::Binary(data, *bits), + SignalEncoding::FourValue(bits) => SignalValue::FourValue(data, *bits), + SignalEncoding::FixedLength => { + SignalValue::String(std::str::from_utf8(data).unwrap()) + } + SignalEncoding::Float => { + SignalValue::Float(f64::from_le_bytes(<[u8; 8]>::try_from(data).unwrap())) + } + SignalEncoding::VariableLength => { + panic!("Variable length signals need to be variable length encoded!") + } + } + } + SignalChangeData::VariableLength(strings) => SignalValue::String(&strings[offset]), + } + } +} + pub(crate) trait SignalSource { /// Loads new signals. /// Many implementations take advantage of loading multiple signals at a time. diff --git a/tests/diff_tests.rs b/tests/diff_tests.rs index f029678..54b32bd 100644 --- a/tests/diff_tests.rs +++ b/tests/diff_tests.rs @@ -4,7 +4,7 @@ use std::io::{BufRead, BufReader}; use waveform::{ - Hierarchy, HierarchyItem, ScopeType, SignalLength, TimescaleUnit, VarType, Waveform, + Hierarchy, HierarchyItem, ScopeType, SignalLength, SignalRef, TimescaleUnit, VarType, Waveform, }; fn run_diff_test(vcd_filename: &str, fst_filename: &str) { @@ -91,7 +91,6 @@ fn diff_hierarchy_item(ref_item: &vcd::ScopeItem, our_item: HierarchyItem, our_h ref_scope.scope_type.to_string(), waveform_scope_type_to_string(our_scope.scope_type()) ); - println!("Scope"); for (ref_child, our_child) in itertools::zip_eq( ref_scope .items @@ -144,17 +143,55 @@ fn diff_signals(ref_reader: &mut vcd::Parser, our: &mut Waveform) time_table_idx += 1; assert_eq!(current_time, time_table[time_table_idx]); } - vcd::Command::ChangeScalar(_, _) => {} + vcd::Command::ChangeScalar(id, value) => { + let signal_ref = vcd_lib_id_to_signal_ref(id); + let our_value = our.get_signal_value_at(signal_ref, time_table_idx as u32); + let our_value_str = our_value.to_bit_string().unwrap(); + assert_eq!(our_value_str, value.to_string()); + } vcd::Command::ChangeVector(_, _) => {} - vcd::Command::ChangeReal(_, _) => {} - vcd::Command::ChangeString(_, _) => {} + vcd::Command::ChangeReal(_, _) => { + todo!("compare real") + } + vcd::Command::ChangeString(_, _) => { + todo!("compare string") + } vcd::Command::Begin(_) => {} // ignore vcd::Command::End(_) => {} // ignore other => panic!("Unhandled command: {:?}", other), } } +} + +fn vcd_lib_id_to_signal_ref(id: vcd::IdCode) -> SignalRef { + let num = id_to_int(id.to_string().as_bytes()).unwrap(); + SignalRef::from_index(num as usize).unwrap() +} + +const ID_CHAR_MIN: u8 = b'!'; +const ID_CHAR_MAX: u8 = b'~'; +const NUM_ID_CHARS: u64 = (ID_CHAR_MAX - ID_CHAR_MIN + 1) as u64; - println!("TODO") +/// Copied from https://github.com/kevinmehall/rust-vcd, licensed under MIT +fn id_to_int(id: &[u8]) -> Option { + if id.is_empty() { + return None; + } + let mut result = 0u64; + for &i in id.iter().rev() { + if !(ID_CHAR_MIN..=ID_CHAR_MAX).contains(&i) { + return None; + } + let c = ((i - ID_CHAR_MIN) as u64) + 1; + result = match result + .checked_mul(NUM_ID_CHARS) + .and_then(|x| x.checked_add(c)) + { + None => return None, + Some(value) => value, + }; + } + Some(result - 1) } #[test]