关注 望森FPGA 查看更多FPGA资讯
这是望森的第 5 期分享
作者 | 望森
来源 | 望森FPGA
目录
2 Connecting ports by position
本文中的代码都能够正常运行,请放心食用😋~
练习的官方网站是:https://hdlbits.01xz.net/
注:作者将每个练习的知识点都放在了题目和答案之后
1 Modules
题目:
下图显示了一个带有子模块的非常简单的电路。在本练习中,请你创建模块 mod_a 的一个实例,然后将模块的三个引脚(in1、in2 和 out)连接到顶层模块的三个端口(线路 a、b 和 out)。
模块 mod_a 已为您提供 —— 您必须实例化它。连接模块时,只有模块上的端口很重要。您不需要知道模块内部的代码。
模块 mod_a 的代码如下所示:
module mod_a ( input in1, input in2, output out );
// Module body
endmodule
模块层次结构是通过在一个模块内实例化另一个模块来创建的。所有使用的模块都属于同一个项目,一个模块的代码不会写在另一个模块的主体内(不同模块的代码不会嵌套)。
您可以通过端口名称或端口位置将信号连接到模块,请尝试这两种方法。
答案:
方法一:
module top_module ( input a, input b, output out );
mod_a instance1 (
.out (out),
.in1 (a),
.in2 (b)
);
endmodule
方法二:
module top_module ( input a, input b, output out );
mod_a instance1 (
a,
b,
out
);
endmodule
知识点:
将信号连接到模块端口
有两种常用方法将线路连接到端口:按位置、按名称。
按位置
按位置将线路连接到端口的语法应该很熟悉,因为它使用类似 C 的语法。实例化模块时,端口根据模块的声明从左到右连接。例如:
mod_a instance1 ( wa, wb, wc );
这将实例化 mod_a 类型的模块并为其指定实例名称“instance1”,然后将信号 wa(新模块外部)连接到新模块的第一个端口(in1),将 wb 连接到第二个端口(in2),将 wc 连接到第三个端口(out)。
此语法的一个缺点是,如果模块的端口列表发生变化,还需要找到并更改模块的所有实例以匹配新模块。
按名称
按名称将信号连接到模块的端口可使线路保持正确连接,即使端口列表发生变化也是如此。
但是,此语法更冗长。例如:
mod_a instance2 ( .out(wc), .in1(wa), .in2(wb) );
上述代码实例化一个名为“instance2”的 mod_a 类型的模块,然后将信号 wa(模块外部)连接到名为 in1 的端口,将 wb 连接到名为 in2 的端口,将 wc 连接到名为 out 的端口。
请注意:此处端口的顺序无关紧要,因为无论其在子模块端口列表中的位置如何,都将连接到正确的名称。还请注意此语法中端口名称前面的句点。
在Verilog实际设计中,我们一般只使用“按名称”的模块实例化方法!
2 Connecting ports by position
题目:
这个问题与上一个问题(模块)类似。
您将获得一个名为 mod_a 的模块,该模块有 2 个输出和 4 个输入,按此顺序排列。您必须按位置将 6 个端口连接到顶层模块的端口 out1、out2、a、b、c 和 d,按此顺序排列。
您将获得以下模块:
module mod_a ( output, output, input, input, input, input );
答案:
module top_module (
input a,
input b,
input c,
input d,
output out1,
output out2
);
mod_a u_mod_a(
out1,out2,a,b,c,d
);
endmodule
3 Connecting ports by name
题目:
这个问题类似于1 Modules。
您将获得一个名为 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 (
.out1(out1),
.out2(out2),
.in1(a),
.in2(b),
.in3(c),
.in4(d)
);
endmodule
4 Three modules
题目:
您将获得一个具有两个输入和一个输出的模块 my_dff(实现 D 触发器)。实例化其中三个,然后将它们链接在一起以形成长度为 3 的移位寄存器。clk 端口需要连接到所有实例。
提供给您的模块是:module my_dff ( input clk, input d, output q );
请注意:要进行内部连接,您需要声明一些线路。命名线路和模块实例时要小心:名称必须是唯一的。
答案:
module top_module ( input clk, input d, output q );
wire q1;
my_dff my_dff_1(
.clk(clk),
.d(d),
.q(q1)
);
wire q2;
my_dff my_dff_2(
.clk(clk),
.d(q1),
.q(q2)
);
my_dff my_dff_3(
.clk(clk),
.d(q2),
.q(q)
);
endmodule
5 Modules and vectors
题目:
本练习的模块端口不再是单个引脚,现在我们有了带有向量作为端口的模块,您可以将线向量而不是普通线连接到这些端口。端口的向量位宽虽然不必与连接到它的线相匹配,但这会导致向量的零填充或截断。本练习不使用向量位宽不匹配的连接。
您将获得一个具有两个输入和一个输出的模块 my_dff8(实现一组 8 个 D 触发器)。实例化其中三个,然后将它们链接在一起以形成一个长度为 3 的 8 位宽移位寄存器。此外,创建一个 4 对 1 多路复用器(未提供),根据 sel[1:0] 选择要输出的内容:
输入 d 处的值、第一个、第二个或第三个 D 触发器之后的值。 (本质上,sel 选择延迟输入的周期数,从零到三个时钟周期。)
提供给您的模块是:module my_dff8 ( input clk, input [7:0] d, output [7:0] q );
不提供多路复用器。一种编写方法是在 always 块内使用 case 语句。
答案:
module top_module (
input clk,
input [7:0] d,
input [1:0] sel,
output [7:0] q
);
wire [7:0] q1;
my_dff8 my_dff8_1(
.clk(clk),
.d(d),
.q(q1)
);
wire [7:0] q2;
my_dff8 my_dff8_2(
.clk(clk),
.d(q1),
.q(q2)
);
wire [7:0] q3;
my_dff8 my_dff8_3(
.clk(clk),
.d(q2),
.q(q3)
);
always@(*)begin
case(sel)
2'd0 : q = d;
2'd1 : q = q1;
2'd2 : q = q2;
2'd3 : q = q3;
default q = q;
endcase
end
endmodule
知识点:
错解分析:【中间变量q1、q2、q3位宽声明错误,导致结果错误】
module top_module (
input clk,
input [7:0] d,
input [1:0] sel,
output [7:0] q
);
wire q1;
my_dff8 my_dff8_1(
.clk(clk),
.d(d),
.q(q1)
);
wire q2;
my_dff8 my_dff8_2(
.clk(clk),
.d(q1),
.q(q2)
);
wire q3;
my_dff8 my_dff8_3(
.clk(clk),
.d(q2),
.q(q3)
);
always@(*)begin
case(sel)
2'd0 : q = d;
2'd1 : q = q1;
2'd2 : q = q2;
2'd3 : q = q3;
default q = q;
endcase
end
endmodule
6 Adder 1
题目:
您将获得一个执行 16 位加法的 add16 模块。实例化其中两个以创建一个 32 位加法器。
一个 add16 模块计算加法结果的低 16 位,而第二个 add16 模块在从第一个加法器接收进位后计算结果的高 16 位。
您的 32 位加法器不需要处理进位输入(假设为 0)或进位输出(忽略),但内部模块需要处理才能正常运行。(换句话说,add16 模块执行 16 位 a + b + cin,而您的模块执行 32 位 a + b)。
请按下图所示将模块连接在一起。
提供的 add16 模块具有以下声明:
module add16 ( input[15:0] a, input[15:0] b, input cin, output[15:0] sum, output cout );
答案:
module top_module(
input [31:0] a,
input [31:0] b,
output [31:0] sum
);
wire [15:0] sum1;
wire cout;
add16 add16_1(
.a(a[15:0]),
.b(b[15:0]),
.cin(1'b0),
.sum(sum1),
.cout(cout)
);
wire [15:0] sum2;
add16 add16_2(
.a(a[31:16]),
.b(b[31:16]),
.cin(cout),
.sum(sum2),
.cout()
);
assign sum = {sum2,sum1};
endmodule
7 Adder 2
题目:
在本练习中,您将创建一个具有两层层次结构的电路。您的 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, provided — 由 16 个... 组成的 16 位加法器模块
-
add1 — 1 位全加法器模块。
如果您的提交缺少模块 add1,您将收到一条错误消息,提示“Error (12006): Node instance "user_fadd[0].a1" instantiates undefined entity "add1"。
答案:
1.真值表
a | b | cin | sum | cout |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 0 | 1 | 1 | 0 |
0 | 1 | 0 | 1 | 0 |
0 | 1 | 1 | 0 | 1 |
1 | 0 | 0 | 1 | 0 |
1 | 0 | 1 | 0 | 1 |
1 | 1 | 0 | 0 | 1 |
1 | 1 | 1 | 1 | 1 |
2.布尔代数
Sum = a ^ b ^ cin
卡诺图化简Cout:
Cout = a&b | a&cin | b&cin
3.代码
module top_module (
input [31:0] a,
input [31:0] b,
output [31:0] sum
);//
wire [15:0] sum1;
wire cout;
add16 add16_1(
.a(a[15:0]),
.b(b[15:0]),
.cin(1'b0),
.sum(sum1),
.cout(cout)
);
wire [15:0] sum2;
add16 add16_2(
.a(a[31:16]),
.b(b[31:16]),
.cin(cout),
.sum(sum2),
.cout()
);
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
8 Carry-select adder
题目:
上一个练习中的行波进位加法器有一个缺点:加法器计算进位输出(在最坏情况下从进位输入计算)的延迟相当慢,并且第二级加法器在第一级加法器完成之前无法开始计算其进位输出,这使得加法器变慢。
一个改进方案是进位选择加法器,如下所示。第一级加法器与以前相同,但我们复制了第二级加法器,一个假设进位输入=0,另一个假设进位输入=1,然后使用快速 2 对 1 多路复用器来选择哪个结果是正确的。
在本练习中,为您提供了与上一个练习相同的模块 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 );
答案:
module top_module(
input [31:0] a,
input [31:0] b,
output [31:0] sum
);
wire [15:0] sum1;
wire cout;
add16 add16_1(
.a(a[15:0]),
.b(b[15:0]),
.cin(1'b0),
.sum(sum1),
.cout(cout)
);
wire [15:0] sum2;
add16 add16_2(
.a(a[31:16]),
.b(b[31:16]),
.cin(1'b0),
.sum(sum2),
.cout()
);
wire [15:0] sum3;
add16 add16_3(
.a(a[31:16]),
.b(b[31:16]),
.cin(1'b1),
.sum(sum3),
.cout()
);
wire [15:0] sum4;
always@(*)begin
case(cout)
1'b0 : sum4 = sum2;
1'b1 : sum4 = sum3;
default sum4 = sum4;
endcase
end
always@(*)begin
sum = {sum4,sum1};
end
endmodule
9 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 );
使用 32 位宽的 XOR 门在 sub 为 1 时反转 b 输入。(这也可以看作是 b[31:0] 与 sub 进行 XOR 运算 32 次。请参阅复制运算符)。还需要将 sub 输入连接到加法器的进位输入。
答案:
module top_module(
input [31:0] a,
input [31:0] b,
input sub,
output [31:0] sum
);
wire [31:0] b_t;
always@(*)begin
b_t = b ^ {32{sub}};
end
wire [15:0] sum1;
wire cout;
add16 add16_1(
.a(a[15:0]),
.b(b_t[15:0]),
.cin(sub),
.sum(sum1),
.cout(cout)
);
wire [15:0] sum2;
add16 add16_2(
.a(a[31:16]),
.b(b_t[31:16]),
.cin(cout),
.sum(sum2),
.cout()
);
always@(*)begin
sum = {sum2,sum1};
end
endmodule
知识点: 为什么使用(a + ~b + 1)而不是(a + ~b)实现减法运算?
因为在大多数数字系统中,负数使用二进制补码表示法表示。
在二进制补码中,您需要对 b 其按位求补(即反转所有位,用 ~b 表示)、然后加 1 的方法得到 -b 。
具体的补码相关知识请查阅数字电路相关资料。
- END -
微信公众号/CSDN/EETOP搜索【望森FPGA】,查看更多FPGA资讯~
相关推荐文章,点击跳转:
FPGA新手必用,Verilog HDL编程学习网站推荐 —— HDLBits-CSDN博客
Getting Started| HDLBits启航篇!-CSDN博客