Zynq搭載ボードの決定版“Eclypse Z7”で始める
高速信号処理システム開発

~100Msps ADC/DACで作る測定器/データ・ロガー/LinuxベースSDR~

[Vol.2.C言語プログラムでZynqの“PS”を動かす実験]



【Index】

Vol.1 開発環境“Vivado”をインストールしてFPGA(PL)でLチカ

Vol.2 C言語プログラムでZynqの“PS”を動かす実験

Vol.3 PLによるディジタル・フィルタのサンプル・プロジェクトによる実験

Zynqを使った「ベア・メタル」アプリケーション開発の基礎

PS側で動作するアプリケーションを作る実験

本連載では,Digilent社製のZynq搭載ボードである“Eclypse Z7”(エクリプス・ゼット・セブン)を利用してディジタル信号処理システムの構築方法を解説します(写真1).Eclypse Z7ボードの詳しい情報はこちらのWebページから入手できます.

開発の中心となるデバイスである“Zynq”は,CPU(PS: Processing System)とFPGA(PL: Programmable Logic)の両方を持ちます.前回,連載の第1回ではZynq内部のFPGA部分を使ってLED点滅回路(Lチカ)を作る実験を行いました.今回は,C言語でプログラムを書いてZynq内部のCPU部分で動作するアプリケーションを作ります.

写真1 本連載の実験で使う“Eclypse Z7”ボード(Digilent社製)

アプリケーション開発の流れ

これから行う作業の流れを図1に示します.今回はZynqのPS部分だけを使ったシンプルなアプリケーションを作ります.ZynqのPSは強力なのでLinuxなどのOSを利用することもできますが,今回は簡単のためOSは使わず,C言語で書いたプログラムをそのままCPUで実行することにします.このような形態のアプリケーションのことを「ベア・メタル」(bare metal)といいます.

図1 PS側で動作するアプリケーション(ベア・メタル)を開発する流れ

Zynq内部のハードウェア部分を設計する

新しいプロジェクトを作る

Vivadoを起動して,新しいプロジェクトを作ります.まず,図2のVivadoの起動画面で“Create Project”をクリックします.続いて表示される図3の画面では[Next]をクリックして先に進みます.

図2 Vivadoを起動して“Create Project”をクリックする 図3 [Next]をクリックする

図4の画面では新規作成するプロジェクトの名前と保存先を指定します.今回のプロジェクト名は“20210307test2”としました.保存先は適宜選択してください.“Create project subdirectory”にチェックを付けてプロジェクトごとにディレクトリを作っておくとデータを整理しやすくなります.

図5の画面では“RTL Project”を選択します.今回は既存のファイルを使用しないので,“Do not specify sources at this time”にチェックを付けて[Next]をクリックします.

図4 新規作成するプロジェクト名と保存先を指定する 図5 “RTL Project”を選び“Do not specify sources at this time”にチェックを付ける

図6の画面では“Board”タブを選択し,“Eclypse Z7”ボードを選択します(連載の第1回で解説している内容にしたがってDigilent社が提供している“board_files”を追加しておく必要があります).

図7の画面では設定項目の一覧が表示されるので,問題ないことを確認して[Finish]をクリックします.

図6 “Boards”のタブで“Eclypse Z7”を選択する 図7 [Finish]をクリックしてプロジェクトの新規作成を完了する

ZynqとEclypse Z7ボード上のコンポーネントを接続する準備

今回の実験では,Eclypse Z7ボード上にあるプッシュ・スイッチとLEDを使います.これらの部品(コンポーネント)とZynqを接続するための準備を行います.

図8に示すように,Vivadoの画面左側の“Flow Navigator”の中の“Create Block Design”をクリックします.

図9の画面では新規作成するブロック・デザインの名前を入力します.今回はデフォルトの“design_1”のままで[OK]をクリックします.

図8 “Create Block Design”をクリックする 図9 デフォルトのまま[OK]をクリックする

図10のように新しいブロック・デザインの画面が表示されるので,左上の枠の中で“Board”タブを選択します.Eclypse Z7ボード上で利用できる“Clocks”や“GPIO”(プッシュ・スイッチとLED),“Pmod”(汎用の外部端子)といったコンポーネントが表示されます.

