Ryuz's tech blog

FPGAなどの技術ブログ

Windows版 GOWIN EDA を WSL2のコマンドラインから使う工夫とか

はじめに

GOWIN の FPGA は低価格で買えるものも多いため、夏休みの自由研究的に使う方も多いのではないでしょうか?

GOWIN EDA はユーザー登録を行って、ライセンスファイルを発行してもらわないと使う事が出来ません。 私は、USBを繋いでプログラムを行ったり、埋め込みロジアナ(Gowin Analyzer Oscilloscope)を使うことなどと、WSL2内のMACアドレスが固定されないことなどを考慮して、あまり深く考えずに WindowsMACアドレスでライセンスを申請してしまいました。

一方で、普段の開発は殆ど WSL2 で、しかも極力コマンドラインで済ませようとする派です。

そこで前々から WSL2 から Windows版 の GOWIN EDA を使う工夫をいくつか行っていましたので、一度整理しておこうと思います。

どこかに書いておかないと、主に自分が困るというのが一番の理由ですが(笑)

WSL から Windows 版のコマンドを呼び出せるようにする

コマンドライン派の方が一番よく使うコマンドは gw_sh かと思います。

そこで下記のようなスクリプトを作って、パスの通ったところ(例えば ~/.local/bin とか)に投げ込みます。

#!/usr/bin/bash
/mnt/c/Gowin/Gowin_V1.9.11.03_x64/IDE/bin/gw_sh.exe $@

これで WSL の中から gw_sh コマンドが使えるようになります。alias とかでもいいかもしれません。 もうちょっと頑張れば、環境変数でバージョンを切り替えたりもできそうな気がします。

次に、programmer_cli も同じように使おうとしたのですが、どういうわけか、こちらはディレクトリを移動しないとうまく動いてくれませんでした。

#!/usr/bin/bash
cd /mnt/c/Gowin/Gowin_V1.9.11.03_x64/Programmer/bin/
./programmer_cli.exe $@

上記のままだと、fs ファイルは絶対パスでしてしないといけません。

私は Makefile の中から programmer_cli を呼んでいたので

run_wsl: $(FS_FILE)
    programmer_cli --device GW5AST-138C --run 2 --fsFile '$(shell wslpath -w $(FS_FILE))' --location 11585

のような wslpathWindowsから見た絶対パス に変換してから呼び出すようにしました。

openFPGAloader

zadig によるデバイスドライバの入れ替えが必要なので、GOWIN の programmer と共存できませんが、openFPGAloader も良く使います。

こちらも、USB は Windows 側のまま使う方が楽であるため、Windows版のopenFPGAloader を使う事も多いです。

手っ取り早くはこちら のものをインストールすることなのですが、私は Tang Mega 138K Pro Dock を使うときにより新しいバージョンが必要となってしまい msys2 をインストールして pacman でインストールしました。

当時の入れ方を忘れてしまったのですが、今 Gemini さんに聞くと

pacman -Syu
pacman -S git make mingw-w64-x86_64-gcc mingw-w64-x86_64-libusb

で入るようです。

そして、また同様に WSL2 で

#!/usr/bin/bash
/mnt/c/msys64/mingw64/bin/openFPGALoader.exe $@

というファイルを作りました。

コマンドラインでプロジェクトをビルド

プロジェクトのビルドもコマンドラインだけでやりたくなります。

gw_sh に tcl スクリプトを渡せば、ある程度コントロールできるようです。

私は Makefile から環境変数経由で tcl スクリプトに値を渡して合成するために gowin_build.tclいうファイルを作りました。

使い方はこちらの Makefile などにありますが、WSL2 の場合は WSLENV という環境変数を用意しておけば、WSL から Windows 版の gw_sh に環境変数が渡せます。

絶対パス相対パスに変換

GOWIN EDA を使っていると IP を生成したり、Analyzer Oscilloscope を設定したりするのに、GUI 版のプロジェクトの作成も避けられません。

幸い、WLS のディスク領域(\\wsl.localhost で始まるパス)にプロジェクトを作っても Windows 版の EDA は問題な開いてくれます。

しかしながらプロジェクトファイル内でプロジェクトの外のディレクトリを参照するとファイルのパスが絶対パスになってしまうようです。

私は、いろいろな RTL を異なるプロジェクトで使いまわす関係上、プロジェクトの外のパスを多用しています。

一方で、プロジェクトのなかのソースファイルが埋まっている .gprj ファイルは XML 形式のようです。

そこで、中の絶対パス相対パスに書き換えるツールを AI に作ってもらったのが こちら になります。

この手のものは、日本語で指示するだけで AI が一瞬で作ってくれるので、ホント凄い時代になったものです。

試したところ、開くだけなら問題なく開ける(保存するとまた絶対パスに戻る)ようですので、git commit 前に相対パスにしておけば、別の場所で clone しても大丈夫になりそうです。

おわりに

以上、どこかにメモを書いた気もするのですが検索しても出てこないので、ここにノウハウを纏めておきます。 割と行き当たりばったりで整備してきたので、「もっといい方法あるよ」というのはいろいろご意見ありそうには思いますが。

