解锁HLS开发|Demo(1):ARM动态配置FPGA的自定义IP


微信公众号:佛系入门ZYNQ图像处理


“狂浪是一种态度”

“八卦HLS”
本次Demo目的及功能
本次Demo原理分析
HLS开发->源文件设计
HLS开发->控制协议的“冲突”
HLS开发->仿真文件设计
HLS开发->C仿真
HLS开发->RTL综合
HLS开发->C/RTL协同仿真
HLS开发->IP核打包
VIVADO使用HLS IP->路径添加
VIVADO使用HLS IP->Zynq调用
XSDK-> HLS IP的C驱动
XSDK->中断控制器及HLS IP中断建立
XSDK->动态配置PL的Led
实验现象->视频演示

“八卦HLS”

道听途说,HLS这碗鸡汤特别的硬核。主要原因有俩:

其一,它在很大程度上降低了FPGA的开发门槛(Xilinx 7系列),支持使用C/C++取代传统的Verilog/VHDL进行FPGA的开发,这可以让那些对FPGA不是很熟知的软件开发人员,快速部署和实现算法的硬件Speedup;

其二,HLS又号称FPGA版本的OpenCV,封装了许多支持RTL综合、功能相同并且拥有相同函数原型的”OpenCV”函数,这也进一步的降低了使用ZYNQ(FPGA+ARM)加速图像处理的门槛。

所以,确认过,HLS这个东东实属硬核!
HLS简直神助攻FPGA的开发:

不过,
硬核的东西通常也有它的“任性”之处,HLS主要有俩:

其一,HLS的开发不能像传统的C/C++那样“肆无忌惮”,因为HLS的RTL综合并不支持内存的动态分配、系统层面的函数调用、复杂的数据类型(标准模板、union等)。

所以,HLS开发有一定的任性要求,需要讲究一些“技巧”。举个简单例子:一个很“大”的数组,直接去分配存储空间,这对堆栈的内存要求其实是巨大的,即使算法本身没有毛病,也很有可能导致C-Sim 或Co-Sim的仿真通不过,从而无法观测波形等后果。

其二,HLS的开发,离不开程序代码的优化,同时,没有规矩也成不了方圆,一个好的优化策略,往往还要求HLS的设计,必须刻意的符合一定的Coding Style。

比如,如何将C/C++的(多维)数组映射到FPGA的(有限)RAM/ROM;又比如,(多层嵌套)for循环或者子函数之间,如何将代码从Unperfect改进到Semi-perfect的状态,进而使用展开、流水、数据流等一系列的优化策略,这背后其实要求,具备一定的FPGA基础和算法并行的思想。

综上,HLS这个东东,硬核与任性并存!
像极了。。

Demo目的及功能

万物皆可盘。

本次Demo,不妨借助Led实验,也算是“抛砖引玉”,简单的盘一下HLS开发FPGA的流程,以及如何在ARM动态配置FPGA的HLS IP,换言之, AXI-Lite通信协议的佛系使用!!

那么,最终的实验现象如何呢?
HLS IP在ARM的配置下,可以实现FPGA四个Led的流水,同时, Led流水快慢可由ARM动态控制。

Demo原理分析

FPGA外接四个Led灯,电路如下图。显而易见,该电路是共阳的特性,只要FPGA的HLS IP在ARM的控制下,依次(延时)输出低电平,就可以实现Led流水的效果以及改变流水的快慢。

那么,问题来了:直接在FPGA例化一个现成的GPIO IP,再添加AXI接口,不也可以在ARM的控制下实现流水及其快慢程度吗?所以,有必要使用HLS这个东东吗?

答案是肯定的!假设在FPGA,例化一个GPIO IP(四个位宽),需要在ARM调用延时函数才能实现这四个Led灯的流水效果,那么,这个延时函数的计时,其实是ARM完成的!

所以,如果要FPGA实现这个延时过程,可以借助HLS,使用高效的C/C++取代传统的Verilog完成IP核的开发, 再由ARM通过AXI-Lite总线接口,传送一个函数参数至FPGA,FPGA的HLS IP根据这个参数完成延时/计时。

