01signal.com

FPGA を適切に起動・リセットするためのロジック

これは、FPGA のリセットに関する連載の第 3 回目で、最後のページです。このページを読む前に、まず前の 2 ページを読むことをお勧めします。

概要

リセットを生成するためのロジックをきちんと構築するために時間をかけるのは、常に良い考えです。現状のデザインに明らかな問題がなくても、設計のこの段階を省略すると、後でしわ寄せが来る可能性が高いです。つまり、ロジックが正しく初期化されていないことに気づかず、不安定動作の問題を解決しようとして何日も費やすことになりかねません。こうした問題を解決しようとするうちに、デザインには、問題の根本を理解せずに作られた醜い回避策が積み重なっていきます。奇妙な不安定動作についてはこのページで詳しく説明しています。

リセット信号をプロジェクトの最初から考えて設計することは、新しい機能が追加されるにつれてプロジェクトが適切に発展していくことを保証するためにも重要です。FPGA プロジェクトをゼロから始める場合、最初に中核機能を実装し、その後時間とともに機能を追加していくことがよくあります。モジュールごとに別々のクロックとリセットが必要になることが多いため、各部分をそれぞれ別々に、急ごしらえで組み合わせるという罠に陥りやすいです。これにより、プロジェクトが進むにつれてデザインはどんどん混沌としていきます。

初日からきちんと書かれた、リセットとクロックを収める中央モジュールを用意すれば、雑然としたプロジェクトを避けやすくなります。そして後で説明するとおり、リセットコントローラとクロックリソース(必要に応じて PLL やクロックバッファ)は互いに影響し合うため、それらを同じモジュールに入れることは有益です。私は通常、このモジュールに clkrst.v という名前を付けます。

ただし、IP コア(IP core)、サブシステム、デザインブロックの中には、独自のリセットやクロックのグループを生成するものがあるため、これらすべてをひとつのモジュールに集中させることができない場合もよくあります。そのような場合は、何が何に依存するのか、異なるソースから来るリセット要求に対してシステム全体がどう反応すべきかを慎重に考える必要があります。たとえば、PCIe ブロックへのリセットはほとんど常にバス自体から来て、そのブロックは接続されているロジックが使うリセット信号を生成します。このような状況では、たとえば PCIe バスからリセットが来たときにシステム全体としてどう応答すべきか(まったく応答しない可能性も含めて)を検討する必要があります。

プロジェクトごとに事情が異なるため、すべてのケースに当てはまる単一の解決策はありません。このページでは、独自のリセットコントローラを構築するための部品として使える概念やアイデア、コード断片を紹介します。ただし、これらのコード断片はプロジェクトに直接コピー&ペーストすることを意図していません。デモンストレーションとして扱ってください。

簡単のため、システム内の他のブロックはクロックもリセットも生成しないと仮定します。ただし、このコントローラを一般的なケースに拡張するのはかなり簡単です。

リセット用ステートマシン

ほとんどのデザインには、リセットが必要な主なシナリオが 2 つあります:

さらに、ウォッチドッグタイマがタイムアウトした場合、あるいは何らかの重大なシステム障害が他の方法で検出された場合にも、リセットが必要になることがあります。

これらすべてのシナリオに対して期待される応答は、根本的な再起動です。つまり、何が原因で失敗したかにかかわらず、それを是正することを保証する種類の再起動です。これは多くの場合、何らかのステートマシン(state machine)を使って行うのが最善です。ステートマシンにより、開始理由にかかわらず、一貫性があり再現可能なリセットシーケンスが保証されます。

とはいえ、局所的な、場合によっては反復的なリセットが理にかなうシナリオもあります。たとえば、ビデオ画像フレームを処理するロジックは、各フレームの開始前にリセットできます。これには通常、軽量なメカニズムが必要です。つまり、ローカルの同期リセット(synchronous reset)がアクティブになったときに、いくつかのレジスタに初期値を代入するだけです。これは他のリセットメカニズムと同じであり、堅牢な動作を保証するための、きれいで簡単な方法であることが多いです。

しかし、この可能性について付け加えることはあまりないので、このページの残りは、FPGA 全体を対象とする根本的なリセットに焦点を当てます。

