リセットについての考察 のバックアップ(No.12)

更新


公開メモ

リセット信号の扱い

FPGA 開発を始めた当初(半年前くらい?)、 リセット信号の取り扱いについてあまり深く考えておらず、 「何となくリセットが掛かりそうな回路」を書いて満足していました。

しかし、レーシングなどについて勉強してからよく考えてみると、 リセットには慎重な扱いが必要であることが分かってきて、 ここらで一度考え直そうと思い立ちました。

ところが、調べれば調べるほどいろんなことが出てきて、 ちょっと泥沼状態です・・・

参考にした内容

小林芳直著「定本 ASICの論理回路設計」CQ出版社
内容的にはちょっと古い気もしますが、レーシングやメタステーブル、スタティックハザードなど、 一目見ただけでは HDLコードに現れてこない注意事項について勉強するにはこれよりちゃんと書かれた本を 見つけられていません。リセットに特化した話が書かれていたわけではありません。
http://marsee101.web.fc2.com/reset_of_fpga.html
主に回路規模の観点から同期リセットと非同期リセットの比較が行われています
http://www.fpgarelated.com/usenet/fpga/show/40963-1.php
STARTUP_SPARTAN3 等のモジュールを使い GSR により FPGA を初期化する話題
http://www.mofeel.net/210-comp-arch-fpga/9908.aspx
非同期リセットに関する話題
http://www.xilinx.com/support/documentation/white_papers/wp272.pdf
リセット回路構成上の注意をまとめた Xilinx の White Paper
http://www.xilinx.com/support/documentation/white_papers/wp275.pdf
FPGA のプリミティブの機能に合わせて HDL 記述に少し気をつけるだけで、 実質的には同じ動作をする回路を半分程度のサイズにできることがあるという話
(Xilinx の White Paper)
http://japan.xilinx.com/xcell/xl55/jp55xcell_04.pdf
同期リセット回路は非同期リセット回路に比べてツールによる最適化が掛かりやすいという話
本稿とは話題が異なるが、Block RAM を推論させる際の HDL の記述方法に関する注意点も書かれていて、非常に参考になる
http://toolbox.xilinx.com/docsan/xilinx6/books/docs/sim/sim.pdf
253ページに GSR 動作をシミュレータで再現する方法が書かれている
http://natu.txt-nifty.com/natsutan/2008/03/fpga_05d1.html
非同期リセット回路で、リセットのデアサートタイミングをクロック同期にする方法
http://japan.xilinx.com/support/documentation/user_guides/j_ug332.pdf
図12-13 に Spartan3A のコンフィグレーション後の GSR の動作が分かるブロックダイアグラムがある
http://www.fpgarelated.com/usenet/fpga/show/7379-3.php
GSR の使い方(結論出ず)

同期リセットと非同期リセット

良く知られるように、リセット信号を持つ回路の構成方法には、 非同期リセットと同期リセットがあって、 たとえば ISE の Language Template にも両方の形が収録されています。

同期リセットと非同期リセットのどちらが回路規模的にお得かは、 ASIC/FPGA/CPLD どれに実装するか、またどのメーカの FPGA に実装するか、 などによっても変わってくるそうです。

ASIC ではリセットを使う回路と使わない回路とで 実際にゲート1つ分の 回路規模と伝達遅延が差として現れるため、どのラッチにリセットを付けて、 どこに付けないかは、回路規模およびパフォーマンスに直に効いてきます。

これに対して(Xilinx の)FPGA では全ての(通常の) FF に同期リセットあるいは 非同期リセット用に使える SET/RESET (FDRSE: 同期) あるいは PRE/CLR (FDCPE: 非同期) 入力が始めから備わっているので、ここに直接リセット信号を繋ぐ限り、 演算器の回路規模や回路遅延への悪影響はありません。 (もちろんリセットネットの配線リソースは消費します)

async_sync.png

ただこれにはいくつか制限があって、以下のような注意事項が挙げられています。
wp272より抜粋)

  • 配線リソース・配線遅延
    • 多くの場合、リセットネットにはかなりの量の配線リソースが消費される
    • リセットネットに要求される最大遅延量を満たすために、他の信号ネットの遅延が増加する場合がある
    • リセットネットを張るためにルーティングにかかる時間が増加する
    • リセットネットはしばしば非常に大きなファンアウトを持つので、 その遅延時間がクロック周期の最小値を制限してしまうこともある
  • ロジックリソース
    • FF に内蔵されたリセット・クリア機能が使えれば余計なロジックは消費されない
    • 1つのスライスに含まれる2つの FF へのリセット線は共有されるため、 2つの FF が異なるリセット信号を持つ場合には1つのスライスに入れられなくなる
    • リセット・セットあるいはクリア・プリセットと、クロックイネーブルとの 信号線の複数が同時にアサートされたときの優先順位はプリミティブに固有なので、 それに沿わない優先順位で HDL を記述すると余計なロジックが挿入されて、 速度や回路規模に悪影響を与えることになる
    • リセット用のロジックのために PAR にかかる時間が増加する
  • FF 自体を削減する、という最適化が働かなくなる
    • FF がリセットを持たない場合には多数の FF を SRL16 を使って実装することで FF の消費を抑えることができる場合があるが (理想的には16個の FF を1つの LUT に押し込めることができる)、 リセットを持つ FF にはこの最適化が働かない (SRL16E にはリセット入力がないため)
    • Distributed RAM や Block RAM にもリセット入力はないので、 FF を RAM として実装するような最適化も働かない
  • LUT / RAM をリセットする機構は存在しない
    • DFF をリセットする機能はプリミティブに含まれていますが、 LUT に実装された SRL16E や Distributed/Block RAM をリセットする機構は プリミティブに存在しないため、もし必要であれば初期化用のステートマシンを 組む必要がある

