コンテンツにスキップ

Synchronizer

Name In/Out Width Description
CLK In 1 メインクロック
SYNC_SETTINGS In -
├─ UPDATE In 1 同期フラグ
└─ SYNC_TIME In 64 同期するデバイス時刻 (SYS_TIME 単位)
SYNC_IN In 1 同期パルス
SYS_TIME Out 57 システム時刻
SYNC Out 1 同期パルス検出フラグ
SKIP_ONE_ASSERT Out 1 1 飛ばしアサート
SYNC_TIME_DIFF Out 14 補正残量 (符号あり)
SYNC_RESYNC_COUNT Out 8 位相を取り直した回数 (255 で飽和)
PS_ENABLE In 1 細調整の有効フラグ
PS_FINE Out 1 細調整中フラグ
PS_EDGE Out 1 細調整のループを更新する同期パルスの検出
PS_EDGE_DIFF Out 14 その同期パルスでの位相カウンタのずれ (符号あり)

Synchronizer は CPU のデバイス時刻に同期した時刻 SYS_TIME を生成するモジュールである. 超音波出力, Modulation, Emission 等はすべてこの SYS_TIME を時刻の基準にしている. デバイス時刻がデバイス間で同期していれば, それらも必然的にすべてのデバイスで同期する.

このモジュールの動作を理解するためには, 以下の事柄を理解しておく必要がある.

  • デバイス時刻 (CPU ボードの ETHSW タイマの時刻) は 単位の データである.
    • なお, 時刻基準は Client を開いた時点である. 単位の なので約 600 年しか使えないが, 事実上問題ない.
  • FPGA からは CPU ボードのデバイス時刻に直接アクセスできない.
  • CPU ボードは一定の周期で同期パルス (SYNC_IN) をアサートする. このパルスの立ち上がりはデバイス時刻を参照している.
  • 同期パルスの周期は 固定である. FPGA はこれを SYS_TIME の単位に直した値 () を定数 params::SyncCycleTicks として持つ

なお, SYS_TIME はデバイス時刻に同期しているが, その単位は で, となっている.

SYS_TIME の初回の時刻同期は CPU ファームウェアと協調して, 以下の手順で行われる.

  1. CPU: 次に同期パルスが立ち上がるデバイス時刻を求め, 以下の計算により SYS_TIME の単位に変換する.
  2. CPU: 変換した値を SYNC_TIME として FPGA 内の BRAM に書き込む.
    • ただしこの時, 現在時刻が立ち上がりの 1 周期以上前であれば, いったん待機する. これにより, 次の UPDATE フラグが 1 つ手前の立ち上がりより後に届くことを保証する.
  3. CPU: 同期の更新要求 (SYNC_SET) を FPGA 内の BRAM に書き込む. Controller がこれを読んで SYNC_TIME を取り込み, UPDATE フラグをアサートする.
  4. FPGA: UPDATE フラグを受けた後, 最初に SYNC_IN が立ち上がったタイミングで, SYNC_TIME を SYS_TIME にセットする.
  5. CPU: デバイス時刻を読み直し, UPDATE フラグが書き込んだ立ち上がりより前に届いたことを確かめる. 間に合っていなければエラー (ERR_SYNC_MISSED) を返す (やり直すには Synchronize を送り直す).

以上により, FPGA 内部の SYS_TIME の時刻がデバイス時刻に同期する.

あとは, SYS_TIME をメインクロックでカウントアップすればいい. しかし, 実際には FPGA のクロックを生成する水晶振動子には個体差があるため, SYS_TIME は徐々にずれていってしまう. 定期的に上記と同様のことを行って補正してもいいが, それは大変なので, 同期パルスが周期的に立ち上がるという性質を用いた別の補正方法を採用している.

同期パルスが立ち上がった際, SYS_TIME は前回の立ち上がりのときの値に, 同期パルスの周期 (SyncCycleTicks) を足した値になっているはずである. そこで, メインクロックでインクリメントされ, SyncCycleTicks で 0 に戻る位相カウンタを持つ. このカウンタは, ずれが無ければ立ち上がりのときに 0 に戻っているはずである. ずれがあると, 同期パルスの立ち上がりの時点のカウンタの値 が0以外になり, 補正すべき量が決まる. 正の値は SYS_TIME が遅れていること, 負の値は進んでいることを表す.

