fayalite/crates/fayalite/src/sim/vcd.rs

1125 lines
33 KiB
Rust

// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
enum_::{Enum, EnumType},
expr::Flow,
int::UInt,
intern::{Intern, Interned},
sim::{
time::{SimDuration, SimInstant},
TraceArray, TraceAsyncReset, TraceBool, TraceBundle, TraceClock, TraceDecl,
TraceEnumDiscriminant, TraceEnumWithFields, TraceFieldlessEnum, TraceInstance,
TraceLocation, TraceMem, TraceMemPort, TraceMemoryId, TraceMemoryLocation, TraceModule,
TraceModuleIO, TraceReg, TraceSInt, TraceScalar, TraceScalarId, TraceScope, TraceSyncReset,
TraceUInt, TraceWire, TraceWriter, TraceWriterDecls,
},
util::HashMap,
};
use bitvec::{order::Lsb0, slice::BitSlice};
use hashbrown::hash_map::Entry;
use std::{
fmt::{self, Write as _},
io, mem,
};
#[derive(Default)]
struct Scope {
last_inserted: HashMap<Interned<str>, usize>,
}
#[derive(Copy, Clone)]
struct VerilogIdentifier {
unescaped_name: Interned<str>,
}
impl VerilogIdentifier {
fn needs_escape(self) -> bool {
// we only allow ascii, so we can just check bytes
let Some((&first, rest)) = self.unescaped_name.as_bytes().split_first() else {
unreachable!("Scope::new_identifier guarantees a non-empty name");
};
if !first.is_ascii_alphabetic() && first != b'_' {
true
} else {
rest.iter()
.any(|&ch| !ch.is_ascii_alphanumeric() && ch != b'_' && ch != b'$')
}
}
}
impl fmt::Display for VerilogIdentifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.needs_escape() {
f.write_str("\\")?;
}
write!(f, "{}", Escaped(self.unescaped_name))
}
}
impl Scope {
fn new_identifier(&mut self, unescaped_name: Interned<str>) -> VerilogIdentifier {
let next_disambiguator = match self.last_inserted.entry(unescaped_name) {
Entry::Vacant(entry) => {
entry.insert(1);
return VerilogIdentifier { unescaped_name };
}
Entry::Occupied(entry) => entry.get() + 1,
};
let mut disambiguated_name = String::from(&*unescaped_name);
for disambiguator in next_disambiguator.. {
disambiguated_name.truncate(unescaped_name.len());
write!(disambiguated_name, "_{disambiguator}").expect("can't fail");
if let Entry::Vacant(entry) = self.last_inserted.entry((*disambiguated_name).intern()) {
let retval = VerilogIdentifier {
unescaped_name: *entry.key(),
};
entry.insert(1);
// speed up future searches
self.last_inserted.insert(unescaped_name, disambiguator);
return retval;
}
}
panic!("too many names");
}
}
pub struct VcdWriterDecls<W: io::Write + 'static> {
writer: W,
timescale: SimDuration,
}
impl<W: io::Write + 'static> VcdWriterDecls<W> {
pub fn new(writer: W) -> Self {
Self {
writer,
timescale: SimDuration::from_picos(1),
}
}
pub fn timescale(&self) -> SimDuration {
self.timescale
}
#[track_caller]
pub fn with_timescale(mut self, timescale: SimDuration) -> Self {
// check timescale validity
vcd_timescale(timescale);
self.timescale = timescale;
self
}
}
#[track_caller]
const fn vcd_timescale(timescale: SimDuration) -> &'static str {
if !timescale.is_power_of_ten() {
panic!("VCD timescale must be a power of 10");
}
macro_rules! timescales {
($($const_name:ident = ($dur:expr, $text:literal),)*) => {
$(const $const_name: SimDuration = $dur;)*
match timescale {
$($const_name => $text,)*
_ => panic!("VCD timescale is too big"),
}
};
}
timescales! {
TIMESCALE_1_AS = (SimDuration::from_attos(1), "1 as"),
TIMESCALE_10_AS = (SimDuration::from_attos(10), "10 as"),
TIMESCALE_100_AS = (SimDuration::from_attos(100), "100 as"),
TIMESCALE_1_FS = (SimDuration::from_femtos(1), "1 fs"),
TIMESCALE_10_FS = (SimDuration::from_femtos(10), "10 fs"),
TIMESCALE_100_FS = (SimDuration::from_femtos(100), "100 fs"),
TIMESCALE_1_PS = (SimDuration::from_picos(1), "1 ps"),
TIMESCALE_10_PS = (SimDuration::from_picos(10), "10 ps"),
TIMESCALE_100_PS = (SimDuration::from_picos(100), "100 ps"),
TIMESCALE_1_NS = (SimDuration::from_nanos(1), "1 ns"),
TIMESCALE_10_NS = (SimDuration::from_nanos(10), "10 ns"),
TIMESCALE_100_NS = (SimDuration::from_nanos(100), "100 ns"),
TIMESCALE_1_US = (SimDuration::from_micros(1), "1 us"),
TIMESCALE_10_US = (SimDuration::from_micros(10), "10 us"),
TIMESCALE_100_US = (SimDuration::from_micros(100), "100 us"),
TIMESCALE_1_MS = (SimDuration::from_millis(1), "1 ms"),
TIMESCALE_10_MS = (SimDuration::from_millis(10), "10 ms"),
TIMESCALE_100_MS = (SimDuration::from_millis(100), "100 ms"),
TIMESCALE_1_S = (SimDuration::from_secs(1), "1 s"),
TIMESCALE_10_S = (SimDuration::from_secs(10), "10 s"),
TIMESCALE_100_S = (SimDuration::from_secs(100), "100 s"),
TIMESCALE_1000_S = (SimDuration::from_secs(1000), "1000 s"),
}
}
impl<W: io::Write> fmt::Debug for VcdWriterDecls<W> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
writer: _,
timescale,
} = self;
f.debug_struct("VcdWriterDecls")
.field("timescale", timescale)
.finish_non_exhaustive()
}
}
/// pass in scope to ensure it's not available in child scope
fn write_vcd_scope<W: io::Write, R>(
writer: &mut W,
scope_type: &str,
scope_name: Interned<str>,
scope: &mut Scope,
f: impl FnOnce(&mut W, &mut Scope) -> io::Result<R>,
) -> io::Result<R> {
writeln!(
writer,
"$scope {scope_type} {} $end",
scope.new_identifier(scope_name),
)?;
let retval = f(writer, &mut Scope::default())?;
writeln!(writer, "$upscope $end")?;
Ok(retval)
}
macro_rules! trait_arg {
(
trait $Arg:ident {
$(
fn $fn:ident(&mut self) -> $ty:ty;
)*
}
) => {
trait $Arg: Sized {
$(fn $fn(&mut self) -> $ty {
unreachable!()
})*
}
$(
impl $Arg for $ty {
fn $fn(&mut self) -> $ty {
self.reborrow()
}
}
)*
};
}
trait_arg! {
trait Arg {
fn module(&mut self) -> ArgModule<'_>;
fn module_body(&mut self) -> ArgModuleBody<'_>;
fn in_type(&mut self) -> ArgInType<'_>;
}
}
struct ArgModule<'a> {
properties: &'a mut VcdWriterProperties,
scope: &'a mut Scope,
}
impl<'a> ArgModule<'a> {
fn reborrow(&mut self) -> ArgModule<'_> {
ArgModule {
properties: self.properties,
scope: self.scope,
}
}
}
struct ArgModuleBody<'a> {
properties: &'a mut VcdWriterProperties,
scope: &'a mut Scope,
}
impl<'a> ArgModuleBody<'a> {
fn reborrow(&mut self) -> ArgModuleBody<'_> {
ArgModuleBody {
properties: self.properties,
scope: self.scope,
}
}
}
struct ArgInType<'a> {
source_var_type: &'static str,
sink_var_type: &'static str,
duplex_var_type: &'static str,
properties: &'a mut VcdWriterProperties,
scope: &'a mut Scope,
}
impl<'a> ArgInType<'a> {
fn reborrow(&mut self) -> ArgInType<'_> {
ArgInType {
source_var_type: self.source_var_type,
sink_var_type: self.sink_var_type,
duplex_var_type: self.duplex_var_type,
properties: self.properties,
scope: self.scope,
}
}
}
trait WriteTrace: Copy {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, arg: A) -> io::Result<()>;
}
impl WriteTrace for TraceDecl {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, arg: A) -> io::Result<()> {
match self {
Self::Scope(v) => v.write_trace(writer, arg),
Self::Scalar(v) => v.write_trace(writer, arg),
}
}
}
impl WriteTrace for TraceScalar {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, arg: A) -> io::Result<()> {
match self {
Self::UInt(v) => v.write_trace(writer, arg),
Self::SInt(v) => v.write_trace(writer, arg),
Self::Bool(v) => v.write_trace(writer, arg),
Self::FieldlessEnum(v) => v.write_trace(writer, arg),
Self::EnumDiscriminant(v) => v.write_trace(writer, arg),
Self::Clock(v) => v.write_trace(writer, arg),
Self::SyncReset(v) => v.write_trace(writer, arg),
Self::AsyncReset(v) => v.write_trace(writer, arg),
}
}
}
fn write_vcd_id<W: io::Write>(writer: &mut W, mut id: usize) -> io::Result<()> {
let min_char = b'!';
let max_char = b'~';
let base = (max_char - min_char + 1) as usize;
loop {
let digit = (id % base) as u8 + min_char;
id /= base;
writer.write_all(&[digit])?;
if id == 0 {
break;
}
}
Ok(())
}
struct Escaped<T: fmt::Display>(T);
impl<T: fmt::Display> fmt::Display for Escaped<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// escaping rules from function GTKWave uses to decode VCD strings:
// https://github.com/gtkwave/gtkwave/blob/491f24d7e8619cfc1fcc65704ee5c967d1083c18/lib/libfst/fstapi.c#L7090
struct Wrapper<W>(W);
impl<W: fmt::Write> fmt::Write for Wrapper<W> {
fn write_str(&mut self, s: &str) -> fmt::Result {
for byte in s.bytes() {
match byte {
b'\\' | b'\'' | b'"' | b'?' => {
self.0.write_str("\\")?;
self.0.write_char(byte as char)?;
}
b'\n' => self.0.write_str(r"\n")?,
b'\r' => self.0.write_str(r"\r")?,
b'\t' => self.0.write_str(r"\t")?,
0x7 => self.0.write_str(r"\a")?,
0x8 => self.0.write_str(r"\b")?,
0xC => self.0.write_str(r"\f")?,
0xB => self.0.write_str(r"\v")?,
_ => {
if byte.is_ascii_graphic() {
self.0.write_char(byte as char)?;
} else {
write!(self.0, r"\x{byte:02x}")?;
}
}
}
}
Ok(())
}
}
write!(Wrapper(f), "{}", self.0)
}
}
fn write_vcd_var<W: io::Write>(
properties: &mut VcdWriterProperties,
memory_element_part_body: MemoryElementPartBody,
writer: &mut W,
var_type: &str,
size: usize,
location: TraceLocation,
name: VerilogIdentifier,
) -> io::Result<()> {
let id = match location {
TraceLocation::Scalar(id) => id.as_usize(),
TraceLocation::Memory(TraceMemoryLocation {
id,
depth,
stride: _,
start,
len,
}) => {
let MemoryProperties {
element_parts,
element_part_index,
element_index,
} = &mut properties.memory_properties[id.as_usize()];
let first_id;
if let Some(element_part) = element_parts.get(*element_part_index) {
first_id = element_part.first_id;
} else {
first_id = properties.next_scalar_id;
properties.next_scalar_id += depth;
element_parts.push(MemoryElementPart {
first_id,
start,
len,
body: memory_element_part_body,
});
}
*element_part_index += 1;
first_id + *element_index
}
};
write!(writer, "$var {var_type} {size} ")?;
write_vcd_id(writer, id)?;
writeln!(writer, " {name} $end")
}
impl WriteTrace for TraceUInt {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> {
let ArgInType {
source_var_type,
sink_var_type,
duplex_var_type,
properties,
scope,
} = arg.in_type();
let Self {
location,
name,
ty,
flow,
} = self;
let mut var_type = match flow {
Flow::Source => source_var_type,
Flow::Sink => sink_var_type,
Flow::Duplex => duplex_var_type,
};
if ty.width() == 0 {
var_type = "string";
}
write_vcd_var(
properties,
MemoryElementPartBody::Scalar,
writer,
var_type,
ty.width(),
location,
scope.new_identifier(name),
)
}
}
impl WriteTrace for TraceSInt {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, arg: A) -> io::Result<()> {
let Self {
location,
name,
ty,
flow,
} = self;
TraceUInt {
location,
name,
ty: UInt::new_dyn(ty.width()),
flow,
}
.write_trace(writer, arg)
}
}
impl WriteTrace for TraceBool {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, arg: A) -> io::Result<()> {
let Self {
location,
name,
flow,
} = self;
TraceUInt {
location,
name,
flow,
ty: UInt::new_dyn(1),
}
.write_trace(writer, arg)
}
}
impl WriteTrace for TraceFieldlessEnum {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, arg: A) -> io::Result<()> {
let Self {
location,
name,
ty,
flow,
} = self;
TraceEnumDiscriminant {
location,
name,
ty,
flow,
}
.write_trace(writer, arg)
}
}
impl WriteTrace for TraceEnumDiscriminant {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> {
let ArgInType {
source_var_type: _,
sink_var_type: _,
duplex_var_type: _,
properties,
scope,
} = arg.in_type();
let Self {
location,
name,
ty,
flow: _,
} = self;
write_vcd_var(
properties,
MemoryElementPartBody::EnumDiscriminant { ty },
writer,
"string",
1,
location,
scope.new_identifier(name),
)
}
}
impl WriteTrace for TraceClock {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, arg: A) -> io::Result<()> {
let Self {
location,
name,
flow,
} = self;
TraceBool {
location,
name,
flow,
}
.write_trace(writer, arg)
}
}
impl WriteTrace for TraceSyncReset {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, arg: A) -> io::Result<()> {
let Self {
location,
name,
flow,
} = self;
TraceBool {
location,
name,
flow,
}
.write_trace(writer, arg)
}
}
impl WriteTrace for TraceAsyncReset {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, arg: A) -> io::Result<()> {
let Self {
location,
name,
flow,
} = self;
TraceBool {
location,
name,
flow,
}
.write_trace(writer, arg)
}
}
impl WriteTrace for TraceScope {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, arg: A) -> io::Result<()> {
match self {
Self::Module(v) => v.write_trace(writer, arg),
Self::Instance(v) => v.write_trace(writer, arg),
Self::Mem(v) => v.write_trace(writer, arg),
Self::MemPort(v) => v.write_trace(writer, arg),
Self::Wire(v) => v.write_trace(writer, arg),
Self::Reg(v) => v.write_trace(writer, arg),
Self::ModuleIO(v) => v.write_trace(writer, arg),
Self::Bundle(v) => v.write_trace(writer, arg),
Self::Array(v) => v.write_trace(writer, arg),
Self::EnumWithFields(v) => v.write_trace(writer, arg),
}
}
}
impl WriteTrace for TraceModule {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> {
let ArgModule { properties, scope } = arg.module();
let Self { name, children } = self;
write_vcd_scope(writer, "module", name, scope, |writer, scope| {
for child in children {
child.write_trace(writer, ArgModuleBody { properties, scope })?;
}
Ok(())
})
}
}
impl WriteTrace for TraceInstance {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> {
let ArgModuleBody { properties, scope } = arg.module_body();
let Self {
name: _,
instance_io,
module,
ty: _,
} = self;
instance_io.write_trace(
writer,
ArgInType {
source_var_type: "wire",
sink_var_type: "wire",
duplex_var_type: "wire",
properties,
scope,
},
)?;
module.write_trace(writer, ArgModule { properties, scope })
}
}
impl WriteTrace for TraceMem {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> {
let ArgModuleBody { properties, scope } = arg.module_body();
let Self {
id,
name,
stride: _,
element_type,
ports,
array_type,
} = self;
write_vcd_scope(writer, "struct", name, scope, |writer, scope| {
write_vcd_scope(
writer,
"struct",
"contents".intern(),
scope,
|writer, scope| {
for element_index in 0..array_type.len() {
write_vcd_scope(
writer,
"struct",
Intern::intern_owned(format!("[{element_index}]")),
scope,
|writer, scope| {
properties.memory_properties[id.as_usize()].element_index =
element_index;
properties.memory_properties[id.as_usize()].element_part_index = 0;
element_type.write_trace(
writer,
ArgInType {
source_var_type: "reg",
sink_var_type: "reg",
duplex_var_type: "reg",
properties,
scope,
},
)
},
)?;
}
Ok(())
},
)?;
for port in ports {
port.write_trace(writer, ArgModuleBody { properties, scope })?;
}
Ok(())
})
}
}
impl WriteTrace for TraceMemPort {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> {
let ArgModuleBody { properties, scope } = arg.module_body();
let Self {
name: _,
bundle,
ty: _,
} = self;
bundle.write_trace(
writer,
ArgInType {
source_var_type: "wire",
sink_var_type: "wire",
duplex_var_type: "wire",
properties,
scope,
},
)
}
}
impl WriteTrace for TraceWire {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> {
let ArgModuleBody { properties, scope } = arg.module_body();
let Self {
name: _,
child,
ty: _,
} = self;
child.write_trace(
writer,
ArgInType {
source_var_type: "wire",
sink_var_type: "wire",
duplex_var_type: "wire",
properties,
scope,
},
)
}
}
impl WriteTrace for TraceReg {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> {
let ArgModuleBody { properties, scope } = arg.module_body();
let Self {
name: _,
child,
ty: _,
} = self;
child.write_trace(
writer,
ArgInType {
source_var_type: "reg",
sink_var_type: "reg",
duplex_var_type: "reg",
properties,
scope,
},
)
}
}
impl WriteTrace for TraceModuleIO {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> {
let ArgModuleBody { properties, scope } = arg.module_body();
let Self {
name: _,
child,
ty: _,
flow: _,
} = self;
child.write_trace(
writer,
ArgInType {
source_var_type: "wire",
sink_var_type: "wire",
duplex_var_type: "wire",
properties,
scope,
},
)
}
}
impl WriteTrace for TraceBundle {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> {
let ArgInType {
source_var_type,
sink_var_type,
duplex_var_type,
properties,
scope,
} = arg.in_type();
let Self {
name,
fields,
ty: _,
flow: _,
} = self;
write_vcd_scope(writer, "struct", name, scope, |writer, scope| {
for field in fields {
field.write_trace(
writer,
ArgInType {
source_var_type,
sink_var_type,
duplex_var_type,
properties,
scope,
},
)?;
}
Ok(())
})
}
}
impl WriteTrace for TraceArray {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> {
let ArgInType {
source_var_type,
sink_var_type,
duplex_var_type,
properties,
scope,
} = arg.in_type();
let Self {
name,
elements,
ty: _,
flow: _,
} = self;
write_vcd_scope(writer, "struct", name, scope, |writer, scope| {
for element in elements {
element.write_trace(
writer,
ArgInType {
source_var_type,
sink_var_type,
duplex_var_type,
properties,
scope,
},
)?;
}
Ok(())
})
}
}
impl WriteTrace for TraceEnumWithFields {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, mut arg: A) -> io::Result<()> {
let ArgInType {
source_var_type,
sink_var_type,
duplex_var_type,
properties,
scope,
} = arg.in_type();
let Self {
name,
discriminant,
non_empty_fields,
ty: _,
flow: _,
} = self;
write_vcd_scope(writer, "struct", name, scope, |writer, scope| {
discriminant.write_trace(
writer,
ArgInType {
source_var_type,
sink_var_type,
duplex_var_type,
properties,
scope,
},
)?;
for field in non_empty_fields {
field.write_trace(
writer,
ArgInType {
source_var_type,
sink_var_type,
duplex_var_type,
properties,
scope,
},
)?;
}
Ok(())
})
}
}
impl<W: io::Write> TraceWriterDecls for VcdWriterDecls<W> {
type Error = io::Error;
type TraceWriter = VcdWriter<W>;
fn write_decls(
self,
module: TraceModule,
trace_scalar_id_count: usize,
trace_memory_id_count: usize,
) -> Result<Self::TraceWriter, Self::Error> {
let Self {
mut writer,
timescale,
} = self;
writeln!(writer, "$timescale {} $end", vcd_timescale(timescale))?;
let mut properties = VcdWriterProperties {
next_scalar_id: trace_scalar_id_count,
memory_properties: (0..trace_memory_id_count)
.map(|_| MemoryProperties {
element_parts: Vec::with_capacity(8),
element_part_index: 0,
element_index: 0,
})
.collect(),
};
module.write_trace(
&mut writer,
ArgModule {
properties: &mut properties,
scope: &mut Scope::default(),
},
)?;
writeln!(writer, "$enddefinitions $end")?;
writeln!(writer, "$dumpvars")?;
Ok(VcdWriter {
writer,
finished_init: false,
timescale,
properties,
})
}
}
enum MemoryElementPartBody {
Scalar,
EnumDiscriminant { ty: Enum },
}
struct MemoryElementPart {
first_id: usize,
start: usize,
len: usize,
body: MemoryElementPartBody,
}
struct MemoryProperties {
element_parts: Vec<MemoryElementPart>,
element_part_index: usize,
element_index: usize,
}
struct VcdWriterProperties {
next_scalar_id: usize,
memory_properties: Box<[MemoryProperties]>,
}
pub struct VcdWriter<W: io::Write + 'static> {
writer: W,
finished_init: bool,
timescale: SimDuration,
properties: VcdWriterProperties,
}
impl<W: io::Write + 'static> VcdWriter<W> {
pub fn timescale(&self) -> SimDuration {
self.timescale
}
}
fn write_string_value_change(
writer: &mut impl io::Write,
value: impl fmt::Display,
id: usize,
) -> io::Result<()> {
write!(writer, "s{} ", Escaped(value))?;
write_vcd_id(writer, id)?;
writer.write_all(b"\n")
}
fn write_bits_value_change(
writer: &mut impl io::Write,
value: &BitSlice,
id: usize,
) -> io::Result<()> {
match value.len() {
0 => writer.write_all(b"s0 ")?,
1 => writer.write_all(if value[0] { b"1" } else { b"0" })?,
_ => {
writer.write_all(b"b")?;
let mut any_ones = false;
for bit in value.iter().rev() {
if *bit {
any_ones = true;
writer.write_all(b"1")?;
} else if any_ones {
writer.write_all(b"0")?;
}
}
if !any_ones {
writer.write_all(b"0")?;
}
writer.write_all(b" ")?;
}
}
write_vcd_id(writer, id)?;
writer.write_all(b"\n")
}
fn write_enum_discriminant_value_change(
writer: &mut impl io::Write,
variant_index: usize,
ty: Enum,
id: usize,
) -> io::Result<()> {
write_string_value_change(
writer,
format_args!(
"{} ({variant_index})",
ty.variants()
.get(variant_index)
.map(|v| &*v.name)
.unwrap_or("<invalid>"),
),
id,
)
}
impl<W: io::Write> TraceWriter for VcdWriter<W> {
type Error = io::Error;
fn set_memory_element(
&mut self,
memory: TraceMemoryId,
element_index: usize,
element_data: &BitSlice,
) -> Result<(), Self::Error> {
for &MemoryElementPart {
first_id,
start,
len,
ref body,
} in &self.properties.memory_properties[memory.as_usize()].element_parts
{
match body {
MemoryElementPartBody::Scalar => write_bits_value_change(
&mut self.writer,
&element_data[start..start + len],
first_id + element_index,
)?,
MemoryElementPartBody::EnumDiscriminant { ty } => {
let mut variant_index = 0;
BitSlice::<usize, Lsb0>::from_element_mut(&mut variant_index)[..len]
.clone_from_bitslice(&element_data[start..start + len]);
write_enum_discriminant_value_change(
&mut self.writer,
variant_index,
*ty,
first_id + element_index,
)?
}
}
}
Ok(())
}
fn set_signal_uint(&mut self, id: TraceScalarId, value: &BitSlice) -> Result<(), Self::Error> {
write_bits_value_change(&mut self.writer, value, id.as_usize())
}
fn set_signal_sint(&mut self, id: TraceScalarId, value: &BitSlice) -> Result<(), Self::Error> {
write_bits_value_change(&mut self.writer, value, id.as_usize())
}
fn finish_init(&mut self) -> Result<(), Self::Error> {
if mem::replace(&mut self.finished_init, true) {
return Ok(());
}
writeln!(self.writer, "$end")
}
fn change_time_to(&mut self, instant: SimInstant) -> Result<(), Self::Error> {
assert!(self.finished_init);
let mut instant_attos = (instant - SimInstant::START).as_attos();
instant_attos += self.timescale.as_attos() / 2;
let timestamp = instant_attos / self.timescale.as_attos();
writeln!(self.writer, "#{timestamp}")
}
fn flush(&mut self) -> Result<(), Self::Error> {
self.writer.flush()
}
fn close(mut self) -> Result<(), Self::Error> {
self.writer.flush()
}
fn set_signal_enum_discriminant(
&mut self,
id: TraceScalarId,
variant_index: usize,
ty: Enum,
) -> Result<(), Self::Error> {
write_enum_discriminant_value_change(&mut self.writer, variant_index, ty, id.as_usize())
}
}
impl<W: io::Write> fmt::Debug for VcdWriter<W> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
writer: _,
finished_init,
timescale,
properties: _,
} = self;
f.debug_struct("VcdWriter")
.field("finished_init", finished_init)
.field("timescale", timescale)
.finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scope() {
let mut scope = Scope::default();
assert_eq!(&*scope.new_identifier("foo".intern()).unescaped_name, "foo");
assert_eq!(
&*scope.new_identifier("foo_0".intern()).unescaped_name,
"foo_0"
);
assert_eq!(
&*scope.new_identifier("foo_1".intern()).unescaped_name,
"foo_1"
);
assert_eq!(
&*scope.new_identifier("foo_3".intern()).unescaped_name,
"foo_3"
);
assert_eq!(
&*scope.new_identifier("foo".intern()).unescaped_name,
"foo_2"
);
assert_eq!(
&*scope.new_identifier("foo".intern()).unescaped_name,
"foo_4"
);
assert_eq!(
&*scope.new_identifier("foo_0".intern()).unescaped_name,
"foo_0_2"
);
assert_eq!(
&*scope.new_identifier("foo_1".intern()).unescaped_name,
"foo_1_2"
);
for i in 5..1000u64 {
// verify it actually picks the next available identifier with no skips or duplicates
assert_eq!(
*scope.new_identifier("foo".intern()).unescaped_name,
format!("foo_{i}"),
);
}
}
}