Test ordered binary lookup #4

Merged
ThePerfectComputer merged 2 commits from test_ordered_binary_lookup into main 2022-08-07 23:06:23 +00:00
4 changed files with 420 additions and 237 deletions

View file

@ -3,13 +3,13 @@ Copyright - Yehowshua Immanuel
# A High performance, VCD Parser written in Rust # A High performance, VCD Parser written in Rust
## Current Features ## Current Features
- pretty fast, parses 3.04 GB VCD file in ~54s on M1 Macbook Air with - pretty fast, parses 3.04 GB VCD file in ~27.23s 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. offers highly robust error(at least on the sample VCD files in this
repository) handling which GTKWave doesn't have.
I noticed that when running FastWave in the VsCode terminal as opposed I noticed that when running FastWave in the VsCode terminal as opposed
to the MacOS system terminal or the Lapce terminal, FastWave takes 67s to the MacOS system terminal or the Lapce terminal.
to parse the 3.04GB file.
# Current Limitations # Current Limitations
@ -40,6 +40,8 @@ Here's a command to test on a malformed VCD:
## Features ## Features
- [ ] macro for getting line number when propagating errors - [ ] macro for getting line number when propagating errors
- [ ] search for any ok_or's
- [ ] search for any unwraps or any direct vectors indexing
- [ ] re-order all signal timelines as binary balanced trees with respect to timestamps - [ ] re-order all signal timelines as binary balanced trees with respect to timestamps
- support multithreaded re-ordering - support multithreaded re-ordering
- [ ] looks into making a macro for filename and linenumber later - [ ] looks into making a macro for filename and linenumber later

View file

@ -1,6 +1,6 @@
use std::{fs::File};
use std::collections::HashMap;
use num::BigInt; use num::BigInt;
use std::collections::HashMap;
use std::fs::File;
use super::*; use super::*;
@ -19,26 +19,130 @@ use scopes::*;
mod events; mod events;
use events::*; use events::*;
pub fn parse_vcd(file : File) -> Result<VCD, String> { use std::cmp::Ordering;
fn compare_strs(a: &str, b: &str) -> Ordering {
// choose the smaller of the two indices
let upper_bound = if a.len() > b.len() { b.len() } else { a.len() };
let a_as_bytes = a.as_bytes();
let b_as_bytes = b.as_bytes();
for i in 0..upper_bound {
let a_byte = a_as_bytes[i];
let b_byte = b_as_bytes[i];
if a_byte > b_byte {
return Ordering::Greater;
}
if b_byte > a_byte {
return Ordering::Less;
}
}
if a.len() > b.len() {
return Ordering::Greater;
}
if a.len() < b.len() {
return Ordering::Less;
}
return Ordering::Equal;
}
fn ordered_binary_lookup(map: &Vec<(String, SignalIdx)>, key: &str) -> Result<SignalIdx, String> {
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 = compare_strs(key, str_val.as_str());
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<VCD, String> {
let mut word_gen = WordReader::new(file); let mut word_gen = WordReader::new(file);
let header = parse_metadata(&mut word_gen)?; 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 // respective signal indexes
let mut signal_map = std::collections::HashMap::new(); let mut signal_map = std::collections::HashMap::new();
// after we parse metadata, we form the VCD object // after we parse metadata, we form the VCD object
let mut vcd = VCD{ let mut vcd = VCD {
metadata : header, metadata: header,
timeline : vec![], timeline: vec![],
timeline_markers : vec![], timeline_markers: vec![],
all_signals : vec![], all_signals: vec![],
all_scopes : vec![], all_scopes: vec![],
scope_roots : vec![], scope_roots: vec![],
}; };
parse_scopes(&mut word_gen, &mut vcd, &mut signal_map)?; parse_scopes(&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;
compare_strs(a, b)
});
for (k, v) in &signal_map1 {
let signal_idx = ordered_binary_lookup(&signal_map1, k.as_str())?;
assert!(*v == signal_idx);
}
// 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
// );
// let now = std::time::Instant::now();
// for (k, v) in &signal_map1 {
// // let signal_idx = ordered_binary_lookup(&signal_map1, k.as_str())?;
// let signal_idx = signal_map.get(k).unwrap();
// assert!(*v == *signal_idx);
// }
// let hashmap_search_elapsed = now.elapsed();
// println!("hashmap_search_elapsed: {:.2?}", hashmap_search_elapsed);
parse_events(&mut word_gen, &mut vcd, &mut signal_map)?; parse_events(&mut word_gen, &mut vcd, &mut signal_map)?;
Ok(vcd) Ok(vcd)
@ -55,29 +159,18 @@ mod tests {
// two loops // two loops
// testing dates // testing dates
for file in test::GOOD_DATE_FILES { for file in test::GOOD_DATE_FILES {
let metadata = parse_metadata( let metadata = parse_metadata(&mut WordReader::new(File::open(file).unwrap()));
&mut WordReader::new(
File::open(file)
.unwrap()
)
);
assert!(metadata.is_ok()); assert!(metadata.is_ok());
assert!(metadata.unwrap().date.is_some()); assert!(metadata.unwrap().date.is_some());
} }
for file in test::FILES { for file in test::FILES {
let metadata = parse_metadata( let metadata = parse_metadata(&mut WordReader::new(File::open(file).unwrap()));
&mut WordReader::new(
File::open(file)
.unwrap()
)
);
assert!(metadata.is_ok()); assert!(metadata.is_ok());
let (scalar, _timescale) = metadata.unwrap().timescale; let (scalar, _timescale) = metadata.unwrap().timescale;
assert!(scalar.is_some()); assert!(scalar.is_some());
} }
} }
#[test] #[test]
@ -94,6 +187,5 @@ mod tests {
// assert!(vcd.is_ok()); // assert!(vcd.is_ok());
} }
} }
} }

