Memory controller for IS42s16160B (On-board memory for DE0 Nano) - Page 1
Forum section: electronics / general

author: pigtwo
author_rank: Regular Contributor
timestamp: June 28, 2014, 11:26:03 pm
content: Hello everyone, I'm just wondering if anyone has written a memory controller for the on-board memory for the DE0 Nano(IS42216160B) and wouldn't mind posting their code.I made a memory controller for it but it doesn't seem to work so I wanted to see a reference to try to figure out what I'm doing wrong.Just so you guys don't think I'm just trying to get some code without writing any myself I'll post my code. But I'm not asking anyone to look through it(I imagine that would be very painful).I found one resource where someone did write a memory controller for it but it was in VHDL (which I don't know) and it had a lot of features that I didn't need which made it harder to read.Code:module Memory_Controller(input wire Clock_50MHZ, RW, Reset, Execute,input wire[5:0] Data_In,input wire[10:0] Addr_In, // In the form [A12, A11, A8-A0]inout wire [5:0] Mem_IO,output wire[5:0] Data_Out,output wire[10:0] Addr_Out,output wire Data_Ready, CS, RAS, CAS, WE, BA0, BA1, A9, A10, CKE, Ready, DQML, DQMH);// This is a basic memory controller for the IS42S16160B memory chip. Which has 256 Mb of memory in 16 bit words.// RW: 1 means write, 0 means read// Symbolic mode register defintionlocalparam[14:0] mode_reg = 15'b00000100010000;// Symbolic OP-Code definition. The OP Code is in the form [!CS, !RAS, !CAS, !WE]localparam[3:0]read= 4'b0101,write= 4'b0100,nop= 4'b0111,activate= 4'b0011,refresh= 4'b0001,load_mode_reg= 4'b0000,precharge= 4'b0010;// Symbolic state defintionslocalparam[4:0]Start= 5'b00000,Start_NOP= 5'b00001,Start_Precharge= 5'b00010,Start_Refresh= 5'b00011,Start_Refresh_Loop= 5'b00100,Load_Mode_Reg= 5'b00101,Load_Mode_Reg_NOP1= 5'b00110,Load_Mode_Reg_NOP2= 5'b00111,Idle= 5'b01000,Refresh= 5'b01001,Refresh_NOP1= 5'b01010,Refresh_NOP2= 5'b01011,Refresh_NOP3= 5'b01100,Refresh_NOP4= 5'b01101,Activate= 5'b01110,Activate_NOP= 5'b01111,Read= 5'b10000,Read_NOP1= 5'b10001,Read_NOP2= 5'b10010,Write= 5'b10011,Write_NOP1= 5'b10100,Precharge= 5'b10101,Precharge_NOP= 5'b10110;// Internal defintionsreg[10:0] address, address_next;reg[4:0] state, state_next;reg[3:0] op_code, op_code_next;reg[5:0] data_out, data_out_next, mem_io, mem_io_next;reg[1:0] nop_count, nop_count_next;reg[3:0] refresh_count, refresh_count_next;reg ready, ready_next, data_ready, data_ready_next, ba0, ba0_next, ba1, ba1_next, a9, a9_next, a10, a10_next;// FSMD register and data specificationsalways @(posedge Clock_50MHZ)beginif(!Reset)beginstate <= Start;op_code <= nop;nop_count <= 0;refresh_count <= 0;ready <= 0;data_ready <= 0;address <= 0;ba0 <= 0;ba1 <= 1;a9 <= 0;a10 <= 0;mem_io <= 0;data_out <= 0;endelsebeginstate <= state_next;op_code <= op_code_next;nop_count <= nop_count_next;refresh_count <= refresh_count_next;ready <= ready_next;data_ready <= data_ready_next;address <= address_next;ba0 <= ba0_next;ba1 <= ba1_next;a9 <= a9_next;a10 <= a10_next;mem_io <= mem_io_next;data_out <= data_out_next;endend// FSMD next state definitionsalways @*begin// Define default valuesstate_next = state;op_code_next = op_code;nop_count_next = nop_count;refresh_count_next = refresh_count;ready_next = ready;data_ready_next = data_ready;address_next = address;ba0_next = ba0;ba1_next = ba1;a9_next = a9;a10_next = a10;mem_io_next = mem_io;data_out_next = data_out;case(state)// Initialization sequenceStart:begindata_out_next = 0;ready_next = 0;op_code_next = nop;data_ready_next = 0;refresh_count_next = 0;mem_io_next = 0;ba0_next = 0;// These two may need to be different but I think they can be constant.ba1_next = 0;// They are essentually MSB of the address.state_next = Start_NOP;endStart_NOP:beginop_code_next = nop;state_next = Start_Precharge;endStart_Precharge:beginop_code_next = refresh;nop_count_next = 0;state_next = Start_Refresh;endStart_Refresh:beginop_code_next = nop;nop_count_next = nop_count + 2'd1;if(nop_count == 1)// Four nops are needed in total. This will achive two and the other comes from the statestate_next = Start_Refresh_Loop; // one of the state transisions later on.elsestate_next = Start_Refresh;endStart_Refresh_Loop:beginop_code_next = nop;refresh_count_next = refresh_count + 4'd1;if(refresh_count == 10)state_next = Start_Precharge;elsebeginop_code_next = load_mode_reg;{ba1_next, ba0_next, address_next, address_next, a10_next, a9_next, address_next[8:0]} = mode_reg;state_next = Load_Mode_Reg;endendLoad_Mode_Reg:beginop_code_next = nop;state_next = Load_Mode_Reg_NOP1;endLoad_Mode_Reg_NOP1:beginop_code_next = nop;state_next = Load_Mode_Reg_NOP2;endLoad_Mode_Reg_NOP2:beginop_code_next = nop;refresh_count_next = 0;a10_next = 0;state_next = Idle;end// IdleIdle:begindata_out_next = data_out;ready_next = 1;refresh_count_next = refresh_count + 4'd1;op_code_next = nop;if(refresh_count == 400) //REMOVE////////// replace with frequency dependant value laterbeginop_code_next = refresh;state_next = Refresh;endelse if((refresh_count != 400)&&Execute)beginop_code_next = activate;ready_next = 0;data_ready_next = 0;state_next = Activate;endelsebeginstate_next = Idle;endend// Refresh sequenceRefresh:beginop_code_next = nop;state_next = Refresh_NOP1;endRefresh_NOP1:beginop_code_next = nop;state_next = Refresh_NOP2;endRefresh_NOP2:beginop_code_next = nop;state_next = Refresh_NOP3;endRefresh_NOP3:beginop_code_next = nop;state_next = Refresh_NOP4;endRefresh_NOP4:beginop_code_next = nop;state_next = Idle;end// Activate sequenceActivate:beginaddress_next = Addr_In;op_code_next = nop;state_next = Activate_NOP;endActivate_NOP:beginif(RW) // Writebeginop_code_next = write;mem_io_next = Data_In;state_next = Write;endelse // Readbeginop_code_next = read;state_next = Read;endend// Write sequenceWrite:beginop_code_next = nop;state_next = Write_NOP1;endWrite_NOP1:beginop_code_next = precharge;state_next = Precharge;end// Read sequenceRead:beginop_code_next = nop;state_next = Read_NOP1;endRead_NOP1:beginop_code_next = nop;data_out_next = Mem_IO; // ????? mem_io ?state_next = Read_NOP2;endRead_NOP2:beginop_code_next = precharge;data_ready_next = 1;state_next = Precharge;end// Precharge sequencePrecharge:beginop_code_next = nop;state_next = Precharge_NOP;endPrecharge_NOP:beginop_code_next = nop;state_next = Idle;enddefault:state_next = Idle;endcaseend// Connectionsassign Mem_IO = (RW) ? mem_io : 6'bz;assign DQMH = 0;assign DQML = 0;assign Data_Out = data_out;assign CS = op_code;assign RAS = op_code;assign CAS = op_code;assign WE = op_code;assign CKE = 1; // Clock enable. Might need a non constant value for start up.assign BA0 = ba0;assign BA1 = ba1;assign A9 = a9;assign A10 = a10;// Selects which banks to activate. Since I'm always using the same banks, IE BA0/BA1 constant, it shouldn't matter.assign Addr_Out = address;assign Data_Ready = data_ready;assign Ready = ready;endmoduleI know this is a long shot but I thought I would try.Thank you!

author: Scrts
author_rank: Frequent Contributor
timestamp: June 29, 2014, 08:57:51 pm
content: I think there's Altera memory core for that. Why not using it?

author: hamster_nz
author_rank: Super Contributor
timestamp: June 29, 2014, 09:30:37 pm
content: If you want to re-implement my SDRAM controller in Verilog to work on the DE0-Nano I'm more than happy to give you any support you need. You can find it athttp://hamsterworks.co.nz/mediawiki/index.php/Simple_SDRAM_Controller.The conversion should be pretty simple. It is all in one souce file, and my VHDL-fu is poor, with only using some special attributes to lock some registers into the I/O blocks.

author: miguelvp
author_rank: Super Contributor
timestamp: June 29, 2014, 10:17:23 pm
content: Quote from: hamster_nz on June 29, 2014, 09:30:37 pmYour link has an extra period I fixed it in this reply.I could use that for my current project since I'm going to need a buffer for capturing the video data.https://www.eevblog.com/forum/beginners/converting-15khz-analog-rgb-to-digital/msg459236/#msg459236

author: marshallh
author_rank: Unknown
timestamp: June 29, 2014, 10:34:59 pm
content: Please post signaltap captures of your init sequence, and sample r/w/refresh sequences

author: marshallh
author_rank: Unknown
timestamp: June 29, 2014, 10:38:11 pm
content: By the way you can use a shorter notation fot the parameters/localparams:Code:localparam [7:0] POSSIBLE_STATE = 'h1,ANOTHER_STATE = 'h2,LAST_STATE = 'hFF;Assigns the constants to the assumed bitwidth (here [7:0])You can also put the output registers right in the module definitionCode:output reg [15:0] data_out,

author: pigtwo
author_rank: Regular Contributor
timestamp: June 30, 2014, 12:11:19 am
content: @Scrts I'm mostly doing it as an educational exercise. That's probably what I'll do if I can't get it to work.@hamster_nz Your website is actually the one resource I found that I referenced in the OP. I just don't know VHDL so I have trouble reading it. Your website was really helpful, it cleared up quite a few things for me previously. Thank you!If I could just ask you a couple of basic things about it that could really help. If you don't remember, it's not a big deal.I don't completely understand how DQM works. In the datasheet it states that it should act like output enable but in all the diagrams its either 'don't care' or zero. What did you do with it?The datasheet says to put clock enable low during the initialization process but this seems weird to me. What did you do with CKE during the initialization process?Quote from: marshallh on June 29, 2014, 10:34:59 pmThis. That would be awesome!Quote from: marshallh on June 29, 2014, 10:38:11 pmThank you, that makes that a lot less annoying.

author: marshallh
author_rank: Unknown
timestamp: June 30, 2014, 02:23:31 am
content: DQM is an active-high mask signal. It is sampled in the same cycle as the data being written.If DQM is 1 then the bits of its byte group will not be overwritten. The old contents in the dram array will be kept.Since you have a DQM for each 8 bits, on a 16bit wide ram you'll have an UpperDQM and LowerDQM, for DQ[15:8], and DQ[7:0] respectively.If you don't ever think you'll be writing less than 16 bit granularity, just tie them low.CKE is only used in power management and is also required during INIT, but only for DDR and onwards.For vanillla sdram you can just tie it high. In DDR1+ you need to run a few thousand clocks throgh the device before you assert CKE so the onboard DLL's can lock to it properly.

author: pigtwo
author_rank: Regular Contributor
timestamp: June 30, 2014, 02:40:58 am
content: Thank you marshallh! That really narrows down my trouble shooting.I've only done basic testing on my design. I don't know if this is a really obvious problem or not so I thought I might mention it to see if anyone has an idea.The problem is that whatever the last thing that was written will be the value that is read no matter what. So if you write to address 0 the value of 2 then write to address 1 the value of 5. Then read address 0, you would get 5, or if you read any other address you would get 5.My first thought was that I wasn't getting the address to the memory correctly and so all operation would happen on the same memory location but I couldn't seem to find any problems.

author: marshallh
author_rank: Unknown
timestamp: June 30, 2014, 03:19:04 am
content: If you are reading the last value on the bus, it means the RAM is never driving the bus. So, your init sequence crashed the ram, or you're issuign the wrong command.On a empty dram you should read cosmic noise, it'll be an odd pattern of data that converges to either 0 or 1 the longer you have it turned on

author: pigtwo
author_rank: Regular Contributor
timestamp: June 30, 2014, 03:25:55 am
content: Ok, that makes sense. I was very skeptical of my initialization sequence. I'll have to look into it.

author: hamster_nz
author_rank: Super Contributor
timestamp: June 30, 2014, 10:28:38 am
content: Quote from: pigtwo on June 30, 2014, 03:25:55 amYeah, it looks a little off. I've got it running in a test bench, and it doesn't look like it does what is required. From the datasheet:The 256Mb SDRAM is initialized after the power is applied to VDD and VDDQ (simultaneously) and the clock is stable with DQM High and CKE HighA 200µs delay is required prior to issuing any command other than a COMMAND INHIBIT or a NOP. The COMMAND INHIBIT or NOP may be applied during the 200us period and should continue at least through the end of the period.With at least one COMMAND INHIBIT or NOP command having been applied, a PRECHARGE command should be applied once the 200µs delay has been satisfied. All banks must be precharged. This will leave all banks in an idle state after which at least eight AUTO REFRESH cycles must be performed. After the AUTO REFRESH cycles are complete, the SDRAM is then ready for mode register programming.My test bench holds RESET for a while, releases it then send a read every now and then (when the READY has been asserted for 19 cycles). The read sequence looks OK, with an ACTIVATE, READ and PRECHARGE sequence.I can't see the PRECHARGE and there is only one of the eight required AUTO REFRESH cycles.The initialization sequence is very important. You have to assume that the SDRAM is in a random state - after all, somebody could have hit the reset button during the middle of transaction. The reset sequence is the only guaranteed way to get all the internal registers into a known state.The way you have spit the address bus has me confused, with breaking out A9 and A10, so I haven't verified that the mode register is being set correctly.Oh, and if you loop 'ready' back into 'execute' nothing gets done. Not sure if that is a design feature or not...

author: pigtwo
author_rank: Regular Contributor
timestamp: June 30, 2014, 03:18:39 pm
content: Thank you, that helps a lot. For some reason when I first learned verilog/using FPGA I skipped the test bench part, which was a mistake. I tried to get a precharge and 10 refresh cycles in but I guess I messed up the loops. But I didn't wait for 200 microseconds though so that needs to be added.I'm at work right now so I can't test anything atm but when I get home I'll try to set up a test bench of my own and fix those problems.QuoteCould you explain more, I'm not certain what you mean by this.

author: miguelvp
author_rank: Super Contributor
timestamp: July 01, 2014, 01:56:58 am
content: http://www.altera.com/products/ip/altera/ocore_sdr_sdram.htmlIt's a single data rate synchronous dynamic random access memory controller.Features:Burst lengths of 1, 2, 4, or 8 data wordsCAS latency of 2 or 3 clock cycles16-bit programmable refresh counter used for automatic refresh2-chip selects for SDRAM devicesSupports the NOP, READA, WRITEA, AUTO_REFRESH, PRECHARGE, ACTIVATE, BURST_STOP, and LOAD_MR commandsSupport for full-page mode operationData mask line for write operationsPLL to increase system performanceSupport for data-path widths of 16, 32, and 64 bitsAvailable both in Verilog and VHDL, the simulation seems to be for the micron MT48LC8M16A2, but other than the ADDR is 12 bits instead of 13 bits on the Nano the rest is the same it seems.Also hamster's VHDL is for the MT48LC4M16 and it uses 13 bits for the ADDR (but I think it should be 12 bits).Edit: never mind I thought it was for the papilio pro SDRAM (64Mbits) but reading further on it mentions 256Mbits)I'm going to use the VHDL version from that altera code for both the IS42s16160B and the MT41J64M16JT-15E on the BeMicro CV board.

author: hamster_nz
author_rank: Super Contributor
timestamp: July 01, 2014, 03:05:49 am
content: Quote from: pigtwo on June 30, 2014, 03:18:39 pmI was trying to see how back-to-back access looked, so I did something along the lines ofAn async assigment so that execute would be asserted whenever your controller asserted "ready"execute <= ready;and then in a clocked processif ready = '1' thenrw <= not rw;end if;It was attempting to send alternating read/writes as fast as the controller could support it. Nothing happend.

author: miguelvp
author_rank: Super Contributor
timestamp: July 01, 2014, 06:56:15 pm
content: @hamsterYou should have linked this page instead:http://hamsterworks.co.nz/mediawiki/index.php/SDRAM_Memory_ControllerI'm so using it.

author: pigtwo
author_rank: Regular Contributor
timestamp: July 01, 2014, 07:05:16 pm
content: @hamster That's definitely a bug there's probably a lot more. What program do you use to make test benches?