FPGA学习笔记(1)——Vivado和HLS

1 Vivado设计

1.1 FPGA基本知识

  1. Xilinx Atrix-7使用6输入LUT结构(0-63)
  2. CLB:可配置逻辑块
  3. Slice:每个CLB包含2个Slice(包含查找表LUT和8位寄存器REG)
  4. 布线池:围绕在CLB周围,衔接FPGA的资源调度
  5. I/O块:FPGA芯片四周对外连接的短线(工程中需要对此进行管脚约束)
  6. FPGA其他资源:Block RAM、DSP Slice(数字信号处理块)、高速串行收发器、PLL时钟发生器(锁相环)、外部存储器控制器(硬件IP)、模数转换模块(XADC)
  7. BRAM:
    (1)FPGA内嵌的存储单元包括BRAM和分布式RAM(基于CLB的查找表LUT)
    (2)BRAM可用于随机存取存储器(RAM)、只读存储器(ROM)、FIFO或移位寄存器。
  8. 时钟资源:
    (1)FPGA内部会将时钟布线资源划分到不同的时钟区,每个时钟区对应一定数量的IO口、逻辑资源、存储器资源、DSP等。
    (2)时钟走线:时钟源到各个时钟有一段延时,延时不整齐,最大的延时就会限制FPGA的最大时钟频率。
  9. 数字信号处理块(DSP):由三个不同的链路块组成。
  10. 高速串行收发器
  11. 外部存储器控制器
  12. 模数转换模块

1.2 Vivado工程创建

1.2.1 设计流程

1、HLS设计:
C/C++/System C -> High-level Synthesis(Vivado HLS)-> IP核(设计核心) —>RTL系统级 —> 综合 —> 实现
(Tools >Validate Design)

2、HDL设计:
HDL代码 —> 综合 —> 实现
*资源:逻辑网表(EDIF)、约束(XDC)、物理数据(XDEF)

1.2.2 工程文件存放

打开新建的工程文件文件夹,基本内容如下:

在这里插入图片描述

(1) .xpr为打开工程文件的文件,Open Project选择此文件。
(2).srcs文件夹存放HDL代码(sources_1)、约束文件XDC(constrs_1)
(3).sim文件夹存放仿真代码
(4).runs文件夹存放综合和实现后产生的文件 (impl_1和synth_1), .bit文件存放在impl_1文件夹下

IP文件夹存放一些IP核

1.2.3 操作流程

(1)新建文件,选择芯片型号。
(2)新建RTL代码(寄存器级).v
(3)头文件可以用.v也可以用.vh。
(4)加入IP核(.xci)
(5)加入约束(.xdc):包括引脚约束和时序约束。
(6)加入仿真文件(.v)

1.2.4 设计流程(Flow Navigator)

(1) 仿真(Simulation):仿真代码逻辑功能是否完善。
(2) 综合(Synthesis):可以查看Schemastic、网表文件、时序报告、资源利用率。
(3) 实现(Implementation):将网表根据引脚和时序约束在FPGA上的电路中进行计算实现。
(4) 生成比特流文件(Generate Bitstream):文件类型为.bit,用于下载到芯片内和debug。
(5) 生成.mcs文件(烧录进Flash):在Implement Design状态下进入Settings->Bitstream->Config…->Configuration Rate->SPI Configuration->Bus Width:4,Enable:Yes->重新Generate Bitstream…
需要先擦除程序,Program Configuration Memory Device中选择Erase

1.2.5 命令行tcl(略)

详情查看:ug835

1.2.6 Verilog语法

1、语法分为可综合和不可综合:可综合的较少,用于创建工程;不可综合的有很多,主要用于测试(testbench)。

2、阻塞赋值与非阻塞赋值:
代码1:非阻塞赋值,执行顺序并行,边沿触发,生成时序逻辑

   always @(posedge clk_i or negedge rst_n) beginif(rst_n) cout <= 1'd1;else if(cout == 4'd10) cout <= 1'd0;   //逻辑运算一定要写完整else cout <= cout + 1'd1;end

