はじめに
このページでは、ソース同期データ出力(source-synchronous data outputs)について説明します。この手法は、データ出力が、FPGA がこれらの出力と並行して生成するクロックに同期している、というものです。
外部部品とのインターフェースが FPGA からの出力ポートだけで構成されている場合、この方法は当然の選択です。また、I/O が双方向の場合にも適しています。これについては、このページの末尾で説明します。クロックとデータの関係全般については、別ページでも説明しています。
基本的な応用例
次の Verilog の例を考えてみましょう。
module top (
input clk,
output reg [7:0] data_out,
output reg sync_clk_out
);
always @(posedge clk)
begin
sync_clk_out <= !sync_clk_out;
if (sync_clk_out)
data_out <= data_out + 1;
end
endmodule
@data_out は、@sync_clk_out の立ち下がりエッジと同時に値が変化することに注意してください。@sync_clk_out が Low の間、@data_out は変化しません。@sync_clk_out が High の間、@data_out は @clk の立ち上がりエッジに応答して新しい値に変化します。
@data_out と @sync_clk_out の両方に IOB レジスタを使用すると、これらの出力信号はすべてほぼ同時に値を変化させます。したがって、これらの信号を受け取る外部部品は @sync_clk_out を自身のクロックとして使うことができます。このようにすれば、外部部品のタイミング要件を満たすのは通常は簡単です(@data_out のサンプリング (sampling) が @sync_clk_out の立ち上がりエッジに同期していると仮定した場合です)。
DDR レジスタの使用
上記の例には大きな欠点があります。@sync_clk_out の周波数が @clk の半分になることです。したがって、@data_out のデータレートも @clk の半分の周波数に制限されます。唯一の例外は、外部部品が @sync_clk_out の両方のクロックエッジに反応する場合(つまり DDR 入力を持つ場合)です。しかし、通常はそうではありません。
データ出力と位置合わせされた出力クロックを生成するには、出力 DDR レジスタを使うことができます。その考え方は、両方のクロックエッジで値を変化させるフリップフロップを使用するというものです。つまり、このようなフリップフロップは次のように動作します。
always @(posedge clk or negedge clk)
if (clk)
sync_clk_out <= 0;
else
sync_clk_out <= 1;
@sync_clk_out は @clk の立ち上がりエッジに応答して Low に変化します。立ち下がりエッジではその逆になります。その結果、同じ周波数の出力クロックが得られます。
しかし、この Verilog コードはほとんどのシンセサイザ (synthesizer) では動作しません。ロジックファブリック内のフリップフロップは、通常、両方のクロックエッジに反応する能力を持たないからです。この能力を持つのは IOB レジスタだけであり、シンセサイザがその可能性を理解できるほど賢いことはほとんどありません。
したがって、ロジックプリミティブ (primitive) のインスタンシエーション (instantiation) が必要になります。プリミティブは使用する FPGA に固有です。次の例は、Kintex UltraScale でこれを行う方法を示しています。
ODDRE1 ODDR_ins
(.C(clk), .Q(sync_clk_out),
.D1(1'b0), .D2(1'b1),
.SR(1'b0));
しかし今度は、クロックは出力 DDR レジスタで生成され、データ信号は通常の IOB レジスタで生成されています。データとクロックの位置合わせは、以前ほど自明ではありません。このため、すべての出力についてタイミングレポートを確認し、遅延の差が十分に小さいことを検証することが重要です。
データについても出力 DDR レジスタを使うことができます。D1 と D2 に同じ値を与えれば、出力は各クロックサイクルで 1 回だけ変化します。
module top (
input clk,
output [7:0] data_out,
output sync_clk_out
);
reg [7:0] data;
ODDRE1 ODDR_ins
(.C(clk), .Q(sync_clk_out),
.D1(1'b0), .D2(1'b1),
.SR(1'b0));
ODDRE1 ODDR_data_ins [7:0]
(.C(clk), .Q(data_out),
.D1(data), .D2(data),
.SR(1'b0));
always @(posedge clk)
data <= data + 1;
endmodule
この方法により、データとクロックの位置合わせが保証されます。出力 DDR レジスタを明示的にインスタンシエーションすることにより、IOB レジスタが使われることも保証されます。この方法の唯一の欠点は、FPGA ごとに固有であることです。
出力 DDR レジスタの動作は、予想外で混乱を招く可能性があることに注意してください。たとえば、@data は @clk の立ち上がりエッジに同期しています。しかし ODDRE1 は両方のクロックエッジで動作します。2 つのデータ入力がどのように、そしていつサンプリングされるかについては、お使いの FPGA のドキュメントを必ず読んでください。
また、この例ではリセットが使われていないことにも注意してください。これは、特に高度な FPGA では安全でない可能性があります。出力 DDR レジスタは、IOB 内のより複雑な論理要素の一部になり得るからです。そのような論理要素は、リセットなしでは正しく動作しないことがあります。
タイミング制約
では、この方法でもタイミング制約は必要なのでしょうか? クロック出力とデータ出力が IOB レジスタの使用によってほぼ完全に位置合わせされているなら、それで十分ではないでしょうか?
答えは、この方法はタイミング制約なしでも完全にうまく動作し得る、ということです。しかし、それでもタイミング制約を利用するのは良い考えです。その理由は、IOB レジスタのページで説明したのと同じです。
その他の考慮事項
ただし、IOB レジスタを使うことだけを保証すればよいわけではありません。出力ポート同士の物理的な近接性にも注意してください。出力ポートが互いに遠く離れていると、この物理的な距離が FPGA 上のクロックスキュー (clock skew) に悪影響を及ぼす可能性があります。トレース長(つまり PCB 上の配線)の差も悪影響を与える可能性があります。さらに、電気的パラメータ(電圧、I/O 規格、スルーレート、駆動電流など)も、すべての出力で同じにする必要があります。
FPGA に IOB レジスタがない場合、タイミング制約は極めて重要になります。ソース同期出力を使用する根拠は、関連するすべての出力ポート間でスキューが小さいことを保証できることにあります。通常は、フリップフロップと I/O ピンの間の遅延を制御すれば十分です。この遅延が、対象となる FPGA で可能な最小値に近ければ、出力間の差はほとんどの目的に対して十分に小さくなります。
双方向 I/O
ソース同期出力の主な特徴は、関連するクロックが FPGA によって生成されることです。これは、すべてのデータ信号が外部部品に向かっている場合には明らかに適しています。しかし、データ信号が双方向にある場合でも、FPGA が生成するクロックに依存することは理にかなっています。
ただし、データ入力は FPGA チップ内部のクロックではなく、PCB 上のクロックに同期していることに注意してください。これらの 2 つのクロックはもちろん正確に同じ周波数ですが、出力ポートの clock-to-output 遅延に依存するタイミング差があります。これは数ナノ秒になり得ます。
したがって、PCB 上にループバックを設けることをお勧めします。つまり、FPGA のクロック出力を入力ポートに接続します。この入力ポートは、あたかも システム同期クロック (system synchronous clock)であるかのように扱うことができます。外部部品は、あたかも FPGA ではなく専用の発振器からクロックが供給されているかのように動作します。これは システム同期クロックに似ています。FPGA と外部部品の両方が、PCB 上の同じ物理信号に依存するからです。
データ入力は、ソース同期入力 (source-synchronous inputs)として扱うことも可能かもしれません。特に、位相シフト技術を使用する場合には、クロックのループバックは必要ありません。これは実際、DDR SDRAM メモリとインターフェースする際の一般的な方法です。
重要な結論は、PCB を製造する前に入力信号のタイミングを慎重に計画し、必要に応じてクロックのループバックを追加することを検討する、ということです。

