概要
SoCFPGAのI/Oは,HPS側I/OとFPGA側I/Oに二分されます。SoCFPGAにはEMAC,I2C,QSPI等を制御する種々のハードマクロが搭載されており,これらはHPS側I/Oを介して接続することができます。一方,HPS側I/Oの数は限られており,ハードマクロすべてを有効にしてHPS側I/Oから接続することはできません。
本制約に対処可能な手段として,ハードマクロの信号線をFPGA側にルーティングし,FPGA側のI/Oを利用する方法があります。
【CycloneV HPS Technical Reference Manual 抜粋】
EMACについてはHPS側I/OまたはFPGA側I/Oいずれかが利用可能である旨,記載があります。
このようなモジュールについてFPGA側のI/Oを使用する場合,当然FPGA側でタイミング制約を記述し,Setup/Holdが要件を満たすように合成する必要があります。単にI/Oをそのまま出力するだけのように感じますが,うまくタイミングが収束しないパターンがあります。
本記事ではその一例を挙げたいと思います。
検証環境
- Linux ubuntu20 5.4.0-42-generic
- Quartus Prime Lite Edition 20.1.0.711
検証用プロジェクトの用意
今回はCycloneV SoC(5CSTFD6D5F31I7)を使用します。5CSTFD6D5F31I7を選択してプロジェクトを作成します。
続いて,Platform Designerを用いてHPSモジュールを作成します。この際,EMAC0をFPGA側に出力するよう設定します。
作成したモジュールの名前を変更します(hps_0 => hps)。必要な信号をダブルクリックでExportし,Generateボタンを押下します。
次にトップモジュール(gmiitest.v)を作成します。内容は先ほどPlatform DesignerでGenerateしたIPをインスタンス化し,各入出力信号をそのまま外部入出力として接続するものです。
module gmiitest
(
output wire [7:0] hps_0_emac0_phy_txd_o,
output wire hps_0_emac0_phy_txen_o,
output wire hps_0_emac0_phy_txer_o,
input wire hps_0_emac0_phy_rxdv_i,
input wire hps_0_emac0_phy_rxer_i,
input wire [7:0] hps_0_emac0_phy_rxd_i,
input wire hps_0_emac0_phy_col_i,
input wire hps_0_emac0_phy_crs_i,
output wire hps_0_emac0_gmii_mdo_o,
output wire hps_0_emac0_gmii_mdo_o_e,
input wire hps_0_emac0_gmii_mdi_i,
output wire hps_0_emac0_ptp_pps_o,
input wire hps_0_emac0_ptp_aux_ts_trig_i,
output wire hps_0_emac0_gtx_clk_clk,
output wire hps_0_emac0_md_clk_clk,
input wire hps_0_emac0_rx_clk_in_clk,
input wire hps_0_emac0_tx_clk_in_clk,
input wire hps_0_emac_ptp_ref_clock_clk,
input wire hps_f2h_axi_clock_clk,
input wire hps_f2h_sdram0_clock_clk,
input wire hps_h2f_axi_clock_clk,
input wire hps_h2f_lw_axi_clock_clk,
output wire [12:0] memory_mem_a,
output wire [2:0] memory_mem_ba,
output wire memory_mem_ck,
output wire memory_mem_ck_n,
output wire memory_mem_cke,
output wire memory_mem_cs_n,
output wire memory_mem_ras_n,
output wire memory_mem_cas_n,
output wire memory_mem_we_n,
output wire memory_mem_reset_n,
inout wire [7:0] memory_mem_dq,
inout wire memory_mem_dqs,
inout wire memory_mem_dqs_n,
output wire memory_mem_odt,
output wire memory_mem_dm,
input wire memory_oct_rzqin
);
hps u0 (
.hps_0_emac0_phy_txd_o (hps_0_emac0_phy_txd_o),
.hps_0_emac0_phy_txen_o (hps_0_emac0_phy_txen_o),
.hps_0_emac0_phy_txer_o (hps_0_emac0_phy_txer_o),
.hps_0_emac0_phy_rxdv_i (hps_0_emac0_phy_rxdv_i),
.hps_0_emac0_phy_rxer_i (hps_0_emac0_phy_rxer_i),
.hps_0_emac0_phy_rxd_i (hps_0_emac0_phy_rxd_i),
.hps_0_emac0_phy_col_i (hps_0_emac0_phy_col_i),
.hps_0_emac0_phy_crs_i (hps_0_emac0_phy_crs_i),
.hps_0_emac0_gmii_mdo_o (hps_0_emac0_gmii_mdo_o),
.hps_0_emac0_gmii_mdo_o_e (hps_0_emac0_gmii_mdo_o_e),
.hps_0_emac0_gmii_mdi_i (hps_0_emac0_gmii_mdi_i),
.hps_0_emac0_ptp_pps_o (hps_0_emac0_ptp_pps_o),
.hps_0_emac0_ptp_aux_ts_trig_i (hps_0_emac0_ptp_aux_ts_trig_i),
.hps_0_emac0_gtx_clk_clk (hps_0_emac0_gtx_clk_clk),
.hps_0_emac0_md_clk_clk (hps_0_emac0_md_clk_clk),
.hps_0_emac0_rx_clk_in_clk (hps_0_emac0_rx_clk_in_clk),
.hps_0_emac0_tx_clk_in_clk (hps_0_emac0_tx_clk_in_clk),
.hps_0_emac_ptp_ref_clock_clk (hps_0_emac_ptp_ref_clock_clk),
.hps_f2h_axi_clock_clk (hps_f2h_axi_clock_clk),
.hps_f2h_sdram0_clock_clk (hps_f2h_sdram0_clock_clk),
.hps_h2f_axi_clock_clk (hps_h2f_axi_clock_clk),
.hps_h2f_lw_axi_clock_clk (hps_h2f_lw_axi_clock_clk),
.memory_mem_a (memory_mem_a),
.memory_mem_ba (memory_mem_ba),
.memory_mem_ck (memory_mem_ck),
.memory_mem_ck_n (memory_mem_ck_n),
.memory_mem_cke (memory_mem_cke),
.memory_mem_cs_n (memory_mem_cs_n),
.memory_mem_ras_n (memory_mem_ras_n),
.memory_mem_cas_n (memory_mem_cas_n),
.memory_mem_we_n (memory_mem_we_n),
.memory_mem_reset_n (memory_mem_reset_n),
.memory_mem_dq (memory_mem_dq),
.memory_mem_dqs (memory_mem_dqs),
.memory_mem_dqs_n (memory_mem_dqs_n),
.memory_mem_odt (memory_mem_odt),
.memory_mem_dm (memory_mem_dm),
.memory_oct_rzqin (memory_oct_rzqin)
);
endmodule
Platform Designerで作成したqipファイルとトップモジュールファイルをプロジェクトに追加します。続いてFitterでエラーが出ないよう,Assignment EditorからDDR3 I/Fの設定を適当に行います(本記事末尾の付録参照)。なお,このときFPGA側I/Oの位置は特に指定しません。指定しない場合はFitterがタイミング制約などを元に位置を決定します。設定後,一通りコンパイル(論理合成〜タイミング解析)を行い,エラーが発生しないことを確認します。
続いてタイミング制約を記述します。その前に,Quartusの設定を変更し,Timing AnalyzerからEMACの内部信号が見えるようにする必要があります(そうしないとTX系のタイミング解析ができない)。
以下ファイルをプロジェクトファイル(.qpf)を配置しているディレクトリに配置し,Quartusを再起動します。
b2t_enable_hps_emac_internal_clock_arcs=on
タイミング制約は以下のように記述します。今回は接続するEthernet PHYとしてKSZ9021GQを想定し,簡単のため基板上の配線遅延,クロックジッタ・スキュー等は無視します。
# GTX Clock
create_generated_clock -name GTXCLK -master_clock emac0_tx_clk \
-source [get_keepers {u0|hps|fpga_interfaces|peripheral_emac0~internal_clock}] \
[get_ports {hps_0_emac0_gtx_clk_clk}]
# Setup/Hold
set_output_delay -clock GTXCLK -max 2.0 \
[get_ports {hps_0_emac0_phy_txd_o[*] hps_0_emac0_phy_txen_o hps_0_emac0_phy_txer_o}]
set_output_delay -clock GTXCLK -min 0.0 \
[get_ports {hps_0_emac0_phy_txd_o[*] hps_0_emac0_phy_txen_o hps_0_emac0_phy_txer_o}]
# RX Clock
create_clock -name RXCLK -period 8.0 [get_ports {hps_0_emac0_rx_clk_in_clk}]
# Setup/Hold
set_input_delay -clock RXCLK -max 5.5 \
[get_ports {hps_0_emac0_phy_rxdv_i hps_0_emac0_phy_rxer_i hps_0_emac0_phy_rxd_i[*]}]
set_input_delay -clock RXCLK -min 0.5 \
[get_ports {hps_0_emac0_phy_rxdv_i hps_0_emac0_phy_rxer_i hps_0_emac0_phy_rxd_i[*]}]
derive_clock_uncertainty
# 注意 #####################################################################
# ここで使用したemac0_tx_clkは,Platform Designerから出力されたsdcに記述されている
# @ hps/synthesis/submodules/hps_hps_fpga_interfaces.sdc
# create_clock -name emac0_tx_clk -period 8.0 \
# [get_keepers {*|fpga_interfaces|peripheral_emac0~internal_clock}] -add
Let's 論理合成
合成結果がこちらです。TX系は問題ありませんが,RX系でタイミングエラーが出ています。単にハードマクロの出力をFPGA側のI/Oに配線するだけですが,このようにタイミングが収束しないことがあります。
パスの詳細をTiming Analyzerで見てみましょう。
最初の5.5nsの遅延はEthernet PHY出力の仕様であり,タイミング制約で記載した内容です。このパスの遅延で支配的なのはオレンジ色で示した行であり,入力バッファからFPGA interfaceまでの内部配線(IC:InterConnect)が要因であることがわかります。
実験その1
前述のパスにFFを挿入することで内部配線を短くして再度合成をしてみましょう。RX側の信号へFFを挿入するにあたり,当該FFのリセット回路を実装する必要があります。Platform Designerから操作を行い,新たにHPSからEMACのリセット信号をExportし,本信号を使用して実装します。
コードの追記・変更内容は以下です。
//--- register declaration
reg hps_0_emac0_phy_rxdv_i_r;
reg hps_0_emac0_phy_rxer_i_r;
reg [7:0] hps_0_emac0_phy_rxd_i_r;
//--- wire declaration
wire reset_rx_n;
wire reset_tx_n;
//--- FF for timing closure
always @ (posedge hps_0_emac0_rx_clk_in_clk or negedge reset_rx_n) begin
if (~reset_rx_n) begin
hps_0_emac0_phy_rxdv_i_r <= 1'b0;
hps_0_emac0_phy_rxer_i_r <= 1'b0;
hps_0_emac0_phy_rxd_i_r <= 8'b0;
end else begin
hps_0_emac0_phy_rxdv_i_r <= hps_0_emac0_phy_rxdv_i;
hps_0_emac0_phy_rxer_i_r <= hps_0_emac0_phy_rxer_i;
hps_0_emac0_phy_rxd_i_r <= hps_0_emac0_phy_rxd_i;
end
end
hps u0 (
.hps_0_emac0_phy_txd_o (hps_0_emac0_phy_txd_o),
//... 省略
.hps_0_emac0_phy_rxdv_i (hps_0_emac0_phy_rxdv_i_r),
.hps_0_emac0_phy_rxer_i (hps_0_emac0_phy_rxer_i_r),
.hps_0_emac0_phy_rxd_i (hps_0_emac0_phy_rxd_i_r),
//... 省略
.hps_emac0_tx_reset_reset_n (reset_tx_n),
.hps_emac0_rx_reset_reset_n (reset_rx_n) // reset for FF
);
結果がこちらです。
狙い通りRX系のSetupは改善しましたが,Holdが悪化し,Setup/Hold双方でエラーが出ています。加えてTX系の信号についてもSetup/Holdでエラーが出ています。そう簡単にはいかないようです。
Timing Analyzerによる確認をしてみましょう。
FF〜FPGA interfaceまでのSetupパスは問題ないようです。省略しますが,Holdパスも問題ありません。一方,入力バッファ〜FFまでのSetupパスはうまく行っていないようです。
後者のパスをChip PlannerにLocateしてみましょう。
青紫の矢印がデータのパスであり,赤紫のパスがクロックのパスです。FFは入力バッファから離れた位置に配置されており,データパスは短くする余地があることがわかります。
実験その2
FF1つではデータパスが長く,タイミング収束しませんでした。そこで,FFの数を2つに増やし,さらなるパスの分割を試みます。追記内容は「実験その1」とほぼ同じであるため,コードは省略します。結果がこちらです。
ほとんど改善は見られていません。理由は簡単で,1つ目のFF〜2つ目のFFまでの距離がとても短く,入力バッファ〜1つ目のFFまでのパスを短くする役割を果たしていないためでした。確認のため,1つ目のFF〜2つ目のFFまでのパスをChip PlannterにLocateした様子をお見せします。
見づらいですが,画面中央部に赤紫の矢印でデータパスが描かれています。以下が拡大図です。
Logic Lockを使用して,2つのFFの位置をよい塩梅に指定することで解決可能かもしれません。しかしながら,本合成環境はQuartusのLite Editionなので当該機能を使用できません。この記事では本機能を使用せずに解決していきます。
脱線しますが,図らずもTX系のタイミングが収束しています。使用するI/Oの位置によって収束可否が決まるようです。逆説的に考えると,I/Oの位置が先に決まっているプロジェクトなどではTX系のタイミング収束作業が必要となる可能性があります。
実験その3
一般に,入力段のタイミングでエラーが出ている場合,入力バッファのすぐそばに配置されているFFを使用してデータを受けることで改善することがあります。
【CycloneV Device Handbook 第5章抜粋:図中,下方に記載がある「Input Register」】
当該FFを使用するには,Assignment Editorから「Fast Input Register:on」を設定します。
結果です。
無事すべてのパスでタイミングが収束しました。
結論
以上のように,ハードマクロをFPGA側I/Oから使用する場合,考えている以上に煩雑なタイミング収束作業が発生するケースがあります。今回はFPGA側I/Oの設定をデフォルト(2.5V, 12mA, Slew Rate: Fast)かつ位置の制約なし,という条件の下で作業を行いましたが,条件次第で収束作業の難易度が変わります。
作業を可能な限り簡単にするには,以下に気をつけると良いと思います。
- FPGA側I/Oを使用する場合,早期にI/O規格を決定し,タイミング収束性を検証すること
- 収束した結果を用いてI/Oピンの位置を検討すること
- 必要に応じてタイミング収束を実現する配置配線結果をLogic Lock等で固定し,開発を進めること
付録:Assignment Editor設定
"To"フィールドの[*]は適宜展開してください。
To | Assignment Name | Value |
---|---|---|
memory_mem_a[*] | I/O Standard | SSTL-15 Class I |
memory_mem_ba[*] | I/O Standard | SSTL-15 Class I |
memory_mem_cas_n | I/O Standard | SSTL-15 Class I |
memory_mem_odt | I/O Standard | SSTL-15 Class I |
memory_mem_ras_n | I/O Standard | SSTL-15 Class I |
memory_mem_reset_n | I/O Standard | SSTL-15 Class I |
memory_mem_we_n | I/O Standard | SSTL-15 Class I |
memory_oct_rzqin | I/O Standard | SSTL-15 Class I |
memory_mem_cke | I/O Standard | SSTL-15 Class I |
memory_mem_cs_n | I/O Standard | SSTL-15 Class I |
memory_mem_dm | I/O Standard | SSTL-15 Class I |
memory_mem_dm | Output Termination | Series 50 Ohm with Calibration |
memory_mem_dq[*] | I/O Standard | SSTL-15 Class I |
memory_mem_dq[*] | Input Termination | Parallel 50 Ohm with Calibration |
memory_mem_dq[*] | Output Termination | Series 50 Ohm with Calibration |
memory_mem_dqs | I/O Standard | SSTL-15 Class I |
memory_mem_dqs | Input Termination | Parallel 50 Ohm with Calibration |
memory_mem_dqs | Output Termination | Series 50 Ohm with Calibration |
memory_mem_dqs_n | I/O Standard | SSTL-15 Class I |
memory_mem_dqs_n | Input Termination | Parallel 50 Ohm with Calibration |
memory_mem_dqs_n | Output Termination | Series 50 Ohm with Calibration |
memory_mem_dqs | I/O Standard | Differential 1.5-V SSTL Class I |
memory_mem_dqs | Input Termination | Parallel 50 Ohm with Calibration |
memory_mem_dqs | Output Termination | Series 50 Ohm with Calibration |
memory_mem_dqs_n | I/O Standard | Differential 1.5-V SSTL Class I |
memory_mem_dqs_n | Input Termination | Parallel 50 Ohm with Calibration |
memory_mem_dqs_n | Output Termination | Series 50 Ohm with Calibration |
memory_mem_ck | I/O Standard | Differential 1.5-V SSTL Class I |
memory_mem_ck | Output Termination | Series 50 Ohm without Calibration |
memory_mem_ck_n | I/O Standard | Differential 1.5-V SSTL Class I |
memory_mem_ck_n | Output Termination | Series 50 Ohm without Calibration |