Compare commits

..

No commits in common. "main" and "add-license-1" have entirely different histories.

59 changed files with 750 additions and 2977 deletions

View file

@ -1,16 +1,13 @@
[package] [package]
name = "fastwave_backend" name = "VCDViewer"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
[profile.release]
debug = 1
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
num = "0.4" num = "0.4"
clap = { version = "4.4.6", features = ["derive"] } clap = { version = "3.1.8", features = ["derive"] }
chrono = "0.4" chrono = "0.4"
# TODO : remove itertools once date parser is reworked. function_name = "0.3.0"
itertools = "0.11" itertools = "0.10.3"

View file

@ -1,2 +0,0 @@
This codebase is also licensed to Yehowshua Immanuel to use
in any manner Yehowshua Immanuel sees fit.

146
README.md
View file

@ -1,131 +1,49 @@
Copyright(2023) - Yehowshua Immanuel # Disclaimer
PROPRIETARY - Copyright - Yehowshua Immanuel
# Vision # The Beginnings of a high-performance, low memory footprint VCD Viewer in Rust for massive multi-GB waveforms
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. ## Current Features
- very fast
- loads 400MB of VCD waveform per second on an 8 core 2017 desktop CPU with NVMe storage
- consumes roughly between 10 - 50MB of memory per GB of waveform
# Status ## Planned Features
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. - elegant/pretty UI
- can be easily ported to work in browser via webassembly
- allows high-performance custom Rust plugins to manipulate and
generate new waveforms live
Browser demo: https://app.surfer-project.org/ ## Running
Screenshot of surfer frontend below: Make sure you have a test vcd file to get you started. You can grab
<img width="1609" alt="image" src="https://github.com/ThePerfectComputer/FastWaveBackend/assets/103011002/63deffc0-98b0-405f-b093-74d10b422b01"> a large VCD file from
# A High performance, VCD Parser written in Rust
I hope for a GUI that eventually looks like the following...
![](assets/FastWaveMockup.png)
# Current Features
- Robust Error Handling
FastWave currently offers highly robust error handling(at least on the sample
VCD files in this repository) which GTKWave doesn't have. For
example, selecting the ``UTILIZATON_ENABLED`` signal after opening
[test2x2_regex22_string1.vcd](./test-vcd-files/xilinx_isim/test2x2_regex22_string1.vcd)
(one of the sample xilinx vcd test files) in GtkWave, will crash GtkWave since
this signal is malformed. FastWave on the otherhand simply notes that the
signal is malformed.
# Performance
## Methods
I have been testing performance on a 3.04GB VCD file that can be found
[here](https://drive.google.com/file/d/1pfm2qo2l8fGTHHJ8TLrg1vSGaV_TUbp2/view?usp=sharing). [here](https://drive.google.com/file/d/1pfm2qo2l8fGTHHJ8TLrg1vSGaV_TUbp2/view?usp=sharing).
For getting the GtkWave results, I fired up GtkWave, clicked on
``File``->``Open New Window``, and selected the VCD file linked to above.
I started my stopwatch as soon as I pressed enter to beging loading the VCD
file, and stopped my stopwatch once the GtkWave titlebar reached 100%.
To get the memory consumption, I opened Activity Monitor on Mac, and recorded
the GtkWave memory usage before and after loading the large VCD file, and
took the difference.
I noticed that when running FastWave in the VsCode terminal as opposed
to the MacOS system terminal or the Lapce terminal, FastWave is notably
slower.
## Results
LZ4 should really improve memory usage once I add it. Note that GTKWave uses LZ4 on the fly.
| Software | Time(s) | Memory(MB) |
|----------|---------|------------|
| FastWave | ~27.30 | 1100+ |
| GtkWave | ~30 | 89.8 |
# Current Limitations
Unable to handle VCD files that have signals with more than
2^32 - 1 = 4,294,967,295 deltas/changes.
# Running
This repository comes with several smaller VCD files emitted from
various EDA tools. If you want a larger VCD file, grab one from
[here](https://drive.google.com/file/d/1pfm2qo2l8fGTHHJ8TLrg1vSGaV_TUbp2/view?usp=sharing).
Here are some examples you can run:
The first build of the program may take some time. The first build of the program may take some time.
1. ``cargo run --release --example parse_vcd tests/vcd-files/aldec/SPI_Write.vcd`` ``cargo run --release test-vcd-files/aldec/SPI_Write.vcd``
2. ``cargo run --release --example vcd``
You can run all the tests with ``cargo test`` You can run all the tests with ``cargo test``
# Testing on Bad Files
You may wish to test the parser on a malformed VCD just to make
sure that the parser gives useful/sane errors.
Here's a command to test on a malformed VCD:
``cargo run --release --example parse_vcd tests/vcd-files/VCD_file_with_errors.vcd``
# TODO # TODO
- [ ] make a custon date parser for possibly up to 18 different versions(that
## Features and Other is, for each possible tool).
- [ ] add documenting comments - [ ] Fix warning especially usage and restriction warnings once I'm
- [ ] make signal query private until later?
- [ ] add signal query code to example once interface is finalized
- [ ] change crate name to vcd_fast_parse
- [ ] perhaps we should be looking up signal values on the VCD class
- [ ] perhaps we should be returning signal trees from the VCD class
- [ ] add lz4 compression support and compare memory perf before and after
- [ ] may need to refactor with allow for get_mut for dynamic
compression-decompression for multiple signal structs
at once to allow for multi-threading
- [ ] test against large waveforms from the
[verilog-vcd-parser](https://github.com/ben-marshall/verilog-vcd-parser)
tool
- [ ] (a bit of work) consolidate error messages in validation phase
- [ ] once the access patterns of the GUI frontend are well understood,
create specific functions and restrict as many types to private
as possible
- [ ] Print out git commit or release number.
- [ ] do a read through all the code - look for uneeded code
- [ ] Move part of the performance section to another markdown file.
## Repairs
- [ ] replace str bracket indices with get(slice)
- [ ] make a custom date parser for possibly up to 18 different versions(that is, for each possible tool).
- [ ] Consolidate error messages and add cursors throughout.
- [ ] Add file and line to the enum errors.
- [ ] Fix warnings especially usage and restriction warnings once I'm
able to successfully parse all sample VCDs. able to successfully parse all sample VCDs.
- [ ] Should be able to load waveform whilst viewing it live.
- could be quite challenging to implement for various reasons
## Code Consistency - [ ] Consolidate error messages and add cursors throughout.
- [ ] Change error messages to line and filenames. Go through all calls to unwrap. - [ ] Consider what to do with don't care values
- [ ] search for any unwraps or any direct vectors indexing will probably just convert them to strings for now.
- [ ] Handle TODOs - [ ] Include line and possible column numbers
- [ ] Remove debug code/comments. - [ ] Take a look at GTKWave parser to compare effificiency.
- [ ] Send survey to community channel.
## Documentation # Questions to Answer
- [ ] Document indexing structure with diagram and possibly include the rational - [ ] Is it safe to assume that we may treat any values before the first
non-zero timestamp as having occured on `#0`?
## Marketing # Probably No Longer Needed
- [ ] Send survey to community - [ ] Should insert nodes in BFS order

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

View file

@ -1,33 +0,0 @@
// Copyright (C) 2022 Yehowshua Immanuel
// This program is distributed under both the GPLV3 license
// and the YEHOWSHUA license, both of which can be found at
// the root of the folder containing the sources for this program.
use clap::Parser;
use std::fs::File;
use fastwave_backend::parse_vcd;
#[derive(Parser)]
struct Cli {
/// The path to the file to read
path: std::path::PathBuf,
}
fn main() -> std::io::Result<()> {
let args = Cli::parse();
use std::time::Instant;
let now = Instant::now();
let file = File::open(&args.path)?;
parse_vcd(file).unwrap();
let elapsed = now.elapsed();
println!(
"Parsed VCD file {} : {:.2?}",
&args.path.as_os_str().to_str().unwrap(),
elapsed
);
Ok(())
}

View file

@ -1,81 +0,0 @@
// Copyright (C) 2022 Yehowshua Immanuel
// This program is distributed under both the GPLV3 license
// and the YEHOWSHUA license, both of which can be found at
// the root of the folder containing the sources for this program.
use std::fs::File;
use fastwave_backend::{parse_vcd, ScopeIdx, SignalIdx, VCD};
fn indented_print(indent: u8, name: &String) {
for _ in 0..indent {
print!(" |");
}
print!("---");
println!("{name}");
}
// TODO: refactor into more general visitor pattern that takes a
// function as an argument.
fn visit_all_scopes(vcd: &VCD) {
fn visit_all_scope_children(root_idx: ScopeIdx, vcd: &VCD, indent: u8) {
if vcd.child_scopes_by_idx(root_idx).is_empty() {
} else {
for child_scope_idx in vcd.child_scopes_by_idx(root_idx) {
indented_print(indent, vcd.scope_name_by_idx(child_scope_idx));
for signal_idx in vcd.get_children_signal_idxs(child_scope_idx) {
let signal = vcd.signal_from_signal_idx(signal_idx);
let SignalIdx(idx) = signal_idx;
indented_print(indent + 1, &format!("{},{}", signal.name(), idx));
}
visit_all_scope_children(child_scope_idx, vcd, indent + 1);
}
}
}
for root_scope_idx in vcd.root_scopes_by_idx() {
indented_print(0, vcd.scope_name_by_idx(root_scope_idx));
visit_all_scope_children(root_scope_idx, vcd, 1u8);
}
}
fn main() -> std::io::Result<()> {
use std::time::Instant;
// we start by printing out the entire signal tree of
// a parsed VCD
let now = Instant::now();
let file_path = "tests/vcd-files/icarus/CPU.vcd";
let file = File::open(file_path)?;
let vcd = parse_vcd(file).unwrap();
let elapsed = now.elapsed();
println!("Parsed VCD file {} : {:.2?}", file_path, elapsed);
println!("Printing Scopes");
visit_all_scopes(&vcd);
println!("Done Printing Scopes");
println!();
// we then parse another VCD, print its signal tree and
// query some values on its timeline
let now = Instant::now();
let file_path = "tests/vcd-files/amaranth/up_counter.vcd";
let file = File::open(file_path)?;
let vcd = parse_vcd(file).unwrap();
let elapsed = now.elapsed();
println!("Parsed VCD file {} : {:.2?}", file_path, elapsed);
println!("Printing Scopes");
visit_all_scopes(&vcd);
println!("Done Printing Scopes");
let state_signal = vcd.signal_from_signal_idx(SignalIdx(4));
let name = state_signal.name();
let timestamps = vec![31499_000u32, 31500_000u32, 57760_000u32];
for timestamp in timestamps {
let time = num::BigUint::from(timestamp);
let val = state_signal.query_string_val_on_tmln(&time, &vcd).unwrap();
println!("Signal `{name}` has value `{val}` at time `{time}`");
}
Ok(())
}

View file

@ -1,12 +0,0 @@
// Copyright (C) 2022 Yehowshua Immanuel
// This program is distributed under both the GPLV3 license
// and the YEHOWSHUA license, both of which can be found at
// the root of the folder containing the sources for this program.
mod vcd;
pub use vcd::parse::parse_vcd;
pub use vcd::signal::{Signal, SignalType, SignalValue, SignalErrors};
pub use vcd::types::{Metadata, Timescale, Version};
pub use vcd::types::{ScopeIdx, SignalIdx, VCD};
pub use num::BigUint;

27
src/main.rs Normal file
View file

@ -0,0 +1,27 @@
use std::fs::File;
use clap::Parser;
pub mod test;
use test::*;
pub mod vcd;
use vcd::parse_vcd;
#[derive(Parser)]
struct Cli {
/// The path to the file to read
#[clap(parse(from_os_str))]
path: std::path::PathBuf}
fn main() -> std::io::Result<()> {
let args = Cli::parse();
let file = File::open(&args.path)?;
let vcd = parse_vcd(file).unwrap();
println!("printing signal tree");
vcd.print_scopes();
Ok(())
}

2
src/test.rs Normal file
View file

@ -0,0 +1,2 @@
mod files;
pub use files::*;

70
src/test/files.rs Normal file
View file

@ -0,0 +1,70 @@
// TODO: we should eventually be able to only test on just
// the files const
pub const files : [&str; 30] = [
"./test-vcd-files/aldec/SPI_Write.vcd",
"./test-vcd-files/ghdl/alu.vcd",
"./test-vcd-files/ghdl/idea.vcd",
"./test-vcd-files/ghdl/pcpu.vcd",
"./test-vcd-files/gtkwave-analyzer/perm_current.vcd",
"./test-vcd-files/icarus/CPU.vcd",
"./test-vcd-files/icarus/rv32_soc_TB.vcd",
"./test-vcd-files/icarus/test1.vcd",
"./test-vcd-files/model-sim/CPU_Design.msim.vcd",
"./test-vcd-files/model-sim/clkdiv2n_tb.vcd",
"./test-vcd-files/my-hdl/Simple_Memory.vcd",
"./test-vcd-files/my-hdl/sigmoid_tb.vcd",
"./test-vcd-files/my-hdl/top.vcd",
"./test-vcd-files/ncsim/ffdiv_32bit_tb.vcd",
"./test-vcd-files/quartus/mipsHardware.vcd",
"./test-vcd-files/quartus/wave_registradores.vcd",
"./test-vcd-files/questa-sim/dump.vcd",
"./test-vcd-files/questa-sim/test.vcd",
"./test-vcd-files/riviera-pro/dump.vcd",
"./test-vcd-files/systemc/waveform.vcd",
"./test-vcd-files/treadle/GCD.vcd",
"./test-vcd-files/vcs/Apb_slave_uvm_new.vcd",
"./test-vcd-files/vcs/datapath_log.vcd",
"./test-vcd-files/vcs/processor.vcd",
"./test-vcd-files/verilator/swerv1.vcd",
"./test-vcd-files/verilator/vlt_dump.vcd",
"./test-vcd-files/vivado/iladata.vcd",
"./test-vcd-files/xilinx_isim/test.vcd",
"./test-vcd-files/xilinx_isim/test1.vcd",
"./test-vcd-files/xilinx_isim/test2x2_regex22_string1.vcd"
];
pub const good_date_files : [&str; 24] = [
"./test-vcd-files/aldec/SPI_Write.vcd",
"./test-vcd-files/ghdl/alu.vcd",
"./test-vcd-files/ghdl/idea.vcd",
"./test-vcd-files/ghdl/pcpu.vcd",
"./test-vcd-files/gtkwave-analyzer/perm_current.vcd",
"./test-vcd-files/icarus/CPU.vcd",
"./test-vcd-files/icarus/rv32_soc_TB.vcd",
"./test-vcd-files/icarus/test1.vcd",
"./test-vcd-files/model-sim/CPU_Design.msim.vcd",
"./test-vcd-files/model-sim/clkdiv2n_tb.vcd",
"./test-vcd-files/my-hdl/Simple_Memory.vcd",
"./test-vcd-files/my-hdl/sigmoid_tb.vcd",
"./test-vcd-files/my-hdl/top.vcd",
"./test-vcd-files/questa-sim/dump.vcd",
"./test-vcd-files/questa-sim/test.vcd",
"./test-vcd-files/riviera-pro/dump.vcd",
"./test-vcd-files/vcs/Apb_slave_uvm_new.vcd",
"./test-vcd-files/vcs/datapath_log.vcd",
"./test-vcd-files/vcs/processor.vcd",
"./test-vcd-files/verilator/swerv1.vcd",
"./test-vcd-files/verilator/vlt_dump.vcd",
"./test-vcd-files/xilinx_isim/test.vcd",
"./test-vcd-files/xilinx_isim/test1.vcd",
"./test-vcd-files/xilinx_isim/test2x2_regex22_string1.vcd"
];
pub const bad_date_files : [&str; 6] = [
"./test-vcd-files/ncsim/ffdiv_32bit_tb.vcd",
"./test-vcd-files/quartus/mipsHardware.vcd",
"./test-vcd-files/quartus/wave_registradores.vcd",
"./test-vcd-files/systemc/waveform.vcd",
"./test-vcd-files/treadle/GCD.vcd",
"./test-vcd-files/vivado/iladata.vcd",
];

View file

@ -1,10 +1,8 @@
// Copyright (C) 2022 Yehowshua Immanuel
// This program is distributed under both the GPLV3 license
// and the YEHOWSHUA license, both of which can be found at
// the root of the folder containing the sources for this program.
pub(crate) mod parse;
mod reader; mod reader;
pub(crate) mod signal; pub use reader::*;
pub(crate) mod types;
mod utilities; mod types;
pub use types::*;
mod parse;
pub use parse::*;

View file

@ -1,35 +1,119 @@
// Copyright (C) 2022 Yehowshua Immanuel use std::{fs::File};
// This program is distributed under both the GPLV3 license use std::collections::HashMap;
// and the YEHOWSHUA license, both of which can be found at use num::BigInt;
// the root of the folder containing the sources for this program. use num::bigint::ToBigInt;
use super::*;
mod combinator_atoms; mod combinator_atoms;
mod events; use combinator_atoms::*;
mod metadata;
mod scopes;
mod types; mod types;
use types::*;
pub fn parse_vcd(file: impl std::io::Read) -> Result<super::types::VCD, String> { mod metadata;
let mut word_gen = super::reader::WordReader::new(file); use metadata::*;
let header = metadata::parse_metadata(&mut word_gen)?; mod scopes;
use scopes::*;
use function_name::named;
#[named]
fn parse_events<'a>(
word_reader : &mut WordReader,
vcd : &'a mut VCD,
signal_map : &mut HashMap<String, Signal_Idx>
) -> Result<(), String> {
loop {
let next_word = word_reader.next_word();
// if we've reached the end of the file, then there is obviously
// nothing left to do...
if next_word.is_none() {break};
let (word, cursor) = next_word.unwrap();
match &word[0..1] {
"$" => {continue}
"#" => {continue}
_ => {}
}
}
Ok(())
}
pub fn parse_vcd(file : File) -> Result<VCD, String> {
let mut word_gen = WordReader::new(file);
let header = parse_metadata(&mut word_gen)?;
// later, we'll need to map parsed ascii symbols to their
// 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 let mut vcd = VCD{
let mut vcd = super::types::VCD {
metadata : header, metadata : header,
tmstmps_encoded_as_u8s: vec![], cursor : 0.to_bigint().unwrap(),
all_signals: vec![], all_signals: vec![],
all_scopes : vec![], all_scopes : vec![],
root_scopes: vec![], scope_roots: vec![],
largest_timestamp: None,
}; };
scopes::parse_scopes(&mut word_gen, &mut vcd, &mut signal_map)?; parse_scopes(&mut word_gen, None, &mut vcd, &mut signal_map)?;
events::parse_events(&mut word_gen, &mut vcd, &mut signal_map)?; // parse_events(&mut word_gen, &mut vcd, &mut signal_map)?;
Ok(vcd) Ok(vcd)
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::test;
use std::fs::File;
#[test]
fn headers() {
// TODO: eventually, once all dates pass, merge the following
// two loops
// testing dates
for file in test::good_date_files {
let metadata = parse_metadata(
&mut WordReader::new(
File::open(file)
.unwrap()
)
);
assert!(metadata.is_ok());
assert!(metadata.unwrap().date.is_some());
}
for file in test::files {
let metadata = parse_metadata(
&mut WordReader::new(
File::open(file)
.unwrap()
)
);
assert!(metadata.is_ok());
let (scalar, timescale) = metadata.unwrap().timescale;
assert!(scalar.is_some());
}
}
#[test]
fn scopes() {
// see if we can parse all signal trees successfully
for file_name in test::files {
let file = File::open(file_name).unwrap();
let vcd = parse_vcd(file);
if !vcd.is_ok() {
dbg!(file_name);
vcd.unwrap();
}
// assert!(vcd.is_ok());
}
}
}

View file

@ -1,49 +1,67 @@
// Copyright (C) 2022 Yehowshua Immanuel
// This program is distributed under both the GPLV3 license
// and the YEHOWSHUA license, both of which can be found at
// the root of the folder containing the sources for this program.
use super::super::reader::{next_word, WordReader};
use super::types::ParseResult; use super::types::ParseResult;
use super::reader::WordReader;
pub(super) fn digit(chr : u8) -> bool { pub(super) fn digit(chr : u8) -> bool {
let zero = b'0'; let zero = b'0' as u8;
let nine = b'9'; let nine = b'9' as u8;
(chr >= zero) && (nine >= chr) let between_zero_and_nine = (chr >= zero) && (nine >= chr);
return between_zero_and_nine
} }
pub(super) fn take_until(word: &str, pattern: u8) -> ParseResult<'_> { pub(super) fn take_until<'a>(word : &'a str, pattern : u8) -> ParseResult<'a> {
let mut new_start = 0; let mut new_start = 0;
for chr in word.as_bytes() { for chr in word.as_bytes() {
if *chr == pattern { if (*chr == pattern) {
break; break
} else { }
else {
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 take_while(word: &str, cond: fn(u8) -> bool) -> ParseResult<'_> { // TODO: if I end up using simulator specific date parsers, ``take_until`` may
// suffice rendering this function obselete, at which point I should delete it.
pub(super) fn truncate_last_chr_when<'a>(word : &'a str, cond : fn(u8) -> bool) -> &'a str {
let last_chr = word.as_bytes().last().unwrap();
let mut new_end_index = word.len();
if cond(*last_chr) {
new_end_index -= 1;
}
return &word[0..new_end_index]
}
pub(super) fn take_while<'a>(word : &'a str, cond : fn(u8) -> bool) -> ParseResult<'a> {
let mut new_start = 0; let mut new_start = 0;
for chr in word.as_bytes() { for chr in word.as_bytes() {
if cond(*chr) { if (cond(*chr)) {
new_start += 1; new_start += 1;
} else { }
break; else {
break
} }
} }
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> {
@ -55,29 +73,32 @@ pub(super) fn tag<'a>(word: &'a str, pattern: &'a str) -> ParseResult<'a> {
let mut res = true; let mut res = true;
for (c_lhs, c_rhs) in iter { for (c_lhs, c_rhs) in iter {
res = res && (c_lhs == c_rhs); res = res && (c_lhs == c_rhs);
if !res { if !res {break}
break;
}
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<R: std::io::Read>( pub(super) fn ident(
word_reader: &mut WordReader<R>, word_reader : &mut WordReader,
keyword : &str, keyword : &str,
) -> Result<(), String> { ) -> Result<(), String> {
// let keyword = "module"; // let keyword = "module";
let (word, cursor) = next_word!(word_reader)?;
let err : Result<(), String> = Err(format!("reached end of file without parser leaving ident"));
let word = word_reader.next_word();
let (word, cursor) = word.ok_or(err).unwrap();
if word == keyword { if word == keyword {
Ok(()) return 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:?}");
Err(err) return Err(err)
} }
} }

View file

@ -1,546 +0,0 @@
// Copyright (C) 2022 Yehowshua Immanuel
// This program is distributed under both the GPLV3 license
// and the YEHOWSHUA license, both of which can be found at
// the root of the folder containing the sources for this program.
use num::BigUint;
use std::collections::HashMap;
use super::super::reader::{next_word, Cursor, Line, Word, WordReader};
use super::super::signal::{LsbIdxOfTmstmpValOnTmln, SignalEnum};
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>(
word_reader: &mut WordReader<R>,
vcd: &mut VCD,
signal_map: &mut HashMap<String, SignalIdx>,
) -> Result<(), String> {
let mut curr_tmstmp_lsb_idx = 0u32;
let mut curr_tmstmp_len_u8 = 0u8;
loop {
let next_word = word_reader.next_word();
// 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
// nothing left to do...
if next_word.is_none() {
break;
};
let (word, cursor) = next_word.unwrap();
let Cursor(Line(_), Word(word_in_line_idx)) = cursor;
// we only want to match on the first word in a line
if word_in_line_idx != 1 {
continue;
}
match &word[0..1] {
"$" => {}
"#" => {
let value = &word[1..];
let (f, l) = (file!(), line!());
let value_biguint = BigUint::parse_bytes(value.as_bytes(), 10)
.ok_or(())
.map_err(|_| {
format!(
"Error near {f}:{l}. Failed to parse {value} as BigInt at {cursor:?}"
)
})?;
let mut value = value_biguint.to_bytes_le();
// TODO : u32 helps with less memory, but should ideally likely be
// configurable.
curr_tmstmp_len_u8 = u8::try_from(value.len()).map_err(|_| {
format!(
"Error near {}:{}. Failed to convert from usize to u8.",
file!(),
line!()
)
})?;
curr_tmstmp_lsb_idx =
u32::try_from(vcd.tmstmps_encoded_as_u8s.len()).map_err(|_| {
format!(
"Error near {}:{}. Failed to convert from usize to u32.",
file!(),
line!()
)
})?;
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
"b" => {
let binary_value = &word[1..];
let observed_num_bits = u32::try_from(binary_value.len()).map_err(|_| {
format!(
"Error near {}:{}, {cursor:?}. \
Found signal with more than 2^32 - 1 bits.",
file!(),
line!()
)
})?;
let mut value_u8: Vec<u8> = Vec::new();
let mut value_string = String::new();
let mut store_as_string = false;
// If we encounter other values than 0 or 1, we can recover from
// the error and store the value as a string.
// Or else, we propagate up other errors.
match binary_str_to_vec_u8(binary_value) {
Ok(result) => {
value_u8 = result;
}
Err(
BinaryParserErrTypes::XValue
| BinaryParserErrTypes::ZValue
| BinaryParserErrTypes::UValue
| BinaryParserErrTypes::WValue
| BinaryParserErrTypes::HValue
| BinaryParserErrTypes::DashValue
| BinaryParserErrTypes::LValue,
) => {
store_as_string = true;
// Turn to lower case for consistency
value_string = binary_value.to_ascii_lowercase();
}
Err(e) => {
let (f, l) = (file!(), line!());
Err(e).map_err(|e| {
format!("Error near {f}:{l}. Error {e:?} at {cursor:?}.")
})?;
}
}
// this word should be the signal alias
let (word, cursor) = next_word!(word_reader)?;
// lookup signal idx
let signal_idx = signal_map.get(word).ok_or(()).map_err(|_| {
format!(
"Error near {}:{}. Failed to lookup signal {word} at {cursor:?}",
file!(),
line!()
)
})?;
let signal = vcd.dealiasing_signal_idx_to_signal_lookup_mut(signal_idx)?;
match signal {
SignalEnum::Data {
name,
signal_type,
ref mut signal_error,
num_bits,
num_bytes,
nums_encoded_as_fixed_width_le_u8,
string_vals,
lsb_indxs_of_num_tmstmp_vals_on_tmln,
byte_len_of_num_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
// an error
if signal_error.is_some() {
continue;
}
// Get the observed number of bits for the value parsed earlier
// and verify that it is not greater than the numbits declared
// when the signal was declared.
// Also account for the error case of a bitwidth of `None`
match num_bits {
Some(ref num_bits) => {
if observed_num_bits > *num_bits {
let (f, l) = (file!(), line!());
let msg = format!("\
Error near {f}:{l}. The bitwidth for signal {name} \
of signal_type {signal_type:?} is expected to be `{num_bits}` not \
`{observed_num_bits}`. \
This error occurred while parsing the vcd file at \
{cursor:?}");
*signal_error = Some(msg);
continue;
}
}
None => {
let (f, l) = (file!(), line!());
let msg = format!(
"\
Error near {f}:{l}. The bitwidth for signal {name} \
must be specified for a signal of type {signal_type:?}. \
This error occurred while parsing the vcd file at \
{cursor:?}"
);
Err(msg)?;
}
};
if store_as_string {
lsb_indxs_of_string_tmstmp_vals_on_tmln
.push(LsbIdxOfTmstmpValOnTmln(curr_tmstmp_lsb_idx));
byte_len_of_string_tmstmp_vals_on_tmln.push(curr_tmstmp_len_u8);
string_vals.push(value_string);
Ok(())
} else {
// timestamp stuff
lsb_indxs_of_num_tmstmp_vals_on_tmln
.push(LsbIdxOfTmstmpValOnTmln(curr_tmstmp_lsb_idx));
byte_len_of_num_tmstmp_vals_on_tmln.push(curr_tmstmp_len_u8);
// value stuff
// we may need to zero extend values
// so that we end up storing all values
// of a particular signal in a consistent
// amount of bytes
let bytes_required = num_bytes.ok_or_else(|| {
format!("Error near {}:{}. num_bytes empty.", file!(), line!())
})?;
let mut curr_num_bytes =
u16::try_from(value_u8.len()).map_err(|_| {
format!(
"Error near {}:{}. \
Found signal {name} with with value change of greater \
than 2^32 - 1 bits on {cursor:?}.",
file!(),
line!()
)
})?;
nums_encoded_as_fixed_width_le_u8.append(&mut value_u8);
while curr_num_bytes < bytes_required {
nums_encoded_as_fixed_width_le_u8.push(0u8);
curr_num_bytes += 1;
}
Ok(())
}
}
SignalEnum::Alias { .. } => {
let (f, l) = (file!(), line!());
let msg = format!(
"Error near {f}:{l}, a signal alias should not point to a signal alias.\n\
This error occurred while parsing vcd file at {cursor:?}");
Err(msg)
}
}?;
}
// handle the case of a one bit signal whose value is set to `0`
"0" => {
// lookup signal idx
let hash = &word[1..];
let signal_idx = signal_map.get(hash).ok_or(()).map_err(|_| {
format!(
"Error near {}:{}. Failed to lookup signal {hash} at {cursor:?}",
file!(),
line!()
)
})?;
let signal = vcd.dealiasing_signal_idx_to_signal_lookup_mut(signal_idx)?;
match signal {
SignalEnum::Data {
name,
signal_type,
ref mut signal_error,
num_bits,
num_bytes,
nums_encoded_as_fixed_width_le_u8,
lsb_indxs_of_num_tmstmp_vals_on_tmln,
byte_len_of_num_tmstmp_vals_on_tmln,
..
} => {
// if this is a bad signal, go ahead and skip it
if signal_error.is_some() {
continue;
}
// Get bitwidth and verify that it is 1.
// Also account for the error case of a bitwidth of `None`
match num_bits {
Some(ref num_bits) => {
if *num_bits != 1 {
let (f, l) = (file!(), line!());
let msg = format!(
"\
Error near {f}:{l}. The bitwidth for signal {name} \
of signal_type {signal_type:?} is expected to be `1` not \
`{num_bits}`. \
This error occurred while parsing the vcd file at \
{cursor:?}"
);
*signal_error = Some(msg);
continue;
}
}
None => {
let (f, l) = (file!(), line!());
let msg = format!(
"\
Error near {f}:{l}. The bitwidth for signal {name} \
must be specified for a signal of type {signal_type:?}. \
This error occurred while parsing the vcd file at \
{cursor:?}"
);
Err(msg)?;
}
};
// timestamp stuff
lsb_indxs_of_num_tmstmp_vals_on_tmln
.push(LsbIdxOfTmstmpValOnTmln(curr_tmstmp_lsb_idx));
byte_len_of_num_tmstmp_vals_on_tmln.push(curr_tmstmp_len_u8);
// value stuff
// we may need to zero extend values
// so that we end up storing all values
// of a particular signal in a consistent
// amount of bytes
let bytes_required = num_bytes.ok_or_else(|| {
format!("Error near {}:{}. num_bytes empty.", file!(), line!())
})?;
nums_encoded_as_fixed_width_le_u8.push(0u8);
let mut curr_num_bytes = 1;
while curr_num_bytes < bytes_required {
nums_encoded_as_fixed_width_le_u8.push(0u8);
curr_num_bytes += 1;
}
Ok(())
}
SignalEnum::Alias { .. } => {
let (f, l) = (file!(), line!());
let msg = format!(
"Error near {f}:{l}, a signal alias should not point to a signal alias.\n\
This error occurred while parsing vcd file at {cursor:?}");
Err(msg)
}
}?;
}
"1" => {
// lokup signal idx
let hash = &word[1..];
let signal_idx = signal_map.get(hash).ok_or(()).map_err(|_| {
format!(
"Error near {}:{}. Failed to lookup signal {hash} at {cursor:?}",
file!(),
line!()
)
})?;
let signal = vcd.dealiasing_signal_idx_to_signal_lookup_mut(signal_idx)?;
match signal {
SignalEnum::Data {
name,
signal_type,
ref mut signal_error,
num_bits,
num_bytes,
nums_encoded_as_fixed_width_le_u8,
lsb_indxs_of_num_tmstmp_vals_on_tmln,
byte_len_of_num_tmstmp_vals_on_tmln,
..
} => {
// if this is a bad signal, go ahead and skip it
if signal_error.is_some() {
continue;
}
// Get bitwidth and verify that it is 1.
// Also account for the error case of a bitwidth of `None`
match num_bits {
Some(ref num_bits) => {
if *num_bits != 1 {
let (f, l) = (file!(), line!());
let msg = format!(
"\
Error near {f}:{l}. The bitwidth for signal {name} \
of signal_type {signal_type:?} is expected to be `1` not \
`{num_bits}`. \
This error occurred while parsing the vcd file at \
{cursor:?}"
);
*signal_error = Some(msg);
continue;
}
}
None => {
let (f, l) = (file!(), line!());
let msg = format!(
"\
Error near {f}:{l}. The bitwidth for signal {name} \
must be specified for a signal of type {signal_type:?}. \
This error occurred while parsing the vcd file at \
{cursor:?}"
);
Err(msg)?;
}
};
// timestamp stuff
lsb_indxs_of_num_tmstmp_vals_on_tmln
.push(LsbIdxOfTmstmpValOnTmln(curr_tmstmp_lsb_idx));
byte_len_of_num_tmstmp_vals_on_tmln.push(curr_tmstmp_len_u8);
// value stuff
// we may need to zero extend values
// so that we end up storing all values
// of a particular signal in a consistent
// amount of bytes
let bytes_required = num_bytes.ok_or_else(|| {
format!("Error near {}:{}. num_bytes empty.", file!(), line!())
})?;
nums_encoded_as_fixed_width_le_u8.push(1u8);
let mut curr_num_bytes = 1;
while curr_num_bytes < bytes_required {
nums_encoded_as_fixed_width_le_u8.push(0u8);
curr_num_bytes += 1;
}
Ok(())
}
SignalEnum::Alias { .. } => {
let (f, l) = (file!(), line!());
let msg = format!(
"Error near {f}:{l}, a signal alias should not point to a signal alias.\n\
This error occurred while parsing vcd file at {cursor:?}");
Err(msg)
}
}?;
}
// other one bit cases
"x" | "X" | "z" | "Z" | "u" | "U" | "h" | "H" | "l" | "L" | "w" | "W" | "-" => {
// Select value and turn to lowercase for consistency
let val = word[0..1].to_ascii_lowercase();
// lokup signal idx
let hash = &word[1..];
let signal_idx = signal_map.get(hash).ok_or(()).map_err(|_| {
format!(
"Error near {}:{}. Failed to lookup signal {hash} at {cursor:?}",
file!(),
line!()
)
})?;
let signal = vcd.dealiasing_signal_idx_to_signal_lookup_mut(signal_idx)?;
match signal {
SignalEnum::Data {
name,
signal_type,
ref mut signal_error,
num_bits,
string_vals,
byte_len_of_string_tmstmp_vals_on_tmln,
lsb_indxs_of_string_tmstmp_vals_on_tmln,
..
} => {
// if this is a bad signal, go ahead and skip it
if signal_error.is_some() {
continue;
}
// Get bitwidth and verify that it is 1.
// Also account for the error case of a bitwidth of `None`
match num_bits {
Some(ref num_bits) => {
if *num_bits != 1 {
let (f, l) = (file!(), line!());
let msg = format!(
"\
Error near {f}:{l}. The bitwidth for signal {name} \
of signal_type {signal_type:?} is expected to be `1` not \
`{num_bits}`. \
This error occurred while parsing the vcd file at \
{cursor:?}"
);
*signal_error = Some(msg);
continue;
}
}
None => {
let (f, l) = (file!(), line!());
let msg = format!(
"\
Error near {f}:{l}. The bitwidth for signal {name} \
must be specified for a signal of type {signal_type:?}. \
This error occurred while parsing the vcd file at \
{cursor:?}"
);
Err(msg)?;
}
};
// record timestamp at which this event occurs
lsb_indxs_of_string_tmstmp_vals_on_tmln
.push(LsbIdxOfTmstmpValOnTmln(curr_tmstmp_lsb_idx));
byte_len_of_string_tmstmp_vals_on_tmln.push(curr_tmstmp_len_u8);
// record value
string_vals.push(val);
Ok(())
}
SignalEnum::Alias { .. } => {
let (f, l) = (file!(), line!());
let msg = format!(
"Error near {f}:{l}, a signal alias should not point to a signal alias.\n\
This error occurred while parsing vcd file at {cursor:?}");
Err(msg)
}
}?;
}
// 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 (hash, cursor) = next_word!(word_reader)?;
// lokup signal idx
let signal_idx = signal_map.get(hash).ok_or(()).map_err(|_| {
format!(
"Error near {}:{}. Failed to lookup signal {hash} at {cursor:?}",
file!(),
line!()
)
})?;
let signal = vcd.dealiasing_signal_idx_to_signal_lookup_mut(signal_idx)?;
match signal {
SignalEnum::Data {
ref mut signal_error,
string_vals,
byte_len_of_string_tmstmp_vals_on_tmln,
lsb_indxs_of_string_tmstmp_vals_on_tmln,
..
} => {
// if this is a bad signal, go ahead and skip it
if signal_error.is_some() {
continue;
}
// record timestamp at which this event occurs
lsb_indxs_of_string_tmstmp_vals_on_tmln
.push(LsbIdxOfTmstmpValOnTmln(curr_tmstmp_lsb_idx));
byte_len_of_string_tmstmp_vals_on_tmln.push(curr_tmstmp_len_u8);
// record string value
string_vals.push(val);
Ok(())
}
SignalEnum::Alias { .. } => {
let (f, l) = (file!(), line!());
let msg = format!(
"Error near {f}:{l}, a signal alias should not point to a signal alias.\n\
This error occurred while parsing vcd file at {cursor:?}");
Err(msg)
}
}?;
}
_ => {}
}
}
Ok(())
}

View file

@ -1,16 +1,11 @@
// Copyright (C) 2022 Yehowshua Immanuel use chrono::prelude::*;
// This program is distributed under both the GPLV3 license
// and the YEHOWSHUA license, both of which can be found at
// the root of the folder containing the sources for this program.
use chrono::prelude::{DateTime, Utc};
use itertools::Itertools; use itertools::Itertools;
use std::fs::File;
use function_name::named;
use super::super::reader::{next_word, Cursor, WordReader}; use super::*;
use super::super::types::{Metadata, Timescale, Version};
use super::combinator_atoms::{digit, tag, take_until, take_while};
use super::types::ParseResult;
#[named]
pub(super) fn parse_date( pub(super) fn parse_date(
word_and_ctx1 : (&str, &Cursor), word_and_ctx1 : (&str, &Cursor),
word_and_ctx2 : (&str, &Cursor), word_and_ctx2 : (&str, &Cursor),
@ -18,16 +13,17 @@ pub(super) fn parse_date(
word_and_ctx4 : (&str, &Cursor), word_and_ctx4 : (&str, &Cursor),
word_and_ctx5 : (&str, &Cursor), word_and_ctx5 : (&str, &Cursor),
) -> Result<DateTime<Utc>, String> { ) -> Result<DateTime<Utc>, String> {
let day = { let day = {
// check for another word in the file // check for another word in the file
let (word, cursor) = word_and_ctx1; let (word, cursor) = word_and_ctx1;
let days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]; let days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
if !days.contains(&word) { if !days.contains(&word) {
let msg = format!("Error near {}:{}.", file!(), line!()); let msg = format!("reached end of file without parser leaving {}\n", function_name!());
let msg2 = format!("{word} is not a valid weekday : expected one of {days:?}\n"); let msg2 = format!("{word} is not a valid weekday : expected one of {days:?}\n");
let msg3 = format!("failure location: {cursor:?}"); let msg3 = format!("failure location: {cursor:?}");
return Err(format!("{}{}{}", msg, msg2, msg3)); return Err(format!("{}{}{}", msg, msg2, msg3))
} }
word.to_string() word.to_string()
@ -38,14 +34,16 @@ pub(super) fn parse_date(
let (word, cursor) = word_and_ctx2; let (word, cursor) = word_and_ctx2;
let months = [ let months = [
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "Jan", "Feb", "Mar", "Apr",
"May", "Jun", "Jul", "Aug",
"Sept", "Oct", "Nov", "Dec",
]; ];
if !months.contains(&word) { if !months.contains(&word) {
let msg = format!("Error near {}:{}.", file!(), line!()); let msg = format!("reached end of file without parser leaving {}\n", function_name!());
let msg2 = format!("{word} is not a valid month : expected one of {months:?}\n"); let msg2 = format!("{word} is not a valid month : expected one of {months:?}\n");
let msg3 = format!("failure location: {cursor:?}"); let msg3 = format!("failure location: {cursor:?}");
return Err(format!("{}{}{}", msg, msg2, msg3)); return Err(format!("{}{}{}", msg, msg2, msg3))
} }
word.to_string() word.to_string()
@ -57,14 +55,15 @@ pub(super) fn parse_date(
let date : u8 = match word.to_string().parse() { let date : u8 = match word.to_string().parse() {
Ok(date) => date, Ok(date) => date,
Err(e) => return Err(format!("Error near {}:{}. {e}", file!(), line!())), Err(_) => {return Err("".to_string())}
}; };
if date > 31 { if date > 31 {
let msg = format!("Error near {}:{}.", file!(), line!()); let msg = format!("reached end of file without parser leaving {}\n", function_name!());
let msg2 = format!("{word} is not a valid date : must be between 0 and 31\n"); let msg2 = format!("{word} is not a valid date : must be between 0 and 31\n");
let msg3 = format!("failure location: {cursor:?}"); let msg3 = format!("failure location: {cursor:?}");
return Err(format!("{}{}{}", msg, msg2, msg3)); return Err(format!("{}{}{}", msg, msg2, msg3))
} }
date.to_string() date.to_string()
@ -76,164 +75,160 @@ pub(super) fn parse_date(
let res = take_until(word, b':'); let res = take_until(word, b':');
res.assert_match()?; res.assert_match()?;
let hh: u8 = res let hh : u8 = res.matched.to_string()
.matched
.to_string()
.parse() .parse()
.map_err(|e| format!("Error near {}:{}. {e}", file!(), line!()))?; .map_err(|_| "failed to parse".to_string())?;
if hh > 23 { if hh > 23 {
let msg = format!("Error near {}:{}.", file!(), line!()); let msg = format!("reached end of file without parser leaving {}\n", function_name!());
let msg2 = format!("{hh} is not a valid hour : must be between 0 and 23\n"); let msg2 = format!("{hh} is not a valid hour : must be between 0 and 23\n");
let msg3 = format!("failure location: {cursor:?}"); let msg3 = format!("failure location: {cursor:?}");
return Err(format!("{}{}{}", msg, msg2, msg3)); return Err(format!("{}{}{}", msg, msg2, msg3))
} }
// get minute // get minute
let word = &res.residual[1..]; // chop off colon which is at index 0 let word = &res.residual[1..]; // chop off colon which is at index 0
let res = take_until(word, b':'); let res = take_until(word, b':');
res.assert_match()?; res.assert_match()?;
let mm: u8 = res let mm : u8 = res.matched.to_string()
.matched
.to_string()
.parse() .parse()
.map_err(|e| format!("Error near {}:{}. {e}", file!(), line!()))?; .map_err(|_| "failed to parse".to_string())?;
if mm > 60 { if mm > 60 {
let msg = format!("Error near {}:{}.", file!(), line!()); let msg = format!("reached end of file without parser leaving {}\n", function_name!());
let msg2 = format!("{mm} is not a valid minute : must be between 0 and 60\n"); let msg2 = format!("{mm} is not a valid minute : must be between 0 and 60\n");
let msg3 = format!("failure location: {cursor:?}"); let msg3 = format!("failure location: {cursor:?}");
return Err(format!("{}{}{}", msg, msg2, msg3)); return Err(format!("{}{}{}", msg, msg2, msg3))
} }
// get second // get second
// let ss : u8 = remainder.to_string().parse().unwrap(); // let ss : u8 = remainder.to_string().parse().unwrap();
res.assert_residual()?; res.assert_residual()?;
let residual = &res.residual[1..]; // chop of colon which is at index 0 let residual = &res.residual[1..]; // chop of colon which is at index 0
let ss: u8 = residual let ss : u8 = residual.to_string()
.to_string()
.parse() .parse()
.map_err(|e| format!("Error near {}:{}. {e}", file!(), line!()))?; .map_err(|_| "failed to parse".to_string())?;
if ss > 60 { if ss > 60 {
let msg = format!("Error near {}:{}.", file!(), line!()); let msg = format!("reached end of file without parser leaving {}\n", function_name!());
let msg2 = format!("{ss} is not a valid second : must be between 0 and 60\n"); let msg2 = format!("{ss} is not a valid second : must be between 0 and 60\n");
let msg3 = format!("failure location: {cursor:?}"); let msg3 = format!("failure location: {cursor:?}");
return Err(format!("{}{}{}", msg, msg2, msg3)); return Err(format!("{}{}{}", msg, msg2, msg3))
} }
(hh.to_string(), mm.to_string(), ss.to_string()) (hh.to_string(), mm.to_string(), ss.to_string())
}; };
let year = { let year = {
// check for another word in the file // check for another word in the file
let (word, _) = word_and_ctx5; let (word, cursor) = word_and_ctx5;
word.to_string() word.to_string()
}; };
// 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 = DateTime::parse_from_str(full_date.as_str(), "%a %b %e %T %Y"); let full_date = Utc.datetime_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().into()); return Ok(full_date.unwrap())
} }
Err(format!( Err("failed to parse date".to_string())
"Error near {}:{}. Failed to parse date.",
file!(),
line!()
))
} }
pub(super) fn parse_version<R: std::io::Read>( #[named]
word_reader: &mut WordReader<R>, pub(super) fn parse_version(word_reader : &mut WordReader) -> Result<Version, String> {
) -> Result<Version, String> {
let mut version = String::new(); let mut version = String::new();
loop { loop {
let (word, _) = next_word!(word_reader)?; let word = word_reader.next_word();
// if there isn't another word left in the file, then we exit
if word.is_none() {
return Err(format!("reached end of file without parser leaving {}", function_name!()))
}
let (word, cursor) = word.unwrap();
if word == "$end" { if word == "$end" {
// truncate trailing whitespace // truncate trailing whitespace
let version = version[0..(version.len() - 1)].to_string(); let version = version[0..(version.len() - 1)].to_string();
return Ok(Version(version)); return Ok(Version(version))
} else {
}
else {
version.push_str(word); version.push_str(word);
version.push(' '); version.push_str(" ");
} }
} }
} }
pub(super) fn parse_timescale<R: std::io::Read>( #[named]
word_reader: &mut WordReader<R>, pub(super) fn parse_timescale(word_reader : &mut WordReader) -> Result<(Option<u32>, Timescale), String> {
) -> Result<(Option<u32>, Timescale), String> { let err_msg = format!("failed in {}", function_name!());
// we might see `1ps $end` or `1 ps $end`
// we might see `scalarunit $end` or `scalar unit $end`
// first get timescale // first get timescale
let (word, _) = next_word!(word_reader)?; let (word, cursor) = word_reader.next_word().ok_or(&err_msg)?;
let ParseResult { matched, residual } = take_while(word, digit); let word = word.to_string();
let ParseResult{matched, residual} = take_while(word.as_str(), digit);
let scalar = matched; let scalar = matched;
let scalar: u32 = scalar let scalar : u32 = scalar.to_string().parse()
.to_string() .map_err(|_| &err_msg)?;
.parse()
.map_err(|e| format!("Error near {}:{}. {e}", file!(), line!()))?;
let timescale = { let timescale = {
if residual.is_empty() { if residual == "" {
let (word, _) = next_word!(word_reader)?; let (word, cursor) = word_reader.next_word().ok_or(&err_msg)?;
let unit = match word { let unit = match word {
"fs" => Ok(Timescale::Fs), "fs" => {Ok(Timescale::fs)}
"ps" => Ok(Timescale::Ps), "ps" => {Ok(Timescale::ps)}
"ns" => Ok(Timescale::Ns), "ns" => {Ok(Timescale::ns)}
"us" => Ok(Timescale::Us), "us" => {Ok(Timescale::us)}
"ms" => Ok(Timescale::Ms), "ms" => {Ok(Timescale::ms)}
"s" => Ok(Timescale::S), "s" => {Ok(Timescale::s)}
_ => Err(format!( _ => {Err(err_msg.to_string())}
"Error near {}:{}. Unknown unit {word}.", }.unwrap();
file!(),
line!()
)),
}?;
(Some(scalar), unit) (Some(scalar), unit)
} else { }
else {
let unit = match residual { let unit = match residual {
"fs" => Ok(Timescale::Fs), "ps" => {Ok(Timescale::ps)}
"ps" => Ok(Timescale::Ps), "ns" => {Ok(Timescale::ns)}
"ns" => Ok(Timescale::Ns), "us" => {Ok(Timescale::us)}
"us" => Ok(Timescale::Us), "ms" => {Ok(Timescale::ms)}
"ms" => Ok(Timescale::Ms), "s" => {Ok(Timescale::s)}
"s" => Ok(Timescale::S), _ => {Err(err_msg.to_string())}
_ => Err(format!( }.unwrap();
"Error near {}:{}. Unknown unit {residual}.",
file!(),
line!()
)),
}?;
(Some(scalar), unit) (Some(scalar), unit)
} }
}; };
// then check for the `$end` keyword // then check for the `$end` keyword
let (word, _) = next_word!(word_reader)?; let (end, cursor) = word_reader.next_word().ok_or(&err_msg)?;
tag(word, "$end").assert_match()?; tag(end, "$end").assert_match()?;
Ok(timescale) return Ok(timescale);
Err("".to_string())
} }
pub(super) fn parse_metadata<R: std::io::Read>( #[named]
word_reader: &mut WordReader<R>, pub(super) fn parse_metadata(word_reader : &mut WordReader) -> Result<Metadata, String> {
) -> Result<Metadata, String> { let err_msg = format!("reached end of file without parser leaving {}", function_name!());
let mut metadata = Metadata { let mut metadata = Metadata {
date : None, date : None,
version : None, version : None,
timescale: (None, Timescale::Unit), timescale : (None, Timescale::unit)
}; };
loop { loop {
// check for another word in the file // check for another word in the file
let (word, _) = next_word!(word_reader)?; let (word, cursor) = word_reader.next_word().ok_or(&err_msg)?;
let ParseResult{matched, residual} = tag(word, "$"); let ParseResult{matched, residual} = tag(word, "$");
match matched { match matched {
@ -241,6 +236,7 @@ pub(super) fn parse_metadata<R: std::io::Read>(
"$" => { "$" => {
match residual { match residual {
"date" => { "date" => {
let err_msg = format!("reached end of file without parser leaving {}", function_name!());
// a date is typically composed of the 5 following words which can // a date is typically composed of the 5 following words which can
// occur in any order: // occur in any order:
// {Day, Month, Date(number in month), hh:mm:ss, year}. // {Day, Month, Date(number in month), hh:mm:ss, year}.
@ -258,8 +254,8 @@ pub(super) fn parse_metadata<R: std::io::Read>(
let mut found_end = false; let mut found_end = false;
let mut lookahead_5_words : Vec<(String, Cursor)> = Vec::new(); let mut lookahead_5_words : Vec<(String, Cursor)> = Vec::new();
for _ in 0..5 { for word in 0..5 {
let (word, cursor) = next_word!(word_reader)?; let (word, cursor) = word_reader.next_word().expect(err_msg.as_str());
let word = word.to_string(); let word = word.to_string();
match word.as_str() { match word.as_str() {
"$end" => { "$end" => {
@ -274,9 +270,7 @@ pub(super) fn parse_metadata<R: std::io::Read>(
// we no longer attempt to parse date if we weren't able to lookahead 5 // we no longer attempt to parse date if we weren't able to lookahead 5
// words // words
if found_end { if found_end {continue}
continue;
}
let permutations = lookahead_5_words let permutations = lookahead_5_words
.iter() .iter()
@ -303,9 +297,10 @@ pub(super) fn parse_metadata<R: std::io::Read>(
// store date and exit loop if a match is found // store date and exit loop if a match is found
if parsed_date.is_ok() { if parsed_date.is_ok() {
metadata.date = Some(parsed_date?); metadata.date = Some(parsed_date.unwrap());
break; break
} }
} }
} }
"version" => { "version" => {
@ -320,16 +315,17 @@ pub(super) fn parse_metadata<R: std::io::Read>(
metadata.timescale = timescale.unwrap(); metadata.timescale = timescale.unwrap();
} }
} }
"scope" => break, // in VCDs, the scope keyword indicates the end of the metadata section
"var" => break, "scope" => {break}
// we keep searching for words until we've found one of the following // we keep searching for words until we've found one of the following
// keywords, ["version", "timescale", "scope", "var"] // keywords, ["version", "timescale", "scope"]
_ => {} _ => {}
} }
} }
// if word does not start with `$`, then we keep looping // if word does not start with `$`, then we keep looping
_ => {} _ => {}
} }
} }
Ok(metadata) return Ok(metadata)
} }

View file

@ -1,442 +1,192 @@
// Copyright (C) 2022 Yehowshua Immanuel //! part of the vcd parser that handles parsing the signal tree and
// This program is distributed under both the GPLV3 license //! building the resulting signal tree
// and the YEHOWSHUA license, both of which can be found at use function_name::named;
// the root of the folder containing the sources for this program.
/// part of the vcd parser that handles parsing the signal tree and use super::*;
/// building the resulting signal tree
use std::collections::HashMap;
use super::super::reader::{curr_word, next_word, WordReader}; #[named]
use super::super::signal::{SignalEnum, SignalType}; pub(super) fn parse_var<'a>(
use super::super::types::{Scope, ScopeIdx, SignalIdx, VCD}; word_reader : &mut WordReader,
parent_scope_idx : Scope_Idx,
use super::combinator_atoms::{ident, tag}; vcd : &'a mut VCD,
use super::types::ParseResult; signal_map : &mut HashMap<String, Signal_Idx>
pub(super) fn parse_var<R: std::io::Read>(
word_reader: &mut WordReader<R>,
parent_scope_idx: ScopeIdx,
vcd: &mut VCD,
signal_map: &mut HashMap<String, SignalIdx>,
path: &Vec<String>,
) -> Result<(), String> { ) -> Result<(), String> {
let (word, cursor) = next_word!(word_reader)?; let err = format!("reached end of file without parser leaving {}", function_name!());
let expected_types = [ let (word, cursor) = word_reader.next_word().ok_or(&err)?;
"event", let expected_types = ["integer", "parameter", "real", "reg", "string", "wire", "tri1", "time"];
"integer",
"parameter",
"real",
"realtime",
"reg",
"string",
"supply0",
"supply1",
"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 {
"event" => Ok(SignalType::Event), "integer" => {Ok(Sig_Type::Integer)}
"integer" => Ok(SignalType::Integer), "parameter" => {Ok(Sig_Type::Parameter)}
"parameter" => Ok(SignalType::Parameter), "real" => {Ok(Sig_Type::Real)}
"real" => Ok(SignalType::Real), "reg" => {Ok(Sig_Type::Reg)}
"realtime" => Ok(SignalType::RealTime), "string" => {Ok(Sig_Type::Str)}
"reg" => Ok(SignalType::Reg), "wire" => {Ok(Sig_Type::Wire)}
"string" => Ok(SignalType::Str), "tri1" => {Ok(Sig_Type::Tri1)}
"supply0" => Ok(SignalType::Supply0), "time" => {Ok(Sig_Type::Time)}
"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!("found keyword `{word}` but expected one of {expected_types:?} on {cursor:?}");
"Error near {}:{} \
found keyword `{word}` but expected one of \
{expected_types:?} on {cursor:?}",
file!(),
line!()
);
Err(err) Err(err)
} }
}?; }?;
let (word, cursor) = next_word!(word_reader)?; let (word, cursor) = word_reader.next_word().ok_or(&err)?;
let parse_err = format!("failed to parse as usize on {cursor:?}"); let parse_err = format!("failed to parse as usize on {cursor:?}");
// $var parameter 3 a IDLE $end // $var parameter 3 a IDLE $end
// ^ - num_bits // ^ - no_bits
let num_bits = match var_type { let no_bits = match var_type {
SignalType::Event Sig_Type::Integer | Sig_Type::Parameter |
| SignalType::Integer Sig_Type::Real | Sig_Type::Reg |
| SignalType::Parameter Sig_Type::Wire => {
| SignalType::Reg let no_bits = word.parse::<usize>().expect(parse_err.as_str());
| SignalType::Supply0 Some(no_bits)
| SignalType::Supply1
| SignalType::Tri
| SignalType::TriAnd
| 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!(
"Error near {}:{} while parsing vcd file at {cursor:?}. \
This signal has {num_bits} > 2^32 - 1 bits.",
file!(),
line!()
)
})?;
Some(num_bits)
} }
// for strings, reals, and realtimes we don't really care what the number of bits is // for strings, we don't really care what the number of bits is
_ => None, _ => {None}
}; };
// $var parameter 3 a IDLE $end // $var parameter 3 a IDLE $end
// ^ - signal_alias // ^ - signal_alias
let (word, _) = next_word!(word_reader)?; let (word, cursor) = word_reader.next_word().ok_or(&err)?;
let signal_alias = word.to_string(); let signal_alias = word.to_string();
// $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, cursor) = word_reader.next_word().ok_or(&err)?;
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()),
} }
} }
let full_signal_name = full_signal_name.join(" "); let full_signal_name = full_signal_name.join(" ");
let num_bytes = if num_bits.is_some() {
let bytes_required = SignalEnum::bytes_required(num_bits.unwrap(), &full_signal_name)?;
Some(bytes_required)
} else {
None
};
// Is the current variable an alias to a signal already encountered? // Is the current variable an alias to a signal already encountered?
// if so, handle ref_signal_idx accordingly, if not, add signal to hash // if so, handle ref_signal_idx accordingly, if not, add signal to hash
// map // map
let (signal, signal_idx) = match signal_map.get(&signal_alias) { let (signal, signal_idx) = match signal_map.get(&signal_alias) {
Some(ref_signal_idx) => { Some(ref_signal_idx) => {
let signal_idx = SignalIdx(vcd.all_signals.len()); let signal_idx = Signal_Idx(vcd.all_signals.len());
let signal = SignalEnum::Alias { let signal = Signal::Alias{
name: full_signal_name.clone(), name: full_signal_name,
path: path signal_alias: *ref_signal_idx};
.iter()
.cloned()
.chain([full_signal_name])
.collect::<Vec<String>>(),
signal_alias: *ref_signal_idx,
};
(signal, signal_idx) (signal, signal_idx)
} }
None => { None => {
let signal_idx = SignalIdx(vcd.all_signals.len()); let signal_idx = Signal_Idx(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 = Signal::Data{
name: full_signal_name.clone(), name: full_signal_name,
path: path sig_type: var_type,
.iter() num_bits: no_bits,
.cloned()
.chain([full_signal_name])
.collect::<Vec<String>>(),
signal_type: var_type,
index: size,
signal_error: None,
num_bits,
num_bytes,
self_idx: signal_idx, self_idx: signal_idx,
nums_encoded_as_fixed_width_le_u8: vec![], timeline: vec![],
string_vals: vec![], scope_parent: parent_scope_idx };
lsb_indxs_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![],
lsb_indxs_of_string_tmstmp_vals_on_tmln: vec![],
};
(signal, signal_idx) (signal, signal_idx)
} }
}; };
vcd.all_signals.push(signal); vcd.all_signals.push(signal);
let ScopeIdx(parent_scope_idx_usize) = parent_scope_idx; let Scope_Idx(parent_scope_idx_usize) = parent_scope_idx;
let parent_scope = vcd.all_scopes.get_mut(parent_scope_idx_usize).unwrap(); let parent_scope = vcd.all_scopes.get_mut(parent_scope_idx_usize).unwrap();
parent_scope.child_signals.push(signal_idx); parent_scope.child_signals.push(signal_idx);
Ok(()) Ok(())
} }
/// Sometimes, variables can be listed outside of scopes. #[named]
/// We call these orphaned vars. pub(super) fn parse_signal_tree<'a>(
fn parse_orphaned_vars<R: std::io::Read>( word_reader : &mut WordReader,
word_reader: &mut WordReader<R>, parent_scope_idx : Option<Scope_Idx>,
vcd: &mut VCD, vcd : &'a mut VCD,
signal_map: &mut HashMap<String, SignalIdx>, signal_map : &mut HashMap<String, Signal_Idx>
) -> Result<(), String> { ) -> Result<(), String> {
// create scope for unscoped signals if such a scope does not
// yet exist
let scope_name = "Orphaned Signals";
// set default scope_idx to the count of existing scope as we
// generally set scope.self_idx to the number of existing scopes
// when that particular scope was inserted
let mut scope_idx = ScopeIdx(vcd.all_scopes.len());
// Override scope_idx if we find a scope named "Orphaned Signals"
// already exists
let mut scope_already_exists = false;
for scope in &vcd.all_scopes {
if scope.name == scope_name {
scope_idx = scope.self_idx;
scope_already_exists = true;
break;
}
}
if !scope_already_exists {
vcd.all_scopes.push(Scope {
name: scope_name.to_string(),
self_idx: scope_idx,
child_signals: vec![],
child_scopes: vec![],
});
vcd.root_scopes.push(scope_idx);
}
// we can go ahead and parse the current var as we've already encountered
// "$var" before now.
parse_var(word_reader, scope_idx, vcd, signal_map, &vec![])?;
loop {
let (word, cursor) = next_word!(word_reader)?;
match word {
"$var" => {
parse_var(word_reader, scope_idx, vcd, signal_map, &vec![])?;
}
"$scope" => break,
_ => {
let msg = format!(
"Error near {}:{}.\
Expected $scope or $var, found \
{word} at {cursor:?}",
file!(),
line!()
);
Err(msg)?;
}
};
}
Ok(())
}
fn parse_scopes_inner<R: std::io::Read>(
word_reader: &mut WordReader<R>,
parent_scope_idx: Option<ScopeIdx>,
vcd: &mut VCD,
signal_map: &mut HashMap<String, SignalIdx>,
path: &Vec<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 err = format!("reached end of file without parser leaving {}", function_name!());
let (keyword, cursor) = word_reader.next_word().ok_or(&err)?;
let expected = ["module", "begin", "task", "function", "fork"]; let expected = ["module", "begin", "task", "function"];
if expected.contains(&keyword) { if expected.contains(&keyword) {
Ok(()) Ok(())
} else { } else {
let err = format!( let err = format!("found keyword `{keyword}` but expected one of `{expected:?}` on {cursor:?}");
"Error near {}:{}. \
found keyword `{keyword}` but expected one of \
{expected:?} on {cursor:?}",
file!(),
line!()
);
Err(err) Err(err)
}?; }?;
// $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, _) = word_reader.next_word().ok_or(&err)?;
// 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 = Scope_Idx(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(Scope_Idx(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.scope_roots.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")?;
let err = format!("reached end of file without parser leaving {}", function_name!());
loop { loop {
let (word, cursor) = next_word!(word_reader)?; let (word, cursor) = word_reader.next_word().ok_or(&err)?;
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 stars 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( parse_signal_tree(word_reader, Some(curr_scope_idx), vcd, signal_map)?;
word_reader,
Some(curr_scope_idx),
vcd,
signal_map,
&path,
)?;
} }
"var" => { "var" => {
parse_var(word_reader, curr_scope_idx, vcd, signal_map, &path)?; parse_var(word_reader, curr_scope_idx, vcd, signal_map)?;
} }
"upscope" => { "upscope" => {
ident(word_reader, "$end")?; ident(word_reader, "$end")?;
break; break
} }
// we ignore comments // we ignore comments
"comment" => loop { "comment" => {
if ident(word_reader, "$end").is_ok() {
break;
}
},
_ => {
let err = format!(
"Error near {}:{}. \
found keyword `{residual}` but expected \
`$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);
}
}
}
} else {
// We'll be conservative and only allow new scopes in this case, and make the nameless
// scope completely transparent. I.e.
// $scope module a $end
// $scope module $end
// $scope module b $end
// ...
// $upscope
// $upscope
// $upscope
// will create `a.b`
loop { loop {
let (word, cursor) = next_word!(word_reader)?; if ident(word_reader, "$end").is_ok() {break}
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!( let err = format!("found keyword `{residual}` but expected `$scope`, `$var`, `$comment`, or `$upscope` on {cursor:?}");
"Error near {}:{}. \ return Err(err)
found keyword `{residual}` in annonyoums scope but expected \
`$scope`, `$comment`, or `$upscope` \
on {cursor:?}",
file!(),
line!()
);
return Err(err);
} }
} }
} }
_ => { _ => {
let err = format!( let err = format!("found keyword `{matched}` but expected `$` on {cursor:?}");
"Error near {}:{}. \ return Err(err)
found keyword `{matched}` but \
expected `$` on {cursor:?}",
file!(),
line!()
);
return Err(err);
}
} }
} }
} }
@ -444,76 +194,39 @@ fn parse_scopes_inner<R: std::io::Read>(
Ok(()) Ok(())
} }
pub(super) fn parse_scopes<R: std::io::Read>( #[named]
word_reader: &mut WordReader<R>, pub(super) fn parse_scopes<'a>(
vcd: &mut VCD, word_reader : &mut WordReader,
signal_map: &mut HashMap<String, SignalIdx>, parent_scope_idx : Option<Scope_Idx>,
vcd : &'a mut VCD,
signal_map : &mut HashMap<String, Signal_Idx>
) -> Result<(), String> { ) -> Result<(), String> {
// get the current word // we've already seen `$scope`, so here we just jump right in
let (word, _) = curr_word!(word_reader)?; parse_signal_tree(word_reader, None, vcd, signal_map)?;
// we may have orphaned vars that occur before the first scope let err = format!("reached end of file without parser leaving {}", function_name!());
if word == "$var" {
parse_orphaned_vars(word_reader, vcd, signal_map)?;
}
// get the current word
let (word, cursor) = curr_word!(word_reader)?;
// the current word should be "scope", as `parse_orphaned_vars`(if it
// was called), should have terminated upon encountering "$scope".
// If `parse_orphaned_vars` was not called, `parse_scopes` should still
// have only been called if the caller encountered the word "$scope"
if word != "$scope" {
let msg = format!(
"Error near {}:{}.\
Expected $scope or $var, found \
{word} at {cursor:?}",
file!(),
line!()
);
return Err(msg);
}
// now for the interesting part
parse_scopes_inner(word_reader, None, vcd, signal_map, &vec![])?;
// let err = format!("reached end of file without parser leaving {}", function_name!());
let expected_keywords = ["$scope", "$enddefinitions"]; let expected_keywords = ["$scope", "$enddefinitions"];
// there could be multiple signal trees, and unfortunately, we
// can't merge the earlier call to `parse_scopes_inner` into this loop
// because this loop gets a word from `next_word` instead of
// `curr_word()`.
loop { loop {
let (word, cursor) = next_word!(word_reader)?; let (word, cursor) = word_reader.next_word().ok_or(&err)?;
match word { match word {
"$scope" => { "$scope" => {
parse_scopes_inner(word_reader, None, vcd, signal_map, &vec![])?; parse_signal_tree(word_reader, None, vcd, signal_map)?;
} }
"$enddefinitions" => { "$enddefinitions" => {
ident(word_reader, "$end")?; ident(word_reader, "$end")?;
break; break
} }
"$comment" => { // we ignore comments
// although we don't store comments, we still need to advance the "comment" => {
// word_reader cursor to the end of the comment
loop { loop {
if ident(word_reader, "$end").is_ok() { if ident(word_reader, "$end").is_ok() {break}
break;
}
} }
} }
_ => { _ => {
let err = format!( let err = format!("found keyword `{word}` but expected oneof `{expected_keywords:?}` on {cursor:?}");
"Error near {}:{} \ return Err(err)
found keyword `{word}` but expected one of \
{expected_keywords:?} on {cursor:?}",
file!(),
line!()
);
return Err(err);
} }
} }
} }

