Skip to content

VHDL · Chapter 13.2 · Advanced Data Structures

Multidimensional and Nested Arrays

When a table needs more than one index, VHDL gives you two distinct tools, and choosing correctly matters. A true two-dimensional array is indexed with a comma, like m of i and j, and models a genuine matrix or grid. An array of arrays, also called nested, has an element type that is itself an array, so it is indexed by chaining, and it is the natural model for a memory as an array of words. The key practical difference is that only the nested form lets you grab an entire row as a unit, slice it, or pass it around, which is why RTL memories almost always use it. This lesson contrasts the two forms, shows indexing and row access, and notes what each synthesizes to.

Foundation14 min readVHDLArrays2D ArraysMemoryData StructuresRTL

1. Engineering intuition — a matrix versus a stack of words

Two-dimensional data comes in two flavours, and they want different shapes. Sometimes it is a genuine grid — a matrix of coefficients, a 2-D pixel block — where (row, column) is one coordinate; a true 2-D array captures that. Other times it is a stack of words — a memory where row i is a complete word you read, write, and slice as a unit; an array of arrays captures that. The deciding question is: do you ever need a whole row at once? If yes — and for memories you always do — you want the nested form, because mem(i) is itself a vector you can manipulate. If you only ever touch individual cells of a fixed grid, a true 2-D array is fine.

2. Formal explanation — true 2-D vs array of arrays

two_dimensions.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
library ieee; use ieee.std_logic_1164.all;
-- (1) TRUE 2-D array: one type, comma-indexed. Good for matrices / grids.
type matrix_t is array (0 to 3, 0 to 7) of std_logic;     -- 4 × 8 grid of bits
signal m : matrix_t;
--   m(i, j)            -- access ONE cell; you canNOT take a whole row m(i) as a value
 
-- (2) ARRAY OF ARRAYS (nested): element type is itself an array. Chained index.
subtype word_t is std_logic_vector(7 downto 0);
type mem_t is array (0 to 15) of word_t;                  -- 16 words of 8 bits
signal mem : mem_t;
--   mem(i)             -- a WHOLE WORD (row) as a value → sliceable, assignable
--   mem(i)(j)          -- bit j of word i
--   mem(i)(3 downto 0) -- slice of a row

A true 2-D array is a single type indexed m(i, j); its elements are scalars and you cannot address a whole row as one object. An array of arrays has an array element type, indexed mem(i)(j); crucially mem(i) is itself a vector — so you can read, write, slice, and pass a whole row.

3. Production usage — memory as an array of words

memory_array_of_words.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- RTL memories use the NESTED form so a word is a first-class value.
process (clk) begin
  if rising_edge(clk) then
    if we = '1' then
      mem(to_integer(unsigned(addr))) <= din;            -- write a WHOLE word (row) at once
    end if;
    dout <= mem(to_integer(unsigned(addr)));             -- read a whole word
  end if;
end process;
 
-- Row-level operations the nested form enables:
--   tag   <= mem(i)(7 downto 4);     -- slice a field out of a word
--   mem(i) <= (others => '0');       -- clear a whole word
 
-- A true 2-D array suits a coefficient grid accessed cell-by-cell:
--   y <= coeff(r, c);                -- one element of a fixed matrix

What hardware does this become? The nested mem is a memory of words — 16 × 8-bit storage, written and read a word at a time — which a synthesizer maps to a register bank or inferred RAM. Because mem(i) is a vector, din/dout are whole words and slicing a field is free wiring. A true 2-D coeff(r, c) becomes a grid of registers/constants addressed per cell. Both are real storage; the nested form is preferred for memories precisely because the word is the unit you operate on.

4. Structural interpretation — 2-D grid vs array of words

true 2D array indexed by comma versus array of arrays indexed by chaining with whole-row accessrow accesstrue 2-D: m(i, j)comma index — cells onlymatrix / gridno whole-row valuearray of arrays:mem(i)(j)chained indexmem(i) = a whole wordsliceable, assignable row12
Two ways to get a second dimension. A true 2-D array is one comma-indexed grid, m(i,j), addressing individual cells — good for matrices, but a whole row is not a value. An array of arrays nests an array element type, mem(i)(j), so each row mem(i) is itself a vector that can be read, written, sliced, and passed as a unit — the natural model for a memory of words and the preferred form for RTL. Both synthesize to real storage (register banks or inferred RAM); the difference is whether a whole row is a first-class object. This is a data-layout structure, captured by a diagram rather than a waveform.

5. Why this is structural, not timing

The choice between a true 2-D array and an array of arrays is a data-layout decision — how the dimensions are typed and indexed, and whether a row is a first-class value — so a structure diagram (above), not a waveform, captures it. The run-time behaviour is just that of the storage it produces (a memory of words reads and writes a word per access, with timing already covered in the sequential/memory material). What this lesson buys you is correct shape: pick nested for memories so word-level read/write/slice is natural, true 2-D for fixed grids accessed cell-by-cell.

6. Debugging example — the 2-D array that would not give a row

Expected: read or write a whole word/row at once. Observed: a compile error trying to use m(i) on a true 2-D array, or awkward per-cell loops to move a row, or a din/word assignment that will not type-check. Root cause: the storage was declared as a true 2-D array (array (R, C)), which has no notion of a row as a value, when the design actually needs word-level access. Fix: declare it as an array of arrays (array (0 to R-1) of word_t) so mem(i) is a vector you can read, write, and slice as a unit. Engineering takeaway: if you ever need a whole row/word as one value — and memories always do — use an array of arrays, not a true 2-D array; the comma-indexed form only addresses individual cells.

nested_for_word_access.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- BUG: true 2-D array → no whole-row value, can't write a word at once.
-- type m_t is array (0 to 15, 0 to 7) of std_logic;  signal m : m_t;
-- m(addr) <= din;   -- illegal: m needs two indices; no row object
-- FIX: array of arrays → a word is a first-class value.
type mem_t is array (0 to 15) of std_logic_vector(7 downto 0);
mem(to_integer(unsigned(addr))) <= din;   -- write a whole word

7. Common mistakes & what to watch for

  • True 2-D array for a memory. It has no row value; use an array of arrays so words are first-class (read/write/slice a whole word).
  • Mixing the two index styles. m(i, j) (true 2-D) vs arr(i)(j) (nested) are different types; use the syntax that matches the declaration.
  • Assuming whole-row ops on true 2-D. You cannot slice or assign a row of a comma-indexed array; only the nested form supports it.
  • Forgetting inference rules for big arrays. Large word memories infer RAM with read latency; check the synthesis result for deep arrays.
  • Ragged data in a true 2-D array. Fixed rectangular grids only; varying-length rows need nesting (and care), not a 2-D array.

8. Engineering insight & continuity

Two indices, two tools: a true 2-D array (m(i,j)) for genuine matrices and grids accessed cell-by-cell, and an array of arrays (mem(i)(j)) for memories and any case where a whole row/word must be a first-class, sliceable value — the form RTL memories use. Match the shape to whether you need row-level access. So far the arrays have had fixed bounds; the next lesson removes that constraint — Unconstrained Array Types — letting a type leave its size open so functions, ports, and generic blocks adapt to whatever width the caller supplies.