Graph: explain cache coherence
simple animated box diagram -- generated by Claude, after a short discussion to clarify and sharpen what I want, didactically
This commit is contained in:
parent
4de78bc2a5
commit
15090bd997
2 changed files with 269 additions and 0 deletions
47
FrOSCon26/drawings/coherence.svg
Normal file
47
FrOSCon26/drawings/coherence.svg
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
width="254.0mm" height="142.9mm" viewBox="0 0 254.0 142.9" version="1.1">
|
||||
<sodipodi:namedview inkscape:document-units="mm" units="mm"/>
|
||||
<title>Cache coherence and observability</title>
|
||||
<g inkscape:groupmode="layer" id="layer-1-scaffold" inkscape:label="1 - scaffold (x=0 everywhere)" style="display:inline">
|
||||
<defs><marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="#555555"/></marker></defs>
|
||||
<rect x="44.00" y="24.00" width="46.00" height="20.00" rx="2.0" fill="#ffffff" stroke="#222222" stroke-width="0.8"/>
|
||||
<text x="67.00" y="35.80" font-family="DejaVu Sans, Verdana, sans-serif" font-size="5.5" font-weight="600" fill="#222222" text-anchor="middle">Core</text>
|
||||
<rect x="164.00" y="24.00" width="46.00" height="20.00" rx="2.0" fill="#ffffff" stroke="#222222" stroke-width="0.8"/>
|
||||
<text x="187.00" y="35.80" font-family="DejaVu Sans, Verdana, sans-serif" font-size="5.5" font-weight="600" fill="#222222" text-anchor="middle">Core</text>
|
||||
<path d="M 67.00,44.00 L 67.00,58.00" stroke="#555555" stroke-width="0.6" fill="none" marker-end="url(#arrow)"/>
|
||||
<path d="M 187.00,44.00 L 187.00,58.00" stroke="#555555" stroke-width="0.6" fill="none" marker-end="url(#arrow)"/>
|
||||
<path d="M 67.00,80.00 L 67.00,110.00" stroke="#555555" stroke-width="0.6" fill="none"/>
|
||||
<path d="M 67.00,110.00 L 90.00,104.00" stroke="#555555" stroke-width="0.6" fill="none"/>
|
||||
<path d="M 187.00,80.00 L 187.00,110.00" stroke="#555555" stroke-width="0.6" fill="none"/>
|
||||
<path d="M 187.00,110.00 L 164.00,104.00" stroke="#555555" stroke-width="0.6" fill="none"/>
|
||||
<rect x="44.00" y="58.00" width="46.00" height="22.00" rx="2.0" fill="#f2f2f2" stroke="#222222" stroke-width="0.8"/>
|
||||
<rect x="164.00" y="58.00" width="46.00" height="22.00" rx="2.0" fill="#f2f2f2" stroke="#222222" stroke-width="0.8"/>
|
||||
<text x="67.00" y="64.00" font-family="DejaVu Sans, Verdana, sans-serif" font-size="4.2" font-weight="600" fill="#222222" text-anchor="middle">Cache</text>
|
||||
<text x="187.00" y="64.00" font-family="DejaVu Sans, Verdana, sans-serif" font-size="4.2" font-weight="600" fill="#222222" text-anchor="middle">Cache</text>
|
||||
<text x="67.00" y="74.00" font-family="DejaVu Sans, Verdana, sans-serif" font-size="6.5" font-weight="700" fill="#8a8a8a" text-anchor="middle">x = 0</text>
|
||||
<text x="187.00" y="74.00" font-family="DejaVu Sans, Verdana, sans-serif" font-size="6.5" font-weight="700" fill="#8a8a8a" text-anchor="middle">x = 0</text>
|
||||
<rect x="82.00" y="104.00" width="90.00" height="24.00" rx="2.0" fill="#f2f2f2" stroke="#222222" stroke-width="0.8"/>
|
||||
<text x="127.00" y="111.00" font-family="DejaVu Sans, Verdana, sans-serif" font-size="4.2" font-weight="600" fill="#222222" text-anchor="middle">DRAM</text>
|
||||
<text x="127.00" y="122.50" font-family="DejaVu Sans, Verdana, sans-serif" font-size="6.5" font-weight="700" fill="#8a8a8a" text-anchor="middle">x = 0</text>
|
||||
</g>
|
||||
<g inkscape:groupmode="layer" id="layer-2-core0-write" inkscape:label="2 - core0 writes x=1" style="display:inline">
|
||||
<rect x="44.00" y="58.00" width="46.00" height="22.00" rx="2.0" fill="#dce8f2" stroke="#14507d" stroke-width="1.1"/>
|
||||
<text x="67.00" y="64.00" font-family="DejaVu Sans, Verdana, sans-serif" font-size="4.2" font-weight="600" fill="#222222" text-anchor="middle">Cache</text>
|
||||
<text x="67.00" y="74.00" font-family="DejaVu Sans, Verdana, sans-serif" font-size="6.5" font-weight="700" fill="#14507d" text-anchor="middle">x = 1</text>
|
||||
</g>
|
||||
<g inkscape:groupmode="layer" id="layer-3-core1-read" inkscape:label="3 - core1 reads: still x=0" style="display:inline">
|
||||
<path d="M 184.00,45.50 C 179.00,51.00 179.00,51.00 184.00,56.50" stroke="#8a8a8a" stroke-width="0.6" fill="none" marker-end="url(#arrow)"/>
|
||||
<rect x="164.00" y="58.00" width="46.00" height="22.00" rx="2.0" fill="#f2f2f2" stroke="#8a8a8a" stroke-width="1.1"/>
|
||||
<text x="187.00" y="64.00" font-family="DejaVu Sans, Verdana, sans-serif" font-size="4.2" font-weight="600" fill="#222222" text-anchor="middle">Cache</text>
|
||||
<text x="187.00" y="74.00" font-family="DejaVu Sans, Verdana, sans-serif" font-size="6.5" font-weight="700" fill="#8a8a8a" text-anchor="middle">x = 0</text>
|
||||
</g>
|
||||
<g inkscape:groupmode="layer" id="layer-4-propagate" inkscape:label="4 - later: DRAM sees x=1, core1 still stale" style="display:inline">
|
||||
<circle cx="119.0" cy="95.0" r="0.7" fill="#8a8a8a"/><circle cx="123.0" cy="95.0" r="0.7" fill="#8a8a8a"/><circle cx="127.0" cy="95.0" r="0.7" fill="#8a8a8a"/><circle cx="131.0" cy="95.0" r="0.7" fill="#8a8a8a"/><circle cx="135.0" cy="95.0" r="0.7" fill="#8a8a8a"/>
|
||||
<rect x="82.00" y="104.00" width="90.00" height="24.00" rx="2.0" fill="#dce8f2" stroke="#14507d" stroke-width="1.1"/>
|
||||
<text x="127.00" y="111.00" font-family="DejaVu Sans, Verdana, sans-serif" font-size="4.2" font-weight="600" fill="#222222" text-anchor="middle">DRAM</text>
|
||||
<text x="127.00" y="122.50" font-family="DejaVu Sans, Verdana, sans-serif" font-size="6.5" font-weight="700" fill="#14507d" text-anchor="middle">x = 1</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.2 KiB |
222
FrOSCon26/drawings/gen-cache-coherence-diagram.py
Normal file
222
FrOSCon26/drawings/gen-cache-coherence-diagram.py
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Cache coherence / observability diagram.
|
||||
|
||||
Four Inkscape layers, meant to be exported one by one (Export > Page) and
|
||||
stacked as transparent overlays in Impress, same workflow as the time-scales
|
||||
diagram. Layer content is INCREMENTAL: each layer only draws what is new at
|
||||
that step; earlier layers stay visible underneath once stacked.
|
||||
|
||||
Story:
|
||||
1 scaffold - two cores, two caches, one shared DRAM, all agree: x=0
|
||||
2 core0-write - core0's cache changes to x=1; nothing else does
|
||||
3 core1-read - core1 reads its own cache: still x=0 (stale, not wrong)
|
||||
4 propagate - some time later, x=1 reaches DRAM on its own;
|
||||
core1's cache is untouched and still says x=0
|
||||
"""
|
||||
|
||||
W, H = 254.0, 142.9
|
||||
|
||||
# ---- palette (kept consistent with time-scales.svg) -----------------------
|
||||
C_LINE = "#555555"
|
||||
C_BOX = "#222222"
|
||||
C_MACH = "#14507d" # "new" value / updated state
|
||||
C_STALE = "#8a8a8a" # stale / unchanged value
|
||||
C_FILL0 = "#f2f2f2" # box fill, neutral state
|
||||
C_FILL1 = "#dce8f2" # box fill, updated state
|
||||
C_WHITE = "#ffffff"
|
||||
FONT = "DejaVu Sans, Verdana, sans-serif"
|
||||
|
||||
F_LABEL, F_VALUE, F_TAG = 5.5, 6.5, 4.2
|
||||
|
||||
# ---- geometry ---------------------------------------------------------
|
||||
CORE_W, CORE_H = 46.0, 20.0
|
||||
CACHE_W, CACHE_H = 46.0, 22.0
|
||||
DRAM_W, DRAM_H = 90.0, 24.0
|
||||
|
||||
CORE0_X, CORE1_X = 44.0, 164.0
|
||||
CORE_Y = 24.0
|
||||
CACHE_Y = 58.0
|
||||
DRAM_X = (W - DRAM_W) / 2.0
|
||||
DRAM_Y = 104.0
|
||||
|
||||
C0_CX = CORE0_X + CORE_W / 2.0
|
||||
C1_CX = CORE1_X + CORE_W / 2.0
|
||||
DRAM_CX = DRAM_X + DRAM_W / 2.0
|
||||
|
||||
|
||||
def esc(s):
|
||||
return s.replace("&", "&").replace("<", "<").replace(">", ">")
|
||||
|
||||
|
||||
def text(x, y, s, size, fill, anchor="middle", weight="normal", family=FONT):
|
||||
return (f'<text x="{x:.2f}" y="{y:.2f}" font-family="{family}" '
|
||||
f'font-size="{size}" font-weight="{weight}" fill="{fill}" '
|
||||
f'text-anchor="{anchor}">{esc(s)}</text>')
|
||||
|
||||
|
||||
def rect(x, y, w, h, fill, stroke, sw=0.6, rx=2.0):
|
||||
return (f'<rect x="{x:.2f}" y="{y:.2f}" width="{w:.2f}" height="{h:.2f}" '
|
||||
f'rx="{rx}" fill="{fill}" stroke="{stroke}" stroke-width="{sw}"/>')
|
||||
|
||||
|
||||
def line(x1, y1, x2, y2, stroke=C_LINE, w=0.6, dash=None, marker=False):
|
||||
d = f' stroke-dasharray="{dash}"' if dash else ""
|
||||
m = ' marker-end="url(#arrow)"' if marker else ""
|
||||
return (f'<path d="M {x1:.2f},{y1:.2f} L {x2:.2f},{y2:.2f}" '
|
||||
f'stroke="{stroke}" stroke-width="{w}" fill="none"{d}{m}/>')
|
||||
|
||||
|
||||
def defs():
|
||||
return ('<defs><marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" '
|
||||
'markerWidth="6" markerHeight="6" orient="auto-start-reverse">'
|
||||
f'<path d="M0,0 L10,5 L0,10 z" fill="{C_LINE}"/></marker></defs>')
|
||||
|
||||
|
||||
# ==========================================================================
|
||||
# static structural pieces, reused across layers where needed
|
||||
# ==========================================================================
|
||||
def core_box(cx_left, label_suffix=""):
|
||||
cx = cx_left + CORE_W / 2.0
|
||||
o = [rect(cx_left, CORE_Y, CORE_W, CORE_H, C_WHITE, C_BOX, 0.8)]
|
||||
o.append(text(cx, CORE_Y + CORE_H / 2.0 + 1.8, "Core", F_LABEL, C_BOX,
|
||||
weight="600"))
|
||||
return o
|
||||
|
||||
|
||||
def cache_box_frame(cx_left):
|
||||
return [rect(cx_left, CACHE_Y, CACHE_W, CACHE_H, C_FILL0, C_BOX, 0.8)]
|
||||
|
||||
|
||||
def cache_tag(cx_left):
|
||||
cx = cx_left + CACHE_W / 2.0
|
||||
return [text(cx, CACHE_Y + 6.0, "Cache", F_TAG, C_BOX, weight="600")]
|
||||
|
||||
|
||||
def cache_value(cx_left, value_str, colour):
|
||||
cx = cx_left + CACHE_W / 2.0
|
||||
return [text(cx, CACHE_Y + CACHE_H / 2.0 + 5.0, value_str, F_VALUE, colour,
|
||||
weight="700")]
|
||||
|
||||
|
||||
def dram_box_frame():
|
||||
return [rect(DRAM_X, DRAM_Y, DRAM_W, DRAM_H, C_FILL0, C_BOX, 0.8)]
|
||||
|
||||
|
||||
def dram_tag():
|
||||
return [text(DRAM_CX, DRAM_Y + 7.0, "DRAM", F_TAG, C_BOX, weight="600")]
|
||||
|
||||
|
||||
def dram_value(value_str, colour):
|
||||
return [text(DRAM_CX, DRAM_Y + DRAM_H / 2.0 + 6.5, value_str, F_VALUE,
|
||||
colour, weight="700")]
|
||||
|
||||
|
||||
def bus_lines():
|
||||
o = []
|
||||
o.append(line(C0_CX, CORE_Y + CORE_H, C0_CX, CACHE_Y, marker=True))
|
||||
o.append(line(C1_CX, CORE_Y + CORE_H, C1_CX, CACHE_Y, marker=True))
|
||||
o.append(line(C0_CX, CACHE_Y + CACHE_H, C0_CX, DRAM_Y + 6.0, C_LINE, 0.6))
|
||||
o.append(line(C0_CX, DRAM_Y + 6.0, DRAM_X + 8.0, DRAM_Y, C_LINE, 0.6))
|
||||
o.append(line(C1_CX, CACHE_Y + CACHE_H, C1_CX, DRAM_Y + 6.0, C_LINE, 0.6))
|
||||
o.append(line(C1_CX, DRAM_Y + 6.0, DRAM_X + DRAM_W - 8.0, DRAM_Y, C_LINE, 0.6))
|
||||
return o
|
||||
|
||||
|
||||
# ==========================================================================
|
||||
# layer 1 - scaffold: full structure, everyone agrees x = 0
|
||||
# ==========================================================================
|
||||
def layer1():
|
||||
o = [defs()]
|
||||
o += core_box(CORE0_X)
|
||||
o += core_box(CORE1_X)
|
||||
o += bus_lines()
|
||||
o += cache_box_frame(CORE0_X)
|
||||
o += cache_box_frame(CORE1_X)
|
||||
o += cache_tag(CORE0_X)
|
||||
o += cache_tag(CORE1_X)
|
||||
o += cache_value(CORE0_X, "x = 0", C_STALE)
|
||||
o += cache_value(CORE1_X, "x = 0", C_STALE)
|
||||
o += dram_box_frame()
|
||||
o += dram_tag()
|
||||
o += dram_value("x = 0", C_STALE)
|
||||
return o
|
||||
|
||||
|
||||
# ==========================================================================
|
||||
# layer 2 - core0 writes: only its own cache changes
|
||||
# ==========================================================================
|
||||
def layer2():
|
||||
o = []
|
||||
# cover old value + box fill with an updated-state box, then new value
|
||||
o.append(rect(CORE0_X, CACHE_Y, CACHE_W, CACHE_H, C_FILL1, C_MACH, 1.1))
|
||||
o += cache_tag(CORE0_X)
|
||||
o += cache_value(CORE0_X, "x = 1", C_MACH)
|
||||
return o
|
||||
|
||||
|
||||
# ==========================================================================
|
||||
# layer 3 - core1 reads its own cache: emphasise the access, value unchanged
|
||||
# ==========================================================================
|
||||
def layer3():
|
||||
o = []
|
||||
cx = CORE1_X + CACHE_W / 2.0
|
||||
# small self-loop "read" arrow from core1 down into its own cache
|
||||
top = CORE_Y + CORE_H + 1.5
|
||||
bot = CACHE_Y - 1.5
|
||||
r = 5.0
|
||||
o.append(f'<path d="M {cx-3:.2f},{top:.2f} '
|
||||
f'C {cx-3-r:.2f},{(top+bot)/2:.2f} {cx-3-r:.2f},{(top+bot)/2:.2f} '
|
||||
f'{cx-3:.2f},{bot:.2f}" stroke="{C_STALE}" stroke-width="0.6" '
|
||||
f'fill="none" marker-end="url(#arrow)"/>')
|
||||
o.append(rect(CORE1_X, CACHE_Y, CACHE_W, CACHE_H, C_FILL0, C_STALE, 1.1))
|
||||
o += cache_tag(CORE1_X)
|
||||
o += cache_value(CORE1_X, "x = 0", C_STALE)
|
||||
return o
|
||||
|
||||
|
||||
# ==========================================================================
|
||||
# layer 4 - later, unobserved: x = 1 reaches DRAM; core1 still stale
|
||||
# ==========================================================================
|
||||
def layer4():
|
||||
o = []
|
||||
# a faint "time passing" mark between the structure and nothing else
|
||||
ty = DRAM_Y - 9.0
|
||||
dots = "".join(f'<circle cx="{DRAM_CX-8+4*i:.1f}" cy="{ty:.1f}" r="0.7" '
|
||||
f'fill="{C_STALE}"/>' for i in range(5))
|
||||
o.append(dots)
|
||||
o.append(rect(DRAM_X, DRAM_Y, DRAM_W, DRAM_H, C_FILL1, C_MACH, 1.1))
|
||||
o += dram_tag()
|
||||
o += dram_value("x = 1", C_MACH)
|
||||
return o
|
||||
|
||||
|
||||
# ==========================================================================
|
||||
def layer(lid, label, body, display="inline"):
|
||||
return (f'<g inkscape:groupmode="layer" id="{lid}" '
|
||||
f'inkscape:label="{label}" style="display:{display}">\n '
|
||||
+ "\n ".join(body) + "\n</g>")
|
||||
|
||||
|
||||
def build():
|
||||
layers = [
|
||||
layer("layer-1-scaffold", "1 - scaffold (x=0 everywhere)", layer1()),
|
||||
layer("layer-2-core0-write", "2 - core0 writes x=1", layer2()),
|
||||
layer("layer-3-core1-read", "3 - core1 reads: still x=0", layer3()),
|
||||
layer("layer-4-propagate", "4 - later: DRAM sees x=1, core1 still stale",
|
||||
layer4()),
|
||||
]
|
||||
return f'''<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
width="{W}mm" height="{H}mm" viewBox="0 0 {W} {H}" version="1.1">
|
||||
<sodipodi:namedview inkscape:document-units="mm" units="mm"/>
|
||||
<title>Cache coherence and observability</title>
|
||||
{chr(10).join(layers)}
|
||||
</svg>
|
||||
'''
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
open("/mnt/user-data/outputs/coherence.svg", "w").write(build())
|
||||
print("written")
|
||||
Loading…
Reference in a new issue