SoC FPGAでLチカ --DE10-Nanoを例に--
SoC FPGAとは
SoCFPGAとは、FPGAのチップにCPUの類のものが搭載されているもの。
やったこと
今回行ったのは以下
今回使用したボード
DE10-Nano(IntelのCyclone Vが実装されたもの)
Digikeyで15,000円くらい。カバーがついてて心なしか安心。
書き込みが専用ツールでなく、USBケーブルで出来るのもうれしい。
ハードウェアロジックでLEDを点滅
Quartus Primeを立ち上げて新規プロジェクトを作成する。
ボードは選択しないで、FPGA"5CSEBA6U23I7DK"を選択。
Verilogコードの作成
Verilogのコードは以下。
クロックが50MHzなので、LEDの点滅が目で追えないけど、
スイッチ押せば止まるので気にしない。
module LED_TEST( input I_CLK_50MHZ, input I_SW1, output reg O_LED1, output reg O_LED2 ); reg [15:0] CLKCNT; reg [15:0] HALFCNT; initial begin O_LED1 = 1'b0; O_LED2 = 1'b1; CLKCNT = 16'b0000_0000_0000_0000; HALFCNT = 16'b1000_0000_0000_0000; end always@( posedge I_CLK_50MHZ ) begin CLKCNT = (I_SW1)? CLKCNT + 1:CLKCNT; O_LED1 = (CLKCNT < HALFCNT)? 1:0; O_LED2 = ~O_LED1; end endmodule
ピンアサインの設定
次にVerilogコードのInput、OutputとFPGAのピンを紐づける。
DE10-NanoのUser Manualに記載がある。
とりあえず必要な箇所だけピックアップして以下に示す。
Quattus PrimeのAssignments > Pin Plannerをクリックし、VelirogのInput、outputと各ピンを接続する。
今回は以下のような感じ。
Node NameはVerilogに記載した名称を入力し、LocationにFPGAのピンアサインを入力する。
I/O Standardの箇所で電圧レベルや種類(シングル or 差動等)を選択する。
Directionはコンパイル時に自動で設定される。
コンパイル
Quartus PrimeのProcessing > Start Compilationをクリックし数分待てば書き込みデータが完成。
書き込み
Quartus PrimeのTools > Programmerをクリックすると、Programmerが起動する。
Auto Detectをクリックすると、基板上のメモリとFPGAが表示される。
FPGA上で右クリックし、edit > change fileを選択。
先ほど作成したコンパイルデータを選択する(projectディレクトリ直下のoutput_files内にあるはず)。
Program/Configureにチェックを入れて、Startをクリック。
無事書き込まれると、右上に100%(Successfuss)が表示される。
動作確認
LED0とLED1が点灯している(実際には高速で点滅している)。 Key0スイッチを押すと、一方のみが点灯する。
以上で、ハードウェアロジックでLEDを操作する説明終了。
LinuxからLEDを操作
次はボード上のLinuxからLEDを操作する。
準備
準備として、以下のDE10-Nanoの演習を一通りこなす。
service.macnica.co.jp
上記の5.演習 3は飛ばしてもよい。
ベアメタルアプリケーション開発はライセンス費用が掛かるので、
よほどのプロでない限りは使用しないはず。
Linuxアプリケーションの開発方法
6.演習4でLinuxアプリケーションの演習があるが、
Hello worldを表示できれば、
開発環境が構築されたことを意味する。
ただしHello Worldだけでは物足りないので、
LEDを点灯させてみる。
演習を完了している場合、LED PIOのベースアドレスは0x0001_0040
となっている。
実際にはブリッジのベースアドレスが0xFF20_0000
なので、
0xFF21_0040
である(演習のpdfに記載されている)。
0xFF21_0040
の各bitが各LEDの値に対応している。
レジスタの値を変えるにはmmap
を使用する。
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <string.h> #include <time.h> #include <sys/time.h> #include <poll.h> #include <sys/types.h> #include <sys/mman.h> #include <sys/stat.h> #include <unistd.h> #include <stdint.h> #include <errno.h> #define BASE_BRIGDE 0xff200000 #define BASE_LEDPIO 0xff210040 #define DEVNAME "/dev/mem" int main(int argc, char** argv) { int fd; uint32_t from; uint32_t num; uint32_t port; volatile uint8_t *iomap; printf("this is IOmemory test program\n"); fd = open(DEVNAME, (O_RDWR|O_SYNC)); if(fd <= 0){ perror(DEVNAME); exit(1); } from = BASE_BRIGDE; num = 0x20000; iomap = mmap(0, num, PROT_READ|PROT_WRITE, MAP_SHARED, fd, from); if (iomap == MAP_FAILED) { printf("Error : mmap failed. ERRO:%d\n", errno); exit(1); } //LEDを1つだけ点灯させる。プログラム。 port = BASE_LEDPIO; *(uint32_t*)(iomap + port - from) = ~0x01; munmap((void*)iomap, num); close(fd); return 0; }
LEDを右から左、左から右へと順番に消灯したい場合は以下のようなプログラムになる。
port = BASE_LEDPIO; uint8_t ledMask = 0x01; uint8_t ledDir = 0; int loopCount = 0; while(loopCount < 60){ *(uint32_t*)(iomap + port - from) = ~ledMask; usleep(100*1000); if(ledDir == 0){ ledMask <<= 1; if(ledMask == (0x01 << 6)){ ledDir = 1; } }else{ ledMask >>= 1; if(ledMask == 0x01){ ledDir = 0; loopCount++; } } }