From c4b5d004194dcaddba78f90598c3511d8f850471 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Mon, 18 Nov 2024 03:04:10 -0800 Subject: [PATCH] WIP adding VCD output --- crates/fayalite/src/sim.rs | 1 + crates/fayalite/src/sim/time.rs | 200 +++++++++++- crates/fayalite/src/sim/vcd.rs | 542 +++++++++++++++++++++++++++++++ crates/fayalite/src/util.rs | 2 +- crates/fayalite/src/util/misc.rs | 48 +++ crates/fayalite/tests/sim.rs | 22 +- 6 files changed, 801 insertions(+), 14 deletions(-) create mode 100644 crates/fayalite/src/sim/vcd.rs diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index d33b0bb..569cbcc 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -46,6 +46,7 @@ use std::{borrow::Cow, collections::BTreeSet, fmt, marker::PhantomData, mem, ops mod interpreter; pub mod time; +pub mod vcd; #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] enum CondBody { diff --git a/crates/fayalite/src/sim/time.rs b/crates/fayalite/src/sim/time.rs index 07e2b79..7c85d8f 100644 --- a/crates/fayalite/src/sim/time.rs +++ b/crates/fayalite/src/sim/time.rs @@ -2,7 +2,7 @@ // See Notices.txt for copyright information use std::{ fmt, - ops::{Add, AddAssign}, + ops::{Add, AddAssign, Sub, SubAssign}, time::Duration, }; @@ -11,9 +11,43 @@ pub struct SimInstant { time_since_start: SimDuration, } +impl SimInstant { + pub const fn checked_add(self, duration: SimDuration) -> Option { + let Some(time_since_start) = self.time_since_start.checked_add(duration) else { + return None; + }; + Some(SimInstant { time_since_start }) + } + pub const fn checked_duration_since(self, earlier: Self) -> Option { + self.time_since_start.checked_sub(earlier.time_since_start) + } + pub const fn checked_sub(self, duration: SimDuration) -> Option { + let Some(time_since_start) = self.time_since_start.checked_sub(duration) else { + return None; + }; + Some(SimInstant { time_since_start }) + } + #[track_caller] + pub const fn duration_since(self, earlier: Self) -> SimDuration { + let Some(retval) = self.checked_duration_since(earlier) else { + panic!( + "tried to compute the duration since a later time -- durations can't be negative" + ); + }; + retval + } + pub const fn saturating_duration_since(self, earlier: Self) -> SimDuration { + let Some(retval) = self.checked_duration_since(earlier) else { + return SimDuration::ZERO; + }; + retval + } +} + impl Add for SimInstant { type Output = SimInstant; + #[track_caller] fn add(mut self, rhs: SimDuration) -> Self::Output { self += rhs; self @@ -21,6 +55,7 @@ impl Add for SimInstant { } impl AddAssign for SimInstant { + #[track_caller] fn add_assign(&mut self, rhs: SimDuration) { self.time_since_start += rhs; } @@ -29,11 +64,40 @@ impl AddAssign for SimInstant { impl Add for SimDuration { type Output = SimInstant; + #[track_caller] fn add(self, rhs: SimInstant) -> Self::Output { rhs.add(self) } } +impl Sub for SimInstant { + type Output = SimDuration; + + #[track_caller] + fn sub(self, rhs: SimInstant) -> Self::Output { + self.duration_since(rhs) + } +} + +impl Sub for SimInstant { + type Output = SimInstant; + + #[track_caller] + fn sub(self, rhs: SimDuration) -> Self::Output { + let Some(retval) = self.checked_sub(rhs) else { + panic!("SimInstant underflow"); + }; + retval + } +} + +impl SubAssign for SimInstant { + #[track_caller] + fn sub_assign(&mut self, rhs: SimDuration) { + *self = *self - rhs; + } +} + impl SimInstant { pub const START: SimInstant = SimInstant { time_since_start: SimDuration::ZERO, @@ -52,6 +116,7 @@ pub struct SimDuration { } impl AddAssign for SimDuration { + #[track_caller] fn add_assign(&mut self, rhs: SimDuration) { *self = *self + rhs; } @@ -60,6 +125,7 @@ impl AddAssign for SimDuration { impl Add for SimDuration { type Output = SimDuration; + #[track_caller] fn add(self, rhs: SimDuration) -> Self::Output { SimDuration { attos: self @@ -70,6 +136,27 @@ impl Add for SimDuration { } } +impl Sub for SimDuration { + type Output = Self; + + #[track_caller] + fn sub(self, rhs: Self) -> Self::Output { + SimDuration { + attos: self + .attos + .checked_add(rhs.attos) + .expect("underflow subtracting durations -- durations can't be negative"), + } + } +} + +impl SubAssign for SimDuration { + #[track_caller] + fn sub_assign(&mut self, rhs: Self) { + *self = *self - rhs; + } +} + #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] pub struct SimDurationParts { pub attos: u16, @@ -84,7 +171,7 @@ pub struct SimDurationParts { macro_rules! impl_duration_units { ( $( - #[unit_const = $UNIT:ident, from_units = $from_units:ident, units = $units:ident, suffix = $suffix:literal] + #[unit_const = $UNIT:ident, from_units = $from_units:ident, as_units = $as_units:ident, units = $units:ident, suffix = $suffix:literal] const $log10_units_per_sec:ident: u32 = $log10_units_per_sec_value:expr; )* ) => { @@ -94,8 +181,11 @@ macro_rules! impl_duration_units { pub const fn $from_units($units: u128) -> Self { Self::from_units_helper::<{ Self::$log10_units_per_sec }>($units) } + pub const fn $as_units(self) -> u128 { + self.attos / const { 10u128.pow(Self::LOG10_ATTOS_PER_SEC - Self::$log10_units_per_sec) } + } )* - pub const fn into_parts(mut self) -> SimDurationParts { + pub const fn to_parts(mut self) -> SimDurationParts { $( let $units = self.attos / const { 10u128.pow(Self::LOG10_ATTOS_PER_SEC - Self::$log10_units_per_sec) }; self.attos %= const { 10u128.pow(Self::LOG10_ATTOS_PER_SEC - Self::$log10_units_per_sec) }; @@ -176,19 +266,19 @@ macro_rules! impl_duration_units { } impl_duration_units! { - #[unit_const = SECOND, from_units = from_secs, units = secs, suffix = "s"] + #[unit_const = SECOND, from_units = from_secs, as_units = as_secs, units = secs, suffix = "s"] const LOG10_SECS_PER_SEC: u32 = 0; - #[unit_const = MILLISECOND, from_units = from_millis, units = millis, suffix = "ms"] + #[unit_const = MILLISECOND, from_units = from_millis, as_units = as_millis, units = millis, suffix = "ms"] const LOG10_MILLIS_PER_SEC: u32 = 3; - #[unit_const = MICROSECOND, from_units = from_micros, units = micros, suffix = "μs"] + #[unit_const = MICROSECOND, from_units = from_micros, as_units = as_micros, units = micros, suffix = "μs"] const LOG10_MICROS_PER_SEC: u32 = 6; - #[unit_const = NANOSECOND, from_units = from_nanos, units = nanos, suffix = "ns"] + #[unit_const = NANOSECOND, from_units = from_nanos, as_units = as_nanos, units = nanos, suffix = "ns"] const LOG10_NANOS_PER_SEC: u32 = 9; - #[unit_const = PICOSECOND, from_units = from_picos, units = picos, suffix = "ps"] + #[unit_const = PICOSECOND, from_units = from_picos, as_units = as_picos, units = picos, suffix = "ps"] const LOG10_PICOS_PER_SEC: u32 = 12; - #[unit_const = FEMTOSECOND, from_units = from_femtos, units = femtos, suffix = "fs"] + #[unit_const = FEMTOSECOND, from_units = from_femtos, as_units = as_femtos, units = femtos, suffix = "fs"] const LOG10_FEMTOS_PER_SEC: u32 = 15; - #[unit_const = ATTOSECOND, from_units = from_attos, units = attos, suffix = "as"] + #[unit_const = ATTOSECOND, from_units = from_attos, as_units = as_attos, units = attos, suffix = "as"] const LOG10_ATTOS_PER_SEC: u32 = 18; } @@ -208,6 +298,96 @@ impl SimDuration { None => panic!("duration too big"), } } + pub const fn abs_diff(self, other: Self) -> Self { + Self { + attos: self.attos.abs_diff(other.attos), + } + } + pub const fn checked_add(self, rhs: Self) -> Option { + let Some(attos) = self.attos.checked_add(rhs.attos) else { + return None; + }; + Some(Self { attos }) + } + pub const fn checked_sub(self, rhs: Self) -> Option { + let Some(attos) = self.attos.checked_sub(rhs.attos) else { + return None; + }; + Some(Self { attos }) + } + pub const fn is_zero(self) -> bool { + self.attos == 0 + } + pub const fn saturating_add(self, rhs: Self) -> Self { + Self { + attos: self.attos.saturating_add(rhs.attos), + } + } + pub const fn saturating_sub(self, rhs: Self) -> Self { + Self { + attos: self.attos.saturating_sub(rhs.attos), + } + } + pub const fn checked_ilog10(self) -> Option { + let Some(ilog10_attos) = self.attos.checked_ilog10() else { + return None; + }; + Some(ilog10_attos as i32 - Self::LOG10_ATTOS_PER_SEC as i32) + } + #[track_caller] + pub const fn ilog10(self) -> i32 { + let Some(retval) = self.checked_ilog10() else { + panic!("tried to take the ilog10 of 0"); + }; + retval + } + pub const fn checked_pow10(log10: i32, underflow_is_zero: bool) -> Option { + let Some(log10) = Self::LOG10_ATTOS_PER_SEC.checked_add_signed(log10) else { + return if log10 < 0 && underflow_is_zero { + Some(Self::ZERO) + } else { + None + }; + }; + let Some(attos) = 10u128.checked_pow(log10) else { + return None; + }; + Some(Self { attos }) + } + #[track_caller] + pub const fn pow10(log10: i32) -> Self { + let Some(retval) = Self::checked_pow10(log10, true) else { + panic!("pow10 overflowed"); + }; + retval + } + pub const fn is_power_of_ten(self) -> bool { + const TEN: u128 = 10; + const NUMBER_OF_POWERS_OF_TEN: usize = { + let mut n = 0; + while let Some(_) = TEN.checked_pow(n as u32) { + n += 1; + } + n + }; + const POWERS_OF_TEN: [u128; NUMBER_OF_POWERS_OF_TEN] = { + let mut retval = [0; NUMBER_OF_POWERS_OF_TEN]; + let mut i = 0; + while i < NUMBER_OF_POWERS_OF_TEN { + retval[i] = TEN.pow(i as u32); + i += 1; + } + retval + }; + let mut i = 0; + while i < NUMBER_OF_POWERS_OF_TEN { + if self.attos == POWERS_OF_TEN[i] { + return true; + } + i += 1; + } + false + } } impl From for SimDuration { diff --git a/crates/fayalite/src/sim/vcd.rs b/crates/fayalite/src/sim/vcd.rs new file mode 100644 index 0000000..cbb38b4 --- /dev/null +++ b/crates/fayalite/src/sim/vcd.rs @@ -0,0 +1,542 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use crate::{ + expr::Flow, + int::UInt, + sim::{ + time::{SimDuration, SimInstant}, + TraceArray, TraceAsyncReset, TraceBool, TraceBundle, TraceClock, TraceDecl, + TraceEnumDiscriminant, TraceEnumWithFields, TraceFieldlessEnum, TraceInstance, + TraceMemPort, TraceModule, TraceModuleIO, TraceReg, TraceSInt, TraceScalar, TraceScalarId, + TraceScope, TraceSyncReset, TraceUInt, TraceWire, TraceWriter, TraceWriterDecls, + }, +}; +use bitvec::slice::BitSlice; +use std::{fmt, io, mem}; + +pub struct VcdWriterDecls { + writer: W, + timescale: SimDuration, +} + +impl VcdWriterDecls { + 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 fmt::Debug for VcdWriterDecls { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + writer: _, + timescale, + } = self; + f.debug_struct("VcdWriterDecls") + .field("timescale", timescale) + .finish_non_exhaustive() + } +} + +fn write_vcd_scope( + writer: &mut W, + scope_type: &str, + scope_name: &str, + f: impl FnOnce(&mut W) -> io::Result, +) -> io::Result { + writeln!(writer, "$scope {scope_type} {scope_name} $end")?; + let retval = f(writer)?; + writeln!(writer, "$upscope $end")?; + Ok(retval) +} + +macro_rules! trait_arg { + ( + trait $Arg:ident { + $( + fn $fn:ident(self) -> $ty:ty; + )* + } + ) => { + trait $Arg: Sized { + $(fn $fn(self) -> $ty { + unreachable!() + })* + } + + $( + impl $Arg for $ty { + fn $fn(self) -> $ty { + self + } + } + )* + }; +} + +trait_arg! { + trait Arg { + fn module(self) -> ArgModule; + fn module_body(self) -> ArgModuleBody; + fn in_type(self) -> ArgInType; + } +} + +struct ArgModule {} + +struct ArgModuleBody {} + +#[derive(Clone, Copy)] +struct ArgInType { + source_var_type: &'static str, + sink_var_type: &'static str, + duplex_var_type: &'static str, +} + +trait WriteTrace: Copy { + fn write_trace(self, writer: &mut W, arg: A) -> io::Result<()>; +} + +impl WriteTrace for TraceDecl { + fn write_trace(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(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_scalar_id(writer: &mut W, id: TraceScalarId) -> io::Result<()> { + let min_char = b'!'; + let max_char = b'~'; + let base = (max_char - min_char + 1) as usize; + let mut id = id.as_usize(); + loop { + let digit = (id % base) as u8 + min_char; + id /= base; + writer.write_all(&[digit])?; + if id == 0 { + break; + } + } + Ok(()) +} + +fn write_vcd_var( + writer: &mut W, + var_type: &str, + size: usize, + id: TraceScalarId, + name: &str, +) -> io::Result<()> { + write!(writer, "$var {var_type} {size} ")?; + write_scalar_id(writer, id)?; + writeln!(writer, " {name} $end") +} + +impl WriteTrace for TraceUInt { + fn write_trace(self, writer: &mut W, arg: A) -> io::Result<()> { + let ArgInType { + source_var_type, + sink_var_type, + duplex_var_type, + } = arg.in_type(); + let Self { id, name, ty, flow } = self; + let var_type = match flow { + Flow::Source => source_var_type, + Flow::Sink => sink_var_type, + Flow::Duplex => duplex_var_type, + }; + if ty.width() == 0 { + write_vcd_var(writer, "string", ty.width(), id, &name) + } else { + write_vcd_var(writer, var_type, ty.width(), id, &name) + } + } +} + +impl WriteTrace for TraceSInt { + fn write_trace(self, writer: &mut W, arg: A) -> io::Result<()> { + let Self { id, name, ty, flow } = self; + TraceUInt { + id, + name, + ty: UInt::new_dyn(ty.width()), + flow, + } + .write_trace(writer, arg) + } +} + +impl WriteTrace for TraceBool { + fn write_trace(self, writer: &mut W, arg: A) -> io::Result<()> { + let Self { id, name, flow } = self; + TraceUInt { + id, + name, + flow, + ty: UInt::new_dyn(1), + } + .write_trace(writer, arg) + } +} + +impl WriteTrace for TraceFieldlessEnum { + fn write_trace(self, writer: &mut W, arg: A) -> io::Result<()> { + todo!() + } +} + +impl WriteTrace for TraceEnumDiscriminant { + fn write_trace(self, writer: &mut W, arg: A) -> io::Result<()> { + todo!() + } +} + +impl WriteTrace for TraceClock { + fn write_trace(self, writer: &mut W, arg: A) -> io::Result<()> { + let Self { id, name, flow } = self; + TraceBool { id, name, flow }.write_trace(writer, arg) + } +} + +impl WriteTrace for TraceSyncReset { + fn write_trace(self, writer: &mut W, arg: A) -> io::Result<()> { + let Self { id, name, flow } = self; + TraceBool { id, name, flow }.write_trace(writer, arg) + } +} + +impl WriteTrace for TraceAsyncReset { + fn write_trace(self, writer: &mut W, arg: A) -> io::Result<()> { + let Self { id, name, flow } = self; + TraceBool { id, name, flow }.write_trace(writer, arg) + } +} + +impl WriteTrace for TraceScope { + fn write_trace(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::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(self, writer: &mut W, arg: A) -> io::Result<()> { + let ArgModule {} = arg.module(); + let Self { name, children } = self; + write_vcd_scope(writer, "module", &name, |writer| { + for child in children { + child.write_trace(writer, ArgModuleBody {})?; + } + Ok(()) + }) + } +} + +impl WriteTrace for TraceInstance { + fn write_trace(self, writer: &mut W, arg: A) -> io::Result<()> { + let ArgModuleBody {} = 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", + }, + )?; + module.write_trace(writer, ArgModule {}) + } +} + +impl WriteTrace for TraceMemPort { + fn write_trace(self, writer: &mut W, arg: A) -> io::Result<()> { + todo!() + } +} + +impl WriteTrace for TraceWire { + fn write_trace(self, writer: &mut W, arg: A) -> io::Result<()> { + let ArgModuleBody {} = 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", + }, + ) + } +} + +impl WriteTrace for TraceReg { + fn write_trace(self, writer: &mut W, arg: A) -> io::Result<()> { + let ArgModuleBody {} = 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", + }, + ) + } +} + +impl WriteTrace for TraceModuleIO { + fn write_trace(self, writer: &mut W, arg: A) -> io::Result<()> { + let ArgModuleBody {} = 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", + }, + ) + } +} + +impl WriteTrace for TraceBundle { + fn write_trace(self, writer: &mut W, arg: A) -> io::Result<()> { + let arg = arg.in_type(); + let Self { + name, + fields, + ty: _, + flow: _, + } = self; + write_vcd_scope(writer, "struct", &name, |writer| { + for field in fields { + field.write_trace(writer, arg)?; + } + Ok(()) + }) + } +} + +impl WriteTrace for TraceArray { + fn write_trace(self, writer: &mut W, arg: A) -> io::Result<()> { + let arg = arg.in_type(); + let Self { + name, + elements, + ty: _, + flow: _, + } = self; + write_vcd_scope(writer, "struct", &name, |writer| { + for element in elements { + element.write_trace(writer, arg)?; + } + Ok(()) + }) + } +} + +impl WriteTrace for TraceEnumWithFields { + fn write_trace(self, writer: &mut W, arg: A) -> io::Result<()> { + todo!() + } +} + +impl TraceWriterDecls for VcdWriterDecls { + type Error = io::Error; + type TraceWriter = VcdWriter; + + fn write_decls(self, module: TraceModule) -> Result { + let Self { + mut writer, + timescale, + } = self; + writeln!(writer, "$timescale {} $end", vcd_timescale(timescale))?; + module.write_trace(&mut writer, ArgModule {})?; + writeln!(writer, "$enddefinitions $end")?; + writeln!(writer, "$dumpvars")?; + Ok(VcdWriter { + writer, + finished_init: false, + timescale, + }) + } +} + +pub struct VcdWriter { + writer: W, + finished_init: bool, + timescale: SimDuration, +} + +impl VcdWriter { + pub fn timescale(&self) -> SimDuration { + self.timescale + } +} + +impl TraceWriter for VcdWriter { + type Error = io::Error; + + fn set_signal_uint(&mut self, id: TraceScalarId, value: &BitSlice) -> Result<(), Self::Error> { + match value.len() { + 0 => self.writer.write_all(b"s0 ")?, + 1 => write!(self.writer, "{} ", if value[0] { "1" } else { "0" })?, + _ => { + self.writer.write_all(b"b")?; + let mut any_ones = false; + for bit in value.iter().rev() { + if *bit { + any_ones = true; + self.writer.write_all(b"1")?; + } else if any_ones { + self.writer.write_all(b"0")?; + } + } + if !any_ones { + self.writer.write_all(b"0")?; + } + self.writer.write_all(b" ")?; + } + } + write_scalar_id(&mut self.writer, id)?; + self.writer.write_all(b"\n") + } + + fn set_signal_sint(&mut self, id: TraceScalarId, value: &BitSlice) -> Result<(), Self::Error> { + self.set_signal_uint(id, value) + } + + 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() + } +} + +impl fmt::Debug for VcdWriter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + writer: _, + finished_init, + timescale, + } = self; + f.debug_struct("VcdWriter") + .field("finished_init", finished_init) + .field("timescale", timescale) + .finish_non_exhaustive() + } +} diff --git a/crates/fayalite/src/util.rs b/crates/fayalite/src/util.rs index 131c54c..fadc7af 100644 --- a/crates/fayalite/src/util.rs +++ b/crates/fayalite/src/util.rs @@ -25,7 +25,7 @@ pub use scoped_ref::ScopedRef; #[doc(inline)] pub use misc::{ get_many_mut, interned_bit, iter_eq_by, BitSliceWriteWithBase, DebugAsDisplay, - DebugAsRawString, MakeMutSlice, + DebugAsRawString, MakeMutSlice, RcWriter, }; pub mod job_server; diff --git a/crates/fayalite/src/util/misc.rs b/crates/fayalite/src/util/misc.rs index e1cc9f4..f482eaa 100644 --- a/crates/fayalite/src/util/misc.rs +++ b/crates/fayalite/src/util/misc.rs @@ -3,6 +3,7 @@ use crate::intern::{Intern, Interned}; use bitvec::{bits, order::Lsb0, slice::BitSlice, view::BitView}; use std::{ + cell::Cell, fmt::{self, Debug, Write}, rc::Rc, sync::{Arc, OnceLock}, @@ -177,3 +178,50 @@ pub fn get_many_mut(slice: &mut [T], indexes: [usize; N]) -> std::array::from_fn(|i| &mut *base.add(indexes[i])) } } + +#[derive(Clone, Default)] +pub struct RcWriter(Rc>>); + +impl Debug for RcWriter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.borrow_impl(|buf| { + f.debug_tuple("RcWriter") + .field(&DebugAsDisplay(format_args!("b\"{}\"", buf.escape_ascii()))) + .finish() + }) + } +} + +impl RcWriter { + fn borrow_impl(&self, f: impl FnOnce(&mut Vec) -> R) -> R { + let buf = Cell::take(&self.0); + struct PutBackOnDrop<'a> { + buf: Vec, + this: &'a RcWriter, + } + impl Drop for PutBackOnDrop<'_> { + fn drop(&mut self) { + self.this.0.set(std::mem::take(&mut self.buf)); + } + } + let mut buf = PutBackOnDrop { buf, this: self }; + f(&mut buf.buf) + } + pub fn borrow(&mut self, f: impl FnOnce(&mut Vec) -> R) -> R { + self.borrow_impl(f) + } + pub fn take(&mut self) -> Vec { + Cell::take(&self.0) + } +} + +impl std::io::Write for RcWriter { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.borrow(|v| v.extend_from_slice(buf)); + Ok(buf.len()) + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} diff --git a/crates/fayalite/tests/sim.rs b/crates/fayalite/tests/sim.rs index 3576b1e..b21b5c0 100644 --- a/crates/fayalite/tests/sim.rs +++ b/crates/fayalite/tests/sim.rs @@ -1,6 +1,11 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information -use fayalite::{int::UIntValue, prelude::*, sim::Simulation}; +use fayalite::{ + int::UIntValue, + prelude::*, + sim::{time::SimDuration, vcd::VcdWriterDecls, Simulation}, + util::RcWriter, +}; #[hdl_module(outline_generated)] pub fn connect_const() { @@ -177,14 +182,25 @@ pub fn mod1() { connect(o, child); } +#[cfg(todo)] #[hdl] #[test] fn test_mod1() { let _n = SourceLocation::normalize_files_for_tests(); let mut sim = Simulation::new(mod1()); - sim.write_bool_or_int(sim.io().o.i, 0xA_hdl_u4); + let mut writer = RcWriter::default(); + sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); + sim.write_bool_or_int(sim.io().o.i, 0x3_hdl_u4); sim.write_bool_or_int(sim.io().o.i2, -2_hdl_i2); - sim.settle_step(); + sim.advance_time(SimDuration::from_micros(1)); + sim.write_bool_or_int(sim.io().o.i, 0xA_hdl_u4); + sim.advance_time(SimDuration::from_micros(1)); + let vcd = String::from_utf8(writer.take()).unwrap(); + println!("####### VCD:\n{vcd}\n#######"); + todo!("generated vcd is incorrect"); + if vcd != r#""# { + panic!(); + } let sim_debug = format!("{sim:#?}"); println!("#######\n{sim_debug}\n#######"); if sim_debug