聊一聊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
微信公众号:
推荐阅读
更多SpinalHDL技术干货请关注Spinal FPGA专栏。