VHDLで固定小数点の設計

VHDLで固定小数点の設計

固定小数点の設計をVHDLでも行ってみます。Verilogはこちらをご覧ください。

 まずはQ14フォーマットの乗算のソースコードを示します。

----------------------------------------------------------------------------------------------------
--  TITLE:					"multiQ14.vhd"
--  MODULE NAME:			
--  PROJECT CODE:			
--  AUTHOR:					 (****@nakaharagiken.com)
--  CREATION DATE:			2020.8.14
--  SOURCE:            		
--  LICENSE:           		Copyright (c) 2020 nakaharagiken All rights reserved. 
--  DESCRIPTION:            
--  NOTES TO USER:			Q14フォーマットどうしの乗算器
--  SOURCE CONTROL:			
--  REVISION HISTORY:  	    v0.1	2020.8.14	First edition
--
--							丸めは上位のブロックで行う事
--							Q14 = 2^14 * flortingdata
--
--              15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
--               |  |^<--------------小数点以下 -------------->
--               |  ||------ 小数点
--               |  |------- 整数
--               |---------- ± 1=負
--
--             (15)14*13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
--      ×)   (15)14*13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0(13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
--     ---------------------------------------------------------------------------------------------------------------
--        31,30,29,28*27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
--------------------------------------------------------------------------------------------------------------------------------------------

-------------------------------------------------------------
-- ライブラリ宣言
-------------------------------------------------------------
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;
use IEEE.std_logic_arith.all;
-------------------------------------------------------------
-- エンティティ宣言
-------------------------------------------------------------
entity multiQ14 is port(
	indatA:		in		std_logic_vector(15 downto 0);			--	[15:0] Q14 format input
	indatB:		in		std_logic_vector(15 downto 0);			--	[15:0] Q14 format input
	outdat:		out		std_logic_vector(29 downto 0)			--	[29:0] Q28 format output
);
end multiQ14;

-------------------------------------------------------------
-- アーキテクチャ宣言
-------------------------------------------------------------
architecture RTL of multiQ14 is

--------------------------------------------------------------
-- SIGNALS
---------------------------------------------------------------
signal	wmultiQ28:		std_logic_vector(31 downto 0);

begin
	wmultiQ28 <= signed(indatA) * signed(indatB);
	outdat <= wmultiQ28(29 downto 0);
end RTL;

-------------------------------------------------------------

 VHDLで乗算しているのは57行目です。Q14フォーマット同士の乗算を行っています。乗算の場合には、桁数(ビット数)が増えるので注意してください。16ビット×16ビット=32ビットになっています。丸めについては後半のテストベンチで解説します。

 テストベンチは少しアナログっぽい事をしたいので、Sin波形のROMを作って、その波形をアンプすることにします。倍率は0.1~1倍。それと反転もできるので、-0.1~-1倍。ここまで一気にシミュレートします。

 まずはSin波形のROMを作ります。10Mspsで500kHzのROMテーブルです。

--  TITLE:					"SIN_ROM.vhd"
--  MODULE NAME:			
--  PROJECT CODE:			
--  AUTHOR:					 (****@nakaharagiken.com)
--  CREATION DATE:			2020.8.15
--  SOURCE:            		
--  LICENSE:           		Copyright (c) 2020 nakaharagiken All rights reserved. 
--  DESCRIPTION:            
--  NOTES TO USER:			Q14フォーマットSIN波形
--  SOURCE CONTROL:			
--  REVISION HISTORY:  	    v0.1	2020.8.15	First edition
--							
--							sin data 10Msps 500kHz
--
-------------------------------------------------------------
-- ライブラリ宣言
-------------------------------------------------------------
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;
use IEEE.std_logic_arith.all;
-------------------------------------------------------------
-- エンティティ宣言
-------------------------------------------------------------
entity SIN_ROM is port(
	clock:		in		std_logic;
	nreset:		in		std_logic;								-- __reset___|~~normal~~~
	enable:		in		std_logic;								-- イネーブル		_____|~|_________
	address:	in		std_logic_vector(4 downto 0);			-- アドレス
	Sindata:	out		std_logic_vector(15 downto 0)			-- Sin波形
);
end SIN_ROM;


-------------------------------------------------------------
-- アーキテクチャ宣言
-------------------------------------------------------------
architecture RTL of SIN_ROM is

--------------------------------------------------------------
-- SIGNALS
---------------------------------------------------------------
signal	rSindata:		std_logic_vector(15 downto 0);

-- sin data 10Msps 500kHz
constant	ROM0	:	std_logic_vector(15 downto 0) := x"0000";
constant	ROM1	:	std_logic_vector(15 downto 0) := x"0FD2";
constant 	ROM2   	:	std_logic_vector(15 downto 0) := x"1E18";
constant 	ROM3   	:	std_logic_vector(15 downto 0) := x"296B";
constant	ROM4  	:	std_logic_vector(15 downto 0) := x"30B1";
constant 	ROM5   	:	std_logic_vector(15 downto 0) := x"3333";
--constant	ROM6   	:	std_logic_vector(15 downto 0) := x"30B1";	ROM4と同じ
--constant 	ROM7   	:	std_logic_vector(15 downto 0) := x"296B";	ROM3と同じ
--constant 	ROM8  	: 	std_logic_vector(15 downto 0) := x"1E18";	ROM2と同じ
--constant 	ROM9  	: 	std_logic_vector(15 downto 0) := x"0FD2";	ROM1と同じ
--constant 	ROM10  	:	std_logic_vector(15 downto 0) := x"0000";	ROM0と同じ
constant 	ROM11  	:	std_logic_vector(15 downto 0) := x"F02E";
constant 	ROM12  	:	std_logic_vector(15 downto 0) := x"E1E8";
constant 	ROM13  	:	std_logic_vector(15 downto 0) := x"D695";
constant 	ROM14  	:	std_logic_vector(15 downto 0) := x"CF4F";
constant 	ROM15  	:	std_logic_vector(15 downto 0) := x"CCCD";
--constant 	ROM16	:	std_logic_vector(15 downto 0) := x"CF4F";	ROM14と同じ
--constant 	ROM17	:	std_logic_vector(15 downto 0) := x"D695";	ROM13と同じ
--constant 	ROM18	:	std_logic_vector(15 downto 0) := x"E1E8";	ROM12と同じ
--constant 	ROM19	:	std_logic_vector(15 downto 0) := x"F02E";	ROM11と同じ

begin

process (clock,nreset) begin
	if nreset = '0' then									-- リセット初期化
		rSindata <= ROM0;
	elsif clock' event and clock = '1' then					-- clock 立ち上がり指示
		if (enable = '1') then
			case address is
				when	"00000"	=>	rSindata <= ROM0;		-- 0
				when	"00001"	=>	rSindata <= ROM1;		-- 1
				when	"00010"	=>	rSindata <= ROM2;		-- 2
				when	"00011"	=>	rSindata <= ROM3;		-- 3
				when	"00100"	=>	rSindata <= ROM4;		-- 4
				when	"00101"	=>	rSindata <= ROM5;		-- 5
				when	"00110"	=>	rSindata <= ROM4;		-- 6
				when	"00111"	=>	rSindata <= ROM3;		-- 7
				when	"01000"	=>	rSindata <= ROM2;		-- 8
				when	"01001"	=>	rSindata <= ROM1;		-- 9
				when	"01010"	=>	rSindata <= ROM0;		-- 10
				when	"01011"	=>	rSindata <= ROM11;		-- 11
				when	"01100"	=>	rSindata <= ROM12;		-- 12
				when	"01101"	=>	rSindata <= ROM13;		-- 13
				when	"01110"	=>	rSindata <= ROM14;		-- 14
				when	"01111"	=>	rSindata <= ROM15;		-- 15
				when	"10000"	=>	rSindata <= ROM14;		-- 16
				when	"10001"	=>	rSindata <= ROM13;		-- 17
				when	"10010"	=>	rSindata <= ROM12;		-- 18
				when	"10011"	=>	rSindata <= ROM11;		-- 19
				when others => rSindata <= ROM0;
			end case;
		end if;
	end if;
end process;

Sindata <= rSindata;
end architecture rtl;

 Sinの波形は元々-1~1になりますが、今回は-0.8~0.8の波形にしてあります。この振幅にあまり意味はありません。-1~1以内であれば問題ありません。

 Sin波形は繰り返し部分が多いので本来は乗算まで使えば1/4の波形をROMに持てば波形を作れますが、今回はプラス側とマイナス側で半分持っています。45行目~65行目がSinのテーブル値です。このようなROMに使うテーブルは、表計算ソフトで計算するとQフォーマットまで作れますので便利です。

--  TITLE:					"TB_multiQ14.vhd"
--  MODULE NAME:			
--  PROJECT CODE:			
--  AUTHOR:					 (****@nakaharagiken.com)
--  CREATION DATE:			2020.8.15
--  SOURCE:            		
--  LICENSE:           		Copyright (c) 2020 nakaharagiken All rights reserved. 
--  DESCRIPTION:            
--  NOTES TO USER:			multiQ14.vhdのテストベンチ
--  SOURCE CONTROL:			
--  REVISION HISTORY:  	    v0.1	2020.8.15	First edition
--
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
use IEEE.std_logic_arith.all;

entity TB_multiQ14 is
end TB_multiQ14;

architecture sim of TB_multiQ14 is

-- 回路記述部
-------------------------------------------------------------

component multiQ14
port(
	indatA:		in		std_logic_vector(15 downto 0);			--	[15:0] Q14 format input
	indatB:		in		std_logic_vector(15 downto 0);			--	[15:0] Q14 format input
	outdat:		out		std_logic_vector(29 downto 0)			--	[29:0] Q28 format output
);
end component;

component SIN_ROM
port(
	clock:		in		std_logic;
	nreset:		in		std_logic;								-- __reset___|~~normal~~~
	enable:		in		std_logic;								-- イネーブル		_____|~|_________
	address:	in		std_logic_vector(4 downto 0);			-- アドレス
	Sindata:	out		std_logic_vector(15 downto 0)			-- Sin波形
);
end component;


constant SYSCLK_PERIOD : time := 100 ns; -- 10MHz


-- 信号の宣言
signal	i_clock:			std_logic;							-- system clock
signal	i_nreset:			std_logic;							-- asynchronous reset 				____reset___|~~~normal~~~
signal	i_enable:			std_logic;							-- イネーブル
signal	raddress:			std_logic_vector(4 downto 0);		-- アドレス
signal	i_coeff:			std_logic_vector(15 downto 0);		-- 掛け算
signal	w_sindata:			std_logic_vector(15 downto 0);		-- Sin波形
signal	o_Multiout:			std_logic_vector(29 downto 0);		-- 出力信号
signal	w_Round:			std_logic_vector(15 downto 0);		-- 丸め出力

----------------------------------------------------------------------
-- Main
----------------------------------------------------------------------

begin

inst_multiQ14 : multiQ14
port map(
	indatA		=>	w_sindata,			--	in	std_logic_vector(17 downto 0);			--	[17:0] Q16 format input
	indatB		=>	i_coeff,			--	in	std_logic_vector(17 downto 0);			--	[17:0] Q16 format input
	outdat		=>	o_Multiout				--	out	std_logic_vector(17 downto 0)
);

inst_SIN_ROM : SIN_ROM
port map(
	clock		=>	i_clock,			--	in		std_logic;
	nreset		=>	i_nreset,			--	in		std_logic;								-- __reset___|~~normal~~~
	enable		=>	i_enable,			--	in		std_logic;								-- イネーブル		_____|~|_________
	address		=>	raddress,			--	in		std_logic_vector(4 downto 0);			-- アドレス
	Sindata		=>	w_sindata			--	out		std_logic_vector(15 downto 0);			-- Sin波形
);

-- Clock Driver
process begin
	i_clock <= '1';
	wait for SYSCLK_PERIOD / 2;
	i_clock <= '0';
	wait for SYSCLK_PERIOD / 2;
end process;

----------------------------------------------------------------------
-- make address
----------------------------------------------------------------------
process (i_clock,i_nreset) begin
	if i_nreset = '0' then
		raddress <= (others =>'0');
	elsif i_clock' event and i_clock = '1' then
		if (raddress < "10010") then
			raddress <= raddress + '1';
		else
			raddress <= (others => '0');
		end if;
	end if;
end process;

--        29,28*27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
--        15,14*13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
	w_Round <= o_Multiout(29 downto 14) + ("000000000000000"  &amp; o_Multiout(13));		-- 実際に使う時にはラッチすること

process begin
	i_nreset <= '0';
	i_enable <= '0';
	i_coeff <= (others => '0');


	wait for (SYSCLK_PERIOD * 50);
	i_nreset <= '1';
	wait for (SYSCLK_PERIOD * 100);
	i_enable <= '1';

	i_coeff <= x"0666";					-- x0.1
	wait for (SYSCLK_PERIOD * 100);
	i_coeff <= x"0CCC";					-- x0.2
	wait for (SYSCLK_PERIOD * 100);
	i_coeff <= x"1333";					-- x0.3
	wait for (SYSCLK_PERIOD * 100);
	i_coeff <= x"1999";					-- x0.4
	wait for (SYSCLK_PERIOD * 100);
	i_coeff <= x"2000";					-- x0.5
	wait for (SYSCLK_PERIOD * 100);
	i_coeff <= x"2666";					-- x0.6
	wait for (SYSCLK_PERIOD * 100);
	i_coeff <= x"2CCC";					-- x0.7
	wait for (SYSCLK_PERIOD * 100);
	i_coeff <= x"3333";					-- x0.8
	wait for (SYSCLK_PERIOD * 100);
	i_coeff <= x"3999";					-- x0.9
	wait for (SYSCLK_PERIOD * 100);
	i_coeff <= x"4000";					-- x1.0
	wait for (SYSCLK_PERIOD * 100);
	i_coeff <= x"F99A";					-- x-0.1
	wait for (SYSCLK_PERIOD * 100);
	i_coeff <= x"F334";					-- x-0.2
	wait for (SYSCLK_PERIOD * 100);
	i_coeff <= x"ECCD";					-- x-0.3
	wait for (SYSCLK_PERIOD * 100);
	i_coeff <= x"E667";					-- x-0.4
	wait for (SYSCLK_PERIOD * 100);
	i_coeff <= x"E000";					-- x-0.5
	wait for (SYSCLK_PERIOD * 100);
	i_coeff <= x"D99A";					-- x-0.6
	wait for (SYSCLK_PERIOD * 100);
	i_coeff <= x"D334";					-- x-0.7
	wait for (SYSCLK_PERIOD * 100);
	i_coeff <= x"CCCD";					-- x-0.8
	wait for (SYSCLK_PERIOD * 100);
	i_coeff <= x"C667";					-- x-0.9
	wait for (SYSCLK_PERIOD * 100);
	i_coeff <= x"C000";				-- x-1.0
end process;


end sim;

 91行目でカウンタを回して、Sinのテーブル用アドレスを作成して、ROMテーブルから読んでいます。

 ROMのデータは77行目でw_sindataに入りますので、i_coeffと乗算を行います。i_coeffは0.1~1および-0.1~-1まで変化させています。

 丸めについてはテストベンチの105行目で行っています。仮に丸めをおこなうとすればこうします。という事になります。今回は乗算の入力が16ビットだったので、出力も合わせて16ビットにします。103行目のコメントが分かりやすいと思います。乗算で得られた30bitのうち、MSBから16bitを使います。この場合、o_Multiout(13)のビットが丸め対象になりますので、このビットをo_Multiout(14)の桁に加算しています。これで四捨五入(零捨一入)になります。四捨五入の定理に従って、条件分けしなくても2進数なのでこれでOKです。VHDLの場合、このような加算において桁数を合わせる必要があります。

 最後に、本ブログページの冒頭のアイキャッチ画像はModelSimでシミュレートした出力波形です。下2つのアナログ波形が乗算後の波形o_Multioutとw_Roundです。振幅が変化しているのが読み取れると思います。16ビットの丸めなので、波形で見てもわからないと思います。フィルタ等で何度も演算を繰り返すと、丸めの誤差が大きくなって、歪として現れる場合があります。