1. はじめに
FPGAにディジタルフィルタを実装したい時、FIRフィルタであればQuartus IPが用意されていますが、IIRは見つけられませんでした。そこで学びなおしも含めてVHDLでIIRフィルタを設計してみました。FPGAでディジタル信号処理を実装する時は、少数の扱いやビットあふれ(オーバーフロー)など、ソフトウェアで実装する時には無い少し面倒な点があります。
今回実装するのは双二次フィルタと呼ばれる二次のIIRフィルタです。ブロック図と伝達関数は以下の通り。a0が1となるように全体を正規化します。
2. 設計
FPGA上に設計する時は遅延器(Dフロップフロップ)、乗算器、加算器と後述のクリップ回路を使用します。実際の設計としてのブロック図は以下のようになります。
図中の数字はbit数を表し、ドットがついているのは固定少数形式であり、「整数部bit数 . 少数部bit数」となります。
①上位4bit付加
加算器によるオーバーフロー対策です。例えば4bit同士の加算を行うとき、
1001b(4bit) + 1110b(4bit) = 10111b(5bit)
のように1bit増える可能性があるため、あらかじめ1bit付加して加算を行う事で、オーバーフローによるエラーを防ぎます。
加算器は図中には3つですが、中段の加算器は3つの信号を加算しているため、実際には2つの加算器で実現されます。よって計4つの加算器に対するオーバーフロー対策をしなければなりません。また符号付き信号にパディングを行う時は単純に0パディングをすると負数だった場合に符号が変わってしまうため、最上位bitをコピーします。例えば4bit信号に対して2bit付加する場合、
0011 → 000011
1010 → 111010
とします。こうする事で、符号と数値を維持したまま、bit拡張を行う事ができます。
②下位12bit切捨て
小数bitの切り捨てです。本当は少数bitの最上位を見て四捨五入した方が正確なのですが、入出力データの16bitに対して切り捨てだろうと四捨五入だろうと大した差はないので設計簡易化のために切り捨てにしました。
・乗算器
符号拡張された20bitデータと16bit(整数部4bit、少数部12bit)フィルタ係数の乗算を行います。普通に記述すればFPGAのエンベデッド乗算器が割り当てられ、自動で符号拡張が行われます。乗算器は二つの信号のbit長の合計が出力のbit長となります。今回は固定小数点演算なので整数部は20 + 4 = 24bit、少数部は0 + 12 = 12bitが出力のbit数となります。
・加算器
24bit信号同士の加算を行います。上述の通り、入力の段階でオーバーフロー対策はしているので、ここでは普通に加算するだけです。
・FF(フリップフロップ)
1クロック分信号を遅延させます。
・CLIP回路
オーバーフロー対策や乗算器で拡張していたbitを減らします。この時、上位bitをただ無視するだけだと、bitエラーを起こすので出力のbit数で表せる最大値より大きい場合や最小値より小さい値はそれぞれ最大値、最小値に丸めます。このように一定値より大きい(小さい)値をその一定値に丸める処理をCLIPと呼びます。例として17bit符号付き整数を16bit符号付き整数にCLIP処理をかけると下図のようになります。
use IEEE.std_logic_1164.all;
use IEEE.std_logic_signed.all;
port(
RST : in std_logic; -- Low Active
CLK : in std_logic; -- Clock
a1 : in std_logic_vector(15 downto 0); -- Filter Factor(signed 整数:4bit 少数:12bit)
a2 : in std_logic_vector(15 downto 0); -- Filter Factor(signed 整数:4bit 少数:12bit)
b0 : in std_logic_vector(15 downto 0); -- Filter Factor(signed 整数:4bit 少数:12bit)
b1 : in std_logic_vector(15 downto 0); -- Filter Factor(signed 整数:4bit 少数:12bit)
b2 : in std_logic_vector(15 downto 0); -- Filter Factor(signed 整数:4bit 少数:12bit)
DATA_IN : in std_logic_vector(15 downto 0); -- Input Data(signed 16bit)
DATA_OUT : out std_logic_vector(15 downto 0); -- Output Data(signed 16bit)
OVF_FLAG : out std_logic -- Overflow Flag(H pulse)
);
architecture RTL of iir_filter is
------------------------------
-- signal --
------------------------------
signal s_data_in_d1 : std_logic_vector(19 downto 0);
signal s_data_in_d2 : std_logic_vector(19 downto 0);
signal s_data_out_d1 : std_logic_vector(19 downto 0);
signal s_data_out_d2 : std_logic_vector(19 downto 0);
signal s_mult_b2 : std_logic_vector(35 downto 0);
signal s_mult_a2 : std_logic_vector(35 downto 0);
signal s_mult_b1 : std_logic_vector(35 downto 0);
signal s_mult_a1 : std_logic_vector(35 downto 0);
signal s_mult_b0 : std_logic_vector(35 downto 0);
signal s_sum2 : std_logic_vector(23 downto 0);
signal s_sum1 : std_logic_vector(23 downto 0);
signal s_sum0 : std_logic_vector(23 downto 0);
signal s_data_in : std_logic_vector(19 downto 0);
signal s_data_out : std_logic_vector(19 downto 0);
begin
-- bit 拡張
s_data_in <= DATA_IN(15) & DATA_IN(15) & DATA_IN(15) & DATA_IN(15) & DATA_IN;
-- 遅延(FF)
process(RST, CLK) begin
if(RST = '0') then
s_data_in_d1 <= (others => '0');
s_data_in_d2 <= (others => '0');
s_data_out_d1 <= (others => '0');
s_data_out_d2 <= (others => '0');
elsif(CLK'event and CLK = '1') then
s_data_in_d1 <= s_data_in;
s_data_in_d2 <= s_data_in_d1;
s_data_out_d1 <= s_data_out;
s_data_out_d2 <= s_data_out_d1;
end if;
end process;
-- 乗算
s_mult_b2 <= b2 * s_data_in_d2;
s_mult_a2 <= a2 * s_data_out_d2;
s_mult_b1 <= b1 * s_data_in_d1;
s_mult_a1 <= a1 * s_data_out_d1;
s_mult_b0 <= b0 * s_data_in;
-- 加算
s_sum2 <= s_mult_b2(35 downto 12) - s_mult_a2(35 downto 12);
s_sum1 <= s_mult_b1(35 downto 12) - s_mult_a1(35 downto 12) + s_sum2;
s_sum0 <= s_mult_b0(35 downto 12) + s_sum1;
-- CLIP処理
s_data_out <= X"7FFFF" when(s_sum0 > X"07FFFF") else
X"80000" when(s_sum0 < X"F80000") else
s_sum0(19 downto 0);
DATA_OUT <= X"7FFF" when(s_data_out > X"07FFF") else
X"8000" when(s_data_out < X"F8000") else
s_data_out(15 downto 0);
-- Overflow flag
OVF_FLAG <= '1' when( s_sum0 > X"07FFFF" or s_sum0 < X"F80000") else
'1' when(s_data_out > X"07FFF" or s_data_out < X"F8000" ) else
'0';
end RTL;