ZYNQ HLS图像处理加速总结(二)——Processing System软件部分

概述

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<Vec2b>(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文档。

  • 0
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值