FPGA HLS 卷积单元 数据类型hls优化约束设置

news2024/11/28 8:30:44

数据类型

自定义精度整形:

ap_int<4> in1, in2;
ap_int<8> concat;
concat = (in1, in2);	// in1和in2拼起来(按照补码拼起来)
/*
例子:
in1 = 1, in2 = -1
补码:
in1 =  0001
in2 =  1001 ==> 1110+1 ==> 1111
concat = 00011111 = 31
*/
concat.xor_reduce();	// 按位异或

image-20221104195907274

自定义定点数

为了替换float,double类型的数,加快运算,节约资源

ap_fixed<11, 6> Var1 = 22.96875; // 一共11个bit,其中6个bit表示整数,5个bit表示小数;剩一个bit表示正负数
ap_ufixed<12, 11> Var2 = 512.5;	// 一共12个bit,11位表示整数,最后一位表示小数

image-20221104200355155

卷积的量化或定点化

根据输入的数据,找到卷积层的数据范围

A= aaaaaaaaaaaaaaaa, fix_point=12
B= bbbbbbbbbbbbbbbb, fix_point=13
C= ????????????????, fix_point=13

????????????????*(2^13)=A*B/2^(12+13)

求C的编码:????????????????
= A*B/2^(12+13-13)
= A*B/2^(fix_pointA + fix_pointB – fix_pointC)

例子:
A:0010.1100 = 44/16 = 2+0.5+0.25 = 2.75 fix_point = 4
B:00101.100 = 44/8 = 5+0.5 = 5.5 fix_point = 3
C:????.???? Fix_point = 4
A*B =44*44 = 011110010000 == 右移(3+4) = 01111.0010000 = 15.125
一共有(3+4)位小数,但是C的精度是4,所以需要把多余的小数移出去
移出去的位数就是(3+4-4) = 3
所以C = 1111.0010 fix_point = 4

自定义卷积

特征的内存排布方式

image-20221104213524620

权重的内存排布方式

image-20221104213622677

卷积的大小不固定,需要根据在内存中的排布方式算出地址

新建conv_core项目

conv_core.h

#ifndef __CONV_CORE_H__
#define __CONV_CORE_H__

#include <ap_int.h>
#include <iostream>
using namespace std;

#define K 8

typedef ap_int<16> data_type;	// 单个数据的大小
typedef ap_int<16*K> tile_type;	// 分块数据的大小

typedef ap_int<32> mul_type;	// 两个数据相乘的数据大小:16*16==>32
typedef ap_int<32*K> mul_tile_type; // 分块数据相乘的大小

typedef ap_int<40> acc_type;  // 一次卷积内的数据相加后的大小,按照经验推断

// 卷积的定义
void conv_core(
		ap_uint<16> in_channel, // 输入特征的通道数
		ap_uint<16> in_height, // 输入特征高度
		ap_uint<16> in_width, // 输入特征宽度
		ap_uint<16> out_channel, // 输出特征通道数

		// 卷积核参数
		ap_uint<8> kernel_width, // 卷积核宽度
		ap_uint<8> kernel_height, // 卷积核高度

		ap_uint<8> stride_x, // 宽度方向步长
		ap_uint<8> stride_y, // 高度方向步长

		ap_uint<1> padding_mode, // 卷积的模式; 0: valid(没有padding填充), 1:same(输入输出的图大小不变)

		ap_uint<1> relu_en, // 激活函数

		tile_type input_feature[], ap_uint<4> input_feature_precision,	// 输入特征图地址和精度(小数点位置)
		tile_type weight[], ap_uint<4> weight_precision,// 权重地址和精度(小数点位置)
		tile_type output_feature[], ap_uint<4> out_feature_precision// 输出特征图地址和精度(小数点位置)
);

#endif

conv_core.cpp

#include "conv_core.h"

