An associative array such as byte mem [longint] is a map. Storage is allocated only for indices actually written, so a 64-bit address space costs no more than its touched locations.
The index may be an integral type, a string, or a class handle. The wildcard [*] takes any integral index value but cannot be used with foreach or the index-returning array methods, so prefer a typed index.
Operation | Meaning |
|---|---|
| 1 when an entry for k is allocated, else 0. Guard reads with it. |
| Count of allocated entries. |
| Remove one entry. |
| Remove every entry. |
Reading a never-written index does not allocate it. It returns the default value for the element type. The LRM requires a warning (unless the array declares its own default), but the wording varies by tool, so guard with exists() and never rely on the warning.
Dense for-loops cannot cover a sparse index space. The methods first(), next(), last() and prev() take the index variable by reference, step it to the neighboring existing key in sorted index order, and return 1 on success or 0 when traversal ends.
That return value makes them natural loop conditions. foreach visits the same sorted key order and suits straight full walks, while the explicit methods allow mid-range starts and backward walks.
The expected-queue checker from the previous lesson breaks when the DUT reorders responses. Order is then no longer the contract, so matching must be by identity.
The fix is an associative array keyed by the ID tag that each request carries (a transaction is one complete operation, such as one read). Predictions are stored under their tag, each response looks up its own entry, and leftover entries at end of test are transactions the DUT dropped. The out-of-order capstone builds a full checker on this idea.
DUT behavior | Checker container | Match rule |
|---|---|---|
In-order responses | Queue, | Arrival order |
Out-of-order responses | Associative array keyed by ID | Tag lookup, then |
mem is byte mem [longint] and address 0x2000 was never written. What does mem.exists(16'h2000) return, and what would a raw read return?exists() returns 0. A raw read returns the default value for byte without allocating an entry.
The checker leaks the entry and the end-of-test drain check wrongly reports a missing response.
first/next loop visit them?5, 100, 7000. Traversal follows sorted key order, never write order; designs that need arrival order use a queue.
Sparse storage, guarded reads, sorted traversal and an out-of-order ID checker with three tags answered out of order.
module tb_assoc;
byte mem[longint]; // 64-bit address space, sparse
longint addr;
longint order[$];
int expected[int]; // out-of-order checker: expected data by ID
int ids[$];
int id;
int want;
int errors = 0;
initial begin
mem[64'h0000_0000_0000_1000] = 8'hA5;
mem[64'hFFFF_FFFF_0000_0000] = 8'h3C; // far apart, still 2 entries
if (mem.num() != 2) errors++;
if (!mem.exists(64'h1000)) errors++;
if (mem.exists(64'h2000)) errors++; // never written: must not exist
if (mem[64'h1000] !== 8'hA5) errors++;
mem.delete(64'h1000);
if (mem.num() != 1) errors++;
mem.delete();
if (mem.num() != 0) errors++;
mem[100] = 8'h11;
mem[5] = 8'h22;
mem[7000] = 8'h33;
if (mem.first(addr)) // ascending key order: 5, 100, 7000
do order.push_back(addr);
while (mem.next(addr));
if (order.size() != 3) errors++;
if (order[0] != 5 || order[1] != 100 || order[2] != 7000) errors++;
expected[7] = 70; // predict three IDs
expected[3] = 30;
expected[9] = 90;
if (expected.exists(4)) errors++; // unknown ID has no entry
ids = {3, 9, 7}; // respond out of order
foreach (ids[i]) begin
id = ids[i];
want = 10 * id;
if (!expected.exists(id)) errors++;
else begin
if (expected[id] !== want) errors++;
expected.delete(id); // retire the entry
end
end
if (expected.num() != 0) errors++; // drain check: every ID answered
if (errors != 0) $fatal(1, "FAIL: associative array self-check");
$display("PASS: sparse entries counted, keys walked in order, OOO tags all retired");
$finish;
end
endmodulePASS: sparse entries counted, keys walked in order, OOO tags all retiredRun with Verilator 5.048 (--binary --timing) on 2026-10-18; every bench printed the PASS line shown, with no warnings. Icarus 13 cannot run this bench (no associative arrays with an int index).