代码都在这里
刚学完AXI-Lite,在用valid/ready
握手在一条链上传递信息的时候,发现每传递一个消息,每一级都需要切换idle
和wait
两个状态,当时就在想能不能砍掉idle
状态,毫无停顿地发下去.学到Double Buffer FIFO的时候,才发现原来可以这样简单地就解决了.
Bubble FIFO
先考虑最简单的Bubble FIFO,每个Buffer只有两种状态:空empty
或满full
. 空就拉高ready
,表示可以接受数据;满就不能再接受数据,拉高valid
,阻塞直至和下一级ready
握手.
flowchart LR e["`**empty** enq.ready=1 deq.valid=0 `"] f["`**full** enq.ready=0 deq.valid=1 `"] e-- enq.valid -->f f-- deq.ready -->e
由于Bubble FIFO只有两种状态,直接用Bool表示即可.
class BubbleFifo[T <: Data](t: T, depth: Int) extends Fifo(t: T, depth: Int){
private class Buffer() extends Module{
val io = IO(new FifoIO(t))
val full = RegInit(false.B)
val dataReg = Reg(t)
when((!full)&io.enq.valid){
dataReg := io.enq.bits
full := true.B
}.elsewhen(full&io.deq.ready){
full := false.B
}
io.enq.ready := !full
io.deq.valid := full
io.deq.bits := dataReg
}
private val buffers = Array.fill(depth) {Module(new Buffer())}
for (i <- 0 until depth-1) {
buffers(i+1).io.enq <> buffers(i).io.deq
}
io.enq <> buffers(0).io.enq
io.deq <> buffers(depth-1).io.deq
}
其中Fifo
是抽象出的基类,所有的FIFO都有一个入队端口enq
和一个出队端口deq
,而且有一个大小depth
的属性.
class FifoIO[T <: Data](t: T) extends Bundle {
val enq = Flipped(new DecoupledIO(t))
val deq = new DecoupledIO(t)
}
abstract class Fifo[T <: Data](val t: T, val depth: Int) extends Module {
val io = IO(new FifoIO(t))
assert(depth > 0)
}
然后分析一下这个的性能.传输过程中可能的波形图大概是这样的(其中第4个时刻buf(2)
没有收到下一级的ready
信号,直到第5个时刻才收到):
可以看到,每个buffer传输一次数据要至少花费两个周期(经历empty
和full
两个状态),所以带宽就是2 cycles/word. chiseltest的结果也是如此
Bubble FIFO就是每次都要在empty
停顿一下,我当初的设计也便是如此,下面介绍不停顿传输的Double Buffer FIFO
Double Buffer FIFO
停顿的根本原因在于,我们无法保证收到数据时下一级一定是ready
状态. 因为如果收到数据时下一级并不ready
,那么下一个周期新收到的数据就会覆盖掉当前buffer
里面的数据,导致数据丢失. 所以在Bubble FIFO中,我们选择当收到数据后便拉低ready
,保证只有当数据成功传递给下一级后,我们再考虑接受新的信息.
然而我们其实可以在收到数据后下仍然拉高ready
,假定下一级一定是ready
状态,便可毫不停顿地一级一级传递下去.但如果下一级并不ready
,而又有新的数据传入,第一个buffer的数据就会被覆盖,数据丢失了. 为了解决这个问题,我们可以增加一个buffer,将新的数据存到第二个buffer里,以防把第一个buffer的数据覆盖,同时还要拉低ready
,阻止新的数据进入,以防第二个buffer的数据也被覆盖. 等到下一级ready
再次拉高,即第一个buffer里面的数据已经传递到下一级后,再将第二个buffer里的数据传入到第一个buffer里(下一级始终读取的是第一个buffer里面的数据).
具体实现时,Double Buffer FIFO便有三个状态,全空empty
,第一个buffer被填满one
,两个buffer都被填满two
flowchart LR empty["`**empty** enq.ready=1 deq.valid=0 `"] one["`**one** enq.ready=1 deq.valid=1 `"] two["`**two** enq.ready=0 dep.valid=1 `"] empty-->|enq.valid|one one-->|!deq.ready&enq.valid|two one-->|deq.ready&!enq.valid|empty two-->|deq.ready|one
代码中,第一个buffer就是dataReg
,第二个buffer就是shadowReg
.
class DoubleBufFifo[T <: Data](t: T, depth: Int) extends Fifo(t: T, depth: Int){
private class Buffer() extends Module{
val io = IO(new FifoIO(t))
val dataReg = Reg(t)
val shadowReg = Reg(t)
object State extends ChiselEnum{
val empty, one, two = Value
}
val stateReg = RegInit(State.empty)
switch(stateReg){
is(State.empty){
when(io.enq.valid) {
stateReg := State.one
dataReg := io.enq.bits
}
}
is(State.one){
when((!io.deq.ready)&(io.enq.valid)){
stateReg := State.two
shadowReg := io.enq.bits
}
when(io.deq.ready&io.enq.valid){
stateReg := State.one
dataReg := io.enq.bits
}
when(io.deq.ready&(!io.enq.valid)){
stateReg := State.empty
}
}
is(State.two){
when(io.deq.ready){
stateReg := State.one
dataReg := shadowReg
}
}
}
io.enq.ready := stateReg =/= State.two
io.deq.valid := stateReg =/= State.empty
io.deq.bits := dataReg
}
private val buffers = Array.fill(depth){Module(new Buffer())}
for(i <- 0 until depth-1){
buffers(i+1).io.enq <> buffers(i).io.deq
}
io.enq <> buffers(0).io.enq
io.deq <> buffers(depth-1).io.deq
}
传输过程中可能的波形图大概是这样(其中第1个周期buf(2)
没有收到下一级的ready
信号,直到第2个周期才收到ready
信号):
可以看到当所有buffer没有阻塞时,可以毫不停顿地传递下去,每个周期都可以传递一个数据,所以带宽就是1 cycles/word.