入門 · レッスン 4 / 10

レジスタ、クロック、状態という考え方

フリップフロップ、クロックエッジ、セットアップとホールドを直感的に説明し、設計を予測可能に保つ同期設計の規則と、同期リセットとイネーブル付きのレジスタをVerilogとVHDLで示します

入門30 分更新 2026年11月13日無料期間 2027年1月31日

このレッスンで学ぶこと

  • Dフリップフロップがクロックエッジで何をするか、そして設計になぜ状態が必要なのかを説明できる
  • セットアップ時間とホールド時間を直感的に説明し、違反するとどうなるかを述べられる
  • 同期設計の規則を挙げ、それぞれの背後にある理由を述べられる
  • 同期リセットとクロックイネーブル付きのレジスタをVerilogとVHDLで書ける

前提レッスン: 組み合わせ論理とルックアップテーブル

レッスン03ではメモリを持たない論理を扱いました。本レッスンでは、あらゆるデジタル設計のもう半分を加えます。レジスタ、すなわちあるクロックエッジから次のクロックエッジまで値を記憶する素子です。レジスタがあれば状態があり、状態があれば、数える、順序づける、フィルタする、通信することができます。同時に、信頼できるFPGA設計のすべてが従う、クロックとタイミングに関する一連の規則も受け継ぐことになります。本レッスンでは、その規則と背後にある理由を示します。

回路に状態が必要な理由

組み合わせ回路が答えられる問いは1つだけです。今この入力なら、今の出力は何か。「パルスがいくつ届いたか」「1マイクロ秒前の入力は何だったか」「手順のどのステップにいるか」には答えられません。これらの問いは履歴に依存するからです。履歴を保持するには回路にメモリが必要で、最も単純で有用なメモリは、選んだ瞬間に新しい値を取るよう指示でき、それ以外のときは今の値を保つ1ビットです。

その素子がDフリップフロップです。データ入力d、クロック入力clk、出力qを持ちます。クロックの立ち上がりエッジごとにdをqにコピーします。エッジとエッジの間は、dが何をしようとqは動きません。レジスタはクロックを共有するフリップフロップの並びで、8ビットレジスタは同じ瞬間に入力を取り込む8個のフリップフロップです。

「選んだ瞬間」が要点です。値はクロックエッジでのみ取り込まれると取り決めることで、設計全体にリズムが生まれます。レジスタ間の論理はクロック周期の間にグリッチを起こし、波打ち、落ち着いてよく、次のエッジまでに終わっていれば何も問題ありません。クロックが、連続的で雑然としたアナログの現実を、きれいな離散ステップの列に変えるのです。

時間の中のDフリップフロップ

タイミング図は4クロックサイクルにわたるフリップフロップを示しています。dとqの行をclkのエッジと対応させて読んでください。

Dフリップフロップはclkの立ち上がりエッジごとにdをサンプリングします。dはエッジ前のセットアップ時間の間と、エッジ後のホールド時間の間、安定していなければなりません。clkdqt_setupt_holdabc
Dフリップフロップはclkの立ち上がりエッジごとにdをサンプリングします。dはエッジ前のセットアップ時間の間と、エッジ後のホールド時間の間、安定していなければなりません。

最初の立ち上がりエッジではdが不定なので、qも不定です。2番目のエッジの前にdが1になり、そのエッジでqが1になります。その後dは2サイクルの間1のままでqも追従しますが、dが変化していないため、3番目のエッジでqが変化しないことに注意してください。4番目のエッジの前にdが0に下がり、qはそのエッジで、dより1クロック遅れて下がります。出力は常に、次のエッジまで遅延された入力です。

図の2本の矢印は、これを成り立たせるタイミング要件を示しています。

セットアップ時間

フリップフロップはトランジスタで作られており、dを取り込む回路は、エッジが来る前の短い時間、入力が安定していることを必要とします。その区間がセットアップ時間で、t_setupまたはt_suと書きます。最近のFPGAでは数十ピコ秒です。dがセットアップ窓の中で変化すると、フリップフロップは古い値、新しい値、あるいは後述のようにそのどちらでもない値を取り込むことがあります。

ホールド時間

同様に、dはエッジの後も短い時間、取り込み回路が入力を手放すまで安定していなければなりません。これがホールド時間で、t_holdまたはt_hと書きます。これも数十ピコ秒で、デバイスによってはゼロまたはわずかに負になります。FPGAではフリップフロップ間の配線がほぼ常にホールド時間を超える遅延を加えるため、ホールド違反はセットアップ違反より稀ですが、非常に短い経路では起こり得るので、ツールはこれも検査します。

規則が破られるとどうなるか

dがセットアップ窓またはホールド窓の中で変化すると、フリップフロップの内部ノードが0と1の間で釣り合ったまま取り残されることがあります。いずれはどちらかに倒れますが、それにかかる時間は原理的に上限がなく、実際にはランダムです。その間、qは有効な0でも有効な1でもなく、それを読む下流の論理は経路によって異なる値を見ることがあります。この状態を準安定状態(メタステーブル)と呼びます。なくすことはできず、稀にすることしかできません。自分のクロック領域の外から信号を受け取るすべての設計は、これに対処しなければなりません。タイミングと制約のトピックと用語集の準安定状態の項で、標準的な対策である2段フリップフロップの同期化回路(シンクロナイザ)を扱います。

