반응형
공부를 하면서 포스팅을 하고 있는 입장이기에, 코드의 정확성은 보장하지 못하거니와
더욱 효율적으로 코드를 작성할 방법도 굉장히 많을 것이다.
하지만 간단한 예시를 들어 모듈을 만들어 보고자 하는 것이 목적이기에 verilog 로 cnn top 모듈을 한 번 만들어 보았다.
참고로, bias 를 더하는게 없다. 직접 만들어 보는 것도 좋을 것 같다.
전에 conv2d 와 maxpool 레이어는 작성을 했는데, dense 층 같은 경우에는 포스팅을 하지 않았었다.
module dense #
(
parameter DATA_WIDTH = 16,
parameter IN_SIZE = 10, // test with small one
parameter OUT_SIZE = 5
)
(
input i_clk,
input i_rst,
input i_start,
input wire signed [DATA_WIDTH-1:0] i_data,
output wire signed [DATA_WIDTH-1:0] o_data,
output o_valid,
output reg o_done
);
// 한 클럭에 입력 데이터가 모든 가중치값과 곱해짐
// 이를 in size 번 만큼 반복
localparam IDLE = 0,
WORK = 1,
DATA_OUT = 2;
reg [1:0] c_state, n_state;
reg signed [DATA_WIDTH - 1:0] weight[0:OUT_SIZE-1];
reg signed [DATA_WIDTH - 1:0] c_result[0:OUT_SIZE-1], n_result[0:OUT_SIZE-1];
reg [$clog2(IN_SIZE):0] c_cnt, n_cnt;
integer j;
initial
begin
// fixed(1)
for(j=0; j<OUT_SIZE; j=j+1) weight[j] = 16'h0100 + j * 10;
end
integer i;
always@(posedge i_clk, negedge i_rst)
if(!i_rst) begin
c_state <= 0;
for(i = 0; i < OUT_SIZE; i=i+1) c_result[i] <= 0;
c_cnt <= 0;
end else begin
c_state <= n_state;
for(i = 0; i < OUT_SIZE; i=i+1) c_result[i] <= n_result[i];
c_cnt <= n_cnt;
end
always@* begin
n_state = c_state;
n_cnt = c_cnt;
o_done = 0;
for(i = 0; i < OUT_SIZE; i=i+1) n_result[i] = c_result[i];
case(c_state)
IDLE: begin
n_cnt = 0;
for(i = 0; i < OUT_SIZE; i=i+1) n_result[i] = 16'b0;
if(i_start) begin
n_state = WORK;
for(i = 0; i < OUT_SIZE; i=i+1) begin
n_result[i] = i_data * weight[i]; // start flag comes with valid data
n_cnt = 1;
end
end
end
WORK: begin
n_cnt = c_cnt + 1;
if(c_cnt == IN_SIZE-1) begin
n_state = DATA_OUT;
n_cnt = 0; // data out counter;
end
// mac unit
for(i = 0; i < OUT_SIZE; i=i+1) begin
n_result[i] = c_result[i] + i_data * weight[i];
end
end
DATA_OUT: begin
n_cnt = c_cnt + 1;
if(c_cnt == OUT_SIZE-1) begin
n_state = IDLE;
o_done = 1;
end
end
endcase
end
assign o_valid = c_state == DATA_OUT;
assign o_data = c_state == DATA_OUT ? c_result[c_cnt] : 0;
endmodule
dense 의 경우 하드코드된 가중치를 준다.
하나의 값이 들어가서 모두 곱해지는 단순한 형태이다.
CNN top file 에 대해서 한 번 보도록 하자.
module CNN_top_valid_tester#
(
parameter DATA_WIDTH = 16,
parameter CONV1_OUT_CH = 4
)
(
/* Data input from bram */
input i_clk, // 25Mhz input clk (vga clk)
input i_rst,
input i_start, // 채널 하나씩 받아야 함(i_start 신호 ci개 필요)
input [DATA_WIDTH-1:0] i_data, // RGB565 data, data input : 6*6 image
/* output inform */
output [DATA_WIDTH-1:0] o_is_apple,
output [DATA_WIDTH-1:0] o_apple_pix_length,
output reg c_done
);
localparam INPUT_WIDTH = 128,
INPUT_HEIGHT = 128;
localparam ONE_KERNEL_SIZE = DATA_WIDTH * 3 * 3; // 16 * 9 = 144
localparam IDLE = 0,
CONV1 = 1,
MAXPOOL1 = 2,
DENSE1 = 3;
reg [1:0] c_state, n_state;
wire [3*DATA_WIDTH-1:0] rgb_o_data;
/*============================\\
R E G
\\============================*/
wire [CONV1_OUT_CH*DATA_WIDTH*9-1:0] r_conv1_kernel; // ci(3) * co(4) * 9 * bit-width(16) = 576 * 3 (ci 없애고 클럭 늘림)
// signed?
wire [CONV1_OUT_CH*DATA_WIDTH-1:0] conv1_o_pixel; // co(4) * data_width(16) = 64
wire [CONV1_OUT_CH-1:0] conv1_o_done; // co(4)
wire [CONV1_OUT_CH-1:0] conv1_o_valid;
wire maxpool1_i_start;
wire [CONV1_OUT_CH*DATA_WIDTH-1:0] maxpool1_o_data; // co(4) * data_width(16) = 64
wire [CONV1_OUT_CH-1:0] maxpool1_o_done;
wire [CONV1_OUT_CH-1:0] maxpool1_o_valid; // co(4)
reg [$clog2((INPUT_WIDTH/2) * (INPUT_HEIGHT/2))-1:0] c_conv1_cnt, n_conv1_cnt; // memory counter ????????????????????????????????????? width/2*height*2
wire [CONV1_OUT_CH*DATA_WIDTH-1:0] conv1_o_bram_data; // co(4) * data_width(16) = 64
wire [$clog2(3*DATA_WIDTH)-1:0] rgb_slicer; // 안하니까 오류나는데
wire [CONV1_OUT_CH*DATA_WIDTH-1:0] mac_mem_o_bram_data;
reg [$clog2(CONV1_OUT_CH)-1:0] c_rom1_i_cnt, n_rom1_i_cnt;
reg [$clog2(INPUT_WIDTH * INPUT_HEIGHT)-1:0] c_conv1_mac_cnt, n_conv1_mac_cnt;
reg [$clog2(INPUT_WIDTH * INPUT_HEIGHT)-1:0] c_mac_mem_out_cnt, n_mac_mem_out_cnt;
wire dense1_i_start;
wire dense1_o_valid;
wire [DATA_WIDTH-1:0] dense1_o_data;
reg [$clog2((INPUT_WIDTH/2) * (INPUT_HEIGHT/2))-1:0] c_conv1_rd_mem_addr, n_conv1_rd_mem_addr;
reg [$clog2(CONV1_OUT_CH)-1:0] c_dense1_cnt, n_dense1_cnt;
reg [DATA_WIDTH-1:0] dense1_i_data;
wire dense1_o_done;
reg [6*DATA_WIDTH-1:0] buf_dense_out; // dense out 개수
reg n_done;
/*============================\\
S U B _ M O D U L E
\\============================*/
rgb565_divider #
(
.DATA_WIDTH(DATA_WIDTH)
)
rgb565div
(
.i_data(i_data),
.o_data(rgb_o_data) // divided to rgb_o_data[DATA_WIDTH*i+:DATA_WIDTH]
);
kernel1_rom conv1_rom
(
.i_ci_cnt(c_rom1_i_cnt),
.o_kernel(r_conv1_kernel)
// .o_bias()
);
// generate
// 생긴 모양 : 16 | 16 | 16 으로 비트가 쪼개져서 들어갈듯
// CONV1
genvar ci, co;
generate
// for(ci = 0; ci < 3; ci=ci+1) begin : conv1_ci // 한 차원씩 연산해 누적합
for(co = 0; co<CONV1_OUT_CH; co=co+1) begin : conv1_co
conv2d#
(
.DATA_WIDTH(DATA_WIDTH),
.IMG_WIDTH(INPUT_WIDTH),
.IMG_HEIGHT(INPUT_HEIGHT),
.PADDING(1),
.KERNEL_SIZE(3)
)
conv1_inst_
(
.i_Clk(i_clk),
.i_Rst(i_rst),
.i_start(i_start),
.i_pixel(rgb_o_data[rgb_slicer*DATA_WIDTH +: DATA_WIDTH]), // 커널 나오는건 무시하고 입력 채널은 하나씩 들어가야 함
.i_kernel(r_conv1_kernel[co*ONE_KERNEL_SIZE+:ONE_KERNEL_SIZE]), // 한 입력 채널 커널 다 돌려야 하니까
.o_pixel(conv1_o_pixel[co*DATA_WIDTH+:DATA_WIDTH]),
.o_done(conv1_o_done[co]),
.o_valid(conv1_o_valid[co])
);
end
// end
endgenerate
// maxpool 에 들어가기 전 가중치에 따른 conv 연산을 모두 적용
// 개별 ci 에 대해 co를 진행하고, 나온 값들을 저장
generate
for(co=0; co<CONV1_OUT_CH; co=co+1) begin : mac_mem_conv1 // 더하는 메모리 (bias 도 더해야 하긴 함)
mac_mem#
(
.DATA_WIDTH(DATA_WIDTH),
.DEPTH(INPUT_WIDTH * INPUT_HEIGHT)
)
conv1_only_mem_inst_
(
.i_rst(i_rst),
.i_wclk(i_clk),
.i_wr_addr(c_conv1_mac_cnt),
.i_wr(&conv1_o_valid),
.i_rclk(i_clk),
.i_rd_addr(c_mac_mem_out_cnt),
.i_rd(1'b1),
.i_bram_en(&conv1_o_valid),
.i_bram_data(conv1_o_pixel[co*DATA_WIDTH+:DATA_WIDTH]),
.o_bram_data(mac_mem_o_bram_data[co*DATA_WIDTH+:DATA_WIDTH])
);
end
endgenerate
// CONV1-maxpool-relu
// should integrate relu module
generate
for(co = 0; co<CONV1_OUT_CH; co=co+1) begin : maxpool1
maxpool2d_gpt#
(
.DATA_WIDTH(DATA_WIDTH),
.IMG_WIDTH(INPUT_WIDTH),
.IMG_HEIGHT(INPUT_HEIGHT)
)
maxpool1_inst_
(
.i_clk(i_clk),
.i_rst(i_rst),
.i_start(maxpool1_i_start),
.i_data(mac_mem_o_bram_data[co*DATA_WIDTH+:DATA_WIDTH]),
.o_data(maxpool1_o_data[co*DATA_WIDTH+:DATA_WIDTH]),
.o_done(maxpool1_o_done[co]),
.o_valid(maxpool1_o_valid[co])
);
end
endgenerate
// CONV1 memory (should make more memory modules!!)
// save maxpool data, when o_valid == true
generate
for(co = 0; co<CONV1_OUT_CH; co=co+1) begin : conv1_mem
conv_mem#
(
.DATA_WIDTH(DATA_WIDTH),
.DEPTH((INPUT_WIDTH/2) * (INPUT_HEIGHT/2))
)
conv1_mem_inst_
(
.i_rst(i_rst),
.i_wclk(i_clk),
.i_wr_addr(c_conv1_cnt), // counter if &maxpool1_o_valid, then cnt + 1
.i_wr(&maxpool1_o_valid),
.i_rclk(i_clk),
.i_rd_addr(c_conv1_rd_mem_addr),
.i_rd(1'b1), // if conv1 end(include saving)? and state == CONV2?
.i_bram_en(&maxpool1_o_valid), // &maxpool1_o_valid ?
.i_bram_data(maxpool1_o_data[co*DATA_WIDTH+:DATA_WIDTH]),
.o_bram_data(conv1_o_bram_data[co*DATA_WIDTH+:DATA_WIDTH])
);
end
endgenerate
// dense
dense#
(
.DATA_WIDTH(DATA_WIDTH),
.IN_SIZE((INPUT_WIDTH/2) * (INPUT_HEIGHT/2)*CONV1_OUT_CH), // img width * height * co
.OUT_SIZE(6)
)
dense1_inst_
(
.i_clk(i_clk),
.i_rst(i_rst),
.i_start(dense1_i_start),
.i_data(dense1_i_data),
.o_data(dense1_o_data),
.o_valid(dense1_o_valid),
.o_done(dense1_o_done)
);
/*============================\\
F F
\\============================*/
always@(posedge i_clk, negedge i_rst)
if(!i_rst) begin
buf_dense_out <= 0;
end else begin
if(c_state == DENSE1)
if(dense1_o_valid)
buf_dense_out <= {buf_dense_out[79:0], dense1_o_data};
else buf_dense_out <= 0;
end
always@(posedge i_clk, negedge i_rst)
if(!i_rst) begin
c_state <= 0;
c_conv1_cnt <= 0;
c_rom1_i_cnt <= 0;
c_conv1_mac_cnt <= 0;
c_mac_mem_out_cnt <= 0;
c_conv1_rd_mem_addr <= 0;
c_dense1_cnt <= 0;
c_done <= 0;
end else begin
c_state <= n_state;
c_conv1_cnt <= n_conv1_cnt;
c_rom1_i_cnt <= n_rom1_i_cnt;
c_conv1_mac_cnt <= n_conv1_mac_cnt;
c_mac_mem_out_cnt <= n_mac_mem_out_cnt;
c_conv1_rd_mem_addr <= n_conv1_rd_mem_addr;
c_dense1_cnt <= n_dense1_cnt;
c_done <= n_done;
end
/*============================\\
C O M B
\\============================*/
always@* begin
n_state = c_state;
n_conv1_cnt = c_conv1_cnt;
n_rom1_i_cnt = c_rom1_i_cnt;
n_conv1_mac_cnt = c_conv1_mac_cnt;
n_mac_mem_out_cnt = c_mac_mem_out_cnt;
n_conv1_rd_mem_addr = c_conv1_rd_mem_addr;
n_dense1_cnt = c_dense1_cnt;
dense1_i_data = 0;
n_done = 0;
case(c_state)
IDLE: begin
if(i_start) n_state = CONV1;
end
CONV1: begin
if(&conv1_o_valid) n_conv1_mac_cnt = c_conv1_mac_cnt + 1;
if(c_conv1_mac_cnt == INPUT_WIDTH * INPUT_HEIGHT-1) n_conv1_mac_cnt = 0; // mac mem 에 저장하기 위한 카운터, 나중에 값 바꿔야 함
if(&conv1_o_done) n_rom1_i_cnt = c_rom1_i_cnt + 1;
if(c_rom1_i_cnt == CONV1_OUT_CH-1) begin
n_rom1_i_cnt = 0;
// maxpool start
n_state = MAXPOOL1;
n_conv1_mac_cnt = 0;
end
end
MAXPOOL1: begin
n_mac_mem_out_cnt = c_mac_mem_out_cnt + 1;
// when memory saving
if(&maxpool1_o_valid) n_conv1_cnt = c_conv1_cnt + 1;
// memsave end?
if(&maxpool1_o_done)
begin
n_conv1_cnt = 0;
n_mac_mem_out_cnt = 0;
n_state = DENSE1;
end
end
DENSE1: begin
n_dense1_cnt = c_dense1_cnt + 1;
if(c_dense1_cnt == CONV1_OUT_CH-1) begin
n_dense1_cnt = 0;
n_conv1_rd_mem_addr = c_conv1_rd_mem_addr + 1;
end
dense1_i_data = conv1_o_bram_data[c_dense1_cnt*DATA_WIDTH+:DATA_WIDTH];
if(dense1_o_done) begin
n_state = IDLE;
n_done = 1;
end
end
endcase
end
assign dense1_i_start = c_state == DENSE1;
assign maxpool1_i_start = c_state == MAXPOOL1 ? !(&maxpool1_o_done) : 0;
assign rgb_slicer = c_rom1_i_cnt;
assign o_is_apple = buf_dense_out[95:80];
assign o_apple_pix_length = buf_dense_out[31:16];
endmodule
이건 나중에 다중 레이어 구현을 위해 FPGA 로직 게이트를 최소한으로 차지하게 만들려고 클럭을 많이 사용했다.
단점은, start sign 이 channel input 개 만큼 들어가서 conv2d 를 작동시켜야 하는 단점이 있다.
channel input, channel output 이 합쳐진 CNN 도 있기는 한데, 이건 지금은 패스하고 만든 이 모듈에 대해서만 설명한다.
OV7670 에서 가져온 rgb565 데이터를 물려서 CNN 측정 해 보는 것이 지금의 목표이지만, 정말로 FPGA 상에서 잘 작동하는지 확인해야 하기 때문에 간단하게 하나씩 레이어를 만들어서 구현해 놓았다.
1.0_cnn_top_splitted_in_channel.zip
0.01MB
공부하는데 참고가 되었으면 한다.
반응형
'Verilog' 카테고리의 다른 글
[Verilog & Project] Yolov3-tiny (0) | 2024.12.02 |
---|---|
[Verilog] HDMI interface - 1 (시작 전 RGB 부터) (0) | 2024.11.07 |
[Verilog] maxpool2d w. line buffer (0) | 2024.10.31 |
[Verilog] Conv2d w. line buffer (0) | 2024.10.30 |
[Verilog] OV7670 카메라 모듈과 FPGA 통신 (0) | 2024.09.09 |