目录
1 Module简述
到目前为止,您已经熟悉模块(Module),该模块是通过输入和输出端口与其外部交互的电路。更大、更复杂的电路是通过将较小的模块和其他连接在一起的模块(例如,assign语句和always块)组成较大的模块而构建的。这形成了一个层次结构,因为模块可以包含其他模块的实例。
下图显示了带有子模块的非常简单的电路。在本练习中,创建模块mod_a的一个实例,然后将模块的三个引脚(in1,in2和out)连接到顶级模块的三个端口(导线a,b和out)。
连接模块时,只有模块上的端口很重要。您不需要知道模块内部的代码。模块mod_a的代码如下所示:
module mod_a ( input in1, input in2, output out );
// Module body
endmodule
只要使用的所有模块都属于同一个项目,就可以通过实例化另一个模块内部的模块来创建模块的层次结构(这样,编译器就知道在哪里可以找到模块)。一个模块的代码未写在另一模块的主体内(不同模块的代码未嵌套)。 您可以通过端口名称或端口位置将信号连接到模块。
- By position
通过位置将电线连接到端口的语法应该很熟悉,因为它使用类似C的语法。实例化模块时,根据模块的声明从左到右连接端口。
例如:mod_a instance1 (wa,wb,wc);
这将实例化类型为mod_a的模块,并为其提供实例名称“ instance1”,然后将信号wa(在新模块之外)连接到新模块的第一个端口(in1),将wb连接到第二个端口(in2),然后wc到第三个端口(out)。
这种语法的一个缺点是,如果模块的端口列表发生更改,则还需要找到并更改模块的所有实例以匹配新模块。 - By name
通过名称将信号连接到模块的端口,即使端口列表发生更改,也可以保持电线正确连接。但是,此语法更为冗长。
例如:mod_a instance2 ( .out(wc), .in1(wa), .in2(wb) );
这实例化了一个名为“ instance2”的mod_a类型的模块,然后将信号wa(在模块外部)连接到名为in1的端口,将wb连接到名为in2的端口,并将wc连接到名为out的端口。
请注意,此处的端口顺序无关紧要,因为将以正确的名称进行连接,而不管其在子模块的端口列表中的位置如何。还要注意此语法中端口名称前的句点。
question:
实例化下图所示电路。
solution:
module top_module ( input a, input b, output out );
mod_a instance1 (.out(out),.in1(a),.in2(b));
endmodule
1.1 Connecting ports by position
question:
此问题与上一个(模块)相似。您将获得一个名为mod_a的模块,该模块具有2个输出和4个输入的顺序。您必须按位置将这6个端口连接到顶级模块的端口out1,out2,a,b,c和d。
获得以下模块:
module mod_a ( output, output, input, input, input, input );
solution:
module top_module (
input a,
input b,
input c,
input d,
output out1,
output out2
);
mod_a instance2 (out1,out2,a,b,c,d);
endmodule
1.2 Connecting ports by name
question:
获得一个名为mod_a的模块,该模块按顺序具有2个输出和4个输入。必须按名称将6个端口连接到顶级模块的端口。
获得以下模块:
module mod_a ( output out1, output out2, input in1, input in2, input in3, input in4);
module top_module (
input a,
input b,
input c,
input d,
output out1,
output out2
);
mod_a instance3 (.out1(out1),.out2(out2),.in1(a),.in2(b),.in3(c),.in4(d));
endmodule
2 Module shift
2.1 D flip-flops’ Module shift
question:
获得一个模块my_dff,该模块具有两个输入和一个输出(实现了一个D触发器)。实例化它们中的三个,然后将它们链接在一起以构成一个长度为3的移位寄存器。clk端口需要连接到所有实例。现有以下模块D触发器:
module my_dff ( input clk, input d, output q );
注意,要进行内部连接前,需要声明一些导线。在命名您的电线和模块实例时要小心:名称必须唯一。
D触发器是在CP正跳沿前接受输入信号,正跳沿时触发翻转,正跳沿后输入即被封锁,三步都是在正跳沿后完成,所以有边沿触发器之称。其功能表如下:
solution:
module top_module ( input clk, input d, output q );
wire q1_wire;
wire q2_wire;
my_dff instance4 (.clk(clk),.d(d),.q(q1_wire));
my_dff instance5 (.clk(clk),.d(q1_wire),.q(q2_wire));
my_dff instance6 (.clk(clk),.d(q2_wire),.q(q));
endmodule
仿真结果如下图:
2.2 An extension of module_shift
现在,模块不再是仅单个引脚的模块端口,而是具有以向量为端口的模块,您将在其中附加导线矢量而不是普通导线。
像Verilog中的其他任何地方一样,端口的向量长度不必与连接到其的电线相匹配,但是这将导致向量的零填充或局部化。
本练习不使用向量长度不匹配的连接。
question:
我们现在有一个模块my_dff8,该模块具有两个输入和一个输出(实现一个8位的D触发器)。实例化它们中的三个,然后将它们链接在一起以构成一个长度为3的8位宽移位寄存器。
此外,创建一个4比1多路复用器,该多路复用器根据sel [1:0]选择输出的内容:输入d的值,在第一个,第二个之后或在第三个D触发器之后。 (本质上,sel选择从0到3个时钟周期延迟输入的周期。)
提供模块是:
module my_dff8 ( input clk, input [7:0] d, output [7:0] q );
没有提供多路复用器。一种可能的写方法是在always块中包含case语句。
solution:
module top_module (
input clk,
input [7:0] d,
input [1:0] sel,
output [7:0] q
);
wire [7:0] q1;
wire [7:0] q2;
wire [7:0] q3;//wire [7:0] q1,q2,q3;
my_dff8 instance0 (.clk(clk),.d(d),.q(q1));
my_dff8 instance1 (.clk(clk),.d(q1),.q(q2));
my_dff8 instance2 (.clk(clk),.d(q2),.q(q3));
always @(*)
begin
case(sel)
2'b00:q = d;
2'b01:q = q1;
2'b10:q = q2;
2'b11:q = q3;
endcase
end
endmodule
2.3 Module add
2.3.1 Create a 32-bit adder
现提供一个执行16位加法的模块add16。实例化其中两个以创建32位加法器。一个add16模块在收到第一个加法器的进位后,计算加法结果的低16位,而第二个add16模块计算结果的高16位。
待设计的32位加法器不需要处理进位(假定为0)或进位(忽略),但是内部模块需要处理才能正常工作。 (换句话说,add16模块执行16位a + b + cin,而您的模块执行32位a + b)。
question:
如下图所示将模块连接在一起。提供的模块add16具有以下声明:
module add16 ( input[15:0] a, input[15:0] b, input cin, output[15:0] sum, output cout );
solution:
module top_module(
input [31:0] a,
input [31:0] b,
output [31:0] sum
);
wire cin;
wire [15:0] sum1,sum2;
wire cout1,cout2;
assign cin = 1'b0;
add16 instance1 (.a(a[15:0]),.b(b[15:0]),.cin(cin),.sum(sum1),.cout(cout1));
add16 instance2 (.a(a[31:16]),.b(b[31:16]),.cin(cout1),.sum(sum2),.cout(cout2));
assign sum = {sum2,sum1};
endmodule
2.3.2 Create a circuit with two levels of hierarchy
在本练习中,我们将创建一个具有两个层次结构的电路。top_module将实例化add16的两个副本(已提供),每个副本将实例化add1的16个副本(待编写)。因此,我们必须编写两个模块:top_module和add1。
与module_add类似,这里将提供执行16位加法的模块add16。必须实例化其中两个以创建32位加法器。一个add16模块计算加法结果的低16位,而第二个add16模块计算结果的高16位。这里32位加法器不需要处理借位(假设为0)或进位(忽略)。
如下图所示,将add16模块连接在一起。提供的模块add16具有以下声明:
module add16 ( input[15:0] a, input[15:0] b, input cin, output[15:0] sum, output cout );
在每个add16中,实例化16个完整的加法器(未提供模块add1)以实际执行加法。您必须编写具有以下声明的完整加法器模块:
module add1 ( input a, input b, input cin, output sum, output cout );
回想一下,一个全加法器计算a + b + cin的总和和进位。 总之,此设计包含三个模块:
- top_module——顶级模块,其中包含两个add16
- 提供的add16 ——一个16位加法器模块,包含add1
- add1 —— 1位全加法器模块。
solution:
module top_module (
input [31:0] a,
input [31:0] b,
output [31:0] sum
);//
wire cin;
wire cout1;
wire cout2;
wire [15:0] sum1,sum2;
assign cin = 1'b0;
add16 instance0 (a[15:0],b[15:0],cin,sum1,cout1);
add16 instance1 (a[31:16],b[31:16],cout1,sum2,cout2);
assign sum = {sum2,sum1};
endmodule
module add1 ( input a, input b, input cin, output sum, output cout );
// Full adder module here
assign sum = a^b^cin;
assign cout = a&b | a&cin | b&cin;
endmodule
2.3.3 a carry-select adder
纹波进位加法器的一个缺点(请参阅前面)是加法器计算进位(在最坏的情况下从进位)的延迟相当慢,并且第二级加法器无法开始计算其进位。进行直到第一阶段加法器完成为止。这使加法器变慢。
进位选择加法器是一种改进,如下所示。第一级加法器与之前相同,但是我们复制了第二级加法器,一个假设进位= 0,一个假设进位= 1,然后使用快速2对1多路复用器选择哪个结果碰巧是正确的。
question:
在本练习中,将为您提供与上一练习相同的模块add16,该模块将两个带进位的16位数字相加,并产生一个进位与16位和。
我们使用自己的16位2对1多路复用器实例化其中的三个,以构建进位选择加法器。 如下图所示将模块连接在一起。
提供的模块add16具有以下声明:
module add16 ( input[15:0] a, input[15:0] b, input cin, output[15:0] sum, output cout );
solution:
module top_module(
input [31:0] a,
input [31:0] b,
output [31:0] sum
);
wire cin1,cin2,cin3,cout1,cout2,cout3;
wire [15:0] sum1,sum2,sum3;
assign cin1 = 1'b0;
assign cin2 = 1'b0;
assign cin3 = 1'b1;
add16 add1 (a[15:0],b[15:0],cin1,sum1,cout1);
add16 add2 (a[31:16],b[31:16],cin2,sum2,cout2);
add16 add3 (a[31:16],b[31:16],cin3,sum3,cout3);
always@(*)
begin
case(cout1)
1'b0:sum={sum2,sum1};
1'b1:sum={sum3,sum1};
endcase
end
endmodule
2.3.4 An adder-subtractor
可以通过有选择地取反一个输入来从加法器构建一个加法器-减法器,这等效于将输入取反然后加1。净结果是一个可以执行两种操作的电路:(a + b + 0) and (a + ~b + 1)。
设计减法器可以转换为补码来实现,即:
X
−
Y
=
[
X
补
]
+
[
−
Y
补
]
X-Y=[X_补]+[-Y_补]
X−Y=[X补]+[−Y补]Y的补码是其反码加1,Y的反码可以用异或门来实现,得到反码后利用全加器来实现
X
+
Y
反
X+Y_反
X+Y反并使最低位进位位为1。异或门的其中一个输入端控制信号C,当C=1时,实现减法;当C=0时,实现加法。异或的运算规则如下(XOR门也可以看作是可编程反相器,其中一个输入控制是否应将另一个反相):
以下两个电路都是XOR门:
question:
每当sub为1时,使用32位宽的XOR门将b输入反相。(也可以将b [31:0]视为与sub复制32次的XOR运算。请参见复制运算符。)还将子输入连接到加法器的输入端。
这里提供了一个16位加法器模块,需要实例化两次:
module add16 ( input[15:0] a, input[15:0] b, input cin, output[15:0] sum, output cout );
solution:
module top_module(
input [31:0] a,
input [31:0] b,
input sub,
output [31:0] sum
);
wire cout1,cout2;
wire [15:0] sum1,sum2;
wire [31:0] b_n;
assign b_n = b ^ {32{sub}};
add16 add1 (a[15:0],b_n[15:0],sub,sum1,cout1);
add16 add2 (a[31:16],b_n[31:16],cout1,sum2,cout2);
assign sum = {sum2,sum1};
endmodule