Ryuz's tech blog

FPGAなどの技術ブログ

パラメータごと合成するという事

はじめに

少し前に、X(Twitter) にこんな絵を張り付けた。 ちょうど BitNet(b1.58) が盛り上がっていて、パラメータ効率の議論が起こっていたときだと思う。

忘れる前にもう少し書いておこうと思う。

上の図では少なくとも DRAM などの外部メモリや、そこへ読み書きアクセスを行ってパラメータ入れ替えをする機構が要らなくなる分、お得であることは伝わると思う。 大量の演算器リソースと、それを埋め尽くす入力リソースが無いと成り立たないので GAFAM のような企業でないと難しいかもしれないが、確かにコスパのいい推論が出来るはずなのだ。

と言う話だったが、これには続きがある。

ここまでは LSI 開発をしてチップを作っても効果がある。が、FPGA だと更に パラメータごと合成する というメリットが発生するのだが、そこにはまだ十分触れられてなかった。

これがFPGAだとさらに嬉しい

これが FPGA の場合、さらに パラメータごと合成する メリットが出てくる。

どういうことか、ここでさらにちょっと具体的なパラメータを入れてみる。

やや作為的ではあるが、例えば 768 は 1024 - 256 であるし、 160 は 128 + 32 であるから、乗算が減算や加算に置き換わる。

もっと極端にパラメータが 2 のべき乗なら接続をずらす(シフト)だけだし、0 なら演算回路ごと消えてしまう。

これがパラメータごと合成するという事だ。 LSI化する場合、どうしても変更可能な要素としてパラメータを可変にするので、どんなパラメータが来ても良いように乗算器はフルスペックで用意するしかない。 しかし、FPGAなどのリコンフィギャラブルだと、パラメータが変るたびにパラメータごと演算器を再合成できるのがメリットだ。

LUT-Net の場合は?

ちなみに拙作の LUT-Net はその究極形だと考えている。

なにしろ、乗算ではなく、LUT演算といいうものを学習させて、そのままLUTのテーブルをパラメータとして、合成と言う工程すら無きに等しい状態にしていますので、これは究極の パラメータごと合成 なのだ。

利用できるシーンは限定される

一見、書き換え可能なFPGAだからこそできる超効率の道にも見えるが、当然凄まじい制約がある。

パラメータごと合成する以上、回路規模以上のパラメータ数が使えない のである。 これは実質的にかなり小さいネットワークに対してしか使えないことを意味する。

加えて、非常に高いデータレートのコアが出来てしまう為、高帯域の入力需要が無いと帯域を使いきれないのでコスパのメリットが出なくなる。

しかし逆に言えば、コスパのいいFPGAに合成して収まる程度のパラメータ数の DL(Deep Lerning)推論で、且つ、十分な入力データがあれば極めてコスパが良くなる。

例えば MNIST などは DL の入門として扱うような今となってはシンプルなものだが、ではルールベースで判別プログラムを書けと言われても容易ではない。

目視で簡単に見つかりそうなヒビとか傷検出みたいなものでも、ルールベースだと案外難しかったりするのだ。

そういった DLとしては簡単だが、ルールベースは困難 という分野で役に立つ。例えば超安価なFPGAに高帯域のビデオで信号流し込むだけで、欠陥検知してくれるなどだと嬉しいシーンなどはあるのではないだろうか?

おわりに

今回は、リコンフィギャラブル故にできるパラメータごとの合成の可能性という FPGA のメリットに着目してみた。

パラメータを増やすことこそ正義のLLM全盛期に、小パラメータネットの効率を言っても見向きもされない面はあるのかもしれないが、ではLLM分野で勝てるのかと言うと、資本力が無いとそもそも参戦すらできない。こういうスキマ分野を考えてみるのも悪くはないと思う。

利用シーンは限られるものの、特に筆者は RaspPI カメラを 1000fps 駆動して遊ぶような人間なので、安価な高帯域データをあしらうのは大好きである。

