カテゴリ:論理設計の話( 10 )

V-scale に使っている FPU 乗算器の話です。
V-scale に使っている乗算器の話 にも書いた通り、整数乗算と FPU 乗算では 4サイクル目までの動作は同じです。
4サイクルで仮数部の乗算結果を求るのは、整数乗算器の説明の通りです。
そして、5サイクル目は正規化と丸めをします。ここは前回と変わりありません。
ただ、今回はちょっと高速化を狙って正規化シフタを変更しています。あまり効果ありませんでしたが…
ちなみに、実際に V-scale で使っている記述とは表現が全然違うのですが、構成も若干違うところがあります。
表現の違いは合成に過大な期待をしていない(加算器をたくさん生成しないとか)ため、構成の違いはディレイ対策の有無と思ってください。


[PR]
by tom01h | 2017-11-19 23:45 | 論理設計の話 | Trackback | Comments(0)
今回はサブノーマル数に対応します。回路化は全く考えていない構成ですが、機能的にはすべてが実装されます。
サブノーマル数は指数フィールドが0で示され、その値は↓です。
((-1)^F[31]) * (0.F[22:0]) * (2^(1-127)) ^はべき乗です
つまり、サブノーマル入力を考慮した指数は
expx = (x[30:23]==8'h00) ? 8'h01 : x[30:23];
で表され、仮数は
fracx = {(x[30:23]!=8'h00),x[22:0]};
で表されます。
また、正規化数しか入力されない場合は、正規化シフトはシフトなしか1ビットの右シフトの2通りから選ぶだけでした。
サブノーマル数が入力された場合は左シフトも必要になります。この先どうせ立派なのが必要になるので、63ビットまでのシフト量に対応した正規化シフタを追加します。
ちなみにこの正規化シフタは、サブノーマル数を出力可能とするために正規化後の指数が1となるところで止まります。
あと、今は必要ないですが、仮数が負の数にも対応しています。
また、サブノーマル数の出力にはこれだけじゃ足りなくて、正規化前の指数が負になっちゃった場合は仮数を大きく右シフトして、その分を指数に足してあげます。
そして、正規化シフタで指数が1となるところまで戻してあげると、サブノーマル数が出力できます。
具体的な実装は Github を確認してください。

[PR]
by tom01h | 2017-11-12 12:43 | 論理設計の話 | Trackback | Comments(0)

FPU のフラグの話

FPU は演算結果と同時に例外フラグを生成する必要があります。
V-scale で使っている演算器の場合は
ビット0が不正確フラグで、演算中に精度落ちがあった場合に通知します。
ビット1がアンダーフローフラグで、こいつはとっても複雑なので下のほうで。
ビット2がオーバーフローフラグで、演算の結果が正規化数で表すことができないくらい大きくなった場合に通知します。
ビット3が0除算フラグで、除数0の除算を通知します。ただし、被除数も0の場合はエラーを優先します。
ビット4がエラーフラグで、例えば0×無限大などの答えを決められない場合に通知します。

アンダーフロー検出について。
アンダーフローはTininessか検出されて、かつ、精度落ちがあったときに通知されます。
Tininessの検出は丸め前検出と丸め後検出を選ぶことができますが、同一のアーキテクチャの中では一貫している必要があります。
Tininessの丸め前検出の場合は簡単で、指数の範囲、演算精度ともに制限なしで計算した結果が正規化数で表すことのできる最小の値よりも小さいときに検出されます。
RISC-Vで使うのは丸め後検出なのですが、こいつが厄介で、演算精度がフォーマットの定める桁数で、指数の範囲の制限なしで計算した結果が正規化数で… 以下同文です。
これの何が厄介かというと、サブノーマル数の演算結果を得るためには正規化シフトは指数を見て止める必要がありますが、Tininessの検出には正規化シフトを止めてはいけないという矛盾が生じます。
でも2つの正規化シフタを持つのは現実的じゃないし…
演算結果がサブノーマル数の場合はTininessを検出してよい。これで足りないのは指数を気にせず正規化シフトした場合の指数が0で、仮数が以下の1番上のケース。この値だけが、正規化シフトを指数に応じて止めてから丸めた時だけ最小値に昇格します。

Tininess 検出のときには丸めで桁上げがない。
1.11111111111|0xxxxxx |は有効桁数の境目
サブノーマル数の演算結果を得るために正規化シフトを止めちゃった場合は桁上げがある。
0.11111111111|10xxxxx |は有効桁数の境目
ただ、演算結果がサブノーマル数になる数を余分に検出しちゃっても構わないので、
0.xxxxxxxxxxxx|x0xxxxx |は有効桁数の境目
を検出すればよい。

この考え方で作った回路が Berkeley Test Float をパスするので、多分この考えで正しいかと思います。

[PR]
by tom01h | 2017-11-08 20:26 | 論理設計の話 | Trackback | Comments(0)
今回は無限大と非数に対応します。また、検証環境として Berkeley Testfloat を導入します。
無限大は大体の想像がつくと思いますが、単精度浮動小数の場合は指数としてとれる範囲が 1~254 であるので、その範囲で表現できる数を超えた大きな数は無限大として扱います。正の場合が 7f800000 で 負の場合は ff800000 です。
非数は、例えば無限大×0とかどうして良いか分からないときの演算結果として生成されます。一度生成されるとその後の演算で伝搬されます。ほかにもいろいろあるのですが、詳しく知りたい方は IEEE 754 を調べてみることをお勧めします。
乗算の場合には以下の優先順で判定します。
  1. 入力のどちらかが非数の場合には非数入力を伝搬する。
  2. 無限大×0は非数を生成する。
  3. 演算途中の仮数が0なら答えは0。
  4. 演算途中の指数が小さければ答えは0。
  5. 演算途中の指数が大きければ答えは無限大。
  6. どれでもなければ普通に計算。
次に Berkeley Testfloat ですが、簡単に言うと膨大に生成した入力データと Berkeley Softfloat という FPU シミュレーションソフトで生成した期待値を使って、FPU の検証を行うソフトです。ホスト CPU の検証をすることが主な目的?なのかもしれませんが、入力値と期待の演算結果を標準出力に表示するテストがあるのでそれを利用します。
例えば
$ ./build/Linux-x86_64-GCC/testfloat_gen -f32_mul
とか打つと、入力×2 期待演算結果 期待フラグの順に表示します。
8683F7FF C07F3FFF 07839504 01
00000000 3C072C85 00000000 00
こんな感じのがすごくたくさん出てきます。これを標準入力から受け取って、FPU 乗算器に演算させた結果と比較して、合否判定をするテストベンチを作成しました。まだサブノーマル数に対応していないので、↓みたいに実行してもらえるとサブノーマル関連のエラーを除くことができます。
$ {PATHto}/testfloat_gen -f32_mul | ./sim/Vfmul_1 | grep FAIL | grep -v " 00[0-7]" | grep -v " 80[0-7]"


[PR]
by tom01h | 2017-11-05 19:41 | 論理設計の話 | Trackback | Comments(0)
FPU 乗算の話です。FPU で扱うデータは正規化数以外にサブノーマル数や無限大から、数字じゃないデータまであるのですが、今回は正規化数に限った話。32bit で表現する単精度浮動小数で言うなら以下のようになります。
ビット31が符号を表す。
ビット30~23が指数を表す。正規化数の場合は1~254。
ビット22~0が仮数の小数点以下の部分を表す。ただし、仮数は 1.**** で表す。
で、F[31:0] の値は↓です。
((-1)^F[31]) * (1.F[22:0]) * (2^(F[30:23]-127)) ^はべき乗です
A[31:0]*B[31:0] を考えた時、
F[47:0]={1'b1,A[22:0]}*{1'b1,B[22:0]}
が仮数の掛け算の結果です。小数点以上が1桁、小数点以下が23桁の数同士の掛け算なので、小数点以上が2桁、小数点以下が46桁の乗算結果が得られます。つまり、F[46]の下に小数点があります。この結果を再度、浮動小数点フォーマットに戻すために、1.**** の形式に変換する必要があります。具体的にはF[47]の値によって分岐する必要があり、
F[47]==0の場合は桁上げ無しなので指数は A[30:23]+B[30:23]-127 になります。
仮数はF[45]が小数点以下第1位になって、[23]が最下位ビットでそれ以下は四捨五入をします。ただ FPU の四捨五入はちょっと特殊で、ちょうど真ん中、つまり[22]が1で[21:0]==0の時は、[23]=0となるように切り上げたり切り捨てたり、[23]によって動作が異なります。
F[47]==1の場合は桁上げ有りなので、指数は A[30:23]+B[30:23]-127+1 になります。
仮数はF[46]が小数点以下… 以下同文
だらだら言葉で説明してもわかりにくいので、Github を見てください。

[PR]
by tom01h | 2017-11-04 21:25 | 論理設計の話 | Trackback | Comments(0)
いよいよ V-scale に使っている乗算器の話です。
V-scale の乗算器は3入力加算器が64bit幅のものと32bit幅のものを1個ずつ持ちます。この2個の加算器を使って5サイクルで32bit乗算をします。後々同じ回路でFloatの乗算もしたいので、4サイクルで24bitの乗算もできるように準備をしておきます。
前回同様、32bit乗算器の部分積は下図のようになります。そして、赤枠で囲ってある部分がFloat用の部分積です。こっちは符号なし限定です。
f0054075_23284224.png
オレンジの部分を64bitの加算器で足しこみ、紫の部分を32bitの加算器で足しこみますが、その前に、右上部分を下にずらして考えます。
f0054075_00095435.png
そして、前回と同じ要領で赤枠内のオレンジと紫のそれぞれを3サイクルかけて1個ずつにまとめます。
f0054075_23520922.png
その後2サイクルは64bitの加算器だけを使って32bit乗算完了です。Github 更新しました。


[PR]
by tom01h | 2017-10-27 23:57 | 論理設計の話 | Trackback | Comments(0)
少ない資源を繰り返し使って乗算結果を得る話です。
今時こんなケチな設計はあまりやらないと思うのですが、V-Scaleに使った乗算器は結構しょぼいのでその説明の前哨戦です。
verilog 上ではちゃんと記述していませんが、X+Y+Z=S+C*2 となるような計算を桁上げなしで計算する回路をCarrySaveAdderと呼びます。ちなみに*2はただの1ビットシフトなので、回路上は何もないようなものです。
贅沢な乗算器ではこのCSAをたくさん使い、部分積が2個になるまで計算してから最後にキャリー伝搬のある加算器で積を求めます。
ここでは、CSA1段に加算器を付けて3入力の加算器として使います。
V-Scaleでは3入力加算器を64bit幅のものを1個と32bit幅のものを1個使っていますが、まずは簡単な64bitの3入力加算器1個のバージョンです。
まぁ、最初の1回は3個の部分積を足す。2回目以降はその加算結果に2個づつ部分積を足しこんでいく。というだけなのですが…
f0054075_22525223.png
32bit乗算では上のような感じにたくさんの部分積ができるのですが、最初の1回は最下位3個、つまり上の3行を足しこんで、
f0054075_22550229.png
2回目以降はその結果を4bitシフトしてさらに2個の部分積を足しこみます。
f0054075_22572798.png
ちなみに下にはみ出ているSと上位に拡張しているSは意味が違って(ややこしくてすみません)、はみ出ているのは×-1の時のインクリメントの意味で、上位に拡張しているのは前回説明した符号拡張の省略形です。
そして、一番下にはみ出している赤字のSですが、符号なしの場合は最上位が×-1倍のわけないので必要ないし、符号付きの場合はそもそも最上位の部分積が必要ないことがわかります。
あまり面白くないですが、Github 更新しました。


[PR]
by tom01h | 2017-10-23 23:52 | 論理設計の話 | Trackback | Comments(0)
Boothで求めた部分積を足しこむ際に符号拡張をばぼる話です。
ここにあげた例ではデータ長が短すぎて有難さがわからないのですが、符号拡張をさぼる手法があります。名前は知りません。
まずは、今までの例ではありがたみどころか説明さえできないので、前回の符号あり乗算の例を無駄に符号拡張します。
赤字が符号拡張です。実際の乗算器では、もっとたくさん符号拡張が必要になります。
f0054075_15224965.png
これをさぼるには…
最下位の部分積とそれ以外ではルールが違うのですが、Sを部分積の符号とすると
最下位は "~S,S,S" を上位ビットに拡張
それ以外は "1,~S" を上位ビットに拡張
上の例だと
f0054075_15233438.png
では、最下位の部分積とそれ以外の部分積の場合で、本来の符号拡張との差を見てみます。
f0054075_15244723.png
ここで、n-1個目の部分積までの差分の総和が{1,1,…,1,0,0}と仮定したときの n個目の部分積までの差分の総和を求めてみます。
部分積は 2bit ずれるのに注意です。
f0054075_15253740.png
n個目の部分積までの差分の総和も{1,1,…,1,0,0}になり、最下位も同様なので、帰納法で証明されたことに… なるんだよね?
つまり、桁あふれ分だけに差が出てくるので、まぁ良いかってことです。
Verilog にするとあまり差が出ないのですが、Github を更新しました。

[PR]
by tom01h | 2017-09-18 18:20 | 論理設計の話 | Trackback | Comments(0)

符号付き乗算器の話

前回は符号なしの話でしたが、Boothの良いところは、そのままで符号付きの乗算ができるところです。
たぶん、2,3を-2,-1にするのが、4の補数(?で良いの?)と相性が良いのかな?
まずは、入力の2進数を4進数と、冗長表現に変換するところまで。符号付きと符号なしだとこんな感じ。4の補数の4進数は3で符号拡張するので良いんだよね?
f0054075_10151286.png
このBoothを使って乗算します。Boothと2進が混ざってカオスですが…
f0054075_13233924.png
つまり、符号拡張さえちゃんとやれば、Boothの乗算は符号について難しい事を考えずに済みます。
これまでの話を Verilog で記述しました。Verilatorで検証することを前提に、テストベンチはC++で記述しています。32bitの符号あり、符号なしの乗算に対応しています。テストベンチは符号ありにしか対応していませんが…
Verilatorが使える環境前提ですが、
mul/ の下で make を実行すると sim/Vmul_0 が出来ます。
sim/Vmul_0 を引数なしで実行すると、ランダムで生成した入力を使った乗算を 1000 回実行します。
好きな回数で実行する場合は、引数を1個与えます。
好きな値をかけるには、引数を2個与えます。
引数は10進数ですが、結果の表示は16進数なので、バグの再現実行が面倒くさいです。
今度こそ符号拡張をさぼる手法の話をしたいと思います。

[PR]
by tom01h | 2017-09-16 22:06 | 論理設計の話 | Trackback | Comments(0)

booth 乗算器の話

唐突に Booth アルゴリズムを使った乗算器の話を始めます。
冗長表現の4進数を使って、うまいこと乗算しましょうという話です。
まず2進数と4進数の掛け算を比べてみると、
f0054075_23201334.png

こんな感じです。4進数を使うと2進数と比べて、部分積の数が半分で済みます。当たり前ですが…
でもこれだと、部分積を求めるための×3とか計算が大変ですよね。なので、冗長4進表現に書き換えます。ここでは、-2,-1,0,1,2の5個のシンボルを使う表現にします。大事なのは、2倍は1ビットシフトで済むこと、-1倍はひとまずは論理否定をしておいて、後で+1すれは良いってところです。
結果から先に言うとこんな感じです。部分積が1個増えちゃいましたが…
f0054075_23295465.png
ここで使ったルールは、元のシンボルが2,3の場合は上の桁に+1して(+4になる)自身は-2,-1となります。その結果に下の桁からの桁上げを足したら冗長4進表現になった新しいシンボルが出来ます。
表にすると下のようになります。
f0054075_23375274.png
この表を見るとわかるように-3,3がないので、部分積が簡単に求まります。
4進表現の部分積を2進数に書き換えてあげると下のようになります。
f0054075_23580016.png
符号拡張をさぼる手法の話はまた今度。

[PR]
by tom01h | 2017-09-13 23:46 | 論理設計の話 | Trackback | Comments(0)