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. Register Slices

Part 3 · Handshakes · Senior (S)

Register Slices

Cutting a long valid/ready path with registers: forward, backward and full slices, what each cuts, and what each costs in latency, area and throughput.

On this page

    Note

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

    Before this: Global Stall or Elastic Pipeline

    Two blocks at opposite corners of a die are joined by a valid/ready channel. Timing fails twice: the data arrives too late at the receiver, and the ready signal arrives too late back at the sender. The integrator is told to "add a register slice", and then asked which kind.

    What exactly does a register slice cut, and why are there several kinds?

    The obvious answer, and why it breaks

    The obvious fix for a long path is to put a flip-flop in it. On the data and valid wires that is easy: delay them by a cycle and both sides still agree, because the slice simply becomes a one-entry stage. Ready is the problem. If you put a flip-flop in the ready wire, the sender learns about a stall one cycle late and sends one more word into a receiver that already said it cannot take it. A slice that registers ready must have somewhere to put that extra word.

    The idea: three slices, by which path they cut

    Slice

    Registers

    Ready path

    Storage

    Throughput

    Forward

    valid and data

    Still combinational: in_ready = !full or out_ready

    One entry

    Full

    Backward

    ready

    Cut

    Needs a spare entry for the word that arrives after a stall

    Full, if it has the spare entry

    Full (both)

    valid, data and ready

    Cut

    Two entries (a skid buffer)

    Full

    Every slice adds one cycle of latency for data. What differs is which long path it removes and how much storage it needs. The forward slice is the cheapest and is what lesson 3.10 calls a good pipeline stage. The full slice is the skid buffer of lesson 3.9.

    A forward slice cuts the valid/data path and keeps full throughput, but the ready path still crosses it combinationally.

    Build it: a forward slice under back-pressure

    The listing registers valid and data and computes ready as !out_vld || out_rdy: the slice can take a new word if it is empty, or if its word is leaving this cycle. A source that always has data feeds it; the sink stalls in a fixed pattern. The checker confirms order and that a word leaves in every cycle the sink is ready.

    fwd_slice.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
    // A forward register slice on a valid/ready channel: valid and data are registered
    // (cutting the long forward path), while ready passes straight through as
    // in_rdy = !out_vld || out_rdy. The slice can accept a new word in the same cycle it
    // hands one on, so it adds one cycle of latency but loses no throughput. The source
    // always has data; the sink stalls in a fixed pattern. The checker confirms order and
    // that every cycle the sink is ready, a word moves out.
    module fwd_slice (
      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
    );
      assign in_rdy = !out_vld || out_rdy;     // combinational: the backward path is not cut
      always_ff @(posedge clk or negedge rst_n)
        if (!rst_n) out_vld <= 0;
        else if (in_rdy) begin
          out_vld  <= in_vld;
          out_data <= in_data;
        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;                  // the source always has a word
      wire        in_rdy, out_vld;
      wire [7:0]  out_data;
      localparam logic [15:0] RDY = 16'b1111_1001_1111_0111;  // sink ready pattern, bit 0 first
      wire        out_rdy = RDY[cyc[3:0]];
      fwd_slice 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;
    
      logic [7:0] expect_w = 0; int errors = 0, ready_cycles = 0, moved = 0;
      always @(posedge clk) if (rst_n && cyc >= 1 && cyc < 16) begin
        if (out_rdy) ready_cycles++;
        if (out_vld && out_rdy) begin
          if (out_data != expect_w) errors++;
          expect_w++; moved++;
        end
      end
      initial begin
        #190;
        if (errors == 0 && moved == ready_cycles)
          $display("PASS: in order, and a word left in every one of the %0d cycles the sink was ready", ready_cycles);
        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:

    • Slot 0: the slice is empty (out_vld = 0), so in_rdy is 1 and the first word enters.

    • From slot 1: the slice always holds a word, and out_data steps 0, 1, 2 on every cycle the sink is ready: full throughput.

    • Slots 3, 9 and 10: the sink stalls (out_rdy = 0). In the same cycles in_rdy drops to 0, and the word on the output is not taken, so it is still there in the next slot (2 in slots 3 and 4). That is the ready path passing straight through the slice: the forward slice cut the data path, not the ready path.

    Choosing a slice

    • Only the data/valid path fails timing: forward slice. Cheapest, full throughput.

    • The ready path fails timing: a slice that registers ready, which means a skid buffer (two entries). Never just a flip-flop in the ready wire.

    • Both fail, or the route is very long: full slices, possibly several in a row; each adds a cycle of latency and, for the full kind, two entries of storage.

    • The route is extremely long: consider credits (Part 4) instead of a chain of slices.

    The design space

    Slice

    Latency added

    Storage

    Paths cut

    None

    0

    0

    None

    Forward

    1 cycle

    1 entry (data width)

    valid and data

    Backward

    0 or 1 cycle depending on design

    1 spare entry

    ready

    Full (skid buffer)

    1 cycle

    2 entries

    valid, data and ready

    How real designs chose

    Where

    Choice

    AXI interconnects and long on-chip routes

    Register slices inserted per channel; each AXI channel has its own VALID/READY, so each can be sliced independently, and the handshake rules (VALID never waits for READY) keep the sliced channel deadlock-free.

    What breaks

    To fix a failing ready path, someone inserts a single flip-flop in the ready wire and nothing else (illustrative). Now the sender sees ready one cycle late. When the receiver stalls, the sender still believes it is ready and sends one more word, which the receiver cannot accept and the slice has nowhere to keep. One word per stall is lost, and only under back-pressure. The rule to remember: registering ready always needs a spare entry. That spare entry is the skid buffer, next lesson.

    Why this matters

    Senior lens. Slices are where you trade latency and area for timing, and the cheapest correct choice depends on which path fails. Read the timing report before choosing: a forward-path failure needs only a forward slice. When many slices are chained on one route, add up the latency they introduce against the protocol's needs, especially on request and response paths whose round trip sets a link's outstanding-transaction requirement (Part 6).

    Interview

    Asked: "Your ready path fails timing. What do you insert?"

    Weak answer: A flip-flop on ready.

    Strong answer: Not a bare flip-flop: with ready registered, the sender learns about a stall a cycle late and sends one more word, which would be lost. I'd insert a slice that registers ready and has a spare entry for that word, a skid buffer, so ready, valid and data are all cut and throughput stays full. If only the forward path failed, a forward slice with a combinational ready would be enough and cheaper.

    Follow-ups: (1) What does a forward slice cost? (2) Why does every slice add latency but not reduce throughput?

    Check yourself In the waveform, how can you tell the forward slice did not cut the ready path?

    in_rdy drops in the same cycle as out_rdy (slots 3, 9 and 10): the stall passes straight through.

    Check yourself Why can a forward slice keep full throughput with only one entry?

    Its ready is !full || out_ready, so it accepts a new word in the same cycle its current word leaves.

    Check yourself A route has three full slices in a row. How much latency do they add, and how much storage?

    Three cycles of latency and six entries of storage (two per slice).

    Key takeaways

    • A forward slice registers valid and data, keeps ready combinational, needs one entry and keeps full throughput.

    • Registering ready always needs a spare entry for the word sent after a stall: that is a skid buffer.

    • Every slice adds a cycle of latency; choose the cheapest one that cuts the path that actually fails.

    Keep going

    Next: Lesson 3.9, Skid Buffers. Cutting the ready path without losing a word or a cycle: the two-entry skid buffer, reasoned from first principles.

    Related topics

    • Part 3 overview: Handshakes

    • RTL course: Skid Buffer and Pipeline Stages

    Previous

    45 / 234

    Next: Skid Buffers

    Up next

    Up nextSkid Buffers

    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_datax0123456789ab
    Simulation output: a forward slice under a stalling sink. in_rdy follows out_rdy in the same cycle: the ready path is still combinational. One slot is one cycle.