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側からアクセスできないみたい。
うう、、、それはハードコーディングですね。もしこのアドレスが変更可能であれば、PS 側の DRAM 領域内にしてください。例えば Ultra96 ならば 0x7F00_0000 - 0x7FFF_FFFF とかに。というのも、PS 側 にはそもそも0xA000_0000 にはメモリが無いので、共有することができません。
— 隠居したエンジニア (@ikwzm) January 5, 2023
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のアーキテクチャを全く理解できていないように思う・・