十二 · 2021年11月03日

SpinalHDL—Function

聊一聊SpinalHDL中Function的使用。阅读本文之前建议先阅读文末往期精选中的两篇文章。

不一样的“function”

诸君尽知,我们在写Verilog/SystemVerilog电路描述时,可以将部分功能抽象到function 中。然则,在Verilog/SystemVerilog中,function只能用于描述组合逻辑,因而我们稍微复杂的时序逻辑的封装都必须采用module的形式了。

而在SpinalHDL里,我们的所有语法都是基于Scala的,而且我们的电路描述一般都在class中。熟悉面向对象设计方法的小伙伴都知道,我们可以在class中定义方法。那么同样地,我们可以在这里用Function来抽象定义电路。不同于Verilog/SystemVerilog中的方法,在这里我们定义Function可以:

  • 我们可以在Function 中定义寄存器、组合电路,Area甚至Component。
  • 所有的参数都是以引用的形式进行传递,方便我们在函数中进行操作。

一言以蔽之,我们在Function中可以写任何符合语法规范的电路结构。我们可以将一个总线作为参数传给function,然后在function内定义寄存器读写操作。我们也可以返回Component、Bus、甚至任何符合Scala 语法规范的东西。

在Spinal HDL中,你会看到大量的实现是基于function来实现的。

按照OOP思维定义硬件

在面向对象的设计思想里,我们会将一个组件及其功能定义在一个class中。这里以我们之前所描述的加法电路为例。在电路描述里我们定义了一个sumPort类:

case class sumPort(dataWidth:Int=8) extends Bundle with IMasterSlave{
  case class dataPort(dataWidth:Int=8) extends Bundle{
    val data1=UInt(dataWidth bits)
    val data2=UInt(dataWidth bits)
  }
  val dataIn=Flow(dataPort(dataWidth))
  val sum=Flow(UInt(dataWidth bits))

  override def asMaster(): Unit = {
    master(dataIn)
    slave(sum)
  }
}

为了实现一个加法的功能,我们要么定义了一个Component、要么定义了一个Area来实现加法功能。而从面向对象的设计思维里,这个加法实现本应是sumPort本身应该具备的功能,因而我们可以在sumPort中定义一个方法用于实现sumPort的加法:

case class sumPort(dataWidth:Int=8) extends Bundle with IMasterSlave{
  case class dataPort(dataWidth:Int=8) extends Bundle{
    val data1=UInt(dataWidth bits)
    val data2=UInt(dataWidth bits)
  }
  val dataIn=Flow(dataPort(dataWidth))
  val sum=Flow(UInt(dataWidth bits))

  override def asMaster(): Unit = {
    master(dataIn)
    slave(sum)
  }

  def sumCal={
    sum:=RegNext(dataIn.translateWith(dataIn.data2+dataIn.data1))
  }
}

这里,我们为sumPort定义了一个sumCal方法用于实现加法(加一级寄存器延迟)。这样我们无需定义额外的Component、Area即可实现加法功能调用:

class addInst4(dataWidth:Int) extends Component{
  val io=new Bundle{
    val sumport0=slave(sumPort(dataWidth))
    val sumport1=slave(sumPort(dataWidth))
  }
  io.sumport0.sumCal
  io.sumport1.sumCal
}

美中不足

借助functin,我们可以很方便的在硬件电路描述里使用OOP的软件思维,然而目前有一点尚美中不足。考虑下面的电路描述:

class funcTest()extends Component{
  val io=new Bundle{
    val data0=in UInt(8 bits)
    val data1=in UInt(8 bits)
    val sum=out UInt(8 bits)
  }
  def sum(data0:UInt,data1:UInt):UInt={
    val sum=Reg( UInt(8 bits))
    sum:=data0+data1
    sum
  }
  io.sum:=sum(io.data0,io.data1)
}

这里我们定义了一个sum的函数来实现加法功能,而在sum函数里我们又定义了一个sum变量寄存器类型。生成Verilog时代码是这般的:

module funcTest (
  input      [7:0]    io_data0,
  input      [7:0]    io_data1,
  output     [7:0]    io_sum,
  input               clk,
  input               reset
);
  reg        [7:0]    _zz_1;

  assign io_sum = _zz_1;
  always @ (posedge clk) begin
    _zz_1 <= (io_data0 + io_data1);
  end
endmodule

可以发现,我们在sum函数中定义的sum变量并没有在生成的Verilog中保存下来。

SpinalHDL目前对function中定义的变量在生成Verilog/SystemVerilog时不会保留原名,而以_zz_* 的形式替代。

当然也可以在function 中定义Area(Area中的变量名会保留)。关于function的进阶例子可以参照下述连接:
https://gitee.com/peasent/SpinalWorkshop/tree/dev/src/main/scala/workshop/function

END

作者:玉骐
原文链接:https://mp.weixin.qq.com/s/83\\\\_87CfvoMBj5tzsoDNYdQ
微信公众号:
 title=

推荐阅读

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