FPGA——HLS编程入门

目录

  • 一、HLS简介
  • 二、HLS与VHDL/Verilog
  • 三、HLS优点与局限
  • 四、入门级的HLS程序
    • (一)官方教程文档
    • (二)新建工程
    • (三)添加源文件
    • (四)添加 C 仿真文件
    • (五)进行 C 仿真
    • (六)进行C综合
    • (七)联合仿真
    • (八)修改变量位宽
    • (九)添加 Directive
    • (十)使用Modelsim 打开联合仿真所产生的波形
    • (十一)导出IP核
  • 五、应用IP核
    • (一)创建Vivado工程导入IP核
    • (二)将 HLS 产生的 IP 添加到工程当中
  • 六、参考资料

一、HLS简介

  • HLS(High Level Synthesis):一款高层次综合工具。
    • 能够将 C/C++ 或 者 system C 等高级语言转化为 RTL (底层硬件描述语言)电路,降低开发时间。
    • 提供了常见的库(例如图像处理相关的 OpenCv 库和其
      它的数学库)。
    • 可以创建IP并通过例化或者使用 BlockDesign 的方式应用到项目中。

转化原理:在前端将 C 语言描述进行分析,然后进行代码层面的优化(code-level transformation),再在后端把这些运算工作进行并行调度(parallelise & schedule),最后生成 RTL 语言。

  • 使用HLS开发流程:
    FPGA——HLS编程入门_第1张图片
    • 第一步C/C++层面的仿真:
      首先在源文件中,添加一个顶层函数,这个函数就是我们想要将来映射到 RTL 电路中的函数,之后需要一个 C Testbench 来对这个函数功能进行验证,在算法层面,检验我们的函数是否能够正常工作。(算法层面的仿真,能够很快地就得出结果,有助于提高我们的开发效率。)
    • 第二步对C 代码进行综合:
      综合后会根据我们的功能函数,产生相应的电路。在 C 综合阶段,HLS 会根据我们对功能函数中的一些约束(Directive),来生成不同的接口。
    • 第三步C/RTL 的联合仿真:
      在这一阶段,HLS 会根据我们的 C Testbench 来生成我们的 RTLTestbench 并且根据我们所选择的仿真工具来进行 RTL 级的仿真。仿真完成后我们可以观察联合仿真所产生的波形。
    • 第四步导出IP
      前面有提到过 HLS 相当于一个 IP 生成器,它能够将我们的高级语言的代码映射为一个 IP,我们可以根据需要将这些 IP 导出到 Vivado 的集成开发环境中,将这些算法的 IP 应用到实际的工程当中。

二、HLS与VHDL/Verilog

  • 随着FPGA密度随着工艺几何尺寸的缩小而不断增长,设计复杂性使得继续使用传统的HDL设计流程变得越来越困难。尽管HDL语言和工具已经发展,但是设计周期仍然长得令人讨厌。为了帮助解决该问题,出现了高级综合(HLS)编译器,以使设计人员能够进入更高的抽象级别。
  • HLS能自动把 C/C++ 之类的高级语言转化成 Verilog/VHDL 之类的底层硬件描述语言(RTL),生成定制硬件在 FPGA 上跑实现加速。这使得不懂硬件的软件工程师也可以拥有玩转硬件的能力。
  • 为了提高设计数字硬件组件的效率,高层综合(HLS)被视为提高设计抽象水平的下一步。但是,HLS工具的结果质量(QoR)往往落后于手动寄存器传输级别(RTL)流程的质量。
  • HLS 经过十数年的发展,虽然有诸如 AutoPilotOpenCL SDKFPGA HLS 商业化成功的案例出现,但距离其完全替代人工 RTL 建模还有很长的路要走。

更多请读HLS与RTL语言使用情况调查与以后hls是否会替代Verilog成为主流FPGA编程语言?.

三、HLS优点与局限

  • 优点:
    • 第一,使对于软件工程,实现算法基于硬件(ASIC或者FPGA)的计算加速。
    • 第二,高层语言能促进 IP 重用的效率。
    • 第三,HLS 能帮助软件和算法工程师参与、甚至主导芯片或 FPGA 设计。
    • 第四、对于IC设计开发,从抽象的C层级进行功能设计。
    • 第五、对于硬件验证,从更抽象的层次进行功能性验证,加速设计流程。

更多请读在FPGA领域中 HLS一直是研究的重点.

  • 局限
    • IP library 尚未全面还在不断升级,距离其完全替代人工 RTL 建模还有路要走。

