BCD计数器设计与验证
BCD码(Binary-Coded Decimal)亦称二进码十进数或二-十进制代码。用4位二进制数来表示1位十进制数中的0~9这10个数码。是一种二进制的数字编码形式,用二进制编码的十进制代码。BCD码这种编码形式利用了四个位元来储存一个十进制的数码,使二进制和十进制之间的转换得以快捷进行。(百度百科)
例子:158 以BCD编码方式编码就会变成
0 |
0 |
0 |
1 |
0 |
1 |
0 |
1 |
1 |
0 |
0 |
0 |
1 |
5 |
8 |
在C语言中如果不通过位运算,一般都会采用一下方式进行拆分,涉及到除法运算:
A = 158/100 = 1 B = (158 % 100)/10 = 5 C = 158 %10 = 8
在FPGA中可以通过级联3个4位计数器的方式实现该BCD编码器。
一、实现单个4位计数器
1、源程序:
/* 实验名称:计数器 * 程序功能:In_cin 来一个高电平则计数一次 * 约定俗成:所有需要外部输入的信号加入前缀"In_" ,所有需往外部输出的信号加入前缀"Out_" */ module my_Counter(In_clk, In_cin, In_rst_n, Out_cout, Out_q);
input In_clk; // 时钟输入 input In_cin; // 触发计数 input In_rst_n; // 复位信号 低复位 output Out_cout; // 溢出或与预定数值相等时输出一个时钟的高电平 output [3:0]Out_q; // 存储计数的数值
reg[3:0] cnt; // 存储计数值
// 捕获 In_clk 上升沿,捕获 In_rst_n 下降沿 // 计数程序块 [email protected](posedge In_clk or negedge In_rst_n) if(1'b0 == In_rst_n) // 复位信号处理 计数归零 cnt <= 4'd0; else if(1'b1 == In_cin) // In_cin 为高时计数 begin if(4'd9 == cnt) // cnt 等于 9 则归零 cnt <=4'd0; else cnt <= cnt + 1'b1; // cnt 小于 9 则累加 end else ;
// 捕获 In_clk 上升沿,捕获 In_rst_n 下降沿 // 溢出或匹配输出程序块 [email protected](posedge In_clk or negedge In_rst_n) if(1'b0 == In_rst_n) // 复位信号处理 计数归零 Out_cout <= 1'b0; else if(1'b1 == In_cin && 4'd9 == cnt) // 注意1'b1 == In_cin 不可省略 Out_cout <= 1'b1; // In_cin 为高并且同时上个计数器计数到9则输出1 else Out_cout <= 1'b0; // 反之输出0 assign Out_q = cnt;
endmodule |
问题点:在上述代码中的我做了一次实验,将1'b1 == In_cin 省略了导致不行出现如下现象:
正常的波形:(其实cout波形也是不对的,数到9就已经是第10个数了,就应该给高电平,可对比IP核)
差异在这里:
假如1'b1 == In_cin省略了,那么意味着只要计数一到9,无论In_cin当前状态是高还是低电平,cout就会输出高电平,这就导致cout提前被拉高(波形对比),同时由会延后到计数器复位归零之后才会拉低。
2、仿真测试代码
/* 实验名称:BCD 计数器验证 */ `timescale 1ns/1ns `define clock_period 20 module mytest_tb; reg clk; reg cin; reg rst_n;
wire cout; wire [3:0]q;
my_Counter BCD_Counter( .In_clk(clk), .In_cin(cin), .In_rst_n(rst_n), .Out_cout(cout), .Out_q(q) );
initial clk = 1'b1; always #(`clock_period / 2) clk = ~clk;
initial begin rst_n = 1'b0; cin = 1'b0; #(`clock_period * 20); 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 endmodule
|
二、计数器级联
1、源程序
/* 实验名称:级联计数器 * 程序功能: * 约定俗成:所有需要外部输入的信号加入前缀"In_",所有需往外部输出的信号加入前缀"Out_" */ module mytest(In_clk, In_cin, In_rst_n, Out_cout, Out_q);
input In_clk; input In_cin; input In_rst_n; output Out_cout; output[11:0] Out_q;
wire Out_line0; // 计数器0的Out_cout端与计数器1的Out_cout端链接 wire Out_line1; // 计数器1的Out_cout端与计数器2的Out_cout端链接 wire[3:0] q0, q1, q2; // 将三组4位信号合并成一组12位的信号 assign Out_q = {q2, q1, q0};
my_Counter Conuter0( .In_clk(In_clk), .In_cin(In_cin), // 重点 .In_rst_n(In_rst_n), .Out_cout(Out_line0), // 重点 //.Out_q(Out_q[3:0]) // 方式1 .Out_q(q0) // 方式2 );
my_Counter Conuter1( .In_clk(In_clk), .In_cin(Out_line0), // 重点 这里来一次高电平意味这计数器0计满 .In_rst_n(In_rst_n), .Out_cout(Out_line1), // 重点 //.Out_q(Out_q[7:4]) // 方式1 .Out_q(q1) // 方式2 );
my_Counter Conuter2( .In_clk(In_clk), .In_cin(Out_line1), // 重点 这里来一次高电平意味这计数器1计满 .In_rst_n(In_rst_n), .Out_cout(Out_cout), // 重点 //.Out_q(Out_q[11:8]) // 方式1 .Out_q(q2) // 方式2 );
endmodule
/* 实验名称:计数器 * 程序功能: * 约定俗成:所有需要外部输入的信号加入前缀"In_" * 所有需往外部输出的信号加入前缀"Out_" */ module my_Counter(In_clk, In_cin, In_rst_n, Out_cout, Out_q);
input In_clk; input In_cin; input In_rst_n; output reg Out_cout; output [3:0]Out_q;
reg[3:0] cnt; // 存储计数值
// 捕获 In_clk 上升沿,捕获 In_rst_n 下降沿 // 计数程序块 [email protected](posedge In_clk or negedge In_rst_n) if(1'b0 == In_rst_n) // 复位信号处理 计数归零 cnt <= 4'd0; else if(1'b1 == In_cin) // In_cin 为高时开始计数 begin if(4'd9 == cnt) // cnt 等于 9 则归零 cnt <=4'd0; else cnt <= cnt + 1'b1; // cnt 小于 9 则累加 end else ;
// 捕获 In_clk 上升沿,捕获 In_rst_n 下降沿 // 溢出输出程序块 [email protected](posedge In_clk or negedge In_rst_n) if(1'b0 == In_rst_n) // 复位信号处理 计数归零 Out_cout <= 1'b0; else if(1'b1 == In_cin && 4'd9 == cnt) Out_cout <= 1'b1; // In_cin 为高并且同时上个计数器计数到9则输出1 else Out_cout <= 1'b0; // 反之输出0
assign Out_q = cnt; // 与计数器相连输出
endmodule
|
2、仿真测试源程序
/* 实验名称:BCD 级联计数器验证 */ `timescale 1ns/1ns `define clock_period 20 module mytest_tb; reg clk; reg cin; reg rst_n;
wire cout; wire [11:0]q;
mytest BCD_Counter( .In_clk(clk), .In_cin(cin), .In_rst_n(rst_n), .Out_cout(cout), .Out_q(q) );
initial clk = 1'b1; always #(`clock_period / 2) clk = ~clk;
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
endmodule
|
仿真后出现如下波形:
那么该如何解决了,小梅哥给出一个很好的调试方式:
1、由于是三级级联计数器,那么我们要先看每一级计数器的状态。需要作如下步骤:
在ModelSim找到SIM窗口:
将Conuter0、Conuter1、Conuter2、都 【Add Wave】同时也看到可以通过快捷键【Ctrl+W】添加。
然后返回到【Wave】窗口在信号窗口中依次按下【Ctrl+A】全选、【Ctrl+G】根据模块自动分组
这是可以看到那些模块是看不到信号的,需要重新编译重新运行才能看到:
接着就可以看到每个信号的现象了:
修复代码如下:( 目前得到的解释是:总之非阻塞赋值有1个时钟周期的延迟才会生效 )
/* 实验名称:级联计数器 * 程序功能: * 约定俗成:所有需要外部输入的信号加入前缀"In_",所有需往外部输出的信号加入前缀"Out_" */ module mytest(In_clk, In_cin, In_rst_n, Out_cout, Out_q);
input In_clk; input In_cin; input In_rst_n; output Out_cout; output[11:0] Out_q;
wire Out_line0; // 计数器0的Out_cout端与计数器1的Out_cout端链接 wire Out_line1; // 计数器1的Out_cout端与计数器2的Out_cout端链接 wire[3:0] q0, q1, q2; // 将三组4位信号合并成一组12位的信号 assign Out_q = {q2, q1, q0};
my_Counter Conuter0( .In_clk(In_clk), .In_cin(In_cin), // 重点 .In_rst_n(In_rst_n), .Out_cout(Out_line0), // 重点 //.Out_q(Out_q[3:0]) // 方式1 .Out_q(q0) // 方式2 );
my_Counter Conuter1( .In_clk(In_clk), .In_cin(Out_line0), // 重点 这里来一次高电平意味这计数器0计满 .In_rst_n(In_rst_n), .Out_cout(Out_line1), // 重点 //.Out_q(Out_q[7:4]) // 方式1 .Out_q(q1) // 方式2 );
my_Counter Conuter2( .In_clk(In_clk), .In_cin(Out_line1), // 重点 这里来一次高电平意味这计数器1计满 .In_rst_n(In_rst_n), .Out_cout(Out_cout), // 重点 //.Out_q(Out_q[11:8]) // 方式1 .Out_q(q2) // 方式2 );
endmodule
/* 实验名称:计数器 * 程序功能: * 约定俗成:所有需要外部输入的信号加入前缀"In_" * 所有需往外部输出的信号加入前缀"Out_" */ module my_Counter(In_clk, In_cin, In_rst_n, Out_cout, Out_q);
input In_clk; input In_cin; input In_rst_n; output reg Out_cout; output [3:0]Out_q;
reg[3:0] cnt; // 存储计数值
// 捕获 In_clk 上升沿,捕获 In_rst_n 下降沿 // 计数程序块 [email protected](posedge In_clk or negedge In_rst_n) if(1'b0 == In_rst_n) // 复位信号处理 计数归零 cnt <= 4'd0; else if(1'b1 == In_cin) // In_cin 为高时开始计数 begin if(4'd9 == cnt) // cnt 等于 9 则归零 cnt <=4'd0; else cnt <= cnt + 1'b1; // cnt 小于 9 则累加 end else ;
/* 这段代码会导致在级联的时候每一级 Out_cout 都会延迟一个时钟周期 // 捕获 In_clk 上升沿,捕获 In_rst_n 下降沿 // 溢出输出程序块 [email protected](posedge In_clk or negedge In_rst_n) if(1'b0 == In_rst_n) // 复位信号处理 计数归零 Out_cout <= 1'b0; else if(1'b1 == In_cin && 4'd9 == cnt) Out_cout <= 1'b1; // In_cin 为高并且同时上个计数器计数到9则输出1 else Out_cout <= 1'b0; // 反之输出0 */ // 修改如下 assign Out_cout = (1'b1 == In_cin && 4'd9 == cnt); assign Out_q = cnt;
endmodule
|
关于逻辑单元:
如果逻辑单元后面的数目为0,那么说明无法实现,说明设计是有问题的。