はじめに
先日 FPGAのリセットについての考察 - Ryuz's tech blog という記事を書きました。
アクセス解析見てみると意外とアクセスが多いページのようで、まじめに普通のFPGAのリセットの話を知りたくて来られた方に申し訳ないので、少しだけそういう話も書いてみます。
リセットの取り扱いはセンシティブな上、微妙に流派や文化もあったりするので、下手なこと書くとマサカリ飛んで来そうではあるのですが、私の知っている範囲のことを FPGAに限定して、なるべく無難に書いてみます。
FPGA でリセットを考えるのは大きく2つ、外部からのリセット信号アサート時(リセットボタンやソフトウェアリセットなど)と、コンフィギュレーション完了時(パワーオン時)の初期化があります。
なお、FPGA のデバイス構造には SRAM 型や Flash 型などがあったりしますが、ここでは基本 SRAM 型の話をします。
SRAM型の FPGA では、動的再構成の為の情報を揮発性のSRAM構造で保持しますので、パワーオン時などに Flash-ROM などからコンフィギュレーションを実施する必要があります。
外部からのリセット信号アサート時
先にリセット信号アサート時の話をします。ASIC設計等ではこちらしかなく、パワーオン時も通常は基板上の電源回路側のパワーオンリセット回路が働き、リセット信号がアサートされますので、「とにかくリセットが来たら初期状態になる事」が必要です。これがそのまま動作中のリセットにも使えます。
この時しばしキーワードとなるのが、「非同期リセット」と、「同期リセット」と、「メタステーブル」です。
メタステーブルについては下記に記事を書いております。
メタステーブルについて考えてみる - Ryuz's tech blog
非同期リセット
SystemVerilog における非同期リセットの書き方は下記の通りで、クロックとリセットのイベントを or で書きます。
always_ff @(posedge clk or negedge reset_n ) begin if ( ~reset_n ) begin hoge <= リセット値; end else begin // なんか処理 end end
非同期リセットの特徴は、クロックが来ていなくても初期化できる ことと、リセット解除に関しては、クロックエッジとの関係次第で、メタステーブルに突入することです。
外部から来るリセット信号は、しばしクロックとはなんのタイミング相関もない非同期信号です。したがって非同期リセットを使う場合でも リセット解除だけはクロック同期させる という事が行われます(AXIバスの aresetn 信号なんかも規格上はそうだったと思います)。
どうしてもクロックなしでも初期化しないといけないシーンは、周辺回路やPLL周りなどで必要になります。 また非同期リセットの ASIC の回路を 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が向上するようです。
同期リセット
SystemVerilog における同期リセットの書き方は下記の通りで、クロックのみがイベントとして書かれます。
always_ff @(posedge clk ) begin if ( reset ) begin hoge <= リセット値; end else begin // なんか処理 end end
多くのFPGAではなるべく同期リセットで記述することが推奨されています。
そして同期リセットの場合は、リセットがクロックに同期していないと、リセット発生時も解除時もメタステーブルを起こす可能性があります。
外部から来るクロックに同期していないリセットを負論理の reset_n とし、 セットも解除も clk に同期した reset (正論理)を生成します。
(* ASYNC_REG = "true" *) logic [1:0] sync_ff = 2'b11; always_ff @(posedge clk ) 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];
こちらはクロックが入らないとリセット入力反映されないという点は注意しておく点です。
FPGA内のFFには通常専用のリセット端子がありますが、非同期で使う場合はそこだけ専用にタイミング解析とタイミング調整を行う必要が出てきて、配置配線の難易度が上がるため、FPGAではすべて同期で使うのが効率の良い結果を得やすいという理由があるようです。
コンフィギュレーション完了時のリセット
こちらは完全に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 固有部分は割り切って限定的に扱うのが安全かとは思います。
おわりに
こういうことがいい書き方なのかどうかは正直自信は無いのですが、とりあえず私はこんな感じでリセットを扱っていますというのがご紹介できればと思った次第です。
もっといいリセットの扱い方があるよとか、これだとトラブルが起こるよ、とかあれば情報お寄せいただけると嬉しいです。
追記:FPGAが同期推奨な理由について
AI に聞いたりしているので嘘もあるかもですが、どうやら Dフリップフロップにリセットを付ける場合、非同期か同期かで増加するトランジスタの数が若干違う場合はあるようです。 また、リセットはファンアウトが大きくなりがちですので、リセット解除をLSI全体にタイミングがずれないように行うためにクロックツリーだけでなくリセットツリーも考える必要があるようです。
FPGAの場合残念ながらハードウェア的に提供されているクロックツリーに対して、後から任意の回路にタイミングを合わせるリセットツリーをコンフィギャラブル回路だけで何とかするのは難しいらしく、同期回路として他の信号と同じようにリセットを扱うのが性能を出すには最適解なようです。
そもそもFPGAの場合、LUTやルーティングスイッチなどの比率が大きい為、フリップフロップにリッチなものを置いてもあまりインパクトが無いという事情などもあるようです。
ですので、おそらく ASCI の回路をそのまま FPGA に持ってくると、リセットの観点でも効率が悪く、周波数が下がってしまったりなどがあるのではないかと想像する次第です(リセットの同期/非同期の切り替えが簡単にできる Veryl はこの辺りを意識したものなのだと思います)。
