FC2カウンター FPGAの部屋 2009年12月23日
fc2ブログ

FPGAやCPLDの話題やFPGA用のツールの話題などです。 マニアックです。 日記も書きます。

FPGAの部屋

FPGAの部屋の有用と思われるコンテンツのまとめサイトを作りました。Xilinx ISEの初心者の方には、FPGAリテラシーおよびチュートリアルのページをお勧めいたします。

SCCBインターフェース回路の説明3(One_Transaction_SCCB.vhd)

今度はOne_Transaction_SCCB.vhdを説明する。これは、SCCB_Reg_Controller.vhd から渡されたアドレス(SCCB_address)とデータ(SCCB_data)を使って、渡されたアドレスのSCCBレジスタにデータを書き込むモジュールだ。entityの記述を下に示す。

entity One_Transaction_SCCB is
    port(
        clk : in std_logic; -- クロック
        reset : in std_logic; -- リセット
        SCCB_address : in std_logic_vector(7 downto 0); -- SCCBレジスタのアドレス
        SCCB_data : in std_logic_vector(7 downto 0); -- SCCBレジスタのデータ
        op_enable : in std_logic; -- 動作イネーブル(200KHz, 5usec間隔)
        start_pulse : in std_logic; -- スタートパルス(1クロック幅)
        end_pulse : out std_logic; -- エンドパルス(1クロック幅)
        SCL : out std_logic;
        SDA : out std_logic
    );
end One_Transaction_SCCB;


start_pulseを入れて、end_pulseが出力されると渡されたアドレスのSCCBレジスタにデータを書き込む操作が終了となる。op_enableはfreqdiv.vhdから出力されたop_enaを接続する。SCLとSDAはCMOSカメラへ。
最初にメインのステートマシン。構造を下に示す。
SCCB_block_fig_1_091216.png

VHDLコードを下に示す。

    -- Main State Machine
    process(clk) begin
        if clk'event and clk='1' then
            if reset='1' then
                cs_main <= idle_main;
            else
                case cs_main is
                    when idle_main =>
                        if start_pulse='1' then -- スタートパルスが来たらスタート
                            cs_main <= start_state;
                        end if;
                    when start_state =>
                        if state_counter=17 and op_enable='1' then -- START ステートの最後で5us のイネーブルの時に遷移
                            cs_main <= ID_Address;
                        end if;
                    when ID_Address =>
                        if state_counter=17 and op_enable='1' then -- ID_Address ステートの最後で5us のイネーブルの時に遷移
                            cs_main <= SCCB_Reg_Addr;
                        end if;
                    when SCCB_Reg_Addr =>
                        if state_counter=17 and op_enable='1' then -- SCCB_Reg_Addr ステートの最後で5us のイネーブルの時に遷移
                            cs_main <= Write_Data;
                        end if;
                    when Write_Data =>
                        if state_counter=17 and op_enable='1' then -- Write_Data ステートの最後で5us のイネーブルの時に遷移
                            cs_main <= stop_state;
                        end if;
                    when stop_state =>
                        if state_counter=17 and op_enable='1' then -- stop_state ステートの最後で5us のイネーブルの時に遷移
                            cs_main <= idle_main;
                        end if;
                end case;
            end if;
        end if;
    end process;


state_counterが17でop_enaが1のときに遷移する。state_counter は0~17までカウントするカウンタで、op_enaが1の時にカウントアップする。
次にデータパスのブロック図を再度示す。
SCCB_block_fig_3_091216.png

SDAはSCLに比べて、必ず遅れるようにFFを2段入れた。SDAの最後のトラーステートバッファのイネーブルは必ずFFの出力とした方が良い。それは、イネーブルが組み合わせ回路だと、どの時点でひげが出て、思わぬ時にONしてしまうかわからないためだ。
下にconstant値と18ビット、9ビットのシフトレジスタのVHDLコードを示す。

