Compare commits

..

64 commits

Author SHA1 Message Date
Yehowshua Immanuel f2676673cd small update 2024-03-16 23:29:10 -04:00
Yehowshua Immanuel 133b1a2693
Sunsetting 2024-03-12 18:50:29 -04:00
Frans Skarman de897a5010
Merge pull request #36 from ThePerfectComputer/double_sided_query
Don't throw away the right hand time of changes
2023-12-13 08:56:15 +00:00
TheZoq2 e2e3541e3f Refactor the double-sided-query to make it less error-prone 2023-12-05 16:44:19 +01:00
TheZoq2 7d414f36dd Correctly compute next index 2023-12-05 14:59:04 +01:00
TheZoq2 9a8c2a03eb Don't throw away the right hand time of changes 2023-12-05 14:29:46 +01:00
Frans Skarman 3851c4e06c
Merge pull request #35 from ThePerfectComputer/scopefix
Add workaround for files produced by verilator which have annonymous scopes
2023-12-05 13:29:26 +00:00
TheZoq2 9ba53df728 Add workaround for files produced by verilator which have annonymous scopes 2023-11-03 19:54:26 +01:00
TheZoq2 7a2bed42a3 Add some clones and expose SignalErrors 2023-10-25 17:55:05 +02:00
TheZoq2 d42d01f9c3 Expose index 2023-10-23 18:15:30 +02:00
Frans Skarman f3e45f8497
Merge pull request #20 from ThePerfectComputer/name_width_separation
Separate name from width
2023-10-23 13:28:02 +00:00
TheZoq2 e7f2f661df Separate name from width 2023-10-23 15:27:47 +02:00
Frans Skarman 4f31ec842f
Merge pull request #33 from oscargus/bumpdependencies
Bump dependencies
2023-10-16 09:02:37 +00:00
Oscar Gustafsson a8ee52a11e Bump dependencies 2023-10-15 22:05:52 +02:00
Frans Skarman e3c60600f4
Merge pull request #32 from oscargus/maxsize
Increase maximum bit width
2023-10-11 08:20:08 +00:00
Oscar Gustafsson 9c54c3a295 Increase maximum bit width 2023-10-11 09:54:59 +02:00
Frans Skarman b9c507c9d8
Merge pull request #29 from oscargus/realparsing
Add support for parsing real values (to a String)
2023-10-10 14:15:50 +00:00
Oscar Gustafsson b886e5d26d Add support for parsing real values (to a String) 2023-10-10 16:08:09 +02:00
Frans Skarman 01eacd4028
Merge pull request #28 from ThePerfectComputer/revert-27-realvalues
Revert "Add support for real values"
2023-10-10 13:18:31 +00:00
Frans Skarman a1890a63dc
Revert "Add support for real values" 2023-10-10 13:15:17 +00:00
Frans Skarman a1bf2cb15f
Merge pull request #27 from oscargus/realvalues
Add support for real values
2023-10-10 13:11:04 +00:00
Oscar Gustafsson 77b1aa0a03 Add support for real values 2023-10-10 12:52:59 +02:00
Frans Skarman eaea65f7ea
Merge pull request #26 from oscargus/signaltype
Add signal type query
2023-10-09 15:10:05 +00:00
Oscar Gustafsson dba5773ebe Add signal type query 2023-10-09 16:20:12 +02:00
Frans Skarman 0df20db383
Merge pull request #25 from oscargus/signaltypes
Add all signal types in standard
2023-10-09 12:38:13 +00:00
Oscar Gustafsson b576e1b57d Add all signal types in standard 2023-10-09 14:04:59 +02:00
Frans Skarman 6c76a066e3
Merge pull request #24 from oscargus/clonetimescale
Add Clone, Eq, PartialEq to Timescale
2023-10-09 07:23:44 +00:00
Oscar Gustafsson 4e8c8e4896 Add derive cases and fmt to Timescale 2023-10-08 20:34:04 +02:00
Frans Skarman 5a7675d0be
Merge pull request #21 from oscargus/fork
Add support for fork scopes
2023-10-06 14:42:53 +00:00
Frans Skarman 76da883dae
Merge pull request #19 from oscargus/bitparsing
Remove identifier from bit string and guarantee lower case
2023-10-06 08:56:14 +00:00
Oscar Gustafsson 5ee512aff6 Remove identifier from bit string and guarantee lower case 2023-10-06 10:53:14 +02:00
Oscar Gustafsson dad11e2f57 Add support for fork scopes 2023-10-06 08:23:59 +02:00
ThePerfectComputer bc7c6913ce
Fix typos 2023-09-30 12:27:14 -04:00
ThePerfectComputer 506abf9058
Update with status and vision 2023-09-30 12:25:58 -04:00
ThePerfectComputer 7b9090f5af
Add screenshot and improve description 2023-09-28 23:33:48 -04:00
ThePerfectComputer 64372871a0
Update README.md
Correct surfer link
2023-09-28 21:10:07 -04:00
Frans Skarman fccd65ef8f
Merge pull request #18 from oscargus/cleanup
Cleanup
2023-09-27 07:40:33 +00:00
Oscar Gustafsson 0795465c36 Clippy automatic fixes 2023-09-26 13:26:38 +02:00
Oscar Gustafsson 5758e77371 Run cargo-fmt 2023-09-26 13:16:14 +02:00
Frans Skarman 22eaf8da15
Merge pull request #15 from oscargus/vhdl
Add support for VHDL std_ulogic
2023-09-26 11:05:21 +00:00
Frans Skarman 806ad9be3c
Merge pull request #12 from oscargus/keepindex
Keep index as part of signal name
2023-09-26 11:03:10 +00:00
Oscar Gustafsson a7537206e2 Add support for VHDL std_ulogic 2023-09-21 12:08:12 +02:00
Oscar Gustafsson 0897bbae66 Keep index as part of signal name 2023-09-20 14:46:13 +02:00
Frans Skarman e46e0b0cc5
Merge pull request #13 from oscargus/commenttypo
Allow comments in scope (fix typo)
2023-09-19 13:00:01 +00:00
Frans Skarman de6fae83c4
Merge pull request #14 from TheZoq2/main
Add real_idx function to signal
2023-09-19 12:59:18 +00:00
Frans Skarman 14a314a2f3
Merge pull request #11 from oscargus/removedeprecation
Replace deprecated method
2023-09-18 17:37:43 +00:00
Oscar Gustafsson 03705f263d Allow comments in scope (fix typo) 2023-09-13 14:18:18 +02:00
Frans Skarman 878b06e666
Merge pull request #1 from LucasKl/main
Add real_idx function to signal
2023-09-11 08:55:19 +00:00
Lucas Klemmer f2689885be Add real_idx function to signal 2023-09-10 16:23:04 +02:00
Frans Skarman 1534924d05
Merge pull request #10 from oscargus/surferreadme
Update README with more information about surfer
2023-09-07 11:10:25 +00:00
Oscar Gustafsson 73332179ee Replace deprecated method 2023-09-06 13:02:21 +02:00
Oscar Gustafsson c0d245c0e5 Update README with more information about surfer 2023-09-06 12:53:38 +02:00
ThePerfectComputer 4f88eab5de
Update README.md 2023-07-25 13:10:14 -04:00
ThePerfectComputer fc169c2e3b
Merge pull request #8 from TheZoq2/expose_stuff
I haven't worked on this in nearly a year. At this point, I might as well merge your pull request. Thanks.
2023-07-01 20:20:05 -04:00
TheZoq2 a8edb3d602 expse paths and fix warnings 2023-06-16 17:08:20 +02:00
TheZoq2 53bbacd261 Expose signal time stamp 2023-05-01 17:37:24 +02:00
TheZoq2 79300afd43 Make generic over readers 2023-01-09 18:45:10 +01:00
TheZoq2 bc73db5dba Do not include range in name 2023-01-03 16:33:44 +01:00
TheZoq2 3da3579213 Add more timing information 2022-12-23 11:34:48 +01:00
TheZoq2 b162d67a9d impl traits required for maps for idxes 2022-12-22 18:11:19 +01:00
ThePerfectComputer 540ed13e2c
Update README.md 2022-10-26 14:21:19 -04:00
Yehowshua Immanuel 38579f08e9 update UI 2022-10-26 14:07:58 -04:00
ThePerfectComputer f95db0cd2d
Merge pull request #6 from ThePerfectComputer/refactor-signal
Refactor signal
2022-10-26 02:23:08 -04:00
ThePerfectComputer ea5675d920
Update README.md 2022-10-20 09:26:58 -04:00
21 changed files with 996 additions and 321 deletions

View file

@ -10,7 +10,7 @@ debug = 1
[dependencies] [dependencies]
num = "0.4" num = "0.4"
clap = { version = "3.1.8", features = ["derive"] } clap = { version = "4.4.6", features = ["derive"] }
chrono = "0.4" chrono = "0.4"
# TODO : remove itertools once date parser is reworked. # TODO : remove itertools once date parser is reworked.
itertools = "0.10.3" itertools = "0.11"

View file

@ -1,11 +1,23 @@
Copyright - Yehowshua Immanuel Copyright(2023) - Yehowshua Immanuel
# Vision
Imagine being able to visualize a CPU pipeline diagram by merely loading a simulation waveform dump, sprinkling in a bit of code, and dragging and dropping some diagram blocks into the visualizer. This project aims to offer such an experience.
Since this project is written in Rust, it should also be able to run in the browser via web-assembly.
# Status
As of January 2024, work on the Fastwave Backend is stalled. It has been a fun journey watching Fastwave enable the first iterations of the [surfer waveform viewer](https://surfer-project.org). Now surfer uses an even better backend called [Wellen](https://github.com/ekiwi/wellen?tab=readme-ov-file). Go check it out! I hear it's really good. Perhaps I will soon archive the Fastwave Backend.
Browser demo: https://app.surfer-project.org/
Screenshot of surfer frontend below:
<img width="1609" alt="image" src="https://github.com/ThePerfectComputer/FastWaveBackend/assets/103011002/63deffc0-98b0-405f-b093-74d10b422b01">
# A High performance, VCD Parser written in Rust # A High performance, VCD Parser written in Rust
Below is a screenshot of the early stages of the proprietary I hope for a GUI that eventually looks like the following...
viewer frontend that builds on this backend:
![](assets/viewer.png) ![](assets/FastWaveMockup.png)
# Current Features # Current Features
@ -40,6 +52,8 @@ slower.
## Results ## Results
LZ4 should really improve memory usage once I add it. Note that GTKWave uses LZ4 on the fly.
| Software | Time(s) | Memory(MB) | | Software | Time(s) | Memory(MB) |
|----------|---------|------------| |----------|---------|------------|
| FastWave | ~27.30 | 1100+ | | FastWave | ~27.30 | 1100+ |

BIN
assets/FastWaveMockup.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 378 KiB

View file

@ -10,7 +10,6 @@ use fastwave_backend::parse_vcd;
#[derive(Parser)] #[derive(Parser)]
struct Cli { struct Cli {
/// The path to the file to read /// The path to the file to read
#[clap(parse(from_os_str))]
path: std::path::PathBuf, path: std::path::PathBuf,
} }
@ -24,8 +23,11 @@ fn main() -> std::io::Result<()> {
parse_vcd(file).unwrap(); parse_vcd(file).unwrap();
let elapsed = now.elapsed(); let elapsed = now.elapsed();
println!("Parsed VCD file {} : {:.2?}", &args.path.as_os_str().to_str().unwrap(), elapsed); println!(
"Parsed VCD file {} : {:.2?}",
&args.path.as_os_str().to_str().unwrap(),
elapsed
);
Ok(()) Ok(())
} }

