カテゴリー「FPGA」の42件の記事

2017年6月24日 (土)

LLVM

今更ですが LLVM を調べ中です。

以前、自作のシェーダー作りたいなぁ、的なことを書きましたが、コンパイラどうしようかというのが一番の悩みでした。

なんとなくな勉強はしつつもコンパイラは、作業量自体が多そうで手を出すのに二の足を踏んでいました。
それに比べて、LLVM だとかなり整理された中間言語体系からバックエンドだけ考えれば済みそうです。

拙作のJelly(コンパイラが作れないのでMIPS互換)の経験上、FPGAのプロセッサの速度を上げる弊害が幾つかあり

  ・ データ依存のインターロック(分岐ミスとかキャッシュミスとかいろいろ)
  ・ RAWハザードのような、演算器の完了待ち
  ・ フォワーディングのような1サイクルで回さないと意味が薄い部分の判定

  など、思いつくものがいろいろあります。
  が、これらはリング保護のような仕組みを考えないDSP的なものであれば、その気になればコンパイラに追い出すことも十分可能なものが多いです。
 そしてそれらをコンパイラに追い出すのを一番困難にしているのが、命令互換性の問題です。
  パイプライン段数が変わった瞬間から、互換性がなくなってしまいます。

  一方で、GPUなどのシェーダープログラムのように、リング0のデバイスドライバでコンパイルして使うことを前提としたアーキテクチャでは、そのほぼすべてをセキュアなまま隠蔽することが出来るので、プロセッサとコンパイラの役割分担を自由に弄れるはずです。

 本当に作れるかは別として、コンパイラ分野が非常に進化してきているのは、夢が広がりますね。

2016年12月24日 (土)

エンコーダ回路

ISE14.7でZybo設定(XCZ010-1CLG400) でエンコーダを作る実験をして見ました。

【パターン1】
まず同時に1つしかビットが立たない前提で、よくある悪いか書き方で

[ソース]
case (data)
8'b0000_0001:   sel <= 0;
8'b0000_0010:   sel <= 1;
8'b0000_0100:   sel <= 2;
8'b0000_1000:   sel <= 3;
8'b0001_0000:   sel <= 4;
8'b0010_0000:   sel <= 5;
8'b0100_0000:   sel <= 6;
8'b1000_0000:   sel <= 7;
endcase

[結果]
Number of Slice Registers : 3
Number of LUTs            : 13


【パターン2】
これに(* full_case *)をつけたり、下記のようにdefaultをつけると

