use celox::{DeadStorePolicy, OptLevel, Simulator, SimulatorBuilder, TestResult};
use insta::assert_snapshot;
#[path = "test_utils/mod.rs"]
#[macro_use]
mod test_utils;
fn setup_and_trace(code: &str, top: &str) -> celox::CompilationTrace {
let result = SimulatorBuilder::new(code, top)
.optimize(true)
.trace_sim_modules()
.trace_post_optimized_sir()
.build_with_trace();
result.trace
}
const DSE_HIERARCHY_SOURCE: &str = r#"
module Sub (
i_data: input logic<8>,
o_data: output logic<8>,
) {
assign o_data = i_data;
}
module Top (
clk: input clock,
rst: input reset,
top_in: input logic<8>,
top_out: output logic<8>,
) {
inst u_sub: Sub (
i_data: top_in,
o_data: top_out,
);
}
"#;
all_backends! {
fn test_simple_assignment(sim) {
@setup { let code = r#"
module Top (a: input logic<32>, b: output logic<32>) {
assign b = a;
}
"#; }
@build Simulator::builder(code, "Top");
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());
}
fn test_dependency_chain(sim) {
@setup { let code = r#"
module Top (a: input logic<32>, b: output logic<32>) {
var c: logic<32>;
assign c = b;
assign b = a;
}
"#; }
@build Simulator::builder(code, "Top");
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());
}
fn test_mixed_selects_execution(sim) {
@setup { let code = r#"
module Top (a: input logic<5>, b: output logic<8>) {
assign b[0] = 1'b1;
assign b[2:1] = 2'b10;
assign b[7:3] = a;
}
"#; }
@build Simulator::builder(code, "Top");
let a = sim.signal("a");
let b = sim.signal("b");
sim.modify(|io| io.set(a, 0b10101u8)).unwrap();
assert_eq!(sim.get(b), 0xADu64.into());
}
fn test_overlapping_override(sim) {
@setup { let code = r#"
module Top (x: input logic<8>, y: input logic<4>, o: output logic<8>) {
var a: logic<8>;
always_comb{
a = x;
a[3:0] = y;
}
assign o = a;
}
"#; }
@build Simulator::builder(code, "Top");
let x = sim.signal("x");
let y = sim.signal("y");
let o = sim.signal("o");
sim.modify(|io| {
io.set(x, 0xFFu8);
io.set(y, 0x0u8);
})
.unwrap();
assert_eq!(sim.get(o), 0xF0u64.into());
}
fn test_comb_override_dependency(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Top (sel: input logic, val: input logic<8>, o: output logic<8>) {
var tmp: logic<8>;
always_comb {
tmp = 8'h11;
if sel {
tmp = val;
}
o = tmp;
}
}
"#; }
@build Simulator::builder(code, "Top");
let sel = sim.signal("sel");
let val = sim.signal("val");
let o = sim.signal("o");
sim.modify(|io| io.set(sel, 0u8)).unwrap();
assert_eq!(sim.get(o), 0x11u64.into());
sim.modify(|io| {
io.set(sel, 1u8);
io.set(val, 0xEEu8);
})
.unwrap();
assert_eq!(sim.get(o), 0xEEu64.into());
}
fn test_always_comb_read_before_write_uses_previous_value(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Top (
a: input logic,
c: output logic,
) {
var b: logic;
always_comb {
c = b;
b = a;
}
}
"#; }
@build Simulator::builder(code, "Top");
let a = sim.signal("a");
let c = sim.signal("c");
sim.modify(|io| io.set(a, 0u8)).unwrap();
assert_eq!(sim.get(c), 0u8.into());
sim.modify(|io| io.set(a, 1u8)).unwrap();
assert_eq!(sim.get(c), 0u8.into());
sim.modify(|io| io.set(a, 0u8)).unwrap();
assert_eq!(sim.get(c), 1u8.into());
}
fn test_comb_function_call_early_return(sim) {
@ignore_on(veryl, sv);
@setup { let code = r#"
module Top (
d: input logic<8>,
q: output logic<8>,
) {
function f (
x: input logic<8>,
) -> logic<8> {
if x == 8'd0 {
return x + 8'd1;
}
return x + 8'd2;
}
always_comb {
q = f(d);
}
}
"#; }
@build Simulator::builder(code, "Top");
let d = sim.signal("d");
let q = sim.signal("q");
sim.modify(|io| io.set(d, 0u8)).unwrap();
assert_eq!(sim.get(q), 1u32.into());
sim.modify(|io| io.set(d, 5u8)).unwrap();
assert_eq!(sim.get(q), 7u32.into());
}
fn test_comb_function_call_return_indexed_local_temp(sim) {
@setup { let code = r#"
module Top (
d: input logic<8>,
q: output logic,
) {
function f (
x: input logic<8>,
) -> logic {
var tmp: logic<8>;
tmp = x;
return tmp[0];
}
always_comb {
q = f(d);
}
}
"#; }
@build Simulator::builder(code, "Top");
let d = sim.signal("d");
let q = sim.signal("q");
sim.modify(|io| io.set(d, 0b1010u8)).unwrap();
assert_eq!(sim.get(q), 0u32.into());
sim.modify(|io| io.set(d, 0b1011u8)).unwrap();
assert_eq!(sim.get(q), 1u32.into());
}
fn test_comb_function_call_partial_write_local_temp(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Top (
d: input logic<8>,
q: output logic<8>,
) {
function f (
x: input logic<8>,
) -> logic<8> {
var tmp: logic<8>;
tmp[7:4] = x[3:0];
tmp[3:0] = x[7:4];
return tmp;
}
always_comb {
q = f(d);
}
}
"#; }
@build Simulator::builder(code, "Top");
let d = sim.signal("d");
let q = sim.signal("q");
sim.modify(|io| io.set(d, 0xABu8)).unwrap();
assert_eq!(sim.get(q), 0xBAu32.into());
}
fn test_comb_function_call_local_and_return_width_coercion(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Top (
sel: input logic,
q0 : output logic<8>,
q1 : output logic<8>,
) {
function f (
x: input logic,
) -> logic<8> {
var tmp: logic<8>;
tmp = 1'b1;
if x {
return 1'b1;
}
return tmp;
}
always_comb {
q0 = f(1'b0);
q1 = f(sel);
}
}
"#; }
@build Simulator::builder(code, "Top");
let sel = sim.signal("sel");
let q0 = sim.signal("q0");
let q1 = sim.signal("q1");
sim.modify(|io| io.set(sel, 0u8)).unwrap();
assert_eq!(sim.get(q0), 1u32.into());
assert_eq!(sim.get(q1), 1u32.into());
sim.modify(|io| io.set(sel, 1u8)).unwrap();
assert_eq!(sim.get(q1), 1u32.into());
}
fn test_comb_function_call_constant_folded_if_return(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Top (
q: output logic<8>,
) {
function f () -> logic<8> {
if 1'b1 {
return 8'd3;
}
}
always_comb {
q = f();
}
}
"#; }
@build Simulator::builder(code, "Top");
let q = sim.signal("q");
assert_eq!(sim.get(q), 3u32.into());
}
fn test_comb_function_call_return_inside_for(sim) {
@ignore_on(veryl, sv);
@setup { let code = r#"
module Top (
d: input logic<4>,
q: output logic<8>,
) {
function f (
x: input logic<4>,
) -> logic<8> {
for i in 0..4 {
if x[i] {
return 8'd9;
}
}
return 8'd1;
}
always_comb {
q = f(d);
}
}
"#; }
@build Simulator::builder(code, "Top");
let d = sim.signal("d");
let q = sim.signal("q");
sim.modify(|io| io.set(d, 0b0000u8)).unwrap();
assert_eq!(sim.get(q), 1u32.into());
sim.modify(|io| io.set(d, 0b0100u8)).unwrap();
assert_eq!(sim.get(q), 9u32.into());
}
fn test_comb_function_call_break_inside_for(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Top (
d: input logic<4>,
q: output logic<8>,
) {
function f (
x: input logic<4>,
) -> logic<8> {
var tmp: logic<8>;
tmp = 8'd0;
for i in 0..4 {
if x[i] {
tmp = i + 8'd1;
break;
}
}
return tmp;
}
always_comb {
q = f(d);
}
}
"#; }
@build Simulator::builder(code, "Top");
let d = sim.signal("d");
let q = sim.signal("q");
sim.modify(|io| io.set(d, 0b0000u8)).unwrap();
assert_eq!(sim.get(q), 0u32.into());
sim.modify(|io| io.set(d, 0b1010u8)).unwrap();
assert_eq!(sim.get(q), 2u32.into());
}
fn test_comb_function_call_nested_helper(sim) {
@setup { let code = r#"
module Top (
d: input logic<8>,
q: output logic<8>,
) {
function f (
x: input logic<8>,
) -> logic<8> {
return x + 8'd1;
}
function g (
x: input logic<8>,
) -> logic<8> {
return f(x) + 8'd1;
}
always_comb {
q = g(d);
}
}
"#; }
@build Simulator::builder(code, "Top");
let d = sim.signal("d");
let q = sim.signal("q");
sim.modify(|io| io.set(d, 8u8)).unwrap();
assert_eq!(sim.get(q), 10u32.into());
}
fn test_comb_function_call_statement_with_output_argument(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Top (
d: input logic<8>,
q: output logic<8>,
) {
function f (
x: input logic<8>,
y: output logic<8>,
) {
y = x + 8'd2;
}
var tmp: logic<8>;
always_comb {
f(d, tmp);
q = tmp;
}
}
"#; }
@build Simulator::builder(code, "Top");
let d = sim.signal("d");
let q = sim.signal("q");
sim.modify(|io| io.set(d, 10u8)).unwrap();
assert_eq!(sim.get(q), 12u32.into());
}
fn test_comb_function_call_expression_with_output_argument(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Top (
d: input logic<8>,
q_return: output logic<8>,
q_output: output logic<8>,
) {
function f (
x: input logic<8>,
y: output logic<8>,
) -> logic<8> {
y = x + 8'd1;
return x + 8'd2;
}
always_comb {
q_return = f(d, q_output);
}
}
"#; }
@build Simulator::builder(code, "Top");
let d = sim.signal("d");
let q_return = sim.signal("q_return");
let q_output = sim.signal("q_output");
sim.modify(|io| io.set(d, 10u8)).unwrap();
assert_eq!(sim.get(q_return), 12u32.into());
assert_eq!(sim.get(q_output), 11u32.into());
}
fn test_comb_function_call_expression_output_is_visible_to_later_operand(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Top (
d: input logic<8>,
q: output logic<8>,
) {
function f (
x: input logic<8>,
y: output logic<8>,
) -> logic<8> {
y = x + 8'd1;
return x + 8'd2;
}
var tmp: logic<8>;
always_comb {
q = f(d, tmp) + tmp;
}
}
"#; }
@build Simulator::builder(code, "Top");
let d = sim.signal("d");
let q = sim.signal("q");
sim.modify(|io| io.set(d, 10u8)).unwrap();
assert_eq!(sim.get(q), 23u32.into());
}
fn test_comb_function_call_expression_with_constant_input_keeps_output_write(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Top (
q_return: output logic<8>,
q_output: output logic<8>,
) {
function f (
x: input logic<8>,
y: output logic<8>,
) -> logic<8> {
y = x + 8'd1;
return x + 8'd2;
}
always_comb {
q_return = f(8'd10, q_output) + 8'd1;
}
}
"#; }
@build Simulator::builder(code, "Top");
let q_return = sim.signal("q_return");
let q_output = sim.signal("q_output");
assert_eq!(sim.get(q_return), 13u32.into());
assert_eq!(sim.get(q_output), 11u32.into());
}
fn test_comb_function_call_expression_output_survives_system_function_wrapper(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Top (
d: input logic<8>,
q_return: output logic<8>,
q_output: output logic<8>,
) {
function f (
x: input logic<8>,
y: output logic<8>,
) -> logic<8> {
y = x + 8'd1;
return x + 8'd2;
}
always_comb {
q_return = $unsigned(f(d, q_output));
}
}
"#; }
@build Simulator::builder(code, "Top");
let d = sim.signal("d");
let q_return = sim.signal("q_return");
let q_output = sim.signal("q_output");
sim.modify(|io| io.set(d, 10u8)).unwrap();
assert_eq!(sim.get(q_return), 12u32.into());
assert_eq!(sim.get(q_output), 11u32.into());
}
fn test_comb_function_call_with_output_argument_in_display_argument(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Top (
d: input logic<8>,
q: output logic<8>,
q_output: output logic<8>,
) {
function f (
x: input logic<8>,
y: output logic<8>,
) -> logic<8> {
y = x + 8'd1;
return x + 8'd2;
}
always_comb {
$display("ret=%0d", f(d, q_output));
q = q_output;
}
}
"#; }
@build Simulator::builder(code, "Top");
let d = sim.signal("d");
let q = sim.signal("q");
let q_output = sim.signal("q_output");
sim.modify(|io| io.set(d, 10u8)).unwrap();
assert_eq!(sim.get(q), 11u32.into());
assert_eq!(sim.get(q_output), 11u32.into());
}
fn test_comb_function_call_with_output_argument_in_index_expression(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Top (
data: input logic<4>,
sel: input logic<2>,
q: output logic,
q_output: output logic<8>,
) {
function f (
x: input logic<2>,
y: output logic<8>,
) -> logic<2> {
y = 8'd10 + x;
return x;
}
always_comb {
q = data[f(sel, q_output)];
}
}
"#; }
@build Simulator::builder(code, "Top");
let data = sim.signal("data");
let sel = sim.signal("sel");
let q = sim.signal("q");
let q_output = sim.signal("q_output");
sim.modify(|io| {
io.set(data, 0b1010u8);
io.set(sel, 1u8);
}).unwrap();
assert_eq!(sim.get(q), 1u32.into());
assert_eq!(sim.get(q_output), 11u32.into());
}
fn test_comb_function_call_with_output_argument_in_destination_index(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Top (
sel: input logic<2>,
q: output logic<4>,
q_output: output logic<8>,
) {
function f (
x: input logic<2>,
y: output logic<8>,
) -> logic<2> {
y = 8'd10 + x;
return x;
}
var data: logic<4>;
always_comb {
data = 4'b0000;
data[f(sel, q_output)] = 1'b1;
q = data;
}
}
"#; }
@build Simulator::builder(code, "Top");
let sel = sim.signal("sel");
let q = sim.signal("q");
let q_output = sim.signal("q_output");
sim.modify(|io| io.set(sel, 2u8)).unwrap();
assert_eq!(sim.get(q), 4u32.into());
assert_eq!(sim.get(q_output), 12u32.into());
}
fn test_comb_function_call_expression_output_is_guarded_by_ternary(sim) {
@ignore_on(veryl, sv);
@setup { let code = r#"
module Top (
sel: input logic,
d: input logic<8>,
q_return: output logic<8>,
q_output: output logic<8>,
) {
function f (
x: input logic<8>,
y: output logic<8>,
) -> logic<8> {
y = x + 8'd1;
return x + 8'd2;
}
always_comb {
q_output = d;
q_return = if sel ? f(d, q_output) : 8'd7;
}
}
"#; }
@build Simulator::builder(code, "Top");
let sel = sim.signal("sel");
let d = sim.signal("d");
let q_return = sim.signal("q_return");
let q_output = sim.signal("q_output");
sim.modify(|io| {
io.set(sel, 0u8);
io.set(d, 10u8);
}).unwrap();
assert_eq!(sim.get(q_return), 7u32.into());
assert_eq!(sim.get(q_output), 10u32.into());
sim.modify(|io| io.set(sel, 1u8)).unwrap();
assert_eq!(sim.get(q_return), 12u32.into());
assert_eq!(sim.get(q_output), 11u32.into());
}
fn test_comb_function_call_expression_output_respects_short_circuit(sim) {
@ignore_on(veryl, sv);
@setup { let code = r#"
module Top (
d: input logic<8>,
and_result: output logic,
or_result: output logic,
and_output: output logic<8>,
or_output: output logic<8>,
) {
function f (
x: input logic<8>,
y: output logic<8>,
) -> logic {
y = x + 8'd1;
return 1'b1;
}
always_comb {
and_output = 8'd0;
or_output = 8'd0;
and_result = 1'b0 && f(d, and_output);
or_result = 1'b1 || f(d, or_output);
}
}
"#; }
@build Simulator::builder(code, "Top");
let d = sim.signal("d");
let and_result = sim.signal("and_result");
let or_result = sim.signal("or_result");
let and_output = sim.signal("and_output");
let or_output = sim.signal("or_output");
sim.modify(|io| io.set(d, 10u8)).unwrap();
assert_eq!(sim.get(and_result), 0u32.into());
assert_eq!(sim.get(or_result), 1u32.into());
assert_eq!(sim.get(and_output), 0u32.into());
assert_eq!(sim.get(or_output), 0u32.into());
}
fn test_comb_function_call_with_output_argument_in_if_condition(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Top (
d: input logic<8>,
q: output logic,
q_output: output logic<8>,
) {
function f (
x: input logic<8>,
y: output logic<8>,
) -> logic {
y = x + 8'd1;
return x != 8'd0;
}
always_comb {
if f(d, q_output) {
q = 1'b1;
} else {
q = 1'b0;
}
}
}
"#; }
@build Simulator::builder(code, "Top");
let d = sim.signal("d");
let q = sim.signal("q");
let q_output = sim.signal("q_output");
sim.modify(|io| io.set(d, 10u8)).unwrap();
assert_eq!(sim.get(q), 1u32.into());
assert_eq!(sim.get(q_output), 11u32.into());
sim.modify(|io| io.set(d, 0u8)).unwrap();
assert_eq!(sim.get(q), 0u32.into());
assert_eq!(sim.get(q_output), 1u32.into());
}
fn test_comb_function_call_with_output_argument_in_case_target(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Top (
d: input logic<8>,
q: output logic<8>,
q_output: output logic<8>,
) {
function f (
x: input logic<8>,
y: output logic<8>,
) -> logic<2> {
y = x + 8'd1;
return x[1:0];
}
always_comb {
case f(d, q_output) {
2'd0: q = 8'd10;
2'd1: q = 8'd11;
default: q = 8'd12;
}
}
}
"#; }
@build Simulator::builder(code, "Top");
let d = sim.signal("d");
let q = sim.signal("q");
let q_output = sim.signal("q_output");
sim.modify(|io| io.set(d, 5u8)).unwrap();
assert_eq!(sim.get(q), 11u32.into());
assert_eq!(sim.get(q_output), 6u32.into());
sim.modify(|io| io.set(d, 6u8)).unwrap();
assert_eq!(sim.get(q), 12u32.into());
assert_eq!(sim.get(q_output), 7u32.into());
}
fn test_comb_function_call_with_output_argument_in_loop_condition(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Top (
d: input logic<8>,
q: output logic<8>,
q_output: output logic<8>,
) {
function f (
x: input logic<8>,
y: output logic<8>,
) -> logic {
y = x + 8'd1;
return x != 8'd0;
}
always_comb {
q = 8'd0;
for i in 0..2 {
if f(d + i, q_output) {
q = q + 8'd1;
}
}
}
}
"#; }
@build Simulator::builder(code, "Top");
let d = sim.signal("d");
let q = sim.signal("q");
let q_output = sim.signal("q_output");
sim.modify(|io| io.set(d, 3u8)).unwrap();
assert_eq!(sim.get(q), 2u32.into());
assert_eq!(sim.get(q_output), 5u32.into());
}
fn test_comb_nested_function_output_call_in_function_condition(sim) {
@ignore_on(veryl, sv);
@setup { let code = r#"
module Top (
d: input logic<8>,
q: output logic<8>,
q_output: output logic<8>,
) {
function inner (
x: input logic<8>,
y: output logic<8>,
) -> logic {
y = x + 8'd1;
return x != 8'd0;
}
function outer (
x: input logic<8>,
y: output logic<8>,
) -> logic<8> {
if inner(x, y) {
return x + 8'd2;
}
return 8'd0;
}
always_comb {
q = outer(d, q_output);
}
}
"#; }
@build Simulator::builder(code, "Top");
let d = sim.signal("d");
let q = sim.signal("q");
let q_output = sim.signal("q_output");
sim.modify(|io| io.set(d, 10u8)).unwrap();
assert_eq!(sim.get(q), 12u32.into());
assert_eq!(sim.get(q_output), 11u32.into());
}
fn test_comb_case_target_output_call_is_evaluated_once(sim) {
@ignore_on(veryl, sv);
@setup { let code = r#"
module Top (
q: output logic<8>,
q_output: output logic<8>,
) {
function f (
x: input logic<8>,
y: output logic<8>,
) -> logic<8> {
y = x + 8'd1;
return x;
}
var tmp: logic<8>;
always_comb {
tmp = 8'd0;
case f(tmp, tmp) {
8'd9: q = 8'd9;
8'd0: q = 8'd10;
default: q = 8'd12;
}
q_output = tmp;
}
}
"#; }
@build Simulator::builder(code, "Top");
let q = sim.signal("q");
let q_output = sim.signal("q_output");
assert_eq!(sim.get(q), 10u32.into());
assert_eq!(sim.get(q_output), 1u32.into());
}
fn test_comb_loop_bound_output_call_writes_back_once(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Top (
d: input logic<8>,
q: output logic<8>,
q_output: output logic<8>,
) {
function f (
x: input logic<8>,
y: output logic<8>,
) -> logic<8> {
y = x + 8'd1;
return x;
}
always_comb {
q = 8'd0;
q_output = 8'd0;
for i in f(d, q_output)..4 {
q = q + 8'd1;
}
}
}
"#; }
@build Simulator::builder(code, "Top");
let d = sim.signal("d");
let q = sim.signal("q");
let q_output = sim.signal("q_output");
sim.modify(|io| io.set(d, 1u8)).unwrap();
assert_eq!(sim.get(q), 3u32.into());
assert_eq!(sim.get(q_output), 2u32.into());
}
fn test_comb_value_system_function_statement_applies_argument_outputs(sim) {
@ignore_on(veryl, sv);
@setup { let code = r#"
module Top (
d: input logic<8>,
q: output logic<8>,
q_output: output logic<8>,
) {
function f (
x: input logic<8>,
y: output logic<8>,
) -> logic<8> {
y = x + 8'd1;
return x + 8'd2;
}
always_comb {
q_output = 8'd0;
$unsigned(f(d, q_output));
q = q_output;
}
}
"#; }
@build Simulator::builder(code, "Top");
let d = sim.signal("d");
let q = sim.signal("q");
let q_output = sim.signal("q_output");
sim.modify(|io| io.set(d, 10u8)).unwrap();
assert_eq!(sim.get(q), 11u32.into());
assert_eq!(sim.get(q_output), 11u32.into());
}
fn test_comb_value_system_function_after_dynamic_break_stays_inactive(sim) {
@ignore_on(veryl, sv);
@setup { let code = r#"
module Top (
stop: input logic,
d: input logic<8>,
count: input logic<8>,
q_output: output logic<8>,
) {
function f (
x: input logic<8>,
y: output logic<8>,
) -> logic<8> {
y = x + 8'd1;
return x;
}
always_comb {
q_output = 8'd0;
for i in 0..count {
if stop && i == 0 {
break;
}
$unsigned(f(d, q_output));
}
}
}
"#; }
@build Simulator::builder(code, "Top");
let stop = sim.signal("stop");
let d = sim.signal("d");
let count = sim.signal("count");
let q_output = sim.signal("q_output");
sim.modify(|io| {
io.set(stop, 1u8);
io.set(d, 10u8);
io.set(count, 2u8);
}).unwrap();
assert_eq!(sim.get(q_output), 0u32.into());
sim.modify(|io| io.set(stop, 0u8)).unwrap();
assert_eq!(sim.get(q_output), 11u32.into());
}
fn test_comb_effectful_if_condition_after_dynamic_break_stays_inactive(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Top (
stop: input logic,
d: input logic<8>,
count: input logic<8>,
q_output: output logic<8>,
) {
function f (
x: input logic<8>,
y: output logic<8>,
) -> logic {
y = x + 8'd1;
return 1'b1;
}
always_comb {
q_output = 8'd0;
for i in 0..count {
if stop && i == 0 {
break;
}
if f(d + i, q_output) {
break;
}
}
}
}
"#; }
@build Simulator::builder(code, "Top");
let stop = sim.signal("stop");
let d = sim.signal("d");
let count = sim.signal("count");
let q_output = sim.signal("q_output");
sim.modify(|io| {
io.set(stop, 1u8);
io.set(d, 10u8);
io.set(count, 2u8);
}).unwrap();
assert_eq!(sim.get(q_output), 0u32.into());
sim.modify(|io| io.set(stop, 0u8)).unwrap();
assert_eq!(sim.get(q_output), 11u32.into());
}
fn test_statement_call_inputs_follow_output_writeback_order(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Top (
d: input logic<8>,
q: output logic<8>,
tmp: output logic<8>,
) {
function inner (
x: input logic<8>,
y: output logic<8>,
) -> logic<8> {
y = x + 8'd1;
return x + 8'd2;
}
function outer (
first: input logic<8>,
second: input logic<8>,
result: output logic<8>,
) {
result = first + second;
}
always_comb {
tmp = 8'd0;
outer(inner(d, tmp), tmp, q);
}
}
"#; }
@build Simulator::builder(code, "Top");
let d = sim.signal("d");
let q = sim.signal("q");
let tmp = sim.signal("tmp");
sim.modify(|io| io.set(d, 10u8)).unwrap();
assert_eq!(sim.get(q), 23u32.into());
assert_eq!(sim.get(tmp), 11u32.into());
}
fn test_comb_effectful_case_after_dynamic_break_stays_inactive(sim) {
@ignore_on(veryl, sv);
@setup { let code = r#"
module Top (
stop: input logic,
d: input logic<8>,
count: input logic<8>,
q_output: output logic<8>,
) {
function f (
x: input logic<8>,
y: output logic<8>,
) -> logic {
y = x + 8'd1;
return 1'b1;
}
always_comb {
q_output = 8'd0;
for i in 0..count {
if stop && i == 0 {
break;
}
case f(d + i, q_output) {
default: break;
}
}
}
}
"#; }
@build Simulator::builder(code, "Top");
let stop = sim.signal("stop");
let d = sim.signal("d");
let count = sim.signal("count");
let q_output = sim.signal("q_output");
sim.modify(|io| {
io.set(stop, 1u8);
io.set(d, 10u8);
io.set(count, 2u8);
}).unwrap();
assert_eq!(sim.get(q_output), 0u32.into());
sim.modify(|io| io.set(stop, 0u8)).unwrap();
assert_eq!(sim.get(q_output), 11u32.into());
}
fn test_comb_function_condition_output_is_guarded_after_early_return(sim) {
@ignore_on(veryl, sv);
@setup { let code = r#"
module Top (
sel: input logic,
d: input logic<8>,
q: output logic<8>,
q_output: output logic<8>,
) {
function inner (
x: input logic<8>,
y: output logic<8>,
) -> logic {
y = x + 8'd1;
return 1'b1;
}
function outer (
sel: input logic,
x: input logic<8>,
y: output logic<8>,
) -> logic<8> {
if sel {
return 8'd0;
}
if inner(x, y) {
return x + 8'd2;
}
return 8'd1;
}
always_comb {
q_output = 8'd7;
q = outer(sel, d, q_output);
}
}
"#; }
@build Simulator::builder(code, "Top");
let sel = sim.signal("sel");
let d = sim.signal("d");
let q = sim.signal("q");
let q_output = sim.signal("q_output");
sim.modify(|io| {
io.set(sel, 1u8);
io.set(d, 10u8);
}).unwrap();
assert_eq!(sim.get(q), 0u32.into());
assert_eq!(sim.get(q_output), 0u32.into());
sim.modify(|io| io.set(sel, 0u8)).unwrap();
assert_eq!(sim.get(q), 12u32.into());
assert_eq!(sim.get(q_output), 11u32.into());
}
fn test_comb_function_call_statement_ignores_return_value(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Top (
d: input logic<8>,
q: output logic<8>,
) {
function f (
x: input logic<8>,
y: output logic<8>,
) -> logic<8> {
y = x + 8'd2;
return x + 8'd99;
}
always_comb {
f(d, q);
}
}
"#; }
@build Simulator::builder(code, "Top");
let d = sim.signal("d");
let q = sim.signal("q");
sim.modify(|io| io.set(d, 10u8)).unwrap();
assert_eq!(sim.get(q), 12u32.into());
}
fn test_comb_returning_output_function_reads_current_caller_store(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Top (
d: input logic<8>,
q: output logic<8>,
) {
var tmp: logic<8>;
function f (
y: output logic<8>,
) -> logic<8> {
y = tmp;
return 8'd0;
}
always_comb {
tmp = d;
f(q);
}
}
"#; }
@build Simulator::builder(code, "Top");
let d = sim.signal("d");
let q = sim.signal("q");
sim.modify(|io| io.set(d, 42u8)).unwrap();
assert_eq!(sim.get(q), 42u32.into());
}
fn test_comb_returning_output_function_allows_nested_output_call(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Top (
d: input logic<8>,
q: output logic<8>,
) {
function f (
x: input logic<8>,
y: output logic<8>,
) {
y = x + 8'd3;
}
function g (
x: input logic<8>,
y: output logic<8>,
) -> logic<8> {
f(x, y);
return 8'd0;
}
always_comb {
g(d, q);
}
}
"#; }
@build Simulator::builder(code, "Top");
let d = sim.signal("d");
let q = sim.signal("q");
sim.modify(|io| io.set(d, 10u8)).unwrap();
assert_eq!(sim.get(q), 13u32.into());
}
fn test_comb_function_call_statement_preserves_return_control_flow(sim) {
@ignore_on(veryl, sv);
@setup { let code = r#"
module Top (
d: input logic<8>,
q: output logic<8>,
) {
function f (
x: input logic<8>,
y: output logic<8>,
) -> logic<8> {
y = x + 8'd1;
return 8'd0;
y = x + 8'd2;
}
always_comb {
f(d, q);
}
}
"#; }
@build Simulator::builder(code, "Top");
let d = sim.signal("d");
let q = sim.signal("q");
sim.modify(|io| io.set(d, 10u8)).unwrap();
assert_eq!(sim.get(q), 11u32.into());
}
fn test_comb_function_call_statement_preserves_conditional_return_control_flow(sim) {
@ignore_on(veryl, sv);
@setup { let code = r#"
module Top (
sel: input logic,
d: input logic<8>,
q: output logic<8>,
) {
function f (
x: input logic<8>,
choose_early: input logic,
y: output logic<8>,
) -> logic<8> {
y = x + 8'd1;
if choose_early {
return 8'd0;
}
y = x + 8'd2;
return 8'd0;
}
always_comb {
f(d, sel, q);
}
}
"#; }
@build Simulator::builder(code, "Top");
let sel = sim.signal("sel");
let d = sim.signal("d");
let q = sim.signal("q");
sim.modify(|io| {
io.set(d, 10u8);
io.set(sel, 1u8);
}).unwrap();
assert_eq!(sim.get(q), 11u32.into());
sim.modify(|io| io.set(sel, 0u8)).unwrap();
assert_eq!(sim.get(q), 12u32.into());
}
fn test_comb_nested_function_call_statement_with_output_argument(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Top (
d: input logic<8>,
q: output logic<8>,
) {
function f (
x: input logic<8>,
y: output logic<8>,
) {
y = x + 8'd1;
}
function g (
x: input logic<8>,
y: output logic<8>,
) {
f(x, y);
}
var tmp: logic<8>;
always_comb {
g(d, tmp);
q = tmp;
}
}
"#; }
@build Simulator::builder(code, "Top");
let d = sim.signal("d");
let q = sim.signal("q");
sim.modify(|io| io.set(d, 10u8)).unwrap();
assert_eq!(sim.get(q), 11u32.into());
}
fn test_comb_function_call_output_reads_current_caller_store(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Top (
d: input logic<8>,
q: output logic<8>,
) {
var tmp: logic<8>;
function f (
y: output logic<8>,
) {
y = tmp;
}
always_comb {
tmp = d;
f(q);
}
}
"#; }
@build Simulator::builder(code, "Top");
let d = sim.signal("d");
let q = sim.signal("q");
sim.modify(|io| io.set(d, 42u8)).unwrap();
assert_eq!(sim.get(q), 42u32.into());
}
fn test_comb_function_call_statement_with_output_argument_in_loop(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Top (
d: input logic<8>,
q: output logic<8>,
) {
function f (
x: input logic<8>,
y: output logic<8>,
) {
y = x + 8'd1;
}
always_comb {
q = 8'd0;
for i in 0..4 {
f(d + i, q);
}
}
}
"#; }
@build Simulator::builder(code, "Top");
let d = sim.signal("d");
let q = sim.signal("q");
sim.modify(|io| io.set(d, 10u8)).unwrap();
assert_eq!(sim.get(q), 14u32.into());
}
fn test_comb_function_call_output_bit_select_preserves_unwritten_loop_bits(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Top (
d: input logic<4>,
q: output logic<4>,
) {
function set_bit (
x: input logic,
y: output logic,
) {
y = x;
}
always_comb {
q = d;
for i in 0..2 {
set_bit(1'b0, q[i]);
}
}
}
"#; }
@build Simulator::builder(code, "Top");
let d = sim.signal("d");
let q = sim.signal("q");
sim.modify(|io| io.set(d, 0b1011u8)).unwrap();
assert_eq!(sim.get(q), 0b1000u32.into());
}
fn test_always_comb_blocking_assignment_chain(sim) {
@ignore_on(sv);
@setup { let code = r#"
module Top (a: input logic<8>, o: output logic<8>) {
var x: logic<8>;
always_comb {
x = a;
x = x + 8'd1;
x = x << 1;
o = x;
}
}
"#; }
@build Simulator::builder(code, "Top");
let a = sim.signal("a");
let o = sim.signal("o");
sim.modify(|io| io.set(a, 10u8)).unwrap();
assert_eq!(sim.get(o), 22u8.into());
}
}
#[test]
fn test_shared_expression_hoisting() {
let code = r#"
module Top (
a: input logic<32>,
b: input logic<32>,
x: output logic<32>,
y: output logic<32>,
) {
// (a + b) is shared
assign x = (a + b) & 32'h1;
assign y = (a + b) | 32'h2;
}
"#;
let trace = setup_and_trace(code, "Top");
let output = trace.format_program().unwrap();
assert_snapshot!("shared_expression_sir", output);
}
#[test]
fn test_mux_safe_hoisting() {
let code = r#"
module Top (
a: input logic<32>,
b: input logic<32>,
c: input logic,
x: output logic<32>,
y: output logic<32>,
) {
var m: logic<32>;
always_comb {
if c {
m = a;
} else {
m = b;
}
}
// (m + 1) is shared but depends on Mux result (m)
// It should NOT be hoisted to entry block.
assign x = (m + 1) & 32'h1;
assign y = (m + 1) | 32'h2;
}
"#;
let trace = setup_and_trace(code, "Top");
let output = trace.format_program().unwrap();
assert_snapshot!("mux_safe_hoisting_sir", output);
}
#[test]
fn test_hash_consing_deduplication() {
let code = r#"
module Top (
a: input logic<32>,
b: input logic<32>,
x: output logic<32>,
) {
// Multiple identical additions
assign x = (a + b) + (a + b);
}
"#;
let trace = setup_and_trace(code, "Top");
let output = trace.format_program().unwrap();
assert_snapshot!("hash_consing_sir", output);
}
#[test]
fn test_rle_comb() {
let trace = setup_and_trace(
r#"
module ModuleA (
x: input logic<32>,
y: input logic<32>,
z: output logic<32>
) {
var temp: logic<32>;
always_comb {
temp = x + y;
z = temp;
}
}
"#,
"ModuleA",
);
let output = trace.format_program().unwrap();
assert_snapshot!("rle_comb", output);
}
#[test]
fn test_dse_preserve_top_ports() {
let mut sim = Simulator::builder(DSE_HIERARCHY_SOURCE, "Top")
.dead_store_policy(DeadStorePolicy::PreserveTopPorts)
.build()
.unwrap();
let top_in = sim.signal("top_in");
let top_out = sim.signal("top_out");
sim.modify(|io| io.set(top_in, 0xABu8)).unwrap();
assert_eq!(sim.get(top_out), 0xABu64.into());
}
#[test]
fn test_o2_dse_preserves_signals_read_by_native_testbench() {
let result = Simulator::builder(
r#"
#[test(test_o2_dse_preserves_signals_read_by_native_testbench)]
module test_o2_dse_preserves_signals_read_by_native_testbench {
var source: logic;
var observed: logic;
assign observed = ~source;
initial {
source = 1'b0;
$assert(observed == 1'b1, "DSE removed a signal read by the testbench");
$finish();
}
}
"#,
"test_o2_dse_preserves_signals_read_by_native_testbench",
)
.opt_level(OptLevel::O2)
.run_test()
.unwrap();
assert_eq!(result, TestResult::Pass);
}
#[test]
fn test_dse_preserve_all_ports() {
let mut sim = Simulator::builder(DSE_HIERARCHY_SOURCE, "Top")
.dead_store_policy(DeadStorePolicy::PreserveAllPorts)
.build()
.unwrap();
let top_in = sim.signal("top_in");
let top_out = sim.signal("top_out");
sim.modify(|io| io.set(top_in, 0x42u8)).unwrap();
assert_eq!(sim.get(top_out), 0x42u64.into());
let sub_signals = sim.instance_signals(&[("u_sub", 0)]);
let sub_o_data = sub_signals.iter().find(|s| s.name == "o_data").unwrap();
assert_eq!(sim.get(sub_o_data.signal), 0x42u64.into());
}