なかなか、ビジネスに結びついていないところではあるのですが、そういう観点も面白いと思う。

FPGAでのレジスタファイル

はじめに

だいぶ以前、最小限の RISC-V 命令だけ実装して遊んだときに下記のようなレジスタファイルを作りました。 後でいろいろパラメータ変えて計測しようと思いつつ完全に忘れていたので思い出したように実験です。

https://github.com/ryuz/jelly/blob/master/rtl/v2/jfive/jelly2_register_file.sv

下記が RISC-V 命令そのままの 書き込み1ポート/読み出し2ポートのレジスタファイルを構成したものです。

書き込み1ポート/読み込み2ポート 32bit×32

基本的には Xilinx の RAM32X1D を活用する方向で作成しており、書き込みと読み出しのアドレスが同じ場合に新しい値を読むようにするなどのロジックを加えています。基本的に LUTRAM がうまく活用できています。

これを拡張したら何が起こるのか備忘録程度に残しておきます。

いろいろ合成実験してみる

ポート数を増やしてみる

もしスーパースカラ実行などを行って IPC(Instructions per Cycle) を 1.0 以上にしたい場合、当然ポート数も増やさなければ複数命令を同時実行することが出来ません。

そこで順に増やしてみました。LUTRAM が使えなくなるので、ダイレクトに FF を消費するのだろうなと思いつつ実験です。

書き込み2ポート/読み出し4ポート

書き込み3ポート/読み出し6ポート

書き込み4ポート/読み出し8ポート

ものの見事にLUTやFF大量利用状態になってしまいました。下手すると増えた分で RISC-V コアがもう一個作れてしまいそうです(笑)。 レジスタは 32bit×32本のままで、ポートを増やすだけでどんどん肥大化していきます。もちろんここからレジスタ数や幅を増やすとその分増えてしまいますのでSIMD なんかやった日には大変なインパクトになりそうです。

bit幅を広げてみる

再びポート数を書き込み1、読み出し2に戻して今度はレジスタの幅を32bitから256bitに8倍に増やしてみました。

幅を256bitに広げてみる

こちらは単純に8倍に増えただけでした。これは SIMD 命令のようなものを実装する分にはそれほどインパクトが無いことを示しています。

Block-RAMを使ってレジスタ数を増やしてみる

Block-RAM の WRITE_FIRST モードを使って 32本だったレジスタを 1024本まで増やしてみます。

レジスタ個数を1024本に拡張

こちらはメモリの構造上、書き込み1ポートが上限になりますが、ほぼ Block-RAM の機能だけで実現できています。 Block-RAM の貴重さ具合は状況によるとしか言えないかと思いますが、なかなか有効そうです。

レジスタ数を増やすと SMT(Simultaneous MultiThreading) のようなことをやったり、レジスタリネーミングを行ったりするのには有効そうです。

おわりに

プロセッサを作るような場合レジスタファイルはしばし重要なポジションにありますが、ことFPGAにおいてはFPGAのもともと持っている機能にうまくはめてやることは重要そうです。 特に書き込みポートを2つ以上にしようとするとほぼすべてのレジスタbitを LUTにする必要が出てきてインパクトが大きそうです(まあ、初めからわかってたことですが)。

複数命令同時実行をする場合も、汎用レジスタ浮動小数点用レジスタを分けるような、専用レジスタ化するような方法を模索して、ポート数の少ないレジスタファイルを複数うまく組み合わせるよう事を考えていくのがよさそうには思いました。

Wikipedia

少数の読み出しポートを持った複数のレジスタファイルのほうが、全ての読み出しポートを備えたレジスタファイルより小さくかつ/または高速であると考えられるようになった。

という記述があったが、FPGAに限らずその通りなのではないかと思います。

追記

FPGA開発日記 の msyksphinz 様より、FPGA固有のこんなテクニックを教えて頂きました!(感謝)

