はじめに
先日 FPGAのリセットについての考察 - Ryuz's tech blog という記事を書きました。
アクセス解析見てみると意外とアクセスが多いページのようで、まじめに普通のFPGAのリセットの話を知りたくて来られた方に申し訳ないので、少しだけそういう話も書いてみます。
リセットの取り扱いはセンシティブな上、微妙に流派や文化もあったりするので、下手なこと書くとマサカリ飛んで来そうではあるのですが、私の知っている範囲のことをなるべく無難に書いてみます。
FPGA でリセットを考えるのは大きく2つ、外部からのリセット信号アサート時(リセットボタンやソフトウェアリセットなど)と、コンフィギュレーション完了時(パワーオン時)の初期化があります。
なお、FPGA のデバイス構造には SRAM 型や Flash 型などがあったりしますが、ここでは基本 SRAM 型の話をします。
SRAM型の FPGA では、動的再構成の為の情報を揮発性のSRAM構造で保持しますので、パワーオン時などに Flash-ROM などからコンフィギュレーションを実施する必要があります。
外部からのリセット信号アサート時
先にリセット信号アサート時の話をします。ASIC設計等ではこちらしかなく、パワーオン時も通常は基板上の電源回路側のパワーオンリセット回路が働き、リセット信号がアサートされますので、「とにかくリセットが来たら初期状態になる事」が必要です。これがそのまま動作中のリセットにも使えます。
この時しばしキーワードとなるのが、「非同期リセット」と、「同期リセット」と、「メタステーブル」です。
メタステーブルについては下記に記事を書いております。
メタステーブルについて考えてみる - Ryuz's tech blog
非同期リセット
外部から来るリセット信号は、しばしクロックとはなんのタイミング相関もない非同期信号です。FPGA は基本的に同期リセット推奨ですので、なんとかこれをクロックに同期したリセット信号に変換する必要があります。
下記が、SystemVerilogでの非同期リセットを同期リセットに変換する記述例です。
外部から来るクロックに同期していないリセットを負論理の reset_n とし、 clk に同期した reset (正論理)を生成します。
(* ASYNC_REG = "true" *) logic [1:0] sync_ff = 2'b11; always_ff @(posedge clk or negedge reset_n ) begin if ( ~reset_n ) begin sync_ff <= 2'b11; end else begin sync_ff <= {sync_ff[0], 1'b0}; end end assign reset = sync_ff[1];
ここで2つ FPGA固有の記述が混ざっていて、本質では無いのですが気になる方もおられると思うので先に注釈しておきます。
(* ASYNC_REG = "true" *)
は、AMD の Vivado などのシンセサイザで、非同期をうまく扱ってくれる属性logic [1:0] sync_ff = 2'b11;
としているのは、コンフィギュレーション完了時の初期値
です。
本題に入ると always_ff のイベントで or negedge reset_n
としている部分が、非同期リセットの指示です。
クロックの立ち上がりだけでなく、reset_n が 0 になったら即座に評価するように指示します。このクロックに関わらず if 文の中が実行される書き方が非同期リセットで、実際にFPGA内のFF(フリップフロップ)の非同期リセット端子が利用されるようにコンフィギュレーションされます。
そして sync_ff
で2つのFFを駆動しています。これはメタステーブル対策です。フリップフロップの非同期リセットは、リセットがアサートされた際はメタステーブルを起こすことなくリセットされるのですが、リセット解除とクロックエッジが立ち上がった瞬間が重なるとメタステーブルを起こす場合があります。
そこで、sync_ff[0] が、メタステーブル状態になっても、それが収まるまで1クロックサイクル待って sync_ff[1] に取り込むようにすることで対策するわけです。
なお、前述の ASYNC_REG は AMD のシンセサイザの場合、メタステーブルを起こす可能性のあるFF間を、メタステーブルがさらに伝搬することが無いように極力短くルーティングしてくれるように働くとのことです。
なお、万全を期したい場合はFFは2段ではなくさらに複数段入れるとメタステーブルによるMTBFが向上するようです。
同期リセット
上のように、同期リセットを生成してしまえば、他の個所ではこのように、同期リセットで記述するのが FPGA では推奨されています。
基本的にFPGA は同期設計を基本としているので、非同期的なものはなるべく入り口で取り払って、同期回路で書くのが望ましいです。
logic [31:0] a; always_ff @(posedge clk ) begin if ( reset ) begin a <= 0; end else begin a <= a + 1; end end
FPGA内のFFには通常専用のリセット端子がありますが、それでも同期リセットは他の信号と同様に論理圧縮の対象に加えられますので、FPGAとしてはFFのリセット端子をどう使うかも含めて、効率的な論理合成が出来るのだそうです。
コンフィギュレーション完了時のリセット
こちらは完全にFPGA固有です。一方で、FPGAではリセットボタンなどがついていない基板設計がなされることもあり、コンフィギュレーション完了時以外に「初期化」のタイミングが存在しない使い方もあります。
しかし、しばしそんな場合でも内部のリセット信号をしばらくアサートしたいなんてことがよくあります。
「IP部品の中にもリセットしたままクロックをXXサイクル以上入れて初期化すること」などと書かれているものもあったりするためです。
筆者がよくやるのは下記のような回路です。
logic [7:0] counter = 8'hff; logic reset = 1'b1; always_ff @(posedge clk) begin if ( counter > 0 ) begin counter <= counter - 8'd1; end reset <= (counter > 0); end
ようするに、FPGAではコンフィギュレーション時に限りレジスタの初期化が出来てしまうのをいいことに、カウントダウンしてリセットを作っているわけです。
もちろんこんな回路を ASIC や CPLD に持っていくとエライことになりますので、FPGA 固有部分は割り切って限定的に扱うのが安全かとは思います。
おわりに
こういうことがいい書き方なのかどうかは正直自信は無いのですが、とりあえず私はこんな感じでリセットを扱っていますというのがご紹介できればと思った次第です。
もっといいリセットの扱い方があるよとか、これだとトラブルが起こるよ、とかあれば情報お寄せいただけると嬉しいです。