feat(hdl): Add rocket-chip AsyncQueue and smoke test This commit introduces the `AsyncQueue` from the `chipsalliance/rocket-chip` library to handle asynchronous clock domain crossings. The key changes are: - Added `rocket-chip` and `diplomacy` as external dependencies. - Created a `chisel_library` for the `AsyncQueue` module and its dependencies from `rocket-chip`. - Added `AsyncQueueSmokeTest.scala`, a ChiselSim-based smoke test that verifies the functionality of the `AsyncQueue` by passing a value between two different clock domains. The test also enables VCD waveform dumping for easier debugging. - Updated the `chisel_library` Bazel rule to allow suppressing fatal warnings, which was necessary for the `rocket-chip` library. This provides a robust and tested solution for handling asynchronous FIFOs in the Chisel design. Change-Id: I53ee24a52852ebd49f27a3e2f6792b88a828f978
diff --git a/hdl/chisel/src/common/AsyncQueueSmokeTest.scala b/hdl/chisel/src/common/AsyncQueueSmokeTest.scala new file mode 100644 index 0000000..a32a876 --- /dev/null +++ b/hdl/chisel/src/common/AsyncQueueSmokeTest.scala
@@ -0,0 +1,118 @@ +package common + +import chisel3._ +import chisel3.util._ +import chisel3.simulator.scalatest.ChiselSim +import org.scalatest.freespec.AnyFreeSpec +import freechips.rocketchip.util._ +import chisel3.simulator.scalatest.HasCliOptions +import svsim.verilator.Backend +import svsim.CommonCompilationSettings + +trait WithVcd { this: HasCliOptions => + override implicit def backendSettingsModifications: svsim.BackendSettingsModifications = + (original: svsim.Backend.Settings) => { + original match { + case settings: Backend.CompilationSettings => + settings.copy( + traceStyle = Some( + Backend.CompilationSettings.TraceStyle.Vcd(filename = "trace.vcd") + ) + ) + case other => other + } + } + + override implicit def commonSettingsModifications: svsim.CommonSettingsModifications = + (original: CommonCompilationSettings) => { + original.copy( + simulationSettings = original.simulationSettings.copy(enableWavesAtTimeZero = true) + ) + } +} + +class AsyncQueueSmokeTest extends Module { + val io = IO(new Bundle { + val enq = Flipped(Decoupled(UInt(32.W))) + val deq = Decoupled(UInt(32.W)) + }) + + // Create two asynchronous clock sources from the single test clock + val enq_clock_wire = Wire(Clock()) + val deq_clock_wire = Wire(Clock()) + val enq_reset_wire = Wire(Bool()) + val deq_reset_wire = Wire(Bool()) + + // A simple clock divider to create a slower enqueue clock (half frequency). + val enq_clock_divider = RegInit(false.B) + enq_clock_divider := !enq_clock_divider + enq_clock_wire := enq_clock_divider.asClock + + // A different divider to create an even slower dequeue clock (quarter frequency) + // that is phase-shifted relative to the enqueue clock. + val deq_clock_divider = RegInit(0.U(2.W)) + deq_clock_divider := deq_clock_divider + 1.U + deq_clock_wire := deq_clock_divider(1).asClock + + // Tie resets to the main test harness reset + enq_reset_wire := reset.asBool + deq_reset_wire := reset.asBool + + // safe=false is used for performance in this simple test. For production, + // safe=true is recommended to prevent metastability issues when the queue + // is nearly full or empty. + val queue = Module(new AsyncQueue(UInt(32.W), AsyncQueueParams.singleton(safe = false))) + + queue.io.enq_clock := enq_clock_wire + queue.io.enq_reset := enq_reset_wire + queue.io.deq_clock := deq_clock_wire + queue.io.deq_reset := deq_reset_wire + + queue.io.enq <> io.enq + io.deq <> queue.io.deq +} + +class AsyncQueueSmokeSpec extends AnyFreeSpec with ChiselSim with WithVcd { + "AsyncQueueSmokeTest should pass a value across clock domains" in { + simulate(new AsyncQueueSmokeTest) { dut => + val resetCycles = 2 + val initialDelayCycles = 5 + val enqHoldCycles = 3 + val deqValidTimeoutCycles = 50 + + // Initialize inputs + dut.io.enq.valid.poke(false.B) + dut.io.deq.ready.poke(false.B) + + // Reset the DUT + dut.reset.poke(true.B) + dut.clock.step(resetCycles) + dut.reset.poke(false.B) + dut.clock.step(initialDelayCycles) + + // Enqueue an item + dut.io.enq.valid.poke(true.B) + dut.io.enq.bits.poke(123.U) + dut.io.enq.ready.expect(true.B) // Should be ready immediately after reset + // Hold valid for enough cycles to guarantee a rising edge on the slower enq_clock + dut.clock.step(enqHoldCycles) + dut.io.enq.valid.poke(false.B) + + // Wait for deq.valid to go high, with a timeout + var timeout = deqValidTimeoutCycles + while (!dut.io.deq.valid.peek().litToBoolean && timeout > 0) { + dut.clock.step() + timeout -= 1 + } + if (timeout == 0) { + fail(s"Timed out after ${deqValidTimeoutCycles} cycles waiting for deq.valid to go high") + } + + // Dequeue the item + dut.io.deq.ready.poke(true.B) + dut.io.deq.valid.expect(true.B) + dut.io.deq.bits.expect(123.U) + dut.clock.step() + } + } +} \ No newline at end of file
diff --git a/hdl/chisel/src/common/BUILD b/hdl/chisel/src/common/BUILD index 9bf0d10..df98801 100644 --- a/hdl/chisel/src/common/BUILD +++ b/hdl/chisel/src/common/BUILD
@@ -45,6 +45,7 @@ ], deps = [ ":library", + "@chipsalliance_rocket_chip//:asyncqueue", ], ) @@ -214,3 +215,12 @@ ], visibility = ["//visibility:public"], ) + +chisel_test( + name = "async_queue_smoke_test", + srcs = ["AsyncQueueSmokeTest.scala"], + deps = [ + ":common", + "@chipsalliance_rocket_chip//:asyncqueue", + ], +)
diff --git a/rules/chisel.bzl b/rules/chisel.bzl index ee5485b..ff49f00 100644 --- a/rules/chisel.bzl +++ b/rules/chisel.bzl
@@ -29,7 +29,6 @@ "-Xcheckinit", "-Xlint:infer-any", "-Xlint:unused", - "-Xfatal-warnings", ] def chisel_library( @@ -39,7 +38,11 @@ exports = [], resources = [], resource_strip_prefix = "", - visibility = None): + visibility = None, + allow_warnings = False): + warn_opts = [] + if not allow_warnings: + warn_opts += ["-Xfatal-warnings"] scala_library( name = name, srcs = srcs, @@ -50,7 +53,7 @@ resources = resources, resource_strip_prefix = resource_strip_prefix, exports = exports, - scalacopts = SCALA_COPTS, + scalacopts = SCALA_COPTS + warn_opts, visibility = visibility, )
diff --git a/rules/repos.bzl b/rules/repos.bzl index 4b18ee6..803b891 100644 --- a/rules/repos.bzl +++ b/rules/repos.bzl
@@ -102,6 +102,24 @@ build_file = "@kelvin_hw//third_party/libsystemctlm-soc:BUILD.bazel", ) + http_archive( + name = "chipsalliance_rocket_chip", + build_file = "@kelvin_hw//third_party/rocket_chip:BUILD.bazel", + urls = ["https://github.com/chipsalliance/rocket-chip/archive/f517abbf41abb65cea37421d3559f9739efd00a9.zip"], + sha256 = "e77bb13328e919ca43ba83a1c110b5314900841125b9ff22813a4b9fe73672a2", + strip_prefix = "rocket-chip-f517abbf41abb65cea37421d3559f9739efd00a9", + ) + + http_archive( + name = "chipsalliance_diplomacy", + urls = ["https://github.com/chipsalliance/diplomacy/archive/6590276fa4dac315ae7c7c01371b954c5687a473.zip"], + sha256 = "3f536b2eba360eb71a542d2a201eabe3a45cfa86302f14d1d565def0ed43ee20", + strip_prefix = "diplomacy-6590276fa4dac315ae7c7c01371b954c5687a473", + build_file_content = """ +exports_files(["diplomacy/src/diplomacy/nodes/HeterogeneousBag.scala"]) + """, + ) + def renode_repos(): http_archive( name = "renode", @@ -171,4 +189,4 @@ "@kelvin_hw//fpga:0001-Export-hw-ip_templates.patch", ], patch_args = ["-p1"], - ) \ No newline at end of file + )
diff --git a/third_party/rocket_chip/BUILD.bazel b/third_party/rocket_chip/BUILD.bazel new file mode 100644 index 0000000..3545c30 --- /dev/null +++ b/third_party/rocket_chip/BUILD.bazel
@@ -0,0 +1,32 @@ +# Copyright 2025 Google LLC +# +# 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("@kelvin_hw//rules:chisel.bzl", "chisel_library") + +package(default_visibility = ["//visibility:public"]) + +chisel_library( + name = "asyncqueue", + srcs = [ + "@chipsalliance_diplomacy//:diplomacy/src/diplomacy/nodes/HeterogeneousBag.scala", + "@chipsalliance_rocket_chip//:src/main/scala/util/AsyncQueue.scala", + "@chipsalliance_rocket_chip//:src/main/scala/util/AsyncResetReg.scala", + "@chipsalliance_rocket_chip//:src/main/scala/util/Counters.scala", + "@chipsalliance_rocket_chip//:src/main/scala/util/Crossing.scala", + "@chipsalliance_rocket_chip//:src/main/scala/util/ShiftReg.scala", + "@chipsalliance_rocket_chip//:src/main/scala/util/SynchronizerReg.scala", + "@chipsalliance_rocket_chip//:src/main/scala/util/package.scala", + ], + allow_warnings = True, +)