いろいろ目から鱗でしたが、ちゃんと調べるべきでした。 ただしレジスタ容量が増えるわけではなく、ポート数をn倍に増やすのにリソースがn倍以上必要なのはその通りなので、もっと良い方法があるならそちらが良いのだとは思います。とはいえ、FPGAでもマルチポートのレジスタファイルをそこそこリーズナブルに検証できるというのはとても有難い技法に思いました。

パラメータ定数ごと乗算を合成する場合の考察

はじめに

毎度おなじみ当方の LUT-Netは、ネットワークのパラメータをLUTのテーブル値として学習して回路にしてしまう為、外部SDRAMなどのメモリからパラメータをロードしながら演算する必要がありません。

今回はLUT-Net は置いておいて、「もし普通の積和演算を使うパーセプトロンモデルのパラメータも、演算時ロードではなく静的に固定パラメータとして合成したらどうなるか?」というのを考えてみました。

この場合、パラメータが固定されますので演算器の再利用は出来ませんが、乗算の係数が固定されているので乗算器を使わずに回路をシフトや可算で簡略化できるケースが出てきます。

例えば、入力値 x を 5 倍する場合、x * 5 を (x << 2) + x に分解すれば加算器一個で実現できます。

また、bit 数が少ない場合、演算結果を予めテーブル化しておくことも可能です。今回は主にテーブル化をメインに考えてみたいと思います。

BF16形式を眺めてみる

最近の深層学習でよく使われる形式の一例としてこちらにある BF16 形式を引用させてもらいます。

BF16

これはFP32形式の下位16bit を切り捨てたような大胆な形式ではありますが、なんと仮数部が7bitしかありません。

浮動小数点形式の乗算では、NaN や INF などの例外をおいておくと、符号部は XOR、指数部は可算、仮数部のみ乗算を行えばよいです。 この時、一番回路が大きくなるのが乗算部分で特にFP32などではDSPを利用するのがほぼ必須でしょう。ところが BF16のような小さな bit 幅で、かつ乗算の片方が固定係数の場合は話が変ってきそうです。 7bit であれば入力パターンは 128種類しかありませんので、128ワードの7bit ROM をテーブル引きすれば終わることになります。

ここで、Xilinx の 6入力 LUT は、64bit のメモリで構成されており、LUTRAM として使う事が出来るタイプが一定量組み込まれています。

つまり、LUTを2個使って 128bit のメモリが構成できれば、それを 7並列にすれば固定定数との乗算は実現できる可能性があります。

さっそく 7bit 128word のメモリを RTL 記述して合成してみます。

RAMの回路は既に昔書いたものがこちらにあるので、

    ADDR_WIDTH = 7;
    DATA_WIDTH  = 7;
    MODE = "READ_FIRST"

として、さらに en も 1 に固定して合成してみます。

結果は下記です。

LUTRAMで 7bit 128word のメモリを合成

LUT が 16 個使われたのみで、そのうち 14 個は LUTRAM として使われています(むしろ普通のLUT 2個が謎です)。

該当箇所は下記のようになっていました。

かなり思惑通りです。

ついでに、さらに精度を 1bit 捨てて 6bit の場合どうなるか、6bit 164word を試したところ下記のようになりました。

LUTRAMで 6bit 64word のメモリを合成

もはや入力が6bit な時点で、6入力LUTを単に置くだけと言うことになりますね。

まとめ

演算器を構成するのではなくROMを引くことで簡略化するような技法は逆数を求めるなどいろいろなシーンで利用できます。

BRAMを使う手もありますが、特に精度が低くていい場合はLUTを ROM にしてしまうのはなかなか効果的に思います。 特に Deep Learning の場合、学習済みのパラメータは推論時には変化しない固定定数となってしまうので、時として一緒に合成してしまうのは悪くない手に思えます。 リッチな汎用乗算器である DSP を温存して、一部でLUTを使った専用回路にしてしまうのも面白い使い方に思いました。

