リファレンスガイド

リセットとクロックドメイン間乗り換えのパターン

リセット同期化回路、2段フロップ同期化回路、パルスおよびハンドシェイク同期化回路、グレイポインタFIFOと、それらを安全にする制約を、コンパイル済みVerilogとともに示します。

AMDAltera最終確認 2026年11月13日· Verilog checked with Icarus Verilog 13.0, constraints for Vivado 2026.1 and Quartus Prime Pro 26.3

このガイドは、クロック領域をまたがなければならない信号や、すべてのフロップに安全に届かなければならないリセットを抱えていて、動くことが分かっているパターンを求めているエンジニアのためのものです。各パターンについて回路、適用できる場面、そしてタイミングツールがそのパスを失敗させたり黙って無視したりしないようにする制約を示します。VerilogはIcarus Verilogでコンパイルし、パルス、ハンドシェイク、グレイポインタの各モジュールはシミュレーションしています。

同期リセットか非同期リセットか

同期リセット 非同期リセット
振る舞い リセットは他のデータ入力と同様にクロックエッジでサンプルされる リセットがアサートされた瞬間にフロップがクリアされ、クロックは不要
アサートに動作中のクロックが必要か はい いいえ
タイミング リセットは通常のデータパスで、セットアップとホールドを満たす必要がある アサートは自由。解除はリカバリとリムーバルを満たす必要がある
グリッチ感度 クロックでフィルタされる グリッチでフロップがリセットされる
ファブリックのコスト フロップの同期クリアを使うか、一部のファミリではLUT入力を1つ使う 専用の非同期クリアピンを使う
一般的な採用先 AMD(UG949で推奨)、Lattice Altera、Microchip、ASIC風のコード

当サイトの既定は正論理の同期リセットです。すべてのパスを通常のタイミング検証対象のパスに保てるからです。どちらを選ぶにせよ、リセットの解除はクロックに同期していなければならず、それを提供するのが最初のパターンです。

リセット同期化回路

非同期にアサートし(クロックが止まっていてもシステムはリセット状態に保たれる)、同期的に解除します(すべてのフロップが同じサイクルでリセットを抜ける)。

