Skip to content
LAM
Read Home Blog
Make Projects HTML Tools Games
Touch grass Notes Resume Links
Home Blog HTML Projects
Tools Games Notes Resume Links
Back PLPTools MIPS Visualizer Computing
Download Open
Show description 1,617 chars · Computing

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

44,852 bytes · HTML source
<!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>&#9671; 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>