Choose how many banks you want and how long each bank takes to respond. Hit Run burst to watch a 8-word sequential read complete — either serialized through one bank or pipelined across all of them.
<!-- Demo: interleaved vs single-bank burst read — 8 sequential words -->
<div class="controls">
<label>Banks:
<select id="bankCount">
<option value="1">1</option>
<option value="2">2</option>
<option value="4" selected>4</option>
<option value="8">8</option>
</select>
</label>
<label>Latency (cycles):
<select id="latency">
<option value="2">2</option>
<option value="4" selected>4</option>
<option value="6">6</option>
<option value="8">8</option>
</select>
</label>
<button id="runBtn" type="button">Run burst</button>
</div>
<div id="bankViz" class="bank-viz" aria-label="Memory bank visualization"></div>
<div class="result-area">
<div class="result-row">
<span class="label-col">1 bank</span>
<div class="bar-wrap"><div id="barSingle" class="bar bar-single"></div></div>
<span id="timeSingle" class="time-label">-</span>
</div>
<div class="result-row">
<span class="label-col">k banks</span>
<div class="bar-wrap"><div id="barInterleaved" class="bar bar-interleaved"></div></div>
<span id="timeInterleaved" class="time-label">-</span>
</div>
</div>
<p id="status" class="status"></p>
/* Layout: controls + bank columns + comparison bars */
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: system-ui, sans-serif; color: #222; padding: .6rem; }
.controls { display: flex; flex-wrap: wrap; gap: .6rem; align-items: center; margin-bottom: .8rem; }
label { font-size: .85rem; display: flex; align-items: center; gap: .3rem; }
select { font-size: .85rem; padding: .2rem .4rem; border: 1px solid #aaa; border-radius: 6px; }
button { font: 600 14px system-ui; padding: .4rem .9rem; background: #1d3557; color: #fff;
border: none; border-radius: 8px; cursor: pointer; }
button:disabled { opacity: .5; cursor: default; }
/* Bank columns — each column shows the addresses that map to it */
.bank-viz { display: flex; gap: 6px; margin-bottom: .8rem; flex-wrap: wrap; }
.bank { border: 1.5px solid #adb1b8; border-radius: 8px; overflow: hidden;
width: 56px; min-width: 56px; }
.bank-header { font-size: .7rem; font-weight: 700; text-align: center;
padding: 2px 0; background: #e8eef3; color: #1d3557; }
.bank-cells { display: flex; flex-direction: column; }
.bank-cell { height: 22px; display: flex; align-items: center; justify-content: center;
font-size: .72rem; font-family: ui-monospace, monospace; color: #444;
border-top: 1px solid #e0e4e8; transition: background .18s; }
.bank-cell.active { background: #f4a261; color: #222; font-weight: 700; }
.bank-cell.done { background: #52b788; color: #fff; }
/* Comparison bars for single-bank vs interleaved time */
.result-area { margin-bottom: .6rem; }
.result-row { display: flex; align-items: center; gap: .5rem; margin-bottom: .4rem; }
.label-col { font-size: .78rem; width: 80px; flex-shrink: 0; font-weight: 600; color: #444; }
.bar-wrap { flex: 1; background: #eee; border-radius: 4px; height: 18px; overflow: hidden; }
.bar { height: 100%; width: 0; border-radius: 4px; transition: width .4s ease; }
.bar-single { background: #e63946; }
.bar-interleaved { background: #2a9d8f; }
.time-label { font-size: .8rem; width: 52px; flex-shrink: 0; text-align: right; }
.status { font-size: .88rem; font-weight: 600; min-height: 1.3em; color: #1d3557; }
/* Memory interleaving demo — compare single-bank serial access vs k-bank pipelined burst */
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
const WORDS = 8; // number of words in the burst
/* Bank mapping: address a goes to bank (a mod k) */
function bankOf(addr, k) { return addr % k; }
/* Single-bank total time: every word queues behind the previous one */
function singleBankTime(n, lat) { return n * lat; }
/* Interleaved total time: each word starts as soon as its bank is free */
function interleavedTime(n, k, lat) {
// pipeline: each word i goes to bank (i mod k); banks work in parallel
const bankFree = new Array(k).fill(0); // next cycle each bank is free to accept a new request
let last = 0;
for (let i = 0; i < n; i++) {
const b = i % k;
const end = bankFree[b] + lat; // this bank starts immediately when free and finishes one latency later
bankFree[b] = end;
last = Math.max(last, end);
}
return last;
}
/* Render bank columns with address slots */
function renderBanks(k) {
const viz = document.getElementById('bankViz');
viz.innerHTML = '';
for (let b = 0; b < k; b++) {
const col = document.createElement('div');
col.className = 'bank';
col.id = 'bank-' + b;
const hdr = document.createElement('div');
hdr.className = 'bank-header';
hdr.textContent = i18nFmt('B%n%', { n: b });
col.appendChild(hdr);
const cells = document.createElement('div');
cells.className = 'bank-cells';
// fill in addresses that map to this bank
for (let addr = b; addr < WORDS; addr += k) {
const cell = document.createElement('div');
cell.className = 'bank-cell';
cell.id = 'cell-' + addr;
cell.textContent = i18nFmt('a%a%', { a: addr });
cells.appendChild(cell);
}
col.appendChild(cells);
viz.appendChild(col);
}
}
/* Animate the burst access step by step */
async function animateBurst(k, lat) {
const delay = ms => new Promise(r => setTimeout(r, ms));
const STEP_MS = 120; // milliseconds per simulated cycle
// reset all cells to idle
for (let a = 0; a < WORDS; a++) {
const el = document.getElementById('cell-' + a);
if (el) el.className = 'bank-cell';
}
// build per-word schedule: when does each address start/end?
const schedule = [];
const bankFreeAt = new Array(k).fill(0);
for (let i = 0; i < WORDS; i++) {
const b = bankOf(i, k);
// word i starts when its bank is free
const start = bankFreeAt[b];
const end = start + lat;
bankFreeAt[b] = end;
schedule.push({ addr: i, bank: b, start, end });
}
const totalT = Math.max(...schedule.map(s => s.end));
for (let t = 0; t <= totalT; t++) {
for (const s of schedule) {
const el = document.getElementById('cell-' + s.addr);
if (!el) continue;
if (t >= s.start && t < s.end) el.className = 'bank-cell active';
else if (t >= s.end) el.className = 'bank-cell done';
}
await delay(STEP_MS);
}
}
/* Update comparison bars */
function updateBars(tSingle, tInterleaved) {
const maxT = Math.max(tSingle, 1);
document.getElementById('barSingle').style.width = '100%';
document.getElementById('barInterleaved').style.width = (tInterleaved / maxT * 100) + '%';
document.getElementById('timeSingle').textContent = i18nFmt('%n% cy', { n: tSingle });
document.getElementById('timeInterleaved').textContent = i18nFmt('%n% cy', { n: tInterleaved });
}
/* Main run handler */
async function run() {
const k = parseInt(document.getElementById('bankCount').value, 10);
const lat = parseInt(document.getElementById('latency').value, 10);
document.getElementById('runBtn').disabled = true;
document.getElementById('status').textContent = 'Simulating burst…';
renderBanks(k);
await new Promise(r => setTimeout(r, 80));
const tSingle = singleBankTime(WORDS, lat);
const tInterleaved = interleavedTime(WORDS, k, lat);
updateBars(tSingle, tInterleaved);
await animateBurst(k, lat);
const speedup = (tSingle / tInterleaved).toFixed(2);
document.getElementById('status').textContent =
i18nFmt('%k% banks: %tI% cycles vs %tS% (×%sp% speedup)', { k, tI: tInterleaved, tS: tSingle, sp: speedup });
document.getElementById('runBtn').disabled = false;
}
document.getElementById('runBtn').addEventListener('click', run);
// show 4-bank layout on load
renderBanks(4);
<!-- Demo: interleaved vs single-bank burst read — 8 sequential words -->
<div class="controls">
<label>Banks:
<select id="bankCount">
<option value="1">1</option>
<option value="2">2</option>
<option value="4" selected>4</option>
<option value="8">8</option>
</select>
</label>
<label>Latency (cycles):
<select id="latency">
<option value="2">2</option>
<option value="4" selected>4</option>
<option value="6">6</option>
<option value="8">8</option>
</select>
</label>
<button id="runBtn" type="button">Run burst</button>
</div>
<div id="bankViz" class="bank-viz" aria-label="Memory bank visualization"></div>
<div class="result-area">
<div class="result-row">
<span class="label-col">1 bank</span>
<div class="bar-wrap"><div id="barSingle" class="bar bar-single"></div></div>
<span id="timeSingle" class="time-label">-</span>
</div>
<div class="result-row">
<span class="label-col">k banks</span>
<div class="bar-wrap"><div id="barInterleaved" class="bar bar-interleaved"></div></div>
<span id="timeInterleaved" class="time-label">-</span>
</div>
</div>
<p id="status" class="status"></p>
/* Layout: controls + bank columns + comparison bars */
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: system-ui, sans-serif; color: #222; padding: .6rem; }
.controls { display: flex; flex-wrap: wrap; gap: .6rem; align-items: center; margin-bottom: .8rem; }
label { font-size: .85rem; display: flex; align-items: center; gap: .3rem; }
select { font-size: .85rem; padding: .2rem .4rem; border: 1px solid #aaa; border-radius: 6px; }
button { font: 600 14px system-ui; padding: .4rem .9rem; background: #1d3557; color: #fff;
border: none; border-radius: 8px; cursor: pointer; }
button:disabled { opacity: .5; cursor: default; }
/* Bank columns — each column shows the addresses that map to it */
.bank-viz { display: flex; gap: 6px; margin-bottom: .8rem; flex-wrap: wrap; }
.bank { border: 1.5px solid #adb1b8; border-radius: 8px; overflow: hidden;
width: 56px; min-width: 56px; }
.bank-header { font-size: .7rem; font-weight: 700; text-align: center;
padding: 2px 0; background: #e8eef3; color: #1d3557; }
.bank-cells { display: flex; flex-direction: column; }
.bank-cell { height: 22px; display: flex; align-items: center; justify-content: center;
font-size: .72rem; font-family: ui-monospace, monospace; color: #444;
border-top: 1px solid #e0e4e8; transition: background .18s; }
.bank-cell.active { background: #f4a261; color: #222; font-weight: 700; }
.bank-cell.done { background: #52b788; color: #fff; }
/* Comparison bars for single-bank vs interleaved time */
.result-area { margin-bottom: .6rem; }
.result-row { display: flex; align-items: center; gap: .5rem; margin-bottom: .4rem; }
.label-col { font-size: .78rem; width: 80px; flex-shrink: 0; font-weight: 600; color: #444; }
.bar-wrap { flex: 1; background: #eee; border-radius: 4px; height: 18px; overflow: hidden; }
.bar { height: 100%; width: 0; border-radius: 4px; transition: width .4s ease; }
.bar-single { background: #e63946; }
.bar-interleaved { background: #2a9d8f; }
.time-label { font-size: .8rem; width: 52px; flex-shrink: 0; text-align: right; }
.status { font-size: .88rem; font-weight: 600; min-height: 1.3em; color: #1d3557; }
/* Memory interleaving demo — compare single-bank serial access vs k-bank pipelined burst */
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
const WORDS = 8; // number of words in the burst
/* Bank mapping: address a goes to bank (a mod k) */
function bankOf(addr, k) { return addr % k; }
/* Single-bank total time: every word queues behind the previous one */
function singleBankTime(n, lat) { return n * lat; }
/* Interleaved total time: each word starts as soon as its bank is free */
function interleavedTime(n, k, lat) {
// pipeline: each word i goes to bank (i mod k); banks work in parallel
const bankFree = new Array(k).fill(0); // next cycle each bank is free to accept a new request
let last = 0;
for (let i = 0; i < n; i++) {
const b = i % k;
const end = bankFree[b] + lat; // this bank starts immediately when free and finishes one latency later
bankFree[b] = end;
last = Math.max(last, end);
}
return last;
}
/* Render bank columns with address slots */
function renderBanks(k) {
const viz = document.getElementById('bankViz');
viz.innerHTML = '';
for (let b = 0; b < k; b++) {
const col = document.createElement('div');
col.className = 'bank';
col.id = 'bank-' + b;
const hdr = document.createElement('div');
hdr.className = 'bank-header';
hdr.textContent = i18nFmt('B%n%', { n: b });
col.appendChild(hdr);
const cells = document.createElement('div');
cells.className = 'bank-cells';
// fill in addresses that map to this bank
for (let addr = b; addr < WORDS; addr += k) {
const cell = document.createElement('div');
cell.className = 'bank-cell';
cell.id = 'cell-' + addr;
cell.textContent = i18nFmt('a%a%', { a: addr });
cells.appendChild(cell);
}
col.appendChild(cells);
viz.appendChild(col);
}
}
/* Animate the burst access step by step */
async function animateBurst(k, lat) {
const delay = ms => new Promise(r => setTimeout(r, ms));
const STEP_MS = 120; // milliseconds per simulated cycle
// reset all cells to idle
for (let a = 0; a < WORDS; a++) {
const el = document.getElementById('cell-' + a);
if (el) el.className = 'bank-cell';
}
// build per-word schedule: when does each address start/end?
const schedule = [];
const bankFreeAt = new Array(k).fill(0);
for (let i = 0; i < WORDS; i++) {
const b = bankOf(i, k);
// word i starts when its bank is free
const start = bankFreeAt[b];
const end = start + lat;
bankFreeAt[b] = end;
schedule.push({ addr: i, bank: b, start, end });
}
const totalT = Math.max(...schedule.map(s => s.end));
for (let t = 0; t <= totalT; t++) {
for (const s of schedule) {
const el = document.getElementById('cell-' + s.addr);
if (!el) continue;
if (t >= s.start && t < s.end) el.className = 'bank-cell active';
else if (t >= s.end) el.className = 'bank-cell done';
}
await delay(STEP_MS);
}
}
/* Update comparison bars */
function updateBars(tSingle, tInterleaved) {
const maxT = Math.max(tSingle, 1);
document.getElementById('barSingle').style.width = '100%';
document.getElementById('barInterleaved').style.width = (tInterleaved / maxT * 100) + '%';
document.getElementById('timeSingle').textContent = i18nFmt('%n% cy', { n: tSingle });
document.getElementById('timeInterleaved').textContent = i18nFmt('%n% cy', { n: tInterleaved });
}
/* Main run handler */
async function run() {
const k = parseInt(document.getElementById('bankCount').value, 10);
const lat = parseInt(document.getElementById('latency').value, 10);
document.getElementById('runBtn').disabled = true;
document.getElementById('status').textContent = 'Simulating burst…';
renderBanks(k);
await new Promise(r => setTimeout(r, 80));
const tSingle = singleBankTime(WORDS, lat);
const tInterleaved = interleavedTime(WORDS, k, lat);
updateBars(tSingle, tInterleaved);
await animateBurst(k, lat);
const speedup = (tSingle / tInterleaved).toFixed(2);
document.getElementById('status').textContent =
i18nFmt('%k% banks: %tI% cycles vs %tS% (×%sp% speedup)', { k, tI: tInterleaved, tS: tSingle, sp: speedup });
document.getElementById('runBtn').disabled = false;
}
document.getElementById('runBtn').addEventListener('click', run);
// show 4-bank layout on load
renderBanks(4);
With a single bank every request must wait for the previous one to finish, so total time grows linearly. With k banks the requests pipeline: the burst completes in roughly latency+(n−1)⋅⌈latency/k⌉ cycles instead of n⋅latency. Add more banks and watch the bar for the interleaved case shrink toward the minimum.
Comments
Loading comments...