もっとも浮動小数点の場合は、乗算の後の加算で桁数を合わせるためのバレルシフタが大きかったりするのですけどね(汗)。

とはいえ、INT8 や INT4 なども Deep Learning には利用されているので、INT6 なんて用意すると FPGA 向けには効率が良かったりするかもしれません。 Deep Learning でも 非線形表現を用いてパラメータを圧縮する論文等はいくつかあったかと思いますが、特にテーブル化する場合、数値表現がリニアである必要はないのでFPGA向けに6bitの特殊な表現形式を定義するのも面白いかもしれません。

追記

UG574を見ると、CLB内の複数のLUTを繋げて、大きなマルチプレクサやシフトレジスタを作るためのセレクタがついていることが分かり、当然LUTRAMの結合にも使えるので下記のようになるようだ。 LUTはDSPやBRAMより配置自由度が高く、単独での速度も速いのでうまくテーブルとして使うと効果的なシーンは沢山ありそうだ。

SIMD/SIMTとMIMDと

はじめに

先日Rustのイテレータでの処理順序関連してこんな記事を書きました。

加えて最近少し Elixir を調べていて、Enum, Stream, Flow などの処理を興味深く見ております。

そこで、ALU(arithmetic Logic Unit)視点から見た2種類の計算機のデータ処理パターンを再考しておきたいと思います。 今回、少し大胆ですが、SIMD(Single Instruction Multiple Data)とSIMT(Single Instruction Multiple Thread)は複数のALUが同時に同じ演算を実装するという点で同じカテゴリとし、別々の計算を行うものとしてMIMD(Multiple Instruction Multiple Data)を置いておきます。

SIMD/SIMT は複数のALUに対して命令デコーダが共通ですので少ないハードウェアリソースで多くの演算が行えるのが特徴で、逆にMIMDは複数のALUに対して個別の命令デコーダがあるのでハードウェアリソースを多く利用してしまう反面自由度が高いのが特徴です。

考察

2つのモデルを置いてみる

簡単のため、次の2つを考えます。

  • 4並列のSIMD演算器を備えた1コアのプロセッサ(SIMD)
  • 1命令しか実行できないコアが4つあるマルチコアのプロセッサ(MIMD)

この時どちらも ALU は4個あります。

ここでそれぞれに例えば画像なり行列なり何でもいいのですが 1024x1024 程度の2次元のデータを処理させることを考えてみます。

それぞれ出力するデータに対応する計算の割り当てとして、計算効率を出そうとすると

  • SIMD/SIMT的なデータの割り当て
    • ALU0 : データ0, 4, 8, 12...
    • ALU1 : データ1, 5, 9, 13...
    • ALU2 : データ2, 6, 10, 14...
    • ALU3 : データ3, 7, 11, 15...
  • MIMD的なデータの割り当て
    • ALU0 : データ0, 1, 2, 3, 4...
    • ALU1 : データ1024, 1025, 1026, 1027...
    • ALU2 : データ2048, 2049, 2050, 2051...
    • ALU3 : データ3072, 3073, 3074, 3075...

のようなイメージになりがちです。 前者は SIMD命令を使ったプログラミングやCUDAなどのプログラミングでやりがちな記述であり、後者はマルチプロセッサ環境で OpenMP などで #pragma omp parallel for などを使った際にありがちなパターンかと思います。

SIMD/SIMTに対する考察

SIMD/SIMTはご承知の通り連続するアドレスに対するロードストアが得意です。外部の SDRAM にとっても連続したアドレッシングしか発生しませんので、システム全体として見てもなかなか効率が良いです。

もしSIMD計算機にMIMD的なアドレッシングをさせると、途端に性能が落ちます。例えば「画像を回転させる」ようなタスクの場合、load 時のアドレスは4つバラバラになりますが、一つでもキャッシュミスするとデータを4個揃えて演算するために4つともの演算器がストールしてしまいます。

