使用VHDL制作二进制转BCD电路

最近在研究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的警告,并不清楚是为什么……

留言

有想法?请给我们留言!您的留言不会直接显示在网站内。