[Mini-NPU RTL] Design Systolic Array + Controller + Activation(ReLU)

"Systolic Array + Controller + Activation(ReLU)"

pe_mac.v

Design Processing Element의 pe_mac.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
`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

pe_systolic_cell.v

Design 2D Systolic Array의 pe_systolic_cell.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
`timescale 1ns / 1ps
module pe_systolic_cell # (
    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,
    input en,
    
    input [data_width-1:0] a_in,
    input [data_width-1:0] b_in,
    output [data_width-1:0] a_out,            // 전달되는 값
    output [data_width-1:0] b_out,            // 전달되는 값
    output [mul_width-1:0] mul_out,
    output [acc_width-1:0] acc_sum_out
);
 
    // a, b 값을 한 번 register에 저장 후 옆/아래로 전달
    reg [data_width-1:0] a_reg, b_reg;
    assign a_out = a_reg;
    assign b_out = b_reg;
    
    always @ (posedge clk or negedge rst) begin
        if (!rst) begin                // reset 신호
            a_reg <= 0;                // reg값 초기화
            b_reg <= 0;                // reg값 초기화
        end else if (en) begin         // en tlsgh
            a_reg <= a_in;             // reg값 update
            b_reg <= b_in;             // reg값 update
        end
    end
    
    // 내부 MAC 연산 동작은 pe_mac Instance를 통해 수행
    pe_mac # (                      // 이 파일에서 설정한 값을 pe_mac 파일에도 적용하기 위함
        .data_width(data_width),
        .mul_width(mul_width),
        .acc_width(acc_width)
    ) unit_pe_mac (                 // unit_pe_mac 이름으로 Instance
        .clk(clk),
        .rst(rst),
        .clear(clear),
        .en(en),
        .a_in(a_reg),               // a_reg * b_reg 누산
        .b_in(b_reg),               // a_reg * b_reg 누산
        .mul_out(mul_out),
        .acc_sum_out(acc_sum_out)
    );
endmodule
 
cs

systolic_array_2d.v

Design 2D Systolic Array의 systolic_array_2d.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
`timescale 1ns / 1ps
module systolic_array_2d # (
    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)
    parameter rows = 2,                     // 행 개수
    parameter cols = 2                      // 열 개수
) (
    input clk,
    input rst,
    input clear,
    input en,
    input [data_width-1:0] a_in_row [0:rows-1],                   // 왼쪽에서 들어오는 A 행렬 stream (각 행마다 입력 하나)
    input [data_width-1:0] b_in_col [0:cols-1],                   // 위쪽에서 들어오는 B 행렬 stream (각 열마다 입력 하나)
    output [mul_width-1:0] pe_mul_out [0:rows-1][0:cols-1],       // 각 PE의 곱셈 결과
    output [acc_width-1:0] pe_acc_sum_out [0:rows-1][0:cols-1]    // 각 PE의 누적 결과
);
 
    wire [data_width-1:0] a_temp [0:rows-1][0:cols];        // 2개의 열을 외부 입력 - 연결 - 외부 출력 연결
    wire [data_width-1:0] b_temp [0:rows][0:cols-1];        // 2개의 행을 외부 입력 - 연결 - 외부 출력 연결
    
    genvar r, c;                                // 반복문에 사용할 변수 선언
    
    generate
    for (r = 0; r < rows; r++begin            // 행 반복
        assign a_temp[r][0= a_in_row[r];      // 각 행에 맞는 데이터를 0번째 열에 공급
    end
    for (c = 0; c < cols; c++begin            // 열 반복
        assign b_temp[0][c] = b_in_col[c];      // 각 열에 맞는 데이터를 0번째 행에 공급
    end
    endgenerate
    
    generate
    for (r = 0; r < rows; r++begin            // 행 반복
        for (c = 0; c < cols; c++begin        // 열 반복
            pe_systolic_cell # (                // 이 파일에서 설정한 값을 pe_systolic_cell 파일에도 적용하기 위함
                .data_width(data_width),
                .mul_width(mul_width),
                .acc_width(acc_width)
            ) unit_cell (                       // unit_cell 이름으로 Instance
                .clk(clk),
                .rst(rst),
                .clear(clear),
                .en(en),
                .a_in(a_temp[r][c]),            // 현재 위치에서 입력을 받음
                .a_out(a_temp[r][c+1]),         // 다음 위치로 출력을 보냄
                .b_in(b_temp[r][c]),            // 현재 위치에서 입력을 받음
                .b_out(b_temp[r+1][c]),         // 다음 위치로 출력을 보냄
                .mul_out(pe_mul_out[r][c]),         // 곱셈 결과
                .acc_sum_out(pe_acc_sum_out[r][c])  // 누적 결과
            );
        end
    end
    endgenerate
endmodule
 
cs

systolic_controller_relu.v

FSM을 통해 데이터 공급 타이밍을 조절하여 사용자가 데이터만 입력하면 전체 동작을 제어하고, 행렬 연산 결과에 Bias와 ReLU 적용

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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
`timescale 1ns / 1ps
module systolic_controller_relu # (
    parameter data_width = 8,               // 데이터 크기 = 8-bit
    parameter k_dim = 2,                    // 공통 차원
    parameter mul_width = 2 * data_width,   // Multiplication (곱한 값은 최대 2 * data_width)
    parameter acc_width = 2 * mul_width,    // Accumulation (곱한 결과 값 누적은 최대 2 * mul_width)
    parameter rows = 2,                     // 행 개수
    parameter cols = 2,                     // 열 개수
    parameter bias = 30                     // 활성화 함수용 파라미터(이 값보다 작은 연산 결과는 Noise로 간주하고 0으로 변환)
) (
    input clk,
    input rst,
    input start_in,                                         // 연산 시작 신호
    input [data_width-1:0] A_in [0:rows-1][0:k_dim-1],      // 왼쪽에서 들어오는 A 행렬 stream (각 행마다 입력 하나)
    input [data_width-1:0] B_in [0:k_dim-1][0:cols-1],      // 위쪽에서 들어오는 B 행렬 stream (각 열마다 입력 하나)
    output reg [acc_width-1:0] C_out [0:rows-1][0:cols-1],  // HW 계산 결과
    output busy_out,                                        // 연산 진행중 신호
    output done_out                                         // 연산 완료 신호
);
 
