From 613dec9303dc923b7ce56ffeaf379e8b2bf6f1ff Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Fri, 10 Jul 2026 21:52:35 +0200 Subject: [PATCH 1/3] Add busperf integration Signed-off-by: Robert Winkler --- MODULE.bazel | 8 + MODULE.bazel.lock | 51 ++++ dependency_support/busperf/BUILD.bazel | 13 + .../busperf/bundled.BUILD.bazel | 36 +++ dependency_support/busperf/extension.bzl | 83 ++++++ .../com_google_absl/BUILD.bazel | 1 + xls/experimental/busperf/BUILD | 74 +++++ xls/experimental/busperf/README.md | 36 +++ xls/experimental/busperf/build_rules/BUILD | 53 ++++ .../busperf/build_rules/build_defs.bzl | 26 ++ .../busperf/build_rules/busperf_macros.bzl | 187 +++++++++++++ .../build_rules/busperf_report_rules.bzl | 67 +++++ .../build_rules/busperf_yaml_rules.bzl | 68 +++++ .../busperf/busperf_yaml_generator.cc | 202 ++++++++++++++ .../busperf/busperf_yaml_generator.h | 49 ++++ .../busperf/busperf_yaml_generator_test.cc | 264 ++++++++++++++++++ xls/experimental/busperf/examples/BUILD | 79 ++++++ xls/experimental/busperf/examples/README.md | 42 +++ .../busperf/examples/bottleneck.x | 153 ++++++++++ .../busperf/examples/tb_bottleneck.v | 56 ++++ xls/experimental/busperf/tests/BUILD | 56 ++++ xls/experimental/busperf/tests/hier.x | 46 +++ .../busperf/tests/testdata/hier.busperf.yaml | 72 +++++ .../busperf/tests/testdata/tb_hier.v | 64 +++++ .../busperf/xls_sig_to_busperf_main.cc | 108 +++++++ 25 files changed, 1894 insertions(+) create mode 100644 dependency_support/busperf/BUILD.bazel create mode 100644 dependency_support/busperf/bundled.BUILD.bazel create mode 100644 dependency_support/busperf/extension.bzl create mode 100644 dependency_support/com_google_absl/BUILD.bazel create mode 100644 xls/experimental/busperf/BUILD create mode 100644 xls/experimental/busperf/README.md create mode 100644 xls/experimental/busperf/build_rules/BUILD create mode 100644 xls/experimental/busperf/build_rules/build_defs.bzl create mode 100644 xls/experimental/busperf/build_rules/busperf_macros.bzl create mode 100644 xls/experimental/busperf/build_rules/busperf_report_rules.bzl create mode 100644 xls/experimental/busperf/build_rules/busperf_yaml_rules.bzl create mode 100644 xls/experimental/busperf/busperf_yaml_generator.cc create mode 100644 xls/experimental/busperf/busperf_yaml_generator.h create mode 100644 xls/experimental/busperf/busperf_yaml_generator_test.cc create mode 100644 xls/experimental/busperf/examples/BUILD create mode 100644 xls/experimental/busperf/examples/README.md create mode 100644 xls/experimental/busperf/examples/bottleneck.x create mode 100644 xls/experimental/busperf/examples/tb_bottleneck.v create mode 100644 xls/experimental/busperf/tests/BUILD create mode 100644 xls/experimental/busperf/tests/hier.x create mode 100644 xls/experimental/busperf/tests/testdata/hier.busperf.yaml create mode 100644 xls/experimental/busperf/tests/testdata/tb_hier.v create mode 100644 xls/experimental/busperf/xls_sig_to_busperf_main.cc diff --git a/MODULE.bazel b/MODULE.bazel index a4a3239ba1..80a461da75 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -120,6 +120,14 @@ use_repo(ac_types_ext, "ac_datatypes") z3_ext = use_extension("//dependency_support/z3:extension.bzl", "z3_extension") use_repo(z3_ext, "z3") +# busperf analyzer (used in //xls/experimental/busperf) +busperf_ext = use_extension("//dependency_support/busperf:extension.bzl", "busperf_extension") +use_repo(busperf_ext, "busperf") + +# Hermetic Rust toolchain for building busperf (needs wasm32-unknown-unknown) +rust_toolchain_ext = use_extension("//dependency_support/busperf:extension.bzl", "rust_toolchain_extension") +use_repo(rust_toolchain_ext, "busperf_rust_toolchain") + bitwuzla_ext = use_extension("//dependency_support/bitwuzla:extension.bzl", "bitwuzla_extension") use_repo(bitwuzla_ext, "bitwuzla", "cadical", "symfpu") diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index a3af2aedce..ecebb370a1 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -909,6 +909,57 @@ ] } }, + "//dependency_support/busperf:extension.bzl%busperf_extension": { + "general": { + "bzlTransitiveDigest": "ETaDDFUNukPRx+Z772WyeeUNeznqKKDMoXb6avxDZQ8=", + "usagesDigest": "zCJGJrEo/JIwOCu9Dr4AVlrhVlx8AbgJiRL7F4ipGHY=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "busperf": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "urls": [ + "https://github.com/antmicro/busperf/archive/a62c4c7c6cb3edb090a6b17f0c6edf18fcb4ebb1.tar.gz" + ], + "integrity": "sha256-tfu+WqRT05t2SsMy6DjY+142ZlkqbzMc9fWIfzzpsI4=", + "strip_prefix": "busperf-a62c4c7c6cb3edb090a6b17f0c6edf18fcb4ebb1", + "build_file": "@@//dependency_support/busperf:bundled.BUILD.bazel" + } + } + }, + "recordedRepoMappingEntries": [ + [ + "", + "bazel_tools", + "bazel_tools" + ] + ] + } + }, + "//dependency_support/busperf:extension.bzl%rust_toolchain_extension": { + "general": { + "bzlTransitiveDigest": "ETaDDFUNukPRx+Z772WyeeUNeznqKKDMoXb6avxDZQ8=", + "usagesDigest": "okUoFneT3yE6UylqiQgKGMyRmUGt4a2ai7a01619RSE=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "busperf_rust_toolchain": { + "repoRuleId": "@@//dependency_support/busperf:extension.bzl%rust_toolchain_repository", + "attributes": {} + } + }, + "recordedRepoMappingEntries": [ + [ + "", + "bazel_tools", + "bazel_tools" + ] + ] + } + }, "//dependency_support/com_github_facebook_zstd:extension.bzl%zstd_extension": { "general": { "bzlTransitiveDigest": "TohWVIeCi0KuQV5K8LoUBVawKWoB/T+8MKhXwG7OAHw=", diff --git a/dependency_support/busperf/BUILD.bazel b/dependency_support/busperf/BUILD.bazel new file mode 100644 index 0000000000..5d65e57ff8 --- /dev/null +++ b/dependency_support/busperf/BUILD.bazel @@ -0,0 +1,13 @@ +# Copyright 2026 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/dependency_support/busperf/bundled.BUILD.bazel b/dependency_support/busperf/bundled.BUILD.bazel new file mode 100644 index 0000000000..17a7606300 --- /dev/null +++ b/dependency_support/busperf/bundled.BUILD.bazel @@ -0,0 +1,36 @@ +# Copyright 2026 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +package(default_visibility = ["//visibility:public"]) + +genrule( + name = "busperf_bin", + srcs = glob(["**"]), + outs = ["busperf"], + cmd = """ + cargo="$$PWD/$(location @busperf_rust_toolchain//:cargo/bin/cargo)" + toolchain_root="$$(dirname $$(dirname $$(dirname "$$cargo")))" + export CARGO_HOME="$$toolchain_root/cargo" + export RUSTUP_HOME="$$toolchain_root/rustup" + manifest="$(location Cargo.toml)" + "$$cargo" build --release --manifest-path="$$manifest" \ + --target-dir="$$PWD/target" --bin busperf + cp "$$PWD/target/release/busperf" $@ + chmod +x $@ + """, + executable = True, + local = True, + tags = ["manual", "requires-network"], + tools = ["@busperf_rust_toolchain//:cargo/bin/cargo"], +) diff --git a/dependency_support/busperf/extension.bzl b/dependency_support/busperf/extension.bzl new file mode 100644 index 0000000000..020c7fc856 --- /dev/null +++ b/dependency_support/busperf/extension.bzl @@ -0,0 +1,83 @@ +# Copyright 2026 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Module extension for busperf.""" + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +def _busperf_extension_impl( + module_ctx): # @unused + http_archive( + name = "busperf", + urls = ["https://github.com/antmicro/busperf/archive/a62c4c7c6cb3edb090a6b17f0c6edf18fcb4ebb1.tar.gz"], + integrity = "sha256-tfu+WqRT05t2SsMy6DjY+142ZlkqbzMc9fWIfzzpsI4=", + strip_prefix = "busperf-a62c4c7c6cb3edb090a6b17f0c6edf18fcb4ebb1", + build_file = Label("//dependency_support/busperf:bundled.BUILD.bazel"), + ) + +busperf_extension = module_extension( + implementation = _busperf_extension_impl, +) + +def _rust_toolchain_repository_impl(repository_ctx): + # Self-contained rustup toolchain, with wasm32-unknown-unknown for + # busperf's HTML report build. + # + # TODO: temporary workaround, easiest way to get the wasm build working. + # Should be replaced with a proper rules_rust build. + rustup_init = repository_ctx.path("rustup-init") + repository_ctx.download( + url = "https://static.rust-lang.org/rustup/dist/x86_64-unknown-linux-gnu/rustup-init", + output = rustup_init, + executable = True, + ) + + rustup_home = repository_ctx.path("rustup") + cargo_home = repository_ctx.path("cargo") + result = repository_ctx.execute( + [ + str(rustup_init), + "-y", + "--no-modify-path", + "--profile", + "minimal", + "--default-toolchain", + "stable", + "--target", + "wasm32-unknown-unknown", + ], + environment = { + "RUSTUP_HOME": str(rustup_home), + "CARGO_HOME": str(cargo_home), + }, + timeout = 1800, + ) + if result.return_code != 0: + fail("rustup-init failed:\n" + result.stdout + result.stderr) + + repository_ctx.file("BUILD.bazel", """\ +exports_files(["cargo/bin/cargo"]) +""") + +rust_toolchain_repository = repository_rule( + implementation = _rust_toolchain_repository_impl, +) + +def _rust_toolchain_extension_impl( + module_ctx): # @unused + rust_toolchain_repository(name = "busperf_rust_toolchain") + +rust_toolchain_extension = module_extension( + implementation = _rust_toolchain_extension_impl, +) diff --git a/dependency_support/com_google_absl/BUILD.bazel b/dependency_support/com_google_absl/BUILD.bazel new file mode 100644 index 0000000000..ffd0fb0cdc --- /dev/null +++ b/dependency_support/com_google_absl/BUILD.bazel @@ -0,0 +1 @@ +package(default_visibility = ["//visibility:public"]) diff --git a/xls/experimental/busperf/BUILD b/xls/experimental/busperf/BUILD new file mode 100644 index 0000000000..fcf78572a8 --- /dev/null +++ b/xls/experimental/busperf/BUILD @@ -0,0 +1,74 @@ +# Copyright 2026 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +package( + default_applicable_licenses = ["//:license"], + features = [ + "layering_check", + "parse_headers", + ], + licenses = ["notice"], # Apache 2.0 +) + +cc_library( + name = "busperf_yaml_generator", + srcs = ["busperf_yaml_generator.cc"], + hdrs = ["busperf_yaml_generator.h"], + deps = [ + "//xls/codegen:module_signature_cc_proto", + "@abseil-cpp//absl/container:flat_hash_map", + "@abseil-cpp//absl/log", + "@abseil-cpp//absl/status", + "@abseil-cpp//absl/status:statusor", + "@abseil-cpp//absl/strings", + "@abseil-cpp//absl/strings:str_format", + "@abseil-cpp//absl/types:span", + ], +) + +cc_test( + name = "busperf_yaml_generator_test", + srcs = ["busperf_yaml_generator_test.cc"], + deps = [ + ":busperf_yaml_generator", + "//xls/codegen:module_signature_cc_proto", + "//xls/common:xls_gunit_main", + "//xls/common/status:matchers", + "@abseil-cpp//absl/container:flat_hash_map", + "@abseil-cpp//absl/log:check", + "@abseil-cpp//absl/status", + "@googletest//:gtest", + "@protobuf", + ], +) + +cc_binary( + name = "xls_sig_to_busperf", + srcs = ["xls_sig_to_busperf_main.cc"], + visibility = ["//xls/experimental/busperf:__subpackages__"], + deps = [ + ":busperf_yaml_generator", + "//xls/codegen:module_signature_cc_proto", + "//xls/common:exit_status", + "//xls/common:init_xls", + "//xls/common/file:filesystem", + "//xls/common/status:status_macros", + "@abseil-cpp//absl/container:flat_hash_map", + "@abseil-cpp//absl/flags:flag", + "@abseil-cpp//absl/log", + "@abseil-cpp//absl/log:check", + "@abseil-cpp//absl/status", + "@abseil-cpp//absl/strings", + ], +) diff --git a/xls/experimental/busperf/README.md b/xls/experimental/busperf/README.md new file mode 100644 index 0000000000..365ad92d4c --- /dev/null +++ b/xls/experimental/busperf/README.md @@ -0,0 +1,36 @@ +# XLS - busperf integration + +This directory lets you analyze bottlenecks in an XLS proc design's +processing pipeline using [busperf](https://github.com/antmicro/busperf). +A bottleneck is a stage that cannot keep up with the stages feeding it, +causing the ready/valid handshake upstream to stall. busperf makes this +visible as measurable backpressure, once it has a bus description telling +it which signals in your design form each ready/valid channel. + +## Layout + +- `build_rules/` contains the Bazel rules and macros. +- `busperf_yaml_generator.{h,cc}` and `xls_sig_to_busperf_main.cc` implement + the tool that turns an XLS codegen signature into a busperf YAML bus + description. +- `examples/` contains example designs demonstrating the full pipeline, from + a DSLX design to a busperf report. +- `tests/` contains tests for the rules and the generator. + +## Bazel rules + +- `xls_busperf_yaml` generates busperf's YAML bus description for a DSLX + proc. +- `busperf_analyze` runs busperf itself against a VCD and a busperf YAML + bus description to produce a text or HTML report. + +## How to use it + +Call `xls_busperf_yaml` in a `BUILD` file, supplying the DSLX proc you want +to analyze, to generate its busperf YAML bus description as a build +artifact. From there, simulate your design to produce a VCD however you +normally would, and call `busperf_analyze` on that VCD and the generated +YAML to get a text or HTML report. See `examples/README.md` for a +complete, working reference of this pipeline end to end, including the +exact commands to build a report and the numbers to look for that confirm +busperf has actually caught a bottleneck. diff --git a/xls/experimental/busperf/build_rules/BUILD b/xls/experimental/busperf/build_rules/BUILD new file mode 100644 index 0000000000..e518462bdb --- /dev/null +++ b/xls/experimental/busperf/build_rules/BUILD @@ -0,0 +1,53 @@ +# Copyright 2026 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") + +package( + default_applicable_licenses = ["//:license"], + default_visibility = ["//xls/experimental/busperf:__subpackages__"], + licenses = ["notice"], # Apache 2.0 +) + +bzl_library( + name = "busperf_yaml_rules_bzl", + srcs = ["busperf_yaml_rules.bzl"], + visibility = ["//visibility:private"], +) + +bzl_library( + name = "busperf_report_rules_bzl", + srcs = ["busperf_report_rules.bzl"], + visibility = ["//visibility:private"], +) + +bzl_library( + name = "busperf_macros_bzl", + srcs = ["busperf_macros.bzl"], + visibility = ["//visibility:private"], + deps = [ + ":busperf_report_rules_bzl", + ":busperf_yaml_rules_bzl", + "//xls/build_rules:xls_build_defs_bzl", + ], +) + +bzl_library( + name = "build_defs_bzl", + srcs = ["build_defs.bzl"], + deps = [ + ":busperf_macros_bzl", + ":busperf_report_rules_bzl", + ], +) diff --git a/xls/experimental/busperf/build_rules/build_defs.bzl b/xls/experimental/busperf/build_rules/build_defs.bzl new file mode 100644 index 0000000000..2b6a012e2b --- /dev/null +++ b/xls/experimental/busperf/build_rules/build_defs.bzl @@ -0,0 +1,26 @@ +# Copyright 2026 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Public API for wiring up an automatic busperf ready/valid setup.""" + +load( + ":busperf_macros.bzl", + _xls_busperf_setup = "xls_busperf_setup", + _xls_busperf_yaml = "xls_busperf_yaml", +) +load(":busperf_report_rules.bzl", _busperf_analyze = "busperf_analyze") + +xls_busperf_yaml = _xls_busperf_yaml +xls_busperf_setup = _xls_busperf_setup +busperf_analyze = _busperf_analyze diff --git a/xls/experimental/busperf/build_rules/busperf_macros.bzl b/xls/experimental/busperf/build_rules/busperf_macros.bzl new file mode 100644 index 0000000000..4a70cb5bad --- /dev/null +++ b/xls/experimental/busperf/build_rules/busperf_macros.bzl @@ -0,0 +1,187 @@ +# Copyright 2026 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Macros wiring up a busperf ready/valid setup for an XLS proc. Private - load from build_defs.bzl.""" + +load("//xls/build_rules:xls_build_defs.bzl", "xls_dslx_verilog") +load(":busperf_report_rules.bzl", "busperf_analyze") +load(":busperf_yaml_rules.bzl", "busperf_yaml") + +visibility(["//xls/experimental/busperf/build_rules/..."]) + +def xls_busperf_yaml( + name, + dslx_top, + library, + reset, + scope, + reset_active_low = False, + children = [], + codegen_args = {}): + """Generates a busperf YAML bus description for one XLS proc design. + + Args: + name: Base name for the generated targets and Verilog module. + dslx_top: DSLX proc name to codegen as the top of the design. + library: xls_dslx_library target containing dslx_top and `children`. + reset: Reset signal name. + scope: Dot-separated VCD scope path to the DUT, e.g. "tb_foo.dut". + reset_active_low: Reset polarity. Defaults to False. + children: DSLX proc names spawned by dslx_top whose internal + ready/valid channels should also get busperf interfaces. + codegen_args: Extra/override codegen_main args. + """ + final_codegen_args = dict(codegen_args) + final_codegen_args["reset"] = reset + final_codegen_args["reset_active_low"] = "true" if reset_active_low else "false" + + xls_dslx_verilog( + name = name + "_verilog", + dslx_top = dslx_top, + library = library, + codegen_args = final_codegen_args, + verilog_file = name + ".v", + tags = ["manual"], + ) + + child_sig_labels = [] + for child in children: + child_codegen_args = dict(final_codegen_args) + child_codegen_args.pop("module_name", None) + child_target = name + "_child_" + child + xls_dslx_verilog( + name = child_target + "_verilog", + dslx_top = child, + library = library, + codegen_args = child_codegen_args, + verilog_file = child_target + ".v", + tags = ["manual"], + ) + child_sig_labels.append(":" + child_target + ".sig.textproto") + + busperf_yaml( + name = name + "_bus_yaml", + signature = ":" + name + ".sig.textproto", + child_signatures = child_sig_labels, + scope = scope, + xls_sig_to_busperf = "//xls/experimental/busperf:xls_sig_to_busperf", + tags = ["manual"], + ) + +def xls_busperf_setup( + name, + dslx_top, + library, + testbench, + reset, + scope, + vcd_filename, + reset_active_low = False, + testbench_defines = {}, + children = [], + codegen_args = {}): + """Generates a full busperf channel analysis setup for one XLS proc design. + + Args: + name: Base name for the generated targets and Verilog module. + dslx_top: DSLX proc name to codegen as the top of the design. + library: xls_dslx_library target containing dslx_top and `children`. + testbench: Checked-in Verilog testbench. + reset: Reset signal name. + scope: Dot-separated VCD scope path to the DUT, e.g. "tb_foo.dut". + vcd_filename: VCD file name + reset_active_low: Reset polarity. Defaults to False. + testbench_defines: defines to pass to iverilog simulator + children: DSLX proc names spawned by dslx_top whose internal + ready/valid channels should also get busperf interfaces. + codegen_args: Extra/override codegen_main args. + """ + # `.v` is hardcoded throughout this macro (the `_vcd` genrule). + setup_codegen_args = dict(codegen_args) + setup_codegen_args["use_system_verilog"] = "false" + + xls_busperf_yaml( + name = name, + dslx_top = dslx_top, + library = library, + reset = reset, + scope = scope, + reset_active_low = reset_active_low, + children = children, + codegen_args = setup_codegen_args, + ) + + testbench_define_flags = " ".join([ + "-D'{}={}'".format(k, v) + for k, v in testbench_defines.items() + ]) + + native.genrule( + name = name + "_vcd", + srcs = [":" + name + ".v", testbench], + outs = [name + ".vcd"], + cmd = ( + "$(location @com_icarus_iverilog//:iverilog) -g2012 {defines} " + + "-o sim.vvp $(location :{design_v}) $(location {tb_v}) && " + + "$(location @com_icarus_iverilog//:vvp) sim.vvp && " + + "mv {vcd_filename} $@" + ).format( + defines = testbench_define_flags, + design_v = name + ".v", + tb_v = testbench, + vcd_filename = vcd_filename, + ), + tools = [ + "@com_icarus_iverilog//:iverilog", + "@com_icarus_iverilog//:vvp", + ], + tags = ["manual"], + ) + + for suffix, format in [("_stats", "text"), ("_report", "html")]: + busperf_analyze( + name = name + suffix, + vcd = ":" + name + "_vcd", + bus_yaml = ":" + name + "_bus_yaml", + format = format, + busperf_bin = "@busperf//:busperf_bin", + tags = ["manual"], + ) + + native.genrule( + name = name + "_open_report", + srcs = [":" + name + "_report"], + outs = [name + "_open_report.sh"], + cmd = (""" +cat > $@ <<'EOF' +#!/usr/bin/env bash +set -euo pipefail +report="bazel-bin/{package}/{report_file}" +if command -v xdg-open >/dev/null 2>&1; then + xdg-open "$$report" +elif command -v open >/dev/null 2>&1; then + open "$$report" +else + echo "No browser opener found; report is at: $$report" >&2 + exit 1 +fi +EOF +chmod +x $@ +""").format( + package = native.package_name(), + report_file = name + "_report.report.html", + ), + executable = True, + tags = ["manual"], + ) diff --git a/xls/experimental/busperf/build_rules/busperf_report_rules.bzl b/xls/experimental/busperf/build_rules/busperf_report_rules.bzl new file mode 100644 index 0000000000..914d550873 --- /dev/null +++ b/xls/experimental/busperf/build_rules/busperf_report_rules.bzl @@ -0,0 +1,67 @@ +# Copyright 2026 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Rule that runs `busperf analyze` to produce a text or HTML report.""" + +visibility(["//xls/experimental/busperf/build_rules/..."]) + +_FORMAT_TO_FLAG_AND_EXT = { + "text": ("--text", ".stats.txt"), + "html": ("--html", ".report.html"), +} + +def _busperf_analyze_impl(ctx): + flag, ext = _FORMAT_TO_FLAG_AND_EXT[ctx.attr.format] + out = ctx.actions.declare_file(ctx.label.name + ext) + + args = ctx.actions.args() + args.add("analyze") + args.add(ctx.file.vcd) + args.add(ctx.file.bus_yaml) + args.add(flag) + args.add("-o", out) + + ctx.actions.run( + executable = ctx.executable.busperf_bin, + arguments = [args], + inputs = [ctx.file.vcd, ctx.file.bus_yaml], + outputs = [out], + mnemonic = "BusperfAnalyze", + progress_message = "Generating busperf %s report for %%{label}" % ctx.attr.format, + ) + return [DefaultInfo(files = depset([out]), runfiles = ctx.runfiles([out]))] + +busperf_analyze = rule( + doc = "Runs `busperf analyze` to produce a text or HTML report.", + implementation = _busperf_analyze_impl, + attrs = { + "vcd": attr.label( + doc = "Simulation VCD.", + allow_single_file = True, + mandatory = True, + ), + "bus_yaml": attr.label( + doc = "busperf YAML bus description.", + allow_single_file = True, + mandatory = True, + ), + "format": attr.string(doc = "Report format.", values = ["text", "html"], mandatory = True), + "busperf_bin": attr.label( + doc = "busperf binary.", + executable = True, + cfg = "exec", + mandatory = True, + ), + }, +) diff --git a/xls/experimental/busperf/build_rules/busperf_yaml_rules.bzl b/xls/experimental/busperf/build_rules/busperf_yaml_rules.bzl new file mode 100644 index 0000000000..15e4edb16f --- /dev/null +++ b/xls/experimental/busperf/build_rules/busperf_yaml_rules.bzl @@ -0,0 +1,68 @@ +# Copyright 2026 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Rule that runs xls_sig_to_busperf to produce a busperf YAML bus description.""" + +visibility(["//xls/experimental/busperf/build_rules/..."]) + +def _busperf_yaml_impl(ctx): + out = ctx.actions.declare_file(ctx.label.name + ".busperf.yaml") + + args = ctx.actions.args() + args.add(ctx.file.signature) + args.add("--scope", ctx.attr.scope) + if ctx.files.child_signatures: + args.add_joined( + "--child_signature", + ctx.files.child_signatures, + join_with = ",", + ) + args.add("--output", out) + + ctx.actions.run( + executable = ctx.executable.xls_sig_to_busperf, + arguments = [args], + inputs = [ctx.file.signature] + ctx.files.child_signatures, + outputs = [out], + mnemonic = "BusperfYaml", + progress_message = "Generating busperf YAML for %{label}", + ) + return [DefaultInfo(files = depset([out]), runfiles = ctx.runfiles([out]))] + +busperf_yaml = rule( + doc = "Runs xls_sig_to_busperf to produce a busperf YAML bus description.", + implementation = _busperf_yaml_impl, + attrs = { + "signature": attr.label( + doc = "Top block's ModuleSignatureProto.", + allow_single_file = True, + mandatory = True, + ), + "child_signatures": attr.label_list( + doc = "Standalone signatures for spawned child procs.", + allow_files = True, + default = [], + ), + "scope": attr.string( + doc = "Dot-separated VCD scope path to the DUT, e.g. \"tb_foo.dut\".", + mandatory = True, + ), + "xls_sig_to_busperf": attr.label( + doc = "xls_sig_to_busperf binary.", + executable = True, + cfg = "exec", + mandatory = True, + ), + }, +) diff --git a/xls/experimental/busperf/busperf_yaml_generator.cc b/xls/experimental/busperf/busperf_yaml_generator.cc new file mode 100644 index 0000000000..190ff69bcf --- /dev/null +++ b/xls/experimental/busperf/busperf_yaml_generator.cc @@ -0,0 +1,202 @@ +// Copyright 2026 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/experimental/busperf/busperf_yaml_generator.h" + +#include +#include +#include + +#include "absl/container/flat_hash_map.h" +#include "absl/log/log.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "absl/strings/str_join.h" +#include "absl/types/span.h" +#include "xls/codegen/module_signature.pb.h" + +namespace xls::busperf { +namespace { + +constexpr std::string_view kDefaultClockName = "clk"; +constexpr std::string_view kDefaultResetName = "rst"; +constexpr std::string_view kClockAlias = "main_clk"; + +// One resolved busperf `interfaces:` entry. +struct BusInterface { + std::string name; + std::vector scope; + std::string ready_port; + std::string valid_port; +}; + +// Gets signature's own ready-valid channels; does not descend into +// instantiations. +std::vector ReadyValidChannelsOf( + const verilog::ModuleSignatureProto& signature) { + std::vector result; + for (const verilog::ChannelInterfaceProto& channel_interface : + signature.channel_interfaces()) { + if (!channel_interface.has_streaming()) { + continue; // single-value: no handshake. + } + if (channel_interface.streaming().flow_control() != + verilog::CHANNEL_FLOW_CONTROL_READY_VALID) { + continue; + } + result.push_back(&channel_interface); + } + return result; +} + +// Quotes and escapes `s` for use as a YAML scalar. +std::string YamlString(std::string_view s) { + std::string result; + result.reserve(s.size() + 2); + result.push_back('"'); + for (char c : s) { + if (c == '\\' || c == '"') { + result.push_back('\\'); + } + result.push_back(c); + } + result.push_back('"'); + return result; +} + +// Recursively collects ready-valid channels from `signature` and any +// instantiated child found in `child_signatures` (keyed by module_name) +// into `interfaces`. +void CollectInterfaces( + const verilog::ModuleSignatureProto& signature, + const std::vector& scope_prefix, + const std::vector& name_prefix, + const absl::flat_hash_map& + child_signatures, + std::vector& interfaces) { + for (const verilog::ChannelInterfaceProto* channel_interface : + ReadyValidChannelsOf(signature)) { + std::vector name = name_prefix; + name.push_back(channel_interface->channel_name()); + interfaces.push_back(BusInterface{ + .name = absl::StrJoin(name, "."), + .scope = scope_prefix, + .ready_port = channel_interface->streaming().ready_port_name(), + .valid_port = channel_interface->streaming().valid_port_name(), + }); + } + + for (const verilog::InstantiationProto& instantiation : + signature.instantiations()) { + if (!instantiation.has_block_instantiation()) { + continue; // extern/fifo: not a proc, nothing to recurse into. + } + const verilog::BlockInstantiationProto& block_instantiation = + instantiation.block_instantiation(); + auto it = child_signatures.find(block_instantiation.block_name()); + if (it == child_signatures.end()) { + LOG(WARNING) << "instance " << block_instantiation.instance_name() + << " (block " << block_instantiation.block_name() + << ") has no matching child signature; skipping its " + "internal channels"; + continue; + } + std::vector child_scope = scope_prefix; + child_scope.push_back(block_instantiation.instance_name()); + std::vector child_name = name_prefix; + child_name.push_back(block_instantiation.instance_name()); + CollectInterfaces(it->second, child_scope, child_name, child_signatures, + interfaces); + } +} + +void AppendClockResetBlock(const verilog::ModuleSignatureProto& signature, + std::vector& lines) { + std::string_view clock_name = signature.clock_name().empty() + ? kDefaultClockName + : signature.clock_name(); + std::string_view reset_name = + signature.has_reset() ? signature.reset().name() : kDefaultResetName; + bool reset_active_low = + signature.has_reset() && signature.reset().active_low(); + + if (signature.clock_name().empty() || !signature.has_reset()) { + LOG(WARNING) << "signature has no clock_name/reset name; each interface " + "will need clock/reset filled in by hand"; + } + + lines.push_back("common_clk_rst_ifs:"); + lines.push_back(absl::StrFormat(" %s: &%s", kClockAlias, kClockAlias)); + lines.push_back(absl::StrFormat(" clock: %s", YamlString(clock_name))); + lines.push_back(absl::StrFormat(" reset: %s", YamlString(reset_name))); + lines.push_back(absl::StrFormat(" reset_type: %s", + YamlString(reset_active_low ? "low" + : "high"))); +} + +void AppendInterfaceEntry(const BusInterface& interface, + std::vector& lines) { + lines.push_back(absl::StrFormat(" %s:", YamlString(interface.name))); + lines.push_back(absl::StrCat( + " scope: [", + absl::StrJoin(interface.scope, ", ", + [](std::string* out, const std::string& scope_part) { + absl::StrAppend(out, YamlString(scope_part)); + }), + "]")); + lines.push_back(absl::StrFormat(" clk_rst_if: *%s", kClockAlias)); + lines.push_back(""); + lines.push_back(" handshake: \"ReadyValid\""); + lines.push_back( + absl::StrFormat(" ready: %s", YamlString(interface.ready_port))); + lines.push_back( + absl::StrFormat(" valid: %s", YamlString(interface.valid_port))); + lines.push_back(""); +} + +} // namespace + +absl::StatusOr GenerateBusperfYaml( + const verilog::ModuleSignatureProto& signature, + absl::Span scope, + const absl::flat_hash_map& + child_signatures) { + std::vector scope_prefix(scope.begin(), scope.end()); + std::vector interfaces; + CollectInterfaces(signature, scope_prefix, /*name_prefix=*/{}, + child_signatures, interfaces); + if (interfaces.empty()) { + return absl::InvalidArgumentError( + "No CHANNEL_FLOW_CONTROL_READY_VALID channel_interfaces found " + "(top-level or in any child signature); refusing to emit a " + "busperf YAML with an empty interfaces: block"); + } + + std::vector lines; + lines.push_back("# Auto-generated by xls_sig_to_busperf from an XLS"); + lines.push_back(absl::StrFormat("# ModuleSignatureProto for module %s.", + YamlString(signature.module_name()))); + AppendClockResetBlock(signature, lines); + lines.push_back(""); + lines.push_back("interfaces:"); + for (const BusInterface& interface : interfaces) { + AppendInterfaceEntry(interface, lines); + } + + return absl::StrJoin(lines, "\n"); +} + +} // namespace xls::busperf diff --git a/xls/experimental/busperf/busperf_yaml_generator.h b/xls/experimental/busperf/busperf_yaml_generator.h new file mode 100644 index 0000000000..b25c1d42a3 --- /dev/null +++ b/xls/experimental/busperf/busperf_yaml_generator.h @@ -0,0 +1,49 @@ +// Copyright 2026 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef XLS_EXPERIMENTAL_BUSPERF_BUSPERF_YAML_GENERATOR_H_ +#define XLS_EXPERIMENTAL_BUSPERF_BUSPERF_YAML_GENERATOR_H_ + +#include + +#include "absl/container/flat_hash_map.h" +#include "absl/status/statusor.h" +#include "absl/types/span.h" +#include "xls/codegen/module_signature.pb.h" + +namespace xls::busperf { + +// Generates a busperf (https://github.com/antmicro/busperf) YAML bus +// description from an XLS ModuleSignatureProto. Covers both external interface, +// and internal channels between a proc and any children it spawns. +// +// Args: +// signature: the top-level block's ModuleSignatureProto (codegen_main +// --output_signature_path=...). +// scope: VCD scope path components leading to the DUT instance, e.g. +// {"tb_passthrough", "dut"}. +// child_signatures: standalone signatures for spawned child procs +// (codegen'd without --module_name, so each module_name is the +// mangled block name), keyed by module_name. Matched against the +// parent's instantiations so their channels get included too, scoped +// under `scope` plus their instance_name. +absl::StatusOr GenerateBusperfYaml( + const verilog::ModuleSignatureProto& signature, + absl::Span scope, + const absl::flat_hash_map& + child_signatures); + +} // namespace xls::busperf + +#endif // XLS_EXPERIMENTAL_BUSPERF_BUSPERF_YAML_GENERATOR_H_ diff --git a/xls/experimental/busperf/busperf_yaml_generator_test.cc b/xls/experimental/busperf/busperf_yaml_generator_test.cc new file mode 100644 index 0000000000..66f9e46163 --- /dev/null +++ b/xls/experimental/busperf/busperf_yaml_generator_test.cc @@ -0,0 +1,264 @@ +// Copyright 2026 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "xls/experimental/busperf/busperf_yaml_generator.h" + +#include +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/container/flat_hash_map.h" +#include "absl/log/check.h" +#include "absl/status/status.h" +#include "google/protobuf/text_format.h" +#include "xls/codegen/module_signature.pb.h" +#include "xls/common/status/matchers.h" + +namespace xls::busperf { +namespace { + +using ::absl_testing::IsOkAndHolds; +using ::absl_testing::StatusIs; +using ::testing::HasSubstr; + +verilog::ModuleSignatureProto ParseSignature(std::string_view text) { + verilog::ModuleSignatureProto proto; + CHECK(google::protobuf::TextFormat::ParseFromString(std::string(text), + &proto)); + return proto; +} + +constexpr std::string_view kPassthroughSignature = R"pb( + module_name: "passthrough" + clock_name: "clk" + reset { name: "rst" active_low: false } + channel_interfaces { + channel_name: "_data_r" + direction: CHANNEL_DIRECTION_RECEIVE + kind: CHANNEL_KIND_STREAMING + streaming { + flow_control: CHANNEL_FLOW_CONTROL_READY_VALID + data_port_name: "_data_r" + ready_port_name: "_data_r_rdy" + valid_port_name: "_data_r_vld" + } + } + channel_interfaces { + channel_name: "_data_s" + direction: CHANNEL_DIRECTION_SEND + kind: CHANNEL_KIND_STREAMING + streaming { + flow_control: CHANNEL_FLOW_CONTROL_READY_VALID + data_port_name: "_data_s" + ready_port_name: "_data_s_rdy" + valid_port_name: "_data_s_vld" + } + } +)pb"; + +TEST(BusperfYamlGeneratorTest, FlatDesignEmitsBothChannels) { + verilog::ModuleSignatureProto signature = + ParseSignature(kPassthroughSignature); + std::vector scope = {"tb_passthrough", "dut"}; + + EXPECT_THAT( + GenerateBusperfYaml(signature, scope, /*child_signatures=*/{}), + IsOkAndHolds( + "# Auto-generated by xls_sig_to_busperf from an XLS\n" + "# ModuleSignatureProto for module \"passthrough\".\n" + "common_clk_rst_ifs:\n" + " main_clk: &main_clk\n" + " clock: \"clk\"\n" + " reset: \"rst\"\n" + " reset_type: \"high\"\n" + "\n" + "interfaces:\n" + " \"_data_r\":\n" + " scope: [\"tb_passthrough\", \"dut\"]\n" + " clk_rst_if: *main_clk\n" + "\n" + " handshake: \"ReadyValid\"\n" + " ready: \"_data_r_rdy\"\n" + " valid: \"_data_r_vld\"\n" + "\n" + " \"_data_s\":\n" + " scope: [\"tb_passthrough\", \"dut\"]\n" + " clk_rst_if: *main_clk\n" + "\n" + " handshake: \"ReadyValid\"\n" + " ready: \"_data_s_rdy\"\n" + " valid: \"_data_s_vld\"\n")); +} + +TEST(BusperfYamlGeneratorTest, SkipsNonReadyValidChannels) { + verilog::ModuleSignatureProto signature = ParseSignature(R"pb( + module_name: "single_val" + clock_name: "clk" + reset { name: "rst" } + channel_interfaces { + channel_name: "config" + direction: CHANNEL_DIRECTION_RECEIVE + kind: CHANNEL_KIND_SINGLE_VALUE + single_value { data_port_name: "config" } + } + channel_interfaces { + channel_name: "_data" + direction: CHANNEL_DIRECTION_RECEIVE + kind: CHANNEL_KIND_STREAMING + streaming { + flow_control: CHANNEL_FLOW_CONTROL_READY_VALID + data_port_name: "_data" + ready_port_name: "_data_rdy" + valid_port_name: "_data_vld" + } + } + )pb"); + + XLS_ASSERT_OK_AND_ASSIGN( + std::string yaml, + GenerateBusperfYaml(signature, {"dut"}, /*child_signatures=*/{})); + EXPECT_THAT(yaml, testing::Not(HasSubstr("config"))); + EXPECT_THAT(yaml, HasSubstr("\"_data\":")); +} + +TEST(BusperfYamlGeneratorTest, HierarchicalDesignRecursesIntoChildren) { + verilog::ModuleSignatureProto parent = ParseSignature(R"pb( + module_name: "hier_top" + clock_name: "clk" + reset { name: "rst" } + instantiations { + block_instantiation { + block_name: "__hier__Doubler_0_next" + instance_name: "__hier__Doubler_0_next_inst" + } + } + )pb"); + verilog::ModuleSignatureProto child = ParseSignature(R"pb( + module_name: "__hier__Doubler_0_next" + channel_interfaces { + channel_name: "_in_ch" + direction: CHANNEL_DIRECTION_RECEIVE + kind: CHANNEL_KIND_STREAMING + streaming { + flow_control: CHANNEL_FLOW_CONTROL_READY_VALID + data_port_name: "_in_ch" + ready_port_name: "_in_ch_rdy" + valid_port_name: "_in_ch_vld" + } + } + )pb"); + absl::flat_hash_map + child_signatures = {{"__hier__Doubler_0_next", child}}; + + XLS_ASSERT_OK_AND_ASSIGN( + std::string yaml, + GenerateBusperfYaml(parent, {"tb_hier", "dut"}, child_signatures)); + EXPECT_THAT(yaml, HasSubstr("\"__hier__Doubler_0_next_inst._in_ch\":")); + EXPECT_THAT(yaml, HasSubstr("scope: [\"tb_hier\", \"dut\", " + "\"__hier__Doubler_0_next_inst\"]")); + EXPECT_THAT(yaml, HasSubstr("ready: \"_in_ch_rdy\"")); +} + +TEST(BusperfYamlGeneratorTest, GrandchildRecursionAccumulatesScopeAndName) { + // top -> mid_inst -> leaf_inst: recursion beyond one hierarchy level. + verilog::ModuleSignatureProto top = ParseSignature(R"pb( + module_name: "top" + clock_name: "clk" + reset { name: "rst" } + instantiations { + block_instantiation { block_name: "mid" instance_name: "mid_inst" } + } + )pb"); + verilog::ModuleSignatureProto mid = ParseSignature(R"pb( + module_name: "mid" + instantiations { + block_instantiation { block_name: "leaf" instance_name: "leaf_inst" } + } + )pb"); + verilog::ModuleSignatureProto leaf = ParseSignature(R"pb( + module_name: "leaf" + channel_interfaces { + channel_name: "_data" + direction: CHANNEL_DIRECTION_SEND + kind: CHANNEL_KIND_STREAMING + streaming { + flow_control: CHANNEL_FLOW_CONTROL_READY_VALID + data_port_name: "_data" + ready_port_name: "_data_rdy" + valid_port_name: "_data_vld" + } + } + )pb"); + absl::flat_hash_map + child_signatures = {{"mid", mid}, {"leaf", leaf}}; + + XLS_ASSERT_OK_AND_ASSIGN( + std::string yaml, + GenerateBusperfYaml(top, {"tb_top", "dut"}, child_signatures)); + EXPECT_THAT(yaml, HasSubstr("\"mid_inst.leaf_inst._data\":")); + EXPECT_THAT(yaml, HasSubstr("scope: [\"tb_top\", \"dut\", \"mid_inst\", " + "\"leaf_inst\"]")); + EXPECT_THAT(yaml, HasSubstr("valid: \"_data_vld\"")); +} + +TEST(BusperfYamlGeneratorTest, MissingChildSignatureIsSkippedNotFatal) { + // Unresolved instantiation is skipped; the top's own channel still emits. + verilog::ModuleSignatureProto parent = ParseSignature(R"pb( + module_name: "hier_top" + clock_name: "clk" + reset { name: "rst" } + channel_interfaces { + channel_name: "_data" + direction: CHANNEL_DIRECTION_RECEIVE + kind: CHANNEL_KIND_STREAMING + streaming { + flow_control: CHANNEL_FLOW_CONTROL_READY_VALID + data_port_name: "_data" + ready_port_name: "_data_rdy" + valid_port_name: "_data_vld" + } + } + instantiations { + block_instantiation { + block_name: "unresolved_block" + instance_name: "unresolved_inst" + } + } + )pb"); + + XLS_ASSERT_OK_AND_ASSIGN( + std::string yaml, + GenerateBusperfYaml(parent, {"dut"}, /*child_signatures=*/{})); + EXPECT_THAT(yaml, HasSubstr("\"_data\":")); + EXPECT_THAT(yaml, testing::Not(HasSubstr("unresolved_inst"))); +} + +TEST(BusperfYamlGeneratorTest, NoReadyValidChannelsIsAnError) { + verilog::ModuleSignatureProto signature = ParseSignature(R"pb( + module_name: "no_channels" + clock_name: "clk" + reset { name: "rst" } + )pb"); + + EXPECT_THAT( + GenerateBusperfYaml(signature, {"dut"}, /*child_signatures=*/{}), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("CHANNEL_FLOW_CONTROL_READY_VALID " + "channel_interfaces found"))); +} + +} // namespace +} // namespace xls::busperf diff --git a/xls/experimental/busperf/examples/BUILD b/xls/experimental/busperf/examples/BUILD new file mode 100644 index 0000000000..6e0c9c80c4 --- /dev/null +++ b/xls/experimental/busperf/examples/BUILD @@ -0,0 +1,79 @@ +# Copyright 2026 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Example designs demonstrating xls_busperf_setup (//xls/experimental/busperf); +# see README.md in this directory. + +load("//xls/experimental/busperf/build_rules:build_defs.bzl", "xls_busperf_setup") +load("//xls/build_rules:xls_build_defs.bzl", "xls_dslx_library", "xls_dslx_test") + +package( + default_applicable_licenses = ["//:license"], + licenses = ["notice"], # Apache 2.0 +) + +xls_dslx_library( + name = "bottleneck_dslx", + srcs = ["bottleneck.x"], +) + +xls_dslx_test( + name = "bottleneck_dslx_test", + srcs = ["bottleneck.x"], + deps = [":bottleneck_dslx"], +) + +xls_busperf_setup( + name = "bottleneck_no_stall", + children = ["Passthrough", "SlowConsumerNoStall"], + codegen_args = { + "clock_period_ps": "10", + "materialize_internal_fifos": "true", + "module_name": "bottleneck_no_stall", + "pipeline_stages": "1", + }, + dslx_top = "BottleneckNoStall", + library = ":bottleneck_dslx", + reset = "rst", + scope = "tb_bottleneck_no_stall.dut", + testbench = "tb_bottleneck.v", + testbench_defines = { + "DUT_MODULE": "bottleneck_no_stall", + "TB_MODULE": "tb_bottleneck_no_stall", + "VCD_NAME": "\"bottleneck_no_stall.vcd\"", + }, + vcd_filename = "bottleneck_no_stall.vcd", +) + +xls_busperf_setup( + name = "bottleneck_stall", + children = ["Passthrough", "SlowConsumerStall4"], + codegen_args = { + "clock_period_ps": "10", + "materialize_internal_fifos": "true", + "module_name": "bottleneck_stall", + "pipeline_stages": "1", + }, + dslx_top = "BottleneckStall", + library = ":bottleneck_dslx", + reset = "rst", + scope = "tb_bottleneck_stall.dut", + testbench = "tb_bottleneck.v", + testbench_defines = { + "DUT_MODULE": "bottleneck_stall", + "TB_MODULE": "tb_bottleneck_stall", + "VCD_NAME": "\"bottleneck_stall.vcd\"", + }, + vcd_filename = "bottleneck_stall.vcd", +) diff --git a/xls/experimental/busperf/examples/README.md b/xls/experimental/busperf/examples/README.md new file mode 100644 index 0000000000..1d622a0053 --- /dev/null +++ b/xls/experimental/busperf/examples/README.md @@ -0,0 +1,42 @@ +# Busperf examples + +The goal is to spot bottlenecks - stages that stall a design's throughput - +directly from a busperf report, without hand-transcribing any signal names. +`xls_busperf_setup` (see `//xls/experimental/busperf`) turns an XLS proc +design straight into that report. + +This is demonstrated in `bottleneck.x`. It defines a small pipeline where one +stage may occasionally be too slow to keep up, stalling the dataflow feeding +into it. The same pipeline is built twice: once with that stage running at +full speed, and once with it stalling. Comparing the two reports side by +side shows what a bottleneck actually looks like in practice, next to a +healthy baseline. + +## Generate a report + +```sh +bazel build //xls/experimental/busperf/examples:bottleneck_stall_stats +cat bazel-bin/xls/experimental/busperf/examples/bottleneck_stall_stats.stats.txt +``` + +## Check that busperf sees the stall + +Build both variants and compare: + +```sh +bazel build //xls/experimental/busperf/examples:bottleneck_no_stall_stats \ + //xls/experimental/busperf/examples:bottleneck_stall_stats +``` + +Both testbenches drive their channels at full throttle (always valid, always +ready), so any backpressure comes from the design itself, not from the +testbench. In `bottleneck_no_stall`'s report, the `_data_r` row reads `Busy +2000 | Backpressure 0` - every cycle transfers. In `bottleneck_stall`'s +report, the same row reads `Busy 504 | Backpressure 1496` - the FIFO fills +and the upstream stage spends most of its time stalled behind it. + +## Open a report in a browser + +```sh +bazel run //xls/experimental/busperf/examples:bottleneck_stall_open_report +``` diff --git a/xls/experimental/busperf/examples/bottleneck.x b/xls/experimental/busperf/examples/bottleneck.x new file mode 100644 index 0000000000..954035b486 --- /dev/null +++ b/xls/experimental/busperf/examples/bottleneck.x @@ -0,0 +1,153 @@ +// Copyright 2026 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Shows busperf catching a backpressure bottleneck: Passthrough feeds +// SlowConsumer through a depth-4 FIFO, and SlowConsumer<4> stalls it while +// SlowConsumer<0> stays healthy. +// +// SlowConsumerNoStall and SlowConsumerStall4 wrap each configuration in a +// non-parametric proc, since xls_busperf_setup's `children` codegen needs a +// concrete name to target and can't convert a bare parametric one. + +#![feature(type_inference_v2)] + +proc Passthrough { + data_r: chan in; + data_s: chan out; + + config( + data_r: chan in, + data_s: chan out + ) { (data_r, data_s) } + + init { } + + next(state: ()) { + let (tok, data) = recv(join(), data_r); + let tok = send(tok, data_s, data); + } +} + +proc SlowConsumer { + data_r: chan in; + data_s: chan out; + + config( + data_r: chan in, + data_s: chan out + ) { (data_r, data_s) } + + init { u32:0 } + + next(state: u32) { + let do_recv = const if STALL_CYCLES == u32:0 { + true + } else { + state % STALL_CYCLES == u32:0 + }; + let (tok, data) = recv_if(join(), data_r, do_recv, u32:0); + let tok = send_if(tok, data_s, do_recv, data); + const if STALL_CYCLES == u32:0 { + u32:0 + } else { + (state + u32:1) % STALL_CYCLES + } + } +} + +proc SlowConsumerNoStall { + config(data_r: chan in, data_s: chan out) { + spawn SlowConsumer(data_r, data_s); + } + init { } + next(state: ()) { } +} + +proc SlowConsumerStall4 { + config(data_r: chan in, data_s: chan out) { + spawn SlowConsumer(data_r, data_s); + } + init { } + next(state: ()) { } +} + +proc BottleneckNoStall { + config(data_r: chan in, data_s: chan out) { + let (fifo_s, fifo_r) = chan("fifo"); + spawn Passthrough(data_r, fifo_s); + spawn SlowConsumerNoStall(fifo_r, data_s); + } + init { } + next(state: ()) { } +} + +#[test_proc] +proc BottleneckNoStallTest { + terminator: chan out; + stim_s: chan out; + resp_r: chan in; + + config(terminator: chan out) { + let (stim_s, stim_r) = chan("stim"); + let (resp_s, resp_r) = chan("resp"); + spawn BottleneckNoStall(stim_r, resp_s); + (terminator, stim_s, resp_r) + } + + init { } + + next(state: ()) { + const TEST_VALUE = u32:42; + let tok = join(); + let tok = send(tok, stim_s, TEST_VALUE); + let (tok, value) = recv(tok, resp_r); + assert_eq(value, TEST_VALUE); + send(tok, terminator, true); + } +} + +proc BottleneckStall { + config(data_r: chan in, data_s: chan out) { + let (fifo_s, fifo_r) = chan("fifo"); + spawn Passthrough(data_r, fifo_s); + spawn SlowConsumerStall4(fifo_r, data_s); + } + init { } + next(state: ()) { } +} + +#[test_proc] +proc BottleneckStallTest { + terminator: chan out; + stim_s: chan out; + resp_r: chan in; + + config(terminator: chan out) { + let (stim_s, stim_r) = chan("stim"); + let (resp_s, resp_r) = chan("resp"); + spawn BottleneckStall(stim_r, resp_s); + (terminator, stim_s, resp_r) + } + + init { } + + next(state: ()) { + const TEST_VALUE = u32:42; + let tok = join(); + let tok = send(tok, stim_s, TEST_VALUE); + let (tok, value) = recv(tok, resp_r); + assert_eq(value, TEST_VALUE); + send(tok, terminator, true); + } +} diff --git a/xls/experimental/busperf/examples/tb_bottleneck.v b/xls/experimental/busperf/examples/tb_bottleneck.v new file mode 100644 index 0000000000..da54e9724b --- /dev/null +++ b/xls/experimental/busperf/examples/tb_bottleneck.v @@ -0,0 +1,56 @@ +// Copyright 2026 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Shared testbench for bottleneck_no_stall and bottleneck_stall, driving +// data_r/data_s at full throttle. DUT_MODULE, TB_MODULE, and VCD_NAME come +// from xls_busperf_setup as `iverilog -D...` flags. + +`timescale 1ns/1ps + +module `TB_MODULE; + reg clk = 0; + reg rst = 1'b1; + + wire [31:0] data_r = 32'hDEADBEEF; + wire data_r_vld = !rst; + wire data_r_rdy; + + wire [31:0] data_s; + wire data_s_vld; + wire data_s_rdy = !rst; + + `DUT_MODULE dut ( + .clk(clk), + .rst(rst), + ._data_r(data_r), + ._data_r_vld(data_r_vld), + ._data_r_rdy(data_r_rdy), + ._data_s(data_s), + ._data_s_vld(data_s_vld), + ._data_s_rdy(data_s_rdy) + ); + + always #5 clk = ~clk; + + initial begin + $dumpfile(`VCD_NAME); + $dumpvars(0, `TB_MODULE); + + repeat (4) @(posedge clk); + rst = 1'b0; + + repeat (2000) @(posedge clk); + $finish; + end +endmodule diff --git a/xls/experimental/busperf/tests/BUILD b/xls/experimental/busperf/tests/BUILD new file mode 100644 index 0000000000..185de9b78a --- /dev/null +++ b/xls/experimental/busperf/tests/BUILD @@ -0,0 +1,56 @@ +# Copyright 2026 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Runs the real pipeline on a hierarchical design and diffs against a golden +# YAML. Run :hier_test_update_golden to update it after an intentional change. + +load("//xls/build_rules:xls_build_defs.bzl", "xls_diff_test", "xls_dslx_library") +load("//xls/experimental/busperf/build_rules:build_defs.bzl", "xls_busperf_setup") + +package( + default_applicable_licenses = ["//:license"], + licenses = ["notice"], # Apache 2.0 +) + +xls_dslx_library( + name = "hier_dslx", + srcs = ["hier.x"], +) + +xls_busperf_setup( + name = "hier", + children = ["Doubler"], + codegen_args = { + "clock_period_ps": "10", + "module_name": "hier", + "pipeline_stages": "1", + }, + dslx_top = "Parent", + library = ":hier_dslx", + reset = "rst", + scope = "tb_hier.dut", + testbench = "testdata/tb_hier.v", + testbench_defines = { + "DUT_MODULE": "hier", + "TB_MODULE": "tb_hier", + "VCD_NAME": "\"hier.vcd\"", + }, + vcd_filename = "hier.vcd", +) + +xls_diff_test( + name = "hier_test", + file = ":hier_bus_yaml", + golden = "testdata/hier.busperf.yaml", +) diff --git a/xls/experimental/busperf/tests/hier.x b/xls/experimental/busperf/tests/hier.x new file mode 100644 index 0000000000..8a7a0467c3 --- /dev/null +++ b/xls/experimental/busperf/tests/hier.x @@ -0,0 +1,46 @@ +// Copyright 2026 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Minimal hierarchical example exercising busperf setup generation for +// internal, per-instance ready/valid channels between a parent and its +// spawned children. + +#![feature(type_inference_v2)] + +proc Doubler { + in_ch: chan in; + out_ch: chan out; + + config(in_ch: chan in, out_ch: chan out) { (in_ch, out_ch) } + + init { () } + + next(state: ()) { + let (tok, x) = recv(join(), in_ch); + let tok = send(tok, out_ch, x + x); + } +} + +proc Parent { + config(in0: chan in, out0: chan out, + in1: chan in, out1: chan out) { + spawn Doubler(in0, out0); + spawn Doubler(in1, out1); + () + } + + init { () } + + next(state: ()) { () } +} diff --git a/xls/experimental/busperf/tests/testdata/hier.busperf.yaml b/xls/experimental/busperf/tests/testdata/hier.busperf.yaml new file mode 100644 index 0000000000..3593a1c2e0 --- /dev/null +++ b/xls/experimental/busperf/tests/testdata/hier.busperf.yaml @@ -0,0 +1,72 @@ +# Auto-generated by xls_sig_to_busperf from an XLS +# ModuleSignatureProto for module "hier". +common_clk_rst_ifs: + main_clk: &main_clk + clock: "clk" + reset: "rst" + reset_type: "high" + +interfaces: + "_in0": + scope: ["tb_hier", "dut"] + clk_rst_if: *main_clk + + handshake: "ReadyValid" + ready: "_in0_rdy" + valid: "_in0_vld" + + "_in1": + scope: ["tb_hier", "dut"] + clk_rst_if: *main_clk + + handshake: "ReadyValid" + ready: "_in1_rdy" + valid: "_in1_vld" + + "_out0": + scope: ["tb_hier", "dut"] + clk_rst_if: *main_clk + + handshake: "ReadyValid" + ready: "_out0_rdy" + valid: "_out0_vld" + + "_out1": + scope: ["tb_hier", "dut"] + clk_rst_if: *main_clk + + handshake: "ReadyValid" + ready: "_out1_rdy" + valid: "_out1_vld" + + "__hier__Doubler_0_next_inst._in_ch": + scope: ["tb_hier", "dut", "__hier__Doubler_0_next_inst"] + clk_rst_if: *main_clk + + handshake: "ReadyValid" + ready: "_in_ch_rdy" + valid: "_in_ch_vld" + + "__hier__Doubler_0_next_inst._out_ch": + scope: ["tb_hier", "dut", "__hier__Doubler_0_next_inst"] + clk_rst_if: *main_clk + + handshake: "ReadyValid" + ready: "_out_ch_rdy" + valid: "_out_ch_vld" + + "__hier__Doubler_0_next_inst_1._in_ch": + scope: ["tb_hier", "dut", "__hier__Doubler_0_next_inst_1"] + clk_rst_if: *main_clk + + handshake: "ReadyValid" + ready: "_in_ch_rdy" + valid: "_in_ch_vld" + + "__hier__Doubler_0_next_inst_1._out_ch": + scope: ["tb_hier", "dut", "__hier__Doubler_0_next_inst_1"] + clk_rst_if: *main_clk + + handshake: "ReadyValid" + ready: "_out_ch_rdy" + valid: "_out_ch_vld" diff --git a/xls/experimental/busperf/tests/testdata/tb_hier.v b/xls/experimental/busperf/tests/testdata/tb_hier.v new file mode 100644 index 0000000000..b4f77380a1 --- /dev/null +++ b/xls/experimental/busperf/tests/testdata/tb_hier.v @@ -0,0 +1,64 @@ +// Copyright 2026 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +`timescale 1ns/1ps + +module tb_hier; + reg clk = 0; + reg rst = 1'b1; + + wire [31:0] in0 = 32'hDEADBEEF; + wire in0_vld = !rst; + wire in0_rdy; + wire [31:0] out0; + wire out0_vld; + wire out0_rdy = !rst; + + wire [31:0] in1 = 32'hDEADBEEF; + wire in1_vld = !rst; + wire in1_rdy; + wire [31:0] out1; + wire out1_vld; + wire out1_rdy = !rst; + + hier dut ( + .clk(clk), + .rst(rst), + ._in0(in0), + ._in0_vld(in0_vld), + ._in0_rdy(in0_rdy), + ._out0(out0), + ._out0_vld(out0_vld), + ._out0_rdy(out0_rdy), + ._in1(in1), + ._in1_vld(in1_vld), + ._in1_rdy(in1_rdy), + ._out1(out1), + ._out1_vld(out1_vld), + ._out1_rdy(out1_rdy) + ); + + always #5 clk = ~clk; + + initial begin + $dumpfile("hier.vcd"); + $dumpvars(0, tb_hier); + + repeat (4) @(posedge clk); + rst = 1'b0; + + repeat (2000) @(posedge clk); + $finish; + end +endmodule diff --git a/xls/experimental/busperf/xls_sig_to_busperf_main.cc b/xls/experimental/busperf/xls_sig_to_busperf_main.cc new file mode 100644 index 0000000000..769256e9dd --- /dev/null +++ b/xls/experimental/busperf/xls_sig_to_busperf_main.cc @@ -0,0 +1,108 @@ +// Copyright 2026 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include + +#include "absl/container/flat_hash_map.h" +#include "absl/flags/flag.h" +#include "absl/log/check.h" +#include "absl/log/log.h" +#include "absl/status/status.h" +#include "absl/strings/str_split.h" +#include "xls/codegen/module_signature.pb.h" +#include "xls/common/exit_status.h" +#include "xls/common/file/filesystem.h" +#include "xls/common/init_xls.h" +#include "xls/common/status/status_macros.h" +#include "xls/experimental/busperf/busperf_yaml_generator.h" + +static constexpr std::string_view kUsage = R"( +Generates a busperf (https://github.com/antmicro/busperf) YAML bus +description from an XLS ModuleSignatureProto (codegen_main +--output_signature_path=...). + +Usage: + xls_sig_to_busperf --scope=tb_top.dut SIGNATURE.textproto + xls_sig_to_busperf --scope=tb_top.dut --output=bus.yaml \ + --child_signature=child_a.sig.textproto,child_b.sig.textproto \ + top.sig.textproto +)"; + +ABSL_FLAG(std::string, scope, "", + "Dot-separated VCD scope path to the DUT instance, e.g. " + "'tb_passthrough.dut'. Required."); +ABSL_FLAG(std::vector, child_signature, {}, + "Standalone ModuleSignatureProto textprotos for spawned child " + "blocks (codegen'd with --top= on the same package, " + "without --module_name so each keeps its mangled block name). " + "Matched against instantiations().block_instantiation()" + ".block_name() by the child signature's own module_name."); +ABSL_FLAG(std::string, output, "", + "Output YAML path. If empty, writes to stdout."); + +namespace xls::busperf { +namespace { + +// Parses `signature_path`/`--child_signature` files, generates the busperf +// YAML, and writes it to `--output` (or stdout if unset). +absl::Status RealMain(std::string_view signature_path) { + verilog::ModuleSignatureProto signature; + XLS_RETURN_IF_ERROR(ParseTextProtoFile(signature_path, &signature)); + + absl::flat_hash_map + child_signatures; + for (const std::string& child_path : absl::GetFlag(FLAGS_child_signature)) { + verilog::ModuleSignatureProto child_signature; + XLS_RETURN_IF_ERROR(ParseTextProtoFile(child_path, &child_signature)); + const std::string& module_name = child_signature.module_name(); + if (child_signatures.contains(module_name)) { + LOG(WARNING) << "multiple --child_signature files have module_name '" + << module_name << "'; only the last one (" << child_path + << ") will be used"; + } + child_signatures[module_name] = std::move(child_signature); + } + + std::string scope_flag = absl::GetFlag(FLAGS_scope); + QCHECK(!scope_flag.empty()) << "Must specify --scope"; + std::vector scope = + absl::StrSplit(scope_flag, '.', absl::SkipEmpty()); + + XLS_ASSIGN_OR_RETURN( + std::string yaml, + GenerateBusperfYaml(signature, scope, child_signatures)); + + std::string output_path = absl::GetFlag(FLAGS_output); + if (output_path.empty()) { + std::cout << yaml; + return absl::OkStatus(); + } + return SetFileContents(output_path, yaml); +} + +} // namespace +} // namespace xls::busperf + +int main(int argc, char** argv) { + std::vector positional_arguments = + xls::InitXls(kUsage, argc, argv); + QCHECK_EQ(positional_arguments.size(), 1) + << "Expected a single positional argument: SIGNATURE.textproto. See " + "--help"; + return xls::ExitStatus(xls::busperf::RealMain(positional_arguments[0])); +} From 69484a14837c73dba8290a2098bc6b44c3d68773 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Fri, 17 Jul 2026 16:31:34 +0200 Subject: [PATCH 2/3] Embed child block signatures recursively in codegen Signed-off-by: Robert Winkler --- xls/codegen/BUILD | 2 + xls/codegen/signature_generation_pass.cc | 39 ++++ .../signature_generation_pass.cc | 33 ++++ .../signature_generation_pass_test.cc | 177 ++++++++++++++++++ 4 files changed, 251 insertions(+) diff --git a/xls/codegen/BUILD b/xls/codegen/BUILD index 7f2008b1ac..6b9b328f1a 100644 --- a/xls/codegen/BUILD +++ b/xls/codegen/BUILD @@ -613,10 +613,12 @@ cc_library( deps = [ ":codegen_pass", ":module_signature", + ":module_signature_cc_proto", ":signature_generator", "//xls/common/status:status_macros", "//xls/ir", "//xls/passes:pass_base", + "@abseil-cpp//absl/base", "@abseil-cpp//absl/log", "@abseil-cpp//absl/status", "@abseil-cpp//absl/status:statusor", diff --git a/xls/codegen/signature_generation_pass.cc b/xls/codegen/signature_generation_pass.cc index 9fb25ba5bc..089339811c 100644 --- a/xls/codegen/signature_generation_pass.cc +++ b/xls/codegen/signature_generation_pass.cc @@ -14,18 +14,53 @@ #include "xls/codegen/signature_generation_pass.h" +#include "absl/base/casts.h" #include "absl/log/log.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/str_format.h" #include "xls/codegen/codegen_pass.h" #include "xls/codegen/module_signature.h" +#include "xls/codegen/module_signature.pb.h" #include "xls/codegen/signature_generator.h" #include "xls/common/status/status_macros.h" +#include "xls/ir/block.h" +#include "xls/ir/instantiation.h" #include "xls/ir/package.h" #include "xls/passes/pass_base.h" namespace xls::verilog { +namespace { + +// Returns `block`'s signature with `block_signature` embedded recursively. +ModuleSignatureProto ResolveEmbeddedSignature(Block* block) { + ModuleSignatureProto proto = *block->GetSignature(); + for (InstantiationProto& instantiation : *proto.mutable_instantiations()) { + if (!instantiation.has_block_instantiation()) { + continue; + } + BlockInstantiationProto* block_instantiation = + instantiation.mutable_block_instantiation(); + for (const ::xls::Instantiation* child_instantiation : + block->GetInstantiations()) { + if (child_instantiation->kind() != ::xls::InstantiationKind::kBlock || + child_instantiation->name() != block_instantiation->instance_name()) { + continue; + } + Block* child_block = + absl::down_cast(child_instantiation) + ->instantiated_block(); + if (child_block->GetSignature().has_value()) { + *block_instantiation->mutable_block_signature() = + ResolveEmbeddedSignature(child_block); + } + break; + } + } + return proto; +} + +} // namespace absl::StatusOr SignatureGenerationPass::RunInternal( Package* package, const CodegenPassOptions& options, PassResults* results, @@ -45,6 +80,10 @@ absl::StatusOr SignatureGenerationPass::RunInternal( block->SetSignature(signature.proto()); changed = true; } + // All blocks now have their own signature; embed children recursively. + for (auto& [block, metadata] : context.metadata()) { + block->SetSignature(ResolveEmbeddedSignature(block)); + } return changed; } diff --git a/xls/codegen_v_1_5/signature_generation_pass.cc b/xls/codegen_v_1_5/signature_generation_pass.cc index ea7f576215..689a545097 100644 --- a/xls/codegen_v_1_5/signature_generation_pass.cc +++ b/xls/codegen_v_1_5/signature_generation_pass.cc @@ -253,6 +253,35 @@ absl::StatusOr GenerateSignature( return b.Build(); } +// Returns `block`'s signature with `block_signature` embedded recursively. +verilog::ModuleSignatureProto ResolveEmbeddedSignature(Block* block) { + verilog::ModuleSignatureProto proto = *block->GetSignature(); + for (verilog::InstantiationProto& instantiation : + *proto.mutable_instantiations()) { + if (!instantiation.has_block_instantiation()) { + continue; + } + verilog::BlockInstantiationProto* block_instantiation = + instantiation.mutable_block_instantiation(); + for (const ::xls::Instantiation* child_instantiation : + block->GetInstantiations()) { + if (child_instantiation->kind() != ::xls::InstantiationKind::kBlock || + child_instantiation->name() != block_instantiation->instance_name()) { + continue; + } + Block* child_block = + absl::down_cast(child_instantiation) + ->instantiated_block(); + if (child_block->GetSignature().has_value()) { + *block_instantiation->mutable_block_signature() = + ResolveEmbeddedSignature(child_block); + } + break; + } + } + return proto; +} + } // namespace absl::StatusOr SignatureGenerationPass::RunInternal( @@ -272,6 +301,10 @@ absl::StatusOr SignatureGenerationPass::RunInternal( block->SetSignature(signature.proto()); changed = true; } + // All blocks now have their own signature; embed children recursively. + for (const std::unique_ptr& block : package->blocks()) { + block->SetSignature(ResolveEmbeddedSignature(block.get())); + } return changed; } diff --git a/xls/codegen_v_1_5/signature_generation_pass_test.cc b/xls/codegen_v_1_5/signature_generation_pass_test.cc index 9763b1616e..0dcdcf2710 100644 --- a/xls/codegen_v_1_5/signature_generation_pass_test.cc +++ b/xls/codegen_v_1_5/signature_generation_pass_test.cc @@ -725,12 +725,112 @@ top block my_block() { block_instantiation { instance_name: "generator_inst" block_name: "generator" + block_signature { + module_name: "generator" + data_ports { + direction: PORT_DIRECTION_OUTPUT + name: "out" + width: 32 + type { type_enum: BITS bit_count: 32 } + } + data_ports { + direction: PORT_DIRECTION_OUTPUT + name: "out_valid" + width: 1 + type { type_enum: BITS bit_count: 1 } + } + data_ports { + direction: PORT_DIRECTION_INPUT + name: "out_ready" + width: 1 + type { type_enum: BITS bit_count: 1 } + } + clock_name: "clk" + reset { + name: "rst" + asynchronous: false + active_low: false + } + pipeline { + latency: 0 + initiation_interval: 1 + pipeline_control { + valid { + input_name: "input_valid" + output_name: "output_valid" + } + } + } + channel_interfaces { + channel_name: "out" + direction: CHANNEL_DIRECTION_SEND + type { type_enum: BITS bit_count: 32 } + kind: CHANNEL_KIND_STREAMING + streaming { + flow_control: CHANNEL_FLOW_CONTROL_READY_VALID + data_port_name: "out" + ready_port_name: "out_ready" + valid_port_name: "out_valid" + } + flop_kind: FLOP_KIND_NONE + } + } } )pb"), EqualsProto(R"pb( block_instantiation { instance_name: "consumer_inst" block_name: "consumer" + block_signature { + module_name: "consumer" + data_ports { + direction: PORT_DIRECTION_INPUT + name: "in" + width: 32 + type { type_enum: BITS bit_count: 32 } + } + data_ports { + direction: PORT_DIRECTION_INPUT + name: "in_valid" + width: 1 + type { type_enum: BITS bit_count: 1 } + } + data_ports { + direction: PORT_DIRECTION_OUTPUT + name: "in_ready" + width: 1 + type { type_enum: BITS bit_count: 1 } + } + clock_name: "clk" + reset { + name: "rst" + asynchronous: false + active_low: false + } + pipeline { + latency: 0 + initiation_interval: 1 + pipeline_control { + valid { + input_name: "input_valid" + output_name: "output_valid" + } + } + } + channel_interfaces { + channel_name: "in" + direction: CHANNEL_DIRECTION_RECEIVE + type { type_enum: BITS bit_count: 32 } + kind: CHANNEL_KIND_STREAMING + streaming { + flow_control: CHANNEL_FLOW_CONTROL_READY_VALID + data_port_name: "in" + ready_port_name: "in_ready" + valid_port_name: "in_valid" + } + flop_kind: FLOP_KIND_NONE + } + } } )pb"))); } @@ -805,6 +905,83 @@ top block my_block(in: bits[32], in_valid: bits[1], in_ready: bits[1], block_instantiation { instance_name: "subblock_inst" block_name: "subblock" + block_signature { + module_name: "subblock" + data_ports { + direction: PORT_DIRECTION_INPUT + name: "in" + width: 32 + type { type_enum: BITS bit_count: 32 } + } + data_ports { + direction: PORT_DIRECTION_INPUT + name: "in_valid" + width: 1 + type { type_enum: BITS bit_count: 1 } + } + data_ports { + direction: PORT_DIRECTION_OUTPUT + name: "in_ready" + width: 1 + type { type_enum: BITS bit_count: 1 } + } + data_ports { + direction: PORT_DIRECTION_OUTPUT + name: "out" + width: 32 + type { type_enum: BITS bit_count: 32 } + } + data_ports { + direction: PORT_DIRECTION_OUTPUT + name: "out_valid" + width: 1 + type { type_enum: BITS bit_count: 1 } + } + data_ports { + direction: PORT_DIRECTION_INPUT + name: "out_ready" + width: 1 + type { type_enum: BITS bit_count: 1 } + } + clock_name: "clk" + reset { name: "rst" asynchronous: false active_low: false } + pipeline { + latency: 0 + initiation_interval: 1 + pipeline_control { + valid { + input_name: "input_valid" + output_name: "output_valid" + } + } + } + channel_interfaces { + channel_name: "in" + direction: CHANNEL_DIRECTION_RECEIVE + type { type_enum: BITS bit_count: 32 } + kind: CHANNEL_KIND_STREAMING + streaming { + flow_control: CHANNEL_FLOW_CONTROL_READY_VALID + data_port_name: "in" + ready_port_name: "in_ready" + valid_port_name: "in_valid" + } + flop_kind: FLOP_KIND_NONE + } + channel_interfaces { + channel_name: "out" + direction: CHANNEL_DIRECTION_SEND + type { type_enum: BITS bit_count: 32 } + kind: CHANNEL_KIND_STREAMING + streaming { + flow_control: CHANNEL_FLOW_CONTROL_READY_VALID + data_port_name: "out" + ready_port_name: "out_ready" + valid_port_name: "out_valid" + } + flop_kind: FLOP_KIND_NONE + } + } } )pb"))); From 1bf6cff0d304feaa0a8815580996d88f2710e6d4 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Fri, 17 Jul 2026 16:31:38 +0200 Subject: [PATCH 3/3] Use embedded child signatures in busperf Signed-off-by: Robert Winkler --- xls/experimental/busperf/BUILD | 4 - .../busperf/build_rules/busperf_macros.bzl | 27 +-- .../build_rules/busperf_yaml_rules.bzl | 13 +- .../busperf/busperf_yaml_generator.cc | 122 +++++++------ .../busperf/busperf_yaml_generator.h | 20 +- .../busperf/busperf_yaml_generator_test.cc | 171 +++++++++--------- xls/experimental/busperf/examples/BUILD | 2 - .../busperf/examples/bottleneck.x | 24 +-- xls/experimental/busperf/tests/BUILD | 1 - .../busperf/xls_sig_to_busperf_main.cc | 42 +---- 10 files changed, 172 insertions(+), 254 deletions(-) diff --git a/xls/experimental/busperf/BUILD b/xls/experimental/busperf/BUILD index fcf78572a8..03e83a8fde 100644 --- a/xls/experimental/busperf/BUILD +++ b/xls/experimental/busperf/BUILD @@ -27,7 +27,6 @@ cc_library( hdrs = ["busperf_yaml_generator.h"], deps = [ "//xls/codegen:module_signature_cc_proto", - "@abseil-cpp//absl/container:flat_hash_map", "@abseil-cpp//absl/log", "@abseil-cpp//absl/status", "@abseil-cpp//absl/status:statusor", @@ -45,7 +44,6 @@ cc_test( "//xls/codegen:module_signature_cc_proto", "//xls/common:xls_gunit_main", "//xls/common/status:matchers", - "@abseil-cpp//absl/container:flat_hash_map", "@abseil-cpp//absl/log:check", "@abseil-cpp//absl/status", "@googletest//:gtest", @@ -64,9 +62,7 @@ cc_binary( "//xls/common:init_xls", "//xls/common/file:filesystem", "//xls/common/status:status_macros", - "@abseil-cpp//absl/container:flat_hash_map", "@abseil-cpp//absl/flags:flag", - "@abseil-cpp//absl/log", "@abseil-cpp//absl/log:check", "@abseil-cpp//absl/status", "@abseil-cpp//absl/strings", diff --git a/xls/experimental/busperf/build_rules/busperf_macros.bzl b/xls/experimental/busperf/build_rules/busperf_macros.bzl index 4a70cb5bad..7752460c21 100644 --- a/xls/experimental/busperf/build_rules/busperf_macros.bzl +++ b/xls/experimental/busperf/build_rules/busperf_macros.bzl @@ -27,19 +27,16 @@ def xls_busperf_yaml( reset, scope, reset_active_low = False, - children = [], codegen_args = {}): """Generates a busperf YAML bus description for one XLS proc design. Args: name: Base name for the generated targets and Verilog module. dslx_top: DSLX proc name to codegen as the top of the design. - library: xls_dslx_library target containing dslx_top and `children`. + library: xls_dslx_library target containing dslx_top. reset: Reset signal name. scope: Dot-separated VCD scope path to the DUT, e.g. "tb_foo.dut". reset_active_low: Reset polarity. Defaults to False. - children: DSLX proc names spawned by dslx_top whose internal - ready/valid channels should also get busperf interfaces. codegen_args: Extra/override codegen_main args. """ final_codegen_args = dict(codegen_args) @@ -55,25 +52,9 @@ def xls_busperf_yaml( tags = ["manual"], ) - child_sig_labels = [] - for child in children: - child_codegen_args = dict(final_codegen_args) - child_codegen_args.pop("module_name", None) - child_target = name + "_child_" + child - xls_dslx_verilog( - name = child_target + "_verilog", - dslx_top = child, - library = library, - codegen_args = child_codegen_args, - verilog_file = child_target + ".v", - tags = ["manual"], - ) - child_sig_labels.append(":" + child_target + ".sig.textproto") - busperf_yaml( name = name + "_bus_yaml", signature = ":" + name + ".sig.textproto", - child_signatures = child_sig_labels, scope = scope, xls_sig_to_busperf = "//xls/experimental/busperf:xls_sig_to_busperf", tags = ["manual"], @@ -89,22 +70,19 @@ def xls_busperf_setup( vcd_filename, reset_active_low = False, testbench_defines = {}, - children = [], codegen_args = {}): """Generates a full busperf channel analysis setup for one XLS proc design. Args: name: Base name for the generated targets and Verilog module. dslx_top: DSLX proc name to codegen as the top of the design. - library: xls_dslx_library target containing dslx_top and `children`. + library: xls_dslx_library target containing dslx_top. testbench: Checked-in Verilog testbench. reset: Reset signal name. scope: Dot-separated VCD scope path to the DUT, e.g. "tb_foo.dut". vcd_filename: VCD file name reset_active_low: Reset polarity. Defaults to False. testbench_defines: defines to pass to iverilog simulator - children: DSLX proc names spawned by dslx_top whose internal - ready/valid channels should also get busperf interfaces. codegen_args: Extra/override codegen_main args. """ # `.v` is hardcoded throughout this macro (the `_vcd` genrule). @@ -118,7 +96,6 @@ def xls_busperf_setup( reset = reset, scope = scope, reset_active_low = reset_active_low, - children = children, codegen_args = setup_codegen_args, ) diff --git a/xls/experimental/busperf/build_rules/busperf_yaml_rules.bzl b/xls/experimental/busperf/build_rules/busperf_yaml_rules.bzl index 15e4edb16f..dd69edb125 100644 --- a/xls/experimental/busperf/build_rules/busperf_yaml_rules.bzl +++ b/xls/experimental/busperf/build_rules/busperf_yaml_rules.bzl @@ -22,18 +22,12 @@ def _busperf_yaml_impl(ctx): args = ctx.actions.args() args.add(ctx.file.signature) args.add("--scope", ctx.attr.scope) - if ctx.files.child_signatures: - args.add_joined( - "--child_signature", - ctx.files.child_signatures, - join_with = ",", - ) args.add("--output", out) ctx.actions.run( executable = ctx.executable.xls_sig_to_busperf, arguments = [args], - inputs = [ctx.file.signature] + ctx.files.child_signatures, + inputs = [ctx.file.signature], outputs = [out], mnemonic = "BusperfYaml", progress_message = "Generating busperf YAML for %{label}", @@ -49,11 +43,6 @@ busperf_yaml = rule( allow_single_file = True, mandatory = True, ), - "child_signatures": attr.label_list( - doc = "Standalone signatures for spawned child procs.", - allow_files = True, - default = [], - ), "scope": attr.string( doc = "Dot-separated VCD scope path to the DUT, e.g. \"tb_foo.dut\".", mandatory = True, diff --git a/xls/experimental/busperf/busperf_yaml_generator.cc b/xls/experimental/busperf/busperf_yaml_generator.cc index 190ff69bcf..522e351623 100644 --- a/xls/experimental/busperf/busperf_yaml_generator.cc +++ b/xls/experimental/busperf/busperf_yaml_generator.cc @@ -14,18 +14,17 @@ #include "xls/experimental/busperf/busperf_yaml_generator.h" +#include #include #include #include -#include "absl/container/flat_hash_map.h" #include "absl/log/log.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/strings/str_join.h" -#include "absl/types/span.h" #include "xls/codegen/module_signature.pb.h" namespace xls::busperf { @@ -62,6 +61,9 @@ std::vector ReadyValidChannelsOf( return result; } +// Returns a string of spaces matching the indentation `level`. +std::string Indent(int64_t level) { return std::string(level * 2, ' '); } + // Quotes and escapes `s` for use as a YAML scalar. std::string YamlString(std::string_view s) { std::string result; @@ -77,26 +79,23 @@ std::string YamlString(std::string_view s) { return result; } -// Recursively collects ready-valid channels from `signature` and any -// instantiated child found in `child_signatures` (keyed by module_name) -// into `interfaces`. -void CollectInterfaces( - const verilog::ModuleSignatureProto& signature, - const std::vector& scope_prefix, - const std::vector& name_prefix, - const absl::flat_hash_map& - child_signatures, - std::vector& interfaces) { +// Recursively collects ready-valid channels from `signature` and its +// instantiated children into `interfaces`. Use CollectInterfaces() instead. +void CollectInterfacesRecursive(const verilog::ModuleSignatureProto& signature, + std::vector& scope_prefix, + std::vector& name_prefix, + std::vector& interfaces) { for (const verilog::ChannelInterfaceProto* channel_interface : ReadyValidChannelsOf(signature)) { - std::vector name = name_prefix; - name.push_back(channel_interface->channel_name()); + name_prefix.push_back(channel_interface->channel_name()); interfaces.push_back(BusInterface{ - .name = absl::StrJoin(name, "."), - .scope = scope_prefix, + .name = absl::StrJoin(name_prefix, "."), + .scope = std::vector(scope_prefix.begin(), + scope_prefix.end()), .ready_port = channel_interface->streaming().ready_port_name(), .valid_port = channel_interface->streaming().valid_port_name(), }); + name_prefix.pop_back(); } for (const verilog::InstantiationProto& instantiation : @@ -106,64 +105,83 @@ void CollectInterfaces( } const verilog::BlockInstantiationProto& block_instantiation = instantiation.block_instantiation(); - auto it = child_signatures.find(block_instantiation.block_name()); - if (it == child_signatures.end()) { - LOG(WARNING) << "instance " << block_instantiation.instance_name() + if (!block_instantiation.has_block_signature()) { + LOG(WARNING) << "Instance " << block_instantiation.instance_name() << " (block " << block_instantiation.block_name() - << ") has no matching child signature; skipping its " + << ") has no embedded child signature; skipping its " "internal channels"; continue; } - std::vector child_scope = scope_prefix; - child_scope.push_back(block_instantiation.instance_name()); - std::vector child_name = name_prefix; - child_name.push_back(block_instantiation.instance_name()); - CollectInterfaces(it->second, child_scope, child_name, child_signatures, - interfaces); + scope_prefix.push_back(block_instantiation.instance_name()); + name_prefix.push_back(block_instantiation.instance_name()); + CollectInterfacesRecursive(block_instantiation.block_signature(), + scope_prefix, name_prefix, interfaces); + scope_prefix.pop_back(); + name_prefix.pop_back(); } } +// Collects ready-valid channels from `signature` (and any embedded child +// signatures) into `interfaces`, scoped under `scope_prefix`. +void CollectInterfaces(const verilog::ModuleSignatureProto& signature, + std::vector& scope_prefix, + std::vector& interfaces) { + std::vector name_prefix; + CollectInterfacesRecursive(signature, scope_prefix, name_prefix, interfaces); +} + void AppendClockResetBlock(const verilog::ModuleSignatureProto& signature, std::vector& lines) { std::string_view clock_name = signature.clock_name().empty() - ? kDefaultClockName - : signature.clock_name(); + ? kDefaultClockName + : signature.clock_name(); std::string_view reset_name = signature.has_reset() ? signature.reset().name() : kDefaultResetName; bool reset_active_low = signature.has_reset() && signature.reset().active_low(); if (signature.clock_name().empty() || !signature.has_reset()) { - LOG(WARNING) << "signature has no clock_name/reset name; each interface " + LOG(WARNING) << "Signature has no clock_name/reset name; each interface " "will need clock/reset filled in by hand"; } + const std::string key_indent = Indent(1); + const std::string field_indent = Indent(2); + lines.push_back("common_clk_rst_ifs:"); - lines.push_back(absl::StrFormat(" %s: &%s", kClockAlias, kClockAlias)); - lines.push_back(absl::StrFormat(" clock: %s", YamlString(clock_name))); - lines.push_back(absl::StrFormat(" reset: %s", YamlString(reset_name))); - lines.push_back(absl::StrFormat(" reset_type: %s", - YamlString(reset_active_low ? "low" - : "high"))); + lines.push_back( + absl::StrFormat("%s%s: &%s", key_indent, kClockAlias, kClockAlias)); + lines.push_back( + absl::StrFormat("%sclock: %s", field_indent, YamlString(clock_name))); + lines.push_back( + absl::StrFormat("%sreset: %s", field_indent, YamlString(reset_name))); + lines.push_back(absl::StrFormat( + "%sreset_type: %s", field_indent, + YamlString(reset_active_low ? "low" : "high"))); } void AppendInterfaceEntry(const BusInterface& interface, std::vector& lines) { - lines.push_back(absl::StrFormat(" %s:", YamlString(interface.name))); + const std::string key_indent = Indent(1); + const std::string field_indent = Indent(2); + + lines.push_back( + absl::StrFormat("%s%s:", key_indent, YamlString(interface.name))); lines.push_back(absl::StrCat( - " scope: [", + field_indent, "scope: [", absl::StrJoin(interface.scope, ", ", [](std::string* out, const std::string& scope_part) { absl::StrAppend(out, YamlString(scope_part)); }), "]")); - lines.push_back(absl::StrFormat(" clk_rst_if: *%s", kClockAlias)); - lines.push_back(""); - lines.push_back(" handshake: \"ReadyValid\""); lines.push_back( - absl::StrFormat(" ready: %s", YamlString(interface.ready_port))); - lines.push_back( - absl::StrFormat(" valid: %s", YamlString(interface.valid_port))); + absl::StrFormat("%sclk_rst_if: *%s", field_indent, kClockAlias)); + lines.push_back(""); + lines.push_back(absl::StrCat(field_indent, "handshake: \"ReadyValid\"")); + lines.push_back(absl::StrFormat("%sready: %s", field_indent, + YamlString(interface.ready_port))); + lines.push_back(absl::StrFormat("%svalid: %s", field_indent, + YamlString(interface.valid_port))); lines.push_back(""); } @@ -171,13 +189,15 @@ void AppendInterfaceEntry(const BusInterface& interface, absl::StatusOr GenerateBusperfYaml( const verilog::ModuleSignatureProto& signature, - absl::Span scope, - const absl::flat_hash_map& - child_signatures) { - std::vector scope_prefix(scope.begin(), scope.end()); + absl::Span scope) { + std::vector lines; + lines.push_back("# Auto-generated by xls_sig_to_busperf from an XLS"); + lines.push_back(absl::StrFormat("# ModuleSignatureProto for module %s.", + YamlString(signature.module_name()))); + + std::vector scope_prefix(scope.begin(), scope.end()); std::vector interfaces; - CollectInterfaces(signature, scope_prefix, /*name_prefix=*/{}, - child_signatures, interfaces); + CollectInterfaces(signature, scope_prefix, interfaces); if (interfaces.empty()) { return absl::InvalidArgumentError( "No CHANNEL_FLOW_CONTROL_READY_VALID channel_interfaces found " @@ -185,10 +205,6 @@ absl::StatusOr GenerateBusperfYaml( "busperf YAML with an empty interfaces: block"); } - std::vector lines; - lines.push_back("# Auto-generated by xls_sig_to_busperf from an XLS"); - lines.push_back(absl::StrFormat("# ModuleSignatureProto for module %s.", - YamlString(signature.module_name()))); AppendClockResetBlock(signature, lines); lines.push_back(""); lines.push_back("interfaces:"); diff --git a/xls/experimental/busperf/busperf_yaml_generator.h b/xls/experimental/busperf/busperf_yaml_generator.h index b25c1d42a3..96d81bdbdd 100644 --- a/xls/experimental/busperf/busperf_yaml_generator.h +++ b/xls/experimental/busperf/busperf_yaml_generator.h @@ -17,32 +17,26 @@ #include -#include "absl/container/flat_hash_map.h" #include "absl/status/statusor.h" #include "absl/types/span.h" #include "xls/codegen/module_signature.pb.h" namespace xls::busperf { -// Generates a busperf (https://github.com/antmicro/busperf) YAML bus -// description from an XLS ModuleSignatureProto. Covers both external interface, -// and internal channels between a proc and any children it spawns. +// Generates a busperf YAML bus description from an XLS ModuleSignatureProto. +// Covers both external interface, and internal channels between a proc +// and any children it spawns. // // Args: // signature: the top-level block's ModuleSignatureProto (codegen_main -// --output_signature_path=...). +// --output_signature_path=...). Child block instantiations carry their +// own signature inline (instantiations().block_instantiation() +// .block_signature()), so this recurses through the whole design. // scope: VCD scope path components leading to the DUT instance, e.g. // {"tb_passthrough", "dut"}. -// child_signatures: standalone signatures for spawned child procs -// (codegen'd without --module_name, so each module_name is the -// mangled block name), keyed by module_name. Matched against the -// parent's instantiations so their channels get included too, scoped -// under `scope` plus their instance_name. absl::StatusOr GenerateBusperfYaml( const verilog::ModuleSignatureProto& signature, - absl::Span scope, - const absl::flat_hash_map& - child_signatures); + absl::Span scope); } // namespace xls::busperf diff --git a/xls/experimental/busperf/busperf_yaml_generator_test.cc b/xls/experimental/busperf/busperf_yaml_generator_test.cc index 66f9e46163..1b74dfdb8e 100644 --- a/xls/experimental/busperf/busperf_yaml_generator_test.cc +++ b/xls/experimental/busperf/busperf_yaml_generator_test.cc @@ -18,12 +18,11 @@ #include #include -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/container/flat_hash_map.h" #include "absl/log/check.h" #include "absl/status/status.h" +#include "gmock/gmock.h" #include "google/protobuf/text_format.h" +#include "gtest/gtest.h" #include "xls/codegen/module_signature.pb.h" #include "xls/common/status/matchers.h" @@ -36,8 +35,8 @@ using ::testing::HasSubstr; verilog::ModuleSignatureProto ParseSignature(std::string_view text) { verilog::ModuleSignatureProto proto; - CHECK(google::protobuf::TextFormat::ParseFromString(std::string(text), - &proto)); + CHECK( + google::protobuf::TextFormat::ParseFromString(std::string(text), &proto)); return proto; } @@ -74,33 +73,31 @@ TEST(BusperfYamlGeneratorTest, FlatDesignEmitsBothChannels) { ParseSignature(kPassthroughSignature); std::vector scope = {"tb_passthrough", "dut"}; - EXPECT_THAT( - GenerateBusperfYaml(signature, scope, /*child_signatures=*/{}), - IsOkAndHolds( - "# Auto-generated by xls_sig_to_busperf from an XLS\n" - "# ModuleSignatureProto for module \"passthrough\".\n" - "common_clk_rst_ifs:\n" - " main_clk: &main_clk\n" - " clock: \"clk\"\n" - " reset: \"rst\"\n" - " reset_type: \"high\"\n" - "\n" - "interfaces:\n" - " \"_data_r\":\n" - " scope: [\"tb_passthrough\", \"dut\"]\n" - " clk_rst_if: *main_clk\n" - "\n" - " handshake: \"ReadyValid\"\n" - " ready: \"_data_r_rdy\"\n" - " valid: \"_data_r_vld\"\n" - "\n" - " \"_data_s\":\n" - " scope: [\"tb_passthrough\", \"dut\"]\n" - " clk_rst_if: *main_clk\n" - "\n" - " handshake: \"ReadyValid\"\n" - " ready: \"_data_s_rdy\"\n" - " valid: \"_data_s_vld\"\n")); + EXPECT_THAT(GenerateBusperfYaml(signature, scope), IsOkAndHolds(R"(# Auto-generated by xls_sig_to_busperf from an XLS +# ModuleSignatureProto for module "passthrough". +common_clk_rst_ifs: + main_clk: &main_clk + clock: "clk" + reset: "rst" + reset_type: "high" + +interfaces: + "_data_r": + scope: ["tb_passthrough", "dut"] + clk_rst_if: *main_clk + + handshake: "ReadyValid" + ready: "_data_r_rdy" + valid: "_data_r_vld" + + "_data_s": + scope: ["tb_passthrough", "dut"] + clk_rst_if: *main_clk + + handshake: "ReadyValid" + ready: "_data_s_rdy" + valid: "_data_s_vld" +)")); } TEST(BusperfYamlGeneratorTest, SkipsNonReadyValidChannels) { @@ -127,9 +124,8 @@ TEST(BusperfYamlGeneratorTest, SkipsNonReadyValidChannels) { } )pb"); - XLS_ASSERT_OK_AND_ASSIGN( - std::string yaml, - GenerateBusperfYaml(signature, {"dut"}, /*child_signatures=*/{})); + XLS_ASSERT_OK_AND_ASSIGN(std::string yaml, + GenerateBusperfYaml(signature, {"dut"})); EXPECT_THAT(yaml, testing::Not(HasSubstr("config"))); EXPECT_THAT(yaml, HasSubstr("\"_data\":")); } @@ -143,29 +139,26 @@ TEST(BusperfYamlGeneratorTest, HierarchicalDesignRecursesIntoChildren) { block_instantiation { block_name: "__hier__Doubler_0_next" instance_name: "__hier__Doubler_0_next_inst" + block_signature { + module_name: "__hier__Doubler_0_next" + channel_interfaces { + channel_name: "_in_ch" + direction: CHANNEL_DIRECTION_RECEIVE + kind: CHANNEL_KIND_STREAMING + streaming { + flow_control: CHANNEL_FLOW_CONTROL_READY_VALID + data_port_name: "_in_ch" + ready_port_name: "_in_ch_rdy" + valid_port_name: "_in_ch_vld" + } + } + } } } )pb"); - verilog::ModuleSignatureProto child = ParseSignature(R"pb( - module_name: "__hier__Doubler_0_next" - channel_interfaces { - channel_name: "_in_ch" - direction: CHANNEL_DIRECTION_RECEIVE - kind: CHANNEL_KIND_STREAMING - streaming { - flow_control: CHANNEL_FLOW_CONTROL_READY_VALID - data_port_name: "_in_ch" - ready_port_name: "_in_ch_rdy" - valid_port_name: "_in_ch_vld" - } - } - )pb"); - absl::flat_hash_map - child_signatures = {{"__hier__Doubler_0_next", child}}; - XLS_ASSERT_OK_AND_ASSIGN( - std::string yaml, - GenerateBusperfYaml(parent, {"tb_hier", "dut"}, child_signatures)); + XLS_ASSERT_OK_AND_ASSIGN(std::string yaml, + GenerateBusperfYaml(parent, {"tb_hier", "dut"})); EXPECT_THAT(yaml, HasSubstr("\"__hier__Doubler_0_next_inst._in_ch\":")); EXPECT_THAT(yaml, HasSubstr("scope: [\"tb_hier\", \"dut\", " "\"__hier__Doubler_0_next_inst\"]")); @@ -179,35 +172,38 @@ TEST(BusperfYamlGeneratorTest, GrandchildRecursionAccumulatesScopeAndName) { clock_name: "clk" reset { name: "rst" } instantiations { - block_instantiation { block_name: "mid" instance_name: "mid_inst" } - } - )pb"); - verilog::ModuleSignatureProto mid = ParseSignature(R"pb( - module_name: "mid" - instantiations { - block_instantiation { block_name: "leaf" instance_name: "leaf_inst" } - } - )pb"); - verilog::ModuleSignatureProto leaf = ParseSignature(R"pb( - module_name: "leaf" - channel_interfaces { - channel_name: "_data" - direction: CHANNEL_DIRECTION_SEND - kind: CHANNEL_KIND_STREAMING - streaming { - flow_control: CHANNEL_FLOW_CONTROL_READY_VALID - data_port_name: "_data" - ready_port_name: "_data_rdy" - valid_port_name: "_data_vld" + block_instantiation { + block_name: "mid" + instance_name: "mid_inst" + block_signature { + module_name: "mid" + instantiations { + block_instantiation { + block_name: "leaf" + instance_name: "leaf_inst" + block_signature { + module_name: "leaf" + channel_interfaces { + channel_name: "_data" + direction: CHANNEL_DIRECTION_SEND + kind: CHANNEL_KIND_STREAMING + streaming { + flow_control: CHANNEL_FLOW_CONTROL_READY_VALID + data_port_name: "_data" + ready_port_name: "_data_rdy" + valid_port_name: "_data_vld" + } + } + } + } + } + } } } )pb"); - absl::flat_hash_map - child_signatures = {{"mid", mid}, {"leaf", leaf}}; - XLS_ASSERT_OK_AND_ASSIGN( - std::string yaml, - GenerateBusperfYaml(top, {"tb_top", "dut"}, child_signatures)); + XLS_ASSERT_OK_AND_ASSIGN(std::string yaml, + GenerateBusperfYaml(top, {"tb_top", "dut"})); EXPECT_THAT(yaml, HasSubstr("\"mid_inst.leaf_inst._data\":")); EXPECT_THAT(yaml, HasSubstr("scope: [\"tb_top\", \"dut\", \"mid_inst\", " "\"leaf_inst\"]")); @@ -215,7 +211,8 @@ TEST(BusperfYamlGeneratorTest, GrandchildRecursionAccumulatesScopeAndName) { } TEST(BusperfYamlGeneratorTest, MissingChildSignatureIsSkippedNotFatal) { - // Unresolved instantiation is skipped; the top's own channel still emits. + // Unresolved instantiation (no embedded block_signature) is skipped; the + // top's own channel still emits. verilog::ModuleSignatureProto parent = ParseSignature(R"pb( module_name: "hier_top" clock_name: "clk" @@ -239,9 +236,8 @@ TEST(BusperfYamlGeneratorTest, MissingChildSignatureIsSkippedNotFatal) { } )pb"); - XLS_ASSERT_OK_AND_ASSIGN( - std::string yaml, - GenerateBusperfYaml(parent, {"dut"}, /*child_signatures=*/{})); + XLS_ASSERT_OK_AND_ASSIGN(std::string yaml, + GenerateBusperfYaml(parent, {"dut"})); EXPECT_THAT(yaml, HasSubstr("\"_data\":")); EXPECT_THAT(yaml, testing::Not(HasSubstr("unresolved_inst"))); } @@ -253,11 +249,10 @@ TEST(BusperfYamlGeneratorTest, NoReadyValidChannelsIsAnError) { reset { name: "rst" } )pb"); - EXPECT_THAT( - GenerateBusperfYaml(signature, {"dut"}, /*child_signatures=*/{}), - StatusIs(absl::StatusCode::kInvalidArgument, - HasSubstr("CHANNEL_FLOW_CONTROL_READY_VALID " - "channel_interfaces found"))); + EXPECT_THAT(GenerateBusperfYaml(signature, {"dut"}), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("CHANNEL_FLOW_CONTROL_READY_VALID " + "channel_interfaces found"))); } } // namespace diff --git a/xls/experimental/busperf/examples/BUILD b/xls/experimental/busperf/examples/BUILD index 6e0c9c80c4..3343ee3336 100644 --- a/xls/experimental/busperf/examples/BUILD +++ b/xls/experimental/busperf/examples/BUILD @@ -36,7 +36,6 @@ xls_dslx_test( xls_busperf_setup( name = "bottleneck_no_stall", - children = ["Passthrough", "SlowConsumerNoStall"], codegen_args = { "clock_period_ps": "10", "materialize_internal_fifos": "true", @@ -58,7 +57,6 @@ xls_busperf_setup( xls_busperf_setup( name = "bottleneck_stall", - children = ["Passthrough", "SlowConsumerStall4"], codegen_args = { "clock_period_ps": "10", "materialize_internal_fifos": "true", diff --git a/xls/experimental/busperf/examples/bottleneck.x b/xls/experimental/busperf/examples/bottleneck.x index 954035b486..6e69cc33d3 100644 --- a/xls/experimental/busperf/examples/bottleneck.x +++ b/xls/experimental/busperf/examples/bottleneck.x @@ -15,10 +15,6 @@ // Shows busperf catching a backpressure bottleneck: Passthrough feeds // SlowConsumer through a depth-4 FIFO, and SlowConsumer<4> stalls it while // SlowConsumer<0> stays healthy. -// -// SlowConsumerNoStall and SlowConsumerStall4 wrap each configuration in a -// non-parametric proc, since xls_busperf_setup's `children` codegen needs a -// concrete name to target and can't convert a bare parametric one. #![feature(type_inference_v2)] @@ -66,27 +62,11 @@ proc SlowConsumer { } } -proc SlowConsumerNoStall { - config(data_r: chan in, data_s: chan out) { - spawn SlowConsumer(data_r, data_s); - } - init { } - next(state: ()) { } -} - -proc SlowConsumerStall4 { - config(data_r: chan in, data_s: chan out) { - spawn SlowConsumer(data_r, data_s); - } - init { } - next(state: ()) { } -} - proc BottleneckNoStall { config(data_r: chan in, data_s: chan out) { let (fifo_s, fifo_r) = chan("fifo"); spawn Passthrough(data_r, fifo_s); - spawn SlowConsumerNoStall(fifo_r, data_s); + spawn SlowConsumer(fifo_r, data_s); } init { } next(state: ()) { } @@ -121,7 +101,7 @@ proc BottleneckStall { config(data_r: chan in, data_s: chan out) { let (fifo_s, fifo_r) = chan("fifo"); spawn Passthrough(data_r, fifo_s); - spawn SlowConsumerStall4(fifo_r, data_s); + spawn SlowConsumer(fifo_r, data_s); } init { } next(state: ()) { } diff --git a/xls/experimental/busperf/tests/BUILD b/xls/experimental/busperf/tests/BUILD index 185de9b78a..8d59e4d78b 100644 --- a/xls/experimental/busperf/tests/BUILD +++ b/xls/experimental/busperf/tests/BUILD @@ -30,7 +30,6 @@ xls_dslx_library( xls_busperf_setup( name = "hier", - children = ["Doubler"], codegen_args = { "clock_period_ps": "10", "module_name": "hier", diff --git a/xls/experimental/busperf/xls_sig_to_busperf_main.cc b/xls/experimental/busperf/xls_sig_to_busperf_main.cc index 769256e9dd..904e8d481c 100644 --- a/xls/experimental/busperf/xls_sig_to_busperf_main.cc +++ b/xls/experimental/busperf/xls_sig_to_busperf_main.cc @@ -15,13 +15,10 @@ #include #include #include -#include #include -#include "absl/container/flat_hash_map.h" #include "absl/flags/flag.h" #include "absl/log/check.h" -#include "absl/log/log.h" #include "absl/status/status.h" #include "absl/strings/str_split.h" #include "xls/codegen/module_signature.pb.h" @@ -32,60 +29,37 @@ #include "xls/experimental/busperf/busperf_yaml_generator.h" static constexpr std::string_view kUsage = R"( -Generates a busperf (https://github.com/antmicro/busperf) YAML bus -description from an XLS ModuleSignatureProto (codegen_main ---output_signature_path=...). +Generates a busperf YAML bus description from an XLS ModuleSignatureProto +(codegen_main --output_signature_path=...). Child block instantiations carry +their own signature inline, so a single top-level signature textproto +is enough to cover the whole design. Usage: xls_sig_to_busperf --scope=tb_top.dut SIGNATURE.textproto - xls_sig_to_busperf --scope=tb_top.dut --output=bus.yaml \ - --child_signature=child_a.sig.textproto,child_b.sig.textproto \ - top.sig.textproto + xls_sig_to_busperf --scope=tb_top.dut --output=bus.yaml top.sig.textproto )"; ABSL_FLAG(std::string, scope, "", "Dot-separated VCD scope path to the DUT instance, e.g. " "'tb_passthrough.dut'. Required."); -ABSL_FLAG(std::vector, child_signature, {}, - "Standalone ModuleSignatureProto textprotos for spawned child " - "blocks (codegen'd with --top= on the same package, " - "without --module_name so each keeps its mangled block name). " - "Matched against instantiations().block_instantiation()" - ".block_name() by the child signature's own module_name."); ABSL_FLAG(std::string, output, "", "Output YAML path. If empty, writes to stdout."); namespace xls::busperf { namespace { -// Parses `signature_path`/`--child_signature` files, generates the busperf -// YAML, and writes it to `--output` (or stdout if unset). +// Parses `signature_path`, generates the busperf YAML, and writes it to +// `--output` (or stdout if unset). absl::Status RealMain(std::string_view signature_path) { verilog::ModuleSignatureProto signature; XLS_RETURN_IF_ERROR(ParseTextProtoFile(signature_path, &signature)); - absl::flat_hash_map - child_signatures; - for (const std::string& child_path : absl::GetFlag(FLAGS_child_signature)) { - verilog::ModuleSignatureProto child_signature; - XLS_RETURN_IF_ERROR(ParseTextProtoFile(child_path, &child_signature)); - const std::string& module_name = child_signature.module_name(); - if (child_signatures.contains(module_name)) { - LOG(WARNING) << "multiple --child_signature files have module_name '" - << module_name << "'; only the last one (" << child_path - << ") will be used"; - } - child_signatures[module_name] = std::move(child_signature); - } - std::string scope_flag = absl::GetFlag(FLAGS_scope); QCHECK(!scope_flag.empty()) << "Must specify --scope"; std::vector scope = absl::StrSplit(scope_flag, '.', absl::SkipEmpty()); - XLS_ASSIGN_OR_RETURN( - std::string yaml, - GenerateBusperfYaml(signature, scope, child_signatures)); + XLS_ASSIGN_OR_RETURN(std::string yaml, GenerateBusperfYaml(signature, scope)); std::string output_path = absl::GetFlag(FLAGS_output); if (output_path.empty()) {