· 

VHDLで1次IIRフィルタ(1)

VHDLでフィルタを作ってみます。PythonでやったことをVHDLに落とし込むってだけ。いやーそれがしんどいんだよねー。

まず1次IIRフィルタの式はこちら。

\[ y_n=b_0x_n+b_1x_{n-1}-a_1y_{n-1} \tag{1} \]

フィルタ係数は等間隔でサンプリングすることを前提としているので、計算も一定の時間で完了することが求められる。が、面倒なので、所要より長い時間カウントして、オーバーフローで次のサンプリングと結果の出力をすることになる。FPGAでFPU(1)でためしたIPは足し算に10クロック、掛け算に15クロックかかっていた。式(1)を実行するには掛け算1回+足し算1回分すなわち最低25クロックが必要。IPの実体を複数インプリメントすることで計算を並列化して計算時間を短縮できるってこともあるけど、今回はどうにもならん。余裕を見て32クロックで1サンプリングってすると、5MS/sを実現するにはコアクロックは160MHz。う~ん、実現でけんなー。PLL内蔵タイプじゃないと、、、後で考えよう。

で、さっそく、

 

iir1_try1.vhd

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
 
entity iir1_try1 is
    port (
        i_1bit : in std_logic;
        o_32bit : out std_logic_vector(31 downto 0);
        i_start : in std_logic;
        o_complete : out std_logic;
        i_a0 : in std_logic_vector(31 downto 0);
        i_a1 : in std_logic_vector(31 downto 0);
        i_b0 : in std_logic_vector(31 downto 0);
        i_b1 : in std_logic_vector(31 downto 0);
        i_clk : in std_logic
    );
end iir1_try1;
 
architecture ppl_type of iir1_try1 is
    signal xn : std_logic;
    signal xnm1 : std_logic:='0';
    signal yn :  std_logic_vector(31 downto 0):=X"00000000";
    signal ynm1 : std_logic_vector(31 downto 0):=X"00000000";
    constant fzero : std_logic_vector(31 downto 0):=X"00000000";
    constant fone : std_logic_vector(31 downto 0):=X"3F800000";
    signal b_index : std_logic_vector(31 downto 0):=X"00000000";
    signal a_index : std_logic_vector(31 downto 0):=X"00000000";
    signal i_start_prev : std_logic:='0';
    signal i_start_pres : std_logic:='0';
    signal i_start_rising : std_logic:='0';
    signal indicator_complete : std_logic:='0';
    type t_state is (STATE_IDLE,STATE_SET1, STATE_WAIT1,STATE_SET2, STATE_WAIT2, STATE_WAIT3, STATE_DONE);
    signal state : t_state := STATE_IDLE;
    signal state_num : integer range 0 to 15 := 0;
    constant fpu_add : std_logic_vector(2 downto 0):="000";
    constant fpu_sub : std_logic_vector(2 downto 0):="001";
    constant fpu_mul : std_logic_vector(2 downto 0):="010";
    constant fpu_rmode : std_logic_vector(1 downto 0):="00";

    component fpu port (
        clk_i           : in std_logic;
        opa_i           : in std_logic_vector(31 downto 0);   
        opb_i           : in std_logic_vector(31 downto 0);
        fpu_op_i                : in std_logic_vector(2 downto 0);
        rmode_i                 : in std_logic_vector(1 downto 0);  
        output_o        : out std_logic_vector(31 downto 0);
                ine_o                   : out std_logic;
        overflow_o      : out std_logic;
        underflow_o     : out std_logic;
        div_zero_o      : out std_logic;
        inf_o                   : out std_logic;
        zero_o                  : out std_logic;
        qnan_o                  : out std_logic;
        snan_o                  : out std_logic;
        start_i                 : in  std_logic;
        ready_o                 : out std_logic 
        ); end component;

    signal opa_i_1, opb_i_1 : std_logic_vector(31 downto 0);
    signal fpu_op_i_1           : std_logic_vector(2 downto 0);
    signal output_o_1 : std_logic_vector(31 downto 0);
    signal start_i_1 : std_logic:='0'; 
    signal ready_o_1 : std_logic ; 
    signal ine_o_1, overflow_o_1, underflow_o_1, div_zero_o_1, inf_o_1, zero_o_1, qnan_o_1, snan_o_1: std_logic;
    signal comp_1: std_logic:='0';

    signal opa_i_2, opb_i_2 : std_logic_vector(31 downto 0);
    signal fpu_op_i_2           : std_logic_vector(2 downto 0);
    signal output_o_2 : std_logic_vector(31 downto 0);
    signal start_i_2 : std_logic:='0'; 
    signal ready_o_2 : std_logic ; 
    signal ine_o_2, overflow_o_2, underflow_o_2, div_zero_o_2, inf_o_2, zero_o_2, qnan_o_2, snan_o_2: std_logic;
    signal comp_2: std_logic:='0';

