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 Foundations35
  • Object-Oriented SystemVerilog for Verification35
  • Constraint Randomization — Interview-Critical Deep Dive66
  • SystemVerilog Assertions — Interview-Critical Deep Dive63
  • Functional Coverage in SystemVerilog28
  • Testbench Architecture in SystemVerilog38
  • Advanced Language & Integration30
  • Senior & Interview Preparation34
    • Senior Debug Playbook
    • Reviewing Verification Code
    • Verification Strategy: Spec to Sign-Off
    • SystemVerilog Interview Q&A Bank
      • Q&A: Language Core
      • Q&A: Arrays & Queues
      • Q&A: Testbench Plumbing
      • Q&A: Classes and Interfaces
      • Q&A: OOP
      • Q&A: Constraint and Assertion Fire-Drill
      • Q&A: Interfaces & Virtual Interfaces
      • Q&A: Processes & IPC
      • Q&A: Scenario and Design Questions
      • Q&A: Constraints Quick-Fire
      • Q&A: SVA Quick-Fire
    • Capstone: Verify a Parameterized FIFO
  • Principal-Level Verification28
Loading lesson

‹SystemVerilog Interview Q&A Bank
  1. Topics
  2. SystemVerilog
  3. Senior & Interview Preparation
  4. SystemVerilog Interview Q&A Bank
  5. Q&A: Arrays & Queues

Part 8 · Senior & Interview Prep · Intermediate

Q&A: Arrays & Queues

Dynamic vs associative vs queue selection, queue as FIFO, array-method one-liners, ref passing, and memory implications.

