首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

学习vivado第7章Lab1——设计优化

学习vivado第7章Lab1——设计优化




概述
创建高质量RTL设计的一个关键部分是采用高层次综合拥有优化C代码的能力。高层次综合经常尝试减少循环和函数的延迟。在循环和函数中为了获得这种目的,高层次综合尝试执行尽可能多的并行操作。在函数层,高层次综合经常尝试执行并行函数。
除了这些自动优化,指令用于:
•并行执行多个任务,例如,相同的函数多次执行或相同的循环多次迭代。采用流水线。
•调整数组(块RAM),,函数,循环和端口的物理实现用以提高数据的利用率和帮助数据流尽快通过设计。
•提供的数据相依性的信息,或缺少这些信息,从而可进行更多优化。
最终的优化技术是修改的C源代码,以删除非预期依赖关系的代码,这种代码可能限制了硬件的性能
教程是由两个实验练习组成。你可以在这些实验练习中用Analysis perspective来执行分析。前提条件是完成了本教程的Design Analysis教程
Lab1
参照循环和函数流水线的使用来创建的设计能够在一个时钟处理一个样本。这个实验包括一些例子,这些例子给您机会去分析两个通常引起不能满足性能要求导致设计失败的原因:循环依赖关系和数据流限制或瓶颈。
Lab2
这个实验展示了怎么从lab1中修改代码来帮助克服一些在代码中无意识存在的内在的性能限制。
教程设计描述
从xilinx网站下载教程设计文件,在教程设计中查看信息。教程所用的设计文件在教程目录vivado_HLS_Tutorial\Design_Optimization
你在实验练习中使用的样本设计是一个矩阵乘法功能。设计目标是在每个时钟周期处理一个新的采样,实现的接口作为数据流传输接口。
Lab1:优化矩阵乘法器
这个练习使用矩阵乘法器设计用以展示你如何全面优化设计重点是在循环设计上,设计的目标是用FIFO接口在每个时钟周期读一个样本,同时使用最少资源。
这个分析包括了在使用函数级优化的循环级优化方法的比较。
这个练习解释了用户分析界面视角的基本操作,还有你如何用这个界面来驱动设计优化。
重要:在教程中的图片和命令假设教程数据路径vivado_HLS_Tutorial被解压放置在c:\vivado_HLS_Tutorial中。
如果教程数据目录解压缩到不同的位置,或者在Linux系统上,调整一些路径名引用到的位置您选择放置Vivado_HLS_Tutorial目录。
步骤1:创建并打开工程
1.打开Vivado HLS 命令提示符
a.在windows系统中,采用Start>All Programs>Xilinx Design Tools>Vivado2014.2>Vivado HLS>Vivado HLS 2014.2 Command Prompt,如下图
b.在linux系统下,打开新的shell,

2. 用命令提示符窗口,如图142,从lab1中将目录切换到RTL Verification教程。
3. 执行TCL并建立vivado HLS Project,采用的是vivado_hls–f run_hls.tcl如图142所示

4. 当vivado HLS 完成,在用户界面里打开工程。用vivado_hls–p matrixmul_prj命令打开,如图143

5. 在资源管理器中打开Source文件夹,并双击matrixmul.cpp文件如图144的源代码
向下滚动文件,查看源代码有两个输入数组a和b,并输出数组。将鼠标停在宏(如图144),看看每个是3×3共9个元素。

步骤2:综合分析设计
1.        在工具栏中点击Run C Synthesis按钮,并把设计综合成RTL。当综合完成,综合报告打开图145,性能估计显示:
•间隔是80个时钟,因为在每个输入数组中有九个元素,设计在输入读时大约花费9时钟。
•间隔比延迟时间的长一个周期,因此在这一点上的硬件不是对应的。
•延迟/间隔是由于内嵌的循环:
        内部循环调用
-        2时钟的延迟
-        所有的迭代花费6个时钟周期
        Col循环
-        要求1个时钟周期输入循环积和1个时钟的推出
-        每次迭代花费8个时钟。
-        所有迭代完成花费24个周期
        顶层循环每次迭代要延迟26个时钟周期,这个循环所有迭代要花费78时钟周期


你可以做两件事情来提高启动间隔:循环流水线或对函数整体进行流水线操作。你开始于循环流水线化的结果和函数整体流水线化的结果进行比较。
当循环流水线化,循环的启动间隔是重要的监控标准。在这个练习中可以看到,即使当设计达到了该循环在每个时钟周期可处理一个样品的阶段,该函数的启动间隔仍被报告,因为它需要对包含在函数内的循环完成处理所有函数数据的时间进行报告。
步骤3:乘积循环流水线
1.        在工具栏上选择New Solution按钮,或者用菜单Project>New Solution来创建一个新的方案solution2
2.        点击Finish,接受默认创建solution2。
3.        保证能在信息窗口中看见C源代码
当流水线嵌套循环时,你会发现嵌套在流水线里用以处理样本数据的循环有较大的好处。高层次综合会自动把循环打平,打平嵌套循环,消除循环转换(基本上是在一个循环内创造更多地迭代,但整体用较少的时钟周期)
4.        在指令标签中。
a.        选择Product循环
b.        右击,并选择Insert Directive
c.        在Directives Editor对话框中,在对话框的上部激活Directives下拉菜单,并选择PIPELINE。
d.        点击OK,默认选项,以1为初始间隔被默认,(每个时钟一次新循环迭代)
指令窗口展示了如下的优化指令(新指令是高亮的)

5. 在工具栏中点击Run C Synthesis按钮,并把设计综合成RTL。
在综合过程中,在控制台中的信息报告展示了循环打平是指对ROW循环执行,默认初始内部目标为1,不能在Product循环内实现,由于依赖关系。


综合报告如图147,Product循环是流水线,间隔为2,顶层循环的间隔不是流水线的。
顶层循环不是流水线的原因是循环打平发生在Row循环。没有对Product循环内的Col循环进行打平。要理解为什么循环打平没有打平所有内嵌的循环,使用Analysis视图。
6. 打开Analysis视图
7. 在性能窗中(performance View),展开Row_Col和Product循环
8. 选择write operation in state C1


9. 右击并选择Goto Source 查看图148


在C1状态的写操作是由于代码设置res为0在Product循环前。因为res是顶层函数参数,往RTL端口写:这个操作必须发生在Product循环操作执行前。因为它不是一个内部操作,但影响I/O行为,这个操作不能被移除或优化。这个操作阻止在Col循环内的Product循环被打平。
更重要的是,值得探寻的是为什么对Product循环来说仅间隔为2是可能的。
消息SCHED-68告诉你


•这个问题表明是有依赖关系的。依赖关系是在一个循环的一次迭代的操作和在相同循环不同的迭代的操作之间。例如,当k=1时和当k=2时的操作(k是循环索引)。
•在60行进行第一次操作,存储(存储器读操作)到数组res中
•在60行进行第二次操作从数组res中加载(内存写操作)。
从图148可以看到第60行是从数组内存中读(由于+ =运算符),并写入数组内存中。数组被映射到一个默认的块RAM和性能视图的细节可以说明为什么发生这种冲突。
性能视图显示了在其中规定的操作安排。图149显示了许多的Product循环安排操作的副本,高亮显示这个问题是如何被理解的。关于res数组的操作突出显示了两个周期的读取和写入。
在成功的调度中,Product循环的下一次迭代显示如下。在这样的调度中,初始化间隔=2,并且循环操作每两个周期重启。在一些块Ram接口中没有冲突。(没有高亮显示单元格,关于各次迭代的重叠)
不成功的调度显示了为什么循环不能在间隔II=1下流水线化。在这种情况下,下一次迭代需要启动在1个时钟周期以后。当第二次迭代试着从地址中开始进行读时,第一次迭代中往块RAM中写的动作依然在进行。这些地址不一样。在同一时间不能被同时施加到RAM块上。


