クロックの逓倍と分周

平成14年9月29日
 このページではXILINXのWebPACKを使って、SpartanIIのDLLという機能を使う方法を紹介します。
  1. DLLという機能
  2. DLLを使用することの宣言
  3. CLKDLLのシンボル
  4. 基本的な使い方
  5. 逓倍のやり方(1)
  6. 逓倍のやり方(2)
  7. 分周のやり方
  8. 逓倍と分周のやり方
  9. ロジック出力をDLLに入力する方法
  10. BUFGの数による制約
  11. ロケーションの手動指定
  12. デューティー比の調整

DLLという機能

 SpartanIIやVirtexEにはDLLという機能が備わっています。 この機能を使うと入力されたクロックを2倍や4倍に、 あるいは2分の1や3分の1、1.5分の1などに分周することができます。

 DLLはPLLと違って、発信器ではありません。DLLは入力されたクロックを遅延させて出力させているわけなので、入力クロックが乱れれば、DLLの出力は当然乱れます。
 DLLは遅延の大きさをコントロールして、遅延クロックと、次のクロックのエッジの位相を合わせることで、スキューのないクロックをつくりだしています。

DLLを使用することの宣言

CLKDLLというシンボルがあります。
これを使うには

LIBRARY UNISIM;
USE UNISIM.Vcomponents.ALL;

で宣言してもよいですし、USE文で組み込まなくても

component CLKDLL port(
    CLKFB  : in std_logic;
    CLKIN  : in std_logic;
    RST    : in std_logic;
    CLK0   : out std_logic;
    CLK180 : out std_logic;
    CLK270 : out std_logic;
    CLK2X  : out std_logic;
    CLK90  : out std_logic;
    CLKDV  : out std_logic;
    LOCKED : out std_logic
);
end component;

と宣言しておけば、論理合成中に自動的に組み込まれます。その場合、BUFGやIBUFGも一緒に宣言しておきましょう。
component BUFG port(
    I  : in std_logic;
    O  : out std_logic
);
end component;

component IBUFG port(
    I  : in std_logic;
    O  : out std_logic
);
end component;

 UNISIMに関しては、WebPACKのディレクトリにunisimというディレクトリがあるので、その中からunisim_VCOMP.vhdというファイルがあるので、眺めてみましょう。

CLKDLLのシンボル

 このシンボルはCLKIN、CLKFB、RSTという入力ピンがあります。CLKINに入力クロックを、CLKFBには出力されたクロックをフィードバックさせて入れます。また、RSTはCLKDLLをリセットします。リセットはCLKDLLで逓倍をおこなうときに入力クロックの周波数が変化する場合に使用します。


 出力ピンにはCLK0,CLK90,CLK180,CLK270,CLK2X,CLK90,LOCKEDがあります。CLK0〜CLK270は位相のずれたクロックが出力され、CLK2Xは2倍周波数のクロックが出力されます。LOCKEDはDLLがロックされるとアクティブになります。

 なお、CLKDLLは周波数を2倍にすることはできますが、3倍にすることはできません。4倍にするにはCLKDLLを2個使って直列に接続します。
 実際にVHDLの中で使うには、フィードバック用の信号を用意しなければなりません。RSTは常に'0'でよいです。

基本的な使い方

 次の例は最も基本的な使い方です。クロックの逓倍も分周もせずに、与えられたクロックと同位相のクロックを出力します。これはスキューをなくすのを目的とする使い方です。

entity文
    CLK_IP : in std_logic;
    ...
architechture文
    signal logicl : std_logic;
    signal CLK_I,CLK_O : std_logic;
    signal CLK_1X_OP : std_logic;
    ...
