文章目录


转换原理

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

8位二进制数243,移位加3计算过程:

FPGA Verilog实现二进制转BCD码_FPGA

16位二进制65244,移位加3计算过程:

FPGA Verilog实现二进制转BCD码_FPGA_02

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:采用状态机实现位宽可变


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