本記事の概要
ザイリンクスの統合ソフトウェア開発環境Vitisには、C言語やC++言語で記述されたプログラムをハードウェアIPに高位合成するVitis HLSが提供されています。これまで高位合成にはVivado HLSが用いられてきました。
前回の記事では、掛け算IPをVitis HLSで作成しZyboへ実装してみました。
今回の記事では、前回読み込んだ任意精度型ライブラリの使い方について解説したいと思います!
それでは、興味のある方はぜひ最後までご覧ください!
本記事の趣旨
任意精度型ライブラリの使い方について解説
任意精度型の特徴と使用するメリット
そもそも、任意精度型とは?
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バイトを最小単位に、複数のバイトで整数や小数、文字などを表現しています。
データ型 | 大きさ | 範囲 |
char | 1バイト | -128~127 |
short | 2バイト | -32768~32767 |
int | 4バイト | -2147483648~2147483647 |
float | 4バイト | ±1.2×10の-34乗~±3.4×10の34乗(有効数字7桁) |
double | 8バイト | ±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乗)の数を表現できるようになります。
なぜ任意精度にする必要があるか?
たいていC/C++言語で作成したプログラムはマシン語に翻訳され、メモリから読み出されてCPU上で処理されます。
メモリやCPUはバイト(8ビット)ごとに処理されるため、8ビットずつ変数を定義しておいたほうが何かと便利です。
一方、FPGAでは、1ビットずつレジスタや配線を記述することができ、その柔軟性が一つの強みになっています。
しかし、高位合成でC/C++言語を回路記述に変換するときに、int型のように1バイトずつの固定精度型の変数しか扱えないと、常に8ビット単位で変数が合成されてしまい、強みである柔軟性を活かせなくなってしまいます。
FPGAにおけるレジスタのリソースは有限のため、無駄にリソースを圧迫してしまう原因になります。
例えば、変数によっては、8ビットもの精度を必要とせず、数ビットだけあれば十分表現できてしまう場合もあります。
回路リソースの最適化のために、必要十分なビット精度で回路を合成できるように、任意精度型と呼ばれる、設計者側で任意にビット数を変更可能なデータ型が用意されています。
任意精度型の種類
ザイリンクスの任意精度型ライブラリで用意されている任意精度型は2種類あります。
任意精度整数型
任意精度整数型で使えるデータ型には符号付き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について解説します。
他の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型などと同様に加減乗除やシフト演算、関係演算子による比較などの演算を行うことが可能です。
任意精度整数型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で表示される範囲よりも大きい場合は切り捨て | – |
いくつか便利なメソッドが用意されています。使用時の注意点や具体例については、本記事でまとめきれなかったので、詳細は以下のリリースノートをご覧ください。
まとめ
以上をまとめると、この通りです。
本記事執筆にあたりリリースノートをじっくり読んだところ、有用なメソッドが数多く準備されていることに気づきました。どんどん活用していきましょう!
ここまで読んでいただき、ありがとうございました。
コメント