begin
    ...
    logicl <= '0';
    ibufg01 : IBUFG PORT MAP (I=>CLK_IP, O=>CLK_I);
    bufg01 : BUFG PORT MAP (I=>CLK_O, O=>CLK_1X_OP);

    DLL1 : CLKDLL
        PORT MAP (CLKIN=>CLK_I, CLKFB=>CLK_1X_OP, RST=>logicl,
            CLK0=>CLK_O,  CLK90=>open, CLK180=>open, CLK270=>open,
            CLK2X=>open, CLKDV=>open, LOCKED=>open);


 CLKDLLの入力クロックはBUFGを通過していなければなりません。また、CLKDLLのCLKFBもBUFGを通さなければなりません。

逓倍のやり方(1)

 次の例では、与えられたクロックを2倍周波数にします。この方法ではフィードバックするクロックは1倍周波数にします。


entity文
    CLK_IP : in std_logic;
    ...
architechture文
    signal logicl : std_logic;
    signal CLK_I,CLK_O,CLK_2X : std_logic;
    signal CLK_1X_OP,CLK_2X_OP : std_logic;
    ...
begin
    ...
    logicl <= '0';
    ibufg01 : IBUFG PORT MAP (I=>CLK_IP, O=>CLK_I);
    bufg01 : BUFG PORT MAP (I=>CLK_O, O=>CLK_1X_OP);
    bufg02 : BUFG PORT MAP (I=>CLK_2X, O=>CLK_2X_OP);

    DLL1 : CLKDLL
        PORT MAP (CLKIN=>CLK_I, CLKFB=>CLK_1X_OP, RST=>logicl,
            CLK0=>CLK_O,  CLK90=>open, CLK180=>open, CLK270=>open,
            CLK2X=>CLK_2X, CLKDV=>open, LOCKED=>open);


逓倍のやり方(2)

 次の例では、与えられたクロックを2倍周波数にします。この方法ではフィードバックするクロックは2倍周波数にします。


entity文
    CLK_IP : in std_logic;
    ...
architechture文
    signal logicl : std_logic;
    signal CLK_I,CLK_O,CLK_2X : std_logic;
    signal CLK_1X_OP,CLK_2X_OP : std_logic;
    ...
begin
    ...
    logicl <= '0';
    ibufg01 : IBUFG PORT MAP (I=>CLK_IP, O=>CLK_I);
    bufg01 : BUFG PORT MAP (I=>CLK_O, O=>CLK_1X_OP);
    bufg02 : BUFG PORT MAP (I=>CLK_2X, O=>CLK_2X_OP);

    DLL1 : CLKDLL
        PORT MAP (CLKIN=>CLK_I, CLKFB=>CLK_2X_OP, RST=>logicl,
            CLK0=>CLK_O,  CLK90=>open, CLK180=>open, CLK270=>open,
            CLK2X=>CLK_2X, CLKDV=>open, LOCKED=>open);


分周のやり方

 次の例では、与えられたクロックを分周します。この方法ではフィードバックするクロックは1倍周波数にします。
 分周出力はCLKDVから取り出されますが、分周比はGENERICとATTRIBUTEを使って指定します。分周比は1.5、2、2.5、3、4、5、8、16の中から選びます。1.5や2.5が指定できるのはすごいことです。


entity文
    CLK_IP : in std_logic;
    ...
architechture文
    signal logicl : std_logic;
    signal CLK_I,CLK_O,CLK_DV : std_logic;
    signal CLK_1X_OP,CLK_DV_OP : std_logic;
    ATTRIBUTE CLKDV_DIVIDE : STRING ;
    ATTRIBUTE CLKDV_DIVIDE OF DLL1: LABEL IS "1.5" ;
    ...
