lp6m’s blog

いろいろかきます

VexRiscvでBRAMのかわりにDRAMを使いたい(失敗)

前回のKV260でVexRiscv動作させた - lp6m’s blogでは、VexRiscvのコアのメモリをBRAMで実装していた。 AXI BRAMを使ってPSから読み書きができた。
Xilinx DPUとVexriscvを両方載せようとすると、BRAMリソースが制約を受けてメモリサイズを小さくせざるを得ない。
パフォーマンスを犠牲にしてよいので、BRAMの変わりにDRAMを使いたい。VexRiscvの命令バス(IBus)とデータバス(DBus)はAXIプロトコルなので、適当に繋いだらできるのでは?と思って繋いだ。
ブロックデザインは以下の通り。


アドレスマップは特に何もいじらず、自動で設定されたものを使用した。

教えてもらうまで知らなかったのだが、PS/PLはどのDDRアドレスにでもアクセスできるのではなくて、PS側にはDDR Low(0x0000_0000から0x7FFF_FFFF)にしか割り当てられていない。
VexRiscvにはこのDDR Low領域に読み書きしてもらわないと、PS側からアクセスできないみたい。


VexRiscvは命令メモリの開始アドレスをハードコーディングするのでresetVectorを0x40000000lに修正した。
github.com


petalinuxプロジェクトのxilinx-kv260-starterkit-2022.1/project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi を修正する。
reserved-memoryでDDR Low領域の一部空間(今回は0x40000000から0x4FFFFFFF)をLinuxに使用されないようにブロックしておく。
CMA領域に連続したメモリアドレスを確保する。この書き方は以下を参考にさせてもらった。
github.com

最後に、確保した空間を簡単に読み書きするために`u-dma-buf`を使用させてもらう。
今までudmabufはXilinx AXI DMA IPのためだけに存在するバッファだと完全に勘違いしていたが、純粋にデータのやりとりのバッファとして使えることを理解した気がする。

/include/ "system-conf.dtsi"
/ {
	chosen {
                bootargs = "earlycon console=ttyPS1,115200 clk_ignore_unused init_fatal_sh=1 cma=512M uio_pdrv_genirq.of_id=generic-uio";
                stdout-path = "serial1:115200n8";
        };
        reserved-memory {
                #address-cells = <2>;
                #size-cells = <2>;
                ranges;
                riscv_buf: riscv_buf@40000000 {
                        compatible = "shared-dma-pool";
                        reusable;
                        reg = <0x0 0x40000000 0x0 0x10000000>;
                        label = "riscv_buf";
                };
        };
        udmabuf@40000000 {
                compatible = "ikwzm,u-dma-buf";
                device-name = "udmabuf0";
                size = <0x10000000>;
                memory-region = <&riscv_buf>;
        };
};

この内容で起動用SDカードを作成した。

petalinux-build
petalinux-package --boot --u-boot --force
petalinux-package --wic --images-dir images/linux/ --bootfiles "ramdisk.cpio.gz.u-boot,boot.scr,Image,system.dtb,system-zynqmp-sck-kv-g-revB.dtb" --disk-name "mmcblk1"

pl.dtsiの作成

xmutilでデバイスツリーオーバレイするためのdtboファイルを作成する。
PL側のノードがないため、エラーになった。 xlnx_rel_v2022.2を指定することでエラーは出なかった。以下リンクと同じエラー。
createdts fails for KV260 XSA · Issue #310 · Xilinx/Vitis-Tutorials · GitHub

xsct
createdts -hw vivado/riscv_base_prj/riscv_base_prj.xsa -zocl -platform-name mydevice -git-branch xlnx_rel_v2022.2 -overlay -compile -out mydevice
exit

生成されたpl.dtsiの内容は以下の通りだった。PLのノードがないためにクロックの情報などがかかれたノードがないがこれでいいのか?

/dts-v1/;
/plugin/;
/ {
	fragment@0 {
		target = <&fpga_full>;
		overlay0: __overlay__ {
			#address-cells = <2>;
			#size-cells = <2>;
			firmware-name = "riscv_base_prj.bit.bin";
			resets = <&zynqmp_reset 116>,<&zynqmp_reset 117>;
		};
	};
};

u-dma-buf.koの作成

u-dma-bufを使用するために、petalinuxでカーネルモジュールをビルドする。以下記事を参考にビルドした。
FPGAの部屋 udmabufをPetaLinux 2018.2でビルドする

起動確認

SDカードイメージを書き込んで起動確認をし、u-dma-buf.koを読み込んだ。

