진행년월: 24.08

 

목차

1. 배경

2. 과제 정의 및 개요

3. 소스코드

4. 결과

 

 

1. 배경

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

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

 

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

그것을 확장하여 이미지 처리 모델인 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. 결과

 

다양한 방법으로 Filter를 구현해 보았습니다.

우선은 convolution 연산을 verilog로 구현할 수 있게 되었고,

확장해서 C로 작성한 코드를 이용할 수도 있었습니다.

 

Filter 자체도 사용할 곳이 많겠지만

다음에는 본래 목적인 이미지 AI 모델을 구현해보겠습니다.

우선은 직접 구현의 방식으로 계획 중에 있고,

잘 안된다면 C코드를 불러오는 DPI 방식까지 고려해두겠습니다.

 

 

- 다음 글

(2) FPGA에서 CNN 구현 : https://chonh0531.tistory.com/21

 

Filter - (2) FPGA에서 CNN구현

진행년월: 24.08 목차1. 배경2. 과제 정의 및 개요3. 소스코드4. 시뮬레이션 결과  1. 배경CNN 모델 중 가장 기본적인 Lenet-5 모델을 구현해보려고 합니다.메모리 등을 고려해야 하기 때문에,성능 자

chonh0531.tistory.com

 

진행년월: 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

 

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

진행년월: 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

 

 

진행년월: 24.05

목차

1. 배경

2. 과제 정의 및 개요

3. 소스코드

4. 시뮬레이션 결과

 

 

1. 배경

모든 칩은 통신이 필요하기 때문에 통신 모듈이 들어가있습니다.

그중에서 좀 기본적인 통신 모듈중 하나인 SPI통신을 이해하고 구현해보려고 합니다.

 

 

2. 과제 정의 및 개요

기본적으로 SPI 통신의 표준 프레임인 Motorola의 메뉴얼을 참고하나,

기본적인 구현 자체에 목적이 있기 때문에 불필요한 기능은 빼고 다음과 같이 스펙 정의를 하도록 합니다.

 

1. SPI Mode 2 (CPOL = 1, CPHA = 0)를 채택하여 하강엣지에 데이터를 읽도록 합니다.
2. CS, SCLK, MOSI, MISO 4개의 핀을 사용합니다.
3. 32비트 데이터를 교환하며 구성은 다음과 같습니다.
    - [0,1]비트는 ID
    - [2]비트는 Read / Write 모드 선택
    - [3]비트는 메모리 선택 주소 (D0 or D1)
    - [4~15]비트는 다용도 확장을 위한 빈공간 (주소 확장 등)
    - [16~31]비트는 16비트 데이터

 

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

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

 

 

 

3. 소스코드

`timescale 1ns / 1ps

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

module SPI_Slave(
    // Internal input
    // We don't use internal clock now.
    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
    );
    
    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
        
// READ Mode: data transfer
/*  now, there is enough time to prepare the data
	because there is time for empty 12-bit transfers in preparation for expansion(addr or options, maybe).
    
    If necessary, We can also design the data to be read from the next cycle using internal clk, 
    which is about least four times faster than SCLK.
*/
    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

 

주석으로 적어둔 것 처럼, 가운데 Spare 비트들은 확장을 위해 두었으며,

해당 비트 직후 Read 명령을 수행해야 하는 경우,

SCLK보다 최소 4배정도 빠른 내부 clock을 이용해 처리할 수 있습니다.

 

 

module tb_SPI_Slave();

//    reg i_clk;
    reg i_rst_H;
    reg [1:0] i_ID;
    
    reg i_SCLK;
    reg i_CS;
    reg i_MOSI;
    wire o_MISO;
    
    SPI_Slave SPI(
//        .i_clk(i_clk),
        .i_rst_H(i_rst_H),
        .i_SCLK(i_SCLK),
        .i_CS(i_CS),
        .i_MOSI(i_MOSI),
        .o_MISO(o_MISO),
        .i_ID(i_ID)
    );
    
    initial begin
//        i_clk = 1'b0;
        i_rst_H = 1'b0;
        i_SCLK = 1'b0;
        i_MOSI = 1'b0;
        i_CS = 1'b1;
        i_ID = 2'b01;
        
        // Reset sequence
        #40 i_rst_H = 1'b1;
        #40 i_rst_H = 1'b0;
        
        // Test case 1: Write mode "0101_0000_0000_0000_1100_1100_1100_1101"
        #20 i_CS = 1'b0; //if timing problem in pre_sim -> #10
        send_SPI_data(32'b0101_0000_0000_0000_1100_1100_1100_1101);
        #40 i_CS = 1'b1;
        
        // Wait for a while before the next case
        #40;
        
        // Test case 2: Read mode "0111_0000_0000_0000_0000_0000_0000_0000"
        #40 i_CS = 1'b0;
        send_SPI_data(32'b0111_0000_0000_0000_0000_0000_0000_0000);
        #40 i_CS = 1'b1;
        
        // Test case 3: Read mode, ID Error "1011_0000_0000_0000_0000_0000_0000_0000"
        #40 i_CS = 1'b0;
        send_SPI_data(32'b1011_0000_0000_0000_0000_0000_0000_0000);
        #40 i_CS = 1'b1;

        #100 $finish;
    end
    
    // Clock generation
//    always #5 begin i_clk = ~i_clk; end
    always #20 begin i_SCLK = ~i_SCLK; end
    
    // Task to send data
    task send_SPI_data(input [31:0] data);
        integer i;
        begin
            for (i = 31; i >= 0; i = i - 1) begin
                @ (posedge i_SCLK) // For timing
                    i_MOSI <= data[i];
            end
        end
    endtask
    
endmodule

 

 

4. 시뮬레이션 결과

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

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

 

 

1. Write Mode Test

 

붉은 화살표로 표시한 CS가 0인 구간에 동작을 하고,

결과적으로 D1에 원하는 데이터가 잘 들어간 것을 볼 수 있습니다.

 

 

2. Read Mode Test

 

Read 명령을 받은 후 16비트 타이밍부터 D1에 있는 데이터가

MISO를 통해 잘 출력하고 있는 것을 볼 수 있습니다.

 

 

 

 

- 다음 글

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

 

SPI 통신 - (2) Synthesis, P&R

- 이전 글(1) RTL : https://chonh0531.tistory.com/2 SPI 통신 - (1) RTL목차1. 배경2. 과제 정의 및 개요3. 소스코드4. 시뮬레이션 결과  1. 배경모든 칩은 통신이 필요하기 때문에 통신 모듈이 들어가있습니다.

chonh0531.tistory.com

 

+ Recent posts