void conv_core(
		// 特征图参数
		ap_uint<16> in_channel, // 输入特征的通道数
		ap_uint<16> in_height, // 输入特征高度
		ap_uint<16> in_width, // 输入特征宽度
		ap_uint<16> out_channel, // 输出特征通道数

		// 卷积核参数
		ap_uint<8> kernel_width, // 卷积核宽度
		ap_uint<8> kernel_height, // 卷积核高度

		ap_uint<8> stride_x, // 宽度方向步长
		ap_uint<8> stride_y, // 高度方向步长

		ap_uint<1> padding_mode, // 卷积的模式; 0: valid(没有padding填充), 1:same(输入输出的图大小不变)

		ap_uint<1> relu_en, // 激活函数

		tile_type input_feature[], ap_uint<4> input_feature_precision,	// 输入特征图地址和精度(小数点位置)
		tile_type weight[], ap_uint<4> weight_precision,// 权重地址和精度(小数点位置)
		tile_type output_feature[], ap_uint<4> out_feature_precision// 输出特征图地址和精度(小数点位置)
)
{
	// Feature: [CHin/K][H][W][K]
	// Weight: [CHout][CHin/K][KH][KW][K]

	// 根据卷积模式,计算padding
	ap_uint<8> padding_x, padding_y;
	if(padding_mode == 0){
		padding_x = padding_y = 0;
	}else{
		padding_x = (kernel_width-1)/2;
		padding_y = (kernel_height-1)/2;
	}
	// 计算分块个数
	ap_uint<16> div_tile_num = (in_channel + K-1) / K;
	// 计算输出截断精度
	ap_uint<5> out_truncate = input_feature_precision + weight_precision - out_feature_precision;
	/*
	 * [x x x] x x x
	 * x x [x x x] x
	 */
	// 计算输出宽度和高度
	ap_uint<16> out_width = (in_width + padding_x*2) / stride_x + 1;
	ap_uint<16> out_height = (in_height + padding_y*2) / stride_y + 1;

	// 选择输出特征的第y行,第x列,第c_out个输出通道的数据
	// 选择第c_out个权重的第y行,第x列,第tile_index个分块

	LOOP_out_channel:
	for(int out_index = 0; out_index < out_channel; ++ out_index){
		LOOP_out_height:
		for(int i = 0; i < out_height; ++ i){
			LOOP_out_width:
			for(int j = 0; j < out_width; ++ j){
				// 相乘结果累加
				acc_type sum=0;
				// 计算输出特征中一个tile的数据
				ap_int<16> out_tile = 0;
				LOOP_kernel_height:
				for(int kh = 0; kh < kernel_height; ++ kh){
					LOOP_kernel_width:
					for(int kw = 0; kw < kernel_width; ++ kw){
						LOOP_div_tile_num:
						for(int tile_index = 0; tile_index < div_tile_num; ++ tile_index){

							// 获取计算点
							ap_uint<16> in_h = i*stride_y-padding_y + kh;
							ap_uint<16> in_w = j*stride_x-padding_x + kw;

							// 获取输入特征和权重的一个块数据
							tile_type data_tile, weight_tile;
							// 有padding会越界
							if(in_h >= 0 && in_h < in_height && in_w >= 0 && in_w < in_width){
								data_tile = input_feature[in_width*in_height*tile_index + in_width*in_h + in_w];
								weight_tile = weight[kernel_width*kernel_height*div_tile_num*out_index
													 + kernel_width*kernel_height*tile_index
													 + kernel_width*kh+kw];
							}else{
								data_tile = 0; weight_tile = 0;
							}
							// 块数据相乘
							mul_tile_type mul_tile_data;
							for(int k = 0; k < K; ++ k)
								mul_tile_data.range(k*32+31, k*32) =
										(data_type)data_tile.range(k*16+15, k*16)*
										(data_type)weight_tile.range(k*16+15, k*16);
							// 相乘结果累加
							for(int k = 0; k < K; ++ k)
								sum += (mul_tile_type)mul_tile_data.range(k*32+31, k*32);
						}
					}
				}

				// 激活函数
				if(relu_en & sum < 0) sum = 0;
				// 截断多余精度
				acc_type res = sum >> out_truncate;

				if (res > 32767)
					res = 32767;
				else if (res < -32768)
					res = -32768;
				out_tile.range((out_index % K) * 16 + 15, (out_index % K) * 16) = res;
				// 存tile里的一个数据
				// 一个tile都存完了或者存到最后一个通道凑不够一个tile
				if((out_index%K) == K - 1 || out_index == (out_channel - 1))
					output_feature[(out_index/K)*out_width*out_height + out_width*i+j] = out_tile;
			}
		}
	}
}

main.cpp

#include "stdio.h"
#include "conv_core.h"