まずはプッシュ・スイッチ(ボタン)の接続を設定をします.“GPIO”の項目の“2 Buttons”を選択して右クリックします.[Connect Board Component]をクリックすると,図11の画面が表示されます.この画面で“AXI GPIO”の項目の“GPIO”を選択して[OK]をクリックします.なお,ここで表示されている“GPIO”は一般的なマイコンにおけるGPIO(Grobal Purpose Input Output,汎用入出力)と同等の回路だと考えて構いません.

図10 “Board”タブの“GPIO”の項目を開き,“2 Buttons”の右クリック・メニューから[Connect Board Component]をクリックする 図11 “AXI GPIO”の項目で“GPIO”を選択して[OK]をクリックする

ここまでの操作が完了すると,ブロック・デザインの画面に図12のようなブロックが追加されます.一般的に,ZynqのCPU部分(PS)とZynqに内蔵されている周辺回路(GPIOも含む)は「AXIバス」で接続する仕様になっています.図12のブロックは,AXIバスに接続できる形式のGPIOを表しています.このブロック(IPあるいはIPコアと呼ぶ)の下部に表示されている“AXI GPIO”がこのIPの名前です(クラス名のようなもの)です.一方で,このブロックの上部に表示されている“axi_gpio_0”はブロックの固有名(インスタンス名のようなもの)です.

なお,VivadoがEclypse Z7ボード上のプッシュ・スイッチの個数やZynqとの接続関係を認識している(“Board”タブの“GPIO”の欄に“2 Buttons”と表示されていた)のは,プロジェクトを新規作成したときにEclypse Z7の“board_files”を読み込んだからです.

図12 プッシュ・スイッチ用のAXIバスの接続ブロックが作られた様子 図13 “2 RGB LEDS”の右クリック・メニューから“Connect Board Component”をクリックする

プッシュ・スイッチに続いて,LEDを接続する準備も行いましょう.Eclypse Z7ボード上には2個のLEDがあり,それぞれがR,G,Bの色を持つフルカラーLEDです.よって,これらのLEDを制御するために必要な信号線は合計で6本となります.

図13に示すとおり,“Board”タブの“GPIO”の項目から“2 RGB LEDS”を右クリックし,“Connect Board Component”をクリックします.ここでも,ZynqのPSとEclypse Z7ボード上のLEDを接続するために“AXI GPIO”を使います.新しくAXI GPIOのブロックを作成しても良いのですが,今回は既存のブロックにポートを追加して使うことにします.

図14の画面で“Connect to existing IP”の欄の中で“axi_gpio_0”の項目を探します.これは,先ほどZynqのPSとEclypse Z7ボード上のプッシュ・スイッチを接続するために用意したGPIOでした.このブロックの“GPIO2”を選択して[OK]をクリックします.すると,図15のように先ほど作った“axi_gpio_0”のブロックに“GPIO2”というポートが追加され,そこから“rgbled_6bits”に向かって配線が伸びた状態になります.

新しく追加された“GPIO2”ポートの直近の“+”マークをクリックすると“gpio2_io_o[5:0]”と表示され,このポートが6ビット幅であることが確認できます.LEDを制御するために6ビット幅の信号線が必要なことも,先に読み込んだ“board_files”に定義されています.

図14 “Aonnect to existing IP”の欄の“axi_gpio_0”の“GPIO2”を選択して[OK]をクリックする 図15 先に作成した“axi_gpio_0”のブロックに“GPIO2”のポートが追加された

ZynqのPSコアを追加する

今回の主役であるZynqのCPU部分(PS)をブロック・デザインに追加します.図16のように,ブロック・デザイン画面の“Diagram”エリアの上部にある[+]ボタンをクリックします.使用可能なIPを検索できる画面が表示されるので,“zynq”と入力します.候補として“ZYNQ7 Processing System”が表示されるので,これをダブル・クリックしてブロック・デザインにIPを追加します.

図16 “Diagram”エリアの[+]ボタンをクリックする 図17 検索欄に“zynq”と入力すると表示される“ZYNQ7 Processing System”をダブル・クリックする

