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





