模块和层次(Component and hierarchy)
一、简介(Introduction)
正如VHDL和Verilog, 你可以在SpinalHDL中定义模块来进行层次化设计。然而, 在SpinalHDL中, 你不需要在例化的时候分配他们的端口。
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:
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
函数来产生所有被移除的和无用信号的列表:
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)
如果你想要参数化模块, 你可以在模块生成的地方通过如下方式给定参数:
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))
}
}
如果有很多参数, 以一种特定的配置类来参数化很方便:
case class MySocConfig(
axiFrequency : HertzNumber,
onChipRamSize : BigInt,
cpu : RiscCoreConfig,
iCache : InstructionCacheConfig)
class MySoc(config: MySocConfig) extends Component {
...
}
你可以在配置中使用函数, 以及对配置属性设置要求(requirements):
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
将其替换。