Compare commits
19 commits
revert-27-
...
main
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f2676673cd | ||
![]() |
133b1a2693 | ||
![]() |
de897a5010 | ||
![]() |
e2e3541e3f | ||
![]() |
7d414f36dd | ||
![]() |
9a8c2a03eb | ||
![]() |
3851c4e06c | ||
![]() |
9ba53df728 | ||
![]() |
7a2bed42a3 | ||
![]() |
d42d01f9c3 | ||
![]() |
f3e45f8497 | ||
![]() |
e7f2f661df | ||
![]() |
4f31ec842f | ||
![]() |
a8ee52a11e | ||
![]() |
e3c60600f4 | ||
![]() |
9c54c3a295 | ||
![]() |
b9c507c9d8 | ||
![]() |
b886e5d26d | ||
![]() |
01eacd4028 |
|
@ -10,7 +10,7 @@ debug = 1
|
|||
|
||||
[dependencies]
|
||||
num = "0.4"
|
||||
clap = { version = "3.1.8", features = ["derive"] }
|
||||
clap = { version = "4.4.6", features = ["derive"] }
|
||||
chrono = "0.4"
|
||||
# TODO : remove itertools once date parser is reworked.
|
||||
itertools = "0.10.3"
|
||||
itertools = "0.11"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
Copyright - Yehowshua Immanuel
|
||||
Copyright(2023) - Yehowshua Immanuel
|
||||
|
||||
# Vision
|
||||
Imagine being able to visualize a CPU pipeline diagram by merely loading a simulation waveform dump, sprinkling in a bit of code, and dragging and dropping some diagram blocks into the visualizer. This project aims to offer such an experience.
|
||||
|
@ -6,10 +6,7 @@ Imagine being able to visualize a CPU pipeline diagram by merely loading a simul
|
|||
Since this project is written in Rust, it should also be able to run in the browser via web-assembly.
|
||||
|
||||
# Status
|
||||
I hope to work on this more actively again soon.
|
||||
|
||||
The Zoq is is working on an excellent frontend call the Surfer. Check it out
|
||||
[here](https://gitlab.com/surfer-project/surfer)!
|
||||
As of January 2024, work on the Fastwave Backend is stalled. It has been a fun journey watching Fastwave enable the first iterations of the [surfer waveform viewer](https://surfer-project.org). Now surfer uses an even better backend called [Wellen](https://github.com/ekiwi/wellen?tab=readme-ov-file). Go check it out! I hear it's really good. Perhaps I will soon archive the Fastwave Backend.
|
||||
|
||||
Browser demo: https://app.surfer-project.org/
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ use fastwave_backend::parse_vcd;
|
|||
#[derive(Parser)]
|
||||
struct Cli {
|
||||
/// The path to the file to read
|
||||
#[clap(parse(from_os_str))]
|
||||
path: std::path::PathBuf,
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ fn visit_all_scopes(vcd: &VCD) {
|
|||
let SignalIdx(idx) = signal_idx;
|
||||
indented_print(indent + 1, &format!("{},{}", signal.name(), idx));
|
||||
}
|
||||
visit_all_scope_children(child_scope_idx, vcd.clone(), indent + 1);
|
||||
visit_all_scope_children(child_scope_idx, vcd, indent + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
mod vcd;
|
||||
pub use vcd::parse::parse_vcd;
|
||||
pub use vcd::signal::{Signal, SignalType, SignalValue};
|
||||
pub use vcd::signal::{Signal, SignalType, SignalValue, SignalErrors};
|
||||
pub use vcd::types::{Metadata, Timescale, Version};
|
||||
pub use vcd::types::{ScopeIdx, SignalIdx, VCD};
|
||||
|
||||
|
|
|
@ -71,10 +71,10 @@ pub(super) fn parse_events<R: std::io::Read>(
|
|||
// handle the case of an n bit signal whose value must be parsed
|
||||
"b" => {
|
||||
let binary_value = &word[1..];
|
||||
let observed_num_bits = u16::try_from(binary_value.len()).map_err(|_| {
|
||||
let observed_num_bits = u32::try_from(binary_value.len()).map_err(|_| {
|
||||
format!(
|
||||
"Error near {}:{}, {cursor:?}. \
|
||||
Found signal with more than 2^16 - 1 bits.",
|
||||
Found signal with more than 2^32 - 1 bits.",
|
||||
file!(),
|
||||
line!()
|
||||
)
|
||||
|
@ -200,11 +200,11 @@ pub(super) fn parse_events<R: std::io::Read>(
|
|||
format!("Error near {}:{}. num_bytes empty.", file!(), line!())
|
||||
})?;
|
||||
let mut curr_num_bytes =
|
||||
u8::try_from(value_u8.len()).map_err(|_| {
|
||||
u16::try_from(value_u8.len()).map_err(|_| {
|
||||
format!(
|
||||
"Error near {}:{}. \
|
||||
Found signal {name} with with value change of greater \
|
||||
than 2^16 - 1 bits on {cursor:?}.",
|
||||
than 2^32 - 1 bits on {cursor:?}.",
|
||||
file!(),
|
||||
line!()
|
||||
)
|
||||
|
@ -492,7 +492,8 @@ pub(super) fn parse_events<R: std::io::Read>(
|
|||
}
|
||||
}?;
|
||||
}
|
||||
"s" => {
|
||||
// Store real values as a string as well and let the user parse it to an f64
|
||||
"s" | " S" | "r" | "R" => {
|
||||
let val = word[1..].to_string();
|
||||
let (hash, cursor) = next_word!(word_reader)?;
|
||||
// lokup signal idx
|
||||
|
|
|
@ -104,10 +104,10 @@ pub(super) fn parse_var<R: std::io::Read>(
|
|||
let num_bits = word
|
||||
.parse::<usize>()
|
||||
.unwrap_or_else(|_| panic!("{}", parse_err));
|
||||
let num_bits = u16::try_from(num_bits).map_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^16 - 1 bits.",
|
||||
This signal has {num_bits} > 2^32 - 1 bits.",
|
||||
file!(),
|
||||
line!()
|
||||
)
|
||||
|
@ -126,10 +126,12 @@ pub(super) fn parse_var<R: std::io::Read>(
|
|||
// $var parameter 3 a IDLE $end
|
||||
// ^^^^ - full_signal_name(can extend until $end)
|
||||
let mut full_signal_name = Vec::<String>::new();
|
||||
let mut size = None;
|
||||
loop {
|
||||
let (word, _) = next_word!(word_reader)?;
|
||||
match word {
|
||||
"$end" => break,
|
||||
other if other.starts_with('[') => size = Some(other.to_string()),
|
||||
_ => full_signal_name.push(word.to_string()),
|
||||
}
|
||||
}
|
||||
|
@ -170,6 +172,7 @@ pub(super) fn parse_var<R: std::io::Read>(
|
|||
.chain([full_signal_name])
|
||||
.collect::<Vec<String>>(),
|
||||
signal_type: var_type,
|
||||
index: size,
|
||||
signal_error: None,
|
||||
num_bits,
|
||||
num_bytes,
|
||||
|
@ -286,87 +289,154 @@ fn parse_scopes_inner<R: std::io::Read>(
|
|||
// $scope module reg_mag_i $end
|
||||
// ^^^^^^^^^ - scope name
|
||||
let (scope_name, _) = next_word!(word_reader)?;
|
||||
// In some cases there are VCD files which have scopes without names.
|
||||
// since these occur in the wild, we'll tolerate them even if it is unclear
|
||||
// if it is supported or not by the spec.
|
||||
if scope_name != "$end" {
|
||||
let mut path = path.clone();
|
||||
path.push(scope_name.to_string());
|
||||
|
||||
let mut path = path.clone();
|
||||
path.push(scope_name.to_string());
|
||||
let curr_scope_idx = ScopeIdx(vcd.all_scopes.len());
|
||||
|
||||
let curr_scope_idx = ScopeIdx(vcd.all_scopes.len());
|
||||
|
||||
// register this scope as a child of the current parent scope
|
||||
// if there is a parent scope, or else we register this scope as
|
||||
// root scope
|
||||
match parent_scope_idx {
|
||||
Some(ScopeIdx(parent_scope_idx)) => {
|
||||
let parent_scope = vcd.all_scopes.get_mut(parent_scope_idx).unwrap();
|
||||
parent_scope.child_scopes.push(curr_scope_idx);
|
||||
// register this scope as a child of the current parent scope
|
||||
// if there is a parent scope, or else we register this scope as
|
||||
// root scope
|
||||
match parent_scope_idx {
|
||||
Some(ScopeIdx(parent_scope_idx)) => {
|
||||
let parent_scope = vcd.all_scopes.get_mut(parent_scope_idx).unwrap();
|
||||
parent_scope.child_scopes.push(curr_scope_idx);
|
||||
}
|
||||
None => vcd.root_scopes.push(curr_scope_idx),
|
||||
}
|
||||
None => vcd.root_scopes.push(curr_scope_idx),
|
||||
}
|
||||
|
||||
// add this scope to list of existing scopes
|
||||
vcd.all_scopes.push(Scope {
|
||||
name: scope_name.to_string(),
|
||||
self_idx: curr_scope_idx,
|
||||
child_signals: vec![],
|
||||
child_scopes: vec![],
|
||||
});
|
||||
// add this scope to list of existing scopes
|
||||
vcd.all_scopes.push(Scope {
|
||||
name: scope_name.to_string(),
|
||||
self_idx: curr_scope_idx,
|
||||
child_signals: vec![],
|
||||
child_scopes: vec![],
|
||||
});
|
||||
|
||||
// $scope module reg_mag_i $end
|
||||
// ^^^^ - end keyword
|
||||
ident(word_reader, "$end")?;
|
||||
// $scope module reg_mag_i $end
|
||||
// ^^^^ - end keyword
|
||||
ident(word_reader, "$end")?;
|
||||
|
||||
loop {
|
||||
let (word, cursor) = next_word!(word_reader)?;
|
||||
let ParseResult { matched, residual } = tag(word, "$");
|
||||
match matched {
|
||||
// we hope that this word starts with a `$`
|
||||
"$" => {
|
||||
match residual {
|
||||
"scope" => {
|
||||
// recursive - parse inside of current scope tree
|
||||
parse_scopes_inner(
|
||||
word_reader,
|
||||
Some(curr_scope_idx),
|
||||
vcd,
|
||||
signal_map,
|
||||
&path,
|
||||
)?;
|
||||
}
|
||||
"var" => {
|
||||
parse_var(word_reader, curr_scope_idx, vcd, signal_map, &path)?;
|
||||
}
|
||||
"upscope" => {
|
||||
ident(word_reader, "$end")?;
|
||||
break;
|
||||
}
|
||||
// we ignore comments
|
||||
"comment" => loop {
|
||||
if ident(word_reader, "$end").is_ok() {
|
||||
loop {
|
||||
let (word, cursor) = next_word!(word_reader)?;
|
||||
let ParseResult { matched, residual } = tag(word, "$");
|
||||
match matched {
|
||||
// we hope that this word starts with a `$`
|
||||
"$" => {
|
||||
match residual {
|
||||
"scope" => {
|
||||
// recursive - parse inside of current scope tree
|
||||
parse_scopes_inner(
|
||||
word_reader,
|
||||
Some(curr_scope_idx),
|
||||
vcd,
|
||||
signal_map,
|
||||
&path,
|
||||
)?;
|
||||
}
|
||||
"var" => {
|
||||
parse_var(word_reader, curr_scope_idx, vcd, signal_map, &path)?;
|
||||
}
|
||||
"upscope" => {
|
||||
ident(word_reader, "$end")?;
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
let err = format!(
|
||||
"Error near {}:{}. \
|
||||
found keyword `{residual}` but expected \
|
||||
`$scope`, `$var`, `$comment`, or `$upscope` \
|
||||
on {cursor:?}",
|
||||
file!(),
|
||||
line!()
|
||||
);
|
||||
return Err(err);
|
||||
// we ignore comments
|
||||
"comment" => loop {
|
||||
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);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
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 {
|
||||
let (word, cursor) = next_word!(word_reader)?;
|
||||
let ParseResult { matched, residual } = tag(word, "$");
|
||||
match matched {
|
||||
// we hope that this word starts with a `$`
|
||||
"$" => {
|
||||
match residual {
|
||||
"scope" => {
|
||||
// recursive - parse inside of current scope tree
|
||||
parse_scopes_inner(
|
||||
word_reader,
|
||||
parent_scope_idx,
|
||||
vcd,
|
||||
signal_map,
|
||||
&path,
|
||||
)?;
|
||||
}
|
||||
"upscope" => {
|
||||
ident(word_reader, "$end")?;
|
||||
break;
|
||||
}
|
||||
// we ignore comments
|
||||
"comment" => loop {
|
||||
if ident(word_reader, "$end").is_ok() {
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
let err = format!(
|
||||
"Error near {}:{}. \
|
||||
found keyword `{residual}` in annonyoums scope but expected \
|
||||
`$scope`, `$comment`, or `$upscope` \
|
||||
on {cursor:?}",
|
||||
file!(),
|
||||
line!()
|
||||
);
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let err = format!(
|
||||
"Error near {}:{}. \
|
||||
found keyword `{matched}` but \
|
||||
expected `$` on {cursor:?}",
|
||||
file!(),
|
||||
line!()
|
||||
);
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ use num::BigUint;
|
|||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct LsbIdxOfTmstmpValOnTmln(pub(super) u32);
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||
pub enum SignalType {
|
||||
Event,
|
||||
Integer,
|
||||
|
@ -40,6 +40,11 @@ pub enum SignalValue {
|
|||
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> {
|
||||
|
@ -48,6 +53,16 @@ impl<'a> Signal<'a> {
|
|||
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,
|
||||
|
@ -67,11 +82,14 @@ impl<'a> Signal<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn num_bits(&self) -> Option<u16> {
|
||||
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,
|
||||
|
@ -80,32 +98,33 @@ impl<'a> Signal<'a> {
|
|||
let Signal(signal_enum) = &self;
|
||||
signal_enum
|
||||
.query_string_val_on_tmln(desired_time, &vcd.tmstmps_encoded_as_u8s, &vcd.all_signals)
|
||||
.map(|(val, _)| val)
|
||||
.map(|QueryResult{current, next: _}| current.map(|c| c.1))
|
||||
}
|
||||
|
||||
pub fn query_num_val_on_tmln(
|
||||
&self,
|
||||
desired_time: &BigUint,
|
||||
vcd: &types::VCD,
|
||||
) -> Result<BigUint, SignalErrors> {
|
||||
) -> 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(|(val, _)| val)
|
||||
.map(|QueryResult{current, next: _}| current.map(|c| c.1))
|
||||
}
|
||||
*/
|
||||
|
||||
pub fn query_val_on_tmln(
|
||||
&self,
|
||||
desired_time: &BigUint,
|
||||
vcd: &types::VCD,
|
||||
) -> Result<(TimeStamp, SignalValue), SignalErrors> {
|
||||
) -> Result<QueryResult<SignalValue>, SignalErrors> {
|
||||
let Signal(signal_enum) = &self;
|
||||
let num_val = signal_enum.query_num_val_on_tmln(
|
||||
let num_query_out = signal_enum.query_num_val_on_tmln(
|
||||
desired_time,
|
||||
&vcd.tmstmps_encoded_as_u8s,
|
||||
&vcd.all_signals,
|
||||
);
|
||||
let str_val = signal_enum.query_string_val_on_tmln(
|
||||
let str_query_out = signal_enum.query_string_val_on_tmln(
|
||||
desired_time,
|
||||
&vcd.tmstmps_encoded_as_u8s,
|
||||
&vcd.all_signals,
|
||||
|
@ -114,16 +133,44 @@ impl<'a> Signal<'a> {
|
|||
// 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_val, str_val) {
|
||||
(Ok((num_val, num_time)), Ok((str_val, str_time))) => {
|
||||
if num_time > str_time {
|
||||
Ok((num_time, SignalValue::BigUint(num_val)))
|
||||
} else {
|
||||
Ok((str_time, SignalValue::String(str_val)))
|
||||
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,
|
||||
}),
|
||||
}
|
||||
}
|
||||
(Ok((num_val, time)), Err(_)) => Ok((time, SignalValue::BigUint(num_val))),
|
||||
(Err(_), Ok((str_val, time))) => Ok((time, SignalValue::String(str_val))),
|
||||
(_e, Err(e)) => Err(e),
|
||||
(Err(e), _e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
@ -135,13 +182,16 @@ pub(super) enum SignalEnum {
|
|||
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<u16>,
|
||||
num_bytes: Option<u8>,
|
||||
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
|
||||
|
@ -179,10 +229,6 @@ pub(super) enum SignalEnum {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub enum SignalErrors {
|
||||
PreTimeline {
|
||||
desired_time: BigUint,
|
||||
timeline_start_time: BigUint,
|
||||
},
|
||||
EmptyTimeline,
|
||||
TimelineNotMultiple,
|
||||
StrTmlnLenMismatch,
|
||||
|
@ -218,6 +264,27 @@ impl SignalEnum {
|
|||
}
|
||||
.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
|
||||
|
@ -225,12 +292,12 @@ 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: u16, name: &String) -> Result<u8, String> {
|
||||
pub(super) fn bytes_required(num_bits: u32, name: &String) -> Result<u16, String> {
|
||||
let bytes_required = (num_bits / 8) + if (num_bits % 8) > 0 { 1 } else { 0 };
|
||||
let bytes_required = u8::try_from(bytes_required).map_err(|_| {
|
||||
let bytes_required = u16::try_from(bytes_required).map_err(|_| {
|
||||
format!(
|
||||
"Error near {}:{}. Signal {name} of length num_bits requires \
|
||||
{bytes_required} > 256 bytes.",
|
||||
{bytes_required} > 65536 bytes.",
|
||||
file!(),
|
||||
line!()
|
||||
)
|
||||
|
@ -343,7 +410,7 @@ impl SignalEnum {
|
|||
Ok((timestamp, signal_val))
|
||||
}
|
||||
|
||||
fn bits_required(&self) -> Option<u16> {
|
||||
fn bits_required(&self) -> Option<u32> {
|
||||
match self {
|
||||
SignalEnum::Data { num_bits, .. } => *num_bits,
|
||||
// TODO: Follow aliases?
|
||||
|
@ -362,7 +429,7 @@ impl SignalEnum {
|
|||
desired_time: &BigUint,
|
||||
tmstmps_encoded_as_u8s: &Vec<u8>,
|
||||
all_signals: &Vec<SignalEnum>,
|
||||
) -> Result<(String, TimeStamp), SignalErrors> {
|
||||
) -> Result<QueryResult<String>, SignalErrors> {
|
||||
let signal_idx = match self {
|
||||
Self::Data { self_idx, .. } => {
|
||||
let SignalIdx(idx) = self_idx;
|
||||
|
@ -396,7 +463,10 @@ impl SignalEnum {
|
|||
// this signal should at least have some events, otherwise, trying to index into
|
||||
// an empty vector later on would fail
|
||||
if lsb_indxs_of_string_tmstmp_vals_on_tmln.is_empty() {
|
||||
return Err(SignalErrors::EmptyTimeline);
|
||||
return Ok(QueryResult {
|
||||
current: None,
|
||||
next: None
|
||||
});
|
||||
}
|
||||
|
||||
// the vector of string timeline lsb indices should have the same
|
||||
|
@ -410,9 +480,9 @@ impl SignalEnum {
|
|||
let (timeline_start_time, _) =
|
||||
self.time_and_str_val_at_event_idx(0, tmstmps_encoded_as_u8s)?;
|
||||
if *desired_time < timeline_start_time {
|
||||
return Err(SignalErrors::PreTimeline {
|
||||
desired_time: desired_time.clone(),
|
||||
timeline_start_time,
|
||||
return Ok(QueryResult {
|
||||
current: None,
|
||||
next: Some(timeline_start_time),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -424,7 +494,10 @@ impl SignalEnum {
|
|||
// check if we're requesting a value that occurs beyond the end of the timeline,
|
||||
// if so, return the last value in this timeline
|
||||
if *desired_time > timeline_end_time {
|
||||
return Ok((timeline_end_val.to_string(), 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),
|
||||
|
@ -443,7 +516,21 @@ impl SignalEnum {
|
|||
lower_idx = mid_idx + 1;
|
||||
}
|
||||
std::cmp::Ordering::Equal => {
|
||||
return Ok((curr_val.to_string(), curr_time));
|
||||
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;
|
||||
|
@ -466,14 +553,17 @@ impl SignalEnum {
|
|||
});
|
||||
}
|
||||
|
||||
Ok((left_val.to_string(), left_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<(BigUint, TimeStamp), SignalErrors> {
|
||||
) -> Result<QueryResult<BigUint>, SignalErrors> {
|
||||
let signal_idx = match self {
|
||||
Self::Data { self_idx, .. } => {
|
||||
let SignalIdx(idx) = self_idx;
|
||||
|
@ -518,7 +608,10 @@ impl SignalEnum {
|
|||
// 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 Err(SignalErrors::EmptyTimeline);
|
||||
return Ok(QueryResult {
|
||||
current: None,
|
||||
next: None
|
||||
});
|
||||
}
|
||||
|
||||
// assertion that value_sequence is a proper multiple of
|
||||
|
@ -541,9 +634,9 @@ impl SignalEnum {
|
|||
let (timeline_start_time, _) =
|
||||
self.time_and_num_val_at_event_idx(0, tmstmps_encoded_as_u8s)?;
|
||||
if *desired_time < timeline_start_time {
|
||||
return Err(SignalErrors::PreTimeline {
|
||||
desired_time: desired_time.clone(),
|
||||
timeline_start_time,
|
||||
return Ok(QueryResult {
|
||||
current: None,
|
||||
next: Some(timeline_start_time),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -555,7 +648,10 @@ impl SignalEnum {
|
|||
// check if we're requesting a value that occurs beyond the end of the timeline,
|
||||
// if so, return the last value in this timeline
|
||||
if *desired_time > timeline_end_time {
|
||||
return Ok((timeline_end_val, 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),
|
||||
|
@ -574,7 +670,21 @@ impl SignalEnum {
|
|||
lower_idx = mid_idx + 1;
|
||||
}
|
||||
std::cmp::Ordering::Equal => {
|
||||
return Ok((curr_val, curr_time));
|
||||
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;
|
||||
|
@ -597,6 +707,9 @@ impl SignalEnum {
|
|||
});
|
||||
}
|
||||
|
||||
Ok((left_val, left_time))
|
||||
return Ok(QueryResult {
|
||||
current: Some((left_time, left_val)),
|
||||
next: Some(right_time),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use chrono::prelude::{DateTime, Utc};
|
|||
use num::BigUint;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Version(pub String);
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
|
|
Loading…
Reference in a new issue