Using SDRAM in FPGA Designs

sdram Nov 21, 2019

Because synchronous dynamic RAM (SDRAM) has complex timing and signalling requirements, a memory controller is necessary to avoid having to deal with the nitty-gritty details when reading or writing. Its job is to hide the complexity of things like row and column addressing, precharging, and refreshing. Instead it lets us treat SDRAM just like plain old static memory.

There are many open-source and proprietary SDRAM controller IPs available, and after having written one myself, I can definitely see the appeal in using something off-the-shelf. But where is the fun in that?

I actually started out by looking at open-source SDRAM controllers from other projects, but to be honest I could barely understand how most of them worked. This is probably because IP authors tend to optimise their implementations quite heavily, depending on the intended use case, rather than opting to write a more general purpose IP.

The IPs I did manage to try either didn't work properly, had missing bits of functionality, or had interfaces that were too complex to be suitable. That was when I decided to roll up my sleeves and write my own memory controller. In retrospect, it wasn't a decision to be taken lightly – it took many iterations before I finally had something I was happy with.

If you plan on going down this path, the best advice I can give is that you know what you're getting yourself into. Print out the datasheet for your SDRAM chip and read it, including all the timing values and footnotes. Then pick up a highlighter and read it a few more times, marking the important details as you come across them.

To learn more about different memory types, I also recommend you track down a copy of Logic and Computer Design Fundamentals. This book covers many aspects of computer hardware design, including a detailed description of DRAM. The more familiar you are with these concepts, the easier it will be to implement your own memory controller.

How SDRAM Works

These days DRAM – and in particular, SDRAM – is in everything. From servers to laptops, phones, watches, cars, consoles, and TVs. Basically, anything with a processor is very likely to also contain one or more SDRAM chips.

Given that there are so many different types of memory available, why has SDRAM been the defacto choice over the past 25 years, for such a wide range of electronic devices? While there are probably good technical reasons for this, the real answer is invariably cost: it provides large storage capacity at low cost.

As always, these benefits come with their own set of trade-offs. Because SDRAM chips have a large storage capacity when compared to other memory types, handling all that capacity becomes necessarily more complicated. The memory must be divided up into banks, rows, and columns in order to reduce the number of physical pins on the chip that are required for addressing.

To read or write a value to a particular memory location, we need to specify a bank, row, and column address. To complicate matters even further, we can’t specify the bank, row, and column addresses at the same time – there just aren’t enough pins on the chip. First, we must activate (open) the bank and row, followed by a read/write at particular column address. Once we have finished reading/writing to a row, we need to precharge (close) the current row before we can activate a different one.

All this means that we need some kind of stateful behaviour when reading and writing to the SDRAM (i.e. a finite state machine). This is much more complicated than other memory types that allow you to reference a memory location without having to break it up into banks, rows, and columns.

The state diagram below should give you some idea of the complexity of a common SDRAM chip:

SDRAM State Diagram

As the name "dynamic" implies (the “D” in SDRAM), it must also be periodically refreshed to ensure the integrity of the data. This is because the individual memory cells in a SDRAM chip are like tiny leaky buckets. A bucket can either be full (representing a one), or empty (representing a zero), but because every bucket has a leak they all must be topped up occasionally. Otherwise, all the buckets would eventually be empty, and we would lose our data. This process is known as refreshing.

I’m not going to give an exhaustive description of how SDRAM works, but it's worth highlighting a few differences when compared to other memory types:

  1. It uses a bank, row, and column addressing scheme.
  2. Rows must be activated before they can be accessed, and precharged once access is no longer required.
  3. It must be periodically refreshed.
  4. Requires a state machine.
  5. Contiguous words from a row or page can be bursted when reading/writing.

A Controller to the Rescue

My SDRAM controller provides a symmetric 32-bit synchronous read/write interface for a 16Mx16-bit SDRAM chip.

Even though the SDRAM chip only has a 16-bit data bus, the controller uses a 32-bit data bus because it is more efficient to burst multiple words from the SDRAM than it is to do individual reads and writes (more on this later).

At its heart, the SDRAM controller is a fairly simple finite state machine (FSM):

  1. After it has been initialised, the controller stays in the IDLE state until a read/write request is made. It automatically moves to the REFRESH state when a refresh is required.
  2. When a request is made, the controller moves via the ACTIVE state to the READ or WRITE state.
  3. After a request has been completed, the controller either moves back to the IDLE state, moves to the ACTIVE state if a new request has been made, or moves to the REFRESH state if a refresh is required.
SDRAM Controller State Machine
SDRAM Controller State Diagram

Reading

The SDRAM controller allows read operations to be performed using a simple interface.

Read requests can be chained so that a new read operation can be requested before the current operation has completed. Using this strategy, we can read any number of words from the SDRAM without wasting clock cycles, thus using the maximum available bandwidth of the SDRAM.

