HLS第二十八课(UG871,接口综合)

接口的返回值,入口参数,都是需要用pragma约束的。

首先来回顾一下接口。
a clock and reset have been added to the design:
ap_clk and ap_rst.
A block-level I/O protocol has been added to control the RTL design: ports
ap_start, ap_idle and ap_done, ap_ready.

The default block-level I/O protocol is called
ap_ctrl_hs.
另一个常用的选项是ap_ctrl_none。
When the interface protocol ap_ctrl_none is used, no block-level I/O protocols areadded to the design. The only ports are those for the clock, reset and the data ports.

+++++++++++++++++++++++++++++++++++
来看第一个例子。

int adders(int in1, int in2, int in3) {
#pragma HLS INTERFACE ap_ctrl_none port=return

#pragma HLS INTERFACE ap_none port=in3
#pragma HLS INTERFACE ap_none port=in2
#pragma HLS INTERFACE ap_none port=in1


	int sum;
	
	sum = in1 + in2 + in3;
	
	return sum;

}

ap_ctrl接口的作用是和其他模块握手,告诉其他模块当前模块的FSM的状态。

#pragma HLS INTERFACE ap_ctrl_none port=return

ap_ctrl接口,是约束到return上的。

再来看看port-level。
对于纯输入信号,常用的是ap_none。
ap_none is the default I/O protocol for these C arguments.

#pragma HLS INTERFACE ap_none port=in1

表示当FSM运行在start for input状态时,会从port拍入数据。
这里,参数是以形参实体变量形式给出的,在实现时,被实现为一个single direction的input port。
模块内部可以配置一个reg。

这个例子中,模块内例化了两个加法处理器,最后将结果传输到return。

++++++++++++++++++++++++++++++++++++++++
第二个例子

void adders_io(int in1, int in2, int *in_out1) {
	*in_out1 = in1 + in2 + *in_out1;
}

第三个参数是形参指针,也就是说,它具备双向inout的能力,
返回值return是void的,也就是说,return可以不提供data port。

对于in1和in2,它们仍然是纯形参实体变量,input port。
可以选择ap_none,则只有input data port,没有任何input handshake port。这是默认选项,所以可以不加约束也是一样的效果。
也可以选择ap_stable,它类似于ap_none,区别在于,被认为是一个正常操作期间是恒定的值,它不是在FSM的start for input 状态读取,而是在模块复位后,在input after rst状态读取的。
所以,如果要使ap_stable的值生效,需要reset模块。通常用于配置常数参数。

也可以选择ap_vld,由前级模块发出有效信号。

#pragma HLS INTERFACE ap_vld port=in1

如果想向前级传递确认信号,可以选择ap_ack,

#pragma HLS INTERFACE ap_ack port=in2

对于inout的形参指针,可以选择ap_hs,那么这个接口具有最完备的握手信号集,支持双向传输。
既有input lane 还有output lane,两套data port。
in_out_i, in_out_i_ap_vld, in_out_i_ack,
in_out_o, in_out_o_ap_vld, in_out_o_ack,

在本例中,可以看出,它作为inout在使用,所以它的ap_hs握手,会有一个ovld来指示输出有效数据。

#pragma HLS INTERFACE ap_hs port=in_out1

本例中,虽然return端口是void,但是ap_ctrl_hs是默认模式,所以仍然会有
ap_start, ap_idle and ap_done, ap_ready.

++++++++++++++++++++++++++++++++++++++
第三个例子。

void array_io (dout_t d_o[N], din_t d_i[N]) {
	int i, rem;
	
	// Store accumulated data
	static dacc_t acc[CHANNELS];
	dacc_t temp;

	// Accumulate each channel
	For_Loop: for (i=0;i<N;i++) {
		rem=i%CHANNELS;
		temp = acc[rem] + d_i[i];
		acc[rem] = temp;
		d_o[i] = acc[rem];
	}
}

两个形参数组,也即形参指针。
模块内部定义了一个static array,这将被实现为BRAM。
数组是一个一维的向量,所以,需要在一个for循环里,对一维向量的每个元素进行逐个处理。