#define IN_WIDTH 10
#define IN_HEIGHT 10
#define IN_CHANNEL 1
#define IN_DIV_TILE_NUM	((IN_CHANNEL+K-1)/K)

#define KERNEL_WIDTH 5
#define KERNEL_HEIGHT 5
#define STRIDE_X 1
#define STRIDE_Y 1

#define RELU_EN 0
#define PADDING_MODE 0 // 0:valid 1:same
#define PADDING_X (PADDING_MODE?(KERNEL_WIDTH-1)/2:0)
#define PADDING_Y (PADDING_MODE?(KERNEL_HEIGHT-1)/2:0)

#define OUT_CHANNEL 1
#define OUT_WIDTH ((IN_WIDTH+PADDING_X*2-KERNEL_WIDTH)/STRIDE_X+1)
#define OUT_HEIGHT ((IN_HEIGHT+PADDING_Y*2-KERNEL_HEIGHT)/STRIDE_Y+1)
#define OUT_DIV_TILE_NUM ((OUT_CHANNEL+K-1)/K)

int main(void)
{
	tile_type input_feature[IN_DIV_TILE_NUM][IN_HEIGHT][IN_WIDTH];
	tile_type weight[OUT_CHANNEL][IN_DIV_TILE_NUM][KERNEL_HEIGHT][KERNEL_WIDTH];
	tile_type output_feature[OUT_DIV_TILE_NUM][OUT_HEIGHT][OUT_WIDTH];

	for(int tile_index = 0; tile_index < IN_DIV_TILE_NUM; ++ tile_index){
		for(int i = 0; i < IN_HEIGHT; ++ i){
			for(int j = 0; j < IN_WIDTH; ++ j){
				for(int k = 0; k < K; ++ k){
					// 可能除不尽
					if(tile_index*K+k < IN_CHANNEL)
						input_feature[tile_index][i][j].range(k*16+15, k*16) = (1<<14);
					else
						input_feature[tile_index][i][j].range(k*16+15, k*16) = 0;
				}
			}
		}
	}

	for(int out_index = 0; out_index < OUT_CHANNEL; ++ out_index){
		for(int tile_index = 0; tile_index < OUT_DIV_TILE_NUM; ++ tile_index){
			for(int i = 0; i < OUT_HEIGHT; ++ i){
				for(int j = 0; j < OUT_WIDTH; ++j){
					for(int k = 0; k < K; ++ k){
						// 输入特征赋值为0,特征值就不用考虑除不尽的问题
						weight[out_index][tile_index][i][j].range(16*k+15, 16*k) = (1<<14);
					}
				}
			}
		}
	}

	for(int tile_index = 0; tile_index < OUT_DIV_TILE_NUM; ++ tile_index){
		for(int i = 0; i < OUT_HEIGHT; ++ i){
			for(int j = 0; j < OUT_WIDTH; ++ j){
				output_feature[tile_index][i][j] = 0;
			}
		}
	}

	printf("initial down\n");

	conv_core(IN_CHANNEL, IN_HEIGHT, IN_WIDTH, OUT_CHANNEL,
			KERNEL_WIDTH, KERNEL_HEIGHT,
			STRIDE_X, STRIDE_Y,
			PADDING_MODE, RELU_EN,
			&input_feature[0][0][0], 14,
			&weight[0][0][0][0], 14,
			&output_feature[0][0][0], 10
	);

	for(int tile_index = 0; tile_index < OUT_DIV_TILE_NUM; ++ tile_index){
		for(int i = 0; i < OUT_HEIGHT; ++ i){
			for(int j = 0; j < OUT_WIDTH; ++ j){
				cout << "out[" << tile_index <<"]["<<i<<"]["<<j<<"]="<<
				(data_type)output_feature[tile_index][i][j].range(15, 0) << "\t";
			}
			cout << endl;
		}
	}


	return 0;
}

运行

C仿真结果:

image-20221105183939657

C综合:

image-20221105184415141

出错了,需要加约束。报错信息:输入特征feature_in是没有固定长度的

但是我们只是把input_feature当作基地址,而不是数组,所以需要告诉工具,数据来自外部存储器

数据接口约束

image-20221105185249772

image-20221105185314764

#pragma HLS INTERFACE m_axi depth=999999999 port=weight offset=slave
#pragma HLS INTERFACE m_axi depth=999999999 port=output_feature offset=slave
#pragma HLS INTERFACE m_axi depth=999999999 port=input_feature offset=slave

