티스토리 뷰

Tech-Tip

행렬곱 - Xilinx HLS 실험 1

eulia1211 2023. 2. 22. 12:47

 

행렬곱 - Xilinx HLS 실험

 

다음은 가장 simple한 행렬곱 버전인 matmul_1이다. 현재 MATSIZE 값은 8이다.

void matmul_1(hls::stream<axis_data> &in_A, hls::stream<axis_data> &out_C)
{
	#pragma HLS INTERFACE mode=axis register_mode=both port=in_A register
	#pragma HLS INTERFACE mode=axis register_mode=both port=out_C  register
	#pragma HLS INTERFACE mode=ap_ctrl_none port=return

	Mat_Dtype input_A[MATSIZE][MATSIZE];
	Mat_Dtype input_B[MATSIZE][MATSIZE];
	Mat_Dtype output_C[MATSIZE][MATSIZE];

	int row, col, index;
	Mat_Dtype res;
	axis_data local_stream;
	converter_ converter;

	// saving streaming data to respective variables
	loop_input_A1: for(row=0; row<MATSIZE; row++)
	{
		loop_input_A2: for(col=0; col<MATSIZE; col++)
		{
			local_stream = in_A.read();
			converter.intval = local_stream.data;
			input_A[row][col] = (float) converter.fpval;
		}
	}

	loop_input_B1: for(row=0; row<MATSIZE; row++)
	{
		loop_input_B2: for(col=0; col<MATSIZE; col++)
		{
			local_stream = in_A.read();
			converter.intval = local_stream.data;
			input_B[row][col] = (float) converter.fpval;
		}
	}

	// MATRIX MULTIPLICATION
	loop1: for(row=0; row<MATSIZE; row++)
		loop2: for(col=0; col<MATSIZE; col++)
		{
			res = 0;
			loop3: for(index=0; index<MATSIZE; index++)
				res += input_A[row][index]*input_B[index][col];

			output_C[row][col] = res;
		}

	// stream output data back
	loop_output_C1: for(row=0; row<MATSIZE; row++)
		loop_output_C2: for(col=0; col<MATSIZE; col++)
		{
			converter.fpval = output_C[row][col];
			local_stream.data = converter.intval;

			// generating the last signal and strobe signal
			if((row==MATSIZE-1) && (col==MATSIZE-1))
				local_stream.last = 1;
			else
				local_stream.last = 0;

			out_C.write(local_stream);
		}

}

가장 심플한 버전의 matmul.cpp를 합성했을때의 결과

matmul_1의 합성 결과
Resource Bing: input은 ROM으로 출력은 RAM을 사용된것을 알 수 있다.

 

loop_input_A1 & loop_input_A2의 Latency Cycles은 66인데, 이는 초기 진입 Latency 2에서 8*8 (64)을 더 합 값이라고 볼수 있다.

loop_input_A1 & loop_input_A2 의 스케줄 그래프

두번째 loop_input_B1 & loop_input_B2의 경우도 마찬가지 이유로 66이다.

 

세번째 실제 연산을 수행하는 loop1_loop2는 111의 clock cycle이 사용된다. 47+64 = 111 인데...

이말의 의미는 다음 loop3가 처음 iteration 이후부터 8개의 곱셈과 8개의 덧셈이 파이프라인이 된다는 뜻?

      loop3: for(index=0; index<MATSIZE; index++)
                    res += input_A[row][index]*input_B[index][col];

 

loop1_loop2의 스케줄 그래프

 

loop_output_C1_loop_output_C2의 latency는 67인데 operator scheduling graph를 보면 integer to float 변환이 시간이 오래 걸리는 것으로 나온다. 특이 한점은 input을 받는 loop에서는 float to integer 변환이 일어나는데 이때는 이와 같은 시간이 필요하지 않았다.

output loop의 scheduled graph

 

 

다시 matmul code로 돌아가서 행렬 곱 code에 #pragma PIPELINE을 추가하는 경우 실험을 해보자.

	// MATRIX MULTIPLICATION
	loop1: for(row=0; row<MATSIZE; row++)
		loop2: for(col=0; col<MATSIZE; col++)
		{
			res = 0;
			#pragma HLS PIPELINE
			loop3: for(index=0; index<MATSIZE; index++)
				res += input_A[row][index]*input_B[index][col];

			output_C[row][col] = res;
		}