begin
    ...
    logicl <= '0';
    ibufg01 : IBUFG PORT MAP (I=>CLK_IP, O=>CLK_I);
    bufg01 : BUFG PORT MAP (I=>CLK_O, O=>CLK_1X_OP);
    bufg02 : BUFG PORT MAP (I=>CLK_DV, O=>CLK_DV_OP);

    DLL1 : CLKDLL
        -- synopsys translate_off
        GENERIC MAP ( CLKDV_DIVIDE => 1.500000 )
        -- synopsys translate_on
        PORT MAP (CLKIN=>CLK_I, CLKFB=>CLK_1X_OP, RST=>logicl,
            CLK0=>CLK_O,  CLK90=>open, CLK180=>open, CLK270=>open,
            CLK2X=>open, CLKDV=>CLK_DV, LOCKED=>open);


逓倍と分周のやり方

 次の例では、逓場と分周を同時に行います。この方法ではフィードバックするクロックは2倍周波数にします。例えば50MHzのクロックから10MHzと100MHzを同時に取り出すことができます。2個のDLLを使わずに1個でできるのが特徴です。


entity文
    CLK_IP : in std_logic;
    ...
architechture文
    signal logicl : std_logic;
    signal CLK_I,CLK_2X,CLK_DV : std_logic;
    signal CLK_2X_OP,CLK_DV_OP : std_logic;
    ATTRIBUTE CLKDV_DIVIDE : STRING ;
    ATTRIBUTE CLKDV_DIVIDE OF DLL1: LABEL IS "5" ;
    ...
begin
    ...
    logicl <= '0';
    ibufg01 : IBUFG PORT MAP (I=>CLK_IP, O=>CLK_I);
    bufg01 : BUFG PORT MAP (I=>CLK_2X, O=>CLK_2X_OP);
    bufg02 : BUFG PORT MAP (I=>CLK_DV, O=>CLK_DV_OP);


    DLL1 : CLKDLL
        -- synopsys translate_off
        GENERIC MAP ( CLKDV_DIVIDE => 5.000000 )
        -- synopsys translate_on
        PORT MAP (CLKIN=>CLK_I, CLKFB=>CLK_2X_OP, RST=>logicl,
            CLK0=>open,  CLK90=>open, CLK180=>open, CLK270=>open,
            CLK2X=>CLK_2X, CLKDV=>CLK_DV, LOCKED=>open);

ロジック出力をDLLに入力する方法

 CLKDLLの入力はBUFGでなければならないのですが、そのクロックの源は外部IOピンか、他のCLKDLLの出力でなければならないという制約があります。このため、フリップフロップの出力や、ロジックをDLLにいれることはできません。
 この制約によって、例えば7進カウンタの出力をCLKDLLに入れたくても、Imprementationの最後でエラーが出てしまいます。
ERROR:MapLib:317 - BUFG symbol "bufg01" (output signal=clk_i) driving CLKDLL
   must be driven by CLKDLL too. To by-pass this check, set environment variable
   XIL_MAP_ALLOW_ANY_DLL_INPUT.
Errors found during the mapping phase.  Output files will not be written.

 これを回避するには、XIL_MAP_ALLOW_ANY_DLL_INPUTという環境変数を設定します。環境変数というのは、要するにOSの環境変数です。Windows95/98/Meを使っている場合には、AUTOEXEC.BATに


SET XIL_MAP_ALLOW_ANY_DLL_INPUT=1

の一行を付け加えてWindows再起動します。
 WindowsNT/2000/XPでは「環境変数の設定」でおこないます。
 この環境変数を設定すると、次のような回路が作れるようになります。

entity文
    CLK_IP : in std_logic;
    ...
architechture文
    signal logicl : std_logic;
    signal MyCLK : std_logic;
    signal CLK_I,CLK_2X : std_logic;
    signal CLK_2X_OP : std_logic;
    ATTRIBUTE CLKDV_DIVIDE : STRING ;
    ATTRIBUTE CLKDV_DIVIDE OF DLL1: LABEL IS "1.5" ;
    ...