再次C综合:

image-20221105185415418

???是因为,循环次数是一个变量,工具无法估计性能

循环次数约束

那就根据test bench里的例子进行测试约束

image-20221105190053192

#include "conv_core.h"

void conv_core(
		// 特征图参数
		ap_uint<16> in_channel, // 输入特征的通道数
		ap_uint<16> in_height, // 输入特征高度
		ap_uint<16> in_width, // 输入特征宽度
		ap_uint<16> out_channel, // 输出特征通道数

		// 卷积核参数
		ap_uint<8> kernel_width, // 卷积核宽度
		ap_uint<8> kernel_height, // 卷积核高度

		ap_uint<8> stride_x, // 宽度方向步长
		ap_uint<8> stride_y, // 高度方向步长

		ap_uint<1> padding_mode, // 卷积的模式; 0: valid(没有padding填充), 1:same(输入输出的图大小不变)

		ap_uint<1> relu_en, // 激活函数

		tile_type input_feature[], ap_uint<4> input_feature_precision,	// 输入特征图地址和精度(小数点位置)
		tile_type weight[], ap_uint<4> weight_precision,// 权重地址和精度(小数点位置)
		tile_type output_feature[], ap_uint<4> out_feature_precision// 输出特征图地址和精度(小数点位置)
)
{
	#pragma HLS INTERFACE m_axi depth=999999999 port=weight offset=slave
	#pragma HLS INTERFACE m_axi depth=999999999 port=output_feature offset=slave
	#pragma HLS INTERFACE m_axi depth=999999999 port=input_feature offset=slave
	// Feature: [CHin/K][H][W][K]
	// Weight: [CHout][CHin/K][KH][KW][K]

	// 根据卷积模式,计算padding
	ap_uint<8> padding_x, padding_y;
	if(padding_mode == 0){
		padding_x = padding_y = 0;
	}else{
		padding_x = (kernel_width-1)/2;
		padding_y = (kernel_height-1)/2;
	}
	// 计算分块个数
	ap_uint<16> div_tile_num = (in_channel + K-1) / K;
	// 计算输出截断精度
	ap_uint<5> out_truncate = input_feature_precision + weight_precision - out_feature_precision;
	/*
	 * [x x x] x x x
	 * x x [x x x] x
	 */
	// 计算输出宽度和高度
	ap_uint<16> out_width = (in_width + padding_x*2) / stride_x + 1;
	ap_uint<16> out_height = (in_height + padding_y*2) / stride_y + 1;

	// 选择输出特征的第y行,第x列,第c_out个输出通道的数据
	// 选择第c_out个权重的第y行,第x列,第tile_index个分块

	LOOP_out_channel:
	for(int out_index = 0; out_index < out_channel; ++ out_index){
#pragma HLS LOOP_TRIPCOUNT min=1 max=1 avg=1
		LOOP_out_height:
		for(int i = 0; i < out_height; ++ i){
#pragma HLS LOOP_TRIPCOUNT min=10 max=10 avg=10
			LOOP_out_width:
			for(int j = 0; j < out_width; ++ j){
#pragma HLS LOOP_TRIPCOUNT min=10 max=10 avg=10
				// 相乘结果累加
				acc_type sum=0;
				// 计算输出特征中一个tile的数据
				ap_int<16> out_tile = 0;
				LOOP_kernel_height:
				for(int kh = 0; kh < kernel_height; ++ kh){
#pragma HLS LOOP_TRIPCOUNT min=5 max=5 avg=5
					LOOP_kernel_width:
					for(int kw = 0; kw < kernel_width; ++ kw){
#pragma HLS LOOP_TRIPCOUNT min=5 max=5 avg=5
						LOOP_div_tile_num:
						for(int tile_index = 0; tile_index < div_tile_num; ++ tile_index){
#pragma HLS LOOP_TRIPCOUNT min=1 max=1 avg=1
							// 获取计算点
							ap_uint<16> in_h = i*stride_y-padding_y + kh;
							ap_uint<16> in_w = j*stride_x-padding_x + kw;

							// 获取输入特征和权重的一个块数据
							tile_type data_tile, weight_tile;
							// 有padding会越界
							if(in_h >= 0 && in_h < in_height && in_w >= 0 && in_w < in_width){
								data_tile = input_feature[in_width*in_height*tile_index + in_width*in_h + in_w];
								weight_tile = weight[kernel_width*kernel_height*div_tile_num*out_index
													 + kernel_width*kernel_height*tile_index
													 + kernel_width*kh+kw];
							}else{
								data_tile = 0; weight_tile = 0;
							}
							// 块数据相乘
							mul_tile_type mul_tile_data;
							for(int k = 0; k < K; ++ k)
								mul_tile_data.range(k*32+31, k*32) =
										(data_type)data_tile.range(k*16+15, k*16)*
										(data_type)weight_tile.range(k*16+15, k*16);
							// 相乘结果累加
							for(int k = 0; k < K; ++ k)
								sum += (mul_tile_type)mul_tile_data.range(k*32+31, k*32);
						}
					}
				}

				// 激活函数
				if(relu_en & sum < 0) sum = 0;
				// 截断多余精度
				acc_type res = sum >> out_truncate;

				if (res > 32767)
					res = 32767;
				else if (res < -32768)
					res = -32768;
				out_tile.range((out_index % K) * 16 + 15, (out_index % K) * 16) = res;
				// 存tile里的一个数据
				// 一个tile都存完了或者存到最后一个通道凑不够一个tile
				if((out_index%K) == K - 1 || out_index == (out_channel - 1))
					output_feature[(out_index/K)*out_width*out_height + out_width*i+j] = out_tile;
			}
		}
	}
}

