use crate::{
RuntimeErrorCode, Simulator,
backend::{EventHandle, MemoryLayout, SimBackend},
ir::SignalRef,
simulator::{
Checkpoint, CheckpointError, InstanceHierarchy, NamedEvent, NamedSignal, StateError,
},
};
use celox_runtime::{
EventInfo, ProcessRefs, SimulationExecutor, SimulationSnapshot, SimulationState,
};
pub struct SimulationCheckpoint<B: SimBackend = crate::DefaultBackend> {
simulator: Checkpoint,
schedule: SimulationSnapshot<B>,
}
impl<B: SimBackend> Clone for SimulationCheckpoint<B> {
fn clone(&self) -> Self {
Self {
simulator: self.simulator.clone(),
schedule: self.schedule.clone(),
}
}
}
impl<B: SimBackend> SimulationCheckpoint<B> {
pub fn time(&self) -> u64 {
self.schedule.time()
}
pub fn state_size(&self) -> usize {
self.simulator.state_size()
}
}
impl<B: SimBackend> std::fmt::Debug for SimulationCheckpoint<B> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SimulationCheckpoint")
.field("time", &self.time())
.field("state_size", &self.state_size())
.finish_non_exhaustive()
}
}
pub struct Simulation<B: SimBackend = crate::DefaultBackend> {
pub(crate) simulator: Simulator<B>,
pub(crate) state: SimulationState<B>,
}
impl<B: SimBackend> std::fmt::Debug for Simulation<B> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Simulation")
.field("time", &self.state.time())
.finish()
}
}
impl<B: SimBackend> SimulationExecutor for Simulator<B> {
type Backend = B;
fn backend(&self) -> &B {
&self.backend
}
fn backend_mut(&mut self) -> &mut B {
&mut self.backend
}
fn eval_comb(&mut self) -> Result<(), RuntimeErrorCode> {
self.eval_comb_checked()
}
fn eval_apply_ff_at(&mut self, event: B::Event) -> Result<(), RuntimeErrorCode> {
self.eval_apply_ff_at_checked(event)
}
fn eval_only_ff_at(&mut self, event: B::Event) -> Result<(), RuntimeErrorCode> {
self.eval_only_ff_at_checked(event)
}
fn apply_ff_at(&mut self, event: B::Event) -> Result<(), RuntimeErrorCode> {
self.apply_ff_at_checked(event)
}
fn run_process(&mut self, index: usize) -> Result<(), RuntimeErrorCode> {
self.backend
.run_process(index)
.map_err(|e| self.decorate_runtime_error(e))
}
fn stage_external_event(
&mut self,
event: B::Event,
_timestamp: u64,
) -> Result<(), RuntimeErrorCode> {
self.components.stage_inputs(event.id(), &mut self.backend);
Ok(())
}
fn fire_external_event(
&mut self,
event: B::Event,
timestamp: u64,
) -> Result<(), RuntimeErrorCode> {
let writes = self
.components
.fire(event.id(), timestamp)
.map_err(|message| RuntimeErrorCode::Runtime {
message,
signals: Vec::new(),
})?;
for write in writes {
write.apply(&mut self.backend);
self.dirty = true;
}
Ok(())
}
fn finish_timed_step(&mut self, timestamp: u64) {
self.dirty = false;
self.dump_unless_rewound(timestamp);
}
}
impl Simulation {
pub fn builder<'a>(code: &'a str, top: &'a str) -> crate::SimulatorBuilder<'a, Simulation> {
crate::SimulatorBuilder::<Simulation>::new(code, top)
}
pub fn from_sources<'a>(
sources: Vec<(&'a str, &'a std::path::Path)>,
top: &'a str,
) -> crate::SimulatorBuilder<'a, Simulation> {
crate::SimulatorBuilder::<Simulation>::from_sources(sources, top)
}
#[cfg(feature = "systemverilog")]
pub fn from_sv_sources<'a>(
sources: Vec<(&'a str, &'a std::path::Path)>,
top: &'a str,
) -> crate::SimulatorBuilder<'a, Simulation> {
crate::SimulatorBuilder::<Simulation>::from_sources(Vec::new(), top)
.into_sv_sources(sources)
}
pub fn from_frontend(
artifact: celox_frontend_sdk::FrontendArtifact,
) -> crate::SimulatorBuilder<'static, Simulation> {
crate::SimulatorBuilder::<Simulation>::from_frontend(artifact)
}
}
pub(crate) fn simulation_state<B: SimBackend>(simulator: &Simulator<B>) -> SimulationState<B> {
let num_events = simulator.backend.num_events();
let topo_signals: Vec<(SignalRef, usize, usize)> = simulator
.program
.design
.events
.ordered_events
.iter()
.map(|addr| {
let signal = simulator.backend.resolve_signal(addr);
let id = simulator
.backend
.resolve_event_opt(addr)
.map(|ev| ev.id())
.unwrap_or(usize::MAX);
let canonical = simulator.program.design.events.canonical(*addr);
let canonical_id = simulator
.backend
.resolve_event_opt(&canonical)
.map(|ev| ev.id())
.unwrap_or(usize::MAX);
(signal, id, canonical_id)
})
.collect();
let mut domain_kinds = vec![None; num_events];
let kinds = simulator.program.design.event_kinds();
for (_, id, _) in topo_signals.iter().copied() {
if id != usize::MAX {
let addr = simulator.backend.id_to_addr_slice()[id];
domain_kinds[id] = kinds.get(&addr).copied();
}
}
let mut event_info = vec![
EventInfo {
canonical_id: usize::MAX,
is_cascaded: false,
eval_ff_event: None,
eval_only_event: None,
apply_event: None,
};
num_events
];
for (id, info) in event_info.iter_mut().enumerate() {
let addr = simulator.backend.id_to_addr_slice()[id];
let canonical = simulator.program.design.events.canonical(addr);
let is_cascaded = simulator
.program
.design
.events
.cascaded_events
.contains(&canonical);
let eval_ff_event = simulator.backend.resolve_event_opt(&canonical);
let eval_only_event = simulator.backend.resolve_eval_only_event(&canonical);
let apply_event = simulator.backend.resolve_apply_event(&canonical);
if let Some(canonical_ev) = eval_ff_event {
*info = EventInfo {
canonical_id: canonical_ev.id(),
is_cascaded,
eval_ff_event,
eval_only_event,
apply_event,
};
}
}
let processes = simulator
.program
.runtime_schema
.processes
.iter()
.map(|slots| ProcessRefs {
status: simulator.backend.resolve_signal(&slots.status),
delay: simulator.backend.resolve_signal(&slots.delay),
})
.collect();
SimulationState::new(
&simulator.backend,
topo_signals,
domain_kinds,
event_info,
processes,
)
}
impl<B: SimBackend> Simulation<B> {
pub(crate) fn new(simulator: Simulator<B>) -> Self {
let state = simulation_state(&simulator);
Self { simulator, state }
}
pub fn warnings(&self) -> &[crate::CompilationWarning] {
self.simulator.warnings()
}
pub fn checkpoint(&self) -> Result<SimulationCheckpoint<B>, CheckpointError> {
Ok(SimulationCheckpoint {
simulator: self.simulator.checkpoint()?,
schedule: self.state.snapshot(),
})
}
pub fn save_state(&mut self) -> Result<celox_runtime::StateFile, StateError> {
if self.has_processes() {
return Err(StateError::Processes);
}
let mut file = self.simulator.save_state()?;
let parts = self.state.export_schedule(&self.simulator.backend);
file.schedule = Some(self.simulator.name_schedule(parts));
Ok(file)
}
pub fn load_state(&mut self, file: &celox_runtime::StateFile) -> Result<(), StateError> {
if self.has_processes() {
return Err(StateError::Processes);
}
let record = file.schedule.as_ref().ok_or(StateError::MissingSchedule)?;
self.simulator.check_vcd_rewind(record.time)?;
let parts = self.simulator.resolve_schedule(record)?;
self.simulator.load_state(file)?;
self.state.import_schedule(parts);
Ok(())
}
pub fn restore(&mut self, checkpoint: &SimulationCheckpoint<B>) -> Result<(), CheckpointError> {
self.simulator.validate_restore(&checkpoint.simulator)?;
self.simulator.check_vcd_rewind(checkpoint.time())?;
let events = self.simulator.backend.id_to_event_slice();
self.state
.restore(&checkpoint.schedule, |event| {
events
.get(event.id())
.copied()
.filter(|local| local.addr() == event.addr())
})
.map_err(|_| CheckpointError::DesignMismatch)?;
self.simulator.restore(&checkpoint.simulator)
}
pub fn dump(&mut self, timestamp: u64) {
self.simulator.dump(timestamp);
}
pub fn try_dump(&mut self, timestamp: u64) -> Result<(), crate::simulator::DumpError> {
self.simulator.try_dump(timestamp)
}
pub fn switch_vcd(&mut self, path: impl AsRef<std::path::Path>) -> std::io::Result<()> {
self.simulator.switch_vcd(path)
}
pub fn flush_vcd(&mut self) -> std::io::Result<()> {
self.simulator.flush_vcd()
}
pub fn signal(&self, path: &str) -> SignalRef {
self.simulator.signal(path)
}
pub fn get(&mut self, signal: SignalRef) -> num_bigint::BigUint {
self.simulator.get(signal)
}
pub fn modify<F>(&mut self, f: F) -> Result<(), RuntimeErrorCode>
where
F: FnOnce(&mut crate::IOContext<B>),
{
self.simulator.modify(f)
}
pub fn add_clock(&mut self, port: &str, period: u64, initial_delay: u64) {
let signal = self.simulator.signal(port);
let addr = self.simulator.program.get_addr(&[], &[port]).unwrap();
if let Some(ev) = self.simulator.backend.resolve_event_opt(&addr) {
self.state.add_clock(ev, signal, period, initial_delay);
}
}
pub fn schedule(&mut self, port: &str, time: u64, value: u64) -> Result<(), RuntimeErrorCode> {
let signal = self.simulator.signal(port);
let addr = self.simulator.program.get_addr(&[], &[port]).unwrap();
let ev_opt = self.simulator.backend.resolve_event_opt(&addr);
if let Some(ev) = ev_opt {
self.state.schedule(ev, signal, time, value as u8);
} else {
return Err(RuntimeErrorCode::NotAnEvent(port.to_string()));
}
Ok(())
}
pub fn step(&mut self) -> Result<Option<u64>, RuntimeErrorCode> {
self.state.step(&mut self.simulator)
}
pub fn run_until(&mut self, end_time: u64) -> Result<(), RuntimeErrorCode> {
if end_time < self.state.time() {
return Ok(());
}
while let Some(next_time) = self.state.next_event_time() {
if next_time > end_time {
break;
}
self.step()?;
}
if self.state.is_finished() {
return Ok(());
}
self.state.set_time(end_time);
self.simulator.dump_unless_rewound(end_time);
Ok(())
}
fn has_processes(&self) -> bool {
!self.simulator.program.runtime_schema.processes.is_empty()
}
pub fn is_finished(&self) -> bool {
self.state.is_finished()
}
pub fn time(&self) -> u64 {
self.state.time()
}
pub fn clock_periods(&self) -> Vec<(usize, u64)> {
self.state.clock_periods()
}
pub fn drain_runtime_events(&mut self) -> Vec<crate::RuntimeEvent> {
self.simulator.drain_runtime_events()
}
pub fn next_event_time(&self) -> Option<u64> {
self.state.next_event_time()
}
pub fn eval_comb(&mut self) -> Result<(), RuntimeErrorCode> {
self.simulator.eval_comb()
}
pub fn memory_as_ptr(&self) -> (*const u8, usize) {
self.simulator.memory_as_ptr()
}
pub fn memory_as_mut_ptr(&mut self) -> (*mut u8, usize) {
self.simulator.memory_as_mut_ptr()
}
pub fn memory_owner(&self) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {
self.simulator.memory_owner()
}
pub fn stable_region_size(&self) -> usize {
self.simulator.stable_region_size()
}
pub fn layout(&self) -> &MemoryLayout {
self.simulator.layout()
}
pub fn named_signals(&self) -> Vec<NamedSignal> {
self.simulator.named_signals()
}
pub fn named_events(&self) -> Vec<NamedEvent<B>> {
self.simulator.named_events()
}
pub fn named_hierarchy(&self) -> InstanceHierarchy {
self.simulator.named_hierarchy()
}
pub fn instance_signals(&self, instance_path: &[(&str, usize)]) -> Vec<NamedSignal> {
self.simulator.instance_signals(instance_path)
}
pub fn child_signal(&self, instance_path: &[(&str, usize)], var: &str) -> SignalRef {
self.simulator.child_signal(instance_path, var)
}
pub fn add_clock_by_id(&mut self, event_id: u32, period: u64, initial_delay: u64) {
let _ = self.try_add_clock_by_id(event_id, period, initial_delay);
}
pub fn try_add_clock_by_id(
&mut self,
event_id: u32,
period: u64,
initial_delay: u64,
) -> Result<(), RuntimeErrorCode> {
let addr = self
.simulator
.backend
.id_to_addr_slice()
.get(event_id as usize)
.copied()
.ok_or_else(|| RuntimeErrorCode::NotAnEvent(format!("event_id={event_id}")))?;
let signal = self.simulator.backend.resolve_signal(&addr);
if let Some(ev) = self.simulator.backend.resolve_event_opt(&addr) {
self.state.add_clock(ev, signal, period, initial_delay);
Ok(())
} else {
Err(RuntimeErrorCode::NotAnEvent(format!("event_id={event_id}")))
}
}
pub fn schedule_by_id(
&mut self,
event_id: u32,
time: u64,
value: u64,
) -> Result<(), RuntimeErrorCode> {
let addr = self
.simulator
.backend
.id_to_addr_slice()
.get(event_id as usize)
.copied()
.ok_or_else(|| RuntimeErrorCode::NotAnEvent(format!("event_id={event_id}")))?;
let signal = self.simulator.backend.resolve_signal(&addr);
let ev_opt = self.simulator.backend.resolve_event_opt(&addr);
if let Some(ev) = ev_opt {
self.state.schedule(ev, signal, time, value as u8);
Ok(())
} else {
Err(RuntimeErrorCode::NotAnEvent(format!(
"event_id={}",
event_id
)))
}
}
}
#[cfg(all(test, feature = "host-runtime"))]
mod process_tests {
use celox_frontend_sdk::{
BinaryOp, Constant, Edge, ExprId, FrontendArtifact, ModuleBuilder, Statement, UnaryOp,
ValueType,
};
use super::Simulation;
use crate::{SimBackend, Simulator, SimulatorBuilder};
fn constant(module: &mut ModuleBuilder, value: u64, width: usize) -> ExprId {
module.constant(Constant::two_state(value, width).unwrap())
}
fn design(four_state: bool, steps: usize) -> FrontendArtifact {
let bit = ValueType::new(1, false, four_state).unwrap();
let word = ValueType::new(16, false, four_state).unwrap();
let mut module = ModuleBuilder::new("Processes").unwrap();
let clk = module.internal("clk", bit).unwrap();
let count = module.output("count", word).unwrap();
let ticks = module.output("ticks", word).unwrap();
for signal in [clk, count, ticks] {
let width = if signal == clk { 1 } else { 16 };
module
.set_initial(signal, Constant::two_state(0u8, width).unwrap())
.unwrap();
}
let count_expr = module.read(count).unwrap();
let one = constant(&mut module, 1, 16);
let next = module.binary(BinaryOp::Add, count_expr, one, word).unwrap();
let count_target = module.whole(count).unwrap();
module
.register(count_target, next, clk, Edge::Posedge, None, None)
.unwrap();
let five = constant(&mut module, 5, 8);
let clk_expr = module.read(clk).unwrap();
let toggled = module.unary(UnaryOp::BitNot, clk_expr, bit).unwrap();
let clk_target = module.whole(clk).unwrap();
module
.process(vec![Statement::Forever {
body: vec![
Statement::Delay { amount: five },
Statement::Assign {
target: clk_target,
value: toggled,
},
],
}])
.unwrap();
let unit = constant(&mut module, 1, 8);
let ticks_expr = module.read(ticks).unwrap();
let incremented = module.binary(BinaryOp::Add, ticks_expr, one, word).unwrap();
let ticks_target = module.whole(ticks).unwrap();
let mut body = Vec::new();
for _ in 0..steps {
body.push(Statement::Delay { amount: unit });
body.push(Statement::Assign {
target: ticks_target,
value: incremented,
});
}
body.push(Statement::Finish);
module.process(body).unwrap();
module.finish()
}
fn check<B: SimBackend>(mut sim: Simulation<B>, steps: u64) {
let count = sim.signal("count");
let ticks = sim.signal("ticks");
sim.run_until(30).unwrap();
assert!(!sim.is_finished());
assert_eq!(sim.get(count), 3u16.into());
assert_eq!(sim.get(ticks), 30u16.into());
sim.run_until(u64::MAX - 1).unwrap();
assert!(sim.is_finished());
assert_eq!(sim.time(), steps);
assert_eq!(sim.get(ticks), steps.into());
assert_eq!(sim.get(count), ((steps + 5) / 10).into());
}
fn builder(four_state: bool, steps: usize) -> SimulatorBuilder<'static, Simulator> {
Simulator::from_frontend(design(four_state, steps))
.four_state(four_state)
.emit_triggers()
}
fn check_all_backends(four_state: bool, steps: usize) {
check(
Simulation::new(builder(four_state, steps).build_interpreter().unwrap()),
steps as u64,
);
check(
Simulation::new(builder(four_state, steps).build_cranelift().unwrap()),
steps as u64,
);
check(
Simulation::new(builder(four_state, steps).build_wasm().unwrap()),
steps as u64,
);
check(
Simulation::new(builder(four_state, steps).build_tiered().unwrap()),
steps as u64,
);
#[cfg(any(
all(target_arch = "x86_64", not(feature = "arm64-codegen")),
all(target_arch = "aarch64", not(feature = "x86_64-codegen"))
))]
{
check(
Simulation::new(builder(four_state, steps).build_native().unwrap()),
steps as u64,
);
let image = builder(four_state, steps)
.compile_native()
.unwrap()
.into_program_image();
let image = crate::NativeProgramImage::from_container_bytes(
&image.to_container_bytes().unwrap(),
)
.unwrap();
check(
Simulation::new(
Simulator::from_sources(Vec::new(), "Processes")
.emit_triggers()
.build_native_from_image(image)
.unwrap(),
),
steps as u64,
);
}
}
#[test]
fn processes_run_on_every_backend() {
check_all_backends(false, 37);
}
#[test]
fn four_state_processes_run_on_every_backend() {
check_all_backends(true, 37);
}
#[test]
fn long_processes_dispatch_through_two_switch_levels() {
check_all_backends(false, 300);
}
#[test]
fn odd_clock_phase_survives_checkpoint_across_backends() {
fn check<B: crate::SimBackend>(mut backend: crate::Simulator<B>) {
let clock = backend.signal("clk");
let count = backend.signal("count");
backend
.modify(|io| {
io.set(clock, 0u8);
io.set(count, 0u8);
})
.unwrap();
let mut sim = Simulation::new(backend);
sim.add_clock("clk", 3, 5);
sim.run_until(11).unwrap();
assert_eq!(sim.get(count), 3u8.into());
assert_eq!(sim.get(clock), 1u8.into());
assert_eq!(sim.next_event_time(), Some(13));
let saved = sim.checkpoint().unwrap();
sim.run_until(12).unwrap();
assert_eq!(sim.get(clock), 1u8.into());
sim.run_until(13).unwrap();
assert_eq!(sim.get(clock), 0u8.into());
sim.run_until(14).unwrap();
assert_eq!(sim.get(count), 4u8.into());
sim.restore(&saved).unwrap();
assert_eq!(sim.time(), 11);
assert_eq!(sim.get(count), 3u8.into());
assert_eq!(sim.get(clock), 1u8.into());
assert_eq!(sim.next_event_time(), Some(13));
sim.run_until(12).unwrap();
assert_eq!(sim.get(clock), 1u8.into());
sim.run_until(13).unwrap();
assert_eq!(sim.get(clock), 0u8.into());
sim.run_until(14).unwrap();
assert_eq!(sim.get(count), 4u8.into());
}
let code = "module Top (clk: input clock, count: output logic<8>) { always_ff (clk) { count += 1; } }";
for four_state in [false, true] {
let build = || {
crate::Simulator::builder(code, "Top")
.four_state(four_state)
.emit_triggers()
};
check(build().build_interpreter().unwrap());
check(build().build_cranelift().unwrap());
check(build().build_wasm().unwrap());
check(build().build_tiered().unwrap());
#[cfg(any(
all(target_arch = "x86_64", not(feature = "arm64-codegen")),
all(target_arch = "aarch64", not(feature = "x86_64-codegen"))
))]
check(build().build_native().unwrap());
}
}
#[cfg(feature = "systemverilog")]
const GENERATED_ALIAS_SOURCE: &str = r#"
module Generator(input logic clk, output logic g);
initial g = 0;
always_ff @(posedge clk) g <= ~g;
endmodule
module Top(input logic cp, cn, output logic [7:0] p, n);
logic gp, gn;
Generator up(.clk(cp), .g(gp));
Generator un(.clk(cn), .g(gn));
initial begin p = 0; n = 0; end
always_ff @(posedge gp) p <= p + 1;
always_ff @(negedge gn) n <= n + 1;
endmodule
"#;
#[cfg(feature = "systemverilog")]
#[test]
fn generated_alias_edges_keep_their_instances_and_stable_phases() {
fn check<B: crate::SimBackend>(mut backend: crate::Simulator<B>) {
for (expected, address) in backend
.program
.design
.events
.ordered_events
.iter()
.enumerate()
{
let event = backend.backend.resolve_event(address);
assert_eq!(
celox_runtime::backend::EventHandle::id(&event),
expected,
"event handles must match IDs embedded in trigger instructions"
);
}
let cp = backend.signal("cp");
let cn = backend.signal("cn");
backend
.modify(|io| {
io.set(cp, 0u8);
io.set(cn, 0u8);
})
.unwrap();
let mut sim = Simulation::new(backend);
sim.add_clock("cp", 10, 2);
sim.add_clock("cn", 10, 7);
sim.run_until(27).unwrap();
assert_eq!(sim.get(sim.signal("p")), 2u8.into());
assert_eq!(sim.get(sim.signal("n")), 1u8.into());
}
let source = GENERATED_ALIAS_SOURCE;
for four_state in [false, true] {
let build = || {
crate::Simulator::from_sv_sources(
vec![(source, std::path::Path::new("generated_alias.sv"))],
"Top",
)
.four_state(four_state)
.emit_triggers()
};
check(build().build_interpreter().unwrap());
check(build().build_cranelift().unwrap());
check(build().build_wasm().unwrap());
check(build().build_tiered().unwrap());
#[cfg(any(
all(target_arch = "x86_64", not(feature = "arm64-codegen")),
all(target_arch = "aarch64", not(feature = "x86_64-codegen"))
))]
check(build().build_native().unwrap());
}
}
#[cfg(all(
feature = "systemverilog",
any(
all(target_arch = "x86_64", not(feature = "arm64-codegen")),
all(target_arch = "aarch64", not(feature = "x86_64-codegen"))
)
))]
#[test]
fn timed_alias_edges_and_checkpoint_survive_tiered_promotion() {
for four_state in [false, true] {
let mut backend = crate::Simulator::from_sv_sources(
vec![(
GENERATED_ALIAS_SOURCE,
std::path::Path::new("promoted_alias.sv"),
)],
"Top",
)
.four_state(four_state)
.emit_triggers()
.tier_promotion(crate::TierPromotion::AfterSteps(32))
.build_tiered()
.unwrap();
let cp = backend.signal("cp");
let cn = backend.signal("cn");
backend
.modify(|io| {
io.set(cp, 0u8);
io.set(cn, 0u8);
})
.unwrap();
let mut sim = Simulation::new(backend);
sim.add_clock("cp", 10, 2);
sim.add_clock("cn", 10, 7);
let saved = sim.checkpoint().unwrap();
assert!(!sim.simulator.is_compiled());
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(5);
let mut until = 27u64;
loop {
sim.run_until(until).unwrap();
assert_eq!(
sim.get(sim.signal("p")),
(((until - 2) / 20 + 1) & 255).into()
);
assert_eq!(
sim.get(sim.signal("n")),
(((until - 17) / 20 + 1) & 255).into()
);
if sim.simulator.is_compiled() {
break;
}
assert!(
std::time::Instant::now() < deadline,
"timed tiered backend did not promote"
);
std::thread::sleep(std::time::Duration::from_millis(5));
until += 10;
}
sim.restore(&saved).unwrap();
assert!(sim.simulator.is_compiled());
sim.run_until(27).unwrap();
assert_eq!(sim.get(sim.signal("p")), 2u8.into());
assert_eq!(sim.get(sim.signal("n")), 1u8.into());
}
}
}