반응형

라인 버퍼를 사용해서 Conv2d 모듈을 생성해 보도록 한다.(unsigned )

 

먼저 Overview 를 작성한다.

 

Convolution 자체는 어떤 값을 커널값과 곱해서 나온 결과를 다시 사용하는 개념이 된다.

 

결국 곱셈 연산을 하게 되는 것이다.

따라서 연산을 할 위치만 조정을 해 주면 된다.

 

나는 다음과 같이 작성해 봤는데, 패딩이 1이고 커널 사이즈가 3인데 이걸 타겟으로 해서 변형하면 아마 작동을 안 할수도 있다.

초기 버전 파일을 가져와서 커널 쪽 오류가 있을 것 같다. 

module conv2d#
	(
		parameter DATA_WIDTH = 8,  // 데이터의 비트 폭
    	parameter IMG_WIDTH = 5,   // 입력 이미지 가로 크기
    	parameter IMG_HEIGHT = 5,  // 입력 이미지 세로 크기
    	parameter PADDING = 1,      // 패딩 크기
    	parameter KERNEL_SIZE = 3   // 커널 크기
	)
	(
		input i_Clk,
		input i_Rst,
		input i_start,
		input [DATA_WIDTH-1:0] i_pixel,
		input [DATA_WIDTH * (KERNEL_SIZE **2)-1 : 0] i_kernel,
		output [DATA_WIDTH-1:0] o_pixel,
		output reg o_done,
		output o_valid
	);


wire [DATA_WIDTH * 2 - 1:0] sum;
localparam 	LINE_BUFF_SIZE = (IMG_WIDTH + 2 * PADDING) * KERNEL_SIZE + 1;
parameter 	IDLE = 0,
			WORK = 1;

// FF registers
reg c_State, n_State;
reg [DATA_WIDTH-1:0] c_line_buff[0:LINE_BUFF_SIZE-1];
reg [DATA_WIDTH-1:0] n_line_buff[0:LINE_BUFF_SIZE-1];
reg [$clog2(IMG_WIDTH):0] c_x, n_x;
reg [$clog2(IMG_HEIGHT):0] c_y, n_y;

reg [DATA_WIDTH-1:0] window[KERNEL_SIZE-1:0][KERNEL_SIZE-1:0];


integer i, j;
integer k, m;
always@(posedge i_Clk, negedge i_Rst)
	if(!i_Rst) begin
		c_State <= 0;
		c_x <= 0;
		c_y <= 1;
		for(i = 0; i<LINE_BUFF_SIZE; i=i+1) 	c_line_buff[i] <= 0;
		for(k=0; k < KERNEL_SIZE; k=k+1) for(m=0; m<KERNEL_SIZE; m=m+1) window[k][m] <= 0;

	end else begin
		c_State <= n_State;
		c_x <= n_x;
		c_y <= n_y;
		for(i = 0; i<LINE_BUFF_SIZE; i=i+1) 	c_line_buff[i] <= n_line_buff[i];
		for(k=0; k < KERNEL_SIZE; k=k+1) for(m=0; m<KERNEL_SIZE; m=m+1) window[k][m] <= i_kernel[DATA_WIDTH * (k*KERNEL_SIZE + m) +: DATA_WIDTH];
	end



