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