Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Async API #1724

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 47 additions & 12 deletions src/main/scala/chisel3/util/Reg.scala
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,7 @@ object ShiftRegister
* val regDelayTwo = ShiftRegister(nextVal, 2, ena)
* }}}
*/
def apply[T <: Data](in: T, n: Int, en: Bool = true.B): T = {
// The order of tests reflects the expected use cases.
if (n != 0) {
RegEnable(apply(in, n-1, en), en)
} else {
in
}
}
def apply[T <: Data](in: T, n: Int, en: Bool = true.B): T = ShiftRegisters(in, n, en).last

/** Returns the n-cycle delayed version of the input signal with reset initialization.
*
Expand All @@ -62,12 +55,54 @@ object ShiftRegister
* val regDelayTwoReset = ShiftRegister(nextVal, 2, 0.U, ena)
* }}}
*/
def apply[T <: Data](in: T, n: Int, resetData: T, en: Bool): T = {
// The order of tests reflects the expected use cases.
def apply[T <: Data](in: T, n: Int, resetData: T, en: Bool): T = ShiftRegisters(in, n, resetData, en).last
}


object ShiftRegisters
{
/** Returns a sequence of delayed input signal registers from 1 to n.
*
* @param in input to delay
* @param n number of cycles to delay
* @param en enable the shift
*
*/
def apply[T <: Data](in: T, n: Int, en: Bool = true.B): Seq[T] = {
if (n != 0) {
val rs = Seq.fill(n)(Reg(chiselTypeOf(in)))
when(en) {
rs.foldLeft(in)((in, out) => {
out := in
out
})
}
rs
} else {
Seq(in)
}
}

/** Returns delayed input signal registers with reset initialization from 1 to n.
*
* @param in input to delay
* @param n number of cycles to delay
* @param resetData reset value for each register in the shift
* @param en enable the shift
*
*/
def apply[T <: Data](in: T, n: Int, resetData: T, en: Bool): Seq[T] = {
if (n != 0) {
RegEnable(apply(in, n-1, resetData, en), resetData, en)
val rs = Seq.fill(n)(RegInit(chiselTypeOf(in), resetData))
when(en) {
rs.foldLeft(in)((in, out) => {
out := in
out
})
}
rs
} else {
in
Seq(in)
}
}
}
190 changes: 190 additions & 0 deletions src/main/scala/chisel3/util/experimental/crossing/AsyncQueue.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package chisel3.util.experimental.crossing

import chisel3._
import chisel3.util._
import chisel3.util.experimental.group

class AsyncEnqueueIO[T <: Data](gen: T) extends Bundle {
val clock: Clock = Input(Clock())
val reset: Bool = Input(Bool())
val source: DecoupledIO[T] = Flipped(Decoupled(gen))
}

class AsyncDequeueIO[T <: Data](gen: T) extends Bundle {
val clock: Clock = Input(Clock())
val reset: Bool = Input(Bool())
val sink: DecoupledIO[T] = Decoupled(gen)
}

/** Memory used for queue.
* In ASIC, it will be synthesised to Flip-Flop
*
* if [[narrow]]: one write port, one read port.
*
* if not [[narrow]]: one write port, multiple read port.
* ASIC will synthesis it to Flip-Flop
* not recommend for FPGA
*/
class DataMemory[T <: Data](gen: T, depth: Int, narrow: Boolean) extends RawModule {
val dataQueue: SyncReadMem[T] = SyncReadMem(depth, gen)

// write IO
val writeEnable: Bool = IO(Input(Bool()))
val writeData: T = IO(Input(gen))
val writeIndex: UInt = IO(Input(UInt(log2Ceil(depth).W)))
when(writeEnable)(dataQueue.write(writeIndex, writeData))

// read IO
val readEnable: Bool = IO(Input(Bool()))

// narrow read IO
val readDataAndIndex: Option[(T, UInt)] = if (narrow) Some((
IO(Output(gen)).suggestName("data"),
IO(Input(UInt(log2Ceil(depth).W))).suggestName("index")
)) else None
readDataAndIndex.foreach { case (readData, readIndex) => readData := dataQueue.read(readIndex) }

// broad read IO
val fullReadData: Option[Vec[T]] = if (narrow) None else Some(IO(Output(Vec(depth, gen))))
fullReadData match {
case Some(fullData) => fullData.zipWithIndex.map { case (data, index) => data := dataQueue.read(index.U) }
case _ =>
}
}