//===================================================
// 1) 내부 상태 및 버퍼 정의
    typedef enum logic [1:0] {
        IDLE,       // 대기
        RUN,        // 연산 진행중
        DONE        // 연산 완료
    } state;
    
    state tmp_state, next_state;    // 상태 변수 선언
    reg [7:0] cnt;                  // 타이머
    
    // Input Buffer : 입력 데이터를 저장해두는 공간
    reg [data_width-1:0] latched_A [0:rows-1][0:k_dim-1];
    reg [data_width-1:0] latched_B [0:k_dim-1][0:cols-1];
 
    // Array Interface : 실제 Systolic Array로 1 Cycle마다 들어갈 데이터
    reg [data_width-1:0] a_in_row [0:rows-1];   // 왼쪽에서 들어오는 A 행렬 stream (각 행마다 입력 하나)
    reg [data_width-1:0] b_in_col [0:cols-1];   // 위쪽에서 들어오는 B 행렬 stream (각 열마다 입력 하나)
    wire en;                                    // 작동 신호
    wire clear;                                 // 초기화 신호
 
    // ** Array의 순수 결과값 **
    wire [acc_width-1:0] acc_sum_out [0:rows-1][0:cols-1];
 
    // 연산 완료까지 걸리는 시간 계산
    localparam calc_cycles = rows + cols + k_dim + 2;
//===================================================
// 2) Instance
    systolic_array_2d # (               // 이 파일에서 설정한 값을 pe_systolic_cell 파일에도 적용하기 위함
        .data_width(data_width),
        .mul_width(mul_width),
        .acc_width(acc_width),
        .rows(rows),
        .cols(cols)
    ) unit_core_array (                 // unit_core_array 이름으로 Instance
        .clk(clk),
        .rst(rst),
        .clear(clear),
        .en(en),
        .a_in_row(a_in_row),
        .b_in_col(b_in_col),
        .pe_mul_out(),                  // Unused
        .pe_acc_sum_out(acc_sum_out)
    );