always@* begin
	// match n registers to c registers
	n_State <= c_State;
	n_x <= c_x; 
	n_y <= c_y;
	for(i = 0; i<LINE_BUFF_SIZE; i=i+1) 	n_line_buff[i] <= c_line_buff[i];
	
	o_done <= 0;
	// o_valid <= 0;

	case(c_State)
		IDLE: begin
			n_x <= 0;
			n_y <= 1;
			for(i = 0; i<LINE_BUFF_SIZE; i=i+1) 	n_line_buff[i] <= 0;
			if(i_start) begin
                // for data sync, if input data and start sign comes together
                n_line_buff[0] <= i_pixel;
                n_x <= c_x + 1;
				n_State <= WORK;
            end
		end

		WORK: begin
			if(c_x == IMG_WIDTH)
			begin
				n_line_buff[2] <= 0;
				n_line_buff[1] <= 0;
				if(c_y >= IMG_HEIGHT) 	n_line_buff[0] <= 0;
				else 					n_line_buff[0] <= i_pixel;
				for(i = 3; i < LINE_BUFF_SIZE; i = i + 1) 	n_line_buff[i] <= c_line_buff[i-3];
			end
			else begin
				for(i = 1; i < LINE_BUFF_SIZE; i = i+1)	n_line_buff[i] <= c_line_buff[i-1];
				if(c_y > IMG_HEIGHT) 	n_line_buff[0] <= 0;
				else 					n_line_buff[0] <= i_pixel;
			end


			if(c_x < IMG_WIDTH) 	n_x <= c_x + 1;
			else begin
				n_x <= 1;
				if(c_y < IMG_HEIGHT + 2) 	n_y <= c_y + 1;
				else 						n_y <= 1;
			end

			if(c_x == IMG_WIDTH && c_y == IMG_HEIGHT + 2) begin
				o_done <= 1;
				n_State <= IDLE;
			end
		end
	endcase
end

genvar kernel_row, kernel_col;
wire [DATA_WIDTH-1:0] mult_result[KERNEL_SIZE-1:0][KERNEL_SIZE-1:0]; // 곱셈 결과 저장, 3x3
wire [DATA_WIDTH-1:0] partial_sum[KERNEL_SIZE*KERNEL_SIZE-1:0]; // 중간 합산 저장

generate
    for (kernel_row = 0; kernel_row < KERNEL_SIZE; kernel_row = kernel_row + 1) begin : row_gen
        for (kernel_col = 0; kernel_col < KERNEL_SIZE; kernel_col = kernel_col + 1) begin : col_gen
            // 각 커널 위치의 곱셈 결과
            assign mult_result[kernel_row][kernel_col] = 
                (c_y >= 2 && c_x == IMG_WIDTH) ?
                (window[kernel_row][kernel_col] * c_line_buff[3*(IMG_WIDTH + 2*PADDING)-2 - kernel_col - ((IMG_WIDTH + 2*PADDING)*kernel_row)]) :
                (window[kernel_row][kernel_col] * c_line_buff[3*(IMG_WIDTH + 2*PADDING) - kernel_col - ((IMG_WIDTH + 2*PADDING)*kernel_row)]);
            
            // 곱셈 결과를 1차원 배열로 정리
            assign partial_sum[kernel_row * KERNEL_SIZE + kernel_col] = mult_result[kernel_row][kernel_col];
        end
    end
endgenerate

// partial_sum 배열의 값을 모두 합산하여 sum에 할당
assign sum = ((c_y >= 2 && c_x == IMG_WIDTH) || c_y >= 3) ? 
             partial_sum[0] + partial_sum[1] + partial_sum[2] + 
             partial_sum[3] + partial_sum[4] + partial_sum[5] + 
             partial_sum[6] + partial_sum[7] + partial_sum[8] : 0;

assign o_pixel = sum;
assign o_valid = ((c_y >= 2 && c_x == IMG_WIDTH) || c_y >= 3) && !o_done;


endmodule

 

 

이건 Testbench 이다.