リセット・セット・クロックイネーブルの優先順位に関する捕捉:

例えば FDRSE は Flipflop : D-type : Reset : Set : Enable の略で、 Reset, Set, Enable はこの順で優先順位が高いため、 次のコードは1つの FDRSE で無理なく実装できます。

LANG:verilog
always @(posedge clk)
    if (reset) begin
        c <= 0;
    end else
    if (force_high) begin
        c <= 1;
    end else
    if (enable) begin
        c <= a & b;
    end

これをちょっとだけ変えて、次のように enable と force_high の優先順位を入れ替えると FDRSE のそれと異なってしまうため、この部分に余計なロジックが挿入されてしまいます。

FDRSE はプリミティブとして存在するが、FDRES は存在しないところがキモです。

LANG:verilog
always @(posedge clk)
    if (reset) begin
        c <= 0;
    end else
    if (enable) begin
        if (force_high) begin
            c <= 1;
        end else begin
            c <= a & b;
        end
    end

もし回路の仕様としてどちらの動作でも構わないのであれば、 前者を採用することで回路規模を低減できます。

enable と reset を入れ替えたり、force_high と reset を入れ替えても同じです。

回路規模やネット遅延について

回路規模に関しては、Xilinx の FPGA では非同期リセットに比べて 同期リセットの方が有利であるという結果が FPGA の部屋 で得られているそうです。

http://marsee101.web.fc2.com/reset_of_fpga.html

Xilinx の「デザイン パフォーマンス向上のためのHDLコーディング法」でも 同期リセット回路は非同期リセット回路に比べてツールによる最適化が掛かりやすいという話が 紹介されています。

同期リセットで行くべき

したがって、手で記述するリセットは非同期ではなく同期型にしておくのが良いようです。

GSR の利用

上記のように、回路設計においてリセット回路は回路規模およびパフォーマンスを 圧迫する要因になり得ます。

これに対して、GSR をうまく使うことでユーザーロジックによるリセットを軽減し、 回路規模やパフォーマンスを大幅に改善できる可能性があります。

GSR とは Global Set Reset 信号の略で、これをアサートすることにより 全ての FF を (Block/Distributed RAM および LUT に実装されたシフトレジスタ、DCM を除く) コンフィグレーション直後の値にリセットすることができます。

GSR の動作はちょうど、隠された非同期リセット信号線があると考えれば良くて、 この機能を使うために追加で配線リソースやロジックリソースが消費されることはありません。

GSR を使うには STARTUP_???? というような名前のプリミティブを使うことになります。

???? の部分には FPGA の名前が入るので、Spartan3 であれば STARTUP_SPARTAN3 とか STARTUP_SPARTAN3A などとなります。

このモジュールには GSR という入力があるので、これを1にすることで全ての FF に非同期リセットがかかります。

ここで言う FF の初期値とは、Verilog で reg を宣言する際に指定する値です。

LANG:verilog
localparam st_idle = 3'b000;

reg [2:0] state = st_idle; // この st_idle のこと

あるいは INIT 属性などで指定することもできます。

(* INIT="1" *) reg enable;

コンフィグレーション直後の値と、リセット直後の値とを異なる値にするのは 余程の事情があるときだけだと思うので、それ以外では非同期リセットの代わりに GSR を使うことで大幅にリソースを削減できます。

また、ユーザーロジックの最適化に対する悪影響もないので、 上記非同期リセットの問題点はすべて解消されています。

(メモ)GSR により Block RAM の出力レジスタの値も初期化されるそうです。
(Block RAM の中身については初期化されません)

似たような考え方で

用途の異なるリセット線を福数本使う場合には、GSR だけでは足りなくなりますが、 リセット信号の伝達に BUFG を使うことでルータへの負担を軽くできるのでは ないかと思います。

FPGA の部屋の marsee さんによれば Xilinx のセミナーでも 紹介された手法らしいです。

http://marsee101.blog19.fc2.com/blog-entry-1464.html

リセット信号を BUFG を通して配線することで ネットの遅延も小さくなりますし、ローカルな配線リソースを消費しない分、 他の配線に与える影響も小さくなるはずなので、BUFG が余っていれば試す価値がありそうです。

この場合、やはり回路は同期リセット回路にしておくのが良いのだと思います。

リセット信号のデアサートは慎重に

同期リセットを使う場合にも、(GSR を含めた) 非同期リセットを使う場合にも、 リセット信号のデアサートのタイミングには十分な配慮が必要になります。

リセットの長さ

同期リセットを使う場合、リセット信号がアサートされる時間は(複数ある場合には最長の) クロック周期よりも長い必要があって、そうでないと最低限1回、 クロックエッジの時点でリセットがアサートされている状況を保証できません。

ただ、この問題はリセット信号を十分長い時間アサートすることで、簡単にクリアできるため、 現実問題としてはあまり注意を払う必要は無いと思います。

デアサートのタイミングによっては正しく初期化できない

デアサートのタイミングが重要、という例を考えてみました。 (Xilinx の White Paper (wp272) にもほぼ同じ例がありました)

