use std::collections::{BTreeMap, HashMap};
use std::io::Read;
use std::io::Write;
use std::process::Command;
use std::process::Stdio;
use xlsynth::{IrBits, IrValue};
use xlsynth_g8r::aig::{AigBitVector, AigOperand, GateFn};
use xlsynth_g8r::aig_serdes::emit_aiger::emit_aiger;
use xlsynth_g8r::aig_serdes::emit_aiger_binary::emit_aiger_binary;
use xlsynth_g8r::gate_builder::{GateBuilder, GateBuilderOptions};
use xlsynth_g8r::test_utils::interesting_ir_roundtrip_cases;
use xlsynth_pir::ir_parser;
use xlsynth_vastly::compile_pipeline_module;
use xlsynth_vastly::pipeline_cycle_from_irvalue;
use xlsynth_vastly::run_pipeline_and_collect_outputs;
use xlsynth_vastly::PipelineCycle;
use xlsynth_vastly::PipelineStimulus;
use xlsynth_vastly::Signedness;
use xlsynth_vastly::Value4;
use test_case::test_case;
use pretty_assertions::assert_eq;
use xlsynth_test_helpers::{compare_golden_sv, compare_golden_text};
#[cfg(unix)]
use std::os::unix::fs::symlink;
fn add_tool_path_value(toolchain_toml_contents: &str) -> String {
let tool_path =
std::env::var("XLSYNTH_TOOLS").expect("XLSYNTH_TOOLS environment variable must be set");
format!(
"{}
tool_path = \"{}\"",
toolchain_toml_contents, tool_path
)
}
fn write_aiger_file(
temp_dir: &tempfile::TempDir,
file_name: &str,
gate_fn: &GateFn,
) -> std::path::PathBuf {
let aiger = emit_aiger(gate_fn, true).expect("emit_aiger should succeed");
let path = temp_dir.path().join(file_name);
std::fs::write(&path, aiger).expect("failed to write aiger file");
path
}
fn write_aiger_binary_file(
temp_dir: &tempfile::TempDir,
file_name: &str,
gate_fn: &GateFn,
) -> std::path::PathBuf {
let aiger = emit_aiger_binary(gate_fn, true).expect("emit_aiger_binary should succeed");
let path = temp_dir.path().join(file_name);
std::fs::write(&path, aiger).expect("failed to write binary aiger file");
path
}
fn ir_bits_to_msb_string(bits: &IrBits) -> String {
(0..bits.get_bit_count())
.rev()
.map(|bit_index| {
if bits.get_bit(bit_index).unwrap() {
'1'
} else {
'0'
}
})
.collect::<String>()
}
fn assert_value4_matches_ir_bits(actual: &Value4, expected_bits: &IrBits) {
assert!(
actual.is_all_known_01(),
"expected known output bits, got {}",
actual.to_bit_string_msb_first()
);
assert_eq!(
actual.to_bit_string_msb_first(),
ir_bits_to_msb_string(expected_bits)
);
}
fn assert_aig_ir_equiv_roundtrip_for_ir_case(
temp_dir: &tempfile::TempDir,
toolchain_toml_path: &std::path::Path,
case_name: &str,
ir_text: &str,
) {
let ir_path = temp_dir.path().join(format!("{case_name}.ir"));
std::fs::write(&ir_path, ir_text).unwrap();
let aiger_path = temp_dir.path().join(format!("{case_name}.aig"));
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let ir2g8r = Command::new(driver)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("ir2g8r")
.arg(ir_path.to_str().unwrap())
.arg("--top")
.arg("main")
.arg("--aiger-out")
.arg(aiger_path.to_str().unwrap())
.output()
.unwrap();
assert!(
ir2g8r.status.success(),
"ir2g8r failed for case {case_name}; stdout: {} stderr: {}",
String::from_utf8_lossy(&ir2g8r.stdout),
String::from_utf8_lossy(&ir2g8r.stderr)
);
let equiv = Command::new(driver)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("aig-ir-equiv")
.arg(aiger_path.to_str().unwrap())
.arg(ir_path.to_str().unwrap())
.arg("--top")
.arg("main")
.arg("--solver")
.arg("toolchain")
.output()
.unwrap();
assert!(
equiv.status.success(),
"aig-ir-equiv should succeed for case {case_name}; stdout: {} stderr: {}",
String::from_utf8_lossy(&equiv.stdout),
String::from_utf8_lossy(&equiv.stderr)
);
}
fn function_type_text_for_ir_text(ir_text: &str) -> String {
let mut parser = ir_parser::Parser::new(ir_text);
let pkg = parser
.parse_and_validate_package()
.expect("roundtrip test IR should parse");
let top = pkg
.get_top_fn()
.expect("roundtrip test IR should have top fn");
let param_types = top
.params
.iter()
.map(|param| param.ty.to_string())
.collect::<Vec<String>>()
.join(", ");
format!("({param_types}) -> {}", top.ret_ty)
}
fn assert_aig2ir_ir_equiv_roundtrip_for_ir_case(
temp_dir: &tempfile::TempDir,
toolchain_toml_path: &std::path::Path,
case_name: &str,
ir_text: &str,
) {
let ir_path = temp_dir.path().join(format!("{case_name}_lift_src.ir"));
std::fs::write(&ir_path, ir_text).unwrap();
let aiger_path = temp_dir.path().join(format!("{case_name}_lift.aig"));
let lifted_ir_path = temp_dir.path().join(format!("{case_name}_lifted.ir"));
let fn_type_text = function_type_text_for_ir_text(ir_text);
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let ir2g8r = Command::new(driver)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("ir2g8r")
.arg(ir_path.to_str().unwrap())
.arg("--top")
.arg("main")
.arg("--aiger-out")
.arg(aiger_path.to_str().unwrap())
.output()
.unwrap();
assert!(
ir2g8r.status.success(),
"ir2g8r failed for case {case_name}; stdout: {} stderr: {}",
String::from_utf8_lossy(&ir2g8r.stdout),
String::from_utf8_lossy(&ir2g8r.stderr)
);
let aig2ir = Command::new(driver)
.arg("aig2ir")
.arg(aiger_path.to_str().unwrap())
.arg("--fn-type")
.arg(&fn_type_text)
.output()
.unwrap();
assert!(
aig2ir.status.success(),
"aig2ir failed for case {case_name}; stdout: {} stderr: {}",
String::from_utf8_lossy(&aig2ir.stdout),
String::from_utf8_lossy(&aig2ir.stderr)
);
std::fs::write(&lifted_ir_path, &aig2ir.stdout).unwrap();
let ir_equiv = Command::new(driver)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("ir-equiv")
.arg(lifted_ir_path.to_str().unwrap())
.arg(ir_path.to_str().unwrap())
.arg("--lhs_ir_top")
.arg("loaded_aiger")
.arg("--rhs_ir_top")
.arg("main")
.arg("--solver")
.arg("toolchain")
.output()
.unwrap();
assert!(
ir_equiv.status.success(),
"lifted aig2ir IR should be equivalent for case {case_name}; stdout: {} stderr: {}",
String::from_utf8_lossy(&ir_equiv.stdout),
String::from_utf8_lossy(&ir_equiv.stderr)
);
}
fn run_ir2pipeline_and_simulate_output(ir_text: &str, input_value: &str) -> String {
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("sample.ir");
std::fs::write(&ir_path, ir_text).unwrap();
let toolchain_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_path, toolchain_toml_contents).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let pipeline_output = Command::new(driver)
.arg("--toolchain")
.arg(toolchain_path.to_str().unwrap())
.arg("ir2pipeline")
.arg(ir_path.to_str().unwrap())
.arg("--pipeline_stages")
.arg("1")
.arg("--top")
.arg("main")
.arg("--delay_model")
.arg("unit")
.arg("--flop_inputs=false")
.arg("--flop_outputs=false")
.arg("--use_system_verilog=true")
.output()
.expect("ir2pipeline invocation should run");
assert!(
pipeline_output.status.success(),
"ir2pipeline failed:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&pipeline_output.stdout),
String::from_utf8_lossy(&pipeline_output.stderr)
);
let pipeline_sv = String::from_utf8(pipeline_output.stdout).unwrap();
let pipeline =
compile_pipeline_module(&pipeline_sv).expect("vastly should compile pipeline SV");
let input_value = IrValue::parse_typed(input_value).expect("input value should parse");
let stimulus = PipelineStimulus {
half_period: 5,
cycles: vec![pipeline_cycle_from_irvalue(&pipeline, &input_value)
.expect("typed XLS input should map to one pipeline cycle")],
};
let outputs =
run_pipeline_and_collect_outputs(&pipeline, &stimulus, &pipeline.initial_state_x())
.expect("vastly should simulate pipeline SV");
let out_value = outputs
.last()
.expect("one simulated cycle expected")
.get("out")
.expect("generated pipeline should have an `out` output");
let decimal = out_value.to_decimal_string_if_known().unwrap_or_else(|| {
panic!(
"expected known output bits, got {}",
out_value.to_bit_string_msb_first()
)
});
format!("out: bits[{}]:{}", out_value.width, decimal)
}
fn two_input_gate_fn<F>(name: &str, make_output: F) -> GateFn
where
F: Fn(AigOperand, AigOperand, &mut GateBuilder) -> AigOperand,
{
let mut gb = GateBuilder::new(name.to_string(), GateBuilderOptions::no_opt());
let a = gb.add_input("a".to_string(), 1);
let b = gb.add_input("b".to_string(), 1);
let out_bit = make_output(*a.get_lsb(0), *b.get_lsb(0), &mut gb);
gb.add_output("out".to_string(), AigBitVector::from_bit(out_bit));
gb.build()
}
fn run_with_broken_stdout(mut command: Command) -> (std::process::ExitStatus, String) {
let mut child = command
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
drop(child.stdout.take());
let mut stderr = String::new();
if let Some(mut err) = child.stderr.take() {
err.read_to_string(&mut stderr)
.expect("failed to read stderr");
}
let status = child.wait().expect("failed to wait on child");
(status, stderr)
}
#[test]
fn test_dslx_stitch_pipeline_cycle_numbering_must_start_at_zero() {
let _ = env_logger::builder().is_test(true).try_init();
let dslx = "fn foo_cycle1(x: u32) -> u32 { x }";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("foo.x");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(command_path)
.arg("dslx-stitch-pipeline")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("foo")
.output()
.unwrap();
assert!(!output.status.success(), "command should fail");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("numbering must start at 0"),
"unexpected stderr: {}",
stderr
);
}
#[cfg(feature = "with-z3-binary-test")]
#[test]
fn test_irequiv_subcommand_assert_label_filter() {
let _ = env_logger::try_init();
let temp_dir = tempfile::tempdir().unwrap();
let lhs_ir = "package p\nfn my_main(__token: token, a: bits[1]) -> bits[1] {\n assert.10: token = assert(__token, a, message=\"rf\", label=\"red\", id=10)\n not.11: bits[1] = not(a, id=11)\n assert.12: token = assert(assert.10, not.11, message=\"gf\", label=\"green\", id=12)\n ret literal.20: bits[1] = literal(value=1, id=20)\n}";
let rhs_ir = "package p\nfn my_main(__token: token, a: bits[1]) -> bits[1] {\n literal.101: bits[1] = literal(value=1, id=101)\n assert.102: token = assert(__token, literal.101, message=\"bf\", label=\"blue\", id=102)\n assert.103: token = assert(assert.102, literal.101, message=\"yf\", label=\"yellow\", id=103)\n ret literal.104: bits[1] = literal(value=1, id=104)\n}";
let lhs_path = temp_dir.path().join("lhs.ir");
std::fs::write(&lhs_path, lhs_ir).unwrap();
let rhs_path = temp_dir.path().join("rhs.ir");
std::fs::write(&rhs_path, rhs_ir).unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml_path, toolchain_toml_contents).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let out1 = std::process::Command::new(driver)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("ir-equiv")
.arg("--solver=z3-binary")
.arg(lhs_path.to_str().unwrap())
.arg(rhs_path.to_str().unwrap())
.arg("--top")
.arg("my_main")
.arg("--assertion-semantics")
.arg("same")
.output()
.unwrap();
assert!(
!out1.status.success(),
"Expected ir-equiv to fail without assertion filter; stderr: {}",
String::from_utf8_lossy(&out1.stderr)
);
let out2 = std::process::Command::new(driver)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("ir-equiv")
.arg("--solver=z3-binary")
.arg(lhs_path.to_str().unwrap())
.arg(rhs_path.to_str().unwrap())
.arg("--top")
.arg("my_main")
.arg("--assert-label-filter")
.arg("blue")
.arg("--assertion-semantics")
.arg("same")
.output()
.unwrap();
let stdout2 = String::from_utf8_lossy(&out2.stdout);
assert!(
out2.status.success(),
"Expected ir-equiv to succeed with assertion filter; stderr: {}",
String::from_utf8_lossy(&out2.stderr)
);
assert!(stdout2.contains("[ir-equiv] success: Solver proved equivalence"));
let out3 = std::process::Command::new(driver)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("ir-equiv")
.arg("--solver=z3-binary")
.arg(lhs_path.to_str().unwrap())
.arg(rhs_path.to_str().unwrap())
.arg("--top")
.arg("my_main")
.arg("--assert-label-filter")
.arg("blue|yellow")
.output()
.unwrap();
let stdout3 = String::from_utf8_lossy(&out3.stdout);
assert!(
out3.status.success(),
"Expected ir-equiv to succeed with multi-label assertion filter; stderr: {}",
String::from_utf8_lossy(&out3.stderr)
);
assert!(stdout3.contains("[ir-equiv] success: Solver proved equivalence"));
}
#[test]
fn test_dslx_show_function() {
let _ = env_logger::builder().is_test(true).try_init();
let dslx = r#"fn f(a: u32, b: u8) -> u32 { a + (b as u32) }"#;
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("f.x");
std::fs::write(&dslx_path, dslx).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(driver)
.arg("dslx-show")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("f")
.output()
.unwrap();
assert!(
output.status.success(),
"stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let got = stdout.trim();
let expected = "fn f(a: u32, b: u8) -> u32 {\n a + (b as u32)\n}\n".trim();
assert_eq!(got, expected, "stdout: {}", stdout);
}
#[test]
fn test_dslx_show_enum() {
let _ = env_logger::builder().is_test(true).try_init();
let dslx = r#"enum E : u8 { A = 0, B = 10 }"#;
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("e.x");
std::fs::write(&dslx_path, dslx).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(driver)
.arg("dslx-show")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("E")
.output()
.unwrap();
assert!(
output.status.success(),
"stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let got = stdout.trim();
let expected = "enum E : u8 {\n A = 0,\n B = 10,\n}\n".trim();
assert_eq!(got, expected, "stdout: {}", stdout);
}
#[test]
fn test_dslx_show_struct() {
let _ = env_logger::builder().is_test(true).try_init();
let dslx = r#"struct S {
a: u32,
b: u8,
}"#;
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("s.x");
std::fs::write(&dslx_path, dslx).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(driver)
.arg("dslx-show")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("S")
.output()
.unwrap();
assert!(
output.status.success(),
"stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let got = stdout.trim();
let expected = "struct S {\n a: u32,\n b: u8,\n}\n".trim();
assert_eq!(got, expected, "stdout: {}", stdout);
}
#[test]
fn test_dslx_show_std_max() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let std_path = temp_dir.path().join("std.x");
let std_text = r#"pub fn max<S: bool, N: u32>(x: xN[S][N], y: xN[S][N]) -> xN[S][N] { if x > y { x } else { y } }"#;
std::fs::write(&std_path, std_text).unwrap();
let main_path = temp_dir.path().join("main.x");
std::fs::write(&main_path, "").unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(driver)
.arg("dslx-show")
.arg("--dslx_input_file")
.arg(main_path.to_str().unwrap())
.arg("std::max")
.arg("--dslx_stdlib_path")
.arg(temp_dir.path().to_str().unwrap())
.output()
.unwrap();
assert!(
output.status.success(),
"stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let got = stdout.trim();
let expected = "pub fn max<S: bool, N: u32>(x: xN[S][N], y: xN[S][N]) -> xN[S][N] {\n if x > y { x } else { y }\n}\n".trim();
assert_eq!(got, expected, "stdout: {}", stdout);
}
#[test]
fn test_dslx_show_imported_function_from_dslx_path() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let lib_dir = temp_dir.path().join("lib");
let client_dir = temp_dir.path().join("client");
std::fs::create_dir(&lib_dir).unwrap();
std::fs::create_dir(&client_dir).unwrap();
let lib_path = lib_dir.join("mylib.x");
let lib_text = r#"pub fn inc(x: u8) -> u8 {
x + u8:1
}
"#;
std::fs::write(&lib_path, lib_text).unwrap();
let main_path = client_dir.join("main.x");
std::fs::write(&main_path, "import mylib;").unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(driver)
.arg("dslx-show")
.arg("--dslx_input_file")
.arg(main_path.to_str().unwrap())
.arg("mylib::inc")
.arg("--dslx_path")
.arg(lib_dir.to_str().unwrap())
.output()
.unwrap();
assert!(
output.status.success(),
"stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let got = stdout.trim();
let expected = "pub fn inc(x: u8) -> u8 {\n x + u8:1\n}\n".trim();
assert_eq!(got, expected, "stdout: {}", stdout);
}
#[test]
fn test_dslx_show_imported_function_without_input_file() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let lib_dir = temp_dir.path().join("lib");
std::fs::create_dir(&lib_dir).unwrap();
let lib_path = lib_dir.join("mylib.x");
let lib_text = r#"pub fn inc(x: u8) -> u8 {
x + u8:1
}
"#;
std::fs::write(&lib_path, lib_text).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(driver)
.arg("dslx-show")
.arg("mylib::inc")
.arg("--dslx_path")
.arg(lib_dir.to_str().unwrap())
.output()
.unwrap();
assert!(
output.status.success(),
"stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let got = stdout.trim();
let expected = "pub fn inc(x: u8) -> u8 {\n x + u8:1\n}\n".trim();
assert_eq!(got, expected, "stdout: {}", stdout);
}
#[test]
fn test_dslx_show_dotted_module_path_nested_type() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let lib_root = temp_dir.path().join("lib");
std::fs::create_dir_all(lib_root.join("foo/bar")).unwrap();
let baz_path = lib_root.join("foo/bar/baz.x");
let baz_text = r#"pub type T = u8;
pub fn inc(x: u8) -> u8 { x + u8:1 }
"#;
std::fs::write(&baz_path, baz_text).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(driver)
.arg("dslx-show")
.arg("foo.bar.baz::T")
.arg("--dslx_path")
.arg(lib_root.to_str().unwrap())
.output()
.unwrap();
assert!(
output.status.success(),
"stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
assert_eq!(stdout.trim(), "pub type T = u8;".trim());
}
#[test]
fn test_dslx_show_type_alias() {
let _ = env_logger::builder().is_test(true).try_init();
let dslx = r#"type Alias = u8;"#;
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("a.x");
std::fs::write(&dslx_path, dslx).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let out = std::process::Command::new(driver)
.arg("dslx-show")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("Alias")
.output()
.unwrap();
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8_lossy(&out.stdout).to_string();
assert_eq!(
stdout.trim(),
"type Alias = u8;".trim(),
"stdout: {}",
stdout
);
}
#[test]
fn test_dslx_show_constant() {
let _ = env_logger::builder().is_test(true).try_init();
let dslx = r#"const TEN = u8:10;"#;
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("c.x");
std::fs::write(&dslx_path, dslx).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let out = std::process::Command::new(driver)
.arg("dslx-show")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("TEN")
.output()
.unwrap();
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8_lossy(&out.stdout).to_string();
assert_eq!(
stdout.trim(),
"const TEN = u8:10;".trim(),
"stdout: {}",
stdout
);
}
#[test]
fn test_dslx_show_quickcheck() {
let _ = env_logger::builder().is_test(true).try_init();
let dslx = r#"#[quickcheck] fn qc(x: u8) -> bool { x == x }"#;
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("q.x");
std::fs::write(&dslx_path, dslx).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(driver)
.arg("dslx-show")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("qc")
.output()
.unwrap();
assert!(
output.status.success(),
"stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let got = stdout.trim();
let expected = "#[quickcheck]\nfn qc(x: u8) -> bool {\n x == x\n}\n".trim();
assert_eq!(got, expected, "stdout: {}", stdout);
}
#[test]
fn test_dslx_show_quickcheck_exhaustive() {
let _ = env_logger::builder().is_test(true).try_init();
let dslx = r#"#[quickcheck(exhaustive)] fn qc_exh(x: u1) -> bool { x == x }"#;
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("q_exh.x");
std::fs::write(&dslx_path, dslx).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(driver)
.arg("dslx-show")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("qc_exh")
.output()
.unwrap();
assert!(
output.status.success(),
"stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let got = stdout.trim();
let expected = "#[quickcheck(exhaustive)]\nfn qc_exh(x: u1) -> bool {\n x == x\n}\n".trim();
assert_eq!(got, expected, "stdout: {}", stdout);
}
#[test]
fn test_dslx_show_quickcheck_count() {
let _ = env_logger::builder().is_test(true).try_init();
let dslx = r#"#[quickcheck(test_count=7)] fn qc_cnt(x: u8) -> bool { x == x }"#;
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("q_cnt.x");
std::fs::write(&dslx_path, dslx).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(driver)
.arg("dslx-show")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("qc_cnt")
.output()
.unwrap();
assert!(
output.status.success(),
"stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let got = stdout.trim();
let expected =
"#[quickcheck(test_count=7)]\nfn qc_cnt(x: u8) -> bool {\n x == x\n}\n".trim();
assert_eq!(got, expected, "stdout: {}", stdout);
}
#[test]
fn test_ir2gates_adder_mapping_flag() {
let _ = env_logger::builder().is_test(true).try_init();
let dslx = "fn main(a: u8, b: u8) -> u8 { a + b }";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("main.x");
let ir_path = temp_dir.path().join("main.ir");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let dslx2ir_output = std::process::Command::new(command_path)
.arg("dslx2ir")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("main")
.output()
.unwrap();
assert!(dslx2ir_output.status.success());
std::fs::write(&ir_path, &dslx2ir_output.stdout).unwrap();
fn run(driver: &str, ir: &std::path::Path, mapping: &str) -> serde_json::Value {
let out = std::process::Command::new(driver)
.arg("ir2gates")
.arg(ir.to_str().unwrap())
.arg("--quiet=true")
.arg(format!("--adder-mapping={}", mapping))
.output()
.unwrap();
assert!(out.status.success());
serde_json::from_slice(&out.stdout).unwrap()
}
let rc = run(command_path, &ir_path, "ripple-carry");
let bk = run(command_path, &ir_path, "brent-kung");
let ks = run(command_path, &ir_path, "kogge-stone");
let rc_depth = rc["deepest_path"]
.as_u64()
.expect("expected deepest_path to be a u64");
let bk_depth = bk["deepest_path"]
.as_u64()
.expect("expected deepest_path to be a u64");
let ks_depth = ks["deepest_path"]
.as_u64()
.expect("expected deepest_path to be a u64");
assert!(rc_depth >= bk_depth);
assert!(rc_depth >= ks_depth);
assert!(ks_depth <= bk_depth);
}
#[test]
fn test_dslx2ir_no_top_required_when_no_opt() {
let _ = env_logger::builder().is_test(true).try_init();
let dslx = "\nfn foo(x: u32) -> u32 { x + u32:1 }\nfn bar(y: u32) -> u32 { y + u32:2 }\n";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("foo.x");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("dslx2ir")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.output()
.unwrap();
assert!(
output.status.success(),
"dslx2ir should not require --dslx_top when --opt is not set;\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("package foo"),
"Expected package name 'foo' in IR, got: {}",
stdout
);
assert!(
stdout.contains("fn __foo__foo("),
"Expected function __foo__foo in IR, got: {}",
stdout
);
assert!(
stdout.contains("fn __foo__bar("),
"Expected function __foo__bar in IR, got: {}",
stdout
);
}
#[test]
fn test_ir2gates_prints_source_positions() {
let _ = env_logger::builder().is_test(true).try_init();
let ir = r#"package prio_pkg
file_number 0 "foo.x"
file_number 1 "bar.x"
top fn main(sel: bits[1] id=1, a: bits[1] id=2, b: bits[1] id=3) -> bits[1] {
p: bits[1] = priority_sel(sel, cases=[a], default=b, id=4, pos=[(0,1,0), (1,2,0)])
ret result: bits[1] = identity(p, id=5)
}"#;
let mut temp_file = tempfile::Builder::new().suffix(".ir").tempfile().unwrap();
write!(temp_file, "{}", ir).unwrap();
let ir_path = temp_file.into_temp_path();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("ir2gates")
.arg(ir_path.to_str().unwrap())
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
xlsynth_test_helpers::compare_golden_text(
&stdout,
"tests/test_ir2gates_show_source.golden.txt",
);
}
#[test_case(true; "with_tool_path")]
#[test_case(false; "without_tool_path")]
fn test_dslx2sv_types_subcommand(use_tool_path: bool) {
let dslx = "enum OpType : u2 { READ = 0, WRITE = 1 }";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("my_module.x");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let toolchain_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml = "[toolchain]\n";
let toolchain_toml_path = if use_tool_path {
add_tool_path_value(&toolchain_toml)
} else {
toolchain_toml.to_string()
};
std::fs::write(&toolchain_path, toolchain_toml_path).unwrap();
let output = Command::new(command_path)
.arg("--toolchain")
.arg(toolchain_path.to_str().unwrap())
.arg("dslx2sv-types")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--sv_enum_case_naming_policy")
.arg("unqualified")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
xlsynth_test_helpers::assert_valid_sv(&stdout);
assert_eq!(
stdout.trim(),
r"typedef enum logic [1:0] {
Read = 2'd0,
Write = 2'd1
} op_type_t;"
);
}
#[test]
fn test_dslx2sv_types_subcommand_enum_qualified_policy() {
let dslx = "enum OpType : u2 { READ = 0, WRITE = 1 }";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("my_module.x");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("dslx2sv-types")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--sv_enum_case_naming_policy")
.arg("enum_qualified")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
xlsynth_test_helpers::assert_valid_sv(&stdout);
assert_eq!(
stdout.trim(),
r"typedef enum logic [1:0] {
OpType_Read = 2'd0,
OpType_Write = 2'd1
} op_type_t;"
);
}
#[test]
fn test_dslx2sv_types_subcommand_struct_ordering_defaults_to_as_declared() {
let dslx = r#"
struct MyStruct {
first: u8,
second: u16,
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("my_module.x");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("dslx2sv-types")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--sv_enum_case_naming_policy")
.arg("unqualified")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
xlsynth_test_helpers::assert_valid_sv(&stdout);
assert_eq!(
stdout.trim(),
r#"typedef struct packed {
logic [7:0] first;
logic [15:0] second;
} my_struct_t;"#
);
}
#[test]
fn test_dslx2sv_types_subcommand_struct_ordering_reversed() {
let dslx = r#"
struct MyStruct {
first: u8,
second: u16,
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("my_module.x");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("dslx2sv-types")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--sv_enum_case_naming_policy")
.arg("unqualified")
.arg("--sv_struct_field_ordering")
.arg("reversed")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
xlsynth_test_helpers::assert_valid_sv(&stdout);
assert_eq!(
stdout.trim(),
r#"typedef struct packed {
logic [15:0] second;
logic [7:0] first;
} my_struct_t;"#
);
}
#[test]
fn test_dslx2sv_types_requires_sv_enum_case_naming_policy_flag() {
let dslx = "enum OpType : u2 { READ = 0, WRITE = 1 }";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("my_module.x");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("dslx2sv-types")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.output()
.unwrap();
assert!(
!output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(stderr.contains("--sv_enum_case_naming_policy"));
assert!(stderr.contains("required"));
}
#[test]
fn test_dslx2sv_types_rejects_invalid_sv_struct_field_ordering_flag_value() {
let dslx = "struct MyStruct { first: u8, second: u16 }";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("my_module.x");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("dslx2sv-types")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--sv_enum_case_naming_policy")
.arg("unqualified")
.arg("--sv_struct_field_ordering")
.arg("bad_policy")
.output()
.unwrap();
assert!(
!output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(stderr.contains("bad_policy"));
assert!(stderr.contains("as_declared"));
assert!(stderr.contains("reversed"));
}
#[test]
fn test_dslx2sv_types_rejects_invalid_sv_enum_case_naming_policy_flag_value() {
let dslx = "enum OpType : u2 { READ = 0, WRITE = 1 }";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("my_module.x");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("dslx2sv-types")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--sv_enum_case_naming_policy")
.arg("bad_policy")
.output()
.unwrap();
assert!(
!output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(stderr.contains("bad_policy"));
assert!(stderr.contains("unqualified"));
assert!(stderr.contains("enum_qualified"));
}
#[test]
fn test_dslx2sv_types_with_std_clog2() {
let dslx = "import std;
const COUNT = u32:24;
const WIDTH = std::clog2(COUNT);
struct MyStruct {
data: bits[WIDTH],
}
";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("my_module.x");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("dslx2sv-types")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--sv_enum_case_naming_policy")
.arg("unqualified")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
xlsynth_test_helpers::assert_valid_sv(&stdout);
assert_eq!(
stdout.trim(),
"localparam bit unsigned [31:0] Count = 32'h00000018;
localparam bit unsigned [31:0] Width = 32'h00000005;
typedef struct packed {
logic [4:0] data;
} my_struct_t;"
);
}
#[test_case(true; "with_tool_path")]
#[test_case(false; "without_tool_path")]
fn test_dslx2ir_with_toolchain_toml(use_tool_path: bool) {
let dslx = "import std; fn main(x: u32) -> u32 { std::popcount(x) }";
let fake_std = "pub fn popcount(x: u32) -> u32 { x }";
let temp_dir = tempfile::tempdir().unwrap();
let stdlib_dir = temp_dir.path().join("stdlib");
std::fs::create_dir(&stdlib_dir).unwrap();
let client_dir = temp_dir.path().join("client");
std::fs::create_dir(&client_dir).unwrap();
let stdlib_path = stdlib_dir.join("std.x");
std::fs::write(&stdlib_path, fake_std).unwrap();
let client_path = client_dir.join("client.x");
std::fs::write(&client_path, dslx).unwrap();
let toolchain_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml = format!(
r#"[toolchain]
[toolchain.dslx]
dslx_stdlib_path = "{}"
"#,
stdlib_dir.to_str().unwrap()
);
let toolchain_toml_path = if use_tool_path {
add_tool_path_value(&toolchain_toml)
} else {
toolchain_toml
};
std::fs::write(&toolchain_path, toolchain_toml_path).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("--toolchain")
.arg(toolchain_path.to_str().unwrap())
.arg("dslx2ir")
.arg("--dslx_input_file")
.arg(client_path.to_str().unwrap())
.arg("--dslx_top")
.arg("main")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
assert!(stdout.contains(
"fn __std__popcount(x: bits[32] id=1) -> bits[32] {
ret x: bits[32] = param(name=x, id=1)
}"
))
}
#[test]
fn test_dslx2pipeline_with_update_of_1d_array() {
let dslx = "import std;
struct MyStruct {
some_bool: bool,
data: u32,
}
fn main(x: MyStruct[4]) -> MyStruct[4] {
update(x, u32:1, MyStruct{some_bool: false, data: u32:42})
}
";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("my_module.x");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("dslx2pipeline")
.arg("--pipeline_stages")
.arg("1")
.arg("--delay_model")
.arg("asap7")
.arg("--flop_inputs=true")
.arg("--flop_outputs=false")
.arg("--add_idle_output=true")
.arg("--separate_lines=false")
.arg("--use_system_verilog=true")
.arg("--array_index_bounds_checking=true")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("main")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
xlsynth_test_helpers::assert_valid_sv(&stdout);
compare_golden_sv(
&stdout,
"tests/test_dslx2pipeline_with_update_of_1d_array.golden.sv",
);
}
#[test_case(true; "with_tool_path")]
#[test_case(false; "without_tool_path")]
fn test_dslx2pipeline_with_redundant_match_arm(use_tool_path: bool) {
let _ = env_logger::try_init();
log::info!("test_dslx2pipeline_with_redundant_match_arm");
let dslx = "fn main(x: bool) -> u32 {
match x {
true => u32:42,
false => u32:64,
_ => u32:128,
}
}";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("my_module.x");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let toolchain_toml = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = format!(
r#"[toolchain]
[toolchain.dslx]
enable_warnings = ["already_exhaustive_match"]
"#
);
let toolchain_toml_path = if use_tool_path {
add_tool_path_value(&toolchain_toml_contents)
} else {
toolchain_toml_contents
};
std::fs::write(&toolchain_toml, toolchain_toml_path).unwrap();
let rust_log = std::env::var("RUST_LOG").unwrap_or_default();
let output = Command::new(command_path)
.arg("--toolchain")
.arg(toolchain_toml.to_str().unwrap())
.arg("dslx2pipeline")
.arg("--pipeline_stages")
.arg("1")
.arg("--delay_model")
.arg("asap7")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("main")
.env("RUST_LOG", rust_log)
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!output.status.success(),
"stdout: {}\nstderr: {}",
stdout,
stderr
);
assert!(
stderr.contains("Match is already exhaustive"),
"stdout: {}\nstderr: {}",
stdout,
stderr
);
}
#[test_case(true; "with_tool_path")]
#[test_case(false; "without_tool_path")]
fn test_dslx2pipeline_with_unused_binding(use_tool_path: bool) {
let _ = env_logger::try_init();
log::info!("test_dslx2pipeline_with_unused_binding");
let dslx = "fn main() -> u32 {
let x = u32:42; // unused_definition
for (i, accum) in u32:0..u32:0 { // empty_range_literal
accum
}(u32:64)
}";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("my_module.x");
std::fs::write(&dslx_path, dslx).unwrap();
let toolchain_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml = "[toolchain]\n";
let toolchain_toml_contents = if use_tool_path {
add_tool_path_value(&toolchain_toml)
} else {
toolchain_toml.to_string()
};
std::fs::write(&toolchain_path, toolchain_toml_contents).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let rust_log = std::env::var("RUST_LOG").unwrap_or_default();
let output = Command::new(command_path)
.arg("--toolchain")
.arg(toolchain_path.to_str().unwrap())
.arg("dslx2pipeline")
.arg("--pipeline_stages")
.arg("1")
.arg("--delay_model")
.arg("asap7")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("main")
.env("RUST_LOG", &rust_log)
.output()
.unwrap();
assert!(
!output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
log::info!("stdout: {}", stdout);
log::info!("stderr: {}", stderr);
assert!(
stderr.contains("is not used in function"),
"stdout: {}\nstderr: {}",
stdout,
stderr
);
let toolchain_toml = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = r#"[toolchain]
[toolchain.dslx]
disable_warnings = ["unused_definition", "empty_range_literal"]
"#;
let toolchain_toml_path = if use_tool_path {
add_tool_path_value(&toolchain_toml_contents)
} else {
toolchain_toml_contents.to_string()
};
std::fs::write(&toolchain_toml, toolchain_toml_path).unwrap();
log::info!("running again with warnings disabled...");
let output = Command::new(command_path)
.arg("--toolchain")
.arg(toolchain_toml.to_str().unwrap())
.arg("dslx2pipeline")
.arg("--pipeline_stages")
.arg("1")
.arg("--delay_model")
.arg("asap7")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("main")
.env("RUST_LOG", &rust_log)
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
log::info!("stdout: {}", stdout);
log::info!("stderr: {}", stderr);
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
stdout,
stderr
);
assert!(!stdout.contains("is not used in function"));
}
#[test]
fn test_dslx2pipeline_with_reset_signal() {
let _ = env_logger::try_init();
log::info!("test_dslx2pipeline_with_reset_signal");
let dslx = "fn main(x: u32) -> u32 {
x + u32:1
}";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("my_module.x");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("dslx2pipeline")
.arg("--pipeline_stages")
.arg("1")
.arg("--delay_model")
.arg("asap7")
.arg("--flop_inputs=true")
.arg("--flop_outputs=true")
.arg("--use_system_verilog=false")
.arg("--input_valid_signal=input_valid")
.arg("--output_valid_signal=output_valid")
.arg("--reset=rst_n")
.arg("--reset_active_low=true")
.arg("--reset_asynchronous=false")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("main")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr);
log::info!("stdout: {}", stdout);
log::info!("stderr: {}", stderr);
xlsynth_test_helpers::assert_valid_sv(&stdout);
compare_golden_sv(
&stdout,
"tests/test_dslx2pipeline_with_reset_signal.golden.sv",
);
}
#[test_case(true; "reset_datapath")]
#[test_case(false; "no_reset_datapath")]
fn test_dslx2pipeline_reset_data_path(reset_dp: bool) {
let _ = env_logger::try_init();
let dslx = "fn main(x: u32) -> u32 { x + u32:1 }";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("my_module.x");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("dslx2pipeline")
.arg("--pipeline_stages")
.arg("1")
.arg("--delay_model")
.arg("asap7")
.arg("--flop_inputs=true")
.arg("--flop_outputs=true")
.arg("--use_system_verilog=false")
.arg("--input_valid_signal=input_valid")
.arg("--output_valid_signal=output_valid")
.arg("--reset=rst_n")
.arg("--reset_active_low=true")
.arg("--reset_asynchronous=false")
.arg(format!("--reset_data_path={reset_dp}"))
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("main")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
xlsynth_test_helpers::assert_valid_sv(&stdout);
let golden_name = if reset_dp {
"tests/test_dslx2pipeline_reset_data_path_true.golden.sv"
} else {
"tests/test_dslx2pipeline_reset_data_path_false.golden.sv"
};
compare_golden_sv(&stdout, golden_name);
}
#[test_case(true; "with_tool_path")]
#[test_case(false; "without_tool_path")]
fn test_ir2opt_subcommand(use_tool_path: bool) {
let _ = env_logger::try_init();
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("ir.ir");
std::fs::write(
&ir_path,
"package sample
fn my_main(x: bits[32]) -> bits[32] {
literal.3: bits[32] = literal(value=0, id=3)
ret add.4: bits[32] = add(literal.3, x, id=4)
}",
)
.unwrap();
let toolchain_toml = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = "[toolchain]\n".to_string();
let toolchain_toml_contents = if use_tool_path {
add_tool_path_value(&toolchain_toml_contents)
} else {
toolchain_toml_contents
};
std::fs::write(&toolchain_toml, toolchain_toml_contents).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("--toolchain")
.arg(toolchain_toml.to_str().unwrap())
.arg("ir2opt")
.arg(ir_path.to_str().unwrap())
.arg("--top")
.arg("my_main")
.output()
.unwrap();
assert!(
output.status.success(),
"ir2opt should succeed; stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert_eq!(
stdout,
"package sample
top fn my_main(x: bits[32] id=5) -> bits[32] {
ret x: bits[32] = param(name=x, id=5)
}\n\n"
);
}
#[test]
fn test_ir_inline_subcommand_default_unroll_true() {
let _ = env_logger::try_init();
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("ir.ir");
std::fs::write(
&ir_path,
r#"package sample
fn g(x: bits[8] id=10) -> bits[8] {
ret add.2: bits[8] = add(x, x, id=2)
}
fn body(i: bits[8] id=20, acc: bits[8] id=21) -> bits[8] {
ret add.4: bits[8] = add(i, acc, id=4)
}
top fn my_main(x: bits[8] id=30) -> bits[8] {
invoke.5: bits[8] = invoke(x, to_apply=g, id=5)
ret counted_for.6: bits[8] = counted_for(invoke.5, trip_count=3, stride=1, body=body, id=6)
}"#,
)
.unwrap();
let toolchain_toml = temp_dir.path().join("xlsynth-toolchain.toml");
std::fs::write(&toolchain_toml, add_tool_path_value("[toolchain]\n")).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("--toolchain")
.arg(toolchain_toml.to_str().unwrap())
.arg("ir-inline")
.arg(ir_path.to_str().unwrap())
.output()
.unwrap();
assert!(
output.status.success(),
"ir-inline should succeed; stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("top fn my_main("),
"unexpected stdout: {}",
stdout
);
assert!(
!stdout.contains("fn g("),
"helper g should have been pruned from default ir-inline output: {}",
stdout
);
assert!(
!stdout.contains("fn body("),
"counted_for body should have been pruned from default ir-inline output: {}",
stdout
);
assert!(
!stdout.contains("invoke("),
"default ir-inline output should not contain invoke nodes: {}",
stdout
);
assert!(
!stdout.contains("counted_for("),
"default ir-inline output should not contain counted_for nodes: {}",
stdout
);
}
#[test]
fn test_ir_inline_subcommand_defaults_to_existing_package_top() {
let _ = env_logger::try_init();
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("ir.ir");
std::fs::write(
&ir_path,
r#"package sample
fn helper(x: bits[8] id=10) -> bits[8] {
ret not.2: bits[8] = not(x, id=2)
}
top fn my_main(x: bits[8] id=20) -> bits[8] {
ret invoke.3: bits[8] = invoke(x, to_apply=helper, id=3)
}"#,
)
.unwrap();
let toolchain_toml = temp_dir.path().join("xlsynth-toolchain.toml");
std::fs::write(&toolchain_toml, add_tool_path_value("[toolchain]\n")).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("--toolchain")
.arg(toolchain_toml.to_str().unwrap())
.arg("ir-inline")
.arg(ir_path.to_str().unwrap())
.output()
.unwrap();
assert!(
output.status.success(),
"ir-inline should default to the package top; stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("top fn my_main("),
"expected package top to be preserved in output: {}",
stdout
);
assert!(
!stdout.contains("invoke("),
"expected helper call to be inlined when using the package top: {}",
stdout
);
}
#[test]
fn test_ir_inline_subcommand_uses_only_function_when_package_has_no_explicit_top() {
let _ = env_logger::try_init();
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("ir.ir");
std::fs::write(
&ir_path,
r#"package sample
fn my_main(x: bits[8] id=10) -> bits[8] {
ret not.2: bits[8] = not(x, id=2)
}"#,
)
.unwrap();
let toolchain_toml = temp_dir.path().join("xlsynth-toolchain.toml");
std::fs::write(&toolchain_toml, add_tool_path_value("[toolchain]\n")).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("--toolchain")
.arg(toolchain_toml.to_str().unwrap())
.arg("ir-inline")
.arg(ir_path.to_str().unwrap())
.output()
.unwrap();
assert!(
output.status.success(),
"ir-inline should use the sole function as top; stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("top fn my_main("),
"expected sole function to become top in output: {}",
stdout
);
}
#[test]
fn test_ir_inline_subcommand_requires_top_when_package_has_multiple_functions_and_no_explicit_top()
{
let _ = env_logger::try_init();
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("ir.ir");
std::fs::write(
&ir_path,
r#"package sample
fn helper(x: bits[8] id=10) -> bits[8] {
ret not.2: bits[8] = not(x, id=2)
}
fn my_main(x: bits[8] id=20) -> bits[8] {
ret invoke.3: bits[8] = invoke(x, to_apply=helper, id=3)
}"#,
)
.unwrap();
let toolchain_toml = temp_dir.path().join("xlsynth-toolchain.toml");
std::fs::write(&toolchain_toml, add_tool_path_value("[toolchain]\n")).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("--toolchain")
.arg(toolchain_toml.to_str().unwrap())
.arg("ir-inline")
.arg(ir_path.to_str().unwrap())
.output()
.unwrap();
assert!(
!output.status.success(),
"ir-inline should fail without --top when multiple functions are present"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("multiple functions and no explicit top function"),
"unexpected stderr: {}",
stderr
);
}
#[test]
fn test_ir_inline_subcommand_unroll_false_keeps_counted_for_body() {
let _ = env_logger::try_init();
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("ir.ir");
std::fs::write(
&ir_path,
r#"package sample
fn g(x: bits[8] id=10) -> bits[8] {
ret add.2: bits[8] = add(x, x, id=2)
}
fn body(i: bits[8] id=20, acc: bits[8] id=21) -> bits[8] {
ret add.4: bits[8] = add(i, acc, id=4)
}
top fn my_main(x: bits[8] id=30) -> bits[8] {
invoke.5: bits[8] = invoke(x, to_apply=g, id=5)
ret counted_for.6: bits[8] = counted_for(invoke.5, trip_count=3, stride=1, body=body, id=6)
}"#,
)
.unwrap();
let toolchain_toml = temp_dir.path().join("xlsynth-toolchain.toml");
std::fs::write(&toolchain_toml, add_tool_path_value("[toolchain]\n")).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("--toolchain")
.arg(toolchain_toml.to_str().unwrap())
.arg("ir-inline")
.arg(ir_path.to_str().unwrap())
.arg("--top")
.arg("my_main")
.arg("--unroll")
.arg("false")
.output()
.unwrap();
assert!(
output.status.success(),
"ir-inline --unroll=false should succeed; stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("top fn my_main("),
"unexpected stdout: {}",
stdout
);
assert!(
!stdout.contains("invoke("),
"ir-inline --unroll=false should still inline explicit invokes: {}",
stdout
);
assert!(
stdout.contains("counted_for("),
"ir-inline --unroll=false should preserve counted_for nodes: {}",
stdout
);
assert!(
!stdout.contains("fn g("),
"helper g should have been pruned after invoke inlining: {}",
stdout
);
assert!(
stdout.contains("fn body("),
"counted_for body should remain reachable when --unroll=false: {}",
stdout
);
}
#[test_case(true; "with_tool_path")]
#[test_case(false; "without_tool_path")]
fn test_dslx2pipeline_with_dslx_path_two_entries(use_tool_path: bool) {
let temp_dir = tempfile::tempdir().unwrap();
let a_dir = temp_dir.path().join("a");
std::fs::create_dir(&a_dir).unwrap();
let b_dir = temp_dir.path().join("b");
std::fs::create_dir(&b_dir).unwrap();
let a_path = a_dir.join("a.x");
std::fs::write(&a_path, "pub const A: u32 = u32:42;").unwrap();
let b_path = b_dir.join("b.x");
std::fs::write(&b_path, "pub const B: u32 = u32:64;").unwrap();
let main_path = temp_dir.path().join("main.x");
std::fs::write(
&main_path,
"import a;
import b;
fn main() -> u32 { a::A + b::B }",
)
.unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let toolchain_toml = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = format!(
r#"[toolchain]
[toolchain.dslx]
dslx_path = ["{}", "{}"]
"#,
a_dir.to_str().unwrap(),
b_dir.to_str().unwrap()
);
let toolchain_toml_contents = if use_tool_path {
add_tool_path_value(&toolchain_toml_contents)
} else {
toolchain_toml_contents
};
std::fs::write(&toolchain_toml, toolchain_toml_contents).unwrap();
let rust_log = std::env::var("RUST_LOG").unwrap_or_default();
let output = Command::new(command_path)
.arg("--toolchain")
.arg(toolchain_toml.to_str().unwrap())
.arg("dslx2pipeline")
.arg("--pipeline_stages")
.arg("1")
.arg("--delay_model")
.arg("asap7")
.arg("--dslx_input_file")
.arg(main_path.to_str().unwrap())
.arg("--dslx_top")
.arg("main")
.env("RUST_LOG", rust_log)
.output()
.expect("xlsynth-driver should succeed");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
log::info!("stdout: {}", stdout);
log::info!("stderr: {}", stderr);
assert!(
stdout.contains("06a"),
"stdout: {} stderr: {}",
stdout,
stderr
);
}
#[test]
fn test_irequiv_subcommand_equivalent() {
let _ = env_logger::try_init();
let temp_dir = tempfile::tempdir().unwrap();
let lhs_ir = "package add_then_sub
fn my_main(x: bits[32]) -> bits[32] {
add.2: bits[32] = add(x, x)
ret sub.3: bits[32] = sub(add.2, x)
}";
let rhs_ir = "package identity
fn my_main(x: bits[32]) -> bits[32] {
ret identity.2: bits[32] = identity(x)
}";
let lhs_path = temp_dir.path().join("lhs.ir");
std::fs::write(&lhs_path, lhs_ir).unwrap();
let rhs_path = temp_dir.path().join("rhs.ir");
std::fs::write(&rhs_path, rhs_ir).unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = "[toolchain]\n".to_string();
let toolchain_toml_contents_with_path = add_tool_path_value(&toolchain_toml_contents);
std::fs::write(&toolchain_toml_path, toolchain_toml_contents_with_path).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("ir-equiv")
.arg("--solver")
.arg("toolchain")
.arg(lhs_path.to_str().unwrap())
.arg(rhs_path.to_str().unwrap())
.arg("--top")
.arg("my_main")
.output()
.expect("xlsynth-driver should succeed");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
log::info!("stdout: {}", stdout);
log::info!("stderr: {}", stderr);
assert!(stdout.contains("[ir-equiv] success: Solver proved equivalence"));
}
#[test]
fn test_irequiv_subcommand_non_equivalent() {
let _ = env_logger::try_init();
let temp_dir = tempfile::tempdir().unwrap();
let lhs_ir = "package add_then_sub
fn my_main(x: bits[32]) -> bits[32] {
umul.2: bits[32] = umul(x, x)
ret udiv.3: bits[32] = udiv(umul.2, x)
}";
let rhs_ir = "package identity
fn my_main(x: bits[32]) -> bits[32] {
ret identity.2: bits[32] = identity(x)
}";
let lhs_path = temp_dir.path().join("lhs.ir");
std::fs::write(&lhs_path, lhs_ir).unwrap();
let rhs_path = temp_dir.path().join("rhs.ir");
std::fs::write(&rhs_path, rhs_ir).unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = "[toolchain]\n".to_string();
let toolchain_toml_contents_with_path = add_tool_path_value(&toolchain_toml_contents);
std::fs::write(&toolchain_toml_path, toolchain_toml_contents_with_path).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("ir-equiv")
.arg("--solver")
.arg("toolchain")
.arg(lhs_path.to_str().unwrap())
.arg(rhs_path.to_str().unwrap())
.arg("--top")
.arg("my_main")
.output()
.expect("xlsynth-driver should succeed");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
log::info!("retcode: {}", output.status);
log::info!("stdout: {}", stdout);
log::info!("stderr: {}", stderr);
assert!(!output.status.success());
assert!(stdout.is_empty());
assert!(
stderr.contains("Verified NOT equivalent; results differ for input"),
"stderr: {:?}",
stderr
);
}
#[test]
fn test_irequiv_blocks_subcommand_equivalent() {
let _ = env_logger::try_init();
let temp_dir = tempfile::tempdir().unwrap();
let block = r#"package test_pkg
block my_block(a: bits[32], b: bits[32], out: bits[32]) {
a: bits[32] = input_port(name=a, id=1)
b: bits[32] = input_port(name=b, id=2)
add.3: bits[32] = add(a, b, id=3)
out: () = output_port(add.3, name=out, id=4)
}
"#;
let lhs_path = temp_dir.path().join("lhs.ir");
std::fs::write(&lhs_path, block).unwrap();
let rhs_path = temp_dir.path().join("rhs.ir");
std::fs::write(&rhs_path, block).unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = "[toolchain]\n".to_string();
let toolchain_toml_contents_with_path = add_tool_path_value(&toolchain_toml_contents);
std::fs::write(&toolchain_toml_path, toolchain_toml_contents_with_path).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("ir-equiv-blocks")
.arg("--solver")
.arg("toolchain")
.arg(lhs_path.to_str().unwrap())
.arg(rhs_path.to_str().unwrap())
.arg("--lhs_top")
.arg("my_block")
.arg("--rhs_top")
.arg("my_block")
.output()
.expect("xlsynth-driver should succeed");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"status not success\nstdout: {}\nstderr: {}",
stdout,
stderr
);
assert!(
stdout.contains("[ir-equiv-blocks] success: Solver proved equivalence"),
"stdout: {}\nstderr: {}",
stdout,
stderr
);
}
#[test]
fn test_irequiv_blocks_subcommand_non_equivalent() {
let _ = env_logger::try_init();
let temp_dir = tempfile::tempdir().unwrap();
let lhs_block = r#"package lhs_pkg
block add_block(a: bits[32], b: bits[32], out: bits[32]) {
a: bits[32] = input_port(name=a, id=1)
b: bits[32] = input_port(name=b, id=2)
add.3: bits[32] = add(a, b, id=3)
out: () = output_port(add.3, name=out, id=4)
}
"#;
let rhs_block = r#"package rhs_pkg
block sub_block(a: bits[32], b: bits[32], out: bits[32]) {
a: bits[32] = input_port(name=a, id=1)
b: bits[32] = input_port(name=b, id=2)
sub.3: bits[32] = sub(a, b, id=3)
out: () = output_port(sub.3, name=out, id=4)
}
"#;
let lhs_path = temp_dir.path().join("lhs.ir");
std::fs::write(&lhs_path, lhs_block).unwrap();
let rhs_path = temp_dir.path().join("rhs.ir");
std::fs::write(&rhs_path, rhs_block).unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = "[toolchain]\n".to_string();
let toolchain_toml_contents_with_path = add_tool_path_value(&toolchain_toml_contents);
std::fs::write(&toolchain_toml_path, toolchain_toml_contents_with_path).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("ir-equiv-blocks")
.arg("--solver")
.arg("toolchain")
.arg(lhs_path.to_str().unwrap())
.arg(rhs_path.to_str().unwrap())
.arg("--lhs_top")
.arg("add_block")
.arg("--rhs_top")
.arg("sub_block")
.output()
.expect("xlsynth-driver should succeed");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!output.status.success(),
"expected failure\nstdout: {}\nstderr: {}",
stdout,
stderr
);
assert!(
stderr.contains("Verified NOT equivalent"),
"stdout: {}\nstderr: {}",
stdout,
stderr
);
}
#[test]
fn test_ir2gates_determinism() {
let _ = env_logger::try_init();
log::info!("test_ir2gates_determinism");
let dslx = "fn main(a: u4, b: u4) -> u4 { a + b * (a ^ b) }";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("determinism_test.x");
let ir_path = temp_dir.path().join("determinism_test.ir");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let dslx2ir_output = Command::new(command_path)
.arg("dslx2ir")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("main")
.output()
.unwrap();
assert!(
dslx2ir_output.status.success(),
"dslx2ir failed:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&dslx2ir_output.stdout),
String::from_utf8_lossy(&dslx2ir_output.stderr)
);
std::fs::write(&ir_path, &dslx2ir_output.stdout).unwrap();
let mut command = std::process::Command::new(command_path);
command
.arg("ir2gates")
.arg(ir_path.to_str().unwrap())
.arg("--quiet=true")
.arg("--toggle-sample-count=2")
.arg("--toggle-seed=42");
if let Ok(rust_log) = std::env::var("RUST_LOG") {
command.env("RUST_LOG", rust_log);
}
let ir2gates_output1 = command.output().unwrap();
log::debug!(
"ir2gates stdout:\n{}",
String::from_utf8_lossy(&ir2gates_output1.stdout)
);
log::debug!(
"ir2gates stderr:\n{}",
String::from_utf8_lossy(&ir2gates_output1.stderr)
);
assert!(
ir2gates_output1.status.success(),
"ir2gates run 1 failed:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&ir2gates_output1.stdout),
String::from_utf8_lossy(&ir2gates_output1.stderr)
);
let output_str_1 = String::from_utf8(ir2gates_output1.stdout).unwrap();
let mut command = std::process::Command::new(command_path);
command
.arg("ir2gates")
.arg(ir_path.to_str().unwrap())
.arg("--quiet=true")
.arg("--toggle-sample-count=2")
.arg("--toggle-seed=42");
if let Ok(rust_log) = std::env::var("RUST_LOG") {
command.env("RUST_LOG", rust_log);
}
let ir2gates_output2 = command.output().unwrap();
log::debug!(
"ir2gates stdout:\n{}",
String::from_utf8_lossy(&ir2gates_output2.stdout)
);
log::debug!(
"ir2gates stderr:\n{}",
String::from_utf8_lossy(&ir2gates_output2.stderr)
);
assert!(
ir2gates_output2.status.success(),
"ir2gates run 2 failed:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&ir2gates_output2.stdout),
String::from_utf8_lossy(&ir2gates_output2.stderr)
);
let output_str_2 = String::from_utf8(ir2gates_output2.stdout).unwrap();
assert_eq!(
output_str_1, output_str_2,
"ir2gates output is non-deterministic!"
);
}
#[test_case(true; "with_tool_path")]
fn test_toolchain_picked_up_when_in_cwd_even_if_no_cmdline_flag(use_tool_path: bool) {
let temp_dir = tempfile::tempdir().unwrap();
let toolchain_toml = temp_dir.path().join("xlsynth-toolchain.toml");
let dslx_stdlib_path = temp_dir.path().join("dslx_stdlib");
std::fs::create_dir(&dslx_stdlib_path).unwrap();
let dslx_stdlib_path_str = dslx_stdlib_path.to_str().unwrap();
let toolchain_toml_contents = format!(
r#"[toolchain]
[toolchain.dslx]
dslx_stdlib_path = "{}"
"#,
dslx_stdlib_path_str
);
let toolchain_toml_contents = if use_tool_path {
add_tool_path_value(&toolchain_toml_contents)
} else {
toolchain_toml_contents
};
std::fs::write(&toolchain_toml, toolchain_toml_contents).unwrap();
let std_path = dslx_stdlib_path.join("std.x");
std::fs::write(&std_path, "pub fn popcount(x: u32) -> u32 { u32:1 }").unwrap();
let main_path = temp_dir.path().join("main.x");
std::fs::write(
&main_path,
"import std; fn main() -> u32 { std::popcount(u32:42) }",
)
.unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("dslx2ir")
.arg("--dslx_input_file")
.arg(main_path.to_str().unwrap())
.arg("--opt=true")
.arg("--dslx_top")
.arg("main")
.current_dir(temp_dir.path())
.output()
.expect("xlsynth-driver should succeed");
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(stderr.is_empty(), "stderr should be empty; got: {}", stderr);
assert!(
stdout.contains(
"top fn __main__main() -> bits[32] {
ret literal.5: bits[32] = literal(value=1, id=5, pos=[(1,0,44)])
}"
),
"stdout: {}",
stdout
);
}
#[test]
fn test_ir_annotate_ranges_smoke() {
let ir_text = r#"package sample
top fn main(x: bits[4] id=1, y: bits[4] id=2) -> bits[4] {
literal.3: bits[4] = literal(value=0xf, id=3)
add.4: bits[4] = add(x, y, id=4)
ret and.5: bits[4] = and(add.4, literal.3, id=5)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("input.ir");
std::fs::write(&ir_path, ir_text).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(command_path)
.arg("ir-annotate-ranges")
.arg(ir_path.to_str().unwrap())
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
assert!(
stdout.contains("// range:") && stdout.contains("known_bits: 0b"),
"expected range and known_bits comments in output; stdout:\n{}",
stdout
);
}
#[test]
fn test_ir_fn_node_count() {
let ir_text = r#"package sample
top fn main(x: bits[8] id=1) -> bits[8] {
ret x: bits[8] = param(name=x, id=1)
}
fn helper(x: bits[8] id=2, y: bits[8] id=3) -> bits[8] {
ret add.4: bits[8] = add(x, y, id=4)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("input.ir");
std::fs::write(&ir_path, ir_text).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(command_path)
.arg("ir-fn-node-count")
.arg(ir_path.to_str().unwrap())
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(stderr.is_empty(), "stderr should be empty; got: {}", stderr);
assert_eq!(stdout, "1\n");
let output = std::process::Command::new(command_path)
.arg("ir-fn-node-count")
.arg(ir_path.to_str().unwrap())
.arg("--top")
.arg("helper")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(stderr.is_empty(), "stderr should be empty; got: {}", stderr);
assert_eq!(stdout, "3\n");
}
#[test]
fn test_ir_fn_autocov_writes_irvals_corpus() {
let ir_text = r#"package sample
top fn main(sel: bits[1] id=1, a: bits[1] id=2, b: bits[1] id=3) -> bits[1] {
ret out: bits[1] = sel(sel, cases=[a, b], id=4)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("input.ir");
let corpus_path = temp_dir.path().join("interesting.irvals");
std::fs::write(&ir_path, ir_text).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(command_path)
.arg("ir-fn-autocov")
.arg(ir_path.to_str().unwrap())
.arg("--corpus-file")
.arg(corpus_path.to_str().unwrap())
.arg("--max-iters")
.arg("128")
.arg("--progress-every")
.arg("0")
.arg("--seed-two-hot-max-bits")
.arg("64")
.arg("--seed-structured=true")
.arg("--no-mux-space=true")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("summary iters=")
&& stdout.contains("corpus_len=")
&& stdout.contains("stop_reason="),
"expected summary line in stdout, got:\n{}",
stdout
);
let corpus_text = std::fs::read_to_string(&corpus_path).unwrap();
let lines: Vec<&str> = corpus_text.lines().collect();
assert!(
!lines.is_empty(),
"expected non-empty corpus, got file:\n{}",
corpus_text
);
assert!(
lines[0].starts_with('(') && lines[0].contains("bits[1]"),
"unexpected first corpus line: {}",
lines[0]
);
}
#[test]
fn test_ir_fn_autocov_stops_at_max_corpus_len_during_structured_seed() {
let ir_text = r#"package sample
top fn main(sel: bits[1] id=1, a: bits[1] id=2, b: bits[1] id=3) -> bits[1] {
ret out: bits[1] = sel(sel, cases=[a, b], id=4)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("input.ir");
let corpus_path = temp_dir.path().join("interesting.irvals");
std::fs::write(&ir_path, ir_text).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(command_path)
.arg("ir-fn-autocov")
.arg(ir_path.to_str().unwrap())
.arg("--corpus-file")
.arg(corpus_path.to_str().unwrap())
.arg("--max-corpus-len")
.arg("4")
.arg("--max-iters")
.arg("128")
.arg("--progress-every")
.arg("0")
.arg("--seed-two-hot-max-bits")
.arg("64")
.arg("--seed-structured=true")
.arg("--no-mux-space=true")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("summary iters=0")
&& stdout.contains("corpus_len=4")
&& stdout.contains("stop_reason=max_corpus_len"),
"expected max_corpus_len summary, got:\n{}",
stdout
);
let corpus_text = std::fs::read_to_string(&corpus_path).unwrap();
let lines: Vec<&str> = corpus_text.lines().collect();
assert_eq!(
lines.len(),
4,
"expected capped corpus file, got:\n{}",
corpus_text
);
}
#[test]
fn test_ir_fn_autocov_replay_counts_toward_max_corpus_len() {
let ir_text = r#"package sample
top fn main(sel: bits[1] id=1, a: bits[1] id=2, b: bits[1] id=3) -> bits[1] {
ret out: bits[1] = sel(sel, cases=[a, b], id=4)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("input.ir");
let corpus_path = temp_dir.path().join("interesting.irvals");
std::fs::write(&ir_path, ir_text).unwrap();
std::fs::write(
&corpus_path,
"(bits[1]:0x0, bits[1]:0x0, bits[1]:0x0)\n(bits[1]:0x1, bits[1]:0x1, bits[1]:0x1)\n",
)
.unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(command_path)
.arg("ir-fn-autocov")
.arg(ir_path.to_str().unwrap())
.arg("--corpus-file")
.arg(corpus_path.to_str().unwrap())
.arg("--max-corpus-len")
.arg("2")
.arg("--max-iters")
.arg("128")
.arg("--progress-every")
.arg("0")
.arg("--seed-two-hot-max-bits")
.arg("64")
.arg("--seed-structured=true")
.arg("--no-mux-space=true")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("summary iters=0")
&& stdout.contains("corpus_len=2")
&& stdout.contains("stop_reason=max_corpus_len"),
"expected replay-bound summary, got:\n{}",
stdout
);
let corpus_text = std::fs::read_to_string(&corpus_path).unwrap();
assert_eq!(
corpus_text,
"(bits[1]:0x0, bits[1]:0x0, bits[1]:0x0)\n(bits[1]:0x1, bits[1]:0x1, bits[1]:0x1)\n"
);
}
#[test]
fn test_ir_fn_to_json() {
let ir_text = r#"package sample
top fn main(x: bits[8] id=1) -> bits[8] {
ret x: bits[8] = param(name=x, id=1)
}
fn helper(x: bits[8] id=2, y: bits[8] id=3) -> bits[8] {
ret add.4: bits[8] = add(x, y, id=4)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("input.ir");
std::fs::write(&ir_path, ir_text).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(command_path)
.arg("ir-fn-to-json")
.arg(ir_path.to_str().unwrap())
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(stderr.is_empty(), "stderr should be empty; got: {}", stderr);
let json: serde_json::Value = serde_json::from_str(stdout.trim()).expect("Output JSON");
assert_eq!(json["package_name"], "sample");
assert_eq!(json["selected_top"], "main");
assert_eq!(json["return_type"], "bits[8]");
assert_eq!(json["node_count"], 2);
assert!(
json["pir"].as_str().unwrap().contains("top fn main("),
"pir field should contain emitted top function: {}",
json["pir"]
);
let nodes = json["nodes"].as_array().expect("nodes array");
assert_eq!(nodes.len(), 2);
assert_eq!(nodes[0]["op"], "nil");
assert_eq!(nodes[1]["op"], "get_param");
assert_eq!(nodes[1]["name"], "x");
assert_eq!(nodes[1]["is_param"], true);
assert_eq!(nodes[1]["is_ret"], true);
let output = std::process::Command::new(command_path)
.arg("ir-fn-to-json")
.arg(ir_path.to_str().unwrap())
.arg("--top")
.arg("helper")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(stderr.is_empty(), "stderr should be empty; got: {}", stderr);
let json: serde_json::Value = serde_json::from_str(stdout.trim()).expect("Output JSON");
assert_eq!(json["selected_top"], "helper");
assert_eq!(json["node_count"], 4);
assert!(
json["pir"].as_str().unwrap().contains("top fn helper("),
"pir field should contain emitted top function: {}",
json["pir"]
);
let nodes = json["nodes"].as_array().expect("nodes array");
assert_eq!(nodes[3]["op"], "add");
assert_eq!(nodes[3]["is_ret"], true);
assert_eq!(nodes[3]["operands"], serde_json::json!([1, 2]));
}
#[test]
fn test_xls_ir_fn_to_z3_smtlib() {
let ir_text = r#"package sample
top fn main(x: bits[8] id=1) -> bits[8] {
ret add.2: bits[8] = add(x, x, id=2)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("input.ir");
std::fs::write(&ir_path, ir_text).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(command_path)
.arg("xls-ir-fn-to-z3-smtlib")
.arg(ir_path.to_str().unwrap())
.arg("--top")
.arg("main")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(stderr.is_empty(), "stderr should be empty; got: {}", stderr);
assert!(
stdout.contains("declare-fun"),
"expected SMT-LIB output to contain declare-fun; stdout: {}",
stdout
);
assert!(
stdout.contains("main"),
"expected SMT-LIB output to reference function name; stdout: {}",
stdout
);
}
#[test]
fn test_ir_fn_structural_hash() {
let ir_text = r#"package sample
top fn main(x: bits[8] id=1) -> bits[8] {
ret add.3: bits[8] = add(x, x, id=3)
}
fn helper(y: bits[8] id=22) -> bits[8] {
ret add.99: bits[8] = add(y, y, id=99)
}
fn different(z: bits[8] id=100) -> bits[8] {
ret sub.7: bits[8] = sub(z, z, id=7)
}
"#;
fn is_hex_64(s: &str) -> bool {
s.len() == 64 && s.chars().all(|c| c.is_ascii_hexdigit())
}
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("input.ir");
std::fs::write(&ir_path, ir_text).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(command_path)
.arg("ir-fn-structural-hash")
.arg(ir_path.to_str().unwrap())
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(stderr.is_empty(), "stderr should be empty; got: {}", stderr);
let main_hash = String::from_utf8_lossy(&output.stdout).trim().to_string();
assert!(
is_hex_64(&main_hash),
"Expected 64-hex hash; got: {}",
main_hash
);
let output = std::process::Command::new(command_path)
.arg("ir-fn-structural-hash")
.arg(ir_path.to_str().unwrap())
.arg("--top")
.arg("helper")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let helper_hash = String::from_utf8_lossy(&output.stdout).trim().to_string();
assert_eq!(
main_hash, helper_hash,
"expected rename-insensitive structural hashes to match"
);
let output = std::process::Command::new(command_path)
.arg("ir-fn-structural-hash")
.arg(ir_path.to_str().unwrap())
.arg("--top")
.arg("different")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let different_hash = String::from_utf8_lossy(&output.stdout).trim().to_string();
assert_ne!(main_hash, different_hash);
let output = std::process::Command::new(command_path)
.arg("ir-fn-structural-hash")
.arg(ir_path.to_str().unwrap())
.arg("--json=true")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let json: serde_json::Value =
serde_json::from_str(String::from_utf8_lossy(&output.stdout).trim())
.expect("Expected valid JSON");
let json_hash = json["structural_hash"]
.as_str()
.expect("structural_hash string");
assert!(is_hex_64(json_hash));
assert_eq!(json_hash, main_hash);
}
#[test]
fn test_dslx_add_sub_opt_ir2gates_pipeline() {
let _ = env_logger::try_init();
let dslx = "
import std;
const UNUSED = std::popcount(u3:0b111);
fn f(x: u8) -> u8 { x + x - x }
#[test] fn test_my_add() { assert_eq(f(u8::MAX), u8::MAX); }
#[quickcheck] fn quickcheck_my_add(x: u8) -> bool { f(x) == x }
";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("f.x");
let ir_path = temp_dir.path().join("f.opt.ir");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let dslx2ir_output = std::process::Command::new(command_path)
.arg("dslx2ir")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("f")
.output()
.unwrap();
assert!(
dslx2ir_output.status.success(),
"dslx2ir failed:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&dslx2ir_output.stdout),
String::from_utf8_lossy(&dslx2ir_output.stderr)
);
std::fs::write(&ir_path, &dslx2ir_output.stdout).unwrap();
log::info!(
"unoptimized IR:\n{}",
String::from_utf8_lossy(&dslx2ir_output.stdout)
);
let module_name = dslx_path.file_stem().unwrap().to_str().unwrap();
let dslx_top = "f";
let ir_top = format!("__{}__{}", module_name, dslx_top);
let ir2opt_output = std::process::Command::new(command_path)
.arg("ir2opt")
.arg(ir_path.to_str().unwrap())
.arg("--top")
.arg(&ir_top)
.output()
.unwrap();
assert!(
ir2opt_output.status.success(),
"ir2opt failed:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&ir2opt_output.stdout),
String::from_utf8_lossy(&ir2opt_output.stderr)
);
let optimized_ir = String::from_utf8_lossy(&ir2opt_output.stdout).to_string();
let opt_ir_path = temp_dir.path().join("f.after_ir2opt.ir");
std::fs::write(&opt_ir_path, &optimized_ir).unwrap();
let ir_round_trip = std::process::Command::new(command_path)
.arg("ir-round-trip")
.arg(opt_ir_path.to_str().unwrap())
.arg("--strip-pos-attrs")
.arg("true")
.output()
.unwrap();
assert!(
ir_round_trip.status.success(),
"ir-round-trip --strip-pos-attrs failed:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&ir_round_trip.stdout),
String::from_utf8_lossy(&ir_round_trip.stderr)
);
let stripped_ir = String::from_utf8_lossy(&ir_round_trip.stdout).to_string();
eprintln!(
"[ir2opt optimized IR for __f__f (stripped)]:\n{}",
stripped_ir
);
let golden_rel = "tests/test_dslx_add_sub_opt_ir2gates_pipeline.golden.ir";
let golden_dir = std::path::Path::new(golden_rel).parent().unwrap();
let _ = std::fs::create_dir_all(golden_dir);
compare_golden_text(&stripped_ir, golden_rel);
std::fs::write(&ir_path, &ir2opt_output.stdout).unwrap();
log::info!(
"optimized IR:\n{}",
String::from_utf8_lossy(&ir2opt_output.stdout)
);
let ir2gates_output = std::process::Command::new(command_path)
.arg("ir2gates")
.arg(ir_path.to_str().unwrap())
.output()
.unwrap();
assert!(
ir2gates_output.status.success(),
"ir2gates failed:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&ir2gates_output.stdout),
String::from_utf8_lossy(&ir2gates_output.stderr)
);
}
#[test]
fn test_ir2gates_quiet_json_output() {
let _ = env_logger::builder().is_test(true).try_init();
let dslx = "fn main(a: u32, b: u32) -> u32 { a & b }";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("main.x");
let ir_path = temp_dir.path().join("main.ir");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let dslx2ir_output = std::process::Command::new(command_path)
.arg("dslx2ir")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("main")
.output()
.unwrap();
assert!(dslx2ir_output.status.success());
std::fs::write(&ir_path, &dslx2ir_output.stdout).unwrap();
let mut command = std::process::Command::new(command_path);
command
.arg("ir2gates")
.arg(ir_path.to_str().unwrap())
.arg("--quiet=true")
.arg("--toggle-sample-count=32")
.arg("--toggle-seed=42");
if let Ok(rust_log) = std::env::var("RUST_LOG") {
command.env("RUST_LOG", rust_log);
}
let ir2gates_output = command.output().unwrap();
log::debug!(
"ir2gates stdout:\n{}",
String::from_utf8_lossy(&ir2gates_output.stdout)
);
log::debug!(
"ir2gates stderr:\n{}",
String::from_utf8_lossy(&ir2gates_output.stderr)
);
assert!(ir2gates_output.status.success());
let stdout = String::from_utf8_lossy(&ir2gates_output.stdout).to_string();
let json: serde_json::Value =
serde_json::from_str(stdout.trim()).expect("Output is not valid JSON");
log::info!("json: {}", json);
assert_eq!(json["deepest_path"], 2);
assert_eq!(json["fanout_histogram"].to_string(), "{\"1\":64}");
assert_eq!(json["live_nodes"], 96);
let expected_toggle_stats: HashMap<&str, i32> = [
("gate_output_toggles", 363),
("gate_input_toggles", 980),
("primary_input_toggles", 980),
("primary_output_toggles", 363),
]
.iter()
.cloned()
.collect();
assert_eq!(
json["toggle_stats"].as_object().unwrap().len(),
expected_toggle_stats.len()
);
for (key, value) in expected_toggle_stats.iter() {
assert_eq!(json["toggle_stats"][key].as_i64().unwrap(), *value as i64);
}
}
#[test]
fn test_ir2gates_quiet_json_output_no_toggle() {
let _ = env_logger::builder().is_test(true).try_init();
let dslx = "fn main(a: u32, b: u32) -> u32 { a & b }";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("main.x");
let ir_path = temp_dir.path().join("main.ir");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let dslx2ir_output = std::process::Command::new(command_path)
.arg("dslx2ir")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("main")
.output()
.unwrap();
assert!(dslx2ir_output.status.success());
std::fs::write(&ir_path, &dslx2ir_output.stdout).unwrap();
let mut command = std::process::Command::new(command_path);
command
.arg("ir2gates")
.arg(ir_path.to_str().unwrap())
.arg("--quiet=true");
if let Ok(rust_log) = std::env::var("RUST_LOG") {
command.env("RUST_LOG", rust_log);
}
let ir2gates_output = command.output().unwrap();
log::debug!(
"ir2gates stdout:\n{}",
String::from_utf8_lossy(&ir2gates_output.stdout)
);
log::debug!(
"ir2gates stderr:\n{}",
String::from_utf8_lossy(&ir2gates_output.stderr)
);
assert!(ir2gates_output.status.success());
let stdout = String::from_utf8_lossy(&ir2gates_output.stdout);
let json: serde_json::Value =
serde_json::from_str(stdout.trim()).expect("Output is not valid JSON");
log::info!("json: {}", json);
assert_eq!(json["deepest_path"], 2);
assert_eq!(json["fanout_histogram"].to_string(), "{\"1\":64}");
assert_eq!(json["live_nodes"], 96);
assert_eq!(
json["graph_logical_effort_worst_case_delay"].to_string(),
"3.333333333333333"
);
assert!(
json["toggle_stats"].is_null(),
"toggle_stats should be null"
);
}
#[test]
fn test_ir2gates_quiet_json_output_independent_op_stats() {
let _ = env_logger::builder().is_test(true).try_init();
let dslx = "fn main(a: u32, b: u32) -> u32 { a & b }";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("main.x");
let ir_path = temp_dir.path().join("main.ir");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let dslx2ir_output = std::process::Command::new(command_path)
.arg("dslx2ir")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("main")
.output()
.unwrap();
assert!(dslx2ir_output.status.success());
std::fs::write(&ir_path, &dslx2ir_output.stdout).unwrap();
let mut command = std::process::Command::new(command_path);
command
.arg("ir2gates")
.arg(ir_path.to_str().unwrap())
.arg("--quiet=true")
.arg("--fraig=false")
.arg("--emit-independent-op-stats=true");
if let Ok(rust_log) = std::env::var("RUST_LOG") {
command.env("RUST_LOG", rust_log);
}
let ir2gates_output = command.output().unwrap();
assert!(
ir2gates_output.status.success(),
"ir2gates failed:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&ir2gates_output.stdout),
String::from_utf8_lossy(&ir2gates_output.stderr)
);
let stdout = String::from_utf8_lossy(&ir2gates_output.stdout);
let json: serde_json::Value =
serde_json::from_str(stdout.trim()).expect("Output is not valid JSON");
let ind = &json["independent_op_stats"];
assert!(ind.is_object(), "independent_op_stats should be present");
for key in [
"independent_sum_live_nodes",
"independent_sum_deepest_path",
"independent_max_live_nodes",
"independent_max_deepest_path",
"independent_critical_path_depth",
"independent_included_node_count",
] {
assert!(ind[key].is_number(), "{} should be a number", key);
}
let max_depth = ind["independent_max_deepest_path"].as_u64().unwrap();
let crit_depth = ind["independent_critical_path_depth"].as_u64().unwrap();
let sum_depth = ind["independent_sum_deepest_path"].as_u64().unwrap();
assert!(
crit_depth >= max_depth,
"critical depth should be at least max per-node depth"
);
assert!(
crit_depth <= sum_depth,
"critical depth should be at most sum of per-node depths"
);
let per_node = ind["per_node"].as_array().unwrap();
assert!(!per_node.is_empty(), "per_node should be non-empty");
assert_eq!(
ind["independent_included_node_count"].as_u64().unwrap() as usize,
per_node.len()
);
for entry in per_node.iter() {
assert!(entry["node_index"].is_number());
assert!(entry["text_id"].is_number());
assert!(entry["live_nodes"].is_number());
assert!(entry["deepest_path"].is_number());
let op = entry["op"].as_str().unwrap().to_lowercase();
for banned in [
"getparam",
"literal",
"nil",
"not(",
"identity",
"tuple",
"tuple_index",
"array",
"concat",
"bit_slice",
"zero_ext",
"sign_ext",
] {
assert!(
!op.contains(banned),
"excluded payload kind should not appear in per_node list: {}",
banned
);
}
}
}
#[test]
fn test_ir2gates_mul_adder_mapping_override_affects_umul_depth() {
let _ = env_logger::builder().is_test(true).try_init();
let ir_text = r#"package sample
top fn main(a: bits[8] id=1, b: bits[8] id=2) -> bits[16] {
ret umul.3: bits[16] = umul(a, b, id=3)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("main.ir");
std::fs::write(&ir_path, ir_text).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
fn get_umul_depth(command_path: &str, ir_path: &std::path::Path, extra_args: &[&str]) -> u64 {
let mut command = std::process::Command::new(command_path);
command
.arg("ir2gates")
.arg(ir_path.to_str().unwrap())
.arg("--quiet=true")
.arg("--fraig=false")
.arg("--emit-independent-op-stats=true");
for arg in extra_args {
command.arg(arg);
}
if let Ok(rust_log) = std::env::var("RUST_LOG") {
command.env("RUST_LOG", rust_log);
}
let ir2gates_output = command.output().unwrap();
assert!(
ir2gates_output.status.success(),
"ir2gates failed:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&ir2gates_output.stdout),
String::from_utf8_lossy(&ir2gates_output.stderr)
);
let stdout = String::from_utf8_lossy(&ir2gates_output.stdout);
let json: serde_json::Value =
serde_json::from_str(stdout.trim()).expect("Output is not valid JSON");
let per_node = json["independent_op_stats"]["per_node"]
.as_array()
.expect("expected independent_op_stats.per_node to be an array");
let expected_op = "umul(bits[8], bits[8]) -> bits[16]";
let entry = per_node
.iter()
.find(|e| e["op"].as_str() == Some(expected_op))
.unwrap_or_else(|| panic!("could not find per-node entry with op {}", expected_op));
entry["deepest_path"]
.as_u64()
.expect("expected per-node deepest_path to be a u64")
}
let default_depth = get_umul_depth(command_path, &ir_path, &[]);
let ripple_depth = get_umul_depth(
command_path,
&ir_path,
&["--mul-adder-mapping=ripple-carry"],
);
assert!(
ripple_depth > default_depth,
"expected ripple-carry multiplier depth ({}) to be greater than default multiplier depth ({})",
ripple_depth,
default_depth
);
}
#[test]
fn test_ir2gates_output_json_file() {
let _ = env_logger::builder().is_test(true).try_init();
let dslx = "fn main(a: u32, b: u32) -> u32 { a & b }";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("main.x");
let ir_path = temp_dir.path().join("main.ir");
let json_path = temp_dir.path().join("stats.json");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let dslx2ir_output = std::process::Command::new(command_path)
.arg("dslx2ir")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("main")
.output()
.unwrap();
assert!(dslx2ir_output.status.success());
std::fs::write(&ir_path, &dslx2ir_output.stdout).unwrap();
let mut command = std::process::Command::new(command_path);
command
.arg("ir2gates")
.arg(ir_path.to_str().unwrap())
.arg(format!("--output_json={}", json_path.display()));
if let Ok(rust_log) = std::env::var("RUST_LOG") {
command.env("RUST_LOG", rust_log);
}
let output = command.output().unwrap();
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("Deepest path"));
let json_content = std::fs::read_to_string(&json_path).unwrap();
let json: serde_json::Value = serde_json::from_str(&json_content).unwrap();
assert_eq!(json["deepest_path"], 2);
assert_eq!(json["live_nodes"], 96);
}
#[test]
fn test_ir2gates_prepared_ir_default_enables_prio_encode_rewrite() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("main.ir");
let prepared_path = temp_dir.path().join("prepared.ir");
let prepared_disabled_path = temp_dir.path().join("prepared_disabled.ir");
let ir_text = r#"package p
fn helper(x: bits[1] id=10) -> bits[1] {
ret not.11: bits[1] = not(x, id=11)
}
top fn main(x: bits[32] id=1) -> bits[5] {
one_hot.2: bits[33] = one_hot(x, lsb_prio=false, id=2)
encode.3: bits[6] = encode(one_hot.2, id=3)
ret bit_slice.4: bits[5] = bit_slice(encode.3, start=0, width=5, id=4)
}"#;
std::fs::write(&ir_path, ir_text).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(command_path)
.arg("ir2gates")
.arg(ir_path.to_str().unwrap())
.arg("--fraig=false")
.arg(format!("--prepared-ir-out={}", prepared_path.display()))
.output()
.unwrap();
assert!(
output.status.success(),
"ir2gates failed:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let prepared_text = std::fs::read_to_string(&prepared_path).unwrap();
assert!(
prepared_text.contains("ext_prio_encode("),
"expected ext_prio_encode rewrite in prepared IR, got:\n{}",
prepared_text
);
assert!(
!prepared_text.contains("fn helper("),
"expected prepared IR output to contain only the prepared top member, got:\n{}",
prepared_text
);
assert!(
!prepared_text.contains("invoke("),
"did not expect invoke() in top-only prepared IR output, got:\n{}",
prepared_text
);
assert!(
!prepared_text.contains("counted_for("),
"did not expect counted_for() in top-only prepared IR output, got:\n{}",
prepared_text
);
let output_disabled = std::process::Command::new(command_path)
.arg("ir2gates")
.arg(ir_path.to_str().unwrap())
.arg("--fraig=false")
.arg("--enable-rewrite-prio-encode=false")
.arg(format!(
"--prepared-ir-out={}",
prepared_disabled_path.display()
))
.output()
.unwrap();
assert!(
output_disabled.status.success(),
"ir2gates (rewrite disabled) failed:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&output_disabled.stdout),
String::from_utf8_lossy(&output_disabled.stderr)
);
let prepared_disabled_text = std::fs::read_to_string(&prepared_disabled_path).unwrap();
assert!(
!prepared_disabled_text.contains("ext_prio_encode("),
"did not expect ext_prio_encode when rewrite is disabled, got:\n{}",
prepared_disabled_text
);
}
#[allow(dead_code)]
fn test_irequiv_subcommand_solver_equivalent(solver: &str) {
let _ = env_logger::try_init();
let temp_dir = tempfile::tempdir().unwrap();
let lhs_ir = "package add_then_sub\nfn my_main(x: bits[32]) -> bits[32] {\n add.2: bits[32] = add(x, x)\n ret sub.3: bits[32] = sub(add.2, x)\n}";
let rhs_ir = "package identity\nfn my_main(x: bits[32]) -> bits[32] {\n ret identity.2: bits[32] = identity(x)\n}";
let lhs_path = temp_dir.path().join("lhs.ir");
std::fs::write(&lhs_path, lhs_ir).unwrap();
let rhs_path = temp_dir.path().join("rhs.ir");
std::fs::write(&rhs_path, rhs_ir).unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = "[toolchain]\n".to_string();
let toolchain_toml_contents_with_path = add_tool_path_value(&toolchain_toml_contents);
std::fs::write(&toolchain_toml_path, toolchain_toml_contents_with_path).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(command_path)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("ir-equiv")
.arg(lhs_path.to_str().unwrap())
.arg(rhs_path.to_str().unwrap())
.arg("--top")
.arg("my_main")
.arg(format!("--solver={}", solver))
.output()
.expect("xlsynth-driver should succeed");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
log::info!("stdout: {}", stdout);
log::info!("stderr: {}", stderr);
assert!(
output.status.success(),
"Solver ir-equiv should succeed; stderr: {}",
stderr
);
assert!(
stdout.contains("Solver proved equivalence"),
"stdout: {}",
stdout
);
}
fn test_irequiv_subcommand_solver_invoke_equivalent(solver: &str) {
let _ = env_logger::try_init();
let temp_dir = tempfile::tempdir().unwrap();
let lhs_ir = r#"package p_invoke
fn g(x: bits[8]) -> bits[8] {
ret add.2: bits[8] = add(x, x, id=2)
}
fn my_main(x: bits[8] id=3) -> bits[8] {
ret invoke.4: bits[8] = invoke(x, to_apply=g, id=4)
}
"#;
let rhs_ir = r#"package p_inline
fn my_main(x: bits[8]) -> bits[8] {
ret add.2: bits[8] = add(x, x, id=2)
}
"#;
let lhs_path = temp_dir.path().join("lhs.ir");
std::fs::write(&lhs_path, lhs_ir).unwrap();
let rhs_path = temp_dir.path().join("rhs.ir");
std::fs::write(&rhs_path, rhs_ir).unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml_path, toolchain_toml_contents).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(command_path)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("ir-equiv")
.arg(lhs_path.to_str().unwrap())
.arg(rhs_path.to_str().unwrap())
.arg("--top")
.arg("my_main")
.arg(format!("--solver={}", solver))
.output()
.expect("xlsynth-driver should run");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
println!("stdout: {}", stdout);
println!("stderr: {}", stderr);
assert!(
output.status.success(),
"Invoke ir-equiv with solver {} should succeed; stderr: {}",
solver,
stderr
);
assert!(
stdout.contains("Solver proved equivalence"),
"stdout: {}",
stdout
);
}
#[cfg_attr(feature="has-boolector", test_case::test_case("boolector"; "invoke_boolector"))]
#[cfg_attr(feature="has-bitwuzla", test_case::test_case("bitwuzla"; "invoke_bitwuzla"))]
#[cfg_attr(feature="with-z3-binary-test", test_case::test_case("z3-binary"; "invoke_z3_binary"))]
#[cfg_attr(feature="with-bitwuzla-binary-test", test_case::test_case("bitwuzla-binary"; "invoke_bitwuzla_binary"))]
#[cfg_attr(feature="with-boolector-binary-test", test_case::test_case("boolector-binary"; "invoke_boolector_binary"))]
#[test_case::test_case("toolchain"; "invoke_toolchain")]
fn test_irequiv_subcommand_solver_invoke_equivalent_param(solver: &str) {
test_irequiv_subcommand_solver_invoke_equivalent(solver);
}
#[allow(dead_code)]
fn test_irequiv_subcommand_solver_equivalent_with_fixed_implicit_activation(solver: &str) {
let _ = env_logger::try_init();
let temp_dir = tempfile::tempdir().unwrap();
let lhs_ir = r#"
package add_then_sub
fn my_main(__token: token, __activation: bits[1], x: bits[32]) -> (token, bits[32]) {
assert.2: token = assert(__token, __activation, message="activation should be false", label="activation_should_be_false")
add.3: bits[32] = add(x, x)
sub.4: bits[32] = sub(add.3, x)
ret tuple.5: (token, bits[32]) = tuple(assert.2, sub.4)
}
"#;
let rhs_ir = r#"
package identity
fn my_main(x: bits[32]) -> bits[32] {
ret identity.2: bits[32] = identity(x)
}
"#;
let lhs_path = temp_dir.path().join("lhs.ir");
std::fs::write(&lhs_path, lhs_ir).unwrap();
let rhs_path = temp_dir.path().join("rhs.ir");
std::fs::write(&rhs_path, rhs_ir).unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = "[toolchain]\n".to_string();
let toolchain_toml_contents_with_path = add_tool_path_value(&toolchain_toml_contents);
std::fs::write(&toolchain_toml_path, toolchain_toml_contents_with_path).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(command_path)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("ir-equiv")
.arg(lhs_path.to_str().unwrap())
.arg(rhs_path.to_str().unwrap())
.arg("--top")
.arg("my_main")
.arg(format!("--solver={}", solver))
.arg("--lhs_fixed_implicit_activation=true")
.output()
.expect("xlsynth-driver should succeed");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
log::info!("stdout: {}", stdout);
log::info!("stderr: {}", stderr);
assert!(
output.status.success(),
"Solver ir-equiv should succeed; \nstdout:\n {}\nstderr:\n {}",
stdout,
stderr
);
assert!(
stdout.contains("Solver proved equivalence"),
"stdout:\n {}\nstderr:\n {}",
stdout,
stderr
);
}
#[allow(dead_code)]
fn test_irequiv_subcommand_solver_different_top_entry_points(solver: &str) {
let _ = env_logger::try_init();
let temp_dir = tempfile::tempdir().unwrap();
let lhs_ir = "package add_then_sub\nfn lhs_main(x: bits[32]) -> bits[32] {\n add.2: bits[32] = add(x, x)\n ret sub.3: bits[32] = sub(add.2, x)\n}";
let rhs_ir = "package identity\nfn rhs_main(x: bits[32]) -> bits[32] {\n ret identity.2: bits[32] = identity(x)\n}";
let lhs_path = temp_dir.path().join("lhs.ir");
std::fs::write(&lhs_path, lhs_ir).unwrap();
let rhs_path = temp_dir.path().join("rhs.ir");
std::fs::write(&rhs_path, rhs_ir).unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = "[toolchain]\n".to_string();
std::fs::write(&toolchain_toml_path, toolchain_toml_contents).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(command_path)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("ir-equiv")
.arg(format!("--solver={}", solver))
.arg(lhs_path.to_str().unwrap())
.arg(rhs_path.to_str().unwrap())
.arg("--lhs_ir_top")
.arg("lhs_main")
.arg("--rhs_ir_top")
.arg("rhs_main")
.output()
.expect("xlsynth-driver should succeed");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
log::info!("stdout: {}", stdout);
log::info!("stderr: {}", stderr);
assert!(output.status.success());
assert!(stdout.contains("Solver proved equivalence"));
}
#[allow(dead_code)]
fn test_irequiv_subcommand_solver_output_bits_strategy(solver: &str) {
let _ = env_logger::try_init();
let temp_dir = tempfile::tempdir().unwrap();
let lhs_dslx = r#"import std;
pub fn main(x: u8, y: u8) -> (u8, u8) { (x / y, x % y) }"#;
let rhs_dslx = r#"import std;
pub fn main(x: u8, y: u8) -> (u8, u8) { if y == u8:0 { (all_ones!<u8>(), zero!<u8>()) } else { std::iterative_div_mod<u32:8, u32:8>(x, y) } }"#;
let lhs_x = temp_dir.path().join("lhs.x");
let rhs_x = temp_dir.path().join("rhs.x");
std::fs::write(&lhs_x, lhs_dslx).unwrap();
std::fs::write(&rhs_x, rhs_dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let lhs_ir = temp_dir.path().join("lhs.ir");
let out = Command::new(command_path)
.arg("dslx2ir")
.arg("--dslx_input_file")
.arg(lhs_x.to_str().unwrap())
.arg("--dslx_top")
.arg("main")
.arg("--opt=true")
.output()
.unwrap();
assert!(out.status.success());
std::fs::write(&lhs_ir, &out.stdout).unwrap();
let rhs_ir = temp_dir.path().join("rhs.ir");
let out = Command::new(command_path)
.arg("dslx2ir")
.arg("--dslx_input_file")
.arg(rhs_x.to_str().unwrap())
.arg("--dslx_top")
.arg("main")
.arg("--opt=true")
.output()
.unwrap();
assert!(
out.status.success(),
"dslx2ir failed: {}",
String::from_utf8_lossy(&out.stderr)
);
std::fs::write(&rhs_ir, &out.stdout).unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml_path, toolchain_toml_contents).unwrap();
let output_single = Command::new(command_path)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("ir-equiv")
.arg(format!("--solver={}", solver))
.arg(lhs_ir.to_str().unwrap())
.arg(rhs_ir.to_str().unwrap())
.output()
.expect("xlsynth-driver should succeed (single-threaded)");
let stdout_single = String::from_utf8_lossy(&output_single.stdout);
let stderr_single = String::from_utf8_lossy(&output_single.stderr);
log::info!("stdout (single-threaded): {}", stdout_single);
log::info!("stderr (single-threaded): {}", stderr_single);
assert!(output_single.status.success());
assert!(stdout_single.contains("Solver proved equivalence"));
let output = Command::new(command_path)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("ir-equiv")
.arg(format!("--solver={}", solver))
.arg(lhs_ir.to_str().unwrap())
.arg(rhs_ir.to_str().unwrap())
.arg("--parallelism-strategy=output-bits")
.output()
.expect("xlsynth-driver should succeed");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
log::info!("stdout: {}", stdout);
log::info!("stderr: {}", stderr);
println!("stdout: {}", stdout);
println!("stderr: {}", stderr);
println!("status: {}", output.status);
assert!(output.status.success());
assert!(stdout.contains("Solver proved equivalence"));
}
#[test]
fn test_ir_strip_pos_data_subcommand() {
let _ = env_logger::try_init();
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("with_pos.ir");
let ir = "package pos_pkg\nfile_number 0 \"foo.x\"\n\n\
top fn main() -> bits[32] {\n ret literal.1: bits[32] = literal(value=1, id=1, pos=[(0,0,0)])\n}\n";
std::fs::write(&ir_path, ir).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(command_path)
.arg("ir-strip-pos-data")
.arg(ir_path.to_str().unwrap())
.output()
.unwrap();
assert!(
output.status.success(),
"ir-strip-pos-data failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let got = String::from_utf8_lossy(&output.stdout);
let want = "package pos_pkg\n\n\
top fn main() -> bits[32] {\n ret literal.1: bits[32] = literal(value=1, id=1)\n}\n";
assert_eq!(got, want);
}
macro_rules! test_irequiv_subcommand_solver_base {
($solver:ident, $feature:expr, $choice:expr) => {
paste::paste! {
#[cfg(feature = $feature)]
#[test]
fn [<test_irequiv_subcommand_ $solver _different_top_entry_points>]() {
test_irequiv_subcommand_solver_different_top_entry_points($choice);
}
#[cfg(feature = $feature)]
#[test]
fn [<test_irequiv_subcommand_ $solver _equivalent>]() {
test_irequiv_subcommand_solver_equivalent($choice);
}
#[cfg(feature = $feature)]
#[test]
fn [<test_irequiv_subcommand_ $solver _output_bits_strategy>]() {
test_irequiv_subcommand_solver_output_bits_strategy($choice);
}
}
};
}
macro_rules! test_irequiv_subcommand_solver {
($solver:ident, $feature:expr, $choice:expr, true) => {
paste::paste! {
test_irequiv_subcommand_solver_base!($solver, $feature, $choice);
#[cfg(feature = $feature)]
#[test]
fn [<test_irequiv_subcommand_ $solver _equivalent_with_fixed_implicit_activation>]() {
test_irequiv_subcommand_solver_equivalent_with_fixed_implicit_activation($choice);
}
}
};
($solver:ident, $feature:expr, $choice:expr, false) => {
test_irequiv_subcommand_solver_base!($solver, $feature, $choice);
};
}
test_irequiv_subcommand_solver!(boolector, "has-boolector", "boolector", true);
test_irequiv_subcommand_solver!(bitwuzla, "has-bitwuzla", "bitwuzla", true);
test_irequiv_subcommand_solver!(
boolector_binary,
"with-boolector-binary-test",
"boolector-binary",
true
);
test_irequiv_subcommand_solver!(z3_binary, "with-z3-binary-test", "z3-binary", true);
test_irequiv_subcommand_solver!(
bitwuzla_binary,
"with-bitwuzla-binary-test",
"bitwuzla-binary",
true
);
#[test_case(true; "with_tool_path")]
#[test_case(false; "without_tool_path")]
fn test_toolchain_common_codegen_flags_resolve(use_tool_path: bool) {
let _ = env_logger::builder().is_test(true).try_init();
log::info!("test_toolchain_common_codegen_flags_resolve");
let temp_dir = tempfile::tempdir().unwrap();
let toolchain_toml = temp_dir.path().join("xlsynth-toolchain.toml");
let mut toolchain_toml_contents = "[toolchain]\n\n[toolchain.codegen]\n".to_string();
toolchain_toml_contents +=
"gate_format = \"br_gate_buf gated_{output}(.in({input}), .out({output}))\"\n";
toolchain_toml_contents += "assert_format = \"`BR_ASSERT({label}, {condition})\"\n";
toolchain_toml_contents += "use_system_verilog = true\n";
let toolchain_toml_contents = if use_tool_path {
add_tool_path_value(&toolchain_toml_contents)
} else {
toolchain_toml_contents
};
std::fs::write(&toolchain_toml, toolchain_toml_contents).unwrap();
let dslx = r#"
fn main(pred: bool, x: u1) -> u1 {
assert!(x == u1:1, "should_be_one");
let gated = gate!(pred, x);
gated
}
"#;
let dslx_path = temp_dir.path().join("test.x");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let mut command = std::process::Command::new(command_path);
command
.arg("--toolchain")
.arg(toolchain_toml.to_str().unwrap())
.arg("dslx2pipeline")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("main")
.arg("--pipeline_stages")
.arg("1")
.arg("--flop_inputs=true")
.arg("--flop_outputs=true")
.arg("--delay_model")
.arg("unit");
if let Ok(rust_log) = std::env::var("RUST_LOG") {
command.env("RUST_LOG", rust_log);
}
let output = command.output().unwrap();
println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
if !use_tool_path {
compare_golden_sv(
&stdout,
"tests/test_toolchain_common_codegen_flags_resolve.golden.sv",
);
}
}
#[test_case(true, true; "with_tool_path_opt")]
#[test_case(true, false; "with_tool_path_noopt")]
#[test_case(false, true; "without_tool_path_opt")]
#[test_case(false, false; "without_tool_path_noopt")]
fn test_ir2pipeline_subcommand(use_tool_path: bool, optimize: bool) {
let _ = env_logger::builder().is_test(true).try_init();
let ir_text = "package sample\n\ntop fn my_main(x: bits[32] id=1) -> bits[32] {\n ret x: bits[32] = param(name=x, id=1)\n}\n";
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("sample.ir");
std::fs::write(&ir_path, ir_text).unwrap();
let toolchain_path = temp_dir.path().join("xlsynth-toolchain.toml");
let mut toolchain_toml_contents = "[toolchain]\n".to_string();
if use_tool_path {
toolchain_toml_contents = add_tool_path_value(&toolchain_toml_contents);
}
std::fs::write(&toolchain_path, toolchain_toml_contents).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let mut cmd = Command::new(command_path);
cmd.arg("--toolchain").arg(toolchain_path.to_str().unwrap());
cmd.arg("ir2pipeline")
.arg(ir_path.to_str().unwrap())
.arg("--pipeline_stages")
.arg("1")
.arg("--top")
.arg("my_main")
.arg("--delay_model")
.arg("unit")
.arg("--flop_inputs=false")
.arg("--flop_outputs=false");
if optimize {
cmd.arg("--opt=true");
}
if let Ok(rust_log) = std::env::var("RUST_LOG") {
cmd.env("RUST_LOG", rust_log);
}
let output = cmd.output().unwrap();
assert!(
output.status.success(),
"ir2pipeline failed:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
compare_golden_sv(
&stdout,
"tests/test_ir2pipeline_identity_pipeline.golden.sv",
);
}
#[test]
fn test_ir2g8r_emits_all_outputs() {
let _ = env_logger::try_init();
let dslx = "fn main(a: u4, b: u4) -> u4 { a & b }";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("main.x");
let ir_path = temp_dir.path().join("main.ir");
let g8rbin_path = temp_dir.path().join("main.g8rbin");
let stats_path = temp_dir.path().join("main.stats.json");
let ugv_path = temp_dir.path().join("main.ugv");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let dslx2ir_output = std::process::Command::new(command_path)
.arg("dslx2ir")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("main")
.output()
.unwrap();
assert!(
dslx2ir_output.status.success(),
"dslx2ir failed: {}",
String::from_utf8_lossy(&dslx2ir_output.stderr)
);
std::fs::write(&ir_path, &dslx2ir_output.stdout).unwrap();
let ir2g8r_output = std::process::Command::new(command_path)
.arg("ir2g8r")
.arg(ir_path.to_str().unwrap())
.arg("--fold=true")
.arg("--hash=true")
.arg("--fraig=true")
.arg("--bin-out")
.arg(g8rbin_path.to_str().unwrap())
.arg("--stats-out")
.arg(stats_path.to_str().unwrap())
.arg("--netlist-out")
.arg(ugv_path.to_str().unwrap())
.output()
.unwrap();
assert!(
ir2g8r_output.status.success(),
"ir2g8r failed: {}",
String::from_utf8_lossy(&ir2g8r_output.stderr)
);
let stdout = String::from_utf8_lossy(&ir2g8r_output.stdout);
assert!(
stdout.contains("fn __main__main("),
"stdout did not contain pretty GateFn: {}",
stdout
);
let g8rbin_data = std::fs::read(&g8rbin_path).expect(".g8rbin file not found");
assert!(!g8rbin_data.is_empty(), ".g8rbin file is empty");
let stats_json = std::fs::read_to_string(&stats_path).expect("stats JSON file not found");
let stats: serde_json::Value =
serde_json::from_str(&stats_json).expect("stats JSON not valid JSON");
assert!(
stats.get("live_nodes").is_some(),
"stats missing live_nodes"
);
assert!(
stats.get("deepest_path").is_some(),
"stats missing deepest_path"
);
assert!(
stats.get("fanout_histogram").is_some(),
"stats missing fanout_histogram"
);
let ugv_data = std::fs::read(&ugv_path).expect(".ugv file not found");
assert!(!ugv_data.is_empty(), ".ugv file is empty");
}
#[test]
fn test_g8r2ir_basic_ir_output() {
let _ = env_logger::builder().is_test(true).try_init();
let mut g8_builder = GateBuilder::new("testmod".to_string(), GateBuilderOptions::no_opt());
let a_val = g8_builder.add_input("a".to_string(), 1);
let y_val = g8_builder.add_and_binary(*a_val.get_lsb(0), *a_val.get_lsb(0));
g8_builder.add_output("y".to_string(), AigBitVector::from_bit(y_val));
let gate_fn = g8_builder.build();
let temp_dir = tempfile::tempdir().unwrap();
let g8r_path = temp_dir.path().join("testmod.g8r");
std::fs::write(&g8r_path, gate_fn.to_string()).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("g8r2ir")
.arg(g8r_path.to_str().unwrap())
.output()
.unwrap();
assert!(
output.status.success(),
"g8r2ir failed:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let ir_text = String::from_utf8_lossy(&output.stdout).to_string();
let golden_rel = "tests/test_g8r2ir_basic_ir_output.golden.ir";
let golden_dir = std::path::Path::new(golden_rel).parent().unwrap();
let _ = std::fs::create_dir_all(golden_dir);
compare_golden_text(&ir_text, golden_rel);
}
#[test]
fn test_g8r2ir_preserves_scalar_multi_output_order() {
let _ = env_logger::builder().is_test(true).try_init();
let mut g8_builder = GateBuilder::new("testmod".to_string(), GateBuilderOptions::no_opt());
let a_val = g8_builder.add_input("a".to_string(), 1);
let b_val = g8_builder.add_input("b".to_string(), 1);
g8_builder.add_output("o0".to_string(), AigBitVector::from_bit(*a_val.get_lsb(0)));
g8_builder.add_output("o1".to_string(), AigBitVector::from_bit(*b_val.get_lsb(0)));
let gate_fn = g8_builder.build();
let temp_dir = tempfile::tempdir().unwrap();
let g8r_path = temp_dir.path().join("testmod_multi_output.g8r");
std::fs::write(&g8r_path, gate_fn.to_string()).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("g8r2ir")
.arg(g8r_path.to_str().unwrap())
.output()
.unwrap();
assert!(
output.status.success(),
"g8r2ir failed:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let ir_text = String::from_utf8_lossy(&output.stdout);
assert!(
ir_text.contains("tuple(a, b, id="),
"expected scalar outputs to preserve order in tuple(a, b); got:\n{}",
ir_text
);
assert!(
!ir_text.contains("tuple(b, a, id="),
"scalar multi-output order was reversed unexpectedly:\n{}",
ir_text
);
}
#[test]
fn test_aig2ir_requires_fn_type_and_suggests_naive_type() {
let _ = env_logger::builder().is_test(true).try_init();
let mut g8_builder = GateBuilder::new("testmod".to_string(), GateBuilderOptions::no_opt());
let a_val = g8_builder.add_input("a".to_string(), 1);
let b_val = g8_builder.add_input("b".to_string(), 1);
g8_builder.add_output("o0".to_string(), AigBitVector::from_bit(*a_val.get_lsb(0)));
g8_builder.add_output("o1".to_string(), AigBitVector::from_bit(*b_val.get_lsb(0)));
let gate_fn = g8_builder.build();
let temp_dir = tempfile::tempdir().unwrap();
let aag_path = write_aiger_file(&temp_dir, "native_multi_output.aag", &gate_fn);
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("aig2ir")
.arg(aag_path.to_str().unwrap())
.output()
.unwrap();
assert!(
!output.status.success(),
"expected aig2ir to require --fn-type; stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert!(
String::from_utf8_lossy(&output.stderr).contains("--fn-type is required"),
"expected missing --fn-type message, got: {}",
String::from_utf8_lossy(&output.stderr)
);
assert!(
String::from_utf8_lossy(&output.stderr)
.contains("naive type suggestion: `(bits[1], bits[1]) -> (bits[1], bits[1])`"),
"expected naive type suggestion in stderr, got: {}",
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn test_aig2ir_with_fn_type_lifts_structured_array_signature() {
let _ = env_logger::builder().is_test(true).try_init();
let ir_text = r#"package sample
top fn main(x: bits[2][3] id=1) -> bits[2][3] {
ret identity.2: bits[2][3] = identity(x, id=2)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("structured_array.ir");
let aag_path = temp_dir.path().join("structured_array.aag");
std::fs::write(&ir_path, ir_text).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let ir2g8r_output = Command::new(command_path)
.arg("ir2g8r")
.arg(ir_path.to_str().unwrap())
.arg("--top")
.arg("main")
.arg("--aiger-out")
.arg(aag_path.to_str().unwrap())
.output()
.unwrap();
assert!(
ir2g8r_output.status.success(),
"ir2g8r failed: stdout: {} stderr: {}",
String::from_utf8_lossy(&ir2g8r_output.stdout),
String::from_utf8_lossy(&ir2g8r_output.stderr)
);
let output = Command::new(command_path)
.arg("aig2ir")
.arg(aag_path.to_str().unwrap())
.arg("--fn-type")
.arg("(bits[2][3]) -> bits[2][3]")
.output()
.unwrap();
assert!(
output.status.success(),
"aig2ir with --fn-type failed:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let lifted_ir = String::from_utf8_lossy(&output.stdout);
assert!(lifted_ir.contains("arg0: bits[2][3]"));
assert!(lifted_ir.contains("-> bits[2][3]"));
}
#[test]
fn test_aig2ir_with_fn_type_accepts_zero_width_top_level_param() {
let _ = env_logger::builder().is_test(true).try_init();
let ir_text = r#"package sample
top fn main(x: () id=1, y: bits[1] id=2) -> bits[1] {
ret identity.3: bits[1] = identity(y, id=3)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("unit_param.ir");
let aag_path = temp_dir.path().join("unit_param.aag");
std::fs::write(&ir_path, ir_text).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let ir2g8r_output = Command::new(command_path)
.arg("ir2g8r")
.arg(ir_path.to_str().unwrap())
.arg("--top")
.arg("main")
.arg("--aiger-out")
.arg(aag_path.to_str().unwrap())
.output()
.unwrap();
assert!(
ir2g8r_output.status.success(),
"ir2g8r failed: stdout: {} stderr: {}",
String::from_utf8_lossy(&ir2g8r_output.stdout),
String::from_utf8_lossy(&ir2g8r_output.stderr)
);
let output = Command::new(command_path)
.arg("aig2ir")
.arg(aag_path.to_str().unwrap())
.arg("--fn-type")
.arg("((), bits[1]) -> bits[1]")
.output()
.unwrap();
assert!(
output.status.success(),
"aig2ir with zero-width param failed:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let lifted_ir = String::from_utf8_lossy(&output.stdout);
assert!(lifted_ir.contains("arg0: ()"));
assert!(lifted_ir.contains("arg1: bits[1]"));
assert!(lifted_ir.contains("-> bits[1]"));
}
#[test]
fn test_aig2ir_reports_fn_type_mismatch() {
let _ = env_logger::builder().is_test(true).try_init();
let lhs_gate = two_input_gate_fn("main", |a, b, gb| gb.add_and_binary(a, b));
let temp_dir = tempfile::tempdir().unwrap();
let lhs_aag = write_aiger_file(&temp_dir, "lhs_sig.aag", &lhs_gate);
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("aig2ir")
.arg(lhs_aag.to_str().unwrap())
.arg("--fn-type")
.arg("(bits[3]) -> bits[1]")
.output()
.unwrap();
assert!(
!output.status.success(),
"expected aig2ir fn-type mismatch to fail; stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert!(
String::from_utf8_lossy(&output.stderr).contains("mismatch"),
"expected mismatch error in stderr, got: {}",
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn test_aig2ir_accepts_binary_aiger() {
let _ = env_logger::builder().is_test(true).try_init();
let lhs_gate = two_input_gate_fn("main", |a, b, gb| gb.add_and_binary(a, b));
let temp_dir = tempfile::tempdir().unwrap();
let lhs_aig = write_aiger_binary_file(&temp_dir, "lhs_sig.aig", &lhs_gate);
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("aig2ir")
.arg(lhs_aig.to_str().unwrap())
.arg("--fn-type")
.arg("(bits[1], bits[1]) -> bits[1]")
.output()
.unwrap();
assert!(
output.status.success(),
"aig2ir failed on binary AIGER with --fn-type: stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert!(String::from_utf8_lossy(&output.stdout).contains("-> bits[1]"));
}
#[test]
fn test_aig2ir_accepts_unit_signature_for_empty_aiger() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let aag_path = temp_dir.path().join("empty.aag");
std::fs::write(&aag_path, "aag 0 0 0 0 0\nc\n").unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("aig2ir")
.arg(aag_path.to_str().unwrap())
.arg("--fn-type")
.arg("() -> ()")
.output()
.unwrap();
assert!(
output.status.success(),
"aig2ir failed on empty unit-signature AIGER: stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert!(
String::from_utf8_lossy(&output.stdout).contains("-> ()"),
"expected lifted unit return type in stdout, got: {}",
String::from_utf8_lossy(&output.stdout)
);
}
#[test]
fn test_g8r_area_table_reports_weighted_area_by_pir_node() {
let _ = env_logger::builder().is_test(true).try_init();
let mut builder = GateBuilder::new("testmod".to_string(), GateBuilderOptions::no_opt());
let a = builder.add_input("a".to_string(), 1);
let b = builder.add_input("b".to_string(), 1);
builder.set_current_pir_node_id(Some(3));
let add_like = builder.add_and_binary(*a.get_lsb(0), *b.get_lsb(0));
builder.set_current_pir_node_id(Some(4));
let shared = builder.add_and_binary(*a.get_lsb(0), *a.get_lsb(0));
builder.add_pir_node_id(shared.node, 3);
builder.set_current_pir_node_id(None);
let unattributed = builder.add_and_binary(*b.get_lsb(0), *b.get_lsb(0));
builder.set_current_pir_node_id(Some(99));
let missing = builder.add_and_binary(*a.get_lsb(0), *b.get_lsb(0));
builder.set_current_pir_node_id(None);
builder.add_output("o0".to_string(), AigBitVector::from_bit(add_like));
builder.add_output("o1".to_string(), AigBitVector::from_bit(shared));
builder.add_output("o2".to_string(), AigBitVector::from_bit(unattributed));
builder.add_output("o3".to_string(), AigBitVector::from_bit(missing));
let gate_fn = builder.build();
let ir_text = r#"package sample
top fn f(a: bits[1] id=1, b: bits[1] id=2) -> bits[1] {
add.3: bits[1] = add(a, b, id=3, pos=[(0,1,2)])
ret not.4: bits[1] = not(add.3, id=4, pos=[(0,3,4)])
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let g8r_path = temp_dir.path().join("testmod.g8rbin");
let ir_path = temp_dir.path().join("sample.ir");
std::fs::write(&g8r_path, bincode::serialize(&gate_fn).unwrap()).unwrap();
std::fs::write(&ir_path, ir_text).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("g8r-area-table")
.arg(g8r_path.to_str().unwrap())
.arg(ir_path.to_str().unwrap())
.output()
.unwrap();
assert!(
output.status.success(),
"g8r-area-table failed:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
compare_golden_text(&stdout, "tests/test_g8r_table.golden.txt");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("PIR node id 99"),
"expected missing-id warning in stderr, got:\n{}",
stderr
);
assert!(
stderr.contains("unattributed row"),
"expected unattributed-row warning context in stderr, got:\n{}",
stderr
);
}
#[test]
fn test_g8r_area_table_can_group_by_opcode() {
let _ = env_logger::builder().is_test(true).try_init();
let mut builder = GateBuilder::new("testmod".to_string(), GateBuilderOptions::no_opt());
let a = builder.add_input("a".to_string(), 1);
let b = builder.add_input("b".to_string(), 1);
builder.set_current_pir_node_id(Some(3));
let add_like = builder.add_and_binary(*a.get_lsb(0), *b.get_lsb(0));
builder.set_current_pir_node_id(Some(4));
let shared = builder.add_and_binary(*a.get_lsb(0), *a.get_lsb(0));
builder.add_pir_node_id(shared.node, 3);
builder.set_current_pir_node_id(None);
let unattributed = builder.add_and_binary(*b.get_lsb(0), *b.get_lsb(0));
builder.set_current_pir_node_id(Some(99));
let missing = builder.add_and_binary(*a.get_lsb(0), *b.get_lsb(0));
builder.set_current_pir_node_id(None);
builder.add_output("o0".to_string(), AigBitVector::from_bit(add_like));
builder.add_output("o1".to_string(), AigBitVector::from_bit(shared));
builder.add_output("o2".to_string(), AigBitVector::from_bit(unattributed));
builder.add_output("o3".to_string(), AigBitVector::from_bit(missing));
let gate_fn = builder.build();
let ir_text = r#"package sample
top fn f(a: bits[1] id=1, b: bits[1] id=2) -> bits[1] {
add.3: bits[1] = add(a, b, id=3, pos=[(0,1,2)])
ret not.4: bits[1] = not(add.3, id=4, pos=[(0,3,4)])
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let g8r_path = temp_dir.path().join("testmod.g8rbin");
let ir_path = temp_dir.path().join("sample.ir");
std::fs::write(&g8r_path, bincode::serialize(&gate_fn).unwrap()).unwrap();
std::fs::write(&ir_path, ir_text).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("g8r-area-table")
.arg(g8r_path.to_str().unwrap())
.arg(ir_path.to_str().unwrap())
.arg("--group-by-opcode")
.output()
.unwrap();
assert!(
output.status.success(),
"g8r-area-table --group-by-opcode failed:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
compare_golden_text(&stdout, "tests/test_g8r_table_group_by_opcode.golden.txt");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("PIR node id 99"),
"expected missing-id warning in stderr, got:\n{}",
stderr
);
assert!(
stderr.contains("unattributed row"),
"expected unattributed-row warning context in stderr, got:\n{}",
stderr
);
}
#[test]
fn test_g8r_critical_path_table_reports_only_level_critical_nodes() {
let _ = env_logger::builder().is_test(true).try_init();
let mut builder = GateBuilder::new("testmod".to_string(), GateBuilderOptions::no_opt());
let a = builder.add_input("a".to_string(), 1);
let b = builder.add_input("b".to_string(), 1);
builder.set_current_pir_node_id(Some(3));
let crit0 = builder.add_and_binary(*a.get_lsb(0), *b.get_lsb(0));
builder.set_current_pir_node_id(Some(4));
let crit1 = builder.add_and_binary(crit0, *a.get_lsb(0));
builder.add_pir_node_id(crit1.node, 3);
builder.set_current_pir_node_id(Some(5));
let crit2 = builder.add_and_binary(crit1, *b.get_lsb(0));
builder.add_pir_node_id(crit2.node, 99);
builder.set_current_pir_node_id(Some(6));
let non_critical0 = builder.add_and_binary(*a.get_lsb(0), *a.get_lsb(0));
let non_critical1 = builder.add_and_binary(non_critical0, *b.get_lsb(0));
builder.set_current_pir_node_id(None);
builder.add_output("o0".to_string(), AigBitVector::from_bit(crit2));
builder.add_output("o1".to_string(), AigBitVector::from_bit(non_critical1));
let gate_fn = builder.build();
let ir_text = r#"package sample
top fn f(a: bits[1] id=1, b: bits[1] id=2) -> bits[1] {
add.3: bits[1] = add(a, b, id=3)
not.4: bits[1] = not(add.3, id=4)
and.5: bits[1] = and(not.4, b, id=5)
ret xor.6: bits[1] = xor(and.5, a, id=6)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let g8r_path = temp_dir.path().join("testmod.g8rbin");
let ir_path = temp_dir.path().join("sample.ir");
std::fs::write(&g8r_path, bincode::serialize(&gate_fn).unwrap()).unwrap();
std::fs::write(&ir_path, ir_text).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("g8r-critical-path-table")
.arg(g8r_path.to_str().unwrap())
.arg(ir_path.to_str().unwrap())
.output()
.unwrap();
assert!(
output.status.success(),
"g8r-critical-path-table failed:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
compare_golden_text(&stdout, "tests/test_g8r_critical_path_table.golden.txt");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("PIR node id 99"),
"expected missing-id warning in stderr, got:\n{}",
stderr
);
assert!(
stderr.contains("unattributed row"),
"expected unattributed-row warning context in stderr, got:\n{}",
stderr
);
}
#[test]
fn test_g8r_critical_path_table_can_group_by_opcode() {
let _ = env_logger::builder().is_test(true).try_init();
let mut builder = GateBuilder::new("testmod".to_string(), GateBuilderOptions::no_opt());
let a = builder.add_input("a".to_string(), 1);
let b = builder.add_input("b".to_string(), 1);
builder.set_current_pir_node_id(Some(3));
let crit0 = builder.add_and_binary(*a.get_lsb(0), *b.get_lsb(0));
builder.set_current_pir_node_id(Some(4));
let crit1 = builder.add_and_binary(crit0, *a.get_lsb(0));
builder.add_pir_node_id(crit1.node, 3);
builder.set_current_pir_node_id(Some(5));
let crit2 = builder.add_and_binary(crit1, *b.get_lsb(0));
builder.add_pir_node_id(crit2.node, 99);
builder.set_current_pir_node_id(Some(6));
let non_critical0 = builder.add_and_binary(*a.get_lsb(0), *a.get_lsb(0));
let non_critical1 = builder.add_and_binary(non_critical0, *b.get_lsb(0));
builder.set_current_pir_node_id(None);
builder.add_output("o0".to_string(), AigBitVector::from_bit(crit2));
builder.add_output("o1".to_string(), AigBitVector::from_bit(non_critical1));
let gate_fn = builder.build();
let ir_text = r#"package sample
top fn f(a: bits[1] id=1, b: bits[1] id=2) -> bits[1] {
add.3: bits[1] = add(a, b, id=3)
not.4: bits[1] = not(add.3, id=4)
and.5: bits[1] = and(not.4, b, id=5)
ret xor.6: bits[1] = xor(and.5, a, id=6)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let g8r_path = temp_dir.path().join("testmod.g8rbin");
let ir_path = temp_dir.path().join("sample.ir");
std::fs::write(&g8r_path, bincode::serialize(&gate_fn).unwrap()).unwrap();
std::fs::write(&ir_path, ir_text).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("g8r-critical-path-table")
.arg(g8r_path.to_str().unwrap())
.arg(ir_path.to_str().unwrap())
.arg("--group-by-opcode")
.output()
.unwrap();
assert!(
output.status.success(),
"g8r-critical-path-table --group-by-opcode failed:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
compare_golden_text(
&stdout,
"tests/test_g8r_critical_path_table_group_by_opcode.golden.txt",
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("PIR node id 99"),
"expected missing-id warning in stderr, got:\n{}",
stderr
);
assert!(
stderr.contains("unattributed row"),
"expected unattributed-row warning context in stderr, got:\n{}",
stderr
);
}
#[test]
fn test_aig2v_ascii_comb_module_name() {
let mut g8_builder = GateBuilder::new("source_name".to_string(), GateBuilderOptions::no_opt());
let a_val = g8_builder.add_input("a".to_string(), 1);
let y_val = g8_builder.add_and_binary(*a_val.get_lsb(0), *a_val.get_lsb(0));
g8_builder.add_output("y".to_string(), AigBitVector::from_bit(y_val));
let gate_fn = g8_builder.build();
let temp_dir = tempfile::tempdir().unwrap();
let aag_path = write_aiger_file(&temp_dir, "source_name.aag", &gate_fn);
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("aig2v")
.arg(aag_path.to_str().unwrap())
.arg("--module-name")
.arg("newmod")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let netlist = String::from_utf8_lossy(&output.stdout);
let expected_netlist = "module newmod(\n input wire a,\n output wire y\n);\n wire G0;\n wire G2;\n assign G0 = 1'b0;\n assign G2 = a & a;\n assign y = G2;\nendmodule\n\n";
assert_eq!(netlist, expected_netlist);
}
#[test]
fn test_aig2v_preserves_duplicate_output_literals() {
let mut g8_builder = GateBuilder::new("dup_outputs".to_string(), GateBuilderOptions::no_opt());
let a_val = g8_builder.add_input("a".to_string(), 1);
let a_bit = *a_val.get_lsb(0);
g8_builder.add_output("y0".to_string(), AigBitVector::from_bit(a_bit));
g8_builder.add_output("y1".to_string(), AigBitVector::from_bit(a_bit));
let gate_fn = g8_builder.build();
let temp_dir = tempfile::tempdir().unwrap();
let aag_path = write_aiger_file(&temp_dir, "dup_outputs.aag", &gate_fn);
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("aig2v")
.arg(aag_path.to_str().unwrap())
.arg("--module-name")
.arg("dup_outputs")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let netlist = String::from_utf8_lossy(&output.stdout);
let expected_netlist = "module dup_outputs(\n input wire a,\n output wire y0,\n output wire y1\n);\n wire G0;\n assign G0 = 1'b0;\n assign y0 = a;\n assign y1 = a;\nendmodule\n\n";
assert_eq!(netlist, expected_netlist);
}
#[test]
fn test_aig2v_binary_flop_inputs_outputs() {
let _ = env_logger::builder().is_test(true).try_init();
let mut g8_builder = GateBuilder::new("source_inv".to_string(), GateBuilderOptions::no_opt());
let i_val = g8_builder.add_input("i".to_string(), 1);
let o_val = g8_builder.add_not(*i_val.get_lsb(0));
g8_builder.add_output("o".to_string(), AigBitVector::from_bit(o_val));
let gate_fn = g8_builder.build();
let temp_dir = tempfile::tempdir().unwrap();
let aig_path = write_aiger_binary_file(&temp_dir, "source_inv.aig", &gate_fn);
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("aig2v")
.arg(aig_path.to_str().unwrap())
.arg("--module-name")
.arg("my_flop_inv")
.arg("--add-clk-port")
.arg("clk")
.arg("--flop-inputs")
.arg("--flop-outputs")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let netlist = String::from_utf8_lossy(&output.stdout);
let expected_output = "module my_flop_inv(\n input wire clk,\n input wire i,\n output wire o\n);\n reg p0_i;\n wire o_comb;\n reg p0_o;\n wire G0;\n assign G0 = 1'b0;\n assign o_comb = ~p0_i;\n always_ff @ (posedge clk) begin\n p0_i <= i;\n p0_o <= o_comb;\n end\n assign o = p0_o;\nendmodule\n\n";
assert_eq!(netlist, expected_output);
}
#[test]
fn test_aig2v_flop_requires_clk_port_error() {
let _ = env_logger::builder().is_test(true).try_init();
let mut g8_builder = GateBuilder::new("dummy".to_string(), GateBuilderOptions::no_opt());
let i_val = g8_builder.add_input("i".to_string(), 1);
g8_builder.add_output("o".to_string(), AigBitVector::from_bit(*i_val.get_lsb(0)));
let gate_fn = g8_builder.build();
let temp_dir = tempfile::tempdir().unwrap();
let aag_path = write_aiger_file(&temp_dir, "dummy.aag", &gate_fn);
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("aig2v")
.arg(aag_path.to_str().unwrap())
.arg("--module-name")
.arg("dummy")
.arg("--flop-inputs")
.output()
.unwrap();
assert!(!output.status.success(), "Command should fail");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains(
"--add-clk-port <NAME> is required when --flop-inputs or --flop-outputs is used."
),
"Stderr should contain the specific error message. Stderr: {}",
stderr
);
}
#[test]
fn test_aig2v_requires_module_name() {
let mut g8_builder = GateBuilder::new("dummy".to_string(), GateBuilderOptions::no_opt());
let i_val = g8_builder.add_input("i".to_string(), 1);
g8_builder.add_output("o".to_string(), AigBitVector::from_bit(*i_val.get_lsb(0)));
let gate_fn = g8_builder.build();
let temp_dir = tempfile::tempdir().unwrap();
let aag_path = write_aiger_file(&temp_dir, "dummy.aag", &gate_fn);
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("aig2v")
.arg(aag_path.to_str().unwrap())
.output()
.unwrap();
assert!(!output.status.success(), "Command should fail");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("--module-name"),
"Stderr should mention --module-name. Stderr: {}",
stderr
);
}
#[test]
fn test_aig2v_fn_type_emits_packed_ports() {
let mut g8_builder = GateBuilder::new("typed_xor".to_string(), GateBuilderOptions::no_opt());
let lhs = g8_builder.add_input("a".to_string(), 16);
let rhs = g8_builder.add_input("b".to_string(), 16);
let output = g8_builder.add_xor_vec(&lhs, &rhs);
g8_builder.add_output("y".to_string(), output);
let gate_fn = g8_builder.build();
let temp_dir = tempfile::tempdir().unwrap();
let aag_path = write_aiger_file(&temp_dir, "typed_xor.aag", &gate_fn);
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("aig2v")
.arg(aag_path.to_str().unwrap())
.arg("--module-name")
.arg("typed_xor")
.arg("--fn-type")
.arg("(bits[16], bits[16]) -> bits[16]")
.arg("--use-system-verilog")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let netlist = String::from_utf8_lossy(&output.stdout);
assert!(netlist.contains("input wire [15:0] arg0"));
assert!(netlist.contains("input wire [15:0] arg1"));
assert!(netlist.contains("output wire [15:0] output_value"));
assert!(netlist.contains("assign output_value[0] = "));
assert!(netlist.contains("arg0[0]"));
assert!(netlist.contains("arg1[0]"));
assert!(!netlist.contains("arg0_0"));
assert!(!netlist.contains("arg1_0"));
assert!(!netlist.contains("output_value_0"));
}
#[test]
fn test_aig2v_fn_type_reports_input_width_mismatch() {
let mut g8_builder =
GateBuilder::new("typed_bad_input".to_string(), GateBuilderOptions::no_opt());
let lhs = g8_builder.add_input("a".to_string(), 8);
let rhs = g8_builder.add_input("b".to_string(), 8);
let output = g8_builder.add_xor_vec(&lhs, &rhs);
g8_builder.add_output("y".to_string(), output);
let gate_fn = g8_builder.build();
let temp_dir = tempfile::tempdir().unwrap();
let aag_path = write_aiger_file(&temp_dir, "typed_bad_input.aag", &gate_fn);
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("aig2v")
.arg(aag_path.to_str().unwrap())
.arg("--module-name")
.arg("typed_bad_input")
.arg("--fn-type")
.arg("(bits[16], bits[16]) -> bits[8]")
.output()
.unwrap();
assert!(
!output.status.success(),
"expected width mismatch to fail; stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("input width mismatch"),
"expected input width mismatch, got: {}",
stderr
);
}
#[test]
fn test_aig2v_fn_type_reports_output_width_mismatch() {
let mut g8_builder =
GateBuilder::new("typed_bad_output".to_string(), GateBuilderOptions::no_opt());
let lhs = g8_builder.add_input("a".to_string(), 8);
let rhs = g8_builder.add_input("b".to_string(), 8);
let output = g8_builder.add_xor_vec(&lhs, &rhs);
g8_builder.add_output("y".to_string(), output);
let gate_fn = g8_builder.build();
let temp_dir = tempfile::tempdir().unwrap();
let aag_path = write_aiger_file(&temp_dir, "typed_bad_output.aag", &gate_fn);
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("aig2v")
.arg(aag_path.to_str().unwrap())
.arg("--module-name")
.arg("typed_bad_output")
.arg("--fn-type")
.arg("(bits[8], bits[8]) -> bits[16]")
.output()
.unwrap();
assert!(
!output.status.success(),
"expected width mismatch to fail; stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("output width mismatch"),
"expected output width mismatch, got: {}",
stderr
);
}
#[test]
fn test_aig2v_fn_type_rejects_non_bits_types() {
let mut g8_builder = GateBuilder::new("typed_array".to_string(), GateBuilderOptions::no_opt());
let lhs = g8_builder.add_input("a".to_string(), 16);
g8_builder.add_output("y".to_string(), lhs);
let gate_fn = g8_builder.build();
let temp_dir = tempfile::tempdir().unwrap();
let aag_path = write_aiger_file(&temp_dir, "typed_array.aag", &gate_fn);
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("aig2v")
.arg(aag_path.to_str().unwrap())
.arg("--module-name")
.arg("typed_array")
.arg("--fn-type")
.arg("(bits[8][2]) -> bits[16]")
.output()
.unwrap();
assert!(
!output.status.success(),
"expected non-bits type to fail; stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("only top-level bits[N] parameters"),
"expected unsupported type message, got: {}",
stderr
);
}
#[test]
fn test_aig2v_fn_type_rejects_non_bits_return_type() {
let mut g8_builder = GateBuilder::new(
"typed_array_return".to_string(),
GateBuilderOptions::no_opt(),
);
let lhs = g8_builder.add_input("a".to_string(), 16);
g8_builder.add_output("y".to_string(), lhs);
let gate_fn = g8_builder.build();
let temp_dir = tempfile::tempdir().unwrap();
let aag_path = write_aiger_file(&temp_dir, "typed_array_return.aag", &gate_fn);
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("aig2v")
.arg(aag_path.to_str().unwrap())
.arg("--module-name")
.arg("typed_array_return")
.arg("--fn-type")
.arg("(bits[16]) -> bits[8][2]")
.output()
.unwrap();
assert!(
!output.status.success(),
"expected non-bits return type to fail; stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("bits[M] or top-level tuple of bits[M] return"),
"expected unsupported return type message, got: {}",
stderr
);
}
#[test]
fn test_aig2v_fn_type_tuple_return_simulates_against_aig_eval_and_ir_fn_eval() {
let ir = r#"package test
fn f(a: bits[2] id=1, b: bits[3] id=2) -> (bits[3], bits[2]) {
a_ext: bits[3] = zero_ext(a, new_bit_count=3, id=3)
b_low: bits[2] = bit_slice(b, start=0, width=2, id=4)
sum: bits[3] = add(a_ext, b, id=5)
xored: bits[2] = xor(a, b_low, id=6)
ret tuple.7: (bits[3], bits[2]) = tuple(sum, xored, id=7)
}
"#;
let fn_type = "(bits[2], bits[3]) -> (bits[3], bits[2])";
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("tuple_word.ir");
let aag_path = temp_dir.path().join("tuple_word.aag");
std::fs::write(&ir_path, ir).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let ir2g8r_output = Command::new(command_path)
.arg("ir2g8r")
.arg(ir_path.to_str().unwrap())
.arg("--top")
.arg("f")
.arg("--aiger-out")
.arg(aag_path.to_str().unwrap())
.output()
.unwrap();
assert!(
ir2g8r_output.status.success(),
"ir2g8r failed: stdout: {} stderr: {}",
String::from_utf8_lossy(&ir2g8r_output.stdout),
String::from_utf8_lossy(&ir2g8r_output.stderr)
);
let sv_output = Command::new(command_path)
.arg("aig2v")
.arg(aag_path.to_str().unwrap())
.arg("--module-name")
.arg("tuple_word")
.arg("--fn-type")
.arg(fn_type)
.arg("--add-clk-port")
.arg("clk")
.arg("--flop-outputs")
.arg("--use-system-verilog")
.output()
.unwrap();
assert!(
sv_output.status.success(),
"aig2v failed: stdout: {} stderr: {}",
String::from_utf8_lossy(&sv_output.stdout),
String::from_utf8_lossy(&sv_output.stderr)
);
let sv = String::from_utf8(sv_output.stdout).unwrap();
assert_eq!(
sv.matches("module tuple_word(").count(),
1,
"expected exactly one top-level module declaration:\n{sv}"
);
assert!(sv.contains("input wire [1:0] arg0"));
assert!(sv.contains("input wire [2:0] arg1"));
assert!(sv.contains("output wire [2:0] output_value_0"));
assert!(sv.contains("output wire [1:0] output_value_1"));
assert!(sv.contains("wire [2:0] output_value_0_comb"));
assert!(sv.contains("wire [1:0] output_value_1_comb"));
assert!(sv.contains("reg [2:0] p0_output_value_0"));
assert!(sv.contains("reg [1:0] p0_output_value_1"));
assert!(sv.contains("assign output_value_0_comb[0] = "));
assert!(sv.contains("assign output_value_1_comb[0] = "));
assert!(sv.contains("assign output_value_0 = p0_output_value_0"));
assert!(sv.contains("assign output_value_1 = p0_output_value_1"));
let module =
compile_pipeline_module(&sv).expect("vastly should compile tuple-return aig2v output");
let input_vectors = [(0u64, 0u64), (1, 4), (2, 5), (3, 7)];
let cycles = input_vectors
.iter()
.map(|(a_value, b_value)| {
let mut inputs = BTreeMap::new();
inputs.insert(
"arg0".to_string(),
Value4::from_u64(2, Signedness::Unsigned, *a_value),
);
inputs.insert(
"arg1".to_string(),
Value4::from_u64(3, Signedness::Unsigned, *b_value),
);
PipelineCycle { inputs }
})
.collect::<Vec<PipelineCycle>>();
let stimulus = PipelineStimulus {
half_period: 5,
cycles,
};
let outputs = run_pipeline_and_collect_outputs(&module, &stimulus, &module.initial_state_x())
.expect("vastly should simulate tuple-return aig2v output");
for ((a_value, b_value), cycle_outputs) in input_vectors.iter().zip(outputs.iter()) {
let args = format!("(bits[2]:0x{a_value:x}, bits[3]:0x{b_value:x})");
let ir_eval_output = Command::new(command_path)
.arg("ir-fn-eval")
.arg(ir_path.to_str().unwrap())
.arg("f")
.arg(&args)
.output()
.unwrap();
assert!(
ir_eval_output.status.success(),
"ir-fn-eval failed: stdout: {} stderr: {}",
String::from_utf8_lossy(&ir_eval_output.stdout),
String::from_utf8_lossy(&ir_eval_output.stderr)
);
let aig_eval_output = Command::new(command_path)
.arg("aig-eval")
.arg(aag_path.to_str().unwrap())
.arg(&args)
.arg("--fn-type")
.arg(fn_type)
.output()
.unwrap();
assert!(
aig_eval_output.status.success(),
"aig-eval failed: stdout: {} stderr: {}",
String::from_utf8_lossy(&aig_eval_output.stdout),
String::from_utf8_lossy(&aig_eval_output.stderr)
);
assert_eq!(
String::from_utf8_lossy(&aig_eval_output.stdout),
String::from_utf8_lossy(&ir_eval_output.stdout),
"aig-eval and ir-fn-eval should agree for args {args}"
);
let expected_value =
IrValue::parse_typed(String::from_utf8_lossy(&ir_eval_output.stdout).trim())
.expect("ir-fn-eval output should parse as an IrValue");
let expected_elements = expected_value
.get_elements()
.expect("tuple-return eval output should have elements");
assert_eq!(expected_elements.len(), 2);
let expected_output_0_bits = expected_elements[0]
.to_bits()
.expect("first tuple element should be bits");
let expected_output_1_bits = expected_elements[1]
.to_bits()
.expect("second tuple element should be bits");
assert_value4_matches_ir_bits(
cycle_outputs
.get("output_value_0")
.expect("first tuple output should be present"),
&expected_output_0_bits,
);
assert_value4_matches_ir_bits(
cycle_outputs
.get("output_value_1")
.expect("second tuple output should be present"),
&expected_output_1_bits,
);
}
}
#[test]
fn test_aig2v_fn_type_flop_outputs_simulates_with_vastly_like_aig_eval() {
let mut g8_builder =
GateBuilder::new("typed_xor_sim".to_string(), GateBuilderOptions::no_opt());
let lhs = g8_builder.add_input("a".to_string(), 16);
let rhs = g8_builder.add_input("b".to_string(), 16);
let output = g8_builder.add_xor_vec(&lhs, &rhs);
g8_builder.add_output("y".to_string(), output);
let gate_fn = g8_builder.build();
let temp_dir = tempfile::tempdir().unwrap();
let aag_path = write_aiger_file(&temp_dir, "typed_xor_sim.aag", &gate_fn);
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let sv_output = Command::new(command_path)
.arg("aig2v")
.arg(aag_path.to_str().unwrap())
.arg("--module-name")
.arg("typed_xor_sim")
.arg("--fn-type")
.arg("(bits[16], bits[16]) -> bits[16]")
.arg("--add-clk-port")
.arg("clk")
.arg("--flop-outputs")
.arg("--use-system-verilog")
.output()
.unwrap();
assert!(
sv_output.status.success(),
"aig2v failed: stdout: {} stderr: {}",
String::from_utf8_lossy(&sv_output.stdout),
String::from_utf8_lossy(&sv_output.stderr)
);
let sv = String::from_utf8(sv_output.stdout).unwrap();
assert_eq!(
sv.matches("module typed_xor_sim(").count(),
1,
"expected exactly one top-level module declaration:\n{sv}"
);
let module = compile_pipeline_module(&sv).expect("vastly should compile typed aig2v output");
let input_vectors = [
(0x0000u64, 0xffffu64),
(0x1234u64, 0x00ffu64),
(0xabcd, 0x2222),
];
let cycles = input_vectors
.iter()
.map(|(lhs_value, rhs_value)| {
let mut inputs = BTreeMap::new();
inputs.insert(
"arg0".to_string(),
Value4::from_u64(16, Signedness::Unsigned, *lhs_value),
);
inputs.insert(
"arg1".to_string(),
Value4::from_u64(16, Signedness::Unsigned, *rhs_value),
);
PipelineCycle { inputs }
})
.collect::<Vec<PipelineCycle>>();
let stimulus = PipelineStimulus {
half_period: 5,
cycles,
};
let outputs = run_pipeline_and_collect_outputs(&module, &stimulus, &module.initial_state_x())
.expect("vastly should simulate typed aig2v output");
for ((lhs_value, rhs_value), cycle_outputs) in input_vectors.iter().zip(outputs.iter()) {
let eval_output = Command::new(command_path)
.arg("aig-eval")
.arg(aag_path.to_str().unwrap())
.arg(format!(
"(bits[16]:0x{lhs_value:04x}, bits[16]:0x{rhs_value:04x})"
))
.arg("--fn-type")
.arg("(bits[16], bits[16]) -> bits[16]")
.output()
.unwrap();
assert!(
eval_output.status.success(),
"aig-eval failed: stdout: {} stderr: {}",
String::from_utf8_lossy(&eval_output.stdout),
String::from_utf8_lossy(&eval_output.stderr)
);
let expected_value =
IrValue::parse_typed(String::from_utf8_lossy(&eval_output.stdout).trim())
.expect("aig-eval output should parse as an IrValue");
let expected_bits = expected_value
.to_bits()
.expect("typed aig-eval output should be bits");
let actual = cycle_outputs
.get("output_value")
.expect("typed packed output should be present");
assert_value4_matches_ir_bits(actual, &expected_bits);
}
}
#[test]
fn test_g8r2v_add_clk_port_behavior() {
let mut g8_builder = GateBuilder::new("testmod".to_string(), GateBuilderOptions::no_opt());
let a_val = g8_builder.add_input("a".to_string(), 1);
let y_val = g8_builder.add_and_binary(*a_val.get_lsb(0), *a_val.get_lsb(0));
g8_builder.add_output("y".to_string(), AigBitVector::from_bit(y_val));
let gate_fn = g8_builder.build();
let temp_dir = tempfile::tempdir().unwrap();
let g8r_path = temp_dir.path().join("testmod.g8r");
std::fs::write(&g8r_path, gate_fn.to_string()).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("g8r2v")
.arg(g8r_path.to_str().unwrap())
.arg("--add-clk-port")
.arg("clk")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let netlist = String::from_utf8_lossy(&output.stdout);
let expected_netlist = "module testmod(\n input wire clk,\n input wire a,\n output wire y\n);\n wire G0;\n wire G2;\n assign G0 = 1'b0;\n assign G2 = a & a;\n assign y = G2;\nendmodule\n\n";
assert_eq!(netlist, expected_netlist);
}
#[test]
fn test_g8r2v_module_name() {
let mut g8_builder = GateBuilder::new("testmod".to_string(), GateBuilderOptions::no_opt());
let a_val = g8_builder.add_input("a".to_string(), 1);
let y_val = g8_builder.add_and_binary(*a_val.get_lsb(0), *a_val.get_lsb(0));
g8_builder.add_output("y".to_string(), AigBitVector::from_bit(y_val));
let gate_fn = g8_builder.build();
let temp_dir = tempfile::tempdir().unwrap();
let g8r_path = temp_dir.path().join("testmod.g8r");
std::fs::write(&g8r_path, gate_fn.to_string()).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("g8r2v")
.arg(g8r_path.to_str().unwrap())
.output()
.unwrap();
assert!(
output.status.success(),
"g8r2v failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("module testmod("),
"netlist should use default module name: {}",
stdout
);
let output = Command::new(command_path)
.arg("g8r2v")
.arg(g8r_path.to_str().unwrap())
.arg("--module-name=newmod")
.output()
.unwrap();
assert!(
output.status.success(),
"g8r2v failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("module newmod("),
"netlist should use overridden module name: {}",
stdout
);
assert!(
!stdout.contains("module testmod("),
"original module name should not appear when overridden: {}",
stdout
);
}
#[test]
fn test_g8r2v_flop_inputs_outputs() {
let _ = env_logger::builder().is_test(true).try_init();
let mut g8_builder = GateBuilder::new("my_flop_inv".to_string(), GateBuilderOptions::no_opt());
let i_val = g8_builder.add_input("i".to_string(), 1);
let o_val = g8_builder.add_not(*i_val.get_lsb(0));
g8_builder.add_output("o".to_string(), AigBitVector::from_bit(o_val));
let gate_fn = g8_builder.build();
let temp_dir = tempfile::tempdir().unwrap();
let g8r_path = temp_dir.path().join("my_flop_inv.g8r");
std::fs::write(&g8r_path, gate_fn.to_string()).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("g8r2v")
.arg(g8r_path.to_str().unwrap())
.arg("--flop-inputs")
.arg("--flop-outputs")
.arg("--add-clk-port")
.arg("clk")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let netlist = String::from_utf8_lossy(&output.stdout);
let expected_output = "module my_flop_inv(\n input wire clk,\n input wire i,\n output wire o\n);\n reg p0_i;\n wire o_comb;\n reg p0_o;\n wire G0;\n assign G0 = 1'b0;\n assign o_comb = ~p0_i;\n always_ff @ (posedge clk) begin\n p0_i <= i;\n p0_o <= o_comb;\n end\n assign o = p0_o;\nendmodule\n\n";
assert_eq!(netlist, expected_output);
}
#[test]
fn test_g8r2v_flop_inputs_only() {
let _ = env_logger::builder().is_test(true).try_init();
let mut g8_builder =
GateBuilder::new("my_flop_inv_fi".to_string(), GateBuilderOptions::no_opt());
let i_val = g8_builder.add_input("i".to_string(), 1);
let o_val = g8_builder.add_not(*i_val.get_lsb(0));
g8_builder.add_output("o".to_string(), AigBitVector::from_bit(o_val));
let gate_fn = g8_builder.build();
let temp_dir = tempfile::tempdir().unwrap();
let g8r_path = temp_dir.path().join("my_flop_inv_fi.g8r");
std::fs::write(&g8r_path, gate_fn.to_string()).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("g8r2v")
.arg(g8r_path.to_str().unwrap())
.arg("--flop-inputs")
.arg("--add-clk-port")
.arg("clk")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let netlist = String::from_utf8_lossy(&output.stdout);
let expected_netlist = r#"module my_flop_inv_fi(
input wire clk,
input wire i,
output wire o
);
reg p0_i;
wire G0;
assign G0 = 1'b0;
assign o = ~p0_i;
always_ff @ (posedge clk) begin
p0_i <= i;
end
endmodule
"#;
assert_eq!(netlist, expected_netlist);
}
#[test]
fn test_g8r2v_flop_outputs_only() {
let _ = env_logger::builder().is_test(true).try_init();
let mut g8_builder =
GateBuilder::new("my_flop_inv_fo".to_string(), GateBuilderOptions::no_opt());
let i_val = g8_builder.add_input("i".to_string(), 1);
let o_val = g8_builder.add_not(*i_val.get_lsb(0));
g8_builder.add_output("o".to_string(), AigBitVector::from_bit(o_val));
let gate_fn = g8_builder.build();
let temp_dir = tempfile::tempdir().unwrap();
let g8r_path = temp_dir.path().join("my_flop_inv_fo.g8r");
std::fs::write(&g8r_path, gate_fn.to_string()).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("g8r2v")
.arg(g8r_path.to_str().unwrap())
.arg("--flop-outputs")
.arg("--add-clk-port")
.arg("clk")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let netlist = String::from_utf8_lossy(&output.stdout);
let expected_output = "module my_flop_inv_fo(\n input wire clk,\n input wire i,\n output wire o\n);\n wire o_comb;\n reg p0_o;\n wire G0;\n assign G0 = 1'b0;\n assign o_comb = ~i;\n always_ff @ (posedge clk) begin\n p0_o <= o_comb;\n end\n assign o = p0_o;\nendmodule\n\n";
assert_eq!(netlist, expected_output);
}
#[test]
fn test_g8r2v_flop_requires_clk_port_error() {
let _ = env_logger::builder().is_test(true).try_init();
let mut g8_builder = GateBuilder::new("dummy".to_string(), GateBuilderOptions::no_opt());
let i_val = g8_builder.add_input("i".to_string(), 1);
g8_builder.add_output("o".to_string(), AigBitVector::from_bit(*i_val.get_lsb(0)));
let gate_fn = g8_builder.build();
let temp_dir = tempfile::tempdir().unwrap();
let g8r_path = temp_dir.path().join("dummy.g8r");
std::fs::write(&g8r_path, gate_fn.to_string()).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("g8r2v")
.arg(g8r_path.to_str().unwrap())
.arg("--flop-inputs") .output()
.unwrap();
assert!(!output.status.success(), "Command should fail");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains(
"--add-clk-port <NAME> is required when --flop-inputs or --flop-outputs is used."
),
"Stderr should contain the specific error message. Stderr: {}",
stderr
);
}
#[test]
fn test_g8r2v_flop_with_custom_clk_name() {
let _ = env_logger::builder().is_test(true).try_init();
let mut g8_builder = GateBuilder::new(
"my_custom_clk_inv".to_string(),
GateBuilderOptions::no_opt(),
);
let i_val = g8_builder.add_input("i".to_string(), 1);
let o_val = g8_builder.add_not(*i_val.get_lsb(0));
g8_builder.add_output("o".to_string(), AigBitVector::from_bit(o_val));
let gate_fn = g8_builder.build();
let temp_dir = tempfile::tempdir().unwrap();
let g8r_path = temp_dir.path().join("my_custom_clk_inv.g8r");
std::fs::write(&g8r_path, gate_fn.to_string()).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("g8r2v")
.arg(g8r_path.to_str().unwrap())
.arg("--flop-inputs")
.arg("--add-clk-port")
.arg("my_clk") .output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let netlist = String::from_utf8_lossy(&output.stdout);
let expected_module_def = "module my_custom_clk_inv(\n input wire my_clk,\n input wire i,
output wire o
);";
assert!(
netlist.contains(expected_module_def),
"Netlist module definition not as expected. Netlist:\n{}",
netlist
);
let expected_ff_block_sensitivity = "always_ff @ (posedge my_clk)";
assert!(
netlist.contains(expected_ff_block_sensitivity),
"Netlist should use custom clock in always_ff sensitivity list. Netlist:\n{}",
netlist
);
}
#[test]
fn test_g8r2v_use_system_verilog() {
let _ = env_logger::builder().is_test(true).try_init();
let mut g8_builder = GateBuilder::new("my_sv_inv".to_string(), GateBuilderOptions::no_opt());
let i_val = g8_builder.add_input("i".to_string(), 1);
let o_val = g8_builder.add_not(*i_val.get_lsb(0));
g8_builder.add_output("o".to_string(), AigBitVector::from_bit(o_val));
let gate_fn = g8_builder.build();
let temp_dir = tempfile::tempdir().unwrap();
let g8r_path = temp_dir.path().join("my_sv_inv.g8r");
std::fs::write(&g8r_path, gate_fn.to_string()).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("g8r2v")
.arg(g8r_path.to_str().unwrap())
.arg("--use-system-verilog")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let netlist = String::from_utf8_lossy(&output.stdout);
let expected_netlist = r#"module my_sv_inv(
input wire i,
output wire o
);
wire G0;
assign G0 = 1'b0;
assign o = ~i;
endmodule
"#;
assert_eq!(netlist, expected_netlist);
}
#[test_case(true; "with_invariant_assertions")]
#[test_case(false; "without_invariant_assertions")]
fn test_ir2combo_priority_sel_invariant(add_inv: bool) {
let _ = env_logger::builder().is_test(true).try_init();
log::info!("test_ir2combo_priority_sel_invariant (add_inv={})", add_inv);
const PRIO_IR: &str = r#"package priority_sel_test
top fn my_main() -> bits[32] {
literal.1: bits[2] = literal(value=1, id=1)
literal.2: bits[32] = literal(value=11, id=2)
literal.3: bits[32] = literal(value=22, id=3)
literal.4: bits[32] = literal(value=0, id=4)
ret priority_sel.5: bits[32] = priority_sel(literal.1, cases=[literal.2, literal.3], default=literal.4, id=5)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("priority_sel.ir");
std::fs::write(&ir_path, PRIO_IR).unwrap();
let toolchain_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_path, toolchain_toml_contents).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let mut cmd = std::process::Command::new(command_path);
cmd.arg("--toolchain").arg(toolchain_path.to_str().unwrap());
cmd.arg("ir2combo")
.arg(ir_path.to_str().unwrap())
.arg("--top")
.arg("my_main")
.arg("--delay_model")
.arg("unit")
.arg(format!("--add_invariant_assertions={}", add_inv))
.arg("--use_system_verilog=true");
if let Ok(rust_log) = std::env::var("RUST_LOG") {
cmd.env("RUST_LOG", rust_log);
}
let output = cmd.output().unwrap();
assert!(
output.status.success(),
"ir2combo failed:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
log::debug!("ir2combo stdout:\n{}", stdout);
log::debug!(
"ir2combo stderr:\n{}",
String::from_utf8_lossy(&output.stderr)
);
let has_asserts = stdout.to_lowercase().contains("assert");
if add_inv {
assert!(
has_asserts,
"Expected invariant assertions to be present when --add_invariant_assertions=true, but none were found. stdout: {}",
stdout
);
} else {
assert!(
!has_asserts,
"Did not expect invariant assertions when --add_invariant_assertions=false, but some were found. stdout: {}",
stdout
);
}
}
#[test]
fn test_ir2pipeline_tuple_outputs_place_last_element_in_lsb_bits() {
let _ = env_logger::builder().is_test(true).try_init();
if should_skip_if_no_slang() {
return;
}
let ir_text = r#"package sample
top fn main(a: bits[1] id=1, b: bits[2] id=2) -> (bits[1], bits[2]) {
ret tuple.3: (bits[1], bits[2]) = tuple(a, b, id=3)
}
"#;
let output_line = run_ir2pipeline_and_simulate_output(ir_text, "(bits[1]:1, bits[2]:0)");
assert_eq!(output_line, "out: bits[3]:4");
}
#[test]
fn test_ir2pipeline_array_outputs_place_element_zero_in_lsb_bits() {
let _ = env_logger::builder().is_test(true).try_init();
if should_skip_if_no_slang() {
return;
}
let ir_text = r#"package sample
top fn main(a: bits[2] id=1, b: bits[2] id=2, c: bits[2] id=3) -> bits[2][3] {
ret array.4: bits[2][3] = array(a, b, c, id=4)
}
"#;
let output_line =
run_ir2pipeline_and_simulate_output(ir_text, "(bits[2]:1, bits[2]:0, bits[2]:0)");
assert_eq!(output_line, "out: bits[6]:1");
}
#[test]
fn test_ir2pipeline_nested_array_outputs_place_first_leaf_in_lsb_bits() {
let _ = env_logger::builder().is_test(true).try_init();
if should_skip_if_no_slang() {
return;
}
let ir_text = r#"package sample
top fn main(a: bits[1] id=1, b: bits[1] id=2, c: bits[1] id=3, d: bits[1] id=4) -> bits[1][2][2] {
array.5: bits[1][2] = array(a, b, id=5)
array.6: bits[1][2] = array(c, d, id=6)
ret array.7: bits[1][2][2] = array(array.5, array.6, id=7)
}
"#;
let output_line = run_ir2pipeline_and_simulate_output(
ir_text,
"(bits[1]:1, bits[1]:0, bits[1]:0, bits[1]:0)",
);
assert_eq!(output_line, "out: bits[4]:1");
}
#[test]
fn test_ir_fn_to_block_smoke() {
let _ = env_logger::builder().is_test(true).try_init();
const SIMPLE_IR: &str = r#"package simple_pkg
top fn my_main(x: bits[8]) -> bits[8] {
one: bits[8] = literal(value=1, id=1)
ret add.2: bits[8] = add(x, one, id=2)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("simple.ir");
std::fs::write(&ir_path, SIMPLE_IR).unwrap();
let toolchain_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_path, toolchain_toml_contents).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("--toolchain")
.arg(toolchain_path.to_str().unwrap())
.arg("ir-fn-to-block")
.arg(ir_path.to_str().unwrap())
.arg("--top")
.arg("my_main")
.output()
.unwrap();
assert!(
output.status.success(),
"ir-fn-to-block failed:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
xlsynth_test_helpers::compare_golden_text(&stdout, "tests/test_ir_fn_to_block_smoke.golden.ir");
}
#[test]
fn test_ir_fn_to_block_golden() {
let _ = env_logger::builder().is_test(true).try_init();
const SIMPLE_IR: &str = r#"package simple_pkg
top fn my_main(x: bits[8]) -> bits[8] {
one: bits[8] = literal(value=1, id=1)
ret add.2: bits[8] = add(x, one, id=2)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("simple.ir");
std::fs::write(&ir_path, SIMPLE_IR).unwrap();
let toolchain_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_path, toolchain_toml_contents).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("--toolchain")
.arg(toolchain_path.to_str().unwrap())
.arg("ir-fn-to-block")
.arg(ir_path.to_str().unwrap())
.arg("--top")
.arg("my_main")
.output()
.unwrap();
assert!(
output.status.success(),
"ir-fn-to-block failed:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
xlsynth_test_helpers::compare_golden_text(&stdout, "tests/test_ir_fn_to_block_smoke.golden.ir");
}
#[test_case(true; "with_tool_path")]
#[test_case(false; "without_tool_path")]
fn test_dslx_g8r_stats_subcommand(use_tool_path: bool) {
let _ = env_logger::builder().is_test(true).try_init();
let dslx = "fn main(a: u1, b: u1) -> u1 { a & b }";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("main.x");
std::fs::write(&dslx_path, dslx).unwrap();
let toolchain_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml = "[toolchain]\n";
let toolchain_toml_contents = if use_tool_path {
add_tool_path_value(&toolchain_toml)
} else {
toolchain_toml.to_string()
};
std::fs::write(&toolchain_path, toolchain_toml_contents).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let mut cmd = Command::new(command_path);
cmd.arg("--toolchain")
.arg(toolchain_path.to_str().unwrap())
.arg("dslx-g8r-stats")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("main");
if let Ok(rust_log) = std::env::var("RUST_LOG") {
cmd.env("RUST_LOG", rust_log);
}
let output = cmd.output().unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
let json: serde_json::Value =
serde_json::from_str(stdout.trim()).expect("Output is not valid JSON");
assert!(json.get("live_nodes").is_some(), "stats missing live_nodes");
assert!(
json.get("deepest_path").is_some(),
"stats missing deepest_path"
);
assert!(
json.get("fanout_histogram").is_some(),
"stats missing fanout_histogram"
);
}
#[test]
fn test_ir_fn_eval() {
let ir = "package test\n\nfn add(a: bits[32], b: bits[32]) -> bits[32] {\n ret add.1: bits[32] = add(a, b)\n}\n";
let dir = tempfile::tempdir().unwrap();
let ir_path = dir.path().join("add.ir");
std::fs::write(&ir_path, ir).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("ir-fn-eval")
.arg(ir_path.to_str().unwrap())
.arg("add")
.arg("(bits[32]:1, bits[32]:2)")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert_eq!(String::from_utf8_lossy(&output.stdout), "bits[32]:3\n");
}
#[test]
fn test_aig_eval_matches_ir_fn_eval() {
let ir = "package test
fn f(a: bits[1] id=1, b: bits[1] id=2, c: bits[1] id=3) -> bits[1] {
and.4: bits[1] = and(a, b, id=4)
ret xor.5: bits[1] = xor(and.4, c, id=5)
}
";
let dir = tempfile::tempdir().unwrap();
let ir_path = dir.path().join("f.ir");
let aag_path = dir.path().join("f.aag");
std::fs::write(&ir_path, ir).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let ir2g8r_output = Command::new(command_path)
.arg("ir2g8r")
.arg(ir_path.to_str().unwrap())
.arg("--top")
.arg("f")
.arg("--aiger-out")
.arg(aag_path.to_str().unwrap())
.output()
.unwrap();
assert!(
ir2g8r_output.status.success(),
"ir2g8r failed: stdout: {} stderr: {}",
String::from_utf8_lossy(&ir2g8r_output.stdout),
String::from_utf8_lossy(&ir2g8r_output.stderr)
);
let args = "(bits[1]:1, bits[1]:0, bits[1]:1)";
let ir_eval_output = Command::new(command_path)
.arg("ir-fn-eval")
.arg(ir_path.to_str().unwrap())
.arg("f")
.arg(args)
.output()
.unwrap();
assert!(
ir_eval_output.status.success(),
"ir-fn-eval failed: stdout: {} stderr: {}",
String::from_utf8_lossy(&ir_eval_output.stdout),
String::from_utf8_lossy(&ir_eval_output.stderr)
);
let aig_eval_output = Command::new(command_path)
.arg("aig-eval")
.arg(aag_path.to_str().unwrap())
.arg(args)
.output()
.unwrap();
assert!(
aig_eval_output.status.success(),
"aig-eval failed: stdout: {} stderr: {}",
String::from_utf8_lossy(&aig_eval_output.stdout),
String::from_utf8_lossy(&aig_eval_output.stderr)
);
assert_eq!(ir_eval_output.stdout, aig_eval_output.stdout);
}
#[test]
fn test_aig_eval_matches_ir_fn_eval_with_tuple_input_output() {
let ir = r#"package test
fn f(x: (bits[1], bits[8], bits[7]) id=1) -> (bits[1], bits[8], bits[7]) {
ret identity.2: (bits[1], bits[8], bits[7]) = identity(x, id=2)
}
"#;
let dir = tempfile::tempdir().unwrap();
let ir_path = dir.path().join("tuple_round_trip.ir");
let aag_path = dir.path().join("tuple_round_trip.aag");
std::fs::write(&ir_path, ir).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let ir2g8r_output = Command::new(command_path)
.arg("ir2g8r")
.arg(ir_path.to_str().unwrap())
.arg("--top")
.arg("f")
.arg("--aiger-out")
.arg(aag_path.to_str().unwrap())
.output()
.unwrap();
assert!(
ir2g8r_output.status.success(),
"ir2g8r failed: stdout: {} stderr: {}",
String::from_utf8_lossy(&ir2g8r_output.stdout),
String::from_utf8_lossy(&ir2g8r_output.stderr)
);
let args = "((bits[1]:1, bits[8]:0xaa, bits[7]:0x55))";
let ir_eval_output = Command::new(command_path)
.arg("ir-fn-eval")
.arg(ir_path.to_str().unwrap())
.arg("f")
.arg(args)
.output()
.unwrap();
assert!(
ir_eval_output.status.success(),
"ir-fn-eval failed: stdout: {} stderr: {}",
String::from_utf8_lossy(&ir_eval_output.stdout),
String::from_utf8_lossy(&ir_eval_output.stderr)
);
let aig_eval_output = Command::new(command_path)
.arg("aig-eval")
.arg(aag_path.to_str().unwrap())
.arg(args)
.arg("--fn-type")
.arg("((bits[1], bits[8], bits[7])) -> (bits[1], bits[8], bits[7])")
.output()
.unwrap();
assert!(
aig_eval_output.status.success(),
"aig-eval failed: stdout: {} stderr: {}",
String::from_utf8_lossy(&aig_eval_output.stdout),
String::from_utf8_lossy(&aig_eval_output.stderr)
);
assert_eq!(ir_eval_output.stdout, aig_eval_output.stdout);
}
#[test]
fn test_aig_eval_matches_ir_fn_eval_with_array_input_output() {
let ir = r#"package test
fn f(x: bits[2][3] id=1) -> bits[2][3] {
ret identity.2: bits[2][3] = identity(x, id=2)
}
"#;
let dir = tempfile::tempdir().unwrap();
let ir_path = dir.path().join("array_round_trip.ir");
let aag_path = dir.path().join("array_round_trip.aag");
std::fs::write(&ir_path, ir).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let ir2g8r_output = Command::new(command_path)
.arg("ir2g8r")
.arg(ir_path.to_str().unwrap())
.arg("--top")
.arg("f")
.arg("--aiger-out")
.arg(aag_path.to_str().unwrap())
.output()
.unwrap();
assert!(
ir2g8r_output.status.success(),
"ir2g8r failed: stdout: {} stderr: {}",
String::from_utf8_lossy(&ir2g8r_output.stdout),
String::from_utf8_lossy(&ir2g8r_output.stderr)
);
let args = IrValue::make_tuple(&[IrValue::make_array(&[
IrValue::make_ubits(2, 0x1).unwrap(),
IrValue::make_ubits(2, 0x2).unwrap(),
IrValue::make_ubits(2, 0x3).unwrap(),
])
.unwrap()])
.to_string();
let ir_eval_output = Command::new(command_path)
.arg("ir-fn-eval")
.arg(ir_path.to_str().unwrap())
.arg("f")
.arg(&args)
.output()
.unwrap();
assert!(
ir_eval_output.status.success(),
"ir-fn-eval failed: stdout: {} stderr: {}",
String::from_utf8_lossy(&ir_eval_output.stdout),
String::from_utf8_lossy(&ir_eval_output.stderr)
);
let aig_eval_output = Command::new(command_path)
.arg("aig-eval")
.arg(aag_path.to_str().unwrap())
.arg(&args)
.arg("--fn-type")
.arg("(bits[2][3]) -> bits[2][3]")
.output()
.unwrap();
assert!(
aig_eval_output.status.success(),
"aig-eval failed: stdout: {} stderr: {}",
String::from_utf8_lossy(&aig_eval_output.stdout),
String::from_utf8_lossy(&aig_eval_output.stderr)
);
assert_eq!(ir_eval_output.stdout, aig_eval_output.stdout);
}
#[test]
fn test_aig_eval_matches_ir_fn_eval_with_zero_width_param() {
let ir = r#"package test
fn f(x: () id=1, y: bits[1] id=2) -> bits[1] {
ret identity.3: bits[1] = identity(y, id=3)
}
"#;
let dir = tempfile::tempdir().unwrap();
let ir_path = dir.path().join("unit_param_round_trip.ir");
let aag_path = dir.path().join("unit_param_round_trip.aag");
std::fs::write(&ir_path, ir).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let ir2g8r_output = Command::new(command_path)
.arg("ir2g8r")
.arg(ir_path.to_str().unwrap())
.arg("--top")
.arg("f")
.arg("--aiger-out")
.arg(aag_path.to_str().unwrap())
.output()
.unwrap();
assert!(
ir2g8r_output.status.success(),
"ir2g8r failed: stdout: {} stderr: {}",
String::from_utf8_lossy(&ir2g8r_output.stdout),
String::from_utf8_lossy(&ir2g8r_output.stderr)
);
let args = "((), bits[1]:1)";
let ir_eval_output = Command::new(command_path)
.arg("ir-fn-eval")
.arg(ir_path.to_str().unwrap())
.arg("f")
.arg(args)
.output()
.unwrap();
assert!(
ir_eval_output.status.success(),
"ir-fn-eval failed: stdout: {} stderr: {}",
String::from_utf8_lossy(&ir_eval_output.stdout),
String::from_utf8_lossy(&ir_eval_output.stderr)
);
let aig_eval_output = Command::new(command_path)
.arg("aig-eval")
.arg(aag_path.to_str().unwrap())
.arg(args)
.arg("--fn-type")
.arg("((), bits[1]) -> bits[1]")
.output()
.unwrap();
assert!(
aig_eval_output.status.success(),
"aig-eval failed: stdout: {} stderr: {}",
String::from_utf8_lossy(&aig_eval_output.stdout),
String::from_utf8_lossy(&aig_eval_output.stderr)
);
assert_eq!(ir_eval_output.stdout, aig_eval_output.stdout);
}
#[test]
fn test_aig_eval_reports_fn_type_mismatch() {
let lhs_gate = two_input_gate_fn("main", |a, b, gb| gb.add_and_binary(a, b));
let dir = tempfile::tempdir().unwrap();
let aag_path = write_aiger_file(&dir, "sig_mismatch.aag", &lhs_gate);
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("aig-eval")
.arg(aag_path.to_str().unwrap())
.arg("(bits[2]:0x3)")
.arg("--fn-type")
.arg("(bits[3]) -> bits[1]")
.output()
.unwrap();
assert!(
!output.status.success(),
"expected aig-eval fn-type mismatch to fail; stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert!(
String::from_utf8_lossy(&output.stderr).contains("mismatch"),
"expected mismatch error in stderr, got: {}",
String::from_utf8_lossy(&output.stderr)
);
}
#[test_case(true; "with_tool_path")]
fn test_tiv2_slice_oob_is_error(use_tool_path: bool) {
let _ = env_logger::builder().is_test(true).try_init();
let dslx = "fn f(x: u32) -> u32 { x[32 +: u32] }";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("f.x");
std::fs::write(&dslx_path, dslx).unwrap();
let toolchain_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml = "[toolchain]\n";
let toolchain_contents = if use_tool_path {
add_tool_path_value(toolchain_toml)
} else {
toolchain_toml.to_string()
};
std::fs::write(&toolchain_path, toolchain_contents).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(command_path)
.arg("--toolchain")
.arg(toolchain_path.to_str().unwrap())
.arg("dslx2ir")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("f")
.arg("--type_inference_v2=true")
.output()
.unwrap();
assert!(
!output.status.success(),
"tiv2 compile should fail; stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.to_lowercase().contains("slice") || stderr.to_lowercase().contains("bound"),
"expected slice/bound error message, got: {}",
stderr
);
}
#[test]
fn test_simulate_simple_pipeline() {
let _ = env_logger::builder().is_test(true).try_init();
let dslx = "fn main(a: u32, b: u32) -> u32 { a + b }";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("my_module.x");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("dslx2pipeline")
.arg("--pipeline_stages")
.arg("1")
.arg("--delay_model")
.arg("asap7")
.arg("--flop_inputs=true")
.arg("--flop_outputs=true")
.arg("--input_valid_signal=input_valid")
.arg("--output_valid_signal=output_valid")
.arg("--reset=rst_n")
.arg("--reset_active_low=true")
.arg("--reset_asynchronous=false")
.arg("--reset_data_path=true")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("main")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let pipeline_sv = String::from_utf8_lossy(&output.stdout).to_string();
xlsynth_test_helpers::assert_valid_sv(&pipeline_sv);
log::info!(
"PIPELINE:\n{}",
pipeline_sv.lines().take(8).collect::<Vec<_>>().join("\n")
);
let inputs = vec![("a", IrBits::u32(5)), ("b", IrBits::u32(6))];
let expected = IrBits::u32(11);
let vcd = xlsynth_test_helpers::simulate_pipeline_single_pulse(
&pipeline_sv,
"__my_module__main",
&inputs,
&expected,
1,
)
.expect("simulation succeeds");
assert!(vcd.contains("$var"));
}
#[test]
fn test_dslx_stitch_pipeline_signature_mismatch() {
let _ = env_logger::builder().is_test(true).try_init();
let dslx = r#"fn foo_cycle0(x: u32, y: u64) -> (u32, u64) { (x, y) }
fn foo_cycle1(a: u64, b: u32) -> u64 { a + b as u64 }"#;
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("foo.x");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("dslx-stitch-pipeline")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("foo")
.output()
.unwrap();
assert!(!output.status.success(), "command should fail");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("does not match"),
"unexpected stderr: {}",
stderr
);
}
fn run_dslx_stitch_pipeline_error_case_with_args(
dslx: &str,
extra_args: &[&str],
) -> std::process::Output {
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("foo.x");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let mut command = Command::new(command_path);
command
.arg("dslx-stitch-pipeline")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("foo");
for arg in extra_args {
command.arg(arg);
}
command.output().unwrap()
}
fn run_dslx_stitch_pipeline_error_case(dslx: &str) -> std::process::Output {
run_dslx_stitch_pipeline_error_case_with_args(dslx, &[])
}
#[test]
fn test_dslx_stitch_pipeline_clk_param_reports_name_validation_error() {
let output = run_dslx_stitch_pipeline_error_case(
"fn foo_cycle0(clk: u32) -> u32 { clk }\nfn foo_cycle1(x: u32) -> u32 { x }",
);
assert!(
!output.status.success(),
"command should fail, stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("stitch error") && stderr.contains("name validation failed"),
"unexpected stderr: {}",
stderr
);
assert!(
stderr.contains("stage parameter `foo_cycle0.clk`")
&& stderr.contains("reserved wrapper/control port `clk`"),
"unexpected stderr: {}",
stderr
);
assert!(
!stderr.contains("panicked") && !stderr.contains("INTERNAL: XLS_RET_CHECK"),
"stderr should not expose panic/internal XLS text: {}",
stderr
);
}
#[test]
fn test_dslx_stitch_pipeline_keyword_param_reports_name_validation_error() {
let output = run_dslx_stitch_pipeline_error_case(
"fn foo_cycle0(input: u32) -> u32 { input }\nfn foo_cycle1(x: u32) -> u32 { x }",
);
assert!(
!output.status.success(),
"command should fail, stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("stitch error") && stderr.contains("name validation failed"),
"unexpected stderr: {}",
stderr
);
assert!(
stderr.contains("stage parameter `foo_cycle0.input`")
&& stderr.contains("SystemVerilog keyword"),
"unexpected stderr: {}",
stderr
);
assert!(
!stderr.contains("panicked") && !stderr.contains("INTERNAL: XLS_RET_CHECK"),
"stderr should not expose panic/internal XLS text: {}",
stderr
);
}
#[test]
fn test_dslx_stitch_pipeline_unit_stage_output_reports_clear_error() {
let output = run_dslx_stitch_pipeline_error_case(
"fn foo_cycle0(x: u32) -> () { () }\nfn foo_cycle1() -> u32 { u32:1 }",
);
assert!(
!output.status.success(),
"command should fail, stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("stitch error")
&& stderr.contains("stage `foo_cycle0` has zero-width return type `()`"),
"unexpected stderr: {}",
stderr
);
assert!(
!stderr.contains("panicked") && !stderr.contains("INTERNAL: XLS_RET_CHECK"),
"stderr should not expose panic/internal XLS text: {}",
stderr
);
}
#[test]
fn test_dslx_stitch_pipeline_output_valid_without_input_valid_reports_clear_error() {
let output = run_dslx_stitch_pipeline_error_case_with_args(
"fn foo_cycle0(x: u32) -> u32 { x }\nfn foo_cycle1(x: u32) -> u32 { x }",
&["--output_valid_signal=output_valid"],
);
assert!(
!output.status.success(),
"command should fail, stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("stitch error")
&& stderr.contains("output_valid_signal requires input_valid_signal"),
"unexpected stderr: {}",
stderr
);
assert!(
!stderr.contains("panicked") && !stderr.contains("INTERNAL: XLS_RET_CHECK"),
"stderr should not expose panic/internal XLS text: {}",
stderr
);
}
#[test]
fn test_dslx_stitch_pipeline_parametric_stage_error() {
let _ = env_logger::builder().is_test(true).try_init();
let dslx = "fn foo_cycle0(x: u32) -> u32 { x }\nfn foo_cycle1<N: u32>(x: u32) -> u32 { x }";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("foo.x");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(command_path)
.arg("dslx-stitch-pipeline")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("foo")
.output()
.unwrap();
assert!(
!output.status.success(),
"command should fail, stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.to_lowercase().contains("parametric"),
"stderr should mention parametric stage error, got: {}",
stderr
);
}
#[test]
fn test_dslx_stitch_pipeline_add_mul() {
let _ = env_logger::builder().is_test(true).try_init();
let dslx = "fn add_mul_cycle0(x: u32, y: u32, z: u32) -> (u32, u32) { (x + y, z) }\nfn add_mul_cycle1(sum: u32, z: u32) -> u32 { sum * z }";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("add_mul.x");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(command_path)
.arg("dslx-stitch-pipeline")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("add_mul")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let sv = String::from_utf8_lossy(&output.stdout).to_string();
compare_golden_sv(&sv, "tests/test_dslx_stitch_pipeline_add_mul.golden.v");
}
#[test_case(true; "with_tool_path")]
#[test_case(false; "without_tool_path")]
fn test_dslx_stitch_pipeline_with_dslx_path_two_entries(use_tool_path: bool) {
let _ = env_logger::try_init();
log::info!("test_dslx_stitch_pipeline_with_dslx_path_two_entries");
let temp_dir = tempfile::tempdir().unwrap();
let a_dir = temp_dir.path().join("a");
std::fs::create_dir(&a_dir).unwrap();
let b_dir = temp_dir.path().join("b");
std::fs::create_dir(&b_dir).unwrap();
let a_path = a_dir.join("a.x");
std::fs::write(&a_path, "pub const A: u32 = u32:42;").unwrap();
let b_path = b_dir.join("b.x");
std::fs::write(&b_path, "pub const B: u32 = u32:64;").unwrap();
let top_path = temp_dir.path().join("foo.x");
std::fs::write(
&top_path,
"import a;\nimport b;\n\nfn foo_cycle0() -> (u32, u32) { (a::A, b::B) }\nfn foo_cycle1(x: u32, y: u32) -> u32 { x + y }",
)
.unwrap();
let toolchain_path = temp_dir.path().join("xlsynth-toolchain.toml");
let mut toolchain_toml = format!(
r#"[toolchain]
[toolchain.dslx]
dslx_path = ["{}", "{}"]
"#,
a_dir.to_str().unwrap(),
b_dir.to_str().unwrap()
);
if use_tool_path {
toolchain_toml = add_tool_path_value(&toolchain_toml);
}
std::fs::write(&toolchain_path, toolchain_toml).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(command_path)
.arg("--toolchain")
.arg(toolchain_path.to_str().unwrap())
.arg("dslx-stitch-pipeline")
.arg("--dslx_input_file")
.arg(top_path.to_str().unwrap())
.arg("--dslx_top")
.arg("foo")
.output()
.expect("xlsynth-driver should run");
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let sv = String::from_utf8_lossy(&output.stdout).to_string();
xlsynth_test_helpers::assert_valid_sv(&sv);
compare_golden_sv(
&sv,
"tests/test_dslx_stitch_pipeline_with_dslx_path_two_entries.golden.v",
);
}
#[test]
fn test_dslx_stitch_pipeline_with_custom_stdlib_path() {
let _ = env_logger::try_init();
let temp_dir = tempfile::tempdir().unwrap();
let stdlib_dir = temp_dir.path().join("fake_stdlib");
std::fs::create_dir(&stdlib_dir).unwrap();
let std_path = stdlib_dir.join("std.x");
std::fs::write(&std_path, "pub fn popcount(x: u32) -> u32 { u32:7 }").unwrap();
let dslx = "import std;\nfn foo_cycle0() -> u32 { std::popcount(u32:123) }\nfn foo_cycle1(x: u32) -> u32 { x }";
let dslx_path = temp_dir.path().join("foo.x");
std::fs::write(&dslx_path, dslx).unwrap();
let toolchain_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml = format!(
r#"[toolchain]
[toolchain.dslx]
dslx_stdlib_path = "{}""#,
stdlib_dir.to_str().unwrap()
);
std::fs::write(&toolchain_path, toolchain_toml).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(command_path)
.arg("--toolchain")
.arg(toolchain_path.to_str().unwrap())
.arg("dslx-stitch-pipeline")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("foo")
.output()
.expect("driver run");
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let sv = String::from_utf8_lossy(&output.stdout).to_string();
compare_golden_sv(
&sv,
"tests/test_dslx_stitch_pipeline_custom_stdlib.golden.v",
);
}
#[test]
fn test_dslx_stitch_pipeline_with_valid() {
let dslx =
"fn foo_cycle0(x: u32) -> u32 { x + u32:1 }\nfn foo_cycle1(y: u32) -> u32 { y + u32:2 }";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("foo.x");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("dslx-stitch-pipeline")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("foo")
.arg("--input_valid_signal=input_valid")
.arg("--reset=rst")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
compare_golden_sv(
&stdout,
"tests/test_dslx_stitch_pipeline_with_valid.golden.v",
);
simulate_basic_valid_pipeline(&stdout, "foo", "input_valid", "rst", false);
}
#[test]
fn test_stitch_with_valid_custom_in_valid_reset() {
let dslx =
"fn foo_cycle0(x: u32) -> u32 { x + u32:1 }\nfn foo_cycle1(y: u32) -> u32 { y + u32:2 }";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("foo.x");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("dslx-stitch-pipeline")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("foo")
.arg("--input_valid_signal=in_valid")
.arg("--reset=rst")
.arg("--reset_active_low=false")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
compare_golden_sv(
&stdout,
"tests/test_dslx_stitch_pipeline_with_valid_in_valid_rst.golden.v",
);
simulate_basic_valid_pipeline(&stdout, "foo", "in_valid", "rst", false);
}
#[test]
fn test_stitch_with_valid_custom_in_valid_rst_n_active_low() {
let dslx =
"fn foo_cycle0(x: u32) -> u32 { x + u32:1 }\nfn foo_cycle1(y: u32) -> u32 { y + u32:2 }";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("foo.x");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("dslx-stitch-pipeline")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("foo")
.arg("--input_valid_signal=in_valid")
.arg("--reset=rst_n")
.arg("--reset_active_low=true")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
compare_golden_sv(
&stdout,
"tests/test_dslx_stitch_pipeline_with_valid_in_valid_rst_n_active_low.golden.v",
);
simulate_basic_valid_pipeline(&stdout, "foo", "in_valid", "rst_n", true);
}
#[test]
fn test_stitch_with_valid_custom_in_and_out_valid() {
let _ = env_logger::try_init();
let dslx =
"fn foo_cycle0(x: u32) -> u32 { x + u32:1 }\nfn foo_cycle1(y: u32) -> u32 { y + u32:2 }";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("foo.x");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("dslx-stitch-pipeline")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("foo")
.arg("--input_valid_signal=in_valid")
.arg("--output_valid_signal=out_valid")
.arg("--reset=rst")
.arg("--reset_active_low=false")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
compare_golden_sv(
&stdout,
"tests/test_dslx_stitch_pipeline_with_valid_in_and_out_valid.golden.v",
);
let inputs = vec![("x", IrBits::u32(5))];
let expected = IrBits::u32(8);
let vcd = xlsynth_test_helpers::simulate_pipeline_single_pulse_custom(
&stdout,
"foo",
&inputs,
&expected,
2,
"in_valid",
Some("out_valid"),
"rst",
false,
)
.expect("simulation succeeds");
assert!(vcd.contains("$var"));
}
#[test]
fn test_stitch_with_valid_custom_in_and_out_valid_no_reset() {
let _ = env_logger::try_init();
let dslx =
"fn foo_cycle0(x: u32) -> u32 { x + u32:1 }\nfn foo_cycle1(y: u32) -> u32 { y + u32:2 }";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("foo.x");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("dslx-stitch-pipeline")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("foo")
.arg("--input_valid_signal=in_valid")
.arg("--output_valid_signal=out_valid")
.output()
.unwrap();
assert!(
output.status.success(),
"driver failed; stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
compare_golden_sv(
&stdout,
"tests/test_dslx_stitch_pipeline_with_valid_in_and_out_valid_no_reset.golden.v",
);
}
fn simulate_basic_valid_pipeline(
sv: &str,
module_name: &str,
input_valid: &str,
reset: &str,
reset_active_low: bool,
) {
let inputs = vec![("x", IrBits::u32(5))];
let expected = IrBits::u32(8);
let vcd = xlsynth_test_helpers::simulate_pipeline_single_pulse_custom(
sv,
module_name,
&inputs,
&expected,
2,
input_valid,
None,
reset,
reset_active_low,
)
.expect("simulation succeeds");
assert!(vcd.contains("$var"));
}
const QUICKCHECK_DSLX: &'static str = r#"
fn f(x: u8) -> bool { x == x }
#[quickcheck] fn qc_success(x: u8) -> bool { f(x) }
#[quickcheck] fn qc_failure(x: u8) -> bool { x == u8:0 }
"#;
#[cfg_attr(feature="has-boolector", test_case("boolector", true; "boolector_success"))]
#[cfg_attr(feature="has-boolector", test_case("boolector", false; "boolector_failure"))]
#[cfg_attr(feature="has-bitwuzla", test_case("bitwuzla", true; "bitwuzla_success"))]
#[cfg_attr(feature="has-bitwuzla", test_case("bitwuzla", false; "bitwuzla_failure"))]
#[cfg_attr(feature="with-z3-binary-test", test_case("z3-binary", true; "z3_binary_success"))]
#[cfg_attr(feature="with-z3-binary-test", test_case("z3-binary", false; "z3_binary_failure"))]
#[cfg_attr(feature="with-bitwuzla-binary-test", test_case("bitwuzla-binary", true; "bitwuzla_bin_success"))]
#[cfg_attr(feature="with-bitwuzla-binary-test", test_case("bitwuzla-binary", false; "bitwuzla_bin_failure"))]
#[cfg_attr(feature="with-boolector-binary-test", test_case("boolector-binary", true; "boolector_bin_success"))]
#[cfg_attr(feature="with-boolector-binary-test", test_case("boolector-binary", false; "boolector_bin_failure"))]
#[test_case("toolchain", true; "toolchain_success")]
#[test_case("toolchain", false; "toolchain_failure")]
fn test_prove_quickcheck_solver_param(solver: &str, should_succeed: bool) {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let file_name = "qc.x";
let dslx_path = temp_dir.path().join(file_name);
std::fs::write(&dslx_path, QUICKCHECK_DSLX).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let mut cmd = Command::new(driver);
let toolchain_toml = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml, toolchain_contents).unwrap();
cmd.arg("--toolchain")
.arg(toolchain_toml.to_str().unwrap())
.arg("prove-quickcheck")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--solver")
.arg(solver)
.arg("--test_filter")
.arg(if should_succeed {
".*success"
} else {
".*failure"
});
let output = cmd.output().unwrap();
if should_succeed {
assert!(
output.status.success(),
"Prove QC with solver {} should succeed. stderr: {}",
solver,
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("Success: All QuickChecks proved"));
} else {
assert!(
!output.status.success(),
"Prove QC with solver {} should fail (property false).",
solver
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("Failure: Some QuickChecks disproved"));
}
}
#[test]
fn test_prove_quickcheck_script_mode() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("qc_script.x");
std::fs::write(&dslx_path, QUICKCHECK_DSLX).unwrap();
let script_path = temp_dir.path().join("script.json");
let script_json = r#"[{"selector":["root"],"command":"Solve"}]"#;
std::fs::write(&script_path, script_json).unwrap();
let toolchain_toml = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml, toolchain_contents).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(driver)
.arg("--toolchain")
.arg(toolchain_toml.to_str().unwrap())
.arg("prove-quickcheck")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--solver")
.arg("toolchain")
.arg("--test_filter")
.arg(".*success")
.arg("--tactic_json")
.arg(script_path.to_str().unwrap())
.output()
.expect("prove-quickcheck invocation should run");
println!("output: {:?}", output);
assert!(
output.status.success(),
"prove-quickcheck tactic run should succeed. stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("[prove-quickcheck] success: QuickCheck obligations proved"),
"unexpected stdout: {}",
stdout
);
}
#[test_case("toolchain"; "multi_qc_toolchain")]
#[cfg_attr(feature="has-boolector", test_case("boolector"; "multi_qc_boolector"))]
#[cfg_attr(feature="has-bitwuzla", test_case("bitwuzla"; "multi_qc_bitwuzla"))]
#[cfg_attr(feature="with-z3-binary-test", test_case("z3-binary"; "multi_qc_z3_binary"))]
#[cfg_attr(feature="with-bitwuzla-binary-test", test_case("bitwuzla-binary"; "multi_qc_bitwuzla_binary"))]
#[cfg_attr(feature="with-boolector-binary-test", test_case("boolector-binary"; "multi_qc_boolector_binary"))]
fn test_prove_quickcheck_multiple_functions_with_assertions_solver(solver: &str) {
let _ = env_logger::builder().is_test(true).try_init();
const MULTI_QC_DSLX: &str = r#"
fn add1(x: u8) -> u8 { x + u8:1 }
#[quickcheck] fn qc_reflexive(x: u8) -> bool {
assert!(x == x, "reflexive");
true
}
#[quickcheck] fn qc_commutative(x: u8, y: u8) -> bool {
assert!(x + y == y + x, "commutative");
x + y == y + x
}
#[quickcheck] fn qc_increment_changes(x: u8) -> bool {
let y = add1(x);
assert!(y != x, "increment differs");
(y > x) || (x == u8:255)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("multi_qc.x");
std::fs::write(&dslx_path, MULTI_QC_DSLX).unwrap();
let toolchain_toml = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml, toolchain_contents).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(driver)
.arg("--toolchain")
.arg(toolchain_toml.to_str().unwrap())
.arg("prove-quickcheck")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--solver")
.arg(solver)
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"prove-quickcheck should succeed for solver {}.\nstdout: {}\nstderr: {}",
solver,
stdout,
stderr
);
assert!(
stdout.contains("Success: All QuickChecks proved"),
"stdout: {}",
stdout
);
}
fn should_skip_if_no_slang() -> bool {
let slang_path = match which::which("slang") {
Ok(path) => path,
Err(_) => {
println!("SKIPPED: `slang` executable not found in PATH");
return true;
}
};
if let Ok(metadata) = std::fs::metadata(&slang_path) {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let perms = metadata.permissions();
if perms.mode() & 0o111 == 0 {
println!(
"SKIPPED: `slang` found at '{}' but not executable (permissions: {:o})",
slang_path.display(),
perms.mode()
);
return true;
}
}
}
match std::process::Command::new(&slang_path)
.arg("--version")
.output()
{
Ok(output) => {
if !output.status.success() {
println!(
"SKIPPED: `slang` exists but failed to run: {}",
String::from_utf8_lossy(&output.stderr)
);
return true;
}
}
Err(e) => {
println!("SKIPPED: `slang` exists but failed to execute: {}", e);
return true;
}
}
false }
#[test]
fn test_run_verilog_pipeline_basic_add1() {
let _ = env_logger::builder().is_test(true).try_init();
if should_skip_if_no_slang() {
return;
}
let dslx = "fn main(x: u32) -> u32 { x + u32:1 }";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("add1.x");
std::fs::write(&dslx_path, dslx).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let pipeline_output = Command::new(driver)
.arg("dslx2pipeline")
.arg("--pipeline_stages")
.arg("1")
.arg("--flop_inputs=false")
.arg("--flop_outputs=false")
.arg("--delay_model")
.arg("asap7")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("main")
.output()
.expect("dslx2pipeline run");
assert!(pipeline_output.status.success());
let pipeline_sv = String::from_utf8(pipeline_output.stdout).unwrap();
xlsynth_test_helpers::assert_valid_sv(&pipeline_sv);
let mut cmd = Command::new(driver);
cmd.arg("run-verilog-pipeline")
.arg("--latency")
.arg("1")
.arg("-") .arg("bits[32]:5")
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped());
let mut child = cmd.spawn().expect("spawn run-verilog-pipeline");
{
let stdin = child.stdin.as_mut().expect("get stdin");
stdin.write_all(pipeline_sv.as_bytes()).unwrap();
}
let output = child.wait_with_output().unwrap();
assert!(
output.status.success(),
"run-verilog-pipeline failed; retcode: {:?}\n stdout: {:?}\n stderr: {:?}",
output.status.code().unwrap(),
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(
stdout.trim().contains("out: bits[32]:6"),
"unexpected stdout: {}",
stdout
);
}
#[test]
fn test_run_verilog_pipeline_wave_dump() {
let _ = env_logger::builder().is_test(true).try_init();
if should_skip_if_no_slang() {
return;
}
let dslx = "fn main(x: u32) -> u32 { x + u32:1 }";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("add1.x");
std::fs::write(&dslx_path, dslx).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let pipeline_output = Command::new(driver)
.arg("dslx2pipeline")
.arg("--pipeline_stages")
.arg("1")
.arg("--flop_inputs=false")
.arg("--flop_outputs=false")
.arg("--delay_model")
.arg("asap7")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("main")
.output()
.expect("dslx2pipeline run");
assert!(pipeline_output.status.success());
let pipeline_sv = String::from_utf8(pipeline_output.stdout).unwrap();
xlsynth_test_helpers::assert_valid_sv(&pipeline_sv);
let wave_path = temp_dir.path().join("dump.vcd");
let mut cmd = Command::new(driver);
cmd.arg("run-verilog-pipeline")
.arg("--latency")
.arg("1")
.arg("--waves")
.arg(wave_path.to_str().unwrap())
.arg("-") .arg("bits[32]:5")
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped());
let mut child = cmd.spawn().expect("spawn run-verilog-pipeline");
{
let stdin = child.stdin.as_mut().expect("get stdin");
stdin.write_all(pipeline_sv.as_bytes()).unwrap();
}
let output = child.wait_with_output().unwrap();
assert!(
output.status.success(),
"run-verilog-pipeline failed; retcode: {:?}\n stdout: {:?}\n stderr: {:?}",
output.status.code().unwrap(),
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.trim().contains("out: bits[32]:6"));
assert!(wave_path.exists(), "waves file not created");
let vcd_contents = std::fs::read_to_string(&wave_path).expect("read vcd");
assert!(
vcd_contents.contains("$var"),
"wave file missing VCD var declarations"
);
}
#[test]
fn test_run_verilog_pipeline_with_valid_signals() {
let _ = env_logger::builder().is_test(true).try_init();
if should_skip_if_no_slang() {
return;
}
let dslx = "fn main(x: u32) -> u32 { x + u32:1 }";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("inc.x");
std::fs::write(&dslx_path, dslx).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let pipeline_output = Command::new(driver)
.arg("dslx2pipeline")
.arg("--pipeline_stages")
.arg("2")
.arg("--delay_model")
.arg("asap7")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("main")
.arg("--input_valid_signal=in_valid")
.arg("--output_valid_signal=out_valid")
.arg("--reset=rst")
.arg("--reset_active_low=false")
.output()
.expect("dslx2pipeline run");
assert!(pipeline_output.status.success(), "dslx2pipeline failed");
let pipeline_sv = String::from_utf8(pipeline_output.stdout).unwrap();
xlsynth_test_helpers::assert_valid_sv(&pipeline_sv);
let mut cmd = Command::new(driver);
cmd.arg("run-verilog-pipeline")
.arg("--input_valid_signal=in_valid")
.arg("--output_valid_signal=out_valid")
.arg("--reset=rst")
.arg("--reset_active_low=false")
.arg("-") .arg("bits[32]:5") .stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped());
let mut child = cmd.spawn().expect("spawn run-verilog-pipeline");
{
let stdin = child.stdin.as_mut().expect("stdin");
stdin.write_all(pipeline_sv.as_bytes()).unwrap();
}
let output = child.wait_with_output().unwrap();
assert!(
output.status.success(),
"run-verilog-pipeline failed; retcode: {:?}\n stdout: {:?}\n stderr: {:?}",
output.status.code().unwrap(),
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(
stdout.trim().contains("out: bits[32]:6"),
"unexpected stdout: {}",
stdout
);
}
#[test]
fn test_run_verilog_pipeline_with_valid_signals_and_flops() {
let _ = env_logger::builder().is_test(true).try_init();
if should_skip_if_no_slang() {
return;
}
let dslx = "fn main(x: u32) -> u32 { x + u32:1 }";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("inc.x");
std::fs::write(&dslx_path, dslx).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let pipeline_output = Command::new(driver)
.arg("dslx2pipeline")
.arg("--pipeline_stages")
.arg("1")
.arg("--delay_model")
.arg("asap7")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("main")
.arg("--input_valid_signal=in_valid")
.arg("--output_valid_signal=out_valid")
.arg("--reset=rst")
.arg("--reset_active_low=false")
.arg("--flop_inputs=true")
.arg("--flop_outputs=true")
.output()
.expect("dslx2pipeline run");
assert!(pipeline_output.status.success(), "dslx2pipeline failed");
let pipeline_sv = String::from_utf8(pipeline_output.stdout).unwrap();
xlsynth_test_helpers::assert_valid_sv(&pipeline_sv);
let mut cmd = Command::new(driver);
cmd.arg("run-verilog-pipeline")
.arg("--input_valid_signal=in_valid")
.arg("--output_valid_signal=out_valid")
.arg("--reset=rst")
.arg("--reset_active_low=false")
.arg("-") .arg("bits[32]:5") .stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped());
let mut child = cmd.spawn().expect("spawn run-verilog-pipeline");
{
let stdin = child.stdin.as_mut().expect("stdin");
stdin.write_all(pipeline_sv.as_bytes()).unwrap();
}
let output = child.wait_with_output().unwrap();
assert!(
output.status.success(),
"run-verilog-pipeline failed; retcode: {:?}\n stdout: {:?}\n stderr: {:?}",
output.status.code().unwrap(),
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(
stdout.trim().contains("out: bits[32]:6"),
"unexpected stdout: {}",
stdout
);
}
#[cfg_attr(feature="has-boolector", test_case("boolector", true; "dslx_equiv_boolector_equivalent"))]
#[cfg_attr(feature="has-boolector", test_case("boolector", false; "dslx_equiv_boolector_nonequivalent"))]
#[cfg_attr(feature="has-bitwuzla", test_case("bitwuzla", true; "dslx_equiv_bitwuzla_equivalent"))]
#[cfg_attr(feature="has-bitwuzla", test_case("bitwuzla", false; "dslx_equiv_bitwuzla_nonequivalent"))]
#[cfg_attr(feature="with-z3-binary-test", test_case("z3-binary", true; "dslx_equiv_z3_binary_equivalent"))]
#[cfg_attr(feature="with-z3-binary-test", test_case("z3-binary", false; "dslx_equiv_z3_binary_nonequivalent"))]
#[cfg_attr(feature="with-bitwuzla-binary-test", test_case("bitwuzla-binary", true; "dslx_equiv_bitwuzla_binary_equivalent"))]
#[cfg_attr(feature="with-bitwuzla-binary-test", test_case("bitwuzla-binary", false; "dslx_equiv_bitwuzla_binary_nonequivalent"))]
#[cfg_attr(feature="with-boolector-binary-test", test_case("boolector-binary", true; "dslx_equiv_boolector_binary_equivalent"))]
#[cfg_attr(feature="with-boolector-binary-test", test_case("boolector-binary", false; "dslx_equiv_boolector_binary_nonequivalent"))]
#[test_case("toolchain", true; "dslx_equiv_toolchain_equivalent")]
#[test_case("toolchain", false; "dslx_equiv_toolchain_nonequivalent")]
fn test_dslx_equiv_solver_param(solver: &str, should_succeed: bool) {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let lhs_dslx = "fn main(x: u32) -> u32 { (x + x) - x }";
let rhs_dslx_equiv = "fn main(x: u32) -> u32 { x }";
let rhs_dslx_nonequiv = "fn main(x: u32) -> u32 { x + u32:1 }";
let lhs_path = temp_dir.path().join("lhs.x");
let rhs_path = temp_dir.path().join("rhs.x");
std::fs::write(&lhs_path, lhs_dslx).unwrap();
std::fs::write(
&rhs_path,
if should_succeed {
rhs_dslx_equiv
} else {
rhs_dslx_nonequiv
},
)
.unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let mut toolchain_toml_contents = "[toolchain]\n".to_string();
toolchain_toml_contents = add_tool_path_value(&toolchain_toml_contents);
std::fs::write(&toolchain_toml_path, toolchain_toml_contents).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(command_path)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("dslx-equiv")
.arg(lhs_path.to_str().unwrap())
.arg(rhs_path.to_str().unwrap())
.arg("--dslx_top")
.arg("main")
.arg("--solver")
.arg(solver)
.output()
.expect("dslx-equiv invocation should run");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
if should_succeed {
assert!(
output.status.success(),
"dslx-equiv should report equivalence. stdout: {}\nstderr: {}",
stdout,
stderr
);
assert!(
stdout.contains("success"),
"expected success marker in stdout: {}",
stdout
);
} else {
assert!(
!output.status.success(),
"dslx-equiv should report non-equivalence. stdout: {}\nstderr: {}",
stdout,
stderr
);
assert!(
stderr.contains("failure")
|| stdout.contains("failure")
|| stderr.contains("Verified NOT equivalent"),
"expected failure marker. stdout: {} stderr: {}",
stdout,
stderr
);
}
}
#[test]
fn test_dslx_equiv_parametric_top_specialization_checks_bindings() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let lhs_dslx = r#"
fn main<N: u32>() -> u32 {
N
}
"#;
let rhs_dslx = r#"
fn main<N: u32>() -> u32 {
N
}
"#;
let lhs_path = temp_dir.path().join("lhs_param.x");
let rhs_path = temp_dir.path().join("rhs_param.x");
std::fs::write(&lhs_path, lhs_dslx).unwrap();
std::fs::write(&rhs_path, rhs_dslx).unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let mut toolchain_toml_contents = "[toolchain]\n".to_string();
toolchain_toml_contents = add_tool_path_value(&toolchain_toml_contents);
std::fs::write(&toolchain_toml_path, toolchain_toml_contents).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let success = std::process::Command::new(driver)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("dslx-equiv")
.arg(lhs_path.to_str().unwrap())
.arg(rhs_path.to_str().unwrap())
.arg("--dslx_top")
.arg("main<u32:16>")
.arg("--solver")
.arg("toolchain")
.output()
.expect("dslx-equiv invocation should run");
let success_stdout = String::from_utf8_lossy(&success.stdout);
let success_stderr = String::from_utf8_lossy(&success.stderr);
assert!(
success.status.success(),
"dslx-equiv should succeed with matching value-only specializations. stdout: {}\nstderr: {}",
success_stdout,
success_stderr
);
assert!(
success_stdout.contains("success"),
"expected success marker in stdout: {}",
success_stdout
);
let mismatch = std::process::Command::new(driver)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("dslx-equiv")
.arg(lhs_path.to_str().unwrap())
.arg(rhs_path.to_str().unwrap())
.arg("--lhs_dslx_top")
.arg("main<u32:16>")
.arg("--rhs_dslx_top")
.arg("main<u32:8>")
.arg("--solver")
.arg("toolchain")
.output()
.expect("dslx-equiv invocation without bindings should run");
let mismatch_stdout = String::from_utf8_lossy(&mismatch.stdout);
let mismatch_stderr = String::from_utf8_lossy(&mismatch.stderr);
assert!(
!mismatch.status.success(),
"dslx-equiv should fail when value-only specializations disagree. stdout: {}\nstderr: {}",
mismatch_stdout,
mismatch_stderr
);
assert!(
mismatch_stderr.contains("failure")
|| mismatch_stdout.contains("failure")
|| mismatch_stderr.contains("inequivalent")
|| mismatch_stdout.contains("inequivalent"),
"expected inequivalence marker in output. stdout: {}\nstderr: {}",
mismatch_stdout,
mismatch_stderr
);
}
#[cfg_attr(feature="has-bitwuzla", test_case("bitwuzla"; "dslx_equiv_default_semantics_bitwuzla"))]
#[test_case("toolchain"; "dslx_equiv_default_semantics_toolchain")]
fn test_dslx_equiv_default_semantics_matches_toolchain(solver: &str) {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let lhs_dslx = r#"
fn main(x: u32) -> u32 {
assert!(false, "lhs failure");
x
}
"#;
let rhs_dslx = r#"
fn main(x: u32) -> u32 {
x
}
"#;
let lhs_path = temp_dir.path().join("lhs.x");
let rhs_path = temp_dir.path().join("rhs.x");
std::fs::write(&lhs_path, lhs_dslx.trim_start()).unwrap();
std::fs::write(&rhs_path, rhs_dslx.trim_start()).unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml_path, toolchain_toml_contents).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(driver)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("dslx-equiv")
.arg(lhs_path.to_str().unwrap())
.arg(rhs_path.to_str().unwrap())
.arg("--dslx_top")
.arg("main")
.arg("--solver")
.arg(solver)
.output()
.expect("dslx-equiv invocation should run");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"dslx-equiv with assertion ignore should succeed for solver {}.\nstdout: {}\nstderr: {}",
solver,
stdout,
stderr
);
assert!(
stdout.contains("success"),
"expected success marker in stdout: {}",
stdout
);
}
#[cfg_attr(feature="has-bitwuzla", test_case("bitwuzla"; "prove_quickcheck_default_semantics_bitwuzla"))]
#[test_case("toolchain"; "prove_quickcheck_default_semantics_toolchain")]
fn test_prove_quickcheck_default_semantics_matches_toolchain(solver: &str) {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let dslx_source = r#"
fn always_fail() -> bool {
assert!(false, "lhs failure");
true
}
#[quickcheck]
fn qc_always_fails() -> bool {
always_fail()
}
"#;
let dslx_path = temp_dir.path().join("qc.x");
std::fs::write(&dslx_path, dslx_source.trim_start()).unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml_path, toolchain_toml_contents).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(driver)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("prove-quickcheck")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--solver")
.arg(solver)
.output()
.expect("prove-quickcheck invocation should run");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!output.status.success(),
"prove-quickcheck with default semantics should fail for solver {} when assertions fire.\nstdout: {}\nstderr: {}",
solver,
stdout,
stderr
);
assert!(
stdout.contains("Failure: Some QuickChecks disproved"),
"expected failure messaging in stdout: {}",
stdout
);
}
#[cfg_attr(feature="has-bitwuzla", test_case("bitwuzla"; "ir_equiv_default_semantics_bitwuzla"))]
#[test_case("toolchain"; "ir_equiv_default_semantics_toolchain")]
fn test_ir_equiv_default_semantics_matches_toolchain(solver: &str) {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let lhs_ir = r#"
package lhs
fn main(x: bits[8] id=1) -> bits[8] {
after_all.2: token = after_all(id=2)
literal.3: bits[1] = literal(value=0, id=3)
assert.4: token = assert(after_all.2, literal.3, message="lhs failure", label="lhs", id=4)
ret x: bits[8] = param(name=x, id=1)
}
"#;
let rhs_ir = r#"
package rhs
fn main(x: bits[8] id=1) -> bits[8] {
ret x: bits[8] = param(name=x, id=1)
}
"#;
let lhs_path = temp_dir.path().join("lhs.ir");
let rhs_path = temp_dir.path().join("rhs.ir");
std::fs::write(&lhs_path, lhs_ir.trim_start()).unwrap();
std::fs::write(&rhs_path, rhs_ir.trim_start()).unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml_path, toolchain_toml_contents).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(driver)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("ir-equiv")
.arg(lhs_path.to_str().unwrap())
.arg(rhs_path.to_str().unwrap())
.arg("--solver")
.arg(solver)
.output()
.expect("ir-equiv invocation should run");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"ir-equiv with default semantics should succeed for solver {} when only lhs asserts.\nstdout: {}\nstderr: {}",
solver,
stdout,
stderr
);
assert!(
stdout.contains("success"),
"expected success marker in stdout: {}",
stdout
);
}
#[cfg_attr(feature="has-boolector", test_case("boolector", true; "dslx_equiv_diff_tops_boolector_equiv"))]
#[cfg_attr(feature="has-boolector", test_case("boolector", false; "dslx_equiv_diff_tops_boolector_nonequiv"))]
#[cfg_attr(feature="has-bitwuzla", test_case("bitwuzla", true; "dslx_equiv_diff_tops_bitwuzla_equiv"))]
#[cfg_attr(feature="has-bitwuzla", test_case("bitwuzla", false; "dslx_equiv_diff_tops_bitwuzla_nonequiv"))]
#[cfg_attr(feature="with-z3-binary-test", test_case("z3-binary", true; "dslx_equiv_diff_tops_z3_equiv"))]
#[cfg_attr(feature="with-z3-binary-test", test_case("z3-binary", false; "dslx_equiv_diff_tops_z3_nonequiv"))]
#[cfg_attr(feature="with-bitwuzla-binary-test", test_case("bitwuzla-binary", true; "dslx_equiv_diff_tops_bitwuzla_bin_equiv"))]
#[cfg_attr(feature="with-bitwuzla-binary-test", test_case("bitwuzla-binary", false; "dslx_equiv_diff_tops_bitwuzla_bin_nonequiv"))]
#[cfg_attr(feature="with-boolector-binary-test", test_case("boolector-binary", true; "dslx_equiv_diff_tops_boolector_bin_equiv"))]
#[cfg_attr(feature="with-boolector-binary-test", test_case("boolector-binary", false; "dslx_equiv_diff_tops_boolector_bin_nonequiv"))]
#[test_case("toolchain", true; "dslx_equiv_diff_tops_toolchain_equiv")]
#[test_case("toolchain", false; "dslx_equiv_diff_tops_toolchain_nonequiv")]
fn test_dslx_equiv_solver_param_different_tops(solver: &str, should_succeed: bool) {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let lhs_dslx = "fn alpha(x: u32) -> u32 { (x + x) - x }"; let rhs_dslx_equiv = "fn beta(x: u32) -> u32 { x }";
let rhs_dslx_nonequiv = "fn beta(x: u32) -> u32 { x + u32:1 }";
let lhs_path = temp_dir.path().join("lhs.x");
let rhs_path = temp_dir.path().join("rhs.x");
std::fs::write(&lhs_path, lhs_dslx).unwrap();
std::fs::write(
&rhs_path,
if should_succeed {
rhs_dslx_equiv
} else {
rhs_dslx_nonequiv
},
)
.unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let mut toolchain_toml_contents = "[toolchain]\n".to_string();
toolchain_toml_contents = add_tool_path_value(&toolchain_toml_contents);
std::fs::write(&toolchain_toml_path, toolchain_toml_contents).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(command_path)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("dslx-equiv")
.arg(lhs_path.to_str().unwrap())
.arg(rhs_path.to_str().unwrap())
.arg("--lhs_dslx_top")
.arg("alpha")
.arg("--rhs_dslx_top")
.arg("beta")
.arg("--solver")
.arg(solver)
.output()
.expect("dslx-equiv invocation should run");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
if should_succeed {
assert!(
output.status.success(),
"dslx-equiv (different tops) should report equivalence. stdout: {}\nstderr: {}",
stdout,
stderr
);
assert!(
stdout.contains("success"),
"expected success marker in stdout: {}",
stdout
);
} else {
assert!(
!output.status.success(),
"dslx-equiv (different tops) should report non-equivalence. stdout: {}\nstderr: {}",
stdout,
stderr
);
assert!(
stderr.contains("failure")
|| stdout.contains("failure")
|| stderr.contains("Verified NOT equivalent"),
"expected failure marker. stdout: {} stderr: {}",
stdout,
stderr
);
}
}
#[test]
fn test_run_verilog_pipeline_with_valid_signals_and_output_flops_only() {
let _ = env_logger::builder().is_test(true).try_init();
if should_skip_if_no_slang() {
return;
}
let dslx = "fn main(x: u32) -> u32 { x + u32:1 }";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("inc_outflop.x");
std::fs::write(&dslx_path, dslx).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let pipeline_output = Command::new(driver)
.arg("dslx2pipeline")
.arg("--pipeline_stages")
.arg("1")
.arg("--delay_model")
.arg("asap7")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("main")
.arg("--input_valid_signal=in_valid")
.arg("--output_valid_signal=out_valid")
.arg("--reset=rst")
.arg("--reset_active_low=false")
.arg("--flop_outputs=true")
.output()
.expect("dslx2pipeline run");
assert!(pipeline_output.status.success(), "dslx2pipeline failed");
let pipeline_sv = String::from_utf8(pipeline_output.stdout).unwrap();
xlsynth_test_helpers::assert_valid_sv(&pipeline_sv);
let mut cmd = Command::new(driver);
cmd.arg("run-verilog-pipeline")
.arg("--input_valid_signal=in_valid")
.arg("--output_valid_signal=out_valid")
.arg("--reset=rst")
.arg("--reset_active_low=false")
.arg("-")
.arg("bits[32]:5")
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped());
let mut child = cmd.spawn().expect("spawn run-verilog-pipeline");
{
child
.stdin
.as_mut()
.unwrap()
.write_all(pipeline_sv.as_bytes())
.unwrap();
}
let output = child.wait_with_output().unwrap();
assert!(
output.status.success(),
"run-verilog-pipeline failed; stdout: {:?} stderr: {:?}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(
stdout.trim().contains("out: bits[32]:6"),
"unexpected stdout: {}",
stdout
);
}
#[test]
fn test_run_verilog_pipeline_with_valid_signals_and_input_flops_only() {
let _ = env_logger::builder().is_test(true).try_init();
if should_skip_if_no_slang() {
return;
}
let dslx = "fn main(x: u32) -> u32 { x + u32:1 }";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("inc_inflop.x");
std::fs::write(&dslx_path, dslx).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let pipeline_output = Command::new(driver)
.arg("dslx2pipeline")
.arg("--pipeline_stages")
.arg("1")
.arg("--delay_model")
.arg("asap7")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("main")
.arg("--input_valid_signal=in_valid")
.arg("--output_valid_signal=out_valid")
.arg("--reset=rst")
.arg("--reset_active_low=false")
.arg("--flop_inputs=true")
.output()
.expect("dslx2pipeline run");
assert!(pipeline_output.status.success(), "dslx2pipeline failed");
let pipeline_sv = String::from_utf8(pipeline_output.stdout).unwrap();
xlsynth_test_helpers::assert_valid_sv(&pipeline_sv);
let mut cmd = Command::new(driver);
cmd.arg("run-verilog-pipeline")
.arg("--input_valid_signal=in_valid")
.arg("--output_valid_signal=out_valid")
.arg("--reset=rst")
.arg("--reset_active_low=false")
.arg("-")
.arg("bits[32]:5")
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped());
let mut child = cmd.spawn().expect("spawn run-verilog-pipeline");
child
.stdin
.as_mut()
.unwrap()
.write_all(pipeline_sv.as_bytes())
.unwrap();
let output = child.wait_with_output().unwrap();
assert!(
output.status.success(),
"run-verilog-pipeline failed; stdout: {:?} stderr: {:?}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(
stdout.trim().contains("out: bits[32]:6"),
"unexpected stdout: {}",
stdout
);
}
#[test]
fn test_dslx_stitch_pipeline_no_flops() {
let _ = env_logger::builder().is_test(true).try_init();
let dslx = "fn noflop_cycle0(x: u32) -> u32 { x + u32:1 }";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("noflop.x");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(command_path)
.arg("dslx-stitch-pipeline")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("noflop")
.arg("--flop_inputs=false")
.arg("--flop_outputs=false")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let sv = String::from_utf8_lossy(&output.stdout);
xlsynth_test_helpers::assert_valid_sv(&sv);
assert!(
!sv.contains("reg p0_"),
"No pipeline flop registers expected, but found some in generated SV"
);
}
#[test]
fn test_dslx_stitch_pipeline_flop_inputs_only() {
let _ = env_logger::builder().is_test(true).try_init();
let dslx = "fn inputflop_cycle0(x: u32) -> u32 { x + u32:1 }";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("inputflop.x");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(command_path)
.arg("dslx-stitch-pipeline")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("inputflop")
.arg("--flop_inputs=true")
.arg("--flop_outputs=false")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let sv = String::from_utf8_lossy(&output.stdout);
xlsynth_test_helpers::assert_valid_sv(&sv);
assert!(
sv.contains("p0_x"),
"Expected input flop register p0_x not found"
);
assert!(
!sv.contains("reg p1_"),
"Unexpected second stage flop registers found when flop_outputs disabled"
);
}
#[test]
fn test_dslx_stitch_pipeline_flop_outputs_only() {
let _ = env_logger::builder().is_test(true).try_init();
let dslx = "fn outputflop_cycle0(x: u32) -> u32 { x + u32:1 }";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("outputflop.x");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(command_path)
.arg("dslx-stitch-pipeline")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("outputflop")
.arg("--flop_inputs=false")
.arg("--flop_outputs=true")
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let sv = String::from_utf8_lossy(&output.stdout);
xlsynth_test_helpers::assert_valid_sv(&sv);
assert!(
sv.contains("p0_out"),
"Expected output flop register p0_out not found"
);
assert!(
!sv.contains("reg p0_x"),
"Input flop p0_x register should not be present when flop_inputs disabled"
);
}
#[cfg(feature = "with-z3-binary-test")]
#[test]
fn test_dslx_equiv_assume_enum_in_bound_swapped_default() {
let _ = env_logger::builder().is_test(true).try_init();
let lhs = r#"
enum E : u3 { A = 0, B = 1 }
fn f(e: E) -> u32 {
match e {
E::A => u32:0,
_ => u32:1,
}
}
"#;
let rhs = r#"
enum E : u3 { A = 0, B = 1 }
fn f(e: E) -> u32 {
match e {
E::B => u32:1,
_ => u32:0,
}
}
"#;
let tmp = tempfile::tempdir().unwrap();
let lhs_path = tmp.path().join("lhs.x");
let rhs_path = tmp.path().join("rhs.x");
std::fs::write(&lhs_path, lhs).unwrap();
std::fs::write(&rhs_path, rhs).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let out_default = std::process::Command::new(driver)
.args([
"dslx-equiv",
lhs_path.to_str().unwrap(),
rhs_path.to_str().unwrap(),
"--dslx_top",
"f",
"--solver",
"z3-binary",
])
.output()
.unwrap();
assert!(
out_default.status.success(),
"Expected equivalence with default enum-bound assumption. stdout: {} stderr: {}",
String::from_utf8_lossy(&out_default.stdout),
String::from_utf8_lossy(&out_default.stderr)
);
let out_with_flag = std::process::Command::new(driver)
.args([
"dslx-equiv",
lhs_path.to_str().unwrap(),
rhs_path.to_str().unwrap(),
"--dslx_top",
"f",
"--solver",
"z3-binary",
"--assume-enum-in-bound",
"true",
])
.output()
.unwrap();
assert!(
out_with_flag.status.success(),
"Expected equivalence with enum-bound assumption. stdout: {} stderr: {}",
String::from_utf8_lossy(&out_with_flag.stdout),
String::from_utf8_lossy(&out_with_flag.stderr)
);
let out_without_flag = std::process::Command::new(driver)
.args([
"dslx-equiv",
lhs_path.to_str().unwrap(),
rhs_path.to_str().unwrap(),
"--dslx_top",
"f",
"--solver",
"z3-binary",
"--assume-enum-in-bound",
"false",
])
.output()
.unwrap();
assert!(
!out_without_flag.status.success(),
"Expected inequivalence without enum-bound assumption. stdout: {} stderr: {}",
String::from_utf8_lossy(&out_without_flag.stdout),
String::from_utf8_lossy(&out_without_flag.stderr)
);
}
fn run_dslx_equiv_enum_in_bound_for_solver(solver: &str, expect_supported: bool) {
let _ = env_logger::builder().is_test(true).try_init();
let lhs = r#"
enum E : u3 { A = 0, B = 1 }
fn f(e: E) -> u32 {
match e {
E::A => u32:0,
_ => u32:1,
}
}
"#;
let rhs = r#"
enum E : u3 { A = 0, B = 1 }
fn f(e: E) -> u32 {
match e {
E::B => u32:1,
_ => u32:0,
}
}
"#;
let tmp = tempfile::tempdir().unwrap();
let lhs_path = tmp.path().join("lhs.x");
let rhs_path = tmp.path().join("rhs.x");
std::fs::write(&lhs_path, lhs).unwrap();
std::fs::write(&rhs_path, rhs).unwrap();
let toolchain_toml = tmp.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml, toolchain_toml_contents).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let out_no_flag = std::process::Command::new(driver)
.arg("--toolchain")
.arg(toolchain_toml.to_str().unwrap())
.arg("dslx-equiv")
.arg(lhs_path.to_str().unwrap())
.arg(rhs_path.to_str().unwrap())
.arg("--dslx_top")
.arg("f")
.arg("--solver")
.arg(solver)
.arg("--assume-enum-in-bound")
.arg("false")
.output()
.unwrap();
assert!(
!out_no_flag.status.success(),
"Expected inequivalence without enum-bound assumption (solver={}). stdout: {} stderr: {}",
solver,
String::from_utf8_lossy(&out_no_flag.stdout),
String::from_utf8_lossy(&out_no_flag.stderr)
);
let out_with_flag = std::process::Command::new(driver)
.arg("--toolchain")
.arg(toolchain_toml.to_str().unwrap())
.arg("dslx-equiv")
.arg(lhs_path.to_str().unwrap())
.arg(rhs_path.to_str().unwrap())
.arg("--dslx_top")
.arg("f")
.arg("--solver")
.arg(solver)
.arg("--assume-enum-in-bound")
.arg("true")
.output()
.unwrap();
if expect_supported {
assert!(
out_with_flag.status.success(),
"Expected equivalence with enum-bound assumption (solver={}). stdout: {} stderr: {}",
solver,
String::from_utf8_lossy(&out_with_flag.stdout),
String::from_utf8_lossy(&out_with_flag.stderr)
);
} else {
assert!(
!out_with_flag.status.success(),
"Expected unsupported/failure for toolchain solver with enum-bound assumption (solver={}). stdout: {} stderr: {}",
solver,
String::from_utf8_lossy(&out_with_flag.stdout),
String::from_utf8_lossy(&out_with_flag.stderr)
);
}
}
#[cfg_attr(feature="has-boolector", test_case::test_case("boolector", true; "enum_in_bound_boolector"))]
#[cfg_attr(feature="has-bitwuzla", test_case::test_case("bitwuzla", true; "enum_in_bound_bitwuzla"))]
#[cfg_attr(feature="with-z3-binary-test", test_case::test_case("z3-binary", true; "enum_in_bound_z3_binary"))]
#[cfg_attr(feature="with-bitwuzla-binary-test", test_case::test_case("bitwuzla-binary", true; "enum_in_bound_bitwuzla_binary"))]
#[cfg_attr(feature="with-boolector-binary-test", test_case::test_case("boolector-binary", true; "enum_in_bound_boolector_binary"))]
#[test_case::test_case("toolchain", false; "enum_in_bound_toolchain_unsupported")]
fn test_dslx_equiv_enum_in_bound_solver_matrix(solver: &str, expect_supported: bool) {
run_dslx_equiv_enum_in_bound_for_solver(solver, expect_supported);
}
#[test_case(true; "with_invariant_assertions")]
#[test_case(false; "without_invariant_assertions")]
fn test_dslx_stitch_pipeline_priority_sel_invariant(add_inv: bool) {
let _ = env_logger::builder().is_test(true).try_init();
let dslx = r#"fn f_cycle0(x0: u3, x4: u6) -> u6 {
{
let x6: u6 = match x0 {
u3:0x3 | u3:0x4 => x4,
u3:0x2 => u6:0x2a,
_ => u6:0x8,
};
x6
}
}
fn f_cycle1(x: u6) -> u6 {
x
}"#;
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("prio.x");
std::fs::write(&dslx_path, dslx).unwrap();
let toolchain_toml_base = if add_inv {
"[toolchain]\n[toolchain.codegen]\nadd_invariant_assertions = true\n"
} else {
"[toolchain]\n[toolchain.codegen]\nadd_invariant_assertions = false\n"
};
let toolchain_toml_contents = add_tool_path_value(toolchain_toml_base);
let toolchain_path = temp_dir.path().join("xlsynth-toolchain.toml");
std::fs::write(&toolchain_path, toolchain_toml_contents).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(command_path)
.arg("--toolchain")
.arg(toolchain_path.to_str().unwrap())
.arg("dslx-stitch-pipeline")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("f")
.output()
.unwrap();
assert!(
output.status.success(),
"command failed; stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
let has_asserts = stdout.to_lowercase().contains("assert");
if add_inv {
assert!(
has_asserts,
"Expected invariant assertions in generated Verilog when add_invariant_assertions=true, but none were found. stdout: {}",
stdout
);
} else {
assert!(
!has_asserts,
"Did not expect invariant assertions when add_invariant_assertions=false, but some were found. stdout: {}",
stdout
);
}
}
#[test]
fn test_dslx2pipeline_array_index_bounds_checking_enum_cast() {
let _ = env_logger::builder().is_test(true).try_init();
let dslx = r#"enum E : u2 {
A = 0,
B = 1,
C = 2,
D = 3,
}
fn main(sel: E, arr: u32[4]) -> u32 {
arr[(sel as u32) + 1]
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("enum_index.x");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let run = |bounds_checking: bool| -> std::process::Output {
std::process::Command::new(command_path)
.arg("dslx2pipeline")
.arg("--pipeline_stages")
.arg("1")
.arg("--delay_model")
.arg("unit")
.arg(format!("--array_index_bounds_checking={}", bounds_checking))
.arg("--use_system_verilog=true")
.arg("--flop_inputs=false")
.arg("--flop_outputs=false")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("main")
.output()
.expect("driver execution")
};
let out_false = run(false);
assert!(
out_false.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out_false.stderr)
);
let sv_false = String::from_utf8_lossy(&out_false.stdout).to_string();
let out_true = run(true);
assert!(
out_true.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out_true.stderr)
);
let sv_true = String::from_utf8_lossy(&out_true.stdout).to_string();
compare_golden_sv(
&sv_true,
"tests/test_dslx2pipeline_array_index_bounds_checking_enum_cast_true.golden.sv",
);
compare_golden_sv(
&sv_false,
"tests/test_dslx2pipeline_array_index_bounds_checking_enum_cast_false.golden.sv",
);
assert_ne!(
sv_false, sv_true,
"Expected different Verilog when bounds checking is toggled"
);
}
#[test]
fn test_dslx_stitch_pipeline_array_index_bounds_checking_enum_cast() {
let _ = env_logger::builder().is_test(true).try_init();
let dslx = r#"enum E : u2 {
A = 0,
B = 1,
C = 2,
D = 3,
}
fn foo_cycle0(sel: E, arr: u32[4]) -> u32 {
arr[(sel as u3) + u3:1]
}
fn foo_cycle1(x: u32) -> u32 { x }
"#;
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("enum_index_pipeline.x");
std::fs::write(&dslx_path, dslx).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let run = |bounds_checking: bool| -> std::process::Output {
std::process::Command::new(command_path)
.arg("dslx-stitch-pipeline")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("foo")
.arg(format!("--array_index_bounds_checking={}", bounds_checking))
.arg("--use_system_verilog=true")
.output()
.expect("driver execution")
};
let out_false = run(false);
assert!(
out_false.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out_false.stderr)
);
let sv_false = String::from_utf8_lossy(&out_false.stdout).to_string();
let out_true = run(true);
assert!(
out_true.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out_true.stderr)
);
let sv_true = String::from_utf8_lossy(&out_true.stdout).to_string();
compare_golden_sv(
&sv_true,
"tests/test_dslx_stitch_pipeline_array_index_bounds_checking_enum_cast_true.golden.sv",
);
compare_golden_sv(
&sv_false,
"tests/test_dslx_stitch_pipeline_array_index_bounds_checking_enum_cast_false.golden.sv",
);
assert_ne!(
sv_false, sv_true,
"Expected different Verilog with bounds checking toggled"
);
}
#[test]
fn test_irequiv_subcommand_json_equivalent() {
let _ = env_logger::try_init();
let temp_dir = tempfile::tempdir().unwrap();
let lhs_ir = "package add_then_sub\nfn my_main(x: bits[32]) -> bits[32] {\n add.2: bits[32] = add(x, x)\n ret sub.3: bits[32] = sub(add.2, x)\n}";
let rhs_ir = "package identity\nfn my_main(x: bits[32]) -> bits[32] {\n ret identity.2: bits[32] = identity(x)\n}";
let lhs_path = temp_dir.path().join("lhs.ir");
let rhs_path = temp_dir.path().join("rhs.ir");
std::fs::write(&lhs_path, lhs_ir).unwrap();
std::fs::write(&rhs_path, rhs_ir).unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml_path, toolchain_toml_contents).unwrap();
let json_path = temp_dir.path().join("irequiv.json");
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("ir-equiv")
.arg(lhs_path.to_str().unwrap())
.arg(rhs_path.to_str().unwrap())
.arg("--top")
.arg("my_main")
.arg("--output_json")
.arg(json_path.to_str().unwrap())
.output()
.unwrap();
assert!(
output.status.success(),
"stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let json_str = std::fs::read_to_string(&json_path).unwrap();
let v: serde_json::Value =
serde_json::from_str(json_str.trim()).expect("json file must be JSON");
assert!(
v.get("time_micros").is_some() && v["time_micros"].is_number(),
"missing/invalid time in JSON: {}",
v
);
assert_eq!(
v["success"].as_bool(),
Some(true),
"unexpected success in JSON: {}",
v
);
}
#[test]
fn test_irequiv_subcommand_json_non_equivalent() {
let _ = env_logger::try_init();
let temp_dir = tempfile::tempdir().unwrap();
let lhs_ir = "package add_then_sub\nfn my_main(x: bits[32]) -> bits[32] {\n umul.2: bits[32] = umul(x, x)\n ret udiv.3: bits[32] = udiv(umul.2, x)\n}";
let rhs_ir = "package identity\nfn my_main(x: bits[32]) -> bits[32] {\n ret identity.2: bits[32] = identity(x)\n}";
let lhs_path = temp_dir.path().join("lhs.ir");
let rhs_path = temp_dir.path().join("rhs.ir");
std::fs::write(&lhs_path, lhs_ir).unwrap();
std::fs::write(&rhs_path, rhs_ir).unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml_path, toolchain_toml_contents).unwrap();
let json_path = temp_dir.path().join("irequiv.json");
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("ir-equiv")
.arg(lhs_path.to_str().unwrap())
.arg(rhs_path.to_str().unwrap())
.arg("--top")
.arg("my_main")
.arg("--output_json")
.arg(json_path.to_str().unwrap())
.output()
.unwrap();
assert!(
!output.status.success(),
"expected failure status; stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let json_str = std::fs::read_to_string(&json_path).unwrap();
let v: serde_json::Value =
serde_json::from_str(json_str.trim()).expect("json file must be JSON");
assert!(
v.get("time_micros").is_some() && v["time_micros"].is_number(),
"missing/invalid time in JSON: {}",
v
);
assert_eq!(
v["success"].as_bool(),
Some(false),
"unexpected success in JSON: {}",
v
);
assert!(
v.get("error_str").is_some(),
"missing error_str in JSON: {}",
v
);
assert!(
v["error_str"].is_string(),
"expected error_str string in JSON: {}",
v
);
}
#[test]
fn test_dslx_equiv_subcommand_json_equivalent() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let lhs_dslx = "fn main(x: u32) -> u32 { (x + x) - x }";
let rhs_dslx = "fn main(x: u32) -> u32 { x }";
let lhs_path = temp_dir.path().join("lhs.x");
let rhs_path = temp_dir.path().join("rhs.x");
std::fs::write(&lhs_path, lhs_dslx).unwrap();
std::fs::write(&rhs_path, rhs_dslx).unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml_path, toolchain_toml_contents).unwrap();
let json_path = temp_dir.path().join("irequiv.json");
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("dslx-equiv")
.arg(lhs_path.to_str().unwrap())
.arg(rhs_path.to_str().unwrap())
.arg("--dslx_top")
.arg("main")
.arg("--output_json")
.arg(json_path.to_str().unwrap())
.output()
.unwrap();
assert!(
output.status.success(),
"stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let json_str = std::fs::read_to_string(&json_path).unwrap();
let v: serde_json::Value =
serde_json::from_str(json_str.trim()).expect("json file must be JSON");
assert!(
v.get("time_micros").is_some() && v["time_micros"].is_number(),
"missing/invalid time in JSON: {}",
v
);
assert_eq!(
v["success"].as_bool(),
Some(true),
"unexpected success in JSON: {}",
v
);
assert!(
v.get("error_str").is_some(),
"missing error_str in JSON: {}",
v
);
assert!(
v["error_str"].is_null(),
"expected null error_str in JSON: {}",
v
);
}
#[test]
fn test_dslx_equiv_subcommand_json_non_equivalent() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let lhs_dslx = "fn main(x: u32) -> u32 { (x + x) - x }";
let rhs_dslx = "fn main(x: u32) -> u32 { x + u32:1 }";
let lhs_path = temp_dir.path().join("lhs.x");
let rhs_path = temp_dir.path().join("rhs.x");
std::fs::write(&lhs_path, lhs_dslx).unwrap();
std::fs::write(&rhs_path, rhs_dslx).unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml_path, toolchain_toml_contents).unwrap();
let json_path = temp_dir.path().join("irequiv.json");
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("dslx-equiv")
.arg(lhs_path.to_str().unwrap())
.arg(rhs_path.to_str().unwrap())
.arg("--dslx_top")
.arg("main")
.arg("--output_json")
.arg(json_path.to_str().unwrap())
.output()
.unwrap();
assert!(
!output.status.success(),
"expected failure; stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let json_str = std::fs::read_to_string(&json_path).unwrap();
let v: serde_json::Value =
serde_json::from_str(json_str.trim()).expect("json file must be JSON");
assert!(
v.get("time_micros").is_some() && v["time_micros"].is_number(),
"missing/invalid time in JSON: {}",
v
);
assert_eq!(
v["success"].as_bool(),
Some(false),
"unexpected success in JSON: {}",
v
);
assert!(
v.get("error_str").is_some(),
"missing error_str in JSON: {}",
v
);
assert!(
v["error_str"].is_string(),
"expected error_str string in JSON: {}",
v
);
}
#[test]
fn test_prove_quickcheck_json_array_mixed() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("qc.x");
std::fs::write(&dslx_path, QUICKCHECK_DSLX).unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml_path, toolchain_toml_contents).unwrap();
let json_path = temp_dir.path().join("irequiv.json");
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("prove-quickcheck")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--solver")
.arg("toolchain")
.arg("--output_json")
.arg(json_path.to_str().unwrap())
.output()
.unwrap();
assert!(
!output.status.success(),
"expected failure due to one disproved QC; stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let json_str = std::fs::read_to_string(&json_path).unwrap();
let v: serde_json::Value =
serde_json::from_str(json_str.trim()).expect("json file must be JSON");
assert_eq!(v["success"].as_bool(), Some(false));
let arr = v["tests"].as_array().expect("expected tests array in JSON");
assert_eq!(arr.len(), 2, "expected two quickchecks");
let mut name_success: std::collections::HashMap<String, bool> =
std::collections::HashMap::new();
for item in arr.iter() {
let name = item["name"].as_str().expect("missing name").to_string();
assert!(item.get("time_micros").is_some() && item["time_micros"].is_number());
let ok = item["success"].as_bool().expect("missing success bool");
if !ok {
assert!(item.get("counterexample").is_some());
assert!(item["counterexample"].is_string());
}
name_success.insert(name, ok);
}
assert_eq!(name_success.get("qc_success"), Some(&true));
assert_eq!(name_success.get("qc_failure"), Some(&false));
}
#[allow(dead_code)]
fn prove_enum_in_bound_success_for_solver(solver: &str) {
let dslx = r#"
enum MyE : u2 { A = 0, B = 1 }
fn target(e: MyE) -> u32 {
match e {
MyE::A => u32:0,
MyE::B => u32:1,
}
}
pub fn good_top(sel: u1) -> u32 {
let choice = if sel == u1:0 { MyE::A } else { MyE::B };
target(choice)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("enum_good.x");
std::fs::write(&dslx_path, dslx).unwrap();
let json_path = temp_dir.path().join("out.json");
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("prove-enum-in-bound")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("good_top")
.arg("--target")
.arg("target")
.arg("--solver")
.arg(solver)
.arg("--output_json")
.arg(json_path.to_str().unwrap())
.output()
.unwrap();
assert!(
output.status.success(),
"prove-enum-in-bound should succeed; stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let json_str = std::fs::read_to_string(json_path).unwrap();
let json: serde_json::Value = serde_json::from_str(&json_str).unwrap();
assert!(json["success"].as_bool().unwrap());
assert!(json["counterexample"].is_null());
assert_eq!(json["assert_label_prefix"], "enum-in-bound");
}
#[allow(dead_code)]
fn prove_enum_in_bound_failure_for_solver(solver: &str) {
let dslx = r#"
enum MyE : u2 { A = 0, B = 1 }
fn target(e: MyE) -> u32 {
match e {
MyE::A => u32:0,
MyE::B => u32:1,
}
}
pub fn bad_top(raw: u2) -> u32 {
let coerced = raw as MyE;
target(coerced)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("enum_bad.x");
std::fs::write(&dslx_path, dslx).unwrap();
let json_path = temp_dir.path().join("out_bad.json");
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("prove-enum-in-bound")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("bad_top")
.arg("--target")
.arg("target")
.arg("--solver")
.arg(solver)
.arg("--output_json")
.arg(json_path.to_str().unwrap())
.output()
.unwrap();
assert!(
!output.status.success(),
"prove-enum-in-bound should fail; stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("Failure: Found counterexample"));
let json_str = std::fs::read_to_string(json_path).unwrap();
let json: serde_json::Value = serde_json::from_str(&json_str).unwrap();
assert!(!json["success"].as_bool().unwrap());
let counterexample = json["counterexample"].as_object().unwrap();
assert_eq!(json["assert_label_prefix"], "enum-in-bound");
assert!(counterexample["inputs"].as_array().unwrap().len() > 0);
assert!(counterexample["output"]["assertion_label"]
.as_str()
.unwrap()
.starts_with("enum-in-bound::"));
}
macro_rules! test_prove_enum_in_bound_solver {
($solver:ident, $feature:expr, $choice:expr) => {
paste::paste! {
#[cfg(feature = $feature)]
#[test]
fn [<test_prove_enum_in_bound_success_ $solver>]() {
let _ = env_logger::builder().is_test(true).try_init();
prove_enum_in_bound_success_for_solver($choice);
}
#[cfg(feature = $feature)]
#[test]
fn [<test_prove_enum_in_bound_failure_ $solver>]() {
let _ = env_logger::builder().is_test(true).try_init();
prove_enum_in_bound_failure_for_solver($choice);
}
}
};
}
test_prove_enum_in_bound_solver!(boolector, "has-boolector", "boolector");
test_prove_enum_in_bound_solver!(bitwuzla, "has-bitwuzla", "bitwuzla");
test_prove_enum_in_bound_solver!(
boolector_binary,
"with-boolector-binary-test",
"boolector-binary"
);
test_prove_enum_in_bound_solver!(z3_binary, "with-z3-binary-test", "z3-binary");
test_prove_enum_in_bound_solver!(
bitwuzla_binary,
"with-bitwuzla-binary-test",
"bitwuzla-binary"
);
#[test]
fn test_prover_all_two_equiv_tasks_succeeds() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let dir = temp_dir.path();
let lhs_equiv = "package add_then_sub\nfn my_main(x: bits[32]) -> bits[32] {\n add.2: bits[32] = add(x, x)\n ret sub.3: bits[32] = sub(add.2, x)\n}";
let rhs_equiv = "package identity\nfn my_main(x: bits[32]) -> bits[32] {\n ret identity.2: bits[32] = identity(x)\n}";
let lhs1 = dir.join("lhs1.ir");
let rhs1 = dir.join("rhs1.ir");
std::fs::write(&lhs1, lhs_equiv).unwrap();
std::fs::write(&rhs1, rhs_equiv).unwrap();
let lhs2 = dir.join("lhs2.ir");
let rhs2 = dir.join("rhs2.ir");
std::fs::write(&lhs2, lhs_equiv).unwrap();
std::fs::write(&rhs2, rhs_equiv).unwrap();
let toolchain_toml = dir.join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml, toolchain_toml_contents).unwrap();
let plan = format!(
r#"{{
"kind": "all",
"tasks": [
{{ "kind": "ir-equiv", "lhs_ir_file": "{}", "rhs_ir_file": "{}", "top": "my_main", "solver": "toolchain" }},
{{ "kind": "ir-equiv", "lhs_ir_file": "{}", "rhs_ir_file": "{}", "top": "my_main", "solver": "toolchain" }}
]
}}"#,
lhs1.display(),
rhs1.display(),
lhs2.display(),
rhs2.display()
);
let plan_path = dir.join("plan.json");
std::fs::write(&plan_path, plan).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let json_path = dir.join("prover.json");
let output = Command::new(driver)
.arg("prover")
.arg("--cores")
.arg("2")
.arg("--plan_json_file")
.arg(plan_path.to_str().unwrap())
.arg("--output_json")
.arg(json_path.to_str().unwrap())
.current_dir(dir)
.output()
.unwrap();
assert!(
output.status.success(),
"prover all(two equiv) should succeed; stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let json_str = std::fs::read_to_string(&json_path).unwrap();
let v: serde_json::Value = serde_json::from_str(json_str.trim()).unwrap();
assert_eq!(v["success"].as_bool(), Some(true));
}
#[test]
fn test_prover_single_task_reports_task_id() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let dir = temp_dir.path();
let lhs_equiv = "package add_then_sub\nfn my_main(x: bits[32]) -> bits[32] {\n add.2: bits[32] = add(x, x)\n ret sub.3: bits[32] = sub(add.2, x)\n}";
let rhs_equiv = "package identity\nfn my_main(x: bits[32]) -> bits[32] {\n ret identity.2: bits[32] = identity(x)\n}";
let lhs = dir.join("lhs.ir");
let rhs = dir.join("rhs.ir");
std::fs::write(&lhs, lhs_equiv).unwrap();
std::fs::write(&rhs, rhs_equiv).unwrap();
let toolchain_toml = dir.join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml, toolchain_toml_contents).unwrap();
let plan = format!(
r#"{{
"kind": "ir-equiv",
"lhs_ir_file": "{}",
"rhs_ir_file": "{}",
"top": "my_main",
"solver": "toolchain",
"task_id": "tid-xyz"
}}"#,
lhs.display(),
rhs.display()
);
let plan_path = dir.join("plan_single.json");
std::fs::write(&plan_path, plan).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let json_path = dir.join("prover_single.json");
let output = Command::new(driver)
.arg("prover")
.arg("--cores")
.arg("1")
.arg("--plan_json_file")
.arg(plan_path.to_str().unwrap())
.arg("--output_json")
.arg(json_path.to_str().unwrap())
.current_dir(dir)
.output()
.unwrap();
assert!(
output.status.success(),
"prover single task should succeed; stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let json_str = std::fs::read_to_string(&json_path).unwrap();
let v: serde_json::Value = serde_json::from_str(json_str.trim()).unwrap();
assert_eq!(v["success"].as_bool(), Some(true));
assert_eq!(v["plan"]["task_id"].as_str(), Some("tid-xyz"));
}
#[test]
fn test_prover_all_mixed_tasks_fails() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let dir = temp_dir.path();
let lhs_equiv = "package add_then_sub\nfn my_main(x: bits[32]) -> bits[32] {\n add.2: bits[32] = add(x, x)\n ret sub.3: bits[32] = sub(add.2, x)\n}";
let rhs_equiv = "package identity\nfn my_main(x: bits[32]) -> bits[32] {\n ret identity.2: bits[32] = identity(x)\n}";
let lhs_eq = dir.join("lhs_eq.ir");
let rhs_eq = dir.join("rhs_eq.ir");
std::fs::write(&lhs_eq, lhs_equiv).unwrap();
std::fs::write(&rhs_eq, rhs_equiv).unwrap();
let qc_path = dir.join("qc.x");
std::fs::write(&qc_path, QUICKCHECK_DSLX).unwrap();
let lhs_dslx = dir.join("lhs.x");
let rhs_dslx = dir.join("rhs.x");
std::fs::write(&lhs_dslx, "fn main(x: u32) -> u32 { (x + x) - x }").unwrap();
std::fs::write(&rhs_dslx, "fn main(x: u32) -> u32 { x }").unwrap();
let toolchain_toml = dir.join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml, toolchain_toml_contents).unwrap();
let plan = format!(
r#"{{
"kind": "all",
"tasks": [
{{ "kind": "dslx-equiv", "lhs_dslx_file": "{}", "rhs_dslx_file": "{}", "dslx_top": "main", "solver": "toolchain" }},
{{ "kind": "prove-quickcheck", "dslx_input_file": "{}", "solver": "toolchain" }}
]
}}"#,
lhs_dslx.display(),
rhs_dslx.display(),
qc_path.display()
);
let plan_path = dir.join("plan.json");
std::fs::write(&plan_path, plan).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let json_path = dir.join("prover.json");
let output = Command::new(driver)
.arg("prover")
.arg("--cores")
.arg("2")
.arg("--plan_json_file")
.arg(plan_path.to_str().unwrap())
.arg("--output_json")
.arg(json_path.to_str().unwrap())
.current_dir(dir)
.output()
.unwrap();
assert!(
!output.status.success(),
"prover all(mixed) should fail; stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let json_str = std::fs::read_to_string(&json_path).unwrap();
let v: serde_json::Value = serde_json::from_str(json_str.trim()).unwrap();
assert_eq!(v["success"].as_bool(), Some(false));
}
#[test]
fn test_prover_first_both_equiv_succeeds() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let dir = temp_dir.path();
let lhs_equiv = "package add_then_sub\nfn my_main(x: bits[32]) -> bits[32] {\n add.2: bits[32] = add(x, x)\n ret sub.3: bits[32] = sub(add.2, x)\n}";
let rhs_equiv = "package identity\nfn my_main(x: bits[32]) -> bits[32] {\n ret identity.2: bits[32] = identity(x)\n}";
let lhs1 = dir.join("lhs1.ir");
let rhs1 = dir.join("rhs1.ir");
let lhs2 = dir.join("lhs2.ir");
let rhs2 = dir.join("rhs2.ir");
for (p, s) in [
(&lhs1, lhs_equiv),
(&rhs1, rhs_equiv),
(&lhs2, lhs_equiv),
(&rhs2, rhs_equiv),
] {
std::fs::write(p, s).unwrap();
}
let toolchain_toml = dir.join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml, toolchain_toml_contents).unwrap();
let plan = format!(
r#"{{
"kind": "first",
"tasks": [
{{ "kind": "ir-equiv", "lhs_ir_file": "{}", "rhs_ir_file": "{}", "top": "my_main", "solver": "toolchain" }},
{{ "kind": "ir-equiv", "lhs_ir_file": "{}", "rhs_ir_file": "{}", "top": "my_main", "solver": "toolchain" }}
]
}}"#,
lhs1.display(),
rhs1.display(),
lhs2.display(),
rhs2.display()
);
let plan_path = dir.join("plan.json");
std::fs::write(&plan_path, plan).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let json_path = dir.join("prover.json");
let output = Command::new(driver)
.arg("prover")
.arg("--cores")
.arg("2")
.arg("--plan_json_file")
.arg(plan_path.to_str().unwrap())
.arg("--output_json")
.arg(json_path.to_str().unwrap())
.current_dir(dir)
.output()
.unwrap();
assert!(
output.status.success(),
"prover first(two equiv) should succeed; stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let json_str = std::fs::read_to_string(&json_path).unwrap();
let v: serde_json::Value = serde_json::from_str(json_str.trim()).unwrap();
assert_eq!(v["success"].as_bool(), Some(true));
}
#[test]
fn test_prover_first_both_nonequiv_fails() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let dir = temp_dir.path();
let lhs_ne = "package add_then_sub\nfn my_main(x: bits[32]) -> bits[32] {\n umul.2: bits[32] = umul(x, x)\n ret udiv.3: bits[32] = udiv(umul.2, x)\n}";
let rhs_equiv = "package identity\nfn my_main(x: bits[32]) -> bits[32] {\n ret identity.2: bits[32] = identity(x)\n}";
let lhs1 = dir.join("lhs1.ir");
let rhs1 = dir.join("rhs1.ir");
let lhs2 = dir.join("lhs2.ir");
let rhs2 = dir.join("rhs2.ir");
for (p, s) in [
(&lhs1, lhs_ne),
(&rhs1, rhs_equiv),
(&lhs2, lhs_ne),
(&rhs2, rhs_equiv),
] {
std::fs::write(p, s).unwrap();
}
let toolchain_toml = dir.join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml, toolchain_toml_contents).unwrap();
let plan = format!(
r#"{{
"kind": "first",
"tasks": [
{{ "kind": "ir-equiv", "lhs_ir_file": "{}", "rhs_ir_file": "{}", "top": "my_main", "solver": "toolchain" }},
{{ "kind": "ir-equiv", "lhs_ir_file": "{}", "rhs_ir_file": "{}", "top": "my_main", "solver": "toolchain" }}
]
}}"#,
lhs1.display(),
rhs1.display(),
lhs2.display(),
rhs2.display()
);
let plan_path = dir.join("plan.json");
std::fs::write(&plan_path, plan).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let json_path = dir.join("prover.json");
let output = Command::new(driver)
.arg("prover")
.arg("--cores")
.arg("2")
.arg("--plan_json_file")
.arg(plan_path.to_str().unwrap())
.arg("--output_json")
.arg(json_path.to_str().unwrap())
.current_dir(dir)
.output()
.unwrap();
assert!(
!output.status.success(),
"prover first(two nonequiv) should fail; stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
let json_str = std::fs::read_to_string(&json_path).unwrap();
let v: serde_json::Value = serde_json::from_str(json_str.trim()).unwrap();
assert_eq!(v["success"].as_bool(), Some(false));
}
#[test]
fn test_prover_any_both_equiv_succeeds() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let dir = temp_dir.path();
let lhs_equiv = "package add_then_sub\nfn my_main(x: bits[32]) -> bits[32] {\n add.2: bits[32] = add(x, x)\n ret sub.3: bits[32] = sub(add.2, x)\n}";
let rhs_equiv = "package identity\nfn my_main(x: bits[32]) -> bits[32] {\n ret identity.2: bits[32] = identity(x)\n}";
let lhs1 = dir.join("lhs1.ir");
let rhs1 = dir.join("rhs1.ir");
let lhs2 = dir.join("lhs2.ir");
let rhs2 = dir.join("rhs2.ir");
for (p, s) in [
(&lhs1, lhs_equiv),
(&rhs1, rhs_equiv),
(&lhs2, lhs_equiv),
(&rhs2, rhs_equiv),
] {
std::fs::write(p, s).unwrap();
}
let toolchain_toml = dir.join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml, toolchain_toml_contents).unwrap();
let plan = format!(
r#"{{
"kind": "any",
"tasks": [
{{ "kind": "ir-equiv", "lhs_ir_file": "{}", "rhs_ir_file": "{}", "top": "my_main", "solver": "toolchain" }},
{{ "kind": "ir-equiv", "lhs_ir_file": "{}", "rhs_ir_file": "{}", "top": "my_main", "solver": "toolchain" }}
]
}}"#,
lhs1.display(),
rhs1.display(),
lhs2.display(),
rhs2.display()
);
let plan_path = dir.join("plan.json");
std::fs::write(&plan_path, plan).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let json_path = dir.join("prover.json");
let output = Command::new(driver)
.arg("prover")
.arg("--cores")
.arg("2")
.arg("--plan_json_file")
.arg(plan_path.to_str().unwrap())
.arg("--output_json")
.arg(json_path.to_str().unwrap())
.current_dir(dir)
.output()
.unwrap();
assert!(
output.status.success(),
"prover any(two equiv) should succeed; stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let json_str = std::fs::read_to_string(&json_path).unwrap();
let v: serde_json::Value = serde_json::from_str(json_str.trim()).unwrap();
assert_eq!(v["success"].as_bool(), Some(true));
}
#[test]
fn test_prover_any_both_nonequiv_fails() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let dir = temp_dir.path();
let lhs_ne = "package add_then_sub\nfn my_main(x: bits[32]) -> bits[32] {\n umul.2: bits[32] = umul(x, x)\n ret udiv.3: bits[32] = udiv(umul.2, x)\n}";
let rhs_equiv = "package identity\nfn my_main(x: bits[32]) -> bits[32] {\n ret identity.2: bits[32] = identity(x)\n}";
let lhs1 = dir.join("lhs1.ir");
let rhs1 = dir.join("rhs1.ir");
let lhs2 = dir.join("lhs2.ir");
let rhs2 = dir.join("rhs2.ir");
for (p, s) in [
(&lhs1, lhs_ne),
(&rhs1, rhs_equiv),
(&lhs2, lhs_ne),
(&rhs2, rhs_equiv),
] {
std::fs::write(p, s).unwrap();
}
let toolchain_toml = dir.join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml, toolchain_toml_contents).unwrap();
let plan = format!(
r#"{{
"kind": "any",
"tasks": [
{{ "kind": "ir-equiv", "lhs_ir_file": "{}", "rhs_ir_file": "{}", "top": "my_main", "solver": "toolchain" }},
{{ "kind": "ir-equiv", "lhs_ir_file": "{}", "rhs_ir_file": "{}", "top": "my_main", "solver": "toolchain" }}
]
}}"#,
lhs1.display(),
rhs1.display(),
lhs2.display(),
rhs2.display()
);
let plan_path = dir.join("plan.json");
std::fs::write(&plan_path, plan).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let json_path = dir.join("prover.json");
let output = Command::new(driver)
.arg("prover")
.arg("--cores")
.arg("2")
.arg("--plan_json_file")
.arg(plan_path.to_str().unwrap())
.arg("--output_json")
.arg(json_path.to_str().unwrap())
.current_dir(dir)
.output()
.unwrap();
assert!(
!output.status.success(),
"prover any(two nonequiv) should fail; stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let json_str = std::fs::read_to_string(&json_path).unwrap();
let v: serde_json::Value = serde_json::from_str(json_str.trim()).unwrap();
assert_eq!(v["success"].as_bool(), Some(false));
}
#[test]
fn test_prover_any_mixed_succeeds() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let dir = temp_dir.path();
let qc_path = dir.join("qc.x");
std::fs::write(&qc_path, QUICKCHECK_DSLX).unwrap();
let lhs_dslx = dir.join("lhs.x");
let rhs_dslx = dir.join("rhs.x");
std::fs::write(&lhs_dslx, "fn main(x: u32) -> u32 { x }").unwrap();
std::fs::write(&rhs_dslx, "fn main(x: u32) -> u32 { x + u32:1 }").unwrap();
let toolchain_toml = dir.join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml, toolchain_toml_contents).unwrap();
let plan = format!(
r#"{{
"kind": "any",
"tasks": [
{{ "kind": "prove-quickcheck", "dslx_input_file": "{}", "test_filter": ".*success", "solver": "toolchain" }},
{{ "kind": "dslx-equiv", "lhs_dslx_file": "{}", "rhs_dslx_file": "{}", "dslx_top": "main", "solver": "toolchain" }}
]
}}"#,
qc_path.display(),
lhs_dslx.display(),
rhs_dslx.display()
);
let plan_path = dir.join("plan.json");
std::fs::write(&plan_path, plan).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let json_path = dir.join("prover.json");
let output = Command::new(driver)
.arg("prover")
.arg("--cores")
.arg("2")
.arg("--plan_json_file")
.arg(plan_path.to_str().unwrap())
.arg("--output_json")
.arg(json_path.to_str().unwrap())
.current_dir(dir)
.output()
.unwrap();
assert!(
output.status.success(),
"prover any(mixed) should succeed; stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let json_str = std::fs::read_to_string(&json_path).unwrap();
let v: serde_json::Value = serde_json::from_str(json_str.trim()).unwrap();
assert_eq!(v["success"].as_bool(), Some(true));
}
#[test]
fn test_prover_any_keep_running_mixed_children_finish() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let dir = temp_dir.path();
let lhs_equiv = "package add_then_sub\nfn my_main(x: bits[32]) -> bits[32] {\n add.2: bits[32] = add(x, x)\n ret sub.3: bits[32] = sub(add.2, x)\n}";
let rhs_equiv = "package identity\nfn my_main(x: bits[32]) -> bits[32] {\n ret identity.2: bits[32] = identity(x)\n}";
let lhs_ne = "package add_then_sub\nfn my_main(x: bits[32]) -> bits[32] {\n umul.2: bits[32] = umul(x, x)\n ret udiv.3: bits[32] = udiv(umul.2, x)\n}";
let lhs1 = dir.join("lhs1.ir");
let rhs1 = dir.join("rhs1.ir");
let lhs2 = dir.join("lhs2.ir");
let rhs2 = dir.join("rhs2.ir");
std::fs::write(&lhs1, lhs_equiv).unwrap();
std::fs::write(&rhs1, rhs_equiv).unwrap();
std::fs::write(&lhs2, lhs_ne).unwrap();
std::fs::write(&rhs2, rhs_equiv).unwrap();
let toolchain_toml = dir.join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml, toolchain_toml_contents).unwrap();
let plan = format!(
r#"{{
"kind": "any",
"keep_running_till_finish": true,
"tasks": [
{{ "kind": "ir-equiv", "lhs_ir_file": "{}", "rhs_ir_file": "{}", "top": "my_main", "solver": "toolchain" }},
{{ "kind": "ir-equiv", "lhs_ir_file": "{}", "rhs_ir_file": "{}", "top": "my_main", "solver": "toolchain" }}
]
}}"#,
lhs1.display(),
rhs1.display(),
lhs2.display(),
rhs2.display()
);
let plan_path = dir.join("plan.json");
std::fs::write(&plan_path, plan).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let json_path = dir.join("prover_any_keep_running.json");
let output = Command::new(driver)
.arg("prover")
.arg("--cores")
.arg("2")
.arg("--plan_json_file")
.arg(plan_path.to_str().unwrap())
.arg("--output_json")
.arg(json_path.to_str().unwrap())
.current_dir(dir)
.output()
.unwrap();
assert!(
output.status.success(),
"prover any(keep_running, mixed) should succeed; stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let json_str = std::fs::read_to_string(&json_path).unwrap();
let v: serde_json::Value = serde_json::from_str(json_str.trim()).unwrap();
assert_eq!(v["success"].as_bool(), Some(true));
let tasks = &v["plan"]["tasks"];
assert!(tasks.is_array());
let t0 = &tasks[0];
let t1 = &tasks[1];
assert_eq!(t0["outcome"].as_str(), Some("Success"));
assert_eq!(t1["outcome"].as_str(), Some("Failed"));
}
#[test]
fn test_prover_first_keep_running_both_nonequiv_finish() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let dir = temp_dir.path();
let lhs_ne = "package add_then_sub\nfn my_main(x: bits[32]) -> bits[32] {\n umul.2: bits[32] = umul(x, x)\n ret udiv.3: bits[32] = udiv(umul.2, x)\n}";
let rhs_equiv = "package identity\nfn my_main(x: bits[32]) -> bits[32] {\n ret identity.2: bits[32] = identity(x)\n}";
let lhs1 = dir.join("lhs1.ir");
let rhs1 = dir.join("rhs1.ir");
let lhs2 = dir.join("lhs2.ir");
let rhs2 = dir.join("rhs2.ir");
for (p, s) in [
(&lhs1, lhs_ne),
(&rhs1, rhs_equiv),
(&lhs2, lhs_ne),
(&rhs2, rhs_equiv),
] {
std::fs::write(p, s).unwrap();
}
let toolchain_toml = dir.join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml, toolchain_toml_contents).unwrap();
let plan = format!(
r#"{{
"kind": "first",
"keep_running_till_finish": true,
"tasks": [
{{ "kind": "ir-equiv", "lhs_ir_file": "{}", "rhs_ir_file": "{}", "top": "my_main", "solver": "toolchain" }},
{{ "kind": "ir-equiv", "lhs_ir_file": "{}", "rhs_ir_file": "{}", "top": "my_main", "solver": "toolchain" }}
]
}}"#,
lhs1.display(),
rhs1.display(),
lhs2.display(),
rhs2.display()
);
let plan_path = dir.join("plan.json");
std::fs::write(&plan_path, plan).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let json_path = dir.join("prover_first_keep_running.json");
let output = Command::new(driver)
.arg("prover")
.arg("--cores")
.arg("2")
.arg("--plan_json_file")
.arg(plan_path.to_str().unwrap())
.arg("--output_json")
.arg(json_path.to_str().unwrap())
.current_dir(dir)
.output()
.unwrap();
assert!(
!output.status.success(),
"prover first(keep_running, both nonequiv) should fail; stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let json_str = std::fs::read_to_string(&json_path).unwrap();
let v: serde_json::Value = serde_json::from_str(json_str.trim()).unwrap();
assert_eq!(v["success"].as_bool(), Some(false));
let tasks = &v["plan"]["tasks"];
assert!(tasks.is_array());
let t0 = &tasks[0];
let t1 = &tasks[1];
assert_eq!(t0["outcome"].as_str(), Some("Failed"));
assert_eq!(t1["outcome"].as_str(), Some("Failed"));
}
#[test]
fn test_prover_nested_all_any_succeeds() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let dir = temp_dir.path();
let lhs_equiv = "package add_then_sub\nfn my_main(x: bits[32]) -> bits[32] {\n add.2: bits[32] = add(x, x)\n ret sub.3: bits[32] = sub(add.2, x)\n}";
let rhs_equiv = "package identity\nfn my_main(x: bits[32]) -> bits[32] {\n ret identity.2: bits[32] = identity(x)\n}";
let lhs_ne = "package add_then_sub\nfn my_main(x: bits[32]) -> bits[32] {\n umul.2: bits[32] = umul(x, x)\n ret udiv.3: bits[32] = udiv(umul.2, x)\n}";
let lhs1 = dir.join("lhs1.ir");
let rhs1 = dir.join("rhs1.ir");
std::fs::write(&lhs1, lhs_equiv).unwrap();
std::fs::write(&rhs1, rhs_equiv).unwrap();
let lhs2 = dir.join("lhs2.ir");
let rhs2 = dir.join("rhs2.ir");
std::fs::write(&lhs2, lhs_ne).unwrap();
std::fs::write(&rhs2, rhs_equiv).unwrap();
let lhs_dslx = dir.join("lhs.x");
let rhs_dslx = dir.join("rhs.x");
std::fs::write(&lhs_dslx, "fn main(x: u32) -> u32 { (x + x) - x }").unwrap();
std::fs::write(&rhs_dslx, "fn main(x: u32) -> u32 { x }").unwrap();
let qc_path = dir.join("qc.x");
std::fs::write(&qc_path, QUICKCHECK_DSLX).unwrap();
let toolchain_toml = dir.join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml, toolchain_toml_contents).unwrap();
let plan = format!(
r#"{{
"kind": "all",
"tasks": [
{{
"kind": "any",
"tasks": [
{{ "kind": "ir-equiv", "lhs_ir_file": "{}", "rhs_ir_file": "{}", "top": "my_main", "solver": "toolchain" }},
{{ "kind": "ir-equiv", "lhs_ir_file": "{}", "rhs_ir_file": "{}", "top": "my_main", "solver": "toolchain" }}
]
}},
{{
"kind": "all",
"tasks": [
{{ "kind": "dslx-equiv", "lhs_dslx_file": "{}", "rhs_dslx_file": "{}", "dslx_top": "main", "solver": "toolchain" }},
{{ "kind": "prove-quickcheck", "dslx_input_file": "{}", "test_filter": ".*success", "solver": "toolchain" }}
]
}}
]
}}"#,
lhs1.display(),
rhs1.display(),
lhs2.display(),
rhs2.display(),
lhs_dslx.display(),
rhs_dslx.display(),
qc_path.display()
);
let plan_path = dir.join("plan.json");
std::fs::write(&plan_path, plan).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let json_path = dir.join("prover_nested_success.json");
let output = Command::new(driver)
.arg("prover")
.arg("--cores")
.arg("3")
.arg("--plan_json_file")
.arg(plan_path.to_str().unwrap())
.arg("--output_json")
.arg(json_path.to_str().unwrap())
.current_dir(dir)
.output()
.unwrap();
assert!(
output.status.success(),
"nested prover(all(any, all)) should succeed; stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let json_str = std::fs::read_to_string(&json_path).unwrap();
let v: serde_json::Value = serde_json::from_str(json_str.trim()).unwrap();
assert_eq!(v["success"].as_bool(), Some(true));
}
#[test]
fn test_prover_nested_any_all_fails() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let dir = temp_dir.path();
let lhs_ne = "package add_then_sub\nfn my_main(x: bits[32]) -> bits[32] {\n umul.2: bits[32] = umul(x, x)\n ret udiv.3: bits[32] = udiv(umul.2, x)\n}";
let rhs_equiv = "package identity\nfn my_main(x: bits[32]) -> bits[32] {\n ret identity.2: bits[32] = identity(x)\n}";
let lhs1 = dir.join("lhs1.ir");
let rhs1 = dir.join("rhs1.ir");
let lhs2 = dir.join("lhs2.ir");
let rhs2 = dir.join("rhs2.ir");
for (p, s) in [
(&lhs1, lhs_ne),
(&rhs1, rhs_equiv),
(&lhs2, lhs_ne),
(&rhs2, rhs_equiv),
] {
std::fs::write(p, s).unwrap();
}
let lhs_dslx = dir.join("lhs.x");
let rhs_dslx = dir.join("rhs.x");
std::fs::write(&lhs_dslx, "fn main(x: u32) -> u32 { (x + x) - x }").unwrap();
std::fs::write(&rhs_dslx, "fn main(x: u32) -> u32 { x }").unwrap();
let qc_path = dir.join("qc.x");
std::fs::write(&qc_path, QUICKCHECK_DSLX).unwrap();
let toolchain_toml = dir.join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml, toolchain_toml_contents).unwrap();
let plan = format!(
r#"{{
"kind": "any",
"tasks": [
{{
"kind": "all",
"tasks": [
{{ "kind": "ir-equiv", "lhs_ir_file": "{}", "rhs_ir_file": "{}", "top": "my_main", "solver": "toolchain" }},
{{ "kind": "dslx-equiv", "lhs_dslx_file": "{}", "rhs_dslx_file": "{}", "dslx_top": "main", "solver": "toolchain" }}
]
}},
{{
"kind": "all",
"tasks": [
{{ "kind": "ir-equiv", "lhs_ir_file": "{}", "rhs_ir_file": "{}", "top": "my_main", "solver": "toolchain" }},
{{ "kind": "prove-quickcheck", "dslx_input_file": "{}", "test_filter": ".*failure", "solver": "toolchain" }}
]
}}
]
}}"#,
lhs1.display(),
rhs1.display(),
lhs_dslx.display(),
rhs_dslx.display(),
lhs2.display(),
rhs2.display(),
qc_path.display()
);
let plan_path = dir.join("plan.json");
std::fs::write(&plan_path, plan).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let json_path = dir.join("prover_nested_failure.json");
let output = Command::new(driver)
.arg("prover")
.arg("--cores")
.arg("3")
.arg("--plan_json_file")
.arg(plan_path.to_str().unwrap())
.arg("--output_json")
.arg(json_path.to_str().unwrap())
.current_dir(dir)
.output()
.unwrap();
assert!(
!output.status.success(),
"nested prover(any(all(...), all(...))) should fail; stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let json_str = std::fs::read_to_string(&json_path).unwrap();
let v: serde_json::Value = serde_json::from_str(json_str.trim()).unwrap();
println!("{}", json_str);
assert_eq!(v["success"].as_bool(), Some(false));
}
#[cfg_attr(feature="has-boolector", test_case::test_case("boolector"; "dslx_equiv_uf_boolector"))]
#[cfg_attr(feature="has-bitwuzla", test_case::test_case("bitwuzla"; "dslx_equiv_uf_bitwuzla"))]
#[cfg_attr(feature="with-z3-binary-test", test_case::test_case("z3-binary"; "dslx_equiv_uf_z3_binary"))]
#[cfg_attr(feature="with-bitwuzla-binary-test", test_case::test_case("bitwuzla-binary"; "dslx_equiv_uf_bitwuzla_binary"))]
#[cfg_attr(feature="with-boolector-binary-test", test_case::test_case("boolector-binary"; "dslx_equiv_uf_boolector_binary"))]
#[allow(dead_code)]
fn test_dslx_equiv_uninterpreted_functions_solver_param(solver: &str) {
let _ = env_logger::builder().is_test(true).try_init();
let lhs_dslx = "fn g(x: u8) -> u8 { x + x }\nfn main(x: u8) -> u8 { g(x) }";
let rhs_dslx = "fn h(x: u8) -> u8 { x - x }\nfn main(x: u8) -> u8 { h(x) }";
let temp_dir = tempfile::tempdir().unwrap();
let lhs_path = temp_dir.path().join("lhs.x");
let rhs_path = temp_dir.path().join("rhs.x");
std::fs::write(&lhs_path, lhs_dslx).unwrap();
std::fs::write(&rhs_path, rhs_dslx).unwrap();
let toolchain_toml = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml, toolchain_contents).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output_no_uf = std::process::Command::new(driver)
.arg("--toolchain")
.arg(toolchain_toml.to_str().unwrap())
.arg("dslx-equiv")
.arg(lhs_path.to_str().unwrap())
.arg(rhs_path.to_str().unwrap())
.arg("--dslx_top")
.arg("main")
.arg("--solver")
.arg(solver)
.output()
.unwrap();
assert!(
!output_no_uf.status.success(),
"dslx-equiv without UF mapping should fail; stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output_no_uf.stdout),
String::from_utf8_lossy(&output_no_uf.stderr)
);
let output_with_uf = std::process::Command::new(driver)
.arg("--toolchain")
.arg(toolchain_toml.to_str().unwrap())
.arg("dslx-equiv")
.arg(lhs_path.to_str().unwrap())
.arg(rhs_path.to_str().unwrap())
.arg("--dslx_top")
.arg("main")
.arg("--solver")
.arg(solver)
.arg("--lhs_uf")
.arg("g:F")
.arg("--rhs_uf")
.arg("h:F")
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output_with_uf.stdout);
let stderr = String::from_utf8_lossy(&output_with_uf.stderr);
assert!(
output_with_uf.status.success(),
"dslx-equiv with UF mapping should succeed for solver {}.\nstdout: {}\nstderr: {}",
solver,
stdout,
stderr
);
assert!(stdout.contains("success"), "stdout: {}", stdout);
}
#[test]
fn test_ir_localized_eco_midlevel_insert_and_substitute() {
let _ = env_logger::builder().is_test(true).try_init();
let old_ir = r#"package eco_pkg
top fn eco_top(x: bits[1] id=1, y: bits[1] id=2) -> bits[1] {
and.3: bits[1] = and(x, y, id=3)
not.4: bits[1] = not(and.3, id=4)
not.5: bits[1] = not(and.3, id=5)
not.6: bits[1] = not(not.4, id=6)
and.7: bits[1] = and(not.4, not.5, id=7)
not.8: bits[1] = not(not.5, id=8)
and.9: bits[1] = and(not.6, and.7, id=9)
and.10: bits[1] = and(and.7, not.8, id=10)
ret and.11: bits[1] = and(and.9, and.10, id=11)
}
"#;
let new_ir = r#"package eco_pkg
top fn eco_top(x: bits[1] id=1, y: bits[1] id=2) -> bits[1] {
and.3: bits[1] = and(x, y, id=3)
not.4: bits[1] = not(and.3, id=4)
not.5: bits[1] = not(and.3, id=5)
and.12: bits[1] = and(not.4, not.5, id=12)
not.6: bits[1] = not(not.4, id=6)
and.7: bits[1] = and(not.4, and.12, id=7)
not.8: bits[1] = not(not.5, id=8)
and.9: bits[1] = and(not.6, and.7, id=9)
and.10: bits[1] = and(and.7, not.8, id=10)
ret and.11: bits[1] = and(and.9, and.10, id=11)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let old_path = temp_dir.path().join("old.ir");
let new_path = temp_dir.path().join("new.ir");
std::fs::write(&old_path, old_ir).unwrap();
std::fs::write(&new_path, new_ir).unwrap();
let json_out = temp_dir.path().join("eco_report.json");
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = std::process::Command::new(command_path)
.arg("ir-localized-eco")
.arg(old_path.to_str().unwrap())
.arg(new_path.to_str().unwrap())
.arg("--json_out")
.arg(json_out.to_str().unwrap())
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let json_bytes = std::fs::read(&json_out).unwrap();
let v: serde_json::Value = serde_json::from_slice(&json_bytes).unwrap();
let added = v
.get("added_node_count")
.and_then(|n| n.as_u64())
.unwrap_or(0);
assert!(
added >= 1,
"expected at least one added node; JSON: {}",
String::from_utf8_lossy(&json_bytes)
);
let patched_old_path = temp_dir.path().join("patched_old.ir");
std::fs::write(&patched_old_path, new_ir).unwrap();
let toolchain_toml = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml, toolchain_contents).unwrap();
let eq_out = std::process::Command::new(command_path)
.arg("--toolchain")
.arg(toolchain_toml.to_str().unwrap())
.arg("ir-equiv")
.arg(patched_old_path.to_str().unwrap())
.arg(new_path.to_str().unwrap())
.arg("--top")
.arg("eco_top")
.output()
.unwrap();
assert!(
eq_out.status.success(),
"ir-equiv should succeed for patched_old vs new; stdout: {} stderr: {}",
String::from_utf8_lossy(&eq_out.stdout),
String::from_utf8_lossy(&eq_out.stderr)
);
}
#[test]
fn test_ir_round_trip_strip_pos_attrs_flag() {
let ir_with_file = r#"package p
file_number 0 "foo.x"
top fn main(x: bits[8]) -> bits[8] {
arr: bits[8][4] = literal(value=[0, 1, 2, 3], id=1)
ai: bits[8] = array_index(arr, indices=[x], assumed_in_bounds=false, id=2)
ret identity.3: bits[8] = identity(ai)
}
"#;
let tmp = tempfile::tempdir().unwrap();
let ir_path = tmp.path().join("in.ir");
std::fs::write(&ir_path, ir_with_file).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let out_preserve = std::process::Command::new(driver)
.arg("ir-round-trip")
.arg(ir_path.to_str().unwrap())
.output()
.unwrap();
assert!(out_preserve.status.success());
let s_preserve = String::from_utf8_lossy(&out_preserve.stdout);
assert!(
s_preserve.contains("file_number"),
"should preserve file_number by default: {}",
s_preserve
);
assert!(
!s_preserve.contains("assumed_in_bounds=false"),
"should omit default false assumed_in_bounds"
);
let out_strip = std::process::Command::new(driver)
.arg("ir-round-trip")
.arg(ir_path.to_str().unwrap())
.arg("--strip-pos-attrs")
.arg("true")
.output()
.unwrap();
assert!(out_strip.status.success());
let s_strip = String::from_utf8_lossy(&out_strip.stdout);
assert!(
!s_strip.contains("file_number"),
"should strip file_number with flag: {}",
s_strip
);
}
#[cfg_attr(feature="has-boolector", test_case::test_case("boolector"; "qc_uf_boolector"))]
#[cfg_attr(feature="has-bitwuzla", test_case::test_case("bitwuzla"; "qc_uf_bitwuzla"))]
#[cfg_attr(feature="with-z3-binary-test", test_case::test_case("z3-binary"; "qc_uf_z3_binary"))]
#[cfg_attr(feature="with-bitwuzla-binary-test", test_case::test_case("bitwuzla-binary"; "qc_uf_bitwuzla_binary"))]
#[cfg_attr(feature="with-boolector-binary-test", test_case::test_case("boolector-binary"; "qc_uf_boolector_binary"))]
#[allow(dead_code)]
fn test_prove_quickcheck_uninterpreted_functions_solver_param(solver: &str) {
let _ = env_logger::builder().is_test(true).try_init();
let dslx = "fn g(x: u8) -> u8 { x + x }\nfn h(x: u8) -> u8 { x - x }\n#[quickcheck] fn qc_equiv(x: u8) -> bool { g(x) == h(x) }\n";
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("qc.x");
std::fs::write(&dslx_path, dslx).unwrap();
let toolchain_toml = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml, toolchain_contents).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let out_no_uf = std::process::Command::new(driver)
.arg("--toolchain")
.arg(toolchain_toml.to_str().unwrap())
.arg("prove-quickcheck")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--test_filter")
.arg("qc_equiv")
.arg("--solver")
.arg(solver)
.output()
.unwrap();
assert!(
!out_no_uf.status.success(),
"prove-quickcheck without UF mapping should fail. stdout: {} stderr: {}",
String::from_utf8_lossy(&out_no_uf.stdout),
String::from_utf8_lossy(&out_no_uf.stderr)
);
let out_with_uf = std::process::Command::new(driver)
.arg("--toolchain")
.arg(toolchain_toml.to_str().unwrap())
.arg("prove-quickcheck")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--test_filter")
.arg("qc_equiv")
.arg("--solver")
.arg(solver)
.arg("--uf")
.arg(&"g:F")
.arg("--uf")
.arg(&"h:F")
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&out_with_uf.stdout);
let stderr = String::from_utf8_lossy(&out_with_uf.stderr);
assert!(
out_with_uf.status.success(),
"prove-quickcheck with UF mapping should succeed for solver {}.\nstdout: {}\nstderr: {}",
solver,
stdout,
stderr
);
assert!(
stdout.contains("Success: All QuickChecks proved"),
"stdout: {}",
stdout
);
}
#[cfg(feature = "enable-fake-task")]
#[test]
fn test_prover_single_fake_timeout() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let dir = temp_dir.path();
let plan = r#"{
"kind": "fake",
"success": true,
"stdout_len": 0,
"stderr_len": 0,
"timeout_ms": 1
}"#;
let plan_path = dir.join("plan.json");
std::fs::write(&plan_path, plan).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let json_path = dir.join("prover_timeout_single.json");
let output = std::process::Command::new(driver)
.arg("prover")
.arg("--cores")
.arg("1")
.arg("--plan_json_file")
.arg(plan_path.to_str().unwrap())
.arg("--output_json")
.arg(json_path.to_str().unwrap())
.current_dir(dir)
.output()
.unwrap();
assert!(
!output.status.success(),
"single fake with timeout should fail; stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let json_str = std::fs::read_to_string(&json_path).unwrap();
let v: serde_json::Value = serde_json::from_str(json_str.trim()).unwrap();
assert_eq!(v["success"].as_bool(), Some(false));
assert_eq!(v["plan"]["outcome"].as_str(), Some("Timeout"));
}
#[cfg(feature = "enable-fake-task")]
#[test]
fn test_prover_all_with_timeout_indefinite() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let dir = temp_dir.path();
let plan = r#"{
"kind": "all",
"tasks": [
{ "kind": "fake", "delay_ms": 10, "success": true, "stdout_len": 0, "stderr_len": 0},
{ "kind": "fake", "success": true, "stdout_len": 0, "stderr_len": 0, "timeout_ms": 1 }
]
}"#;
let plan_path = dir.join("plan.json");
std::fs::write(&plan_path, plan).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let json_path = dir.join("prover_all_timeout.json");
let output = std::process::Command::new(driver)
.arg("prover")
.arg("--cores")
.arg("2")
.arg("--plan_json_file")
.arg(plan_path.to_str().unwrap())
.arg("--output_json")
.arg(json_path.to_str().unwrap())
.current_dir(dir)
.output()
.unwrap();
println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
assert!(
!output.status.success(),
"all(success, timeout) should be overall non-success; stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let json_str = std::fs::read_to_string(&json_path).unwrap();
let v: serde_json::Value = serde_json::from_str(json_str.trim()).unwrap();
assert_eq!(v["success"].as_bool(), Some(false));
assert_eq!(v["plan"]["outcome"].as_str(), Some("IndefiniteChildren"));
let tasks = &v["plan"]["tasks"];
assert_eq!(tasks[0]["outcome"].as_str(), Some("Success"));
assert_eq!(tasks[1]["outcome"].as_str(), Some("Timeout"));
}
#[test]
fn test_gv_read_stats_basic() {
let netlist = r#"module top(a, y);
input a;
output y;
wire a;
wire y;
wire w;
INV i0 (.A(a), .Y(w));
INV i1 (.A(w), .Y(y));
endmodule
"#;
let temp_dir = tempfile::tempdir().unwrap();
let gv_path = temp_dir.path().join("test.gv");
std::fs::write(&gv_path, netlist).unwrap();
let command_path = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(command_path)
.arg("gv-read-stats")
.arg(gv_path.to_str().unwrap())
.output()
.unwrap();
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("Instances: 2"), "{}", stdout);
assert!(stdout.contains("INV"), "{}", stdout);
}
#[cfg(feature = "enable-fake-task")]
#[test]
fn test_prover_any_with_timeout_indefinite() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let dir = temp_dir.path();
let plan = r#"{
"kind": "any",
"tasks": [
{ "kind": "fake", "delay_ms": 10, "success": false, "stdout_len": 0, "stderr_len": 0},
{ "kind": "fake", "success": true, "stdout_len": 0, "stderr_len": 0, "timeout_ms": 1 }
]
}"#;
let plan_path = dir.join("plan.json");
std::fs::write(&plan_path, plan).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let json_path = dir.join("prover_all_timeout.json");
let output = std::process::Command::new(driver)
.arg("prover")
.arg("--cores")
.arg("2")
.arg("--plan_json_file")
.arg(plan_path.to_str().unwrap())
.arg("--output_json")
.arg(json_path.to_str().unwrap())
.current_dir(dir)
.output()
.unwrap();
println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
assert!(
!output.status.success(),
"any(fail, timeout) should be overall non-success; stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let json_str = std::fs::read_to_string(&json_path).unwrap();
let v: serde_json::Value = serde_json::from_str(json_str.trim()).unwrap();
assert_eq!(v["success"].as_bool(), Some(false));
assert_eq!(v["plan"]["outcome"].as_str(), Some("IndefiniteChildren"));
let tasks = &v["plan"]["tasks"];
assert_eq!(tasks[0]["outcome"].as_str(), Some("Failed"));
assert_eq!(tasks[1]["outcome"].as_str(), Some("Timeout"));
}
#[cfg(feature = "enable-fake-task")]
#[test]
fn test_prover_first_with_timeout_and_success() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let dir = temp_dir.path();
let plan = r#"{
"kind": "first",
"tasks": [
{ "kind": "fake", "delay_ms": 100, "success": true, "stdout_len": 0, "stderr_len": 0},
{ "kind": "fake", "success": true, "stdout_len": 0, "stderr_len": 0, "timeout_ms": 1 }
]
}"#;
let plan_path = dir.join("plan.json");
std::fs::write(&plan_path, plan).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let json_path = dir.join("prover_first_timeout_success.json");
let output = std::process::Command::new(driver)
.arg("prover")
.arg("--cores")
.arg("2")
.arg("--plan_json_file")
.arg(plan_path.to_str().unwrap())
.arg("--output_json")
.arg(json_path.to_str().unwrap())
.current_dir(dir)
.output()
.unwrap();
println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
assert!(
output.status.success(),
"first(success, timeout) should be overall success; stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let json_str = std::fs::read_to_string(&json_path).unwrap();
let v: serde_json::Value = serde_json::from_str(json_str.trim()).unwrap();
assert_eq!(v["success"].as_bool(), Some(true));
assert_eq!(v["plan"]["outcome"].as_str(), Some("Success"));
let tasks = &v["plan"]["tasks"];
assert_eq!(tasks[0]["outcome"].as_str(), Some("Success"));
assert_eq!(tasks[1]["outcome"].as_str(), Some("Timeout"));
}
#[test]
#[cfg(feature = "unstable-dslx-specialize")]
fn test_dslx_specialize_basic() {
let _ = env_logger::builder().is_test(true).try_init();
let dslx_source = r#"
fn id<N: u32>(x: bits[N]) -> bits[N] { x }
fn helper<M: u32>(x: bits[M]) -> bits[M] { id(x) }
fn call() -> bits[32] { helper(bits[32]:0x0) }
"#;
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("parametric.x");
std::fs::write(&dslx_path, dslx_source).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("dslx-specialize")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("call")
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
assert!(
output.status.success(),
"dslx-specialize failed (status={});\nstdout:{}\nstderr:{}",
output.status,
stdout,
String::from_utf8_lossy(&output.stderr)
);
assert!(
!stdout.contains("fn id<N"),
"Expected parametric definition to be removed.\nSpecialized module:\n{}",
stdout
);
assert!(
!stdout.contains("fn helper<M"),
"Expected helper parametric definition to be removed.\nSpecialized module:\n{}",
stdout
);
assert!(
stdout.contains("fn id_"),
"Expected a specialized clone of id().\nSpecialized module:\n{}",
stdout
);
assert!(
stdout.contains("fn helper_"),
"Expected a specialized clone of helper().\nSpecialized module:\n{}",
stdout
);
assert!(stdout.contains("fn call"));
}
#[test]
fn test_aig_equiv_reports_equivalent_designs() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml_path, toolchain_toml_contents).unwrap();
let lhs_gate = two_input_gate_fn("and_lhs", |a, b, gb| gb.add_and_binary(a, b));
let rhs_gate = two_input_gate_fn("and_rhs", |a, b, gb| gb.add_and_binary(b, a));
let lhs_aag = write_aiger_file(&temp_dir, "lhs.aag", &lhs_gate);
let rhs_aag = write_aiger_file(&temp_dir, "rhs.aag", &rhs_gate);
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("aig-equiv")
.arg(lhs_aag.to_str().unwrap())
.arg(rhs_aag.to_str().unwrap())
.arg("--solver")
.arg("toolchain")
.output()
.unwrap();
assert!(
output.status.success(),
"aig-equiv failed: stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert!(
String::from_utf8_lossy(&output.stdout)
.contains("[aig-equiv] success: Solver proved equivalence"),
"unexpected stdout: {}",
String::from_utf8_lossy(&output.stdout)
);
}
#[test]
fn test_aig_equiv_reports_non_equivalent_designs() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml_path, toolchain_toml_contents).unwrap();
let lhs_gate = two_input_gate_fn("and_lhs", |a, b, gb| gb.add_and_binary(a, b));
let rhs_gate = two_input_gate_fn("or_rhs", |a, b, gb| gb.add_or_binary(a, b));
let lhs_aag = write_aiger_file(&temp_dir, "lhs_not_equiv.aag", &lhs_gate);
let rhs_aag = write_aiger_file(&temp_dir, "rhs_not_equiv.aag", &rhs_gate);
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("aig-equiv")
.arg(lhs_aag.to_str().unwrap())
.arg(rhs_aag.to_str().unwrap())
.arg("--solver")
.arg("toolchain")
.output()
.unwrap();
assert!(
!output.status.success(),
"expected aig-equiv to fail for inequivalent designs; stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert!(
String::from_utf8_lossy(&output.stderr).contains("[aig-equiv] failure:"),
"unexpected stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn test_aig_equiv_accepts_binary_aiger() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml_path, toolchain_toml_contents).unwrap();
let lhs_gate = two_input_gate_fn("and_lhs", |a, b, gb| gb.add_and_binary(a, b));
let rhs_gate = two_input_gate_fn("and_rhs", |a, b, gb| gb.add_and_binary(b, a));
let lhs_aig = write_aiger_binary_file(&temp_dir, "lhs.aig", &lhs_gate);
let rhs_aig = write_aiger_binary_file(&temp_dir, "rhs.aig", &rhs_gate);
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("aig-equiv")
.arg(lhs_aig.to_str().unwrap())
.arg(rhs_aig.to_str().unwrap())
.arg("--solver")
.arg("toolchain")
.output()
.unwrap();
assert!(
output.status.success(),
"aig-equiv failed on binary AIGER: stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn test_g8r_equiv_reports_equivalent_designs() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml_path, toolchain_toml_contents).unwrap();
let lhs_gate = two_input_gate_fn("and_lhs", |a, b, gb| gb.add_and_binary(a, b));
let rhs_gate = two_input_gate_fn("and_rhs", |a, b, gb| gb.add_and_binary(b, a));
let lhs_g8r_path = temp_dir.path().join("lhs.g8r");
let rhs_g8r_path = temp_dir.path().join("rhs.g8r");
std::fs::write(&lhs_g8r_path, lhs_gate.to_string()).unwrap();
std::fs::write(&rhs_g8r_path, rhs_gate.to_string()).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("g8r-equiv")
.arg(lhs_g8r_path.to_str().unwrap())
.arg(rhs_g8r_path.to_str().unwrap())
.arg("--solver")
.arg("toolchain")
.output()
.unwrap();
assert!(
output.status.success(),
"g8r-equiv failed: stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert!(
String::from_utf8_lossy(&output.stdout)
.contains("[g8r-equiv] success: Solver proved equivalence"),
"unexpected stdout: {}",
String::from_utf8_lossy(&output.stdout)
);
}
#[test]
fn test_g8r_ir_equiv_reports_equivalent_designs() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml_path, toolchain_toml_contents).unwrap();
let lhs_gate = two_input_gate_fn("main", |a, b, gb| gb.add_and_binary(a, b));
let lhs_g8r_path = temp_dir.path().join("lhs.g8r");
std::fs::write(&lhs_g8r_path, lhs_gate.to_string()).unwrap();
let rhs_ir = r#"package sample
top fn main(a: bits[1] id=1, b: bits[1] id=2) -> bits[1] {
ret and.3: bits[1] = and(a, b, id=3)
}
"#;
let rhs_ir_path = temp_dir.path().join("rhs.ir");
std::fs::write(&rhs_ir_path, rhs_ir).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("g8r-ir-equiv")
.arg(lhs_g8r_path.to_str().unwrap())
.arg(rhs_ir_path.to_str().unwrap())
.arg("--top")
.arg("main")
.arg("--solver")
.arg("toolchain")
.output()
.unwrap();
assert!(
output.status.success(),
"g8r-ir-equiv failed for equivalent design; stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert!(
String::from_utf8_lossy(&output.stdout)
.contains("[g8r-ir-equiv] success: Solver proved equivalence"),
"unexpected stdout: {}",
String::from_utf8_lossy(&output.stdout)
);
}
#[test]
fn test_g8r_ir_equiv_reports_non_equivalent_designs() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml_path, toolchain_toml_contents).unwrap();
let lhs_gate = two_input_gate_fn("main", |a, b, gb| gb.add_and_binary(a, b));
let lhs_g8r_path = temp_dir.path().join("lhs_not_equiv.g8r");
std::fs::write(&lhs_g8r_path, lhs_gate.to_string()).unwrap();
let rhs_ir = r#"package sample
top fn main(a: bits[1] id=1, b: bits[1] id=2) -> bits[1] {
ret or.3: bits[1] = or(a, b, id=3)
}
"#;
let rhs_ir_path = temp_dir.path().join("rhs_not_equiv.ir");
std::fs::write(&rhs_ir_path, rhs_ir).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("g8r-ir-equiv")
.arg(lhs_g8r_path.to_str().unwrap())
.arg(rhs_ir_path.to_str().unwrap())
.arg("--top")
.arg("main")
.arg("--solver")
.arg("toolchain")
.output()
.unwrap();
assert!(
!output.status.success(),
"expected g8r-ir-equiv to fail for inequivalent designs; stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert!(
String::from_utf8_lossy(&output.stderr).contains("[g8r-ir-equiv] failure:"),
"unexpected stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn test_g8r_ir_equiv_preserves_scalar_multi_output_order() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml_path, toolchain_toml_contents).unwrap();
let mut builder = GateBuilder::new("main".to_string(), GateBuilderOptions::no_opt());
let a = builder.add_input("a".to_string(), 1);
let b = builder.add_input("b".to_string(), 1);
builder.add_output("o0".to_string(), AigBitVector::from_bit(*a.get_lsb(0)));
builder.add_output("o1".to_string(), AigBitVector::from_bit(*b.get_lsb(0)));
let lhs_gate = builder.build();
let lhs_g8r_path = temp_dir.path().join("lhs_multi_output.g8r");
std::fs::write(&lhs_g8r_path, lhs_gate.to_string()).unwrap();
let rhs_ir = r#"package sample
top fn main(a: bits[1] id=1, b: bits[1] id=2) -> (bits[1], bits[1]) {
ret tuple.3: (bits[1], bits[1]) = tuple(a, b, id=3)
}
"#;
let rhs_ir_path = temp_dir.path().join("rhs_multi_output.ir");
std::fs::write(&rhs_ir_path, rhs_ir).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("g8r-ir-equiv")
.arg(lhs_g8r_path.to_str().unwrap())
.arg(rhs_ir_path.to_str().unwrap())
.arg("--top")
.arg("main")
.arg("--solver")
.arg("toolchain")
.output()
.unwrap();
assert!(
output.status.success(),
"g8r-ir-equiv should preserve scalar multi-output order; stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn test_aig_ir_equiv_reports_equivalent_designs() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml_path, toolchain_toml_contents).unwrap();
let lhs_gate = two_input_gate_fn("main", |a, b, gb| gb.add_and_binary(a, b));
let lhs_aag = write_aiger_file(&temp_dir, "lhs.aag", &lhs_gate);
let rhs_ir = r#"package sample
top fn main(a: bits[1] id=1, b: bits[1] id=2) -> bits[1] {
ret and.3: bits[1] = and(a, b, id=3)
}
"#;
let rhs_ir_path = temp_dir.path().join("rhs.ir");
std::fs::write(&rhs_ir_path, rhs_ir).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("aig-ir-equiv")
.arg(lhs_aag.to_str().unwrap())
.arg(rhs_ir_path.to_str().unwrap())
.arg("--top")
.arg("main")
.arg("--solver")
.arg("toolchain")
.output()
.unwrap();
assert!(
output.status.success(),
"aig-ir-equiv failed for equivalent design; stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert!(
String::from_utf8_lossy(&output.stdout)
.contains("[aig-ir-equiv] success: Solver proved equivalence"),
"unexpected stdout: {}",
String::from_utf8_lossy(&output.stdout)
);
}
#[test]
fn test_aig_ir_equiv_reports_non_equivalent_designs() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml_path, toolchain_toml_contents).unwrap();
let lhs_gate = two_input_gate_fn("main", |a, b, gb| gb.add_and_binary(a, b));
let lhs_aag = write_aiger_file(&temp_dir, "lhs_not_equiv.aag", &lhs_gate);
let rhs_ir = r#"package sample
top fn main(a: bits[1] id=1, b: bits[1] id=2) -> bits[1] {
ret or.3: bits[1] = or(a, b, id=3)
}
"#;
let rhs_ir_path = temp_dir.path().join("rhs_not_equiv.ir");
std::fs::write(&rhs_ir_path, rhs_ir).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("aig-ir-equiv")
.arg(lhs_aag.to_str().unwrap())
.arg(rhs_ir_path.to_str().unwrap())
.arg("--top")
.arg("main")
.arg("--solver")
.arg("toolchain")
.output()
.unwrap();
assert!(
!output.status.success(),
"expected aig-ir-equiv to fail for inequivalent designs; stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn test_aig_ir_equiv_accepts_binary_aiger() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml_path, toolchain_toml_contents).unwrap();
let lhs_gate = two_input_gate_fn("main", |a, b, gb| gb.add_and_binary(a, b));
let lhs_aig = write_aiger_binary_file(&temp_dir, "lhs.aig", &lhs_gate);
let rhs_ir = r#"package sample
top fn main(a: bits[1] id=1, b: bits[1] id=2) -> bits[1] {
ret and.3: bits[1] = and(a, b, id=3)
}
"#;
let rhs_ir_path = temp_dir.path().join("rhs.ir");
std::fs::write(&rhs_ir_path, rhs_ir).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("aig-ir-equiv")
.arg(lhs_aig.to_str().unwrap())
.arg(rhs_ir_path.to_str().unwrap())
.arg("--top")
.arg("main")
.arg("--solver")
.arg("toolchain")
.output()
.unwrap();
assert!(
output.status.success(),
"aig-ir-equiv failed on binary AIGER: stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn test_aig_ir_equiv_accepts_vacuous_unit_function() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml_path, toolchain_toml_contents).unwrap();
let lhs_aag = temp_dir.path().join("empty.aag");
std::fs::write(&lhs_aag, "aag 0 0 0 0 0\nc\n").unwrap();
let rhs_ir = r#"package sample
top fn main() -> () {
ret tuple.1: () = tuple()
}
"#;
let rhs_ir_path = temp_dir.path().join("rhs_unit.ir");
std::fs::write(&rhs_ir_path, rhs_ir).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("aig-ir-equiv")
.arg(lhs_aag.to_str().unwrap())
.arg(rhs_ir_path.to_str().unwrap())
.arg("--top")
.arg("main")
.arg("--solver")
.arg("toolchain")
.output()
.unwrap();
assert!(
output.status.success(),
"aig-ir-equiv failed on vacuous unit function: stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn test_aig_ir_equiv_repacks_flattened_aiger_inputs() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml_path, toolchain_toml_contents).unwrap();
let lhs_gate = two_input_gate_fn("main", |a, b, gb| gb.add_and_binary(a, b));
let lhs_aag = write_aiger_file(&temp_dir, "lhs_flat.aag", &lhs_gate);
let rhs_ir = r#"package sample
top fn main(x: bits[2] id=1) -> bits[1] {
x0: bits[1] = bit_slice(x, start=0, width=1, id=2)
x1: bits[1] = bit_slice(x, start=1, width=1, id=3)
ret and.4: bits[1] = and(x0, x1, id=4)
}
"#;
let rhs_ir_path = temp_dir.path().join("rhs_flat.ir");
std::fs::write(&rhs_ir_path, rhs_ir).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("aig-ir-equiv")
.arg(lhs_aag.to_str().unwrap())
.arg(rhs_ir_path.to_str().unwrap())
.arg("--top")
.arg("main")
.arg("--solver")
.arg("toolchain")
.output()
.unwrap();
assert!(
output.status.success(),
"aig-ir-equiv should succeed after repacking flattened AIGER inputs; stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn test_aig_ir_equiv_treats_native_scalar_multi_output_as_flat_return_bits() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml_path, toolchain_toml_contents).unwrap();
let mut builder = GateBuilder::new("main".to_string(), GateBuilderOptions::no_opt());
let a = builder.add_input("a".to_string(), 1);
let b = builder.add_input("b".to_string(), 1);
builder.add_output("o0".to_string(), AigBitVector::from_bit(*a.get_lsb(0)));
builder.add_output("o1".to_string(), AigBitVector::from_bit(*b.get_lsb(0)));
let lhs_aag = write_aiger_file(&temp_dir, "lhs_native_multi_output.aag", &builder.build());
let rhs_ir = r#"package sample
top fn main(a: bits[1] id=1, b: bits[1] id=2) -> (bits[1], bits[1]) {
ret tuple.3: (bits[1], bits[1]) = tuple(a, b, id=3)
}
"#;
let rhs_ir_path = temp_dir.path().join("rhs_native_multi_output.ir");
std::fs::write(&rhs_ir_path, rhs_ir).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("aig-ir-equiv")
.arg(lhs_aag.to_str().unwrap())
.arg(rhs_ir_path.to_str().unwrap())
.arg("--top")
.arg("main")
.arg("--solver")
.arg("toolchain")
.output()
.unwrap();
assert!(
!output.status.success(),
"aig-ir-equiv should interpret native scalar outputs via signature packing; stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert!(
String::from_utf8_lossy(&output.stderr).contains("NOT equivalent"),
"expected non-equivalence report in stderr, got: {}",
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn test_aig_ir_equiv_reports_signature_mismatch() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml_path, toolchain_toml_contents).unwrap();
let lhs_gate = two_input_gate_fn("main", |a, b, gb| gb.add_and_binary(a, b));
let lhs_aag = write_aiger_file(&temp_dir, "lhs_sig.aag", &lhs_gate);
let rhs_ir = r#"package sample
top fn main(x: bits[3] id=1) -> bits[1] {
ret literal.2: bits[1] = literal(value=1, id=2)
}
"#;
let rhs_ir_path = temp_dir.path().join("rhs_sig_mismatch.ir");
std::fs::write(&rhs_ir_path, rhs_ir).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("--toolchain")
.arg(toolchain_toml_path.to_str().unwrap())
.arg("aig-ir-equiv")
.arg(lhs_aag.to_str().unwrap())
.arg(rhs_ir_path.to_str().unwrap())
.arg("--top")
.arg("main")
.arg("--solver")
.arg("toolchain")
.output()
.unwrap();
assert!(
!output.status.success(),
"expected signature mismatch to fail; stdout: {} stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert!(
String::from_utf8_lossy(&output.stderr).contains("mismatch"),
"expected mismatch error in stderr, got: {}",
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn test_aig_ir_equiv_roundtrip_from_ir2g8r_interesting_signatures_aiger_out() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml_path, toolchain_toml_contents).unwrap();
for case in interesting_ir_roundtrip_cases() {
assert_aig_ir_equiv_roundtrip_for_ir_case(
&temp_dir,
&toolchain_toml_path,
case.name,
case.ir_text,
);
}
}
#[test]
fn test_aig2ir_ir_equiv_roundtrip_from_ir2g8r_interesting_signatures_aiger_out() {
let _ = env_logger::builder().is_test(true).try_init();
let temp_dir = tempfile::tempdir().unwrap();
let toolchain_toml_path = temp_dir.path().join("xlsynth-toolchain.toml");
let toolchain_toml_contents = add_tool_path_value("[toolchain]\n");
std::fs::write(&toolchain_toml_path, toolchain_toml_contents).unwrap();
for case in interesting_ir_roundtrip_cases() {
assert_aig2ir_ir_equiv_roundtrip_for_ir_case(
&temp_dir,
&toolchain_toml_path,
case.name,
case.ir_text,
);
}
}
#[test]
#[cfg(feature = "unstable-dslx-specialize")]
fn test_dslx_specialize_parametric_top_binding() {
let _ = env_logger::builder().is_test(true).try_init();
let dslx_source = r#"
fn id<N: u32>(x: bits[N]) -> bits[N] { x }
fn call<N: u32>(x: bits[N]) -> bits[N] { id(x) }
"#;
let temp_dir = tempfile::tempdir().unwrap();
let dslx_path = temp_dir.path().join("parametric_top.x");
std::fs::write(&dslx_path, dslx_source).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("dslx-specialize")
.arg("--dslx_input_file")
.arg(dslx_path.to_str().unwrap())
.arg("--dslx_top")
.arg("call<u32:32>")
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
assert!(
output.status.success(),
"dslx-specialize failed (status={});\nstdout:{}\nstderr:{}",
output.status,
stdout,
String::from_utf8_lossy(&output.stderr)
);
assert!(
stdout.contains("fn call_32"),
"Expected specialized clone of call(); module:\n{}",
stdout
);
assert!(
stdout.contains("fn id_32"),
"Expected specialized clone of id(); module:\n{}",
stdout
);
assert!(
!stdout.contains("fn call<N"),
"Expected parametric top definition to be removed; module:\n{}",
stdout
);
}
#[test]
fn test_ir_query_anycmp_anymul_single_user() {
let _ = env_logger::builder().is_test(true).try_init();
let ir_text = r#"package test
fn main(x: bits[8] id=1, y: bits[8] id=2) -> bits[1] {
m: bits[8] = umul(x, y, id=3)
ret cmp: bits[1] = eq(m, x, id=4)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("test.ir");
std::fs::write(&ir_path, ir_text).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("ir-query")
.arg(ir_path.to_str().unwrap())
.arg("$anycmp($anymul[1u](x, y), _)")
.output()
.unwrap();
assert!(
output.status.success(),
"ir-query failed (status={});\nstdout:{}\nstderr:{}",
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("ret cmp: bits[1] = eq("),
"unexpected ir-query output: {}",
stdout
);
}
#[test]
fn test_ir_query_wildcard_lsb_prio_matches_both_one_hot_variants() {
let _ = env_logger::builder().is_test(true).try_init();
let ir_text = r#"package test
fn main(x: bits[4] id=1) -> bits[5] {
oh_t: bits[5] = one_hot(x, lsb_prio=true, id=2)
oh_f: bits[5] = one_hot(x, lsb_prio=false, id=3)
ret out: bits[5] = xor(oh_t, oh_f, id=4)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("test.ir");
std::fs::write(&ir_path, ir_text).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("ir-query")
.arg(ir_path.to_str().unwrap())
.arg("one_hot(x, lsb_prio=_)")
.output()
.unwrap();
assert!(
output.status.success(),
"ir-query failed (status={});\nstdout:{}\nstderr:{}",
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let expected = concat!(
"oh_t: bits[5] = one_hot(x, lsb_prio=true, id=2)\n",
"oh_f: bits[5] = one_hot(x, lsb_prio=false, id=3)\n",
);
assert_eq!(stdout, expected, "unexpected stdout: {}", stdout);
}
#[test]
fn test_ir_query_check_query_true_does_not_read_ir_file() {
let _ = env_logger::builder().is_test(true).try_init();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("ir-query")
.arg("this_file_should_not_be_read.ir")
.arg("$anycmp(x, _)")
.arg("--check_query=true")
.output()
.unwrap();
assert!(
output.status.success(),
"ir-query --check_query failed (status={});\nstdout:{}\nstderr:{}",
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert_eq!(
String::from_utf8_lossy(&output.stdout),
"",
"expected no stdout"
);
assert_eq!(
String::from_utf8_lossy(&output.stderr),
"",
"expected no stderr"
);
}
#[test]
fn test_ir_query_show_file_prefixes_matches_with_path() {
let _ = env_logger::builder().is_test(true).try_init();
let ir_text = r#"package test
fn main(x: bits[4] id=1) -> bits[5] {
oh_t: bits[5] = one_hot(x, lsb_prio=true, id=2)
oh_f: bits[5] = one_hot(x, lsb_prio=false, id=3)
ret out: bits[5] = xor(oh_t, oh_f, id=4)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("test.ir");
std::fs::write(&ir_path, ir_text).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("ir-query")
.arg(ir_path.to_str().unwrap())
.arg("one_hot(x, lsb_prio=_)")
.arg("--show-file=true")
.output()
.unwrap();
assert!(
output.status.success(),
"ir-query failed (status={});\nstdout:{}\nstderr:{}",
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let ir_path_str = ir_path.to_str().unwrap();
let expected = format!(
"{}: oh_t: bits[5] = one_hot(x, lsb_prio=true, id=2)\n{}: oh_f: bits[5] = one_hot(x, lsb_prio=false, id=3)\n",
ir_path_str, ir_path_str
);
assert_eq!(stdout, expected, "unexpected stdout: {}", stdout);
}
#[test]
fn test_ir_query_malformed_query_reports_error() {
let _ = env_logger::builder().is_test(true).try_init();
let ir_text = r#"package test
fn main(x: bits[4] id=1) -> bits[5] {
ret oh: bits[5] = one_hot(x, lsb_prio=true, id=2)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("test.ir");
std::fs::write(&ir_path, ir_text).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("ir-query")
.arg(ir_path.to_str().unwrap())
.arg("one_hot(x, lsb_prio=maybe)")
.output()
.unwrap();
assert!(!output.status.success(), "expected ir-query to fail");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("xlsynth-driver: ir-query:"),
"unexpected stderr: {}",
stderr
);
assert!(
stderr.contains("Failed to parse query:"),
"unexpected stderr: {}",
stderr
);
assert!(
stderr.contains("lsb_prio expects boolean literal or '_'"),
"unexpected stderr: {}",
stderr
);
}
#[test]
fn test_ir_rewrite_rewrites_all_matches_in_single_pass_and_prints_package() {
let _ = env_logger::builder().is_test(true).try_init();
let ir_text = r#"package test
top fn main(p: bits[1] id=1, x: bits[8] id=2, y: bits[8] id=3) -> (bits[8], bits[8]) {
sx: bits[8] = sel(p, cases=[x, x], id=10)
sy: bits[8] = sel(p, cases=[y, y], id=11)
ret tuple.12: (bits[8], bits[8]) = tuple(sx, sy, id=12)
}
"#;
let expected = r#"package test
top fn main(p: bits[1] id=1, x: bits[8] id=2, y: bits[8] id=3) -> (bits[8], bits[8]) {
ret tuple.12: (bits[8], bits[8]) = tuple(x, y, id=12)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("test.ir");
std::fs::write(&ir_path, ir_text).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("ir-rewrite")
.arg(ir_path.to_str().unwrap())
.arg("sel(selector=p, cases=[v, v])")
.arg("v")
.output()
.unwrap();
assert!(
output.status.success(),
"ir-rewrite failed (status={});\nstdout:{}\nstderr:{}",
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert_eq!(String::from_utf8_lossy(&output.stdout), expected);
assert_eq!(String::from_utf8_lossy(&output.stderr), "");
}
#[test]
fn test_ir_rewrite_all_matches_does_not_rewrite_new_helper_nodes() {
let _ = env_logger::builder().is_test(true).try_init();
let ir_text = r#"package test
top fn main() -> bits[1] {
ret literal.1: bits[1] = literal(value=0, id=1)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("test.ir");
std::fs::write(&ir_path, ir_text).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("ir-rewrite")
.arg(ir_path.to_str().unwrap())
.arg("literal(0)")
.arg("not($const(value=0, width=1))")
.output()
.unwrap();
assert!(
output.status.success(),
"ir-rewrite failed (status={});\nstdout:{}\nstderr:{}",
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("literal(value=0"),
"expected helper zero literal to remain in output: {}",
stdout
);
assert!(
stdout.contains("ret not."),
"expected root to be rewritten to not(...): {}",
stdout
);
assert!(
!stdout.contains("not(not("),
"new helper nodes should not be rewritten in same pass: {}",
stdout
);
}
#[test]
fn test_ir_rewrite_target_node_rewrites_exact_node() {
let _ = env_logger::builder().is_test(true).try_init();
let ir_text = r#"package test
top fn main(p: bits[1] id=1, x: bits[8] id=2, y: bits[8] id=3) -> (bits[8], bits[8]) {
sx: bits[8] = sel(p, cases=[x, x], id=10)
sy: bits[8] = sel(p, cases=[y, y], id=11)
ret tuple.12: (bits[8], bits[8]) = tuple(sx, sy, id=12)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("test.ir");
std::fs::write(&ir_path, ir_text).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("ir-rewrite")
.arg(ir_path.to_str().unwrap())
.arg("sel(selector=p, cases=[v, v])")
.arg("v")
.arg("--target")
.arg("11")
.output()
.unwrap();
assert!(
output.status.success(),
"ir-rewrite --target failed (status={});\nstdout:{}\nstderr:{}",
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("sx: bits[8] = sel(p, cases=[x, x], id=10)"),
"earlier matching node should remain: {}",
stdout
);
assert!(
stdout.contains("tuple(sx, y, id=12)"),
"target node should be replaced in tuple operand: {}",
stdout
);
}
#[test]
fn test_ir_rewrite_target_operand_rewrites_exact_slot() {
let _ = env_logger::builder().is_test(true).try_init();
let ir_text = r#"package test
top fn main(x: bits[8] id=1) -> bits[8] {
zero: bits[8] = literal(value=0, id=10)
ret add.20: bits[8] = add(x, zero, id=20)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("test.ir");
std::fs::write(&ir_path, ir_text).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("ir-rewrite")
.arg(ir_path.to_str().unwrap())
.arg("literal(0)")
.arg("$const(value=1, width=8)")
.arg("--target")
.arg("20:1")
.output()
.unwrap();
assert!(
output.status.success(),
"ir-rewrite --target operand failed (status={});\nstdout:{}\nstderr:{}",
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("literal(value=1"),
"expected replacement literal: {}",
stdout
);
assert!(
stdout.contains("ret add.20: bits[8] = add(x, literal."),
"expected only add operand slot to be rewritten: {}",
stdout
);
}
#[test]
fn test_ir_rewrite_target_operand_preserves_other_users() {
let _ = env_logger::builder().is_test(true).try_init();
let ir_text = r#"package test
top fn main(x: bits[8] id=1) -> (bits[8], bits[8]) {
zero: bits[8] = literal(value=0, id=10)
add.20: bits[8] = add(x, zero, id=20)
sub.21: bits[8] = sub(x, zero, id=21)
ret tuple.22: (bits[8], bits[8]) = tuple(add.20, sub.21, id=22)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("test.ir");
std::fs::write(&ir_path, ir_text).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("ir-rewrite")
.arg(ir_path.to_str().unwrap())
.arg("literal(0)")
.arg("$const(value=1, width=8)")
.arg("--target")
.arg("20:1")
.output()
.unwrap();
assert!(
output.status.success(),
"ir-rewrite --target operand failed (status={});\nstdout:{}\nstderr:{}",
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("add.20: bits[8] = add(x, literal."),
"expected targeted add operand rewrite: {}",
stdout
);
assert!(
stdout.contains("sub.21: bits[8] = sub(x, zero, id=21)"),
"non-target user should still reference original zero: {}",
stdout
);
}
#[test]
fn test_ir_rewrite_malformed_target_reports_error_before_ir_io() {
let _ = env_logger::builder().is_test(true).try_init();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("ir-rewrite")
.arg("this_file_should_not_be_read.ir")
.arg("literal(0)")
.arg("$const(value=1, width=1)")
.arg("--target=bad")
.output()
.unwrap();
assert!(!output.status.success(), "expected ir-rewrite to fail");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("Invalid rewrite target"),
"unexpected stderr: {}",
stderr
);
assert!(
!stderr.contains("Failed to read"),
"expected target parse rejection before IR I/O: {}",
stderr
);
}
#[test]
fn test_ir_rewrite_target_missing_node_fails_with_no_stdout() {
let _ = env_logger::builder().is_test(true).try_init();
let ir_text = r#"package test
top fn main(x: bits[8] id=1) -> bits[8] {
ret identity.2: bits[8] = identity(x, id=2)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("test.ir");
std::fs::write(&ir_path, ir_text).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("ir-rewrite")
.arg(ir_path.to_str().unwrap())
.arg("identity(x)")
.arg("x")
.arg("--target")
.arg("99")
.output()
.unwrap();
assert!(!output.status.success(), "expected ir-rewrite to fail");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("target node text_id=99 not found"),
"unexpected stderr: {}",
stderr
);
assert_eq!(String::from_utf8_lossy(&output.stdout), "");
}
#[test]
fn test_ir_rewrite_target_node_mismatch_fails_with_no_stdout() {
let _ = env_logger::builder().is_test(true).try_init();
let ir_text = r#"package test
top fn main(x: bits[8] id=1) -> bits[8] {
ret identity.2: bits[8] = identity(x, id=2)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("test.ir");
std::fs::write(&ir_path, ir_text).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("ir-rewrite")
.arg(ir_path.to_str().unwrap())
.arg("add(x, y)")
.arg("x")
.arg("--target")
.arg("2")
.output()
.unwrap();
assert!(!output.status.success(), "expected ir-rewrite to fail");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("target node text_id=2 does not match"),
"unexpected stderr: {}",
stderr
);
assert_eq!(String::from_utf8_lossy(&output.stdout), "");
}
#[test]
fn test_ir_rewrite_target_operand_out_of_bounds_fails_with_no_stdout() {
let _ = env_logger::builder().is_test(true).try_init();
let ir_text = r#"package test
top fn main(x: bits[8] id=1) -> bits[8] {
zero: bits[8] = literal(value=0, id=10)
ret add.20: bits[8] = add(x, zero, id=20)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("test.ir");
std::fs::write(&ir_path, ir_text).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("ir-rewrite")
.arg(ir_path.to_str().unwrap())
.arg("literal(0)")
.arg("$const(value=1, width=8)")
.arg("--target")
.arg("20:2")
.output()
.unwrap();
assert!(!output.status.success(), "expected ir-rewrite to fail");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("operand 2 is out of bounds"),
"unexpected stderr: {}",
stderr
);
assert_eq!(String::from_utf8_lossy(&output.stdout), "");
}
#[test]
fn test_ir_rewrite_target_operand_mismatch_fails_with_no_stdout() {
let _ = env_logger::builder().is_test(true).try_init();
let ir_text = r#"package test
top fn main(x: bits[8] id=1) -> bits[8] {
zero: bits[8] = literal(value=0, id=10)
ret add.20: bits[8] = add(x, zero, id=20)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("test.ir");
std::fs::write(&ir_path, ir_text).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("ir-rewrite")
.arg(ir_path.to_str().unwrap())
.arg("literal(0)")
.arg("$const(value=1, width=8)")
.arg("--target")
.arg("20:0")
.output()
.unwrap();
assert!(!output.status.success(), "expected ir-rewrite to fail");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("operand 0 does not match"),
"unexpected stderr: {}",
stderr
);
assert_eq!(String::from_utf8_lossy(&output.stdout), "");
}
#[test]
fn test_ir_rewrite_malformed_match_reports_error() {
let _ = env_logger::builder().is_test(true).try_init();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("ir-rewrite")
.arg("this_file_should_not_be_read.ir")
.arg("one_hot(x, lsb_prio=maybe)")
.arg("x")
.output()
.unwrap();
assert!(!output.status.success(), "expected ir-rewrite to fail");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("Failed to parse match pattern:"),
"unexpected stderr: {}",
stderr
);
assert!(
stderr.contains("lsb_prio expects boolean literal or '_'"),
"unexpected stderr: {}",
stderr
);
assert!(
!stderr.contains("Failed to read"),
"expected parse rejection before IR I/O: {}",
stderr
);
}
#[test]
fn test_ir_rewrite_malformed_replacement_reports_error() {
let _ = env_logger::builder().is_test(true).try_init();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("ir-rewrite")
.arg("this_file_should_not_be_read.ir")
.arg("and(x, y)")
.arg("and(_, x)")
.output()
.unwrap();
assert!(!output.status.success(), "expected ir-rewrite to fail");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("Failed to parse replacement:"),
"unexpected stderr: {}",
stderr
);
assert!(
stderr.contains("wildcard '_'"),
"unexpected stderr: {}",
stderr
);
assert!(
!stderr.contains("Failed to read"),
"expected parse rejection before IR I/O: {}",
stderr
);
}
#[test]
fn test_ir_rewrite_top_overrides_package_top_without_changing_output_top() {
let _ = env_logger::builder().is_test(true).try_init();
let ir_text = r#"package test
top fn main(p: bits[1] id=1, x: bits[8] id=2) -> bits[8] {
ret identity.20: bits[8] = identity(x, id=20)
}
fn helper(p: bits[1] id=3, x: bits[8] id=4) -> bits[8] {
ret sel.10: bits[8] = sel(p, cases=[x, x], id=10)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("test.ir");
std::fs::write(&ir_path, ir_text).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("ir-rewrite")
.arg("--top")
.arg("helper")
.arg(ir_path.to_str().unwrap())
.arg("sel(selector=p, cases=[x, x])")
.arg("x")
.output()
.unwrap();
assert!(
output.status.success(),
"ir-rewrite --top failed (status={});\nstdout:{}\nstderr:{}",
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("top fn main"),
"expected package top to remain main:\n{}",
stdout
);
assert!(
stdout.contains(
"fn helper(p: bits[1] id=3, x: bits[8] id=4) -> bits[8] {\n ret x: bits[8] = param(name=x, id=4)\n}"
),
"expected helper rewrite in stdout:\n{}",
stdout
);
}
#[test]
fn test_ir_rewrite_no_match_prints_unchanged_package() {
let _ = env_logger::builder().is_test(true).try_init();
let ir_text = r#"package test
top fn main(x: bits[8] id=1, y: bits[8] id=2) -> bits[8] {
ret add.10: bits[8] = add(x, y, id=10)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("test.ir");
std::fs::write(&ir_path, ir_text).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("ir-rewrite")
.arg(ir_path.to_str().unwrap())
.arg("sub(x, y)")
.arg("x")
.output()
.unwrap();
assert!(
output.status.success(),
"ir-rewrite failed (status={});\nstdout:{}\nstderr:{}",
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert_eq!(String::from_utf8_lossy(&output.stdout), ir_text);
assert_eq!(String::from_utf8_lossy(&output.stderr), "");
}
#[test]
fn test_ir_rewrite_must_match_fails_when_no_matches_found() {
let _ = env_logger::builder().is_test(true).try_init();
let ir_text = r#"package test
top fn main(x: bits[8] id=1, y: bits[8] id=2) -> bits[8] {
ret add.10: bits[8] = add(x, y, id=10)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("test.ir");
std::fs::write(&ir_path, ir_text).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("ir-rewrite")
.arg("--must-match")
.arg(ir_path.to_str().unwrap())
.arg("sub(x, y)")
.arg("x")
.output()
.unwrap();
assert!(!output.status.success(), "expected ir-rewrite to fail");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("No matches found"),
"unexpected stderr: {}",
stderr
);
assert_eq!(
String::from_utf8_lossy(&output.stdout),
"",
"expected no stdout on must-match failure"
);
}
#[cfg(unix)]
#[test]
fn test_ir_query_corpus_follows_symlinked_ir_files() {
let _ = env_logger::builder().is_test(true).try_init();
let ir_text = r#"package test
fn main(x: bits[4] id=1) -> bits[5] {
ret oh: bits[5] = one_hot(x, lsb_prio=true, id=2)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let real_dir = temp_dir.path().join("real");
std::fs::create_dir_all(&real_dir).unwrap();
let real_ir = real_dir.join("real.ir");
std::fs::write(&real_ir, ir_text).unwrap();
let corpus_dir = temp_dir.path().join("corpus");
std::fs::create_dir_all(&corpus_dir).unwrap();
let link_path = corpus_dir.join("link.ir");
symlink(&real_ir, &link_path).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("ir-query-corpus")
.arg(corpus_dir.to_str().unwrap())
.arg("one_hot(x, lsb_prio=_)")
.output()
.unwrap();
assert!(
output.status.success(),
"ir-query-corpus failed (status={});\nstdout:{}\nstderr:{}",
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let expected = format!(
"{}: ret oh: bits[5] = one_hot(x, lsb_prio=true, id=2)\n",
link_path.to_str().unwrap()
);
assert_eq!(stdout, expected, "unexpected stdout: {}", stdout);
}
#[test]
fn test_ir_op_histo_prints_single_file_histogram() {
let _ = env_logger::builder().is_test(true).try_init();
let ir = r#"package test
top fn main(x: bits[1] id=1, y: bits[1] id=2, z: bits[1] id=3) -> bits[1] {
n: bits[1] = and(x, y, id=4)
ret out: bits[1] = or(n, z, id=5)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("input.ir");
std::fs::write(&ir_path, ir).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("ir-op-histo")
.arg(ir_path.to_str().unwrap())
.output()
.unwrap();
assert!(
output.status.success(),
"ir-op-histo failed (status={});\nstdout:{}\nstderr:{}",
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let expected = "{and(bits[1], bits[1]) -> bits[1]: 1, or(bits[1], bits[1]) -> bits[1]: 1}\n";
assert_eq!(stdout, expected, "unexpected stdout: {}", stdout);
}
#[test]
fn test_ir_op_histo_include_types_false_prints_operator_names() {
let _ = env_logger::builder().is_test(true).try_init();
let ir = r#"package test
top fn main(x: bits[2] id=1, y: bits[2] id=2, z: bits[2] id=3) -> bits[2] {
n: bits[2] = and(x, y, id=4)
ret out: bits[2] = or(n, z, id=5)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("input.ir");
std::fs::write(&ir_path, ir).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("ir-op-histo")
.arg(ir_path.to_str().unwrap())
.arg("--include-types=false")
.output()
.unwrap();
assert!(
output.status.success(),
"ir-op-histo failed (status={});\nstdout:{}\nstderr:{}",
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let expected = "{and: 1, or: 1}\n";
assert_eq!(stdout, expected, "unexpected stdout: {}", stdout);
}
#[test]
fn test_ir_op_histo_top_overrides_package_top() {
let _ = env_logger::builder().is_test(true).try_init();
let ir = r#"package test
top fn main(x: bits[1] id=1, y: bits[1] id=2) -> bits[1] {
ret out: bits[1] = and(x, y, id=3)
}
fn alternate(x: bits[2] id=4, y: bits[2] id=5) -> bits[2] {
ret out: bits[2] = or(x, y, id=6)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("input.ir");
std::fs::write(&ir_path, ir).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("ir-op-histo")
.arg(ir_path.to_str().unwrap())
.arg("--top")
.arg("alternate")
.output()
.unwrap();
assert!(
output.status.success(),
"ir-op-histo failed (status={});\nstdout:{}\nstderr:{}",
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let expected = "{or(bits[2], bits[2]) -> bits[2]: 1}\n";
assert_eq!(stdout, expected, "unexpected stdout: {}", stdout);
}
#[test]
fn test_ir_op_histo_corpus_streams_per_file_and_total() {
let _ = env_logger::builder().is_test(true).try_init();
let ir_a = r#"package test
fn main(x: bits[1] id=1, y: bits[1] id=2) -> bits[1] {
ret out: bits[1] = and(x, y, id=3)
}
"#;
let ir_b = r#"package test
fn main(x: bits[1] id=1, y: bits[1] id=2, z: bits[1] id=3) -> bits[1] {
n: bits[1] = and(x, y, id=4)
ret out: bits[1] = or(n, z, id=5)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let corpus_dir = temp_dir.path().join("corpus");
std::fs::create_dir_all(&corpus_dir).unwrap();
let a_path = corpus_dir.join("a.ir");
let b_path = corpus_dir.join("b.ir");
std::fs::write(&a_path, ir_a).unwrap();
std::fs::write(&b_path, ir_b).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("ir-op-histo-corpus")
.arg(corpus_dir.to_str().unwrap())
.output()
.unwrap();
assert!(
output.status.success(),
"ir-op-histo-corpus failed (status={});\nstdout:{}\nstderr:{}",
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let expected = format!(
"{}: {{and(bits[1], bits[1]) -> bits[1]: 1}}\n{}: {{and(bits[1], bits[1]) -> bits[1]: 1, or(bits[1], bits[1]) -> bits[1]: 1}}\ntotal: {{and(bits[1], bits[1]) -> bits[1]: 2, or(bits[1], bits[1]) -> bits[1]: 1}}\n",
a_path.to_str().unwrap(),
b_path.to_str().unwrap()
);
assert_eq!(stdout, expected, "unexpected stdout: {}", stdout);
}
#[test]
fn test_ir_op_histo_corpus_include_types_false_streams_per_file_and_total() {
let _ = env_logger::builder().is_test(true).try_init();
let ir_a = r#"package test
fn main(x: bits[1] id=1, y: bits[1] id=2) -> bits[1] {
ret out: bits[1] = and(x, y, id=3)
}
"#;
let ir_b = r#"package test
fn main(x: bits[2] id=1, y: bits[2] id=2, z: bits[2] id=3) -> bits[2] {
n: bits[2] = and(x, y, id=4)
ret out: bits[2] = or(n, z, id=5)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let corpus_dir = temp_dir.path().join("corpus");
std::fs::create_dir_all(&corpus_dir).unwrap();
let a_path = corpus_dir.join("a.ir");
let b_path = corpus_dir.join("b.ir");
std::fs::write(&a_path, ir_a).unwrap();
std::fs::write(&b_path, ir_b).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("ir-op-histo-corpus")
.arg(corpus_dir.to_str().unwrap())
.arg("--include-types=false")
.output()
.unwrap();
assert!(
output.status.success(),
"ir-op-histo-corpus failed (status={});\nstdout:{}\nstderr:{}",
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let expected = format!(
"{}: {{and: 1}}\n{}: {{and: 1, or: 1}}\ntotal: {{and: 2, or: 1}}\n",
a_path.to_str().unwrap(),
b_path.to_str().unwrap()
);
assert_eq!(stdout, expected, "unexpected stdout: {}", stdout);
}
#[test]
fn test_ir_fn_node_count_corpus_streams_per_file() {
let _ = env_logger::builder().is_test(true).try_init();
let ir_a = r#"package test
fn main(x: bits[1] id=1) -> bits[1] {
ret x: bits[1] = param(name=x, id=1)
}
"#;
let ir_b = r#"package test
fn main(x: bits[1] id=1, y: bits[1] id=2) -> bits[1] {
ret out: bits[1] = and(x, y, id=3)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let corpus_dir = temp_dir.path().join("corpus");
std::fs::create_dir_all(&corpus_dir).unwrap();
let a_path = corpus_dir.join("a.ir");
let b_path = corpus_dir.join("b.ir");
std::fs::write(&a_path, ir_a).unwrap();
std::fs::write(&b_path, ir_b).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let output = Command::new(driver)
.arg("ir-fn-node-count-corpus")
.arg(corpus_dir.to_str().unwrap())
.output()
.unwrap();
assert!(
output.status.success(),
"ir-fn-node-count-corpus failed (status={});\nstdout:{}\nstderr:{}",
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let expected = format!(
"{}: 1\n{}: 3\n",
a_path.to_str().unwrap(),
b_path.to_str().unwrap()
);
assert_eq!(stdout, expected, "unexpected stdout: {}", stdout);
}
#[test]
fn test_ir_query_exits_cleanly_on_broken_pipe() {
let _ = env_logger::builder().is_test(true).try_init();
let ir_text = r#"package test
fn main(x: bits[4] id=1) -> bits[5] {
ret oh: bits[5] = one_hot(x, lsb_prio=true, id=2)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("test.ir");
std::fs::write(&ir_path, ir_text).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let mut command = Command::new(driver);
command
.arg("ir-query")
.arg(ir_path.to_str().unwrap())
.arg("one_hot(x, lsb_prio=_)");
let (status, stderr) = run_with_broken_stdout(command);
assert!(status.success(), "ir-query status: {}", status);
assert_eq!(stderr, "", "unexpected stderr: {}", stderr);
}
#[test]
fn test_ir_rewrite_exits_cleanly_on_broken_pipe() {
let _ = env_logger::builder().is_test(true).try_init();
let ir_text = r#"package test
top fn main(p: bits[1] id=1, x: bits[8] id=2) -> bits[8] {
ret sel.10: bits[8] = sel(p, cases=[x, x], id=10)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let ir_path = temp_dir.path().join("test.ir");
std::fs::write(&ir_path, ir_text).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let mut command = Command::new(driver);
command
.arg("ir-rewrite")
.arg(ir_path.to_str().unwrap())
.arg("sel(selector=p, cases=[x, x])")
.arg("x");
let (status, stderr) = run_with_broken_stdout(command);
assert!(status.success(), "ir-rewrite status: {}", status);
assert_eq!(stderr, "", "unexpected stderr: {}", stderr);
}
#[test]
fn test_ir_query_corpus_exits_cleanly_on_broken_pipe() {
let _ = env_logger::builder().is_test(true).try_init();
let ir_text = r#"package test
fn main(x: bits[4] id=1) -> bits[5] {
ret oh: bits[5] = one_hot(x, lsb_prio=true, id=2)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let corpus_dir = temp_dir.path().join("corpus");
std::fs::create_dir_all(&corpus_dir).unwrap();
let ir_path = corpus_dir.join("test.ir");
std::fs::write(&ir_path, ir_text).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let mut command = Command::new(driver);
command
.arg("ir-query-corpus")
.arg(corpus_dir.to_str().unwrap())
.arg("one_hot(x, lsb_prio=_)");
let (status, stderr) = run_with_broken_stdout(command);
assert!(status.success(), "ir-query-corpus status: {}", status);
assert_eq!(stderr, "", "unexpected stderr: {}", stderr);
}
#[test]
fn test_ir_op_histo_corpus_exits_cleanly_on_broken_pipe() {
let _ = env_logger::builder().is_test(true).try_init();
let ir_text = r#"package test
fn main(x: bits[4] id=1) -> bits[4] {
ret y: bits[4] = identity(x, id=2)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let corpus_dir = temp_dir.path().join("corpus");
std::fs::create_dir_all(&corpus_dir).unwrap();
let ir_path = corpus_dir.join("test.ir");
std::fs::write(&ir_path, ir_text).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let mut command = Command::new(driver);
command
.arg("ir-op-histo-corpus")
.arg(corpus_dir.to_str().unwrap());
let (status, stderr) = run_with_broken_stdout(command);
assert!(status.success(), "ir-op-histo-corpus status: {}", status);
assert_eq!(stderr, "", "unexpected stderr: {}", stderr);
}
#[test]
fn test_ir_fn_node_count_corpus_exits_cleanly_on_broken_pipe() {
let _ = env_logger::builder().is_test(true).try_init();
let ir_text = r#"package test
fn main(x: bits[4] id=1) -> bits[4] {
ret y: bits[4] = identity(x, id=2)
}
"#;
let temp_dir = tempfile::tempdir().unwrap();
let corpus_dir = temp_dir.path().join("corpus");
std::fs::create_dir_all(&corpus_dir).unwrap();
let ir_path = corpus_dir.join("test.ir");
std::fs::write(&ir_path, ir_text).unwrap();
let driver = env!("CARGO_BIN_EXE_xlsynth-driver");
let mut command = Command::new(driver);
command
.arg("ir-fn-node-count-corpus")
.arg(corpus_dir.to_str().unwrap());
let (status, stderr) = run_with_broken_stdout(command);
assert!(
status.success(),
"ir-fn-node-count-corpus status: {}",
status
);
assert_eq!(stderr, "", "unexpected stderr: {}", stderr);
}