本节导读
本章将学习掌握BCD码的原理、分类以及优缺点,并以此设计一个多位的8421码计数器并进行验证。学会基本的错误定位以及修改能力。
BCD码(Binary-Coded Decimal)又被称为二进码十进数、二─十进制代码是一种十进制的数字编码,用4位二进制数来表示十进制数中的0~9个十个数之一。BCD编码又可以分成有权码和无权码两种,其中有权码如:8421码、5421码以及2421码等;无权码如:余3码、格雷码以及余3循环码等。
1.1.1 BCD码原理
BCD码中最常用的是8421码,其各个bit权值分别是8d、4d、2d、1d;同理5421码各位的权依次为5d、4d、2d、1d。5421码特点是最高位连续5个0后连续5个1,故其当计数器采用这种编码时,最高位可产生对称方波输出;余3码是在8421码上加0011b的出来的;格雷码的特点是任意两个相邻的代码只有一位二进制数不同,编码格式不唯一;余3循环码具有格雷码的特点并且编码的首尾可以连接来进行循环,这样可用反馈移位寄存器来实现,硬件实现简单。下面表3.4给出常见的几种编码格式:
xxx 常见的BCD码 在实际使用中如不特指BCD码格式均为8421码。通过以上介绍将十进制895转换为BCD码就是1000_1001_0101,同理若将BCD码1001_0110_0100转换为十进制数即为964。 BCD码的运算规则:BCD码是十进制数,而运算器对数据做加减运算时,都是按二进制运算规则进行处理的。这样,当将BCD码传送给运算器进行运算时,其结果需要修正。修正的规则是:当两个BCD码相加,如果和等于或小于1001b(即十进制数9),不需要修正;如果相加之和在1010b 到1111b(即十六进制数 0AH~0FH)之间,则需加6d也就是0110进行修正;如果相加时,本位产生了进位,也需加 6 进行修正。下面举例说明:计算5+8,将5和8转换为8421 BCD码后输入加法器,则运算如下:0101 + 1000 = 1101 结果大于9d,+0110b 即加6d修正得出10011b,补充高位为0001_0011b。即5+8=13,结论正确。 BCD码的主要应用之一就是数码管,假设要将十进制数158显示,一般解决办法是把需要显示的十进制数的个、十、百、千位数等进行拆分,即把158拆分出1、5、8,然后查出对应的数码管显示段码再送去给数码管连接的IO口。这个过程可以进行下面的运算:先进行除法运算158/100得出百位1,再取余158%100= 58后继续进行除法运算58 / 10得出十位5,再进行一次取余158%10,得到个位8。以上过程可以看出需要除法,但是由于除法运算是比较消耗计算时间导致整体需要的指令周期太久。但是如果先将其转换为BCD码,则可大幅度减少运算时间。具体应用将在数码管一讲中详细介绍。 1.1.1 BCD计数器Verilog实现由上面分析可以得出本设计的模块接口示意图如图 3.4‑1所示,其各端口的功能描述如表所示。 图 3.4‑1 BCD计数器模块示意图 xxx BCD计数器模块端口功能描述
建立工程子文件夹后,新建一个以名为BCD_Counter的工程保存在prj下,并在本工程目录的rtl文件夹下新建Verilog file文件在此文件下并以BCD_Counter.v保存。由图 3.4‑1及表10.2即可得出端口列表。(说明:在本书后面章节中将只给出模块示意图以及端口功能描述,不再附端口描述相关代码) 从表3.42可以发现计数器的计数值为9。 reg [3:0]cnt; //定义计数器寄存器 always@(posedge Clk or negedge Rst_n) if(Rst_n == 1'b0) cnt <= 4'd0; else if(Cin == 1'b1)begin if(cnt == 4'd9) cnt <= 4'd0; else cnt <= cnt + 1'b1; end else cnt <= cnt; |
产生进位信号,代码如下: always@(posedge Clk or negedge Rst_n) if (!Rst_n) Cout <= 1'b0; else if(Cin == 1'b1 && cnt ==4'd9) Cout <= 1'b1; else Cout <= 1'b0; |
BCD计数器值输出,代码如下:
1.1.1 激励创建及仿真测试为了测试仿真编写测试激励文件,新建BCD_Counter_tb.v文件保存到testbench文件夹下。本激励文件与3.2节类似,除产生正常的时钟以及复位信号外,还生成了重复30次的占空比为1:5周期为120ns的Cin信号。 initial begin Rst_n = 1'b0; Cin = 1'b0; #(`clock_period*200); Rst_n = 1'b1; #(`clock_period*20); repeat(30)begin Cin = 1'b1; #`clock_period; Cin = 1'b0; #(`clock_period*5); end #(`clock_period*20); $stop; end |
设置好仿真脚本后进行功能仿真,可以看到如图 3.4‑2所示的波形文件,可以看出在复位信号置高后,每当进位输入信号Cin为高时计数值输出q完成一次自加,直到计数值为9后清零重新计数并产生进位信号。 图 3.4‑2 功能仿真波形图 1.1.1 级联BCD计数器设计与仿真现在以上面的BCD计数器为基础设计级联的多位BCD计数器,这里将计数器位数设置为12,即3个BCD计数器级联既可以实现。新建Verilog file文件在此文件下输入以下内容并以BCD_Counter_top.v保存至rtl文件夹下。本文件实现了例化与调用BCD_counter.v文件并将进位信号根据需要连接。 module BCD_Counter_top(Clk, Cin, Rst_n, Cout, q); input Clk;//计数基准时钟 input Cin; //计数器进位输入 input Rst_n; //系统复位 output Cout; //计数进位输出 output [11:0]q; //计数值输出 wire Cout0,Cout1; wire [3:0]q0,q1,q2; assign q = {q2,q1,q0}; BCD_Counter BCD_Counter0( .Clk(Clk), .Cin(Cin), .Rst_n(Rst_n), .Cout(Cout0), .q(q0) ); BCD_Counter BCD_Counter1( .Clk(Clk), .Cin(Cout0), .Rst_n(Rst_n), .Cout(Cout1), .q(q1) ); BCD_Counter BCD_Counter2( .Clk(Clk), .Cin(Cout1), .Rst_n(Rst_n), .Cout(Cout), .q(q2) ); endmodule |
将上述的文件设置为顶层,并再次进行分析和综合直至没有错误以及警告。点击RTLViewer,可以看到图 3.4‑3的模块结构,可以看出符合预期目的。 图 3.4‑3多级BCD计数器RTL视图 为了测试仿真编写测试激励文件,新建BCD_Counter_top_tb.v文件保存到testbench文件夹下,输入以下内容再次进行分析和综合直至没有错误以及警告。本激励文件为了简化分析,复位后将cin一直置高,并延迟一定的时间。由于现在为三级BCD计数器,计数器满值为十六进制的999,特将仿真时间进行了延长至5000个时钟周期。 initial begin Rst_n = 1'b0; Cin = 1'b0; #(`clock_period*200); Rst_n = 1'b1; #(`clock_period*20); Cin = 1'b1; #(`clock_period*5000); $stop; end |
设置好仿真脚本后进行功能仿真,但是这时可以看到如图 3.4‑4所示的波形文件,可以看到进位输出信号Cout在计数值q变为十六进制999后延迟了两个系统周期才有输出,不符合既定设计,即设计存在错误。 图 3.4‑4 多级BCD计数器初次功能仿真 为了定位错误,将子模块的相关信号加入到Wave栏,并再次仿真查看内部数据的信息进行分析解决。 在ModelSim找到Inatance窗口并找到顶层文件,如图 3.4‑5所示点击加号后可以看到本顶层设计调用的模块。如图 3.4‑6所示单击不同的模块在Object栏可以看到其端口列表,选中需要的右键Add Wave,即可将内部信号加入到波形窗口。这里将每个模块的计数值输出信号q以及进位输出信号Cout加入到波形窗口中。 图 3.4‑5 添加内部信号到wave窗口 图 3.4‑6 添加内部信号到Wave窗口 单击工具栏中的Restart来复位仿真,在弹出对话框中全选后点击OK,点击Run-All来重启仿真,如图 3.4‑7、图 3.4‑8、图 3.4‑9所示。 图 3.4‑7 复位仿真 图 3.4‑8 复位仿真 图 3.4‑9 重启仿真 可以看到仿真后加入内部信号的波形较乱没有层次,这里介绍一个分组操作,首先Ctrl+A选中所有wave窗口中的波形,后Ctrl+G进行分组。分组后如图 3.4‑10所示,放大局部信号可以看到造成这个原因是由于每一级的BCD计数器的进位输出信号均延迟了一个时钟周期,从而导致顶层文件进位输出Cout信号输出延迟了三个时钟周期的问题,此时的计数器值已经变为了十六进制的002,而不再是999。 图 3.4‑10 加入内部信号的功能仿真波形 这样就定位了错误,只需将修改进位输出信号Cout修改为与计数值信号q计数到9时同时输出。将产生进位信号的逻辑修改为如下,且将Cout的类型改为wire型。 assign Cout = (Cin == 1'b1 && cnt == 4'd9); |
再次仿真后可以看到在图 3.4‑11中十六进制的999时输出进位信号,符合了原定的要求,可以再将显示格式修改为二进制数,如图 3.4‑12中在计数值q为1001_1001_1001产生进位信号,通过前面所讲的BCD码原理将其转换为BCD码的格式也是999。 图 3.4‑11 修改后的BCD计数器波形图 图 3.4‑12 修改后的BCD计数器波形图 至此,就完成了一个BCD计数器的设计,并且学会了基本的调试修改能力。
本章学习了BCD码编码的原理、种类以及优缺点,设计并验证一个多位8421码计数器。在设计及仿真过程中学习了基本的错误定位和修改的方法。可以在本章的基础上设计其他几种常见的BCD编码计数器。具体BCD计数器的板级验证,可待后续章节中关于数码管的驱动设计后再行
|