module reset_sync (
  input  wire clk,
  input  wire arst_n,   // asynchronous, active-low, from pin or PLL lock
  output wire rst_n     // asserts asynchronously, releases on clk
);
  (* ASYNC_REG = "TRUE" *) reg [1:0] sync_n;
  always @(posedge clk or negedge arst_n) begin
    if (!arst_n) sync_n <= 2'b00;
    else         sync_n <= {sync_n[0], 1'b1};
  end
  assign rst_n = sync_n[1];
endmodule

これをクロック領域ごとに1つ置き、同じ非同期ソースから駆動します。領域が大きければ、その後ろにレジスタのファンアウトツリーを続けてください。ツールは最終段のフロップを複製してくれますが、それは許可した場合だけです(KEEPを付けないこと)。出力は同期リセットにも非同期クリアピンにも使えます。重要なのは解除がクロックに揃っていることです。

2段フロップ同期化回路

レベルが送り先クロックの少なくとも2周期の間安定している、単一ビットの信号向けです。

module sync2 (
  input  wire clk_dst,
  input  wire d_src,     // a level that stays stable for several clk_dst cycles
  output wire d_dst
);
  (* ASYNC_REG = "TRUE" *) reg [1:0] ff;
  always @(posedge clk_dst) ff <= {ff[0], d_src};
  assign d_dst = ff[1];
endmodule
1段目のフロップでの準安定状態(メタステーブル)。d_srcがエッジ3の近くで変化し、ff0はそのサイクルの一部で未確定のまま、その後落ち着きます。ff1はきれいなレベルしか見ることがなく、ここでは入力が早く来た場合より1サイクル遅れています。clk_dstd_srcff0ff1
1段目のフロップでの準安定状態(メタステーブル)。d_srcがエッジ3の近くで変化し、ff0はそのサイクルの一部で未確定のまま、その後落ち着きます。ff1はきれいなレベルしか見ることがなく、ここでは入力が早く来た場合より1サイクル遅れています。

この図から導かれる規則:

規則 理由
2つのフロップの間に決してロジックを置かない ゲートが1つ増えるごとに1段目のフロップが解決に使える時間が削られる
複数ビットのバスをビットごとに同期化しない 各ビットがそれぞれ別のサイクルで解決するので、バスとしての整合性が失われる
ソースは組み合わせ論理ではなくレジスタでなければならない 入力のグリッチが本物の遷移としてサンプルされる
約400 MHz以上、または非常に長いMTBF目標では3段のフロップを使う MTBFの計算は/reference/timing-formulae/を参照
送り先の2周期より短いパルスは失われる 後述のパルス同期化回路を使う

パルス(トグル)同期化回路

ソース領域での1サイクルのパルスを送り先領域での1サイクルのパルスに変換します。パルスの間隔が送り先の少なくとも3周期空いていれば、クロック周波数の比は問いません。

module pulse_sync (
  input  wire clk_src,
  input  wire pulse_src,   // single-cycle pulse in clk_src
  input  wire clk_dst,
  output wire pulse_dst    // single-cycle pulse in clk_dst
);
  reg tog_src = 1'b0;
  always @(posedge clk_src)
    if (pulse_src) tog_src <= ~tog_src;

  (* ASYNC_REG = "TRUE" *) reg [1:0] ff;
  reg tog_prev;
  always @(posedge clk_dst) begin
    ff       <= {ff[0], tog_src};
    tog_prev <= ff[1];
  end
  assign pulse_dst = ff[1] ^ tog_prev;
endmodule

トグルレジスタが各パルスをレベルの変化に変え、2段のフロップがそのレベルを同期化し、遅延したコピーとのXORがエッジをパルスに戻します。送り先が吸収できるより速くパルスが到着しうる場合は、後述のハンドシェイクかFIFOを使ってください。

ハンドシェイク同期化回路

リクエストとアクノリッジを使って1ワードのデータを渡すので、ソースはいつデータを変えてよいかを知ることができます。スループットは低い(両領域合わせて1ワードあたり約6サイクル)ものの、常に正しく動きます。

module handshake_sync #(parameter W = 16) (
  input  wire         clk_src,
  input  wire         req_src,     // pulse when data_src is valid
  input  wire [W-1:0] data_src,
  output wire         busy_src,    // high until the far side has taken the data
  input  wire         clk_dst,
  output reg          valid_dst = 1'b0,
  output reg  [W-1:0] data_dst
);
  reg         req_tog = 1'b0;
  reg [W-1:0] data_hold;
  always @(posedge clk_src)
    if (req_src && !busy_src) begin
      req_tog   <= ~req_tog;
      data_hold <= data_src;
    end

  (* ASYNC_REG = "TRUE" *) reg [1:0] req_ff = 2'b00;
  reg req_prev = 1'b0;
  always @(posedge clk_dst) begin
    req_ff    <= {req_ff[0], req_tog};
    req_prev  <= req_ff[1];
    valid_dst <= req_ff[1] ^ req_prev;
    if (req_ff[1] ^ req_prev) data_dst <= data_hold;
  end

  (* ASYNC_REG = "TRUE" *) reg [1:0] ack_ff = 2'b00;
  always @(posedge clk_src) ack_ff <= {ack_ff[0], req_ff[1]};
  assign busy_src = req_tog ^ ack_ff[1];
endmodule

データバスdata_holdは同期化回路なしで領域をまたぎます。これが安全なのは、リクエストがトグルする前からアクノリッジが戻った後まで安定していることが保証されており、最後の節の制約でそのことをツールに伝えるからです。

グレイポインタを使う非同期FIFO

ストリーミングデータには非同期FIFOが適切な道具です。データはデュアルポートのブロックRAMが保持します。各側は書き込みポインタまたは読み出しポインタを自分の領域で持ち、各ポインタは2段フロップ同期化回路を通して相手側に渡され、フルとエンプティの計算に使われます。

バイナリのポインタは同期化できません。複数のビットが同時に変化しうるので、各ビットが別のサイクルで解決するかもしれないからです。グレイコードはインクリメントごとにちょうど1ビットしか変わらないため、同期化されたグレイポインタは常に古い値か新しい値のどちらかであり、混ざった値になることはありません。ポインタはアドレスより1ビット広くし、フルとエンプティを区別できるようにします。

