diff --git a/MODULE.bazel b/MODULE.bazel
index 7e81aa5..ce22ce9 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -37,18 +37,10 @@ chisel.toolchain(
)
use_repo(
chisel,
+ "chisel_firtool",
"chisel_maven",
)
bazel_dep(name = "rules_python", version = "1.7.0", dev_dependency = True)
bazel_dep(name = "rules_verilog", version = "1.1.1")
-
-circt_repo = use_repo_rule(
- "//verilog:circt_repo.bzl",
- "circt_repo",
-)
-
-circt_repo(
- name = "circt",
-)
diff --git a/README.md b/README.md
index b49efbb..ed1c098 100644
--- a/README.md
+++ b/README.md
@@ -10,12 +10,16 @@ This repository packages the following helpers as a BCR-friendly module:
- `chisel_binary`
- `chisel_library`
- `chisel_test`
+- `fir_library`
+- `verilog_file`
+- `verilog_directory`
- `verilog_single_file_library`
## Features
-- One extension (`chisel.toolchain`) to fetch Chisel/ScalaTest Maven artifacts.
+- One extension (`chisel.toolchain`) to fetch Chisel/ScalaTest Maven artifacts and a compatible native firtool.
- Ready-to-use macro defaults via `@chisel_maven` aliases (`:chisel`, `:chisel_plugin`, etc.).
+- Automatic firtool version selection from Chisel's own build metadata via `@chisel_firtool`.
- Built-in Verilator runtime wrapper in `chisel_test` for BCR Verilator layout quirks.
- Minimal smoke tests and GitHub CI workflows.
@@ -38,7 +42,7 @@ scala_deps = use_extension("@rules_scala//scala/extensions:deps.bzl", "scala_dep
scala_deps.scala()
scala_deps.scalatest() # only needed if you use chisel_test
-# Chisel dependencies (creates @chisel_maven)
+# Chisel dependencies (creates @chisel_maven and @chisel_firtool)
chisel = use_extension("@rules_chisel//chisel:extensions.bzl", "chisel")
chisel.toolchain(
chisel_version = "7.8.0",
@@ -46,17 +50,17 @@ chisel.toolchain(
firtool_resolver_version = "2.0.1", # choose a known compatible resolver for your Chisel release
lock_file = "//:maven_install.json", # run `touch maven_install.json && REPIN=1 bazel run @chisel_maven//:pin` to generate the lock file
)
-use_repo(chisel, "chisel_maven")
+use_repo(chisel, "chisel_firtool", "chisel_maven")
```
If you are authoring a reusable Bazel module (not an application root) and only need Chisel for that module's own development/tests, use `dev_dependency = True` on `use_extension(...)` so your toolchain tag does not leak to downstream consumers.
-
## Notes
- Scala toolchain setup is **mandatory** in your own `MODULE.bazel`. This is by design: `rules_chisel` leaves Scala version/toolchain control to users.
- `chisel_test` wraps `scala_test` and sets up a Verilator runtime environment. It expects `@verilator//:bin/verilator` and `@verilator//:verilator_includes`. If you don't use `chisel_test`, you can skip the Verilator dependency.
- Please explicitly set `firtool_resolver_version` in `chisel.toolchain(...)`. Use the Chisel Maven POM as the source of truth (for example: [`chisel_2.13-7.8.0.pom`](https://repo1.maven.org/maven2/org/chipsalliance/chisel_2.13/7.8.0/chisel_2.13-7.8.0.pom), see dependency `firtool-resolver_2.13` with `2.0.1`).
+- `@chisel_firtool` reads the configured Chisel artifact's `BuildInfo.firtoolVersion` and asks `firtool-resolver` for that exact native tool. The download happens while Bazel prepares external repositories, not inside each Verilog action.
- To make dependency resolution faster, deterministic and avoid repeated conflict-selection drift/noise across builds, set `lock_file` and pin once: `touch maven_install.json && REPIN=1 bazel run @chisel_maven//:pin`.
- `lock_file` records a resolved result. The first pin still performs dependency resolution; repin when you intentionally change dependency versions.
@@ -66,7 +70,13 @@ You can check `tests` for examples as well.
```starlark
load("@rules_chisel//chisel:defs.bzl", "chisel_binary", "chisel_library", "chisel_test")
-load("@rules_chisel//verilog:defs.bzl", "verilog_single_file_library")
+load(
+ "@rules_chisel//verilog:defs.bzl",
+ "fir_library",
+ "verilog_directory",
+ "verilog_file",
+ "verilog_single_file_library",
+)
chisel_library(
name = "adder_lib",
@@ -86,6 +96,21 @@ chisel_test(
deps = [":adder_lib"],
)
+fir_library(
+ name = "adder_fir",
+ generator = ":emit_adder",
+)
+
+verilog_file(
+ name = "adder.sv",
+ srcs = [":adder_fir"],
+)
+
+verilog_directory(
+ name = "adder_split",
+ srcs = [":adder_fir"],
+)
+
verilog_single_file_library(
name = "merged_sv",
srcs = [
@@ -127,6 +152,7 @@ Local smoke targets:
bazel build //...
bazel test //tests/smoke:verilog_concat_test
bazel test //tests/smoke:simple_adder_test --test_output=errors
+bazel test //tests/smoke:fir_verilog_test --test_output=errors
tests/version_compat/check_chisel_versions.sh
```
diff --git a/chisel/extensions.bzl b/chisel/extensions.bzl
index 786dfcc..ed6ab87 100644
--- a/chisel/extensions.bzl
+++ b/chisel/extensions.bzl
@@ -4,6 +4,9 @@ load("@rules_jvm_external//:defs.bzl", "maven_install")
load("@rules_jvm_external//private/extensions:download_pinned_deps.bzl", "download_pinned_deps")
load("@rules_jvm_external//private/rules:v1_lock_file.bzl", "v1_lock_file")
load("@rules_jvm_external//private/rules:v2_lock_file.bzl", "v2_lock_file")
+load("//verilog:firtool_repo.bzl", "firtool_repo")
+
+_FIRTOOL_REPO_NAME = "chisel_firtool"
_DEFAULT_REPOSITORIES = [
"https://repo1.maven.org/maven2",
@@ -170,6 +173,13 @@ def _chisel_extension_impl(module_ctx):
pin_repo_name = "unpinned_" + internal_repo_name if settings.lock_file else internal_repo_name,
scala_version = settings.scala_version,
)
+ firtool_repo(
+ name = _FIRTOOL_REPO_NAME,
+ chisel_version = settings.chisel_version,
+ firtool_resolver_version = settings.firtool_resolver_version,
+ repositories = settings.repositories,
+ scala_version = settings.scala_version,
+ )
root_is_dev = True
for mod in module_ctx.modules:
@@ -180,8 +190,8 @@ def _chisel_extension_impl(module_ctx):
return module_ctx.extension_metadata(
reproducible = settings.lock_file != None,
- root_module_direct_deps = [] if root_is_dev else [settings.repo_name],
- root_module_direct_dev_deps = [settings.repo_name] if root_is_dev else [],
+ root_module_direct_deps = [] if root_is_dev else [settings.repo_name, _FIRTOOL_REPO_NAME],
+ root_module_direct_dev_deps = [settings.repo_name, _FIRTOOL_REPO_NAME] if root_is_dev else [],
)
toolchain = tag_class(
diff --git a/tests/version_compat/check_chisel_versions.sh b/tests/version_compat/check_chisel_versions.sh
index 5070016..e5500eb 100755
--- a/tests/version_compat/check_chisel_versions.sh
+++ b/tests/version_compat/check_chisel_versions.sh
@@ -83,7 +83,7 @@ chisel.toolchain(
scala_version = "${SCALA_VERSION}",
firtool_resolver_version = "${firtool_version}",
)
-use_repo(chisel, "chisel_maven")
+use_repo(chisel, "chisel_firtool", "chisel_maven")
MODULE
cat > "${ws}/BUILD.bazel" <<'BUILD'
@@ -100,6 +100,13 @@ chisel_binary(
deps = [":compat_lib"],
main_class = "compat.Main",
)
+
+genrule(
+ name = "firtool_version",
+ outs = ["firtool-version.txt"],
+ cmd = "$(location @chisel_firtool//:bin/firtool) --version > $@",
+ tools = ["@chisel_firtool//:bin/firtool"],
+)
BUILD
cat > "${ws}/Compat.scala" <<'SCALA'
@@ -135,7 +142,7 @@ SCALA
--nohome_rc \
--noworkspace_rc \
--output_user_root="${out_root}" \
- build //:compat_lib //:compat_bin \
+ build //:compat_lib //:compat_bin //:firtool_version \
--enable_bzlmod \
--repository_cache="${TMP_ROOT}/repo_cache" \
--disk_cache="${TMP_ROOT}/disk_cache" \
diff --git a/verilog/BUILD.bazel b/verilog/BUILD.bazel
index cdef828..ae78d29 100644
--- a/verilog/BUILD.bazel
+++ b/verilog/BUILD.bazel
@@ -3,4 +3,8 @@ package(
default_visibility = ["//visibility:private"],
)
-exports_files(["defs.bzl"])
+exports_files([
+ "ResolveFirtool.java",
+ "defs.bzl",
+ "firtool_repo.bzl",
+])
diff --git a/verilog/ResolveFirtool.java b/verilog/ResolveFirtool.java
new file mode 100644
index 0000000..c28101f
--- /dev/null
+++ b/verilog/ResolveFirtool.java
@@ -0,0 +1,68 @@
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.InputStream;
+import java.lang.reflect.Method;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+
+public final class ResolveFirtool {
+ private ResolveFirtool() {}
+
+ public static void main(String[] args) throws Exception {
+ if (args.length != 1) {
+ throw new IllegalArgumentException("Usage: ResolveFirtool ");
+ }
+
+ Object buildInfo = Class.forName("chisel3.BuildInfo$").getField("MODULE$").get(null);
+ Object versionOption = buildInfo.getClass().getMethod("firtoolVersion").invoke(buildInfo);
+ Method isDefined = versionOption.getClass().getMethod("isDefined");
+ if (!(Boolean) isDefined.invoke(versionOption)) {
+ throw new IllegalStateException("The configured Chisel artifact does not declare a firtool version");
+ }
+ String version = (String) versionOption.getClass().getMethod("get").invoke(versionOption);
+
+ String javaExecutable = Paths.get(
+ System.getProperty("java.home"),
+ "bin",
+ System.getProperty("os.name").toLowerCase().startsWith("windows") ? "java.exe" : "java")
+ .toString();
+ ProcessBuilder processBuilder = new ProcessBuilder(
+ javaExecutable,
+ "-cp",
+ System.getProperty("java.class.path"),
+ "firtoolresolver.Main",
+ version);
+ processBuilder.environment().remove("CHISEL_FIRTOOL_PATH");
+ processBuilder.redirectError(ProcessBuilder.Redirect.INHERIT);
+
+ Process process = processBuilder.start();
+ String output;
+ try (InputStream input = process.getInputStream();
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream()) {
+ input.transferTo(bytes);
+ output = bytes.toString(StandardCharsets.UTF_8.name()).trim();
+ }
+ int exitCode = process.waitFor();
+ if (exitCode != 0) {
+ throw new IllegalStateException("firtool-resolver failed with exit code " + exitCode);
+ }
+ if (output.isEmpty()) {
+ throw new IllegalStateException("firtool-resolver did not return a firtool path");
+ }
+
+ String[] outputLines = output.split("\\R");
+ Path source = Paths.get(outputLines[outputLines.length - 1]);
+ Path destination = Paths.get(args[0]);
+ Files.createDirectories(destination.getParent());
+ Files.copy(source, destination, StandardCopyOption.REPLACE_EXISTING);
+ File destinationFile = destination.toFile();
+ if (!destinationFile.setExecutable(true, false) && !destinationFile.canExecute()) {
+ throw new IllegalStateException("Unable to make firtool executable: " + destination);
+ }
+
+ System.out.println("Resolved firtool " + version + " for the configured Chisel artifact");
+ }
+}
diff --git a/verilog/circt_repo.bzl b/verilog/circt_repo.bzl
deleted file mode 100644
index a53f571..0000000
--- a/verilog/circt_repo.bzl
+++ /dev/null
@@ -1,32 +0,0 @@
-"""Repository rule to fetch OS-specific circt binaries."""
-
-def _circt_repo_impl(ctx):
- os_name = ctx.os.name.lower()
- if os_name.startswith("mac"):
- url = "https://github.com/llvm/circt/releases/download/firtool-1.137.0/circt-full-static-macos-x64.tar.gz"
- sha256 = "225578a949893835b660939947d9f78db404ba61e17148601f7ee6fdf5f887dc"
- elif "windows" in os_name:
- url = "https://github.com/llvm/circt/releases/download/firtool-1.137.0/circt-full-static-windows-x64.zip"
- sha256 = "" # Omit sha256 for Windows for now
- else:
- url = "https://github.com/llvm/circt/releases/download/firtool-1.137.0/circt-full-static-linux-x64.tar.gz"
- sha256 = "c00d58b93c9d7ad13f0e95e78cef180a5dfb9a416cd11a2dd7814fdbe4132439"
-
- kwargs = {
- "stripPrefix": "firtool-1.137.0",
- "url": url,
- }
- if sha256:
- kwargs["sha256"] = sha256
-
- ctx.download_and_extract(**kwargs)
-
- ctx.file("BUILD.bazel", """
-package(default_visibility = ["//visibility:public"])
-
-exports_files(["bin/firtool", "bin/firtool.exe"])
-""")
-
-circt_repo = repository_rule(
- implementation = _circt_repo_impl,
-)
diff --git a/verilog/defs.bzl b/verilog/defs.bzl
index 8147f12..d5d845f 100644
--- a/verilog/defs.bzl
+++ b/verilog/defs.bzl
@@ -47,7 +47,7 @@ fir_library = rule(
executable = True,
allow_files = True,
cfg = "exec",
- default = Label("@circt//:bin/firtool"),
+ default = Label("@chisel_firtool//:bin/firtool"),
),
},
)
@@ -112,7 +112,7 @@ def verilog_attrs():
executable = True,
allow_files = True,
cfg = "exec",
- default = Label("@circt//:bin/firtool"),
+ default = Label("@chisel_firtool//:bin/firtool"),
),
}
diff --git a/verilog/firtool_repo.bzl b/verilog/firtool_repo.bzl
new file mode 100644
index 0000000..eb16e6e
--- /dev/null
+++ b/verilog/firtool_repo.bzl
@@ -0,0 +1,117 @@
+"""Prepares a native firtool using Chisel's firtool-resolver."""
+
+def _maven_urls(repositories, group, artifact, version):
+ relative_path = "{group_path}/{artifact}/{version}/{artifact}-{version}.jar".format(
+ group_path = group.replace(".", "/"),
+ artifact = artifact,
+ version = version,
+ )
+ return [repository.rstrip("/") + "/" + relative_path for repository in repositories]
+
+def _download_jar(ctx, group, artifact, version, output):
+ ctx.download(
+ canonical_id = "{}:{}:{}".format(group, artifact, version),
+ output = output,
+ url = _maven_urls(ctx.attr.repositories, group, artifact, version),
+ )
+
+def _firtool_repo_impl(ctx):
+ scala_short = ".".join(ctx.attr.scala_version.split(".")[:2])
+ bootstrap_dir = "_resolver_bootstrap"
+ chisel_jar = bootstrap_dir + "/chisel.jar"
+ collection_compat_jar = bootstrap_dir + "/scala-collection-compat.jar"
+ resolver_jar = bootstrap_dir + "/firtool-resolver.jar"
+ scala_jar = bootstrap_dir + "/scala-library.jar"
+ scala_xml_jar = bootstrap_dir + "/scala-xml.jar"
+
+ _download_jar(
+ ctx,
+ "org.chipsalliance",
+ "chisel_{}".format(scala_short),
+ ctx.attr.chisel_version,
+ chisel_jar,
+ )
+ _download_jar(
+ ctx,
+ "org.chipsalliance",
+ "firtool-resolver_{}".format(scala_short),
+ ctx.attr.firtool_resolver_version,
+ resolver_jar,
+ )
+ _download_jar(
+ ctx,
+ "org.scala-lang",
+ "scala-library",
+ ctx.attr.scala_version,
+ scala_jar,
+ )
+ _download_jar(
+ ctx,
+ "org.scala-lang.modules",
+ "scala-collection-compat_{}".format(scala_short),
+ "2.11.0",
+ collection_compat_jar,
+ )
+ _download_jar(
+ ctx,
+ "org.scala-lang.modules",
+ "scala-xml_{}".format(scala_short),
+ "2.2.0",
+ scala_xml_jar,
+ )
+
+ java = ctx.which("java")
+ if java == None:
+ fail("Preparing firtool requires Java 11 or newer on PATH")
+
+ path_separator = ";" if "windows" in ctx.os.name.lower() else ":"
+ classpath = path_separator.join([
+ str(ctx.path(chisel_jar)),
+ str(ctx.path(collection_compat_jar)),
+ str(ctx.path(resolver_jar)),
+ str(ctx.path(scala_jar)),
+ str(ctx.path(scala_xml_jar)),
+ ])
+ cache_dir = ctx.path(bootstrap_dir + "/cache")
+ output = ctx.path("bin/firtool")
+ result = ctx.execute(
+ [
+ java,
+ "--class-path",
+ classpath,
+ ctx.path(ctx.attr._resolver_source),
+ output,
+ ],
+ environment = {
+ "CHISEL_FIRTOOL_CACHE": str(cache_dir.get_child("firtool")),
+ "COURSIER_CACHE": str(cache_dir.get_child("coursier")),
+ },
+ quiet = False,
+ timeout = 600,
+ )
+ if result.return_code != 0:
+ fail("firtool-resolver failed:\nstdout:\n{}\nstderr:\n{}".format(
+ result.stdout,
+ result.stderr,
+ ))
+
+ # Bootstrap dependencies are not part of the generated repository.
+ ctx.delete(bootstrap_dir)
+ ctx.file("BUILD.bazel", """package(default_visibility = ["//visibility:public"])
+
+exports_files(["bin/firtool"])
+""")
+
+firtool_repo = repository_rule(
+ implementation = _firtool_repo_impl,
+ attrs = {
+ "chisel_version": attr.string(mandatory = True),
+ "firtool_resolver_version": attr.string(mandatory = True),
+ "repositories": attr.string_list(mandatory = True),
+ "scala_version": attr.string(mandatory = True),
+ "_resolver_source": attr.label(
+ allow_single_file = [".java"],
+ default = Label("//verilog:ResolveFirtool.java"),
+ ),
+ },
+)