범용적인 NPU 만들기(8) - 코드 및 설명 - Relu, maxpooling

최현우·2023년 7월 23일
0
post-thumbnail

Relu는 애초에 매우 간단한 구조라 이전에 설명했던 것과 동일한데 maxpooling은 이전에 생략한게 좀 있었고 또 중간중간 수정한 부분이 있어 설명이 필요하다.


Relu

module relu(in, en_relu, out);

input [7:0] in;
input en_relu;
output [7:0] out;

wire sel;

assign sel = ~(en_relu & in[7]);

assign out[7] = in[7] & sel;
assign out[6] = in[6] & sel;
assign out[5] = in[5] & sel;
assign out[4] = in[4] & sel;
assign out[3] = in[3] & sel;
assign out[2] = in[2] & sel;
assign out[1] = in[1] & sel;
assign out[0] = in[0] & sel;

endmodule

Relu 모듈은 추후 BN 기능을 추가할 예정이나 아직 곱셈기를 설계하지 못했기 때문에 일단은 Relu 기능만을 수행하는 상태로 두었다.


maxpooling

maxpooling은 전체적인 구조는 변하지 않았지만 몇가지 부분이 추가되었다.

module maxpooling(in, en, en_mp, out, out_en, clk, reset);

input signed [7:0] in;
input en, en_mp, clk, reset;
output signed [7:0] out;
output out_en;

wire en_count, en2;
assign en_count = en & en_mp;
assign en2 = en & (~en_mp);

//comparator
reg comparator_out;
wire signed [7:0] mux_out;
always @(*) begin
    if (mux_out > in) comparator_out = 1'b0;
    else comparator_out = 1'b1;
end

reg [1:0] count;
reg count_out;
// counter
always @(posedge clk) begin
    if (!reset) begin
        count <= 0;
        count_out <= 0;
    end
    else if (en_count == 1) begin
        if (count == 2'b11) begin
            count <= count + 1'b1;
            count_out <= 1;
        end
        else begin
            count <= count + 1'b1;
            count_out <= 0;
        end
    end
    else count_out <= 0;
end


//out_en
wire en2_d;
D_FF1 D_en2(en2, en2_d, clk, reset);
assign out_en = count_out | en2_d;


//mux_in
wire com_en_and;
assign com_en_and = comparator_out & en;

wire mux_in_s;
assign mux_in_s = com_en_and | en2;

wire signed [7:0] mux_in;
assign mux_in = mux_in_s ? in : mux_out;

D_FF8 D_in(mux_in, out, clk, reset);

/*wire en_mp_d_i;
D_FF1 D_en_mp(~(en_mp), en_mp_d_i, clk, reset);*/

//mux_out
assign mux_out = (out_en) ? 8'b1000_0000 : out;



endmodule

컨트롤을 위한 wire들이 많아서 이해하기 어려울 수 있다. 설명이 불필요한 몇몇 중간 wire들을 생략하고 각 부분들을 나눠서 truth table을 보면 이해하기 쉽다.

먼저 mux_in_s에 대한 truth table은 다음과 같다.

mux_in_s는 FF에 이전의 결과값(혹은 -128)과 현재 입력값 중 무엇이 FF에 저장될지를 결정하는 신호이다.

그렇기에 en = 0 인 경우 무효한 값이 FF에 저장이 되는 것을 막기 위해서 mux_in_s = 0이 되어 이전의 out값을 유지해야한다.

en_mp의 경우 maxpooling 기능을 사용하느냐 마느냐를 결정하는 신호이다. 만약 en_mp = 0이라면 이는 maxpooling을 사용하지 않는다는 뜻이므로 en = 1일 때 매사이클마다 FF의 값이 input 값으로 바뀌어야한다.

그에 따라 위와 같은 truth table을 만족하게 회로를 설계했다.

결과적으로 en = 0 일때는 무효한 값이 FF에 저장되지 않고 en = 1, en_mp = 1일 때 comparator의 결과에 따라 큰 값이 저장되게 하며 en = 1, en_mp = 0일 때는 항상 input이 저장되게 된다.


다음은 out_en에 대해서이다.

out_en은 결과값이 유효한 값임을 나타내는 동시에 mux_out의 값을 정한다.

out_en은 counter_out이나 en2_d 둘 중 하나라도 1인 경우 1이되는데 결국 이는 en_mp = 1일 때 en = 1인 신호가 4번 들어왔을 때, 즉 maxpooling 기능이 켜져 4번 유효한 값이 들어와 유효한 output이 출력될 때

혹은 en_mp = 0로 기능이 꺼져 en = 1인 신호가 들어올때마다 유효한 output이 출력될 때 1로 출력된다는 뜻이 된다.

out_en은 또한 mux_out에 -128이 들어갈지 out이 들어갈지를 결정하는 신호이다.

out이 이전 mux_in의 결과임을 고려한다면 out_en은 결국 이전의 계산값을 다시 input과 비교하는데 사용할 것인지 아니면 처음부터 다시할 것인지를 결정한다고 할 수 있다.

그래서 결국 out_en = 1, 즉 유효한 결과가 나왔다는건 한주기의 계산(max값 찾기)이 끝났다는 뜻이고 다시 계산을 처음부터 시작해야한다는 뜻이기에 mux_out에 -128이 할당되도록 한다.


maxpooling 모듈은 테스트벤치에서 잘 작동해서 별로 수정이 많지 않았었는데 포스팅을 하면서 구조를 다시 한번 검토해보니 불필요한 부분이나 오류를 발생시킬 수도 있는 부분이 있어서 수정했다.

사실 해당 모듈을 작성한지 오래돼서 이해가 잘 되지 않아 의아한 부분이 몇군데 있었는데 그 부분들이 수정한 부분이었다. 회로설계뿐만이 아니라 다른 일에서도 나중에 다시 보면 관점이 초기화돼서 그런지 그때 보이지 않던 부분들이 보이는 경우가 꽤 많은 것 같다.

2개의 댓글

comment-user-thumbnail
2023년 7월 23일

정보 감사합니다.

1개의 답글