simple extern module simulation works!

This commit is contained in:
Jacob Lifshay 2025-03-21 01:45:18 -07:00
parent f378b9a1d2
commit a5a7284483
Signed by: programmerjake
SSH key fingerprint: SHA256:HnFTLGpSm4Q4Fj502oCFisjZSoakwEuTsJJMSke63RQ
17 changed files with 650 additions and 167 deletions

View file

@ -20,9 +20,10 @@ use crate::{
}, },
memory::PortKind, memory::PortKind,
module::{ module::{
transform::deduce_resets::deduce_resets, AnnotatedModuleIO, Block, Id, InstantiatedModule, transform::deduce_resets::deduce_resets, AnnotatedModuleIO, Block, ExternModuleBody, Id,
ModuleBody, NameId, NormalModuleBody, ScopedNameId, Stmt, StmtConnect, StmtDeclaration, InstantiatedModule, ModuleBody, NameId, NormalModuleBody, ScopedNameId, Stmt, StmtConnect,
StmtFormal, StmtIf, StmtInstance, StmtMatch, StmtReg, StmtWire, TargetInInstantiatedModule, StmtDeclaration, StmtFormal, StmtIf, StmtInstance, StmtMatch, StmtReg, StmtWire,
TargetInInstantiatedModule,
}, },
prelude::*, prelude::*,
reset::{ResetType, ResetTypeDispatch}, reset::{ResetType, ResetTypeDispatch},
@ -1631,12 +1632,21 @@ impl<T> fmt::Debug for DebugOpaque<T> {
} }
} }
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
struct CompiledExternModule {
io_ty: Bundle,
module_io_targets: Interned<[Target]>,
module_io: Interned<[CompiledValue<CanonicalType>]>,
simulation: ExternModuleSimulation<Bundle>,
}
#[derive(Debug)] #[derive(Debug)]
pub struct Compiler { pub struct Compiler {
insns: Insns<InsnsBuilding>, insns: Insns<InsnsBuilding>,
original_base_module: Interned<Module<Bundle>>, original_base_module: Interned<Module<Bundle>>,
base_module: Interned<Module<Bundle>>, base_module: Interned<Module<Bundle>>,
modules: HashMap<InstantiatedModule, CompiledModule>, modules: HashMap<InstantiatedModule, CompiledModule>,
extern_modules: Vec<CompiledExternModule>,
compiled_values: HashMap<TargetInInstantiatedModule, CompiledValue<CanonicalType>>, compiled_values: HashMap<TargetInInstantiatedModule, CompiledValue<CanonicalType>>,
compiled_exprs: HashMap<Expr<CanonicalType>, CompiledExpr<CanonicalType>>, compiled_exprs: HashMap<Expr<CanonicalType>, CompiledExpr<CanonicalType>>,
compiled_exprs_to_values: HashMap<CompiledExpr<CanonicalType>, CompiledValue<CanonicalType>>, compiled_exprs_to_values: HashMap<CompiledExpr<CanonicalType>, CompiledValue<CanonicalType>>,
@ -1665,6 +1675,7 @@ impl Compiler {
original_base_module, original_base_module,
base_module, base_module,
modules: HashMap::new(), modules: HashMap::new(),
extern_modules: Vec::new(),
compiled_values: HashMap::new(), compiled_values: HashMap::new(),
compiled_exprs: HashMap::new(), compiled_exprs: HashMap::new(),
compiled_exprs_to_values: HashMap::new(), compiled_exprs_to_values: HashMap::new(),
@ -4690,8 +4701,28 @@ impl Compiler {
ModuleBody::Normal(NormalModuleBody { body }) => { ModuleBody::Normal(NormalModuleBody { body }) => {
self.compile_block(module, body, Interned::default(), &mut trace_decls); self.compile_block(module, body, Interned::default(), &mut trace_decls);
} }
ModuleBody::Extern(_extern_module_body) => { ModuleBody::Extern(ExternModuleBody {
todo!("simulating extern module: {:?}", module); 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 { let hashbrown::hash_map::Entry::Vacant(entry) = self.modules.entry(*module) else {
@ -4972,6 +5003,7 @@ impl Compiler {
Compiled { Compiled {
insns: Insns::from(self.insns).intern_sized(), insns: Insns::from(self.insns).intern_sized(),
base_module, base_module,
extern_modules: Intern::intern_owned(self.extern_modules),
io: Instance::new_unchecked( io: Instance::new_unchecked(
ScopedNameId( ScopedNameId(
NameId("<simulator>".intern(), Id::new()), NameId("<simulator>".intern(), Id::new()),
@ -5004,6 +5036,7 @@ struct CompiledModule {
pub struct Compiled<T: BundleType> { pub struct Compiled<T: BundleType> {
insns: Interned<Insns<InsnsBuildingDone>>, insns: Interned<Insns<InsnsBuildingDone>>,
base_module: CompiledModule, base_module: CompiledModule,
extern_modules: Interned<[CompiledExternModule]>,
io: Instance<T>, io: Instance<T>,
traces: SimTraces<Interned<[SimTrace<SimTraceKind, ()>]>>, traces: SimTraces<Interned<[SimTrace<SimTraceKind, ()>]>>,
trace_memories: Interned<[(StatePartIndex<StatePartKindMemories>, TraceMem)]>, trace_memories: Interned<[(StatePartIndex<StatePartKindMemories>, TraceMem)]>,
@ -5018,6 +5051,7 @@ impl<T: BundleType> Compiled<T> {
let Self { let Self {
insns, insns,
base_module, base_module,
extern_modules,
io, io,
traces, traces,
trace_memories, trace_memories,
@ -5026,6 +5060,7 @@ impl<T: BundleType> Compiled<T> {
Compiled { Compiled {
insns, insns,
base_module, base_module,
extern_modules,
io: Instance::from_canonical(io.canonical()), io: Instance::from_canonical(io.canonical()),
traces, traces,
trace_memories, trace_memories,
@ -5036,6 +5071,7 @@ impl<T: BundleType> Compiled<T> {
let Compiled { let Compiled {
insns, insns,
base_module, base_module,
extern_modules,
io, io,
traces, traces,
trace_memories, trace_memories,
@ -5044,6 +5080,7 @@ impl<T: BundleType> Compiled<T> {
Self { Self {
insns, insns,
base_module, base_module,
extern_modules,
io: Instance::from_canonical(io.canonical()), io: Instance::from_canonical(io.canonical()),
traces, traces,
trace_memories, trace_memories,
@ -6785,6 +6822,7 @@ impl Ord for WaitTarget {
struct SimulationExternModuleState { struct SimulationExternModuleState {
module_state: SimulationModuleState, module_state: SimulationModuleState,
io_ty: Bundle,
sim: ExternModuleSimulation<Bundle>, sim: ExternModuleSimulation<Bundle>,
running_generator: Option<Pin<Box<dyn Future<Output = ()> + 'static>>>, running_generator: Option<Pin<Box<dyn Future<Output = ()> + 'static>>>,
wait_target: Option<WaitTarget>, wait_target: Option<WaitTarget>,
@ -6794,12 +6832,14 @@ impl fmt::Debug for SimulationExternModuleState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self { let Self {
module_state, module_state,
io_ty,
sim, sim,
running_generator, running_generator,
wait_target, wait_target,
} = self; } = self;
f.debug_struct("SimulationExternModuleState") f.debug_struct("SimulationExternModuleState")
.field("module_state", module_state) .field("module_state", module_state)
.field("io_ty", io_ty)
.field("sim", sim) .field("sim", sim)
.field( .field(
"running_generator", "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<usize>,
}
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 { struct SimulationImpl {
state: interpreter::State, state: interpreter::State,
io: Expr<Bundle>, io: Expr<Bundle>,
main_module: SimulationModuleState, main_module: SimulationModuleState,
extern_modules: Box<[SimulationExternModuleState]>, extern_modules: Box<[SimulationExternModuleState]>,
needs_settle: bool, state_ready_to_run: bool,
trace_decls: TraceModule, trace_decls: TraceModule,
traces: SimTraces<Box<[SimTrace<SimTraceKind, BitVec>]>>, traces: SimTraces<Box<[SimTrace<SimTraceKind, BitVec>]>>,
trace_memories: HashMap<StatePartIndex<StatePartKindMemories>, TraceMem>, trace_memories: HashMap<StatePartIndex<StatePartKindMemories>, TraceMem>,
@ -6923,7 +6980,7 @@ impl SimulationImpl {
io: self_io, io: self_io,
main_module, main_module,
extern_modules, extern_modules,
needs_settle, state_ready_to_run,
trace_decls, trace_decls,
traces, traces,
trace_memories, trace_memories,
@ -6938,7 +6995,7 @@ impl SimulationImpl {
.field("io", io.unwrap_or(self_io)) .field("io", io.unwrap_or(self_io))
.field("main_module", main_module) .field("main_module", main_module)
.field("extern_modules", extern_modules) .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("trace_decls", trace_decls)
.field("traces", traces) .field("traces", traces)
.field("trace_memories", trace_memories) .field("trace_memories", trace_memories)
@ -6949,8 +7006,27 @@ impl SimulationImpl {
} }
fn new(compiled: Compiled<Bundle>) -> Self { fn new(compiled: Compiled<Bundle>) -> Self {
let io_target = Target::from(compiled.io); let io_target = Target::from(compiled.io);
// TODO: add extern_modules let extern_modules = Box::from_iter(compiled.extern_modules.iter().map(
let extern_modules: Box<[SimulationExternModuleState]> = Box::new([]); |&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 { Self {
state: State::new(compiled.insns), state: State::new(compiled.insns),
io: compiled.io.to_expr(), io: compiled.io.to_expr(),
@ -6972,7 +7048,7 @@ impl SimulationImpl {
}), }),
), ),
extern_modules, extern_modules,
needs_settle: true, state_ready_to_run: true,
trace_decls: compiled.base_module.trace_decls, trace_decls: compiled.base_module.trace_decls,
traces: SimTraces(Box::from_iter(compiled.traces.0.iter().map( traces: SimTraces(Box::from_iter(compiled.traces.0.iter().map(
|&SimTrace { |&SimTrace {
@ -7129,20 +7205,9 @@ impl SimulationImpl {
} }
} }
#[track_caller] #[track_caller]
fn advance_time(this: &Rc<RefCell<Self>>, duration: SimDuration) { fn advance_time(this_ref: &Rc<RefCell<Self>>, duration: SimDuration) {
Self::settle(this); let instant = this_ref.borrow().instant + duration;
let mut this = this.borrow_mut(); Self::run_until(this_ref, WaitTarget::Instant(instant));
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)
});
} }
#[must_use] #[must_use]
fn yield_advance_time_or_settle( fn yield_advance_time_or_settle(
@ -7197,86 +7262,157 @@ impl SimulationImpl {
target, target,
} }
} }
fn get_ready_external_modules(&mut self, run_list: &mut Vec<usize>) -> Option<WaitTarget> { /// returns the next `WaitTarget` and the set of things ready to run then.
run_list.clear(); fn get_ready_to_run_set(&self, ready_to_run_set: &mut ReadyToRunSet) -> Option<WaitTarget> {
let mut iter = self.extern_modules.iter_mut().enumerate().filter_map( ready_to_run_set.clear();
|(module_index, extern_module)| Some((module_index, extern_module.wait_target?)), let mut wait_target = None;
); if self.state_ready_to_run {
let (first_module_index, mut wait_target) = iter.next()?; ready_to_run_set.state_ready_to_run = true;
run_list.push(first_module_index); wait_target = Some(WaitTarget::Settle);
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;
} }
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] #[track_caller]
fn settle(this_ref: &Rc<RefCell<Self>>) { fn run_until(this_ref: &Rc<RefCell<Self>>, run_target: WaitTarget) {
let mut this = this_ref.borrow_mut(); let mut this = this_ref.borrow_mut();
let mut ready_to_run_set = ReadyToRunSet::default();
let generator_waker = this.generator_waker.clone();
assert!( assert!(
this.main_module.uninitialized_ios.is_empty(), this.main_module.uninitialized_ios.is_empty(),
"didn't initialize all inputs", "didn't initialize all inputs",
); );
let mut run_list = Vec::new(); match run_target {
let generator_waker = this.generator_waker.clone(); WaitTarget::Settle => {}
for _ in 0..100000 { WaitTarget::Instant(run_target) => assert!(run_target >= this.instant),
if !this.needs_settle { }
if let Some(wait_target) = this.get_ready_external_modules(&mut run_list) { let mut settle_cycle = 0;
if wait_target > WaitTarget::Instant(this.instant) { let mut run_extern_modules = true;
return; loop {
} assert!(settle_cycle < 100000, "settle(): took too many steps");
for module_index in run_list.drain(..) { settle_cycle += 1;
this.extern_modules[module_index].wait_target = None; let next_wait_target = match this.get_ready_to_run_set(&mut ready_to_run_set) {
let Some(mut generator) = Some(next_wait_target) if next_wait_target <= run_target => next_wait_target,
this.extern_modules[module_index].running_generator.take() _ => break,
else { };
continue; match next_wait_target {
}; WaitTarget::Settle => {}
drop(this); WaitTarget::Instant(instant) => {
let generator = match generator settle_cycle = 0;
.as_mut() this.set_instant_no_sim(instant);
.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;
} }
return;
} }
this.state.setup_call(0); if run_extern_modules {
if this.breakpoints.is_some() { for module_index in ready_to_run_set.extern_modules_ready_to_run.drain(..) {
loop { let extern_module = &mut this.extern_modules[module_index];
let this = &mut *this; extern_module.wait_target = None;
match this let mut generator = if !extern_module.module_state.did_initial_settle {
.state let sim = extern_module.sim;
.run(this.breakpoints.as_mut().expect("just checked")) 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) => { Poll::Ready(()) => None,
println!( Poll::Pending => Some(generator),
"hit breakpoint at:\n{:?}", };
this.state.debug_insn_at(this.state.pc), this = this_ref.borrow_mut();
); this.extern_modules[module_index]
match break_action { .module_state
BreakAction::DumpStateAndContinue => { .did_initial_settle = true;
println!("{this:#?}"); if !this.extern_modules[module_index]
} .module_state
BreakAction::Continue => {} .uninitialized_ios
} .is_empty()
} {
RunResult::Return(()) => break, 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 { if this.main_module.did_initial_settle {
this.read_traces::<false>(); this.read_traces::<false>();
@ -7286,10 +7422,6 @@ impl SimulationImpl {
this.state.memory_write_log.sort_unstable(); this.state.memory_write_log.sort_unstable();
this.state.memory_write_log.dedup(); this.state.memory_write_log.dedup();
this.main_module.did_initial_settle = true; 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| { this.for_each_trace_writer_storing_error(|this, trace_writer_state| {
Ok(match trace_writer_state { Ok(match trace_writer_state {
TraceWriterState::Decls(trace_writer_decls) => TraceWriterState::Running( TraceWriterState::Decls(trace_writer_decls) => TraceWriterState::Running(
@ -7310,7 +7442,14 @@ impl SimulationImpl {
}); });
this.state.memory_write_log.clear(); 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<RefCell<Self>>) {
Self::run_until(this_ref, WaitTarget::Settle);
} }
fn get_module(&self, which_module: WhichModule) -> &SimulationModuleState { fn get_module(&self, which_module: WhichModule) -> &SimulationModuleState {
match which_module { match which_module {
@ -7342,7 +7481,7 @@ impl SimulationImpl {
let compiled_value = self let compiled_value = self
.get_module_mut(which_module) .get_module_mut(which_module)
.write_helper(io, which_module); .write_helper(io, which_module);
self.needs_settle = true; self.state_ready_to_run = true;
match compiled_value.range.len() { match compiled_value.range.len() {
TypeLen::A_SMALL_SLOT => { TypeLen::A_SMALL_SLOT => {
self.state.small_slots[compiled_value.range.small_slots.start] = value as _; self.state.small_slots[compiled_value.range.small_slots.start] = value as _;
@ -7374,7 +7513,7 @@ impl SimulationImpl {
let compiled_value = self let compiled_value = self
.get_module_mut(which_module) .get_module_mut(which_module)
.write_helper(Expr::canonical(io), which_module); .write_helper(Expr::canonical(io), which_module);
self.needs_settle = true; self.state_ready_to_run = true;
let value: BigInt = value.into(); let value: BigInt = value.into();
match compiled_value.range.len() { match compiled_value.range.len() {
TypeLen::A_SMALL_SLOT => { TypeLen::A_SMALL_SLOT => {
@ -7495,7 +7634,7 @@ impl SimulationImpl {
let compiled_value = self let compiled_value = self
.get_module_mut(which_module) .get_module_mut(which_module)
.write_helper(io, which_module); .write_helper(io, which_module);
self.needs_settle = true; self.state_ready_to_run = true;
assert_eq!(Expr::ty(io), value.ty()); assert_eq!(Expr::ty(io), value.ty());
Self::read_write_sim_value_helper( Self::read_write_sim_value_helper(
&mut self.state, &mut self.state,
@ -7531,7 +7670,6 @@ impl SimulationImpl {
MaybeNeedsSettle::NoSettleNeeded(v) => v, MaybeNeedsSettle::NoSettleNeeded(v) => v,
} }
} }
#[track_caller]
async fn yield_settle_if_needed<F, O>( async fn yield_settle_if_needed<F, O>(
this_ref: &Rc<RefCell<Self>>, this_ref: &Rc<RefCell<Self>>,
module_index: usize, module_index: usize,
@ -7713,18 +7851,22 @@ impl<T: BundleType> fmt::Debug for Simulation<T> {
} }
macro_rules! impl_simulation_methods { macro_rules! impl_simulation_methods {
(async_await = ($($async:tt, $await:tt)?)) => { (
#[track_caller] async_await = ($($async:tt, $await:tt)?),
pub $($async)? fn read_bool_or_int<I: BoolOrIntType>(&mut self, io: Expr<I>) -> I::Value { track_caller = ($(#[$track_caller:tt])?),
let retval = self which_module = |$self:ident| $which_module:expr,
) => {
$(#[$track_caller])?
pub $($async)? fn read_bool_or_int<I: BoolOrIntType>(&mut $self, io: Expr<I>) -> I::Value {
let retval = $self
.sim_impl .sim_impl
.borrow_mut() .borrow_mut()
.read_bool_or_int(io, WhichModule::Main); .read_bool_or_int(io, $which_module);
self.settle_if_needed(retval)$(.$await)? $self.settle_if_needed(retval)$(.$await)?
} }
#[track_caller] $(#[$track_caller])?
pub $($async)? fn write_bool_or_int<I: BoolOrIntType>( pub $($async)? fn write_bool_or_int<I: BoolOrIntType>(
&mut self, &mut $self,
io: Expr<I>, io: Expr<I>,
value: impl ToExpr<Type = I>, value: impl ToExpr<Type = I>,
) { ) {
@ -7733,68 +7875,68 @@ macro_rules! impl_simulation_methods {
let value = value let value = value
.to_literal_bits() .to_literal_bits()
.expect("the value that is being written to an input must be a literal"); .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, io,
I::bits_to_value(Cow::Borrowed(&value)), I::bits_to_value(Cow::Borrowed(&value)),
WhichModule::Main, $which_module,
); );
} }
#[track_caller] $(#[$track_caller])?
pub $($async)? fn write_clock(&mut self, io: Expr<Clock>, value: bool) { pub $($async)? fn write_clock(&mut $self, io: Expr<Clock>, value: bool) {
self.sim_impl $self.sim_impl
.borrow_mut() .borrow_mut()
.write_bit(Expr::canonical(io), value, WhichModule::Main); .write_bit(Expr::canonical(io), value, $which_module);
} }
#[track_caller] $(#[$track_caller])?
pub $($async)? fn read_clock(&mut self, io: Expr<Clock>) -> bool { pub $($async)? fn read_clock(&mut $self, io: Expr<Clock>) -> bool {
let retval = self let retval = $self
.sim_impl .sim_impl
.borrow_mut() .borrow_mut()
.read_bit(Expr::canonical(io), WhichModule::Main); .read_bit(Expr::canonical(io), $which_module);
self.settle_if_needed(retval)$(.$await)? $self.settle_if_needed(retval)$(.$await)?
} }
#[track_caller] $(#[$track_caller])?
pub $($async)? fn write_bool(&mut self, io: Expr<Bool>, value: bool) { pub $($async)? fn write_bool(&mut $self, io: Expr<Bool>, value: bool) {
self.sim_impl $self.sim_impl
.borrow_mut() .borrow_mut()
.write_bit(Expr::canonical(io), value, WhichModule::Main); .write_bit(Expr::canonical(io), value, $which_module);
} }
#[track_caller] $(#[$track_caller])?
pub $($async)? fn read_bool(&mut self, io: Expr<Bool>) -> bool { pub $($async)? fn read_bool(&mut $self, io: Expr<Bool>) -> bool {
let retval = self let retval = $self
.sim_impl .sim_impl
.borrow_mut() .borrow_mut()
.read_bit(Expr::canonical(io), WhichModule::Main); .read_bit(Expr::canonical(io), $which_module);
self.settle_if_needed(retval)$(.$await)? $self.settle_if_needed(retval)$(.$await)?
} }
#[track_caller] $(#[$track_caller])?
pub $($async)? fn write_reset<R: ResetType>(&mut self, io: Expr<R>, value: bool) { pub $($async)? fn write_reset<R: ResetType>(&mut $self, io: Expr<R>, value: bool) {
self.sim_impl $self.sim_impl
.borrow_mut() .borrow_mut()
.write_bit(Expr::canonical(io), value, WhichModule::Main); .write_bit(Expr::canonical(io), value, $which_module);
} }
#[track_caller] $(#[$track_caller])?
pub $($async)? fn read_reset<R: ResetType>(&mut self, io: Expr<R>) -> bool { pub $($async)? fn read_reset<R: ResetType>(&mut $self, io: Expr<R>) -> bool {
let retval = self let retval = $self
.sim_impl .sim_impl
.borrow_mut() .borrow_mut()
.read_bit(Expr::canonical(io), WhichModule::Main); .read_bit(Expr::canonical(io), $which_module);
self.settle_if_needed(retval)$(.$await)? $self.settle_if_needed(retval)$(.$await)?
} }
#[track_caller] $(#[$track_caller])?
pub $($async)? fn read<IO: Type>(&mut self, io: Expr<IO>) -> SimValue<IO> { pub $($async)? fn read<IO: Type>(&mut $self, io: Expr<IO>) -> SimValue<IO> {
let retval = self let retval = $self
.sim_impl .sim_impl
.borrow_mut() .borrow_mut()
.read(Expr::canonical(io), WhichModule::Main); .read(Expr::canonical(io), $which_module);
SimValue::from_canonical(self.settle_if_needed(retval)$(.$await)?) SimValue::from_canonical($self.settle_if_needed(retval)$(.$await)?)
} }
#[track_caller] $(#[$track_caller])?
pub $($async)? fn write<IO: Type, V: ToSimValue<IO>>(&mut self, io: Expr<IO>, value: V) { pub $($async)? fn write<IO: Type, V: ToSimValue<IO>>(&mut $self, io: Expr<IO>, value: V) {
self.sim_impl.borrow_mut().write( $self.sim_impl.borrow_mut().write(
Expr::canonical(io), Expr::canonical(io),
value.into_sim_value(Expr::ty(io)).into_canonical(), value.into_sim_value(Expr::ty(io)).into_canonical(),
WhichModule::Main, $which_module,
); );
} }
}; };
@ -7855,7 +7997,11 @@ impl<T: BundleType> Simulation<T> {
{ {
SimulationImpl::settle_if_needed(&self.sim_impl, v) 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)] #[doc(hidden)]
/// This is explicitly unstable and may be changed/removed at any time /// This is explicitly unstable and may be changed/removed at any time
pub fn set_breakpoints_unstable(&mut self, pcs: HashSet<usize>, trace: bool) { pub fn set_breakpoints_unstable(&mut self, pcs: HashSet<usize>, trace: bool) {
@ -7913,12 +8059,10 @@ impl<T: BundleType> ExternModuleSimulationState<T> {
io_ty: T::from_canonical(io_ty.canonical()), io_ty: T::from_canonical(io_ty.canonical()),
} }
} }
#[track_caller]
pub async fn settle(&mut self) { pub async fn settle(&mut self) {
SimulationImpl::yield_advance_time_or_settle(self.sim_impl.clone(), self.module_index, None) SimulationImpl::yield_advance_time_or_settle(self.sim_impl.clone(), self.module_index, None)
.await .await
} }
#[track_caller]
pub async fn advance_time(&mut self, duration: SimDuration) { pub async fn advance_time(&mut self, duration: SimDuration) {
SimulationImpl::yield_advance_time_or_settle( SimulationImpl::yield_advance_time_or_settle(
self.sim_impl.clone(), self.sim_impl.clone(),
@ -7927,14 +8071,17 @@ impl<T: BundleType> ExternModuleSimulationState<T> {
) )
.await .await
} }
#[track_caller]
async fn settle_if_needed<F, O>(&mut self, v: MaybeNeedsSettle<F, O>) -> O async fn settle_if_needed<F, O>(&mut self, v: MaybeNeedsSettle<F, O>) -> O
where where
for<'a> F: MaybeNeedsSettleFn<&'a mut interpreter::State, Output = O>, for<'a> F: MaybeNeedsSettleFn<&'a mut interpreter::State, Output = O>,
{ {
SimulationImpl::yield_settle_if_needed(&self.sim_impl, self.module_index, v).await 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: pub trait ExternModuleSimGenerator:

View file

@ -5,11 +5,14 @@ use fayalite::{
int::UIntValue, int::UIntValue,
prelude::*, prelude::*,
reset::ResetType, reset::ResetType,
sim::{time::SimDuration, vcd::VcdWriterDecls, Simulation, ToSimValue}, sim::{
time::SimDuration, vcd::VcdWriterDecls, ExternModuleSimGenerator,
ExternModuleSimulationState, Simulation, ToSimValue,
},
ty::StaticType, ty::StaticType,
util::RcWriter, util::RcWriter,
}; };
use std::num::NonZeroUsize; use std::{future::IntoFuture, num::NonZeroUsize};
#[hdl_module(outline_generated)] #[hdl_module(outline_generated)]
pub fn connect_const() { pub fn connect_const() {
@ -1443,3 +1446,61 @@ fn test_conditional_assignment_last() {
panic!(); 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<Bool>,
o: Expr<Bool>,
}
impl ExternModuleSimGenerator for Sim {
type IOType = extern_module;
fn run<'a>(
&'a self,
mut sim: ExternModuleSimulationState<Self::IOType>,
) -> impl IntoFuture<Output = ()> + '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!();
}
}

View file

@ -819,7 +819,7 @@ Simulation {
did_initial_settle: true, did_initial_settle: true,
}, },
extern_modules: [], extern_modules: [],
needs_settle: false, state_ready_to_run: false,
trace_decls: TraceModule { trace_decls: TraceModule {
name: "array_rw", name: "array_rw",
children: [ children: [

View file

@ -115,7 +115,7 @@ Simulation {
did_initial_settle: true, did_initial_settle: true,
}, },
extern_modules: [], extern_modules: [],
needs_settle: false, state_ready_to_run: false,
trace_decls: TraceModule { trace_decls: TraceModule {
name: "conditional_assignment_last", name: "conditional_assignment_last",
children: [ children: [

View file

@ -91,7 +91,7 @@ Simulation {
did_initial_settle: true, did_initial_settle: true,
}, },
extern_modules: [], extern_modules: [],
needs_settle: false, state_ready_to_run: false,
trace_decls: TraceModule { trace_decls: TraceModule {
name: "connect_const", name: "connect_const",
children: [ children: [

View file

@ -134,7 +134,7 @@ Simulation {
did_initial_settle: true, did_initial_settle: true,
}, },
extern_modules: [], extern_modules: [],
needs_settle: false, state_ready_to_run: false,
trace_decls: TraceModule { trace_decls: TraceModule {
name: "connect_const_reset", name: "connect_const_reset",
children: [ children: [

View file

@ -254,7 +254,7 @@ Simulation {
did_initial_settle: true, did_initial_settle: true,
}, },
extern_modules: [], extern_modules: [],
needs_settle: false, state_ready_to_run: false,
trace_decls: TraceModule { trace_decls: TraceModule {
name: "counter", name: "counter",
children: [ children: [

View file

@ -235,7 +235,7 @@ Simulation {
did_initial_settle: true, did_initial_settle: true,
}, },
extern_modules: [], extern_modules: [],
needs_settle: false, state_ready_to_run: false,
trace_decls: TraceModule { trace_decls: TraceModule {
name: "counter", name: "counter",
children: [ children: [

View file

@ -95,7 +95,7 @@ Simulation {
did_initial_settle: true, did_initial_settle: true,
}, },
extern_modules: [], extern_modules: [],
needs_settle: false, state_ready_to_run: false,
trace_decls: TraceModule { trace_decls: TraceModule {
name: "duplicate_names", name: "duplicate_names",
children: [ children: [

View file

@ -1336,7 +1336,7 @@ Simulation {
did_initial_settle: true, did_initial_settle: true,
}, },
extern_modules: [], extern_modules: [],
needs_settle: false, state_ready_to_run: false,
trace_decls: TraceModule { trace_decls: TraceModule {
name: "enums", name: "enums",
children: [ children: [

View file

@ -0,0 +1,224 @@
Simulation {
state: State {
insns: Insns {
state_layout: StateLayout {
ty: TypeLayout {
small_slots: StatePartLayout<SmallSlots> {
len: 0,
debug_data: [],
..
},
big_slots: StatePartLayout<BigSlots> {
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<Memories> {
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: <simulator>::extern_module,
instantiated: Module {
name: extern_module,
..
},
},
main_module: SimulationModuleState {
base_targets: [
Instance {
name: <simulator>::extern_module,
instantiated: Module {
name: extern_module,
..
},
}.i,
Instance {
name: <simulator>::extern_module,
instantiated: Module {
name: extern_module,
..
},
}.o,
],
uninitialized_ios: {},
io_targets: {
Instance {
name: <simulator>::extern_module,
instantiated: Module {
name: extern_module,
..
},
}.i,
Instance {
name: <simulator>::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<fayalite::bundle::Bundle>,
},
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<BigSlots>(0),
},
state: 0x1,
last_state: 0x1,
},
SimTrace {
id: TraceScalarId(1),
kind: BigBool {
index: StatePartIndex<BigSlots>(1),
},
state: 0x1,
last_state: 0x1,
},
],
trace_memories: {},
trace_writers: [
Running(
VcdWriter {
finished_init: true,
timescale: 1 ps,
..
},
),
],
instant: 20 μs,
clocks_triggered: [],
..
}

View file

@ -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

View file

@ -712,7 +712,7 @@ Simulation {
did_initial_settle: true, did_initial_settle: true,
}, },
extern_modules: [], extern_modules: [],
needs_settle: false, state_ready_to_run: false,
trace_decls: TraceModule { trace_decls: TraceModule {
name: "memories", name: "memories",
children: [ children: [

View file

@ -670,7 +670,7 @@ Simulation {
did_initial_settle: true, did_initial_settle: true,
}, },
extern_modules: [], extern_modules: [],
needs_settle: false, state_ready_to_run: false,
trace_decls: TraceModule { trace_decls: TraceModule {
name: "memories2", name: "memories2",
children: [ children: [

View file

@ -1754,7 +1754,7 @@ Simulation {
did_initial_settle: true, did_initial_settle: true,
}, },
extern_modules: [], extern_modules: [],
needs_settle: false, state_ready_to_run: false,
trace_decls: TraceModule { trace_decls: TraceModule {
name: "memories3", name: "memories3",
children: [ children: [

View file

@ -267,7 +267,7 @@ Simulation {
did_initial_settle: true, did_initial_settle: true,
}, },
extern_modules: [], extern_modules: [],
needs_settle: false, state_ready_to_run: false,
trace_decls: TraceModule { trace_decls: TraceModule {
name: "mod1", name: "mod1",
children: [ children: [

View file

@ -330,7 +330,7 @@ Simulation {
did_initial_settle: true, did_initial_settle: true,
}, },
extern_modules: [], extern_modules: [],
needs_settle: false, state_ready_to_run: false,
trace_decls: TraceModule { trace_decls: TraceModule {
name: "shift_register", name: "shift_register",
children: [ children: [