FPGA Verilog实现二进制转BCD码

1,477 阅读2分钟

转换原理

二进制转BCD码可以采用Double_dabble算法,维基百科有详细介绍,简单可称为“移位加3”算法。

8位二进制数243,移位加3计算过程: 在这里插入图片描述 16位二进制65244,移位加3计算过程:

在这里插入图片描述

verilog实现

方法1:纯组合逻辑实现

来自维基百科的实现。可以看出,二进制位宽为W,则BCD位宽只需要(W + (W - 4) / 3+1)位。如W=8,只需要10位,范围0-255,百位只需要两位就可以表示。 binTobcd.v文件:

`timescale 1ns / 1ps
module binTobcd #( 
    parameter W = 18
)  // input width
( 
    //Inputs
    input [W - 1 : 0] bin ,   // binary
    //Outputs
    output reg [W + (W - 4) / 3: 0] bcd 
); // bcd {...,thousands,hundreds,tens,ones}

integer i, j;

always @(bin) begin
    for (i = 0; i <= W + (W - 4) / 3; i = i + 1)
        bcd[i] = 0;     // initialize with zeros
    bcd[W - 1: 0] = bin;                                   // initialize with input vector
    for (i = 0; i <= W - 4; i = i + 1)                       // iterate on structure depth
        for (j = 0; j <= i / 3; j = j + 1)                     // iterate on structure width
            if (bcd[W - i + 4 * j - : 4] > 4)                      // if > 4
                bcd[W - i + 4 * j - : 4] = bcd[W - i + 4 * j - : 4] + 4'd3; // add 3
end

endmodule

方法2:采用状态机实现位宽可变

来自nandland。 Binary_to_BCD.v,实测会消耗较多时钟周期。

/*
    INPUT_WIDTH = 8;
    DECIMAL_DIGITS = 3;
    消耗60 clk
*/

module Binary_to_BCD #(
    parameter INPUT_WIDTH,
    parameter DECIMAL_DIGITS
)(
    //Inputs
    input i_Clock,
    input i_Rst_n,
    input [INPUT_WIDTH - 1 : 0] i_Binary,
    input i_Start,
    
    //Outputs
    output [DECIMAL_DIGITS * 4 - 1 : 0] o_BCD,
    output o_DV
);

localparam s_IDLE = 3'b000;
localparam s_SHIFT = 3'b001;
localparam s_CHECK_SHIFT_INDEX = 3'b010;
localparam s_ADD = 3'b011;
localparam s_CHECK_DIGIT_INDEX = 3'b100;
localparam s_BCD_DONE = 3'b101;

reg [2 : 0] r_SM_Main = s_IDLE;
reg [DECIMAL_DIGITS * 4 - 1 : 0] r_BCD = 0;
reg [INPUT_WIDTH - 1 : 0] r_Binary = 0;
reg [DECIMAL_DIGITS - 1 : 0] r_Digit_Index = 0;
reg [7 : 0] r_Loop_Count = 0;
reg r_DV = 1'b0;

wire [3 : 0] w_BCD_Digit = r_BCD[r_Digit_Index * 4 + : 4];

assign o_BCD = r_BCD;
assign o_DV = r_DV;

always @(posedge i_Clock) begin
    if(!i_Rst_n) begin
        r_BCD <= 'h0;
        r_Binary <= 'h0;
        r_DV <= 'h0;
        r_SM_Main <= 'h0;
        r_Loop_Count <= 'h0;
        r_Digit_Index <= 'h0;
    end
    else begin
        case (r_SM_Main)
            // Stay in this state until i_Start comes along
            s_IDLE : begin
                r_DV <= 1'b0;
                if (i_Start == 1'b1) begin
                    r_Binary <= i_Binary;
                    r_SM_Main <= s_SHIFT;
                    r_BCD <= 0;
                end
                else
                    r_SM_Main <= s_IDLE;
            end
            
            // Always shift the BCD Vector until we have shifted all bits through
            // Shift the most significant bit of r_Binary into r_BCD lowest bit.
            s_SHIFT : begin
                r_BCD <= r_BCD << 1;
                r_BCD[0] <= r_Binary[INPUT_WIDTH - 1];
                r_Binary <= r_Binary << 1;
                r_SM_Main <= s_CHECK_SHIFT_INDEX;
            end
            
            // Check if we are done with shifting in r_Binary vector
            s_CHECK_SHIFT_INDEX : begin
                if (r_Loop_Count == INPUT_WIDTH - 1) begin
                    r_Loop_Count <= 0;
                    r_SM_Main <= s_BCD_DONE;
                end
                else begin
                    r_Loop_Count <= r_Loop_Count + 1;
                    r_SM_Main <= s_ADD;
                end
            end
            
            // Break down each BCD Digit individually.? Check them one-by-one to
            // see if they are greater than 4.? If they are, increment by 3.
            // Put the result back into r_BCD Vector.?
            s_ADD : begin
                if (w_BCD_Digit > 4) begin
                    r_BCD[(r_Digit_Index * 4) + : 4] <= w_BCD_Digit + 3;
                end
                r_SM_Main <= s_CHECK_DIGIT_INDEX;
            end
            
            // Check if we are done incrementing all of the BCD Digits
            s_CHECK_DIGIT_INDEX : begin
                if (r_Digit_Index == DECIMAL_DIGITS - 1) begin
                    r_Digit_Index <= 0;
                    r_SM_Main <= s_SHIFT;
                end
                else begin
                    r_Digit_Index <= r_Digit_Index + 1;
                    r_SM_Main <= s_ADD;
                end
            end
            
            s_BCD_DONE : begin
                r_DV <= 1'b1;
                r_SM_Main <= s_IDLE;
            end
            default :
                r_SM_Main <= s_IDLE;
        endcase
    end
end // always @ (posedge i_Clock)?

endmodule // Binary_to_BCD

仿真文件,Binary_to_BCD_tb.v:

`timescale 1ns/1ps

