4. 简单组合逻辑 — 译码器

4.1. 章节导读

上一章节我们学习、设计并实现了简单组合逻辑中的多路选择器,在本章我们用Verilog语言描述一个具有3-8译码器功能的电路,通过学习3-8译码器继续巩固整个设计流程以及语法的使用。

4.2. 理论学习

译码是编码的逆过程,在编码时,每一种二进制代码,都赋予了特定的含义,即都表示了一个确定的信号或者对象。把代码状态的特定含义翻译出来的过程叫做译码,实现译码操作的电路称为译码器。或者说,译码器是可以将输入二进制代码的状态翻译成输出信号,以表示其原来含义的电路。

译码器(decoder)是一类多输入多输出组合逻辑电路器件,其可以分为:变量译码和显示译码两类。变量译码器一般是一种较少输入变为较多输出的器件,常见的有n线-2^n线译码和8421BCD码译码两类;显示译码器用来将二进制数转换成对应的七段码,一般其可分为驱动LED和驱动LCD两类。

本节我们主要讲解变量译码,最常见的变量译码器为3-8译码器,主要用于端口的扩展。假如我们有8个led灯需要单独控制,理论上我们需要用8个I/O口,普通的单片机也够用,但是如果我们控制的不是8个led灯,而是一个点阵屏,那就可想而知我们要使用的I/O口数量不是一般控制器就能满足的了,即便是I/O资源丰 富的FPGA在面对巨大的点阵屏时也可能面临管脚资源不够用的尴尬境地。此种情况下使用3-8译码器就可以很好的解决这个问题,我们可以通过控制器控制3个I/O输出的8种情况来分别控制8个输出状态,相当于用3个I/O口就可以独立控制8个led灯,即一个3-8译码器就能够节约出来5个I/O口,算起来是相当合算 的。现在的3-8译码器大都做成了独立ASIC芯片,价格也往往非常便宜。

4.3. 实战演练

4.3.1. 实验目标

设计并仿真验证3-8译码器。

注:3-8译码器的上板验证需要用到8个led灯或者数码管,因为板卡led灯数目不够且数码管部分还未作讲解,3-8译码器只进行仿真验证,不再上板测试,上板测试可等到数码管讲解完毕后,读者自行验证。

4.3.2. 程序设计

4.3.2.1. 模块框图

根据功能分析,该工程只需实现一个3-8译码器的功能,所以设计成一个模块即可。模块命名decoder3_8,模块的输入为3个1bit信号,输出为1个8bit信号,实现通过输入3个信号组成的二进制的8种情况来控制对应输出8bit的8种不同状态。根据上面的分析设计出的Visio框图如图 11‑1所示。

decoder002

图 11‑1 模块框图

端口列表与功能描述如表格 11‑1所示。

表格 11‑1 输入输出信号描述

信号

位宽

类型

功能描述

in1

1Bit

Input

输入信号1

in2

1Bit

Input

输入信号2

in3

1Bit

Input

输入信号3

out

8Bit

Output

译码后的输出信号

4.3.2.2. 波形图绘制

和之前一样,框图结构设计完毕后就可以通过波形图的方式来描述输入和输出之间具体的映射关系。经分析得,输入为3个1bit信号,其任意二进制组合有8种情况,每种组合与out输出8bit的 8种状态一一对应,实现由3种输入控制对应的8种输出的译码效果。我们根据上面的分析列出如表格 11‑2所示的真值表,然后再根据真值表的输入与输出的对应关系画波形图。其波形如图 11‑2所示,与真值表的关系一一对应。

表格 11‑2 3-8译码器真值表

输入(input)

输出(output)

in1

in2

in3

out

0

0

0

0000_0001

0

0

1

0000_0010

0

1

0

0000_0100

0

1

1

0000_1000

1

0

0

0001_0000

1

0

1

0010_0000

1

1

0

0100_0000

1

1

1

1000_0000

decoder003

图 11‑2 信号波形关系图

4.3.2.3. 代码编写

实现3-8译码器功能的Verilog代码形式也有很多种,我们这里主要列举两种最容易理解的方法,通过这两种方法的用法对比,使学习者能对if-else和case这两种语法有一个比较深刻的理解。

  1. always中if-else实现方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
module decoder3_8
(
input wire in1 , //输入信号in1
input wire in2 , //输入信号in2
input wire in3 , //输入信号in3

output reg [7:0] out //输出信号out
);

