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 Foundations57
  • Combinational Design58
  • Sequential Design61
    • Flip-Flops and Registers
    • Counters and Timers
    • Shift Registers and LFSRs
    • Pipelining
    • Stalls, Bubbles and Hazards
    • Retiming
      • Retiming Mechanism
      • Retiming Decision Guide
      • Handshake-Aware Retiming
      • Practice
      • Sample Q&A
      • Checklist
      • Worked Examples and Waveforms
      • Corner Cases and Stress Tests
      • Integration and Sign-off Notes
  • FSM & Control66
  • Timing & Clocking64
  • Reset & CDC58
  • Memory & Buses94
  • Advanced RTL60
  • Senior & Interview175
  • Glossary and Notation
Loading lesson

‹Retiming
  1. Topics
  2. RTL Design
  3. Sequential Design
  4. Retiming
  5. Handshake-Aware Retiming

Part 3 · Sequential · Intermediate

Handshake-Aware Retiming

Register slices and skid buffers that cut combinational ready paths at full throughput.

On this page

    Why this matters

    In a valid/ready chain, ready travels backwards through every stage, so a 10-stage chain has a 10-gate ready path. A skid buffer cuts it without losing a transfer.

    After this lesson you can: explain why in_ready = !skid_v is safe, trace a fill, stall and drain, and decide where skid buffers belong.

    The ready path is a timing problem

    In a valid/ready chain, in_ready = !out_valid || out_ready makes ready travel backwards through every stage combinationally. A 10-stage chain has a 10-gate-deep ready path.

    A skid buffer (a two-slot register slice here) cuts this dependency while preserving the handshake contract. This implementation derives in_ready = !skid_v from registered occupancy: it advertises a spare slot, rather than delaying a copy of out_ready. Ordinary register movement alone is not a substitute for designing and checking this protocol boundary.

    When main is blocked and skid is empty, in_ready remains high because the skid can accept one more item. After that acceptance fills skid, in_ready drops. If main leaves while skid is full, skid moves to main; upstream sees ready return after that edge. See the primary Pipeline Skid Buffer derivation for the two-slot capacity argument.

    Skid buffer datapath: a new item goes to the main register, or parks in the skid register when main is blocked; in_ready comes only from the skid_v flag

    Read it from the left: data enters on the top path, and the skid register is the side pocket below it.

    • In normal flow the item goes straight through the mux into the main register.

    • When main is blocked, the next accepted item is parked in the skid register.

    • in_ready is driven by the skid_v flag alone, which is a register output.

    • out_ready controls the mux and the registers but has no path to in_ready; this is what cuts the ready chain.

    Fill, stall and drain

    Edge / state before edge

    Source / out_ready

    in_ready / out_valid

    Accepted / delivered

    State after edge

    0: empty

    A=0 / 0

    1 / 0

    A / none

    main=A, skid empty

    1: main=A

    B=1 / 0

    1 / 1

    B / none

    main=A, skid=B

    2: main=A, skid=B

    C=2 / 0

    0 / 1

    none / none

    Both hold; source retains C

    3: main=A, skid=B

    C=2 / 1

    0 / 1

    none / A

    main=B, skid empty; in_ready rises after edge

    4: main=B

    C=2 / 1

    1 / 1

    C / B

    main=C, skid empty

    5: main=C

    none / 1

    1 / 1

    none / C

    Empty

    Each slot is one clock edge and shows the values that edge sees, so slot k matches table row k. Slot 6 is the empty state after the last edge. The data lanes show a value only while their valid is high.

    • in_ready stays high through slot 1 even though out_ready is low, because the skid slot is free.

    • in_ready falls in slot 2, after B parked in the skid register, and stays low in slot 3 while A leaves.

    • C is offered in slots 2, 3 and 4 and only accepted at edge 4, when in_ready is high again.

    • Deliveries at edges 3, 4 and 5 are consecutive: the output carries A, B, then C with no gap.

    A and B occupy the two available slots, so C cannot be accepted at edge 3 even though A departs then: ready depends only on the old skid state. Deliveries at edges 3, 4 and 5 remain consecutive. Sustained unstalled flow supports one item per cycle, but recovery from full occupancy can pause input acceptance for a cycle. The directed check below follows these exact transitions before random stalls.

    What you gain and pay

    A skid buffer doubles the registers but cuts the ready chain. Throughput stays at one item per cycle.

    Plain stage

    Skid buffer

    in_ready depends on

    out_ready (combinational)

    A register (skid_v)

    Registers

    1 data + valid

    2 data + 2 valid

    Throughput

    1 item / cycle

    1 item / cycle

    Timing

    Ready chain grows with depth

    Chain is cut at every skid buffer

    Tip

    You don't need a skid buffer on every stage. Insert one every few stages, or at block boundaries, wherever the combinational ready path would become critical.

    Common mistakes

    Common pitfall

    A one-register slice that simply registers ready (without a skid) either loses throughput (half-rate) or drops data. The skid register exists to catch the in-flight item. Always verify a custom handshake stage with random stalls and a scoreboard, as the testbench below does.

    Interview

    Be able to explain why in_ready = !skid_v is safe: when skid_v is 0, there is always room for one more item (main or skid), so accepting is never lossy.

    Check yourself The skid buffer has two data registers. In normal flow, which one carries data?

    Only the main register. When main is blocked, the spare skid slot catches one further accepted item. It moves into main when main is consumed; ready then returns from the updated occupancy.

    Testbench

    The skid buffer plus a testbench with random stalls, a scoreboard and input/output stability monitors. Shutdown preserves a pending source offer, then drains with the sink continuously ready.

    skid_buffer.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
    module skid_buffer #(parameter int W = 8) (
      input  logic         clk, rst_n,
      input  logic         in_valid,  output logic in_ready,  input  logic [W-1:0] in_data,
      output logic         out_valid, input  logic out_ready, output logic [W-1:0] out_data
    );
      // main output register
      logic         main_v;  logic [W-1:0] main_d;
      // skid register: holds the one item accepted just after downstream stalled
      logic         skid_v;  logic [W-1:0] skid_d;
    
      assign in_ready  = !skid_v;                // depends ONLY on a register, not on out_ready
      assign out_valid = main_v;
      assign out_data  = main_d;
    
      always_ff @(posedge clk or negedge rst_n)
        if (!rst_n) begin main_v <= 0; skid_v <= 0; end
        else begin
          if (!skid_v) begin
            // normal operation: take input if main is free or leaving
            if (!main_v || out_ready) begin
              main_v <= in_valid;
            end else if (in_valid) begin
              skid_v <= 1;                        // downstream stalled: park the new item
            end
          end else if (out_ready) begin
            // skid occupied and main leaves: skid moves into main
            main_v <= 1;
            skid_v <= 0;
          end
        end
    
      always_ff @(posedge clk)
        if (!skid_v) begin
          if (in_valid && (!main_v || out_ready))   main_d <= in_data;
          else if (in_valid)                         skid_d <= in_data;
        end else if (out_ready) main_d <= skid_d;
    endmodule
    

    Remember

    • in_ready comes from the skid_v flag alone, so out_ready has no path to in_ready.

    • A skid buffer doubles the registers, keeps one item per cycle and can pause input for a cycle on recovery.

    • A one-register slice that just registers ready halves the rate or drops data.

    Keep going

    • Practice: Two-stage valid/ready pipeline and skid buffers

    • Code lab: Skid buffer

    • Interview: run a mock interview on this topic

    Related topics

    • Valid/ready buses

    Previous

    170 / 694

    Next: Practice

    Up next

    Up nextPractice

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Retiming Mechanism
    • Sample Q&A
    • Checklist
    in_validin_data012xin_readyout_validout_datax012xout_ready
    The six-edge trace as a waveform: A, B and C carry data 0, 1 and 2, in_ready drops at edge 2 and returns at edge 4, and three deliveries come at edges 3, 4 and 5