なお、今更ですが、申請すればフローティングライセンスももらえるという噂を聞きました(笑)。 ただし、仮にそうだとしても「FLEXlm などの設定が面倒」という理由も発生しそうですので、きっと少しは需要がある事でしょう。

GOWINで加算の性能を調べてみる

はじめに

最近 GOWIN を触る機会も多いのですが、他のベンダーさんの FPGA とくらべてあまり深いところまでは触っていません。

また、 AMD の CARRY8 のようなものが無いのではという噂も聞いたのと、LUT4 であるという点なども加味して、大雑把に性能を調べておこうと思って触ってみました。

やったこと

合成に時間がかかるのは嫌なので、 Tang Nano 4k (GW1NSR-LV4CQN48PC6/I5) をターゲットに、カウンタの幅を変えて合成し、レポートの Fmax を調べるという事をやりました。

GOWIN EDA は V1.9.11.02 (64-bit) [Windows版]を使っています。

合成したコードは下記の通りで、WIDTH を変えて実験します。

`default_nettype none

module tang_nano_4k_adder
        #(
            parameter int   WIDTH = 32
        )
        (
            input   var logic           in_reset_n,
            input   var logic           in_clk,     // 27MHz

            output  var logic   [0:0]   led_n
        );
    

    logic   [WIDTH-1:0]  counter;
    always_ff @(posedge in_clk) begin
        if ( !in_reset_n ) begin
            counter <= 0;
        end
        else begin
            counter <= counter + 1;
        end
    end
    assign led_n[0] = ~counter[WIDTH-1];

endmodule


`default_nettype wire

合成結果のレポートで fmax を探すとこのように出てくるようです。

結果

本格的にやるならスクリプト書いても良かったのですが、 手っ取り早く手動で合成を繰り返しました(笑)。

bit数 MHz LUT Regs
1 レポート無し 1 1
2 368.474 3 2
3 322.238 4 3
4 296.991 5 4
5 342.906 7 5
6 378.582 8 6
7 330.006 9 7
8 326.435 10 8
9 318.041 11 9
10 348.501 12 10
11 306.913 13 11
12 335.184 14 12
16 311.387 18 16
32 203.419 34 32
64 148.368 66 64
128 67.424 132 128
256 39.306 262 256

おわりに

なんとなく素直に LUT の数が増えていきながら fmax も順調に下がっていくようですね。ただレジスタは桁数ぴったりですが LUT は時々余分に増えているので、単純な全加算器が連なっていくのではなく、何かしらキャリールックアヘッドみたいなのがあるのでしょうか?

とりあえずRISC-V なんかを作る場合、RV32 はいいけど RV64 は大変そうだという気がしてきます。

また Tang Nano には無縁ですが、最近は FPGA に繋ぐメモリも 4G 以上になるケースも増えていて、DAMのアドレッシングも 64bit じゃ足りなんないなんてこともあるので、大きな数を扱うときは気にしておいた方がいいのかもしれません。

逆に、へんなことせずに 32bit 以下ぐらいで使う分には十分実用的な速度が出ていそうに思いました。 中のアーキテクチャはどうなっているのでしょうね。資料があるならいつかまた時間のある時に見てみたいと思います。

アクティブマルチスペクトルカメラを妄想してみる

今作っている Spartan7 + オンセミ社 PYTHON300 イメージセンサ、やっと絵が出始めました。

お手製のPYTHON300カメラモジュール

で、いつも通りの LUT-Network への応用や、オプティカルフロー計測、その他リアルタイムアクティブセンシングいろいろ考えているのですが、その中の応用例の1つを張っておきます。

しれっと PMOD つけているので外部でLED基板を作ればシャッター同期制御が簡単にできるはずです。

マルチチャネル計測はだいぶ以前から言ってるネタですが、別件で温めていたのでXには書いたけどブログに書いてなかったと思うのですがだいぶ前に書いた絵があるので張るだけ張っておきます。

リアルタイムフィードバック要素よりデータ分析要素が多いので私的にはそこまで重視はしてなかったのですが、こちらの方が需要はありそうなので。

動作例

  • 一応アクティブセンシング
  • 照明とシャッターの同期はグローバルシャッターでしかできない
  • とにかく安くスペクトル方向に何か測れる
  • 照明制御へのフィードバックでいろいろな変化が付けられる
  • 60fps相当のデータレートで16ch撮影できるので、深層学習の素材としても良い
  • 速いので動く物体を対象にできる可能性
  • MIPI なので Raspberry PI にもつながるかも?

などなど?

照明制御は1例ですが、FPGA弄ると外界と同期しながら多数の情報が取れるため、いろいろな応用があるかとは思います。

一方、当方はあくまでリアルタイムコンピュータ推しなので、単なるセンサーにせずに何かもっと即時性のある出力先を探したいところではありますが。

メタステーブルについて考えてみる

はじめに

もう専門の方から一直線にマサカリが飛んできそうなタイトルで怖いんですが、ちょうど今週末は非同期周りを整理していて X でも少し盛り上がったのでネタにしておきます。

