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
    • SystemVerilog Data Types
    • Arrays: Packed, Dynamic, Queues & Associative
      • Packed vs Unpacked Arrays
      • Dynamic Arrays and Queues
      • Associative Arrays
      • Queues
      • Array Methods and Selection
      • Choosing the Right Array Type
    • Procedural Code & Scheduling
    • Operators, Expressions & Width Rules
    • Packages, Typedefs & Compilation
    • Interfaces, Modports & Clocking Blocks
  • 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
  • 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. Queues

Part 1 · Language Foundations · Intermediate

Queues

push/pop at both ends, insert/delete, $-slicing, bounded queues, and queues as scoreboard FIFOs.

On this page

    A variable-size array optimized for the ends

    A queue is declared with [$] — int q[$] — and is the container to reach for whenever elements arrive and leave over time. The simulator implements it so that adding or removing at either end is O(1), while insertion in the middle costs O(n). Unlike a dynamic array there is no allocation step: a queue starts empty and grows one element per push. The special index $ means “last element”, so q[$] is the back and q[0] the front.

    queue_demo.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    module queue_demo;
      int q[$];
    
      initial begin
        q.push_back(10);       // {10}
        q.push_back(20);       // {10, 20}
        q.push_front(5);       // {5, 10, 20}
        q.insert(1, 7);        // {5, 7, 10, 20}  insert before index 1
        q.delete(2);           // {5, 7, 20}      remove index 2
    
        $display("front=%0d back=%0d size=%0d", q[0], q[$], q.size());
    
        while (q.size() > 0)
          $display("popped %0d", q.pop_front()); // FIFO drain order
    
        // Slicing: q[1:$] is everything but the front element
        q = {1, 2, 3, 4, 5};
        q = q[1:$];            // drop front -> {2,3,4,5}
        q = q[0:$-1];          // drop back  -> {2,3,4}
        q = {q, 99};           // concat-append -> {2,3,4,99}
        q.delete();            // empty the whole queue
      end
    endmodule

    Bounded queues and slicing semantics

    Declaring int q[$:7] creates a bounded queue with maximum index 7, i.e. at most 8 elements. Pushing beyond the bound is ignored and the simulator issues a warning — it does not error out, which makes silent data loss the real hazard. Bounded queues are useful for modeling hardware FIFOs with real depth limits, but most testbench queues are unbounded and rely on the consumer keeping up. Slices like q[1:$] produce a new queue value; combined with concatenation they give you list surgery without explicit loops.

    Diagram
    QUEUE OPERATIONS AND COSTS
    
                push_front / pop_front          push_back / pop_back
                      O(1)                              O(1)
                       │                                 │
                       ▼                                 ▼
      front ──► ┌─────┬─────┬─────┬─────┬─────┬─────┐ ◄── back
                │q[0] │q[1] │q[2] │ ... │     │q[$] │
                └─────┴─────┴──┬──┴─────┴─────┴─────┘
                               │
                      insert(i,v) / delete(i)
                          O(n) — shifts elements
    
      bounded:  int q[$:7];   push #9 → DROPPED + warning (no error!)
      slicing:  q[1:$]  drop front     q[0:$-1]  drop back

    Queues as testbench FIFOs — the scoreboard pattern

    The single most important queue idiom is the scoreboard expected queue: the reference side pushes predicted transactions with push_back, the monitor side pops with pop_front, and in-order checking falls out of FIFO discipline. At end of test, a non-empty queue means the DUT dropped transactions — so checking residual size() is part of the pattern, not an afterthought. Interviewers often ask exactly this: “how would you check in-order data integrity through a DUT?” and expect the two-queue (or queue-per-stream) answer.

    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
    class scoreboard;
      packet expected_q[$];   // reference model pushes here
    
      function void write_expected(packet p);
        expected_q.push_back(p);
      endfunction
    
      function void write_actual(packet p);
        packet exp;
        if (expected_q.size() == 0) begin
          $error("Unexpected packet: DUT produced output with nothing predicted");
          return;
        end
        exp = expected_q.pop_front();   // in-order check
        if (!exp.compare(p))
          $error("Mismatch: exp=%s act=%s", exp.convert2string(), p.convert2string());
      endfunction
    
      function void check_drained();
        if (expected_q.size() != 0)
          $error("%0d expected packets never seen at DUT output",
                 expected_q.size());
      endfunction
    endclass

    Key takeaways

    • Queues grow per push — no new[] step; O(1) at both ends, O(n) in the middle.

    • $ indexes the last element; q[1:$] and concatenation give loop-free list surgery.

    • Bounded queues ([$:N]) silently drop overflow pushes with only a warning.

    • push_back / pop_front is the canonical in-order scoreboard FIFO — check residual size() at end of test.

    Common pitfalls

    • pop_front() on an empty queue — returns the default value plus a warning, easy to misread as real data.

    • Using insert/delete in hot loops — each call shifts elements, turning O(1) FIFO code into O(n²).

    • Forgetting the end-of-test drain check — dropped DUT transactions go unnoticed.

    • Deleting elements inside a foreach over the same queue — iteration indices shift under you.

    Previous

    16 / 363

    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
    • Dynamic Arrays and Queues
    • Choosing the Right Array Type