/** Sink of [[AsyncQueue]] constructor.
*
* @tparam T Hardware type to be converted.
* @note SRAM-based clock-domain-crossing source.
*/
class AsyncQueueSink[T <: Data](gen: T, depth: Int, sync: Int, narrow: Boolean = true) extends MultiIOModule {
require(depth > 0 && isPow2(depth), "todo")
require(sync >= 2, "todo")
private val depthWidth: Int = log2Ceil(depth)
/** Dequeue Decoupled IO. */
val dequeue: DecoupledIO[T] = IO(Decoupled(gen))

val readIndexGray: UInt = withReset(reset.asAsyncReset())(grayCounter(depthWidth + 1, dequeue.fire(), false.B, "readIndex"))
val readIndexGrayReg: UInt = withReset(reset.asAsyncReset())(RegNext(next = readIndexGray, init = 0.U).suggestName("readIndexReg"))
val writeIndexGray: UInt = IO(Input(UInt((depthWidth + 1).W)))

/** ready signal to indicate [[DecoupledIO]] this queue is not empty, can still dequeue new data. */
val empty: Bool = readIndexGray === writeIndexGray
val valid: Bool = !empty
val validReg: Bool = withReset(reset.asAsyncReset())(RegNext(next = valid, init = false.B).suggestName("validReg"))

// dequeue to [[DecoupledIO]]
dequeue.valid := validReg

// port to access memory
val readEnable: Bool = IO(Output(Bool()))
readEnable := valid

val readDataAndIndex: Option[(T, UInt)] = if (narrow) Some((
IO(Input(gen)).suggestName("data"),
IO(Output(UInt(log2Ceil(depth).W))).suggestName("index")
)) else None
readDataAndIndex.foreach {
case (data, index) =>
dequeue.bits := data
index := readIndexGray(depthWidth, 0)
}

// This register does not NEED to be reset, as its contents will not
// be considered unless the asynchronously reset deq valid, register is set.
// It is possible that bits latches when the source domain is reset / has power cut
// This is safe, because isolation gates brought mem low before the zeroed [[writeIndex]] reached us.

val fullReadData: Option[Vec[T]] = if (narrow) None else Some(IO(Input(Vec(depth, gen))))
fullReadData.foreach(fullData => dequeue.bits := fullData(readIndexGray(depthWidth, 0)))
}

/** Source of [[AsyncQueue]] constructor.
*
* @tparam T Hardware type to be converted.
* @todo make sync optional, if None use async logic.
* add some verification codes.
*/
class AsyncQueueSource[T <: Data](gen: T, depth: Int, sync: Int) extends MultiIOModule {
require(depth > 0 && isPow2(depth), "todo")
require(sync >= 2, "todo")
private val depthWidth: Int = log2Ceil(depth)
/** Enqueue Decoupled IO. */
val enqueue: DecoupledIO[T] = IO(Flipped(Decoupled(gen)))

val writeIndexGray: UInt = withReset(reset.asAsyncReset())(grayCounter(depthWidth + 1, enqueue.fire(), false.B, "writeIndex"))
val writeIndexGrayReg: UInt = withReset(reset.asAsyncReset())(RegNext(next = writeIndexGray, init = 0.U).suggestName("writeIndexReg"))
val readIndexGray: UInt = IO(Input(UInt((depthWidth + 1).W)))

/** ready signal to indicate [[DecoupledIO]] this queue is not full, can still enqueue new data. */
val full: Bool = writeIndexGray === (readIndexGray ^ (depth | depth >> 1).U)
val ready: Bool = !full
val readyReg: Bool = withReset(reset.asAsyncReset())(RegNext(next = ready, init = false.B).suggestName("readyReg"))

// enqueue from [[DecoupledIO]]
enqueue.ready := readyReg

// port to access memory
val writeEnable: Bool = IO(Output(Bool()))
writeEnable := enqueue.fire()
val writeData: T = IO(Output(gen))
writeData := enqueue.bits
val writeIndex: UInt = IO(Output(UInt(log2Ceil(depth).W)))
writeIndex := writeIndexGrayReg(depthWidth, 0)
}