単一のクロック領域内では、ツールがその仕事をしていれば、次に述べるタイミング検査のおかげで準安定状態は決して生じません。

レジスタ間のタイミング

同期設計のすべての経路は同じ形をしています。あるフリップフロップがあるエッジで値を送り出し、値は何らかの組み合わせ論理と配線を通り、別のフリップフロップが次のエッジでそれを取り込みます。

FF 1dq組み合わせ論理LUT+キャリー、1〜6段FF 2dqclkt_clk_to_qt_logict_routet_setup1クロック周期内に収める
レジスタ間経路。クロック周期は、送り出し側フリップフロップのクロックから出力までの遅延、論理遅延、配線遅延、取り込み側フリップフロップのセットアップ時間を合わせた長さより長くなければなりません。

計算は単純です。クロック周期が10 ns(100 MHz)なら、送り出し側のフリップフロップはエッジの後、出力を駆動するのに約0.4 nsかかり、取り込み側のフリップフロップは約0.1 nsのセットアップを必要とし、その間の論理と配線は残りの約9.5 nsを使えます。3〜4段のLUTとその配線なら余裕で収まります。20段では収まりません。配置配線は設計中のすべての経路についてこれを検査し、最悪のものをクリティカルパスとして報告します。その遅延が予算内なら設計は正のスラックを持ち、そのスピードグレードのすべてのデバイスで、データシートの範囲内のすべての温度と電圧において、そのクロックレートで動作します。

これが同期設計を守る見返りです。クロック周期というたった1つの数がすべての経路を制約し、たった1つの自動検査が、設計が時間的に正しいかどうかを教えてくれます。デジタル設計において、この保証をこれほど安価に与えてくれるものは他にありません。

同期設計の規則

この保証が成り立つのは、設計が短い規則の組に従う場合だけです。それぞれに理由があり、それぞれが初心者によって日常的に破られています。

  1. クロックは1つ、専用のクロックネットワークで分配する。 論理ブロック内のすべてのフリップフロップは同じクロックで駆動され、デバイスの低スキューなグローバルクロックツリーを通じて届けられるべきです。そうすれば、すべてのフリップフロップが数十ピコ秒以内の差でエッジを見ます。2つのフリップフロップがエッジを2 nsずれて見たら、上のタイミング予算はツールが予測できない方向に2 ns狂います。複数のクロックを使う設計はありますが、クロック間のすべての乗り換えは特別なケースであり、専用のレッスンがあります。
  2. 論理からクロックを生成しない。 クロックとして使われたLUT出力は、レッスン03で説明したとおりグリッチを起こし、各グリッチは余分なエッジになります。また、予測できないスキューを持つ通常の配線で引き回されます。遅いレートが必要なら、速いクロックを保ったまま、下のレジスタのようにクロックイネーブルを使ってください。異なる周波数が必要ならPLLを使ってください。
  3. 同期リセットを使い、データパスを通じて適用する。 下の例のようにクロックエッジで効くリセットは通常のタイミング経路であり、ツールが検査できます。非同期リセットは任意の瞬間に解除され、その瞬間がクロックエッジに近いと、設計全体で一斉に準安定状態を引き起こします。状況によっては非同期リセットを使う正当な理由があり、中級トラックのリセット設計のレッスンで扱いますが、デフォルトは同期です。
  4. 外部からのすべての入力と外部へのすべての出力をレジスタで受ける。 ピン、ボタン、他のクロック領域からの信号は、自分のクロックに揃っていません。使う前に2段のフリップフロップを通してください。出力はフリップフロップから駆動し、クロックに対するタイミングを正確にしてください。
  5. 信号を書き込むエッジの前に読んで、新しい値を期待しない。 すべてのフリップフロップはエッジで更新され、新しい値はその後に見えます。qを使う論理は、サイクル全体にわたって古い値を見ます。これは制限ではありません。これがタイミング解析を可能にしているのです。

同期リセットとイネーブル付きレジスタ

ほとんどの実際の設計を構成しているレジスタを示します。クロック、出力をゼロに強制する同期リセット、新しい値を読み込むかどうかを制御するイネーブル、そして幅を設定できるデータ入力と出力を持ちます。enが0のときレジスタは値を保持します。これが、2つ目のクロックなしに設計の一部を遅いレートで動かす方法です。

