リセットについての考察 の履歴(No.5)
更新- 履歴一覧
- 差分 を表示
- 現在との差分 を表示
- ソース を表示
- 電気回路/HDL/リセットについての考察 へ行く。
リセット信号の扱い†
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 の動作が分かるブロックダイアグラムがある
同期リセットと非同期リセット†
良く知られるように、リセット信号を持つ回路の構成方法には、 非同期リセットと同期リセットがあって、 たとえば ISE の Language Template にも両方の形が収録されています。
同期リセットと非同期リセットのどちらが回路規模的にお得かは、 ASIC に実装するか、どのメーカの FPGA に実装するか、 などにより変わってくるそうです。
ASIC ではリセットを使う回路と使わない回路とでは実際にゲート1つ分の 回路規模と伝達遅延が差として現れるため、どのラッチにリセットを付けて、 どこに付けないかは、回路規模およびパフォーマンスに直に効いてきます。
これに対して(Xilinx の)FPGA では全ての(通常の) FF に同期リセットあるいは 非同期リセット用に使える SET/RESET (FDRSE: 同期) あるいは PRE/CLR (FDCPE: 非同期) 入力が始めから備わってますので、ここに直接リセット信号を繋ぐ限り、 演算器の回路規模や回路遅延への悪影響はありません。 (もちろんリセットネットの配線リソースは消費します)
ただこれにはいくつか制限があって、以下のような事が考えられます(wp272より抜粋)。
- 配線リソース
- 多くの場合、リセットネットにはかなりの量の配線リソースが消費される
- リセットネットに要求される最大遅延量を満たすために、他の信号ネットの遅延が増加する場合がある
- リセットネットを張るためにルーティングにかかる時間が増加する
- ロジックリソース
- FF に内蔵されたリセット・クリア機能が使えれば余計なロジックは消費されない
- 1つのスライスに含まれる2つの FF へのリセット線は共有されるため、 2つの FF が異なるリセット信号を持つ場合には1つのスライスに入れられなくなる
- リセット・セットあるいはクリア・プリセットと、クロックイネーブルとの 信号線の複数が同時にアサートされたときの優先順位はプリミティブに固有なので、 それに沿わない優先順位で HDL を記述すると余計なロジックが挿入されて、 速度や回路規模に悪影響を与えることになる
- リセット用のロジックのために PAR にかかる時間が増加する
- FF 自体を削減する、という最適化が働かなくなる
- FF がリセットを持たない場合には多数の FF を SRL16E を使って実装することで 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 のこと
コンフィグレーション直後の値と、リセット直後の値とを異なる値にするのは 余程の事情があるときだけだと思うので、それ以外では非同期リセットの代わりに 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 が同期化されていない場合の問題が明らかになります。
- rst = 1 が数クロック続いて state == 4'b0001 になる
- rst が 0 に変化する時刻は、ネット遅延により、各ビットで少しだけ異なる
- 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 による再初期化をエミュレートするために
- GSR というダミーの信号線を用意する
- 論理合成時には
- この信号線はどこからもドライブされないためゼロのままになる
- if (GSR) というような条件式は決して成立しないため、最適化により削除される
- シミュレーション時には
- 上位のモジュールから reset_gen.GSR に glbl.GSR を繋ぐことで GSR をアサートする
- if (GSR) 部分のコードで FF の初期化をエミュレートする
という方針になります。
LANG:verilog module reset_gen #( parameter COUNTER_BITS = 8 ) ( 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) ); wire GSR; // ドライブ源がないので Synthesis 時には無視されるダミーの信号線 // 同期リセット信号の生成 reg [COUNTER_BITS-1:0] count = 0; always @(posedge GSR or posedge clk) begin if (GSR) 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 をドライブする。
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 ( .async_rst(async_rst), .clk(clk), .rst(sync_rst) ); `ifdef simulation assign reset_gen.GSR = GSR; // 下位モジュールに GSR を配信する `endif ... 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) によるダミーの初期化文を記述しなければならない
- reg signal = initial_value; という部分と、if (GSR) signal <= initial_value;
の2カ所に初期値を記述しなければならず、DRY (Google:Don't repeat yourself)
の原則に反する
もちろん、1つ定数を用意してそれを2カ所で使えば原理的には問題ないのだけれど - 複雑な階層の末端のモジュールまで、テストベンチから GSR 信号を届けなければならず、 その部分の記述も煩雑
また、合成時に出る
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 信号が信頼できるようなら、 クロックの安定度については心配ないのかもしれません?
ユーザーロジックを使ってさらに延長することを試みる†
こういうことが問題になる背景には、GSR がアサートされている限り通常の FF を使えないため、 例えば「DCM の LOCKED がアサートされてから 200 クロック後にリセットを解除する」 というようなロジックを組むのに工夫がいるという点があります。
通常の FF の代わりに LUT をシフトレジスタとして使うことのできる SRL16 を利用してカウンタを構成することにより回避しようと考えました。
以下がその考察です。
GSR 中にも動作するカウンタ回路†
次の回路により GSR 中かどうかにかかわらず N クロック ( N <= 16 ) に一度、 トリガを出すことができます。
シフトレジスタを用いてワンホットカウンタを構成しています。
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) );
より安全なカウンタ回路†
上記のカウンタは parameter の指定により任意の周期を簡単に作れるのが良いのですが、 パワーオン直後などクロックが不安定な状況で、 万が一ホットビットが失われてしまうと、 まったくトリガを発しないまま、永久に復帰することができません。
より安全な回路として、周期は 32 固定になってしまいますが SRLC16 を使ったジョンソンカウンタを作ってみました。
これならば万が一起動がうまく行かなくても、最悪周期がおかしくなるだけで済みます。
また init 入力を 16 クロック間アサートすることで、 カウンタを正常な状態に戻せるようになっていますので、 本当に最悪の場合には手動でリセットが効きます。
というわけで、かなり安全に使えるんじゃないかと思います。
LANG:verilog(linenumber) // GSR 中にも動作する16ビットジョンソンカウンタ (32クロック周期でトリガを発生) module srl16_pulser32 ( input wire clk, input wire en, input wire init, // 16クロック間アサートすることで初期化 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 が解除されるコードを書いてみました。
LANG:verilog(linenumber) module gsr_gen #( parameter EXTENTION = 10 // x 32 クロックだけ延長する ) ( input wire async_rst, input wire clk ); wire GSR; // ドライブ源がないため論理合成時には無視されるダミーの信号線 // GSR 状態を検出する reg GSR_active = 1; always @(posedge GSR or posedge clk) if (GSR) begin // GSR 動作をエミュレート // 合成時には最適化により削除されるので初期値は別途指定しなければならない 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 出力 wire GSR_o = async_rst | ( GSR_active && gsr_extended ); // GSR により すべての FF に非同期リセットがかかる STARTUP_SPARTAN3 STARTUP_SPARTAN3A_inst ( .CLK(), // Clock input for start-up sequence .GSR(GSR_o), // 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) ); endmodule
同期リセット信号を生成†
GSR とは独立に動作するので切り離しました。
LANG:verilog(linenumber) module reset_gen #( parameter COUNTER_BITS = 8 ) ( input wire async_rst, input wire clk, output reg rst = 1 ); wire GSR; // ドライブ源がないため論理合成時には無視されるダミーの信号線 // GSR 後 2**COUNTER_BITS だけ経ってから rst を下げる reg [COUNTER_BITS-1:0] count = 0; always @(posedge GSR or posedge clk) begin if (GSR) 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 信号を合わせて入れることで、 パワーオン直後、クロックが安定してからさらに数百クロック後に GSR を解除し、 さらにその後数百クロック後に同期リセットを解除する回路になっています。
GSR のネット遅延が FPGA 全体でどのくらいになるのか記述を見つけられなかったのですが、 さすがに数百クロック待っても伝わらないなどということは無いと思うので、 本当に非同期で構わない部分のみ GSR で初期化し、残りを同期リセットで初期化するという 方針で危険はないんじゃないかと思っています。
LANG:verilog module top_module( ... input wire async_rst_n, // アクティブ・ロー ... ); ... wire GSR; // ドライブ源がないため論理合成時には無視されるダミーの信号線 ... // GSR を生成 gsr_gen gsr_gen ( .async_rst(async_rst), .clk(clk) ); `ifdef simulation assign gsr_gen.GSR = GSR; `endif // 同期リセット信号を生成 wire sync_rst_pre; reset_gen reset_gen ( .async_rst(!async_rst_n | !dcm_locked), .clk(clk), .rst_out(sync_rst_pre) ); `ifdef simulation assign reset_gen.GSR = GSR; `endif // 同期リセット信号をグローバルな高速ネットを使って配信 wire sync_rst; BUFG sync_rst_buf ( .I(sync_rst_pre), .O(sync_rst) ); ...
実際の回路の構成としては、
上記の要領で if (GSR) の形で、 あたかも非同期リセットが掛かっているかのような記述をしながら、 実際には reg の初期値で非同期リセットを実現しつつ、
どうしても同期リセットが必要な部分には sync_rst をそのまま、 あるいはこれをローカルに別のクロックに同期したリセット信号を作成し、 同期リセット回路に入力することで実装しようと考えています。
同期リセット信号の配信にはグローバルな高速配線リソースを使っているので、 ローカルな配線リソースを圧迫することはなく、また、 ネット遅延によりクロック周期を圧迫することも少なくできると思います。
未接続の wire GSR で非同期リセットを疑似的に実装している部分について†
ソースコードのポータビリティなどを考えると、 GSR による FF の初期化の部分をわざわざ上記のようなおかしな記述にしなくても、 (GSR による)非同期リセットを用いている全てのモジュールの入力リストに input wire async_rst という信号線を用意しておいて、 ローカルには wire GSR の代わりにこの信号線で初期化するコードを書いておき、
実際の論理合成ではトップモジュールでこれを未接続にしておけば、
- 論理合成で最適化により省略される状況は変わりませんし
- シミュレーション時にネットを接続するのが簡単ですし
- GSR を使えない状況に対応するためのポータビリティも確保できますし
良いことづくめな気がしてきました。
上記ソースも後でこの方針で書き換えるかもしれません。
注意点†
ここまで読んでいただいた上で いまさらなのですが、 まだ実機では試せていませんので、話半分で(!)お願いします(汗
コメント†
リセットの取り扱いについて†
[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 はネット遅延が規定されていないので手動リセット回路としては使わない方が良いという記述がちらほらと。これは非同期であることを正しく考慮できるなら気にしなくても良いのかもしれませんが・・・ いずれにせよ、また考え直さないと行けないみたいです。 -- [武内(管理人)]