Vitis HLSの任意精度型ライブラリの使い方について解説

Xilinx SoC
スポンサーリンク

本記事の概要

ザイリンクスの統合ソフトウェア開発環境Vitisには、C言語やC++言語で記述されたプログラムをハードウェアIPに高位合成するVitis HLSが提供されています。これまで高位合成にはVivado HLSが用いられてきました。

前回の記事では、掛け算IPをVitis HLSで作成しZyboへ実装してみました。

今回の記事では、前回読み込んだ任意精度型ライブラリの使い方について解説したいと思います!

ひがし

それでは、興味のある方はぜひ最後までご覧ください!

この記事の対象読者
  • ハードウェア記述には馴染みがあるが、高位合成は初めてのハードウェアエンジニア
  • 高位合成によりソフトウェアの一部の機能をハードウェア化して処理を高速化したいソフトウェアエンジニア
  • FPGA初学者の学生

本記事の趣旨

任意精度型ライブラリの使い方について解説

任意精度型の特徴と使用するメリット

そもそも、任意精度型とは?

C/C++言語のデータ型

前提として、C/C++言語でデフォルトで用意されているデータ型の最小単位はバイト

C/C++言語では、変数を扱うために、いくつかのデータ型が用意されています。
例えば、変数を使うときにはデータ型と変数名を、下記のコードのように宣言します。

データ型 変数名;

データ型の例には整数を扱うchar型、int型や、小数を扱うfloat型などがあります。
char型は8ビット(1バイト)の2進数で構成され-128から127までの整数、int型は32ビット(4バイト)の2進数で構成され-2,147,483,648から2,147,483,647までの整数を表現しています。
いずれのデータ型も1バイトを最小単位に、複数のバイトで整数や小数、文字などを表現しています。

データ型大きさ範囲
char1バイト-128~127
short2バイト-32768~32767
int4バイト-2147483648~2147483647
float4バイト±1.2×10の-34乗~±3.4×10の34乗(有効数字7桁)
double8バイト±2.2×10の-308乗~±1.8×10の308乗(有効数字15桁)

このように、C/C++言語では1バイト(8ビット)刻みでデータ型が用意されており、メモリやCPUで操作・記憶するときの最小単位はバイトになります。

任意精度型ライブラリで追加されるデータ型

任意精度型ライブラリを追加で読み込めば、
1バイトよりも細かいビット数で変数を宣言できる

include文を使って追加で任意精度型ライブラリを読み込むことによって、1バイト(8ビット)よりも細かいビット数でデータ型を宣言することが可能になります。
例えば、下記のコードのように、任意精度整数型ライブラリを読み込んでみましょう。

#include <ap_int.h> //任意精度型ライブラリ

任意精度型ライブラリを読み込むことによって、以下の例のようなap_intというデータ型を宣言することができるようになります。ap_intでは、変数のビットサイズを設計者側で任意に変更することができる点が特徴です。

ap_int<4>    a;

例えば、上記の変数aを用いて4ビットの符号付き整数、つまり-8~7までの16種類(2の4乗)の数を表現できるようになります。

通常のint型と任意精度整数型の違い
  • int型:大きさ4バイト(32ビット)で固定
  • ap_int<N>型:大きさをNビットに設計者側で変更可能(N=1~1024)

なぜ任意精度にする必要があるか?

任意精度型を宣言する理由
  • レジスタのリソースは有限。FPGA上で必要十分な最低限のビット精度で論理合成したい。
  • C/C++言語におけるデフォルトのデータ型は1バイト(8ビット)単位。
  • 1バイトよりも細かいビット数で変数を高位合成できるように、任意精度型が用意されている。

たいていC/C++言語で作成したプログラムはマシン語に翻訳され、メモリから読み出されてCPU上で処理されます。
メモリやCPUはバイト(8ビット)ごとに処理されるため、8ビットずつ変数を定義しておいたほうが何かと便利です。

