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

  • RTL Foundations36
  • Combinational Design25
  • Sequential Design27
  • FSM & Control27
  • Timing & Clocking39
  • Reset & CDC34
  • Memory & Buses57
  • Advanced RTL28
  • Senior & Interview76
    • Low-Power RTL Hub
    • DFT and Scan Hub
    • Physical Awareness Hub
    • Waveform Debug Hub
      • Debug hierarchy before touching code
      • Controlled dumps and trigger capture
      • Waveform bug patterns: latch and race
      • Handshake protocol violation
    • STA and ECO Hub
    • RTL-to-Implementation Closure
    • DFT-Ready RTL
    • Capstone: Resilient Streaming Peripheral
    • Principal Design Review
    • Senior RTL Interview Questions & Model Answers
  • Glossary and Notation
Loading lesson

‹Waveform Debug Hub
  1. Topics
  2. RTL Design
  3. Senior & Interview
  4. Waveform Debug Hub
  5. Waveform bug patterns: latch and race

Part 9 · Senior · Senior

Waveform bug patterns: latch and race

A stale-grant latch, deterministic blocking pipeline collapse, and a separate-process scheduling race.

On this page

    Why this matters

    A stale latch and a collapsed pipeline both look plausible in a wave, and the stage that adds no delay can hide behind repeated input values. Reading input history against output tells them apart.

    After this lesson you can: recognise a stale combinational latch and a blocking-assignment collapse in a waveform, and prove each with a varied input history.

    A missing default holds stale state

    A combinational grant with no assignment for the idle case must hold its old value. Synthesis builds a latch, and the wave shows a stale grant while no request is active.

    arb_grant_bug.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // yosys: latch
    `timescale 1ns/1ps
    module arb_grant_bug (
      input  logic       req0, req1,
      output logic [1:0] grant
    );
      always_comb begin
        if (req0) grant = 2'd0;
        else if (req1) grant = 2'd1;
        // no default: grant holds its old value when both requests are 0
      end
    endmodule

    The fix assigns the output first. The idle code 3 parks the grant where no request can be mistaken for one.

    arb_grant_fixed.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // yosys: ok
    `timescale 1ns/1ps
    module arb_grant_fixed (
      input  logic       req0, req1,
      output logic [1:0] grant
    );
      always_comb begin
        grant = 2'd3;                 // idle parks where no request can be mistaken
        if (req0) grant = 2'd0;
        else if (req1) grant = 2'd1;
      end
    endmodule

    Watch grant against req0 and req1. The idle slots keep the previous grant instead of parking.

    • Slot 0: req0 is 1, so grant is 0.

    • Slot 1: req1 is 1, so grant is 1.

    • Slot 2: both requests are 0, but grant holds 1 instead of parking.

    • Slot 3: req0 is 1, so grant is 0.

    • Slot 4: both requests are 0, but grant holds 0.

    • Slot 5: req1 is 1, so grant is 1.

    Blocking assignments collapse the extra stage

    Within one process, statement order is deterministic: a = din; b = a makes both registers sample din at the rising edge. The second stage adds no delay. Input-to-output behavior still waits for that edge; this is not a zero-latency combinational path or an interprocess race, and synthesis can agree with this collapsed behavior.

    pipe_bug.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    `timescale 1ns/1ps
    module pipe_bug (
      input  logic clk, din,
      output logic a, b
    );
      always @(posedge clk) begin
        a = din;                      // BUG: blocking in clocked logic updates at once
        b = a;                        // reads the just-written a
      end
    endmodule

    Nonblocking assignments sample together at the edge. Each stage takes the previous value, so the pipeline delays by one stage per flop.

    pipe_fixed.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    `timescale 1ns/1ps
    module pipe_fixed (
      input  logic clk, din,
      output logic a, b
    );
      always_ff @(posedge clk) begin
        a <= din;
        b <= a;
      end
    endmodule

    Watch b against a and din. Every slot shows all three equal: the second stage adds no delay.

    • Slot 1: din is 1, so a is 1 and b is already 1.

    • Slot 2: din is 0, so a is 0 and b is already 0.

    • Slot 3: din is 1, so a is 1 and b is already 1.

    • Slot 4: din is 0, so a is 0 and b is already 0.

    • Slot 5: din is 1, so a is 1 and b is already 1.

    • Slot 6: din is 0, so a is 0 and b is already 0.

    With nonblocking assignments b takes the previous a. Varied input history distinguishes that extra stage; repeated input values can make correct stages equal too. The finite bench below checks both histories after register updates.

    A separate-process scheduling race

    design.sv
    1
    2
    3
    4
    5
    6
    7
    // snippet: deliberately racy; do not require either simulator ordering
    `timescale 1ns/1ps
    always @(posedge clk) a = din;
    always @(posedge clk) b = a;
    // If the first process runs first, b sees new din; if the second runs
    // first, b sees old a. Use nonblocking assignments in both processes
    // to sample old state independently of active-region execution order.

    Before edge: a=0, din=1

    After edge

    a process executes first

    a=1, b=1

    b process executes first

    a=1, b=0

    Both use nonblocking assignments

    a=1, b=0 after NBA, independent of ordering

    Read the pair, not the line

    Common pitfall

    A stale latch and collapsed pipeline can both look plausible. Compare edge-indexed input history against settled outputs. Equality alone does not identify an assignment style or prove a scheduling race.

    Interview

    “Blocking versus nonblocking?” Blocking updates immediately within a process. Nonblocking samples right-hand sides and schedules updates for NBA. Single-process blocking collapse is deterministic; blocking communication between separate clocked processes can race with simulator scheduling.

    Check yourself An idle bus shows a grant left over from an earlier request. What should you inspect?

    Inspect combinational assignment coverage: the shown block omits the idle assignment and retains a stale grant. A waveform alone does not exclude other causes, including scheduling races; confirm the source and input history.

    Check yourself Two pipeline stages show identical data every cycle. What does that prove, and what is the fix?

    Equality alone proves no bug: repeated inputs also produce equal stages. Compare a varied input history with the intended latency, then inspect assignments. For the single-process collapse shown here, nonblocking assignments restore the extra stage.

    Testbench

    Both bug halves beside their fixes, with a reference that counts the symptoms.

    arb_grant_bug2.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
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    `timescale 1ns/1ps
    module arb_grant_bug2 (
      input  logic       req0, req1,
      output logic [1:0] grant
    );
      always_comb begin
        if (req0) grant = 2'd0;
        else if (req1) grant = 2'd1;
      end
    endmodule
    
    module arb_grant_fixed2 (
      input  logic       req0, req1,
      output logic [1:0] grant
    );
      always_comb begin
        grant = 2'd3;
        if (req0) grant = 2'd0;
        else if (req1) grant = 2'd1;
      end
    endmodule
    
    module pipe_bug2 (
      input  logic clk, rst_n, din,
      output logic a, b
    );
      always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
          a = 1'b0;
          b = 1'b0;
        end else begin
          a = din;
          b = a;
        end
      end
    endmodule
    
    module pipe_fixed2 (
      input  logic clk, rst_n, din,
      output logic a, b
    );
      always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
          a <= 1'b0;
          b <= 1'b0;
        end else begin
          a <= din;
          b <= a;
        end
      end
    endmodule
    

    Remember

    • A combinational block with no idle assignment holds its old value, and the wave shows a stale grant.

    • With blocking assignments in one process, both registers sample din at the edge, so the second stage adds no delay.

    • Equal outputs prove nothing: repeated inputs also make correct stages equal.

    Keep going

    • Practice: FSM incomplete case and unique0: simulation versus synthesis

    • Interview: run a mock interview on this topic

    Related topics

    • Handshake protocol violation

    Previous

    289 / 350

    Next: Handshake protocol violation

    Up next

    Up nextHandshake protocol violation

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Debug hierarchy before touching code
    clkreq0req1grant0101
    A missing default holds a stale grant while idle
    clkdinab
    Blocking assignments copy through both stages in one cycle