Files
kernbench2/scripts/paper/paper_plot_ipcq_alternatives_comparison.py
2026-06-23 19:19:55 -07:00

1076 lines
46 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""4-way comparative study — IPCQ vs HW alternatives.
Architectures compared (the chosen design + 3 HW alternatives):
Case 1) Doorbell + Polling (traditional MMIO doorbell)
2 DMA transactions (data + doorbell), peer polls or IRQ.
Case 2) HMQ (Hardware Message Queue, NVLink-style)
separate HMQ engine pushes descriptors, large tensors still
need DMA → duplicated datapath.
Case 3) RDMA-CQ (Completion Queue, InfiniBand / RoCE)
DMA write → CQE auto-posted at peer → CQ poll / IRQ.
Case 4) IPCQ ★ chosen (HW Ring + Credit Return)
ring buffer + credit return, control plane in PE_IPCQ HW,
data plane in PE_DMA, direction-addressed (E/W/N/S NoC + UCIe).
Outputs PNGs into src/kernbench/benches/1H_milestone_output/IPCQ/:
ipcq_alternatives_architecture.png 2×2 block diagram
ipcq_alternatives_latency.png per-send timing stack (4 bars)
ipcq_alternatives_opcounts.png control-plane op count
ipcq_alternatives_sequence.png 2×2 swimlane flow
ipcq_alternatives_decision_matrix.png 5-criterion "why IPCQ won" matrix
Cycle / op counts are step-counts; numbers labelled "illustrative" are
order-of-magnitude comparators (no benchmark target).
"""
from __future__ import annotations
import textwrap
from pathlib import Path
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
_OUT_DIR = (
Path(__file__).resolve().parents[2]
/ "src" / "kernbench" / "benches"
/ "1H_milestone_output" / "IPCQ"
)
_COLOR = {
"ipcq": "#3b6ea5", # blue — chosen
"doorbell": "#c0504d", # red
"hmq": "#e07a3f", # orange
"rdma": "#8064a2", # purple
"neutral": "#888888",
}
_VERDICT = {
"doorbell": "",
"hmq": "",
"rdma": "",
"ipcq": "",
}
_TITLE = {
"doorbell": "Case 1: Doorbell + Polling",
"hmq": "Case 2: HMQ (HW Message Queue)",
"rdma": "Case 3: RDMA-CQ (Completion Queue)",
"ipcq": "Case 4 ★: IPCQ (HW Ring + Credit Return)",
}
_SHORT = {
"doorbell": "Case 1:\nDoorbell + Polling",
"hmq": "Case 2: HMQ\n(HW Message Queue)",
"rdma": "Case 3: RDMA-CQ\n(Completion Queue)",
"ipcq": "Case 4 ★: IPCQ\n(HW Ring + Credit Return)",
}
_ORDER = ["doorbell", "hmq", "rdma", "ipcq"]
# ─── Diagram 1 : 2×2 architecture blocks ────────────────────────────
def _plot_architecture() -> Path:
fig, axes = plt.subplots(2, 2, figsize=(17.0, 12.0))
def _block(ax, x, y, w, h, text, color="#dde3ec", border="#333",
fontsize=9.5, bold=True):
ax.add_patch(mpatches.FancyBboxPatch(
(x, y), w, h, boxstyle="round,pad=0.02,rounding_size=0.04",
facecolor=color, edgecolor=border, linewidth=1.4))
ax.text(x + w / 2, y + h / 2, text,
ha="center", va="center", fontsize=fontsize,
fontweight="bold" if bold else "normal")
def _arrow(ax, x1, y1, x2, y2, color="#333", lw=1.7, style="-"):
ax.annotate("", xy=(x2, y2), xytext=(x1, y1),
arrowprops=dict(arrowstyle="->", color=color,
lw=lw, ls=style, shrinkA=2, shrinkB=2))
def _verdict_box(ax, kind):
# Name plate at the bottom of every panel. IPCQ (chosen) gets a
# green background; the others get an architecture-tinted bg.
is_chosen = kind == "ipcq"
ax.text(5.0, 0.75, _TITLE[kind],
ha="center", va="center", fontsize=11.5, fontweight="bold",
color=("#1B5E20" if is_chosen else "white"),
bbox=dict(
facecolor=("#E8F5E9" if is_chosen else _COLOR[kind]),
edgecolor=("#2E7D32" if is_chosen else _COLOR[kind]),
boxstyle="round,pad=0.50", linewidth=1.8))
def _set_panel(ax, kind):
ax.set_xlim(0, 10); ax.set_ylim(0, 10); ax.axis("off")
# Visible frame around the panel, tinted by case color.
ax.add_patch(mpatches.FancyBboxPatch(
(0.15, 0.15), 9.7, 9.7,
boxstyle="round,pad=0.05,rounding_size=0.10",
facecolor="white", edgecolor=_COLOR[kind],
linewidth=2.2, zorder=0))
# (D) IPCQ — Case 4, chosen
ax = axes[1, 1]; _set_panel(ax, "ipcq")
_block(ax, 0.5, 7.0, 2.4, 1.4, "Sender PE\nkernel\n(tl.send)",
color="#cfe2ff", border=_COLOR["ipcq"])
_block(ax, 0.5, 4.6, 2.4, 1.5,
"PE_IPCQ (HW)\nhead/tail/credit\n→ in-line ring push",
color="#dee9f5", border=_COLOR["ipcq"], fontsize=8)
_block(ax, 0.5, 2.2, 2.4, 1.5,
"PE_DMA\ndirect push\n(no host CPU)",
color="#dee9f5", border=_COLOR["ipcq"], fontsize=8)
_block(ax, 4.0, 3.5, 2.0, 2.0, "NoC / UCIe\n(direction-addr)",
color="#f5f5f5", border=_COLOR["neutral"], fontsize=8.5)
_block(ax, 7.2, 2.2, 2.4, 1.5, "PE_DMA\nlanding\nTCM",
color="#dee9f5", border=_COLOR["ipcq"], fontsize=8)
_block(ax, 7.2, 4.6, 2.4, 1.5,
"PE_IPCQ (HW)\ntail advance +\ncredit piggyback",
color="#dee9f5", border=_COLOR["ipcq"], fontsize=8)
_block(ax, 7.2, 7.0, 2.4, 1.4, "Receiver PE\nkernel\n(tl.recv)",
color="#cfe2ff", border=_COLOR["ipcq"])
_arrow(ax, 1.7, 7.0, 1.7, 6.1, color=_COLOR["ipcq"])
_arrow(ax, 1.7, 4.6, 1.7, 3.7, color=_COLOR["ipcq"])
_arrow(ax, 2.9, 2.95, 4.0, 4.0, color=_COLOR["ipcq"])
_arrow(ax, 6.0, 4.0, 7.2, 2.95, color=_COLOR["ipcq"])
_arrow(ax, 8.4, 3.7, 8.4, 4.6, color=_COLOR["ipcq"])
_arrow(ax, 8.4, 6.1, 8.4, 7.0, color=_COLOR["ipcq"])
ax.annotate("", xy=(2.9, 5.35), xytext=(7.2, 5.35),
arrowprops=dict(arrowstyle="->", color=_COLOR["ipcq"],
ls=":", lw=1.0))
ax.text(5.0, 5.6, "credit return (piggyback)", ha="center",
fontsize=7.5, style="italic", color=_COLOR["ipcq"])
_verdict_box(ax, "ipcq")
# (A) Doorbell + polling — Case 1
ax = axes[0, 0]; _set_panel(ax, "doorbell")
_block(ax, 0.5, 7.0, 2.4, 1.4, "Sender\nkernel",
color="#fde2e0", border=_COLOR["doorbell"])
_block(ax, 0.5, 4.6, 2.4, 1.5, "DMA write\n(data)",
color="#f5e0dd", border=_COLOR["doorbell"], fontsize=8.5)
_block(ax, 0.5, 2.2, 2.4, 1.5, "DMA write\n(doorbell)",
color="#f5e0dd", border=_COLOR["doorbell"], fontsize=8.5)
_block(ax, 4.0, 3.5, 2.0, 2.0, "NoC / UCIe",
color="#f5f5f5", border=_COLOR["neutral"], fontsize=8.5)
_block(ax, 7.2, 4.6, 2.4, 1.5, "Doorbell\nregister",
color="#f5e0dd", border=_COLOR["doorbell"], fontsize=8.5)
_block(ax, 7.2, 2.2, 2.4, 1.5,
"Receiver\nlanding TCM",
color="#f5e0dd", border=_COLOR["doorbell"], fontsize=8.5)
_block(ax, 7.2, 7.0, 2.4, 1.4,
"Receiver\nkernel\n(poll / IRQ)",
color="#fde2e0", border=_COLOR["doorbell"])
_arrow(ax, 1.7, 7.0, 1.7, 6.1, color=_COLOR["doorbell"])
_arrow(ax, 1.7, 4.6, 1.7, 3.7, color=_COLOR["doorbell"])
_arrow(ax, 2.9, 5.35, 4.0, 4.5, color=_COLOR["doorbell"])
_arrow(ax, 2.9, 2.95, 4.0, 3.7, color=_COLOR["doorbell"])
_arrow(ax, 6.0, 4.5, 7.2, 5.35, color=_COLOR["doorbell"])
_arrow(ax, 6.0, 3.7, 7.2, 2.95, color=_COLOR["doorbell"])
_arrow(ax, 8.4, 6.1, 8.4, 7.0, color=_COLOR["doorbell"])
_verdict_box(ax, "doorbell")
# (B) HMQ — Case 2
ax = axes[0, 1]; _set_panel(ax, "hmq")
_block(ax, 0.5, 7.0, 2.4, 1.4, "Sender CPU\nbuild desc.",
color="#ffe0c4", border=_COLOR["hmq"])
_block(ax, 0.5, 4.6, 2.4, 1.5, "HMQ engine\n(push desc.)",
color="#f5e0c8", border=_COLOR["hmq"], fontsize=8.5)
_block(ax, 0.5, 2.2, 2.4, 1.5, "PE_DMA\n(large tensor)",
color="#f5e0c8", border=_COLOR["hmq"], fontsize=8.5)
_block(ax, 4.0, 3.5, 2.0, 2.0, "NoC / UCIe\n(both paths)",
color="#f5f5f5", border=_COLOR["neutral"], fontsize=8.5)
_block(ax, 7.2, 4.6, 2.4, 1.5, "Peer HMQ\n(pop desc.)",
color="#f5e0c8", border=_COLOR["hmq"], fontsize=8.5)
_block(ax, 7.2, 2.2, 2.4, 1.5, "PE_DMA\nlanding TCM",
color="#f5e0c8", border=_COLOR["hmq"], fontsize=8.5)
_block(ax, 7.2, 7.0, 2.4, 1.4, "Receiver CPU\nuse data ptr",
color="#ffe0c4", border=_COLOR["hmq"])
_arrow(ax, 1.7, 7.0, 1.7, 6.1, color=_COLOR["hmq"])
_arrow(ax, 1.7, 4.6, 1.7, 3.7, color=_COLOR["hmq"])
_arrow(ax, 2.9, 5.35, 4.0, 4.5, color=_COLOR["hmq"], style="--")
_arrow(ax, 2.9, 2.95, 4.0, 3.7, color=_COLOR["hmq"])
_arrow(ax, 6.0, 4.5, 7.2, 5.35, color=_COLOR["hmq"], style="--")
_arrow(ax, 6.0, 3.7, 7.2, 2.95, color=_COLOR["hmq"])
_arrow(ax, 8.4, 4.6, 8.4, 3.7, color=_COLOR["hmq"], style=":")
_arrow(ax, 8.4, 6.1, 8.4, 7.0, color=_COLOR["hmq"])
ax.text(5.0, 6.6, "desc path (dashed) + DMA path (solid)\n"
"= duplicated datapath",
ha="center", fontsize=7.5, style="italic",
color=_COLOR["hmq"])
_verdict_box(ax, "hmq")
# (C) RDMA-CQ — Case 3
ax = axes[1, 0]; _set_panel(ax, "rdma")
_block(ax, 0.5, 7.0, 2.4, 1.4, "Sender\nkernel",
color="#e6d8f0", border=_COLOR["rdma"])
_block(ax, 0.5, 4.6, 2.4, 1.5,
"DMA write\n(carries CQE\ntag)",
color="#ddd2eb", border=_COLOR["rdma"], fontsize=8.5)
_block(ax, 0.5, 2.2, 2.4, 1.5,
"WQE\n(work-queue entry)",
color="#ddd2eb", border=_COLOR["rdma"], fontsize=8.5)
_block(ax, 4.0, 3.5, 2.0, 2.0, "NoC / UCIe",
color="#f5f5f5", border=_COLOR["neutral"], fontsize=8.5)
_block(ax, 7.2, 4.6, 2.4, 1.5,
"CQ\n(auto-posted\nCQE)",
color="#ddd2eb", border=_COLOR["rdma"], fontsize=8.5)
_block(ax, 7.2, 2.2, 2.4, 1.5,
"Receiver\nlanding TCM",
color="#ddd2eb", border=_COLOR["rdma"], fontsize=8.5)
_block(ax, 7.2, 7.0, 2.4, 1.4,
"Receiver\nkernel\n(poll CQ / IRQ)",
color="#e6d8f0", border=_COLOR["rdma"])
_arrow(ax, 1.7, 7.0, 1.7, 6.1, color=_COLOR["rdma"])
_arrow(ax, 1.7, 4.6, 1.7, 3.7, color=_COLOR["rdma"])
_arrow(ax, 2.9, 5.35, 4.0, 4.5, color=_COLOR["rdma"])
_arrow(ax, 2.9, 2.95, 4.0, 3.7, color=_COLOR["rdma"], style="--")
_arrow(ax, 6.0, 4.5, 7.2, 5.35, color=_COLOR["rdma"])
_arrow(ax, 6.0, 3.7, 7.2, 2.95, color=_COLOR["rdma"], style="--")
_arrow(ax, 8.4, 6.1, 8.4, 7.0, color=_COLOR["rdma"])
_verdict_box(ax, "rdma")
fig.suptitle(
"IPCQ HW Alternatives — Architecture Comparison",
fontsize=14.5, fontweight="bold", y=0.995)
fig.tight_layout(rect=(0, 0, 1, 0.97))
out = _OUT_DIR / "ipcq_alternatives_architecture.png"
fig.savefig(out, dpi=150, bbox_inches="tight")
plt.close(fig)
return out
# Module-level flow-diagram helpers and constants.
_FLOW_DATA = "#6699CC" # blue flit (data)
_FLOW_CTRL = "#ED7D65" # orange flit (control)
_FLOW_CRED = "#7BB661" # green (credit / ack)
# Font-size multiplier for the flow helpers. The stacked figure renders at
# full text width (a two-column figure*), so its fonts are scaled up here so
# they stay legible after the figure is fit to the page. Default 1.0 leaves
# the 2×2 flow figure unchanged.
_FS = 1.0
def _flow_swim(ax, x, y, w, h, label, sub=None):
ax.add_patch(mpatches.FancyBboxPatch(
(x, y), w, h,
boxstyle="round,pad=0.02,rounding_size=0.08",
facecolor="white", edgecolor="#3a3a3a", linewidth=1.6))
ax.text(x + w / 2, y + h / 2 + (0.20 if sub else 0),
label, ha="center", va="center",
fontsize=10.5 * _FS, fontweight="bold", color="#111")
if sub:
ax.text(x + w / 2, y + h / 2 - 0.35, sub,
ha="center", va="center",
fontsize=8 * _FS, color="#555", fontstyle="italic")
def _flow_flit(ax, x, y, color, letter="", size=0.32):
ax.add_patch(mpatches.FancyBboxPatch(
(x - size / 2, y - size / 2), size, size,
boxstyle="round,pad=0.01,rounding_size=0.04",
facecolor=color, edgecolor="#222", linewidth=0.8))
if letter:
ax.text(x, y, letter, ha="center", va="center",
fontsize=8.5 * _FS, fontweight="bold", color="white")
def _flow_wire(ax, x1, x2, y, color="#888"):
ax.plot([x1, x2], [y, y], color=color, lw=1.0, zorder=0)
def _flow_dot(ax, x, y, color):
ax.add_patch(mpatches.Circle((x, y), 0.13,
facecolor=color, edgecolor="#333",
linewidth=0.8, zorder=4))
def _flow_label(ax, x, y, text, color="#222", fontsize=9):
ax.text(x, y, text, ha="center", va="center",
fontsize=fontsize * _FS, color=color, fontweight="bold")
def _flow_lead(ax, x_tail, y_tail, x_head, y_head, color="#777"):
ax.plot([x_tail, x_head], [y_tail, y_head],
color=color, lw=0.8, ls=":", zorder=2)
def _flow_setup(ax, kind):
ax.set_xlim(0, 20); ax.set_ylim(0, 10); ax.axis("off")
# Thin, subtle outer frame so the per-case flow inside dominates.
ax.add_patch(mpatches.FancyBboxPatch(
(0.15, 0.15), 19.7, 9.7,
boxstyle="round,pad=0.05,rounding_size=0.12",
facecolor="white", edgecolor=_COLOR[kind],
linewidth=0.9, alpha=0.85, zorder=0))
ax.text(10, 0.65, _TITLE[kind],
ha="center", va="center", fontsize=11.5 * _FS,
fontweight="bold",
color=("#1B5E20" if kind == "ipcq" else "white"),
bbox=dict(
facecolor=("#E8F5E9" if kind == "ipcq" else _COLOR[kind]),
edgecolor=("#2E7D32" if kind == "ipcq" else _COLOR[kind]),
boxstyle="round,pad=0.40", linewidth=1.5))
def _draw_flow_case(ax, kind):
"""Draw one Sender → NoC → Receiver flow panel for `kind`."""
_flow_setup(ax, kind)
if kind == "doorbell":
_draw_case_doorbell(ax)
elif kind == "hmq":
_draw_case_hmq(ax)
elif kind == "rdma":
_draw_case_rdma(ax)
elif kind == "ipcq":
_draw_case_ipcq(ax)
def _draw_case_doorbell(ax):
_flow_swim(ax, 0.7, 4.4, 2.8, 2.0, "Sender\nkernel")
_flow_swim(ax, 7.4, 4.4, 3.0, 2.0, "NoC / UCIe", sub="2 DMA tx")
_flow_swim(ax, 14.4, 4.4, 3.0, 2.0, "Doorbell reg\n+ landing TCM")
_flow_swim(ax, 17.6, 6.7, 2.0, 1.7, "Receiver\n(poll)", sub="poll loop")
_flow_wire(ax, 3.5, 7.4, 5.7); _flow_wire(ax, 10.4, 14.4, 5.7)
for x in [4.0, 4.7, 5.4]:
_flow_flit(ax, x, 5.7, _FLOW_DATA, "D")
for x in [11.0, 11.7]:
_flow_flit(ax, x, 5.7, _FLOW_DATA, "D")
_flow_wire(ax, 3.5, 7.4, 5.1); _flow_wire(ax, 10.4, 14.4, 5.1)
_flow_flit(ax, 4.0, 5.1, _FLOW_CTRL, "F")
_flow_flit(ax, 4.7, 5.1, _FLOW_CTRL, "DB")
_flow_flit(ax, 11.0, 5.1, _FLOW_CTRL, "DB")
for y in (8.0, 7.5, 7.0):
ax.annotate("", xy=(15.5, y), xytext=(17.6, y),
arrowprops=dict(arrowstyle="->", color="#aa3a30",
lw=1.0, ls=":"))
_flow_dot(ax, 5.0, 3.5, "#c5a35a")
_flow_lead(ax, 5.0, 3.6, 5.0, 4.4)
_flow_label(ax, 5.0, 3.2, "fence + 2 DMA writes", color="#5a3e20")
_flow_dot(ax, 17.0, 8.8, "#7BB661")
_flow_lead(ax, 17.0, 8.7, 17.0, 8.0)
_flow_label(ax, 17.0, 9.1, "receiver polling loop", color="#2E7D32")
def _draw_case_hmq(ax):
_flow_swim(ax, 0.7, 4.4, 2.8, 2.0, "Sender CPU")
_flow_swim(ax, 4.6, 6.6, 2.8, 1.8, "HMQ engine", sub="descriptor")
_flow_swim(ax, 4.6, 3.6, 2.8, 1.8, "PE_DMA", sub="data path")
_flow_swim(ax, 9.6, 4.4, 3.0, 2.0, "NoC / UCIe", sub="2 paths")
_flow_swim(ax, 14.4, 6.6, 2.8, 1.8, "Peer HMQ")
_flow_swim(ax, 14.4, 3.6, 2.8, 1.8, "Recv TCM")
_flow_swim(ax, 17.6, 4.4, 2.0, 2.0, "Receiver\nCPU")
_flow_wire(ax, 3.5, 4.6, 7.5); _flow_wire(ax, 7.4, 9.6, 7.5)
_flow_wire(ax, 12.6, 14.4, 7.5); _flow_wire(ax, 17.2, 17.6, 5.4)
_flow_flit(ax, 4.0, 7.5, _FLOW_CTRL, "Hd")
_flow_flit(ax, 8.5, 7.5, _FLOW_CTRL, "Hd")
_flow_flit(ax, 13.5, 7.5, _FLOW_CTRL, "Hd")
_flow_wire(ax, 3.5, 4.6, 4.5); _flow_wire(ax, 7.4, 9.6, 4.5)
_flow_wire(ax, 12.6, 14.4, 4.5)
for x in [4.0, 8.0, 8.7, 13.5]:
_flow_flit(ax, x, 4.5, _FLOW_DATA, "D")
_flow_dot(ax, 11.1, 9.0, "#c5a35a")
_flow_lead(ax, 11.1, 8.9, 11.1, 7.6)
_flow_label(ax, 11.1, 9.25, "duplicated control + data paths",
color="#5a3e20")
def _draw_case_rdma(ax):
_flow_swim(ax, 0.7, 4.4, 2.8, 2.0, "Sender\nkernel")
_flow_swim(ax, 4.8, 4.4, 2.8, 2.0, "WQE +\nPE_DMA")
_flow_swim(ax, 8.5, 4.4, 3.0, 2.0, "NoC / UCIe")
_flow_swim(ax, 13.0, 4.4, 3.0, 2.0, "CQ + landing\nTCM",
sub="auto-CQE")
_flow_swim(ax, 17.0, 6.7, 2.5, 1.7, "Receiver\n(poll CQ)")
_flow_wire(ax, 3.5, 4.8, 5.7)
_flow_flit(ax, 4.0, 5.7, _FLOW_CTRL, "W")
_flow_wire(ax, 7.6, 8.5, 5.4); _flow_wire(ax, 11.5, 13.0, 5.4)
_flow_flit(ax, 8.1, 5.4, _FLOW_DATA, "D")
_flow_flit(ax, 9.2, 5.4, _FLOW_DATA, "D")
_flow_flit(ax, 9.9, 5.4, _FLOW_CTRL, "Q")
_flow_flit(ax, 11.0, 5.4, _FLOW_DATA, "D")
_flow_flit(ax, 12.0, 5.4, _FLOW_CTRL, "Q")
for y in (8.0, 7.5, 7.0):
ax.annotate("", xy=(14.5, y), xytext=(17.0, y),
arrowprops=dict(arrowstyle="->", color="#7c1b78",
lw=1.0, ls=":"))
_flow_dot(ax, 11.0, 3.5, "#c5a35a")
_flow_lead(ax, 11.0, 3.6, 11.0, 4.4)
_flow_label(ax, 11.0, 3.2, "CQE rides with DMA payload",
color="#5a3e20")
_flow_dot(ax, 18.0, 8.8, "#7BB661")
_flow_lead(ax, 18.0, 8.7, 18.0, 8.0)
_flow_label(ax, 18.0, 9.1, "CQ poll / IRQ", color="#2E7D32")
def _draw_case_ipcq(ax):
_flow_swim(ax, 0.7, 4.4, 2.8, 2.0, "Sender PE\n(tl.send)")
_flow_swim(ax, 4.5, 6.6, 3.0, 1.8, "PE_IPCQ", sub="ring push")
_flow_swim(ax, 4.5, 3.6, 3.0, 1.8, "PE_DMA", sub="data push")
_flow_swim(ax, 9.4, 4.4, 3.0, 2.0, "NoC / UCIe", sub="dir-addr")
_flow_swim(ax, 13.8, 6.6, 3.0, 1.8, "PE_IPCQ",
sub="tail++ , credit")
_flow_swim(ax, 13.8, 3.6, 3.0, 1.8, "PE_DMA",
sub="landing TCM")
_flow_swim(ax, 17.6, 4.4, 2.0, 2.0, "Receiver\nPE\n(tl.recv)")
_flow_wire(ax, 3.5, 4.5, 7.5); _flow_wire(ax, 7.5, 9.4, 7.5)
_flow_wire(ax, 12.4, 13.8, 7.5); _flow_wire(ax, 16.8, 17.6, 5.4)
_flow_flit(ax, 4.0, 7.5, _FLOW_CTRL, "M")
_flow_flit(ax, 8.4, 7.5, _FLOW_CTRL, "M")
_flow_flit(ax, 13.0, 7.5, _FLOW_CTRL, "M")
_flow_wire(ax, 3.5, 4.5, 4.5); _flow_wire(ax, 7.5, 9.4, 4.5)
_flow_wire(ax, 12.4, 13.8, 4.5)
_flow_flit(ax, 4.0, 4.5, _FLOW_DATA, "D")
_flow_flit(ax, 8.4, 4.5, _FLOW_DATA, "D")
_flow_flit(ax, 11.0, 4.5, _FLOW_DATA, "D")
_flow_flit(ax, 13.0, 4.5, _FLOW_DATA, "D")
ax.annotate("", xy=(4.5, 9.0), xytext=(16.8, 9.0),
arrowprops=dict(arrowstyle="->", color=_FLOW_CRED,
lw=1.6, ls=(0, (5, 3))))
_flow_label(ax, 10.5, 9.35, "credit piggyback (no separate ACK)",
color=_FLOW_CRED)
_flow_dot(ax, 6.0, 2.6, "#c5a35a")
_flow_lead(ax, 6.0, 2.7, 6.0, 3.6)
_flow_label(ax, 6.0, 2.3, "one in-line ring push + one DMA",
color="#5a3e20")
def _flow_legend(fig, leg_y=0.02):
"""Two-row legend so the long control-flit key doesn't overlap
the shorter data/credit entries."""
row_top = [
(_FLOW_DATA, "Data flit D = payload"),
(_FLOW_CRED, "Credit / piggyback return"),
]
row_bottom = [
(_FLOW_CTRL, "Control flit: W=WQE · Q=CQE · M=ring meta"
" · DB=doorbell · F=fence · Hd=HMQ desc"),
]
def _emit(items, y):
lx = 0.04
for color, text in items:
fig.patches.append(mpatches.FancyBboxPatch(
(lx, y), 0.020, 0.022,
boxstyle="round,pad=0.005,rounding_size=0.005",
facecolor=color, edgecolor="#222", linewidth=0.6,
transform=fig.transFigure))
fig.text(lx + 0.025, y + 0.011, text,
ha="left", va="center", fontsize=10 * _FS,
fontweight="bold", color="#222")
lx += 0.32
_emit(row_top, y=leg_y + 0.030)
_emit(row_bottom, y=leg_y)
# ─── Diagram 1b : 4-case flow-style architecture (latency_model look)
def _plot_architecture_flow() -> Path:
"""4 panels arranged 2×2 — each a horizontal Sender → NoC → Receiver
flow with small flit-style packets, dot-marker annotations and a
bottom name plate. Aesthetic mirrors latency_model.png. Fonts scaled
up via _FS so the figure stays legible when fit to the page."""
global _FS
_FS = 1.5
try:
fig, axes = plt.subplots(2, 2, figsize=(18.0, 11.5))
_draw_flow_case(axes[0, 0], "doorbell")
_draw_flow_case(axes[0, 1], "hmq")
_draw_flow_case(axes[1, 0], "rdma")
_draw_flow_case(axes[1, 1], "ipcq")
fig.suptitle(
"Per-send architecture = $\\Sigma$ control overhead + "
"data-path DMA + receiver wake",
fontsize=14.5 * _FS, fontweight="bold", y=0.995)
_flow_legend(fig, leg_y=0.015)
fig.tight_layout(rect=(0, 0.07, 1, 0.96))
out = _OUT_DIR / "ipcq_alternatives_architecture_flow.png"
fig.savefig(out, dpi=150, bbox_inches="tight")
plt.close(fig)
finally:
_FS = 1.0
return out
# ─── Diagram 1c : 4-case flow-style architecture — stacked 4×1 ──────
def _plot_architecture_stacked() -> Path:
"""Same per-case flow content as the 2×2 figure, but stacked one
case per row (4 rows × 1 column). Wider panels give each case more
horizontal room. Rendered at full text width (two-column figure*), so
fonts are scaled up via _FS to stay legible after page fitting."""
global _FS
_FS = 1.9
try:
fig, axes = plt.subplots(4, 1, figsize=(13.0, 15.0))
for ax, kind in zip(axes, ["doorbell", "hmq", "rdma", "ipcq"]):
_draw_flow_case(ax, kind)
fig.suptitle(
"Per-send architecture = $\\Sigma$ control overhead + "
"data-path DMA + receiver wake",
fontsize=14.5 * _FS, fontweight="bold", y=0.997)
_flow_legend(fig, leg_y=0.010)
fig.tight_layout(rect=(0, 0.06, 1, 0.97))
out = _OUT_DIR / "ipcq_alternatives_architecture_stacked.png"
fig.savefig(out, dpi=150, bbox_inches="tight")
plt.close(fig)
finally:
_FS = 1.0
return out
# ─── Diagram 2 : Per-send latency breakdown (vertical stacks) ───────
def _plot_latency() -> Path:
# (label, short, illustrative cycles, fill)
steps = {
"ipcq": [
("kernel tl.send", "tl.send", 5, "#7CA7D9"),
("PE_IPCQ ring push", "ring push", 4, "#9CBCDE"),
("PE_DMA push (NoC hop)", "DMA push", 12, "#BCD2E5"),
("PE_IPCQ tail + credit", "tail+credit", 4, "#DCE5ED"),
("kernel tl.recv unblock", "tl.recv", 3, "#EEF2F6"),
],
"doorbell": [
("DMA write data", "DMA(data)", 12, "#D58680"),
("Fence (data before doorbell)", "fence", 6, "#DC9C95"),
("DMA write doorbell", "DMA(db)", 10, "#E1AFA8"),
("Receiver poll loop / IRQ", "poll / IRQ", 25, "#E8C4BE"),
("Kernel use data", "use data", 3, "#EFD8D4"),
],
"hmq": [
("CPU build descriptor", "build desc", 8, "#E29465"),
("HMQ push (control path)", "HMQ push", 7, "#E5A57F"),
("DMA push (data, parallel)", "DMA push", 12, "#E8B69A"),
("Peer HMQ pop", "HMQ pop", 7, "#ECC9B6"),
("CPU read data ptr", "read ptr", 4, "#F1DBCE"),
],
"rdma": [
("Build WQE", "WQE", 8, "#A68FB8"),
("DMA write + CQE post", "DMA+CQE", 14, "#B6A4C4"),
("CQE arrives in peer CQ", "CQE arrive", 6, "#C5B9D0"),
("CQ poll / IRQ", "poll / IRQ", 22, "#D5CEDD"),
("Kernel use data", "use data", 3, "#E5E2EA"),
],
}
totals = {k: sum(c for _, _, c, _ in steps[k]) for k in _ORDER}
base = totals["ipcq"]
fig, ax = plt.subplots(figsize=(15.0, 9.0))
x = list(range(len(_ORDER)))
for i, k in enumerate(_ORDER):
bottom = 0
for name, short, cy, color in steps[k]:
ax.bar(x[i], cy, bottom=bottom, width=0.62, color=color,
edgecolor="black", linewidth=0.7)
# Wrap label so it fits within bar width.
if cy >= 8:
wrapped = textwrap.fill(name, width=18)
label = f"{wrapped}\n({cy} cy)"
fontsize = 8.5
else:
wrapped = textwrap.fill(short, width=12)
label = f"{wrapped}\n({cy})"
fontsize = 7.5
ax.text(x[i], bottom + cy / 2, label,
ha="center", va="center",
fontsize=fontsize, fontweight="bold", color="#111")
bottom += cy
# Total above bar.
ax.text(x[i], bottom + max(totals.values()) * 0.012,
f"{bottom} cy ({bottom/base:.1f}× IPCQ)",
ha="center", va="bottom", fontsize=10.5,
fontweight="bold", color=_COLOR[k])
# X-tick labels: case names (★ already baked into _SHORT for IPCQ).
ax.set_xticks(x)
ax.set_xticklabels([_SHORT[k] for k in _ORDER],
fontsize=10.5, fontweight="bold")
for tick, k in zip(ax.get_xticklabels(), _ORDER):
tick.set_color(_COLOR[k])
ax.set_ylabel("illustrative cycles per single PE-to-PE send")
ax.set_ylim(0, max(totals.values()) * 1.18)
ax.grid(axis="y", ls=":", alpha=0.5)
ax.set_title(
"Per-send timing — control + data path "
"(illustrative cycle stack)",
fontsize=12.5, fontweight="bold", pad=12)
fig.tight_layout()
out = _OUT_DIR / "ipcq_alternatives_latency.png"
fig.savefig(out, dpi=150)
plt.close(fig)
return out
# ─── Diagram 3 : Op-count / control-plane traffic ───────────────────
def _plot_opcounts() -> Path:
# Each row = one ops-category, columns = the 4 alternatives.
categories = [
"DMA transactions\n(data path)",
"Doorbell\nregister writes",
"HMQ descriptor\npush/pop pairs",
"CQE\n(completion entry)",
"Polling /\nIRQ wake-ups",
"Ring slot\nadvance (HW)",
"Credit return\npacket",
]
# Per-architecture op count (in declared _ORDER: door,hmq,rdma,ipcq).
counts_by_kind = {
# cat: door hmq rdma ipcq
"doorbell": [2, 1, 0, 0, 1, 0, 0],
"hmq": [1, 0, 1, 0, 0, 0, 0],
"rdma": [1, 0, 0, 1, 1, 0, 0],
"ipcq": [1, 0, 0, 0, 0, 1, 1],
}
totals = {k: sum(counts_by_kind[k]) for k in _ORDER}
x = list(range(len(categories)))
w = 0.18
fig, ax = plt.subplots(figsize=(14.0, 6.0))
offsets = [-1.5 * w, -0.5 * w, 0.5 * w, 1.5 * w]
for c, kind in enumerate(_ORDER):
vals = counts_by_kind[kind]
ax.bar([xi + offsets[c] for xi in x], vals, width=w,
color=_COLOR[kind], edgecolor="black",
label=f"{_SHORT[kind]} (total {totals[kind]})")
ax.set_xticks(x)
ax.set_xticklabels(categories, fontsize=9)
ax.set_ylabel("ops per single PE-to-PE data transfer")
ax.set_yticks([0, 1, 2])
ax.set_title(
"Control-plane op count per data transfer\n"
"Case 1: 2 DMA + poll · Case 2: desc push/pop + DMA · "
"Case 3: DMA + CQE + poll · "
"Case 4 (IPCQ): 1 DMA + in-line ring + credit (piggybacked v2)",
fontsize=11.5, fontweight="bold")
ax.grid(axis="y", ls=":", alpha=0.5)
ax.legend(loc="upper right", fontsize=9.5, framealpha=0.95, ncol=2)
ax.set_ylim(0, 2.6)
fig.tight_layout()
out = _OUT_DIR / "ipcq_alternatives_opcounts.png"
fig.savefig(out, dpi=150)
plt.close(fig)
return out
# ─── Diagram 4 : 2×2 swimlane sequence per implementation ───────────
def _plot_sequence() -> Path:
fig, axes = plt.subplots(2, 2, figsize=(16.0, 11.0))
def _lane(ax, x, label, color):
ax.axvline(x, ymin=0.04, ymax=0.96, color=color, lw=2.0, alpha=0.7)
ax.text(x, 9.65, label, ha="center", va="bottom",
fontsize=10, fontweight="bold", color=color)
def _msg(ax, x1, x2, y, text, color="#333", style="-"):
ax.annotate("", xy=(x2, y), xytext=(x1, y),
arrowprops=dict(arrowstyle="->", color=color,
lw=1.3, ls=style))
ax.text((x1 + x2) / 2, y + 0.18, text, ha="center",
fontsize=8, color="#222", fontweight="bold",
bbox=dict(facecolor="white", edgecolor="none", pad=1.0))
def _act(ax, x, y_top, y_bot, color="#9bbb59", w=0.30):
ax.add_patch(mpatches.Rectangle(
(x - w / 2, y_bot), w, y_top - y_bot,
facecolor=color, edgecolor="black", linewidth=0.4, alpha=0.5))
def _setup(ax, kind, subtitle):
ax.set_xlim(0, 10); ax.set_ylim(0, 10); ax.axis("off")
ax.set_title(f"{_TITLE[kind]}\n{subtitle}",
fontsize=11, fontweight="bold",
color=_COLOR[kind], pad=8)
def _verdict(ax, kind):
if not _VERDICT[kind]:
return
ax.text(5.0, 0.5, _VERDICT[kind],
ha="center", va="center", fontsize=10, fontweight="bold",
bbox=dict(facecolor="#E8F5E9", edgecolor="#2E7D32",
boxstyle="round,pad=0.4", linewidth=1.5))
# (D) IPCQ — Case 4, chosen
ax = axes[1, 1]
_setup(ax, "ipcq",
"tl.send → ring push → DMA → tail advance + credit piggyback → tl.recv")
_lane(ax, 1.5, "Sender\nkernel", _COLOR["ipcq"])
_lane(ax, 5.0, "PE_IPCQ +\nPE_DMA (HW)", "#666")
_lane(ax, 8.5, "Receiver\nkernel", _COLOR["ipcq"])
_act(ax, 1.5, 9.0, 7.0)
_msg(ax, 1.5, 5.0, 8.4, "tl.send(payload)", color=_COLOR["ipcq"])
_act(ax, 5.0, 7.0, 5.0, color="#88aacc")
_msg(ax, 5.0, 8.5, 6.0, "DMA push → TCM", color=_COLOR["ipcq"])
_msg(ax, 5.0, 1.5, 5.2, "tail advance (HW)",
color=_COLOR["ipcq"], style=":")
_act(ax, 8.5, 5.5, 3.0)
_msg(ax, 8.5, 1.5, 3.7, "credit (piggyback)",
color=_COLOR["ipcq"], style=":")
_msg(ax, 8.5, 8.5, 2.5, "tl.recv → unblock", color=_COLOR["ipcq"])
_verdict(ax, "ipcq")
# (A) Doorbell + polling — Case 1
ax = axes[0, 0]
_setup(ax, "doorbell",
"DMA(data) → fence → DMA(doorbell) → peer polls")
_lane(ax, 1.5, "Sender\nkernel", _COLOR["doorbell"])
_lane(ax, 4.3, "PE_DMA", "#666")
_lane(ax, 6.7, "Doorbell\nreg + TCM", "#666")
_lane(ax, 9.0, "Receiver\nkernel", _COLOR["doorbell"])
_act(ax, 1.5, 9.0, 6.5)
_msg(ax, 1.5, 4.3, 8.4, "issue DMA(data)", color=_COLOR["doorbell"])
_msg(ax, 4.3, 6.7, 7.5, "data → TCM", color=_COLOR["doorbell"])
_msg(ax, 1.5, 4.3, 6.8, "fence + DMA(doorbell)",
color=_COLOR["doorbell"])
_msg(ax, 4.3, 6.7, 6.1, "doorbell write",
color=_COLOR["doorbell"])
_act(ax, 9.0, 5.5, 1.5)
_msg(ax, 9.0, 6.7, 4.5, "poll doorbell ...",
color=_COLOR["doorbell"], style=":")
_msg(ax, 9.0, 6.7, 3.5, " poll doorbell ...",
color=_COLOR["doorbell"], style=":")
_msg(ax, 9.0, 6.7, 2.5, " poll → set",
color=_COLOR["doorbell"])
_verdict(ax, "doorbell")
# (B) HMQ — Case 2
ax = axes[0, 1]
_setup(ax, "hmq",
"CPU build desc → HMQ push (control) + DMA (data) → peer pop + DMA recv")
_lane(ax, 1.0, "Sender\nCPU", _COLOR["hmq"])
_lane(ax, 3.4, "HMQ\nengine", "#666")
_lane(ax, 5.6, "PE_DMA", "#666")
_lane(ax, 7.8, "Peer HMQ +\nTCM", "#666")
_lane(ax, 9.5, "Receiver\nCPU", _COLOR["hmq"])
_act(ax, 1.0, 9.0, 6.5)
_msg(ax, 1.0, 3.4, 8.4, "build desc", color=_COLOR["hmq"])
_msg(ax, 1.0, 5.6, 7.5, "DMA push (data)", color=_COLOR["hmq"])
_msg(ax, 3.4, 7.8, 6.8, "desc relay",
color=_COLOR["hmq"], style="--")
_msg(ax, 5.6, 7.8, 5.8, "data → TCM", color=_COLOR["hmq"])
_act(ax, 9.5, 5.0, 2.0)
_msg(ax, 9.5, 7.8, 4.0, "pop desc",
color=_COLOR["hmq"], style="--")
_msg(ax, 9.5, 9.5, 3.0, "use ptr", color=_COLOR["hmq"])
_verdict(ax, "hmq")
# (C) RDMA-CQ — Case 3
ax = axes[1, 0]
_setup(ax, "rdma",
"WQE post → DMA + auto-CQE → peer CQ poll / IRQ")
_lane(ax, 1.0, "Sender\nkernel", _COLOR["rdma"])
_lane(ax, 3.4, "WQE +\nDMA", "#666")
_lane(ax, 5.8, "NoC", "#666")
_lane(ax, 7.6, "Peer CQ +\nTCM", "#666")
_lane(ax, 9.5, "Receiver\nkernel", _COLOR["rdma"])
_act(ax, 1.0, 9.0, 6.5)
_msg(ax, 1.0, 3.4, 8.4, "post WQE", color=_COLOR["rdma"])
_msg(ax, 3.4, 5.8, 7.5, "DMA + CQE tag", color=_COLOR["rdma"])
_msg(ax, 5.8, 7.6, 6.5, "data + CQE", color=_COLOR["rdma"])
_act(ax, 9.5, 5.5, 1.5)
_msg(ax, 9.5, 7.6, 4.5, "poll CQ ...",
color=_COLOR["rdma"], style=":")
_msg(ax, 9.5, 7.6, 3.5, " poll CQ ...",
color=_COLOR["rdma"], style=":")
_msg(ax, 9.5, 7.6, 2.5, " CQE arrived",
color=_COLOR["rdma"])
_verdict(ax, "rdma")
fig.suptitle(
"PE-to-PE communication flow — IPCQ vs alternatives "
"(swimlane sequence)",
fontsize=14, fontweight="bold", y=0.995)
fig.tight_layout(rect=(0, 0, 1, 0.97))
out = _OUT_DIR / "ipcq_alternatives_sequence.png"
fig.savefig(out, dpi=150, bbox_inches="tight")
plt.close(fig)
return out
# ─── Diagram 5 : Decision matrix — why IPCQ won ────────────────────
def _plot_decision_matrix() -> Path:
"""Side-by-side comparison across the 5 criteria. Rows = criteria,
columns = the 4 architectures in case order. Green ✓ highlights the
cells where IPCQ wins; everything else is neutral.
"""
# Rows in column order: doorbell, hmq, rdma, ipcq.
criteria = [
("Latency (single send)",
"High (2 DMAs +\n fence + poll/IRQ)",
"Mid (desc relay\n + DMA parallel)",
"High (DMA + CQE\n + poll/IRQ)",
"Low (5 events,\n ~28 cy in-line)"),
("Host CPU on critical path?",
"Yes (issue both\n DMAs + poll)",
"Yes (CPU builds\n descriptor)",
"Yes (post WQE\n + poll CQ)",
"No (kernel-side\n HW push)"),
("Polling / IRQ wake-up?",
"Yes (poll or IRQ\n on doorbell)",
"No (CPU just\n reads desc ptr)",
"Yes (poll or IRQ\n on CQ)",
"No (tail advance\n + piggyback credit)"),
("Duplicated control + data\n datapaths?",
"No (1 datapath,\n but 2 DMAs)",
"Yes (HMQ engine\n + DMA in parallel)",
"Partial (CQE rides\n with DMA)",
"No (PE_IPCQ ctrl,\n PE_DMA data)"),
("Right-sized for single-owner\n PE-to-PE?",
"Yes",
"Yes (but extra HW)",
"Multi-tenant focus\n (extra isolation)",
"Yes"),
]
cols = [_SHORT[k] for k in _ORDER]
# Green ✓ only where IPCQ has a clear win.
good = {(r, "ipcq") for r in range(len(criteria))}
fig, ax = plt.subplots(figsize=(14.0, 8.0))
ax.axis("off")
n_rows = len(criteria) + 1
n_cols = len(cols) + 1
col_w = [3.0, 2.2, 2.2, 2.2, 2.2] # narrower cells
row_h = 1.30
# Header row
headers = ["Criterion"] + cols
x_left = 0.0
y_top = (n_rows - 1) * row_h
x_acc = [x_left]
for cw in col_w:
x_acc.append(x_acc[-1] + cw)
fig_w = x_acc[-1]
fig_h = n_rows * row_h
for ci, h in enumerate(headers):
cw = col_w[ci]
cx = x_acc[ci]
if ci == 0:
fill = "#1F4E79"; text_color = "white"
text = h
else:
kind = _ORDER[ci - 1]
fill = _COLOR[kind]; text_color = "white"
text = h + ("\n" + _VERDICT[kind] if _VERDICT[kind] else "")
ax.add_patch(mpatches.Rectangle(
(cx, y_top), cw, row_h, facecolor=fill,
edgecolor="black", linewidth=0.8))
ax.text(cx + cw / 2, y_top + row_h / 2, text,
ha="center", va="center", fontsize=11,
fontweight="bold", color=text_color)
# Body rows
for ri, row in enumerate(criteria):
rname, *cells = row
# Bottom-most rows render first → reverse so first criterion
# sits just below the header.
y = (n_rows - 2 - ri) * row_h
# Criterion cell.
ax.add_patch(mpatches.Rectangle(
(x_acc[0], y), col_w[0], row_h,
facecolor="#F0F4F8", edgecolor="black", linewidth=0.5))
ax.text(x_acc[0] + 0.15, y + row_h / 2, rname,
ha="left", va="center", fontsize=11.5,
fontweight="bold", color="#1F4E79")
# Architecture cells.
for ci, kind in enumerate(_ORDER):
cx = x_acc[ci + 1]; cw = col_w[ci + 1]
# Verdict word (Yes/No/High/Mid/Low/Partial/etc.) is the
# part before the first "(" — bolded. Bracketed qualifier
# stays regular weight.
raw = " ".join(cells[ci].split())
if "(" in raw:
pre, _, rest = raw.partition("(")
verdict = pre.strip()
bracket = textwrap.fill("(" + rest.strip(), width=22)
else:
verdict = raw
bracket = ""
if (ri, kind) in good:
fill = "#E8F5E9"; mark = ""
else:
fill = "#F5F7FA"; mark = ""
ax.add_patch(mpatches.Rectangle(
(cx, y), cw, row_h, facecolor=fill,
edgecolor="black", linewidth=0.5))
cy = y + row_h / 2
if bracket:
ax.text(cx + cw / 2, cy + 0.30, mark + verdict,
ha="center", va="center", fontsize=11.5,
fontweight="bold", color="#111")
ax.text(cx + cw / 2, cy - 0.30, bracket,
ha="center", va="center", fontsize=10,
color="#444")
else:
ax.text(cx + cw / 2, cy, mark + verdict,
ha="center", va="center", fontsize=11.5,
fontweight="bold", color="#111")
ax.set_xlim(-0.1, fig_w + 0.1)
ax.set_ylim(-0.1, fig_h + 0.1)
ax.set_title(
"Why IPCQ (HW Ring + Credit Return)? — decision matrix",
fontsize=14, fontweight="bold", pad=14)
fig.tight_layout()
out = _OUT_DIR / "ipcq_alternatives_decision_matrix.png"
fig.savefig(out, dpi=150, bbox_inches="tight")
plt.close(fig)
return out
# ─── Diagram 7 : Control vs Data plane overlay (4 panels) ───────────
def _plot_control_data_overlay() -> Path:
"""2×2 panels (one per case). Each panel uses solid arrows for data
path and dashed for control path so the reader sees where each
design moves the control burden."""
fig, axes = plt.subplots(2, 2, figsize=(16.0, 11.0))
DATA = "#1b4f8a"
CTRL = "#c0504d"
def _box(ax, x, y, w, h, text, fontsize=9):
ax.add_patch(mpatches.FancyBboxPatch(
(x, y), w, h, boxstyle="round,pad=0.02,rounding_size=0.06",
facecolor="#f7f7f7", edgecolor="#333", linewidth=1.2))
ax.text(x + w / 2, y + h / 2, text, ha="center", va="center",
fontsize=fontsize, fontweight="bold", color="#111")
def _arrow(ax, x1, y1, x2, y2, kind, lw=2.0):
color, style = (DATA, "-") if kind == "data" else (CTRL, (0, (5, 3)))
ax.annotate("", xy=(x2, y2), xytext=(x1, y1),
arrowprops=dict(arrowstyle="->", color=color,
lw=lw, ls=style, shrinkA=3, shrinkB=3))
def _setup(ax, kind):
ax.set_xlim(0, 10); ax.set_ylim(0, 10); ax.axis("off")
ax.add_patch(mpatches.FancyBboxPatch(
(0.15, 0.15), 9.7, 9.7,
boxstyle="round,pad=0.05,rounding_size=0.10",
facecolor="white", edgecolor=_COLOR[kind],
linewidth=2.2, zorder=0))
ax.text(5.0, 0.65, _TITLE[kind],
ha="center", va="center", fontsize=11.5,
fontweight="bold",
color=("#1B5E20" if kind == "ipcq" else "white"),
bbox=dict(
facecolor=("#E8F5E9" if kind == "ipcq" else _COLOR[kind]),
edgecolor=("#2E7D32" if kind == "ipcq" else _COLOR[kind]),
boxstyle="round,pad=0.40", linewidth=1.5))
# Helper to draw sender/NoC/receiver row of 5 nodes.
def _row(ax, labels):
xs = [1.0, 3.0, 5.0, 7.0, 9.0]
positions = []
for x, lbl in zip(xs, labels):
_box(ax, x - 0.9, 5.0, 1.8, 1.6, lbl, fontsize=8.5)
positions.append((x, 5.8))
return positions
# ── Case 1: Doorbell + Polling ──
ax = axes[0, 0]; _setup(ax, "doorbell")
pos = _row(ax, ["Sender\nkernel", "PE_DMA",
"NoC / UCIe", "Doorbell\nreg + TCM",
"Receiver\nkernel (poll)"])
# data path: kernel→DMA→NoC→TCM (top of node row visually)
_arrow(ax, pos[0][0] + 0.9, 5.8, pos[1][0] - 0.9, 5.8, "data")
_arrow(ax, pos[1][0] + 0.9, 6.0, pos[2][0] - 0.9, 6.0, "data")
_arrow(ax, pos[2][0] + 0.9, 6.0, pos[3][0] - 0.9, 6.0, "data")
# control path: sender→DMA (doorbell submit)→NoC→doorbell reg→poll
_arrow(ax, pos[0][0] + 0.9, 5.4, pos[1][0] - 0.9, 5.4, "ctrl")
_arrow(ax, pos[1][0] + 0.9, 5.2, pos[2][0] - 0.9, 5.2, "ctrl")
_arrow(ax, pos[2][0] + 0.9, 5.2, pos[3][0] - 0.9, 5.2, "ctrl")
_arrow(ax, pos[4][0] - 0.9, 5.2, pos[3][0] + 0.9, 5.2, "ctrl") # poll
ax.text(5.0, 7.4, "2 DMA transactions + receiver polling loop",
ha="center", fontsize=10.5, color="#333", style="italic")
# ── Case 2: HMQ ──
ax = axes[0, 1]; _setup(ax, "hmq")
pos = _row(ax, ["Sender\nCPU", "HMQ\nengine",
"NoC / UCIe", "Peer\nHMQ + TCM",
"Receiver\nCPU"])
# control: CPU → HMQ → NoC → Peer HMQ
_arrow(ax, pos[0][0] + 0.9, 6.0, pos[1][0] - 0.9, 6.0, "ctrl")
_arrow(ax, pos[1][0] + 0.9, 6.0, pos[2][0] - 0.9, 6.0, "ctrl")
_arrow(ax, pos[2][0] + 0.9, 6.0, pos[3][0] - 0.9, 6.0, "ctrl")
_arrow(ax, pos[3][0] + 0.9, 6.0, pos[4][0] - 0.9, 6.0, "ctrl")
# data (parallel path via PE_DMA): sender CPU → NoC → TCM
_arrow(ax, pos[0][0] + 0.9, 5.2, pos[2][0] - 0.9, 5.2, "data")
_arrow(ax, pos[2][0] + 0.9, 5.2, pos[3][0] - 0.9, 5.2, "data")
ax.text(5.0, 7.4, "control + data on TWO parallel paths "
"(duplicated)",
ha="center", fontsize=10.5, color="#333", style="italic")
# ── Case 3: RDMA-CQ ──
ax = axes[1, 0]; _setup(ax, "rdma")
pos = _row(ax, ["Sender\nkernel", "WQE + DMA",
"NoC / UCIe", "Peer CQ\n+ TCM",
"Receiver\nkernel (poll)"])
# data: kernel → DMA → NoC → TCM (with CQE tag)
_arrow(ax, pos[0][0] + 0.9, 5.8, pos[1][0] - 0.9, 5.8, "data")
_arrow(ax, pos[1][0] + 0.9, 5.8, pos[2][0] - 0.9, 5.8, "data")
_arrow(ax, pos[2][0] + 0.9, 5.8, pos[3][0] - 0.9, 5.8, "data")
# control: post WQE + auto-CQE arrives + poll/IRQ
_arrow(ax, pos[0][0] + 0.9, 5.2, pos[1][0] - 0.9, 5.2, "ctrl")
_arrow(ax, pos[2][0] + 0.9, 5.0, pos[3][0] - 0.9, 5.0, "ctrl")
_arrow(ax, pos[4][0] - 0.9, 5.0, pos[3][0] + 0.9, 5.0, "ctrl")
ax.text(5.0, 7.4, "CQE rides with DMA + CQ poll/IRQ at receiver",
ha="center", fontsize=10.5, color="#333", style="italic")
# ── Case 4: IPCQ ──
ax = axes[1, 1]; _setup(ax, "ipcq")
pos = _row(ax, ["Sender PE\n(tl.send)", "PE_IPCQ\n+ PE_DMA",
"NoC / UCIe", "PE_IPCQ\n+ PE_DMA",
"Receiver PE\n(tl.recv)"])
# data: kernel → PE_DMA → NoC → TCM
_arrow(ax, pos[0][0] + 0.9, 5.8, pos[1][0] - 0.9, 5.8, "data")
_arrow(ax, pos[1][0] + 0.9, 5.8, pos[2][0] - 0.9, 5.8, "data")
_arrow(ax, pos[2][0] + 0.9, 5.8, pos[3][0] - 0.9, 5.8, "data")
_arrow(ax, pos[3][0] + 0.9, 5.8, pos[4][0] - 0.9, 5.8, "data")
# control: PE_IPCQ ring push + meta + credit piggyback
_arrow(ax, pos[1][0] + 0.9, 5.2, pos[2][0] - 0.9, 5.2, "ctrl")
_arrow(ax, pos[2][0] + 0.9, 5.2, pos[3][0] - 0.9, 5.2, "ctrl")
# credit return arrow (dashed back)
ax.annotate("", xy=(pos[1][0] - 0.4, 6.8),
xytext=(pos[3][0] + 0.4, 6.8),
arrowprops=dict(arrowstyle="->", color="#2E7D32",
lw=1.6, ls=(0, (4, 3))))
ax.text(5.0, 7.05, "credit piggyback (no separate ACK)",
ha="center", fontsize=9, color="#2E7D32", fontweight="bold")
ax.text(5.0, 7.6, "single in-line control token + single DMA "
"(no host CPU, no polling)",
ha="center", fontsize=10.5, color="#333", style="italic")
# Global figure legend.
fig.text(0.5, 0.025,
"— Data path - - - Control path "
"- - - Credit return",
ha="center", va="center", fontsize=11.5, fontweight="bold")
fig.suptitle(
"Control vs Data Plane Overlay — where each design moves "
"the control burden",
fontsize=14.5, fontweight="bold", y=0.995)
fig.tight_layout(rect=(0, 0.04, 1, 0.97))
out = _OUT_DIR / "ipcq_alternatives_control_data_overlay.png"
fig.savefig(out, dpi=150, bbox_inches="tight")
plt.close(fig)
return out
def main() -> None:
_OUT_DIR.mkdir(parents=True, exist_ok=True)
for plot in (_plot_architecture, _plot_architecture_flow,
_plot_architecture_stacked,
_plot_latency,
_plot_opcounts, _plot_sequence,
_plot_decision_matrix,
_plot_control_data_overlay):
print(f"wrote {plot()}")
if __name__ == "__main__":
main()