constant START_PATTERN_SCL : std_logic_vector :=        "111111111111111111";
constant IDA_SCCBR_WD_PATTERN_SCL : std_logic_vector :=    "010101010101010101";
constant STOP_PATTERN_SCL : std_logic_vector :=            "011111111111111111";
constant START_PATTERN_SDA : std_logic_vector :=        "111111110";
constant ID_ADDRESS_PATTERN_SDA : std_logic_vector :=    "010000100";
constant STOP_PATTERN_SDA : std_logic_vector :=            "011111111";


    -- SLC 用18ビット・シフトレジスタ
    process(clk) begin
        if clk'event and clk='1' then
            if reset='1' then
                SCL_shift_reg <= (others => '1');
            else
                case cs_main is
                    when idle_main =>
                        if start_pulse='1' then -- スタートパルスが来たらスタート
                            SCL_shift_reg <= START_PATTERN_SCL;
                        end if;
                    when start_state =>
                        if op_enable='1' then
                            if state_counter=17 then -- START ステートの最後で5us のイネーブルの時に遷移 
                                SCL_shift_reg <= IDA_SCCBR_WD_PATTERN_SCL;
                            else
                                SCL_shift_reg <= SCL_shift_reg(16 downto 0) & '1';
                            end if;
                        end if;
                    when ID_Address =>
                        if op_enable='1' then
                            if state_counter=17 then
                                SCL_shift_reg <= IDA_SCCBR_WD_PATTERN_SCL;
                            else
                                SCL_shift_reg <= SCL_shift_reg(16 downto 0) & '1';
                            end if;
                        end if;
                    when SCCB_Reg_Addr =>
                        if op_enable='1' then
                            if state_counter=17 then
                                SCL_shift_reg <= IDA_SCCBR_WD_PATTERN_SCL;
                            else
                                SCL_shift_reg <= SCL_shift_reg(16 downto 0) & '1';
                            end if;
                        end if;
                    when Write_Data =>
                        if op_enable='1' then
                            if state_counter=17 then
                                SCL_shift_reg <= STOP_PATTERN_SCL;
                            else
                                SCL_shift_reg <= SCL_shift_reg(16 downto 0) & '1';
                            end if;
                        end if;
                    when stop_state =>
                        if op_enable='1' then
                            SCL_shift_reg <=  SCL_shift_reg(16 downto 0) & '1';
                        end if;
                end case;
            end if;
        end if;
    end process;
    
    -- SDA 用9ビット・シフトレジスタ
    process(clk) begin
        if clk'event and clk='1' then
            if reset='1' then
                SDA_shift_reg <= (others => '1');
            else
                case cs_main is
                    when idle_main =>
                        if start_pulse='1' then -- スタートパルスが来たらスタート
                            SDA_shift_reg <= START_PATTERN_SDA;
                        end if;
                    when start_state =>
                        if op_enable='1' and state_counter(0)='1' then -- 2回に1回遷移
                            if state_counter=17 then -- START ステートの最後で5us のイネーブルの時に遷移 
                                SDA_shift_reg <= ID_ADDRESS_PATTERN_SDA;
                            else
                                SDA_shift_reg <= SDA_shift_reg(7 downto 0) & '1';
                            end if;
                        end if;
                    when ID_Address =>
                        if op_enable='1' and state_counter(0)='1' then -- 2回に1回遷移
                            if state_counter=17 then
                                SDA_shift_reg <= SCCB_address & '1';
                            else
                                SDA_shift_reg <= SDA_shift_reg(7 downto 0) & '1';
                            end if;
                        end if;
                    when SCCB_Reg_Addr =>
                        if op_enable='1' and state_counter(0)='1' then -- 2回に1回遷移
                            if state_counter=17 then
                                SDA_shift_reg <= SCCB_data & '1';
                            else
                                SDA_shift_reg <= SDA_shift_reg(7 downto 0) & '1';
                            end if;
                        end if;
                    when Write_Data =>
                        if op_enable='1' and state_counter(0)='1' then -- 2回に1回遷移
                            if state_counter=17 then
                                SDA_shift_reg <= STOP_PATTERN_SDA;
                            else
                                SDA_shift_reg <= SDA_shift_reg(7 downto 0) & '1';
                            end if;
                        end if;
                    when stop_state =>
                        if op_enable='1' and state_counter(0)='1' then -- 2回に1回遷移
                            SDA_shift_reg <=  SDA_shift_reg(7 downto 0) & '1';
                        end if;
                end case;
            end if;
        end if;
    end process;


これで大体説明できたと思う。これらのファイルを合わせて、テストベンチを書くと下のようなシミュレーション波形が得られる。
SCCB_simulation_091221.png
  1. 2009年12月23日 21:27 |
  2. 画像処理
  3. | トラックバック:0
  4. | コメント:0

