Skip to content
Open
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
18 changes: 18 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
Language: Cpp
BasedOnStyle: LLVM

# 4 spaces everywhere
IndentWidth: 4
TabWidth: 4
UseTab: Never
ContinuationIndentWidth: 4

# Modern C++ style
Standard: c++20
ColumnLimit: 120
PointerAlignment: Left

# Organize includes
SortIncludes: true
IncludeBlocks: Regroup
15 changes: 15 additions & 0 deletions .clangd
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
If:
PathMatch: (^|.*/)crates/bender-slang/cpp/.*\.(h|hpp|hh|c|cc|cpp|cxx)$
CompileFlags:
Add:
- -std=c++20
- -fno-cxx-modules
- -I.
- -I../../../crates
- -I../vendor/slang/include
- -I../vendor/slang/external
- -I../../../target/slang-generated-include
- -I../../../target/cxxbridge
- -DSLANG_USE_MIMALLOC=1
- -DSLANG_USE_THREADS=1
- -DSLANG_BOOST_SINGLE_HEADER=1
4 changes: 0 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ on:
push:
branches: [master]
pull_request:
branches: [master]
workflow_dispatch:

jobs:
Expand All @@ -26,13 +25,10 @@ jobs:
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust}}
components: rustfmt
- name: Build
run: cargo build --all-features
- name: Cargo Test
run: cargo test --workspace --all-features
- name: Format (fix with `cargo fmt`)
run: cargo fmt -- --check
- name: Run unit-tests
run: tests/run_all.sh
shell: bash
Expand Down
35 changes: 35 additions & 0 deletions .github/workflows/formatting.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: formatting

on:
push:
branches: [master]
pull_request:
branches: [master]
workflow_dispatch:

jobs:
rustfmt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
components: rustfmt
- name: Check Rust formatting
run: cargo fmt -- --check

clang-format:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Check C/C++ formatting
uses: DoozyX/clang-format-lint-action@v0.18
with:
source: "."
extensions: "h,hpp,c,cc,cpp,cxx"
exclude: "./crates/bender-slang/vendor"
15 changes: 9 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
.*
!/.ci/
!.git*
!.travis.yml
/target
/tests/tmp
# Cargo build files
target

# Temporary test files
tests/**/tmp
tests/**/.bender

# clangd
.cache/clangd
38 changes: 37 additions & 1 deletion crates/bender-slang/build.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,37 @@
// Copyright (c) 2025 ETH Zurich
// Tim Fischer <fischeti@iis.ee.ethz.ch>

#[cfg(unix)]
// We create a symlink from the generated include directory to a stable location in the target directory
// so that tools like clangd can find the headers without needing to know the exact OUT_DIR path.
// This is purely for improving the development experience and is not necessary for the build itself.
fn refresh_include_symlink(generated_include_dir: &std::path::Path) {
use std::ffi::OsStr;
use std::fs;
use std::os::unix::fs::symlink;
use std::path::PathBuf;

let Ok(out_dir) = std::env::var("OUT_DIR") else {
return;
};
let out_dir = PathBuf::from(out_dir);

let Some(target_root) = out_dir
.ancestors()
.find(|path| path.file_name() == Some(OsStr::new("target")))
else {
return;
};

let stable_link = target_root.join("slang-generated-include");
let _ = fs::remove_file(&stable_link);
let _ = fs::remove_dir_all(&stable_link);
let _ = symlink(generated_include_dir, &stable_link);
}

#[cfg(not(unix))]
fn refresh_include_symlink(_generated_include_dir: &std::path::Path) {}

