AWS f1インスタンスでのFPGA開発

最終更新: 2017-05-06 19:02

AWSののFPGAインスタンスを使って開発する方法について記録する。AWSでのFPGA開発は以下の3ステップで行う。

  1. FPGA Developer AMIを起動する
  2. FPGA HDKでAmazon FPGA Image (AFI)を作成する
  3. FPGA SDKでAFIをロードする

FPGA Developer AMIの起動

まず、AWSでのFPGA開発に必要なVivadoなどのソフトが用意されたFPGA Developer AMIを起動する。

https://github.com/aws/aws-fpga#fpga-developer-ami によればこのAMIははc4/m4/r4/t2.2 xlargeのいずれでも動くそうなので、最初はこれらの安価なインスタンス上で開発・シミュレーションを行い、開発が終わったところでf1インスタンスを立ち上げるようにすれば費用が抑えられる(が結局実機がないと何も分からないのでf1インスタンス上で作業した)。

の2点に注意する必要がある(らしい)

公開鍵の登録などの一連の設定を終えたらログインする。ユーザー名はcentosなので注意。

ログインしたら/home/centos/src/にREADME.mdなどがあるので一通り読む。

テストは

vivado -mode batch -source /home/centos/src/test/counter/gen_bitstream.tcl

で実行する。write_bitstream completed successfullyと表示されたら成功。カウンターのような簡単なコードでもかなり時間がかかった。

Exampleを動かす

この操作はf1インスタンス上で行う。

Hello World Exampleを動かす

Step by step guide how to load and test a registered AFI from within an F1 instanceに従って実行する。

AWS FPGA Management toolsを設定する。

$ AWS_FPGA_REPO_DIR=/home/centos/src/project_data/aws-fpga
$ git clone https://github.com/aws/aws-fpga.git $AWS_FPGA_REPO_DIR
$ cd $AWS_FPGA_REPO_DIR
$ source sdk_setup.sh

0番スロットのFPGAをクリアする

sudo fpga-clear-local-image  -S 0

