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
  • Object-Oriented SystemVerilog for Verification30
    • Classes, Objects & Handles
    • Inheritance and Polymorphism
    • Static, Encapsulation & Parameterized Classes
    • Virtual Interfaces: the Class-to-Pins Bridge
    • Events, Semaphores, Mailboxes and Processes
      • Events: Edge Waits and Level Fixes
      • Semaphores: One Key and Many
      • Mailboxes: Typed Pipes with Backpressure
      • Threads and Control: Fork, Disable, Wait, Process
      • Synchronization Patterns: Handshakes, Phasing and Objections
      • Lab: Generator-to-Driver Handshake
    • OOP Patterns for Testbenches
  • 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

‹Events, Semaphores, Mailboxes and Processes
  1. Topics
  2. SystemVerilog
  3. Object-Oriented SystemVerilog for Verification
  4. Events, Semaphores, Mailboxes and Processes
  5. Synchronization Patterns: Handshakes, Phasing and Objections

Part 2 · OOP for Verification · Intermediate

Synchronization Patterns: Handshakes, Phasing and Objections

Completion counters, objections with drain, races, barriers and agents.

On this page

    Why this matters

    A test ended while the last packet was still inside the DUT. The count of busy components touched zero for one cycle between two stages, and the bench took that as done.

    After this lesson you can: end a test on a completion count or objections with a drain window, and order processes with a barrier.

    Completion without guessing

    A generator returning means items were accepted, not driven. Three closers exist: a bounded mailbox whose last put means acceptance, a per-item done flag, and a completion counter the env waits on.

    The counter is the most explicit: the driver increments items_driven per item and the env waits for items_driven == items_generated. It serves any number of observers and doubles as an end-of-test statistic.

    Signal

    Means

    Cost

    Bounded mailbox put returning

    Driver accepted the item

    Cheapest, slightly loose

    Per-item done flag

    Driver finished the item

    One flag plus a wait

    Completion counter

    Exact driven count

    Most wiring, most precise

    Objections with drain

    End of test evolves from #delay guesswork to transaction counts to objections. Any component raises while busy and drops when idle, and the test ends when the count stays zero through a drain window.

    No lock is needed around the counter. raise and drop are functions, so they cannot block, and in practice no other process interleaves with them (a function cannot call the task semaphore::get anyway). What makes the scheme sound is one envelope objection that spans each run, so inter-item gaps never read as done, plus the drain window.

    Zero for an instant is not the end: only a quiet drain window is.

    The class below keeps the count; the bench after it proves the envelope plus drain idea with plain counters.

    top_obj.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
    timeunit 1ns;      // one time unit for the classes and the module
    timeprecision 1ps;
    
    class objection_c;
      local int unsigned count;
      local string holders[$];
      local time drain_time = 100;
      function void raise(string who);   // runs to completion: no lock needed
        count++;
        holders.push_back(who);
      endfunction
      function void drop(string who);
        int at = -1;
        count--;
        for (int i = 0; i < holders.size(); i++)
          if (holders[i] == who) at = i;
        if (at >= 0) holders.delete(at);
      endfunction
      task wait_for_done();
        forever begin
          wait (count == 0);
          #drain_time;
          if (count == 0) return;
        end
      endtask
    endclass
    module top_obj;
      objection_c obj = new();
      initial begin
        obj.raise("gen");
        #50;
        obj.drop("gen");
        obj.wait_for_done();
        if ($time != 150) $fatal(1, "FAIL: test ended at %0d ns, not after the 100 ns drain", $time);
        $display("PASS: drop at 50 plus a 100 ns drain ended the test at 150");
        $finish;
      end
    endmodule
    Terminal
    PASS: drop at 50 plus a 100 ns drain ended the test at 150

    Races, order and barriers

    One mailbox is FIFO, but puts from two producers in one timestep interleave by scheduler choice. Order across two mailboxes is never guaranteed, so a scoreboard must pend the early side by id instead of assuming expected beats actual.

    Signal drives from testbench code need nonblocking assignment at clock edges, ideally through a clocking block. A blocking drive in the same timestep as a raw sampler leaves the outcome to scheduler order.

    The barrier below makes two processes wait for each other. Its generation counter is the point: it keeps round two from corrupting round one.

    top_barrier.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
    class barrier_c;
      local int unsigned parties;
      local int unsigned arrived;
      local int unsigned generation;
      function new(int unsigned n);
        parties = n;
      endfunction
      task arrive_and_wait();
        int unsigned my_gen = generation;
        arrived++;
        if (arrived == parties) begin
          arrived = 0;
          generation++;
        end
        else wait (generation != my_gen);
      endtask
    endclass
    module top_barrier;
      timeunit 1ns;
      timeprecision 1ps;
      barrier_c b = new(2);
      time first_released;
      initial begin
        fork
          begin b.arrive_and_wait(); first_released = $time; end
          begin #5; b.arrive_and_wait(); end   // second party arrives late
        join
        if (first_released != 5) $fatal(1, "FAIL: first party left at %0d ns, before the second arrived", first_released);
        $display("PASS: barrier held the first party until the second arrived at 5");
        $finish;
      end
    endmodule
    Terminal
    PASS: barrier held the first party until the second arrived at 5

    Common pitfall

    Ending the instant the count hits zero ends the test inside a raise gap between producer and consumer. Always wait out a drain window and re-check, and keep a watchdog running beside every end-of-test scheme.

    Interview

    Likely questions: how the generator knows the driver finished, and how two masters collide on an arbiter in one window.

    • Name acceptance versus completion, then pick the counter.

    • A barrier at the scenario step boundary, never delay tuning.

    • One shared objection counter globally, per-agent counts in the report.

    Check yourself The objection count hits zero between the generator drop and the driver raise. Why must end of test wait longer?

    That gap is normal pipelining, not idleness. A drain window with a re-check lets late consumers re-raise before the finish decision.

    Check yourself A scoreboard reads expected then actual from two mailboxes and compares pairwise. What breaks?

    Arrival order across two boxes is unordered. When actual arrives first the pairing slips, so each side needs its own thread plus pending state keyed by id.

    Testbench

    Four items with per-item raise and drop under one envelope, then a drain window with a re-check.

    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
    module tb_objection_envelope;
      timeunit 1ns;
      timeprecision 1ps;
      int envelope = 1;   // raised before time 0, so the checker cannot see a false idle
      int outstanding = 0;
      int completed = 0;
      int errors = 0;
      initial begin : producer
        envelope = 1;
        repeat (4) begin
          wait (outstanding == 0);
          outstanding++;
          #5;
        end
        envelope = 0;
      end
      initial begin : consumer
        repeat (4) begin
          wait (outstanding == 1);
          #10;
          outstanding--;
          completed++;
        end
      end
      initial begin
        wait (envelope == 0 && outstanding == 0);
        #30;
        if (envelope !== 0 || outstanding !== 0) errors++;
        if (completed !== 4) errors++;
        if (errors == 0) $display("PASS: 4-item run ended by envelope plus drain");
        else $fatal(1, "FAIL: completed=%0d envelope=%0d", completed, envelope);
        $finish;
      end
    endmodule
    Terminal
    PASS: 4-item run ended by envelope plus drain

    Remember

    • A generator returning means items were accepted, not driven.

    • Raise while busy, drop when idle, and finish only after a quiet drain window.

    • Order across two mailboxes is not guaranteed: match by identity.

    Rule of thumb: zero for an instant is not done; zero for a window is.

    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. These class benches were run on Verilator only.

    Related topics

    • Next: Lab: Generator-to-Driver Handshake

    Previous

    58 / 259

    Next: Lab: Generator-to-Driver Handshake

    Up next

    Up nextLab: Generator-to-Driver Handshake

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Events: Edge Waits and Level Fixes
    • Semaphores: One Key and Many
    • Mailboxes: Typed Pipes with Backpressure