使用Verilog实现二进制转BCD码

8421BCD码介绍

BCD码(Binary-Coded Decimal‎),利用四个2进制位储存一个10进制的数,不同的BCD码编码方式会导致结果不一样,而8421BCD码的编码结果如下表所示。可以发现8421BCD码其实是其对应的二进制表示。

十进制数BCD8421码
00000
10001
20010
30011
40100
50101
60110
70111
81000
91001

加3移位法

在Verilog中,当然可以同C语言一样,使用除法和求余来得到各个位上的数据,但是这将耗费大量的LE资源,因为FPGA中没有现成的除法器可用(乘法器是有的),综合时编译器会用LE强行搭一个除法器出来。

为了节省资源和增强灵活性,我们使用加3移位法和状态机相配合的方式实现功能。在进一步解释前,先看一个例子。

NUM1NUM2NUM3NUM4DATAOperation
0000(0)0000(0)0000(0)0000(0)1111_1111
0000(0)0000(0)0000(0)0001(1)111_1111所有数据左移一位
0000(0)0000(0)0000(0)0011(3)11_1111所有数据左移一位
0000(0)0000(0)0000(0)0111(7)1_1111所有数据左移一位
0000(0)0000(0)0000(0)1010(10)1_11114个NUM中有大于4的就加3
0000(0)0000(0)0001(1)0101(5)1111所有数据左移一位
0000(0)0000(0)0001(1)1000(8)11114个NUM中有大于4的就加3
0000(0)0000(0)0011(3)0001(1)111所有数据左移一位
0000(0)0000(0)0110(6)0011(3)11所有数据左移一位
0000(0)0000(0)1001(9)0011(3)114个NUM中有大于4的就加3
0000(0)0001(1)0010(2)0111(7)1所有数据左移一位
0000(0)0001(1)0010(2)1010(10)14个NUM中有大于4的就加3
0000(0)0010(2)0101(5)0010(5)所有数据左移一位

表格中输入的原始数据为8'b1111_1111 = 8'd255,而推算到最后的结果也为255,说明方法是正确的。

这一方法可描述为,当任意一个NUM中的值大于4时,停止移位操作并将那个NUM的值加3后再进行移位,知道原始数据全部移入NUM中时停止。

模块设计

状态机设计

通过分析这一方法,可以发现总共要做的操作只有两个:移位和加3。

据此,可以确定状态机的状态数量为4:IDLEADDSHIFTDONE

状态跳转如下图:

  • 当有数据到来时,从IDLE进入SHIFT状态

  • 移位一次后进入ADD状态判断是否需要加3操作

  • 处于ADD状态时,如果数据已经全部移位,进入DONE状态输出数据,反之回到SHIFT

  • DONE状态发送数据,完成后回到IDLE

接口设计

module bin2bcd8421 #
(
    parameter   DATA_WIDTH = 8//最大为17
)
(
    input                       clk     ,
    input                       rst_n   ,
    input                       start   ,
    input   [DATA_WIDTH-1:0]    din     ,
    output                      done    ,
    output  [19:0]              dout    
);

接口处使用参数化的设计,输出受限于内部只定义了5个NUM,所以固定为20位,而参数DATA_WIDTH最大为17则是因为最大的5位数99999的二进制数为17位。

整体代码

module bin2bcd8421 #
(
    parameter   DATA_WIDTH = 8//最大为17
)
(
    input                       clk     ,
    input                       rst_n   ,
    input                       start   ,
    input   [DATA_WIDTH-1:0]    din     ,
    output                      done    ,
    output  [19:0]              dout    
);
​
    reg [DATA_WIDTH-1:0]  din_r;
    reg [19:0]  dout_r;
    reg [3:0]   num1,num2,num3,num4,num5;
​
    reg [4:0]   cnt_bit;
    wire        add_cnt_bit,
                end_cnt_bit;
​
//状态机参数定义
    reg [3:0]   state_c,
                state_n;
​
    localparam  IDLE    =    4'b0001,
                ADD     =    4'b0010,
                SHIFT   =    4'b0100,
                DONE    =    4'b1000;
​
    wire        idle2shift  ,
                shift2add   ,
                add2shift   ,
                add2done    ,
                done2idle   ;
​
//状态机状态跳转
    always@(posedge clk or negedge rst_n)begin
        if(!rst_n)
            state_c <= IDLE;
        else
            state_c <= state_n;
    end
​
    always@(*)begin
        case(state_c)
            IDLE :begin
                if(idle2shift)
                    state_n <= SHIFT;
                else
                    state_n <= state_c;
            end
            ADD  :begin
                if(add2done)
                    state_n <= DONE;
                else if(add2shift)
                    state_n <= SHIFT;
                else
                    state_n <= state_c;
            end
            SHIFT:begin
                if(shift2add)
                    state_n <= ADD;
                else
                    state_n <= state_c;
            end
            DONE :begin
                if(done2idle)
                    state_n <= IDLE;
                else
                    state_n <= state_c;
            end
            default: state_n <= IDLE;
        endcase
    end