View file

@ -1,27 +1,25 @@
// Copyright (C) 2022 Yehowshua Immanuel
// This program is distributed under both the GPLV3 license
// and the YEHOWSHUA license, both of which can be found at
// 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.is_empty() { if self.residual == "" {
Err("no residual".to_string()) return Err("no residual".to_string())
} else { }
Ok(self.residual) else {
return Ok(self.residual)
} }
} }
} }

View file

@ -1,54 +1,52 @@
// Copyright (C) 2022 Yehowshua Immanuel use std::fs::File;
// This program is distributed under both the GPLV3 license
// and the YEHOWSHUA license, both of which can be found at
// the root of the folder containing the sources for this program.
use std::collections::VecDeque; use std::collections::VecDeque;
use std::io;
use std::io::BufRead;
use std::slice; use std::slice;
use std::str; use std::str;
use std::io::prelude::*;
use std::io;
#[derive(Debug, Clone)] #[derive(Debug)]
pub(super) struct Line(pub(super) usize); struct Line(usize);
#[derive(Debug, Clone)] #[derive(Debug)]
pub(super) struct Word(pub(super) usize); struct Word(usize);
#[derive(Debug, Clone)] #[derive(Debug)]
pub(super) struct Cursor(pub(super) Line, pub(super) Word); pub struct Cursor(Line, Word);
pub(super) struct WordReader<R: io::Read> { impl Cursor {
reader: io::BufReader<R>, pub(super) fn error(&self, word : &str) -> Result<(), String> {
eof: bool, let Cursor(Line(line_no), Word(word_no)) = self;
Err(format!("Error on word '{word}' {word_no} words into line {line_no}!"))
}
}
pub struct WordReader {
reader : io::BufReader<File>,
EOF : bool,
buffers : Vec<String>, buffers : Vec<String>,
curr_line : usize, curr_line : usize,
str_slices : VecDeque<(*const u8, usize, Cursor)>, str_slices : VecDeque<(*const u8, usize, Cursor)>,
curr_slice: Option<(*const u8, usize, Cursor)>,
} }
impl<R: std::io::Read> WordReader<R> { impl WordReader {
pub(super) fn new(file: R) -> WordReader<R> { pub(super) fn new(file : File) -> WordReader {
let reader = io::BufReader::new(file); let mut reader = io::BufReader::new(file);
WordReader { WordReader {
reader, reader : reader,
eof: false, EOF : false,
buffers : vec![], buffers : vec![],
curr_line : 0, curr_line : 0,
str_slices: VecDeque::new(), str_slices : VecDeque::new()
curr_slice: None,
} }
} }
pub(super) fn next_word(&mut self) -> Option<(&str, Cursor)> { pub(super) fn next_word(&mut self) -> Option<(&str, Cursor)> {
// although reaching the eof is not technically an error, in most cases, // if there are no more words, attempt to read more content
// we treat it like one in the rest of the codebase.
// if there are no more words in the buffer, attempt to read more content
// from the file // from the file
if self.str_slices.is_empty() { if self.str_slices.is_empty() {
self.buffers.clear(); self.buffers.clear();
if self.eof { if self.EOF {return None}
return None;
}
let num_buffers = 10; let num_buffers = 10;
@ -60,72 +58,32 @@ impl<R: std::io::Read> WordReader<R> {
// if we've reached the end of the file on the first attempt to read // if we've reached the end of the file on the first attempt to read
// a line in this for loop, no further attempts are necessary and we // a line in this for loop, no further attempts are necessary and we
if bytes_read == 0 { if bytes_read == 0 {
self.eof = true; self.EOF = true;
break; break;
} }
let words = self.buffers[buf_idx].split_ascii_whitespace(); let mut words = self.buffers[buf_idx].split_ascii_whitespace();
for word in words.enumerate() { for word in words.enumerate() {
let (word_idx, word) = word; let (word_idx, word) = word;
let position = Cursor(Line(self.curr_line), Word(word_idx + 1)); let position = Cursor(Line(self.curr_line), Word(word_idx + 1));
self.str_slices self.str_slices.push_back((word.as_ptr(), word.len(), position))
.push_back((word.as_ptr(), word.len(), position))
} }
} }
} }
// if after we've attempted to read in more content from the file, // if after we've attempted to read in more content from the file,
// there are still no words... // there are still no words...
if self.str_slices.is_empty() { if self.str_slices.is_empty() {
return None; return None
} }
// if we make it here, we return the next word // if we make it here, we return the next word
unsafe { unsafe {
let (ptr, len, position) = self.str_slices.pop_front().unwrap(); let (ptr, len, position) = self.str_slices.pop_front().unwrap();
let slice = slice::from_raw_parts(ptr, len); let slice = slice::from_raw_parts(ptr, len);
self.curr_slice = Some((ptr, len, position.clone()));
return Some((str::from_utf8(slice).unwrap(), position)); return Some((str::from_utf8(slice).unwrap(), position));
}; };
} }
pub(super) fn curr_word(&mut self) -> Option<(&str, Cursor)> {
match &self.curr_slice {
Some(slice) => unsafe {
let (ptr, len, position) = slice.clone();
let slice = slice::from_raw_parts(ptr, len);
return Some((str::from_utf8(slice).unwrap(), position));
},
None => None,
} }
}
}
macro_rules! next_word {
($word_reader:ident) => {
$word_reader.next_word().ok_or(()).map_err(|_| {
format!(
"Error near {}:{}. Did not expect to reach end of file here.",
file!(),
line!()
)
})
};
}
macro_rules! curr_word {
($word_reader:ident) => {
$word_reader.curr_word().ok_or(()).map_err(|_| {
format!(
"Error near {}:{}. A call to curr_word! shouldn't \
fail unless next_word has not yet been invoked.",
file!(),
line!()
)
})
};
}
pub(super) use curr_word;
pub(super) use next_word;

