二进制转BCD码模块-FPGA常见问题论坛-FPGA CPLD-ChipDebug

二进制转BCD码模块

01
概述

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

02
设计目标

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

03
接口信号
信号 I/O 位宽 定义

clk

输入

1

系统工作时钟,50MHZ

rst_n

输入

1

系统复位,低电平有效

din

输入

IN_DATA_W

需要转换的二进制数据

din_vld

输入

1

需要转换的二进制数据有效指示信号

dout

输出

OUT_DATA_W

转换后的BCD码输出数据

dout_vld

输出

1

转换后的BCD码输出数据有效指示信号

rdy

输出

1

忙闲指示信号,高电平时,才能输入数据到此模块进行转换

04
模块接口时序

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

0bc7786b9a113125

 

图1 接口时序

05
转换原理

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

http://bbs.eeworld.com.cn/thread-510929-1-1.html

比如一个n位二进制其在十进制编码方式的值为:

d2b5ca33bd113927

 

��=∑�=0�−1��∗2�=��−1∗2�−1+��−2∗2�−2+…+�1∗21+�0

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

d2b5ca33bd114306

 

式中每项乘2,其实就是将二进制数据左移一位,只是在移位的过程中要做一些修正。
在移位的过程中,当现态 Sn<5时,次态不变。当现态 5≤Sn≤7时,左移一次,其次态 S{n+1}将会超过9,对于一个BCD码来说,这样的状态属于禁用状态。而当 8≤Sn≤9时,左移1位,则会向高1位的BCD码输入一个进位的信号,由于二进制和BCD码权不一致,当发生进位时,虽然码元只是左移1位,但次态 S{n+1}将减少6。基于上面这两种情况,在BCD转换时需要对转换结果加以校正。校正过程如下:
当Sn≥5时,让Sn先加上3,然后再左移1位,次态S{n+1}=2*(Sn+3)=2*Sn+6,正好补偿由于进位而减少的数值,并且向后一个变换单元送入一个进位信号,这个方法 叫“加3移位法”。
注意:现态 Sn和次态 S{n+1}都是指BCD码,即用4位二进制表示的1位BCD码。当 Sn=8或Sn=9时:BCD码的1000(8)乘以2为0001_0110(16),但是左移后变为0001_0000,减少了6。所以需要加上6,这里的方法是加3左移一位,相当于加上6。

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

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

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

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

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

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

d2b5ca33bd114344

 

06
实现思路

知道原理之后,结合能够根据输入信号位宽自动得到输出数据位宽,尽量节约资源等信息分析,如果不需要手动修改其余参数,就不适用采用流水线的实现方式,流水线实现方式每个时钟都可以输入信号,但是如果输入数据的位宽过大,将会消耗大量资源。故采用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和输入数据的影响。

07
参考代码

参考代码如下:

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

 

08
仿真

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

 

IN_DATA_W为12位的仿真结果:

d2b5ca33bd114548

 

图2 输入数据位宽12

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

d2b5ca33bd114623

 

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

09
12位输入数据最大时钟频率

d2b5ca33bd114650

 

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

10
待优化的点

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

 

请登录后发表评论

    没有回复内容