Show description
PLPTools MIPS Visualizer
PLPTools MIPS Visualizer
◇ PLPTools MIPS Visualizer
Load example…
LED + Switches
UART Hello
Palindrome Check
Fibonacci
Loop Counter
Assemble
Step ▶
Run ▶▶
Pause
Reset
Speed:
Hex
Assembly Code
# PLPTools MIPS Visualizer — Starter
# Try the examples in the dropdown above!
.org 0x10000000
main:
# Load a bit pattern
li $t0, 0b10101010 # pattern = 0xAA
# Write to LEDs (memory-mapped I/O)
lui $t1, 0xf010 # $t1 = 0xf0100000
sw $t0, 0($t1) # turn on LEDs!
# Read switches into $t2
lui $t2, 0xf020 # $t2 = 0xf0200000
lw $t3, 0($t2) # read switch state
# Echo switches to LEDs
sw $t3, 0($t1)
j main # loop forever
Ready — click Assemble to begin.
Pipeline Stages
IFFetch
→
IDDecode
→
EXExecute
→
WBWrite
CPU State
Program Counter (PC)
0x10000000
Instructions executed
0
—
Register File (32 × 32-bit)
Memory Access Log
No memory accesses yet.
Peripherals
LEDs — addr 0xf0100000
Value: 0x00 = 00000000b
Switches — addr 0xf0200000
Value: 0x00 = 00000000b
Seven-Segment — addr 0xf0400000
----
sw $t0, 0($t1) where $t1=0xf0400000
UART — status 0xf0300000 / data 0xf0300004
Send
lw reads queued input • sw prints output
Memory Map
0x10000000 — .text (program)
0x10008000 — .data
0xf0100000 — LEDs (write)
0xf0200000 — Switches (read)
0xf0300000 — UART status
0xf0300004 — UART data
0xf0400000 — 7-segment
Instruction Decode
—
R-Type Quick Ref
add $rd,$rs,$rt
sub $rd,$rs,$rt
and/or/nor/xor $rd,$rs,$rt
sll/srl/sra $rd,$rt,shamt
slt $rd,$rs,$rt
jr $rs
I/J-Type Quick Ref
addi/ori/andi $rt,$rs,imm
lui $rt,imm
lw/sw $rt,offset($rs)
beq/bne $rs,$rt,label
j/jal label
li $rt,imm (pseudo)
PLPTools MIPS Visualizer
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PLPTools MIPS Visualizer</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Courier New', Courier, monospace;
background: #0d1117;
color: #c9d1d9;
height: 100vh;
overflow: hidden;
display: flex;
flex-direction: column;
}
/* ── Header ── */
#header {
background: #161b22;
border-bottom: 1px solid #30363d;
padding: 6px 12px;
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
flex-wrap: wrap;
}
#header h1 { font-size: 13px; color: #58a6ff; margin-right: auto; letter-spacing: 0.03em; }
button {
background: #21262d;
color: #c9d1d9;
border: 1px solid #30363d;
padding: 4px 10px;
border-radius: 6px;
cursor: pointer;
font-family: inherit;
font-size: 12px;
transition: background 0.12s;
}
button:hover { background: #30363d; }
button.primary { background: #1f6feb; border-color: #388bfd; color: #fff; }
button.primary:hover { background: #388bfd; }
button.success { background: #196c2e; border-color: #2ea043; color: #fff; }
button.success:hover { background: #2ea043; }
button.danger { background: #4a1010; border-color: #f85149; color: #f85149; }
button.danger:hover { background: #f85149; color: #fff; }
button:disabled { opacity: 0.38; cursor: not-allowed; }
select {
background: #21262d;
color: #c9d1d9;
border: 1px solid #30363d;
padding: 4px 8px;
border-radius: 6px;
font-family: inherit;
font-size: 12px;
cursor: pointer;
}
input[type=range] {
-webkit-appearance: none;
width: 80px; height: 4px;
background: #30363d;
border-radius: 2px;
outline: none;
vertical-align: middle;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 12px; height: 12px;
background: #58a6ff;
border-radius: 50%;
cursor: pointer;
}
/* ── Workspace grid ── */
#workspace {
display: grid;
grid-template-columns: 310px 1fr 250px;
flex: 1;
overflow: hidden;
gap: 1px;
background: #21262d;
}
.panel {
background: #0d1117;
overflow-y: auto;
padding: 8px;
display: flex;
flex-direction: column;
gap: 8px;
}
.ptitle {
font-size: 10px;
color: #8b949e;
text-transform: uppercase;
letter-spacing: 0.06em;
border-bottom: 1px solid #21262d;
padding-bottom: 3px;
}
.slabel { font-size: 10px; color: #8b949e; text-transform: uppercase; letter-spacing: 0.05em; margin-top: 4px; }
/* ── Editor ── */
#code-editor {
width: 100%;
flex: 1;
min-height: 260px;
background: #161b22;
color: #c9d1d9;
border: 1px solid #30363d;
border-radius: 6px;
padding: 8px;
font-family: inherit;
font-size: 12px;
line-height: 1.6;
resize: none;
tab-size: 4;
outline: none;
}
#code-editor:focus { border-color: #58a6ff; }
#error-output {
background: #1a0808;
border: 1px solid #f85149;
border-radius: 6px;
padding: 6px 8px;
font-size: 11px;
color: #f85149;
display: none;
white-space: pre-wrap;
max-height: 80px;
overflow-y: auto;
}
#status-bar { font-size: 11px; color: #8b949e; padding: 2px 4px; }
/* ── Pipeline ── */
#pipeline {
display: flex;
gap: 3px;
align-items: center;
}
.pipe-stage {
flex: 1;
text-align: center;
padding: 4px 2px;
border-radius: 4px;
font-size: 10px;
background: #161b22;
border: 1px solid #30363d;
color: #8b949e;
transition: all 0.15s;
line-height: 1.3;
}
.pipe-stage.active { background: #1a2f4a; border-color: #58a6ff; color: #58a6ff; }
.pipe-arrow { color: #30363d; font-size: 10px; flex-shrink: 0; }
/* ── Registers ── */
#pc-box {
background: #161b22;
border: 1px solid #30363d;
border-radius: 6px;
padding: 6px 8px;
font-size: 12px;
}
.pc-row { display: flex; justify-content: space-between; margin-bottom: 2px; }
.pc-lbl { color: #8b949e; font-size: 11px; }
.pc-val { color: #f0c040; font-size: 11px; }
#current-instr { color: #7ee787; font-size: 11px; margin-top: 3px; font-style: italic; min-height: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#registers {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1px;
font-size: 11px;
}
.reg-row {
display: flex;
gap: 3px;
padding: 2px 3px;
border-radius: 3px;
align-items: center;
transition: background 0.15s;
}
.reg-row.changed { animation: regflash 0.5s ease; }
@keyframes regflash { 0% { background: #2ea043; } 100% { background: transparent; } }
.reg-num { color: #484f58; width: 20px; flex-shrink: 0; font-size: 10px; }
.reg-name { color: #58a6ff; width: 30px; flex-shrink: 0; font-size: 10px; }
.reg-val { color: #e6edf3; flex: 1; text-align: right; font-size: 10px; font-variant-numeric: tabular-nums; }
.reg-val.nz { color: #f0c040; }
/* ── Memory view ── */
#memory-view {
background: #161b22;
border: 1px solid #30363d;
border-radius: 6px;
padding: 6px;
font-size: 10px;
max-height: 130px;
overflow-y: auto;
}
.mem-row { display: flex; gap: 6px; padding: 1px 0; }
.mem-addr { color: #58a6ff; width: 90px; flex-shrink: 0; }
.mem-val { color: #e6edf3; width: 80px; }
.mem-ascii { color: #8b949e; }
.mem-row.hi { background: #1c2d3a; border-radius: 2px; }
/* ── LEDs ── */
#led-wrap {
background: #161b22;
border: 1px solid #30363d;
border-radius: 6px;
padding: 8px;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
}
#led-row { display: flex; gap: 6px; }
.led-unit { display: flex; flex-direction: column; align-items: center; gap: 2px; }
.led {
width: 22px; height: 22px;
border-radius: 50%;
background: #1c1c1c;
border: 2px solid #2a2a2a;
transition: all 0.08s;
}
.led.on {
background: #ff4400;
border-color: #ff7700;
box-shadow: 0 0 8px 2px rgba(255, 100, 0, 0.7);
}
.led-bit-lbl { font-size: 9px; color: #484f58; }
#led-val { font-size: 10px; color: #8b949e; }
/* ── Switches ── */
#sw-wrap {
background: #161b22;
border: 1px solid #30363d;
border-radius: 6px;
padding: 8px;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
}
#switch-row { display: flex; gap: 6px; }
.sw-unit { display: flex; flex-direction: column; align-items: center; gap: 2px; }
.sw-btn {
width: 22px; height: 34px;
border-radius: 4px;
background: #21262d;
border: 2px solid #30363d;
cursor: pointer;
font-size: 10px;
color: #8b949e;
padding: 0;
transition: all 0.1s;
display: flex; align-items: center; justify-content: center;
}
.sw-btn.on {
background: #1f3a6e;
border-color: #58a6ff;
color: #58a6ff;
box-shadow: 0 0 5px rgba(88,166,255,0.4);
}
.sw-bit-lbl { font-size: 9px; color: #484f58; }
#sw-val { font-size: 10px; color: #8b949e; }
/* ── Seven-segment ── */
#sseg-wrap {
background: #080808;
border: 1px solid #30363d;
border-radius: 6px;
padding: 6px 8px;
text-align: center;
}
#sseg-display {
font-size: 28px;
font-weight: bold;
color: #ff3300;
text-shadow: 0 0 8px #ff3300;
letter-spacing: 4px;
line-height: 1;
}
#sseg-addr { font-size: 9px; color: #484f58; margin-top: 2px; }
/* ── UART ── */
#uart-term {
background: #010a12;
border: 1px solid #30363d;
border-radius: 6px;
padding: 6px;
font-size: 11px;
height: 100px;
overflow-y: auto;
color: #39d353;
white-space: pre-wrap;
word-break: break-all;
}
#uart-row { display: flex; gap: 4px; }
#uart-input {
flex: 1;
background: #161b22;
border: 1px solid #30363d;
border-radius: 4px;
padding: 3px 7px;
color: #c9d1d9;
font-family: inherit;
font-size: 11px;
outline: none;
}
#uart-input:focus { border-color: #58a6ff; }
/* ── Bottom bar ── */
#bottom {
background: #161b22;
border-top: 1px solid #30363d;
padding: 6px 12px;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
gap: 12px;
flex-shrink: 0;
max-height: 140px;
overflow: hidden;
}
.bsect { display: flex; flex-direction: column; gap: 3px; }
.bsect .ptitle { margin-bottom: 2px; }
.bref { font-size: 10px; color: #8b949e; line-height: 1.7; }
.bref b { color: #c9d1d9; }
#decode-panel { font-size: 11px; color: #8b949e; white-space: pre; line-height: 1.6; }
</style>
</head>
<body>
<!-- ════════════════ HEADER ════════════════ -->
<div id="header">
<h1>◇ PLPTools MIPS Visualizer</h1>
<select id="example-select">
<option value="">Load example…</option>
<option value="led_switches">LED + Switches</option>
<option value="uart_hello">UART Hello</option>
<option value="palindrome">Palindrome Check</option>
<option value="fibonacci">Fibonacci</option>
<option value="loop_counter">Loop Counter</option>
</select>
<button id="btn-assemble" class="primary">Assemble</button>
<button id="btn-step" disabled>Step ▶</button>
<button id="btn-run" class="success" disabled>Run ▶▶</button>
<button id="btn-pause" disabled>Pause</button>
<button id="btn-reset" class="danger" disabled>Reset</button>
<span style="font-size:11px;color:#8b949e;">Speed:</span>
<input type="range" id="speed-slider" min="1" max="10" value="5">
<label style="font-size:11px;color:#8b949e;">
<input type="checkbox" id="hex-mode" checked> Hex
</label>
</div>
<!-- ════════════════ WORKSPACE ════════════════ -->
<div id="workspace">
<!-- LEFT: Editor -->
<div class="panel" id="editor-panel">
<div class="ptitle">Assembly Code</div>
<textarea id="code-editor" spellcheck="false"># PLPTools MIPS Visualizer — Starter
# Try the examples in the dropdown above!
.org 0x10000000
main:
# Load a bit pattern
li $t0, 0b10101010 # pattern = 0xAA
# Write to LEDs (memory-mapped I/O)
lui $t1, 0xf010 # $t1 = 0xf0100000
sw $t0, 0($t1) # turn on LEDs!
# Read switches into $t2
lui $t2, 0xf020 # $t2 = 0xf0200000
lw $t3, 0($t2) # read switch state
# Echo switches to LEDs
sw $t3, 0($t1)
j main # loop forever
</textarea>
<div id="error-output"></div>
<div id="status-bar">Ready — click Assemble to begin.</div>
<div class="ptitle">Pipeline Stages</div>
<div id="pipeline">
<div class="pipe-stage" id="stage-if">IF<br>Fetch</div>
<div class="pipe-arrow">→</div>
<div class="pipe-stage" id="stage-id">ID<br>Decode</div>
<div class="pipe-arrow">→</div>
<div class="pipe-stage" id="stage-ex">EX<br>Execute</div>
<div class="pipe-arrow">→</div>
<div class="pipe-stage" id="stage-wb">WB<br>Write</div>
</div>
</div>
<!-- CENTER: CPU state -->
<div class="panel" id="cpu-panel">
<div class="ptitle">CPU State</div>
<div id="pc-box">
<div class="pc-row">
<span class="pc-lbl">Program Counter (PC)</span>
<span class="pc-val" id="pc-val">0x10000000</span>
</div>
<div class="pc-row">
<span class="pc-lbl">Instructions executed</span>
<span class="pc-val" id="instr-count">0</span>
</div>
<div id="current-instr">—</div>
</div>
<div class="ptitle">Register File (32 × 32-bit)</div>
<div id="registers"></div>
<div class="ptitle">Memory Access Log</div>
<div id="memory-view"><div style="color:#484f58;font-size:10px;padding:4px;">No memory accesses yet.</div></div>
</div>
<!-- RIGHT: Peripherals -->
<div class="panel" id="periph-panel">
<div class="ptitle">Peripherals</div>
<div class="slabel">LEDs — addr 0xf0100000</div>
<div id="led-wrap">
<div id="led-row"></div>
<div id="led-val">Value: 0x00 = 00000000b</div>
</div>
<div class="slabel">Switches — addr 0xf0200000</div>
<div id="sw-wrap">
<div id="switch-row"></div>
<div id="sw-val">Value: 0x00 = 00000000b</div>
</div>
<div class="slabel">Seven-Segment — addr 0xf0400000</div>
<div id="sseg-wrap">
<div id="sseg-display">----</div>
<div id="sseg-addr">sw $t0, 0($t1) where $t1=0xf0400000</div>
</div>
<div class="slabel">UART — status 0xf0300000 / data 0xf0300004</div>
<div id="uart-term"></div>
<div id="uart-row">
<input id="uart-input" type="text" placeholder="Type to queue UART bytes…" maxlength="80">
<button id="btn-uart-send">Send</button>
</div>
<div style="font-size:9px;color:#484f58;">lw reads queued input • sw prints output</div>
</div>
</div>
<!-- ════════════════ BOTTOM BAR ════════════════ -->
<div id="bottom">
<div class="bsect">
<div class="ptitle">Memory Map</div>
<div class="bref">
<b>0x10000000</b> — .text (program)<br>
<b>0x10008000</b> — .data<br>
<b>0xf0100000</b> — LEDs (write)<br>
<b>0xf0200000</b> — Switches (read)<br>
<b>0xf0300000</b> — UART status<br>
<b>0xf0300004</b> — UART data<br>
<b>0xf0400000</b> — 7-segment
</div>
</div>
<div class="bsect">
<div class="ptitle">Instruction Decode</div>
<div id="decode-panel">—</div>
</div>
<div class="bsect">
<div class="ptitle">R-Type Quick Ref</div>
<div class="bref">
<b>add</b> $rd,$rs,$rt<br>
<b>sub</b> $rd,$rs,$rt<br>
<b>and/or/nor/xor</b> $rd,$rs,$rt<br>
<b>sll/srl/sra</b> $rd,$rt,shamt<br>
<b>slt</b> $rd,$rs,$rt<br>
<b>jr</b> $rs
</div>
</div>
<div class="bsect">
<div class="ptitle">I/J-Type Quick Ref</div>
<div class="bref">
<b>addi/ori/andi</b> $rt,$rs,imm<br>
<b>lui</b> $rt,imm<br>
<b>lw/sw</b> $rt,offset($rs)<br>
<b>beq/bne</b> $rs,$rt,label<br>
<b>j/jal</b> label<br>
<b>li</b> $rt,imm (pseudo)
</div>
</div>
</div>
<script>
// ================================================================
// Constants & CPU state
// ================================================================
const REG_NAMES = [
'$zero','$at','$v0','$v1','$a0','$a1','$a2','$a3',
'$t0','$t1','$t2','$t3','$t4','$t5','$t6','$t7',
'$s0','$s1','$s2','$s3','$s4','$s5','$s6','$s7',
'$t8','$t9','$k0','$k1','$gp','$sp','$fp','$ra'
];
const REG_IDX = {};
REG_NAMES.forEach((n,i) => { REG_IDX[n] = i; });
for (let i = 0; i < 32; i++) REG_IDX['$' + i] = i;
const MMIO_LED = 0xf0100000;
const MMIO_SW = 0xf0200000;
const MMIO_USTAT = 0xf0300000;
const MMIO_UDATA = 0xf0300004;
const MMIO_SSEG = 0xf0400000;
let cpu = null;
let uartRxBuf = [];
let uartLog = '';
let switchVal = 0;
let ledVal = 0;
let ssegVal = 0;
let prevRegs = new Array(32).fill(0);
let hiReg = 0, loReg = 0;
let assembled = false;
let runTimer = null;
function mkCPU() {
return {
regs: new Array(32).fill(0),
pc: 0x10000000,
memory: new Map(),
instrMem: [], // decoded instruction objects in order
addrMap: new Map(), // addr -> index in instrMem
labels: new Map(),
instrCount: 0,
halted: false,
};
}
cpu = mkCPU();
// ================================================================
// Assembler helpers
// ================================================================
function parseImm(s) {
s = s.trim();
if (s.startsWith("'") && s.length >= 3) return s.charCodeAt(1);
if (/^0[xX]/.test(s)) return parseInt(s, 16) | 0;
if (/^0[bB]/.test(s)) return parseInt(s.slice(2), 2) | 0;
const n = parseInt(s, 10);
if (isNaN(n)) throw new Error('Bad immediate: ' + s);
return n;
}
function parseReg(s) {
s = s.trim().replace(/,/g, '');
if (!(s in REG_IDX)) throw new Error('Unknown register: ' + s);
return REG_IDX[s];
}
function parseMem(s) {
const m = s.match(/^(-?(?:0[xXbB][\da-fA-F]+|\d+)?)\((\$\w+)\)$/);
if (!m) throw new Error('Bad memory operand: ' + s);
return { offset: m[1] === '' ? 0 : parseImm(m[1]), reg: parseReg(m[2]) };
}
function sx16(v) { v &= 0xffff; return v & 0x8000 ? v - 0x10000 : v; }
// ================================================================
// Two-pass assembler
// ================================================================
function assemble(src) {
const lines = src.split('\n');
const labels = new Map();
const instrs = [];
let addr = 0x10000000;
const errors = [];
// PASS 1 — collect labels, estimate addresses
for (let li = 0; li < lines.length; li++) {
let ln = lines[li].replace(/#.*/,'').trim();
if (!ln) continue;
if (/^\.(org|text|data|word|byte|space|ascii|asciiz)/i.test(ln)) {
if (/^\.org/i.test(ln)) addr = parseImm(ln.split(/\s+/)[1]);
continue;
}
const lm = ln.match(/^(\w+):\s*(.*)/);
if (lm) { labels.set(lm[1], addr); ln = lm[2].trim(); if (!ln) continue; }
const parts = ln.split(/[\s,]+/).filter(Boolean);
const op = parts[0].toLowerCase();
const slots = pseudoSlots(op);
addr += slots * 4;
}
// PASS 2 — encode instructions
addr = 0x10000000;
for (let li = 0; li < lines.length; li++) {
let ln = lines[li].replace(/#.*/,'').trim();
if (!ln) continue;
if (/^\.(org)/i.test(ln)) { addr = parseImm(ln.split(/\s+/)[1]); continue; }
if (/^\.(text|data|word|byte|space|ascii|asciiz)/i.test(ln)) continue;
const lm = ln.match(/^(\w+):\s*(.*)/);
if (lm) { ln = lm[2].trim(); if (!ln) continue; }
const parts = ln.split(/[\s,]+/).filter(Boolean);
const op = parts[0].toLowerCase();
const args = parts.slice(1);
try {
const expanded = expandOp(op, args, addr, labels, ln);
for (const instr of expanded) {
instr.srcLine = li;
instr.srcText = ln;
instr.addr = addr;
instrs.push(instr);
addr += 4;
}
} catch (e) {
errors.push(`Line ${li+1}: ${e.message}`);
addr += pseudoSlots(op) * 4;
}
}
if (errors.length) throw new Error(errors.join('\n'));
return { instrs, labels };
}
function pseudoSlots(op) {
return { li:2, la:2, blt:2, bgt:2, ble:2, bge:2 }[op] || 1;
}
function expandOp(op, args, addr, labels, src) {
const R3 = () => ({ rd: parseReg(args[0]), rs: parseReg(args[1]), rt: parseReg(args[2]) });
const Shift = () => ({ rd: parseReg(args[0]), rt: parseReg(args[1]), shamt: parseImm(args[2]) & 0x1f });
const I3 = () => ({ rt: parseReg(args[0]), rs: parseReg(args[1]), imm: parseImm(args[2]) });
const Mem = () => { const m = parseMem(args[1]); return { rt: parseReg(args[0]), rs: m.reg, imm: m.offset }; };
const Br = () => {
const rs = parseReg(args[0]), rt = parseReg(args[1]);
const tgt = args[2];
const taddr = labels.has(tgt) ? labels.get(tgt) : parseImm(tgt);
return { rs, rt, imm: ((taddr - (addr + 4)) >> 2) & 0xffff };
};
const J = () => {
const tgt = args[0];
return { target: labels.has(tgt) ? labels.get(tgt) : parseImm(tgt) };
};
switch (op) {
// Pseudos
case 'nop': return [{ op:'sll', rd:0, rs:0, rt:0, shamt:0 }];
case 'move': return [{ op:'add', rd:parseReg(args[0]), rs:parseReg(args[1]), rt:0 }];
case 'li': {
const rt = parseReg(args[0]);
const v = parseImm(args[1]) >>> 0;
const hi = (v >>> 16) & 0xffff, lo = v & 0xffff;
if (hi === 0) return [
{ op:'ori', rt, rs:0, imm:lo },
{ op:'sll', rd:0, rs:0, rt:0, shamt:0 } // padding slot
];
return [
{ op:'lui', rt, imm:hi },
lo ? { op:'ori', rt, rs:rt, imm:lo } : { op:'sll', rd:0, rs:0, rt:0, shamt:0 }
];
}
case 'la': {
const rt = parseReg(args[0]);
const tgt = args[1];
const v = (labels.has(tgt) ? labels.get(tgt) : parseImm(tgt)) >>> 0;
return [
{ op:'lui', rt, imm:(v>>>16)&0xffff },
{ op:'ori', rt, rs:rt, imm:v&0xffff }
];
}
case 'blt': {
const rs=parseReg(args[0]),rt=parseReg(args[1]),tgt=args[2];
const ta=labels.has(tgt)?labels.get(tgt):parseImm(tgt);
const off=((ta-(addr+8))>>2)&0xffff;
return [{ op:'slt',rd:1,rs,rt },{ op:'bne',rs:1,rt:0,imm:off }];
}
case 'bgt': {
const rs=parseReg(args[0]),rt=parseReg(args[1]),tgt=args[2];
const ta=labels.has(tgt)?labels.get(tgt):parseImm(tgt);
const off=((ta-(addr+8))>>2)&0xffff;
return [{ op:'slt',rd:1,rs:rt,rt:rs },{ op:'bne',rs:1,rt:0,imm:off }];
}
case 'ble': {
const rs=parseReg(args[0]),rt=parseReg(args[1]),tgt=args[2];
const ta=labels.has(tgt)?labels.get(tgt):parseImm(tgt);
const off=((ta-(addr+8))>>2)&0xffff;
return [{ op:'slt',rd:1,rs:rt,rt:rs },{ op:'beq',rs:1,rt:0,imm:off }];
}
case 'bge': {
const rs=parseReg(args[0]),rt=parseReg(args[1]),tgt=args[2];
const ta=labels.has(tgt)?labels.get(tgt):parseImm(tgt);
const off=((ta-(addr+8))>>2)&0xffff;
return [{ op:'slt',rd:1,rs,rt },{ op:'beq',rs:1,rt:0,imm:off }];
}
// R-type
case 'add': case 'addu': case 'sub': case 'subu':
case 'and': case 'or': case 'nor': case 'xor':
case 'slt': case 'sltu': return [{ op, ...R3() }];
case 'sll': case 'srl': case 'sra': return [{ op, ...Shift() }];
case 'sllv':case 'srlv':case 'srav':
return [{ op, rd:parseReg(args[0]), rt:parseReg(args[1]), rs:parseReg(args[2]) }];
case 'jr': return [{ op, rs:parseReg(args[0]) }];
case 'jalr': return [{ op, rd:parseReg(args[0]), rs:parseReg(args[1]) }];
case 'mult': case 'multu': case 'div': case 'divu':
return [{ op, rs:parseReg(args[0]), rt:parseReg(args[1]) }];
case 'mfhi': return [{ op, rd:parseReg(args[0]) }];
case 'mflo': return [{ op, rd:parseReg(args[0]) }];
// I-type
case 'addi': case 'addiu': case 'slti': case 'sltiu': return [{ op, ...I3() }];
case 'andi': case 'ori': case 'xori':
return [{ op, rt:parseReg(args[0]), rs:parseReg(args[1]), imm:parseImm(args[2])&0xffff }];
case 'lui': return [{ op, rt:parseReg(args[0]), imm:parseImm(args[1])&0xffff }];
case 'lw': case 'lh': case 'lb': case 'lhu': case 'lbu':
case 'sw': case 'sh': case 'sb': return [{ op, ...Mem() }];
case 'beq': case 'bne': return [{ op, ...Br() }];
// J-type
case 'j': return [{ op, ...J() }];
case 'jal': return [{ op, ...J() }];
case 'syscall': return [{ op:'syscall' }];
case 'break': return [{ op:'break' }];
default: throw new Error('Unknown op: ' + op);
}
}
// ================================================================
// Memory-mapped I/O
// ================================================================
function memRead(a) {
a = a >>> 0;
if (a === MMIO_SW) return switchVal;
if (a === MMIO_USTAT) return uartRxBuf.length > 0 ? 0x3 : 0x1;
if (a === MMIO_UDATA) return uartRxBuf.length > 0 ? uartRxBuf.shift() : 0;
if (a === MMIO_LED) return ledVal;
if (a === MMIO_SSEG) return ssegVal;
return cpu.memory.get(a) || 0;
}
function memWrite(a, v) {
a = a >>> 0; v = v | 0;
cpu.memory.set(a, v);
if (a === MMIO_LED) { ledVal = v & 0xff; uiLEDs(); return; }
if (a === MMIO_UDATA){ uiUART(String.fromCharCode(v & 0xff)); return; }
if (a === MMIO_SSEG) { ssegVal = v; uiSSEG(v); return; }
uiMemRow(a, v);
}
// ================================================================
// Execute one instruction
// ================================================================
function execOne() {
if (cpu.halted) return false;
const idx = cpu.addrMap.get(cpu.pc >>> 0);
if (idx === undefined) {
setStatus('Halted: PC 0x' + (cpu.pc>>>0).toString(16).padStart(8,'0') + ' not in program memory.', true);
cpu.halted = true;
return false;
}
const ins = cpu.instrMem[idx];
prevRegs = cpu.regs.slice();
animPipeline();
showDecode(ins);
const pc = cpu.pc;
cpu.pc += 4;
cpu.instrCount++;
const R = cpu.regs;
switch (ins.op) {
case 'add': case 'addu': R[ins.rd] = (R[ins.rs] + R[ins.rt]) | 0; break;
case 'sub': case 'subu': R[ins.rd] = (R[ins.rs] - R[ins.rt]) | 0; break;
case 'and': R[ins.rd] = R[ins.rs] & R[ins.rt]; break;
case 'or': R[ins.rd] = R[ins.rs] | R[ins.rt]; break;
case 'nor': R[ins.rd] = ~(R[ins.rs] | R[ins.rt]); break;
case 'xor': R[ins.rd] = R[ins.rs] ^ R[ins.rt]; break;
case 'slt': R[ins.rd] = R[ins.rs] < R[ins.rt] ? 1 : 0; break;
case 'sltu': R[ins.rd] = (R[ins.rs]>>>0) < (R[ins.rt]>>>0) ? 1 : 0; break;
case 'sll': R[ins.rd] = R[ins.rt] << ins.shamt; break;
case 'srl': R[ins.rd] = R[ins.rt] >>> ins.shamt; break;
case 'sra': R[ins.rd] = R[ins.rt] >> ins.shamt; break;
case 'sllv': R[ins.rd] = R[ins.rt] << (R[ins.rs] & 0x1f); break;
case 'srlv': R[ins.rd] = R[ins.rt] >>> (R[ins.rs] & 0x1f); break;
case 'srav': R[ins.rd] = R[ins.rt] >> (R[ins.rs] & 0x1f); break;
case 'jr': cpu.pc = R[ins.rs] >>> 0; break;
case 'jalr': R[ins.rd] = pc + 4; cpu.pc = R[ins.rs] >>> 0; break;
case 'mult': {
const r = BigInt(R[ins.rs]) * BigInt(R[ins.rt]);
loReg = Number(BigInt.asIntN(32, r & 0xffffffffn));
hiReg = Number(BigInt.asIntN(32, r >> 32n)); break;
}
case 'multu': {
const r = BigInt(R[ins.rs]>>>0) * BigInt(R[ins.rt]>>>0);
loReg = Number(r & 0xffffffffn);
hiReg = Number(r >> 32n); break;
}
case 'div':
if (R[ins.rt] !== 0) {
loReg = (R[ins.rs] / R[ins.rt]) | 0;
hiReg = R[ins.rs] % R[ins.rt];
} break;
case 'mfhi': R[ins.rd] = hiReg; break;
case 'mflo': R[ins.rd] = loReg; break;
case 'addi': case 'addiu': R[ins.rt] = (R[ins.rs] + sx16(ins.imm)) | 0; break;
case 'andi': R[ins.rt] = R[ins.rs] & (ins.imm & 0xffff); break;
case 'ori': R[ins.rt] = R[ins.rs] | (ins.imm & 0xffff); break;
case 'xori': R[ins.rt] = R[ins.rs] ^ (ins.imm & 0xffff); break;
case 'slti': R[ins.rt] = R[ins.rs] < sx16(ins.imm) ? 1 : 0; break;
case 'sltiu':R[ins.rt] = (R[ins.rs]>>>0) < (sx16(ins.imm)>>>0) ? 1 : 0; break;
case 'lui': R[ins.rt] = (ins.imm << 16) | 0; break;
case 'lw': { const ea=(R[ins.rs]+sx16(ins.imm))>>>0; R[ins.rt]=memRead(ea); break; }
case 'lh': {
const ea=(R[ins.rs]+sx16(ins.imm))>>>0, w=memRead(ea&~3), sh=(ea&2)*8;
let h=(w>>>sh)&0xffff; if(h&0x8000) h-=0x10000; R[ins.rt]=h; break;
}
case 'lhu': {
const ea=(R[ins.rs]+sx16(ins.imm))>>>0, w=memRead(ea&~3), sh=(ea&2)*8;
R[ins.rt]=(w>>>sh)&0xffff; break;
}
case 'lb': {
const ea=(R[ins.rs]+sx16(ins.imm))>>>0, w=memRead(ea&~3), sh=(3-(ea&3))*8;
let b=(w>>>sh)&0xff; if(b&0x80) b-=0x100; R[ins.rt]=b; break;
}
case 'lbu': {
const ea=(R[ins.rs]+sx16(ins.imm))>>>0, w=memRead(ea&~3), sh=(3-(ea&3))*8;
R[ins.rt]=(w>>>sh)&0xff; break;
}
case 'sw': { const ea=(R[ins.rs]+sx16(ins.imm))>>>0; memWrite(ea, R[ins.rt]); break; }
case 'sh': {
const ea=(R[ins.rs]+sx16(ins.imm))>>>0, wa=ea&~3, sh=(ea&2)*8;
memWrite(wa, (memRead(wa)&~(0xffff<<sh))|((R[ins.rt]&0xffff)<<sh)); break;
}
case 'sb': {
const ea=(R[ins.rs]+sx16(ins.imm))>>>0, wa=ea&~3, sh=(3-(ea&3))*8;
memWrite(wa, (memRead(wa)&~(0xff<<sh))|((R[ins.rt]&0xff)<<sh)); break;
}
case 'beq': if(R[ins.rs]===R[ins.rt]) cpu.pc=(pc+4+(sx16(ins.imm)<<2))>>>0; break;
case 'bne': if(R[ins.rs]!==R[ins.rt]) cpu.pc=(pc+4+(sx16(ins.imm)<<2))>>>0; break;
case 'j': cpu.pc=((pc&0xf0000000)|(ins.target&0x0fffffff))>>>0; break;
case 'jal': R[31]=(pc+4)|0; cpu.pc=((pc&0xf0000000)|(ins.target&0x0fffffff))>>>0; break;
case 'syscall': doSyscall(); break;
case 'break':
cpu.halted = true;
setStatus('BREAK — program halted.');
break;
default:
cpu.halted = true;
setStatus('Unknown op: ' + ins.op, true);
}
R[0] = 0; // $zero always 0
uiRegisters();
uiPC();
return !cpu.halted;
}
function doSyscall() {
switch (cpu.regs[2]) {
case 1: uiUART(String(cpu.regs[4])); break;
case 11: uiUART(String.fromCharCode(cpu.regs[4] & 0xff)); break;
case 10: cpu.halted = true; setStatus('Program exited (syscall 10).'); stopRun(); break;
case 12: cpu.regs[2] = uartRxBuf.length ? uartRxBuf.shift() : 0; break;
}
}
// ================================================================
// UI builders
// ================================================================
function buildRegisters() {
const c = document.getElementById('registers');
c.innerHTML = '';
for (let i = 0; i < 32; i++) {
const r = document.createElement('div');
r.className = 'reg-row';
r.id = 'rr' + i;
r.innerHTML = `<span class="reg-num">$${i}</span><span class="reg-name">${REG_NAMES[i]}</span><span class="reg-val" id="rv${i}">0x00000000</span>`;
c.appendChild(r);
}
}
function buildLEDs() {
const row = document.getElementById('led-row');
row.innerHTML = '';
for (let i = 7; i >= 0; i--) {
const u = document.createElement('div');
u.className = 'led-unit';
u.innerHTML = `<div class="led" id="led${i}"></div><div class="led-bit-lbl">${i}</div>`;
row.appendChild(u);
}
}
function buildSwitches() {
const row = document.getElementById('switch-row');
row.innerHTML = '';
for (let i = 7; i >= 0; i--) {
const u = document.createElement('div');
u.className = 'sw-unit';
const btn = document.createElement('button');
btn.className = 'sw-btn';
btn.id = 'sw' + i;
btn.textContent = '0';
btn.addEventListener('click', () => toggleSwitch(i));
u.appendChild(btn);
u.innerHTML += `<div class="sw-bit-lbl">${i}</div>`;
u.insertBefore(btn, u.firstChild);
row.appendChild(u);
}
}
function toggleSwitch(bit) {
switchVal ^= (1 << bit);
const on = (switchVal >> bit) & 1;
const btn = document.getElementById('sw' + bit);
btn.className = 'sw-btn' + (on ? ' on' : '');
btn.textContent = on ? '1' : '0';
document.getElementById('sw-val').textContent =
'Value: 0x' + (switchVal&0xff).toString(16).padStart(2,'0') +
' = ' + (switchVal&0xff).toString(2).padStart(8,'0') + 'b';
}
// ================================================================
// UI updaters
// ================================================================
const hexMode = () => document.getElementById('hex-mode').checked;
function fmt(v) {
if (hexMode()) return '0x' + (v>>>0).toString(16).padStart(8,'0');
return String(v | 0);
}
function uiRegisters() {
for (let i = 0; i < 32; i++) {
const el = document.getElementById('rv' + i);
if (!el) continue;
el.textContent = fmt(cpu.regs[i]);
el.className = 'reg-val' + (cpu.regs[i] !== 0 ? ' nz' : '');
if (cpu.regs[i] !== prevRegs[i]) {
const row = document.getElementById('rr' + i);
row.classList.remove('changed');
void row.offsetWidth;
row.classList.add('changed');
}
}
}
function uiPC() {
document.getElementById('pc-val').textContent = '0x' + (cpu.pc>>>0).toString(16).padStart(8,'0');
document.getElementById('instr-count').textContent = cpu.instrCount;
const idx = cpu.addrMap.get(cpu.pc >>> 0);
document.getElementById('current-instr').textContent =
idx !== undefined ? '→ ' + cpu.instrMem[idx].srcText : '—';
}
function uiLEDs() {
for (let i = 0; i < 8; i++) {
const el = document.getElementById('led' + i);
if (el) el.className = 'led' + ((ledVal >> i) & 1 ? ' on' : '');
}
document.getElementById('led-val').textContent =
'Value: 0x' + (ledVal&0xff).toString(16).padStart(2,'0') +
' = ' + (ledVal&0xff).toString(2).padStart(8,'0') + 'b';
}
function uiSSEG(v) {
const hex = (v>>>0).toString(16).toUpperCase().padStart(4,'0').slice(-4);
document.getElementById('sseg-display').textContent = hex;
}
function uiUART(ch) {
uartLog += ch;
const term = document.getElementById('uart-term');
term.textContent = uartLog.slice(-3000);
term.scrollTop = term.scrollHeight;
}
let memViewRows = new Map();
function uiMemRow(addr, val) {
const view = document.getElementById('memory-view');
const key = addr.toString(16);
let row = memViewRows.get(key);
if (!row) {
// Remove placeholder
const ph = view.querySelector('[data-ph]');
if (ph) ph.remove();
row = document.createElement('div');
row.className = 'mem-row';
memViewRows.set(key, row);
view.appendChild(row);
if (memViewRows.size > 24) {
const first = memViewRows.keys().next().value;
const old = memViewRows.get(first);
memViewRows.delete(first);
old.remove();
}
}
const ascii = (val >= 32 && val < 127) ? String.fromCharCode(val) : '.';
row.innerHTML =
`<span class="mem-addr">0x${addr.toString(16).padStart(8,'0')}</span>` +
`<span class="mem-val">0x${(val>>>0).toString(16).padStart(8,'0')}</span>` +
`<span class="mem-ascii">'${ascii}'</span>`;
row.className = 'mem-row hi';
setTimeout(() => { if (row) row.className = 'mem-row'; }, 400);
}
function showDecode(ins) {
const rn = i => `${REG_NAMES[i]}($${i})`;
let info = '';
switch (ins.op) {
case 'add': case 'addu': case 'sub': case 'subu':
case 'and': case 'or': case 'nor': case 'xor':
case 'slt': case 'sltu':
info = `R-type op=${ins.op}\n ${rn(ins.rd)} ← ${rn(ins.rs)} ${ins.op} ${rn(ins.rt)}`; break;
case 'sll': case 'srl': case 'sra':
info = `Shift op=${ins.op}\n ${rn(ins.rd)} ← ${rn(ins.rt)} ${ins.op} ${ins.shamt}`; break;
case 'jr':
info = `Jump Register\n PC ← ${rn(ins.rs)}`; break;
case 'lui':
info = `Load Upper Imm\n ${rn(ins.rt)} ← 0x${(ins.imm&0xffff).toString(16).padStart(4,'0')}0000`; break;
case 'addi': case 'addiu':
info = `I-type op=${ins.op}\n ${rn(ins.rt)} ← ${rn(ins.rs)} + ${ins.imm}`; break;
case 'ori': case 'andi': case 'xori':
info = `I-type op=${ins.op}\n ${rn(ins.rt)} ← ${rn(ins.rs)} op 0x${(ins.imm&0xffff).toString(16)}`; break;
case 'lw':
info = `Load Word\n ${rn(ins.rt)} ← Mem[${rn(ins.rs)} + ${ins.imm}]\n addr = 0x${((cpu.regs[ins.rs]+sx16(ins.imm))>>>0).toString(16)}`; break;
case 'sw':
info = `Store Word\n Mem[${rn(ins.rs)} + ${ins.imm}] ← ${rn(ins.rt)}\n addr = 0x${((cpu.regs[ins.rs]+sx16(ins.imm))>>>0).toString(16)}`; break;
case 'beq': info = `Branch if Equal\n if ${rn(ins.rs)} == ${rn(ins.rt)}`; break;
case 'bne': info = `Branch if Not Equal\n if ${rn(ins.rs)} != ${rn(ins.rt)}`; break;
case 'j': info = `Jump\n PC ← 0x${(ins.target>>>0).toString(16)}`; break;
case 'jal': info = `Jump and Link\n $ra ← PC+4\n PC ← 0x${(ins.target>>>0).toString(16)}`; break;
case 'break':info = 'BREAK — halt'; break;
default: info = ins.op;
}
document.getElementById('decode-panel').textContent = info;
}
function animPipeline() {
const ids = ['if','id','ex','wb'];
ids.forEach(id => document.getElementById('stage-'+id).classList.remove('active'));
ids.forEach((id,i) => {
setTimeout(()=> document.getElementById('stage-'+id).classList.add('active'), i*55);
setTimeout(()=> document.getElementById('stage-'+id).classList.remove('active'), (i+1)*55+80);
});
}
function setStatus(msg, err) {
const el = document.getElementById('status-bar');
el.textContent = msg;
el.style.color = err ? '#f85149' : '#8b949e';
}
// ================================================================
// Controls
// ================================================================
function doAssemble() {
const src = document.getElementById('code-editor').value;
document.getElementById('error-output').style.display = 'none';
try {
const { instrs, labels } = assemble(src);
cpu = mkCPU();
cpu.regs[28] = 0x10008000; // $gp
cpu.regs[29] = 0x10007ffc; // $sp
cpu.instrMem = instrs;
cpu.labels = labels;
instrs.forEach((ins, idx) => cpu.addrMap.set(ins.addr >>> 0, idx));
if (instrs.length) cpu.pc = instrs[0].addr;
ledVal = 0; ssegVal = 0; switchVal = 0; uartRxBuf = []; uartLog = '';
uiLEDs(); uiSSEG(0);
document.getElementById('uart-term').textContent = '';
document.getElementById('memory-view').innerHTML = '<div data-ph style="color:#484f58;font-size:10px;padding:4px;">No memory accesses yet.</div>';
memViewRows.clear();
prevRegs = new Array(32).fill(0);
hiReg = 0; loReg = 0;
assembled = true;
uiRegisters(); uiPC();
setBtn(true);
setStatus(`Assembled OK — ${instrs.length} instruction slots, entry 0x${instrs[0]?.addr.toString(16)}`);
} catch (e) {
const eo = document.getElementById('error-output');
eo.textContent = e.message;
eo.style.display = 'block';
setStatus('Assembly failed.', true);
assembled = false;
setBtn(false);
}
}
function setBtn(ok) {
document.getElementById('btn-step').disabled = !ok;
document.getElementById('btn-run').disabled = !ok;
document.getElementById('btn-reset').disabled = !ok;
document.getElementById('btn-pause').disabled = true;
}
function doStep() {
if (!assembled || cpu.halted) return;
const ok = execOne();
if (!ok || cpu.halted) {
document.getElementById('btn-step').disabled = true;
document.getElementById('btn-run').disabled = true;
}
}
function doRun() {
if (!assembled || cpu.halted) return;
document.getElementById('btn-run').disabled = true;
document.getElementById('btn-pause').disabled = false;
document.getElementById('btn-step').disabled = true;
const speed = parseInt(document.getElementById('speed-slider').value);
const delay = Math.max(8, 550 - speed * 50);
const ticks = speed >= 8 ? 30 : speed >= 5 ? 8 : 2;
let guard = 50000;
runTimer = setInterval(() => {
let ok = true;
for (let i = 0; i < ticks && ok; i++) { ok = execOne(); guard--; }
if (!ok || guard <= 0) {
stopRun();
if (guard <= 0) setStatus('Paused — 50k instruction limit. Use Step or Run again.');
}
}, delay);
}
function stopRun() {
if (runTimer) { clearInterval(runTimer); runTimer = null; }
document.getElementById('btn-run').disabled = !assembled || cpu.halted;
document.getElementById('btn-step').disabled = !assembled || cpu.halted;
document.getElementById('btn-pause').disabled = true;
}
function doReset() {
stopRun();
cpu = mkCPU(); assembled = false;
ledVal = 0; ssegVal = 0; switchVal = 0; uartRxBuf = []; uartLog = '';
prevRegs = new Array(32).fill(0); hiReg = 0; loReg = 0;
uiLEDs(); uiSSEG(0);
// reset switch buttons
for (let i = 0; i < 8; i++) {
const b = document.getElementById('sw'+i);
if (b) { b.className = 'sw-btn'; b.textContent = '0'; }
}
document.getElementById('sw-val').textContent = 'Value: 0x00 = 00000000b';
document.getElementById('uart-term').textContent = '';
document.getElementById('memory-view').innerHTML = '<div data-ph style="color:#484f58;font-size:10px;padding:4px;">No memory accesses yet.</div>';
memViewRows.clear();
document.getElementById('decode-panel').textContent = '—';
document.getElementById('current-instr').textContent = '—';
document.getElementById('error-output').style.display = 'none';
uiRegisters(); uiPC();
setBtn(false);
setStatus('Reset — click Assemble to begin.');
}
// ================================================================
// Example programs
// ================================================================
const EXAMPLES = {
led_switches: `# LED + Switches Demo
# Toggle the switches (right panel) to control the LEDs!
# This shows memory-mapped I/O — writing to an address
# controls real hardware.
.org 0x10000000
main:
lui $t1, 0xf010 # $t1 = 0xf0100000 (LED register)
lui $t2, 0xf020 # $t2 = 0xf0200000 (Switch register)
loop:
lw $t0, 0($t2) # read switches
sw $t0, 0($t1) # echo to LEDs
j loop # repeat forever`,
uart_hello: `# UART Hello World
# Writes characters to the UART by storing ASCII codes
# to address 0xf0300004 one byte at a time.
.org 0x10000000
main:
lui $t1, 0xf030 # $t1 base = 0xf0300000
li $t0, 72 # 'H'
sw $t0, 4($t1)
li $t0, 101 # 'e'
sw $t0, 4($t1)
li $t0, 108 # 'l'
sw $t0, 4($t1)
sw $t0, 4($t1) # 'l'
li $t0, 111 # 'o'
sw $t0, 4($t1)
li $t0, 44 # ','
sw $t0, 4($t1)
li $t0, 32 # ' '
sw $t0, 4($t1)
li $t0, 87 # 'W'
sw $t0, 4($t1)
li $t0, 111 # 'o'
sw $t0, 4($t1)
li $t0, 114 # 'r'
sw $t0, 4($t1)
li $t0, 108 # 'l'
sw $t0, 4($t1)
li $t0, 100 # 'd'
sw $t0, 4($t1)
li $t0, 33 # '!'
sw $t0, 4($t1)
li $t0, 10 # newline
sw $t0, 4($t1)
break`,
palindrome: `# Palindrome Checker
# Stores "racecar" in memory, then checks it.
# $s0 = 1 if palindrome, 0 if not.
# Result shown on LEDs!
.org 0x10000000
main:
# Build string in RAM at 0x10008000
lui $t0, 0x1000
ori $t0, $t0, 0x8000 # $t0 = data base
li $t1, 114 ; sw $t1, 0($t0) # 'r'
li $t1, 97 ; sw $t1, 4($t0) # 'a'
li $t1, 99 ; sw $t1, 8($t0) # 'c'
li $t1, 101 ; sw $t1, 12($t0) # 'e'
li $t1, 99 ; sw $t1, 16($t0) # 'c'
li $t1, 97 ; sw $t1, 20($t0) # 'a'
li $t1, 114 ; sw $t1, 24($t0) # 'r'
li $t1, 0 ; sw $t1, 28($t0) # null
# Get string length
move $a0, $t0
jal strlen # $v0 = length in words
sll $t2, $v0, 2
add $a1, $t0, $t2
addi $a1, $a1, -4 # $a1 = last char addr
move $a0, $t0
jal is_palindrome # $v0 = 1 if yes
move $s0, $v0
# Show result on LEDs
lui $t3, 0xf010
sw $s0, 0($t3)
break
# strlen($a0) -> $v0 = word count until 0
strlen:
li $v0, 0
slen_lp:
lw $t9, 0($a0)
beq $t9, $zero, slen_done
addi $v0, $v0, 1
addi $a0, $a0, 4
j slen_lp
slen_done:
jr $ra
# is_palindrome($a0=left, $a1=right) -> $v0
is_palindrome:
li $v0, 1
pal_lp:
bge $a0, $a1, pal_done
lw $t7, 0($a0)
lw $t8, 0($a1)
bne $t7, $t8, pal_fail
addi $a0, $a0, 4
addi $a1, $a1, -4
j pal_lp
pal_fail:
li $v0, 0
pal_done:
jr $ra`,
fibonacci: `# Fibonacci Sequence
# Computes fib(0)..fib(9) and stores in memory.
# Each result shown on LEDs as it's computed.
.org 0x10000000
main:
lui $s0, 0x1000
ori $s0, $s0, 0x8000 # data array base
li $t0, 0 # fib(n-2)
li $t1, 1 # fib(n-1)
li $s1, 0 # index
li $s2, 10 # count
lui $s3, 0xf010 # LED addr
fib_loop:
beq $s1, $s2, fib_done
# store fib(n) = $t0
sll $t3, $s1, 2
add $t3, $s0, $t3
sw $t0, 0($t3)
# show on LEDs
sw $t0, 0($s3)
# advance
add $t2, $t0, $t1
move $t0, $t1
move $t1, $t2
addi $s1, $s1, 1
j fib_loop
fib_done:
break`,
loop_counter: `# Loop Counter
# Counts 0 to 255 and shows each value on LEDs.
# Great intro to addi, beq, and j.
.org 0x10000000
main:
lui $t1, 0xf010 # LED address
li $t0, 0 # counter
li $t2, 256 # stop at 256
count_loop:
beq $t0, $t2, done
sw $t0, 0($t1) # show on LEDs
addi $t0, $t0, 1
j count_loop
done:
li $t0, 0xff
sw $t0, 0($t1) # all LEDs on
break`,
};
document.getElementById('example-select').addEventListener('change', function () {
if (this.value && EXAMPLES[this.value]) {
document.getElementById('code-editor').value = EXAMPLES[this.value];
doReset();
document.getElementById('error-output').style.display = 'none';
setStatus('Example loaded — click Assemble.');
this.value = '';
}
});
document.getElementById('btn-assemble').addEventListener('click', doAssemble);
document.getElementById('btn-step').addEventListener('click', doStep);
document.getElementById('btn-run').addEventListener('click', doRun);
document.getElementById('btn-pause').addEventListener('click', stopRun);
document.getElementById('btn-reset').addEventListener('click', doReset);
document.getElementById('hex-mode').addEventListener('change', uiRegisters);
document.getElementById('btn-uart-send').addEventListener('click', () => {
const inp = document.getElementById('uart-input');
const txt = inp.value.trim();
if (!txt) return;
for (const ch of txt) uartRxBuf.push(ch.charCodeAt(0));
uartRxBuf.push(10); // newline
uiUART('> ' + txt + '\n');
inp.value = '';
});
document.getElementById('uart-input').addEventListener('keydown', e => {
if (e.key === 'Enter') document.getElementById('btn-uart-send').click();
});
// ================================================================
// Init
// ================================================================
buildRegisters();
buildLEDs();
buildSwitches();
uiRegisters();
uiPC();
</script>
</body>
</html>