不安定なクロックとリセットの必要性

クロックの生成に FPGA 自身の PLL を使うことは非常に一般的であり、通常推奨されます。しかし、これらの PLL は通常、FPGA 上の他のすべてのロジックがアクティブになると同時に動作を開始します。その結果、これらのクロックに依存するロジックには、不安定なクロックが供給されます。その周波数は通常よりも大幅に高くなることがあります。

これが起きると、関連するロジックのパス(path)のタイミングは保証されません。したがって、PLL によって生成されるクロックに依存するロジックは、PLL がロックされるまで、タイミング制約(timing constraints)を達成できなかったかのように扱わなければなりません。

適切かつ一般的な解決策は、関連する PLL がロックされるまで、そのようなロジックをすべてリセット状態に保持することです。あるいは、PLL がロックされるまでクロックを無視するように設定してもよいです。たとえば、フリップフロップのクロックイネーブル(clock enable、CE)入力をインアクティブにしておく方法があります。

外部クロックが FPGA のピンから論理要素へ直接接続されている場合、問題はクロックジェネレータ(通常は PLL)が FPGA にビットストリームが読み込まれるよりも速くロックしたかどうかです。これを確実に判断するのは必ずしも容易ではありません。

その結果、ほとんどのデザインでは多くの同期要素をリセットする必要があります。より正確には、不安定なクロックが原因でリセットが必要かどうかという問いについて、これらの要素は 3 つのグループに分けられます:

一部の FPGA では、FPGA のウェイクアップがすべての PLL がロックされるまで遅延されるように、コンフィグレーションプロセス(つまりビットストリームのロードと FPGA の初期化のプロセス)を設定できます。これはこの問題を解決する方法になり得ます。この解決策の欠点は、外部クロックに問題がある場合、FPGA がまったく起動しないことです。このような状況は非常に混乱を招く可能性があります。

シンプルなステートマシン

根本的な起動や再起動はかなりまれなイベントなので、必要以上に数マイクロ秒余計にかかっても問題になりません。多くの場合、100 ms 程度までなら問題ないことが多く、それを利用できます。したがって、単純なカウンタはイベントシーケンスを実装する簡単な方法です。

最も単純な形では、次のようになります:

reg [4:0] reset_count;
reg       rst_src_pll, rst_src_shreg, rst_src_debounce;
reg       clear_counter;
reg       master_reset;

initial reset_count = 0;
initial master_reset = 1;
initial clear_counter = 1;

always @(posedge wakeup_clk)
  begin
    clear_counter <= rst_src_pll || rst_src_shreg || rst_src_debounce;

    master_reset <= (reset_count != 31);

    if (clear_counter)
      reset_count <= 0;
    else if (reset_count != 31)
      reset_count <= reset_count + 1;
  end

@rst_src_pll、@rst_src_shreg、@rst_src_debounce は、システムをリセットするさまざまな理由を表しています。これらのレジスタには、他のロジックによって値が与えられます。そのようなロジックの例をいくつか見ていきますが、ここで重要なのは、これらのレジスタが @wakeup_clk に同期していることです(したがって、クロックドメインクロッシング(clock domain crossing)は必要ありません)。

@clear_counter は、これらのリセット理由の論理 OR です。このレジスタは @reset_count をゼロにします。そうでない場合、このカウンタは(この例では)0 から 31 まで数えて、その後停止します。

最後に、@reset_count が数え終わるまでの間、@master_reset がアクティブになります。これが最も単純なリセットステートマシンの形であり、31 まで数えるのは非常に控えめな回数です。

つまり、@rst_src_N 信号のいずれかがアクティブになると、それが 1 クロックサイクルだけでも、同期リセットは 31 クロックサイクルアクティブになります。

長いリセットパルスには 2 つの利点があります。第 1 に、関連する @rst_src_N がランダムにオンとオフを繰り返す場合(たとえば、PLL ロック検出器のふらつき、押しボタン、リセットを複数回要求するソフトウェアなど)、これらの複数回のアクティブ化は、見えるところの同期リセットには伝播しません。こうした複数回のアクティブ化は通常無害ですが、出力ピンに不要な動作を引き起こす可能性があります。たとえば、電子回路をテストしている人が何かおかしいと混乱するなど、悪影響を及ぼすことがあります。