View file

@ -4,10 +4,12 @@
// the root of the folder containing the sources for this program. // the root of the folder containing the sources for this program.
use std::fs::File; use std::fs::File;
use fastwave_backend::{ScopeIdx, VCD, parse_vcd, SignalIdx}; use fastwave_backend::{parse_vcd, ScopeIdx, SignalIdx, VCD};
fn indented_print(indent : u8, name : &String) { fn indented_print(indent: u8, name: &String) {
for _ in 0..indent {print!(" |");} for _ in 0..indent {
print!(" |");
}
print!("---"); print!("---");
println!("{name}"); println!("{name}");
} }
@ -15,7 +17,7 @@ fn indented_print(indent : u8, name : &String) {
// TODO: refactor into more general visitor pattern that takes a // TODO: refactor into more general visitor pattern that takes a
// function as an argument. // function as an argument.
fn visit_all_scopes(vcd: &VCD) { fn visit_all_scopes(vcd: &VCD) {
fn visit_all_scope_children(root_idx: ScopeIdx, vcd: &VCD, indent : u8) { fn visit_all_scope_children(root_idx: ScopeIdx, vcd: &VCD, indent: u8) {
if vcd.child_scopes_by_idx(root_idx).is_empty() { if vcd.child_scopes_by_idx(root_idx).is_empty() {
} else { } else {
for child_scope_idx in vcd.child_scopes_by_idx(root_idx) { for child_scope_idx in vcd.child_scopes_by_idx(root_idx) {
@ -25,7 +27,7 @@ fn visit_all_scopes(vcd: &VCD) {
let SignalIdx(idx) = signal_idx; let SignalIdx(idx) = signal_idx;
indented_print(indent + 1, &format!("{},{}", signal.name(), idx)); indented_print(indent + 1, &format!("{},{}", signal.name(), idx));
} }
visit_all_scope_children(child_scope_idx, vcd.clone(), indent + 1); visit_all_scope_children(child_scope_idx, vcd, indent + 1);
} }
} }
} }
@ -36,7 +38,6 @@ fn visit_all_scopes(vcd: &VCD) {
} }
fn main() -> std::io::Result<()> { fn main() -> std::io::Result<()> {
use std::time::Instant; use std::time::Instant;
// we start by printing out the entire signal tree of // we start by printing out the entire signal tree of
@ -53,7 +54,6 @@ fn main() -> std::io::Result<()> {
println!("Done Printing Scopes"); println!("Done Printing Scopes");
println!(); println!();
// we then parse another VCD, print its signal tree and // we then parse another VCD, print its signal tree and
// query some values on its timeline // query some values on its timeline
let now = Instant::now(); let now = Instant::now();
@ -73,11 +73,8 @@ fn main() -> std::io::Result<()> {
let timestamps = vec![31499_000u32, 31500_000u32, 57760_000u32]; let timestamps = vec![31499_000u32, 31500_000u32, 57760_000u32];
for timestamp in timestamps { for timestamp in timestamps {
let time = num::BigUint::from(timestamp); let time = num::BigUint::from(timestamp);
let val = state_signal let val = state_signal.query_string_val_on_tmln(&time, &vcd).unwrap();
.query_string_val_on_tmln(&time, &vcd)
.unwrap();
println!("Signal `{name}` has value `{val}` at time `{time}`"); println!("Signal `{name}` has value `{val}` at time `{time}`");
} }
Ok(()) Ok(())

View file

@ -5,8 +5,8 @@
mod vcd; mod vcd;
pub use vcd::parse::parse_vcd; pub use vcd::parse::parse_vcd;
pub use vcd::types::{ScopeIdx, SignalIdx, VCD}; pub use vcd::signal::{Signal, SignalType, SignalValue, SignalErrors};
pub use vcd::types::{Metadata, Timescale, Version}; pub use vcd::types::{Metadata, Timescale, Version};
pub use vcd::signal::{Signal}; pub use vcd::types::{ScopeIdx, SignalIdx, VCD};
pub use num::BigUint; pub use num::BigUint;

View file

@ -3,8 +3,8 @@
// and the YEHOWSHUA license, both of which can be found at // and the YEHOWSHUA license, both of which can be found at
// the root of the folder containing the sources for this program. // the root of the folder containing the sources for this program.
mod reader;
pub(crate) mod types;
pub(crate) mod parse; pub(crate) mod parse;
mod reader;
pub(crate) mod signal; pub(crate) mod signal;
pub(crate) mod types;
mod utilities; mod utilities;

View file

@ -2,16 +2,14 @@
// This program is distributed under both the GPLV3 license // This program is distributed under both the GPLV3 license
// and the YEHOWSHUA license, both of which can be found at // and the YEHOWSHUA license, both of which can be found at
// the root of the folder containing the sources for this program. // the root of the folder containing the sources for this program.
use std::fs::File;
mod combinator_atoms; mod combinator_atoms;
mod types; mod events;
mod metadata; mod metadata;
mod scopes; mod scopes;
mod events; mod types;
pub fn parse_vcd(file: impl std::io::Read) -> Result<super::types::VCD, String> {
pub fn parse_vcd(file: File) -> Result<super::types::VCD, String> {
let mut word_gen = super::reader::WordReader::new(file); let mut word_gen = super::reader::WordReader::new(file);
let header = metadata::parse_metadata(&mut word_gen)?; let header = metadata::parse_metadata(&mut word_gen)?;
@ -27,6 +25,7 @@ pub fn parse_vcd(file: File) -> Result<super::types::VCD, String> {
all_signals: vec![], all_signals: vec![],
all_scopes: vec![], all_scopes: vec![],
root_scopes: vec![], root_scopes: vec![],
largest_timestamp: None,
}; };
scopes::parse_scopes(&mut word_gen, &mut vcd, &mut signal_map)?; scopes::parse_scopes(&mut word_gen, &mut vcd, &mut signal_map)?;

View file

@ -6,15 +6,13 @@ use super::super::reader::{next_word, WordReader};
use super::types::ParseResult; use super::types::ParseResult;
pub(super) fn digit(chr: u8) -> bool { pub(super) fn digit(chr: u8) -> bool {
let zero = b'0' as u8; let zero = b'0';
let nine = b'9' as u8; let nine = b'9';
let between_zero_and_nine = (chr >= zero) && (nine >= chr); (chr >= zero) && (nine >= chr)
return between_zero_and_nine;
} }
pub(super) fn take_until<'a>(word: &'a str, pattern: u8) -> ParseResult<'a> { pub(super) fn take_until(word: &str, pattern: u8) -> ParseResult<'_> {
let mut new_start = 0; let mut new_start = 0;
for chr in word.as_bytes() { for chr in word.as_bytes() {
@ -25,13 +23,13 @@ pub(super) fn take_until<'a>(word: &'a str, pattern: u8) -> ParseResult<'a> {
} }
} }
return ParseResult { ParseResult {
matched: &word[0..new_start], matched: &word[0..new_start],
residual: &word[new_start..], residual: &word[new_start..],
}; }
} }
pub(super) fn take_while<'a>(word: &'a str, cond: fn(u8) -> bool) -> ParseResult<'a> { pub(super) fn take_while(word: &str, cond: fn(u8) -> bool) -> ParseResult<'_> {
let mut new_start = 0; let mut new_start = 0;
for chr in word.as_bytes() { for chr in word.as_bytes() {
@ -42,10 +40,10 @@ pub(super) fn take_while<'a>(word: &'a str, cond: fn(u8) -> bool) -> ParseResult
} }
} }
return ParseResult { ParseResult {
matched: &word[0..new_start], matched: &word[0..new_start],
residual: &word[new_start..], residual: &word[new_start..],
}; }
} }
pub(super) fn tag<'a>(word: &'a str, pattern: &'a str) -> ParseResult<'a> { pub(super) fn tag<'a>(word: &'a str, pattern: &'a str) -> ParseResult<'a> {
@ -63,20 +61,23 @@ pub(super) fn tag<'a>(word: &'a str, pattern: &'a str) -> ParseResult<'a> {
new_start += 1; new_start += 1;
} }
return ParseResult { ParseResult {
matched: &word[0..new_start], matched: &word[0..new_start],
residual: &word[new_start..], residual: &word[new_start..],
}; }
} }
pub(super) fn ident(word_reader: &mut WordReader, keyword: &str) -> Result<(), String> { pub(super) fn ident<R: std::io::Read>(
word_reader: &mut WordReader<R>,
keyword: &str,
) -> Result<(), String> {
// let keyword = "module"; // let keyword = "module";
let (word, cursor) = next_word!(word_reader)?; let (word, cursor) = next_word!(word_reader)?;
if word == keyword { if word == keyword {
return Ok(()); Ok(())
} else { } else {
let err = format!("found keyword `{word}` but expected `{keyword}` on {cursor:?}"); let err = format!("found keyword `{word}` but expected `{keyword}` on {cursor:?}");
return Err(err); Err(err)
} }
} }

View file