begin
    ...
    process(CLK_IP) begin
        if(CLK_IP'event and CLK_IP = '1') then
            MyCLK <= not MyCLK;
        end if;
    end process;

    logicl <= '0';
    bufg01 : BUFG PORT MAP (I=>MyCLK, O=>CLK_I);
    bufg02 : BUFG PORT MAP (I=>CLK_2X, O=>CLK_2X_OP);
    
    DLL1 : CLKDLL
        PORT MAP (CLKIN=>CLK_I, CLKFB=>CLK_2X_OP, RST=>logicl,
            CLK0=>open,  CLK90=>open, CLK180=>open, CLK270=>open,
            CLK2X=>CLK_2X, CLKDV=>open, LOCKED=>open);

 もしCLK_2Xだけでなく、1倍クロックも取り出そうとするならば、「自動配置」がエラーを出すでしょう。その場合には次節で紹介する「ロケーション手動指定」を利用してください。
 ImprementするとWarningがでますが、論理合成と配置配線は成功します。
 このようにして回路を作成したのですが、自分で作ったクロックにCLKDLLはうまくロックしてくれず、まだ2倍周波数を出すには至っていません。1倍の方はOKです。

BUFGの数による制約

 CLKDLLの入力ピン、CLKINとCLKFBはBUFGの配線でなければなりません。CLKDLLを使うには最低でも1個のBUFGを使います。SpartanIIにはCLKDLLは4個ですが、BUFGも4個しかありません。

 CLKDLLの出力には6つのクロック出力がありますが、同時に使えるのは最高でも2つです。なぜなら、CLKDLLは2個のBUFGしかドライブできないからです。

 CLKDLLから分周出力を取り出すには、フィードバック用にBUFGを1個と、分周出力用にBUFGを1個使います。それゆえ、CLKDLLを分周器として使うと貴重なBUFGを2個使います。SpartanIIにCLKDLLは4個ありますが、BUFGの制限により分周器は2個しか作れません。

 入力信号はIBUFGに割り当てれば貴重なBUFGを1個節約できますし、たぶんそれがデフォルトです。

ロケーションの手動指定

 CLKDLLを2個使って2段の分周を行いたいことがあります。
 例えば1段目のDLLで1.5分周を行い、2段目で2.5分周を行うとします。入力に45MHzのクロックを使用すれば、1段目で30MHzが出力され、2段目で12MHzが出力されます。


 FPGAの合成で、どのプリミティブにフィットさせるかは、通常「自動配置」というCADの機能が行ってくれるのですが、しかし上の図のような2段の分周回路を作った時に「自動配置」が機能してくれないことがありました。そこで、手動配置を行う必要が出てきました。

ERROR:Place:1726 - Could not find an automatic placement for the following
   components:
    clk_ip of type GCLK IOB is unplaced.
    dll_a of type DLL is unplaced.
    bufg01 of type GCLK BUFFER is unplaced.
    bufg02 of type GCLK BUFFER is unplaced.
    dll_b of type DLL is unplaced.
    bufg03 of type GCLK BUFFER is unplaced.
    bufg04 of type GCLK BUFFER is unplaced.
ERROR:Place:1727 - Xilinx requires using locate constraints to preplace such
   connected GCLK/GCLKIO/DLL components.

 次の図はSpartanIIのクロック系統を示す図です。SpartanIIにはクロック用の入力パッド、DLL、BUFG、グローバル配線が4組あります。  しかしながら、DLLは上の2つと下の2つがペアになっていて、上側のDLLは上側のBUFGに対してしか出力できません。入力はどこからでもできます。
 これらのことを考慮して配置を決めます。配置を指定するオプションは"LOC"というアトリビュートです。


 デバイスのピンとDLLは対応をとらなければなりません。例えばUCFファイルでクロック用パッドをGCK1に指定しているならば、1段目のCLKDLLを"DLL1"に指定します。UCFファイルで指定していないならば、IBUFGのLOCをGCLKPAD0〜3に指定することでうまくいきました。
 なお、上の図に書いた名前(DLL0、GCLKBUF3など)をCLKDLLやBUFGのLOCアトリビュートの値として使用します。

entity文
    CLK_IP : in std_logic;
    ...
architechture文
    signal logicl : std_logic;
    signal CLK_I,CLKA_O,CLKA_DV,CLKA_FB : std_logic;
    signal CLKB_O,CLKB_DV,CLKB_FB : std_logic;
    signal CLKA_DV_OP,CLKB_DV_OP : std_logic;
    ATTRIBUTE CLKDV_DIVIDE : STRING ;
    ATTRIBUTE CLKDV_DIVIDE OF DLL_A: LABEL IS "1.5" ;
    ATTRIBUTE CLKDV_DIVIDE OF DLL_B: LABEL IS "2.5" ;
    ATTRIBUTE LOC : STRING ;
    ATTRIBUTE LOC OF DLL_A: LABEL IS "DLL1" ;
    ATTRIBUTE LOC OF DLL_B: LABEL IS "DLL3" ;
    ATTRIBUTE LOC OF ibufg1: LABEL IS "GCLKPAD0" ; -- <= これでうまくいった
    ATTRIBUTE LOC OF bufg01: LABEL IS "GCLKBUF0" ;
    ATTRIBUTE LOC OF bufg02: LABEL IS "GCLKBUF1" ;
    ATTRIBUTE LOC OF bufg03: LABEL IS "GCLKBUF2" ;
    ATTRIBUTE LOC OF bufg04: LABEL IS "GCLKBUF3" ;
    ...
begin
    ...
    logicl <= '0';
    ibufg1 : IBUFG PORT MAP (I=>CLK_IP , O=>CLK_I);
    bufg01 : BUFG PORT MAP (I=>CLKA_O , O=>CLKA_FB);
    bufg02 : BUFG PORT MAP (I=>CLKA_DV, O=>CLKA_DV_OP);
    bufg03 : BUFG PORT MAP (I=>CLKB_O , O=>CLKB_FB);
    bufg04 : BUFG PORT MAP (I=>CLKB_DV, O=>CLKB_DV_OP);

    DLL_A : CLKDLL
        -- synopsys translate_off
        GENERIC MAP ( CLKDV_DIVIDE => 1.500000 )
        -- synopsys translate_on
        PORT MAP (CLKIN=>CLK_I, CLKFB=>CLKA_FB, RST=>logicl,
            CLK0=>CLKA_O,  CLK90=>open, CLK180=>open, CLK270=>open,
            CLK2X=>open, CLKDV=>CLKA_DV, LOCKED=>open);

    DLL_B : CLKDLL
        -- synopsys translate_off
        GENERIC MAP ( CLKDV_DIVIDE => 2.500000 )
        -- synopsys translate_on
        PORT MAP (CLKIN=>CLKA_DV_OP, CLKFB=>CLKB_FB, RST=>logicl,
            CLK0=>CLKB_O,  CLK90=>open, CLK180=>open, CLK270=>open,
            CLK2X=>open, CLKDV=>CLKB_DV, LOCKED=>open);

デューティー比の調整

 CLKDLLにはDUTY_CYCLE_CORRECTIONというアトリビュートもあります。これはTRUEかFALSEを指定します。TRUEにすると、DLLの出力のデューティー比を50%/50%にします。FALSEなら入力クロックのデューティー比になります。つまり、DLLが入力クロックを単純に遅延させて出力するようになります。
    ATTRIBUTE DUTY_CYCLE_CORRECTION : STRING ;
    ATTRIBUTE DUTY_CYCLE_CORRECTION OF DLL1: LABEL IS "TRUE" ;
 なお、2倍周波数のクロックを出力させる場合、このデューティー比は50%/50%ですが、DLLがまだロックしていない状態の場合は、元のクロックと同じ周波数で、デューティー比が25%/75%になります。これはDUTY_CYCLE_CORRECTIONアトリビュートではどうにもなりません。

戻る