[Embedded System Lab] Chapter 7 - 8x8 PE Implementation
"8x8 PE Implementation"
[Objective]
8x8 PE Array를 설계해서 MAC 연산 기반 행렬 곱셈 결과를 Simulation을 통해 검증하여 Logic 이해하기
[Theory]
▣ PE(Processing Element)
· 컴퓨터 System에서 사용되는 기본 연산 단위
· 핵심 기능은 MAC 연산
→ 많은 PE가 반복적으로 동작하면서 대규모 병렬 연산을 빠르게 수행 가능
<PE Structure>
· Wn(Weight)와 An(Input Activation)을 곱한 결과를 Yn-1(이전까지 누적된 Partial Sum)과 더함
▣ Stationary
· 데이터를 메모리에서 계속 가져오지 않고, 한 번 가져온 데이터를 PE 내부에 유지하여 재사용하는 방식
(Output Stationary, Weight Stationary, Input Stationary, Row Stationary)
→ 시간도 많이 걸리고 전력 소모도 큰 메모리 접근 횟수를 감소하여 효율 증가
> Output Stationary
· 각 PE가 계산하는 출력 값(Partial Sum)을 누적하면서 제자리에서 유지
· Data Flow : Input Activation = 여러 번 흐름 / Weight = 여러 번 흐름 / Partial Sum = PE 내부에 유지
> Weight Stationary
· 각 PE가 필요한 Weight를 제자리에 유지하고 재사용
· Data Flow : Input Activation = 여러 번 흐름 / Weight = PE 내부에 유지 / Partial Sum = 생성되어 외부로 출력
· Data Flow : Input Activation = 여러 번 흐름 / Weight = PE 내부에 유지 / Partial Sum = 생성되어 외부로 출력
<Weight Stationary 기반 PE Structure>
▼
▼
▼
· 연산 전에 Weight를 먼저 넣어 연산 중에 Weight 이동을 줄이고, 입력 데이터와 Partial Sum 흐름에 집중▣ Partial Sum Workflow
① Weight가 PE Array에 미리 저장
② Input 데이터가 Cycle마다 지연되어 흐르면서 각 PE에서 MAC 연산 수행
③ 출력 행렬의 원소가 누산
** Weight Stationary 방식이므로 Weight는 자리를 유지하고 Input이 흐르면서 출력 값을 계산
[Experiment]
▣ tb_pe_8x8.v
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | // System Verilog로 진행 `timescale 1ns / 1ps module tb_pe_8x8; reg clk; // Clock 신호 reg rst; // Reset 신호 reg preload; // Weight Preload 신호 reg mac; // MAC 연산 수행 신호 reg signed[7:0] weight[0:7]; // 8-bit Signed 값 8개 reg signed[7:0] in[0:7]; // 8-bit Signed 값 8개 wire signed[17:0] out[0:7]; // 18-bit Signed 값 8개 integer r, c, k; reg signed[7:0] in_data[0:63]; // txt 파일의 In Data를 Load (1차원 배열) reg signed[7:0] weight_data[0:63]; // txt 파일의 Weight Data를 Load (1차원 배열) reg signed[17:0] amm[0:7][0:7]; // Actual Matrix Multiplication 계산 (8x8) // pe_8x8 Module Instance pe_8x8 dut ( .clk(clk), .rst(rst), .preload(preload), .mac(mac), .in(in), .weight(weight), .out(out) ); initial begin clk <= 1; // clk 초기화 forever #1 clk <= ~clk; // 1ns마다 반전 = 주기 2ns end initial begin rst <= 1; // Reset 신호 On preload <= 0; // Preload 초기화 mac <= 0; // MAC 초기화 $readmemh("input.txt", in_data); // txt 파일에서 In 데이터 Load $readmemh("weight.txt", weight_data); // txt 파일에서 Weight 데이터 Load #1 rst <= 0; // Reset 신호 Off // 정답 계산 for (c = 0; c < 8; c = c + 1) begin for (r = 0; r < 8; r = r + 1) begin amm[r][c] = 0; for (k = 0; k < 8; k = k + 1) amm[r][c] = amm[r][c] + weight_data[r * 8 + k] * in_data[k * 8 + c]; // Weight x Input end end preload <= 1; // Weight Preload 신호 High @ (negedge clk); // 첫 데이터를 출력하기 위해 대기 for (c = 7; c >= 0; c = c - 1) begin for (k = 0; k < 8; k = k + 1) weight[k] <= weight_data[k * 8 + c]; // 마지막 Column부터 Column 별로 Weight Data Load @ (negedge clk); // 한 Cycle마다 한 Column씩 처리 end preload <= 0; // Weight Preload 신호 Low mac <= 1; // MAC 연산 수행 신호 High for (k = 0; k < 16; k = k + 1) begin for (r = 0; r < 8; r = r + 1) begin c = k - r; // 현재 사이클에서 r번째 행에 들어가야 할 열 계산 if (c >= 0 && c < 8) in[r] <= in_data[r * 8 + c]; // c가 0~7 사이일 때만 유효한 데이터 할당 else in[r] <= 0; // 아직 차례가 아니거나 끝난 경우 0 end @ (negedge clk); // 대기 end repeat(10) @ (posedge clk); // 파이프라인에 남은 마지막 데이터가 빠져나올 때까지 대기 mac <= 0; // MAC 연산 수행 신호 Low $finish; // Simulation 종료 end endmodule | cs |
· Verilog 방식으로는 Module 내부에서 2차원으로 받아도 상관없지만 2차원 Port로 입력 받기는 어려워서 System Verilog로 진행
· [7:0] in, weight [0:7] : 한 Cycle에 8개의 데이터만 받아서 데이터가 흐르도록 설계
· [17:0] out [0:7] : 8-bit Weight와 8-bit Input을 곱하고(16-bit), 8번 누적(3-bit)하므로 최소 19-bit
→ 실험 데이터 범위 내에서 18-bit로 최적화
· in_data[0:63], weight_data[0:63] : txt 파일의 Input Data와 Weight Data를 1차원 배열로 Load하여 저장하는 변수
· amm[0:7][0:7] : 실제 행렬 곱을 연산하여 설계한 HW로 연산한 결과와 비교
① Instance를 통해 pe_8x8.v Module과 연결
② Clock을 2ns 주기로 설정
③ rst = High, preload & mac = Low → 초기화 진행
④ txt 파일에서 Input Data와 Weight Data를 1차원 배열 형태로 Load
⑤ (Weight) x (Input)을 계산하여 설계한 Module의 연산 결과와 비교할 정답 행렬 생성
⑥ Weight Preload 신호를 켜고, 마지막 Column부터 Column 별로 Weight Data를 weight 변수에 Load
→ 한 Cycle마다 한 Column씩 처리되도록 대기
⑦ MAC 신호를 켜고, 데이터가 Skewing돼서 PE에 들어갈 수 있도록 k를 사용
→ 유효한 Column 범위가 아닌 경우(아직 차례가 아니거나, 입력이 끝난 경우) in 값으로 0을 할당
⑧ Pipeline에 남은 마지막 데이터가 Flush 되도록 Clock을 반복하며 대기
⑨ Simulation 종료
▣ pe_8x8.v
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | // System Verilog로 진행 `timescale 1ns / 1ps module pe_8x8( input clk, // Clock 신호 input rst, // Reset 신호 input preload, // Weight Preload 신호 input mac, // MAC 연산 수행 신호 input signed [7:0] in[0:7], // 8-bit Signed 값 8개 input signed [7:0] weight[0:7], // 8-bit Signed 값 8개 output reg signed [17:0] out[0:7] // 18-bit Signed 값 8개 ); reg signed [7:0] pe_weight [0:7][0:7]; // PE Weight Preload reg signed [7:0] in_reg [0:7][0:7]; // 오른쪽으로 전달되는 In Data reg signed [17:0] psum_reg [0:7][0:7]; // 아래로 전달되는 Partial Sum integer r, c; always @(posedge clk or posedge rst) begin if (rst) begin // Reset 신호가 High인 경우 for (r = 0; r < 8; r = r + 1) begin out[r] <= 0; // out의 모든 값 초기화 for (c = 0; c < 8; c = c + 1) begin pe_weight[r][c] <= 0; // PE Weight 초기화 in_reg[r][c] <= 0; // In Register 초기화 psum_reg[r][c] <= 0; // Partial Sum Register 초기화 end end end else begin if (preload) begin // Preload 신호가 High인 경우 // tb에서 Weight를 마지막 Column부터 Column 별로 데이터를 가져오므로 Transpose 됨 for (c = 0; c < 8; c = c + 1) begin for (r = 7; r > 0; r = r - 1) pe_weight[r][c] <= pe_weight[r-1][c]; // 위의 행의 데이터를 전달 받음 pe_weight[0][c] <= weight[c]; // 0번째 행은 새로운 Weight 할당 end end else if (mac) begin // MAC 신호가 High인 경우 for (r = 0; r < 8; r = r + 1) begin for (c = 0; c < 8; c = c + 1) begin if (c == 0) // 0번째 Column인 경우 in_reg[r][c] <= in[r]; // 새로운 In Data 할당 else // 0번째 Column이 아닌 경우 in_reg[r][c] <= in_reg[r][c-1]; // 왼쪽 In Data를 전달 받음 if (r == 0) begin // 0번째 Row인 경우 // 0번째 Column이면 새로운 In Data | 0번째 Column이 아니면 왼쪽 In Data와 곱함 psum_reg[r][c] <= pe_weight[r][c] * ((c == 0) ? in[r] : in_reg[r][c-1]); end else begin // 0번째 Row가 아닌 경우 // 0번째 Column이면 새로운 In Data | 0번째 Column이 아니면 왼쪽 In Data와 곱해서 누적 psum_reg[r][c] <= psum_reg[r-1][c] + pe_weight[r][c] * ((c == 0) ? in[r] : in_reg[r][c-1]); end end end end end end always @ (*) begin for (c = 0; c < 8; c = c + 1) begin out[c] = psum_reg[7][c]; // 마지막 행에서 계산된 Partial Sum은 해당 Column의 최종 결과 end end endmodule | cs |
· Verilog 방식으로는 Module 내부에서 2차원으로 받아도 상관없지만 2차원 Port로 입력 받기는 어려워서 System Verilog로 진행
· in, weight [0:7] : 한 Cycle에 8개의 데이터만 받아서 데이터가 흐르도록 설계
· pe_weight : PE에 Weight를 Preload하는 변수
· in_reg : Input Data를 오른쪽으로 전달하는 변수
· psum_reg : Partial Sum을 아래로 전달하는 변수
> Reset 신호가 High인 경우
· out, pe_weight, in_reg, psum_reg를 모두 초기화
> Preload 신호가 High인 경우
· 0번째 Row가 아닌 경우 : 위 PE의 Weight를 전달받음 / 0번째 Row인 경우 : "weight"값을 할당 받음
→ "weight"는 tb_pe_8x8.v에서 마지막 Column부터 Column 별로 데이터를 Load
→ pe_weight의 0번째 행에 Column 별로 Load 한 데이터를 할당
∴ Weight Data는 Transpose되어 PE에 Load
· tb_pe_8x8.v에서 Clock 대기를 통해 한 Column씩 데이터를 할당 → 총 8 Cycle이 지난 후에 8x8 PE에 모든 Weight가 Preload 됨
> MAC 신호가 High인 경우
· 0번째 Column인 경우 : tb_pe_8x8.v에서 Skewing 되어 들어오는 Input Data를 할당
· 0번째 Column이 아닌 경우 : 왼쪽 PE의 Input을 전달받음
· 이후 Partial Sum 계산
· 0번째 Row & 0번째 Column : 새로운 Input을 Weight와 곱셈
· 0번째 Row & n번째 Column : 왼쪽 PE에서 전달받은 Input을 Weight와 곱셈
· n번째 Row & 0번째 Column : 새로운 Input을 Weight와 곱한 후, 위에서 전달받은 이전의 Partial Sum 값에 누적
· n번째 Row & n번째 Column : 왼쪽 PE에서 전달받은 Input을 Weight와 곱한 후, 위에서 전달받은 이전의 Partial Sum 값에 누적
→ 마지막 행에서 계산된 Partial Sum은 누적이 완료된 것이므로 out으로 출력
[Result]
· Weight Data가 마지막 Column부터 Column 별로 Data가 Load
· Input Data는 행 별로 Skewing 되어 들어가고 모든 값이 들어간 후에는 다시 0을 출력
· 설계한 HW로 계산한 결과와 SW로 계산한 결과가 동일 = 행렬 곱 연산이 제대로 진행
· 곱 연산이 Pipeline으로 잘 진행되며 Input에 대한 곱 연산이 완료된 후에는 0을 출력
· 곱 연산이 Pipeline으로 잘 진행되며 Input에 대한 곱 연산이 완료된 후에는 0을 출력
[Reference]
· 8x8_PE_7 (Embedded System Lab : Chapter7) - William J. Song
댓글
댓글 쓰기