介绍
BCD码也称二进码十进数,可以分为有权码和无权码两个种类。常见的有权BCD码有8421码、2421码、5421码,无权BCD码有余3码、余3循环码、格雷码。
其中,8421BCD码是最基本和最常用的BCD码。其各个bit位的权值分别为8d、4d、2d、1d(5421码、2421码同理)。
BCD码常用于数码管这类经常用到除法计算的硬件,通常进行除法运算耗费的时间较长,所以常常先将数字转换为BCD码,这就可以节约一部分运算时间。
两个8421BCD码进行相加时,如果和大于9,需要加6d进行修正。(详细原理见文末)
总之,了解BCD码的原理尤其是8421BCD码的应用可以对FPGA的学习产生有效帮助。
本文使用的软件和硬件平台同第一篇博文,开发流程也相同,这里就不再赘述,可以参考这里→fpga自学之路[1]计数器
1.绘制波形图
对于时序逻辑电路来说,需要时钟的驱动,复位方式选择异步复位,cin是外部输入的计数信号。
同时定义一个变量cnt来对cin进行计数(BCD码的范围是0~9,故将cnt的位宽设置为4),计数溢出时将flag置为高电平。最后设置一个4位宽的输出端口来引出计数值并连接到4个LED上,方便我们观察。
2.rtl代码
编写代码如下:
module BCD_counter
(
input sys_clk ,
input sys_rst_n ,
input cin , //计数信号
output [3:0]q , //计数值
output reg flag //计满信号
);
parameter CNT_MAX = 9; //设置计数最大值
reg [3:0] cnt; //声明一个4位宽的计数变量cnt
always@(posedge sys_clk or negedge sys_rst_n) //复位方式设为异步复位
if(sys_rst_n == 1'b0)
cnt <= 4'd0; //复位信号为低电平时计数变量清零
else if(cin == 1'b1)begin //计数信号到来时执行计数操作
if(cnt == 4'd9)
cnt <= 4'd0; //计数器计满时清零
else
cnt <= cnt + 1; //其他情况对计数信号进行计数
end
else
cnt <= cnt ; //没有计数信号到来时维持原有数值
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
flag <= 1'b0; //复位信号为低电平时标志信号设为低电平
else if(cin == 1'b1 && cnt == CNT_MAX)
flag <= 1'b1; //计数溢出时将标志信号置高电平
else
flag <= 1'b0; //其他情况标志信号保持不变
assign q = cnt; //将计数变量cnt引出
endmodule
在Quartus II中进行一次全编译后,没有报错。
此时,我们可以在任务栏中查看RTL 视图,方便我们理解硬件结构。
3.编写testbench代码
为了验证我们的设计,需要编写testbench文件进行仿真验证
我们编写testbench文件,是为了产生满足条件的激励信号,同时对模块的输出进行捕捉,测试输出是否满足要求。
编写代码如下:
`timescale 1ns/1ns //设置时间参数
module tb_BCD_counter(); //模块声明
reg sys_clk;
reg sys_rst_n;
reg cin;
wire [3:0]q;
wire flag; //声明端口
initial sys_clk = 1'b0;
always#10 sys_clk = ~sys_clk; //设置时钟周期为20ns
initial //初始化语句
begin
sys_rst_n = 1'b0; cin = 1'b0;//设置初始参数
#40 //延迟40个时间单位
sys_rst_n = 1'b1; //复位信号结束使能
#10
repeat(30000)begin //我们产生多次波形方便观察
cin = 1'b1;
#20;
cin = 1'b0;
#100; //产生占空比为1:6的方波
end
end
BCD_counter BCD_counter_inst //例化语句
(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.cin (cin),
.q (q),
.flag (flag)
);
endmodule
将tb_BCD_counter文件添加到工程中,并进行编译,结果没有报错。
添加完仿真文件后,使用Quartus & Modelsim 联合仿真
首先添加波形
得到仿真波形如下:
对局部进行放大,可以看出:
当复位信号有效时(为低电平),cnt未开始计数,复位信号为高电平且计数信号到来时计数开始
每次计数信号cin到来时cnt增加1
当计数溢出时cnt清零,且flag信号电平被拉高
从这里看出来我们的设计是正确的。
4.拓展
上面的流程中,我们使用了设计了单个8421BCD计数器,而在实际开发中单个计数器常常不能胜任复杂的项目,此时就需要用到级联多个BCD计数器来满足我们的需求。
我们需要建立一个顶层模块,并将上文中我们使用到的计数器电路作为底层模块进行多次例化。
代码如下:
编写代码如下:
module BCD_counter_top //声明顶层模块
(
input sys_clk ,
input sys_rst_n ,
input cin , //计数信号
output [11:0]q , //为了级联三个计算器故位宽设置为12
output flag //计满信号
);
wire cin1;
wire cin2;
wire[3:0]q0,q1,q2;
BCD_counter BCD_counter_inst0
(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.cin(cin), //计数信号
.q(q0), //计数值
.flag(cin1) //计满信号
);
BCD_counter BCD_counter_inst1
(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.cin(cin1), //计数信号
.q(q1), //计数值
.flag(cin2) //计满信号
);
BCD_counter BCD_counter_inst2
(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.cin(cin2), //计数信号
.q(q2), //计数值
.flag(flag) //计满信号
);
assign q = {q2,q1,q0};
endmodule
编译通过后,我们查看rtl视图,可以看到三个底层的BCD_计数器模块共同级联成了top模块:
然后我们再将上面编写的testbench文件稍加修改,用于该模块的仿真:
代码如下:
编写代码如下:
`timescale 1ns/1ns //设置时间参数
module tb_BCD_counter_top(); //模块声明
reg sys_clk;
reg sys_rst_n;
reg cin;
wire [11:0]q;
wire flag; //声明端口
initial sys_clk = 1'b0;
always#10 sys_clk = ~sys_clk; //每10个时间单位sys_clk反转一次
initial //初始化语句
begin
sys_rst_n = 1'b0; cin = 1'b0;//设置初始参数
#40 //延迟40个时间单位
sys_rst_n = 1'b1;
#10
repeat(3000)begin //产生3000个周期占空比为1:6的方波
cin = 1'b1;
#20;
cin = 1'b0;
#100;
end
end
BCD_counter_top BCD_counter_top_inst //例化语句
(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.cin (cin),
.q (q),
.flag (flag)
);
endmodule
在modesim中产生的波形如图:
通过观察波形,我们发现每个计数器间进位信号的传递均延迟了一个时钟周期,导致顶层模块的进位信号延迟了两个时钟周期,这不符合我们的预期,所以需要对rtl代码进行重新编写。
原代码段:
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
flag <= 1'b0;
else if(cin == 1'b1 && cnt == CNT_MAX)
flag <= 1'b1;
else
flag <= 1'b0;
修改为:
assign flag = (cnt == 4'd9 && cin == 1'b1);
**同时将flag的类型修改为wire型**
修改后的仿真波形如下,我们可以看到仿真的波形是正确的
原因分析:
修改前的底层rtl电路:
我们可以看到flag信号其实是经过了一个D触发器的,所以延迟了一个时钟周期。这个延迟在经过多级电路后被放大,所以造成了顶层电路更大的延迟。
修改后的电路:
flag信号经过的是一个逻辑门电路,延迟可以忽略不计,所以在经过层级较少的电路后,也不会产生明显的延迟。
文章来源:https://www.toymoban.com/news/detail-408231.html
5.总结
本次项目我们设计了一个BCD计数器,并在拓展部分将三个BCD计数器级联起来组成了一个12位的计数器,通过分析产生的异常,学会了简单的代码调试,也理解了verilog的语法差异。文章来源地址https://www.toymoban.com/news/detail-408231.html
附.关于8421BCD码的思考
-
两个8421BCD码相加,如1001(9d)+1001(9d)=0001_0010(18d)。虽然用二进制看来等式是正确的,但是按照8421BCD码的规则来看0001_0010=12,此时就需要+6来进行修正。
为什么加6?因为二进制的第五位权值是16,但在8421BCD码中该位权值为10,故需要加6来修正。
到了这里,关于fpga[1.1]BCD计数器(附源码)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!