本次学习参考Stratus内置的学习例程(simple_p2p),学习内容主要如下所示:

  • Stratus HLS软件运行需要的必要文件及其写法
  • Stratus HLS软件操作方式
  • Stratus HLS内置的p2p端口的基本使用(非流水线)
  • Stratus HLS自定义数据类型

1.Stratus HLS必要文件与写法

Stratus工程所需要的文件如下图所示:

文件 类型 说明
设计文件 cpp+h 描述设计的头文件和cpp文件
TestBench cpp+h 描述测试平台的头文件和cpp文件
System cpp+h 连接设计文件和TestBench的头文件和cpp文件
main.cpp cpp 整个仿真平台的顶层文件
project.tcl tcl 指定工程配置(仿真选项和综合选项)的tcl文件
Makefile makefile 由project.tcl生成的makefile文件

1.1.设计文件

设计文件的头文件如下所示:

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
#ifndef __NEW1__H
#define __NEW1__H

#include "cynw_p2p.h" // p2p端口的头文件,如需使用cynw_p2p则需要引用该头文件

#include "new1_input_type.h" // 类型new1_INPUT_DT的头文件
#include "new1_output_type.h" // 类型new1_OUTPUT_DT的头文件

SC_MODULE(new1) { // 定义模块new1
public:
cynw_p2p < new1_INPUT_DT >::in inputs; // 一个p2p输入端口
cynw_p2p < new1_OUTPUT_DT >::out outputs; // 一个p2p输出端口

// Declaration of clock and reset parameters
sc_in_clk clk; // 时钟端口,类型为sc_in_clk
sc_in < bool > rst; // 复位端口
SC_CTOR(new1):inputs("inputs"), outputs("outputs"), clk("clk"), rst("rst") {// 构造函数
SC_CTHREAD(thread1, clk.pos()); // 定义线程thread1,绑定时钟上升沿
reset_signal_is(rst,0); // 定义复位为0时有效

// Connect the clk and rst signals to the metaports
inputs.clk_rst(clk, rst); // 绑定输入端口的时钟和复位
outputs.clk_rst(clk, rst); // 绑定输出端口的时钟和复位
}
void thread1();

new1_OUTPUT_DT my_function(new1_INPUT_DT);
};

#endif

在设计头文件中,定义了一个模块new1,具有一个p2p输入端口和一个p2p输出端口以及时钟和复位端口,并声明函数thread1为线程,为其绑定了时钟和复位,thread1的实现在cpp文件中如下所示:

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
#include "new1.h"

// The thread function for the design
void new1::thread1(){
// Reset the interfaces
{ // 复位行为部分
CYN_PROTOCOL("reset");

inputs.reset(); // 输入端口复位
outputs.reset(); // 输出端口复位

wait(); // 复位行为以wait结束
}

// Main execution loop
while (1){ // 模块行为被包括在该无限循环中
new1_INPUT_DT input_val = inputs.get(); // get为阻塞的从input端口中获取一个数据
new1_OUTPUT_DT output_val = my_function(input_val); // 执行数据处理
outputs.put(output_val); // put为阻塞的发送一个数据
}
}
//
// User's computation function
//
new1_OUTPUT_DT new1::my_function(new1_INPUT_DT var){ // 进行数据处理,处理方式为+1
new1_OUTPUT_DT my_outputs;
my_outputs.out1 = var.in1 + 1;
return (my_outputs);
}

1.2.TB文件

TestBench的头文件如下所示,其定义了一个模块tb,其他部分预设计文件类似

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
#ifndef __TB__H
#define __TB__H

#include "cynw_p2p.h"

#include "new1_input_type.h"
#include "new1_output_type.h"

