PYNQ-Z1 の ビットストリームを再ビルドしたときのタイミング違反を無くす


はじめに

PYNQ の PL 部のビルド環境一式は github で公開されていて、自分でカスタマイズすることが出来ます。
この記事では、PYNQ の PL 部を Vivado 2016.2 で再ビルドした時のタイミング違反を解消する方法を説明します。
タイミング違反を放置していると動作中に時々おかしくなる等のとても面倒な問題を引き起こすので、放置は厳禁です。
ただし、私は PYNQ を持っていないので実機での確認はしていません。あくまでも Vivado の開発環境での結果です。

ダウンロード

github からダウンロードしてください。

shell$ git clone https://github.com/Xilinx/PYNQ
shell$ cd PYNQ

Vivado 2016.1 で再ビルド

現在 github で公開されているリポジトリは Vivado 2016.1 用です。
Vivado 2016.1 を起動して、Tcl Console を開いて次のように入力すればビットストリームが出来ます。

cd <cloneしたディレクトリ>/Pynq-Z1/vivado/base
source ../../bitstream/base.tcl

Vivado 2016.2 で再ビルド

Vivado 2016.2 を起動して前節のように入力しても、次のようなエラーが出て失敗します。

ERROR: [BD_TCL-109] This script was generated using Vivado <2016.1> and is being run in <2016.2> of Vivado.
Please run the script in Vivado <2016.1> then open the design in Vivado <2016.2>.
Upgrade the design by running "Tools => Report => Report IP Status...", then run write_bd_tcl to create an updated script.

このエラーは Pynq-Z1/bitstream/base.tcl を次のように修正すれば回避できます。

Pynq-Z1/bitstream/base.tcl(修正前)
################################################################
# Check if script is running in correct Vivado version.
################################################################
set scripts_vivado_version 2016.1
set current_vivado_version [version -short]
Pynq-Z1/bitstream/base.tcl(修正後)
################################################################
# Check if script is running in correct Vivado version.
################################################################
set scripts_vivado_version 2016.2
set current_vivado_version [version -short]

残念ながらこの方法でエラーを回避できるのは Vivado 2016.2 までで、Vivado 2016.4 では IP のバージョンの違いからか失敗します。

タイミング違反を解消する

Vivado でビットストリームを再ビルドすると、何故か、タイミング違反が発生します。

クロック間の非同期設定

Timing Report をみると、まず目につくのは Inter-Clock Paths の clk_fpga_0-to-clk_fpga_3 です。ところが、そもそも clk_fpga_0、clk_fpga_1、clk_fpga_2、clk_fpga_3 は互いに非同期のはずなのでこのようなタイミング違反が出るのはおかしいのです。どうやら、これらのクロックが互いに非同期であることを指定していないようです。なぜ指定していないのかはわかりません(手抜き?)。
そこで Pynq-Z1/vivado/base/src/constraints/top.xdc の最後に次の行を追加することで、これらのクロックが互いに非同期関係であることを指定します。

Pynq-Z1/vivado/base/src/constraints/top.xdc
  :
 (前略)
  :
set_clock_groups -asynchronous -group {clk_fpga_0} -group {clk_fpga_1} -group {clk_fpga_2} -group {clk_fpga_3}

上記の Timing Report をみると、clk_fpga_0-to-clk_fpga_3 以外にも Intra-Clock Path の clk_fpga_0、clk_fpga_1、clk_fpga_3 にもタイミング違反が発生しています。
しかし、Vivado (または Quartus II など)の配置配線プログラムは、タイミング違反が発生した時は最も悪いタイミング違反を解消しようと頑張るのですが、もし最も悪いタイミング違反が解消できなかった時、それよりもマシなタイミング違反の解消まで諦めてしまいます。裏を返せば、最も悪いタイミング違反を解消すると配置配線プログラムはそれよりもマシなタイミング違反も解消しようと頑張るので、結果的に他のタイミング違反が解消される可能性があります。

そこでまずは、この修正した top.xdc を使って再ビルドしてみましょう。すると、次のようにタイミング違反はかなり減少します。clk_fpga_0-to-clk_fpga_3 とclk_fpga_0 のタイミング違反は無くなり、clk_fpga_1 のタイミング違反は -0.266ns から -0.072nsに、clk_fpga_3 のタイミング違反は -2.137ns から -0.649ns に改善されていることがわかります。

