FPGAボード(Sipeed Lichee Tang Primer)を使って設計しているオリジナル16ビットCPUコアMoscoviumには、半精度浮動小数演算ユニット(Half precision FPU、以降H-FPUと呼びます)が搭載できます。
「#16-5 半精度浮動小数演算ユニット」
「#16-6 半精度浮動小数演算ユニット(弐)」
半精度浮動小数型(16ビットhalf型)は、16ビット幅の汎用レジスタを持つMoscoviumには最適でした。一方、有効数字は10進数換算で約3桁強と、満足できる精度ではありませんでした。
その後、汎用レジスタや演算器を32ビット幅に拡張したオリジナル32ビットCPUコアNihoniumを設計しました。現時点で、NihoniumのコプロセッサとしてH-FPUを接続することは可能なRTLにしてあります。
折角、汎用レジスタが32ビットになったので、浮動小数型も32ビット長にすることができます。32ビット長の浮動小数型としては、half型と同様、IEEE754で規定されているbinary32(単精度浮動小数、以降float型と呼びます)が挙げられます。
今回は、単精度浮動小数演算ユニット(Single precision FPU、以降S-FPUと呼びます)を設計します。
half型とfloat型は同じIEEE754で規定されているだけあって、下図の様にバイナリフォーマットは、ほぼ同じです。
float型では、全体のビット幅が16ビット→32ビットに拡張されたのに伴って、指数部Ex’と仮数部Fx’のビット幅も拡張されています。
手元には既に、H-FPUのRTLが有りますので、そこからチョチョイと、ビット幅を広げる設計変更をすれば、S-FPUが完成するはずです。従って、改めて作図し直すまでもなく、ブロック図レベルとしては、H-FPUのブロック図と同じ形となります(下図)。
※half→single、h***→s***等と読み替えてください。内部バスや演算器のビット幅が拡張となります。
以下、代表的なブロックについて、float型への設計変更を進めて行きます。
32ビットコプロセッサバスA[31:0]、B[31:0]から受け取った2数を加減算(A±B)します。演算アルゴリズムについては、half型と同じです。以下、half型との差分についてのみ、考察します。
float型で表現可能な、非有限値(NaN、INF、ゼロ等)との加減算は、half型と同様です。
32ビットコプロセッサバスA[31:0]、B[31:0]から受け取った2数を乗算(A×B)します。演算アルゴリズムについては、half型と同じです。以下、half型との差分についてのみ、考察します。
float型で表現可能な、非有限値(NaN、INF、ゼロ等)との乗算は、half型と同様です。
TD IDEのIP Generatorを使って、以下図の様に乗算器マクロを作成してください。
TD IDEの乗算器ハードマクロは18ビット長であるのに対して、必要なのは24ビット長の乗算器なので、4個の乗算器ハードマクロで部分積を作って、加算して求めるような論理を生成するはずです。
H-FPUの乗算器は11ビット長だったので、18ビット乗算器ハードマクロ1個で充足していました。H-FPUに比べて、論理規模と伝搬遅延時間が増大しますが、H-FPUと同様、1サイクルで実行することにします。クリティカルパスは強化されますが、多分、問題ないでしょう(後ほどSTAで確認)。
32ビットコプロセッサバスA[31:0]、B[31:0]から受け取った2数を除算(A÷B)します。演算アルゴリズムについては、half型と同じです。以下、half型との差分についてのみ、考察します。
float型で表現可能な、非有限値(NaN、INF、ゼロ等)との除算は、half型と同様です。
float型からint型への型変換だけでなく、floor()関数やceil()関数の実装等に於いて、float型の数値を整数化する必要が生じます。
floor()関数やceil()関数等を実装するためには、float型のまま、整数化する処理が必要となります。
この処理を実行する回路だけ、新規設計して追加します。
NihoniumにH-FPUを接続することは可能なので、H-FPU命令を備えています。H-FPU命令は、hadd命令,hmul命令などの様に、先頭文字「h」で始まる命令です。
では、S-FPU用として「f」で始まる、fadd命令、fmul命令などを作りましょう。
ここで一考。
そこで、H-FPU命令と、S-FPU命令の命令コード(オペコード)を同一コードに割り当ててしまいます。すると、何という事でしょう!従来のH-FPU命令コードのまま、S-FPUを制御できてしまいます。
命令数に変動が無いので、論理規模は変わらず、伝搬遅延時間も変わりありません。
※NHビットの設定だけで、16ビットモードと32ビットモードを切り替える、Nihoniumの基本方針と同じ考え方です。
「欠点」とまで、言えるかどうかわかりませんが、考え得る欠点としては、Nihoniumに対して、H-FPUとS-FPUを同時に接続することは、できなくなります。でも、それだけのことです。普通、S-FPUを搭載したら、並行してH-FPUを使いたいケースは無いでしょうから、問題になることはないでしょう。
従来通り、H-FPUだけを接続して使うことは可能ですし、新たにS-FPUだけを接続すれば、Nihoniumで単精度浮動小数float型の演算を実行することができます。
アセンブラとしては、H-FPU命令(h***命令)とS-FPU命令(f***命令)を別名として、同じ命令コードにアセンブルします。アセンブラレベルでは、別命令扱いとなることに留意してください。アセンブルリストの一例を以下に示しておきます。
// @adr code // adr*2 instruction
@0000 7001 // 0000 fadd r0,r1
@0001 7001 // 0002 hadd r0,r1
アセンブラレベルでは、fadd命令とhadd命令は別扱いですが、共に同じ命令コード0x7001にアセンブルされていることが判ります。
Nihoniumは、状態レジスタsrのNHビットによって、動作モードが変化します。
コプロセッサとして接続するS-FPUには、32ビット幅の汎用レジスタや内部バスが必要なので、NH=1を設定した場合だけS-FPU命令を使用することができます。
プログラム実行時、NihoniumにH-FPUまたはS-FPUが接続されているかどうか?検出したい場合には、以下の命令列を実行して、判定することができます。
ldbiu r0,1
hneg r0,r0 // fneg r0,r0 でも良い
fneg命令とhneg命令は同じ命令コードに展開されるので、どちらを使っても結果は同じです。実行後の汎用レジスタr0の値で、以下の様に判定してください。
検出したFPUに従って処理を変えたり、場合によってはエラー出力してプログラムを停止するなどの処置に使用できます。
H-FPUからS-FPUへの設計変更で必要となった、float型のまま整数化する機能について、CPUにも新規に命令を追加します。
動作は、「ゼロ方向への丸め(round down to zero)」なので、fdown命令とします。
H-FPUでは、以下のようにすると同じ動作をエミュレートできるため、命令は追加しません。
fdown r0,r1
↓ H-FPUで相当する動作
huint r0,r1
hhalf r0,r0
hmvsg r0,r1
Moscovium+H-FPUにも追加すると便利かもしれませんが、今回は追加しません。余計な修正がCPUに入ると、検証などの工数が馬鹿にならないので、必要に迫られない限り手を付けないのが賢明だという判断です。
※Blocklyを使った開発環境の整備に伴い、H-FPUにもhdown命令を追加しました。こちらの記事をご確認ください。
今回設計したS-FPUを搭載したマイコンNHOC113FS0408を作成しました。
以下リンクより、最新のデザインをダウンロードすることができます。
FPGAボード(Sipeed Lichee Tang Primer)への書き込み手順はこちらを参照してください。
以下に、TD IDEでのコンパイル(論理合成・配置配線)結果をまとめておきます。
S-FPU単体でコンパイルしてみると、3,300LUT程の論理規模でした。Moscoviumコア+乗除算器が約3,600LUTですので、CPU(Moscovium)ほどの大きさがあります。いくつか並べて比較してみましょう。
ユニット | 論理規模[LUT] | 乗算器マクロ数 |
Moscovium | 2,600 | 0 |
Moscovium+乗除算器 | 3,600 | 1 |
Nihonium | 5,600 | 4 |
H-FPUコプロセッサ | 1,100 | 1 |
S-FPUコプロセッサ | 3,300 | 4 |
S-FPUは、H-FPUから比べると×3倍ほどの論理規模でした。浮動小数型のビット幅では×2倍なのですが……。まあまあ、大きい回路ですね……。演算精度が許すなら、H-FPUを使った方が、お得感があります。
iverilogを使って、論理検証を行います。検証方針は、H-FPUの時と同じです(下図)。
half型(16ビット)→float型(32ビット)などに読み替えてください。
ただし、1点だけ、H-FPUの時とは異なる点がありました。H-FPUの時はピッタリ期待値を一致させることができたのですが、S-FPUでは、どうしてもfloat型最下位ビット(LSB)の値だけ、うまく合わせることができませんでした。
常に上例の様になるわけではなくて、完全に一致する場合もあり、合わせ込むことは断念しました。一々エラーを表示しても、目視で最下位ビットだけの不一致だと確認するのは手間なので、最下位ビットだけの相違であれば、パス(合致)として検証を実行しました。
加減乗除算数10,000パタンと、その他のFPUコマンドによる処理結果を検証した結果、問題ありませんでした。勿論、確認した数10,000パタンの範囲内です。本HPの回路やプログラムは全て無保証ですので、ご理解の上、ご利用ください。
S-FPU命令をシミュレーション実行して見て、各命令の実行サイクル数を調べました。以下表にまとめて、解説します。
H-FPU | S-FPU | メモ | ||||
命令 | min | max | 命令 | min | max | |
hadd | 3 | 8 | fadd | 3 | 8 |
仮数部ビット数増加で+1、 指数差≧0x20の対処で−1サイクル。 |
hsub | 3 | 8 | fsub | 3 | 8 | |
hcmp | 3 | 8 | fcmp | 3 | 8 | |
hmul | 3 | 4 | fmul | 3 | 3 | 正規化を演算に押し込んで−1サイクル。 |
hdiv | 6 | 7 | fdiv | 3 | 7 | 除算ステップ数+2、ロード−1、正規化−1サイクル。 |
huint | 3 | 7 | fuint | 3 | 8 | uint16→32に伴って、+1サイクル。 |
hfrac | 3 | 7 | ffrac | 3 | 8 | |
hhalf | 3 | 5 | fsngl | 3 | 6 | |
hmvsg | 2 | 2 | fmvsg | 2 | 2 | 変わらず。 |
hneg | 2 | 2 | fneg | 2 | 2 | |
hsat | 2 | 2 | fsat | 2 | 2 | |
hdown | 2 | 2 | fdown | 2 | 2 |
解説:
上に記載の通り、H-FPUのサイクル数を検証したところ、何ヶ所か無駄なサイクルが有りました。S-FPUへ設計変更する際に見直したので、H-FPUと比べてそん色ない速度で処理可能なコプロセッサとなりました。10進数換算で有効数字3桁強しか無かったH-FPUに比べると十分実用的な、有効数字7桁弱のS-FPUが、H-FPUとそん色無い速度で実行できるので、浮動小数演算が必要になった場合には、S-FPU一択で悩まずに済みます。
なお、H-FPUでも適応可能な見直し箇所もあるのですが、修正すると論理検証までやり直しで無駄な工数🏭が発生するため、H-FPUへの適応は見送ります。S-FPUを使った応用は、次回です。