无论是Amba总线还是其他类总线,握手与分发总是无处不在。在SpinalHDL中,Stream的抽象提取个人认为绝对是一个精彩的操作。SpinalHDL的lib中针对Stream也提供了大量的组件。今天来看下StreamFork。
StreamFork
Fork在Verilog/Systemerilog中也是很常见的,顾名思义,StreamFork用于向多个端口发起任务:
在逻辑实现里并不少见。比如一个控制模块在收到指令后需要向多个子模块发起指令,只有在子模块完成响应后才能够给上游响应。这种代码写起来也不难,当然也可以自己抽象。而SpinalHDL借助Scala将此类场景完美的给抽象成为了一个标准的组件:StreamFork。不得不说,StreamFork在我的日常设计里还是随处可见的。
先来看看StreamFork的声明:
class StreamFork[T <: Data](dataType: HardType[T], portCount: Int, synchronous: Boolean = false) extends Component
其例化参数有三个:
前两个参数都非常好理解,这里我们来着重关注第三个参数synchronous。整个模块的端口列表为:
先来看看synchronous为true时的实现:
当synchronous为true时,此时input端口的ready在所有output端口的ready信号均为高时才置为高,而所有output端口在input端口任务fire的时候端口的valid才拉高,这种方式保障了所有output端口的valid均在同一个cycle时刻才会拉高。也就保证了“synchronous”。这里有一点值得注意:在synchronous模式下,从每个output port端口上来看,其valid的拉高的前提条件是ready信号必须为高点平,而ready信号是下游的从端给的,而很多协议里往往有一个要求是master端valid信号的拉高条件判断不应依赖于slave端的状态,这种场景下使用synchronous可能会导致接口的死锁。
而当synchronous为false时,其实现形式为:
在这里,可以看到为每个output端口配置了一个寄存器linkEnable用于标记该端口任务是否已经触发。linkEnable初始化时为高点平,而每当对应的端口消耗一个任务时,其将会拉低。当input来一个任务时(valid拉高),每个从端口valid只有在自己的linkenable为高时其valid方拉起,也就确保了每个output端口不会一个任务拉多个cycle valid。而对于input端口ready信号而言,默认为高点平。当input valid拉起时,其ready信号取决于每个output端口的ready信号和ready信号。当linkEnable为高点平时,表明该端口任务尚未消耗,此时ready为低时那么也将input ready信号拉低。而当input ready信号为高点平时,所有的linkEnbale均再次拉高,以处理下一次的任务。如此当上游input端口来一个任务时,output端口是异步消耗的,直到所有的output端口均处理完任务后input ready方响应上游接口。
看完上面的代码其实可以发现,StreamFork中所有的端口的输出其实都是组合逻辑,而之前也曾讲过,针对握手信号,SpinalHDL将功能实现和时序改善分开来做个人一直觉得是一个非常好的实现方式。若想在整体系统中跑较高的频率完全可以自主插入寄存器打拍,可参照这篇文章:
除了类声明中的例化参数模式,SpinalHDL针对StreamFork也提供了如下的例化方式:
StreamFork妙用
众所周知,像Xilinx设计中很多IP的设计都是基于Axi4总线的。如果让你实现一个Axi4Lite Master模块的RTL设计你该怎么去做呢?
将其他总线转换为Axi4Lite Master总线常见的设计往往采用状态机的形式,很常见也很简单。但仔细想象Axi4Lite总线其实是5个通道,每个通道均可以抽象成为Stream接口,那么用StreamFork可以很轻松的实现接口转换。这里以一个自定义的接口类型为例:
整个设计并未用到状态机,仅依靠StreamFork便实现一个接口转换。感兴趣的小伙伴不妨可以自己研究下。
作者:玉骐
原文链接:https://mp.weixin.qq.com/s/EfXcHwpAqWrZQrvOGBcB4A
微信公众号:
推荐阅读
更多SpinalHDL技术干货请关注Spinal FPGA专栏。