刚学前端设计的时候,听到的就是组合逻辑、时序逻辑,很重要!但是究竟有什么用?到底怎么体现,没有多少老师可以明确指出来,当自己看的东西多了,就可以理解了,甚至可以得出自己的范式。
到目前为止,要想掌握组合逻辑,就请先掌握本文列出的编码器、译码器、数据选择器、加法器等简单的组合逻辑电路。
刚开始听verilog的时候,估计都听过3-8译码器,但是听过编码器吗?
编码器和译码器可以将不同输入数据变换为不同的输出数据。
编码器的数据输入比编码后的输出位数大,编码器常用来减少数据通道数目;而译码器与之相反。
编码器把输入信号编写成一个对应的二进制信号,即把2^N个输入信号转化为N位编码输出。还可以分为:普通编码器和优先编码器。
普通编码器:只能一个一个的输入,如果同时输入两个信号,编码器不能识别;
优先编码器:如果输入两个信号,对高位优先识别。(联想if-else的优先级)
module code8_3(
I,
O
);
input [7:0] I;
output [2:0] O;
reg [2:0] O;
always@(I)
begin
case(I)
8'b0000_0001:O = 3'b111;
8'b0000_0010:O = 3'b110;
8'b0000_0100:O = 3'b101;
8'b0000_1000:O = 3'b100;
8'b0001_0000:O = 3'b011;
8'b0010_0000:O = 3'b010;
8'b0100_0000:O = 3'b001;
8'b1000_0000:O = 3'b000;
default: O=3'bxxx;
endcase
end
endmodule
`timescale 1ns/1ns
`define clk_period 20
module code8_3_tb;
reg [7:0] I;
wire [2:0] O;
reg clk;
code8_3 code8_3_inst(
.I(I),
.O(O)
);
initial clk = 1'b1;
always #`clk_period clk = ~clk;
initial begin
I = 8'b0000_0001;
#`clk_period;
I = 8'b0000_0010;
#`clk_period;
I = 8'b0000_0100;
#`clk_period;
I = 8'b0000_1000;
#`clk_period;
I = 8'b0001_0000;
#`clk_period;
I = 8'b0010_0000;
#`clk_period ;
I = 8'b0100_0000;
#`clk_period;
I = 8'b1000_0000;
#`clk_period;
I = 8'b0000_0000;
#`clk_period;
$stop;
end
endmodule
详细介绍见:优先级编码器74LS148的电路结构、工作原理及使用方法
module code_8_3(
EI_n,
I,
Y,
GS_n,
EO_n
);
input EI_n;
input [7:0] I;
output [2:0] Y;
output GS_n;
output EO_n;
reg [2:0] Y;
reg GS_n;
reg EO_n;
always@(EI_n or I)
begin
if(!EI_n)begin
if (~I[7]) begin Y = 3'd0;EO_n = 1;GS_n = 0;end
else if (~I[6]) begin Y = 3'd1;EO_n = 1;GS_n = 0;end
else if (~I[5]) begin Y = 3'd2;EO_n = 1;GS_n = 0;end
else if (~I[4]) begin Y = 3'd3;EO_n = 1;GS_n = 0;end
else if (~I[3]) begin Y = 3'd4;EO_n = 1;GS_n = 0;end
else if (~I[2]) begin Y = 3'd5;EO_n = 1;GS_n = 0;end
else if (~I[1]) begin Y = 3'd6;EO_n = 1;GS_n = 0;end
else if (~I[0]) begin Y = 3'd7;EO_n = 1;GS_n = 0;end
else begin Y = 3'd7;EO_n = 0;GS_n = 1;end
end
else begin Y = 3'd7;EO_n = 1;GS_n = 1;end
end
endmodule
`timescale 1ns/1ns
`define clk_period 20
module code_8_3_tb;
reg EI_n;
reg [7:0] I;
wire [2:0] Y;
wire GS_n;
wire EO_n;
reg clk;
code_8_3 code_8_3_inst(
.EI_n(EI_n),
.I(I),
.Y(Y),
.GS_n(GS_n),
.EO_n(EO_n)
);
initial clk = 1'b1;
always #`clk_period clk = ~clk;
initial begin
EI_n = 1'b1;
I = 8'b1101_1111;
#`clk_period;
EI_n = 1'b0;
I = 8'b1111_1110;
#`clk_period;
I = 8'b0111_1101;
#`clk_period;
I = 8'b1010_1010;
#`clk_period;
I = 8'b1100_1111;
#`clk_period;
I = 8'b1111_1111;
#`clk_period ;
I = 8'b1110_0100;
#`clk_period;
I = 8'b1111_1111;
#`clk_period;
I = 8'b1110_0100;
#`clk_period;
I = 8'b1110_0110;
#`clk_period;
I = 8'b1111_1010;
#`clk_period;
I = 8'b1111_1100;
#`clk_period;
$stop;
end
endmodule
从电路功能上看,译码器和编码器没有实质的差别。
编码器把输入信号编写成一个对应的二进制信号,即把2^N个输入信号转化为N位编码输出。
而译码器是把输入的N位二进制信号转换成2^N个代表代码原意的状态信号并输出。
module decoder3_8(
Y,
S,
A
);
output [7:0] Y;
input [2:0] S;
input [2:0] A;
reg [7:0] Y;
always@(*)
begin
if(S[0] & (S[1]==0) & (S[2]==0))begin
case(A)
3'b000:Y = 8'b1111_1110;
3'b001:Y = 8'b1111_1101;
3'b010:Y = 8'b1111_1011;
3'b011:Y = 8'b1111_0111;
3'b100:Y = 8'b1110_1111;
3'b101:Y = 8'b1101_1111;
3'b110:Y = 8'b1011_1111;
3'b111:Y = 8'b0111_1111;
default Y=8'bX;
endcase
end
else Y = 8'b1111_1111;
end
endmodule
`timescale 1ns/1ns
module decoder3_8_tb;
wire [7:0] Y;
reg [2:0] S;
reg [2:0] A;
decoder3_8 decoder3_8_tb(
.Y(Y),
.S(S),
.A(A)
);
initial begin
A = 3'b000;
S = 3'b000;
#20;
A = 3'b001;
S = 3'b010;
#20;
A = 3'b000;
S = 3'b001;
#20;
A = 3'b001;
#20;
A = 3'b010;
#20;
A = 3'b011;
#20;
A = 3'b100;
#20 ;
A = 3'b101;
#20;
A = 3'b110;
#20;
A = 3'b111;
#20;
A = 3'b000;
#20;
$stop;
end
endmodule
在做前面几个仿真文件时,出现了时钟,到这里突然意识到,组合逻辑仿真时不必引入时钟,只需要利用延迟就可以得出仿真结果。
请点击查看译码器和数据选择器
输入S1 | 输入S0 | 输出Y |
---|---|---|
0 | 0 | D0 |
0 | 1 | D1 |
1 | 0 | D2 |
1 | 1 | D3 |
module MUX4_1a(
D0,D1,D2,D3,
S0,S1,
Y
);
input D0,D1,D2,D3;
input S0,S1;
output Y;
wire notS0,notS1,Y0,Y1,Y2,Y3,Y4;
not not0(notS0,S0);
not not1(notS1,S1);
and and0(Y0,notS0,notS1,D0);
and and1(Y1,notS0,S1,D1);
and and2(Y2,S0,notS1,D2);
and and3(Y3,S0,S1,D3);
or or1(Y,Y0,Y1,Y2,Y3);
endmodule
module MUX4_1b(
D0,D1,D2,D3,
S0,S1,
Y
);
input D0,D1,D2,D3;
input S0,S1;
output Y;
assign Y = ((D0 & ~S0 & ~S1)|(D1 & ~S0 & S1)|(D2 & S0 & ~S1)|(D3 & S0 & S1));
endmodule
module MUX4_1c(
D0,D1,D2,D3,
S0,S1,
Y
);
input D0,D1,D2,D3;
input S0,S1;
output Y;
assign Y = S1?(S0?D3:D2):(S0?D1:D0);
endmodule
module MUX4_1d(
D0,D1,D2,D3,
S0,S1,
Y
);
input D0,D1,D2,D3;
input S0,S1;
output Y;
reg Y;
//采用了if-else结构
always@(D0 or D1 or D2 or D3 or S0 or S1)
begin
if ({S1,S0}==2'b00) Y=D0;
else if ({S1,S0}==2'b01) Y=D1;
else if ({S1,S0}==2'b10) Y=D2;
else if ({S1,S0}==2'b11) Y=D3;
else Y=1'bx;
end
endmodule
module MUX4_1e(
D0,D1,D2,D3,
S0,S1,
Y
);
input D0,D1,D2,D3;
input S0,S1;
output Y;
reg Y;
//采用了case结构
always@(D0 or D1 or D2 or D3 or S0 or S1)
begin
case({S1,S0})
2'b00:Y=D0;
2'b01:Y=D1;
2'b10:Y=D2;
2'b11:Y=D3;
default: Y=1'bx;
endcase
end
endmodule
随着数据选择器的输入增多,if-else和case综合出来的电路差异就很大。这种情况下就推荐使用case语句。
加法运算是最基本的运算,简单的乘法、除法、减法和复杂的FFT(快速傅里叶变换)都可以分解为加法运算。
点击阅读笔记连载 | Day7 【半加器、全加器、16位加法器、16位减法器设计】 【原理及verilog实现、仿真】篇
半加器,就是y=a+b,不考虑进位,如下真值表,a、b表示2个相加的数,y表示和,Co表示结果有没有进位。
从真值表可以得出,y和Co的布尔表达式
Y = (~a&b) | (a&~b)
Co = a&b
module half_adder(
input a, //第一个加数a
input b, //第二个加数b
output sum, //和
output cout //位
);
assign sum=a ^ b; //sum=a⊕b
assign cout=a & b; //cout=ab
endmodule
强烈推荐阅读:Verilog全加器
全加器,就是y=a+b+c_up,要考虑进位,如下真值表,a、b表示2个相加的数,c_up表示低位向本位的进位标志,Co表示计算结果有没有向高位进位
从真值表可以得出,y和Co的布尔表达式
y = (~a&~b&c_up) | (~a&b&~c_up) | (a&~b&~c_up) | (a&b&c_up)
Co = (~a&b&c_up) | (a&~b&c_up) | (a&b&~c_up) | (a&b&c_up)
减法器,就是y=a-b-c_down,要考虑借位,如下真值表,a、b表示2个相减的数,c_down低位向本位的借位标志,Co表示计算结果有没有向高位借位
从真值表可以得出,y和Co的布尔表达式
y = (~a&~b&c_down) | (~a&b&~c_down) | (a&~b&~c_down) | (a&b&c_down)
C0 = (~a&~b&c_down) | (~a&b&~c_down) | (~a&b&c_down) | (a&b&c_down)
输入a | 输入b | 输出F |
---|---|---|
0 | 0 | 1 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
module nand_2(
a,
b,
F
);
input a;
input b;
output F;
assign F = ~(a&b);
endmodule
module nand_2(
a,
b,
F
);
input a;
input b;
output F;
reg F;
always@(a or b)
begin
if( a==1'b1 && b==1'b1)
F = 0;
else
F = 1;
end
endmodule
三态门大多数挂在总线上,以实现不同数字部件之间的数据传输。
三态门是在普通电路的基础上附加控制电路,从而使得门电路的输出端除了输出高电平、低电平外,还可以出现第三种状态,即高阻状态(或称禁止态)。
din为数据输入端,dout为数据输出端,en为控制输入端。
数据输入 | 控制输入端 | 数据输出 |
---|---|---|
din | en | dout |
x | 0 | z |
0 | 1 | 0 |
1 | 1 | 1 |
module tri_gate(
din,
en,
dout
);
input din;
input en;
output dout;
reg dout;
always@(din or en)
begin
if(en)
dout = din;
else
dout = 1'bz;
end
endmodule
module tri_gate(
din,
en,
dout
);
input din;
input en;
output dout;
assign dout=en?din:1'bz;
endmodule