找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 6288|回复: 1
收起左侧

FPGA学习-BCD计数器设计与验证

[复制链接]
ID:108531 发表于 2016-3-12 22:40 | 显示全部楼层 |阅读模式
Love 梦想

BCD码(Binary-Coded Decimal)亦称二进码十进数或二-十进制代码。用4位二进制数来表示1位十进制数中的0~910个数码。是一种二进制的数字编码形式,用二进制编码的十进制代码。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 下降沿

    // 计数程序块

    always@(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 下降沿

    // 溢出或匹配输出程序块

    always@(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 下降沿

    // 计数程序块

    always@(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 下降沿

    // 溢出输出程序块

    always@(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 下降沿

    // 计数程序块

    always@(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 下降沿

    // 溢出输出程序块

    always@(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,那么说明无法实现,说明设计是有问题的。

 

回复

使用道具 举报

ID:642632 发表于 2020-7-4 13:12 | 显示全部楼层
不进行级联的话没有cin是可以的,你自己可能都没搞清楚,有点误导别人
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表