From 01195c203a151fb276a1838a4fd8abbc53f1c49d Mon Sep 17 00:00:00 2001 From: Jason Meiring Date: Mon, 5 Jan 2015 21:07:43 -0800 Subject: [PATCH] Adding JavaScript files for 65C02 emulator with monitor and disassembler, and the start of a memory and I/o module for the Apple //c --- apple_iic_memory.js | 299 ++++++++++++++ cpu.js | 939 ++++++++++++++++++++++++++++++++++++++++++++ disassembler.js | 359 +++++++++++++++++ index.html | 23 ++ main.js | 130 ++++++ memory.js | 55 +++ monitor.js | 28 ++ resources.js | 65 +++ 8 files changed, 1898 insertions(+) create mode 100644 apple_iic_memory.js create mode 100644 cpu.js create mode 100644 disassembler.js create mode 100644 index.html create mode 100644 main.js create mode 100644 memory.js create mode 100644 monitor.js create mode 100644 resources.js diff --git a/apple_iic_memory.js b/apple_iic_memory.js new file mode 100644 index 0000000..4132d99 --- /dev/null +++ b/apple_iic_memory.js @@ -0,0 +1,299 @@ +function AppleIIcMemory() { + this.bank0 = new Array(65536); + this.bank1 = new Array(65536); + + this.rom = new Array(12288); + + this.lastRead = 0x0000; // used to detect write protection disable + // (requires two subsequent reads at the same address) + + this.hardReset(); // set the default states +} + +AppleIIcMemory.prototype.hardReset = function() { + // Soft switches - start in default state + this.altzp = this.bank0; // altzp = 0; use main bank + this.enlcram = false; // read rom + + this.offset = 0x1000; // offset (for upper bank switched ROM/RAM) + // offset for bank 1 = 0x0000, offset for bank2 = 0x1000 + + this.nowrite = false; // write upper 16Kram + this.ramrd = this.bank0; // read from main 48K bank + this.ramwrt = this.bank0; // write to main 48K bank + this.store80 = false; // RAMRD/RAMWRT controls lower 48K + this.page2 = this.bank0; // text/graphics on main bank (if store80 enabled) + this.hires = false; // text display mode + + this.altchar = false; // display primary character set + this.col80 = false; // 40 character text + this.textmode = true; // display text + this.mixed = false; // display only text + + this.ioudis = true; // disable access to IOU (mouse hardware) + this.dhires = false; // double high resolution off + +}; + +AppleIIcMemory.prototype.setData = function(addr, data) { + if (addr < 0x0200) { + // Switched zero page and stack: controlled by ALTZP + this.altzp[addr] = data; + } else if (addr < 0xC000) { + // 48K bank switched RAM: controlled by 80STORE & RAMWRT + var ram = this.ramwrt; + if (this.store80) { + // 80STORE is enabled: this affects the text page separately + if (addr >= 0x0300 && addr < 0x0700) { + ram = this.page2; + } else if (this.hires && addr >= 0x2000 && add < 0x3F00) { + ram = this.page2; + } + } + ram[addr] = data; + } else if (addr < 0xC100) { + // Hardware page + switch(addr) { + case 0xC000: // action:W - 80STORE Off: RAMRD and RAMWRT determine RAM locations + this.store80 = false; + break; + case 0xC001: // action:W - 80STORE On: PAGE2 switches between TLP1 and TLP1X + this.store80 = true; + break; + case 0xC002: // action:W - RAMRD Off: read main 48K bank + this.ramrd = this.bank0; + break; + case 0xC003: // action:W - RAMRD On: read auxiliary 48K bank + this.ramrd = this.bank1; + break; + case 0xC004: // action:W - RAMWRT Off: write to main 48K bank + this.ramwrt = this.bank0; + break; + case 0xC005: // action:W - RAMWRT On: write to auxiliary 48K bank + this.ramwrt = this.bank1; + break; + case 0xC008: // action:W - ALTZP Off: use main 16K bank, page 0 and page 1 + this.altzp = this.bank0; + break; + case 0xC00D: // action:W - 80COL Off: display 40 columns + this.col80 = false; + break; + case 0xC00E: // action:W - 80COL On: display 80 columns + this.col80 = true; + break; + case 0xC00E: // action:W - ALTCHAR Off: display text using primary character set + this.altchar = false; + break; + case 0xC00E: // action:W - ALTCHAR On: display text using alternate character set + this.altchar = true; + break; + case 0xC010: // action:W - ALTZP On: use auxiliary 16K bank, page 0 and page 1 + this.altzp = this.bank1; + break; + case 0xC050: // action:RW - TXTCLR, display graphics + this.textmode = false; + break; + case 0xC051: // action:RW - TXTSET, display text + this.textmode = true; + break; + case 0xC052: // action:RW - MIXCLR, display full screen + this.mixed = false; + break; + case 0xC053: // action:RW - MIXSET, display split screen + this.mixed = true; + break; + case 0xC054: // action:RW - PAGE2 Off: Select TLP1 and HRP1 + this.page2 = this.bank0; + break; + case 0xC055: // action:RW - PAGE2 On: Select TLP2 and HRP2 (or TLP1X and HRP1X if 80STORE on) + this.page2 = this.bank1; + break; + case 0xC056: // action:RW - HIRES Off: Display text and lo resolution page + this.hires = false; + break; + case 0xC057: // action:RW - HIRES On: Display high resolution pages, switch with PAGE2 + this.hires = true; + break; + case 0xC05E: // action:RW - DHIRES On: Turn on double high resolution + if (this.iodis) this.dhires = true; + break; + case 0xC05F: // action:RW - DHIRES Off: Turn off double high resolution + if (this.ioudis) this.dhires = false; + break; + case 0xC078: + case 0xC07E: // action:W - IOUDIS On: enable access to DIHRES switch; disable IOU access to $C058-$C05F + this.ioudis = true; + break; + case 0xC079: + case 0xC07F: // action:W - IOUDIS Off: disable access to DIHRES switch; enable IOU access to $C058-$C05F + this.ioudis = false; + break; + default: + } + } else if (addr < 0xD000) { + // I/O Firmware + } else { + // 12K ROM & 16K bank switched RAM + if (! this.nowrite) { + // only take action if write protection is disabled + this.altzp[addr-this.offset] = data; + } + } +}; + +// Return an 8-bit chuck of data at a given address +AppleIIcMemory.prototype.getData = function(addr) { + var data, ram; + if (addr < 0x0200) { + // Switched zero page and stack: controlled by ALTZP + data = this.altzp[addr]; + } else if (addr < 0xC000) { + // 48K bank switched RAM: controlled by RAMRD + ram = this.ramrd; + if (this.store80) { + // 80STORE is enabled: this affects the text page separately + if (addr >= 0x0300 && addr < 0x0700) { + ram = this.page2; + } else if (this.hires && addr >= 0x2000 && add < 0x3F00) { + ram = this.page2; + } + } + data = ram[addr]; + } else if (addr < 0xC100) { + // Hardware page + switch(addr) { + case 0xC011: // action:R7 - RDBNK2, read whether $D000 bank 2 (1) or bank 1 (0) + data = (this.offset === 0x1000) << 7; + break; + case 0xC012: // action:R - RDLCRAM, reading RAM (1) or ROM (0) + data = this.enlcram << 7; + break; + case 0xC013: // action:R7 - RDRAMRD, reading auxiliary (1) or main (0) 48K bank + data = (this.ramrd === this.bank1) << 7; + break; + case 0xC014: // action:R7 - RDRAMWRT, writing auxiliary (1) or main (0) 48K bank + data = (this.ramwrt === this.bank1) << 7; + break; + case 0xC017: // action:R7 - RDALTZP, read whether auxiliary (1) or main (0) bank + data = (this.altzp === this.bank1) << 7; + break; + case 0xC018: // action:R7 - RD80STORE, read whether 80STORE is on (1) or off (0); + data = this.store80 << 7; + break; + case 0xC01A: // action:R7 - RDTEXT, read whether TEXT is on (1) or off (0); + data = this.textmode << 7; + break; + case 0xC01B: // action:R7 - RDMIXED, read whether MIXED is on (1) or off (0); + data = this.mixed << 7; + break; + case 0xC01C: // action:R7 - RDPAGE2, read whether PAGE2 is on (1) or off (0); + data = (this.page2 === this.bank1) << 7; + break; + case 0xC01D: // action:R7 - RDHIRES, read whether HIRES is on (1) or off (0); + data = this.hires << 7; + break; + case 0xC01E: // action:R7 - RDALTCHAR, read whether ALTCHAR is on (1) of off (0) + data = this.altchar << 7; + break; + case 0xC01F: // action:R7 - RD80COL, read whether 80COL is on (1) or off (0) + data = this.col80 << 7; + break; + case 0xC050: // action:RW - TXTCLR, display graphics + this.textmode = false; + break; + case 0xC051: // action:RW - TXTSET, display text + this.textmode = true; + break; + case 0xC052: // action:RW - MIXCLR, display full screen + this.mixed = false; + break; + case 0xC053: // action:RW - MIXSET, display split screen + this.mixed = true; + break; + case 0xC054: // action:RW - PAGE2 Off: Select TLP1 and HRP1 + this.page2 = this.bank0; + break; + case 0xC055: // action:RW - PAGE2 On: Select TLP2 and HRP2 (or TLP1X and HRP1X if 80STORE on) + this.page2 = this.bank1; + break; + case 0xC056: // action:RW - HIRES Off: Display text and lo resolution page + this.hires = false; + break; + case 0xC057: // action:RW - HIRES On: Display high resolution pages, switch with PAGE2 + this.hires = true; + break; + case 0xC05E: // action:RW - DHIRES On: Turn on double high resolution + if (this.ioudis) this.dhires = true; + break; + case 0xC05F: // action:RW - DHIRES Off: Turn off double high resolution + if (this.ioudis) this.dhires = false; + break; + case 0xC07E: // action:R7 - RIOUDIS, read whether IOUDIS is on (0) or off (1) + data = this.ioudis << 7; + break; + case 0xC07F: // action:R7 - RDDHIRES, read whether DHIRES is on (1) or off (0) + data = this.dhires << 7; + break; + case 0xC080: // action:R - read RAM, no write; use $D000 bank 2 + this.enlcram = true; + this.offset = 0x1000; + this.nowrite = true; + break; + case 0xC081: // action:RR - read ROM, write RAM; use $D000 bank 2 + this.enlcram = false; + this.offset = 0x1000; + this.nowrite = (this.lastRead !== 0xC081); + break; + case 0xC082: // action:R - read ROM, no write; use $D000 bank 2 + this.enlcram = false; + this.offset = 0x1000; + this.nowrite = true; + break; + case 0xC083: // action:RR - read and write RAM; use $D000 bank 2 + this.enlcram = true; + this.offset = 0x1000; + this.nowrite = (this.lastRead !== 0xC083); + break; + case 0xC088: // action:R - read RAM, no write; use $D000 bank 1 + this.enlcram = true; + this.offset = 0x0000; + this.nowrite = true; + break; + case 0xC089: // action:RR - read ROM, write RAM; use $D000 bank 1 + this.enlcram = false; + this.offset = 0x0000; + this.nowrite = (this.lastRead !== 0xC089); + break; + case 0xC08A: // action:R - read ROM, no write; use $D000 bank 1 + this.enlcram = false; + this.offset = 0x0000; + this.nowrite = true; + break; + case 0xC08B: // action:RR - read and write RAM; use $D000 bank 1 + this.enlcram = true; + this.offset = 0x0000; + this.nowrite = (this.lastRead !== 0xC08B); + break; + default: + data = 0x00; + } + } else if (addr < 0xD000) { + // I/O Firmware + } else { + // 12K ROM & 16K bank switched RAM + if (! this.enlcram) { + // Read ROM + data = this.rom[addr - 0xD000]; + } else { + data = this.altzp[addr-this.offset]; + } + } + this.lastRead = addr; + return data; +}; + +// Return a 16-bit chuck of data at a given address +AppleIIcMemory.prototype.getAddr = function(addr) { + return this.getData(addr) | this.getData[(addr+1) & 0xFFFF] << 8; +}; + diff --git a/cpu.js b/cpu.js new file mode 100644 index 0000000..1318dfa --- /dev/null +++ b/cpu.js @@ -0,0 +1,939 @@ +function StatusRegister() { + this.N = false; // bit 7: Negative flag + this.V = false; // bit 6: oVerflow flag + this.R = true; // bit 5: Reserved flag: hardwired to '1' + this.B = false; // bit 4: Break flag + this.D = false; // bit 3: Decimal flag + this.I = false; // bit 2: Interrupt flag + this.Z = false; // bit 1: Zero flag + this.C = false; // bit 0: Carry flag +} + +StatusRegister.prototype.get = function() { + return (this.N<<7) + (this.V<<6) + 32 + (this.B<<4) + + (this.D<<3) + (this.I<<2) + (this.Z<<1) + this.C; +}; + +StatusRegister.prototype.set= function(value) { + this.N = (value & (1<<7)) > 0; + this.V = (value & (1<<6)) > 0; + this.B = (value & (1<<4)) > 0; + this.D = (value & (1<<3)) > 0; + this.I = (value & (1<<2)) > 0; + this.Z = (value & (1<<1)) > 0; + this.C = (value & 1) > 0; +}; + + +function Cpu(memCtrl) { + // Simulates connections to the IRQ and NMI pins. If set to true (HIGH), then + // IRQ or NMI interrupts will be triggered + this.irq_pin = false; + this.nmi_pin = false; + + // Interrupt vector addresses; these are hardwired in the CPU + var VECTOR_NMI = 0xFFFA; // non-maskable interrupt + var VECTOR_RST = 0xFFFC; // reset + var VECTOR_IRQ = 0xFFFE; // interrupt request + + // Registers + var regA = 0x00; // Accumulator + var regX = 0x00; // X-index register + var regY = 0x00; // Y-index register + var regSP = 0xFF; // Stack pointer + var regPC = 0x00; // Program counter + regSR = new StatusRegister(); // Status register + + // State information + var cycle = 0; // CPU cycle counter + var opcode = 0x00; // current opcode + var PCinc = 0x00; // amount to increase PC at end of cycle + var waiting = false; // CPU waiting (due to WAI) + var stopped = false; // CPU halted (due to STP) + + // Helper functions + function operandAddr() { // Address of LSB of operand + return (regPC + 1) & 0xFFFF; + } + function updateNZ(data) { // Update the N and Z flags + regSR.Z = (data === 0); // Z flag true if data == 0 + regSR.N = (data & 0x80) > 0; // N flag true if bit 7 = 1; + } + function pushStack(data) { // Push data onto stack + var addr = (0x01 << 8) | regSP; + memCtrl.setData(addr, data); + regSP = (regSP - 1) & 0xFF; + } + function pullStack() { // Pull data from stack + regSP = (regSP + 1) & 0xFF; + var addr = (0x01 << 8) | regSP; + return memCtrl.getData(addr); + } + function pushStackAddr(addr) { // Push 16-bit address onto stack + pushStack((addr & 0xFF00) >> 8); // push high byte + pushStack(addr & 0x00FF); // push low byte + } + function pullStackAddr() { // Pull 16-bit address from stack + var lo = pullStack(); // pull low byte + var hi = pullStack(); // pull high byte + return lo + (hi << 8); + } + function checkBranch(addr, condition) { // check if we can do a branch + if (condition) { + // do the branch + cycle++; + if ((regPC & 0xFF00) != (addr & 0xFF00)) + cycle++; // add a cycle if PB crossed + regPC = addr; + } + } + + // Cycle lookup table + var opcycles = + [7, 6, 2, 1, 5, 3, 5, 5, 3, 2, 2, 1, 6, 4, 6, 5, + 2, 5, 5, 1, 5, 4, 6, 5, 2, 4, 2, 1, 6, 4, 6, 5, + 6, 6, 2, 1, 3, 3, 5, 5, 4, 2, 2, 1, 4, 4, 6, 5, + 2, 5, 5, 1, 4, 4, 6, 5, 2, 4, 2, 1, 4, 4, 6, 5, + 6, 6, 2, 1, 3, 3, 5, 5, 3, 2, 2, 1, 3, 4, 6, 5, + 2, 5, 5, 1, 4, 4, 6, 5, 2, 4, 3, 1, 8, 4, 6, 5, + 6, 6, 2, 1, 3, 3, 5, 5, 4, 2, 2, 1, 6, 4, 6, 5, + 2, 5, 5, 1, 4, 4, 6, 5, 2, 4, 4, 1, 6, 4, 6, 5, + 3, 6, 2, 1, 3, 3, 3, 5, 2, 2, 2, 1, 4, 4, 4, 5, + 2, 6, 5, 1, 4, 4, 4, 5, 2, 5, 2, 1, 4, 5, 5, 5, + 2, 6, 2, 1, 3, 3, 3, 5, 2, 2, 2, 1, 4, 4, 4, 5, + 2, 5, 5, 1, 4, 4, 4, 5, 2, 4, 2, 1, 4, 4, 4, 5, + 2, 6, 2, 1, 3, 3, 5, 5, 2, 2, 2, 3, 4, 4, 6, 5, + 2, 5, 5, 1, 4, 4, 6, 5, 2, 4, 3, 3, 4, 4, 7, 5, + 2, 6, 2, 1, 3, 3, 5, 5, 2, 2, 2, 1, 4, 4, 6, 5, + 2, 5, 5, 1, 4, 4, 6, 5, 2, 4, 4, 1, 4, 4, 7, 5]; + + + /** Implicit addressing + * Address is implicit in operand + * e.g. RTS + * 0 bytes + */ + var amImplicit = function() { + return regPC; + }; + + /** Accumulator addressing + * Use accumulator for data + * e.g. ASL A + * 0 bytes + */ + var amAccumulator = function() { + return regPC; + }; + + /** Immediate Mode addressing + * Address immediately follows opcode + * e.g. LDA #$65 + * 1 byte + */ + var amImmediate = function() { + PCinc++; + return (regPC + 1) & 0xFFFF; + }; + + /** Zero Page addressing + * Zero page address is formed by taking next 8-bit value + * e.g. LDA $4F + * 1 byte + */ + var amZeroPage = function() { + PCinc++; + return memCtrl.getData(operandAddr()); + }; + + /** Zero Page, X addressing + * Zero page address is formed by taking next 8-bit value and + * adding X to it. Address wraps around page $00 + * e.g. LDA $65,X + * 1 byte + */ + var amZeroPageX = function() { + PCinc++; + return (memCtrl.getData(operandAddr()) + regX) & 0x00FF; + }; + + /** Zero Page, Y addressing + * Zero page address is formed by taking next 8-bit value and + * adding Y to it. Address wraps around page $00 + * e.g. LDA $5C,Y + * 1 byte + */ + var amZeroPageY = function() { + PCinc++; + return (memCtrl.getData(operandAddr()) + regY) & 0x00FF; + }; + + + /** Zero Page Indirect addressing + * Address is formed by taking zero page address from next 8-bit value + * Zero page location is LSB of 16-bit address. Wrap around occurs. + * e.g. LDA ($23) + * 1 byte + */ + var amZPIndirect = function() { + PCinc++; + var addr = memCtrl.getData(operandAddr()); + return memCtrl.getData(addr) + (memCtrl.getData(addr+1 & 0x00FF) << 8); + }; + + /** Zero Page Indirect, X addressing + * Address is formed by taking zero page address from next 8-bit value + * and adding X to it (with wrap around). + * Zero page location is LSB of 16-bit address. Wrap around occurs. + * e.g. LDA ($D2,X) + * 1 byte + */ + var amZPIndirectX = function() { + PCinc++; + var addr = (memCtrl.getData(operandAddr()) + regX) & 0x00FF; + return memCtrl.getData(addr) + (memCtrl.getData(addr+1 & 0x00FF) << 8); + }; + + /** Zero Page Indirect, Y addressing + * Address is formed by taking zero page address from next 8-bit value + * Zero page location is LSB of 16-bit address. Y is added to this value + * e.g. LDA ($C1),Y + * 1 byte, +1 cycle if PB crossed + */ + var amZPIndirectY = function() { + PCinc++; + var zp_addr = memCtrl.getData(operandAddr()); + var ind_addr = memCtrl.getData(zp_addr) + (memCtrl.getData((zp_addr+1) & 0x00FF) << 8); + var addr = (ind_addr + regY) & 0xFFFF; + // check if PB is crossed + if ((addr & 0xFF00) != (ind_addr & 0xFF00)) { cycle++; } + return addr; + }; + + /** Zero Page Relative addressing + * Zero page address is given by the next 8-bit value, and the 8-bit value + * that follows is a signed offset (-128 -> +127) + * e.g. BBR0 $23,12 + * 2 bytes + */ + var amZPRelative = function() { + PCinc += 2; + return memCtrl.getData(operandAddr()); + }; + + /** Relative addressing + * Signed offset (-128 -> +127) is given by the next 8-bit value + * e.g. BEQ -34 + * 1 byte + */ + var amRelative = function() { + PCinc++; + var offset = memCtrl.getData(operandAddr()); + if (offset > 127) { + return (regPC + (offset - 256)) & 0xFFFF; + } else { + return (regPC + offset) & 0xFFFF; + } + }; + + /** Absolute addressing + * Located at address pointed to by next 16-bit memory value + * e.g. JMP $872F + * 2 bytes + */ + var amAbsolute = function() { + PCinc += 2; + return memCtrl.getAddr(operandAddr()); + }; + + /** Absolute, X addressing + * Address is formed by taking next 16-bit memory value, and + * adding X to it + * e.g. JMP $1291,X + * 2 bytes, +1 cycle if PB crossed + */ + var amAbsoluteX = function() { + PCinc += 2; + var base_addr = memCtrl.getAddr(operandAddr()); + var addr = (base_addr + regX) & 0xFFFF; + // check if PB is crossed (except for DEC and INC - new for 65C02) + if (((addr & 0xFF00) != (base_addr & 0xFF00)) && + (opcode != 0xDE && 0xFE)) { cycle++; } + return addr; + }; + + /** Absolute, Y addressing + * Address is formed by taking next 16-bit memory value, and + * adding Y to it + * e.g. JMP $C123,Y + * 2 bytes, +1 cycle if PB crossed + */ + var amAbsoluteY = function() { + PCinc += 2; + var base_addr = memCtrl.getAddr(operandAddr()); + var addr = (base_addr + regY) & 0xFFFF; + // check if PB is crossed + if ((addr & 0xFF00) != (base_addr & 0xFF00)) { cycle++; } + return addr; + }; + + /** Absolute Indirect addressing + * Next 16-bits are the address of another address + * e.g. JMP ($4C21) + * 2 bytes + */ + var amIndirect = function() { + PCinc += 2; + var addr = memCtrl.getAddr(operandAddr()); + return memCtrl.getAddr(addr); + }; + + /** Absolute Indirect Indexed addressing + * Next 16-bits are added to X, which point to another address + * e.g. JMP ($0823,X) + * 2 bytes + */ + var amAbsIndIndx = function() { + PCinc += 2; cycle += 5; + var addr = (memCtrl.getAddr(operandAddr()) + regX) & 0xFFFF; + return memCtrl.getAddr(addr); + }; + + // Opcode functions + var opLDA = function(amFn) { regA = memCtrl.getData(amFn()); updateNZ(regA); }; + var opLDX = function(amFn) { regX = memCtrl.getData(amFn()); updateNZ(regX); }; + var opLDY = function(amFn) { regY = memCtrl.getData(amFn()); updateNZ(regY); }; + var opSTA = function(amFn) { memCtrl.setData(amFn(), regA); }; + var opSTX = function(amFn) { memCtrl.setData(amFn(), regX); }; + var opSTY = function(amFn) { memCtrl.setData(amFn(), regY); }; + var opSTZ = function(amFn) { memCtrl.setData(amFn(), 0x00); }; + var opPHA = function(amFn) { pushStack(regA); }; + var opPHX = function(amFn) { pushStack(regX); }; + var opPHY = function(amFn) { pushStack(regY); }; + var opPHP = function(amFn) { regSR.B = true; pushStack(regSR.get()); }; + var opPLA = function(amFn) { regA = pullStack(); updateNZ(regA); }; + var opPLX = function(amFn) { regX = pullStack(); updateNZ(regX); }; + var opPLY = function(amFn) { regY = pullStack(); updateNZ(regY); }; + var opPLP = function(amFn) { regSR.set(pullStack()); regSR.B = true; }; + var opTSX = function(amFn) { regX = regSP; updateNZ(regX); }; + var opTXS = function(amFn) { regSP = regX; }; + var opINA = function(amFn) { regA = (regA+1) & 0xFF; updateNZ(regA); }; + var opINX = function(amFn) { regX = (regX+1) & 0xFF; updateNZ(regX); }; + var opINY = function(amFn) { regY = (regY+1) & 0xFF; updateNZ(regY); }; + var opDEA = function(amFn) { regA = (regA-1) & 0xFF; updateNZ(regA); }; + var opDEX = function(amFn) { regX = (regX-1) & 0xFF; updateNZ(regX); }; + var opDEY = function(amFn) { regY = (regY-1) & 0xFF; updateNZ(regY); }; + + var opINC = function(amFn) { + var addr = amFn(); + var data = memCtrl.getData(addr); // retrieve data from memory + data = memCtrl.getData(addr); // 2nd mem read (new for 65C02) + data = (data + 1) & 0xFF; + memCtrl.setData(addr, data); + updateNZ(data); + }; + var opDEC = function(amFn) { + var addr = amFn(); + var data = memCtrl.getData(addr); // retrieve data from memory + data = memCtrl.getData(addr); // 2nd mem read (new for 65C02) + data = (data - 1) & 0xFF; + memCtrl.setData(addr, data); + updateNZ(data); + }; + var opASL = function(amFn) { + var addr = amFn(); + var data = memCtrl.getData(addr); // retrieve data from memory + data = memCtrl.getData(addr); // 2nd mem read (new for 65C02) + regSR.C = (data & 0x80) > 0; // Get the high bit and put it in the carry flag + data = (data << 1) & 0xFF; // shift left and mask + memCtrl.setData(addr, data); + updateNZ(data); + }; + var opASL_A = function(amFn) { + regSR.C = (regA & 0x80) > 0; + regA = (regA << 1) & 0xFF; + updateNZ(regA); + }; + var opLSR = function(amFn) { + var addr = amFn(); + var data = memCtrl.getData(addr); // retrieve data from memory + data = memCtrl.getData(addr); // 2nd mem read (new for 65C02) + regSR.C = (data & 0x01) > 0; // get the low bit and put it in the carry flag + data = (data >> 1) & 0xFF; // shift right and mask + memCtrl.setData(addr, data); + updateNZ(data); + }; + var opLSR_A = function(amFn) { + regSR.C = (regA & 0x01) > 0; + regA = (regA >> 1) & 0xFF; + updateNZ(regA); + }; + var opROL = function(amFn) { + var addr = amFn(); + var data = memCtrl.getData(addr); // retrieve data from memory + data = memCtrl.getData(addr); // 2nd mem read (new for 65C02) + data = (data << 1) | regSR.C; // shift left and add the carry bit + regSR.C = (data & 0x100) > 0; // get bit-8 and put it in the carry flag + data &= 0xFF; // mask the data + memCtrl.setData(addr, data); + updateNZ(data); + }; + var opROL_A = function(amFn) { + regA = (regA << 1) | regSR.C; + regSR.C = (regA & 0x100) > 0; + regA &= 0xFF; + updateNZ(regA); + }; + var opROR = function(amFn) { + var addr = amFn(); + var data = memCtrl.getData(addr); // retrieve data from memory + data = memCtrl.getData(addr); // 2nd mem read (new for 65C02) + var newC = (data & 0x01) > 0; // get new Carry flag from bit 1 + data = (data | (regSR.C << 8)) >> 1; // add existing Carry flag and shift right + data &= 0xFF; // mask the data + regSR.C = newC; // set the Carry flag + memCtrl.setData(addr, data); + updateNZ(data); + }; + var opROR_A = function(amFn) { + var newC = (regA & 0x01) > 0; + regA = (regA | (regSR.C << 8)) >> 1; + regA &= 0xFF; + regSR.C = newC; + updateNZ(regA); + }; + var opAND = function(amFn) { regA = (regA & memCtrl.getData(amFn())) & 0xFF; updateNZ(regA); }; + var opORA = function(amFn) { regA = (regA | memCtrl.getData(amFn())) & 0xFF; updateNZ(regA); }; + var opEOR = function(amFn) { regA = (regA ^ memCtrl.getData(amFn())) & 0xFF; updateNZ(regA); }; + var opBIT = function(amFn) { + var data = memCtrl.getData(amFn()); + var result = data & regA; + regSR.Z = (result === 0x00); + if (opcode != 0x89) { // new for 65C02: immediate mode does not affect V and N flags + regSR.V = (data & 0x40) > 0; + regSR.N = (data & 0x80) > 0; + } + }; + var opCMP = function(amFn) { + var data = memCtrl.getData(amFn()); + regSR.C = (regA >= data); + updateNZ((regA - data) & 0xFF); + }; + var opCPX = function(amFn) { + var data = memCtrl.getData(amFn()); + regSR.C = (regX >= data); + updateNZ((regX - data) & 0xFF); + }; + var opCPY = function(amFn) { + var data = memCtrl.getData(amFn()); + regSR.C = (regY >= data); + updateNZ((regY - data) & 0xFF); + }; + var opTRB = function(amFn) { + var addr = amFn(); + var data = memCtrl.getData(addr); + var result = data & (regA ^ 0xFF); + regSR.Z = (regA & data) === 0; + memCtrl.setData(addr, result); + }; + var opTSB = function(amFn) { + var addr = amFn(); + var data = memCtrl.getData(addr); + var result = data | regA; + regSR.Z = (regA & data) === 0; + memCtrl.setData(addr, result); + }; + var opRMB = function(amFn) { + var addr = amFn(); + var bitmask = 1 << (opcode >> 4); + var result = memCtrl.getData(addr) & (bitmask ^ 0xFF); + memCtrl.setData(addr, result); + }; + var opSMB = function(amFn) { + var addr = amFn(); + var bitmask = 1 << ((opcode >> 4) - 8); + var result = memCtrl.getData(addr) | bitmask; + memCtrl.setData(addr, result); + }; + var opADC = function(amFn) { + var data = memCtrl.getData(amFn()); + var result; + if (!regSR.D) { + result = regA + data + regSR.C; + } else { + cycle++; // new for 65C02 to make flags in D mode correct + result = (regA & 0x0F) + (data & 0x0F) + regSR.C; + if (result >= 0x0A) { result = ((result + 0x06) & 0x0F) + 0x10; } + result = (regA & 0xF0) + (data & 0xF0) + result; + if (result >= 0xA0) { result += 0x60; } + } + regSR.C = (result & 0x100) > 0; + result &= 0xFF; + regSR.V = ((regA^result)&(data^result)&0x80) > 0; + regA = result; + updateNZ(regA); + }; + var opSBC = function(amFn) { + var data = memCtrl.getData(amFn()); + var result; + if (!regSR.D) { + result = regA - data - (1 - regSR.C); + } else { + cycle++; // new for 65C02 to make flags in D mode correct + result = (regA & 0x0F) - (data & 0x0F) - (1 - regSR.C); + if (result < 0) {result = ((result - 0x06) & 0x0F) - 0x10; } + result = (regA & 0xF0) - (data & 0xF0) + result; + if (result < 0) {result -= 0x60; } + } + regSR.C = (result >= 0); + result &= 0xFF; + regSR.V = ((regA^result)&((255-data)^result)&0x80) > 0; + regA = result; + updateNZ(regA); + }; + var opJMP = function(amFn) { regPC = amFn(); PCinc = 0; }; + var opJSR = function(amFn) { pushStackAddr((regPC+2) & 0xFFFF); regPC = amFn(); PCinc = 0;}; + var opRTS = function(amFn) { regPC = pullStackAddr(); }; + var opRTI = function(amFn) { this.regSR.set(pullStack()); regSR.B = true; regPC = pullStackAddr(); PCinc = 0;}; + var opBRA = function(amFn) { regPC = amFn(); }; + var opBEQ = function(amFn) { checkBranch(amFn(), regSR.Z); }; + var opBNE = function(amFn) { checkBranch(amFn(), !regSR.Z); }; + var opBCC = function(amFn) { checkBranch(amFn(), !regSR.C); }; + var opBCS = function(amFn) { checkBranch(amFn(), regSR.C); }; + var opBVC = function(amFn) { checkBranch(amFn(), !regSR.V); }; + var opBVS = function(amFn) { checkBranch(amFn(), regSR.V); }; + var opBMI = function(amFn) { checkBranch(amFn(), regSR.N); }; + var opBPL = function(amFn) { checkBranch(amFn(), !regSR.N); }; + var opBBR = function(amFn) { + var bitmask = 1 << (opcode >> 4); + if ((memCtrl.getData(amFn()) & bitmask) === 0) { + var offset = memCtrl.getData((regPC+2) & 0xFFFF); + if (offset > 127) { + regPC = (regPC + (offset - 256)) & 0xFFFF; + } else { + regPC = (regPC + offset) & 0xFFFF; + } + } + }; + var opBBS = function(amFn) { + var bitmask = 1 << ((opcode >> 4) - 8); + if ((memCtrl.getData(amFn()) & bitmask) > 0) { + var offset = memCtrl.getData((regPC+2) & 0xFFFF); + if (offset > 127) { + regPC = (regPC + (offset - 256)) & 0xFFFF; + } else { + regPC = (regPC + offset) & 0xFFFF; + } + } + }; + + var opCLC = function(amFn) { regSR.C = false; }; + var opCLD = function(amFn) { regSR.D = false; }; + var opCLI = function(amFn) { regSR.I = false; }; + var opCLV = function(amFn) { regSR.V = false; }; + var opSEC = function(amFn) { regSR.C = true; }; + var opSED = function(amFn) { regSR.D = true; }; + var opSEI = function(amFn) { regSR.I = true; }; + var opTAX = function(amFn) { regX = regA; updateNZ(regX); }; + var opTAY = function(amFn) { regY = regA; updateNZ(regY); }; + var opTXA = function(amFn) { regA = regX; updateNZ(regA); }; + var opTYA = function(amFn) { regA = regY; updateNZ(regA); }; + var opNOP = function(amFn) { + // Different NOP instructions have different byte sizes + // PCinc = 1 by default. This switch statements targets NOPs + // with byte sizes greater than 1 + switch (opcode & 0x0F) { + case 0x02: + case 0x04: + PCinc = 2; + break; + case 0x0C: + PCinc = 3; + break; + default: + PCinc = 1; + } + }; + + var opBRK = function(amFn) { + pushStackAddr(regPC+2); // push PC plus 2 (making BRK a 2-byte instruction) + regSR.B = true; + pushStack(regSR.get()); // push SR with B flag set + regSR.I = true; // set I flag + regSR.D = false; // clear D flag before jumping (new for 65C02) + regPC = memCtrl.getAddr(VECTOR_IRQ); + PCinc = 0; + }; + + var opWAI = function(amFn) { + regSR.B = true; + waiting = true; + }; + + var opSTP = function(amFn) { + stopped = true; + waiting = true; + }; + + // opcode <-> address mode lookup table + this.optable = new Array(256); + this.optable[0x00] = function() {opBRK(amImplicit);}; + this.optable[0x01] = function() {opORA(amZPIndirectX);}; + this.optable[0x02] = function() {opNOP(amImplicit);}; + this.optable[0x03] = function() {opNOP(amImplicit);}; + this.optable[0x04] = function() {opTSB(amZeroPage);}; + this.optable[0x05] = function() {opORA(amZeroPage);}; + this.optable[0x06] = function() {opASL(amZeroPage);}; + this.optable[0x07] = function() {opRMB(amZeroPage);}; + this.optable[0x08] = function() {opPHP(amImplicit);}; + this.optable[0x09] = function() {opORA(amImmediate);}; + this.optable[0x0A] = function() {opASL_A(amAccumulator);}; + this.optable[0x0B] = function() {opNOP(amImplicit);}; + this.optable[0x0C] = function() {opTSB(amAbsolute);}; + this.optable[0x0D] = function() {opORA(amAbsolute);}; + this.optable[0x0E] = function() {opASL(amAbsolute);}; + this.optable[0x0F] = function() {opBBR(amZPRelative);}; + this.optable[0x10] = function() {opBPL(amRelative);}; + this.optable[0x11] = function() {opORA(amZPIndirectY);}; + this.optable[0x12] = function() {opORA(amZPIndirect);}; + this.optable[0x13] = function() {opNOP(amImplicit);}; + this.optable[0x14] = function() {opTRB(amZeroPage);}; + this.optable[0x15] = function() {opORA(amZeroPageX);}; + this.optable[0x16] = function() {opASL(amZeroPageX);}; + this.optable[0x17] = function() {opRMB(amZeroPage);}; + this.optable[0x18] = function() {opCLC(amImplicit);}; + this.optable[0x19] = function() {opORA(amAbsoluteY);}; + this.optable[0x1A] = function() {opINA(amImplicit);}; + this.optable[0x1B] = function() {opNOP(amImplicit);}; + this.optable[0x1C] = function() {opTRB(amAbsolute);}; + this.optable[0x1D] = function() {opORA(amAbsoluteX);}; + this.optable[0x1E] = function() {opASL(amAbsoluteX);}; + this.optable[0x1F] = function() {opBBR(amZPRelative);}; + this.optable[0x20] = function() {opJSR(amAbsolute);}; + this.optable[0x21] = function() {opAND(amZPIndirectX);}; + this.optable[0x22] = function() {opNOP(amImplicit);}; + this.optable[0x23] = function() {opNOP(amImplicit);}; + this.optable[0x24] = function() {opBIT(amZeroPage);}; + this.optable[0x25] = function() {opAND(amZeroPage);}; + this.optable[0x26] = function() {opROL(amZeroPage);}; + this.optable[0x27] = function() {opRMB(amZeroPage);}; + this.optable[0x28] = function() {opPLP(amImplicit);}; + this.optable[0x29] = function() {opAND(amImmediate);}; + this.optable[0x2A] = function() {opROL_A(amAccumulator);}; + this.optable[0x2B] = function() {opNOP(amImplicit);}; + this.optable[0x2C] = function() {opBIT(amAbsolute);}; + this.optable[0x2D] = function() {opAND(amAbsolute);}; + this.optable[0x2E] = function() {opROL(amAbsolute);}; + this.optable[0x2F] = function() {opBBR(amZPRelative);}; + this.optable[0x30] = function() {opBMI(amRelative);}; + this.optable[0x31] = function() {opAND(amZPIndirectY);}; + this.optable[0x32] = function() {opAND(amZPIndirect);}; + this.optable[0x33] = function() {opNOP(amImplicit);}; + this.optable[0x34] = function() {opBIT(amZeroPageX);}; + this.optable[0x35] = function() {opAND(amZeroPageX);}; + this.optable[0x36] = function() {opROL(amZeroPageX);}; + this.optable[0x37] = function() {opRMB(amZeroPage);}; + this.optable[0x38] = function() {opSEC(amImplicit);}; + this.optable[0x39] = function() {opAND(amAbsoluteY);}; + this.optable[0x3A] = function() {opDEA(amImplicit);}; + this.optable[0x3B] = function() {opNOP(amImplicit);}; + this.optable[0x3C] = function() {opBIT(amAbsoluteX);}; + this.optable[0x3D] = function() {opAND(amAbsoluteX);}; + this.optable[0x3E] = function() {opROL(amAbsoluteX);}; + this.optable[0x3F] = function() {opBBR(amZPRelative);}; + this.optable[0x40] = function() {opRTI(amImplicit);}; + this.optable[0x41] = function() {opEOR(amZPIndirectX);}; + this.optable[0x42] = function() {opNOP(amImplicit);}; + this.optable[0x43] = function() {opNOP(amImplicit);}; + this.optable[0x44] = function() {opNOP(amImplicit);}; + this.optable[0x45] = function() {opEOR(amZeroPage);}; + this.optable[0x46] = function() {opLSR(amZeroPage);}; + this.optable[0x47] = function() {opRMB(amZeroPage);}; + this.optable[0x48] = function() {opPHA(amImplicit);}; + this.optable[0x49] = function() {opEOR(amImmediate);}; + this.optable[0x4A] = function() {opLSR_A(amAccumulator);}; + this.optable[0x4B] = function() {opNOP(amImplicit);}; + this.optable[0x4C] = function() {opJMP(amAbsolute);}; + this.optable[0x4D] = function() {opEOR(amAbsolute);}; + this.optable[0x4E] = function() {opLSR(amAbsolute);}; + this.optable[0x4F] = function() {opBBR(amZPRelative);}; + this.optable[0x50] = function() {opBVC(amRelative);}; + this.optable[0x51] = function() {opEOR(amZPIndirectY);}; + this.optable[0x52] = function() {opEOR(amZPIndirect);}; + this.optable[0x53] = function() {opNOP(amImplicit);}; + this.optable[0x54] = function() {opNOP(amImplicit);}; + this.optable[0x55] = function() {opEOR(amZeroPageX);}; + this.optable[0x56] = function() {opLSR(amZeroPageX);}; + this.optable[0x57] = function() {opRMB(amZeroPage);}; + this.optable[0x58] = function() {opCLI(amImplicit);}; + this.optable[0x59] = function() {opEOR(amAbsoluteY);}; + this.optable[0x5A] = function() {opPHY(amImplicit);}; + this.optable[0x5B] = function() {opNOP(amImplicit);}; + this.optable[0x5C] = function() {opNOP(amImplicit);}; + this.optable[0x5D] = function() {opEOR(amAbsoluteX);}; + this.optable[0x5E] = function() {opLSR(amAbsoluteX);}; + this.optable[0x5F] = function() {opBBR(amZPRelative);}; + this.optable[0x60] = function() {opRTS(amImplicit);}; + this.optable[0x61] = function() {opADC(amZPIndirectX);}; + this.optable[0x62] = function() {opNOP(amImplicit);}; + this.optable[0x63] = function() {opNOP(amImplicit);}; + this.optable[0x64] = function() {opSTZ(amZeroPage);}; + this.optable[0x65] = function() {opADC(amZeroPage);}; + this.optable[0x66] = function() {opROR(amZeroPage);}; + this.optable[0x67] = function() {opRMB(amZeroPage);}; + this.optable[0x68] = function() {opPLA(amImplicit);}; + this.optable[0x69] = function() {opADC(amImmediate);}; + this.optable[0x6A] = function() {opROR_A(amAccumulator);}; + this.optable[0x6B] = function() {opNOP(amImplicit);}; + this.optable[0x6C] = function() {opJMP(amIndirect);}; + this.optable[0x6D] = function() {opADC(amAbsolute);}; + this.optable[0x6E] = function() {opROR(amAbsolute);}; + this.optable[0x6F] = function() {opBBR(amZPRelative);}; + this.optable[0x70] = function() {opBVS(amRelative);}; + this.optable[0x71] = function() {opADC(amZPIndirectY);}; + this.optable[0x72] = function() {opADC(amZPIndirect);}; + this.optable[0x73] = function() {opNOP(amImplicit);}; + this.optable[0x74] = function() {opSTZ(amZeroPageX);}; + this.optable[0x75] = function() {opADC(amZeroPageX);}; + this.optable[0x76] = function() {opROR(amZeroPageX);}; + this.optable[0x77] = function() {opRMB(amZeroPage);}; + this.optable[0x78] = function() {opSEI(amImplicit);}; + this.optable[0x79] = function() {opADC(amAbsoluteY);}; + this.optable[0x7A] = function() {opPLY(amImplicit);}; + this.optable[0x7B] = function() {opNOP(amImplicit);}; + this.optable[0x7C] = function() {opJMP(amAbsIndIndx);}; + this.optable[0x7D] = function() {opADC(amAbsoluteX);}; + this.optable[0x7E] = function() {opROR(amAbsoluteX);}; + this.optable[0x7F] = function() {opBBR(amZPRelative);}; + this.optable[0x80] = function() {opBRA(amRelative);}; + this.optable[0x81] = function() {opSTA(amZPIndirectX);}; + this.optable[0x82] = function() {opNOP(amImplicit);}; + this.optable[0x83] = function() {opNOP(amImplicit);}; + this.optable[0x84] = function() {opSTY(amZeroPage);}; + this.optable[0x85] = function() {opSTA(amZeroPage);}; + this.optable[0x86] = function() {opSTX(amZeroPage);}; + this.optable[0x87] = function() {opSMB(amZeroPage);}; + this.optable[0x88] = function() {opDEY(amImplicit);}; + this.optable[0x89] = function() {opBIT(amImmediate);}; + this.optable[0x8A] = function() {opTXA(amImplicit);}; + this.optable[0x8B] = function() {opNOP(amImplicit);}; + this.optable[0x8C] = function() {opSTY(amAbsolute);}; + this.optable[0x8D] = function() {opSTA(amAbsolute);}; + this.optable[0x8E] = function() {opSTX(amAbsolute);}; + this.optable[0x8F] = function() {opBBS(amZPRelative);}; + this.optable[0x90] = function() {opBCC(amRelative);}; + this.optable[0x91] = function() {opSTA(amZPIndirectY);}; + this.optable[0x92] = function() {opSTA(amZPIndirect);}; + this.optable[0x93] = function() {opNOP(amImplicit);}; + this.optable[0x94] = function() {opSTY(amZeroPageX);}; + this.optable[0x95] = function() {opSTA(amZeroPageX);}; + this.optable[0x96] = function() {opSTX(amZeroPageY);}; + this.optable[0x97] = function() {opSMB(amZeroPage);}; + this.optable[0x98] = function() {opTYA(amImplicit);}; + this.optable[0x99] = function() {opSTA(amAbsoluteY);}; + this.optable[0x9A] = function() {opTXS(amImplicit);}; + this.optable[0x9B] = function() {opNOP(amImplicit);}; + this.optable[0x9C] = function() {opSTZ(amAbsolute);}; + this.optable[0x9D] = function() {opSTA(amAbsoluteX);}; + this.optable[0x9E] = function() {opSTZ(amAbsoluteX);}; + this.optable[0x9F] = function() {opBBS(amZPRelative);}; + this.optable[0xA0] = function() {opLDY(amImmediate);}; + this.optable[0xA1] = function() {opLDA(amZPIndirectX);}; + this.optable[0xA2] = function() {opLDX(amImmediate);}; + this.optable[0xA3] = function() {opNOP(amImplicit);}; + this.optable[0xA4] = function() {opLDY(amZeroPage);}; + this.optable[0xA5] = function() {opLDA(amZeroPage);}; + this.optable[0xA6] = function() {opLDX(amZeroPage);}; + this.optable[0xA7] = function() {opSMB(amZeroPage);}; + this.optable[0xA8] = function() {opTAY(amImplicit);}; + this.optable[0xA9] = function() {opLDA(amImmediate);}; + this.optable[0xAA] = function() {opTAX(amImplicit);}; + this.optable[0xAB] = function() {opNOP(amImplicit);}; + this.optable[0xAC] = function() {opLDY(amAbsolute);}; + this.optable[0xAD] = function() {opLDA(amAbsolute);}; + this.optable[0xAE] = function() {opLDX(amAbsolute);}; + this.optable[0xAF] = function() {opBBS(amZPRelative);}; + this.optable[0xB0] = function() {opBCS(amRelative);}; + this.optable[0xB1] = function() {opLDA(amZPIndirectY);}; + this.optable[0xB2] = function() {opLDA(amZPIndirect);}; + this.optable[0xB3] = function() {opNOP(amImplicit);}; + this.optable[0xB4] = function() {opLDY(amZeroPageX);}; + this.optable[0xB5] = function() {opLDA(amZeroPageX);}; + this.optable[0xB6] = function() {opLDX(amZeroPageY);}; + this.optable[0xB7] = function() {opSMB(amZeroPage);}; + this.optable[0xB8] = function() {opCLV(amImplicit);}; + this.optable[0xB9] = function() {opLDA(amAbsoluteY);}; + this.optable[0xBA] = function() {opTSX(amImplicit);}; + this.optable[0xBB] = function() {opNOP(amImplicit);}; + this.optable[0xBC] = function() {opLDY(amAbsoluteX);}; + this.optable[0xBD] = function() {opLDA(amAbsoluteX);}; + this.optable[0xBE] = function() {opLDX(amAbsoluteY);}; + this.optable[0xBF] = function() {opBBS(amZPRelative);}; + this.optable[0xC0] = function() {opCPY(amImmediate);}; + this.optable[0xC1] = function() {opCMP(amZPIndirectX);}; + this.optable[0xC2] = function() {opNOP(amImplicit);}; + this.optable[0xC3] = function() {opNOP(amImplicit);}; + this.optable[0xC4] = function() {opCPY(amZeroPage);}; + this.optable[0xC5] = function() {opCMP(amZeroPage);}; + this.optable[0xC6] = function() {opDEC(amZeroPage);}; + this.optable[0xC7] = function() {opSMB(amZeroPage);}; + this.optable[0xC8] = function() {opINY(amImplicit);}; + this.optable[0xC9] = function() {opCMP(amImmediate);}; + this.optable[0xCA] = function() {opDEX(amImplicit);}; + this.optable[0xCB] = function() {opWAI(amImplicit);}; + this.optable[0xCC] = function() {opCPY(amAbsolute);}; + this.optable[0xCD] = function() {opCMP(amAbsolute);}; + this.optable[0xCE] = function() {opDEC(amAbsolute);}; + this.optable[0xCF] = function() {opBBS(amZPRelative);}; + this.optable[0xD0] = function() {opBNE(amRelative);}; + this.optable[0xD1] = function() {opCMP(amZPIndirectY);}; + this.optable[0xD2] = function() {opCMP(amZPIndirect);}; + this.optable[0xD3] = function() {opNOP(amImplicit);}; + this.optable[0xD4] = function() {opNOP(amImplicit);}; + this.optable[0xD5] = function() {opCMP(amZeroPageX);}; + this.optable[0xD6] = function() {opDEC(amZeroPageX);}; + this.optable[0xD7] = function() {opSMB(amZeroPage);}; + this.optable[0xD8] = function() {opCLD(amImplicit);}; + this.optable[0xD9] = function() {opCMP(amAbsoluteY);}; + this.optable[0xDA] = function() {opPHX(amImplicit);}; + this.optable[0xDB] = function() {opSTP(amImplicit);}; + this.optable[0xDC] = function() {opNOP(amImplicit);}; + this.optable[0xDD] = function() {opCMP(amAbsoluteX);}; + this.optable[0xDE] = function() {opDEC(amAbsoluteX);}; + this.optable[0xDF] = function() {opBBS(amZPRelative);}; + this.optable[0xE0] = function() {opCPX(amImmediate);}; + this.optable[0xE1] = function() {opSBC(amZPIndirectX);}; + this.optable[0xE2] = function() {opNOP(amImplicit);}; + this.optable[0xE3] = function() {opNOP(amImplicit);}; + this.optable[0xE4] = function() {opCPX(amZeroPage);}; + this.optable[0xE5] = function() {opSBC(amZeroPage);}; + this.optable[0xE6] = function() {opINC(amZeroPage);}; + this.optable[0xE7] = function() {opSMB(amZeroPage);}; + this.optable[0xE8] = function() {opINX(amImplicit);}; + this.optable[0xE9] = function() {opSBC(amImmediate);}; + this.optable[0xEA] = function() {opNOP(amImplicit);}; + this.optable[0xEB] = function() {opNOP(amImplicit);}; + this.optable[0xEC] = function() {opCPX(amAbsolute);}; + this.optable[0xED] = function() {opSBC(amAbsolute);}; + this.optable[0xEE] = function() {opINC(amAbsolute);}; + this.optable[0xEF] = function() {opBBS(amZPRelative);}; + this.optable[0xF0] = function() {opBEQ(amRelative);}; + this.optable[0xF1] = function() {opSBC(amZPIndirectY);}; + this.optable[0xF2] = function() {opSBC(amZPIndirect);}; + this.optable[0xF3] = function() {opNOP(amImplicit);}; + this.optable[0xF4] = function() {opNOP(amImplicit);}; + this.optable[0xF5] = function() {opSBC(amZeroPageX);}; + this.optable[0xF6] = function() {opINC(amZeroPageX);}; + this.optable[0xF7] = function() {opSMB(amZeroPage);}; + this.optable[0xF8] = function() {opSED(amImplicit);}; + this.optable[0xF9] = function() {opSBC(amAbsoluteY);}; + this.optable[0xFA] = function() {opPLX(amImplicit);}; + this.optable[0xFB] = function() {opNOP(amImplicit);}; + this.optable[0xFC] = function() {opNOP(amImplicit);}; + this.optable[0xFD] = function() {opSBC(amAbsoluteX);}; + this.optable[0xFE] = function() {opINC(amAbsoluteX);}; + this.optable[0xFF] = function() {opBBS(amZPRelative);}; + + // CPU Execution Functions + + // Run the CPU for a given number of cycles + var run = function(num_cycles) { + var final_cycle = cycle + num_cycles; + // Check for overflow + if (final_cycle < cycle) { + cycle = 0; + final_cycle = num_cycles; + } + + while (cycle <= final_cycle && !stopped) { + // handle interrupts + if (this.irq_pin) { this.irq(); if (cycle > final_cycle) return; } + if (this.nmi_pin) { this.nmi(); if (cycle > final_cycle) return; } + if (waiting) return; + + opcode = memCtrl.getData(regPC); + PCinc = 1; + cycle += opcycles[opcode]; + + // Get the opcode function and addressing mode function from the lookup table + optable[opcode](); + + regPC = (regPC + PCinc) & 0xFFFF; + } + }; + + // Reset the CPU + var reset = function() { + regSR.D = false; + regSR.I = true; + regSR.B = true; + + stopped = false; + waiting = false; + + regPC = memCtrl.getAddr(VECTOR_RST); + cycle += 7; + }; + + // Interrupt request (via the IRQ pin) + this.irq = function() { + // Only do an IRQ interrupt if I flag is clear and the processor isn't stopped + // Also, NMI interrupts have priority over IRQ + if (! stopped) { + waiting = false; // Allow execution to continue past WAI, even if the IRQ isn't taken + if (!regSR.I && !this.nmi_pin) { + pushStackAddr(regPC); // push PC + regSR.B = false; + pushStack(regSR.get()); // push SR with B flag clear + regSR.I = true; // set interrupt disable flag + regSR.D = false; // clear D flag before jumping (new for 65C02) + regPC = memCtrl.getAddr(VECTOR_IRQ); + PCinc = 0; + cycle += 7; + } + } + }; + + // Non-maskable interrupt request (via the NMI pin) + var nmi = function() { + // Alway do an IRQ interrupt unless the processor is stopeed + if (!stopped) { + this.nmi_pin = false; // NMI is edge sensitive interrupt + waiting = false; + pushStackAddr(regPC); // push PC + regSR.B = false; + pushStack(regSR.get()); // push SR with B flag clear + regSR.I = true; // set interrupt disable flag + regSR.D = false; // clear D flag before jumping (new for 65C02) + regPC = memCtrl.getAddr(VECTOR_NMI); + PCinc = 0; + cycle += 7; + } + }; + + return { + irq_pin: this.irq_pin, + nmi_pin: this.nmi_pin, + + regA: function() {return regA;}, + regX: function() {return regX;}, + regY: function() {return regY;}, + regSP: function() {return regSP;}, + regPC: function() {return regPC;}, + regSR: function() {return regSR;}, + + setA: function(value) {regA = value & 0xFF; }, + setX: function(value) {regX = value & 0xFF; }, + setY: function(value) {regY = value & 0xFF; }, + setSP: function(value) {regSP = value & 0xFF; }, + setPC: function(value) {regPC = value & 0xFFFF; }, + setSR: function(value) {regSR.set(value); }, + + cycle: function() {return cycle;}, + + run: run, + reset: reset, + irq: irq, + nmi: nmi + }; +} \ No newline at end of file diff --git a/disassembler.js b/disassembler.js new file mode 100644 index 0000000..f615e27 --- /dev/null +++ b/disassembler.js @@ -0,0 +1,359 @@ +var STR_PAD_LEFT = 1; +var STR_PAD_RIGHT = 2; +var STR_PAD_BOTH = 3; + +function pad(str, len, pad, dir) { + + if (typeof(len) == "undefined") { var len = 0; } + if (typeof(pad) == "undefined") { var pad = ' '; } + if (typeof(dir) == "undefined") { var dir = STR_PAD_RIGHT; } + + if (len + 1 >= str.length) { + + switch (dir){ + + case STR_PAD_LEFT: + str = Array(len + 1 - str.length).join(pad) + str; + break; + + case STR_PAD_BOTH: + var right = Math.ceil((padlen = len - str.length) / 2); + var left = padlen - right; + str = Array(left+1).join(pad) + str + Array(right+1).join(pad); + break; + + default: + str = str + Array(len + 1 - str.length).join(pad); + break; + + } // switch + + } + + return str; + +} + +function Disassembler(memCtrl) { + this.memCtrl = memCtrl; + this.regPC = 0x0000; // program counter + + this.disassemble = function(startloc, nbytes) { + var bytecount = 0; + this.regPC = startloc; + + do { + dissambly = this.opToStr(); + bytecount += dissambly.numBytes; + + console.log(dissambly.text); + } while (bytecount < nbytes); + }; + + this.opToStr = function() { + var numbytes = 0; + var startpos = 0; + var mem_dump; + var opcode = this.memCtrl.getData(this.regPC); + var am_string = this.optable[opcode][1]; + + // Get the hex address of the opcode and the opcode hex code + mem_dump = "$" + this.regPC.toString(16) + ": " + opcode.toString(16); + this.regPC = (this.regPC + 1) & 0xFFFF; + numbytes++; + + var pos = am_string.indexOf("ADDR", startpos); + if (pos != -1) { + mem_dump += " " + this.memCtrl.getData(this.regPC).toString(16); + mem_dump += " " + this.memCtrl.getData((this.regPC + 1) & 0xFFFF).toString(16); + am_string = am_string.replace("ADDR", this.memCtrl.getAddr(this.regPC).toString(16)); + startpos += 4; + this.regPC = (this.regPC + 2) & 0xFFFF; + numbytes += 2; + } + pos = am_string.indexOf("BIT", startpos); + if (pos != -1) { + var bitStr = (((opcode & 0xF0) >> 4) % 8).toString(); + am_string = am_string.replace("BIT", bitStr); + startpos += 1; + } + pos = am_string.indexOf("BYTE", startpos); + if (pos != -1) { + mem_dump += " " + this.memCtrl.getData(this.regPC).toString(16); + am_string = am_string.replace("BYTE", this.memCtrl.getData(this.regPC).toString(16)); + startpos += 2; + this.regPC = (this.regPC + 1) & 0xFFFF; + numbytes++; + } + pos = am_string.indexOf("BYTE", startpos); + if (pos != -1) { + mem_dump += " " + this.memCtrl.getData(this.regPC).toString(16); + am_string = am_string.replace("BYTE", this.memCtrl.getData(this.regPC).toString(16)); + this.regPC += (this.regPC + 1) & 0xFFFF; + numbytes++; + } + + mem_dump = pad(mem_dump, 20, ' ', STR_PAD_RIGHT).toUpperCase(); + var finalStr = mem_dump + this.optable[opcode][0] + " " + am_string.toUpperCase(); + + return {numBytes: numbytes, text: finalStr}; + }; + + this.optable = new Array(256); + this.optable[0x00] = ["BRK", ""]; + this.optable[0x01] = ["ORA", "($BYTE,X)"]; + this.optable[0x02] = ["NOP", ""]; + this.optable[0x03] = ["NOP", ""]; + this.optable[0x04] = ["TSB", "$BYTE"]; + this.optable[0x05] = ["ORA", "$BYTE"]; + this.optable[0x06] = ["ASL", "$BYTE"]; + this.optable[0x07] = ["RMB", "BIT,$BYTE"]; + this.optable[0x08] = ["PHP", ""]; + this.optable[0x09] = ["ORA", "#BYTE"]; + this.optable[0x0A] = ["ASL", "A"]; + this.optable[0x0B] = ["NOP", ""]; + this.optable[0x0C] = ["TSB", "$ADDR"]; + this.optable[0x0D] = ["ORA", "$ADDR"]; + this.optable[0x0E] = ["ASL", "$ADDR"]; + this.optable[0x0F] = ["BBR", "BIT,$BYTE,BYTE"]; + this.optable[0x10] = ["BPL", "BYTE"]; + this.optable[0x11] = ["ORA", "($BYTE),Y"]; + this.optable[0x12] = ["ORA", "($BYTE)"]; + this.optable[0x13] = ["NOP", ""]; + this.optable[0x14] = ["TRB", "$BYTE"]; + this.optable[0x15] = ["ORA", "$BYTE,X"]; + this.optable[0x16] = ["ASL", "$BYTE,X"]; + this.optable[0x17] = ["RMB", "BIT,$BYTE"]; + this.optable[0x18] = ["CLC", ""]; + this.optable[0x19] = ["ORA", "$ADDR,Y"]; + this.optable[0x1A] = ["INA", "A"]; + this.optable[0x1B] = ["NOP", ""]; + this.optable[0x1C] = ["TRB", "$ADDR"]; + this.optable[0x1D] = ["ORA", "$ADDR,X"]; + this.optable[0x1E] = ["ASL", "$ADDR,X"]; + this.optable[0x1F] = ["BBR", "BIT,$BYTE,BYTE"]; + this.optable[0x20] = ["JSR", "$ADDR"]; + this.optable[0x21] = ["AND", "($BYTE,X)"]; + this.optable[0x22] = ["NOP", ""]; + this.optable[0x23] = ["NOP", ""]; + this.optable[0x24] = ["BIT", "$BYTE"]; + this.optable[0x25] = ["AND", "$BYTE"]; + this.optable[0x26] = ["ROL", "$BYTE"]; + this.optable[0x27] = ["RMB", "BIT,$BYTE"]; + this.optable[0x28] = ["PLP", ""]; + this.optable[0x29] = ["AND", "#BYTE"]; + this.optable[0x2A] = ["ROL", "A"]; + this.optable[0x2B] = ["NOP", ""]; + this.optable[0x2C] = ["BIT", "$ADDR"]; + this.optable[0x2D] = ["AND", "$ADDR"]; + this.optable[0x2E] = ["ROL", "$ADDR"]; + this.optable[0x2F] = ["BBR", "BIT,$BYTE,BYTE"]; + this.optable[0x30] = ["BMI", "BYTE"]; + this.optable[0x31] = ["AND", "($BYTE),Y"]; + this.optable[0x32] = ["AND", "($BYTE)"]; + this.optable[0x33] = ["NOP", ""]; + this.optable[0x34] = ["BIT", "$BYTE,X"]; + this.optable[0x35] = ["AND", "$BYTE,X"]; + this.optable[0x36] = ["ROL", "$BYTE,X"]; + this.optable[0x37] = ["RMB", "BIT,$BYTE"]; + this.optable[0x38] = ["SEC", ""]; + this.optable[0x39] = ["AND", "$ADDR,Y"]; + this.optable[0x3A] = ["DEA", "A"]; + this.optable[0x3B] = ["NOP", ""]; + this.optable[0x3C] = ["BIT", "$ADDR,X"]; + this.optable[0x3D] = ["AND", "$ADDR,X"]; + this.optable[0x3E] = ["ROL", "$ADDR,X"]; + this.optable[0x3F] = ["BBR", "BIT,$BYTE,BYTE"]; + this.optable[0x40] = ["RTI", ""]; + this.optable[0x41] = ["EOR", "($BYTE,X)"]; + this.optable[0x42] = ["NOP", ""]; + this.optable[0x43] = ["NOP", ""]; + this.optable[0x44] = ["NOP", ""]; + this.optable[0x45] = ["EOR", "$BYTE"]; + this.optable[0x46] = ["LSR", "$BYTE"]; + this.optable[0x47] = ["RMB", "BIT,$BYTE"]; + this.optable[0x48] = ["PHA", ""]; + this.optable[0x49] = ["EOR", "#BYTE"]; + this.optable[0x4A] = ["LSR", "A"]; + this.optable[0x4B] = ["NOP", ""]; + this.optable[0x4C] = ["JMP", "$ADDR"]; + this.optable[0x4D] = ["EOR", "$ADDR"]; + this.optable[0x4E] = ["LSR", "$ADDR"]; + this.optable[0x4F] = ["BBR", "BIT,$BYTE,BYTE"]; + this.optable[0x50] = ["BVC", "BYTE"]; + this.optable[0x51] = ["EOR", "($BYTE),Y"]; + this.optable[0x52] = ["EOR", "($BYTE)"]; + this.optable[0x53] = ["NOP", ""]; + this.optable[0x54] = ["NOP", ""]; + this.optable[0x55] = ["EOR", "$BYTE,X"]; + this.optable[0x56] = ["LSR", "$BYTE,X"]; + this.optable[0x57] = ["RMB", "BIT,$BYTE"]; + this.optable[0x58] = ["CLI", ""]; + this.optable[0x59] = ["EOR", "$ADDR,Y"]; + this.optable[0x5A] = ["PHY", ""]; + this.optable[0x5B] = ["NOP", ""]; + this.optable[0x5C] = ["NOP", ""]; + this.optable[0x5D] = ["EOR", "$ADDR,X"]; + this.optable[0x5E] = ["LSR", "$ADDR,X"]; + this.optable[0x5F] = ["BBR", "BIT,$BYTE,BYTE"]; + this.optable[0x60] = ["RTS", ""]; + this.optable[0x61] = ["ADC", "($BYTE,X)"]; + this.optable[0x62] = ["NOP", ""]; + this.optable[0x63] = ["NOP", ""]; + this.optable[0x64] = ["STZ", "$BYTE"]; + this.optable[0x65] = ["ADC", "$BYTE"]; + this.optable[0x66] = ["ROR", "$BYTE"]; + this.optable[0x67] = ["RMB", "BIT,$BYTE"]; + this.optable[0x68] = ["PLA", ""]; + this.optable[0x69] = ["ADC", "#BYTE"]; + this.optable[0x6A] = ["ROR", "A"]; + this.optable[0x6B] = ["NOP", ""]; + this.optable[0x6C] = ["JMP", "($ADDR)"]; + this.optable[0x6D] = ["ADC", "$ADDR"]; + this.optable[0x6E] = ["ROR", "$ADDR"]; + this.optable[0x6F] = ["BBR", "BIT,$BYTE,BYTE"]; + this.optable[0x70] = ["BVS", "BYTE"]; + this.optable[0x71] = ["ADC", "($BYTE),Y"]; + this.optable[0x72] = ["ADC", "($BYTE)"]; + this.optable[0x73] = ["NOP", ""]; + this.optable[0x74] = ["STZ", "$BYTE,X"]; + this.optable[0x75] = ["ADC", "$BYTE,X"]; + this.optable[0x76] = ["ROR", "$BYTE,X"]; + this.optable[0x77] = ["RMB", "BIT,$BYTE"]; + this.optable[0x78] = ["SEI", ""]; + this.optable[0x79] = ["ADC", "$ADDR,Y"]; + this.optable[0x7A] = ["PLY", ""]; + this.optable[0x7B] = ["NOP", ""]; + this.optable[0x7C] = ["JMP", "($ADDR,X)"]; + this.optable[0x7D] = ["ADC", "$ADDR,X"]; + this.optable[0x7E] = ["ROR", "$ADDR,X"]; + this.optable[0x7F] = ["BBR", "BIT,$BYTE,BYTE"]; + this.optable[0x80] = ["BRA", "BYTE"]; + this.optable[0x81] = ["STA", "($BYTE,X)"]; + this.optable[0x82] = ["NOP", ""]; + this.optable[0x83] = ["NOP", ""]; + this.optable[0x84] = ["STY", "$BYTE"]; + this.optable[0x85] = ["STA", "$BYTE"]; + this.optable[0x86] = ["STX", "$BYTE"]; + this.optable[0x87] = ["SMB", "BIT,$BYTE"]; + this.optable[0x88] = ["DEY", ""]; + this.optable[0x89] = ["BIT", "#BYTE"]; + this.optable[0x8A] = ["TXA", ""]; + this.optable[0x8B] = ["NOP", ""]; + this.optable[0x8C] = ["STY", "$ADDR"]; + this.optable[0x8D] = ["STA", "$ADDR"]; + this.optable[0x8E] = ["STX", "$ADDR"]; + this.optable[0x8F] = ["BBS", "BIT,$BYTE,BYTE"]; + this.optable[0x90] = ["BCC", "BYTE"]; + this.optable[0x91] = ["STA", "($BYTE),Y"]; + this.optable[0x92] = ["STA", "($BYTE)"]; + this.optable[0x93] = ["NOP", ""]; + this.optable[0x94] = ["STY", "$BYTE,X"]; + this.optable[0x95] = ["STA", "$BYTE,X"]; + this.optable[0x96] = ["STX", "$BYTE,Y"]; + this.optable[0x97] = ["SMB", "BIT,$BYTE"]; + this.optable[0x98] = ["TYA", ""]; + this.optable[0x99] = ["STA", "$ADDR,Y"]; + this.optable[0x9A] = ["TXS", ""]; + this.optable[0x9B] = ["NOP", ""]; + this.optable[0x9C] = ["STZ", "$ADDR"]; + this.optable[0x9D] = ["STA", "$ADDR,X"]; + this.optable[0x9E] = ["STZ", "$ADDR,X"]; + this.optable[0x9F] = ["BBS", "BIT,$BYTE,BYTE"]; + this.optable[0xA0] = ["LDY", "#BYTE"]; + this.optable[0xA1] = ["LDA", "($BYTE,X)"]; + this.optable[0xA2] = ["LDX", "#BYTE"]; + this.optable[0xA3] = ["NOP", ""]; + this.optable[0xA4] = ["LDY", "$BYTE"]; + this.optable[0xA5] = ["LDA", "$BYTE"]; + this.optable[0xA6] = ["LDX", "$BYTE"]; + this.optable[0xA7] = ["SMB", "BIT,$BYTE"]; + this.optable[0xA8] = ["TAY", ""]; + this.optable[0xA9] = ["LDA", "#BYTE"]; + this.optable[0xAA] = ["TAX", ""]; + this.optable[0xAB] = ["NOP", ""]; + this.optable[0xAC] = ["LDY", "$ADDR"]; + this.optable[0xAD] = ["LDA", "$ADDR"]; + this.optable[0xAE] = ["LDX", "$ADDR"]; + this.optable[0xAF] = ["BBS", "BIT,$BYTE,BYTE"]; + this.optable[0xB0] = ["BCS", "BYTE"]; + this.optable[0xB1] = ["LDA", "($BYTE),Y"]; + this.optable[0xB2] = ["LDA", "($BYTE)"]; + this.optable[0xB3] = ["NOP", ""]; + this.optable[0xB4] = ["LDY", "$BYTE,X"]; + this.optable[0xB5] = ["LDA", "$BYTE,X"]; + this.optable[0xB6] = ["LDX", "$BYTE,Y"]; + this.optable[0xB7] = ["SMB", "BIT,$BYTE"]; + this.optable[0xB8] = ["CLV", ""]; + this.optable[0xB9] = ["LDA", "$ADDR,Y"]; + this.optable[0xBA] = ["TSX", ""]; + this.optable[0xBB] = ["NOP", ""]; + this.optable[0xBC] = ["LDY", "$ADDR,X"]; + this.optable[0xBD] = ["LDA", "$ADDR,X"]; + this.optable[0xBE] = ["LDX", "$ADDR,Y"]; + this.optable[0xBF] = ["BBS", "BIT,$BYTE,BYTE"]; + this.optable[0xC0] = ["CPY", "#BYTE"]; + this.optable[0xC1] = ["CMP", "($BYTE,X)"]; + this.optable[0xC2] = ["NOP", ""]; + this.optable[0xC3] = ["NOP", ""]; + this.optable[0xC4] = ["CPY", "$BYTE"]; + this.optable[0xC5] = ["CMP", "$BYTE"]; + this.optable[0xC6] = ["DEC", "$BYTE"]; + this.optable[0xC7] = ["SMB", "BIT,$BYTE"]; + this.optable[0xC8] = ["INY", ""]; + this.optable[0xC9] = ["CMP", "#BYTE"]; + this.optable[0xCA] = ["DEX", ""]; + this.optable[0xCB] = ["WAI", ""]; + this.optable[0xCC] = ["CPY", "$ADDR"]; + this.optable[0xCD] = ["CMP", "$ADDR"]; + this.optable[0xCE] = ["DEC", "$ADDR"]; + this.optable[0xCF] = ["BBS", "BIT,$BYTE,BYTE"]; + this.optable[0xD0] = ["BNE", "BYTE"]; + this.optable[0xD1] = ["CMP", "($BYTE),Y"]; + this.optable[0xD2] = ["CMP", "($BYTE)"]; + this.optable[0xD3] = ["NOP", ""]; + this.optable[0xD4] = ["NOP", ""]; + this.optable[0xD5] = ["CMP", "$BYTE,X"]; + this.optable[0xD6] = ["DEC", "$BYTE,X"]; + this.optable[0xD7] = ["SMB", "BIT,$BYTE"]; + this.optable[0xD8] = ["CLD", ""]; + this.optable[0xD9] = ["CMP", "$ADDR,Y"]; + this.optable[0xDA] = ["PHX", ""]; + this.optable[0xDB] = ["STP", ""]; + this.optable[0xDC] = ["NOP", ""]; + this.optable[0xDD] = ["CMP", "$ADDR,X"]; + this.optable[0xDE] = ["DEC", "$ADDR,X"]; + this.optable[0xDF] = ["BBS", "BIT,$BYTE,BYTE"]; + this.optable[0xE0] = ["CPX", "#BYTE"]; + this.optable[0xE1] = ["SBC", "($BYTE,X)"]; + this.optable[0xE2] = ["NOP", ""]; + this.optable[0xE3] = ["NOP", ""]; + this.optable[0xE4] = ["CPX", "$BYTE"]; + this.optable[0xE5] = ["SBC", "$BYTE"]; + this.optable[0xE6] = ["INC", "$BYTE"]; + this.optable[0xE7] = ["SMB", "BIT,$BYTE"]; + this.optable[0xE8] = ["INX", ""]; + this.optable[0xE9] = ["SBC", "#BYTE"]; + this.optable[0xEA] = ["NOP", ""]; + this.optable[0xEB] = ["NOP", ""]; + this.optable[0xEC] = ["CPX", "$ADDR"]; + this.optable[0xED] = ["SBC", "$ADDR"]; + this.optable[0xEE] = ["INC", "$ADDR"]; + this.optable[0xEF] = ["BBS", "BIT,$BYTE,BYTE"]; + this.optable[0xF0] = ["BEQ", "BYTE"]; + this.optable[0xF1] = ["SBC", "($BYTE),Y"]; + this.optable[0xF2] = ["SBC", "($BYTE)"]; + this.optable[0xF3] = ["NOP", ""]; + this.optable[0xF4] = ["NOP", ""]; + this.optable[0xF5] = ["SBC", "$BYTE,X"]; + this.optable[0xF6] = ["INC", "$BYTE,X"]; + this.optable[0xF7] = ["SMB", "BIT,$BYTE"]; + this.optable[0xF8] = ["SED", ""]; + this.optable[0xF9] = ["SBC", "$ADDR,Y"]; + this.optable[0xFA] = ["PLX", ""]; + this.optable[0xFB] = ["NOP", ""]; + this.optable[0xFC] = ["NOP", ""]; + this.optable[0xFD] = ["SBC", "$ADDR,X"]; + this.optable[0xFE] = ["INC", "$ADDR,X"]; + this.optable[0xFF] = ["BBS", "BIT,$BYTE,BYTE"]; +} \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..7db841a --- /dev/null +++ b/index.html @@ -0,0 +1,23 @@ + + + + + JavaScript 65C02 CPU Emulator + + + + +
+ +

