범용적인 NPU 개발기(5) - 코드 및 설명(1) - arithmetic_core

최현우·2023년 7월 16일
0

코드 작성하고 디버깅하느라 이전 포스팅을 올리고 많은 시간이 지났다.

현재 코드 작성을 완료하고 테스트벤치 등을 통해 functionality check를 끝낸 상태인데 본래라면 synthesis를 하고 그 결과에 따라 더 최적화를 진행하겠지만 아쉽게도 나는 더이상 재학생이 아니라 학교의 synthesis 툴을 사용할 수 없다...

그리하여 기본적인 아키텍처에 대한 코드 작성을 여기서 완료하고 이후에는 곱셈기+덧셈기의 상세 설계를 수행할 예정이다.

물론 현재 작성한 기본 아키텍처의 코드를 올리고 설명하는 것이 선행될 것이다.

현재 구상하는 NPU의 계층 구조는 다음과 같은데 NPU 안에 control part, arithmatic part, memory part가 존재하고 현재 구현한 부분은 이 중에서 arithmatic part만이다.

arithmatic part는 여러개의 동일한 arithmatic core로 이루어져 있으며 각 코어들은 같은 input feature map을 공유하지만 다른 weight를 입력받는다. 즉, 동시에 다른 채널을 계산한다.


오늘은 이 arithmatic core의 내부 구조에 대해 설명하고 다음 포스팅에서는 내부의 세부 모듈들에 대해 설명할 예정인데 이미 사전에 아키텍처 포스팅에서 설명한 바 있지만 해당 포스팅에서는 가독성을 위해 여러가지 생략했고 또 코드를 작성하면서 몇가지 수정된 부분들이 있다.

코드 및 설명 포스팅에서는 해당 모듈의 베릴로그 코드와 상세한 아키텍처를 올리고 설명할 것이다. 이번 포스팅에서는 arithmetic_core를 다룰 것인데 사실 해당 모듈은 세부 모듈을 전부 모아둔 것에 가까워서 특별한 기능이 없는지라 설명이 그리 길지 않다.


module arithmetic_core (in, weight, bias, bound_level, step, en,
                            en_relu, en_mp, 
                            out, out_en,
                            clk, reset);
    parameter cell_bit = 8;
    parameter N_cell = 9;
    parameter biasport = 16;
    parameter outport = 8;

    input [cell_bit*N_cell-1:0] in;
    input [cell_bit*N_cell-1:0] weight;
    input signed [biasport-1:0] bias;
    input [1:0] bound_level;
    input [2:0] step;
    input en, en_relu, en_mp;
    input clk, reset;

    output signed [outport-1:0] out;
    output out_en;

    
//////////////// pe
    wire signed [outport-1:0] out_pe;
    wire out_en_pe;

    PE P0(in, weight, bias, bound_level, step, en, out_pe, out_en_pe, clk, reset);
///////////////


/////////////////// clk+2


//////////////// FF for other input
    wire en_relu_d, en_relu_d2;

    D_FF1 D_en_relu0(en_relu, en_relu_d, clk, reset);
    D_FF1 D_en_relu1(en_relu_d, en_relu_d2, clk, reset);

    wire en_mp_d, en_mp_d2;

    D_FF1 D_en_mp0(en_mp, en_mp_d, clk, reset);
    D_FF1 D_en_mp1(en_mp_d, en_mp_d2, clk, reset);
////////////////


////// relu
    wire signed [outport-1:0] out_relu;
    relu r0(out_pe, en_relu_d2, out_relu);
//////



////////// maxpooling
    maxpooling m0(out_relu, out_en_pe, en_mp_d2, out, out_en, clk, reset);


    ///// out < clk+3

endmodule

각 input에 대해 설명하자면 (clk과 reset은 따로 언급하지 않겠다.) in, weight, bias는 convolution에 필요한 input feature map, weight, bias을 뜻한다.

bound_level은 상위 몇번 비트부터 사용할 것인지를 결정하는 input인데 이에 대해선 processing_element 포스팅에서 상세하게 다루도록 하겠다.

step은 몇 clk 동안 계산을 할 것인지를 결정하는 input으로 필터가 9개의 weight를 가진 경우 1 clk 만으로 모든 계산을 마칠 수 있으므로 0, 16개의 경우 두 사이클의 결과값을 합쳐서 내보내야하므로 1. 27의 경우는 3사이클이 필요하므로 2. 이와같이 몇사이클의 값을 합산해서 내보낼 것인가를 결정하는 input으로 필터의 weight의 개수에 따라 달라진다.

en은 해당 input들이 유효한지를 알려주는 input이다.

여기까지의 input들이 processing_element에 들어간다. 즉 processing_element가 convolution을 수행하는 모듈이라고 보면 된다. 이름을 잘 지었어야했는데 실수했다.

나머지 en_relu, en_mp의 경우 relu를 수행할 것인지, maxpooling을 수행할 것인지를 알려주는 input으로 processing_element가 결과값을 출력하기 까지 2clk이 걸리므로 해당 값들도 flip flop을 통해 2clk 만큼 지연되어야하기에 각각 2개의 FF이 연속해서 배치되어 있다.


이렇게 2clk 후 출력된 값들은 이후의 모듈들에 입력되는데 processing_element(이하 pe)의 결과값은 relu 모듈로 들어가 en_relu_d2 (en_relu가 2clk 지연된 값)에 따라 relu을 수행하거나 아니면 그대로 내보내 maxpooling 모듈로 보낸다.

relu 모듈은 FF이 존재하지 않는 combination 회로로 입력된 clk내에 결과를 내보낸다.

maxpooling은 relu로부터의 결과값과 pe로부터의 out_pe_en을 받는데 relu가 clk을 소모하지 않고 따로 en을 출력하지도 않아 relu로부터 받는 en값이라고 봐도 무방하다. maxpooling 모듈은 out_pe_en을 이용해 받은 input들이 유효한 값인지 아닌지를 판단한다.

maxpooling은 out_pe_en, en_mp_d2 (en_mp가 2clk 지연된 값)에 따라 out_relu을 받아들여 계산을 수행하고 out으로 값을 내보내며 out_en으로 해당 값이 유효한지 아닌지를 표시한다. 이 둘이 arithmatic core의 output이다.


결과적으로 데이터의 흐름은 input -> processing_element -> relu -> maxpooling -> output 이 된다. 이는 relu, maxpooling 기능이 켜지는지 아닌지에 상관없다. en_relu, en_mp가 0으로 이 둘이 비활성화되어도 이들이 그에 맞는 기능을 수행하지 않을 뿐이지 데이터가 이들을 거쳐가는 것은 변함없다.

모듈 사이에 존재하는 out_pe_en은 각 모듈이 독립적으로 기능할 수 있게 해 설계 난이도를 줄이는데 큰 도움을 주었다. 또한 en 신호를 사용함으로써 고려해야하는 경우의 수가 줄었기( 예) maxpooling 모듈은 step 값에 영향을 받지 않음 )에 결과적으로 maxpooling 모듈의 면적을 줄이는데도 도움을 주지 않았을까 추측한다.

0개의 댓글