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

  • How to Use This Course
  • Reading Paths
  • Concept Map
  • How to Read the Diagrams
  • Part 0: What Is a Protocol?10
  • Part 1: Timing and Clocking11
  • Part 2: Encoding and Framing12
  • Part 3: Handshakes13
    • Valid-Only Streams
    • Wait States and Stretching
    • The Valid/Ready Handshake
    • The Valid/Ready Rules
    • Request/Acknowledge: Two-Phase and Four-Phase
    • Push and Pull
    • Global Stall or Elastic Pipeline
    • Register Slices
    • Skid Buffers
    • Bubbles and Half Throughput
    • Latency-Insensitive Design
    • Protocols Compared: Handshakes
    • Drills: Handshakes
  • Part 4: Flow Control17
  • Part 5: Transactions19
  • Part 6: Concurrency and Performance17
  • Part 7: Ordering13
  • Part 8: Sharing and Arbitration12
  • Part 9: Deadlock and Liveness13
  • Part 10: Reliability and Recovery20
  • Part 11: Bring-Up and Link Management10
  • Part 12: Power8
  • Part 13: Shared State and Coherence9
  • Part 14: Security and Isolation7
  • Part 15: Specifying and Verifying11
  • Part 16: Protocols Dissected17
  • Part 17: Capstones5
  • Part 18: Interview Preparation6
Loading lesson

‹Part 3: Handshakes
  1. Topics
  2. First Principles of Hardware Protocols
  3. Part 3: Handshakes
  4. Skid Buffers

Part 3 · Handshakes · Senior (S)

Skid Buffers

Why a registered ready needs exactly one spare entry, how the two-entry skid buffer uses it, and how to see it working on a waveform without losing a word or a cycle.

