Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here6
  • SystemVerilog Language Foundations28
    • SystemVerilog Data Types
    • Arrays: Packed, Dynamic, Queues & Associative
      • Packed vs Unpacked Arrays
      • Dynamic Arrays and Queues
      • Associative Arrays
      • Array Methods and Selection
    • Procedural Code & Scheduling
    • Operators, Expressions & Width Rules
    • Packages, Typedefs & Compilation
    • Interfaces, Modports & Clocking Blocks
  • Object-Oriented SystemVerilog for Verification30
  • Constraint Randomization — Interview-Critical Deep Dive36
  • SystemVerilog Assertions — Interview-Critical Deep Dive40
  • Functional Coverage in SystemVerilog22
  • Testbench Architecture in SystemVerilog23
  • Advanced Language & Integration25
  • Senior & Interview Preparation21
  • Principal-Level Verification28
Loading lesson

‹Arrays: Packed, Dynamic, Queues & Associative
  1. Topics
  2. SystemVerilog
  3. SystemVerilog Language Foundations
  4. Arrays: Packed, Dynamic, Queues & Associative
  5. Associative Arrays

Part 1 · Language Foundations · Intermediate

Associative Arrays

Sparse hash-like arrays keyed by value, ordered traversal and out-of-order checkers.

On this page

    Why this matters

    A memory model returned 0 for an address the test had never written, and the test treated it as data. Sparse storage is the right model for big address spaces, but only with exists() guarding the reads.

    After this lesson you can: store and read a sparse map, guard reads with exists(), walk keys in order, and match out-of-order responses by ID.

    Storage per written key

    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

    exists(k)

    1 when an entry for k is allocated, else 0. Guard reads with it.

    num()

    Count of allocated entries.

    delete(k)

    Remove one entry.

    delete()

    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.

    Walking keys in order

    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.

    Only written keys exist, and traversal visits them in key order.

    An out-of-order checker

    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, push_back and pop_front

    Arrival order

    Out-of-order responses

    Associative array keyed by ID

    Tag lookup, then delete(id)

    Common mistakes

    Common pitfall

    Reading mem[addr] without exists() consumes the default value as if it were real data. Guard first: the required warning is easy to miss in a long log and does not stop the test.

    Interview

    Likely question: “queue or associative checker?”

    • In-order DUT: queue of predictions, FIFO compare, residual size check.

    • Out-of-order DUT: associative array keyed by ID, per-tag compare and delete.

    • End of test: leftovers mean dropped transactions in both styles.

    Check yourself 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.

    Check yourself A checked response is compared but its entry is never deleted. What breaks?

    The checker leaks the entry and the end-of-test drain check wrongly reports a missing response.

    Testbench

    Predict first The bench writes keys 100, 5 and 7000, in that order. In what order does the 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.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    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
    endmodule
    Terminal
    PASS: sparse entries counted, keys walked in order, OOO tags all retired

    Remember

    • Only written keys use storage; reading a missing key returns the default and adds nothing.

    • first, next, last and prev walk keys in sorted order and return 0 at the end.

    • Out-of-order checking stores predictions by ID and deletes each one when matched.

    Rule of thumb: guard with exists(), delete what you matched, and expect an empty array at the end.

    How this was checked

    Run 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).

    Related topics

    • Next: Array Methods and Selection

    Previous

    14 / 259

    Next: Array Methods and Selection

    Up next

    Up nextArray Methods and Selection

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Packed vs Unpacked Arrays