四、入门级的HLS程序

软件:vivado 2018.3
实现:使用 HLS 完成 led 灯闪烁

(一)官方教程文档

  1. 赛灵思最新的 UG902 版本已经将其中的 HLS video 相关章节移动至新的文档当中,较 2016.1 的版本有了较大的变化:UG902
  2. 最新版的有关 HLS 图像处理的参考文档:UG1233
  3. 关于 Vivado HLS 的使用方法的参考文档 UG871 的链接:UG871

(二)新建工程

  • 打开 HLS 后如下图所示,点击 Create New Project,创建一个新的工程:
    FPGA——HLS编程入门_第2张图片

  • 输入工程的名称,选择工程的保存路径,点击 Next
    FPGA——HLS编程入门_第3张图片

  • 选择综合时要用的顶层函数,我们在这里暂不不添加,直接点击 Next
    FPGA——HLS编程入门_第4张图片

  • 添加 C 仿真文件,我们同样先不添加,点击 Next
    FPGA——HLS编程入门_第5张图片

  • 时钟周期默认为10ns,选择器件:
    FPGA——HLS编程入门_第6张图片

  • 点击Finish
    FPGA——HLS编程入门_第7张图片

其中 source 栏用来存放功能函数的源码,Test Bench 用来存放 C 仿真文件,solution 中包含着本次工程运行中和运行完成后的输出文件。

(三)添加源文件

  • 右键 Source,点击 New file,可以新建一个保存源码的目录,在里面新建一个 led.cpp 文件与头文件:
    FPGA——HLS编程入门_第8张图片
    FPGA——HLS编程入门_第9张图片

  • Source 文件中的头文件的代码:

#ifndef _SHIFT_LED_H_
#define _SHIFT_LED_H_
//#include "ap_int.h"
//define CNT_MAX 100000000
#define CNT_MAX 100
#define FLASH_FLAG CNT_MAX-2
typedef     int led_t;
typedef     int cnt_t;
//typedef ap_int<1> led_t;
//typedef ap_int<32> cnt_t;
void flash_led(led_t *led_o , led_t led_i);
#endif

CNT_MAX 100000000 :在 100M 时钟频率下计数一秒钟所需要的计数次数。(在仿真的时候,我们可以将其注释掉,采用下一个最大值定义,这样能够加快我们仿真的速度)
FLASH_FLAG 是LED 闪烁的标志,当计数到该值时,LED 发生变化。
flash_led():本次工程中所需要设计的定成函数。
ap_int.h:引入 ap_int.h 这个头文件,这个头文件是由HLS 的库所提供的,通过引入这个头文件我们就可以调用其中的函数和关键字来声明一个任意位宽的数据。

  • 源文件代码:
#include "led.h"

void flash_led(led_t *led_o , led_t led_i){
	cnt_t i; 
	for(i=0;i<CNT_MAX;i++){
		if(i==FLASH_FLAG){
			*led_o = ~led_i;
		} 
	}
}

(四)添加 C 仿真文件

  • 右键 Test Bench,选择添加新建文件,在弹出窗口新建一个 test_led.cpp
    FPGA——HLS编程入门_第10张图片

  • test_led.cpp代码:

#include "led.h"       //引入led.h头文件
#include  
int main(){
	led_t led_i=0x01; 
	led_t led_o; 
	const int SHIFT_TIME = 4; 
	int i;
	for(i=0;i<SHIFT_TIME;i++){       //调用flash_led函数
		flash_led(&led_o , led_i);
		led_i = led_o;               //给激励
		printf("shift_out is %d \n",(int)(led_o&0x01));
	}
}

(五)进行 C 仿真

  • 点击 projectproject seethingssynthesis→browser→选择 flash_led 作为顶层函数:
    FPGA——HLS编程入门_第11张图片

  • 然后点击 projectRun C simulation进行C仿真:
    FPGA——HLS编程入门_第12张图片

Console 窗口中,我们可以看到输出的结果时 01 交替变化,证明 C 仿真的结果正确。

(六)进行C综合

  • 点击 SolutionRun C SynthesisActive Solution,进行C综合(编译器会将 C++代码映射到 RTL 电路):
    FPGA——HLS编程入门_第13张图片