Implementation Strategy の変更

前節のやり方でもタイミング違反はまだ残っています。そこで配置配線プログラムにもう少し頑張ってもらいましょう。手っ取り早く頑張ってもらうために、Vivado の配置配線の戦略(Implementation Strategy) を Default から Performance_ExplorePostRoutePhysOpt に変更します。

Pynq-Z1/bitstream/base.tcl(修正前)
# call implement
launch_runs impl_1 -to_step write_bitstream -jobs 4
wait_on_run impl_1
Pynq-Z1/bitstream/base.tcl(修正後)
#
set_property strategy "Performance_ExplorePostRoutePhysOpt"  [get_runs impl_1]

# call implement
launch_runs impl_1 -to_step write_bitstream -jobs 4
wait_on_run impl_1

配置配線に時間はかかりますが、次のように、clk_fpga_1 のタイミング違反は無くなり、clk_fpga_3 のタイミング違反は -0.649ns から -0.514ns に少しですが改善されています。

clk_fpga_3 の動作周波数を変更

残念ながら、配置配線プログラムの頑張りをもってしても clk_fpga_3 のタイミング違反は無くなりませんでした。このままでは使い物になりません。しかもタイミング違反を起こしているのが Xilinx 社の提供している axi_dma という IP の中なのでソースコードに手を入れて改善することもできません。そこでとりあえず、clk_fpga_3 の周波数を 166.6MHz(6nsec) から 100MHz(10nsec) に下げてしまいましょう。
clk_fpga_3 は tracebuffer_arduino 、tracebuffer_pmods というモジュールの基本クロックとして使われています。tracebuffer は外部からの入力をサンプリングして Zynq のメモリに DMA を使って書き込むモジュールです。clk_fpga_3 の周波数を下げるとサンプリングする周波数も下がってしまいますが、タイミング違反のまま使うと動作中に面倒な問題を引き起こしますので、背に腹は代えられません。

clk_fpga_3 の動作周波数を 100MHz にするには base.tcl に記述されている processing_system7_0 のプロパティを変更します。具体的には CONFIG.PCW_ACT_FPGA3_PERIPHERAL_FREQMHZ を 100.000000 に、CONFIG.PCW_FCLK3_PERIPHERAL_DIVISOR0 と CONFIG.PCW_FCLK3_PERIPHERAL_DIVISOR1 をそれぞれ 5 と 2 に、CONFIG.PCW_FPGA3_PERIPHERAL_FREQMH を 100 に変更します。

