카테고리 없음

FPGA로 가속기 만들기 : MATMUL 행렬 곱셈 하기

SciomageLAB 2024. 10. 20. 16:35
반응형

개요

PYNQ-Z2 FPGA에서 행렬 곱 처리하는 가속기를 만드는 예제이다. FPGA 특성상 회로에서 CPU에서 하나씩 처리 하는 것보다 FPGA에서 병렬처리를 하면 성능 향상을 확인 할 수 있다. 이 프로젝트 역시 cpp로 코드를 작성하고 HLS를 이용해서 블럭을 만들고, VIVADO에서 합성하고, 보드에서 실행 하는 순서이다. 이 글 맨 아래 있는 참고자료를 참고 했다. 나중엔 이걸 이용해서 CONV연산, CNN까지 확장 할 수 있다.

HLS로 IP 생성

우선 HLS로 IP를 생성해야 한다.

matmul 디렉토리를 만들고 프로젝트 이름을 matmul으로 한다.

Top Function을 matmul으로 하고, matmul.cpp를 추가 한다.

타겟 보드를 Pynq-z2로 설정 한다.

#ifndef __MATMUL_H__
#define __MATMUL_H__

#define MAT_A_ROWS 32
#define MAT_A_COLS 32
#define MAT_B_ROWS 32
#define MAT_B_COLS 32

typedef int mat_a_t;
typedef int mat_b_t;
typedef int result_t;

void matmul(mat_a_t a[MAT_A_ROWS][MAT_A_COLS],
        mat_b_t b[MAT_B_ROWS][MAT_B_COLS],
        result_t res[MAT_A_ROWS][MAT_B_COLS]);

#endif

matmul.hpp 파일을 생성하고 위 코드를 복사한다.

#include matmul.hpp

void matmul(mat_a_t a[MAT_A_ROWS][MAT_A_COLS],
        mat_b_t b[MAT_B_ROWS][MAT_B_COLS],
        result_t res[MAT_A_ROWS][MAT_B_COLS]) {
#pragma HLS INTERFACE axis port=a
#pragma HLS INTERFACE axis port=b
#pragma HLS INTERFACE axis port=res
#pragma HLS INTERFACE ap_ctrl_none port=return

    int tempA[MAT_A_ROWS][MAT_A_COLS];
    int tempB[MAT_B_ROWS][MAT_B_COLS];
    int tempAB[MAT_A_ROWS][MAT_B_COLS];

    for (int ia = 0; ia < MAT_A_ROWS; ia++) {
        for (int ja = 0; ja < MAT_A_COLS; ja++) {
            tempA[ia][ja] = a[ia][ja];
        }
    }
    for (int ib = 0; ib < MAT_B_ROWS; ib++) {
        for (int jb = 0; jb < MAT_B_COLS; jb++) {
            tempB[ib][jb] = b[ib][jb];
        }
    }
    /* for each row and column of AB */

    row: for (int i = 0; i < MAT_A_ROWS; ++i) {
        col: for (int j = 0; j < MAT_B_COLS; ++j) {
            /* compute (AB)i,j */
            int ABij = 0;
            product: for (int k = 0; k < MAT_A_COLS; ++k) {
                ABij += tempA[i][k] * tempB[k][j];

            }
            tempAB[i][j] = ABij;
        }
    }

    for (int iab = 0; iab < MAT_A_ROWS; iab++) {
        for (int jab = 0; jab < MAT_B_COLS; jab++) {
            res[iab][jab] = tempAB[iab][jab];
        }
    }

}

matmul.cpp 파일에 위 내용을 복사 한다. 일반적인 그냥 행렬을 곱하는 내용이다.

함수 구현부 아래를 보면 HLS 인터페이스가 axis로 돼있다. 파라미터를 AXI4-Stream으로 입력/출력하게 돼있다. 입력 파라미터를 temp 변수로 복사 했다가 처리하고 결과를 다시 파라미터 변수에 쓰는 것이 특징이다.

