新建Vivado工程
设置clock,10表示一个周期10ns,带宽100M
vivado工具比较保守,计算需要的延迟是14,实际优化可以在10,设置大一点,优化的计算更多,一般约束设置大一点在30-50
选择开发板 xc7z020clg400-1
Source:描述功能模块的cpp和h代码
Test Bench:测试代码的main.cpp
C Code
matrix_mul.h
#ifndef __MATRIX_MUL__
#define __MATRIX_MUL__
#include "ap_fixed.h"
void matrix_mul(ap_int<8> A[4][4], ap_int<8> B[4][4], ap_int<16> C[4][4]);
#endif
-
#ifndef __xxxx__
#define __xxxx__
#endif
用于放置重复引入头文件 -
ap_fixed.h
:arbitrary precision data types 用于自定义精度整形(FPGA的浮点数运算很慢,在后面的模型推理的时候 整形数运算比较快) - 两个8位数相乘,结果最大可以是16位
.h文件包含了一个矩阵乘法的函数声明
matrix_mul.cpp
#include "matrix_mul.h"
void matrix_mul(ap_int<8> A[4][4], ap_int<8> B[4][4], ap_int<16> C[4][4])
{
for(int i = 0; i < 4; ++ i){
for(int j = 0; j < 4; ++ j){
C[i][j] = 0;
for(int k = 0; k < 4; ++ k){
C[i][j] += A[i][k]*B[k][j];
}
}
}
}
i行k列乘k行j列
main.cpp
#include "matrix_mul.h"
#include <iostream>
int main()
{
ap_int<8> A[4][4], B[4][4];
ap_int<16> C[4][4];
// initial A&B
for(int i = 0; i < 4; ++ i)
for(int j = 0; j < 4; ++ j)
A[i][j] = B[i][j] = i*4 + j;
// inference
matrix_mul(A, B, C);
// print C matrix
for(int i = 0; i < 4; ++ i){
for(int j = 0; j < 4; ++ j){
std::cout << "C["<<i<<"] = " << C[i][j] << "\t";
}
std::cout << std::endl;
}
return 0;
}
C Simulation
当有许多模块的时候需要指定顶层函数,设置顶层模块(top function):
C仿真主要用于编译C代码与HLS无关,测试功能是否与预期一致。使用C++完成testbench,同样地,C语言可以编译为对应的激励,驱动上一步骤完成的电路模块
_csim.log
文件显示仿真结果
INFO: [SIM 2] *************** CSIM start ***************
INFO: [SIM 4] CSIM will launch GCC as the compiler.
Compiling ../../../main.cpp in debug mode
Compiling ../../../matrix_mul.cpp in debug mode
Generating csim.exe
C[0] = 56 C[0] = 62 C[0] = 68 C[0] = 74
C[1] = 152 C[1] = 174 C[1] = 196 C[1] = 218
C[2] = 248 C[2] = 286 C[2] = 324 C[2] = 362
C[3] = 344 C[3] = 398 C[3] = 452 C[3] = 506
INFO: [SIM 1] CSim done with 0 errors.
INFO: [SIM 3] *************** CSIM finish ***************
C Synthesis
C++代码综合成RTL逻辑,生成综合报告,包括时序,延时,资源占用,端口信息等。
-
Loop Latency:完成所有循环需要的时钟周期
-
Iteration Latency:循环里面,迭代一次需要的周期
-
Initiation Interval(II):两次迭代之间的时钟间隔
-
Trip Count:循环次数
工程的资源占用情况(工程使用的资源数目&FPGA的总可用资源):
端口信息,ABC数组默认存在存储器ap_memory中,以下ap_clk、ap_rst、ap_start、ap_done、ap_idle、ap_ready都是控制信号
端口分析:
- 控制端口用于控制和显示该模块的工作状态。各个端口的功功能如下,默认情况下会生成下面四个控制端口。
- ap_start(in):为高时,该模块开始处理数据。
- ap_done(out):为高时,表示模块处理数据完成。
- ap_idle(out):表明模块是否处于空闲态。高电平有效。为高时,该处于空闲态。
- ap_ready(out):为高时,表示模块可以接受新的数据。
- 数据端口用于传递模块的输入输出参数。
参数d_o,d_i 为数组类型,故默认状态下回生成内存接口。内存接口 (数组类型参数)数据来自外部的memory,通过地址信号读取相应的数据,输入到该模块中。输入数组从外部内存中读源数据,输出数组从向外部内存写入结果数据。各个端口的定义如下。- address:地址信号
- ce0:片选信号
- we0:写使能信号
- d0 :数据信号
并行优化
A和B的四个数同时相乘,让原先一行乘一列需要8个周期完成,变成一个周期完成
两种实现方法:
- unroll,手动展开循环
- pipline,系统自动推断展开
- partiotion,切分数组
- reshape,改变存储长度
unroll
source文件自动添加优化代码:#pragma HLS UNROLL
#include "matrix_mul.h"
void matrix_mul(ap_int<8> A[4][4], ap_int<8> B[4][4], ap_int<16> C[4][4])
{
for(int i = 0; i < 4; ++ i){
for(int j = 0; j < 4; ++ j){
C[i][j] = 0;
for(int k = 0; k < 4; ++ k){
#pragma HLS UNROLL
C[i][j] += A[i][k]*B[k][j];
}
}
}
}
运行C综合的结果:((1+2)*4+2)*4+1 =57
pipeline
选择第二个for循环,设置pipeline的迭代间隔等于1
自动添加优化语句:#pragma HLS PIPELINE II=1
#include "matrix_mul.h"
void matrix_mul(ap_int<8> A[4][4], ap_int<8> B[4][4], ap_int<16> C[4][4])
{
for(int i = 0; i < 4; ++ i){
for(int j = 0; j < 4; ++ j){
#pragma HLS PIPELINE II=1
C[i][j] = 0;
for(int k = 0; k < 4; ++ k){
C[i][j] += A[i][k]*B[k][j];
}
}
}
}
C综合结果:
计算一共需要34个周期,这已经比以前的169个周期快了不少了,但是我们希望16个周期就能计算完。
没有达到预期的目标是因为没有指定数组A和B的端口类型,默认为双端口。读取数据时一次只能读取2个,所以16次计算,每次读取4个数据,一共16*4
个数据需要花费16*4/2=32
个周期
端口只有ce0和ce1:
所以需要同时取出四个数据,可以通过ArrayPartition和ArrayReshape实现
Array_Partition
数组分割:A竖着切割(按照维度2),分成4份分别存储在4个存储器里面;B横着切割(按照维度1)分别存储在4个存储器里面;
计算时同时各取出4个进行运算:
注意维度:高是维度1,宽是维度2
#include "matrix_mul.h"
void matrix_mul(ap_int<8> A[4][4], ap_int<8> B[4][4], ap_int<16> C[4][4])
{
// partiton 优化语句
#pragma HLS ARRAY_PARTITION variable=B complete dim=1
#pragma HLS ARRAY_PARTITION variable=A complete dim=2
for(int i = 0; i < 4; ++ i){
for(int j = 0; j < 4; ++ j){
#pragma HLS PIPELINE II=1
C[i][j] = 0;
for(int k = 0; k < 4; ++ k){
C[i][j] += A[i][k]*B[k][j];
}
}
}
}
C综合结果:
1*16+2 = 18
数组A和B都有四个端口同时读数据了:
Array_Reshape
重定义数组在存储器中的排列方式。原来默认一个地址行放一个数据,宽度只占8位。
A每次计算取一行,所以按照维度 2的方向放置数据:
#include "matrix_mul.h"
void matrix_mul(ap_int<8> A[4][4], ap_int<8> B[4][4], ap_int<16> C[4][4])
{
#pragma HLS ARRAY_RESHAPE variable=B complete dim=1
#pragma HLS ARRAY_RESHAPE variable=A complete dim=2
for(int i = 0; i < 4; ++ i){
for(int j = 0; j < 4; ++ j){
#pragma HLS PIPELINE II=1
C[i][j] = 0;
for(int k = 0; k < 4; ++ k){
C[i][j] += A[i][k]*B[k][j];
}
}
}
}
C综合结果:
latency约束
这里Latency指的是每次迭代的花费的周期数
设置计算消耗的周期数设置为5
#include "matrix_mul.h"
void matrix_mul(ap_int<8> A[4][4], ap_int<8> B[4][4], ap_int<16> C[4][4])
{
#pragma HLS ARRAY_RESHAPE variable=B complete dim=1
#pragma HLS ARRAY_RESHAPE variable=A complete dim=2
for(int i = 0; i < 4; ++ i){
for(int j = 0; j < 4; ++ j){
#pragma HLS LATENCY min=5 max=5
#pragma HLS PIPELINE II=1
C[i][j] = 0;
for(int k = 0; k < 4; ++ k){
C[i][j] += A[i][k]*B[k][j];
}
}
}
}
所以最后使用的周期数一共是:16×( Initiation Interval -1 ) + Latency = (16-1)×1+5 = 20
文章来源:https://www.toymoban.com/news/detail-487064.html
原来不加latency时,延时是1,所以是(16-1)*1+1=16
文章来源地址https://www.toymoban.com/news/detail-487064.html
到了这里,关于FPGA HLS Matrix_MUL 矩阵乘法的计算与优化的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!