[ソース]
case (data)
8'b0000_0001:   sel <= 0;
8'b0000_0010:   sel <= 1;
8'b0000_0100:   sel <= 2;
8'b0000_1000:   sel <= 3;
8'b0001_0000:   sel <= 4;
8'b0010_0000:   sel <= 5;
8'b0100_0000:   sel <= 6;
8'b1000_0000:   sel <= 7;
default:        sel <= {SEL_WIDTH{1'bx}};
endcase

[結果]
Number of Slice Registers : 0
Number of LUTs            : 6

当然LUTが減ります。

【パターン3】
が、よく考えたらこれでいいのではないかと

[ソース]
casex (data)
8'bxxxx_xxx1:   sel <= 0;
8'bxxxx_xx1x:   sel <= 1;
8'bxxxx_x1xx:   sel <= 2;
8'bxxxx_1xxx:   sel <= 3;
8'bxxx1_xxxx:   sel <= 4;
8'bxx1x_xxxx:   sel <= 5;
8'bx1xx_xxxx:   sel <= 6;
8'b1xxx_xxxx:   sel <= 7;
default:        sel <= {SEL_WIDTH{1'bx}};
endcase

[結果]
Number of Slice Registers : 0
Number of LUTs            : 4


【パターン4】
ここで機能追加してプライオリティーエンコーダにしてみます。

[ソース]
casex (data)
8'b0000_0001:   sel <= 0;
8'b0000_001x:   sel <= 1;
8'b0000_01xx:   sel <= 2;
8'b0000_1xxx:   sel <= 3;
8'b0001_xxxx:   sel <= 4;
8'b001x_xxxx:   sel <= 5;
8'b01xx_xxxx:   sel <= 6;
8'b1xxx_xxxx:   sel <= 7;
default:        sel <= {SEL_WIDTH{1'bx}};
endcase

[結果]
Number of Slice Registers : 0
Number of LUTs            : 4

プライオリティーエンコーダにしてもロジックが変わりませんでした。


【パターン5】

ここでパターン3をもっと汎用的に for文で書いて見ます。
[ソース]
sel <= {SEL_WIDTH{1'bx}};
for ( i = 0; i < DATA_WIDTH; i = i+1 ) begin
    reg_compare    = {DATA_WIDTH{1'bx}};
    reg_compare[i] = 1'b1;
    if ( data == reg_compare ) begin
        sel <= i;
    end
end

[結果]
Number of Slice Registers : 0
Number of LUTs            : 0
Number of Block RAM/ROM   : 1

なんとメモリテーブルが生成されました!(爆死)


【パターン6】

これがプライオリティーエンコーダだと

[ソース]
sel <= {SEL_WIDTH{1'bx}};
for ( i = 0; i < DATA_WIDTH; i = i+1 ) begin
    if ( data[i] ) begin
        sel <= i;
    end
end

[結果]
Number of Slice Registers : 0
Number of LUTs            : 4

めでたしめでたし。


うーん、まさかBRAM使ったROMが出来るとは思わなかった。
casex って if 相当では書き直せないのだろうか?

ついでに言うと、プライオリティーの有無でサイズが無いのも実験として不十分なので、16bit幅に増やして追試すると

プライオリティー無し:LUTs : 10
プライオリティーあり:LUTs : 11

となっった。

やはり、プライオリティーの有無はちゃんと回路規模に影響するらしい(良かった)。

とはいえ、プライオリティー無しエンコーダは汎用的にシンプルにかくのはどうすればいいのだろうか?

各bitの立つ条件を個別に記述する手はもちろんあるが、何かもっといい手がありそうな気もする。

2016年11月28日 (月)

バイリニア補間するユニット

  バイリニア補間するユニットを作って、以前作成したテクスチャキャッシュと統合して2Dサンプラーユニットを作って見ました。
  各バイリニアのユニットは、4サイクルかけて1ピクセル処理できるようにしており、それを8並列で結合して合成して見ました。
  xc7z010-1clg400 の ISE14.7での合成で下記のとおりです。
  (Vivadoだと合成段階では周波数出してくれないので)

  Number of Slice Registers    : 8,371 (23%)
  Number of Slice LUTs         : 5,343 (30%)
  Number of Block RAM/FIFO : 36 (60%)
  Number of DSP48E1s          : 33(41%)

  Maximum Frequency: 404.040MHz

  キャッシュミスしなければ、4並列で1サイクル1ピクセルなわけですが、実際には8並列でも、キャッシュミス区間は埋めきれない模様です。
  規模的には16並列とかでも収まりはしそうですが、そこまでしなくてもVGA程度ならリアルタイムでいけそうな感じです。とりあえずシミュレーション上では、0度,45度,90度の回転で、SDRAMアクセスは殆どのアドレスで二度読みは起こらなかったので、タグアドレスの割り振りはまあまあうまく機能しているようです。

  下記は45度回転させながらのシミュレーション出力です。ニアレストネイバーのときよりもだいぶギザギザがなくなりました。

Bilinear

 

   上は、テストベンチとして書いた、アフィン変換のアドレス生成によるものですが、本来は2Dサンプラーですので、ピクセルシェーダ的なものを上に載せたいわけです。

  さて、なかなかシミュレータの世界から出れていないのですが、実機で動く日はいつになるやら。
  固定パイプラインシェーダーでよいので、何かしら動かしたいなぁ。

2016年10月29日 (土)

除算の検討(回復法と非回復法)

引き続きなぜか除算に迷走しています(苦笑)。
除算にはいろいろなやり方がありますが、今回はシェーダーとかのプログラマブルロジックではなく、固定グラフィックスパイプライン内のパースペクティブ補正に組み込むことを前提にスループット1の整数除算器を考えてみます。

除算器の種類とかはwikipediaに書かれているレベルでもいろいろ存在するようです。

CPUなどに内蔵される除算器は、そもそもグラフィックスのように除算のみが大量に押し寄せることも稀なので、スループット1未満のマルチサイクル除算器の構成が多く、SRT法など(Pentiumの有名な除算バグのアレです)が高速化に効果的なようです。ただしSRT法でスループット1でパイプラインを組むと各ステージにテーブルが必要になってしまい規模的に現実的ではないと思われます。

となると、地道に減算系のパイプラインを組むしかないので、回復法と、非回復法を検討して見ました。グラフィックスパイプラインなのでレイテンシは一旦気にせずにロジックスライス内のFFの使用率を上げて周波数を稼ぐとすると、気になるのはどうコンパクトに収まるかになってきます。

各ステージでの計算としての大きな違いは、

回復法)
  減算してみて負なら元に戻す
  つまり減算器の後ろにセレクタ
  符号の扱いは別途ケアが必要

非回復法)
  非除数と除数の符号を見て、加算と減算を切り替える
  つまり加算器手前の符号の反転/非反転をセレクト
  パイプラインの最後に正規化が必要

の違いなので、1ステージの計算が規模的にあまり変わらない可能性もあり、それなら最後の正規化の無い回復法もありかなとか考えていました(周波数に極端な差が出ないない前提で)。

なので先立って、1ステージ分の計算回路の実験です。

// 回復法の1ステージ分
module div_test_restoring(
            input   wire                    reset,
            input   wire                    clk,
            input   wire                    cke,

            input   wire            [31:0]  a,
            input   wire            [31:0]  b,

            output  reg             [31:0]  c
        );

    wire     [31:0]  t = a - b;

    always @(posedge clk) begin
        if ( reset ) begin
            c <= 0;
        end
        else if ( cke ) begin
            c <= t[31] ? a : t;
        end
    end

endmodule

// 非回復法の1ステージ分
module div_test_non_restoring(
            input   wire                    reset,
            input   wire                    clk,
            input   wire                    cke,

            input   wire    signed  [31:0]  a,
            input   wire    signed  [31:0]  b,

            output  reg     signed  [31:0]  c
        );

    always @(posedge clk) begin
        if ( reset ) begin
            c <= 0;
        end
        else if ( cke ) begin
            c <= (a[31] == b[31]) ? a - b : a + b;
        end
    end

endmodule

XC7Z010向けのISE14.7での合成結果、

 回復法    : LUT 64個消費
  非回復法 : LUT 32個消費

という結果です。2倍の差ですね。

XILINXのCLBは LUT とFFの間に、加算器のためのキャリーチェーンがあるので、加算前に細工を施す非回復法はLUTにロジックを閉じ込められるようですが、加算器の後に処理の入る回復法だともう一つ別のLUTが必要になるようですね。

これは非回復法での設計が正解なようです。

<蛇足>
  「Xilinx のIPコア使えばいいのではないか?」という声も聞こえてきそうですが、GUIで生成するIPコアには幾つか欠点もあります。

  • parameter などでビット幅など変える汎用的なモジュールが作り難い
  • IPコアがいるモジュールはOOC(Out of Context)出来ない
  • veritakなど外部のシミュレータが使いにくくなる
  • AXI4S系のI/Fの場合、内部ブラックボックスなので、パイプライン数やサイクル数を仮定したコードがかけない(valid/readyでの待ち合わせが必須)

など、細かな課題があったりもします。

 

2016年9月24日 (土)

XILINXのDSP推論

 普段RTLで演算を書くときは、合成器の推論に任せっぱなしだったのですが、前回の延長で、4サイクルでバイリニアの積和を考えている途中で、ちょっと気になることがあって、少し実験をしてみました。
 実際、多くのケースでDSP48E1をかなりうまく推論してくれるようですが、どうもアキミュレータへの足し込みが入ると推論をミスするケースがあるように思えます。
  RTLで書いたり、DSP48E1プリミティブを直接呼んだりで実験したところ

  DSP48E1プリミティブを直接呼ぶと

 

Dsp_dsp

と、当然DSP1こになるところ、OPMODEに関連する部分を使う等価RTLを書いてみると

Dsp_rtl_3

と、積和部分がCLBを使ったロジックで構成されてしまうようです(書き方が悪いのかもしれませんが)。

 もちろん ADDMACC_MACRO とか MACC_MACRO とかを使えば解決できるわけですが、この辺のマクロは内部パイプラインがブラックボックスになっており、イマイチ面白みがありません。

 そこで DSP48E1プリミティブを直接呼ぶ事も踏まえていろいろ考えてみました。

 ついでにこのあたりでいろいろ気が付き始めたのですが、GPU等でよく使う行列演算の中で、4x4 とか 3x3の行列演算が多数あって

  a*x + b*y + c*z + d

  的な計算をするわけですが、固定小数点に限定すれば DSP48E1 の PCOUT と PCIN をうまく繋いでカスケードすれば、殆ど CLB を消費せずに書けることに気が付きました。

 ひとつだけ厄介な問題があって、DSPをカスケードして後段の加算器を乗算器とセットで効率よく使うには、乗算のステージをDSP毎にずらしてやる必要があるようです。

 で、この辺は UG479の付録Aとか読んでいただければ想像つくかと思います。

 で、a*x + b*y + c*z + d な演算をDSP48E1を直接呼んで、5段パイプラインの演算を書いてみると、非常にコンパクトで高速なモジュールが出来ました。

 で、同じ動きをする演算をちゃんと乗算ステージをずらしながらRTLで書き直してみると、まったく同じネットリストが出てきました。ISEやVivadoの推論なかなか大したものです。

   汎用性をひとまず置いておくと、DSPへのマッピングを狙ったコードを書くのは結構有効な気がします。

 ちなみに DSP48 の乗算器は 18x25 です。単精度浮動小数点の仮数部は暗黙の1と符号を入れると25bitです。

   GPUだとしばしば、座標値(整数)×浮動小数点という演算が発生しますが、座標値はバイリニアなどのためのサブピクセル入れても18bitに十分収まるるはずです。
 DSPの構造を意識して書けばかなり効率的な書きかたが出来そうな気もします(もっと言えば、そもそも仮数部も18bitで足りる気もします)。

 DSPの乗算器と加算器を有効に使うには、乗算結果をそのまま次のステージで加算に使う必要があります。
  なので48bitあるDSPのPレジスタを活用しながら、内部でなるべく固定小数点のまま演算を進めて、除算などの必要のあるステージでだけ正規化して必要精度のbitだけ取り出し直すということをやれば、いい感じにFPGAにマッピングできそうな気がします。

2016年8月21日 (日)

テクスチャキャッシュを試作してみました

久しぶりに以前の続きです。

下記のようなアーキテクチャでGPU用のテクスチャキャッシュを試作してみました。
この上にバイリニア補間を行うSamplerが乗っかればそれっぽくなるはずです。

Texture_cache

軽量なリングバスを実験的に入れてみましたので各ユニットのサイズや個数は可変です。

バイリニア補間に4点使うので、4回アクセスを4並列でやればパフォーマンス上バランスがいいかなって事で、4並列で設定して合成して見ました。シミュレーションではまあまあ目論みどおりの挙動で動いているようです。

この構成だとテクスチャキャッシュだけで ZC7Z010CGL400 のBRAMを50%消費してしまいました(LUTは15%程度、周波数はISE合成で389MHzでした)。

ちなみにデータは書き込みの際に8x8ピクセルのブロック単位で、それぞれ保存するようにし、アクセス方向に対するパフォーマンス変動を抑えられるようにして見ました。

また RGB 24bit だとアクセス効率が悪そうなのでRGBも分解して個別に置くようにしています。

L1キャッシュは上に載る予定のSamplerユニット毎に1対1で対応、L2キャッシュはメモリのアドレスに対して1対1で対応するようにしつつ、以前考察したナンプレ式の対応表としています。

この方式もシミュレーションを見る限りはそこそこうまくいっているようです。

今日はここまでです。まともに動くのはいつになるやら...

2016年6月27日 (月)

Xilinxの分散メモリを見直してみた

GPUを検討しつつ、本丸のシェーダーになかなか行かずにテクスチャキャッシュを検討中であったりします。

キャッシュといえば、メインのキャッシュメモリとそれを管理するTAGメモリで構成されるわけですが、今回はTAGメモリのお話です。

メインのキャッシュメモリはBlobk-RAMで構成したいと普通に考えていますが、容量の少ないTAGメモリに関してはDistributed-RAM(分散メモリ)を使うことを考えています。

 今まで、Distributed-RAM については、Block-RAM 使うにはサイズが小さくてもったいない箇所にLUTのFFで代用できる便利な機能程度に思っていたのですが、今回いろいろと見直すところがありました。

 狙って使えば、という部分はありますが、使い方次第で非常に潜在能力が高い気がしてきました。

 まず、今回のテクスチャキャッシュのTAGメモリは、Writeが無いのでダーティービットとかの管理もなく、100% READ_FIRST な書き込みのみです。
 何しろ、ヒットしたらそのまま、ミスヒットしたら読み直してその値に更新、なので、TAGメモリに関しては100%更新しちゃっていいわけで、しかもシングルポートで済みます。
 このシングルポート 且つ READ_FIRST というのがDistributed-RAMに非常にフィットします。

 まず、シングルポートなので、RAM64X1 にアサインでき、1個のSLICEMに4個確保できます。スライス一個で256bit(32バイト)というのは大きいです。

 しかも READ_FIRST はそのままスライス内のFFに古い値をREADしながら新しい値を書き込めるように思えるのでコンパクトに収まりそうです。

 ついでに言うと、Distributed-RAM はただのLUT 1個なのでめちゃくちゃ速いです。
  別のLUTと直列にしてREAD と ミスヒット判定をセットにしても 300~400MHz の合成レポートが帰ってきました(幅次第ですが)。

 これはうまく嵌るように周辺を作れば、非常に高いパフォーマンスが出せそうな気がします。 楽しみがまた増えました。

 一方で、当然ながら分散メモリも長所ばかりではないので気が付いた点を少し書いておくと

 ・ CE が(BlockRAMで言うEN)が無い
 ・ デュアルポートにするとリソースが増える

 などがあります。

  CEについては、キャッシュなどの場合、先行する処理のミスヒットや出力のREADY待ちでパイプラインストールが必要なので、どうしても回避が難しいところがあります。 WEとのANDをSLICE内に入れて欲しかったなぁ、などと思ってしまいますが、もともとの素子が高速なのでAND用にLUTをもう一段組んでもダメージが少ないという判断なのでしょう(憶測)。

 また デュアルポート云々については、逆にリソースの増えない(電力は増えるけど)BlockRAMの方は、使えるなら積極的にデュアルポート機能を使って効率的な回路を組んだほうが効果的な気もします。

 まだまだ奥が深いです。

2016年6月26日 (日)

バイリニア補間ユニットの並列数について考える

先日作った射影変換回路では、ニアレストネイバーでの処理でした。

せっかく浮動小数点コアまで作って、座標は小数精度で計算しているのだから、バイリニア補間ぐらいやりたい、と思ってしまいます。

ここで、1画素生成するのにテクスチャは4画素読み込む必要が出てきます。
とはいえテクスチャキャッシュがしっかりしていれば、メモリに対するアクセスは1画素につき一回のはずです。

ではどう組めば綺麗にいくか考えて見ました。

当初考えた案としては大きくは下記の2つ

1) 4ピクセル並列アクセスして 1[pixel/cycle] を処理するSamplerユニットを作る
2) 4回読み込みをする 0.25[pixel/cycle] 処理能力のSamplerユニットを1)の4倍の個数並列動作するように作る

リアルタイムシステム屋の私は普段なら1)のアーキが適するケースが多いのですが、今回のようなグラフィックスのシステムに関しては2)の方がメリットが大きそうです。

 こうなってくると、設計指針が見えてきます。

 キャッシュミスヒットなども見越して、少しマージンを取った上で

  • 出力帯域に必要なだけのSamplerユニットの並列数を決める
  • 想定テクスチャサイズに対して十分なヒット率が出る前提でL2キャッシュの容量と見合ったメモリ帯域を決める
  • Samplerの並列実行を前提に、1つのSamplerが分担するであろう想定テクスチャサイズでL1キャッシュのサイズを決める
  • 各Samplerの重複部分のマージンを賄えた上でL1<=>L2の帯域を決める

 バイリニアの場合、書くピクセルは都合4回程度再利用されるはずなのでL1キャッシュは効果的に効くはずで。
 一方で、L2キャッシュは

  • Sampler同士で重複利用する部分のヒット率向上
  • SDRAMアクセスを大きな単位とすることでメモリアクセス効率向上

 の2点に大きく寄与するはずです。

 サイズも L1 < L2 と なるはずで、アーキテクチャとしては綺麗に嵌りそうな感じです。

 ついでに間のバスもここはシンプルにリングバス使えないかなと、構想(妄想?)中だったりします。

<余談>
 リアルタイムシステムを考える場合、4並列コアより、4倍早いコア1つの方が価値があります。4並列の場合、4つのタスクが同時に終わりますが、1コアですめばタスクは順番に完了していきますので、デッドラインの厳しいものから順にスケジューリングすることでシステムの応答性を高めることが出来ます。
 一方で、ポラックの法則しかり、4倍早いコアを作るのには4倍以上のリソースがかかります。GPUなどの場合、各ピクセルのリアルタイム要件は等しいですので、並列化がメリットが大きいわけです。

  さらに余談を進めると、TSS(タイムシェアリングシステム)というのは、リソースが希少な時代のCPUの利便性を高めるための産物でして、パフォーマンスのあるCPUをわざわざパフォーマンスの低いCPUが複数あるように見せかけて使う方式には、リアルタイムシステムという観点ではデメリットしかなかったりします。
 一方で近年のOSはRTOS的な要素を取り込みつつはあるものの現代に至ってもベースがTSSなので、そのへんはなんだかもったいない気がする今日この頃です。

テクスチャキャッシュについて考えてみる

 続、GPU考察です。テクスチャマッピングはまだ先と思っていましたが、先日射影変換回路を作ったので少し検討して見ます。
 前回は射影変換の座標計算のみ作って、メモリアクセスについては力技前提でXILINXのIPコアのキャッシュを使いました。

 よくよく考えて見ると前回の射影変換はポリゴン1面に対するテクスチャマッピングに他ならないわけです。
 そしてこの手のテクスチャへのメモリアクセスはCPUなどのそれと違ってX-Y平面状に線形的にアクセスするので、例えば座標変換の回転方向の成分によってはY方向に走査することもあるわけです。

 このとき例えばキャッシュ容量がテクスチャサイズ以上にあればよいのかもしれませんが、そうで無い場合悲惨なことになると予想されます。
 例えばポリゴンを90度回転させて貼り付けるケースの場合、1ライン目の走査で、メモリアドレスとしてはY方向に走査していきます。このとき毎回1ピクセルを作るデータ分の為だけにキャッシュライン分のアクセスが行われます。ここで2ライン目の走査までキャッシュの中身が残っていればいいのですが、要領が十分でなかったり、タグの衝突などが起こりキャッシュが残っていなければ極めて悲惨なことになります。Zyboの場合、BRAMは当然少ないし貴重なので、これは重要な問題です。

 またこの際のDDR3-SDRAMのアクセス効率も極めて重要と予想されます。X方向の走査であればアドレスは連続なので、DRAMの同一ページ内で高速なアクセスが起こるはずですが、Y方向の走査であればページをまたぐ為、プリチャージとアクティベートで時間を取られてしまうはずです。さらにバンクだけは同じだったりするとバンクインターリーブすら効かず、悲惨極まりないことになると予想されます。特にZynqのようなメモリをARMプロセッサと共存するようなアーキテクチャの場合、PL側の設計が悪くて知らぬ間にARMの性能が大きく落ちているとか大いにありそうな気がします。

 この回避方法はいろいろ考えられます。

 まず最大の問題はSDRAMのデータ配置がX方向とY方向で非対称になっている点です。
 データ走査の方向に影響を受けにくくするためにはなるべくX-Y方向に非対称にデータを
置けば言い訳です。
 例えば8x8なりのブロックを作って、その単位で書き込み/キャッシュを行えばポリゴンの
走査方向への影響は大幅に緩和されるはずです。

 次にテクスチャキャッシュのTAG-RAMのアサインです。
 簡単のため、例えば16ブロックをキャッシュできるシンプルなダイレクトマップキャッシュを
考える場合、下記のようにアサインすると縦方向走査時のの競合性キャッシュミスは避けられません。


0 1 2 3 4 5 6 7 8 9 a b c d e f
0 1 2 3 4 5 6 7 8 9 a b c d e f
0 1 2 3 4 5 6 7 8 9 a b c d e f
0 1 2 3 4 5 6 7 8 9 a b c d e f
0 1 2 3 4 5 6 7 8 9 a b c d e f
0 1 2 3 4 5 6 7 8 9 a b c d e f
0 1 2 3 4 5 6 7 8 9 a b c d e f
0 1 2 3 4 5 6 7 8 9 a b c d e f
0 1 2 3 4 5 6 7 8 9 a b c d e f
0 1 2 3 4 5 6 7 8 9 a b c d e f
0 1 2 3 4 5 6 7 8 9 a b c d e f
0 1 2 3 4 5 6 7 8 9 a b c d e f
0 1 2 3 4 5 6 7 8 9 a b c d e f
0 1 2 3 4 5 6 7 8 9 a b c d e f
0 1 2 3 4 5 6 7 8 9 a b c d e f
0 1 2 3 4 5 6 7 8 9 a b c d e f

 ここで、ナンプレ(数独)のように、各縦横や4x4範囲内で同じタグにならないように
配置すれば、競合性キャッシュミスは相当避けられる気がします(wayも作れればさらに良い)。

0 1 2 3 4 5 6 7 8 9 a b c d e f
4 5 6 7 8 9 a b c d e f 0 1 2 3
8 9 a b c d e f 0 1 2 3 4 5 6 7
c d e f 0 1 2 3 4 5 6 7 8 9 a b
1 2 3 4 5 6 7 8 9 a b c d e f 0
5 6 7 8 9 a b c d e f 0 1 2 3 4
9 a b c d e f 0 1 2 3 4 5 6 7 8
d e f 0 1 2 3 4 5 6 7 8 9 a b c
2 3 4 5 6 7 8 9 a b c d e f 0 1
6 7 8 9 a b c d e f 0 1 2 3 4 5
a b c d e f 0 1 2 3 4 5 6 7 8 9
e f 0 1 2 3 4 5 6 7 8 9 a b c d
3 4 5 6 7 8 9 a b c d e f 0 1 2
7 8 9 a b c d e f 0 1 2 3 4 5 6
b c d e f 0 1 2 3 4 5 6 7 8 9 a
f 0 1 2 3 4 5 6 7 8 9 a b c d e

アドレスを逆引きできないのでTAGメモリにフルアドレス保持が必要だが、もともと小さいRAMなので効果の方が大きい気がする。

ということで、Zyboの少ないBRAMを駆使して効率よく映像をぐりぐりまわせるテクスチャキャッシュが作れないか構想(妄想?)中です。

2016年5月 3日 (火)

作成した浮動小数点演算ライブラリの整理

作成した浮動小数点演算ライブラリを整理してみました。

XILINXのIP使えば済む話ではあるのですが、

  ・低精度でいいけど高速な逆数計算が欲しかった(本命)
  ・非正規化数とか例外とか不要なのでコンパクトにしたい
  ・IPコア使うと veritak とかでのシミュレーションが面倒

  とかしょうも無い理由で、ある程度必要なものを書いて見ました。
  デバッグ目的の投影変換もまあまあ動いているのでまずは良しとしましょう。

xc7z010-1clg400 指定で、デフォルトオプション ISE14.7 で合成のみ実行しています。
ここだけ合成のみで周波数見積もりしてくれて便利なISEを利用しています。

[reciprocal 64点補間]  (Distributed RAM)
latency:6  throughput:1
Maximum Frequency           479.157MHz
Number of Slice Registers          315
Number of Slice LUTs               133
Number of DSP48E1s                   1

[reciprocal 1024点補間]  (Block RAM)
latency:6  throughput:1
Maximum Frequency           479.157MHz
Number of Slice Registers          225
Number of Slice LUTs                92
Number of Block RAM/FIFO          2
Number of DSP48E1s                   1

[multiply 指数部:8bit 仮数部:23bit (単精度相当)]
latency:6  throughput:1
Maximum Frequency           394.633MHz
Number of Slice Registers          201
Number of Slice LUTs               110
Number of DSP48E1s                   2

[add 指数部:8bit 仮数部:23bit (単精度相当)]
latency:7  throughput:1
Maximum Frequency           397.456MHz
Number of Slice Registers          386
Number of Slice LUTs               399

[fixed_to_float 32bit整数 => 単精度]
latency:4  throughput:1
Maximum Frequency           397.456MHz
Number of Slice Registers          221
Number of Slice LUTs               583

[float_to_fixed  単精度 => 32bit整数]
latency:4  throughput:1
Maximum Frequency           430.293MHz
Number of Slice Registers          177
Number of Slice LUTs               215

簡易逆数計算の reciprocal 以外あまりメリット無い気もしますが、いい勉強にはなりました。

やはり、加減算や整数変換などバレルシフタの必要な系がレイテンシ長くなりがちです。

valid ready のハンドシェークで、バブル埋め出来るようにしたりとかしょうも無い機構で1段余分に食っていたりもします。

ただ、例外処理や非正規化数の扱いをまじめにやるとそこが段数増える要因にもなるので今回そこを省いたのは効果大きかった気がします。

もともとGPU的なものを考えるときに、ラスタライザの中の投影補正は毎サイクルなのでスループットさえ1なら、レイテンシ長くていいのですが、頂点シェーダー部分となるとピイ九セル処理より稼働率低いので、演算器の共有がやりたくなるのでレイテンシ短くしたいわけです。

一応、次のステップ狙って、周波数は高めに合成できるように作ったつもりですが、次はいつになるやら。

例によって、コードはgithubにて
https://github.com/ryuz/jelly/

[2016.05.04追記]
XILINX の IP の reciprocal で 半精度浮動小数点の逆数が max latency 4 cycle ですね。
前からありましたっけ? (^^;;  完全に見落としていました。

ちょっと別件でアンインストールせずに残していた Vivado2014.4.1 (Floating-point 7.0)では Single と Double しか選べない(Customも無し)なので、ここ数年の間に増えた機能みたいですね。

ディープラーニングでGPUなどの半精度浮動小数の需要が増えているらしいですが、FPGAでもこのあたり引き続き充実してくると嬉しいですね。

そういえば確か GRAPE-1 でも 64k×8bit のROM をいくつか使って、8bit 精度の演算をLUT方式で巧みに計算されていたと何かの本で読んだ記憶があります。

用途と精度をあわせることは重要ですね。

より以前の記事一覧