公開日:2021/06/05
title.png

16th Season


#16-5 半精度浮動小数点演算ユニット

 FPGAボード(Sipeed Lichee Tang Primer)用に作成したオリジナル16ビットCPUコアMoscoviumに関しては、乗除算コプロセッサを追加したことで、CPU命令で整数の四則演算を実施することが可能となっています。
 次に強化するのは、浮動小数点演算です。

 PC等では、通常、32ビット単精度浮動小数点演算(float型)や、64ビット倍精度浮動小数点演算(double型)が使用されています。しかし、16ビットCPUコアであるMoscoviumにとっては、32ビットのfloat型でさえ、手に余るのです。
 「さすがにMoscoviumに浮動小数点演算は厳しいな……」、そう思っていたところ、面白いものを見つけました。16ビット半精度浮動小数点演算(half型)と言う物がある様です。

IEEE 754 binary16

 PC等でfloat型やdouble型の表現に用いられていて、一般的となっているのは、IEEE 754と言う規格です。この中に、binary16と言うフォーマットが規定されていて、これが今回対応したいと考えている浮動小数点表現です。

  IEEE 754 binary16を検索する(別ウインドウが開きます)

 half、half-float、FP16等の名前で呼ばれているようですので、本HPではhalf型と表記したいと思います。

 実は、IEEE 754の中では、実際に演算に使用するfloat型やdouble型のフォーマットと異なり、half型のフォーマットは「交換形式」として定義されていて、データ転送やストレージに使用される形式とされています。

half型バイナリ表現

 half型のバイナリデータは以下図のような形式となっています。

a165_half.png

 符号(Sx)1ビット、指数(Ex’)5ビット、仮数(Fx’)10ビットの計16ビットです(↓)。

  half型X=(−1)Sx×0x1.<Fx’>×2Ex’−15

 10進数換算の有効数字としては、たったの3桁程度と言う事で、円周率だとπ=3.14が関の山です。ことによると近似値22/7≒3.14285714286と変わりません(1^^;)。22/7なら、整数演算できますので、「こんな精度で浮動小数点演算要るのか?」ってのが第一印象です。

※単精度浮動小数点数(float型)の解説は、こちらのページにあります。half型とfloat型は、指数と仮数のビット数が異なるだけで基本的には同じ構造をしています。

用途

 half型で表現可能な数値は、以下の様になります。

 絶対値で、約6.10×10−5〜65,504と言う、非常に広いダイナミックレンジを扱えることから、0〜65,535までの整数しか扱う事の出来ないuint16型で不足する用途での交換形式として使用されているようです。何しろhalf型は、uint16型と同じ16ビット幅ですからね。
 具体的には、GPU(Graphics Processing Unit)などで使われているのだとか。へぇ〜。

 次節でもう少し具体的な設計を進めますが、仮数Fxが11ビットしかないので、演算回路はfloat型やdouble型と比べてかなり小さくすることが出来ます。このためhalf型の演算回路を多並列に構成して、並列処理のスループット向上などにも応用が進んでいるようです。

※最近のPC用CPUが採用している64ビットのアーキテクチャ相当であれば、その中に16ビットhalf型のデータを64/16=4個も格納することが出来て、4数の並列演算が可能となります。128ビットなら、更にその倍。

FPUを作ろう

 然して、今回はMoscovium周辺演算回路として、FPU(Floating Point Unit)を設計しようと思っています。

 16ビットCPUであるMoscoviumには、16ビット汎用レジスタが8個しかありません。32ビットfloat型を汎用レジスタに保持しようとすると、2個のレジスタをペアで使うしか無い上、データバスも16ビットしかないので、データの転送にも2バスサイクルを要してしまいます。
 これでは、折角のFPUが泣きます。
 ところが、16ビットhalf型であれば、16ビット汎用レジスタにピタリ収まるし、16ビットバスでの1バスサイクル転送も可能で、とてもHappyなのです。

 以下、Moscovium用Half precision FPU(HFPU)の目標仕様とします。

  目標仕様:

浮動小数点演算の算数

 以下、HFPUの設計に必要となる、浮動小数点演算の算数について、簡単にまとめておきます。以下、A,Bをhalf型の浮動小数点数値とします。

 実は桁合わせ処理が必要になる分、浮動小数点演算では乗除算よりも加減算の方が少し処理が複雑となって、その分実行サイクル数もかかります。面白いですね。除算はステップ除算を使うと、そこそこサイクル数がかさみますから、恐らく一番速いのは乗算でしょう(加減算を抑えて)。
 しかも加減算には、「桁落ち」という演算精度を低下させる現象が起きるので、浮動小数点演算の中で加減算はとても曲者です。

非有限値の演算

 half型には、整数型には存在しなかったINFやNaN等の非有限値が存在するので、これらの演算にも対応する必要があります。

 以上で全て網羅できていると思うし……、合っているとは思うけど……。
 数学の極限値計算(lim等)を踏襲した演算ルールみたいですが、なかなか難しくて正解が解りませんでした。CygwinのGCCでプログラムを組んで、実際に計算させてみた結果に基づいた、つ・も・り……(↓)。

  #include <stdio.h>
  int main (void)
  {
    float inf=1./0.;
    float nan=0./0.;
    printf ("%g %g %g\n",inf,nan, 123. - inf);
    return (0);
  }


 上コードは一例です。加減乗除算、±0、±A、±INF、NaNの組み合わせをすべて計算させて表示してみました。
 まあ、ここで設計したHFPUの演算結果が万一間違っていたとしても、as isで使ってください(無保証ですから〜)m(1__)m。

浮動小数点演算回路の基本設計

 以下、前節の算数に基づいて、浮動小数点演算回路の設計を進めて行きます。

ブロック図と、各ブロックの機能設計

 ここまでの検討に基づいて作成したブロック図は以下の通りです。

a165_hfpu.png

 以下、各ブロックの機能設計を進めます。

HFPU用コプロセッサコマンド

 CPUからのコマンド入力(ccmd[4:0])は、他のコプロセッサ(MULC16DIVC32)と共用なので、こちらのコプロセッサコマンドと競合しないよう、以下表の通り、コマンドを割り当てます。

ccmd[4:0] コード 内容
HADD 5'h11 half add: A+B
HSUB 5'h12 half subtract: A-B
HMUL 5'h13 half multiply: A*B
HDIV 5'h14 half divide: A/B
HNEG 5'h15 half negate: -A
HHLF 5'h16 cast uint16 to half: half (A)
HINT 5'h17 cast half to uint16: uint16 (abs (A))
HFRC 5'h18 half obtain fraction: uint16 (abs (A)*65,536)
HSGN 5'h19 half move sign: sgn (A)=sgn (B)
HSAT 5'h1a half saturate: INF, ZERO, NaN
HCMP 5'h1b half compare: C=NCVZ (flag for A-B)

 四則演算に留まらず、整数⇔half型浮動小数点数変換など、最低限必要な機能を割り当ててあります。


 今回はここまでで一旦閉じて、次回動作検証してHFPUとそれを搭載したMoscovium CPUを完成させます💥


btn_arduino.png

btn_prev.png btn_next.png

home