文章目录
- 与门
- 或门
- 非门
- 异或门
- 同或门
- 比较器
- 半加器
- 全加器
- 乘法器
- 数据选择器
- 3-8 译码器
- 三态门
组合逻辑电路的特点是任意时刻的输出仅仅取决于输入信号,输入信号变化,输出立即变化,其变化不依赖于时钟。
本文中的例子中模块名都是gate,仿真测试文件中的模块名都是sim_gate。如果你在一个工程下创建了好几个.v文件,要运行其中的一个仿真,可以选中该文件,点击Set as Top,这样仿真的就是置于顶部的那个文件了。
与门
与门的代码如下。
module gate(a,b,c);
input a;
input b;
output c;
assign c = a & b;
endmodule
仿真测试代码如下。
`timescale 1ns / 1ps
module sim_gate();
reg a;
reg b;
wire c;
initial
begin
a = 0;
b = 0;
forever
begin
#({$random}%100) //生成一个0-99的随机数,以 ns 为单位延迟该随机数的时长
a = ~a;
#({$random}%100)
b = ~b;
end
end
gate uut_gate(
.a(a),
.b(b),
.c(c)
);
endmodule
与门的仿真结果如下图所示,仿真运行时间设置为1us。
通过仿真图可以看出来,两个输入都为1时,输出结果才是1。
与门的RTL图如下。
如果上述例子中的a和b位宽大于1,比如a和b的位宽均为4,那么a&b就是对应位的相与,仿真结果如下图所示。
这时RTL图就是下图所示的这样。
或门
或门的代码只需要将与门代码中的"&“改为”|"即可。
或门的仿真结果如下图所示。
通过上面的输出图可以看到,两个输入中只要有一个为1,输出结果就是1。
或门的RTL图如下。
非门
非门的代码比与门的代码更简单,不再需要变量c,b=~a即可,然后把仿真文件中的代码删减一下即可。
非门的仿真结果如下图所示。
非门的输出就是将输入取反。
非门的RTL图如下。
异或门
异或门的代码只需要将与门代码中的"&“改为”^"即可。
异或门的仿真结果如下图所示。
通过上图可以看到,两个输入不同时,输出为1,输入相同时,输出为0。
异或门的RTL图如下。
同或门
同或门的代码只需要将与门代码中的"&“改为”^~"即可。
同或门的仿真结果如下图所示。
通过上图可以看到,两个输入相同时,输出为1,输入不同时,输出为0。
同或门的RTL图如下。
比较器
比较器这里以 c=a>b 为例说明,如果 a>b,那么c的值为1,否则为0。对于二进制来说,只有a=1,b=0时,c=1。
比较器的代码只需要将与门代码中的"&“改为”>"即可。
比较器的仿真结果如下图所示。
该例子中,输入a大于输入b时,即只有 a=1,b=0 时,输出才为1。
比较器的RTL图如下。
半加器
半加器和全加器是算术运算电路中的基本单元,由于半加器不考虑从低位来的进位,所以称为半加器。
半加器的真值表如下表所示。
a | b | sum | count |
---|---|---|---|
0 | 0 | 0 | 0 |
0 | 1 | 1 | 0 |
1 | 0 | 1 | 0 |
1 | 1 | 0 | 1 |
半加器的代码如下。
module gate(a,b,sum,count);
input a;
input b;
output sum;
output count;
assign sum = a + b;
assign count = a & b;
endmodule
仿真测试代码如下。
`timescale 1ns / 1ps
module sim_gate();
reg a;
reg b;
wire sum;
wire count;
initial
begin
a = 0;
b = 0;
forever
begin
#({$random}%100)
a = ~a;
#({$random}%100)
b = ~b;
end
end
gate uut_gate(
.a(a),
.b(b),
.sum(sum),
.count(count)
);
endmodule
半加器的仿真结果如下图所示。
将上图输出和其真值表对照,输出与预期一致。
半加器的RTL图如下。
全加器
全加器在半加器的基础上考虑来自低位的进位,因此实现起来更加复杂,参与运算的数也比半加器多一个。
全加器的真值表如下表所示。
a | b | cin | sum | count |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 1 | 0 | 1 | 0 |
1 | 0 | 0 | 1 | 0 |
1 | 1 | 0 | 0 | 1 |
0 | 0 | 1 | 1 | 0 |
0 | 1 | 1 | 0 | 1 |
1 | 0 | 1 | 0 | 1 |
1 | 1 | 1 | 1 | 1 |
全加器的代码如下。
module gate(a,b,sum,count,cin);
input a;
input b;
input cin;
output sum;
output count;
assign {count,sum} = a + b + cin; //通过一个位拼接运算达到计算count和sum的目的
endmodule
仿真测试代码如下。
`timescale 1ns / 1ps
module sim_gate();
reg a;
reg b;
reg cin;
wire sum;
wire count;
initial
begin
a = 0;
b = 0;
cin = 0;
forever
begin
#({$random}%100)
a = ~a;
#({$random}%100)
b = ~b;
#({$random}%100)
cin = ~cin;
end
end
gate uut_gate(
.a(a),
.b(b),
.cin(cin),
.sum(sum),
.count(count)
);
endmodule
全加器的仿真结果如下图所示。
将上图输出和其真值表对照,输出与预期一致。
全加器的RTL图如下。
乘法器
乘法器这里在设置时将两个乘数设置为两位的,这样其最大表示十进制的3,相乘后最大表示十进制的9,因此需要4位二进制的输出。
乘法器的代码如下。
module gate(a,b,c);
input[1:0] a;
input[1:0] b;
output[3:0] c;
assign c = a * b;
endmodule
仿真测试代码如下。
module sim_gate();
reg[1:0] a;
reg[1:0] b;
wire[3:0] c;
initial
begin
a = 0;
b = 0;
forever
begin
#({$random}%100)
a = {$random}%4;
#({$random}%100)
b = {$random}%4;
end
end
gate uut_gate(
.a(a),
.b(b),
.c(c)
);
endmodule
乘法器的仿真结果如下图所示。
将输入和输出以无符号的十进制数显示,可以清楚地看到两数相乘的结果。
乘法器的RTL图如下。
数据选择器
下面这个例子是四选一数据选择器,选择信号是两位,输入信号是4个,可以将其设置为1位,也可以设置为多位,输出信号与输入信号的位数一致。
四选一数据选择器的选择情况如下表所示。
sel | mux |
---|---|
00(0) | a |
01(1) | b |
10(2) | c |
11(3) | d |
数据选择器的代码如下。
module gate(a,b,c,d,sel,mux);
input[2:0] a;
input[2:0] b;
input[2:0] c;
input[2:0] d;
input[1:0] sel;
output reg[2:0] mux;
always@(a,b,c,d,sel)
begin
case(sel)
2'b00 : mux = a;
2'b01 : mux = b;
2'b10 : mux = c;
2'b11 : mux = d;
endcase
end
endmodule
仿真测试代码如下。
`timescale 1ns / 1ps
module sim_gate();
reg[2:0] a;
reg[2:0] b;
reg[2:0] c;
reg[2:0] d;
reg[1:0] sel;
wire[2:0] mux;
initial
begin
a = 0;
b = 0;
c = 0;
d = 0;
forever
begin
#({$random}%100)
a = {$random}%8;
#({$random}%100)
b = {$random}%8;
#({$random}%100)
c = {$random}%8;
#({$random}%100)
d = {$random}%8;
end
end
initial
begin
sel = 2'b00;
#250 sel = 2'b01;
#250 sel = 2'b10;
#250 sel = 2'b11;
end
gate uut_gate(
.a(a),
.b(b),
.c(c),
.d(d),
.sel(sel),
.mux(mux)
);
endmodule
输入是1位时,数据选择器的仿真结果如下图所示。
可以看到,数据选择器的输出是根据代码编写的那样进行的。
输入是3位时,数据选择器的仿真结果如下图所示。
为了直观的看到不同,将输入位数设置为3位的,输出也是按照代码编写的那样进行的。
输入是1位时,数据选择器的RTL图如下。
输入是3位时,数据选择器的RTL图如下。
3-8 译码器
3-8 译码器的译码情况如下表所示。
addr | decoder |
---|---|
000(0) | 11111110 |
001(1) | 11111101 |
010(2) | 11111011 |
011(3) | 11110111 |
100(4) | 11101111 |
101(5) | 11011111 |
110(6) | 10111111 |
111(7) | 01111111 |
3-8 译码器的代码如下。
module gate(addr,decoder);
input[2:0] addr;
output reg[7:0] decoder;
always@(addr)
begin
case(addr)
3'b000 : decoder = 8'b1111_1110;
3'b001 : decoder = 8'b1111_1101;
3'b010 : decoder = 8'b1111_1011;
3'b011 : decoder = 8'b1111_0111;
3'b100 : decoder = 8'b1110_1111;
3'b101 : decoder = 8'b1101_1111;
3'b110 : decoder = 8'b1011_1111;
3'b111 : decoder = 8'b0111_1111;
endcase
end
endmodule
仿真测试代码如下。
`timescale 1ns / 1ps
module sim_gate();
reg[2:0] addr;
wire[7:0] decoder;
initial
begin
addr = 3'b000;
#125 addr = 3'b001;
#125 addr = 3'b010;
#125 addr = 3'b011;
#125 addr = 3'b100;
#125 addr = 3'b101;
#125 addr = 3'b110;
#125 addr = 3'b111;
end
gate uut_gate(
.addr(addr),
.decoder(decoder)
);
endmodule
3-8 译码器的仿真结果如下图所示。
由上述仿真结果可以看到,译码输出和代码中编写的是一致的。
3-8 译码器的RTL图如下。
三态门
三态门是FPGA中经常用到的器件,使能信号用于打开或关闭三态门。
三态门的代码如下。
module gate(en,in,out);
input en;
input in;
output out;
assign out = en ? in : 1'bz;
endmodule
仿真测试代码如下。
`timescale 1ns / 1ps
module sim_gate();
reg en;
reg in;
wire out;
initial
begin
en = 1;
#250 en = 0;
#250 en = 1;
#250 en = 0;
end
initial
begin
in = 0;
forever
begin
#({$random}%100)
in = ~in;
end
end
gate uut_gate(
.en(en),
.in(in),
.out(out)
);
endmodule
三态门的仿真结果如下图所示。
可以看到,使能信号为1时,输出等于输入信号,使能信号为0时,输出为高阻状态。
三态门的RTL图如下。
接下来在三态门的基础上增加一个输入输出口,将两个三态门进行关联,从而实现双向的输入和输出。
如上图,当en0为0,en1为1时,1通道打开,双向IO bio就等于1通道的in1,故1通道向外发送数据,0通道接收数据,out0等于bio;当en0为1,en1为0时,0通道打开,双向IO bio就等于0通道的in0,0通道向外发送数据,1通道接收数据,out1等于bio。
双向的输入和输出的代码如下。
module gate(en,in,out,bio);
input en;
input in;
output out;
inout bio;
assign bio = en ? in : 1'bz;
assign out = bio;
endmodule
仿真测试代码如下。
module sim_gate();
reg en0;
reg in0;
wire out0;
reg en1;
reg in1;
wire out1;
wire bio;
initial
begin
en0 = 0;
en1 = 1;
#200
en0 = 0;
en1 = 0;
#50
en0 = 1;
en1 = 0;
#200
en0 = 0;
en1 = 0;
#50
en0 = 0;
en1 = 1;
#250
en0 = 1;
en1 = 0;
end
initial
begin
in0 = 0;
in1 = 0;
forever
begin
#({$random}%100)
in0 = ~in0;
#({$random}%100)
in1 = ~in1;
end
end
gate uut_gate0(
.en(en0),
.in(in0),
.out(out0),
.bio(bio)
);
gate uut_gate1(
.en(en1),
.in(in1),
.out(out1),
.bio(bio)
);
endmodule
双向的输入和输出的仿真结果如下图所示。
这里out0和out1的值是一样的,因为在定义时,它们都等于bio的值。
参考资料:
ZYNQ 开发平台 FPGA 教程 AX7020