一方、FPGAでは、1ビットずつレジスタや配線を記述することができ、その柔軟性が一つの強みになっています。
しかし、高位合成でC/C++言語を回路記述に変換するときに、int型のように1バイトずつの固定精度型の変数しか扱えないと、常に8ビット単位で変数が合成されてしまい、強みである柔軟性を活かせなくなってしまいます
FPGAにおけるレジスタのリソースは有限のため、無駄にリソースを圧迫してしまう原因になります。
例えば、変数によっては、8ビットもの精度を必要とせず、数ビットだけあれば十分表現できてしまう場合もあります。
回路リソースの最適化のために、必要十分なビット精度で回路を合成できるように、任意精度型と呼ばれる、設計者側で任意にビット数を変更可能なデータ型が用意されています。

任意精度型の種類

ザイリンクスの任意精度型ライブラリで用意されている任意精度型は2種類あります。

任意精度型の種類
名称データ型includeヘッダ言語
任意精度整数型・ap_int<N>
・ap_uint<N>
#include “ap_int.h”C++
任意精度固定小数点型・ap_fixed<W,I,Q,O,N>
・ap_ufixed<W,I,Q,O,N>
#include “ap_fixed.h”C++

任意精度整数型

任意精度整数型で使えるデータ型には符号付きap_int符号なしap_uintの2種類があります。
例えば、4ビットの符号付きを選択すると-8~7までの16種類、符号なしを選択すると0~15までの16種類を扱うことができるようになります。

どちらも変数を宣言するには、ビットサイズNを決める必要があります。Nは1~1024までの範囲で指定することが可能です。

ひがし

ビットサイズは0-1の変数を格納する四角い箱を何個用意するかですね

ライブラリは、下記のコードのようにinclude文を使ってヘッダーファイルを追加し変数を宣言して使用します。

#include <ap_int.h> //任意精度型ライブラリ

int main()
{
    ap_int<2> a; //2-bit signed integer(2ビット符号付き整数)
    ap_uint<4> b; //4-bit unsigned integer(4ビット符号なし整数)
...
}

任意精度固定小数点型

任意精度固定小数点型にも、符号付きap_fixed符号なしap_ufixedの2種類があります。
任意精度整数型では整数のみを扱っていましたが、任意精度固定小数点型では小数を扱うことができるようになります。
任意精度整数型では変数の宣言には指定する識別子はビットサイズNのみだけだったのですが、任意精度固定小数点型の変数を宣言するには、5つの識別子W,I,Q,O,Nを指定することができます。

任意精度固定小数点型で指定する5つの識別子
識別子説明備考
W変数全体のビット数
I整数部分のビット数
Q量子化モード(丸めるときの動作を指定)デフォルトはAP_TRN
(負の無限大への切り捨て)
Oオーバーフローモード(オーバーフローしたときの動作を指定)デフォルトはAP_WRAP
(折り返し)
Nオーバーフロー折り返しモードの場合の飽和ビット数

本記事では、5つの識別子のうち、WとIについて解説します。
他の3つの識別子Q, O, Nは特に値を指定せずにデフォルトのまま使用することもできるので、本記事では説明を省略します。より詳しく理解したい方はザイリンクスのリリースノートを参照ください。

基本的には、識別子Wが変数全体のビット数、識別子IがW個のビットのうち整数部分のビット数、残りのW-I個(図中B=W-I)のビットは小数部分を表しています。

ひがし

Wはビットサイズで四角い箱を何個用意するかです。
Iはそのうち整数部分の青い箱を何個用意するかというイメージをまず持っておくと良いかと思います。

W=2, I=0の任意精度型固定小数点の符号付き変数cを例に、変数が取りうる範囲を確認しましょう。

ap_float<2, 0>    c;

整数部分が0、小数部分が2の符号付き変数ですので、図のように-1/2~1/4までの間の小数を1/4刻みで表現することが可能になります。

ただ、注意すべき点として整数部分のビット数Iの値には負の数を入れることも可能です。
この場合は、図のように小数部分の途中から数が表現されます。

ひがし

