Skip to content
VLSI Mentor

VHDL · Chapter 13.5 · Advanced Data Structures

Memory and RAM/ROM Modeling

There is no memory keyword in VHDL. A RAM or ROM is just an array of words written in a coding style the synthesizer recognizes and maps to dedicated storage. A ROM is a constant array indexed at runtime, mapping to block ROM or LUTs. A RAM is a signal array with a clocked write, and the decisive detail is the read: a synchronous, registered read infers dedicated block RAM, while an asynchronous read infers distributed LUT RAM instead. You also choose the read-during-write behaviour, provide initial contents, and pick single-port or simple dual-port with one write and one read port. Getting the style right is what makes the tool infer the memory primitive you intended instead of a wall of flip-flops. This lesson covers the ROM, single-port, and dual-port styles and the synchronous-read rule for block RAM.

Foundation15 min readVHDLMemoryRAMROMBlock RAMSynthesis

1. Engineering intuition — memory is a recognized pattern, not a keyword

FPGAs and ASICs have dedicated memory blocks — block RAM, LUT RAM, ROM — and the synthesizer maps your code onto them by recognizing a pattern, not by a special type. That pattern is: an array of words, a clocked write, and a read. The single most important knob is whether the read is registered: a block RAM physically has a registered read port, so to infer one your code must read through a register (synchronous read). Read combinationally (asynchronous) and the tool cannot use block RAM, so it falls back to distributed/LUT RAM or flops. So memory modeling is really about writing the array access in the exact shape the target primitive expects.

2. Formal explanation — ROM and single-port RAM styles

rom_and_ram.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all;
-- ROM: a CONSTANT array indexed at runtime → block ROM / LUTs.
type rom_t is array (0 to 255) of std_logic_vector(7 downto 0);
constant ROM : rom_t := ( 0 => x"3A", 1 => x"7F", others => x"00" );   -- initial contents
-- (registered read recommended for block ROM inference)
process (clk) begin
  if rising_edge(clk) then dout <= ROM(to_integer(unsigned(addr))); end if;
end process;
 
-- SINGLE-PORT RAM with SYNCHRONOUS read → infers BLOCK RAM.
type ram_t is array (0 to 1023) of std_logic_vector(31 downto 0);
signal ram : ram_t;
process (clk) begin
  if rising_edge(clk) then
    if we = '1' then ram(to_integer(unsigned(addr))) <= din; end if;   -- clocked write
    dout <= ram(to_integer(unsigned(addr)));                           -- REGISTERED read → BRAM
  end if;
end process;
-- (An ASYNCHRONOUS read — dout <= ram(addr) as a concurrent statement — infers DISTRIBUTED/LUT RAM.)

A ROM is a constant array indexed by a runtime address; a RAM is a signal array with a clocked write. The read style decides the primitive: a registered read (assigned inside the clocked process) infers block RAM; an asynchronous read infers distributed RAM. Initial contents come from the array's initializer.

3. Production usage — read-during-write and simple dual-port

dual_port_and_rdw.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- SIMPLE DUAL-PORT: one write port, one read port (e.g. for FIFOs, buffers).
process (clk) begin
  if rising_edge(clk) then
    if we = '1' then ram(to_integer(unsigned(waddr))) <= din; end if;  -- write port
    dout <= ram(to_integer(unsigned(raddr)));                          -- independent read port
  end if;
end process;
 
-- READ-DURING-WRITE choice when waddr = raddr in the same cycle:
--   READ-FIRST  (read OLD data): read assigned from the array BEFORE/independent of the write
--   WRITE-FIRST (read NEW data): bypass — dout takes din when writing the same address
-- The exact ordering in the clocked process selects which BRAM mode the tool infers.

What hardware does this become? With a synchronous read, both the single- and dual-port styles map to a block RAM primitive: 1024×32 storage with registered output, one or two ports. The read-during-write ordering you write (assign dout from the array vs bypass din) selects the BRAM's read-first/write-first mode — which matters when a read and write hit the same address in one cycle. Choosing dual-port lets reads and writes proceed independently, the shape FIFOs and line buffers need. The array is the memory; the access style picks the primitive and its collision behavior.

4. Structural interpretation — synchronous-read RAM inference

