use celox::{BigUint, Simulator};
#[path = "test_utils/mod.rs"]
#[macro_use]
mod test_utils;
all_backends! {
fn indeterminate_short_circuit_lhs_executes_effectful_rhs(sim) {
@omit_veryl;
@ignore_on(sv);
@build Simulator::builder(r#"
module Top (
u: input logic,
and_output: output logic,
or_output: output logic,
and_result: output logic,
or_result: output logic,
) {
function set_output (y: output logic) -> logic {
y = 1'b1;
return 1'b1;
}
always_comb {
and_output = 1'b0;
or_output = 1'b0;
and_result = u && set_output(and_output);
or_result = u || set_output(or_output);
}
}
"#, "Top").four_state(true);
let u = sim.signal("u");
sim.modify(|io| {
io.set_four_state(u, BigUint::from(0u8), BigUint::from(1u8));
})
.unwrap();
assert_eq!(sim.get(sim.signal("and_output")), 1u8.into());
assert_eq!(sim.get(sim.signal("or_output")), 1u8.into());
}
fn indeterminate_ternary_executes_effectful_arms_in_order(sim) {
@ignore_on(veryl, sv);
@build Simulator::builder(r#"
module Top (
sel: input logic,
seed: input logic<8>,
q_output: output logic<8>,
result: output logic,
) {
function set_two (y: output logic<8>) -> logic {
y = 8'd2;
return 1'b0;
}
function increment (
x: input logic<8>,
y: output logic<8>,
) -> logic {
y = x + 8'd1;
return 1'b0;
}
always_comb {
q_output = seed;
result = if sel ? set_two(q_output) : increment(q_output, q_output);
}
}
"#, "Top").four_state(true);
let sel = sim.signal("sel");
let seed = sim.signal("seed");
let q_output = sim.signal("q_output");
sim.modify(|io| {
io.set(seed, 1u8);
io.set_four_state(sel, BigUint::from(1u8), BigUint::from(1u8));
})
.unwrap();
assert_eq!(sim.get_four_state(q_output), (3u8.into(), 0u8.into()));
sim.modify(|io| {
io.set_four_state(sel, BigUint::from(0u8), BigUint::from(1u8));
})
.unwrap();
assert_eq!(sim.get_four_state(q_output), (3u8.into(), 0u8.into()));
sim.modify(|io| io.set_four_state(sel, 1u8.into(), 0u8.into()))
.unwrap();
assert_eq!(sim.get_four_state(q_output), (2u8.into(), 0u8.into()));
sim.modify(|io| io.set_four_state(sel, 0u8.into(), 0u8.into()))
.unwrap();
assert_eq!(sim.get_four_state(q_output), (2u8.into(), 0u8.into()));
}
fn logical_unknown_truth_table_matches_comb_and_ff(sim) {
@omit_veryl;
@ignore_on(sv);
@build Simulator::builder(r#"
module Top (
clk: input clock,
u: input logic,
c_and_one: output logic,
c_or_zero: output logic,
c_zero_and: output logic,
c_one_or: output logic,
f_and_one: output logic,
f_or_zero: output logic,
f_zero_and: output logic,
f_one_or: output logic,
) {
assign c_and_one = u && 1'b1;
assign c_or_zero = u || 1'b0;
assign c_zero_and = 1'b0 && u;
assign c_one_or = 1'b1 || u;
always_ff (clk) {
f_and_one = u && 1'b1;
f_or_zero = u || 1'b0;
f_zero_and = 1'b0 && u;
f_one_or = 1'b1 || u;
}
}
"#, "Top").four_state(true);
let clk = sim.event("clk");
let u = sim.signal("u");
for (value, label) in [(1u8, "X"), (0u8, "Z")] {
sim.modify(|io| {
io.set_four_state(u, BigUint::from(value), BigUint::from(1u8));
})
.unwrap();
sim.tick(clk).unwrap();
for prefix in ["c", "f"] {
for suffix in ["and_one", "or_zero"] {
let name = format!("{prefix}_{suffix}");
let signal = sim.signal(&name);
assert_eq!(
sim.get_four_state(signal),
(BigUint::from(1u8), BigUint::from(1u8)),
"{name}, lhs={label}",
);
}
let zero_and = format!("{prefix}_zero_and");
assert_eq!(
sim.get_four_state(sim.signal(&zero_and)),
(BigUint::from(0u8), BigUint::from(0u8)),
"{zero_and}, rhs={label}",
);
let one_or = format!("{prefix}_one_or");
assert_eq!(
sim.get_four_state(sim.signal(&one_or)),
(BigUint::from(1u8), BigUint::from(0u8)),
"{one_or}, rhs={label}",
);
}
}
}
fn ternary_unknown_condition_merges_branch_bits(sim) {
@omit_veryl;
@ignore_on(sv);
@build Simulator::builder(r#"
module Top (
clk: input clock,
sel: input logic,
a: input logic<8>,
b: input logic<8>,
c: output logic<8>,
f: output logic<8>,
) {
assign c = if sel ? a : b;
always_ff (clk) {
f = if sel ? a : b;
}
}
"#, "Top").four_state(true);
let clk = sim.event("clk");
let sel = sim.signal("sel");
let a = sim.signal("a");
let b = sim.signal("b");
for (cond_value, label) in [(1u8, "X"), (0u8, "Z")] {
sim.modify(|io| {
io.set_four_state(sel, BigUint::from(cond_value), BigUint::from(1u8));
io.set_four_state(a, BigUint::from(0xaau8), BigUint::from(0u8));
io.set_four_state(b, BigUint::from(0xbbu8), BigUint::from(0u8));
})
.unwrap();
sim.tick(clk).unwrap();
for name in ["c", "f"] {
assert_eq!(
sim.get_four_state(sim.signal(name)),
(BigUint::from(0xbbu8), BigUint::from(0x11u8)),
"{name}, cond={label}",
);
}
sim.modify(|io| {
io.set_four_state(a, BigUint::from(0xa5u8), BigUint::from(0u8));
io.set_four_state(b, BigUint::from(0xa5u8), BigUint::from(0u8));
})
.unwrap();
sim.tick(clk).unwrap();
for name in ["c", "f"] {
assert_eq!(
sim.get_four_state(sim.signal(name)),
(BigUint::from(0xa5u8), BigUint::from(0u8)),
"{name}, identical branches, cond={label}",
);
}
}
}
fn wide_logical_unknown_truth_table_uses_dominant_values(sim) {
@omit_veryl;
@ignore_on(sv);
@build Simulator::builder(r#"
module Top (
clk: input clock,
u: input logic<130>,
c_and_one: output logic,
c_or_zero: output logic,
c_and_zero: output logic,
c_or_one: output logic,
f_and_one: output logic,
f_or_zero: output logic,
f_and_zero: output logic,
f_or_one: output logic,
) {
assign c_and_one = u && 1'b1;
assign c_or_zero = u || 1'b0;
assign c_and_zero = u && 1'b0;
assign c_or_one = u || 1'b1;
always_ff (clk) {
f_and_one = u && 1'b1;
f_or_zero = u || 1'b0;
f_and_zero = u && 1'b0;
f_or_one = u || 1'b1;
}
}
"#, "Top").four_state(true);
let clk = sim.event("clk");
let u = sim.signal("u");
let unknown_bit = BigUint::from(1u8) << 100usize;
for (value, label) in [
(unknown_bit.clone(), "X in the high chunk"),
(BigUint::from(0u8), "Z in the high chunk"),
] {
sim.modify(|io| {
io.set_four_state(u, value.clone(), unknown_bit.clone());
})
.unwrap();
sim.tick(clk).unwrap();
for prefix in ["c", "f"] {
for suffix in ["and_one", "or_zero"] {
let name = format!("{prefix}_{suffix}");
assert_eq!(
sim.get_four_state(sim.signal(&name)),
(BigUint::from(1u8), BigUint::from(1u8)),
"{name}, {label}",
);
}
let and_zero = format!("{prefix}_and_zero");
let and_zero_signal = sim.signal(&and_zero);
assert_eq!(
sim.get_four_state(and_zero_signal),
(BigUint::from(0u8), BigUint::from(0u8)),
"{and_zero}, {label}",
);
let or_one = format!("{prefix}_or_one");
let or_one_signal = sim.signal(&or_one);
assert_eq!(
sim.get_four_state(or_one_signal),
(BigUint::from(1u8), BigUint::from(0u8)),
"{or_one}, {label}",
);
}
}
}
fn logical_not_known_one_dominates_unknown_bits(sim) {
@ignore_on(sv);
@build Simulator::builder(r#"
module Top (
clk: input clock,
narrow: input logic<8>,
wide: input logic<130>,
c_narrow: output logic,
c_wide: output logic,
f_narrow: output logic,
f_wide: output logic,
) {
assign c_narrow = !narrow;
assign c_wide = !wide;
always_ff (clk) {
f_narrow = !narrow;
f_wide = !wide;
}
}
"#, "Top").four_state(true);
let clk = sim.event("clk");
let narrow = sim.signal("narrow");
let wide = sim.signal("wide");
let narrow_unknown = BigUint::from(1u8) << 2usize;
let narrow_known_one = BigUint::from(1u8) << 7usize;
let wide_unknown = BigUint::from(1u8) << 3usize;
let wide_known_one = BigUint::from(1u8) << 100usize;
for (unknown_value, label) in [(1u8, "X"), (0u8, "Z")] {
let unknown_value = BigUint::from(unknown_value);
sim.modify(|io| {
io.set_four_state(
narrow,
narrow_known_one.clone() | (&narrow_unknown * &unknown_value),
narrow_unknown.clone(),
);
io.set_four_state(
wide,
wide_known_one.clone() | (&wide_unknown * &unknown_value),
wide_unknown.clone(),
);
})
.unwrap();
sim.tick(clk).unwrap();
for name in ["c_narrow", "c_wide", "f_narrow", "f_wide"] {
let signal = sim.signal(name);
assert_eq!(
sim.get_four_state(signal),
(BigUint::from(0u8), BigUint::from(0u8)),
"{name}, operand contains a known 1 and {label}",
);
}
sim.modify(|io| {
io.set_four_state(
narrow,
&narrow_unknown * &unknown_value,
narrow_unknown.clone(),
);
io.set_four_state(
wide,
&wide_unknown * &unknown_value,
wide_unknown.clone(),
);
})
.unwrap();
sim.tick(clk).unwrap();
for name in ["c_narrow", "c_wide", "f_narrow", "f_wide"] {
let signal = sim.signal(name);
assert_eq!(
sim.get_four_state(signal),
(BigUint::from(1u8), BigUint::from(1u8)),
"{name}, operand contains no known 1 and {label}",
);
}
}
}
fn effectful_ternary_takes_known_true_branch_despite_unknown_bits(sim) {
@ignore_on(veryl, sv);
@build Simulator::builder(r#"
module Top (
clk: input clock,
clear: input logic,
cond: input logic<130>,
side_state: output logic,
result: output logic,
) {
var side_effect: logic;
function set_side_effect (y: output logic) -> logic {
y = 1'b1;
return 1'b0;
}
always_ff (clk) {
if clear {
side_effect = 1'b0;
result = 1'b0;
} else {
result = if cond ? 1'b1 : set_side_effect(side_effect);
}
}
assign side_state = side_effect;
}
"#, "Top").four_state(true);
let clk = sim.event("clk");
let clear = sim.signal("clear");
let cond = sim.signal("cond");
let known_one = BigUint::from(1u8) << 100usize;
let unknown = BigUint::from(1u8) << 3usize;
sim.modify(|io| {
io.set(clear, 1u8);
})
.unwrap();
sim.tick(clk).unwrap();
sim.modify(|io| {
io.set(clear, 0u8);
io.set_four_state(cond, &known_one | &unknown, unknown.clone());
})
.unwrap();
sim.tick(clk).unwrap();
let result = sim.signal("result");
let side_state = sim.signal("side_state");
assert_eq!(
sim.get_four_state(result),
(BigUint::from(1u8), BigUint::from(0u8)),
);
assert_eq!(
sim.get_four_state(side_state),
(BigUint::from(0u8), BigUint::from(0u8)),
"the unselected effectful arm must not execute",
);
}
fn ff_procedural_control_uses_known_nonzero_truth(sim) {
@omit_veryl;
@ignore_on(sv);
@build Simulator::builder(r#"
module Top (
clk: input clock,
cond: input logic<8>,
wide_cond: input logic<130>,
if_result: output logic,
wide_if_result: output logic,
case_result: output logic,
function_result: output logic,
function_side: output logic,
) {
function choose (c: input logic<8>, side: output logic) -> logic {
if c {
side = 1'b1;
return 1'b1;
} else {
side = 1'b0;
return 1'b0;
}
}
always_ff (clk) {
if cond {
if_result = 1'b1;
} else {
if_result = 1'b0;
}
if wide_cond {
wide_if_result = 1'b1;
} else {
wide_if_result = 1'b0;
}
case cond {
8'h80: case_result = 1'b1;
default: case_result = 1'b0;
}
function_result = choose(cond, function_side);
}
}
"#, "Top").four_state(true);
let clk = sim.event("clk");
let cond = sim.signal("cond");
let wide_cond = sim.signal("wide_cond");
let unknown = BigUint::from(1u8) << 2usize;
let known_one = BigUint::from(1u8) << 7usize;
let wide_known_one = BigUint::from(1u8) << 100usize;
for (unknown_value, label) in [(1u8, "X"), (0u8, "Z")] {
sim.modify(|io| {
io.set_four_state(
cond,
known_one.clone() | (&unknown * unknown_value),
unknown.clone(),
);
io.set_four_state(
wide_cond,
wide_known_one.clone() | (&unknown * unknown_value),
unknown.clone(),
);
})
.unwrap();
sim.tick(clk).unwrap();
for name in [
"if_result",
"wide_if_result",
"function_result",
"function_side",
] {
assert_eq!(
sim.get_four_state(sim.signal(name)),
(BigUint::from(1u8), BigUint::from(0u8)),
"{name}, known one plus {label}",
);
}
assert_eq!(
sim.get_four_state(sim.signal("case_result")),
(BigUint::from(0u8), BigUint::from(0u8)),
"an unknown equality must not select a case arm ({label})",
);
sim.modify(|io| {
io.set_four_state(cond, &unknown * unknown_value, unknown.clone());
io.set_four_state(wide_cond, &unknown * unknown_value, unknown.clone());
})
.unwrap();
sim.tick(clk).unwrap();
for name in [
"if_result",
"wide_if_result",
"case_result",
"function_result",
"function_side",
] {
assert_eq!(
sim.get_four_state(sim.signal(name)),
(BigUint::from(0u8), BigUint::from(0u8)),
"{name}, only {label}",
);
}
}
sim.modify(|io| {
io.set_four_state(cond, known_one, BigUint::from(0u8));
io.set_four_state(wide_cond, wide_known_one, BigUint::from(0u8));
})
.unwrap();
sim.tick(clk).unwrap();
assert_eq!(
sim.get_four_state(sim.signal("case_result")),
(BigUint::from(1u8), BigUint::from(0u8)),
);
}
fn ff_unknown_reset_is_not_active(sim) {
@omit_veryl;
@ignore_on(sv);
@build Simulator::builder(r#"
module Top (
clk: input clock,
reset_high: input reset_sync_high,
reset_low: input reset_sync_low,
d: input logic,
q_high: output logic,
q_low: output logic,
) {
always_ff (clk, reset_high) {
if_reset {
q_high = 1'b0;
} else {
q_high = d;
}
}
always_ff (clk, reset_low) {
if_reset {
q_low = 1'b0;
} else {
q_low = d;
}
}
}
"#, "Top").four_state(true);
let clk = sim.event("clk");
let reset_high = sim.signal("reset_high");
let reset_low = sim.signal("reset_low");
let d = sim.signal("d");
sim.modify(|io| io.set(d, 1u8)).unwrap();
for (value, label) in [(1u8, "X"), (0u8, "Z")] {
sim.modify(|io| {
io.set_four_state(
reset_high,
BigUint::from(value),
BigUint::from(1u8),
);
io.set_four_state(reset_low, BigUint::from(value), BigUint::from(1u8));
})
.unwrap();
sim.tick(clk).unwrap();
for name in ["q_high", "q_low"] {
assert_eq!(
sim.get_four_state(sim.signal(name)),
(BigUint::from(1u8), BigUint::from(0u8)),
"{name}, reset={label}",
);
}
}
sim.modify(|io| {
io.set_four_state(reset_high, BigUint::from(1u8), BigUint::from(0u8));
io.set_four_state(reset_low, BigUint::from(0u8), BigUint::from(0u8));
})
.unwrap();
sim.tick(clk).unwrap();
for name in ["q_high", "q_low"] {
assert_eq!(
sim.get_four_state(sim.signal(name)),
(BigUint::from(0u8), BigUint::from(0u8)),
"{name}, known active reset",
);
}
}
fn ff_assert_uses_procedural_four_state_truth(sim) {
@omit_veryl;
@ignore_on(wasm, sv);
@build Simulator::builder(r#"
module Top (
clk: input clock,
cond: input logic<130>,
) {
always_ff (clk) {
$assert_continue(cond, "bad condition");
}
}
"#, "Top").four_state(true);
let clk = sim.event("clk");
let cond = sim.signal("cond");
let known_one = BigUint::from(1u8) << 100usize;
let unknown = BigUint::from(1u8) << 2usize;
sim.modify(|io| {
io.set_four_state(cond, &known_one | &unknown, unknown.clone());
})
.unwrap();
sim.tick(clk).unwrap();
assert!(sim.drain_runtime_events().is_empty());
for (value, mask, label) in [
(BigUint::from(0u8), BigUint::from(0u8), "0"),
(unknown.clone(), unknown.clone(), "X"),
(BigUint::from(0u8), unknown.clone(), "Z"),
] {
sim.modify(|io| {
io.set_four_state(cond, value.clone(), mask.clone());
})
.unwrap();
sim.tick(clk).unwrap();
assert_eq!(
sim.drain_runtime_events(),
vec![celox::RuntimeEvent::AssertContinue {
message: "bad condition".to_string(),
}],
"cond={label}",
);
}
}
fn comb_assert_uses_procedural_four_state_truth(sim) {
@omit_veryl;
@ignore_on(wasm, sv);
@build Simulator::builder(r#"
module Top (
cond: input logic<130>,
) {
always_comb {
$assert_continue(cond, "bad condition");
}
}
"#, "Top").four_state(true);
let cond = sim.signal("cond");
let known_one = BigUint::from(1u8) << 100usize;
let unknown = BigUint::from(1u8) << 2usize;
sim.drain_runtime_events();
for (unknown_value, label) in [(1u8, "X"), (0u8, "Z")] {
sim.modify(|io| {
io.set_four_state(
cond,
&known_one | (&unknown * unknown_value),
unknown.clone(),
);
})
.unwrap();
assert!(
sim.drain_runtime_events().is_empty(),
"known one plus {label} must pass",
);
sim.modify(|io| {
io.set_four_state(cond, &unknown * unknown_value, unknown.clone());
})
.unwrap();
assert_eq!(
sim.drain_runtime_events(),
vec![celox::RuntimeEvent::AssertContinue {
message: "bad condition".to_string(),
}],
"only {label} must fail",
);
}
}
fn wide_ternary_condition_known_one_dominates_unknown_bits(sim) {
@ignore_on(sv);
@build Simulator::builder(r#"
module Top (
clk: input clock,
sel: input logic<130>,
a: input logic<8>,
b: input logic<8>,
c: output logic<8>,
f: output logic<8>,
) {
assign c = if sel ? a : b;
always_ff (clk) {
f = if sel ? a : b;
}
}
"#, "Top").four_state(true);
let clk = sim.event("clk");
let sel = sim.signal("sel");
let a = sim.signal("a");
let b = sim.signal("b");
let known_one = BigUint::from(1u8) << 100usize;
let unknown_bit = BigUint::from(1u8) << 3usize;
for (unknown_value, label) in [
(unknown_bit.clone(), "X"),
(BigUint::from(0u8), "Z"),
] {
sim.modify(|io| {
io.set_four_state(
sel,
known_one.clone() | unknown_value.clone(),
unknown_bit.clone(),
);
io.set_four_state(a, BigUint::from(0xa5u8), BigUint::from(0u8));
io.set_four_state(b, BigUint::from(0x3cu8), BigUint::from(0u8));
})
.unwrap();
sim.tick(clk).unwrap();
for name in ["c", "f"] {
let signal = sim.signal(name);
assert_eq!(
sim.get_four_state(signal),
(BigUint::from(0xa5u8), BigUint::from(0u8)),
"{name}, lower unknown bit is {label}",
);
}
}
}
fn wide_ternary_unknown_condition_merges_every_arm_chunk(sim) {
@ignore_on(veryl, sv);
@build Simulator::builder(r#"
module Top (
clk: input clock,
sel: input logic,
a: input logic<130>,
b: input logic<130>,
c: output logic<130>,
f: output logic<130>,
) {
assign c = if sel ? a : b;
always_ff (clk) {
f = if sel ? a : b;
}
}
"#, "Top").four_state(true);
let clk = sim.event("clk");
let sel = sim.signal("sel");
let a = sim.signal("a");
let b = sim.signal("b");
let bit = |index: usize| BigUint::from(1u8) << index;
let a_value = bit(129) | bit(128) | bit(66) | bit(3) | bit(0);
let a_mask = bit(66) | bit(65) | bit(3) | bit(2);
let b_value = bit(128) | bit(64) | bit(3);
let b_mask = bit(66) | bit(3) | bit(2);
let full_mask = (BigUint::from(1u8) << 130usize) - BigUint::from(1u8);
let diff = ((&a_value ^ &b_value) | (&a_mask ^ &b_mask)) & &full_mask;
let expected_value = (&a_value | &diff) & &full_mask;
let expected_mask = (&a_mask | &diff) & &full_mask;
for (cond_value, label) in [(1u8, "X"), (0u8, "Z")] {
sim.modify(|io| {
io.set_four_state(sel, BigUint::from(cond_value), BigUint::from(1u8));
io.set_four_state(a, a_value.clone(), a_mask.clone());
io.set_four_state(b, b_value.clone(), b_mask.clone());
})
.unwrap();
sim.tick(clk).unwrap();
for name in ["c", "f"] {
let signal = sim.signal(name);
assert_eq!(
sim.get_four_state(signal),
(expected_value.clone(), expected_mask.clone()),
"{name}, cond={label}",
);
}
}
}
fn wide_unaligned_partial_store_and_slice_preserve_four_state_bits(sim) {
@build Simulator::builder(r#"
module Top (
seed: input logic<192>,
val: input logic<125>,
wide: output logic<192>,
sliced: output logic<125>,
) {
always_comb {
wide = seed;
wide[130:6] = val;
sliced = wide[130:6];
}
}
"#, "Top").four_state(true);
let seed = sim.signal("seed");
let val = sim.signal("val");
let wide = sim.signal("wide");
let sliced = sim.signal("sliced");
let bit = |index: usize| BigUint::from(1u8) << index;
let full_mask = (BigUint::from(1u8) << 192usize) - BigUint::from(1u8);
let val_width_mask = (BigUint::from(1u8) << 125usize) - BigUint::from(1u8);
let field_mask = &val_width_mask << 6usize;
let keep_mask = &full_mask ^ &field_mask;
let seed_mask = bit(190) | bit(129) | bit(63) | bit(4);
let seed_value = bit(191) | bit(130) | bit(64) | bit(5) | bit(0) | &seed_mask;
let val_value = bit(124) | bit(65) | bit(64) | bit(1);
let val_mask = bit(123) | bit(66) | bit(63) | bit(0);
let expected_value =
(&seed_value & &keep_mask) | ((&val_value & &val_width_mask) << 6usize);
let expected_mask =
(&seed_mask & &keep_mask) | ((&val_mask & &val_width_mask) << 6usize);
sim.modify(|io| {
io.set_four_state(seed, seed_value.clone(), seed_mask.clone());
io.set_four_state(val, val_value.clone(), val_mask.clone());
})
.unwrap();
assert_eq!(
sim.get_four_state(wide),
(expected_value, expected_mask),
"unaligned wide partial store",
);
assert_eq!(
sim.get_four_state(sliced),
(val_value & val_width_mask, val_mask),
"slice following the partial store",
);
}
}