<!-- Demo: virtual-to-physical address translation with a page table and TLB -->
<div class="container">
<div class="top-row">
<div class="input-group">
<label for="vaddr">Virtual address (0–63):</label>
<input id="vaddr" type="number" min="0" max="63" value="42" />
<button id="btnTranslate" type="button">Translate</button>
<button id="btnReset" type="button" class="ghost">Reset TLB</button>
</div>
<div class="legend">
<span class="badge badge-vpn">VPN (page number)</span>
<span class="badge badge-off">Offset</span>
<span class="badge badge-pfn">PFN (frame number)</span>
</div>
</div>
<div class="panels">
<div class="panel" id="panelAddr">
<div class="panel-title">Virtual Address Bits</div>
<div id="addrViz" class="addr-viz"></div>
</div>
<div class="panel" id="panelTLB">
<div class="panel-title">TLB (2 entries)</div>
<table id="tlbTable" class="mem-table">
<thead><tr><th>VPN</th><th>PFN</th><th>Valid?</th></tr></thead>
<tbody></tbody>
</table>
</div>
<div class="panel" id="panelPT">
<div class="panel-title">Page Table</div>
<table id="ptTable" class="mem-table">
<thead><tr><th>VPN</th><th>PFN</th><th>Present?</th></tr></thead>
<tbody></tbody>
</table>
</div>
</div>
<div id="log" class="log"></div>
<div id="result" class="result"></div>
</div>
/* Styles for the virtual memory paging demo */
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: system-ui, sans-serif; font-size: 14px; color: #222; }
.container { padding: .6rem .8rem; }
.top-row { display: flex; flex-wrap: wrap; gap: .6rem; align-items: center; margin-bottom: .7rem; }
.input-group { display: flex; flex-wrap: wrap; gap: .4rem; align-items: center; }
label { font-weight: 600; }
input[type=number] { width: 64px; padding: .3rem .4rem; border: 1px solid #adb1b8; border-radius: 6px; font-size: 14px; }
button { font: 600 13px system-ui; padding: .35rem .75rem; border: 1px solid #1d3557; background: #1d3557; color: #fff; border-radius: 6px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.legend { display: flex; gap: .4rem; flex-wrap: wrap; align-items: center; }
.badge { display: inline-block; padding: .15rem .5rem; border-radius: 4px; font: 600 12px ui-monospace, monospace; }
.badge-vpn { background: #d4eaff; color: #1d3557; }
.badge-off { background: #fff3cc; color: #7a5700; }
.badge-pfn { background: #d6f5e3; color: #0a5c2a; }
.panels { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: .6rem; margin-bottom: .6rem; }
@media (max-width: 560px) { .panels { grid-template-columns: 1fr; } }
.panel { border: 1px solid #cdd9e3; border-radius: 8px; overflow: hidden; }
.panel-title { background: #e8eef3; padding: .3rem .5rem; font-weight: 700; font-size: 12px; letter-spacing: .03em; }
.mem-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.mem-table th { background: #f0f4f8; padding: .25rem .4rem; text-align: center; font-weight: 700; }
.mem-table td { padding: .25rem .4rem; text-align: center; border-top: 1px solid #e5eaee; font-family: ui-monospace, monospace; }
.mem-table tr.hit td { background: #d6f5e3; }
.mem-table tr.miss td { background: #fde8e8; }
.mem-table tr.fault td { background: #fff3cc; }
.addr-viz { display: flex; gap: 2px; justify-content: center; align-items: center; padding: .5rem .4rem; flex-wrap: wrap; }
.bit { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 32px; border-radius: 4px; font: 700 13px ui-monospace, monospace; }
.bit-vpn { background: #d4eaff; color: #1d3557; }
.bit-off { background: #fff3cc; color: #7a5700; }
.bit-sep { width: 8px; }
.log { font-size: 13px; line-height: 1.7; min-height: 4em; padding: .3rem .1rem; }
.log .step { color: #444; }
.log .step.good { color: #0a7d33; font-weight: 600; }
.log .step.warn { color: #b45000; font-weight: 600; }
.log .step.err { color: #c92f3c; font-weight: 600; }
.result { font-weight: 700; font-size: 1.05rem; min-height: 1.5em; }
.result.ok { color: #0a7d33; }
.result.err { color: #c92f3c; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// System parameters
// Total virtual address bits = 6, Bits for page offset = 3, so page size = 8 bytes, 8 virtual pages
const ADDR_BITS = 6;
const PAGE_BITS = 3;
const PAGE_SIZE = 1 << PAGE_BITS; // 8 bytes per page
const NUM_VPAGES = 1 << (ADDR_BITS - PAGE_BITS); // 8 virtual pages total
// Static page table: 8 entries (one per virtual page)
// Each entry: { pfn: <frame number> | null, present: true/false }
const PAGE_TABLE = [
{ pfn: 2, present: true }, // VPN 0 -> frame 2 (present)
{ pfn: 5, present: true }, // VPN 1 -> frame 5 (present)
{ pfn: null, present: false },// VPN 2 -> not present (page fault)
{ pfn: 7, present: true }, // VPN 3 -> frame 7 (present)
{ pfn: 1, present: true }, // VPN 4 -> frame 1 (present)
{ pfn: null, present: false },// VPN 5 -> not present (page fault)
{ pfn: 4, present: true }, // VPN 6 -> frame 4 (present)
{ pfn: 3, present: true }, // VPN 7 -> frame 3 (present)
];
// TLB state: two entries, initially invalid
let tlb = [
{ vpn: null, pfn: null, valid: false },
{ vpn: null, pfn: null, valid: false },
];
let tlbNext = 0; // round-robin eviction index
function renderTLB(highlightRow) {
// Render the TLB table, highlighting the given row
const tbody = document.querySelector('#tlbTable tbody');
tbody.innerHTML = '';
tlb.forEach((e, i) => {
const tr = document.createElement('tr');
if (i === highlightRow && e.valid) tr.className = 'hit';
else if (i === highlightRow && !e.valid) tr.className = 'miss';
const vpnCell = e.valid ? e.vpn : '-';
const pfnCell = e.valid ? e.pfn : '-';
const valCell = e.valid ? 'Yes' : 'No';
tr.innerHTML = `<td>${vpnCell}</td><td>${pfnCell}</td><td>${valCell}</td>`;
tbody.appendChild(tr);
});
}
function renderPT(highlightVpn, fault) {
// Render the page table, highlighting the accessed VPN
const tbody = document.querySelector('#ptTable tbody');
tbody.innerHTML = '';
PAGE_TABLE.forEach((e, vpn) => {
const tr = document.createElement('tr');
if (vpn === highlightVpn) tr.className = fault ? 'fault' : 'hit';
const pfnCell = e.present ? e.pfn : '-';
const presCell = e.present ? 'Yes' : 'No';
tr.innerHTML = `<td>${vpn}</td><td>${pfnCell}</td><td>${presCell}</td>`;
tbody.appendChild(tr);
});
}
function renderAddr(vaddr, vpn, off) {
// Render the binary breakdown of the virtual address
const el = document.getElementById('addrViz');
el.innerHTML = '';
const bits = vaddr.toString(2).padStart(ADDR_BITS, '0').split('');
bits.forEach((b, i) => {
const span = document.createElement('span');
span.className = 'bit ' + (i < ADDR_BITS - PAGE_BITS ? 'bit-vpn' : 'bit-off');
span.textContent = b;
el.appendChild(span);
});
const sep = document.createElement('span');
sep.className = 'bit-sep';
sep.innerHTML = '';
// Colors: blue=VPN bits, yellow=offset bits
}
function addLog(msg, cls) {
const log = document.getElementById('log');
const div = document.createElement('div');
div.className = 'step' + (cls ? ' ' + cls : '');
div.innerHTML = msg;
log.appendChild(div);
}
function setResult(msg, ok) {
const el = document.getElementById('result');
el.textContent = msg;
el.className = 'result ' + (ok ? 'ok' : 'err');
}
function translate() {
const raw = parseInt(document.getElementById('vaddr').value, 10);
if (isNaN(raw) || raw < 0 || raw >= (1 << ADDR_BITS)) {
setResult(i18nFmt('Address must be between 0 and %max%.', { max: (1 << ADDR_BITS) - 1 }), false);
return;
}
const vpn = raw >> PAGE_BITS; // top (ADDR_BITS - PAGE_BITS) bits
const off = raw & (PAGE_SIZE - 1); // bottom PAGE_BITS bits
document.getElementById('log').innerHTML = '';
document.getElementById('result').textContent = '';
renderAddr(raw, vpn, off);
addLog(i18nFmt('Split <b>%vaddr%</b>: VPN = <b>%vpn%</b>, offset = <b>%off%</b>', { vaddr: raw, vpn: vpn, off: off }));
// Step 1: check TLB
const tlbIdx = tlb.findIndex(e => e.valid && e.vpn === vpn);
if (tlbIdx !== -1) {
// TLB hit: compute physical address directly
const pfn = tlb[tlbIdx].pfn;
const paddr = (pfn << PAGE_BITS) | off;
renderTLB(tlbIdx);
renderPT(-1, false);
addLog('TLB HIT — translation found in TLB.', 'good');
addLog(i18nFmt('Frame number from TLB: %pfn%', { pfn: pfn }));
addLog(i18nFmt('Physical address = %paddr%', { paddr: paddr }));
setResult(i18nFmt('VA %vaddr% -> PA %paddr%', { vaddr: raw, paddr: paddr }), true);
} else {
// TLB miss: must consult page table
addLog('TLB MISS — not in TLB, reading page table.', 'warn');
addLog('Looking up VPN in page table...');
renderTLB(-1);
const pte = PAGE_TABLE[vpn];
if (!pte.present) {
// Page not present: raise a page fault
renderPT(vpn, true);
addLog('PAGE FAULT — page not in RAM, OS must load it from disk.', 'err');
setResult(i18nFmt('Page fault on VPN %vpn%', { vpn: vpn }), false);
} else {
const pfn = pte.pfn;
const paddr = (pfn << PAGE_BITS) | off;
renderPT(vpn, false);
addLog(i18nFmt('Page table entry VPN %vpn% -> PFN %pfn% (present).', { vpn: vpn, pfn: pfn }));
// Update TLB with the new translation (round-robin)
tlb[tlbNext] = { vpn: vpn, pfn: pfn, valid: true };
tlbNext = (tlbNext + 1) % tlb.length;
renderTLB(-1);
addLog('TLB updated with this translation.');
addLog(i18nFmt('Physical address = %paddr%', { paddr: paddr }));
setResult(i18nFmt('VA %vaddr% -> PA %paddr%', { vaddr: raw, paddr: paddr }), true);
}
}
}
function reset() {
// Clear TLB and all displayed state
tlb = [
{ vpn: null, pfn: null, valid: false },
{ vpn: null, pfn: null, valid: false },
];
tlbNext = 0;
document.getElementById('log').innerHTML = '';
document.getElementById('result').textContent = '';
document.getElementById('addrViz').innerHTML = '';
renderTLB(-1);
renderPT(-1, false);
}
document.getElementById('btnTranslate').addEventListener('click', translate);
document.getElementById('btnReset').addEventListener('click', reset);
// Initial render
renderTLB(-1);
renderPT(-1, false);
<!-- Demo: virtual-to-physical address translation with a page table and TLB -->
<div class="container">
<div class="top-row">
<div class="input-group">
<label for="vaddr">Virtual address (0–63):</label>
<input id="vaddr" type="number" min="0" max="63" value="42" />
<button id="btnTranslate" type="button">Translate</button>
<button id="btnReset" type="button" class="ghost">Reset TLB</button>
</div>
<div class="legend">
<span class="badge badge-vpn">VPN (page number)</span>
<span class="badge badge-off">Offset</span>
<span class="badge badge-pfn">PFN (frame number)</span>
</div>
</div>
<div class="panels">
<div class="panel" id="panelAddr">
<div class="panel-title">Virtual Address Bits</div>
<div id="addrViz" class="addr-viz"></div>
</div>
<div class="panel" id="panelTLB">
<div class="panel-title">TLB (2 entries)</div>
<table id="tlbTable" class="mem-table">
<thead><tr><th>VPN</th><th>PFN</th><th>Valid?</th></tr></thead>
<tbody></tbody>
</table>
</div>
<div class="panel" id="panelPT">
<div class="panel-title">Page Table</div>
<table id="ptTable" class="mem-table">
<thead><tr><th>VPN</th><th>PFN</th><th>Present?</th></tr></thead>
<tbody></tbody>
</table>
</div>
</div>
<div id="log" class="log"></div>
<div id="result" class="result"></div>
</div>
/* Styles for the virtual memory paging demo */
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: system-ui, sans-serif; font-size: 14px; color: #222; }
.container { padding: .6rem .8rem; }
.top-row { display: flex; flex-wrap: wrap; gap: .6rem; align-items: center; margin-bottom: .7rem; }
.input-group { display: flex; flex-wrap: wrap; gap: .4rem; align-items: center; }
label { font-weight: 600; }
input[type=number] { width: 64px; padding: .3rem .4rem; border: 1px solid #adb1b8; border-radius: 6px; font-size: 14px; }
button { font: 600 13px system-ui; padding: .35rem .75rem; border: 1px solid #1d3557; background: #1d3557; color: #fff; border-radius: 6px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.legend { display: flex; gap: .4rem; flex-wrap: wrap; align-items: center; }
.badge { display: inline-block; padding: .15rem .5rem; border-radius: 4px; font: 600 12px ui-monospace, monospace; }
.badge-vpn { background: #d4eaff; color: #1d3557; }
.badge-off { background: #fff3cc; color: #7a5700; }
.badge-pfn { background: #d6f5e3; color: #0a5c2a; }
.panels { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: .6rem; margin-bottom: .6rem; }
@media (max-width: 560px) { .panels { grid-template-columns: 1fr; } }
.panel { border: 1px solid #cdd9e3; border-radius: 8px; overflow: hidden; }
.panel-title { background: #e8eef3; padding: .3rem .5rem; font-weight: 700; font-size: 12px; letter-spacing: .03em; }
.mem-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.mem-table th { background: #f0f4f8; padding: .25rem .4rem; text-align: center; font-weight: 700; }
.mem-table td { padding: .25rem .4rem; text-align: center; border-top: 1px solid #e5eaee; font-family: ui-monospace, monospace; }
.mem-table tr.hit td { background: #d6f5e3; }
.mem-table tr.miss td { background: #fde8e8; }
.mem-table tr.fault td { background: #fff3cc; }
.addr-viz { display: flex; gap: 2px; justify-content: center; align-items: center; padding: .5rem .4rem; flex-wrap: wrap; }
.bit { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 32px; border-radius: 4px; font: 700 13px ui-monospace, monospace; }
.bit-vpn { background: #d4eaff; color: #1d3557; }
.bit-off { background: #fff3cc; color: #7a5700; }
.bit-sep { width: 8px; }
.log { font-size: 13px; line-height: 1.7; min-height: 4em; padding: .3rem .1rem; }
.log .step { color: #444; }
.log .step.good { color: #0a7d33; font-weight: 600; }
.log .step.warn { color: #b45000; font-weight: 600; }
.log .step.err { color: #c92f3c; font-weight: 600; }
.result { font-weight: 700; font-size: 1.05rem; min-height: 1.5em; }
.result.ok { color: #0a7d33; }
.result.err { color: #c92f3c; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// System parameters
// Total virtual address bits = 6, Bits for page offset = 3, so page size = 8 bytes, 8 virtual pages
const ADDR_BITS = 6;
const PAGE_BITS = 3;
const PAGE_SIZE = 1 << PAGE_BITS; // 8 bytes per page
const NUM_VPAGES = 1 << (ADDR_BITS - PAGE_BITS); // 8 virtual pages total
// Static page table: 8 entries (one per virtual page)
// Each entry: { pfn: <frame number> | null, present: true/false }
const PAGE_TABLE = [
{ pfn: 2, present: true }, // VPN 0 -> frame 2 (present)
{ pfn: 5, present: true }, // VPN 1 -> frame 5 (present)
{ pfn: null, present: false },// VPN 2 -> not present (page fault)
{ pfn: 7, present: true }, // VPN 3 -> frame 7 (present)
{ pfn: 1, present: true }, // VPN 4 -> frame 1 (present)
{ pfn: null, present: false },// VPN 5 -> not present (page fault)
{ pfn: 4, present: true }, // VPN 6 -> frame 4 (present)
{ pfn: 3, present: true }, // VPN 7 -> frame 3 (present)
];
// TLB state: two entries, initially invalid
let tlb = [
{ vpn: null, pfn: null, valid: false },
{ vpn: null, pfn: null, valid: false },
];
let tlbNext = 0; // round-robin eviction index
function renderTLB(highlightRow) {
// Render the TLB table, highlighting the given row
const tbody = document.querySelector('#tlbTable tbody');
tbody.innerHTML = '';
tlb.forEach((e, i) => {
const tr = document.createElement('tr');
if (i === highlightRow && e.valid) tr.className = 'hit';
else if (i === highlightRow && !e.valid) tr.className = 'miss';
const vpnCell = e.valid ? e.vpn : '-';
const pfnCell = e.valid ? e.pfn : '-';
const valCell = e.valid ? 'Yes' : 'No';
tr.innerHTML = `<td>${vpnCell}</td><td>${pfnCell}</td><td>${valCell}</td>`;
tbody.appendChild(tr);
});
}
function renderPT(highlightVpn, fault) {
// Render the page table, highlighting the accessed VPN
const tbody = document.querySelector('#ptTable tbody');
tbody.innerHTML = '';
PAGE_TABLE.forEach((e, vpn) => {
const tr = document.createElement('tr');
if (vpn === highlightVpn) tr.className = fault ? 'fault' : 'hit';
const pfnCell = e.present ? e.pfn : '-';
const presCell = e.present ? 'Yes' : 'No';
tr.innerHTML = `<td>${vpn}</td><td>${pfnCell}</td><td>${presCell}</td>`;
tbody.appendChild(tr);
});
}
function renderAddr(vaddr, vpn, off) {
// Render the binary breakdown of the virtual address
const el = document.getElementById('addrViz');
el.innerHTML = '';
const bits = vaddr.toString(2).padStart(ADDR_BITS, '0').split('');
bits.forEach((b, i) => {
const span = document.createElement('span');
span.className = 'bit ' + (i < ADDR_BITS - PAGE_BITS ? 'bit-vpn' : 'bit-off');
span.textContent = b;
el.appendChild(span);
});
const sep = document.createElement('span');
sep.className = 'bit-sep';
sep.innerHTML = '';
// Colors: blue=VPN bits, yellow=offset bits
}
function addLog(msg, cls) {
const log = document.getElementById('log');
const div = document.createElement('div');
div.className = 'step' + (cls ? ' ' + cls : '');
div.innerHTML = msg;
log.appendChild(div);
}
function setResult(msg, ok) {
const el = document.getElementById('result');
el.textContent = msg;
el.className = 'result ' + (ok ? 'ok' : 'err');
}
function translate() {
const raw = parseInt(document.getElementById('vaddr').value, 10);
if (isNaN(raw) || raw < 0 || raw >= (1 << ADDR_BITS)) {
setResult(i18nFmt('Address must be between 0 and %max%.', { max: (1 << ADDR_BITS) - 1 }), false);
return;
}
const vpn = raw >> PAGE_BITS; // top (ADDR_BITS - PAGE_BITS) bits
const off = raw & (PAGE_SIZE - 1); // bottom PAGE_BITS bits
document.getElementById('log').innerHTML = '';
document.getElementById('result').textContent = '';
renderAddr(raw, vpn, off);
addLog(i18nFmt('Split <b>%vaddr%</b>: VPN = <b>%vpn%</b>, offset = <b>%off%</b>', { vaddr: raw, vpn: vpn, off: off }));
// Step 1: check TLB
const tlbIdx = tlb.findIndex(e => e.valid && e.vpn === vpn);
if (tlbIdx !== -1) {
// TLB hit: compute physical address directly
const pfn = tlb[tlbIdx].pfn;
const paddr = (pfn << PAGE_BITS) | off;
renderTLB(tlbIdx);
renderPT(-1, false);
addLog('TLB HIT — translation found in TLB.', 'good');
addLog(i18nFmt('Frame number from TLB: %pfn%', { pfn: pfn }));
addLog(i18nFmt('Physical address = %paddr%', { paddr: paddr }));
setResult(i18nFmt('VA %vaddr% -> PA %paddr%', { vaddr: raw, paddr: paddr }), true);
} else {
// TLB miss: must consult page table
addLog('TLB MISS — not in TLB, reading page table.', 'warn');
addLog('Looking up VPN in page table...');
renderTLB(-1);
const pte = PAGE_TABLE[vpn];
if (!pte.present) {
// Page not present: raise a page fault
renderPT(vpn, true);
addLog('PAGE FAULT — page not in RAM, OS must load it from disk.', 'err');
setResult(i18nFmt('Page fault on VPN %vpn%', { vpn: vpn }), false);
} else {
const pfn = pte.pfn;
const paddr = (pfn << PAGE_BITS) | off;
renderPT(vpn, false);
addLog(i18nFmt('Page table entry VPN %vpn% -> PFN %pfn% (present).', { vpn: vpn, pfn: pfn }));
// Update TLB with the new translation (round-robin)
tlb[tlbNext] = { vpn: vpn, pfn: pfn, valid: true };
tlbNext = (tlbNext + 1) % tlb.length;
renderTLB(-1);
addLog('TLB updated with this translation.');
addLog(i18nFmt('Physical address = %paddr%', { paddr: paddr }));
setResult(i18nFmt('VA %vaddr% -> PA %paddr%', { vaddr: raw, paddr: paddr }), true);
}
}
}
function reset() {
// Clear TLB and all displayed state
tlb = [
{ vpn: null, pfn: null, valid: false },
{ vpn: null, pfn: null, valid: false },
];
tlbNext = 0;
document.getElementById('log').innerHTML = '';
document.getElementById('result').textContent = '';
document.getElementById('addrViz').innerHTML = '';
renderTLB(-1);
renderPT(-1, false);
}
document.getElementById('btnTranslate').addEventListener('click', translate);
document.getElementById('btnReset').addEventListener('click', reset);
// Initial render
renderTLB(-1);
renderPT(-1, false);
Comments
Loading comments...