begin
    -- instantiate fpu
    i_fpu_1: fpu port map (
        clk_i => i_clk,
        opa_i => opa_i_1,
        opb_i => opb_i_1,
        fpu_op_i =>  fpu_op_i_1,
        rmode_i => fpu_rmode,        
        output_o => output_o_1,  
        ine_o => ine_o_1,
        overflow_o => overflow_o_1,
        underflow_o => underflow_o_1,                
        div_zero_o => div_zero_o_1,
        inf_o => inf_o_1,
        zero_o => zero_o_1,          
        qnan_o => qnan_o_1,          
        snan_o => snan_o_1,
        start_i => start_i_1,
        ready_o => ready_o_1
    );
    i_fpu_2: fpu port map (
        clk_i => i_clk,
        opa_i => opa_i_2,
        opb_i => opb_i_2,
        fpu_op_i =>  fpu_op_i_2,
        rmode_i => fpu_rmode,        
        output_o => output_o_2,  
        ine_o => ine_o_2,
        overflow_o => overflow_o_2,
        underflow_o => underflow_o_2,                
        div_zero_o => div_zero_o_2,
        inf_o => inf_o_2,
        zero_o => zero_o_2,          
        qnan_o => qnan_o_2,          
        snan_o => snan_o_2,
        start_i => start_i_2,
        ready_o => ready_o_2
    );

    i_start_pres<=i_start;
    i_start_rising<='1' when (i_start_prev='0' and i_start_pres='1') else '0';
    o_complete<=indicator_complete;

    process (i_clk,i_start_rising,state) begin
        if i_clk'event and i_clk='1' then
            i_start_prev<=i_start;
            if i_start_rising='1' and state=STATE_IDLE then
                -- b_0+b_1
                opa_i_1<=i_b0;
                opb_i_1<=i_b1;
                fpu_op_i_1<=fpu_add;
                start_i_1<='1';
                comp_1<='0';

                -- a_1*y_{n-1}
                opa_i_2<=i_a1;
                opb_i_2<=ynm1;
                fpu_op_i_2<=fpu_mul;
                start_i_2<='1';
                comp_2<='0';

                xn<=i_1bit;

                state<=STATE_SET1;
                state_num<=1;
            end if;
            if state=STATE_SET1 then
                start_i_1<='0';
                start_i_2<='0';
                state<=STATE_WAIT1;
                state_num<=2;
            end if;
            if state=STATE_WAIT1 then
                if ready_o_1='1' then
                    comp_1<='1';
                    b_index<=output_o_1;
                end if;
                if ready_o_2='1' then
                    comp_2<='1';
                    a_index<=output_o_2;
                end if;
                if comp_1='1' and comp_2='1' then
                    if xnm1='0' and xn='0' then
                        opa_i_1<=fzero;
                    elsif xnm1='0' and xn='1' then
                        opa_i_1<=i_b0;
                    elsif xnm1='1' and xn='0' then
                        opa_i_1<=i_b1;
                    elsif xnm1='1' and xn='1' then
                        opa_i_1<=b_index;
                    end if;
                    opb_i_1<=a_index;
                    fpu_op_i_1<=fpu_sub;
                    start_i_1<='1';
                    comp_1<='0';
                    state<=STATE_SET2;
                    state_num<=3;
                    end if;
            end if;
            if state=STATE_SET2 then
                start_i_1<='0';
                state<=STATE_WAIT2;
                state_num<=4;
            end if;
            if state=STATE_WAIT2 then
                if ready_o_1='1' then
                    comp_1<='1';
                    yn<=output_o_1;
                    o_32bit<=output_o_1;
                    state<=STATE_DONE;
                    state_num<=5;
                    indicator_complete<='1';
                    end if;
            end if;
            if state=STATE_DONE then
                indicator_complete<='0';
                xnm1<=xn;
                ynm1<=yn;
                state<=STATE_IDLE;
                state_num<=0;
            end if;
        end if;
    end process;    
end;

FPUへの代入に行数がかかっているので、長大に見えるけど、まぁまぁシンプルにできた。またいきなり答えを書いてしまったけど、STATEを変えながら動かしていくってのが、コンピュータ言語使いにとっては安定して作れる。まず、どういうSTATEを作るべきかを考えて、一旦STATEを無条件で遷移させるコードにして(これで、1クロックごとにSTATEを移動していく)、その後各STATEで何を待ってどう処理するかを書いていくだけなので、考え方が簡単。

 

そして、テストベンチ。

 

iir1_try1_tb.vhd

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
 
entity iir1_try1_tb is
end iir1_try1_tb;
 