/** cross-clock-domain syncing asynchronous queue.
*
* @note [[AsyncQueueSource.writeIndexGray]] and [[AsyncQueueSink.readIndexGray]] will be synced to each other.
* both of these use a dual-gray-counter for index and empty/full detecting.
* index has `depth + 1` size
* {{{
* ramIndex := index(depth, 0)
* full := writeIndex === (readIndex ^ (depth | depth >> 1).U)
* empty := writeIndex === readIndex
* }}}
*
*/
class AsyncQueue[T <: Data](gen: T, depth: Int, sync: Int, narrow: Boolean) extends MultiIOModule {
val enqueue: AsyncEnqueueIO[T] = IO(new AsyncEnqueueIO(gen))
val sourceModule: AsyncQueueSource[T] =
withClockAndReset(enqueue.clock, enqueue.reset)(Module(new AsyncQueueSource(gen, depth, sync)))
sourceModule.enqueue <> enqueue.source

val dequeue: AsyncDequeueIO[T] = IO(new AsyncDequeueIO(gen))
val sinkModule: AsyncQueueSink[T] =
withClockAndReset(enqueue.clock, enqueue.reset)(Module(new AsyncQueueSink(gen, depth, sync, narrow)))
dequeue.sink <> sinkModule.dequeue

// read/write index bidirectional sync
sourceModule.readIndexGray :=
withClockAndReset(enqueue.clock, enqueue.reset.asAsyncReset()) {
val shiftRegisters = ShiftRegisters(sinkModule.readIndexGray, sync)
group(shiftRegisters, s"AsyncResetSynchronizerShiftReg_w${sinkModule.readIndexGray.width}_d$sync", "syncReadIndexGray")
shiftRegisters.last
}
sinkModule.writeIndexGray :=
withClockAndReset(dequeue.clock, dequeue.reset.asAsyncReset()) {
val shiftRegisters = ShiftRegisters(sourceModule.writeIndexGray, sync)
group(shiftRegisters, s"AsyncResetSynchronizerShiftReg_w${sourceModule.readIndexGray.width}_d$sync", "syncReadIndexGray")
shiftRegisters.last
}

val memoryModule: DataMemory[T] = withClock(dequeue.clock)(Module(new DataMemory(gen, depth, narrow)))
memoryModule.writeEnable := sourceModule.writeEnable
memoryModule.writeData := sourceModule.writeData
memoryModule.writeIndex := sourceModule.writeIndex
memoryModule.readEnable := sinkModule.readEnable
(memoryModule.readDataAndIndex zip sinkModule.readDataAndIndex).foreach {
case ((memoryData, memoryIndex), (sinkData, sinkIndex)) =>
sinkData := withClock(dequeue.clock)(RegNext(memoryData))
memoryIndex := sinkIndex
case _ =>
}

(memoryModule.fullReadData zip sinkModule.fullReadData).foreach {
case (memoryFullData, sinkFullData) =>
sinkFullData := withClock(dequeue.clock)(RegNext(memoryFullData))
case _ =>
}
}
42 changes: 42 additions & 0 deletions src/main/scala/chisel3/util/experimental/crossing/package.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package chisel3.util.experimental

import chisel3._
import chisel3.experimental.requireIsHardware
import chisel3.util.DecoupledIO

package object crossing {

implicit class DecoupledToAsyncIO[T <: DecoupledIO[Data]](target: T) {
def asyncDequeue(clock: Clock, reset: Reset): AsyncDequeueIO[T] = {
requireIsHardware(target)
requireIsHardware(clock)
requireIsHardware(reset)
require(target.valid.direction == ActualDirection.Output)
val io = Wire(new AsyncDequeueIO(target))
io.clock := clock
io.reset := clock
io.sink <> target
io
}

def asyncEnqueue(clock: Clock, reset: Reset): AsyncEnqueueIO[T] = {
requireIsHardware(target)
requireIsHardware(clock)
requireIsHardware(reset)
require(target.valid.direction == ActualDirection.Input)
val io = Wire(new AsyncEnqueueIO(target))
io.clock := clock
io.reset := clock
io.source <> target
io
}
}

private[crossing] def grayCounter(bits: Int, increment: Bool = true.B, clear: Bool = false.B, name: String = "binary"): UInt = {
val incremented = Wire(UInt(bits.W))
val binary = RegNext(next = incremented, init = 0.U).suggestName(name)
incremented := Mux(clear, 0.U, binary + increment.asUInt())
incremented ^ (incremented >> 1)
}

}
18 changes: 18 additions & 0 deletions src/test/scala/chiselTests/Reg.scala
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,21 @@ class ShiftRegisterSpec extends ChiselPropSpec {
forAll(smallPosInts) { (shift: Int) => assertTesterPasses{ new ShiftResetTester(shift) } }
}
}

class ShiftsTester(n: Int) extends BasicTester {
val (cntVal, done) = Counter(true.B, n)
val start = 23.U
val srs = ShiftRegisters(cntVal + start, n)
when(RegNext(done)) {
srs.zipWithIndex.foreach{ case (data, index) =>
assert(data === (23 + n - 1 - index).U)
}
stop()
}
}

class ShiftRegistersSpec extends ChiselPropSpec {
property("ShiftRegisters should shift") {
forAll(smallPosInts) { (shift: Int) => assertTesterPasses{ new ShiftsTester(shift) } }
}
}