次のコードは同期リセットを持つフリーランニングのワンホットコーディング・ ステートマシンのつもりで書いたものです。

LANG:verilog
reg [3:0] state;
always @(posedge clk)
    if (rst)
        state <= 4'b0001;
    else
        state <= { state[2:0], state[3] };    // ローテート

この rst に、例えばプッシュボタンからの非同期信号をそのまま繋いでしまうと、 タイミングによっては正しくリセットが行われないと思います。

これは、rst が非同期信号であるために、いわゆるレーシングを生じるためです。

以下にもう少し詳しく考えてみます。

上記 if 文は3項演算子で書くことができて、その場合には次のようになります。

LANG:verilog
reg [3:0] state;
always @(posedge clk)
    state <= rst ? 4'b0001 : { state[2:0], state[3] };    // ローテート

さらにプリミティブの動作を分かりやすくするためにビット毎に分けると 次のようになります。

LANG:verilog
reg [3:0] state;
wire [3:0] initial_state = 4'b0001;
wire [3:0] rotated_state = { state[2:0], state[3] };    // ローテート
always @(posedge clk) begin
    state[0] <= rst ? initial_state[0] : rotated_state[0];
    state[1] <= rst ? initial_state[1] : rotated_state[1];
    state[2] <= rst ? initial_state[2] : rotated_state[2];
    state[3] <= rst ? initial_state[3] : rotated_state[3];
end

ここまで書くと、この回路で rst が同期化されていない場合の問題が明らかになります。

  1. rst = 1 が数クロック続いて state == 4'b0001 になる
  2. rst が 0 に変化する時刻は、ネット遅延により、各ビットで少しだけ異なる
  3. rst==0 が state[3:1] にくらべてほんのわずかに速く state[0] に伝わると、
    • state[0] <= rotated_state[0]; // == 1'b0
    • state[1] <= initial_state[1]; // == 1'b0
    • state[2] <= initial_state[2]; // == 1'b0
    • state[3] <= initial_state[3]; // == 1'b0

となる可能性があって、この場合、リセット直後にホットビットが失われてしまい、 回路はまともに動作しません。

同様に、バイナリカウンタを定数値からデクリメントして使うような場合も、 おかしな結果を生む可能性のある例になると思います。

非同期リセット回路でも

非同期リセット回路でもリセットをデアサートするタイミングと クロックエッジとが重なれば同じ状況が起きると思います。

クロックのデアサートをクロックエッジに同期する

最も簡単な回避方法は、同期リセット回路へ入力するリセット信号デアサートを クロックエッジに同期することだと思います。

LANG:verilog
input wire async_rst,
input wire clk,
...

// リセット信号の同期化
wire sync_rst;
double_ff( .idata(async_rst), .oclk(clk), .odata(sync_rst) );

// 同期化されたリセットを、同期リセット回路に入力
reg [3:0] state;
always @(posedge clk)
    if (sync_rst)
        state <= 4'b0001;
    else
        state <= { state[2:0], state[3] };    // ローテート

非同期リセット回路においても、リセットデアサートタイミングのクロック同期は可能で、 その方法については、下記サイトが参考になりました。

http://natu.txt-nifty.com/natsutan/2008/03/fpga_05d1.html

が、少なくとも Xilinx の FPGA では同期リセットの方が 回路の最適化上で格段に有利とのことなので、以下では同期リセットのみ考えます。

複数のクロック信号がある場合

上と同じ考え方でいくと、もし回路が複数のクロック信号を持っているならば、 リセット信号もクロックの種類と同じだけの種類用意して、 必ずクロックに同期したリセット信号で回路を初期化しなければならない ことになります。

同期化したリセット信号を使っていて、それらの信号に不用意に TIG 制約を付けていなければ、同期リセット回路に間違って異なるクロックに同期した リセット信号を入力してしまったりしても、ツールが遅延時間解析で見つけてくれるので、 うっかりミスも防げます。

同期リセットの問題点

結論として、すべてのリセットを同期リセットとして、 クロックに同期したリセットのデアサートを意識的に行うことで、 安全なリセット回路ができあがります。

ただ、実際にこの方針で回路設計を行うと、規模が小さい間は良いのですが、 大きくなるに従って無理が出てきます。

1つには多くのクロックを使う場合、複数のリセットネットの伝達のために 多くの回路要素を消費してしまうことです。

もう1つは大きなリセットネットはネット内の遅延時間が大きくなるため、

  • リセット信号のネット遅延がクロック周波数を制限してしまったり、
  • リセット信号線の最適化のためにルーティング時間が非常に長く掛かったり、
  • リセット信号線の最適化のために他の信号線の遅延時間が長くなったり、

といった悪影響が現れてきます。

対症療法:同期リセット信号ネットの分割

リセットのデアサートのタイミングは、 上で見たように密接に関連する複数の FF の間で揃っていれば良いので、 同期リセット信号ネットを分割することで、上記のような問題を解決することができます。

すなわち、モジュールの入り口まで非同期リセット信号を届けておいて、 必要なモジュールでのみ、同期リセット信号を作って使えば良いことになります。

ただし同期リセットが必要な回路と、そうでない回路を慎重に見極めて、 必要な箇所で確実にリセットの同期回路を挿入しなければならないため、 十分な配慮が必要なことには代わりありません。

また、リセット信号の同期化のために FF を2つも消費することになりますので、 配線リソースを省けるメリットと、FF の消費とを考え合わせて、 慎重に回路規模を見積もることが必要になります。

