diff --git a/README.md b/README.md index 33a0176..b87c6b4 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ Here's a command to test on a malformed VCD: - [ ] may need to refactor with allow for get_mut for dynamic compression-decompression for multiple signal structs at once to allow for multi-threading - - [ ] add string support for timeline value scanner + - [x] add string support for timeline value scanner - [ ] test against large waveforms from the [verilog-vcd-parser](https://github.com/ben-marshall/verilog-vcd-parser) tool @@ -91,6 +91,7 @@ Here's a command to test on a malformed VCD: - [ ] Move part of the performance section to another markdown file. ## Repairs + - [ ] replace str bracket indices with get(slice) - [ ] make a custom date parser for possibly up to 18 different versions(that is, for each possible tool). - [ ] Consolidate error messages and add cursors throughout. - [ ] Add file and line to the enum errors. @@ -98,6 +99,7 @@ Here's a command to test on a malformed VCD: able to successfully parse all sample VCDs. ## Code Consistency + - [ ] split impls in signal.rs into groups - [ ] Change error messages to line and filenames. Go through all calls to unwrap. - [ ] search for any unwraps or any direct vectors indexing - [ ] Handle TODOs diff --git a/src/main.rs b/src/main.rs index 97bc5c7..8bd5291 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ pub mod test; pub mod vcd; use vcd::*; -use num::BigUint; +use num::{BigUint, traits::sign}; #[derive(Parser)] struct Cli { @@ -19,32 +19,48 @@ fn main() -> std::io::Result<()> { let args = Cli::parse(); use std::time::Instant; + let now = Instant::now(); - let file = File::open(&args.path)?; let vcd = parse_vcd(file).unwrap(); - let elapsed = now.elapsed(); - println!("Elapsed: {:.2?}", elapsed); + + println!("Parsed VCD file {} : {:.2?}", &args.path.as_os_str().to_str().unwrap(), elapsed); // the following is really only for test-vcd-files/icarus/CPU.vcd // at the moment if args.path.as_os_str().to_str().unwrap() == "test-vcd-files/icarus/CPU.vcd" { - let signal = &vcd.all_signals[51]; - let name = match signal { - Signal::Data { name, .. } => name, - _ => "ERROR", - }; - let val = signal + let rs2_data_signal = &vcd.all_signals[51]; + let name = rs2_data_signal.name(); + // query testbench -> CPU -> rs2_data[31:0] @ 4687s + let time = BigUint::from(4687u32); + let val = rs2_data_signal .query_num_val_on_tmln( - BigUint::from(4687u32), + &time, &vcd.tmstmps_encoded_as_u8s, &vcd.all_signals, ) .unwrap(); - dbg!(format!("{val:#X}")); - dbg!(name); + println!("Signal `{name}` has value `{val}` at time `{time}`"); + + // also need to test testbench -> CPU -> ID_EX_RD[4:0] } + // this is to help with testing stringed enums + if args.path.as_os_str().to_str().unwrap() == "test-vcd-files/amaranth/up_counter.vcd" { + let state_signal = &vcd.all_signals[4]; + let name = state_signal.name(); + let time = BigUint::from(57760000u32); + let val = state_signal + .query_string_val_on_tmln( + &time, + &vcd.tmstmps_encoded_as_u8s, + &vcd.all_signals, + ) + .unwrap(); + println!("Signal `{name}` has value `{val}` at time `{time}`"); + } + + Ok(()) } diff --git a/src/vcd/parse/events.rs b/src/vcd/parse/events.rs index 053a22b..bad78e6 100644 --- a/src/vcd/parse/events.rs +++ b/src/vcd/parse/events.rs @@ -112,7 +112,7 @@ pub(super) fn parse_events<'a>( ) })?; - let signal = vcd.try_dereference_alias_mut(signal_idx)?; + let signal = vcd.dealiasing_signal_idx_to_signal_lookup_mut(signal_idx)?; match signal { Signal::Data { @@ -225,7 +225,7 @@ pub(super) fn parse_events<'a>( ) })?; - let signal = vcd.try_dereference_alias_mut(signal_idx)?; + let signal = vcd.dealiasing_signal_idx_to_signal_lookup_mut(signal_idx)?; match signal { Signal::Data { @@ -316,7 +316,7 @@ pub(super) fn parse_events<'a>( ) })?; - let signal = vcd.try_dereference_alias_mut(signal_idx)?; + let signal = vcd.dealiasing_signal_idx_to_signal_lookup_mut(signal_idx)?; match signal { Signal::Data { @@ -409,7 +409,7 @@ pub(super) fn parse_events<'a>( ) })?; - let signal = vcd.try_dereference_alias_mut(signal_idx)?; + let signal = vcd.dealiasing_signal_idx_to_signal_lookup_mut(signal_idx)?; match signal { Signal::Data { @@ -477,6 +477,55 @@ pub(super) fn parse_events<'a>( } }?; } + "s" => { + let val = word[1..].to_string(); + let (hash, cursor) = next_word!(word_reader)?; + // lokup signal idx + let signal_idx = signal_map.get(hash).ok_or(()).map_err(|_| { + format!( + "Error near {}:{}. Failed to lookup signal {hash} at {cursor:?}", + file!(), + line!() + ) + })?; + + let signal = vcd.dealiasing_signal_idx_to_signal_lookup_mut(signal_idx)?; + + match signal { + Signal::Data { + name, + sig_type, + ref mut signal_error, + num_bits, + string_vals, + byte_len_of_num_tmstmp_vals_on_tmln, + byte_len_of_string_tmstmp_vals_on_tmln, + lsb_indxs_of_string_tmstmp_vals_on_tmln, + .. + } => { + // if this is a bad signal, go ahead and skip it + if signal_error.is_some() { + continue; + } + + // record timestamp at which this event occurs + lsb_indxs_of_string_tmstmp_vals_on_tmln + .push(LsbIdxOfTmstmpValOnTmln(curr_tmstmp_lsb_idx)); + byte_len_of_string_tmstmp_vals_on_tmln.push(curr_tmstmp_len_u8); + + // record string value + string_vals.push(val); + Ok(()) + } + Signal::Alias { .. } => { + let (f, l) = (file!(), line!()); + let msg = format!( + "Error near {f}:{l}, a signal alias should not point to a signal alias.\n\ + This error occurred while parsing vcd file at {cursor:?}"); + Err(msg) + } + }?; + } _ => {} } } diff --git a/src/vcd/signal.rs b/src/vcd/signal.rs index fdeabe6..44c1c82 100644 --- a/src/vcd/signal.rs +++ b/src/vcd/signal.rs @@ -76,6 +76,7 @@ pub enum SignalErrors { }, EmptyTimeline, TimelineNotMultiple, + StrTmlnLenMismatch, OrderingFailure { lhs_time: BigUint, mid_time: BigUint, @@ -90,6 +91,29 @@ pub enum SignalErrors { type TimeStamp = BigUint; type SignalValNum = BigUint; +// getter functions +impl Signal { + pub fn self_idx(&self) -> Result { + match self { + Signal::Data { self_idx, ..} => {return Ok(self_idx.clone())}, + Signal::Alias { .. } => Err(format!( + "Error near {}:{}. A signal alias shouldn't \ + point to a signal alias.", + file!(), + line!() + )), + } + } + + pub fn name(&self) -> String { + match self { + Signal::Data { name, ..} => name, + Signal::Alias { name, .. } => name + }.clone() + } + +} + impl Signal { pub(super) fn bytes_required(num_bits: u16, name: &String) -> Result { let bytes_required = (num_bits / 8) + if (num_bits % 8) > 0 { 1 } else { 0 }; @@ -187,9 +211,124 @@ impl Signal { Ok((timestamp, signal_val)) } + pub fn query_string_val_on_tmln( + &self, + desired_time: &BigUint, + tmstmps_encoded_as_u8s: &Vec, + all_signals: &Vec, + ) -> Result { + let signal_idx = match self { + Self::Data { self_idx, .. } => { + let SignalIdx(idx) = self_idx; + *idx + } + Self::Alias { + name: _, + signal_alias, + } => { + let SignalIdx(idx) = signal_alias; + *idx + } + }; + + // if the signal idx points to data variant of the signal, + // extract: + // 1. the vector of string values + // 2. the vector of indices into timeline where events occur + // for this signal + // else we propagate Err(..). + let (string_vals, lsb_indxs_of_string_tmstmp_vals_on_tmln) = + match &all_signals[signal_idx] { + Signal::Data { + ref string_vals, + ref lsb_indxs_of_string_tmstmp_vals_on_tmln, + .. + } => { + Ok(( + string_vals, + lsb_indxs_of_string_tmstmp_vals_on_tmln, + )) + } + Signal::Alias { .. } => Err(SignalErrors::PointsToAlias), + }?; + // this signal should at least have some events, otherwise, trying to index into + // an empty vector later on would fail + if lsb_indxs_of_string_tmstmp_vals_on_tmln.is_empty() { + return Err(SignalErrors::EmptyTimeline); + } + + // the vector of string timeline lsb indices should have the same + // length as the vector of string values + if string_vals.len() != lsb_indxs_of_string_tmstmp_vals_on_tmln.len() { + return Err(SignalErrors::StrTmlnLenMismatch); + } + + // check if we're requesting a value that occurs before the recorded + // start of the timeline + let (timeline_start_time, _) = + self.time_and_str_val_at_event_idx(0, tmstmps_encoded_as_u8s)?; + if *desired_time < timeline_start_time { + return Err(SignalErrors::PreTimeline { + desired_time: desired_time.clone(), + timeline_start_time: timeline_start_time, + }); + } + + let mut lower_idx = 0usize; + let mut upper_idx = lsb_indxs_of_string_tmstmp_vals_on_tmln.len() - 1; + let (timeline_end_time, timeline_end_val) = + self.time_and_str_val_at_event_idx(upper_idx, tmstmps_encoded_as_u8s)?; + + // check if we're requesting a value that occurs beyond the end of the timeline, + // if so, return the last value in this timeline + if *desired_time > timeline_end_time { + return Ok(timeline_end_val.to_string()); + } + + // This while loop is the meat of the lookup. Performance is log2(n), + // where n is the number of events on the timeline. + // We can assume that by the time we get here, that the desired_time + // is an event that occurs on the timeline, given that we handle any events + // occuring after or before the recorded tiimeline in the code above. + while lower_idx <= upper_idx { + let mid_idx = lower_idx + ((upper_idx - lower_idx) / 2); + let (curr_time, curr_val) = + self.time_and_str_val_at_event_idx(mid_idx, tmstmps_encoded_as_u8s)?; + let ordering = curr_time.cmp(desired_time); + + match ordering { + std::cmp::Ordering::Less => { + lower_idx = mid_idx + 1; + } + std::cmp::Ordering::Equal => { + return Ok(curr_val.to_string()); + } + std::cmp::Ordering::Greater => { + upper_idx = mid_idx - 1; + } + } + } + + let (left_time, left_val) = + self.time_and_str_val_at_event_idx(lower_idx - 1, tmstmps_encoded_as_u8s)?; + let (right_time, _) = + self.time_and_str_val_at_event_idx(lower_idx, tmstmps_encoded_as_u8s)?; + + let ordered_left = left_time < *desired_time; + let ordered_right = *desired_time < right_time; + if !(ordered_left && ordered_right) { + return Err(SignalErrors::OrderingFailure { + lhs_time: left_time, + mid_time: desired_time.clone(), + rhs_time: right_time, + }); + } + + return Ok(left_val.to_string()); + } pub fn query_num_val_on_tmln( &self, - desired_time: BigUint, + desired_time: &BigUint, tmstmps_encoded_as_u8s: &Vec, all_signals: &Vec, ) -> Result { @@ -207,6 +346,13 @@ impl Signal { } }; + // if the signal idx points to data variant of the signal, + // extract: + // 1. the vector of LE u8 compressed values + // 2. the vector of indices into timeline where events occur + // for this signal + // 3. the number of bytes per value for this signal + // else we propagate Err(..). let (nums_encoded_as_fixed_width_le_u8, lsb_indxs_of_num_tmstmp_vals_on_tmln, num_bytes) = match &all_signals[signal_idx] { Signal::Data { @@ -244,10 +390,6 @@ impl Signal { if nums_encoded_as_fixed_width_le_u8.len() != (lsb_indxs_of_num_tmstmp_vals_on_tmln.len() * (bytes_required as usize)) { - dbg!(( - nums_encoded_as_fixed_width_le_u8.len(), - (lsb_indxs_of_num_tmstmp_vals_on_tmln.len() * (bytes_required as usize)) - )); return Err(SignalErrors::TimelineNotMultiple); } @@ -255,9 +397,9 @@ impl Signal { // start of the timeline let (timeline_start_time, _) = self.time_and_num_val_at_event_idx(0, tmstmps_encoded_as_u8s)?; - if desired_time < timeline_start_time { + if *desired_time < timeline_start_time { return Err(SignalErrors::PreTimeline { - desired_time: desired_time, + desired_time: desired_time.clone(), timeline_start_time: timeline_start_time, }); } @@ -269,7 +411,7 @@ impl Signal { // check if we're requesting a value that occurs beyond the end of the timeline, // if so, return the last value in this timeline - if desired_time > timeline_end_time { + if *desired_time > timeline_end_time { return Ok(timeline_end_val); } @@ -282,7 +424,7 @@ impl Signal { let mid_idx = lower_idx + ((upper_idx - lower_idx) / 2); let (curr_time, curr_val) = self.time_and_num_val_at_event_idx(mid_idx, tmstmps_encoded_as_u8s)?; - let ordering = curr_time.cmp(&desired_time); + let ordering = curr_time.cmp(desired_time); match ordering { std::cmp::Ordering::Less => { @@ -302,12 +444,12 @@ impl Signal { let (right_time, _) = self.time_and_num_val_at_event_idx(lower_idx, tmstmps_encoded_as_u8s)?; - let ordered_left = left_time < desired_time; - let ordered_right = desired_time < right_time; + let ordered_left = left_time < *desired_time; + let ordered_right = *desired_time < right_time; if !(ordered_left && ordered_right) { return Err(SignalErrors::OrderingFailure { lhs_time: left_time, - mid_time: desired_time, + mid_time: desired_time.clone(), rhs_time: right_time, }); } diff --git a/src/vcd/types.rs b/src/vcd/types.rs index edfc79b..d0bdf5d 100644 --- a/src/vcd/types.rs +++ b/src/vcd/types.rs @@ -60,12 +60,12 @@ pub struct VCD { } impl VCD { - /// We take in a Signal and attempt to dereference that signal if it is of + /// We take in a Signal and attempt to de-alias that signal if it is of /// variant ``Signal::Alias``. If it is of variant ``Signal::Alias`` and points to /// another alias, that's an error. Otherwise, we return the ``Signal::Data`` /// pointed to by the ``Signal::Alias``. /// If the Signal is of varint ``Signal::Data``, then that can be returned directly. - pub(super) fn try_dereference_alias_mut<'a>( + pub(super) fn dealiasing_signal_idx_to_signal_lookup_mut<'a>( &'a mut self, idx: &SignalIdx, ) -> Result<&'a mut Signal, String> { @@ -92,7 +92,7 @@ impl VCD { )), } } - pub(super) fn try_dereference_alias<'a>( + pub(super) fn dealiasing_signal_idx_to_signal_lookup<'a>( &'a self, idx: &SignalIdx, ) -> Result<&'a Signal, String> { @@ -106,6 +106,35 @@ impl VCD { Signal::Alias { name, signal_alias } => *signal_alias, }; + // Should now point to Signal::Data variant, or else there's an error + let SignalIdx(idx) = signal_idx; + let signal = self.all_signals.get(idx).unwrap(); + match signal { + Signal::Data { .. } => Ok(signal), + Signal::Alias { .. } => Err(format!( + "Error near {}:{}. A signal alias shouldn't \ + point to a signal alias.", + file!(), + line!() + )), + } + } + /// Takes a signal as input and returns the signal if the signal is of the + /// Signal::Data variant, else the function follows follows the uses the + /// SignalIdx in the signal_alias field of Signal::Alias variant to index + /// into the signal arena in the all_signals field of the vcd, and returns + /// the resulting signal if that signal is a Signal::Data variant, else, + /// this function returns an Err. + pub fn dealiasing_signal_lookup<'a>( + &'a self, + signal: &Signal + ) -> Result<&'a Signal, String> { + // dereference signal if Signal::Alias, or keep idx if Signal::Data + let signal_idx = match signal { + Signal::Data { self_idx, .. } => *self_idx, + Signal::Alias { name, signal_alias } => *signal_alias, + }; + // Should now point to Signal::Data variant, or else there's an error let SignalIdx(idx) = signal_idx; let signal = self.all_signals.get(idx).unwrap();