【Vitis HLS入門編(1)】高位合成の方法について解説 -掛け算IPをZyboに実装-

Xilinx SoC
スポンサーリンク

本記事の概要

Vitisには、C言語やC++言語で記述されたプログラムをハードウェアIPに高位合成するVitis HLSが提供されています。これまで高位合成にはVivado HLSが用いられてきましたが、2019年からはVitis HLSに移行しているようです。

高位合成とは

回路の機能を、VerilogやVHDLといったハードウェア記述言語ではなく、C言語やC++言語といった高級プログラミング言語を用いて記述し、ツールを用いてハードウェア記述言語で構成されるIPに変換すること。

高位合成を行う利点は
・レジスタ転送のタイミングを意識することなく、回路の機能のみに集中して設計が可能
・高級プログラミング言語の段階で検証やデバッグが可能

ひがし

ハードウェア記述言語に慣れている人もそうでない人も、検証やデバッグをラピッドに行い、開発工数を削減できるのが利点だと思います。

デメリットは、変換後のハードウェアIPにおけるタイミングやリソースを思ったとおりに最適化してくれないなどの、かゆいところに手が届きにくいところかと思います。

今回は、「Vitis」での開発環境に慣れ親しんでおくために、今回の記事では掛け算IPをVitis HLSで作成し、Zyboへ実装してみました。

ひがし

IPを作るだけだとうまく出来ている実感があまり湧かないので、実際にFPGAに実装するところまで実施しています。

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

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

※Vitis HLSにはVivado HLSに比べて使いづらいところやWindows版にバグの噂もあるようです。本サイトでは高位合成にはVitis HLSを使用していき、使い勝手の悪いところなどは適宜本サイトで報告していきたいと思います。

本記事の目標

本記事では、高位合成の記事第一回目とのことで、基本のLED点滅回路を作成していきます。
よくチュートリアルで紹介される、掛け算を行う関数をハードウェアIPに変換します。
回路実装では、スイッチからの入力を掛け算してLEDに出力しています。この方法で、実際に動作を確認しました。

今回の記事では、まず高位合成を実感することを意識し、pragma構文で指定するリソースやタイミングなどの最適化指示子(ディレクティブ)は使わずに進めようと思います。
※pragmaについては別記事で解説します。

Vitis HLSは最初プログラミング大全を参考にしています。
HLSだけでなく、ZynqやMicroblazeなどのプロセッサを活用したシステム開発の方法が丁寧に記載されているので、初学者がさらにステップアップするのに非常に素晴らしい書籍だと思います。

スイッチからの入力を掛け算してLEDに出力するLED点滅回路を高位合成により作成し、
Zybo回路基板に実装・動作検証を行うこと。

開発工程

開発工程

① Vitis HLSにより掛け算を行う関数をハードウェアIPに高位合成
② 高位合成したハードウェアIPをZybo基板に実装

システムの構成

開発環境

環境
  • 開発用PC: Windows 10, 64bit
    • Vivado Design Suite – HLx Edition – 2020.2
    • Vitis コア開発キット – 2020.2
  • 開発用基板: Zybo Zynq-7010評価ボード(Board Rev.4)
    • Zynq XC7Z010-1CLG400C

開発ボード Zybo Zynq-7010評価ボード

動作原理

① スイッチ4個から2ビットの信号を2個受信(それぞれをA[1:0]、B[1:0]とする)
② 掛け算IPでA[1:0]×B[1:0]を計算し、最大4ビットの信号C[3:0]を出力
③ LED4個にC[3:0]を出力し、LEDを点灯させる

ひがし

本開発では、上の図のようにProgrammable Logic (PL)しか使っていません。
そのため、ZyncのようなCPU内蔵型のFPGAを使用しなくてもよいのですが、手元ですぐ使えそうなZybo基板を使用しました。

Vitis HLSプロジェクトの作成

プロジェクトの前準備

まずは、Vitis HLSを立ち上げ、[Create Projet]をクリックして新しいプロジェクトを作成します。

プロジェクトの名前やディレクトリを指定するウィンドウが立ち上がりますので、[Project Name]と[Location]を入力して、[Next]をクリックします。