組み込みやっていると、チャタリングとかシュミットトリガとかはよく聞くわりに、HDL 書かない限りはあまり聞かない メタステーブル ですが(偏見?)、FPGAでの非同期バグでは鉄板のネタなので触れておきたいと思います。

再現性がないのでやらかすとデバッグがとても厄介なのですよね。

ちなみに私がメタステーブルを最初に勉強したのは定本ASICの論理回路設計です。

メタステーブルとは

フリップフロップ(FF)に値を正しくラッチさせるためには、セットアップ時間、ホールド時間、VIL/VIHなどの電圧範囲を守る必要があります。

これらが守られてない場合、出力が 0 になっても1になっても文句は言えないわけですが、0でも1でもない第3の状態としてFFがメタステーブル状態に突入することがあります。

メタステーブル状態に入ると、1にも0にも確定していない不安定な状態が指数分布に従う時間だけ続いて、やがて0か1に倒れて安定状態になるそうです。

「このようなことが起こると困りますよね。定格はちゃんと守って使いましょう。」で済めばいいのですが、そうはいかないのでこの話になるわけです。

人間がボタンを押すとかの非同期な事象に加え、異なるオシレータからのクロックドメイン同士の通信だとこのメタステーブル状態の発生は基本的に避けられません。

同じ100MHz同士であったとしても、オシレータ は数 ppm 程度の誤差は常に持っていますので、100MHzもあれば1秒後には100サイクルとかのオーダーでずれてしまっているわけで、その間に 100回ぐらいメタステーブルになりうる条件を作ってしまいます。

ただし、メタステーブル状態はずっと続くわけではないので、一定時間で収まり、ダブルラッチなどしておけば、次のクロックまでには高い確率で収まります。

ChatGPT 曰く

メタステーブル状態の発生に関する MTBF(Mean Time Between Failures)の式は以下のようになります:

\text{MTBF} = \frac{1}{f_{\text{clock}} \cdot f_{\text{data}} \cdot T_{\text{window}}} \cdot e^{\frac{T_{\text{res}}}{\tau}}
  •  f_{\text{clock}} : クロック周波数
  •  f_{\text{data}} : データ入力の切り替わり頻度
  •  T_{\text{window}} : メタステーブルが発生する時間幅(セットアップ+ホールド)
  •  T_{\text{res}} : 解決のために使える時間
  •  \tau : フリップフロップ固有の時定数

だそうです。

確率は常にゼロにはなりませんが、通常はダブルラッチするだけで数万年オーダーの MTBF となるそうなので、ちゃんとあしらっておけば、確率的にはECCのないメモリのソフトエラーなどの方がよっぽど心配すべき事象になる程度には下げられるのだと思います。

非同期信号のラッチ

いろんな解釈があると思いますが、入力信号を綺麗にラッチするために、入力クロックの位相をPLLで調整していっていい感じの場所を探すなんてことはよくやるわけです。

で、そんなノリでクロックの方の時間をずらしながら図示してみると、

データとクロックの関係

すべてのラッチ条件が満たせていてスペック通り0や1が取り込める領域と、そうでない領域があります。

とはいえ、「非同期を受けるときはダブルラッチしなさい」とよく訓練されているプログラマだと、とりあえずダブルラッチしているので最終的に0が1に値が倒れるので、早い方か遅い方のどちらかをラッチしたように見えてしまいがちです。

しかし、0と1のどちらかに倒れる話と、メタステーブル状態に突入する話は全く別の話なのでこれは注意しておく必要があります。

FPGA での非同期の扱い

RTL で書いてちゃんと制約する

私は Verilator などの OSS のシミュレータを使う関係上、あまりベンダーの IP は使わずに RTL でいろいろなものを書いてしまいがちです。

その際 AMD の場合、楽に制約を設定するために ASYNC_REG というアトリビュートがあります。 UG912にこのアトリビュートの詳しい説明があります。

UG912の図

これは何者かというと、合成時に関して言えば、メタステーブルが伝搬しにくいように受け側のFFを複数並べてダブルラッチ/トリプルラッチなどを作るときにFF同士を最短経路で繋がるように配置配線を制約してくれるというものです。

折角ダブルラッチ構成にしてもFF同士の距離が離れてしまうと、せかっく元のFFでは1クロック時間たってメタステーブルが収まったのに、取り込む側が配線遅延でまだメタステーブル中だった信号をラッチしてしまい自分もメタステーブル状態に突入する という身もふたもないことが起こってしまいます。

ベンダーの用意したライブラリを使う

AMD の場合、例えば UG974 などを見ると、下記のようなマクロが用意されています。

  • XPM_CDC_ARRAY_SINGLE
  • XPM_CDC_ASYNC_RST
  • XPM_CDC_GRAY
  • XPM_CDC_HANDSHAKE
  • XPM_CDC_PULSE
  • XPM_CDC_SINGLE
  • XPM_CDC_SYNC_RST

これらはクロックドメインをまたいで信号をやり取りするときの、定石的な方法をブラックボックス化してくれているようです。

