【FPGA开发】SDRAM的读写VGA显示

1. 原理及实验目的介绍

1.1 实验目的

        实验的目的旨在使用sdram作为中间媒介,在sdram上读写以在显示器上显示图像的目的。

1.2 实验原理

1.2.1 SDRAM型号介绍

这里我们使用的sdram为Hynix公司生产的型号为HY57V281620F的 芯片,其存储空间可以分为4Bank x 2M x16Bit,其中4Bank指的是该芯片有4个bank区间,2M指的是每个bank2M的存储地址,列地址A0~A8,行地址是 A0~A11,16bit指的是每个地址空间可以存16bit的数据,因此该芯片的总存储容量为128Mbit。

在本次实验中,因为我们不显示视频,我们的实验目的仅是为了显示一张640*480像素的图片,按照像素RGB的显示,每个像素的大小为8bits。所以我们所需要的大小为 640*480 bytes = 307200 bytes。SDRAM一个bank的空间大小为2^21 = 2097152 bytes 也就是32Mb,所以说一个bank就足够容纳这个图像的内容空间。因此我们在这次实验中,只需要对一个bank进行操作。

1.2.2 SDRAM的运行机制 :

SDRAM中使用的电容器来存储数据,但这些电容器不是永久性的存储介质。电容器中的电荷会逐渐泄漏,导致存储的信息消失。为了防止数据丢失,需要定期刷新操作(ref)。sdram和外界只有两个交流的接口

1.  cmd[17:0] : 用于控制sdram的一些操作过程指令

cmd组成 : {Cs_n,Ras_n,Cas_n,We_n,Ba,Addr}

具体代表的功能如下:

2.  dq[15:0] : 用于sdram数据读写的传输通道

2. 代码及功能框架设计:

2.1 功能框架 : 

我们在设计了五个模块分别是 : 

  • sdram 初始化模块 
  • switch sdram状态跳转仲裁模块
  • sdram刷新模块
  • write 写sdram的模块
  • read  读sdram的模块

2.2 代码框架

2.2.1 ini_module

对于sdram来说,在上电之后优先要进行的就是初始化的过程。在完成初始化之后,我们才可以进行后续的刷新和读写。具体的需要在这个过程中加入以下的几个步骤

比较重要的是对于模式寄存器的配置:(MRS)具体如下:

在本次实验中我们使用突发读和突发写,并且顺行突发,并且单次突发长度为16bits

具体代码如下:


// -----------------------------------------------------------------------------
// Copyright (c) 2014-2023 All rights reserved
// -----------------------------------------------------------------------------
// Author : XIBO WU (Gatsby) wuxibo2023@163.com
// File   : sdram_init.v
// Create : 2023-12-08 14:55:32
// Revise : 2023-12-15 15:07:25
// Editor : sublime text3, tab size (4)
// -----------------------------------------------------------------------------


/*----------------------------------------------------------------------------------------------------
sdram初始化
cmd真值表		cs_n	ras_n	cas_n	we_n	ba[1:0]	addr[11]	addr[10],addr[9:0]
	NOP			0		1		1		1		X		X			X		X
	PRLL		0		0		1		0		X		x			1		X
	REF			0		0		0		1		X		X			X		X

MR模式寄存器		{ba[1:0],addr[11:0]}	=	{00,00,000,011,0,010}
		
-----------------------------------------------------------------------------------------------------*/

