Restart – step by step: Read/Write SDRAM via Verilog

By | October 14, 2012

5 years ago, I was trying to design a CCD camera for astrophotography in  my spare time. But since I was major in Software Development, I didn’t know much about hardware design other than 8051 MCU. After 1 year with many failures on wrong directions, I just clarified I’d better use CPLD or FPGA with Verilog/VHDL for sequential logic circuit instead of using a MCU everywhere. It was a hard progress. I had a goal too huge, and was eager for the result.

By making some tiny CPLD projects those years, I had a better understanding on sequential/combinational logic design using Verilog. I made a general experiment board for one of the projects, it mounted a Altera CPLD EPM570T100 and a Cypress 68013A USB client controller with 51 core. So I thought I can continue the camera designing now.

Last time, I was stopped by the RAM size limitation which should store a whole frame data of the Image sensor before sending via USB bulk protocol. So we begin by a little step: adding an external SDRAM to the system.

SDRAM has much complex timing diagram than SRAM. It has 6 operation stages for a simple single read: Active a “row”, Wait For the “tRCD”, send Read Command with a “column”, PRECHARGE(means “DeActive”), Wait for the “CAS Latency”, Read data.

tRCD is a const time related to the chip, my “H57V2562GTR-60C” has the tRCD equals to 18ns.

CAS Latency can be set during “Load Mode Register” stage. It can be 2 or 3 clock period.

More efficiently, there is “Brush Mode” for both reading and writing which can continuous read or write within a Active-Precharge period. But We are not going to discuss about it in this design.

I want to design a module to convert this complex interface into a more simple one. Operations which begin with a START signal on a rising-edge of the clock, need to wait for the FINISH signal only. So that a byte was read out or written into the SDRAM. The complex 6 stages would be hidden behind this module.

A FSM was created to handle this:

The whole write operation simulated at gate level by ModelSim(Altera Edition):

The read operation simulated at gate level:

Since I would operate the module at 10Mhz, the clock period is 100ns, the tRCD(18ns) is less than one clock time, so the NOP at T1 & T2 is not necessary.

My general experiment board with the SDRAM board mounted by half DIP sockets and half “jumper wires”:

Here is the code:

//Author: Lcsky.org
//Reference: http://www-inst.eecs.berkeley.edu/~cs150/fa02/handouts/10/Lab/
//Date: 2012/09/22