module gray_ptr #(parameter AW = 4) (
  input  wire          clk,
  input  wire          rst,
  input  wire          inc,
  output reg  [AW:0]   bin,      // one extra bit to tell full from empty
  output reg  [AW:0]   gray
);
  wire [AW:0] bin_next  = bin + {{AW{1'b0}}, inc};
  wire [AW:0] gray_next = bin_next ^ (bin_next >> 1);
  always @(posedge clk) begin
    if (rst) begin
      bin  <= {(AW+1){1'b0}};
      gray <= {(AW+1){1'b0}};
    end else begin
      bin  <= bin_next;
      gray <= gray_next;
    end
  end
endmodule

module gray_to_bin #(parameter AW = 4) (
  input  wire [AW:0] gray,
  output wire [AW:0] bin
);
  genvar i;
  generate
    for (i = 0; i <= AW; i = i + 1) begin : g_xor
      assign bin[i] = ^(gray >> i);
    end
  endgenerate
endmodule

フラグはローカルのグレイポインタと同期化した相手側のポインタを比較します:

module fifo_flags #(parameter AW = 4) (
  input  wire [AW:0] wr_gray,        // write pointer, write domain
  input  wire [AW:0] rd_gray_sync,   // read pointer after two flops in write domain
  input  wire [AW:0] rd_gray,        // read pointer, read domain
  input  wire [AW:0] wr_gray_sync,   // write pointer after two flops in read domain
  output wire        full,
  output wire        empty
);
  assign empty = (rd_gray == wr_gray_sync);
  assign full  = (wr_gray == {~rd_gray_sync[AW:AW-1], rd_gray_sync[AW-2:0]});
endmodule

エンプティはポインタが等しいことを意味します。フルは上位2ビットを反転した以外は等しいことを意味し、これがグレイコードで「1周先行している」ことを表す形です。相手側のポインタは2サイクル古いので、どちらのフラグも悲観的ですが、それは安全な側です。FIFOはやや早めにフルを報告することはあっても、決してオーバーフローしません。RAMのアドレス指定にはバイナリのポインタを、領域の乗り換えにはグレイのポインタを使ってください。ベンダーのFIFOジェネレータ(AMDのFIFO GeneratorとXPM FIFO、AlteraのDCFIFO、LatticeのFIFO_DC)はまさにこれを実装しており、量産では手書きのFIFOよりそちらを優先すべきです。

リセットツリー

手順 理由
非同期ソースを1つにする(電源投入、外部ピン、PLLロック) 「システムが立ち上がった」ことの唯一の情報源にする
クロック領域ごとにリセット同期化回路を1つ置く 解除を各クロックに揃える
すべてのPLLがロックを報告するまでリセットを保持する 解除の前にクロックが安定していなければならない
最も遅い領域を最後に解除するか、カウンタでリセットを順序付ける 速い領域が初期化前の遅い領域を読むのを防ぐ
大きなブロックごとに先頭でリセットをレジスタに通す ファンアウトを分割してタイミングを収束させる
必要なものだけをリセットする: 制御状態、カウンタ、validフラグ データパスのレジスタとRAMにはリセットは不要で、付けると配線を消費する

AMDのUG949はリセットを少なく、同期で、正論理にすることを推奨しています。AlteraとMicrochipのファブリックは非同期クリアが安価で、そのガイドはどちらも認めています。同じ信号で2つの方式を混ぜないでください。

制約すべきもの

パターン Vivado XDC Quartus SDC 理由
同期化回路のフロップすべて set_property ASYNC_REG TRUE [get_cells u_s/ff_reg*](またはRTL内の属性) QSFにset_instance_assignment -name SYNCHRONIZER_IDENTIFICATION FORCED -to "u_s|ff*"(Quartusは階層を縦棒で区切る) 2つのフロップを隣接させ、ツールによるロジック挿入を防ぎ、MTBFレポートを有効にする
同期化回路に入る単一ビットの乗り換え set_false_path -to [get_cells u_s/ff_reg[0]]またはset_max_delay -datapath_only set_false_path -to [get_registers u_s/ff[0]] このパスは設計上非同期。これがないとツールは失敗させるか、さらに悪いことに「修正」してしまう
ハンドシェイクのデータバス、グレイポインタのバス set_max_delay -datapath_only T_dst -from [get_cells src_reg*] -to [get_cells dst_reg*]と、グレイバスにはset_bus_skew set_max_delayとバスへのset_net_delay -max 安定ウィンドウの仮定が成り立つよう遅延を制限する。フォルスパスにするとツールはバスを任意に遅く配線してしまう
全体として無関係なクロック set_clock_groups -asynchronous 同じ グループ間に同期パスが存在しないことを宣言する
リセット同期化回路の入力 set_false_path -from [get_ports arst_n] 同じ 非同期のアサートはタイミング検証対象のパスではない。解除はすでに同期している

データを運ぶものにはset_false_pathよりset_max_delay -datapath_onlyを優先してください。フォルスパスは「決して検査しない」、遅延の上限は「緩く検査する」という意味で、後の変更で遅くなった乗り換えを捕まえられるのは後者だけです。大きな変更のたびにベンダーのCDCレポート(Vivadoのreport_cdc、Quartus Prime ProのTiming Analyzer CDCビューア)を実行してください。

出典

誤りやバージョン変更に気づいたら 訂正の手順をご覧ください。