当前位置:   article > 正文

基于FPGA的二进制转BCD码模块

基于FPGA的二进制转BCD码模块

1、 概述

  BCD码(Binary-Coded Decimal‎),用4位二进制数来表示1位十进制数中的0~9这10个数码,是一种二进制的数字编码形式,用二进制编码的十进制代码。BCD码这种编码形式利用了四个位来储存一个十进制的数码,使二进制和十进制之间的转换得以快捷的进行。

2、 设计目标

  实现BCD译码并显示十进制结果的程序,例化时只需要指定输入数据的最大位宽即可,其余参数无需输入,该模块内部自动确定,从而是实现简单例化。

3、接口信号

信号输出输出方向位宽定义
clk输入1系统工作时钟,50MHZ
rst_n输入1系统复位,低电平有效
din输入DATA_W需要转换的二进制数据
din_vld输入1需要转换的二进制数据有效指示信号
dout输出根据din变化转换后的BCD码输出数据
dout_vld输出1转换后的BCD码输出数据有效指示信号
rdy输出1忙闲指示信号,高电平时,才能输入数据到此模块进行转换

4、模块接口时序

  正常的接口时序如下,rdy为高电平时,din_vld信号拉高一个时钟,直到dout_vld信号为高电平时,表示转换完成。注意:如果在rdy为低电平期间将din_vld拉高,为了保证之前的数据转换正确,则此时输入的din信号是无效的。
在这里插入图片描述

图1 接口时序

5、转换原理

  转换原理参考小梅哥的一篇文章,链接:

【小梅哥FPGA进阶教程】第二章 二进制转BCD - FPGA/CPLD - 电子工程世界-论坛

  比如一个n位二进制 b n − 1 b n − 2 . . . . . . b 2 b 1 b 0 b_{n-1}b_{n-2}......b_2b_1b_0 bn1bn2......b2b1b0,其在十进制编码方式的值为:

N D = ∑ i = 0 n − 1 b i ∗ 2 i = b n − 1 ∗ 2 n − 1 + b n − 2 ∗ 2 n − 2 + . . . + b 1 ∗ 2 1 + b 0 N_D=\sum_{i=0}^{n-1}{b_i*2^i}=b_{n-1}*2^{n-1}+b_{n-2}*2^{n-2}+...+b_1*2^1+b_0 ND=i=0n1bi2i=bn12n1+bn22n2+...+b121+b0

  把上面的公式拆解一下得到下式:

N D = { . . . { ( ( b n − 1 ∗ 2 + b n − 2 ) ∗ 2 + b n − 3 ) ∗ 2... } ∗ 2 + b 1 } ∗ 2 + b 0 N_D=\{...\{((b_{n-1}*2+b_{n-2})*2+b_{n-3})*2...\}*2+b_1\}*2+b_0 ND={...{((bn12+bn2)2+bn3)2...}2+b1}2+b0

  式中每项乘2,其实就是将二进制数据左移一位,只是在移位的过程中要做一些修正。

  在移位的过程中,当现态 S n < 5 S_n<5 Sn<5时,次态不变。当现态 5 ≤ S n ≤ 7 5≤S_n≤7 5Sn7时,左移一次,其次态 S n + 1 S_{n+1} Sn+1将会超过9,对于一个BCD码来说,这样的状态属于禁用状态。而当 8 ≤ S n ≤ 9 8≤S_n≤9 8Sn9时,左移1位,则会向高1位的BCD码输入一个进位的信号,由于二进制和BCD码权不一致,当发生进位时,虽然码元只是左移1位,但次态 S n + 1 S_{n+1} Sn+1将减少6。基于上面这两种情况,在BCD转换时需要对转换结果加以校正。校正过程如下:

  当 S n ≥ 5 S_{n}≥5 Sn5 时,让 S n S_n Sn先加上3,然后再左移1位,次态 S n + 1 = 2 ∗ ( S n + 3 ) = 2 ∗ S n + 6 S_{n+1}=2*(S_n+3)=2*S_n+6 Sn+1=2(Sn+3)=2Sn+6,正好补偿由于进位而减少的数值,并且向后一个变换单元送入一个进位信号,这个方法 叫“加3移位法”。

  注意:现态 S n S_n Sn和次态 S n + 1 S_{n+1} Sn+1都是指BCD码,即用4位二进制表示的1位BCD码。当 S n = 8 或 S n = 9 S_n=8或S_n=9 Sn=8Sn=9时:BCD码的1000(8)乘以2为0001_0110(16),但是左移后变为0001_0000,减少了6。所以需要加上6,这里的方法是加3左移一位,相当于加上6。