次に、合成するCソースコードを指定するウィンドウに移行します。
今回の記事ではVitis HLS上でソースコードをゼロから作成しますので、ここでは何も指定せずに[Next]をクリックします。

次に、テストベンチファイルを指定するウィンドウに移行しますが、こちらも同様の理由で何も指定せずに[Next]をクリックします。

最後に、FPGAチップを選択するウィンドウに移行します。
[…]をクリックし、新たに開いたウィンドウ上で、[Boards]タブ>[Zybo]と選択します。
選択が完了したら、[OK]と[Finish]をクリックして、Vitis HLSの立ち上げを完了します。

ひがし

Zyboの設定ファイルの読み込み方法は以下の記事をご覧ください。

ひがし

以上の手続きでVitis HLSのプロジェクトの作成は完了です。次の図のようなウィンドウが立ち上がるはずです。
このプロジェクト上でCソースコードを作成し、高位合成を行っていきます。

C、C++コードの作成

では、実際にCコード、あるいはC++コードを作成していきましょう。
高位合成では大きく分けて2種類のコードを作成します。

Vitis HLSで作成する2種類のコード
  1. ソースコード:実際に高位合成してハードウェアIPに変換する関数
  2. テストベンチ:ソースコードで作成した関数をシミュレーションして、所望の機能を満足しているかどうかを確認するための関数(最上位はmainとする)

ソースコードとテストベンチで作成する関数のイメージ図はこのような感じです。
実際に高位合成する対象をソースコードに作成します。
そして、そのソースコードが所望の動作をしているかどうかを、高位合成する前にシミュレーションにより確かめていきます。
このとき、ソースコードとは別に、うまく動作しているかどうかを確かめるための参照関数をテストベンチ上で作成して、出力が一致するかどうかを比較して確認することが多いです。

ひがし

参照関数をテストベンチ上に作成することは任意だと私は考えています。

例えば、高位合成する対象の関数から出力された値を見れば、ソースコードの動作が正しいと判断できる場合はそちらを見ても良いと思います。あくまでも、比較処理は動作検証を楽にするための手法の一つだと思います。

シミュレーションに関して、Vitis HLSでは2種類のシミュレーションが用意されています。

Vitis HLSで用意されている2種類のシミュレーションツール
  1. Cシミュレーション:作成したソースコードがソフトウェアとしての機能を満足していることを確認する高位合成前に行うシミュレーション。
  2. C/RTL協調シミュレーション:高位合成後のハードウェア記述について、CシミュレーションとRTLシミュレーションを協調して行い、結果を比較検証することによってハードウェア記述のRTL記述が正しいことを確認する高位合成後に行うシミュレーション
ひがし

この2種類のシミュレーションをうまく活用しながら、ソースコードと合成後のハードウェアIPの機能が所望の動作をしていることをラピッドに検証できる点が、高位合成ツールを活用するメリットだと思います。

高位合成の作業手順は以下の図の通り行うのが効率的だと思います。

まずCシミュレーションを繰り返してソースコードが所望の機能を満足するように作り込みを行います。
その後、高位合成を行った後に、C/RTL協調シミュレーションを走らせて、高位合成後のハードウェア記述が所望の動作を行っていることを確認します。協調シミュレーションでは、全体のレイテンシーやリソースも見つつ、ハードウェア記述の最適化を行っていきます。

ひがし

上記の作業手順はあくまでも一例で、例えばC/RTL協調シミュレーションを行ったあとに、ソースコードの作り込みが必要だと気づくことも多いと思います。
ディレクティブの付与のタイミングも、Cシミュレーション前にやってしまっても良いでしょう。
ただ、おそらくCシミュレーションの結果はディレクティブの付与の仕方で変わるものではないと思うので、一旦Cソースコードを作ってしまってから適宜付与したほうが効率的かなと思います。

では、前置きが長くなりましたが、実際にソースコードとテストベンチを作成していきましょう。

高位合成で変換するCコード

[Explorer]>[Source]を右クリックし、[New File]を選択します。
ファイルを選択するウィンドウが立ち上がるので、適切な名前でファイルを作成します。
ここで、拡張子を.cppにすれば、C++のコードが作成されます。

