映像伝送機器間の同期～高精度なIP時刻同期プロトコル～

# はじめに

インテル® チタンパートナーであるアイベックステクノロジーは、放送局など映像伝送市場に向けて、自社 IP コアを FPGA に搭載した製品や組み込みボードを提供しております。

放送の現場ではカメラ、スイッチャー、モニター等全ての機器が同期して動くことが求められます。複数の映像入力を組み合わせて製作するテレビ番組、特に生放送番組には高精度な同期が不可欠です。映像を途切れさせることなくカメラを別視点のものに切り替えられるのは、全ての機器が同じタイミングで動作している、すなわち同期しているからです。

放送局では従来、映像や音声、さらにそれらを伝送する機器を同期させる基準信号は同軸ケーブルを用いて 1 対 1 で伝送していました。しかし、2015 年には同軸ケーブルの代わりに多対多の IP ネットワークで伝送する規格の一つ SMPTE ST 2110 が策定されました。この規格では機器の同期にも IP ネットワークを利用する PTP（Precision Time Protocol）という高精度な時刻同期プロトコルを使用します。これらに対応する機器が現れてからは放送局の IP 化の動きが広がっています。

この記事ではまず、放送局の IP 化の前後を比較しながら映像伝送と時刻同期の関係を概説し、時刻同期に使用されるプロトコルである PTP について紹介します。記事の後半では Stratix® 10 FPGA に PTP パケットから時刻情報を取り出す機能を実装し、PTP を使った同期に必要な信号を Signal Tap ロジック・アナライザーを使って観測する実験を行います。

# 映像伝送から時刻同期まで

## 映像伝送と同期

従来の放送局における伝送システムの構成を簡単に示したものが図 1 です。ここでは、複数のカメラの映像をスイッチャーと呼ばれる装置で切り替えて出力する部分に注目します。なお、スイッチャーが出力した映像は、その後テロップや効果音等が加えられて、テレビ番組として送出されます。複数の機器が基準となる一つのタイミングに合わせて動作している時、それらの機器は同期していると言えます。従来の放送局ではシンクジェネレーターという機器から基準信号を出力し、各機器にそれを分配することで同期を行っていました。この基準信号は BB（Black Burst）と呼ばれ、同軸ケーブルで入力します。また、映像は SDI（Serial Digital Interface）と呼ばれるインターフェースを用いて伝送し、この伝送に使用されるのも同軸ケーブルです。図 1 のような構成において、機器の同期で必要となるのは、① 送信機同士の同期、② 送信機と受信機の同期、の 2 種類です。

![伝送システムの構成-IP化前.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2984339/fc8b04b8-d2ce-4b98-7abe-fa2a43e478c8.png)\
図 1 従来の放送局における伝送システム

### ① 送信機同士の同期

送信機同士の同期は、映像を乱さず切り替えるために必要です。この同期に関わるのが図 1 中の緑色の点線で囲った部分です。例として、図 2 のように、3 台のカメラ A、B、C を使ってテレビ番組制作を行う場面を想定します。映像はフレームと呼ばれる静止画の連続でできていて、別のカメラからの映像との切り替えはこのフレームとフレームの間の決まったタイミング（SMPTE RP 168 で規定）で行います。全てのカメラが BB を基準に同期することで、切り替えのタイミングが揃い、映像が乱れることなくカメラを別視点のものに切り替えることが可能になります。

![送信機同士の同期.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2984339/760737f6-a15c-382e-db6f-fd514ab2f295.png)\
図 2 送信機同士の同期

### ② 送信機と受信機の同期

送信機同士の同期が複数の映像の切り替えにおいて重要である一方、送信機と受信機の同期は 1 つの映像を乱れることなく伝送するために必要です。この同期に関わるのは図 1 中の水色の点線で囲った部分です。簡単化のために、カメラ 1 台とスイッチャー 1 台の関係を取り上げたものが図 3 です。受信機は通常、伝送時のジッターの影響を吸収するために内部にバッファーを持ち、映像データをある程度溜めてから取り出し、切り替え等の処理を行います。バッファーの入出力のペースが釣り合わず、データが溢れたり枯渇したりした場合、映像の乱れが発生します。カメラが BB に同期して映像を送り、スイッチャーが BB に同期してバッファーからデータを取り出すことで、バッファーの入出力のペースを揃えることができ、映像を乱さず伝送することが可能になります。

![送信機と受信機の同期.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2984339/f7920765-a5fe-d8cd-edb8-1058cfadf760.png)\
図 3 送信機と受信機の同期

## 放送局の IP 化

図 1 で示した伝送システムでは、映像信号や BB は同軸ケーブルを用いて各機器に入力していました。しかし、近年ではそれを IP に置き換える動きが進んでいます。IP 化により、映像と同期は物理的に同じケーブルを共有することができ、配線が簡潔になる等のメリットがあります。図 1 に示した放送局のシステムを IP 化した場合、図 4 のような構成になります。カメラとスイッチャーの論理的な接続関係や、先に述べた、送信機同士の同期、送信機と受信機の同期が必要であることは IP 化後も変わりません。

![伝送システムの構成-IP化後.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2984339/1f1dde91-1a12-4161-db7f-93a2adedfe01.png)\
図 4 IP 化された放送局における伝送システム

IP 化されたシステムでは、映像や音声といったメディアは SMPTE ST 2110 等の規格に則ってパケットで伝送し、機器間の同期は PTP というプロトコルで行います。同期の基準を BB から PTP に置き換えると、カメラは PTP に同期して映像を出力し、スイッチャーも PTP に同期して映像を受信します。ここで、PTP に同期していることは、PTP で時刻同期した高精度な時刻を元に生成したタイミングで動作することを表します。PTP での時刻同期には、同期のためのパケット（以下 PTP パケット）をそれぞれの機器が送受信できる他、同期の基準時刻を配信することができる PTP マスターが必要です。これは、図 3 でシンクジェネレーターを用いて BB を生成していた部分に相当します。

