最近在研究FPGA,涉及到一个将二进制数转换为BCD码显示出来的工作,整理出一套同时支持整数和小数的由二进制转换为十进制的算法。这套算法是可综合并且实测有效的。

这个电路的设计思路来源于两方面。一方面是人工进行进制转换的方法:将每一位二进制数所代表的十进制数加起来;另一方面是汇编语言中的BCD加法调整指令DAA的算法:若加法的结果大于9,则标记进位并且将结果再加上6。

电路的原理大致总结如下:

  1. 将二进制数分为几个部分(例如每1位一组,或者4位一组),然后将这几个部分手动转换成BCD码。比如这样:A(5) = 1 -> 16, = 0 -> 0或者A(7 downto 4) = 1 -> x"16", = 2 -> x"32"等等。
  2. 将几个部分的二进制数转换为的BCD码用BCD加法加起来。

根据纸上谈兵的分析,如果每一位分为一组的话,则输入数据需要经过大量的加法器才能到达输出,延时应该会比较显著。如果很多位分为一组的话,那么你需要做很多的工作。

以下是我做的8位二进制整数+4位二进制小数转BCD码的例子。另外你可以直接跳到最后一段代码,查看关键算法之BCD加法算法。

首先展示整数部分的转换。Whole为输入的二进制整数(unsigned类型),WholeBcd为输出的BCD码(std_logic_vector类型)。这里采用的是4位一组。

process (Whole)
	variable result: unsigned(11 downto 0);
begin
	result := to_unsigned(0, 12);
	
	if (Whole(3 downto 0) >= 10) then
		result := result + Whole(3 downto 0) + 6;
	else
		result := result + Whole(3 downto 0);
	end if;
	
	case Whole(7 downto 4) is
		when "0000" => result := result;
		when "0001" => result := BcdAdd(result, "000000010110");
		when "0010" => result := BcdAdd(result, "000000110010");
		when "0011" => result := BcdAdd(result, "000001001000");
		when "0100" => result := BcdAdd(result, "000001100100");
		when "0101" => result := BcdAdd(result, "000010000000");
		when "0110" => result := BcdAdd(result, "000010010110");
		when "0111" => result := BcdAdd(result, "000100010010");
		when "1000" => result := BcdAdd(result, "000100101000");
		when "1001" => result := BcdAdd(result, "000101000100");
		when "1010" => result := BcdAdd(result, "000101100000");
		when "1011" => result := BcdAdd(result, "000101110110");
		when "1100" => result := BcdAdd(result, "000110010010");
		when "1101" => result := BcdAdd(result, "001000001000");
		when "1110" => result := BcdAdd(result, "001000100100");
		when "1111" => result := BcdAdd(result, "001001000000");
		when others => result := result;
	end case;
	
	WholeBcd <= std_logic_vector(result);
end process;

小数部分的转换采用的是每一位单独转换(因为总共也没多少位)之后加起来。

process (Fraction)
	variable result: unsigned(15 downto 0);
begin
	result := to_unsigned(0, 16);
	if (Fraction(0) = '1') then
		result := result + x"0625";
	end if;
	if (Fraction(1) = '1') then
		result := BcdAdd(result, x"1250");
	end if;
	if (Fraction(2) = '1') then
		result := BcdAdd(result, x"2500");
	end if;
	if (Fraction(3) = '1') then
		result := BcdAdd(result, x"5000");
	end if;
	
	FractionBcd <= std_logic_vector(result);
end process;

最后是这个谜一般的BCD加法函数。

function Max(L, R: integer) return integer is
begin
	if (L > R) then
		return L;
	else
		return R;
	end if;
end function;

function BcdAdd(L, R: unsigned) return unsigned is
	variable size: integer := Max(L'length, R'length);
	variable i: integer;
	variable result: unsigned(size - 1 downto 0);
	variable l1, r1: unsigned(size - 1 downto 0);
begin
	if (L'length > R'length) then
		size := L'length;
	else
		size := R'length;
	end if;
	
	result := (others => '0');
	l1 := resize(L, size);
	r1 := resize(R, size);
	i := 0;
	while (i + 4 < size) loop if (result(i + 4 downto i) + l1(i + 3 downto i) + r1(i + 3 downto i) >= 10) then
			result(i + 4 downto i) :=
				result(i + 4 downto i) + l1(i + 3 downto i) + r1(i + 3 downto i) + 6;
		else
			result(i + 4 downto i) := 
				result(i + 4 downto i) + l1(i + 3 downto i) + r1(i + 3 downto i);
		end if;
		i := i + 4;
	end loop;
	
	if ('0' & result(size - 1 downto i) + l1(size - 1 downto i) + r1(size - 1 downto i) >= 10) then
		result(size - 1 downto i) := 
			result(size - 1 downto i) + l1(size - 1 downto i) + r1(size - 1 downto i) + 6;
	else
		result(size - 1 downto i) := 
			result(size - 1 downto i) + l1(size - 1 downto i) + r1(size - 1 downto i);
	end if;
	
	return result;
end function;

最后加一句声明:这样弄出来虽然结果比较正确,但是用Lattice Diamond综合的过程中出来不少no driver的警告,并不清楚是为什么……