SCCBインターフェース回路の説明2(freqdiv.vhd、SCCB_reg_values_ROM.vhd)

次にまずはfreqdiv.vhd から。freqdiv.vhd は25MHzから200KHzに分周する回路だ。ただ単に分周するのではなく、200KHzに相当する間隔、つまり5usec ごとに25MHzクロック1クロック分の幅 (40nsec) 分のイネーブル信号を出力する。それがop_ena だ。なんでこんなことをするのかと言うと、分周してクロック周波数を200KHz とすると、25MHz で動作する回路とのインターフェースが面倒だからだ。ツール、つまりISEでも自動では面倒を見てくれない。お互いの信号のやりとりは非同期としての扱いになってしまう。25MHzクロック1クロック分の幅 (40nsec) 分のイネーブル信号だったら、回路動作は25MHzクロックでの動作となるので、ツールが自動的(制約を掛けておけば)に良いようにしてくれる。ということで、このような構成になっている。下にVHDLコードを示す。

-- Frequncy Divider 
-- 200KHz clock
-- マスタークロックを200KHzのop_enaに分周します。

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
use ieee.std_logic_arith.all;

entity FreqDiv is
    generic(
        divisor : integer := 125
    );
    port(
        clk, reset : in std_logic;
        op_ena : out std_logic
    );
end FreqDiv;

architecture RTL of FreqDiv is
signal lcnt : std_logic_vector(15 downto 0);
begin
    process(clk) begin -- Enable frequency is 200KHz
        if clk'event and clk='1' then
            if reset='1' then
                lcnt <= (others => '0');
            elsif lcnt = conv_std_logic_vector(divisor, 16)-1 then
                lcnt <= (others => '0');
            else
                lcnt <= lcnt + 1;
            end if;
        end if;
    end process;
    
    process(clk) begin
        if clk'event and clk='1' then
            if lcnt = conv_std_logic_vector(divisor, 16)-1 then
                op_ena <= '1';
            else
                op_ena <= '0';
            end if;
        end if;
    end process;
end RTL;


次は、SCCB_reg_values_ROM.vhd だが、これは、”VHDLでのブロックRAMや分散RAMの初期化(16進数で書かれた外部データファイル)”で説明した大体そのままなのだが、RAMではなくROMになっている。下にVHDLコードを示す。

-- SCCB_reg_values_ROM.vhd
-- SCCBプロトコル・コントローラを実装してSCCBレジスタに書き込む値をセーブしてある ROM。書き込みデータは SCCB_reg_values.data にセーブしておく。
-- SCCB_reg_values.data のフォーマット"1280" 最初の2キャラクタが16進形式のアドレス、次の2キャラクタが16進形式のデータ。例は12番地に80を書く
-- SCCB_reg_values_ROM.data には必ず256行のデータを用意しておく。アドレスがFFの場合はレジスタ・アクセスはそこで終了する。

library IEEE, STD;
use IEEE.std_logic_1164.all;
use STD.textio.all;
use IEEE.std_logic_textio.all;
use IEEE.std_logic_unsigned.all;
use IEEE.std_logic_arith.all;
-- pragma translate_off
library UNISIM;
use UNISIM.VCOMPONENTS.ALL;
-- pragma translate_on

entity SCCB_reg_values_ROM is
    port(
        clk : in std_logic; -- Clock
        address : in std_logic_vector(7 downto 0);
        dout : out std_logic_vector(15 downto 0)
    );
end SCCB_reg_values_ROM;

architecture RTL of SCCB_reg_values_ROM is
type RamType is array(0 to 255) of std_logic_vector(15 downto 0);
impure function InitRamFromFile (RamFileName : in string) return RamType is
    FILE RamFile : text is in RamFileName;
    variable RamFileLine : line;
    variable RAM : RamType;
begin
    for I in RamType'range loop
        readline (RamFile, RamFileLine);
        hread (RamFileLine, RAM(I));
    end loop;
    return RAM;
end function;
signal RAM : RamType := InitRamFromFile("SCCB_reg_values.data");
begin
    process(clk) begin
        if clk'event and clk='1' then
            dout <= RAM(conv_integer(address));
        end if;
    end process;