今回、作成したソースコードを以下に記しておきます。

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

void swtoled(ap_uint<2> *sw1_ip, ap_uint<2> *sw2_ip, ap_uint<4> *led_op) {

    *led_op = *sw1_ip * *sw2_ip;
}
ひがし

入力したsw1_ipとsw2_ipを単に掛け算して、led_opに出力するだけの関数swtoled()です。
読み出している任意精度型ライブラリについては、別記事に記載しておきます。

テストベンチ関数

次に、テストベンチ関数を作成していきましょう。
ソースコード同様に、[Explorer]>[Test Bench]を右クリック[New File]を選択して、ファイルを新しく作成します。

今回、作成したソースコードを以下に記しておきます。

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

void swtoled(ap_uint<2> *sw1_ip, ap_uint<2> *sw2_ip, ap_uint<4> *led_op);

int main()
{
    int			aloop, bloop;
    ap_uint<2> a, b;
    ap_uint<4> c_soft, c_hard;

    int fail_num = 0;
    const int test_num = 16;

    for (aloop = 0; aloop <= 3; aloop++) {
    	for (bloop = 0; bloop <= 3; bloop++) {
    		//-- soft test bench start --
    		c_soft = aloop * bloop;
    		//-- soft test bench finish --

    		//-- hard test bench start --
    		a = (ap_uint<2>)aloop;
    		b = (ap_uint<2>)bloop;
    		swtoled(&a, &b, &c_hard);
    		//-- hard test bench finish --

    		//-- verification start --
    		if (c_hard != c_soft) {
    			fail_num += 1;
    		}
    		//-- verification finish --
    	}
    }

	if (fail_num != 0) {
        std::cout << "FAIL" << std::endl;
        return -1;
	}else{
        std::cout << "SUCCESS" << std::endl;
        return 0;
	}
}

テストベンチ関数では、A(0~3)とB(0~3)すべての組み合わせについてA×Bを試してみて、テストベンチ上の掛け算の結果とソースコード関数による掛け算の結果が完全一致するかどうかを検証しています。

Cシミュレーション

では、作成したソースコードとテストベンチ関数を用いて、シミュレーションを行います。
前準備として、作成したソースコードのうち、どの関数を高位合成するのかを設定しておきます。
Vitis HLSのプロジェクト上で[Project]タブの[Project Settings…]を選択します。

新たに立ち上がったウィンドウで、左の枠内で[Synthesis]を選択し、そのTop Functionに作成した関数名[swtoled]を入力します。

この作業まで完了したら、左上の矢印マークから[C Simulation]を選択します。

そうすれば、以下のウィンドウが立ち上がるので、そのままOKをクリックすれば、Cシミュレーションが行われます。

結果を見てみましょう。
コンソール画面には色々出ていますが、[SUCCESS]の文字が表示されています。これで問題なく所望の機能を満足していることを確認できました。

高位合成(C synthesis)

次に、高位合成をしましょう。
左上の矢印マークから[C Synthesis]を選択します。

自動的に高位合成が始まり、特に問題なければ、図のように高位合成の結果が出力されます。

ひがし

順調に高位合成が進んでいますね。
ここで、合成後のハードウェア記述のタイミングやリソースなどを確認します。

C/RTL協調シミュレーション(Co-simulation)

作成したハードウェア記述の動作検証を、C/RTL協調シミュレーションで行いましょう。
左上の矢印マークから[Co-Simulation]を選択します。

以下のウィンドウで、Dump Traceを[all]に選択しておくと、RTLシミュレーション上で各変数のタイミング信号を確認することが可能です。
選択が完了しOKをクリックすれば、協調シミュレーションが行われます。

無事に協調シミュレーションが完了したようです。

RTLシミュレーションのタイミング信号の結果は、上にある波マーク(Open Wave Viewer…)をクリックすれば確認することが可能です。

sw1_ipとsw2_ipを掛け算した結果が、レイテンシ0でled_opから出力されていることがわかり、問題なく動作している様子を確認できます。

ハードウェアIPの作成

では、最後に高位合成したハードウェア記述から、Vivadoに取り込むためのハードウェアIPを作成していきます。
左上の矢印マークから[Export RTL]を選択します。

