8421BCD码介绍
BCD码(Binary-Coded Decimal),利用四个2进制位储存一个10进制的数,不同的BCD码编码方式会导致结果不一样,而8421BCD码的编码结果如下表所示。可以发现8421BCD码其实是其对应的二进制表示。
十进制数 | BCD8421码 |
---|---|
0 | 0000 |
1 | 0001 |
2 | 0010 |
3 | 0011 |
4 | 0100 |
5 | 0101 |
6 | 0110 |
7 | 0111 |
8 | 1000 |
9 | 1001 |
加3移位法
在Verilog中,当然可以同C语言一样,使用除法和求余来得到各个位上的数据,但是这将耗费大量的LE资源,因为FPGA中没有现成的除法器可用(乘法器是有的),综合时编译器会用LE强行搭一个除法器出来。
为了节省资源和增强灵活性,我们使用加3移位法和状态机相配合的方式实现功能。在进一步解释前,先看一个例子。
NUM1 | NUM2 | NUM3 | NUM4 | DATA | Operation |
---|---|---|---|---|---|
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_1111 | 4个NUM中有大于4的就加3 |
0000(0) | 0000(0) | 0001(1) | 0101(5) | 1111 | 所有数据左移一位 |
0000(0) | 0000(0) | 0001(1) | 1000(8) | 1111 | 4个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) | 11 | 4个NUM中有大于4的就加3 |
0000(0) | 0001(1) | 0010(2) | 0111(7) | 1 | 所有数据左移一位 |
0000(0) | 0001(1) | 0010(2) | 1010(10) | 1 | 4个NUM中有大于4的就加3 |
0000(0) | 0010(2) | 0101(5) | 0010(5) | 无 | 所有数据左移一位 |
表格中输入的原始数据为8'b1111_1111 = 8'd255,而推算到最后的结果也为255,说明方法是正确的。
这一方法可描述为,当任意一个NUM中的值大于4时,停止移位操作并将那个NUM的值加3后再进行移位,知道原始数据全部移入NUM中时停止。
模块设计
状态机设计
通过分析这一方法,可以发现总共要做的操作只有两个:移位和加3。
据此,可以确定状态机的状态数量为4:IDLE、ADD 、SHIFT、DONE。
状态跳转如下图:
-
当有数据到来时,从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资源的节省。