在 C 综合后的结果中,我们可以查看所占用的资源,设计所需的 Latency
和接口的类型等等。

  • TimingLatency 报告:其中 Latency 指的是,设计电路完成一次任务需要的时间,Interval 指的是两次任务之间的时间间隔:
    FPGA——HLS编程入门_第14张图片
  • 占用的逻辑资源:
    FPGA——HLS编程入门_第15张图片
    可以看到,本次设计占用了 41 个触发器和 93 个查找表.
  • 综合出来的模块的接口:
    FPGA——HLS编程入门_第16张图片
    我们可以看到,综合后的信号接口列表如下图所 示,可以看到,综合后还生成了一些其他信号,这些信号的生成,这些信号可以用来标志本模块的工作状态。值得注意的是,这些信号与所采取的接口协议(Protocol)有关,中的 Protocol,这些接口的 Protocol 与我们采取的 Directive 相关。
  • 查看 C 综合后生成的 RTL 代码:
    FPGA——HLS编程入门_第17张图片
    值得说明的是,由 HLS 生成的代码不具有可读性.

(七)联合仿真

进行 C/RTL 联合仿真,来验证映射出来的 RTL 电路是否正确,值得说明的是,Vivado HLS会利用我们的C Testbench 自动生成VerilogTestbench,同时,联合仿真结束过后,我们可以通过使用 Vivado 或者 Modelsim 来查看仿真波形。

  • 点击 SolutionRun C/RTL Cosimulation:选择Modelsim仿真工具,Verilog仿真
    FPGA——HLS编程入门_第18张图片

观察 Console 打印出来的结果与C 仿真时得到的结果一致。

(八)修改变量位宽

  • 在此之前对源文件进行修改一下:
#ifndef _SHIFT_LED_H_
#define _SHIFT_LED_H_
#include "ap_int.h"
//#define CNT_MAX 100000000
#define CNT_MAX 100
#define FLASH_FLAG CNT_MAX-2
//typedef     int led_t;
//typedef     int cnt_t;
typedef ap_int<1> led_t;
typedef ap_int<32> cnt_t;
void flash_led(led_t *led_o , led_t led_i);
#endif

使用了 ap_int 的模板来从定义了 let_t 这个数据位宽为 1bit 的变量类型led_t,和数据位宽为 32bit 的变量类型 cnt_t

  • 再次进行 C 仿真:
    FPGA——HLS编程入门_第19张图片

改变将数据类型改变为自定义位宽的数据类型后,综合后生成所
需要的触发器减少为 10个,查找表减少为 63个。

(九)添加 Directive

  • 对输出信号 led_o 进行约束,点击led.cpp文件后,在 Directive 窗口中右键选中 led_o,选中插入 Directive
    FPGA——HLS编程入门_第20张图片
    FPGA——HLS编程入门_第21张图片

Destination 选择 Source file,这样工具会将约束语句添加到源文件中,若选择 Directive file 中会将 Directive写入到一个 Tcl 脚本中。
Option 选项中,选择 ap_ovld这会给输出信号一个输出有效指示信号。

  • 对输出信号 led_i 进行约束:在 option 选项中选中 ap_vld 信号,这会给输入信号一个输入有效信号,之后点击保存。

  • 再次进行 C 仿真:
    FPGA——HLS编程入门_第22张图片

改变将数据类型改变为自定义位宽的数据类型后,综合后生成所需要的触发器增加为 12个,查找表增加为 101个。

FPGA——HLS编程入门_第23张图片

查看添加 directive 后综合的接口信号,可以看到 HLS 已经为输入输出信号添加上了输入输出的有效标志。这样,我们再将来调用这个 IP 的时候,就能够控制它的时序。

  • 再次执行 Cosimulation
    FPGA——HLS编程入门_第24张图片
  • 验证完功能后将led闪烁时间更改回1秒闪烁一次,再重新C综合一次:
#ifndef _SHIFT_LED_H_
#define _SHIFT_LED_H_
#include "ap_int.h"
#define CNT_MAX 100000000
//#define CNT_MAX 100
#define FLASH_FLAG CNT_MAX-2
//typedef     int led_t;
//typedef     int cnt_t;
typedef ap_int<1> led_t;
typedef ap_int<32> cnt_t;
void flash_led(led_t *led_o , led_t led_i);
#endif

(十)使用Modelsim 打开联合仿真所产生的波形

  • Modelsim 中,定位到 Solution 中,然后打开.wdf文件(路径:Solution/sim/verilog/flash_led.wdf):
    FPGA——HLS编程入门_第25张图片

  • 将定成模块的信号全部添加到波形窗口中:
    FPGA——HLS编程入门_第26张图片

