[Minit-NPU RTL] Design Processing Element

"Processing Element"

pe_mac.v

8-bit 입력 2개를 받아 곱셈을 하고, "en" 신호가 "1"이면 해당 곱셈 결과 값을 누적하는 모듈
"clear" 신호가 "1"이면 누적된 값을 초기화

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
`timescale 1ns / 1ps
module pe_mac #(
    parameter data_width = 8,               // 데이터 크기 = 8-bit
    parameter mul_width = 2 * data_width,   // Multiplication (곱한 값은 최대 2 * data_width)
    parameter acc_width = 2 * mul_width     // Accumulation (곱한 결과 값 누적은 최대 2 * mul_width)
) (
    input clk,
    input rst,
    input clear,                             // Accumulator 초기화
    input en,                                // 연산 가능 신호
    input [data_width-1:0] a_in,
    input [data_width-1:0] b_in,
    output [mul_width-1:0] mul_out,
    output reg [acc_width-1:0] acc_sum_out
);
 
    assign mul_out = a_in* b_in;             // 곱셈 연산
 
    always @ (posedge clk or negedge rst) begin
        if (!rst) begin                      // reset 신호
            acc_sum_out <= 0;                // Accumulator 초기화
        end else if (clear) begin            // Accumulator clear 신호
            acc_sum_out <= 0;                // Accumulator 초기화
        end else if (en) begin               // 누적 연산 가능 신호
            acc_sum_out <= acc_sum_out + mul_out;   // 누적 연산
        end
    end
endmodule
 
cs

tb_pe_mac.v

"pe_mac.v" 코드를 Instance
테스트 벤치 코드에서도 pe_mac을 구현하여 DUT에서 계산한 결과와 TB에서 계산한 결과가 동일한지 확인
서로 맞지 않다면 Count를 늘려가며 통계에 사용
입력 값 "a_in", "b_in"은 0~255 범위 중 랜덤한 값을 사용
누적 신호 값 "en"은 0/1 중 랜덤한 값을 사용, "1"일 때 누적 진행
누적 값 초기화 "clear"는 5% 확률로 "1"이 되어 초기화

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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
`timescale 1ns / 1ps
module tb_pe_mac;
    localparam data_width = 8;                  // TB 파일에서만 적용
    localparam mul_width = 2 * data_width;      // TB 파일에서만 적용
    localparam acc_width = 2 * mul_width;       // TB 파일에서만 적용
 
    reg clk;
    reg rst;
    reg clear;
    reg en;
    reg [data_width-1:0] a_in;
    reg [data_width-1:0] b_in;
    wire [mul_width-1:0] mul_out;
    wire [acc_width-1:0] acc_sum_out;
 
    // 통계를 위한 Count 변수
    integer err_mul_cnt;
    integer err_acc_cnt;
    integer total_cnt;
// ================================================================
// 1) Clock & Reset
    initial begin
        clk <= 0;
        forever #5 clk <= ~clk;        // 주기 10ns
    end
 
    initial begin
        rst <= 0;           // 초기화
        a_in <= 0;          // 초기화
        b_in <= 0;          // 초기화
        en <= 0;            // 초기화
        clear <= 0;         // 초기화
        
        #30                 // 30ns 이후
        rst <= 1;           // reset 신호 인가
        
        #2000               // 2000ns동안 랜덤 입력에 대한 계산 진행
        
        // 통계 출력
        $display("===============================");
        $display("[Total Cycles] : %d", total_cnt);
        $display("[MUL Errors]   : %d", err_mul_cnt);
        $display("[ACC Errors]   : %d", err_acc_cnt);
        $finish;            // 종료
    end
// ================================================================
// 2) DUT와 TB값을 비교하기 위해 DUT와 똑같은 Logic을 TB 내부에 구현
    wire [mul_width-1:0] tb_mul_out;
    reg [acc_width-1:0] tb_acc_sum_out;
    
    assign tb_mul_out = a_in * b_in;
    
    always @ (posedge clk or negedge rst) begin
        if (!rst) begin
            tb_acc_sum_out <= 0;
        end else if (clear) begin
            tb_acc_sum_out <= 0;
        end else if (en) begin
            tb_acc_sum_out <= tb_acc_sum_out + tb_mul_out;
        end
    end