@ -3,18 +3,17 @@
// and the YEHOWSHUA license, both of which can be found at // and the YEHOWSHUA license, both of which can be found at
// the root of the folder containing the sources for this program. // the root of the folder containing the sources for this program.
use std::collections::HashMap;
use num::BigUint; use num::BigUint;
use std::collections::HashMap;
use super::super::utilities::{BinaryParserErrTypes, binary_str_to_vec_u8}; use super::super::reader::{next_word, Cursor, Line, Word, WordReader};
use super::super::signal::{SignalEnum, LsbIdxOfTmstmpValOnTmln}; use super::super::signal::{LsbIdxOfTmstmpValOnTmln, SignalEnum};
use super::super::reader::{WordReader, Cursor, Line, Word, next_word};
use super::super::types::{SignalIdx, VCD}; use super::super::types::{SignalIdx, VCD};
use super::super::utilities::{binary_str_to_vec_u8, BinaryParserErrTypes};
pub(super) fn parse_events<R: std::io::Read>(
pub(super) fn parse_events<'a>( word_reader: &mut WordReader<R>,
word_reader: &mut WordReader, vcd: &mut VCD,
vcd: &'a mut VCD,
signal_map: &mut HashMap<String, SignalIdx>, signal_map: &mut HashMap<String, SignalIdx>,
) -> Result<(), String> { ) -> Result<(), String> {
let mut curr_tmstmp_lsb_idx = 0u32; let mut curr_tmstmp_lsb_idx = 0u32;
@ -40,14 +39,14 @@ pub(super) fn parse_events<'a>(
"#" => { "#" => {
let value = &word[1..]; let value = &word[1..];
let (f, l) = (file!(), line!()); let (f, l) = (file!(), line!());
let value = BigUint::parse_bytes(value.as_bytes(), 10) let value_biguint = BigUint::parse_bytes(value.as_bytes(), 10)
.ok_or(()) .ok_or(())
.map_err(|_| { .map_err(|_| {
format!( format!(
"Error near {f}:{l}. Failed to parse {value} as BigInt at {cursor:?}" "Error near {f}:{l}. Failed to parse {value} as BigInt at {cursor:?}"
) )
})?; })?;
let mut value = value.to_bytes_le(); let mut value = value_biguint.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.
curr_tmstmp_len_u8 = u8::try_from(value.len()).map_err(|_| { curr_tmstmp_len_u8 = u8::try_from(value.len()).map_err(|_| {
@ -66,15 +65,16 @@ pub(super) fn parse_events<'a>(
) )
})?; })?;
vcd.tmstmps_encoded_as_u8s.append(&mut value); vcd.tmstmps_encoded_as_u8s.append(&mut value);
vcd.largest_timestamp = Some(value_biguint);
} }
// handle the case of an n bit signal whose value must be parsed // handle the case of an n bit signal whose value must be parsed
"b" => { "b" => {
let binary_value = &word[1..]; let binary_value = &word[1..];
let observed_num_bits = u16::try_from(binary_value.len()).map_err(|_| { let observed_num_bits = u32::try_from(binary_value.len()).map_err(|_| {
format!( format!(
"Error near {}:{}, {cursor:?}. \ "Error near {}:{}, {cursor:?}. \
Found signal with more than 2^16 - 1 bits.", Found signal with more than 2^32 - 1 bits.",
file!(), file!(),
line!() line!()
) )
@ -85,9 +85,9 @@ pub(super) fn parse_events<'a>(
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 other values than 0 or 1, 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 propagate up other errors.
match binary_str_to_vec_u8(binary_value) { match binary_str_to_vec_u8(binary_value) {
Ok(result) => { Ok(result) => {
value_u8 = result; value_u8 = result;
@ -95,10 +95,15 @@ pub(super) fn parse_events<'a>(
Err( Err(
BinaryParserErrTypes::XValue BinaryParserErrTypes::XValue
| BinaryParserErrTypes::ZValue | BinaryParserErrTypes::ZValue
| BinaryParserErrTypes::UValue, | BinaryParserErrTypes::UValue
| BinaryParserErrTypes::WValue
| BinaryParserErrTypes::HValue
| BinaryParserErrTypes::DashValue
| BinaryParserErrTypes::LValue,
) => { ) => {
store_as_string = true; store_as_string = true;
value_string = binary_value.to_string(); // Turn to lower case for consistency
value_string = binary_value.to_ascii_lowercase();
} }
Err(e) => { Err(e) => {
let (f, l) = (file!(), line!()); let (f, l) = (file!(), line!());
@ -125,7 +130,7 @@ pub(super) fn parse_events<'a>(
match signal { match signal {
SignalEnum::Data { SignalEnum::Data {
name, name,
sig_type, signal_type,
ref mut signal_error, ref mut signal_error,
num_bits, num_bits,
num_bytes, num_bytes,
@ -134,6 +139,7 @@ pub(super) fn parse_events<'a>(
lsb_indxs_of_num_tmstmp_vals_on_tmln, lsb_indxs_of_num_tmstmp_vals_on_tmln,
byte_len_of_num_tmstmp_vals_on_tmln, byte_len_of_num_tmstmp_vals_on_tmln,
lsb_indxs_of_string_tmstmp_vals_on_tmln, lsb_indxs_of_string_tmstmp_vals_on_tmln,
byte_len_of_string_tmstmp_vals_on_tmln,
.. ..
} => { } => {
// we've already identified in a prior loop iteration that the signal has // we've already identified in a prior loop iteration that the signal has
@ -152,7 +158,7 @@ pub(super) fn parse_events<'a>(
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 `{num_bits}` not \ of signal_type {signal_type:?} is expected to be `{num_bits}` not \
`{observed_num_bits}`. \ `{observed_num_bits}`. \
This error occurred while parsing the vcd file at \ This error occurred while parsing the vcd file at \
{cursor:?}"); {cursor:?}");
@ -165,7 +171,7 @@ pub(super) fn parse_events<'a>(
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 {signal_type:?}. \
This error occurred while parsing the vcd file at \ This error occurred while parsing the vcd file at \
{cursor:?}" {cursor:?}"
); );
@ -176,6 +182,7 @@ pub(super) fn parse_events<'a>(
if store_as_string { if store_as_string {
lsb_indxs_of_string_tmstmp_vals_on_tmln lsb_indxs_of_string_tmstmp_vals_on_tmln
.push(LsbIdxOfTmstmpValOnTmln(curr_tmstmp_lsb_idx)); .push(LsbIdxOfTmstmpValOnTmln(curr_tmstmp_lsb_idx));
byte_len_of_string_tmstmp_vals_on_tmln.push(curr_tmstmp_len_u8);
string_vals.push(value_string); string_vals.push(value_string);
Ok(()) Ok(())
} else { } else {
@ -193,11 +200,11 @@ pub(super) fn parse_events<'a>(
format!("Error near {}:{}. num_bytes empty.", file!(), line!()) format!("Error near {}:{}. num_bytes empty.", file!(), line!())
})?; })?;
let mut curr_num_bytes = let mut curr_num_bytes =
u8::try_from(value_u8.len()).map_err(|_| { u16::try_from(value_u8.len()).map_err(|_| {
format!( format!(
"Error near {}:{}. \ "Error near {}:{}. \
Found signal {name} with with value change of greater \ Found signal {name} with with value change of greater \
than 2^16 - 1 bits on {cursor:?}.", than 2^32 - 1 bits on {cursor:?}.",
file!(), file!(),
line!() line!()
) )
@ -238,7 +245,7 @@ pub(super) fn parse_events<'a>(
match signal { match signal {
SignalEnum::Data { SignalEnum::Data {
name, name,
sig_type, signal_type,
ref mut signal_error, ref mut signal_error,
num_bits, num_bits,
num_bytes, num_bytes,
@ -261,7 +268,7 @@ pub(super) fn parse_events<'a>(
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 signal_type {signal_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:?}"
@ -275,7 +282,7 @@ pub(super) fn parse_events<'a>(
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 {signal_type:?}. \
This error occurred while parsing the vcd file at \ This error occurred while parsing the vcd file at \
{cursor:?}" {cursor:?}"
); );
@ -329,7 +336,7 @@ pub(super) fn parse_events<'a>(
match signal { match signal {
SignalEnum::Data { SignalEnum::Data {
name, name,
sig_type, signal_type,
ref mut signal_error, ref mut signal_error,
num_bits, num_bits,
num_bytes, num_bytes,
@ -352,7 +359,7 @@ pub(super) fn parse_events<'a>(
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 signal_type {signal_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:?}"
@ -366,7 +373,7 @@ pub(super) fn parse_events<'a>(
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 {signal_type:?}. \
This error occurred while parsing the vcd file at \ This error occurred while parsing the vcd file at \
{cursor:?}" {cursor:?}"
); );
@ -404,9 +411,10 @@ pub(super) fn parse_events<'a>(
}?; }?;
} }
// // other one bit cases // other one bit cases
"x" | "X" | "z" | "Z" | "u" | "U" => { "x" | "X" | "z" | "Z" | "u" | "U" | "h" | "H" | "l" | "L" | "w" | "W" | "-" => {
let val = word.to_string(); // Select value and turn to lowercase for consistency
let val = word[0..1].to_ascii_lowercase();
// lokup signal idx // lokup signal idx
let hash = &word[1..]; let hash = &word[1..];
let signal_idx = signal_map.get(hash).ok_or(()).map_err(|_| { let signal_idx = signal_map.get(hash).ok_or(()).map_err(|_| {
@ -422,7 +430,7 @@ pub(super) fn parse_events<'a>(
match signal { match signal {
SignalEnum::Data { SignalEnum::Data {
name, name,
sig_type, signal_type,
ref mut signal_error, ref mut signal_error,
num_bits, num_bits,
string_vals, string_vals,
@ -444,7 +452,7 @@ pub(super) fn parse_events<'a>(
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 signal_type {signal_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:?}"
@ -458,7 +466,7 @@ pub(super) fn parse_events<'a>(
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 {signal_type:?}. \
This error occurred while parsing the vcd file at \ This error occurred while parsing the vcd file at \
{cursor:?}" {cursor:?}"
); );
@ -484,7 +492,8 @@ pub(super) fn parse_events<'a>(
} }
}?; }?;
} }
"s" => { // Store real values as a string as well and let the user parse it to an f64
"s" | " S" | "r" | "R" => {
let val = word[1..].to_string(); let val = word[1..].to_string();
let (hash, cursor) = next_word!(word_reader)?; let (hash, cursor) = next_word!(word_reader)?;
// lokup signal idx // lokup signal idx

View file

@ -2,14 +2,14 @@
// This program is distributed under both the GPLV3 license // This program is distributed under both the GPLV3 license
// and the YEHOWSHUA license, both of which can be found at // and the YEHOWSHUA license, both of which can be found at
// the root of the folder containing the sources for this program. // the root of the folder containing the sources for this program.
use chrono::prelude::{DateTime, Utc, TimeZone}; use chrono::prelude::{DateTime, Utc};
use itertools::Itertools; use itertools::Itertools;
use super::super::reader::{Cursor, WordReader, next_word}; use super::super::reader::{next_word, Cursor, WordReader};
use super::super::types::{Timescale, Version, Metadata}; use super::super::types::{Metadata, Timescale, Version};
use super::combinator_atoms::{take_until, take_while, digit, tag}; use super::combinator_atoms::{digit, tag, take_until, take_while};
use super::types::{ParseResult}; use super::types::ParseResult;
pub(super) fn parse_date( pub(super) fn parse_date(
word_and_ctx1: (&str, &Cursor), word_and_ctx1: (&str, &Cursor),
@ -133,9 +133,9 @@ pub(super) fn parse_date(
// unfortunately, the minutes, seconds, and hour could occur in an // unfortunately, the minutes, seconds, and hour could occur in an
// unexpected order // unexpected order
let full_date = format!("{day} {month} {date} {hh}:{mm}:{ss} {year}"); let full_date = format!("{day} {month} {date} {hh}:{mm}:{ss} {year}");
let full_date = Utc.datetime_from_str(full_date.as_str(), "%a %b %e %T %Y"); let full_date = DateTime::parse_from_str(full_date.as_str(), "%a %b %e %T %Y");
if full_date.is_ok() { if full_date.is_ok() {
return Ok(full_date.unwrap()); return Ok(full_date.unwrap().into());
} }
Err(format!( Err(format!(
@ -145,7 +145,9 @@ pub(super) fn parse_date(
)) ))
} }
pub(super) fn parse_version(word_reader: &mut WordReader) -> Result<Version, String> { pub(super) fn parse_version<R: std::io::Read>(
word_reader: &mut WordReader<R>,
) -> Result<Version, String> {
let mut version = String::new(); let mut version = String::new();
loop { loop {
@ -157,13 +159,13 @@ pub(super) fn parse_version(word_reader: &mut WordReader) -> Result<Version, Str
return Ok(Version(version)); return Ok(Version(version));
} else { } else {
version.push_str(word); version.push_str(word);
version.push_str(" "); version.push(' ');
} }
} }
} }
pub(super) fn parse_timescale( pub(super) fn parse_timescale<R: std::io::Read>(
word_reader: &mut WordReader, word_reader: &mut WordReader<R>,
) -> Result<(Option<u32>, Timescale), String> { ) -> Result<(Option<u32>, Timescale), String> {
// we might see `1ps $end` or `1 ps $end` // we might see `1ps $end` or `1 ps $end`
// first get timescale // first get timescale
@ -177,7 +179,7 @@ pub(super) fn parse_timescale(
.map_err(|e| format!("Error near {}:{}. {e}", file!(), line!()))?; .map_err(|e| format!("Error near {}:{}. {e}", file!(), line!()))?;
let timescale = { let timescale = {
if residual == "" { if residual.is_empty() {
let (word, _) = next_word!(word_reader)?; let (word, _) = next_word!(word_reader)?;
let unit = match word { let unit = match word {
"fs" => Ok(Timescale::Fs), "fs" => Ok(Timescale::Fs),
@ -217,10 +219,12 @@ pub(super) fn parse_timescale(
let (word, _) = next_word!(word_reader)?; let (word, _) = next_word!(word_reader)?;
tag(word, "$end").assert_match()?; tag(word, "$end").assert_match()?;
return Ok(timescale); Ok(timescale)
} }
pub(super) fn parse_metadata(word_reader: &mut WordReader) -> Result<Metadata, String> { pub(super) fn parse_metadata<R: std::io::Read>(
word_reader: &mut WordReader<R>,
) -> Result<Metadata, String> {
let mut metadata = Metadata { let mut metadata = Metadata {
date: None, date: None,
version: None, version: None,
@ -327,5 +331,5 @@ pub(super) fn parse_metadata(word_reader: &mut WordReader) -> Result<Metadata, S
_ => {} _ => {}
} }
} }
return Ok(metadata); Ok(metadata)
} }

View file

@ -5,45 +5,67 @@
/// 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 std::collections::HashMap; use std::collections::HashMap;
use super::super::reader::{WordReader, next_word, curr_word}; use super::super::reader::{curr_word, next_word, WordReader};
use super::super::types::{VCD, Scope, ScopeIdx, SignalIdx}; use super::super::signal::{SignalEnum, SignalType};
use super::super::signal::{SigType, SignalEnum}; use super::super::types::{Scope, ScopeIdx, SignalIdx, VCD};
use super::combinator_atoms::{tag, ident}; use super::combinator_atoms::{ident, tag};
use super::types::{ParseResult}; use super::types::ParseResult;
pub(super) fn parse_var<'a>( pub(super) fn parse_var<R: std::io::Read>(
word_reader: &mut WordReader, word_reader: &mut WordReader<R>,
parent_scope_idx: ScopeIdx, parent_scope_idx: ScopeIdx,
vcd: &'a mut VCD, vcd: &mut VCD,
signal_map: &mut HashMap<String, SignalIdx>, signal_map: &mut HashMap<String, SignalIdx>,
path: &Vec<String>,
) -> Result<(), String> { ) -> Result<(), String> {
let (word, cursor) = next_word!(word_reader)?; let (word, cursor) = next_word!(word_reader)?;
let expected_types = [ let expected_types = [
"event",
"integer", "integer",
"parameter", "parameter",
"real", "real",
"realtime",
"reg", "reg",
"string", "string",
"wire", "supply0",
"tri1", "supply1",
"time", "time",
"tri",
"triand",
"trior",
"trireg",
"tri0",
"tri1",
"wand",
"wire",
"wor",
]; ];
// $var parameter 3 a IDLE $end // $var parameter 3 a IDLE $end
// ^^^^^^^^^ - var_type // ^^^^^^^^^ - var_type
let var_type = match word { let var_type = match word {
"integer" => Ok(SigType::Integer), "event" => Ok(SignalType::Event),
"parameter" => Ok(SigType::Parameter), "integer" => Ok(SignalType::Integer),
"real" => Ok(SigType::Real), "parameter" => Ok(SignalType::Parameter),
"reg" => Ok(SigType::Reg), "real" => Ok(SignalType::Real),
"string" => Ok(SigType::Str), "realtime" => Ok(SignalType::RealTime),
"wire" => Ok(SigType::Wire), "reg" => Ok(SignalType::Reg),
"tri1" => Ok(SigType::Tri1), "string" => Ok(SignalType::Str),
"time" => Ok(SigType::Time), "supply0" => Ok(SignalType::Supply0),
"supply1" => Ok(SignalType::Supply1),
"tri" => Ok(SignalType::Tri),
"triand" => Ok(SignalType::TriAnd),
"trior" => Ok(SignalType::TriOr),
"trireg" => Ok(SignalType::TriReg),
"tri0" => Ok(SignalType::Tri0),
"tri1" => Ok(SignalType::Tri1),
"time" => Ok(SignalType::Time),
"wand" => Ok(SignalType::WAnd),
"wire" => Ok(SignalType::Wire),
"wor" => Ok(SignalType::WOr),
_ => { _ => {
let err = format!( let err = format!(
"Error near {}:{} \ "Error near {}:{} \
@ -63,25 +85,36 @@ pub(super) fn parse_var<'a>(
// $var parameter 3 a IDLE $end // $var parameter 3 a IDLE $end
// ^ - num_bits // ^ - num_bits
let num_bits = match var_type { let num_bits = match var_type {
SigType::Integer SignalType::Event
| SigType::Parameter | SignalType::Integer
| SigType::Real | SignalType::Parameter
| SigType::Reg | SignalType::Reg
| SigType::Wire | SignalType::Supply0
| SigType::Tri1 | SignalType::Supply1
| SigType::Time => { | SignalType::Tri
let num_bits = word.parse::<usize>().expect(parse_err.as_str()); | SignalType::TriAnd
let num_bits = u16::try_from(num_bits).map_err(|_| { | SignalType::TriOr
| SignalType::TriReg
| SignalType::Tri0
| SignalType::Tri1
| SignalType::Time
| SignalType::WAnd
| SignalType::Wire
| SignalType::WOr => {
let num_bits = word
.parse::<usize>()
.unwrap_or_else(|_| panic!("{}", parse_err));
let num_bits = u32::try_from(num_bits).map_err(|_| {
format!( format!(
"Error near {}:{} while parsing vcd file at {cursor:?}. \ "Error near {}:{} while parsing vcd file at {cursor:?}. \
This signal has {num_bits} > 2^16 - 1 bits.", This signal has {num_bits} > 2^32 - 1 bits.",
file!(), file!(),
line!() line!()
) )
})?; })?;
Some(num_bits) Some(num_bits)
} }
// for strings, we don't really care what the number of bits is // for strings, reals, and realtimes we don't really care what the number of bits is
_ => None, _ => None,
}; };
@ -93,10 +126,12 @@ pub(super) fn parse_var<'a>(
// $var parameter 3 a IDLE $end // $var parameter 3 a IDLE $end
// ^^^^ - full_signal_name(can extend until $end) // ^^^^ - full_signal_name(can extend until $end)
let mut full_signal_name = Vec::<String>::new(); let mut full_signal_name = Vec::<String>::new();
let mut size = None;
loop { loop {
let (word, _) = next_word!(word_reader)?; let (word, _) = next_word!(word_reader)?;
match word { match word {
"$end" => break, "$end" => break,
other if other.starts_with('[') => size = Some(other.to_string()),
_ => full_signal_name.push(word.to_string()), _ => full_signal_name.push(word.to_string()),
} }
} }
@ -116,7 +151,12 @@ pub(super) fn parse_var<'a>(
Some(ref_signal_idx) => { Some(ref_signal_idx) => {
let signal_idx = SignalIdx(vcd.all_signals.len()); let signal_idx = SignalIdx(vcd.all_signals.len());
let signal = SignalEnum::Alias { let signal = SignalEnum::Alias {
name: full_signal_name, name: full_signal_name.clone(),
path: path
.iter()
.cloned()
.chain([full_signal_name])
.collect::<Vec<String>>(),
signal_alias: *ref_signal_idx, signal_alias: *ref_signal_idx,
}; };
(signal, signal_idx) (signal, signal_idx)
@ -125,11 +165,17 @@ pub(super) fn parse_var<'a>(
let signal_idx = SignalIdx(vcd.all_signals.len()); let signal_idx = SignalIdx(vcd.all_signals.len());
signal_map.insert(signal_alias.to_string(), signal_idx); signal_map.insert(signal_alias.to_string(), signal_idx);
let signal = SignalEnum::Data { let signal = SignalEnum::Data {
name: full_signal_name, name: full_signal_name.clone(),
sig_type: var_type, path: path
.iter()
.cloned()
.chain([full_signal_name])
.collect::<Vec<String>>(),
signal_type: var_type,
index: size,
signal_error: None, signal_error: None,
num_bits: num_bits, num_bits,
num_bytes: num_bytes, num_bytes,
self_idx: signal_idx, self_idx: signal_idx,
nums_encoded_as_fixed_width_le_u8: vec![], nums_encoded_as_fixed_width_le_u8: vec![],
string_vals: vec![], string_vals: vec![],
@ -137,7 +183,6 @@ pub(super) fn parse_var<'a>(
byte_len_of_num_tmstmp_vals_on_tmln: vec![], byte_len_of_num_tmstmp_vals_on_tmln: vec![],
byte_len_of_string_tmstmp_vals_on_tmln: vec![], byte_len_of_string_tmstmp_vals_on_tmln: vec![],
lsb_indxs_of_string_tmstmp_vals_on_tmln: vec![], lsb_indxs_of_string_tmstmp_vals_on_tmln: vec![],
scope_parent: parent_scope_idx,
}; };
(signal, signal_idx) (signal, signal_idx)
} }
@ -153,9 +198,9 @@ pub(super) fn parse_var<'a>(
/// Sometimes, variables can be listed outside of scopes. /// Sometimes, variables can be listed outside of scopes.
/// We call these orphaned vars. /// We call these orphaned vars.
fn parse_orphaned_vars<'a>( fn parse_orphaned_vars<R: std::io::Read>(
word_reader: &mut WordReader, word_reader: &mut WordReader<R>,
vcd: &'a mut VCD, vcd: &mut VCD,
signal_map: &mut HashMap<String, SignalIdx>, signal_map: &mut HashMap<String, SignalIdx>,
) -> Result<(), String> { ) -> Result<(), String> {
// create scope for unscoped signals if such a scope does not // create scope for unscoped signals if such a scope does not
@ -181,7 +226,6 @@ fn parse_orphaned_vars<'a>(
if !scope_already_exists { if !scope_already_exists {
vcd.all_scopes.push(Scope { vcd.all_scopes.push(Scope {
name: scope_name.to_string(), name: scope_name.to_string(),
parent_idx: None,
self_idx: scope_idx, self_idx: scope_idx,
child_signals: vec![], child_signals: vec![],
child_scopes: vec![], child_scopes: vec![],
@ -191,14 +235,14 @@ fn parse_orphaned_vars<'a>(
// we can go ahead and parse the current var as we've already encountered // we can go ahead and parse the current var as we've already encountered
// "$var" before now. // "$var" before now.
parse_var(word_reader, scope_idx, vcd, signal_map)?; parse_var(word_reader, scope_idx, vcd, signal_map, &vec![])?;
loop { loop {
let (word, cursor) = next_word!(word_reader)?; let (word, cursor) = next_word!(word_reader)?;
match word { match word {
"$var" => { "$var" => {
parse_var(word_reader, scope_idx, vcd, signal_map)?; parse_var(word_reader, scope_idx, vcd, signal_map, &vec![])?;
} }
"$scope" => break, "$scope" => break,
_ => { _ => {
@ -217,17 +261,18 @@ fn parse_orphaned_vars<'a>(
Ok(()) Ok(())
} }
fn parse_scopes_inner<'a>( fn parse_scopes_inner<R: std::io::Read>(
word_reader: &mut WordReader, word_reader: &mut WordReader<R>,
parent_scope_idx: Option<ScopeIdx>, parent_scope_idx: Option<ScopeIdx>,
vcd: &'a mut VCD, vcd: &mut VCD,
signal_map: &mut HashMap<String, SignalIdx>, signal_map: &mut HashMap<String, SignalIdx>,
path: &Vec<String>,
) -> Result<(), String> { ) -> Result<(), String> {
// $scope module reg_mag_i $end // $scope module reg_mag_i $end
// ^^^^^^ - module keyword // ^^^^^^ - module keyword
let (keyword, cursor) = next_word!(word_reader)?; let (keyword, cursor) = next_word!(word_reader)?;
let expected = ["module", "begin", "task", "function"]; let expected = ["module", "begin", "task", "function", "fork"];
if expected.contains(&keyword) { if expected.contains(&keyword) {
Ok(()) Ok(())
} else { } else {
@ -244,79 +289,154 @@ fn parse_scopes_inner<'a>(
// $scope module reg_mag_i $end // $scope module reg_mag_i $end
// ^^^^^^^^^ - scope name // ^^^^^^^^^ - scope name
let (scope_name, _) = next_word!(word_reader)?; let (scope_name, _) = next_word!(word_reader)?;
// In some cases there are VCD files which have scopes without names.
// since these occur in the wild, we'll tolerate them even if it is unclear
// if it is supported or not by the spec.
if scope_name != "$end" {
let mut path = path.clone();
path.push(scope_name.to_string());
let curr_scope_idx = ScopeIdx(vcd.all_scopes.len()); let curr_scope_idx = ScopeIdx(vcd.all_scopes.len());
// register this scope as a child of the current parent scope // register this scope as a child of the current parent scope
// if there is a parent scope, or else we register this scope as // if there is a parent scope, or else we register this scope as
// root scope // root scope
match parent_scope_idx { match parent_scope_idx {
Some(ScopeIdx(parent_scope_idx)) => { Some(ScopeIdx(parent_scope_idx)) => {
let parent_scope = vcd.all_scopes.get_mut(parent_scope_idx).unwrap(); let parent_scope = vcd.all_scopes.get_mut(parent_scope_idx).unwrap();
parent_scope.child_scopes.push(curr_scope_idx); parent_scope.child_scopes.push(curr_scope_idx);
}
None => vcd.root_scopes.push(curr_scope_idx),
} }
None => vcd.root_scopes.push(curr_scope_idx),
}
// add this scope to list of existing scopes // add this scope to list of existing scopes
vcd.all_scopes.push(Scope { vcd.all_scopes.push(Scope {
name: scope_name.to_string(), name: scope_name.to_string(),
parent_idx: parent_scope_idx, self_idx: curr_scope_idx,
self_idx: curr_scope_idx, child_signals: vec![],
child_signals: vec![], child_scopes: vec![],
child_scopes: vec![], });
});
// $scope module reg_mag_i $end // $scope module reg_mag_i $end
// ^^^^ - end keyword // ^^^^ - end keyword
ident(word_reader, "$end")?; ident(word_reader, "$end")?;
loop { loop {
let (word, cursor) = next_word!(word_reader)?; let (word, cursor) = next_word!(word_reader)?;
let ParseResult { matched, residual } = tag(word, "$"); let ParseResult { matched, residual } = tag(word, "$");
match matched { match matched {
// we hope that this word starts with a `$` // we hope that this word starts with a `$`
"$" => { "$" => {
match residual { match residual {
"scope" => { "scope" => {
// recursive - parse inside of current scope tree // recursive - parse inside of current scope tree
parse_scopes_inner(word_reader, Some(curr_scope_idx), vcd, signal_map)?; parse_scopes_inner(
} word_reader,
"var" => { Some(curr_scope_idx),
parse_var(word_reader, curr_scope_idx, vcd, signal_map)?; vcd,
} signal_map,
"upscope" => { &path,
ident(word_reader, "$end")?; )?;
break; }
} "var" => {
// we ignore comments parse_var(word_reader, curr_scope_idx, vcd, signal_map, &path)?;
"comment" => loop { }
if ident(word_reader, "$end").is_ok() { "upscope" => {
ident(word_reader, "$end")?;
break; break;
} }
}, // we ignore comments
_ => { "comment" => loop {
let err = format!( if ident(word_reader, "$end").is_ok() {
"Error near {}:{}. \ break;
found keyword `{residual}` but expected \ }
`$scope`, `$var`, `$comment`, or `$upscope` \ },
on {cursor:?}", _ => {
file!(), let err = format!(
line!() "Error near {}:{}. \
); found keyword `{residual}` but expected \
return Err(err); `$scope`, `$var`, `$comment`, or `$upscope` \
on {cursor:?}",
file!(),
line!()
);
return Err(err);
}
} }
} }
_ => {
let err = format!(
"Error near {}:{}. \
found keyword `{matched}` but \
expected `$` on {cursor:?}",
file!(),
line!()
);
return Err(err);
}
} }
_ => { }
let err = format!( } else {
"Error near {}:{}. \ // We'll be conservative and only allow new scopes in this case, and make the nameless
found keyword `{matched}` but \ // scope completely transparent. I.e.
expected `$` on {cursor:?}", // $scope module a $end
file!(), // $scope module $end
line!() // $scope module b $end
); // ...
return Err(err); // $upscope
// $upscope
// $upscope
// will create `a.b`
loop {
let (word, cursor) = next_word!(word_reader)?;
let ParseResult { matched, residual } = tag(word, "$");
match matched {
// we hope that this word starts with a `$`
"$" => {
match residual {
"scope" => {
// recursive - parse inside of current scope tree
parse_scopes_inner(
word_reader,
parent_scope_idx,
vcd,
signal_map,
&path,
)?;
}
"upscope" => {
ident(word_reader, "$end")?;
break;
}
// we ignore comments
"comment" => loop {
if ident(word_reader, "$end").is_ok() {
break;
}
},
_ => {
let err = format!(
"Error near {}:{}. \
found keyword `{residual}` in annonyoums scope but expected \
`$scope`, `$comment`, or `$upscope` \
on {cursor:?}",
file!(),
line!()
);
return Err(err);
}
}
}
_ => {
let err = format!(
"Error near {}:{}. \
found keyword `{matched}` but \
expected `$` on {cursor:?}",
file!(),
line!()
);
return Err(err);
}
} }
} }
} }
@ -324,9 +444,9 @@ fn parse_scopes_inner<'a>(
Ok(()) Ok(())
} }
pub(super) fn parse_scopes<'a>( pub(super) fn parse_scopes<R: std::io::Read>(
word_reader: &mut WordReader, word_reader: &mut WordReader<R>,
vcd: &'a mut VCD, vcd: &mut VCD,
signal_map: &mut HashMap<String, SignalIdx>, signal_map: &mut HashMap<String, SignalIdx>,
) -> Result<(), String> { ) -> Result<(), String> {
// get the current word // get the current word
@ -356,7 +476,7 @@ pub(super) fn parse_scopes<'a>(
} }
// now for the interesting part // now for the interesting part
parse_scopes_inner(word_reader, None, vcd, signal_map)?; parse_scopes_inner(word_reader, None, vcd, signal_map, &vec![])?;
// let err = format!("reached end of file without parser leaving {}", function_name!()); // let err = format!("reached end of file without parser leaving {}", function_name!());
let expected_keywords = ["$scope", "$enddefinitions"]; let expected_keywords = ["$scope", "$enddefinitions"];
@ -370,13 +490,13 @@ pub(super) fn parse_scopes<'a>(
match word { match word {
"$scope" => { "$scope" => {
parse_scopes_inner(word_reader, None, vcd, signal_map)?; parse_scopes_inner(word_reader, None, vcd, signal_map, &vec![])?;
} }
"$enddefinitions" => { "$enddefinitions" => {
ident(word_reader, "$end")?; ident(word_reader, "$end")?;
break; break;
} }
"comment" => { "$comment" => {
// although we don't store comments, we still need to advance the // although we don't store comments, we still need to advance the
// word_reader cursor to the end of the comment // word_reader cursor to the end of the comment
loop { loop {

View file

@ -4,26 +4,24 @@
// the root of the folder containing the sources for this program. // the root of the folder containing the sources for this program.
#[derive(Debug)] #[derive(Debug)]
pub(super) struct ParseResult<'a> { pub(super) struct ParseResult<'a> {
pub(super) matched : &'a str, pub(super) matched: &'a str,
pub(super) residual : &'a str} pub(super) residual: &'a str,
}
impl<'a> ParseResult<'a> { impl<'a> ParseResult<'a> {
pub(super) fn assert_match(&self) -> Result<&str, String> {
pub(super) fn assert_match(& self) -> Result<&str, String> { if self.matched.is_empty() {
if self.matched == "" { Err("no match".to_string())
return Err("no match".to_string()) } else {
} Ok(self.matched)
else {
return Ok(self.matched)
} }
} }
pub(super) fn assert_residual(& self) -> Result<&str, String> { pub(super) fn assert_residual(&self) -> Result<&str, String> {
if self.residual == "" { if self.residual.is_empty() {
return Err("no residual".to_string()) Err("no residual".to_string())
} } else {
else { Ok(self.residual)
return Ok(self.residual)
} }
} }
} }

View file

@ -3,7 +3,6 @@
// and the YEHOWSHUA license, both of which can be found at // and the YEHOWSHUA license, both of which can be found at
// the root of the folder containing the sources for this program. // the root of the folder containing the sources for this program.
use std::collections::VecDeque; use std::collections::VecDeque;
use std::fs::File;
use std::io; use std::io;
use std::io::BufRead; use std::io::BufRead;
use std::slice; use std::slice;
@ -16,8 +15,8 @@ pub(super) struct Word(pub(super) usize);
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(super) struct Cursor(pub(super) Line, pub(super) Word); pub(super) struct Cursor(pub(super) Line, pub(super) Word);
pub(super) struct WordReader { pub(super) struct WordReader<R: io::Read> {
reader: io::BufReader<File>, reader: io::BufReader<R>,
eof: bool, eof: bool,
buffers: Vec<String>, buffers: Vec<String>,
curr_line: usize, curr_line: usize,
@ -25,11 +24,11 @@ pub(super) struct WordReader {
curr_slice: Option<(*const u8, usize, Cursor)>, curr_slice: Option<(*const u8, usize, Cursor)>,
} }
impl WordReader { impl<R: std::io::Read> WordReader<R> {
pub(super) fn new(file: File) -> WordReader { pub(super) fn new(file: R) -> WordReader<R> {
let reader = io::BufReader::new(file); let reader = io::BufReader::new(file);
WordReader { WordReader {
reader: reader, reader,
eof: false, eof: false,
buffers: vec![], buffers: vec![],
curr_line: 0, curr_line: 0,

View file

@ -2,33 +2,49 @@
// This program is distributed under both the GPLV3 license // This program is distributed under both the GPLV3 license
// and the YEHOWSHUA license, both of which can be found at // and the YEHOWSHUA license, both of which can be found at
// the root of the folder containing the sources for this program. // the root of the folder containing the sources for this program.
use super::types::{ScopeIdx, SignalIdx};
use super::types; use super::types;
use num::{BigUint}; use super::types::SignalIdx;
use num::BigUint;
// Index to the least significant byte of a timestamp // Index to the least significant byte of a timestamp
// value on the timeline // value on the timeline
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct LsbIdxOfTmstmpValOnTmln(pub(super) u32); pub struct LsbIdxOfTmstmpValOnTmln(pub(super) u32);
#[derive(Debug)] #[derive(Debug, Eq, PartialEq, Clone)]
pub enum SigType { pub enum SignalType {
Event,
Integer, Integer,
Parameter, Parameter,
Real, Real,
RealTime,
Reg, Reg,
Str, Str,
Wire, Supply0,
Tri1, Supply1,
Time, Time,
Tri,
TriAnd,
TriOr,
TriReg,
Tri0,
Tri1,
WAnd,
Wire,
WOr,
} }
#[derive(Debug)] #[derive(Debug, PartialEq)]
pub(super) enum TimelineQueryResults { pub enum SignalValue {
BigUint(BigUint), BigUint(BigUint),
String(String), String(String),
} }
pub struct QueryResult<T> {
pub current: Option<(TimeStamp, T)>,
pub next: Option<TimeStamp>,
}
pub struct Signal<'a>(pub(super) &'a SignalEnum); pub struct Signal<'a>(pub(super) &'a SignalEnum);
impl<'a> Signal<'a> { impl<'a> Signal<'a> {
@ -36,21 +52,127 @@ impl<'a> Signal<'a> {
let Signal(signal_enum) = &self; let Signal(signal_enum) = &self;
signal_enum.name() signal_enum.name()
} }
pub fn name_with_index(&self) -> String {
let Signal(signal_enum) = &self;
signal_enum.name_with_index()
}
pub fn index(&self) -> Option<String> {
let Signal(signal_enum) = &self;
signal_enum.index()
}
pub fn path(&self) -> &[String] {
match self.0 {
SignalEnum::Data { path, .. } => path,
SignalEnum::Alias { path, .. } => path,
}
}
pub fn signal_type(&self) -> Option<&SignalType> {
let Signal(signal_enum) = &self;
signal_enum.signal_type()
}
pub fn real_idx(&self) -> SignalIdx {
match self.0 {
SignalEnum::Data { self_idx, .. } => *self_idx,
SignalEnum::Alias { signal_alias, .. } => *signal_alias,
}
}
pub fn num_bits(&self) -> Option<u32> {
let Signal(signal_enum) = &self;
signal_enum.bits_required()
}
// NOTE: (zoq) I am removing thse because they aren't used in Surfer so I can't test them
// properly
/*
pub fn query_string_val_on_tmln( pub fn query_string_val_on_tmln(
&self, &self,
desired_time: &BigUint, desired_time: &BigUint,
vcd: &types::VCD, vcd: &types::VCD,
) -> Result<String, SignalErrors> { ) -> Result<String, SignalErrors> {
let Signal(signal_enum) = &self; let Signal(signal_enum) = &self;
signal_enum.query_string_val_on_tmln(desired_time, &vcd.tmstmps_encoded_as_u8s, &vcd.all_signals) signal_enum
.query_string_val_on_tmln(desired_time, &vcd.tmstmps_encoded_as_u8s, &vcd.all_signals)
.map(|QueryResult{current, next: _}| current.map(|c| c.1))
} }
pub fn query_num_val_on_tmln( pub fn query_num_val_on_tmln(
&self, &self,
desired_time: &BigUint, desired_time: &BigUint,
vcd: &types::VCD, vcd: &types::VCD,
) -> Result<BigUint, SignalErrors> { ) -> Result<Option<BigUint>, SignalErrors> {
let Signal(signal_enum) = &self; let Signal(signal_enum) = &self;
signal_enum.query_num_val_on_tmln(desired_time, &vcd.tmstmps_encoded_as_u8s, &vcd.all_signals) signal_enum
.query_num_val_on_tmln(desired_time, &vcd.tmstmps_encoded_as_u8s, &vcd.all_signals)
.map(|QueryResult{current, next: _}| current.map(|c| c.1))
}
*/
pub fn query_val_on_tmln(
&self,
desired_time: &BigUint,
vcd: &types::VCD,
) -> Result<QueryResult<SignalValue>, SignalErrors> {
let Signal(signal_enum) = &self;
let num_query_out = signal_enum.query_num_val_on_tmln(
desired_time,
&vcd.tmstmps_encoded_as_u8s,
&vcd.all_signals,
);
let str_query_out = signal_enum.query_string_val_on_tmln(
desired_time,
&vcd.tmstmps_encoded_as_u8s,
&vcd.all_signals,
);
// Both num and str will return the newest value that is closest to
// the desired time. If both have valid values, select the most recent
// one
match (num_query_out, str_query_out) {
(Ok(num_result), Ok(str_result)) => {
let next = match (num_result.next, str_result.next) {
(Some(n), Some(s)) => Some(n.min(s)),
(Some(n), None) => Some(n),
(None, Some(s)) => Some(s),
(None, None) => None,
};
match (num_result.current, str_result.current) {
(Some((num_time, num_value)), Some((str_time, str_value))) => {
if num_time > str_time {
Ok(QueryResult {
current: Some((num_time, SignalValue::BigUint(num_value))),
next,
})
} else {
Ok(QueryResult {
current: Some((str_time, SignalValue::String(str_value))),
next,
})
}
}
(Some((num_time, num_val)), None) => Ok(QueryResult {
current: Some((num_time, SignalValue::BigUint(num_val))),
next,
}),
(None, Some((str_time, str_value))) => Ok(QueryResult {
current: Some((str_time, SignalValue::String(str_value))),
next,
}),
(None, None) => Ok(QueryResult {
current: None,
next,
}),
}
}
(_e, Err(e)) => Err(e),
(Err(e), _e) => Err(e),
}
} }
} }
@ -58,14 +180,18 @@ impl<'a> Signal<'a> {
pub(super) enum SignalEnum { pub(super) enum SignalEnum {
Data { Data {
name: String, name: String,
sig_type: SigType, path: Vec<String>,
signal_type: SignalType,
/// The optional [start:end] part of the signal name that is sometimes
/// added to signals
index: Option<String>,
/// 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<u16>, num_bits: Option<u32>,
num_bytes: Option<u8>, num_bytes: Option<u16>,
/// TODO : may be able to remove self_idx /// TODO : may be able to remove self_idx
self_idx: SignalIdx, self_idx: SignalIdx,
/// A signal may take on a new value and hold that value /// A signal may take on a new value and hold that value
@ -93,20 +219,16 @@ pub(super) enum SignalEnum {
byte_len_of_num_tmstmp_vals_on_tmln: Vec<u8>, byte_len_of_num_tmstmp_vals_on_tmln: Vec<u8>,
byte_len_of_string_tmstmp_vals_on_tmln: Vec<u8>, byte_len_of_string_tmstmp_vals_on_tmln: Vec<u8>,
lsb_indxs_of_string_tmstmp_vals_on_tmln: Vec<LsbIdxOfTmstmpValOnTmln>, lsb_indxs_of_string_tmstmp_vals_on_tmln: Vec<LsbIdxOfTmstmpValOnTmln>,
scope_parent: ScopeIdx,
}, },
Alias { Alias {
name: String, name: String,
path: Vec<String>,
signal_alias: SignalIdx, signal_alias: SignalIdx,
}, },
} }
#[derive(Debug)] #[derive(Debug)]
pub enum SignalErrors { pub enum SignalErrors {
PreTimeline {
desired_time: BigUint,
timeline_start_time: BigUint,
},
EmptyTimeline, EmptyTimeline,
TimelineNotMultiple, TimelineNotMultiple,
StrTmlnLenMismatch, StrTmlnLenMismatch,
@ -128,11 +250,41 @@ type SignalValNum = BigUint;
impl SignalEnum { impl SignalEnum {
pub fn name(&self) -> String { pub fn name(&self) -> String {
match self { match self {
SignalEnum::Data { name, ..} => name, SignalEnum::Data { name, .. } => name,
SignalEnum::Alias { name, .. } => name SignalEnum::Alias { name, .. } => name,
}.clone() }
.clone()
} }
pub fn signal_type(&self) -> Option<&SignalType> {
match self {
SignalEnum::Data { signal_type, .. } => Some(signal_type),
// TODO: Follow aliases?
SignalEnum::Alias { .. } => None,
}
.clone()
}
pub fn name_with_index(&self) -> String {
match self {
SignalEnum::Data {
name, index: None, ..
} => format!("{name}"),
SignalEnum::Data {
name,
index: Some(size),
..
} => format!("{name} {size}"),
SignalEnum::Alias { name, .. } => name.clone(),
}
}
pub fn index(&self) -> Option<String> {
match self {
SignalEnum::Data { index, .. } => index.clone(),
SignalEnum::Alias { .. } => None,
}
}
} }
// helper functions ultimately used by Signal's query functions later on // helper functions ultimately used by Signal's query functions later on
@ -140,12 +292,12 @@ impl SignalEnum {
/// Computes the bytes required to store a signal's numerical value /// Computes the bytes required to store a signal's numerical value
/// using the num_bits which another function would provide from /// using the num_bits which another function would provide from
/// the num_bits field of the Signal::Data variant. /// the num_bits field of the Signal::Data variant.
pub(super) fn bytes_required(num_bits: u16, name: &String) -> Result<u8, String> { pub(super) fn bytes_required(num_bits: u32, name: &String) -> Result<u16, String> {
let bytes_required = (num_bits / 8) + if (num_bits % 8) > 0 { 1 } else { 0 }; let bytes_required = (num_bits / 8) + if (num_bits % 8) > 0 { 1 } else { 0 };
let bytes_required = u8::try_from(bytes_required).map_err(|_| { let bytes_required = u16::try_from(bytes_required).map_err(|_| {
format!( format!(
"Error near {}:{}. Signal {name} of length num_bits requires \ "Error near {}:{}. Signal {name} of length num_bits requires \
{bytes_required} > 256 bytes.", {bytes_required} > 65536 bytes.",
file!(), file!(),
line!() line!()
) )
@ -187,6 +339,10 @@ impl SignalEnum {
lsb_indxs_of_string_tmstmp_vals_on_tmln[event_idx]; lsb_indxs_of_string_tmstmp_vals_on_tmln[event_idx];
let timestamp_idx = timestamp_idx as usize; let timestamp_idx = timestamp_idx as usize;
if byte_len_of_string_tmstmp_vals_on_tmln.is_empty() {
return Err(SignalErrors::EmptyTimeline);
}
// form timestamp // form timestamp
let byte_len = byte_len_of_string_tmstmp_vals_on_tmln[event_idx] as usize; let byte_len = byte_len_of_string_tmstmp_vals_on_tmln[event_idx] as usize;
let timestamp = &tmstmps_encoded_as_u8s[timestamp_idx..(timestamp_idx + byte_len)]; let timestamp = &tmstmps_encoded_as_u8s[timestamp_idx..(timestamp_idx + byte_len)];
@ -253,6 +409,14 @@ impl SignalEnum {
Ok((timestamp, signal_val)) Ok((timestamp, signal_val))
} }
fn bits_required(&self) -> Option<u32> {
match self {
SignalEnum::Data { num_bits, .. } => *num_bits,
// TODO: Follow aliases?
SignalEnum::Alias { .. } => None,
}
}
} }
// Val and string query functions. // Val and string query functions.
@ -265,7 +429,7 @@ impl SignalEnum {
desired_time: &BigUint, desired_time: &BigUint,
tmstmps_encoded_as_u8s: &Vec<u8>, tmstmps_encoded_as_u8s: &Vec<u8>,
all_signals: &Vec<SignalEnum>, all_signals: &Vec<SignalEnum>,
) -> Result<String, SignalErrors> { ) -> Result<QueryResult<String>, SignalErrors> {
let signal_idx = match self { let signal_idx = match self {
Self::Data { self_idx, .. } => { Self::Data { self_idx, .. } => {
let SignalIdx(idx) = self_idx; let SignalIdx(idx) = self_idx;
@ -274,6 +438,7 @@ impl SignalEnum {
Self::Alias { Self::Alias {
name: _, name: _,
signal_alias, signal_alias,
path: _,
} => { } => {
let SignalIdx(idx) = signal_alias; let SignalIdx(idx) = signal_alias;
*idx *idx
@ -286,24 +451,22 @@ impl SignalEnum {
// 2. the vector of indices into timeline where events occur // 2. the vector of indices into timeline where events occur
// for this signal // for this signal
// else we propagate Err(..). // else we propagate Err(..).
let (string_vals, lsb_indxs_of_string_tmstmp_vals_on_tmln) = let (string_vals, lsb_indxs_of_string_tmstmp_vals_on_tmln) = match &all_signals[signal_idx]
match &all_signals[signal_idx] { {
SignalEnum::Data { SignalEnum::Data {
ref string_vals, ref string_vals,
ref lsb_indxs_of_string_tmstmp_vals_on_tmln, ref lsb_indxs_of_string_tmstmp_vals_on_tmln,
.. ..
} => { } => Ok((string_vals, lsb_indxs_of_string_tmstmp_vals_on_tmln)),
Ok(( SignalEnum::Alias { .. } => Err(SignalErrors::PointsToAlias),
string_vals, }?;
lsb_indxs_of_string_tmstmp_vals_on_tmln,
))
}
SignalEnum::Alias { .. } => Err(SignalErrors::PointsToAlias),
}?;
// this signal should at least have some events, otherwise, trying to index into // this signal should at least have some events, otherwise, trying to index into
// an empty vector later on would fail // an empty vector later on would fail
if lsb_indxs_of_string_tmstmp_vals_on_tmln.is_empty() { if lsb_indxs_of_string_tmstmp_vals_on_tmln.is_empty() {
return Err(SignalErrors::EmptyTimeline); return Ok(QueryResult {
current: None,
next: None
});
} }
// the vector of string timeline lsb indices should have the same // the vector of string timeline lsb indices should have the same
@ -317,9 +480,9 @@ impl SignalEnum {
let (timeline_start_time, _) = let (timeline_start_time, _) =
self.time_and_str_val_at_event_idx(0, tmstmps_encoded_as_u8s)?; self.time_and_str_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 { return Ok(QueryResult {
desired_time: desired_time.clone(), current: None,
timeline_start_time: timeline_start_time, next: Some(timeline_start_time),
}); });
} }
@ -331,7 +494,10 @@ impl SignalEnum {
// check if we're requesting a value that occurs beyond the end of the timeline, // 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 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.to_string()); return Ok(QueryResult {
current: Some((timeline_end_time, timeline_end_val.to_string())),
next: None,
});
} }
// This while loop is the meat of the lookup. Performance is log2(n), // This while loop is the meat of the lookup. Performance is log2(n),
@ -350,7 +516,21 @@ impl SignalEnum {
lower_idx = mid_idx + 1; lower_idx = mid_idx + 1;
} }
std::cmp::Ordering::Equal => { std::cmp::Ordering::Equal => {
return Ok(curr_val.to_string()); let next_time = if mid_idx < lsb_indxs_of_string_tmstmp_vals_on_tmln.len() - 1 {
Some(
self.time_and_str_val_at_event_idx(
mid_idx + 1,
tmstmps_encoded_as_u8s,
)?
.0,
)
} else {
None
};
return Ok(QueryResult {
current: Some((curr_time, curr_val.to_string())),
next: next_time,
});
} }
std::cmp::Ordering::Greater => { std::cmp::Ordering::Greater => {
upper_idx = mid_idx - 1; upper_idx = mid_idx - 1;
@ -373,14 +553,17 @@ impl SignalEnum {
}); });
} }
return Ok(left_val.to_string()); Ok(QueryResult {
current: Some((left_time, left_val.to_string())),
next: Some(right_time),
})
} }
pub fn query_num_val_on_tmln( pub fn query_num_val_on_tmln(
&self, &self,
desired_time: &BigUint, desired_time: &BigUint,
tmstmps_encoded_as_u8s: &Vec<u8>, tmstmps_encoded_as_u8s: &Vec<u8>,
all_signals: &Vec<SignalEnum>, all_signals: &Vec<SignalEnum>,
) -> Result<BigUint, SignalErrors> { ) -> Result<QueryResult<BigUint>, SignalErrors> {
let signal_idx = match self { let signal_idx = match self {
Self::Data { self_idx, .. } => { Self::Data { self_idx, .. } => {
let SignalIdx(idx) = self_idx; let SignalIdx(idx) = self_idx;
@ -388,6 +571,7 @@ impl SignalEnum {
} }
Self::Alias { Self::Alias {
name: _, name: _,
path: _,
signal_alias, signal_alias,
} => { } => {
let SignalIdx(idx) = signal_alias; let SignalIdx(idx) = signal_alias;
@ -424,7 +608,10 @@ impl SignalEnum {
// this signal should at least have some events, otherwise, trying to index into // this signal should at least have some events, otherwise, trying to index into
// an empty vector later on would fail // an empty vector later on would fail
if lsb_indxs_of_num_tmstmp_vals_on_tmln.is_empty() { if lsb_indxs_of_num_tmstmp_vals_on_tmln.is_empty() {
return Err(SignalErrors::EmptyTimeline); return Ok(QueryResult {
current: None,
next: None
});
} }
// assertion that value_sequence is a proper multiple of // assertion that value_sequence is a proper multiple of
@ -447,9 +634,9 @@ impl SignalEnum {
let (timeline_start_time, _) = let (timeline_start_time, _) =
self.time_and_num_val_at_event_idx(0, tmstmps_encoded_as_u8s)?; 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 { return Ok(QueryResult {
desired_time: desired_time.clone(), current: None,
timeline_start_time: timeline_start_time, next: Some(timeline_start_time),
}); });
} }
@ -461,7 +648,10 @@ impl SignalEnum {
// check if we're requesting a value that occurs beyond the end of the timeline, // 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 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); return Ok(QueryResult {
current: Some((timeline_end_time, timeline_end_val)),
next: None,
});
} }
// This while loop is the meat of the lookup. Performance is log2(n), // This while loop is the meat of the lookup. Performance is log2(n),
@ -480,7 +670,21 @@ impl SignalEnum {
lower_idx = mid_idx + 1; lower_idx = mid_idx + 1;
} }
std::cmp::Ordering::Equal => { std::cmp::Ordering::Equal => {
return Ok(curr_val); let next_time = if mid_idx < lsb_indxs_of_num_tmstmp_vals_on_tmln.len() - 1 {
Some(
self.time_and_num_val_at_event_idx(
mid_idx + 1,
tmstmps_encoded_as_u8s,
)?
.0,
)
} else {
None
};
return Ok(QueryResult {
current: Some((curr_time, curr_val)),
next: next_time,
});
} }
std::cmp::Ordering::Greater => { std::cmp::Ordering::Greater => {
upper_idx = mid_idx - 1; upper_idx = mid_idx - 1;
@ -503,6 +707,9 @@ impl SignalEnum {
}); });
} }
return Ok(left_val); return Ok(QueryResult {
current: Some((left_time, left_val)),
next: Some(right_time),
});
} }
} }

View file

@ -4,13 +4,15 @@
// This program is distributed under both the GPLV3 license // This program is distributed under both the GPLV3 license
// and the YEHOWSHUA license, both of which can be found at // and the YEHOWSHUA license, both of which can be found at
// the root of the folder containing the sources for this program. // the root of the folder containing the sources for this program.
use chrono::prelude::{DateTime, Utc};
use super::signal::{Signal, SignalEnum}; use super::signal::{Signal, SignalEnum};
use chrono::prelude::{DateTime, Utc};
use num::BigUint;
use std::fmt;
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct Version(pub String); pub struct Version(pub String);
#[derive(Debug)] #[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum Timescale { pub enum Timescale {
Fs, Fs,
Ps, Ps,
@ -21,6 +23,20 @@ pub enum Timescale {
Unit, Unit,
} }
impl fmt::Display for Timescale {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Timescale::Fs => write!(f, "fs"),
Timescale::Ps => write!(f, "ps"),
Timescale::Ns => write!(f, "ns"),
Timescale::Us => write!(f, "μs"),
Timescale::Ms => write!(f, "ms"),
Timescale::S => write!(f, "s"),
Timescale::Unit => write!(f, "unit"),
}
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct Metadata { pub struct Metadata {
pub date: Option<DateTime<Utc>>, pub date: Option<DateTime<Utc>>,
@ -29,17 +45,16 @@ pub struct Metadata {
} }
// We do a lot of arena allocation in this codebase. // We do a lot of arena allocation in this codebase.
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ScopeIdx(pub usize); pub struct ScopeIdx(pub usize);
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct SignalIdx(pub usize); pub struct SignalIdx(pub usize);
#[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) self_idx: ScopeIdx, pub(super) self_idx: ScopeIdx,
pub(super) child_signals: Vec<SignalIdx>, pub(super) child_signals: Vec<SignalIdx>,
@ -63,6 +78,7 @@ pub struct VCD {
pub(super) all_signals: Vec<SignalEnum>, pub(super) all_signals: Vec<SignalEnum>,
pub(super) all_scopes: Vec<Scope>, pub(super) all_scopes: Vec<Scope>,
pub(super) root_scopes: Vec<ScopeIdx>, pub(super) root_scopes: Vec<ScopeIdx>,
pub(super) largest_timestamp: Option<BigUint>,
} }
impl VCD { impl VCD {
@ -84,7 +100,7 @@ impl VCD {
let scope = &self.all_scopes[idx]; let scope = &self.all_scopes[idx];
&scope.name &scope.name
} }
pub fn signal_from_signal_idx<'a>(&'a self, idx: SignalIdx) -> Signal<'a> { pub fn signal_from_signal_idx(&self, idx: SignalIdx) -> Signal<'_> {
let SignalIdx(idx) = idx; let SignalIdx(idx) = idx;
let signal_enum = &self.all_signals[idx]; let signal_enum = &self.all_signals[idx];
return Signal(signal_enum); return Signal(signal_enum);
@ -105,7 +121,7 @@ impl VCD {
// dereference signal if Signal::Alias, or keep idx if Signal::Data // dereference signal if Signal::Alias, or keep idx if Signal::Data
let signal_idx = match signal { let signal_idx = match signal {
SignalEnum::Data { self_idx, .. } => *self_idx, SignalEnum::Data { self_idx, .. } => *self_idx,
SignalEnum::Alias {signal_alias, .. } => *signal_alias, SignalEnum::Alias { signal_alias, .. } => *signal_alias,
}; };
// Should now point to Signal::Data variant, or else there's an error // Should now point to Signal::Data variant, or else there's an error
@ -121,4 +137,8 @@ impl VCD {
)), )),
} }
} }
pub fn max_timestamp(&self) -> &Option<BigUint> {
&self.largest_timestamp
}
} }

View file

@ -7,6 +7,10 @@ pub(super) enum BinaryParserErrTypes {
XValue, XValue,
ZValue, ZValue,
UValue, UValue,
HValue,
LValue,
DashValue,
WValue,
OtherValue(char), OtherValue(char),
TooLong, TooLong,
} }
@ -34,11 +38,15 @@ fn base2_str_to_byte(word: &[u8]) -> Result<u8, BinaryParserErrTypes> {
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],
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),
b'l' | b'L' => return Err(BinaryParserErrTypes::LValue),
b'h' | b'H' => return Err(BinaryParserErrTypes::HValue),
b'w' | b'W' => return Err(BinaryParserErrTypes::WValue),
b'-' => return Err(BinaryParserErrTypes::DashValue),
_ => return Err(BinaryParserErrTypes::OtherValue(*chr as char)), _ => return Err(BinaryParserErrTypes::OtherValue(*chr as char)),
} }
} }
@ -65,13 +73,13 @@ pub(super) fn binary_str_to_vec_u8(binary_str: &str) -> Result<Vec<u8>, BinaryPa
if head_idx < 8 { if head_idx < 8 {
head_idx = 0 head_idx = 0
} else { } else {
head_idx = head_idx - 8; 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 -= 8;
} }
} }
Ok(vec_u8) Ok(vec_u8)

View file

@ -5,7 +5,7 @@
// TODO: we should eventually be able to only test on just // TODO: we should eventually be able to only test on just
// the files const // the files const
pub const FILES : [&str; 30] = [ pub const FILES: [&str; 31] = [
"./tests/vcd-files/aldec/SPI_Write.vcd", "./tests/vcd-files/aldec/SPI_Write.vcd",
"./tests/vcd-files/ghdl/alu.vcd", "./tests/vcd-files/ghdl/alu.vcd",
"./tests/vcd-files/ghdl/idea.vcd", "./tests/vcd-files/ghdl/idea.vcd",
@ -36,10 +36,11 @@ pub const FILES : [&str; 30] = [
"./tests/vcd-files/xilinx_isim/test.vcd", "./tests/vcd-files/xilinx_isim/test.vcd",
"./tests/vcd-files/xilinx_isim/test1.vcd", "./tests/vcd-files/xilinx_isim/test1.vcd",
// TODO : add signal ignore list to handle bitwidth mismatches // TODO : add signal ignore list to handle bitwidth mismatches
"./tests/vcd-files/xilinx_isim/test2x2_regex22_string1.vcd" "./tests/vcd-files/xilinx_isim/test2x2_regex22_string1.vcd",
"./tests/vcd-files/scope_with_comment.vcd",
]; ];
pub const GOOD_DATE_FILES : [&str; 24] = [ pub const GOOD_DATE_FILES: [&str; 24] = [
"./test-vcd-files/aldec/SPI_Write.vcd", "./test-vcd-files/aldec/SPI_Write.vcd",
"./test-vcd-files/ghdl/alu.vcd", "./test-vcd-files/ghdl/alu.vcd",
"./test-vcd-files/ghdl/idea.vcd", "./test-vcd-files/ghdl/idea.vcd",
@ -63,10 +64,10 @@ pub const GOOD_DATE_FILES : [&str; 24] = [
"./test-vcd-files/verilator/vlt_dump.vcd", "./test-vcd-files/verilator/vlt_dump.vcd",
"./test-vcd-files/xilinx_isim/test.vcd", "./test-vcd-files/xilinx_isim/test.vcd",
"./test-vcd-files/xilinx_isim/test1.vcd", "./test-vcd-files/xilinx_isim/test1.vcd",
"./test-vcd-files/xilinx_isim/test2x2_regex22_string1.vcd" "./test-vcd-files/xilinx_isim/test2x2_regex22_string1.vcd",
]; ];
pub const BAD_DATE_FILES : [&str; 6] = [ pub const BAD_DATE_FILES: [&str; 6] = [
"./test-vcd-files/ncsim/ffdiv_32bit_tb.vcd", "./test-vcd-files/ncsim/ffdiv_32bit_tb.vcd",
"./test-vcd-files/quartus/mipsHardware.vcd", "./test-vcd-files/quartus/mipsHardware.vcd",
"./test-vcd-files/quartus/wave_registradores.vcd", "./test-vcd-files/quartus/wave_registradores.vcd",

View file

@ -0,0 +1,296 @@
$date
Sat Dec 26 15:33:14 2020
$end
$version
ModelSim Version 10.5b
$end
$timescale
1ns
$end
$scope module clkdiv2n_tb $end
$comment foo $end
$var reg 1 ! clk $end
$var reg 1 " reset $end
$var wire 1 # clk_out $end
$scope module t1 $end
$var parameter 32 $ WIDTH $end
$var parameter 32 % N $end
$var wire 1 & clk $end
$var wire 1 ' reset $end
$var wire 1 # clk_out $end
$var reg 3 ( r_reg [2:0] $end
$var wire 1 ) r_nxt [2] $end
$var wire 1 * r_nxt [1] $end
$var wire 1 + r_nxt [0] $end
$var reg 1 , clk_track $end
$upscope $end
$comment foo $end
$upscope $end
$comment foo $end
$enddefinitions $end
#0
$comment foo $end
$dumpvars
0!
x"
bx (
x,
b11 $
b110 %
x#
x+
x*
x)
x'
0&
$end
#5
1"
1'
b0 (
0,
1+
0*
0)
0#
#10
1!
1&
#15
0"
0'
#20
0!
0&
#30
1!
1&
b1 (
0+
1*
#40
0!
0&
#50
1!
1&
b10 (
1+
#60
0!
0&
#70
1!
1&
b11 (
0+
0*
1)
#80
0!
0&
#90
1!
1&
b100 (
1+
#100
0!
0&
#110
1!
1&
b101 (
0+
1*
#120
0!
0&
#130
1!
1&
b0 (
1,
1+
0*
0)
1#
#140
0!
0&
#150
1!
1&
b1 (
0+
1*
#160
0!
0&
#170
1!
1&
b10 (
1+
#180
0!
0&
#190
1!
1&
b11 (
0+
0*
1)
#200
0!
0&
#210
1!
1&
b100 (
1+
#220
0!
0&
#230
1!
1&
b101 (
0+
1*
#240
0!
0&
#250
1!
1&
b0 (
0,
1+
0*
0)
0#
#260
0!
0&
#270
1!
1&
b1 (
0+
1*
#280
0!
0&
#290
1!
1&
b10 (
1+
#300
0!
0&
#310
1!
1&
b11 (
0+
0*
1)
#320
0!
0&
#330
1!
1&
b100 (
1+
#340
0!
0&
#350
1!
1&
b101 (
0+
1*
#360
0!
0&
#370
1!
1&
b0 (
1,
1+
0*
0)
1#
#380
0!
0&
#390
1!
1&
b1 (
0+
1*
#400
0!
0&
#410
1!
1&
b10 (
1+
#420
0!
0&
#430
1!
1&
b11 (
0+
0*
1)
#440
0!
0&
#450
1!
1&
b100 (
1+
#460
0!
0&
#470
1!
1&
b101 (
0+
1*
#480
0!
0&
#490
1!
1&
b0 (
0,
1+
0*
0)
0#
#500
0!
0&
#510
1!
1&
b1 (
0+
1*