FPGA タイミング制約シミュレータを作った — 波形・スラック・XDC をリアルタイム可視化
タイトルを更新しました 4/5
はじめに
FPGA設計で set_input_delay / set_output_delay を書くとき、「この値どこから来てるんだっけ?」と毎回なります。
データシートから Tco を拾って、ボード遅延を足して、セットアップ/ホールドのマージンを計算して……。
頭の中だけで追うのは辛いので、パラメータを動かしながらリアルタイムに波形・スラック・XDC制約を確認できるツールを作りました。
Live Demo: https://MameMame777.github.io/fpga-timing-simulator/
GitHub: https://github.com/MameMame777/fpga-timing-simulator
できること
| 機能 | 説明 |
|---|---|
| タイミング解析 | Setup/Hold スラックをリアルタイム計算。パラメータ変更で即座に Pass/Fail 判定 |
| 波形アニメーション | Launch Edge → データ伝搬 → Capture Edge の一連の流れを Canvas で可視化 |
| タイミングダイアグラム | クロックエッジ、データウィンドウ、スラックを SVG で静的表示 |
| XDC 制約生成 |
create_clock、set_input_delay、set_output_delay をコピー可能な形で出力 |
| System / Source Sync | 共通クロック方式とフォワードクロック方式の両方に対応 |
| SDR / DDR | R→R, R→F, F→R, F→F の全エッジコンボに対応 |
| Independent Capture Clock | Capture側クロックを Launch側と独立に設定可能(period/duty/port) |
| Routing min/max | FPGA内部ルーティング遅延を min/max で入力し、解析式とXDCへ反映 |
| ジッタ / 不確実性 |
set_input_jitter, set_system_jitter, set_clock_uncertainty をモデリング |
なぜ作ったか
タイミング制約ってわかりにくい!
- Input/Output Path の制約を書く際、どの値がどの XDC コマンドに対応するのかが分かりにくい
- Source Synchronous でフォワードクロックを使う場合の delay 計算が system sync と異なる
- DDR や falling edge キャプチャの
-clock_fallフラグの付け忘れに気づきにくい - Launch/Capture クロックを分離したときのエッジ関係(特に F→R / R→F)を直感的に把握しづらい
このツールでは各パラメータをスライダーで動かすと、波形・スラック値・XDC テキストが即座に連動して更新されるので、制約の意味を体感的に理解できます。
技術スタック
React 19 + TypeScript 5.9 (strict) + Vite 8
テスト: Vitest (36ケース)
デプロイ: GitHub Pages (GitHub Actions 自動デプロイ)
ランタイム依存: React / React-DOM のみ
アーキテクチャ
src/
├── types/timing.ts ← ドメイン型定義 (純粋 interface)
├── engine/timing-analyzer.ts ← 計算ロジック (純粋関数, UI非依存)
├── utils/xdc-generator.ts ← XDC制約テキスト生成 (純粋関数)
├── components/ ← React UIコンポーネント
├── canvas/WaveformRenderer.ts← Canvas 2D アニメーション
└── svg/ ← SVG 可視化コンポーネント
ポイントは計算ロジック (engine) を純粋関数として UI から完全に分離していること。これにより:
-
analyzeInputPath()/analyzeOutputPath()を Vitest で直接テスト可能 - 将来 CLI ツールや別の UI フレームワークへの移植が容易
Setup / Hold スラック計算の詳細
タイミング解析の核心部分を解説します。
System Synchronous (共通クロック)
// データ到着時間
dataArrivalMax = tcoSourceMax + boardDelayMax + routingDelayMax
dataArrivalMin = tcoSourceMin + boardDelayMin + routingDelayMin
// Required time (制約側)
setupRequired = effectiveWindow + skew − tsu − totalUncertainty
holdRequired = skew + th + totalUncertainty
// スラック (正=OK, 負=Violation)
setupSlack = setupRequired − dataArrivalMax
holdSlack = dataArrivalMin − holdRequired
effectiveWindow はエッジコンボで変わります:
| Launch → Capture | effectiveWindow |
|---|---|
| R→R | T (= period) |
| R→F | T × duty |
| F→R | T × (1 − duty) |
| F→F | T |
Source Synchronous (フォワードクロック)
Source sync ではクロックがデータと一緒に伝搬するため、Required time の計算式が変わります。
なお データ到着時間 (dataArrival) は System Sync と同一です — Source sync で相殺されるのは XDC の set_input_delay 式上の board delay であり、解析エンジン内部ではデータ経路全体(tco + boardDelay + routingDelay)を追跡しています。
// データ到着時間(System Sync と同一)
dataArrivalMax = tcoSourceMax + boardDelayMax + routingDelayMax
dataArrivalMin = tcoSourceMin + boardDelayMin + routingDelayMin
// Required time: フォワードクロックの到着時間が加わる
setupRequired = effectiveWindow + clockArrivalMin + skew − tsu − totalUncertainty
holdRequired = clockArrivalMax + skew + th + totalUncertainty
ポイント: Setup には clockArrivalMin(クロック早着 = マージン減)、Hold には clockArrivalMax(クロック遅着 = 制約厳しい)を使います。
Independent Capture Clock(Captureクロック分離)
Captureクロックを独立させると、computeEdgeTimes は以下のルールで有効キャプチャエッジを決めます。
// launchTime より後に来る、指定極性の最初の capture edge を採用
captureTime = firstCaptureEdgeAfter(launchTime)
effectiveWindow = captureTime - launchTime
同じ period/duty を設定しても、独立Captureモードでは「captureクロックとして別扱い」で描画・解析します。
XDC 制約生成
パラメータを入力すると、そのまま Vivado に貼れる XDC を自動生成します:
# Clock definition
create_clock -period 10.000 -name sys_clk [get_ports sys_clk]
set_input_jitter [get_clocks -of_objects [get_ports sys_clk]] 0.100
set_system_jitter 0.050
set_clock_uncertainty 0.100 [get_clocks sys_clk]
# Input delay constraints (System Synchronous)
# input_delay_max = tco_max(2.0) + board_delay_max(1.0) + routing_delay_max(0.5) = 3.500
set_input_delay -max 3.500 -clock sys_clk [get_ports data_in]
# input_delay_min = tco_min(1.0) + board_delay_min(0.3) + routing_delay_min(0.2) = 1.500
set_input_delay -min 1.500 -clock sys_clk [get_ports data_in]
# Output delay constraints
# output_delay_max = board_delay_max(1.0) + routing_delay_max(0.5) + tsu_dest(2.0) = 3.500
set_output_delay -max 3.500 -clock sys_clk [get_ports data_out]
# output_delay_min = board_delay_min(0.3) + routing_delay_min(0.2) - th_dest(0.3) = 0.200
set_output_delay -min 0.200 -clock sys_clk [get_ports data_out]
System Sync vs Source Sync の出力の違い
| System Sync | Source Sync | |
|---|---|---|
| input_delay の基準クロック | システムクロック | フォワードクロック |
| input_delay_max の式 | tco_max + board_delay_max + routing_delay_max + skew |
tco_max + routing_delay_max + skew |
-clock 引数 |
sys_clk |
fwd_clk |
Source sync では XDC の set_input_delay 式において データとフォワードクロックが同じボード上を伝搬するため board delay が相殺されます(forwarded clock 基準で記述するため)。一方で、FPGA内部ルーティング遅延は相殺されないため routing_delay は両モードで寄与します。
-clock_fall フラグの落とし穴
Capture edge が falling の場合、-clock_fall を付けないと Vivado は rising edge 基準で解析します。結果として本来 fail のパスが通ってしまう偽陽性が発生しうるので注意してください。
# Falling edge capture の場合
set_input_delay -max 3.500 -clock_fall -clock sys_clk [get_ports data_in]
可視化の技術的な工夫
Canvas vs SVG の使い分け
| 技術 | 用途 | 理由 |
|---|---|---|
| Canvas 2D | 波形アニメーション |
requestAnimationFrame で 60fps 描画。ピクセル単位の制御が必要 |
| SVG (React) | タイミング図・パスブロック図 | 宣言的レンダリング。DevTools で DOM として検査可能 |
GUIからパラメータを変更できます。リアルタイムにタイミングチャートに反映されます。