综合结果:

image-20221105190949870

优化

最内层的块相乘只用两个周期完成

一个周期用来去input_tile,另一个取weight_tile,输出计算结果的同时,也在取数据

image-20221105191912130

添加 pipeline 优化

迭代间隔 II = 2

image-20221105192124549

如果效果理想的话应该是 1*10*10*5*5*2=5000

但是综合结果并不是:

image-20221105193101366

卷积一次:2*25 = 50

这里循环的latency是59,多了9个周期是迭代latency

但是顶层的迭代latency达到了70,其中多的11周期的计算在这里:

image-20221105194344619

完美循环优化与计算顺序的调换

  • 完美循环

使用if语句使循环之间没有代码,使得循环可以合并

从存储器取块数据相乘累加的运算,其实可以和计算结果的存储并行

image-20221105201529553

  • 计算顺序调换

将out_channel挪到kernel_width上面,这样每计算完一次卷积tile,就判断一次对输出特征写计算结果

#include "conv_core.h"

void conv_core(
		// 特征图参数
		ap_uint<16> in_channel, // 输入特征的通道数
		ap_uint<16> in_height, // 输入特征高度
		ap_uint<16> in_width, // 输入特征宽度
		ap_uint<16> out_channel, // 输出特征通道数

		// 卷积核参数
		ap_uint<8> kernel_width, // 卷积核宽度
		ap_uint<8> kernel_height, // 卷积核高度

		ap_uint<8> stride_x, // 宽度方向步长
		ap_uint<8> stride_y, // 高度方向步长

		ap_uint<1> padding_mode, // 卷积的模式; 0: valid(没有padding填充), 1:same(输入输出的图大小不变)

		ap_uint<1> relu_en, // 激活函数

		tile_type input_feature[], ap_uint<4> input_feature_precision,	// 输入特征图地址和精度(小数点位置)
		tile_type weight[], ap_uint<4> weight_precision,// 权重地址和精度(小数点位置)
		tile_type output_feature[], ap_uint<4> out_feature_precision// 输出特征图地址和精度(小数点位置)
)
{
	#pragma HLS INTERFACE m_axi depth=999999999 port=weight offset=slave
	#pragma HLS INTERFACE m_axi depth=999999999 port=output_feature offset=slave
	#pragma HLS INTERFACE m_axi depth=999999999 port=input_feature offset=slave
	// Feature: [CHin/K][H][W][K]
	// Weight: [CHout][CHin/K][KH][KW][K]

	// 根据卷积模式,计算padding
	ap_uint<8> padding_x, padding_y;
	if(padding_mode == 0){
		padding_x = padding_y = 0;
	}else{
		padding_x = (kernel_width-1)/2;
		padding_y = (kernel_height-1)/2;
	}
	// 计算分块个数
	ap_uint<16> div_tile_num = (in_channel + K-1) / K;
	// 计算输出截断精度
	ap_uint<5> out_truncate = input_feature_precision + weight_precision - out_feature_precision;
	/*
	 * [x x x] x x x
	 * x x [x x x] x
	 */
	// 计算输出宽度和高度
	ap_uint<16> out_width = (in_width + padding_x*2) / stride_x + 1;
	ap_uint<16> out_height = (in_height + padding_y*2) / stride_y + 1;

	// 计算输出特征中一个tile的数据
	ap_int<16> out_tile = 0;
	// 相乘结果累加
	acc_type sum=0;

	// 选择输出特征的第y行,第x列,第c_out个输出通道的数据
	// 选择第c_out个权重的第y行,第x列,第tile_index个分块


	LOOP_out_height:
	for(int i = 0; i < out_height; ++ i){
#pragma HLS LOOP_TRIPCOUNT min=1 max=1 avg=1
		LOOP_out_width:
		for(int j = 0; j < out_width; ++ j){
#pragma HLS LOOP_TRIPCOUNT min=10 max=10 avg=10
			LOOP_out_channel:
			for(int out_index = 0; out_index < out_channel; ++ out_index){
#pragma HLS LOOP_TRIPCOUNT min=10 max=10 avg=10

				LOOP_kernel_height:
				for(int kh = 0; kh < kernel_height; ++ kh){
#pragma HLS LOOP_TRIPCOUNT min=5 max=5 avg=5
					LOOP_kernel_width:
					for(int kw = 0; kw < kernel_width; ++ kw){
#pragma HLS LOOP_TRIPCOUNT min=5 max=5 avg=5
						LOOP_div_tile_num:
						for(int tile_index = 0; tile_index < div_tile_num; ++ tile_index){
#pragma HLS PIPELINE II=2
#pragma HLS LOOP_TRIPCOUNT min=1 max=1 avg=1
							// 获取计算点
							ap_uint<16> in_h = i*stride_y-padding_y + kh;
							ap_uint<16> in_w = j*stride_x-padding_x + kw;

							// 获取输入特征和权重的一个块数据
							tile_type data_tile, weight_tile;
							// 有padding会越界
							if(in_h >= 0 && in_h < in_height && in_w >= 0 && in_w < in_width){
								data_tile = input_feature[in_width*in_height*tile_index + in_width*in_h + in_w];
								weight_tile = weight[kernel_width*kernel_height*div_tile_num*out_index
													 + kernel_width*kernel_height*tile_index
													 + kernel_width*kh+kw];
							}else{
								data_tile = 0; weight_tile = 0;
							}
							// 块数据相乘
							mul_tile_type mul_tile_data;
							for(int k = 0; k < K; ++ k)
								mul_tile_data.range(k*32+31, k*32) =
										(data_type)data_tile.range(k*16+15, k*16)*
										(data_type)weight_tile.range(k*16+15, k*16);
							// 相乘结果累加
							for(int k = 0; k < K; ++ k)
								sum += (mul_tile_type)mul_tile_data.range(k*32+31, k*32);

							if(tile_index == div_tile_num-1 && kh == kernel_height-1 && kw == kernel_width-1){
								// 激活函数
								if(relu_en & sum < 0) sum = 0;
								// 截断多余精度
								acc_type res = sum >> out_truncate;

								if (res > 32767)
									res = 32767;
								else if (res < -32768)
									res = -32768;

								// 先缓存下来,下面一次写入
								out_tile.range((out_index % K) * 16 + 15, (out_index % K) * 16) = res;
								sum = 0;
								// 存tile里的一个数据
								// 一个tile都存完了或者存到最后一个通道凑不够一个tile
								if((out_index%K) == K - 1 || out_index == (out_channel - 1)){
									output_feature[(out_index/K)*out_width*out_height + out_width*i+j] = out_tile;
									out_tile = 0;
								}
							}
						}
					}
				}


			}
		}
	}
}

