diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c58c3d87..09c727a4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -103,9 +103,9 @@ jobs: run: cargo test --test version_test # ----------------------------------------------------------- - # 2) xlsynth-vastly optional oracle lane (iverilog/vvp) + # 2) xlsynth-vastly reference simulator lane (currently Icarus) # ----------------------------------------------------------- - xlsynth-vastly-iverilog-tests: + xlsynth-vastly-reference-sim-tests: needs: [lint-check-ubuntu] runs-on: ubuntu-latest @@ -130,16 +130,16 @@ jobs: profile: minimal override: true - - name: Install Icarus Verilog + - name: Install Icarus Verilog reference simulator run: | sudo apt-get update sudo apt-get install -y iverilog iverilog -V vvp -V - - name: Run xlsynth-vastly oracle tests + - name: Run xlsynth-vastly reference simulator tests run: | - cargo test -p xlsynth-vastly --features iverilog-tests + cargo test -p xlsynth-vastly --features reference-sim-tests # ----------------------------------------------------------- # 3) Build/test on Ubuntu 22.04 and 24.04 diff --git a/xlsynth-vastly/Cargo.toml b/xlsynth-vastly/Cargo.toml index 42cfd1df..6309fc5b 100644 --- a/xlsynth-vastly/Cargo.toml +++ b/xlsynth-vastly/Cargo.toml @@ -22,4 +22,4 @@ xlsynth = { path = "../xlsynth", version = "0.36.0" } [features] default = [] -iverilog-tests = [] +reference-sim-tests = [] diff --git a/xlsynth-vastly/README.md b/xlsynth-vastly/README.md index 086a627e..52545b1e 100644 --- a/xlsynth-vastly/README.md +++ b/xlsynth-vastly/README.md @@ -3,8 +3,28 @@ `xlsynth-vastly` simulates Verilog/SystemVerilog that XLS codegen emits, with support for both combinational (`combo`) and pipelined designs over the constructs we target. We ground correctness with fuzzing over XLS-generated -artifacts in both forms, and with oracle-style comparisons such as semantic VCD -diffing against `iverilog`/`vvp`. +artifacts in both forms, and with differential checks against external +reference implementations such as semantic VCD diffing against an external +simulator backend. + +The language standards are the semantic source of truth here. External +simulators are used as independent implementations to cross-check against the +spec, not as spec providers themselves. + +## Scope and Trust Model + +`xlsynth-vastly` is a deliberately limited simulator intended for testing and +debugging the subset of Verilog/SystemVerilog emitted by XLS and VAST. + +It is not designed or intended to be a general-purpose Verilog simulator. The +implementation makes a best-effort attempt to reflect the Verilog/SystemVerilog +specification, but any trust in its behavior should be limited to the language +subset exercised by generated XLS/VAST outputs and the surrounding regression +and differential-test coverage. + +In other words: if `xlsynth-vastly` accepts and correctly simulates broader +source-language inputs, that is useful, but it is not the primary contract of +the crate. ## Quickstart Commands @@ -27,13 +47,13 @@ cargo run -p xlsynth-vastly --bin vastly-sim-pipeline -- /path/to/foo.sv \ --vcd-out ./pipeline.vcd ``` -- Compare combo simulation results with Icarus Verilog (best as a semantic oracle check): +- Compare combo simulation results against a reference simulator: ```bash cargo run -p xlsynth-vastly --bin vastly-sim-combo -- /path/to/foo.combo.v \ --inputs "0xf00,0xba5;0x0000,0x3f80" \ --vcd-out ./combo.vcd \ - --compare-to-iverilog + --compare-to-reference-sim=iverilog ``` - Run baseline tests that do not require external simulators: @@ -42,23 +62,23 @@ cargo run -p xlsynth-vastly --bin vastly-sim-combo -- /path/to/foo.combo.v \ cargo test -p xlsynth-vastly ``` -- Run extended oracle tests that use `iverilog`/`vvp`: +- Run extended reference-simulator tests: ```bash -cargo test -p xlsynth-vastly --features iverilog-tests +cargo test -p xlsynth-vastly --features reference-sim-tests ``` ## Tooling Requirements -- Core unit/integration tests do not require `iverilog`/`vvp`. -- Oracle/differential tests against Icarus Verilog require `iverilog` and `vvp` - on `PATH`, and are enabled with `--features iverilog-tests`. -- `iverilog-tests` exists so default `cargo test` remains tool-independent, - while still providing an explicit opt-in lane for oracle-backed comparisons. -- In `iverilog-tests` mode, `iverilog`/`vvp` are only invoked as subprocesses to - produce oracle outputs; the oracle backend is intentionally pluggable so - additional simulators can be slotted in later (e.g. for further SV support - comparisons). +- Core unit/integration tests do not require external simulators. +- Reference-simulator tests require the current external simulator backend to + be available on `PATH`; enable them with `--features reference-sim-tests`. +- `reference-sim-tests` exists so default `cargo test` remains + tool-independent while still providing an explicit opt-in lane for + differential checks against external implementations. +- The reference-simulator layer is intentionally pluggable so additional + implementations can be slotted in later, including backends with only + two-value semantics such as Yosys/CXXRTL. ## Stimulus Format @@ -96,15 +116,16 @@ cargo run -p xlsynth-vastly --bin vastly-sim-combo -- /path/to/foo.combo.v \ - Values may be hex (`0x...`) or decimal. - Values map positionally to module input ports in module-header order. -### Compare against Icarus Verilog +### Compare against a reference simulator -If `iverilog`/`vvp` are on `PATH`, generate an Icarus VCD and semantically diff: +If the current reference-simulator backend is on `PATH`, generate a reference +VCD and semantically diff: ```bash cargo run -p xlsynth-vastly --bin vastly-sim-combo -- /path/to/foo.combo.v \ --inputs "0xf00,0xba5;0x0000,0x3f80" \ --vcd-out ./combo.vcd \ - --compare-to-iverilog + --compare-to-reference-sim=iverilog ``` ## `vastly-sim-pipeline` @@ -130,8 +151,8 @@ cargo run -p xlsynth-vastly --bin vastly-sim-pipeline -- /path/to/foo.sv \ ## Testing -- Baseline tests (no Icarus dependency): +- Baseline tests (no external-simulator dependency): `cargo test -p xlsynth-vastly` -- Extended oracle tests (requires `iverilog`/`vvp`): - `cargo test -p xlsynth-vastly --features iverilog-tests` +- Extended reference-simulator tests: + `cargo test -p xlsynth-vastly --features reference-sim-tests` - Fuzz targets live under `xlsynth-vastly/fuzz`; see workspace `FUZZ.md`. diff --git a/xlsynth-vastly/fuzz/Cargo.toml b/xlsynth-vastly/fuzz/Cargo.toml index 5d55f45d..9d084ecf 100644 --- a/xlsynth-vastly/fuzz/Cargo.toml +++ b/xlsynth-vastly/fuzz/Cargo.toml @@ -28,8 +28,8 @@ members = ["."] warnings = "deny" [[bin]] -name = "diff_iverilog" -path = "fuzz_targets/diff_iverilog.rs" +name = "diff_refsim_4value" +path = "fuzz_targets/diff_refsim_4value.rs" test = false doc = false diff --git a/xlsynth-vastly/fuzz/fuzz_targets/diff_iverilog.rs b/xlsynth-vastly/fuzz/fuzz_targets/diff_refsim_4value.rs similarity index 100% rename from xlsynth-vastly/fuzz/fuzz_targets/diff_iverilog.rs rename to xlsynth-vastly/fuzz/fuzz_targets/diff_refsim_4value.rs diff --git a/xlsynth-vastly/fuzz/fuzz_targets/xls_ir_codegen_semantics.rs b/xlsynth-vastly/fuzz/fuzz_targets/xls_ir_codegen_semantics.rs index b8a33ffa..8bf1ba08 100644 --- a/xlsynth-vastly/fuzz/fuzz_targets/xls_ir_codegen_semantics.rs +++ b/xlsynth-vastly/fuzz/fuzz_targets/xls_ir_codegen_semantics.rs @@ -237,11 +237,12 @@ fuzz_target!(|data: &[u8]| { stimulus, &stimulus_ordinal, ); - if include_iverilog_oracle() { + if include_iverilog_reference_sim() { // Keep Icarus on the plain-Verilog path only. XLS can emit // SystemVerilog array assignment forms that current Icarus does not - // support, so the external oracle is: Icarus Verilog, plus an - // explicit Vastly-V/Vastly-SV equality check. + // support, so this reference implementation check is: Icarus + // Verilog on the plain-Verilog path, plus an explicit + // Vastly-V/Vastly-SV equality check. let got_iv_v = match eval_codegen_with_iverilog_verilog(&verilog_src, &input_map) { Ok(v) => v, Err(e) => { @@ -290,6 +291,59 @@ fuzz_target!(|data: &[u8]| { &stimulus_ordinal, ); } + if include_yosys_cxxrtl_reference_sim() && input_map_is_two_value_safe(&input_map) { + let got_cxxrtl_v = match eval_codegen_with_yosys_cxxrtl_verilog( + &verilog_src, + "fuzz_codegen_v", + &input_map, + ) { + Ok(v) => v, + Err(e) => { + unsupported( + "yosys/cxxrtl could not compile/eval generated Verilog", + &ir_text, + Some(&verilog_src), + Some(&format!( + "{e} {}", + summarize_stimulus_with_index(stimulus, &stimulus_ordinal) + )), + data, + ); + return; + } + }; + + assert_same_bits( + "yosys_cxxrtl_verilog", + &want_bits, + &got_cxxrtl_v, + &ir_text, + &verilog_src, + data, + stimulus, + &stimulus_ordinal, + ); + assert_same_bits( + "yosys_cxxrtl_v_vs_vastly_v", + &got_cxxrtl_v, + &got_v, + &ir_text, + &verilog_src, + data, + stimulus, + &stimulus_ordinal, + ); + assert_same_bits( + "yosys_cxxrtl_v_vs_vastly_sv", + &got_cxxrtl_v, + &got_sv, + &ir_text, + &sv_src, + data, + stimulus, + &stimulus_ordinal, + ); + } } let mut stage1_retired: Option<(String, Vec)> = None; @@ -609,6 +663,28 @@ fn eval_codegen_with_iverilog_verilog( result } +fn eval_codegen_with_yosys_cxxrtl_verilog( + src: &str, + module_name: &str, + inputs: &BTreeMap, +) -> Result { + let m = xlsynth_vastly::compile_combo_module(src) + .map_err(|e| format!("compile_combo_module failed before Yosys/CXXRTL run: {e:?}"))?; + if m.output_ports.len() != 1 { + return Err(format!( + "expected exactly one output port, got {}", + m.output_ports.len() + )); + } + let out_name = m.output_ports[0].name.clone(); + let outputs = xlsynth_vastly::eval_yosys_cxxrtl_combo(src, module_name, inputs) + .map_err(|e| format!("eval_yosys_cxxrtl_combo failed: {e:?}"))?; + outputs + .get(&out_name) + .cloned() + .ok_or_else(|| format!("output `{out_name}` missing from Yosys/CXXRTL result")) +} + fn eval_pipeline_with_vastly( src: &str, pipeline_stages: usize, @@ -809,8 +885,13 @@ fn strict_unsupported() -> bool { env_truthy("VASTLY_FUZZ_STRICT_UNSUPPORTED") } -fn include_iverilog_oracle() -> bool { - env_truthy("VASTLY_FUZZ_INCLUDE_IVERILOG_ORACLE") +fn include_iverilog_reference_sim() -> bool { + env_truthy("VASTLY_FUZZ_INCLUDE_IVERILOG_REFERENCE_SIM") + || env_truthy("VASTLY_FUZZ_INCLUDE_IVERILOG_ORACLE") +} + +fn include_yosys_cxxrtl_reference_sim() -> bool { + env_truthy("VASTLY_FUZZ_INCLUDE_YOSYS_CXXRTL_REFERENCE_SIM") } fn include_stage1_pipeline_oracle() -> bool { @@ -825,6 +906,14 @@ fn use_autocov_stimuli() -> bool { env_truthy("VASTLY_FUZZ_USE_AUTOCOV") } +fn input_map_is_two_value_safe(input_map: &BTreeMap) -> bool { + let mut env = xlsynth_vastly::Env::new(); + for (name, value) in input_map { + env.insert(name.clone(), value.clone()); + } + xlsynth_vastly::env_is_two_value_safe(&env) +} + fn unsupported( reason: &str, ir_text: &str, diff --git a/xlsynth-vastly/fuzz/tests/codegen_semantics_typed_top_level.rs b/xlsynth-vastly/fuzz/tests/codegen_semantics_typed_top_level.rs index 89b5db4a..ffb3b0b6 100644 --- a/xlsynth-vastly/fuzz/tests/codegen_semantics_typed_top_level.rs +++ b/xlsynth-vastly/fuzz/tests/codegen_semantics_typed_top_level.rs @@ -5,6 +5,8 @@ use std::time::SystemTime; use xlsynth_vastly::compile_combo_module; use xlsynth_vastly::eval_combo; +use xlsynth_vastly::eval_yosys_cxxrtl_combo; +use xlsynth_vastly::has_yosys_cxxrtl_toolchain; use xlsynth_vastly::plan_combo_eval; use xlsynth_vastly::run_iverilog_combo_and_collect_vcd; use xlsynth_vastly::LogicBit; @@ -140,6 +142,24 @@ fn eval_codegen_with_iverilog_verilog( result } +fn eval_codegen_with_yosys_cxxrtl_verilog( + src: &str, + module_name: &str, + inputs: &BTreeMap, +) -> Result { + let m = compile_combo_module(src).map_err(|e| format!("compile_combo_module failed: {e:?}"))?; + if m.output_ports.len() != 1 { + return Err(format!("expected exactly one output port, got {}", m.output_ports.len())); + } + let out_name = m.output_ports[0].name.clone(); + let outputs = eval_yosys_cxxrtl_combo(src, module_name, inputs) + .map_err(|e| format!("eval_yosys_cxxrtl_combo failed: {e:?}"))?; + outputs + .get(&out_name) + .cloned() + .ok_or_else(|| format!("missing output `{out_name}`")) +} + fn value4_from_msb_bits(bits: &str) -> Result { let mut out = Vec::with_capacity(bits.len()); for c in bits.chars().rev() { @@ -217,8 +237,22 @@ fn typed_top_level_codegen_matches_ir_and_oracles() { let got_v = eval_codegen_with_vastly(&verilog_src, &input_map).unwrap(); let got_sv = eval_codegen_with_vastly(&sv_src, &input_map).unwrap(); let got_iv = eval_codegen_with_iverilog_verilog(&verilog_src, &input_map).unwrap(); + let got_cxxrtl = if has_yosys_cxxrtl_toolchain() { + Some( + eval_codegen_with_yosys_cxxrtl_verilog(&verilog_src, "compound_shapes_v", &input_map) + .unwrap(), + ) + } else { + None + }; assert_eq!(got_v.to_bit_string_msb_first(), want.to_bit_string_msb_first()); assert_eq!(got_sv.to_bit_string_msb_first(), want.to_bit_string_msb_first()); assert_eq!(got_iv.to_bit_string_msb_first(), want.to_bit_string_msb_first()); + if let Some(got_cxxrtl) = got_cxxrtl { + assert_eq!( + got_cxxrtl.to_bit_string_msb_first(), + want.to_bit_string_msb_first() + ); + } } diff --git a/xlsynth-vastly/src/bin/vastly-sim-combo.rs b/xlsynth-vastly/src/bin/vastly-sim-combo.rs index 98d4c329..d7b87e0c 100644 --- a/xlsynth-vastly/src/bin/vastly-sim-combo.rs +++ b/xlsynth-vastly/src/bin/vastly-sim-combo.rs @@ -5,6 +5,7 @@ use std::path::PathBuf; use clap::ArgAction; use clap::Parser; +use clap::ValueEnum; use xlsynth_vastly::Signedness; use xlsynth_vastly::Value4; @@ -17,6 +18,11 @@ use xlsynth_vastly::plan_combo_eval; use xlsynth_vastly::run_combo_and_write_vcd; use xlsynth_vastly::run_iverilog_combo_and_collect_vcd; +#[derive(Copy, Clone, Debug, Eq, PartialEq, ValueEnum)] +enum ReferenceSimBackend { + Iverilog, +} + #[derive(Parser, Debug)] #[command(name = "vastly-sim-combo")] #[command(about = "Run a *.combo.v over input vectors and dump a VCD", long_about = None)] @@ -33,9 +39,9 @@ struct Args { #[arg(long, default_value = "combo.vcd")] vcd_out: PathBuf, - /// Compare our VCD to Icarus Verilog (iverilog/vvp) by semantic VCD diff. - #[arg(long)] - compare_to_iverilog: bool, + /// Compare our VCD to a reference simulator by semantic VCD diff. + #[arg(long, value_enum)] + compare_to_reference_sim: Option, /// Print top-level output port values for each input vector to stdout. #[arg(long, default_value_t = true, action = ArgAction::Set)] @@ -78,18 +84,27 @@ fn main_inner() -> xlsynth_vastly::Result<()> { run_combo_and_write_vcd(&m, &plan, &vectors, &args.vcd_out)?; - if args.compare_to_iverilog { + if let Some(reference_sim_backend) = args.compare_to_reference_sim { let td = mk_temp_dir()?; - let iv_vcd = td.join("iverilog.vcd"); - run_iverilog_combo_and_collect_vcd(&args.combo_v, &m, &vectors, &iv_vcd)?; + let reference_sim_vcd = td.join("reference_sim.vcd"); + match reference_sim_backend { + ReferenceSimBackend::Iverilog => { + run_iverilog_combo_and_collect_vcd( + &args.combo_v, + &m, + &vectors, + &reference_sim_vcd, + )?; + } + } let ours_text = std::fs::read_to_string(&args.vcd_out) .map_err(|e| xlsynth_vastly::Error::Parse(format!("io error: {e}")))?; - let iv_text = std::fs::read_to_string(&iv_vcd) + let reference_sim_text = std::fs::read_to_string(&reference_sim_vcd) .map_err(|e| xlsynth_vastly::Error::Parse(format!("io error: {e}")))?; let ours = Vcd::parse(&ours_text)?; - let iv = Vcd::parse(&iv_text)?; - diff_vcd_exact(&ours, &iv, &VcdDiffOptions::default())?; + let reference_sim = Vcd::parse(&reference_sim_text)?; + diff_vcd_exact(&ours, &reference_sim, &VcdDiffOptions::default())?; } Ok(()) diff --git a/xlsynth-vastly/src/lib.rs b/xlsynth-vastly/src/lib.rs index 1f1a599d..1994c641 100644 --- a/xlsynth-vastly/src/lib.rs +++ b/xlsynth-vastly/src/lib.rs @@ -3,6 +3,9 @@ //! Verilog(-ish) expression evaluator with 4-state values (0/1/X/Z). //! //! v1 focuses on a small operator subset centered on the ternary operator. +//! Verilog/SystemVerilog semantics are defined by the language standard; +//! external simulators are used here as differential reference +//! implementations rather than as spec providers. pub mod ast; pub mod ast_spanned; @@ -23,6 +26,7 @@ pub mod parser; mod parser_spanned; mod pipeline_compile; mod pipeline_harness; +mod reference_sim; mod sim_harness; mod sim_observer; mod sv_ast; @@ -32,6 +36,7 @@ mod value; mod vcd; mod vcd_diff; mod vcd_writer; +mod yosys_cxxrtl_combo; pub use crate::ast_spanned::SpannedExpr; pub use crate::ast_spanned::SpannedExprKind; @@ -75,6 +80,11 @@ pub use crate::pipeline_harness::run_pipeline_and_collect_coverage; pub use crate::pipeline_harness::run_pipeline_and_collect_outputs; pub use crate::pipeline_harness::run_pipeline_and_write_vcd; pub use crate::pipeline_harness::step_pipeline_state_with_env; +pub use crate::reference_sim::ReferenceSimCapabilities; +pub use crate::reference_sim::ReferenceSimKind; +pub use crate::reference_sim::ValueDomain; +pub use crate::reference_sim::env_is_two_value_safe; +pub use crate::reference_sim::expr_is_two_value_safe; pub use crate::sim_harness::Cycle; pub use crate::sim_harness::Stimulus; pub use crate::sim_harness::run_and_write_vcd; @@ -85,6 +95,8 @@ pub use crate::value::Value4; pub use crate::vcd::Vcd; pub use crate::vcd_diff::VcdDiffOptions; pub use crate::vcd_diff::diff_vcd_exact; +pub use crate::yosys_cxxrtl_combo::eval_yosys_cxxrtl_combo; +pub use crate::yosys_cxxrtl_combo::has_yosys_cxxrtl_toolchain; use std::collections::BTreeMap; diff --git a/xlsynth-vastly/src/reference_sim.rs b/xlsynth-vastly/src/reference_sim.rs new file mode 100644 index 00000000..90829e0e --- /dev/null +++ b/xlsynth-vastly/src/reference_sim.rs @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: Apache-2.0 + +//! Reference-simulator metadata and conservative value-domain filtering. +//! +//! The language standard is the semantic source of truth. These helpers model +//! which external implementations are suitable for differential grounding over +//! a given subset of the language. + +use crate::Env; +use crate::LogicBit; +use crate::Value4; +use crate::ast::BinaryOp; +use crate::ast::Expr; + +/// Whether a reference implementation can credibly ground two-value-only or +/// full four-value semantics for a sample. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum ValueDomain { + TwoValue, + FourValue, +} + +/// Concrete external implementations used for differential checks. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum ReferenceSimKind { + Iverilog, + YosysCxxrtl, +} + +/// Describes the semantic/value-domain surface a reference implementation +/// should be used for. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct ReferenceSimCapabilities { + pub value_domain: ValueDomain, + pub supports_expr_diff: bool, + pub supports_module_diff: bool, +} + +impl ReferenceSimKind { + /// Returns the current differential-grounding capabilities for this + /// reference implementation. + pub fn capabilities(self) -> ReferenceSimCapabilities { + match self { + Self::Iverilog => ReferenceSimCapabilities { + value_domain: ValueDomain::FourValue, + supports_expr_diff: true, + supports_module_diff: true, + }, + Self::YosysCxxrtl => ReferenceSimCapabilities { + value_domain: ValueDomain::TwoValue, + supports_expr_diff: false, + supports_module_diff: true, + }, + } + } +} + +/// Returns whether every environment binding is strictly two-valued (`0/1` +/// only) and therefore suitable for grounding against a two-valued reference +/// implementation. +pub fn env_is_two_value_safe(env: &Env) -> bool { + env.iter().all(|(_, value)| value_is_two_value(value)) +} + +/// Conservatively checks whether an expression stays within a two-valued subset +/// suitable for comparison against a two-valued reference implementation. +/// +/// This intentionally rejects more than strictly necessary. False negatives are +/// acceptable here because the goal is to keep the initial two-valued grounding +/// path simple and semantically unsurprising. +pub fn expr_is_two_value_safe(expr: &Expr, env: &Env) -> bool { + env_is_two_value_safe(env) && expr_is_two_value_safe_inner(expr) +} + +fn expr_is_two_value_safe_inner(expr: &Expr) -> bool { + match expr { + Expr::Ident(_) => true, + Expr::Literal(value) | Expr::UnsizedNumber(value) => value_is_two_value(value), + Expr::UnbasedUnsized(bit) => matches!(bit, LogicBit::Zero | LogicBit::One), + Expr::Call { .. } => false, + Expr::Concat(parts) => parts.iter().all(expr_is_two_value_safe_inner), + Expr::Replicate { count, expr } => { + expr_is_two_value_safe_inner(count) && expr_is_two_value_safe_inner(expr) + } + Expr::Cast { width, expr } => { + expr_is_two_value_safe_inner(width) && expr_is_two_value_safe_inner(expr) + } + Expr::Index { expr, index } => { + expr_is_two_value_safe_inner(expr) && expr_is_two_value_safe_inner(index) + } + Expr::Slice { expr, msb, lsb } => { + expr_is_two_value_safe_inner(expr) + && expr_is_two_value_safe_inner(msb) + && expr_is_two_value_safe_inner(lsb) + } + Expr::IndexedSlice { + expr, base, width, .. + } => { + expr_is_two_value_safe_inner(expr) + && expr_is_two_value_safe_inner(base) + && expr_is_two_value_safe_inner(width) + } + Expr::Unary { expr, .. } => expr_is_two_value_safe_inner(expr), + Expr::Binary { op, lhs, rhs } => { + !matches!(op, BinaryOp::CaseEq | BinaryOp::CaseNeq) + && expr_is_two_value_safe_inner(lhs) + && expr_is_two_value_safe_inner(rhs) + } + Expr::Ternary { cond, t, f } => { + expr_is_two_value_safe_inner(cond) + && expr_is_two_value_safe_inner(t) + && expr_is_two_value_safe_inner(f) + } + } +} + +fn value_is_two_value(value: &Value4) -> bool { + value + .bits_lsb_first() + .iter() + .all(|bit| matches!(bit, LogicBit::Zero | LogicBit::One)) +} + +#[cfg(test)] +mod tests { + use super::ReferenceSimKind; + use super::ValueDomain; + use super::env_is_two_value_safe; + use super::expr_is_two_value_safe; + use crate::Env; + use crate::Value4; + use crate::parser::parse_expr; + + fn parse(text: &str) -> crate::ast::Expr { + parse_expr(text).unwrap_or_else(|e| panic!("parse failed for {text:?}: {e:?}")) + } + + fn parse_value(text: &str) -> Value4 { + match parse(text) { + crate::ast::Expr::Literal(value) | crate::ast::Expr::UnsizedNumber(value) => value, + other => panic!("expected literal-ish value, got {other:?}"), + } + } + + #[test] + fn capabilities_distinguish_two_value_and_four_value_backends() { + let four_value = ReferenceSimKind::Iverilog.capabilities(); + assert_eq!(four_value.value_domain, ValueDomain::FourValue); + assert!(four_value.supports_expr_diff); + assert!(four_value.supports_module_diff); + + let two_value = ReferenceSimKind::YosysCxxrtl.capabilities(); + assert_eq!(two_value.value_domain, ValueDomain::TwoValue); + assert!(!two_value.supports_expr_diff); + assert!(two_value.supports_module_diff); + } + + #[test] + fn two_value_filter_accepts_plain_zero_one_expressions() { + let env = Env::new(); + let expr = parse("((4'b0011 + 4'b0001) == 4'd4) ? 1'b1 : 1'b0"); + assert!(expr_is_two_value_safe(&expr, &env)); + assert!(expr_is_two_value_safe(&parse("4'(2'b11)"), &env)); + } + + #[test] + fn two_value_filter_rejects_unknown_sensitive_constructs() { + let env = Env::new(); + assert!(!expr_is_two_value_safe(&parse("4'b10x1"), &env)); + assert!(!expr_is_two_value_safe(&parse("'z"), &env)); + assert!(!expr_is_two_value_safe(&parse("4'b0011 === 4'b0011"), &env)); + assert!(!expr_is_two_value_safe(&parse("$signed(4'b0011)"), &env)); + } + + #[test] + fn env_filter_rejects_non_two_value_bindings() { + let mut env = Env::new(); + env.insert("good", parse_value("4'b1010")); + assert!(env_is_two_value_safe(&env)); + + env.insert("bad", parse_value("4'b10x1")); + assert!(!env_is_two_value_safe(&env)); + assert!(!expr_is_two_value_safe(&parse("bad == 4'b1010"), &env)); + } +} diff --git a/xlsynth-vastly/src/yosys_cxxrtl_combo.rs b/xlsynth-vastly/src/yosys_cxxrtl_combo.rs new file mode 100644 index 00000000..c670ff11 --- /dev/null +++ b/xlsynth-vastly/src/yosys_cxxrtl_combo.rs @@ -0,0 +1,335 @@ +// SPDX-License-Identifier: Apache-2.0 + +use std::collections::BTreeMap; +use std::fmt::Write as _; +use std::process::Command; +use std::time::SystemTime; + +use crate::CompiledComboModule; +use crate::Env; +use crate::Error; +use crate::LogicBit; +use crate::Result; +use crate::Signedness; +use crate::Value4; +use crate::compile_combo_module; +use crate::env_is_two_value_safe; + +/// Returns whether the local Yosys/CXXRTL toolchain required for the +/// two-valued reference-implementation path is available on `PATH`. +pub fn has_yosys_cxxrtl_toolchain() -> bool { + command_works("yosys", &["-V"]) + && command_works("g++", &["--version"]) + && cxxrtl_runtime_include_dir().is_some() +} + +/// Evaluates a combinational Verilog module through Yosys+CXXRTL for a single +/// concrete two-valued input vector and returns the top-level output bindings. +pub fn eval_yosys_cxxrtl_combo( + src: &str, + module_name: &str, + inputs: &BTreeMap, +) -> Result> { + require_yosys_cxxrtl_toolchain()?; + let m = compile_combo_module(src)?; + ensure_two_value_inputs(inputs)?; + validate_inputs(&m, inputs)?; + + let td = mk_temp_dir()?; + let result = (|| { + let dut_path = td.join("dut.v"); + let cxxrtl_cc_path = td.join("dut.cc"); + let driver_cc_path = td.join("driver.cc"); + let sim_path = td.join("sim"); + + std::fs::write(&dut_path, src) + .map_err(|e| Error::Parse(format!("failed to write Yosys DUT source: {e}")))?; + std::fs::write(&driver_cc_path, render_driver_cpp(module_name, &m, inputs)) + .map_err(|e| Error::Parse(format!("failed to write CXXRTL driver source: {e}")))?; + + let yosys_script = format!( + "read_verilog {dut}; rename {top} top; hierarchy -top top; proc; memory_memx; opt_expr -keepdc; opt_clean; write_cxxrtl -header {out}", + dut = yosys_path(&dut_path), + top = module_name, + out = yosys_path(&cxxrtl_cc_path), + ); + let yosys = Command::new("yosys") + .current_dir(&td) + .arg("-Q") + .arg("-p") + .arg(&yosys_script) + .output() + .map_err(|e| Error::Parse(format!("failed to run yosys: {e}")))?; + if !yosys.status.success() { + return Err(Error::Parse(format!( + "yosys failed\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&yosys.stdout), + String::from_utf8_lossy(&yosys.stderr), + ))); + } + + let cxxrtl_include_dir = cxxrtl_runtime_include_dir().ok_or_else(|| { + Error::Parse("failed to locate the Yosys CXXRTL runtime include directory".to_string()) + })?; + let compile = Command::new("g++") + .current_dir(&td) + .arg("-std=c++17") + .arg("-O3") + .arg(format!("-I{}", cxxrtl_include_dir.display())) + .arg("-o") + .arg(&sim_path) + .arg(&driver_cc_path) + .arg(&cxxrtl_cc_path) + .output() + .map_err(|e| Error::Parse(format!("failed to compile CXXRTL driver: {e}")))?; + if !compile.status.success() { + return Err(Error::Parse(format!( + "CXXRTL compile failed\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&compile.stdout), + String::from_utf8_lossy(&compile.stderr), + ))); + } + + let run = Command::new(&sim_path) + .current_dir(&td) + .output() + .map_err(|e| Error::Parse(format!("failed to run compiled CXXRTL binary: {e}")))?; + if !run.status.success() { + return Err(Error::Parse(format!( + "CXXRTL execution failed\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&run.stdout), + String::from_utf8_lossy(&run.stderr), + ))); + } + + parse_driver_output(&m, &String::from_utf8_lossy(&run.stdout)) + })(); + let _ = std::fs::remove_dir_all(&td); + result +} + +fn command_works(cmd: &str, args: &[&str]) -> bool { + Command::new(cmd) + .args(args) + .output() + .map(|o| o.status.success()) + .unwrap_or(false) +} + +fn require_yosys_cxxrtl_toolchain() -> Result<()> { + if has_yosys_cxxrtl_toolchain() { + return Ok(()); + } + Err(Error::Parse( + "yosys with CXXRTL runtime headers and g++ are required for Yosys/CXXRTL reference simulation".to_string(), + )) +} + +fn cxxrtl_runtime_include_dir() -> Option { + if let Some(path) = std::env::var_os("YOSYS_CXXRTL_INCLUDE_DIR") { + let path = std::path::PathBuf::from(path); + if path.join("cxxrtl/capi/cxxrtl_capi.h").is_file() { + return Some(path); + } + } + + let yosys = find_on_path("yosys")?; + let prefix = yosys.parent()?.parent()?; + let path = prefix.join("share/yosys/include/backends/cxxrtl/runtime"); + if path.join("cxxrtl/capi/cxxrtl_capi.h").is_file() { + return Some(path); + } + None +} + +fn find_on_path(binary: &str) -> Option { + let path = std::env::var_os("PATH")?; + for entry in std::env::split_paths(&path) { + let candidate = entry.join(binary); + if candidate.is_file() { + return Some(candidate); + } + } + None +} + +fn ensure_two_value_inputs(inputs: &BTreeMap) -> Result<()> { + let mut env = Env::new(); + for (name, value) in inputs { + env.insert(name.clone(), value.clone()); + } + if env_is_two_value_safe(&env) { + return Ok(()); + } + Err(Error::Parse( + "Yosys/CXXRTL reference simulation requires concrete two-valued inputs".to_string(), + )) +} + +fn validate_inputs(m: &CompiledComboModule, inputs: &BTreeMap) -> Result<()> { + if m.input_ports.len() != inputs.len() { + return Err(Error::Parse(format!( + "input port count mismatch: module has {} inputs but vector has {}", + m.input_ports.len(), + inputs.len() + ))); + } + for p in &m.input_ports { + let value = inputs + .get(&p.name) + .ok_or_else(|| Error::Parse(format!("missing expected input `{}`", p.name)))?; + if value.width != p.width { + return Err(Error::Parse(format!( + "input width mismatch for `{}`: module width={} vector width={}", + p.name, p.width, value.width + ))); + } + } + Ok(()) +} + +fn render_driver_cpp( + _module_name: &str, + m: &CompiledComboModule, + inputs: &BTreeMap, +) -> String { + let mut s = String::new(); + s.push_str("#include \"dut.h\"\n"); + s.push_str("#include \n"); + s.push_str("#include \n"); + s.push_str("#include \n"); + s.push_str("#include \n"); + s.push_str("#include \n"); + s.push_str("#include \n\n"); + s.push_str("template\n"); + s.push_str("void set_hex(cxxrtl::value &dst, const char *hex) {\n"); + s.push_str(" for (size_t i = 0; i < cxxrtl::value::chunks; ++i) dst.data[i] = 0;\n"); + s.push_str(" const size_t len = std::strlen(hex);\n"); + s.push_str(" size_t bit_index = 0;\n"); + s.push_str(" for (size_t n = 0; n < len; ++n) {\n"); + s.push_str(" const char ch = hex[len - 1 - n];\n"); + s.push_str(" unsigned nibble = 0;\n"); + s.push_str(" if (ch >= '0' && ch <= '9') nibble = unsigned(ch - '0');\n"); + s.push_str(" else if (ch >= 'a' && ch <= 'f') nibble = 10u + unsigned(ch - 'a');\n"); + s.push_str(" else if (ch >= 'A' && ch <= 'F') nibble = 10u + unsigned(ch - 'A');\n"); + s.push_str(" else throw std::runtime_error(\"bad hex input\");\n"); + s.push_str(" for (size_t k = 0; k < 4 && bit_index < Bits; ++k, ++bit_index) {\n"); + s.push_str(" if ((nibble & (1u << k)) == 0) continue;\n"); + s.push_str(" const size_t chunk_index = bit_index / cxxrtl::value::chunk::bits;\n"); + s.push_str(" const size_t chunk_offset = bit_index % cxxrtl::value::chunk::bits;\n"); + s.push_str(" dst.data[chunk_index] |= cxxrtl::chunk_t(1) << chunk_offset;\n"); + s.push_str(" }\n"); + s.push_str(" }\n"); + s.push_str("}\n\n"); + s.push_str("template\n"); + s.push_str("std::string to_bits(const cxxrtl::value &value) {\n"); + s.push_str(" std::string out;\n"); + s.push_str(" out.reserve(Bits);\n"); + s.push_str(" for (size_t n = 0; n < Bits; ++n) {\n"); + s.push_str(" const size_t bit_index = Bits - 1 - n;\n"); + s.push_str(" const size_t chunk_index = bit_index / cxxrtl::value::chunk::bits;\n"); + s.push_str(" const size_t chunk_offset = bit_index % cxxrtl::value::chunk::bits;\n"); + s.push_str( + " const bool bit = ((value.data[chunk_index] >> chunk_offset) & cxxrtl::chunk_t(1)) != 0;\n", + ); + s.push_str(" out.push_back(bit ? '1' : '0');\n"); + s.push_str(" }\n"); + s.push_str(" return out;\n"); + s.push_str("}\n\n"); + writeln!(&mut s, "int main() {{\n cxxrtl_design::p_top top;\n").unwrap(); + for input in &m.input_ports { + let value = inputs.get(&input.name).unwrap(); + let hex = value.to_hex_string_if_known().unwrap(); + writeln!( + &mut s, + " set_hex(top.p_{name}, \"{hex}\");", + name = input.name + ) + .unwrap(); + } + s.push_str(" top.step();\n"); + for output in &m.output_ports { + writeln!( + &mut s, + " std::cout << \"OUT name={name} bits=\" << to_bits(top.p_{name}) << \"\\n\";", + name = output.name + ) + .unwrap(); + } + s.push_str(" return 0;\n}\n"); + s +} + +fn parse_driver_output(m: &CompiledComboModule, stdout: &str) -> Result> { + let mut out = BTreeMap::new(); + for line in stdout.lines() { + let line = line.trim(); + if !line.starts_with("OUT ") { + continue; + } + let mut name = None; + let mut bits = None; + for part in line.split_whitespace() { + if let Some(value) = part.strip_prefix("name=") { + name = Some(value.to_string()); + } else if let Some(value) = part.strip_prefix("bits=") { + bits = Some(value.to_string()); + } + } + let name = + name.ok_or_else(|| Error::Parse(format!("malformed CXXRTL output line: {line}")))?; + let bits = + bits.ok_or_else(|| Error::Parse(format!("malformed CXXRTL output line: {line}")))?; + out.insert(name, value4_from_two_value_bits(&bits)?); + } + for output in &m.output_ports { + if !out.contains_key(&output.name) { + return Err(Error::Parse(format!( + "missing output `{}` in CXXRTL output", + output.name + ))); + } + } + Ok(out) +} + +fn value4_from_two_value_bits(bits: &str) -> Result { + let mut out = Vec::with_capacity(bits.len()); + for c in bits.chars().rev() { + out.push(match c { + '0' => LogicBit::Zero, + '1' => LogicBit::One, + _ => { + return Err(Error::Parse(format!( + "unexpected non-two-value CXXRTL bit `{c}` in `{bits}`" + ))); + } + }); + } + Ok(Value4::new(out.len() as u32, Signedness::Unsigned, out)) +} + +fn mk_temp_dir() -> Result { + let base = std::env::temp_dir(); + let pid = std::process::id(); + let nanos = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .ok() + .map(|d| d.as_nanos()) + .unwrap_or(0); + for attempt in 0u32..1000u32 { + let p = base.join(format!("vastly_cxxrtl_combo_{pid}_{nanos}_{attempt}")); + match std::fs::create_dir(&p) { + Ok(()) => return Ok(p), + Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => continue, + Err(e) => return Err(Error::Parse(format!("create temp dir failed: {e}"))), + } + } + Err(Error::Parse( + "failed to create unique temp dir for Yosys/CXXRTL combo run".to_string(), + )) +} + +fn yosys_path(path: &std::path::Path) -> String { + path.to_string_lossy().into_owned() +} diff --git a/xlsynth-vastly/tests/compare_combo_vcd_to_iverilog.rs b/xlsynth-vastly/tests/compare_combo_vcd_to_reference_sim.rs similarity index 95% rename from xlsynth-vastly/tests/compare_combo_vcd_to_iverilog.rs rename to xlsynth-vastly/tests/compare_combo_vcd_to_reference_sim.rs index 1cdf79f5..18c0c4f6 100644 --- a/xlsynth-vastly/tests/compare_combo_vcd_to_iverilog.rs +++ b/xlsynth-vastly/tests/compare_combo_vcd_to_reference_sim.rs @@ -1,5 +1,5 @@ // SPDX-License-Identifier: Apache-2.0 -#![cfg(feature = "iverilog-tests")] +#![cfg(feature = "reference-sim-tests")] use std::collections::BTreeMap; use std::collections::BTreeSet; @@ -61,7 +61,7 @@ fn assert_common_var_timelines_match(a: &Vcd, b: &Vcd) { } #[test] -fn combo_vcd_matches_iverilog_for_casez_function() { +fn combo_vcd_matches_reference_sim_for_casez_function() { let dut = r#" module m( input wire [1:0] sel, @@ -127,7 +127,7 @@ endmodule let td = mk_temp_dir(); let dut_path = td.join("dut.v"); let ours_vcd = td.join("ours.vcd"); - let iv_vcd = td.join("iverilog.vcd"); + let iv_vcd = td.join("reference_sim.vcd"); { let mut f = std::fs::File::create(&dut_path).unwrap(); @@ -145,7 +145,7 @@ endmodule } #[test] -fn helper_function_with_locals_and_signed_casts_matches_iverilog() { +fn helper_function_with_locals_and_signed_casts_matches_reference_sim() { let dut = r#" module fuzz_codegen_v( input wire [6:0] p0, @@ -192,7 +192,7 @@ endmodule let td = mk_temp_dir(); let dut_path = td.join("dut.v"); let ours_vcd = td.join("ours.vcd"); - let iv_vcd = td.join("iverilog.vcd"); + let iv_vcd = td.join("reference_sim.vcd"); { let mut f = std::fs::File::create(&dut_path).unwrap(); @@ -256,7 +256,7 @@ endmodule let td = mk_temp_dir(); let dut_path = td.join("dut.v"); let ours_vcd = td.join("ours.vcd"); - let iv_vcd = td.join("iverilog.vcd"); + let iv_vcd = td.join("reference_sim.vcd"); { let mut f = std::fs::File::create(&dut_path).unwrap(); @@ -317,7 +317,7 @@ endmodule let td = mk_temp_dir(); let dut_path = td.join("dut.v"); let ours_vcd = td.join("ours.vcd"); - let iv_vcd = td.join("iverilog.vcd"); + let iv_vcd = td.join("reference_sim.vcd"); { let mut f = std::fs::File::create(&dut_path).unwrap(); @@ -335,7 +335,7 @@ endmodule } #[test] -fn priority_sel_helper_with_decimal_x_default_matches_iverilog() { +fn priority_sel_helper_with_decimal_x_default_matches_reference_sim() { let dut = r#" module fuzz_codegen_v( input wire [7:0] p0, @@ -403,7 +403,7 @@ endmodule let td = mk_temp_dir(); let dut_path = td.join("dut.v"); let ours_vcd = td.join("ours.vcd"); - let iv_vcd = td.join("iverilog.vcd"); + let iv_vcd = td.join("reference_sim.vcd"); { let mut f = std::fs::File::create(&dut_path).unwrap(); @@ -421,7 +421,7 @@ endmodule } #[test] -fn dynamic_bit_slice_helper_matches_iverilog() { +fn dynamic_bit_slice_helper_matches_reference_sim() { let dut = r#" module dbs_mod_v( input wire [7:0] x, @@ -468,7 +468,7 @@ endmodule let td = mk_temp_dir(); let dut_path = td.join("dut.v"); let ours_vcd = td.join("ours.vcd"); - let iv_vcd = td.join("iverilog.vcd"); + let iv_vcd = td.join("reference_sim.vcd"); { let mut f = std::fs::File::create(&dut_path).unwrap(); @@ -486,7 +486,7 @@ endmodule } #[test] -fn bit_slice_update_helper_matches_iverilog() { +fn bit_slice_update_helper_matches_reference_sim() { let dut = r#" module bsu_mod_v( input wire [7:0] x, @@ -535,7 +535,7 @@ endmodule let td = mk_temp_dir(); let dut_path = td.join("dut.v"); let ours_vcd = td.join("ours.vcd"); - let iv_vcd = td.join("iverilog.vcd"); + let iv_vcd = td.join("reference_sim.vcd"); { let mut f = std::fs::File::create(&dut_path).unwrap(); @@ -553,7 +553,7 @@ endmodule } #[test] -fn array_index_helper_matches_iverilog() { +fn array_index_helper_matches_reference_sim() { let dut = r#" module f( input wire [31:0] arr, @@ -607,7 +607,7 @@ endmodule let td = mk_temp_dir(); let dut_path = td.join("dut.v"); let ours_vcd = td.join("ours.vcd"); - let iv_vcd = td.join("iverilog.vcd"); + let iv_vcd = td.join("reference_sim.vcd"); { let mut f = std::fs::File::create(&dut_path).unwrap(); @@ -625,7 +625,7 @@ endmodule } #[test] -fn array_slice_helper_matches_iverilog() { +fn array_slice_helper_matches_reference_sim() { let dut = r#" module f( input wire [31:0] arr, @@ -681,7 +681,7 @@ endmodule let td = mk_temp_dir(); let dut_path = td.join("dut.v"); let ours_vcd = td.join("ours.vcd"); - let iv_vcd = td.join("iverilog.vcd"); + let iv_vcd = td.join("reference_sim.vcd"); { let mut f = std::fs::File::create(&dut_path).unwrap(); @@ -699,7 +699,7 @@ endmodule } #[test] -fn array_update_helper_matches_iverilog() { +fn array_update_helper_matches_reference_sim() { let dut = r#" module f( input wire [31:0] arr, @@ -768,7 +768,7 @@ endmodule let td = mk_temp_dir(); let dut_path = td.join("dut.v"); let ours_vcd = td.join("ours.vcd"); - let iv_vcd = td.join("iverilog.vcd"); + let iv_vcd = td.join("reference_sim.vcd"); { let mut f = std::fs::File::create(&dut_path).unwrap(); @@ -786,7 +786,7 @@ endmodule } #[test] -fn unpacked_array_generated_combo_matches_iverilog() { +fn unpacked_array_generated_combo_matches_reference_sim() { let dut = r#" module fuzz_codegen_v( input wire [2:0] p0, @@ -827,7 +827,7 @@ endmodule let td = mk_temp_dir(); let dut_path = td.join("dut.v"); let ours_vcd = td.join("ours.vcd"); - let iv_vcd = td.join("iverilog.vcd"); + let iv_vcd = td.join("reference_sim.vcd"); { let mut f = std::fs::File::create(&dut_path).unwrap(); @@ -845,7 +845,7 @@ endmodule } #[test] -fn nested_unpacked_array_generated_combo_matches_iverilog() { +fn nested_unpacked_array_generated_combo_matches_reference_sim() { let dut = r#" module fuzz_codegen_v( input wire [1:0] p0, @@ -886,7 +886,7 @@ endmodule let td = mk_temp_dir(); let dut_path = td.join("dut.v"); let ours_vcd = td.join("ours.vcd"); - let iv_vcd = td.join("iverilog.vcd"); + let iv_vcd = td.join("reference_sim.vcd"); { let mut f = std::fs::File::create(&dut_path).unwrap(); @@ -904,7 +904,7 @@ endmodule } #[test] -fn finding96_codegen_decode_guarded_shift_matches_iverilog() { +fn finding96_codegen_decode_guarded_shift_matches_reference_sim() { // Repro from fuzz finding 96 trace. let dut = r#" module fuzz_codegen_v( @@ -946,7 +946,7 @@ endmodule let td = mk_temp_dir(); let dut_path = td.join("dut.v"); let ours_vcd = td.join("ours.vcd"); - let iv_vcd = td.join("iverilog.vcd"); + let iv_vcd = td.join("reference_sim.vcd"); { let mut f = std::fs::File::create(&dut_path).unwrap(); @@ -964,7 +964,7 @@ endmodule } #[test] -fn finding57_codegen_wide_unary_minus_slice_matches_iverilog() { +fn finding57_codegen_wide_unary_minus_slice_matches_reference_sim() { // Repro class from fuzz finding 57 trace. let dut = r#" module fuzz_codegen_v( @@ -1006,7 +1006,7 @@ endmodule let td = mk_temp_dir(); let dut_path = td.join("dut.v"); let ours_vcd = td.join("ours.vcd"); - let iv_vcd = td.join("iverilog.vcd"); + let iv_vcd = td.join("reference_sim.vcd"); { let mut f = std::fs::File::create(&dut_path).unwrap(); @@ -1024,7 +1024,7 @@ endmodule } #[test] -fn finding1_codegen_wide_multiply_slice_matches_iverilog() { +fn finding1_codegen_wide_multiply_slice_matches_reference_sim() { // Repro class from fuzz finding 1 trace. let dut = r#" module fuzz_codegen_v( @@ -1071,7 +1071,7 @@ endmodule let td = mk_temp_dir(); let dut_path = td.join("dut.v"); let ours_vcd = td.join("ours.vcd"); - let iv_vcd = td.join("iverilog.vcd"); + let iv_vcd = td.join("reference_sim.vcd"); { let mut f = std::fs::File::create(&dut_path).unwrap(); diff --git a/xlsynth-vastly/tests/compare_module_to_iverilog.rs b/xlsynth-vastly/tests/compare_module_to_reference_sim.rs similarity index 97% rename from xlsynth-vastly/tests/compare_module_to_iverilog.rs rename to xlsynth-vastly/tests/compare_module_to_reference_sim.rs index a7d32c3d..f9a3838a 100644 --- a/xlsynth-vastly/tests/compare_module_to_iverilog.rs +++ b/xlsynth-vastly/tests/compare_module_to_reference_sim.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 -#![cfg(feature = "iverilog-tests")] +#![cfg(feature = "reference-sim-tests")] -mod iverilog_oracle; +mod reference_sim_iverilog; use std::io::Write; use std::process::Command; @@ -30,7 +30,7 @@ fn vbits(width: u32, signedness: Signedness, msb: &str) -> Value4 { #[test] fn module_step_matches_iverilog_simple_counter() { - iverilog_oracle::require_iverilog(); + reference_sim_iverilog::require_iverilog(); let src = r#" module m(input logic clk, input logic en, output logic [3:0] q); @@ -60,7 +60,7 @@ endmodule #[test] fn module_step_part_select_and_concat() { - iverilog_oracle::require_iverilog(); + reference_sim_iverilog::require_iverilog(); let src = r#" module m(input logic clk, input logic [3:0] a, output logic [7:0] q); diff --git a/xlsynth-vastly/tests/compare_pipeline_vcd_to_iverilog.rs b/xlsynth-vastly/tests/compare_pipeline_vcd_to_reference_sim.rs similarity index 97% rename from xlsynth-vastly/tests/compare_pipeline_vcd_to_iverilog.rs rename to xlsynth-vastly/tests/compare_pipeline_vcd_to_reference_sim.rs index 234d1194..47c8906b 100644 --- a/xlsynth-vastly/tests/compare_pipeline_vcd_to_iverilog.rs +++ b/xlsynth-vastly/tests/compare_pipeline_vcd_to_reference_sim.rs @@ -1,5 +1,5 @@ // SPDX-License-Identifier: Apache-2.0 -#![cfg(feature = "iverilog-tests")] +#![cfg(feature = "reference-sim-tests")] use std::collections::BTreeMap; use std::io::Write; @@ -37,7 +37,7 @@ fn vbits(width: u32, signedness: Signedness, msb: &str) -> Value4 { } #[test] -fn vcd_matches_iverilog_for_pipeline_with_combo_outputs() { +fn vcd_matches_reference_sim_for_pipeline_with_combo_outputs() { require_iverilog(); let dut = r#" @@ -97,7 +97,7 @@ endmodule let td = mk_temp_dir(); let ours_vcd = td.join("ours.vcd"); - let iv_vcd = td.join("iverilog.vcd"); + let iv_vcd = td.join("reference_sim.vcd"); run_pipeline_and_write_vcd(&cm, &stimulus, &init, &ours_vcd).unwrap(); run_iverilog_pipeline_and_collect_vcd(dut, &cm, &stimulus, &init, &iv_vcd); @@ -110,7 +110,7 @@ endmodule } #[test] -fn vcd_matches_iverilog_for_pipeline_with_two_stateful_stages() { +fn vcd_matches_reference_sim_for_pipeline_with_two_stateful_stages() { require_iverilog(); let dut = r#" @@ -258,7 +258,7 @@ endmodule let td = mk_temp_dir(); let ours_vcd = td.join("ours.vcd"); - let iv_vcd = td.join("iverilog.vcd"); + let iv_vcd = td.join("reference_sim.vcd"); run_pipeline_and_write_vcd(&cm, &stimulus, &init, &ours_vcd).unwrap(); run_iverilog_pipeline_and_collect_vcd(dut, &cm, &stimulus, &init, &iv_vcd); diff --git a/xlsynth-vastly/tests/compare_to_iverilog.rs b/xlsynth-vastly/tests/compare_to_reference_sim.rs similarity index 96% rename from xlsynth-vastly/tests/compare_to_iverilog.rs rename to xlsynth-vastly/tests/compare_to_reference_sim.rs index bfec044b..e738a72d 100644 --- a/xlsynth-vastly/tests/compare_to_iverilog.rs +++ b/xlsynth-vastly/tests/compare_to_reference_sim.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 -#![cfg(feature = "iverilog-tests")] +#![cfg(feature = "reference-sim-tests")] -mod iverilog_oracle; +mod reference_sim_iverilog; use xlsynth_vastly::Env; use xlsynth_vastly::LogicBit; @@ -25,7 +25,7 @@ fn vbits(width: u32, signedness: Signedness, msb: &str) -> Value4 { fn assert_eval_matches_oracle(expr: &str, env: &Env) { let ours = xlsynth_vastly::eval_expr(expr, env).expect("eval_expr"); - let oracle = iverilog_oracle::run_oracle(expr, env); + let oracle = reference_sim_iverilog::run_oracle(expr, env); assert_eq!(ours.value.width, oracle.width, "width mismatch for {expr}"); assert_eq!( @@ -159,8 +159,8 @@ fn signedness_observable_via_extension_for_literals() { let env = Env::new(); // 4'sb1000 should be signed, 4'b1000 should be unsigned. - let o_signed = iverilog_oracle::run_oracle("4'sb1000", &env); - let o_unsigned = iverilog_oracle::run_oracle("4'b1000", &env); + let o_signed = reference_sim_iverilog::run_oracle("4'sb1000", &env); + let o_unsigned = reference_sim_iverilog::run_oracle("4'b1000", &env); let ours_signed = xlsynth_vastly::eval_expr("4'sb1000", &env) .unwrap() @@ -175,11 +175,11 @@ fn signedness_observable_via_extension_for_literals() { assert_eq!(ours_unsigned, Signedness::Unsigned); assert_eq!( - iverilog_oracle::infer_signedness_from_ext(&o_signed), + reference_sim_iverilog::infer_signedness_from_ext(&o_signed), Some(true) ); assert_eq!( - iverilog_oracle::infer_signedness_from_ext(&o_unsigned), + reference_sim_iverilog::infer_signedness_from_ext(&o_unsigned), Some(false) ); } diff --git a/xlsynth-vastly/tests/compare_vcd_to_iverilog.rs b/xlsynth-vastly/tests/compare_vcd_to_reference_sim.rs similarity index 95% rename from xlsynth-vastly/tests/compare_vcd_to_iverilog.rs rename to xlsynth-vastly/tests/compare_vcd_to_reference_sim.rs index a0ef97d2..a415b487 100644 --- a/xlsynth-vastly/tests/compare_vcd_to_iverilog.rs +++ b/xlsynth-vastly/tests/compare_vcd_to_reference_sim.rs @@ -1,5 +1,5 @@ // SPDX-License-Identifier: Apache-2.0 -#![cfg(feature = "iverilog-tests")] +#![cfg(feature = "reference-sim-tests")] mod vcd_oracle; @@ -32,7 +32,7 @@ fn vbits(width: u32, signedness: Signedness, msb: &str) -> Value4 { } #[test] -fn vcd_matches_iverilog_for_simple_counter() { +fn vcd_matches_reference_sim_for_simple_counter() { vcd_oracle::require_iverilog(); let dut = r#" @@ -71,7 +71,7 @@ endmodule let td = mk_temp_dir(); let ours_vcd = td.join("ours.vcd"); - let iv_vcd = td.join("iverilog.vcd"); + let iv_vcd = td.join("reference_sim.vcd"); xlsynth_vastly::run_and_write_vcd(&cm, &stimulus, &init, &ours_vcd).unwrap(); vcd_oracle::run_iverilog_and_collect_vcd(dut, &stimulus, &init, &iv_vcd); diff --git a/xlsynth-vastly/tests/context_sweep_iverilog.rs b/xlsynth-vastly/tests/context_sweep_reference_sim.rs similarity index 98% rename from xlsynth-vastly/tests/context_sweep_iverilog.rs rename to xlsynth-vastly/tests/context_sweep_reference_sim.rs index 55945fed..8fd5e89c 100644 --- a/xlsynth-vastly/tests/context_sweep_iverilog.rs +++ b/xlsynth-vastly/tests/context_sweep_reference_sim.rs @@ -1,15 +1,16 @@ // SPDX-License-Identifier: Apache-2.0 -#![cfg(feature = "iverilog-tests")] +#![cfg(feature = "reference-sim-tests")] // This file provides deterministic, oracle-backed "context sweep" coverage for // expression semantics that frequently break under width/signedness inference. // // Why this exists: -// - The `diff_iverilog` fuzzer is excellent at finding edge cases, but it is -// stochastic and can regress silently if a previously-found corner case +// - The `diff_refsim_4value` fuzzer is excellent at finding edge cases, but it +// is stochastic and can regress silently if a previously-found corner case // becomes harder to rediscover. // - These sweeps pin down families of context-sensitive rules as stable, -// reproducible integration tests against an external oracle (`iverilog`). +// reproducible integration tests against an external reference +// implementation. // // How this is structured: // - Generate bounded cartesian-style combinations across widths, signedness, @@ -18,7 +19,7 @@ // - Assert both width and value bits against oracle results for each // expression. -mod iverilog_oracle; +mod reference_sim_iverilog; use xlsynth_vastly::Env; use xlsynth_vastly::LogicBit; @@ -57,7 +58,7 @@ fn assert_eval_matches_oracle(expr: &str, label: &str) { fn assert_eval_matches_oracle_with_env(expr: &str, env: &Env, label: &str) { let ours = xlsynth_vastly::eval_expr(expr, &env).expect("eval_expr"); - let oracle = iverilog_oracle::run_oracle(expr, &env); + let oracle = reference_sim_iverilog::run_oracle(expr, &env); assert_eq!( ours.value.width, oracle.width, "width mismatch [{label}] expr={expr}" diff --git a/xlsynth-vastly/tests/iverilog_oracle.rs b/xlsynth-vastly/tests/reference_sim_iverilog.rs similarity index 100% rename from xlsynth-vastly/tests/iverilog_oracle.rs rename to xlsynth-vastly/tests/reference_sim_iverilog.rs