WIP adding VCD output
All checks were successful
/ deps (push) Successful in 18s
/ test (push) Successful in 4m48s

This commit is contained in:
Jacob Lifshay 2024-11-17 01:02:00 -08:00
parent 414a2d74f1
commit 6eef3c23b5
Signed by: programmerjake
SSH key fingerprint: SHA256:B1iRVvUJkvd7upMIiMqn6OyxvD2SgJkAH3ZnUOj6z+c
3 changed files with 731 additions and 6 deletions

View file

@ -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<T: BundleType> {
insns: Interned<Insns<InsnsBuildingDone>>,
base_module: CompiledModule,
io: Instance<T>,
trace_decls: TraceModule,
traces: Interned<[SimTrace]>,
}
impl<T: BundleType> Compiled<T> {
@ -2781,11 +2794,15 @@ impl<T: BundleType> Compiled<T> {
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<Bundle>) -> Self {
@ -2793,21 +2810,471 @@ impl<T: BundleType> Compiled<T> {
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<str> {
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<str>,
children: Interned<[TraceDecl]>,
}),
Instance(TraceInstance {
fn children(self) -> _ {
self.children
}
name: Interned<str>,
children: Interned<[TraceDecl]>,
ty: Bundle,
flow: Flow,
}),
ModuleIO(TraceModuleIO {
fn children(self) -> _ {
self.children
}
name: Interned<str>,
children: Interned<[TraceDecl]>,
ty: Bundle,
flow: Flow,
}),
Bundle(TraceBundle {
fn children(self) -> _ {
self.children
}
name: Interned<str>,
children: Interned<[TraceDecl]>,
ty: Bundle,
flow: Flow,
}),
Array(TraceArray {
fn children(self) -> _ {
self.children
}
name: Interned<str>,
children: Interned<[TraceDecl]>,
ty: Array,
flow: Flow,
}),
EnumWithFields(TraceEnumWithFields {
fn children(self) -> _ {
self.children
}
name: Interned<str>,
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<str>,
ty: UInt,
flow: Flow,
}),
SInt(TraceSInt {
fn id(self) -> _ {
self.id
}
id: TraceScalarId,
name: Interned<str>,
ty: SInt,
flow: Flow,
}),
Bool(TraceBool {
fn id(self) -> _ {
self.id
}
id: TraceScalarId,
name: Interned<str>,
ty: Bool,
flow: Flow,
}),
FieldlessEnum(TraceFieldlessEnum {
fn id(self) -> _ {
self.id
}
id: TraceScalarId,
name: Interned<str>,
ty: Enum,
flow: Flow,
}),
EnumDiscriminant(TraceEnumDiscriminant {
fn id(self) -> _ {
self.id
}
id: TraceScalarId,
name: Interned<str>,
ty: Enum,
flow: Flow,
}),
Clock(TraceClock {
fn id(self) -> _ {
self.id
}
id: TraceScalarId,
name: Interned<str>,
ty: Clock,
flow: Flow,
}),
SyncReset(TraceSyncReset {
fn id(self) -> _ {
self.id
}
id: TraceScalarId,
name: Interned<str>,
ty: SyncReset,
flow: Flow,
}),
AsyncReset(TraceAsyncReset {
fn id(self) -> _ {
self.id
}
id: TraceScalarId,
name: Interned<str>,
ty: AsyncReset,
flow: Flow,
}),
}),
}
pub trait TraceWriterDecls: fmt::Debug + 'static + Sized {
type Error: std::error::Error + Send + Sync + 'static;
type TraceWriter: TraceWriter<Error = Self::Error>;
fn write_decls(self, module: TraceModule) -> Result<Self::TraceWriter, Self::Error>;
}
trait TraceWriterDeclsDynTrait: fmt::Debug {
fn write_decls_dyn(self: Box<Self>, module: TraceModule) -> std::io::Result<DynTraceWriter>;
}
fn err_into_io<E: std::error::Error + Send + Sync + 'static>(e: E) -> std::io::Error {
match <dyn std::error::Error + Send + Sync>::downcast::<std::io::Error>(Box::new(e)) {
Ok(retval) => *retval,
Err(e) => std::io::Error::other(e),
}
}
impl<T: TraceWriterDecls> TraceWriterDeclsDynTrait for T {
fn write_decls_dyn(self: Box<Self>, module: TraceModule) -> std::io::Result<DynTraceWriter> {
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<dyn TraceWriterDeclsDynTrait>);
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::TraceWriter, Self::Error> {
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<Self>) -> 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<T: TraceWriter> 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<Self>) -> 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<dyn TraceWriterDynTrait>);
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<T: TraceWriterDecls> {
Decls(T),
Init(T::TraceWriter),
Running(T::TraceWriter),
Errored(Option<T::Error>),
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
struct SimTrace {
id: TraceScalarId,
}
struct SimulationImpl {
state: interpreter::State,
io: Expr<Bundle>,
uninitialized_inputs: HashSet<Target>,
io_targets: HashMap<Target, CompiledValue<CanonicalType>>,
made_initial_step: bool,
trace_decls: TraceModule,
traces: Interned<[SimTrace]>,
trace_writers: Vec<TraceWriterState<DynTraceWriterDecls>>,
}
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<DynTraceWriterDecls>| 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<DynTraceWriterDecls>| {
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<T: BundleType> {
@ -3048,6 +3584,9 @@ impl<T: BundleType> fmt::Debug for Simulation<T> {
uninitialized_inputs,
io_targets,
made_initial_step,
trace_decls,
traces,
trace_writers,
},
io,
} = self;
@ -3060,6 +3599,9 @@ impl<T: BundleType> fmt::Debug for Simulation<T> {
)
.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<T: BundleType> Simulation<T> {
pub fn new(module: Interned<Module<T>>) -> 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<Bundle> {
let Self { sim_impl, io } = self;
Simulation {

View file

@ -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<Self> {
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<const UNITS_PER_SEC: u32>(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<Duration> for SimDuration {
fn from(duration: Duration) -> Self {
Self::from_nanos(duration.as_nanos())
}
}

View file

@ -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!();
}