Fixed-Size Arrays
Packed vs unpacked, multi-dimensional, initialization, foreach, system functions.
Module 3 · Page 3.1
The Array Type You Use Without Thinking — Until You Can't
Fixed-size arrays are everywhere. The 256-deep register file in your APB peripheral? Static array. The 64-entry scoreboard lookup table in your testbench? Static array. The 8-byte packet buffer in your AXI driver? Static array. You use them constantly because they require no allocation, no deallocation, no size management — the compiler handles all of that at elaboration time.
But "simple" doesn't mean "trivial." SystemVerilog's array model has two orthogonal dimensions: packed and unpacked. That distinction determines whether your array is treated as a contiguous vector of bits or as a collection of independent objects — and it directly affects how the array connects to module ports, how it synthesizes, and what operators apply to it. Mixing them up silently produces the wrong hardware, and the tools don't always warn you.
The other issue: fixed-size arrays in SystemVerilog are initialized to X in simulation, not to zero. Every verification engineer who writes logic [7:0] buffer [64]; and immediately reads from it will get X on the first access. In C, uninitialized arrays are undefined behavior. In SV, they're explicit X — which propagates, poisons downstream logic, and produces the kind of waveform that takes a junior engineer an hour to trace.
Two Dimensions You Must Understand: Packed and Unpacked
SystemVerilog arrays have two independent dimension categories, and they stack on each other. Packed dimensions sit to the left of the variable name and define the bit-width of each element. Unpacked dimensions sit to the right of the variable name and define how many of those elements exist. A declaration like logic [7:0] mem [256] reads as: "256 elements (unpacked), each element is an 8-bit vector (packed)."
Packed Arrays — Contiguous Bit Vectors
A packed array is just a wider signal. logic [31:0] word is a packed 32-bit array — the dimensions describe bit positions within a single contiguous block of memory. You can apply any bitwise operator, slice any range, use it in arithmetic and comparisons. Synthesis tools have been handling packed arrays since Verilog-1995 — they map directly to wires and registers.
Unpacked Arrays — Collections of Objects
An unpacked array is a collection. logic [7:0] buf [4] is four separate 8-bit signals arranged in a named group. You index into it with buf[0], buf[1], etc. You can copy entire unpacked arrays with a single assignment, compare them with == and !=, and pass them to tasks and functions. But you cannot take bit-slices across element boundaries — buf[1:0] is an element slice (two elements), not a 16-bit concatenation of buf[0] and buf[1].
- Packed Array — Contiguous bits. Treated as one wide vector. Supports all bit operators, slicing, arithmetic. Maps directly to RTL wires/regs. logic [N-1:0] name
- Unpacked Array — Collection of elements. Indexed separately. Supports whole-array copy and comparison. Cannot bit-slice across elements. type name [N]
- Packed + Unpacked — Each element is a packed vector. Most common form in RTL/TB. logic [7:0] mem [256] = 256 bytes of memory.
- Multi-Dimensional — Packed dimensions stack left, unpacked stack right. logic [7:0] m[4][4] = 4×4 matrix of bytes.
Syntax Reference — Every Form You'll Use
// ── PACKED ARRAY (single wide vector) ────────────────────────────
logic [31:0] word; // 32-bit packed: one contiguous signal
logic [7:0] byte_val; // 8-bit packed: standard byte
// ── UNPACKED ARRAY (N elements, each of given type) ───────────────
logic [7:0] mem [256]; // 256 elements × 8-bit (0..255)
logic [7:0] fifo [0:63]; // 64 elements × 8-bit (explicit range)
int scores [16]; // 16 integers (signed 32-bit each)
// ── MULTI-DIMENSIONAL UNPACKED ────────────────────────────────────
logic [7:0] matrix [4][4]; // 4 rows × 4 cols of bytes
logic bitmask[8][8]; // 8×8 array of single bits
// ── PACKED MULTI-DIMENSIONAL ──────────────────────────────────────
logic [3:0][7:0] reg_file; // one 32-bit signal (4 packed bytes)
// reg_file[3] = bits [31:24], reg_file[0] = bits [7:0]
// ── INITIALIZATION AT DECLARATION ────────────────────────────────
int defaults[4] = '{0, 1, 2, 3}; // array literal
logic [7:0] cleared [4] = '{4{8'h00}}; // all zeros
logic [7:0] preset [3] = '{8'hAA, 8'hBB, 8'hCC};
// ── ACCESS ────────────────────────────────────────────────────────
mem[0] = 8'hFF; // write element 0
matrix[2][3] = 8'hAB; // write row 2, col 3
// ── SYSTEM FUNCTIONS ─────────────────────────────────────────────
$size(mem) // = 256 (total elements in first dimension)
$size(matrix, 1) // = 4 (size of dimension 1 = rows)
$size(matrix, 2) // = 4 (size of dimension 2 = cols)
$dimensions(mem) // = 1 (one unpacked dimension)
$low(fifo) // = 0 (lowest index)
$high(fifo) // = 63 (highest index)
$left(mem) // = 0 (leftmost declared index)
$right(mem) // = 255 (rightmost declared index)| Declaration | Packed bits | Unpacked elements | Total bits | Access form |
|---|---|---|---|---|
logic [31:0] w | 32 | 1 | 32 | w, w[7:0] |
logic [7:0] m[4] | 8 | 4 | 32 | m[0], m[3][5] |
logic [3:0][7:0] r | 32 (packed) | 1 | 32 | r, r[2], r[2][3:0] |
logic [7:0] x[4][4] | 8 | 4×4 = 16 | 128 | x[0][0], x[3][3] |
int arr[8] | 32 (int) | 8 | 256 | arr[0] to arr[7] |
Memory Layout — Visualizing Packed vs Unpacked
Unpacked Array Memory Model
Declaration: logic [7:0] buf [4] — 4 independent 8-bit elements. Each element has its own storage. You access them by index, not by bit position across the whole array.
| Index | Variable | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 | Value |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | buf[0] | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 1 | 8'hAB |
| 1 | buf[1] | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 1 | 8'hCD |
| 2 | buf[2] | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 8'hEF |
| 3 | buf[3] | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 8'h01 |
Packed Multi-Dimensional Array — Bit Mapping
Declaration: logic [3:0][7:0] reg_file — 4 packed bytes forming one 32-bit signal. The leftmost packed dimension selects the byte; the rightmost selects the bit within that byte.
| Selector | Bit range in 32-bit word | Access | Example value |
|---|---|---|---|
reg_file[3] | [31:24] — MSB byte | 8-bit slice of the packed vector | 8'hDE |
reg_file[2] | [23:16] | 8-bit slice | 8'hAD |
reg_file[1] | [15:8] | 8-bit slice | 8'hBE |
reg_file[0] | [7:0] — LSB byte | 8-bit slice | 8'hEF |
reg_file | [31:0] — whole word | Full 32-bit packed value | 32'hDEADBEEF → use 32'hCAFEBABE instead |
Multi-Dimensional Unpacked — Addressing Matrix
Declaration: logic [7:0] cache [4][8] — 4 rows, 8 columns, each cell holds a byte. Accessing row 2, column 5: cache[2][5]
| Row \ Col | [0] | [1] | [2] | [3] | [4] | [5] | [6] | [7] |
|---|---|---|---|---|---|---|---|---|
| cache[0] | 8'h00 | 8'h01 | 8'h02 | 8'h03 | 8'h04 | 8'h05 | 8'h06 | 8'h07 |
| cache[1] | 8'h10 | 8'h11 | 8'h12 | 8'h13 | 8'h14 | 8'h15 | 8'h16 | 8'h17 |
| cache[2] | 8'h20 | 8'h21 | 8'h22 | 8'h23 | 8'h24 | 8'h25 | 8'h26 | 8'h27 |
| cache[3] | 8'h30 | 8'h31 | 8'h32 | 8'h33 | 8'h34 | 8'h35 | 8'h36 | 8'h37 |
Green cell: cache[2][5] = 8'h25
Code Examples — From Basics to Real Verification
Example 1 — Beginner: Declare, Write, Read, Iterate
module tb_array_basics;
logic [7:0] data [8]; // 8-element byte array
int total;
int i;
initial begin
// ── Initialize all elements ──────────────────────────────────
foreach (data[i])
data[i] = i * 16; // 0x00, 0x10, 0x20, ... 0x70
// ── Read and display ─────────────────────────────────────────
$display("Array contents:");
foreach (data[i])
$display(" data[%0d] = 0x%02h", i, data[i]);
// ── System functions ─────────────────────────────────────────
$display("$size(data) = %0d", $size(data)); // 8
$display("$low(data) = %0d", $low(data)); // 0
$display("$high(data) = %0d", $high(data)); // 7
$display("$dimensions(data) = %0d", $dimensions(data)); // 1
// ── Whole-array copy ─────────────────────────────────────────
logic [7:0] copy [8];
copy = data; // single statement — copies all 8 elements
$display("copy[3] = 0x%02h (should be 0x30)", copy[3]);
// ── Whole-array comparison ───────────────────────────────────
if (copy == data)
$display("copy and data are identical");
data[0] = 8'hFF;
if (copy != data)
$display("copy and data now differ at [0]");
// ── Sum using a loop ─────────────────────────────────────────
total = 0;
foreach (data[i]) total += data[i];
$display("Sum = %0d", total);
$finish;
end
endmoduleExpected output:
Array contents:
data[0] = 0x00
data[1] = 0x10
data[2] = 0x20
data[3] = 0x30
data[4] = 0x40
data[5] = 0x50
data[6] = 0x60
data[7] = 0x70
$size(data) = 8
$low(data) = 0
$high(data) = 7
$dimensions(data) = 1
copy[3] = 0x30 (should be 0x30)
copy and data are identical
copy and data now differ at [0]
Sum = 376Example 2 — Intermediate: Register File Model (RTL-Style)
// Simplified 32-entry, 32-bit register file — common in RISC-V style cores
module reg_file (
input logic clk,
input logic we, // write enable
input logic [4:0] wr_addr, // 5-bit = 32 entries
input logic [31:0] wr_data,
input logic [4:0] rd_addr,
output logic [31:0] rd_data
);
logic [31:0] regs [0:31]; // 32 × 32-bit static array
// Synchronous write
always_ff @(posedge clk)
if (we && wr_addr != 5'b0) // reg[0] is hardwired to 0 in RISC-V
regs[wr_addr] <= wr_data;
// Asynchronous read (combinational)
assign rd_data = (rd_addr == 5'b0) ? 32'h0 : regs[rd_addr];
endmodule
// Testbench
module tb_reg_file;
logic clk = 0;
logic we;
logic [4:0] wr_addr, rd_addr;
logic [31:0] wr_data, rd_data;
reg_file dut(.*);
always #5 clk = ~clk;
initial begin
we = 1; wr_addr = 5'd5; wr_data = 32'hCAFE_BABE;
@(posedge clk); #1;
we = 0; rd_addr = 5'd5;
#1;
$display("reg[5] = 0x%08h (expect CAFEBABE)", rd_data);
// reg[0] always reads 0
rd_addr = 5'd0; #1;
$display("reg[0] = 0x%08h (expect 00000000)", rd_data);
$finish;
end
endmoduleExample 3 — Verification: Packet Buffer with Integrity Check
module tb_packet_buffer;
parameter int MAX_PKT = 8;
parameter int PKT_LEN = 16; // bytes per packet
typedef logic [7:0] byte_t;
typedef byte_t pkt_t [PKT_LEN]; // one packet = 16-byte array
pkt_t send_buf [MAX_PKT]; // buffer of 8 packets
pkt_t recv_buf [MAX_PKT]; // received copy
int mismatch_cnt;
int i, j;
// Build expected packets (fill with known pattern)
task automatic build_packets();
foreach (send_buf[i, j])
send_buf[i][j] = (i * PKT_LEN + j) & 8'hFF;
endtask
// Simulate DUT output (correct + one injected error)
task automatic simulate_recv();
recv_buf = send_buf; // whole-array copy
recv_buf[3][7] = 8'hFF; // inject error: pkt 3, byte 7
endtask
// Compare and report
task automatic check_packets();
mismatch_cnt = 0;
foreach (send_buf[i]) begin
if (send_buf[i] != recv_buf[i]) begin // compare entire packet
foreach (send_buf[i, j]) begin
if (send_buf[i][j] !== recv_buf[i][j]) begin
$error("PKT[%0d][%0d]: exp=0x%02h got=0x%02h",
i, j, send_buf[i][j], recv_buf[i][j]);
mismatch_cnt++;
end
end
end
end
if (mismatch_cnt == 0)
$display("PASS: all %0d packets match", MAX_PKT);
else
$display("FAIL: %0d mismatches detected", mismatch_cnt);
endtask
initial begin
build_packets();
simulate_recv();
check_packets();
$finish;
end
endmoduleExpected output:
ERROR: PKT[3][7]: exp=0x37 got=0xFF
FAIL: 1 mismatches detectedExample 4 — Corner Case: Initialization, Out-of-Bounds, Packed Access
module tb_corner_cases;
logic [7:0] uninitialized [4]; // not initialized — will be X
logic [7:0] packed_access [4];
initial begin
// ── Uninitialized = X ─────────────────────────────────────────
$display("uninitialized[0] = %02h", uninitialized[0]); // xx
$display("uninitialized[0] === 8'hxx: %0b",
uninitialized[0] === 8'hxx); // 1
// ── Out-of-bounds: no error, returns X ───────────────────────
logic [7:0] uninitialized [4];
// Accessing index 5 in a 4-element array (valid indices: 0-3)
$display("out-of-bounds [5] = %02h", uninitialized[5]); // xx
// Simulation does NOT throw an error — it just returns X
// ── Packed slice on unpacked element ─────────────────────────
packed_access[0] = 8'hAB;
$display("lower nibble: %h", packed_access[0][3:0]); // B
$display("bit 7: %b", packed_access[0][7]); // 1
// ── Array literal assignment ──────────────────────────────────
packed_access = '{8'hAA, 8'hBB, 8'hCC, 8'hDD};
$display("[0]=%02h [1]=%02h [2]=%02h [3]=%02h",
packed_access[0], packed_access[1],
packed_access[2], packed_access[3]);
// Note: '{} assigns index 0 first (leftmost = index 0)
// ── Default value in literal ──────────────────────────────────
packed_access = '{default: 8'hFF}; // fill all elements with FF
$display("after default fill: [2]=%02h", packed_access[2]); // FF
$finish;
end
endmoduleExpected output:
uninitialized[0] = xx
uninitialized[0] === 8'hxx: 1
out-of-bounds [5] = xx
lower nibble: b
bit 7: 1
[0]=AA [1]=BB [2]=CC [3]=DD
after default fill: [2]=FFSimulation Behavior — What the Simulator Actually Does
Initialization: X is the Truth
Every logic-type static array — whether module-level or in a static task/function — starts simulation at X. Not 0, not Z: X. This is intentional. SystemVerilog's four-state model uses X to represent "unknown initialization state," which is exactly what you have before any reset sequence drives the values.
The practical consequence: if your testbench initializes a reference model array with logic [7:0] ref_data [256] and then immediately compares it against DUT output, you will see mismatches everywhere because X !== 8'h00 is always true. Always initialize explicitly — either element-by-element in an initial block or via array literals at declaration.
Out-of-Bounds Access Behavior
| Scenario | Simulation result | Synthesis result | What to do |
|---|---|---|---|
| Read index > $high (static index) | Returns X, no error by default | Elaboration warning or error | Add bound check: if (i < $size(arr)) |
| Read index > $high (runtime index) | Returns X silently | Tool-dependent — may generate extra mux logic | Assert bounds at runtime |
| Write index > $high | Ignored silently (no write occurs) | Elaboration error | Guard writes with bounds check |
| Negative index on unsigned array | X (unsigned wrap-around) | Error | Declare index as int and guard for negative |
Synthesis Implications
| Usage | Synthesized hardware | Notes |
|---|---|---|
logic [7:0] r [4] in always_ff | 4 × 8-bit flip-flop registers | Standard register array — synthesizes cleanly |
| Constant index access | Direct wire to specific register bits | Zero extra logic |
| Variable index access (read) | Mux tree selecting from all elements | Timing-critical; depth grows with array size |
| Variable index access (write) | Demux — enable decode for each element | Area grows linearly with array size |
Packed array [N-1:0] | Single wide register or wire | No mux overhead |
Where Fixed-Size Arrays Show Up in Real Verification Work
// ── 1. SCOREBOARD: expected vs received transaction buffer ────────
logic [31:0] exp_data [16]; // expected DUT output (pre-computed)
logic [31:0] got_data [16]; // captured DUT output
if (got_data !== exp_data) // whole-array !== catches X mismatches too
$error("Buffer mismatch detected");
// ── 2. MONITOR: byte capture buffer for incoming AXI beat ─────────
logic [7:0] beat_bytes [64]; // 64-byte max beat
int byte_count = 0;
// In the monitor's capture loop:
// beat_bytes[byte_count++] = captured_byte;
// ── 3. DRIVER: pre-loaded stimulus table ──────────────────────────
logic [31:0] stim_table [256] = '{default: 32'h0};
// Load from file at sim start:
// $readmemh("stim.hex", stim_table);
// ── 4. CONSTRAINT: fixed-size array inside rand class ─────────────
class axi_burst_txn;
rand logic [7:0] data [16]; // 16-byte fixed burst
rand logic [7:0] strb [16]; // strobe per byte
constraint valid_strb {
foreach (strb[i])
strb[i] inside {8'h00, 8'hFF}; // either all-masked or all-valid
}
endclass
// ── 5. SVA ASSERTION: check stable array contents ─────────────────
// property p_stable_lut;
// @(posedge clk) !update_en |=> lut_array == $past(lut_array);
// endproperty
// assert property (p_stable_lut);
// ── 6. $readmemh / $writememh for file-based initialization ───────
logic [31:0] rom [1024];
initial
$readmemh("rom_init.hex", rom); // loads hex values from file into array
// rom_init.hex format: one hex value per line, e.g.:
// CAFEBABE
// 00000001
// ...Bugs Engineers Actually Hit With Static Arrays
Bug 1 — Reading Before Initialization: Everything is X
logic [31:0] ref_model [64]; // BUGGY: no initialization
initial begin
// DUT just completed — compare against reference model
if (dut_output !== ref_model[0]) // ref_model[0] is X — always mismatches!
$error("MISMATCH at [0]"); // fires even when DUT is correct
end
// FIXED: initialize before use
logic [31:0] ref_model [64] = '{default: 32'h0}; // all zeros
// OR load from file:
initial $readmemh("expected.hex", ref_model);Bug 2 — Wrong Array Literal Syntax: {} vs '{}
logic [7:0] buf [4];
// BUGGY: { } is concatenation, not array literal assignment
buf = {8'hAA, 8'hBB, 8'hCC, 8'hDD}; // COMPILE ERROR or type mismatch
// Concatenation produces a packed 32-bit value, not an unpacked array
// FIXED: use '{ } for unpacked array literals
buf = '{8'hAA, 8'hBB, 8'hCC, 8'hDD}; // CORRECT: array literal
// Also valid for default fill:
buf = '{default: 8'hFF}; // fills all 4 elements with 0xFF
// And for partial init with default:
buf = '{8'h01, 8'h02, default: 8'h00}; // [0]=01, [1]=02, [2]=00, [3]=00Bug 3 — Packed vs Unpacked Port Mismatch
// Module declares an unpacked port
module dut (input logic [7:0] data_in [4]); // unpacked [4]
// ...
endmodule
module tb;
logic [31:0] packed_sig; // 32-bit packed
logic [7:0] unpack_arr [4]; // 4-element unpacked
// BUGGY: connecting a packed 32-bit signal to an unpacked [4][8] port
dut d1 (.data_in(packed_sig)); // TYPE ERROR at elaboration
// CORRECT: connect matching unpacked array
dut d2 (.data_in(unpack_arr)); // types match
// If you genuinely need to convert between the two, use explicit assignment:
always_comb begin
foreach (unpack_arr[i])
unpack_arr[i] = packed_sig[i*8 +: 8]; // slice bytes from packed sig
end
endmoduleBug 4 — Multi-Dimensional Index Order Confusion
logic [7:0] matrix [4][8]; // [4 rows][8 cols]
// BUGGY: engineer wants row 2, column 5 but swaps dimensions
logic [7:0] val = matrix[5][2]; // reads col 5 of row... wait, index 5 on dim[4] is OUT OF BOUNDS
// matrix[5] returns X (out of bounds on first dim which only goes 0..3)
// matrix[5][2] = X — no error, silent wrong result
// CORRECT: first index = row (0..3), second = col (0..7)
val = matrix[2][5]; // row 2, column 5
// RULE: dimensions are indexed left-to-right as declared
// logic [7:0] matrix [ROWS][COLS] → matrix[row][col]Interview Questions
Beginner Level
Q1: What is the default initial value of a logic type static array in SV simulation? X (unknown). Unlike bit type arrays which initialize to 0, logic and reg arrays start as X in simulation. This represents an unknown/uninitialized state, not a logical 0. Always initialize before reading to avoid X-propagation through downstream logic. Q2: What is the difference between logic [31:0] w and logic [7:0] m [4]? Both hold 32 bits.logic [31:0] w is a packed 32-bit vector — one contiguous signal that supports bit-slicing, arithmetic, and bitwise operations across all 32 bits at once. logic [7:0] m [4] is an unpacked array of four independent 8-bit elements — you can copy it whole or compare it with ==, but you cannot take a bit-slice that crosses element boundaries. They are different types; you cannot directly connect them at module ports without explicit conversion.
Intermediate Level
Q3: What does logic [3:0][7:0] bus declare, and how do you access the second byte? A packed multi-dimensional array — effectively a 32-bit signal split into four named byte lanes. bus[3] = bits [31:24] (MSB byte), bus[0] = bits [7:0] (LSB byte). To access the second byte (index 1): bus[1] which gives bits [15:8]. You can also use standard packed slicing: bus[15:8] is equivalent. Q4: What happens when you access an out-of-bounds index on a static array in simulation? The simulator returns X on a read and silently ignores a write — no exception, no fatal error by default. Most simulators issue a warning, but the simulation continues. This is a dangerous silent failure mode: if an index variable wraps around or is uninitialized, you get X flowing through your logic with no obvious indication of the root cause. Always guard variable-indexed array accesses with explicit bounds checks.
Experienced Engineer Level
Q5: In RTL synthesis, what hardware does always_comb data_out = mem[addr] generate versus always_comb data_out = mem[3]?mem[3] (constant index) generates a direct wire — zero gates, zero timing overhead. mem[addr] (runtime variable index) generates a multiplexer tree: one 2-to-1 mux per address bit, producing a log2(N)-deep mux tree for an N-entry array. For a 256-entry array this is an 8-deep mux cascade, with significant timing implications. This is why real register files use dedicated SRAM macros or a decoded enable structure rather than a variable-indexed array read in combinational logic.
Best Practices & Coding Guidelines
- Always initialize arrays — Use '{default: val} at declaration or $readmemh in the initial block. Never read before writing. In RTL, use reset logic to drive arrays to known values.
- Use foreach for portability — foreach (arr[i]) automatically respects the array bounds — no magic numbers. It works on any dimensionality and handles non-zero-based ranges correctly.
- Match packed/unpacked at ports — If a module port uses an unpacked array, the connecting signal must also be the same unpacked type. Never assume a packed and unpacked array of equal total bits are interchangeable — they are not.
- Guard variable index accesses — In simulation, always add an assertion or conditional check when using a runtime variable as an array index. Out-of-bounds returns X silently and the bug can travel far before anyone notices.
| Task | Preferred approach | Avoid |
|---|---|---|
| Initialize all elements to a value | arr = '{default: val} | Manual loop — more code, same result |
| Iterate over all elements | foreach (arr[i]) | for (int i=0; i<SIZE; i++) — error-prone size magic |
| Copy entire array | dst = src (whole-array assign) | Element-by-element loop — verbose |
| Compare entire array | if (a !== b) (use !== to catch X) | if (a != b) — misses X differences |
| Load from file | $readmemh("file.hex", arr) | Hardcoded values — unmaintainable |
| Large memory model in testbench | Associative array or dynamic array | Static array of 1M entries — wastes simulator memory |
Summary
Fixed-size arrays are the first tool you reach for whenever you know the array dimensions at design time. They synthesize directly to hardware, cost nothing at runtime, and — when used correctly — make RTL intent crystal clear. The two things worth internalizing deeply: the packed/unpacked distinction (it determines what operations apply and how ports connect), and the X-initialization default (it turns every unguarded read before write into a silent bug factory).
- Packed dimensions go left of the name; unpacked go right. The split determines the type, not just the bit count.
logicarrays start as X,bit/intarrays start as 0. Choose the right type for your use case.- Whole-array copy and comparison are single-statement operations on unpacked arrays — use
!==rather than!=to catch X differences in scoreboards. - Variable index access synthesizes to a mux tree. That is usually the wrong choice for large memories — use SRAM macros or associative arrays depending on context.
foreachis the right loop for arrays. It respects bounds automatically and handles multi-dimensional indexing cleanly.