在初始间隔为1时,不能使Product循环流水线化。下一个实验练习展示了如何重写代码来消除这种限制。(一些技术不让回写到同一个数组或ram块中)。在这个实验练习中,你可以优化代码来这样做。
下一步是把上层循环流水线化。Col循环。自动展开Product循环并创建更多的操作。因此使用更多地硬件资源,但是能保证在Product循环的不同迭代之间没有依赖性。
10. 返回综合视窗
步骤4:Col 循环流水线化
1.        在工具栏上选择New Solution按钮,或者用菜单Project>New Solution来创建一个新的方案solution3
2.        因为solution2已经添加指令。用下拉菜单选择select solution1作为源,(solution1没有添加指令和约束)。
3.        点击Finish,接受默认创建solution3。
4.        打开C源代码matrixmul.cpp让它在信息窗口中显示。
5.        在指令标签中
a.        选择Col循环(loop Col)
b.        右击并选择Insert Directive
c.        在Directives Editor对话框中,激活Directives下拉菜单选择PIPELINE
d.        点击OK,默认选项。初始间隔为1为默认的,(每个时钟一次新的循环迭代)
指令窗口展示了如下的优化指令(新指令是高亮的)


6.        在工具栏中点击Run C Synthesis按钮,并把设计综合成RTL
在综合过程中,信息报告在控制台窗口展示,可以看到Product循环被展开,在Row循环上循环被打平。默认的初始间隔目标没有在Row_Col循环上达到由于在内存数组a的资源限制。



重新查看综合报告,注意以上,循环Row_Col循环的间隔仅有两个:目标是一个周期处理一个样本。再次,你可以用分析视图来突出为什么开始的目标没有实现。
7.        打开Analysis视图
8.        在Performance View中,扩展Row_Col循环
数组操作时高亮的在图151中(在SCHED-69消息中),对于数组a有3个读操作。2个操作在C1启动。第三个读操作在C2启动。
数组作为一个RAM块被实现。参数传递给函数的数组被实现为块RAM的端口。在这两种情况下,RAM块只能有一个最大的两个端口(用于双端口RAM块)。通过一个单独的块RAM接口访问阵列的,没有足够的端口,以便在一个时钟周期能够读出三个所有值。
另一种方法是查看该资源的限制是使用的资源窗格
9.        点击Resource tab


10.        扩展内存如图152
在图152中,在状态1中2周期读操作,与那些在状态C2开始有重叠。因此仅表现出一个单周期。然而,很显然,该资源被用在多个状态。
在看这个观点,很显然,即使该端口的问题得到解决了,b口出现了同样的问题:它也有执行3次读。
高层综合一次只能报告一个计划错误或警告,因为一旦出现第一个问题,创建一个可实现调度的行动无效,其他调度就不能安排了。
高层综合允许数组进行分区,映射在一起,重新塑造。这些技术允许在不改变源代码被修改的访问数组。
11.        返回综合窗口


步骤5 :数组变换
1.        在工具栏上选择New Solution按钮,或者用菜单Project>New Solution来创建一个新的方案solution4
2.        点击Finish,接受默认创建solution4。
因为Product循环的循环序号是k,两个数组必须分开由于各自的K维度:设计在每个时钟需要获取超过两个的k值。
对于数组a,维度为2,因为它使用的模式是a[k];对于数组b,维度是1,因为它使用的模式是b[k][j]。
分开这两个数组,创建一个k数组,在这种情况下,k序号端口。作为一种选择,我们能够用重塑替代分区,容许创建一个宽度的数组来替代k端口。
在这次转化后,在这个模块以外的在块RAM中的数据必须以相同的方式重塑:如果HLS不完整这个过程,数据必须被重置:
•对数组a:i元素,k次每个宽度data_word_size
•对数组b:j元素,k次每个宽度data_word_size
3.        打开C源代码matrixmul.cpp让它在信息窗口中显示。
4.        在指令标签中
a.        选择variable a
b.        右击并选择Insert Directive
c.        在Directives Editor对话框中,激活Directives下拉菜单选择ARRAY_RESHAPE
d.        设置维度为2
e.        点击OK
5.        对变量b重复这个过程,并设置维度为1 图153
指令窗口展示了如下的优化指令(新指令是高亮的)