cpp 파일과 hpp 파일을 예쁘게 만들어 놓고 C합성을 하면 이렇게 나온다. 파이프라인을 따로 적용하지 않아서 그런지 시간이 1.385ms로 좀 걸린다. 내용을 확인하고 문제가 없으면 RTL을 파일을 내보낸다.

Vivado 블럭 디자인

matmul_vivado 이름으로 vivado 프로젝트를 생성한다.

프로젝트 설정에 들어가서, 위의 HLS에서 만든 IP를 추가 한다.

블럭 디자인을 하나 만들고 zynq로 검색해서 생성하고, 상단 민트색 바를 클릭해서 자동연결을 실행하면 위 이미지와 같이 기본 연결이 설정 된다.

zynq 블럭을 더블클릭 해서 PS-PL 설정 탭에서 HP Slave AXI에서 HP0를 선택한다. (DMA 사용을 위해서 적용 하는 것 같다.)

HLS에서 만든 matmul과 DMA 블럭 두 개를 검색해서 추가 한다.

DMA0 블럭을 선택해서 Scatter Gather Engine을 해재 한다. DMA0은 읽기 쓰기 모두 활성화 한다.

DMA1 블럭을 선택해서 Scatter Gather Engine을 해제 한다. DMA1은 읽기만 활성화 한다.

HLS에서 생성한 matmul 함수의 입력이 두 개, 출력이 한 개 이기 때문에 이것에 맞도록 설정 한다.

상단 민트색 자동연결을 하면 처음에는 DMA연결을 하고, 한 번 더 하면 matmul에 대한 자동 연결을 실행한다. 그 다음에 위 이미지대로 matmul IP 블럭에 있는 a, b, res를 각각 위 이미지대로 DMA에 맞게 연결 한다.

유효성 검사 후 HDL Wrapper를 생성하고 Bitstream을 생성한다. 컴퓨터 성능에 따라 5분 내외 시간이 걸린다.

블럭 디자인(.tcl), Bitstream(.bit), hwh 파일 내보내기하고 한 디렉토리에 같은 이름으로 복사 한다.

zynq보드의 overlays에 matmul 디렉토리를 생성하고 위 3개 파일을 복사 한다.

<pre class="wp-block-syntaxhighlighter-code">import pynq.lib.dma
import numpy as np

mmol = pynq.Overlay(/home/xilinx/pynq/overlays/matmul/matmul.bit)

dma0 = mmol.axi_dma_0
dma1 = mmol.axi_dma_1

from pynq import Xlnk
xlnk = Xlnk()
a = xlnk.cma_array(shape=(32,32), dtype=np.int)
b = xlnk.cma_array(shape=(32,32), dtype=np.int)
res = xlnk.cma_array(shape=(32,32), dtype=np.int)

for i in range(32):
    for j in range(32):
        a[i][j] = 8;
        b[i][j] = 8;

dma0.sendchannel.transfer(a)
dma1.sendchannel.transfer(b)
dma0.recvchannel.transfer(res)
print(res)

jupyter에서 위 코드를 실행하면 아래처럼 행렬 곱셈 결과가 정상적으로 출력된다. bitstream을 읽어와서 xlnk.cma_array 배열을 크기에 맞게 생성한 후에 각각 모두 8로 채우고 DMA 통해서 a와 b를 입력, 결과를 res로 받아서 출력하는 내용이다.

<pre class="wp-block-syntaxhighlighter-code">[[2048 2048 2048 ... 2048 2048 2048]
 [2048 2048 2048 ... 2048 2048 2048]
 [2048 2048 2048 ... 2048 2048 2048]
 ...
 [2048 2048 2048 ... 2048 2048 2048]
 [2048 2048 2048 ... 2048 2048 2048]
 [2048 2048 2048 ... 2048 2048 2048]]

참고 자료

반응형