PSのIPを追加すると図18のように画面の上部に“Run Block Automation”と表示されるので,これをクリックします.すると図19の画面が表示されるので,デフォルトのまま[OK]ボタンをクリックします.

“Run Block Automation”を実行すると,ブロック・デザインの画面に“AXI Interconnect”というIPと“Processor System Reset”というIPが新しく追加されます.配線をたどればわかりますが(配線をクリックするとハイライトされる),“AXI Interconnect”はZynqのPSとGPIOの間を接続しているAXIバスのIPです.また,“Processor System Reset”は回路全体を同期させるためのリセット信号を出力するIPです.これらの回路はAXIバスを利用するときに常に必要となるので,今回のように“Run Block Automation”の機能を使って自動生成すると便利です.

図18 ブロック・デザイン画面にZynqのPSが追加される.画面上部の“Run Block Automation”をクリックする
図19 デフォルトのまま[OK]をクリックする

続いて,各ブロックどうしの配線を行います.Zynq内部のAXIバスやリセット回路の基本的な挙動や接続形式は仕様として決められています.設計者がこれらの回路の挙動を細かく設定しても良いのですが,今回のような簡単な回路の場合は自動配線機能を利用すると便利です.図20のように画面上部に“Run Connection Automation”という表示が出るので,これをクリックします.続いて表示される図21の画面ではデフォルト設定のまま[OK]をクリックします.

図20 画面上部に表示される“Run Connection Automation”をクリックする 図21 デフォルトのまま[OK]をクリックする

最終的に図22のようなブロック・デザインが完成します.この状態でブロック・デザインの画面を右クリックして[Regenerate Layout]をクリックすると,IPコアの配置や配線がきれいにまとめられます.

図22 完成したブロック・デザイン

完成したデザインをもとにしてビットストリームを作成する

ブロック・デザインができたら,Zynq内部のハードウェア定義情報をまとめたビットストリーム・ファイルを作成します.

まずは,Vivadoの画面上部の“Validate Design”ボタンをクリックして作成したデザインに問題がないことを検証します(図23).デザインに問題がなければ図24のメッセージが表示されるので[OK]をクリックします.もし問題が発生した場合はここまでの手順に誤りがなかったか確認してください.

図23 “Validate Design”ボタンをクリックする 図24 デザインの検証結果に問題がなければこのメッセージが表示される

図25のように,“BLOCK DESIGN”エリア内の“Sources”タブを選択します.“Design Sources”を展開すると先ほど作成したブロック・デザイン“design_1”があるので,これを右クリックして“Create HDL Wrapper”をクリックします.

すると図26の画面が表示されるので,”Let Vivado manage wrapper and auto-update”を選択して[OK]をクリックします.この操作によって,先ほどブロック・デザインとして作成した回路を包含するVerilog HDL(“design_1_wrapper.v”)が作成されます.このVerilogファイルは,これから論理合成を行う際のトップ・モジュールとして扱われます.

図25 [Create HDL Wrapper]をクリックする 図26 “Let Vivado manage wrapper and auto-update”を選択して[OK]をクリックする

問題なく処理が完了すると,図27のような状態になります.

図28に示すようにVivadoの画面左側の“Flow Navigation”の中の“Gererate Bitstream”をクリックして,Zynqに書き込むハードウェア情報であるビットストリームを作成します.

図27 “HDL wrapper”(トップ・モジュールに相当するもの)の作成が完了した状態 図28 “Generate Bitstream”をクリックする

図29の画面が表示されたら,ビットストリームを作成するのに使用するCPUのコア数を設定して(最大値がおすすめです)[OK]をクリックします.ビットストリームができるまで少々待ちます(図30).

図29 ビットストリームの作成に使用するプロセッサのコア数を設定して[OK]をクリックする 図30 ビットストリームの作成が完了するまで待つ

ビットストリームの作成が完了すると,図31のように“write_bitstream Complete”と表示されます.これでZynq内部のハードウェア定義を行う作業は終了です.

図31 ビットストリームの作成が完了した様子

