#![cfg(any(
all(target_arch = "x86_64", not(feature = "arm64-codegen")),
all(target_arch = "aarch64", not(feature = "x86_64-codegen"))
))]
use celox::{MemoryLayout, MemoryLayoutMode, OptimizedSir, Simulator, SimulatorBuilder};
#[test]
fn zero_reset_keeps_narrow_arrays_strided_and_preserves_dynamic_ff_writes() {
for width in [1, 3, 7, 8, 17, 24, 33, 51, 57] {
for four_state in [false, true] {
for opt_level in [celox::OptLevel::O0, celox::OptLevel::O2] {
let code = format!(
r#"
module Top (
clk: input clock, clear: input logic, write: input logic,
index: input logic<3>, probe: input logic<3>,
d: input logic<{width}>, q: output logic<{width}>,
old: output logic<{width}>, low: output logic, high: output logic,
) {{
var lanes: logic<{width}> [8];
always_ff (clk) {{
old = lanes[index];
if clear {{
for i in 0..8 {{ lanes[i] = '0; }}
}} else if write {{
lanes[index] = d;
}}
}}
assign q = lanes[probe];
assign low = q[0];
assign high = q[{high_bit}];
}}
"#,
high_bit = width - 1,
);
let result = SimulatorBuilder::new(&code, "Top")
.four_state(four_state)
.opt_level(opt_level)
.trace_post_optimized_sir()
.build_with_trace();
let mut sim = result.res.unwrap();
let program = result.trace.post_optimized_sir.unwrap();
let layout =
MemoryLayout::build(&program, four_state, MemoryLayoutMode::ElementStrided);
let address = program.get_addr(&[], &["lanes"]).unwrap();
assert!(
layout.unpacked_arrays.contains_key(&address),
"zero reset must permit strided storage: width={width}, four_state={four_state}, opt={opt_level:?}"
);
let clk = sim.event("clk");
let clear = sim.signal("clear");
let write = sim.signal("write");
let index = sim.signal("index");
let probe = sim.signal("probe");
let d = sim.signal("d");
let q = sim.signal("q");
let old = sim.signal("old");
let low = sim.signal("low");
let high = sim.signal("high");
let full = (1u64 << width) - 1;
for round in 0..2 {
sim.modify(|io| io.set(clear, 1u8)).unwrap();
sim.tick(clk).unwrap();
let mut expected = [(0u64, 0u64); 8];
for lane in 0..8 {
sim.modify(|io| io.set(probe, lane as u8)).unwrap();
assert_eq!(sim.get_four_state(q), (0u8.into(), 0u8.into()));
}
sim.modify(|io| {
io.set(clear, 0u8);
io.set(write, 1u8);
})
.unwrap();
for lane in [7, 0, 4, 2, 6, 1, 5, 3, 7] {
let mask = if four_state {
((lane as u64 ^ round) | ((lane as u64 & 1) << (width - 1))) & full
} else {
0
};
let value = ((full ^ lane as u64 ^ round) | mask) & full;
sim.modify(|io| {
io.set(index, lane as u8);
io.set_four_state(d, value.into(), mask.into());
})
.unwrap();
sim.tick(clk).unwrap();
let (old_value, old_mask) = expected[lane];
assert_eq!(sim.get_four_state(old), (old_value.into(), old_mask.into()));
expected[lane] = (value, mask);
for (lane, &(value, mask)) in expected.iter().enumerate() {
sim.modify(|io| io.set(probe, lane as u8)).unwrap();
assert_eq!(sim.get_four_state(q), (value.into(), mask.into()));
assert_eq!(
sim.get_four_state(low),
((value & 1).into(), (mask & 1).into())
);
assert_eq!(
sim.get_four_state(high),
((value >> (width - 1)).into(), (mask >> (width - 1)).into())
);
}
}
}
}
}
}
}
#[test]
fn native_compilation_can_be_initialized_later() {
let code = r#"
module Top (o: output logic<8>) {
assign o = 8'h5a;
}
"#;
let compilation = Simulator::builder(code, "Top").compile_native().unwrap();
assert!(compilation.warnings().is_empty());
assert!(!compilation.program_image().code_image().is_empty());
let directory = tempfile::tempdir().unwrap();
let image_path = directory.path().join("top.native-image");
compilation.write_image(&image_path).unwrap();
let image =
celox::NativeProgramImage::from_container_bytes(&std::fs::read(&image_path).unwrap())
.unwrap();
assert_eq!(image.code_image(), compilation.program_image().code_image());
let mut sim = compilation.initialize().unwrap();
assert_eq!(sim.get(sim.signal("o")), 0x5au64.into());
}
#[test]
fn native_image_restores_runtime_metadata_without_source_compilation() {
let code = r#"
module Top (o: output logic<8>) {
assign o = 8'ha5;
}
"#;
let image = Simulator::builder(code, "Top")
.compile_native()
.unwrap()
.into_program_image();
let image =
celox::NativeProgramImage::from_container_bytes(&image.to_container_bytes().unwrap())
.unwrap();
let output = image
.reflection()
.signals()
.iter()
.find(|signal| signal.full_name == "Top.o")
.expect("compiled image should retain output reflection")
.signal;
let mut sim = Simulator::from_sources(Vec::new(), "Top")
.build_native_from_image(image)
.unwrap();
assert_eq!(sim.get(output), 0xa5u64.into());
assert!(sim.named_signals().iter().any(|signal| signal.name == "o"));
assert_eq!(sim.named_hierarchy().module_name, "Top");
let child_output = sim.child_signal(&[], "o");
assert_eq!(sim.get(child_output), 0xa5u64.into());
assert!(!sim.build_vcd_descs(false).is_empty());
}
#[test]
fn native_image_restores_four_state_mode_for_vcd() {
let code = r#"
module Top (a: input logic<8>, o: output logic<8>) {
assign o = a;
}
"#;
let image = Simulator::builder(code, "Top")
.four_state(true)
.compile_native()
.unwrap()
.into_program_image();
let image =
celox::NativeProgramImage::from_container_bytes(&image.to_container_bytes().unwrap())
.unwrap();
let directory = tempfile::tempdir().unwrap();
let vcd_path = directory.path().join("top.vcd");
let mut sim = Simulator::from_sources(Vec::new(), "Top")
.four_state(false)
.vcd(&vcd_path)
.build_native_from_image(image)
.unwrap();
sim.set_four_state(sim.signal("a"), 0xffu8.into(), 0xffu8.into());
sim.dump(0);
sim.flush_vcd().unwrap();
let dump = std::fs::read_to_string(vcd_path).unwrap();
assert!(dump.contains("xxxxxxxx"), "{dump}");
}
#[cfg(all(target_arch = "x86_64", not(feature = "arm64-codegen")))]
fn run_single_block_mir(insts: Vec<celox::native_backend::mir::MInst>, vreg_count: usize) -> u64 {
use celox::native_backend::emit;
use celox::native_backend::jit_mem;
use celox::native_backend::mir::{
BlockId as MBlockId, MBlock, MFunction, SpillDesc, VRegAllocator,
};
use celox::native_backend::regalloc;
let mut vregs = VRegAllocator::new();
for _ in 0..vreg_count {
vregs.alloc();
}
let mut func = MFunction::new(vregs, vec![SpillDesc::transient(); vreg_count]);
let mut block = MBlock::new(MBlockId(0));
for inst in insts {
block.push(inst);
}
block.push(celox::native_backend::mir::MInst::Return);
func.blocks.push(block);
func.verify();
let ra = regalloc::run_regalloc(&mut func).unwrap();
let emit_result = emit::emit(&func, &ra.assignment, ra.spill_frame_size).expect("emit failed");
let jit = jit_mem::JitCode::new(&emit_result.code).expect("mmap failed");
let mut state = vec![0u8; 8];
let ret = unsafe { jit.call(&mut state) };
assert_eq!(ret, 0);
u64::from_le_bytes(state[..8].try_into().unwrap())
}
fn compile_and_run(
code: &str,
top: &str,
setup: impl Fn(&mut [u8], &OptimizedSir, &MemoryLayout),
) -> (Vec<u8>, OptimizedSir, MemoryLayout) {
compile_and_run_inner(code, top, setup, false)
}
fn compile_and_run_inner(
code: &str,
top: &str,
setup: impl Fn(&mut [u8], &OptimizedSir, &MemoryLayout),
debug: bool,
) -> (Vec<u8>, OptimizedSir, MemoryLayout) {
let trace = SimulatorBuilder::new(code, top)
.optimize(true)
.trace_post_optimized_sir()
.build_with_trace();
let sir = trace.trace.post_optimized_sir.unwrap();
let layout = MemoryLayout::build(&sir, false, MemoryLayoutMode::ElementStrided);
let eu = &sir.sir.eval_comb[0];
#[cfg(all(target_arch = "x86_64", not(feature = "arm64-codegen")))]
let emit_result = {
use celox::native_backend::{emit, isel, mir_legalize, regalloc};
let mut mfunc = isel::lower_execution_unit(eu, &layout, false);
mir_legalize::legalize(&mut mfunc);
if debug {
println!("=== MIR ===\n{mfunc}");
}
let ra = regalloc::run_regalloc(&mut mfunc).unwrap();
if debug {
println!("=== Assignment ===\n{:?}", ra.assignment);
}
emit::emit(&mfunc, &ra.assignment, ra.spill_frame_size).expect("emit failed")
};
#[cfg(all(target_arch = "aarch64", not(feature = "x86_64-codegen")))]
let emit_result = {
use celox::native_backend::emit;
emit::emit_prepared_eu(eu, &layout, false, "native_exec", false, None, || false)
.expect("emit failed")
};
if debug {
use celox::native_backend::emit;
println!(
"=== Disassembly ===\n{}",
emit::disassemble(&emit_result.code[..emit_result.text_size], 0)
);
}
let jit = celox::native_backend::jit_mem::JitCode::new(&emit_result.code).expect("mmap failed");
let mut state = vec![0u8; layout.merged_total_size.max(256)];
setup(&mut state, &sir, &layout);
let ret = unsafe { jit.call(&mut state) };
assert_eq!(ret, 0, "JIT function returned non-zero (error)");
(state, sir, layout)
}
fn write_u32_at(state: &mut [u8], sir: &OptimizedSir, layout: &MemoryLayout, name: &str, val: u32) {
let addr = sir.get_addr(&[], &[name]).unwrap();
let off = layout.offsets[&addr];
state[off..off + 4].copy_from_slice(&val.to_le_bytes());
}
fn read_u32_at(state: &[u8], sir: &OptimizedSir, layout: &MemoryLayout, name: &str) -> u32 {
let addr = sir.get_addr(&[], &[name]).unwrap();
let off = layout.offsets[&addr];
u32::from_le_bytes(state[off..off + 4].try_into().unwrap())
}
#[cfg(all(target_arch = "x86_64", not(feature = "arm64-codegen")))]
#[test]
fn test_native_bsr_or_zero_and_nonzero() {
use celox::native_backend::mir::{BaseReg, MInst, OpSize, VReg};
let run = |value| {
run_single_block_mir(
vec![
MInst::LoadImm {
dst: VReg(0),
value,
},
MInst::BsrOr {
dst: VReg(1),
src: VReg(0),
zero_value: 63,
},
MInst::Store {
base: BaseReg::SimState,
offset: 0,
src: VReg(1),
size: OpSize::S64,
},
],
2,
)
};
assert_eq!(run(0), 63);
assert_eq!(run(1), 0);
assert_eq!(run(0x8000_0000_0000_0000), 63);
assert_eq!(run(0x10), 4);
}
#[cfg(all(target_arch = "x86_64", not(feature = "arm64-codegen")))]
#[test]
fn test_native_bsr_nonzero() {
use celox::native_backend::mir::{BaseReg, MInst, OpSize, VReg};
let run = |value| {
run_single_block_mir(
vec![
MInst::LoadImm {
dst: VReg(0),
value,
},
MInst::Bsr {
dst: VReg(1),
src: VReg(0),
},
MInst::Store {
base: BaseReg::SimState,
offset: 0,
src: VReg(1),
size: OpSize::S64,
},
],
2,
)
};
assert_eq!(run(1), 0);
assert_eq!(run(0x10), 4);
assert_eq!(run(0x8000_0000_0000_0000), 63);
}
#[cfg(all(target_arch = "x86_64", not(feature = "arm64-codegen")))]
#[test]
fn test_native_div_zero_safe_rhs_select() {
use celox::native_backend::mir::{BaseReg, CmpKind, MInst, OpSize, VReg};
let run = |lhs, rhs| {
run_single_block_mir(
vec![
MInst::LoadImm {
dst: VReg(0),
value: lhs,
},
MInst::LoadImm {
dst: VReg(1),
value: rhs,
},
MInst::LoadImm {
dst: VReg(2),
value: 0,
},
MInst::LoadImm {
dst: VReg(3),
value: 1,
},
MInst::Cmp {
dst: VReg(4),
lhs: VReg(1),
rhs: VReg(2),
kind: CmpKind::Eq,
},
MInst::Select {
dst: VReg(5),
cond: VReg(4),
true_val: VReg(3),
false_val: VReg(1),
},
MInst::UDiv {
dst: VReg(6),
lhs: VReg(0),
rhs: VReg(5),
},
MInst::Store {
base: BaseReg::SimState,
offset: 0,
src: VReg(6),
size: OpSize::S64,
},
],
7,
)
};
assert_eq!(run(42, 0), 42);
assert_eq!(run(42, 7), 6);
}
#[cfg(all(target_arch = "x86_64", not(feature = "arm64-codegen")))]
#[test]
fn test_native_div_preserves_live_divisor_across_div() {
use celox::native_backend::mir::{BaseReg, MInst, OpSize, VReg};
let result = run_single_block_mir(
vec![
MInst::LoadImm {
dst: VReg(0),
value: 100,
},
MInst::LoadImm {
dst: VReg(1),
value: 2,
},
MInst::LoadImm {
dst: VReg(2),
value: 50,
},
MInst::UDiv {
dst: VReg(3),
lhs: VReg(0),
rhs: VReg(1),
},
MInst::UDiv {
dst: VReg(4),
lhs: VReg(2),
rhs: VReg(1),
},
MInst::Store {
base: BaseReg::SimState,
offset: 0,
src: VReg(4),
size: OpSize::S64,
},
],
5,
);
assert_eq!(result, 25);
}
#[test]
fn test_native_add() {
let code = r#"
module Top (x: input logic<32>, y: input logic<32>, z: output logic<32>) {
assign z = x + y;
}
"#;
let (state, sir, layout) = compile_and_run(code, "Top", |state, sir, layout| {
write_u32_at(state, sir, layout, "x", 100);
write_u32_at(state, sir, layout, "y", 200);
});
assert_eq!(read_u32_at(&state, &sir, &layout, "z"), 300);
}
#[test]
fn test_native_bitwise() {
let code = r#"
module Top (a: input logic<32>, b: input logic<32>, x: output logic<32>, y: output logic<32>) {
assign x = a & b;
assign y = a | b;
}
"#;
let (state, sir, layout) = compile_and_run(code, "Top", |state, sir, layout| {
write_u32_at(state, sir, layout, "a", 0xFF00FF00);
write_u32_at(state, sir, layout, "b", 0x0F0F0F0F);
});
assert_eq!(read_u32_at(&state, &sir, &layout, "x"), 0x0F000F00);
assert_eq!(read_u32_at(&state, &sir, &layout, "y"), 0xFF0FFF0F);
}
#[test]
fn test_native_shared_expression() {
let code = r#"
module Top (a: input logic<32>, b: input logic<32>, x: output logic<32>, y: output logic<32>) {
assign x = (a + b) & 32'd1;
assign y = (a + b) | 32'd2;
}
"#;
let (state, sir, layout) = compile_and_run(code, "Top", |state, sir, layout| {
write_u32_at(state, sir, layout, "a", 7);
write_u32_at(state, sir, layout, "b", 3);
});
assert_eq!(read_u32_at(&state, &sir, &layout, "x"), 0); assert_eq!(read_u32_at(&state, &sir, &layout, "y"), 10); }
#[test]
fn test_native_sub() {
let code = r#"
module Top (a: input logic<32>, b: input logic<32>, z: output logic<32>) {
assign z = a - b;
}
"#;
let (state, sir, layout) = compile_and_run(code, "Top", |state, sir, layout| {
write_u32_at(state, sir, layout, "a", 500);
write_u32_at(state, sir, layout, "b", 200);
});
assert_eq!(read_u32_at(&state, &sir, &layout, "z"), 300);
}
#[test]
fn test_native_xor() {
let code = r#"
module Top (a: input logic<32>, b: input logic<32>, z: output logic<32>) {
assign z = a ^ b;
}
"#;
let (state, sir, layout) = compile_and_run(code, "Top", |state, sir, layout| {
write_u32_at(state, sir, layout, "a", 0xAAAAAAAA);
write_u32_at(state, sir, layout, "b", 0x55555555);
});
assert_eq!(read_u32_at(&state, &sir, &layout, "z"), 0xFFFFFFFF);
}
#[test]
fn test_native_mul() {
let code = r#"
module Top (a: input logic<32>, b: input logic<32>, z: output logic<32>) {
assign z = a * b;
}
"#;
let (state, sir, layout) = compile_and_run(code, "Top", |state, sir, layout| {
write_u32_at(state, sir, layout, "a", 7);
write_u32_at(state, sir, layout, "b", 6);
});
assert_eq!(read_u32_at(&state, &sir, &layout, "z"), 42);
}
#[test]
fn test_simulator_native_simple_assignment() {
let code = r#"
module Top (a: input logic<32>, b: output logic<32>) {
assign b = a;
}
"#;
let mut sim = Simulator::builder(code, "Top").build_native().unwrap();
let a = sim.signal("a");
let b = sim.signal("b");
sim.modify(|io| io.set(a, 0xDEADBEEFu32)).unwrap();
assert_eq!(sim.get(b), 0xDEADBEEFu32.into());
}
#[test]
fn test_native_execution_timing_is_opt_in_and_counts_host_calls() {
let code = r#"
module Top (a: input logic<32>, b: output logic<32>) {
assign b = a;
}
"#;
let mut sim = Simulator::builder(code, "Top").build_native().unwrap();
assert_eq!(sim.finish_native_execution_timing(), None);
sim.start_native_execution_timing();
sim.eval_comb().unwrap();
let timing = sim
.finish_native_execution_timing()
.expect("timing must be available after it is started");
assert_eq!(timing.calls(), 1);
}
#[test]
fn test_simulator_native_add() {
let code = r#"
module Top (
x: input logic<32>,
y: input logic<32>,
z: output logic<32>,
) {
assign z = x + y;
}
"#;
let mut sim = Simulator::builder(code, "Top").build_native().unwrap();
let x = sim.signal("x");
let y = sim.signal("y");
let z = sim.signal("z");
sim.modify(|io| {
io.set(x, 100u32);
io.set(y, 200u32);
})
.unwrap();
assert_eq!(sim.get(z), 300u32.into());
}
#[test]
fn test_simulator_native_dependency_chain() {
let code = r#"
module Top (a: input logic<32>, b: output logic<32>) {
var c: logic<32>;
assign c = b;
assign b = a;
}
"#;
let mut sim = Simulator::builder(code, "Top").build_native().unwrap();
let a = sim.signal("a");
let c = sim.signal("c");
sim.modify(|io| io.set(a, 0x12345678u32)).unwrap();
assert_eq!(sim.get(c), 0x12345678u32.into());
}
#[test]
fn test_native_shl_register() {
let code = r#"
module Top (
val: input logic<32>,
shift_amt: input logic<32>,
z: output logic<32>,
) {
assign z = val << shift_amt;
}
"#;
let (state, sir, layout) = compile_and_run(code, "Top", |state, sir, layout| {
write_u32_at(state, sir, layout, "val", 0xFF);
write_u32_at(state, sir, layout, "shift_amt", 16);
});
assert_eq!(read_u32_at(&state, &sir, &layout, "z"), 0x00FF0000);
}
#[test]
fn test_native_oversized_variable_shifts() {
let code = r#"
module Top (
val: input logic<32>,
signed_val: input i32,
shift_amt: input logic<32>,
left: output logic<32>,
right: output logic<32>,
arithmetic: output i32,
) {
assign left = val << shift_amt;
assign right = val >> shift_amt;
assign arithmetic = signed_val >>> shift_amt;
}
"#;
let (state, sir, layout) = compile_and_run(code, "Top", |state, sir, layout| {
write_u32_at(state, sir, layout, "val", 0x8000_0001);
write_u32_at(state, sir, layout, "signed_val", 0x8000_0000);
write_u32_at(state, sir, layout, "shift_amt", 64);
});
assert_eq!(read_u32_at(&state, &sir, &layout, "left"), 0);
assert_eq!(read_u32_at(&state, &sir, &layout, "right"), 0);
assert_eq!(read_u32_at(&state, &sir, &layout, "arithmetic"), u32::MAX);
}
#[test]
fn test_debug_let_bitslice_write() {
let code = r#"
module Top (
o_lo: output logic<32>,
o_hi: output logic<32>
) {
var data: logic<64> [4];
always_comb {
for i in 0..4 {
data[i] = 64'd0;
}
for g in 0..2 {
for s in 0..2 {
let idx: u32 = g * 2 + s;
data[idx][63:32] = (g * 2 + s) as u32;
data[idx][31:0] = (g * 2 + s + 100) as u32;
}
}
o_hi = data[2][63:32];
o_lo = data[2][31:0];
}
}
"#;
let trace = SimulatorBuilder::new(code, "Top")
.optimize(true)
.trace_post_optimized_sir()
.build_with_trace();
let sir_text = trace.trace.format_program().unwrap();
println!("{sir_text}");
let sir = trace.trace.post_optimized_sir.unwrap();
let layout = celox::MemoryLayout::build(&sir, false, MemoryLayoutMode::ElementStrided);
for (eu_idx, eu) in sir.sir.eval_comb.iter().enumerate() {
#[cfg(all(target_arch = "x86_64", not(feature = "arm64-codegen")))]
let emit_result = {
use celox::native_backend::{emit, isel, regalloc};
let mut mfunc = isel::lower_execution_unit(eu, &layout, false);
println!("=== EU {eu_idx} MIR ===\n{mfunc}");
let ra = regalloc::run_regalloc(&mut mfunc).unwrap();
println!("=== EU {eu_idx} Assignment ===\n{:?}", ra.assignment);
emit::emit(&mfunc, &ra.assignment, ra.spill_frame_size).expect("emit failed")
};
#[cfg(all(target_arch = "aarch64", not(feature = "x86_64-codegen")))]
let emit_result = celox::native_backend::emit::emit_prepared_eu(
eu,
&layout,
false,
"native_exec_debug",
false,
None,
|| false,
)
.expect("emit failed");
println!(
"=== EU {eu_idx} Disassembly ===\n{}",
celox::native_backend::emit::disassemble(&emit_result.code[..emit_result.text_size], 0)
);
}
let mut sim = SimulatorBuilder::new(code, "Top").build_native().unwrap();
let o_hi = sim.signal("o_hi");
let o_lo = sim.signal("o_lo");
println!("Native: o_hi={:?}, o_lo={:?}", sim.get(o_hi), sim.get(o_lo));
assert_eq!(sim.get(o_hi), 2u64.into());
assert_eq!(sim.get(o_lo), 102u64.into());
}
#[test]
fn test_native_dynamic_index_pattern() {
let code = r#"
module Top (
packed: input logic<32>,
idx: input logic<2>,
val: input logic<8>,
z: output logic<32>,
) {
var mask: logic<32>;
var shift: logic<32>;
assign shift = idx as u32 * 8;
assign mask = 32'hFF << shift;
assign z = (packed & ~mask) | ((val as u32) << shift);
}
"#;
let (state, sir, layout) = compile_and_run(code, "Top", |state, sir, layout| {
write_u32_at(state, sir, layout, "packed", 0x04030201);
let idx_addr = sir.get_addr(&[], &["idx"]).unwrap();
let idx_off = layout.offsets[&idx_addr];
state[idx_off] = 2;
let val_addr = sir.get_addr(&[], &["val"]).unwrap();
let val_off = layout.offsets[&val_addr];
state[val_off] = 0x55;
});
assert_eq!(read_u32_at(&state, &sir, &layout, "z"), 0x04550201);
}
#[test]
fn native_image_roundtrip_preserves_waveform_write_activity() {
let code = r#"module Top (a: input logic<8>, q: output logic<8>) { assign q = a + 1; }"#;
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("image.vcd");
let image = Simulator::builder(code, "Top")
.vcd(&path)
.compile_native()
.unwrap()
.into_program_image();
assert!(image.layout().trace.is_some());
let image =
celox::NativeProgramImage::from_container_bytes(&image.to_container_bytes().unwrap())
.unwrap();
let mut sim = Simulator::from_sources(Vec::new(), "Top")
.vcd(&path)
.build_native_from_image(image)
.unwrap();
sim.dump(0);
sim.set(sim.signal("a"), 41u8);
sim.dump(1);
assert_eq!(sim.get_as::<u8>(sim.signal("q")), 42);
let before = sim.vcd_statistics().unwrap();
sim.dump(2);
assert_eq!(
sim.vcd_statistics().unwrap().comparisons,
before.comparisons
);
assert!(before.changes >= 4);
sim.flush_vcd().unwrap();
let bytes = std::fs::read(path).unwrap();
let mut parser = vcd::Parser::new(bytes.as_slice());
parser.parse_header().unwrap();
assert!(parser.map(Result::unwrap).any(|command| {
matches!(command, vcd::Command::ChangeVector(_, ref value) if value.to_string() == "101010")
}));
}
#[test]
fn tracing_an_imported_strided_image_requests_compatible_recompilation() {
let code = r#"
module Top (clk: input clock, d: input logic<3>, index: input logic<2>, q: output logic<3>) {
var mem: logic<3>[4];
always_ff (clk) { mem[index] = d; }
assign q = mem[index];
}
"#;
let image = Simulator::builder(code, "Top")
.compile_native()
.unwrap()
.into_program_image();
assert!(!image.layout().unpacked_arrays.is_empty());
let dir = tempfile::tempdir().unwrap();
let result = Simulator::from_sources(Vec::new(), "Top")
.vcd(dir.path().join("incompatible.vcd"))
.build_native_from_image(image);
let Err(error) = result else {
panic!("an incompatible waveform layout was accepted");
};
assert!(
error
.to_string()
.contains("compile this native image with VCD enabled"),
"{error}"
);
}