十二 · 2021年10月22日

见微知著——从example感受Lib 的强大

在SpinalHDL的世界里,它基于core提供了丰富的Lib库,包含了Stream、Flow、Fragment、State Machine、Bus Slave Factory等一系列强大的使用库,从而让HDL的世界能够像普通软件语言一样进行扩展,本篇就一个小的example来对比SpinalHDL Lib库的强大

考虑下图电路逻辑:

 title=

上图中电路有四组接口:

 title=

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
微信公众号:
 title=
推荐阅读
更多SpinalHDL技术干货请关注Spinal FPGA专栏。
推荐阅读
关注数
1579
内容数
131
用SpinalHDL提升生产力
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息