ZynqのPSで実行するソフトウェアを作る

PS用のプログラム開発環境“SDK”を起動する

ZynqのPSで実行するプログラムの開発は,Vivadoと一緒にインストールされている“SDK”(Software Development Kit)という開発環境で行います.Zynq内部のハードウェア定義情報は,さきほど作成したビットストリーム(bitstream)の中に含まれています.最初に,このビットストリームを“SDK”に渡す作業を行います.

図32のように,Vivadoの画面上部のメニュー・バーから[File] - [Export] - [Export Hardware]をクリックします.すると図33の画面が表示されるので,“Include bitstream”にチェックを付けて[OK]をクリックします.

図32 [File]-[Export]-[Export Hardware]をクリックする 図33 “Include bitstream”にチェックを付けて[OK]をクリックする

続いて,図34のように[File] - [Launch SDK]をクリックします.すると図35の画面が表示されるので[OK]をクリックします.

図34 [File] - [Launch SDK]をクリックする 図35 デフォルトのまま[OK]をクリックする

初めてSDKを起動するときは図36の画面が表示されるので,適宜チェックを付けて[アクセスを許可する]をクリックします.少し待つと,さきほど作成したハードウェア情報を読み込んだ状態でSDKが起動します.

新しいアプリケーション・プロジェクトを作る

SDKでは,PSで実行するプログラムを「アプリケーション・プロジェクト」(Application Project)という単位で管理します.SDKが起動したら,[File] - [New] - [Application Project]をクリックして新しいアプリケーション・プロジェクトを作成します(図37).

図36 初めてSDKを起動するときに表示される.適宜チェックを付けて[アクセスを許可する]をクリックする 図37 [File] - [New] - [Application Project]をクリックする

図38の画面でアプリケーション・プロジェクトの名前を入力します.今回は“test1”としました.また,“OS Platform”の欄は“standalone”を選択します.今回のプロジェクトではOSを使わず,これから作るプログラムをCPUで直接実行します.このようなアプリケーションの形態を「ベア・メタル」(bare metal)といいます.以上の設定ができたら[Next]をクリックします.

続いて表示される図39の画面では“Empty Application”を選択して[Finish]をクリックします.これでアプリケーション・プロジェクトの作成は完了です.

図38 新規作成するプロジェクトの名前を決める.“OS Platform”は“standalone”を選択して[Next]をクリックする 図39 “Empty Application”を選択して[Finish]をクリックする

C言語でソース・コードを記述する

ZynqのPSで実行するプログラムを作るために,C言語のソース・コードを用意します.まず,図40に示すとおりSDKの画面左側のツリーの中から[プロジェクト名](今回は“test1”) - [src]を右クリックします.右クリック・メニューの中から[New] - [Source File]をクリックして新しいソース・コードのファイルを作成します.

図41の画面では“Source file”の欄に“main.c”と入力して[Finish]をクリックします.

図40 新しいソース・ファイルを作る 図41 ファイル名は“main.c”とする

新しく作成したソース・ファイル“main.c”の編集画面が表示されるので,リスト1の内容を記述します.

//AXI GPIO driver
#include "xgpio.h"

//send data over UART
#include "xil_printf.h"

//information about AXI peripherals
#include "xparameters.h"

//wait
#include "sleep.h"

int main()
{
	XGpio gpio;
	u32 btn, led;

	XGpio_Initialize(&gpio, 0);

	XGpio_SetDataDirection(&gpio, 2, 0x00000000); //Output for LED
	XGpio_SetDataDirection(&gpio, 1, 0xffffffff); //Input for Button

	while(1)
	{
		btn = XGpio_DiscreteRead(&gpio, 1);

		switch(btn)
		{
			case 0x00000001:
				led = 0x00000004; //LD0 red
				break;
			case 0x00000002:
				led = 0x00000020; //LD1 red
				break;
			case 0x00000003:
				led = 0x0000003f; //LED0 & LED1 white
				break;
			default:
				led = 0x00000000;
				break;
		}

		XGpio_DiscreteWrite(&gpio, 2, led);

		xil_printf("button state: %08x\r\n", btn);

		usleep(100000); //wait 100 msec
	}
}


