HLS #2 使用xfopencv中的sobel算例

  对了,我最近开通了微信公众号,计划是两边会同步更新,并逐步的会将博客上的文章同步至公众号中。
感兴趣的朋友可以扫描下方的二维码或者搜索“里先森sements”来关注,欢迎来玩~!


本篇文章介绍了如何在HLS(2017.4)工具下,使用赛灵思针对优化过的xfopencv图像处理库里的sobel算例,开发一个可以部署到FPGA进行sobel运算的IP。在本例中,sobel加速运算IP使用了AXIStream作为图像数据的输入以及输出,使用AXILite作为控制接口。

目录

0 - 准备工作

1 - 新建一个HLS工程

2 - 稍作修改

3 - 添加AXIStream与AXILite接口

4 - 修改Testbench

5 - 优化

6 - 生成IP


0 - 准备工作

这里需要使用到两个软件工具\资源:

  • HLS(这里我使用的是2017.4版本,如果使用较低的版本请确保支持hls::stream等函数)
  • xilinx xfopencv(赛灵思针对使用HLS在FPGA上部署图像运算而专门进行优化开发的 Xinlinx Fpga OPENCV 库)

xfopencv的下载链接可以访问xilinx的github进行下载(GitHub - Xilinx/xfopencv
https://github.com/Xilinx/xfopencv

下载压缩包后,解压到工作目录,这里我的工作目录如下,xfopencv-master旁边的是HLS的工程目录

1 - 新建一个HLS工程

我们在工作目录新建一个工程文件夹,xf_sobel_test。将xfopencv-master/example/sobelfilter复制到xf_sobel_test文件夹中

打开HLS,在xf_sobel_test工程文件夹下建立HLS工程文件夹,命名为xls_sobel_test

添加TOP函数所在的cpp文件 xf_sobel_accel.cpp

添加testbench测试文件 xf_sobel_te.cpp 与data中的测试图片data/im0.jpg

添加完毕后的目录如下所示

2 - 稍作修改

这里为了方便后面部署,我们这里稍微修改一下

我们待测试的输入图像大小为360*202,所以这里我们要将测试图片 im0.jpg 的大小像素修改为360*202

同时,我们还需要将测试文件testbench中用到的宏定义 WIDTH 和 HEIGHT 进行相应的修改

这里如果WIDTH与HEIGHT大小设置不当,可能导致SIGSEGV报错,或者导致hls::stream<ap_axiu<8, 1, 1, 1> >.1' is read while empty之类的错误

HLS在windows运行环境下,栈的大小只有8192K。而这里设置的WIDTH和HEIGHT参数会导致后面HLS在进行C模拟仿真时申请图像大小空间(申请到的大小并非实际图像占用大小)。在这个样例中,你可以设置为960与720,更大的可能会导致C仿真报SIGSEGV错误

在xls_sobel_test上右键,打开项目设置

给仿真文件添加编译环境变量

注意:这里是使用的 xfopencv 2018 版本的,因此此后面的CFLAG应该设为

simulation:-D__XFCV_HLS_MODE__ -I你代码目录下拷贝来的xfopencv的include文件夹路径 -std=c++0x

syn:-D__XFCV_HLS_MODE__ -I你代码目录下拷贝来的xfopencv的include文件夹路径

但是在 xfopencv 2019 版本中,无论是仿真还是综合,两者的CFLAG都应该设为

-D__SDSVHLS__ -I你代码目录下拷贝来的xfopencv的include文件夹路径 --std=c++0x

-D__XFCV_HLS_MODE__ -I你代码目录下拷贝来的xfopencv的include文件夹路径 -std=c++0x

给综合文件添加编译环境变量

-D__XFCV_HLS_MODE__ -I你代码目录下拷贝来的xfopencv的include文件夹路径

两个的变量大部分相同。如果是在windows环境下使用HLS,注意给仿真文件添加环境变量要带 -std=c++0x 而给综合文件的不要带 -std=c++0x

-I 后面直接复制目录的绝对路径即可,不带空格。保存后HLS的头文件索引器会自动将绝对路径转换为较短的相对路径

运行一下c仿真,查看结果

3 - 添加AXIStream与AXILite接口

这里我们在例程的基础上改动(删了重写)

打开 xf_sobel_accel.cpp 文件,将内容修改为如下

#include "xf_sobel_config.h"

#include "common/xf_infra.h"	//xf::AXIvideo2xfMat

//inter implement function
void sobel_accel(xf::Mat<IN_TYPE, HEIGHT, WIDTH, NPC1> &_src,xf::Mat<OUT_TYPE, HEIGHT, WIDTH, NPC1> &_dstgx,xf::Mat<OUT_TYPE, HEIGHT, WIDTH, NPC1> &_dstgy)
{
	xf::Sobel<XF_BORDER_CONSTANT,FILTER_WIDTH,IN_TYPE,OUT_TYPE,HEIGHT, WIDTH,NPC1, XF_USE_URAM>(_src, _dstgx,_dstgy);
}

//outer top function
void IpSobelAccelTop(hls::stream< ap_axiu<8,1,1,1> >& _src,hls::stream< ap_axiu<8,1,1,1> >& _dst,int height,int width,bool side)
{
	xf::Mat<IN_TYPE, HEIGHT, WIDTH, NPC1> _imgInput(height,width);
	xf::Mat<IN_TYPE, HEIGHT, WIDTH, NPC1> _imgSobelx(height,width);
	xf::Mat<IN_TYPE, HEIGHT, WIDTH, NPC1> _imgSobely(height,width);

	xf::AXIvideo2xfMat(_src,_imgInput);
	sobel_accel(_imgInput,_imgSobelx,_imgSobely);

	if(side)
		xf::xfMat2AXIvideo(_imgSobelx,_dst);
	else
		xf::xfMat2AXIvideo(_imgSobely,_dst);
}

这里为了添加AXIStream,我们新写了一个 IpSobelAccelTop 函数作为新的TOP函数,记得在项目设置中更换TOP函数,该函数使用hls::stream作为图像数据的输入输出,并且使用一个bool变量来选择是进行X方向的sobel算子计算还是y方向的sobel算子计算。

为什么用hls::stream<ap_axiu<8,1,1,1>>这个类型的变量?请参考:HLS #3 - Sobel算例实例应用

IpSobelAccelTop函数里面调用了 xf::AXIvideo2xfMat 函数与 xf::xfMat2AXIvideo 来完成AXIStream与 xf::Mat 数据类型之间的转换,其内部实则还是调用原本的sobel_accel函数。

打开 Directive指令 侧栏,我们给 IpSobelAccelTop 函数指定 s_axilite 接口

给 _src 和 _dst 指定 axis接口

4 - 修改Testbench

将testbench文件内容替换为如下

#include <stdio.h>
#include <stdlib.h>

#include "xf_headers.h"
#include "xf_sobel_config.h"

#include "common/xf_axi.h"	//cvMat2AXIvideoxf

int main(int argc, char** argv)
{
	if(argc != 2)
	{
		fprintf(stderr,"Invalid Number of Arguments!\nUsage:\n");
		fprintf(stderr,"<Executable Name> <input image path> \n");
		return -1;
	}

	/*	reading in the color image	*/
	cv::Mat cvmRawImg;
	cvmRawImg = cv::imread(argv[1], 0);

	if(cvmRawImg.data == NULL)
	{
		fprintf(stderr,"cannot open cvmRawImg at %s\n",argv[1]);
		return 0;
	}
	else
	{
		printf("\n######\nargv[1]: %s\ncvmRawImg.rows=%d,cvmRawImg.cols=%d\n######\n",argv[1],cvmRawImg.rows,cvmRawImg.cols);
	}

	/*	generate opencv reference	*/
	cv::Mat cvmCvGradXImg;

	cv::Sobel(cvmRawImg,cvmCvGradXImg,CV_8U,1,0,FILTER_WIDTH,1,0,cv::BORDER_CONSTANT);
	cv::imwrite("cvmCvGradXImg.jpg",cvmCvGradXImg);

	/*	Design under test	*/
	hls::stream<ap_axiu<8,1,1,1>> _axisImgSrc,_axisImgDst;

	cv::Mat cvmIpGradXImg;
	cvmIpGradXImg.create(cvmRawImg.rows,cvmRawImg.cols,CV_8UC1);

	cvMat2AXIvideoxf<NPC1>(cvmRawImg,_axisImgSrc);
	IpSobelAccelTop(_axisImgSrc,_axisImgDst,cvmRawImg.rows,cvmRawImg.cols,true);
	AXIvideo2cvMatxf<NPC1>(_axisImgDst,cvmIpGradXImg);

	cv::imwrite("cvmIpGradXImg.jpg",cvmIpGradXImg);

	/*	judge diff	*/
	cv::Mat cvmDiffGradX;
	cvmDiffGradX.create(cvmRawImg.rows,cvmRawImg.cols,CV_8UC1);

	cv::absdiff(cvmCvGradXImg,cvmIpGradXImg,cvmDiffGradX);
	cv::imwrite("cvmDiffGradX.jpg",cvmDiffGradX);				//generate DiffImg between CvGradImg and IpGradImg

	//find min and max differences
	double _minVal = 256,_maxVal = 0;
	int _cnt = 0;

	for(int i = 0;i < cvmRawImg.rows;i++)
	{
		for(int j = 0;j < cvmRawImg.cols;j++)
		{
			unsigned char v = cvmDiffGradX.at<unsigned char>(i,j);
			if(v > 0)
				_cnt++;
			if(_minVal > v)
				_minVal = v;
			if(_maxVal < v)
				_maxVal = v;
		}
	}

	float _err = 100.0 * (float)_cnt/(cvmRawImg.rows * cvmRawImg.cols);
	printf("\n######\nMinimum error = %f\nMaximum error = %f\nPercentage of error = %f\n######\n",_minVal,_maxVal,_err);

	if(_err > 0.0f)
	{
		printf("\n######\nTest failed\n######\n");
		return 1;
	}
	else
	{
		printf("\n######\nTest passed\n######\n");
		return 0;
	}

}

这里testbench主要完成了三个操作

  • 生成软件opencv的参考图
  • 获得使用IpSobelAccelTop计算图
  • 比较参考和计算图的差异

运行一下C仿真,查看生成的diff图以及结果

可以看到,调试输出提示参考图和计算图的差值为0,仿真通过。直接查看diff图可以看到是纯黑图片。

运行一下综合,查看时延、资源消耗以及接口

可以看到,时延最大为22w,最小为7w,接口综合出了AXIStream接口以及AXILite接口

5 - 优化

这样的时延接口我们其实相对来说是不能接受的,在时钟周期为10ns的情况下,22w的时延意味着计算一个图片需要将近1.5ms

这里我们可以做以下几个优化

  • 使用 DATAFLOW 或者 PIPELINE
  • 如果使用 DATAFLOW 优化,还可以对每个Mat类型的变量使用 STREM 数据流优化
  • 对 height 以及 width 参数设置为 ap_stable 类型

全部设置完后的指令侧栏如下

运行一下综合,查看原始和优化后的报告对比

可以看到,使用DATAFLOW的 sl3_apStable 解决方案大大降低了最大时延,稳定在7w时钟周期。由于 ap_stable 的使用,资源消耗也有所减低。

6 - 生成IP

运行 Export RTL 生成IP核,放入VIVADO工程中使用

救救穷学生,5毛买个辣条也可

  • 3
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值