数组在HLS中,默认被实现为SPRAM。
所以,形参数组,被默认实现为RAM port,因为假设的是RAM存在于模块外部。
这时,默认使用的接口约束是ap_memory。
RAM port理论上是有 read channel和write channel的。
在本例中,d_o这个RAM port,只被用来write,所以,它的read channel被省去了。
只有d_o_d0,d_o_addr0,d_o_we0,d_o_ce0。
在本例中,d_i这个RAM_port,只被用来read,所以,它的write channel被省去了。
只有d_i_q0,d_i_addr0,d_i_ce0.

the rolled for-loop ensure only one data sample can be read (or written) at a time.
for循环默认被理解为一个顺序执行的FSM,如果要提高并行度,就需要UNROLL FOR LOOP。

#pragma HLS UNROLL

被UNROLL后,同时访问RAM的需求就来了,需要给RAM多增加访问端口。

给d_i端口添加资源约束,

#pragma HLS RESOURCE variable=d_i core=RAM_2P_BRAM

注意,这里给端口添加资源约束,并不是模块内将端口用此资源来实现,而是说,假定模块外部,连接到此端口上的资源类型。
有,
d_i_q0,d_i_addr0,d_i_ce0,
d_i_q1,d_i_addr1,d_i_ce1,

给d_o端口添加接口约束,

#pragma HLS INTERFACE ap_fifo port=d_o

这里,将d_o端口约束为ap_fifo,并不是模块内将端口用一个FIFO来实现,而是说,假定模块外部,连接到此端口上的,是一个FIFO,所以d_o需要呈现为一个ap_fifo接口。
d_o_din, d_o_write, d_o_full_n,

这里,说明一点,形参数组,本质上是形参指针,可以被实现为RAM接口,也可以实现为FIFO接口。

之前使用SPRAM时,II是66,使用了DPRAM时,II是33,II降了一半,
要想更大程度的降低II,还需要继续增大RAM的访问接口,

给d_o端口,继续添加约束,添加数组分割约束,

#pragma HLS ARRAY_PARTITION variable=d_o block factor=4 dim=1

这个约束,使得d_o端口,将被分割成多个子端口,每个子端口,都被实现为FIFO接口。
d_o_din0, d_o_write0, d_o_full_n0,
d_o_din1, d_o_write1, d_o_full_n1,
d_o_din2, d_o_write2, d_o_full_n2,
d_o_din3, d_o_write3, d_o_full_n3,

继续添加约束,
给d_i端口,添加数组分割约束,

#pragma HLS ARRAY_PARTITION variable=d_i block factor=2 dim=1

这个约束,使得d_o端口,将被分割成多个子端口,每个子端口,都被实现为DPRAM接口。
d_i_0_q0,d_i_0_addr0,d_i_0_ce0,
d_i_0_q1,d_i_0_addr1,d_i_0_ce1,
d_i_1_q0,d_i_1_addr0,d_i_1_ce0,
d_i_1_q1,d_i_1_addr1,d_i_1_ce1,

现在,II被降到了17,
如果还想最大程度的降低II,可以考虑complete模式的数组分割约束。

#pragma HLS ARRAY_PARTITION variable=d_o complete dim=1
#pragma HLS ARRAY_PARTITION variable=d_i complete dim=1
#pragma HLS ARRAY_PARTITION variable=acc complete dim=1

d_i被完全分割时,就需要去掉之前的资源约束了,删除RAM_2p_BRAM的资源约束。
通常也是不推荐使用资源约束的。

此时,II降低到了1。
注意,谨慎使用资源约束。

可以看到,综合后,d_o数组每个元素都有一套自己的FIFO接口。

注意,HLS在约束时,有两个概念,一个是variable,一个是port,在模块内部,大多数情况下,去识别variable,只有在top层的模块,会有从形参variable衍生出的同名的port,这些portname,最终会以端口prefix前缀的形式存在。

+++++++++++++++++++++++++++++++++++++++++
第四个例子。

void axi_interfaces (dout_t d_o[N], din_t d_i[N]) {
	int i, rem;
	
	// Store accumulated data
	static dacc_t acc[CHANNELS];

	// Accumulate each channel
	For_Loop: for (i=0;i<N;i++) {
		rem=i%CHANNELS;
		acc[rem] = acc[rem] + d_i[i];
		d_o[i] = acc[rem];
	}
}

