From 245e303450611cd94c599ad1b6cfd18254b92aad Mon Sep 17 00:00:00 2001 From: Daniel Keller Date: Sat, 14 Feb 2026 22:08:24 +0100 Subject: [PATCH 1/2] feat(script): add Xcelium script format support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add `bender script xcelium` command that generates a flat .f file for Cadence Xcelium/xrun. Per-group incdirs, defines, and source files are emitted directly — consistent with the vsim and vcs templates. Also adds a global `--incdir` / `-I` flag to inject user-provided include directories into both the global incdir list and each per-source-group's incdirs. Other changes: - Add `name` field to TplSrcStruct (from src.package) - Add `root_package` to template context - Support `--relative-path` for $ROOT-relative output - Sanitize library names (hyphens to underscores) in group names --- src/cmd/script.rs | 26 +++++++++++++++++++++++++- src/script_fmt/xcelium_f.tera | 13 +++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 src/script_fmt/xcelium_f.tera diff --git a/src/cmd/script.rs b/src/cmd/script.rs index 847563059..aa4438473 100644 --- a/src/cmd/script.rs +++ b/src/cmd/script.rs @@ -34,6 +34,10 @@ pub struct ScriptArgs { #[arg(short = 'D', long, action = ArgAction::Append, global = true, help_heading = "General Script Options")] pub define: Vec, + /// Add an include directory + #[arg(short = 'I', long, action = ArgAction::Append, global = true, help_heading = "General Script Options")] + pub incdir: Vec, + /// Remove source annotations from the generated script #[arg(long, help_heading = "General Script Options")] pub no_source_annotations: bool, @@ -179,6 +183,12 @@ pub enum ScriptFormat { #[arg(long, action = ArgAction::Append, alias = "vcom-arg")] vcom_args: Vec, }, + /// Cadence Xcelium script + Xcelium { + /// Use relative paths + #[arg(long)] + relative_path: bool, + }, /// Cadence Genus script Genus, /// Xilinx Vivado synthesis script @@ -246,6 +256,7 @@ pub fn run(sess: &Session, args: &ScriptArgs) -> Result<()> { ScriptFormat::Synopsys { .. } => vec!["synopsys", "synthesis"], ScriptFormat::Formality => vec!["synopsys", "synthesis", "formality"], ScriptFormat::Riviera { .. } => vec!["riviera", "simulation"], + ScriptFormat::Xcelium { .. } => vec!["xcelium", "simulation"], ScriptFormat::Genus => vec!["genus", "synthesis"], ScriptFormat::Vivado { .. } => concat(vivado_targets, &["synthesis"]), ScriptFormat::VivadoSim { .. } => concat(vivado_targets, &["simulation"]), @@ -366,6 +377,10 @@ pub fn run(sess: &Session, args: &ScriptArgs) -> Result<()> { tera_context.insert("vcom_args", vcom_args); include_str!("../script_fmt/riviera_tcl.tera") } + ScriptFormat::Xcelium { relative_path } => { + tera_context.insert("relativize_path", relative_path); + include_str!("../script_fmt/xcelium_f.tera") + } ScriptFormat::Genus => include_str!("../script_fmt/genus_tcl.tera"), ScriptFormat::Vivado { no_simset, only } | ScriptFormat::VivadoSim { no_simset, only } => { only_args = only.clone(); @@ -439,6 +454,7 @@ fn emit_template( ) -> Result<()> { tera_context.insert("HEADER_AUTOGEN", HEADER_AUTOGEN); tera_context.insert("root", sess.root); + tera_context.insert("root_package", &sess.manifest.package.name); // tera_context.insert("srcs", &srcs); tera_context.insert("abort_on_error", &!args.no_abort_on_error); @@ -478,8 +494,13 @@ fn emit_template( tera_context.insert("all_defines", &all_defines); all_incdirs.sort(); + let user_incdirs: Vec = args.incdir.iter().map(PathBuf::from).collect(); let all_incdirs: IndexSet = if emit_incdirs { - all_incdirs.into_iter().map(|p| p.to_path_buf()).collect() + all_incdirs + .into_iter() + .map(|p| p.to_path_buf()) + .chain(user_incdirs.iter().cloned()) + .collect() } else { IndexSet::new() }; @@ -507,6 +528,7 @@ fn emit_template( }, |src, ty, files| { split_srcs.push(TplSrcStruct { + name: src.package.map(|s| s.to_string()), metadata: { let package = src.package.unwrap_or("None"); let target = src.target.reduce().to_string(); @@ -530,6 +552,7 @@ fn emit_template( .iter() .map(|p| p.to_path_buf()) .collect::>(); + incdirs.extend(user_incdirs.iter().cloned()); incdirs.sort(); incdirs }, @@ -597,6 +620,7 @@ fn emit_template( #[derive(Debug, Serialize)] struct TplSrcStruct { + name: Option, metadata: String, defines: IndexSet<(String, Option)>, incdirs: IndexSet, diff --git a/src/script_fmt/xcelium_f.tera b/src/script_fmt/xcelium_f.tera new file mode 100644 index 000000000..4efe4e3a9 --- /dev/null +++ b/src/script_fmt/xcelium_f.tera @@ -0,0 +1,13 @@ +# {{ HEADER_AUTOGEN }} +{%- for group in srcs %} +{% if source_annotations %}# {{ group.metadata }}{% endif %} +{%- for incdir in group.incdirs %} ++incdir+{% if relativize_path and incdir is starting_with(root) %}{{ incdir | replace(from=root, to='$ROOT') }}{% else %}{{ incdir }}{% endif %} +{%- endfor %} +{%- for define in group.defines %} ++define+{{ define.0 }}{% if define.1 %}={{ define.1 }}{% endif %} +{%- endfor %} +{%- for file in group.files %} +{% if relativize_path and file is starting_with(root) %}{{ file | replace(from=root, to='$ROOT') }}{% else %}{{ file }}{% endif %} +{%- endfor %} +{%- endfor %} From 346b168c4d756de0c71824050656a0a7aea7f562 Mon Sep 17 00:00:00 2001 From: Daniel Keller Date: Sat, 14 Feb 2026 22:32:57 +0100 Subject: [PATCH 2/2] fix: Remove unused template fields breaking cli_regression Remove `root_package` and `name` fields from template context that were not used by any .tera template but changed the template_json output, causing cli_template_json regression test to fail. --- src/cmd/script.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/cmd/script.rs b/src/cmd/script.rs index aa4438473..87ef56966 100644 --- a/src/cmd/script.rs +++ b/src/cmd/script.rs @@ -454,7 +454,6 @@ fn emit_template( ) -> Result<()> { tera_context.insert("HEADER_AUTOGEN", HEADER_AUTOGEN); tera_context.insert("root", sess.root); - tera_context.insert("root_package", &sess.manifest.package.name); // tera_context.insert("srcs", &srcs); tera_context.insert("abort_on_error", &!args.no_abort_on_error); @@ -528,7 +527,6 @@ fn emit_template( }, |src, ty, files| { split_srcs.push(TplSrcStruct { - name: src.package.map(|s| s.to_string()), metadata: { let package = src.package.unwrap_or("None"); let target = src.target.reduce().to_string(); @@ -620,7 +618,6 @@ fn emit_template( #[derive(Debug, Serialize)] struct TplSrcStruct { - name: Option, metadata: String, defines: IndexSet<(String, Option)>, incdirs: IndexSet,