On this page

    Note

    Level: Senior (S) · Answers: Is the receiver ready?

    Before this: Register Slices

    An interviewer draws a pipeline stage with a registered ready output and asks how many entries of storage it needs. One candidate says one: it is a pipeline register. Another says it depends on how long the stall lasts. The interviewer's answer is two, regardless of how long the stall lasts, and the candidates are asked to explain why.

    Why does registering ready need exactly one extra entry, and how does a skid buffer use it?

    The obvious answer, and why it breaks

    The obvious stage with a registered ready has one data register, and drives ready from a flip-flop that says "I'm not full". Walk through a stall. In cycle t the stage is full and its output is blocked. It decides "I'm full" and registers ready = 0, but that only appears at its input in cycle t + 1. During cycle t the sender still sees the old ready = 1 and sends a word. The stage has nowhere to put it. A one-entry stage with a registered ready loses a word on every stall.

    The idea: one spare entry for the word in flight

    The word that arrives after the stall began is the only one that can arrive, because from the next cycle on the sender sees ready = 0. So exactly one extra entry is needed, however long the stall lasts: the sender's view of ready is late by one cycle, never more. That spare entry is the skid register: the stage "skids" one word past the point where it wanted to stop.

    1. Normal flow: the spare entry is empty, ready = 1, words pass straight into the output register.

    2. Stall begins: the output is blocked; a word arrives anyway (the sender has not seen the stall yet). It goes into the spare entry, and ready drops to 0 from the next cycle.

    3. Stall ends: the output register's word leaves, the spare entry's word moves into the output register, the spare entry empties, and ready returns to 1. The output never goes empty, so no cycle is lost.

    Because ready now comes straight from a flip-flop (the inverse of "spare entry full"), the long combinational ready path through the stage is cut. Together with the registered output, the skid buffer cuts every path: it is the full register slice of lesson 3.8.

    The DCLink step 2b picture: both paths through the stage are registered, and the second entry absorbs the one-cycle lag in ready.

    Build it: DCLink step 2b

    The listing implements the skid buffer in its usual form: in_rdy is the inverse of the spare entry's valid bit. The testbench feeds it from a source that always has data and drains it into a sink that stalls in a fixed pattern. The checker requires every word in order, none lost or repeated, and a word out in every cycle the sink is ready: full throughput.

    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
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    // A two-entry skid buffer: both the forward path (valid, data) and the backward path
    // (ready) are registered. Because in_rdy is a register, the source learns about a
    // stall one cycle late, so one more word can arrive after the output stalls. The
    // second entry (the skid register) catches it. Full throughput when the sink is
    // ready; no word lost or duplicated when it stalls.
    module skid_buffer (
      input  logic       clk, rst_n,
      input  logic       in_vld,  output logic       in_rdy,  input  logic [7:0] in_data,
      output logic       out_vld, input  logic       out_rdy, output logic [7:0] out_data
    );
      logic       skid_vld;                    // the spare entry holds a word
      logic [7:0] skid_data;
      assign in_rdy = !skid_vld;               // straight from a flip-flop: the backward path is cut
    
      always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
          out_vld <= 0; skid_vld <= 0;
        end else if (in_rdy) begin             // spare entry empty: we may accept a word
          if (out_vld && !out_rdy) begin       // output stalled: a word that arrives goes to the spare entry
            if (in_vld) begin skid_vld <= 1; skid_data <= in_data; end
          end else begin                       // output empty or moving on: the word goes straight to the output
            out_vld <= in_vld; out_data <= in_data;
          end
        end else if (out_rdy) begin            // spare entry full and the output moves on: refill from it
          out_data <= skid_data; skid_vld <= 0;   // out_vld stays 1
        end
      end
    endmodule
    
    module tb;
      logic clk = 0;
      always #5 clk = ~clk;
      logic rst_n = 0;
      initial begin
        $dumpfile("wave.vcd"); $dumpvars(0, tb);
        #12 rst_n = 1;
      end
      int cyc = 0;
      always @(posedge clk) if (rst_n) cyc <= cyc + 1;
    
      logic [7:0] next_word = 0;
      wire        in_vld = 1;
      wire        in_rdy, out_vld;
      wire [7:0]  out_data;
      localparam logic [31:0] RDY = 32'b1111_1111_0111_1100_1111_1101_1111_1111;
      wire        out_rdy = RDY[cyc[4:0]];
      skid_buffer u (.clk, .rst_n, .in_vld, .in_rdy, .in_data(next_word), .out_vld, .out_rdy, .out_data);
      always @(posedge clk) if (rst_n && in_vld && in_rdy) next_word <= next_word + 1;
      wire skid_vld = u.skid_vld;              // copy for the waveform
    
      logic [7:0] expect_w = 0; int errors = 0, moved = 0, ready_cycles = 0;
      always @(posedge clk) if (rst_n) begin
        if (cyc >= 1 && out_rdy) ready_cycles++;
        if (out_vld && out_rdy) begin
          if (out_data != expect_w) errors++;
          expect_w++; moved++;
        end
      end
      initial begin
        #340;
        if (errors == 0 && moved == ready_cycles)
          $display("PASS: %0d words out in order, one in every cycle the sink was ready, with ready registered on both sides", moved);
        else $display("FAIL: errors %0d, moved %0d of %0d ready cycles", errors, moved, ready_cycles);
        $finish;
      end
    endmodule

    Watch it run

    How to read it, slot by slot:

    • Slots 0 and 1: words 6 and 7 flow through, one per cycle; the spare entry is empty (skid_vld = 0) and in_rdy = 1.

    • Slot 2: the sink stalls (out_rdy = 0) with word 8 on the output. The sender still sees in_rdy = 1, so word 9 arrives anyway.

    • Slot 3: word 9 is in the spare entry (skid_vld = 1), and in_rdy is now 0, so nothing more arrives. Word 8 is still on the output, and leaves at the end of this slot.

    • Slot 4: word 9 moves from the spare entry to the output, the spare entry empties, and in_rdy returns to 1. out_vld never dropped: no bubble.

    • Slots 9 to 12: a two-cycle stall. The spare entry fills once (slot 10) and stays full while the stall lasts; in_rdy stays low; word e holds on the output until the sink takes it, and word f follows from the spare entry in slot 12. Two stall cycles, still one spare entry.

    The design space

    Stage

    Entries

    Ready path

    Throughput

    Risk

    One entry, ready combinational (forward slice)

    1

    Not cut

    Full

    Long ready chain through many stages

    One entry, ready = !full (registered by nature)

    1

    Cut

    Half (lesson 3.10)

    Throughput loss

    One entry, ready through a flip-flop, no spare

    1

    Cut

    Full until a stall

    Loses one word per stall

    Skid buffer

    2

    Cut

    Full

    None, at twice the storage

    Deeper FIFO

    N

    Cut

    Full

    More area; only needed when ready arrives more than one cycle late

    The last row generalises the lesson. If the sender learns of a stall k cycles late, it can send k extra words, so the stage needs k spare entries. A skid buffer is the k = 1 case. Part 4 turns this into credit flow control, where k is a whole round trip.

    How real designs chose

    Where

    Choice

    AXI register slices on long routes

    The full register slice is a skid buffer: it registers VALID, data and READY, with two entries per channel.

    DCLink (this course)

    Step 2b places a skid buffer between sender and receiver; Part 4 replaces it with credits when the path gets long.

    What breaks

    A skid buffer is built with the spare entry but with in_rdy computed from a condition that also looks ahead at the sink (illustrative): it drops ready one cycle too early "to be safe". Nothing is lost, but every stall now costs an extra idle cycle on the output, and a link with frequent short stalls loses several percent of its throughput. The check that catches it is the one in the listing's testbench: a word must leave in every cycle the sink is ready. Verifying only order and no loss would pass this design.

    Why this matters

    Senior lens. The skid buffer encodes the most important sizing rule in this course in its smallest form: spare buffering equals how late the sender learns to stop, times the rate it sends at. One cycle late, one word: two entries. A round trip late: a round trip of words, which is credit sizing (lesson 4.5). In reviews, check skid buffers for both properties, no loss and no bubble, because a design that is conservative about ready trades throughput for safety without telling anyone.

    Interview

    Asked: "Why does a skid buffer need two entries, not one?"

    Weak answer: To make it faster.

    Strong answer: Because its ready is registered, the sender learns about a stall one cycle late and sends exactly one more word. With one entry, that word has nowhere to go. One spare entry catches it, and it is always exactly one, because from the next cycle the sender sees ready low. When the stall ends, the spare word moves to the output, so the output never empties and throughput stays full. In general, k cycles of ready lag need k spare entries.

    Follow-ups: (1) How would you verify there is no bubble after a stall? (2) What if ready reached the sender two cycles late?

    Check yourself In the waveform, why does the spare entry fill only once during the two-cycle stall at slots 9 to 10?

    After the first stall cycle, in_rdy is low, so the sender sends nothing more. Only the one word sent before it saw ready fall needs the spare entry.

    Check yourself A testbench for a skid buffer checks order and no loss. What bug can still pass?

    A bubble after each stall (the output goes empty for a cycle). Add a check that a word leaves in every cycle the sink is ready.

    Check yourself A stage's ready reaches the sender through two registers. How many entries does it need for full throughput without loss?

    Three: one output entry plus two spare entries for the two words sent before the sender sees ready fall.

    Key takeaways

    • A registered ready reaches the sender one cycle late, so exactly one extra word arrives after a stall begins.

    • A skid buffer's spare entry catches that word; ready comes from a flip-flop, so all paths are cut and throughput stays full.

    • In general, k cycles of ready lag need k spare entries: the root of credit sizing in Part 4.

    Keep going

    Next: Lesson 3.10, Bubbles and Half Throughput. The most common handshake bug in interviews: a stage that silently runs at half speed.

    Related topics

    • Part 3 overview: Handshakes

    • RTL course: Skid Buffer and Pipeline Stages

    Previous

    46 / 234

    Next: Bubbles and Half Throughput

    Up next

    Up nextBubbles and Half Throughput

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Valid-Only Streams
    • Wait States and Stretching
    • The Valid/Ready Handshake
    clkin_rdyout_vldout_rdyout_data6789abcdef10111213skid_vld
    Simulation output: two stalls absorbed by the spare entry, with out_vld high throughout. One slot is one cycle; the window starts at cycle 7.