习惯了Verilog的小伙伴,初次看到SpinalHDL的代码时,总会不自觉的和Verilog代码对照,本篇就SpinalHDL中的代码组织结构进行一个简要的梳理。
Component<—>module
我们在编写Verilog代码时,代码的的组织往往是按照module来组织的,而在SpianlHDL里,与之相对的是Component,SpianlHDL里我们编写的每个class继承Component则与之对应的在生成RTL时会相对生成一个module,因而往往我们在编写SpinalHDL时更多的是继承Component:
这里定义了两个class:AdderCell及Adder,这两个类均继承了Component,因此在生成RTL时与之对应的将会生成AdderCell及Adder两个module。而我们在使用时需要做的即使模块例化,端口链接,而当端口将多时,我们可以定义伴生对象来解决,避免成为连线工程师。
端口声明
和我们编写Verilog代码相同,当在SpinalHDL里的class继承了Component之后,那么我们需要在class里声明输入输出端口,而SpinalHDL里提供的输入输出端口声明的关键词有:
对于普通的输入输出引脚,我们可以用in/out去进行端口声明,而这里值得注意的是,在SpinalHDL里,我们甚至可以用一个类来做端口:
这里我们在VgaCtrl里,timeings端口将VgaTimings类作为一个端口声明,如此能够将结构性的端口“一劳永逸”,避免像我们手写Verilog时千行代码,百行端口声明的尴尬。
当我们用class来做端口声明时,master/slave是另一种选择,它需要我们在定义类时继承扩展Bundle及Imaster Slave:
这里我们继承了ImasterSlave,则需要我们在class中实现asMaster方法,asMaster方法中需要声明当我们这个类用做端口声明的master时,里面的哪些变量为输入端口,哪些变量为输出端口。这里Vga中,表明当Vga做master端口时,vSync、hSync、colorEn、color均作为输出端口,而做slave端口时,这些端口将会被推断为输入端口
还有一点值得注意的是,在SpianlHDL里,较为推荐将端口的声明放在Bundle中:
将端口声明放在Bundle中,SpinalHDL在生成RTL时,会检测在Bundle中的所有元素是否都声明了端口方向。
有一点是像上面的MyAdder将端口声明放在Bundle中,生成的RTL代码的端口都会有一个"io_"前缀,若想在代码中避免可在SpinalHDL里添加"noIoPrefix()"。
在SpinalHDL里,Component对于端口的访问与Verilog类似:
可读本Component的输入输出端口及
可读本Component例化的子Component输入输出端口
有趣的一点是当我们想访问更深一级子Compontent变量或端口名时也有方式:
some.where.else.theSignal.pull()
在生成RTL代码时,会讲该信号声明一个端口链接偷穿到我们这一级的Component。
Component参数化设计
与我们编写Verilog时相同,在SpinalHDL里同样支持参数化设计,而且更加的强大:
这里MyAdder的端口位宽采用参数化设计,我们可以在例化时实时的配置修改端口位宽声明。而当我们有多个参数时,我们可以将参数封装为参数类:
这里MySoc的所有参数声明均在MySocConfig中,不同模块都要使用这里面的参数时就不用每个都反复的声明一遍了~。
原作者:玉骐