View file

@ -1,18 +1,26 @@
//! part of the vcd parser that handles parsing the signal tree and //! part of the vcd parser that handles parsing the signal tree and
//! building the resulting signal tree //! building the resulting signal tree
use core::time;
use super::*; use super::*;
#[derive(Debug)] #[derive(Debug)]
pub(super) enum BinaryParserErrTypes {XValue, ZValue, UValue, OtherValue(char), TooLong} pub(super) enum BinaryParserErrTypes {
XValue,
ZValue,
UValue,
OtherValue(char),
TooLong,
}
// We build a quick and not so dirty bit string parser. // We build a quick and not so dirty bit string parser.
fn base2_str_to_byte(word : &[u8]) -> Result<u8, BinaryParserErrTypes> { fn base2_str_to_byte(word: &[u8]) -> Result<u8, BinaryParserErrTypes> {
let mut val = 0u8; let mut val = 0u8;
// shouldn't have more than 8 chars in str // shouldn't have more than 8 chars in str
let len = word.len(); let len = word.len();
if len > 8 { if len > 8 {
return Err(BinaryParserErrTypes::TooLong) return Err(BinaryParserErrTypes::TooLong);
} }
let bit_lut = [ let bit_lut = [
@ -23,89 +31,93 @@ fn base2_str_to_byte(word : &[u8]) -> Result<u8, BinaryParserErrTypes> {
0b0001_0000u8, 0b0001_0000u8,
0b0010_0000u8, 0b0010_0000u8,
0b0100_0000u8, 0b0100_0000u8,
0b1000_0000u8 0b1000_0000u8,
]; ];
for (idx, chr) in word.iter().rev().enumerate() { for (idx, chr) in word.iter().rev().enumerate() {
match chr { match chr {
b'1' => {val = bit_lut[idx] | val} b'1' => val = bit_lut[idx] | val,
b'0' => {} b'0' => {}
b'x' | b'X' => {return Err(BinaryParserErrTypes::XValue)} b'x' | b'X' => return Err(BinaryParserErrTypes::XValue),
b'z' | b'Z' => {return Err(BinaryParserErrTypes::ZValue)} b'z' | b'Z' => return Err(BinaryParserErrTypes::ZValue),
b'u' | b'U' => {return Err(BinaryParserErrTypes::UValue)} b'u' | b'U' => return Err(BinaryParserErrTypes::UValue),
_ => {return Err(BinaryParserErrTypes::OtherValue(*chr as char))} _ => return Err(BinaryParserErrTypes::OtherValue(*chr as char)),
} }
} }
Ok(val) Ok(val)
} }
fn binary_str_to_vec_u8(binary_str : &str) -> Result<Vec<u8>, BinaryParserErrTypes> { fn binary_str_to_vec_u8(binary_str: &str) -> Result<Vec<u8>, BinaryParserErrTypes> {
let mut vec_u8 : Vec<u8> = Vec::new(); let mut vec_u8: Vec<u8> = Vec::new();
let binary_str_as_bytes = binary_str.as_bytes(); let binary_str_as_bytes = binary_str.as_bytes();
let mut tail_idx = binary_str_as_bytes.len(); let mut tail_idx = binary_str_as_bytes.len();
// clamp head if provided binary str is less than 8 long // clamp head if provided binary str is less than 8 long
let mut head_idx = let mut head_idx = if tail_idx >= 8 {
if tail_idx >= 8 binary_str_as_bytes.len() - 8
{binary_str_as_bytes.len() - 8} } else {
else 0
{0}; };
while tail_idx > 0 { while tail_idx > 0 {
let curr_b_val = &binary_str_as_bytes[head_idx..tail_idx]; let curr_b_val = &binary_str_as_bytes[head_idx..tail_idx];
let val_u8 = base2_str_to_byte(curr_b_val)?; let val_u8 = base2_str_to_byte(curr_b_val)?;
vec_u8.push(val_u8); vec_u8.push(val_u8);
if head_idx < 8 { if head_idx < 8 {
head_idx = 0 head_idx = 0
} } else {
else {
head_idx = head_idx - 8; head_idx = head_idx - 8;
} }
if tail_idx < 8 { if tail_idx < 8 {
tail_idx = 0 tail_idx = 0
} } else {
else {
tail_idx = tail_idx - 8; tail_idx = tail_idx - 8;
} }
} }
Ok(vec_u8) Ok(vec_u8)
} }
pub(super) fn parse_events<'a>( pub(super) fn parse_events<'a>(
word_reader : &mut WordReader, word_reader: &mut WordReader,
vcd : &'a mut VCD, vcd: &'a mut VCD,
signal_map : &mut HashMap<String, SignalIdx> signal_map: &mut HashMap<String, SignalIdx>,
) -> Result<(), String> { ) -> Result<(), String> {
// let hash_time = std::time::Duration::ZERO;
// let hash_time = std::time::Duration::ZERO;
loop { loop {
let next_word = word_reader.next_word(); let next_word = word_reader.next_word();
// The following is the only case where eof is not an error. // The following is the only case where eof is not an error.
// If we've reached the end of the file, then there is obviously // If we've reached the end of the file, then there is obviously
// nothing left to do... // nothing left to do...
if next_word.is_err() {break}; if next_word.is_err() {
break;
};
let (word, cursor) = next_word.unwrap(); let (word, cursor) = next_word.unwrap();
let Cursor(Line(_), Word(word_in_line_idx)) = cursor; let Cursor(Line(_), Word(word_in_line_idx)) = cursor;
// we only want to match on the first word in a line // we only want to match on the first word in a line
if word_in_line_idx != 1 {continue} if word_in_line_idx != 1 {
continue;
}
match &word[0..1] { match &word[0..1] {
"$" => {} "$" => {}
"#" => { "#" => {
let value = &word[1..]; let value = &word[1..];
let (f, l )= (file!(), line!()); let (f, l) = (file!(), line!());
let value = BigInt::parse_bytes(value.as_bytes(), 10).ok_or( let value = BigInt::parse_bytes(value.as_bytes(), 10).ok_or(
format!("Error near {f}:{l}. Failed to parse {value} as BigInt at {cursor:?}").as_str())?; format!("Error near {f}:{l}. Failed to parse {value} as BigInt at {cursor:?}")
.as_str(),
)?;
let (_, mut value) = value.to_bytes_le(); let (_, mut value) = value.to_bytes_le();
// TODO : u32 helps with less memory, but should ideally likely be // TODO : u32 helps with less memory, but should ideally likely be
// configurable. // configurable.
let (f, l )= (file!(), line!()); let (f, l) = (file!(), line!());
let start_idx = u32::try_from(vcd.timeline.len()).map_err( let start_idx = u32::try_from(vcd.timeline.len()).map_err(|_| {
|_| format!("Error near {f}:{l}. Failed to convert from usize to u32."))?; format!("Error near {f}:{l}. Failed to convert from usize to u32.")
})?;
vcd.timeline_markers.push(StartIdx(start_idx)); vcd.timeline_markers.push(StartIdx(start_idx));
vcd.timeline.append(&mut value); vcd.timeline.append(&mut value);
} }
@ -115,50 +127,55 @@ pub(super) fn parse_events<'a>(
let binary_value = &word[1..]; let binary_value = &word[1..];
let observed_num_bits = binary_value.len(); let observed_num_bits = binary_value.len();
let mut value_u8 : Vec<u8> = Vec::new(); let mut value_u8: Vec<u8> = Vec::new();
let mut value_string = String::new(); let mut value_string = String::new();
let mut store_as_string = false; let mut store_as_string = false;
// If we encounter x or z in a value, we can recover from // If we encounter x or z in a value, we can recover from
// the error and store the value as a string. // the error and store the value as a string.
// Or else, we we propagate up other errors. // Or else, we we propagate up other errors.
match binary_str_to_vec_u8(binary_value) { match binary_str_to_vec_u8(binary_value) {
Ok(result) => {value_u8 = result;} Ok(result) => {
Err(BinaryParserErrTypes::XValue | value_u8 = result;
BinaryParserErrTypes::ZValue | }
BinaryParserErrTypes::UValue Err(
) => BinaryParserErrTypes::XValue
{ | BinaryParserErrTypes::ZValue
store_as_string = true; | BinaryParserErrTypes::UValue,
value_string = binary_value.to_string(); ) => {
} store_as_string = true;
value_string = binary_value.to_string();
}
Err(e) => { Err(e) => {
let (f, l )= (file!(), line!()); let (f, l) = (file!(), line!());
Err(e).map_err( Err(e).map_err(|e| {
|e| format!("Error near {f}:{l}. Error {e:?} at {cursor:?}."))?; format!("Error near {f}:{l}. Error {e:?} at {cursor:?}.")
})?;
} }
} }
// this word should be the signal alias // this word should be the signal alias
let (word, cursor) = word_reader.next_word().unwrap(); let (word, cursor) = word_reader.next_word().unwrap();
// lookup signal idx // lookup signal idx
let (f, l )= (file!(), line!()); let SignalIdx(ref signal_idx) = signal_map.get(word).ok_or(()).map_err(|_| {
let SignalIdx(ref signal_idx) = signal_map.get(word).ok_or( format!(
format!("Error near {f}:{l}. Failed to lookup signal {word} at {cursor:?}"))?; "Error near {}:{}. Failed to lookup signal {word} at {cursor:?}",
file!(),
line!()
)
})?;
// account for fact that signal idx could be an alias, so there // account for fact that signal idx could be an alias, so there
// could be one step of indirection // could be one step of indirection
let signal_idx = let signal_idx = {
{
let signal = vcd.all_signals.get(*signal_idx).unwrap(); let signal = vcd.all_signals.get(*signal_idx).unwrap();
match signal { match signal {
Signal::Data {..} => {*signal_idx} Signal::Data { .. } => *signal_idx,
Signal::Alias {signal_alias, ..} => { Signal::Alias { signal_alias, .. } => {
let SignalIdx(ref signal_idx) = signal_alias; let SignalIdx(ref signal_idx) = signal_alias;
signal_idx.clone() signal_idx.clone()
} }
} }
}; };
@ -167,11 +184,20 @@ pub(super) fn parse_events<'a>(
// of the signal signal_idx references // of the signal signal_idx references
let signal = vcd.all_signals.get_mut(signal_idx).unwrap(); let signal = vcd.all_signals.get_mut(signal_idx).unwrap();
match signal { match signal {
Signal::Data {name, sig_type, ref mut signal_error, num_bits, Signal::Data {
u8_timeline, u8_timeline_markers, string_timeline, name,
string_timeline_markers, ..} => { sig_type,
ref mut signal_error,
if signal_error.is_some() {continue;} num_bits,
u8_timeline,
u8_timeline_markers,
string_timeline,
string_timeline_markers,
..
} => {
if signal_error.is_some() {
continue;
}
// Get the observed number of bits for the value parsed earlier // Get the observed number of bits for the value parsed earlier
// and verify that it is not greater than the numbits declared // and verify that it is not greater than the numbits declared
@ -193,39 +219,40 @@ pub(super) fn parse_events<'a>(
} }
None => { None => {
let (f, l) = (file!(), line!()); let (f, l) = (file!(), line!());
let msg = format!("\ let msg = format!(
"\
Error near {f}:{l}. The bitwidth for signal {name} \ Error near {f}:{l}. The bitwidth for signal {name} \
must be specified for a signal of type {sig_type:?}. \ must be specified for a signal of type {sig_type:?}. \
This error occurred while parsing the vcd file at \ This error occurred while parsing the vcd file at \
{cursor:?}"); {cursor:?}"
);
Err(msg)?; Err(msg)?;
} }
}; };
let (f, l )= (file!(), line!()); let (f, l) = (file!(), line!());
let timeline_idx = u32::try_from(vcd.timeline.len()).map_err( let timeline_idx = u32::try_from(vcd.timeline.len()).map_err(|_| {
|_| format!("Error near {f}:{l}. Failed to convert from usize to u32."))?; format!("Error near {f}:{l}. Failed to convert from usize to u32.")
})?;
let timeline_idx = TimelineIdx(timeline_idx); let timeline_idx = TimelineIdx(timeline_idx);
if store_as_string { if store_as_string {
string_timeline_markers.push(timeline_idx); string_timeline_markers.push(timeline_idx);
string_timeline.push(value_string); string_timeline.push(value_string);
Ok(()) Ok(())
} else {
}
else {
u8_timeline_markers.push(timeline_idx); u8_timeline_markers.push(timeline_idx);
let mut curr_num_bytes = value_u8.len(); let mut curr_num_bytes = value_u8.len();
u8_timeline.append(&mut value_u8); u8_timeline.append(&mut value_u8);
// we may need to zero extend values // we may need to zero extend values
// so that we end up storing all values // so that we end up storing all values
// of a particular signal in a consistent // of a particular signal in a consistent
// amount of bytes // amount of bytes
let num_bits = num_bits.unwrap(); let num_bits = num_bits.unwrap();
let bytes_required = (num_bits / 8) + let bytes_required =
if (num_bits % 8) > 0 {1} else {0}; (num_bits / 8) + if (num_bits % 8) > 0 { 1 } else { 0 };
while curr_num_bytes < bytes_required { while curr_num_bytes < bytes_required {
// useful for debugging // useful for debugging
@ -243,8 +270,8 @@ pub(super) fn parse_events<'a>(
Ok(()) Ok(())
} }
} }
Signal::Alias {..} => { Signal::Alias { .. } => {
let (f, l )= (file!(), line!()); let (f, l) = (file!(), line!());
let msg = format!( let msg = format!(
"Error near {f}:{l}, a signal alias should not point to a signal alias.\n\ "Error near {f}:{l}, a signal alias should not point to a signal alias.\n\
This error occurred while parsing vcd file at {cursor:?}"); This error occurred while parsing vcd file at {cursor:?}");
@ -257,21 +284,24 @@ pub(super) fn parse_events<'a>(
"0" => { "0" => {
// lookup signal idx // lookup signal idx
let hash = &word[1..]; let hash = &word[1..];
let (f, l )= (file!(), line!()); let (f, l) = (file!(), line!());
let SignalIdx(ref signal_idx) = signal_map.get(hash).ok_or( let SignalIdx(ref signal_idx) = signal_map.get(hash).ok_or(()).map_err(|_| {
format!("Error near {f}:{l}. Failed to lookup signal {hash} at {cursor:?}"))?; format!(
"Error near {}:{}. Failed to lookup signal {hash} at {cursor:?}",
file!(),
line!()
)
})?;
// account for fact that signal idx could be an alias, so there // account for fact that signal idx could be an alias, so there
// could be one step of indirection // could be one step of indirection
let signal_idx = let signal_idx = {
{
let signal = vcd.all_signals.get(*signal_idx).unwrap(); let signal = vcd.all_signals.get(*signal_idx).unwrap();
match signal { match signal {
Signal::Data {..} => {*signal_idx} Signal::Data { .. } => *signal_idx,
Signal::Alias {signal_alias, ..} => { Signal::Alias { signal_alias, .. } => {
let SignalIdx(ref signal_idx) = signal_alias; let SignalIdx(ref signal_idx) = signal_alias;
signal_idx.clone() signal_idx.clone()
} }
} }
}; };
@ -280,11 +310,19 @@ pub(super) fn parse_events<'a>(
// of the signal signal_idx references // of the signal signal_idx references
let signal = vcd.all_signals.get_mut(signal_idx).unwrap(); let signal = vcd.all_signals.get_mut(signal_idx).unwrap();
match signal { match signal {
Signal::Data {name, sig_type, ref mut signal_error, num_bits, Signal::Data {
u8_timeline, u8_timeline_markers, ..} => { name,
sig_type,
ref mut signal_error,
num_bits,
u8_timeline,
u8_timeline_markers,
..
} => {
// if this is a bad signal, go ahead and skip it // if this is a bad signal, go ahead and skip it
if signal_error.is_some() {continue;} if signal_error.is_some() {
continue;
}
// Get bitwidth and verify that it is 1. // Get bitwidth and verify that it is 1.
// Also account for the error case of a bitwidth of `None` // Also account for the error case of a bitwidth of `None`
@ -292,38 +330,43 @@ pub(super) fn parse_events<'a>(
Some(ref num_bits) => { Some(ref num_bits) => {
if *num_bits != 1 { if *num_bits != 1 {
let (f, l) = (file!(), line!()); let (f, l) = (file!(), line!());
let msg = format!("\ let msg = format!(
"\
Error near {f}:{l}. The bitwidth for signal {name} \ Error near {f}:{l}. The bitwidth for signal {name} \
of sig_type {sig_type:?} is expected to be `1` not \ of sig_type {sig_type:?} is expected to be `1` not \
`{num_bits}`. \ `{num_bits}`. \
This error occurred while parsing the vcd file at \ This error occurred while parsing the vcd file at \
{cursor:?}"); {cursor:?}"
);
*signal_error = Some(msg); *signal_error = Some(msg);
continue; continue;
} }
} }
None => { None => {
let (f, l) = (file!(), line!()); let (f, l) = (file!(), line!());
let msg = format!("\ let msg = format!(
"\
Error near {f}:{l}. The bitwidth for signal {name} \ Error near {f}:{l}. The bitwidth for signal {name} \
must be specified for a signal of type {sig_type:?}. \ must be specified for a signal of type {sig_type:?}. \
This error occurred while parsing the vcd file at \ This error occurred while parsing the vcd file at \
{cursor:?}"); {cursor:?}"
);
Err(msg)?; Err(msg)?;
} }
}; };
let (f, l )= (file!(), line!()); let (f, l) = (file!(), line!());
let timeline_idx = u32::try_from(vcd.timeline.len()).map_err( let timeline_idx = u32::try_from(vcd.timeline.len()).map_err(|_| {
|_| format!("Error near {f}:{l}. Failed to convert from usize to u32."))?; format!("Error near {f}:{l}. Failed to convert from usize to u32.")
})?;
let timeline_idx = TimelineIdx(timeline_idx); let timeline_idx = TimelineIdx(timeline_idx);
u8_timeline_markers.push(timeline_idx); u8_timeline_markers.push(timeline_idx);
u8_timeline.push(0u8); u8_timeline.push(0u8);
Ok(()) Ok(())
} }
Signal::Alias {..} => { Signal::Alias { .. } => {
let (f, l )= (file!(), line!()); let (f, l) = (file!(), line!());
let msg = format!( let msg = format!(
"Error near {f}:{l}, a signal alias should not point to a signal alias.\n\ "Error near {f}:{l}, a signal alias should not point to a signal alias.\n\
This error occurred while parsing vcd file at {cursor:?}"); This error occurred while parsing vcd file at {cursor:?}");
@ -335,21 +378,23 @@ pub(super) fn parse_events<'a>(
"1" => { "1" => {
// lokup signal idx // lokup signal idx
let hash = &word[1..]; let hash = &word[1..];
let (f, l )= (file!(), line!()); let SignalIdx(ref signal_idx) = signal_map.get(hash).ok_or(()).map_err(|_| {
let SignalIdx(ref signal_idx) = signal_map.get(hash).ok_or( format!(
format!("Error near {f}:{l}. Failed to lookup signal {hash} at {cursor:?}"))?; "Error near {}:{}. Failed to lookup signal {hash} at {cursor:?}",
file!(),
line!()
)
})?;
// account for fact that signal idx could be an alias, so there // account for fact that signal idx could be an alias, so there
// could be one step of indirection // could be one step of indirection
let signal_idx = let signal_idx = {
{
let signal = vcd.all_signals.get(*signal_idx).unwrap(); let signal = vcd.all_signals.get(*signal_idx).unwrap();
match signal { match signal {
Signal::Data {..} => {*signal_idx} Signal::Data { .. } => *signal_idx,
Signal::Alias {signal_alias, ..} => { Signal::Alias { signal_alias, .. } => {
let SignalIdx(ref signal_idx) = signal_alias; let SignalIdx(ref signal_idx) = signal_alias;
signal_idx.clone() signal_idx.clone()
} }
} }
}; };
@ -358,11 +403,19 @@ pub(super) fn parse_events<'a>(
// of the signal signal_idx references // of the signal signal_idx references
let signal = vcd.all_signals.get_mut(signal_idx).unwrap(); let signal = vcd.all_signals.get_mut(signal_idx).unwrap();
match signal { match signal {
Signal::Data {name, sig_type, ref mut signal_error, num_bits, Signal::Data {
u8_timeline, u8_timeline_markers, ..} => { name,
sig_type,
ref mut signal_error,
num_bits,
u8_timeline,
u8_timeline_markers,
..
} => {
// if this is a bad signal, go ahead and skip it // if this is a bad signal, go ahead and skip it
if signal_error.is_some() {continue;} if signal_error.is_some() {
continue;
}
// Get bitwidth and verify that it is 1. // Get bitwidth and verify that it is 1.
// Also account for the error case of a bitwidth of `None` // Also account for the error case of a bitwidth of `None`
@ -370,38 +423,43 @@ pub(super) fn parse_events<'a>(
Some(ref num_bits) => { Some(ref num_bits) => {
if *num_bits != 1 { if *num_bits != 1 {
let (f, l) = (file!(), line!()); let (f, l) = (file!(), line!());
let msg = format!("\ let msg = format!(
"\
Error near {f}:{l}. The bitwidth for signal {name} \ Error near {f}:{l}. The bitwidth for signal {name} \
of sig_type {sig_type:?} is expected to be `1` not \ of sig_type {sig_type:?} is expected to be `1` not \
`{num_bits}`. \ `{num_bits}`. \
This error occurred while parsing the vcd file at \ This error occurred while parsing the vcd file at \
{cursor:?}"); {cursor:?}"
);
*signal_error = Some(msg); *signal_error = Some(msg);
continue; continue;
} }
} }
None => { None => {
let (f, l) = (file!(), line!()); let (f, l) = (file!(), line!());
let msg = format!("\ let msg = format!(
"\
Error near {f}:{l}. The bitwidth for signal {name} \ Error near {f}:{l}. The bitwidth for signal {name} \
must be specified for a signal of type {sig_type:?}. \ must be specified for a signal of type {sig_type:?}. \
This error occurred while parsing the vcd file at \ This error occurred while parsing the vcd file at \
{cursor:?}"); {cursor:?}"
);
Err(msg)?; Err(msg)?;
} }
}; };
let (f, l )= (file!(), line!()); let (f, l) = (file!(), line!());
let timeline_idx = u32::try_from(vcd.timeline.len()).map_err( let timeline_idx = u32::try_from(vcd.timeline.len()).map_err(|_| {
|_| format!("Error near {f}:{l}. Failed to convert from usize to u32."))?; format!("Error near {f}:{l}. Failed to convert from usize to u32.")
})?;
let timeline_idx = TimelineIdx(timeline_idx); let timeline_idx = TimelineIdx(timeline_idx);
u8_timeline_markers.push(timeline_idx); u8_timeline_markers.push(timeline_idx);
u8_timeline.push(1u8); u8_timeline.push(1u8);
Ok(()) Ok(())
} }
Signal::Alias {..} => { Signal::Alias { .. } => {
let (f, l )= (file!(), line!()); let (f, l) = (file!(), line!());
let msg = format!( let msg = format!(
"Error near {f}:{l}, a signal alias should not point to a signal alias.\n\ "Error near {f}:{l}, a signal alias should not point to a signal alias.\n\
This error occurred while parsing vcd file at {cursor:?}"); This error occurred while parsing vcd file at {cursor:?}");
@ -414,21 +472,23 @@ pub(super) fn parse_events<'a>(
let val = word.to_string(); let val = word.to_string();
// lokup signal idx // lokup signal idx
let hash = &word[1..]; let hash = &word[1..];
let (f, l )= (file!(), line!()); let SignalIdx(ref signal_idx) = signal_map.get(hash).ok_or(()).map_err(|_| {
let SignalIdx(ref signal_idx) = signal_map.get(hash).ok_or( format!(
format!("Error near {f}:{l}. Failed to lookup signal {hash} at {cursor:?}"))?; "Error near {}:{}. Failed to lookup signal {hash} at {cursor:?}",
file!(),
line!()
)
})?;
// account for fact that signal idx could be an alias, so there // account for fact that signal idx could be an alias, so there
// could be one step of indirection // could be one step of indirection
let signal_idx = let signal_idx = {
{
let signal = vcd.all_signals.get(*signal_idx).unwrap(); let signal = vcd.all_signals.get(*signal_idx).unwrap();
match signal { match signal {
Signal::Data {..} => {*signal_idx} Signal::Data { .. } => *signal_idx,
Signal::Alias {signal_alias, ..} => { Signal::Alias { signal_alias, .. } => {
let SignalIdx(ref signal_idx) = signal_alias; let SignalIdx(ref signal_idx) = signal_alias;
signal_idx.clone() signal_idx.clone()
} }
} }
}; };
@ -437,11 +497,19 @@ pub(super) fn parse_events<'a>(
// of the signal signal_idx references // of the signal signal_idx references
let signal = vcd.all_signals.get_mut(signal_idx).unwrap(); let signal = vcd.all_signals.get_mut(signal_idx).unwrap();
match signal { match signal {
Signal::Data {name, sig_type, ref mut signal_error, num_bits, Signal::Data {
string_timeline, string_timeline_markers, ..} => { name,
sig_type,
ref mut signal_error,
num_bits,
string_timeline,
string_timeline_markers,
..
} => {
// if this is a bad signal, go ahead and skip it // if this is a bad signal, go ahead and skip it
if signal_error.is_some() {continue;} if signal_error.is_some() {
continue;
}
// Get bitwidth and verify that it is 1. // Get bitwidth and verify that it is 1.
// Also account for the error case of a bitwidth of `None` // Also account for the error case of a bitwidth of `None`
@ -449,38 +517,43 @@ pub(super) fn parse_events<'a>(
Some(ref num_bits) => { Some(ref num_bits) => {
if *num_bits != 1 { if *num_bits != 1 {
let (f, l) = (file!(), line!()); let (f, l) = (file!(), line!());
let msg = format!("\ let msg = format!(
"\
Error near {f}:{l}. The bitwidth for signal {name} \ Error near {f}:{l}. The bitwidth for signal {name} \
of sig_type {sig_type:?} is expected to be `1` not \ of sig_type {sig_type:?} is expected to be `1` not \
`{num_bits}`. \ `{num_bits}`. \
This error occurred while parsing the vcd file at \ This error occurred while parsing the vcd file at \
{cursor:?}"); {cursor:?}"
);
*signal_error = Some(msg); *signal_error = Some(msg);
continue; continue;
} }
} }
None => { None => {
let (f, l) = (file!(), line!()); let (f, l) = (file!(), line!());
let msg = format!("\ let msg = format!(
"\
Error near {f}:{l}. The bitwidth for signal {name} \ Error near {f}:{l}. The bitwidth for signal {name} \
must be specified for a signal of type {sig_type:?}. \ must be specified for a signal of type {sig_type:?}. \
This error occurred while parsing the vcd file at \ This error occurred while parsing the vcd file at \
{cursor:?}"); {cursor:?}"
);
Err(msg)?; Err(msg)?;
} }
}; };
let (f, l )= (file!(), line!()); let (f, l) = (file!(), line!());
let timeline_idx = u32::try_from(vcd.timeline.len()).map_err( let timeline_idx = u32::try_from(vcd.timeline.len()).map_err(|_| {
|_| format!("Error near {f}:{l}. Failed to convert from usize to u32."))?; format!("Error near {f}:{l}. Failed to convert from usize to u32.")
})?;
let timeline_idx = TimelineIdx(timeline_idx); let timeline_idx = TimelineIdx(timeline_idx);
string_timeline_markers.push(timeline_idx); string_timeline_markers.push(timeline_idx);
string_timeline.push(val); string_timeline.push(val);
Ok(()) Ok(())
} }
Signal::Alias {..} => { Signal::Alias { .. } => {
let (f, l )= (file!(), line!()); let (f, l) = (file!(), line!());
let msg = format!( let msg = format!(
"Error near {f}:{l}, a signal alias should not point to a signal alias.\n\ "Error near {f}:{l}, a signal alias should not point to a signal alias.\n\
This error occurred while parsing vcd file at {cursor:?}"); This error occurred while parsing vcd file at {cursor:?}");
@ -494,4 +567,4 @@ pub(super) fn parse_events<'a>(
} }
Ok(()) Ok(())
} }

View file

@ -4,18 +4,27 @@ use chrono::prelude::*;
pub(super) struct Version(pub String); pub(super) struct Version(pub String);
#[derive(Debug)] #[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)] #[derive(Debug)]
pub(super) struct Metadata { pub(super) struct Metadata {
pub(super) date : Option<DateTime<Utc>>, pub(super) date: Option<DateTime<Utc>>,
pub(super) version : Option<Version>, pub(super) version: Option<Version>,
pub(super) timescale : (Option<u32>, Timescale)} pub(super) timescale: (Option<u32>, Timescale),
}
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub(super) struct ScopeIdx(pub(super) usize); pub(super) struct ScopeIdx(pub(super) usize);
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone, PartialEq)]
pub(super) struct SignalIdx(pub(super) usize); pub(super) struct SignalIdx(pub(super) usize);
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
@ -25,63 +34,71 @@ pub(super) struct TimelineIdx(pub(super) u32);
pub struct StartIdx(pub(super) u32); pub struct StartIdx(pub(super) u32);
#[derive(Debug)] #[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)] #[derive(Debug)]
pub(super) enum Signal{ pub(super) enum Signal {
Data{ Data {
name : String, name: String,
sig_type : SigType, sig_type: SigType,
// I've seen a 0 bit signal parameter in a xilinx // I've seen a 0 bit signal parameter in a xilinx
// simulation before that gets assigned 1 bit values. // simulation before that gets assigned 1 bit values.
// I consider this to be bad behavior. We capture such // I consider this to be bad behavior. We capture such
// errors in the following type. // errors in the following type.
signal_error : Option<String>, signal_error: Option<String>,
num_bits : Option<usize>, num_bits: Option<usize>,
// TODO : may be able to remove self_idx // TODO : may be able to remove self_idx
self_idx : SignalIdx, self_idx: SignalIdx,
// we could encounter a mix of pure values and strings // we could encounter a mix of pure values and strings
// for the same signal timeline // for the same signal timeline
u8_timeline : Vec<u8>, u8_timeline: Vec<u8>,
u8_timeline_markers : Vec<TimelineIdx>, u8_timeline_markers: Vec<TimelineIdx>,
string_timeline : Vec<String>, string_timeline: Vec<String>,
string_timeline_markers : Vec<TimelineIdx>, string_timeline_markers: Vec<TimelineIdx>,
scope_parent : ScopeIdx}, scope_parent: ScopeIdx,
Alias{ },
name : String, Alias {
signal_alias : SignalIdx} name: String,
signal_alias: SignalIdx,
},
} }
#[derive(Debug)] #[derive(Debug)]
pub(super) struct Scope { pub(super) struct Scope {
pub(super) name : String, pub(super) name: String,
pub(super) parent_idx : Option<ScopeIdx>, pub(super) parent_idx: Option<ScopeIdx>,
pub(super) self_idx : ScopeIdx, pub(super) self_idx: ScopeIdx,
pub(super) child_signals : Vec<SignalIdx>,
pub(super) child_scopes : Vec<ScopeIdx>}
pub(super) child_signals: Vec<SignalIdx>,
pub(super) child_scopes: Vec<ScopeIdx>,
}
// TODO: document how timeline is represented // TODO: document how timeline is represented
#[derive(Debug)] #[derive(Debug)]
pub struct VCD { pub struct VCD {
pub(super) metadata : Metadata, pub(super) metadata: Metadata,
pub timeline : Vec<u8>, pub timeline: Vec<u8>,
pub timeline_markers : Vec<StartIdx>, pub timeline_markers: Vec<StartIdx>,
pub(super) all_signals : Vec<Signal>, pub(super) all_signals: Vec<Signal>,
pub(super) all_scopes : Vec<Scope>, pub(super) all_scopes: Vec<Scope>,
pub(super) scope_roots : Vec<ScopeIdx>} pub(super) scope_roots: Vec<ScopeIdx>,
}
impl VCD { 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 // functions upon encountering scopes and signals
fn print_scope_tree( fn print_scope_tree(&self, root_scope_idx: ScopeIdx, depth: usize) {
&self, let all_scopes = &self.all_scopes;
root_scope_idx : ScopeIdx,
depth : usize)
{
let all_scopes = &self.all_scopes;
let all_signals = &self.all_signals; let all_signals = &self.all_signals;
let indent = " ".repeat(depth * 4); let indent = " ".repeat(depth * 4);
@ -94,17 +111,16 @@ impl VCD {
for SignalIdx(ref signal_idx) in &root_scope.child_signals { for SignalIdx(ref signal_idx) in &root_scope.child_signals {
let child_signal = &all_signals[*signal_idx]; let child_signal = &all_signals[*signal_idx];
let name = match child_signal { let name = match child_signal {
Signal::Data{name, ..} => {name} Signal::Data { name, .. } => name,
Signal::Alias{name, ..} => {name} Signal::Alias { name, .. } => name,
}; };
println!("{indent} - sig: {name}") println!("{indent} - sig: {name}")
} }
println!(); println!();
for scope_idx in &root_scope.child_scopes { 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) { pub fn print_scopes(&self) {
@ -120,23 +136,23 @@ impl VCD {
for signal in &self.all_signals { for signal in &self.all_signals {
match signal { match signal {
Signal::Alias {..} => {} Signal::Alias { .. } => {}
Signal::Data { Signal::Data {
name, name,
self_idx, self_idx,
u8_timeline, u8_timeline,
.. } => { ..
if u8_timeline.len() > max_len { } => {
max_len = u8_timeline.len(); if u8_timeline.len() > max_len {
let SignalIdx(idx_usize) = self_idx; max_len = u8_timeline.len();
idx = *idx_usize; let SignalIdx(idx_usize) = self_idx;
signal_name = name.clone(); idx = *idx_usize;
} signal_name = name.clone();
} }
}
} }
} }
dbg!((idx, max_len, signal_name)); dbg!((idx, max_len, signal_name));
} }
} }