リスト1 PSで実行するアプリケーションのC言語ソース・コード“main.c”

以下,今回のソース・コード“main.c”の内容を簡単に説明します.

2行目ではGPIOにアクセスする関数を扱うために“xgpio.h”をインクルードしています.また,5行目ではUART経由で文字列を送受信する関数である“xil_printf()”を使うために“xil_printf.h”をインクルードしています.8行目でインクルードしている“xparameters.h”には定数の定義が書かれています(今回は使用しない).11行目でインクルードしている“sleep.h”はウェイトを入れるために使う“usleep()”のヘッダ・ファイルです.

18行目から21行目ではGPIOを初期化しています.LED(led)と接続する“GPIO2”はすべて出力モード,プッシュ・スイッチ(btn)と接続する“GPIO1”はすべて入力モードに設定しています.

23行目以降のwhile文では,プッシュ・スイッチの入力を変数“btn”に格納して,その値に応じてLEDの状態を変化させています.また,45行目ではプッシュ・スイッチの入力をUART経由で送信しています.

ソース・コードの入力が完成した様子を図42に示します.ソース・コードを入力してからキーボードで[Ctrl] + [s]を押して保存すると,同時にコンパイルが行われます.

図42 ソース・コードの入力が完了した状態

プログラムを実行する

ソース・コードの記述が完了して無事にコンパイルも済んだら,Zynqにプログラムを書き込みます.写真2のようにEclypse Z7ボードに電源ケーブルとUSBケーブルを接続して準備をします.“JP5”のジャンパは“JTAG”側にしておいてください.

写真2 Eclypse Z7の“JP5”を“JTAG”側にする

Eclypse Z7ボードの電源を入れた状態で,図43のようにSDKのメニュー・バーから[Xilinx] - [Program FPGA]をクリックします.続いて表示される図44の画面で[Program]をクリックします.

図43 [Xilinx] - [Program FPGA]をクリックする 図44 [Program]をクリックする

初めてSDKからZynqにデータを転送するときは,図45の画面が表示されます.適宜チェックを付けて[アクセスを許可する]をクリックします.この後Zynqに対してプログラムが書き込まれるので,少し待ちます.

プログラムの書き込みが完了したら,アプリケーションを実行してみましょう.図46のようにSDKの画面左側のツリーから[プロジェクト名](今回は“test1”) - [Run As] - [1 Launch on Hardware (System Debugger)] をクリックするとZynq上でアプリケーションの動作が開始します.

図45 [アクセスを許可する]をクリックする 図46 Zynqに書き込んだプログラムをデバッガで実行する

アプリケーションを実行した状態でEclypse Z7ボード上のプッシュ・スイッチ“BTN0”および“BTN1”を押すと,それに対応したLEDが赤色に点灯します.また,2つのプッシュ・スイッチを同時に押すと両方のLEDが白色に光ります(かなりまぶしい).なお,リスト1の30行目や33行目で変数“led”の値を書き換えると,光らせるLEDの色を変更できます.対応するビットは下位(LSB)から順に“LD0”の“B”(青),“G”(緑),“R”(赤),“LD1”の“B”(青),“G”(緑),“R”(赤)となっています.

さらに,PC側で“Tera Term”などのターミナル・ソフトウェア(Tera Termのインストールや使い方はこちらを参照)を起動しておけばプッシュ・スイッチの状態を表示することができます.Tera Termのシリアル・ポートはボー・レートを“115200 bps”,データ長を“8 bit”,ストップ・ビットを“1 bit”,パリティを「なし」に設定しておきます(図47).ZynqからUART経由で送信される文字列をPCで受信している様子を図48に示します.

図47 “Tera Term”のシリアル・ポートの設定 図48 Zynqから送信される文字列を“Tera Term”で受信している様子

プログラムを修正したい場合は,デバッガでプログラムを実行している途中でも新しいプログラムを上書きすることができます.なお,今回はJTAGで書き込んでいるだけなので,Eclypse Z7ボードの電源を切ると書き込んだプログラムは消えます.

(c)2021 Nobuyasu Beppu