実は今まで使ったことなかったのですが、使えるときは使った方が安全 かなと心を入れ替えつつ、そうはいっても使えないときに困らないようにRTLで互換部品も書いておこうと、今日この辺りを書きながら勉強してました。

案外普段自分がやってる方法と同じものもあれば違うものもあり勉強になりました。

衝撃だったのが、FFの段数がデフォルトで4なのですね。 トリプルラッチどころじゃないですね。 いつもダブルラッチで済ませているので不安を覚えました。 段数を増やすほどにMTBFは増大していきますので、周波数にもよりますが、品質の問われる用途に応じて増減させるものと思われます。

身もふたもない話

そんなプリミティブなところ弄くらんでも とりあえず非同期FIFO使っておけばOK というのはその通りで、ほぼすべてのFPGAベンダーが非同期FIFOを提供していますね。

AMD も UltraScele 世代だと、IP 生成以外に XPM_FIFO_ASYNC などのマクロも使えるようですね。

逆に 非同期FIFO があまりにもうまく臭いものに蓋をしてくれているせいで、このような話を調べる必要も普段はあんまりなかったとも言えます。

グレイコードに関する疑問

ちなみに IP 使うの避けがちな私は、超昔に書いたグレイコード使うFIFOを未だによく使っていたりします。

とはいえグレイコードには少し疑問も残っていて、複数bitが同時にメタステーブルを起こしてしまう可能性、すなわち、クロック周期からメタステーブルに入る条件のマージン分を引いた値に、全bitの遅延ばらつきを納めておかないと、メタステーブルを起こすのは高々1bitで、それが1に倒れても0に倒れても前後どちらかの符号になるだけ というグレイコードFIFOの前提条件が壊れる件です。

よく

set_max_delay -datapath_only -from [get_clocks clk1] -to [get_clocks clk2]  2.00

のような制約を書くのですが、結局、遅延時間はいくつを書けばいいのかと。

もちろんこれがクロック周期以上ずれるとメタス以前の問題で符号が化けるので、クロック周期より小さい値ではないといけないとは思うのですが、具体的にいくつにするのが良いのかなかなかよくわからず。

もっと言うとバラつきが問題なので set_max_delay というのもそもそもなんか違うんじゃないかと思ってみたり。

XPM_CDC_GRAY の中身にいったいどんな制約が埋め込まれているのか興味津々なのですが、合成結果だけ見ても分からないのですよね。

どう書くのが正解なのでしょうね。いやむしろ XPM_CDC_GRAY を使うべきなのでしょうね。

おわりに

なんとなくとりとめもない記事になってしまいましたが、こんなことをしていた1日でした。

余談(VIH/VIL)の話

今回FPGAの中の話でしたが、FPGAの外だと、Hとみなせる電圧(VIH)、L とみなせる電圧(VIL)に規定があります。

で通常はあまり問題ないのですが、I2Cみたいにプルアップ抵抗からの給電でHレベルを作るようなバスだと、電圧の立ち上がりがとても長いケースがあります。

そうするとFPGAの数百MHzなんかでラッチすると、電位が VIH と VIL の間にいるときに何度もラッチしてしまって、中にはメタステーブル状態に入って 0 や 1 を行き来することがあります。

チャタリングとはまた違う現象で、電圧は単調増加しているのになぜかラッチ後のデジタル波形に髭が出てくるんですね。

対策は、例えば8サイクルに1回だけラッチするとか、通信している仕様にあわせてラッチ周期を間引くことのようです。

間引いてしまえば、偶然1個メタステーブルを起こすようなタイミングに来ても、前後は正しくラッチされますので、0と1どちらに倒れても I2C としてのプロトコルに不整合は起こらなくなるわけですね。

いやはや、非同期系はほんと面倒なことが多い。

リアルタイムコンピューティング的CNN再考

はじめに

当方はFPGAを使ったリアルタイムコンピューティングをメインに活動しております。

流行りの深層学習は私の中では One of them でしかないつもりなのですが、それでも昨今どうしても無視はできないので、リアルタイムコンピューティング観点でCNNを眺めなおしたらどうなるのかというのを再考察してみたいと思います。

まずリアルタイムコンピューティング的には

  • 入力をなるべく低遅延で出力に反映させる
  • 情報は古くなると価値が下がるという前提で、価値が最大化するようアルゴリズムと計算機アーキテクチャをセットで考える

の2点を大事にしています。

世の中は計算している間にどんどん変化していきます。壁にぶつかってからブレーキを踏んでも遅いし、ロボットが流れてくる不良品の隣の良品を廃棄しても困るわけです。

CNNを考えてみる

当方は画像信号処理を扱うことが多いので、無難にCNNの話をします。よくあるこんなやつです。

よくあるCNNのアーキテクチャ

フルサイズで入力された画像を順番に各レイヤーで処理して、途中でプーリング処理を挟んで小さくしながら、でも特徴量を表すチャネルの深さは深くなりながら(次元数を増やしながら)次々と処理していきます。

特にこのプーリング処理で縮小されていくのは重要で、前段では細部を、後段では全体を認識することができます。

