uchan が 2021年12月13日10時20分34秒 に編集
初版
タイトルの変更
FPGAの論理演算は遅延するらしい
タグの変更
FPGA
verilog
メイン画像の変更
記事種類の変更
セットアップや使用方法
本文の変更
FPGA で AND 等の論理演算が若干遅延することを実際に観測してみました。 ## 実験設定 使用した FPGA ボードは [Tang Nano 4K](https://wiki.sipeed.com/hardware/zh/tang/Tang-Nano-4K/Nano-4K.html) です。 実験のために以下のような Verilog プログラムを作りました。`raddr` と `rx_v` が今回観測したい信号で、それぞれ条件演算子および AND 演算子が使われています。`assign` は継続代入というもので、右辺の信号のいずれかが変化するたびに代入が行われます。 ```Verilog:信号の定義 wire [7:0] rdata; wire [2:0] raddr; wire rx_v; reg rx_en, rdata_v, rx_rdy, blank_rd; assign raddr = rx_rdy ? 0 : 5; // 今回注目する信号(条件演算子) assign rx_v = rdata_v & rx_rdy & ~blank_rd; // 今回注目する信号(AND 演算子) // rx_en, rdata_v, rx_rdy, blank_rd はいずれも // always 文でのノンブロッキング代入により変化する always @(posedge sys_clk) begin if (!rst_n) rx_en <= 1'd0; else rx_en <= ~rx_en; // rx_en は clk を 2 分周した波形 end always @(posedge sys_clk) begin if (!rst_n) rdata_v <= 1'd0; else rdata_v <= rx_en; // end always @(posedge sys_clk) begin if (!rst_n) rx_rdy <= 1'd0; else if (rdata_v && raddr == 5 && rdata[0]) rx_rdy <= 1'd1; else if (rdata_v && rx_v) rx_rdy <= 1'd0; end always @(posedge sys_clk) begin if (!rst_n) blank_rd <= 1'd0; else if (rdata_v && raddr == REG_LSR && rdata[0]) blank_rd <= 1'd1; else if (rdata_v) blank_rd <= 1'd0; end ``` 波形の観測は Gowin Analysis Oscilloscope で行いました。これは Gowin の FPGA に特有の測定機能で、FPGA の内蔵メモリに波形データを記録し、PC に転送して画面に表示してくれます。波形データは設定したクロックのエッジで記録できます。今回は `sys_clk` および `sys_clk` を PLL で 4 倍にしたクロックの立ち上がりエッジで波形を記録させました。 `sys_clk` は 27MHz を PLL で 13/7 倍した 50.143MHz です。「`sys_clk` を PLL で 4 倍にしたクロック」は 27MHz を 52/7 = 4×13/7 倍した 200.571MHz です。 ## 実験結果 観測された波形を示します。 ![観測波形](https://camo.elchika.com/1c97ff28226b530eae61abe6710bcc55734f31f0/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f63333936313234302d643365342d346361652d396632662d3965396366383634616562342f63666431373134662d353864342d343837332d616339662d346239383432346238333034/) まず `sys_clk` で記録した波形に注目します。見ての通り `sys_clk`(一番上の信号)は常に 0 で記録されています。立ち上がりエッジの直前の信号レベルで記録されるということですね。 2 行目の信号 `I_RX_EN` には `rx_en` が接続されているので、`I_RX_EN` と `rx_en` は等価だと思っていただいて大丈夫です。同様に 3 行目、4 行目の信号 `I_RADDR`、`O_RDATA` にはそれぞれ 、`raddr`、`rdata` が接続されており、それぞれ等価と思ってください。 `rx_en` は `sys_clk` の立ち上がりエッジのタイミングで反転するので、`sys_clk` をちょうど 2 分周した信号となります。 `rdata` は、黄色い縦線のタイミングで 5→0 に変化しています。`sys_clk` の立ち上がりエッジに合わせて、一瞬で 5→0 に変わったかのように観測されています。 ## 遅延の考察 次に図の下半分(`sys_clk` の 4 倍のクロックで信号を記録した場合)に注目します。4 倍のクロックでサンプリングしたため、`sys_clk` の変化も捉えることができています。 先ほどと異なるのは、`rx_en` と` raddr` の変化タイミングの違いです。`rx_en` は依然として `sys_clk` の立ち上がりエッジと同じタイミングで変化しているものの、`raddr` は少し遅れて変化しています。 `sys_clk` の 4 倍のクロックでしか記録をしないので、実際の遅れ幅は確定できません。ただ、少なくとも `sys_clk` の周期の 1/4(5ns 程度)以上は遅延していると判断できると思います。(`sys_clk` の周期の 1/4 より短い遅延は観測できないと筆者は認識していますが、間違っていたらコメントください) `rx_v` について見てみます。上の図では後半に山が 1 つだけ見えますが、下の図では山が 2 つ観測されました。先ほどの大きな山の前に、小さな山が出現したのです。 `rx_v` の右辺は `rdata_v & rx_rdy & ~blank_rd;` ですから、`rdata_v`=0 のときは `rx_v`=0 になるはずです。しかし、この小さな山のタイミングでは `rdata_v`=0 になっているにもかかわらず、`rx_v`=1 となっています。単に「信号が遅れている」だけでなく、プログラム上起きないはずの不整合な状態になっていることが分かりました。