On Github mattgodbolt / bbc-micro-emulation
Matt Godbolt Hit Escape to see all slides, space for next Arrow keys also work
LDA / STA / LDX etc ; load A / store A / load X / etc TAX / TXA etc ; transfer A to X PHA / PLA ; push A / pull A CMP ; compare with A ADC / SBC ; add/subtract with carry CLC / SEC ; clear / set carry JMP ; jump BEQ / BNE / BCC / ; branch if equal / not equal / carry clear BCS / BMI etc ; / carry set / minus etc JSR / RTS ; jump to subroutine / return from subroutine
a9 20 LDA #32 ; A = 32 a5 70 LDA $70 ; A = readmem(0x70) ad 34 12 LDA $1234 ; A = readmem(0x1234) bd 34 12 LDA $1234, X ; A = readmem(0x1234 + X) b9 34 12 LDA $1234, Y ; A = readmem(0x1234 + Y) b1 70 LDA ($70), Y ; t1 = readmem(0x70) ; t2 = readmem(0x71) ; A = readmem((t1 | (t2<<8)) + Y) b5 70 LDA $70, X ; A = readmem(0x70 + X) a1 70 LDA ($70, X) ; t1 = readmem(0x70 + X) ; t2 = readmem(0x71 + X) ; A = readmem(t1 | (t2<<8))
.strlen ; 0x70/0x71 point to string. ; returns length in A (low) and X (high) a0 00 LDY #0 ; we're going to use Y as the loop counter. Start at 0 a2 00 LDX #0 ; initialize the high part of the length .lp b1 70 LDA ($70), Y ; read the byte pointed to by (0x70/0x71) + Y f0 09 BEQ end ; if it's zero, we're at the end of the string c8 INY ; otherwise increment the loop counter d0 f9 BNE lp ; if it didn't overflow past 0xff to 0, re-loop e6 71 INC $71 ; else, increment the high part of the address e8 INX ; and the X counter d0 f4 BNE lp ; and re-loop (NB falling off here would mean > 65536) .end 98 TYA ; put the loop counter into A (the low part of the length) 60 RTS ; and return
var a = 0, x = 0, y = 0, s = 0, p = {c:false, z:false /* ... */}; function readbyte(addr) { /* ... */ } var pc = readbyte(0xfffc) | (readbyte(0xfffd) << 8); while (true) { var opcode = readbyte(pc); pc++; switch (opcode) { case 0xa9: /*LDA #xx*/ a = readbyte(pc); pc++; break; case 0xa2: /*LDX #xx*/ x = readbyte(pc); pc++; break; case 0xa0: /*LDY #xx*/ y = readbyte(pc); pc++; break; case 0x98: /*TYA*/ a = y; break; case 0x69: /*ADC #xx*/ a += readbyte(pc); pc++; break; case 0xb1: /*LDA (xx),Y*/ zp = readbyte(pc); pc++; addr = readbyte(zp) | (readbyte(zp+1)>>8); a = readbyte(addr + y); break; case 0xe8: /*INX*/ x = x + 1; break; // and so on...
case 0x69: /*ADC #xx*/ a += readbyte(pc); pc++; if (p.c) a++; // previous carry p.c = a > 0xff; // new carry a &= 0xff; p.z = !!a; p.n = !!(a & 0x80); break; case 0xb1: /*LDA (xx),Y*/ zp = readbyte(pc); pc++; addr = readbyte(zp) | (readbyte(zp+1)>>8); a = readbyte(addr + y); p.z = !!a; p.n = !!(a & 0x80); break;
var ram = new Uint8Array(0x8000); var os = load("OS"); var romsel = 0; var roms = []; roms[15] = load("BASIC"); function readmem(addr) { if (addr < 0x8000) return ram[addr]; if (addr < 0xc000) return roms[romsel][addr - 0x8000]; if ((addr >= 0xfe00 && addr < 0xff00) || (addr >= 0xfc00 && addr < 0xfd00)) return readhw(addr); return os[addr - 0xc000]; } function writemem(addr, b) { if (addr < 0x8000) ram[addr] = b; if (addr >= 0xfe00 && addr < 0xff00) writehw(addr, b); // else does nothing - it's ROM }
function updateKeys() { if (freescanning) { for (i = 0; i < 10; ++i) { for (j = 1; j < 8; ++j) if (keys[i][j]) setca2(); } } else { for (j = 1; j < 8; ++j) if (keys[curCol[j]) setca2(); } }
function tickTimer1() { count--; if (count == -3 && !suppressed) { // Timer fired! timerFlags |= T1HIT; // mark we hit in timer flags // Send an IRQ if needed if (irqEnabled & timerFlags) cpu.interrupt(); // If we're in one-shot mode, prevent further IRQs if (!(configFlags & 0x40)) suppressed = true; } // Reload timer value if (count == 3) count += latch + 4; }
LDA #42 ; 2 cycles LDA &70 ; 3 cycles LDA &1234 ; 4 cycles LDA (&70), Y ; 5-6 cycles INC &1234, X ; 7 cycles
function runCpu(clocks) { while (clocks > 0) { var opcode = readbyte(pc); pc++; switch (opcode) { case 0xb1: /*LDA (xx),Y*/ zp = readbyte(pc); pc++; addr = readbyte(zp) | (readbyte(zp+1)>>8); a = readbyte(addr + y); p.z = !!a; p.n = !!(a & 0x80); clocks -= 5; break; // ...and other opcodes... } if (irqFlag && p.i == false) { pc = irqHandler; // ...and much more... } } }
var opcodes6502 = { 0x00: "BRK", 0x01: "ORA (,x)", 0x03: "SLO (,x)", 0x04: "NOP zp", 0x05: "ORA zp", // skipping a few... 0xFD: "SBC abs,x", 0xFE: "INC abs,x", 0xFF: "ISB abs,x", };
function getOp(op) { switch (op) { case "NOP": return { op: [] } case "LDA": return { op: ["cpu.a = REG", "cpu.setzn(REG);"], read: true } case "STA": return { op: ["REG = cpu.a"], write: true } case "LDX": return { op: ["cpu.x = REG", "cpu.setzn(REG);"], read: true } //... case "INC": return { op: ["REG = (REG + 1) & 0xff;", "cpu.setzn(REG);" ], read: true, write: true }; //... } }
function gen(op, addrMode) { var ig = InstructionGen(); var op = getOp(op); switch (addrMode) { case "abs": ig.tick(3); ig.append("var addr = cpu.getw();"); if (op.read) { ig.readOp("addr", "REG"); if (op.write) ig.writeOp("addr", "REG"); // spurious write } ig.append(op.op); if (op.write ig.writeOp("addr", "REG"); return ig.render(); //... } }
var REG = 0|0; var addr = cpu.getw(); cpu.polltimeAddr(4, addr); REG = cpu.readmem(addr); cpu.polltimeAddr(1, addr); cpu.checkInt(); cpu.writemem(addr, REG); REG = (REG + 1) & 0xff; cpu.setzn(REG); cpu.polltimeAddr(1, addr); cpu.writemem(addr, REG);
scrx += 8; var b = readbyte(addr++); var offset = scry * 1280 + scrx; for (var i = 0; i < 8; ++i) { fb32[offset + i] = convertPixel(b, i); } if (scrx >= curEndPos) { scrx = curStartPos; scry ++; if (scry >= numLines) { scry = 0; generateIrq(); } }
Also finally hacked Lunar Jetman