上面佛系讲解了,本次Demo使用HLS 开发自定义IP的必要性。

那么,问题又双叒叕来了:HLS IP确实可以帮助FPGA实现计时,但ARM怎么知道FPGA的HLS IP当前流水到了哪个状态?因为不知道当前流水到哪个状态,ARM就无法传递HLS IP下一个流水状态。

如果直接在FPGA例化一个GPIO IP,ARM其实可以很轻松的完成这个流水的赋值过程(移位操作)。那使用HLS IP的话,能不能实现同样的操作呢?

答案也是肯定的!可以借助AXI-Lite总线协议的中断功能,HLS IP每流水一次,使能ARM中断响应,ARM读取HLS IP当前的输出,更新之后(移位)再写到HLS IP的输入。(动态配置)

综上,FPGA的HLS IP完成延时,流水灯延时多久,由ARM传参决定;FPGA流水到哪个状态,由FPGA使能中断告知ARM。周而复始,流水点亮FPGA的每个Led!

HLS开发->源文件设计

如下图,头文件定义了HLS IP输入的数据类型data_in,输出类型data_out,任意精度ap_uint<4>对应FPGA的4个Led。

data_in声明的in参数,可以理解为ARM响应HLS IP的中断之后,从FPGA读取到的前一个输出(e.g:1110)。

*pl_out声明为data_out类型,可以理解为in在HLS IP内部流水之后的Led输出(1110->1101)。

那么,头文件还声明led这个函数的返回是data_out类型不直接是C/C++的Void类型,有何用处呢?(稍后会讲解)

下图的源文件,定义了FPGA现流水操作的HLS IP,将in和cnt两个参数定义为AXI-Lite接口,可以由ARM初始化和动态配置;

pl_out输出的接口指定为ap_none模式,因为指针类型的参数,默认的接口模式是ap_vld,本次Demo的Led输出可以不做数据有效性的判断,故而显式指定为ap_none模式。
(可以选择默认ap_vld,只不过RTL综合结果会多消耗寄存器)