이경우는 최적화 옵션을 넣지 않은 경우와 같았다.

 

다음 pragmas의 위치를 세번째 loop의 안으로 넣어보았다.

	// MATRIX MULTIPLICATION
	loop1: for(row=0; row<MATSIZE; row++)
		loop2: for(col=0; col<MATSIZE; col++)
		{
			res = 0;
			loop3: for(index=0; index<MATSIZE; index++)
				#pragma HLS PIPELINE
				res += input_A[row][index]*input_B[index][col];

			output_C[row][col] = res;
		}

 

이번에는 pragmas의 위치를 loop2 들어가기전에 위치해봤다.

	// MATRIX MULTIPLICATION
	loop1: for(row=0; row<MATSIZE; row++)
		#pragma HLS PIPELINE
		loop2: for(col=0; col<MATSIZE; col++)
		{
			res = 0;
			loop3: for(index=0; index<MATSIZE; index++)
				res += input_A[row][index]*input_B[index][col];

			output_C[row][col] = res;
		}

이경우, 하드웨어 리소스를 크게 늘어나지만, 행렬곱의 latency가 55로 줄어드는 것을 볼수 있다.

하지만, 위 solution은 DSP를 320개나 사용하기 때문에, 현재 target device인 Zybo Z7-20가 가지고 있는 220개를 넘어 Bitstream 생성 시에 에러가 발생한다.

 

Zybo 보드의 Resource 현황

 

 

일단 다음 버전의 행렬곱 가속기 ip의 성능을 체크하기 위해서 vivado 툴을 이용하여 시스템을 구성한다.

Vivado tool을 이용한 시스템 통합

 

이후, Vitis IDE를 통하여 성능 테스트를 진행하였다. 이때 Application code는 다음과 같다.

#include <stdio.h>
#include <stdlib.h>
#include "xaxidma.h"
#include "xparameters.h"
#include "platform.h"
#include <xtime_l.h>  // Timer for execution time calculations
#include "xil_printf.h"

#define EXPT 5
#define Mat_Dtype float
#define MATSIZE 8


void matrixmul_benchmark(Mat_Dtype input_A[MATSIZE][MATSIZE], Mat_Dtype input_B[MATSIZE][MATSIZE], Mat_Dtype output_C[MATSIZE][MATSIZE]);

unsigned int float_to_u32(float val)
{
	unsigned int result;
	union float_bytes {
		float v;
		unsigned char bytes[4];
	} data;

	data.v = val;

	result = (data.bytes[3] << 24) + (data.bytes[2] << 16) + (data.bytes[1] << 8) + (data.bytes[0]);

	return result;
}

unsigned int u32_to_float(unsigned int val)
{
	union {
		float val_float;
		unsigned char bytes[4];
	} data;

	data.bytes[3] = (val >> (8*3)) & 0xff;
	data.bytes[2] = (val >> (8*2)) & 0xff;
	data.bytes[1] = (val >> (8*1)) & 0xff;
	data.bytes[0] = (val >> (8*0)) & 0xff;

	return data.val_float;
}