首先,这里面有for循环,为了提高效率,首先要将for循环UNROLL。

#pragma HLS UNROLL factor=8

展平成8倍面积。
Select a factor of 8 to partially unroll the for-loop. This is equivalent to re-writing the C code to execute eight copies of the loop-body in each iteration of the loop (where the new loop only executes for 4 iterations in total, not 32,now i+=8).

然后,为了增加访问接口,进行数组分割,

#pragma HLS ARRAY_PARTITION variable=d_o cyclic factor=8 dim=1

这个约束,将d_o形参数组,分割成8个子数组,这次是以cyclic交替形式分割的。这也是实现为stream的推荐分割方式。

然后,为d_o形参数组,约束接口形式。将d_o形参数组,对外的接口形式,约束为axis形式。

#pragma HLS INTERFACE axis register both port=d_o

同样的,为d_i增加数组分割约束和接口约束。

#pragma HLS ARRAY_PARTITION variable=d_i cyclic factor=8 dim=1
#pragma HLS INTERFACE axis register both port=d_i

When the report opens in the information pane, confirm both d_i and d_o are implemented as eight separate AXI4-Stream ports.
d_o_0_AXIS,d_o_1_AXIS,d_o_2_AXIS,d_o_3_AXIS,
d_o_4_AXIS,d_o_5_AXIS,d_o_6_AXIS,d_o_7_AXIS,
d_i_0_AXIS,d_i_1_AXIS,d_i_2_AXIS,d_i_3_AXIS,
d_i_4_AXIS,d_i_5_AXIS,d_i_6_AXIS,d_i_7_AXIS,

此时的II,降低为9,
为了进一步降低II,还要继续对for循环优化,
虽然UNROLL了循环体,使其平铺了8路处理机,但是处理机内部的,仍然是顺序执行的,
在第一个操作步处理完后,只能等待其他后续操作步处理完,并再一次调度到首个操作步时,才能再次接收输入。
这种情况下,可以使用pipeline优化for循环。
使用了流水线约束后,循环体处理机在被综合时,不再是被综合成一个顺序FSM,分别调度每个操作步,而是将每个操作步综合成独立的处理机,每个操作步拥有自己的FSM。操作步之间,通过握手及FIFO实现同步。

#pragma HLS PIPELINE  rewind

指定II的目标是1,也可以不指定,因为默认II就是1.
rewind使得流水线连续向前运转。无间隔执行。
When the top-level of the design is a loop, you can use the pipeline rewind option.
when implemented in RTL, this loop runs continuously (with no end of function and function re-start cycles).
一般场合中,rewind并不常用。因为通常模块都需要re-start。

此时的II,降为4,

一次完整的数组输入,是32个数据,分为8个端口,共需要4次才能完整输入全部数据。

+++++++++++++++++++++++++++++++++++++++++
第五个例子。

void axi_interfaces (dout_t d_o[N], din_t d_i[N]) {
	int i, rem;
	
	// Store accumulated data
	static dacc_t acc[CHANNELS];

	// Accumulate each channel
	For_Loop: for (i=0;i<N;i++) {
		rem=i%CHANNELS;
		acc[rem] = acc[rem] + d_i[i];
		d_o[i] = acc[rem];
	}
}

首先,这里面有for循环,为了提高效率,首先要将for循环UNROLL。

#pragma HLS UNROLL factor=8

展平成8倍面积。
Select a factor of 8 to partially unroll the for-loop. This is equivalent to re-writing the C code to execute eight copies of the loop-body in each iteration of the loop (where the new loop only executes for 4 iterations in total, not 32,now i+=8).

再使用pipeline优化for循环。

#pragma HLS PIPELINE  rewind

然后,为了增加访问接口,进行数组分割,

#pragma HLS ARRAY_PARTITION variable=d_i cyclic factor=8 dim=1
#pragma HLS ARRAY_PARTITION variable=d_o cyclic factor=8 dim=1

将return的访问,以及ap_ctrl的访问,收纳到s_axi_lite总线中去,ap_ctrl和return,不再以IO端口的形式存在,而是位于s_axilite总线中的register。

#pragma HLS INTERFACE s_axilite port=return

Guess you like

Origin blog.csdn.net/weixin_42418557/article/details/121047359
Recommended