03 · RAM: single-port and dual-port

Memory you can write. A single-port RAM checks itself about 3,000 times a second, and a dual-port RAM is a frame buffer: written at 50 MHz by a little paint program, read at 25 MHz by the screen.

Verified on the board 2026-10-24 Quartus Prime Lite 17.1 LCD shield (game pad) or VGA Skill: build-ram
Download ram.qar (full project)

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.

What you will see

Screen read back from the board: a colour gradient test picture with a dark grid every 20 pixels and a white frame.

WhereWhat
ScreenThe frame buffer, each of its 160 × 120 pixels drawn as a 4 × 4 block. At start-up: the test picture above
Game padJoystick moves the cursor; A paints with the wheel's colour (turn the wheel to change it); B erases; Select clears; Start redraws the test picture
HEX5..HEX0Self-test errors since power-up, in decimal. Must stay 0
SW0 upInject one wrong bit per pass on purpose: errors must climb by one per pass (proves the test can see errors). LEDR0 shows SW0
Your PCpython scripts/de10.py log: RAM BIST passes=00005372 errors=00000000 last=0000 every second

Block diagram

single-port, 50 MHz ram_bistLFSR pattern, write all,read back, compare ram_sp8,192 × 16one address, one clock HEX5..0 errors USB: report / s dual-port: write 50 MHz, read 25 MHz game padvia ADC painttest picture, cursor,waits for PLL locked ram_dp frame buffer19,200 × 12 (160 × 120 RGB444)port A write @ 50 MHzport B read @ 25 MHz cursor overlayaddress = nextpixel (x + 1) LCD + VGA debug_link + tx_muxscreen & report → USB

The three RAM templates

All three are plain Verilog written the way Quartus recognizes as block memory (M9K). They live in lib/rtl/:

ModulePortsUse it for
ram_spone address, write + read, one clockscratch memory, tables you update, a CPU's data RAM
ram_dpone write port, one read port, each with its own clockframe buffers, FIFOs, passing data between clock domains
ram_tdptwo independent read/write ports, two clocksshared memory between two engines (e.g. CPU and DMA)
ram_dp #(.WIDTH(12), .AW(15), .DEPTH(19200)) fb (
  .wr_clk(clk_50), .wr_addr(wr_addr), .wr_d(wr_d), .we(we),      // paint
  .rd_clk(clk_25), .rd_addr(rd_addr), .rd_q(pixel));              // screen

DEPTH keeps the memory to what you need: 19,200 words instead of the 32,768 a 15-bit address could reach. Every read answers one clock after the address, so the screen asks for the next pixel, exactly as with ROMs (example 02).

Things learned building it (each one cost a build)

Measured on the board

SW0 up:    RAM BIST passes=00005F5E errors=00005F5E last=0001     ~3,050 passes / s, one error per pass, as injected

SW0 down (no injection, errors must stay 0) is verified in simulation (tb_ram_bist.sv); the board run with SW0 down is still to do.

Screen read back over USB: the test picture, pixel for pixel (image above). Resources: 7,193 logic elements (14 %), memory 78 % (screen read-back buffer 56 %, frame buffer 14 %, BIST RAM 8 %). Timing: no negative slack.

Run it

python scripts/de10.py build examples/03_ram
python scripts/de10.py program examples/03_ram
python scripts/de10.py log 3
python scripts/de10.py screen logs/paint.png

Build it yourself

python scripts/new_project.py examples/03_ram ram_top --lib display gamepad debug print mux bcd sevenseg ram
python scripts/de10.py build examples/03_ram
python scripts/de10.py program examples/03_ram

Test without the board

python scripts/sim.py examples/03_ram/sim/tb_paint_fb.sv     → 50 pixels checked, 0 errors   PASS
python scripts/sim.py examples/03_ram/sim/tb_ram_bist.sv     → passes with 0 errors; with inject, 1 per pass   PASS

Files

FileWhat
src/ram_top.svTop: display, game pad, frame buffer, self-test, USB report
src/paint.svFrame-buffer writer: test picture, cursor, painting
src/ram_bist.svSingle-port RAM self-test with error injection
sim/tb_paint_fb.sv, sim/tb_ram_bist.svUnit tests
lib/rtl/ram_sp.sv, ram_dp.sv, ram_tdp.svThe RAM templates
lib/rtl/tx_mux.svTwo text senders sharing the USB link