画像処理でもよく画像ピラミッドを作りますが、縮小された画像で全体を把握しつつ、オリジナルの解像度に近いところで細かい計測をします。

さて、この段階で、入力から出力までのレイテンシが長そうだというのがなんとなく想像がつくわけです。

リアルタイムコンピューティング的に考えてみる

リアルタイムコンピューティング的には入力をなるべく低遅延で出力に反映させるという事がしたいわけです。

ここで唐突にセマンティックセグメンテーションを考えてみます。

カメラからは左上のピクセルから順に画素が読み出されてきて計算機に入力されるわけですが、1画素新しい情報を得たら、なるべく早くその画素がどういうクラスなのか情報を出力したいわけです。

となると、まあ、下図のようなアーキテクチャに考えが至るわけです。

リアルタイムコンピューティング的CNN(妄想)

まず、何はなくとも、入力されたらなるべくすぐに、赤い線のようにまっすぐと入力画素に対応する部分の出力結果をアップデートしたいわけです。

そうはいってもこれだと、例えば 3x3 の畳み込み層だと遅延は2ライン貯める時間で済んでも、高々周辺の9画素の情報だけで推論しろという無茶な話になります。

ここで、本サイトお得意の1000fpsの高速度カメラなどの話になります。世の中の変化より十分早くオーバーサンプリングしておけば、少し大雑把な周辺情報は1フレーム前の情報でもある程度あてにできるよね? という、撮影対象のダイナミクスよりもサンプリングレートを上げる話に持っていきます。

要するに例えばCNNでありがちな犬猫認識をしつつ動きを追う場合、「猫パンチの手の位置は素早く動くが、1/1000秒後にそこにいた猫が犬に入れ替わったりはすまい」という前提で、「手の動きは最新フレームで追いかけるが、その手が猫のものか犬のものかは少し古い画像も使って認識してもいいよね」というアルゴリズムにするわけです。

で、これを以前やりかけたまま放置してるのが、以前書いた

ミップマップを使ったIIRフィルタライクなRNNを考える - Ryuz's tech blog

です。

まあ、学習させるのがしんどくてそのうち高価なGPU買う機会があったらやろうと放置したまま幾星霜。

しかしまあそういう思想なわけです。

Layer1 で最短経路で出力を更新しつつ、Layer2 以下で Pooling して縮小した画像を処理してさらに下の層へ。一方で、下の層の認識結果も同様にアップコンバートしながら上に戻していくわけです。

各レイヤーでそれぞれのサイズ感での認識ができればな、というのと、過去のデータの更新という意味で RNN 的な要素も取り込めればと思ってしまうわけです。

計算効率をどう上げるか

最近Xのこのへんで書いた話なのですが、画像ピラミッドの縮小に目を付けた話です。

畳み込み層といっても中は IM2COL した後は普通の全結合層が入っているわけですが、全結合層を考えると入力100chで出力100chの時、10000接続あるわけでch数の二乗で演算量とパラメータ数が増えていくわけです。なので画像サイズが1/4になってもチャネルが2倍になると計算量は元と変わらなくなります。

一方、私のやってる LUT-Net の場合、物理制約で 6入力LUTのカスケード数で例えば3段の場合 63=216固定であり、ch数が倍になっても接続は倍にしかなりません。どういうトリックかというと、全結合層の場合、1つのチャネルは全部の出力にもれなく重複して繋がってるところ、LUT-Net ではどんどん重複が減って疎結合になっていく代わりに計算量がリニアな伸びに制限できます。

でまあ、LUT-Net の場合はパラメータ入れ替えできないからこういうことはできないのだけど、仮にDSPとか普通のネットに同じようなポリシーで疎結合化をできたとしたら、単に演算量の話だけすると、1, 1/2, 1/4, 1/8 ・・・ と減っていくよねと。 でほんとにそんなことが出来たら 1 + 1/2 + 1/4 + 1/8 +・・・ = 2 なので、2レイヤー分の演算ユニット入れておけば全部うまいこと回せないかと。低遅延&演算器稼働率100%が目指すところではあるわけです。

演算ユニット2つで無限の深さをやれないか?(妄想)

猫が猫かどうか認識する機能なんて、猫のサイズを同じサイズまでリサイズすれば同じ認識器でいけるはずだし、単純に探索範囲=画像サイズで縮小するほど処理量減っていくはずだし、なんか、なんとかなりそうな気がしなくもないところです。

まあ、実際問題、どうやって学習させるんだとか、精度出るのかとか、いろいろ未知数というかほとんど妄想なのですが、晩酌しながら考えて呟いてしまったわけです。

おわりに

上記のような考えが、まあ半分以上妄想として、じゃあ全部妄想かというとおそらくそんなことは無くて、リアルタイム計算にもっと適したネットワークモデルというのは少なからず、今のCNNより改善できる形はあるであろうし、それを学習させる方法なんかもまだまだ未探索な世界なんじゃないかと思います。