代码2:阻塞赋值,执行顺序串行,电平触发,生成组合逻辑

   always @(rst_n) beginif(cout == 4'd5) cout = 1'd1;else begina=b;c=a;cout = cout + 1'd1;endend

3、关键字:

parameter   参数
wire        线
reg         寄存器
assign      逻辑简单的语句
always      一直执行
begin end   
case(value) endcase

1.2.7 仿真验证 Simulation

1、搭建testbench测试平台:
(1)输入激励(clk,rst_n等)
(2)例化顶层验证设计
(3)响应
(4)对比输出

2、代码:

`timescale 1ns / 1ps
module simulation();reg			clk_in;
parameter	CYCLE = 10;
always #(CYCLE) clk_in = ~clk_in;		//每10ns,时钟反转一次reg			rst_n;
wire		LED_out;//模块名	例化名
TOP		TOP_Init
(	//端口相互连接,前面的是TOP模块内的input和output,后面的是此模块的变量.clk_in(clk_in),.rst_n(rst_n),.LED_out(LED_out)
);initial begin    //初始化clk_in = 0;rst_n = 0;#100;rst_n = 1;endendmodule

3、要求:
(1)激励接口reg输入到例化的模块中

   reg clk_in;reg rst_n;

(2)响应接口wire从例化中输出到仿真代码中

   wire counter;

(3)待验证设计例化
例如:

   counter_top uut_counter_top(  //先写模块名称,再写例化的名称.clk_in(clk_in),.rst_n(rst_n),.counter(counter));

(4)虚拟的时钟:

always #10 clk_in = ~clk_in;

1.2.8 约束文件

1、约束分为:时序约束、IO约束、时序例外约束(有先后顺序)。

1.2.8.1 时序约束

时序约束分为外部输入延时,内部延时,数据路径延时,输出延时。

(1)create_clock:主时钟必须最早创建,端口进来的主时钟以及GT输出的时钟都必须由用户使用create_clock自主创建。主时钟通常有两种情况:一是时钟由外部时钟源提供,通过引脚进入FPGA,该时钟引脚绑定为主时钟;另一种是告诉收发器(GT)的时钟RXOUTCLK或TXOUTCLK。对于7系列的FPGA,需要对GT的这两个时钟手工约束:对UltraScale FPGA,只需要对GT的驶入时钟约束即可,Vivado会自动对这两个时钟约束。

如果是差分输入的时钟,可以仅在查分对的P侧用get_ports获取端口,并使用create_clock创建。
create_clock -name <name> -period <period> -waveform {<rise_time> <fall_time>} [get_ports <input_port>]

参数含义
-name时钟名称
-period时钟周期,单位为ns
-waveform波形参数,第一个参数为时钟的第一个上升沿时刻,第二个参数为时钟的第一个下降沿时刻
-add在同一时刻源上定义多个时钟时使用
  • 针对vivado自动推导的衍生时钟,比如MMCM/PLL/BUFR的输出时钟,可以由Vivado自动推导,用户无需创建。(MMCM可以调整相位,PLL占用的面积较小)
    但是,工具不能自动推导出使用寄存器和组合逻辑搭建的分频器等衍生的时钟,必须有用户使用create_generated_clock来创建。
create_clock -name clk1 -period 10.000[get_ports CKP1]
create_generated_clock -name clk2 [get_pins REGA/Q] -source [get_ports CKP1] -divide_by 2
  • clk1是原本就有的主时钟,clk2是衍生出来的时钟。

(2)create_generated_clock:

create_generated_clock -name <generated_clock_name> \-source <master_clock_source_pin_or_port> \-multiply_by <mult_factor> \-master_clock <master_clk> \<pin_or_port>
参数含义
-name时钟名称
-source产生该时钟的源时钟
-multiply_by源时钟的多少倍频
-divide_by源时钟的多少分频

这个约束是在FPGA内部产生的衍生时钟,所以参数中有一个-source,就是制定这个时钟是从哪里来的,这个时钟叫master clock,是指上级时钟,区别于primary clock。它可以是上面讲的primary clock,也可以是其他的衍生时钟。该命令不是设定周期或波形,而是描述时钟电路如何对上级时钟进行转换, 转换可以是如下关系:

  • 简单的频率分频
  • 简单的频率倍频
  • 频率倍频与分频的组合,获得一个非整数的比例,通常由MMCM或PLL完成
  • 相移或波形反相
  • 占空比改变
  • 上述所有关系的组合

衍生时钟又分为两种情况:
①Vivado自动推导的衍生时钟
②用户自定义的衍生时钟
首先来看第一种,如果使用PLL或者MMCM,则Vivado会自动推导出一个约束。在xdc文件中,不对着两个输入时钟进行约束,只对输入的clk进行约束,也可以看到vivado生成了约束(有三个约束,因为PLL会自动输出一个反馈时钟)。

  • 自动推导的好处:当MMMCM/PLL/BUFR的配置改变而影响到输出时钟的频率和相位时,用户无需改写约束。
  • 劣势:用户不清楚自动推导出的衍生钟的名字,当设计层次改变时,衍生时钟的名字可能改变,但由于该衍生时钟的约束并非我们自己定义的,因此可能会没有关注到它名字的改变,当我们使用这些衍生时钟进行别的约束时,就会出现错误。
    解决办法:用户手动写出自动推导的衍生时钟的名字,只需要写名字,其余的不写。
create_generated_clock -name <generated_clock_name> \-source <master_clock_source_pin_or_port>

这一步很容易会被提示critical warning,其实有个很简单的方法,就是name和source都按照vivado中生成的来。具体我们到后面的例子中会讲到。

(3)set_clock_group:
使用方法:

#第一种
set_clock_groups -asynchronous -group <clock_name_1> -group <clock_name_2>
#第二种
set_clock_groups -physically_exclusive -group <clock_name_1> -group <clock_name_2>

这个约束常用的方法有三种,第一种用法是当两个主时钟是异步关系时,使用-asynchronous来指定。这个在我们平时用的还是比较多的,一般稍微大点的工程,都会出现至少两个主时钟,而且这两个时钟之间并没有任何的相位关系,这时就要指定:

create_clock -period 10 -name clk1 [get_ports clk1]
create_clock -period 8 -name clk2 [get_ports clk2]
set_clock_groups -asynchronous -group clk1 -group clk2

第二种用法是当我们需要验证同一个时钟端口在不同时钟频率下能否获得时序收敛时使用。比如有两个异步主时钟clk1和clk2,需要验证在clk2频率为100MHz,clk1频率分别为50MHz、100MHz和200MHz下的时序收敛情况,我们就可以这样写。

create_clock -name clk1A -period 20.0 [get_ports clk1]
create_clock -name clk1B -period 10.0 [get_ports clk1] -add
create_clock -name clk1C -period 5.0  [get_ports clk1] -add 
create_clock -name clk2 -period 10.0 [get_ports clk2]
set_clock_groups -physically_exclusive -group clk1A -group clk1B -group clk1C
set_clock_groups -asynchronous -group "clk1A clk1B clk1C" -group clk2

第三种用法就是当我们使用BUFGMUX时,会有两个输入时钟,但只会有一个时钟被使用。比如MMCM输入100MHz时钟,两个输出分别为50MHz和200MHz,这两个时钟进入了BUFGMUX。
<如图:FPGA时序约束理论篇之时钟周期约束01.png>
在这种情况下,我们需要设置的时序约束如下:

set_clock_groups -logically_exclusive \-group [get_clocks -of [get_pins inst_mmcm/inst/mmcm_adv_inst/CLKOUT0]] \-group [get_clocks -of [get_pins inst_mmcm/inst/mmcm_adv_inst/CLKOUT1]]

(4)创建虚拟时钟
虚拟时钟通常用于设定对输入和输出的延时约束,这个约束其实是属于IO约束中的延迟约束。虚拟时钟和前面讲的延迟约束的使用场景不太相同。顾名思义,虚拟时钟,就是没有与之绑定的物理管脚。
虚拟时钟主要用于以下三个场景:

  • 外部IO的参考时钟并不是设计中的时钟
  • FPGA I/O路径参考时钟来源于内部衍生时钟,但与主时钟的频率关系并不是整数倍
  • 针对I/O指定不同的jitter和latency

简而言之,之所以要创建虚拟时钟,对于输入来说,是因为输入到FPGA数据的捕获时钟是FPGA内部产生的,与主时钟频率不同;或者PCB上有Clock Buffer导致时钟延迟不同。对于输出来说,下游器件只接收到FPGA发送过去的数据,并没有随路时钟,用自己内部的时钟去捕获数据。
如下图所示,在FPGA的A和B端口分别有两个输入,其中捕获A端口的时钟是主时钟,而捕获B端口的时钟是MMCM输出的衍生时钟,而且该衍生时钟与主时钟的频率不是整数倍关系。

<图片:FPGA时序约束理论之时钟周期约束02.png>

这种情况下时序约束如下:

create_clock -name sysclk -period 10 [get_ports clkin]
create_clock -name virclk -peroid 6.4
set_input_delay 2 -clock sysclk [get_ports A]
set_input_delay 2 -clock virclk [get_ports B]

可以看到,创建虚拟时钟用的也是create_clock约束,但后面并没有加get_ports参数,因此被称为虚拟时钟。

再举个输出的例子,我们常用的UART和SPI,当FPGA通过串口向下游器件发送数据时,仅仅发过去了uart_tx这个数据,下游器件通过自己内部的时钟去捕获uart_tx上的数据,这就需要通过虚拟时钟来约束;而当FPGA通过SPI向下游器件发送数据时,会发送sclk/sda/csn三个信号,其中sclk就是sda的随路时钟,下游器件通过sclk去捕获sda的数据,而不是用自己内部的时钟,这是就不需要虚拟时钟,直接使用set_output_delay即可。

注意,虚拟时钟必须在约束I/O延迟之前被定义。

(5)最大最小延迟约束
顾名思义,就是设置路径的max/min delay,主要应用场景有两个:

  • 输入管脚的信号经过组合逻辑后直接输出到管脚
  • 异步电路之间的最大最小延迟

设置方式:

set_max_delay <delay> [-datapath_only] [-from <node_list>][-through <node_list>]
set_min_delay <delay> [-from <node_list>] [-to <node_list>] [-through <node_list>]
参数含义
-from有效的起始节点包含:时钟,input(input)端口,或时序单元(寄存器,RAM)的时钟引脚。
-to有效的终止节点包含:时钟,output(output)端口或时序单元的数据端口。
-through有效的节点包含:引脚,端口,线网

max/min delay的约束平时用的相对少一些,因为在跨异步时钟域时,我们往往会设置asynchronous或者false_path。对于异步时钟,我们一般都会通过设计来保证时序能够收敛,而不是通过时序约束来保证。

1.2.8.2 IO约束

不加任何IO约束的端口,时序要求被视为无穷大。set_input_delayset_output_delay是从系统角度来约束的。-minhold slack时间,不大于周期,-maxsetup slack时间,需要<=0。一般默认都是设置0。

1.2.8.3 时序例外约束

包括set_max_delayset_min_delayset_multicycle_pathset_false_path等,这类约束除了要满足xdc的先后优先级外,还要遵循自身的优先级限制。

  1. 总的准则是:针对同一条路径,对约束目标描述越具体的优先级越高。

  2. 注:XDC里面每一行相当于一条指令,Vivado按照行序从前往后读取XDC指令,所以越后面的XDC指令,其优先级越高。比如当有2条XDC指令约束同一个东西时,后面指令会因为执行的比较晚,而覆盖前一条指令的效果。
    因为XDC中的指令有先后顺序,所以推荐的XDC文件组织方式一般是把timing约束放在前面,而把物理位置约束放在后面。

## Timing Assertions Section
# Primary clocks
# Virtual clocks
# Generated clocks
# Clock Groups
# Input and output delay constraints## Timing Exceptions Section
# False Paths
# Max Delay / Min Delay
# Multicycle Paths
# Case Analysis
# Disable Timing## Physical Constraints Section
1.2.8.4 高级时钟约束

1、时序的零起点:
create_clock定义的主时钟的起点即时序的"零起点",在这之前的上游路径延时都被工具自动忽略
create-clock -name sysclk -period 10 [get_ports sys_clk]
create-clock -name sysclk_bad -period 10 [get_pins clk_infra_i/sys_clk_buf/0]
<图片:高级时钟约束01/02>

2、时钟定义的先后顺序:
时钟的定义也遵从XDC/Tcl的一般优先级,即:在同一个点上,由用户定义的时钟会覆盖工具自动推导的时钟,且后定义的时钟会覆盖先定义的时钟。若要二者并存,必须使用-add选项。
<图片:高级时钟约束03/04>

create_clock -name sysclk -period 10 [get_ports sys_clk]
create_generated_clock -name clkbufg -source [get_ports sys_clk] -divide_by 1 [get_pins clk_infra_i/clkfsm_buf/0]
create_generated_clock -name clkbufr -source [get_ports sys_clk] -divide by 1 [get_pins clk_infra_i/sys_clk_buf/0] -add -master_clock sysclk
  • 这个例子添加主时钟的衍生时钟clkbufg-add -master_clock sysclk
    上述例子中BUFG的输出端由用户自定义了一个衍生钟clkbufg,这个衍生钟便会覆盖此处原有的sysclk。此外,图示BUFR工作在bypass模式,其输出不会自动创建衍生钟,但在BUFR的输出端定义一个衍生钟clkbufr,并使用-add 和 -master_clock 选项后,这一点上会存在sysclk和clkbufg两个重叠的时钟。如下的Tcl命令验证了我们的推论。
    <图片:高级时钟约束05>

4、同步时钟与异步时钟:
<图片:高级时钟约束06>
在XDC中,所有的时钟都会被缺省认为是相关的,也就是说,网表中所有存在的时序路径都会被Vivado分析。这也意味着FPGA设计人员必须通过约束告诉工具,哪些路径是无需分析的,哪些时钟域之间是异步的。

1.2.9 综合 Synthesis

1、移位寄存器:srl_style
用LUT建立:

(* srl_style = "register" *) reg [WIDTH-1:0] shreg;
interger i;
always @(posedge clk) beginif(clken) beginfor(i=0;i<WIDTH-1;i=i+1)shreg[i+1] <= shreg[i];shreg[0] <= SI;end
end
assign SO = shreg[WIDTH - 1];
  • srl_reg: ->SRL->FF->
  • reg_srl: ->FF->SRL->
  • reg_srl_reg: ->FF->SRL->FF-> (高性能)
  • register: ->FF->FF->FF->FF->
  • srl: ->SRL-> (占用的资源少)
  • 注:srl为查找表(不支持复位),FF为触发器

2、ram_style和rom_style
利用vivado综合生成memory。支持:Block RAM,分布式资源(LUT RAMs)。
Verilog:

(*ram_style = "distributed"*) reg [data_size-1:0] myram[2**addr_size-1:0]

3、use_dsp48

  • 利用综合工具综合算术运算(dsp48):乘法、乘加/乘减、乘累加。
  • 但是,加法,减法,累加利用常规逻辑运算实现。
    代码:
use_dsp48   yes/no

[例1]如果有24-bit加24-bit,再存入寄存器;需要24个LUT,25个Register。因此需要利用dsp48,来减少资源消耗和加速性能。

4、其他属性:
black_box :综合工具对此不进行综合。value:yes/no,这个属性能被放在module,entity,component内。

1.3 IP创建使用

1.3.1 IP使用方法

  1. IP management创建IP工程 (IP Integrator)
  2. 在当前工程中定制IP(写好Verilog HDL,综合,>Tools>Create and Package New IP)
  3. OOC:综合后的IP核
    globally:只生成RTL代码,在工程内综合
  4. IP版本控制,能锁定,能更新
  5. IP导入工程(.xci:Add Existing IP,
    .dcp:Add or Create Design Sources)
    调用IP:新建文件夹,复制IP进去,在Tools > Settings > IP > Repository > 导入路径
  6. 使用IP:IP Catalog(有官方IP、第三方IP、User IP) —> 选择IP —> IP实例化到top.v文件内
  7. IP综合后,会生成单独的xdc约束文件(一般为只读)。
    使用IP内的FIFO(先入先出存储器):FIFO Generator;
    PLL(锁相环,分频/倍频时钟管理电路):Clock Wizard;
    *---------------------------------------------------------------------------
    Note: CMT(Clock Management Tile)包括MMCM(Mixed-mode Clock Manager)和PLL(Phase-locked loop)
    *---------------------------------------------------------------------------

1.4 模块化设计

顶层模块(top)---module A----module A1----module A2----module A3---module B---module C----module C1

2 Vivado HLS 学习笔记

2.1 HLS 概述——Vivado HLS加速FPGA算法开发

Vivado集成了HLS工具,使用C、C++以及System C语言对Xilinx的FPGA器件进行编程。用户通过高层次综合生成HDL级的IP核,从而加速IP创建。
缺点是不方便HDL的细节调整。
HLS(high-level synthesis,高层次综合)支持C/C++/System C(也可以使用matlab输出.c/.h)。

2.1.1 开发流程:

(1)创建工程,编写设计输入(.c/.cpp/.h),测试文件testbench(.c/.cpp)
(2)Run C Simulation
(3)Run C Synthesis
(4)Run C/RTL Cosimulation
(5)Export RTL
*注意:.h文件不要添加到工程内,
C Testbench文件非常重要,用于输出结果自检,生成RTL Testbench.
在C Synthesis步骤中添加Constraints/Directives.
资料:ug871,ug902

2.1.2 Expolrer、Toolbars使用:

  1. Explorer:可以有多个Solution,下面包括:constraints,csim,impl,sim,syn

  2. 每个solution都有自己的directives,用来优化C综合,可以写在source file,也可以写在directive file里(推荐)。
    写在ditective.tcl内,作为一个tcl命令,每个solution都有自己的命令,可以查看不同的综合效果。移植到第三方软件,也需要移植directive.tcl。

【例1】实现简单的数组运算
头文件:VectorAdd.h

#define N 5	//向量长度
typedef int data_t;
void VectorAdd(data_t A[N], data_t c, data_t B[N]);

模块代码:VectorAdd.cpp

#include "VectorAdd.h"void VectorAdd(data_t A[N], data_t c, data_t B[N])
{unsigned int i;myloop:   //此为directive标签,使用ditective.tclfor(i=0; i<N; i++){
//#pragma HLS PIPELINE //此为嵌入C语言的ditectiveB[i] = A[i] + c;}
}

testbench代码:

#include <iostream>
#include <iomanip>
#include "VectorAdd.h"
using namespace std;int main()   //main函数必须为int类型,0正确,1错误
{data_t A[N] = {-4, -3, 0 ,1 ,2};	//输入data_t c = 5;						//输出data_t B[N] = {0};					//输出data_t RefB[N] = {1, 2, 5, 6, 7};	//参考unsigned int i = 0;unsigned int errcnt = 0;			//错误次数VectorAdd(A,c,B);					//函数cout << setfill('-' ) << setw(35) << '-' << '\n';cout << setfill(' ' ) << setw(10) << left << 'A';cout << setfill(' ' ) << setw(10) << left << 'c';cout << setfill(' ' ) << setw(10) << left << 'B';cout << setfill(' ' ) << setw(10) << left << "RefB" << '\n';cout << setfill('-' ) << setw(35) << '-' << '\n';for(i=0; i<N; i++){cout << setfill(' ') << setw(10) << left << A[i];cout << setfill(' ') << setw(10) << left << c;cout << setfill(' ') << setw(10) << left << B[i];cout << setfill(' ') << setw(10) << left << RefB[i];if(B[i] == RefB[i]){cout << '\n';}else{cout << '(' << RefB[i] << ')' << '\n';errcnt++;}}cout << setfill('-') << setw(35) << '-' << '\n';if(errcnt > 0){cout << "Test Failed" << '\n';return 1;   //错误}else{cout << "Test Pass" << endl;return 0;   //正确}
}

注:main函数必须为int类型,0正确,1错误

3、Toolbars:有New Solution,Run C Simulation,Run C Synthesis,Run C/RTL Cosimulation,Export RTL,Open Report,Open Wave Viewer,Compare Reports

4、C语言中不可被综合的部分:①动态内存分配②涉及到系统层面的操作

2.1.3 C Sythesis(C综合)

查看报告:Outline:Performance,Lantency和Utilization
可以和其他的solution比较,查看性能差异

2.1.4 Run C/RTL Cosimulation

Vivado Simulator,Verilog,Dump Trace:port
仿真报告:Status:pass,就可以查看仿真波形

2.2 处理任意精度数据类型

2.2.1 C语言支持

  1. 任意精度的数据类型
语言整型数据类型需要的头文件
C[u]int(1024 bits)#include <ap_cint.h>
C++ap_[u]int(1024 bits)能被拓展到32Kb宽#include <ap_int.h>
C++ap_[u]fixed<W,I,Q,O,N>#include <ap_fixed.h>

有兴趣参考ug902,Table 1-7

1、例子:18*18的乘法器
Input data type :
ap_int<18>
Product data type : ap_int<36>
使用任意精度的数据类型可以获得:更高的时钟频率,更好的数据吞吐率、消耗更少的资源。

2、 提示:
(1)定义数据类型最好在头文件内。
(2)在debug中非常有用。

C++头文件

#include <ap_int.h>#define W 18
#define __NO_SYNTH__
#ifdef __NO_SYNTH__
typedef int data_t;
typedef int prod_t;
#else
typedef ap_int<W> data_t;
typedef ap_int<2*W> prod_t;
#endif

在Vivado HLS Outline 中会高亮显示。

(3)sizeof()返回值返回数据类型或变量的数据长度。
sizeof(type/variable)

(4)数据类型

种类类型最小位宽Note
bolleanbool1 btye
characterchar1 byte
wchar_t1 byte可能是unsigned/signed
char16_t2 bytesc++11 type(HLS不支持)
char32_t4 bytesc++11 type(HLS不支持)
intergershort2 bytes
int4 bytes
long4 bytes
long long8 bytesc99/c++11 type
floating pointfloat4 bytes
double8 bytes
long double8 bytes

sizeof()代码:

cout << "ap_int<1>:\t" << sizeof(ap_int<1>) << "bytes" << endl;
cout << "ap_int<16>:\t" << sizeof(ap_int<16>) << "bytes" << endl;
cout << "ap_int<20>:\t" << sizeof(ap_int<20>) << "bytes" << endl;
ap_fixed<4,1> a = 0.125;
cout << "ap_fixed<4>:\t" << sizeof(a) << "bytes" << endl;

输出:

ap_int<1>:1 bytes          1->8->8/8=1 byte
ap_int<16>:2 bytes         16->16->16/8=2 bytes
ap_int<20>:4 bytes         20->32->32/8=4 bytes
ap_fixed<4>:1 bytes        4->8->8/8=1 byte

MVSC可以编译HLS,需要进行设置。

2.3 数据类型的转换

  1. 数据初始化:
int var_i = -6;
ap_int<6> a_6bit_var_c = -22;    //copy初始化
ap_int<6> a_6bit_var_d(-22);     //direct初始化
//ap_int<6> a_6bit_var_u{-22};     //uniform初始化,不支持ap_int<6> a_6bit_var_r2("0b101010",2);
ap_int<6> a_6bit_var_r8("0o52",8);
ap_int<6> a_6bit_var_r10("-22",10);
ap_int<6> a_6bit_var_r16("0x2A",16);
  1. ap_[u]fixed变量定义与初始化:
    ap_[u]fixed<W,I,Q,O>
    W:总字长
    I:整数位字长
    W-I:小数位字长
    Q:量化模式,default:AP_TRN_ZERO
    O:溢出模式,default:AP_WRAP

[例1] var1和var2的真值:

ap_fixed<3,2> var1 = 1.25;          //1   [01.0]
ap_fixed<3,2,AP_RND> var2 = 1.25;   //1.5 [01.1]

[例2] var3和var4的真值:

//19的字长为:                         [010011.]
ap_fixed<4,4> var = 19;                //[0011.]因此为3
ap_fixed<4,4,AP_RND,AP_SAT> var4 = 19; //[0111.]四位有符号数的最大值7
  1. f代表单精度的浮点数float
double   vf2(5.0);
float    vf3(5.0f);
  • Vivado HLS的math库(hls_math.h)提供了可综合的C/C++库的扩展支持。
  1. ①隐式数据转换:promotion:小类型数据变大类型数据。
    conversion:大类型数据变小类型,会导致损失或错误。
    ②显式数据类型转化:
ap_uint<3>     i3 = 4;
ap_uint<4>     i4 = 10;
ap_ufixed<6,4> i5 = i4/i3;
cout << "The value of i5:\t" << i5 << "\n";ap_ufixed<6,4> i6 = (ap_ufixed<6,4>)i4/i3;
cout << "The value of i6:\t" << i6 << "\n";ap_ufixed<6,4> i9 = (ap_ufixed<6,4>)(i4)/i3;
cout << "The value of i6:\t" << i9 << "\n";

输出:

The value of i5: 2
The value of i6: 2.5
The value of i9: 2.5
  1. 二进制运算符
  • 两个变量相加(M+N):位宽为ap_int<max(M,N)+1>
  • 两个变量相乘(M*N):位宽为ap_int<M+N>
  • 两个变量相除(M/N):位宽为ap_int
  • 两个变量取余(M%N):位宽为ap_int<min(M,N)>
    保证大数据不溢出,小数据不损失。
  • typeid获取数据类型
#include <iostream>
#include <iomanip>
#include <typeinfo>
#include <ap_int.h>
#include <ap_fixed.h>using namespace std;int main()
{ap_int<4> v1 = 3;int v2 = 6;ap_fixed<6,2> v3 = 1.25;cout << left << setw(30) << setfill("-") << "-" << "\n";cout << left << "v1" << typeid(v1).name() << "Value:"<< v1 << "\n";cout << left << "v2" << typeid(v2).name() << "Value:"<< v2 << "\n";cout << left << "v3" << typeid(v3).name() << "Value:"<< v3 << "\n";cout << left << setw(30) << setfill("-") << "-" << "\n";return 0;
}

2.4 复合数据类型

有结构体和数组。数组被映射为memory端口。

1、结构体:当结构体被用在顶层函数(top-level)时,

  • 结构体元素被映射为scalar端口。(先在头文件中声明)

[例2]头文件中定义结构体:

//--------------------------StructPort.h
#include <ap_int.h>#define W 4
#define N 4typedef ap_int<W> A_t;
typedef ap_uint<W> B_t;typedef struct {A_t A;         //占据4 bits,1 byteB_t B[N];      //占据4*4=16 bits,但是为4 bytes(4 bits占一位)
}data_t;data_t StructPort(data_t i_val);
//-------------------------.c
#include "StructPort.h"data_t StructPort(data_t i_val)
{data_t o_val;int i;o_val.A = i_val.A + 2;for(i=0;i<N;i++){o_val.B[i] = i_val.B[i] + 2;}return o_val;
}
cout << "the size of this struct is " << sizeof(data_t) << "\n";输出:the size of this struct is 5
  • 结构体优化方式:Byte_pad:filed_levelstruct_level

1.1 数据包选择(Data Pack Mode):插入Directives,DATA_PACK,field_level,OK------->%HLS DATA_PACK variable=i_val field_level

  • field_level的含义:结构体内所有的元素的位宽必须是以8为边界,4bit->8bit,12bit->16bit
  • struct_level的含义:每个元素的实际位宽保留,但是在封装后的位宽以8bit的整数倍为边界。
  • 可以看出Data Pack可以降低lantency和initial interval,提高线路吞吐率,这里利用的是for循环展开。

2、枚举类型(enumerated type):数值定义为一个符号常量
枚举类型会自动分配整数,第一个是0,后面的依次加1。
[例3]

//------------------------EnumApp.h
#include <ap_int.h>#define W 4
typedef ap_int<W> a_t;
typedef enum {M_INIT,     //0M_ADD,      //1M_SUB,      //2M_HOLD      //3
} mymode_t;a_t EnumApp(a_t A,mytype_t mode);
//-----------------------.c
#include "EnumApp.h"a_t EnumApp(a_t A,mymode_t mode)
{static a_t res;switch(mode){case M_INT: res = A;       break;case M_ADD: res = res + A; break;case M_SUB: res = res - A; break;case M_HOLD:break;}return res;
}
  • mode会被自动综合成2 bits

3、小结:

  • Vivado HLS支持struct和enum。
  • struct和enum都可以成为top-level函数的接口。
  • 如果struct出现在top-level函数接口:能被data pack封装。
  • 如果enum出现在top-level函数接口:实际上是一个整数,HLS能自动推断数据位宽。

2.5 C/C++基本运算

[例4] 加一个常数。

//----------------------AddConst.h
#include <ap_fixed.h>#define W 10
#define I 2
typedef ap_ufixed<W,I> din_t;void AddConst(din_t din,din_t &sum);
//---------------------AddConst.cpp
#include "AddConst.h"void AddConst(din_t din,din_t &sum)
{sum = din + din_t(0.25);   //din_t(0.25)为常数
}
//-------------------AddConst_tb.cpp
#include <iostream>
#include <iomanip>
#include "AddConst.h"
using namespace std;int main()
{for(i=0;i<10;i++>){AddConst(va,sum);RefSum = va + din_t(0.25);va = va + din_t(0.125);if(sum == RefSum){cout << '\n';}else{ErrCnt++;cout << '(' << "Failed" << ')' << '\n';}}
}
  • 逻辑运算:! && ||返回true/false
  • 位运算:>> << ~ & | ^ 返回

[例5] Complex Multiplier

#include "CmpMult.h"void CmpMult(t_a_cmp a,t_b_cmp b,t_p_cmp &p)
{data_a_t ar = std::real(a);data_a_t ai = std::imag(a);data_b_t br = std::real(b);data_b_t bi = std::imag(b);#ifndef Solution1
#define
//法1:data_p_t pr;data_p_t pi;//4次乘法,2次加法:使用4个dsp48pr = (ar * br) - (ai * bi);pi = (ar * bi) + (ai * br);
//法1 end
#else
//法2:data_p_t pc;data_p_t pr;data_p_t pi;//3次乘法,5次加法:使用3个dsp48,19个FF,1个LUTpc = bi * (ar -ai);pr = pc + ar * (br - bi);pi = pc + ai * (br + bi);
#endifp.real() = pr;p.imag() = pi;
}

[例6] 欧几里得算法:计算最大公约数
-GCD(1071,462)

abComputation Process
10714621071=2*462+147
462147462=3*147+21
14721147=7*21+0
210
  • b=a%b => b==0 (判断,循环)
//---------------------------gcd.h
#include <ap_int.h>#define LW 11
#define SW 10typedef ap_uint<LW> da_t;
typedef ap_uint<SW> db_t;//Make sure the port da is the larger one
//the port db is the smaller one
db_t gcd(da_t da,db_t db);
//--------------------------gcd.cpp
#include "gcd.h"db_t gcd(da_t da,db_t db)
{if(db == 0){return da;}else{return gcd(db,da % db);    //HLS不支持递归函数}
}

2.6 C/C++测试平台的基本架构——描述高效的C testbench

  1. C++描述testbench:Driver/Stimulus -> Reference Model & DUT(design under test) -> Monitor -> Scoreboard(得分板):参考模型和设计输出对比。

  2. C仿真比RTL/Verilog仿真快。

  3. C testbench的作用:验证C函数的正确性(C simulation),验证RTL设计(C/RTL Cosimulation)。

  4. Testbench的要求:
    ①top_level函数多次执行,验证多种可能性。
    ②输出比较
    ③返回值:0:正确,1:有错误。

[例7]

#include "ScalarMult.h"
prod_t ScalarMut(data_t A,data_t B)
{prod_t prod;prod = A * B;return prod;
}
#include <iostream>
#include <iomanip>
#include "ScalarMult.h"using namespace std;int main()
{data_t A[4] = {-4,4,0,5};data_t B[4] = {4,-4,1,5};prod_t RefP[4] = {-16,-16,0,25};prod_t P;unsigned int i;unsigned RrrCnt = 0;cout << left << setw(30) << setfill('-') << '-' << '\n';cout << left << setw(10) << setfill(' ') << 'A';cout << left << setw(10) << setfill(' ') << 'B';cout << left << setw(10) << setfill(' ') << 'P' << '\n';cout << left << setw(30) << setfill('-') << '-' << '\n';for(i=0;i<4;i++){P = ScalarMult(A[i],B[i]);cout << left << setw(10) << setfill(' ') << A[i];cout << left << setw(10) << setfill(' ') << B[i];cout << left << setw(10) << setfill(' ') << P;if(P == RefP[i]){cout << '\n';}else{cout << '(' << RefP[i] << ')' << endl;ErrCnt++;}}cout << left << setw(30) << setfill
}

3 硬件加速设计方法

3.1 高质量VerilogHDL描述方法

3.1.1基本认知

  1. HDL语言仅是对已知硬件电路的文本表现形式编写前,对所需实现的硬件电路“胸有成竹”。
  2. 互联性:wire型变量描述各个模块之间的端口与网线连接关系
  3. 并发:可以有效地描述并行的硬件系统
  4. 时间:定义了绝对和相对的时间度量,可综合操作符具有物理延迟
  5. 可综合的语句:always,if-else,case,assign
  6. 不可综合的语句:function,for,fork-join,while(用于testbench)

3.1.2 映射的硬件结构

1.if-else:多路选择器(multiplexing hardware)
输出结果由输入的选择条件决定。

if (Aflag = '1') thenOutData <= A + B;
elseOutData <= C + D;
endif

[./001-multiplexing.jpg]
重构if-else映射的硬件结构:加法器结构复杂,减少了一个加法器,减少了硬件的面积。

if(Aflga == 1'b1)beginOp1 <= A;Op2 <= B;end
elsebeginOp1 <= C;Op2 <= D;endOutData <= Op1 + Op2;

[./001-multiplexing2.jpg]
但第一种元件控制信号Aflag的延迟只有一个选择器,第二种元件控制信号Aflag的延迟有控制器和加法器之和。第二种电路性能可能比第一种性能差。

  1. 单if语句:无优先级的判断结构
    推荐初学者尽量使用单if语句(if…else if…else if)描述多条件判断结构
always @(a or b or c or d or sel0 or sel1 or sel2 or sel3) beginz = 0;if(sel3) z = d;else if(sel2) z = c;else if(sel1) z = b;else if(sel0) z = a;
end
  1. 多if语句:具有优先级的判断结构
always @(a or b or c or d or sel0 or sel1 or sel2 or sel3) beginz = 0;if(sel0) z = a;if(sel1) z = b;if(sel2) z = c;if(sel3) z = d;
end
  • 最后一级选择信号具有最高优先级。
  • 具有优先级的多选结构会消耗组合逻辑。不推荐这种写法
  • 某些设计中,有些信号需要先到(如关键使能信号,选择信号等),有些需要后到达(如慢速信号、有效时间较长的信号等),此时则需要使用if…if结构。
  • 设计方法:最高优先级给最迟到达的关键信号
  1. case:无优先级的判断结构
always @(a or b or c or d or sel0 or sel1) begincase({sel0,sel1})2'b00: z = d;2'b01: z = c;2'b10: z = b;2'b11: z = a;default: z = 1'b0;endcase
end
  • 与单if语句的区别:条件互斥
  • 多用于指令译码电路
  1. latch:异步电路、门控时钟(慎用)
  • 不能过滤毛刺,能用D触发器,不用latch
  • 容易引入latch的途径:使用不完备的条件判断语句,缺少else,缺少default

4 备忘录

  1. Docnav使用,FPGA基础,Vivado使用,HLS使用

  2. Verilog语句讲解:
    组合逻辑
    时序逻辑

  3. 模块例化

  4. Vivado需要加环境变量

  5. Vivado生成bit流文件后,软核设计完成。

  6. File > Lauch SDK。打开Xilinx SDK(软件开发套件),会生成一个.hdf文件(硬件设计文件),可以查看寄存器地址。

  7. 烧录方法:
    (1)准备两条线,先烧写bit流文件,再烧写软件。
    (2)生成.bit文件和.elf/.hex文件后,使用脚本,将软核和软件整合到一起(生成两个文件.bit(断电丢失)和.mcs(烧写到flash内部)),使用Vivado烧写。

  8. 打开C_Sky DebugServer就可以看到连接是否成功。

  9. 设计结构:
    PS :Processing System(系统)
    PL :Programmable Logic(E902软核)
    PS和PL通过AXI来通信
    链接:https://blog.csdn.net/boayel/article/details/104090014

  10. 为e902添加自己的模块,查看平头哥的官方手册,dummy为空模块,选择一个dummy,加入自己/官方的IP,只要接口一样就能加入,AHB总线,AXI总线,有一个AHB到AXI的桥IP(AHB-Lite to AXI Bridge).

  11. 建立Block design,加入IP,选中IP,ctrl+T加入引脚。

  12. 自定义IP:Tools->Create and Package New IP->Create AXI4 Peripheral,完成后,在IP Catalog中,右键编辑IP。

  13. 打开verilog文件,按照提示输入代码。加入输出引脚,输入寄存器register到输出,一层层例化。

  14. 在Package IP里确认。打包Re-Package IP

  15. 在Block Design内使用此IP,加入互联IP(AXI Interconnect),设置slave接口1个,master接口1个,S连M(x2),再自动连线,删除复位模块,连复位线。输出模块的引脚右键make external,或者ctrl+T。验证设计(validate design)。

  16. 分配地址,Address Editor,查看用户手册,加入地址。完成

  17. sources,模块右键Generate Output Products。Create HDL Wrapper。打开生成的.v文件。

  18. 加入myio_top顶层文件,例化上面的.v文件,

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/324633.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vivado Spartan-7 配置存储器器件

下表所示闪存器件支持通过 Vivado 软件对 Spartan -7 器件执行擦除、空白检查、编程和验证等配置操作。 本附录中的表格所列赛灵思系列非易失性存储器将不断保持更新 &#xff0c; 并支持通过 Vivado 软件对其中所列非易失性存储器 进行擦除、空白检查、编程和验证。赛灵…

带EXCEL附件邮件发送相关代码

1.查看生成的邮件 2.1 非面向对象的方式&#xff08;demo直接copy即可&#xff09; ​ REPORT Z12. DATA: IT_DOCUMENT_DATA TYPE SODOCCHGI1,IT_CONTENT_TEXT TYPE STANDARD TABLE OF SOLISTI1 WITH HEADER LINE,IT_PACKING_LIST TYPE TABLE OF SOPCKLSTI1 WITH HEADER LIN…

在做题中学习(57):寻找数组的中心下标

724. 寻找数组的中心下标 - 力扣&#xff08;LeetCode&#xff09; 解法&#xff1a;前缀和后缀和 思路&#xff1a;要看一个数是不是中心下标&#xff0c;就看他前面数的和 与 后面数的和 相不相等。 1.i前面数的和&#xff0c;是[0,i-1] 的前缀和&#xff0c;i后面数的和&am…

快递物流查询:如何实现快递批量查询?这些技巧助你轻松应对

在日常生活和工作中&#xff0c;我们经常需要查询快递物流信息&#xff0c;尤其是当面对大量的快递包裹时&#xff0c;逐一查询无疑会耗费大量的时间和精力。这时&#xff0c;实现快递批量查询就显得尤为重要。本文将为你介绍办公提效工具一些实现快递批量查询的技巧&#xff0…

国内有哪些知名的网络安全厂商?

首先就是360&#xff0c;这个我相信大家并不陌生了吧&#xff0c;你的电脑装过360么&#xff1f; 360在个人终端服务那是妥妥的扛把子&#xff0c;但是在企业服务里虽然有他们的身影却略显不足。 第二个就是深信服&#xff0c;网络安全的老牌大佬&#xff0c;业务覆盖了全球5…

【数据分析】 JupyterNotebook安装及使用简介

各位大佬好 &#xff0c;这里是阿川的博客 &#xff0c; 祝您变得更强 个人主页&#xff1a;在线OJ的阿川 大佬的支持和鼓励&#xff0c;将是我成长路上最大的动力 阿川水平有限&#xff0c;如有错误&#xff0c;欢迎大佬指正 在数据分析中&#xff0c;一般用Pycharm编辑代…

18 分页:介绍

目录 简单例子 页表存在哪里 列表中究竟有什么 分页&#xff1a;也很慢 内存追踪 小结 在解决大多数空间管理问题上面&#xff0c;操作系统有两种方法&#xff1a; 第一种就是将空间分割成不同长度的分片&#xff0c;类似于虚拟内存管理中的分段&#xff0c;但是这个方法…

MySQL45讲(一)(40)

回顾binlog_formatstatement STATEMENT 记录SQL语句。日志文件小&#xff0c;节约IO&#xff0c;但是对一些系统函数不能准确复制或不能复制&#xff0c;如now()、uuid()等 在RR隔离级别下&#xff0c;binlog_formatstatement 如果执行insert select from 这条语句是对于一张…

win10禁止自动更新的终极方法

添加注册表值 1.运行&#xff0c;输入regedit 2.打开注册表编辑器依次进入以下路径“计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings”。 3.在Settings项中&#xff0c;新建DWORD&#xff08;32位&#xff09;值(D)&#xff0c;重命名为以下命名“Fl…

vuex核心概念-getters

除了state之外&#xff0c;有时我们还需要从state中派生出一些状态&#xff0c;这些状态是依赖state的&#xff0c;此时会用到getters。

2023版brupsuite专业破解安装

安装教程&#xff0c;分两部分&#xff1a; 1、安装java环境、参考链接JAVA安装配置----最详细的教程&#xff08;测试木头人&#xff09;_java安装教程详细-CSDN博客 2、安装2023.4版本brupsuite&#xff1a;参考链接 2023最新版—Brup_Suite安装配置----最详细的教程&…

实体同城商家短视频获客,3天直播课,玩转实体商家私域,引爆门店增长

课程内容&#xff1a; 实体同城3天直播课【资料】 实体商家获客第一天 .mp4 实体商家获客第二天上.mp4 实体商家获客第二天,mp4 实体商家获客第三天.mp4 实体商家获客第4天.mp4 网盘自动获取 链接&#xff1a;https://pan.baidu.com/s/1lpzKPim76qettahxvxtjaQ?pwd0b8x…

数据结构----二叉树

博主主页: 码农派大星. 关注博主带你了解更多数据结构知识 1. 树型结构 1.1 概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上…

对Windows超融合S2D的一些补充

先说一个不知道算不算BUG的例子&#xff0c;下面这个存储池是用两台服务器各2块10G建立的&#xff0c;除去系统保留的部分&#xff0c;显示还有13G可用。 但如果使用其新建虚拟磁盘会显示可用的空间为0 然后我又各增加了一块10G硬盘进池&#xff0c;变成了可用空间为30.5GB …

如何理解VMware中的网络模式(NAT、桥接、仅主机)

目录 Ⅰ.NAT模式 Ⅱ.仅主机模式 Ⅲ.桥接模式 Ⅰ.NAT模式 NAT模式&#xff1a;将物理机的网卡作为虚拟交换机的上线链路&#xff0c;将vmware的私有网络转成可以上网的地址进行网络访问&#xff0c;因此在NAT模式下虚拟机是可以访问外部网络的&#xff08;图一&#xff09; …

springboot整合redis多数据源(附带RedisUtil)

单数据源RedisUtil(静态) 单数据源RedisUtil,我这里implements ApplicationContextAware在setApplicationContext注入redisTemplate,工具类可以直接类RedisUtil.StringOps.get()使用 package com.vehicle.manager.core.util;import com.alibaba.fastjson.JSON; import lombok.e…

uni-segmented-control插件使用

dcloud插件市场 前端/uniapp 1.HBuildX打开目标项目 2.进入dcloud插件市场下载目标插件 3.看到如下提示(已经可以在目标项目中使用插件啦) 4.项目正式使用

风丘方案助力车企升级 解决“国六”标准新难题

一 背景 尾气排放指标是衡量汽车质量和品质的主要指标之一&#xff0c;且汽车的尾气排放必须达到相应的标准才准许出厂&#xff0c;因此&#xff0c;对汽车排放的尾气进行检测是汽车生产过程的重要环节。汽车尾气检测过程是在排放实验室里进行的&#xff0c;这需要模拟汽车实际…

CSS-伪类选择器

结构伪类选择器 作用&#xff1a;根据元素的结构关系查找元素 分类&#xff1a; 选择器说明元素名:first-child查找第一个元素元素名:last-child查找最后一个元素元素名:nth-child(N)查找第N名元素 <!DOCTYPE html> <html lang"en"> <head><me…

FPGA OSD 方案,应用于XBOX游戏机收费等领域

FPGA方案&#xff0c;HDMI IN接收原始HDMI 信号&#xff0c;HDMI OUT输出叠加字符/图片后的HDMI信号 客户应用&#xff1a;XBOX游戏机收费 主要特性&#xff1a; 1.支持多分辨率格式显示 2.支持OSD 叠加多个图层 3.支持字体大小随意配置 4.支持字体格式随意配置 5.零延时&…