​
//状态机跳转条件
    assign  idle2shift  = (state_c == IDLE  ) && start;
    assign  shift2add   = (state_c == SHIFT );
    assign  add2shift   = (state_c == ADD   );
    assign  add2done    = (state_c == ADD   ) && end_cnt_bit;
    assign  done2idle   = (state_c == DONE  ) && 1'b1;
​
//输入寄存
    always@(posedge clk or negedge rst_n)begin
        if(!rst_n)
            din_r <= 'd0;
        else if(start)
            din_r <= din;
    end
​
//bit计数器
    always@(posedge clk or negedge rst_n)begin
        if(!rst_n)
            cnt_bit <= 1'b0;
        else if(end_cnt_bit)
            cnt_bit <= 1'b0;
        else if(add_cnt_bit)
            cnt_bit <= cnt_bit + 1'b1;
        else    
            cnt_bit <= cnt_bit;
    end
    assign add_cnt_bit = (state_c == SHIFT);
    assign end_cnt_bit = (cnt_bit == DATA_WIDTH);
​
//输出控制
    always@(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
                num1 <= 4'd0;   num2 <= 4'd0;   num3 <= 4'd0;   num4 <= 4'd0;   num5 <= 4'd0;   dout_r <= 20'd0;
            end
        else if(state_c == IDLE)begin
                num1 <= 4'd0;   num2 <= 4'd0;   num3 <= 4'd0;   num4 <= 4'd0;   num5 <= 4'd0;   dout_r <= 20'd0;
            end
        else if(state_c == SHIFT)begin
                num1 <= {num1[2:0],num2[3]};
                num2 <= {num2[2:0],num3[3]};
                num3 <= {num3[2:0],num4[3]};
                num4 <= {num4[2:0],num5[3]};
                num5 <= {num5[2:0],din_r[DATA_WIDTH-1-cnt_bit]};
            end
        else if(add2done)begin
                dout_r <= {num1,num2,num3,num4,num5}; 
                end
        else if(state_c == ADD)begin
                if(num1 >= 5)
                    num1 <= num1 + 3;
                if(num2 >= 5)
                    num2 <= num2 + 3;
                if(num3 >= 5)
                    num3 <= num3 + 3;
                if(num4 >= 5)
                    num4 <= num4 + 3;
                if(num5 >= 5)
                    num5 <= num5 + 3;
            end
        else begin
                dout_r <= dout_r;
                num1 <= num1;
                num2 <= num2;
                num3 <= num3;
                num4 <= num4;
                num5 <= num5;
            end
    end
​
    assign  dout = dout_r;
    assign  done = done2idle;
​
​
endmodule

仿真测试

Testbench代码

`timescale 1ns/1ps
module bin2bcd8421_tb();
​
    reg           clk   ;
    reg           rst_n ;
    reg           start ;
    reg   [16:0]  din   ;
    wire          done  ;
    wire  [19:0]  dout  ;
​
    parameter CYC = 20;
​
always #(CYC/2) clk = ~clk;
​
initial begin
    clk = 0;
    rst_n = 1;
    #(CYC*2);
    rst_n = 0;
    #(CYC*18);
    rst_n = 1;
end
​
initial begin
    start = 0;
    #(CYC*50);
​
    start = 1;
    din = 'd99999;
    #(CYC);
    start = 0;
​
    repeat(10)
        begin
            #(CYC*50);
            start = 1;
            din = {$random};
            #(CYC);
            start = 0;
        end
​
    #1000;
    $stop;
end
​
bin2bcd8421 #(.DATA_WIDTH(17)) u0
(
    /*input         */  .clk     (clk),
    /*input         */  .rst_n   (rst_n),
    /*input         */  .start   (start),
    /*input   [7:0] */  .din     (din),
    /*output        */  .done    (done),
    /*output  [19:0]*/  .dout    (dout)
);
​
endmodule

仿真结果

可以看到,输入了17'd79140和17‘d89729,输出的结果是能对应上的。

模块使用及修改方法

目前这一模块受限于NUM的个数,最大只支持17位输入,20位输出,但是很多时候我们的设计中是无法准确对应这两个数字的,这里提供了修改的方法。

关于输出位数

输出位数的修改取决于输入数据的极限值,我们可以使用电脑自带的计算器来进行验证,举个例子:

已知需要转换的数据的位宽位20位,那么极限值为20'hfffff = 20'd1048575,所以需要7个NUM来进行输出,因此输出位宽为7*4= 28。

关于输入位数

一般情况下是输入位数来确定输出位数,但也不是没有反过来的情况,我们同样可以用计算器来进行验证,举个例子:

一直最大输出为6*4 = 24位,那么输出的极限值位999999,而这一值的二进制为17'h1869F,所以输入为17位。

小结

这一方法对于软件语言来说是没有太大意义的,直接使用求余和除法反而会更快。但是对于FPGA来说,内部是没有现成的除法器可用的,而除法器的复杂性导致其会消耗大量珍贵的LE资源。这一方法则是用处理时间的延长换来了LE资源的节省。

  • 4
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值