6.        在工具栏中点击Run C Synthesis按钮,并把设计综合成RTL
综合报告展示了顶层Row_Col循环是在每个时钟周期处理一个样本数据。图154


•顶层模块完成花费12时钟。
•Row_Col循环输出一个样本在3个周期后(迭代延迟)
•每个周期读取一个样本(初始间隔)
•9次迭代后完成所有的样本(经过9次迭代/样品(行程数),它完成所有样品)
•3+9=12周期
该函数完成,并返回到开始处理下一组数据。现在,为了实现数据流,改变的块RAM接口为FIFO接口。
步骤6 :应用FIFO接口
1.        在工具栏上选择New Solution按钮,或者用菜单Project>New Solution来创建一个新的方案solution5
2.        点击Finish,接受默认创建solution5。
3.        打开C源代码matrixmul.cpp让它在信息窗口中显示。
4.        在指令标签中
a.        选择variable a
b.        右击并选择Insert Directive
c.        在Directives Editor对话框中,激活Directives下拉菜单选择INTERFACE
d.        点击Mode下拉菜单选择ap_fifo
e.        点击OK
5.        对b和变量res重复上述过程。
指令窗口展示了如下的优化指令(新指令是高亮的)图155


6.        在工具栏中点击Run C Synthesis按钮,并把设计综合成RTL
图156在综合运行后控制台显示如下


在图157中从代码中,数组res执行写按照以下顺序(MAT_B_COLS=MAT_B_ROWS=3)
•在57行写[0][0]
•在60行写[0][0]
•在60行写[0][0]
•在60行写[0][0]
•在57行写[0][1](序号J)
•在60行写[0][1]
•Etc
连续四次往地址[0] [0]写入,不构成流式访问的模式;这是随机存取


如图157检查代码可以发现读数组a和b类似的问题。它不可能使用一个FIFO接口用于与代码数据访问作为写入。要使用FIFO接口,在Vivado高层次综合中用优化指令是不够的,因为当前的代码强制了一定的读写顺序。进一步优化需要重新编写代码,这在实验2完成。
修改代码之前,但是,用流水线的功能取代循环这是值得,并对比这两种方法的区别。
步骤7 :函数流水线化
1.        在工具栏上选择New Solution按钮,或者用菜单Project>New Solution来创建一个新的方案solution6
重点:在这一布,从solution4复制指令作为这个解决方案,这个解决方案没有制定FIFO接口。
2.        从下拉菜单选择solution4在Options部分中。解决方案显示如158


3.        点击Finish,接受默认创建solution6。
4.        打开C源代码matrixmul.cpp让它在信息窗口中显示。
5.        在指令标签中
a.        在循环Col上选择pipeline指令
b.        右击并选择Remove Directive
c.        选择顶层函数matrixmul
d.        右击并选择Insert Directive
e.        在Directives Editor对话框中激活Directives下拉菜单并选择PIPELINW
f.        点击OK
指令标签显示如下图159


6.        在工具栏中点击Run C Synthesis按钮,并把设计综合成RTL
7.        点击Compare Reports 工具栏按钮
a.        添加solution4
b.        添加solution6
c.        点击ok
方案4和6比较如160


设计现在完成用少量的时钟,并能在5个时钟上能启动一次新的事物。但是面积和资源都增加因为所有的循环都展开了


流水线循环使得循环以保持不展开,从而提供了控制资源的良好手段。当流水线的功能,包含在该函数的所有循环被展开,这是流水线的要求。流水线功能设计可以处理一组新的9个样品,每5个时钟周期。这超过每秒1样品的要求,因为高层次综合的默认行为是产生具有最高性能的设计。
流水线功能可获得最佳性能。然而,如果它超过了所需的性能,但可能需要多个附加指令以减缓设计了下来。流水线循环给你一个简单的方法来控制资源,以部分地展开设计,以满足性能的选项。



记录学习中的点点滴滴,让每一天过的更加有意义!
返回列表