非同期信号を扱うための危ういVerilogライブラリ のバックアップ(No.2)

更新


公開メモ

ご注意

※ 以下は練習のために書いてみた回路です。
しっかりしたテストを経ていないので、鵜呑みにしないで下さい。
あまり役に立たない個人的なメモになってしまっていてすみません。

クロックに同期していない信号を扱う際の注意点

デジタル回路設計に於いて、クロックに同期していない信号を扱う必要があるとき、 十分な注意を払わないと、予想しない結果が生じて慌てることになります(なるそうです・・・)。

では何に注意すればよいかというと、

  • メタステーブル状態の伝搬を考慮する

これに尽きます。

メタステーブル状態とは簡単に言うと、 あるフリップフロップ(FF)の出力が1つの値に定まらず、 ふらついている状況を表す言葉です。

どうしてそんなことが起きるのかというと、、、

例えばDフリップフロップ(D−FF)は、 供給されるクロック信号の立ち上がりエッジ(あるいは立ち下がりエッジ)に 同期したタイミングで入力信号を読み取り、次のクロックエッジが来るまで 出力にその値を出し続けるという動作をします。

このように書いて分かるように、理想的には、エッジの瞬間の値を読み取って、 エッジの直後から正しい値を出力するのがD−FFの動作です。

しかし実際のFFは「瞬間」の値を読み取れるわけではないため、 エッジの前後の短い時間の間に入力信号が変化してしまうと、 正しい値を読み取れず、出力値が不定になってしまいます。

そこで、FFを正しく使うための指針として、 クロックエッジ前後の短い時間を入力信号の変化を禁止する時間として定め、 その間に入力信号が変化しないときに限ってFFの動作が保証されています。

クロックエッジの「直前」に設けられた入力信号の変化禁止時間を最小セットアップ時間、
クロックエッジの「直後」に設けられた入力信号の変化禁止時間を最小ホールド時間、
とそれぞれ呼びます。

入力信号がこれらの最小値よりも大きなセットアップ時間、 ホールド時間を持つときにのみ、FFが正しく動作する、と言い換えることもできます。

下図で説明します。

metastable.png

上段部分にFFに入力されるクロック信号が表示されており、
中段はクロックの立ち上がりに同期して動作する D−FF に、 クロックに同期した信号 (sync) が入力される状況を表しています。

信号はクロックの立ち上がりにほぼ同期して直前のFFから出力されますが、 いくつかのゲートや配線を通るため、無視できない量の遅延時間の後にFFの入力に到達します。 この遅延時間は、信号経路に含まれる(複数の)ロジック(ゲート)を通過するのに 掛かる時間と、そっらを繋ぐ配線を通過するのに掛かる時間の和になります。 言い換えると、クロックエッジからこの合計の遅延時間の後に、入力信号の値が定まることになります。

図から明らかなように、クロック周期から遅延時間を引き算したものがセットアップ時間です。 回路が複雑になり、あるいは信号経路が長くなりすぎて、遅延時間が大きくなると、 直後のFFのセットアップ時間が短くなり、最悪素子固有の最小値を下回ると、 素子の動作がおかしくなります。

一方でホールド時間はと言うと、FFの出力はクロックエッジの直後に変化する訳では無く、 内部に有限のゲート遅延を持ちます。FFに規定される最小ホールド時間は非常に短いため、 大抵はFFの出力遅延だけでホールド時間の制約を満たしてしまいます。 すなわち、内部FF同士のデータのやりとりでホールド時間を考える必要は、 ほとんどありません。(クロックの供給に、クロック専用の高速配線リソースが利用できない ときには、同じクロックを表すネットでも、遠く離れた位置では大きなスキューが生じるため、 実質的に同じクロックと見なせなくなってしまうこともあり、 結果としてホールド時間が問題になることもあります。)

このような通常のクロック同期信号の動作と比較するため、上図の下段には、 クロックに同期していない信号をFFの入力に繋いだ状況を想定しました。 入力信号がクロックタイミングとは無関係に変化するため、 最小セットアップ時間あるいは最小ホールド時間の規定に違反して、 クロックエッジ付近で値が変化する状況がしばしば起きてしまいます。

セットアップ時間やホールド時間が守られず、エッジ付近で入力信号が変化すると、 FFが不安定な状況に陥り、出力信号の値が比較的長い時間(数 ns 程度)に渡り、 ばたばたと振動してしまうことがあります。

図では黄色の矢印の時刻にセットアップ時間の違反が起きています。 そのせいで次クロックの出力値が長い間発信してしまい、 数 ns の時間を掛けて徐々に 0 に収まっていく様子を表しています。

このように出力信号がふらついている間に別のラッチがこの信号を入力すると、 1と読まれるか0と読まれるかが定まらない、というだけではなく、 高確率で次のラッチもタイミング違反となり、メタステーブル状態が伝播してしまいます。

あるいは、メタステーブルな出力が複数の素子に入力されていると、 同じ信号を読んでいるにもかかわらず、 その値を素子毎に異なる値に読んでしまうことがあり得ます。

クロックドメイン間で安全に信号線(バス)を受け渡す

非同期 Fifo の入力側と出力側とで相互にカウンタ値を受け渡す用途など、 汎用的に使いたくて作った回路です。

それぞれ2段の FF を介して request / acknowledgement をやりとりして、 データを安全に受け渡すことを試みています。

インスタンシエーション時にパラメータでバス幅を変えられるので、 いろんな用途に使えると思います。(正しく動けば、ですが・・・)

動作タイミング図

synchronize.png

iclk の3周期とoclk の3周期を足した程度の頻度に一回、入力側から出力側へ値が更新される予定です。

Verilog コード

LANG:verilog
module synchronize #(
    parameter DATA_BITS = 8
) (
    input rst,
    input iclk,
    input [DATA_BITS-1:0] idata,
    input oclk,
    output reg [DATA_BITS-1:0] odata
);

    (* TIG="TRUE" *) reg [DATA_BITS-1:0] temp;
    (* TIG="TRUE" *) reg req;
    (* TIG="TRUE" *) reg ack;

    reg ack1, ack2;
    always @(posedge oclk)
        if (rst) begin
            req <= 1;
            ack1 <= 0;
            ack2 <= 0;
            odata <= temp;
        end else begin
            if ( req == ack2 ) begin
                req <= !req;
                odata <= temp;  // interclock signal "temp"
            end
            ack1 <= ack;        // interclock signal "ack"
            ack2 <= ack1;
        end
        
    reg req1, req2;
    always @(posedge iclk)
        if (rst) begin
            ack <= 0;
            req1 <= 0;
            req2 <= 0;
            temp <= idata;
        end else begin 
            if ( req2 != ack ) begin
                ack <= !ack;
                temp <= idata;
            end
            req1 <= req;        // interclock signal "req"
            req2 <= req1;
        end
        
endmodule

コメント


Counter: 164119 (from 2010/06/03), today: 19, yesterday: 0