<!-- Instruction scheduling demo: naïve in-order vs. list-scheduled -->
<p class="hint">Each row is a machine instruction. <b>Loads</b> have latency 3 — the result is not ready for 3 cycles. An add that must wait for a load's result causes <b>stall</b> cycles (dead time). Click <b>Schedule</b> to reorder.</p>
<div class="prog-container">
<div class="prog-panel">
<div class="panel-label">Naïve order</div>
<div id="naive-list" class="instr-list"></div>
<div class="cycles-row"><span class="cycles-label">Total cycles:</span><span id="naive-cycles" class="cycles-val bad-num"></span></div>
</div>
<div class="prog-panel">
<div class="panel-label">Scheduled order</div>
<div id="sched-list" class="instr-list"></div>
<div class="cycles-row"><span class="cycles-label">Total cycles:</span><span id="sched-cycles" class="cycles-val"></span></div>
</div>
</div>
<div class="pipeline-wrap">
<div class="pipeline-label" id="pipeline-title">Pipeline — naïve order (stalls in red)</div>
<div id="pipeline" class="pipeline"></div>
</div>
<div class="status" id="status"></div>
<div class="btns">
<button id="btn-schedule" type="button">Schedule</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
/* Global reset and base styles */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; font-size: 14px; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.prog-container { display: flex; gap: 12px; margin-bottom: .8rem; }
.prog-panel { flex: 1; min-width: 0; }
.panel-label { font-size: .75rem; font-weight: 700; color: #5a7088; text-transform: uppercase; letter-spacing: .05em; margin-bottom: 4px; }
/* Instruction list styles */
.instr-list { display: flex; flex-direction: column; gap: 3px; }
.instr { display: flex; align-items: center; gap: 6px; background: #e8eef3; border: 1px solid #cdd9e3; border-radius: 6px; padding: 4px 8px; font: 600 12px ui-monospace, monospace; cursor: default; }
.instr.type-load { border-left: 3px solid #e76f51; }
.instr.type-add { border-left: 3px solid #2a9d8f; }
.instr .lat { margin-left: auto; font-size: .7rem; color: #777; }
.cycles-row { display: flex; align-items: center; gap: 6px; margin-top: 4px; font-size: .8rem; color: #555; }
.cycles-val { font-weight: 700; color: #1d3557; }
.cycles-val.bad-num { color: #c92f3c; }
.cycles-val.good-num { color: #0a7d33; }
/* Pipeline timeline grid styles */
.pipeline-wrap { margin: .4rem 0 .6rem; }
.pipeline-label { font-size: .75rem; font-weight: 700; color: #5a7088; text-transform: uppercase; letter-spacing: .05em; margin-bottom: 4px; }
.pipeline { display: flex; flex-direction: column; gap: 2px; }
.p-row { display: flex; gap: 2px; align-items: center; }
.p-row-label { width: 92px; font: 600 11px ui-monospace, monospace; color: #555; flex-shrink: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.p-cell { width: 24px; height: 20px; border-radius: 3px; font-size: 9px; display: flex; align-items: center; justify-content: center; }
.p-cell.exec { background: #2a9d8f; color: #fff; }
.p-cell.stall { background: #e63946; color: #fff; }
.p-cell.idle { background: #e8eef3; color: transparent; }
/* Legend and status styles */
.legend { font-size: .75rem; color: #555; margin-bottom: .4rem; display: flex; gap: 10px; flex-wrap: wrap; }
.leg { display: flex; align-items: center; gap: 4px; }
.leg-box { width: 12px; height: 12px; border-radius: 2px; flex-shrink: 0; }
.leg-exec { background: #2a9d8f; }
.leg-stall { background: #e63946; }
.leg-idle { background: #e8eef3; border: 1px solid #cdd9e3; }
.status { font-size: .95rem; font-weight: 600; min-height: 1.4em; margin: .3rem 0; }
.status.good { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; padding: .4rem .85rem; border: 1px solid #1d3557; background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
var i18nFmt = function(s, v) { return s.replace(/%(\w+)%/g, function(_, k) { return String(v[k] !== undefined ? v[k] : ''); }); };
// Program: 4 loads (latency 3) + 3 adds (latency 1) — naïve order interleaves loads with their adds, causing stalls.
// dep = list of instruction IDs this instruction depends on; result available after lat cycles
var PROGRAM = [
{ id: 'LD1', type: 'load', lat: 3, text: 'LOAD r1,[a]', dep: [] },
{ id: 'LD2', type: 'load', lat: 3, text: 'LOAD r2,[b]', dep: [] },
{ id: 'ADD1', type: 'add', lat: 1, text: 'ADD r3,r1,r2', dep: ['LD1','LD2'] },
{ id: 'LD3', type: 'load', lat: 3, text: 'LOAD r4,[c]', dep: [] },
{ id: 'LD4', type: 'load', lat: 3, text: 'LOAD r5,[d]', dep: [] },
{ id: 'ADD2', type: 'add', lat: 1, text: 'ADD r6,r4,r5', dep: ['LD3','LD4'] },
{ id: 'ADD3', type: 'add', lat: 1, text: 'ADD r7,r3,r6', dep: ['ADD1','ADD2'] }
];
// build a quick-lookup map by id
var pm = {};
PROGRAM.forEach(function(instr) { pm[instr.id] = instr; });
// simulateInOrder(order): in-order pipeline — stalls when the next instruction's deps are not yet ready
// returns { cycles, stalls, rows: [{id, cycle, type}] }
function simulateInOrder(order) {
var issued = {};
var c = 0; var stalls = 0;
var rows = []; // execution trace rows
order.forEach(function(id) {
var instr = pm[id];
// stall until all dependency results are available
while (instr.dep.some(function(d) {
return issued[d] === undefined || issued[d] + pm[d].lat > c;
})) {
rows.push({ id: 'STALL', cycle: c, type: 'stall' });
c++; stalls++;
}
rows.push({ id: id, cycle: c, type: 'exec' });
issued[id] = c;
c++;
});
return { cycles: c, stalls: stalls, rows: rows };
}
// chainLen(id): latency-weighted critical-path depth used as scheduling priority
function chainLen(id) {
var instr = pm[id];
if (!instr.dep.length) return instr.lat;
return instr.lat + Math.max.apply(null, instr.dep.map(chainLen));
}
// listSchedule: greedy out-of-order scheduler — each cycle pick the ready instruction with the longest remaining chain (critical-path heuristic)
function listSchedule(naiveOrder) {
var issued = {};
var rem = naiveOrder.slice();
var res = [];
var c = 0;
while (rem.length > 0) {
// collect all instructions whose dependencies are fully satisfied at this cycle
var ready = rem.filter(function(id) {
return pm[id].dep.every(function(d) {
return issued[d] !== undefined && issued[d] + pm[d].lat <= c;
});
});
if (!ready.length) { c++; continue; }
// break ties by longest remaining dependency chain
ready.sort(function(a, b) { return chainLen(b) - chainLen(a); });
var chosen = ready[0];
issued[chosen] = c;
res.push(chosen);
rem.splice(rem.indexOf(chosen), 1);
c++;
}
return res;
}
// renderInstrList: display the instruction list in the given panel
function renderInstrList(elId, order) {
var el = document.getElementById(elId);
el.innerHTML = '';
order.forEach(function(id) {
var instr = pm[id];
var div = document.createElement('div');
div.className = 'instr type-' + instr.type;
div.innerHTML = '<span>' + instr.text + '</span><span class="lat">lat ' + instr.lat + '</span>';
el.appendChild(div);
});
}
// renderPipeline: draw the cycle-by-cycle execution timeline with color-coded cells
function renderPipeline(elId, simResult) {
var el = document.getElementById(elId);
el.innerHTML = '';
var totalCycles = simResult.cycles;
// build the exec/stall/idle color legend
var legend = document.createElement('div');
legend.className = 'legend';
legend.innerHTML =
'<div class="leg"><div class="leg-box leg-exec"></div>executing</div>' +
'<div class="leg"><div class="leg-box leg-stall"></div>stall (wasted)</div>' +
'<div class="leg"><div class="leg-box leg-idle"></div>idle</div>';
el.appendChild(legend);
// build per-instruction executed-cycle sets from the simulation trace
var instrRows = {};
var uniqueIds = [];
simResult.rows.forEach(function(r) {
if (r.type !== 'exec') return;
if (!instrRows[r.id]) { instrRows[r.id] = []; uniqueIds.push(r.id); }
instrRows[r.id].push(r.cycle);
});
// collect all cycle indices that were stalls
var stallCycles = {};
simResult.rows.forEach(function(r) {
if (r.type === 'stall') stallCycles[r.cycle] = true;
});
uniqueIds.forEach(function(id) {
var instr = pm[id];
var execSet = {};
instrRows[id].forEach(function(cy) { execSet[cy] = true; });
var row = document.createElement('div');
row.className = 'p-row';
var lbl = document.createElement('div');
lbl.className = 'p-row-label';
lbl.title = instr.text;
lbl.textContent = instr.text;
row.appendChild(lbl);
for (var c = 0; c < totalCycles; c++) {
var cell = document.createElement('div');
if (execSet[c]) { cell.className = 'p-cell exec'; cell.textContent = '▶'; }
else { cell.className = 'p-cell idle'; cell.textContent = '.'; }
row.appendChild(cell);
}
el.appendChild(row);
});
if (simResult.stalls > 0) {
// add a stall row showing every wasted cycle
var srow = document.createElement('div');
srow.className = 'p-row';
var slbl = document.createElement('div');
slbl.className = 'p-row-label';
slbl.textContent = '— STALL —';
srow.appendChild(slbl);
for (var c = 0; c < totalCycles; c++) {
var scell = document.createElement('div');
if (stallCycles[c]) { scell.className = 'p-cell stall'; scell.textContent = '✕'; }
else { scell.className = 'p-cell idle'; scell.textContent = '.'; }
srow.appendChild(scell);
}
el.appendChild(srow);
}
}
var naiveOrder = PROGRAM.map(function(i) { return i.id; });
var scheduledOrder = naiveOrder.slice();
var isScheduled = false;
function render() {
var naiveResult = simulateInOrder(naiveOrder.slice());
var schedResult = simulateInOrder(scheduledOrder.slice());
renderInstrList('naive-list', naiveOrder);
renderInstrList('sched-list', scheduledOrder);
document.getElementById('naive-cycles').textContent = naiveResult.cycles;
document.getElementById('sched-cycles').textContent = schedResult.cycles;
document.getElementById('sched-cycles').className = 'cycles-val' + (isScheduled ? ' good-num' : '');
var activeResult = isScheduled ? schedResult : naiveResult;
document.getElementById('pipeline-title').textContent = isScheduled ? 'Pipeline — scheduled order (stalls eliminated)' : 'Pipeline — naïve order (stalls in red)';
renderPipeline('pipeline', activeResult);
var saved = naiveResult.cycles - schedResult.cycles;
var statusEl = document.getElementById('status');
if (isScheduled) {
statusEl.textContent = i18nFmt('Scheduled: %sched% cycles vs %naive% naïve — saved %saved% cycle(s)!', { naive: naiveResult.cycles, sched: schedResult.cycles, saved: saved });
statusEl.className = 'status good';
} else {
statusEl.textContent = i18nFmt('Naïve: %stalls% stall cycle(s) wasted — click Schedule to fix it.', { stalls: naiveResult.stalls });
statusEl.className = 'status bad';
}
}
document.getElementById('btn-schedule').addEventListener('click', function() {
scheduledOrder = listSchedule(naiveOrder.slice());
isScheduled = true;
render();
});
document.getElementById('btn-reset').addEventListener('click', function() {
scheduledOrder = naiveOrder.slice();
isScheduled = false;
render();
});
render();
<!-- Instruction scheduling demo: naïve in-order vs. list-scheduled -->
<p class="hint">Each row is a machine instruction. <b>Loads</b> have latency 3 — the result is not ready for 3 cycles. An add that must wait for a load's result causes <b>stall</b> cycles (dead time). Click <b>Schedule</b> to reorder.</p>
<div class="prog-container">
<div class="prog-panel">
<div class="panel-label">Naïve order</div>
<div id="naive-list" class="instr-list"></div>
<div class="cycles-row"><span class="cycles-label">Total cycles:</span><span id="naive-cycles" class="cycles-val bad-num"></span></div>
</div>
<div class="prog-panel">
<div class="panel-label">Scheduled order</div>
<div id="sched-list" class="instr-list"></div>
<div class="cycles-row"><span class="cycles-label">Total cycles:</span><span id="sched-cycles" class="cycles-val"></span></div>
</div>
</div>
<div class="pipeline-wrap">
<div class="pipeline-label" id="pipeline-title">Pipeline — naïve order (stalls in red)</div>
<div id="pipeline" class="pipeline"></div>
</div>
<div class="status" id="status"></div>
<div class="btns">
<button id="btn-schedule" type="button">Schedule</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
/* Global reset and base styles */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; font-size: 14px; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.prog-container { display: flex; gap: 12px; margin-bottom: .8rem; }
.prog-panel { flex: 1; min-width: 0; }
.panel-label { font-size: .75rem; font-weight: 700; color: #5a7088; text-transform: uppercase; letter-spacing: .05em; margin-bottom: 4px; }
/* Instruction list styles */
.instr-list { display: flex; flex-direction: column; gap: 3px; }
.instr { display: flex; align-items: center; gap: 6px; background: #e8eef3; border: 1px solid #cdd9e3; border-radius: 6px; padding: 4px 8px; font: 600 12px ui-monospace, monospace; cursor: default; }
.instr.type-load { border-left: 3px solid #e76f51; }
.instr.type-add { border-left: 3px solid #2a9d8f; }
.instr .lat { margin-left: auto; font-size: .7rem; color: #777; }
.cycles-row { display: flex; align-items: center; gap: 6px; margin-top: 4px; font-size: .8rem; color: #555; }
.cycles-val { font-weight: 700; color: #1d3557; }
.cycles-val.bad-num { color: #c92f3c; }
.cycles-val.good-num { color: #0a7d33; }
/* Pipeline timeline grid styles */
.pipeline-wrap { margin: .4rem 0 .6rem; }
.pipeline-label { font-size: .75rem; font-weight: 700; color: #5a7088; text-transform: uppercase; letter-spacing: .05em; margin-bottom: 4px; }
.pipeline { display: flex; flex-direction: column; gap: 2px; }
.p-row { display: flex; gap: 2px; align-items: center; }
.p-row-label { width: 92px; font: 600 11px ui-monospace, monospace; color: #555; flex-shrink: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.p-cell { width: 24px; height: 20px; border-radius: 3px; font-size: 9px; display: flex; align-items: center; justify-content: center; }
.p-cell.exec { background: #2a9d8f; color: #fff; }
.p-cell.stall { background: #e63946; color: #fff; }
.p-cell.idle { background: #e8eef3; color: transparent; }
/* Legend and status styles */
.legend { font-size: .75rem; color: #555; margin-bottom: .4rem; display: flex; gap: 10px; flex-wrap: wrap; }
.leg { display: flex; align-items: center; gap: 4px; }
.leg-box { width: 12px; height: 12px; border-radius: 2px; flex-shrink: 0; }
.leg-exec { background: #2a9d8f; }
.leg-stall { background: #e63946; }
.leg-idle { background: #e8eef3; border: 1px solid #cdd9e3; }
.status { font-size: .95rem; font-weight: 600; min-height: 1.4em; margin: .3rem 0; }
.status.good { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; padding: .4rem .85rem; border: 1px solid #1d3557; background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
var i18nFmt = function(s, v) { return s.replace(/%(\w+)%/g, function(_, k) { return String(v[k] !== undefined ? v[k] : ''); }); };
// Program: 4 loads (latency 3) + 3 adds (latency 1) — naïve order interleaves loads with their adds, causing stalls.
// dep = list of instruction IDs this instruction depends on; result available after lat cycles
var PROGRAM = [
{ id: 'LD1', type: 'load', lat: 3, text: 'LOAD r1,[a]', dep: [] },
{ id: 'LD2', type: 'load', lat: 3, text: 'LOAD r2,[b]', dep: [] },
{ id: 'ADD1', type: 'add', lat: 1, text: 'ADD r3,r1,r2', dep: ['LD1','LD2'] },
{ id: 'LD3', type: 'load', lat: 3, text: 'LOAD r4,[c]', dep: [] },
{ id: 'LD4', type: 'load', lat: 3, text: 'LOAD r5,[d]', dep: [] },
{ id: 'ADD2', type: 'add', lat: 1, text: 'ADD r6,r4,r5', dep: ['LD3','LD4'] },
{ id: 'ADD3', type: 'add', lat: 1, text: 'ADD r7,r3,r6', dep: ['ADD1','ADD2'] }
];
// build a quick-lookup map by id
var pm = {};
PROGRAM.forEach(function(instr) { pm[instr.id] = instr; });
// simulateInOrder(order): in-order pipeline — stalls when the next instruction's deps are not yet ready
// returns { cycles, stalls, rows: [{id, cycle, type}] }
function simulateInOrder(order) {
var issued = {};
var c = 0; var stalls = 0;
var rows = []; // execution trace rows
order.forEach(function(id) {
var instr = pm[id];
// stall until all dependency results are available
while (instr.dep.some(function(d) {
return issued[d] === undefined || issued[d] + pm[d].lat > c;
})) {
rows.push({ id: 'STALL', cycle: c, type: 'stall' });
c++; stalls++;
}
rows.push({ id: id, cycle: c, type: 'exec' });
issued[id] = c;
c++;
});
return { cycles: c, stalls: stalls, rows: rows };
}
// chainLen(id): latency-weighted critical-path depth used as scheduling priority
function chainLen(id) {
var instr = pm[id];
if (!instr.dep.length) return instr.lat;
return instr.lat + Math.max.apply(null, instr.dep.map(chainLen));
}
// listSchedule: greedy out-of-order scheduler — each cycle pick the ready instruction with the longest remaining chain (critical-path heuristic)
function listSchedule(naiveOrder) {
var issued = {};
var rem = naiveOrder.slice();
var res = [];
var c = 0;
while (rem.length > 0) {
// collect all instructions whose dependencies are fully satisfied at this cycle
var ready = rem.filter(function(id) {
return pm[id].dep.every(function(d) {
return issued[d] !== undefined && issued[d] + pm[d].lat <= c;
});
});
if (!ready.length) { c++; continue; }
// break ties by longest remaining dependency chain
ready.sort(function(a, b) { return chainLen(b) - chainLen(a); });
var chosen = ready[0];
issued[chosen] = c;
res.push(chosen);
rem.splice(rem.indexOf(chosen), 1);
c++;
}
return res;
}
// renderInstrList: display the instruction list in the given panel
function renderInstrList(elId, order) {
var el = document.getElementById(elId);
el.innerHTML = '';
order.forEach(function(id) {
var instr = pm[id];
var div = document.createElement('div');
div.className = 'instr type-' + instr.type;
div.innerHTML = '<span>' + instr.text + '</span><span class="lat">lat ' + instr.lat + '</span>';
el.appendChild(div);
});
}
// renderPipeline: draw the cycle-by-cycle execution timeline with color-coded cells
function renderPipeline(elId, simResult) {
var el = document.getElementById(elId);
el.innerHTML = '';
var totalCycles = simResult.cycles;
// build the exec/stall/idle color legend
var legend = document.createElement('div');
legend.className = 'legend';
legend.innerHTML =
'<div class="leg"><div class="leg-box leg-exec"></div>executing</div>' +
'<div class="leg"><div class="leg-box leg-stall"></div>stall (wasted)</div>' +
'<div class="leg"><div class="leg-box leg-idle"></div>idle</div>';
el.appendChild(legend);
// build per-instruction executed-cycle sets from the simulation trace
var instrRows = {};
var uniqueIds = [];
simResult.rows.forEach(function(r) {
if (r.type !== 'exec') return;
if (!instrRows[r.id]) { instrRows[r.id] = []; uniqueIds.push(r.id); }
instrRows[r.id].push(r.cycle);
});
// collect all cycle indices that were stalls
var stallCycles = {};
simResult.rows.forEach(function(r) {
if (r.type === 'stall') stallCycles[r.cycle] = true;
});
uniqueIds.forEach(function(id) {
var instr = pm[id];
var execSet = {};
instrRows[id].forEach(function(cy) { execSet[cy] = true; });
var row = document.createElement('div');
row.className = 'p-row';
var lbl = document.createElement('div');
lbl.className = 'p-row-label';
lbl.title = instr.text;
lbl.textContent = instr.text;
row.appendChild(lbl);
for (var c = 0; c < totalCycles; c++) {
var cell = document.createElement('div');
if (execSet[c]) { cell.className = 'p-cell exec'; cell.textContent = '▶'; }
else { cell.className = 'p-cell idle'; cell.textContent = '.'; }
row.appendChild(cell);
}
el.appendChild(row);
});
if (simResult.stalls > 0) {
// add a stall row showing every wasted cycle
var srow = document.createElement('div');
srow.className = 'p-row';
var slbl = document.createElement('div');
slbl.className = 'p-row-label';
slbl.textContent = '— STALL —';
srow.appendChild(slbl);
for (var c = 0; c < totalCycles; c++) {
var scell = document.createElement('div');
if (stallCycles[c]) { scell.className = 'p-cell stall'; scell.textContent = '✕'; }
else { scell.className = 'p-cell idle'; scell.textContent = '.'; }
srow.appendChild(scell);
}
el.appendChild(srow);
}
}
var naiveOrder = PROGRAM.map(function(i) { return i.id; });
var scheduledOrder = naiveOrder.slice();
var isScheduled = false;
function render() {
var naiveResult = simulateInOrder(naiveOrder.slice());
var schedResult = simulateInOrder(scheduledOrder.slice());
renderInstrList('naive-list', naiveOrder);
renderInstrList('sched-list', scheduledOrder);
document.getElementById('naive-cycles').textContent = naiveResult.cycles;
document.getElementById('sched-cycles').textContent = schedResult.cycles;
document.getElementById('sched-cycles').className = 'cycles-val' + (isScheduled ? ' good-num' : '');
var activeResult = isScheduled ? schedResult : naiveResult;
document.getElementById('pipeline-title').textContent = isScheduled ? 'Pipeline — scheduled order (stalls eliminated)' : 'Pipeline — naïve order (stalls in red)';
renderPipeline('pipeline', activeResult);
var saved = naiveResult.cycles - schedResult.cycles;
var statusEl = document.getElementById('status');
if (isScheduled) {
statusEl.textContent = i18nFmt('Scheduled: %sched% cycles vs %naive% naïve — saved %saved% cycle(s)!', { naive: naiveResult.cycles, sched: schedResult.cycles, saved: saved });
statusEl.className = 'status good';
} else {
statusEl.textContent = i18nFmt('Naïve: %stalls% stall cycle(s) wasted — click Schedule to fix it.', { stalls: naiveResult.stalls });
statusEl.className = 'status bad';
}
}
document.getElementById('btn-schedule').addEventListener('click', function() {
scheduledOrder = listSchedule(naiveOrder.slice());
isScheduled = true;
render();
});
document.getElementById('btn-reset').addEventListener('click', function() {
scheduledOrder = naiveOrder.slice();
isScheduled = false;
render();
});
render();
Comments
Loading comments...