use celox::SimulatorBuilder;
use insta::assert_snapshot;
fn mir_trace(code: &str, top: &str) -> String {
let result = SimulatorBuilder::new(code, top)
.optimize(true)
.trace_mir()
.build_with_trace();
result.trace.mir.unwrap_or_default()
}
#[test]
fn high_pressure_comb_mir() {
let code = r#"
module Top (
a0: input logic<32>, a1: input logic<32>,
a2: input logic<32>, a3: input logic<32>,
a4: input logic<32>, a5: input logic<32>,
a6: input logic<32>, a7: input logic<32>,
a8: input logic<32>, a9: input logic<32>,
a10: input logic<32>, a11: input logic<32>,
a12: input logic<32>, a13: input logic<32>,
a14: input logic<32>, a15: input logic<32>,
o: output logic<32>,
) {
assign o = a0 + a1 + a2 + a3 + a4 + a5 + a6 + a7
+ a8 + a9 + a10 + a11 + a12 + a13 + a14 + a15;
}
"#;
let output = mir_trace(code, "Top");
assert_snapshot!(output);
}
#[test]
fn large_comb_pressure_mir() {
let n = 20;
let mut ports = String::new();
for i in 0..n {
ports.push_str(&format!(" a{i}: input logic<32>,\n"));
}
for i in 0..n {
ports.push_str(&format!(" o{i}: output logic<32>,\n"));
}
ports = ports.trim_end_matches(",\n").to_string() + "\n";
let mut body = String::new();
body.push_str(" var xor_all: logic<32>;\n");
body.push_str(" assign xor_all = a0");
for i in 1..n {
body.push_str(&format!(" ^ a{i}"));
}
body.push_str(";\n");
for i in 0..n {
body.push_str(&format!(" assign o{i} = xor_all + a{i};\n"));
}
let code = format!("module Top (\n{ports}) {{\n{body}}}");
let output = mir_trace(&code, "Top");
assert_snapshot!(output);
}
#[test]
fn rle_comb_mir() {
let code = r#"
module Top (
x: input logic<32>,
y: input logic<32>,
temp: output logic<32>,
z: output logic<32>,
) {
assign temp = x + y;
assign z = x + y;
}
"#;
let output = mir_trace(code, "Top");
assert_snapshot!(output);
}
#[test]
fn shared_expression_mir() {
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 output = mir_trace(code, "Top");
assert_snapshot!(output);
}
#[test]
fn ff_branch_mir() {
let code = r#"
module Top (
clk: input '_ clock,
rst: input '_ reset,
d: input logic<8>,
q: output logic<8>,
) {
always_ff(clk) {
if_reset {
q = 0;
} else {
q = d;
}
}
}
"#;
let output = mir_trace(code, "Top");
assert_snapshot!(output);
}
#[test]
fn trace_contains_the_exact_fused_native_pipeline() {
let code = r#"
module Top (
clk: input '_ clock,
d: input logic<8>,
q: output logic<8>,
y: output logic<8>,
) {
always_ff(clk) {
q = d;
}
assign y = q + 1;
}
"#;
let result = SimulatorBuilder::new(code, "Top")
.optimize(true)
.trace_mir()
.build_with_trace();
result.res.expect("traced native build should succeed");
let native_sir = result
.trace
.native_optimized_sir
.expect("native optimized SIR should be captured");
let mir = result.trace.mir.expect("native MIR should be captured");
assert!(native_sir.contains("=== Native function eval_comb ==="));
assert!(native_sir.contains("=== Native function eval_comb_apply_ff[0] ==="));
assert!(mir.contains("=== Native function eval_comb_apply_ff[0] ==="));
assert!(
!mir.contains("Execution Unit eval_apply_ffs"),
"trace must not independently re-lower source execution units"
);
}