architecture sim of iir1_try1_tb is
    component iir1_try1 is port (
        i_1bit : in std_logic;
        o_32bit : out std_logic_vector(31 downto 0);
        i_start : in std_logic;
        o_complete : out std_logic;
        i_a0 : in std_logic_vector(31 downto 0);
        i_a1 : in std_logic_vector(31 downto 0);
        i_b0 : in std_logic_vector(31 downto 0);
        i_b1 : in std_logic_vector(31 downto 0);
        i_clk : in std_logic
    ); end component;
    signal clksys : std_logic:='0';
    constant STEPSYS : TIME := 6.25 ns;

    signal i_1bit_tb : std_logic:='0';
    signal o_32bit_tb : std_logic_vector(31 downto 0);
    signal i_start_tb : std_logic:='0';
    signal o_complete_tb : std_logic;
    signal i_a0_tb : std_logic_vector(31 downto 0):=X"3F800000";
    signal i_a1_tb : std_logic_vector(31 downto 0):=X"BF7814CD";
    signal i_b0_tb : std_logic_vector(31 downto 0):=X"3C7D6652";
    signal i_b1_tb : std_logic_vector(31 downto 0):=X"3C7D6652";
    signal clk_counter : integer range 0 to 255:=0;
    signal clk_counter_bb : integer range 0 to 65535:=0;

begin
    iir1_try1_inst : component iir1_try1 port map(
        i_1bit=>i_1bit_tb,
        o_32bit=>o_32bit_tb,
        i_start=>i_start_tb,
        o_complete=>o_complete_tb,
        i_a0=>i_a0_tb,
        i_a1=>i_a1_tb,
        i_b0=>i_b0_tb,
        i_b1=>i_b1_tb,
        i_clk=>clksys
    );
    clksys <= not(clksys) after STEPSYS/2;
    process(clksys)begin
        if clksys'event and clksys='1' then
            if clk_counter=31 then
                clk_counter<=0;
                i_start_tb<='1';
            else
                clk_counter<=clk_counter+1;
                i_start_tb<='0';
            end if;
        end if;
    end process;
    process(clksys)begin
        if clksys'event and clksys='1' then
            clk_counter_bb<=clk_counter_bb+1;
            if clk_counter_bb=8000 then
                clk_counter_bb<=0;
                if i_1bit_tb='0' then
                    i_1bit_tb<='1';
                else
                    i_1bit_tb<='0';
                end if;
            end if;
        end if;
    end process;
end;

サンプリング周波数を5MHzにするためにシステムクロックを160MHzにして32クロックでサンプリングしています。フィルタ係数はPythonでフィルタ(5)で使ったbutterによる係数にしています。

 

 Makefile

VHDLC=/usr/bin/ghdl
SRC=iir1_try1.vhd fpu_v19/fpupack.vhd fpu_v19/pre_norm_addsub.vhd fpu_v19/addsub_28.vhd fpu_v19/post_norm_addsub.vhd fpu_v19/pre_norm_mul.vhd fpu_v19/mul_24.vhd fpu_v19/serial_mul.vhd fpu_v19/post_norm_mul.vhd fpu_v19/pre_norm_div.vhd fpu_v19/serial_div.vhd fpu_v19/post_norm_div.vhd fpu_v19/pre_norm_sqrt.vhd fpu_v19/sqrt.vhd fpu_v19/post_norm_sqrt.vhd fpu_v19/comppack.vhd fpu_v19/fpu.vhd
TBSRC=iir1_try1_tb.vhd
VCD=iir1_try1.vcd
SIMRTL=iir1_try1_tb

all :
        @make comp
        @make tb
        @make tb_e
        @make vcd

comp :
        $(VHDLC) -a --ieee=synopsys -fexplicit $(SRC)

tb :
        $(VHDLC) -a --ieee=synopsys -fexplicit $(TBSRC)

tb_e :
        $(VHDLC) -e --ieee=synopsys -fexplicit $(SIMRTL)

vcd :
        $(VHDLC) -r --ieee=synopsys -fexplicit $(SIMRTL) --vcd=$(VCD)

これがまぁなんとも、いとも簡単にそれらしい結果が出るんだわ。

フィルタがかかった波形が得られている。

STATEの遷移も期待通り。

が、なんかちょっとpythonの結果と違っている気がする。、、、あ、シンボルレートがちがうわ、、、

Pythonでフィルタ(5)のコード(一番下のやつ)をもってきて

f_symbol=10e3

f_symbol=20e3

にして、シンボルもLスタートに変更して実行したものと比較してみる。

ktkr--!完璧やん。初老のおじさんにこんなことができるようになるなんて、なんて世の中だ。世間の善意(web情報)とテクノロジーの進歩に感謝。

 

今日は浜松にMJ来てるんやね。