use super::Provenance;
use super::wgsl::{Detector, Mutant};
pub const RTL: &str = include_str!("lanes64.v");
pub const HARNESS: &str = include_str!("lanes64_harness.cpp");
pub const IMPLEMENTATION: &str = "fpga-lanes64";
pub const HOST_LAYOUT: u32 = 1;
pub const ADDRESS_BITS: u8 = 20;
pub const VERILATOR_OPTIONS: [&str; 10] = ["--cc", "--exe", "--build", "-O3", "-Wno-fatal", "-Wno-lint", "-Wno-style", "-GAW=20", "-CFLAGS", "-O2 -DWQ_AW=20"];
const DOMAIN_IMPLEMENTATION: &[u8] = b"wai:quantum-fabric-fpga\x01";
const DOMAIN_MEMBER: &[u8] = b"wai:quantum-fabric-fpga-member\x01";
fn hex(bytes: &[u8]) -> String {
bytes.iter().map(|b| format!("{b:02x}")).collect()
}
fn put(o: &mut Vec<u8>, bytes: &[u8]) {
o.extend_from_slice(&(bytes.len() as u64).to_be_bytes());
o.extend_from_slice(bytes);
}
pub fn implementation_id() -> String {
implementation_id_of(RTL)
}
pub fn implementation_id_of(rtl: &str) -> String {
let mut o = DOMAIN_IMPLEMENTATION.to_vec();
put(&mut o, rtl.as_bytes());
put(&mut o, HARNESS.as_bytes());
for option in VERILATOR_OPTIONS {
put(&mut o, option.as_bytes());
}
o.extend_from_slice(&HOST_LAYOUT.to_be_bytes());
format!("{IMPLEMENTATION}/{}", hex(&blake3::hash(&o).as_bytes()[..8]))
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SimIdentity {
pub simulator: String,
pub compiler: String,
pub host: String,
}
impl SimIdentity {
pub fn member_id(&self) -> String {
let mut o = DOMAIN_MEMBER.to_vec();
put(&mut o, self.simulator.as_bytes());
put(&mut o, self.compiler.as_bytes());
put(&mut o, self.host.as_bytes());
o.push(ADDRESS_BITS);
format!("local/fpga-sim-{}", hex(&blake3::hash(&o).as_bytes()[..6]))
}
pub fn key(&self) -> String {
"fpga-sim-verilator".into()
}
pub fn version(&self) -> &str {
self.simulator.split_whitespace().nth(1).unwrap_or("")
}
pub fn provenance(&self) -> Provenance {
Provenance::Simulator(format!("verilator {}", self.version()))
}
}
pub const MUTANTS: [Mutant; 6] = [
Mutant { name: "F1", find: "assign fx = (prod + 64'sd536870912) >>> 30;", replace: "assign fx = (prod + 64'sd268435456) >>> 30;", detector: Detector::Probe },
Mutant { name: "F2", find: "assign fx = (prod + 64'sd536870912) >>> 30;", replace: "assign fx = (prod + 64'sd536870912) >>> 29;", detector: Detector::Probe },
Mutant { name: "F3", find: "wire signed [32:0] hl = ah * $signed({1'b0, bl});", replace: "wire signed [32:0] hl = ah * $signed(bl);", detector: Detector::Probe },
Mutant { name: "F4", find: "wire selected = ((i_ix & ctrl[AW:0]) == ctrl[AW:0]);", replace: "wire selected = ((i_ix & ctrl[AW:0]) != ctrl[AW:0]);", detector: Detector::Certificate },
Mutant { name: "F5", find: "wire signed [63:0] s0r = p0r0 - p1i0 + p2r1 - p3i1;", replace: "wire signed [63:0] s0r = p0r0 - p1i0 + p2r1 + p3i1;", detector: Detector::Certificate },
Mutant { name: "F6", find: "if (oor && index < report) begin", replace: "if (1'b0) begin", detector: Detector::Range },
];
pub fn mutate(m: &Mutant) -> String {
let n = RTL.matches(m.find).count();
assert_eq!(n, 1, "mutant {}: `{}` occurs {n} times in the datapath", m.name, m.find);
RTL.replacen(m.find, m.replace, 1)
}
#[cfg(test)]
mod tests {
use super::*;
fn identity() -> SimIdentity {
SimIdentity { simulator: "Verilator 5.052 2026-09-05 rev v5.052".into(), compiler: "a compiler 21.0".into(), host: "aarch64-macos".into() }
}
#[test]
fn every_field_of_the_identity_is_in_the_member_id() {
let base = identity();
let id = base.member_id();
assert!(id.starts_with("local/fpga-sim-") && id.len() == "local/fpga-sim-".len() + 12, "{id}");
assert!(id.as_str() > "local/cpu-x86_64" && id.as_str() > "local/cpu-aarch64");
for v in [
SimIdentity { simulator: "Verilator 5.054".into(), ..identity() },
SimIdentity { compiler: "another".into(), ..identity() },
SimIdentity { host: "x86_64-linux".into(), ..identity() },
] {
assert_ne!(v.member_id(), id, "{v:?}");
}
assert_eq!(base.key(), "fpga-sim-verilator");
assert_eq!(base.version(), "5.052");
assert_eq!(base.provenance(), Provenance::Simulator("verilator 5.052".into()));
}
#[test]
fn the_implementation_id_follows_the_datapath_and_the_harness() {
let id = implementation_id();
assert!(id.starts_with("fpga-lanes64/") && id.len() == "fpga-lanes64/".len() + 16, "{id}");
for m in &MUTANTS {
assert_ne!(implementation_id_of(&mutate(m)), id, "mutant {}", m.name);
}
assert!(VERILATOR_OPTIONS.contains(&"-GAW=20") && VERILATOR_OPTIONS.contains(&"-O2 -DWQ_AW=20"));
assert_eq!(ADDRESS_BITS, 20);
}
#[test]
fn every_mutant_edits_the_datapath_once_and_names_distinct_edits() {
let mut seen = std::collections::BTreeSet::new();
for m in &MUTANTS {
let edited = mutate(m);
assert_ne!(edited, RTL, "mutant {} changes nothing", m.name);
assert!(seen.insert(edited), "mutant {} duplicates another", m.name);
}
for d in [Detector::Probe, Detector::Certificate, Detector::Range] {
assert!(MUTANTS.iter().any(|m| m.detector == d), "no mutant for {d:?}");
}
}
}