//===================================================
// 3) FSM & Counter
    always @ (posedge clk or negedge rst) begin
        if (!rst) begin
            tmp_state <= IDLE;                      // 대기 상태로 설정
            cnt <= 0;                               // Counter 초기화
        end else begin
            tmp_state <= next_state;                // 다음 상태로 변경
            if (tmp_state == RUN) cnt <= cnt + 1;   // RUN 상태라면 카운터 증가
            else cnt <= 0;                          // RUN 상태가 아닌 경우 카운터 초기화
        end
    end
    
    always @ (*begin
        next_state = tmp_state;                             // 현재 상태를 다음 상태에 저장
        case (tmp_state)
            IDLE: begin                                     // IDLE 상태
                if (start_in) next_state = RUN;             // start 신호가 오면 다음 상태 = RUN
            end
            RUN: begin                                      // RUN 상태
                if (cnt >= calc_cycles) next_state = DONE;  // 시간이 다 되면 다음 상태 = DONE
            end
            DONE: begin                                     // DONE 상태
                if (!start_in) next_state = IDLE;           // start 신호가 꺼지면 다음 상태 = IDLE
            end
        endcase
    end
    
    // Output Assignments
    assign busy_out = (tmp_state == RUN);   // 연산 진행중
    assign done_out = (tmp_state == DONE);  // 연산 완료
    
    // Array Control Signals
    assign en = (tmp_state == RUN);         // 동작 신호
    
    // IDLE 상태에서 start 신호가 오면 Clear 수행 (Accumulator 초기화)
    assign clear = (tmp_state == IDLE) && start_in;
//===================================================
// 4) Input Buffer Latching (연산 도중 입력값이 바뀌어도 내부 연산이 꼬이지 않도록 저장)
    always @ (posedge clk) begin
        if (tmp_state == IDLE && start_in) begin     // 대기 상태에서 start 신호가 들어온 경우
            latched_A <= A_in;                       // 현재 A_in 값을 저장하여 연산에 사용
            latched_B <= B_in;                       // 현재 B_in 값을 저장하여 연산에 사용
        end
    end
//===================================================
// 5) Data Skewing Logic
    genvar r, c;
    generate
    // Row Input Control (Matrix A → Row Input)
    for (r = 0; r < rows; r++begin
        always @ (*begin
            a_in_row[r] = 0;                         // 기본값 0
            if (tmp_state == RUN) begin              // RUN 상태인 경우
                int k;
                k = cnt - r;                         // 현재 보내야 할 데이터의 Index
                if (k >= 0 && k < k_dim) begin       // k가 유효한 범위 내에 존재하는 경우에만 데이터 주입
                    a_in_row[r] = latched_A[r][k];
                end
            end
        end
    end
    
    // Column Input Control (Matrix B → Col Input)
    for (c = 0; c < cols; c++begin
        always @ (*begin
            b_in_col[c] = 0;                         // 기본값 0
            if (tmp_state == RUN) begin              // RUN 상태인 경우
                int k;
                k = cnt - c;                         // 현재 보내야 할 데이터의 Index
                if (k >= 0 && k < k_dim) begin       // k가 유효한 범위 내에 존재하는 경우에만 데이터 주입
                    b_in_col[c] = latched_B[k][c];
                end
            end
        end
    end
    endgenerate
//===================================================
// 6) Post-Processing Unit (ReLU + Bias)
    generate
    for (r = 0; r < rows; r++begin
        for (c = 0; c < cols; c++begin
            always @ (*begin
                // Bias 계산 (Signed 연산)
                // 음수인 결과 값이 큰 양수 값으로 인식될 수 있으므로 32-bit로 변환하여 계산
                int tmp_val;
                tmp_val = int'(acc_sum_out[r][c]) - bias;
                    
                // ReLU 계산
                // 음수이면 0, 양수이면 해당 값 그대로 통과
                if (tmp_val < 0) begin
                    C_out[r][c] = 0;
                end else begin
                    C_out[r][c] = tmp_val[acc_width-1:0];
                end
            end
        end
    end
    endgenerate
endmodule
cs

tb_systolic_array_controller_relu.v

SW로 계산한 결과와 HW로 계산한 결과를 비교

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
120
121
122
`timescale 1ns / 1ps
module tb_systolic_controller_relu;
    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)
    parameter k_dim = 4;                    // 공통 차원
    parameter rows = 1;                     // 행 개수
    parameter cols = 4;                     // 열 개수    
    parameter bias = 50;                    // Bias를 50으로 설정 (행렬 곱 결과가 50보다 작으면 0이 됨)
 
    reg clk;
    reg rst;
    reg start_in;                                       // 연산 시작 신호
    wire done_out;
    wire busy_out;
    reg [data_width-1:0] tb_A [0:rows-1][0:k_dim-1];    // 왼쪽에서 들어오는 A 행렬 stream (각 행마다 입력 하나)
    reg [data_width-1:0] tb_B [0:k_dim-1][0:cols-1];    // 위쪽에서 들어오는 B 행렬 stream (각 열마다 입력 하나)
    reg [acc_width-1:0] C_out [0:rows-1][0:cols-1];     // HW 계산 결과
    
    int tb_C [0:rows-1][0:cols-1];                      // SW 계산 결과
    int err_cnt;                                        // 통계용 변수
    
    systolic_controller_relu # (    // 이 파일에서 설정한 값을 pe_systolic_cell 파일에도 적용하기 위함
        .data_width(data_width),
        .mul_width(mul_width),
        .acc_width(acc_width),
        .k_dim(k_dim),
        .rows(rows),
        .cols(cols),
        .bias(bias)
    ) dut (                         // unit_core_array 이름으로 Instance
        .clk(clk),
        .rst(rst),
        .start_in(start_in),
        .done_out(done_out),
        .busy_out(busy_out),
        .A_in(tb_A),
        .B_in(tb_B),
        .C_out(C_out)
    );
    
    initial begin
        clk <= 0;
        forever #5 clk <= ~clk;     // 주기 10ns
    end
    
    initial begin
        rst <= 0;                   // 초기화
        start_in <= 0;              // 초기화
        tb_A = '{default:0};        // 초기화
        tb_B = '{default:0};        // 초기화
        err_cnt = 0;                // 초기화
        
        #30
        rst <= 1;                   // 동작 신호
        $display("===== Controller Verification with ReLU (Bias=%0d) =====", bias);
        
        for (int iter = 0; iter < 5; iter++begin
            // Random Data Setup
            for (int r = 0; r < rows; r++)
                for (int k = 0; k < k_dim; k++)
                    tb_A[r][k] = $urandom_range(0, (1<<data_width)-1);
            
            for (int k = 0; k < k_dim; k++)
                for (int c = 0; c < cols; c++)
                    tb_B[k][c] = $urandom_range(0, (1<<data_width)-1);
        
            // SW 계산
            for (int r = 0; r < rows; r++begin
                for (int c = 0; c < cols; c++begin
                    int tmp_sum;
                    tmp_sum = 0;
                    
                    // MAC 계산
                    for (int k = 0; k < k_dim; k++)
                        tmp_sum += tb_A[r][k] * tb_B[k][c];
                        
                    // Bias 계산
                    tmp_sum = tmp_sum - bias;
                    
                    // ReLU
                    if (tmp_sum < 0) tb_C[r][c] = 0;
                    else tb_C[r][c] = tmp_sum;
                end
            end
            
            // HW 계산
            @ (posedge clk);
            start_in = 1;
            @ (posedge clk);
            start_in = 0;
            
            // 계산 완료 대기
            wait(done_out == 1);
            
            // SW와 HW 결과 비교
            $display("[Iter %0d] Checking Result", iter + 1);
            for (int r = 0; r < rows; r++begin
                for (int c = 0; c < cols; c++begin
                    if (C_out[r][c] !== tb_C[r][c]) begin
                        $display("[ERROR] [%0d][%0d] : DUT=%0d, TB=%0d", r, c, C_out[r][c], tb_C[r][c]);
                        err_cnt++;
                    end else begin
                        // 0이 나오면 ReLU가 동작하여 음수 값이 0
                        if (C_out[r][c] == 0) $display("[PASS] [%0d][%0d] : DUT=%0d, TB=%0d (ReLU Activated)", r, c, C_out[r][c], tb_C[r][c]);
                        else $display("[PASS] [%0d][%0d] : DUT=%0d, TB=%0d", r, c, C_out[r][c], tb_C[r][c]);
                    end
                end
            end
            
            if (err_cnt == 0) $display("[Iter %0d] FINAL RESULT : PASS!", iter + 1);
            else $display("[Iter %0d] FINAL RESULT : FAIL (# of Error : %0d)", iter + 1, err_cnt);
            
            // 다음 명령 대기
            @ (posedge clk);
        end
    
        $display("===== All Tests Finished =====");
        $finish;
    end
endmodule
 
cs

Result

"#30 rst <= 1"에 의해 30ns에 rst가 1로 올라가는 것을 확인 가능
"start_in" 신호가 "1"이 된 후 1 Cycle 뒤에 "busy_out"이 "1"이 되는 것을 보아 RUN 상태에 들어간 것을 유추 가능
SW 결과인 "tb_C"과 HW 결과인 "C_out"의 결과가 같아 Console 창에 PASS가 출력되는 것을 확인 가능
계산이 완료되어 "done_out"이 "1"이 되고, 이후 "start_in"이 다시 "1"이 될 때까지 대기하다 "1"이 되고 나서 연산 시작
Bias를 뺀 값이 0 이하인 값이 없어 (ReLU Activated) 문구는 확인 불가

위의 출력 결과 MAC 값이 대략 40000 ~ 100000에 해당하여, ReLU를 확인하기 위해 Bias를 60000으로 변경
MAC 값으로 0이 출력된 경우는 MAC 값에 Bias를 뺀 결과가 음수여서 ReLU가 적용된 것
MAC 값으로 0인 경우 ReLU Activated가 출력되는 것을 확인 가능

Reference

▶ Design Processing Element
https://hecess.blogspot.com/2026/01/minit-npu-rtl-design-processing-element.html

▶ 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

댓글

  1. NPU Study의 스터디장 공대삼쩜영 입니다
    요약 정리는 잘 하신것 같고 추가 학습을 위해 질문드립니다.
    1. Activation Func 은 ReLU 안쓰고 Sigmoid 쓸 수도 있는데 어떤 차이가 있을지? 뭐가 더 나은지 어떻게 결정하는지?
    2. 변수 선언 시 reuse 를 위해 parameter 로 datawidth 사용해주셨는데요. define 으로 사용하는 것과 parameter 로 사용하는것에 대한 차이점과 어떤걸 더 선호하는지?

    답글삭제
    답글
    1. 1. Sigmoid vs ReLU
      ▶ Sigmoid 함수
      · 특징 : 입력을 0~1 사이의 값으로 압축 & 완만한 S자 곡선을 그림
      · AI 학습 관점
      · 장점 : 출력이 확률과 유사하여, 이진 분류의 최종 출력층에 주로 사용
      · 단점 : 입력값이 아주 크거나 작으면 기울기가 0에 가까워져, Deep Layer로 갈수록 학습 불가
      · HW 구현 관점
      · 면적↑, 가격↑, Timing 맞추기 어려움, 보통 Lookup Table 방식 또는 테일러 급수 근사 사용

      ▶ ReLU 함수
      · 특징 : 0이하의 값은 0, 0보다 크면 입력값 그대로 출력
      · AI 학습 관점
      · 장점 : 양수 구간에서 기울기가 항상 1, 학습 속도가 매우 빠름
      · 단점 : 음수는 무조건 0
      · HW 구현 관점
      · 단순히 부호 bit(MSB)만 확인 → 면적↓, 가격↓

      위와 같은 특징으로 Hidden Layer에서는 무조건 ReLU를 우선 고려하고, Output Layer에서는 분류에서는 Sigmoid, 값 예측에서는 ReLU를 사용합니다.

      2. define vs parameter
      ▶ define
      · Global 범위로, 한 번 선언되면 Compile되는 모든 파일에 영향
      → 다른 파일에서 또 define을 하면 충돌하거나 덮어씌워지고, Instance별 변경이 불가

      ▶ parameter
      · Local 범위로, 해당 Module 안에서만 유효
      → Instance화 할 때 외부에서 값을 변경 가능 (= Re-Use)

      위와 같은 특징으로 절대 변하지 않는 시스템 상수 외에는 parameter로 설정하여, 다른 사람이 사용 시 내부 코드를 수정하지 않고 parameter만 변경하여 사용할 수 있도록 합니다.

      삭제

댓글 쓰기

이 블로그의 인기 게시물

[Mini-NPU RTL] NN Reference Model

[Mini-NPU RTL] TPU (Study Paper)