반응형

공부를 하면서 포스팅을 하고 있는 입장이기에, 코드의 정확성은 보장하지 못하거니와

더욱 효율적으로 코드를 작성할 방법도 굉장히 많을 것이다.

하지만 간단한 예시를 들어 모듈을 만들어 보고자 하는 것이 목적이기에 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

 

공부하는데 참고가 되었으면 한다.

 

반응형

+ Recent posts