DRAM

Fall 2023

You are not logged in.

Please Log In for full access to the web site.
Note that this link will take you to an external site (https://shimmer.mit.edu) to authenticate, and then you will be redirected back to this page.

Hi everyone!

Two groups that I'm advising for final projects wanted to use the 128 MiB of DDR2 (Dual Data Rate, i.e. double-pumped, i.e. runs on a positive and negative edge of its input clock; Version 2) DRAM (Dynamic Random Access Memory) located off-chip on our FPGAs.

Disclaimer: this is a pain in the neck, and unless I approved this explicitly during your presentation you definitely shouldn't make this part of your project However, in case folks are curious, or in case you four approved DRAM-users want to know where to start, I've written up a sample DRAM controller for you to study and use as needed, and gathered a few resources you might find helpful.

========================

What's DRAM?

https://en.wikipedia.org/wiki/Dynamic_random-access_memory

How do I write a DRAM controller?

You don't because that would take you a year. Instead, you use one Xilinx wrote. It's called MIG, or the memory interface generator. The interface to MIG is actually pretty simple - it's https://docs.xilinx.com/v/u/1.4-English/ug586_7Series_MIS pages 105 to 110.

Great, but how do I instantiate MIG?

Find it in the IP catalog in the Vivado GUI, under "Memory Interface Generator". There are a ton of options in this IP customization dialog, and you really don't care about a lot of them.

Quick primer before we get into the step by step: for MIG designs, you actually have three clocks to work with.

  • Input clock: the clock the rest of your design besides the memory interface runs at. This is the clock you're used to working with - and if you don't set up a clock divider or anything you'll probably have this at 100 MHz.

  • DRAM controller clock: this is the clock the memory runs off of. We're going to set this to 325 MHz, because 325 MHz is really really fast but also can be divided cleanly from input clocks of 100 MHz to 200 MHz. You won't interact with this clock at all, because it's self contained within MIG. However, this clock will be divided to obtain the...

  • UI clock: the clock that you use to interact with the MIG interface. Depending on your design choices, this is either 4 or 2 times slower than the DRAM clock (in professional parlance ,this is a 4:1 or 2:1 PHY (i.e. physical memory layer) to controller (i.e. the MIG interface) ratio). We will be using a 4:1 PHY to controller clock ratio since the controller does not support being run at the 2:1 ratio under our 325 MHz controller clock, which is really pushing our data rate to the limit. This means the UI clock will be 81.25 MHz by the time we're done.

What this all means is that the MIG takes in your system clock, which most of your design runs at, generates and exports the controller clock to the hardware, and generates + outputs the UI clock from the controller clock. You then use the UI clock to interface with the MIG controller, crossing between this clock domain and your system clock domain as necessary to ferry data in and out of DRAM.

You might also have this system clock at 200 MHz, which I usually recommend for the sorts of high performance lower-latency designs that rely on the extra storage DRAM provides. Then you can always cross domains as needed to get to the lower clocks used with peripherals such as 100Mbit ethernet and VGA, without sacrificing throughput. Just be careful - a 200 MHz clock imposes tighter latency-sensitive timing requirements on your design, but in exchange allows you to reach higher throughput and better effective performance (depending on the dynamics of your pipeline).

With all this out of the way, let's customize our MIG:

  • Memory type: DDR2 SDRAM

  • Clock Period: 3077 ps (-> 324.99 MHz, close enough)

  • PHY to Controller clock ratio: 4:1

  • Memory part: MT47H64M16HR-26E (this is the chipset onboard your Nexys DDR, I checked)

  • Data width: 16 ("but jay, I want to have 32-bit words in my memory!" don't worry, we'll get to this in a sec, stick with me)

  • Data mask: yes

  • Number of bank machines: 4

  • Ordering: strict

Click next...

Leave the rest as default and click next...

  • System clock: no buffer (use the existing input clock without any modifications as the input to MIG's internal dividers. there's fancier stuff you can do here, ignore it.)

  • Reference clock: use system clock

  • Debug: disabled, heck no

  • Internal Vref: enabled, frees pins so why not

  • Power reduction: enabled, why not

  • XADC: must be enabled

Next again...

  • Termination impedance: 50 Ohms

Now to tell MIG how the DRAM on our FPGA is laid out.

  • Fixed pin-out, then click next

  • Download this thing from Digilent and decompress it. This contains a .ucf file describing the DRAM interface of your FPGA to Vivado. Think of it like an XDC file for your DRAM signals - because we have this, we can stick DRAM signals on the output of our top level and have them routed through to the right pins of the DRAM controller.

  • Once you've glanced at the UCF file because it's fun to look at, read it into the MIG GUI and click "validate". Everything should just work, and then you can click next.

Skip the systems signals page, decline the simulation agreement (for the love of all that is holy please don't try and simulate this thing, it's such a pain, just use the reference like you would any other peripheral and do manual testing), and press go.

That's it! Instantiate this IP in your design, and make the output/inout signals from the MIG interface outputs/inouts from your top level as you would any other external-facing signal. Now you can use the app_* input signals, clock on the ui_clk outputted from the MIG, and get stuff done.

This is good but I like to see examples.

Me too. So I made an example module that utilizes your DRAM. It's in our GitHub, and employs a somewhat maybe parametrizable 512 bit synchronous read/write interface.

I can hear your confusion already. "512 bits!!! What's that about! Wasn't our data width 16!"

It was, you're right. But DRAM interfaces are by nature very bursty to increase throughput. Specifically, our DRAM comes in a BL8 configuration (burst length 8) which means we write a whopping 128 bits to the memory at once! Basically this means that we've effectively got a memory with width 128, and that reflects in the interface MIG gives you. Write bursts can also be performed in very rapid succession, sometimes commands can be run back to back / get batched into one request to the memory, and stores are buffered / non-blocking (for your purposes, they're instantaneous provided app_wdf_rdy is high - see the Xilinx doc for more details), which is a useful performance boost...

Let me know if you have any questions about this! I wrote this document up very quickly (in like under 30 minutes) so I don't expect it to be perfect. If your team requires the use of DRAM, I'm happy to meet more with you to discuss this interface further, or provide a full test harness design that allows you to make sure your DRAM module works as expected.

Sample DRAM module (note there's an ILA in there, comment that out if you don't want it)

dram.sv