진행년월: 24.07

 

본래 다른 곳에 올리려고 했다가 옮기는 중에

설명이 전부 날아가버려서, 우선은 과정 정도로 남겨놓고

빠른 시일 내에 다시 작성하도록 하겠습니다.

 

목차

1. 배경

2. 과제 정의 및 개요

3. 소스코드

4. 시뮬레이션 결과

 

 

1. 배경

CNN 모델 중 가장 기본적인 Lenet-5 모델을 구현해보려고 합니다.

메모리 등을 고려해야 하기 때문에,

성능 자체를 끌어올리기보다는 구현 자체에 초점을 맞춥니다.

 

 

2. 과제 정의 및 개요

 

 

구현할 모델은 위와 같습니다.

 

1. CNN C코드 작성
2. Verilog 구현용 C코드 작성
3. Verilog 코드 구현
4. 이미지 예측 정확도 평가

 

3. 소스코드

 

 

module cnn (
    input clk,
    input reset,
    input [7:0] t_rdata,
    output reg [9:0] t_addr,
    output reg [11:0] output_address, // Output memory address
    output reg signed [10:0] output_data, // Output write data
    output reg output_write_enable // Output write enable
);

// RAM Signals
reg [9:0] image_address;
reg [7:0] image_write_data;
wire [7:0] image_read_data;
reg image_write_enable;
wire signed [10:0] output_read_data;

// ROM Signals
reg [7:0] weight_address;
wire signed [7:0] weight_read_data;
reg [2:0] bias_address;
wire signed [7:0] bias_read_data;

// Loop Index Registers
reg [4:0] row, next_row, col, next_col;
reg [2:0] filter, next_filter, kernel_row, next_kernel_row, kernel_col, next_kernel_col;

// Additional Registers
reg [9:0] current_image_address, next_image_address;
(* use_dsp = "yes" *) reg signed [18:0] conv_sum, next_conv_sum;
reg [7:0] current_weight_address, next_weight_address;
reg [11:0] current_output_address, next_output_address;
reg signed [10:0] temp_output, next_temp_output;

// State Definitions
localparam s0 = 0, s1 = 1, s2 = 2, s3 = 3, s4 = 4, s5 = 5, s6 = 6;
reg [3:0] state, next_state;

// File Descriptor
integer fd;

// RAM Instantiation
i_ram image_ram (
    .clka(clk),
    .wea(image_write_enable),
    .addra(image_address),
    .dina(image_write_data),
    .douta(image_read_data)
);

a_ram output_ram (
    .clka(clk),
    .wea(output_write_enable),
    .addra(output_address),
    .dina(output_data),
    .douta(output_read_data)
);

// ROM Instantiation
w0_rom weight_rom (
    .clka(clk),
    .addra(weight_address),
    .douta(weight_read_data)
);

b0_rom bias_rom (
    .clka(clk),
    .addra(bias_address),
    .douta(bias_read_data)
);