## 機器の時刻と PTP 同期

PTP は時刻を同期するプロトコルです。基準時刻を持つ機器をマスターと呼び、マスターと PTP パケットを送受信することで基準時刻と内部の時刻のオフセットを計算し、内部の時刻を調整する機器をスレーブと呼びます。図 4 でカメラとスイッチャーは PTP スレーブに相当します。PTP スレーブは SMPTE ST 2059 に従って PTP で同期した高精度な時刻から映像の送受信タイミングを算出し、それによって、PTP スレーブ同士は同じタイミングでデータを送受信することができます。このように、時刻の基準を 1 つ用意し、全ての機器がそれに同期することで、システム全体の同期が実現されるのです。

## PTP で同期する機器の構成

IP 化されたシステムを構成する機器の一つに、映像パケットを IP で受信し、マスモニと呼ばれるモニター機器等に入力するために SDI で出力する機器が考えられます。この機器を例に、PTP で同期する機器の構成とその同期の過程を紹介します。

![システム構成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2984339/edf92d8e-1a1e-9af0-4808-3262b7ea6ec9.png)\
図 5 映像パケットを受信して映像信号を出力する機器の構成

図 5 はある PTP スレーブ機器の構成を想定したものです。内部の時刻をインクリメントするための時刻同期用クロックを持ち、このクロックの周波数を上げたり下げたりすることで、時刻をマスターに同期します。この周波数の調整はマスターとのオフセット計算の結果をもとに行い、時刻がマスターより進んでいる場合は周波数を下げ、遅れている場合は周波数を上げます。また、同期開始時等マスターとの時刻の差が大きい場合は現在時刻を直接修正します。これらの動作を実現するため、機器は以下の a ～ d の機能を持ちます。

a. PTP パケットの送受信\
b. 時刻情報の取得\
c. マスターとのオフセットの計算\
d. オフセットを使った周波数の調整と現在時刻の修正

オフセット計算と周波数の調整を繰り返すことで、機器は随時マスターに時刻を同期することができます。詳細な同期の過程は後ほど紹介します。

さらに、モニターが映像を表示するためには、

- 垂直同期（映像のフレーム開始タイミング）
- 水平同期（映像の 1 ラインの開始タイミング）
- 映像データ（表示する色等の情報）
- ビデオクロック（上 3 つの信号を送るクロック）

の信号が必要です。時刻同期用クロックの調整に追従して調整されるビデオクロックと、PTP で同期した時刻から算出した垂直同期、水平同期のタイミングを生成し、これらに合わせて映像データを出力することで、映像を PTP で同期することができます。

このような機器以外にも、カメラやスイッチャー等、PTP で同期する機器は他にもありますが、同期部分の仕組みと、同期したクロックを使用して目的の処理を行うという点は共通しています。

# 時刻同期プロトコル PTP

## 高精度な時刻同期の実現

PTP は IEEE の規格番号から IEEE 1588 とも呼ばれます。IP で時刻同期するプロトコルとしては NTP もよく知られていますが、これらのプロトコルの最大の違いは同期精度です。NTP による同期精度は数 10 ミリ秒以内ですが、それでは映像切替のタイミングを一致させることができず、受信機のバッファーの入出力のペースを一定に保つこともできないので、映像を乱さず伝送することができません。これに対して PTP は 1 マイクロ秒未満の精度で時刻同期することが可能です。このことから高精度な時刻同期を要求する金融・産業・電力システム等様々な分野での利用が想定されており、そのうち映像伝送における利用方法は SMPTE ST 2059 で補完されます。

PTP が高精度な同期を実現できる理由はおおまかに 2 つあり、その 1 つ目は厳密な時刻取り扱いです。PTP ではパケットの送受信時刻を取得するタイミングを厳密に決めることが推奨されており、そのためにはハードウェアレベルで PTP システムを構築することが要求されます。図 6 に示すように PTP パケットの送受信時刻は PTP に対応した機器（PTP ノード）と IP 回線の境界を越えたタイミングで取得します。具体的にはプリアンブルの直後、MAC フレームの先頭が境界を越えたタイミングです。

![PTPパケットの送受信時刻のイメージ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2984339/b9150fc6-68f5-700f-5f2a-eb4d6180892d.png)\
図 6 PTP パケットの送受信時刻のイメージ

2 つ目は PTP 対応スイッチによるほぼ理想的な伝送路の確保です。PTP で高精度な時刻配信と同期を行うには理想的な伝送路が求められます。これは通信の行きと帰りにかかる時間がそれぞれ同一であることを意味します。PTP ではスイッチを中継したことでかかった時間をパケットに記録しておく、あるいはスイッチが時刻を再分配することが規定されていて、これらの処理に対応したスイッチをそれぞれ TC スイッチ、BC スイッチと呼びます。2 つは PTP 対応スイッチと総称し、これを使用することで、スイッチで中継される時間の影響を受けずに時刻同期を行うことができるようになります。

## PTP ノードによるネットワーク

先に述べたように、PTP による時刻同期はマスターからの配信とスレーブでの調整によって行われます。この時、図 7 に示すように、マスターが配信する時刻に、その上流にあるマスターから受信して同期した時刻が使われていることがあります。このようにマスターを辿っていくと GM（Grandmaster Clock）と呼ばれる時刻の配信源に辿り着きます。GM は PTP ノードから時刻を受け取っているのではなく、原子時計や GPS に代表される GNSS のような時刻源から高精度な時刻を受け取り、それを直下のスレーブに配信しています。

![PTPによる時刻配信.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2984339/0ec92ac6-c167-e854-188d-bb77e58072e0.png)\
図 7 PTP による時刻配信

どの PTP ノードが GM、マスター、スレーブになるかは規格に定められたアルゴリズムで決まります。各ノードは、自身のアクセスできる時刻源やクロックの揺らぎの大きさ等の情報を Announce メッセージと呼ばれるパケットで送り合い、互いの時刻の配信源としての性能を把握します。そこから GM はどれか、マスターまたはスレーブのどちらの役割をすべきか、どのマスターから時刻を受信するか等を決定します。これを Best Master Clock Algorithm と呼びます。

