FPGAの部屋

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

FPGAの部屋の有用と思われるコンテンツのまとめサイトを作りました。ご利用ください。 http://marsee101.web.fc2.com/index.html

2009年12月

FPGAの部屋の有用と思われるコンテンツのまとめサイト”を更新しました。
主に”画像処理”を大幅に更新しました。

PERIOD制約を入力クロックに対して書いておくと、派生したクロックもその倍率によって制約がかかるというのは知っていたのだが、それぞれのクロックを使ったFF同士のデータ間の制約については確認したことがなかった。今回、ISE11の制約ガイドで確かめてみた。制約ガイドUG625(v11.4) 2009年12月2日の58ページの”関連するDCM/PLL/MMCM ドメイン(自動)”に書いてある。
その概要を下に引用する。

最もよくあるクロック回路では、入力クロックがDCM/PLL/MMCM に使用され、出力がデバイスの同期パスのクロックに
使用されています。この場合、DCM/PLL/MMCM への入力クロックにPERIOD 制約を定義することをお勧めします。
この入力クロックにPERIOD 制約を付けると、ザイリンクスツールは各DCM/PLL/MMCM 出力クロックに対して新しい
PERIOD 制約を自動的に作成し、出力クロックドメイン間のクロック関係を決定し、これらの同期ドメイン間のパスをす
べて解析します。


図も引用する。この図を見ると一目瞭然。
977b21bb.png


CLK1Xで動作するFFからCLK2Xで動作するFFのデータパスも、CLKINのPERIOD制約を与えておけば、解析されるはず。良かった。
さてそれでは実際の回路で検証してみることにする。
CMOSカメラからディスプレイ出力回路で、SRAMのWEは48MHzクロックで出力している。24MHzクロックのFFの出力を使用して48MHzのクロック動作のFFで受けている。24MHzクロックにPRIOD制約が掛けてあって、DCMのCLK2Xで48MHzを出力している。下がそのパスのセットアップ時間の解析結果。
01b6e48f.png


上のピンクの四角がソースクロックとディスティネーション・クロック。cam_pclkが24MHzでclk48が48MHzクロックだ。下のClock Path Skewを見ると、1.789 - 5.266 = -3.477ns となっている。他の静的タイミングを見るとcam_pclk の遅延が5.266nsだった。多分、clk48のクロック遅延が1.789ns だと思う。これで、セットアップ時間にクロックスキューが換算されている事がわかった。

ESP企画の旧パージョンボード(画像ベースボード、デジタルCMOSカメラ、208ピンSpartan3E XC3S500+2M高速SRAM基板)を使って、画像を出力していると、時々下の画像のようにちらちらしておかしくなる。(インプリメントの具合によって違うみたい。ちらつくインプリはずっとちらついているし、ちらつかないインプリはズートちらつかない)
791901eb.jpg


拡大してみると、特に本の白い部分で黒いポチポチが見える。これはほんのちょっとVHDLソースを変えたりすると直ってしまう。詳しくは、”CMOSカメラから画像を入力してディスプレイへ出力15(できた!!!)”参照。これはどうして起こるか確かめてみることにした。
いろいろ原因を考えてみたが、SDRAMのReadとWriteもぶつかっているようでも無い。やはりクロックが臭いのかな?ということで、CMOSカメラからのPCLKは下の図のようにDCMを通しているが、PCLKを直接クロックとして使用することにした。それでもXSTが自動的にBUFGは入れてくれていると思うけど。。。
9d04273f.png


つまり上図でDCM1/1を通さないで直接、回路のマスタクロックとして使用した。そうしたら、おかしい現象は解消された。下のラプラシアンフィルタの図もなんか緑っぽく、ノイジーだったのが、ノイズが減った。(下図はノイジーな状態)
0b76bb0e.jpg


下に変更点を示す。DCM_module_24MHzのmclk出力からmclkをとらないで、外部入力のcam_pclkから直接mclk へ接続した。mclk が回路全体のクロックである。

    DCM_module_24MHz_inst : DCM_module_24MHz port map(
        clk48MHz_in => clk,
        clk48_out => clk48,
        cam_clk_out => cam_clk_node,
        pclk_in => cam_pclk,
        -- mclk_out => mclk,
        mclk_out => open,
        cam_clk_locked => cam_clk_locked,
        mclk_locked => mclk_locked
    );
    reset <= not mclk_locked;
    mclk <= cam_pclk;


DACクロックもやはり反転して、DACデータの真ん中でクロックを立ち上げた方が良いようだ。

    -- DAC用クロックの生成
    n_mclk <= not mclk;
    ODDR2_for_dac_clk : ODDR2 generic map(
        SRTYPE => "ASYNC"
    ) port map(
        Q => dac_clk,
        C0 => mclk,
        C1 => n_mclk,
        CE => '1',
        D0 => '0',
        D1 => '1',
        R => reset,
        S => '0'
    );


D0 => '0', D1 => '1' で反対になっている。
これで、安心して仕事の回路がデバックできる。

(追記)
このボードはclk入力もpclk入力もGCLKにアサインされていないので、ISE11だと、下のような制約を入れる必要がある。どうにかならないものかな?GCLKにアサインされていれば。。。

NET "cam_pclk" CLOCK_DEDICATED_ROUTE = FALSE;
PIN "DCM_module_24MHz_inst/DCM_pclk.CLKIN" CLOCK_DEDICATED_ROUTE = FALSE;



(もう一度、追記)
新しいESP企画の画像ボードは、ちゃんとクロックがGCLK入力にアサインされていて、PCLKにDCMを使っても問題ないようです。

今度は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カメラへ。
最初にメインのステートマシン。構造を下に示す。
885b8702.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の時にカウントアップする。
次にデータパスのブロック図を再度示す。
dfb9d5bc.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;


これで大体説明できたと思う。これらのファイルを合わせて、テストベンチを書くと下のようなシミュレーション波形が得られる。
86f3e33c.png

次にまずは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


↑このページのトップヘ