forked from libre-chip/fayalite
1125 lines
33 KiB
Rust
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}"),
|
|
);
|
|
}
|
|
}
|
|
}
|