その意味で、31 クロックサイクルの例はかなり最小限です。許容できるなら、10〜100 ms に相当する値まで数えるとさらに良いです。そうすれば、それより短いふらつきはリセットコントローラによって隠されます。

長いリセットパルスが必要な第 2 の理由は、元の同期リセットをローカルコピーへ分配する処理(前のページで説明)に 1 クロックサイクルの遅延が伴うからです。リセットをロジック全体に分配するために信号を複数回コピーする必要がある場合、合計遅延が長くなるだけでなく、不均一になる可能性もあります。長いリセットパルスがあれば、ある時点で確実にすべてのロジックがアクティブな同期リセットにさらされます。リセット経路に不均一な遅延があるのは、リセットの解除も不均一になるため、やはり良い考えではありません。ただし、それが問題にならないこともあります。その点、31 クロックサイクルのリセットパルスは、おそらく必要以上にかなり長いですが、害はありません。

他のクロックドメイン用のリセット

@master_reset は通常の同期リセットですが、アプリケーションロジックが使うクロックとは異なるクロックに同期しています。他のクロック用の同期リセットを生成するには、各クロックに対して次のようなことを行う必要があります:

reg reset_clk_pre1, reset_clk_pre2;
reg reset_clk;

always @(posedge clk)
  begin
    reset_clk <= reset_clk_pre2;
    reset_clk_pre2 <= reset_clk_pre1;
    reset_clk_pre1 <= master_reset;
  end

これは 3 段のごく普通のクロックドメインクロッシング(clock domain crossing)であり、@reset_clk を生成します。この信号は @clk に付随する同期リセットです。実際には 2 段で十分ですが、重要な信号なので、念には念を入れてレジスタを 1 段追加しました。

リセットコントローラのクロック

@wakeup_clk(つまりリセットステートマシン用のクロック)として使えるクロックは、原理的には 3 種類あります。

最初の選択肢が最も扱いやすいです。FPGA の PLL を駆動するリファレンスクロックはほとんどの場合あるので、このリファレンスクロックをそのままウェイクアップクロックとして使えます。ただし、FPGA が起動したときにクロックが実際に安定していること、つまり、FPGA がビットストリームを読み込むのにかかる時間が、外部オシレータが有効なクロックを生成するのにかかる時間より長いことを確認することが重要です。データシートを見れば、通常は大きなマージンがあります。しかし、ボードの電源投入シーケンスが適切に計画されていないと、オシレータの電源電圧が正しいレベルに達するずっと前に、FPGA にビットストリームの読み込みが許可されてしまう可能性が大いにあります。

第 2 の選択肢は、FPGA 自身の PLL が生成するクロックを使うことです。明らかな利点は、このクロックがアプリケーションロジックにも使われる可能性があるため、クロックリソースと電力の両方の点で効率的であることです。この選択肢では、クロックが安定するまでリセットステートマシンを初期状態に保持する必要があります。これは、次のようにして行えます:

reg rst_src_pll;
reg rst_src_pll_pre;

initial rst_src_pll = 1;
initial rst_src_pll_pre = 1;

always @(posedge wakeup_clk)
  begin
    rst_src_pll <= rst_src_pll_pre;
    rst_src_pll_pre <= !pll_locked;
  end

@pll_locked は @wakeup_clk を生成する PLL のロック検出器出力です(アクティブハイ)。この信号は非同期なので、最初に @wakeup_clk で同期化し、その後、上に示したとおり @clear_counter をアクティブにする理由のひとつとして使います(つまり @rst_src_pll として)。

この選択肢のもうひとつの利点は、リファレンスクロックが一時的に不安定または存在しない場合(特にボードの電源投入直後)、ロック検出器も不安定になる可能性が高いことです。したがって、@master_reset を解除する前に @reset_count が(31 よりはるかに大きい)大きな数まで数えるなら、リファレンスクロックが正常に動作するまで FPGA がしっかりとリセット状態に保持される可能性があります。ただし、これに依存することはできません。

