paper(platform): edit-pass through §2 KernBench Platform done
- Reordered §2 subsections to follow the SIP → CUBE → PE → graph
flow: Why KernBench → Device and execution model → Latency model
→ Modeled hardware configuration. Readers now meet the device
hierarchy before the graph abstraction that re-uses it.
- §2.2 Device and execution model: starts with the SIP/CUBE/PE
hierarchy paragraphs (each anchoring fig:sip-arch, fig:cube-arch,
fig:pe-arch); then the runtime-API/sim-engine/components bullet
list; then the atomic-vs-composite command distinction (corrects
the prior over-narrow framing that read every PE command as
composite -- atomic single-engine commands exist too, and PE_CPU
itself runs control-plane work directly).
- §2.3 Latency model: opens with the four-contribution decomposition
(per-node overhead, per-edge transmission, drain, queuing delay)
and the latency_model schematic; retains existing The hardware as
a graph / From graph to DES / Latency contributions / Congestion
/ Control-plane cost model / Accuracy paragraphs. Accuracy
paragraph now closes on KernBench's sufficiency for *relative*
HW/SW design trade-offs given analytic + external-simulator
agreement.
- New figures and assets:
- figures/sip_architecture.pdf (SIP-level graph view)
- figures/cube_architecture.pdf (CUBE-level zoom-in)
- figures/latency_model.png (conceptual latency-model
schematic with per-node /
per-edge / drain / queuing-delay
colour coding)
- figures/pe_architecture.png (carried over)
- Source-of-truth generator for the latency schematic:
scripts/paper/paper_latency_model_diagram.py (a report-only
harness under scripts/paper/ per the /paper isolation rule).
- main.tex preamble: \usepackage{tikz} added (kept from prior
sequence-diagram draft -- harmless now that the latency model is
a PNG; left in to keep paragraph numbering stable).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,535 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Conceptual latency-model diagram for the KernBench paper (v5).
|
||||
|
||||
Generic naming (Requester Node A/B, Router, Destination Node), with
|
||||
Router internals visible (two input ports -> switch -> output queue ->
|
||||
output port) so the queuing delay can be located *at the out port*
|
||||
rather than at the box edge. The Destination Node shows queue -> drain
|
||||
slot -> processing logic.
|
||||
|
||||
Highlights:
|
||||
* Same-size Routers, each with explicit switching logic + output queue.
|
||||
* Edge labels removed -- the wires speak for themselves.
|
||||
* Annotation lines are strictly vertical; arrow heads use a larger
|
||||
mutation_scale so no thin line protrudes past the tip; `shrinkA`
|
||||
pulls the tail away from the text so they no longer overlap.
|
||||
* "flit-level interleaving on wires" (not "shared FIFO").
|
||||
* Destination: queue -> drain -> processing logic (no "served").
|
||||
|
||||
Output:
|
||||
docs/report/1H-codesign-paper/figures/latency_model.png
|
||||
|
||||
Per /paper isolation: this is a report-only harness under scripts/paper/.
|
||||
"""
|
||||
|
||||
import math
|
||||
from pathlib import Path
|
||||
|
||||
import matplotlib.patches as patches
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
OUT = Path(
|
||||
"/Users/ywkang/kernbench/docs/report/1H-codesign-paper/figures/latency_model.png"
|
||||
)
|
||||
OUT.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
# --- Colours --------------------------------------------------------------
|
||||
C_A = "#E07B5E"
|
||||
C_B = "#5E9BD1"
|
||||
C_NODE = "#FFFFFF"
|
||||
C_BORD = "#222222"
|
||||
C_WIRE = "#222222"
|
||||
C_ANN = "#333333"
|
||||
C_DIM = "#777777"
|
||||
C_SWITCH = "#FAFAFA"
|
||||
C_OQUEUE = "#EFEFEF"
|
||||
C_PROC = "#F4ECDC"
|
||||
C_QUEUE = "#3CB371" # medium-sea-green: distinctive vs A/B flit colours
|
||||
|
||||
|
||||
# --- Canvas ---------------------------------------------------------------
|
||||
fig = plt.figure(figsize=(17.0, 5.0))
|
||||
ax = fig.add_subplot(111)
|
||||
ax.set_xlim(0, 33)
|
||||
ax.set_ylim(2.2, 11.2)
|
||||
ax.axis("off")
|
||||
|
||||
|
||||
# --- Top header: end-to-end latency formula -----------------------------
|
||||
ax.text(
|
||||
16.5, 10.6,
|
||||
r"End-to-end latency = $\Sigma$ per-node overhead + "
|
||||
r"$\Sigma$ per-edge transmission + drain + "
|
||||
r"queuing delay",
|
||||
ha="center", fontsize=12, color=C_ANN, weight="bold",
|
||||
)
|
||||
ax.plot([1.5, 31.5], [10.05, 10.05], color=C_DIM, lw=0.6)
|
||||
|
||||
|
||||
# --- Helpers --------------------------------------------------------------
|
||||
def box(cx, cy, w, h, label, fs=11, fweight="normal"):
|
||||
ax.add_patch(patches.FancyBboxPatch(
|
||||
(cx - w / 2, cy - h / 2), w, h,
|
||||
boxstyle="round,pad=0.05",
|
||||
facecolor=C_NODE, edgecolor=C_BORD, linewidth=1.6,
|
||||
))
|
||||
if label:
|
||||
ax.text(cx, cy, label, ha="center", va="center",
|
||||
fontsize=fs, weight=fweight)
|
||||
|
||||
|
||||
def draw_flit_aligned(cx, cy, angle_rad, w, h, color, label):
|
||||
"""Draw a flit polygon rotated to align with the wire angle."""
|
||||
cos_a, sin_a = math.cos(angle_rad), math.sin(angle_rad)
|
||||
corners = []
|
||||
for dx, dy in [(-w / 2, -h / 2), (w / 2, -h / 2),
|
||||
(w / 2, h / 2), (-w / 2, h / 2)]:
|
||||
rx = dx * cos_a - dy * sin_a
|
||||
ry = dx * sin_a + dy * cos_a
|
||||
corners.append((cx + rx, cy + ry))
|
||||
ax.add_patch(patches.Polygon(
|
||||
corners, facecolor=color, edgecolor="black", linewidth=0.5,
|
||||
))
|
||||
ax.text(cx, cy, label, ha="center", va="center",
|
||||
fontsize=9, color="white", weight="bold")
|
||||
|
||||
|
||||
def draw_wire_with_flits(x0, y0, x1, y1, n_flits, label_char,
|
||||
color=None, flit_w=0.66, flit_h=0.95, gap=0.10):
|
||||
"""Wire (line) + endpoint arrowhead + flits centred on the line.
|
||||
|
||||
Wires are drawn in the neutral C_WIRE colour: the wire itself is
|
||||
the place where the *transmission* delay accumulates (flit_size /
|
||||
BW), not where flits queue. Queuing happens before the wire, at
|
||||
the FIFO at the egress side -- coloured separately.
|
||||
"""
|
||||
head_back = 0.30
|
||||
dx, dy = x1 - x0, y1 - y0
|
||||
wire_len = math.hypot(dx, dy)
|
||||
ux, uy = dx / wire_len, dy / wire_len
|
||||
x_line_end = x1 - ux * head_back
|
||||
y_line_end = y1 - uy * head_back
|
||||
ax.plot([x0, x_line_end], [y0, y_line_end],
|
||||
color=C_WIRE, lw=1.6, zorder=1)
|
||||
head_len, head_half = 0.30, 0.16
|
||||
bx = x1 - ux * head_len
|
||||
by = y1 - uy * head_len
|
||||
px, py = -uy, ux
|
||||
tri = [
|
||||
(x1, y1),
|
||||
(bx + px * head_half, by + py * head_half),
|
||||
(bx - px * head_half, by - py * head_half),
|
||||
]
|
||||
ax.add_patch(patches.Polygon(tri, facecolor=C_WIRE,
|
||||
edgecolor=C_WIRE, linewidth=0.0))
|
||||
# Flit train
|
||||
angle_rad = math.atan2(dy, dx)
|
||||
train_len = n_flits * flit_w + (n_flits - 1) * gap
|
||||
centre_p = 0.5
|
||||
start_p = centre_p - (train_len / 2) / wire_len
|
||||
step_p = (flit_w + gap) / wire_len
|
||||
if isinstance(label_char, str):
|
||||
labels = [label_char] * n_flits
|
||||
cols = [color] * n_flits
|
||||
else:
|
||||
labels = [c[0] for c in label_char]
|
||||
cols = [c[1] for c in label_char]
|
||||
for i in range(n_flits):
|
||||
p = start_p + (i + 0.5) * step_p
|
||||
cx = x0 + p * dx
|
||||
cy = y0 + p * dy
|
||||
draw_flit_aligned(cx, cy, angle_rad,
|
||||
flit_w, flit_h, cols[i], labels[i])
|
||||
|
||||
|
||||
def draw_router(cx, cy, w, h, two_inputs=True):
|
||||
"""Same-size Router with explicit internal switching logic and an
|
||||
output queue. Returns the wire-attachment (x,y) for each input
|
||||
port and the single output port:
|
||||
((in1_x, in1_y), (in2_x, in2_y) or None, (out_x, out_y))
|
||||
"""
|
||||
# Outer box
|
||||
box(cx, cy, w, h, "")
|
||||
ax.text(cx, cy + h / 2 - 0.32, "Router",
|
||||
ha="center", fontsize=10, weight="bold")
|
||||
|
||||
# Input ports (small circles on the left edge)
|
||||
in_x = cx - w / 2
|
||||
in_x_internal = in_x + 0.25
|
||||
if two_inputs:
|
||||
in_y_top = cy + 0.55
|
||||
in_y_bot = cy - 0.55
|
||||
for iy in (in_y_top, in_y_bot):
|
||||
ax.add_patch(patches.Circle(
|
||||
(in_x_internal, iy), 0.12,
|
||||
facecolor="white", edgecolor=C_BORD, linewidth=0.9,
|
||||
))
|
||||
else:
|
||||
in_y_top = None
|
||||
in_y_bot = cy
|
||||
ax.add_patch(patches.Circle(
|
||||
(in_x_internal, in_y_bot), 0.12,
|
||||
facecolor="white", edgecolor=C_BORD, linewidth=0.9,
|
||||
))
|
||||
|
||||
# Switch (small box, centre-left-ish). sw_cy = cy so that the
|
||||
# output queue and (single-input) input port are at the same y as
|
||||
# the router centre -- this keeps all router-to-router edges
|
||||
# strictly horizontal.
|
||||
sw_w, sw_h = 0.85, 1.10
|
||||
sw_cx = cx - 0.55
|
||||
sw_cy = cy
|
||||
# The switch is the router's processing logic -- colour it the
|
||||
# same as the Destination Node's processing-logic block so the
|
||||
# two read as the same "processing" concept.
|
||||
ax.add_patch(patches.Rectangle(
|
||||
(sw_cx - sw_w / 2, sw_cy - sw_h / 2), sw_w, sw_h,
|
||||
facecolor=C_PROC, edgecolor=C_BORD, linewidth=0.8,
|
||||
))
|
||||
ax.text(sw_cx, sw_cy, "switch", ha="center", va="center",
|
||||
fontsize=7.5, style="italic")
|
||||
|
||||
# Short feeder lines (no arrowhead) from input ports to the
|
||||
# switch. We deliberately drop arrowheads here: the head + port
|
||||
# circle were too small at this scale and read as an overlap.
|
||||
sw_left_x = sw_cx - sw_w / 2
|
||||
if two_inputs:
|
||||
for iy in (in_y_top, in_y_bot):
|
||||
ax.plot(
|
||||
[in_x_internal + 0.12, sw_left_x],
|
||||
[iy, sw_cy + 0.25 * ((iy - cy) / 0.55)],
|
||||
color=C_DIM, lw=0.7, zorder=1,
|
||||
)
|
||||
else:
|
||||
ax.plot(
|
||||
[in_x_internal + 0.12, sw_left_x],
|
||||
[in_y_bot, sw_cy],
|
||||
color=C_DIM, lw=0.7, zorder=1,
|
||||
)
|
||||
|
||||
# Output queue (small queue holding a couple of flits). This *is*
|
||||
# a real queueing location, so its fill takes the C_QUEUE family.
|
||||
oq_w, oq_h = 0.95, 0.55
|
||||
oq_cx = cx + 0.55
|
||||
oq_cy = sw_cy
|
||||
ax.add_patch(patches.Rectangle(
|
||||
(oq_cx - oq_w / 2, oq_cy - oq_h / 2), oq_w, oq_h,
|
||||
facecolor="#D9F0E1", edgecolor=C_QUEUE, linewidth=0.9,
|
||||
))
|
||||
ax.text(oq_cx, oq_cy + oq_h / 2 + 0.18, "FIFO",
|
||||
ha="center", fontsize=7, color=C_DIM, style="italic")
|
||||
# Two small flits inside (A and B) hinting at the in-flight contents
|
||||
mini_w, mini_h = 0.22, 0.34
|
||||
for i, (col, lab) in enumerate([(C_A, "A"), (C_B, "B")]):
|
||||
mx = oq_cx - 0.30 + i * (mini_w + 0.06)
|
||||
ax.add_patch(patches.Rectangle(
|
||||
(mx, oq_cy - mini_h / 2), mini_w, mini_h,
|
||||
facecolor=col, edgecolor="black", linewidth=0.3,
|
||||
))
|
||||
ax.text(mx + mini_w / 2, oq_cy, lab,
|
||||
ha="center", va="center",
|
||||
fontsize=5.5, color="white", weight="bold")
|
||||
|
||||
# Short feeder line (no arrowhead) from switch into out queue
|
||||
ax.plot(
|
||||
[sw_cx + sw_w / 2, oq_cx - oq_w / 2],
|
||||
[sw_cy, oq_cy],
|
||||
color=C_DIM, lw=0.7, zorder=1,
|
||||
)
|
||||
# Short feeder line from out queue to out port
|
||||
out_x_internal = cx + w / 2 - 0.25
|
||||
ax.plot(
|
||||
[oq_cx + oq_w / 2, out_x_internal - 0.12],
|
||||
[oq_cy, oq_cy],
|
||||
color=C_DIM, lw=0.7, zorder=1,
|
||||
)
|
||||
# Output port circle on the right edge (a port marker, not a
|
||||
# queue -- the queue is on the wire that follows, not the port).
|
||||
ax.add_patch(patches.Circle(
|
||||
(out_x_internal, oq_cy), 0.12,
|
||||
facecolor="white", edgecolor=C_BORD, linewidth=0.9,
|
||||
))
|
||||
|
||||
return (
|
||||
(in_x_internal, in_y_top) if two_inputs else None,
|
||||
(in_x_internal, in_y_bot),
|
||||
(out_x_internal, oq_cy),
|
||||
oq_cx, # also return the out-queue centre so callouts can target it
|
||||
oq_cy,
|
||||
)
|
||||
|
||||
|
||||
# --- Box layout ---------------------------------------------------------
|
||||
# Requester centres set to match Router 1's input-port y so that
|
||||
# Edge 1A and Edge 1B are strictly horizontal. Box heights are kept
|
||||
# small enough that a visible gap separates the two Requester boxes.
|
||||
R1_TMP_CY = 7.0
|
||||
ReqA = (2.8, R1_TMP_CY + 0.55) # = 7.55, matches in_y_top of R1
|
||||
ReqB = (2.8, R1_TMP_CY - 0.55) # = 6.45, matches in_y_bot of R1
|
||||
box(*ReqA, 3.0, 0.85, "Requester\nNode A", fs=10)
|
||||
box(*ReqB, 3.0, 0.85, "Requester\nNode B", fs=10)
|
||||
|
||||
R_W, R_H = 3.0, 2.8
|
||||
R1 = (10.5, 7.0)
|
||||
R2 = (20.0, 7.0)
|
||||
|
||||
r1_in_top, r1_in_bot, r1_out, r1_oq_cx, r1_oq_cy = draw_router(
|
||||
*R1, R_W, R_H, two_inputs=True,
|
||||
)
|
||||
_, r2_in_bot, r2_out, r2_oq_cx, r2_oq_cy = draw_router(
|
||||
*R2, R_W, R_H, two_inputs=False,
|
||||
)
|
||||
|
||||
# Destination Node (same height as Router; wide enough so queue +
|
||||
# drain + processing-logic all fit on a single horizontal row).
|
||||
Dst = (28.4, 7.0)
|
||||
Dst_W, Dst_H = 6.0, R_H # match the Router height
|
||||
box(*Dst, Dst_W, Dst_H, "")
|
||||
ax.text(Dst[0], Dst[1] + Dst_H / 2 - 0.25, "Destination Node",
|
||||
ha="center", fontsize=10, weight="bold")
|
||||
|
||||
|
||||
# --- Edges: requester -> router 1 (Edge 1A & 1B, with flits) ------------
|
||||
draw_wire_with_flits(
|
||||
ReqA[0] + 1.6, ReqA[1],
|
||||
r1_in_top[0] - 0.02, r1_in_top[1],
|
||||
n_flits=4, color=C_A, label_char="A",
|
||||
)
|
||||
draw_wire_with_flits(
|
||||
ReqB[0] + 1.6, ReqB[1],
|
||||
r1_in_bot[0] - 0.02, r1_in_bot[1],
|
||||
n_flits=4, color=C_B, label_char="B",
|
||||
)
|
||||
|
||||
# Edge 2: router1 out -> router2 in (horizontal, interleaved)
|
||||
labels_e2 = [("A", C_A) if i % 2 == 0 else ("B", C_B) for i in range(8)]
|
||||
E2_y = r1_out[1]
|
||||
draw_wire_with_flits(
|
||||
r1_out[0] + 0.02, r1_out[1],
|
||||
r2_in_bot[0] - 0.02, r2_in_bot[1],
|
||||
n_flits=8, label_char=labels_e2,
|
||||
)
|
||||
|
||||
# Edge 3: router2 out -> destination (horizontal, interleaved)
|
||||
labels_e3 = [("A", C_A) if i % 2 == 0 else ("B", C_B) for i in range(4)]
|
||||
draw_wire_with_flits(
|
||||
r2_out[0] + 0.02, r2_out[1],
|
||||
Dst[0] - Dst_W / 2, r2_out[1],
|
||||
n_flits=4, label_char=labels_e3,
|
||||
)
|
||||
|
||||
|
||||
# --- Destination internals: queue -> drain -> processing logic ----------
|
||||
# Light-green halo behind the queue area marks it as a queueing point.
|
||||
C_QUEUE_BG = "#D9F0E1"
|
||||
|
||||
dy_main = Dst[1] - 0.10
|
||||
Dst_left = Dst[0] - Dst_W / 2
|
||||
|
||||
# Queue (4 flits, FIFO; rightmost is the next to drain, so the
|
||||
# order is set so the serve sequence after the in-flight A alternates
|
||||
# A (in drain) -> B -> A -> B -> A -- giving a clean BABA queue.
|
||||
qW, qH, qGap = 0.42, 0.62, 0.07
|
||||
q_labels = ["A", "B", "A", "B"]
|
||||
q_cols = [C_A, C_B, C_A, C_B]
|
||||
q_total = len(q_labels) * qW + (len(q_labels) - 1) * qGap
|
||||
q_x_start = Dst_left + 0.35
|
||||
|
||||
# Green halo behind the queue boxes (the queue itself is a queueing
|
||||
# location -- mark it with the C_QUEUE colour family)
|
||||
ax.add_patch(patches.FancyBboxPatch(
|
||||
(q_x_start - 0.10, dy_main - qH / 2 - 0.08),
|
||||
q_total + 0.20, qH + 0.16,
|
||||
boxstyle="round,pad=0.02",
|
||||
facecolor=C_QUEUE_BG, edgecolor=C_QUEUE,
|
||||
linewidth=0.9, zorder=1.5,
|
||||
))
|
||||
|
||||
for i, (lab, col) in enumerate(zip(q_labels, q_cols)):
|
||||
qx = q_x_start + i * (qW + qGap)
|
||||
ax.add_patch(patches.Rectangle(
|
||||
(qx, dy_main - qH / 2), qW, qH,
|
||||
facecolor=col, edgecolor="black", linewidth=0.4,
|
||||
zorder=2,
|
||||
))
|
||||
ax.text(qx + qW / 2, dy_main, lab,
|
||||
ha="center", va="center",
|
||||
fontsize=8, color="white", weight="bold", zorder=3)
|
||||
# Common y for the "queue" / "drain" labels. Matches the FIFO label
|
||||
# spacing inside the Router (0.18 above the box top) and uses the same
|
||||
# font size for visual consistency.
|
||||
DST_LABEL_Y = dy_main + qH / 2 + 0.18
|
||||
ax.text(q_x_start + q_total / 2, DST_LABEL_Y,
|
||||
"queue",
|
||||
ha="center", fontsize=7, style="italic", color=C_DIM)
|
||||
|
||||
# Drain slot (height matched to queue boxes; font matched to FIFO/queue
|
||||
# labels for visual consistency)
|
||||
dr_W, dr_H = 0.85, qH
|
||||
dr_x = q_x_start + q_total + 0.35
|
||||
dr_cx = dr_x + dr_W / 2
|
||||
dr_cy = dy_main
|
||||
ax.add_patch(patches.FancyBboxPatch(
|
||||
(dr_x, dr_cy - dr_H / 2), dr_W, dr_H,
|
||||
boxstyle="round,pad=0.03",
|
||||
facecolor=C_A, edgecolor="black", linewidth=1.0,
|
||||
))
|
||||
ax.text(dr_cx, dr_cy, "A", ha="center", va="center",
|
||||
fontsize=9, color="white", weight="bold")
|
||||
ax.text(dr_cx, DST_LABEL_Y,
|
||||
"drain",
|
||||
ha="center", fontsize=7, style="italic", color=C_DIM)
|
||||
|
||||
# Drain-time double-arrow underneath the drain slot. Move the "drain
|
||||
# time" text a touch further down so the descender does not collide
|
||||
# with the arrowhead.
|
||||
dt_y = dr_cy - dr_H / 2 - 0.28
|
||||
ax.annotate(
|
||||
"", xy=(dr_x + dr_W, dt_y), xytext=(dr_x, dt_y),
|
||||
arrowprops=dict(arrowstyle="<->", lw=0.9, color=C_ANN),
|
||||
)
|
||||
ax.text(dr_cx, dt_y - 0.42, "drain time",
|
||||
ha="center", fontsize=8.5, color=C_ANN, style="italic")
|
||||
|
||||
# Processing-logic block (after drain) — wider so the two-line text
|
||||
# does not collide with the box edges
|
||||
pl_W, pl_H = 1.75, 1.00
|
||||
pl_x = dr_x + dr_W + 0.40
|
||||
pl_cx = pl_x + pl_W / 2
|
||||
pl_cy = dr_cy
|
||||
ax.add_patch(patches.FancyBboxPatch(
|
||||
(pl_x, pl_cy - pl_H / 2), pl_W, pl_H,
|
||||
boxstyle="round,pad=0.03",
|
||||
facecolor=C_PROC, edgecolor=C_BORD, linewidth=1.0,
|
||||
))
|
||||
ax.text(pl_cx, pl_cy, "processing\nlogic",
|
||||
ha="center", va="center", fontsize=9)
|
||||
|
||||
# Small gray arrows along the queue -> drain -> processing chain
|
||||
ax.annotate(
|
||||
"", xy=(dr_x - 0.04, dr_cy),
|
||||
xytext=(q_x_start + q_total + 0.13, dr_cy),
|
||||
arrowprops=dict(arrowstyle="-|>", lw=0.7,
|
||||
color=C_DIM, mutation_scale=7),
|
||||
)
|
||||
ax.annotate(
|
||||
"", xy=(pl_x - 0.04, dr_cy),
|
||||
xytext=(dr_x + dr_W + 0.04, dr_cy),
|
||||
arrowprops=dict(arrowstyle="-|>", lw=0.7,
|
||||
color=C_DIM, mutation_scale=7),
|
||||
)
|
||||
|
||||
|
||||
# --- Annotations (strictly vertical, no diagonals, no overlap) ---------
|
||||
|
||||
def vcallout(text, xy, x_text_top_y, fs=10, clearance=0.55,
|
||||
marker_color=None):
|
||||
"""Vertical dotted callout. Optional `marker_color` paints a small
|
||||
coloured circle just left of the text -- used to tie the label to
|
||||
a colour code in the diagram.
|
||||
"""
|
||||
text_y = x_text_top_y
|
||||
if text_y < xy[1]:
|
||||
line_top_y = text_y + clearance
|
||||
else:
|
||||
line_top_y = text_y - clearance
|
||||
ax.plot([xy[0], xy[0]], [xy[1], line_top_y],
|
||||
color=C_ANN, lw=0.9, linestyle=":", zorder=2)
|
||||
if marker_color is not None:
|
||||
# Text-width estimate at fs=10 (~0.18 data units per char) so
|
||||
# the marker is placed clearly to the left of the text.
|
||||
text_w = len(text) * 0.18
|
||||
marker_x = xy[0] - text_w / 2 - 0.30
|
||||
ax.add_patch(patches.Circle(
|
||||
(marker_x, text_y), 0.14,
|
||||
facecolor=marker_color, edgecolor=C_BORD, linewidth=0.6,
|
||||
zorder=3,
|
||||
))
|
||||
ax.text(xy[0], text_y, text,
|
||||
ha="center", va="center",
|
||||
fontsize=fs, color=C_ANN, zorder=3)
|
||||
|
||||
|
||||
# Transmission delay -> flit on Edge 2 (text BELOW the wire).
|
||||
# Extra clearance ~ 2 x text height so the line stops well clear of
|
||||
# the label.
|
||||
e2_total = 8 * 0.66 + 7 * 0.10
|
||||
e2_centre = (r1_out[0] + r2_in_bot[0]) / 2
|
||||
e2_start = e2_centre - e2_total / 2
|
||||
trans_idx = 5
|
||||
trans_cx = e2_start + (trans_idx + 0.5) * (0.66 + 0.10)
|
||||
vcallout(
|
||||
"transmission delay = flit_size / BW",
|
||||
xy=(trans_cx, E2_y - 0.55),
|
||||
x_text_top_y=E2_y - 2.6,
|
||||
clearance=0.45,
|
||||
)
|
||||
|
||||
# Flit-level interleaving -> ABOVE the wire, anchored on a flit near
|
||||
# the centre/right of the wire. Text y matches the queuing-delay
|
||||
# callout so the two labels sit on the same horizontal row, separated
|
||||
# horizontally so they don't collide.
|
||||
int_idx = 4
|
||||
int_cx = e2_start + (int_idx + 0.5) * (0.66 + 0.10)
|
||||
vcallout(
|
||||
"flit-level interleaving on wires",
|
||||
xy=(int_cx, E2_y + 0.55),
|
||||
x_text_top_y=R1[1] + R_H / 2 + 0.85,
|
||||
)
|
||||
|
||||
# Queuing delay -> at Router 1 out-queue. Text positioned just above
|
||||
# the Router-1 box.
|
||||
vcallout(
|
||||
"queuing delay",
|
||||
xy=(r1_oq_cx, r1_oq_cy + 0.30),
|
||||
x_text_top_y=R1[1] + R_H / 2 + 0.85,
|
||||
marker_color=C_QUEUE,
|
||||
)
|
||||
|
||||
# Drain -> below the drain slot (text is below).
|
||||
# Extra clearance ~ 2.5 x text height so the line stops further from
|
||||
# the label.
|
||||
vcallout(
|
||||
"drain = per-flit service occupancy",
|
||||
xy=(dr_cx, dt_y - 0.66), # just *inside* the Destination Node
|
||||
# bottom edge -- the dotted line then
|
||||
# penetrates the box slightly rather
|
||||
# than hanging below it
|
||||
x_text_top_y=E2_y - 2.6,
|
||||
clearance=0.60,
|
||||
)
|
||||
|
||||
# Per-node overhead -> points at Router 1's switch block (where the
|
||||
# component's fixed processing cost lives). Text on the same row as
|
||||
# transmission delay and drain so the three "below the wire" callouts
|
||||
# sit on one horizontal baseline.
|
||||
R1_SW_CX = R1[0] - 0.55 # sw_cx for Router 1
|
||||
R1_SW_BOTTOM_Y = R1[1] - 0.55 # bottom of sw box
|
||||
vcallout(
|
||||
"per-node overhead",
|
||||
xy=(R1_SW_CX, R1_SW_BOTTOM_Y),
|
||||
x_text_top_y=E2_y - 2.6,
|
||||
clearance=0.60,
|
||||
marker_color=C_PROC,
|
||||
)
|
||||
|
||||
|
||||
# --- Legend (close to the diagram) --------------------------------------
|
||||
LX, LY = 1.0, 2.6
|
||||
ax.add_patch(patches.Rectangle((LX, LY), 0.7, 0.45,
|
||||
facecolor=C_A, edgecolor="black",
|
||||
linewidth=0.4))
|
||||
ax.text(LX + 0.95, LY + 0.22, "Transaction A flit",
|
||||
va="center", fontsize=10)
|
||||
|
||||
ax.add_patch(patches.Rectangle((LX + 4.8, LY), 0.7, 0.45,
|
||||
facecolor=C_B, edgecolor="black",
|
||||
linewidth=0.4))
|
||||
ax.text(LX + 5.75, LY + 0.22, "Transaction B flit",
|
||||
va="center", fontsize=10)
|
||||
|
||||
|
||||
# --- Save ----------------------------------------------------------------
|
||||
fig.savefig(OUT, dpi=140, bbox_inches="tight", facecolor="white")
|
||||
print(f"Wrote {OUT}")
|
||||
Reference in New Issue
Block a user