Modules
到目前为止,您已经熟悉了 ,这是一种通过输入和输出端口与其外部交互的电路。更大、更复杂的电路是通过将较小的模块和其他连接在一起的部分(例如赋值语句和始终块)组合而成的更大模块来构建的。这形成了一个层次结构,因为模块可以包含其他模块的实例。module
下图显示了一个带有子模块的非常简单的电路。在本练习中,创建模块的一个实例,然后将模块的三个引脚(、 和 )连接到顶级模块的三个端口(电线、 和 )。该模块是为你提供的——你必须实例化它。mod_ain1in2outaboutmod_a
连接模块时,只有模块上的端口很重要。您不需要知道模块中的代码。模块的代码如下所示:
![](https://img-blog.csdnimg.cn/img_convert/4e37540edb67a95e787358f92927d41c.png)
module mod_a(input in1,input in2,output out);
// Module body
endmodule
模块的层次结构是通过在另一个模块中实例化一个模块来创建的,只要使用的所有模块都属于同一个项目(因此编译器知道在哪里可以找到模块)。一个模块的代码不会写在另一个模块的主体中(不同模块的代码不嵌套)。
您可以通过端口名称或端口位置将信号连接到模块。对于额外的练习,请尝试两种方法。
![](https://img-blog.csdnimg.cn/img_convert/d4dc7d8d6d894c078454c684a83b3863.png)
将信号连接到模块端口
将电线连接到端口有两种常用的方法:按位置或按名称。
按位置
按位置将电线连接到端口的语法应该很熟悉,因为它使用类似 C 的语法。实例化模块时,端口根据模块的声明从左到右连接。例如:
mod_a instance1 ( wa, wb, wc );
这将实例化类型的模块并为其提供 实例名称“instance1”,然后将信号(在新模块外部)连接到新模块的第一个端口 ()、 第二个端口 () 和 第三个端口 ()。此语法的一个缺点是,如果模块的端口列表发生更改,则还需要查找并更改模块的所有实例化以匹配新模块。mod_awain1wbin2wcout
按名称
通过 按名称将信号连接到模块的端口,即使端口列表发生变化,电线也能保持正确连接。但是,此语法更详细。
mod_a instance2 ( .out(wc), .in1(wa), .in2(wb) );
上行实例化名为“instance2”类型的模块,然后将信号(模块外部)连接到名为 的端口、名为 的端口和 名为 的端口。请注意,端口的排序在这里无关紧要,因为无论它在子模块的端口列表中的位置如何,都将连接到正确的名称。另请注意此语法中紧靠端口名称前面的句点。mod_awain1wbin2wcout
module top_module ( input a, input b, output out );
mod_a instance2 ( .out(out), .in1(a), .in2(b) );
endmodule
Connecting ports by position
此问题与上一个问题类似(模块).您将获得一个名为的模块,该模块按此顺序具有 2 个输出和 4 个输入。您必须按位置将 6 个端口连接到您的 顶级模块的端口 、 和 ,按该顺序排列。mod_aout1out2abcd
您将获得以下模块:
module mod_a ( output, output, input, input, input, input );
![](https://img-blog.csdnimg.cn/img_convert/9451d9402224b8c45638f75ec14a380e.png)
module top_module (
input a,
input b,
input c,
input d,
output out1,
output out2
);
mod_a ( out1, out2, a, b, c, d );
endmodule
Connecting ports by name
此问题类似于模块.您将获得一个名为的模块,该模块按某种顺序具有 2 个输出和 4 个输入。您必须按名称将 6 个端口连接到您的 顶级模块的端口:mod_a
端口在mod_a | 端口在top_module |
output out1 | out1 |
output out2 | out2 |
input in1 | a |
input in2 | b |
input in3 | c |
input in4 | d |
您将获得以下模块:
module mod_a ( output out1, output out2, input in1, input in2, input in3, input in4);
![](https://img-blog.csdnimg.cn/img_convert/5e3408ac5f8e7cdfa76a424543759aa7.png)
module top_module (
input a,
input b,
input c,
input d,
output out1,
output out2
);
mod_a instance_3( .out1(out1), .out2(out2), .in1(a), .in2(b), .in3(c), .in4(d));
endmodule
Three module
您将获得一个具有两个输入和一个输出的模块( D 触发器)。实例化其中的三个,然后将它们链接在一起以形成长度为 3 的移位寄存器。端口需要连接到所有实例。my_dffclk
提供给您的模块是:module my_dff ( input clk, input d, output q );
请注意,要进行内部连接,您需要声明一些电线。命名线路和模块实例时要小心:名称必须是唯一的。
![](https://img-blog.csdnimg.cn/img_convert/ba85c26013ec9ed48babcc7cab6f8236.png)
module top_module ( input clk, input d, output q );
wire a0,a1;
my_dff D1(.clk(clk), .d(d),.q(a0));
my_dff D2(.clk(clk), .d(a0),.q(a1));
my_dff D3(.clk(clk), .d(a1),.q(q));
endmodule
Modules and vectors
本练习是module_shift.模块端口不是只有单个引脚,我们现在有以矢量作为端口的模块,您将在其上附加线矢量而不是普通线。与 Verilog 中的其他位置一样,端口的矢量长度不必与连接到它的导线匹配,但这会导致矢量的零填充或截断。本练习不使用矢量长度不匹配的连接。
您将获得一个具有两个输入和一个输出的模块(实现一组 8 D 触发器)。实例化其中三个,然后将它们链接在一起,形成长度为 3 的 8 位宽移位寄存器。此外,创建一个 4 对 1 多路复用器(未提供),该多路复用器根据以下条件选择要输出的内容:输入 d 处的值、第一个 d 字拖之后、第二个之后或第三个 D 触发器之后的值。(实质上,选择延迟输入的周期数,从零到三个时钟周期。my_dff8sel[1:0]sel
提供给您的模块是:module my_dff8 ( input clk, input [7:0] d, output [7:0] q );
未提供多路复用器(multiplexer)。一种可能的编写方法是在带有语句的块内。(另请参阅:alwayscase多路复用9to1V)
![](https://img-blog.csdnimg.cn/img_convert/0504525cfc0a5a85f53700bad69c7f56.png)
module top_module (
input clk,
input [7:0] d,
input [1:0] sel,
output [7:0] q
);
wire [7:0]a0,a1,a2;
my_dff8 D1( .clk(clk), .d(d), .q(a0));
my_dff8 D2( .clk(clk), .d(a0), .q(a1));
my_dff8 D3( .clk(clk), .d(a1), .q(a2));
always@(*) begin
case(sel)
2'b00: q=d;
2'b01: q=a0;
2'b10: q=a1;
2'b11: q=a2;
endcase
end
endmodule
以下声明是不对的
wire [7:0]a0,[7:0]a1,[7:0]a2;错
Adder 1加法器
您将获得一个执行 16 位加法的模块。实例化其中两个以创建 32 位加法器。一个 add16 模块计算加法结果的低 16 位,而第二个 add16 模块在收到来自第一个加法器的结转后计算结果的高 16 位。您的 32 位加法器不需要处理进入(假设为 0)或传出(忽略),但内部模块需要处理才能正常运行。(换句话说,模块执行 16 位 a + b + cin,而模块执行 32 位 a + b)。add16add16
如下图所示将模块连接在一起。提供的模块具有以下声明:add16
module add16 ( input[15:0] a, input[15:0] b, input cin, output[15:0] sum, output cout );
![](https://img-blog.csdnimg.cn/img_convert/33c7fc648f077243af879aaca5e09144.png)
module top_module(
input [31:0] a,
input [31:0] b,
output [31:0] sum
);
wire cout1,cout2;
wire [15:0]sum1,sum2;
add16 lo( .a(a[15:0]), .b(b[15:0]), .cin(16'b0), .sum(sum1),.cout(cout1) );
add16 hi( .a(a[31:16]), .b(b[31:16]), .cin(cout1), .sum(sum2),.cout(cout2));
assign sum={sum2,sum1};
endmodule
Adder 2加法器
在本练习中,您将创建一个具有两个层次结构级别的线路。您将实例化(提供)的两个副本,每个副本将实例化 16 个副本(您必须编写)。因此,您必须编写两个模块:和 。top_moduleadd16add1top_moduleadd1
就像module_add题中一样,您将获得一个执行 16 位加法的模块。必须实例化其中两个才能创建 32 位加法器。一个模块计算加法结果的低 16 位,而第二个模块计算结果的高 16 位。您的 32 位加法器不需要处理进位(假设为 0)或进转(忽略)。add16add16add16
如下图所示将模块连接在一起。提供的模块具有以下声明:add16add16
module add16 ( input[15:0] a, input[15:0] b, input cin, output[15:0] sum, output cout );
在每个加法器中,16个完整的加法器(模块,未提供)被实例化以实际执行加法。您必须编写具有以下声明的完整加法器模块:add16add1
module add1 ( input a, input b, input cin, output sum, output cout );
回想一下,一个完整的加法器计算 a+b+cin 的总和和结转。
总之,此设计中有三个模块:
top_module— 您的顶级模块,包含以下两个...
add16,提供 — 一个 16 位加法器模块,由 16 个...
add1— 一个 1 位全加法器模块。
如果您的提交缺少 ,您将收到一条错误消息,指出 。module add1Error (12006): Node instance "user_fadd[0].a1" instantiates undefined entity "add1"
![](https://img-blog.csdnimg.cn/img_convert/a2e37e2b5746cbf6e80312109e187639.png)
一种思路:使用generate复制电路
module add16 ( input [15:0]a, input [15:0]b, input cin, output [15:0]sum, output cout );
add1 add0( .a(a[0]), .b(b[0]), .cin(0),.sum(sum[0]), .cout(cout0[0]));
wire [15:0]cout0;
genvar i;
generate
for(i=1;i<16;i=i+1)begin: add
add1 U_PROC( .a(a[i]), .b(b[i]), .cin(cout0[i-1]),.sum(sum[i]), .cout0(cout0[i]));
end
endgenerate
endmodule
解答
module top_module (
input [31:0] a,
input [31:0] b,
output [31:0] sum
);//
wire cout1,cout2;
wire [15:0]sum1,sum2;
add16 lo( .a(a[15:0]), .b(b[15:0]), .cin(16'b0), .sum(sum1),.cout(cout1) );
add16 hi( .a(a[31:16]), .b(b[31:16]), .cin(cout1), .sum(sum2),.cout(cout2));
assign sum={sum2,sum1};
endmodule
module add1 ( input a, input b, input cin, output sum, output cout );
always@(*) begin
case({a,b,cin})
3'b000:{sum,cout}=2'b00;
3'b001:{sum,cout}=2'b10;
3'b010:{sum,cout}=2'b10;
3'b011:{sum,cout}=2'b01;
3'b100:{sum,cout}=2'b10;
3'b101:{sum,cout}=2'b01;
3'b110:{sum,cout}=2'b01;
3'b111:{sum,cout}=2'b11;
endcase
end
// Full adder module here
endmodule
Carry-select adder进位选择加法器
纹波进位加法器的一个缺点(参见以前的练习) 是加法器计算执行的延迟(在最坏的情况下从进包开始)相当慢,并且第二级加法器在第一级加法器完成之前无法开始计算其进转。这会使加法器变慢。一项改进是进位选择加法器,如下所示。第一级加法器与以前相同,但我们复制了第二级加法器,一个假设携带=0,一个假设携带=1,然后使用快速 2 比 1 多路复用器来选择恰好正确的结果。
在本练习中,将为您提供与上一练习相同的模块,该模块将两个带进转的 16 位数字相加,并生成一个带出数和 16 位总和。您必须实例化其中三个,才能使用自己的 16 位 2 对 1 多路复用器构建进位选择加法器。add16
如下图所示将模块连接在一起。提供的模块具有以下声明:add16
module add16 ( input[15:0] a, input[15:0] b, input cin, output[15:0] sum, output cout );
![](https://img-blog.csdnimg.cn/img_convert/0b1da4fd96d162277c5468436d090732.png)
module top_module(
input [31:0] a,
input [31:0] b,
output [31:0] sum
);
wire cout_to_cin;
wire [15:0]sum_0;
wire [15:0]sum_1;
add16 u_add16_1( .a(a[15:0]), .b(b[15:0]), .cin(0), .sum(sum[15:0]), .cout(cout_to_cin));
add16 u_add16_2( .a(a[31:16]), .b(b[31:16]), .cin(0), .sum(sum_0), .cout());
add16 u_add16_3( .a(a[31:16]), .b(b[31:16]), .cin(1), .sum(sum_1), .cout());
always@(*)begin
case(cout_to_cin)
0: sum[31:16]= sum_0;
1: sum[31:16]= sum_1;
endcase
end
endmodule
Adder-subtractor加法减法器
可以通过选择性地否定其中一个输入来从加法器构建加法器-减法器,这相当于反转输入然后加 1。最终结果是一个可以执行两个操作的电路:(a + b + 0)和(a + ~b + 1)。如果您想更详细地解释此电路的工作原理,请参阅维基百科。
在下面构建加法器-减法器。
为您提供了一个 16 位加法器模块,您需要实例化两次:
module add16 ( input[15:0] a, input[15:0] b, input cin, output[15:0] sum, output cout );
当sub为1时,使用32位宽XOR门反相b输入。(这也可以看作是 b[31:0] XORed,子复制了 32 次。看复制运算符.).同时将子输入连接到加法器的携带。
![](https://img-blog.csdnimg.cn/img_convert/9e47d07b5f6dfa3575ac32a6f70a3df6.png)
module top_module(
input [31:0] a,
input [31:0] b,
input sub,
output [31:0] sum
);
wire [31:0]nb;
assign nb=b^{32{sub}};
wire cout1,cout2;
wire [15:0]sum1,sum2;
add16 lo( .a(a[15:0]), .b(nb[15:0]), .cin(sub), .sum(sum1),.cout(cout1) );
add16 hi( .a(a[31:16]), .b(nb[31:16]), .cin(cout1), .sum(sum2),.cout(cout2));
assign sum={sum2,sum1};
endmodule