在SpinalHDL的世界里,它基于core提供了丰富的Lib库,包含了Stream、Flow、Fragment、State Machine、Bus Slave Factory等一系列强大的使用库,从而让HDL的世界能够像普通软件语言一样进行扩展,本篇就一个小的example来对比SpinalHDL Lib库的强大
考虑下图电路逻辑:
上图中电路有四组接口:
memWrite为Flow接口(valid+payload),用于对Ram进行写操作,cmdA为Stream接口(valid+ready+payload),用于对Ram进行读操作,cmdB、rsp均为Stream接口,当cmdA、cmdB同时到达时,rsp输出cmdA读出的Ram内的值与cmdB负载(payload)进行异或输出,采用Stream协议(valid+ready)。
乍看,意兴阑珊
上图中的逻辑按照功能划分基本可分为两大基础部分:
- Ram读写逻辑:接收memWrite的写指令,将数据写入ram;接收cmdA的读指令,并将读取出的数据按照Stream协议进行封装产生cmdA_rdata
- Join逻辑:等待cmdA、cmdB同时有效时异或产生rsp输出逻辑,并处理Stream握手协议。
电路功能很小,逻辑亦很清晰,用Verilog来写亦不是难事,这里用SpinalHDL手写该逻辑电路如下:
case class MemoryWrite() extends Bundle{
val address = UInt(8 bits)
val data = Bits(32 bits)
}
case class StreamUnit() extends Component{
val io = new Bundle{
val memWrite = slave Flow(MemoryWrite())
val cmdA = slave Stream(UInt(8 bits))
val cmdB = slave Stream(Bits(32 bits))
val rsp = master Stream(Bits(32 bits))
}
val mem = Mem(Bits(32 bits),1 << 8)
mem.write(
enable = io.memWrite.valid,
address = io.memWrite.address,
data = io.memWrite.data
)
//******* MemReadStage **************
//MemReadStage declarations
val memReadValid = RegInit(False)
val memReadReady = Bool
//MemReadStage handshake
io.cmdA.ready := !memReadValid || memReadReady
when(io.cmdA.ready){
memReadValid := io.cmdA.valid
}
//MemReadStage memory access
val memReadData = mem.readSync(
enable = io.cmdA.fire,
address = io.cmdA.payload
)
//******** Join stage **************
// Join arbitration
io.rsp.valid := memReadValid && io.cmdB.valid
memReadReady := io.rsp.fire
io.cmdB.ready := io.rsp.fire
// Join datapath
io.rsp.payload := memReadData ^ io.cmdB.payload
}
功能很简单,但我们也需要按照Stream协议规范处理接口握手信号及每一个电路细节,稍有不慎就需要我们仿真一会儿~
再回首,别有洞天
再看这个逻辑电路图,所有的接口包括逻辑模块之间的电路都可以抽象为Stream接口或Lib接口,而SpinalHDL针对Stream、Flow提供了丰富的Lib库,借助库函数,可以非常方便的实现上述电路逻辑:
case class MemoryWrite() extends Bundle{
val address = UInt(8 bits)
val data = Bits(32 bits)
}
case class StreamUnit() extends Component{
val io = new Bundle{
val memWrite = slave Flow(MemoryWrite())
val cmdA = slave Stream(UInt(8 bits))
val cmdB = slave Stream(Bits(32 bits))
val rsp = master Stream(Bits(32 bits))
}
val mem = Mem(Bits(32 bits),1 << 8)
mem.write(
enable = io.memWrite.valid,
address = io.memWrite.address,
data = io.memWrite.data
)
val memReadStream = mem.streamReadSync(io.cmdA)
io.rsp << StreamJoin.arg(memReadStream,io.cmdB).translateWith(memReadStream.payload ^ io.cmdB.payload)
}
在上面的代码中,14~18行例化了一个Mem并实现其写端口逻辑,20行通过Mem提供的streamReadSync函数实现对Mem的读操作(接收一个Stream总线作为读请求,payload做读地址,valid做读使能,读取的结果赋值给一个Stream接口),第21行StreamJoin函数接收两个Stream接口作为参数,等待两个Stream接口均有效时输出rsp Stream valid,并将数据进行translateWith(memReadStream.payload ^ io.cmdB.payload)替换。
在上述代码中,核心代码只有20~21行,通过对Lib的调用,我们能够快速的构建描述电路,而其生成的同样也是RTL可读代码,但大大减少了调试的时间。
写在最后
无论SpinalHDL也好还是传统的Verilog也好,其本质仍需要我们有逻辑设计的思维,但SpinalHDL为我们提供了一个能够像写软件的方式去实现硬件,条条大路通罗马,最近的或许是最好的,至于诸君或喜或厌,那就萝卜白菜,各有所爱了。
END
作者:玉骐
原文链接:https://mp.weixin.qq.com/s/Sv8srJIAOVRoeyz0AngPbA
微信公众号:
推荐阅读
更多SpinalHDL技术干货请关注Spinal FPGA专栏。