A NoC that works in the demo and freezes under real traffic has usually merged what must stay separate: requests and responses sharing one buffer pool, so a full request queue blocks the responses that would drain it. This is message-dependent deadlock. The integrator does not design the router, but assigns the message classes, checks the credit sizing the generator chose and must recognise the freeze. Routing algorithms, deadlock-free turn rules and credit flow control are taught in the architecture course (XY routing and deadlock freedom, credit flow control); this lesson keeps only the two checks that are the integrator's.
First check: message classes. Requests and responses (and, on a coherent fabric, snoops) must travel on separate virtual networks, that is separate sets of buffers and virtual channels per message class, so that each class can drain even when another has backed up. Virtual channels inside one class exist for a different reason, head-of-line blocking and routing deadlock; they do not by themselves cure message-dependent deadlock. The coherency course calls the per-class separation virtual networks, and so does this course. Second check: credits. A sender may send only while it holds a credit for a free buffer slot downstream, and credits return after a round trip, so the buffer must cover the loop: credits in flits must be at least the round-trip cycles times the flits per cycle, or the link idles waiting for credits.
# Illustrative values: credit sizing in flits.
# Link moves 1 flit per cycle; the credit loop is wire out + buffer + credit back.
FLITS_PER_CYCLE = 1
ROUND_TRIP = 6 # cycles, illustrative
def link_throughput(credits_in_flits):
return min(1.0, credits_in_flits / (ROUND_TRIP * FLITS_PER_CYCLE))
needed = ROUND_TRIP * FLITS_PER_CYCLE
assert needed == 6
assert link_throughput(needed) == 1.0 # enough credits: full rate
assert abs(link_throughput(4) - 2 / 3) < 1e-9 # too few: a third of the link idles
# separate virtual networks: each class needs its own credits, never shared
classes = ["request", "response"]
total_buffer_flits = len(classes) * needed
assert total_buffer_flits == 12
print("PASS: 6 credits for a 6-cycle loop, 4 credits give 66.7 percent, two classes need 12 flits")Every message class (request, response, and snoop where coherent) is listed with its own virtual network and reserved buffers.
Credit counts in flits cover the round-trip case for the slowest link, not just one hop.
The routing rule and its deadlock freedom are taken from the generator's documentation, with the integrator's custom routes (if any) re-proved.
Stress with all classes active at once, because buffer-sharing bugs hide until every buffer is full simultaneously. On a freeze, dump per-class occupancy per router: one class at 100 percent with its responses blocked behind its own requests is the diagnosis. For the full triage method see the NoC triage playbook in the architecture course.
The link runs at two thirds of its rate (4 of 6) with no error: credits are exhausted before the first one returns. The fix is 6 credits (in flits) per virtual network for that link, or a shorter credit loop.