また上記はあくまで「空間認識」において、広い範囲を見るために縮小という概念を時間方向に展開して効率化を試みたわけですが、RNN構造はそもそも「変化に含まれる情報の識別」という意味合いも持っておりますので、学習データ次第ではこの構造で例えばジェスチャー認識のようなもっと動きパターンに起因するものを認識できる可能性なども出てくるかもしれません。

こういったシステムを実際に作ってみて、試してみるというようなことができるのが、FPGAの良いところであり、計算機科学に興味を持つメリットが生まれてくるところです。

どこかの誰かの新しいアイデアに繋がれば幸いです。

ハイサンプリングレートのススメ

はじめに

当サイトはリアルタイムコンピューティングの為に、ハイフレームレート撮影などサンプリングレートを高めることでリアルタイム性を上げることをことあるごとに推奨しています。

それはどういうことかというのを少し説明したいと思います。

高速度撮影すると画質が落ちるという誤解

高速度撮影すると画質が落ちると思われがちです。

イメージセンサには光ショットノイズや暗電流ノイズなど様々なノイズが入り込みます。 ノイズはランダムですので、N倍の時間露光すれば S/N は √N 倍改善するのはその通りです。 逆に露光時間の短い高速度撮影ではノイズは大きくなります。

しかしながら、フレーム間の露光できない継ぎ目の時間を無視すれば、N枚の画像を単純に足し算すれば、S/N はN倍の時間露光したのと同じになり、しかもダイナミックレンジがN倍になるというメリットまで得られます。

そして、何より、リアルタイムコンピューティング的に次の図のようなことが成り立ちます。

高速度撮影の効果

10ms の露光撮影では、10ms 後の結果が得られるまで何もできません。

一方で、2ms 露光 の高速度撮影では、S/Nは悪く、認識精度は下がるものの 2ms から情報が得られ始め、10ms 後には 10ms 露光と同じ精度が得られます。

ロボットの制御などを考えてもらうとわかりやすいと思いますが、精度は悪くとも、その時点で最も尤もらしい方向に向かって予備動作を始めておけば、10ms のあいだ口を開けて待ってるだけの状態よりよい結果が得られる可能性が出てきます。

「露光と露光の間の不感時間は問題にならないのか?」というのも時々聞かれるのですが、「用途による」としか答えられない部分があります。一方で、あんまり気にならないケースが意外に多いと思っています。というか、撮影中の動きブラーや、ローリングシャッター歪みのような露光中に撮影対象が動くことで起こる課題に対して、長時間ロコ脳のまま挑む方が課題が多い気もしています。

映像出力の場合はどうなのか

筆者はOLEDディスプレイの高速駆動などを公開していますが、OLED に限らず、プラズマディスプレイ、プロジェクタ、天井のLED照明、など PWM 制御で人間に明暗を見せている機器は沢山あります。このブログの読者の皆様もLEDチカチカの延長で LED の輝度を変えたことがある人も多いのではないでしょうか?

これも人間の感度よりも高いサンプリングレートを利用している例です。

PWMを使った照度制御

同じ原理で S/N の悪い高サンプリングレートの画像の撮影を、S/N の悪いまま高いサンプリングレートで表示すると人間の目には長時間露光の映像と同じように見えて、かつ、遅延だけが減少するという事が起こります。例えば遠隔操縦などする場合、遅延は大敵ですのでこれだけでも高い効果が得られます。

そしてここにAIなどの認識を加える場合も同様で、仮に認識精度が悪く、本当は 50% の照度で表示すべきところ、誤った認識結果で 80% の照度で表示してしまったとしても、次のフレームの認識で誤りを正して 20% の輝度まで下げれば、人間には 50% の照度に見えてしまうのです。

ハイサンプリングレートで、早く認識する ということは、案外リカバリが効く為、ローサンプリングレートで 高精度だが遅い 認識より、時にロバストに機能します。後者は高精度ではあれ誤認識すると取り返しがつきにくいのです。

データ帯域が増える?

これは解像度そのままにフレームレートを上げればそうなのですが、そもそも動いているものを対象にする場合、動きブラーでボケボケの画像を高解像度で撮影するよりも、データ帯域そのままに解像度を下げてその分フレームレートに振る方が良いケースもあります。

画像サイズを縦横1/4にしても、サンプリングレートを上げて精度を上げることで 1/4 ピクセル精度まで計算できれば、縦横分で 16倍フレームレートを上げることができます。

計算量が増える?

これも必ずしもそうではありません。例えばこのスレッドで書いた話があります。

そもそも、1枚の画像の中で頑張るという発想ではなく、新しい情報が入ってくる度に情報をよりもっともらしいものにアップデートするという考え方のリアルタイムコンピューティングでは、アルゴリズムのオーダー自体がごっそり変わることも珍しくありません。

ここに頭を捻る価値がありますし、計算機アーキテクチャとセットでアルゴリズムを考える意義が出てきます。

おわりに

今回もリアルタイムコンピューティングのススメでした。

世の中のサンプリングレートは、人間の感度など、物理世界のダイナミクスに対して必要十分なところに設定されがちです。 一方で例えば 1bitΔΣ変調 のような、ダイナミクスより十分高いサンプリングレート を前提として、不連続なアルゴリズム進化を遂げる例もあります。