左移加3算法

  此处二进制转 BCD 码的硬件实现,采用左移加 3 的算法,具体描述如下:(此处以 8-bit 二进制码为例)

  1、左移要转换的二进制码 1 位

  2、左移之后,BCD 码分别置于百位、十位、个位

  3、如果移位后所在的 BCD 码列大于或等于 5,则对该值加 3

  4、继续左移的过程直至全部移位完成

  举例:将十六进制码 0xFF 转换成 BCD 码

OperationHundredsTensUnitsBinary
HEXFF
Start11111111
Shift 1111111110
Shift 21111111100
Shift 311111111000
Add 3101011111000
Shift 41010111110000
Add 31100011110000
Shift 511000111100000
Shift 6110001111000000
Add 31001001111000000
Shift 710010011110000000
Add 310010101010000000
Shift 8100101010100000000
BCD255

6、实现思路

  知道原理之后,结合能够根据输入信号位宽自动得到输出数据位宽,尽量节约资源等信息分析,如果不需要手动修改其余参数,就不适用采用流水线的实现方式,流水线实现方式每个时钟都可以输入信号,但是如果输入数据的位宽过大,将会消耗大量资源。故采用rdy信号高电平才能输入数据来实现此模块。

  经过分析,可以得到移位次数为输入数据位宽IN_DATA_W-3,即使用一个IN_DATA_W-2进制的计数器cnt为主架构,即可得到输出数据。

各种信号含义:

  OUT_DATA_W:输出数据的十进制数据位数,例如输入数据位宽IN_DATA_W等于4时,能表示最大数据为15,对应的BCD码为8’h15,即2位十进制,则OUT_DATA_W=2,则输出数据位宽为4*OUT_DATA_W,OUT_DATA_W由clogb函数自动计算得到。

  din_ff0:输入数据暂存信号,当rdy_ff0信号(rdy延迟一个时钟的信号)和输入数据din_vld均有效时,将输入数据din保存。

  flag:转换标志信号,初始值为低电平,当输入数据din_vld有效时拉高,当计数器cnt计数结束的时候拉低,其余时间保持不变。

  cnt:IN_DATA_W-2进制计数器,初始值为0,由于每次转换(一次移位和修正操作),所以当flag信号为高电平时计数器加一,当计数器计数到IN_DATA_W-3时清零。

  data_shift:移位寄存器,位宽为输入数据位宽+输出数据位宽(即IN_DATA_W+4OUT_DATA_W),初始值为0,当计数器加一条件有效且计数器为0时,将输入数据din_ff0左移三位,其余高位为0,当计数器加一条件有效且计数器不为0,将移位寄存器数据左移一位,高位由data_compare信号的低4OUT_DATA_W-2信号组成。

  data_compare:该信号是对移位前的data_shift的BCD码数据位进行判断,如果大于等于5,则将该数据位加上3,这里判断与BCD的位数是有关的,所以采用for循环复制电路来实现,有几位BCD码,则就对几位的进行判断,修正。

  dout:输出数据,当计数器cnt计数结束时,将移位寄存器data_shift的高OUT_DATA_W位输出。

  dout_vld:输出数据有效指示信号,初始值位0,当计数器cnt计数结束时拉高,其实时间拉低。

  rdy:模块忙闲指示信号,当输入数据din_vld或者标志信号flag有效时拉低,表示此时模块正在转换数据,不能输入数据,其余时间拉高,表示该模块处于空闲,可以输入数据。注意:该信号必须使用组合逻辑产生,否则会产生bug丢失数据,或者在其余模块增加电路来弥补缺陷,得不偿失。

  rdy_ff0:忙闲指示信号延迟信号,将rdy信号打一拍,主要是用于产生din_ff0,在转换数据过程中,保证本次转换数据的正确性,在此期间转换不受din_vld和输入数据的影响。