MIMDに対する考察

こちらは独立したコアが4つあるわけなので、なるべくお互いが干渉しない等に個別に演算を進めるのが理想的となるため、後者のようなメモリ割り当てとなっています。もしSIMDのような演算の割り当てをしてしまうと、各コアが保有しているL1キャッシュに使わないデータまで取り込むことになってしまい性能が出ません。 また、SIMDで効率が良いアクセスパターンは、外部 SDRAM にとってはややランダムなアクセスになるための若干の不利が生じます。

一方で、先に上げた画像を回転させるようなタスクでは、各コアがお互いを待ち合わせることなく担当個所を全力で処理できるため効率が良いことになってきます。

そして何よりMIMDの場合、あるコアはExcel、あるコアは Chrome、あるコアは音楽再生、のように、全く違うアプリケーションが実行できる利点があります。サーバーとして多くのユーザーのリクエストを非同期に処理するなどにも向いています。

実際には

実際には多くのケースで SIMD命令を備えたコアが複数ある というプロセッサを使うことが多くなってるのではないかと思います。 この時、両者のハイブリッド的なアクセスとなるように工夫しながらプログラムを書くわけですが、両者の特性を知っておくことはとても大事ですね。

ここでFPGAを考えてみる

FPGA の場合、たとえば乗算器のような、ALUを構成するのが便利なハードマクロが多数内蔵されていることが多いいです。この多数用意された並列演算器に、DMA などを構成してメモリや外部デバイスなどからデータを送り込んで演算させるというのが基本ですが、この時 SIMD的なアドレッシングもMIMD的なアドレッシングも どちらも出来てしまう というのがFPGAの楽しいところです。

普通にプロセッサを買ってきてプログラミングをする場合、自分の使っているプロセッサで性能が出るように書くしかないわけですが、FPGAの場合どのALUにどのように処理を割り当てるかの自由度がかなりプログラマ側にあります。加えて周波数は低めですので、例えば 1G pixel/sec の帯域で画像処理するような場合嫌でも並列演算することになります。

多くのケースではSIMD的に並列にするのですが、稀に画像にアフィン変換を掛けるような用途だと部分的に MIMD 的にしたりすることもあります。その際のメモリ効率もアクセスパターンやキャッシュの量や仕組みなど工夫の余地は多いように思います。

終わりに

計算機のデータプロセッシングとしての利用側面を考えた場合、沢山の演算器を並べてそこに効率よくデータを流し込む機構を考える ことに他ならないのですが、市販プロセッサでは「SIMDをMIMDにして使う」などと言う自由度はソフトウェアプログラマにはありません。一方でFPGAにはその余地がソフトウェアプログラマに解放されている点でとても面白いと思うわけです。

フルロジックEther通信とボード間時計合わせ

はじめに

以前、ZynqMPでリアルタイムOSをFPGA化してみた というRTOSのスケジューリング部分をフルロジックで実装してみましたという記事を書きました。

今度は、それを複数ボード拡張することを目論んで、RTOSらしくリアルタイム保証や時計合わせを行うべく WAVESHAREのLAN8720 の互換のボードを買いあさって ZYBO に繋げて遊んでおります。いろいろと Twitter に呟いていますが、情報が拡散してきたので一度ブログにまとめておきます。

KV260 でやりたっかたのですがPMODが足りないので、暫定で 久々の ZYBO です。幸い2枚持ってましたし。

最終的な狙いどころとして、今度は通信部分もフルロジックで実装することで、

  • HPCみたいに各ノード間でマルチホップなメッセージ交換して MPI みたいなことできるようにしたい
  • HPCみたいに別ノードのメモリ空間に相互乗り入れさせたい(ccNUMA的な?)
  • RTOSらしく時間保証したい/ノード間で時計を揃えたい
  • EtherCAT みたいなリアルタイムの通信もしたい