//out:根据3个输入信号选择输出对应的8bit out信号
always@(*)
//使用"{}"位拼接符将3个1bit数据按照顺序拼成一个3bit数据
if({in1, in2, in3} == 3'b000)
out = 8'b0000_0001;
else if({in1, in2, in3} == 3'b001)
out = 8'b0000_0010;
else if({in1, in2, in3} == 3'b010)
out = 8'b0000_0100;
else if({in1, in2, in3} == 3'b011)
out = 8'b0000_1000;
else if({in1, in2, in3} == 3'b100)
out = 8'b0001_0000;
else if({in1, in2, in3} == 3'b101)
out = 8'b0010_0000;
else if({in1, in2, in3} == 3'b110)
out = 8'b0100_0000;
else if({in1, in2, in3} == 3'b111)
out = 8'b1000_0000;
else
//最后一个else对应的if中的条件只有一种情况,还可能产生以上另外的7种情况
//如果不加这个else综合器会把不符合该if中条件的上面另外7种情况都考虑进去
//会产生大量的冗余逻辑并产生latch(锁存器),所以在组合逻辑中最后一个if
//后一定要加上else,并任意指定一种确定的输出情况
out = 8'b0000_0001;

endmodule

根据上面RTL代码综合出的RTL视图如图 11‑3所示。

decoder004

图 11‑3 RTL视图(一)

(2)always中case实现方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
module decoder3_8
(
input wire in1 , //输入信号in1
input wire in2 , //输入信号in2
input wire in3 , //输入信号in3

output reg [7:0] out //输出信号out
);

//out:根据输入的3bit in信号选择输出对应的8bit out信号
always@(*)
case({in1, in2, in3})
3'b000 : out = 8'b0000_0001; //输入与输出的8种译码对应关系
3'b001 : out = 8'b0000_0010;
3'b010 : out = 8'b0000_0100;
3'b011 : out = 8'b0000_1000;
3'b100 : out = 8'b0001_0000;
3'b101 : out = 8'b0010_0000;
3'b110 : out = 8'b0100_0000;
3'b111 : out = 8'b1000_0000;
//因为case中列举了in所有可能输入的8种情况,且每种情况都有对应确定的输出
//所以此处default可以省略,但是为了以后因不能够完全列举而产生latch
//所以我们默认一定要加上default,并任意指定一种确定的输出情况
default: out = 8'b0000_0001;
endcase

endmodule

根据上面RTL代码综合出的RTL视图如图 11‑4所示。

decoder005

图 11‑4 RTL视图(二)

有了上一章中多路选择器的例子后,我们再使用if-else和case想必大家已经不再陌生,对如何编写一个模块的基本结构也有了大概的了解。通过以上两种不同的代码编写方式,我们进行一个总结:经过验证对比发现两种方法虽然最后实现的功能是一样的,而所得到的RTL视图差别较大,但最后的逻辑资源使用却是相同的(时 序逻辑中不一定相同),说明综合器进行了适当的优化。if-else的这种写法是存在优先级的,即第一个if中的条件的优先级最高,后面的if中的条件的优先级依次递减,好在该if中的条件只有一个,也只会产生一种情况,并不会产生优先级的冲突,所以这里优先级的高低关系并不会对最后的功能产生任何影响。而case在 任何时候都不存在优先级的问题,而是通过判断case中的条件来选择对应的输出。

通过RTL视图我们也能够发现if括号里面的条件会生成名为“EQUAL”的比较器单元,而case则会生成名为“DECODER”的译码器单元,这些单元并不是FPGA硬件底层中最小单元,而只是一种用于RTL视图中易于表达的抽象后的图形,使之更易于我们观察、理解其代码所实现功能的硬件结构的大致样子,也符合了 “HDL(硬件描述语言)”所表述的含义。

4.3.2.4. 仿真验证

仿真文件编写

仿真参考代码如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
\`timescale 1ns/1ns
module tb_decoder3_8();

//reg define
reg in1;
reg in2;
reg in3;

//wire define
wire [7:0] out;

//初始化输入信号
initial begin
in1 <= 1'b0;
in2 <= 1'b0;
in3 <= 1'b0;
end

//in1:产生输入随机数,模拟输入端1的输入情况
always #10 in1 <= {$random} % 2;

//in2:产生输入随机数,模拟输入端2的输入情况
always #10 in2 <= {$random} % 2;

//in3:产生输入随机数,模拟输入端3的输入情况
always #10 in3 <= {$random} % 2;

//------------------------------------------------------------
initial begin
$timeformat(-9, 0, "ns", 6);
$monitor("@time %t:in1=%b in2=%b in3=%b out=%b",$time,in1,in2,in3,out);
end
//------------------------------------------------------------

//-------------decoder3_8_inst----------------
decoder3_8 decoder3_8_ins
(
.in1(in1), //input in1
.in2(in2), //input in2
.in3(in3), //input in3

.out(out) //output [7:0] out
);

endmodule

仿真波形分析

RTL代码设计完成后,我们按照流程编写Testbench,然后启动ModelSim进行仿真测试验证,同样我们也让波形跑了500ns,通过图 11‑5所示的波形我们可以观察到,3个输入的in均为任意随机数,所以由in组成的3bit数据也为随机数,而每个随机数都对一个out输出的8bit值,我们仔细核对 输入in和输出out之间的对应关系,发现波形中3个输入信号in与输出信号out之间的对应关系和编写的代码中的译码关系是完全一致的,完全符合我们代码中的逻辑设计。

decoder006

图 11‑5 仿真波形图

我们观察“Transcript”界面(如图 11‑6所示)中打印的结果,将其与前面绘制的真值表进行比对,发现结果是一致的,从而进一步验证了RTL代码设计的正确性。

decoder007

图 11‑6 打印结果

4.4. 章末总结

本章主要讲解了数字电路中的经典组合逻辑3-8译码器如何用Verilog代码去实现,并对比了if- else语句和case语句所表达的逻辑的异同,希望大家在以后的应用中能够合理、熟练的使用这两种语法。本章所涉及到的大部分语法和知识点都已在上一章中讲解使用过,希望大家能够强化学习,加深对语法的认知,熟练掌握Verilog语言中的“单词(关键字)”和“句子(常用语句)”,并大胆尝试使用。

新语法总结

重点掌握

1、{ ,}位拼接运算符(两个数之间中间用“,”隔开,也可以有多个“,”进行更多位的拼接)

4.5. 拓展训练

体会if-else和case语法的不同,然后自己验证观察if-else实现的译码器中的最后一个else去掉后综合后所生成的RTL视图是怎样的。