diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index 90991b4..2eed11c 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -21,16 +21,21 @@ use crate::{ StmtWire, }, prelude::*, - sim::interpreter::{ - Insn, InsnField, InsnFieldKind, InsnFieldType, Insns, InsnsBuilding, InsnsBuildingDone, - SlotDebugData, SmallUInt, State, StatePartArrayIndex, StatePartArrayIndexed, - StatePartIndex, StatePartIndexRange, StatePartKind, StatePartKindBigSlots, - StatePartKindSmallSlots, StatePartLayout, StatePartLen, StatePartsValue, TypeArrayIndex, - TypeArrayIndexes, TypeIndex, TypeIndexRange, TypeLayout, TypeLen, TypeParts, + sim::{ + interpreter::{ + Insn, InsnField, InsnFieldKind, InsnFieldType, Insns, InsnsBuilding, InsnsBuildingDone, + SlotDebugData, SmallUInt, State, StatePartArrayIndex, StatePartArrayIndexed, + StatePartIndex, StatePartIndexRange, StatePartKind, StatePartKindBigSlots, + StatePartKindSmallSlots, StatePartLayout, StatePartLen, StatePartsValue, + TypeArrayIndex, TypeArrayIndexes, TypeIndex, TypeIndexRange, TypeLayout, TypeLen, + TypeParts, + }, + time::SimInstant, }, ty::StaticType, util::DebugAsDisplay, }; +use bitvec::{bits, order::Lsb0, slice::BitSlice}; use hashbrown::{HashMap, HashSet}; use num_bigint::BigInt; use num_traits::ToPrimitive; @@ -40,6 +45,7 @@ use petgraph::visit::{ use std::{borrow::Cow, collections::BTreeSet, fmt, marker::PhantomData, mem, ops::IndexMut}; mod interpreter; +pub mod time; #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] enum CondBody { @@ -2756,6 +2762,11 @@ impl Compiler { self.base_module, self.base_module.source_location(), ), + trace_decls: TraceModule { + name: self.base_module.name(), + children: Interned::default(), // TODO: finish + }, + traces: Interned::default(), // TODO: finish } } } @@ -2770,6 +2781,8 @@ pub struct Compiled { insns: Interned>, base_module: CompiledModule, io: Instance, + trace_decls: TraceModule, + traces: Interned<[SimTrace]>, } impl Compiled { @@ -2781,11 +2794,15 @@ impl Compiled { insns, base_module, io, + trace_decls, + traces, } = self; Compiled { insns, base_module, io: Instance::from_canonical(io.canonical()), + trace_decls, + traces, } } pub fn from_canonical(canonical: Compiled) -> Self { @@ -2793,21 +2810,471 @@ impl Compiled { insns, base_module, io, + trace_decls, + traces, } = canonical; Self { insns, base_module, io: Instance::from_canonical(io.canonical()), + trace_decls, + traces, } } } +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct TraceScalarId(usize); + +impl TraceScalarId { + pub fn as_usize(self) -> usize { + self.0 + } +} + +macro_rules! impl_trace_decl { + ( + $( + #[kind = $category_kind:ident] + $(#[$category_meta:meta])* + $category_variant:ident($category_enum:ident { + fn $category_property_fn:ident(self) -> $category_property_fn_ret_ty:ty; + $( + $(#[$meta:meta])* + $variant:ident($struct:ident { + fn $property_fn:ident($property_fn_self:ident) -> _ $property_fn_block:block + $($(#[$field_meta:meta])* + $field_name:ident: $field_ty:ty,)* + }), + )* + }), + )* + ) => { + $( + #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] + #[non_exhaustive] + $(#[$category_meta])* + pub enum $category_kind { + $($(#[$meta])* + $variant,)* + } + + impl From<$category_kind> for TraceKind { + fn from(v: $category_kind) -> Self { + TraceKind::$category_variant(v) + } + } + + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] + #[non_exhaustive] + $(#[$category_meta])* + pub enum $category_enum { + $($(#[$meta])* + $variant($struct),)* + } + + impl $category_enum { + pub fn kind(self) -> $category_kind { + match self { + $(Self::$variant(_) => $category_kind::$variant,)* + } + } + pub fn name(self) -> Interned { + match self { + $(Self::$variant(v) => v.name,)* + } + } + pub fn $category_property_fn(self) -> $category_property_fn_ret_ty { + match self { + $(Self::$variant(v) => v.$property_fn(),)* + } + } + } + + impl From<$category_enum> for TraceDecl { + fn from(v: $category_enum) -> Self { + TraceDecl::$category_variant(v) + } + } + + $( + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] + #[non_exhaustive] + $(#[$meta])* + pub struct $struct { + $($(#[$field_meta])* + pub $field_name: $field_ty,)* + } + + impl $struct { + pub fn $property_fn($property_fn_self) -> $category_property_fn_ret_ty $property_fn_block + } + + impl From<$struct> for $category_enum { + fn from(v: $struct) -> Self { + $category_enum::$variant(v) + } + } + + impl From<$struct> for TraceDecl { + fn from(v: $struct) -> Self { + TraceDecl::$category_variant($category_enum::$variant(v)) + } + } + )* + )* + + #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] + pub enum TraceKind { + $($(#[$category_meta])* + $category_variant($category_kind),)* + } + + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] + pub enum TraceDecl { + $($(#[$category_meta])* + $category_variant($category_enum),)* + } + }; +} + +impl_trace_decl! { + #[kind = TraceScopeKind] + Scope(TraceScope { + fn children(self) -> Interned<[TraceDecl]>; + Module(TraceModule { + fn children(self) -> _ { + self.children + } + name: Interned, + children: Interned<[TraceDecl]>, + }), + Instance(TraceInstance { + fn children(self) -> _ { + self.children + } + name: Interned, + children: Interned<[TraceDecl]>, + ty: Bundle, + flow: Flow, + }), + ModuleIO(TraceModuleIO { + fn children(self) -> _ { + self.children + } + name: Interned, + children: Interned<[TraceDecl]>, + ty: Bundle, + flow: Flow, + }), + Bundle(TraceBundle { + fn children(self) -> _ { + self.children + } + name: Interned, + children: Interned<[TraceDecl]>, + ty: Bundle, + flow: Flow, + }), + Array(TraceArray { + fn children(self) -> _ { + self.children + } + name: Interned, + children: Interned<[TraceDecl]>, + ty: Array, + flow: Flow, + }), + EnumWithFields(TraceEnumWithFields { + fn children(self) -> _ { + self.children + } + name: Interned, + children: Interned<[TraceDecl]>, + ty: Enum, + flow: Flow, + }), + }), + #[kind = TraceScalarKind] + Scalar(TraceScalar { + fn id(self) -> TraceScalarId; + UInt(TraceUInt { + fn id(self) -> _ { + self.id + } + id: TraceScalarId, + name: Interned, + ty: UInt, + flow: Flow, + }), + SInt(TraceSInt { + fn id(self) -> _ { + self.id + } + id: TraceScalarId, + name: Interned, + ty: SInt, + flow: Flow, + }), + Bool(TraceBool { + fn id(self) -> _ { + self.id + } + id: TraceScalarId, + name: Interned, + ty: Bool, + flow: Flow, + }), + FieldlessEnum(TraceFieldlessEnum { + fn id(self) -> _ { + self.id + } + id: TraceScalarId, + name: Interned, + ty: Enum, + flow: Flow, + }), + EnumDiscriminant(TraceEnumDiscriminant { + fn id(self) -> _ { + self.id + } + id: TraceScalarId, + name: Interned, + ty: Enum, + flow: Flow, + }), + Clock(TraceClock { + fn id(self) -> _ { + self.id + } + id: TraceScalarId, + name: Interned, + ty: Clock, + flow: Flow, + }), + SyncReset(TraceSyncReset { + fn id(self) -> _ { + self.id + } + id: TraceScalarId, + name: Interned, + ty: SyncReset, + flow: Flow, + }), + AsyncReset(TraceAsyncReset { + fn id(self) -> _ { + self.id + } + id: TraceScalarId, + name: Interned, + ty: AsyncReset, + flow: Flow, + }), + }), +} + +pub trait TraceWriterDecls: fmt::Debug + 'static + Sized { + type Error: std::error::Error + Send + Sync + 'static; + type TraceWriter: TraceWriter; + fn write_decls(self, module: TraceModule) -> Result; +} + +trait TraceWriterDeclsDynTrait: fmt::Debug { + fn write_decls_dyn(self: Box, module: TraceModule) -> std::io::Result; +} + +fn err_into_io(e: E) -> std::io::Error { + match ::downcast::(Box::new(e)) { + Ok(retval) => *retval, + Err(e) => std::io::Error::other(e), + } +} + +impl TraceWriterDeclsDynTrait for T { + fn write_decls_dyn(self: Box, module: TraceModule) -> std::io::Result { + Ok(DynTraceWriter(Box::new( + TraceWriterDecls::write_decls(*self, module).map_err(err_into_io)?, + ))) + } +} + +pub trait TraceWriter: fmt::Debug + 'static { + type Error: std::error::Error + Send + Sync + 'static; + fn finish_init(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + fn change_time_to(&mut self, instant: SimInstant) -> Result<(), Self::Error> { + let _ = instant; + Ok(()) + } + fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + fn close(self) -> Result<(), Self::Error> + where + Self: Sized, + { + Ok(()) + } + fn set_signal_uint(&mut self, id: TraceScalarId, value: &BitSlice) -> Result<(), Self::Error>; + fn set_signal_sint(&mut self, id: TraceScalarId, value: &BitSlice) -> Result<(), Self::Error>; + fn set_signal_bool(&mut self, id: TraceScalarId, value: bool) -> Result<(), Self::Error> { + if value { + self.set_signal_uint(id, bits![1]) + } else { + self.set_signal_uint(id, bits![0]) + } + } + fn set_signal_clock(&mut self, id: TraceScalarId, value: bool) -> Result<(), Self::Error> { + self.set_signal_bool(id, value) + } + fn set_signal_sync_reset(&mut self, id: TraceScalarId, value: bool) -> Result<(), Self::Error> { + self.set_signal_bool(id, value) + } + fn set_signal_async_reset( + &mut self, + id: TraceScalarId, + value: bool, + ) -> Result<(), Self::Error> { + self.set_signal_bool(id, value) + } +} + +pub struct DynTraceWriterDecls(Box); + +impl fmt::Debug for DynTraceWriterDecls { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl TraceWriterDecls for DynTraceWriterDecls { + type Error = std::io::Error; + type TraceWriter = DynTraceWriter; + fn write_decls(self, module: TraceModule) -> Result { + self.0.write_decls_dyn(module) + } +} + +trait TraceWriterDynTrait: fmt::Debug + 'static { + fn finish_init_dyn(&mut self) -> std::io::Result<()>; + fn change_time_to_dyn(&mut self, instant: SimInstant) -> std::io::Result<()>; + fn flush_dyn(&mut self) -> std::io::Result<()>; + fn close_dyn(self: Box) -> std::io::Result<()>; + fn set_signal_uint_dyn(&mut self, id: TraceScalarId, value: &BitSlice) -> std::io::Result<()>; + fn set_signal_sint_dyn(&mut self, id: TraceScalarId, value: &BitSlice) -> std::io::Result<()>; + fn set_signal_bool_dyn(&mut self, id: TraceScalarId, value: bool) -> std::io::Result<()>; + fn set_signal_clock_dyn(&mut self, id: TraceScalarId, value: bool) -> std::io::Result<()>; + fn set_signal_sync_reset_dyn(&mut self, id: TraceScalarId, value: bool) -> std::io::Result<()>; + fn set_signal_async_reset_dyn(&mut self, id: TraceScalarId, value: bool) + -> std::io::Result<()>; +} + +impl TraceWriterDynTrait for T { + fn finish_init_dyn(&mut self) -> std::io::Result<()> { + Ok(TraceWriter::finish_init(self).map_err(err_into_io)?) + } + fn change_time_to_dyn(&mut self, instant: SimInstant) -> std::io::Result<()> { + Ok(TraceWriter::change_time_to(self, instant).map_err(err_into_io)?) + } + fn flush_dyn(&mut self) -> std::io::Result<()> { + Ok(TraceWriter::flush(self).map_err(err_into_io)?) + } + fn close_dyn(self: Box) -> std::io::Result<()> { + Ok(TraceWriter::close(*self).map_err(err_into_io)?) + } + fn set_signal_uint_dyn(&mut self, id: TraceScalarId, value: &BitSlice) -> std::io::Result<()> { + Ok(TraceWriter::set_signal_uint(self, id, value).map_err(err_into_io)?) + } + fn set_signal_sint_dyn(&mut self, id: TraceScalarId, value: &BitSlice) -> std::io::Result<()> { + Ok(TraceWriter::set_signal_sint(self, id, value).map_err(err_into_io)?) + } + fn set_signal_bool_dyn(&mut self, id: TraceScalarId, value: bool) -> std::io::Result<()> { + Ok(TraceWriter::set_signal_bool(self, id, value).map_err(err_into_io)?) + } + fn set_signal_clock_dyn(&mut self, id: TraceScalarId, value: bool) -> std::io::Result<()> { + Ok(TraceWriter::set_signal_clock(self, id, value).map_err(err_into_io)?) + } + fn set_signal_sync_reset_dyn(&mut self, id: TraceScalarId, value: bool) -> std::io::Result<()> { + Ok(TraceWriter::set_signal_sync_reset(self, id, value).map_err(err_into_io)?) + } + fn set_signal_async_reset_dyn( + &mut self, + id: TraceScalarId, + value: bool, + ) -> std::io::Result<()> { + Ok(TraceWriter::set_signal_async_reset(self, id, value).map_err(err_into_io)?) + } +} + +pub struct DynTraceWriter(Box); + +impl fmt::Debug for DynTraceWriter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl TraceWriter for DynTraceWriter { + type Error = std::io::Error; + fn finish_init(&mut self) -> Result<(), Self::Error> { + self.0.finish_init_dyn() + } + fn flush(&mut self) -> Result<(), Self::Error> { + self.0.flush_dyn() + } + fn close(self) -> Result<(), Self::Error> { + self.0.close_dyn() + } + fn change_time_to(&mut self, instant: SimInstant) -> Result<(), Self::Error> { + self.0.change_time_to_dyn(instant) + } + fn set_signal_uint(&mut self, id: TraceScalarId, value: &BitSlice) -> Result<(), Self::Error> { + self.0.set_signal_uint_dyn(id, value) + } + fn set_signal_sint(&mut self, id: TraceScalarId, value: &BitSlice) -> Result<(), Self::Error> { + self.0.set_signal_sint_dyn(id, value) + } + fn set_signal_bool(&mut self, id: TraceScalarId, value: bool) -> Result<(), Self::Error> { + self.0.set_signal_bool_dyn(id, value) + } + fn set_signal_clock(&mut self, id: TraceScalarId, value: bool) -> Result<(), Self::Error> { + self.0.set_signal_clock_dyn(id, value) + } + fn set_signal_sync_reset(&mut self, id: TraceScalarId, value: bool) -> Result<(), Self::Error> { + self.0.set_signal_sync_reset_dyn(id, value) + } + fn set_signal_async_reset( + &mut self, + id: TraceScalarId, + value: bool, + ) -> Result<(), Self::Error> { + self.0.set_signal_async_reset_dyn(id, value) + } +} + +#[derive(Debug)] +enum TraceWriterState { + Decls(T), + Init(T::TraceWriter), + Running(T::TraceWriter), + Errored(Option), +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +struct SimTrace { + id: TraceScalarId, +} + struct SimulationImpl { state: interpreter::State, io: Expr, uninitialized_inputs: HashSet, io_targets: HashMap>, made_initial_step: bool, + trace_decls: TraceModule, + traces: Interned<[SimTrace]>, + trace_writers: Vec>, } impl SimulationImpl { @@ -2853,6 +3320,9 @@ impl SimulationImpl { uninitialized_inputs: HashSet::new(), io_targets: HashMap::new(), made_initial_step: false, + trace_decls: compiled.trace_decls, + traces: compiled.traces, + trace_writers: vec![], }; let io_target = Target::from(compiled.io); for (BundleField { name, .. }, value) in compiled @@ -2991,6 +3461,72 @@ impl SimulationImpl { _ => unreachable!(), } } + fn close_all_trace_writers(&mut self) -> std::io::Result<()> { + let trace_writers = mem::take(&mut self.trace_writers); + let mut retval = Ok(()); + let close_trace_writer = + |trace_writer: TraceWriterState| match trace_writer { + TraceWriterState::Decls(v) => v.write_decls(self.trace_decls)?.close(), + TraceWriterState::Init(v) => v.close(), + TraceWriterState::Running(v) => v.close(), + TraceWriterState::Errored(Some(e)) => Err(e), + TraceWriterState::Errored(None) => Ok(()), + }; + for trace_writer in trace_writers { + retval = retval.and(close_trace_writer(trace_writer)); + } + retval + } + fn close(mut self) -> std::io::Result<()> { + self.close_all_trace_writers() + } + fn flush_traces(&mut self) -> std::io::Result<()> { + let flush_trace_writer = |trace_writer: TraceWriterState| { + Ok(Some(match trace_writer { + TraceWriterState::Decls(v) => { + let mut v = v.write_decls(self.trace_decls)?; + v.flush()?; + TraceWriterState::Init(v) + } + TraceWriterState::Init(mut v) => { + v.flush()?; + TraceWriterState::Init(v) + } + TraceWriterState::Running(mut v) => { + v.flush()?; + TraceWriterState::Running(v) + } + TraceWriterState::Errored(Some(e)) => return Err(e), + TraceWriterState::Errored(None) => return Ok(None), + })) + }; + let mut retval = Ok(()); + for trace_writer in &mut self.trace_writers { + *trace_writer = match flush_trace_writer(mem::replace( + trace_writer, + TraceWriterState::Errored(None), + )) { + Ok(Some(v)) => v, + Ok(None) => TraceWriterState::Errored(None), + Err(e) => { + if retval.is_ok() { + retval = Err(e); + TraceWriterState::Errored(None) + } else { + TraceWriterState::Errored(Some(e)) + } + } + }; + } + retval + } +} + +impl Drop for SimulationImpl { + fn drop(&mut self) { + self.close_all_trace_writers() + .expect("error closing trace writers"); + } } pub struct Simulation { @@ -3048,6 +3584,9 @@ impl fmt::Debug for Simulation { uninitialized_inputs, io_targets, made_initial_step, + trace_decls, + traces, + trace_writers, }, io, } = self; @@ -3060,6 +3599,9 @@ impl fmt::Debug for Simulation { ) .field("io_targets", &SortedMapDebug(io_targets)) .field("made_initial_step", made_initial_step) + .field("trace_decls", trace_decls) + .field("traces", traces) + .field("trace_writers", trace_writers) .finish() } } @@ -3068,6 +3610,12 @@ impl Simulation { pub fn new(module: Interned>) -> Self { Self::from_compiled(Compiled::new(module)) } + pub fn flush_traces(&mut self) -> std::io::Result<()> { + self.sim_impl.flush_traces() + } + pub fn close(self) -> std::io::Result<()> { + self.sim_impl.close() + } pub fn canonical(self) -> Simulation { let Self { sim_impl, io } = self; Simulation { diff --git a/crates/fayalite/src/sim/time.rs b/crates/fayalite/src/sim/time.rs new file mode 100644 index 0000000..d548ab9 --- /dev/null +++ b/crates/fayalite/src/sim/time.rs @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information +use std::{fmt, time::Duration}; + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +pub struct SimInstant { + time_since_start: SimDuration, +} + +impl fmt::Debug for SimInstant { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.time_since_start.fmt(f) + } +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +pub struct SimDuration { + attos: u128, +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] +pub struct SimDurationParts { + pub attos: u16, + pub femtos: u16, + pub picos: u16, + pub nanos: u16, + pub micros: u16, + pub millis: u16, + pub secs: u128, +} + +macro_rules! impl_duration_units { + ( + $( + #[unit_const = $UNIT:ident, from_units = $from_units:ident, units = $units:ident, suffix = $suffix:literal] + const $log10_units_per_sec:ident: u32 = $log10_units_per_sec_value:expr; + )* + ) => { + impl SimDuration { + $( + const $log10_units_per_sec: u32 = $log10_units_per_sec_value; + pub const fn $from_units($units: u128) -> Self { + Self::from_units_helper::<{ Self::$log10_units_per_sec }>($units) + } + )* + pub const fn into_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) }; + )* + SimDurationParts { + $($units: $units as _,)* + } + } + pub const fn from_parts_checked(parts: SimDurationParts) -> Option { + let attos = 0u128; + $( + let Some(product) = const { 10u128.pow(Self::LOG10_ATTOS_PER_SEC - Self::$log10_units_per_sec) }.checked_mul(parts.$units as u128) else { + return None; + }; + let Some(attos) = attos.checked_add(product) else { + return None; + }; + )* + Some(Self { + attos, + }) + } + } + + impl fmt::Debug for SimDuration { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let ilog10_attos = match self.attos.checked_ilog10() { + Some(v) => v, + None => Self::LOG10_ATTOS_PER_SEC, + }; + let (suffix, int, fraction, fraction_digits) = + match Self::LOG10_ATTOS_PER_SEC.saturating_sub(ilog10_attos) { + $( + ..=Self::$log10_units_per_sec => { + let divisor = const { 10u128.pow(Self::LOG10_ATTOS_PER_SEC - Self::$log10_units_per_sec) }; + ( + $suffix, + self.attos / divisor, + self.attos % divisor, + (Self::LOG10_ATTOS_PER_SEC - Self::$log10_units_per_sec) as usize, + ) + }, + )* + _ => unreachable!(), + }; + write!(f, "{int}")?; + if fraction != 0 { + write!(f, ".{fraction:0fraction_digits$}")?; + } + write!(f, " {suffix}") + } + } + + #[cfg(test)] + #[test] + fn test_duration_debug() { + $( + assert_eq!( + format!("{:?}", SimDuration::$from_units(123)), + concat!("123 ", $suffix) + ); + assert_eq!( + format!("{:?}", SimDuration::$from_units(1)), + concat!("1 ", $suffix), + ); + let mut v = SimDuration::$from_units(1); + if v.attos < 1 << 53 { + v.attos += 1; + assert_eq!( + format!("{v:?}"), + format!("{} {}", v.attos as f64 / 10.0f64.powf((SimDuration::LOG10_ATTOS_PER_SEC - SimDuration::$log10_units_per_sec) as f64), $suffix), + "1 {} + 1 as == {} as", $suffix, v.attos, + ); + } + )* + } + }; +} + +impl_duration_units! { + #[unit_const = SECOND, from_units = from_secs, units = secs, suffix = "s"] + const LOG10_SECS_PER_SEC: u32 = 0; + #[unit_const = MILLISECOND, from_units = from_millis, units = millis, suffix = "ms"] + const LOG10_MILLIS_PER_SEC: u32 = 3; + #[unit_const = MICROSECOND, from_units = from_micros, units = micros, suffix = "μs"] + const LOG10_MICROS_PER_SEC: u32 = 6; + #[unit_const = NANOSECOND, from_units = from_nanos, units = nanos, suffix = "ns"] + const LOG10_NANOS_PER_SEC: u32 = 9; + #[unit_const = PICOSECOND, from_units = from_picos, units = picos, suffix = "ps"] + const LOG10_PICOS_PER_SEC: u32 = 12; + #[unit_const = FEMTOSECOND, from_units = from_femtos, units = femtos, suffix = "fs"] + const LOG10_FEMTOS_PER_SEC: u32 = 15; + #[unit_const = ATTOSECOND, from_units = from_attos, units = attos, suffix = "as"] + const LOG10_ATTOS_PER_SEC: u32 = 18; +} + +impl SimDuration { + const fn from_units_helper(units: u128) -> Self { + let Some(attos) = + units.checked_mul(const { 10u128.pow(Self::LOG10_ATTOS_PER_SEC - UNITS_PER_SEC) }) + else { + panic!("duration too big"); + }; + Self { attos } + } + pub const ZERO: SimDuration = SimDuration::from_secs(0); + pub const fn from_parts(parts: SimDurationParts) -> Self { + match Self::from_parts_checked(parts) { + Some(v) => v, + None => panic!("duration too big"), + } + } +} + +impl From for SimDuration { + fn from(duration: Duration) -> Self { + Self::from_nanos(duration.as_nanos()) + } +} diff --git a/crates/fayalite/tests/sim.rs b/crates/fayalite/tests/sim.rs index 1696d6a..fef71e5 100644 --- a/crates/fayalite/tests/sim.rs +++ b/crates/fayalite/tests/sim.rs @@ -115,6 +115,12 @@ fn test_connect_const() { }, }, made_initial_step: true, + trace_decls: TraceModule { + name: "connect_const", + children: [], + }, + traces: [], + trace_writers: [], }"# { panic!(); } @@ -673,6 +679,12 @@ fn test_mod1() { }, }, made_initial_step: true, + trace_decls: TraceModule { + name: "mod1", + children: [], + }, + traces: [], + trace_writers: [], }"# { panic!(); }