Hello World Exampleのイメージをロードする。(読み込むAGFI IDはHello World Example Metadataに書いてある。

sudo fpga-load-local-image -S 0 -I agfi-0f0e045f919413242

ロードされたことを確認

$ sudo fpga-describe-local-image -S 0 -R -H
Type  FpgaImageSlot  FpgaImageId             StatusName    StatusCode   ErrorName    ErrorCode   ShVersion
AFI          0       agfi-0f0e045f919413242  loaded            0        ok               0       0x04151701
Type  FpgaImageSlot  VendorId    DeviceId    DBDF
AFIDEVICE    0       0x1d0f      0xf000      0000:00:1d.0

テストの実行

$ cd /home/centos/src/project_data/aws-fpga/hdk/cl/examples/cl_hello_world/software/runtime
$ make all
$ sudo ./test_hello_world
AFI PCI  Vendor ID: 0x1d0f, Device ID 0xf000
===== Starting with peek_poke_example =====
register: 0xdeadbeef
Resulting value matched expected value 0xdeadbeef. It worked!
Developers are encourged to modify the Virtual DIP Switch by calling the linux shell command to demonstrate how AWS FPGA Virtual DIP switches can be used to change a CustomLogic functionality:
$ fpga-set-virtual-dip-switch -S (slot-id) -D (16 digit setting)

In this example, setting a virtual DIP switch to zero clears the corresponding LED, even if the peek-poke example would set it to 1.
For instance:
# fpga-set-virtual-dip-switch -S 0 -D 1111111111111111
# fpga-get-virtual-led  -S 0
FPGA slot id 0 have the following Virtual LED:
1010-1101-1101-1110
# fpga-set-virtual-dip-switch -S 0 -D 0000000000000000
# fpga-get-virtual-led  -S 0
FPGA slot id 0 have the following Virtual LED:
0000-0000-0000-0000

It worked!とのことなのでよさそう

CL_DRAM_DMA CustomLogic Exampleを動かす

次にCL_DRAM_DMA CustomLogic Exampleを動かす。

EDMAドライバーのインストール

https://github.com/aws/aws-fpga/blob/master/sdk/linux_kernel_drivers/edma/edma_install.md に従う。

$ cd $AWS_FPGA_REPO_DIR
$ cd sdk/linux_kernel_drivers/edma
$ make
$ echo 'edma' | sudo tee --append /etc/modules-load.d/edma.conf
$ sudo cp edma-drv.ko /lib/modules/`uname -r`/
$ sudo depmod
$ sudo modprobe edma-drv

make以降の手順はカーネルアップデートや再起動のたびに行う必要があるらしい

ドライバーがインストールできたら/dev/edma*_queue_*みたいなファイルができるらしいので確認。

$ ls /dev/edma*
/dev/edma0_queue_0

大丈夫そう。

実行

$ cd $AWS_FPGA_REPO_DIR
$ cd hdk/cl/examples/cl_dram_dma/software/runtime
$ sudo ./test_dram_dma 
DRAM DMA read the same string as it wrote on channel 0 (it worked correctly!)
DRAM DMA read the same string as it wrote on channel 1 (it worked correctly!)
DRAM DMA read the same string as it wrote on channel 2 (it worked correctly!)
DRAM DMA read the same string as it wrote on channel 3 (it worked correctly!)
Starting MSI-X Interrupt test 
Polling device file: /dev/fpga0_event0 for interrupt events 
Triggering MSI-X Interrupt 0
Interrupt present for Interrupt 0. It worked!

It worked!とのことなのでこのExampleも動くっぽい。

CからFPGAのメモリにアクセスする方法

FPGAのメモリはPCIeで接続されている。メモリマップはAWS FPGA PCIe Memory Mapに書かれている。

FPGAのメモリにアクセスする方法にっているついてはAWS FPGA: Programmer's View of the Custom Logicに記述がある。画像を引用すると、

Hello World Exampleを眺める

Hello World ExampleはDのFPGA PCIe Libを用いた書き込みを行っているように見える。該当部分のCコードを参照すると

    /* write a value into the mapped address space */
    uint32_t value = 0xefbeadde;
    uint32_t expected = 0xdeadbeef;
    rc = fpga_pci_poke(pci_bar_handle, HELLO_WORLD_REG_ADDR, value);
    fail_on(rc, out, "Unable to write to the fpga !");

    /* read it back and print it out; you should expect the byte order to be
     * reversed (That's what this CL does) */
    rc = fpga_pci_peek(pci_bar_handle, HELLO_WORLD_REG_ADDR, &value);
    fail_on(rc, out, "Unable to read read from the fpga !");

のように32bit単位での読み書きを行っている。

この読み書きを処理するSystemVerilogのコード

//-------------------------------------------------
// Hello World Register
//-------------------------------------------------
// When read it, returns the byte-flipped value.

always_ff @(posedge clk_main_a0)
   if (!rst_main_n_sync) begin                    // Reset
      hello_world_q[31:0] <= 32'h0000_0000;
   end
   else if (wready & (wr_addr == `HELLO_WORLD_REG_ADDR)) begin  
      hello_world_q[31:0] <= wdata[31:0];
   end
   else begin                                // Hold Value
      hello_world_q[31:0] <= hello_world_q[31:0];
   end

assign hello_world_q_byte_swapped[31:0] = {hello_world_q[7:0],   hello_world_q[15:8],
                                           hello_world_q[23:16], hello_world_q[31:24]};

のようになっている。

書き込まれたメモリアドレスを見てなんか処理するっぽい。

Read/Writeの使い方

32bitごとに書き込むのはあまりにつらいのでRead/Writeでうまいことやりたい。それがGのDMA Kernel Driverを使った方法。使い方はUsing AWS EDMA in C/C++ applicationに書いてある。
このドキュメントの例を持ってくると(なおMakeでエラーになるので30行目のint i;は消去した)

#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>

#define BUF_SIZE              256
#define OFFSET_IN_FPGA_DRAM   0x10000000

static char *rand_str(char *str, size_t size)  // randomize a string of size <size>
{
    const char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRTSUVWXYZ1234567890";
    int i;

    for(i = 0; i < size; i++) {
        int key = rand() % (int) (sizeof charset - 1);
        str[i] = charset[key];
    }

    str[size-1] = '\0';
    return str;
}


int main()
{
    char* srcBuf;
    char* dstBuf;
    int fd;
    int ret;

    srcBuf = (char*)malloc(BUF_SIZE * sizeof(char));
    dstBuf = (char*)malloc(BUF_SIZE * sizeof(char));

    /* Initialize srcBuf */
    rand_str(srcBuf, BUF_SIZE);

    /* Open an EDMA queue for read/write */
    if((fd = open("/dev/edma0_queue_0",O_RDWR)) == -1) {
        perror("open failed with errno");
    }

    /* Write the entire source buffer to offset OFFSET_IN_FPGA_DRAM */

    ret = pwrite(fd , srcBuf, BUF_SIZE, OFFSET_IN_FPGA_DRAM);
    if(ret < 0) {
        perror("write failed with errno");
    }
    
    printf("Tried to write %u bytes, succeeded in writing %u bytes\n", BUF_SIZE, ret);

    /* ensure the write made it to the Shell/CL interface */
    fsync(fd);

    ret = pread(fd, dstBuf, BUF_SIZE, OFFSET_IN_FPGA_DRAM);

    if(ret < 0) {
        perror("read failed with errno");
    }
    
    printf("Tried reading %u byte, succeeded in read %u bytes\n", BUF_SIZE,ret);

    if(close(fd) < 0) {
        perror("close failed with errno");
    }

    printf("Data read is %s\n", dstBuf);
    
    return 0;
}

とするとPCIeのメモリに文字列を書き込むことができる。これに対応するSystemVerilogのコードは今読んでる。

テストベンチ

テストベンチの使い方: https://github.com/aws/aws-fpga/blob/master/hdk/docs/RTL_Simulating_CL_Designs.md

情報源