06 · The 64 MB SDRAM
The DE10-Lite has 64 MB of SDRAM next to the FPGA: about 40 times all the FPGA's block memory together. This example drives it with a small controller written here, and tests every one of its 33,554,432 words.
The repo keeps the full project (.qar, plus the sources here) but no prebuilt bitstream: one build (a few minutes) makes the .sof on your computer.
Yes, it is DRAM
SDRAM = synchronous dynamic RAM: every bit is a tiny capacitor that leaks, so each row must be refreshed every 64 ms (the controller does it every 7.8 µs, one row at a time), and a word is reached in steps: open the row (ACTIVATE), read or write the column, close the row (PRECHARGE). The chip on the board is an IS42S16320: 4 banks × 8,192 rows × 1,024 columns × 16 bits.
Measured on the board
PASS 00 delay=2 64K words errors=00010000 clocks=00163944 ← read delay 2: every word wrong
PASS 01 delay=3 64K words errors=00000000 clocks=00173C60 ← 3 and up: all correct
PASS 02 delay=4 64K words errors=00000000 clocks=00183ED1
PASS 03 delay=5 64K words errors=00000000 clocks=001941A0
PASS 04 delay=6 64K words errors=00000000 clocks=001A4335
PASS 05 delay=7 64K words errors=00000000 clocks=001B46C4
PASS 06 delay=4 32M words errors=00000001 clocks=307DA7D4 ← the whole chip; the 1 error is the one SW0 injected
PASS 07 delay=4 32M words errors=00000001 clocks=307DA7D6
- The first six passes measure the read delay (clocks from the READ command until the data is sampled) on 64K words: 2 is too early, 3 is the first that works. The controller uses 4, one clock of margin, which is also what the simulation model predicted. (5 to 7 "work" only because the data bus keeps its last value after a read.)
- A full pass writes and reads all 33,554,432 words in 0x307DA7D4 clocks = 16.3 s at 50 MHz: about 4 million accesses a second, 8 MB/s. Simple and safe, not fast; a burst controller is 10 to 20 times faster.
- SW0 was up during this run: the design writes one wrong word per pass on purpose, and the test finds exactly that one. A full pass with SW0 down (expected: 0) is still to be run by someone at the board; the 64K passes above, which do not include the injected address, show 0. Resources: 1,664 logic elements (3 %). Timing: no negative slack.
What you will see
| Where | What |
|---|---|
| HEX5..HEX0 | Errors since start-up (with SW0 down it must stay 0) |
| LEDR9 / LEDR1 / LEDR0 | SDRAM ready / reading / writing; LEDR2 shows SW0 |
| SW0 up | Inject one wrong word per pass (proves the checker); KEY0: start over |
| Your PC | The lines above, plus progress every 4M words |
Block diagram
The controller (lib/rtl/sdram_ctrl.sv)
- Clock: 50 MHz logic; DRAM_CLK is the same clock shifted 180° by the PLL, so the SDRAM samples our signals in the
middle of their 20 ns: about 10 ns of margin both ways, no fragile I/O timing analysis needed. The pins use the I/O cells'
own registers (
FAST_OUTPUT_REGISTER/FAST_INPUT_REGISTERinsrc/extra.qsf) so their timing is the same in every build. - Start-up: wait 200 µs, PRECHARGE ALL, 8 AUTO REFRESH, LOAD MODE (burst 1, CAS latency 3).
- Each access: ACTIVATE the row, READ or WRITE the column with A10 = 1 (auto-precharge), wait. About 9 clocks for a write, 13 for a read.
- Interface: when
ready, raisereqfor one clock withwe,addr= {bank, row, column},wdata; reads come back asrvalid+rdata.
What it took (three real bugs, each found on the board)
- Requests swallowed by refresh. The test saw
ready, raisedreqfor one clock, and in that very clock a refresh became due: the controller refreshed and the request vanished; the test hung. Signal Tap showed the test waiting and the controller idle. Fix: the controller now latches every request and serves it after the refresh. - Every write one address late. The test raised
reqand moved its address counter on in the same clock, so the controller (which takes the request a clock later) saw the next address. Reading address 1 twice proved it. Fix: a separate request-address register set together withreq. - A name used before its declaration became a silent 1-bit wire. Now the file starts with
`default_nettype none, which turns that into a compile error.
The simulation (lib/sim/tb_sdram_ctrl.sv with the SDRAM model lib/sim/sdram_model.sv, which drives read data only in the
datasheet's valid window) passed all along: it fails with read delay 3 or 5, so it checks the timing, but it could not see
bugs that lived in how the test talked to the controller. The board and Signal Tap could.
Run it
python scripts/de10.py build examples/06_sdram
python scripts/de10.py program examples/06_sdram
python scripts/de10.py log 40
Build it yourself
python scripts/new_project.py examples/06_sdram sdram_top --lib pll sdram print bcd seg7
python scripts/de10.py build examples/06_sdram
Test without the board
python scripts/sim.py lib/sim/tb_sdram_ctrl.sv → 300 words, 0 read errors, 0 protocol errors PASS
Files
| File | What |
|---|---|
src/sdram_top.sv | The memory test |
src/extra.qsf | I/O-cell registers for the SDRAM pins |
lib/rtl/sdram_ctrl.sv | The controller |
lib/sim/sdram_model.sv, lib/sim/tb_sdram_ctrl.sv | SDRAM model and unit test |