本文基本是书本Parallel Programming in FPGA的读书笔记
《ParProg Of FPGA》
- 不同的抽象层次
- Verilog/VHDL
- EDA Tool: Transfer RTL to Digital Circuit,同时针对硬件平台进行分析
- RTL(Register Transfer Level)
- HLS 输入高级的系统结构,产生RTL结构
- 算法级别(比如计算图)
- Verilog/VHDL
- What Does HLS Do?
- 找寻到程序中的并行性
- 在关键的数据通路上加Reg缓存去保证时钟频率收敛
- 自动生成选择数据通路的控制逻辑
- 自动生成接口
- 将数据存储映射到存储器件,并且保证同步性
- 可以把HLS看做一种编译器,类比gcc,不是编译产生汇编代码,而是编译产生一些RTL
- Vivado Flow
- Logic Synthesis: 将RTL级的门电路(Verilog描述的)转化为Netlist的网表
- Netlist网表: 包含了Logic Elements以及他们之间的Connection
- 与FPGA里面的资源是有关的
- Netlist网表: 包含了Logic Elements以及他们之间的Connection
- 后面是Implemention(Place & Route) 将上面的逻辑设计铺到FPGA上,包含在bitstream中
- Bitstream:比特流包含了FPGA元素的所有配置(包括了连接,逻辑资源,片上存储),以二进制形式
- Logic Synthesis: 将RTL级的门电路(Verilog描述的)转化为Netlist的网表
- HLS Input
- Function 描述算法
- 不能有动态内存
malloc(); new(); delete();
- 不能有系统调用(会被忽略)
abort();exit()
- 不能调用其他的系统库(math.h可以,其他不一定支持)
- 少用指针
- 不能有动态内存
- Testbench调用Function
- 目标设备(FPGA型号)
- 时钟周期
- Implemention过程中的一些Guiding
- Function 描述算法
- FPGA Structure
- FF+LUT - CLB(Configurable Logic Block)
- Routing Track连接各个Slice,他们的输入输出连接在Switch Box上
- Programmble Interconnect(Switch Box)是一个Multiplexer复选器
- Switch Box一般连接外围接口(在最外层)
- 除了几个主流模块之外还会集成比如DSP48(BRAM)这样的硬件模块
- 让整个FPGA变得更“硬”
- 在DSP48上实现一个MAC比直接用逻辑资源要优化多了
- 一个BRAM大概32K
- Design指标
- 不能直接用clk cycle,因为跑的时钟频率不一定一样
- 在HLS中用Task来描述一个单位活动,Task Latency是一个指标,task interval描述task出现的频繁程度(有点时候被其他工具叫做thoroughput)
- Task Interval取决于Task的实现方式
- 限制Task Interval(Throughput)的因素
- Recurrence & FeedBack Loop
- 当目前的元素计算依赖于同一元素的之前一个计算
- 实际代码中比如
static
或者是For Loop
中都会出现
- Resource LImit
- 单位时钟能够读取的Memory速度有限
- Recurrence & FeedBack Loop
- 同时Data Rate也是一个重要的指标
- 在HLS中用Task来描述一个单位活动,Task Latency是一个指标,task interval描述task出现的频繁程度(有点时候被其他工具叫做thoroughput)
- 不能直接用clk cycle,因为跑的时钟频率不一定一样
- Sequential Design
- 目标是获得高并行度
- 但是Control Logic会比较复杂
- 一般复杂的设计汇中Sequential以及Paralleldesign会构成Trade-Off
- functional Pipeline
- 将整个函数看为一个计算流的一个整体
- 函数内部的循环以及分支被转化为非条件的(给它摊平?)
- 一般用于简单,高数据率的
《ug871 - Xilinx HLS Tutorial》
Intro
- Lab1
- Design Flow
- Xilinx HLS 软件打开,建立工程
- 添加Design和Test Source
- 注意添加文件时候,所有在local directory的文件都会被包含
- 如果还需要包含不在该目录下的,需要
edit cflag
- 添加c的编译search path
- 貌似教程的意思就是叫你把项目文件夹建在你的src文件的同目录下
- 不然会索引不到!
- 一个工程可以有好几个solution
- 它们的target technology, package, constriant, synthesis directives可以不一样
- 设置默认的时钟速率(10ns)-以及clk uncertainty(一般默认是-1.25ns,给信号setup和hold用的)
- 选择Part
- 添加Design和Test Source
- Validate C Source Code 验证C Testbench
Project » Run C Simulation
- 会存储下输出的结果(out.dat)和GoldenResults相对比
- 对比的是C仿真跑出来的结果
- 中间也可以Launch Debugger
- 这里的C Testbench在RTL Simulation中会被Reuse
- 会存储下输出的结果(out.dat)和GoldenResults相对比
- High Level Synthesis (真正的)
Solution > C Synthesis > Active Solution
- 查看Performance Estimation
- Timing 能跑到的时钟速率
- Latency 完成任务需要的clk cycle数目
- Detail
- Instance - Submodule
- Loop
- Utilization Estimation
- Summary - RAM,LUT资源都用了多少
- Interface 用到了哪些接口
- clk,rst一定有
- 有的时候HLS会产生一些control port
- RTL Verification
Solution » C/RTL Simulation
- Export IP
Solution > Export RTL
- 导出到了Vivado的IP Catalog当中
- 选择好之后impl文件夹下会出现ip,此文件夹下的verilog文件内部是生成的rtl文件(确切的来说是.v,描述了rtl)
- 此外,创建solution之后的所有操作会存在./constraint下的 script.tcl中
- 另外一个.tcl表示了对rtl的各种优化方法
-
Project > Close Incative tabs
- 可在新建solution之后关闭掉之前solution所打开的窗口
- 使用tcl脚本
viavdo_hls -f run_hls.tcl
- 会自动完成整个hls工程做的事情
vivado_hls -p $PROJ_NAME
- 优化
- I/O Protocol First
- HLS对没有明确的RAM_ACCESS会选择Dual Port
- 输入默认为32-bit
- 输出端口默认带了一个valid信号,这是对指针的变量的默认处理办法
- 打开.c文件,在右侧的”Directive” Panel
- 必须打开source code才能添加directive,这个稍稍有些蠢,但是就是这样
-
- 这条约束表示c array应该用single-port BRAM来处理
- 插入directive之后重新synth
- 观看overview
- 点击panel上面的眼镜,进入analysis界面
-
- 点击下面可以从时序analysis切换到Resource Analysis
- (目前还有一个比较蠢的事情是int型对应32bit,需要多篇DSP48去完成乘法,不合理,在后文中ArbitaryBit中会有提及))
-
- 目标是最大throughput(吞吐量)(Lowest Interval)!
- 优化的空间
- 目前Loop还是rolled,就是loop中的资源是被re-used,如果Unroll Loop就可以让loop并行起来
- 添加 > LOOP UNROLL
- 由于shiftreg在c中是一个数组,被HLS默认综合为了一个RAM,但是我想让他是一个shiftreg
- 添加ARRAY PARTICIAN
- 目前Loop还是rolled,就是loop中的资源是被re-used,如果Unroll Loop就可以让loop并行起来
- 之前已经被添加过的directive前面带一个”#”
-
Project > Compare Results
- 来对比不同的solution
- 很明显的资源换时间
- 优化的空间
-
- 当你添加新的配置之后,会自动加上去()
- 在GUI中配置优化规则,就相当于在代码中加上
#pragma HLS RESOURCE variable=c core=RAM_1P_BRAM
这一过程在ubuntu上的Vivado 18上可以正常完成,但是在win的17上面就会报各种神奇错误…暂时还没解决(估计是下载到的文件版本,也就是ug871的版本和vivado版本不对应,目前统一为19.1的可以work)
C Validation
-
- 由于方针实际执行的文件夹并不在prj文件夹下,而在csim下
- 其产生的resluts.dat也是在
$PRJ/solution/csim/build
- 困扰我的in_data_t的定义在哪里,查了之后发现是在
hamming_window.h
中有typedef int16_t in_data_t
hamming_window.h
由于放在和工程目录同一个目录下而自动被include进来了- 然后按住ctrl其实可以跳转文件
- Data Type
- Standard C :
int16_t int32_t
- ANSI C(Provided in HLS, in AP_cint.c) int16,int32
- 使用ANSI C的时候不能用debug环境
- C++
typedef ap_int<16> in_data_t
- Standard C :
- Lab3 里面出现了一个语法错误
- 不是xilinx的工程师这么搞怪的吗
- 参考了这个帖子解决了
- 不是xilinx的工程师这么搞怪的吗
Interface Synthesis
- Definition: Adding RTL Ports into C Design, both pysical and IO Protocol
- Block Level IO Protocol - Adder Example
- 所谓Block.就是大模块Block的IO控制信号,一定包含时钟和重置信号,可能还会有一些额外的控制信号
- 用
#PRAGMA HLS INTERFACE ap_none port=in1
- IO Protocol的基本作用是,通过增加额外的信号线与控制逻辑让整个设计同步起来.默认的控制信号会标示
ap_ctrl_hs
- 经过synthesis之后可以查看Analysis,点开interface位置
- 默认产生了ap的控制信号
- 3个输入都分配了一个32比特的输入,而输出ap_return也有一个
- Block Protocol 与函数有关,所以我们需要specify主要的函数
- IO Protocol Type & Timing Diagram
- 其实和axi4有很好的对应
- start/idle - s_axis_tready/s_axis_tvalid
- done/ready - m_axis_tready/m_axis_tvalid
- Modify The Protocol
- 在添加directive的界面中,选中变量选择interface,并且选择Protocol
-
- 有比如
ap_ctrl_none/ap_ctrl_hs/ap_ctrl_chain/s_axilite
- 对整个文件添加Protocol为ap_ctrl_none
- 可以发现ap接口都没了
- 有比如
- 其实和axi4有很好的对应
- Port IO Protocol
- lab2exaple中adder examples的没有用return的形式返回,而是用了指针输出
*out
- port io的默认protocol就是
ap_none
- 这样看起来好像选择directive里面的Destination中的Source Code/Directive Code是将这行代码添加到什么文件中(那么source不是所有solution公用的吗?)
set_directive_interface -mode ap_vld "adders_io" in1 set_directive_interface -mode ap_ack "adders_io" in2 set_directive_interface -mode ap_hs "adders_io" in_out1
-
- 按照上面设置之后的port类型
- lab2exaple中adder examples的没有用return的形式返回,而是用了指针输出
- Array
- C中的array被默认综合为ram
- d_o默认是ram对应的接口(Protocol - AP_Memory)
- Data Port
- ADDR Port
- CE (Control Enable)
- WE (Write Enable)
- d_i类型但是readonly
- 数据位宽与地址位宽都按照C中被设置为了对应的
- 我们可以改变
- 选择RAM Port是Single Port还是Dual Port (HLS会默认选择最大Data Rate的,如果你指定了Dual Port而HLS发现只需要Single它会override掉你)
- 我们的example里面不会同时做read和write(Rolled Loop保证了)
- 但是我们可以先loopunroll再改成dual port来更高的并行度啊
- 通过Loop Unroll,我们可以read更多的东西,增加datarate,代价是复制N份逻辑
- 还可以把do设置为fifo
- 选择RAM Port是Single Port还是Dual Port (HLS会默认选择最大Data Rate的,如果你指定了Dual Port而HLS发现只需要Single它会override掉你)
- 修改之后的do port变为
- d_o_din
- d_o_write
- d_o_full
- 直接打开一个新的solution,意味着继承之前的direcives
- Array Partition
- 对某个port(在本example中是di,do)
- type - Block
- 如果选择complete就是完全的partitian,如果完全了就不能综合为RAM,因此需要去掉限制RAM的directive
- factor - 4
- 现在do被综合为了4个fifo,di变成了两个block-RAM的接口
- 如果我们开启Full Partitian
- 可以看到现在do有32个fifo,延迟变成了1
- di也变成了32根ap_none的线
- AXI4 Interface
- 如何获得一个最优的设计?
- 正确的识别input和output channel 然后让他们完成cyclic partitioning
- 当top-level design是一个循环的时候,可以将这个循环设置为PIPELINE with LOOP REWIND告诉RTL这里是一个continuous loop
- 把port设置为axis
- 然后在top function里面设置INTERFACE为axi-lite,这表示了return值用的是什么接口(这里默认接口是ap_ctrl_hs,因为我们的do用的是axis,其实不设置也可以)
- 完成综合了之后会发现原先的
ap_done,ap_start
被axi-lite的借口代替了 - Export RTL之后,同时还生成了driver文件