end RTL;


SCCB_reg_values.dataの一部を下に示す。

ACDF
FF00
0000

  1. 2009年12月23日 19:50 |
  2. 画像処理
  3. | トラックバック:0
  4. | コメント:0

SCCBインターフェース回路の説明1(SCCB_Reg_Controller.vhd)

SCCBインターフェース回路が完成した。VHDLソースを全部公開するか、説明を書きながら一部分公開するかで迷ったが、説明を書きながら一部分公開しようと思う。
最上位のトップ階層はSCCB_Reg_Controller.vhd である。このファイルのentity を下に示す。

-- SCCB_Reg_Controller.vhd
-- SCCBレジスタのコントローラ
-- SCCB_reg_values_ROM.data には必ず256行のデータを用意しておく。アドレスがFFの場合はレジスタ・アクセスはそこで終了する。
-- 1行のデータは16ビット幅となっていて、上位8ビットがアドレス、下位8ビットがデータとなる。
-- One_Transaction_SCCBにアドレスとデータを渡して、1クロック分のstart_pules をアサートしSCCBレジスタに書き込む
-- One_Transaction_SCCBが書き込みを終了したら、end_pulseをアサートされるので、終了を検出できる。
-- SCCB_reg_values_ROM.dataのアドレスにFFが書いてあるエントリまで、上記の動作を実行する。

library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;
use IEEE.std_logic_arith.all;
-- pragma translate_off
library UNISIM;
use UNISIM.VCOMPONENTS.ALL;
-- pragma translate_on

entity SCCB_Reg_Controller is
    port(
        clk : in std_logic; -- クロック
        reset  : in std_logic; -- リセット
        SCL : out std_logic; -- SCCBのクロック
        SDA : out std_logic -- SCCBのデータ
    );
end SCCB_Reg_Controller;


clkとresetを受け取って、SCL(SCCBクロック)とSDA(SCCBデータ)を出力する。
この下に、freqdiv.vhd、One_Transaction_SCCB.vhd、SCCB_reg_values_ROM.vhd の各モジュールがある。
SCCB_Reg_Controller.vhd には、SCCB_reg_values_ROM.vhd から読み込んだデータをSCCBレジスタを設定するモジュール(One_Transaction_SCCB.vhd) に渡すステートマシンがある。これはアドレス値0xFFを読むと停止する。下にそのステートマシンのVHDLコードを示す。

    -- SCCBのレジスタをセットするためのステートマシン
    process(clk) begin
        if clk'event and clk='1' then
            if reset='1' then
                cs_reg_set <= idle;
                start_pulse <= '0';
            else
                case cs_reg_set is
                    when idle =>
                        if ROM_data(15 downto 8) = x"FF" then -- レジスタセット終了
                            cs_reg_set <= end_state;
                            start_pulse <= '0';
                        else -- アドレスがFFでないので、セットするレジスタがある
                            cs_reg_set <= start_pulse_state;
                            start_pulse <= '1';
                        end if;
                    when start_pulse_state =>
                        cs_reg_set <= wait_one_trans_SCCB;
                        start_pulse <= '0';
                    when wait_one_trans_SCCB =>
                        if end_pulse='1' then -- SCCBレジスタへの書き込みが終了
                            cs_reg_set <= next_address;
                            start_pulse <= '0';
                        end if;
                    when next_address =>
                        if ROM_address=x"FF" then -- 終了
                            cs_reg_set <= end_state;
                            start_pulse <= '0';
                        else
                            cs_reg_set <= idle;
                            start_pulse <= '0';
                        end if;
                    when end_state =>
                        cs_reg_set <= end_state;
                        start_pulse <= '0';
                end case;
            end if;
        end if;
    end process;


このステートマシンでは、One_Transaction_SCCB.vhd にstart_pulse を出して1つのSCCBレジスタへのWriteを実行させる。One_Transaction_SCCB.vhd のSCCBレジスタへのWrite終了後に受け取るend_pulse を受けるとSCCB_reg_values_ROM.vhd のアドレスを1つ進める。そして、idleステートに戻り、アドレスに0xFFが出てきたらend_state に行ったきりになって終了となる。
  1. 2009年12月23日 08:28 |
  2. 画像処理
  3. | トラックバック:0
  4. | コメント:0