7、参考代码

module hex2bcd #(
    parameter   IN_DATA_W       =           14               //输入数据位宽;
)(
    input                                   clk             ,//系统时钟;
    input                                   rst_n           ,//系统复位,低电平有效;

    input       [IN_DATA_W-1:0]             din             ,//输入二进制数据;
    input                                   din_vld         ,//输入数据有效指示信号,高电平有效;

    output reg                              rdy             ,//忙闲指示信号,该信号高电平时才能输入有效数据;
    output reg  [4*OUT_DATA_W-1:0]          dout            ,//输出8421BCD码;
    output reg                              dout_vld         //输出数据有效指示信号,高电平有效;
    );

    localparam  CNT_W           =           clogb(IN_DATA_W-3);//根据输入数据的位宽自动计算需要移动的轮数;
		localparam  OUT_DATA_W      =           clogb2({{IN_DATA_W}{1'b1}});//自动计算输出数据对应的十进制位数;

		reg         [IN_DATA_W-1:0]             din_ff0     ;
    reg                                     rdy_ff0     ;
    reg                                     flag        ;
    reg         [CNT_W-1:0]                 cnt         ;
    reg         [IN_DATA_W+OUT_DATA_W*4-1:0]data_shift  ;
    reg                                     end_cnt_ff0 ;

    wire        [OUT_DATA_W*4-1:0]          data_compare;
    wire                                    add_cnt     ;
    wire                                    end_cnt     ;

		function integer clogb2(input integer depth);begin
        if(depth==0)
            clogb2 = 1;
        else if(depth!=0)
            for(clogb2=0;depth>0;clogb2=clogb2+1)
                depth=depth/10;
    end
    endfunction

     //自动计算位宽
    function integer clogb(input integer depth);begin
        if(depth==0)
            clogb = 1;
        else if(depth!=0)
            for(clogb=0;depth>0;clogb=clogb+1)
                depth=depth>>1;
    end
    endfunction
    
    //当输入数据有效并且此时该模块空闲时保存输入数据,否则不保存输入数据,这样可以保证本次转换数据完全正确;
    always@(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            din_ff0 <= 0;
        end
        else if(din_vld && rdy_ff0)begin
            din_ff0 <= din;
        end
    end

    //标志信号flag,当输入数据有效时拉高,当计数器计数完成时清零;
    always@(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            flag <= 1'b0;
        end
        else if(din_vld)begin
            flag <= 1'b1;
        end
        else if(end_cnt)begin
            flag <= 1'b0;
        end
    end

    //移位计数器,每次转换需要移动IN_DATA_W-2次,初始值为0,加一条件flag信号有效,结束条件是计数到IN_DATA_W-2次;
    always@(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            cnt <= 0;
        end
        else if(add_cnt)begin
            if(end_cnt)
                cnt <= 0;
            else
                cnt <= cnt + 1;
        end
    end

    assign add_cnt = flag;       
    assign end_cnt = add_cnt && cnt == IN_DATA_W-3;

    always@(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            data_shift <= 0;
        end
        else if(add_cnt)begin
            if(cnt==0)begin//初始时将输入数据左移三位保存;
                data_shift <= {{{OUT_DATA_W-3}{1'b0}},din_ff0,3'b0};
            end
            else begin//计数器加一条件有效时,将移位寄存器数据左移一位;
                data_shift <= {data_compare[OUT_DATA_W*4-2:0],data_shift[IN_DATA_W-1:0],1'b0};
            end
        end
    end

    //移位后大于等于5之后加3;
    genvar bit_num;
    generate 
				for(bit_num = 0 ; bit_num < OUT_DATA_W ; bit_num = bit_num + 1)begin : DATA
            assign data_compare[4*bit_num+3 : 4*bit_num] = data_shift[IN_DATA_W+4*bit_num+3 : IN_DATA_W+4*bit_num] + (data_shift[IN_DATA_W+4*bit_num+3 : IN_DATA_W+4*bit_num]>=5 ? 4'd3 : 4'd0);
        end
    endgenerate

    //将计数器延迟一拍,用于生成输出信号;
    always@(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            end_cnt_ff0 <= 1'b0;
        end
        else begin
            end_cnt_ff0 <= end_cnt;
        end
    end

    //通过计数器结束条件产生输出信号;
    always@(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            dout <= 0;
        end
        else if(end_cnt_ff0)begin
            dout <= data_shift[IN_DATA_W+OUT_DATA_W*4-1 : IN_DATA_W];
        end
    end

    //通过计数器结束条件生成输出有效指示信号;
    always@(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            dout_vld <= 1'b0;
        end
        else begin
            dout_vld <= end_cnt_ff0;
        end
    end

    //产生忙闲指示信号,必须使用组合逻辑;
    always@(*)begin
        if(din_vld || flag)begin
            rdy = 1'b0;
        end
        else begin
            rdy = 1'b1;
        end
    end

    //将忙闲指示信号延迟一个时钟,默认该模块处于空闲状态;
    always@(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin//
            rdy_ff0 <= 1'b1;
        end
        else begin
            rdy_ff0 <= rdy;
        end
    end

    endmodule
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159

8、仿真

testbech参考代码:

`timescale 1 ns/1 ns
module test();
    parameter	CYCLE		= 10;//The unit is ns. The default value is 10ns;
    parameter	RST_TIME	= 10;//Reset time: Reset 3 clock widths by default;
    parameter	STOP_TIME	= 100;//Time for simulation running after reset (unit: clock cycle). Simulation stops after 1000 clocks are run by default;

    // hex2bcd Parameters
    parameter IN_DATA_W   = 16                        ;

    // hex2bcd Inputs
    reg   clk                   ;
    reg   rst_n                 ;
    reg   [IN_DATA_W-1:0]  din  ;
    reg   din_vld               ;

    // hex2bcd Outputs
    wire  rdy                   ;
    wire  [19:0]  dout          ;
    wire  dout_vld              ;

    hex2bcd #(
        .IN_DATA_W  ( IN_DATA_W               )
    )
    u_hex2bcd (
        .clk                     ( clk        ),
        .rst_n                   ( rst_n      ),
        .din                     ( din        ),
        .din_vld                 ( din_vld    ),

        .rdy                     ( rdy        ),
        .dout                    ( dout       ),
        .dout_vld                ( dout_vld   )
    );

    //The local clock is generated at 100 MB;
    initial begin
        clk = 0;
        forever #(CYCLE/2) clk=~clk;
    end

    //Generate reset signal;
    initial begin
        rst_n = 1;
        #2;
        rst_n = 0;
        #(RST_TIME*CYCLE);
        rst_n = 1;
        #(STOP_TIME*CYCLE);
        $stop;//Stop simulation;
    end

    //Input signal din assignment method;
    initial begin
        #1;din = 0;din_vld=0;//Initial assignment value;
        #(10*CYCLE);//Start assigning values;
    end

    always@(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin//
            din <= 0;
        end
        else if(rdy)begin
            din <= 63532;
            din_vld <= 1'b1;
        end
        else begin
            din_vld <= 1'b0;
        end
    end

endmodule
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71

  IN_DATA_W为12位的仿真结果:
在这里插入图片描述

图2 输入数据位宽12

  IN_DATA_W为16位·时的仿真结果如下:

在这里插入图片描述

图3 输入数据位宽13位仿真

9、12位数据最大时钟频率

在这里插入图片描述

图4 系统运行最大时钟频率

10、待优化的点

  该模块只需要确定输入数据的最大位宽,就可以自动计算出模块内部以及输出信号的位宽,但是输出数据会以十进制的最大位宽设置,比如输入数据是12位,输出数据最大位宽应该是16383对应的17位数据,但是模块自动计算的输出数据位宽会是20位,这是由于自动计算时的机制决定的,修改这部分需要结合内部代码一起修改,当然使用者可以只将输出信号的低17位输出也行。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/69517
推荐阅读
相关标签
  

闽ICP备14008679号