そもそもリアルタイム保証出来ていたら計算も同期計算できるので、非同期システムと違ってハンドシェーク処理が不要になるのではないか? というのもあります。

  1. 各ボードで同じ時間にタスクが起床される
  2. 各ボードで最悪実行時間を保証した処理が終わる
  3. 時間保証されたタイムスロットで計算結果交換

という流れが容易にできそうな気がします。

これはノード間の通信遅延以下の精度で時計が揃えば基本的に成立するはずです。

加えて、プログラマブルロジック(FPGA)らしく、外部のセンサーやアクチュエータなど、入出力デバイスとのやり取りのタイミングもボード間で揃えることができるはずです。

Twitter で冗談で書いたが、ナノ秒オーダーの精度が出てくれば、LiDAR 等のように光の遅延で自己位置推定などができる可能性さえもあるかもしれません。

余談ですが、同期/非同期については、例えばロジック設計であればこの違いは明確で、同期処理では全員が処理をクロック立ち上がりに揃えることでハンドシェークが不要です。

もう少し大きな粒度のプロトコルになってくると、例えばTCP通信などは、データが届いたか? 次のデータを送って良いか? などをお互いでハンドシェークしながら通信しています。 計算タイミングの点では HPC でのクラスタ演算がスループットを出す為に演算のタイミングをある程度揃えていたはずです、一方でエッジでは、例えばROSなんかは各ノードが自分のタイミングで計算して publish できますので、非同期メッセージ通信の代表格の一つかもしれません。リアルタイムに近いエッジ分野のROSが非同期式で、スループットさえ出ればよいHPCが同期式というのもなんだか不思議な感じです。

構想

とりあえず、後半のリアルタイム保証の枠組みを先にしっかり整えた方がよさそうなのでそこから始めました。

基本的にはリング接続か直線的な接続を考えています。 リングを前提にしたいのですが KV260 が PMOD 一個しかないので仕方がないので、両対応で実装を進めています。

イデアとしては基本的には、毎周期のタイムスロット内で必ず行うリアルタイム通信と、一定頻度で入れる時計合わせ通信、残帯域でのノンリアルタイム通信用を考えています。

非リアルタイムスロットは、ある程度の頻度で時計合わせプロトコルが奪うので、時間保証されない感じになります。

タイムスロットの考え方

接続方法としては、リングと直線の2種で、それぞれリアルタイムスロットではダイレクトにパケットを一周させ(詳細仕様しりませんが EtherCAT と同じ?)ようなことをして、ノンリアルタイムスロットでは宛先付きのパケットをお隣同士で交換しながらマルチホップな通信を考えています(経路が空いていればダイレクトに送っても良い)。

接続形態

時計合わせは、基本的に往復の伝搬時間を測ってその半分を補正してやればよいはずです。

ちなみにこの辺のアイデアはすでに世の中にあるようで、 TSN(Time-Sensitive Networking) とか PTP(Precision Time Protocol) とかがそうであるようです。

そしてノード同士を接続する規格とかは CCIX とか CXL とか、これまたいっぱいあるわけですが、これらもまたリアルタイム系の規格とは別物です。

例によっていろいろと車輪の発明なのですが、これらのオープンでない規格をわざわざ調べる元気もないですし、やりたいことが全部やれないと意味が無いので、独自仕様で進めます。

今回単にFPGA同士を繋ぐ、ある程度安くて高速な回線が欲しかっただけなので、Ether が便利だったという以上でも以下でもないですし、他のシステムと繋がない限りは特になにかの標準に準拠する必要もないのかなと思います。

時計合わせとリアルタイム通信まで作ってみた

という事で、ノード間の時計合わせと、リアルタイム通信まで作ってみました。

youtu.be