module	sdram_ini(
			input	wire		i_clk,	//50MHZ
			input	wire		i_rst,
			
			output	reg			o_ini_end_flag,
			output	reg[17:0]	o_ini_cmd
			);
	
	parameter	TRP		=	2,
				TRC		=	4,
				TMRD	=	2;

	parameter	DEY_200US		=	9999,
				PALL_CNT		=	10000,
				REF_ST1			=	10001+TRP,
				REF_ST2			=	10002+TRP+TRC,
				MRD_CNT			=	10003+TRP+TRC+TRC,
				END_INI_CNT		=	10003+TRP+TRC+TRC+TMRD;
			
	parameter	NOP		=	18'h1c000,//18'b01_1100_0000_0000_0000
				PALL	=	18'h08400,//18'b00_1000_0100_0000_0000
				REF		=	18'h04000,//18'b00_0100_0000_0000_0000
				MR		=	18'h00032;//18'b00_0000_0000_0011_0010
			
	
	reg[15:0]	ini_cnt;


	always @(posedge i_clk) begin
		if (i_rst == 1'b1) begin
			ini_cnt <= 'd0;
		end
		else if (ini_cnt == END_INI_CNT)begin
			ini_cnt <= 'd0;
		end
		else if(o_ini_end_flag == 1'b0) begin
			ini_cnt <= ini_cnt + 'd1;
		end
		else begin
			ini_cnt <= ini_cnt;
		end
	end


	always @(posedge i_clk) begin
		if (i_rst == 1'b1) begin
			o_ini_end_flag <= 1'b0;
		end
		else if (ini_cnt >= END_INI_CNT)begin
			o_ini_end_flag <= 1'b1;
		end
		else begin
			o_ini_end_flag <= o_ini_end_flag;
		end
	end

	always @(posedge i_clk) begin
		if (i_rst == 1'b1) begin
			o_ini_cmd <= NOP;
		end
		else if (ini_cnt <= DEY_200US)begin
			o_ini_cmd <= NOP;
		end
		else if(ini_cnt == PALL_CNT) begin
			o_ini_cmd <= PALL;
		end
		else if(ini_cnt > PALL_CNT && ini_cnt < REF_ST1) begin
			o_ini_cmd <= NOP;
		end
		else if(ini_cnt == REF_ST1) begin
			o_ini_cmd <= REF;
		end
		else if(ini_cnt > REF_ST1 && ini_cnt < REF_ST2) begin
			o_ini_cmd <= NOP;
		end
		else if(ini_cnt == REF_ST2) begin
			o_ini_cmd <= REF;
		end
		else if(ini_cnt > REF_ST2 && ini_cnt < MRD_CNT) begin
			o_ini_cmd <= NOP;
		end
		else if(ini_cnt == MRD_CNT)begin
			o_ini_cmd <= MR;
		end
		else if(ini_cnt > MRD_CNT)begin
			o_ini_cmd <= NOP;
		end
	end


	
endmodule		

2.2.2 sd_main_ctrl 

这个模块是sdram的控制模块,我们在这个模块中,控制sdram的状态机跳转。具体的状态机跳转如下:

对于这个sdram芯片我们需要每15.625us进行一次刷新,所以我们在读写的过程中如果发生刷新的请求,我们必须中断我们的读写过程来进行刷新的操作。我们需要注意的是我们跳转回仲裁状态机的过程中,有两个key_reg,一个是data_end,一个ref_break_end。data_end,表示数据已经传输完成了,ref_break_end表示中断了进行刷新。

2.2.3 sdram_ref

SDRAM 是动态 RAM 必须要通过刷新才能保证给每一个存储单元充电, 并保证存储单元中的数据不丢失。由于此 SDRAM 芯片要求刷新周期是 ,4096 次刷新 /64ms. 如果按照等间隔去执行刷新的话 64ms/4096=15.625us,我们的时钟频率是50mhz,每个周期的长度为20ns,所以说我们每隔780个时钟周期,我们就需要进行一次刷新。我们用一个计数器周期性的发出刷新请求。

代码如下:


// -----------------------------------------------------------------------------
// Copyright (c) 2014-2024 All rights reserved
// -----------------------------------------------------------------------------
// Author : XIBO WU (Gatsby) wuxibo2023@163.com
// File   : sdram_main_ctrl.v
// Create : 2023-12-13 10:04:05
// Revise : 2024-01-04 18:25:28
// Editor : sublime text3, tab size (4)
// -----------------------------------------------------------------------------

module sdram_main_ctrl(
		input  wire 				i_clk,
		input  wire 				i_vclk,
		input  wire 				i_rst,
		output wire [17:0] 			o_cmd,
		output wire [15:0] 			io_dq,
		output wire [7:0]		    o_vga,
		output wire 			    o_hsync,
		output wire 			    o_vsync,
		input  wire 				i_wfifo_en,
		input  wire [7:0]			i_wfifo_data

	);

	parameter IDLE  = 6'b000001;
	parameter INI   = 6'b000010;
	parameter SW    = 6'b000100;
	parameter REF   = 6'b001000;
	parameter READ  = 6'b010000;
	parameter WRITE = 6'b100000;

	reg [5:0] state;

	//INI
	wire 		ini_flag;
	wire 		ini_end;
	//REF
	wire 		ref_req;
	wire 		ref_en;
	wire 		ref_end;
	//READ
	wire 		read_data_end;
	wire 		read_ref_break_end;
	wire 		r_en;
	wire        r_req;
	reg [15:0]  r_dq;
	wire 		vga_start;
	wire 		vga_rd_en;
	wire [7:0]	vga_rd_data;
	//WRITE
	wire  		write_data_end;
	wire 		write_ref_break_end;
	wire 		w_req;
	wire [15:0] w_dq;
	// wire 		wfifo_en;
	// wire [7:0]	wfifo_data;
	wire 		w_en;				//写使能状态跳转
	wire 		oe;				//output enable



	wire [17:0] ref_cmd,ini_cmd,w_cmd,r_cmd;

	assign ini_flag = (state == INI) ? 1'b1 : 1'b0;          //此处和课程不一样
	assign ref_en 	= (ref_req && state == SW); 
	assign w_en     = (w_req == 1'b1) && (ref_req == 1'b0) && (state == SW);
	assign r_en     = (w_req == 1'b0  && ref_req == 1'b0 && r_req == 1'b1 && state == SW);

	assign o_cmd[17:14] = ref_cmd[17:14] & ini_cmd[17:14] & w_cmd[17:14] & r_cmd[17:14];
	assign o_cmd[13:0]  = ref_cmd[13:0]  | ini_cmd[13:0]  | w_cmd[13:0] | r_cmd[13:0]; 
	assign oe           = (state == WRITE) ? 1'b1 : 1'b0;
	assign o_dq         = oe ? w_dq : 16'dz;

	always @(posedge i_clk) begin
		r_dq <= io_dq;
	end


	always @(posedge i_clk or posedge i_rst) begin
		if (i_rst == 1'b1) begin
			state <= IDLE;
		end
		else begin case(state)
				IDLE : begin
						state <= INI;
				end
				INI : begin 
					if(ini_end == 1'b1) begin
						state <= SW;
					end	
				end
				SW : begin 
					if(ref_en == 1'b1) begin
						state <= REF;
					end
					else if(w_req == 1'b1 && ref_req == 1'b0) begin
						state <= WRITE;
					end
					else if(r_req == 1'b1 && ref_req == 1'b0 && w_req == 1'b0) begin
						state <= READ;
					end 
				end
				REF : begin
					if(ref_end == 1'b1) begin
						state <= SW;
					end
				end 
				WRITE : begin
					if(write_data_end == 1'b1 || write_ref_break_end == 1'b1) begin
						state <= SW;
					end
				end
				READ : begin
					if(read_data_end == 1'b1 || read_ref_break_end == 1'b1) begin
						state <= SW;
					end
				end
			endcase
		end
	end


	sdram_ini inst_sdram_ini (
			.i_clk           (i_clk),
			.i_rst           (i_rst),
			.o_ini_end_flag  (ini_end),
			.o_ini_cmd       (ini_cmd)
		);


	sdram_ref  inst_sdram_ref (
			.i_clk     (i_clk),
			.i_rst     (i_rst),
			.i_ini_end (ini_end),
			.i_ref_en  (ref_en),
			.o_ref_end (ref_end),
			.o_ref_req (ref_req),
			.o_ref_cmd (ref_cmd)
		);

	sdram_wr inst_sdram_wr (
			.i_clk                 (i_clk),
			.i_rst                 (i_rst),
			.o_w_req               (w_req),
			.i_wfifo_en            (wfifo_en),
			.i_wfifo_data          (wfifo_data),
			.i_w_en                (w_en),
			.i_ref_req             (ref_req),
			.o_write_ref_break_end (write_ref_break_end),
			.o_write_data_end      (write_data_end),
			.o_w_cmd               (w_cmd),
			.o_w_dq                (w_dq)
		);


	sdram_rd inst_sdram_rd (
			.i_clk                (i_clk),
			.i_vclk               (i_vclk),
			.i_rst                (i_rst),
			.o_r_req              (r_req),
			.i_r_en               (r_en),
			.i_ref_req            (ref_req),
			.o_read_ref_break_end (read_ref_break_end),
			.o_read_data_end      (read_data_end),
			.o_r_cmd              (r_cmd),
			.i_r_dq               (r_dq),
			.o_vga_start          (vga_start),
			.i_vga_rd_en          (vga_rd_en),
			.o_vga_rd_data        (vga_rd_data)
		);


	//vga显示,显示行和列 ,还有对应各自的像素的数值8bits
	VGA_TIMING  inst_VGA_TIMING (
			.sclk       (i_vclk),
			.rst_n      (vga_start),
			.po_vga     (o_vga),
			.po_de      (po_de),
			.po_v_sync  (o_vsync),
			.po_h_sync  (o_hsync),
			.rgb_pixel  (vga_rd_data),
			.rd_fifo_en (vga_rd_en)
		);



endmodule

2.2.4 sdram_wr

sdram在写的过程比较重要需要考虑的是如何应对ref的刷新进程,并且能在刷新结束之后,继续自己的之前的写入过程。具体我们要实现的流程是我们通过串口把要写的数据写入我们设定好的fifo中,之后写入sdram的时候再从这个fifo读取数据。

状态机如下:

中间涉及一个比较重要的地方就是,本次实验我们只写入同一个bank,所以bank的地址都为0,我们具体要选择写入的列由WRITE中cmd的后几位决定,我们具体要写入的行由ACTIVE状态中,我们激活的行来决定。

代码如下:

// -----------------------------------------------------------------------------
// Copyright (c) 2014-2024 All rights reserved
// -----------------------------------------------------------------------------
// Author : XIBO WU (Gatsby) wuxibo2023@163.com
// File   : sdram_wr.v
// Create : 2023-12-27 17:12:11
// Revise : 2024-01-02 17:32:39
// Editor : sublime text3, tab size (4)
// -----------------------------------------------------------------------------

/*----------------------------------------------------------------------------------------------------
sdram初始化
cmd真值表		cs_n	ras_n	cas_n	we_n	ba[1:0]	addr[11]	addr[10],addr[9:0]
	NOP			0		1		1		1		X		X			X		X
	PRLL		0		0		1		0		X		x			1		X
	REF			0		0		0		1		X		X			X		X
	WR          0       1       0       0       V       V           V       V
	ACT         0       0       1       1       V       V           V       V        

MR模式寄存器		{ba[1:0],addr[11:0]}	=	{00,00,000,011,0,010}
-----------------------------------------------------------------------------------------------------*/


module sdram_wr (
		input	wire 		i_clk,
		input 	wire 		i_rst,
		output  wire 		o_w_req,
		input   wire 		i_wfifo_en,
		input   wire [7:0]  i_wfifo_data,
		input   wire 		i_w_en,
		input   wire 		i_ref_req,
		output  wire 		o_write_ref_break_end,
		output  wire 		o_write_data_end,
		output  wire [17:0]	o_w_cmd,
		output  wire [15:0] o_w_dq
	);

	//sdram一行的大小位512*16 bits 所以当fifo的大小位1024*8bits的时候就可以启动fifo的读取

	parameter IDLE    = 5'b0_0001;
	parameter WREQ    = 5'b0_0010;
	parameter ACTIVE  = 5'b0_0100;
	parameter WRITE   = 5'b0_1000;
	parameter PREC    = 5'b1_0000;


	parameter WR    = 18'b01_0000_0000_0000_0000;
	parameter ACT   = 18'b01_0000_0000_0000_0000;
	parameter NOP   = 18'h1c000;
	parameter PALL	= 18'h08400;//18'b00_1000_0100_0000_0000


	reg [4:0] 	state;
	reg 		sd_ram_start;
	reg [2:0]	act_cnt;	
	reg 		act_end;
	wire[1:0]   bank_addr;
	reg	[11:0]	sd_row_cnt;
	reg 		sd_row_end;
	reg [1:0]	burst_cnt;
	reg [7:0]	burst_col_cnt;
	reg			img_end;
	reg			rd_fifo_en;
	wire[15:0]	rd_fifo_data;
	wire[9 :0]	rd_data_count;
	reg 		sd_row_end_flag;
	reg [3:0] 	pre_cnt;
	reg 		pre_end;
	reg 		ref_break;
	reg [17:0]  cmd;
	wire    	ref_break_end;


	wire 		full,empty;


	//assign 
	assign o_w_dq       		 = rd_fifo_data;
	assign o_write_ref_break_end = (state == PREC) & ref_break & pre_end;
	assign o_write_data_end      = (state == PREC) & sd_row_end_flag & pre_end;
	assign o_w_cmd               = cmd;
	assign bank_addr             = 2'b00;

	always @(posedge i_clk) begin
		if (i_rst == 1'b1) begin
			state <= IDLE; 
		end
		else case (state) 
			IDLE : begin
				if(sd_ram_start == 1'b1) 
					state <= WREQ;
			end		
			WREQ : begin
				if(i_w_en == 1'b1) 
					state <= ACTIVE;
			end
			ACTIVE : begin
				if(act_end == 1'b1) 
					state <= WRITE;
			end
			WRITE: begin
				if(sd_row_end == 1'b1 || (burst_cnt == 'd3 && i_ref_req == 1'b1))
					state <= PREC;
			end 
			PREC: begin
				if(pre_end == 1'b1 && sd_row_end_flag == 1'b1)
					state <= IDLE;
				else if(ref_break == 1'b1 && pre_end == 1'b1)
				 	state <= WREQ;
			end
			default : state <= IDLE;
		endcase

	end
	

	//IDLE
	always @(posedge i_clk) begin
		if (i_rst == 1'b1) begin
			sd_ram_start <= 1'b0;
		end
		else if(state != IDLE) begin
			sd_ram_start <= 1'b0;
		end
		else if (state == IDLE && rd_data_count >= 'd510) begin
			sd_ram_start <= 1'b1;
		end
	end


	//WREQ
	assign o_w_req = (state == WREQ);



	//ACTIVE
	always @(posedge i_clk) begin
		if (i_rst == 1'b1) begin
			act_cnt <= 'd0;
		end
		else if(state == ACTIVE && act_cnt == 'd3) begin
			act_cnt <= 'd0;
		end
		else if (state == ACTIVE) begin
			act_cnt <= act_cnt + 'd1;
		end
	end

	always @(posedge i_clk) begin
		if (i_rst == 1'b1) begin
			act_end <= 'd0;
		end
		else if (act_cnt == 'd2) begin
			act_end <= 1'b1;
		end
		else begin
			act_end <= 1'b0;
		end
	end


	//WRITE
	always @(posedge i_clk) begin
		if (i_rst == 1'b1) begin
			burst_cnt <= 'd0;
		end
		else if (state == WRITE) begin
			burst_cnt <= burst_cnt + 'd1;
		end
		else begin
			burst_cnt <= 'd0;
		end
	end

	always @(posedge i_clk) begin
		if (i_rst == 1'b1) begin
			burst_col_cnt <= 'd0;
		end
		else if (sd_row_end == 1'b1) begin
			burst_col_cnt <= 'd0;
		end
		else if (state == WRITE && burst_cnt == 'd3) begin
			burst_col_cnt <= burst_col_cnt + 'd1;
		end
	end


	always @(posedge i_clk) begin
		if (i_rst == 1'b1) begin
			sd_row_end <= 1'b0;
		end
		else if (burst_col_cnt == 'd127 && burst_cnt == 'd2 && state == WRITE) begin
			sd_row_end <= 1'b1;
		end
		else begin
			sd_row_end <= 1'b0;
		end
	end

	always @(posedge i_clk) begin
		if (i_rst == 1'b1) begin
			sd_row_cnt <= 'd0;
		end
		else if (sd_row_cnt == 'd299 && sd_row_end == 1'b1) begin
			sd_row_cnt <= 'd0;
		end
		else if (sd_row_end == 1'b1)begin
			sd_row_cnt <= sd_row_cnt + 'd1;
		end
	end

	always @(posedge i_clk) begin
		if (i_rst == 1'b1) begin
			img_end <= 1'b0;
		end
		else if (sd_row_cnt == 'd299 && burst_cnt == 'd2) begin
			img_end <= 1'b1;
		end
		else begin
			img_end <= 1'b0;
		end
	end


	always @(posedge i_clk) begin
		if (i_rst == 1'b1) begin
			rd_fifo_en <= 1'b0;
		end
		else if (state == WRITE) begin
			rd_fifo_en <= 1'b1;
		end
		else begin
			rd_fifo_en <= 1'b0;
		end
	end


	//PRECHARGE
	always @(posedge i_clk) begin
		if (i_rst == 1'b1) begin
			sd_row_end_flag <= 1'b0;
		end
		else if (state == PREC && pre_cnt == 'd8) begin
			sd_row_end_flag <= 1'b0;
		end
		else if (state == WRITE && sd_row_end == 1'b1) begin
			sd_row_end_flag <= 1'b1;
		end
		else begin
			sd_row_end_flag <= sd_row_end_flag;
		end
	end

	always @(posedge i_clk) begin
		if (i_rst == 1'b1) begin
			pre_cnt <= 'd0;
		end
		else if (state == PREC && pre_cnt == 'd8) begin
			pre_cnt <= 'd0;
		end
		else if (state == PREC) begin
			pre_cnt <= pre_cnt + 'd1;
		end
		else begin
			pre_cnt <= pre_cnt;
		end
	end

	always @(posedge i_clk) begin
		if (i_rst == 1'b1) begin
			pre_end <= 1'b0;
		end
		else if (state == PREC && pre_cnt == 'd7) begin
			pre_end <= 1'b1;
		end
		else begin
			pre_end <= 1'b0;
		end
	end

	//PREC & REF
	always @(posedge i_clk) begin
		if (i_rst == 1'b1) begin
			ref_break <= 1'b0;
		end
		else if (state == PREC && pre_cnt == 'd8) begin
			ref_break <= 1'b0;
		end
		else if (burst_cnt == 'd3 && i_ref_req == 1'b1 && sd_row_end != 1'b1 && state == WRITE) begin
			ref_break <= 1'b1;
		end
		else begin
			ref_break <= ref_break;
		end
	end


	//ALL
	always @(posedge i_clk) begin
		if (i_rst == 1'b1) begin
			cmd <= NOP;
		end
		else if (state == ACTIVE && act_cnt == 'd0) begin
			cmd <= {ACT[17:14],bank_addr,sd_row_cnt};
		end
		else if(state == WRITE && burst_cnt == 'd0) begin
			cmd <= {WR[17:14],bank_addr,{3'b000,burst_col_cnt,2'b00}};
		end
		else if(state == PREC && sd_row_end == 1'b1) begin
			cmd <= PALL;
		end
		else begin
			cmd <= NOP;
		end
	end


	asfifo_w2048x8_r1024x16 wrbuffer (
	  .wr_clk(i_clk),                // input wire wr_clk
	  .rd_clk(i_clk),                // input wire rd_clk
	  .din(i_wfifo_data),            // input wire [7 : 0] din
	  .wr_en(i_wfifo_en),            // input wire wr_en
	  .rd_en(rd_fifo_en),            // input wire rd_en
	  .dout(rd_fifo_data),           // output wire [15 : 0] dout
	  .full(full),                   // output wire full
	  .empty(empty),                 // output wire empty
	  .rd_data_count(rd_data_count)  // output wire [9 : 0] rd_data_count
	);

endmodule

2.2.5 sdram_rd

sdram的读取是整个状态机优先级最低的一个阶段,我们要读取的时候要保证,我们这个过程中没有进行刷新的写入的操作,读取的流程如下,我们从sdram读取数据之后,我们保存数据到我们的fifo中,因为此次实验,我们要做的事VGA的图像显示,所以说,我们在这个阶段就用vga模块读取这个fifo里面的数据,最后显示出来。

状态机如下:

这个状态机和写状态机十分相像,但是一些触发条件有一些细化的区别,我们读取的行列也和写一样,ACTIVE来激活我们要读取的行,然后用READ阶段的cmd来读取我们所需要的列。

代码如下:


// -----------------------------------------------------------------------------
// Copyright (c) 2014-2024 All rights reserved
// -----------------------------------------------------------------------------
// Author : XIBO WU (Gatsby) wuxibo2023@163.com
// File   : sdram_rd.v
// Create : 2024-01-03 11:41:34
// Revise : 2024-01-04 18:13:02
// Editor : sublime text3, tab size (4)
// -----------------------------------------------------------------------------


/*----------------------------------------------------------------------------------------------------
sdram初始化
cmd真值表		cs_n	ras_n	cas_n	we_n	ba[1:0]	addr[11]	addr[10],addr[9:0]
	NOP			0		1		1		1		X		X			X		X
	PRLL		0		0		1		0		X		x			1		X
	REF			0		0		0		1		X		X			X		X
	WR          0       1       0       0       V       V           V       V
	RD          0       1       0       1       V       V           V       V
	ACT         0       0       1       1       V       V           V       V        

MR模式寄存器		{ba[1:0],addr[11:0]}	=	{00,00,000,011,0,010}
-----------------------------------------------------------------------------------------------------*/


module sdram_rd (
		input	wire 		i_clk,
		input   wire 		i_vclk, //25mHz vga CLK 查vga相关的文档可以知道
		input 	wire 		i_rst,
		output  wire 		o_r_req,
		input   wire 		i_r_en,
		input   wire 		i_ref_req,
		output  wire 		o_read_ref_break_end,
		output  wire 		o_read_data_end,
		output  wire [17:0]	o_r_cmd,
		input   wire [15:0] i_r_dq,
		output 	wire 		o_vga_start,
		input   wire		i_vga_rd_en,
		output  wire [7:0]  o_vga_rd_data
	);
 
	//sdram一行的大小位512*16 bits 所以当fifo的大小位1024*8bits的时候就可以启动fifo的读取

	parameter IDLE    = 5'b0_0001;
	parameter RREQ    = 5'b0_0010;
	parameter ACTIVE  = 5'b0_0100;
	parameter READ    = 5'b0_1000;
	parameter PREC    = 5'b1_0000;


	parameter WR    = 18'b01_0000_0000_0000_0000;
	parameter RD    = 18'b01_0100_0000_0000_0000;
	parameter ACT   = 18'b01_0000_0000_0000_0000;
	parameter NOP   = 18'h1c000;
	parameter PALL	= 18'h08400;//18'b00_1000_0100_0000_0000


	reg [4:0] 	state;
	reg 		sd_read_start;
	reg [2:0]	act_cnt;	
	reg 		act_end;
	wire[1:0]   bank_addr;
	reg	[11:0]	sd_row_cnt;
	reg 		sd_row_end;
	reg [1:0]	burst_cnt;
	reg [7:0]	burst_col_cnt;
	reg			img_end;
	reg			rd_fifo_en;
	wire[15:0]	rd_fifo_data;
	wire[10:0]	rd_data_count;
	wire[10:0]  wr_data_count;
	reg 		sd_row_end_flag;
	reg [3:0] 	pre_cnt;
	reg 		pre_end;
	reg 		ref_break;
	reg [17:0]  cmd;
	wire    	ref_break_end;
	wire 		wfifo_en;
	wire [7:0]  wfifo_data;
	reg  [4:0]	read_state_dly;
	reg 		vga_fifo_ready;
	wire 		full,empty;


	//assign 
	assign o_r_dq       		 = rd_fifo_data;
	assign o_read_ref_break_end  = (state == PREC) & ref_break & pre_end;
	assign o_read_data_end       = (state == PREC) & sd_row_end_flag & pre_end;
	assign o_r_cmd               = cmd;
	assign bank_addr             = 2'b00;

	always @(posedge i_clk) begin
		if (i_rst == 1'b1) begin
			state <= IDLE; 
		end
		else begin case (state) 
			IDLE : begin
				if(sd_read_start == 1'b1) 
					state <= RREQ;
			end		
			RREQ : begin
				if(i_r_en == 1'b1) 
					state <= ACTIVE;
			end
			ACTIVE : begin
				if(act_end == 1'b1) 
					state <= READ;
			end
			READ: begin
				if(sd_row_end == 1'b1 || (burst_cnt == 'd3 && i_ref_req == 1'b1))
					state <= PREC;
			end 
			PREC: begin
				if(pre_end == 1'b1 && sd_row_end_flag == 1'b1)
					state <= IDLE;
				else if(ref_break == 1'b1 && pre_end == 1'b1)
				 	state <= RREQ;
			end
			default : state <= IDLE;
		endcase
	end

	end
	

	//IDLE
	//当fifo中的大小小于512的时候 才需要继续写入 这是一个阈值的问题
	//当fifo的有效数据太多的时候,我们应该优先考虑的是把fifo中的数据发送给VGA
	always @(posedge i_clk) begin
		if (i_rst == 1'b1) begin
			sd_read_start <= 1'b0;
		end
		else if(state != IDLE) begin
			sd_read_start <= 1'b0;
		end
		else if (state == IDLE && wr_data_count <'d512) begin
			sd_read_start <= 1'b1;
		end
	end


	//RREQ
	assign o_r_req = (state == RREQ);



	//ACTIVE
	always @(posedge i_clk) begin
		if (i_rst == 1'b1) begin
			act_cnt <= 'd0;
		end
		else if(state == ACTIVE && act_cnt == 'd3) begin
			act_cnt <= 'd0;
		end
		else if (state == ACTIVE) begin
			act_cnt <= act_cnt + 'd1;
		end
	end

	always @(posedge i_clk) begin
		if (i_rst == 1'b1) begin
			act_end <= 'd0;
		end
		else if (act_cnt == 'd2) begin
			act_end <= 1'b1;
		end
		else begin
			act_end <= 1'b0;
		end
	end


	//READ
	always @(posedge i_clk) begin
		if (i_rst == 1'b1) begin
			burst_cnt <= 'd0;
		end
		else if (state == READ) begin
			burst_cnt <= burst_cnt + 'd1;
		end
		else begin
			burst_cnt <= 'd0;
		end
	end

	always @(posedge i_clk) begin
		if (i_rst == 1'b1) begin
			burst_col_cnt <= 'd0;
		end
		else if (sd_row_end == 1'b1) begin
			burst_col_cnt <= 'd0;
		end
		else if (state == READ && burst_cnt == 'd3) begin
			burst_col_cnt <= burst_col_cnt + 'd1;
		end
	end


	always @(posedge i_clk) begin
		if (i_rst == 1'b1) begin
			sd_row_end <= 1'b0;
		end
		else if (burst_col_cnt == 'd127 && burst_cnt == 'd2 && state == READ) begin
			sd_row_end <= 1'b1;
		end
		else begin
			sd_row_end <= 1'b0;
		end
	end

	always @(posedge i_clk) begin
		if (i_rst == 1'b1) begin
			sd_row_cnt <= 'd0;
		end
		else if (sd_row_cnt == 'd299 && sd_row_end == 1'b1) begin
			sd_row_cnt <= 'd0;
		end
		else if (sd_row_end == 1'b1)begin
			sd_row_cnt <= sd_row_cnt + 'd1;
		end
	end

	always @(posedge i_clk) begin
		if (i_rst == 1'b1) begin
			img_end <= 1'b0;
		end
		else if (sd_row_cnt == 'd299 && burst_cnt == 'd2) begin
			img_end <= 1'b1;
		end
		else begin
			img_end <= 1'b0;
		end
	end


	always @(posedge i_clk) begin
		if (i_rst == 1'b1) begin
			rd_fifo_en <= 1'b0;
		end
		else if (state == READ) begin
			rd_fifo_en <= 1'b1;
		end
		else begin
			rd_fifo_en <= 1'b0;
		end
	end


	//PRECHARGE
	always @(posedge i_clk) begin
		if (i_rst == 1'b1) begin
			sd_row_end_flag <= 1'b0;
		end
		else if (state == PREC && pre_cnt == 'd8) begin
			sd_row_end_flag <= 1'b0;
		end
		else if (state == READ && sd_row_end == 1'b1) begin
			sd_row_end_flag <= 1'b1;
		end
		else begin
			sd_row_end_flag <= sd_row_end_flag;
		end
	end

	always @(posedge i_clk) begin
		if (i_rst == 1'b1) begin
			pre_cnt <= 'd0;
		end
		else if (state == PREC && pre_cnt == 'd8) begin
			pre_cnt <= 'd0;
		end
		else if (state == PREC) begin
			pre_cnt <= pre_cnt + 'd1;
		end
		else begin
			pre_cnt <= pre_cnt;
		end
	end

	always @(posedge i_clk) begin
		if (i_rst == 1'b1) begin
			pre_end <= 1'b0;
		end
		else if (state == PREC && pre_cnt == 'd7) begin
			pre_end <= 1'b1;
		end
		else begin
			pre_end <= 1'b0;
		end
	end

	//PREC & REF
	always @(posedge i_clk) begin
		if (i_rst == 1'b1) begin
			ref_break <= 1'b0;
		end
		else if (state == PREC && pre_cnt == 'd8) begin
			ref_break <= 1'b0;
		end
		else if (burst_cnt == 'd3 && i_ref_req == 1'b1 && sd_row_end != 1'b1 && state == READ) begin
			ref_break <= 1'b1;
		end
		else begin
			ref_break <= ref_break;
		end
	end


	//ALL
	always @(posedge i_clk) begin
		if (i_rst == 1'b1) begin
			cmd <= NOP;
		end
		else if (state == ACTIVE && act_cnt == 'd0) begin
			cmd <= {ACT[17:14],bank_addr,sd_row_cnt};
		end
		else if(state == READ && burst_cnt == 'd0) begin
			cmd <= {RD[17:14],bank_addr,{3'b000,burst_col_cnt,2'b00}};
		end
		else if(state == PREC && sd_row_end == 1'b1) begin
			cmd <= PALL;
		end
		else begin
			cmd <= NOP;
		end
	end


	always @(posedge i_clk) begin
		if (i_rst == 1'b1) begin
			read_state_dly <= 4'b0;
		end
		else begin
			read_state_dly <= {read_state_dly[3:0],state[3]};
		end
	end



	always @(posedge i_clk) begin
		if (i_rst == 1'b1) begin
			vga_fifo_ready <= 1'b0;
		end
		else if(rd_data_count > 648)begin
			vga_fifo_ready <= 1'b1;
		end
	end


//VGAVGAVGA
assign wfifo_data  = i_r_dq;
assign wfifo_en    = read_state_dly[4];
assign o_vga_start = vga_fifo_ready;


//vga必须等fifo缓冲了一定数据之后才能读数据

asfifo_w2048x16r4096x8 rdbuffer (
	  .wr_clk(clk), // input wr_clk
	  .rd_clk(i_vclk), // input rd_clk      -> 此处是vga读取fifo的数据来显示
	  .din(wfifo_data), // input [15 : 0] din
	  .wr_en(wfifo_en), // input wr_en
	  .rd_en(i_vga_rd_en), // input rd_en
	  .dout(o_vga_rd_data), // output [7 : 0] dout
	  .full(full), // output full
	  .empty(empty), // output empty
	  .rd_data_count(rd_data_count), // output [10 : 0] rd_data_count
	  .wr_data_count(wr_data_count) // output [10 : 0] wr_data_count
);
endmodule






3. 顶层功能及框架描述

3.1 顶层功能描述:

本次实验中,我们要显示的是通过串口写入sdram中,然后再通过读取sdram显示到显示器上。我们通过matlab解析一个图片的RGB内容,rgb的单像素内容为8bits,我们用串口发送这些数据,到fifo中。等到能够写满bank一行的时候,我们开始写操作。但在没开始写操作的时候,我们的读操作已经开始读取sdram了,这个时候读出来的就是白条,等到我们把sdram写完的时候,这些白条的位置就会被相应的数据覆盖。

下面看一下顶层的代码:

顶层的代码之留下了和sdram和串口,以及VGA相应行列RGB数据的相应接口


// -----------------------------------------------------------------------------
// Copyright (c) 2014-2024 All rights reserved
// -----------------------------------------------------------------------------
// Author : XIBO WU (Gatsby) wuxibo2023@163.com
// File   : top_sdram.v
// Create : 2023-12-08 17:08:26
// Revise : 2024-01-04 18:26:14
// Editor : sublime text3, tab size (4)
// -----------------------------------------------------------------------------


module top_sdram(
	input					i_clk,
	input					i_rst_n,
	input					i_rx,   
	inout      [15 : 0] 	io_Dq,
    output     [11 : 0] 	o_Addr,
    output     [1 : 0] 		o_Ba,
    output     				o_sdclk,
    output              	o_Cke,
    output 					o_Cs_n,
    output 					o_Ras_n,
    output					o_Cas_n,
    output 					o_We_n,
	output		[7:0]		o_vga,
    output				    o_hsync,
    output					o_vsync,
    output     [1: 0] 		o_Dqm       
	);

	wire 	   rst;
	wire 	   sclk;
	wire 	   vclk;
	wire [7:0] po_data;
	wire 	   po_flag;
	assign o_Dqm = 2'b00;
	assign rst = ~i_rst_n;
	assign o_Cke = 1'b1;
	assign o_sdclk = i_clk;//50 Mhz




	clk_gen inst_clk_gen (
	    // Clock out ports
	    .clk_50mhz(sclk),     // output clk_50mhz
	    .clk_25mhz(vclk),     // output clk_25mhz
	   // Clock in ports
	    .clk_in1(i_clk)
	);      // input clk_in1

	sdram_main_ctrl inst_sdram_main_ctrl (
		.i_clk (sclk),
		.i_vclk(vclk),
		.i_rst (rst),
		.o_cmd ({o_Cs_n,o_Ras_n,o_Cas_n,o_We_n,o_Ba,o_Addr}),
		.o_dq  (io_Dq),
		.o_vga (o_vga),
		.o_hsync(o_hsync),
		.o_vsync(o_vsync),
		.i_wfifo_en (po_flag),
		.i_wfifo_data (po_data)
	);


	uart_rx  inst_uart_rx (
		.sclk    (sclk),
		.rst_n   (i_rst_n),
		.rx      (rx),
		.po_data (po_data),
		.po_flag (po_flag)
	);



endmodule 

4. 结语

这里面还有VGA_TIMING模块还有uart_rx模块,在这里我们就不做展示了,后面的时候,后面在其他的文章里,会专门写串口和vga的部分的代码,这些都是可以复用的模块,后面会进行使用。

我们这次做的实验到这里就结束了

  • 26
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值