use celox::{BigUint, Simulator};
struct Rng(u64);
impl Rng {
fn new(seed: u64) -> Self {
Self(seed)
}
fn next(&mut self) -> u64 {
self.0 ^= self.0 << 13;
self.0 ^= self.0 >> 7;
self.0 ^= self.0 << 17;
self.0
}
fn range(&mut self, lo: u64, hi: u64) -> u64 {
lo + self.next() % (hi - lo)
}
}
fn gen_random_comb_module(rng: &mut Rng, width: usize) -> (String, Vec<u64>) {
let n_inputs = rng.range(2, 5) as usize;
let n_temps = rng.range(1, 4) as usize;
let mut code = "module Top(\n".to_string();
for i in 0..n_inputs {
code += &format!(" i{i}: input logic<{width}>,\n");
}
code += &format!(" o: output logic<{width}>\n) {{\n");
let ops = ["+", "-", "&", "|", "^"];
let mut last_var = "i0".to_string();
for t in 0..n_temps {
let op = ops[rng.range(0, ops.len() as u64) as usize];
let rhs_idx = rng.range(0, n_inputs as u64) as usize;
let rhs = format!("i{rhs_idx}");
code +=
&format!(" var t{t}: logic<{width}>;\n assign t{t} = {last_var} {op} {rhs};\n");
last_var = format!("t{t}");
}
code += &format!(" assign o = {last_var};\n}}\n");
let mut inputs = Vec::new();
for _ in 0..n_inputs {
let mask = if width >= 64 {
u64::MAX
} else {
(1u64 << width) - 1
};
inputs.push(rng.next() & mask);
}
(code, inputs)
}
fn run_fuzz_case(seed: u64, width: usize) {
let mut rng = Rng::new(seed);
let (code, inputs) = gen_random_comb_module(&mut rng, width);
let sim_n_result = std::panic::catch_unwind(|| {
let mut sim = Simulator::builder(&code, "Top").build().unwrap();
for (i, &input) in inputs.iter().enumerate() {
let sig = sim.signal(&format!("i{i}"));
match width {
w if w <= 32 => sim.set(sig, input as u32),
w if w <= 64 => sim.set(sig, input),
_ => sim.set_wide(sig, BigUint::from(input)),
}
}
let o = sim.signal("o");
sim.get(o)
});
let sim_c_result = std::panic::catch_unwind(|| {
let mut sim = Simulator::builder(&code, "Top").build_cranelift().unwrap();
for (i, &input) in inputs.iter().enumerate() {
let sig = sim.signal(&format!("i{i}"));
match width {
w if w <= 32 => sim.set(sig, input as u32),
w if w <= 64 => sim.set(sig, input),
_ => sim.set_wide(sig, BigUint::from(input)),
}
}
let o = sim.signal("o");
sim.get(o)
});
match (sim_n_result, sim_c_result) {
(Ok(vn), Ok(vc)) => {
assert_eq!(
vn, vc,
"Fuzz mismatch (seed={seed}, width={width}):\n code: {code}\n native={vn:#x}\n cranelift={vc:#x}"
);
}
(Err(_), Err(_)) => {} (Ok(vn), Err(e)) => {
panic!(
"Cranelift panicked but native succeeded (seed={seed}): native={vn:#x}, error={e:?}"
);
}
(Err(e), Ok(vc)) => {
panic!(
"Native panicked but Cranelift succeeded (seed={seed}): cranelift={vc:#x}, error={e:?}"
);
}
}
}
#[test]
fn fuzz_narrow_100_seeds() {
for seed in 0..100 {
run_fuzz_case(seed * 31337 + 1, 32);
}
}
#[test]
fn fuzz_64bit_100_seeds() {
for seed in 0..100 {
run_fuzz_case(seed * 7919 + 42, 64);
}
}
#[test]
fn fuzz_wide_128bit_50_seeds() {
for seed in 0..50 {
run_fuzz_case(seed * 104729 + 7, 128);
}
}