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" & 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ビットの丸めなので、波形で見てもわからないと思います。フィルタ等で何度も演算を繰り返すと、丸めの誤差が大きくなって、歪として現れる場合があります。
-
前の記事
Verilogで固定小数点の設計 2020.08.14
-
次の記事
平均化のデジタル回路設計 2020.08.22

コメントを書く