单独实例化的语法如下:
<module_name> <instance_name> (.<port_name_0> (),
.<port_name_1> (),
…
.<port_name_N> ());
其中,<module_name>是一个已经完成模块的名字,<instance_name>是我们给它实例化对象起的一个名字,这两者之间的对应关系很像C++中类和对象之间的关系。<port_name_X>对应被实例化模块中的具体端口名称,其后的为与端口连接的父模块内部的变量。例如:
wire a, b, c;
myAnd insAnd (.in0 (a), .in1(b), .out©);
注意,实例化的时候,实例的输出端口只能连接线网类型的变量,而输入端口可以连接线网或者寄存器类型的变量。
有些情况下,我们可能需要同时实例化一个模块多次,这个时候如果使用单独实例化语句会使代码显得非常的臃肿,也不利于阅读和修改,于是Verilog提供了数组实例化语句,语法如下:
<module_name> <instance_name> <instance_array_range>
(.<port_name_0> (variable0),
.<port_name_1> (variable1),
…
.<port_name_N> (variableN));
可以看出,相比于单独实例化语句,它主要多了一个<instance_array_range>参数,利用这个参数,我们就可以控制实例的数量。例如:
wire [3:0] a, b, c;
myAnd insAnd[3:0] (.in0 (a), .in1(b), .out©);
上述数组实例化语句的功能相当于
myAnd insAnd3 (.in0 (a[3]), .in1(b[3]), .out(c[3]));
myAnd insAnd2 (.in0 (a[2]), .in1(b[2]), .out(c[2]));
myAnd insAnd1 (.in0 (a[1]), .in1(b[1]), .out(c[1]));
myAnd insAnd0 (.in0 (a[0]), .in1(b[0]), .out(c[0]));
有些时候,众多实例中的有些端口是需要共用信号的,例如使能信号,此时可以写成这样:
wire en;
wire [3:0] a, b, c;
myEnAnd insEnAnd[3:0] (.in0 (a), .in1(b), .inEn (en), .out©);
此时的数组实例化语句的功能相当于
myAnd insAnd3 (.in0 (a[3]), .in1(b[3]), .inEn (en), .out(c[3]));
myAnd insAnd2 (.in0 (a[2]), .in1(b[2]), .inEn (en), .out(c[2]));
myAnd insAnd1 (.in0 (a[1]), .in1(b[1]), .inEn (en), .out(c[1]));
myAnd insAnd0 (.in0 (a[0]), .in1(b[0]), .inEn (en), .out(c[0]));
注意,数组实例化时,对输入的变量位宽是有一定要求的:
一、等于所有实例对应端口的位宽之和。例如对于上例的变量a来说,它的位宽等于4个实例中in0端口的位宽和:1bit*4 = 4bits。这样变量的位宽将会被均分到各个实例的对应端口上;
二、等于模块对应端口的位宽。例如对于上例的变量en来说,它的位宽就等于模块只能够inEn端口的位宽,为1bit,此时该变量就会被连接至所有的实例对应的端口上。
对于其他情况的位宽输入Verilog的数组实例化语句都是不支持的,请不要在这个地方进行错误的发明创造。
参数重载也是实例化语句的一个重要组成部分。在Verilog基本程序框架中,我们提到过,为了增强模块的重用性,Verilog会在模块中定义一些参数,从而通过再例化的时候对参数进行重载来适应不同的需求。按照例化时对参数的重新赋值方式,我们可以把参数重载分为内部重载与外部重载,分别介绍如下:
内部重载
内部重载使用”#(.<parameter_name>(new_value), …)”语法,例如:
cellAnd #(.WIDTH(4)) m (a,b,c);
这是我们在【Verilog基本程序模板】小节中给出的例子。
外部重载
相比于在模块实例化的时候来修改参数的值,外部重载允许在编译的时候再修改参数的值。外部重载需要用到defparam关键字,举例如下:
cellAnd m (a,b,c);
defparam m.WIDTH = 4;
不过在使用defparam的时候需谨慎,因为有些综合工具或者它们的早期版本并不支持该语法。
实例化时实例端口的赋值形式有多种,当然,最常用,最典型也是最推荐的就是映射赋值,不过除此以外,端口赋值还有多种形式,列举如下供大家了解:
一、映射赋值。例如:
wire a, b, c;
myAnd insAnd (.in0 (a), .in1(b), .out©);
二、位置赋值。例如:
wire a, b, c;
myAnd insAnd (a, b, c);
三、部分赋值。这是由于有些模块在使用时并不是所有端口都需要的,若上例中的端口b是可以不使用的,那么按照映射赋值可以写成:
myAnd insAnd (.in0 (a), .out©);
而按照位置赋值必须写成:
myAnd insAnd (a, , c);
注意其中的多余的那个逗号,是用来占位用的,有了它,后面的c变量来能正确对应到out端口。
四、常数赋值。例如:
wire a, c;
myAnd insAnd (.in0 (a), .in1(1’b1), .out©);
注意,常数只能用于实例的输入端口。
五、表达式赋值。例如:
wire a, b, c, d;
myAnd insAnd (.in0 (a&d), .in1(~b), .out©);
不过不建议这样做,因为不太符合规范。