FPGA VHDL SDRAM Controller

Introduction

For a long time I hesitated engaging the idea of writing an SDRAM controller. I think my reluctance was due to the stigma that SDRAM controllers are extremely hard and complicated, and I always wanted something quick and simple. Once I finally committed to designing one, and after looking at two other more simplistic controllers, it was not really that difficult and I managed to get a working design in a day or so.

Modern SDRAM, DDR, DDR2, DDR3, etc. are designed for modern computer systems that will have a memory controller of some kind. The memory controller will accept memory requests from the CPU, analyze the requests, rearrange them, queue them up, and dispatch them to the SDRAM in the most efficient manner. While this is fine for a modern computer, a memory controller like that is very complicated for someone simply trying to use an SDRAM with their simpler FPGA project.

Since SDRAM is cheap ($3.50 for 32MB at the time of writing) most FPGA development boards come with some sort of SDRAM instead of more expensive (but faster and much easier to use) SRAM. In my case I needed more RAM than the FPGA’s block RAM capacity, and my board did not have any SRAM. I wanted a simple controller that would allow constant access time, like SRAM, and random 8-bit access. Let’s face it, not every design needs 32-bit access or involves streaming massive amounts of data. If you have those kinds of requirements, then there are already plenty of other SDRAM/DDR controllers out there for that kind of memory access.

My projects are more 8-bit or 16-bit oriented systems with completely random access patterns. This kind of memory access is a nightmare for SDRAM, even the modern DDR2 and DDR3. SDRAM achieves its high bandwidth by transferring multi-byte data from consecutive addresses. That is all fine and well if you need 64-bit (or wider) data, or are processing chunks of consecutive memory. But when all you need it a single 8-bit byte, and the next memory access is another 8-bit byte somewhere else in memory, you can never take advantage of SDRAM bursting. You will always have the worst case access time (about 70ns in my case, based on the SDRAM I was using.)

 

Design Goals

SDRAM: Winbond W9825G6JH 4M x 4 Banks x 16-bit SDRAM

The controller I designed needed to satisfy these goals:

1. Have a constant access time.
2. A refresh cycle cannot hold up a read or write request.
3. Have 8-bit addressable reads and writes.

To satisfy the first requirement I determined from datasheet specifications that any read, write, or refresh cycle could be performed in 70ns. This is about 14.2MiB/sec and can easily keep up with an 8-bit or 16-bit CPU running at 10MHz or so. Most of my designs use CPUs running much lower, with 1MHz to 4MHz being typical. This leaves a lot of time to multiplex access to the RAM between other parts of the circuit like video generation, ROM, etc.

A refresh cycle needs to be issued at least once every 7.2us to keep the SDRAM from losing its contents. To keep the refresh cycles from interfering with normal reads or writes, the host circuit is responsible for controlling and issuing refresh cycles. This is easily achieved since the SDRAM is typically much faster than the host CPU. For example, a 1MHz CPU will only make one request every 1us. So after each CPU cycle where a memory access is performed, a refresh cycle can be issued.  One refresh every 1us or so is well under the minimum requirement of one refresh every 7.2us.

The 8-bit access is facilitated by the SDRAM having upper and lower byte enables. The LSbit of the address is used to control the UB and LB enables for writes, and controls a byte-select mux for reads. Since the SDRAM has 16-bit words, the memory can support 8-bit and 16-bit host CPUs directly.

 

SDRAM Terminology and Basic Operation

Datasheets are always written with the assumption that you know what they are talking about, and just about all the information I could find on SDRAM operation also assumed some previous knowledge about how SDRAM works. When you are trying to learn, this can be frustrating to say the least.  Below is some information that I could never find explained very well.

SDRAM stores data in banks, and most modern SDRAMs have multiple banks (the SDRAM I’m using has four banks.) Each bank is configured as x-rows of y-bits (columns). The number of banks, rows, and bits-per-row (columns) determines the SDRAM’s density.

Unlike SRAM and other memory ICs, SDRAM is a “command based” memory similar to SPI-flash, SD cards, etc. To read or write data you have to issue commands to the memory, along with the address and data (if writing.) Due to the way SDRAM stores data (memory cells use a capacitor to store a bit of data as a charge), reading data from a memory cell destroys the value. Thus, after reading any data, the value has to be written back to the memory cell to restore the data.