synchronous RAM: write port into array, registered read producing dout one cycle laterwrite portwe, addr, din (clocked)word array (storage)DEPTH × DATA_Wread registersynchronous read → BRAMdoutone cycle after addrwriteregistered read12
A synchronous-read RAM infers block RAM. The write port clocks din into the array at addr when we is high; the read port assigns the array output into a register, so dout appears one cycle after addr — exactly the structure of a block RAM with a registered read port. An asynchronous read (a concurrent dout <= ram(addr)) would instead infer distributed/LUT RAM with combinational output. The read-during-write ordering selects read-first vs write-first behavior on same-address collisions. This is a memory-inference structure; the waveform below shows the one-cycle registered read.

5. Simulation interpretation — the one-cycle synchronous read

Synchronous-read RAM: write address 5, then read it back one cycle late

8 cycles
Synchronous-read RAM: write address 5, then read it back one cycle latewe=1: write 0xC3 into address 5 (clocked write)we=1: write 0xC3 intoaddress 5 (clocked write)registered read: dout = 0xC3 — ONE cycle after addr is presentedregistered read: dout =0xC3 — ONE cycle after addris presentedclkwe11000000addr55555555dinC3C3------------dout----C3C3C3C3C3C3t0t1t2t3t4t5t6t7
The registered read is what makes this a block RAM: dout reflects the addressed word one clock after the address, not combinationally. That single cycle of read latency is the visible signature of synchronous-read memory — and exactly the timing a block RAM primitive provides. An asynchronous read would show dout following addr in the same cycle, at the cost of using LUT/distributed RAM.

6. Debugging example — the RAM that became a wall of flip-flops

Expected: a large array infers block RAM. Observed: synthesis builds thousands of flip-flops (or fails to fit), or reports the memory could not be mapped to block RAM. Root cause: the read was asynchronous (a concurrent dout <= ram(addr)), or the array was reset/initialized in a way block RAM cannot support, or it had too many ports / an unsupported read-during-write style — so the tool could not use a block RAM primitive and fell back to registers/LUT RAM. Fix: use a synchronous (registered) read inside the clocked process, avoid a global array reset, and keep to a supported port count and read-during-write mode so the BRAM template matches. Engineering takeaway: block RAM inference needs a registered read and a BRAM-compatible style — an asynchronous read or array-wide reset forces distributed RAM or flip-flops instead.

register_the_read.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- BUG: asynchronous read → cannot infer block RAM (distributed RAM / flops instead).
-- dout <= ram(to_integer(unsigned(addr)));   -- concurrent, combinational read
-- FIX: register the read inside the clocked process → block RAM.
process (clk) begin if rising_edge(clk) then
  dout <= ram(to_integer(unsigned(addr)));     -- synchronous read → BRAM
end if; end process;

7. Common mistakes & what to watch for

  • Asynchronous read expecting block RAM. BRAM needs a registered read; a combinational read infers distributed/LUT RAM.
  • Resetting the whole array. Block RAM cannot be reset cycle-by-cycle; use initial contents, not an array-wide synchronous reset.
  • Ignoring read-during-write. Same-address read+write in one cycle returns old or new data per your ordering; choose read-first/write-first deliberately.
  • Too many ports. Most block RAMs are single- or simple-dual-port; true multi-port memories cost extra logic or replication.
  • Forgetting the read-latency. A synchronous read adds one cycle; account for it in surrounding timing (FIFOs, pipelines).

8. Engineering insight & continuity

Memory modeling is pattern-matching: an array of words plus the right access style infers the intended primitive — a constant array for ROM, a clocked write with a registered read for block RAM, an asynchronous read for distributed RAM — with read-during-write ordering and port count selecting the exact mode. The registered read (and its one-cycle latency) is the signature of block RAM. This is the synthesis payoff of arrays of words. Module 13 next turns to two simulation-only data constructs that round out the type system — Protected Types (the next lesson: shared, safely-accessed state for testbenches) and then access types.

Standards & specifications

Governing standard
IEEE Std 1076 (VHDL)(opens IEEE in a new tab)

Defines the VHDL language — types, the simulation cycle, and the semantics a conforming analyser and simulator must implement. Synthesis restrictions and vendor coding rules are tool behaviour, not language rules.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the VHDL curriculum.