Cyclone V Soc でLチカ

 

前回の記事ではDE10-nanoの起動用SDカードを作成した

今回の記事ではFPGAをコンフィグしてFPGA側のLEDをC言語のプログラムで操作する

何番煎じだというかんじですが)

設定手順

今回は下記の手順で設定を行う

  1. FPGAデータの作成
  2. u-bootからのFPGAコンフィグ
  3. Lチカプログラムを作成、実行する

1.FPGAデータの合成

FPGAへの書き込みに使用するデータのプロジェクトには今回もTerasicが出している
DE10_NANO_SoC_GHRDを利用する

makeファイルがあるため合成にはコマンドラインmake rbfを実行することで、
FPGAの書き込みに使うoutput_files/DE10_NANO_SoC_GHRD.rbfが生成される

ただし、Quartus 19.xで合成しようとするとQsysコンポーネントで使用しているPIOのIPが見つからないためエラーになってしまう。なので、合成にはQuartus 18.1で合成した。

 

 2.u-bootからのFPGAコンフィグ

作成したRBFファイルをFPGAに書き込む。

FPGAのコンフィグはLinuxから行う方法とLinuxのブート前にu-bootのスクリプトから行う方法があるが、今回はu-bootからのコンフィグを行う。

まずは、RBFファイルをu-bootから読めるようにFATパーティション(zImageとかを入れたパーティション)に入れる。

次にu-bootのコンソールからFPGAのコンフィグスクリプトを登録する
u-bootのコンソールに入るには起動時にシリアルコンソールで下記のメッセージが表示された際にキー入力を行う。

In:    serial
Out:   serial
Err:   serial
Model: Altera SOCFPGA Cyclone V SoC Development Kit
Net:   eth0: ethernet@ff702000
Hit any key to stop autoboot:

 

FPGAをコンフィグするには1度メモリ上にデータを展開して、FPGAにそのデータを流す
そのためのコマンドは下記のようになる

fatload mmc 0:1 ${loadaddr} DE10_NANO_SoC_GHRD.rbf
fpga load 0 ${loadaddr} 2082088;

fatloadではDE10_NANO_SoC_GHRD.rbfを${loadaddr}のアドレスに展開している
この${loadaddr}は後のLinuxの展開に使われるアドレスだがその前に借りる形になる

 fpga loadでは展開したデータをFPGAに書き込んでいる。2082088はFPGAデータのサイズで本来ならfatloadしたサイズを取得するべきだが、今回は固定値にしている。
FPGAが変わらなければ固定値でも問題ないはず)

 このコマンドがうまくいっていればLEDが点灯して一番端のLEDが点滅する

 

次にこれらのコマンドを自動で実行するように環境変数を追記する

通常、ブートの際にキー入力が行われなければmmc_bootのスクリプトからzImageのロード等が行われLinuxが立ち上がる

FPGAのコンフィグをこのブートの流れに組み込むために、setenv, editenvコマンドを使って下記の通り変数を追記、編集する

callscript=if fatload mmc 0:1 ${loadaddr} soc_system.rbf; then fpga load 0 ${loadaddr} 2082088; bridge enable; echo done config FPGA; else echo not found DE10_NANO_SoC_GHRD.rbf;fi;
mmc_boot=run callscript; if mmc dev ${devnum}; then devtype=mmc; run scan_dev_for_boot_part; fi
saveenv

この変更でmmc_bootでboot前にcallscriptが実行される

callscriptではFPGAデータのロードに成功すればFPGAのコンフィグを行う

また、bridge enableというコマンドを実行しているが、これはFPGA-CPU間の通信のためのポートに関して、レジスタ設定を行っている

(このあたりのu-bootのコマンド一覧についてはこちらで調べられている)

最後にsaveenvすることで再起動しても今回の変更が反映される

 

3.Lチカプログラムの作成実行

最後にFPGAレジスタを操作してLEDを点灯(消灯)させる

LEDを制御するモジュールはQsysの設定でlw_hps2fpgaの0x0000_3000番地に接続されている。lw_hps2fpgaのベースアドレスは0xff20_0000(ここでCyclone Vのアドレスマップが確認できる)

なので、0xff20_3000番地に値をかきこむことでLEDのON/OFFが切り替えられる。

C言語でこの物理アドレスにアクセスするプログラムは下記のようになる

 

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>

int main()
{
  volatile unsigned int *address;
  unsigned int lw_h2f_led = 0xff203000U;
  unsigned int map_size = 0x1000U; //map_sizeは適当(小さすぎるとページングの関係でマッピングに失敗するため)
  int fd;

  /* メモリアクセス用のデバイスファイルを開く */
  /* キャッシュを無効化するためO_SYNCフラグをたてる*/
  if ((fd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) {
    perror("open");
    return -1;
  }

  /* lw_h2f_ledをマッピングした仮想アドレスを取得する*/
  address = (int*)mmap(NULL,  0x1000U, PROT_READ | PROT_WRITE, MAP_SHARED, fd, lw_h2f_led);
  if (address == MAP_FAILED) {
    perror("mmap");
    close(fd);
    return -1;
  }

  printf("led status check.\n");
  printf("led status : 0x%08x.\n", address[0x00]);  /* FPGAのレジスタからデータを取得*/
  address[0x0] = 0x00;  /* LEDは初期状態では点灯しているので消灯 */
  printf("led status : 0x%08x.\n", address[0x00]);  /* 変更が反映されていることを確認*/

  munmap((void*)address, map_size);
  close(fd);

  return 0;
}

このプログラムをarmのクロスコンパイラでコンパイルした後、生成されたa.outをDE10-nanoで実行するとLEDが消灯する(0を書き込んだので)

 

 

これで自分の書いたHDLにCPUからアクセスするための環境ができた(AvalonとかAXIとかのInterfaceの話は残っているが)ので、今後はHDLを書いたり、それ用のLinuxドライバも作っていきたいと思う。

 

間違っているところ等あれば教えていただけると嬉しいです。