module Binary_to_BCD_tb;

reg clk;
reg rst_n;

reg start;
reg [7:0] bin;
wire [11:0] dec;
wire done;
wire [3:0] dec_b = dec[11:8];
wire [3:0] dec_s = dec[7:4];
wire [3:0] dec_g = dec[3:0];
wire [7:0] dec_real = dec_b * 100 + dec_s * 10 + dec_g;

reg [7:0] idx;
always # (10/2) clk <= !clk;

initial begin
    clk = 0;
    rst_n = 0;
    start = 0;
    bin = 0;
    idx = 0;
    #50
    @(posedge clk);
    rst_n = 1;
    #50
    @(posedge clk);
    
    for(idx = 0; idx < 254; idx = idx + 1)
        trig_bcd_covert(idx);

    #2000;
    $stop();
    $display("stop");
end

task trig_bcd_covert(
    input [7:0] i_bin
);  
begin
    #80
    @(posedge clk);
    bin = i_bin;
    start = 1;
    @(posedge clk);
    start = 0;
    @(posedge done);
    if(dec_real == i_bin)
        $display("ok:%3d -> %1x%1x%1x", bin, dec_b, dec_s, dec_g);
    else 
        $display("err:%3d -> %1x%1x%1x", bin, dec_b, dec_s, dec_g);
end
endtask

Binary_to_BCD #(
    .INPUT_WIDTH(8),
    .DECIMAL_DIGITS(3)
)Binary_to_BCD_ut0(
    //Inputs
    .i_Clock(clk),
    .i_Rst_n(rst_n),
    .i_Binary(bin[7:0]),
    .i_Start(start),
    
    //Outputs
    .o_BCD(dec[11:0]),
    .o_DV(done)
);

endmodule

方法3:个人写的基于状态机的BCD码转换

只支持8为二进制转BCD,其他位宽也是一样的操作,bin_to_bcd.v

/*
    二进制转BCD码
    243(11110011) = 0010 0100 0011 = 2/4/3
    消耗17个clk = 9+8
*/