Iが負になると、さきほどの青い箱の数というイメージとずれてきてしまいました。そこで、私は小数点の位置を基準にして、変数をどの位置から表現するかという視点で見るようにしています。例えば、Iが+5なら整数4桁目(5-1)から、Iが-1なら小数点第2位((-1)-1)から数を表現するという風にです。

例を使ってみていきましょう。

W=2, I=-1の任意精度型固定小数点の符号付き変数dを例にとります。

ap_float<2, -1>    d;

まず、小数第1位(I=-1)の次の小数第2位(I-1)から、そこからW(=2)ビット分の小数第3位まで(B=3)を表現しています。
図のように-1/4~1/8までの間の小数を1/8刻みで表現することが可能になります。

小数第1位の値はこの変数では表現できないので、最上位のビットの値を詰めて処理されます。例えば、正の数であれば0詰め、負の数であれば1詰めで処理されます。

任意精度整数型の使い方

通常の加減乗除、シフト演算、関係演算子など

任意精度整数型の変数は、通常のint型などと同様に加減乗除やシフト演算、関係演算子による比較などの演算を行うことが可能です。

任意精度型でもint型同様に定義される演算
演算記号
加減乗除X+Y 加算, X-Y 減算, X*Y 乗算, X/Y 除算, X%Y 剰余
論理演算子X|Y ビット単位OR, X&Y ビット単位AND, X^Y ビット単位XOR
単項演算子+X プラス符号をつける, -X マイナス符号をつける, ~X ビット単位の逆, !X 論理の反転
三項演算子d?X:Y dがtrueならばX、falseならばY
シフト演算子X<<Y 右シフト、X>>Y 左シフト
複合代入演算子+=など
関係演算子== 等号、!= 等号否定、< 小なり、> 大なり、<= 以下、>= 以上

任意精度整数型ap_intの便利なメソッドの紹介

ap_int型/ap_uint型には上記の一般的な演算以外にも、便利なメソッドが含まれています。
その一部を紹介していこうと思います。

メソッド名使い方返り値
連結Val1.concat(Val2)
(Val1, Val2)
2変数をビット連結した値Val1=101,Val2=001のとき
Val1.concat(Val2) = 101001
(Val1, Val2) = 101001
ビット選択Val1[bit]選択した1ビットの値Val1=10101のとき
Val1[0] = 1, Val1[1] = 0, Val1[2] = 1
範囲選択Val1.range(Hi, Lo)
Val1(Hi, Lo)
引数で指定されるビットの範囲で表される値Val1=110101のとき
Val1.range(4, 2) = 101
Val1(3, 1) = 010
Val1(3, 5) = 011(※)
※LoがHiより大きいとき逆順になる
ビットリダクションVal.and_reduce()
Val.or_reduce()
など
すべてのビットにAND演算やOR演算を適用するVal=10のとき
Val1.and_reduce()=1 And 0 = 0(false)
明示的な型変換Val.to_int()
Val.to_int64()
Val.to_double()
など
C/C++のint変数やdouble変数を返す
※値がintやdoubleで表示される範囲よりも大きい場合は切り捨て
ひがし

いくつか便利なメソッドが用意されています。使用時の注意点や具体例については、本記事でまとめきれなかったので、詳細は以下のリリースノートをご覧ください。

AMD Technical Information Portal

まとめ

以上をまとめると、この通りです。

本記事のまとめ
  • 任意精度型ライブラリを読み込むことによって、1バイトよりも細かいビット数で変数を宣言できる
  • FPGAで必要十分な最低限のビット精度で論理合成したいときに有用
  • 任意精度型には任意精度整数型任意精度固定小数点型があり、どちらも通常のintやfloat同様の演算が可能。メソッドも充実している。
ひがし

本記事執筆にあたりリリースノートをじっくり読んだところ、有用なメソッドが数多く準備されていることに気づきました。どんどん活用していきましょう!

ここまで読んでいただき、ありがとうございました。

参考資料

AMD Technical Information Portal
AMD Technical Information Portal

コメント