このような条件を加えていくと、画像処理アルゴリズムももっともっと考える余地や研究領域が増えてきて面白くなります。

そしてこのような世界でアルゴリズム探求をできるようにしてくれるのが FPGA という楽しいプロセッサなのです。 FPGAプログラマが少ない分、逆にこの分野はブルーオーシャンですので、 レッドオーシャンと化しているGPUでのAI分野で疲弊している方、RTL プログラミングを勉強する価値はこんなところにもありますよ。と、また無理やりFPGA推しして〆ておきます。

演算量とリアルタイム性の関係

はじめに

昨今、深層学習を中心に例えば画像認識を行うにしても大量の演算能力を投入して、高度な結果を得ようとするものが増えました。

しかしながら、同期デジタル回路による計算機が持つ演算能力には上限があり、演算器数×周波数以上の時間当たりの演算はできません。 また、LSIの中に集積されるトランジスタの数は半導体プロセスと面積、要するにコストで決まる有限のものです。

したがって、計算効率に関しては、如何にLSI内のトランジスタを無駄なく演算器に割り当てて、かつ、その演算器を休ませずに稼働させるか、という問題になってきます。これは計算機アーキテクチャの工夫で変わってきますのでどのような計算機を設計するべきか頭を捻る価値の高い分野です。 これは何もLSI設計者だけの話だけではなく、CPU/GPU/FPGAプログラミングでも使っているチップのトランジスタを如何に高稼働させるか考える点では共通だと思っています(そもそも私LSI開発したことあるとは言い難いですしw)。

そしてスループット性能(時間当たりにどれだけ大量の計算ができるか)は、演算器の稼働率だけで決まるのですが、一方で、同じデバイス条件で同じ演算量をこなす場合でも、リアルタイム性すなわち低遅延性(低レイテンシ)という点においては、少しだけ条件が変わってきます。 極論をいうと最高性能のでる計算機で、意味もなくリングバッファに一定時間溜めてから出力するような仕組みを加えるだけで、演算量(スループット)を変えることなく遅延(レイテンシ)だけを大きく悪化させることもできてしまいます。

では同じだけの演算量を認識処理に投入できる中で最も低遅延なアーキテクチャとは何なのか?という、疑問が浮かんできます。

今日の記事はその点にフォーカスしてみたいと思います。

汎用計算機の繰り返し適用と専用計算器パイプライン

以前このあたりのブログで描いた絵を再掲しておきます。

なるべくシンプルに演算能力が同じでも計算機アーキテクチャ次第でレイテンシが小さくなる例を示したいと思います。 対象は画像処理とし、カメラの入力画像に対して、その計算機で出来る最高の演算をリアルタイムに適用することとします(例えばFPGAなら、内蔵するDSPを常に全部使っている状態を想像してください)。

まず、汎用計算機を繰り返し適用する例を考えます。

汎用計算機

例えば、LSI内のすべての演算能力を投入して、入力データ帯域のN倍の帯域を持つ汎用演算器を用意すれば、同じ汎用演算器を繰り返し利用して、1フレームの間にN種類の処理を逐次的に処理できます。 この時、基本的にはある処理がすべて終わってから次の処理が起動されますので、前の処理の結果は一度すべてメモリに書き出されます。

ここで、同じくLSI内のすべての演算能力を投入するパイプライン型の専用計算機を考えます。

専用計算機のパイプライン

ここでは、入力データ帯域と同じ帯域でデータを処理するように各処理専用の計算ユニットをN種類用意して、それぞれにLSI内の演算リソースを分割して割り当てます。

そして各計算ユニットは計算が終わった端から次の演算器にパイプライン的にオンザフライで直接的にデータを渡していきます。

こちらもLSI内にある計算資源は100%駆動されますので、演算量は汎用演算器方式と変わりません。

入ってきたデータをメモリに溜めることなく、新しいデータが入ってくる度にできる計算をどんどんやっていこうという貪欲なアルゴリズムで、とにかく今入ってきたデータで、今出力できる最善を出力し続けようという試みです。

同じ演算量であっても、レイテンシが減らせる場合があるというのは、理解いただけるのではないかと思います。

そうは言ってもできない計算もあるよね

もちろん、これは理想論で、例えば「画像の各ピクセル値の平均と分散を求めてから画像のコントラストを調整する」ような処理であれば、まず画像全体のデータがないと処理ができません。汎用計算機であれば、メモリを2回走査して、1回目で平均と分散を求め、2回目で各ピクセル値を補正するでしょう。

一方で、パイプライン型の計算機だと、例えばカメラからの画像は左上のピクセルから順に計算ユニットに入ってきます。

カメラ画像の走査

この時、あるピクセルが入力されたとき、普通に考えるとまだ、オレンジの部分の情報しかないため、水色の部分の情報を待たないと今入ってきたピクセルコントラスト補正はできません。