## E2E 方式による時刻同期

マスターとスレーブの間の時刻同期の方式はいくつかありますが、代表的なものは E2E 方式です。E2E 方式では Sync メッセージ、Follow_Up メッセージ、Delay_Req メッセージ、Delay_Resp メッセージを使用します。Follow_Up メッセージは省くことができる場合もありますが、PTP ノード自体はどちらの場合にも対応できる機能を持たなければならないと SMPTE ST 2059 で規定されています。これらのメッセージの送受信を、マスターとスレーブの持つ時刻の例とともに示したものが図 8 です。

![マスターとスレーブ間のメッセージのやり取り.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2984339/fd6cab1d-348d-fcb0-52a6-c9fd9b55cf5f.png)\
図 8 マスターとスレーブの間のメッセージの送受信

スレーブは、以下の ① ～ ⑤ の順でそれぞれのメッセージの送受信を行い、オフセット計測のために必要な時刻 T1、t2、t3、T4 を取得します。

① マスターが Sync メッセージを送信します。\
② スレーブが Sync メッセージを受信した時刻 t2 を記録します。\
③ マスターが Sync メッセージを送信した時刻 T1 を Follow_Up メッセージに載せて送信します。\
④ スレーブが Delay_Req メッセージをマスターへ送信します。この時 Delay_Req メッセージを送信した時刻 t3 を記録します。\
⑤ マスターが Delay_Req メッセージを受信した時刻 T4 を Delay_Resp メッセージに載せて送信します。

T1、t2、t3、T4 から以下のようにオフセットが計算できます。

```math
t2 = T1 + Delay + Offset\\
t3 = T4 - Delay + Offset
```

より

```math
t2 + t3 = T1 + T4 + (Offset * 2)\\
Offset = \frac{(t2 - T1) + (t3 - T4)}{2}
```

例えば

```math
\{T1, t2, t3, T4\} = \{100[ns], 450[ns], 500[ns], 450[ns]\}
```

の時、オフセットは以下のように導かれます。

```math
Offset = ((t2 – T1) + (t3 – T4)) / 2 = 200[ns]
```

# 実験

## 実験環境

PTP マスターとの通信に必要な最低限の機能を実装し、前の章で紹介した時刻 T1、t2、t3、T4 を取り出して Signal Tap ロジック・アナライザーで確認します。実験には 10G SFP+ ポートを備えた Stratix® 10 FPGA 搭載ボードを使用し、MAC と PHY には PTP に対応した 25G Ethernet Intel® Stratix® 10 FPGA IP（以下 MAC IP）を使用します。この IP は 10G にも対応しています。合成、配置配線には Intel® Quartus® Prime Pro Edition version 19.4 を使用しました。マスターには BC スイッチである Mellanox SN2410 シリーズを使います。スイッチの仕様は公開情報なので省きます。

図 9 に実験環境の概観を示します。図中の実験用回路が今回の実験のために実装した回路です。時刻を取得する際に使用する MAC IP の信号は、青色、オレンジ色、緑色の矢印で表しています。

![実験環境概観.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2984339/34e3ec48-be15-0aca-c79d-ba00b89d6206.png)\
図 9 実験環境概観

## 実験の前提

実験を簡単にするため、今回は以下の前提を置いています。

### スイッチと実験対象のみのネットワーク

スイッチに実験対象の FPGA ボード以外の機器を接続しないことで、パケットの送信元、宛先のチェックを省きました。実際にはスイッチは GM と通信していますが、GM と実験機器は直接通信することはないので、スイッチと実験対象のみのネットワークとみなすことができます。

### VLAN タグでの PTP の通信のフィルター

本来 PTP パケットをフィルターするには、受信したパケットの UDP 宛先ポートを確認する必要があります。今回の実験においては、PTP の通信には VLAN ID 1588 の VLAN タグを使い、それ以外の通信にはこの VLAN ID を使わないことにしています。これにより VLAN タグから PTP の通信がフィルターできるため、UDP 宛先ポートのチェックを行っていません。

### マルチキャストグループへの参加

マスターからの PTP パケットを受信するためには、スレーブ機器は特定のマルチキャストグループへの参加をスイッチに伝える必要がありますが、既に参加しているものとします。

### Delay_Req メッセージ生成の簡略化

今回の実験は PTP パケットの観測を行いますが、スレーブからマスターに送信する Delay_Req メッセージ内のデータは時刻同期に直接関係がありません。固定値（0）で問題ないため、パケット生成に関する詳細は省略します。

### Announce メッセージの解析の省略

Announce メッセージは、マスターがどの方式でスレーブと同期するかを伝えるものですが、今回は E2E 方式で、かつ Follow_Up メッセージから T1 を取り出す（two-step モード）という前提を置き、解析は省略します。

## MAC IP の入出力

