The demo below simulates two cores sharing a single memory address. Each core has its own cache slot. Use the buttons to issue Read and Write operations from either core, and watch the MESI state of each cache slot update in real time.
<!-- MESI cache-coherence demo — two cores, one shared address -->
<div class="scene">
<div class="core-box" id="core0">
<div class="core-label">Core 0</div>
<div class="cache-slot" id="slot0">
<div class="slot-label">Cache</div>
<div class="slot-state" id="state0">I</div>
<div class="slot-value" id="val0">—</div>
</div>
<div class="btn-row">
<button id="r0" type="button">Read</button>
<button id="w0" type="button">Write</button>
</div>
</div>
<div class="bus-area">
<div class="bus-label">Interconnect Bus</div>
<div class="bus-line">
<div class="bus-msg" id="busmsg"></div>
</div>
<div class="mem-box">
<div class="mem-label">Main Memory</div>
<div class="mem-val" id="memval">0</div>
</div>
</div>
<div class="core-box" id="core1">
<div class="core-label">Core 1</div>
<div class="cache-slot" id="slot1">
<div class="slot-label">Cache</div>
<div class="slot-state" id="state1">I</div>
<div class="slot-value" id="val1">—</div>
</div>
<div class="btn-row">
<button id="r1" type="button">Read</button>
<button id="w1" type="button">Write</button>
</div>
</div>
</div>
<div class="log-area">
<div class="log-hdr">Event Log</div>
<div id="log"></div>
</div>
<div class="btns-bottom">
<button id="resetBtn" type="button" class="ghost">Reset</button>
</div>
/* Layout and color for the MESI cache-coherence demo */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #1a2233; background: #f6f8fb; }
.scene { display: flex; align-items: flex-start; gap: 10px; justify-content: center; padding: 14px 8px 6px; flex-wrap: wrap; }
.core-box { background: #fff; border: 1.5px solid #c5d3e0; border-radius: 12px; padding: 10px 12px; min-width: 130px; display: flex; flex-direction: column; align-items: center; gap: 8px; }
.core-label { font-weight: 700; font-size: .85rem; color: #1d3557; }
.cache-slot { background: #edf2f7; border: 1.5px solid #b0c4d8; border-radius: 8px; padding: 6px 14px; text-align: center; min-width: 100px; }
.slot-label { font-size: .7rem; color: #5a7088; text-transform: uppercase; letter-spacing: .05em; margin-bottom: 2px; }
.slot-state { font-size: 1.5rem; font-weight: 900; letter-spacing: .05em; transition: color .3s; }
.slot-value { font-size: 1rem; font-weight: 600; color: #2d6a4f; min-height: 1.4em; }
.state-M { color: #e63946; }
.state-E { color: #0077b6; }
.state-S { color: #2a9d8f; }
.state-I { color: #adb5bd; }
.btn-row { display: flex; gap: 6px; }
button { font: 600 13px system-ui; padding: .38rem .75rem; border: 1.5px solid #1d3557; background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; transition: background .15s; }
button:hover { background: #2a4a7f; }
button.ghost { background: #fff; color: #1d3557; }
button.ghost:hover { background: #e8eef4; }
.bus-area { display: flex; flex-direction: column; align-items: center; gap: 6px; padding-top: 28px; min-width: 100px; }
.bus-label { font-size: .7rem; color: #5a7088; text-transform: uppercase; letter-spacing: .05em; }
.bus-line { width: 4px; height: 70px; background: #b0c4d8; border-radius: 2px; position: relative; }
.bus-msg { position: absolute; left: 50%; transform: translateX(-50%); top: 50%; background: #e63946; color: #fff; font-size: .65rem; font-weight: 700; padding: 2px 5px; border-radius: 4px; white-space: nowrap; opacity: 0; transition: opacity .3s; }
.bus-msg.visible { opacity: 1; }
.mem-box { background: #fff; border: 1.5px solid #a8c4de; border-radius: 8px; padding: 5px 14px; text-align: center; }
.mem-label { font-size: .7rem; color: #5a7088; text-transform: uppercase; letter-spacing: .05em; }
.mem-val { font-size: 1.1rem; font-weight: 700; color: #1d3557; }
.log-area { margin: 4px 10px 0; background: #fff; border: 1px solid #d1dce8; border-radius: 8px; max-height: 120px; overflow-y: auto; padding: 6px 10px; }
.log-hdr { font-size: .72rem; color: #5a7088; text-transform: uppercase; letter-spacing: .05em; margin-bottom: 4px; }
#log { font-size: .8rem; line-height: 1.6; color: #1a2233; }
.log-entry { padding: 1px 0; border-bottom: 1px solid #f0f4f8; }
.log-entry:last-child { border-bottom: none; }
.btns-bottom { display: flex; justify-content: center; padding: 8px; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// MESI protocol: each cache line is in one of four states per core.
// States: M=Modified (dirty, exclusive), E=Exclusive (clean, exclusive),
// S=Shared (clean, multiple readers), I=Invalid (no data). Transitions on read/write.
// MESI state machine for two cores sharing one address
let mem = 0;
let writeCounter = 0;
// cache[i] = { state: 'M'|'E'|'S'|'I', value: number|null }
const cache = [
{ state: 'I', value: null },
{ state: 'I', value: null }
];
// Map state letter to CSS class for color coding
const stateClass = { M: 'state-M', E: 'state-E', S: 'state-S', I: 'state-I' };
function render() {
// Update DOM to reflect current cache and memory state
for (let i = 0; i < 2; i++) {
const s = document.getElementById('state' + i);
const v = document.getElementById('val' + i);
s.textContent = cache[i].state;
s.className = 'slot-state ' + stateClass[cache[i].state];
v.textContent = cache[i].state === 'I' ? '—' : cache[i].value;
}
document.getElementById('memval').textContent = mem;
}
function flashBus(msg) {
// Show a bus message briefly to illustrate interconnect traffic
const el = document.getElementById('busmsg');
el.textContent = msg;
el.classList.add('visible');
setTimeout(() => el.classList.remove('visible'), 900);
}
function addLog(text) {
const log = document.getElementById('log');
const d = document.createElement('div');
d.className = 'log-entry';
d.textContent = text;
log.prepend(d);
// Keep log bounded so the UI stays tidy
while (log.children.length > 30) log.removeChild(log.lastChild);
}
// doRead(core): simulate a cache-read from this core under MESI
function doRead(core) {
const other = 1 - core;
const prev = cache[core].state;
if (prev !== 'I') {
// Hit — no bus traffic needed
addLog(i18nFmt('Core %core%: Read hit (state=%state%, val=%val%) — no bus traffic.', { core, state: prev, val: cache[core].value }));
return;
}
// cache miss: fetch from other core (if M) or from memory
if (cache[other].state === 'M') {
// Other core holds Modified copy — must write back before sharing
mem = cache[other].value;
flashBus(i18nFmt('WB ← Core %core%', { core: other }));
addLog(i18nFmt('Core %other%: Write-back to memory (val=%mem%) — now Shared.', { other, mem }));
cache[other].state = 'S';
cache[other].value = mem;
} else if (cache[other].state === 'E') {
cache[other].state = 'S';
}
// now load from memory
cache[core].state = (cache[other].state === 'S') ? 'S' : 'E';
cache[core].value = mem;
if (cache[other].state !== 'S') {
flashBus(i18nFmt('RD → Core %core%', { core }));
}
addLog(i18nFmt('Core %core%: Read miss → state=%state%, loaded val=%val%.', { core, state: cache[core].state, val: mem }));
render();
}
// doWrite(core): simulate a cache-write, invalidating all other copies
function doWrite(core) {
const other = 1 - core;
writeCounter++;
const newVal = writeCounter;
if (cache[other].state === 'M') {
// Other core has Modified data — write back first, then invalidate
mem = cache[other].value;
cache[other].state = 'I';
cache[other].value = null;
flashBus(i18nFmt('INV → Core %core%', { core: other }));
addLog(i18nFmt('Core %other%: Write-back to memory (val=%mem%) before invalidation.', { other, mem }));
} else if (cache[other].state === 'E' || cache[other].state === 'S') {
// Send invalidation message on bus to all sharers
cache[other].state = 'I';
cache[other].value = null;
flashBus(i18nFmt('INV → Core %core%', { core: other }));
addLog(i18nFmt('Core %other%: Invalidated (was Shared/Exclusive).', { other }));
}
cache[core].state = 'M';
cache[core].value = newVal;
// Memory is stale while this core holds Modified; it will be updated on eviction or another read
addLog(i18nFmt('Core %core%: Write → Modified, val=%val%. All other copies invalidated.', { core, val: newVal }));
render();
}
document.getElementById('r0').addEventListener('click', () => doRead(0));
document.getElementById('w0').addEventListener('click', () => doWrite(0));
document.getElementById('r1').addEventListener('click', () => doRead(1));
document.getElementById('w1').addEventListener('click', () => doWrite(1));
document.getElementById('resetBtn').addEventListener('click', () => {
mem = 0; writeCounter = 0;
cache[0] = { state: 'I', value: null };
cache[1] = { state: 'I', value: null };
document.getElementById('log').innerHTML = '';
document.getElementById('busmsg').classList.remove('visible');
render();
addLog('Reset. Both caches Invalid. Memory = 0.');
});
render();
addLog('Initial state: both caches Invalid. Memory = 0.');
<!-- MESI cache-coherence demo — two cores, one shared address -->
<div class="scene">
<div class="core-box" id="core0">
<div class="core-label">Core 0</div>
<div class="cache-slot" id="slot0">
<div class="slot-label">Cache</div>
<div class="slot-state" id="state0">I</div>
<div class="slot-value" id="val0">—</div>
</div>
<div class="btn-row">
<button id="r0" type="button">Read</button>
<button id="w0" type="button">Write</button>
</div>
</div>
<div class="bus-area">
<div class="bus-label">Interconnect Bus</div>
<div class="bus-line">
<div class="bus-msg" id="busmsg"></div>
</div>
<div class="mem-box">
<div class="mem-label">Main Memory</div>
<div class="mem-val" id="memval">0</div>
</div>
</div>
<div class="core-box" id="core1">
<div class="core-label">Core 1</div>
<div class="cache-slot" id="slot1">
<div class="slot-label">Cache</div>
<div class="slot-state" id="state1">I</div>
<div class="slot-value" id="val1">—</div>
</div>
<div class="btn-row">
<button id="r1" type="button">Read</button>
<button id="w1" type="button">Write</button>
</div>
</div>
</div>
<div class="log-area">
<div class="log-hdr">Event Log</div>
<div id="log"></div>
</div>
<div class="btns-bottom">
<button id="resetBtn" type="button" class="ghost">Reset</button>
</div>
/* Layout and color for the MESI cache-coherence demo */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #1a2233; background: #f6f8fb; }
.scene { display: flex; align-items: flex-start; gap: 10px; justify-content: center; padding: 14px 8px 6px; flex-wrap: wrap; }
.core-box { background: #fff; border: 1.5px solid #c5d3e0; border-radius: 12px; padding: 10px 12px; min-width: 130px; display: flex; flex-direction: column; align-items: center; gap: 8px; }
.core-label { font-weight: 700; font-size: .85rem; color: #1d3557; }
.cache-slot { background: #edf2f7; border: 1.5px solid #b0c4d8; border-radius: 8px; padding: 6px 14px; text-align: center; min-width: 100px; }
.slot-label { font-size: .7rem; color: #5a7088; text-transform: uppercase; letter-spacing: .05em; margin-bottom: 2px; }
.slot-state { font-size: 1.5rem; font-weight: 900; letter-spacing: .05em; transition: color .3s; }
.slot-value { font-size: 1rem; font-weight: 600; color: #2d6a4f; min-height: 1.4em; }
.state-M { color: #e63946; }
.state-E { color: #0077b6; }
.state-S { color: #2a9d8f; }
.state-I { color: #adb5bd; }
.btn-row { display: flex; gap: 6px; }
button { font: 600 13px system-ui; padding: .38rem .75rem; border: 1.5px solid #1d3557; background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; transition: background .15s; }
button:hover { background: #2a4a7f; }
button.ghost { background: #fff; color: #1d3557; }
button.ghost:hover { background: #e8eef4; }
.bus-area { display: flex; flex-direction: column; align-items: center; gap: 6px; padding-top: 28px; min-width: 100px; }
.bus-label { font-size: .7rem; color: #5a7088; text-transform: uppercase; letter-spacing: .05em; }
.bus-line { width: 4px; height: 70px; background: #b0c4d8; border-radius: 2px; position: relative; }
.bus-msg { position: absolute; left: 50%; transform: translateX(-50%); top: 50%; background: #e63946; color: #fff; font-size: .65rem; font-weight: 700; padding: 2px 5px; border-radius: 4px; white-space: nowrap; opacity: 0; transition: opacity .3s; }
.bus-msg.visible { opacity: 1; }
.mem-box { background: #fff; border: 1.5px solid #a8c4de; border-radius: 8px; padding: 5px 14px; text-align: center; }
.mem-label { font-size: .7rem; color: #5a7088; text-transform: uppercase; letter-spacing: .05em; }
.mem-val { font-size: 1.1rem; font-weight: 700; color: #1d3557; }
.log-area { margin: 4px 10px 0; background: #fff; border: 1px solid #d1dce8; border-radius: 8px; max-height: 120px; overflow-y: auto; padding: 6px 10px; }
.log-hdr { font-size: .72rem; color: #5a7088; text-transform: uppercase; letter-spacing: .05em; margin-bottom: 4px; }
#log { font-size: .8rem; line-height: 1.6; color: #1a2233; }
.log-entry { padding: 1px 0; border-bottom: 1px solid #f0f4f8; }
.log-entry:last-child { border-bottom: none; }
.btns-bottom { display: flex; justify-content: center; padding: 8px; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// MESI protocol: each cache line is in one of four states per core.
// States: M=Modified (dirty, exclusive), E=Exclusive (clean, exclusive),
// S=Shared (clean, multiple readers), I=Invalid (no data). Transitions on read/write.
// MESI state machine for two cores sharing one address
let mem = 0;
let writeCounter = 0;
// cache[i] = { state: 'M'|'E'|'S'|'I', value: number|null }
const cache = [
{ state: 'I', value: null },
{ state: 'I', value: null }
];
// Map state letter to CSS class for color coding
const stateClass = { M: 'state-M', E: 'state-E', S: 'state-S', I: 'state-I' };
function render() {
// Update DOM to reflect current cache and memory state
for (let i = 0; i < 2; i++) {
const s = document.getElementById('state' + i);
const v = document.getElementById('val' + i);
s.textContent = cache[i].state;
s.className = 'slot-state ' + stateClass[cache[i].state];
v.textContent = cache[i].state === 'I' ? '—' : cache[i].value;
}
document.getElementById('memval').textContent = mem;
}
function flashBus(msg) {
// Show a bus message briefly to illustrate interconnect traffic
const el = document.getElementById('busmsg');
el.textContent = msg;
el.classList.add('visible');
setTimeout(() => el.classList.remove('visible'), 900);
}
function addLog(text) {
const log = document.getElementById('log');
const d = document.createElement('div');
d.className = 'log-entry';
d.textContent = text;
log.prepend(d);
// Keep log bounded so the UI stays tidy
while (log.children.length > 30) log.removeChild(log.lastChild);
}
// doRead(core): simulate a cache-read from this core under MESI
function doRead(core) {
const other = 1 - core;
const prev = cache[core].state;
if (prev !== 'I') {
// Hit — no bus traffic needed
addLog(i18nFmt('Core %core%: Read hit (state=%state%, val=%val%) — no bus traffic.', { core, state: prev, val: cache[core].value }));
return;
}
// cache miss: fetch from other core (if M) or from memory
if (cache[other].state === 'M') {
// Other core holds Modified copy — must write back before sharing
mem = cache[other].value;
flashBus(i18nFmt('WB ← Core %core%', { core: other }));
addLog(i18nFmt('Core %other%: Write-back to memory (val=%mem%) — now Shared.', { other, mem }));
cache[other].state = 'S';
cache[other].value = mem;
} else if (cache[other].state === 'E') {
cache[other].state = 'S';
}
// now load from memory
cache[core].state = (cache[other].state === 'S') ? 'S' : 'E';
cache[core].value = mem;
if (cache[other].state !== 'S') {
flashBus(i18nFmt('RD → Core %core%', { core }));
}
addLog(i18nFmt('Core %core%: Read miss → state=%state%, loaded val=%val%.', { core, state: cache[core].state, val: mem }));
render();
}
// doWrite(core): simulate a cache-write, invalidating all other copies
function doWrite(core) {
const other = 1 - core;
writeCounter++;
const newVal = writeCounter;
if (cache[other].state === 'M') {
// Other core has Modified data — write back first, then invalidate
mem = cache[other].value;
cache[other].state = 'I';
cache[other].value = null;
flashBus(i18nFmt('INV → Core %core%', { core: other }));
addLog(i18nFmt('Core %other%: Write-back to memory (val=%mem%) before invalidation.', { other, mem }));
} else if (cache[other].state === 'E' || cache[other].state === 'S') {
// Send invalidation message on bus to all sharers
cache[other].state = 'I';
cache[other].value = null;
flashBus(i18nFmt('INV → Core %core%', { core: other }));
addLog(i18nFmt('Core %other%: Invalidated (was Shared/Exclusive).', { other }));
}
cache[core].state = 'M';
cache[core].value = newVal;
// Memory is stale while this core holds Modified; it will be updated on eviction or another read
addLog(i18nFmt('Core %core%: Write → Modified, val=%val%. All other copies invalidated.', { core, val: newVal }));
render();
}
document.getElementById('r0').addEventListener('click', () => doRead(0));
document.getElementById('w0').addEventListener('click', () => doWrite(0));
document.getElementById('r1').addEventListener('click', () => doRead(1));
document.getElementById('w1').addEventListener('click', () => doWrite(1));
document.getElementById('resetBtn').addEventListener('click', () => {
mem = 0; writeCounter = 0;
cache[0] = { state: 'I', value: null };
cache[1] = { state: 'I', value: null };
document.getElementById('log').innerHTML = '';
document.getElementById('busmsg').classList.remove('visible');
render();
addLog('Reset. Both caches Invalid. Memory = 0.');
});
render();
addLog('Initial state: both caches Invalid. Memory = 0.');
Notice what happens when one core writes while the other holds a Shared copy: the writing core's cache transitions to Modified and the other core's copy is immediately Invalidated — it must fetch the new value before its next read. This ping-pong between cores is called cache-line bouncing, and it is the hidden performance cost every parallel programmer must respect.
Comments
Loading comments...