module reg_en #(
  parameter WIDTH = 8
) (
  input  wire             clk,
  input  wire             rst,
  input  wire             en,
  input  wire [WIDTH-1:0] d,
  output reg  [WIDTH-1:0] q
);
  always @(posedge clk) begin
    if (rst)
      q <= {WIDTH{1'b0}};
    else if (en)
      q <= d;
  end
endmodule
library ieee;
use ieee.std_logic_1164.all;

entity reg_en is
  generic (
    WIDTH : positive := 8
  );
  port (
    clk : in  std_logic;
    rst : in  std_logic;
    en  : in  std_logic;
    d   : in  std_logic_vector(WIDTH-1 downto 0);
    q   : out std_logic_vector(WIDTH-1 downto 0)
  );
end entity;

architecture rtl of reg_en is
begin
  process (clk)
  begin
    if rising_edge(clk) then
      if rst = '1' then
        q <= (others => '0');
      elsif en = '1' then
        q <= d;
      end if;
    end if;
  end process;
end architecture;

暗記すべき構造は両言語で同じです。クロックに感応するブロックが1つあります(always @(posedge clk)、またはrising_edgeを使うprocess (clk))。その中で、他のすべてに優先するようにまずリセットを検査します。次にイネーブル。そして実際の代入。このブロックの中に書かれたものはすべてフリップフロップになり、ある分岐で代入されないものは古い値を保ちます。追加のコードなしに「無効時は保持」の振る舞いが生まれるのはこのためです。

合成はこれをフリップフロップのプリミティブに直接対応付けます。すべてのFPGAのフリップフロップには専用のクロックイネーブルピンと専用の同期リセットピンがあるので、このif文はLUTを1つも消費しません。優先順位を逆にして、イネーブルがリセットを制御するように書くと、ツールは両者を組み合わせるためにLUTを必要とし、イネーブルがローのときに効かないリセットになってしまいます。

短いテストベンチが3つの振る舞いを検査します。無効時に保持、有効時に読み込み、再び無効時に保持です。

`timescale 1ns/1ps
module reg_en_tb;
  reg        clk = 1'b0;
  reg        rst = 1'b1;
  reg        en  = 1'b0;
  reg  [7:0] d   = 8'h00;
  wire [7:0] q;

  reg_en #(.WIDTH(8)) dut (.clk(clk), .rst(rst), .en(en), .d(d), .q(q));

  always #5 clk = ~clk;   // 100 MHz

  initial begin
    repeat (2) @(posedge clk);
    rst <= 1'b0;
    d   <= 8'hA5;           // en still low: q must stay 0
    @(posedge clk); #1;
    if (q !== 8'h00) $display("FAIL: q changed while en was low");
    en  <= 1'b1;
    @(posedge clk); #1;
    if (q !== 8'hA5) $display("FAIL: q did not load, got %h", q);
    en  <= 1'b0;
    d   <= 8'h3C;
    @(posedge clk); #1;
    if (q !== 8'hA5) $display("FAIL: q changed after en dropped");
    $display("done");
    $finish;
  end
endmodule

各@(posedge clk)の後の#1に注目してください。フリップフロップはエッジで更新されるので、まったく同じ瞬間にqを検査するとテストベンチと設計の間で競合が起きます。1 ns待つことでエッジから離れられます。レッスン06でこれを厳密にします。

状態を一文で

同期設計とは、現在の状態を保持するレジスタの集合に、現在の状態と入力から次の状態と出力を計算する組み合わせ論理を加えたものであり、クロックがある状態から次の状態へ進めます。カウンタ(レッスン08)はその最も単純な例です。有限ステートマシン(レッスン07)は一般的な例です。すべてのプロセッサ、すべてのプロトコルエンジン、すべてのビデオパイプラインは、同じものの大規模版です。

演習

  1. 冒頭のタイミング図では、dは取り込むエッジのちょうど1サイクル前に立ち上がっています。紙の上またはWaveDromのオンラインエディタで、dがエッジの0.5サイクル前に立ち上がり、0.5サイクル後に立ち下がるように図を描き直してください。セットアップ窓とホールド窓が今どこにあるかを示し、設計がまだ安全かどうかを述べてください。
  2. ある設計が200 MHzで動作します。フリップフロップのクロックから出力までの遅延は0.45 ns、セットアップ時間は0.08 nsです。各LUTとその配線には約0.9 nsかかります。2つのレジスタの間に置けるLUT段数の最大値はいくつで、経路が3段を使う場合のスラックはいくらですか。
  3. reg_enにclrという2つ目のイネーブルを追加してください。enに関係なく、ただしrstがローのときに限り、次のエッジでqをすべて1にするものです。優先順位のどこに置くかを決め、両言語で実装し、テストベンチを拡張して検査してください。

まとめ

  • Dフリップフロップは立ち上がりクロックエッジごとにdをqにコピーし、それ以外はqを保持します。レジスタはクロックを共有するフリップフロップの並びです。
  • dはエッジ前のセットアップ時間とエッジ後のホールド時間の間、安定していなければなりません。どちらかに違反すると、有効なレベルに落ち着くまでの遅延に上限がない準安定状態のリスクがあります。
  • すべてのレジスタ間経路は、クロックから出力、論理、配線、セットアップを1クロック周期に収めなければなりません。配置配線はそのすべてを検査し、最悪のものをクリティカルパスとして報告します。
  • 同期設計の規則は、グローバルネットワーク上の1つのクロック、論理からクロックを作らない、同期リセット、入出力をレジスタで受ける、サイクル全体で古い値を読む、です。
  • 標準的なレジスタはリセットが最初、イネーブルが2番目、代入が最後で、リセットとイネーブルはどちらもコストなしでフリップフロップの専用ピンに対応付けられます。

さらに読む