SC_MODULE(tb)
{
public:
cynw_p2p < new1_OUTPUT_DT >::base_in inputs;
cynw_p2p < new1_INPUT_DT >::base_out outputs;

// Declaration of clock and reset parameters
sc_in_clk clk;
sc_out < bool > rst;
sc_in < bool > rst_in; // sampling version of "rst"

SC_CTOR(tb)
{
SC_CTHREAD(source, clk.pos());
SC_CTHREAD(sink, clk.pos());
reset_signal_is(rst_in,0);
rst_in(rst);
}
void source();
void sink();
};

#endif

tb定义了source和sink两个线程,线程描述如下所示:

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
#include "tb.h"

// Source thread
void tb::source(){
// Reset the output metaport and cycle the design's reset
outputs.reset(); // 输出端口复位

// 端口复位,拉低rst两个时钟周期后拉高
rst = 0;
wait(2);
rst = 1;
wait();

// 输入激励,这里的激励是从0发到9
for (int i = 0; i < 10; i++){
// Write values to the DUT
new1_INPUT_DT tmp;
tmp.in1 = i;
outputs.put(tmp);
}
}

// Read all the expected values from the design
void tb::sink() {
inputs.reset(); // 复位行为
wait(); // to synchronize with reset

// 接收10个输出后结束
for (int i = 0; i < 10; i++) {
// Read values from the DUT
new1_OUTPUT_DT input_val = inputs.get();
// printf("%d\n", input_val);
cerr << "Read " << input_val.out1 << "\n";
}
esc_stop();
}

可以发现TB的行为没有按设计文件编写,因为TB并需要综合,所以可以用更符合C的方式编写。

1.3.system文件

system文件用于连接TB和设计,头文件如下所示:

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
42
43
44
#ifndef SYSTEM_H_INCLUDED
#define SYSTEM_H_INCLUDED

#include <systemc.h>
#include <esc.h>
#include "cynw_p2p.h"

#include "tb.h"
#include "new1_input_type.h"
#include "new1_output_type.h"
#include "new1_wrap.h"

SC_MODULE(TOP)
{
public:
// cynw_p2p channels
cynw_p2p < new1_INPUT_DT > inputs_chan;
cynw_p2p < new1_OUTPUT_DT > outputs_chan;

// clock and reset signals
sc_clock clk;
sc_signal < bool > rst;
// The testbench and DUT modules.
new1_wrapper *m_dut; // 声明指向设计的指针
tb *m_tb; // 声明指向tb的指针

void initInstances();
void deleteInstances();

SC_CTOR(TOP): clk("clk", 5, SC_NS, 0.5, 0, SC_NS, true), // 定义时钟
inputs_chan("inputs_chan"),
outputs_chan("outputs_chan"),
rst("rst")
{
initInstances();
}

~TOP()
{
deleteInstances();
}
};

#endif // SYSTEM_H_INCLUDED

system定义了模块TOP,即整个仿真系统的顶层,使用指针的方式声明子模块,需要注意的是,Stratus会自动为设计的模块添加wrapper,因此设计指针的类型为new1_wrapper而不是new,连线的部分在cpp文件中如下所示:

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
#include "system.h"

void TOP::initInstances()
{
m_dut = new new1_wrapper("new1_wrapper");

// Connect the design module
m_dut->clk(clk);
m_dut->rst(rst);
m_dut->inputs(inputs_chan);
m_dut->outputs(outputs_chan);


// Connect the testbench
m_tb = new tb("tb");
m_tb->clk(clk);
m_tb->rst(rst);
m_tb->outputs(inputs_chan);
m_tb->inputs(outputs_chan);
}

void TOP::deleteInstances()
{
delete m_tb;
delete m_dut;
}

1.4.main

main文件用于启动仿真、连接设计和连接联合仿真等功能,一般不需要修改,main.cpp文件如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include "system.h"

TOP *top = NULL;

extern void esc_elaborate() {
top = new TOP("top");
}

extern void esc_cleanup() {
delete top;
}

int sc_main(int argc, char *argv[]) {
esc_initialize(argc, argv);
esc_elaborate();
sc_start();
return 0;
}

