Part 1 · Language Foundations · Intermediate

Packed vs Unpacked Arrays

Memory layout, range syntax, multidimensional declarations, part-selects and slicing, and what synthesis accepts.

Two layouts, two meanings

The dimension position in a declaration decides everything. Dimensions written before the name are packed: the whole array is one contiguous vector of bits that the simulator stores and operates on as a single value. Dimensions written after the name are unpacked: each element is an independent variable, and the simulator may store them anywhere — there is no guarantee of contiguity. That is why you can write bus + 1 on a packed array but not on an unpacked one: arithmetic, comparison, and part-selects are vector operations, and only packed arrays are vectors.

Packed dimensions may only be built from single-bit types (bit, logic, reg) and other packed types. Unpacked dimensions can hold anything — ints, structs, classes, even other arrays. A common declaration like logic [7:0] mem [0:1023] reads as: 1024 unpacked elements, each a packed 8-bit vector — exactly the shape of a byte memory.

diagram
MEMORY LAYOUT — logic [3:0][7:0] pkt  vs  logic [7:0] buf_q [0:3]

  PACKED  logic [3:0][7:0] pkt;         ONE 32-bit vector
  ┌───────────┬───────────┬───────────┬───────────┐
  │ pkt[3]    │ pkt[2]    │ pkt[1]    │ pkt[0]    │
  │ bits31:24 │ bits23:16 │ bits15:8  │ bits7:0   │
  └───────────┴───────────┴───────────┴───────────┘
   contiguous — pkt[2][3] selects one bit of the vector
   pkt + 1, pkt == 32'h0, pkt[15:8] all legal

  UNPACKED  logic [7:0] buf_q [0:3];    FOUR separate bytes
  ┌─────────┐   ┌─────────┐   ┌─────────┐   ┌─────────┐
  │buf_q[0] │   │buf_q[1] │   │buf_q[2] │   │buf_q[3] │
  └─────────┘   └─────────┘   └─────────┘   └─────────┘
   independent storage — no whole-array arithmetic,
   compare/copy element-wise or with array methods

Range syntax, multidimensional arrays, and slicing

Packed ranges conventionally run [msb:lsb] with the high index on the left; unpacked ranges traditionally run [0:n-1] or use the shorthand [n], which means [0:n-1]. With multiple dimensions, you index unpacked dimensions first, left to right, then packed dimensions left to right — a detail interviewers love because most candidates get the ordering backwards.

systemverilog
module array_layout_demo;
  // 2 unpacked rows, each row a packed 4-lane x 8-bit vector
  logic [3:0][7:0] frame [0:1];

  initial begin
    frame[0]        = 32'hDEAD_BEEF; // whole packed value of row 0
    frame[0][3]     = 8'hAA;         // lane 3 (bits 31:24) of row 0
    frame[0][3][7]  = 1'b0;          // single bit
    frame[1][1:0]   = 16'hCAFE;      // packed slice: lanes 1..0

    // Variable slice with +: (width must be constant)
    for (int i = 0; i < 4; i++)
      $display("lane %0d = %0h", i, frame[0][i*8 +: 8]);
  end
endmodule
systemverilog
// Assignment patterns and array copy rules
logic [7:0] a [0:3];
logic [7:0] b [0:3];
logic [7:0] c [0:7];

initial begin
  a = '{8'h11, 8'h22, 8'h33, 8'h44}; // assignment pattern
  b = '{default: 8'h00};             // fill every element
  b = a;                             // legal: identical shape
  // c = a;                          // ILLEGAL: 8 elements vs 4
  if (a == b) $display("arrays equal (element-wise compare)");
end

Indexing order summary

  • Declaration: packed dims before the name, unpacked dims after the name.

  • Indexing: unpacked dims consumed first (left to right), then packed dims (left to right).

  • Part-selects ([3:0], +:, -:) are legal only on packed dimensions — unpacked slices use full-element ranges.

  • Whole-array assignment and equality require identical element type and shape on both sides.


When synthesis cares

Synthesis accepts both packed and unpacked fixed-size arrays, but the layout drives what hardware you get. A packed array is a flat bus of wires or flops. A large unpacked array of packed elements (logic [31:0] mem [0:4095]) is the canonical shape that RAM-inference engines look for: single write port in a clocked block, reads through an index. Mixing the two styles — say, packing a 4096-entry memory into one 131072-bit vector — defeats RAM inference and explodes into discrete flops. Dynamic arrays, queues, and associative arrays are never synthesizable ; they exist only in simulation, which is why RTL coding guidelines ban them outside testbench code.

Simulator performance also differs. Packed arrays are stored as machine words and bit operations on them are fast; very wide unpacked-of-packed structures cost more per-element bookkeeping but allow lazy allocation in some simulators. For 4-state logic, every bit needs two stored bits (value + X/Z), doubling memory versus 2-state bit — a reason large TB-internal arrays are often declared bit.

Key takeaways

  • Dims before the name = packed (one vector); dims after = unpacked (independent elements).

  • Index unpacked dimensions first, then packed — memorize the ordering for interviews.

  • Packed arrays support arithmetic, comparison, and bit slicing; unpacked arrays are copied/compared element-wise.

  • RAM inference wants unpacked-of-packed (logic [31:0] mem [0:N-1]); 2-state bit halves TB memory cost.

Common pitfalls

  • Writing logic [0:7] data and expecting bit 7 to be the MSB — direction follows your declaration, not convention.

  • Trying part-select on an unpacked dimension (mem[3:0] of an unpacked array selects elements, not bits).

  • Assigning arrays with different unpacked shapes — compile error even if total bit count matches.

  • Packing a large memory into one huge vector — kills RAM inference and slows simulation.