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
    • Procedural Code & Scheduling
    • Operators, Expressions & Width Rules
    • Packages, Typedefs & Compilation
    • Interfaces, Modports & Clocking Blocks
      • Interface Basics
      • Modports
      • Clocking Blocks
      • Interface BFM
  • 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

‹Interfaces, Modports & Clocking Blocks
  1. Topics
  2. SystemVerilog
  3. SystemVerilog Language Foundations
  4. Interfaces, Modports & Clocking Blocks
  5. Clocking Blocks

Part 1 · Language Foundations · Intermediate

Clocking Blocks

Sample with 1step skew, drive with output skew, and keep one path per signal.

On this page

    Why this matters

    The bench saw the grant a cycle early on one simulator and on time on another. It sampled at the clock edge itself, racing the flop that produced the grant.

    After this lesson you can: declare a clocking block, explain #1step input and output skew, wait for clocking events, and keep one path per signal.

    The race a clocking block kills

    A testbench that drives DUT inputs with blocking assignments right at @(posedge clk) races the DUT flops that sample them. Both run on the same edge, and either may run first, so the DUT captures the old value or the new one depending on simulator ordering. Reading a nonblocking flop output at the edge is not a race: it always returns the old value, because the update waits for the NBA region.

    A clocking block fixes both directions by construction. Inputs sample with an input skew; the default #1step means the value just before the edge, untouched by anything the edge triggers.

    Outputs drive with an output skew after the edge. They land like real stimulus arriving between clocks and never collide with same-edge DUT sampling.

    Sample just before the edge, drive just after it, and the bench never races the DUT.
    Predict first A flop's output q changes at this clock edge. Testbench code samples q with input skew #1step, and other code with skew #0. Which one sees the old value?

    The #1step sample sees the old value, from just before the edge. The #0 sample is taken after this edge's updates, so it sees the new value: one cycle earlier than the DUT's own flops would.

    A zero input skew samples in the Observed region, after this edge's flops have updated, so the bench sees the new value and is off by one cycle. A fixed time skew couples the bench to a clock period. 1step is one time-precision unit before the edge: the last stable value of the previous cycle, independent of the period.

    Declare the block, use its idioms

    The interface declares the block once; testbench code then drives and samples through bus.cb. Many codebases also export the block through a modport (modport tb (clocking cb)); this course uses it on the interface instance instead.

    bus_if.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    interface bus_if (input logic clk);
      timeunit 1ns; timeprecision 1ps;
      logic        req, gnt;
      logic [31:0] addr;
      logic [63:0] rdata;
    
      clocking cb @(posedge clk);
        default input #1step output #2;
        output req, addr;        // testbench drives these (through cb)
        input  gnt, rdata;       // testbench samples these (through cb)
      endclocking
    endinterface

    Idiom

    Meaning

    @(bus.cb)

    Wait for the clocking event itself, cleaner than a raw edge.

    bus.cb.gnt

    The sampled pre-edge value, stable even mid-cycle.

    bus.cb.req <= v

    A drive landing after the edge, seen cleanly at the next one.

    Raw bus.req

    The bypass path. Never drive it beside the clocking block.

    The waveform shows the handshake's timing: the requester drives req 1 ns after an edge, like a small output skew, and the one-flop responder captures it at the next edge.

    • Slot 0: req is 0 and gnt is 0. The bus is idle.

    • Slot 1: req is 1, driven just after the slot-1 edge (output skew). gnt is still 0: the flop has not seen it yet.

    • Slot 2: the slot-2 edge captures req, so gnt rises. req drops just after that edge.

    • Slot 3: req is 0 and gnt is 0. The grant drops one cycle after the request.

    Drive and sample each signal through exactly one path. A raw drive beside a clocking drive reintroduces the race and the two drives fight.

    Count cycles, not nanoseconds

    The ##N operator counts N events of the scope's default clocking. With no default clocking in scope, there is nothing to count, so this lesson waits with explicit clocking events instead.

    repeat (2) @(bus.cb) waits two clocking events with no extra declaration. The testbench below uses it.

    Common mistakes

    Common pitfall

    Mixing a raw-signal drive with a clocking-block drive on the same signal brings back the original race on top of a drive conflict. Pick the clocking block and delete the raw path.

    Interview

    “What does input #1step mean?” Sampling in the Preponed region: the stable value immediately before the edge. “Why does that remove races?” Pre-edge sampling plus post-edge driving means the bench and the DUT never touch a signal in the same region.

    Check yourself What does input #1step sample, and why not #0?

    1step samples the Preponed value just before the edge. A zero skew samples in the Observed region, after this edge's flops have updated, so the bench would see the new value one cycle early.

    Check yourself One signal is driven through both bus.cb.req and raw bus.req. What breaks?

    Both paths. The drives conflict and the sampling race returns. Keep one path per signal.

    Check yourself No default clocking is declared. What does ##2 mean?

    Nothing legal: the operator counts events of the scope's default clocking, and none exists. Wait with explicit @(bus.cb) instead.

    Testbench

    A one-flop grant, four request pulses driven through bus.cb, and a count of the grants sampled through it.

    bus_if.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
    interface bus_if (input logic clk);
      timeunit 1ns; timeprecision 1ps;
      logic req, gnt;
      clocking cb @(posedge clk);
        default input #1step output #2;
        output req;                       // the testbench drives req through cb
        input  gnt;                       // and samples gnt through cb
      endclocking
      modport dut (input req, output gnt, input clk);
    endinterface
    
    module dut (bus_if.dut bus);
      timeunit 1ns; timeprecision 1ps;
      always_ff @(posedge bus.clk)
        bus.gnt <= bus.req;               // one-flop grant
    endmodule
    
    module tb_top;
      timeunit 1ns; timeprecision 1ps;
      logic clk = 0;
      always #5 clk = ~clk;
      bus_if bus (.clk(clk));
      dut    u_dut (.bus(bus));
      int seen = 0;
      initial begin
        bus.cb.req <= 1'b0;
        repeat (2) @(bus.cb);             // count clocking events, not nanoseconds
        for (int i = 0; i < 4; i++) begin
          bus.cb.req <= 1'b1;             // lands 2 ns after this edge
          @(bus.cb);                      // the DUT captures req at this edge
          bus.cb.req <= 1'b0;
          @(bus.cb);                      // sampled just before this edge: gnt is 1
          if (bus.cb.gnt === 1'b1) seen++;
          $display("pulse %0d at %0d ns: sampled gnt=%b", i, $time, bus.cb.gnt);
        end
        if (seen == 4) $display("PASS: clocking-block sampling counted all 4 grants");
        else $fatal(1, "FAIL: counted %0d of 4 grants", seen);
        $finish;
      end
    endmodule
    Terminal
    pulse 0 at 35 ns: sampled gnt=1
    pulse 1 at 55 ns: sampled gnt=1
    pulse 2 at 75 ns: sampled gnt=1
    pulse 3 at 95 ns: sampled gnt=1
    PASS: clocking-block sampling counted all 4 grants

    Every drive goes through bus.cb, so it lands 2 ns after the edge and never at the DUT's sampling instant, and every read is the pre-edge sample.

    Remember

    • input #1step samples the value just before the edge.

    • output #N drives N time units after the edge.

    • Drive and sample each signal through one path only.

    Rule of thumb: sample before the edge, drive after it.

    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: it has no clocking blocks.

    Verilator 5.048 does not accept a clocking block reached through a modport, so the lesson uses the block on the interface instance.

    The waveform comes from a plain-Verilog model of the same handshake, drawn with Icarus 13.

    Related topics

    • Next: Interface BFM

    Previous

    33 / 259

    Next: Interface BFM

    Up next

    Up nextInterface BFM

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Interface Basics
    clkreqgnt
    A registered grant follows its request by one cycle