Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions xlsynth-driver/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ cargo run -p xlsynth-driver -- --toolchain=$HOME/xlsynth-toolchain.toml \
cargo run -p xlsynth-driver -- --toolchain=$HOME/xlsynth-toolchain.toml \
dslx2pipeline ../sample-usage/src/sample.x add1 \
--delay_model=asap7 --pipeline_stages=2
cargo run -p xlsynth-driver -- dslx2sv-types ../tests/structure_zoo.x \
--sv_enum_case_naming_policy=unqualified
cargo run -p xlsynth-driver -- dslx2sv-types \
--dslx_input_file=../tests/structure_zoo.x \
--sv_enum_case_naming_policy=unqualified \
--sv_struct_field_ordering=reversed
```

For a full list of options, run `xlsynth-driver <subcommand> --help`.
Expand Down Expand Up @@ -376,6 +378,10 @@ Required flags:

- `--sv_enum_case_naming_policy <unqualified|enum_qualified>` – controls whether enum members are emitted as unqualified case names (e.g. `Read`) or prefixed with the enum name (e.g. `OpType_Read`).

Optional flags:

- `--sv_struct_field_ordering <as_declared|reversed>` – controls the packed bit layout of generated `typedef struct packed` declarations by emitting members in DSLX declaration order (`as_declared`, the default) or in reverse declaration order (`reversed`).

### `dslx-show`: Show a DSLX symbol definition

Resolves and prints a DSLX symbol definition (enums, structs, type aliases, constants, functions, quickchecks).
Expand Down
38 changes: 23 additions & 15 deletions xlsynth-driver/src/dslx2sv_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,31 @@

//! Execution path for the `xlsynth-driver dslx2sv-types` subcommand.
//!
//! This module parses the CLI-selected enum-case naming policy and routes it to
//! the shared `SvBridgeBuilder` so callers (for example Bazel rules) can choose
//! whether generated SV enum members are emitted as case-only symbols or
//! enum-qualified symbols.
//! This module parses the CLI-selected SV emission policies and routes them to
//! the shared `SvBridgeBuilder` so callers (for example Bazel rules) can
//! choose enum-member spelling and packed-struct bit layout.

use clap::ArgMatches;

use crate::common::get_dslx_paths;
use crate::toolchain_config::ToolchainConfig;
use xlsynth::sv_bridge_builder::SvEnumCaseNamingPolicy;
use xlsynth::sv_bridge_builder::{SvEnumCaseNamingPolicy, SvStructFieldOrderingPolicy};

/// Converts DSLX type definitions to SV type declarations and writes the result
/// to stdout.
///
/// This function is intentionally thin: it wires file contents, import search
/// paths, and the caller-selected [`SvEnumCaseNamingPolicy`] into the shared
/// bridge implementation. Passing `Unqualified` for a module whose enums reuse
/// case names across types will surface as a generation-time collision error in
/// the builder.
/// paths, and the caller-selected SV emission policies into the shared bridge
/// implementation. Passing `Unqualified` for a module whose enums reuse case
/// names across types will surface as a generation-time collision error in the
/// builder. Choosing `Reversed` for `struct_field_ordering_policy` changes the
/// packed bit layout of emitted SV structs, not only their textual order.
pub fn dslx2sv_types(
input_file: &std::path::Path,
dslx_stdlib_path: Option<&std::path::Path>,
search_paths: &[&std::path::Path],
enum_case_naming_policy: SvEnumCaseNamingPolicy,
struct_field_ordering_policy: SvStructFieldOrderingPolicy,
) {
log::info!("dslx2sv_types");
let dslx = std::fs::read_to_string(input_file).unwrap();
Expand All @@ -36,8 +37,10 @@ pub fn dslx2sv_types(

let mut import_data =
xlsynth::dslx::ImportData::new(dslx_stdlib_path, additional_search_path_views);
let mut builder =
xlsynth::sv_bridge_builder::SvBridgeBuilder::with_enum_case_policy(enum_case_naming_policy);
let mut builder = xlsynth::sv_bridge_builder::SvBridgeBuilder::with_policies(
enum_case_naming_policy,
struct_field_ordering_policy,
);
xlsynth::dslx_bridge::convert_leaf_module(&mut import_data, &dslx, input_file, &mut builder)
.unwrap();
let sv = builder.build();
Expand All @@ -47,11 +50,12 @@ pub fn dslx2sv_types(
/// Handles the `dslx2sv-types` subcommand from the top-level Clap dispatch.
///
/// The CLI definition in `main.rs` requires and validates
/// `--sv_enum_case_naming_policy` using Clap's `ValueEnum` support on
/// [`SvEnumCaseNamingPolicy`], so this function can retrieve the typed value
/// `--sv_enum_case_naming_policy` and `--sv_struct_field_ordering` using
/// Clap's `ValueEnum` support on [`SvEnumCaseNamingPolicy`] and
/// [`SvStructFieldOrderingPolicy`], so this function can retrieve typed values
/// directly and then delegate to [`dslx2sv_types`]. Calling this with
/// `ArgMatches` from another subcommand would panic because the code
/// unwraps/`expect`s required `dslx2sv-types` arguments.
/// `ArgMatches` from another subcommand would panic because the code unwraps
/// required `dslx2sv-types` arguments.
pub fn handle_dslx2sv_types(matches: &ArgMatches, config: &Option<ToolchainConfig>) {
log::info!("handle_dslx2sv_types");
let input_file = matches.get_one::<String>("dslx_input_file").unwrap();
Expand All @@ -61,6 +65,9 @@ pub fn handle_dslx2sv_types(matches: &ArgMatches, config: &Option<ToolchainConfi
let enum_case_naming_policy = *matches
.get_one::<SvEnumCaseNamingPolicy>("sv_enum_case_naming_policy")
.expect("clap should require sv_enum_case_naming_policy");
let struct_field_ordering_policy = *matches
.get_one::<SvStructFieldOrderingPolicy>("sv_struct_field_ordering")
.expect("clap should default sv_struct_field_ordering");

let paths = get_dslx_paths(matches, config);
let dslx_stdlib_path = paths.stdlib_path.as_ref().map(|p| p.as_path());
Expand All @@ -70,5 +77,6 @@ pub fn handle_dslx2sv_types(matches: &ArgMatches, config: &Option<ToolchainConfi
dslx_stdlib_path,
&search_views,
enum_case_naming_policy,
struct_field_ordering_policy,
);
}
15 changes: 14 additions & 1 deletion xlsynth-driver/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ use clap::{Arg, ArgAction};
use once_cell::sync::Lazy;
use report_cli_error::report_cli_error_and_exit;
use serde::Deserialize;
use xlsynth::sv_bridge_builder::SvEnumCaseNamingPolicy;
use xlsynth::sv_bridge_builder::{SvEnumCaseNamingPolicy, SvStructFieldOrderingPolicy};
use xlsynth_prover::prover::types::AssertionSemantics;
use xlsynth_prover::prover::types::QuickCheckAssertionSemantics;

Expand Down Expand Up @@ -699,6 +699,19 @@ fn main() {
.value_parser(clap::builder::EnumValueParser::<
SvEnumCaseNamingPolicy,
>::new()),
)
.arg(
Arg::new("sv_struct_field_ordering")
.long("sv_struct_field_ordering")
.value_name("POLICY")
.help(
"Packed-struct layout policy for generated SystemVerilog packed structs; member order controls the packed bit layout",
)
.default_value("as_declared")
.action(ArgAction::Set)
.value_parser(clap::builder::EnumValueParser::<
SvStructFieldOrderingPolicy,
>::new()),
),
)
.subcommand(
Expand Down
121 changes: 121 additions & 0 deletions xlsynth-driver/tests/invoke_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,94 @@ fn test_dslx2sv_types_subcommand_enum_qualified_policy() {
);
}

// Verifies: dslx2sv-types keeps the default packed-struct layout when the new
// struct field ordering flag is omitted.
// Catches: Regressions where adding the optional struct-ordering flag silently
// changes the default packed-struct member layout.
#[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;"#
);
}

// Verifies: dslx2sv-types reverses packed-struct member declarations, which
// changes the emitted packed layout, when the new policy is selected.
// Catches: Regressions where the CLI accepts the new policy but the builder
// still emits the default packed layout.
#[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;"#
);
}

// Negative test: omitting the required enum-case naming policy flag returns a
// CLI error instead of silently choosing a default.
#[test]
Expand Down Expand Up @@ -836,6 +924,39 @@ fn test_dslx2sv_types_requires_sv_enum_case_naming_policy_flag() {
assert!(stderr.contains("required"));
}

// Negative test: invalid struct field ordering policy values are rejected by
// CLI validation with a helpful allowed-values error.
#[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"));
}

// Negative test: invalid enum-case naming policy values are rejected by CLI
// validation with a helpful allowed-values error.
#[test]
Expand Down
Loading
Loading