本記事の概要
これまでFPGAを用いたシステム開発には、ハードウェア設計にVivado、ソフトウェアアプリケーションの開発にXilinx SDKを用いられてきました。
しかし、2019年以降、ソフトウェアアプリケーションの開発は、Xilinx SDKから統合ソフトウェア開発環境「Vitis」へと移行しています。
「Vitis」での開発環境に慣れ親しんでおくためにも、前回の記事で作成した”Hello World”アプリケーションを応用し”LED点滅”アプリケーションを、Vitis 2020.2で作成しました。
今回の記事では、統合ソフトウェア開発環境VitisとXilinx FPGA向け設計ツール Vivadoを用いて、開発ボードZybo (搭載SoCチップ Zynq)に実装する”LED点滅”アプリケーションを作成する方法についてまとめます。
前回の記事「Vitis 2020.2で”LED点滅”プログラムを実行してみた – (1)ハードウェア構成 –」では、既にVivadoを用いて、Zynq SoCのハードウェア設計を完了しました。
今回の記事では引き続き”LED点滅”アプリケーションのプログラムを作成していきます。
それでは、興味のある方はぜひ最後までご覧ください!
目標と工程
開発目標
本記事の目標は次の通りです:
統合ソフトウェア開発環境VitisとFPGA設計環境Vivadoを用いて
Zybo上の”LED”を点滅させるアプリケーションを作成する
開発工程
システムの構成
開発環境
開発ボード Zybo Zynq-7010評価ボード
Vitisプロジェクトの作成
Vitis 2020.2のワークスペースの作成
“Hello World”アプリケーションを作成したときと同様に、Vitisのワークスペースを作成していきます。
ワークスペースの構成については、次の記事を参照ください。
ワークスペースの作成手順をまとめると、次の通りです。
前回の記事と重複する内容になりますので、改めて確認したい方は[+]マークから詳細をご確認ください。
1. Vitis 2020.2の立ち上げ
まずはブロックデザインを作成したVivadoファイルを立ち上げ、[Tools]>[Launch Vitis IDE]を選択します。
2. Workspaceディレクトリの指定
立ち上がったウィンドウ上で、Workspaceを格納するディレクトリを指定します。指定が完了したら、[Launch]をクリックします。
3. アプリケーションプロジェクトの作成開始
新しくVitisのウィンドウが立ち上がります。[Create Application Project]をクリックし、新しいアプリケーションプロジェクトを作成していきます。
4. ハードウェアデザイン(XSAファイル)の読み込み
最初のウィンドウではそのまま[Next]をクリックします。
Vivadoで作成したハードウェアデザイン(XSAファイル)を読み込んでいきます。
[Create a new platform from hardware (XSA)]タブを選択し、[Browse]をクリックし、前回の記事で作成した“hello_world_system_wrapper.xsa”を選択します。
選択が完了したら、[Next]をクリックします。
5. アプリケーションプロジェクト名の指定
アプリケーションプロジェクト名を入力し、アプリケーションを動作させるプロセッサの種類を選択したら、[Next]をクリックします。
6. ドメインの指定
今回は特に設定変更不要ですので、そのまま[Next]をクリックします。
7. アプリケーションプロジェクトのテンプレートの読み込み
今回はゼロからプログラムを作成していきますので、“Empty Application”を選択し、[Finish]をクリックします。
Vitis IDEのウィンドウの立ち上げまで完了したら、このワークスペース上で、”LED点滅”プログラムを作成していきましょう。
LED点滅プログラムのソースコードの作成
はじめに、プログラムのソースコードをワークスペースに追加します。
Vitis workspace上のExplorer欄に、新規にソースコードを追加します。
Explorer内の[src]を右クリックし、[New]>[File]と選択します。
このウィンドウ上で、新規に作成するファイル名を指定します。今回は、ファイル名を[led.c]としました。ポイントは、拡張子を[***.c]としておくことです。
指定し終わったら、[Next]をクリックしましょう。
追加した[led.c]は何も書かれていない空白のファイルです。ここにソースコードを書いていきます。
まずは、全ソースコードを掲載しておきます。
#include "xparameters.h"
#define LED *((volatile unsigned int*) (XPAR_GPIO_0_BASEADDR + 0x00))
#define LED_ctrl *((volatile unsigned int*) (XPAR_GPIO_0_BASEADDR + 0x04))
int main()
{
LED_ctrl = 0x0;
LED = 0x1;
return 0;
}
ソースコードの解説
前項で掲載したソースコードの意味を順に解説していきます。
① GPIOのレジスタアドレスの定義
#include "xparameters.h"
#define LED *((volatile unsigned int*) (XPAR_GPIO_0_BASEADDR + 0x00))
#define LED_ctrl *((volatile unsigned int*) (XPAR_GPIO_0_BASEADDR + 0x04))
まずは、ソースコード冒頭に記載している#include文と#define文について、順に見ていきましょう。
#include 文で読み出している”xparameters.h”とは?
#include文では、”xparameters.h”というヘッダーファイルを読み込んでいます。
この”xparameters.h”は「どこに」格納されていて、「何を」記載しているのでしょうか?
まず、「どこに?」ですが、プラットフォームプロジェクト(led_blink_system_wrapper)の中に格納されています。
ワークスペースを作成するときに、XSAファイルと呼ばれるハードウェアのデザインを読み込みました。そのときに、自動でこのヘッダーファイルが生成されているようです。
では、次に、”xparameters.h”は「何を?」記載しているかについて確認しましょう。ヘッダファイルを開くと、例えば、
/* Canonical definitions for peripheral AXI_GPIO_0 */
#define XPAR_GPIO_0_BASEADDR 0x41200000
#define XPAR_GPIO_0_HIGHADDR 0x4120FFFF
#define XPAR_GPIO_0_DEVICE_ID XPAR_AXI_GPIO_0_DEVICE_ID
#define XPAR_GPIO_0_INTERRUPT_PRESENT 0
#define XPAR_GPIO_0_IS_DUAL 0
と記載されています。
例えば、[XPAR_GPIO_0_BASEADDR]という変数を定義し、その変数に[0x41200000]という数値を割り当てています。
“AXI GPIO”のレジスタアドレスとは?
[XPAR_GPIO_0_BASEADDR]や数値[0x41200000]の意味は一体何でしょうか?ハードウェア構成のときに”AXI GPIO”というIPをブロックダイアグラムに追加しました。この数値は”AXI GPIO”における各レジスタのベースアドレスを表しています。
まずは、LEDと接続されている”AXI GPIO”のレジスタ構成について確認していきましょう。
レジスタ構成については、Xilinx社のAXI GPIOに関するデータシートAXI GPIO v2.0 LogiCORE IP Product GuideのP10″Register Space”に詳細に記載されています。
データシートに基づき、AXI_GPIOのレジスタ空間の構成を整理しました。
あるベースアドレスに対して、オフセットを加えて、データレジスタやコントロールレジスタのアドレスとしているようです。
例えば、[AXI GPIO]というIPは4個のLEDに4bitのデータを出力していました。この4bitのデータを格納するデータレジスタを示すアドレスは「ベースアドレス+オフセット0x0000」ということになります。
では、[AXI GPIO]のベースアドレスの値は何なのか?
ベースアドレスの確認方法は2通りあります。
まず、1つ目の方法についてです。
先程の[xparameters.h]の中身を見たときに答えが書かれています。
変数[XPAR_GPIO_0_BASEADDR]に値[0x41200000]を割り当てていましたが、まさにこれが[AXI GPIO]のベースアドレスとなります。
#define XPAR_GPIO_0_BASEADDR 0x41200000
別の方法は、Vivadoで作成したデザインから確認が可能です。
Vivadoのデザイン上で[Address Editor]とよばれるタグがあるので、それをクリックします。すると、[/axi_gpio_0/S_AXI]という名前のバスがあるので、その”Master Base Address”を確認しましょう。[0x4120_0000]という値が格納されているはずです。
例えば、GPIOの出力(今回はその先のLED)にデータを送るためには、ベースアドレス[XPAR_GPIO_0_BASEADDR(=0x4120_0000)]にオフセット[0x0000]を足したアドレスのレジスタに値を格納すればよいことになります
では、[led.c]の冒頭のソースコードに戻ります。
#include "xparameters.h"
#define LED *((volatile unsigned int*) (XPAR_GPIO_0_BASEADDR + 0x00))
#define LED_ctrl *((volatile unsigned int*) (XPAR_GPIO_0_BASEADDR + 0x04))
ヘッダーファイル”xparameters.h”には、システム上の各レジスタのベースアドレスなどが格納されていました。#include文はこのヘッダーファイルを読み出しています。
次の#define文では、[LED]と[LED_ctrl]という名称の変数を定義しています。
各変数には、ベースアドレス[XPAR_GPIO_0_BASEADDR(=0x4120_0000)]にオフセット[0x00]や[0x04]を足したアドレスが指し示すレジスタを、ポインタを使って割り当てています。
この冒頭の定義文によって、新しく定義した[LED]と[LED_ctrl]という変数を用いて、GPIOのデータレジスタとコントロールレジスタに値を格納することが可能になりました。
② レジスタへの値の代入
では、続くソースコードを確認しましょう。
int main()
{
LED_ctrl = 0x0;
LED = 0x1;
return 0;
}
ソースコード本文になります。
まず、コントロールレジスタ[LED_ctrl]に値[0x0]を格納しています。
Xilinx社のAXI GPIOに関するデータシートAXI GPIO v2.0 LogiCORE IP Product GuideのP12″AXI GPIO 3-State Control Register (GPIOx_TRI)”に、各値の説明が掲載されています。
今回は、AXI GPIOを介してLEDにデータを送ることが目標です。そこで、ソースコード上ではコントロールレジスタ[LED_ctrl]には[0x0]を格納しています。
次に、データレジスタ[LED]に値[0x1](=2進数表記で”0001″)を格納しています。
接続している4つのLEDのうち、右から一つ目のLEDを点灯させることを指示しています。
ここまででプログラムの作成は完了です。
では、このプログラムのもと、実際に回路基板上でLEDを点灯させてみましょう。
実装と動作確認
LEDの点灯
実装までの手順は、前回の”Hello World”のプログラムと変更ありません。
改めて確認したい方は[+]マークから詳細をご確認ください。
①プロジェクトのビルド
まず、プラットフォームプロジェクトをビルドしていきます。
エクスプローラ内のプラットフォームプロジェクト(led_blink_system_wrapper)を選択し、右クリックします。[Build Project]を選択すると、ビルドが始まります。
次に、システムプロジェクトをビルドしていきます。
エクスプローラ内のシステムプロジェクト(led_blink_application_system)を選択し、右クリックします。同様に[Build Project]を選択すると、ビルドが始まります。
②コンフィグレーション
回路基板をホストPCにつなぎます。
つないで電源を入れたら、PC側からFPGAへのbitstreamファイルの書き込み(コンフィグレーション)とアプリケーションソフトウェアのDDRへの書き込みを実行します。
“led_blink_application_system”を右クリックし、[Debug as]>[Launch Hardware]を選択すると、コンフィグレーションとソフトウェアの書き込みが実施されます。
今回は、UARTの信号を受信しないので、ホストPCとのCOMポートの設定は必要ありません。
そのまま、[Resume]アイコンをクリックしてプログラムを実行しましょう
見事に右から1つ目のLEDのみが点灯しました。
少しプログラムを変更してみましょう。
GPIOのデータレジスタ(変数名[LED])に“0x3″(2進数表示で0011)を代入してみます。
#include "xparameters.h"
#define LED *((volatile unsigned int*) (XPAR_GPIO_0_BASEADDR + 0x00))
#define LED_ctrl *((volatile unsigned int*) (XPAR_GPIO_0_BASEADDR + 0x04))
int main()
{
LED_ctrl = 0x0;
LED = 0x3;
return 0;
}
右から1番目と2番目のLEDが点灯すれば成功です!
LEDの点滅
LEDを点滅させるということを行いたいと思います。
これまでは定常的にLEDを点灯させるのみでしたが、点滅となると時間変化を伴う動的な制御が必要になります。
そこで、プログラム上ではwhileループと時間遅延を設ける構文を追加します。
#include "xparameters.h"
#define LED *((volatile unsigned int*) (XPAR_GPIO_0_BASEADDR + 0x00))
#define LED_ctrl *((volatile unsigned int*) (XPAR_GPIO_0_BASEADDR + 0x04))
int main()
{
int i, j;
LED_ctrl = 0x0; //write to control register
while(1) {
for ( i=0; i<2; i++ ) {
//switching on and off
if(i==0){
LED = 0x1; //on
}else{
LED = 0x0; //off
}
for ( j=0; j<100000000; j++); //Latency
}
}
return 0;
}
“while(1){ }”により常にループを回し続け、iに関するforループでLEDのONとOFFを切り替えています。
jに関するforループは、jを100メガ回足し続けることによって、whileループの1ループの時間が長くなるようにしています。
このプログラムを実装し、LEDを動かした動画がこちらです。
およそ1秒間隔で右から1番目のLEDが点滅しているのがわかります。
LEDがピカピカ光ると達成感が得られて、楽しいですね。
電子工作の醍醐味の一つだと思います!!
やはり、実際にLEDが点滅する様子を見ると、プログラムを動かしている実感がわきますね!
ここまで読んでいただき、ありがとうございました。
コメント