1.5.project.tcl

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
30
# 设置物理库信息,这里的写法可以照抄,调用的是Stratus内置的一个库
set LIB_PATH "[get_install_path]/share/stratus/techlibs/GPDK045/gsclib045_svt_v4.4/gsclib045/timing"
set LIB_LEAF "slow_vdd1v2_basicCells.lib"
use_tech_lib "$LIB_PATH/$LIB_LEAF" # 设置物理库

# set clock:设置时钟库
set_attr clock_period 5.0 # 设置时钟周期为5ns
set_attr default_input_delay 0.1 # 设置输入delay为0.1ns

# message level
set_attr message_detail 2 # 设置信息等级为2

# set dump setting:设置仿真工具信息
use_verilog_simulator incisive # 使用仿真器incisive(Stratus内置,其实为ncverilog)
set_attr cc_options " -g" # 仿真后选项,可直接照抄
enable_waveform_logging -vcd # 设置输出波形文件为vcd,还可以选择fsdb
set_attr end_of_sim_command "make saySimPassed" # 仿真后执行的命令

# system config:设置仿真平台信息,这一步需要设置所有描述不需要进行综合部分的文件为system_module
define_system_module main.cpp # 设置main为system_module
define_system_module system.cpp # 设置system部分为system_module
define_system_module tb.cpp # 设置仿真平台tb为system_module

# hls config:设置高级综合信息
define_hls_module new1 new1.cpp # 设置hls_module为new1
define_hls_config new1 BASIC # 设置hls_config为BASIC
# define_hls_config new1 DPA --dpopt_auto=op,expr

# 设置仿真信息
define_sim_config B "new1 RTL_V BASIC" # 定义仿真目标,仿真目标为RTL_V级

设置物理库到设置仿真平台信息都比较容易理解,比较复杂的是设置高级综合信息这个部分。高级综合信息的设置分为两个部分,分别是设置待综合的模块和综合等级,分别对应define_hls_moduledefine_hls_config命令。define_hls_module用于指定高级综合的对象,即指定待综合的模块和描述该模块的文件指令如下所示:

1
define_hls_module 模块名 文件名

一个例子如下所示,指定需要对new1.cpp的中包含的new1模块进行高级综合:

1
define_hls_module new1 new1.cpp

第二个部分为指定高级综合等级,高级综合具有多个等级,对应不同的性能和面积的折中,这里使用BASIC,指定指令如下所示:

1
2
define_hls_config 模块名 综合等级
define_hls_config new1 BASIC # 指定模块new1高级综合等级为BASIC

随后需要设置仿真信息,只能对一个高级综合对象进行仿真,每次进行高级综合,生成3个模型,RTL_V是其中的一种,设置仿真信息需要指定对哪一个高级综合等级的哪一个高级综合对象中的哪一个模型进行仿真,仿真指令如下所示:

1
2
define_sim_config 配置名称 "模块名称 模型类型 综合等级"
define_sim_config B "new1 RTL_V BASIC" # 定义仿真目标,仿真目标为RTL_V级

1.6.Makefile

Makefile由project.tcl直接生成,不需要手动编写

2.操作方式

2.1.makefile生成

Makefile通过project.tcl自动生成,指令如下所指示:

1
bdw_makegen project.tcl

2.2.进行高级综合

高级综合指令如下所示:

1
make hls_配置名称

例如上述脚本,高级综合指令为:

1
make hls_B

生成的文件位于bdw_work/modules文件夹下

2.3.进行仿真

进行仿真指令的命令如下所示:

1
make sim_配置名称

例如上述脚本,进行仿真指令为:

1
make sim_B

生成的波形文件位于bdw_work/sim文件夹下

3.debug

当dump fsdb波形时,会发生fsdb连接的错误,此时解决方法为:

  • 进行make clean操作
  • 将dump的波形类型改为vcd并重新生成Makefile
  • 进行仿真
  • 将dump类型的模型改为fsdb并重新生成Makefile
  • 进行仿真即可