Check the console for the cool output!

+
LOADING
+

Output:

+
+ + + + + + + + diff --git a/main.js b/main.js new file mode 100644 index 0000000..1d4533b --- /dev/null +++ b/main.js @@ -0,0 +1,130 @@ +// A cross-browser requestAnimationFrame +// See https://hacks.mozilla.org/2011/08/animating-with-javascript-from-setinterval-to-requestanimationframe/ +var requestAnimFrame = (function(){ + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function(callback){ + window.setTimeout(callback, 1000 / 60); + }; +})(); + + +var mem64k = new DebugMemController(); +var cpu = Cpu(mem64k); +var dis = new Disassembler(mem64k); +var mon = new Monitor(cpu); + +var intervalId; // id for interval timer + +var print_flag = true; +var old_irq_pin; +var old_nmi_pin; + +var statusElem; + + +// The main game loop +var lastTime; +function main() { + console.log("Inside main"); + var now = Date.now(); + var dt = (now - lastTime) / 1000.0; + + runCPU(dt); + render(); + + lastTime = now; + requestAnimFrame(main); +} + +function init() { + console.log("Inside init"); + // Create the canvas + var canvas = document.createElement("canvas"); + var ctx = canvas.getContext("2d"); + canvas.width = 512; + canvas.height = 480; + document.getElementById("screen").appendChild(canvas); + + statusElem = document.getElementById("status"); + + mem64k.loadData(0x0000, resources.get('test6502')); + cpu.reset(); + + old_irq_pin = (mem64k.getData(0xBFFC) & 0x01) > 0; + old_nmi_pin = ((mem64k.getData(0xBFFC) & 0x02) >> 1) > 0; + + cpu.setPC(0x0400); + + console.log("Press keys to operate CPU:"); + console.log("0) reset"); + console.log("1) NMI"); + console.log("2) IRQ"); + console.log("3) end simulation"); + + print_flag = false; + + statusElem.innerHTML = "Running"; + lastTime = Date.now; + main(); +} + +function runCPU(dt) { + var numCycles = dt*1022727; + if (print_flag) { + console.log(mon.getStatusStr()); + dis.disassemble(cpu.regPC()); + } + cpu_start = Date.now(); + cpu.run(numCycles); + cpu_dt = (Date.now() - cpu_start) / 1000.0; + console.log('CPU time = ' + cpu_dt); + + var irq_pin = (mem64k.getData(0xBFFC) & 0x01) > 0; + var nmi_pin = ((mem64k.getData(0xBFFC) & 0x02) >> 1) > 0; + + if (irq_pin != old_irq_pin) { + console.log("Changed IRQ pin: " + old_irq_pin + " --> " + irq_pin); + old_irq_pin = irq_pin; + cpu.irq_pin = irq_pin; + } + if (nmi_pin != old_nmi_pin) { + console.log("Changed NMI pin: " + old_nmi_pin + " --> " + nmi_pin); + old_nmi_pin = nmi_pin; + cpu.nmi_pin = nmi_pin; + } +} + +function render() { + // Nothing yet +} + +document.onkeypress = function (e) { + e = e || window.event; + if (e.keyCode === 51) { + clearInterval(intervalId); + console.log("STOPPED"); + statusElem.innerHTML = "STOPPED"; + } + else if (e.keyCode === 48) { cpu.reset(); statusElem.innerHTML = "Running (" + intervalId + "): ";} + else if(e.keyCode === 49) { cpu.nmi(); statusElem.innerHTML += "1 ";} + else if(e.keyCode === 50) { cpu.irq(); statusElem.innerHTML += "2 ";} + else if(e.keyCode >= 0 && e.keyCode <= 255) { + mem64k.setData(mem64k.in_port, e.keyCode); + } +}; + +$(document).ready(function() { + console.log("Document ready"); + $.when( + resources.loadBinary('/tests/6502_functional_test.bin', 'test6502') + + ).then(function() { + init(); + }, function() { + console.log("Resource not available"); + }) ; +}); diff --git a/memory.js b/memory.js new file mode 100644 index 0000000..ea52bfc --- /dev/null +++ b/memory.js @@ -0,0 +1,55 @@ + +function DebugMemController() { + this.in_port = 0xBFF0; + this.out_port = 0xBFF0; + + // Create the memory bank and fill it with 0's + this.bank0 = new Array(65536); + var i = 0; + while (i < 65536) { this.bank0[i] = 0x00; i++; } +} + +// Return an 8-bit chuck of data at a given address +DebugMemController.prototype.getData = function(addr) { + return this.bank0[addr]; +}; + +// Return a 16-bit chuck of data at a given address +DebugMemController.prototype.getAddr = function(addr) { + return this.bank0[addr] | this.bank0[(addr+1) & 0xFFFF] << 8; +}; + +// Assign an 8-bit value to a given address +DebugMemController.prototype.setData = function(addr, value) { + this.bank0[addr] = value; + if (addr === this.out_port) { + if (value !== 10 && value !== 13) { + document.getElementById("output").innerHTML += String.fromCharCode(value); + } else { + document.getElementById("output").innerHTML += '
'; + } + } +}; + +// Load a block of data in memory starting at addr +DebugMemController.prototype.loadData = function(addr, data) { + var end_data = data.length; + if (end_data > (65536 - addr)) { + end_data = 65536 - addr; + } + + // Copy the data into bank 0 + var isrc = 0; + while(isrc < end_data) { this.bank0[addr+isrc] = data[isrc]; isrc++; } +}; + +// Return an array specified number of bytes starting at addr +DebugMemController.prototype.dumpData = function(addr, nbytes) { + var end_addr = addr + nbytes; + if (end_addr > 65536) { + // truncate the data + end_addr = 65536; + } + + return this.bank0.slice(addr, end_addr); +}; \ No newline at end of file diff --git a/monitor.js b/monitor.js new file mode 100644 index 0000000..66a08c3 --- /dev/null +++ b/monitor.js @@ -0,0 +1,28 @@ + +function Monitor(cpu) { + this.cpu = cpu; + + this.getStatusStr = function() { + var status; + status = "PC=" + this.cpu.regPC().toString(16); + status += " A=" + this.cpu.regA().toString(16); + status += " X=" + this.cpu.regX().toString(16); + status += " Y=" + this.cpu.regY().toString(16); + status += " SP=01" + this.cpu.regSP().toString(16); + status += " SR="; + status = status.toUpperCase(); + + if (this.cpu.regSR().N) { status += "N"; } else { status += "n"; } + if (this.cpu.regSR().V) { status += "V1"; } else { status += "v1"; } + if (this.cpu.regSR().B) { status += "B"; } else { status += "b"; } + if (this.cpu.regSR().D) { status += "D"; } else { status += "d"; } + if (this.cpu.regSR().I) { status += "I"; } else { status += "i"; } + if (this.cpu.regSR().Z) { status += "Z"; } else { status += "z"; } + if (this.cpu.regSR().C) { status += "C"; } else { status += "c"; } + + status += " (" + this.cpu.regSR().get().toString(16).toUpperCase() + ")"; + status += ", cycle = " + this.cpu.cycle().toString(); + + return status; + }; +} \ No newline at end of file diff --git a/resources.js b/resources.js new file mode 100644 index 0000000..8cdd5b8 --- /dev/null +++ b/resources.js @@ -0,0 +1,65 @@ +// use this transport for "binary" data type +$.ajaxTransport("+binary", function(options, originalOptions, jqXHR){ + // check for conditions and support for blob / arraybuffer response type + if (window.FormData && ((options.dataType && (options.dataType == 'binary')) || (options.data && ((window.ArrayBuffer && options.data instanceof ArrayBuffer) || (window.Blob && options.data instanceof Blob))))) + { + return { + // create new XMLHttpRequest + send: function(_, callback){ + // setup all variables + var xhr = new XMLHttpRequest(), + url = options.url, + type = options.type, + // blob or arraybuffer. Default is blob + dataType = options.responseType || "blob", + data = options.data || null; + + xhr.addEventListener('load', function(){ + var data = {}; + data[options.dataType] = xhr.response; + // make callback and send data + callback(xhr.status, xhr.statusText, data, xhr.getAllResponseHeaders()); + }); + + xhr.open(type, url, true); + xhr.responseType = dataType; + xhr.send(data); + }, + abort: function(){ + jqXHR.abort(); + } + }; + } +}); + +(function() { + var resourceCache = []; + + function loadBinary(url, name) { + + return $.ajax({ + url: url, + type: "GET", + dataType: 'binary', + responseType:'arraybuffer', + processData: false, + + success: function(result){ + // create unsigned Int array and convert this array into blob + var arrayBufferView = new Uint8Array(result ); + console.log("Resource loaded: " + url); + resourceCache[name] = arrayBufferView; + return arrayBufferView; + } + }); + } + + function get(name) { + return resourceCache[name]; + } + + window.resources = { + loadBinary: loadBinary, + get: get + }; +})(); \ No newline at end of file