The following example describes how to read two 32-bit words using the SDRAM controller. This method can be used to read any number of words:

  1. Write the address to the addr bus.
  2. Request a read operation by deasserting the we signal and asserting the req signal.
  3. Wait for the ack signal to be asserted. This means that the read request has been acknowledged, and a read operation has begun.
  4. Write another address to the addr bus.
  5. Wait for the valid signal to be asserted. This means that the first read request has been completed and the data is available on the data bus.
  6. Read the value on the q data bus.
  7. Wait for ack signal to be asserted. This means that the second read request has been acknowledged, and a read operation has begun.
  8. Deassert the req signal when we’re done making requests.
  9. Wait for the valid signal to be asserted. This means that the second read request has been completed and the data is available on the data bus.
  10. Read the value on the q data bus.
SDRAM Controller Read Operation
SDRAM Controller Read Operation

The number of clock cycles between the ack and valid signals being asserted is determined by the CAS latency of the SDRAM. Clocking the SDRAM at faster speeds may require a longer CAS latency, which would affect this timing.

Writing

The SDRAM controller handles write operations similarly to read operations.

Write requests can also be chained, so that a new write operation can be requested before the current operation has completed.

The following example describes how to write two 32-bit words using the SDRAM controller. This method can be used to write any number of words:

  1. Write an address to the addr bus and a value to the data bus.
  2. Request a write operation by asserting the we and req signals.
  3. Wait for the ack signal to be asserted. This means that the write request has been acknowledged, and a write operation has begun.
  4. Write another address to the addr bus and a value to the data bus.
  5. Wait for ack signal to be asserted. This means that the second write request has been acknowledged, and a write operation has begun.
  6. Deassert the we and req signals when we’re done making requests.
SDRAM Controller Write Operation
SDRAM Controller Write Operation

Bursting

When reading or writing to contiguous memory locations in the SDRAM, it is much more efficient to burst multiple 16-bit words than it is to transfer one 16-bit word at a time. This is because wasted clock cycles can be avoided if the SDRAM controller doesn’t have to precharge active rows after each word is transferred.

SDRAM chips typically allow bursting 2, 4, or 8 contiguous words. In our case, for every read or write operation the controller will burst two 16-bit words and concatenate them to provide a 32-bit interface.

Fortunately, this process is handled by the controller and is completely transparent to the end-user.

Refreshing

SDRAM must be periodically refreshed to ensure the integrity of the stored data. Thankfully, the controller handles refreshing the SDRAM automatically, so you don't need to worry too much about this.

If a refresh is required while a read or write operation is pending, the controller will wait until the end of the current operation before performing a refresh operation. If the controller is in the IDLE state, then a refresh operation will be performed immediately.

Clocking

The time taken for a signal to propagate from the SDRAM to the FPGA is determined by the timing characteristics of the SDRAM chip, and in some cases, the physical distance that the electrical signals have to travel.

If the overall delay is too great, the setup and hold time requirements of the capture circuit could be violated. At best, your static timing analysis (STA) would fail, and at worst, you may have unreliable behaviour (which is very hard to debug).

The time between a rising clock edge and data being available on the SDRAM data bus is called the access time. The length of time that the SDRAM will hold a value stable on the data bus is called the output hold time. According to the datasheet for the SDRAM chip, the access time tAC is 6ns, while the output hold time tOH is 2.5ns.

To compensate for these delays – and avoid timing violations – we can phase shift the SDRAM clock with respect to the system clock. By adjusting the phase relationship so that the SDRAM clock leads the system clock, we can ensure that the SDRAM output signals arrive in time to meet our timing constraints.

This can be done by configuring a PLL with two clock outputs that have the same frequency, but different phases. Choosing the correct value for the phase shift can be easily determined through trial and error, although I am interested to know if there is a more scientific method for calculating it.

SDRAM Launch and Capture Timing Analysis
SDRAM Launch and Capture Timing Analysis

For my SDRAM controller, I configured the PLL so that the SDRAM clock leads the system clock by 90 degrees; this equates to around 5.2ns for a 48MHz clock.

If we assume that the SDRAM access time plus propagation delay – known as the input delay – is 6.5ns, then the SDRAM output signals will arrive about 1.3ns after a rising system clock edge. This is much better placed to meet the timing constraints of the capture circuit.

This relationship between launch and capture clock edges is illustrated in the following diagram:

SDRAM Launch and Capture
SDRAM Launch and Capture

A value is presented on the SDRAM data bus tAC nanoseconds after the first SDRAM_CLK clock edge (1). The value remains stable on the data bus until tOH nanoseconds after the second SDRAM_CLK edge (2). The data is captured at the second SYS_CLK edge (4).

Because the system and SDRAM clocks are now out of phase, we need to define a multicycle path constraint so that the correct launch and capture edges are used during STA:

set_multicycle_path -setup -end \
  -rise_from [get_clocks {SDRAM_CLK}] \
  -rise_to [get_clocks {emu|pll|pll_inst|altera_pll_i|general[0].gpll~PLL_OUTPUT_COUNTER|divclk}] 2

Trying It Out

My SDRAM controller IP is available on GitHub. Hopefully you either find it useful in your next project, or are able to learn about how SDRAM works by reading the source code. Enjoy!

Josh Bassett

Hello. I'm a software developer living in Byron Bay, Australia.