uchan が 2021年09月18日11時30分40秒 に編集
初版
タイトルの変更
FPGAでスイッチ入力を受けるときにregを使うと安定する
タグの変更
FPGA
メタステーブル
verilog
本文の変更
FPGA でスイッチ入力を受け取るときに、そのまま受け取るのではなく、reg を経由すると安定することを実験で確かめました。 ## スイッチ接続回路 今回使った FPGA ボードは [Tang Nano](https://tangnano.sipeed.com/en/) で、スイッチは次の回路によって FPGA に接続されています。 ![Tang Nano のスイッチ接続回路](https://camo.elchika.com/bb9a0ae65bf49ce8899e868178ef9b86863a38b7/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f63333936313234302d643365342d346361652d396632662d3965396366383634616562342f36366430356363662d633864382d343437322d613765342d366564316331613334656364/) 上記の回路図は [Tang Nano の回路図](http://dl.sipeed.com/TANG/Nano/HDK/Tang-NANO-2704(Schematic).pdf) から抜粋しました。チャタリングを緩和するために 0.1μF のコンデンサと 10kΩ の抵抗が接続されています。 ## 動作の様子 Tang Nano には赤・青・緑の 3 色フルカラー LED が搭載されていて、各色の ON/OFF を切り替えることで 2^3 = 8 通りの色を作り出せます。今回作った実験プログラムは、スイッチを 1 回押す毎に、LED の点灯状態が「緑→青緑→青→赤→赤緑→赤青→白→消灯」と切り替わることを意図しています。 reg 無し版の動作 @[youtube](https://www.youtube.com/watch?v=SsHKBSELGqI) reg を使わない場合、スイッチ B を 8 回クリックしても点灯状態が 1 巡しません。1 巡しないというか、1 回のクリックが複数回のクリックに誤認識されているように見えます。そして、押したときだけ反応して欲しいのに、離したときも反応していますね。とても不安定です。 reg 有り版の動作 @[youtube](https://www.youtube.com/watch?v=OfWtmWXYZWQ) reg を経由してスイッチ信号を扱うようにした場合、意図通り 8 回のクリックで 1 巡するようになりました。かなり安定しています。 ## 実験プログラム 実験に用いた Verilog プログラムを紹介します。まずは reg を使わないバージョンです。 ```Verilog module Counter( input sys_clk, input rst_n_async, input swb_n_async, output [2:0] led_n ); reg [2:0] led; assign led_n = ~led; always @(negedge swb_n_async or negedge rst_n_async) begin if (!rst_n_async) led <= 3'd0; else led <= led + 3'd1; end endmodule ``` `swb_n_async` はスイッチ入力を受け取る信号名です。回路図の IOB3B に繋がっています。`_n` は信号が論理反転していることを、`_async` はスイッチが非同期入力であることを表します。ちなみに `rst_n_async`も同様のスイッチ入力で、こちらは回路のリセット信号として使っています。 `led` はフルカラー LED です。`led[0]` が緑、`led[1]` が青、`led[2]` が赤に接続されています。`negedge swb_n_async` のときに LED の点灯状態を切り替えるようにしているので、スイッチを押したときには切り替わり、離したときは何も起きないで欲しいです。しかし、現実はそうはなりませんでした。 次に reg を使うバージョンを示します。 ```Verilog module Counter( input sys_clk, input rst_n_async, input swb_n_async, output [2:0] led_n ); reg rst_n; reg swb_n; always @(posedge sys_clk) begin rst_n <= rst_n_async; swb_n <= swb_n_async; end reg [2:0] led; assign led_n = ~led; always @(negedge swb_n or negedge rst_n) begin if (!rst_n) led <= 3'd0; else led <= led + 3'd1; end endmodule ``` 非同期入力である `swb_n_async`を、クロックの立ち上がりで `swb_n` に書き込み、以降の部分では `swb_n` を使うようにしました。その他は reg 無し版と同じです。 ## メタステーブル FPGA に詳しいわけではないのですが、これはおそらく「メタステーブル」という状態が発生しているのだと思います。非同期信号を同期回路で扱う場合に発生しがちな状態だそうです。 メタステーブルは reg を挿入することで緩和できるそうで、今回のスイッチ入力もバッチリ改善しました。ただ、挿入する reg が 1 つで足りる保証は無いようで、場合によっては 2 個、3 個と挿入する必要があるとのことです。今回は 1 個で十分でしたが、不安定なら reg を増やしてみたら如何でしょうか? [Tang Nano の開発元が提供するサンプルプログラム](https://github.com/sipeed/Tang-Nano-examples/blob/master/example_led/led_prj/src/led.v) ではスイッチ入力をそのまま利用しているのですが、これはリセット信号だからなんとかなっているものの、一般のスイッチ入力では通用しないことが分かりました。