WIP adding VCD output
This commit is contained in:
parent
904752fa0c
commit
16ea6850c8
|
@ -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 {
|
||||
|
|
|
@ -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<Self> {
|
||||
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<SimDuration> {
|
||||
self.time_since_start.checked_sub(earlier.time_since_start)
|
||||
}
|
||||
pub const fn checked_sub(self, duration: SimDuration) -> Option<Self> {
|
||||
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<SimDuration> for SimInstant {
|
||||
type Output = SimInstant;
|
||||
|
||||
#[track_caller]
|
||||
fn add(mut self, rhs: SimDuration) -> Self::Output {
|
||||
self += rhs;
|
||||
self
|
||||
|
@ -21,6 +55,7 @@ impl Add<SimDuration> for SimInstant {
|
|||
}
|
||||
|
||||
impl AddAssign<SimDuration> for SimInstant {
|
||||
#[track_caller]
|
||||
fn add_assign(&mut self, rhs: SimDuration) {
|
||||
self.time_since_start += rhs;
|
||||
}
|
||||
|
@ -29,11 +64,40 @@ impl AddAssign<SimDuration> for SimInstant {
|
|||
impl Add<SimInstant> 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<SimDuration> 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<SimDuration> 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<Self> {
|
||||
let Some(attos) = self.attos.checked_add(rhs.attos) else {
|
||||
return None;
|
||||
};
|
||||
Some(Self { attos })
|
||||
}
|
||||
pub const fn checked_sub(self, rhs: Self) -> Option<Self> {
|
||||
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<i32> {
|
||||
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<Self> {
|
||||
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<Duration> for SimDuration {
|
||||
|
|
542
crates/fayalite/src/sim/vcd.rs
Normal file
542
crates/fayalite/src/sim/vcd.rs
Normal file
|
@ -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<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()
|
||||
}
|
||||
}
|
||||
|
||||
fn write_vcd_scope<W: io::Write, R>(
|
||||
writer: &mut W,
|
||||
scope_type: &str,
|
||||
scope_name: &str,
|
||||
f: impl FnOnce(&mut W) -> io::Result<R>,
|
||||
) -> io::Result<R> {
|
||||
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<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_scalar_id<W: io::Write>(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<W: io::Write>(
|
||||
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<W: io::Write, A: Arg>(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<W: io::Write, A: Arg>(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<W: io::Write, A: Arg>(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<W: io::Write, A: Arg>(self, writer: &mut W, arg: A) -> io::Result<()> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl WriteTrace for TraceEnumDiscriminant {
|
||||
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, arg: A) -> io::Result<()> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl WriteTrace for TraceClock {
|
||||
fn write_trace<W: io::Write, A: Arg>(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<W: io::Write, A: Arg>(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<W: io::Write, A: Arg>(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<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::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, 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<W: io::Write, A: Arg>(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<W: io::Write, A: Arg>(self, writer: &mut W, arg: A) -> io::Result<()> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl WriteTrace for TraceWire {
|
||||
fn write_trace<W: io::Write, A: Arg>(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<W: io::Write, A: Arg>(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<W: io::Write, A: Arg>(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<W: io::Write, A: Arg>(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<W: io::Write, A: Arg>(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<W: io::Write, A: Arg>(self, writer: &mut W, arg: A) -> io::Result<()> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: io::Write> TraceWriterDecls for VcdWriterDecls<W> {
|
||||
type Error = io::Error;
|
||||
type TraceWriter = VcdWriter<W>;
|
||||
|
||||
fn write_decls(self, module: TraceModule) -> Result<Self::TraceWriter, Self::Error> {
|
||||
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<W: io::Write + 'static> {
|
||||
writer: W,
|
||||
finished_init: bool,
|
||||
timescale: SimDuration,
|
||||
}
|
||||
|
||||
impl<W: io::Write + 'static> VcdWriter<W> {
|
||||
pub fn timescale(&self) -> SimDuration {
|
||||
self.timescale
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: io::Write> TraceWriter for VcdWriter<W> {
|
||||
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<W: io::Write> fmt::Debug for VcdWriter<W> {
|
||||
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()
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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<T, const N: usize>(slice: &mut [T], indexes: [usize; N]) ->
|
|||
std::array::from_fn(|i| &mut *base.add(indexes[i]))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct RcWriter(Rc<Cell<Vec<u8>>>);
|
||||
|
||||
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<R>(&self, f: impl FnOnce(&mut Vec<u8>) -> R) -> R {
|
||||
let buf = Cell::take(&self.0);
|
||||
struct PutBackOnDrop<'a> {
|
||||
buf: Vec<u8>,
|
||||
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<R>(&mut self, f: impl FnOnce(&mut Vec<u8>) -> R) -> R {
|
||||
self.borrow_impl(f)
|
||||
}
|
||||
pub fn take(&mut self) -> Vec<u8> {
|
||||
Cell::take(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::io::Write for RcWriter {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
self.borrow(|v| v.extend_from_slice(buf));
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue