Recap: What Is a Skew?

Part 1 covered how a clocking block lets you group signals under a clock and apply timing rules to them. Those timing rules are called skews. This article goes deep on how input and output skews work, what #1step and #0 actually mean in simulation, and how to read the waveforms that result.

Here's the key intuition before looking at any code:

  • Input skew — how far before the clock edge the signal is sampled (read)
  • Output skew — how far after the clock edge the signal is driven (written)

Think of it like a daily 9:00 am team meeting. An input skew of 2ns is like taking notes on what's happening at 8:58 am — just before the meeting. An output skew of 5ns is like sending out your decisions 5 minutes after the meeting starts. The clock edge is the meeting; the skew controls when you observe or act.

10 min read | Intermediate Level

What You'll Learn

  • How to specify a skew explicitly per signal, and what bare numbers versus explicit time units mean
  • Exactly what #1step means, and why it's the safest input skew
  • Exactly what output #0 (or no skew at all) means, and why it isn't literally "zero delay"
  • How different output skews change when a signal is actually driven relative to the clock edge
  • How different input skews change what value gets captured when a signal is changing rapidly

Skew Syntax

Skews can be specified as a default for all signals in the block, or per-signal to override the default. They can also be given in explicit time units (like #2ns, #1ps) or as bare numbers that follow the active timescale:


clocking cb @(posedge clk);
  input  #1ps  req;     // sample 1 ps before posedge clk
  output #2    gnt;     // drive 2 time-units after posedge clk (follows timescale)
  input  #1 output #3 sig;  // inout: sample 1 unit before, drive 3 units after
endclocking

For the bare #2 on gnt: if the active timescale is 1ns/1ps, then #2 means 2ns. If it were 1ps/1ps, it would mean 2ps.

Always check your active timescale when using bare numbers for skew — the same #2 can mean very different things depending on what's in effect for that file.

The Special Case: #1step

#1step is the most commonly used input skew, and it's worth understanding precisely. Instead of specifying a time offset, it tells the simulator to sample the signal in the Preponed region of the current time step. This region runs before any active logic evaluates at that time — which means you're guaranteed to read the value the signal had at the end of the previous time step, before any new changes take effect.

In practical terms: #1step gives you the last stable, settled value of the signal just before the clock edge fires. It's the safest choice for inputs because it completely sidesteps race conditions.


clocking cb @(posedge clk);
  input #1step req;   // sample req in the Preponed region — always safe
endclocking

The Special Case: Output with #0 or No Skew

Outputs declared with #0 or with no skew at all are driven in the Re-NBA region of the same time step as the clock edge. The Re-NBA region runs after all non-blocking assignments in the design have settled. This ensures your testbench output doesn't interfere with what the design is computing on the same edge. It isn't simply "zero nanoseconds" — it's a deliberate scheduling position that keeps testbench and design updates separated.

A Complete Example

Let's build a working example step by step. The design is a simple request-grant module: it asserts gnt as soon as it sees req high.


module des (input logic req, clk, output logic gnt);
  always @(posedge clk)
    gnt <= req ? 1 : 0;
endmodule

The interface bundles the signals and the clocking block together. Here, gnt gets an input skew of 1ns (sampled 1ns before the positive edge), and req gets an output skew of 5ns (driven 5ns after the positive edge):


interface req_gnt_if (input logic clk);
  logic gnt;
  logic req;

  clocking cb @(posedge clk);
    input  #1ns gnt;   // sample gnt 1ns before posedge
    output #5ns req;   // drive  req 5ns after  posedge
  endclocking
endinterface

The testbench drives req through the clocking block and then reads back gnt:


module tb;
  logic clk;

  initial clk = 0;
  always #10 clk = ~clk;   // 20ns period, posedge at 10, 30, 50 ...

  logic req_val = 0;       // testbench copy of req: an output clockvar cannot be read back

  req_gnt_if vif (.clk(clk));
  des dut (.clk(clk), .req(vif.req), .gnt(vif.gnt));

  initial begin
    vif.cb.req <= req_val;

    // Toggle req several times at random intervals
    repeat (5) begin
      repeat ($urandom_range(1,3)) @(vif.cb);
      req_val = ~req_val;
      vif.cb.req <= req_val;
    end

    #40 $finish;
  end
endmodule

From a waveform viewer, you'd see that every time the testbench assigns vif.cb.req, the actual change on the wire appears 5ns after the positive clock edge — not at the edge itself. This is the output skew at work.

Quick Check

Q: In the example above, if the testbench had written vif.req <= ~vif.req instead of going through vif.cb.req, when would the actual change appear on the wire?

Show Answer

Immediately, at whatever simulation time that statement executes — with no relationship to the clock edge at all. Bypassing the clocking block means the 5ns output skew is never applied.

Output Skew in Detail

To make output skew very visible, here's an interface with three clocking blocks that all share the same clock but each have a different output skew:


interface _if (input logic clk);
  logic req;

  clocking cb_0 @(posedge clk);
    output #0ns req;    // driven in Re-NBA region (same edge)
  endclocking

  clocking cb_1 @(posedge clk);
    output #2ns req;    // driven 2ns after posedge
  endclocking

  clocking cb_2 @(posedge clk);
    output #5ns req;    // driven 5ns after posedge
  endclocking
endinterface

cb_0, cb_1, and cb_2 are all declared on the exact same @(posedge clk) event, side by side — SystemVerilog allows multiple clocking blocks to reference the same clock event, each maintaining its own independent skew, which is exactly what this example (and the next one) relies on to demonstrate different skews side by side.

The testbench iterates through three rounds, using a different clocking block each time to assert req:


module tb;
  logic clk;

  initial clk = 0;
  always #10 clk = ~clk;

  _if vif (.clk(clk));

  initial begin
    for (int i = 0; i < 3; i++) begin
      repeat (2) @(vif.cb_0);   // wait 2 posedges

      case (i)
        0: vif.cb_0.req <= 1;   // driven at Re-NBA of posedge
        1: vif.cb_1.req <= 1;   // driven 2ns after posedge
        2: vif.cb_2.req <= 1;   // driven 5ns after posedge
      endcase

      repeat (2) @(vif.cb_0);
      vif.req <= 0;             // direct reset between rounds (no clocking block skew intended here)
    end
    #20 $finish;
  end
endmodule

The line vif.req <= 0; bypasses the clocking block intentionally — it's used here only to reset the signal between test rounds, not as part of the timed stimulus. In a real testbench, always drive stimulus through the clocking block; direct assignments skip the skew entirely and should only be used for housekeeping tasks like initialization or reset.

In a waveform viewer, all three rounds show req going high at different offsets from the clock edge: immediately (Re-NBA region), 2ns later, and 5ns later. The clock edge itself is the same in all three cases — only the output skew differs.

Input Skew in Detail

Input skew controls when the clocking block takes a snapshot of a signal. To demonstrate this clearly, we use a design that drives a random 4-bit value every 1ns — so the signal is changing quickly, and different sampling times will capture different values:


module des (output logic [3:0] gnt);
  always #1 gnt <= $random;
endmodule

The interface has four clocking blocks, each sampling gnt at a different point relative to the positive clock edge:


interface _if (input logic clk);
  logic [3:0] gnt;

  clocking cb_0 @(posedge clk);
    input #0     gnt;      // sample at posedge, in Observed region
  endclocking

  clocking cb_1 @(posedge clk);
    input #1step gnt;      // sample in Preponed region (just before posedge)
  endclocking

  clocking cb_2 @(posedge clk);
    input #1ns   gnt;      // sample 1ns before posedge
  endclocking

  clocking cb_3 @(posedge clk);
    input #2ns   gnt;      // sample 2ns before posedge
  endclocking
endinterface

The testbench launches four parallel threads. Each thread waits for its own clocking block's edge and then prints the sampled value:


module tb;
  logic clk;

  initial clk = 0;
  always #5 clk = ~clk;   // 10ns period

  _if    vif (.clk(clk));
  des    dut (.gnt(vif.gnt));

  initial begin
    fork
      begin @(vif.cb_0); $display("cb_0 (#0    ): gnt = 0x%0h", vif.cb_0.gnt); end
      begin @(vif.cb_1); $display("cb_1 (#1step): gnt = 0x%0h", vif.cb_1.gnt); end
      begin @(vif.cb_2); $display("cb_2 (#1ns  ): gnt = 0x%0h", vif.cb_2.gnt); end
      begin @(vif.cb_3); $display("cb_3 (#2ns  ): gnt = 0x%0h", vif.cb_3.gnt); end
    join
    #10 $finish;
  end
endmodule

Example output (exact values depend on the simulation seed):


cb_3 (#2ns  ): gnt = 0x9
cb_2 (#1ns  ): gnt = 0x3
cb_1 (#1step): gnt = 0x3
cb_0 (#0    ): gnt = 0xd

Why are the values different? The design is changing gnt every 1ns. The testbench's clock starts at 0 and first toggles high 5ns later, so the first positive edge actually lands at 5ns, not a round number like 10ns. At that edge:

  • cb_3 samples at 3ns — it sees whatever value was present 2ns before the edge
  • cb_2 samples at 4ns — it sees the value from 1ns before the edge
  • cb_1 samples in the Preponed region at 5ns — it captures the last settled value before any active logic fires at the edge. This is why it gets the same result as cb_2 here: both saw the same stable value set at 4ns, with nothing changing gnt again before 5ns
  • cb_0 samples at the edge itself, in the Observed region — it may catch a value already updated by active events at 5ns, which is why it differs from the others

This illustrates exactly why #1step is preferred for testbench inputs: it's designed to give you the last clean, settled value rather than a potentially mid-update snapshot.

Quick Check

Q: Of the four clocking blocks in this example, which one risks sampling a value the design just updated on the very same clock edge you're trying to synchronize with?

Show Answer

cb_0 (input #0). Sampling in the Observed region happens after active events at that time step have already run, so it can catch a brand-new value from that same edge instead of the stable value from before it — exactly the race condition #1step is designed to avoid.

Choosing the Right Skew: Practical Guidelines

  • For inputs, always prefer #1step unless you have a specific reason to sample at a precise time offset. It gives the cleanest, race-free read.
  • For outputs, use a small positive time value (like #1ns or #2ns) that still satisfies the setup time of the design but clearly places the drive event after the clock edge. Avoid #0 unless you understand the Re-NBA scheduling implications.
  • Match your skew to your clock period. Driving an output #8ns after the edge of a 10ns clock means it arrives very close to the next edge — this could violate setup time on the design side.
  • Use bare numbers with caution. Always confirm the active timescale in the file so you know what a bare #2 actually means.

Where Does Skew Choice Matter in Practice?

Getting output skew right matters most when the design has real setup/hold requirements — a DDR-style interface that samples on both clock edges, or a protocol with a tight setup window, needs its output skew tuned so stimulus arrives comfortably before the design's own sampling point, not right on top of it.

Common Beginner Mistakes

Mistake #1: Driving signals directly instead of through the clocking block

Writing vif.req <= 1 in your testbench bypasses the output skew entirely — the signal is driven at whatever time that line executes, with no guaranteed relationship to the clock edge. Why? Always use vif.cb.req <= 1 for stimulus, or the clocking block's timing guarantees simply don't apply.

Mistake #2: Confusing the direction convention

Remember: input in the clocking block means the testbench reads it (the design drives it). Why? Swapping this causes your testbench to try to drive a signal it declared as input, which results in a compile error or X values.

Mistake #3: Assuming #0 output means "no delay"

It means the Re-NBA region — a specific simulation scheduling point after the design's non-blocking assignments have settled. Why? This makes it safer than a raw #0 delay outside a clocking block, but it's still not literally zero time — it's a protected ordering point, not a timing guarantee of "instantly."

Mistake #4: Using a bare number without checking timescale

output #5 req could mean 5ns or 5ps depending on the file's timescale directive. Why? Use explicit units like #5ns whenever precision matters, so the skew doesn't silently change meaning if the timescale is ever edited.

Practice Exercise

Challenge: Work Out the Sample Time

A clocking block declares input #3ns gnt, and the clock period is 20ns with a posedge at 100ns. At what simulation time is gnt sampled?

Hint

Input skew is always measured as an offset before the clocking event — subtract the skew value from the clock edge time.

Solution

97ns. Input skew is a delay before the clocking event, so 100ns − 3ns = 97ns.

Quick Check

Q: Two clocking blocks are both on posedge clk. One has input #1step gnt, the other has input #0 gnt. The design updates gnt on that same positive edge. Which one is safer to use for reading gnt, and why?

Show Answer

#1step is safer. It reads in the Preponed region, before any active events fire at that edge, so it can't see a partially-updated or brand-new value. #0 reads in the Observed region, after active events, which may already reflect the design's new value from that very same edge.

Summary

  • Input skew is the time before the clock edge at which a signal is sampled — a larger input skew samples further in the past relative to the edge
  • Output skew is the time after the clock edge at which a signal is driven — a larger output skew changes the signal later in the clock cycle
  • #1step is the safest input skew — it samples in the Preponed region, capturing the last stable value before the edge
  • Output #0 is driven in the Re-NBA region — not "zero delay," but a protected scheduling zone after the design's non-blocking assignments
  • Multiple clocking blocks can share the same clock event, each with its own independent skew — useful for observing the same signal at several different timing references at once
  • Always drive testbench signals through the clocking block handle to ensure skew is applied correctly

Continuing the Interfaces & Modports Chapter

Continue with SystemVerilog Modport.

Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5