forked from libre-chip/fayalite
		
	WIP adding VCD output
This commit is contained in:
		
							parent
							
								
									09aa9fbc78
								
							
						
					
					
						commit
						c4b5d00419
					
				
					 6 changed files with 801 additions and 14 deletions
				
			
		|  | @ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue