From 003c73f20b4a9e8ab706cd87b0f830ab32c3f857 Mon Sep 17 00:00:00 2001 From: Qijia Yang <2421653893@qq.com> Date: Thu, 20 Aug 2026 11:10:08 +0800 Subject: [PATCH] fix: resolve firtool from Chisel metadata --- MODULE.bazel | 10 +- README.md | 36 +++++- chisel/extensions.bzl | 14 ++- tests/version_compat/check_chisel_versions.sh | 11 +- verilog/BUILD.bazel | 6 +- verilog/ResolveFirtool.java | 68 ++++++++++ verilog/circt_repo.bzl | 32 ----- verilog/defs.bzl | 4 +- verilog/firtool_repo.bzl | 117 ++++++++++++++++++ 9 files changed, 245 insertions(+), 53 deletions(-) create mode 100644 verilog/ResolveFirtool.java delete mode 100644 verilog/circt_repo.bzl create mode 100644 verilog/firtool_repo.bzl 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"), + ), + }, +)