blob: a32a876a0207859734b8f1306cc9fe2e167fe02c [file] [log] [blame]
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()
}
}
}