终于还是看起了HLS

曾经,我认为这玩意不太靠谱...

Posted by tianchen on November 27, 2019

本文基本是书本Parallel Programming in FPGA的读书笔记

《ParProg Of FPGA》

  • 不同的抽象层次
    • Verilog/VHDL
      • EDA Tool: Transfer RTL to Digital Circuit,同时针对硬件平台进行分析
    • RTL(Register Transfer Level)
      • HLS 输入高级的系统结构,产生RTL结构
    • 算法级别(比如计算图)
  • What Does HLS Do?
    • 找寻到程序中的并行性
    • 在关键的数据通路上加Reg缓存去保证时钟频率收敛
    • 自动生成选择数据通路的控制逻辑
    • 自动生成接口
    • 将数据存储映射到存储器件,并且保证同步性
  • 可以把HLS看做一种编译器,类比gcc,不是编译产生汇编代码,而是编译产生一些RTL

  • Vivado Flow
    • Logic Synthesis: 将RTL级的门电路(Verilog描述的)转化为Netlist的网表
      • Netlist网表: 包含了Logic Elements以及他们之间的Connection
        • 与FPGA里面的资源是有关的
    • 后面是Implemention(Place & Route) 将上面的逻辑设计铺到FPGA上,包含在bitstream
      • Bitstream:比特流包含了FPGA元素的所有配置(包括了连接,逻辑资源,片上存储),以二进制形式

  • HLS Input
    • Function 描述算法
      • 不能有动态内存 malloc(); new(); delete();
      • 不能有系统调用(会被忽略) abort();exit()
      • 不能调用其他的系统库(math.h可以,其他不一定支持)
      • 少用指针
    • Testbench调用Function
    • 目标设备(FPGA型号)
    • 时钟周期
    • Implemention过程中的一些Guiding
  • 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速度有限
      • 同时Data Rate也是一个重要的指标
  • Sequential Design
    • 目标是获得高并行度
    • 但是Control Logic会比较复杂
    • 一般复杂的设计汇中Sequential以及Paralleldesign会构成Trade-Off
  • functional Pipeline
    • 将整个函数看为一个计算流的一个整体
    • 函数内部的循环以及分支被转化为非条件的(给它摊平?)
    • 一般用于简单,高数据率的

《ug871 - Xilinx HLS Tutorial》

Intro

  1. Lab1
    • Design Flow
    1. 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
    2. Validate C Source Code 验证C Testbench

      Project » Run C Simulation

      • 会存储下输出的结果(out.dat)和GoldenResults相对比
        • 对比的是C仿真跑出来的结果
        • 中间也可以Launch Debugger
      • 这里的C Testbench在RTL Simulation中会被Reuse
    3. High Level Synthesis (真正的)

      Solution > C Synthesis > Active Solution

    4. 查看Performance Estimation
      • Timing 能跑到的时钟速率
      • Latency 完成任务需要的clk cycle数目
      • Detail
        • Instance - Submodule
        • Loop
      • Utilization Estimation
        • Summary - RAM,LUT资源都用了多少
      • Interface 用到了哪些接口
        • clk,rst一定有
        • 有的时候HLS会产生一些control port
    5. RTL Verification

      Solution » C/RTL Simulation

    6. 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所打开的窗口
    7. 使用tcl脚本
      • viavdo_hls -f run_hls.tcl
      • 会自动完成整个hls工程做的事情
      • vivado_hls -p $PROJ_NAME
    8. 优化
      • 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
        • 之前已经被添加过的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
  • Lab3 里面出现了一个语法错误
    • 不是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接口都没了
  • 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类型
  • 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
    • 修改之后的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文件