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,
+)