いずれにせよ、@wakeup_clk が PLL の出力であるという事実は、このクロックが安定する前にロジックへ供給されることを必然的に意味します。したがって、その期間中にステートマシンが正しく機能するかどうかは明らかではありません。これは問題にならないと主張することもできます。なぜなら、いつかはクロックが十分に良くなり、その時点で適切なリセットが生成されるからです。リセット後にすべてが過去のものになるのなら、それ以前に何が起きても気にする必要はあるでしょうか?

より厳密なアプローチは、ウェイクアップクロックがロックされるまでリセットを安定してアクティブに保持し、FPGA がビットストリームのコンフィグレーション直後に奇妙な動作をしないようにすることです。これには、このクロックでは非常に単純なロジックだけを使うように注意する必要があります。つまり、クロックの周波数が予想より高くても大きくは失敗しない種類のロジックであるべきです。

@wakeup_clk が一時的に高すぎる周波数になった場合に何が起きるかを分析するには、@reset_count がリセットステートマシンの中で唯一のベクタ型レジスタであることに注意してください。これは、他のすべてのフリップフロップは、タイミング違反のために、計算された「次の値」をせいぜい 1 クロック遅くサンプリング(sampling)する可能性があることを意味します。特に、PLL がロックしていない間は @pll_locked がローなので、@rst_src_pll はすぐに安定してハイになり、したがって @clear_counter も安定してハイになります。フリップフロップの D 入力(次の値)が変化しないなら、クロックがどれほど速くても問題ありません。

したがって、唯一起こり得る問題は @reset_count です。計算された次の値が @clear_counter によって安定してゼロに保持されるまで、誤ってカウントアップする可能性があります。たとえば、現在の値が 3(2 進数 011)の場合、計算上の次の値は 4(2 進数 100)です。しかし、タイミングの問題で下位 2 ビットがサンプリングされず、3 ビット目だけがサンプリングされた場合、カウンタの値は代わりに 7(2 進数 111)へ飛ぶ可能性があります。

これを防ぐために、@pll_locked から @clear_counter までのレジスタチェーンの初期値はすべて、@reset_count を安定してゼロに保持する方向に割り当てられています。したがって、@wakeup_clk が安定するまで @pll_locked が安定してローを維持するなら(そうあるべきですが)、@reset_count はゼロから動かず、@master_reset は安定してアクティブのままになります。

最後の選択肢である FPGA のリングオシレータをリセットコントローラに使うことについて:私は自分で試したことがないので、それがどれほど良いアイデアかは確信がありません。しかし、他の選択肢がなくて困っている誰かの役に立つなら、Xilinx の FPGA ではだいたい次のようにします。FPGA の Configuration User Guide で STARTUPE2(またはそれに類する名前)というプリミティブ(primitive)を探してください。CFGMCLK という出力があるはずです。これは FPGA 自身の不正確なリングオシレータからのクロックです。周波数はおよそ 50〜65 MHz です。このクロックは FPGA が起動したときに安定していることが保証されており、タイミング制約はかなり高い周波数(たとえば 100 MHz)に設定するでしょう。

しかし、これは本当に他に選択肢がない場合にするでしょう。たとえば、FPGA 起動時に外部リファレンスクロックが安定しておらず、追加の遅延をロジックで実装する必要がある場合などです。

PLL のリセット

PLL をリセットシーケンスの一部としてリセットするのは、一般的に良い考えです。これにより、リファレンスクロックが有効であることが分かっているときに PLL がリセットされることが保証されます。また、リセットがユーザーによって開始された場合(たとえばリセットボタンを押すなど)、それは PLL が適切にロックされなかったことに起因する問題への対応かもしれません。そんなことは起こるべきではありませんが、万一のために備えておくのです。

@clear_counter は PLL のリセットには使えません。PLL がロックを失うとすぐにアクティブになるからです。これを使うと、PLL はリセット状態のままでロックされることがなく、リセットも解除されなくなります。同じ理由で、PLL のリセットを @reset_count から導出することもできません。@reset_count は、すべての PLL がロックされているとき以外はゼロに保持されます。

