FPGA のクロック制約の有無で回路の動作が大きく変わるケースに遭遇したので、紹介します。今回使用した FPGA ボードは Tang Nano 9K(FPGA として GW1NR-LV9QN88PC6/I5 を搭載)です。クロック制約(create_clock)を書かないと、I/O から何も信号が出てこないという事態が発生しました。クロック制約を書くことで期待通りの動作となりました。
クロック制約を書かなかった場合と書いた場合で、I/O の出力がどうなるかを観察しました。使用したロジックアナライザは DreamSourceLab DSLogic Plus です。
クロック制約を書かなかった場合、下図の通り、I/O から何も信号が出ない状態となりました。本来、tick_toggle は 1ms ごとに 1/0 を繰り返すはずなのです。
※USB 用の回路を作っているときの話ですので、そういう信号名になってますが、USB 用の信号であることは本題に直接関係ないはずです。
クロック制約ファイルに次の行を書き足しました。
create_clock -period 10.4167 -name usb_clk -waveform {0 5.20833} [get_nets {usb_clk}]
すると、下図の通り、tick_toggle が動き出しました。他の信号も意図通りに出ています。
create_clock vs create_generated_clockusb_clk(rPLL の出力)は sys_clk を元に生成されますので、派生クロックを定義する create_generated_clock を使うのが正しいかもしれません。ただし、今回の設計では sys_clk と usb_clk の間に同期的なデータパスはなく、解析したいのは usb_clk ドメイン内のタイミングだけです。そのため、usb_clk を独立したクロックとして create_clock で定義しても実用上は問題ありません。むしろ、usb_clk と sys_clk は無関係のクロックであるように扱う方がいろいろ安全かなとも思います。そのため、今回は create_clock でクロックを生成しています。
合成後のタイミングレポートを読めば、Gowin の合成ツールが usb_clk を何 MHz の信号だと思って扱ったかが分かります。タイミングレポートは impl/pnr/プロジェクト名.tr.html として出力されます。クロック制約を書いた場合と書かない場合で比較してみます。
| クロック制約 | Constraint | Actual Fmax | 余裕 |
|---|---|---|---|
| 書いたとき | 96.000(MHz) | 104.916(MHz) | 0.8852(ns) |
| 書かないとき | 96.000(MHz) | 96.021(MHz) | 0.0023(ns) |
クロック制約を書いた場合、Actual Fmax が向上しました。これが、回路が正常に動くようになった要因だと思われます。
クロック制約を書かなかった場合でも Actual Fmax は Constraint を満たしているため、回路が動きそうな気もするのですが、実際にはまったく動作しませんでした。恐らくですが、rPLL のジッタが関与していると思います(後述)。
※クロック制約を書かなくても usb_clk の Constraint は 96MHz となりました。この理由は、Gowin のタイミング解析ツールが rPLL の設定を自動的に把握したからでしょう。rPLL の出力周波数は rPLL の Verilog ファイルのパラメータから計算可能です。Gowin の解析ツールは rPLL の知識を持っていて、各種パラメータから周波数を計算したのだと思います。
tick_toggle 発生回路参考までに、tick_toggle を発生させる部分の回路(Veryl コード)を示します。usb_clk はこのモジュールの上位にあるトップモジュールから供給されるクロックです。27MHz の外付けクロックを元に rPLL で生成した 96MHz(周期 10.4167ns)のクロックです。
const TICK_MAX: u32 = CLOCK_HZ / 1000;
var tick_gen : bit<$clog2(TICK_MAX)>; // 1ms 周期を作り出すタイマ
var tick : bit; // 1ms 周期で 1 クロック幅だけ 1 になるイネーブル信号
always_ff (usb_clk, usb_rst_n) {
if_reset {
tick_gen = 0;
tick = 0;
} else if tick_gen == TICK_MAX - 1 {
tick_gen = 0;
tick = 1;
} else {
tick_gen += 1;
tick = 0;
}
}
var tick_toggle : logic;
always_ff (usb_clk, usb_rst_n) {
if_reset {
tick_toggle = 0;
} else if (tick) {
tick_toggle = ~tick_toggle;
}
}
回路としては問題があるように見えませんよね。まさか create_clock が無いと完全に回路が動かないことがあるというのを体験して、驚きました。新しいクロックを作ったとき、ついクロック制約を書き忘れるんですよね。気をつけないと。
rPLL に限りませんが、あらゆる発振器は真に一定の周波数を出力できず、ある程度周波数が変動します。各サイクルの周期変動をジッタと呼びます。合成ツールが rPLL のジッタを考慮することで、生成された回路がさらに安定して動作するはずです。
今回、create_clock を使った場合に Actual Fmax が向上し、それが rPLL のジッタを吸収したのだと考えています。GW1NR シリーズの rPLL のジッタについては "GW1NR series of FPGA Products Data Sheet DS117-3.2.4E" の 3.4.7 PLL Switching Characteristics に記載がありました。それによれば、rPLL の出力周波数が 100MHz 未満の場合、ジッタは「<30mUI」となっています。
UI は Unit Interval のことで、1UI はクロックの 1 周期分だそうです(参照:クロック信号の精度表記について - 半導体事業 - マクニカ)。今回の場合、96MHz ですから 1UI = 10.4167ns です。したがって、30mUI = 0.3125ns となります。つまり、rPLL の出力クロックは 0.3125ns 未満の幅で揺れ動く可能性があるということが分かります。
usb_clk に対して create_clock を書いたおかげで Actual Fmax が向上し、タイミングレポート上の余裕が 0.8852ns に増えました。一方、制約を書かなかった場合の余裕は 0.0023ns しかなく、rPLL のジッタや電源・温度・配線遅延などのばらつきを考えると、実機動作にはほとんど余裕がなかったと考えられます。動作不良の原因をジッタだけに断定することはできませんが、少なくとも明示的なクロック制約により、配置配線とタイミング解析の条件が改善されたことは確かです。
create_clock を書いたために、偶然にも rPLL のジッタを吸収できるほどの余裕が生まれました。しかし、合成ツールとしては Actual Fmax が制約を満たせばいいわけですから、将来にわたり rPLL のジッタを吸収できるくらい高い Actual Fmax を維持してくれる保証はないと思います。
Gowin の合成ツールでは、set_clock_uncertainty という命令を使ってクロックのばらつき値を指定できるようです。これを使って rPLL のジッタの最大値を設定しておけば、それを考慮して合成してくれるはずです。ということで、rPLL のジッタを考慮するよう、次のような設定を入れておくことにします。
create_clock -period 10.4167 -name usb_clk -waveform {0 5.20833} [get_nets {usb_clk}]
set_clock_uncertainty 0.3125 -from [get_clocks {usb_clk}] -to [get_clocks {usb_clk}]
これで合成してタイミングレポートを見てみました。指定前に比べ、Path Slacks Table の Path Slack 値が、軒並み 0.3ns ほど小さい値になっていました。最大のばらつきがあったときを想定した計算をしてくれているようです。これで、将来にわたり安心できそうです。
set_clock_uncertainty の構文set_clock_uncertainty の構文は、ドキュメント(SUG940)によれば次のようになっています。
set_clock_uncertainty
<uncertainty>
-setup | -hold
[-from <from_clock> | -rise_from <rise_from_clock> | -fall_from <fall_from_clock>]
[-to <to_clock> | -rise_to <rise_to_clock> | -fall_to <fall_to_clock>]
<uncertainty> はばらつきを ns 単位で指定します。今回なら 0.3125ns です。
-setup は setup と recovery のばらつき解析を指定し、-hold は hold と removal のばらつき解析を指定するとのことです。あたかも省略できないような構文ですが、実は省略可能で、省略した場合は setup、hold、recovery、removal がすべて有効になるとのことです。今回は、すべて有効化したいので省略しました。(省略可能なんだったら [] で囲んで欲しいですね!)
-from はデータを出す側のクロック、-to はデータを受ける側のクロックを指定します。今回は usb_clk により駆動される回路に対してばらつき解析をしたいため、-from と -to には両方とも usb_clk を指定します。これらに違うクロックを指定すると、クロックまたぎの部分(CDC: クロックドメインクロッシング)について解析されることになると思います。