7010的硬核是兩個Cortex-A9,主頻666M(233333….),硬浮點+neon協處理器,性能不是很好,因為xilinx SDK可以生成底層IP的driver,所以PS裸跑起來很簡單,通過JTAG調試很方便。初期時考慮到跑linux系統時的HLS IP的driver和VDMA的driver要寫內核模塊,VDMA雖然在3.17的內核源碼已經集成了驅動,但并沒有找到詳細的相關資料,也在xilinx community上問到有人說這個驅動很坑。于是打算用AMP,一個核跑linux負責上層的相關應用,一個核裸跑+ucosii或FreeRTOS負責操作AXI總線上的外設,然而想法總是很天真的。。。弄了好長時間AMP,把CPU1成功的掛起后,并不能再跑程序,問題始終也沒找到。不過在這期間也一直在進行linux下對IP核操作的相關嘗試。最終沒用AMP也解決了這個驅動的問題,下面簡單總結下遇到的部分問題
PS系統搭建簡述
參照前一篇文章中github里的那個文檔進行操作,文件系統使用的是Linaro,在這推薦下xillybus,xillybus提供了完整的軟硬件工程,它提供的xillinux系統精簡的非常好的,系統從上電到登陸不到10秒,硬件工程除提供了HDMI/VGA顯示、音頻的IP(在xillinux系統里已經集成了驅動),還提供了一個優化的HLS接口,可以按照網站上的步驟實現一個簡單的硬件加速的例子(然而并不能走AXI4-Stream總線)網站上還有多篇文檔介紹devicetree和PCI-E。回到例子里Linaro文件系統,改動了好多地方,首先添加的bash自動補全,這個比較簡單不多說,linaro在用戶登陸后會執行一個檢查軟件更新的腳本,這個腳本運行時間巨長,導致每次ssh登陸都要等好長時間,一開始還能忍,后來用Qt Deploy的時候直接超時。。。然后在/etc/update-motd.d路徑下找到了罪魁禍首(我不會告訴你我是硬找的)。這個路徑下有兩個腳本是進行檢查更新操作的,直接注釋掉就好。然后配了一個smb方便傳輸文件。最后把交叉編譯好的opencv和Qt庫拷進去就可以進行下一步了。
分配DDR給PL
這里要注意,vdma進行數據搬運的時候需要確定的物理地址并有一定的預留空間,大家都知道linux是通過mmu將物理地址映射為虛擬地址然后給程序使用的,并且在軟件上成功申請一段大的連續的物理地址的內存概率是隨著系統運行時間的增加而降低的(內存越來越碎,dma傳輸要求是連續的),所以要在內存沒跑碎之前就留下一塊內存不給PS用,uboot可以設置linux運行使用的內存的大小,但和之前我用過的又有些不一樣,在早期的uboot里只要設置”mem”env的值就好。(之前的內核沒有devicetree機制),一開始這么試了一下,板子上的DDR為512M,我試了下”mem=510M”給PL預留了2M是可以的,但把mem改的更小的時候,內核就掛啦。。。參見之前寫的文章”Zynq Reseving Physical Memory Issue“給出了解決方法,簡單的說就是要告訴uboot和內核:devicetree要放哪呀。(順便說下要想使uboot env的修改永久生效要重新編譯遍uboot,官方提供的uboot不帶SPI Flash的驅動)。
HLS生成的IP在linux下的操作
HLS在Export IP核的時候是也會“贈”一個軟件驅動的,這個驅動給出了查詢更改IP核狀態、讀寫功能寄存器的接口,并可以按照一個標準的流程初始化IP核,流程參見之前寫的”Vivado HLS —Processor Control“。這個驅動在linux下使用需要對devicetree做些小的更改。
首先要知道這個驅動是通過UIO(userspace I/O)內核模塊進行操作的,需要內核配置為:(defconfig就是這個,檢查一下就好)
CONFIG_UIO=y
CONFIG_UIO_PDRV_GENIRQ=y
然后需要通過devicetree文件告訴內核:我的外設里有一個uio設備,它在0x某某地址上。具體的操作是在 由SDK創建的devicetree工程中修改”compatible”的值,如在我的pl.dtsi文件中
image_filter_0: image_filter@43c40000 {
compatible = "xlnx,image-filter-1.1";
interrupt-parent = <&intc>;
interrupts = <0 35 4>;
reg = <0x43c40000 0x10000>;
xlnx,s-axi-control-bus-addr-width = <0x8>;
xlnx,s-axi-control-bus-data-width = <0x20>;
};
改為:
image_filter_0: image_filter@43c40000 {
compatible = "generic-uio,uio";
interrupt-parent = <&intc>;
interrupts = <0 35 4>;
reg = <0x43c40000 0x10000>;
xlnx,s-axi-control-bus-addr-width = <0x8>;
xlnx,s-axi-control-bus-data-width = <0x20>;
};
重新生成dtb文件后更新到板子上,就可以先測試下HLS生成的IP核能不能初始化了。
VDMA在linux下的操作
vdma的driver不好用,但找到了另一個方式——函數”mmap()”,這個函數可以將物理地址映射到虛擬地址空間上。
將vdma的基址映射到虛擬地址空間上,在linux系統下就可以直接通過指針訪問vdma的各個寄存器
handle->vdmaVirtualAddress = (unsigned int*)mmap(NULL, 65535, PROT_READ | PROT_WRITE, MAP_SHARED, handle->vdmaHandler, (off_t)handle->baseAddr);
這個函數的返回值就是申請到的vdma基址的虛擬地址,這個地址加上寄存器offset就可以用來配置各個寄存器了,在使用之前要校驗下申請的地址是不是有效的
if (handle->vdmaVirtualAddress == MAP_FAILED) {
perror("vdmaVirtualAddress mapping for absolute memory access failed.\n");
return -1;
}
然后還要申請圖像存放的地址,這里根據vdma的運行方式可以配置多個地址存放多幅圖像數據。
下面是我的主程序,僅供參考:
int main() {
//variable start
int j, i;
Vec2b pix;
struct timeval tstart, tend, hls_start, hls_end;
float timeuse;
Mat src_rgb = imread(INPUT_IMAGE, 1);
Mat src_yuv(src_rgb.rows, src_rgb.cols, CV_8UC2);
Mat dst_yuv(src_rgb.rows, src_rgb.cols, CV_8UC2);
Mat dst_rgb(src_rgb.rows, src_rgb.cols, CV_8UC3);
//convert to yuv format
cvtcolor_rgb2yuv422(src_rgb, src_yuv);
IplImage src = src_yuv;
IplImage dst = dst_yuv;
//variable end
#if SW_GENERATE
printf("opencv software processing\n");
//calculate software used time
gettimeofday(&tstart, NULL);
opencv_sobel_init();
opencv_sobel(&src, &dst);
gettimeofday(&tend, NULL);
timeuse = 1000000 * (tend.tv_sec - tstart.tv_sec) + (tend.tv_usec - tstart.tv_usec);
timeuse /= 1000000;
printf("soft used time is %f\n", timeuse);
cvtColor(dst_yuv, dst_rgb, CV_YUV2BGR_YUYV);
imwrite(OUTPUT_IMAGE_GOLDEN, dst_rgb);
#endif
if (!(init_filter() == XST_SUCCESS))
{
printf("filter init faild!");
}
set_reg_filter();
// Setup VDMA handle and memory-mapped ranges
vdma_setup(&handle, 0x43000000, 640, 480, 2, 0x1f400000, 0x1f800000, 0x1fc00000);
gettimeofday(&tstart, NULL);
memcpy(handle.fb1VirtualAddress, (uchar *)src.imageData, 640 * 480 * 2);
#if MEMCPY_CHECK
printf("memcpy checking \n");
u32 memcpy_error_flag = 0;
for (i = 0; i < src_yuv.rows; i++) //row 480
{
for (j = 0; j < src_yuv.cols; j++) //col 640*2
{
pix = src_yuv.at(i, j);
if ((handle.fb1VirtualAddress[j * 2 + i * 640 * 2] != pix.val[0]) || (
handle.fb1VirtualAddress[j * 2 + i * 640 * 2 + 1] != pix.val[1]))
{
memcpy_error_flag = 1;
}
}
}
if (memcpy_error_flag == 1)
{
printf("img copy error");
return 0;
}
//memset(handle.fb1VirtualAddress, 0, handle.width * handle.height * handle.pixelLength);
printf("memcpy check result FB2:(ORI)\n");
for (j = 512; j < 512 + 20; j++) printf(" %02x", handle.fb2VirtualAddress[j]); printf("\n");
#endif
gettimeofday(&hls_start, NULL);
vdma_start_triple_buffering(&handle);
//printf("hahahaha\n");
wait_done_filter();
gettimeofday(&hls_end, NULL);
#if RESULT_CHECK
printf("RESULT CHECK FB2:(NOW)\n");
for (j = 635 * 2; j < 635 * 2 + 20; j++) printf(" %02x", handle.fb2VirtualAddress[j]); printf("\n");
//}
#endif
memcpy((uchar *)dst.imageData, handle.fb2VirtualAddress, 640 * 480 * 2);
gettimeofday(&tend, NULL);
timeuse = 1000000 * (tend.tv_sec - tstart.tv_sec) + (tend.tv_usec - tstart.tv_usec);
timeuse /= 1000000;
printf("hard total used time is %f\n", timeuse);
timeuse = 1000000 * (hls_end.tv_sec - hls_start.tv_sec) + (hls_end.tv_usec - hls_start.tv_usec);
timeuse /= 1000000;
printf("hard hls used time is %f\n", timeuse);
print_vdma_register_status();
cvtColor(dst_yuv, dst_rgb, CV_YUV2BGR_YUYV);
imwrite(OUTPUT_IMAGE, dst_rgb);
stop_filter();
// Halt VDMA and unmap memory ranges
vdma_halt(&handle);
return image_compare(OUTPUT_IMAGE, OUTPUT_IMAGE_GOLDEN);
}
vdma寄存器配置參考pg020_axi_vdma文檔
軟件編譯選項
這里單獨列出來是因為感覺在這種處理器性能不是很好的硬件平臺下進行大計算量算法的實施的情況下,一定要讓軟件以最高效率運行(盡力最高效率吧)下面是辛辛苦苦寫的Makefile,用的通配符@梅神,稍微改改就可以用在新工程上
CC=g++
CFLAGS= -g -O2 -mcpu=cortex-a9 -mfpu=neon -ftree-vectorize -mvectorize-with-neon-quad #-mfloat-abi=softfp -ffast-math
CFLAGS+=`pkg-config --cflags opencv`
LDFLAGS+=`pkg-config --libs opencv`
OBJS = $(patsubst %.c,%.o,$(wildcard *.c))
OBJS += $(patsubst %.cpp,%.o,$(wildcard *.cpp))
all: vdma_test
%.o: %.cpp
$(CC) -c $(CFLAGS) -o $@ $<
%.o: %.c
$(CC) -c $(CFLAGS) -o $@ $<
vdma_test: $(OBJS)
$(CC) -o $@ $(OBJS) $(LDFLAGS)
clean:
rm vdma_test $(OBJS)
cflag的優化配置參考的xapp1206-boost-sw-performance-zynq7soc-w-neon文檔。
評論
查看更多