int Matrixmul()
{
	int status;
	int row, col;
	float time_processor=0;
	float time_FPGA=0;
	float curr_time=0;

	Mat_Dtype input_A[MATSIZE][MATSIZE], input_B[MATSIZE][MATSIZE], output_C_SW[MATSIZE][MATSIZE];
	Mat_Dtype output_C_HW[MATSIZE][MATSIZE];
	Mat_Dtype DMA_output[MATSIZE*MATSIZE];
	Mat_Dtype DMA_input[2*MATSIZE*MATSIZE];
//	u32 DMA_output[MATSIZE*MATSIZE];
//	u32 DMA_input[2*MATSIZE*MATSIZE];


	////////////////////////////////////////////////////////////////////////////////////////////
	// ACP DMA 0 Initialization
	XAxiDma_Config *DMA_confptr0; // DMA configuration pointer
	XAxiDma AxiDMA0; // DMA instance pointer
	// Copy the DMA information (received from hardware in xparameters.h file)
	DMA_confptr0 = XAxiDma_LookupConfig(XPAR_AXIDMA_0_DEVICE_ID);
	status = XAxiDma_CfgInitialize(&AxiDMA0, DMA_confptr0);
	if(status != XST_SUCCESS)
	{
		printf("DMA 0 Init Failed\t\n");
		return XST_FAILURE;
	}
	////////////////////////////////////////////////////////////////////////////////////////////
	// Performance Evaluations Start
	for(int k=0; k<EXPT; k++)
	{
		XTime seed_value;
		XTime_GetTime(&seed_value);
		srand(seed_value);

		// /////////////////////////////////////////////////////////////////////////////////////
		// Generate test data
		float initData = 0.0;
		for(row=0; row<MATSIZE; row++)
		{
			for(col=0; col<MATSIZE; col++)
			{
//				input_A[row][col] = ((float) rand()/(RAND_MAX/5));
//				input_B[row][col] = ((float) rand()/(RAND_MAX/5));
				input_A[row][col] = ((float) initData);
				input_B[row][col] = ((float) initData);
				initData += 1.0;
			}
		}

		// to store the time at which certain processes starts and ends
		XTime time_PS_start, time_PS_end;
		XTime time_PL_start, time_PL_end;  // PL time calculations

		////////////////////////////////////////////////////////////////////////////////////////
		// Performance Evaluation for SW Matrix Multiplication
		XTime_SetTime(0);
		XTime_GetTime(&time_PS_start);
		// Call software benchmark function
		matrixmul_benchmark(input_A, input_B, output_C_SW);
		XTime_GetTime(&time_PS_end);  // Capture the timer value at the end
		curr_time = ((float) 1.0*(time_PS_end - time_PS_start) / (COUNTS_PER_SECOND/1000000));
		time_processor = time_processor + curr_time;
		printf("/**********************************\t\n");
		printf("Execution Time for PS in Micro-Seconds for %d iteration: %f\t\n", k, curr_time);


		////////////////////////////////////////////////////////////////////////////////////////
		// Generate DMA Input
		int index=0;
		for(row=0; row<MATSIZE; row++)
		{
			for(col=0; col<MATSIZE; col++)
			{
//				DMA_input[index] = float_to_u32(input_A[row][col]);
				DMA_input[index] = input_A[row][col];
				index = index+1;
			}
		}
//		printf("****** From A, DMA_input[0] = %f\n", DMA_input[0]);

		for(row=0; row<MATSIZE; row++)
		{
			for(col=0; col<MATSIZE; col++)
			{
//				DMA_input[index] = float_to_u32(input_B[row][col]);
				DMA_input[index] = input_B[row][col];
				index = index+1;
			}
		}
//		printf("****** From B, DMA_input[0] = %f\n", DMA_input[0]);

		////////////////////////////////////////////////////////////////////////////////////////
		// Matrix Multiplication on PL using DMA 0
		XTime_SetTime(0);
		XTime_GetTime(&time_PL_start);

		// Cache Flush
		Xil_DCacheFlushRange((UINTPTR)DMA_input, (sizeof(Mat_Dtype)*MATSIZE*MATSIZE*2));
		Xil_DCacheFlushRange((UINTPTR)DMA_output, (sizeof(Mat_Dtype)*MATSIZE*MATSIZE));

		status = XAxiDma_SimpleTransfer(&AxiDMA0, (UINTPTR)DMA_output, (sizeof(Mat_Dtype)*MATSIZE*MATSIZE), XAXIDMA_DEVICE_TO_DMA);
		if (status != XST_SUCCESS) {
			return XST_FAILURE;
		}
		status = XAxiDma_SimpleTransfer(&AxiDMA0, (UINTPTR)DMA_input, (sizeof(Mat_Dtype)*MATSIZE*MATSIZE*2), XAXIDMA_DMA_TO_DEVICE);
		if (status != XST_SUCCESS) {
			return XST_FAILURE;
		}

		//printf("DMA Transfer Started... !!!\n");

		//  We have only configure the DMA to perform these two transactions.
		// DMA might not have started the transactions.
		// -
		// (MM2S_DMASR (MM2S DMA Status Register – Offset 04h)
		// 04h  	MM2S_DMASR		MM2S DMA Status register
		// Bit 0: DMA Channel Halted. Indicates the run/stop state of the DMA channel.
		//		• 0 = DMA channel running.
		//		• 1 = DMA channel halted.
		// Bit 1: DMA Channel Idle. Indicates the state of AXI DMA operations.
		//		• 0 = Not Idle.
		//		• 1 = Idle.
		status = XAxiDma_ReadReg(XPAR_AXIDMA_0_BASEADDR, 0x04) & 0x00000002;
		while(status!=0x00000002) // while ( status != Idle )
		{
			status = XAxiDma_ReadReg(XPAR_AXIDMA_0_BASEADDR, 0x04) & 0x00000002;
		}
		//printf("MM2S DMA Transfer Completed... !!!\n");

		// --
		// S2MM_DMASR (S2MM DMA Status Register – Offset 34h)
		// 34h		S2MM_DMASR		S2MM DMA Status register
		status = XAxiDma_ReadReg(XPAR_AXIDMA_0_BASEADDR, 0x34) & 0x00000002;
		while(status!=0x00000002)
		{
			status = XAxiDma_ReadReg(XPAR_AXIDMA_0_BASEADDR, 0x34) & 0x00000002;
		}
		//printf("S2MM DMA Transfer Completed... !!!\n");

		XTime_GetTime(&time_PL_end);
		curr_time = ((float)1.0 * (time_PL_end - time_PL_start) / (COUNTS_PER_SECOND/1000000));
		time_FPGA = time_FPGA + curr_time;
		printf("Execution Time for Non-Optimized MMUL in PL in Micro-Seconds for %d iteration: %f\t\n", k, curr_time);

		index = 0;
		for(row=0; row<MATSIZE; row++)
		{
			for(col=0; col<MATSIZE; col++)
			{
//				output_C_HW[row][col] = u32_to_float(DMA_output[index]);
				output_C_HW[row][col] = DMA_output[index];
				index = index + 1;
			}
		}

		// Compare Benchmark and hardware function output
		// Receive stream output C from hardware function
		for(row=0; row<MATSIZE; row++)
		{
			for(col=0; col<MATSIZE; col++)
			{
				float diff = abs(output_C_HW[row][col]-output_C_SW[row][col]);
				if(diff > 0.001)
				{
					printf("Non-Optimized MMUL Error at row %d and col %d\t\n", row, col);
					printf("Hardware output %f\r\n", output_C_HW[row][col]);
					printf("Software output %f\r\n", output_C_SW[row][col]);
					break;
				}
			}
		}

	} // k < EXPT

	printf("---> Execution Time for PS in Micro-Seconds: %f \t\n", time_processor/EXPT);
	printf("---> Average Execution Time for Non-Optimized MMUL in PL in Micro-Seconds: %f \t\n", time_FPGA/EXPT);

	return 0;
}