C综合已经达到我们的预期了

image-20221105201042737

访问方式的优化-通道方向的并行

给特征图和权重分别分配一条总线,那么就可以同时取出两个数据,只需要一个周期就可以完成一次tile的计算

bundle:一捆,不选的话默认是同一个总线

所以这里我们给input_feature和weight取两个不同的名字,就给它们分配了不同的总线:

image-20221105202209564

image-20221105202520163

AXI是全双工的,可以同时写和读,但是同时读两个就不太行,读一个的同时写一个可以。所以不需要给output_feature分配一个总线

然后我们就可以在一个周期内运行一次tile计算,设置pipeline的II = 1:

image-20221105203023536

也就是K个数据的乘法和累加,需要K个乘法器同时进行

image-20221105203224253

可以看出来是2500个周期:1*10*10*5*5*(1 clock)=2500

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring mvc处理异常

文章目录一、Handler ExceptionResolver处理异常二、ExceptionHandler注解三、重点&#xff1a;添加ExceptionHandler注解方法的形参只能是异常类型四、重点2&#xff1a;捕获所有方法的异常—ControllerAdvice注解五、总结六、ResponseStatusExceptionResolve自定义异常显示页…

[go学习笔记.第十一章.项目案例] 1.家庭收支记账软件项目

一.基本介绍 1.项目开发流程说明 2.项目需求说明 目标: 模拟实现一个基于文本界面的<<家庭记账软件>> 掌握初步的编程技巧和调试技巧 主要涉及以下知识点 : (1).局部变量和基本数据类型 (2).循环语句 (3).分支语句 (4).简单的屏幕输出格式控制 (5).进阶&#xff1…

