モノ創りで国造りを

ハード/ソフト問わず知見をまとめてます

SoC FPGAでLチカ --DE10-Nanoを例に--

SoC FPGAとは

SoCFPGAとは、FPGAのチップにCPUの類のものが搭載されているもの。

やったこと

今回行ったのは以下

  1. FPGAのハードウェアロジックでLEDを点滅
  2. LinuxFPGAボードのLEDを点滅

今回使用したボード

DE10-Nano(IntelのCyclone Vが実装されたもの)
Digikeyで15,000円くらい。カバーがついてて心なしか安心。
書き込みが専用ツールでなく、USBケーブルで出来るのもうれしい。 f:id:yuji2yuji:20190730161014p:plain

ハードウェアロジックで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に記載がある。
とりあえず必要な箇所だけピックアップして以下に示す。
f:id:yuji2yuji:20190730161821p:plain
f:id:yuji2yuji:20190730161908p:plain
f:id:yuji2yuji:20190730161928p:plain

Quattus PrimeのAssignments > Pin Plannerをクリックし、VelirogのInput、outputと各ピンを接続する。
今回は以下のような感じ。
f:id:yuji2yuji:20190730162313p:plain 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++;
            }
        }
    }