use celox::Simulator;
#[path = "test_utils/mod.rs"]
#[macro_use]
mod test_utils;
all_backends! {
fn test_same_module_count_and_empty_ff(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Top (
clk : input clock ,
rst : input reset ,
push : input logic ,
pop : input logic ,
empty: output logic ,
) {
var count: logic<8>;
var r_empty: logic;
always_ff (clk, rst) {
if_reset {
count = 0;
} else if push && !pop {
count = count + 1;
} else if pop && !push {
if count != 0 {
count = count - 1;
}
}
}
// Separate always_ff block reads count — should read OLD count (NBA)
always_ff (clk, rst) {
if_reset {
r_empty = 1;
} else {
if count == 0 {
r_empty = 1;
} else {
r_empty = 0;
}
}
}
assign empty = r_empty;
}
"#; }
@build Simulator::builder(code, "Top");
let clk = sim.event("clk");
let rst = sim.signal("rst");
let push = sim.signal("push");
let pop = sim.signal("pop");
let empty = sim.signal("empty");
sim.modify(|io| {
io.set(rst, 0u8);
io.set(push, 0u8);
io.set(pop, 0u8);
})
.unwrap();
sim.tick(clk).unwrap();
assert_eq!(sim.get(empty), 1u32.into(), "empty=1 after reset");
sim.modify(|io| io.set(rst, 1u8)).unwrap();
sim.modify(|io| io.set(push, 1u8)).unwrap();
sim.tick(clk).unwrap();
assert_eq!(
sim.get(empty),
1u32.into(),
"After push: r_empty should see OLD count=0 → empty=1 (1 cycle delay)"
);
sim.modify(|io| io.set(push, 0u8)).unwrap();
sim.tick(clk).unwrap();
assert_eq!(
sim.get(empty),
0u32.into(),
"After settling: r_empty sees count=1 → empty=0"
);
sim.modify(|io| io.set(pop, 1u8)).unwrap();
sim.tick(clk).unwrap();
assert_eq!(
sim.get(empty),
0u32.into(),
"After pop: r_empty should see OLD count=1 → empty=0 (1 cycle delay)"
);
sim.modify(|io| io.set(pop, 0u8)).unwrap();
sim.tick(clk).unwrap();
assert_eq!(
sim.get(empty),
1u32.into(),
"After settling: r_empty sees count=0 → empty=1"
);
}
fn test_cross_module_count_and_empty_ff(sim) {
@setup { let code = r#"
module EmptyDetector (
clk : input clock,
rst : input reset,
count_in : input logic<8>,
r_empty : output logic,
) {
always_ff (clk, rst) {
if_reset {
r_empty = 1;
} else {
if count_in == 0 {
r_empty = 1;
} else {
r_empty = 0;
}
}
}
}
module Top (
clk : input clock,
rst : input reset,
push : input logic,
pop : input logic,
empty: output logic,
) {
var count: logic<8>;
always_ff (clk, rst) {
if_reset {
count = 0;
} else if push && !pop {
count = count + 1;
} else if pop && !push {
if count != 0 {
count = count - 1;
}
}
}
inst det: EmptyDetector (
clk,
rst,
count_in: count,
r_empty: empty,
);
}
"#; }
@build Simulator::builder(code, "Top");
let clk = sim.event("clk");
let rst = sim.signal("rst");
let push = sim.signal("push");
let pop = sim.signal("pop");
let empty = sim.signal("empty");
sim.modify(|io| {
io.set(rst, 0u8);
io.set(push, 0u8);
io.set(pop, 0u8);
})
.unwrap();
sim.tick(clk).unwrap();
assert_eq!(sim.get(empty), 1u32.into(), "empty=1 after reset");
sim.modify(|io| io.set(rst, 1u8)).unwrap();
sim.modify(|io| io.set(push, 1u8)).unwrap();
sim.tick(clk).unwrap();
assert_eq!(
sim.get(empty),
1u32.into(),
"Cross-module: r_empty should see OLD count=0 → empty=1 (1 cycle delay)"
);
sim.modify(|io| io.set(push, 0u8)).unwrap();
sim.tick(clk).unwrap();
assert_eq!(sim.get(empty), 0u32.into(), "empty=0 after settling");
sim.modify(|io| io.set(pop, 1u8)).unwrap();
sim.tick(clk).unwrap();
assert_eq!(
sim.get(empty),
0u32.into(),
"Cross-module: r_empty should see OLD count=1 → empty=0 (1 cycle delay)"
);
sim.modify(|io| io.set(pop, 0u8)).unwrap();
sim.tick(clk).unwrap();
assert_eq!(sim.get(empty), 1u32.into(), "empty=1 after settling");
}
fn test_empty_never_stuck_at_zero(sim) {
@setup { let code = r#"
module Top (
clk : input clock,
rst : input reset,
push : input logic,
pop : input logic,
empty: output logic,
) {
var count: logic<8>;
var r_empty: logic;
always_ff (clk, rst) {
if_reset {
count = 0;
} else if push && !pop {
count = count + 1;
} else if pop && !push {
if count != 0 {
count = count - 1;
}
}
}
always_ff (clk, rst) {
if_reset {
r_empty = 1;
} else {
if count == 0 {
r_empty = 1;
} else {
r_empty = 0;
}
}
}
assign empty = r_empty;
}
"#; }
@build Simulator::builder(code, "Top");
let clk = sim.event("clk");
let rst = sim.signal("rst");
let push = sim.signal("push");
let pop = sim.signal("pop");
let empty = sim.signal("empty");
let count = sim.signal("count");
sim.modify(|io| {
io.set(rst, 0u8);
io.set(push, 0u8);
io.set(pop, 0u8);
})
.unwrap();
sim.tick(clk).unwrap();
sim.modify(|io| io.set(rst, 1u8)).unwrap();
for _ in 0..5 {
sim.modify(|io| io.set(push, 1u8)).unwrap();
sim.tick(clk).unwrap();
sim.modify(|io| io.set(push, 0u8)).unwrap();
sim.tick(clk).unwrap();
}
assert_eq!(sim.get(count), 5u32.into(), "count should be 5");
assert_eq!(sim.get(empty), 0u32.into(), "not empty with 5 items");
for i in 0..5 {
sim.modify(|io| io.set(pop, 1u8)).unwrap();
sim.tick(clk).unwrap();
sim.modify(|io| io.set(pop, 0u8)).unwrap();
sim.tick(clk).unwrap();
let expected_count = 4 - i;
assert_eq!(
sim.get(count),
(expected_count as u32).into(),
"count should be {} after popping {} items",
expected_count,
i + 1
);
}
sim.tick(clk).unwrap();
assert_eq!(
sim.get(empty),
1u32.into(),
"BUG: empty should be 1 after all items popped — stuck at 0 forever?"
);
}
}