use celox::Simulator;
#[path = "test_utils/mod.rs"]
#[macro_use]
mod test_utils;
all_backends! {
fn test_for_loop_instance_hierarchy(sim) {
@omit_veryl;
@ignore_on(sv);
@setup { let code = r#"
module Sub (
clk: input '_ clock,
i_data: input logic<8>,
o_data: output logic<8>
) {
assign o_data = i_data + 8'h01;
}
module Top (
clk: input '_ clock,
rst: input reset,
top_in: input logic<8>,
top_out: output logic<8>[2]
) {
for i in 0..2: g {
inst u_sub: Sub (
clk,
i_data: top_in,
o_data: top_out[i],
);
}
}
"#; }
@build Simulator::builder(code, "Top");
let hierarchy = sim.named_hierarchy();
assert_eq!(hierarchy.children.len(), 1, "should have 1 child group");
let (child_name, instances) = &hierarchy.children[0];
assert_eq!(child_name, "u_sub");
assert_eq!(instances.len(), 2, "for-loop should produce 2 instances");
assert_eq!(instances[0].module_name, "Sub");
assert_eq!(instances[1].module_name, "Sub");
let top_in = sim.signal("top_in");
sim.modify(|io| io.set(top_in, 0x10u8)).unwrap();
let child0_o = sim.child_signal(&[("u_sub", 0)], "o_data");
let child1_o = sim.child_signal(&[("u_sub", 1)], "o_data");
assert_eq!(sim.get(child0_o), 0x11u8.into());
assert_eq!(sim.get(child1_o), 0x11u8.into());
}
fn test_flattened_instance_port_connection(sim) {
@setup { let code = r#"
module Sub (
i_data: input logic<8>,
o_data: output logic<8>
) {
assign o_data = i_data;
}
module Top (
top_in: input logic<8>,
top_out: output logic<8>
) {
inst u_sub: Sub (
i_data: top_in,
o_data: top_out
);
}
"#; }
@build Simulator::builder(code, "Top");
let top_in = sim.signal("top_in");
let top_out = sim.signal("top_out");
sim.modify(|io| io.set(top_in, 0x55u8)).unwrap();
assert_eq!(sim.get(top_out), 0x55u8.into());
}
fn test_instance_input_function_output_writeback(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Child (
i: input logic,
o: output logic
) {
assign o = i;
}
module Top (
a: input logic,
child_o: output logic,
seen_o: output logic
) {
function write_seen (
x: input logic,
seen: output logic
) -> logic {
seen = x;
return x;
}
var seen: logic;
inst child: Child (
i: write_seen(a, seen),
o: child_o
);
assign seen_o = seen;
}
"#; }
@build Simulator::builder(code, "Top");
let a = sim.signal("a");
let child_o = sim.signal("child_o");
let seen_o = sim.signal("seen_o");
sim.modify(|io| io.set(a, 1u8)).unwrap();
assert_eq!(sim.get(child_o), 1u8.into());
assert_eq!(sim.get(seen_o), 1u8.into());
}
fn test_instance_input_function_output_concat_dynamic_writeback(sim) {
@ignore_on(veryl, sv);
@setup { let code = r#"
module Child (
i: input logic,
o: output logic
) {
assign o = i;
}
module Top (
value: input logic<2>,
index: input logic,
child_o: output logic,
mem_o: output logic<2>,
tmp_o: output logic
) {
function write_pair (
x: input logic<2>,
dst: output logic<2>
) -> logic {
dst = x;
return x[0];
}
var mem: logic<2>;
var tmp: logic;
inst child: Child (
i: write_pair(value, {mem[index], tmp}),
o: child_o
);
assign mem_o = mem;
assign tmp_o = tmp;
}
"#; }
@build Simulator::builder(code, "Top");
let value = sim.signal("value");
let index = sim.signal("index");
let child_o = sim.signal("child_o");
let mem_o = sim.signal("mem_o");
let tmp_o = sim.signal("tmp_o");
sim.modify(|io| {
io.set(value, 3u8);
io.set(index, 1u8);
}).unwrap();
assert_eq!(sim.get(child_o), 1u8.into());
assert_eq!(sim.get(mem_o), 2u8.into());
assert_eq!(sim.get(tmp_o), 1u8.into());
}
fn test_inactive_instance_input_output_call_adds_no_parent_driver(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Child (
i: input logic,
o: output logic
) {
assign o = i;
}
module Top (
a: input logic,
child_o: output logic,
seen_o: output logic
) {
function write_seen (
x: input logic,
seen: output logic
) -> logic {
seen = !x;
return x;
}
var seen: logic;
inst child: Child (
i: if 1'b0 ? write_seen(a, seen) : a,
o: child_o
);
assign seen = a;
assign seen_o = seen;
}
"#; }
@build Simulator::builder(code, "Top");
let a = sim.signal("a");
let child_o = sim.signal("child_o");
let seen_o = sim.signal("seen_o");
sim.modify(|io| io.set(a, 1u8)).unwrap();
assert_eq!(sim.get(child_o), 1u8.into());
assert_eq!(sim.get(seen_o), 1u8.into());
}
fn test_instance_input_function_output_preserves_runtime_display(sim) {
@omit_veryl;
@ignore_on(wasm, sv);
@setup { let code = r#"
module Child (
i: input logic,
o: output logic
) {
assign o = i;
}
module Top (
a: input logic,
child_o: output logic,
seen_o: output logic
) {
function write_seen (
x: input logic,
seen: output logic
) -> logic {
seen = x;
$display("seen=%0d", seen);
return x;
}
var seen: logic;
inst child: Child (
i: write_seen(a, seen),
o: child_o
);
assign seen_o = seen;
}
"#; }
@build Simulator::builder(code, "Top");
let a = sim.signal("a");
let child_o = sim.signal("child_o");
let seen_o = sim.signal("seen_o");
sim.drain_runtime_events();
sim.modify(|io| io.set(a, 1u8)).unwrap();
assert_eq!(sim.get(child_o), 1u8.into());
assert_eq!(sim.get(seen_o), 1u8.into());
assert_eq!(
sim.drain_runtime_events(),
vec![celox::RuntimeEvent::Display {
message: "seen=1".to_string(),
}],
);
}
fn test_instance_output_dynamic_index_function_output_writeback(sim) {
@ignore_on(veryl, sv);
@setup { let code = r#"
module Child (
i: input logic,
o: output logic
) {
assign o = i;
}
module Top (
sel: input logic,
mem_o: output logic<2>,
tmp_o: output logic
) {
function choose_index (
x: input logic,
tmp: output logic
) -> logic {
tmp = x;
return x;
}
var mem: logic<2>;
var tmp: logic;
inst child: Child (
i: 1'b1,
o: mem[choose_index(sel, tmp)]
);
assign mem_o = mem;
assign tmp_o = tmp;
}
"#; }
@build Simulator::builder(code, "Top");
let sel = sim.signal("sel");
let mem_o = sim.signal("mem_o");
let tmp_o = sim.signal("tmp_o");
sim.modify(|io| io.set(sel, 1u8)).unwrap();
assert_eq!(sim.get(mem_o), 2u8.into());
assert_eq!(sim.get(tmp_o), 1u8.into());
}
fn test_instance_output_dynamic_index_preserves_runtime_display(sim) {
@omit_veryl;
@ignore_on(wasm, sv);
@setup { let code = r#"
module Child (i: input logic, o: output logic) {
assign o = i;
}
module Top (
sel: input logic,
mem_o: output logic<2>,
tmp_o: output logic
) {
function choose_index (
x: input logic,
tmp: output logic
) -> logic {
tmp = x;
$display("index=%0d", x);
return x;
}
var mem: logic<2>;
var tmp: logic;
inst child: Child (
i: 1'b1,
o: mem[choose_index(sel, tmp)]
);
assign mem_o = mem;
assign tmp_o = tmp;
}
"#; }
@build Simulator::builder(code, "Top");
let sel = sim.signal("sel");
let mem_o = sim.signal("mem_o");
let tmp_o = sim.signal("tmp_o");
sim.drain_runtime_events();
sim.modify(|io| io.set(sel, 1u8)).unwrap();
assert_eq!(sim.get(mem_o), 3u8.into());
assert_eq!(sim.get(tmp_o), 1u8.into());
assert_eq!(
sim.drain_runtime_events(),
vec![celox::RuntimeEvent::Display {
message: "index=1".to_string(),
}],
);
}
fn test_instance_output_index_runtime_effect_tracks_plain_sibling_source(sim) {
@omit_veryl;
@ignore_on(wasm, sv);
@setup { let code = r#"
module Child (i: input logic, o: output logic) {
assign o = i;
}
module Top (
d: input logic,
offset: input logic,
mem_o: output logic<2>
) {
function emit (x: input logic) -> logic {
$display("d=%0d", x);
return 1'b0;
}
var mem: logic<2>;
inst child: Child (
i: 1'b1,
o: mem[emit(d) + offset]
);
assign mem_o = mem;
}
"#; }
@build Simulator::builder(code, "Top");
let offset = sim.signal("offset");
sim.drain_runtime_events();
sim.modify(|io| io.set(offset, 1u8)).unwrap();
sim.drain_runtime_events();
sim.modify(|io| io.set(offset, 0u8)).unwrap();
assert_eq!(
sim.drain_runtime_events(),
vec![celox::RuntimeEvent::Display {
message: "d=0".to_string(),
}],
);
}
fn test_instance_output_dynamic_index_composes_aliasing_writeback(sim) {
@ignore_on(veryl, sv);
@setup { let code = r#"
module Child (i: input logic, o: output logic) {
assign o = i;
}
module Top (
sel: input logic,
mem_o: output logic<2>
) {
function choose_index (
x: input logic,
tmp: output logic
) -> logic {
tmp = x;
return x;
}
var mem: logic<2>;
inst child: Child (
i: 1'b1,
o: mem[choose_index(sel, mem[0])]
);
assign mem_o = mem;
}
"#; }
@build Simulator::builder(code, "Top");
let sel = sim.signal("sel");
let mem_o = sim.signal("mem_o");
sim.modify(|io| io.set(sel, 1u8)).unwrap();
assert_eq!(sim.get(mem_o), 3u8.into());
}
fn test_instance_output_concat_advances_each_destination(sim) {
@ignore_on(veryl, sv);
@setup { let code = r#"
module Child (i: input logic<2>, o: output logic<2>) {
assign o = i;
}
module Top (
value: input logic<2>,
mem_o: output logic<2>,
tmp_o: output logic
) {
var mem: logic<2>;
var tmp: logic;
inst child: Child (
i: value,
o: {mem[tmp], tmp}
);
assign mem_o = mem;
assign tmp_o = tmp;
}
"#; }
@build Simulator::builder(code, "Top");
let value = sim.signal("value");
let mem_o = sim.signal("mem_o");
let tmp_o = sim.signal("tmp_o");
sim.modify(|io| io.set(value, 3u8)).unwrap();
assert_eq!(sim.get(tmp_o), 1u8.into());
assert_eq!(sim.get(mem_o), 2u8.into());
}
fn test_instance_output_concat_runtime_effect_observes_prior_slice(sim) {
@omit_veryl;
@ignore_on(wasm, sv);
@setup { let code = r#"
module Child (i: input logic<2>, o: output logic<2>) {
assign o = i;
}
module Top (
value: input logic<2>,
mem_o: output logic<2>,
tmp_o: output logic
) {
function observe_index (x: input logic) -> logic {
$display("index=%0d", x);
return x;
}
var mem: logic<2>;
var tmp: logic;
inst child: Child (
i: value,
o: {mem[observe_index(tmp)], tmp}
);
assign mem_o = mem;
assign tmp_o = tmp;
}
"#; }
@build Simulator::builder(code, "Top");
let value = sim.signal("value");
let tmp_o = sim.signal("tmp_o");
sim.drain_runtime_events();
sim.modify(|io| io.set(value, 3u8)).unwrap();
assert_eq!(sim.get(tmp_o), 1u8.into());
assert_eq!(
sim.drain_runtime_events(),
vec![celox::RuntimeEvent::Display {
message: "index=1".to_string(),
}],
);
}
fn test_instance_output_index_runtime_effect_triggers_on_child_change(sim) {
@omit_veryl;
@ignore_on(wasm, sv);
@setup { let code = r#"
module Child (i: input logic, o: output logic) {
assign o = i;
}
module Top (
data: input logic,
sel: input logic,
mem_o: output logic<2>
) {
function observe_index (x: input logic) -> logic {
$display("index=%0d", x);
return x;
}
var mem: logic<2>;
inst child: Child (
i: data,
o: mem[observe_index(sel)]
);
assign mem_o = mem;
}
"#; }
@build Simulator::builder(code, "Top");
let data = sim.signal("data");
let sel = sim.signal("sel");
sim.drain_runtime_events();
sim.modify(|io| {
io.set(data, 0u8);
io.set(sel, 1u8);
}).unwrap();
sim.drain_runtime_events();
sim.modify(|io| io.set(data, 1u8)).unwrap();
assert_eq!(
sim.drain_runtime_events(),
vec![celox::RuntimeEvent::Display {
message: "index=1".to_string(),
}],
);
}
fn test_instance_output_index_effect_triggers_when_two_state_parent_is_unchanged(sim) {
@omit_veryl;
@ignore_on(wasm, sv);
@setup { let code = r#"
module Child (mode: input logic<2>, o: output logic) {
always_comb {
case mode {
2'd0: o = 1'b0;
2'd1: o = 1'bx;
default: o = 1'bz;
}
}
}
module Top (
mode: input logic<2>,
index: input logic,
mem_o: output bit<2>
) {
function observe_index (x: input logic) -> logic {
$display("index=%0d", x);
return x;
}
var mem: bit<2>;
inst child: Child (
mode,
o: mem[observe_index(index)]
);
assign mem_o = mem;
}
"#; }
@build Simulator::builder(code, "Top");
let mode = sim.signal("mode");
let index = sim.signal("index");
let mem_o = sim.signal("mem_o");
sim.modify(|io| {
io.set(mode, 0u8);
io.set(index, 0u8);
}).unwrap();
sim.drain_runtime_events();
assert_eq!(sim.get_as::<u8>(mem_o), 0);
sim.modify(|io| io.set(mode, 1u8)).unwrap();
let x_parent_value = sim.get_as::<u8>(mem_o);
sim.drain_runtime_events();
sim.modify(|io| io.set(mode, 2u8)).unwrap();
assert_eq!(sim.get_as::<u8>(mem_o), x_parent_value);
assert_eq!(
sim.drain_runtime_events(),
vec![celox::RuntimeEvent::Display {
message: "index=0".to_string(),
}],
);
}
fn test_unconnected_child_output_needs_no_parent_glue(sim) {
@setup { let code = r#"
module Child (
i: input logic,
unused: output logic
) {
assign unused = i;
}
module Top (
i: input logic,
o: output logic
) {
inst child: Child (
i: i
);
assign o = i;
}
"#; }
@build Simulator::builder(code, "Top");
let i = sim.signal("i");
let o = sim.signal("o");
sim.modify(|io| io.set(i, 1u8)).unwrap();
assert_eq!(sim.get(o), 1u8.into());
}
fn test_instance_input_port_assignment_width_context(sim) {
@setup { let code = r#"
module Child (
widen_u: input logic<16>,
widen_s: input signed logic<16>,
sum9: input logic<9>,
trunc8: input logic<8>,
fill8: input logic<8>,
o_u16: output logic<16>,
o_s16: output logic<16>,
o_sum9: output logic<9>,
o_trunc: output logic<8>,
o_fill: output logic<8>
) {
assign o_u16 = widen_u;
assign o_s16 = widen_s;
assign o_sum9 = sum9;
assign o_trunc = trunc8;
assign o_fill = fill8;
}
module Top (
narrow_u: input logic<8>,
s8: input signed logic<8>,
a: input logic<8>,
b: input logic<8>,
wide16: input logic<16>,
o_u16: output logic<16>,
o_s16: output logic<16>,
o_sum9: output logic<9>,
o_trunc: output logic<8>,
o_fill: output logic<8>
) {
inst child: Child (
widen_u: narrow_u,
widen_s: s8,
sum9: a + b,
trunc8: wide16,
fill8: '1,
o_u16,
o_s16,
o_sum9,
o_trunc,
o_fill
);
}
"#; }
@build Simulator::builder(code, "Top");
let u8_in = sim.signal("narrow_u");
let s8 = sim.signal("s8");
let a = sim.signal("a");
let b = sim.signal("b");
let wide16 = sim.signal("wide16");
let o_u16 = sim.signal("o_u16");
let o_s16 = sim.signal("o_s16");
let o_sum9 = sim.signal("o_sum9");
let o_trunc = sim.signal("o_trunc");
let o_fill = sim.signal("o_fill");
sim.modify(|io| {
io.set(u8_in, 0xffu8);
io.set(s8, 0x80u8);
io.set(a, 0xffu8);
io.set(b, 1u8);
io.set(wide16, 0xab34u16);
})
.unwrap();
assert_eq!(sim.get(o_u16), 0x00ffu16.into());
assert_eq!(sim.get(o_s16), 0xff80u16.into());
assert_eq!(sim.get(o_sum9), 0x0100u16.into());
assert_eq!(sim.get(o_trunc), 0x34u8.into());
assert_eq!(sim.get(o_fill), 0xffu8.into());
}
fn test_dynamic_output_port_rmw_preserves_unselected_bits(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Child (
a: input logic<2>,
y: output logic<2>
) {
assign y = a;
}
module Top (
idx: input logic<3>,
a: input logic<2>,
out: output logic<8>
) {
var mem: logic<8>;
inst child: Child (
a,
y: mem[idx +: 2]
);
assign out = mem;
}
"#; }
@build Simulator::builder(code, "Top");
let idx = sim.signal("idx");
let a = sim.signal("a");
let out = sim.signal("out");
sim.modify(|io| {
io.set(idx, 0u8);
io.set(a, 3u8);
})
.unwrap();
assert_eq!(sim.get(out), 0x03u8.into());
sim.modify(|io| io.set(idx, 2u8)).unwrap();
assert_eq!(sim.get(out), 0x0fu8.into());
sim.modify(|io| {
io.set(a, 2u8);
})
.unwrap();
assert_eq!(sim.get(out), 0x0bu8.into());
sim.modify(|io| io.set(a, 0u8)).unwrap();
assert_eq!(sim.get(out), 0x03u8.into());
sim.modify(|io| {
io.set(idx, 6u8);
io.set(a, 3u8);
})
.unwrap();
assert_eq!(sim.get(out), 0xc3u8.into());
}
fn test_dynamic_output_port_converts_four_state_child_to_two_state_parent(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Child (y: output logic) {
assign y = 1'bx;
}
module Top (idx: input logic, out: output bit<2>) {
var mem: bit<2>;
inst child: Child (y: mem[idx]);
assign out = mem;
}
"#; }
@build Simulator::builder(code, "Top");
let idx = sim.signal("idx");
let out = sim.signal("out");
assert_eq!(sim.get_as::<u8>(out), 0);
sim.modify(|io| io.set(idx, 1u8)).unwrap();
assert_eq!(sim.get_as::<u8>(out), 0);
}
fn test_dynamic_minus_colon_output_port_rmw(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Child (a: input logic<2>, y: output logic<2>) {
assign y = a;
}
module Top (idx: input logic<3>, a: input logic<2>, out: output logic<8>) {
var mem: logic<8>;
inst child: Child (a, y: mem[idx -: 2]);
assign out = mem;
}
"#; }
@build Simulator::builder(code, "Top");
let idx = sim.signal("idx");
let a = sim.signal("a");
let out = sim.signal("out");
sim.modify(|io| { io.set(idx, 1u8); io.set(a, 3u8); }).unwrap();
assert_eq!(sim.get(out), 0x03u8.into());
sim.modify(|io| { io.set(idx, 4u8); io.set(a, 2u8); }).unwrap();
assert_eq!(sim.get(out), 0x13u8.into());
sim.modify(|io| io.set(a, 0u8)).unwrap();
assert_eq!(sim.get(out), 0x03u8.into());
sim.modify(|io| { io.set(idx, 7u8); io.set(a, 3u8); }).unwrap();
assert_eq!(sim.get(out), 0xc3u8.into());
}
fn test_dynamic_step_output_port_rmw(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Child (a: input logic<2>, y: output logic<2>) {
assign y = a;
}
module Top (idx: input logic<2>, a: input logic<2>, out: output logic<8>) {
var mem: logic<8>;
inst child: Child (a, y: mem[idx step 2]);
assign out = mem;
}
"#; }
@build Simulator::builder(code, "Top");
let idx = sim.signal("idx");
let a = sim.signal("a");
let out = sim.signal("out");
sim.modify(|io| { io.set(idx, 0u8); io.set(a, 3u8); }).unwrap();
assert_eq!(sim.get(out), 0x03u8.into());
sim.modify(|io| { io.set(idx, 2u8); io.set(a, 2u8); }).unwrap();
assert_eq!(sim.get(out), 0x23u8.into());
sim.modify(|io| io.set(a, 0u8)).unwrap();
assert_eq!(sim.get(out), 0x03u8.into());
sim.modify(|io| { io.set(idx, 3u8); io.set(a, 3u8); }).unwrap();
assert_eq!(sim.get(out), 0xc3u8.into());
}
fn test_dynamic_prefix_colon_output_port_allows_zero_lsb(sim) {
@omit_veryl;
@ignore_on(sv);
@setup { let code = r#"
module Child (a: input logic<8>, y: output logic<8>) {
assign y = a;
}
module Top (idx: input logic, a: input logic<8>, out: output logic<16>) {
var mem: logic<8> [2];
inst child: Child (a, y: mem[idx][7:0]);
assign out = mem;
}
"#; }
@build Simulator::builder(code, "Top");
let idx = sim.signal("idx");
let a = sim.signal("a");
let out = sim.signal("out");
sim.modify(|io| { io.set(idx, 0u8); io.set(a, 0x5au8); }).unwrap();
assert_eq!(sim.get(out), 0x005au16.into());
sim.modify(|io| { io.set(idx, 1u8); io.set(a, 0xa5u8); }).unwrap();
assert_eq!(sim.get(out), 0xa55au16.into());
}
fn test_multiple_instances_isolation(sim) {
@setup { let code = r#"
module Worker (
clk: input clock,
i_val: input logic<8>,
o_val: output logic<8>
) {
var internal_reg: logic<8>;
always_ff {
internal_reg = i_val + 1;
}
assign o_val = internal_reg;
}
module Top (
clk: input clock,
in0: input logic<8>,
in1: input logic<8>,
out0: output logic<8>,
out1: output logic<8>
) {
inst u0: Worker ( clk: clk, i_val: in0, o_val: out0 );
inst u1: Worker ( clk: clk, i_val: in1, o_val: out1 );
}
"#; }
@build Simulator::builder(code, "Top");
let clk = sim.event("clk");
let in0 = sim.signal("in0");
let in1 = sim.signal("in1");
let out0 = sim.signal("out0");
let out1 = sim.signal("out1");
sim.modify(|io| {
io.set(in0, 10u8);
io.set(in1, 20u8);
})
.unwrap();
sim.tick(clk).unwrap();
assert_eq!(sim.get(out0), 11u8.into());
assert_eq!(sim.get(out1), 21u8.into());
}
fn test_deep_hierarchical_path_resolution(sim) {
@setup { let code = r#"
module Leaf ( i: input logic, o: output logic ) {
assign o = ~i;
}
module Mid ( i: input logic, o: output logic ) {
inst u_leaf: Leaf ( i: i, o: o );
}
module Top ( top_i: input logic, top_o: output logic ) {
inst u_mid: Mid ( i: top_i, o: top_o );
}
"#; }
@build Simulator::builder(code, "Top");
let top_i = sim.signal("top_i");
let top_o = sim.signal("top_o");
sim.modify(|io| io.set(top_i, 1u8)).unwrap();
assert_eq!(sim.get(top_o), 0u8.into());
}
fn test_constant_propagation_across_hierarchy(sim) {
@setup { let code = r#"
module Sub ( i: input logic<8>, o: output logic<8> ) {
assign o = i + 8'h01;
}
module Top ( o: output logic<8> ) {
inst u_sub: Sub ( i: 8'h0F, o: o );
}
"#; }
@build Simulator::builder(code, "Top");
let o = sim.signal("o");
sim.modify(|_| {}).unwrap();
assert_eq!(sim.get(o), 0x10u8.into());
}
fn test_hierarchical_concat_feedback_runtime(sim) {
@omit_veryl;
@ignore_on(native, cranelift, wasm, sv);
@setup { let code = r#"
module Child (
a: input logic<2>,
lo: output logic,
) {
assign lo = a[1];
}
module Top (
inp: input logic,
out: output logic,
) {
var v: logic<2>;
var lo: logic;
inst c: Child (
a: v,
lo: lo,
);
assign v = {inp, lo};
assign out = v[0];
}
"#; }
@build Simulator::builder(code, "Top");
let inp = sim.signal("inp");
let out = sim.signal("out");
sim.modify(|io| io.set(inp, 0u8)).unwrap();
assert_eq!(sim.get(out), 0u8.into());
sim.modify(|io| io.set(inp, 1u8)).unwrap();
assert_eq!(sim.get(out), 1u8.into());
sim.modify(|io| io.set(inp, 0u8)).unwrap();
assert_eq!(sim.get(out), 0u8.into());
}
fn test_hierarchical_concat_feedback_runtime_multi_observe(sim) {
@omit_veryl;
@ignore_on(native, cranelift, wasm, sv);
@setup { let code = r#"
module Child (
a: input logic<2>,
lo: output logic,
) {
assign lo = a[1];
}
module Top (
inp: input logic,
out0: output logic,
out1: output logic,
) {
var v: logic<2>;
var lo: logic;
inst c: Child (
a: v,
lo: lo,
);
assign v = {inp, lo};
assign out0 = v[0];
assign out1 = v[1];
}
"#; }
@build Simulator::builder(code, "Top");
let inp = sim.signal("inp");
let out0 = sim.signal("out0");
let out1 = sim.signal("out1");
for bit in [0u8, 1u8, 0u8, 1u8] {
sim.modify(|io| io.set(inp, bit)).unwrap();
assert_eq!(sim.get(out0), bit.into());
assert_eq!(sim.get(out1), bit.into());
}
}
fn test_hierarchical_concat_feedback_with_constant_middle_bit(sim) {
@omit_veryl;
@ignore_on(native, cranelift, wasm, sv);
@setup { let code = r#"
module Child (
a: input logic<3>,
lo: output logic,
) {
assign lo = a[2];
}
module Top (
inp: input logic,
out: output logic,
mid: output logic,
) {
var v: logic<3>;
var lo: logic;
inst c: Child (
a: v,
lo: lo,
);
assign v = {inp, 1'b0, lo};
assign out = v[0];
assign mid = v[1];
}
"#; }
@build Simulator::builder(code, "Top");
let inp = sim.signal("inp");
let out = sim.signal("out");
let mid = sim.signal("mid");
sim.modify(|io| io.set(inp, 0u8)).unwrap();
assert_eq!(sim.get(out), 0u8.into());
assert_eq!(sim.get(mid), 0u8.into());
sim.modify(|io| io.set(inp, 1u8)).unwrap();
assert_eq!(sim.get(out), 1u8.into());
assert_eq!(sim.get(mid), 0u8.into());
}
fn test_hierarchical_dynamic_index_feedback_runtime(sim) {
@omit_veryl;
@ignore_on(native, cranelift, wasm, sv);
@setup { let code = r#"
module ChildFb (
a: input logic<3>,
lo: output logic,
) {
assign lo = a[2];
}
module ChildDyn (
a: input logic<3>,
idx: input logic,
o: output logic,
) {
assign o = a[idx];
}
module Top (
i1: input logic,
i2: input logic,
sel: input logic,
out_fb: output logic,
out_dyn: output logic,
) {
var v: logic<3>;
var lo: logic;
var d: logic;
inst fb: ChildFb (
a: v,
lo: lo,
);
inst dyn: ChildDyn (
a: v,
idx: sel,
o: d,
);
// Instance-crossing feedback (fb) and dynamic index access (dyn).
// lo = v[2], while v is built by split assignments so bit-dependencies are precise.
assign v[2:1] = {i2, i1};
assign v[0] = lo;
assign out_fb = lo;
assign out_dyn = d;
}
"#; }
@build Simulator::builder(code, "Top");
let i1 = sim.signal("i1");
let i2 = sim.signal("i2");
let sel = sim.signal("sel");
let out_fb = sim.signal("out_fb");
let out_dyn = sim.signal("out_dyn");
sim.modify(|io| {
io.set(i1, 1u8);
io.set(i2, 0u8);
io.set(sel, 0u8);
})
.unwrap();
assert_eq!(sim.get(out_fb), 0u8.into());
assert_eq!(sim.get(out_dyn), 0u8.into());
sim.modify(|io| io.set(sel, 1u8)).unwrap();
assert_eq!(sim.get(out_fb), 0u8.into());
assert_eq!(sim.get(out_dyn), 1u8.into());
sim.modify(|io| {
io.set(i1, 0u8);
io.set(i2, 1u8);
io.set(sel, 0u8);
})
.unwrap();
assert_eq!(sim.get(out_fb), 1u8.into());
assert_eq!(sim.get(out_dyn), 1u8.into());
}
fn test_hierarchical_dual_dynamic_readers_feedback_runtime(sim) {
@omit_veryl;
@ignore_on(native, cranelift, wasm, sv);
@setup { let code = r#"
module ChildFb (
a: input logic<3>,
lo: output logic,
) {
assign lo = a[2];
}
module ChildDyn (
a: input logic<3>,
idx: input logic,
o: output logic,
) {
assign o = a[idx];
}
module Top (
i1: input logic,
i2: input logic,
sel0: input logic,
sel1: input logic,
out_fb: output logic,
out0: output logic,
out1: output logic,
) {
var v: logic<3>;
var lo: logic;
var d0: logic;
var d1: logic;
inst fb: ChildFb (
a: v,
lo: lo,
);
inst dyn0: ChildDyn (
a: v,
idx: sel0,
o: d0,
);
inst dyn1: ChildDyn (
a: v,
idx: sel1,
o: d1,
);
assign v[2:1] = {i2, i1};
assign v[0] = lo;
assign out_fb = lo;
assign out0 = d0;
assign out1 = d1;
}
"#; }
@build Simulator::builder(code, "Top");
let i1 = sim.signal("i1");
let i2 = sim.signal("i2");
let sel0 = sim.signal("sel0");
let sel1 = sim.signal("sel1");
let out_fb = sim.signal("out_fb");
let out0 = sim.signal("out0");
let out1 = sim.signal("out1");
sim.modify(|io| {
io.set(i1, 1u8);
io.set(i2, 0u8);
io.set(sel0, 0u8);
io.set(sel1, 1u8);
})
.unwrap();
assert_eq!(sim.get(out_fb), 0u8.into());
assert_eq!(sim.get(out0), 0u8.into());
assert_eq!(sim.get(out1), 1u8.into());
sim.modify(|io| io.set(sel0, 1u8)).unwrap();
assert_eq!(sim.get(out_fb), 0u8.into());
assert_eq!(sim.get(out0), 1u8.into());
assert_eq!(sim.get(out1), 1u8.into());
sim.modify(|io| {
io.set(i2, 1u8);
io.set(sel1, 0u8);
})
.unwrap();
assert_eq!(sim.get(out_fb), 1u8.into());
assert_eq!(sim.get(out0), 1u8.into());
assert_eq!(sim.get(out1), 1u8.into());
sim.modify(|io| {
io.set(i1, 0u8);
io.set(sel0, 1u8);
io.set(sel1, 1u8);
})
.unwrap();
assert_eq!(sim.get(out_fb), 1u8.into());
assert_eq!(sim.get(out0), 0u8.into());
assert_eq!(sim.get(out1), 0u8.into());
}
fn test_hierarchical_overlapping_partial_write_dynamic_index_runtime(sim) {
@ignore_on(native, cranelift, wasm, sv);
@setup { let code = r#"
module ChildFb (
a: input logic<3>,
lo: output logic,
) {
assign lo = a[2];
}
module ChildDyn (
a: input logic<3>,
idx: input logic,
o: output logic,
) {
assign o = a[idx];
}
module Top (
i0: input logic,
i1: input logic,
i2: input logic,
sel: input logic,
out_fb: output logic,
out_dyn: output logic,
out_v0: output logic,
out_v1: output logic,
) {
var v: logic<3>;
var lo: logic;
var d: logic;
inst fb: ChildFb (
a: v,
lo: lo,
);
inst dyn: ChildDyn (
a: v,
idx: sel,
o: d,
);
// Non-overlapping source for feedback input.
assign v[2] = i2;
// Overlapping writes to the same bit: final v[1] must be lo (not i1).
always_comb {
v[1] = i1;
v[1] = lo;
v[0] = i0;
}
assign out_fb = lo;
assign out_dyn = d;
assign out_v0 = v[0];
assign out_v1 = v[1];
}
"#; }
@build Simulator::builder(code, "Top");
let i0 = sim.signal("i0");
let i1 = sim.signal("i1");
let i2 = sim.signal("i2");
let sel = sim.signal("sel");
let out_fb = sim.signal("out_fb");
let out_dyn = sim.signal("out_dyn");
let out_v0 = sim.signal("out_v0");
let out_v1 = sim.signal("out_v1");
sim.modify(|io| {
io.set(i0, 1u8);
io.set(i1, 1u8);
io.set(i2, 0u8);
io.set(sel, 1u8);
})
.unwrap();
assert_eq!(sim.get(out_fb), 0u8.into());
assert_eq!(sim.get(out_v0), 1u8.into());
assert_eq!(sim.get(out_v1), 0u8.into());
assert_eq!(sim.get(out_dyn), 0u8.into());
sim.modify(|io| io.set(sel, 0u8)).unwrap();
assert_eq!(sim.get(out_dyn), 1u8.into());
sim.modify(|io| io.set(i2, 1u8)).unwrap();
assert_eq!(sim.get(out_fb), 1u8.into());
assert_eq!(sim.get(out_v1), 1u8.into());
assert_eq!(sim.get(out_v0), 1u8.into());
sim.modify(|io| io.set(sel, 1u8)).unwrap();
assert_eq!(sim.get(out_dyn), 1u8.into());
sim.modify(|io| io.set(i1, 0u8)).unwrap();
assert_eq!(sim.get(out_v1), 1u8.into());
assert_eq!(sim.get(out_dyn), 1u8.into());
sim.modify(|io| {
io.set(i0, 0u8);
io.set(sel, 0u8);
})
.unwrap();
assert_eq!(sim.get(out_v0), 0u8.into());
assert_eq!(sim.get(out_dyn), 0u8.into());
}
fn test_hierarchical_concat_then_overlap_dynamic_index_runtime(sim) {
@ignore_on(native, cranelift, wasm, sv);
@setup { let code = r#"
module ChildFb (
a: input logic<3>,
lo: output logic,
) {
assign lo = a[2];
}
module ChildDyn (
a: input logic<3>,
idx: input logic,
o: output logic,
) {
assign o = a[idx];
}
module Top (
i0: input logic,
i1: input logic,
i2: input logic,
sel: input logic,
out_fb: output logic,
out_dyn: output logic,
out_v1: output logic,
) {
var v: logic<3>;
var lo: logic;
var d: logic;
inst fb: ChildFb (
a: v,
lo: lo,
);
inst dyn: ChildDyn (
a: v,
idx: sel,
o: d,
);
// Full concat assignment then overlapping bit override.
// Final v[1] should be lo (== v[2] == i2), not i1.
always_comb {
v = {i2, i1, i0};
v[1] = lo;
}
assign out_fb = lo;
assign out_dyn = d;
assign out_v1 = v[1];
}
"#; }
@build Simulator::builder(code, "Top");
let i0 = sim.signal("i0");
let i1 = sim.signal("i1");
let i2 = sim.signal("i2");
let sel = sim.signal("sel");
let out_fb = sim.signal("out_fb");
let out_dyn = sim.signal("out_dyn");
let out_v1 = sim.signal("out_v1");
sim.modify(|io| {
io.set(i0, 0u8);
io.set(i1, 0u8);
io.set(i2, 1u8);
io.set(sel, 1u8);
})
.unwrap();
assert_eq!(sim.get(out_fb), 1u8.into());
assert_eq!(sim.get(out_v1), 1u8.into());
assert_eq!(sim.get(out_dyn), 1u8.into());
sim.modify(|io| io.set(i1, 1u8)).unwrap();
assert_eq!(sim.get(out_v1), 1u8.into());
assert_eq!(sim.get(out_dyn), 1u8.into());
sim.modify(|io| io.set(i2, 0u8)).unwrap();
assert_eq!(sim.get(out_fb), 0u8.into());
assert_eq!(sim.get(out_v1), 0u8.into());
assert_eq!(sim.get(out_dyn), 0u8.into());
}
fn test_child_signal_access(sim) {
@setup { let code = r#"
module Sub (
i_data: input logic<8>,
o_data: output logic<8>
) {
assign o_data = i_data + 8'h01;
}
module Top (
top_in: input logic<8>,
top_out: output logic<8>
) {
inst u_sub: Sub (
i_data: top_in,
o_data: top_out
);
}
"#; }
@build Simulator::builder(code, "Top");
let top_in = sim.signal("top_in");
let child_i_data = sim.child_signal(&[("u_sub", 0)], "i_data");
let child_o_data = sim.child_signal(&[("u_sub", 0)], "o_data");
sim.modify(|io| io.set(top_in, 0x10u8)).unwrap();
assert_eq!(sim.get(child_i_data), 0x10u8.into());
assert_eq!(sim.get(child_o_data), 0x11u8.into());
}
fn test_named_hierarchy_structure(sim) {
@omit_veryl;
@setup { let code = r#"
module Leaf (
i: input logic,
o: output logic
) {
assign o = ~i;
}
module Mid (
i: input logic,
o: output logic
) {
inst u_leaf: Leaf ( i: i, o: o );
}
module Top (
top_i: input logic,
top_o: output logic
) {
inst u_mid: Mid ( i: top_i, o: top_o );
}
"#; }
@build Simulator::builder(code, "Top");
let hierarchy = sim.named_hierarchy();
assert_eq!(hierarchy.module_name, "Top");
assert!(hierarchy.signals.iter().any(|s| s.name == "top_i"));
assert!(hierarchy.signals.iter().any(|s| s.name == "top_o"));
assert_eq!(hierarchy.children.len(), 1);
let (mid_name, mid_instances) = &hierarchy.children[0];
assert_eq!(mid_name, "u_mid");
assert_eq!(mid_instances.len(), 1);
assert_eq!(mid_instances[0].module_name, "Mid");
assert!(mid_instances[0].signals.iter().any(|s| s.name == "i"));
assert!(mid_instances[0].signals.iter().any(|s| s.name == "o"));
assert_eq!(mid_instances[0].children.len(), 1);
let (leaf_name, leaf_instances) = &mid_instances[0].children[0];
assert_eq!(leaf_name, "u_leaf");
assert_eq!(leaf_instances.len(), 1);
assert_eq!(leaf_instances[0].module_name, "Leaf");
assert!(leaf_instances[0].signals.iter().any(|s| s.name == "i"));
assert!(leaf_instances[0].signals.iter().any(|s| s.name == "o"));
assert!(leaf_instances[0].children.is_empty());
}
fn test_named_hierarchy_multiple_instances(sim) {
@omit_veryl;
@setup { let code = r#"
module Worker (
clk: input clock,
i_val: input logic<8>,
o_val: output logic<8>
) {
var r_val: logic<8>;
always_ff { r_val = i_val; }
assign o_val = r_val;
}
module Top (
clk: input clock,
in0: input logic<8>,
in1: input logic<8>,
out0: output logic<8>,
out1: output logic<8>
) {
inst u0: Worker ( clk: clk, i_val: in0, o_val: out0 );
inst u1: Worker ( clk: clk, i_val: in1, o_val: out1 );
}
"#; }
@build Simulator::builder(code, "Top");
let hierarchy = sim.named_hierarchy();
assert_eq!(hierarchy.module_name, "Top");
assert_eq!(hierarchy.children.len(), 2);
for (name, instances) in &hierarchy.children {
assert!(name == "u0" || name == "u1");
assert_eq!(instances.len(), 1);
assert_eq!(instances[0].module_name, "Worker");
}
}
fn test_instance_signals_child(sim) {
@omit_veryl;
@setup { let code = r#"
module Sub (
i_data: input logic<8>,
o_data: output logic<8>
) {
assign o_data = i_data + 8'h01;
}
module Top (
top_in: input logic<8>,
top_out: output logic<8>
) {
inst u_sub: Sub (
i_data: top_in,
o_data: top_out
);
}
"#; }
@build Simulator::builder(code, "Top");
let child_signals = sim.instance_signals(&[("u_sub", 0)]);
assert!(!child_signals.is_empty());
let names: Vec<&str> = child_signals.iter().map(|s| s.name.as_str()).collect();
assert!(names.contains(&"i_data"), "expected i_data in {:?}", names);
assert!(names.contains(&"o_data"), "expected o_data in {:?}", names);
}
fn test_instance_signals_deep_hierarchy(sim) {
@omit_veryl;
@setup { let code = r#"
module Leaf (
i: input logic<8>,
o: output logic<8>
) {
assign o = i + 8'h01;
}
module Mid (
i: input logic<8>,
o: output logic<8>
) {
inst u_leaf: Leaf ( i: i, o: o );
}
module Top (
top_i: input logic<8>,
top_o: output logic<8>
) {
inst u_mid: Mid ( i: top_i, o: top_o );
}
"#; }
@build Simulator::builder(code, "Top");
let leaf_signals = sim.instance_signals(&[("u_mid", 0), ("u_leaf", 0)]);
assert!(!leaf_signals.is_empty());
let names: Vec<&str> = leaf_signals.iter().map(|s| s.name.as_str()).collect();
assert!(names.contains(&"i"), "expected i in {:?}", names);
assert!(names.contains(&"o"), "expected o in {:?}", names);
let top_i = sim.signal("top_i");
sim.modify(|io| io.set(top_i, 0x42u8)).unwrap();
let leaf_o = leaf_signals.iter().find(|s| s.name == "o").unwrap();
assert_eq!(sim.get(leaf_o.signal), 0x43u8.into());
}
fn test_instance_signals_multiple_instances(sim) {
@omit_veryl;
@setup { let code = r#"
module Worker (
i_val: input logic<8>,
o_val: output logic<8>
) {
assign o_val = i_val + 8'h01;
}
module Top (
in0: input logic<8>,
in1: input logic<8>,
out0: output logic<8>,
out1: output logic<8>
) {
inst u0: Worker ( i_val: in0, o_val: out0 );
inst u1: Worker ( i_val: in1, o_val: out1 );
}
"#; }
@build Simulator::builder(code, "Top");
let signals_u0 = sim.instance_signals(&[("u0", 0)]);
let signals_u1 = sim.instance_signals(&[("u1", 0)]);
assert!(!signals_u0.is_empty());
assert!(!signals_u1.is_empty());
let names_u0: Vec<&str> = signals_u0.iter().map(|s| s.name.as_str()).collect();
let names_u1: Vec<&str> = signals_u1.iter().map(|s| s.name.as_str()).collect();
assert!(names_u0.contains(&"o_val"));
assert!(names_u1.contains(&"o_val"));
let o_val_u0 = signals_u0.iter().find(|s| s.name == "o_val").unwrap();
let o_val_u1 = signals_u1.iter().find(|s| s.name == "o_val").unwrap();
assert_ne!(o_val_u0.signal, o_val_u1.signal);
let in0 = sim.signal("in0");
let in1 = sim.signal("in1");
sim.modify(|io| {
io.set(in0, 10u8);
io.set(in1, 20u8);
})
.unwrap();
assert_eq!(sim.get(o_val_u0.signal), 11u8.into());
assert_eq!(sim.get(o_val_u1.signal), 21u8.into());
}
fn test_instance_signals_nonexistent_path(sim) {
@omit_veryl;
@setup { let code = r#"
module Top (
i: input logic,
o: output logic
) {
assign o = i;
}
"#; }
@build Simulator::builder(code, "Top");
let signals = sim.instance_signals(&[("nonexistent", 0)]);
assert!(signals.is_empty());
let signals = sim.instance_signals(&[("a", 0), ("b", 0), ("c", 0)]);
assert!(signals.is_empty());
}
}