// Finite State Machine (FSM)
always @ (reset, state, t_rdata, image_read_data, output_read_data, weight_read_data, bias_read_data, row, col, filter, kernel_row, kernel_col, current_image_address, conv_sum, current_weight_address, current_output_address, temp_output) begin
    // Default memory settings
    t_addr = 0;
    image_address = 0;
    image_write_data = 0;
    image_write_enable = 0;
    output_address = 0;
    output_data = 0;
    output_write_enable = 0;
    weight_address = 0;
    bias_address = 0;

    // Default register settings
    next_state = state;
    next_row = row;
    next_col = col;
    next_filter = filter;
    next_kernel_row = kernel_row;
    next_kernel_col = kernel_col;
    next_image_address = current_image_address;
    next_conv_sum = conv_sum;
    next_weight_address = current_weight_address;
    next_output_address = current_output_address;
    next_temp_output = temp_output;

    case (state)
        s0: begin
            next_state = s1;
            next_image_address = 0;
            t_addr = 0;
            next_row = 0;
            next_col = 0;
        end

        s1: begin
            image_write_data = t_rdata;
            image_address = current_image_address;
            image_write_enable = 1'b1;
            next_state = s1;
            next_col = col + 1;
            if (next_col == 28) begin
                next_col = 0;
                next_row = row + 1;
                if (next_row == 28) begin
                    next_row = 0;
                    next_state = s2;
                end
            end
            next_image_address = next_row * 28 + next_col;
            t_addr = next_image_address;
        end

        s2: begin
            next_filter = 0;
            next_row = 0;
            next_col = 0;
            next_conv_sum = 0;
            next_kernel_row = 0;
            next_kernel_col = 0;
            image_address = 0;
            weight_address = 0;
            next_state = s3;
        end

        s3: begin
            next_conv_sum = conv_sum + $signed({1'b0, image_read_data}) * weight_read_data;
            next_state = s3;
            next_kernel_col = kernel_col + 1;
            if (next_kernel_col == 5) begin
                next_kernel_col = 0;
                next_kernel_row = kernel_row + 1;
                if (next_kernel_row == 5) begin
                    next_kernel_row = 0;
                    next_state = s4;
                    bias_address = filter;
                end
            end
            next_image_address = (row + next_kernel_row) * 28 + col + next_kernel_col;
            next_weight_address = filter * 25 + next_kernel_row * 5 + next_kernel_col;
            image_address = next_image_address;
            weight_address = next_weight_address;
        end

        s4: begin
            next_temp_output = conv_sum[18:8];
            if (next_temp_output[10] == 1'b1) begin
                next_temp_output = next_temp_output + (|conv_sum[7:0]); // Round towards zero (RTZ)
            end
            next_temp_output = next_temp_output + bias_read_data;
            if (next_temp_output[10] == 1'b1) begin
                next_temp_output = 0;
            end
            next_output_address = filter * 576 + row * 24 + col;
            output_address = next_output_address;
            output_data = next_temp_output;
            output_write_enable = 1'b1;
            next_state = s3;
            next_col = col + 1;
            if (next_col == 24) begin
                next_col = 0;
                next_row = row + 1;
                if (next_row == 24) begin
                    next_row = 0;
                    next_filter = filter + 1;
                    if (next_filter == 6) begin
                        next_filter = 0;
                        next_state = s5;
                    end
                end
            end
            next_conv_sum = 0;
            next_image_address = next_row * 28 + next_col;
            next_weight_address = next_filter * 25;
            image_address = next_image_address;
            weight_address = next_weight_address;
        end

        s5: begin
            next_state = s5;
        end

        default: begin
            next_state = s0;
        end
    endcase

    if (reset == 1) begin
        next_state = s0;
        next_row = 0;
        next_col = 0;
        next_filter = 0;
        next_kernel_row = 0;
        next_kernel_col = 0;
        next_image_address = 0;
        next_conv_sum = 0;
        next_weight_address = 0;
        next_output_address = 0;
        next_temp_output = 0;
    end
end

// State update on clock edge
always @ (posedge clk) begin
    state <= next_state;
    row <= next_row;
    col <= next_col;
    filter <= next_filter;
    kernel_row <= next_kernel_row;
    kernel_col <= next_kernel_col;
    current_image_address <= next_image_address;
    conv_sum <= next_conv_sum;
    current_weight_address <= next_weight_address;
    current_output_address <= next_output_address;
    temp_output <= next_temp_output;
end

// Synthesis directives and file writing
// synthesis translate_off
always @ (posedge clk) begin
    fd = $fopen("test.txt", "a");
    if (state == s4 && output_write_enable == 1'b1) begin
        $fwrite(fd, "A[%0d][%0d][%0d] = %0d\n", filter, row, col, output_data);
    end
    if (reset == 1) begin
        fd = $fopen("test.txt", "w");
    end
    $fclose(fd);
end
// synthesis translate_on

endmodule

 

module tb;
    reg clk;
    reg reset;
    wire [9:0] t_addr;
    wire [7:0] t_rdata;
    wire [11:0] output_address;
    wire [10:0] output_data;
    wire output_write_enable;

    //
    // ROM instantiation
    //
    t_rom t_test (
        .clka(clk),
        .addra(t_addr),
        .douta(t_rdata)
    );

    //
    // CNN instantiation
    //
    cnn uut (
        .clk(clk),
        .reset(reset),
        .t_addr(t_addr),
        .t_rdata(t_rdata),
        .output_address(output_address),
        .output_data(output_data),
        .output_write_enable(output_write_enable)
    );

    // 100 MHz clock
    initial begin
        clk = 0;
        forever #5 clk = ~clk;
    end

    // reset
    initial begin
        reset = 0;
        #100 reset = 1;
        #100 reset = 0;
    end
endmodule

 

4. 시뮬레이션 결과

 

진행년월: 24.07

 

본래 다른 곳에 올리려고 했다가 옮기는 중에

설명이 전부 날아가버려서, 우선은 과정 정도로 남겨놓고

빠른 시일 내에 다시 작성하도록 하겠습니다.

 

목차

1. 배경

2. 과제 정의 및 개요

3. 소스코드

4. 시뮬레이션 결과

 

 

1. 배경

Convolution 연산은 신호 처리 부분에서 많이 사용합니다.

특히, Filtering이 주된 용도이고, HPF, LPF가 있겠죠.

 

저는 이번에 2차원 Filter를 이해하고 설계해본 뒤에

application으로 이미지 처리 모델인 CNN을 구현해보려고 합니다.

 

 

2. 과제 정의 및 개요

 

제가 사용할 필터는 아래와 같으며, LPF(Low Pass Filter) 입니다.

 

 

위 필터는 중심을 기준으로 평균화해줍니다.

 

원본 이미지와 필터링된 이미지를 먼저 보면,

 

좌측(원본)이 조금 뚜렷하고, 우측(필터링)은 일종의 블러효과를 넣은 것처럼 흐릿해졌습니다.

 

 

A = imread('lena_gray.png');
B = imresize(A, 0.5);
dlmwrite('img_in.txt',B);

 

MATLAB을 통해 512 X 512 size 원본 이미지를 256 X 256 size로 조절한 뒤 저장하였습니다.

이제 이것이 input image가 될 것입니다.

 

 

전체 Process
1. C코드로 먼저 Fixed Point로 변환 및 구현하며, 입력, 출력 예시를 준비
2. Verilog 구현
3. Double Buffering으로 데이터 충돌 방지
4. Line Buffering으로 입력과 동시에 처리해 Cycle
5. Parameterizing, Testbench에서 DPI를 통한 C코드 활용

 

 

3. 소스코드

- 3.1 C코드 구현

#include <stdio.h>
#include <math.h>

void filter2d(unsigned char in_img[], unsigned char out_img[],
			int height, int width) {
	int		h[3][3] = {0x08, 0x10, 0x08, 0x10, 0x20, 0x10, 0x08, 0x10, 0x08};
	for(int i=0;i<height;i++) {
		for(int j=0;j<width;j++) {
			int	sum = 0;
			if(i>0 && j>0)				sum += in_img[(i-1)*width+j-1]*h[0][0];
			if(i>0) 				sum += in_img[(i-1)*width+j  ]*h[0][1];
			if(i>0 && j<width-1)			sum += in_img[(i-1)*width+j+1]*h[0][2];
			if(j>0)					sum += in_img[(i  )*width+j-1]*h[1][0];
								sum += in_img[(i  )*width+j  ]*h[1][1];
			if(j<width-1)				sum += in_img[(i  )*width+j+1]*h[1][2];
			if(i<height-1 && j>0)			sum += in_img[(i+1)*width+j-1]*h[2][0]; 
			if(i<height-1)				sum += in_img[(i+1)*width+j  ]*h[2][1]; 
			if(i<height-1 && j<width-1)		sum += in_img[(i+1)*width+j+1]*h[2][2];
								sum = (sum + (1<<6)) >> 7;
			if(sum < 0) out_img[i*width+j] = 0;
			else if(sum > 255) out_img[i*width+j] = 255;
			else out_img[i*width+j] = sum;
		}
	}
}

int main(void) {
	int			i, a;
	FILE		*inf, *outf, *memf;
	unsigned char	in_img[256*256];
	unsigned char	out_img[256*256];
	inf = fopen("img_in.txt", "r"); 
	outf = fopen("img_out.txt", "w");
	memf = fopen("img_in.dat", "w");

	for(i=0;i<256*256;i++) {
		fscanf(inf, "%d,", &a);
		in_img[i] = a;
		fprintf(memf, "%02X\n", in_img[i]);
	}

	filter2d(in_img, out_img, 256, 256);

	for(i=0;i<256*256;i++) {
		fprintf(outf, "%3d ", out_img[i]);
		if(i%256 == 255) fprintf(outf, "\n");
	}

	fclose(inf);
	fclose(outf);
	fclose(memf);
}

 

 

 

 

- 3.2 Verilog 구현

 

module filter2d (
				input			clk,
				input			n_reset,
				input			start,
				output	reg		finish,

				output			cs,
				output			we,
				output	[16:0]	addr,
				output	[7:0]	din,
				input	[7:0]	dout,

				input			h_write,
				input	[3:0]	h_idx,
				input	[7:0]	h_data
);

reg			on_proc;
reg	[3:0]	cnt;
reg	[7:0]	cnt_x;
reg	[7:0]	cnt_y;

always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		on_proc <= 1'b0;
		cnt <= 0;
		cnt_x <= 0;
		cnt_y <= 0;
		finish <= 1'b0;
	end else begin
		if(start == 1'b1) on_proc <= 1'b1;
		else if((cnt == 11) && (cnt_x == 255) && (cnt_y == 255)) on_proc <= 1'b0;

		if(on_proc == 1'b1) begin
			cnt <= (cnt == 11) ? 0 : cnt+1;
			if(cnt == 11) begin
				cnt_x <= (cnt_x == 255) ? 0 : cnt_x+1;
				if(cnt_x == 255) begin
					cnt_y <= (cnt_y == 255) ? 0 : cnt_y+1;
				end
			end
		end
		finish <= ((cnt == 11) && (cnt_x == 255) && (cnt_y == 255));
	end
end

wire			mem_rd = (cnt >= 0) && (cnt <= 8) && (on_proc == 1'b1);
reg		[16:0]	rd_addr;
always@(*) begin
	case(cnt)
		4'd0:	rd_addr = (cnt_y-1)*256 + cnt_x-1;
		4'd1:	rd_addr = (cnt_y-1)*256 + cnt_x;
		4'd2:	rd_addr = (cnt_y-1)*256 + cnt_x+1;
		4'd3:	rd_addr = (cnt_y  )*256 + cnt_x-1;
		4'd4:	rd_addr = (cnt_y  )*256 + cnt_x;
		4'd5:	rd_addr = (cnt_y  )*256 + cnt_x+1;
		4'd6:	rd_addr = (cnt_y+1)*256 + cnt_x-1;
		4'd7:	rd_addr = (cnt_y+1)*256 + cnt_x;
		4'd8:	rd_addr = (cnt_y+1)*256 + cnt_x+1;
		default:	rd_addr = 'bx;
	endcase 
end

reg		[7:0]	pd;
wire			pd_en = (cnt >= 1) && (cnt <= 9);
always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		pd <= 0;
	end else begin
		if(pd_en == 1'b1) pd <= dout;
	end
end

reg	signed	[7:0]	h[0:8];
always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		h[0] <= 8'h08;
		h[1] <= 8'h10;
		h[2] <= 8'h08;
		h[3] <= 8'h10;
		h[4] <= 8'h20;
		h[5] <= 8'h10;
		h[6] <= 8'h08;
		h[7] <= 8'h10;
		h[8] <= 8'h08;
	end else begin
		if(h_write == 1'b1) begin
			h[h_idx] <= h_data;
		end
	end
end

wire signed [7:0]	coeff = h[cnt-2];
wire signed [15:0]	mul = pd * coeff;
reg	 signed [19:0]	acc;
wire signed [19:0]	acc_in = (cnt == 1) ? 0 : mul + acc;
reg					acc_en;

always@(*) begin
	acc_en = 1'b0;
	case(cnt)
		4'd 1: acc_en = 1'b1;
		4'd 2: if((cnt_y > 0) && (cnt_x >   0)) acc_en = 1'b1;
		4'd 3: if((cnt_y > 0)                 ) acc_en = 1'b1;
		4'd 4: if((cnt_y > 0) && (cnt_x < 255)) acc_en = 1'b1;
		4'd 5: if(cnt_x >   0)	acc_en = 1'b1;
		4'd 6: 					acc_en = 1'b1;
		4'd 7: if(cnt_x < 255)	acc_en = 1'b1;
		4'd 8: if((cnt_y < 255) && (cnt_x >   0)) acc_en = 1'b1;
		4'd 9: if((cnt_y < 255)                 ) acc_en = 1'b1;
		4'd10: if((cnt_y < 255) && (cnt_x < 255)) acc_en = 1'b1;
		default: acc_en = 1'b0;
	endcase
end

always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		acc <= 'b0;
	end else begin
		if(acc_en == 1'b1) acc <= acc_in;
	end
end

wire	[19:0]	pd_rnd_1 = acc + (1<<6);
wire	[12:0]	pd_rnd = pd_rnd_1[19:7];
wire	[7:0]	pd_out = (pd_rnd < 0) ? 0 :
						 (pd_rnd > 255) ? 255 :
						 pd_rnd[7:0];
assign			din = pd_out;

wire			mem_wr = (cnt == 11);
wire	[16:0]	wr_addr = cnt_y * 256 + cnt_x + 256*256;

assign	cs = mem_rd | mem_wr;
assign	we = mem_wr;
assign	addr = (mem_rd == 1'b1) ? rd_addr : wr_addr;

endmodule

 

module top_filter_2d;

reg		clk, n_reset;
reg		start;
wire	finish;

initial clk = 1'b0;
always #5 clk = ~clk;

initial begin
	n_reset = 1'b1;
	$readmemh("../c/img_in.dat", i_buf.data);

	#3;
	n_reset = 1'b0;
	#20;
	n_reset = 1'b1;
	@(posedge clk);
	@(posedge clk);
	@(posedge clk);
	start = 1'b1;
	@(posedge clk);
	start = 1'b0;
end

wire	cs, we;
wire	[16:0]	addr;
wire	[7:0]	din;
wire	[7:0]	dout;

filter2d	i_filter (.clk(clk), .n_reset(n_reset), .start(start), .finish(finish),
				.cs(cs), .we(we), .addr(addr), .din(din), .dout(dout),
				.h_write(1'b0), .h_idx(4'b0), .h_data(8'b0));

mem_single #(
				.WD(8),
				.DEPTH(256*256*2)
) i_buf (
				.clk(clk),
				.cs(cs),
				.we(we),
				.addr(addr),
				.din(din),
				.dout(dout)
);

always@(posedge clk) begin
	if(finish == 1'b1) begin
		for(int i=0;i<256;i++) begin
			for(int j=0;j<256;j++) begin
				$write("%3d ", i_buf.data[i*256+j+256*256]);
			end
			$write("\n");
		end
		$finish;
	end
end

endmodule

 

module mem_single #(
		  WD = 128
		, DEPTH = 64
		, WA = $clog2(DEPTH)
) ( 
		  input					clk
		, input					cs
		, input					we
		, input		[WA-1:0]	addr
		, input		[WD-1:0]	din
		, output 	[WD-1:0]	dout
);

reg	[WD-1:0]	data[DEPTH-1:0];
reg	[WA-1:0]	addr_d;

always@(posedge clk) begin
	if(cs == 1'b1) begin
		if(we == 1'b1) data[addr] <= din;
		addr_d <= addr;
	end
end
assign dout = data[addr_d];

endmodule

 

위 mem_single 모듈은 아래에서도 계속 사용합니다.

 

 

 

- 3.3 Double Buffering

module filter2d (
				input	clk,
				input	n_reset,

				input			i_strb,
				input	[7:0]	i_data,

				output 			o_strb,
				output 	[7:0]	o_data
);

wire			start;
wire			mem_rd;
wire	[15:0]	rd_addr;
wire	[7:0]	rd_data;

filter2d_buf i_buf(
				.clk(clk),
				.n_reset(n_reset),
				.i_strb(i_strb),
				.i_data(i_data),

				.start(start),

				.mem_rd(mem_rd),
				.rd_addr(rd_addr),
				.rd_data(rd_data)
);

filter2d_op i_op(
				.clk(clk),
				.n_reset(n_reset),
				.start(start),

				.mem_rd(mem_rd),
				.rd_addr(rd_addr),
				.rd_data(rd_data),

				.o_strb(o_strb),
				.o_data(o_data)
);

endmodule

 

module filter2d_op (
				input			clk,
				input			n_reset,
				input			start,

				output				mem_rd,
				output reg	[15:0]	rd_addr,
				input		[7:0]	rd_data,

				output reg			o_strb,
				output reg	[7:0]	o_data,

				input			h_write,
				input	[3:0]	h_idx,
				input	[7:0]	h_data
);

reg			on_proc;
reg	[3:0]	cnt;
reg	[7:0]	cnt_x;
reg	[7:0]	cnt_y;

always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		on_proc <= 1'b0;
		cnt <= 0;
		cnt_x <= 0;
		cnt_y <= 0;
	end else begin
		if(start == 1'b1) on_proc <= 1'b1;
		else if((cnt == 11) && (cnt_x == 255) && (cnt_y == 255)) on_proc <= 1'b0;

		if(on_proc == 1'b1) begin
			cnt <= (cnt == 11) ? 0 : cnt+1;
			if(cnt == 11) begin
				cnt_x <= (cnt_x == 255) ? 0 : cnt_x+1;
				if(cnt_x == 255) begin
					cnt_y <= (cnt_y == 255) ? 0 : cnt_y+1;
				end
			end
		end
	end
end

assign mem_rd = (cnt >= 0) && (cnt <= 8) && (on_proc == 1'b1);
always@(*) begin
	case(cnt)
		4'd0:	rd_addr = (cnt_y-1)*256 + cnt_x-1;
		4'd1:	rd_addr = (cnt_y-1)*256 + cnt_x;
		4'd2:	rd_addr = (cnt_y-1)*256 + cnt_x+1;
		4'd3:	rd_addr = (cnt_y  )*256 + cnt_x-1;
		4'd4:	rd_addr = (cnt_y  )*256 + cnt_x;
		4'd5:	rd_addr = (cnt_y  )*256 + cnt_x+1;
		4'd6:	rd_addr = (cnt_y+1)*256 + cnt_x-1;
		4'd7:	rd_addr = (cnt_y+1)*256 + cnt_x;
		4'd8:	rd_addr = (cnt_y+1)*256 + cnt_x+1;
		default:	rd_addr = 'bx;
	endcase 
end

reg		[7:0]	pd;
wire			pd_en = (cnt >= 1) && (cnt <= 9);
always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		pd <= 0;
	end else begin
		if(pd_en == 1'b1) pd <= rd_data;
	end
end

reg	signed	[7:0]	h[0:8];
always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		h[0] <= 8'h08;
		h[1] <= 8'h10;
		h[2] <= 8'h08;
		h[3] <= 8'h10;
		h[4] <= 8'h20;
		h[5] <= 8'h10;
		h[6] <= 8'h08;
		h[7] <= 8'h10;
		h[8] <= 8'h08;
	end else begin
		if(h_write == 1'b1) begin
			h[h_idx] <= h_data;
		end
	end
end

wire signed [7:0]	coeff = h[cnt-2];
wire signed [15:0]	mul = pd * coeff;
reg	 signed [19:0]	acc;
wire signed [19:0]	acc_in = (cnt == 1) ? 0 : mul + acc;
reg					acc_en;

always@(*) begin
	acc_en = 1'b0;
	case(cnt)
		4'd 1: acc_en = 1'b1;
		4'd 2: if((cnt_y > 0) && (cnt_x >   0)) acc_en = 1'b1;
		4'd 3: if((cnt_y > 0)                 ) acc_en = 1'b1;
		4'd 4: if((cnt_y > 0) && (cnt_x < 255)) acc_en = 1'b1;
		4'd 5: if(cnt_x >   0)	acc_en = 1'b1;
		4'd 6: 					acc_en = 1'b1;
		4'd 7: if(cnt_x < 255)	acc_en = 1'b1;
		4'd 8: if((cnt_y < 255) && (cnt_x >   0)) acc_en = 1'b1;
		4'd 9: if((cnt_y < 255)                 ) acc_en = 1'b1;
		4'd10: if((cnt_y < 255) && (cnt_x < 255)) acc_en = 1'b1;
		default: acc_en = 1'b0;
	endcase
end

always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		acc <= 'b0;
	end else begin
		if(acc_en == 1'b1) acc <= acc_in;
	end
end

wire	[19:0]	pd_rnd_1 = acc + (1<<6);
wire	[12:0]	pd_rnd = pd_rnd_1[19:7];
wire	[7:0]	pd_out = (pd_rnd < 0) ? 0 :
						 (pd_rnd > 255) ? 255 :
						 pd_rnd[7:0];

always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		o_strb <= 1'b0;
		o_data <= 'b0;
	end else begin
		o_strb <= (cnt == 11);
		if(cnt == 11) begin
			o_data <= pd_out;
		end
	end
end

endmodule

 

module filter2d_buf (
				input			clk,
				input			n_reset,
				input			i_strb,
				input	[7:0]	i_data,

				output reg		start,

				input			mem_rd,
				input	[15:0]	rd_addr,
				output	[7:0]	rd_data
);

reg	[7:0]	cnt_x;
reg	[7:0]	cnt_y;

always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		cnt_x <= 255;
		cnt_y <= 255;
	end else begin
		if(i_strb == 1'b1) begin
			cnt_x <= (cnt_x == 255) ? 0 : cnt_x+1;
			if(cnt_x == 255) begin
				cnt_y <= (cnt_y == 255) ? 0 : cnt_y+1;
			end
		end
	end
end

reg			mode;
wire		mode_change;
reg			mem_wr;
reg	[7:0]	wr_data;

assign mode_change = (mem_wr == 1'b1) && (cnt_x == 255) && (cnt_y == 255);
always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		mode <= 1'b0;
		start <= 1'b0;
	end else begin
		if(mode_change == 1'b1) begin
			mode <= ~mode;
		end
		start <= mode_change;
	end
end

always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		mem_wr <= 1'b0;
		wr_data <= 8'b0;
	end else begin
		mem_wr <= i_strb;
		wr_data <= i_data;
	end
end
wire	[15:0]	wr_addr = cnt_y*256 + cnt_x;

wire			cs0 = (mode == 1'b0) ? mem_wr : mem_rd; 
wire			we0 = (mode == 1'b0) ? mem_wr : 1'b0; 
wire	[15:0]	addr0 = (mode == 1'b0) ? wr_addr : rd_addr;
wire	[7:0]	din0 = (mode == 1'b0) ? wr_data : 'b0;
wire	[7:0]	dout0;

wire			cs1 = (mode == 1'b1) ? mem_wr : mem_rd; 
wire			we1 = (mode == 1'b1) ? mem_wr : 1'b0; 
wire	[15:0]	addr1 = (mode == 1'b1) ? wr_addr : rd_addr;
wire	[7:0]	din1 = (mode == 1'b1) ? wr_data : 'b0;
wire	[7:0]	dout1;

assign	rd_data = (mode == 1'b0) ? dout1 : dout0;


mem_single #(
				.WD(8),
				.DEPTH(256*256)
) i_buf0 (
				.clk(clk),
				.cs(cs0),
				.we(we0),
				.addr(addr0),
				.din(din0),
				.dout(dout0)
);

mem_single #(
				.WD(8),
				.DEPTH(256*256)
) i_buf1 (
				.clk(clk),
				.cs(cs1),
				.we(we1),
				.addr(addr1),
				.din(din1),
				.dout(dout1)
);

endmodule

 

module top_filter_2d;

reg		clk, n_reset;
reg		start;

initial clk = 1'b0;
always #5 clk = ~clk;

reg	[7:0]	img_data[0:65535];
reg			i_strb;
reg	[7:0]	i_data;
integer	idx, cnt;
initial begin
	cnt = 0;
	n_reset = 1'b1;
	$readmemh("../c/img_in.dat", img_data);
	i_strb = 1'b0;
	i_data = 'bx;
	#3;
	n_reset = 1'b0;
	#20;
	n_reset = 1'b1;
	@(posedge clk);
	@(posedge clk);
	@(posedge clk);
	repeat(3) begin
		for(idx=0;idx<65536;idx=idx+1) begin
			i_strb = 1'b1;
			i_data = img_data[idx];
			@(posedge clk);
			repeat(16) begin
				i_strb = 1'b0;
				i_data = 'bx;
				@(posedge clk);
			end
		end
	end
	@(posedge clk);
	@(posedge clk);
	@(posedge clk);
	$finish;
end

wire			o_strb;
wire	[7:0]	o_data;
filter2d	i_filter (
					.clk(clk), 
					.n_reset(n_reset), 
					.i_strb(i_strb), 
					.i_data(i_data),
					.o_strb(o_strb), 
					.o_data(o_data),
					.h_write(1'b0),
					.h_idx(4'b0),
					.h_data(8'b0)
);

always@(posedge clk) begin
	if(o_strb == 1'b1) begin
		$write("%3d ", o_data);
		cnt = cnt + 1;
		if(cnt[7:0] == 0) begin
			$write("\n");
		end
	end
end

endmodule

 

- 3.4 Line Buffer

module filter2d (
				input			clk,
				input			n_reset,

				input			i_strb,
				input	[7:0]	i_data,

				output reg			o_strb,
				output reg	[7:0]	o_data,

				input			h_write,
				input	[3:0]	h_idx,
				input	[7:0]	h_data
);

reg			garbage;
reg	[3:0]	cnt;
reg	[7:0]	cnt_x;
reg	[7:0]	cnt_y;
reg	[7:0]	i_data_d;

always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		garbage <= 1'b1;
		cnt <= 7;
		cnt_x <= 254;
		cnt_y <= 254;
		i_data_d <= 'b0;
	end else begin
		if(i_strb == 1'b1) begin
			cnt_x <= (cnt_x == 255) ? 0 : cnt_x+1;
			if(cnt_x == 255) begin
				cnt_y <= (cnt_y == 255) ? 0 : cnt_y+1;
				if(cnt_y == 255) garbage <= 1'b0;
			end
		end
		if(i_strb == 1'b1) cnt <= 0;
		else if(cnt < 7) cnt <= cnt+1;
		if(i_strb == 1'b1) i_data_d <= i_data;
	end
end

reg 	[7:0]	ibuf[2:0][2:0];
wire	[7:0]	dout;
always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		for(int i=0;i<3;i++) begin
			for(int j=0;j<3;j++) begin
				ibuf[i][j] <= 'b0;
			end
		end
	end else begin
		if(cnt == 0) begin
			for(int i=0;i<3;i++) begin
				for(int j=0;j<2;j++) begin
					ibuf[i][j] <= ibuf[i][j+1];
				end
			end
			ibuf[2][2] <= i_data_d;
		end
		if(cnt == 1) ibuf[0][2] <= dout;
		if(cnt == 2) ibuf[1][2] <= dout;
	end
end

wire		mem_rd = (cnt == 0) || (cnt == 1);
wire		mem_wr = (cnt == 2);

reg		[8:0]	wr_addr;
wire	[8:0]	rd_addr0 = wr_addr;
wire	[8:0]	rd_addr1 = (wr_addr<256) ? wr_addr+256 : wr_addr-256;
wire	[8:0]	rd_addr = (cnt == 0) ? rd_addr0 : rd_addr1;

always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		wr_addr <= 0;
	end else begin
		if(mem_wr == 1'b1) begin
			wr_addr <= (wr_addr == 2*256-1) ? 0 : wr_addr + 1;
		end
	end
end

wire			cs = mem_rd | mem_wr;
wire			we = mem_wr;
wire	[8:0]	addr = (mem_wr == 1'b1) ? wr_addr : rd_addr;
wire	[7:0]	din = i_data_d;

mem_single #(
				.WD(8),
				.DEPTH(2*256)
) i_buf0 (
				.clk(clk),
				.cs(cs),
				.we(we),
				.addr(addr),
				.din(din),
				.dout(dout)
);

reg	signed	[7:0]	h[0:8];
always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		h[0] <= 8'h08;
		h[1] <= 8'h10;
		h[2] <= 8'h08;
		h[3] <= 8'h10;
		h[4] <= 8'h20;
		h[5] <= 8'h10;
		h[6] <= 8'h08;
		h[7] <= 8'h10;
		h[8] <= 8'h08;
	end else begin
		if(h_write == 1'b1) begin
			h[h_idx] <= h_data;
		end
	end
end

reg	signed	[15:0]	mul[2:0][2:0];
always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		for(int i=0;i<3;i++) begin
			for(int j=0;j<3;j++) begin
				mul[i][j] <= 'b0;
			end
		end
	end else begin
		if((cnt == 3) && (garbage == 1'b0)) begin
			mul[0][0] <= ((cnt_y > 0) && (cnt_x >   0)) ? ibuf[0][0] * h[0] : 'b0;
			mul[0][1] <= ((cnt_y > 0)                 ) ? ibuf[0][1] * h[1] : 'b0;
			mul[0][2] <= ((cnt_y > 0) && (cnt_x < 255)) ? ibuf[0][2] * h[2] : 'b0;
			mul[1][0] <= (cnt_x >   0) ? ibuf[1][0] * h[3] : 'b0;
			mul[1][1] <= 				 ibuf[1][1] * h[4];
			mul[1][2] <= (cnt_x < 255) ? ibuf[1][2] * h[5] : 'b0;
			mul[2][0] <= ((cnt_y < 255) && (cnt_x >   0)) ? ibuf[2][0] * h[6] : 'b0;
			mul[2][1] <= ((cnt_y < 255)                 ) ? ibuf[2][1] * h[7] : 'b0;
			mul[2][2] <= ((cnt_y < 255) && (cnt_x < 255)) ? ibuf[2][2] * h[8] : 'b0;
		end
	end
end

reg	 signed [19:0]	sum_in;
reg	 signed [19:0]	sum;
always@(*) begin
	sum_in = 0;
	for(int i=0;i<3;i++) begin
		for(int j=0;j<3;j++) begin
			sum_in = sum_in + mul[i][j];
		end
	end
end
always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		sum <= 'b0;
	end else begin
		if((cnt == 4) && (garbage == 1'b0)) begin
			sum <= sum_in;
		end
	end
end

wire	[19:0]	pd_rnd_1 = sum + (1<<6);
wire	[12:0]	pd_rnd = pd_rnd_1[19:7];
wire	[7:0]	pd_out = (pd_rnd < 0) ? 0 :
						 (pd_rnd > 255) ? 255 :
						 pd_rnd[7:0];

always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		o_strb <= 1'b0;
		o_data <= 'b0;
	end else begin
		o_strb <= ((cnt == 5) && (garbage == 1'b0));
		if((cnt == 5) && (garbage == 1'b0)) begin
			o_data <= pd_out;
		end
	end
end

endmodule

 

 

- 3.5 Parameter, DPI

module filter2d #(
				H = 256,
				W = 256
) (
				input			clk,
				input			n_reset,

				input			i_strb,
				input	[7:0]	i_data,

				output reg			o_strb,
				output reg	[7:0]	o_data,

				input			h_write,
				input	[3:0]	h_idx,
				input	[7:0]	h_data
);

reg			garbage;
reg	[3:0]	cnt;
reg	[$clog2(W)-1:0]	cnt_x;
reg	[$clog2(H)-1:0]	cnt_y;
reg	[7:0]	i_data_d;

always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		garbage <= 1'b1;
		cnt <= 7;
		cnt_x <= W-2;
		cnt_y <= H-2;
		i_data_d <= 'b0;
	end else begin
		if(i_strb == 1'b1) begin
			cnt_x <= (cnt_x == W-1) ? 0 : cnt_x+1;
			if(cnt_x == W-1) begin
				cnt_y <= (cnt_y == H-1) ? 0 : cnt_y+1;
				if(cnt_y == H-1) garbage <= 1'b0;
			end
		end
		if(i_strb == 1'b1) cnt <= 0;
		else if(cnt < 7) cnt <= cnt+1;
		if(i_strb == 1'b1) i_data_d <= i_data;
	end
end

reg 	[7:0]	ibuf[2:0][2:0];
wire	[7:0]	dout;
always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		for(int i=0;i<3;i++) begin
			for(int j=0;j<3;j++) begin
				ibuf[i][j] <= 'b0;
			end
		end
	end else begin
		if(cnt == 0) begin
			for(int i=0;i<3;i++) begin
				for(int j=0;j<2;j++) begin
					ibuf[i][j] <= ibuf[i][j+1];
				end
			end
			ibuf[2][2] <= i_data_d;
		end
		if(cnt == 1) ibuf[0][2] <= dout;
		if(cnt == 2) ibuf[1][2] <= dout;
	end
end

wire		mem_rd = (cnt == 0) || (cnt == 1);
wire		mem_wr = (cnt == 2);

localparam	BUF_LEN = 2*W;
reg		[$clog2(BUF_LEN)-1:0]	wr_addr;
wire	[$clog2(BUF_LEN)-1:0]	rd_addr0 = wr_addr;
wire	[$clog2(BUF_LEN)-1:0]	rd_addr1 = (wr_addr<W) ? wr_addr+W: wr_addr-W;
wire	[$clog2(BUF_LEN)-1:0]	rd_addr = (cnt == 0) ? rd_addr0 : rd_addr1;

always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		wr_addr <= 0;
	end else begin
		if(mem_wr == 1'b1) begin
			wr_addr <= (wr_addr == BUF_LEN-1) ? 0 : wr_addr + 1;
		end
	end
end

wire			cs = mem_rd | mem_wr;
wire			we = mem_wr;
wire	[8:0]	addr = (mem_wr == 1'b1) ? wr_addr : rd_addr;
wire	[7:0]	din = i_data_d;

mem_single #(
				.WD(8),
				.DEPTH(BUF_LEN)
) i_buf0 (
				.clk(clk),
				.cs(cs),
				.we(we),
				.addr(addr),
				.din(din),
				.dout(dout)
);

reg	signed	[7:0]	h[0:8];
always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		h[0] <= 8'h08;
		h[1] <= 8'h10;
		h[2] <= 8'h08;
		h[3] <= 8'h10;
		h[4] <= 8'h20;
		h[5] <= 8'h10;
		h[6] <= 8'h08;
		h[7] <= 8'h10;
		h[8] <= 8'h08;
	end else begin
		if(h_write == 1'b1) begin
			h[h_idx] <= h_data;
		end
	end
end

reg	signed	[15:0]	mul[2:0][2:0];
always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		for(int i=0;i<3;i++) begin
			for(int j=0;j<3;j++) begin
				mul[i][j] <= 'b0;
			end
		end
	end else begin
		if((cnt == 3) && (garbage == 1'b0)) begin
			mul[0][0] <= ((cnt_y > 0) && (cnt_x >   0)) ? ibuf[0][0] * h[0] : 'b0;
			mul[0][1] <= ((cnt_y > 0)                 ) ? ibuf[0][1] * h[1] : 'b0;
			mul[0][2] <= ((cnt_y > 0) && (cnt_x < W-1)) ? ibuf[0][2] * h[2] : 'b0;
			mul[1][0] <= (cnt_x >   0) ? ibuf[1][0] * h[3] : 'b0;
			mul[1][1] <= 				 ibuf[1][1] * h[4];
			mul[1][2] <= (cnt_x < W-1) ? ibuf[1][2] * h[5] : 'b0;
			mul[2][0] <= ((cnt_y < H-1) && (cnt_x >   0)) ? ibuf[2][0] * h[6] : 'b0;
			mul[2][1] <= ((cnt_y < H-1)                 ) ? ibuf[2][1] * h[7] : 'b0;
			mul[2][2] <= ((cnt_y < H-1) && (cnt_x < W-1)) ? ibuf[2][2] * h[8] : 'b0;
		end
	end
end

reg	 signed [19:0]	sum_in;
reg	 signed [19:0]	sum;
always@(*) begin
	sum_in = 0;
	for(int i=0;i<3;i++) begin
		for(int j=0;j<3;j++) begin
			sum_in = sum_in + mul[i][j];
		end
	end
end
always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		sum <= 'b0;
	end else begin
		if((cnt == 4) && (garbage == 1'b0)) begin
			sum <= sum_in;
		end
	end
end

wire	[19:0]	pd_rnd_1 = sum + (1<<6);
wire	[12:0]	pd_rnd = pd_rnd_1[19:7];
wire	[7:0]	pd_out = (pd_rnd < 0) ? 0 :
						 (pd_rnd > 255) ? 255 :
						 pd_rnd[7:0];

always@(posedge clk or negedge n_reset) begin
	if(n_reset == 1'b0) begin
		o_strb <= 1'b0;
		o_data <= 'b0;
	end else begin
		o_strb <= ((cnt == 5) && (garbage == 1'b0));
		if((cnt == 5) && (garbage == 1'b0)) begin
			o_data <= pd_out;
		end
	end
end

endmodule

 

module top_filter_2d;

reg		clk, n_reset;
reg		start;

initial clk = 1'b0;
always #5 clk = ~clk;

import "DPI" function void init_filter2d(input int h, input int w);
import "DPI" function byte get_input();
import "DPI" function byte get_output();

reg			i_strb;
reg	[7:0]	i_data;
initial begin
	n_reset = 1'b1;
	init_filter2d(256, 256);
	i_strb = 1'b0;
	i_data = 'bx;
	#3;
	n_reset = 1'b0;
	#20;
	n_reset = 1'b1;
	@(posedge clk);
	@(posedge clk);
	@(posedge clk);
	repeat(3) begin
		repeat(256*256) begin
			i_strb = 1'b1;
			i_data = get_input();
			@(posedge clk);
			repeat(16) begin
				i_strb = 1'b0;
				i_data = 'bx;
				@(posedge clk);
			end
		end
	end
	@(posedge clk);
	@(posedge clk);
	@(posedge clk);
	$finish;
end

wire			o_strb;
wire	[7:0]	o_data;
filter2d	i_filter (
					.clk(clk), 
					.n_reset(n_reset), 
					.i_strb(i_strb), 
					.i_data(i_data),
					.o_strb(o_strb), 
					.o_data(o_data),
					.h_write(1'b0),
					.h_idx(4'b0),
					.h_data(8'b0)
);

reg		[7:0]	out_ref;
always@(posedge clk) begin
	if(o_strb == 1'b1) begin
		out_ref = get_output();
		if(o_data != out_ref) begin
			$display("Error!! o_data = %3d, out_ref = %3d", o_data, out_ref);
			#10;
			$finish;
		end
	end
end

endmodule

 

#include <stdio.h>
#include <stdlib.h>

unsigned char	*in_img;
unsigned char	*out_img;
int				height, width;

void filter2d(void) {
	int		h[3][3] = {0x08, 0x10, 0x08, 0x10, 0x20, 0x10, 0x08, 0x10, 0x08};
	for(int i=0;i<height;i++) {
		for(int j=0;j<width;j++) {
			int	sum = 0;
			if(i>0 && j>0)				sum += in_img[(i-1)*width+j-1]*h[0][0];
			if(i>0) 					sum += in_img[(i-1)*width+j  ]*h[0][1];
			if(i>0 && j<width-1)		sum += in_img[(i-1)*width+j+1]*h[0][2];
			if(j>0)						sum += in_img[(i  )*width+j-1]*h[1][0];
										sum += in_img[(i  )*width+j  ]*h[1][1];
			if(j<width-1)				sum += in_img[(i  )*width+j+1]*h[1][2];
			if(i<height-1 && j>0)		sum += in_img[(i+1)*width+j-1]*h[2][0]; 
			if(i<height-1)				sum += in_img[(i+1)*width+j  ]*h[2][1]; 
			if(i<height-1 && j<width-1)	sum += in_img[(i+1)*width+j+1]*h[2][2];
			sum = (sum + (1<<6)) >> 7;
			if(sum < 0) out_img[i*width+j] = 0;
			else if(sum > 255) out_img[i*width+j] = 255;
			else out_img[i*width+j] = sum;
		}
	}
}

void init_filter2d(int h, int w) {
	int			i, a;
	FILE		*inf;
	inf = fopen("../c/img_in.txt", "r");

	height = h;
	width = w;
	in_img = malloc(height*width*sizeof(unsigned char));
	out_img = malloc(height*width*sizeof(unsigned char));
	for(i=0;i<height*width;i++) {
		fscanf(inf, "%d,", &a);
		in_img[i] = a;
	}

	filter2d();

	fclose(inf);
}

unsigned char get_input(void) {
	static	int	i;
	unsigned char res = in_img[i];
	i++;
	if(i==height*width) i = 0;	
	return res;
}

unsigned char get_output(void) {
	static	int	i;
	unsigned char res = out_img[i];
	i++;
	if(i==height*width) i = 0;	
	return res;
}

 

 

4. 시뮬레이션 결과

 

진행년월: 24.08

 

 

 

IDEC 강의 "SOC 설계 및 구현" 에서 실습하였습니다.

내용이 너무 방대하고, 자료가 많고 용량이 크기 때문에

적절하게 정리할 수 있는 방법을 찾고 올리도록 하겠습니다.

 

 

 

DE2-115 Board
- Intel FPGA Cyclone IV

Quartus II
- Verilog codes (H/W)
 
Keil uVision
- Assembly codes (S/W)


Process
1. Quartus II에서 사용할 Peripheral을 AHB-Lite에 적절히 연결
2. API들을 추가
3. Quartus 에서 compile, program 후 동작 확인

 

 

진행년월: 24.08

 

IDEC 강의 "SOC 설계 및 구현" 에서 실습하였습니다.

내용이 너무 방대하고, 자료가 많고 용량이 크기 때문에

적절하게 정리할 수 있는 방법을 찾고 올리도록 하겠습니다.

 

 

 

DE2-115 Board
- Intel FPGA Cyclone IV

Quartus II
- Verilog codes (H/W)
 
Keil uVision
- Assembly codes (S/W)


Process
1. Quartus II에서 사용할 Peripheral을 AHB-Lite에 적절히 연결
2. 메모리맵을 참고하여 Keil에서 ASSEMBLY 코드 작성 후 빌드
3. 완성된 code.hex를 FPGA/AHB_BRAM/에 복사
4. Quartus 에서 compile, program 후 동작 확인

 

 

진행년월: 24.08

 

IDEC 강의 "SOC 설계 및 구현" 에서 실습하였습니다.

내용이 너무 방대하고, 자료가 많고 용량이 크기 때문에

적절하게 정리할 수 있는 방법을 찾고 올리도록 하겠습니다.

 

 

 

DE2-115 Board
- Intel FPGA Cyclone IV

Quartus II
- Verilog codes (H/W)
 
Keil uVision
- Assembly codes (S/W)


Process
1. Quartus II에서 사용할 Peripheral을 AHB-Lite에 적절히 연결
2. 메모리맵을 참고하여 Keil에서 ASSEMBLY 코드 작성 후 빌드
3. 완성된 code.hex를 FPGA/AHB_BRAM/에 복사
4. Quartus 에서 compile, program 후 동작 확인

 

 

진행년월: 24.06

목차

1. 배경

2. 과제 정의 및 개요

3. 소스코드

4. 시뮬레이션 결과

 

 

1. 배경

규모가 큰 설계를 하게 된다면, 메모리에 접근은 반드시 필요하게 됩니다.

Cache는 고속으로 일을 처리해 응답시간을 줄여주고,

DRAM 등에 접근하는 횟수를 줄여주어 메모리 부하를 줄여줍니다.

 

이전에 Testbench들은 몇 가지 의도된 케이스를 직접 만들어 실험하였고,

모든 케이스가 검증되었다고 볼 수는 없습니다.

Cache 메모리 컨트롤러를 설계하며,

다양한 Test case들에 대해 Coverage를 올리는 Verification에 대해 학습해 봅니다.

 

 

2. 과제 정의 및 개요

 

위 그림처럼 원래는 Cache가 DRAM에 접근하지만,

이번 설계에서는 조금 더 간단하게 하기 위해

Bus Interface가 있다고 가정하고 설계해 보겠습니다.

 

CPU Interface
1. cpu_addr은 32-bit address
2. cpu_din/cpu_dout은 32-bit in/out data
3. cpu_cs가 0일 때 전송
4. cpu_we는 Read/Write, 0일 때 Read
5. cpu_nwait는 전송이 끝나는 flag

Dram Interface
1. dram_addr은 32-bit address
2. dram_din/dram_dout은 32-bit in/out data
3. dram_cs가 0일때 전송
4. dram_we는 Read/Write, 0일 때 Read
5. dram_nwait는 전송이 끝나는 flag
 
Cache Spec.
1. 한 line에 4 word
2. 1024 lines, block address는 10-bit
3. address는 32-bit : tag는 18-bit
즉, 18(tag)+10(block addr)+2(word addr)+2(intra word addr) = 32 bit

Cache Interface
1. cache_din/cache_dout은 data(32*4) + tag(18) = 146-bit
2. valid, dirty bit는 따로 관리

 

 

 

State Diagram과 Timing Diagram은 아래와 같습니다. 

 

3. 소스코드

module cache (
				input	clk,
				input	n_reset,

				input				cpu_cs,
				input				cpu_we,
				input		[31:0]	cpu_addr,
				input		[31:0]	cpu_din,
				output reg	[31:0]	cpu_dout,
				output				cpu_nwait,

				output			dram_cs,
				output			dram_we,
				output	[31:0]	dram_addr,
				output	[31:0]	dram_din,
				input	[31:0]	dram_dout,
				input			dram_nwait
);

parameter	IDLE   = 4'b0000;
parameter	READ   = 4'b0001;
parameter	R_WMEM = 4'b0010;
parameter	R_RMEM = 4'b0011;
parameter	R_REND = 4'b0100;
parameter	R_OUT  = 4'b0101;
parameter	WRITE  = 4'b1001;
parameter	W_WMEM = 4'b1010;
parameter	W_RMEM = 4'b1011;
parameter	W_REND = 4'b1100;

reg	[3:0]	state, next;
wire		hit, dirty, valid;
reg	[1:0]	cnt;

always@(*) begin
	next = state;
	case(state)
		IDLE: begin
				if(cpu_cs == 1'b1) begin
					if(cpu_we == 1'b1) next = WRITE;
					else next = READ;
				end
			end
		READ: begin
				if(hit == 1'b1) begin
					if(cpu_cs == 1'b1) begin
						if(cpu_we == 1'b1) next = WRITE;
						else next = READ;
					end else begin
						next = IDLE;
					end
				end else begin
					if(dirty == 1'b1) next = R_WMEM;
					else next = R_RMEM;
				end
			end
		R_WMEM: begin
				if((dram_nwait == 1'b1) && (cnt == 3)) next = R_RMEM;
			end
		R_RMEM: begin
				if((dram_nwait == 1'b1) && (cnt == 3)) next = R_REND;
			end
		R_REND: begin
				if(dram_nwait == 1'b1) next = R_OUT;
			end
		R_OUT: begin
				if(cpu_cs == 1'b1) begin
					if(cpu_we == 1'b1) next = WRITE;
					else next = READ;
				end else begin
					next = IDLE;
				end
			end
		WRITE: begin
				if(hit == 1'b1) begin
					next = IDLE;
				end else begin
					if(dirty == 1'b1) next = W_WMEM;
					else next = W_RMEM;
				end
			end
		W_WMEM: begin
				if((dram_nwait == 1'b1) && (cnt == 3)) next = W_RMEM;
			end
		W_RMEM: begin
				if((dram_nwait == 1'b1) && (cnt == 3)) next = W_REND;
			end
		W_REND: begin
				if(dram_nwait == 1'b1) next = IDLE;
			end
	endcase
end

always@(negedge n_reset or posedge clk) begin
	if(n_reset == 1'b0) begin
		state <= IDLE;
		cnt <= 0;
	end else begin
		state <= next;
		if((state == R_WMEM) || (state == R_RMEM) ||
				(state == W_WMEM) || (state == W_RMEM)) begin
			if(dram_nwait == 1'b1) begin
				cnt <= cnt + 1;
			end
		end
	end
end

reg		[31:0]	cpu_addr_d;
reg		[31:0]	cpu_din_d;
wire	[145:0]	cache_dout;
reg		[145:0]	cache_line;
always@(negedge n_reset or posedge clk) begin
	if(n_reset == 1'b0) begin
		cpu_addr_d <= 'b0;
		cpu_din_d <= 'b0;
	end else begin
		if((cpu_cs == 1'b1) && (cpu_nwait == 1'b1)) begin
			cpu_addr_d <= cpu_addr;
			if(cpu_we == 1'b1) cpu_din_d <= cpu_din;
		end
	end
end
assign cpu_nwait = (state == IDLE) ||
					((state == READ) && (hit == 1'b1)) ||
					(state == R_OUT);
always@(*) begin
	if(state == READ) begin
		case(cpu_addr_d[3:2])
			2'b00: cpu_dout = cache_dout[31:0];
			2'b01: cpu_dout = cache_dout[63:32];
			2'b10: cpu_dout = cache_dout[95:64];
			2'b11: cpu_dout = cache_dout[127:96];
		endcase
	end else begin
		case(cpu_addr_d[3:2])
			2'b00: cpu_dout = cache_line[31:0];
			2'b01: cpu_dout = cache_line[63:32];
			2'b10: cpu_dout = cache_line[95:64];
			2'b11: cpu_dout = cache_line[127:96];
		endcase
	end
end

wire	cache_read = ((state == IDLE) && (cpu_cs == 1'b1))
					|| ((state == READ) && (cpu_cs == 1'b1) && (hit == 1'b1))
					|| ((state == R_OUT) && (cpu_cs == 1'b1));
wire	cache_write = ((state == WRITE) && (hit == 1'b1))
					|| ((state == R_REND) && (dram_nwait == 1'b1))
					|| ((state == W_REND) && (dram_nwait == 1'b1));
wire	cache_cs = cache_read || cache_write;
wire	cache_we = cache_write;
wire	[9:0]	cache_addr = (state == IDLE) || (state == READ) ||
														(state == R_OUT) ?
							cpu_addr[13:4] : cpu_addr_d[13:4];
// tag 18, 4 words
reg		[145:0]	cache_din;

always@(*) begin
	if(state == WRITE) begin
		cache_din[145:0] = cache_dout[145:0];
		if(cpu_addr_d[3:2] == 2'b00) cache_din[31:0] = cpu_din_d;
		if(cpu_addr_d[3:2] == 2'b01) cache_din[63:32] = cpu_din_d;
		if(cpu_addr_d[3:2] == 2'b10) cache_din[95:64] = cpu_din_d;
		if(cpu_addr_d[3:2] == 2'b11) cache_din[127:96] = cpu_din_d;
	end else if(state == R_REND) begin
		cache_din[145:128] = cpu_addr_d[31:14];
		cache_din[127:96] = dram_dout;
		cache_din[95:0] = cache_line[95:0];
	end else if(state == W_REND) begin
		cache_din[145:128] = cpu_addr_d[31:14];
		cache_din[127:96] = dram_dout;
		cache_din[95:0] = cache_line[95:0];
		if(cpu_addr_d[3:2] == 2'b00) cache_din[31:0] = cpu_din_d;
		if(cpu_addr_d[3:2] == 2'b01) cache_din[63:32] = cpu_din_d;
		if(cpu_addr_d[3:2] == 2'b10) cache_din[95:64] = cpu_din_d;
		if(cpu_addr_d[3:2] == 2'b11) cache_din[127:96] = cpu_din_d;
	end else begin
		cache_din = cache_line;
	end
end

mem_single #(
				.WD(146),
				.DEPTH(1024)
) i_cache_mem (
				.clk(clk),
				.cs(cache_cs),
				.we(cache_we),
				.addr(cache_addr),
				.din(cache_din),
				.dout(cache_dout)
);

reg		[1023:0]	valids;
reg		[1023:0]	dirtys;
always@(negedge n_reset or posedge clk) begin
	if(n_reset == 1'b0) begin
		cache_line <= 'b0;
		valids <= 1024'b0;
		dirtys <= 1024'b0;
	end else begin
		if(state == READ) begin
			cache_line <= cache_dout;
		end else if(state == WRITE) begin
			cache_line <= cache_dout;
		end else if((state == R_RMEM) && (dram_nwait == 1'b1)) begin
			if(cnt == 2'b01) cache_line[31:0] <= dram_dout;
			if(cnt == 2'b10) cache_line[63:32] <= dram_dout;
			if(cnt == 2'b11) cache_line[95:64] <= dram_dout;
		end else if((state == R_REND) && (dram_nwait == 1'b1)) begin
			cache_line[127:96] <= dram_dout;
		end else if((state == W_RMEM) && (dram_nwait == 1'b1)) begin
			if(cnt == 2'b01) cache_line[31:0] <= dram_dout;
			if(cnt == 2'b10) cache_line[63:32] <= dram_dout;
			if(cnt == 2'b11) cache_line[95:64] <= dram_dout;
		end else if((state == W_REND) && (dram_nwait == 1'b1)) begin
			cache_line[127:96] <= dram_dout;
		end
		if((state == WRITE) && (hit == 1'b1)) begin
			dirtys[cpu_addr_d[13:4]] <= 1'b1;
		end else if((state == R_REND) && (dram_nwait == 1'b1)) begin
			dirtys[cpu_addr_d[13:4]] <= 1'b0;
		end else if((state == W_REND) && (dram_nwait == 1'b1)) begin
			dirtys[cpu_addr_d[13:4]] <= 1'b1;
		end
		if((state == R_REND) && (dram_nwait == 1'b1)) begin
			valids[cpu_addr_d[13:4]] <= 1'b1;
		end else if((state == W_REND) && (dram_nwait == 1'b1)) begin
			valids[cpu_addr_d[13:4]] <= 1'b1;
		end
	end
end

assign	valid = valids[cpu_addr_d[13:4]];
assign	dirty = dirtys[cpu_addr_d[13:4]];
wire	[17:0]	tag = cache_dout[145:128];
assign	hit = (tag == cpu_addr_d[31:14]) &&
			  (valid == 1'b1);


wire	dram_read = (state == R_RMEM) || (state == W_RMEM);
wire	dram_write = (state == R_WMEM) || (state == W_WMEM);
assign	dram_cs = dram_read || dram_write;
assign	dram_we = dram_write;
assign	dram_addr = (state == R_RMEM) || (state == W_RMEM) ?
						{cpu_addr_d[31:4], cnt, 2'b00} :
						{cache_line[145:128], cpu_addr_d[13:4],cnt, 2'b00};
assign	dram_din = (cnt == 2'b00) ? cache_line[31:0] :
				   (cnt == 2'b01) ? cache_line[63:32] :
				   (cnt == 2'b10) ? cache_line[95:64] :
									cache_line[127:96];

endmodule

 

 

module mem_single #(
		  WD = 128
		, DEPTH = 64
		, WA = $clog2(DEPTH)
) ( 
		  input					clk
		, input					cs
		, input					we
		, input		[WA-1:0]	addr
		, input		[WD-1:0]	din
		, output 	[WD-1:0]	dout
);

reg	[WD-1:0]	data[DEPTH-1:0];
reg	[WA-1:0]	addr_d;

always@(posedge clk) begin
	if(cs == 1'b1) begin
		if(we == 1'b1) data[addr] <= din;
		addr_d <= addr;
	end
end
assign dout = data[addr_d];

endmodule

 

편의를 위해 system-verilog가 사용되었습니다.

verilog-HDL로 컴파일하기 위해서는

Parameter 자리에 WA 부분을 localparam으로 옮기고,

이에 따라 input 정의에 WA가 아닌 직접 기술해줘야 합니다.

 

 

- top0 (Test case 직접 구상)

module top_cache;

reg		clk, n_reset;

initial clk = 1'b0;
always #5 clk = ~clk;

reg	[31:0]	dram_data[0:64*1024*1024-1];

initial begin
	$vcdplusfile("cache.vpd");
	$vcdpluson(0,top_cache);
end

reg				cpu_cs;
reg				cpu_we;
reg		[31:0]	cpu_addr;
reg		[31:0]	cpu_din;
wire	[31:0]	cpu_dout;
wire			cpu_nwait;
initial begin
	n_reset = 1'b1;
	for(int i=0;i<64*1024*1024;i++) dram_data[i] = $random;
	#3;
	n_reset = 1'b0;
	#20;
	n_reset = 1'b1;
	cpu_cs = 1'b0;

	@(posedge clk);
	@(posedge clk);
	@(posedge clk);

	// first miss
	cpu_cs = 1'b1;
	cpu_we = 1'b0;
	cpu_addr = 32'h00A37B9C;
	while(1) begin
		@(posedge clk);
		#6;
		cpu_cs = 1'b0;
		if(cpu_nwait == 1'b1) break;
	end

	// hit
	cpu_cs = 1'b1;
	cpu_we = 1'b0;
	cpu_addr = 32'h00A37B98;
	while(1) begin
		@(posedge clk);
		#6;
		cpu_cs = 1'b0;
		if(cpu_nwait == 1'b1) break;
	end

	// miss on the same cache line
	cpu_cs = 1'b1;
	cpu_we = 1'b0;
	cpu_addr = 32'h00A3BB98;
	while(1) begin
		@(posedge clk);
		#6;
		cpu_cs = 1'b0;
		if(cpu_nwait == 1'b1) break;
	end

	// write hit
	cpu_cs = 1'b1;
	cpu_we = 1'b1;
	cpu_addr = 32'h00A3BB90;
	cpu_din = 32'hFFFFFFFF;
	while(1) begin
		@(posedge clk);
		#6;
		cpu_cs = 1'b0;
		if(cpu_nwait == 1'b1) break;
	end

	// miss & write back because of dirty
	cpu_cs = 1'b1;
	cpu_we = 1'b0;
	cpu_addr = 32'h00A37B94;
	while(1) begin
		@(posedge clk);
		#6;
		cpu_cs = 1'b0;
		if(cpu_nwait == 1'b1) break;
	end

	// miss on the same cache line
	cpu_cs = 1'b1;
	cpu_we = 1'b0;
	cpu_addr = 32'h00A3BB90;
	while(1) begin
		@(posedge clk);
		#6;
		cpu_cs = 1'b0;
		if(cpu_nwait == 1'b1) break;
	end

	@(posedge clk);
	@(posedge clk);
	@(posedge clk);
	@(posedge clk);
	$finish;
end

wire			dram_cs;
wire			dram_we;
wire	[31:0]	dram_addr;
wire	[31:0]	dram_din;
wire	[31:0]	dram_dout;
wire			dram_nwait;

cache i_cache(
				.clk(clk),
				.n_reset(n_reset),

				.cpu_cs(cpu_cs),
				.cpu_we(cpu_we),
				.cpu_addr(cpu_addr),
				.cpu_din(cpu_din),
				.cpu_dout(cpu_dout),
				.cpu_nwait(cpu_nwait),

				.dram_cs(dram_cs),
				.dram_we(dram_we),
				.dram_addr(dram_addr),
				.dram_din(dram_din),
				.dram_dout(dram_dout),
				.dram_nwait(dram_nwait)
);

reg		[1:0]	cnt;
reg				dram_we_d;
reg		[31:0]	dram_addr_d;
reg		[31:0]	dram_din_d;
always@(negedge n_reset or posedge clk) begin
	if(n_reset == 1'b0) begin
		cnt <= 0;
	end else begin
		if((dram_cs == 1'b1) || (cnt > 0)) begin
			cnt <= cnt + 1;
		end
		if((dram_cs == 1'b1) && (dram_nwait == 1'b1)) begin
			dram_we_d <= dram_we;
			dram_addr_d <= dram_addr;
			dram_din_d <= dram_din;
		end
		if((dram_we_d == 1'b1) && (cnt == 3)) begin
			dram_data[dram_addr_d[31:2]] <= dram_din_d;
		end
	end
end

assign dram_dout = (dram_nwait==1'b1) ? dram_data[dram_addr_d[31:2]] : 'bx;
assign dram_nwait = (cnt == 0);

endmodule

 

기존의 방식처럼 4가지 경우의 수를 직접 기술한 테스트벤치입니다.

편하게 확장하기 위해 system verilog로 작성하였습니다.

 

 

- top1 Random generation

module top_cache;
parameter	DRAM_SIZE = 64*1024*1024;

reg		clk, n_reset;

initial clk = 1'b0;
always #5 clk = ~clk;

reg	[31:0]	dram_data[0:DRAM_SIZE-1];
reg	[31:0]	dram_data_ref[0:DRAM_SIZE-1];

initial begin
	$shm_open("./waveform");
	$shm_probe(top_cache,"AS");
end

reg				cpu_cs;
reg				cpu_we;
reg		[31:0]	cpu_addr;
reg		[31:0]	cpu_din;
wire	[31:0]	cpu_dout;
wire			cpu_nwait;
initial begin
	n_reset = 1'b1;
	for(int i=0;i<DRAM_SIZE;i++) begin
		dram_data[i] = $random;
		dram_data_ref[i] = dram_data[i];
	end
	#3;
	n_reset = 1'b0;
	#20;
	n_reset = 1'b1;
	cpu_cs = 1'b0;

	@(posedge clk);
	@(posedge clk);
	@(posedge clk);
	#6;

	repeat(10000) begin                   // -----> 10000 cases
		cpu_cs = $random % 2;             // -----> access or not
		if(cpu_cs == 1'b1) begin
			cpu_we = $random % 2;         // -----> read or write
			cpu_addr = {$random & (DRAM_SIZE-1), 2'b00}; // ----> random addr
			if(cpu_we == 1'b1) cpu_din = $random;        // ----> random data on write
			else cpu_din = 'bx;
		end else begin
			cpu_we = 1'bx;
			cpu_addr = 'bx;
			cpu_din = 'bx;
		end
		while(1) begin
			@(posedge clk);
			#6;
			if(cpu_nwait == 1'b1) begin
				break;
			end else begin
				cpu_cs = 1'bx;
				cpu_we = 1'bx;
				cpu_addr = 'bx;
				cpu_din = 'bx;
			end
		end
	end

	@(posedge clk);
	@(posedge clk);
	@(posedge clk);
	@(posedge clk);
	$finish;
end

wire			dram_cs;
wire			dram_we;
wire	[31:0]	dram_addr;
wire	[31:0]	dram_din;
wire	[31:0]	dram_dout;
reg				dram_nwait;

cache i_cache(
				.clk(clk),
				.n_reset(n_reset),

				.cpu_cs(cpu_cs),
				.cpu_we(cpu_we),
				.cpu_addr(cpu_addr),
				.cpu_din(cpu_din),
				.cpu_dout(cpu_dout),
				.cpu_nwait(cpu_nwait),

				.dram_cs(dram_cs),
				.dram_we(dram_we),
				.dram_addr(dram_addr),
				.dram_din(dram_din),
				.dram_dout(dram_dout),
				.dram_nwait(dram_nwait)
);

reg		[1:0]	cnt;
reg				dram_we_d;
reg		[31:0]	dram_addr_d;
reg		[31:0]	dram_din_d;
always@(negedge n_reset or posedge clk) begin
	if(n_reset == 1'b0) begin
		cnt <= 0;
	end else begin
		if((dram_cs == 1'b1) || (cnt > 0)) begin
			cnt <= cnt + 1;
		end
		if((dram_cs == 1'b1) && (dram_nwait == 1'b1)) begin
			dram_we_d <= dram_we;
			dram_addr_d <= dram_addr;
			dram_din_d <= dram_din;
		end
		if((dram_we_d == 1'b1) && (cnt == 3)) begin
			dram_data[dram_addr_d[31:2]] <= dram_din_d;
		end
	end
end

assign dram_dout = (dram_nwait==1'b1) ? dram_data[dram_addr_d[31:2]] : 'bx;
assign dram_nwait = (cnt == 0);

reg		[31:0]	cpu_addr_d;
reg		[31:0]	cpu_din_d;
reg		[1:0]	cpu_prev_op;
always@(posedge clk) begin
	if(cpu_nwait == 1'b1) begin
		cpu_prev_op <= {cpu_cs, cpu_we};
		if(cpu_cs == 1'b1) begin
			cpu_addr_d <= cpu_addr;
			cpu_din_d <= cpu_din;
		end
		if(cpu_prev_op == 2'b10) begin
			if(dram_data_ref[cpu_addr_d[31:2]] == cpu_dout) begin
			end else begin
				$display("Error!! addr = %X, dram_data = %X, cpu_dout = %X",
					cpu_addr_d, dram_data_ref[cpu_addr_d[31:2]], cpu_dout);
				#10; $finish;
			end
		end else if(cpu_prev_op == 2'b11) begin
			dram_data_ref[cpu_addr_d[31:2]] <= cpu_din_d;
		end
	end
end


endmodule

 

직접 기술할 수 없을 수많은 케이스를 확인하기 위해

Random generation을 사용해 10000가지 경우의 수로 검증합니다.

 

 

- top2 (Constrained Random generation)

module top_cache;
parameter	DRAM_SIZE = 64*1024*1024;

reg		clk, n_reset;

initial clk = 1'b0;
always #5 clk = ~clk;

reg	[31:0]	dram_data[0:DRAM_SIZE-1];
reg	[31:0]	dram_data_ref[0:DRAM_SIZE-1];

initial begin
	$shm_open("./waveform");
	$shm_probe(top_cache,"AS");
end

reg				cpu_cs;
reg				cpu_we;
reg		[31:0]	cpu_addr;
reg		[31:0]	cpu_din;
wire	[31:0]	cpu_dout;
wire			cpu_nwait;
reg		[31:0]	p_addr;
initial begin
	n_reset = 1'b1;
	for(int i=0;i<DRAM_SIZE;i++) begin
		dram_data[i] = $random;
		dram_data_ref[i] = dram_data[i];
	end
	#3;
	n_reset = 1'b0;
	#20;
	n_reset = 1'b1;
	cpu_cs = 1'b0;

	@(posedge clk);
	@(posedge clk);
	@(posedge clk);
	#6;

	p_addr = {$random & (DRAM_SIZE-1), 2'b00};
	repeat(10000) begin
		cpu_cs = $random % 2;
		if(cpu_cs == 1'b1) begin
			cpu_we = $random % 2;
			if($random%4 > 0) begin         // -----> in 75% probability
				if($random%2 == 0) cpu_addr = p_addr + $random%4 * 4;     // --    increase
				else cpu_addr = p_addr - $random%4 * 4;                   //  |--> or decrease
				if(cpu_addr >= DRAM_SIZE*4) cpu_addr = (DRAM_SIZE-1) * 4; // --    previous addr
			end else begin                  // -----> in 25% probability
				cpu_addr = {$random & (DRAM_SIZE-1), 2'b00};  // ----> new random addr
			end
			p_addr = cpu_addr;                                // ----> save generated addr
			if(cpu_we == 1'b1) cpu_din = $random;
			else cpu_din = 'bx;
		end else begin
			cpu_we = 1'bx;
			cpu_addr = 'bx;
			cpu_din = 'bx;
		end
		while(1) begin
			@(posedge clk);
			#6;
			if(cpu_nwait == 1'b1) begin
				break;
			end else begin
				cpu_cs = 1'bx;
				cpu_we = 1'bx;
				cpu_addr = 'bx;
				cpu_din = 'bx;
			end
		end
	end

	@(posedge clk);
	@(posedge clk);
	@(posedge clk);
	@(posedge clk);
	$finish;
end

wire			dram_cs;
wire			dram_we;
wire	[31:0]	dram_addr;
wire	[31:0]	dram_din;
wire	[31:0]	dram_dout;
reg				dram_nwait;

cache i_cache(
				.clk(clk),
				.n_reset(n_reset),

				.cpu_cs(cpu_cs),
				.cpu_we(cpu_we),
				.cpu_addr(cpu_addr),
				.cpu_din(cpu_din),
				.cpu_dout(cpu_dout),
				.cpu_nwait(cpu_nwait),

				.dram_cs(dram_cs),
				.dram_we(dram_we),
				.dram_addr(dram_addr),
				.dram_din(dram_din),
				.dram_dout(dram_dout),
				.dram_nwait(dram_nwait)
);

reg		[1:0]	cnt;
reg				dram_we_d;
reg		[31:0]	dram_addr_d;
reg		[31:0]	dram_din_d;
always@(negedge n_reset or posedge clk) begin
	if(n_reset == 1'b0) begin
		cnt <= 0;
	end else begin
		if((dram_cs == 1'b1) || (cnt > 0)) begin
			cnt <= cnt + 1;
		end
		if((dram_cs == 1'b1) && (dram_nwait == 1'b1)) begin
			dram_we_d <= dram_we;
			dram_addr_d <= dram_addr;
			dram_din_d <= dram_din;
		end
		if((dram_we_d == 1'b1) && (cnt == 3)) begin
			dram_data[dram_addr_d[31:2]] <= dram_din_d;
		end
	end
end

assign dram_dout = (dram_nwait==1'b1) ? dram_data[dram_addr_d[31:2]] : 'bx;
assign dram_nwait = (cnt == 0);

reg		[31:0]	cpu_addr_d;
reg		[31:0]	cpu_din_d;
reg		[1:0]	cpu_prev_op;
always@(posedge clk) begin
	if(cpu_nwait == 1'b1) begin
		cpu_prev_op <= {cpu_cs, cpu_we};
		if(cpu_cs == 1'b1) begin
			cpu_addr_d <= cpu_addr;
			cpu_din_d <= cpu_din;
		end
		if(cpu_prev_op == 2'b10) begin
			if(dram_data_ref[cpu_addr_d[31:2]] == cpu_dout) begin
			end else begin
				$display("Error!! addr = %X, dram_data = %X, cpu_dout = %X",
					cpu_addr_d, dram_data_ref[cpu_addr_d[31:2]], cpu_dout);
				#10; $finish;
			end
		end else if(cpu_prev_op == 2'b11) begin
			dram_data_ref[cpu_addr_d[31:2]] <= cpu_din_d;
		end
	end
end


endmodule

 

무작위로 할 경우, 원하는 상황이 나올지 안 나올지 확인하기 힘듭니다.

제약조건을 걸어 75% 확률로 hit를 만들고,

이전 주소에서 word단위로 이동되도록 합니다.

이 과정에서 DRAM의 유효범위를 넘지 않게 합니다.

 

 

-top3 (task 적용)

module top_cache;
parameter	DRAM_SIZE = 64*1024*1024;

reg		clk, n_reset;

initial clk = 1'b0;
always #5 clk = ~clk;

reg	[31:0]	dram_data[0:DRAM_SIZE-1];
reg	[31:0]	dram_data_ref[0:DRAM_SIZE-1];

initial begin
	$shm_open("./waveform");
	$shm_probe(top_cache,"AS");
end

reg				cpu_cs;
reg				cpu_we;
reg		[31:0]	cpu_addr;
reg		[31:0]	cpu_din;
wire	[31:0]	cpu_dout;
wire			cpu_nwait;

task mem_drive (
	input			cs,
	input			we,
	input	[31:0]	addr,
	input	[31:0]	din
);
begin
	cpu_cs = cs;
	if(cs == 1'b1) begin
		cpu_we = we;
		cpu_addr = addr;
		if(we == 1'b1) cpu_din = din;
		else cpu_din = 'bx;
	end else begin
		cpu_we = 1'bx;
		cpu_addr = 'bx;
		cpu_din = 'bx;
	end
	while(1) begin
		@(posedge clk);
		#6;
		if(cpu_nwait == 1'b1) begin
			break;
		end else begin
			cpu_cs = 1'bx;
			cpu_we = 1'bx;
			cpu_addr = 'bx;
			cpu_din = 'bx;
		end
	end
end
endtask

reg				cs;
reg				we;
reg		[31:0]	addr;
reg		[31:0]	din;
reg		[31:0]	p_addr;

initial begin
	n_reset = 1'b1;
	for(int i=0;i<DRAM_SIZE;i++) begin
		dram_data[i] = $random;
		dram_data_ref[i] = dram_data[i];
	end
	#3;
	n_reset = 1'b0;
	#20;
	n_reset = 1'b1;
	cpu_cs = 1'b0;

	@(posedge clk);
	@(posedge clk);
	@(posedge clk);
	#6;

	// first miss
	mem_drive(1'b1, 1'b0, 32'h00A37B9C, 'b0);
	// hit
	mem_drive(1'b1, 1'b0, 32'h00A37B98, 'b0);
	// miss on the same cache line
	mem_drive(1'b1, 1'b0, 32'h00A3BB98, 'b0);
	// write hit
	mem_drive(1'b1, 1'b1, 32'h00A3BB90, 32'hFFFFFFFF);
	// miss & write back because of dirty
	mem_drive(1'b1, 1'b0, 32'h00A37B94, 'b0);
	// miss on the same cache line
	mem_drive(1'b1, 1'b0, 32'h00A3BB90, 'b0);

	p_addr = {$random & (DRAM_SIZE-1), 2'b00};
	repeat(10000) begin
		cs = $random % 2;
		we = $random % 2;
		if($random%4 > 0) begin
			if($random%2 == 0) addr = p_addr + $random%4 * 4;
			else addr = p_addr - $random%4 * 4;
			if(addr >= DRAM_SIZE*4) addr = (DRAM_SIZE-1) * 4;
		end else begin
			addr = {$random & (DRAM_SIZE-1), 2'b00};
		end
		din = $random;
		p_addr = addr;

		mem_drive(cs, we, addr, din);
	end

	@(posedge clk);
	@(posedge clk);
	@(posedge clk);
	@(posedge clk);
	$finish;
end

wire			dram_cs;
wire			dram_we;
wire	[31:0]	dram_addr;
wire	[31:0]	dram_din;
wire	[31:0]	dram_dout;
reg				dram_nwait;

cache i_cache(
				.clk(clk),
				.n_reset(n_reset),

				.cpu_cs(cpu_cs),
				.cpu_we(cpu_we),
				.cpu_addr(cpu_addr),
				.cpu_din(cpu_din),
				.cpu_dout(cpu_dout),
				.cpu_nwait(cpu_nwait),

				.dram_cs(dram_cs),
				.dram_we(dram_we),
				.dram_addr(dram_addr),
				.dram_din(dram_din),
				.dram_dout(dram_dout),
				.dram_nwait(dram_nwait)
);

reg		[1:0]	cnt;
reg				dram_we_d;
reg		[31:0]	dram_addr_d;
reg		[31:0]	dram_din_d;
always@(negedge n_reset or posedge clk) begin
	if(n_reset == 1'b0) begin
		cnt <= 0;
	end else begin
		if((dram_cs == 1'b1) || (cnt > 0)) begin
			cnt <= cnt + 1;
		end
		if((dram_cs == 1'b1) && (dram_nwait == 1'b1)) begin
			dram_we_d <= dram_we;
			dram_addr_d <= dram_addr;
			dram_din_d <= dram_din;
		end
		if((dram_we_d == 1'b1) && (cnt == 3)) begin
			dram_data[dram_addr_d[31:2]] <= dram_din_d;
		end
	end
end

assign dram_dout = (dram_nwait==1'b1) ? dram_data[dram_addr_d[31:2]] : 'bx;
assign dram_nwait = (cnt == 0);

reg		[31:0]	cpu_addr_d;
reg		[31:0]	cpu_din_d;
reg		[1:0]	cpu_prev_op;
always@(posedge clk) begin
	if(cpu_nwait == 1'b1) begin
		cpu_prev_op <= {cpu_cs, cpu_we};
		if(cpu_cs == 1'b1) begin
			cpu_addr_d <= cpu_addr;
			cpu_din_d <= cpu_din;
		end
		if(cpu_prev_op == 2'b10) begin
			if(dram_data_ref[cpu_addr_d[31:2]] == cpu_dout) begin
			end else begin
				$display("Error!! addr = %X, dram_data = %X, cpu_dout = %X",
					cpu_addr_d, dram_data_ref[cpu_addr_d[31:2]], cpu_dout);
				#10; $finish;
			end
		end else if(cpu_prev_op == 2'b11) begin
			dram_data_ref[cpu_addr_d[31:2]] <= cpu_din_d;
		end
	end
end


endmodule

 

처음에 의도한 4개의 case를 보고, random 한 케이스를 봅니다.

4개의 case는 주소만 다를 뿐 같은 작업이므로,

task 함수를 만들어 활용합니다.

 

 

4. 시뮬레이션 결과

동작을 확인해 보기 위해 function simulation을 합니다.

Tool은 Xcelium과 VCS를 사용하였습니다.

 

VCS 툴에서는 Coverage를 제공합니다.

Coverage는, Statement, line, block coverage를 계산해

모든 경우의 수 중 얼마나 검증해 봤는가 확인해 주는 기능입니다.

 

다음과 같이 사용할 수 있으며, 이후 결과만 보여드리도록 하겠습니다.

 

- 명령어

vcs -full64 -sverilog -debug_access+all top0.v cache.v mem_sim.v -cm line+cond+fsm+tgl+branch -cmhier cov.conf

./simv -cm line+cond+fsm+tgl+branch
urg -full64 -dir ./simv.vdb -format text
vi urgReport/modinfo.txt

 

 

- cov.conf

+tree top_cache.i_cache

 

 

 

 

4-1. top0 (Testcase 직접구상)

 

1. 처음으로 읽는 부분은 miss이고, dram에서 데이터를 읽어옵니다.

이때, 근처의 데이터 포함 4개의 데이터가 읽어져 cache에 저장됩니다.

 

2. 그다음 비슷한 주소를 읽으면, cache에 저장된 데이터가 즉시 불러와집니다.

 

1. 그 다음 miss에 의해 다시 dram에서 데이터를 읽어옵니다.

 

2. 그 다음 cache에 저장된 주소중 하나에 쓰기를 진행하면(hit), 즉시 cache의 데이터가 수정됩니다.

 

 

1. 이후 miss 읽기를 진행하면, dirty bit가 1이기 때문에,

먼저 기존 주소의 dram을 update 하기 위해 쓰기를 진행합니다.

 

2. 그다음 원래 요청주소에 대해 dram에서 데이터를 읽어옵니다.

 

 

- Coverage_top0

 

직접 만든 케이스들은, 모든 경우의 수를 테스트하지 못합니다.

그래서 coverage score 역시 매우 낮습니다.

 

 

 

4-2. top1 (random generation)

 

random generation에 의해 1만 가지 case가 동작하고 있습니다.

 

- Coverage_top1

 

top0와 달리 점수가 월등하게 오른 것을 볼 수 있습니다.

 

 

 

4-3. top2 (Constrained random generation)

이 경우 역시 top1과 마찬가지로 waveform은 의미가 없으므로, 바로 점수를 보도록 하겠습니다.

 

 

- Coverage_top2

 

조금이지만 score가 더 올라간 것을 확인할 수 있습니다.

 

 

 

 

위처럼 직접 찾아 확인해 볼 수 있고, 다시 방법을 찾아 검증할 수 있습니다.

현재는 Cover 되지 않은 부분 중에 문제가 되는 경우는 없습니다.

따라서 여기까지만 테스트하도록 합니다.

 

 

 

※참고: 직접 확인하는 방법 말고, functional coverage를 사용하는 방법도 있습니다.

program automatic test;
    covergroup fcov @(port_event);   // coverage group 생성
        coverpoint sa;
        coverpoint da;
    endgroup: fcov

    bit[3:0] sa, da;
    event port_event;
    real coverage = 0.0;
    fcov port_fc = new();    // Instantiate & coverage object 생성 

    initial while (coverage < 99.5) begin
        ...
        sa = pkt_ref.sa;
        da = pkt_ref.da;
        ->port_event;      // port_fc coverage group의 data가 sampling 됨
        // port_fc.sample(); // alternative form of updating of bins

        coverage = $get_coverage(); // overall coverage  , coverage result query
        // coverage = port_fc.get_inst_coverage(); // instance coverage
    end
endprogram: test

출처: https://wikidocs.net/172887

 

다음 검증에는 위와 같은 방법도 고려해보려고 합니다.

- 이전 글

(1) RTL : https://chonh0531.tistory.com/5

 

UART 통신 - (1) RTL

목차1. 배경2. 과제 정의 및 개요3. 소스코드4. 시뮬레이션 결과  1. 배경지난 프로젝트에서 컴퓨터와 신호를 주고받기 위해 UART 통신을 사용하였는데요,UART 통신도 이해하고 Verilog로 구현해보고

chonh0531.tistory.com

 

목차

1. 과제 개요

2. 소스코드

3. 실습 결과

 

 

1. 과제 개요

이전에 구현해 본 UART_RX를 테스트해 보기로 합니다.

UART 신호 생성은 ARM Cortex-M4 Core가 있는 NUCLEO-F429ZI 보드를 이용하여

FPGA에서 LCD를 제어해 보도록 합니다.

 

1. 컴퓨터에서 UART 통신으로 Nucleo 보드에 명령을 전달
2. Nucleo 보드는 FPGA에 다시 UART 통신으로 명령을 전달
3. FPGA는 data 부분의 마지막 4비트를 모터 속도제어에 이용

 

 

 

2. 소스코드

- Top 모듈 Uart_lcd

`timescale 1ns / 1ps

module uart_lcd(
input clk,
input rst,
input rx,
output [7:0]data,
output lcd_e,
output lcd_rs
    );
 wire rx_clk;
     
 wire [7:0]rx_register;
 wire [4:0]clk_cnt;
 wire [3:0]rx_cnt;    
 wire [7:0]lcd_register;
 wire [6:0]lcd_cnt;
 
uart_clock #(.max(163)) C0 (.clk_in(clk),.rst(rst),.clk_out(rx_clk));    
    
uart_rx R0 (.clk(rx_clk),.rst(rst),.rx(rx),.rx_register(rx_register),.clk_cnt(clk_cnt),.rx_cnt(rx_cnt));

lcd L0 (.clk(clk),.rx_clk(rx_clk),.rst(rst),.rx_register(rx_register),.rx_cnt(rx_cnt),.clk_cnt(clk_cnt),.lcd_e(lcd_e),.lcd_rs(lcd_rs),.data(data),.lcd_cnt(lcd_cnt),.lcd_register(lcd_register));

ila_0 ila_0 (.clk(clk),.probe0(lcd_cnt),.probe1(rx),.probe2(data),.probe3(lcd_register),.probe4(rx_cnt));
    
endmodule

 

 

- UART_RX

module uart_rx(
input clk,
input rx,
input rst,
output reg [7:0]rx_register,
output reg rx_end,
output reg [4:0]clk_cnt,
output reg [3:0]rx_cnt
    );  

localparam RX_IDLE = 2'b00;
localparam RX_START = 2'b01;
localparam RX_STOP = 2'b10;

reg [1:0]current_state ;
reg [1:0]next_state;
    
reg [15:0]rx_check;
    
always @(posedge clk or posedge rst) begin
    if (rst)
        clk_cnt <= 0;   
    else if (clk_cnt == 16 && rx_cnt != 11)
        clk_cnt <= 1;
    else if (clk_cnt == 16 && rx_cnt == 11)
        clk_cnt <= 0;    
    else 
        case (next_state)
        RX_IDLE : clk_cnt <= 0;
        RX_START : clk_cnt <= clk_cnt+1;
        RX_STOP : clk_cnt <= clk_cnt+1;
        default : clk_cnt <= clk_cnt;
        endcase                
    end

always @(posedge clk or posedge rst) begin
    if (rst)
        rx_cnt <= 0;
    else if (rx_cnt == 11 && clk_cnt == 16)
        rx_cnt <= 0;
    else if (current_state == RX_IDLE && rx == 0)
        rx_cnt <= 1;
    else if (clk_cnt == 16)
        rx_cnt <= rx_cnt+1;
    else
        rx_cnt <= rx_cnt;
    end                            
    
always @(posedge clk or posedge rst) begin
    if (rst)
        current_state <= RX_IDLE;
    else
        current_state <= next_state;
    end   

always @(negedge clk or posedge rst) begin
    if (rst)
        next_state <= RX_IDLE;
    else
        case(current_state)
        RX_IDLE : if (rx == 0)
                    next_state <= RX_START;
               else       
                    next_state <= next_state;                    
        RX_START : if (rx_cnt == 11)
                    next_state <= RX_STOP;
                else 
                    next_state <= next_state;                              
        RX_STOP : if (rx_cnt == 0)
                    next_state <= RX_IDLE;      
               else
                    next_state <= next_state;
        default : next_state <= next_state;                           
        endcase
    end    
    
 reg [4:0]rx_score;
    
always @(posedge clk or posedge rst) begin
    if (rst)
        rx_check <= 16'b1111_1111_1111_1111;          
    else 
        rx_check <= {rx_check[15:0], rx};                   
    end

always @(posedge clk or posedge rst) begin
    if (rst)
        rx_score <= 0;
    else if (rx_cnt == 0)
        rx_score <= 0;    
    else if (clk_cnt ==16)
        rx_score <= 0;
    else if (rx_cnt == 11)
        rx_score <= 0;        
    else if (rx == 1)
        rx_score <= rx_score+1;       
    else 
        rx_score <= rx_score;
    end             

reg rx_sampling;

always @(posedge clk or posedge rst) begin
    if (rst)
        rx_sampling <= 1;
    else if (rx_cnt == 0)
        rx_sampling <= 1;    
    else if (clk_cnt == 16 && rx_score > 8 && rx_cnt != 11)        
        rx_sampling <= 1;
    else if (clk_cnt == 16 && rx_score < 8 && rx_cnt != 11)        
        rx_sampling <= 0;    
    else 
        rx_sampling <= rx_sampling;
    end

reg [9:0]rx_received;    
    
always @(posedge clk or posedge rst) begin
    if (rst)
        rx_received <= 10'b00_0000_0000;
    else if (clk_cnt == 1 && rx_cnt >1) 
        rx_received <= {rx_received[8:0], rx_sampling};
    else 
        rx_received <= rx_received;
    end    

always @(posedge clk or posedge rst) begin
    if (rst)
        rx_register <= 8'b0000_0000;
    else if (rx_cnt == 11 && clk_cnt == 2)
        rx_register <= rx_received[8:1];
    else 
        rx_register <= rx_register;
    end                                              

always @(posedge clk or posedge rst) begin
    if (rst)
        rx_end <= 1;
    else 
        case (next_state)
        RX_IDLE : rx_end <= 1;
        RX_START : rx_end <= 0;
        RX_STOP : rx_end <= 0;
        default : rx_end <= rx_end;
        endcase
    end    
       
endmodule

 

이전 포스팅에서 언급하였듯이,

코드를 최적화하지 않고 직관적으로 코딩한 후 그대로 두었습니다.

 

- UART_Clock

`timescale 1ns / 1ps

module uart_clock(
input clk_in,
input rst,
output reg clk_out
    );
    
reg [10:0]clk_cnt;

parameter max = 1;
    
always @(posedge clk_in or posedge rst) begin
    if (rst) begin
        clk_cnt <= 0;
        end
    else if (clk_cnt == max) begin        
        clk_cnt <= 0;
        end
    else 
        clk_cnt <= clk_cnt + 1;
    end

always @(posedge clk_in or posedge rst) begin
    if (rst) begin
        clk_out <= 0;
        end
    else if (clk_cnt == max) begin        
        clk_out <= !clk_out;
        end
    else 
        clk_out <= clk_out;
    end    
            
endmodule

 

 

- LCD

`timescale 1ns / 1ps

module lcd(
input clk,
input rx_clk,
input rst,
input [7:0]rx_register,
input [3:0]rx_cnt,
input [4:0]clk_cnt,
output reg lcd_e,
output reg lcd_rs,
output reg [7:0]data,
output reg [6:0]lcd_cnt,
output reg [7:0]lcd_register
    );
    
 reg [7:0] dspdata [0:37];
 integer i = 0;
 integer j = 0;
 
    always @(posedge rx_clk or posedge rst) begin
        if (rst)
            lcd_register <= 8'h80;
        else if (rx_cnt == 11 && clk_cnt == 16) begin
            lcd_register[0] <= rx_register[7];
            lcd_register[1] <= rx_register[6];
            lcd_register[2] <= rx_register[5];
            lcd_register[3] <= rx_register[4];
            lcd_register[4] <= rx_register[3];
            lcd_register[5] <= rx_register[2];
            lcd_register[6] <= rx_register[1];
            lcd_register[7] <= rx_register[0];
            end
        else
            lcd_register <= lcd_register;
        end            
    
    always @(posedge rx_clk or posedge rst) begin
        if (rst)
            lcd_cnt <= 4;
        else if (lcd_cnt == 20 && rx_cnt == 11 && clk_cnt == 16)
            lcd_cnt <= 22;
        else if (lcd_cnt == 37 && rx_cnt == 11 && clk_cnt == 16)
            lcd_cnt <= 5;       
        else if (rx_cnt== 11 && clk_cnt == 16)
            lcd_cnt <= lcd_cnt+1;             
        else
            lcd_cnt <= lcd_cnt;
        end                    
    
    always @(posedge clk or posedge rst) begin
        if (rst) begin
            dspdata[0] <= 8'b00111000;    // function set 8bit, 2line, 5x7 dot
            dspdata[1] <= 8'b00001100;    // display on/off , display on, cursor off, cursor blink off 
            dspdata[2] <= 8'b00000110;    // entry mode set increment cursor position, no display shift
            dspdata[3] <= 8'b00000001;    // clear display
            dspdata[4] <= 8'h80;    // set cg ram address 1000 0000   1번라인 첫번째부터
            dspdata[5] <= 8'b00110000;    // 0
            dspdata[6] <= 8'b00110000;    // 0
            dspdata[7] <= 8'b00110000;    // 0
            dspdata[8] <= 8'b00110000;    // 0
            dspdata[9] <= 8'b00110000;    // 0
            dspdata[10] <= 8'b00110000;    // 0
            dspdata[11] <= 8'b00110000;    // 0
            dspdata[12] <= 8'b00110000;    // 0
            dspdata[13] <= 8'b00110000;    // 0
            dspdata[14] <= 8'b00110000;    // 0
            dspdata[15] <= 8'b00110000;    // 0
            dspdata[16] <= 8'b00110000;    // 0
            dspdata[17] <= 8'b00110000;    // 0
            dspdata[18] <= 8'b00110000;    // 0
            dspdata[19] <= 8'b00110000;    // 0
            dspdata[20] <= 8'b00110000;    // 0           
            dspdata[21] <= 8'hC0;   // set cg ram address 1100 0000   2번라인 첫번째부터
            dspdata[22] <= 8'b00110000;    // 0
            dspdata[23] <= 8'b00110000;    // 0
            dspdata[24] <= 8'b00110000;    // 0
            dspdata[25] <= 8'b00110000;    // 0
            dspdata[26] <= 8'b00110000;    // 0
            dspdata[27] <= 8'b00110000;    // 0
            dspdata[28] <= 8'b00110000;    // 0
            dspdata[29] <= 8'b00110000;    // 0
            dspdata[30] <= 8'b00110000;    // 0
            dspdata[31] <= 8'b00110000;    // 0
            dspdata[32] <= 8'b00110000;    // 0
            dspdata[33] <= 8'b00110000;    // 0
            dspdata[34] <= 8'b00110000;    // 0
            dspdata[35] <= 8'b00110000;    // 0
            dspdata[36] <= 8'b00110000;    // 0
            dspdata[37] <= 8'b00110000;    // 0
            end
        else if (rx_cnt == 0)
            dspdata[lcd_cnt] <= lcd_register;    
        else begin
            dspdata[0] <= dspdata[0];
            dspdata[1] <= dspdata[1];
            dspdata[2] <= dspdata[2];
            dspdata[3] <= dspdata[3];            
            dspdata[4] <= dspdata[4];
            dspdata[5] <= dspdata[5];
            dspdata[6] <= dspdata[6];
            dspdata[7] <= dspdata[7];
            dspdata[8] <= dspdata[8];
            dspdata[9] <= dspdata[9];
            dspdata[10] <= dspdata[10];
            dspdata[11] <= dspdata[11];
            dspdata[12] <= dspdata[12];
            dspdata[13] <= dspdata[13];
            dspdata[14] <= dspdata[14];
            dspdata[15] <= dspdata[15];
            dspdata[16] <= dspdata[16];
            dspdata[17] <= dspdata[17];
            dspdata[18] <= dspdata[18];
            dspdata[19] <= dspdata[19];
            dspdata[20] <= dspdata[20];            
            dspdata[21] <= dspdata[21];
            dspdata[22] <= dspdata[22];
            dspdata[23] <= dspdata[23];
            dspdata[24] <= dspdata[24];
            dspdata[25] <= dspdata[25];
            dspdata[26] <= dspdata[26];
            dspdata[27] <= dspdata[27];
            dspdata[28] <= dspdata[28];
            dspdata[29] <= dspdata[29];
            dspdata[30] <= dspdata[30];
            dspdata[31] <= dspdata[31];
            dspdata[32] <= dspdata[32];
            dspdata[33] <= dspdata[33];
            dspdata[34] <= dspdata[34];
            dspdata[35] <= dspdata[35];
            dspdata[36] <= dspdata[36];
            dspdata[37] <= dspdata[37];
            end
    end                                
    
    always @(posedge clk or posedge rst) begin
        if (rst) begin
            i <= 0;            
            lcd_e <= 0;            
            data <= 8'b0;
            end
        else begin
            if(i <= 1000000) begin
                i <= i + 1;
                lcd_e <= 1;
                data <= dspdata[j];
                end 
            else if ((i > 1000000) && (i < 2000000)) begin
                i <= i + 1;
                lcd_e <= 0;
                end 
            else if (i == 2000000) begin              
                i <= 1'b0;
                end 
                                      
            else begin
                i <= i;
                lcd_e <= lcd_e;
                data <= data;
                end
        end
   end     
   
   always @(posedge clk or posedge rst) begin
       if (rst)
        j <= 0;    
    else if (j == 38)   
        j <= 4;
    else if (i == 2000000)
        j <= j+1;    
    else 
        j <= j;    
    end
    
    always @(posedge clk or posedge rst) begin
        if (rst)
            lcd_rs <= 0;            
        else if (j <= 4)
            lcd_rs <= 0;
        else if (j > 4 && j < 21)
            lcd_rs <= 1;
        else if (j == 21)
            lcd_rs <= 0;
        else if (j > 21 && j < 38)
            lcd_rs <= 1;
        else 
            lcd_rs <= lcd_rs;
        end                               
endmodule

 

 

- Nucleo Board 제어용 코드

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "string.h"
/* USER CODE END Includes */

/* USER CODE BEGIN PV */
  uint8_t rx_data;
  int led_mode = 0;

/* USER CODE END PV */


int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART3_UART_Init();

  /* Initialize interrupts */
  MX_NVIC_Init();
  /* USER CODE BEGIN 2 */

  HAL_UART_Receive_IT(&huart3, (uint8_t *)&rx_data, 1);

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
	  switch (led_mode)
	  {
	  	  case 1 : GPIOB -> ODR = 0x0001; break;
	  	  case 2 : GPIOB -> ODR = 0x0080; break;
	  	  case 3 : GPIOB -> ODR = 0x4000; break;
	  	  case 0 : GPIOB -> ODR = 0x0000; break;
	  }

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/* USER CODE BEGIN 4 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if(huart -> Instance == USART3)
  {
	  HAL_UART_Receive_IT(&huart3, (uint8_t *)&rx_data, 1);
	  HAL_UART_Transmit(&huart3, (uint8_t *)&rx_data, 1, 500);
	  switch (rx_data)
	  {
	  case '1': led_mode = 1; break;
	  case '2': led_mode = 2; break;
	  case '3': led_mode = 3; break;
	  default: led_mode = 0;
	  }

  }

}
/* USER CODE END 4 */

 

CUBE IDE를 다루는 법은 본 글의 목적에 맞지 않으므로

자세히 설명하지 않고, 필요한 부분만 적어두었습니다.

led 부분의 경우 uart 통신이 잘 이루어지는지 확인하기 위해 넣었습니다.

 

3. 실습결과

FPGA는 ZYNQ-7000의 것을 사용하였고,

Tool은 Vivado를 사용하였습니다.

Nucleo 보드는 CUBE IDE에서 제어하였습니다.

 

 

 

 

키보드를 통해 입력한 문자들이

Nucleo 보드를 거쳐 LCD에 잘 출력되는 것을 볼 수 있습니다.

 

이번에는 합성 및 P&R 단계를 생략하고 테스트를 먼저 진행하였습니다.

이후 시간이 된다면 해당 부분도 포스팅하도록 하겠습니다.

 

진행년월: 24.05

목차

1. 배경

2. 과제 정의 및 개요

3. 소스코드

4. 시뮬레이션 결과

 

 

1. 배경

지난 프로젝트에서 컴퓨터와 신호를 주고받기 위해 UART 통신을 사용하였는데요,

UART 통신도 이해하고 Verilog로 구현해보고자 합니다.

Receiver 부분과 Transmitter 부분이 있는데,

전부 구현은 하지만, 테스트 단계에서

SPI 때처럼 Receiver 부분을 중점적으로 테스트하려고 합니다.

 

 

2. 과제 정의 및 개요

 

1. TX, RX 2개의 핀을 사용합니다.
2. 10비트 데이터를 교환하며 구성은 다음과 같습니다.
    - [0] 비트는 start bit (0)
    - [1~8] 비트는 8비트 data
    - [9] 비트는 end bit (1)
3. 16배로 오버샘플링하며, 중앙 샘플링 방법을 채택하도록 합니다.
    - 간단한 예시이므로 노이즈가 심하지 않을 것임을 알고 있어 채택하였습니다.
    - 7, 8, 9번째 신호 중 2개 이상 1이 나온다면, 1로 인정합니다.

 

 

Timing Diagram과 State Diagram은 아래와 같습니다.

간단한 구현이므로 엄격하게 많은 상황을 포함하여 그리지는 않았습니다.

 

3. 소스코드

- Top module

`timescale 1ns / 1ps

module uart(
    input i_clk,            // i_clk = 50Mhz
    input i_rx,
    input i_rst,
    output o_tx
);

wire w_tx_clk;
wire w_rx_clk;
wire [7:0] w_rx_register;
wire w_rx_end;
wire [4:0] w_clk_cnt;
wire [3:0] w_rx_cnt;
wire [3:0] w_send_cnt;
wire [1:0] w_current_state;
wire [1:0] w_next_state;

uart_clock #(.max(2604)) uart_clk_gen_tx (
    .clk_in(i_clk),
    .rst(i_rst),
    .clk_out(w_tx_clk)
); // Baud rate = 9600, max = 50,000,000 / 9,600 = 5,208

uart_clock #(.max(163)) uart_clk_gen_rx (
    .i_clk(i_clk),
    .i_rst(i_rst),
    .o_clk(w_rx_clk)
); // Sampling rate = 16*, max = 50,000,000 / 9,600 * 16 = 325

uart_rx uart_receiver (
    .i_clk(w_rx_clk),
    .i_rst(i_rst),
    .i_rx(i_rx),
    .o_rx_register(w_rx_register),
    .o_rx_end(w_rx_end),
    .o_clk_cnt(w_clk_cnt),
    .o_rx_cnt(w_rx_cnt)
);

uart_tx uart_transmitter (
    .i_clk(w_tx_clk),
    .i_rst(i_rst),
    .o_tx(o_tx),
    .i_rx_register(w_rx_register),
    .i_rx_end(w_rx_end),
    .o_send_cnt(w_send_cnt),
    .o_current_state(w_current_state),
    .o_next_state(w_next_state)
);

endmodule

 

 

주석에 적어둔 것 처럼, 보드레이트를 맞추기 위한 작업이 포함되어 있습니다.

 

- Uart_clock

`timescale 1ns / 1ps

module uart_clock(
    input i_clk,
    input i_rst,
    output reg o_clk
);
    
reg [10:0] r_clk_cnt;

parameter max = 1;
    
always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        r_clk_cnt <= 0;
    end else if (r_clk_cnt == max) begin        
        r_clk_cnt <= 0;
    end else 
        r_clk_cnt <= r_clk_cnt + 1;
end

always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        o_clk_out <= 0;
    end else if (r_clk_cnt == max) begin        
        o_clk <= !o_clk;
    end else 
        o_clk <= o_clk;
end    

endmodule

 

- Uart_TX

`timescale 1ns / 1ps

module uart_tx(
    input i_clk,
    input i_rst,
    input [7:0] i_rx_register,
    input i_rx_end,
    output reg o_tx,
    output reg [3:0] o_send_cnt,
    output reg [1:0] o_current_state,
    output reg [1:0] o_next_state
);
    
localparam TX_IDLE = 2'b00;
localparam TX_START = 2'b01;
localparam TX_DATA = 2'b10;
localparam TX_STOP = 2'b11;

always @(posedge i_clk or posedge i_rst) begin
    if (i_rst)
        o_current_state <= TX_IDLE;
    else
        o_current_state <= o_next_state;
end
 
always @(negedge i_clk or posedge i_rst) begin
    if (i_rst)
        o_next_state <= TX_IDLE;
    else
        case(o_current_state)
        TX_IDLE : if (i_rx_end == 1)
                    o_next_state <= TX_START;
                  else
                    o_next_state <= o_next_state;
        TX_START : o_next_state <= TX_DATA;
        TX_DATA : if (o_send_cnt == 8)
                    o_next_state <= TX_STOP;
               else
                    o_next_state <= o_next_state;
        TX_STOP : o_next_state <= TX_IDLE;
        endcase
end                                       
        
always @(posedge i_clk or posedge i_rst) begin
    if (i_rst)
        o_send_cnt <= 0;
    else if (o_send_cnt == 10)
        o_send_cnt <= 0;
    else 
        case(o_next_state)
            TX_IDLE : o_send_cnt <= 0;
            TX_START : o_send_cnt <= 0;
            TX_DATA : o_send_cnt <= o_send_cnt+1;
            TX_STOP : o_send_cnt <= o_send_cnt+1;
        endcase 
end       

always @(posedge i_clk or posedge i_rst) begin
    if (i_rst)
        o_tx <= 1;
    else
        case (o_next_state)
        TX_IDLE : o_tx <= 1;
        TX_START : o_tx <= 0;
        TX_DATA : o_tx <= i_rx_register[o_send_cnt];
        TX_STOP : o_tx <= 1;
        endcase
end    

endmodule

 

TX 부분은 들어온 RX 신호를 그대로 다시 전송할 수 있도록 간단하게 구현하였습니다.

 

- UART_RX

module uart_rx(
    input i_clk,
    input i_rx,
    input i_rst,
    output reg [7:0] o_rx_register,
    output reg o_rx_end,
    output reg [4:0] o_clk_cnt,
    output reg [3:0] o_rx_cnt
);

localparam RX_IDLE  = 2'b00;
localparam RX_START = 2'b01;
localparam RX_STOP  = 2'b10;

reg [1:0] r_current_state;
reg [1:0] r_next_state;
reg [15:0] r_rx_check;
reg [4:0] r_rx_score;
reg r_rx_sampling;
reg [9:0] r_rx_received;

// Clock counter and bit counter logic
always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        o_clk_cnt <= 0;
    end else if (o_clk_cnt == 16 && o_rx_cnt != 11) begin
        o_clk_cnt <= 1;
    end else if (o_clk_cnt == 16 && o_rx_cnt == 11) begin
        o_clk_cnt <= 0;
    end else begin
        case (r_next_state)
            RX_IDLE : o_clk_cnt <= 0;
            RX_START : o_clk_cnt <= o_clk_cnt + 1;
            RX_STOP : o_clk_cnt <= o_clk_cnt + 1;
            default : o_clk_cnt <= o_clk_cnt;
        endcase
    end
end

// Bit counter logic
always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        o_rx_cnt <= 0;
    end else if (o_rx_cnt == 11 && o_clk_cnt == 16) begin
        o_rx_cnt <= 0;
    end else if (r_current_state == RX_IDLE && i_rx == 0) begin
        o_rx_cnt <= 1;
    end else if (o_clk_cnt == 16) begin
        o_rx_cnt <= o_rx_cnt + 1;
    end else begin
        o_rx_cnt <= o_rx_cnt;
    end
end

// FSM for RX control
always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        r_current_state <= RX_IDLE;
    end else begin
        r_current_state <= r_next_state;
    end
end

always @(negedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        r_next_state <= RX_IDLE;
    end else begin
        case (r_current_state)
            RX_IDLE : if (i_rx == 0)
                         r_next_state <= RX_START;
                     else
                         r_next_state <= r_next_state;
            RX_START : if (o_rx_cnt == 11)
                         r_next_state <= RX_STOP;
                     else
                         r_next_state <= r_next_state;
            RX_STOP : if (o_rx_cnt == 0)
                         r_next_state <= RX_IDLE;
                     else
                         r_next_state <= r_next_state;
            default : r_next_state <= r_next_state;
        endcase
    end
end

// Central sampling logic as per the original code
always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        r_rx_check <= 16'b1111_1111_1111_1111;
    end else begin
        r_rx_check <= {r_rx_check[14:0], i_rx};
    end
end

always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        r_rx_score <= 0;
    end else if (o_rx_cnt == 0) begin
        r_rx_score <= 0;
    end else if (o_clk_cnt == 16) begin
        r_rx_score <= 0;
    end else if (o_rx_cnt == 11) begin
        r_rx_score <= 0;
    end else if (i_rx == 1) begin
        r_rx_score <= r_rx_score + 1;
    end else begin
        r_rx_score <= r_rx_score;
    end
end

always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        r_rx_sampling <= 1;
    end else if (o_rx_cnt == 0) begin
        r_rx_sampling <= 1;
    end else if (o_clk_cnt == 16 && r_rx_score > 8 && o_rx_cnt != 11) begin
        r_rx_sampling <= 1;
    end else if (o_clk_cnt == 16 && r_rx_score < 8 && o_rx_cnt != 11) begin
        r_rx_sampling <= 0;
    end else begin
        r_rx_sampling <= r_rx_sampling;
    end
end

// Register received data
always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        r_rx_received <= 10'b0;
    end else if (o_clk_cnt == 1 && o_rx_cnt > 1) begin
        r_rx_received <= {r_rx_received[8:0], r_rx_sampling};
    end
end

// Update RX register
always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        o_rx_register <= 8'b0;
    end else if (o_rx_cnt == 11 && o_clk_cnt == 2) begin
        o_rx_register <= r_rx_received[8:1];
    end
    	else
        rx_register <= rx_register;
end

// RX end signal
always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        o_rx_end <= 1;
    end else begin
        case (r_next_state)
            RX_IDLE : o_rx_end <= 1;
            RX_START : o_rx_end <= 0;
            RX_STOP : o_rx_end <= 0;
            default : o_rx_end <= o_rx_end;
        endcase
    end
end

endmodule

 

여기서 o_rx_end 신호는 의미상으로만 존재할뿐, 사용하지 않습니다.

Data Update Timing 등은 설계하기 나름이며, 최대한 간단한 형태로 설계하였습니다.

 

- tb_rx

`timescale 1ns / 1ps

module tb_rx;

    reg clk;
    reg rx;
    reg rst;
    wire [7:0] rx_register;
    wire rx_end;
    
    reg [7:0] tx_data; // 보낼 데이터
    integer i;

    // UART RX 모듈 인스턴스화
    uart_rx uut (
        .i_clk(clk),
        .i_rx(rx),
        .i_rst(rst),
        .o_rx_register(rx_register),
        .o_rx_end(rx_end)
    );

    // Clock generation (100 MHz)
    initial begin
        clk = 0;
        forever #5 clk = ~clk;  // 10ns 주기 클럭, 100 MHz
    end

    // 테스트 시나리오
    initial begin
        // 초기화
        rst = 1;  // 리셋
        rx = 1;   // IDLE 상태 (RX 라인은 기본적으로 High 상태)
        tx_data = 8'b0101_0111; // 전송할 데이터: 8'b0101_0111
        #100 rst = 0;  // 리셋 해제

        // 데이터 전송 (UART 프레임 생성)
        // 시작 비트 (Start Bit)
        rx = 0;
        #160; // 16 클럭 사이클 (샘플링 속도)

        // 데이터 비트 (Data Bits)
        for (i = 0; i < 8; i = i + 1) begin
            rx = tx_data[i];
            #160; // 각 데이터 비트는 16 클럭 사이클 동안 유지
        end

        // 정지 비트 (Stop Bit)
        rx = 1;
        #160; // Stop Bit

        // 대기 후 결과 확인
        #200;
        
        // 결과 출력
        if (rx_register == tx_data) begin
            $display("Test Passed! Received Data: %b", rx_register);
        end else begin
            $display("Test Failed! Received Data: %b, Expected Data: %b", rx_register, tx_data);
        end

        // 시뮬레이션 종료
        #100;
        $finish;
    end

endmodule

 

앞서 언급하였듯이 RX 위주로 확인할 것이기 때문에,

RX의 테스트벤치만을 간단하게 작성하였습니다.

 

 

4. 시뮬레이션 결과

동작을 확인해보기 위해, 또 모범적인 waveform을 마련해 놓기 위해 function simulation을 합니다.

Tool은 Vivado를 사용하였습니다.

 

사실, 앞서 언급한 바와 같이 rx_end 신호는 의미상으로만 존재합니다.

제 구현에서는 rx_end 신호가 필요하지 않습니다.

 

만약, 필요하다면 처음 rx_end 신호가 Low로 내려가는 순간은

다른 신호와 마찬가지로 샘플링된 결과에 의해 작동해야 합니다.

처음에 그렸던 Timing Diagram과 비슷한 waveform을 만들고자 의도한 바입니다.

 

rx_register에 저장되는 Timing 등은 설계하기 나름이지만,

여유를 두기 위해 통신이 끝날 때로 두었습니다.

 

 

 

- 다음 글

(2) Arm보드로 FPGA LCD제어: https://chonh0531.tistory.com/6

 

UART 통신 - (2) Arm보드로 FPGA LCD 제어

- 이전 글(1) RTL : https://chonh0531.tistory.com/5목차1. 과제 개요2. 소스코드3. 실습 결과  1. 과제 개요이전에 구현해 본 UART_RX를 테스트해 보기로 합니다.UART 신호 생성은 ARM Cortex-M4 Core가 있는 NUCLEO-F42

chonh0531.tistory.com

 

 

'RTL, Synthesis, P&R' 카테고리의 다른 글

Cache controller - (1) RTL  (0) 2024.09.07
UART 통신 - (2) Arm보드로 FPGA LCD 제어  (0) 2024.09.03
SPI 통신 - (3) Arduino로 FPGA Motor 제어  (0) 2024.09.03
SPI 통신 - (2) Synthesis, P&R  (0) 2024.09.03
SPI 통신 - (1) RTL  (0) 2024.09.03

- 이전 글

(1) RTL : https://chonh0531.tistory.com/2

(2) Synthesis, P&R : https://chonh0531.tistory.com/3

목차

1. 과제 개요

2. 소스코드

3. 실습 결과

 

 

1. 과제 개요

이전에 구현해 본 SPI_Slave를 테스트해 보기로 하였고,

Master 역할로 Arduino Uno 보드를 이용하여

FPGA에서 Motor를 제어해보도록 합니다.

 

1. 컴퓨터에서 UART 통신으로 Arduino Uno 보드에 명령어를 전달
2. Arduino Uno는 FPGA에 SPI 통신으로 명령을 전달
3. FPGA는 data 부분의 마지막 4비트를 모터 속도제어에 이용

 

 

 

2. 소스코드

- SPI_Slave

`timescale 1ns / 1ps

module top(
    input i_clk, i_SCLK, i_CS, i_rst_H, i_MOSI,
    output o_out, o_nsleep, o_MISO 
    );
    
    wire [3:0] w_duty;
    wire [4:0] w_bitcnt;
                                          
    SPI_Slave U0 (.i_clk(i_clk), .i_SCLK(i_SCLK), .i_CS(i_CS), .i_rst(i_rst_H), .i_MOSI(i_MOSI), .o_MISO(o_MISO), .o_duty(w_duty), .o_bitcnt(w_bitcnt));
    motor U1 (.i_clk(i_clk), .i_rst(i_rst), .i_duty(w_duty), .o_out(o_out), .o_nsleep(o_nsleep));

endmodule

 

module motor(
    input i_clk,
    input i_rst_H,
    input [3:0] i_duty,
    output reg o_out,
    output reg o_nsleep
    );

    reg [18:0] r_cnt;
    reg [4:0] r_duty;
    reg r_clk2;

    always @(posedge i_clk or posedge i_rst_H) begin
        if (i_rst_H)
            o_nsleep <= 0;
        else 
            o_nsleep <= 1;
    end

    always @(posedge i_clk or posedge i_rst_H) begin
        if (i_rst_H) begin
            r_cnt <= 0;
            r_clk2 <= 0;
        end
        else if (r_cnt == 500_000) begin
            r_clk2 <= ~r_clk2;
            r_cnt <= 0;
        end
        else
            r_cnt <= r_cnt + 1;
    end
    
    always @(posedge r_clk2 or posedge i_rst_H) begin
        if (i_rst_H)
            r_duty <= 0;
        else if (r_duty >= 16)
            r_duty <= 0;
        else
            r_duty <= r_duty + 1;
    end
    
    always @(posedge i_clk or posedge i_rst_H) begin
        if (i_rst_H)
            o_out <= 0;
        else if (r_duty < i_duty)
            o_out <= 1;
        else
            o_out <= 0;
    end
endmodule

 

`timescale 1ns / 1ps

`define IDLE 2'b00
`define READ 2'b10
`define WRITE 2'b11

module SPI_Slave(
    // Internal input
    input i_clk,
    input i_rst_H,
    input [1:0] i_ID,

    // SPI Interface
    input i_SCLK,
    input i_CS,
    input i_MOSI,
    output reg o_MISO,

    // Additional output for duty cycle
    output reg [3:0] o_duty
    );

    reg [5:0] bitcnt = 6'd0;
    reg [1:0] current_state = 2'b00;
    reg [1:0] next_state = 2'b00;
    
    reg [15:0] D0;
    reg [15:0] D1;

    reg addr;    
    reg [31:0] data_RX;
    reg id_ok;

// FSM part
    always @(negedge i_SCLK or posedge i_CS) begin
        if (i_CS) begin
            current_state <= `IDLE;
            next_state <= `IDLE;
        end
        else begin
            current_state <= next_state;
            case(current_state)
                `IDLE : begin
                    if (bitcnt == 6'd3 && id_ok == 1 && data_RX[0] == 1)
                        next_state <= `READ;
                    else if (bitcnt == 6'd3 && id_ok == 1 && data_RX[0] == 0)
                        next_state <= `WRITE;
                    else
                        next_state <= next_state;
                end
                `READ : begin
                    if (bitcnt == 6'd0)
                        next_state <= `IDLE;
                    else
                        next_state <= next_state;
                end
                `WRITE : begin
                    if (bitcnt == 6'd0)
                        next_state <= `IDLE;
                    else
                        next_state <= next_state;
                end
                default : next_state <= next_state; // no cases. just for synthesis
            endcase
        end
    end

    always @(posedge i_SCLK or posedge i_CS) begin
        if(i_CS) begin
            id_ok <= 0;
        end
        else if (bitcnt == 6'd2 && data_RX[1:0] == i_ID) begin
            id_ok <= 1;
        end
        else
            id_ok <= id_ok;
    end

// Receive data
    always @(negedge i_SCLK or posedge i_CS) begin
        if(i_CS) begin
            bitcnt <= 6'd0;
            data_RX <= 32'd0;
        end
        else begin
            bitcnt <= bitcnt + 6'd1;
            data_RX <= {data_RX[30:0], i_MOSI};
        end
    end

// Read address
    always @(negedge i_SCLK or posedge i_CS) begin
        if(i_CS) begin
            addr <= 1'd0;
        end
        else if (current_state == `IDLE && bitcnt == 6'd4) begin
            addr <= data_RX[0];
        end
        else
            addr <= addr;
    end

// Write data
    always @(posedge i_SCLK or posedge i_rst_H) begin
        if(i_rst_H) begin
            D0 <= 16'd0;
            D1 <= 16'd0;
        end
        else if(current_state == `WRITE && addr == 1'b0 && bitcnt == 6'd32) begin
            D0 <= data_RX[15:0];
        end
        else if(current_state == `WRITE && addr == 1'b1 && bitcnt == 6'd32) begin
            D1 <= data_RX[15:0];
        end
        else begin
            D0 <= D0;
            D1 <= D1;
        end
    end

// Update o_duty from D0 or D1
always @(posedge i_SCLK or posedge i_rst_H) begin
    if (i_rst_H)
        o_duty <= 4'd0;
    else if (current_state == `WRITE && bitcnt == 6'd32) begin
        if (addr == 1'b0)
            o_duty <= D0[3:0]; // Update duty cycle from the lower 4 bits of D0
        else
            o_duty <= D1[3:0]; // Update duty cycle from the lower 4 bits of D1
    end
    else
        o_duty <= o_duty;
end

// READ Mode: data transfer
    always @(posedge i_SCLK) begin
        if(current_state == `READ && addr == 1'b0) begin
            case(bitcnt)
                6'd16 : o_MISO <= D0[15];
                6'd17 : o_MISO <= D0[14];
                6'd18 : o_MISO <= D0[13];
                6'd19 : o_MISO <= D0[12];
                6'd20 : o_MISO <= D0[11];
                6'd21 : o_MISO <= D0[10];
                6'd22 : o_MISO <= D0[9];
                6'd23 : o_MISO <= D0[8];
                6'd24 : o_MISO <= D0[7];
                6'd25 : o_MISO <= D0[6];
                6'd26 : o_MISO <= D0[5];
                6'd27 : o_MISO <= D0[4];
                6'd28 : o_MISO <= D0[3];
                6'd29 : o_MISO <= D0[2];
                6'd30 : o_MISO <= D0[1];
                6'd31 : o_MISO <= D0[0];
                default : o_MISO <= 0;
            endcase
        end else if(current_state == `READ && addr == 1'b1) begin
            case(bitcnt)
                6'd16 : o_MISO <= D1[15];
                6'd17 : o_MISO <= D1[14];
                6'd18 : o_MISO <= D1[13];
                6'd19 : o_MISO <= D1[12];
                6'd20 : o_MISO <= D1[11];
                6'd21 : o_MISO <= D1[10];
                6'd22 : o_MISO <= D1[9];
                6'd23 : o_MISO <= D1[8];
                6'd24 : o_MISO <= D1[7];
                6'd25 : o_MISO <= D1[6];
                6'd26 : o_MISO <= D1[5];
                6'd27 : o_MISO <= D1[4];
                6'd28 : o_MISO <= D1[3];
                6'd29 : o_MISO <= D1[2];
                6'd30 : o_MISO <= D1[1];
                6'd31 : o_MISO <= D1[0];
                default : o_MISO <= 0;
            endcase
        end else
            o_MISO <= 1'b0;
    end

endmodule

 

기존 코드에서, 입력으로 받은 데이터중 하위 4비트 데이터를 duty로 전달하였습니다.

motor 제어 부분은 간단하므로, 추가로 설명하지 않았습니다.

 

 

- SPI_Master에 사용할 Arduino 코드

#include <SPI.h>


enum InputState {
  WAITING_FOR_MODE,
  WAITING_FOR_ID,  // ID 입력 대기 상태 추가
  WAITING_FOR_UPPER_LOWER,
  WAITING_FOR_ADDRESS,
  WAITING_FOR_DATA
};

InputState currentState = WAITING_FOR_MODE;

bool writeMode;
bool upperData; // 4번째 비트 (Upper/Lower)
bool address;
uint16_t addr; // 주소
uint16_t data; // 데이터
uint8_t id; // ID 값 (2비트)

const int CS_PIN = 10; // SS 핀 (CS 핀) 고정값 변경 못함
const int SCLK_PIN = 13; // SCLK 핀
const int ID_PIN_1 = 40; // ID 핀 1
const int ID_PIN_2 = 42; // ID 핀 2

void setup() {
  Serial.begin(9600); // 통신속도
  SPI.begin(); // SPI 초기화
  SPI.setClockDivider(SPI_CLOCK_DIV2); // 클럭 속도 설정
  SPI.setDataMode(SPI_MODE2); // SPI 모드 설정 (모드 2)

  // SPI 핀 모드 설정
  pinMode(CS_PIN, OUTPUT); // SS 핀을 출력 모드로 설정
  digitalWrite(CS_PIN, HIGH); // 슬레이브 비선택 상태
  pinMode(SCLK_PIN, OUTPUT); // SCLK 핀을 출력 모드로 설정
  digitalWrite(SCLK_PIN, LOW); // 초기 상태 LOW

  // ID 핀 모드 설정
  pinMode(ID_PIN_1, OUTPUT);
  pinMode(ID_PIN_2, OUTPUT);
  digitalWrite(ID_PIN_1, LOW);
  digitalWrite(ID_PIN_2, LOW);

  // 시작 프롬프트 출력
  Serial.println("Enter read/write mode (1 for read, 0 for write):"); // 콘솔에 값 출력
}

void loop() { // 반복문 
  if (Serial.available() > 0) { //시리얼 통신에 값이 있으면
    String input = Serial.readStringUntil('\n'); // 엔터까지 읽기
    input.trim(); // 공백 제거

    if (currentState == WAITING_FOR_MODE) {
      // 모드 입력 받기
      if (input.equals("1") || input.equals("0")) {
        writeMode = input.equals("0"); // 수정: 0을 입력하면 true(0), 1을 입력하면 false(1) 0이 WRITE니까 입력해야 하니까 상태 변화
        currentState = WAITING_FOR_ID; // ID 입력 대기 상태로 전환
        Serial.println("Enter ID value (0 to 3):");  // WRITE일 경우 ID 값 받기
      } else {
        Serial.println("Invalid input. Enter 1 for read, 0 for write:"); //READ는 그냥 넘어가고 0, 1 이외의 값은 그냥 끝내기
      }
    } else if (currentState == WAITING_FOR_ID) { // WRITE 입력하면 상태가 얘로 바뀌니까 반복문에 의해서 얘가 실행
      // ID 입력 받기
      int idValue = input.toInt();
      if (idValue >= 0 && idValue <= 3) {
        id = idValue;
        digitalWrite(ID_PIN_1, id & 0x01); // id가 입력한 값으로 들어온 만약 id로 3을 입력하면 0000 0011이랑 0000 0001이란 &연산 = 0000 0001
        digitalWrite(ID_PIN_2, (id >> 1) & 0x01); // 0000 0011 >> 1 => 0000 0001 & 0000 00001
        currentState = WAITING_FOR_UPPER_LOWER;
        Serial.println("Enter upper or lower (1 for upper, 0 for lower):");
      } else {
        Serial.println("Invalid input. Enter ID value (0 to 3):");
      }
    } else if (currentState == WAITING_FOR_UPPER_LOWER) {
      // upper/lower 입력 받기
      upperData = input.toInt();
      // if (input.equals("1") || input.equals("0")) {
        //upperData = input.equals("1");
      if(upperData == 1 || upperData == 0) {
        currentState = WAITING_FOR_ADDRESS;
        Serial.println("Enter address (in hex):");
      } else {
        Serial.println("Invalid input. Enter 1 for upper, 0 for lower:");
      }
    } else if (currentState == WAITING_FOR_ADDRESS) { // ADDRESS인데 그냥 그 빈공간임
      // 주소 입력 받기
      addr = strtol(input.c_str(), NULL, 16);
      currentState = WAITING_FOR_DATA;
      Serial.println("Enter data (in hex):");
    } else if (currentState == WAITING_FOR_DATA) {
      // 데이터 입력 받기
      data = strtol(input.c_str(), NULL, 16);

      // 32비트 데이터 구성
      uint32_t dataToSend = ((uint32_t)id << 30) | ((uint32_t)(!writeMode) << 29) | ((uint32_t)upperData << 28) | ((uint32_t)addr << 16) | data;

      // SPI 데이터 전송
      SPI.beginTransaction(SPISettings(800, MSBFIRST, SPI_MODE2)); // SPI 설정 (모드 2)
      digitalWrite(CS_PIN, LOW); // 슬레이브 선택

      byte bytesToSend[4];
      bytesToSend[0] = (dataToSend >> 24) & 0xFF;
      bytesToSend[1] = (dataToSend >> 16) & 0xFF;
      bytesToSend[2] = (dataToSend >> 8) & 0xFF;
      bytesToSend[3] = dataToSend & 0xFF;
      
      uint32_t receivedData = 0;
      for (int i = 0; i < 4; ++i) {
        receivedData = (receivedData << 8) | SPI.transfer(bytesToSend[i]);
      }

      digitalWrite(CS_PIN, HIGH); // 슬레이브 비선택
      SPI.endTransaction(); // SPI 트랜잭션 종료

      // 결과 출력
      Serial.print("Sent: ID(");
      Serial.print(id, BIN); // ID 값을 이진수로 출력
      Serial.print("), ");
      Serial.print(writeMode ? "Write" : "Read");
      Serial.print(", ");
      Serial.print(upperData ? "Upper" : "Lower");
      Serial.print(", Address: 16'b");
      Serial.print(addr, BIN);
      Serial.print(", Data: 16'b");
      Serial.print(data, BIN);
      Serial.println();

      // 읽기 모드일 때 수신된 데이터 출력
      if (!writeMode) {
        Serial.print("Received: 0x");
        Serial.println(receivedData, HEX);
      }

      // 다시 모드 입력 상태로 전환
      currentState = WAITING_FOR_MODE;
      Serial.println("Enter read/write mode (1 for read, 0 for write):");
    }
  }
}

 

upper / lower는 각각 D1, D0 데이터에 넣는다는 의미입니다.

 

3. 실습결과

FPGA는 ZYNQ-7000의 것을 사용하였고,

Tool은 Vivado를 사용하였습니다.

Arduino의 제어 Tool은 Arduino IDE를 사용하였습니다.

 

 

데이터 "1000_0000_0000_0000_0000_0000_1001_1001" 이 전송되며,

10은 ID, 0은 writemode, 0이므로 D0에 0000_0000_1001_1001이 저장됩니다.

또한 최하위 1001은 duty로 전달되며, 모터 동작 속도를 결정하게 됩니다.

 

결과적으로, SPI_Slave 설계 프로젝트는 약식이지만 모든 테스트를 통과하였고,

Tape-out 단계 전까지 완료되었습니다.

 

'RTL, Synthesis, P&R' 카테고리의 다른 글

UART 통신 - (2) Arm보드로 FPGA LCD 제어  (0) 2024.09.03
UART 통신 - (1) RTL  (0) 2024.09.03
SPI 통신 - (2) Synthesis, P&R  (0) 2024.09.03
SPI 통신 - (1) RTL  (0) 2024.09.03
4-digit Counter with FPGA  (0) 2024.08.30

- 이전 글

(1) RTL : https://chonh0531.tistory.com/2

 

SPI 통신 - (1) RTL

목차1. 배경2. 과제 정의 및 개요3. 소스코드4. 시뮬레이션 결과  1. 배경모든 칩은 통신이 필요하기 때문에 통신 모듈이 들어가있습니다.그중에서 좀 기본적인 통신 모듈중 하나인 SPI통신을 이

chonh0531.tistory.com

 

 

 

 

목차

1. 합성 코드

2. Pre-Layout Simulation

3. P&R

4. Post-Layout Simulation

 

 

1. 합성 코드

리눅스 환경에서 진행하였으며, 디렉토리는 다음과 같습니다.

SPI  ─  SIM  ─ function_SIM ─ [SPI_Slave.v , tb_SPI_Slave.v]
                     └ pre_SIM ─ [tb_SPI_Slave_pre.v] 
                     └ post_SIM ─ [tb_SPI_Slave_post.v]
        └  SYN  ─ RTL ─ [constraints.sdc , run_genus.tcl]
                      └ Report ─ [ ... ]
                      └ Output ─ [SPI_Slave_netlist.v , SPI_Slave_sdc.sdc , delays.sdf]
        └ PNR ─ [ SPI_Slave_GDS.gds , SPI_Slave_PNR.v , SPI_Slave_delay_PNR.sdf]
                    └ timingReports ─ [SPI_Slave_preCTS_all.tarpt , SPI_Slave_postCTS_all.tarpt, SPI_Slave_postRoute_all.tarpt]

 

 

Tool은 Genus를 사용하였으며 라이브러리는 교육용 라이브러리 사용하였습니다.

스크립트 코드는 다음과 같습니다.

## run_genus.tcl

set_db init_lib_search_path ../../../LIB/lib/
set_db init_hdl_search_path ../../SIM/function_SIM/
read_libs slow_vdd1v0_basicCells.lib

read_hdl SPI_Slave.v
elaborate
read_sdc ./constraints.sdc

set_db syn_generic_effort medium
set_db syn_map_effort medium
set_db syn_opt_effort medium

syn_generic
syn_map
syn_opt

#reports
report_timing > ../Report/report_timing.rpt
report_power > ../Report/report_power.rpt
report_area > ../Report/report_area.rpt
report_qor > ../Report/report_qor.rpt



#Outputs
write_hdl > ../Output/SPI_Slave_netlist.v
write_sdc > ../Output/SPI_Slave_sdc.sdc
write_sdf -timescale ns -nonegchecks -recrem split -edges check_edge  -setuphold split > ../Output/delays.sdf

 

 

 

Constraint는 지금 단계에서는 간단하게 clock 설정만 하겠습니다.

 create_clock -name i_SCLK -period 40 -waveform {0 5} [get_ports "i_SCLK"]

 

 

 

2. Pre-Layout Simulation

합성 결과로 나온 SPI_Slave_netlist.v 파일과,

delay 정보를 포함시킨 tb_SPI_Slave_pre.v 파일 및 라이브러리를 컴파일시켜 

시뮬레이션을 진행하였습니다.

Tool은 역시 Xcelium을 사용하였습니다.

 

 

 

이전에 했던 Function Simulation과 동일하게 작동하는 것을 확인할 수 있습니다.

사진에는 복잡하여 보이지 않았으나, 각 gate들에 연결된 wire들이 있고, delay는 적용되어 있습니다.

 

 

3. P&R

Tool은 Innovus를 사용하였으며, 자세한 과정은 생략하고 각 단계에서의 사진으로 대체하고,

마지막에 Script로 공유하도록 하겠습니다.

원래는 P&R 이전에 Timing 분석을 해야 하지만, Tool이 없는 관계로

각 단계에서 모두 Slack Time이 양수인지 확인하는 것으로 대체하겠습니다.

 

 

- Site Error

 

시작하자마자 문제가 생겼는데요,

Violation이 생겼습니다.

 

 

techSite 문제가 생긴 것 같은데요.

 

 

문제가 생긴 Object들이 모두 BUFX2 Cell에 있습니다.

BUFX2 Cell의 Site에 문제가 있는 것 같습니다.

 

 

교육용으로 사용하던 macro.lef 파일에 Site 이름이 잘못되어 있었는데요.

BUFX2 SITE를 CoreSiteDouble에서 CoreSite로 변경하였습니다.

 

 

- Pre-CTS

 

- Post-CTS

 

- Post-Route

 

- Result

 

이후 문제가 없이 잘 진행되었습니다.

결과적으로 Filler까지 추가한 다음 GDS 파일을 준비해놓고,

Post-Layout Simulation을 위한 netlist 파일과 sdf 파일을 준비합니다.

 

아래는 일련의 과정들을 cmd log로부터 추출하여 script로 만든 코드입니다.

 

- run_innovus.tcl

#Design Import
set init_gnd_net VSS
set init_lef_file {../../../LIB/lef/tech.lef ../../../LIB/lef/macro.lef}
set init_verilog ../../SYN/Output/SPI_Slave_netlist.v
set init_mmmc_file Default_01.view
set init_pwr_net VDD
init_design


#Floorplan
floorPlan -site CoreSite -r 0.921979067555 0.699987 10.0 10 10 10


#Power Planning
## Power-Connect Globalnets
clearGlobalNets
globalNetConnect VDD -type pgpin -pin VDD -instanceBasename * -hierarchicalInstance {}
globalNetConnect VSS -type pgpin -pin VSS -instanceBasename * -hierarchicalInstance {}

## Pin Assign
getPinAssignMode -pinEditInBatch -quiet
setPinAssignMode -pinEditInBatch true
editPin -fixOverlap 1 -unit MICRON -spreadDirection clockwise -side Left -layer 1 -spreadType center -spacing 3 -pin {i_CS {i_ID[0]} {i_ID[1]} i_MOSI i_rst_H i_SCLK}
setPinAssignMode -pinEditInBatch false

getPinAssignMode -pinEditInBatch -quiet
setPinAssignMode -pinEditInBatch true
editPin -fixOverlap 1 -unit MICRON -spreadDirection clockwise -side Right -layer 1 -spreadType center -spacing 3 -pin o_MISO
setPinAssignMode -pinEditInBatch false

## Add Ring
set sprCreateIeRingOffset 1.0
set sprCreateIeRingThreshold 1.0
set sprCreateIeRingJogDistance 1.0
set sprCreateIeRingLayers {}
set sprCreateIeRingOffset 1.0
set sprCreateIeRingThreshold 1.0
set sprCreateIeRingJogDistance 1.0
set sprCreateIeRingLayers {}
set sprCreateIeStripeWidth 10.0
set sprCreateIeStripeThreshold 1.0
set sprCreateIeStripeWidth 10.0
set sprCreateIeStripeThreshold 1.0
set sprCreateIeRingOffset 1.0
set sprCreateIeRingThreshold 1.0
set sprCreateIeRingJogDistance 1.0
set sprCreateIeRingLayers {}
set sprCreateIeStripeWidth 10.0
set sprCreateIeStripeThreshold 1.0
setAddRingMode -ring_target default -extend_over_row 0 -ignore_rows 0 -avoid_short 0 -skip_crossing_trunks none -stacked_via_top_layer Metal11 -stacked_via_bottom_layer Metal1 -via_using_exact_crossover_size 1 -orthogonal_only true -skip_via_on_pin {  standardcell } -skip_via_on_wire_shape {  noshape }
addRing -nets {VDD VSS} -type core_rings -follow core -layer {top Metal1 bottom Metal1 left Metal2 right Metal2} -width {top 1.8 bottom 1.8 left 1.8 right 1.8} -spacing {top 1.8 bottom 1.8 left 1.8 right 1.8} -offset {top 1.8 bottom 1.8 left 1.8 right 1.8} -center 0 -threshold 0 -jog_distance 0 -snap_wire_center_to_grid None


#Power Routing
setSrouteMode -viaConnectToShape { noshape }
sroute -connect { blockPin padPin padRing corePin floatingStripe } -layerChangeRange { Metal1(1) Metal11(11) } -blockPinTarget { nearestTarget } -padPinPortConnect { allPort oneGeom } -padPinTarget { nearestTarget } -corePinTarget { firstAfterRowEnd } -floatingStripeTarget { blockring padring ring stripe ringpin blockpin followpin } -allowJogging 1 -crossoverViaLayerRange { Metal1(1) Metal11(11) } -nets { VDD VSS } -allowLayerChange 1 -blockPin useLef -targetViaLayerRange { Metal1(1) Metal11(11) }


#Placement
setPlaceMode -fp false
place_design

## Violation Check
checkPlace SPI_Slave.checkPlace
setDrawView place
fit


#CTS & Routing
## Pre-CTS (ECO - Optimize Design)
setOptMode -fixCap true -fixTran true -fixFanoutLoad false
optDesign -preCTS

## Timing Report (Timing - Report timing, OutDir => timingReports Slack time must be (+)value)
redirect -quiet {set honorDomain [getAnalysisMode -honorClockDomains]} > /dev/null
timeDesign -preCTS -pathReports -drvReports -slackReports -numPaths 50 -prefix simple_spi_preCTS -outDir timingReports

## Route-Nano Route
setNanoRouteMode -quiet -routeTopRoutingLayer 11
setNanoRouteMode -quiet -routeBottomRoutingLayer 1
setNanoRouteMode -quiet -drouteEndIteration 1
setNanoRouteMode -quiet -routeWithTimingDriven false
setNanoRouteMode -quiet -routeWithSiDriven false
routeDesign -globalDetail

## Post-CTS
setOptMode -fixCap true -fixTran true -fixFanoutLoad false
optDesign -postCTS

## Post Routing(Tool - Set Mode - Specify Analysis Mode - On-chip Variation)
setAnalysisMode -cppr none -clockGatingCheck true -timeBorrowing true -useOutputPinCap true -sequentialConstProp false -timingSelfLoopsNoSkew false -enableMultipleDriveNet true -clkSrcPath true -warn true -usefulSkew true -analysisType onChipVariation -log true
setNanoRouteMode -quiet -routeWithTimingDriven false
setNanoRouteMode -quiet -routeWithSiDriven false
routeDesign -globalDetail

## Post-Route
setOptMode -fixCap true -fixTran true -fixFanoutLoad false
setDelayCalMode -engine default -siAware true
optDesign -postRoute


#Filler
getFillerMode -quiet
addFiller -cell FILL64 FILL32 FILL16 FILL8 FILL4 FILL2 FILL1 -prefix FILLER


#Verify
getMultiCpuUsage -localCpu
get_verify_drc_mode -disable_rules -quiet
get_verify_drc_mode -quiet -area
get_verify_drc_mode -quiet -layer_range
get_verify_drc_mode -check_ndr_spacing -quiet
get_verify_drc_mode -check_only -quiet
get_verify_drc_mode -check_same_via_cell -quiet
get_verify_drc_mode -exclude_pg_net -quiet
get_verify_drc_mode -ignore_trial_route -quiet
get_verify_drc_mode -max_wrong_way_halo -quiet
get_verify_drc_mode -use_min_spacing_on_block_obs -quiet
get_verify_drc_mode -limit -quiet
set_verify_drc_mode -disable_rules {} -check_ndr_spacing auto -check_only default -check_same_via_cell true -exclude_pg_net false -ignore_trial_route false -ignore_cell_blockage false -use_min_spacing_on_block_obs auto -report SPI_Slave.drc.rpt -limit 1000
verify_drc
set_verify_drc_mode -area {0 0 0 0}


#GDSII (save)
## save design
saveDesign SPI_Slave_complete

## save gds
streamOut SPI_Slave.gds -libName DesignLib -units 2000 -mode ALL

## save netlist
saveNetlist SPI_Slave_PNR.v

## save sdf
write_sdf  -ideal_clock_network SPI_Slave_PNR.sdf

 

script로 만들어두면, 문제가 생겼을 때 손으로 다시 하는 것이 아니라,

다음 명령어로 빠르게 코드로 진행할 수 있습니다.

innovus -files run_innovus.tcl

 

 

 

4. Post-Layout Simulation

P&R로부터 나온 SPI_Slave_PNR.v 파일과,

테스트벤치에 SPI_Slave_delay_PNR.sdf 파일을 연결시켜 시뮬레이션합니다.

 

 

 

Post_Sim 역시 기대하던 결과가 잘 나오는 것을 알 수 있습니다.

원래는 더 다양한 테스트 케이스 등 검증해야 할 것이 많지만

현재는 간단한 동작을 테스트 중이므로 이것으로 마무리합니다.

 

 

 

- 다음 글

(3) Arduino로 FPGA Motor 제어 : https://chonh0531.tistory.com/4

 

SPI 통신 - (3) Arduino로 FPGA Motor 제어

- 이전 글(1) RTL : https://chonh0531.tistory.com/2(2) Synthesis, P&R : https://chonh0531.tistory.com/3목차1. 과제 개요2. 소스코드3. 실습 결과  1. 과제 개요이전에 구현해 본 SPI_Slave를 테스트해 보기로 하였고,Master

chonh0531.tistory.com

 

'RTL, Synthesis, P&R' 카테고리의 다른 글

UART 통신 - (2) Arm보드로 FPGA LCD 제어  (0) 2024.09.03
UART 통신 - (1) RTL  (0) 2024.09.03
SPI 통신 - (3) Arduino로 FPGA Motor 제어  (0) 2024.09.03
SPI 통신 - (1) RTL  (0) 2024.09.03
4-digit Counter with FPGA  (0) 2024.08.30

+ Recent posts