一、Stream类简介
SpinalHDL的lib中提供了一个Stream类,这个类继承自SpinalHDL的Bundle类,支持Master/Slave,携带数据,因此主要包括三个信号:valid、ready、payload。
这三个信号可以构成最简单的握手接口,满足大部分情况下的module间交互。接下来介绍其中的一些方法,与Verilog中写简单握手的方法一一对应。
1.1 Stream的方向定义
val io = new Bundle{
val data_in = slave Stream(UInt(8 bits))
val data_out = master Stream(UInt(8 bits))
}
上图为一个流水级寄存器的io信号示例,data_in为模块输入,则对于简单握手协议来说本模块是接收数据的slave,因此data_in作为一个Stream类包含的三个信号及其方向分别为:valid(in)、ready(out)、payload(in)。
反之,data_out为模块输出,其包含的三个信号及其方向分别为:valid(out)、ready(in)、payload(out)。
1.2 Stream实现简单握手时序
一个典型的简单握手时序如图所示,上级的valid与payload同时产生,直至下级ready拉高,实现上级valid与下级ready同时为高时握手成功。valid只有在握手成功的下一拍才允许拉低,若握手失败,则valid需保持为高。
在SpinalHDL的document中,给出了上述的几种连接方式。那么在SpinalHDL中,data_in和data_out应如何连接才能实现上图的握手时序呢?
由于要实现寄存器打拍的效果,那么必须选择latency为1的连接方式。①和③两种latency为0的连接方式显然不能满足。其中x << y或y >> x就相当于是使用Verilog中的assign赋值语句。
而②可以实现打拍的连接方式,并且自动完成了本级valid、payload的时序逻辑赋值和ready的组合逻辑赋值。<-<看似一个赋值保留字,其实是Stream的一个方法:
/*Connect that to this. The valid/payload/ready path are cut by an register stage */
/* Connect that to this. The valid/payload path are cut by an register stage */
def <-<(that: Stream[T]): Stream[T] = {
this << that.stage()
that
}
/* Connect this to that. The valid/payload path are cut by an register stage */
def >->(into: Stream[T]): Stream[T] = {
into <-< this
into
}
顺藤摸瓜,继续看看源码中的stage方法:
/** Connect this to a valid/payload register stage and return its output stream
*/
def stage() : Stream[T] = this.m2sPipe()
//! if collapsBubble is enable then ready is not "don't care" during valid low !
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
}
可以发现,最后摸到了m2sPipe方法,这对应了表格中描述的connect y to x through a m2sPipe.
该方法返回的是一个Stream对象,其中valid和payload被定义为了Reg类型,并且只有在下级ready拉高时,才会将上级valid与payload赋值给下级。而下级ready的赋值逻辑则有两种情况:① 下级valid为低,即下级在当前拍处于无数据状态;② 下下级ready为高,代表下级在下一拍可以接受新数据。
1.3 若valid的赋值条件不仅仅是握手成功
在一些情况中,下级valid的赋值条件不仅仅是上级与下级握手成功,还有一些特定的逻辑。例如多对一握手与一对多握手,可能需要改变valid和ready的赋值条件。那么,也可以使用Stream中的方法来实现:
/** Block this when cond is False. Return the resulting stream */
def continueWhen(cond: Bool): Stream[T] = {
val next = new Stream(payloadType)
next.valid := this.valid && cond
this.ready := next.ready && cond
next.payload := this.payload
return next
}
使用示例如下:
data_out <-< data_in.continueWhen(cond)
1.4 若payload经过组合逻辑而非直接向下传递
在流水线结构中,两个流水级寄存器之间通常存在组合逻辑,实现特定的功能。那么此时两个模块之间虽然valid-ready信号还是正常传递,但payload就不是简单地将上级传递来的payload向下传递了。这种情况恐怕是最常见的,一开始在document中没有找到合适的方法,差点弃用了Stream。
但是,阅读源码之后发现了一个可以实现这种需求的方法:
/** Replace this stream's payload with another one */
def translateWith[T2 <: Data](that: T2): Stream[T2] = {
val next = new Stream(that).setCompositeName(this, "translated", true)
next.arbitrationFrom(this)
next.payload := that
next
}
示例如下:
data_out <-< data_in.translateWith(anotherData)
原理是valid与ready仍然按照m2sPipe方法传递,而payload则传递另外的data,如经过组合逻辑之后的data。
至此,本文梳理了有关SpinalHDL lib中Stream的部分方法,用来实现常用的简单握手。上文介绍的方法已经可以覆盖许多简单握手的使用场景,但是对Stream的挖掘依然远远不够。SpinalHDL lib中还有许多有用的类可以使用,关键是在阅读document之外,多多阅读源码,会有不错的发现!