刷题日记【第九篇】-笔试必刷题【杨辉三角的变形+计算某字符出现的次数+字符串通配符+统计每个月兔子的总数】

下列sql语句中哪条语句可为用户zhangsan分配数据库userdb表userinfo的查询和插入数据权限&#xff08;A&#xff09;。 常用的管理权限的命令为&#xff1a; grant select/insert/update/delete on 数据库名.表名 to 用户名‘该用户允许访问的ip’ 在oracle中&#xff0c;下面哪…

世界上只有一种共识算法,那就是Paxos

分布式系统原理系列目录 分布式系统的麻烦副本与一致性为什么需要一个分布式共识算法世界上只有一种共识算法&#xff0c;那就是PaxosCAP定理&#xff0c;说起来一句话&#xff0c;实际坑不少BASE&#xff0c;可用性高于强一致性分布式事务方案那么多&#xff0c;到底该选哪一…

计算机毕业设计(附源码)python智慧灭火器管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

大数据学习3.1 Hadoop环境准备

Hadoop环境Hadoop集群拓扑1、集群拓扑2、角色分配一、虚拟机安装二、虚拟机克隆1、克隆类型&#xff08;1&#xff09;完整克隆&#xff08;2&#xff09;链接克隆2、克隆步骤&#xff08;1&#xff09;克隆出master虚拟机&#xff08;2&#xff09;克隆出slave1虚拟机&#xf…

深入理解Linux文件描述符

>> Linux基础IO系列文章 1. Linux文件操作系统接口的学习使用 一、前言 在上一篇博客中&#xff0c;我们初步学习了Linux文件操作的系统接口&#xff0c;不难发现的是&#xff0c;这些系统函数都与文件描述符密切相关&#xff1a;open函数返回值是一个文件描述符&#…

Python编程挑战赛

题1&#xff1a;给小朋友分糖&#xff0c;每人分到糖的数量不同&#xff0c;输入小朋友的数量&#xff0c;计算至少需要多少糖&#xff1f; 思路&#xff1a;第1个小朋友1颗糖&#xff0c;第2个小朋友2颗糖&#xff0c;第3个小朋友3颗糖&#xff0c;……第n个小朋友n颗糖&#…

[数据结构]实现双向链表

作者&#xff1a; 华丞臧. 专栏&#xff1a;【数据结构】 各位读者老爷如果觉得博主写的不错&#xff0c;请诸位多多支持(点赞收藏关注)。如果有错误的地方&#xff0c;欢迎在评论区指出。 文章目录一、带头双向循环链表二、带头双向循环链表接口实现2.1 双向链表的初始化、打…

动态规划算法的题到底应该怎么做?思路教给你自己写

本文是我通过做题和借鉴一些前人总结的套路而得出的思路和方法&#xff0c;刚好这次CSDN第八周的周赛上出了三道动态规划的题目&#xff0c;我会结合题目对我的思路进行一个输出&#xff0c;会从最简单的一维dp开始讲解到二维dp&#xff0c;希望对你有帮助&#xff0c;有错误希…

