前回の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>;
};
};
};
起動確認
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;
}
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);
MEM_BASE[0] = 0x40020437;
MEM_BASE[1] = 0x00040413;
MEM_BASE[2] = 0x00042607;
MEM_BASE[3] = 0x00442687;
MEM_BASE[4] = 0x00c68753;
MEM_BASE[5] = 0x00e42427;
MEM_BASE[6] = 0x0000006f;
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;
MEM_BASE[0+4096] = float_as_uint(a);
MEM_BASE[1+4096] = float_as_uint(b);
pl_resetn_1();
usleep(100);
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のアーキテクチャを全く理解できていないように思う・・