// ================================================================
// 3) 매 clock마다 DUT의 결과와 TB 결과를 비교
    always @ (posedge clk) begin
        if (!rst) begin
            err_mul_cnt <= 0;
            err_acc_cnt <= 0;
            total_cnt <= 0;
        end else begin
            total_cnt <= total_cnt + 1;
            if (mul_out !== tb_mul_out) begin
                $display("[MUL_ERROR] Time=%0,t | DUT=%d, TB=%d", $time mul_out, tb_mul_out);
                err_mul_cnt = err_mul_cnt + 1;
            end else begin
                $display("[MUL_PASS] Time=%0t | DUT=%d, TB=%d", $time, mul_out, tb_mul_out);
            end
            if (acc_sum_out !== tb_acc_sum_out) begin
                $display("[ACC_ERROR] Time=%0t | DUT=%d, TB=%d", $time, acc_sum_out, tb_acc_sum_out);
                err_acc_cnt = err_acc_cnt + 1;
            end else begin
                $display("[ACC_PASS] Time=%0t | DUT=%d, TB=%d", $time, acc_sum_out, tb_acc_sum_out);
            end
        end
    end
// ================================================================
// 4) Instance를 통한 port 연결
    pe_mac #(                           // TB 파일에서 설정한 값을 DUT에도 적용하기 위함
        .data_width(data_width),
        .mul_width(mul_width),                    
        .acc_width(acc_width)
    ) dut (                             // dut 이름으로 instance
        .clk(clk),
        .rst(rst),
        .clear(clear),
        .en(en),
        .a_in(a_in),
        .b_in(b_in),
        .mul_out(mul_out),
        .acc_sum_out(acc_sum_out)
    );
// ================================================================
// 5) 입력 생성
    always @ (posedge clk or negedge rst) begin
        if (!rst) begin
            a_in <= 0;
            b_in <= 0;
        end else begin
            a_in <= $urandom_range(0, (1<<data_width)-1);    // 1을 왼쪽으로 8칸 shift = 256(1_0000_0000)
            b_in <= $urandom_range(0, (1<<data_width)-1);    // 1을 왼쪽으로 8칸 shift = 256(1_0000_0000)
            en <= $urandom_range(01);                      // 0/1 중 랜덤, 1일 때 mul 값을 누적
            if ($urandom_range(099< 5begin             // 5% 확률로 clear 신호 켜서 acc 값 초기화
                clear <= 1;
            end else begin
                clear <= 0;
            end
        end
    end
endmodule
 
cs

Result

"#30"을 통해 30ns 이후부터 "rst=1"이 되어 연산을 시작
DUT의 mul 값인 "mul_out"과 TB의 mul 값인 "tb_mul_out"의 값이 같아 Console 창에서 "[MUL_Pass]"가 출력
"en" 신호가 0이므로 곱셈 결과 값이 누적되지 않아 ACC값은 "0"으로 출력

"en" 신호가 1이 된 경우 해당 곱셈 값은 누적되는 것을 "acc_sum_in"과 "tb_acc_sum_in"을 통해 확인 가능

"clear" 신호가 1이 되어 누적 값인 "acc_sum_in"과 "tb_acc_sum_in"이 "0"으로 초기화 되는 것을 확인 가능

"#30" 이후 rst 값을 줬기 때문에 30ns에 "#2000"인 2030ns에 종료되며 최종 연산 횟수와 Error 개수를 출력

Reference

▶ Design 1D PE Chain
https://hecess.blogspot.com/2026/01/mini-npu-rtl-design-1d-pe-chain.html

▶ Design 2D Systolic Array
https://hecess.blogspot.com/2026/01/mini-npu-rtl-design-2d-systolic-array.html

▶ Design Systolic Array + Controller
https://hecess.blogspot.com/2026/01/mini-npu-rtl-design-systolic-array.html

▶ Design Systolic Array + Controller + Activation(ReLU)
https://hecess.blogspot.com/2026/01/mini-npu-rtl-design-systolic-array_27.html

댓글

이 블로그의 인기 게시물

[Mini-NPU RTL] NN Reference Model

[Mini-NPU RTL] TPU (Study Paper)