学习目标为:

  • 如何使用高级综合生成流水线
  • 如何使用Stratus进行层次化设计

1.生成流水线

Stratus允许指定一个主循环(while(1))中的内容为流水线方式实现,即每个时钟周期均可以进入数据执行,需要在主循环开始时添加如下语句指定使用流水线实现:

1
HLS_PIPELINE_LOOP(<STALL_TYPE>, <cycle>, <name>);

上述指定该loop为流水线实现,具有三个参数,分别如下所示:

  • STALL_TYPE:实现类型,包括HARD_STALLSOFT_STALL两种
  • cycle:数据进入间隔,即“每隔多少个时钟周期可进入一个数据”,当cycle=1时表示每个周期均可进入数据
  • name:流水线配置名称,每个循环流水线名称不同即可

对于STALL_TYPE中的两种,具有以下的区别:

  • HARD_STALL:当流水线的某一级阻塞时,整条流水线停止运行
  • SOFT_STALL:当流水线的某一级阻塞时,仅阻塞级之前的流水线停止运行,阻塞级之后的流水线继续运行

对于要生成流水线的代码片(循环体),Stratus有以下的要求:

  • 循环展开(Nested Loops):循环体中仅可以嵌套次数指定的循环,且被指定生成流水线的循环要么为无限循环,要么为指定次数循环
  • 数据依赖(Data Dependencies):需要保证每一级需要的数据在运行这一级之前已经生成,即数据流向固定向前,不存在反向数据流(产生数据冲突),若发生这种情况Stratus会报错:Unable to satisfy HLS_HLS_PIPELINE_LOOP directive "main_pipeline",possibly because of a statement in this line.
  • 端口访问(Port Access Conflicts):对于端口的访问需要谨慎,需要避免连续两个周期访问一个端口的写法,因为会产生对端口的访问冲突(前一次进入loop和后一次loop在同一周期需要访问同一个接口),这种情况会报出Warning:Pipelining forces multiple assignments to output data_out
  • 非平衡流水线(Unbalanced Protocol Blocks):避免在展开为流水线的循环中使用消耗时钟周期不同的条件判断。即若在循环中使用if-else语句,两个代码块消耗的时钟周期必须一致。
  • 循环跳出(Conditional Exits in Pipelined Loops):允许使用break语句跳出循环,但用于判断是否跳出循环的逻辑消耗的时间必须少于数据进入间隔时钟周期

学习过程使用上一次使用的+1功能电路,将其执行线程改为以下按流水线展开:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void dut_template::t() {
{
HLS_DEFINE_PROTOCOL("reset");
x_in.reset();
y_out.reset();
wait();
}
while(1) {
// HLS_PIPELINE_LOOP(HARD_STALL, 1, "main_loop");
HLS_PIPELINE_LOOP(SOFT_STALL, 1, "main_loop");
DT x_val = x_in.get();
DT out_val = x_val + 1;
y_out.put(out_val);
}
}

这里使用了输入间隔为1个周期(每个周期均可输入)的SOFT_STALL形式的流水线。

2.层次化设计

为了观察流水线功能,这次将两个+1功能模块dut_template连在一起进行仿真,顶层为pipeline_test,代码如下所所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#ifndef _DUT_PIPE
#define _DUT_PIPE

#include "cynw_p2p.h"
#include "cynw_fifo.h"
#include "defines.h"
#include "dut_template_wrap.h"

SC_MODULE(pipeline_test) {
public:
cynw_p2p<DT, ioConfig>::base_in x_in;
cynw_p2p<DT, ioConfig>::base_out y_out;
cynw_p2p<DT, ioConfig> tmp;
sc_in_clk clk;
sc_in<bool> rst;

dut_template_wrapper *ut0;
dut_template_wrapper *ut1;

SC_CTOR(pipeline_test):
x_in("x_in"),y_out("y_out"),tmp("tmp"),
clk("clk"),rst("rst") {

ut0 = new dut_template_wrapper("ut0");
ut0->clk(clk);
ut0->rst(rst);
ut0->x_in(x_in);
ut0->y_out(tmp);

ut1 = new dut_template_wrapper("ut1");
ut1->clk(clk);
ut1->rst(rst);
ut1->x_in(tmp);
ut1->y_out(y_out);
}

// void t(); 不可调用函数进行连线!!!

};

#endif

首先关注使用的p2p接口如下所示:

1
2
cynw_p2p<DT, ioConfig>::base_in x_in;
cynw_p2p<DT, ioConfig>::base_out y_out;

需要注意的是本次使用了base_inbase_out而不是inout(参考笔记1),因为这两个端口的目的仅仅为连接使用,相当于连线,因此不需要使用inout,也不需要指定时钟与复位信号。随后关注调用部分:

1
2
dut_template_wrapper *ut0;
dut_template_wrapper *ut1;

这里的调用方式为调用dut_template_wrapper而不是dut_template,这是Stratus的区别,若要在高级综合中保留层次结构,则需要在这里调用wrapper而不是本身,对应的,也需要在tcl中指定子模块dut_template为待综合模块。最后一点需要注意的是,SC_CTOR中连线部分需要在本函数中编写,不可像system中一样调用函数进行连线,否则会在仿真过程中产生问题。该设计对应的project.tcl如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
...
use_tech_lib "$LIB_PATH/$LIB_LEAF"


set_attr clock_period 10.0
set_attr message_detail 3
set_attr default_input_delay 0.1

set_attr cc_options " -g --std=c++0x"
enable_waveform_logging -vcd
set_attr end_of_sim_command "make saySimPassed"

define_system_module basic_ut/main.cpp
define_system_module basic_ut/system.cpp
define_system_module basic_ut/tb.cpp

define_hls_module pipeline_test dut_module/pipeline_test.cpp
define_hls_module dut_template dut_module/dut_template.cpp # 子模块也需要指定为待综合模块

define_io_config * TLM
define_io_config * PIN

define_hls_config pipeline_test BASIC
define_hls_config dut_template BASIC # 子模块也需要指定综合等级

define_sim_config T -io_config TLM
define_sim_config B -io_config PIN

define_sim_config H {pipeline_test RTL_V BASIC}

3.仿真结果

仿真结束后使用verdi查看波形,未添加流水线的波形如下所示:

可以发现这种情况下每两个周期才能输入一个数据,添加了流水线的波形如下所示:

添加了流水线展开后,可以发现每个时钟周期均可输入新的数据。