新しくウィンドウが立ち上がるので、そのまま[OK]をクリックすれば、IPが作成されます。

ひがし

以上で、高位合成は完了です!
では、Vivadoプロジェクトを作成して動作を確認していきましょう。

Vivadoプロジェクトの作成

ブロックデザインの作成

Vivadoプロジェクトを作成し、先程高位合成したハードウェアIPを読み込みます。
図のように、[IP Catalog]をProject Managerから選択し、クリックします。
[IP Catalog]上で、[Vivado Repository]を右クリックし、[Add Repositiory…]を選択します。

作成したハードウェアIPを選択します。
ハードウェアIPはVitis HLSプロジェクトの[solution1]>[impl]>[ip]フォルダに格納されているので、それを選択します。

レポジトリに追加されたら、次にブロックデザインに追加していきましょう。
ブロックデザインを立ち上げたら、右クリックで[Add IP]を選択し、作成したIPを選びます。

ブロックデザインにSWtoLEDが追加されたら、さらにSliceというIPコアを追加します。
このIPは入力した信号の一部のビットを切り出して、出力するIPです。

IPコアSlice

入力バスから一部のビットを除去して、残りのビットを出力するIP

このIPを使って、スイッチからの4bit入力を2つに分割しました。
Sliceの設定は次の図のとおりにしています。

作成したIPを配線で接続し、外部入出力ポートをつなぎ、ブロックデザインを作成することができました。

あとは、このブロックデザインからwrapperファイルを作成し、論理合成していきましょう。
制約ファイルはDigilent社から提供される、ZYBO_Master.xdcを使用することにしました。

論理合成からbitファイル作成まで

それでは、ブロックデザインの作成が完了したので、論理合成からbitファイルの作成までを一気に行っていきます。

以前の記事と重複する内容になりますので、改めて確認したい方は[+]マークから詳細をご確認ください。

1. ブロックデザインからHDL記述への変換

読み込んだブロックデザインをもとにHDL(ハードウェア言語)を作成します。
ブロックデザインのデザイン名[design_1.bd]を右クリックし、[Create HDL Wrapper …]を選択します。

2. 論理合成、配置配線、bitstreamファイルの作成

[Generate Bitstream]をクリックし、論理合成、配置配線、bitstreamファイルの作成までを行います。

実装と動作検証

bitファイルの実装

Zybo基板をPCにUSB接続し、bitファイルを実装します。
実装の方法はいくつかありますが、今回はVivado上で行います。

Bitstreamファイルの作成が完了したら、[Open Hardware Manager]をクリックし、[Open Target]>[Auto Connect]を選択します。
下の図のように、上部に緑色のバーが現れるので、そこの[Program Device]を選択します。
bitファイルを選択するウィンドウが立ち上がるので、Programをクリックして実装を開始します。

動作検証の結果

Zybo上でスイッチを入れて、掛け算を行っていきましょう。

① SW1 = 01 (10進数で1)、SW2 = 01 (10進数で1) → LED = 0001 (10進数で1)

② SW1 = 11 (10進数で3)、SW2 = 01 (10進数で1) → LED = 0011 (10進数で3)

③ SW1 = 11 (10進数で3)、SW2 = 10 (10進数で2) → LED = 0110 (10進数で6)

④ SW1 = 11 (10進数で3)、SW2 = 11 (10進数で3) → LED = 1001 (10進数で9)

ひがし

いくつかの組み合わせを試してみましたが、うまく計算できてそうですね。
これで高位合成したハードウェアIPの動作検証は完了です。

まとめ

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

本記事のまとめ

本記事では、

  • 掛け算を行う関数をハードウェアIPに高位合成し、さらに高位合成の結果を実感するためにLED点滅回路に実装。
  • 回路実装では、スイッチからの入力を掛け算してLEDに出力して、掛け算IPの動作を確認。
ひがし

高位合成は奥が深いので、長期に渡って記事にまとめていきたいと思います!

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

参考資料

https://japan.xilinx.com/support/documentation/sw_manuals_j/xilinx2020_1/ug1399-vitis-hls.pdf

コメント