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/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"))); diff --git a/xls/experimental/busperf/BUILD b/xls/experimental/busperf/BUILD new file mode 100644 index 0000000000..03e83a8fde --- /dev/null +++ b/xls/experimental/busperf/BUILD @@ -0,0 +1,70 @@ +# 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/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/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/flags:flag", + "@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..7752460c21 --- /dev/null +++ b/xls/experimental/busperf/build_rules/busperf_macros.bzl @@ -0,0 +1,164 @@ +# 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, + 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. + 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. + 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"], + ) + + busperf_yaml( + name = name + "_bus_yaml", + signature = ":" + name + ".sig.textproto", + 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 = {}, + 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. + 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 + 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, + 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..dd69edb125 --- /dev/null +++ b/xls/experimental/busperf/build_rules/busperf_yaml_rules.bzl @@ -0,0 +1,57 @@ +# 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) + args.add("--output", out) + + ctx.actions.run( + executable = ctx.executable.xls_sig_to_busperf, + arguments = [args], + inputs = [ctx.file.signature], + 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, + ), + "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..522e351623 --- /dev/null +++ b/xls/experimental/busperf/busperf_yaml_generator.cc @@ -0,0 +1,218 @@ +// 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 + +#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 "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; +} + +// 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; + 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 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)) { + name_prefix.push_back(channel_interface->channel_name()); + interfaces.push_back(BusInterface{ + .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 : + 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(); + if (!block_instantiation.has_block_signature()) { + LOG(WARNING) << "Instance " << block_instantiation.instance_name() + << " (block " << block_instantiation.block_name() + << ") has no embedded child signature; skipping its " + "internal channels"; + continue; + } + 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(); + 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"; + } + + 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: &%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) { + 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( + 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("%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(""); +} + +} // namespace + +absl::StatusOr GenerateBusperfYaml( + const verilog::ModuleSignatureProto& signature, + 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, 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"); + } + + 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..96d81bdbdd --- /dev/null +++ b/xls/experimental/busperf/busperf_yaml_generator.h @@ -0,0 +1,43 @@ +// 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/status/statusor.h" +#include "absl/types/span.h" +#include "xls/codegen/module_signature.pb.h" + +namespace xls::busperf { + +// 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=...). 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"}. +absl::StatusOr GenerateBusperfYaml( + const verilog::ModuleSignatureProto& signature, + absl::Span scope); + +} // 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..1b74dfdb8e --- /dev/null +++ b/xls/experimental/busperf/busperf_yaml_generator_test.cc @@ -0,0 +1,259 @@ +// 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/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" + +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), 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) { + 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"})); + 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" + 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"); + + 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\"]")); + 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" + 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"); + + 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\"]")); + EXPECT_THAT(yaml, HasSubstr("valid: \"_data_vld\"")); +} + +TEST(BusperfYamlGeneratorTest, MissingChildSignatureIsSkippedNotFatal) { + // 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" + 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"})); + 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"}), + 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..3343ee3336 --- /dev/null +++ b/xls/experimental/busperf/examples/BUILD @@ -0,0 +1,77 @@ +# 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", + 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", + 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..6e69cc33d3 --- /dev/null +++ b/xls/experimental/busperf/examples/bottleneck.x @@ -0,0 +1,133 @@ +// 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. + +#![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 BottleneckNoStall { + config(data_r: chan in, data_s: chan out) { + let (fifo_s, fifo_r) = chan("fifo"); + spawn Passthrough(data_r, fifo_s); + spawn SlowConsumer(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 SlowConsumer(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..8d59e4d78b --- /dev/null +++ b/xls/experimental/busperf/tests/BUILD @@ -0,0 +1,55 @@ +# 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", + 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..904e8d481c --- /dev/null +++ b/xls/experimental/busperf/xls_sig_to_busperf_main.cc @@ -0,0 +1,82 @@ +// 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 "absl/flags/flag.h" +#include "absl/log/check.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 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 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::string, output, "", + "Output YAML path. If empty, writes to stdout."); + +namespace xls::busperf { +namespace { + +// 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)); + + 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)); + + 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])); +}