


FPGAが時々動かない??-ステート・マシンのデッドロック-

FPGAにおけるステート・マシン(FSM)のエンコード方式は?

今回は、異なるクロック間でのデータ受け渡し(Clock Domain Crossing: CDC)での注意点を解説します。よくやってしまいがちなミスを挙げ、NGな理由と正しい方法について解説します。FPGAを安定して動作させるためにとても重要です。
目次
周期が短いクロックCLK1から、周期が長いクロックCLK2へ、パルス信号を受け渡す際、何の対策もしないと、CLK2側でパルスを取り逃がすことがあります(図1)。
そこで、図2、図3のようにORゲートを使ってパルスを伸ばし、周期の長いクロックCLK2でも受け取れる長さにする方法が考えられます。
フリップ・フロップで、D1に対して1サイクル遅れたD2を作り、D1とD2のORを取ることで、パルスを2サイクル分に長くしています。パルスが長くなったので、周期が長いCLK2でも受け取れる…なんだかうまくいきそうですよね?
しかし、図2の回路ではCDCはうまくいかないときがあります。図2の回路で起こりうる波形を図4に示します。
実際のFPGAでは、D1とD2の経路の長さは同じではありません。したがって、D1のデータがCLK2のフリップ・フロップに到達する時間と、D2のデータがCLK2のフリップ・フロップに到達する時間は異なります。
もし、D2のほうが遅れて到達すると、D3は図4のようにラクダのコブのような波形になります。CLK1とCLK2の位相関係はどうなっているかわからない(それが非同期)ので、コブの谷のところでCLK2側がデータをラッチすることが起こり得ます。すると、図4のように、パルスを取り逃がしてしまいます。
これ、ホントに起こります。
しかも、データの到達時間は、デバイスの個体や温度などによってばらつきます。したがって、複数作った基板のうち、1枚だけうまく動かないとか、冷やした時だけうまく動かないとかが発生します。
こういう不具合は、再現性が低いためデバッグがかなりしんどいです。図2のような回路は避けるようにしましょう。
上記のような不具合を防ぐためには、図5のような回路にします。CLK1側の出力はフリップ・フロップ出力にします。
D3のところはラクダのコブになる可能性があります。しかし、それを同じクロックCLK1でいったん受け取ると、図6のD4のように、ちゃんとデータが受け取れます。なぜなら、同じCLK1なので、論理合成ツールがセットアップ時間、ホールド時間が満たされるように頑張って(配置・配線して)くれるからです。
すると、CLK2のフリップ・フロップからはちゃんと長くなったパルスD4が見え、パルスを受け取ることができます。
パルスに限らず、組合せ回路出力の信号を別のクロックで受け取ると、同様の問題が発生します。CDCでは、必ずフリップ・フロップ出力にしましょう。