MAC IP は表 1 に示す入出力ポートを持ちます。
[25G Ethernet Intel® Stratix® 10 FPGA IP User Guide](https://www.intel.com/content/www/us/en/docs/programmable/683154/21-1-19-4-0/about-the.html) 6.1 節、6.2 節、6.7 節を参照し、今回の実験に関するものだけを紹介します。

表 1 MAC IP の入出力ポート
|信号名|I/O|クロックドメイン|説明|
|--|:--:|:--:|--|
|`clk_txmac`|O|　　　　　　　　|TX 信号のクロック|
|`clk_rxmac`|O|　　　　　　　　|RX 信号のクロック|
|`l1_tx_data`|I|`clk_txmac`|TX パケット|
|`l1_tx_valid`|I|`clk_txmac`|`l1_tx_data` が有効データ|
|`l1_tx_startofpacket`|I|`clk_txmac`|`l1_tx_data` はパケットの先頭|
|`l1_rx_data`|O|`clk_rxmac`|RX パケット|
|`l1_rx_valid`|O|`clk_rxmac`|`l1_rx_data` が有効データ|
|`l1_rx_startofpacket`|O|`clk_rxmac`|`l1_rx_data` はパケットの先頭|
|`tx_time_of_day_96b_data`|I|`clk_txmac`|TOD 信号|
|`rx_time_of_day_96b_data`|I|`clk_rxmac`|TOD 信号|
|`tx_egress_timestamp_request_valid`|I|`clk_txmac`|パケット送信時刻の要求パルス|
|`tx_egress_timestamp_96b_data`|O|`clk_txmac`|パケット送信時刻|
|`tx_egress_timestamp_96b_valid`|O|`clk_txmac`|`tx_egress_timestamp_96b_data` は有効データ|
|`rx_ingress_timestamp_96b_data`|O|`clk_rxmac`|パケット受信時刻|
|`rx_ingress_timestamp_96b_valid`|O|`clk_rxmac`|`rx_ingress_timestamp_96b_data` は有効データ|

## MAC IP への時刻供給

PTP スレーブは内部で時刻をカウントします。この時刻を 2 つのクロック `clk_rxmac` と `clk_txmac` のそれぞれに同期して MAC IP に供給する必要があります。供給する信号は TOD 信号、これを行うモジュールは TOD モジュールと呼ばれます。今回の実験ではこのモジュールの詳細は省略します。

## 実装方針

t2、t3 は、それぞれ MAC IP が出力するパケット受信時刻とパケット送信時刻を適切なタイミングで観測することで得られます。一方 T1、T4 はマスターから送られる PTP パケットの解析で得られます。パケットを受信し解析するためには、① 受信パケットから解析対象のパケットだけを取り出すフィルター、② 取り出されたパケットの蓄積、③ パケットの解析、をするモジュールが必要です。今回は、実験用に作成した環境で T1 と T4 にあたる信号を取り出すことだけを目的とし、① のフィルターと ② の蓄積のモジュールは汎用的な環境を考慮しない簡素化した実装にします。

## パケットフィルターの作成

先に述べたように、この実験環境では BC スイッチは PTP パケットだけを VLAN ID 1588 で送信するため、VLAN ID 1588 であれば PTP パケットとして判別するパケットフィルターモジュールを作成します。MAC IP からの出力は L2 イーサネットフレームです。モジュールはこれを入力とし、パケットの先頭から値を読み取っていきます。まずは先頭 12octet を読み飛ばし、続く 2octet 分の TPID が 8100h（以降、16 進数を接尾辞 h で表します）、つまり 802.1Q による VLAN タグが付与されていることを確認します。その後、続く 2octet の下位 12bit 分の VID が 1588 であれば PTP パケットとし、IP ヘッダや UDP ヘッダを読み飛ばして UDP ペイロードを読み取ります。UDP ペイロードの先頭から 34octet が PTP ヘッダで、ヘッダの先頭 1octet が表 2 で示す MessageType です。

表 2 PTP パケットの MessageType
| メッセージの種類 | MessageType (1octet) |
|---|---|
| Sync | 00h |
| Delay_Req | 01h |
| Follow_Up | 08h |
| Delay_Resp | 09h |

この MessageType によって、PTP パケットのメッセージの種類が判別できます。

## オフセット計算に必要な時刻を取り出す

### Signal Tap ロジック・アナライザーによる観測

オフセット計算に必要な時刻 T1、t2、t3、T4 は、図 9 の実験用回路の出力信号から得られます。これを Signal Tap ロジック・アナライザーで観測しました。T1、t2、t3、T4 の値が得られた状態を図 10 に示します。

![時刻情報観測.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2984339/afb7dd0a-5f97-13e3-e796-a41b2c6e5deb.png)\
図 10 Signal Tap ロジック・アナライザーで実験用回路の出力を観測した様子

以降では、実験用回路が MAC IP と通信し時刻を取り出す過程について述べます。

### t2 を取り出す

図 8 のように PTP パケットを送受信した時、最初に取り出せるのは t2 です。今回、T1 は Sync メッセージからではなく Follow_Up メッセージから取得するので、T1 より先に Sync メッセージの到着時刻である t2 が確定するためです。`l1_rx_startofpacket=1` かつ `rx_ingress_timestamp_96b_valid=1` の時、つまりパケット到着時に、 `rx_ingress_timestamp_96b_data` を t2 の候補としてラッチします。パケットを解析して、この時到着したパケットが Sync メッセージであれば、この値が t2 だと確定します。図 10 では ① で示した部分に該当します。

`rx_ingress_timestamp_96b_data` は 96bit ですが、下位 16bit は使いません。この 96bit の信号は V2 Format というフォーマットで、[25G Ethernet Intel® Stratix® 10 FPGA IP User Guide - 4.1.9.5. PTP Timestamp and TOD Formats](https://www.intel.com/content/www/us/en/docs/programmable/683154/21-1-19-4-0/ptp-timestamp-and-tod-formats.html) に詳細が記載されています。上位 48bit が秒を表し、続く 32bit がナノ秒を表します。下位 16bit はナノ秒より細かいスケールですがこれは使いません。上位 80bit を t2 としてオフセット計算に使います。

### T1 を取り出す

T1 は Follow_Up メッセージに載っているタイムスタンプから取得します。受信したパケットを解析して Follow_Up メッセージならばパケットを蓄積します。Follow_Up メッセージでは PTP ヘッダを除く先頭 10octet がタイムスタンプになっており、MSB 側 48bit が秒を表し、LSB 側 32bit がナノ秒を表します。このタイムスタンプが T1 で、図 10 の ② で示した部分がそれに該当します。この 80bit のタイムスタンプを T1 としてオフセット計算に使います。

### t3 を取り出す

t3 はスレーブから送信する Delay_Req メッセージの送信時刻そのものです。Delay_Req メッセージは、スレーブから送信する必要がある唯一のパケットですが、これに載せるタイムスタンプ自体は時刻同期に使われません。そのため、PTP ヘッダを除くペイロードのタイムスタンプ部には 0 を載せて送信します。この時、パケットの送信時刻を得るために、MAC IP にパケットを入力する際 `l1_tx_startofpacket` のアサートと同時に `tx_egress_timestamp_request_valid` を 1clk だけアサートします。これにより、MAC IP は送信時刻が確定すると `tx_egress_timestamp_96b_valid` をアサートするため、このタイミングで `tx_egress_timestamp_96b_data` をラッチすることで t3 が取り出せます。図 10 の ③ で示した部分がそれに該当します。t2 と同様に上位 80bit を t3 としてオフセット計算に使います。

### T4 を取り出す

T4 は Delay_Resp メッセージに載っているタイムスタンプから取得します。T1 が載っている Sync メッセージと少しペイロード形式は違いますが、タイムスタンプが載っている位置は変わらないため、同様に取り出します。図 10 では ④ で示した部分に該当します。この 80bit のタイムスタンプを T4 としてオフセット計算に使います。

### オフセットを手計算してみる

それでは、今回得られた時刻情報からオフセットを計算してみます。計算方法は[既にご紹介したもの](#e2e-方式による時刻同期)です。

```math
T1 = 63A4\_FE2Dh [s] + 13F3\_0AE7h [ns]\\
t2 = 0000\_0044h [s] + 07DD\_2159h [ns]\\
t3 = 0000\_0044h [s] + 08C9\_6EE7h [ns]\\
T4 = 63A4\_FE2Dh [s] + 14DF\_596Eh [ns]
```

```math
t2 - T1 = - 63A4\_FDE9h [s] - 0C15\_E98Eh [ns]\\
t3 - T4 = - 63A4\_FDE9h [s] - 0C15\_EA87h [ns]
```

```math
Offset = - 63A4\_FDE9h [s] - 0C15\_EA0Ah [ns]
```

こちらの機器はまだ時刻同期しておらず、内部の時刻が 1970 年になっているため、ここから大幅な修正が必要です。しかし、時刻同期の第一歩が踏み出せました。

# おわりに

放送局を例に挙げて、映像伝送と同期の関係、および時刻同期が PTP を使ってどのように行われるのかを最初に紹介しました。それから、PTP スレーブを簡易実装した FPGA 回路で、同期に必要な 4 つの時刻情報を観測する実験を行いました。インテル® の IP を使用することで時刻同期に必要な情報が簡単に取り出せますが、クロック調整の実装はユーザーに委ねられているため、実際に PTP を活用する場合はここが同期精度を左右する勘所となりそうです。
最後まで読んでいただきありがとうございました。

# 付録

以下は実験用回路の Verilog RTL ソースコードです。

```verilog
// ***************************************************************************
//  File Name   : ptp_tx_filt.v
//  Description : PTP関連信号取り出しモジュール
// ***************************************************************************

module ptp_tx_filt (
    //  クロック & リセット
    input                   xrst,
    input                   clk,

    //  パケット入力
    input                   i_sop,
    input                   i_eop,
    input                   i_valid,
    input  [63:0]           i_data,

    //  時刻
    input                   i_txing_req,    //  i_sop=1かつi_valid=1の時に有効
    input                   i_txing_vld,
    input  [95:0]           i_txing_data,

    //  VLAN
    output                  o_vlan_vld,     //  i_eop=1の時に有効
    output [11:0]           o_vlan_tag,     //  i_eop=1&o_vlan_vld=1の時に有効

    //  情報出力
    output                  o_ptp_vld,      //  PTPパケットだった (パルス)
    output                  o_ptp_dlrq,     //  PTP-Delay Reqパケットだった (パルス)
    output                  o_txing_vld,
    output [95:0]           o_txing_data
);

    reg                     r_ptp_dlrq;
    reg    [3:0]            r_ptp_mstp;
    reg                     r_ptp_vld;
    reg    [95:0]           r_txing_data;
    reg                     r_txing_req;
    reg                     r_txing_vld;
    reg    [11:0]           r_vlan_tag;
    reg                     r_vlan_vld;
    reg    [5:0]            r_word_cnt;
    wire                    w_cnt_add;
    wire                    w_cnt_clear;
    wire                    w_cnt_first;
    wire                    w_enb_ptp_mstp;
    wire                    w_enb_word2;
    wire                    w_enb_word6;
    wire                    w_ms_ptp_dlrq;
    wire                    w_ptp;
    wire                    w_ptp_dlrq;
    wire                    w_ptp_vld;
    wire                    w_set_txing_req;
    wire                    w_txing_vld;

    // 制御
        // ワード数カウンタ
    assign  w_cnt_first         =   i_valid & i_sop;
    assign  w_cnt_clear         =   i_valid & i_eop;
    assign  w_cnt_add           =   i_valid & ((r_word_cnt[5:0] != 0) ? 1'b1 : 1'b0);
    always @ (posedge clk or negedge xrst) begin
        if (!xrst)                  r_word_cnt[5:0] <= 6'd0;
        else if (w_cnt_clear)       r_word_cnt[5:0] <= 6'd0;
        else if (w_cnt_first)       r_word_cnt[5:0] <= 6'd1;
        else if (w_cnt_add)         r_word_cnt[5:0] <= r_word_cnt[5:0] + 6'd1;
    end
        // ワード取り込みパルス
    assign  w_enb_word2         =   (r_word_cnt[5:0] == 6'd1)  ? i_valid : 1'b0;
    assign  w_enb_word6         =   (r_word_cnt[5:0] == 6'd5)  ? i_valid : 1'b0;

    // VLAN取り出し (2ワード目にVLANが存在する想定)
    always @ (posedge clk or negedge xrst) begin
        if (!xrst) begin
            r_vlan_vld          <=  1'b0;
            r_vlan_tag[11:0]    <=  12'd0;
        end else if (w_enb_word2) begin
            r_vlan_vld          <=  (i_data[31:16] == 16'h8100) ? 1'b1 : 1'b0;
            r_vlan_tag[11:0]    <=  i_data[11:0];
        end
    end
    assign  o_vlan_vld          =   r_vlan_vld;
    assign  o_vlan_tag[11:0]    =   r_vlan_tag[11:0];

    // PTPかどうか (VLANタグ1588固定)
    assign  w_ptp               =   (r_vlan_tag[11:0] == 12'd1588) ? r_vlan_vld : 1'b0;

    // PTP Message Type取り出し
    assign  w_enb_ptp_mstp      =   w_ptp & w_enb_word6;
    always @ (posedge clk or negedge xrst) begin
        if (!xrst)                  r_ptp_mstp[3:0] <=  4'd0;
        else if (w_enb_ptp_mstp)    r_ptp_mstp[3:0] <=  i_data[11:8];
    end

    // PTP Message Type判別
        // Delay Reqかどうか
    assign  w_ms_ptp_dlrq       =   (r_ptp_mstp[3:0] == 4'd1) ? 1'b1 : 1'b0;

    // PTP情報確定タイミング(最後のパケットで出す)
    assign  w_ptp_vld           =   w_ptp & i_valid & i_eop;
    assign  w_ptp_dlrq          =   w_ms_ptp_dlrq & i_valid & i_eop;
    always @ (posedge clk or negedge xrst) begin
         if (!xrst)                 r_ptp_vld   <=  1'b0;
         else                       r_ptp_vld   <=  w_ptp_vld;
    end
    always @ (posedge clk or negedge xrst) begin
         if (!xrst)                 r_ptp_dlrq <=   1'b0;
         else                       r_ptp_dlrq <=   w_ptp_dlrq;
    end
    assign  o_ptp_vld           =   r_ptp_vld;
    assign  o_ptp_dlrq          =   r_ptp_dlrq;

    // txing情報要求
    assign  w_set_txing_req     =   i_txing_req & i_sop & i_valid;
    always @ (posedge clk or negedge xrst) begin
        if (!xrst)                  r_txing_req <=  1'b0;
        else if (w_set_txing_req)   r_txing_req <=  1'b1;
        else if (i_txing_vld)       r_txing_req <=  1'b0;
    end

    // txing情報
    assign  w_txing_vld         =   i_txing_vld & r_txing_req & w_ms_ptp_dlrq;
    always @ (posedge clk or negedge xrst) begin
        if (!xrst)                  r_txing_vld <=  1'b0;
        else                        r_txing_vld <=  w_txing_vld;
    end
    always @ (posedge clk or negedge xrst) begin
        if (!xrst)                  r_txing_data[95:0]  <=  96'd0;
        else if (w_txing_vld)       r_txing_data[95:0]  <=  i_txing_data[95:0];
    end
    assign  o_txing_vld         =   r_txing_vld;
    assign  o_txing_data[95:0]  =   r_txing_data[95:0];
endmodule
```

コード 1 実験用回路の送信側 Verilog RTL ソースコード

```verilog
// ***************************************************************************
//  File Name   : ptp_rx_filt.v
//  Description : PTP関連信号取り出しモジュール
// ***************************************************************************

module ptp_rx_filt (
    //  クロック & リセット
    input                   xrst,
    input                   clk,

    //  パケット入力
    input                   i_sop,
    input                   i_eop,
    input                   i_valid,
    input  [63:0]           i_data,

    //  パケット入力時刻
    input                   i_rxing_vld,
    input  [95:0]           i_rxing_data,

    //  VLAN
    output                  o_vlan_vld,     //  i_eop=1の時に有効
    output [11:0]           o_vlan_tag,     //  i_eop=1&o_vlan_vld=1の時に有効

    //  情報出力
    output                  o_ptp_vld,      //  PTPパケットだった (パルス)
    output                  o_ptp_syn,      //  PTP-syncパケットだった (パルス)
    output                  o_ptp_flw,      //  PTP-Followupパケットだった (パルス)
    output                  o_ptp_dlrs,     //  PTP-Delay Respパケットだった (パルス)
    output [47:0]           o_ptp_cf_ns,
    output [15:0]           o_ptp_cf_sbns,
    output [47:0]           o_ptp_ts_sc,
    output [31:0]           o_ptp_ts_nsc,
    output [95:0]           o_rxing_data
);

    reg    [15:0]           r_ptp_cf1;
    reg    [47:0]           r_ptp_cf2;
    reg                     r_ptp_dlrs;
    reg                     r_ptp_flw;
    reg    [3:0]            r_ptp_mstp;
    reg                     r_ptp_syn;
    reg    [63:0]           r_ptp_ts1;
    reg    [15:0]           r_ptp_ts2;
    reg                     r_ptp_vld;
    reg    [95:0]           r_rxing_data;
    reg    [95:0]           r_syn_rxing_data;
    reg    [11:0]           r_vlan_tag;
    reg                     r_vlan_vld;
    reg    [5:0]            r_word_cnt;
    wire                    w_cnt_add;
    wire                    w_cnt_clear;
    wire                    w_cnt_first;
    wire                    w_enb_ptp_cf1;
    wire                    w_enb_ptp_cf2;
    wire                    w_enb_ptp_mstp;
    wire                    w_enb_ptp_ts1;
    wire                    w_enb_ptp_ts2;
    wire                    w_enb_word11;
    wire                    w_enb_word12;
    wire                    w_enb_word2;
    wire                    w_enb_word6;
    wire                    w_enb_word7;
    wire                    w_enb_word8;
    wire                    w_ms_ptp_dlrs;
    wire                    w_ms_ptp_flw;
    wire                    w_ms_ptp_syn;
    wire                    w_ptp;
    wire                    w_ptp_dlrs;
    wire                    w_ptp_flw;
    wire                    w_ptp_syn;
    wire                    w_ptp_vld;
    wire                    w_rxing_set;

    // 制御
        // ワード数カウンタ
    assign  w_cnt_first         =   i_valid & i_sop;
    assign  w_cnt_clear         =   i_valid & i_eop;
    assign  w_cnt_add           =   i_valid & ((r_word_cnt[5:0] != 0) ? 1'b1 : 1'b0);
    always @ (posedge clk or negedge xrst) begin
        if (!xrst)                  r_word_cnt[5:0] <= 6'd0;
        else if (w_cnt_clear)       r_word_cnt[5:0] <= 6'd0;
        else if (w_cnt_first)       r_word_cnt[5:0] <= 6'd1;
        else if (w_cnt_add)         r_word_cnt[5:0] <= r_word_cnt[5:0] + 6'd1;
    end
        // ワード取り込みパルス
    assign  w_enb_word2         =   (r_word_cnt[5:0] == 6'd1)  ? i_valid : 1'b0;
    assign  w_enb_word6         =   (r_word_cnt[5:0] == 6'd5)  ? i_valid : 1'b0;
    assign  w_enb_word7         =   (r_word_cnt[5:0] == 6'd6)  ? i_valid : 1'b0;
    assign  w_enb_word8         =   (r_word_cnt[5:0] == 6'd7)  ? i_valid : 1'b0;
    assign  w_enb_word11        =   (r_word_cnt[5:0] == 6'd10) ? i_valid : 1'b0;
    assign  w_enb_word12        =   (r_word_cnt[5:0] == 6'd11) ? i_valid : 1'b0;

    // 外部タイムスタンプ情報取り込み
    assign  w_rxing_set         =   i_valid & i_sop & i_rxing_vld;
    always @ (posedge clk or negedge xrst) begin
        if (!xrst)                  r_rxing_data[95:0] <= 96'd0;
        else if (w_rxing_set)       r_rxing_data[95:0] <= i_rxing_data[95:0];
    end

    // VLAN取り出し (2ワード目にVLANが存在する想定)
    always @ (posedge clk or negedge xrst) begin
        if (!xrst) begin
            r_vlan_vld          <=  1'b0;
            r_vlan_tag[11:0]    <=  12'd0;
        end else if (w_enb_word2) begin
            r_vlan_vld          <=  (i_data[31:16] == 16'h8100) ? 1'b1 : 1'b0;
            r_vlan_tag[11:0]    <=  i_data[11:0];
        end
    end
    assign  o_vlan_vld          =   r_vlan_vld;
    assign  o_vlan_tag[11:0]    =   r_vlan_tag[11:0];

    // PTPかどうか (VLANタグ1588固定)
    assign  w_ptp               =   (r_vlan_tag[11:0] == 12'd1588) ? r_vlan_vld : 1'b0;

    // PTP Message Type取り出し
    assign  w_enb_ptp_mstp      =   w_ptp & w_enb_word6;
    always @ (posedge clk or negedge xrst) begin
        if (!xrst)                  r_ptp_mstp[3:0] <=  4'd0;
        else if (w_enb_ptp_mstp)    r_ptp_mstp[3:0] <=  i_data[11:8];
    end

    // PTP Message Type判別
        // Syncかどうか
    assign  w_ms_ptp_syn        =   (r_ptp_mstp[3:0] == 4'd0) ? 1'b1 : 1'b0;
        // Followupかどうか
    assign  w_ms_ptp_flw        =   (r_ptp_mstp[3:0] == 4'd8) ? 1'b1 : 1'b0;
        // Delay Respかどうか
    assign  w_ms_ptp_dlrs       =   (r_ptp_mstp[3:0] == 4'd9) ? 1'b1 : 1'b0;

    // Correction Field取り出し
    assign  w_enb_ptp_cf1       =   (w_ms_ptp_syn | w_ms_ptp_flw | w_ms_ptp_dlrs) & w_enb_word7;
    assign  w_enb_ptp_cf2       =   (w_ms_ptp_syn | w_ms_ptp_flw | w_ms_ptp_dlrs) & w_enb_word8;
    always @ (posedge clk or negedge xrst) begin
         if (!xrst)                 r_ptp_cf1[15:0] <=  16'd0;
         else if (w_enb_ptp_cf1)    r_ptp_cf1[15:0] <=  i_data[15:0];
    end
    always @ (posedge clk or negedge xrst) begin
         if (!xrst)                 r_ptp_cf2[47:0] <=  48'd0;
         else if (w_enb_ptp_cf2)    r_ptp_cf2[47:0] <=  i_data[63:16];
    end
    assign  o_ptp_cf_ns[47:0]   =   {r_ptp_cf1[15:0], r_ptp_cf2[47:16]};
    assign  o_ptp_cf_sbns[15:0] =   r_ptp_cf2[15:0];

    // PTP時刻情報取り出し
    assign  w_enb_ptp_ts1       =   (w_ms_ptp_syn | w_ms_ptp_flw | w_ms_ptp_dlrs) & w_enb_word11;
    assign  w_enb_ptp_ts2       =   (w_ms_ptp_syn | w_ms_ptp_flw | w_ms_ptp_dlrs) & w_enb_word12;
    always @ (posedge clk or negedge xrst) begin
         if (!xrst)                 r_ptp_ts1[63:0] <=  64'd0;
         else if (w_enb_ptp_ts1)    r_ptp_ts1[63:0] <=  i_data[63:0];
    end
    always @ (posedge clk or negedge xrst) begin
         if (!xrst)                 r_ptp_ts2[15:0] <=  16'd0;
         else if (w_enb_ptp_ts2)    r_ptp_ts2[15:0] <=  i_data[63:48];
    end
    assign  o_ptp_ts_sc[47:0]   =   r_ptp_ts1[63:16];
    assign  o_ptp_ts_nsc[31:0]  =   {r_ptp_ts1[15:0], r_ptp_ts2[15:0]};

    // PTP情報確定タイミング(最後のパケットで出す)
    assign  w_ptp_vld           =   w_ptp & i_valid & i_eop;
    assign  w_ptp_syn           =   w_ms_ptp_syn  & i_valid & i_eop;
    assign  w_ptp_flw           =   w_ms_ptp_flw  & i_valid & i_eop;
    assign  w_ptp_dlrs          =   w_ms_ptp_dlrs & i_valid & i_eop;
    always @ (posedge clk or negedge xrst) begin
         if (!xrst)                 r_ptp_vld   <=  1'b0;
         else                       r_ptp_vld   <=  w_ptp_vld;
    end
    always @ (posedge clk or negedge xrst) begin
         if (!xrst)                 r_ptp_syn   <=  1'b0;
         else                       r_ptp_syn   <=  w_ptp_syn;
    end
    always @ (posedge clk or negedge xrst) begin
         if (!xrst)                 r_ptp_flw   <=  1'b0;
         else                       r_ptp_flw   <=  w_ptp_flw;
    end
    always @ (posedge clk or negedge xrst) begin
         if (!xrst)                 r_ptp_dlrs <=   1'b0;
         else                       r_ptp_dlrs <=   w_ptp_dlrs;
    end
    assign  o_ptp_vld           =   r_ptp_vld;
    assign  o_ptp_syn           =   r_ptp_syn;
    assign  o_ptp_flw           =   r_ptp_flw;
    assign  o_ptp_dlrs          =   r_ptp_dlrs;

    // syncのrxing出力
    always @ (posedge clk or negedge xrst) begin
        if (!xrst)                  r_syn_rxing_data[95:0] <= 96'd0;
        else if (w_ptp_syn)         r_syn_rxing_data[95:0] <= r_rxing_data[95:0];
    end
    assign  o_rxing_data[95:0]  =   r_syn_rxing_data[95:0];
endmodule
```

コード 2 実験用回路の受信側 Verilog RTL ソースコード

```verilog
e25g u_e25g (
    .clk_txmac                          (clk_txmac          ),
    .l1_tx_startofpacket                (tx_sop             ),
    .l1_tx_endofpacket                  (tx_eop             ),
    .l1_tx_valid                        (tx_valid           ),
    .l1_tx_ready                        (tx_ready           ),
    .l1_tx_data                         (tx_data            ),
    .clk_rxmac                          (clk_rxmac          ),
    .l1_rx_valid                        (rx_valid           ),
    .l1_rx_startofpacket                (rx_sop             ),
    .l1_rx_endofpacket                  (rx_eop             ),
    .l1_rx_data                         (rx_data            ),
    .tx_egress_timestamp_request_valid  (tx_ts_req          ),
    .tx_egress_timestamp_96b_valid      (tx_ts_valid        ),
    .tx_egress_timestamp_96b_data       (tx_ts_data         ),
    .rx_ingress_timestamp_96b_valid     (rx_ts_valid        ),
    .rx_ingress_timestamp_96b_data      (rx_ts_data         ),

    // 省略
);

ptp_tx_filt u_ptp_tx (
    .xrst                               (xrst               ),
    .clk                                (clk_txmac          ),
    .i_sop                              (tx_sop             ),
    .i_eop                              (tx_eop             ),
    .i_valid                            (tx_valid & tx_ready),
    .i_data                             (tx_data            ),
    .i_txing_req                        (tx_ts_req          ),
    .i_txing_vld                        (tx_ts_valid        ),
    .i_txing_data                       (tx_ts_data         ),
    .o_vlan_vld                         (                   ),
    .o_vlan_tag                         (                   ),
    .o_ptp_vld                          (                   ),
    .o_ptp_dlrq                         (                   ),
    .o_txing_vld                        (                   ),
    .o_txing_data                       (                   )
);

ptp_rx_filt u_ptp_rx (
    .xrst                               (xrst               ),
    .clk                                (clk_rxmac          ),
    .i_sop                              (rx_sop             ),
    .i_eop                              (rx_eop             ),
    .i_valid                            (rx_valid           ),
    .i_data                             (rx_data            ),
    .i_rxing_vld                        (rx_ts_valid        ),
    .i_rxing_data                       (rx_ts_data         ),
    .o_vlan_vld                         (                   ),
    .o_vlan_tag                         (                   ),
    .o_ptp_vld                          (                   ),
    .o_ptp_syn                          (                   ),
    .o_ptp_flw                          (                   ),
    .o_ptp_dlrs                         (                   ),
    .o_ptp_cf_ns                        (                   ),
    .o_ptp_cf_sbns                      (                   ),
    .o_ptp_ts_sc                        (                   ),
    .o_ptp_ts_nsc                       (                   ),
    .o_rxing_data                       (                   )
);
```

コード 3 実験用回路のインスタンス部分

# 参考文献

- [Endrun IEEE 1588 PTP グランドマスタークロック](https://www.shoshin.co.jp/c/endrun/1588ptp.html)
  PTP 自体やそれの利用にまつわる情報が Q&A 形式でまとめられています。

- [技術解説\_IEEE1588-2008](https://sites.google.com/network-additions.com/technical1588)
  PTP での同期を実現するために必要となる技術が詳しく解説されています。

- [コラム：映像制作/伝送分野の IP 化と PTP（SMPTE ST 2059）4K・8K 放送に向けた時刻同期の取り組み(第 1 回)](https://www.seiko-sol.co.jp/ip_live_production/)
  映像制作/伝送分野の IP 化と PTP の関係について解説されています。

- NM BOOKS「IP ライブ伝送システム」解説シリーズ第 5 弾 リモートプロダクション 必須の基本項目 ～分かれば、できる。できれば、分かる。～（株式会社 ニューメディア 編集部)

- [RP 168-2009 - SMPTE Recommended Practice - Definition of Vertical Interval Switching Point for Synchronous Video Switching | SMPTE Standard | IEEE Xplore](https://doi.org/10.5594/SMPTE.RP168-2009)

- [SMPTE ST 2110 -  Society of Motion Picture & Television Engineers](https://www.smpte.org/standards/st2110)

- [ST 2059-1:2021 - SMPTE Standard - Generation and Alignment of Interface Signals to the SMPTE Epoch | SMPTE Standard | IEEE Xplore](https://doi.org/10.5594/SMPTE.ST2059-1.2021)