SYS_TIME の補正は, SYS_TIME が基準より進んでいる場合 (即ち, 水晶振動子の周波数が基準より高い場合) は SYS_TIME のインクリメントを止め, SYS_TIME が遅れている場合 (即ち, 水晶振動子の周波数が基準より低い場合) は SYS_TIME を することにより実行する. そのため, SYS_TIME は逆戻りすることはないが, 一つ飛ばしでカウントアップされる可能性がある. 一つ飛ばしが発生した際は SKIP_ONE_ASSERT をアサートする.

補正の残量は SYNC_TIME_DIFF として出力され, デバッグ用に GPIO Output から観測できる.

立ち上がりの時点のずれが を超えている場合 (同期パルスが張り直された場合など) は, 少しずつ補正するのをやめ, 位相カウンタをその立ち上がりに合わせ直す.

初回同期の後に位相カウンタをリセットした回数は SYNC_RESYNC_COUNT として出力される.

補正を同期パルスの立ち上がり直後に集中して行うと, 同期パルスの周期に由来する周期的なノイズが生じる. 同期パルスの周期は なので, 補正の集中により生じるノイズの周波数は である. これはヒトの聴覚が比較的敏感な周波数であるため, 補正タイミングを疑似乱数を用いて分散している. 補正は平均 3125 クロックに 1 回 (疑似乱数により 1077–5172 クロックの間隔) の頻度で, 1 回につき SYS_TIME の 1 単位ずつ行う.

(とはいえ実際にはこのノイズはほとんど聞こえない)

上記の補正は SYS_TIME を 1 単位 () ずつ動かすだけで, メインクロックそのものは各デバイスの水晶振動子で自走している. そのため, 出力のエッジは各デバイスの水晶振動子由来のグリッドに乗り, グリッド同士の位相はデバイス間でずれ続ける. PTP で同期パルスがいくら揃っていても, デバイス間の出力のずれは 1 単位幅の一様分布 (標準偏差 ) に, 補正の段差 ( 単位程度) が重なったものになる (4 台の実測で, 標準偏差約 , peak-to-peak 約 ).

細調整は, メインクロックを生成する MMCM の dynamic fine phase shift でクロックの位相そのものを動かし, 同期パルスの立ち上がりにクロックの立ち上がりを合わせる (位相ロック). 既定で有効で, SetFpgaConfig の sync_phase_lock で無効にできる. 4 台の実測で, デバイス間の出力のずれは標準偏差約 , peak-to-peak 約 になった.

MMCM は入力 を 40 逓倍して VCO を とし, 50 分周したメインクロック (, fine phase shift 付き) と, 5 分周した PSCLK (, シフトなし) を出す. 位相シフトの 1 ステップは VCO 周期の 1/56 (約 ) で, メインクロックの 1 周期は 2800 ステップに当たる. 同じ向きに回し続けられるので, ステップを出す頻度でクロックの周波数を変えられる. PSEN から PSDONE までは 12 PSCLK かかり, 1 ステップは 14 PSCLK ごとに出せる (追従できる周波数差は最大約 ).

細調整中は上記の SYS_TIME の補正を止め, 同期パルスの立ち上がりで見た位相カウンタのずれ から誤差 を作る. クロックの立ち上がりが同期パルスの立ち上がりに重なると, 検出の位相は と で半々になり, は を行き来する (bang-bang の位相検出). 同期パルス自体の揺れ (数 ns) がディザになるので, 平均すると 1 単位より細かく合わせられる.

同期パルスごと () に PI でステップのレートを決め, PSCLK 側の位相累算器がそのレートでステップを出す. レートの 1 単位は PSCLK () あたり 1 ステップ (約 ) で, (約 ) で飽和させる. で細調整に入り, でやめる. 微調整をしていない間は通常の補正で位相を合わせつつ, 積分器で周波数を推定する.

を で飽和させ, 同期パルスごとに積分器 とレート を次のように更新する.

  • 細調整中: ,
  • 細調整外で : ,
  • それ以外: 更新しない

は で飽和させる. , , は SetFpgaConfig の sync_kp, sync_ki, sync_kf で設定する (既定値は 64, 1024, 4096). 細調整に入ってから安定するまでは約 かかる.