From a5a72844839b5dfea3c7092d4a50a9d9b2c3dfe9 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Fri, 21 Mar 2025 01:45:18 -0700 Subject: [PATCH] simple extern module simulation works! --- crates/fayalite/src/sim.rs | 451 ++++++++++++------ crates/fayalite/tests/sim.rs | 65 ++- .../fayalite/tests/sim/expected/array_rw.txt | 2 +- .../expected/conditional_assignment_last.txt | 2 +- .../tests/sim/expected/connect_const.txt | 2 +- .../sim/expected/connect_const_reset.txt | 2 +- .../tests/sim/expected/counter_async.txt | 2 +- .../tests/sim/expected/counter_sync.txt | 2 +- .../tests/sim/expected/duplicate_names.txt | 2 +- crates/fayalite/tests/sim/expected/enums.txt | 2 +- .../tests/sim/expected/extern_module.txt | 224 +++++++++ .../tests/sim/expected/extern_module.vcd | 51 ++ .../fayalite/tests/sim/expected/memories.txt | 2 +- .../fayalite/tests/sim/expected/memories2.txt | 2 +- .../fayalite/tests/sim/expected/memories3.txt | 2 +- crates/fayalite/tests/sim/expected/mod1.txt | 2 +- .../tests/sim/expected/shift_register.txt | 2 +- 17 files changed, 650 insertions(+), 167 deletions(-) create mode 100644 crates/fayalite/tests/sim/expected/extern_module.txt create mode 100644 crates/fayalite/tests/sim/expected/extern_module.vcd diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index 4c20644..a5d7d13 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -20,9 +20,10 @@ use crate::{ }, memory::PortKind, module::{ - transform::deduce_resets::deduce_resets, AnnotatedModuleIO, Block, Id, InstantiatedModule, - ModuleBody, NameId, NormalModuleBody, ScopedNameId, Stmt, StmtConnect, StmtDeclaration, - StmtFormal, StmtIf, StmtInstance, StmtMatch, StmtReg, StmtWire, TargetInInstantiatedModule, + transform::deduce_resets::deduce_resets, AnnotatedModuleIO, Block, ExternModuleBody, Id, + InstantiatedModule, ModuleBody, NameId, NormalModuleBody, ScopedNameId, Stmt, StmtConnect, + StmtDeclaration, StmtFormal, StmtIf, StmtInstance, StmtMatch, StmtReg, StmtWire, + TargetInInstantiatedModule, }, prelude::*, reset::{ResetType, ResetTypeDispatch}, @@ -1631,12 +1632,21 @@ impl fmt::Debug for DebugOpaque { } } +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +struct CompiledExternModule { + io_ty: Bundle, + module_io_targets: Interned<[Target]>, + module_io: Interned<[CompiledValue]>, + simulation: ExternModuleSimulation, +} + #[derive(Debug)] pub struct Compiler { insns: Insns, original_base_module: Interned>, base_module: Interned>, modules: HashMap, + extern_modules: Vec, compiled_values: HashMap>, compiled_exprs: HashMap, CompiledExpr>, compiled_exprs_to_values: HashMap, CompiledValue>, @@ -1665,6 +1675,7 @@ impl Compiler { original_base_module, base_module, modules: HashMap::new(), + extern_modules: Vec::new(), compiled_values: HashMap::new(), compiled_exprs: HashMap::new(), compiled_exprs_to_values: HashMap::new(), @@ -4690,8 +4701,28 @@ impl Compiler { ModuleBody::Normal(NormalModuleBody { body }) => { self.compile_block(module, body, Interned::default(), &mut trace_decls); } - ModuleBody::Extern(_extern_module_body) => { - todo!("simulating extern module: {:?}", module); + ModuleBody::Extern(ExternModuleBody { + verilog_name: _, + parameters: _, + simulation, + }) => { + let Some(simulation) = simulation else { + panic!( + "can't simulate extern module without extern_module_simulation: {}", + module.leaf_module().source_location() + ); + }; + self.extern_modules.push(CompiledExternModule { + io_ty: module.leaf_module().io_ty(), + module_io_targets: module + .leaf_module() + .module_io() + .iter() + .map(|v| Target::from(v.module_io)) + .collect(), + module_io, + simulation, + }); } } let hashbrown::hash_map::Entry::Vacant(entry) = self.modules.entry(*module) else { @@ -4972,6 +5003,7 @@ impl Compiler { Compiled { insns: Insns::from(self.insns).intern_sized(), base_module, + extern_modules: Intern::intern_owned(self.extern_modules), io: Instance::new_unchecked( ScopedNameId( NameId("".intern(), Id::new()), @@ -5004,6 +5036,7 @@ struct CompiledModule { pub struct Compiled { insns: Interned>, base_module: CompiledModule, + extern_modules: Interned<[CompiledExternModule]>, io: Instance, traces: SimTraces]>>, trace_memories: Interned<[(StatePartIndex, TraceMem)]>, @@ -5018,6 +5051,7 @@ impl Compiled { let Self { insns, base_module, + extern_modules, io, traces, trace_memories, @@ -5026,6 +5060,7 @@ impl Compiled { Compiled { insns, base_module, + extern_modules, io: Instance::from_canonical(io.canonical()), traces, trace_memories, @@ -5036,6 +5071,7 @@ impl Compiled { let Compiled { insns, base_module, + extern_modules, io, traces, trace_memories, @@ -5044,6 +5080,7 @@ impl Compiled { Self { insns, base_module, + extern_modules, io: Instance::from_canonical(io.canonical()), traces, trace_memories, @@ -6785,6 +6822,7 @@ impl Ord for WaitTarget { struct SimulationExternModuleState { module_state: SimulationModuleState, + io_ty: Bundle, sim: ExternModuleSimulation, running_generator: Option + 'static>>>, wait_target: Option, @@ -6794,12 +6832,14 @@ impl fmt::Debug for SimulationExternModuleState { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { module_state, + io_ty, sim, running_generator, wait_target, } = self; f.debug_struct("SimulationExternModuleState") .field("module_state", module_state) + .field("io_ty", io_ty) .field("sim", sim) .field( "running_generator", @@ -6894,12 +6934,29 @@ impl std::task::Wake for GeneratorWaker { } } +#[derive(Default)] +struct ReadyToRunSet { + state_ready_to_run: bool, + extern_modules_ready_to_run: Vec, +} + +impl ReadyToRunSet { + fn clear(&mut self) { + let Self { + state_ready_to_run, + extern_modules_ready_to_run, + } = self; + *state_ready_to_run = false; + extern_modules_ready_to_run.clear(); + } +} + struct SimulationImpl { state: interpreter::State, io: Expr, main_module: SimulationModuleState, extern_modules: Box<[SimulationExternModuleState]>, - needs_settle: bool, + state_ready_to_run: bool, trace_decls: TraceModule, traces: SimTraces]>>, trace_memories: HashMap, TraceMem>, @@ -6923,7 +6980,7 @@ impl SimulationImpl { io: self_io, main_module, extern_modules, - needs_settle, + state_ready_to_run, trace_decls, traces, trace_memories, @@ -6938,7 +6995,7 @@ impl SimulationImpl { .field("io", io.unwrap_or(self_io)) .field("main_module", main_module) .field("extern_modules", extern_modules) - .field("needs_settle", needs_settle) + .field("state_ready_to_run", state_ready_to_run) .field("trace_decls", trace_decls) .field("traces", traces) .field("trace_memories", trace_memories) @@ -6949,8 +7006,27 @@ impl SimulationImpl { } fn new(compiled: Compiled) -> Self { let io_target = Target::from(compiled.io); - // TODO: add extern_modules - let extern_modules: Box<[SimulationExternModuleState]> = Box::new([]); + let extern_modules = Box::from_iter(compiled.extern_modules.iter().map( + |&CompiledExternModule { + io_ty, + module_io_targets, + module_io, + simulation, + }| { + SimulationExternModuleState { + module_state: SimulationModuleState::new( + module_io_targets + .iter() + .copied() + .zip(module_io.iter().copied()), + ), + io_ty, + sim: simulation, + running_generator: None, + wait_target: Some(WaitTarget::Settle), + } + }, + )); Self { state: State::new(compiled.insns), io: compiled.io.to_expr(), @@ -6972,7 +7048,7 @@ impl SimulationImpl { }), ), extern_modules, - needs_settle: true, + state_ready_to_run: true, trace_decls: compiled.base_module.trace_decls, traces: SimTraces(Box::from_iter(compiled.traces.0.iter().map( |&SimTrace { @@ -7129,20 +7205,9 @@ impl SimulationImpl { } } #[track_caller] - fn advance_time(this: &Rc>, duration: SimDuration) { - Self::settle(this); - let mut this = this.borrow_mut(); - this.instant += duration; - this.for_each_trace_writer_storing_error(|this, mut trace_writer_state| { - match &mut trace_writer_state { - TraceWriterState::Decls(_) | TraceWriterState::Init(_) => unreachable!(), - TraceWriterState::Running(trace_writer) => { - trace_writer.change_time_to(this.instant)?; - } - TraceWriterState::Errored(_) => {} - } - Ok(trace_writer_state) - }); + fn advance_time(this_ref: &Rc>, duration: SimDuration) { + let instant = this_ref.borrow().instant + duration; + Self::run_until(this_ref, WaitTarget::Instant(instant)); } #[must_use] fn yield_advance_time_or_settle( @@ -7197,86 +7262,157 @@ impl SimulationImpl { target, } } - fn get_ready_external_modules(&mut self, run_list: &mut Vec) -> Option { - run_list.clear(); - let mut iter = self.extern_modules.iter_mut().enumerate().filter_map( - |(module_index, extern_module)| Some((module_index, extern_module.wait_target?)), - ); - let (first_module_index, mut wait_target) = iter.next()?; - run_list.push(first_module_index); - for (next_module_index, next_wait_target) in iter { - match next_wait_target.cmp(&wait_target) { - std::cmp::Ordering::Less => run_list.clear(), - std::cmp::Ordering::Equal => {} - std::cmp::Ordering::Greater => continue, - } - run_list.push(next_module_index); - wait_target = next_wait_target; + /// returns the next `WaitTarget` and the set of things ready to run then. + fn get_ready_to_run_set(&self, ready_to_run_set: &mut ReadyToRunSet) -> Option { + ready_to_run_set.clear(); + let mut wait_target = None; + if self.state_ready_to_run { + ready_to_run_set.state_ready_to_run = true; + wait_target = Some(WaitTarget::Settle); } - Some(wait_target) + for (module_index, extern_module) in self.extern_modules.iter().enumerate() { + let Some(extern_module_wait_target) = extern_module.wait_target else { + continue; + }; + if let Some(wait_target) = &mut wait_target { + match extern_module_wait_target.cmp(wait_target) { + std::cmp::Ordering::Less => ready_to_run_set.clear(), + std::cmp::Ordering::Equal => {} + std::cmp::Ordering::Greater => continue, + } + } else { + wait_target = Some(extern_module_wait_target); + } + ready_to_run_set + .extern_modules_ready_to_run + .push(module_index); + } + wait_target + } + fn set_instant_no_sim(&mut self, instant: SimInstant) { + self.instant = instant; + self.for_each_trace_writer_storing_error(|this, mut trace_writer_state| { + match &mut trace_writer_state { + TraceWriterState::Decls(_) | TraceWriterState::Init(_) => unreachable!(), + TraceWriterState::Running(trace_writer) => { + trace_writer.change_time_to(this.instant)?; + } + TraceWriterState::Errored(_) => {} + } + Ok(trace_writer_state) + }); } #[track_caller] - fn settle(this_ref: &Rc>) { + fn run_until(this_ref: &Rc>, run_target: WaitTarget) { let mut this = this_ref.borrow_mut(); + let mut ready_to_run_set = ReadyToRunSet::default(); + let generator_waker = this.generator_waker.clone(); assert!( this.main_module.uninitialized_ios.is_empty(), "didn't initialize all inputs", ); - let mut run_list = Vec::new(); - let generator_waker = this.generator_waker.clone(); - for _ in 0..100000 { - if !this.needs_settle { - if let Some(wait_target) = this.get_ready_external_modules(&mut run_list) { - if wait_target > WaitTarget::Instant(this.instant) { - return; - } - for module_index in run_list.drain(..) { - this.extern_modules[module_index].wait_target = None; - let Some(mut generator) = - this.extern_modules[module_index].running_generator.take() - else { - continue; - }; - drop(this); - let generator = match generator - .as_mut() - .poll(&mut std::task::Context::from_waker(&generator_waker)) - { - Poll::Ready(()) => None, - Poll::Pending => Some(generator), - }; - this = this_ref.borrow_mut(); - this.extern_modules[module_index].running_generator = generator; - } - continue; + match run_target { + WaitTarget::Settle => {} + WaitTarget::Instant(run_target) => assert!(run_target >= this.instant), + } + let mut settle_cycle = 0; + let mut run_extern_modules = true; + loop { + assert!(settle_cycle < 100000, "settle(): took too many steps"); + settle_cycle += 1; + let next_wait_target = match this.get_ready_to_run_set(&mut ready_to_run_set) { + Some(next_wait_target) if next_wait_target <= run_target => next_wait_target, + _ => break, + }; + match next_wait_target { + WaitTarget::Settle => {} + WaitTarget::Instant(instant) => { + settle_cycle = 0; + this.set_instant_no_sim(instant); } - return; } - this.state.setup_call(0); - if this.breakpoints.is_some() { - loop { - let this = &mut *this; - match this - .state - .run(this.breakpoints.as_mut().expect("just checked")) + if run_extern_modules { + for module_index in ready_to_run_set.extern_modules_ready_to_run.drain(..) { + let extern_module = &mut this.extern_modules[module_index]; + extern_module.wait_target = None; + let mut generator = if !extern_module.module_state.did_initial_settle { + let sim = extern_module.sim; + let io_ty = extern_module.io_ty; + drop(this); + Box::into_pin(sim.run(ExternModuleSimulationState { + sim_impl: this_ref.clone(), + module_index, + io_ty, + })) + } else if let Some(generator) = extern_module.running_generator.take() { + drop(this); + generator + } else { + continue; + }; + let generator = match generator + .as_mut() + .poll(&mut std::task::Context::from_waker(&generator_waker)) { - RunResult::Break(break_action) => { - println!( - "hit breakpoint at:\n{:?}", - this.state.debug_insn_at(this.state.pc), - ); - match break_action { - BreakAction::DumpStateAndContinue => { - println!("{this:#?}"); - } - BreakAction::Continue => {} - } - } - RunResult::Return(()) => break, + Poll::Ready(()) => None, + Poll::Pending => Some(generator), + }; + this = this_ref.borrow_mut(); + this.extern_modules[module_index] + .module_state + .did_initial_settle = true; + if !this.extern_modules[module_index] + .module_state + .uninitialized_ios + .is_empty() + { + panic!( + "extern module didn't initialize all outputs before \ + waiting, settling, or reading any inputs: {}", + this.extern_modules[module_index].sim.source_location + ); } + this.extern_modules[module_index].running_generator = generator; + } + } + if ready_to_run_set.state_ready_to_run { + this.state_ready_to_run = false; + run_extern_modules = true; + this.state.setup_call(0); + if this.breakpoints.is_some() { + loop { + let this = &mut *this; + match this + .state + .run(this.breakpoints.as_mut().expect("just checked")) + { + RunResult::Break(break_action) => { + println!( + "hit breakpoint at:\n{:?}", + this.state.debug_insn_at(this.state.pc), + ); + match break_action { + BreakAction::DumpStateAndContinue => { + println!("{this:#?}"); + } + BreakAction::Continue => {} + } + } + RunResult::Return(()) => break, + } + } + } else { + let RunResult::Return(()) = this.state.run(()); + } + if this + .clocks_triggered + .iter() + .any(|i| this.state.small_slots[*i] != 0) + { + this.state_ready_to_run = true; + // wait for clocks to settle before running extern modules again + run_extern_modules = false; } - } else { - let RunResult::Return(()) = this.state.run(()); } if this.main_module.did_initial_settle { this.read_traces::(); @@ -7286,10 +7422,6 @@ impl SimulationImpl { this.state.memory_write_log.sort_unstable(); this.state.memory_write_log.dedup(); this.main_module.did_initial_settle = true; - this.needs_settle = this - .clocks_triggered - .iter() - .any(|i| this.state.small_slots[*i] != 0); this.for_each_trace_writer_storing_error(|this, trace_writer_state| { Ok(match trace_writer_state { TraceWriterState::Decls(trace_writer_decls) => TraceWriterState::Running( @@ -7310,7 +7442,14 @@ impl SimulationImpl { }); this.state.memory_write_log.clear(); } - panic!("settle(): took too many steps"); + match run_target { + WaitTarget::Settle => {} + WaitTarget::Instant(instant) => this.set_instant_no_sim(instant), + } + } + #[track_caller] + fn settle(this_ref: &Rc>) { + Self::run_until(this_ref, WaitTarget::Settle); } fn get_module(&self, which_module: WhichModule) -> &SimulationModuleState { match which_module { @@ -7342,7 +7481,7 @@ impl SimulationImpl { let compiled_value = self .get_module_mut(which_module) .write_helper(io, which_module); - self.needs_settle = true; + self.state_ready_to_run = true; match compiled_value.range.len() { TypeLen::A_SMALL_SLOT => { self.state.small_slots[compiled_value.range.small_slots.start] = value as _; @@ -7374,7 +7513,7 @@ impl SimulationImpl { let compiled_value = self .get_module_mut(which_module) .write_helper(Expr::canonical(io), which_module); - self.needs_settle = true; + self.state_ready_to_run = true; let value: BigInt = value.into(); match compiled_value.range.len() { TypeLen::A_SMALL_SLOT => { @@ -7495,7 +7634,7 @@ impl SimulationImpl { let compiled_value = self .get_module_mut(which_module) .write_helper(io, which_module); - self.needs_settle = true; + self.state_ready_to_run = true; assert_eq!(Expr::ty(io), value.ty()); Self::read_write_sim_value_helper( &mut self.state, @@ -7531,7 +7670,6 @@ impl SimulationImpl { MaybeNeedsSettle::NoSettleNeeded(v) => v, } } - #[track_caller] async fn yield_settle_if_needed( this_ref: &Rc>, module_index: usize, @@ -7713,18 +7851,22 @@ impl fmt::Debug for Simulation { } macro_rules! impl_simulation_methods { - (async_await = ($($async:tt, $await:tt)?)) => { - #[track_caller] - pub $($async)? fn read_bool_or_int(&mut self, io: Expr) -> I::Value { - let retval = self + ( + async_await = ($($async:tt, $await:tt)?), + track_caller = ($(#[$track_caller:tt])?), + which_module = |$self:ident| $which_module:expr, + ) => { + $(#[$track_caller])? + pub $($async)? fn read_bool_or_int(&mut $self, io: Expr) -> I::Value { + let retval = $self .sim_impl .borrow_mut() - .read_bool_or_int(io, WhichModule::Main); - self.settle_if_needed(retval)$(.$await)? + .read_bool_or_int(io, $which_module); + $self.settle_if_needed(retval)$(.$await)? } - #[track_caller] + $(#[$track_caller])? pub $($async)? fn write_bool_or_int( - &mut self, + &mut $self, io: Expr, value: impl ToExpr, ) { @@ -7733,68 +7875,68 @@ macro_rules! impl_simulation_methods { let value = value .to_literal_bits() .expect("the value that is being written to an input must be a literal"); - self.sim_impl.borrow_mut().write_bool_or_int( + $self.sim_impl.borrow_mut().write_bool_or_int( io, I::bits_to_value(Cow::Borrowed(&value)), - WhichModule::Main, + $which_module, ); } - #[track_caller] - pub $($async)? fn write_clock(&mut self, io: Expr, value: bool) { - self.sim_impl + $(#[$track_caller])? + pub $($async)? fn write_clock(&mut $self, io: Expr, value: bool) { + $self.sim_impl .borrow_mut() - .write_bit(Expr::canonical(io), value, WhichModule::Main); + .write_bit(Expr::canonical(io), value, $which_module); } - #[track_caller] - pub $($async)? fn read_clock(&mut self, io: Expr) -> bool { - let retval = self + $(#[$track_caller])? + pub $($async)? fn read_clock(&mut $self, io: Expr) -> bool { + let retval = $self .sim_impl .borrow_mut() - .read_bit(Expr::canonical(io), WhichModule::Main); - self.settle_if_needed(retval)$(.$await)? + .read_bit(Expr::canonical(io), $which_module); + $self.settle_if_needed(retval)$(.$await)? } - #[track_caller] - pub $($async)? fn write_bool(&mut self, io: Expr, value: bool) { - self.sim_impl + $(#[$track_caller])? + pub $($async)? fn write_bool(&mut $self, io: Expr, value: bool) { + $self.sim_impl .borrow_mut() - .write_bit(Expr::canonical(io), value, WhichModule::Main); + .write_bit(Expr::canonical(io), value, $which_module); } - #[track_caller] - pub $($async)? fn read_bool(&mut self, io: Expr) -> bool { - let retval = self + $(#[$track_caller])? + pub $($async)? fn read_bool(&mut $self, io: Expr) -> bool { + let retval = $self .sim_impl .borrow_mut() - .read_bit(Expr::canonical(io), WhichModule::Main); - self.settle_if_needed(retval)$(.$await)? + .read_bit(Expr::canonical(io), $which_module); + $self.settle_if_needed(retval)$(.$await)? } - #[track_caller] - pub $($async)? fn write_reset(&mut self, io: Expr, value: bool) { - self.sim_impl + $(#[$track_caller])? + pub $($async)? fn write_reset(&mut $self, io: Expr, value: bool) { + $self.sim_impl .borrow_mut() - .write_bit(Expr::canonical(io), value, WhichModule::Main); + .write_bit(Expr::canonical(io), value, $which_module); } - #[track_caller] - pub $($async)? fn read_reset(&mut self, io: Expr) -> bool { - let retval = self + $(#[$track_caller])? + pub $($async)? fn read_reset(&mut $self, io: Expr) -> bool { + let retval = $self .sim_impl .borrow_mut() - .read_bit(Expr::canonical(io), WhichModule::Main); - self.settle_if_needed(retval)$(.$await)? + .read_bit(Expr::canonical(io), $which_module); + $self.settle_if_needed(retval)$(.$await)? } - #[track_caller] - pub $($async)? fn read(&mut self, io: Expr) -> SimValue { - let retval = self + $(#[$track_caller])? + pub $($async)? fn read(&mut $self, io: Expr) -> SimValue { + let retval = $self .sim_impl .borrow_mut() - .read(Expr::canonical(io), WhichModule::Main); - SimValue::from_canonical(self.settle_if_needed(retval)$(.$await)?) + .read(Expr::canonical(io), $which_module); + SimValue::from_canonical($self.settle_if_needed(retval)$(.$await)?) } - #[track_caller] - pub $($async)? fn write>(&mut self, io: Expr, value: V) { - self.sim_impl.borrow_mut().write( + $(#[$track_caller])? + pub $($async)? fn write>(&mut $self, io: Expr, value: V) { + $self.sim_impl.borrow_mut().write( Expr::canonical(io), value.into_sim_value(Expr::ty(io)).into_canonical(), - WhichModule::Main, + $which_module, ); } }; @@ -7855,7 +7997,11 @@ impl Simulation { { SimulationImpl::settle_if_needed(&self.sim_impl, v) } - impl_simulation_methods!(async_await = ()); + impl_simulation_methods!( + async_await = (), + track_caller = (#[track_caller]), + which_module = |self| WhichModule::Main, + ); #[doc(hidden)] /// This is explicitly unstable and may be changed/removed at any time pub fn set_breakpoints_unstable(&mut self, pcs: HashSet, trace: bool) { @@ -7913,12 +8059,10 @@ impl ExternModuleSimulationState { io_ty: T::from_canonical(io_ty.canonical()), } } - #[track_caller] pub async fn settle(&mut self) { SimulationImpl::yield_advance_time_or_settle(self.sim_impl.clone(), self.module_index, None) .await } - #[track_caller] pub async fn advance_time(&mut self, duration: SimDuration) { SimulationImpl::yield_advance_time_or_settle( self.sim_impl.clone(), @@ -7927,14 +8071,17 @@ impl ExternModuleSimulationState { ) .await } - #[track_caller] async fn settle_if_needed(&mut self, v: MaybeNeedsSettle) -> O where for<'a> F: MaybeNeedsSettleFn<&'a mut interpreter::State, Output = O>, { SimulationImpl::yield_settle_if_needed(&self.sim_impl, self.module_index, v).await } - impl_simulation_methods!(async_await = (async, await)); + impl_simulation_methods!( + async_await = (async, await), + track_caller = (), + which_module = |self| WhichModule::Extern { module_index: self.module_index }, + ); } pub trait ExternModuleSimGenerator: diff --git a/crates/fayalite/tests/sim.rs b/crates/fayalite/tests/sim.rs index 8c8a10f..0265a7a 100644 --- a/crates/fayalite/tests/sim.rs +++ b/crates/fayalite/tests/sim.rs @@ -5,11 +5,14 @@ use fayalite::{ int::UIntValue, prelude::*, reset::ResetType, - sim::{time::SimDuration, vcd::VcdWriterDecls, Simulation, ToSimValue}, + sim::{ + time::SimDuration, vcd::VcdWriterDecls, ExternModuleSimGenerator, + ExternModuleSimulationState, Simulation, ToSimValue, + }, ty::StaticType, util::RcWriter, }; -use std::num::NonZeroUsize; +use std::{future::IntoFuture, num::NonZeroUsize}; #[hdl_module(outline_generated)] pub fn connect_const() { @@ -1443,3 +1446,61 @@ fn test_conditional_assignment_last() { panic!(); } } + +#[hdl_module(outline_generated, extern)] +pub fn extern_module() { + #[hdl] + let i: Bool = m.input(); + #[hdl] + let o: Bool = m.output(); + #[derive(Clone, Eq, PartialEq, Hash, Debug)] + struct Sim { + i: Expr, + o: Expr, + } + impl ExternModuleSimGenerator for Sim { + type IOType = extern_module; + + fn run<'a>( + &'a self, + mut sim: ExternModuleSimulationState, + ) -> impl IntoFuture + 'a { + let Self { i, o } = *self; + async move { + sim.write(o, true).await; + sim.advance_time(SimDuration::from_nanos(500)).await; + let mut invert = false; + loop { + sim.advance_time(SimDuration::from_micros(1)).await; + let v = sim.read_bool(i).await; + sim.write(o, v ^ invert).await; + invert = !invert; + } + } + } + } + m.extern_module_simulation(Sim { i, o }); +} + +#[test] +fn test_extern_module() { + let _n = SourceLocation::normalize_files_for_tests(); + let mut sim = Simulation::new(extern_module()); + let mut writer = RcWriter::default(); + sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); + sim.write(sim.io().i, false); + sim.advance_time(SimDuration::from_micros(10)); + sim.write(sim.io().i, true); + sim.advance_time(SimDuration::from_micros(10)); + sim.flush_traces().unwrap(); + let vcd = String::from_utf8(writer.take()).unwrap(); + println!("####### VCD:\n{vcd}\n#######"); + if vcd != include_str!("sim/expected/extern_module.vcd") { + panic!(); + } + let sim_debug = format!("{sim:#?}"); + println!("#######\n{sim_debug}\n#######"); + if sim_debug != include_str!("sim/expected/extern_module.txt") { + panic!(); + } +} diff --git a/crates/fayalite/tests/sim/expected/array_rw.txt b/crates/fayalite/tests/sim/expected/array_rw.txt index 57d985e..34643f2 100644 --- a/crates/fayalite/tests/sim/expected/array_rw.txt +++ b/crates/fayalite/tests/sim/expected/array_rw.txt @@ -819,7 +819,7 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - needs_settle: false, + state_ready_to_run: false, trace_decls: TraceModule { name: "array_rw", children: [ diff --git a/crates/fayalite/tests/sim/expected/conditional_assignment_last.txt b/crates/fayalite/tests/sim/expected/conditional_assignment_last.txt index 45f10a5..c4242c4 100644 --- a/crates/fayalite/tests/sim/expected/conditional_assignment_last.txt +++ b/crates/fayalite/tests/sim/expected/conditional_assignment_last.txt @@ -115,7 +115,7 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - needs_settle: false, + state_ready_to_run: false, trace_decls: TraceModule { name: "conditional_assignment_last", children: [ diff --git a/crates/fayalite/tests/sim/expected/connect_const.txt b/crates/fayalite/tests/sim/expected/connect_const.txt index c8802f8..d357741 100644 --- a/crates/fayalite/tests/sim/expected/connect_const.txt +++ b/crates/fayalite/tests/sim/expected/connect_const.txt @@ -91,7 +91,7 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - needs_settle: false, + state_ready_to_run: false, trace_decls: TraceModule { name: "connect_const", children: [ diff --git a/crates/fayalite/tests/sim/expected/connect_const_reset.txt b/crates/fayalite/tests/sim/expected/connect_const_reset.txt index 8631d44..b3eb3ea 100644 --- a/crates/fayalite/tests/sim/expected/connect_const_reset.txt +++ b/crates/fayalite/tests/sim/expected/connect_const_reset.txt @@ -134,7 +134,7 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - needs_settle: false, + state_ready_to_run: false, trace_decls: TraceModule { name: "connect_const_reset", children: [ diff --git a/crates/fayalite/tests/sim/expected/counter_async.txt b/crates/fayalite/tests/sim/expected/counter_async.txt index 1e9012a..558d943 100644 --- a/crates/fayalite/tests/sim/expected/counter_async.txt +++ b/crates/fayalite/tests/sim/expected/counter_async.txt @@ -254,7 +254,7 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - needs_settle: false, + state_ready_to_run: false, trace_decls: TraceModule { name: "counter", children: [ diff --git a/crates/fayalite/tests/sim/expected/counter_sync.txt b/crates/fayalite/tests/sim/expected/counter_sync.txt index 029b076..d31db25 100644 --- a/crates/fayalite/tests/sim/expected/counter_sync.txt +++ b/crates/fayalite/tests/sim/expected/counter_sync.txt @@ -235,7 +235,7 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - needs_settle: false, + state_ready_to_run: false, trace_decls: TraceModule { name: "counter", children: [ diff --git a/crates/fayalite/tests/sim/expected/duplicate_names.txt b/crates/fayalite/tests/sim/expected/duplicate_names.txt index ac89814..5c6c18a 100644 --- a/crates/fayalite/tests/sim/expected/duplicate_names.txt +++ b/crates/fayalite/tests/sim/expected/duplicate_names.txt @@ -95,7 +95,7 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - needs_settle: false, + state_ready_to_run: false, trace_decls: TraceModule { name: "duplicate_names", children: [ diff --git a/crates/fayalite/tests/sim/expected/enums.txt b/crates/fayalite/tests/sim/expected/enums.txt index 1e73407..089ea31 100644 --- a/crates/fayalite/tests/sim/expected/enums.txt +++ b/crates/fayalite/tests/sim/expected/enums.txt @@ -1336,7 +1336,7 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - needs_settle: false, + state_ready_to_run: false, trace_decls: TraceModule { name: "enums", children: [ diff --git a/crates/fayalite/tests/sim/expected/extern_module.txt b/crates/fayalite/tests/sim/expected/extern_module.txt new file mode 100644 index 0000000..cb575a5 --- /dev/null +++ b/crates/fayalite/tests/sim/expected/extern_module.txt @@ -0,0 +1,224 @@ +Simulation { + state: State { + insns: Insns { + state_layout: StateLayout { + ty: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 2, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(extern_module: extern_module).extern_module::i", + ty: Bool, + }, + SlotDebugData { + name: "InstantiatedModule(extern_module: extern_module).extern_module::o", + ty: Bool, + }, + ], + .. + }, + }, + memories: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + insns: [ + // at: module-XXXXXXXXXX.rs:1:1 + 0: Return, + ], + .. + }, + pc: 0, + memory_write_log: [], + memories: StatePart { + value: [], + }, + small_slots: StatePart { + value: [], + }, + big_slots: StatePart { + value: [ + 1, + 1, + ], + }, + }, + io: Instance { + name: ::extern_module, + instantiated: Module { + name: extern_module, + .. + }, + }, + main_module: SimulationModuleState { + base_targets: [ + Instance { + name: ::extern_module, + instantiated: Module { + name: extern_module, + .. + }, + }.i, + Instance { + name: ::extern_module, + instantiated: Module { + name: extern_module, + .. + }, + }.o, + ], + uninitialized_ios: {}, + io_targets: { + Instance { + name: ::extern_module, + instantiated: Module { + name: extern_module, + .. + }, + }.i, + Instance { + name: ::extern_module, + instantiated: Module { + name: extern_module, + .. + }, + }.o, + }, + did_initial_settle: true, + }, + extern_modules: [ + SimulationExternModuleState { + module_state: SimulationModuleState { + base_targets: [ + ModuleIO { + name: extern_module::i, + is_input: true, + ty: Bool, + .. + }, + ModuleIO { + name: extern_module::o, + is_input: false, + ty: Bool, + .. + }, + ], + uninitialized_ios: {}, + io_targets: { + ModuleIO { + name: extern_module::i, + is_input: true, + ty: Bool, + .. + }, + ModuleIO { + name: extern_module::o, + is_input: false, + ty: Bool, + .. + }, + }, + did_initial_settle: true, + }, + io_ty: Bundle { + #[hdl(flip)] /* offset = 0 */ + i: Bool, + /* offset = 1 */ + o: Bool, + }, + sim: ExternModuleSimulation { + generator: Sim { + i: ModuleIO { + name: extern_module::i, + is_input: true, + ty: Bool, + .. + }, + o: ModuleIO { + name: extern_module::o, + is_input: false, + ty: Bool, + .. + }, + }, + source_location: SourceLocation( + module-XXXXXXXXXX.rs:4:1, + ), + _phantom: PhantomData, + }, + running_generator: Some( + ..., + ), + wait_target: Some( + Instant( + 20.500000000000 μs, + ), + ), + }, + ], + state_ready_to_run: false, + trace_decls: TraceModule { + name: "extern_module", + children: [ + TraceModuleIO { + name: "i", + child: TraceBool { + location: TraceScalarId(0), + name: "i", + flow: Source, + }, + ty: Bool, + flow: Source, + }, + TraceModuleIO { + name: "o", + child: TraceBool { + location: TraceScalarId(1), + name: "o", + flow: Sink, + }, + ty: Bool, + flow: Sink, + }, + ], + }, + traces: [ + SimTrace { + id: TraceScalarId(0), + kind: BigBool { + index: StatePartIndex(0), + }, + state: 0x1, + last_state: 0x1, + }, + SimTrace { + id: TraceScalarId(1), + kind: BigBool { + index: StatePartIndex(1), + }, + state: 0x1, + last_state: 0x1, + }, + ], + trace_memories: {}, + trace_writers: [ + Running( + VcdWriter { + finished_init: true, + timescale: 1 ps, + .. + }, + ), + ], + instant: 20 μs, + clocks_triggered: [], + .. +} \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/extern_module.vcd b/crates/fayalite/tests/sim/expected/extern_module.vcd new file mode 100644 index 0000000..e026a50 --- /dev/null +++ b/crates/fayalite/tests/sim/expected/extern_module.vcd @@ -0,0 +1,51 @@ +$timescale 1 ps $end +$scope module extern_module $end +$var wire 1 ! i $end +$var wire 1 " o $end +$upscope $end +$enddefinitions $end +$dumpvars +0! +1" +$end +#500000 +#1500000 +0" +#2500000 +1" +#3500000 +0" +#4500000 +1" +#5500000 +0" +#6500000 +1" +#7500000 +0" +#8500000 +1" +#9500000 +0" +#10000000 +1! +#10500000 +#11500000 +1" +#12500000 +0" +#13500000 +1" +#14500000 +0" +#15500000 +1" +#16500000 +0" +#17500000 +1" +#18500000 +0" +#19500000 +1" +#20000000 diff --git a/crates/fayalite/tests/sim/expected/memories.txt b/crates/fayalite/tests/sim/expected/memories.txt index e3b4f16..cd778d4 100644 --- a/crates/fayalite/tests/sim/expected/memories.txt +++ b/crates/fayalite/tests/sim/expected/memories.txt @@ -712,7 +712,7 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - needs_settle: false, + state_ready_to_run: false, trace_decls: TraceModule { name: "memories", children: [ diff --git a/crates/fayalite/tests/sim/expected/memories2.txt b/crates/fayalite/tests/sim/expected/memories2.txt index c838f36..2359749 100644 --- a/crates/fayalite/tests/sim/expected/memories2.txt +++ b/crates/fayalite/tests/sim/expected/memories2.txt @@ -670,7 +670,7 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - needs_settle: false, + state_ready_to_run: false, trace_decls: TraceModule { name: "memories2", children: [ diff --git a/crates/fayalite/tests/sim/expected/memories3.txt b/crates/fayalite/tests/sim/expected/memories3.txt index 01bca44..ad12aa4 100644 --- a/crates/fayalite/tests/sim/expected/memories3.txt +++ b/crates/fayalite/tests/sim/expected/memories3.txt @@ -1754,7 +1754,7 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - needs_settle: false, + state_ready_to_run: false, trace_decls: TraceModule { name: "memories3", children: [ diff --git a/crates/fayalite/tests/sim/expected/mod1.txt b/crates/fayalite/tests/sim/expected/mod1.txt index f783171..3656247 100644 --- a/crates/fayalite/tests/sim/expected/mod1.txt +++ b/crates/fayalite/tests/sim/expected/mod1.txt @@ -267,7 +267,7 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - needs_settle: false, + state_ready_to_run: false, trace_decls: TraceModule { name: "mod1", children: [ diff --git a/crates/fayalite/tests/sim/expected/shift_register.txt b/crates/fayalite/tests/sim/expected/shift_register.txt index e2ca6e2..901cf70 100644 --- a/crates/fayalite/tests/sim/expected/shift_register.txt +++ b/crates/fayalite/tests/sim/expected/shift_register.txt @@ -330,7 +330,7 @@ Simulation { did_initial_settle: true, }, extern_modules: [], - needs_settle: false, + state_ready_to_run: false, trace_decls: TraceModule { name: "shift_register", children: [