## 模块和层次(Component and hierarchy) ### 一、简介(Introduction) 正如VHDL和Verilog, 你可以在SpinalHDL中定义模块来进行层次化设计。然而, 在SpinalHDL中, 你不需要在例化的时候分配他们的端口。 ```Scala class AdderCell() extends Component { //声明外部端口, 推荐在Bundle中用io命名 val io = new Bundle { val a, b, cin = in Bool() val sum, cout = out Bool() } //写一些逻辑 io.sum := io.a ^ io.b ^ io.cin io.cout := (io.a & io.b) | (io.a & io.cin) | (io.b & io.cin) } class Adder(width: Int) extends Component { ... //例化两个AdderCell val cell0 = new AdderCell() val cell1 = new AdderCell() cell1.io.cin := cell0.io.cout //连接cell0的cout和cell1的cin //创建ArrayCell阵列的另一个例化例子 val cellArray = Array.fill(width)(new AdderCell()) cellArray(1).io.cin := cellArray(0).io.cout //连接cell0的cout和cell1的cin ... } ``` Verilog: ```Verilog module Adder ( ); wire cell0_io_a; wire cell0_io_b; wire cell0_io_cin; wire cell1_io_a; wire cell1_io_b; wire cellArray_0_io_a; wire cellArray_0_io_b; wire cellArray_0_io_cin; wire cellArray_1_io_a; wire cellArray_1_io_b; wire cell0_io_sum; wire cell0_io_cout; wire cell1_io_sum; wire cell1_io_cout; wire cellArray_0_io_sum; wire cellArray_0_io_cout; wire cellArray_1_io_sum; wire cellArray_1_io_cout; AdderCell cell0 ( .io_a (cell0_io_a ), //i .io_b (cell0_io_b ), //i .io_cin (cell0_io_cin ), //i .io_sum (cell0_io_sum ), //o .io_cout (cell0_io_cout) //o ); AdderCell cell1 ( .io_a (cell1_io_a ), //i .io_b (cell1_io_b ), //i .io_cin (cell0_io_cout), //i .io_sum (cell1_io_sum ), //o .io_cout (cell1_io_cout) //o ); AdderCell cellArray_0 ( .io_a (cellArray_0_io_a ), //i .io_b (cellArray_0_io_b ), //i .io_cin (cellArray_0_io_cin ), //i .io_sum (cellArray_0_io_sum ), //o .io_cout (cellArray_0_io_cout) //o ); AdderCell cellArray_1 ( .io_a (cellArray_1_io_a ), //i .io_b (cellArray_1_io_b ), //i .io_cin (cellArray_0_io_cout), //i .io_sum (cellArray_1_io_sum ), //o .io_cout (cellArray_1_io_cout) //o ); endmodule //AdderCell replaced by AdderCell //AdderCell replaced by AdderCell //AdderCell replaced by AdderCell module AdderCell ( input io_a, input io_b, input io_cin, output io_sum, output io_cout ); assign io_sum = ((io_a ^ io_b) ^ io_cin); assign io_cout = (((io_a && io_b) || (io_a && io_cin)) || (io_b && io_cin)); endmodule ``` > `val io = new Bundle {...}`推荐在Bundle中声明了名为`io`的外部端口。如果把bundle命名为`io`, SpinalHDL会检查其元素是否都定义为输入或输出。 > 如果你更喜欢, 你也可以用`Module`语句替代`Component`(他们本质上一样) ### 二、输入/输出定义(Input/output definition) 定义输入输出的语句如下所示: | 语句 | 描述 | 返回类型 | | :-----------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------: | | in Bool()/out Bool() | 创建输入Bool类型/输出Bool类型 | Bool | | in/out Bits/UInt/SInt[(x bits)] | 创建对应类型的输入/输出 | Bits/UInt/SInt | | in/out(T) | 对于其他数据类型, 需要加括号, 这是Scala的约束 | T | | master/slave(T) | 这条语句需要spinal.lib库的支持(如果你在对象里只用`slave`语句, 用spinal.lib.slave就可以)T应该加上IMasterSlave-这里有一些doc做解释。你也可以不带括号, maste T也可以 | T | 关于模块的互联还有一些规则如下: + 模块能读取output和子模块的input信号。 + 模块能读取他们自己output端口的值(不像VHDL)。 > 备注:如出于某些原因需要从本层次外很远的地方读取信号(比如debugg或临时补丁), 你可以用`some.where.else.theSignal.pull()`实现。 ### 三、信号剪枝(Pruned signals) SpinalHDL会产生所有命名了的信号以及他们的依赖关系, 然而所有无用的/未命名的/0宽度的信号会在RTL产生时被移除。 你可以在生成的`SpinalReport`对象中用`printPruned`和`printPrunedIO`函数来产生所有被移除的和无用信号的列表: ```Scala class TopLevel extends Component { val io = new Bundle { val a, b = in UInt(8 bits) val result = out UInt(8 bits) } io.result := io.a + io.b val unusedSignal = UInt(8 bits) val unusedSignal2 = UInt(8 bits) unusedSignal2 := unusedSignal } object Main { def main(args: Array[String]) { SpinalVhdl(new TopLevel).printPruned() //这会报告: // [Warning] Unused wire detected : toplevel/unusedSignal : UInt[8 bits] // [Warning] Unused wire detected : toplevel/unusedSignal2 : UInt[8 bits] } } ``` ### 四、参数化硬件电路("Generic"——VHDL, "Parameter"——Verilog) 如果你想要参数化模块, 你可以在模块生成的地方通过如下方式给定参数: ```Scala class MyAdder(width: BitCount) extends Component { val io = new Bundle { val a, b = in UInt(width) val result = out UInt(width) } io.result := io.a + io.b } object Main { def main(args: Array[String]) { SpinalVhdl(new MyAdder(32 bits)) } } ``` 如果有很多参数, 以一种特定的配置类来参数化很方便: ```Scala case class MySocConfig( axiFrequency : HertzNumber, onChipRamSize : BigInt, cpu : RiscCoreConfig, iCache : InstructionCacheConfig) class MySoc(config: MySocConfig) extends Component { ... } ``` 你可以在配置中使用函数, 以及对配置属性设置要求(requirements): ```Scala case class MyBusConfig(addressWidth: Int, dataWidth: Int) { def bytePerWord = dataWidth / 8 def addressType = UInt(addressWidth bits) def dataType = Bits(dataWidth bits) require(dataWidth == 32 || dataWidth == 64, "Data width must be 32 or 64") } ``` ### 五、综合模块名(Synthesized component names) 在module中, 每个模块都有名字, 叫做“不完全的名字(partial name)”。“全名(full name)”通过用“_”连接每个模块的父类名字得到, 例如:`io_clockDomain_reset`。你可以用`setName`用定制化的名字替代原始名。这在与外部模块用接口链接的时候非常有用。类似的方法相对应的还有`getName`, `setPartialName`, `getPartialName`。 在综合时, 每个模块的名字由Scala的类定义得到, 你也可以使用`setDefinitionName`将其替换。