반응형
라인 버퍼를 사용해서 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 |