module bin_to_bcd(
    input clk,
    input rst_n,
    input start, //1 clk high pulse
    input [7:0] bin,
    
    output reg [11:0] bcd,  //[W + (W - 4) / 3: 0], w=8 -> 8+1=9:0=10
    output done     //need 17 clk time
);

localparam S0_IDLE    = 0;
localparam S1_SHIFT   = 1;
localparam S2_ADD     = 2;
localparam S3_FINISH  = 4;

reg [3:0] fsm;
reg [3:0] cnt_shift;        //max=8
reg [7:0] bin_buf;
reg [11:0] bcd_buf;

assign done = (fsm == S3_FINISH);

always @ (posedge clk) begin
    if(!rst_n) begin
        fsm <= S0_IDLE;
        cnt_shift <= 'h0;
        bcd_buf <= 'h0;
        bin_buf <= 'h0;
        bcd <= 'h0;
    end
    else begin
        case(fsm)
            S0_IDLE: begin
                if(start) begin
                    fsm <= S1_SHIFT;
                    bin_buf <= bin;
                    cnt_shift <= 'h0;
                    bcd_buf <= 'h0;
                end
            end
            
            //移位操作
            S1_SHIFT: begin
                if(cnt_shift != 8) begin
                    cnt_shift <= cnt_shift + 1;
                    {bcd_buf, bin_buf} <= {bcd_buf, bin_buf} << 1;
                    fsm <= S2_ADD;
                end
                else 
                    fsm <= S3_FINISH;
            end
            
            //加3操作
            S2_ADD: begin
                if(cnt_shift != 8) begin
                    fsm <= S1_SHIFT;
                    if(bcd_buf[11:8] > 4)
                        bcd_buf[11:8] <= bcd_buf[11:8] + 'd3;
                    if(bcd_buf[7:4] > 4)
                        bcd_buf[7:4] <= bcd_buf[7:4] + 'd3;
                    if(bcd_buf[3:0] > 4)
                        bcd_buf[3:0] <= bcd_buf[3:0] + 'd3;
                end
                else begin
                    fsm <= S3_FINISH;
                    bcd <= bcd_buf;
                end
            end
            
            //完成
            S3_FINISH: begin
                fsm <= S0_IDLE;
                bin_buf <= 'h0;
                cnt_shift <= 'h0;
                bcd_buf <= 'h0;
            end
        endcase
    end
end
endmodule

仿真文件,bin_to_bcd_tb.v:

`timescale 1ns/1ps

module bin_to_bcd_tb;

reg clk;
reg rst_n;

reg start;
reg [7:0] bin;
wire [11:0] bcd;
wire done;
wire [3:0] bcd_b = bcd[11:8];
wire [3:0] bcd_s = bcd[7:4];
wire [3:0] bcd_g = bcd[3:0];
wire [7:0] bcd_real = bcd_b * 100 + bcd_s * 10 + bcd_g;

reg [7:0] idx;
always # (10/2) clk <= !clk;

initial begin
    clk = 0;
    rst_n = 0;
    start = 0;
    bin = 0;
    idx = 0;
    #50
    @(posedge clk);
    rst_n = 1;
    #50
    @(posedge clk);
    
    for(idx = 0; idx < 254; idx = idx + 1)
        trig_bcd_covert(idx);
    #2000;
    $stop();
    $display("stop");
    
end

task trig_bcd_covert(
    input [7:0] i_bin
);  
begin
    #80
    @(posedge clk);
    bin = i_bin;
    start = 1;
    @(posedge clk);
    start = 0;
    @(posedge done);
    if(bcd_real == i_bin)
        $display("ok:%3d -> %1x%1x%1x", bin, bcd_b, bcd_s, bcd_g);
    else 
        $display("err:%3d -> %1x%1x%1x", bin, bcd_b, bcd_s, bcd_g);
end
endtask

bin_to_bcd bin_to_bcd_ut0(
    .clk(clk),
    .rst_n(rst_n),
    .start(start),
    .bin(bin[7:0]),
    
    .bcd(bcd[11:0]),
    .done(done)
);

endmodule