クロックの種類が多くない場合で、かつ BUFG が余っている状況では同期リセット信号線を BUFG 経由で配信すれば、 巨大なファンアウトを持つリセット信号も最小の遅延時間で伝達することが 可能ですので、以下のような難しいことを考えなくても良い場合もあるかもしれません。

リセットのデアサートタイミングが問題にならない例

すべてを同期リセット回路で構成し、 それらにクロックに同期したリセット信号を届けることは、 上で見たように非常に大きな負担となります。

そこで、リセットの必要のない部分にはリセットを掛けないという方針を取らざるを得ないようです。

実際、非同期なデアサートがいつでも問題を引き起こすかというとそうでもなくて、 例えばバイナリカウンタをゼロからインクリメントする場合や、 初期ステートが無条件で遷移せず、外部信号のアサートを待つような回路、 あるいは始めの2ステートがジョンソンカウンタの無条件遷移になっている場合など、 リセット信号の非同期なデアサートにより問題が生じない回路も多数存在します。

そのような部分にはロジックによるリセット回路を実装せず、 GSR による再初期化で対応すれば、 リセットを実装するためのリソースを大幅に削減できます。

今考えている最善の方針

ということで、現状での最善策としては、

  • すべてのビットについてクロック同期のリセットが必要かどうかを慎重に見極める
  • クロック同期のリセットが必要な回路には、正しくクロックに同期したリセット信号を届ける
  • そうでない部分は GSR で再初期化することにより対応する

というようなことを考えています。

ただ、調べてみると GSR を手動リセットに使うのは色々手間の掛かる部分もあるようで、 本当にこの方針で良いのか、以下のように考察中です。

シミュレーションで GSR をエミュレートする方法

GSR 信号を有効活用する上でまず壁になりそうなのが、 GSR の動作をシミュレーションで確認するのが大変であるという点になります。

上記のように GSR をユーザー回路からコントロールするには STARTUP_SPARTAN3 モジュールを使うことになります。

また、Xilinx FPGA ではコンフィグレーション直後の 100ns 程度の間、 自動的に GSR がアサートされ、デバイスはリセット状態を保ちます。

普通に ISE から ModelSim や ISim を起動してデバッグすれば、 ユーザーコードの他に Xilinx の glbl モジュールがインプリメントされ、 シミュレーションの起動から 100 ns 程度の間 GSR がアサートされることで、 この動作がエミュレートされます。

ただ、このようにして GSR がアサートされても、 ビヘイビャーレベルではシミュレーション結果には GSR の動作が反映されません。 すなわち、GSR がアサートされている間にも通常通りレジスタの書き換えが行えてしまいます。

考えてみればそれはそうで、恐らく Verilog 言語には GSR のような 非明示的なリセット回路を記述する構文はないのだと思います。

ではどうするかというと、 http://toolbox.xilinx.com/docsan/xilinx6/books/docs/sim/sim.pdf の253ページに1つの方法が例示されています。

個々のレジスタに対して特別に GSR に対応するための記述を行っておき、 テストベンチから個々のモジュールに GSR 信号を配信するというものです。

コード例

次の例は非同期リセット信号を使って GSR をドライブし、FPGA 内の全ての FF を初期値に戻すと共に、非同期リセット信号がデアサートされてから 256 クロック程度経ってからデアサートされる同期リセット信号を生成する回路を書いたものです。

このコードを使って GSR 動作のシミュレーション方法を解説します。

この回路で再リセット時に初期化されなければならない FF は rst および count です。

実機では STARTUP_SPARTAN3A の .GSR 入力に1を立てることでこれらの FF は初期化されるのですが、 何も特別なことをしなければシミュレーション上では再初期化が働きません。

そこで、GSR による再初期化をエミュレートするために

  1. GSR_in というダミーの非同期リセット信号線を用意する
  2. 論理合成時には
    1. この信号線はどこからもドライブされないためゼロのままになる
    2. if (GSR_in) というような条件式は決して成立しないため、最適化により削除される
  3. シミュレーション時には
    1. 上位のモジュールから GSR_in に glbl.GSR を繋ぐことで非同期リセットをアサートする
    2. if (GSR_in) 部分のコードで FF の初期化をエミュレートする

という方針になります。

