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
  • 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
    • Error Models
    • Parity
    • Hamming Distance
    • CRC
    • ECC and SECDED
    • Forward Error Correction
    • Error Responses
    • Poison and Containment
    • ACK and NAK
    • Sequence Numbers
    • Replay Buffers
    • Go-Back-N or Selective Repeat
    • Idempotency and Safe Replay
    • Link-Level or End-to-End Protection
    • Error Classes and Reporting
    • Recovery and Degradation
    • Interface Protection and Safety
    • The Reliability Budget
    • Protocols Compared: Reliability and Recovery
    • Drills: Reliability and Recovery
  • 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 10: Reliability and Recovery
  1. Topics
  2. First Principles of Hardware Protocols
  3. Part 10: Reliability and Recovery
  4. Replay Buffers

Part 10 · Reliability and Recovery · Senior (S)

Replay Buffers

How a link recovers a corrupted packet without help from above: sequence numbers, cumulative ACKs, a NAK, and a replay buffer sized to the acknowledgement round trip.

On this page

    Note

    Level: Senior (S) · Answers: What if bits go wrong?

    Before this: Part 10 overview, The Credit Loop and Buffer Sizing

    A link between two chips runs at tens of gigabits per second. Every so often, a bit flips on the wire. The receiver's CRC check catches it: packet 3 is corrupt, so the receiver throws it away. By then the sender has already sent packets 4, 5, 6 and 7, and it has no reason to think anything went wrong.

    The upper layers expect every packet, exactly once, in order. How does the link put packet 3 back where it belongs, without the layers above ever noticing?

    The obvious answers, and why they break

    Answer one: the receiver asks for packet 3 again. The sender sent it and forgot it; it has nothing to resend. And if the receiver keeps packets 4 to 7 while it waits, it has to hold them and reorder later, which costs buffering at the receiver and complicates every packet.

    Answer two: correct the error at the receiver. That is forward error correction (lesson 10.6). It works for the errors its code can fix, costs bandwidth and latency on every packet, and still needs a fallback for errors beyond its strength.

    Answer three: leave it to software. A missing packet becomes a timeout far up the stack, orders of magnitude slower than the link's own round trip, and every layer above has to handle it. The end-to-end argument says the ends must check correctness anyway, but a link that fails often makes everything above it slow. Recovering at the link is the performance enhancement the argument allows.

    The idea: keep a copy until the receiver says it arrived

    Link-level replay rests on four pieces, each simple on its own:

    1. Sequence numbers. The sender numbers every packet. The receiver knows which number it expects next, so it can tell a missing, repeated or out-of-order packet from a good one.

    2. A replay buffer at the sender. Every packet sent is also kept, until an acknowledgement covers it. Only then is its slot freed.

    3. Cumulative ACKs. The receiver accepts only the packet it expects, intact. An ACK carrying number n means "everything up to and including n arrived", so one ACK can free many slots and a lost ACK is repaired by the next one.

    4. A NAK and a replay. When the receiver sees a bad CRC or a gap in the numbers, it drops the packet and sends one NAK carrying the number of the last good packet. The sender frees everything up to that number and resends everything after it, oldest first. A replay timer covers the case where the NAK, or every ACK, is lost: if nothing comes back for too long, the sender replays anyway.

    The receiver drops packets 4 to 7 even though they were intact. That is the price of the simplest scheme, go-back-N: the receiver never buffers out-of-order packets, so it needs no reordering logic, and the sender resends a little more than it strictly must. Lesson 10.12 compares it with selective repeat, which resends only the bad packet.

    The canonical figure with replay added: the sender keeps every unacknowledged packet, the receiver tracks the next sequence number it will accept, and ACK/NAK messages flow back.
    The exchange simulated below. One NAK, naming the last good packet, triggers the replay of everything after it. Solid arrows carry packets, dashed arrows carry ACK and NAK, and the cross marks the corrupted packet.

    Build it: DCLink step 9

    The listing below is the circuit that produced the waveform further down. To stay short, every packet is a single flit carrying a 4-bit sequence number, an 8-bit payload and a CRC-8; there are two pipeline stages each way; and the replay timer is left out (the NAK path is enough to show the mechanism). The channel corrupts the first transmission of packet 3. A checker confirms that all eight payloads arrive once and in order.

    tb.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
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    // Link-level replay (DCLink step 9, single-flit packets). The sender stamps each
    // packet with a 4-bit sequence number and a CRC-8, keeps a copy in an 8-entry
    // replay buffer, and frees copies when a cumulative ACK covers them. The channel
    // corrupts the first transmission of packet 3. The receiver drops it and every
    // later packet until packet 3 arrives intact, and sends one NAK naming the last
    // good packet. The sender then replays from packet 3 onwards (go-back-N).
    // The checker confirms all eight payloads are delivered once, in order.
    module tb;
      localparam int L = 2;            // pipeline stages each way
      localparam int N = 8;            // packets to deliver
      reg clk = 0;
      always #5 clk = ~clk;
      reg rst_n = 0;
      initial begin
        $dumpfile("wave.vcd");
        $dumpvars(0, tb);
        #12 rst_n = 1;
      end
    
      // CRC-8, polynomial x^8 + x^2 + x + 1, initial value 0, over the 12 bits {seq, payload}.
      function automatic [7:0] crc8(input [11:0] d);
        reg [7:0] c;
        c = 8'h00;
        for (int i = 11; i >= 0; i--)
          c = {c[6:0], 1'b0} ^ ((c[7] ^ d[i]) ? 8'h07 : 8'h00);
        return c;
      endfunction
    
      // ---------------- Sender ----------------
      reg  [7:0] rbuf [0:7];           // replay buffer: payload by seq[2:0]
      reg  [3:0] next_seq = 0;         // sequence number of the next new packet
      reg  [3:0] send_ptr = 0;         // sequence number of the next packet to put on the wire
      reg  [3:0] una      = 0;         // oldest unacknowledged sequence number
      reg  [3:0] made     = 0;         // new packets created so far
      wire       replaying = (send_ptr != next_seq);
      wire       room      = (4'(next_seq - una) < 4'd8);   // replay buffer not full
      wire       new_pkt   = !replaying && room && (made < N);
      wire       tx_vld    = replaying || new_pkt;
      wire [3:0] tx_seq    = send_ptr;
      wire [7:0] tx_pay    = replaying ? rbuf[send_ptr[2:0]] : 8'hA0 + made;
      wire [7:0] tx_crc    = crc8({tx_seq, tx_pay});
    
      // ACK/NAK arriving from the receiver (end of the return pipeline)
      wire       ack_in, nak_in;
      wire [3:0] ack_seq_in;
    
      always @(posedge clk) begin
        if (!rst_n) begin
          next_seq <= 0; send_ptr <= 0; una <= 0; made <= 0;
        end else begin
          if (new_pkt) begin
            rbuf[next_seq[2:0]] <= tx_pay;   // keep a copy until it is acknowledged
            next_seq <= next_seq + 1;
            made     <= made + 1;
          end
          if (tx_vld) send_ptr <= send_ptr + 1;
          if (ack_in || nak_in) una <= ack_seq_in + 1;     // cumulative: frees everything up to ack_seq
          if (nak_in) send_ptr <= ack_seq_in + 1;          // go back: resend from the first unacknowledged packet
        end
      end
    
      // ---------------- Channel: L stages forward, one injected error ----------------
      reg        corrupted_once = 0;
      wire       corrupt = tx_vld && tx_seq == 4'd3 && !corrupted_once;
      always @(posedge clk) if (corrupt) corrupted_once <= 1;
      reg [20:0] fwd [0:L-1];          // {vld, seq, payload, crc}
      always @(posedge clk) begin
        fwd[0] <= rst_n ? {tx_vld, tx_seq, tx_pay ^ (corrupt ? 8'h01 : 8'h00), tx_crc} : 21'd0;
        for (int i = 1; i < L; i++) fwd[i] <= rst_n ? fwd[i-1] : 21'd0;
      end
      wire       rx_vld = fwd[L-1][20];
      wire [3:0] rx_seq = fwd[L-1][19:16];
      wire [7:0] rx_pay = fwd[L-1][15:8];
      wire [7:0] rx_crc = fwd[L-1][7:0];
    
      // ---------------- Receiver ----------------
      reg  [3:0] expected = 0;
      reg        nak_sent = 0;
      wire       crc_ok    = (crc8({rx_seq, rx_pay}) == rx_crc);
      wire       rx_accept = rx_vld && crc_ok && rx_seq == expected;
      wire       send_nak  = rx_vld && !rx_accept && !nak_sent;
      reg  [7:0] delivered [0:N-1];
      integer    ndel = 0;
      always @(posedge clk) begin
        if (!rst_n) begin
          expected <= 0; nak_sent <= 0;
        end else if (rx_accept) begin
          expected <= expected + 1;
          nak_sent <= 0;
          delivered[ndel] <= rx_pay;
          ndel <= ndel + 1;
        end else if (send_nak) begin
          nak_sent <= 1;
        end
      end
    
      // Return path: L stages carrying {ack, nak, ack_seq}. ACK names the packet just
      // accepted; NAK names the last good packet (expected - 1); otherwise ack_seq is 0.
      reg [5:0] back [0:L-1];
      always @(posedge clk) begin
        back[0] <= rst_n ? {rx_accept, send_nak, rx_accept ? rx_seq : send_nak ? 4'(expected - 1) : 4'd0} : 6'd0;
        for (int i = 1; i < L; i++) back[i] <= rst_n ? back[i-1] : 6'd0;
      end
      assign ack_in     = back[L-1][5];
      assign nak_in     = back[L-1][4];
      assign ack_seq_in = back[L-1][3:0];
    
      // ---------------- Checker ----------------
      integer errors = 0, sends = 0;
      always @(posedge clk) if (rst_n && tx_vld) sends++;
      initial begin
        #400;
        for (int i = 0; i < N; i++)
          if (i >= ndel || delivered[i] !== 8'hA0 + i) errors++;
        if (errors == 0 && ndel == N && una == 4'(N))
          $display("PASS: %0d packets delivered once and in order, %0d transmissions (%0d replayed)", ndel, sends, sends - N);
        else
          $display("FAIL: %0d delivered, %0d errors, una = %0d", ndel, errors, una);
        $finish;
      end
    endmodule

    The sender's whole recovery logic is two lines: una <= ack_seq_in + 1 frees every slot an ACK or NAK covers, and send_ptr <= ack_seq_in + 1 on a NAK rewinds the transmit pointer to the first unacknowledged packet. While send_ptr is behind next_seq, the sender is replaying from its buffer instead of making new packets.

    Watch it run

    How to read it, slot by slot. Sequence-number lanes mean something only while their valid lane (tx_vld or rx_vld) is high.

    • Slots 0–7: the sender transmits packets 0 to 7. In slot 3, corrupt is high: the channel flips one payload bit of packet 3.

    • Slots 2–4: packets 0, 1 and 2 reach the receiver two cycles later and are accepted (rx_accept).

    • Slot 5: corrupted packet 3 arrives. Its CRC fails, so rx_accept stays low and the receiver sends a NAK naming packet 2, its last good packet.

    • Slots 6–9: packets 4 to 7 arrive intact but are dropped: the receiver still expects 3.

    • Slots 4–6 and 7: ACKs for 0, 1 and 2 reach the sender (ack_in), and una, the oldest unacknowledged number, steps to 1, 2, 3. In slot 7 the NAK arrives (nak_in).

    • Slots 8–12: the sender replays packets 3 to 7 from its buffer. They arrive in slots 10 to 14 and are accepted in order; their ACKs move una from 4 to 8 in slots 13 to 17, emptying the buffer.

    The single bit error cost five extra transmissions: packet 3 itself and the four packets behind it. Those four were the packets sent during the time it took the NAK to come back. On a longer or faster link, more packets fit into that time, so each error costs more. That is the first reason replay buffers and error rates are designed together.

    Size the replay buffer

    A copy leaves the buffer only when its acknowledgement arrives. So the buffer must hold everything the sender can transmit during one acknowledgement round trip: the packet's own transmission, the trip forward, the receiver's CRC check, any time the receiver waits before sending an ACK (coalescing), the trip back, and the sender's processing. If the buffer is smaller, it fills and the sender stalls, exactly like a credit loop that is shorter than its round trip (lesson 4.5):

    replay buffer≥bandwidth×RTTack​+one maximum-size packet

    With illustrative numbers: a link moving 4 GB/s of packets, with an acknowledgement round trip of 600 ns and a largest packet of 280 bytes including its header:

    Diagram
    replay buffer >= 4 GB/s x 600 ns + 280 B
                  = 4e9 B/s x 600e-9 s + 280 B
                  = 2,400 B + 280 B
                  = 2,680 bytes

    The sequence-number field has its own limit. With n-bit numbers, go-back-N may keep at most 2ⁿ − 1 packets unacknowledged; with 2ⁿ outstanding, a full window of new packets and a full window of replays would carry identical numbers and the receiver could not tell them apart. The listing uses 4-bit numbers (at most 15 outstanding) and an 8-entry buffer, so the buffer is the tighter limit. PCI Express gives every packet a 12-bit sequence number.

    The design space

    Approach

    What it costs

    When it wins

    Go-back-N replay (this lesson)

    A replay buffer of bandwidth × RTT; each error resends everything sent during one RTT

    Low error rates and moderate RTT; simplest receiver

    Selective repeat

    A reorder buffer at the receiver as well; more complex bookkeeping (lesson 10.12)

    Higher error rates or long RTTs, where resending a whole round trip is expensive

    Forward error correction

    Extra bits and decode latency on every packet, error or not (lesson 10.6)

    Error rates too high for replay alone; often combined with replay as the fallback

    Detect, drop, recover end to end

    No link buffer, but recovery is slow and happens far up the stack

    Lossy networks where higher layers already retransmit

    Detect and report only

    Nothing at the link; the transaction fails with an error

    Channels so reliable that errors are exceptional events (most on-chip buses)

    How real protocols chose

    Protocol

    Its constraint

    Its choice

    PCI Express

    Chip-to-chip links at high rates where bit errors are expected, and upper layers that must see every packet once

    Link-level replay in the data link layer: a 12-bit sequence number and an LCRC on every packet, a replay buffer, ACK and NAK packets (an ACK covers every packet up to its number), and a replay timer. On a NAK or timeout it replays every unacknowledged packet from the oldest (go-back-N). A counter of consecutive replays forces the link to retrain when it rolls over; replay timeouts and rollovers are logged as correctable errors.

    Classic Ethernet

    Cheap, simple stations; recovery already exists in higher layers

    No link-level replay. A frame whose check sequence fails is dropped, and recovery is left to higher layers such as TCP.

    AXI

    On-chip wires with very low error rates

    No retransmission. A failed transfer gets an error response, and recovery is up to the requester.

    What breaks

    A team reduces return-path traffic by sending one ACK per 16 packets instead of one per packet (illustrative numbers). With 64-byte packets at 4 GB/s, one packet takes 16 ns, so the receiver now holds an ACK for up to 16 × 16 = 256 ns. The acknowledgement round trip grows from 600 ns to 856 ns, and the replay buffer from the sizing above, 2,680 bytes, is now too small:

    Diagram
    needed = 4 GB/s x 856 ns + 280 B = 3,424 B + 280 B = 3,704 bytes
    usable = 2,680 B - 280 B = 2,400 bytes for the round trip
    throughput while the buffer limits it = 2,400 / 3,424 = about 70%

    Nothing fails functionally. The link simply stalls whenever the replay buffer fills, waiting for an ACK that the receiver is deliberately delaying. The signature on a waveform is the same as a short credit loop: the sender idle with its buffer full while the receiver has nothing to do. The fix is to size the buffer for the new round trip, or to ACK more often when the buffer is filling, which many designs do: they coalesce ACKs on a lightly loaded link and ACK promptly when the sender is running short.

    Why this matters

    Principal lens. Replay hides errors from everything above the link, and that is both its value and its risk. A link with deteriorating margin keeps working, a little slower each week, while the replay count climbs. Treat the replay rate as a health signal: count replays and timeouts, expose them to software, and decide in the specification what rate means "retrain", "lower the speed" or "take the link down". Also budget the tail: each replay adds roughly one acknowledgement round trip of latency to the packets it delays, so a latency-sensitive design must state its target error rate as well as its bandwidth.

    Interview

    Asked: "How big should the replay buffer be?"

    Weak answer: Big enough to hold a few packets, maybe the largest burst; errors are rare, so it barely gets used.

    Strong answer: Every packet stays in the buffer until its ACK arrives, so the buffer must cover bandwidth × the acknowledgement round trip, including the receiver's ACK coalescing, plus one maximum-size packet. If it is smaller, the sender stalls even with zero errors. Then I would check the sequence-number width: go-back-N allows at most 2ⁿ − 1 packets outstanding.

    Follow-ups: (1) What happens if an ACK is lost? (2) When would you choose selective repeat instead of go-back-N?

    Check yourself An ACK is lost on the return path, but later ACKs arrive. What does the sender do?

    Nothing special. ACKs are cumulative: the next ACK, naming a later sequence number, frees every slot up to that number, including the ones the lost ACK would have freed. Only if every ACK and NAK stopped arriving would the replay timer fire.

    Check yourself In the simulation, why did the receiver drop packets 4 to 7 even though their CRCs were good?

    Go-back-N receivers accept only the next expected sequence number. Packet 3 was missing, so 4 to 7 were out of sequence. Keeping them would need a reorder buffer at the receiver; go-back-N trades that buffer for resending them.

    Check yourself A link uses 8-bit sequence numbers with go-back-N. The replay buffer can hold 300 packets. How many can actually be outstanding?

    At most 2⁸ − 1 = 255. Beyond that, sequence numbers would repeat within one window and the receiver could not tell a replay from a new packet. The sender must stop at 255 even though the buffer has room.

    Key takeaways

    • Link-level replay: number every packet, keep a copy until a cumulative ACK covers it, and on a NAK or timeout resend everything after the last good packet.

    • The replay buffer must cover bandwidth × the acknowledgement round trip, including ACK coalescing, plus one maximum packet, or the sender stalls with no errors at all.

    • With n-bit sequence numbers, go-back-N allows at most 2ⁿ − 1 packets outstanding; watch the replay rate, because replay hides a failing link.

    Keep going

    Next: Lesson 10.12, Go-Back-N or Selective Repeat. When resending a whole round trip of packets costs too much, and what the receiver must add to resend only the bad one.

    Related topics

    • Part 10 overview: Reliability and Recovery

    • Lesson 4.5: The Credit Loop and Buffer Sizing

    • RTL course: Parity Datapath

    Previous

    152 / 234

    Next: Go-Back-N or Selective Repeat

    Up next

    Up nextGo-Back-N or Selective Repeat

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Error Models
    • Parity
    • Hamming Distance
    clktx_vldtx_seq01234567345678corruptrx_vldrx_seq01234567345678rx_acceptack_innak_inuna012345678
    Simulation output of the listing above: packet 3 is corrupted once, the receiver NAKs, and the sender replays packets 3 to 7. One slot is one clock cycle.