To facilitate the restoration of data during normal memory operations, the SDRAM has what are called “sense amplifiers”. When memory is addressed, the requested data is destructively read from the memory bank to the sense amplifiers. When the read operation is done, the sense amplifiers restore the data by writing back to the original memory cells.  If the memory operation was a write, then the data in the sense amplifiers is replaced with the new values, which are then stored in the memory cells.

To move data from a memory bank to the sense amps for reading or writing is called “activation”. Once a bank is active you can issue read and write commands on the retrieved data.

When you are done reading and writing, you must close the bank with a “precharge” command.  This will write the data back to memory and prepare the sense amps to receive data for the next activate command. This can be confusing because a precharge generally happens *after* the activation and read / write operations.

It is this concept of “precharge” and “activate” that was the most confusing for me, since most documentation assumes you already understand this process.

Basically, after the SDRAM is initialized the normal command flow would be:

Activate -> Read / Write -> Precharge

Since all banks are precharged during initialization, they are ready for activation. The precharge at the end of the sequence commits the data back to the memory cells and prepares the sense amps for another activate.

The amount of time a bank can remain active is limited, and also depends on the memory address. You can also have multiple banks active at once which allows greater flexibility and data throughput, however this kind of bank manipulation is better left to an intelligent memory controller and is beyond what I did with my controller.

There is a minimum amount of time from the activate command to when the data is ready to be read or written. There is also a minimum amount of time between activate to precharge, and then from precharge to another activate.

The cumulative time for the SDRAM I am using is 70ns from activate to activate, which becomes the minimum and fixed access time for my controller. A refresh cycle is just like a read or write cycle, except the refresh command is issued instead of a read or write command, and takes the same 70ns:

Activate -> Refresh -> Precharge

The SDRAM takes care of determining what row to refresh during a refresh cycles, so all you have to worry about is issuing a refresh command at least once every 7.2us.

The amount of data transferred from a bank to the sense amps is limited. To access data outside of the current activated bank requires the bank to be precharged (written back to memory), and a new address specified followed by an activate command.

 

Theory of Operation

After power-up and SDRAM needs to be initialized before you can start reading or writing data. All SDRAMs will have specific requirements, but they are probably all very similar:

1. Wait 200us with DQM signals high and NOP command issued.
2. Precharge all banks.
3. Issue eight refresh cycles.
4. Set mode register.
5. Issue eight refresh cycles.

The mode register specifies how many words of data will be accessed during a read or write operation, among other options. This is also known as the “burst size” and in the SDRAM I am using it can be 1, 2, 4, or 8 16-bit words. Because I’m keeping things simple my burst size is 1.

The controller’s host interface is pretty simple:

 

The clock input must be 100MHz. After reset the host should wait for ready_o to go high, at which point the SDRAM is ready for use.

NOTE: Since SDRAM samples on the rising edge of the clock and most HDL is written to do register-transfer on the rising edge of the clock, this controller does its register transfer on the falling edge of the clock. Therefore the interaction between the rw_i or refresh_i input and the done_o output is 5ns. If you need more time, i.e. a full 10ns to change state, then the external SDRAM should be driven by a clock that is 180-degrees from the clk_100m0_i input and the design changes to use the rising edge.

I changed my design to use the rising edge of the clock.  There were some later issues with using the falling edge, and all the strange issues were resolved by using the rising edge and buffering the output data.  Your use may vary depending on how your SDRAM clock is wired to your FPGA.

 

 

Full Source:

 

Simulation Timing:

Read Cycle, 70ns:

SDRAM Read Cycle

 

Write Cycle, 70ns

SDRAM Write Cycle

 

Refresh Cycle, 70ns:

SDRAM Refresh Cycle

Here is the simulation testbed HDL I used. This design has also been synthesized and proven in hardware as the main RAM and ROM in an 8-bit CPU SoC.