xilinx-kv260-starterkit-20221:~$ dmesg | grep cma
[    0.000000] cma: Reserved 512 MiB at 0x0000000057800000
[    0.000000] Kernel command line: earlycon console=ttyPS1,115200 clk_ignore_unused init_fatal_sh=1 cma=512M uio_pdrv_genirq.of_id=generic-uio
[    0.000000] Memory: 3213632K/4193280K available (14528K kernel code, 1012K rwdata, 4060K rodata, 2176K init, 571K bss, 193216K reserved, 786432K cma-reserved)
xilinx-kv260-starterkit-20221:~$ sudo insmod u-dma-buf.ko
xilinx-kv260-starterkit-20221:~$ dmesg | grep u-dma-buf
[  117.342056] u-dma-buf udmabuf@40000000: driver probe start.
[  117.342904] u-dma-buf udmabuf@40000000: assigned reserved memory node riscv_buf@40000000
[  117.405800] u-dma-buf udmabuf0: driver version = 4.0.0
[  117.405816] u-dma-buf udmabuf0: major number   = 237
[  117.405821] u-dma-buf udmabuf0: minor number   = 0
[  117.405826] u-dma-buf udmabuf0: phys address   = 0x0000000040000000
[  117.405831] u-dma-buf udmabuf0: buffer size    = 268435456
[  117.405838] u-dma-buf udmabuf@40000000: driver installed.

CMA領域が正しく確保されており、u-dma-bufのロードも正しくできているように見える。

VexRiscv動作確認

VexRiscv_Ultra96/test.cpp at dev · lp6m/VexRiscv_Ultra96 · GitHubをベースにudmabufを使用するように修正する。
リセットのGPIOは前回と同じくgpio-172を使用する。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <time.h>
#include <stdlib.h>
#include <fcntl.h>
#include <dirent.h>
#include <unistd.h>
#include <fcntl.h>
#include <cstring>

#define REG(address) *(volatile unsigned int*)(address)


int pl_resetn_1(){
    int fd;
    char attr[32];

    DIR *dir = opendir("/sys/class/gpio/gpio172");
    if (!dir) {
        fd = open("/sys/class/gpio/export", O_WRONLY);
        if (fd < 0) {
            perror("open(/sys/class/gpio/export)");
            return -1;
        }
        strcpy(attr, "172");
        write(fd, attr, strlen(attr));
        close(fd);
        dir = opendir("/sys/class/gpio/gpio172");
        if (!dir) {
            return -1;
        }
    }
    closedir(dir);

    fd = open("/sys/class/gpio/gpio172/direction", O_WRONLY);
    if (fd < 0) {
        perror("open(/sys/class/gpio/gpio172/direction)");
        return -1;
    }
    strcpy(attr, "out");
    write(fd, attr, strlen(attr));
    close(fd);

    fd = open("/sys/class/gpio/gpio172/value", O_WRONLY);
    if (fd < 0) {
        perror("open(/sys/class/gpio/gpio172/value)");
        return -1;
    }
    sprintf(attr, "%d", 0);
    write(fd, attr, strlen(attr));

    sprintf(attr, "%d", 1);
    write(fd, attr, strlen(attr));
    close(fd);
    return 0;
}

unsigned int float_as_uint(float f){
    union {float f; unsigned int i; } union_a;
    union_a.f = f;
    return union_a.i;
}

float uint_as_float(unsigned int i){
    union {float f; unsigned int i; } union_a;
    union_a.i = i;
    return union_a.f;
}

// This program is DMEM[0]+DMEM[1]=DMEM[2]
int main(){
    int fd  = open("/dev/udmabuf0", O_RDWR);
    if (fd < 0) {
        printf("Device Open Error");
        exit(-1);
    }

    volatile unsigned int* MEM_BASE = (volatile unsigned int*) mmap(NULL, 0x10000000, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    //set RISC-V Instruction
    MEM_BASE[0] = 0x40020437; //  0: lui s0,0x40020000
    MEM_BASE[1] = 0x00040413; //  4: mv  s0,s0
    MEM_BASE[2] = 0x00042607; //  8: flw  fa2,0(s0) # 0x40020000
    MEM_BASE[3] = 0x00442687; //  C: flw  fa3,4(s0)
    MEM_BASE[4] = 0x00c68753; // 10: fadd fa4,fa2,fa3
    MEM_BASE[5] = 0x00e42427; // 14: fsw  fa4,8(s0) # 0x40020000
    MEM_BASE[6] = 0x0000006f; // 18: j   0x18

    //TEST start
    srand(100);
    int all_ok = 1;

    for(int i = 0; i < 100; i++){
        float a = (rand()%100)/100.0f;
        float b = (rand()%100)/100.0f;
        //set input data
        MEM_BASE[0+4096] = float_as_uint(a);
        MEM_BASE[1+4096] = float_as_uint(b);
        //reset to launch RISC-V core
        pl_resetn_1();
        //wait RISC-V execution completion by waiting some period or using polling
        usleep(100);
        //get output data
        unsigned int _c = MEM_BASE[2+4096];
        float c = uint_as_float(_c);
        printf("%f+%f=%f:", a, b, c);
        if (a + b == c){
            printf("OK\n");
        } else {
            printf("NG\n");
            all_ok = 0;
        }
    }
    if (all_ok) printf("ALL PASSED\n");
    close(fd);
    return 0;
}

全ての結果が0のままでエラーになってしまった。
どこに問題があるのかを考え中。今だにPS/PL間のデータのやり取りの方法やZynqのアーキテクチャを全く理解できていないように思う・・