View file

@ -1,715 +0,0 @@
// Copyright (C) 2022 Yehowshua Immanuel
// This program is distributed under both the GPLV3 license
// and the YEHOWSHUA license, both of which can be found at
// the root of the folder containing the sources for this program.
use super::types;
use super::types::SignalIdx;
use num::BigUint;
// Index to the least significant byte of a timestamp
// value on the timeline
#[derive(Debug, Copy, Clone)]
pub struct LsbIdxOfTmstmpValOnTmln(pub(super) u32);
#[derive(Debug, Eq, PartialEq, Clone)]
pub enum SignalType {
Event,
Integer,
Parameter,
Real,
RealTime,
Reg,
Str,
Supply0,
Supply1,
Time,
Tri,
TriAnd,
TriOr,
TriReg,
Tri0,
Tri1,
WAnd,
Wire,
WOr,
}
#[derive(Debug, PartialEq)]
pub enum SignalValue {
BigUint(BigUint),
String(String),
}
pub struct QueryResult<T> {
pub current: Option<(TimeStamp, T)>,
pub next: Option<TimeStamp>,
}
pub struct Signal<'a>(pub(super) &'a SignalEnum);
impl<'a> Signal<'a> {
pub fn name(&self) -> String {
let Signal(signal_enum) = &self;
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(
&self,
desired_time: &BigUint,
vcd: &types::VCD,
) -> Result<String, SignalErrors> {
let Signal(signal_enum) = &self;
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(
&self,
desired_time: &BigUint,
vcd: &types::VCD,
) -> Result<Option<BigUint>, SignalErrors> {
let Signal(signal_enum) = &self;
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),
}
}
}
#[derive(Debug)]
pub(super) enum SignalEnum {
Data {
name: String,
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
/// simulation before that gets assigned 1 bit values.
/// I consider this to be bad behavior. We capture such
/// errors in the following type:
signal_error: Option<String>,
num_bits: Option<u32>,
num_bytes: Option<u16>,
/// TODO : may be able to remove self_idx
self_idx: SignalIdx,
/// A signal may take on a new value and hold that value
/// for sometime. We only need to record the value of a signal
/// when it changes(the is what VCDs tend to do).
/// A signal may need x amount of bytes to record its largest
/// possible value, so we record every single value of a given
/// signal as a sequence of x number of u8s.
/// For example, we might find that `my_signal.
/// nums_encoded_as_fixed_width_le_u8`
/// has two 32 bit values, namely, 1 and 2, encoded as follows:
/// my_signal.nums_encoded_as_fixed_width_le_u8 = vec![1u8, 0u8,
/// 0u8, 0u8, 2u8, 0u8, 0u8, 0u8];
nums_encoded_as_fixed_width_le_u8: Vec<u8>,
string_vals: Vec<String>,
/// we could do Vec<(LsbIdxOfTmstmpValOnTmln, u8)>, but I
/// suspect that Vec<LsbIdxOfTmstmpValOnTmln> is more cache
/// friendly. We use ``LsbIdxOfTmstmpValOnTmln`` to index into
/// the LSB of a particular timestamp encoded as the
/// minimum length u8 sequence within
/// ``vcd.tmstmps_encoded_as_u8s``, and we use the values in
/// ``byte_len_of_num_tmstmp_vals_on_tmln`` to determine how
/// many u8 values a particular timestamp is composed of.
lsb_indxs_of_num_tmstmp_vals_on_tmln: Vec<LsbIdxOfTmstmpValOnTmln>,
byte_len_of_num_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>,
},
Alias {
name: String,
path: Vec<String>,
signal_alias: SignalIdx,
},
}
#[derive(Debug)]
pub enum SignalErrors {
EmptyTimeline,
TimelineNotMultiple,
StrTmlnLenMismatch,
OrderingFailure {
lhs_time: BigUint,
mid_time: BigUint,
rhs_time: BigUint,
},
PointsToAlias,
NoNumBytes,
Other(String),
}
// these are thin type aliases primarily to make code more readable later on
type TimeStamp = BigUint;
type SignalValNum = BigUint;
// getter functions
impl SignalEnum {
pub fn name(&self) -> String {
match self {
SignalEnum::Data { name, .. } => name,
SignalEnum::Alias { name, .. } => name,
}
.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
impl SignalEnum {
/// Computes the bytes required to store a signal's numerical value
/// using the num_bits which another function would provide from
/// the num_bits field of the Signal::Data variant.
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 = u16::try_from(bytes_required).map_err(|_| {
format!(
"Error near {}:{}. Signal {name} of length num_bits requires \
{bytes_required} > 65536 bytes.",
file!(),
line!()
)
})?;
Ok(bytes_required)
}
/// This function takes an event_idx which(is used to index into the
/// global timeline field of a VCD struct instance) and computes
/// the time pointed at by event_idx.
/// This function also uses the same idx to index into the
/// string_vals field of an instance of the Signal::Data variant
/// and gets a string value.
/// The function returns a tuple of the timestamp and string value.
fn time_and_str_val_at_event_idx(
&self,
event_idx: usize,
tmstmps_encoded_as_u8s: &Vec<u8>,
) -> Result<(TimeStamp, &str), SignalErrors> {
let (
string_vals,
lsb_indxs_of_string_tmstmp_vals_on_tmln,
byte_len_of_string_tmstmp_vals_on_tmln,
) = match self {
SignalEnum::Data {
string_vals,
lsb_indxs_of_string_tmstmp_vals_on_tmln,
byte_len_of_string_tmstmp_vals_on_tmln,
..
} => Ok((
string_vals,
lsb_indxs_of_string_tmstmp_vals_on_tmln,
byte_len_of_string_tmstmp_vals_on_tmln,
)),
SignalEnum::Alias { .. } => Err(SignalErrors::PointsToAlias),
}?;
// get index
let LsbIdxOfTmstmpValOnTmln(timestamp_idx) =
lsb_indxs_of_string_tmstmp_vals_on_tmln[event_idx];
let timestamp_idx = timestamp_idx as usize;
if byte_len_of_string_tmstmp_vals_on_tmln.is_empty() {
return Err(SignalErrors::EmptyTimeline);
}
// form timestamp
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 = BigUint::from_bytes_le(timestamp);
// get signal value
let signal_val = string_vals[event_idx].as_str();
Ok((timestamp, signal_val))
}
/// This function takes an event_idx which(is used to index into the
/// global timeline field of a VCD struct instance) and computes
/// the time pointed at by event_idx.
/// This function also uses the same idx to index into the
/// nums_encoded_as_fixed_width_le_u8 and
/// byte_len_of_num_tmstmp_vals_on_tmln fields of an instance
/// of the Signal::Data variant to compute the signal's corresponding
/// numerical value at the time pointed at by event_didx.
/// The function returns a tuple of the timestamp and numerical
/// value.
fn time_and_num_val_at_event_idx(
&self,
event_idx: usize,
tmstmps_encoded_as_u8s: &Vec<u8>,
) -> Result<(TimeStamp, SignalValNum), SignalErrors> {
let (
num_bytes,
nums_encoded_as_fixed_width_le_u8,
lsb_indxs_of_num_tmstmp_vals_on_tmln,
byte_len_of_num_tmstmp_vals_on_tmln,
) = match self {
SignalEnum::Data {
num_bytes,
nums_encoded_as_fixed_width_le_u8,
lsb_indxs_of_num_tmstmp_vals_on_tmln,
byte_len_of_num_tmstmp_vals_on_tmln,
..
} => Ok((
num_bytes,
nums_encoded_as_fixed_width_le_u8,
lsb_indxs_of_num_tmstmp_vals_on_tmln,
byte_len_of_num_tmstmp_vals_on_tmln,
)),
SignalEnum::Alias { .. } => Err(SignalErrors::PointsToAlias),
}?;
// get index
let LsbIdxOfTmstmpValOnTmln(timestamp_idx) =
lsb_indxs_of_num_tmstmp_vals_on_tmln[event_idx];
let timestamp_idx = timestamp_idx as usize;
// form timestamp
let byte_len = byte_len_of_num_tmstmp_vals_on_tmln[event_idx] as usize;
let timestamp = &tmstmps_encoded_as_u8s[timestamp_idx..(timestamp_idx + byte_len)];
let timestamp = BigUint::from_bytes_le(timestamp);
// get signal value
let bytes_per_value = num_bytes.ok_or_else(|| SignalErrors::NoNumBytes)?;
let bytes_per_value = bytes_per_value as usize;
let start_idx = event_idx * bytes_per_value;
let end_idx = (event_idx + 1) * bytes_per_value;
let signal_val = &nums_encoded_as_fixed_width_le_u8[start_idx..end_idx];
let signal_val = BigUint::from_bytes_le(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.
// Function that take in a desired time on the timeline for a
// specific signal and return a numerical or string value in a Result,
// or an error in a Result.
impl SignalEnum {
pub fn query_string_val_on_tmln(
&self,
desired_time: &BigUint,
tmstmps_encoded_as_u8s: &Vec<u8>,
all_signals: &Vec<SignalEnum>,
) -> Result<QueryResult<String>, SignalErrors> {
let signal_idx = match self {
Self::Data { self_idx, .. } => {
let SignalIdx(idx) = self_idx;
*idx
}
Self::Alias {
name: _,
signal_alias,
path: _,
} => {
let SignalIdx(idx) = signal_alias;
*idx
}
};
// if the signal idx points to data variant of the signal,
// extract:
// 1. the vector of string values
// 2. the vector of indices into timeline where events occur
// for this signal
// else we propagate Err(..).
let (string_vals, lsb_indxs_of_string_tmstmp_vals_on_tmln) = match &all_signals[signal_idx]
{
SignalEnum::Data {
ref string_vals,
ref lsb_indxs_of_string_tmstmp_vals_on_tmln,
..
} => Ok((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
// an empty vector later on would fail
if lsb_indxs_of_string_tmstmp_vals_on_tmln.is_empty() {
return Ok(QueryResult {
current: None,
next: None
});
}
// the vector of string timeline lsb indices should have the same
// length as the vector of string values
if string_vals.len() != lsb_indxs_of_string_tmstmp_vals_on_tmln.len() {
return Err(SignalErrors::StrTmlnLenMismatch);
}
// check if we're requesting a value that occurs before the recorded
// start of the timeline
let (timeline_start_time, _) =
self.time_and_str_val_at_event_idx(0, tmstmps_encoded_as_u8s)?;
if *desired_time < timeline_start_time {
return Ok(QueryResult {
current: None,
next: Some(timeline_start_time),
});
}
let mut lower_idx = 0usize;
let mut upper_idx = lsb_indxs_of_string_tmstmp_vals_on_tmln.len() - 1;
let (timeline_end_time, timeline_end_val) =
self.time_and_str_val_at_event_idx(upper_idx, tmstmps_encoded_as_u8s)?;
// check if we're requesting a value that occurs beyond the end of the timeline,
// if so, return the last value in this timeline
if *desired_time > timeline_end_time {
return Ok(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),
// where n is the number of events on the timeline.
// We can assume that by the time we get here, that the desired_time
// is an event that occurs on the timeline, given that we handle any events
// occuring after or before the recorded tiimeline in the code above.
while lower_idx <= upper_idx {
let mid_idx = lower_idx + ((upper_idx - lower_idx) / 2);
let (curr_time, curr_val) =
self.time_and_str_val_at_event_idx(mid_idx, tmstmps_encoded_as_u8s)?;
let ordering = curr_time.cmp(desired_time);
match ordering {
std::cmp::Ordering::Less => {
lower_idx = mid_idx + 1;
}
std::cmp::Ordering::Equal => {
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 => {
upper_idx = mid_idx - 1;
}
}
}
let (left_time, left_val) =
self.time_and_str_val_at_event_idx(lower_idx - 1, tmstmps_encoded_as_u8s)?;
let (right_time, _) =
self.time_and_str_val_at_event_idx(lower_idx, tmstmps_encoded_as_u8s)?;
let ordered_left = left_time < *desired_time;
let ordered_right = *desired_time < right_time;
if !(ordered_left && ordered_right) {
return Err(SignalErrors::OrderingFailure {
lhs_time: left_time,
mid_time: desired_time.clone(),
rhs_time: right_time,
});
}
Ok(QueryResult {
current: Some((left_time, left_val.to_string())),
next: Some(right_time),
})
}
pub fn query_num_val_on_tmln(
&self,
desired_time: &BigUint,
tmstmps_encoded_as_u8s: &Vec<u8>,
all_signals: &Vec<SignalEnum>,
) -> Result<QueryResult<BigUint>, SignalErrors> {
let signal_idx = match self {
Self::Data { self_idx, .. } => {
let SignalIdx(idx) = self_idx;
*idx
}
Self::Alias {
name: _,
path: _,
signal_alias,
} => {
let SignalIdx(idx) = signal_alias;
*idx
}
};
// if the signal idx points to data variant of the signal,
// extract:
// 1. the vector of LE u8 compressed values
// 2. the vector of indices into timeline where events occur
// for this signal
// 3. the number of bytes per value for this signal
// else we propagate Err(..).
let (nums_encoded_as_fixed_width_le_u8, lsb_indxs_of_num_tmstmp_vals_on_tmln, num_bytes) =
match &all_signals[signal_idx] {
SignalEnum::Data {
num_bytes,
ref nums_encoded_as_fixed_width_le_u8,
ref lsb_indxs_of_num_tmstmp_vals_on_tmln,
..
} => {
if num_bytes.is_none() {
return Err(SignalErrors::NoNumBytes);
}
Ok((
nums_encoded_as_fixed_width_le_u8,
lsb_indxs_of_num_tmstmp_vals_on_tmln,
num_bytes,
))
}
SignalEnum::Alias { .. } => Err(SignalErrors::PointsToAlias),
}?;
// this signal should at least have some events, otherwise, trying to index into
// an empty vector later on would fail
if lsb_indxs_of_num_tmstmp_vals_on_tmln.is_empty() {
return Ok(QueryResult {
current: None,
next: None
});
}
// assertion that value_sequence is a proper multiple of
// timeline_markers
let bytes_required = num_bytes.ok_or_else(|| {
SignalErrors::Other(format!(
"Error near {}:{}. num_bytes empty.",
file!(),
line!()
))
})?;
if nums_encoded_as_fixed_width_le_u8.len()
!= (lsb_indxs_of_num_tmstmp_vals_on_tmln.len() * (bytes_required as usize))
{
return Err(SignalErrors::TimelineNotMultiple);
}
// check if we're requesting a value that occurs before the recorded
// start of the timeline
let (timeline_start_time, _) =
self.time_and_num_val_at_event_idx(0, tmstmps_encoded_as_u8s)?;
if *desired_time < timeline_start_time {
return Ok(QueryResult {
current: None,
next: Some(timeline_start_time),
});
}
let mut lower_idx = 0usize;
let mut upper_idx = lsb_indxs_of_num_tmstmp_vals_on_tmln.len() - 1;
let (timeline_end_time, timeline_end_val) =
self.time_and_num_val_at_event_idx(upper_idx, tmstmps_encoded_as_u8s)?;
// check if we're requesting a value that occurs beyond the end of the timeline,
// if so, return the last value in this timeline
if *desired_time > timeline_end_time {
return Ok(QueryResult {
current: Some((timeline_end_time, timeline_end_val)),
next: None,
});
}
// This while loop is the meat of the lookup. Performance is log2(n),
// where n is the number of events on the timeline.
// We can assume that by the time we get here, that the desired_time
// is an event that occurs on the timeline, given that we handle any events
// occuring after or before the recorded tiimeline in the code above.
while lower_idx <= upper_idx {
let mid_idx = lower_idx + ((upper_idx - lower_idx) / 2);
let (curr_time, curr_val) =
self.time_and_num_val_at_event_idx(mid_idx, tmstmps_encoded_as_u8s)?;
let ordering = curr_time.cmp(desired_time);
match ordering {
std::cmp::Ordering::Less => {
lower_idx = mid_idx + 1;
}
std::cmp::Ordering::Equal => {
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 => {
upper_idx = mid_idx - 1;
}
}
}
let (left_time, left_val) =
self.time_and_num_val_at_event_idx(lower_idx - 1, tmstmps_encoded_as_u8s)?;
let (right_time, _) =
self.time_and_num_val_at_event_idx(lower_idx, tmstmps_encoded_as_u8s)?;
let ordered_left = left_time < *desired_time;
let ordered_right = *desired_time < right_time;
if !(ordered_left && ordered_right) {
return Err(SignalErrors::OrderingFailure {
lhs_time: left_time,
mid_time: desired_time.clone(),
rhs_time: right_time,
});
}
return Ok(QueryResult {
current: Some((left_time, left_val)),
next: Some(right_time),
});
}
}

View file

@ -1,144 +1,110 @@
// use crate::Signal; use std::collections::{BTreeMap, HashMap};
use chrono::prelude::*;
// Copyright (C) 2022 Yehowshua Immanuel use num::BigInt;
// This program is distributed under both the GPLV3 license
// and the YEHOWSHUA license, both of which can be found at
// the root of the folder containing the sources for this program.
use super::signal::{Signal, SignalEnum};
use chrono::prelude::{DateTime, Utc};
use num::BigUint;
use std::fmt;
#[derive(Debug, Clone)]
pub struct Version(pub String);
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum Timescale {
Fs,
Ps,
Ns,
Us,
Ms,
S,
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(super) struct Version(pub String);
pub date: Option<DateTime<Utc>>,
pub version: Option<Version>, #[derive(Debug)]
pub timescale: (Option<u32>, Timescale), pub(super) enum Timescale {fs, ps, ns, us, ms, s, unit}
#[derive(Debug)]
pub(super) struct Metadata {
pub(super) date : Option<DateTime<Utc>>,
pub(super) version : Option<Version>,
pub(super) timescale : (Option<u32>, Timescale)}
#[derive(Debug, Copy, Clone)]
pub(super) struct Scope_Idx(pub(super) usize);
#[derive(Debug, Copy, Clone)]
pub(super) struct Signal_Idx(pub(super) usize);
#[derive(Debug)]
pub(super) enum Sig_Type {Integer, Parameter, Real, Reg, Str, Wire, Tri1, Time}
#[derive(Debug)]
pub(super) struct TimeStamp(BigInt);
#[derive(Debug)]
pub(super) enum Sig_Value {
Numeric(BigInt),
NonNumeric(String)}
#[derive(Debug)]
pub(super) enum Signal{
Data{
name : String,
sig_type : Sig_Type,
num_bits : Option<usize>,
// TODO : may be able to remove self_idx
self_idx : Signal_Idx,
timeline : Vec<(TimeStamp, Sig_Value)>,
scope_parent : Scope_Idx},
Alias{
name : String,
signal_alias : Signal_Idx}
} }
// We do a lot of arena allocation in this codebase.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ScopeIdx(pub usize);
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
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) self_idx: ScopeIdx, pub(super) parent_idx : Option<Scope_Idx>,
// TODO : may be able to remove self_idx
pub(super) self_idx : Scope_Idx,
pub(super) child_signals : Vec<Signal_Idx>,
pub(super) child_scopes : Vec<Scope_Idx>}
pub(super) child_signals: Vec<SignalIdx>,
pub(super) child_scopes: Vec<ScopeIdx>,
}
#[derive(Debug)] #[derive(Debug)]
pub struct VCD { pub struct VCD {
pub metadata: Metadata, pub(super) metadata : Metadata,
// Since we only need to store values when there is an actual change pub(super) cursor : BigInt,
// in the timeline, we keep a vector that stores the time at which an pub(super) all_signals : Vec<Signal>,
// event occurs. Time t is always stored/encoded as the minimum length sequence
// of u8.
// We essentially fill ``tmstmps_encoded_as_u8s`` with BigUints converted
// to sequences of little endian u8s.
// It is up to the signals to keep track of the start/stop indices in the
// vector of u8s that constitute a timestamp value. Signals don't have to
// keep track of all timestamp values, a given signal only needs to keep
// track of the timestamps at which the given signal value changes.
pub(super) tmstmps_encoded_as_u8s: Vec<u8>,
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) scope_roots : Vec<Scope_Idx>}
pub(super) largest_timestamp: Option<BigUint>,
}
impl VCD { impl VCD {
pub fn root_scopes_by_idx(&self) -> Vec<ScopeIdx> { // TODO : make this a generic traversal function that applies specified
self.root_scopes.clone() // functions upon encountering scopes and signals
} fn print_scope_tree(
pub fn child_scopes_by_idx(&self, scope_idx: ScopeIdx) -> Vec<ScopeIdx> { &self,
let ScopeIdx(idx) = scope_idx; root_scope_idx : Scope_Idx,
let scope = &self.all_scopes[idx]; depth : usize)
scope.child_scopes.clone() {
} let all_scopes = &self.all_scopes;
pub fn get_children_signal_idxs(&self, scope_idx: ScopeIdx) -> Vec<SignalIdx> { let all_signals = &self.all_signals;
let ScopeIdx(idx) = scope_idx;
let scope = &self.all_scopes[idx];
scope.child_signals.clone()
}
pub fn scope_name_by_idx(&self, scope_idx: ScopeIdx) -> &String {
let ScopeIdx(idx) = scope_idx;
let scope = &self.all_scopes[idx];
&scope.name
}
pub fn signal_from_signal_idx(&self, idx: SignalIdx) -> Signal<'_> {
let SignalIdx(idx) = idx;
let signal_enum = &self.all_signals[idx];
return Signal(signal_enum);
}
/// We take in a Signal and attempt to de-alias that signal if it is of
/// variant ``Signal::Alias``. If it is of variant ``Signal::Alias`` and points to
/// another alias, that's an error. Otherwise, we return the ``Signal::Data``
/// pointed to by the ``Signal::Alias``.
/// If the Signal is of varint ``Signal::Data``, then that can be returned directly.
pub(super) fn dealiasing_signal_idx_to_signal_lookup_mut<'a>(
&'a mut self,
idx: &SignalIdx,
) -> Result<&'a mut SignalEnum, String> {
// get the signal pointed to be SignalIdx from the arena
let SignalIdx(idx) = idx;
let signal = &self.all_signals[*idx];
// dereference signal if Signal::Alias, or keep idx if Signal::Data let indent = " ".repeat(depth * 4);
let signal_idx = match signal { let Scope_Idx(root_scope_idx) = root_scope_idx;
SignalEnum::Data { self_idx, .. } => *self_idx, let root_scope = &all_scopes[root_scope_idx];
SignalEnum::Alias { signal_alias, .. } => *signal_alias, let root_scope_name = &root_scope.name;
println!("{indent}scope: {root_scope_name}");
for Signal_Idx(ref signal_idx) in &root_scope.child_signals {
let child_signal = &all_signals[*signal_idx];
let name = match child_signal {
Signal::Data{name, ..} => {name}
Signal::Alias{name, ..} => {name}
}; };
println!("{indent} - sig: {name}")
// Should now point to Signal::Data variant, or else there's an error
let SignalIdx(idx) = signal_idx;
let signal = self.all_signals.get_mut(idx).unwrap();
match signal {
SignalEnum::Data { .. } => Ok(signal),
SignalEnum::Alias { .. } => Err(format!(
"Error near {}:{}. A signal alias shouldn't \
point to a signal alias.",
file!(),
line!()
)),
} }
println!();
for scope_idx in &root_scope.child_scopes {
// let Scope_Idx(ref scope_idx_usize) = scope_idx;
// let child_scope = &all_scopes[*scope_idx_usize];
self.print_scope_tree(*scope_idx, depth+1);
}
// let root = vcd.all_scopes;
} }
pub fn max_timestamp(&self) -> &Option<BigUint> { pub fn print_scopes(&self) {
&self.largest_timestamp for scope_root in &self.scope_roots {
self.print_scope_tree(*scope_root, 0);
}
} }
} }

View file

@ -1,86 +0,0 @@
// Copyright (C) 2022 Yehowshua Immanuel
// This program is distributed under both the GPLV3 license
// and the YEHOWSHUA license, both of which can be found at
// the root of the folder containing the sources for this program.
#[derive(Debug)]
pub(super) enum BinaryParserErrTypes {
XValue,
ZValue,
UValue,
HValue,
LValue,
DashValue,
WValue,
OtherValue(char),
TooLong,
}
// We build a quick and not so dirty bit string parser.
fn base2_str_to_byte(word: &[u8]) -> Result<u8, BinaryParserErrTypes> {
let mut val = 0u8;
// shouldn't have more than 8 chars in str
let len = word.len();
if len > 8 {
return Err(BinaryParserErrTypes::TooLong);
}
let bit_lut = [
0b0000_0001u8,
0b0000_0010u8,
0b0000_0100u8,
0b0000_1000u8,
0b0001_0000u8,
0b0010_0000u8,
0b0100_0000u8,
0b1000_0000u8,
];
for (idx, chr) in word.iter().rev().enumerate() {
match chr {
b'1' => val |= bit_lut[idx],
b'0' => {}
b'x' | b'X' => return Err(BinaryParserErrTypes::XValue),
b'z' | b'Z' => return Err(BinaryParserErrTypes::ZValue),
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)),
}
}
Ok(val)
}
pub(super) fn binary_str_to_vec_u8(binary_str: &str) -> Result<Vec<u8>, BinaryParserErrTypes> {
let mut vec_u8: Vec<u8> = Vec::new();
let binary_str_as_bytes = binary_str.as_bytes();
let mut tail_idx = binary_str_as_bytes.len();
// clamp head if provided binary str is less than 8 long
let mut head_idx = if tail_idx >= 8 {
binary_str_as_bytes.len() - 8
} else {
0
};
while tail_idx > 0 {
let curr_b_val = &binary_str_as_bytes[head_idx..tail_idx];
let val_u8 = base2_str_to_byte(curr_b_val)?;
vec_u8.push(val_u8);
if head_idx < 8 {
head_idx = 0
} else {
head_idx -= 8;
}
if tail_idx < 8 {
tail_idx = 0
} else {
tail_idx -= 8;
}
}
Ok(vec_u8)
}

View file

@ -0,0 +1,4 @@
Icarus,Verilator,GHDL,VCS,QuestaSim,ModelSim,Quartus,SystemC,Treadle,Aldec,Riviera-PRO,MyHDL,ncsim,xilinx_isim,vivado,GTKWave-Analyzer,Amaranth
https://github.com/dpretet/vcd/blob/master/test1.vcd,https://github.com/wavedrom/vcd-samples/blob/trunk/swerv1.vcd,https://raw.githubusercontent.com/AdoobII/idea_21s/main/vhdl/idea.vcd,https://raw.githubusercontent.com/ameyjain/8-bit-Microprocessor/master/8-bit%20microprocessor/processor.vcd,https://github.com/mr-gaurav/Sequence-Counter/blob/main/test.vcd,https://github.com/Mohammad-Heydariii/Digital-Systems-Lab-Course/blob/main/Lab_project4/modelsim_files/clkdiv2n_tb.vcd,https://github.com/PedroTLemos/ProjetoInfraHard/blob/master/mipsHardware.vcd,https://github.com/jroslindo/Mips-Systemc/blob/main/REGISTRADORES_32_bits/wave_registradores.vcd,https://github.com/chipsalliance/treadle/blob/master/src/test/resources/GCD.vcd,https://github.com/SVeilleux9/FPGA-GPIO-Extender/blob/main/Firmware/aldec/SPI_Write/SPI_Write.vcd,https://github.com/prathampathak/Tic-Tac-Tao/blob/main/dump.vcd,https://github.com/aibtw/myHdl_Projects/blob/main/SimpleMemory/Simple_Memory.vcd,https://github.com/amiteee78/RTL_design/blob/master/ffdiv_32bit/ffdiv_32bit_prop_binom/run_cad/ffdiv_32bit_tb.vcd,https://github.com/mukul54/qrs-peak-fpga/blob/master/utkarsh/utkarsh.sim/sim_1/behav/xsim/test.vcd,https://github.com/saharmalmir/Eth2Ser/blob/master/UART2ETH.runs/impl_1/iladata.vcd,https://github.com/Asfagus/Network-Switch/blob/main/perm_current.vcd,Locally Simulated File
https://github.com/ombhilare999/riscv-core/blob/master/src/rv32_soc_TB.vcd,https://github.com/bigBrain1901/nPOWER-ISA-5-STAGE-PIPELINED-CPU/blob/master/post_compile_files/vlt_dump.vcd,https://github.com/gaoqqt2n/CPU/blob/master/SuperPipelineCPU/vcdfile/pcpu.vcd,https://raw.githubusercontent.com/Akashay-Singla/RISC-V/main/Pipeline/datapath_log.vcd,https://github.com/SparshAgarwal/Computer-Architecture/blob/master/hw3/hw3_1/dump.vcd,https://github.com/sh619/Songyu_Huang-Chisel/blob/main/MU0_final_version/simulation/qsim/CPU_Design.msim.vcd,,https://github.com/amrhas/PDRNoC/blob/VCRouter/noctweak/Debug/waveform.vcd.vcd,,,,https://github.com/Abhishek010397/Programming-RISC-V/blob/master/top.vcd,,https://github.com/DanieleParravicini/regex_coprocessor/blob/master/scripts/sim/test2x2_regex22_string1.vcd,https://github.com/BradMcDanel/multiplication-free-dnn/blob/master/verilog/iladata.vcd,,
https://github.com/b06902044/computer_architecture/blob/main/CPU.vcd,,https://github.com/charlycop/VLSI-1/blob/master/EXEC/ALU/alu.vcd,https://raw.githubusercontent.com/sathyapriyanka/APB_UVC_UVM/main/Apb_slave_uvm_new.vcd,,,,,,,,https://github.com/DarthSkipper/myHDL_Sigmoid/blob/master/out/testbench/sigmoid_tb.vcd,,https://github.com/pabloec1729/Hashes-generator/blob/master/RTL/velocidad/test.vcd,,,
1 Icarus Verilator GHDL VCS QuestaSim ModelSim Quartus SystemC Treadle Aldec Riviera-PRO MyHDL ncsim xilinx_isim vivado GTKWave-Analyzer Amaranth
2 https://github.com/dpretet/vcd/blob/master/test1.vcd https://github.com/wavedrom/vcd-samples/blob/trunk/swerv1.vcd https://raw.githubusercontent.com/AdoobII/idea_21s/main/vhdl/idea.vcd https://raw.githubusercontent.com/ameyjain/8-bit-Microprocessor/master/8-bit%20microprocessor/processor.vcd https://github.com/mr-gaurav/Sequence-Counter/blob/main/test.vcd https://github.com/Mohammad-Heydariii/Digital-Systems-Lab-Course/blob/main/Lab_project4/modelsim_files/clkdiv2n_tb.vcd https://github.com/PedroTLemos/ProjetoInfraHard/blob/master/mipsHardware.vcd https://github.com/jroslindo/Mips-Systemc/blob/main/REGISTRADORES_32_bits/wave_registradores.vcd https://github.com/chipsalliance/treadle/blob/master/src/test/resources/GCD.vcd https://github.com/SVeilleux9/FPGA-GPIO-Extender/blob/main/Firmware/aldec/SPI_Write/SPI_Write.vcd https://github.com/prathampathak/Tic-Tac-Tao/blob/main/dump.vcd https://github.com/aibtw/myHdl_Projects/blob/main/SimpleMemory/Simple_Memory.vcd https://github.com/amiteee78/RTL_design/blob/master/ffdiv_32bit/ffdiv_32bit_prop_binom/run_cad/ffdiv_32bit_tb.vcd https://github.com/mukul54/qrs-peak-fpga/blob/master/utkarsh/utkarsh.sim/sim_1/behav/xsim/test.vcd https://github.com/saharmalmir/Eth2Ser/blob/master/UART2ETH.runs/impl_1/iladata.vcd https://github.com/Asfagus/Network-Switch/blob/main/perm_current.vcd Locally Simulated File
3 https://github.com/ombhilare999/riscv-core/blob/master/src/rv32_soc_TB.vcd https://github.com/bigBrain1901/nPOWER-ISA-5-STAGE-PIPELINED-CPU/blob/master/post_compile_files/vlt_dump.vcd https://github.com/gaoqqt2n/CPU/blob/master/SuperPipelineCPU/vcdfile/pcpu.vcd https://raw.githubusercontent.com/Akashay-Singla/RISC-V/main/Pipeline/datapath_log.vcd https://github.com/SparshAgarwal/Computer-Architecture/blob/master/hw3/hw3_1/dump.vcd https://github.com/sh619/Songyu_Huang-Chisel/blob/main/MU0_final_version/simulation/qsim/CPU_Design.msim.vcd https://github.com/amrhas/PDRNoC/blob/VCRouter/noctweak/Debug/waveform.vcd.vcd https://github.com/Abhishek010397/Programming-RISC-V/blob/master/top.vcd https://github.com/DanieleParravicini/regex_coprocessor/blob/master/scripts/sim/test2x2_regex22_string1.vcd https://github.com/BradMcDanel/multiplication-free-dnn/blob/master/verilog/iladata.vcd
4 https://github.com/b06902044/computer_architecture/blob/main/CPU.vcd https://github.com/charlycop/VLSI-1/blob/master/EXEC/ALU/alu.vcd https://raw.githubusercontent.com/sathyapriyanka/APB_UVC_UVM/main/Apb_slave_uvm_new.vcd https://github.com/DarthSkipper/myHDL_Sigmoid/blob/master/out/testbench/sigmoid_tb.vcd https://github.com/pabloec1729/Hashes-generator/blob/master/RTL/velocidad/test.vcd

View file

@ -1,77 +0,0 @@
// Copyright (C) 2022 Yehowshua Immanuel
// This program is distributed under both the GPLV3 license
// and the YEHOWSHUA license, both of which can be found at
// the root of the folder containing the sources for this program.
// TODO: we should eventually be able to only test on just
// the files const
pub const FILES: [&str; 31] = [
"./tests/vcd-files/aldec/SPI_Write.vcd",
"./tests/vcd-files/ghdl/alu.vcd",
"./tests/vcd-files/ghdl/idea.vcd",
"./tests/vcd-files/ghdl/pcpu.vcd",
"./tests/vcd-files/gtkwave-analyzer/perm_current.vcd",
"./tests/vcd-files/icarus/CPU.vcd",
"./tests/vcd-files/icarus/rv32_soc_TB.vcd",
"./tests/vcd-files/icarus/test1.vcd",
"./tests/vcd-files/model-sim/CPU_Design.msim.vcd",
"./tests/vcd-files/model-sim/clkdiv2n_tb.vcd",
"./tests/vcd-files/my-hdl/Simple_Memory.vcd",
"./tests/vcd-files/my-hdl/sigmoid_tb.vcd",
"./tests/vcd-files/my-hdl/top.vcd",
"./tests/vcd-files/ncsim/ffdiv_32bit_tb.vcd",
"./tests/vcd-files/quartus/mipsHardware.vcd",
"./tests/vcd-files/quartus/wave_registradores.vcd",
"./tests/vcd-files/questa-sim/dump.vcd",
"./tests/vcd-files/questa-sim/test.vcd",
"./tests/vcd-files/riviera-pro/dump.vcd",
"./tests/vcd-files/systemc/waveform.vcd",
"./tests/vcd-files/treadle/GCD.vcd",
"./tests/vcd-files/vcs/Apb_slave_uvm_new.vcd",
"./tests/vcd-files/vcs/datapath_log.vcd",
"./tests/vcd-files/vcs/processor.vcd",
"./tests/vcd-files/verilator/swerv1.vcd",
"./tests/vcd-files/verilator/vlt_dump.vcd",
"./tests/vcd-files/vivado/iladata.vcd",
"./tests/vcd-files/xilinx_isim/test.vcd",
"./tests/vcd-files/xilinx_isim/test1.vcd",
// TODO : add signal ignore list to handle bitwidth mismatches
"./tests/vcd-files/xilinx_isim/test2x2_regex22_string1.vcd",
"./tests/vcd-files/scope_with_comment.vcd",
];
pub const GOOD_DATE_FILES: [&str; 24] = [
"./test-vcd-files/aldec/SPI_Write.vcd",
"./test-vcd-files/ghdl/alu.vcd",
"./test-vcd-files/ghdl/idea.vcd",
"./test-vcd-files/ghdl/pcpu.vcd",
"./test-vcd-files/gtkwave-analyzer/perm_current.vcd",
"./test-vcd-files/icarus/CPU.vcd",
"./test-vcd-files/icarus/rv32_soc_TB.vcd",
"./test-vcd-files/icarus/test1.vcd",
"./test-vcd-files/model-sim/CPU_Design.msim.vcd",
"./test-vcd-files/model-sim/clkdiv2n_tb.vcd",
"./test-vcd-files/my-hdl/Simple_Memory.vcd",
"./test-vcd-files/my-hdl/sigmoid_tb.vcd",
"./test-vcd-files/my-hdl/top.vcd",
"./test-vcd-files/questa-sim/dump.vcd",
"./test-vcd-files/questa-sim/test.vcd",
"./test-vcd-files/riviera-pro/dump.vcd",
"./test-vcd-files/vcs/Apb_slave_uvm_new.vcd",
"./test-vcd-files/vcs/datapath_log.vcd",
"./test-vcd-files/vcs/processor.vcd",
"./test-vcd-files/verilator/swerv1.vcd",
"./test-vcd-files/verilator/vlt_dump.vcd",
"./test-vcd-files/xilinx_isim/test.vcd",
"./test-vcd-files/xilinx_isim/test1.vcd",
"./test-vcd-files/xilinx_isim/test2x2_regex22_string1.vcd",
];
pub const BAD_DATE_FILES: [&str; 6] = [
"./test-vcd-files/ncsim/ffdiv_32bit_tb.vcd",
"./test-vcd-files/quartus/mipsHardware.vcd",
"./test-vcd-files/quartus/wave_registradores.vcd",
"./test-vcd-files/systemc/waveform.vcd",
"./test-vcd-files/treadle/GCD.vcd",
"./test-vcd-files/vivado/iladata.vcd",
];

View file

@ -1,22 +0,0 @@
// Copyright (C) 2022 Yehowshua Immanuel
// This program is distributed under both the GPLV3 license
// and the YEHOWSHUA license, both of which can be found at
// the root of the folder containing the sources for this program.
use std::fs::File;
mod files;
use files::*;
#[test]
fn parse_all_VCDs() {
// see if we can parse all signal trees successfully
for file_name in FILES {
let file = File::open(file_name).unwrap();
let vcd = fastwave_backend::parse_vcd(file);
if !vcd.is_ok() {
dbg!(file_name);
vcd.unwrap();
}
}
}

View file

@ -1,92 +0,0 @@
$date
Thu Dec 17 17:19:03 2020
$end
$version
Aldec HDL Simulator Version 10.03.3558
$end
$timescale
1 ps
$end
$scope module tb $end
$scope module t $end
$var wire 1 ! CLK $end
$var wire 1 " LED $end
$var wire 1 # PIN_10 $end
$var wire 1 $ PIN_11 $end
$var wire 1 % PIN_12 $end
$var wire 1 & PIN_13 $end
$var wire 1 ' SPI_In $end
$var wire 1 ( SPI_Out $end
$var wire 1 ) SPI_Data_Available $end
$var wire 1 * RegMap_In $end
$var wire 1 + RegMap_Out $end
$var wire 1 , RegMap_Data_Available $end
$var wire 8 - AddrBus [7:0] $end
$var wire 8 . DataBus [7:0] $end
$scope module controller $end
$var wire 1 ! clk $end
$var wire 1 " LED $end
$var wire 8 . DataBus [7:0] $end
$var wire 1 ) SPI_Data_Available $end
$var wire 1 , RegMap_Data_available $end
$var wire 8 / addr [7:0] $end
$var wire 8 0 data [7:0] $end
$var wire 1 1 BusActive $end
$var reg 8 2 AddrBus [7:0] $end
$var reg 1 3 SPI_In $end
$var reg 1 4 SPI_Out $end
$var reg 1 5 RegMap_In $end
$var reg 1 6 RegMap_Out $end
$var reg 1 7 LED_state $end
$var reg 3 8 block [2:0] $end
$var reg 3 9 doing [2:0] $end
$var parameter 3 : IDLE $end
$var parameter 3 ; READ_ADDR $end
$var parameter 3 < READ_DATA $end
$var parameter 3 = TX $end
$var parameter 3 > SPI $end
$upscope $end
$scope module reg_mag_i $end
$var wire 1 ! clk $end
$var wire 1 + RegMap_Out $end
$var wire 1 * RegMap_In $end
$var wire 8 - AddrBus [7:0] $end
$var wire 8 . DataBus [7:0] $end
$var wire 1 ? r_w $end
$var wire 1 @ outputData $end
$var wire 1 A inputData $end
$var reg 1 B RegMap_Data_Available $end
$var reg 8 C inData [7:0] $end
$var reg 8 D inAddr [7:0] $end
$var reg 8 E outData [7:0] $end
$var reg 1 F addr_rcv $end
$var reg 1 G data_rcv $end
$var reg 2 H state [1:0] $end
$var parameter 2 I INIT $end
$var parameter 2 J IDLE $end
$var parameter 2 K RX $end
$var parameter 2 L TX $end
$var parameter 32 M MAXADDRESS $end
$upscope $end
$scope module SPI_i $end
$var wire 1 ! clk $end
$var wire 1 # SCK $end
$var wire 1 $ SSEL $end
$var wire 1 % MOSI $end
$var wire 1 & MISO $end
$var wire 1 ' SPI_In $end
$var wire 1 ( SPI_Out $end
$var wire 8 - AddrBus [7:0] $end
$var wire 8 . DataBus [7:0] $end
$var wire 1 N SCK_risingedge $end
$var wire 1 O SCK_fallingedge $end
$var wire 1 P SSEL_active $end
$var wire 1 Q MOSI_data $end
$var reg 1 R SPI_Data_Available $end

View file

@ -1,296 +0,0 @@
$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*

View file

@ -1,17 +0,0 @@
Icarus,https://github.com/dpretet/vcd/blob/master/test1.vcd,https://github.com/ombhilare999/riscv-core/blob/master/src/rv32_soc_TB.vcd,https://github.com/b06902044/computer_architecture/blob/main/CPU.vcd
Verilator,https://github.com/wavedrom/vcd-samples/blob/trunk/swerv1.vcd,https://github.com/bigBrain1901/nPOWER-ISA-5-STAGE-PIPELINED-CPU/blob/master/post_compile_files/vlt_dump.vcd,
GHDL,https://raw.githubusercontent.com/AdoobII/idea_21s/main/vhdl/idea.vcd,https://github.com/gaoqqt2n/CPU/blob/master/SuperPipelineCPU/vcdfile/pcpu.vcd,https://github.com/charlycop/VLSI-1/blob/master/EXEC/ALU/alu.vcd
VCS,https://raw.githubusercontent.com/ameyjain/8-bit-Microprocessor/master/8-bit%20microprocessor/processor.vcd,https://raw.githubusercontent.com/Akashay-Singla/RISC-V/main/Pipeline/datapath_log.vcd,https://raw.githubusercontent.com/sathyapriyanka/APB_UVC_UVM/main/Apb_slave_uvm_new.vcd
QuestaSim,https://github.com/mr-gaurav/Sequence-Counter/blob/main/test.vcd,https://github.com/SparshAgarwal/Computer-Architecture/blob/master/hw3/hw3_1/dump.vcd,
ModelSim,https://github.com/Mohammad-Heydariii/Digital-Systems-Lab-Course/blob/main/Lab_project4/modelsim_files/clkdiv2n_tb.vcd,https://github.com/sh619/Songyu_Huang-Chisel/blob/main/MU0_final_version/simulation/qsim/CPU_Design.msim.vcd,
Quartus,https://github.com/PedroTLemos/ProjetoInfraHard/blob/master/mipsHardware.vcd,,
SystemC,https://github.com/jroslindo/Mips-Systemc/blob/main/REGISTRADORES_32_bits/wave_registradores.vcd,https://github.com/amrhas/PDRNoC/blob/VCRouter/noctweak/Debug/waveform.vcd.vcd,
Treadle,https://github.com/chipsalliance/treadle/blob/master/src/test/resources/GCD.vcd,,
Aldec,https://github.com/SVeilleux9/FPGA-GPIO-Extender/blob/main/Firmware/aldec/SPI_Write/SPI_Write.vcd,,
Riviera-PRO,https://github.com/prathampathak/Tic-Tac-Tao/blob/main/dump.vcd,,
MyHDL,https://github.com/aibtw/myHdl_Projects/blob/main/SimpleMemory/Simple_Memory.vcd,https://github.com/Abhishek010397/Programming-RISC-V/blob/master/top.vcd,https://github.com/DarthSkipper/myHDL_Sigmoid/blob/master/out/testbench/sigmoid_tb.vcd
ncsim,https://github.com/amiteee78/RTL_design/blob/master/ffdiv_32bit/ffdiv_32bit_prop_binom/run_cad/ffdiv_32bit_tb.vcd,,
xilinx_isim,https://github.com/mukul54/qrs-peak-fpga/blob/master/utkarsh/utkarsh.sim/sim_1/behav/xsim/test.vcd,https://github.com/DanieleParravicini/regex_coprocessor/blob/master/scripts/sim/test2x2_regex22_string1.vcd,https://github.com/pabloec1729/Hashes-generator/blob/master/RTL/velocidad/test.vcd
vivado,https://github.com/saharmalmir/Eth2Ser/blob/master/UART2ETH.runs/impl_1/iladata.vcd,https://github.com/BradMcDanel/multiplication-free-dnn/blob/master/verilog/iladata.vcd,
GTKWave-Analyzer,https://github.com/Asfagus/Network-Switch/blob/main/perm_current.vcd,,
Amaranth,Locally Simulated File,,
1 Icarus https://github.com/dpretet/vcd/blob/master/test1.vcd https://github.com/ombhilare999/riscv-core/blob/master/src/rv32_soc_TB.vcd https://github.com/b06902044/computer_architecture/blob/main/CPU.vcd
2 Verilator https://github.com/wavedrom/vcd-samples/blob/trunk/swerv1.vcd https://github.com/bigBrain1901/nPOWER-ISA-5-STAGE-PIPELINED-CPU/blob/master/post_compile_files/vlt_dump.vcd
3 GHDL https://raw.githubusercontent.com/AdoobII/idea_21s/main/vhdl/idea.vcd https://github.com/gaoqqt2n/CPU/blob/master/SuperPipelineCPU/vcdfile/pcpu.vcd https://github.com/charlycop/VLSI-1/blob/master/EXEC/ALU/alu.vcd
4 VCS https://raw.githubusercontent.com/ameyjain/8-bit-Microprocessor/master/8-bit%20microprocessor/processor.vcd https://raw.githubusercontent.com/Akashay-Singla/RISC-V/main/Pipeline/datapath_log.vcd https://raw.githubusercontent.com/sathyapriyanka/APB_UVC_UVM/main/Apb_slave_uvm_new.vcd
5 QuestaSim https://github.com/mr-gaurav/Sequence-Counter/blob/main/test.vcd https://github.com/SparshAgarwal/Computer-Architecture/blob/master/hw3/hw3_1/dump.vcd
6 ModelSim https://github.com/Mohammad-Heydariii/Digital-Systems-Lab-Course/blob/main/Lab_project4/modelsim_files/clkdiv2n_tb.vcd https://github.com/sh619/Songyu_Huang-Chisel/blob/main/MU0_final_version/simulation/qsim/CPU_Design.msim.vcd
7 Quartus https://github.com/PedroTLemos/ProjetoInfraHard/blob/master/mipsHardware.vcd
8 SystemC https://github.com/jroslindo/Mips-Systemc/blob/main/REGISTRADORES_32_bits/wave_registradores.vcd https://github.com/amrhas/PDRNoC/blob/VCRouter/noctweak/Debug/waveform.vcd.vcd
9 Treadle https://github.com/chipsalliance/treadle/blob/master/src/test/resources/GCD.vcd
10 Aldec https://github.com/SVeilleux9/FPGA-GPIO-Extender/blob/main/Firmware/aldec/SPI_Write/SPI_Write.vcd
11 Riviera-PRO https://github.com/prathampathak/Tic-Tac-Tao/blob/main/dump.vcd
12 MyHDL https://github.com/aibtw/myHdl_Projects/blob/main/SimpleMemory/Simple_Memory.vcd https://github.com/Abhishek010397/Programming-RISC-V/blob/master/top.vcd https://github.com/DarthSkipper/myHDL_Sigmoid/blob/master/out/testbench/sigmoid_tb.vcd
13 ncsim https://github.com/amiteee78/RTL_design/blob/master/ffdiv_32bit/ffdiv_32bit_prop_binom/run_cad/ffdiv_32bit_tb.vcd
14 xilinx_isim https://github.com/mukul54/qrs-peak-fpga/blob/master/utkarsh/utkarsh.sim/sim_1/behav/xsim/test.vcd https://github.com/DanieleParravicini/regex_coprocessor/blob/master/scripts/sim/test2x2_regex22_string1.vcd https://github.com/pabloec1729/Hashes-generator/blob/master/RTL/velocidad/test.vcd
15 vivado https://github.com/saharmalmir/Eth2Ser/blob/master/UART2ETH.runs/impl_1/iladata.vcd https://github.com/BradMcDanel/multiplication-free-dnn/blob/master/verilog/iladata.vcd
16 GTKWave-Analyzer https://github.com/Asfagus/Network-Switch/blob/main/perm_current.vcd
17 Amaranth Locally Simulated File