在编写verilog代码时最痛苦的事情便是例化模块时端口的连接,这时候的你我便成了连线工程师,本节就在spinalhdl中如何像软件调用方法那样优雅地例化端口进行探讨。
习惯了写verilog的小伙伴们在做大型工程时是否有遇到过连续数天时间化身“连线工程师”去例化模块、为端口赋值连接的场景(关键是这些工作量老板他也不认)。尽管在systemverilog中提供了interface接口的概念,但是从事fpga的小伙伴都清楚无论是xilinx的vivado还是intel quartus虽然支持systemverilog但远没有做到像软件代码编辑器那般做到自动联想与提示。最近分析一个intel的大型源码工程其中用到了大量的systemverilog中的interface及struct,但自动关联提示做的真是一团糟,导致阅读体验真是差的一匹…… 本文以一个简单的加法器的例子来看如何在spinalhdl中如何避免成为连线工程师。 加法器端口列表如下所示:端口名方向位宽说明
valid_ininput1输入有效标志
data1input8输入数据
data2input8输入数据
sumoutput8和
sum_validoutput1和有效标志
初阶
刚开始接触spinalhdl时这个加法器我们可能会这么来写:
class add(datawidth:int) extends component{ val validin=in bool() val data1=in uint(datawidth bits) val data2=in uint(datawidth bits) val sum=out uint(datawidth bits) val sumvalid=out bool() sum:=regnextwhen(data1+data2,validin) sumvalid:=regnext(validin,false)}
这里针对端口的实现形式和我们在verilog中的方式基本相同。那么当我们在例化这个模块时,我们可能会这么来写:
class addinst(datawidth:int) extends component { val io=new bundle{ val validin_0=in bool() val data1_0=in uint(datawidth bits) val data2_0=in uint(datawidth bits) val sum_0=out uint(datawidth bits) val sumvalid_0=out bool()
val validin_1=in bool() val data1_1=in uint(datawidth bits) val data2_1=in uint(datawidth bits) val sum_1=out uint(datawidth bits) val sumvalid_1=out bool() } val add0=new add(datawidth) val add1=new add(datawidth) add0.validin《》io.validin_0 add0.data1《》io.data1_0 add0.data2《》io.data2_0 add0.sum《》io.sum_0 add0.sumvalid《》io.sumvalid_0 add1.validin《》io.validin_1 add1.data1《》io.data1_1 add1.data2《》io.data2_1 add1.sum《》io.sum_1 add1.sumvalid《》io.sumvalid_1}
这里例化了两个加法器,可以看到,这里如同我们写verilog代码般一根根连线,当有众多模块需要去例化时还是蛮痛苦的。
中阶
在systemverilog中提供了interface的概念用于封装接口,在spinalhdl中,我们可以借助软件面向对象的思想把接口给抽象出来:
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) }}
这里我们将加法器的端口抽象成sumport端口。其中包含两个flow类型:datain、sum。并声明当作为master端口时datain为master、sum为slave。这样,我们的加法器便可以这么来写:
case class add2(datawidth:int=8)extends component{ val io=new bundle{ val sumport=slave(sumport(datawidth)) } io.sumport.sum.payload:=regnextwhen(io.sumport.datain.data1+io.sumport.datain.data2,io.sumport.datain.valid) io.sumport.sum.valid:=regnext(io.sumport.datain.valid,false)}
而我们在例化时,便可以简洁地例化:
class addinst1(datawidth:int) extends component{ val io=new bundle{ val sumport0=slave(sumport(datawidth)) val sumport1=slave(sumport(datawidth)) } val addinst_0=add2(datawidth) val addinst_1=add2(datawidth) io.sumport0《》addinst_0.io.sumport io.sumport1《》addinst_1.io.sumport}
如此我们便能简洁地例化加法器。虽然这里地做法思想和systemverilog中地思想基本一致,但好处是我们能够在idea中像阅读软件代码那般快速地跳转和定位,相较于厂商工具中那样分析工程地痛苦实在是好太多。
高阶
在中阶例,我们采用了类似systemverilog中interface及struct概念,但可以发现,我们这里依旧存在连线行为。一个模块例化一次要连线一次,要例化n次还是要……
在软件代码中,调用一个方法或者模块往往一行代码了事:声明调用函数并将参数放在括号列表里。那么在这里,我们能否像软件调用那样一行代码搞定呢?
可以的!由于spinalhdl是基于scala的,因此我们可以将端口列表当成参数列表来传递。这里我们先为我们的加法器定义一个伴生对象:
object add2{ def apply(datawidth: int,port unit = { val addinst=new add2(datawidth) addinst.io.sumport《》port }}
这里我们为加法器add2定义了一个伴生对象(伴生对象声明为object,名字与类名相同)。并在其中定义了一个apply方法,传入两个参数:位宽datawidth及端口port,并在apply实现中完成模块例化及端口连接(一次连线,终身使用)。随后我们在例化时便可以像软件调用方法那样例化模块了:
class addinst1(datawidth:int) extends component{ val io=new bundle{ val sumport0=slave(sumport(datawidth)) val sumport1=slave(sumport(datawidth)) } add2(datawidth,io.sumport0) add2(datawidth,io.sumport0)}
一行代码搞定一个模块的一次例化和端口连接!
原文标题:spinalhdl—像软件调用方法般例化模块
文章出处:【微信公众号:fpga之家】欢迎添加关注!文章转载请注明出处。
串口特殊用法—智能卡通讯
2018年世界移动通信大会5G热门话题盘点
基于LM4809构建的立体声耳机放大器电路图
NVIDIA生态系统赋能AI产业
用于备用电池的智能电池充电器
在SpinalHDL中如何优雅地例化端口?
如何通过ROOM框在PCB板上来放置元器件
AI+物流为物流方案提供“大脑”和“眼睛”
浅谈信号完整性技巧
什么牌子的蓝牙耳机最好,618性价比最好的蓝牙耳机推荐
5G即将到来!智能手机将面对什么样的挑战?
一款阻止黑客攻击汽车的应用设计
音频信号两种连接方式的原理与区别及转换方式分析
智能家居显示屏将带你体验智能家居发展的新趋势
加密IC DM2016在数字电视加密中的设计应用
用于增强实用锂离子电池循环性能的高抗氧化性氰功能化硼酸锂盐
新能源汽车电机测功机测试项目及特点
模拟/数字混合信号大规模集成芯片(LSI) MM8000系列
TCL 2018上半年净利润比上年同期上升50%-60%
三元表达式引发的空指针问题分析