在逻辑设计里,握手信号是我们经常要处理的,而如果设计中对于这种握手信号我们想在中间加一级寄存器缓存该诸君可知该怎么处理么~
夹缝中打拍
有经验的小伙伴对于这种握手信号打拍相信都较为熟悉,其关键点在保障接口时序的同时又避免带宽损失。对于握手信号,我们可以抽象为一个master Stream端口,一个slave Stream端口。那么这里添加寄存器缓存的形式就有三种了:
- master——>slave方向添加寄存器。
- slave——>master方向添加寄存器。
- slave<——>master双向添加寄存器。
看下SpinalHDL里给我们提供了哪些方法:
master——>slave
在SpinalHDL里,master——>slave方向添加寄存器寄存器调用的函数为m2sPipe,看下这里的源代码:
def m2sPipe(collapsBubble : Boolean = true,crossClockData: Boolean = false, flush : Bool = null): Stream[T] = {
val ret = Stream(payloadType).setCompositeName(this, "m2sPipe", true)
val rValid = RegInit(False).setCompositeName(this, "m2sPipe_rValid", true)
val rData = Reg(payloadType).setCompositeName(this, "m2sPipe_rData", true)
if (crossClockData) rData.addTag(crossClockDomain)
this.ready := (Bool(collapsBubble) && !ret.valid) || ret.ready
when(this.ready) {
rValid := this.valid
rData := this.payload
}
if(flush != null) rValid clearWhen(flush)
ret.valid := rValid
ret.payload := rData
ret
}
这里在Stream的master到slave打拍为valid、payload添加了一级寄存器,而重点是处理ready信号。当不允许气泡拼接时(collapsBubble\=false),此时:
this.ready := ret.ready
设计里将slave的ready信号直接赋值给master端口,而此时会在握手时产生气泡,降低传输效率。
而当允许气泡拼接时,此时:
this.ready := ret.ready||(!ret.valid)
此时由于为valid缓存了一拍,因此master的ready信号需考虑slave的信号端口情况:
- 当slave端口的valid信号为低时,意味着此时内部的一级缓存可以接收新的数据,因此master端口的ready信号可以可以拉起。
- 当slave端口valid为高且ready信号也为高电平时意味着缓存的一拍数据这一拍已经消耗了,master因而可以输入新一拍数据。
slave——>master
slave到master通路之间的寄存器打拍主要为ready方向,在SpinalHDL里,源代码为:
def s2mPipe(): Stream[T] = {
val ret = Stream(payloadType).setCompositeName(this, "s2mPipe", true)
val rValid = RegInit(False).setCompositeName(this, "s2mPipe_rValid", true)
val rBits = Reg(payloadType).setCompositeName(this, "s2mPipe_rData", true)
ret.valid := this.valid || rValid
this.ready := !rValid
ret.payload := Mux(rValid, rBits, this.payload)
when(ret.ready) {
rValid := False
}
when(this.ready && (!ret.ready)) {
rValid := this.valid
rBits := this.payload
}
ret
}
这里在master与slave之间的ready路径上添加一级寄存器,为避免气泡,因此在实现中会有一拍数据的缓存,由于master端的ready信号与!rValid信号相连,而rValid信号初始值为False,因此无论slave端是否ready拉起,此时内部均能缓存一拍数据,从而避免气泡,充分利用带宽。
slave<——>master
有了前两个的基础,这里在master与slave之间所有路径都添加寄存器就简单明了了:
/** Connect that to this. The valid/payload/ready path are cut by an register stage
*/
def <-/<(that: Stream[T]): Stream[T] = {
this << that.s2mPipe.m2sPipe()
that
}
可以看到,这里先对ready路径进添加寄存器,随后在valid、payload路径上添加一级寄存器来实现mater与slave中所有路径均添加寄存器缓存。
而无论是mater到slave,还是slave到master,SpinalHDL里都可以抽象成运算符般的操作让我们的代码更具备可读性。
写在最后
可以看到,在SpinalHDL里,我们在描述的仍旧是电路,而SpinalHDL能够将电路更进一步的抽象化,在描述电路时能让我们随时随地像调IP似的调用lib,代码如软件版可读与优雅,何乐而不为呢。
作者:玉骐
原文链接:https://mp.weixin.qq.com/s/6y1wdLD3HPlEEIND6Eo4zQ
微信公众号:
推荐阅读
更多SpinalHDL技术干货请关注Spinal FPGA专栏。