しかしリアルタイム計算機における画像処理とは基本的にカメラ動画に対するリアルタイム計算ですので、1フレーム前の情報でよければ水色の部分の情報がすでにあります。 この時、ローリングシャッターカメラを想定すると、そもそもラインごとに時間経過しているわけですから、どのピクセルが来た時も同じ条件で1フレーム時間内の全ピクセル空間の情報にアクセスできるという事になります(わかりやすくローリングシャッターの例を書きましたが、グローバルシャッターでも本質は同じです)。

カメラ画像処理の例(赤枠面積はいつも同じ)

これは情報が時刻を持っており、古くなるほど情報の価値が下がる という事を念頭に置いて、価値を最大化するアルゴリズムを考えて出力する という視点に立てば、至極まっとうな考え方であり、フレームの最後まで待って処理をするというアルゴリズムが必ずしも最適でない という点に踏み込めばアルゴリズム変形で解決される場合があります。

(もちろん「フレームの途中でコントラストが変わったらどうするんだ!」という意見も出てくるわけですが、「そもそもローリング歪みに目をつぶってる件は?」という反論もあるわけでして。)

もちろんどんな場合でも解決できるとは言いませんが、従来のアルゴリズムを単に高速に実行するだけではなく、計算中に経過する時間も加味してより最適なアルゴリズムを再考する という事は重要だと思っています。

なお、フレームレートを速くすればするほど、前のフレームを使った場合の誤差は低減していきますので、対象とする実世界のダイナミクスやデバイスとセットでアルゴリズムを考える ということも重要です。

特に深層学習において、このような考え方でネットワーク構造などの学習モデルを作る ということはあまりなされていない気がしており、まだまだ発展の余地がありそうに思います。

レイテンシ以外のパイプライン方式の利点

それ以外の利点として思いつくものをいくつか挙げておきます。

リソース効率がよい

これは各計算ユニットで、それぞれの処理専用に計算機を作れますので、汎用化するコストが減ります。単純に比率としてより多くのトランジスタを計算に割り振れます。こちらで発表した内容とかみたいな話です。

ヘテロジニアスな計算ができる

汎用計算機の場合、例えば INT8 で計算機を作って 深層学習の各レイヤーを逐次処理させるなどという事を行うわけですが、パイプライン方式であれば、1層目はINT8 で、2層目はINT5 で、3層目は INT11 で、みたいなこともできてしまうわけです。

メモリが削減できる

パイプライン方式の利点として、計算ユニット同士がメモリを介さずに直結できる可能性を有しておりますので、メモリそのものが不要になる場合すらあると思います。これはコストや電力にも効いてきます。

結局はアルゴリズム

結局のところ計算機アーキテクチャを考える際には、アルゴリズムをどうするか(深層学習の場合モデルをどうするか)かと思います。

その時に、デバイスの性能を引き出すためのアルゴリズムを考えたり、逆にアルゴリズムを成立させるためにデバイス側に工夫を加えたり、計算機アーキテクチャやデバイス特性まで考えてアルゴリズムを改善していき、それに合わせたシステムを作ってしまうという事が大事なのだと思います。

CPU/GPU で出来るアルゴリズムを前提に勝負をしてしまうと、「NVIDIAを〇〇万枚買える組織」が最強という事になってしまい、アルゴリズムの工夫で勝つ ということがやりにくくなってしまうと思っています。これはとてももったいないことです。

なぜこのようなアルゴリズムに踏み込む人が少ないかというのを考察すると、このようなアルゴリズムをやるには例えば RTLプログラミングのような

が必要であり、さらに悪いことにこれらにしばし HDL(ハードウェア記述言語)という名前がついているせいで、ソフトウェアプログラミングではないという誤解というか、壁を作っている点にある気もします(このあたりは私の被害妄想かもしれませんがw)。

あと、「手軽にそういうことができるカメラとFPGAのセットがない」というのは、まあ間違いなくあるので、こちらのような妄想プロジェクトを進めていたりするわけですが。

おわりに

例によって最後に「FPGAいいよ」という話ですが、このようなアルゴリズムと計算機アーキテクチャとデバイス制御を手っ取り早くむずびつけて、今までなかったものを探索するのにFPGAはお手軽でとてもいいデバイスだと思うわけです。

みんなでやろうFPGAプログラミング!(笑)

おまけ

私の経験上、今あるアーキテクチャで出てる性能維持したまま、リアルタイム性だけ後付けしようとしてもほとんど成功例を見たことがありません。

リアルタイムアーキテクチャに思うところ

一方で、FPGA などで出来る超リアルタイムアルゴリズムを順に高度化していくのは案外前に進みますし、従来と全く異なる形でゴールにたどり着けたりもします。 ようするに What は同じでも How を変えないとゴールにたどり着けない気がするのですよね。 特にSOTAなAIは、「すごい精度出すHowを見つけたよ!」という論文ばかりなので、リアルタイム化を考えるときは、参考にはしても鵜呑みにしてはいけない気がしています。

一方で、視点を変えて、別の場所からゴールを目指すアプローチは、前人未踏感があって楽しいですよ。LUT-Net (BinaryBrain) もそうやって生まれました。