// Control module for SDRAM.
module sdram_ctrl (
		 RAM_D,				// to SDRAM
		 RAM_CLK,			// to SDRAM
		 RAM_DQM,			// to SDRAM
		 RAM_CS,				// to SDRAM
		 RAM_RAS,			// to SDRAM
		 RAM_CAS,			// to SDRAM
		 RAM_WE,				// to SDRAM
		 RAM_BA,				// to SDRAM
		 RAM_A,				// to SDRAM
		 CLK_IN, 			// Clock Line
		 CTRL_RESET_IN,	// Control Line
		 CTRL_START_IN,	// Control Line
		 CTRL_W_IN,			// Control Line
		 CTRL_ADDR_IN,		// Control Line
		 CTRL_DATA_IN,		// Control Line
		 CTRL_DATA_OUT,	// Control Line
		 CTRL_DONE_OUT,		// Control Line
		 DEBUG_OUT		// Control Line
	);

	inout[7:0] RAM_D;
	output RAM_CLK;
	output RAM_DQM;
	output RAM_CS;
	output RAM_RAS;
	output RAM_CAS;
	output RAM_WE;
	output[1:0] RAM_BA;
	output[12:0] RAM_A;
	input CLK_IN;
	input CTRL_RESET_IN;
	input CTRL_START_IN;
	input CTRL_W_IN;
	input[23:0] CTRL_ADDR_IN;	// 24bit address for 16M units
	input[7:0] CTRL_DATA_IN;
	output[7:0] CTRL_DATA_OUT;
	output CTRL_DONE_OUT;
	output[3:0] DEBUG_OUT;

	//Clock: 10M
	parameter[31:0] CLK_SPEED = 10000000;

	// counter for states
	wire[15:0] count;
	reg count_rst;

	// current and nextstate
	reg[3:0] CS;
	reg[3:0] NS;

	// different commands that can be issued to the SDRAM
	reg NOP;
	reg loadModeRegister;
	reg active_read, active_write;
	wire active;
	reg read;
	reg write;
	reg precharge;
	reg autoPrecharge;
	reg burstTerminate;
	reg autoRefresh;
	reg selfRefresh;
	reg data_out_lock;

	// counter for incrementing address
	reg[12:0] addr_out;
	reg[1:0] bank_addr;

	reg[23:0] ctrl_addr_in;
	reg[7:0] ctrl_data_in;
	reg[7:0] ctrl_data_out;
	reg ctrl_done;

	// mapping of commands to control lines Table1 p12 DATA sheet
	assign RAM_RAS = ~(active | precharge |
				  autoRefresh | selfRefresh
				  | loadModeRegister);
	assign RAM_CAS = ~(read | write |
				  autoRefresh | selfRefresh
				  | loadModeRegister);
	assign RAM_WE = ~(write | burstTerminate |
				 precharge | loadModeRegister);

	assign RAM_CLK = ~CLK_IN;
	assign RAM_CLKE = 1;
	assign RAM_DQM = 0;
	assign RAM_CS = 0;
	assign RAM_BA = bank_addr;
	assign RAM_A = addr_out;
	assign DEBUG_OUT = CS;

	assign active = active_write | active_read;

	assign RAM_D = write ? ctrl_data_in : 8'bzzzzzzzz;

	assign CTRL_DATA_OUT = ctrl_data_out;
	assign CTRL_DONE_OUT = ctrl_done;

	// muxing of row addr, column addr, and mode-register data
	// into to 12bit address line
	always @(ctrl_addr_in or loadModeRegister or read or write or active_read or active_write) 
	begin
		if (write) begin //(addr = column addr) when reading and writting, A10=0 (no auto precharge)
			addr_out = {2'b00, 1'b0 /* A10 */, 1'b0, ctrl_addr_in[8:0]};
		end else if (read) //(addr = column addr) when reading and writting, A10=0 (no auto precharge)
			addr_out = {2'b00, 1'b0 /* A10 */, 1'b0, ctrl_addr_in[8:0]};
		else if (loadModeRegister) // mode data
			addr_out = {3'b000, 1'b1 /* Single Burst */, 2'b00, 3'b010 /* CAS Latency = 2 */, 1'b0, 3'b000};
		else if (active_write) //(addr = row addr) when active_write
			addr_out = ctrl_addr_in[21:9];
		else begin //(addr = row addr) when active_read
			addr_out = ctrl_addr_in[21:9];
		end
	end

	always@(ctrl_addr_in or active_read or active_write or read or write)
	begin
		if (active_read|read)
			bank_addr = ctrl_addr_in[23:22];
		else 
			bank_addr = ctrl_addr_in[23:22];
	end

	counter #(16) state_counter(.OUT(count), .CLK(CLK_IN),
					 .ENABLE(1), .RESET(count_rst));

	parameter[3:0]
		idle = 0,
		init_wait = 1,
		init_precharge = 2,
		init_auto_refresh1 = 3,
		init_auto_refresh2 = 4,
		init_load_mode_reg = 5,
		pause_state = 7,
		read_state = 8,
		write_state = 9,
		stop_state = 10;

	always @ (posedge CLK_IN) begin
		if (CTRL_RESET_IN) begin
			CS <= idle;
		end else begin
			CS <= NS;
		end
	end

	always @ (negedge CLK_IN) begin
		if (data_out_lock && CS == read_state) begin
			ctrl_data_out <= RAM_D;
		end
	end

	always @(*) begin 
		NOP = 0;
		loadModeRegister = 0;
		active_read = 0;
		active_write = 0;
		read = 0;
		write = 0;
		precharge = 0;
		autoPrecharge = 0;
		burstTerminate = 0;
		autoRefresh = 0;
		selfRefresh = 0;
		data_out_lock = 0;

		count_rst = 0;

		case (CS) /* synthesis full_case */

			idle:begin
				NS = init_wait;
				count_rst = 1;
				ctrl_done = 0;
			end

			init_wait:begin // nop for 100 micro sec
				NOP = 1;
				if (count == CLK_SPEED/10000) begin
					NS = init_precharge;
					count_rst = 1;
				end else
					NS = init_wait;
			end

			init_precharge:begin // 2 cycles (all banks): precharge, nop
				if (count == 0) begin
					precharge = 1;
					NS = init_precharge;
				end else begin
					NOP = 1;
					count_rst = 1;
					NS = init_auto_refresh1;
				end
			end

			init_auto_refresh1:begin // 8 cycles: auto-refresh, nopX7
				if (count == 0) begin
					autoRefresh = 1;
					NS = init_auto_refresh1;
				end else if (count == 7) begin
					NOP = 1;
					NS = init_auto_refresh2;
					count_rst = 1;
				end else begin
					NOP = 1;
					NS = init_auto_refresh1;
				end
			end

			init_auto_refresh2:begin // 8 cycles: auto-refresh, nopX7
				if (count == 0) begin
					autoRefresh = 1;
					NS = init_auto_refresh2;
				end else if (count == 7) begin
					NOP = 1;
					NS = init_load_mode_reg;
					count_rst = 1;
				end else begin
					NOP = 1;
					NS = init_auto_refresh2;
				end
			end

			init_load_mode_reg:begin // 2 cycles
				if (count == 0) begin
					loadModeRegister = 1;
					NS = init_load_mode_reg;
				end else begin
					NOP = 1;
					count_rst = 1;
					NS = pause_state;
				end
			end

			pause_state: begin // waiting for CTRL_START_IN
				NOP = 1;
				if (CTRL_START_IN) begin
					NS = CTRL_W_IN ? write_state : read_state;
					count_rst = 1;

				end else begin
					NS = pause_state;
					ctrl_done = 1;
				end
			end

			write_state: begin // 3 cycles: active, write, precharge
				NS = write_state;
				if (count == 0) begin
					ctrl_addr_in = CTRL_ADDR_IN;
					ctrl_data_in = CTRL_DATA_IN;
					ctrl_done = 0;

					active_write = 1;
				end else if (count == 1) begin
					write = 1;
				end else begin
					count_rst = 1;
					precharge = 1;
					NS = pause_state;
				end
			end

			read_state: begin // 4 cycles: active, read, precharge, nop(get data back)
				NS = read_state;
				if (count == 0) begin
					ctrl_addr_in = CTRL_ADDR_IN;
					ctrl_done = 0;

					active_read = 1;
				end else if (count == 1) begin
					read = 1;
				end else if (count == 2) begin
					precharge = 1;
				end else begin
					// trigger another "always block" to read data in half period later.
					data_out_lock = 1;

					NOP = 1;
					count_rst = 1;
					NS = pause_state;
				end
			end

			stop_state: begin // stop 
				NOP = 1;
				count_rst = 1;
				NS = stop_state;
			end
		endcase
	end
endmodule

Leave a Reply

Your email address will not be published. Required fields are marked *