int main()
{
	init_platform();
	Matrixmul();
	cleanup_platform();
	return 0;
}

void matrixmul_benchmark(Mat_Dtype input_A[MATSIZE][MATSIZE], Mat_Dtype input_B[MATSIZE][MATSIZE], Mat_Dtype output_C[MATSIZE][MATSIZE])
{
	for(int row=0; row<MATSIZE; row++)
	{
		for(int col=0; col<MATSIZE; col++)
		{
			float res=0;
			for(int index=0; index<MATSIZE; index++)
			{
				res = res + input_A[row][index]*input_B[index][col];
			}
			output_C[row][col] = res;
		}
	}
}

 

마지막으로 실행 결과를 살펴 보자.

위 캡쳐화면의 아래 부분 Vitis Serial Terminal을 보면 프로세서에서 수행되는 8x8행렬 곱의 경우 평균 25.428 us 이 필요하며, FPGA에서 가속된 행렬곱의 경우 15.9579 us 이 요구되는 것을 알수 있다.

 

 

'Tech-Tip' 카테고리의 다른 글

최신 GPU Spec 체크  (0) 2023.05.21
행렬곱 - Xilinx HLS 실험 4  (0) 2023.02.23
행렬곱 - Xilinx HLS 실험 3  (0) 2023.02.23
행렬곱 - Xilinx HLS 실험 2  (0) 2023.02.22
Intel Quartus - Modelsim - 소프트웨어설치  (0) 2023.01.02
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2025/12   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함