FPGA HLS Matrix_MUL 矩阵乘法的计算与优化

本文介绍如何使用Vivado HLS工具优化4x4矩阵乘法的实现过程,包括循环展开、流水线设计、数组分割及重塑等并行优化技术的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

新建Vivado工程

设置clock,10表示一个周期10ns,带宽100M

vivado工具比较保守,计算需要的延迟是14,实际优化可以在10,设置大一点,优化的计算更多,一般约束设置大一点在30-50

image-20221031202053105

选择开发板 xc7z020clg400-1

image-20221031201900305

Source:描述功能模块的cpp和h代码

Test Bench:测试代码的main.cpp

image-20221031204119132

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列

image-20221031205949570

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):

image-20221031211100559

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逻辑,生成综合报告,包括时序,延时,资源占用,端口信息等。

image-20221101110827719

  • Loop Latency:完成所有循环需要的时钟周期

  • Iteration Latency:循环里面,迭代一次需要的周期

  • Initiation Interval(II):两次迭代之间的时钟间隔

  • Trip Count:循环次数

image-20220925202823776


工程的资源占用情况(工程使用的资源数目&FPGA的总可用资源):

image-20221101111612197


端口信息,ABC数组默认存在存储器ap_memory中,以下ap_clk、ap_rst、ap_start、ap_done、ap_idle、ap_ready都是控制信号

image-20221101111911350

端口分析

  1. 控制端口用于控制和显示该模块的工作状态。各个端口的功功能如下,默认情况下会生成下面四个控制端口。
    • ap_start(in):为高时,该模块开始处理数据。
    • ap_done(out):为高时,表示模块处理数据完成。
    • ap_idle(out):表明模块是否处于空闲态。高电平有效。为高时,该处于空闲态。
    • ap_ready(out):为高时,表示模块可以接受新的数据。
  2. 数据端口用于传递模块的输入输出参数。
    参数d_o,d_i 为数组类型,故默认状态下回生成内存接口。内存接口 (数组类型参数)数据来自外部的memory,通过地址信号读取相应的数据,输入到该模块中。输入数组从外部内存中读源数据,输出数组从向外部内存写入结果数据。各个端口的定义如下。
    • address:地址信号
    • ce0:片选信号
    • we0:写使能信号
    • d0 :数据信号

并行优化

A和B的四个数同时相乘,让原先一行乘一列需要8个周期完成,变成一个周期完成

两种实现方法:

  • unroll,手动展开循环
  • pipline,系统自动推断展开
    • partiotion,切分数组
    • reshape,改变存储长度

unroll

image-20221101115409688

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

image-20221101204510037

pipeline

选择第二个for循环,设置pipeline的迭代间隔等于1

image-20221101210011113

自动添加优化语句:#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综合结果:

image-20221101210324088

计算一共需要34个周期,这已经比以前的169个周期快了不少了,但是我们希望16个周期就能计算完。

没有达到预期的目标是因为没有指定数组A和B的端口类型,默认为双端口。读取数据时一次只能读取2个,所以16次计算,每次读取4个数据,一共16*4个数据需要花费16*4/2=32个周期

端口只有ce0和ce1:

image-20221101210841771

所以需要同时取出四个数据,可以通过ArrayPartition和ArrayReshape实现

Array_Partition

数组分割:A竖着切割(按照维度2),分成4份分别存储在4个存储器里面;B横着切割(按照维度1)分别存储在4个存储器里面;

计算时同时各取出4个进行运算:

注意维度:高是维度1,宽是维度2

image-20221101211456726

image-20221101211537330

#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

image-20221101211658088

数组A和B都有四个端口同时读数据了:

image-20221101211756643

Array_Reshape

重定义数组在存储器中的排列方式。原来默认一个地址行放一个数据,宽度只占8位。

A每次计算取一行,所以按照维度 2的方向放置数据:

image-20220925201701754

#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综合结果:

image-20221101212321242

image-20221101212414932

latency约束

这里Latency指的是每次迭代的花费的周期数

设置计算消耗的周期数设置为5

image-20221101212901561

image-20220925195648390

#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

image-20221101212952817

原来不加latency时,延时是1,所以是(16-1)*1+1=16

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值