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
    • Why Flow Control
    • On/Off Flow Control
    • Credit-Based Flow Control
    • Credit Initialisation
    • The Credit Loop and Buffer Sizing
    • Credit Classes and Pools
    • Credit Return Latency and Batching
    • Credit Bugs
    • Window Flow Control
    • Rate-Based Shaping
    • Hop-by-Hop or End-to-End
    • Lossless or Lossy
    • Switching Modes
    • Head-of-Line Blocking
    • Choosing Flow Control
    • Protocols Compared: Flow Control
    • Drills: Flow Control
  • 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 4: Flow Control
  1. Topics
  2. First Principles of Hardware Protocols
  3. Part 4: Flow Control
  4. Credit Classes and Pools

Part 4 · Flow Control · Senior (S)

Credit Classes and Pools

When several kinds of traffic share a link: one credit pool or separate pools, header versus data credits, and how a shared pool lets bulk traffic starve urgent traffic.

On this page

    Note

    Level: Senior (S) · Answers: How much may I send before I hear back?

    Before this: The Credit Loop and Buffer Sizing

    A link carries two kinds of traffic: bulk DMA writes and short, urgent interrupt messages. Both use one pool of receiver credits. One day the DMA's destination memory gets busy, the DMA writes back up into the link's buffer, and an interrupt message waits more than a hundred cycles for a credit. Nothing is broken, and the interrupt is still late.

    When several kinds of traffic share a link, how should credits be divided between them?

    The obvious answer, and why it breaks

    The obvious answer is one shared pool: any traffic may use any free slot, which uses the buffer most efficiently. It breaks when one class's consumer slows down. That class's words sit in the buffer holding credits, its sender keeps spending every credit that frees up, and soon the pool is full of words that cannot move. Other classes, whose consumers are perfectly fast, cannot get a single credit. The slow class has blocked everyone through the shared resource.

    The idea: separate what must not block each other

    Give each class of traffic its own credits for its own buffer space, so one class can only ever fill its own share. The choice of how to divide is a spectrum:

    Scheme

    How credits are divided

    Isolation

    Buffer efficiency

    Shared pool

    One pool for all classes

    None: one class can take every slot

    Best: no slot reserved for an idle class

    Split pools

    A fixed share per class

    Complete

    Worst: an idle class's share sits empty

    Shared + reserved

    A minimum reserved per class, the rest shared

    Each class is guaranteed its minimum

    Close to shared

    A second kind of split is by what is stored. Packets have a fixed-size header and a variable-size payload. Counting both in one unit wastes buffer: a header-only packet would consume a full packet's worth of space. Separate header credits and data credits let a receiver accept many small packets or a few large ones within the same memory.

    Shared plus reserved: bulk traffic can use most of the buffer, but urgent traffic always has somewhere to go.

    Build it: starve, split, reserve

    The model shares an 8-slot buffer between bulk class A, whose consumer is slow and blocked half the time, and urgent class B, which has one word every 10 cycles and a fast consumer. It measures the worst wait for a B word under each policy.

    example.py
    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
    """Two traffic classes sharing a receiver buffer of 8 slots.
    
    Class A is bulk traffic that always has data, and its consumer is often blocked: it
    drains one word every 4 cycles, but only during the first 100 of every 200 cycles. Class B is urgent and occasional (one word every 10 cycles), and
    its consumer is fast. Credits are managed three ways:
      shared:   one pool of 8 credits for both classes
      split:    4 credits reserved for each class
      reserved: a shared pool of 6 plus 2 credits only class B may use
    Measure the worst wait for a class B word to be sent.
    """
    
    
    def run(policy, cycles=4000):
        buf = {"A": 0, "B": 0}
        b_waiting_since = None
        worst_b_wait, b_sent = 0, 0
        for t in range(cycles):
            if t % 10 == 0 and b_waiting_since is None:
                b_waiting_since = t                     # a B word becomes ready to send
            used = buf["A"] + buf["B"]
            def can_send(c):
                if policy == "shared":
                    return used < 8
                if policy == "split":
                    return buf[c] < 4
                if c == "A":                            # reserved: A may use only the shared 6
                    return buf["A"] < 6 and used < 8
                return used < 8                         # B may use any free slot, including its 2
            if b_waiting_since is not None and can_send("B"):
                buf["B"] += 1; b_sent += 1
                worst_b_wait = max(worst_b_wait, t - b_waiting_since)
                b_waiting_since = None
            elif can_send("A"):
                buf["A"] += 1
            if t % 200 < 100 and t % 4 == 0 and buf["A"]:
                buf["A"] -= 1                           # A's consumer: slow, and blocked half the time
            if buf["B"]:
                buf["B"] -= 1                           # fast consumer of B
        return worst_b_wait, b_sent
    
    
    print("policy    worst wait for a class B word (cycles)")
    waits = {}
    for policy in ("shared", "split", "reserved"):
        w, n = run(policy)
        waits[policy] = w
        print(f"{policy:8}  {w:5d}   ({n} B words sent)")
    assert waits["shared"] > waits["reserved"] and waits["reserved"] <= 1
    print("PASS: in a shared pool bulk traffic holds every credit and urgent traffic waits; a reservation fixes it")

    It prints:

    Diagram
    policy    worst wait for a class B word (cycles)
    shared      101   (200 B words sent)
    split         0   (400 B words sent)
    reserved      0   (400 B words sent)
    PASS: in a shared pool bulk traffic holds every credit and urgent traffic waits; a reservation fixes it

    With a shared pool, class A fills every slot during its consumer's blocked periods, and class B waits over 100 cycles, sending only half its words in the run. Splitting the pool removes the wait entirely, at the cost of four slots that class B rarely uses. Reserving just two slots for B gives the same zero wait while class A keeps six slots: the best of both.

    The design space

    If...

    Choose

    All traffic is one kind, or classes never block each other

    One shared pool

    Classes must be strictly isolated (for example requests and responses, lesson 9.4)

    Separate pools, often on separate virtual channels

    Classes differ in urgency but buffers are precious

    Shared pool plus a small reservation per class

    Packets vary widely in size

    Separate header and data credits

    How real protocols chose

    Protocol

    Its division

    PCI Express

    Six credit types: posted, non-posted and completion traffic each have their own header credits and data credits. One data credit covers 16 bytes of payload; each packet uses one header credit of its type.

    On-chip networks

    Credits per virtual channel, so traffic classes on separate channels cannot block each other's buffers (lesson 8.8).

    Ethernet with priority flow control

    The on/off version of the same idea: each priority can be paused separately.

    PCIe separates its three traffic types for more than urgency: its ordering and deadlock-avoidance rules depend on some types being able to pass others (lessons 7.6 and 9.4), which is impossible if they share one pool.

    What breaks

    The opening story, with the debug steps (illustrative):

    • Symptom: interrupt latency spikes only when a particular DMA is busy.

    • Evidence: at the spike, the receiver's shared buffer is full of DMA writes and the interrupt message's credit request is waiting.

    • Root cause: one shared pool; the DMA's slow destination held every credit.

    • Fix: a small reservation for the interrupt class (or a separate channel). The lasting lesson: classes that must not wait for each other must not share a pool without a guaranteed minimum.

    Why this matters

    Principal lens. Credit pools are where quality of service and deadlock freedom are actually enforced, long before any arbiter sees the traffic. Decide the classes from what must never block what: requests versus responses, urgent versus bulk, one tenant versus another. Then give each class a guaranteed minimum and size the shared part from the total round-trip bandwidth. Specify the policy in the interface contract, because a receiver that quietly turns reserved credits into shared ones breaks guarantees the sender relies on.

    Interview

    Asked: "Why does PCIe track header and data credits separately?"

    Weak answer: Because headers and data are different.

    Strong answer: Headers are fixed size and payloads vary from nothing to large. One unit for both would waste buffer: a header-only packet would cost as much as a full one. With separate credits, one header credit per packet and data credits in 16-byte units, a receiver can accept many small packets or a few large ones within the same memory. PCIe also splits posted, non-posted and completion traffic into separate credit types so they can't block each other, which its ordering and deadlock rules depend on.

    Follow-ups: (1) What goes wrong with one shared pool for all traffic? (2) What does a reserved minimum per class buy?

    Check yourself In the model, why does class B wait over 100 cycles with a shared pool, even though its own consumer is fast?

    Class A's consumer is blocked for 100 cycles at a time; class A's words fill all 8 slots and hold their credits, so class B cannot get any credit until class A's consumer resumes.

    Check yourself Why does reserving 2 slots for B work as well as a full split?

    B only ever needs a slot or two at a time; the reservation guarantees them, while A keeps the use of the other six.

    Check yourself A receiver has 64 data credits and 4 header credits. A sender has many 16-byte packets to send. What limits it?

    Header credits: only 4 packets can be outstanding however small they are, even though data credits are plentiful.

    Key takeaways

    • A shared credit pool lets one class with a slow consumer take every slot and starve the others.

    • Separate pools isolate classes completely; a shared pool with a reserved minimum per class isolates them almost as well with less waste.

    • Separate header and data credits fit fixed headers and variable payloads into one buffer efficiently.

    Keep going

    Next: Lesson 4.7, Credit Return Latency and Batching. Returning credits late, or in batches, quietly lengthens the loop. How much it costs.

    Related topics

    • Part 4 overview: Flow Control

    • Architecture course: Credit Flow Control and Buffers

    Previous

    56 / 234

    Next: Credit Return Latency and Batching

    Up next

    Up nextCredit Return Latency and Batching

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Why Flow Control
    • On/Off Flow Control
    • Credit-Based Flow Control