33 comments to FPGA VHDL SDRAM Controller

  • peter

    Hi, I’ve just been looking at your memory controller. I have to say I’ve never seen a state machine implemented in the manner you have done it. Not having the clock in the sensitivity list is particularly unusually. I take it that you have successfully implemented this code in hardware and haven’t had any issues with it? Peter

    • matthew

      The clock is in the sensitivity list of the FSM process:

      The other process block is the combinatorial next-state logic and is not dependent on the clock, only the current state and the timer_r register.

      Yes, it has been proven in hardware.

  • mahdi

    Please send for me code of test bench of sdram controller
    THX

  • Alex

    Wow. This is brilliant!

    I’ve been playing around with FPGA’s for about a month now with an attempt to handle a speed problem that I’ve encountered with the use of Microcontrollers. I decided the next step was probably to put some effort into FPGA’s

    So I’ve made a few small things here and there within the month, but by far the most complex has only been a 24Bit TFT LCD Controller. To really display anything useful though I need to have a large enough display buffer and my next step was to take on the arduous task of creating an SDRAM controller.

    I started reading up a bit on these things in an attempt to understand it so I could build one. After seeing an SDRAM controller in VHDL by Altera, being a newbie at FPGA’s and VHDL was really discouraging, but your code makes it look so simple. And I hope my eyes aren’t fooling me but I think I even understand most of it.

    So I’ve decided to take a shot at implementing your controller into my design. A few things I don’t understand though, you say a refresh needs to be done every 7.2us. I’m not sure how I would handle that while also doing reads and writes.

    Also, does a refresh call mean bringing refresh_i high and bringing it low when done_o is on the falling edge? In your example it looks like you keep refresh_i high even after done_o is on the falling edge. If that’s the case, a refresh cycle takes about 70ns or 7 clock cycles, correct? Do I just forcefully only handle reads and writes during the 720 clock cycles that I’m not doing a refresh, or can I start a read/write prior to a refresh cycle, and resume once the refresh is complete?

    In some of the datasheets I’ve read, it seems that SDRAM shines when doing burst read/write where I can read or write up to a full page with one read/write command. This code only seems to be able to do single word read/write. Is there any way to burst read/write with this code?

    Do you have any example code of this in use?

    Sorry for the barrage of questions. I think I’m just overjoyed that a simple implementation is out there so I can get working with this sooner than later.

    And if I haven’t mentioned how grateful I am for this, I’d really like to thank you for putting this great learning tool out there. (Learning tool in that it is simple enough for a newbie to understand and feel good about the fact that he gets it)

    • matthew

      Alex,

      SDRAM does work better with an intelligent controller that keeps track of reads and writes, can open multiple banks at once, and pull data back in busts. All of that is necessary to hide the access latency of SDRAM when used in modern computers. Such an SDRAM controller is extremely complex and are actually much easier to find. However, such controllers are also a hindrance when you need deterministic access to memory and you don’t need bursts of consecutive data.

      This controller was designed for my needs which are truly random single byte access. I like messing with old computers (8-bit and 16-bit systems from the early 80’s) and I needed an SDRAM controller that would let me use this cheap memory in such a system.

      You handle refresh cycles by working them into your normal access pattern. In my case I’m using 1MHz to 3MHz CPUs and the memory is much faster than the system’s memory cycle, which has a 1us clock period in the case of a 1MHz CPU. The CPU only accesses memory during the high clock period and latches the data on the falling edge of the clock (this is my situation and will highly depend on your CPU). The next 500ns are available to the system for dedicated circuits to access the memory. My memory FSM performs a single refresh cycle during the period, thus always providing more than enough refresh cycles to the SDRAM and not interfering with the CPU’s memory accesses.

      No, you cannot interrupt a refresh cycle with a read or write.

  • Ricardo

    Hi Matthew,

    I am a fan of old computes and new to FPGAs.

    After playing around doing some simple projects with my development board (a Papilio Pro with a Xilinx Spartan-6) I have started to build a Sinclair ZX Spectrum clone (a 8 bit computer made in UK in the 80s, with a Zilog Z-80A CPU at 3.5 MHz). Now I have a full working clone using the internal BRAM but I need more RAM to add some features.

    I am trying to use your controller to access the SDRAM that comes with the development board, a 4 banks x 1MB x 16bits Micron MT48LC4M16A2 chip, like if it was SRAM.

    I am also using a test ROM that verifies the ‘simulated’ RAM is working as expected. It does four test. After some test and trial, I have managed to pass the first two tests but I have not been able to pass the other two.

    Can you help me with this? Do you have some example code? I am happy to send you my code if you want to check it.

    Please excuse my poor English, it is not my native language.

    Thank you.
    Ricardo.

  • Andy

    Sir, I am not able to get the outputs. Can you tell me the value of input and other parameters to be set in the test bench for Read cycle, Write cycle and Refresh cycle.

    • matthew

      The SDRAM initializes for a very long time (200us), so you are probably not letting the simulation run long enough for the controller to come out of reset. If you look at lines 192, 206, and 226 above, you will see some parameters that you should use during simulation.

      Change this:

      To this:

      Do that for all three lines (note the timer values are not all the same.) Then run the simulation for 1us and look at the wave forms starting around 440ns. If you use the controller in a real FPGA with SDRAM, be sure to change those lines back to their non-simulation values.

  • Alexey K

    Thank you very much for your job! For me it is very important to have simple controller which I can understand and tweak in ways I need.
    I’ve adapted this controller for IS42S16400J SDRAM chip which is installed on Terasic DE0 board (Cyclone III FPGA).

    • matthew

      I’m glad you got it working, thanks for the feedback! I have made some tweaks myself after further testing. Primarily I changed to operate on the rising edge of the clock and buffered the output data. Updated code will be posted soon.

  • Andy

    Please send me code for test bench for all the three cycles.
    Thank you

  • boy

    Hi Matthew, your posting is informative and your code is easy to read. Anyway, I’m trying to modify your code for implementation to real device. I’m using XSA-3S1000 board by xess. If you don’t mind, may you give me Implementation Constrain File (.ucf) from your code, so that I can adapt to my XSA board ?
    Thank you in advance.

    • matthew

  • crg

    Thanks for your beautiful program. I have written some verilog programs. One problem: I’m confused with the suffixes of the signals. eg. sdCe_bo, bank_s , cmd_r, cmd_x … Is there any advice for the naming rules?
    Can you suggest some books, manuals or other materials about this. Thank you very much.

    • matthew

      The suffixes are just my own preference and how I keep track of what the signals are. My suffix naming conventions are usually like this:

      _i input
      _o output
      _n active low
      _r register
      _x register next
      _s signal only (named wire)
      _t defined type
      _G generic

      I can’t remember what the “_bo” meant, probably “buffered output” or something like that. As for books, I have an older post on that topic, but I still recommend these titles:

      FPGA Prototyping by VHDL Examples by Pong P Chu
      RTL Hardware Design Using VHDL by Pong P Chu
      Digital Design and Computer Architecture by David and Sarah Harris (either edition)

  • damien

    Hi,
    I tried to implement your code in a FPGA from Xilinx ( Spartan SP601 ) but i have some issues.
    I use ISE Design Suite 14.7 and when I launch the compiler I obtain the following errors :

    ERROR:ConstraintSystem:300 – In file: DDR2_SDRAM.ucf(4): Unable to resolve
    constraint association type “sdram_cke_net”. “sdram_cke_net” is not a valid
    constraint type for association in the “UCF” constraint format.

    ERROR:ConstraintSystem:300 – In file: DDR2_SDRAM.ucf(13): Syntax error. Ensure
    that the previous constraint specification was terminated with ‘;’.

    Theses errors appaers only for the ucf file and only for the two first paragraphs.
    So I guess I do something wrong but I don’t understand what ( I’m kind of a new to FPGA ).
    I also don’t understand why the entity sdram_simple is declared twice ( in the source code and in the ucf file)

    Thank in advance,
    Damien

    PS:sorry for bad english

    • matthew

      Hello Damien,

      The example UCF file I posted contains more than just the UCF part. The UCF file is only the “NET” statements. The top part was showing the signals and entity as I defined them in my top level.

      I hope this helps.
      Matthew

  • David

    Hi,

    Have you come across any good references that explain how the row and column addresses work in DRAM?

    If you have a 16bit wide DRAM organised as say 2M x 16 in 4 banks, why is there a row address, you select the bank, then the column address and read out the 16bits. If you introduce a row address then you are only looking at a single bit location, surely?

    Looking at your code, the row and column is mentioned in the setup ports , but only address in the later sections.

    Many thanks,
    David

    • matthew

      Hello David,

      Can’t say that I have ever looked for a reference that explains the row and column addressing in DRAM. I suspect the address was split like that for two reasons: 1. to save physical pins, and 2. to give the memory time to access the internal cells. This is just speculation on my part though.

  • Vlad

    Hi Matthew,

    first of all, thanks for the code – I was looking for something like this (and for similar purpose – old PCs) !

    Trying to play with Altera Cyclone IV and ran into strange problem – the controller stops working after 10 – 50 cycles. Even if I don’t do anything, just issue REFRESH command, at some moment it stops responding completely. I can see that the last REFRESH at least started – RAS and CAS go to LOW for one clock period, but DONE never goes HIGH at the end of this refresh cycle…

    I tried to run on slower clock, but it does not change anything…

    Any idea where to look ?

    Thanks again !

    • matthew

      Are you using the same SDRAM? You should probably start by checking the setup sequence for your specific SDRAM. Aside from that, I’m not sure what to suggest other than checking how your SDRAM is connected and making sure you are not having a clock skew problem. Timing of the SDRAM is pretty critical and I don’t know if you can just slow it down.

  • Vlad

    OK, already found the answer – the issue was with asynchronous REFRESH request signal. After registering the REFRESH request with the controller clock everything is working !

  • Hi
    In VHDL code,I don’t see the SDRAM’CLK signal?
    Could you show it for me,please!
    Thanks

  • mosio

    Hi,
    thank you for your understandable code Matthew.
    Is there any timing constraint file for your code?
    or we do not need to set any timing constraint?
    thank you…

  • Sarmad Wahab

    Hi Matthew,
    Really appreciate your work. I have knowledge about VHDL and FPGA’s. My task is to design SDRAM DDR3 Memory controller for FPGA Artix 7 and later I have to interface it with wishbone. Though I know we can generate memory controller through MIG in ISE Design Suite which I did. But after reading your code it looks not that much lengthy as MIG have. Personally I want to design my own memory controller to know how actually things works and after that I believe I will be able to interface it with wishbone. My question is reading data sheet from JEDEC or Micron for DDR3 will I be able to design memory controller?. Which steps I need to take care can you guide me in this sense. I don’t want to copy paste your code I want to do it by my self, I just want to know the first step from where shall I start.

    • matthew

      I agree, the MIG and other memory controllers look very complicated to me too. That’s one of the main reasons I wrote my own. I find that a lot of engineers and programmers like to over complicate things, I suppose to either try and look smart, or because they are writing something too generic which adds unnecessary complexity.

      I would start with something simple, like the SDRAM controller I did. I started by looking at other SDRAM controllers, reading the datasheets, learning about SDRAM operation, and finally writing my own controller. I took me more time to understand SDRAM operation than to write the HDL. I have looked at DDR/2/3 memory, and the basic interfacing should be similar.

      Really the hardest part of a memory controller is trying to make it “intelligent”, i.e. opening multiple banks, interleaving and scheduling reads and writes, etc. Since I was working on an old 8-bit CPU SoC, my access patterns were not good for the bulk kinds of data words that SDRAM or DDR can deliver.

      If you want to learn SDRAM (DDR/2/3), I suggest you start with a SDRAM controller and work up from there. If you just want to get your circuit going and need the bulk transfer features of DDR, then generate something with MIG and get on with your design.

  • Sarmad Wahab

    Thank you for reply. I have already started reading the book as mentioned above. “RTL Hardware Design VHDL” there I have revised the FSM, Arbiter. After that I will go for datasheet. Thank you once again

  • Sarmad Wahab

    Hi Matthew,
    Sorry for asking questions again and again. Can you please tell me which data sheet you read for this implementation ? Jedec or Micron with version ? . Secondly the memory controller you designed is SDR (Single Data Rate) or DDR (Double Data Rate).

    Thanks in Advance.

Leave a Reply

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">