On this page

    Q: Dynamic array vs associative array vs queue — how do you choose?

    By access pattern. Dynamic array — size known at runtime, contiguous indices, fixed once allocated: a data payload. Associative array — sparse or non-integer keys, allocated per element on first write: a memory model keyed by address. Queue — ordered collection that grows and shrinks at the ends: anything FIFO-ish, which is most of a testbench (scoreboards, generators, monitors).

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    byte payload[];                  // dynamic: payload = new[len];
    logic [31:0] mem [logic [31:0]]; // associative: sparse 4GB space, 
                                     //   only written addresses use memory
    fifo_txn sb_q[$];                // queue: push_back / pop_front
    
    // the selection table:
    // known size, dense     → dynamic
    // sparse / keyed        → associative   (mem.exists(addr) before read!)
    // ordered, grow/shrink  → queue

    Follow-up: "What does reading a non-existent associative element return?" — The type's default (0 for 2-state, X for 4-state) and it does NOT allocate — but reading a non-existent element through a [] on the left side does. Guard reads with exists().

    Junior vs senior: a junior defines each type. A senior answers with the access-pattern decision rule and volunteers the exists() guard unprompted.


    Q: How do you model a FIFO with a queue?

    Push at one end, pop at the other: push_back() to enqueue, pop_front() to dequeue gives in-order FIFO semantics. This two-method pattern is the reference model inside virtually every scoreboard: expected transactions go in on the input monitor, and get popped and compared on the output monitor.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    fifo_txn exp_q[$];
    
    // input monitor side
    function void write_in(fifo_txn t);
      exp_q.push_back(t);
    endfunction
    
    // output monitor side
    function void write_out(fifo_txn t);
      fifo_txn exp;
      if (exp_q.size() == 0) begin
        $error("DUT output with no expected txn (spurious pop?)");
        return;
      end
      exp = exp_q.pop_front();
      if (!t.compare(exp)) $error("Mismatch: exp %p got %p", exp, t);
    endfunction

    Follow-up: "How do you make it a LIFO? Bounded?" — LIFO: pop_back() instead of pop_front(). Bounded: declare fifo_txn q[$:7] for max 8 entries — pushes beyond the bound are silently discarded, so in a model you should check size() yourself and flag overflow instead of relying on the bound.

    Junior vs senior: a junior shows push/pop. A senior shows it inside the scoreboard pattern with the empty-queue guard, and knows bounded queues drop silently.


    Q: Give me array-method one-liners — find all errors, sum a field, unique values, sort by field.

    Array locator and ordering methods with the with clause replace hand-written loops. They are interview favorites because each is one line — and because the return types trip people: locator methods return a queue (possibly empty), never a scalar.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    fifo_txn q[$];
    
    fifo_txn errs[$]   = q.find with (item.is_err);          // all matches
    int      idxs[$]   = q.find_index with (item.len > 8);   // their indices
    int      total     = q.sum  with (int'(item.len));       // accumulate field
    fifo_txn big[$]    = q.max  with (item.len);             // queue of size 0/1!
    int      lens[$]   = q.unique with (item.len);           // dedupe by field
    q.sort  with (item.addr);                                // in-place ascending
    q.rsort with (item.len);                                 // descending
    q.shuffle();                                             // random order
    
    // trap: max/min/unique return QUEUES — guard before [0]
    if (big.size()) $display("longest = %0d", big[0].len);

    Follow-up: "What does sum return on an empty queue, and what type?" — 0, with the type of the with expression — which is why the int cast matters: summing a 4-bit field without it wraps at 16.

    Junior vs senior: a junior writes the loop. A senior writes the one-liner, casts inside sum, and guards the queue-returning methods before indexing.


    Q: How do you pass a large array to a function — and why ref?

    By default SystemVerilog passes arguments by value — the entire array is copied on every call. For a megabyte memory image that is a megabyte memcpy per call. ref passes a reference (no copy, callee can modify the original); const ref gives the no-copy benefit while making the array read-only — the right default for checkers and printers.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // copies 1MB per call — slow, and writes don't reach the caller
    function automatic int count_bad(byte img[]);
      ...
    endfunction
    
    // no copy, read-only — preferred for analysis
    function automatic int count_bad_fast(const ref byte img[]);
      ...
    endfunction
    
    // no copy, writable — for in-place modification
    function automatic void scrub(ref byte img[]);
      foreach (img[i]) if (img[i] == 8'hXX) img[i] = 0;
    endfunction
    // note: ref requires the subroutine to be automatic

    Follow-up: "Does this apply to passing objects?" — Class handles are already references; passing a handle by value copies only the handle (pointer), not the object. ref on a handle means the callee can reassign which object the caller's handle points to.

    Junior vs senior: a junior knows ref avoids the copy. A senior reaches for const ref by default, knows ref requires automatic, and can contrast array copying with handle copying.


    Q: What are the memory implications of each array type?

    Fixed and dynamic arrays allocate contiguously for all elements, used or not. Associative arrays allocate per element on demand plus key-lookup overhead — the only sane way to model a 4GB address space in which a test touches a few kilobytes. Queues allocate to current size and grow as needed; an unbounded queue that is pushed but never popped is the classic testbench memory leak.

    Diagram
    MODELING A 4GB MEMORY
    
      logic [7:0] mem [0:4294967295];   // fixed: 4GB allocated → sim dies
      logic [7:0] mem [logic [31:0]];   // assoc: KBs for touched bytes only
    
      THE QUEUE LEAK
      monitor ──push_back──► sb.exp_q ──pop_front──► compare
                                │
            output path dead? pops never happen →
            exp_q grows for the whole sim → memory climbs, sim crawls
      defense: end-of-test check  if (exp_q.size()) $error("leftover");
               + periodic watermark check on long-running tests

    Follow-up: "Your regression slows down over hours and memory climbs — first suspects?" — Unpopped scoreboard queues and unbounded mailbox growth: a checker path that stopped consuming. Watermark-print queue sizes periodically; the leaking one is obvious.

    Junior vs senior: a junior compares allocation strategies abstractly. A senior maps each to its testbench use case and names the unpopped-queue leak with its end-of-test defense.

    Key takeaways

    • Select by access pattern: dense→dynamic, sparse/keyed→associative, ordered grow/shrink→queue.

    • push_back + pop_front is the scoreboard FIFO idiom; guard empty before popping.

    • Locator methods return queues — guard size() before [0]; cast inside sum.

    • const ref for large read-only arrays; ref requires automatic subroutines.

    • End-of-test queue-empty checks catch both checker leaks and lost transactions.

    Common pitfalls

    • Reading associative entries without exists() — default values masquerade as data.

    • q.max(...)[0] on an empty result queue — index of nothing.

    • Passing big arrays by value in hot paths — invisible megabyte copies per call.

    • Unbounded queues with a dead consumer — the slow-leak sim that dies at hour six.

    Previous

    319 / 363

    Next: Q&A: Testbench Plumbing

    Up next

    Up nextQ&A: Testbench Plumbing

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Q&A: Classes and Interfaces
    • Q&A: OOP
    • Q&A: Constraint and Assertion Fire-Drill