【ZYBO】オーディオ・コーデックSSM2603を使ってみる(I2Sコーディング~シミュレーション)

前回はI2Sについてまとめました。今回はその内容をVHDLで記述した後、シミュレーションを行います。

【ZYBO】オーディオ・コーデックSSM2603を使ってみる(I2Sについて)

目標

前回の記事に載せたタイミングチャートを目標とします。サンプリング周波数は48kHzです。
i2s timing chart

ソースコード

パッケージにてMCLK周波数とサンプリング周波数、またデータビット数を指定可能としました。リセットが解除されると動作し続ける仕様です。

  • ssm2603_ctrl_pkg.vhd
  • package ssm2603_ctrl_pkg is
        constant c_mclk_period      : integer := 12288 * 1000;  -- システムクロック周波数
        constant c_sampling_rate    : integer := 48 * 1000;     -- サンプリング周波数
        constant c_word_length      : integer := 24;            -- データビット数
        constant c_lrc_period       : integer := (c_mclk_period / c_sampling_rate) / 2; -- mclkを元にしたLRCLOCK周期
        constant c_bclk_period      : integer := (c_lrc_period / 32) / 2;               -- mclkを元にしたBCLK周期
    end package;
  • i2s_ctrl.vhd
  • library ieee;
    use ieee.std_logic_1164.all;
    use ieee.std_logic_unsigned.all;
    use ieee.std_logic_arith.all;
    
    use work.ssm2603_ctrl_pkg.all;
    
    entity i2s_ctrl is
        port   (
            -- sysreset/clock
            srstn_i         : in    std_logic;
            clk_i           : in    std_logic;
            -- i2s bclk
            bclk_o          : out   std_logic;
            pblrc_o         : out   std_logic;
            pbdat_o         : out   std_logic;
            reclrc_o        : out   std_logic;
            recdat_i        : in    std_logic;
            -- digital data
            data_i          : in    std_logic_vector (c_word_length-1 downto 0);
            data_o          : out   std_logic_vector (c_word_length-1 downto 0)
        );
    end i2s_ctrl;
    
    architecture rtl of i2s_ctrl is
    
    type type_i2s_st is (pre_padding, data, post_padding);
    signal i2s_st    : type_i2s_st;
    
    -- bclk
    signal bclk                 :   std_logic_vector (1 downto 0);
    signal bclk_count           :   std_logic_vector (7 downto 0);
    signal bclk_period          :   std_logic_vector (7 downto 0);
    -- pblrc/reclrc
    signal bclk_fall            :   std_logic;
    signal valid_data_count     :   std_logic_vector (7 downto 0);
    signal valid_data_count_max :   std_logic_vector (7 downto 0);
    signal lsb                  :   std_logic;
    signal post_pad_count       :   std_logic_vector (7 downto 0);
    signal post_pad_count_max   :   std_logic_vector (7 downto 0);
    signal last                 :   std_logic;
    signal lrc_count            :   std_logic_vector (15 downto 0);
    signal lrc_count_max        :   std_logic_vector (15 downto 0);
    signal lrc                  :   std_logic;
    -- pbdat
    signal pb_data_shift        :   std_logic_vector (c_word_length-1 downto 0);
    -- recdat
    signal bclk_rise            :   std_logic;
    signal rec_data_shift       :   std_logic_vector (c_word_length-1 downto 0);
    signal rec_data_latch       :   std_logic_vector (c_word_length-1 downto 0);
    
    begin
    
    --------------------------------------------------------
    --  bclk
    --------------------------------------------------------
    
    -- bclk周期
    bclk_period <= conv_std_logic_vector (c_bclk_period, 8);
    
    process (clk_i) begin
        if (rising_edge(clk_i)) then
            if (srstn_i = '0') then
                bclk_count  <= (others => '0');
            else
                if (bclk_count = bclk_period-1) then
                    bclk_count  <= (others => '0');
                else
                    bclk_count  <= bclk_count + 1;
                end if;
            end if;
        end if;
    end process;
    
    process (clk_i) begin
        if (rising_edge(clk_i)) then
            if (srstn_i = '0') then
                bclk    <= (others => '0');
            else
                if (bclk_count = bclk_period-1) then
                    bclk(0) <= not bclk(0);
                end if;
                
                bclk(1) <= bclk(0);
            end if;
        end if;
    end process;
    
    bclk_o      <= bclk(0);
    bclk_rise   <= bclk(0) and (not bclk(1));
    bclk_fall   <= (not bclk(0)) and bclk(1);
    
    --------------------------------------------------------
    --  pblrc/reclrc
    --------------------------------------------------------
    -- pb/rec信号制御用ステート・マシン
    process (clk_i) begin
        if (rising_edge(clk_i)) then
            if (srstn_i = '0') then
                i2s_st   <= pre_padding;
            else
                -- ステート・マシンはbclk立ち下がりのみで動作
                if (bclk_fall = '1') then
                    case i2s_st is
                        when pre_padding    =>  -- 初回パディング無視
                            i2s_st   <= data;
    
                        when data           =>  -- WORD数分のbclk立ち下がりを待つ
                            if (lsb = '1') then     -- 最終ビットで次ステートへ
                                i2s_st   <= post_padding;
                            else
                                i2s_st   <= data;
                            end if;
    
                        when post_padding   =>  -- lrc信号は常に32bit分のhigh/lowを繰り返すので(32-WORD-1)分のパディング
                            if (last = '1') then    -- (32-WORD-1)回目のパディングで初めに戻る
                                i2s_st   <= pre_padding;
                            else
                                i2s_st   <= post_padding;
                            end if;
    
                        when others         =>
                            i2s_st   <= pre_padding;
    
                    end case;
                else
                    i2s_st   <= i2s_st;
                end if;
            end if;
        end if;
    end process;
    
    -- 有効データ数(=指定WORD数)カウント
    valid_data_count_max <= conv_std_logic_vector(c_word_length, 8);        -- カウント最大値
    lsb <= '1' when (valid_data_count = valid_data_count_max-1) else '0';   -- LSB(=最終ビット)時にアサート
    
    -- 有効データ数カウント
    process (clk_i) begin
        if (rising_edge(clk_i)) then
            if (srstn_i = '0') then
                valid_data_count <= (others => '0');
            else
                if (i2s_st = data) then
                    if (bclk_fall = '1') then
                        if (valid_data_count = valid_data_count_max-1) then
                            valid_data_count <= (others => '0');
                        else
                            valid_data_count <= valid_data_count + 1;
                        end if;
                    end if;
                else
                    valid_data_count    <= (others => '0');
                end if;
            end if;
        end if;
    end process;
    
    -- 有効データ終了後のパディングカウント
    post_pad_count_max  <= conv_std_logic_vector(32-c_word_length-1, 8);    -- 32bit-指定WORD-初回パディング
    last    <= '1' when (post_pad_count = post_pad_count_max-1) else '0';   -- パディング最終ビットでアサート
    
    process (clk_i) begin
        if (rising_edge(clk_i)) then
            if (srstn_i = '0') then
                post_pad_count <= (others => '0');
            else
                if (i2s_st = post_padding) then
                    if (bclk_fall = '1') then
                        if (post_pad_count = post_pad_count_max-1) then
                            post_pad_count <= (others => '0');
                        else
                            post_pad_count <= post_pad_count + 1;
                        end if;
                    end if;
                else
                    post_pad_count    <= (others => '0');
                end if;
            end if;
        end if;
    end process;
    
    -- lrcカウント
    lrc_count_max <= conv_std_logic_vector(c_lrc_period, 16);
    
    process (clk_i) begin
        if (rising_edge(clk_i)) then
            if (srstn_i = '0') then
                lrc_count <= (others => '0');
            else
                if (lrc_count = lrc_count_max-1) then
                    lrc_count <= (others => '0');
                else
                    lrc_count <= lrc_count + 1;
                end if;
            end if;
        end if;
    end process;
    
    process (clk_i) begin
        if (rising_edge(clk_i)) then
            if (srstn_i = '0') then
                lrc  <= '0';
            else
                if (lrc_count = lrc_count_max-1) then
                    lrc  <= not lrc;
                end if;
            end if;
        end if;
    end process;
    
    -- pbdat
    process (clk_i) begin
        if (rising_edge(clk_i)) then
            if (srstn_i = '0') then
                pb_data_shift  <= (others => '0');
            else
                if (bclk_fall = '1') then   -- bclk立ち下がりでデータ変化
                    if (i2s_st = pre_padding) then  -- 初回パディング時に入力データラッチ
                        pb_data_shift  <= data_i;
                    else                            -- 以後右シフト
                        pb_data_shift  <= pb_data_shift(c_word_length-2 downto 0) & '0';
                    end if;
                end if;
            end if;
        end if;
    end process;
    
    -- recdat
    process (clk_i) begin
        if (rising_edge(clk_i)) then
            if (srstn_i = '0') then
                rec_data_shift  <= (others => '0');
            else    -- bclk立ち上がりで右シフトして最下位ビットにデータ取得
                if ((bclk_rise = '1') and (i2s_st = data)) then
                    rec_data_shift  <=  rec_data_shift(c_word_length-2 downto 0) & recdat_i;
                end if;
            end if;
        end if;
    end process;
    
    process (clk_i) begin
        if (rising_edge(clk_i)) then
            if (srstn_i = '0') then
                rec_data_latch  <= (others => '0');
            else -- 32回目のbclk立ち下がりでパラレルデータ出力
                if ((bclk_fall = '1') and (i2s_st = post_padding) and (last = '1')) then
                    rec_data_latch <= rec_data_shift(c_word_length-2 downto 0) & recdat_i;
                end if;
            end if;
        end if;
    end process;
    
    pblrc_o     <= lrc;
    reclrc_o    <= lrc;
    pbdat_o     <= pb_data_shift(c_word_length-1);
    data_o      <= rec_data_latch;
    
    end rtl;

    シミュレーション

    流れとブロック図

    シミュレーションの流れです。

    1. テストデータを生成。i2s_ctrlのdata_iへ入力。
    2. 入力されたパラレルデータはpbdat_oからシリアル化されて出力される。
    3. pbdat_oとrecdat_iを直結して折り返す。
    4. data_oから入力テストデータが出力される。

    i2s block

    テストベンチ

  • testbench_i2s.vhd
  • library ieee;
    use ieee.std_logic_1164.all;
    use ieee.std_logic_unsigned.all;
    
    library work;
    use work.ssm2603_ctrl_pkg.all;
    
    entity testbench is
    end testbench;
    
    architecture rtl of testbench is
    
    component test_gen is
        port   (
            -- sysreset/clock
            srstn_i     : in    std_logic;
            clk_i       : in    std_logic;
            -- test gen
            test_o      : out   std_logic_vector (c_word_length-1 downto 0)
        );
    end component;
    
    component i2s_ctrl is
        port   (
            -- sysreset/clock
            srstn_i         : in    std_logic;
            clk_i           : in    std_logic;
            -- i2s bclk
            bclk_o          : out   std_logic;
            pblrc_o         : out   std_logic;
            pbdat_o         : out   std_logic;
            reclrc_o        : out   std_logic;
            recdat_i        : in    std_logic;
            -- digital data
            data_i          : in    std_logic_vector (c_word_length-1 downto 0);
            data_o          : out   std_logic_vector (c_word_length-1 downto 0)
        );
    end component;
    
    signal srstn_i  :   std_logic;
    signal clk_i    :   std_logic;
    signal bclk_o   :   std_logic;
    signal pblrc_o  :   std_logic;
    signal pbdat_o  :   std_logic;
    signal reclrc_o :   std_logic;
    signal recdat_i :   std_logic;
    signal test     :   std_logic_vector (c_word_length-1 downto 0);
    signal data_o   :   std_logic_vector (c_word_length-1 downto 0);
    
    begin
    
    process begin
        srstn_i <= '0';
        wait for 1 us;
        srstn_i <= '1';
        wait;
    end process;
    
    process begin
        clk_i   <= '0';
        wait for 40690 ps;
        clk_i   <= '1';
        wait for 40690 ps;
    end process;
    
    u_test_gen  :   test_gen
        port map   (
            -- sysreset/clock
            srstn_i     => srstn_i,     --: in    std_logic;
            clk_i       => clk_i,       --: in    std_logic;
            -- test gen
            test_o      => test         --: out   std_logic_vector (c_word_length-1 downto 0)
        );
    
    u_i2s_ctrl  :   i2s_ctrl
        port map   (
            -- sysreset/clock
            srstn_i         => srstn_i         ,--: in    std_logic;
            clk_i           => clk_i           ,--: in    std_logic;
            -- i2s bclk
            bclk_o          => bclk_o          ,--: out   std_logic;
            pblrc_o         => pblrc_o         ,--: out   std_logic;
            pbdat_o         => pbdat_o         ,--: out   std_logic;
            reclrc_o        => reclrc_o        ,--: out   std_logic;
            recdat_i        => recdat_i        ,--: in    std_logic;
            -- digital data
            data_i          => test           ,--: in    std_logic_vector (c_word_length-1 downto 0);
            data_o          => data_o          --: out   std_logic_vector (c_word_length-1 downto 0)
        );
    
    recdat_i    <= pbdat_o;
    
    end rtl;
  • test_gen.vhd
  • library ieee;
    use ieee.std_logic_1164.all;
    use ieee.std_logic_unsigned.all;
    use ieee.std_logic_arith.all;
    
    library work;
    use work.ssm2603_ctrl_pkg.all;
    
    entity test_gen is
        port   (
            -- sysreset/clock
            srstn_i     : in    std_logic;
            clk_i       : in    std_logic;
            -- test gen
            test_o      : out   std_logic_vector (c_word_length-1 downto 0)
        );
    end test_gen;
    
    architecture rtl of test_gen is
    
    signal increment        :   std_logic_vector (c_word_length-1 downto 0);
    
    begin
    
    process (clk_i) begin
        if (rising_edge(clk_i)) then
            if (srstn_i = '0') then
                increment   <= (others => '0');
            else
                increment   <= increment + 1;
            end if;
        end if;
    end process;
    
    test_o  <= increment;
    
    end rtl;

    シミュレーション波形

    まずはテストベンチトップでの波形です。test(=data_i)をclk_iで毎クロックインクリメントしており、どの入力値を取り込んでいるのか分からず、結果としてdata_oの出力値が正しいかが不明です。
    i2s sim top

    初めのdata_iをラッチするタイミングは「初めのbclk立ち下がり」です。i2s_ctrlの内部にて初回のラッチ値を確認すると、0x000004。トップ波形にて初めに出力されるdata_oと同じです。i2s sim first latch

    更に2回目以降のデータラッチタイミングは「pblrcが切り替わった直後のbclk立ち下がり」。同様に確認すると、0x000084なので問題なさそうです。
    i2s sim first latch

    次回はZYBOに実装するためのトップファイルやXilinx IPの作成、またI2C経由でのSSM2603レジスタ設定をPSから行います。

    シェアする

    • このエントリーをはてなブックマークに追加

    フォローする