タイミングダイアグラム
SVG で描画した静的タイミング図。クロックの立ち上がり/立ち下がりエッジ、データ到着ウィンドウ、Setup/Hold スラックを1枚に集約しています。
Output Path の解析
Output Path では FPGA 内部の tco(Clock-to-Output)が起点となります:
// データ到着時間
dataArrivalMax = fpga.tcoMax + boardDelayMax + routingDelayMax
dataArrivalMin = fpga.tcoMin + boardDelayMin + routingDelayMin
// Required time(相手デバイスの tsu/th 基準)
setupRequired = effectiveWindow + skew − tsuDest − totalUncertainty
holdRequired = skew + thDest + totalUncertainty
Input Path と構造は同じですが、起点が外部デバイスの tco ではなく FPGA の tco に、制約のタイミング要件が FPGA の tsu/th ではなく相手デバイスの tsu/th になる点が異なります。
テスト設計
タイミング計算のようなドメインロジックは、手計算した期待値をコメントに残すのが重要です:
it('basic R→R: positive setup slack', () => {
const r = analyzeInputPath(baseClock, baseInput, baseFPGA, 'system_sync', RR);
// setupRequired = window(10) + skew(0) - tsu(1.2) - uncertainty(0) = 8.8
// dataArrivalMax = tcoMax(2) + boardMax(1.5) = 3.5
// setupSlack = 8.8 - 3.5 = 5.3
expect(r.setupSlack).toBeCloseTo(5.3);
});
テストケース 36件の内訳:
| describe グループ | 件数 | 内容 |
|---|---|---|
computeEdgeTimes |
6 | 4エッジコンボ + 非対称 duty (30%) + 極小 period (1ns) |
analyzeInputPath — system sync |
10 | setup/hold 正常・violation・スキュー・ジッタ・routing 寄与 等 |
analyzeInputPath — source sync |
4 | fwd clock 到着・clockSkew 平均・非対称遅延・ユーザskew 累積 |
analyzeOutputPath — system sync |
5 | 基本 R→R / hold / uncertainty / F→R / routing 寄与 |
analyzeOutputPath — source sync |
1 | source sync output path |
| エッジケース | 4 | ゼロジッタ・R→R=F→F 対称性・R→F+F→R 総和・全数値 finite |
| independent capture clock | 6 | 高速/低速 capture period・edge 探索・window 反映 |
| 合計 | 36 |
GitHub Pages へのデプロイ
Vite の base パス設定
// vite.config.ts
export default defineConfig({
plugins: [react()],
base: '/fpga-timing-simulator/',
})
GitHub Pages は https://<user>.github.io/<repo>/ のサブパスから配信されるため、base を設定しないとアセットの読み込みが壊れて白画面になります。
GitHub Actions ワークフロー
# main push 時に自動デプロイ
jobs:
build:
steps:
- npm ci → npm test → npm run build
- actions/upload-pages-artifact (dist/)
deploy:
needs: build
uses: actions/deploy-pages@v4
Settings → Pages → Source で GitHub Actions を選択するのを忘れずに。
まとめ
- FPGA I/O タイミング制約の計算過程をインタラクティブに可視化するツールを作りました
- パラメータを動かすと波形・スラック・XDC がリアルタイムに連動して更新されます
- System Sync / Source Sync / DDR / independent capture clock / routing min-max / ジッタ・不確実性まで対応
- 計算ロジックを純粋関数として分離し、36件のユニットテストで品質を担保
FPGA のタイミング制約を学習する際や、実際の設計でパラメータの影響を素早く確認したい場面で活用していただければ幸いです。
Live Demo: https://MameMame777.github.io/fpga-timing-simulator/
GitHub: https://github.com/MameMame777/fpga-timing-simulator
フィードバックや改善提案は GitHub Issues でお待ちしています。