Pynq-Z1/bitstream/base.tcl(修正前)
 (前略)
   :
  # Create instance: processing_system7_0, and set properties
  set processing_system7_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:processing_system7:5.5 processing_system7_0 ]
  set_property -dict [ list \
CONFIG.PCW_ACT_APU_PERIPHERAL_FREQMHZ {650.000000} \
CONFIG.PCW_ACT_CAN0_PERIPHERAL_FREQMHZ {23.8095} \
CONFIG.PCW_ACT_CAN1_PERIPHERAL_FREQMHZ {23.8095} \
CONFIG.PCW_ACT_CAN_PERIPHERAL_FREQMHZ {10.000000} \
CONFIG.PCW_ACT_DCI_PERIPHERAL_FREQMHZ {10.096154} \
CONFIG.PCW_ACT_ENET0_PERIPHERAL_FREQMHZ {125.000000} \
CONFIG.PCW_ACT_ENET1_PERIPHERAL_FREQMHZ {10.000000} \
CONFIG.PCW_ACT_FPGA0_PERIPHERAL_FREQMHZ {100.000000} \
CONFIG.PCW_ACT_FPGA1_PERIPHERAL_FREQMHZ {142.857132} \
CONFIG.PCW_ACT_FPGA2_PERIPHERAL_FREQMHZ {200.000000} \
CONFIG.PCW_ACT_FPGA3_PERIPHERAL_FREQMHZ {166.666672} \
   :
 (中略)
   :
CONFIG.PCW_FCLK3_PERIPHERAL_CLKSRC {IO PLL} \
CONFIG.PCW_FCLK3_PERIPHERAL_DIVISOR0 {6} \
CONFIG.PCW_FCLK3_PERIPHERAL_DIVISOR1 {1} \
CONFIG.PCW_FCLK_CLK0_BUF {true} \
CONFIG.PCW_FCLK_CLK1_BUF {true} \
CONFIG.PCW_FCLK_CLK2_BUF {true} \
CONFIG.PCW_FCLK_CLK3_BUF {true} \
CONFIG.PCW_FPGA0_PERIPHERAL_FREQMHZ {100} \
CONFIG.PCW_FPGA1_PERIPHERAL_FREQMHZ {142} \
CONFIG.PCW_FPGA2_PERIPHERAL_FREQMHZ {200} \
CONFIG.PCW_FPGA3_PERIPHERAL_FREQMHZ {160} \
   :
 (後略)
Pynq-Z1/bitstream/base.tcl(修正後)
(前略)
   :
  # Create instance: processing_system7_0, and set properties
  set processing_system7_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:processing_system7:5.5 processing_system7_0 ]
  set_property -dict [ list \
CONFIG.PCW_ACT_APU_PERIPHERAL_FREQMHZ {650.000000} \
CONFIG.PCW_ACT_CAN0_PERIPHERAL_FREQMHZ {23.8095} \
CONFIG.PCW_ACT_CAN1_PERIPHERAL_FREQMHZ {23.8095} \
CONFIG.PCW_ACT_CAN_PERIPHERAL_FREQMHZ {10.000000} \
CONFIG.PCW_ACT_DCI_PERIPHERAL_FREQMHZ {10.096154} \
CONFIG.PCW_ACT_ENET0_PERIPHERAL_FREQMHZ {125.000000} \
CONFIG.PCW_ACT_ENET1_PERIPHERAL_FREQMHZ {10.000000} \
CONFIG.PCW_ACT_FPGA0_PERIPHERAL_FREQMHZ {100.000000} \
CONFIG.PCW_ACT_FPGA1_PERIPHERAL_FREQMHZ {142.857132} \
CONFIG.PCW_ACT_FPGA2_PERIPHERAL_FREQMHZ {200.000000} \
CONFIG.PCW_ACT_FPGA3_PERIPHERAL_FREQMHZ {100.000000} \
   :
 (中略)
   :
CONFIG.PCW_FCLK3_PERIPHERAL_CLKSRC {IO PLL} \
CONFIG.PCW_FCLK3_PERIPHERAL_DIVISOR0 {5} \
CONFIG.PCW_FCLK3_PERIPHERAL_DIVISOR1 {2} \
CONFIG.PCW_FCLK_CLK0_BUF {true} \
CONFIG.PCW_FCLK_CLK1_BUF {true} \
CONFIG.PCW_FCLK_CLK2_BUF {true} \
CONFIG.PCW_FCLK_CLK3_BUF {true} \
CONFIG.PCW_FPGA0_PERIPHERAL_FREQMHZ {100} \
CONFIG.PCW_FPGA1_PERIPHERAL_FREQMHZ {142} \
CONFIG.PCW_FPGA2_PERIPHERAL_FREQMHZ {200} \
CONFIG.PCW_FPGA3_PERIPHERAL_FREQMHZ {100} \
   :
 (後略)

最終的に次のような結果になりました。無事にタイミング違反も無くなっています。

ハードウェアのサンプル周波数を下げた場合、ソフトウェアの方もそれに対応して変更する必要があります。

python/pynq/drivers/trace_buffer.py(修正前)
from pynq import PL
from pynq import MMIO
from pynq.drivers import DMA
from pynq.iop import PMODA
from pynq.iop import PMODB
from pynq.iop import ARDUINO

MAX_SAMPLE_RATE             = 166666667
python/pynq/drivers/trace_buffer.py(修正後)
from pynq import PL
from pynq import MMIO
from pynq.drivers import DMA
from pynq.iop import PMODA
from pynq.iop import PMODB
from pynq.iop import ARDUINO

MAX_SAMPLE_RATE             = 100000000