`timescale 1ns / 1ps

module tb_conv2d;

    // Parameters
    parameter DATA_WIDTH = 8;
    parameter IMG_WIDTH = 5;
    parameter IMG_HEIGHT = 5;
    parameter PADDING = 1;
    parameter KERNEL_SIZE = 3;

    // Inputs
    reg i_Clk;
    reg i_Rst;
    reg i_start;
    reg [DATA_WIDTH-1:0] i_pixel;
    reg [DATA_WIDTH * (KERNEL_SIZE ** 2) - 1:0] i_kernel;
    reg [DATA_WIDTH-1:0] image[0:IMG_HEIGHT-1][0:IMG_WIDTH-1];

    // Outputs
    wire [DATA_WIDTH-1:0] o_pixel;
    wire o_done;
    wire o_valid;

    // Instantiate the conv2d module
    conv2d #(
        .DATA_WIDTH(DATA_WIDTH),
        .IMG_WIDTH(IMG_WIDTH),
        .IMG_HEIGHT(IMG_HEIGHT),
        .PADDING(PADDING),
        .KERNEL_SIZE(KERNEL_SIZE)
    ) uut (
        .i_Clk(i_Clk),
        .i_Rst(i_Rst),
        .i_start(i_start),
        .i_pixel(i_pixel),
        .i_kernel(i_kernel),
        .o_pixel(o_pixel),
        .o_done(o_done),
        .o_valid(o_valid)
    );

    // Clock generation
    initial begin
        i_Clk = 0;
        forever #5 i_Clk = ~i_Clk;  // 100MHz 클럭

        
    end


    integer x, y;
    // Test sequence
    initial begin
        // Initialize inputs
        i_Rst = 0;
        i_start = 0;
        i_pixel = 0;
        i_kernel = {8'd1, 8'd2, 8'd1,   // 커널 정의 (3x3)
                     8'd2, 8'd1, 8'd2,
                     8'd1, 8'd2, 8'd1};

        // Reset the module
        #5;
        i_Rst = 1;
        #5;

        // Start the convolution operation
        i_start = 1;

        image[0][0] = 8'd1; image[0][1] = 8'd2; image[0][2] = 8'd3; image[0][3] = 8'd4; image[0][4] = 8'd1;
        image[1][0] = 8'd2; image[1][1] = 8'd3; image[1][2] = 8'd4; image[1][3] = 8'd1; image[1][4] = 8'd2;
        image[2][0] = 8'd3; image[2][1] = 8'd4; image[2][2] = 8'd1; image[2][3] = 8'd2; image[2][4] = 8'd3;
        image[3][0] = 8'd4; image[3][1] = 8'd1; image[3][2] = 8'd2; image[3][3] = 8'd3; image[3][4] = 8'd4;
        image[4][0] = 8'd1; image[4][1] = 8'd2; image[4][2] = 8'd3; image[4][3] = 8'd4; image[4][4] = 8'd1;

        // Send pixel data
        for (y = 0; y < IMG_HEIGHT; y = y + 1) begin
            for (x = 0; x < IMG_WIDTH; x = x + 1) begin
                i_pixel = image[y][x]; 
                #10;  // 픽셀 전송 대기
                i_start = 0;
            end
        end
        

        // Wait for the process to complete
        wait(o_done);
        # 100;
        // stop simulation
        $stop;
    end

    // Monitor outputs
    initial begin
        $monitor("Time: %0t | o_pixel: %d | o_done: %b | o_valid: %b", 
                 $time, o_pixel, o_done, o_valid);
    end

endmodule

 

근데 convolution 연산 자체는 입력 이미지 사이즈와 채널이 크기 때문에, 라인 버퍼를 사용하는 방식은 권장되지 않는 것 같다.

대충 버퍼 사용되는 양만 해도 IMG_WIDTH * 3 * output channel 개 사용해야 되니 정말 많다(한 conv2d 모듈 내에서)

이를 이제 다충 채널에 대해서 쓰려면 generate 문을 사용해서 여러개 생성하고, kernel 입력 값을 넣어주게 되면 output 생성이 가능하겠다.

 

간단한 line buffer 를 사용한 conv2d 구현을 알아보았다.

반응형

'Verilog' 카테고리의 다른 글

[Verilog] CNN top module (w. dense)  (0) 2024.11.05
[Verilog] maxpool2d w. line buffer  (0) 2024.10.31
[Verilog] OV7670 카메라 모듈과 FPGA 통신  (0) 2024.09.09
[Verilog] OV7670 - more2  (0) 2024.05.28
[Verilog] SCCB & OV7670 - more1  (0) 2024.05.28

+ Recent posts