時計が揃っているので、パルス幅情報をリアルタイムパケットで交換して、同じ時刻でパルスを出せば、同じ時刻に同じ幅の、つまり同じ形の波形が2つのボードから出るわけです。

Ethert のボード間通信は測ってみると 0.5us (上のお城の 2.5目盛分)ぐらいあるにもかかわらず、ちゃんと通信遅延はキャンセルして時刻が揃っています。

ぱっと見た感じ、数十ns程度の誤差範囲には収まっていそうです。 ボード間通信遅延より十分短い誤差量で時計合わせが出来ました。

おわりに

RTOSや通信をナノ秒単位の制御ができるRTLでのフルロジックで書いてしまい、高度な浮動小数点演算などはRPU(Cortex-R5) などでシンプルに書くというのは悪くないアプローチな気がしています。

この手のサイクル単位を扱うものは HLS(高位合成言語) よりも RTL 言語が向いていると思いますので、RTL学習の動機づけとなる題材としては悪くないのではないかと思いました。

RTL記述におけるX(不定値)の扱い

はじめに

Verilog などの RTL 記述言語の多くは、文法上 0/1 以外に X(不定値) や Z(high-Z) を含めた4値を扱えます。

不定値の取り扱いにはおそらく「これが正解」といった絶対的なものはなく、ケースバイケースな気がしております。

コーディングルールを考えるときの悩みの一つではあったりするので、少し整理してみたいと思います。

シミュレーションにおける不定

例えば、AXI バスの valid 信号が 0 の時、他の信号線は仕様上は don't care です。

このとき他の信号に X が入っていれば、波形ビューワ―上での don't care 区間はビジュアル的に把握できますし、「うっかり valid が下がった後のタイミングでラッチしてしまうコード」を書いてしまっても、不定値が伝搬していきますのですぐに気が付くことができます。

なので、積極的に 不定値X を埋めるコードを書いておけば、検証がやりやすくなる面はあるかと思います。

一方で、

if ( a ) begin
    //  何か処理 
end

のようなときに、a に x が入っていても、単に 偽 になるだけなので 0 が入っているのと同じ動作をしてしまい、テストを通過してバグを取りこぼすなんてこともあります。

ここはシミュレータにもいくつか機能があって、例えば Verilator などは内部的に2値しか使わない関係もあり、不定値を乱数にしたり固定値にしてくれます。 もちろん乱数は乱数ですので偶然テストが通ることはあるのですが、バグの早期発見の確率を上げてくれる要素ではあります。

なので私はよく 4値の扱える Vivado Simulator(xsim) と 2値でシミュレーションする Verilator の両方でテストを流したりもします。そうすると片方では通るのに片方で通らないというようなことが割と発生し、ラピッドプロトタイピングで「まずは大雑把にデバッグして動くようにしてしまいたい」時などに重宝しています。

論理合成時における不定

論理合成時に不定値を含めるのはまずそのあり方から賛否がありあるかと思います。不定値を含めると当然出てきたネットリストの不定値部分の動作はシンセサイザ次第となってしまいます。これは後工程で出力されたネットリストを取り扱いにくくなることを意味します。 私はそもそもFPGA開発しかやったことないですが、 LSI開発されてる方々だと例えば、後からネットリストレベルでバグ修正して ECO(Engineering Change Order) を出したりされることもあるそうですので、その修正が RTL に戻せなくなったりとか、いろいろと課題がありそうです。

一方で、FPGA 開発なんかの場合、「めんどうなことはシンセサイザに任せてしまえ」というのはありかと思います。

人間が手動で論理圧縮をする場合、例えばカルノー図なんかを書いてみて、論理がシンプルになるようにするには X 部分を 1 に倒すのがいいか、0に倒すのがいいか検討するようなこともできます。

とはいえ、Xのままシンセサイザに合成してもらい、自動での論理圧縮に丸投げするのも、FPGAのように簡単にリコンパイルできる世界だと全然ありな気がします。

