赞
踩
BCD码(Binary-Coded Decimal),用4位二进制数来表示1位十进制数中的0~9这10个数码,是一种二进制的数字编码形式,用二进制编码的十进制代码。BCD码这种编码形式利用了四个位来储存一个十进制的数码,使二进制和十进制之间的转换得以快捷的进行。
实现BCD译码并显示十进制结果的程序,例化时只需要指定输入数据的最大位宽即可,其余参数无需输入,该模块内部自动确定,从而是实现简单例化。
信号 | 输出输出方向 | 位宽 | 定义 |
---|---|---|---|
clk | 输入 | 1 | 系统工作时钟,50MHZ |
rst_n | 输入 | 1 | 系统复位,低电平有效 |
din | 输入 | DATA_W | 需要转换的二进制数据 |
din_vld | 输入 | 1 | 需要转换的二进制数据有效指示信号 |
dout | 输出 | 根据din变化 | 转换后的BCD码输出数据 |
dout_vld | 输出 | 1 | 转换后的BCD码输出数据有效指示信号 |
rdy | 输出 | 1 | 忙闲指示信号,高电平时,才能输入数据到此模块进行转换 |
正常的接口时序如下,rdy为高电平时,din_vld信号拉高一个时钟,直到dout_vld信号为高电平时,表示转换完成。注意:如果在rdy为低电平期间将din_vld拉高,为了保证之前的数据转换正确,则此时输入的din信号是无效的。
转换原理参考小梅哥的一篇文章,链接:
【小梅哥FPGA进阶教程】第二章 二进制转BCD - FPGA/CPLD - 电子工程世界-论坛
比如一个n位二进制 b n − 1 b n − 2 . . . . . . b 2 b 1 b 0 b_{n-1}b_{n-2}......b_2b_1b_0 bn−1bn−2......b2b1b0,其在十进制编码方式的值为:
N D = ∑ i = 0 n − 1 b i ∗ 2 i = b n − 1 ∗ 2 n − 1 + b n − 2 ∗ 2 n − 2 + . . . + b 1 ∗ 2 1 + b 0 N_D=\sum_{i=0}^{n-1}{b_i*2^i}=b_{n-1}*2^{n-1}+b_{n-2}*2^{n-2}+...+b_1*2^1+b_0 ND=i=0∑n−1bi∗2i=bn−1∗2n−1+bn−2∗2n−2+...+b1∗21+b0
把上面的公式拆解一下得到下式:
N D = { . . . { ( ( b n − 1 ∗ 2 + b n − 2 ) ∗ 2 + b n − 3 ) ∗ 2... } ∗ 2 + b 1 } ∗ 2 + b 0 N_D=\{...\{((b_{n-1}*2+b_{n-2})*2+b_{n-3})*2...\}*2+b_1\}*2+b_0 ND={...{((bn−1∗2+bn−2)∗2+bn−3)∗2...}∗2+b1}∗2+b0
式中每项乘2,其实就是将二进制数据左移一位,只是在移位的过程中要做一些修正。
在移位的过程中,当现态 S n < 5 S_n<5 Sn<5时,次态不变。当现态 5 ≤ S n ≤ 7 5≤S_n≤7 5≤Sn≤7时,左移一次,其次态 S n + 1 S_{n+1} Sn+1将会超过9,对于一个BCD码来说,这样的状态属于禁用状态。而当 8 ≤ S n ≤ 9 8≤S_n≤9 8≤Sn≤9时,左移1位,则会向高1位的BCD码输入一个进位的信号,由于二进制和BCD码权不一致,当发生进位时,虽然码元只是左移1位,但次态 S n + 1 S_{n+1} Sn+1将减少6。基于上面这两种情况,在BCD转换时需要对转换结果加以校正。校正过程如下:
当 S n ≥ 5 S_{n}≥5 Sn≥5 时,让 S n S_n Sn先加上3,然后再左移1位,次态 S n + 1 = 2 ∗ ( S n + 3 ) = 2 ∗ S n + 6 S_{n+1}=2*(S_n+3)=2*S_n+6 Sn+1=2∗(Sn+3)=2∗Sn+6,正好补偿由于进位而减少的数值,并且向后一个变换单元送入一个进位信号,这个方法 叫“加3移位法”。
注意:现态 S n S_n Sn和次态 S n + 1 S_{n+1} Sn+1都是指BCD码,即用4位二进制表示的1位BCD码。当 S n = 8 或 S n = 9 S_n=8或S_n=9 Sn=8或Sn=9时:BCD码的1000(8)乘以2为0001_0110(16),但是左移后变为0001_0000,减少了6。所以需要加上6,这里的方法是加3左移一位,相当于加上6。
左移加3算法
此处二进制转 BCD 码的硬件实现,采用左移加 3 的算法,具体描述如下:(此处以 8-bit 二进制码为例)
1、左移要转换的二进制码 1 位
2、左移之后,BCD 码分别置于百位、十位、个位
3、如果移位后所在的 BCD 码列大于或等于 5,则对该值加 3
4、继续左移的过程直至全部移位完成
举例:将十六进制码 0xFF 转换成 BCD 码
Operation | Hundreds | Tens | Units | Binary | |
---|---|---|---|---|---|
HEX | F | F | |||
Start | 1111 | 1111 | |||
Shift 1 | 1 | 1111 | 1110 | ||
Shift 2 | 11 | 1111 | 1100 | ||
Shift 3 | 111 | 1111 | 1000 | ||
Add 3 | 1010 | 1111 | 1000 | ||
Shift 4 | 1 | 0101 | 1111 | 0000 | |
Add 3 | 1 | 1000 | 1111 | 0000 | |
Shift 5 | 11 | 0001 | 1110 | 0000 | |
Shift 6 | 110 | 0011 | 1100 | 0000 | |
Add 3 | 1001 | 0011 | 1100 | 0000 | |
Shift 7 | 1 | 0010 | 0111 | 1000 | 0000 |
Add 3 | 1 | 0010 | 1010 | 1000 | 0000 |
Shift 8 | 10 | 0101 | 0101 | 0000 | 0000 |
BCD | 2 | 5 | 5 |
知道原理之后,结合能够根据输入信号位宽自动得到输出数据位宽,尽量节约资源等信息分析,如果不需要手动修改其余参数,就不适用采用流水线的实现方式,流水线实现方式每个时钟都可以输入信号,但是如果输入数据的位宽过大,将会消耗大量资源。故采用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和输入数据的影响。
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
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位的仿真结果:
IN_DATA_W为16位·时的仿真结果如下:
该模块只需要确定输入数据的最大位宽,就可以自动计算出模块内部以及输出信号的位宽,但是输出数据会以十进制的最大位宽设置,比如输入数据是12位,输出数据最大位宽应该是16383对应的17位数据,但是模块自动计算的输出数据位宽会是20位,这是由于自动计算时的机制决定的,修改这部分需要结合内部代码一起修改,当然使用者可以只将输出信号的低17位输出也行。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。