9.3 实战演练——译码器
9.3.1 实验目标
设计并仿真验证3-8译码器。
注意:3-8译码器的上板验证需要用到8个LED灯或者数码管,因为板卡LED灯数目不够且数码管部分还未讲解,此处对3-8译码器只进行仿真验证,不再上板测试,读者可在学习完数码管相关知识后自行验证。
9.3.2 程序设计
1. 模块框图
根据功能分析,该工程只需实现一个3-8译码器,所以设计成一个模块即可。模块名为decoder3_8,模块的输入为3个1bit信号,输出为1个8bit信号,实现通过输入3个信号组成的二进制的8种情况来控制对应输出8bit的8种不同状态。根据上面的分析设计出的Visio框图如图9-15所示。
图9-15 模块框图
端口列表与功能描述如表9-3所示。
表9-3 输入输出信号描述
2. 波形图绘制
和之前一样,框图结构设计完毕后就可以通过波形图的方式来描述输入和输出之间具体的映射关系。经分析得,输入为3个1bit信号,其任意二进制组合有8种情况,每种组合与out输出8bit的8种状态一一对应,实现由3种输入控制对应的8种输出的译码效果。我们根据上面的分析列出如表9-4所示的真值表,然后再根据真值表的输入与输出的对应关系画波形图。其波形如图9-16所示,与真值表的关系一一对应。
表9-4 3-8译码器真值表
图9-16 信号波形关系图
3. 代码编写
实现3-8译码器功能的Verilog代码形式也有很多种,我们这里主要列举两种最容易理解的方法,通过这两种方法的用法对比,学习者能对if-else和case这两种语法有一个比较深刻的理解。
(1)用if-else语句实现译码器
用if-else语句实现译码器的参考代码具体参见代码清单9-5。
代码清单9-5 if-else语句实现译码器(decoder 3_8.v)
1 module decoder3_8 2 ( 3 input wire in1 , //输入信号in1 4 input wire in2 , //输入信号in2 5 input wire in3 , //输入信号in3 6 7 output reg [7:0] out //输出信号out 8 ); 9 10 // out:根据3个输入信号选择输出对应的8bit out信号 11 always@(*) 12 //使用“{}”位拼接符将3个1bit数据按照顺序拼成一个3bit数据 13 if({in1, in2, in3} == 3’b000) 14 out = 8'b0000_0001; 15 else if({in1, in2, in3} == 3'b001) 16 out = 8'b0000_0010; 17 else if({in1, in2, in3} == 3'b010) 18 out = 8'b0000_0100; 19 else if({in1, in2, in3} == 3'b011) 20 out = 8'b0000_1000; 21 else if({in1, in2, in3} == 3'b100) 22 out = 8'b0001_0000; 23 else if({in1, in2, in3} == 3'b101) 24 out = 8'b0010_0000; 25 else if({in1, in2, in3} == 3'b110) 26 out = 8'b0100_0000; 27 else if({in1, in2, in3} == 3'b111) 28 out = 8'b1000_0000; 29 else 30 //最后一个else对应的if中的条件只有一种情况,还可能产生上面另外的7种情况, 31 //如果不加这个else综合器,则会把不符合该if条件的另外7种情况都考虑进去, 32 //会产生大量的冗余逻辑并产生latch(锁存器),所以在组合逻辑中最后一个if 33 //后一定要加上else,并任意指定一种确定的输出情况 34 out = 8'b0000_0001; 35 36 endmodule
根据上面RTL代码综合出的RTL视图如图9-17所示。
图9-17 用if-else实现方法生成的RTL视图
(2)用case语句实现译码器
用case语句实现译码器的参考代码具体参见代码清单9-6。
代码清单9-6 case语句实现译码器(decoder 3_8.v)
1 module decoder3_8 2 ( 3 input wire in1 , //输入信号in1 4 input wire in2 , //输入信号in2 5 input wire in3 , //输入信号in3 6 7 output reg [7:0] out //输出信号out 8 ); 9 10 // out:根据输入的3bit in信号选择输出对应的8bit out信号 11 always@(*) 12 case({in1, in2, in3}) 13 3'b000 : out = 8'b0000_0001; //输入与输出的8种译码对应关系 14 3'b001 : out = 8'b0000_0010; 15 3'b010 : out = 8'b0000_0100; 16 3'b011 : out = 8'b0000_1000; 17 3'b100 : out = 8'b0001_0000; 18 3'b101 : out = 8'b0010_0000; 19 3'b110 : out = 8'b0100_0000; 20 3'b111 : out = 8'b1000_0000; 21 //因为case中列举了in所有可能输入的8种情况,且每种情况都有对应确定的输出, 22 //所以此处default可以省略,但是为了避免因条件未完全列举而产生latch, 23 //我们默认要加上default,并任意指定一种确定的输出情况 24 default: out = 8'b0000_0001; 25 endcase 26 27 endmodule
根据上面RTL代码综合出的RTL视图如图9-18所示。
图9-18 用case实现方法生成的RTL视图
有了第8章中多路选择器的例子后,我们再使用if-else和case时,想必大家已经不再陌生,对如何编写一个模块的基本结构也有了大概的了解。通过以上两种不同的代码编写方式,我们进行一个总结:经过验证对比发现两种方法最后实现的功能虽然是一样的,然而所得到的RTL视图差别较大,但最后的逻辑资源使用却是相同的(时序逻辑中不一定相同),说明综合器进行了适当的优化。if-else的这种写法是存在优先级的,即第一个if中的条件的优先级最高,后面的if中的条件的优先级依次递减,幸好该if中的条件只有一个,也只会产生一种情况,并不会产生优先级的冲突,所以这里优先级的高低关系并不会对最后的功能产生任何影响。而case在任何时候都不存在优先级的问题,而是通过判断case中的条件来选择对应的输出。
通过RTL视图我们也能够发现if括号里面的条件会生成名为“EQUAL”的比较器单元,而case则会生成名为“DECODER”的译码器单元,这些单元并不是FPGA硬件底层中的最小单元,而只是一种用于RTL视图中易于表达的抽象后的图形,使之更易于我们观察,理解其代码所实现功能的大致硬件结构,也符合“HDL(硬件描述语言)”所表述的含义。
4. 仿真验证
(1)仿真文件编写
译码器仿真参考代码如代码清单9-7所示。
代码清单9-7 decoder 3_8模块仿真代码(tb_decoder3_8.v)
1 `timescale 1ns/1ns 2 module tb_decoder3_8(); 3 4 // reg define 5 reg in1; 6 reg in2; 7 reg in3; 8 9 // wire define 10 wire [7:0] out; 11 12 //初始化输入信号 13 initial begin 14 in1 <= 1'b0; 15 in2 <= 1'b0; 16 in3 <= 1'b0; 17 end 18 19 // in1:产生输入随机数,模拟输入端1的输入情况 20 always #10 in1 <= {$random} % 2; 21 22 // in2:产生输入随机数,模拟输入端2的输入情况 23 always #10 in2 <= {$random} % 2; 24 25 // in3:产生输入随机数,模拟输入端3的输入情况 26 always #10 in3 <= {$random} % 2; 27 28 // ------------------------------------------------------------ 29 initial begin 30 $timeformat(-9, 0, "ns", 6); 31 $monitor(“@time %t:in1=%b in2=%b in3=%b out=%b”, $time, in1, in2, in3, out); 32 end 33 // ------------------------------------------------------------ 34 35 // -------------decoder3_8_inst---------------- 36 decoder3_8 decoder3_8_ins 37 ( 38 .in1(in1), //input in1 39 .in2(in2), //input in2 40 .in3(in3), //input in3 41 42 .out(out) //output [7:0] out 43 ); 44 45 endmodule
(2)仿真波形分析
RTL代码设计完成后,我们按照流程编写Testbench,然后启动ModelSim进行仿真测试验证。同样,我们也让波形运行了500ns,通过图9-19所示的波形可以观察到,3个输入的in均为任意随机数,所以由in组成的3bit数据也为随机数,而每个随机数都对一个out输出8bit的值,仔细核对输入in和输出out之间的对应关系,发现波形中3个输入信号in与输出信号out之间的对应关系和编写的代码中的译码关系是完全一致的,完全符合代码中的逻辑设计。
图9-19 仿真波形图
观察“Transcript”界面(见图9-20)中打印的结果,将其与前面绘制的真值表进行比对,发现结果是一致的,从而进一步验证了RTL代码设计的正确性。
图9-20 打印结果