(十一)导出IP核

  • 点击 solution 选择 Export RTL:(导出的 IP 核将在 Solution 这个文件夹中可以找到)
    FPGA——HLS编程入门_第27张图片

五、应用IP核

(一)创建Vivado工程导入IP核

  • 点击Setting,点击 IP,选中仓库,再点击+号:
    FPGA——HLS编程入门_第28张图片

  • 在弹出的界面中,定位到 Solution,点击选择系统将自动识别到 IP;
    FPGA——HLS编程入门_第29张图片

  • 点击 applyOK,即添加成功:
    FPGA——HLS编程入门_第30张图片

  • 点击IP Catalog 即可发现 HLS 已经被添加到了 IP 仓库中:
    FPGA——HLS编程入门_第31张图片

(二)将 HLS 产生的 IP 添加到工程当中

  • IP Catalog 中选中由 HLS 生成的 IP,双击并生成该 IP
    FPGA——HLS编程入门_第32张图片

  • 新建设计文件:
    FPGA——HLS编程入门_第33张图片

  • 代码:

`timescale 1ns / 1ps
module Lab_HLS_Led( 
input    wire    clk ,
input    wire    rst_n ,
output   wire    led_o);
wire             rst ;//同步复位
wire             ap_ready ;//当前可以接收下一次数据
reg              ap_start ;//IP 开始工作
reg              led_i_vld ;//输入数据有效
wire             led_o_vld ;
reg              led_i ;//输入的 led 信号
wire             led_o_r ;
wire             ap_done ;
wire             ap_idle ;
reg     [1:0]    delay_cnt ;
assign rst = ~rst_n ;
assign led_o = led_o_r ;

//----------------delay_cnt------------------
always @(posedge clk) begin
      if (rst==1'b1) begin
        delay_cnt <= 'd0;
      end
      else if(delay_cnt[1]==1'b0) begin
        delay_cnt <= delay_cnt + 1'b1;
      end
end

//----------------ap_start------------------
always @(posedge clk) begin
      if (rst==1'b1) begin
        ap_start <= 1'b0;
      end
      else if(delay_cnt[1]==1'b1)begin
        ap_start <= 1'b1;
      end
end

//----------------led_i_vld------------------
always @(posedge clk) begin
      if (rst==1'b1) begin
        led_i_vld <= 1'b0;
      end
      else if(delay_cnt[1]==1'b1)begin
        led_i_vld <= 1'b1;
      end
end

//----------------ap_i------------------
always @(posedge clk) begin
      if (rst==1'b1) begin
        led_i <= 1'b0;
      end
      else if(led_o_vld==1'b1)begin
        led_i <= led_o_r ;
      end
end

flash_led_0 inst_flash_led (
    .led_o_V_ap_vld(led_o_vld), // output wire led_o_V_ap_vld
    .led_i_V_ap_vld(led_i_vld), // input wire led_i_V_ap_vld
    .ap_clk(clk),               // input wire ap_clk
    .ap_rst(rst),               // input wire ap_rst
    .ap_start(ap_start),        // input wire ap_start
    .ap_done(ap_done),          // output wire ap_done
    .ap_idle(ap_idle),          // output wire ap_idle
    .ap_ready(ap_ready),        // output wire ap_ready
    .led_o_V(led_o_r),          // output wire [0 : 0] led_o_V
    .led_i_V(led_i)             // input wire [0 : 0] led_i_V
);

endmodule
  • 添加约束文件:
    FPGA——HLS编程入门_第34张图片
  • 代码:
##############LED define################## 
set_property PACKAGE_PIN P15 [get_ports {led_o}]
set_property IOSTANDARD LVCMOS33 [get_ports {led_o}]
##############Reset define################## 
set_property PACKAGE_PIN P16 [get_ports {rst_n}]
set_property IOSTANDARD LVCMOS33 [get_ports {rst_n}]
##############50M CLK define################## 
create_clock -period 20.000 -name clk -waveform {0.000 10.000} [get_ports clk]
set_property PACKAGE_PIN N18 [get_ports {clk}]
set_property IOSTANDARD LVCMOS33 [get_ports {clk}]
  • 添加ILA的IP核,将位宽改为16位:
    FPGA——HLS编程入门_第35张图片

ILA用于观察信号变化情况。

  • 生成bit文件,下载验证:

六、参考资料

[Part3_Z7_Lite系列教程之HLS篇 V1.1]

你可能感兴趣的:(实验,fpga,hls)