mod cat;
mod diff;
#[allow(dead_code, unused_imports, clippy::manual_repeat_n, mismatched_lifetime_syntaxes)]
mod vcd;
pub use cat::{write_signals_wave, write_signals_wave_multi, SignalOutputOptions};
pub use diff::{
compare_signal_meta, compare_signal_names, diff_wave_sets, diff_waves, open_and_read_wave_sets,
open_and_read_waves,
};
use fst_reader::{
is_fst_file, FstArrayType, FstEnumType, FstHierarchyEntry, FstPackType, FstReader,
FstVarDirection, FstVarType,
};
use std::collections::HashMap;
use std::fs::File;
use std::io::{BufRead, BufReader, Seek, Write};
use std::path::Path;
pub type SignalNames = HashMap<usize, Vec<String>>;
pub type NameId = u32;
#[derive(Debug)]
struct NameNode {
segment: String,
parent: Option<NameId>,
children: HashMap<String, NameId>,
}
fn is_simple_identifier(s: &str) -> bool {
let mut chars = s.chars();
match chars.next() {
Some(c) if c.is_ascii_alphabetic() || c == '_' => {}
_ => return false,
}
chars.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$')
}
fn split_range_suffix(s: &str) -> (&str, &str) {
if !s.ends_with(']') {
return (s, "");
}
let split_pos = s.rfind(" [").or_else(|| {
let pos = s.rfind('[')?;
if pos > 0 { Some(pos) } else { None }
});
if let Some(pos) = split_pos {
let bracket_start = s[pos..].find('[').unwrap() + pos + 1;
let content = &s[bracket_start..s.len() - 1];
let is_numeric_range = !content.is_empty()
&& content.chars().all(|c| c.is_ascii_digit() || c == ':');
if is_numeric_range {
return (&s[..pos], &s[pos..]);
}
}
(s, "")
}
fn format_segment(s: &str) -> String {
let (base, range_suffix) = split_range_suffix(s);
if is_simple_identifier(base) {
s.to_string()
} else if range_suffix.is_empty() {
format!("\\{} ", base)
} else {
format!("\\{} {}", base, &range_suffix[1..])
}
}
#[derive(Debug)]
pub struct NameTree {
nodes: Vec<NameNode>,
}
impl NameTree {
pub fn new() -> Self {
NameTree {
nodes: vec![NameNode {
segment: String::new(),
parent: None,
children: HashMap::new(),
}],
}
}
pub fn root(&self) -> NameId {
0
}
pub fn intern(&mut self, parent: NameId, segment: &str) -> NameId {
if let Some(&id) = self.nodes[parent as usize].children.get(segment) {
return id;
}
let id = self.nodes.len() as NameId;
self.nodes.push(NameNode {
segment: segment.to_string(),
parent: Some(parent),
children: HashMap::new(),
});
self.nodes[parent as usize]
.children
.insert(segment.to_string(), id);
id
}
pub fn segments(&self, leaf: NameId) -> Vec<&str> {
let mut parts = Vec::new();
let mut cur = leaf;
while let Some(parent) = self.nodes[cur as usize].parent {
parts.push(self.nodes[cur as usize].segment.as_str());
cur = parent;
}
parts.reverse();
parts
}
pub fn format_path(&self, leaf: NameId) -> String {
let segments = self.segments(leaf);
segments
.iter()
.map(|s| format_segment(s))
.collect::<Vec<_>>()
.join(".")
}
pub fn find(&self, segments: &[&str]) -> Option<NameId> {
let mut cur = self.root();
for seg in segments {
cur = *self.nodes[cur as usize].children.get(*seg)?;
}
Some(cur)
}
}
impl Default for NameTree {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct WaveHierarchy {
pub signal_map: SignalMap,
pub names: NameTree,
}
pub(crate) fn kway_merge_channels<T>(
rxs: &[crossbeam_channel::Receiver<T>],
get_time: impl Fn(&T) -> u64,
mut on_item: impl FnMut(T) -> std::io::Result<()>,
) -> std::io::Result<()> {
let mut heads: Vec<Option<T>> = rxs.iter().map(|rx| rx.recv().ok()).collect();
loop {
let min_idx = heads
.iter()
.enumerate()
.filter_map(|(i, opt)| opt.as_ref().map(|c| (i, get_time(c))))
.min_by_key(|&(_, t)| t)
.map(|(i, _)| i);
match min_idx {
Some(idx) => {
on_item(heads[idx].take().unwrap())?;
heads[idx] = rxs[idx].recv().ok();
}
None => return Ok(()),
}
}
}
const IMPLICIT_DIRECTION: &str = "implicit";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VarMeta {
pub var_type: String,
pub size: u32,
pub direction: String,
}
#[derive(Debug, Clone)]
pub struct VarEntry {
pub name: NameId,
pub meta: VarMeta,
pub attrs: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct SignalInfo {
pub vars: Vec<VarEntry>,
}
pub type SignalMap = HashMap<usize, SignalInfo>;
pub fn names_only(map: &SignalMap, tree: &NameTree) -> SignalNames {
map.iter()
.map(|(&k, info)| {
(
k,
info.vars.iter().map(|v| tree.format_path(v.name)).collect(),
)
})
.collect()
}
pub(crate) type FstFileReader = FstReader<BufReader<File>>;
#[derive(Debug, Clone, Default)]
pub struct NameOptions {
pub no_range_space: bool,
}
fn fst_var_type_str(t: FstVarType) -> &'static str {
match t {
FstVarType::Event => "event",
FstVarType::Integer => "integer",
FstVarType::Parameter => "parameter",
FstVarType::Real => "real",
FstVarType::RealParameter => "real_parameter",
FstVarType::Reg => "reg",
FstVarType::Supply0 => "supply0",
FstVarType::Supply1 => "supply1",
FstVarType::Time => "time",
FstVarType::Tri => "tri",
FstVarType::TriAnd => "triand",
FstVarType::TriOr => "trior",
FstVarType::TriReg => "trireg",
FstVarType::Tri0 => "tri0",
FstVarType::Tri1 => "tri1",
FstVarType::Wand => "wand",
FstVarType::Wire => "wire",
FstVarType::Wor => "wor",
FstVarType::Port => "port",
FstVarType::SparseArray => "sparray",
FstVarType::RealTime => "realtime",
FstVarType::GenericString => "string",
FstVarType::Bit => "bit",
FstVarType::Logic => "logic",
FstVarType::Int => "int",
FstVarType::ShortInt => "shortint",
FstVarType::LongInt => "longint",
FstVarType::Byte => "byte",
FstVarType::Enum => "enum",
FstVarType::ShortReal => "shortreal",
}
}
fn fst_direction_str(d: FstVarDirection) -> &'static str {
match d {
FstVarDirection::Implicit => IMPLICIT_DIRECTION,
FstVarDirection::Input => "input",
FstVarDirection::Output => "output",
FstVarDirection::InOut => "inout",
FstVarDirection::Buffer => "buffer",
FstVarDirection::Linkage => "linkage",
}
}
fn build_hierarchy<R: BufRead + Seek>(
fst_reader: &mut fst_reader::FstReader<R>,
options: &NameOptions,
) -> Result<(SignalMap, NameTree), String> {
let mut signal_map: SignalMap = HashMap::new();
let mut tree = NameTree::new();
let mut scope_id: NameId = tree.root();
let mut last_handle: Option<usize> = None;
let mut enum_tables: HashMap<u64, (String, Vec<(String, String)>)> = HashMap::new();
let mut enum_names: EnumNameRegistry = HashMap::new();
let mut conflict_error: Option<String> = None;
fst_reader
.read_hierarchy(|entry| match entry {
FstHierarchyEntry::Scope { name, .. } => {
scope_id = tree.intern(scope_id, &name);
last_handle = None;
}
FstHierarchyEntry::UpScope => {
scope_id = tree.nodes[scope_id as usize].parent.unwrap_or(tree.root());
last_handle = None;
}
FstHierarchyEntry::Var {
tpe,
direction,
name,
length,
handle,
..
} => {
let name = if options.no_range_space {
name.replace(" [", "[")
} else {
name
};
let leaf = tree.intern(scope_id, &name);
let idx = handle.get_index();
last_handle = Some(idx);
let size = match tpe {
FstVarType::Real | FstVarType::RealParameter | FstVarType::RealTime => 64,
_ => length,
};
let entry = signal_map.entry(idx).or_insert_with(|| SignalInfo {
vars: Vec::new(),
});
entry.vars.push(VarEntry {
name: leaf,
meta: VarMeta {
var_type: fst_var_type_str(tpe).to_string(),
size,
direction: fst_direction_str(direction).to_string(),
},
attrs: Vec::new(),
});
}
FstHierarchyEntry::EnumTable {
name,
handle,
mapping,
} => {
let mapping: Vec<(String, String)> =
mapping.into_iter().map(|(enc, val)| (val, enc)).collect();
if conflict_error.is_none() {
if let Err(e) = check_enum_conflict(&mut enum_names, &name, &mapping) {
conflict_error = Some(e);
}
}
enum_tables.insert(handle, (name, mapping));
}
FstHierarchyEntry::EnumTableRef { handle } => {
if let Some(var_idx) = last_handle {
if let Some((name, mapping)) = enum_tables.get(&handle) {
push_attr(&mut signal_map, var_idx, format_enum_attr(name, mapping));
}
}
}
FstHierarchyEntry::Array { name, array_type, left, right } => {
if let Some(var_idx) = last_handle {
push_attr(&mut signal_map, var_idx,
format_array_attr(&name, array_type, left, right));
}
}
FstHierarchyEntry::Pack { name, pack_type, value } => {
if let Some(var_idx) = last_handle {
push_attr(&mut signal_map, var_idx,
format_pack_attr(&name, pack_type, value));
}
}
FstHierarchyEntry::SVEnum { name, enum_type, value } => {
if let Some(var_idx) = last_handle {
push_attr(&mut signal_map, var_idx,
format_sv_enum_attr(&name, enum_type, value));
}
}
_ => {}
})
.map_err(|e| format!("Failed to read hierarchy: {}", e))?;
if let Some(e) = conflict_error {
return Err(e);
}
Ok((signal_map, tree))
}
pub fn write_names<W: Write>(
writer: &mut W,
handle_to_names: &SignalNames,
sort: bool,
) -> std::io::Result<()> {
let mut entries: Vec<String> = handle_to_names
.values()
.flat_map(|names| names.iter().cloned())
.collect();
if sort {
entries.sort();
}
for entry in entries {
writeln!(writer, "{}", entry)?;
}
Ok(())
}
pub struct VcdData {
parser: vcd::Parser<BufReader<File>>,
id_to_idx: HashMap<vcd::IdCode, usize>,
current_time: u64,
}
pub enum WaveReader {
Fst(Box<FstFileReader>),
Vcd(VcdData),
}
fn vcd_value_char(v: vcd::Value) -> char {
match v {
vcd::Value::V0 => '0',
vcd::Value::V1 => '1',
vcd::Value::X => 'x',
vcd::Value::Z => 'z',
}
}
pub(crate) fn next_vcd_change(vcd_data: &mut VcdData) -> Option<(u64, usize, String)> {
while let Some(Ok(cmd)) = vcd_data.parser.next() {
match cmd {
vcd::Command::Timestamp(t) => {
vcd_data.current_time = t;
}
vcd::Command::ChangeScalar(id, val) => {
if let Some(&idx) = vcd_data.id_to_idx.get(&id) {
return Some((vcd_data.current_time, idx, vcd_value_char(val).to_string()));
}
}
vcd::Command::ChangeVector(id, ref vec) => {
if let Some(&idx) = vcd_data.id_to_idx.get(&id) {
let s: String = vec.iter().map(vcd_value_char).collect();
return Some((vcd_data.current_time, idx, s));
}
}
vcd::Command::ChangeReal(id, val) => {
if let Some(&idx) = vcd_data.id_to_idx.get(&id) {
return Some((vcd_data.current_time, idx, val.to_string()));
}
}
vcd::Command::ChangeString(id, ref s) => {
if let Some(&idx) = vcd_data.id_to_idx.get(&id) {
return Some((vcd_data.current_time, idx, s.clone()));
}
}
_ => {}
}
}
None
}
type EnumTableRegistry = HashMap<i64, (String, Vec<(String, String)>)>;
type EnumNameRegistry = HashMap<String, Vec<(String, String)>>;
fn format_mapping(mapping: &[(String, String)]) -> String {
mapping.iter().map(|(k, v)| format!("{}={}", k, v)).collect::<Vec<_>>().join(" ")
}
fn check_enum_conflict(
enum_names: &mut EnumNameRegistry,
name: &str,
mapping: &[(String, String)],
) -> Result<(), String> {
if !name.contains("::") {
return Ok(());
}
match enum_names.entry(name.to_string()) {
std::collections::hash_map::Entry::Occupied(e) => {
if e.get() != mapping {
return Err(format!(
"conflicting enum definitions for '{}': [{}] vs [{}]",
name,
format_mapping(e.get()),
format_mapping(mapping),
));
}
}
std::collections::hash_map::Entry::Vacant(e) => {
e.insert(mapping.to_vec());
}
}
Ok(())
}
fn parse_vcd_enum_table(name: &str) -> Option<(String, Vec<(String, String)>)> {
let parts: Vec<&str> = name.split_whitespace().collect();
if parts.len() < 2 {
return None;
}
let table_name = parts[0].to_string();
let count: usize = parts[1].parse().ok()?;
if parts.len() < 2 + count * 2 {
return None;
}
let values = &parts[2..2 + count];
let encodings = &parts[2 + count..2 + count * 2];
let mapping: Vec<(String, String)> = values
.iter()
.zip(encodings.iter())
.map(|(v, e)| (v.to_string(), e.to_string()))
.collect();
Some((table_name, mapping))
}
fn format_enum_attr(name: &str, mapping: &[(String, String)]) -> String {
format!("enum {}: {}", name, format_mapping(mapping))
}
fn format_array_attr(name: &str, array_type: FstArrayType, left: i32, right: i32) -> String {
let subtype = match array_type {
FstArrayType::None => "none",
FstArrayType::Unpacked => "unpacked",
FstArrayType::Packed => "packed",
FstArrayType::Sparse => "sparse",
};
let arg = ((left as i64) << 32) | (right as u32 as i64);
format!("array {}: {} {}", subtype, name, arg)
}
fn format_pack_attr(name: &str, pack_type: FstPackType, value: u64) -> String {
let subtype = match pack_type {
FstPackType::None => "none",
FstPackType::Unpacked => "unpacked",
FstPackType::Packed => "packed",
FstPackType::TaggedPacked => "tagged_packed",
};
format!("class {}: {} {}", subtype, name, value)
}
fn format_sv_enum_attr(name: &str, enum_type: FstEnumType, value: u64) -> String {
let subtype = match enum_type {
FstEnumType::Integer => "integer",
FstEnumType::Bit => "bit",
FstEnumType::Logic => "logic",
FstEnumType::Int => "int",
FstEnumType::ShortInt => "shortint",
FstEnumType::LongInt => "longint",
FstEnumType::Byte => "byte",
FstEnumType::UnsignedInteger => "unsigned_integer",
FstEnumType::UnsignedBit => "unsigned_bit",
FstEnumType::UnsignedLogic => "unsigned_logic",
FstEnumType::UnsignedInt => "unsigned_int",
FstEnumType::UnsignedShortInt => "unsigned_shortint",
FstEnumType::UnsignedLongInt => "unsigned_longint",
FstEnumType::UnsignedByte => "unsigned_byte",
FstEnumType::Reg => "reg",
FstEnumType::Time => "time",
};
format!("enum {}: {} {}", subtype, name, value)
}
fn push_attr(signal_map: &mut SignalMap, handle: usize, attr: String) {
if let Some(info) = signal_map.get_mut(&handle) {
if let Some(var_entry) = info.vars.last_mut() {
var_entry.attrs.push(attr);
}
}
}
#[allow(clippy::too_many_arguments)]
fn walk_vcd_items(
items: &[vcd::ScopeItem],
scope_id: NameId,
tree: &mut NameTree,
signal_map: &mut SignalMap,
id_to_idx: &mut HashMap<vcd::IdCode, usize>,
enum_tables: &mut EnumTableRegistry,
enum_names: &mut EnumNameRegistry,
options: &NameOptions,
) -> Result<(), String> {
let mut last_idx: Option<usize> = None;
for item in items {
match item {
vcd::ScopeItem::Scope(scope) => {
let child_id = tree.intern(scope_id, &scope.identifier);
walk_vcd_items(
&scope.items,
child_id,
tree,
signal_map,
id_to_idx,
enum_tables,
enum_names,
options,
)?;
last_idx = None;
}
vcd::ScopeItem::Var(var) => {
let next_idx = id_to_idx.len();
let idx = *id_to_idx.entry(var.code).or_insert(next_idx);
last_idx = Some(idx);
let ref_name = if options.no_range_space {
var.reference.replace(" [", "[")
} else {
var.reference.clone()
};
let name = match &var.index {
Some(vcd::ReferenceIndex::BitSelect(n)) => {
format!("{} [{}]", ref_name, n)
}
Some(vcd::ReferenceIndex::Range(hi, lo)) => {
if options.no_range_space {
format!("{}[{}:{}]", ref_name, hi, lo)
} else {
format!("{} [{}:{}]", ref_name, hi, lo)
}
}
None => ref_name,
};
let leaf = tree.intern(scope_id, &name);
let entry = signal_map.entry(idx).or_insert_with(|| SignalInfo {
vars: Vec::new(),
});
entry.vars.push(VarEntry {
name: leaf,
meta: VarMeta {
var_type: var.var_type.to_string(),
size: var.size,
direction: IMPLICIT_DIRECTION.to_string(),
},
attrs: Vec::new(),
});
}
vcd::ScopeItem::Attribute(attr) => {
let is_enum_table = attr.attr_type == vcd::AttributeType::Misc
&& attr.subtype == "07";
if is_enum_table {
let name_trimmed = attr.name.trim_matches('"');
if !name_trimmed.is_empty() {
if let Some(parsed) = parse_vcd_enum_table(&attr.name) {
check_enum_conflict(enum_names, &parsed.0, &parsed.1)?;
enum_tables.insert(attr.arg, parsed.clone());
if let Some(idx) = last_idx {
push_attr(signal_map, idx, format_enum_attr(&parsed.0, &parsed.1));
}
}
} else {
if let Some(idx) = last_idx {
if let Some((name, mapping)) = enum_tables.get(&attr.arg) {
push_attr(signal_map, idx, format_enum_attr(name, mapping));
}
}
}
} else if let Some(idx) = last_idx {
let attr_str = format!("{} {}: {} {}", attr.attr_type, attr.subtype, attr.name, attr.arg);
push_attr(signal_map, idx, attr_str);
}
}
_ => {}
}
}
Ok(())
}
fn build_vcd_hierarchy(
header: &vcd::Header,
options: &NameOptions,
) -> Result<(SignalMap, HashMap<vcd::IdCode, usize>, NameTree), String> {
let mut signal_map: SignalMap = HashMap::new();
let mut id_to_idx: HashMap<vcd::IdCode, usize> = HashMap::new();
let mut tree = NameTree::new();
let mut enum_tables: EnumTableRegistry = HashMap::new();
let mut enum_names: EnumNameRegistry = HashMap::new();
walk_vcd_items(
&header.items,
tree.root(),
&mut tree,
&mut signal_map,
&mut id_to_idx,
&mut enum_tables,
&mut enum_names,
options,
)?;
Ok((signal_map, id_to_idx, tree))
}
pub fn write_attrs<W: Write>(
writer: &mut W,
signal_map: &SignalMap,
tree: &NameTree,
sort: bool,
) -> std::io::Result<()> {
let mut entries: Vec<(String, &VarMeta, &[String])> = signal_map
.values()
.flat_map(|info| {
info.vars
.iter()
.map(|v| (tree.format_path(v.name), &v.meta, v.attrs.as_slice()))
})
.collect();
if sort {
entries.sort_by(|(a, _, _), (b, _, _)| a.cmp(b));
}
for (name, meta, attrs) in entries {
if meta.direction != IMPLICIT_DIRECTION {
writeln!(writer, "{} {} {} {}", name, meta.var_type, meta.size, meta.direction)?;
} else {
writeln!(writer, "{} {} {}", name, meta.var_type, meta.size)?;
}
for attr in attrs {
writeln!(writer, " {}", attr)?;
}
}
Ok(())
}
pub fn merge_signal_maps(
maps: &[(&SignalMap, &NameTree, &str)],
) -> Result<(SignalMap, NameTree, Vec<usize>), String> {
if maps.len() == 1 {
let (map, tree, _) = maps[0];
return Ok((map.clone(), clone_name_tree(tree), vec![0]));
}
let mut merged = SignalMap::new();
let mut merged_tree = NameTree::new();
let mut offsets = Vec::with_capacity(maps.len());
let mut next_handle: usize = 0;
let mut seen_names: HashMap<String, &str> = HashMap::new();
let mut enum_names: EnumNameRegistry = HashMap::new();
for &(map, tree, path) in maps {
offsets.push(next_handle);
for (&handle, info) in map {
let new_handle = handle + next_handle;
let mut new_vars = Vec::with_capacity(info.vars.len());
for var in &info.vars {
let flat_name = tree.format_path(var.name);
if let Some(&prev_path) = seen_names.get(&flat_name) {
return Err(format!(
"duplicate signal '{}' found in both {} and {}",
flat_name, prev_path, path,
));
}
seen_names.insert(flat_name, path);
let segments = tree.segments(var.name);
let mut cur = merged_tree.root();
for seg in &segments {
cur = merged_tree.intern(cur, seg);
}
for attr in &var.attrs {
if let Some(rest) = attr.strip_prefix("enum ") {
if let Some(colon_pos) = rest.find(':') {
let enum_name = rest[..colon_pos].trim();
if enum_name.contains("::") {
let mapping: Vec<(String, String)> = rest[colon_pos + 1..]
.split_whitespace()
.filter_map(|pair| {
let (k, v) = pair.split_once('=')?;
Some((k.to_string(), v.to_string()))
})
.collect();
check_enum_conflict(&mut enum_names, enum_name, &mapping)?;
}
}
}
}
new_vars.push(VarEntry {
name: cur,
meta: var.meta.clone(),
attrs: var.attrs.clone(),
});
}
merged.insert(new_handle, SignalInfo { vars: new_vars });
}
let max_handle = map.keys().max().copied().unwrap_or(0);
next_handle += max_handle + 1;
}
Ok((merged, merged_tree, offsets))
}
fn clone_name_tree(tree: &NameTree) -> NameTree {
NameTree {
nodes: tree
.nodes
.iter()
.map(|n| NameNode {
segment: n.segment.clone(),
parent: n.parent,
children: n.children.clone(),
})
.collect(),
}
}
pub fn open_wave_files(
paths: &[&Path],
options: &NameOptions,
format: Option<WaveFormat>,
) -> Result<(Vec<WaveReader>, WaveHierarchy, Vec<usize>), String> {
let mut readers = Vec::with_capacity(paths.len());
let mut hierarchies = Vec::with_capacity(paths.len());
for &path in paths {
let (reader, hier) = open_wave_file_with_format(path, options, format)?;
readers.push(reader);
hierarchies.push(hier);
}
let maps_with_paths: Vec<(&SignalMap, &NameTree, &str)> = hierarchies
.iter()
.zip(paths.iter())
.map(|(h, p)| (&h.signal_map, &h.names, p.to_str().unwrap_or("<unknown>")))
.collect();
let (merged_map, merged_tree, offsets) = merge_signal_maps(&maps_with_paths)?;
Ok((
readers,
WaveHierarchy {
signal_map: merged_map,
names: merged_tree,
},
offsets,
))
}
fn open_as_fst(
buf: BufReader<File>,
path: &Path,
options: &NameOptions,
) -> Result<(WaveReader, WaveHierarchy), String> {
let mut fst_reader = fst_reader::FstReader::open(buf)
.map_err(|e| format!("Failed to open FST file {}: {}", path.display(), e))?;
let (signal_map, tree) = build_hierarchy(&mut fst_reader, options)
.map_err(|e| format!("Failed to read hierarchy from {}: {}", path.display(), e))?;
Ok((
WaveReader::Fst(Box::new(fst_reader)),
WaveHierarchy {
signal_map,
names: tree,
},
))
}
fn open_as_vcd(
buf: BufReader<File>,
path: &Path,
options: &NameOptions,
) -> Result<(WaveReader, WaveHierarchy), String> {
let mut parser = vcd::Parser::new(buf).with_gtkwave_extensions(true);
let header = parser
.parse_header()
.map_err(|e| format!("Failed to parse VCD file {}: {}", path.display(), e))?;
let (signal_map, id_to_idx, tree) = build_vcd_hierarchy(&header, options)?;
let vcd_data = VcdData {
parser,
id_to_idx,
current_time: 0,
};
Ok((
WaveReader::Vcd(vcd_data),
WaveHierarchy {
signal_map,
names: tree,
},
))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WaveFormat {
Fst,
Vcd,
}
pub fn open_wave_file(
path: &Path,
options: &NameOptions,
) -> Result<(WaveReader, WaveHierarchy), String> {
open_wave_file_with_format(path, options, None)
}
pub fn open_wave_file_with_format(
path: &Path,
options: &NameOptions,
format: Option<WaveFormat>,
) -> Result<(WaveReader, WaveHierarchy), String> {
let f = File::open(path).map_err(|e| format!("Failed to open {}: {}", path.display(), e))?;
match format {
Some(WaveFormat::Fst) => {
let buf = BufReader::new(f);
open_as_fst(buf, path, options)
}
Some(WaveFormat::Vcd) => {
let buf = BufReader::new(f);
open_as_vcd(buf, path, options)
}
None => {
let mut buf = BufReader::new(f);
if is_fst_file(&mut buf) {
return open_as_fst(buf, path, options);
}
buf.seek(std::io::SeekFrom::Start(0))
.map_err(|e| format!("Failed to seek in {}: {}", path.display(), e))?;
open_as_vcd(buf, path, options)
}
}
}