fn main() {
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
let target_env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap();
Expand Down Expand Up @@ -64,6 +95,11 @@ fn main() {
let dst = slang_lib.build();
let lib_dir = dst.join("lib");

// Create a symlink for the generated include directory
if target_os == "linux" || target_os == "macos" {
refresh_include_symlink(&dst.join("include"));
}

// Configure Linker to find Slang static library
println!("cargo:rustc-link-search=native={}", lib_dir.display());
println!("cargo:rustc-link-lib=static=svlang");
Expand Down Expand Up @@ -97,7 +133,7 @@ fn main() {
let compiler = std::env::var("CXX").unwrap_or_else(|_| "g++".to_string());
// We search for the static libstdc++ file using g++
let output = std::process::Command::new(&compiler)
.args(&["-print-file-name=libstdc++.a"])
.args(["-print-file-name=libstdc++.a"])
.output()
.expect("Failed to run g++");

Expand Down
3 changes: 0 additions & 3 deletions crates/bender-slang/cpp/slang_bridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
#include "slang_bridge.h"

#include "bender-slang/src/lib.rs.h"
#include "slang/diagnostics/DiagnosticEngine.h"
#include "slang/diagnostics/TextDiagnosticClient.h"
#include "slang/syntax/CSTSerializer.h"
#include "slang/syntax/SyntaxPrinter.h"
#include "slang/syntax/SyntaxVisitor.h"
Expand All @@ -17,7 +15,6 @@
#include <unordered_set>

using namespace slang;
using namespace slang::driver;
using namespace slang::syntax;
using namespace slang::parsing;

Expand Down
4 changes: 2 additions & 2 deletions crates/bender-slang/cpp/slang_bridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
#include "rust/cxx.h"
#include "slang/diagnostics/DiagnosticEngine.h"
#include "slang/diagnostics/TextDiagnosticClient.h"
#include "slang/driver/Driver.h"
#include "slang/parsing/Preprocessor.h"
#include "slang/syntax/SyntaxTree.h"
#include "slang/text/SourceManager.h"

#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>

struct SlangPrintOpts;
Expand Down
37 changes: 37 additions & 0 deletions crates/bender-slang/tests/basic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use std::path::PathBuf;

fn fixture_path(rel: &str) -> String {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../..")
.join("tests/pickle")
.join(rel)
.canonicalize()
.expect("valid fixture path")
.to_string_lossy()
.into_owned()
}

#[test]
fn parse_valid_file_succeeds() {
let mut session = bender_slang::SlangSession::new();
let files = vec![fixture_path("src/top.sv")];
let includes = vec![fixture_path("include")];
let defines = vec![];
assert!(session.parse_group(&files, &includes, &defines).is_ok());
assert_eq!(session.tree_count(), 1);
}

#[test]
fn parse_invalid_file_returns_parse_error() {
let mut session = bender_slang::SlangSession::new();
let files = vec![fixture_path("src/broken.sv")];
let includes = vec![];
let defines = vec![];
let result = session.parse_group(&files, &includes, &defines);

match result {
Err(bender_slang::SlangError::ParseGroup { .. }) => {}
Err(other) => panic!("expected SlangError::ParseGroup, got {other}"),
Ok(_) => panic!("expected parse to fail"),
}
}
3 changes: 3 additions & 0 deletions tests/cli_regression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,5 +161,8 @@ regression_tests! {
packages: &["packages"],
packages_graph: &["packages", "--graph"],
packages_flat: &["packages", "--flat"],
// Enable once the golden binary is built with `slang` support.
// pickle_basic: &["pickle", "--target", "top"],
// pickle_top_trim: &["pickle", "--target", "top", "--top", "top"],

}
75 changes: 75 additions & 0 deletions tests/pickle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright (c) 2025 ETH Zurich
// Tim Fischer <fischeti@iis.ee.ethz.ch>

#[cfg(feature = "slang")]
mod tests {
use assert_cmd::cargo;

fn run_pickle(args: &[&str]) -> String {
let mut full_args = vec!["-d", "tests/pickle", "pickle"];
full_args.extend(args);

let out = cargo::cargo_bin_cmd!()
.args(&full_args)
.output()
.expect("Failed to execute bender binary");

assert!(
out.status.success(),
"pickle command failed.\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);

String::from_utf8(out.stdout).expect("stdout must be utf-8")
}

#[test]
fn pickle_top_trim_filters_unreachable_modules() {
let full = run_pickle(&["--target", "top"]);
assert!(full.contains("module unused_top;"));
assert!(full.contains("module unused_leaf;"));

let trimmed = run_pickle(&["--target", "top", "--top", "top"]);
assert!(trimmed.contains("module top ("));
assert!(trimmed.contains("module core;"));
assert!(trimmed.contains("module leaf;"));
assert!(!trimmed.contains("module unused_top;"));
assert!(!trimmed.contains("module unused_leaf;"));
}

#[test]
fn pickle_rename_applies_prefix_and_suffix() {
let renamed = run_pickle(&[
"--target", "top", "--top", "top", "--prefix", "p_", "--suffix", "_s",
]);

assert!(renamed.contains("module p_top_s ("));
assert!(renamed.contains("module p_core_s;"));
assert!(renamed.contains("module p_leaf_s;"));
}

#[test]
fn pickle_exclude_rename_keeps_selected_names() {
let renamed = run_pickle(&[
"--target",
"top",
"--top",
"top",
"--prefix",
"p_",
"--suffix",
"_s",
"--exclude-rename",
"top",
"--exclude-rename",
"core",
]);

assert!(renamed.contains("module top ("));
assert!(renamed.contains("module core;"));
assert!(renamed.contains("module p_leaf_s;"));
assert!(!renamed.contains("module p_top_s ("));
assert!(!renamed.contains("module p_core_s;"));
}
}
1 change: 1 addition & 0 deletions tests/pickle/Bender.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
packages: {}
19 changes: 19 additions & 0 deletions tests/pickle/Bender.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package:
name: pickle_repo

sources:
- defines:
ENABLE_LOGGING: 1
files:
- src/common_pkg.sv
- src/bus_intf.sv
- src/leaf.sv
- src/core.sv
- src/unused_leaf.sv
- src/unused_top.sv

- target: top
include_dirs:
- include
files:
- src/top.sv
6 changes: 6 additions & 0 deletions tests/pickle/include/macros.svh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Simple macro to test if includes are resolved correctly
`define LOG(msg) \
$display("[LOG]: %s", msg);

// A constant used in the RTL
localparam int unsigned DataWidth = 32;
2 changes: 2 additions & 0 deletions tests/pickle/src/broken.sv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module broken(;
endmodule
21 changes: 21 additions & 0 deletions tests/pickle/src/bus_intf.sv
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
interface bus_intf #(
parameter int Width = 32
) (
input logic clk
);
logic [Width-1:0] addr;
logic [Width-1:0] data;
logic valid;
logic ready;

modport master (
output addr, data, valid,
input ready
);

modport slave (
input addr, data, valid,
output ready
);

endinterface
13 changes: 13 additions & 0 deletions tests/pickle/src/common_pkg.sv
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package common_pkg;

typedef enum logic [1:0] {
Idle = 2'b00,
Busy = 2'b01,
Error = 2'b11
} state_t;

function automatic logic is_error(state_t s);
return s == Error;
endfunction

endpackage
3 changes: 3 additions & 0 deletions tests/pickle/src/core.sv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module core;
leaf u_leaf();
endmodule
2 changes: 2 additions & 0 deletions tests/pickle/src/leaf.sv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module leaf;
endmodule
Loading