上图的这个源文件,还将函数接口的控制协议定义为s-axilite模式(#pragma HLS INTERFACE s_axilite port=return),主要目的是让HLS IP能够产生中断告知ARM,从而更新HLS IP的输入in。

所以,对于之前的问题:函数的返回是data_out类型不直接是C/C++的Void类型,其作用就在这里!如果定义为Void类型,即使ARM响应HLS IP的中断之后,也读不到数据。

综上,整个HLS设计,相当于有两个输出(同步),一个是pl_out输出,专门驱动FPGA的Led;一个是函数返回值,HLS IP通过中断告知ARM读取这个data_out类型的ps_out返回值,ps_out代表FPGA上一次流水的结果,也是即将要写入HLS IP的输入(in)。

HLS开发->控制协议的“冲突”

实际上,在这个Demo,顶层函数接口的控制协议定义为s-axilite模式,除了上面所说的主要目的(使能HLS IP中断),还有另外的一个用处:

HLS IP的顶层接口,通常有四种“块级”的控制协议(Block-Level-protocol),包括ap-ctrl-hs,ap-ctrl-chain,ap-ctrl-none和s-axilite。对于C/RTL协同仿真,HLS只支持前面的两种(默认是ap-ctrl-hs)。

然而,本次Demo的输入参数(in和cnt)已经指定为axi-lite/s-axilite接口,如果顶层函数的控制协议,不显式的指定为s-axilite,采用默认的ap-ctrl-hs或者显示的指定为ap-ctrl-chain,HLS工程的协同仿真都会报错。

对于这个报错问题,菜鸟本人的浅见是,不能在ARM通过多个接口协议同时控制同一个HLS IP的数据端口。

那么,为什么要刻意的强调是数据端口呢?

因为,在HLS IP的控制协议显式指定为s-axilite模式的情况下,时钟信号和复位信号可以采用默认的ap-ctrl-hs或者指定为ap-ctrl-chain,进而支持C/RTL协同仿真,以便观测波形结果。

HLS开发->仿真文件设计

HLS的仿真文件有三个要求和一个注意。
Testbench三个要求:测试激励、参考模型、自检。

测试激励,顾名思义,输入到HLS IP进行仿真的数据,可以通过Testbench“在线”生成,或者数组定义,或者文件流输入。

参考模型,测试激励输入到HLS IP之后的期望输出/参考,也可以理解成C/C++层面的软件算法结果,可以事先的通过数组或者文件的形式保存好,具体可视数据量而定。

自检,HLS IP实际输出与参考模型的一一比对,允许有一定的误差,因为在软件层面,C/C++支持浮点型数据的处理,而FPGA的是定点型处理。

当然,硬核的HLS也自带一些支持浮点型运算的IP核,但是这种IP的数量极少,支持的数学运算也是特定的,或者说,这些浮点型IP并不通用。

Testbench一个注意:自检完之后,如果仿真通过,必须返回0(非1);如果仿真失败,可以返回1-255任意数值(非-1)。

所以,即使HLS IP的输出与参考模型的完全一致或在误差允许的范围之内,如果自检完毕不返回0,也会导致HLS的协同仿真通不过进而报错。

如下图,是本次Demo的Testbench仿真测试文件:

对于仿真文件的设计,安利一个小小的技巧,也算是自己踩的一个坑:

假设ARM提供给FPGA的时钟是100MHz,理论上,在FPGA应该计数100_000_000个时钟才算一秒。

但这其实是在板子上的实现,对于上图的延时参数cnt,如果在仿真过程指定成100_000_000的大小,那基本上,可以先去吃个饭,或者睡个觉,再回来查看仿真结果。

因为C-sim、Co-Sim仿真的时间单位,永远跟FPGA的实际时钟无关,只跟HLS新建工程指定的那个时钟有关(默认10纳秒)。

所以,这也是本次Demo为什么设置cnt参数比较小,100_000_000实在是伤不起!

HLS开发->C仿真

HLS的C仿真,可以说是在C/C++这个层面,对HLS的整个设计进行算法上的仿真验证(Top-Function),至于进行怎么样的仿真,完全由仿真文件Testbench决定。可以“简单”的把HLS的C仿真想象成Visual Studio或者Eclipse这样的工具。

本次Demo的HLS IP进行C仿真,结果如下:

HLS开发->RTL综合

C仿真通过,下一步进行HLS设计的RTL综合,HLS会根据用户在源文件指定的一系列优化策略(directive指令),完成C/C++到Verilog/VHDL的转换过程。

怎么优化HLS的转换过程,不在本次Demo详讲范围,留至以后Demo。

其实,这种转换过程(高层次综合),或者说FPGA的C/C++/Python开发,算得上FPGA行业发展的一种趋势。

本次Demo的综合结果如下:

从上图RTL综合可以看出,对于HLS的顶层函数data_out led(data_cnt cnt,data_in in,data_out *pl_out),函数的输入参数in和cnt并没有独立的端口。

因为这两个参数挂载到AXI-Lite总线上,也就是s-axi接口,它们由ARM直接进行配置 ;同时顶层函数还多了一个interrupt输出,也就是HLS IP的中断输出,告知ARM可以读取FPGA的输出结果。

HLS开发->C/RTL协同仿真

RTL综合之后,下一步要执行Co-Sim仿真,以便观测波形。

Co-Sim仿真的设置,如下图,可以默认使用Vivado自带的仿真功能,也可以“下拉”指定第三方的仿真软件如Modelsim等;

至于,是否要观测除顶层端口以外的其他波形,可以由Dump Trace“下拉”选项自定义,自定义务必要选择all或port!!!因为默认的是none选项,RTL综合之后,它不会输出波形文件。

本次Demo协同仿真之后,输出波形如下,可以看到FPGA的输出(pl_out端口),依次流水1110->1101->1011->0111->1110->,符合预期结果。

HLS开发->打包IP核

HLS开发自定义IP的最后一步,完成IP核的打包输出。如下图,选择Verilog语言,并在Format Selection选择IP Catalog选项,这代表HLS将以压缩包的形式输出IP核,以便在VIVADO完成IP的管理及调用。

VIVADO使用HLS IP->路径添加

完成上述的IP打包过程,会在HLS工程文件下找到压缩包的“路径”,如下图所示:

接下来,可以在VIVADO的IP Catalog(集成管理器),根据该路径添加本次Demo的HLS IP,具体步骤如下图的“操作”

VIVADO使用HLS IP->Zynq调用

VIVADO添加HLS IP之后,ZYNQ进行调用,最终的块设计,如下图:

本次Demo用到了FPGA的资源,需要根据板子的电路,给Led分配引脚(约束文件),如下图:

最后,还需要经过:比特流文件产生->导出硬件->运行XSDK等几个步骤,才能在ARM开发应用程序,完成ZYNQ系统和HLS IP的配置。

上述几个步骤,包括前面在ZYNQ调动HLS IP的块设计,篇幅有限,实在不能一一细说,一一种草。

相信使用过VIVADO工具的老铁,都掌握了这些基本操作!!!

XSDK->HLS IP的C驱动

通常,如果HLS设计带有AXI接口(S-axilite),在打包IP核输出的时候,会自动生成了HLS IP的C语言驱动程序,用户可以直接调用这些封装好的函数。

如下图,本次Demo的HLS IP驱动程序,有四部分:

第一部分,XLed_LookupConfig、XLed_CfgInitialize、XLed_Initialize等函数,完成HLS IP的查找、配置、初始化;其中XLed_Initialize是对前面两个函数的再次封装。

言外之意,初始化HLS IP,要么调用前面两个函数,要么只调用最后一个。

第二部分,XLed_Start\IsDone\IsIdle\IsReady等函数,实现RTL接口的ap_ctrl_hs控制协议,完成整个HLS IP的“顶层”控制(主要是启动)。

显而易见,这部分还有一个XLed_Get_return函数。HLS IP产生中断告知ARM之后,通过这个函数读取FPGA输出。

第三部分,HLS顶层函数对应的RTL数据端口,其中,XLed_Set_cnt_V和XLed_Set_in_V对应之前源文件添加了S-axilite接口协议的cnt和in参数。

ARM可以通过这些函数传递参数到FPGA的HLS IP,动态的配置函数参数从而改变本次Demo的流水快慢。

第四部分,管理外设中断的内部使能、全局使能和清除工作。

XSDK->中断控制器及HLS IP中断建立

借助中断控制器,可以在ZYNQ内部完成中断的建立。本次Demo是FPGA到ARM的中断,也有单独的ARM内部定时等中断。

它们的建立过程都离不开中断控制器,同时,外部中断(按键、HLS IP)和内部的定时中断,对控制器的配置都是一样的,只跟应用程序使用ARM内部的哪个CPU有关(默认是CPU0),可以把中断控制器看成ARM的外设进行初始化的配置,如下图:

初始化之后,下一步就是,外设中断的真正建立。建立过程可以指定中断的优先级;指定中断的触发类型(上升沿、高电平等);指定中断服务的函数句柄,当中断响应之后,完成服务函数的调用。如下图的“0”代表优先级最高,“3”代表是上升沿触发:
本次Demo的中断服务函数,编写如下,通过中断服务函数,完成ARM读取FPGA输出的工作:
最后,建个顶层函数,对上面的中断控制器和中断建立的子函数进行封装,方便于ARM对HLS IP的动态配置,同时根据头文件“xparameters.h”找到外设的宏定义,完成子函数的初始化如下:
XSDK->动态配置PL的Led

附上本次Demo的主函数部分,注释直接明了:

顶层的头文件声明如下:


实验现象->视频演示

ARM配置HLS IP实现FPGA的Led流水,同时,流水的快慢,可由ARM动态控制。

关注公众号:佛系入门ZYNQ图像处理,查看本次Demo录制的视频!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值