シンセサイザは X をどう扱っても良いわけですから、0 にする /1 にする/値保持する、などの中から一番都合のいいものを探す努力をしてくれます。

ちなみに私はよく

always_ff @(posedge aclk) begin
  if ( ~aresetn ) begin
     axi4s_tdata <= 'x;
     axi4s_tvalid <= 1'b0;
  else if ( aclken ) begin
     axi4s_tdata <= ほげほげ;
     axi4s_tvalid <= ふがふが;
  end
end

のようなコードを書きます(賛否あるのですが)。

FPGAのシンセサイザは X を如何様に扱っても合法ですので、多くの場合もっとも論理圧縮の効く「リセットしない」に合成してくれます。

この書き方のメリットは tdata と tvalid のようなまとまったものをセットで書けるという点です。

もちろんお堅い LSI 開発だと、 「always文1つに変数一個まで」とか、そもそも「 always文自体禁止」とかのコーディングルールも聞きますので、FPGAでのラピッドプロト以外では許されないかなり荒っぽい書き方なのかもしれません。

このように、不定値の扱いはなかなか賛否両論ありそうに思う分野です。

それはバグなのか?

ここで、例として最初に書いた「AXIバスで、うっかり valid が下がった後のタイミングでdataをラッチしてしまうコード」を書いて商品を販売してしまった場合を考えてみます。

ですが、ここでたまたま「valid が下がっても data はキープされる実装になっていたのですべての場合で正常に動作する」ことが判明したとしましょう。

これは少なくとも製品をリコールする必要はないかと思います。

そういう意味でこれをバグと呼ぶかどうかの議論もあると思いますし、不定値をフェールセーフな値に倒せないかの議論にもなります。

フェールセーフな書き方

そしてここまでの話は don't care 部分(不定値)の扱いを

  • 論理圧縮が効くように扱うべきなのか
  • フェールセーフな方向に定義するべきなのか

という議論にもつながると思います。

手元にある定本 ASICの論理回路設計のジョンソンカウンタでマイナーループからの脱出について技法が書かれていますが、ソフトエラー(宇宙線でのbit化け)などのバグ以外の要因も含めて、万一よからぬ状態になった場合に復帰するように不定値部分をあえて手動で定義することは有効なケースは大いにありそうな気がします。

この際、何が安全かはシンセサイザは知る由がありませんので、プログラマが明示する必要が出てきます。この点でもまた高品質なLSIが作りたければ「合成記述に不定値は使うべきではない」という話の合理性にも繋がってきます。

(FPGAの場合、ダウンロードしている回路自体にソフトエラーが起こりうるので、こういう設計をする効果が如何ほどあるかは 謎ですが)。

終わりに

取り扱っているものの特性や、立ち位置によって、不定値にどう立ち向かうべきか千差万別な気がしております。

ラピッドプロトタイピングでは自動化する部分を最大化して早く安く検証を行いたいでしょうし、ASIC開発などでは長い製品ライフタイムの中でトータルコストを落としたければ設計時に低レイヤまで手動で管理した方が良いということになるかと思います。

私はFPGAなラピッドプロトがメインですが、それ前提にあまり乱暴なコード例ばかりを書いていると、「変な事を広めるな」と、マサカリが飛んできそうな気もしますし、悩ましいところです。

皆さまは不定値ってどう使われていますか? 私はかなり我流でやっているので、世間ではどういう取り扱いがなされているのかとても興味があります。 よろしければコメントお寄せください

FPGA開発セミナー(2023/01/25)で発表いたしました

下記、「実践的!FPGA開発セミナー vol.18」にて Lightning Talk 枠で発表させて頂きました。 fixstars.connpass.com

発表資料を以下に置いております。 speakerdeck.com

なお、今回を機に、以前 SlideShare に置いていたものを一部 SpeakerDeck にも置きましたので、よろしければご活用ください。 speakerdeck.com