解決策は、PLL 用に @clear_counter と似た独立したリセットレジスタを作ることです。上記の表記を使って、ロックされていない PLL が @rst_src_pll によって表される場合、次のようになります:

reg clear_counter;
reg reset_plls;

initial clear_counter = 1;
initial reset_plls = 1;

assign reset_sources = rst_src_shreg || rst_src_debounce;

always @(posedge wakeup_clk)
  begin
    clear_counter <= rst_src_pll || reset_sources;
    reset_plls <= reset_sources;

[ ... ]

このコードでは、@rst_src_pll は @reset_sources から除外され、@clear_counter にのみ使われています。その結果、PLL は、PLL 自身がロックされていないことによる場合を除き、FPGA 全体と一緒にリセットされます。

@wakeup_clk 自体はリセットされる PLL によって生成できないことを明確にしておく価値があります。したがって、このクロックは他の可能性に基づいて生成する必要があります。

@reset_sources がランダムにローとハイを繰り返すなら、示したコードに従って @reset_plls もランダムに変化することに注意してください。PLL のリセットが激しくオンオフしても PLL に悪いことは起きないため、これは通常無害です。ただし、これを避ける方法もあります。次に説明します。

より複雑な起動シーケンス

単純なカウンタ(@reset_count)を状態変数として使うと、より複雑な起動シーケンスの実装が簡単になります。たとえば、クロックを停止した状態でアクティブになる適切な非同期リセット(asynchronous reset)を生成するのは非常に簡単です。クロックを停止する時間帯とリセットをアクティブにする時間帯を定義する単純な論理式で行えます。

したがって、この単純なカウンタ方式は、プロジェクト開始時にリセットステートマシンへの要求が単純に見えるデザインにとっても良い出発点です。後で複雑な起動シーケンスが必要だと判明した場合でも、既存のロジックを拡張してこの目標を達成するのは簡単です。

いずれにせよ、起動シーケンスの計画は、シーケンスの各フェーズが有効になる時間帯を定義することに帰着します。そのような各期間は、@reset_count が取るべき値の範囲に変換されます。

たとえば、デザインに 2 つ以上の無関係なクロック(unrelated clocks)がある場合、上に示した方法(「他のクロックドメイン用のリセット」を参照)で各クロックドメイン用のリセット信号を作成できます。しかし、その方法を採用すると、各クロックドメインは実質的にランダムな順序でリセットから復帰します。これは通常は問題になりませんが、問題になる場合は、@reset_count の進行に基づいて、各クロックドメインのリセットを定義された時刻に解除できます。

カウンタを複数実装することも可能です。これは、リセットシーケンスが、続行する前に特定の条件が満たされるのを待つ必要がある場合に役立ちます。たとえば、リセットシーケンスが PLL のリセット、PLL のロック待ち、そしてリセットシーケンスの続行を含む場合、すべての PLL がロックされるまでゼロに保持されるカウンタを 1 つ用意するのが理にかなっています。2 つ目のカウンタは、最初のカウンタが数え終わるまでゼロに保持されます。

PLL がロックを失った場合、PLL 自身はリセットされませんが、PLL に依存するものはすべてリセットされます。これは望ましい動作ではないかもしれません。PLL がロックを失うことは、ほとんどのデザインでは重大な障害だからです。ロック喪失時に完全なリセットを要求するには、次に定義する @pll_restart を、@reset_sources を得るために OR でまとめられる信号群に追加します:

assign pll_restart = rst_src_pll && !master_reset;

これは単純にこう言っています。「マスタリセットがインアクティブになった後に PLL のロックが外れたなら、PLL も含めてすべてをもう一度リセットする。」これが機能するには、リセットカウントがロック検出器の可能性のあるふらつきに耐えられるだけ長くなければなりません。つまり、リセットカウントは、PLL がまだロックしていないのにロック検出器がハイになる可能性がある時間より長くなければなりません。これは、PLL がロックを取得するのにかかる時間と混同しないでください。ロック検出器の中にはまったくふらつかないものもありますし、これらのふらつきは、おそらくデータシートに指定されている PLL のロック時間よりも大幅に短いでしょう。

ウェイクアップシフトレジスタ

少しやり過ぎかもしれませんが、私はこの種のウェイクアップシフトレジスタをデザインに追加することがよくあります:

