赞
踩
1、HLS设计:
C/C++/System C -> High-level Synthesis(Vivado HLS)-> IP核(设计核心) —>RTL系统级 —> 综合 —> 实现
(Tools >Validate Design)
2、HDL设计:
HDL代码 —> 综合 —> 实现
*资源:逻辑网表(EDIF)、约束(XDC)、物理数据(XDEF)
打开新建的工程文件文件夹,基本内容如下:
(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)新建RTL代码(寄存器级).v
(3)头文件可以用.v也可以用.vh。
(4)加入IP核(.xci)
(5)加入约束(.xdc):包括引脚约束和时序约束。
(6)加入仿真文件(.v)
(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
详情查看:ug835
1、语法分为可综合和不可综合:可综合的较少,用于创建工程;不可综合的有很多,主要用于测试(testbench)。
2、阻塞赋值与非阻塞赋值:
代码1:非阻塞赋值,执行顺序并行,边沿触发,生成时序逻辑
always @(posedge clk_i or negedge rst_n) begin
if(rst_n) cout <= 1'd1;
else if(cout == 4'd10) cout <= 1'd0; //逻辑运算一定要写完整
else cout <= cout + 1'd1;
end
代码2:阻塞赋值,执行顺序串行,电平触发,生成组合逻辑
always @(rst_n) begin
if(cout == 4'd5) cout = 1'd1;
else begin
a=b;
c=a;
cout = cout + 1'd1;
end
end
3、关键字:
parameter 参数
wire 线
reg 寄存器
assign 逻辑简单的语句
always 一直执行
begin end
case(value) endcase
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; end endmodule
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、约束分为:时序约束、IO约束、时序例外约束(有先后顺序)。
时序约束分为外部输入延时,内部延时,数据路径延时,输出延时。
(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 | 在同一时刻源上定义多个时钟时使用 |
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
(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,也可以是其他的衍生时钟。该命令不是设定周期或波形,而是描述时钟电路如何对上级时钟进行转换, 转换可以是如下关系:
衍生时钟又分为两种情况:
①Vivado自动推导的衍生时钟
②用户自定义的衍生时钟
首先来看第一种,如果使用PLL或者MMCM,则Vivado会自动推导出一个约束。在xdc文件中,不对着两个输入时钟进行约束,只对输入的clk进行约束,也可以看到vivado生成了约束(有三个约束,因为PLL会自动输出一个反馈时钟)。
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约束中的延迟约束。虚拟时钟和前面讲的延迟约束的使用场景不太相同。顾名思义,虚拟时钟,就是没有与之绑定的物理管脚。
虚拟时钟主要用于以下三个场景:
简而言之,之所以要创建虚拟时钟,对于输入来说,是因为输入到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。对于异步时钟,我们一般都会通过设计来保证时序能够收敛,而不是通过时序约束来保证。
不加任何IO约束的端口,时序要求被视为无穷大。set_input_delay
和set_output_delay
是从系统角度来约束的。-min
是hold slack
时间,不大于周期,-max
是setup slack
时间,需要<=0。一般默认都是设置0。
包括set_max_delay
,set_min_delay
,set_multicycle_path
,set_false_path
等,这类约束除了要满足xdc的先后优先级外,还要遵循自身的优先级限制。
总的准则是:针对同一条路径,对约束目标描述越具体的优先级越高。
注: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、时序的零起点:
用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
-add -master_clock sysclk
4、同步时钟与异步时钟:
<图片:高级时钟约束06>
在XDC中,所有的时钟都会被缺省认为是相关的,也就是说,网表中所有存在的时序路径都会被Vivado分析。这也意味着FPGA设计人员必须通过约束告诉工具,哪些路径是无需分析的,哪些时钟域之间是异步的。
1、移位寄存器:srl_style
用LUT建立:
(* srl_style = "register" *) reg [WIDTH-1:0] shreg;
interger i;
always @(posedge clk) begin
if(clken) begin
for(i=0;i<WIDTH-1;i=i+1)
shreg[i+1] <= shreg[i];
shreg[0] <= SI;
end
end
assign SO = shreg[WIDTH - 1];
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
use_dsp48 yes/no
[例1]如果有24-bit加24-bit,再存入寄存器;需要24个LUT,25个Register。因此需要利用dsp48,来减少资源消耗和加速性能。
4、其他属性:
① black_box :综合工具对此不进行综合。value:yes/no,这个属性能被放在module,entity,component内。
顶层模块(top)---module A----module A1
----module A2
----module A3
---module B
---module C----module C1
Vivado集成了HLS工具,使用C、C++以及System C语言对Xilinx的FPGA器件进行编程。用户通过高层次综合生成HDL级的IP核,从而加速IP创建。
缺点是不方便HDL的细节调整。
HLS(high-level synthesis,高层次综合)支持C/C++/System C(也可以使用matlab输出.c/.h)。
(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
Explorer:可以有多个Solution,下面包括:constraints,csim,impl,sim,syn
每个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.tcl
for(i=0; i<N; i++)
{
//#pragma HLS PIPELINE //此为嵌入C语言的ditective
B[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语言中不可被综合的部分:①动态内存分配②涉及到系统层面的操作
查看报告:Outline:Performance,Lantency和Utilization
可以和其他的solution比较,查看性能差异
Vivado Simulator,Verilog,Dump Trace:port
仿真报告:Status:pass,就可以查看仿真波形
语言 | 整型数据类型 | 需要的头文件 |
---|---|---|
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 |
---|---|---|---|
bollean | bool | 1 btye | |
character | char | 1 byte | |
wchar_t | 1 byte | 可能是unsigned/signed | |
char16_t | 2 bytes | c++11 type(HLS不支持) | |
char32_t | 4 bytes | c++11 type(HLS不支持) | |
interger | short | 2 bytes | |
int | 4 bytes | ||
long | 4 bytes | ||
long long | 8 bytes | c99/c++11 type | |
floating point | float | 4 bytes | |
double | 8 bytes | ||
long double | 8 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,需要进行设置。
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] 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
double vf2(5.0);
float vf3(5.0f);
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
#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; }
有结构体和数组。数组被映射为memory端口。
1、结构体:当结构体被用在顶层函数(top-level)时,
[例2]头文件中定义结构体:
//--------------------------StructPort.h
#include <ap_int.h>
#define W 4
#define N 4
typedef ap_int<W> A_t;
typedef ap_uint<W> B_t;
typedef struct {
A_t A; //占据4 bits,1 byte
B_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
1.1 数据包选择(Data Pack Mode):插入Directives,DATA_PACK,field_level,OK------->%HLS DATA_PACK variable=i_val field_level
2、枚举类型(enum
erated type):数值定义为一个符号常量
枚举类型会自动分配整数,第一个是0,后面的依次加1。
[例3]
//------------------------EnumApp.h
#include <ap_int.h>
#define W 4
typedef ap_int<W> a_t;
typedef enum {
M_INIT, //0
M_ADD, //1
M_SUB, //2
M_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;
}
3、小结:
[例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'; } } }
[例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个dsp48 pr = (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个LUT pc = bi * (ar -ai); pr = pc + ar * (br - bi); pi = pc + ai * (br + bi); #endif p.real() = pr; p.imag() = pi; }
[例6] 欧几里得算法:计算最大公约数
-GCD(1071,462)
a | b | Computation Process |
---|---|---|
1071 | 462 | 1071=2*462+147 |
462 | 147 | 462=3*147+21 |
147 | 21 | 147=7*21+0 |
21 | 0 |
//---------------------------gcd.h
#include <ap_int.h>
#define LW 11
#define SW 10
typedef 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不支持递归函数
}
}
C++描述testbench:Driver/Stimulus -> Reference Model & DUT(design under test) -> Monitor -> Scoreboard(得分板):参考模型和设计输出对比。
C仿真比RTL/Verilog仿真快。
C testbench的作用:验证C函数的正确性(C simulation),验证RTL设计(C/RTL Cosimulation)。
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 }
1.if-else:多路选择器(multiplexing hardware)
输出结果由输入的选择条件决定。
if (Aflag = '1') then
OutData <= A + B;
else
OutData <= C + D;
endif
[./001-multiplexing.jpg]
重构if-else映射的硬件结构:加法器结构复杂,减少了一个加法器,减少了硬件的面积。
if(Aflga == 1'b1)
begin
Op1 <= A;
Op2 <= B;
end
else
begin
Op1 <= C;
Op2 <= D;
end
OutData <= Op1 + Op2;
[./001-multiplexing2.jpg]
但第一种元件控制信号Aflag的延迟只有一个选择器,第二种元件控制信号Aflag的延迟有控制器和加法器之和。第二种电路性能可能比第一种性能差。
always @(a or b or c or d or sel0 or sel1 or sel2 or sel3) begin
z = 0;
if(sel3) z = d;
else if(sel2) z = c;
else if(sel1) z = b;
else if(sel0) z = a;
end
always @(a or b or c or d or sel0 or sel1 or sel2 or sel3) begin
z = 0;
if(sel0) z = a;
if(sel1) z = b;
if(sel2) z = c;
if(sel3) z = d;
end
always @(a or b or c or d or sel0 or sel1) begin
case({sel0,sel1})
2'b00: z = d;
2'b01: z = c;
2'b10: z = b;
2'b11: z = a;
default: z = 1'b0;
endcase
end
Docnav使用,FPGA基础,Vivado使用,HLS使用
Verilog语句讲解:
组合逻辑
时序逻辑
模块例化
Vivado需要加环境变量
Vivado生成bit流文件后,软核设计完成。
File > Lauch SDK。打开Xilinx SDK(软件开发套件),会生成一个.hdf文件(硬件设计文件),可以查看寄存器地址。
烧录方法:
(1)准备两条线,先烧写bit流文件,再烧写软件。
(2)生成.bit文件和.elf/.hex文件后,使用脚本,将软核和软件整合到一起(生成两个文件.bit(断电丢失)和.mcs(烧写到flash内部)),使用Vivado烧写。
打开C_Sky DebugServer就可以看到连接是否成功。
设计结构:
PS :Processing System(系统)
PL :Programmable Logic(E902软核)
PS和PL通过AXI来通信
链接:https://blog.csdn.net/boayel/article/details/104090014
为e902添加自己的模块,查看平头哥的官方手册,dummy为空模块,选择一个dummy,加入自己/官方的IP,只要接口一样就能加入,AHB总线,AXI总线,有一个AHB到AXI的桥IP(AHB-Lite to AXI Bridge).
建立Block design,加入IP,选中IP,ctrl+T加入引脚。
自定义IP:Tools->Create and Package New IP->Create AXI4 Peripheral,完成后,在IP Catalog中,右键编辑IP。
打开verilog文件,按照提示输入代码。加入输出引脚,输入寄存器register到输出,一层层例化。
在Package IP里确认。打包Re-Package IP
在Block Design内使用此IP,加入互联IP(AXI Interconnect),设置slave接口1个,master接口1个,S连M(x2),再自动连线,删除复位模块,连复位线。输出模块的引脚右键make external,或者ctrl+T。验证设计(validate design)。
分配地址,Address Editor,查看用户手册,加入地址。完成
sources,模块右键Generate Output Products。Create HDL Wrapper。打开生成的.v文件。
加入myio_top顶层文件,例化上面的.v文件,
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。