静的タイミング解析を正しく理解する
タイミング解析ツールが何を計算するのか、実際の数値で見るセットアップ・ホールドチェック、VivadoやQuartusのレポートの読み方、そして最初に何を直すべきかを解説します
このレッスンで学ぶこと
- 静的タイミング解析ツールが何を列挙するのか、なぜシミュレーション刺激が不要なのかを説明できる
- 遅延、スキュー、不確かさが与えられたレジスタ間経路について、セットアップとホールドのスラックを手計算できる
- VivadoまたはQuartus Primeのタイミングレポートで、到着時刻、要求時刻、論理段数、配線の割合を見つけられる
- WNSとTNSを区別し、両者から設計が収束に近いのか構造的に遅すぎるのかを判断できる
- 失敗している経路について、制約の問題か、ホールドの問題か、論理段数の問題かに応じて最初の修正を選べる
所定のクロック周波数で動作しなければならないFPGA設計は、すべて静的タイミング解析(STA)を通過します。そしてSTAが生成するレポートは、ツールが出力する文書の中で最も重要なものです。本レッスンは、設計を作り、赤い「timing not met」の行を目にし、RTLに手を付ける前にその数値の意味を理解したいエンジニアのためのものです。実際の遅延値を使ってセットアップチェックとホールドチェックを順に追い、各数値がVivadoやQuartus Primeのレポートのどこに現れるかを学び、最後に何を最初に直すべきかを決める短い手順で締めくくります。
解析ツールが実際に計算するもの
シミュレータは刺激を与え、値の変化を観察します。タイミング解析ツールはそのどちらも行いません。配置配線後のネットリスト、選択したデバイスとスピードグレードの遅延モデル、そして制約を読み込みます。次に、クロック同期素子(フリップフロップ、ブロックRAM(BRAM)のポート、DSPレジスタ、入力ピン)から始まり、別のクロック同期素子または出力ピンで終わるすべての経路を列挙します。各経路について、論理と配線を通る最悪ケースの遅延を合計し、その合計をキャプチャ側レジスタがサンプルする時刻と比較します。
ここから3つのことが導かれます。第一に、解析は網羅的です。テストベンチが一度も通らない経路も含め、すべての経路がチェックされます。第二に、意図的に悲観的です。セットアップにはプロセス・電圧・温度の最も遅い組み合わせを、ホールドには最も速い組み合わせを仮定します。第三に、結果の良し悪しは制約次第です。クロック定義のない経路はまったく解析されず、誤ったクロック定義を持つ経路は誤った期限に対して解析されます。クロック制約そのものについては、/reference/xdc-sdc-constraints/ のチートシートで扱っています。
図には、あらゆるチェックの5つの構成要素が示されています。ローンチ側レジスタまでのクロック経路、ローンチ側レジスタのクロック-出力間遅延、組み合わせ論理、配線、そしてキャプチャ側レジスタまでのクロック経路です。そもそもなぜレジスタがこうした期限を課すのかを思い出したい場合は、/learn/beginner/04-registers-clocks-and-state/ を参照してください。
実際の数値で見るセットアップチェック
200 MHzのクロックを考えます。周期は5.000 nsです。経路はある立ち上がりエッジでローンチされ、次のエッジでキャプチャされなければなりません。以下の数値を使います。これはミッドレンジの16 nmまたは28 nm品の最も遅いスピードグレードで典型的な値です。
| 項目 | 記号 | 値 |
|---|---|---|
| ソース側クロック経路(ルートからFF Aまで) | 1.200 ns | |
| FF Aのクロック-出力間遅延 | t_cko | 0.450 ns |
| LUT 2段 | logic | 0.600 ns |
| LUT間およびFF Bまでの配線 | route | 1.900 ns |
| 宛先側クロック経路(ルートからFF Bまで) | 1.150 ns | |
| クロック不確かさ(ジッタと位相誤差) | 0.035 ns | |
| FF Bのセットアップ時間 | t_su | 0.080 ns |
データ到着時刻は、ローンチ側のすべてを合計したものです。
arrival = 1.200 + 0.450 + 0.600 + 1.900 = 4.150 ns
データ要求時刻は、キャプチャエッジに、クロックがFF Bに到達するまでの遅延を加え、そこから余裕を削るマージンを引いたものです。
required = 5.000 + 1.150 - 0.035 - 0.080 = 6.035 ns
slack = required - arrival = 6.035 - 4.150 = +1.885 ns
正のスラックは、この経路が1.885 nsの余裕をもって通過することを意味します。ここで、ルーターがこのネットを混雑した領域を横切って配線せざるを得ず、配線遅延が3.900 nsに増えたとしましょう。到着時刻は6.150 nsになり、スラックは6.035から6.150を引いた-0.115 nsです。経路は115 psだけ失敗します。これは小さな数値で、RTLを変更せずに配置の改善やファンアウトの削減で直せる、まさに典型的な失敗です。
クロックスキューの役割に注目してください。キャプチャ側のクロックはローンチ側のクロックより0.050 ns早く到着しており(1.200 nsに対して1.150 ns)、スキューが予算から50 psを奪っています。キャプチャ側のクロックがローンチ側より遅ければ、スキューはセットアップに有利に、ホールドに不利に働いたはずです。一般式は /reference/timing-formulae/ にまとめてあります。
実際の数値で見るホールドチェック
ホールドは、最も多くの人が誤解するチェックです。クロック周期とは何の関係もないからです。ホールドチェックが問うのは、あるエッジでローンチされた新しい値が論理を駆け抜け、同じエッジがまだ古い値のサンプルに使われている間にキャプチャ側レジスタを壊してしまわないか、ということです。したがってこのチェックは両端で同じクロックエッジを使い、最も遅い遅延ではなく最も速い遅延を使います。
| 項目 | 高速側の値 |
|---|---|
| ソース側クロック経路 | 1.200 ns |
| FF Aのクロック-出力間遅延(最小) | 0.250 ns |
| 論理(最小) | 0.150 ns |
| 配線(最小) | 0.200 ns |
| 宛先側クロック経路 | 1.150 ns |
| FF Bのホールド時間 | t_h |
arrival = 1.200 + 0.250 + 0.150 + 0.200 = 1.800 ns
required = 1.150 + 0.100 = 1.250 ns
slack = arrival - required = 1.800 - 1.250 = +0.550 ns
ホールドでは引き算の向きが逆になります。データは要求時刻より後に到着しなければならないので、スラックは到着時刻から要求時刻を引いたものです。ここでは、FF Bが必要とする時間を550 ps超えてデータが安定に保たれています。この設計を1 MHzで動かしても500 MHzで動かしても、この数値は変わりません。
ホールド違反は、キャプチャ側のクロックがローンチ側よりはるかに遅く到着するときに現れます。よくある原因は、本来一緒にタイミング解析されるべきでない2つのクロックツリー間の経路、別のクロックが通っていないLUTやBUFGを通したクロック、入力遅延制約のない入力ピンです。最後の原因があるため、I/Oでホールド違反が出る設計の多くは、論理にはまったく問題がありません。ベンダーのルーターは小さな内部ホールド違反を自動的に遅延挿入で修正するので、適切に制約された設計の内部でホールド違反を見ることはめったにありません。
レポートで目にする細かい点を1つ挙げます。2つのクロック経路はルートから分岐点までの区間を共有しており、その区間が同時に最も遅い遅延と最も速い遅延を取ることはあり得ません。VivadoもQuartus Prime Timing Analyzerも、この共有区間の差分を「クロックペシミズム除去(clock pessimism removal)」という名前で戻してくれます。通常20 psから100 psが戻ってきますが、これは設計者が影響を及ぼせるものではありません。
レポートの読み方
VivadoとQuartus Primeは同じ情報を異なる配置で表示しますが、何を見るべきかが分かれば対応関係は単純です。
| 概念 | Vivado(report_timing) | Quartus Prime(Timing Analyzer) |
|---|---|---|
| 経路の両端 | SourceとDestination | From NodeとTo Node |
| 期限 | Requirement | Launch ClockとLatch Clockのエッジ |
| ローンチ側の合計 | Data Path Delay、続いてArrival Time列 | Data Arrival Time |
| キャプチャ側の合計 | Required Time | Data Required Time |
| 論理と配線の比率 | logic x ns (y%)とroute z ns (w%) | 要素ごとのCell列とinterconnect列 |
| 深さ | Logic Levels | Data Arrivalのリストの行数を数える |
| 共有クロックの戻し分 | Clock Pessimism Removal | Clock Pessimism |
| 結果 | Slack (MET)またはSlack (VIOLATED) | パネル最上行のSlack |
失敗している経路では、3つの項目を必ず見直すべきです。
Logic Levels(Vivado)または到着リストのセル数(Quartus)は、組み合わせ論理のコーンがどれだけ深いかを示します。28 nm品で200 MHzなら、配線だけで予算を超えてしまうまでに、おおよそLUT 6段から8段が限度です。最新の7 nmや16 nm品ではこの段数は増えますが、論理に対する配線の比率も高くなるので、深さは依然として重要です。
論理と配線のパーセンテージは、問題が設計にあるのか配置にあるのかを示します。論理25%・配線75%でLUTが3段しかない経路は、配置またはファンアウトの問題です。12段にわたって論理が60%を占める経路は、パイプライン化の問題です。
クロック経路のスキューの行は、制約の誤りに対する早期警告です。単一のクロックツリー内のスキューは通常150 ps未満です。数nsのスキューが見えたら、両端が別々のクロック上にあるか、一方のクロックが通るべきでないものを通っている可能性が高いです。
Tclコンソールからサマリーと最悪経路を取得するには、次のようにします。
# Vivado, after route_design
report_timing_summary -max_paths 5 -file timing_summary.rpt
report_timing -sort_by group -max_paths 10 -nworst 1 -delay_type min_max
check_timing
# Quartus Prime Timing Analyzer (quartus_sta)
report_timing -setup -npaths 10 -detail full_path
report_timing -hold -npaths 10 -detail full_path
report_clocks
report_ucp
各ブロックの最後のコマンドが、多くの人が飛ばすものです。Vivadoのcheck_timingとQuartusの「unconstrained paths」レポートは、解析ツールが無視したすべてのエンドポイントを列挙します。2,000個の未制約エンドポイントを抱えたまま「タイミングを満たした」設計は、何も満たしていません。
WNS・TNSとその仲間
Vivadoレポートのサマリーヘッダーには、WNS、TNS、WHS、THS、WPWS、TPWSの6つの数値があります。Quartusはクロックごとのスラックと、そこから導かれるFmaxを報告します。日常的に重要なのは最初の2つです。
**最悪負スラック(WNS)**は、設計中で最も悪いセットアップエンドポイント1つのスラックです。正であれば、すべてのセットアップチェックが通っています。-0.115 nsであれば、最悪の経路が115 ps足りず、それ以外はすべてそれより良い状態です。
**負スラックの総和(TNS)**は、失敗しているすべてのエンドポイントの負スラックの合計です。各エンドポイントが寄与するのは、最大でもそのエンドポイント自身の最悪経路の分だけです。WNSが-0.115 nsでTNSが-0.230 nsなら、失敗しているエンドポイントはおそらく2つで、狙いを絞った1つの修正で設計はたぶん収束します。WNSが-0.115 nsでTNSが-48.000 nsなら、数百のエンドポイントがそれぞれ少しずつ足りていません。このパターンはほぼ常にグローバルな原因を意味します。リセットやイネーブルのような高ファンアウトのネット、混雑した領域、あるいはデバイスに対して数パーセント攻めすぎたクロック周期です。
WHSとTHSはホールドについての同じ数値です。WPWSとTPWSはパルス幅チェックで、クロックリソースやブロックRAMにハード的な限界を超える速度を要求したときに失敗します。これはRTLで何をしても直りません。
Quartusはクロックごとのfmaxを出力します。これは目標ではなく情報として扱ってください。現在の配置での最悪経路から導かれる値であり、また論理ではなくハードブロックの限界が上限になっている場合には「restricted Fmax」も表示されます。
何を最初に直すか
次の順序で進めてください。該当する最初のステップで止まれば、制約を直すだけで済んだはずの設計をパイプライン化してしまう事態を避けられます。
- 制約が実際のボードを記述していることを確認する。 クロック周期を発振器またはPLL出力と照合します。report_clocksに出てくるすべてのクロックが意図したもので、意図した周期になっていることを確認します。check_timingまたは未制約経路レポートを見ます。フォールスパスは本当に非同期境界をまたぐ経路にだけ追加し、一括のフォールスパスではなく /reference/reset-and-cdc-patterns/ のパターンを使います。
- セットアップ違反より先にホールド違反を解消する。 ホールド違反はあらゆる周波数で設計を壊すので、立ち上げ中にクロックを落として回避することができません。大きなホールド違反のほぼすべてはクロック経路か制約の誤りなので、ステップ1が終わっていればこのステップは速く済みます。
- WNSとTNSを一緒に読む。 WNSもTNSも小さければ局所的な問題で、最悪経路を直します。WNSが小さくTNSが大きければグローバルな問題で、個々の経路に手を付ける前に、report_high_fanout_netsやQuartusのファンアウトレポートで共通の原因を探します。
- 最悪経路を論理と配線の比率および深さで分類する。 配線が重く浅い経路なら、レジスタ複製でファンアウトを減らす、2つのブロックを近づけるようにフロアプランする、またはデータパスのレジスタからリセットを外します。論理が重く深い経路なら、パイプラインレジスタを追加するか、キャリーチェーンが短くなるように演算を再構成します。
- RTLを変更するのはその後。 パイプライン化はレイテンシを変え、レイテンシは周囲の制御論理を変えます。深い経路に対しては正しい修正ですが、検証コストが最も高い修正でもあります。
ツールで実際に試せる経路
以下のモジュールは、3つの32ビット入力をレジスタ間で1サイクルに加算します。ほとんどのデバイスでは、連結された2つのキャリー伝搬と配線で3 nsから4 nsの間に収まり、100 MHzでは余裕で通過し、300 MHzでは失敗します。
module sum3 #(
parameter integer W = 32
) (
input logic clk,
input logic rst,
input logic [W-1:0] a,
input logic [W-1:0] b,
input logic [W-1:0] c,
output logic [W-1:0] y
);
logic [W-1:0] a_q, b_q, c_q;
always_ff @(posedge clk) begin
if (rst) begin
a_q <= '0;
b_q <= '0;
c_q <= '0;
y <= '0;
end else begin
a_q <= a;
b_q <= b;
c_q <= c;
y <= a_q + b_q + c_q;
end
end
endmodule
300 MHz向けに制約を付け、最悪経路を報告させます。
create_clock -period 3.333 -name clk [get_ports clk]
set_input_delay -clock clk 0.500 [get_ports {a[*] b[*] c[*]}]
set_output_delay -clock clk 0.500 [get_ports {y[*]}]
レポートには、入力レジスタの1つから2本のキャリーチェーンを経由してyのあるビットに至る経路が表示されます。各キャリー要素が1段として数えられるため、Logic Levelsの値は数十になり、論理のパーセンテージは高くなります。これがパイプライン化の問題の典型的な兆候で、2つ目のバージョンでは加算を2サイクルに分割することで解決しています。VHDL版を示しているのは、どちらの言語でも構造は同一で、解析ツールはどちらからも同じネットリストを見るからです。
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity sum3_pipe is
generic (
W : positive := 32
);
port (
clk : in std_logic;
rst : in std_logic;
a : in std_logic_vector(W-1 downto 0);
b : in std_logic_vector(W-1 downto 0);
c : in std_logic_vector(W-1 downto 0);
y : out std_logic_vector(W-1 downto 0)
);
end entity sum3_pipe;
architecture rtl of sum3_pipe is
signal a_q, b_q, c_q : unsigned(W-1 downto 0);
signal ab_q, c_d : unsigned(W-1 downto 0);
begin
process (clk)
begin
if rising_edge(clk) then
if rst = '1' then
a_q <= (others => '0');
b_q <= (others => '0');
c_q <= (others => '0');
ab_q <= (others => '0');
c_d <= (others => '0');
y <= (others => '0');
else
a_q <= unsigned(a);
b_q <= unsigned(b);
c_q <= unsigned(c);
ab_q <= a_q + b_q;
c_d <= c_q;
y <= std_logic_vector(ab_q + c_d);
end if;
end if;
end process;
end architecture rtl;
パイプライン化した版でレポートを再実行すると、最悪経路に含まれるキャリーチェーンは1本になります。レイテンシは2サイクルから3サイクルに増えており、周囲の論理はこれを考慮しなければなりません。それを確実にするには、正しいサイクルで和をチェックするテストベンチを、/learn/beginner/06-simulation-and-testbenches/ の手法を使って書きます。
演習
上記のパイプライン化していないsum3モジュールを、手持ちの任意のボード向けに、300 MHzの制約を付けてVivadoまたはQuartus Primeでビルドしてください。WNS、TNS、Logic Levelsの値、最悪経路の論理のパーセンテージを記録します。次にsum3_pipeに切り替え(VHDLからVerilogを自分で書くか、その逆)、同じ4つの数値を記録します。最後に、パイプライン化していない版の制約を100 MHzに変更し、ホールドスラックを300 MHzの実行結果と比較します。ホールドスラックが変わらなかった理由を一文で説明してください。ビルドとレポートの読み方の手順は /learn/beginner/10-from-rtl-to-board/ にあります。
まとめ
- 解析ツールはクロック同期のすべての経路を列挙し、刺激もシミュレーションもなしに、最悪ケースの到着時刻をキャプチャクロックと比較します。
- セットアップスラックは要求時刻から到着時刻を引いたもので、周期に依存します。ホールドスラックは到着時刻から要求時刻を引いたもので、周期に依存しません。
- レポートでは、Logic Levelsと論理・配線のパーセンテージが、パイプライン化すべきか、配置とファンアウトを直すべきかを教えてくれます。
- WNSは最悪経路がどれだけ足りないかを示し、TNSはいくつの経路が足りないかを示します。小さなWNSと大きなTNSはグローバルな原因を示唆します。
- まず制約と未制約経路を確認し、次にホールド、次にセットアップを見て、RTLを変更するのは経路が本当に深すぎるときだけにします。