reg [15:0]   wakeup_shift;
reg          rst_src_shreg;

initial rst_src_shreg = 1;
initial wakeup_shift = 0;

always @(posedge wakeup_clk)
  begin
    rst_src_shreg <= !wakeup_shift[15];
    wakeup_shift <= { wakeup_shift, 1'b1 };
  end

ほとんどの FPGA は @wakeup_shift をシフトレジスタプリミティブとして実装し、これは LUT 1 個分に相当するリソースしか消費しません。したがって、リソース的には安価であり、電源投入時にリセットが確実に行われるためのもうひとつのメカニズムを提供します。これは PLL のないデザインでは必要です。なぜなら、@clear_counter をアクティブにするものが他にないからです。しかし PLL がある場合でも、FPGA のビットストリームコンフィグレーションのオプションによっては、FPGA が起動した時点で PLL がすでにロックしている可能性があります。

いずれにせよ、これは推奨される追加要素です。念には念を入れすぎることはありません。

外部リセットボタン

リセットボタンは非常に一般的です。その動作が何をするかはデザインによって異なります。ひとつの可能性は、ユーザーが「リセット」だと思っているボタンが、FPGA にビットストリームの読み込みプロセスを開始させる FPGA ピンに接続されていることです。また、組み込みプロセッサを持つ FPGA では、プロセッサのリセットピンに接続されているかもしれません。

そして、このリセットボタンは、FPGA ロジックをリセットする目的で FPGA の汎用 I/O ピンに接続されている可能性もあります。この場合、@clear_counter をアクティブにするさらにもうひとつの理由になります。

@reset_count が 10 ms 以上に相当する数まで数えるなら、入力ピンからの信号のデバウンス(debounce)は不要です。ふらつきはカウンタ自体が吸収するからです。この場合、これで十分です:

reg rst_src_debounce;
reg rst_src_debounce_pre;

initial rst_src_debounce = 1;
initial rst_src_debounce_pre = 1;

always @(posedge wakeup_clk)
  begin
    rst_src_debounce <= rst_src_debounce_pre;
    rst_src_debounce_pre <= reset_button_pin;
  end

しかし、カウンタがすぐに数え終わる場合(上の例のように 31 までしか達しない場合)、押しボタンのデバウンスが必要です。これにはいくつかの方法があります。たとえば:

reg [17:0] debounce_count = 0;
reg        reset_button_d, reset_button_d2, reset_button_d3;

wire       debounce_reached = (debounce_count == 250000);

initial debounce_count = 0;
initial rst_src_debounce = 1;

always @(posedge wakeup_clk)
  begin
    reset_button_d3 <= reset_button_d2;
    reset_button_d2 <= reset_button_d;
    reset_button_d <= reset_button_pin;

    if (reset_button_d2 != reset_button_d3)
      debounce_count <= 0;
    else if (!debounce_reached)
      debounce_count <= debounce_count + 1;

    if (debounce_reached)
      rst_src_debounce <= reset_button_d3;
  end

これは比較的厳格なデバウンサであり、ノイズの多い入力信号にはうまく機能しません。これは、ニーズによっては利点にも欠点にもなります。

このコード例は 25 MHz クロック用に書かれています。@reset_button_pin が 10 ms の間同じ値を維持した場合、その値が @rst_src_debounce にコピーされます。@debounce_count は、@reset_button_d3 の値が変化したのと同じクロックサイクルでゼロに変化することに注意してください。したがって、@reset_button_d3 の値が変化した場合、その値はすぐには @rst_src_debounce にコピーされず、同じ値が長時間維持された後にのみコピーされます。

まとめ

FPGA の初期化を設計する際には、考慮すべき多くのことがあります。このページではいくつかの概念とアイデアを示しました。しかし、実際のタスクは、動作を開始する必要があるイベントを正しく認識することであることを忘れないことが重要です。また、ロジックを確実に動作状態へ導くために、そのような各イベントへの正しい応答を定義することも重要です。

このページは英語から機械翻訳されたものです。不明な点があれば、原文を参照してください。
Copyright © 2021-2026. All rights reserved. (dcc38493)