SpinalHDL和Chisel都是基于scala来实现的,而在SpinalHDL的example里,偶然看到一个apply的有趣用法。
“神奇”的逻辑,"奇葩"的写法
偶然看到一个例子,在SpinalHDL里的example关于UDP设计的代码里看到了这么一段代码:
初见这段代码,按照我的理解,flushRx所描述的是一段电路,然而我通篇代码看下来active信号的拉高也仅仅在其中的apply函数,而在代码的其他地方,倒是见到了不少flushRx()的调用,等等,调用电路单元flushRx难道是把电路一遍遍复制么,这也有点儿太不靠谱儿了,太奇葩了吧。
矛盾的指向——apply
在上面的那段代码里,active拉高仅出现在apply函数里,而从整体代码的设计意图来看,状态机里调用flushRx的初衷在于拉高active,而apply是scala中经常遇到的函数,那么这里是否采用了scala的语法功能来描述电路呢?
先看一段scala的普通代码:
执行结果如下:
这里首先声明了一个trait a 类型变量,并为该变量生命了两个applu方法,随后分别对变量进行调用aa(),aa(3),而从执行结果来看,在声明变量a时,打印了“hello”,而当我们调用aa()时,打印了world函数,而在我们调用aa(3)时,其打印了world 3。而apply的妙用也就在此:
用括号传递给变量(对象)一个或多个参数时,Scala 会把它转换成对 apply 方法的调用。
进一步逻辑解耦合
像本篇开始提到的那个例子,在状态机里调用flushRx(),相当于执行了拉高active操作。这里我们将与接口相关的时序操作均封装在flushRx中并将接口的赋值封装成函数的形式供其他人调用,从而将算法设计与接口时序分离。
这里看一个简单的例子:
case class applyTest() extends Component{
val a=in Bool()
val b=in Bool()
val c=out Bool()
val cSetCtrl=new Area{
c.setAsReg() init(False)
def apply()={
c.set()
}
when(c){
c.clear()
}
}
when(a||b)(cSetCtrl())
}
object applyTestApp extends App{
SpinalSystemVerilog(applyTest())
}
代码本身意义不大,这里我们将对接口c的时序操作均放置在cSetCtrl中,并为其定义一个apply方法,在apply方法中将c拉高。而在逻辑处理里,当a或者b有一个为高电平时调用cSetCtrl。其生成的RTL代码如下:
module test (
input data1,
input data2,
output dataOut,
input clk,
input reset
);
reg outputGen_outF;
assign dataOut = outputGen_outF;
always @ (posedge clk or posedge reset) begin
if (reset) begin
outputGen_outF <= 1'b0;
end else begin
if(outputGen_outF)begin
outputGen_outF <= 1'b0;
end
if((data1 || data2))begin
outputGen_outF <= 1'b1;
end
end
end
endmodule
也许有小伙伴看到第15行至18行代码或许感觉有些奇怪,这段代码会综合生成一个两输入LUT和一个寄存器锁存输出,相当于一个或门加一个寄存器输出。至于为什么就留给小伙伴们自己去思考了。
END
作者:玉骐
原文链接:https://mp.weixin.qq.com/s/CwFKo8KyxvKqQQ0Xsy9BKA
微信公众号:
推荐阅读
更多SpinalHDL技术干货请关注Spinal FPGA专栏。