diff --git a/README.md b/README.md index 4ad7bdc..70fe645 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Copyright - Yehowshua Immanuel ## Current Features - pretty fast, parses 3.04 GB VCD file in ~54s on M1 Macbook Air with - respect to 50s with GTKWave on the same device. FastWave currently + respect to 30s with GTKWave on the same device. FastWave currently offers highly robust error handling which GTKWave doesn't have. I noticed that when running FastWave in the VsCode terminal as opposed @@ -40,6 +40,7 @@ Here's a command to test on a malformed VCD: ## Features - [ ] macro for getting line number when propagating errors + - [ ] search for any unwraps or any direct vectors indexing - [ ] re-order all signal timelines as binary balanced trees with respect to timestamps - support multithreaded re-ordering - [ ] looks into making a macro for filename and linenumber later diff --git a/src/vcd/parse.rs b/src/vcd/parse.rs index 320f8bb..8927102 100644 --- a/src/vcd/parse.rs +++ b/src/vcd/parse.rs @@ -1,6 +1,6 @@ -use std::{fs::File}; -use std::collections::HashMap; use num::BigInt; +use std::collections::HashMap; +use std::fs::File; use super::*; @@ -19,27 +19,95 @@ use scopes::*; mod events; use events::*; -pub fn parse_vcd(file : File) -> Result { +use std::cmp::Ordering; + +fn compare_strs(a: &str, b: &str) -> Ordering { + let last_idx = if a.len() > b.len() { a.len() } else { b.len() }; + // let last_idx += -1; + Ordering::Less +} + +fn ordered_binary_lookup(map: &Vec<(String, SignalIdx)>, key: &str) -> Result { + let mut upper_idx = map.len() - 1; + let mut lower_idx = 0usize; + + while lower_idx <= upper_idx { + let mid_idx = lower_idx + ((upper_idx - lower_idx) / 2); + let (str_val, signal_idx) = map.get(mid_idx).unwrap(); + let ordering = key.partial_cmp(str_val.as_str()).unwrap(); + + match ordering { + Ordering::Less => { + upper_idx = mid_idx - 1; + } + Ordering::Equal => { + return Ok(*signal_idx); + } + Ordering::Greater => { + lower_idx = mid_idx + 1; + } + } + } + + return Err(format!( + "Error near {}:{}. Unable to find key: `{key}` in the map.", + file!(), + line!() + )); +} + +pub fn parse_vcd(file: File) -> Result { let mut word_gen = WordReader::new(file); let header = parse_metadata(&mut word_gen)?; - // later, we'll need to map parsed ascii symbols to their + // later, we'll need to map parsed ascii symbols to their // respective signal indexes let mut signal_map = std::collections::HashMap::new(); // after we parse metadata, we form the VCD object - let mut vcd = VCD{ - metadata : header, - timeline : vec![], - timeline_markers : vec![], - all_signals : vec![], - all_scopes : vec![], - scope_roots : vec![], + let mut vcd = VCD { + metadata: header, + timeline: vec![], + timeline_markers: vec![], + all_signals: vec![], + all_scopes: vec![], + scope_roots: vec![], }; parse_scopes(&mut word_gen, &mut vcd, &mut signal_map)?; - parse_events(&mut word_gen, &mut vcd, &mut signal_map)?; + + // the signal map should not contain any empty string + for (k, v) in &signal_map { + if k.len() == 0 { + return Err(format!("Critical error near {}:{}. There should be no empty strings in vcd string -> SignalIdx hashmap.", file!(), line!())); + } + } + + // now that we've parsed all scopes and filled the hashmap + // with signals, we convert hashmap to an ordered vector + let mut signal_map1: Vec<(String, SignalIdx)> = signal_map + .iter() + .map(|(string, idx)| (string.clone(), idx.clone())) + .collect(); + signal_map1.sort_by(|a: &(String, SignalIdx), b: &(String, SignalIdx)| { + let a = &a.0; + let b = &b.0; + a.partial_cmp(&b).unwrap() + }); + + let now = std::time::Instant::now(); + for (k, v) in &signal_map1 { + let signal_idx = ordered_binary_lookup(&signal_map1, k.as_str())?; + assert!(*v == signal_idx); + } + let ordered_binary_search_elapsed = now.elapsed(); + println!( + "ordered_binary_search_elapsed: {:.2?}", + ordered_binary_search_elapsed + ); + + // parse_events(&mut wosrd_gen, &mut vcd, &mut signal_map)?; Ok(vcd) } @@ -55,29 +123,18 @@ mod tests { // two loops // testing dates for file in test::GOOD_DATE_FILES { - let metadata = parse_metadata( - &mut WordReader::new( - File::open(file) - .unwrap() - ) - ); + let metadata = parse_metadata(&mut WordReader::new(File::open(file).unwrap())); assert!(metadata.is_ok()); assert!(metadata.unwrap().date.is_some()); } for file in test::FILES { - let metadata = parse_metadata( - &mut WordReader::new( - File::open(file) - .unwrap() - ) - ); + let metadata = parse_metadata(&mut WordReader::new(File::open(file).unwrap())); assert!(metadata.is_ok()); let (scalar, _timescale) = metadata.unwrap().timescale; assert!(scalar.is_some()); } - } #[test] @@ -94,6 +151,5 @@ mod tests { // assert!(vcd.is_ok()); } - } -} \ No newline at end of file +} diff --git a/src/vcd/types.rs b/src/vcd/types.rs index 0a9e004..6bcd66e 100644 --- a/src/vcd/types.rs +++ b/src/vcd/types.rs @@ -4,18 +4,27 @@ use chrono::prelude::*; pub(super) struct Version(pub String); #[derive(Debug)] -pub(super) enum Timescale {Fs, Ps, Ns, Us, Ms, S, Unit} +pub(super) enum Timescale { + Fs, + Ps, + Ns, + Us, + Ms, + S, + Unit, +} #[derive(Debug)] pub(super) struct Metadata { - pub(super) date : Option>, - pub(super) version : Option, - pub(super) timescale : (Option, Timescale)} + pub(super) date: Option>, + pub(super) version: Option, + pub(super) timescale: (Option, Timescale), +} #[derive(Debug, Copy, Clone)] pub(super) struct ScopeIdx(pub(super) usize); -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub(super) struct SignalIdx(pub(super) usize); #[derive(Debug, Copy, Clone)] @@ -25,63 +34,71 @@ pub(super) struct TimelineIdx(pub(super) u32); pub struct StartIdx(pub(super) u32); #[derive(Debug)] -pub(super) enum SigType {Integer, Parameter, Real, Reg, Str, Wire, Tri1, Time} +pub(super) enum SigType { + Integer, + Parameter, + Real, + Reg, + Str, + Wire, + Tri1, + Time, +} #[derive(Debug)] -pub(super) enum Signal{ - Data{ - name : String, - sig_type : SigType, - // I've seen a 0 bit signal parameter in a xilinx - // simulation before that gets assigned 1 bit values. - // I consider this to be bad behavior. We capture such - // errors in the following type. - signal_error : Option, - num_bits : Option, - // TODO : may be able to remove self_idx - self_idx : SignalIdx, - // we could encounter a mix of pure values and strings - // for the same signal timeline - u8_timeline : Vec, - u8_timeline_markers : Vec, - string_timeline : Vec, - string_timeline_markers : Vec, - scope_parent : ScopeIdx}, - Alias{ - name : String, - signal_alias : SignalIdx} +pub(super) enum Signal { + Data { + name: String, + sig_type: SigType, + // I've seen a 0 bit signal parameter in a xilinx + // simulation before that gets assigned 1 bit values. + // I consider this to be bad behavior. We capture such + // errors in the following type. + signal_error: Option, + num_bits: Option, + // TODO : may be able to remove self_idx + self_idx: SignalIdx, + // we could encounter a mix of pure values and strings + // for the same signal timeline + u8_timeline: Vec, + u8_timeline_markers: Vec, + string_timeline: Vec, + string_timeline_markers: Vec, + scope_parent: ScopeIdx, + }, + Alias { + name: String, + signal_alias: SignalIdx, + }, } #[derive(Debug)] pub(super) struct Scope { - pub(super) name : String, + pub(super) name: String, - pub(super) parent_idx : Option, - pub(super) self_idx : ScopeIdx, - - pub(super) child_signals : Vec, - pub(super) child_scopes : Vec} + pub(super) parent_idx: Option, + pub(super) self_idx: ScopeIdx, + pub(super) child_signals: Vec, + pub(super) child_scopes: Vec, +} // TODO: document how timeline is represented #[derive(Debug)] pub struct VCD { - pub(super) metadata : Metadata, - pub timeline : Vec, - pub timeline_markers : Vec, - pub(super) all_signals : Vec, - pub(super) all_scopes : Vec, - pub(super) scope_roots : Vec} + pub(super) metadata: Metadata, + pub timeline: Vec, + pub timeline_markers: Vec, + pub(super) all_signals: Vec, + pub(super) all_scopes: Vec, + pub(super) scope_roots: Vec, +} impl VCD { - // TODO : make this a generic traversal function that applies specified + // TODO : make this a generic traversal function that applies specified // functions upon encountering scopes and signals - fn print_scope_tree( - &self, - root_scope_idx : ScopeIdx, - depth : usize) - { - let all_scopes = &self.all_scopes; + fn print_scope_tree(&self, root_scope_idx: ScopeIdx, depth: usize) { + let all_scopes = &self.all_scopes; let all_signals = &self.all_signals; let indent = " ".repeat(depth * 4); @@ -94,17 +111,16 @@ impl VCD { for SignalIdx(ref signal_idx) in &root_scope.child_signals { let child_signal = &all_signals[*signal_idx]; let name = match child_signal { - Signal::Data{name, ..} => {name} - Signal::Alias{name, ..} => {name} + Signal::Data { name, .. } => name, + Signal::Alias { name, .. } => name, }; println!("{indent} - sig: {name}") } println!(); for scope_idx in &root_scope.child_scopes { - self.print_scope_tree(*scope_idx, depth+1); + self.print_scope_tree(*scope_idx, depth + 1); } - } pub fn print_scopes(&self) { @@ -120,23 +136,23 @@ impl VCD { for signal in &self.all_signals { match signal { - Signal::Alias {..} => {} - Signal::Data { - name, - self_idx, - u8_timeline, - .. } => { - if u8_timeline.len() > max_len { - max_len = u8_timeline.len(); - let SignalIdx(idx_usize) = self_idx; - idx = *idx_usize; - signal_name = name.clone(); - } - + Signal::Alias { .. } => {} + Signal::Data { + name, + self_idx, + u8_timeline, + .. + } => { + if u8_timeline.len() > max_len { + max_len = u8_timeline.len(); + let SignalIdx(idx_usize) = self_idx; + idx = *idx_usize; + signal_name = name.clone(); } + } } } dbg!((idx, max_len, signal_name)); } -} \ No newline at end of file +}