MySQL数据库基础知识

今天是更新数据库的第一篇&#xff0c;关于数据库环境搭建问题博主先不在这里介绍了&#xff0c;博主今天是直接讲知识了&#xff0c;等以后的博客&#xff0c;博主再更新数据库搭建问题。在这里我们使用命令行式客户端&#xff0c;先不使用windows下的图形化界面&#xff0c;使…

ReadingTime-十一月

CV文章浅读_not_everday0x1105.CAViT for video object re-id 2022_中科院这个月主要是要学习pytorch和一些CV baseline的复现&#xff0c;搞搞毕设雏形&#x1f199; 以后还是把笔记写纸上要么写博客&#xff0c;不放本地了&#x1f628; 网页版小绿鲸zen好用 &#x1f603; …

峰会实录 | 基于StarRocks和腾讯云EMR构建云上Lakehouse

作者&#xff1a;腾讯云EMR业务负责人陈龙&#xff08;本文为作者在 StarRocks Summit Asia 2022 上的分享&#xff09; 我目前负责腾讯云EMR 的研发工作&#xff0c;此前先后在百度、支付宝做后端研发。2011年加入腾讯&#xff0c;先后参与了腾讯云Redis、腾讯云云数据库、Ap…

小米 Civi 2 (ziyi) 机型解锁bl 获取root权限教程 +其他机型参数对比+救砖

*********机型优点与其他机型参数对比***************** 小米 Civi 2 (ziyi) 国行版机型前置由3200万主摄3200万超广角组成的双摄是它最大的亮点&#xff0c;配有4颗柔光灯。自拍相当不错。他的后置主摄采用5000万像素相机&#xff0c;IMX766传感器&#xff0c;1/1.56英寸感光…

【MybatisPlus】CRUD操作,映射匹配兼容性,ID生成策略,逻辑删除,乐观锁

文章目录MybatisPlus简介一、数据层基本的开发1. 引入jar包2. 配置数据源3. 编写实体类4. 创建Dao接口5. 测试二、CRUD使用1. 查询2. 添加3. 删除4. 修改5. 分页查询三、条件查询1. 条件查询的方式2. 多条件查询四、映射匹配兼容性1. 表字段与编码属性设计不同步2. 编码中添加了…

矩阵理论复习(二)

内积空间的定义 模与内积 向量x和y的夹角 正交向量、正交组和正交矩阵 度量矩阵 基向量内积、度量矩阵、任意向量内积之间的关系 欧式空间的两个基对应的度量矩阵彼此合同 度量矩阵的行列式的几何问题 正交补子空间 内积空间子空间U与U的正交补子空间的直和 …

LeetCode 138. 复制带随机指针的链表

难度 中等 题目链接 示例&#xff1a; 解题思路&#xff1a; 首先&#xff0c;大家肯定会这样想&#xff1a;定义一个cur循环遍历&#xff0c;每次遍历一个&#xff0c;就malloc一个。 当遍历后面的时候&#xff0c;就开始尾插。 但现在有一个问题是&#xff1a;这个random指…

通信直放站基础知识

直放站的定义 直放站&#xff08;中继器&#xff09;属于同频放大设备&#xff0c;是指在无线通信传输过程中起到信号增强的一种无线电发射中转设备。直放站的基本功能就是一个射频信号功率增强器。直放站在下行链路中&#xff0c;由施主天线从现有的覆盖区域中拾取信号&#x…

《嵌入式 - 嵌入式大杂烩》CoreMark性能测试

1 CoreMark简介 CoreMark是由EEMBC(Embedded Microprocessor Benchmark Consortium)的Shay Gla-On于2009年提出的一项基准测试程序&#xff0c;CoreMark的主要目标是简化操作&#xff0c;并提供一套测试单核处理器核心的方法。测试标准是在配置参数的组合下单位时间内运行的Co…

【树莓派不吃灰系列】快速导航

目录1. 通用篇2. Python篇3. 编程IO篇❤️ 博客主页 单片机菜鸟哥&#xff0c;一个野生非专业硬件IOT爱好者 ❤️❤️ 本篇创建记录 2022-11-06 ❤️❤️ 本篇更新记录 2022-11-06 ❤️&#x1f389; 欢迎关注 &#x1f50e;点赞 &#x1f44d;收藏 ⭐️留言 &#x1f4dd;&…