faf011dae5
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1076 lines
46 KiB
Python
1076 lines
46 KiB
Python
"""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()
|