[1]、lauren的FPGA(微信公众号)
[2]、Xilinx暑期学校
我们在进行HLS编译的时候会设置顶层文件,顶层文件中包括形参、返回值等等。这些映射到硬件电路中会产生信号,这些信号具体又遵循什么样的接口描述我们需要设置,这不仅可以帮助我们设置更优的约束条件使得编译出来的硬件电路更加完美,更重要的是可以让我们精确了解端口所遵循的时序,只有这样我们才可以控制输入输出数据流,进而完成一定的功能。另一方面,Vivado HLS需要提取控制逻辑构成状态机,因此会形成一些握手信号,比如ap_start,ap_done,ap_idle,ap_ready等。事实上,Vivado HLS还提供了其他的接口原型供选择。
常见的接口约束可以分为Block-Level I/O Protocols,Port-Level I/O Protocols,这两分别是对于顶层函数和顶层函数的形参来说的。每种情况又分为不同的形式如下:
先看一个简单的例子,
具体说明一下上面的ap信号,这个信号说白了就是一些握手信号,在实际中经常把这些握手信号改成AXI_Lite协议进行数据传输,具体如何改成AXI_Lite协议接下来博客中会进行相应的介绍。这里注意ap_retuen信号在ap_done有效的时候有效。
默认情况下Block-Level I/O Protocols,Port-Level I/O Protocols遵循的接口协议。
对于顶层函数握手信号ap_ctrl的设置,可是设置成三种:
1、ap_ctrl_hs:包含ap_start,ap_ready,ap_idle,ap_done四个信号。
1.1:该模块在ap_start升高之后开始操作。
1.2:在ap_start拉高之后,输出ap_idle立即变低,表示设计不再空闲。
1.3:ap_start信号必须保持高位,直到ap_ready变为高位。一旦ap_ready拉高:一、如果ap_start仍然很高,设计将启动下一个事务;二、如果ap_start被降低,设计将完成当前事务并停止操作。
1.4:可以从输入端口读取数据,输入端口可以使用独立于block-level I/O协议的端口级 port-leve I/O协议,相应的port-leve I/O协议在文章后面有相应的介绍。
1.5:可以将数据写入输出端口。输出端口可以使用独立于block-level I/O协议的port-leve I/O协议。
1.6:当块完成操作时,输出ap_done变高,如果有ap_return端口,那么当ap_done较高时,该端口上的数据是有效的。因此,ap_done信号还指示输出ap_return上的数据何时有效。
1.7:当设计准备好接收新的输入时,ap_ready信号会升高;ap_ready信号在设计开始操作之前是无效的;在非流水线设计中,ap_ready信号与ap_done同时有效;在流水线设计中,ap_start被高采样后,ap_ready信号在任何周期都可能会升高。这取决于设计是如何流水线的;如果ap_ready高时ap_start信号低,则设计执行到ap_done高时才停止操作; 如果ap_ready高时ap_start信号高,则下一个事务立即启动,设计继续运行。
1.8:ap_idle信号指示什么时候设计是空闲的、不工作的;如果ap_ready高时ap_start信号低,设计停止操作,ap_idle信号在ap_done后一个周期变高;如果ap_ready高时ap_start信号高,设计继续运行,ap_idle信号低。
上面关于时序的介绍很重要,同学们可以细读一下。如果我们只是使用,上面的信号没有那么麻烦,只需要拉高ap_start即可,至于数据什么时候输入、什么时候有效,那受相应数据的port-leve I/O协议决定。
2、ap_ctrl_none:没有任何握手信号,一般这种ap_ctrl无法得到应用。
3、ap_ctrl_chain:与ap_ctrl_hs相似,只不过比其多了一个ap_continue信号
ap_ctrl_chain block-level I/O协议与ap_ctrl_hs协议类似,但是提供了一个名为ap_continue的额外输入端口。高电平有效,ap_continuesignal表示下游块已经准备好接受新的数据输入。如果下游块不能使用新的数据输入,则ap_continue信号较低,这将阻止本模块块生成新的数据。
下游块的ap_ready端口可以直接驱动ap_continue端口。
ap_continue信号高当ap_done高时,设计继续运行。其他块级I/O信号的行为与ap_ctrl_hs块级I/O协议中描述的相同。当ap_done高时,ap_continue信号低,设计停止操作,ap_done信号高,如果ap_return端口存在,ap_return端口上的数据仍然有效。
在下面的图中,第一个事务完成,第二个事务立即启动,因为ap_done高时ap_continue高。但是,设计在第二个事务结束时停止,直到ap_continue再次有效。
由于Port-leve I/O的接口较多,我们这里简单的介绍几种,更加详细的阅读,同学们可以查看技术手册UG902,上面的介绍很详细
1、ap_none:ap_none Port-leve I/O协议是最简单的接口类型,并且没有其他与之关联的信号。输入和输出数据信号都没有关联的控制端口来指示何时读取或写入数据。
ap_none接口不需要额外的硬件开销。但是,ap_none接口需要满足以下要求:
生成模块的要求:
1.1:在正确的时间向输入端口提供数据。
1.2:保持数据不变直至本次有效传输的结束
外部使用模块的要求:
1.3:使用者块在正确的时间读取输出端口
数据不可以使用ap_none接口。
事实上我们很少使用该接口协议,因为这个接口协议对外围的控制要求太高。
2、ap_stable:像ap_none一样,ap_stable Port-leve I/O 协议没有在设计中添加任何接口控制端口。ap_stable类型通常用于在正常操作期间可以更改但保持稳定的数据,比如提供配置数据的端口。
在复位的时候这个值发生改变就用这种模式
应用到端口的数据在正常运行期间保持稳定,但是不可以是常数。接口的输出不需要寄存器寄存。ap_stable类型只能应用于输入端口。当应用到inout端口时,只有端口的输入被认为是稳定的。该接口也是不常使用。
3、ap_hs:与ap_ctrl一样包含三种协议,ap_ack,ap_vld,and ap_ovld。
ap_hs Port-leve I/O协议在开发过程中提供了最大的灵活性,允许自底向上和自顶向下的设计流。对于变量信号这是最常用的方式。
举一个例子如下:
ap_hs的时序图如下:
上面的时序图很容易读懂,详细的情况也可以参阅UG902进行学习。
对于输入信号,不存在ack信号时,如果设计已经为输入数据做好了准备,但是相应的vld为低电平,那么设计就会停止并等待输入有效值有效,以表明一个新的输入值。
存在ack信号时,当将输入有效值断言为高时,将输出确认断言为高,以表示已读取数据。
对于输出信号,不存在ack信号时,当一个输出端口被写出时,它的相关输出ovld被同时有效,以表明有效数据出现在端口上。
存在ack信号时,如果关联的ack为低电平,设计就会停止并等待ack有效。
当ack有效时,ovld信号将在下一个时钟拉低。其实这些信息从时序图种都可以学到。
4、ap_ack:ap_ack Port-leve I/O 协议是ap_hs接口类型的一个子集
在输出端口上使用ap_ack的设计将不能使用C/RTL协同仿真来验证,具体的时序在讲解ap_hs中已经做了相应的讲解。
5、ap_vld、ap_ovld都是ap_hs的子集,包含了ap_hs的一部分端口。
这里关于3、4、5,大家千万注意的一点是,他们对应的是形参,形参在一次done有效之间是不变的,这是C语言里面的语法标准,这里博主差点理解错误,按照了并行理解。这里要注意C语言是顺序执行,接收一次形参,在函数返回之前都在执行函数形参是保持不变的
6、ap_memory,这里直接给出相应的时序图
对于默认的块RAM,设计期望输入数据d_q0在下一个时钟周期中可用。可以使用RESOURCE指令来指示RAM有较长的读取延迟。
7、ap_fifo:对于这种情况下必须要求输入数据是线性输入,且只能使用一次
时序图如下:
这个应该是最容易理解的时序图。
这里简单介绍了几个常用的接口描述,更加详细的判断可以查阅UG902手册。
对函数添加Pipeline约束,顶层数组一般会从单端口RAM变成双端口RAM,如下:
直接给顶层数组形参添加单端口RAM的约束,如下:
上面的SPR其实就是Single Port RAM,也就是单端口RAM的约束。
创作不易,认为文章有帮助的同学们可以关注、点赞、转发支持。为行业贡献及其微小的一部分。对文章有什么看法或者需要更近一步交流的同学,可以加入下面的群: