模块和层次(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对象中用printPrunedprintPrunedIO函数来产生所有被移除的和无用信号的列表:

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将其替换。