LANG:verilog
module reset_gen #(
    parameter COUNTER_BITS = 8
) (
    input wire GSR_in,      // ダミーの非同期リセット信号線
    input wire async_rst,
    input wire clk,
    output reg rst = 1
);
    // GSR により すべての FF に async_rst による非同期リセットがかかる
    STARTUP_SPARTAN3 STARTUP_SPARTAN3A_inst (
        .CLK(),             // Clock input for start-up sequence
        .GSR(async_rst),    // Global Set/Reset input (GSR can not be used as a port name)
        .GTS()              // Global 3-state input (GTS can not be used as a port name)
    );

    // 同期リセット信号の生成
    reg [COUNTER_BITS-1:0] count = 0;
    always @(posedge GSR_in or posedge clk) begin
        if (GSR_in) begin // GSR 動作をエミュレート
            // 合成時には最適化により削除されるので初期値は別途指定しなければならない
            rst <= 1;
            count <= 0;
        end else begin
            // 0 からのインクリメントに同期リセットは必要ない
            count <= count + 1;
            if ( count == {COUNTER_BITS{1'b1}} )
                rst <= 0;
        end
    end

endmodule

シミュレーション用にコンパイルするときのみ上位モジュールの GSR 信号線で下位モジュールの GSR_in をドライブする。

ただし、この GSR にはドライブ源がないので、論理合成時には無視されることになる。

LANG:verilog
module project_top #(
    parameter COUNTER_BITS = 8
) (
    input wire async_rst,
    input wire clk,
    ...
);

    wire GSR;  // ドライブ源がないため論理合成時には無視されるダミーの信号線

    wire sync_rst;
    reset_gen reset_gen (
        .GSR_in(GSR),
        .async_rst(async_rst),
        .clk(clk),
        .rst(sync_rst)
    );

    ...

endmodule 

テストベンチからトップモジュールの project_top.GSR を glbl.GSR でドライブする。

LANG:verilog
module project_top_test;
    ...
    
    project_top uut (
       ...
    );
    assign project_top.GSR = glbl.GSR;
    ...

endmodule

一応、このような方法で GSR をエミュレートが可能です。

  • すべてのレジスタについて if (GSR_in) によるダミーの初期化文を記述しなければならないこと
  • 複雑な階層の末端のモジュールまで、テストベンチから GSR 信号を届けなければならず、 その部分の記述も煩雑なこと

の2つは、通常の非同期リセットでも同じなため我慢できるとして、

  • reg signal = initial_value; という部分と、if (GSR_in) signal <= initial_value; の2カ所に初期値を記述しなければならず、DRY (Google:Don't repeat yourself) の原則に反する
    もちろん、1つ定数を用意してそれを2カ所で使えば原理的には問題ないのだけれど

と言う点については、うまい回避策も思い浮かばず、嫌な感じです。

また、合成時に出る

WARNING:Xst:1426 - The value init of the FF/Latch rst hinder the constant cleaning in 
  the block reset_gen. You should achieve better results by setting this init to 0.

という警告も気になるところです。

ネット上で検索してもこの警告の意味するところが分かりませんでした。

GSR 信号の延長

コメント欄で marsee さんから、GSR 信号線をリセットとして使うと、 リセット状態が解除された時点でクロックが安定している保証がないので 気をつけた方が良いと指摘していただいたので、これについて考えてみました。

まず、Xilinx の ug332 の12章あたり、特に 図12-13 を見ると、STARTUP モジュールから出力される GSR 信号は、STARTUP_WAIT=TRUE のときすべての DCM の LOCKED 信号がアサートされるのを待ってから、 さらに 100 ns 程度経った後にデアサートされるみたいです。

したがって、もし LOCKED 信号が信頼できるようなら、 クロックの安定度については心配ないのかもしれません?

実際のところ「電源オン直後にクロックが不安定な可能性」というのは ちょっと誤解を生む表現かもしれないです。

FPGA の場合には、ボードに電源が供給され、 電圧が十分に安定した後、FPGA のコンフィグレーションが行われます。

そしてコンフィグレーションが終了するまでの間、かなりの時間にわたりクロック源はクロック供給を続けますので、コンフィグレーションが終わり、GSR がリリースされた段階で未だ外部クロックが安定していないとすれば、恐らく追加で数ミリ秒待ったとしても安定供給は期待できないのではないかと思います。

したがって、コンフィグレーション直後にクロックの安定が気になるとすれば、コンフィグレーションでパラメータが決まった後、DCM が安定にクロックを出力するまでに掛かる時間に関するものになるのではないでしょうか。

もしそうならば、DCM の LOCKED 信号を基準として、100 ns 待ち、それよりもさらにユーザーロジックで追加の待ち時間を入れることで、初期化の確実性が向上するのかどうか、というあたりが論点になりそうに思えます。

(2010.6.29追記)marsee さんからさらに書き込みをいただきました。 コンフィグレーションをパラレルで高速に行った場合には、 外部クロックの起動時間よりもコンフィグレーションに掛かる時間の方が短くなる可能性があって、 その場合には、実際に追加で数ミリ秒の待ち時間が必要になることもあるのだそうです。

回路設計の前に、まじめに発振子のスペックと、 コンフィグレーション時間とを比べてみる必要がありそうです。

ユーザーロジックを使ってさらに延長することを試みる

こういうことが問題になる背景には、GSR がアサートされている限り通常の FF を使えないため、 例えば「DCM の LOCKED がアサートされてから 200 クロック後にリセットを解除する」 というようなロジックを組むのに工夫がいるという点があります。

以下では通常の FF の代わりに LUT をシフトレジスタとして使うことでカウンタを構成し、 DCM の LOCKED 後に GSR をリリースするタイミングを測ることを考えました。

GSR 中にも動作するカウンタ回路

次の回路により GSR 中かどうかにかかわらず N クロック ( N <= 16 ) に一度、 トリガを出すことができます。

GSR 中には通常の FF を使ったカウンタを作れないことに注意して下さい。

ここでは GSR に影響を受けない LUT により構成されたシフトレジスタを用いているところがキモです。

16bit のシフトレジスタを使ってワンホットカウンタを構成しています。

LANG:verilog(linenumber)
// N クロックに1度トリガを出力する
module srl16_pulser #(
    parameter N = 10
) (
    input wire clk,
    input wire en,
    output wire pulse
);
    localparam BIT = N - 1;
    SRL16E #(
        .INIT(16'h0001)     // Initial Value of Shift Register
    ) GSR_counter_l (
        .Q(pulse),          // SRL data output
        .A0(BIT[0]),        // Select[0] input
        .A1(BIT[1]),        // Select[1] input
        .A2(BIT[2]),        // Select[2] input
        .A3(BIT[3]),        // Select[3] input
        .CLK(clk),          // Clock input
        .CE(en),            // Clock enable input
        .D(pulse)           // SRL data input
    );
endmodule

2つ繋げれば、100 クロックに一度 1 を出力するような回路も簡単に作れます。

LANG:verilog(linenumber)
// それぞれ 10 clk / 100 clk に一度 1 になる
wire pulse10, pulse100;
srl16_pulser #(10) pulser1 ( .clk(clk), .en(1), .pulse(pulse10) );
srl16_pulser #(10) pulser1 ( .clk(clk), .en(pulse10), .pulse(pulse100) );

この回路は LUT を1個だけしか使わないので、 非常に省スペースのカウンタとして、結構使い手があるかもしれません?

問題はカウンタのリセット回路がないことですが・・・

より安全なカウンタ回路

上記のカウンタは parameter の指定により任意の周期を簡単に作れるのが良いのですが、 パワーオン直後などクロックが不安定な状況で、 万が一ホットビットが失われてしまうと、 まったくトリガを発しないまま、永久に復帰できません。 リセット回路がないのも痛いです。

より安全な回路として、周期は 32 固定になってしまいますが SRLC16 を使ったジョンソンカウンタを作ってみました。

これならば万が一起動がうまく行かなくても、最悪周期が短くなるだけで済みます。

また init 入力を 16 クロック間アサートすることで、 カウンタを正常な状態に戻せるようになっていますので、 本当に最悪の場合には手動リセットを行えます。

というわけで、まずまず安全に使えるんじゃないかと思います。

LANG:verilog(linenumber)
// GSR 中にも動作する16ビットジョンソンカウンタ (32クロック周期でトリガを発生)
module srl16_pulser32 (
    input wire clk,
    input wire init,        // 16クロック間アサートすることで初期化
    input wire en,
    output wire pulse
);
    wire Q14, Q15;
    localparam A = 14;
    SRLC16E #(
        .INIT(16'h0001)     // Initial Value of Shift Register
    ) GSR_counter_l (
        .Q15(Q15),          // Carry output
        .Q  (Q14),          // SRL data output
        .A0(A[0]),          // Select[0] input
        .A1(A[1]),          // Select[1] input
        .A2(A[2]),          // Select[2] input
        .A3(A[3]),          // Select[3] input
        .CLK(clk),          // Clock input
        .CE(en | init),     // Clock enable input
        .D(!Q15 & !init)    // SRL data input
    );
    assign pulse = !Q14 & Q15;
endmodule

GSR を延長する

上記 srl16_pulser32 を使い、async_rst が下りてから EXTENTION x 32 クロック後に GSR を解除するコード (実際には GSR_out を上位モジュールで STARTUP_SPARTAN3 に入れる)を書いてみました。

LANG:verilog(linenumber)
module gsr_gen #(
    parameter EXTENTION = 10    // x 32 クロックだけ延長する
) (
    input wire async_rst,
    input wire clk,
    input wire GSR_in,  // 論理合成時に無視されるダミーの非同期リセット
    output wire GSR_out // GSR 信号出力
);
    // GSR 状態を検出する
    reg GSR_active = 1;
    always @(posedge GSR_in or posedge clk)
        if (GSR_in) begin
            GSR_active <= 1;
        end else begin
            GSR_active <= 0;
        end

    // 32 クロックに一度パルスを出す (GSR 中にも動作する)
    wire pulse32;
    srl16_pulser32 pulser32 (
        .clk(clk),
        .en(!async_rst),
        .init(!GSR_active),
        .pulse(pulse32)
    );
    
    // GSR を EXTENTION x 32 クロック延長する
    wire gsr_extended;
    SRL16E #(
        .INIT(16'hffff)     // Initial Value of Shift Register
    ) GSR_counter_h (
        .Q(gsr_extended),   // SRL data output
        .A0(EXTENTION[0]),  // Select[0] input
        .A1(EXTENTION[1]),  // Select[1] input
        .A2(EXTENTION[2]),  // Select[2] input
        .A3(EXTENTION[3]),  // Select[3] input
        .CLK(clk),          // Clock input
        .CE(!async_rst & (pulse32| !GSR_active) ), // Clock enable input
        .D(!GSR_active)     // SRL data input
    );

    // GSR 出力
    assign GSR_out = async_rst | ( GSR_active && gsr_extended );

endmodule

ミリ秒単位の長い遅延時間が必要となる場合には、 srl16_pulser32 を重ねて使えば良いはずです。

例えば基本クロックが 100 MHz の時、4つ重ねると約 10ms の周期が得られますので、 EXTENTION パラメータを 5 とすることで 50ms の遅延が得られます。

注意点として async_rst が続けざまに on/off された場合の動作を記述しておきます

  1. async_rst を離してから GSR がリリースされる前に再度 async_rst をアサートした場合、 GSR の待機カウンタはリセットされません。2度目のリセット中にカウンタが止まっていたかの ように振る舞い、その後、動き出したカウンタが既定値に達すると GSR が解除されます
  2. GSR 解除の後 16 クロック以内に再度 async_rst がアサートされた場合、 待機カウンタがまだクリアされていないため、async_rst が離されてから 最短で次のクロック、最長でも 32 クロック以内に GSR がリリースされてしまいます

同期リセット信号を生成

GSR とは独立に動作するので切り離しました。

LANG:verilog(linenumber)
module reset_gen #(
    parameter COUNTER_BITS = 8
) (
    input wire clk,
    input wire GSR_in,  // 論理合成時に無視されるダミーの非同期リセット
    output reg rst = 1
);
    // 同期リセット信号の生成 => GSR 後 2**COUNTER_BITS だけ経ってから rst を下げる
    reg [COUNTER_BITS-1:0] count = 0;
    always @(posedge GSR_in or posedge clk) begin
        if (GSR_in) begin // GSR 動作をエミュレート(合成時には最適化により削除される)
            rst <= 1;
            count <= 0;
        end else begin
            // 0 からのインクリメントに同期リセットは必要ない
            count <= count + 1;
            if ( count == {COUNTER_BITS{1'b1}} )
                rst <= 0;
        end
    end

endmodule

使い方

使うときは、async_rst にリセット端子からの入力と dcm_locked 信号の and を入れることで、 パワーオン直後、クロックが安定してからさらに数百クロック後に GSR を解除し、 さらにその後数百クロック後に同期リセットを解除する回路になっています。

GSR のネット遅延が FPGA 全体でどのくらいになるのか記述を見つけられなかったのですが、 さすがに数百クロック待っても伝わらないなどということは無いと思うので、 本当に非同期で構わない部分のみ GSR で初期化し、残りを同期リセットで初期化するという 方針で危険はないんじゃないかと思っています。

LANG:verilog
module top_module(
    ...
  
    input wire clk_in,
    input wire async_rst_n,    // アクティブ・ロー
    ...

);
    ...

    wire GSR;      // ドライブ源がないため論理合成時には無視されるダミーの信号線
    wire GSR_out;
    ...

    // GSR を生成
    gsr_gen gsr_gen (
        .async_rst(!async_rst_n | !dcm_locked),
        .GSR_in(GSR),
        .GSR_out(GSR_out),
        .clk(clk_in)   // DCM からのクロックではなく外部クロックで駆動する
    );

    // GSR により すべての FF に async_rst による非同期リセットがかかる
    // シミュレーション時にはテストベンチで以下のように接続する必要がある
    //   assign glbl.GSR = GSR_out;
    //   assign GSR_in = glbl.GSR;  // ビヘイビャーレベルシミュレーション時
    // GSR を持たないデバイスでは次のようにすれば通常の
    // 非同期リセットとして合成することができる
    //   assign GSR_in = GSR_out;
    STARTUP_SPARTAN3A STARTUP_SPARTAN3A_inst (
        .CLK(),             // Clock input for start-up sequence
        .GSR(GSR_out),      // Global Set/Reset input (GSR can not be used as a port name)
        .GTS()              // Global 3-state input (GTS can not be used as a port name)
    );

    // 同期リセット信号を生成
    wire sync_rst_pre;
    reset_gen reset_gen (
        .GSR_in(GSR),
        .clk(clk),         // DCM からのクロックで駆動してもOK
        .rst(sync_rst_pre)
    );
    
    // 同期リセット信号をグローバルな高速ネットを使って配信
    wire sync_rst;
    BUFG sync_rst_buf ( .I(sync_rst_pre), .O(sync_rst) );
    ...

実際の回路の構成としては、

上記の要領で if (GSR_in) の形で、 あたかも非同期リセットが掛かっているかのような記述をしながら、 実際には reg の初期値で非同期リセットを実現しつつ、

どうしても同期リセットが必要な部分には sync_rst をそのまま、 あるいはこれをローカルに別のクロックに同期したリセット信号を作成し、 同期リセット回路に入力することで実装しようと考えています。

同期リセット信号の配信にはグローバルな高速配線リソースを使っているので、 ローカルな配線リソースを圧迫することはなく、また、 ネット遅延によりクロック周期を圧迫することも少なくできると思います。

Post-PAR シミュレーションでは

上で考察したとおり、 GSR を用いた回路についてビヘイビャーレベルのシミュレーションを行うには、 GSR の動作を模した非同期リセット線を独自に実装し、 そのリセット線を glbl.GSR でドライブするという、 非常に大回りな回避策を採らなければならなりませんでした。

Post-PAR シミュレーションなどでは、この問題は多少易しく解決できるようです。

というのも、simprims に含まれる X_SFF などのフリップフロップ モジュールは 内部で glbl.GSR を読み取ることで、 外部回路が無くても GSR 状態を正しく再現するようにできているためです。

つまり、glbl.GSR さえ正しくドライブしてやれば、 GSR がらみの動作を正しく Post-PAR シミュレーションできることになります。

ただし1つ注意が必要なこととして、STARTUP_SPARTAN3 などのモジュールは Post-PAR シミュレーションモデルでは何ら効果を発揮しないという点があります。 すなわち、STARTUP_SPARTAN3 の .GSR 入力を駆動しても、 PAR シミュレーションにおいて glbl.GSR が変化することはありません。

glbl.GSR などの動作は合成結果とは別レベルで動いているため、 STARTUP_SPARTAN3 の合成結果から glbl.GSR を駆動するわけにはいかない、 ということのようですね。

正しくシミュレーションするには STARTUP_SPARTAN3 の .GSR ピンへ入力した信号で glbl.GSR を駆動してやることになります。

上記のように、トップレベルモジュールの信号線で STARTUP_SPARTAN3 を駆動していれば、テストベンチから次のようにして glbl.GSR に接続できます。

LANG:verilog
    assign glbl.GSR = uut.GSR_out;

注意点

ここまで読んでいただいた上で いまさらなのですが、 まだ実機では試せていませんので、話半分で(!)お願いします(汗

コメント




リセットの取り扱いについて

[ふn] (2010-07-03 (土) 12:22:22)

ちょっと、時期を逃してしまいましたが・・・『reg の宣言時に初期値』についてですが、Altera 社のデバイスでは保証しない、と言われました。確かにQuartusのハンドブックにも、できるようなことを書いてありましたが、運が悪いとどちらに転ぶかわからないそうです。

リセットの取り扱いについて

[marsee] (2010-06-22 (火) 13:34:32)

Xilinxのセミナでは、リセットが必要ないところは使わないようにした方が良いとのことでした。
XilinxのFPGAは、コンフィギュレーションする時にD-FFをすべて0にしています。よって、初期値が0で良いならばリセットする必要がないわけです。シミュレーションの時に困ることがあると思いますが、それでいいならば、リセットしない方が回路規模や色々なタイミングを満足するために良いとの事でした。

  • はい、リセットはできる限り省くのが良いみたいですね。Verilog では本文中に(後から)記述した形式で reg の宣言時に初期値を決めれば 0 以外の初期値も可能なように思うのですが・・・正しいでしょうか? -- [武内(管理人)]
  • 論理合成ツールがXSTの場合は可能だそうです。たぶん、Synplifyなどでは無視されるのだと思います。 -- [marsee]
  • ちょっと調べてみたところ、おっしゃるとおり Synplify ではだめなようでした。http://tinyurl.com/2c22jfw によれば /* synthesis xc_props="INIT=S" */ というような属性で1にするか0にするかは選べるみたいですが、ベクター全体が1か0になってしまうため、適当な定数を初期値とするのは難しいようですね。 -- [武内(管理人)]
  • と思ったら、http://tinyurl.com/25gwa2h では Synplify でも初期値を使えると書いてありますね。上の記述は 2005 年で、こちらは 2009 年なのでこの間に対応したと言うことかもしれません。が、この記事でも初期値は使えるツールと使えないツールがあるため、ポータビリティが低いという雰囲気になっています。同じ記事で Quartus でも使えると書いてありますので、最新版のツールを使う限りターゲットが FPGA であれば大抵大丈夫なのかもしれません。後から ASIC に焼き直すとかいう可能性がなければ気にしなくても??? -- [武内(管理人)]
  • XSTもSynplifyも使える環境にあるので、後で本当はどうなのか確かめてみます。私は少なくともうちののシステムはリセットをしようと思っています。理由はクロックが電源ONから安定とは限らないからです。 -- [marsee]
  • GSRを使う文脈をいろいろ考えてはみているのですが、クロックが安定するまで待つ方法を始め、おっしゃる通りいろいろ難しいところがありそうですね。今はそのあたりをシミュレーションで確かめる方法を検討しているのですが、STARTUP_SPARTAN3A の GSR ピンを上げてもレジスタ値は初期化されていないようで・・・何か根本的に違うことをしているみたいです? -- [武内(管理人)]
  • ビヘイビャーレベルのシミュレーションで GSR の動作を正しくエミュレートするには http://toolbox.xilinx.com/docsan/xilinx6/books/docs/sim/sim.pdf の253ページにあるように、個々のレジスタに対して特別な記述をしなければならないのですね。これはかなり面倒そうです。また GSR はネット遅延が規定されていないので手動リセット回路としては使わない方が良いという記述がちらほらと。これは非同期であることを正しく考慮できるなら気にしなくても良いのかもしれませんが・・・ いずれにせよ、また考え直さないと行けないみたいです。 -- [武内(管理人)]
  • 「電源オン直後にクロックが不安定な可能性」というのは、水晶発振器自体が電源ONの時に不安定な状態を抜け出す待ち時間が必要と言う意味で使いました。例えば、私たちが使ったEG-2121CAは、発振開始時間が10ms MAXです。これでマスタのコンフィギュレーションならば大丈夫かもしれませんが、スレーブセレクトマップだと問題が出る可能性があります。なかなかコメントでは説明が難しいですね。http://ndap3-net.ebz.epson.co.jp/w/www/PDFS/epdoc_qd.nsf/a2256996cd15b6cd49257074002d15eb/1fb0e0141cc1205a4925707c00291790/$FILE/EG-21xxCA_J09X.pdf -- [marsee]
  • FPGAをスレーブにしてパラレル通信で高速に書き込んでしまうと、クロックが安定するよりもコンフィグレーションの方が早く終わってしまうこともあるのですね。私の頭にはマスタ SPIでのコンフィグレーションしかなかったので、思い至りませんでした。一応、上記のようにすればGSRでもリセットのデアサートに遅延を入れられるので、いざとなれば数十ミリ秒を待つこともできそうに思えるのですが、まだ実機で試していないのでうまく行ったらまた報告しようと思います。 -- [武内(管理人)]
  • あれ、マスタ SPI でも FPGA のサイズが小さかったりすると結構危ういですね。10 ms というのが周波数がどの程度の範囲に入るまでの時間なのか、データシートからは分からなかったのですが、この時間をしっかり待とうとするとユーザーロジックでの待機が必要となることが理解できました。 -- [武内(管理人)]

Counter: 73802 (from 2010/06/03), today: 7, yesterday: 0