/** * @fileoverview This file implements the C1Pjs 6502 CPU component. * @author Jeff Parsons * @version 1.0 * Created 2012-Jun-15 * * Copyright © 2012-2016 Jeff Parsons * * This file is part of C1Pjs, which is part of the JavaScript Machines Project (aka JSMachines) * at and . * * C1Pjs is free software: you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * C1Pjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with C1Pjs. If not, * see . * * You are required to include the above copyright notice in every source code file of every * copy or modified version of this work, and to display that copyright notice on every screen * that loads or runs any version of this software (see C1PComputer.COPYRIGHT). * * Some C1Pjs files also attempt to load external resource files, such as character-image files, * ROM files, and disk image files. Those external resource files are not considered part of the * C1Pjs program for purposes of the GNU General Public License, and the author does not claim * any copyright as to their contents. */ "use strict"; if (NODE) { var str = require("../../shared/lib/strlib"); var usr = require("../../shared/lib/usrlib"); var web = require("../../shared/lib/weblib"); var Component = require("../../shared/lib/component"); } /** * C1PCPU(parmsCPU) * * The C1PCPU object has one component-specific initialization property: * * autoStart: true to automatically start, false to not, or null (default) * to make the autoStart decision based on whether or not a Debugger is * installed (if there's no Debugger AND no "Run" button, then auto-start, * otherwise don't) * * It is hard-coded to simulate a 6502 microprocessor, but it also contains * hooks into other components for communication with the outside world (eg, * Panel and Debugger components). This is a logical simulation, not a physical * simulation, and performance is important, so we take lots of liberties; any * idiosyncrasies of actual 6502 hardware may not be simulated here, unless it * affects the accuracy of the simulation when running actual 6502 software. * * @constructor * @extends Component */ function C1PCPU(parmsCPU) { Component.call(this, "C1PCPU", parmsCPU); this.clearRegs(); this.flags.fPowered = false; this.flags.fRunning = false; this.fAutoStart = parmsCPU["autoStart"]; /* * speed is a number from 0 to 2, where 0 means run as close to 1Mhz as possible, * 1 means run at the fastest safe speed, and 2 means run at maximum speed. * * It's updated via the setSpeed() function, which the Debugger's "option" command * uses to adjust the virtual speed (eg, "o slow", "o fast"). There may also * be a button present to control the speed as well (using the "setSpeed" binding). */ this.SPEED_SLOW = 0; // see this.mhzSlow this.SPEED_FAST = 1; // see this.mhzFast this.SPEED_MAX = 2; this.speed = this.SPEED_SLOW; this.nCyclesPerSecond = 1000000; /* * Additional values that control the overall speed of the simulated hardware, * and the frequency at which various updates should occur. There are no UI * mechanisms for tweaking these values (yet). * * NOTE: Use of the term "second" below refers to a virtual CPU second, consisting of * 1 million simulated cycles. The values below are used to divide those 1 million * cycles into intervals of "work", and as long we are limiting the simulation to 1Mhz * per ACTUAL second, then 1 virtual second == 1 real second. * * However, if the setSpeed() function is used to lift the 1Mhz limit, then 1 virtual * second may become much shorter, which is why you may briefly notice the video and/or * status (control panel) updates occurring more frequently. To compensate, calcCycles() * will automatically scale these values if a recent speed recalculation reveals that * we're running significantly faster than 1Mhz. */ this.nYieldsPerSecond = 30; this.nVideoUpdatesPerSecond = 30; this.nStatusUpdatesPerSecond = 5; this.mhzSlow = 1; this.mhzFast = 8; this.aSpeeds = ["Slow", "Fast", "Max"]; this.aSpeedDescs = ["(" + this.mhzSlow + "Mhz)", "(up to " + this.mhzFast + "Mhz)", "(unlimited)"]; /* * Lists of notification handlers: aReadNotify and aWriteNotify are lists (ie, Arrays) * of 4-element sub-arrays that, in turn, contain: * * [0]: starting address of memory range to monitor * [1]: ending address of memory range to monitor (inclusive) * [2]: registered component * [3]: registered function to call for every read/write from/to memory in that range * * The virtual Serial Port and virtual Keyboard components use these handlers to trap * references to their respective memory-based "ports". Also, the ROM component uses it * to "repair" any writes to its address range, since memory is one big array, and arrays * don't support "write-only" regions. * * NOTE: the Video component does NOT use notification handlers, because video memory * is written (and occasionally read) far too frequently for that to be efficient. We * just let the CPU pound on it like any other chunk of memory, and then make periodic * calls directly to the Video component to refresh all portions of the video buffer * that have changed since the last refresh. See displayVideo() for more details. * * WARNING: Write notifications currently do not catch STACK writes (ie, BRK, JSR, PHA and * PHP instructions), because I simply haven't added the necessary code. Besides, JSR is * one of the most-executed instructions, so I'd rather not slow it down. Note that this * STACK write limitation affects both the CPU's write-notification handlers AND the Debugger's * write breakpoints. */ this.aReadNotify = []; this.aWriteNotify = []; /* * To speed up the processing of read and write notification handlers, we keep track of * lower and upper address bounds for each set. These variables maintain those bounds. * They are initialized to values outside the accessible range of addresses. */ this.addrReadLower = 0x10000; this.addrReadUpper = 0x0; this.addrWriteLower = 0x10000; this.addrWriteUpper = 0x0; /* * Processor status register (P) flag masks */ this.BIT_PN = 0x80; // N = sign this.BIT_PV = 0x40; // V = overflow this.BIT_PB = 0x10; // B = break this.BIT_PD = 0x08; // D = decimal this.BIT_PI = 0x04; // I = interrupt this.BIT_PZ = 0x02; // Z = zero this.BIT_PC = 0x01; // C = carry // this.VECTOR_NMI = 0xfffa; this.VECTOR_RESET = 0xfffc; // this.VECTOR_IRQ = 0xfffe; /* * Popular opcodes */ this.OP_JSR = 0x20; /* * opSim operation codes */ this.OP_SIM = 0x02; this.SIMOP_HLT = 0x00; this.SIMOP_MSG = 0x01; /* * This 256-entry array of opcode functions is at the heart of the CPU engine: step(n). * * It might be worth trying a switch() statement instead, to see how the performance compares, * but I suspect that will vary quite a bit across JavaScript engines; for now, I'm putting my * money on array lookup. */ this.aOpcodeFuncs = [ this.opBRK, // 0x00 this.opORAindx, // 0x01 this.opSim, // 0x02 this.opUndefined, // 0x03 this.opUndefined, // 0x04 this.opORAzp, // 0x05 this.opASLzp, // 0x06 this.opUndefined, // 0x07 this.opPHP, // 0x08 this.opORAimm, // 0x09 this.opASLacc, // 0x0a this.opUndefined, // 0x0b this.opUndefined, // 0x0c this.opORAabs, // 0x0d this.opASLabs, // 0x0e this.opUndefined, // 0x0f this.opBPL, // 0x10 this.opORAindy, // 0x11 this.opUndefined, // 0x12 this.opUndefined, // 0x13 this.opUndefined, // 0x14 this.opORAzpx, // 0x15 this.opASLzpx, // 0x16 this.opUndefined, // 0x17 this.opCLC, // 0x18 this.opORAabsy, // 0x19 this.opUndefined, // 0x1a this.opUndefined, // 0x1b this.opUndefined, // 0x1c this.opORAabsx, // 0x1d this.opASLabsx, // 0x1e this.opUndefined, // 0x1f this.opJSRabs, // 0x20 this.opANDindx, // 0x21 this.opUndefined, // 0x22 this.opUndefined, // 0x23 this.opBITzp, // 0x24 this.opANDzp, // 0x25 this.opROLzp, // 0x26 this.opUndefined, // 0x27 this.opPLP, // 0x28 this.opANDimm, // 0x29 this.opROLacc, // 0x2a this.opUndefined, // 0x2b this.opBITabs, // 0x2c this.opANDabs, // 0x2d this.opROLabs, // 0x2e this.opUndefined, // 0x2f this.opBMI, // 0x30 this.opANDindy, // 0x31 this.opUndefined, // 0x32 this.opUndefined, // 0x33 this.opUndefined, // 0x34 this.opANDzpx, // 0x35 this.opROLzpx, // 0x36 this.opUndefined, // 0x37 this.opSEC, // 0x38 this.opANDabsy, // 0x39 this.opUndefined, // 0x3a this.opUndefined, // 0x3b this.opUndefined, // 0x3c this.opANDabsx, // 0x3d this.opROLabsx, // 0x3e this.opUndefined, // 0x3f this.opRTI, // 0x40 this.opEORindx, // 0x41 this.opUndefined, // 0x42 this.opUndefined, // 0x43 this.opUndefined, // 0x44 this.opEORzp, // 0x45 this.opLSRzp, // 0x46 this.opUndefined, // 0x47 this.opPHA, // 0x48 this.opEORimm, // 0x49 this.opLSRacc, // 0x4a this.opUndefined, // 0x4b this.opJMPimm16, // 0x4c this.opEORabs, // 0x4d this.opLSRabs, // 0x4e this.opUndefined, // 0x4f this.opBVC, // 0x50 this.opEORindy, // 0x51 this.opUndefined, // 0x52 this.opUndefined, // 0x53 this.opUndefined, // 0x54 this.opEORzpx, // 0x55 this.opLSRzpx, // 0x56 this.opUndefined, // 0x57 this.opCLI, // 0x58 this.opEORabsy, // 0x59 this.opUndefined, // 0x5a this.opUndefined, // 0x5b this.opUndefined, // 0x5c this.opEORabsx, // 0x5d this.opLSRabsx, // 0x5e this.opUndefined, // 0x5f this.opRTS, // 0x60 this.opADCindx, // 0x61 this.opUndefined, // 0x62 this.opUndefined, // 0x63 this.opUndefined, // 0x64 this.opADCzp, // 0x65 this.opRORzp, // 0x66 this.opUndefined, // 0x67 this.opPLA, // 0x68 this.opADCimm, // 0x69 this.opRORacc, // 0x6a this.opUndefined, // 0x6b this.opJMPabs16, // 0x6c this.opADCabs, // 0x6d this.opRORabs, // 0x6e this.opUndefined, // 0x6f this.opBVS, // 0x70 this.opADCindy, // 0x71 this.opUndefined, // 0x72 this.opUndefined, // 0x73 this.opUndefined, // 0x74 this.opADCzpx, // 0x75 this.opRORzpx, // 0x76 this.opUndefined, // 0x77 this.opSEI, // 0x78 this.opADCabsy, // 0x79 this.opUndefined, // 0x7a this.opUndefined, // 0x7b this.opUndefined, // 0x7c this.opADCabsx, // 0x7d this.opRORabsx, // 0x7e this.opUndefined, // 0x7f this.opUndefined, // 0x80 this.opSTAindx, // 0x81 this.opUndefined, // 0x82 this.opUndefined, // 0x83 this.opSTYzp, // 0x84 this.opSTAzp, // 0x85 this.opSTXzp, // 0x86 this.opUndefined, // 0x87 this.opDEY, // 0x88 this.opUndefined, // 0x89 this.opTXA, // 0x8a this.opUndefined, // 0x8b this.opSTYabs, // 0x8c this.opSTAabs, // 0x8d this.opSTXabs, // 0x8e this.opUndefined, // 0x8f this.opBCC, // 0x90 this.opSTAindy, // 0x91 this.opUndefined, // 0x92 this.opUndefined, // 0x93 this.opSTYzpx, // 0x94 this.opSTAzpx, // 0x95 this.opSTXzpy, // 0x96 this.opUndefined, // 0x97 this.opTYA, // 0x98 this.opSTAabsy, // 0x99 this.opTXS, // 0x9a this.opUndefined, // 0x9b this.opUndefined, // 0x9c this.opSTAabsx, // 0x9d this.opUndefined, // 0x9e this.opUndefined, // 0x9f this.opLDYimm, // 0xa0 this.opLDAindx, // 0xa1 this.opLDXimm, // 0xa2 this.opUndefined, // 0xa3 this.opLDYzp, // 0xa4 this.opLDAzp, // 0xa5 this.opLDXzp, // 0xa6 this.opUndefined, // 0xa7 this.opTAY, // 0xa8 this.opLDAimm, // 0xa9 this.opTAX, // 0xaa this.opUndefined, // 0xab this.opLDYabs, // 0xac this.opLDAabs, // 0xad this.opLDXabs, // 0xae this.opUndefined, // 0xaf this.opBCS, // 0xb0 this.opLDAindy, // 0xb1 this.opUndefined, // 0xb2 this.opUndefined, // 0xb3 this.opLDYzpx, // 0xb4 this.opLDAzpx, // 0xb5 this.opLDXzpy, // 0xb6 this.opUndefined, // 0xb7 this.opCLV, // 0xb8 this.opLDAabsy, // 0xb9 this.opTSX, // 0xba this.opUndefined, // 0xbb this.opLDYabsx, // 0xbc this.opLDAabsx, // 0xbd this.opLDXabsy, // 0xbe this.opUndefined, // 0xbf this.opCPYimm, // 0xc0 this.opCMPindx, // 0xc1 this.opUndefined, // 0xc2 this.opUndefined, // 0xc3 this.opCPYzp, // 0xc4 this.opCMPzp, // 0xc5 this.opDECzp, // 0xc6 this.opUndefined, // 0xc7 this.opINY, // 0xc8 this.opCMPimm, // 0xc9 this.opDEX, // 0xca this.opUndefined, // 0xcb this.opCPYabs, // 0xcc this.opCMPabs, // 0xcd this.opDECabs, // 0xce this.opUndefined, // 0xcf this.opBNE, // 0xd0 this.opCMPindy, // 0xd1 this.opUndefined, // 0xd2 this.opUndefined, // 0xd3 this.opUndefined, // 0xd4 this.opCMPzpx, // 0xd5 this.opDECzpx, // 0xd6 this.opUndefined, // 0xd7 this.opCLD, // 0xd8 this.opCMPabsy, // 0xd9 this.opUndefined, // 0xda this.opUndefined, // 0xdb this.opUndefined, // 0xdc this.opCMPabsx, // 0xdd this.opDECabsx, // 0xde this.opUndefined, // 0xdf this.opCPXimm, // 0xe0 this.opSBCindx, // 0xe1 this.opUndefined, // 0xe2 this.opUndefined, // 0xe3 this.opCPXzp, // 0xe4 this.opSBCzp, // 0xe5 this.opINCzp, // 0xe6 this.opUndefined, // 0xe7 this.opINX, // 0xe8 this.opSBCimm, // 0xe9 this.opNOP, // 0xea this.opUndefined, // 0xeb this.opCPXabs, // 0xec this.opSBCabs, // 0xed this.opINCabs, // 0xee this.opUndefined, // 0xef this.opBEQ, // 0xf0 this.opSBCindy, // 0xf1 this.opUndefined, // 0xf2 this.opUndefined, // 0xf3 this.opUndefined, // 0xf4 this.opSBCzpx, // 0xf5 this.opINCzpx, // 0xf6 this.opUndefined, // 0xf7 this.opSED, // 0xf8 this.opSBCabsy, // 0xf9 this.opUndefined, // 0xfa this.opUndefined, // 0xfb this.opUndefined, // 0xfc this.opSBCabsx, // 0xfd this.opINCabsx, // 0xfe this.opUndefined // 0xff ]; /* * This is a 256-byte array of cycle counts, indexed by opcode. * Obviously, true cycle counts are a bit more complicated, but this * gets us most of the way to an authentic-feeling simulation. * * NOTE: BCD functions now account for an extra cycle, and branches * now account for an extra cycle whenever the branch is taken. * However, branches still don't add an extra cycle whenever the branch * crosses a page boundary. * * The other gaping hole in our cycle-counting is accounting for all * page-boundary penalties. Ideally, that's just a matter of checking * MODE_ABSX, MODE_ABSY, and MODE_INDY instructions for EA straddling * a page boundary--but is it more complicated than that? What if the * criteria is not the final EA, but whether the pre-indexing and * post-indexing EAs are in different pages? I also need to confirm * whether any other situations merit checking (eg, when a 2 or 3-byte * instruction straddles a page boundary). */ this.aOpcodeCycles = [ 7,6,0,0,0,3,5,0,3,2,2,0,0,4,6,0, 2,5,0,0,0,4,6,0,2,4,0,0,0,4,7,0, 3,6,0,0,3,3,5,0,4,2,2,0,4,4,6,0, 2,5,0,0,0,4,6,0,2,4,0,0,0,4,7,0, 6,6,0,0,0,3,5,0,3,2,2,0,3,4,6,0, 2,5,0,0,0,4,6,0,2,4,0,0,0,4,7,0, 6,6,0,0,0,3,5,0,4,2,2,0,5,4,6,0, 2,5,0,0,0,4,6,0,2,4,0,0,0,4,7,0, 0,6,0,0,3,3,3,0,2,0,2,0,4,4,4,0, 2,5,0,0,4,4,4,0,2,4,2,0,0,4,0,0, 2,6,2,0,3,3,3,0,2,2,2,0,4,4,4,0, 2,5,0,0,4,4,4,0,2,4,2,0,4,4,4,0, 2,6,0,0,3,3,5,0,2,2,2,0,4,4,6,0, 2,5,0,0,0,4,6,0,2,4,0,0,0,4,7,0, 2,6,0,0,3,3,5,0,2,2,2,0,4,4,6,0, 2,5,0,0,0,4,6,0,2,4,0,0,0,4,7,0 ]; } Component.subclass(C1PCPU); /** * @this {C1PCPU} * @param {boolean|undefined} fPowerOn is true for the initial reset, so that if the Debugger isn't * loaded, we can elect to start running. Under any other circumstances (such as whenever Computer.reset() * is called), "auto-run" is not a good idea, and can actually introduce bugs (eg, multiple run() timers). * * Note that we follow the same model here as other selected reset() handlers; for example, Video.reset() * accepts an fPowerOn parameter to govern what's initially displayed on the video screen. */ C1PCPU.prototype.reset = function(fPowerOn) { if (this.flags.fRunning) { this.halt(); } this.clearRegs(); this.regPC = this.getWord(this.VECTOR_RESET); this.clearError(); // clear any fatal error/exception /* * If there's a Debugger, notify Debugger.reset(); otherwise, start running */ if (DEBUGGER && this.dbg) { this.dbg.reset(); } else if (fPowerOn) { if (this.fAutoStart === true || this.fAutoStart === null && (!DEBUGGER || !this.dbg) && this.bindings["run"] === undefined) { this.run(); // start running automatically on the initial power-up, assuming there's no Debugger } } }; /** * @this {C1PCPU} * @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea") * @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "run") * @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement) * @param {string} [sValue] optional data value * @return {boolean} true if binding was successful, false if unrecognized binding request */ C1PCPU.prototype.setBinding = function(sHTMLType, sBinding, control, sValue) { var fBound = false; switch(sBinding) { case "run": this.bindings[sBinding] = control; control.onclick = function(cpu) { return function() { if (!cpu.flags.fRunning) { cpu.run(); } else { cpu.halt(); } }; }(this); fBound = true; break; case "A": case "X": case "Y": case "S": case "PC": case "C": case "Z": case "I": case "D": case "B": case "V": case "N": case "speed": this.bindings[sBinding] = control; fBound = true; break; case "setSpeed": this.bindings[sBinding] = control; control.onclick = function(cpu) { return function() { var speed = (cpu.speed >= cpu.SPEED_MAX? cpu.SPEED_SLOW : cpu.speed+1); cpu.setSpeed(speed, true); }; }(this); fBound = true; break; default: break; } return fBound; }; /** * @this {C1PCPU} * @param {Array} abMemory * @param {number} start * @param {number} end */ C1PCPU.prototype.setBuffer = function(abMemory, start, end) { this.abMem = abMemory; this.offMem = start; this.cbMem = end - start + 1; this.offLimit = this.offMem + this.cbMem; if (this.offMem) { /* * It's not that we couldn't support an address buffer that starts at a non-zero offset; * we simply have lots of code (eg, all the opcode handlers) that assumes offMem is zero, * and therefore that abMem can be indexed by any of the CPU registers without adding offMem. * All that code would have to be changed (at a slight performance penalty) if we couldn't * make this assumption. */ Component.error("unsupported CPU address buffer offset (" + this.offMem + ")"); return; } this.setReady(); }; /** * @this {C1PCPU} * @param {boolean} fOn * @param {C1PComputer} cmp */ C1PCPU.prototype.setPower = function(fOn, cmp) { if (fOn && !this.flags.fPowered) { this.cmp = cmp; /* * Attach the Debugger, if any, to the CPU, so that the CPU can periodically * notify it as needed (when the CPU starts, stops, and executes instructions) */ if (DEBUGGER) { this.dbg = cmp.getComponentByType("debugger"); if (this.dbg) this.dbg.init(); } /* * Attach the Video device to the CPU, so that the CPU can periodically update * the video display via displayVideo(), as cycles permit. */ var video = cmp.getComponentByType("video"); if (video) { this.displayVideo = function(v) { return function() { v.updateScreen(); }; }(video); this.setFocus = function(v) { return function() { v.setFocus(); }; }(video); } this.flags.fPowered = true; this.reset(true); this.update(); } }; /** * Add a memory read-notification handler to the CPU's list of such handlers. * * @this {C1PCPU} * @param {number} start address * @param {number} end address * @param {Component} component * @param {function(number,number)} fn is called with the EA and PC values at the time of the write */ C1PCPU.prototype.addReadNotify = function(start, end, component, fn) { if (this.findNotify(this.aReadNotify, start, end, component, fn) < 0) { if (this.addrReadLower > start) this.addrReadLower = start; if (this.addrReadUpper < end) this.addrReadUpper = end; this.aReadNotify.push([start, end, component, fn]); if (DEBUG) this.log("addReadNotify(" + str.toHexWord(start) + "," + str.toHexWord(end) + "," + component.id + "): new read range: " + str.toHexWord(this.addrReadLower) + "-" + str.toHexWord(this.addrReadUpper)); } }; /** * @this {C1PCPU} * @param {number} addrRead is the EA value at the time of the read * @param {number} [addrFrom] is the PC value at the time of the read; * this will be undefined for read notifications triggered by assorted Debugger commands, * so all handlers should be prepared for that as well. */ C1PCPU.prototype.checkReadNotify = function(addrRead, addrFrom) { for (var i=0; i < this.aReadNotify.length; i++) { if (addrRead >= this.aReadNotify[i][0] && addrRead <= this.aReadNotify[i][1]) { this.aReadNotify[i][3].call(this.aReadNotify[i][2], addrRead, addrFrom); } } }; /** * Remove a memory read-notification handler from the CPU's list of such handlers. * * @this {C1PCPU} * @param {number} start address * @param {number} end address * @param {Component} component * @param {function(number,number)} fn of previously added handler * @return {boolean} true if remove was successful, false if the handler was not found */ C1PCPU.prototype.removeReadNotify = function(start, end, component, fn) { var aBounds = this.removeNotify(this.aReadNotify, start, end, component, fn); if (aBounds.length == 4) { this.addrReadLower = aBounds[2]; this.addrReadUpper = aBounds[3]; if (DEBUG) this.log("removeReadNotify(" + str.toHexWord(start) + "," + str.toHexWord(end) + "," + component.id + "): new read range: " + str.toHexWord(this.addrReadLower) + "-" + str.toHexWord(this.addrReadUpper)); return true; } return false; }; /** * Add a memory write-notification handler to the CPU's list of such handlers. * * @this {C1PCPU} * @param {number} start address * @param {number} end address * @param {Component} component * @param {function(number,number)} fn is called with the EA and PC values at the time of the write */ C1PCPU.prototype.addWriteNotify = function(start, end, component, fn) { if (this.findNotify(this.aWriteNotify, start, end, component, fn) < 0) { if (this.addrWriteLower > start) this.addrWriteLower = start; if (this.addrWriteUpper < end) this.addrWriteUpper = end; this.aWriteNotify.push([start, end, component, fn]); if (DEBUG) this.log("addWriteNotify(" + str.toHexWord(start) + "," + str.toHexWord(end) + "," + component.id + "): new write range: " + str.toHexWord(this.addrWriteLower) + "-" + str.toHexWord(this.addrWriteUpper)); } }; /** * @this {C1PCPU} * @param {number} addrWrite is the EA value at the time of the write * @param {number} [addrFrom] is the PC value at the time of the write; * this will be undefined for write notifications triggered by assorted Debugger commands, * so all handlers should be prepared for that as well. */ C1PCPU.prototype.checkWriteNotify = function(addrWrite, addrFrom) { for (var i=0; i < this.aWriteNotify.length; i++) { if (addrWrite >= this.aWriteNotify[i][0] && addrWrite <= this.aWriteNotify[i][1]) { this.aWriteNotify[i][3].call(this.aWriteNotify[i][2], addrWrite, addrFrom); } } }; /** * Remove a memory write-notification handler from the CPU's list of such handlers. * * @this {C1PCPU} * @param {number} start address * @param {number} end address * @param {Component} component * @param {function(number,number)} fn of previously added handler * @return {boolean} true if remove was successful, false if the handler was not found */ C1PCPU.prototype.removeWriteNotify = function(start, end, component, fn) { var aBounds = this.removeNotify(this.aWriteNotify, start, end, component, fn); if (aBounds.length == 4) { this.addrWriteLower = aBounds[2]; this.addrWriteUpper = aBounds[3]; if (DEBUG) this.log("removeWriteNotify(" + str.toHexWord(start) + "," + str.toHexWord(end) + "," + component.id + "): new write range: " + str.toHexWord(this.addrWriteLower) + "-" + str.toHexWord(this.addrWriteUpper)); return true; } return false; }; /** * Find a memory notification handler from the given array of handlers * * @this {C1PCPU} * @param {Array} aNotify array of handlers * @param {number} start address * @param {number} end address * @param {Component} component * @param {function(number,number)} fn of previously added handler * @return {number} index of the matching handler, or -1 if not found */ C1PCPU.prototype.findNotify = function(aNotify, start, end, component, fn) { for (var i=0; i < aNotify.length; i++) { if (aNotify[i][0] == start && aNotify[i][1] == end && aNotify[i][2] == component && aNotify[i][3] == fn) { return i; } } return -1; }; /** * Remove a memory notification handler from the given array of handlers * * @this {C1PCPU} * @param {Array} aNotify array of handlers * @param {number} start address * @param {number} end address * @param {Component} component * @param {function(number,number)} fn of previously added handler * @return {Array} bounds of previous handler ([0] and [1]) and new lower and upper address bounds ([2] and [3]) */ C1PCPU.prototype.removeNotify = function(aNotify, start, end, component, fn) { var aBounds = []; var i = this.findNotify(aNotify, start, end, component, fn); if (i >= 0) { aBounds.push(aNotify[i][0]); aBounds.push(aNotify[i][1]); aNotify.splice(i, 1); var addrLower = 0x10000, addrUpper = 0x0; for (i=0; i < aNotify.length; i++) { if (addrLower > aNotify[i][0]) addrLower = aNotify[i][0]; if (addrUpper < aNotify[i][1]) addrUpper = aNotify[i][1]; } aBounds.push(addrLower); aBounds.push(addrUpper); } return aBounds; }; /** * @this {C1PCPU} * @param {number} [speed] is one of: 0 (slow), 1 (fast) or 2 (maximum) * @param {boolean} [fOnClick] is true if called from a click handler that might have stolen focus * @desc Whenever the speed is changed, the running cycle count and corresponding start time must be reset, * so that the next effective speed calculation obtains sensible results. In fact, when run() initially calls * setSpeed() with no parameters, that's all this function does (it doesn't change the current speed setting). */ C1PCPU.prototype.setSpeed = function(speed, fOnClick) { if (speed !== undefined) { this.speed = speed; if (this.bindings["setSpeed"]) this.bindings["setSpeed"].innerHTML = this.aSpeeds[speed >= 2? 0 : speed+1]; this.println("running at " + this.aSpeeds[speed].toLowerCase() + " speed " + this.aSpeedDescs[speed]); if (fOnClick) this.setFocus(); } this.nRunCycles = 0; this.msRunStart = usr.getTime(); this.calcCycles(); }; /** * @this {C1PCPU} * @param {number} nCycles * @param {number} msElapsed */ C1PCPU.prototype.calcSpeed = function(nCycles, msElapsed) { if (msElapsed) { this.mhz = Math.round(nCycles / ( msElapsed * 100)) / 10; if (msElapsed >= 86400000) this.setSpeed(); // reset all our counters once per day so that we never have to worry about overflow } }; /** * @this {C1PCPU} */ C1PCPU.prototype.displayVideo = function() { // Nothing to do until setPower() installs a replacement function }; /** * @this {C1PCPU} */ C1PCPU.prototype.setFocus = function() { // Nothing to do until setPower() installs a replacement function }; /** * @this {C1PCPU} * @param {string} sReg * @param {number} vReg * @param {number} [len] */ C1PCPU.prototype.displayReg = function(sReg, vReg, len) { if (this.bindings[sReg] !== undefined) { if (len === undefined) len = 1; var s = "0000" + vReg.toString(16); this.bindings[sReg].innerHTML = s.slice(s.length-len).toUpperCase(); } }; /** * @this {C1PCPU} */ C1PCPU.prototype.displayStatus = function() { this.displayReg("A", this.regA, 2); this.displayReg("X", this.regX, 2); this.displayReg("Y", this.regY, 2); var regP = this.getRegP(); this.displayReg("C", (regP & this.BIT_PC)? 1 : 0); this.displayReg("Z", (regP & this.BIT_PZ)? 1 : 0); this.displayReg("I", (regP & this.BIT_PI)? 1 : 0); this.displayReg("D", (regP & this.BIT_PD)? 1 : 0); this.displayReg("B", (regP & this.BIT_PB)? 1 : 0); this.displayReg("V", (regP & this.BIT_PV)? 1 : 0); this.displayReg("N", (regP & this.BIT_PN)? 1 : 0); this.displayReg("S", this.regS, 4); this.displayReg("PC", this.regPC, 4); if (this.bindings["speed"] && this.mhz) { this.bindings["speed"].innerHTML = this.mhz.toFixed(1) + "Mhz"; } }; /** * @this {C1PCPU} * @return {boolean} */ C1PCPU.prototype.isRunning = function() { return this.flags.fRunning; }; /** * Calculate the number of cycles to process for each "burst" of CPU activity. The size of a burst * is driven by the following values: * * nYieldsPerSecond (eg, 30) * nVideoUpdatesPerSecond (eg, 30) * nStatusUpdatesPerSecond (eg, 5) * * The largest of the above values forces the size of the burst to its smallest value. Let's say that * largest value is 30. Assuming nCyclesPerSecond is 1,000,000, that results in bursts of 33,333 cycles. * * At the end of each burst, we subtract burst cycles from yield, video, and status cycle "threshold" * counters. Whenever the "next yield" cycle counter goes to (or below) zero, we compare elapsed time * to the time we expected the virtual hardware to take (eg, 1000ms/50 or 20ms), and if we still have time * remaining, we sleep the remaining time (or 0ms if there's no remaining time), and then restart run(). * * Similarly, whenever the "next video update" cycle counter goes to (or below) zero, we call displayVideo(), * and whenever the "next status update" cycle counter goes to (or below) zero, we call displayStatus(). * * @this {C1PCPU} * @param {boolean} [fRecalc] is true if the caller wants to recalculate thresholds based on the * most recent mhz calculation (see calcSpeed) */ C1PCPU.prototype.calcCycles = function(fRecalc) { /* * Calculate the most cycles we're allowed to execute in a single "burst" */ var nMostUpdatesPerSecond = this.nYieldsPerSecond; if (nMostUpdatesPerSecond < this.nVideoUpdatesPerSecond) nMostUpdatesPerSecond = this.nVideoUpdatesPerSecond; if (nMostUpdatesPerSecond < this.nStatusUpdatesPerSecond) nMostUpdatesPerSecond = this.nStatusUpdatesPerSecond; /* * Calculate "per" values for the yield, video update, and status update cycle counters */ var vMultiplier = 1; if (fRecalc && this.speed > this.SPEED_SLOW && this.mhz) vMultiplier = this.mhz; if (vMultiplier > this.mhzFast && this.speed < this.SPEED_MAX) vMultiplier = this.mhzFast; this.msPerYield = Math.round(1000/this.nYieldsPerSecond); this.nCyclesPerBurst = Math.floor(this.nCyclesPerSecond / nMostUpdatesPerSecond * vMultiplier); this.nCyclesPerYield = Math.floor(this.nCyclesPerSecond / this.nYieldsPerSecond * vMultiplier); this.nCyclesPerVideoUpdate = Math.floor(this.nCyclesPerSecond / this.nVideoUpdatesPerSecond * vMultiplier); this.nCyclesPerStatusUpdate = Math.floor(this.nCyclesPerSecond / this.nStatusUpdatesPerSecond * vMultiplier); /* * And initialize "next" yield, video update, and status update cycle "threshold" counters to those "per" values */ if (!fRecalc) { this.nCyclesNextYield = this.nCyclesPerYield; this.nCyclesNextVideoUpdate = this.nCyclesPerVideoUpdate; this.nCyclesNextStatusUpdate = this.nCyclesPerStatusUpdate; } this.nRecalcCycles = 0; }; /** * @this {C1PCPU} */ C1PCPU.prototype.calcStartTime = function() { if (this.nRecalcCycles >= this.nCyclesPerSecond) { this.calcCycles(true); } this.nCyclesThisRun = 0; this.msStartThisRun = usr.getTime(); }; /** * @this {C1PCPU} * @return {number} */ C1PCPU.prototype.calcRemainingTime = function() { var msCurrent = usr.getTime(); var msYield = this.msPerYield; if (this.nCyclesThisRun) { /* * Normally, we would assume we executed a full quota of work over msPerYield, but since the CPU * now has the option of calling yieldCPU(), that might not be true. If nCyclesThisRun is correct, then * the ratio of nCyclesThisRun/nCyclesPerYield should represent the percentage of work we performed, * and so applying that percentage to msPerYield should give us a better estimate of work vs. time. */ msYield = Math.round(msYield * this.nCyclesThisRun / this.nCyclesPerYield); // if (msYield < this.msPerYield) this.println("scaling msPerYield (" + this.msPerYield + ") to msYield (" + msYield + ")"); } var msElapsedThisRun = msCurrent - this.msStartThisRun; var msRemainsThisRun = msYield - msElapsedThisRun; /* * We could pass only "this run" results to calcSpeed(): * * nCycles = this.nCyclesThisRun; * msElapsed = msElapsedThisRun; * * but it seems preferable to use longer time periods and hopefully get a more accurate speed. * * Also, if msRemainsThisRun >= 0 && this.speed == this.SPEED_SLOW, we could pass these results instead: * * nCycles = this.nCyclesThisRun; * msElapsed = this.msPerYield; * * to insure that we display a smooth, constant 1Mhz. But the displayed speed seems pretty steady as-is. */ var nCycles = this.nRunCycles; var msElapsed = msCurrent - this.msRunStart; if (DEBUG && msRemainsThisRun < 0 && this.speed == this.SPEED_FAST) { this.println("warning: updates @" + msElapsedThisRun + "ms (prefer " + Math.round(msYield) + "ms)"); } this.calcSpeed(nCycles, msElapsed); if (msRemainsThisRun < 0) { /* * This is an easy case: it's taking more than 1 second to simulate 1Mhz, * so all we can do is yield for as little time as possible (ie, 0ms) and hope the * simulation is at least usable. */ msRemainsThisRun = 0; } else { if (this.speed == this.SPEED_FAST) { /* * This case requires us to artificially limit the CPU speed. calcSpeed() * already cranks up the number of cycles we process per burst, in proportion * to the effective mhz, so there isn't much to do here except sleep for * whatever time is in msRemainsThisRun. * * The artificial limit was chosen largely because it's not currently possible * for the keyboard component to inject keys fast enough to avoid duplicate * keystrokes at higher speeds. Perhaps with future improvements to the keyboard * component, this limit can be lifted. */ if (this.mhz <= this.mhzFast) msRemainsThisRun = 0; } else if (this.speed == this.SPEED_MAX) { /* * This is also an easy case: yield for as little time as possible (ie, 0ms), to execute * the maximum number of cycles per second. */ msRemainsThisRun = 0; } } /* * Last but not least, update nRecalcCycles, so that when run() starts up again and calls calcStartTime(), * it'll be ready to decide if calcCycles() should be called again. */ this.nRecalcCycles += this.nCyclesThisRun; return msRemainsThisRun; }; /** * @this {C1PCPU} */ C1PCPU.prototype.run = function() { if (!this.setBusy(true)) { this.update(); if (this.cmp) this.cmp.stop(this.msRunStart, this.nRunCycles); return; } if (!this.flags.fRunning) { /* * setSpeed() without a speed parameter leaves the selected speed in place, but also resets the * cycle counter and timestamp for the current series of run() calls, calculates the maximum number * of cycles for each burst based on the last known effective CPU speed, and resets the nRecalcCycles * threshold counter. */ this.setSpeed(); if (this.cmp) this.cmp.start(); this.flags.fRunning = true; if (this.bindings["run"]) this.bindings["run"].innerHTML = "Halt"; this.setFocus(); } /* * calcStartTime() initializes the cycle counter and timestamp for this run() invocation, and optionally * recalculates the the maximum number of cycles for each burst if the nRecalcCycles threshold has been reached. */ this.calcStartTime(); try { do { /* * NOTE: nCyclesPerBurst is how many cycles we WANT to run each iteration of step(), but that just * initializes nBurstCycles, which (after subtracting any remaining nStepCycles) is how many cycles * we ACTUALLY ran. */ this.step(this.nCyclesPerBurst); /* * nCyclesThisRun is increased by nBurstCycles, plus any additional cycles step() processed after * its cycle count had reached zero (and conversely, minus any cycles that it still had yet to process); * ditto for nRunCycles, which is the cycle count since the CPU first started running. */ var nCycles = this.nBurstCycles - this.nStepCycles; this.nRunCycles += nCycles; this.nCyclesThisRun += nCycles; /* * These step() cycle variables must be zeroed now, so that getCycles() always returns a valid cycle count. */ this.nBurstCycles = this.nStepCycles = 0; this.nCyclesNextVideoUpdate -= this.nCyclesPerBurst; if (this.nCyclesNextVideoUpdate <= 0) { this.nCyclesNextVideoUpdate += this.nCyclesPerVideoUpdate; this.displayVideo(); } this.nCyclesNextStatusUpdate -= this.nCyclesPerBurst; if (this.nCyclesNextStatusUpdate <= 0) { this.nCyclesNextStatusUpdate += this.nCyclesPerStatusUpdate; this.displayStatus(); } this.nCyclesNextYield -= this.nCyclesPerBurst; if (this.nCyclesNextYield <= 0) { this.nCyclesNextYield += this.nCyclesPerYield; break; } } while (this.flags.fRunning); } catch (e) { this.halt(); this.update(); this.setBusy(false); this.setError(e.stack || e.message); return; } setTimeout(function(cpu) { return function() {cpu.run();}; }(this), this.calcRemainingTime()); }; /** * @this {C1PCPU} * @param {number} nMinCycles (0 implies a single-step, and therefore breakpoints should be ignored) * @return {boolean|undefined} undefined indicates that the last instruction was not executed (eg, * we hit an execution breakpoint), false implies a post-execution condition was triggered (eg, a write * breakpoint), and true indicates successful completion of all requested cycles. */ C1PCPU.prototype.step = function(nMinCycles) { /* * The Debugger uses fCompleted to determine if the instruction completed (true) or was interrupted * by a breakpoint or some other exceptional condition (false). NOTE: this does NOT include thrown * exceptions, which step() expects the caller to catch using its own exception handler. * * The CPU relies on the use of halt() rather than fCompleted, because the CPU never single-steps * (ie, nMinCycles is always some large number), whereas the Debugger does. And conversely, when the * Debugger is single-stepping (even when performing multiple single-steps), fRunning is never set, * so halt() would have no effect as far as the Debugger is concerned. */ var fCompleted = true; /* * fDebugCheck is true if we need to "check" every instruction with the Debugger. The Debugger will * call cpu.step(n) with n == 0 if it's executing only ONE instruction (ie, the user just clicked the * "Step" button, or they've issued a "t" or "t1" command). Otherwise, it will call with n == 1 * (ie, the user is holding the "Step" button, or they've issued a "t#" command where # > 1). * * In the first case, we want to ignore (ie, "step over") any breakpoints; otherwise, the Debugger has * no easy way of moving past a breakpoint (other than clearing it, of course). In the second case, * we want to honor any breakpoints, which in turn will set fCompleted to false and signal the Debugger * to stop. * * Note that as a practical matter, both 0 and 1 are otherwise treated the same when it comes to the * minimum number of cycles to run: one and only one instruction will execute, since every instruction * consumes at least 1 cycle. */ this.regEA = this.regEAWrite = -1; var fDebugCheck = (DEBUGGER && nMinCycles && this.dbg && this.dbg.checksEnabled()); /* * We move the minimum cycle count to nStepCycles (the number of cycles left to run), so that other * methods have the ability to force that number to zero (eg, halt()), and thus we don't have to check * some other criteria just to determine whether we should continue running or not. */ this.nBurstCycles = this.nStepCycles = nMinCycles; do { var bOpCode = this.abMem[this.regPC]; if (fDebugCheck && !this.dbg.checkInstruction(this.regPC, bOpCode)) { fCompleted = undefined; this.halt(); break; } this.regPC++; this.aOpcodeFuncs[bOpCode].call(this); /* * Assert that all register contents remain within their respective ranges. */ Component.assert(!(this.regA & ~0xff) && !(this.regX & ~0xff) && !(this.regY & ~0xff) && !(this.regS & ~0x1ff), "register out of bounds"); /* * WARNING: By making the following read-or-write test exclusive, we're not going to catch * those situations where an instruction does BOTH. For example, JSR pushes the old PC * (which, if we were tracking STACK writes, would set regEAWrite) and then fetches a new PC * (which should set regEA). However, that's a situation which, except for a peculiar * combination of read and write breakpoints set by the Debugger, is one we simply don't care * about. Moreover, none of our opcode handlers currently set BOTH regEA and regEAWrite, * so it would be completely pointless to check both conditions here. * * Since READS are FAR more common than WRITES, we check the common case first. If the * day comes that we implement opcode functions that set both regEA AND regEAWrite, then we'll * want to remove the "else" below and stop making the read and write tests exclusive. * If we don't, then some of those rare/unusual writes will either get missed or delayed. * * It's also worth mentioning here that not all instructions READ or WRITE (eg, INX, INY and * others that are completely flag or register-bound). It's a bit ironic that those instructions, * which are typically the fastest, have to perform both the READ and WRITE tests below. But * on the plus side, both tests will fail, so they'll still get out of here faster than any of * the other instructions. */ if (this.regEA >= 0) { /* * Serial emulation requires a read notification handler, and the keyboard may eventually * want one, too. */ if (this.regEA >= this.addrReadLower && this.regEA <= this.addrReadUpper) { this.checkReadNotify(this.regEA, this.regPC); } if (fDebugCheck && !this.dbg.checkMemoryRead(this.regEA)) { fCompleted = false; this.halt(); break; } this.regEA = -1; } else if (this.regEAWrite >= 0) { /* * We process the write notification handlers before the write breakpoint handlers, * because we don't want to leave any ROM (read-only memory) writes in place before we * (potentially) give up control. Undoing every write to ROM address space is an essential * part of simulating the "read-only" behavior of ROM. * * Obviously, there are other write notification handlers as well (like the keyboard's), * which may sometimes do things we'd prefer to intercept first, but let's keep things simple. */ if (this.regEAWrite >= this.addrWriteLower && this.regEAWrite <= this.addrWriteUpper) { this.checkWriteNotify(this.regEAWrite, this.regPC); } if (fDebugCheck && !this.dbg.checkMemoryWrite(this.regEAWrite, this.abMem[this.regEAWrite])) { fCompleted = false; this.halt(); break; } this.regEAWrite = -1; } this.nStepCycles -= this.aOpcodeCycles[bOpCode]; } while (this.nStepCycles > 0); return fCompleted; }; /** * yieldCPU() is similar to halt() with regard to how it resets various cycle countdown values, * but the CPU remains in a "running" state. * * @this {C1PCPU} */ C1PCPU.prototype.yieldCPU = function() { this.nCyclesNextYield = 0; // this will break us out of run(), once we break out of step() this.nBurstCycles -= this.nStepCycles; this.nStepCycles = 0; // this will break us out of step() }; /** * halt() is similar to yieldCPU(), but it doesn't need to zero nCyclesNextYield to break out of run(); * it simply needs to clear fRunning. * * @this {C1PCPU} */ C1PCPU.prototype.halt = function() { this.isBusy(true); this.nBurstCycles -= this.nStepCycles; this.nStepCycles = 0; if (this.flags.fRunning) { this.flags.fRunning = false; if (this.bindings["run"]) this.bindings["run"].innerHTML = "Run"; } }; /** * @this {C1PCPU} * * This used to be performed at the end of every step(), but run() -- which relies upon step() -- needed to have * more control over when these updates are performed. However, for other callers of step(), such as the Debugger, * the combination of step() + update() provides the old behavior. */ C1PCPU.prototype.update = function() { this.displayVideo(); this.displayStatus(); }; /** * getCycles() returns the number of cycles executed so far. Note that we can be called after * a run() OR during a run(), perhaps from a handler triggered during the current run's step(), * so nRunCycles must always be adjusted by number of cycles step() was asked to run (nBurstCycles), * less the number of cycles it has yet to run (nStepCycles). * * nRunCycles is reset whenever the CPU is halted or the CPU speed is changed, so returning 0 when the * CPU is stopped seems perfectly reasonable. As a result, components that rely on getCycles() returning a * steadily increasing number should also be prepared for a reset at any time (eg, the Keyboard's * updateMemory() function). * * @this {C1PCPU} * @return {number} */ C1PCPU.prototype.getCycles = function() { return (this.flags.fRunning? this.nRunCycles + this.nBurstCycles - this.nStepCycles : 0); }; /** * @this {C1PCPU} * @param {number} addr * @return {number} * * Unlike the Debugger versions of these functions, these presume that addr is always valid, * since it's internally generated, not user-supplied. Of course, we could still have internal * bugs, so asserts are included, but they are present in DEBUG code only (automatically * removed from RELEASE code). * * Moreover, it's unlikely we'll use this function much (unless performance becomes secondary * to code size), since all the opCode functions should perform their own fetches, for obvious * performance reasons. */ C1PCPU.prototype.getByte = function(addr) { Component.assert((addr >= this.offMem && addr < this.offLimit), "invalid address: " + str.toHexWord(addr)); var b = this.abMem[addr]; Component.assert(!(b & ~0xff), "invalid byte (" + b + ") at address " + str.toHexWord(addr)); return b; }; /** * @this {C1PCPU} * @param {number} addr * @return {number} */ C1PCPU.prototype.getWord = function(addr) { Component.assert((addr >= this.offMem && addr < this.offLimit), "invalid address: " + str.toHexWord(addr)); var w = this.abMem[addr] | (this.abMem[addr+1] << 8); Component.assert(!(w & ~0xffff), "invalid word (" + w + ") at address " + str.toHexWord(addr)); return w; }; /** * @this {C1PCPU} * @param {number} addr * @param {number} b */ C1PCPU.prototype.setByte = function(addr, b) { Component.assert((addr >= this.offMem && addr < this.offLimit), "invalid address: " + str.toHexWord(addr)); Component.assert(!(b & ~0xff), "invalid byte (" + b + ") at address " + str.toHexWord(addr)); this.abMem[addr] = b; }; /** * @this {C1PCPU} * @return {number} */ C1PCPU.prototype.getRegP = function() { /* * // C = LAZY_C; * this.regP = ((this.regP & 0xfe) | ((((this.regRC & 0x0100)))? 0x01 : 0)); * // Z = LAZY_Z; * this.regP = ((this.regP & 0xfd) | ((((this.regRZ & 0xff) == 0))? 0x02 : 0)); * // V = LAZY_V; * this.regP = ((this.regP & 0xbf) | (((((((this.regRV & 0xff) ^ this.regRU) ^ (this.regRV >> 1)) & 0x80) != 0))? 0x40 : 0)); * // N = LAZY_N; * this.regP = ((this.regP & 0x7f) | ((((this.regRN & 0x80)))? 0x80 : 0)); */ var regP = ((this.regRC & 0x0100)? 0x01 : 0x00); regP |= (!(this.regRZ & 0xff)? 0x02 : 0x00); regP |= (((((this.regRV & 0xff) ^ this.regRU) ^ (this.regRV >> 1)) & 0x80)? 0x40 : 0x00); regP |= ((this.regRN & 0x80)? 0x80 : 0x00); return (this.regP & 0x3C) | regP; }; /** * @this {C1PCPU} * * Clear the C flag */ C1PCPU.prototype.clearC = function() { this.regRC = 0x00; }; /** * @this {C1PCPU} * * Set the C flag */ C1PCPU.prototype.setC = function() { this.regRC = 0x100; }; /** * @this {C1PCPU} * * Clear the N bit */ C1PCPU.prototype.clearN = function() { this.regRN = 0x00; }; /** * @this {C1PCPU} * * Set the N bit */ C1PCPU.prototype.setN = function() { this.regRN = 0x80; }; /** * @this {C1PCPU} * * Clear the V bit */ C1PCPU.prototype.clearV = function() { this.regRV = 0x00; this.regRU = 0x00; }; /** * @this {C1PCPU} * * Set the V bit */ C1PCPU.prototype.setV = function() { this.regRV = 0x00; this.regRU = 0x80; }; /** * @this {C1PCPU} * * Clear the Z bit */ C1PCPU.prototype.clearZ = function() { this.regRZ = 0x01; }; /** * @this {C1PCPU} * * Set the Z bit */ C1PCPU.prototype.setZ = function() { this.regRZ = 0x00; }; /** * @this {C1PCPU} * * Set the BCD bit and install the BCD opcode handlers */ C1PCPU.prototype.setBCD = function() { this.regP |= 0x08; this.aOpcodeFuncs[0x61] = this.opADCindxBCD; this.aOpcodeFuncs[0x65] = this.opADCzpBCD; this.aOpcodeFuncs[0x69] = this.opADCimmBCD; this.aOpcodeFuncs[0x6d] = this.opADCabsBCD; this.aOpcodeFuncs[0x71] = this.opADCindyBCD; this.aOpcodeFuncs[0x75] = this.opADCzpxBCD; this.aOpcodeFuncs[0x79] = this.opADCabsyBCD; this.aOpcodeFuncs[0x7d] = this.opADCabsxBCD; this.aOpcodeFuncs[0xe1] = this.opSBCindxBCD; this.aOpcodeFuncs[0xe5] = this.opSBCzpBCD; this.aOpcodeFuncs[0xe9] = this.opSBCimmBCD; this.aOpcodeFuncs[0xed] = this.opSBCabsBCD; this.aOpcodeFuncs[0xf1] = this.opSBCindyBCD; this.aOpcodeFuncs[0xf5] = this.opSBCzpxBCD; this.aOpcodeFuncs[0xf9] = this.opSBCabsyBCD; this.aOpcodeFuncs[0xfd] = this.opSBCabsxBCD; }; /** * @this {C1PCPU} * * Clear the BCD bit and remove the BCD opcode handlers */ C1PCPU.prototype.clearBCD = function() { this.regP &= ~0x08; this.aOpcodeFuncs[0x61] = this.opADCindx; this.aOpcodeFuncs[0x65] = this.opADCzp; this.aOpcodeFuncs[0x69] = this.opADCimm; this.aOpcodeFuncs[0x6d] = this.opADCabs; this.aOpcodeFuncs[0x71] = this.opADCindy; this.aOpcodeFuncs[0x75] = this.opADCzpx; this.aOpcodeFuncs[0x79] = this.opADCabsy; this.aOpcodeFuncs[0x7d] = this.opADCabsx; this.aOpcodeFuncs[0xe1] = this.opSBCindx; this.aOpcodeFuncs[0xe5] = this.opSBCzp; this.aOpcodeFuncs[0xe9] = this.opSBCimm; this.aOpcodeFuncs[0xed] = this.opSBCabs; this.aOpcodeFuncs[0xf1] = this.opSBCindy; this.aOpcodeFuncs[0xf5] = this.opSBCzpx; this.aOpcodeFuncs[0xf9] = this.opSBCabsy; this.aOpcodeFuncs[0xfd] = this.opSBCabsx; }; /** * @this {C1PCPU} * @param {number} reg * @param {number} mem * @return {number} * * Refer to http://www.6502.org/tutorials/decimal_mode.html for 6502-specific details. * Refer to http://homepage.cs.uiowa.edu/~jones/bcd/bcd.html for optimization tips. */ C1PCPU.prototype.addBCD = function(reg, mem) { var carry = ((this.regRC & 0x0100)? 1 : 0); /* * First add the low nibbles. */ var r = (reg & 0x0f) + (mem & 0x0f) + carry; /* * Adjust the result. NOTE: The extra AND'ing and ADD'ing isn't necessary if we want to * assume that ONLY valid BCD digits will be added, but we probably shouldn't assume that. * NOTE: We use an OR instead of an ADD at the end because it's logically equivalent and faster. */ if (r >= 0x0A) r = ((r + 0x06) & 0x0f) | 0x10; /* * Now add the high nibbles. */ r += (reg & 0xf0) + (mem & 0xf0); /* * Before we do the next adjust, it seems that N and V are dependent on this intermediate * result (however, the meaning of N and V in BCD mode is not well documented). */ this.regRU = reg ^ mem; this.regRV = r; this.regRN = (r & 0xff); /* * Final adjustment. */ if (r >= 0xA0) r += 0x60; /* * NOTE: If the intermediate result was 0x1A0 or more, then adding 0x60 would yield a result * of 0x200 or more, but because the rest of the simulator tests regRC for 0x100, rather than * comparing regRC for values >= 0x100, we'll miss the fact that there was a carry, unless we * scale any value in the 0x200-0x2ff range down to 0x100-0x1ff. We then assert that the * resulting value is within the proper range. */ if (r >= 0x200) r -= 0x100; Component.assert((r & 0x1ff) == r, "BCD addition overflow"); /* * In BCD mode, the C flag reflects the decimal result, but the Z flag reflects binary addition. */ this.regRC = r; this.regRZ = ((reg + mem + carry) & 0xff); /* * Account for an extra cycle in BCD mode as well. */ this.nStepCycles--; return r & 0xff; }; /** * @this {C1PCPU} * @param {number} reg * @param {number} mem * @return {number} * * Refer to http://www.6502.org/tutorials/decimal_mode.html for 6502-specific details. * Refer to http://homepage.cs.uiowa.edu/~jones/bcd/bcd.html for optimization tips. */ C1PCPU.prototype.subBCD = function(reg, mem) { var notcarry = ((this.regRC & 0x0100)? 0 : 1); /* * First subtract the low nibbles. */ var r = (reg & 0x0f) - (mem & 0x0f) - notcarry; /* * Adjust the result. NOTE: The extra AND'ing and SUB'ing isn't necessary if we want to * assume that ONLY valid BCD digits will be added, but we probably shouldn't assume that. */ if (r < 0x00) r = ((r - 0x06) & 0x0f) - 0x10; /* * Now subtract the high nibbles. */ r += (reg & 0xf0) - (mem & 0xf0); /* * Final adjustment. */ if (r < 0x00) r -= 0x60; /* * In BCD mode, the Z -- and C, N and V -- flags are all set as if binary subtraction was performed. */ // RC = (A - ML - !LAZY_C); SET_LAZY_NZ(RCL); this.regRN = this.regRZ = (this.regRC = (reg - mem - notcarry)) & 0xff; // SET_LAZY_OV(A,ML,RC); this.regRU = reg ^ mem; this.regRV = this.regRC; // SET_LAZY_C(!LAZY_C); this.regRC ^= 0x0100; /* * Account for an extra cycle in BCD mode as well. */ this.nStepCycles--; return r & 0xff; }; /** * @this {C1PCPU} */ C1PCPU.prototype.clearRegs = function() { this.regA = 0; this.regX = 0; this.regY = 0; this.regS = 0x100; this.regP = 0; this.regRN = 0; this.regRZ = 0; this.regRU = 0; this.regRV = 0; this.regRC = 0; this.regPC = 0; this.regEA = -1; this.regEAWrite = -1; this.mhz = 0; this.nRunCycles = this.nBurstCycles = this.nStepCycles = 0; }; /* * Macro reference (from my original 1998 C source code, preserved in the comments below): * * #define BYTE(a) (abMem[(a) & 0xffff]) * #define WORD(a) (*(word *)&BYTE(a)) * #define SBYTE(a) ((int)(char)BYTE(a)) * #define STACK(a) BYTE(((a) & 0xff)+0x100) * #define M WORD(EA) * #define ML BYTE(EA+0) * #define MH BYTE(EA+1) * #define A (aRegs[0].value) // 8 bits * #define X (aRegs[1].value) // 8 bits * #define Y (aRegs[2].value) // 8 bits * #define S (aRegs[3].value) // 8 bits * #define P (aRegs[4].value) // 8 bits * #define RN (aRegs[5].value) // 8 bits * #define RZ (aRegs[6].value) // 8 bits * #define RU (aRegs[7].value) // 8 bits * #define RV (aRegs[8].value) // 16 bits * #define RC (aRegs[9].value) // 16 bits * #define EA (aRegs[10].value)// 16 bits * #define LA (aRegs[11].value)// 16 bits * #define PC (aRegs[12].value)// 16 bits * #define EF (aRegs[13].value)// 8 bits * #define C P0 * #define Z P1 * #define I P2 * #define D P3 * #define B P4 * #define V P6 * #define N P7 * #define W EF0 * #define LAZY_C (RCH0) * #define SET_LAZY_C(b) (RCH0 = (b)) * #define LAZY_N (RN7) * #define SET_LAZY_N(b) (RN7 = (b)) * #define SET_LAZY_NZ(v) (RN = RZ = (v)) * #define LAZY_Z ((byte)RZ == 0) * #define SET_LAZY_Z(b) (RZ = !(b)) * #define LAZY_V ((((RVL ^ RU) ^ (RV >> 1)) & 0x80) != 0) * #define SET_LAZY_V(b) (RV = 0, RU = ((b)? 0x80 : 0x00)) * #define SET_LAZY_OV(a,b,r) (RU = (a) ^ (b), RV = (r)) */ /** * @this {C1PCPU} */ C1PCPU.prototype.opBRK = function() { // opcode 0x00 // PC++; this.regPC++; // STACK(S--) = PCH; this.abMem[this.regS--] = (this.regPC >> 8); this.regS |= 0x100; // STACK(S--) = PCL; this.abMem[this.regS--] = (this.regPC & 0xff); this.regS |= 0x100; // B = 1; this.regP |= 0x10; // C = LAZY_C; Z = LAZY_Z; V = LAZY_V; N = LAZY_N; this.regP = this.getRegP(); // STACK(S--) = P; this.abMem[this.regS--] = this.regP; this.regS |= 0x100; // B = 0; this.regP &= 0xef; // EA = 0xFFFE; this.regEA = 0xFFFE; // PC = M; this.regPC = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8)); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opORAindx = function() { // opcode 0x01 // EA = WORD((BYTE(PC++)+X) & 0xff); this.regEA = ((this.abMem[this.regPC++]) + this.regX) & 0xff; this.regEA = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8)); // A = A | ML; SET_LAZY_NZ(A); this.regRN = this.regRZ = (this.regA |= this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opORAzp = function() { // opcode 0x05 // EA = BYTE(PC++); this.regEA = this.abMem[this.regPC++]; // A = A | ML; SET_LAZY_NZ(A); this.regRN = this.regRZ = (this.regA |= this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opASLzp = function() { // opcode 0x06 // EA = BYTE(PC++); this.regEAWrite = this.abMem[this.regPC++]; // RC = ML << 1; this.regRC = this.abMem[this.regEAWrite] << 1; // ML = RCL; SET_LAZY_NZ(RCL); this.regRN = this.regRZ = this.abMem[this.regEAWrite] = (this.regRC & 0xff); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opPHP = function() { // opcode 0x08 this.regP = this.getRegP(); // STACK(S--) = P; this.abMem[this.regS--] = this.regP; this.regS |= 0x100; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opORAimm = function() { // opcode 0x09 // EA = PC++; this.regEA = this.regPC++; // A = A | ML; SET_LAZY_NZ(A); this.regRN = this.regRZ = (this.regA |= this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opASLacc = function() { // opcode 0x0a // RC = A << 1; this.regRC = this.regA << 1; // A = RCL; SET_LAZY_NZ(RCL); this.regRN = this.regRZ = this.regA = (this.regRC & 0xff); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opORAabs = function() { // opcode 0x0d // EA = WORD(PC); PC += 2; this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)); // A = A | ML; SET_LAZY_NZ(A); this.regRN = this.regRZ = (this.regA |= this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opASLabs = function() { // opcode 0x0e // EA = WORD(PC); PC += 2; this.regEAWrite = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)); // RC = ML << 1; this.regRC = this.abMem[this.regEAWrite] << 1; // ML = RCL; SET_LAZY_NZ(RCL); this.regRN = this.regRZ = this.abMem[this.regEAWrite] = (this.regRC & 0xff); // W = 1; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opBPL = function() { // opcode 0x10 // PC = PC + (LAZY_N == 0? SBYTE(PC) : 0) + 1; this.regPC += (!(this.regRN & 0x80)? (this.nStepCycles--,((this.abMem[this.regPC] << 24) >> 24)) : 0) + 1; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opORAindy = function() { // opcode 0x11 // EA = WORD(BYTE(PC++))+Y; this.regEA = (this.abMem[this.regPC++]); this.regEA = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8)) + this.regY; // A = A | ML; SET_LAZY_NZ(A); this.regRN = this.regRZ = (this.regA |= this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opORAzpx = function() { // opcode 0x15 // EA = (BYTE(PC++)+X) & 0xff; this.regEA = (this.abMem[this.regPC++]+this.regX) & 0xff; // A = A | ML; SET_LAZY_NZ(A); this.regRN = this.regRZ = (this.regA |= this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opASLzpx = function() { // opcode 0x16 // EA = (BYTE(PC++)+X) & 0xff; this.regEAWrite = (this.abMem[this.regPC++]+this.regX) & 0xff; // RC = ML << 1; this.regRC = this.abMem[this.regEAWrite] << 1; // ML = RCL; SET_LAZY_NZ(RCL); this.regRN = this.regRZ = this.abMem[this.regEAWrite] = (this.regRC & 0xff); // W = 1; // NOTE: Consider alternatives for tracking zero-page writes (eg, regEAWriteZP) }; /** * @this {C1PCPU} */ C1PCPU.prototype.opCLC = function() { // opcode 0x18 // SET_LAZY_C(0); this.regRC = 0x00; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opORAabsy = function() { // opcode 0x19 // EA = WORD(PC)+Y; PC += 2; this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regY; // A = A | ML; SET_LAZY_NZ(A); this.regRN = this.regRZ = this.regA |= this.abMem[this.regEA]; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opORAabsx = function() { // opcode 0x1d // EA = WORD(PC)+X; PC += 2; this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regX; // A = A | ML; SET_LAZY_NZ(A); this.regRN = this.regRZ = this.regA |= this.abMem[this.regEA]; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opASLabsx = function() { // opcode 0x1e // EA = WORD(PC)+X; PC += 2; this.regEAWrite = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regX; // RC = ML << 1; this.regRC = this.abMem[this.regEAWrite] << 1; // ML = RCL; SET_LAZY_NZ(RCL); this.regRN = this.regRZ = this.abMem[this.regEAWrite] = (this.regRC & 0xff); // W = 1; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opJSRabs = function() { // opcode 0x20 // EA = PC; PC += 1; this.regEA = this.regPC++; // STACK(S--) = PCH; this.abMem[this.regS--] = (this.regPC >> 8); this.regS |= 0x100; // STACK(S--) = PCL; this.abMem[this.regS--] = (this.regPC & 0xff); this.regS |= 0x100; // PC = M; this.regPC = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8)); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opANDindx = function() { // opcode 0x21 // EA = WORD((BYTE(PC++)+X) & 0xff); this.regEA = ((this.abMem[this.regPC++]) + this.regX) & 0xff; this.regEA = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8)); // A = A & ML; SET_LAZY_NZ(A); this.regRN = this.regRZ = (this.regA &= this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opBITzp = function() { // opcode 0x24 // EA = BYTE(PC++); this.regEA = this.abMem[this.regPC++]; // SET_LAZY_Z((A & ML) == 0); this.regRZ = (this.regA & this.abMem[this.regEA]); // SET_LAZY_N(ML7); this.regRN = ((this.regRN & 0x7f) | (this.abMem[this.regEA] & 0x80)); // SET_LAZY_V(ML6); this.regRV = 0; this.regRU = ((this.abMem[this.regEA] & 0x40)? 0x80 : 0x00); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opANDzp = function() { // opcode 0x25 // EA = BYTE(PC++); this.regEA = this.abMem[this.regPC++]; // A = A & ML; SET_LAZY_NZ(A); this.regRN = this.regRZ = (this.regA &= this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opROLzp = function() { // opcode 0x26 // EA = BYTE(PC++); this.regEAWrite = this.abMem[this.regPC++]; // RCL = ML; this.regRC = ((this.regRC & 0xff00) | this.abMem[this.regEAWrite]); // RC = RC << 1; this.regRC <<= 1; // RCL0 = RCH1; this.regRC = ((this.regRC & 0xfffe) | (((this.regRC & 0x0200))? 0x0001 : 0)); // ML = RCL; SET_LAZY_NZ(RCL); this.regRN = this.regRZ = this.abMem[this.regEAWrite] = (this.regRC & 0xff); // W = 1; // NOTE: Consider alternatives for tracking zero-page writes (eg, regEAWriteZP) }; /** * @this {C1PCPU} */ C1PCPU.prototype.opPLP = function() { // opcode 0x28 // P = STACK(++S); this.regS = ((this.regS+1) & 0xff) | 0x100; this.regP = this.abMem[this.regS]; // SET_LAZY_C(C); this.regRC = ((this.regP & 0x01)? 0x0100 : 0); // SET_LAZY_Z(Z); this.regRZ = (!(this.regP & 0x02)? 0x01 : 0); // SET_LAZY_N(N); this.regRN = (this.regP & 0x80); // SET_LAZY_V(V); this.regRV = 0; this.regRU = ((this.regP & 0x40)? 0x80 : 0x00); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opANDimm = function() { // opcode 0x29 // EA = PC++; this.regEA = this.regPC++; // A = A & ML; SET_LAZY_NZ(A); this.regRN = this.regRZ = (this.regA &= this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opROLacc = function() { // opcode 0x2a // RCL = A; this.regRC = ((this.regRC & 0xff00) | this.regA); // RC = RC << 1; this.regRC <<= 1; // RCL0 = RCH1; this.regRC = ((this.regRC & 0xfffe) | ((this.regRC & 0x0200)? 0x0001 : 0)); // A = RCL; SET_LAZY_NZ(RCL); this.regRN = this.regRZ = this.regA = (this.regRC & 0xff); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opBITabs = function() { // opcode 0x2c // EA = WORD(PC); PC += 2; this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)); // SET_LAZY_Z((A & ML) == 0); this.regRZ = (this.regA & this.abMem[this.regEA]); // SET_LAZY_N(ML7); this.regRN = ((this.regRN & 0x7f) | (this.abMem[this.regEA] & 0x80)); // SET_LAZY_V(ML6); this.regRV = 0; this.regRU = ((this.abMem[this.regEA] & 0x40)? 0x80 : 0x00); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opANDabs = function() { // opcode 0x2d // EA = WORD(PC); PC += 2; this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)); // A = A & ML; SET_LAZY_NZ(A); this.regRN = this.regRZ = (this.regA &= this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opROLabs = function() { // opcode 0x2e // EA = WORD(PC); PC += 2; this.regEAWrite = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)); // RCL = ML; this.regRC = ((this.regRC & 0xff00) | this.abMem[this.regEAWrite]); // RC = RC << 1; this.regRC <<= 1; // RCL0 = RCH1; this.regRC = ((this.regRC & 0xfffe) | (((this.regRC & 0x0200))? 0x0001 : 0)); // ML = RCL; SET_LAZY_NZ(RCL); this.regRN = this.regRZ = this.abMem[this.regEAWrite] = (this.regRC & 0xff); // W = 1; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opBMI = function() { // opcode 0x30 // PC = PC + (LAZY_N != 0? SBYTE(PC) : 0) + 1; this.regPC += ((this.regRN & 0x80)? (this.nStepCycles--,((this.abMem[this.regPC] << 24) >> 24)) : 0) + 1; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opANDindy = function() { // opcode 0x31 // EA = WORD(BYTE(PC++))+Y; this.regEA = (this.abMem[this.regPC++]); this.regEA = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8)) + this.regY; // A = A & ML; SET_LAZY_NZ(A); this.regRN = this.regRZ = (this.regA &= this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opANDzpx = function() { // opcode 0x35 // EA = (BYTE(PC++)+X) & 0xff; this.regEA = (this.abMem[this.regPC++]+this.regX) & 0xff; // A = A & ML; SET_LAZY_NZ(A); this.regRN = this.regRZ = (this.regA &= this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opROLzpx = function() { // opcode 0x36 // EA = (BYTE(PC++)+X) & 0xff; this.regEAWrite = (this.abMem[this.regPC++]+this.regX) & 0xff; // RCL = ML; this.regRC = ((this.regRC & 0xff00) | this.abMem[this.regEAWrite]); // RC = RC << 1; this.regRC <<= 1; // RCL0 = RCH1; this.regRC = ((this.regRC & 0xfffe) | (((this.regRC & 0x0200))? 0x0001 : 0)); // ML = RCL; SET_LAZY_NZ(RCL); this.regRN = this.regRZ = this.abMem[this.regEAWrite] = (this.regRC & 0xff); // W = 1; // NOTE: Consider alternatives for tracking zero-page writes (eg, regEAWriteZP) }; /** * @this {C1PCPU} */ C1PCPU.prototype.opSEC = function() { // opcode 0x38 // SET_LAZY_C(1); this.regRC = 0x0100; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opANDabsy = function() { // opcode 0x39 // EA = WORD(PC)+Y; PC += 2; this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regY; // A = A & ML; SET_LAZY_NZ(A); this.regRN = this.regRZ = (this.regA &= this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opANDabsx = function() { // opcode 0x3d // EA = WORD(PC)+X; PC += 2; this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regX; // A = A & ML; SET_LAZY_NZ(A); this.regRN = this.regRZ = (this.regA &= this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opROLabsx = function() { // opcode 0x3e // EA = WORD(PC)+X; PC += 2; this.regEAWrite = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regX; // RCL = ML; this.regRC = ((this.regRC & 0xff00) | this.abMem[this.regEAWrite]); // RC = RC << 1; this.regRC <<= 1; // RCL0 = RCH1; this.regRC = ((this.regRC & 0xfffe) | (((this.regRC & 0x0200))? 0x0001 : 0)); // ML = RCL; SET_LAZY_NZ(RCL); this.regRN = this.regRZ = this.abMem[this.regEAWrite] = (this.regRC & 0xff); // W = 1; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opRTI = function() { // opcode 0x40 // P = STACK(++S); this.regS = ((this.regS+1) & 0xff) | 0x100; this.regP = this.abMem[this.regS]; // SET_LAZY_C(C); this.regRC = ((this.regP & 0x01)? 0x0100 : 0); // SET_LAZY_Z(Z); this.regRZ = (!(this.regP & 0x02)? 0x01 : 0); // SET_LAZY_N(N); this.regRN = (this.regP & 0x80); // SET_LAZY_V(V); this.regRV = 0; this.regRU = ((this.regP & 0x40)? 0x80 : 0x00); // PCL = STACK(++S); // PCH = STACK(++S); this.regS = ((this.regS+2) & 0xff) | 0x100; this.regPC = (this.abMem[(this.regS-1) | 0x100]) | (this.abMem[this.regS] << 8); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opEORindx = function() { // opcode 0x41 // EA = WORD((BYTE(PC++)+X) & 0xff); this.regEA = ((this.abMem[this.regPC++]) + this.regX) & 0xff; this.regEA = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8)); // A = A ^ ML; SET_LAZY_NZ(A); this.regRN = this.regRZ = (this.regA ^= this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opEORzp = function() { // opcode 0x45 // EA = BYTE(PC++); this.regEA = this.abMem[this.regPC++]; // A = A ^ ML; SET_LAZY_NZ(A); this.regRN = this.regRZ = (this.regA ^= this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opLSRzp = function() { // opcode 0x46 // EA = BYTE(PC++); this.regEAWrite = this.abMem[this.regPC++]; // SET_LAZY_C(ML0); this.regRC = ((this.regRC & 0xfeff) | ((this.abMem[this.regEAWrite] & 0x01)? 0x0100 : 0)); // ML = RCL = ML >> 1; this.abMem[this.regEAWrite] = ((this.regRC = ((this.regRC & 0xff00) | (this.abMem[this.regEAWrite] >> 1))) & 0xff); // SET_LAZY_NZ(RCL); this.regRN = this.regRZ = (this.regRC & 0xff); // W = 1; // NOTE: Consider alternatives for tracking zero-page writes (eg, regEAWriteZP) }; /** * @this {C1PCPU} */ C1PCPU.prototype.opPHA = function() { // opcode 0x48 // STACK(S--) = A; this.abMem[this.regS--] = this.regA; this.regS |= 0x100; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opEORimm = function() { // opcode 0x49 // EA = PC++; this.regEA = this.regPC++; // A = A ^ ML; SET_LAZY_NZ(A); this.regRN = this.regRZ = (this.regA ^= this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opLSRacc = function() { // opcode 0x4a // SET_LAZY_C( A0); this.regRC = ((this.regRC & 0xfeff) | ((this.regA & 0x01)? 0x0100 : 0)); // A = RCL = A >> 1; this.regA = ((this.regRC = ((this.regRC & 0xff00) | (this.regA >> 1))) & 0xff); // SET_LAZY_NZ(RCL); this.regRN = this.regRZ = (this.regRC & 0xff); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opJMPimm16 = function() { // opcode 0x4c // EA = PC; this.regEA = this.regPC; // PC += 2; // this.regPC += 2; // PC = M; this.regPC = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8)); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opEORabs = function() { // opcode 0x4d // EA = WORD(PC); PC += 2; this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)); // A = A ^ ML; SET_LAZY_NZ(A); this.regRN = this.regRZ = (this.regA ^= this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opLSRabs = function() { // opcode 0x4e // EA = WORD(PC); PC += 2; this.regEAWrite = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)); // SET_LAZY_C(ML0); this.regRC = ((this.regRC & 0xfeff) | ((this.abMem[this.regEAWrite] & 0x01)? 0x0100 : 0)); // ML = RCL = ML >> 1; this.abMem[this.regEAWrite] = ((this.regRC = ((this.regRC & 0xff00) | (this.abMem[this.regEAWrite] >> 1))) & 0xff); // SET_LAZY_NZ(RCL); this.regRN = this.regRZ = (this.regRC & 0xff); // W = 1; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opBVC = function() { // opcode 0x50 // PC = PC + (LAZY_V == 0? SBYTE(PC) : 0) + 1; this.regPC += (!((((this.regRV & 0xff) ^ this.regRU) ^ (this.regRV >> 1)) & 0x80)? (this.nStepCycles--,((this.abMem[this.regPC] << 24) >> 24)) : 0) + 1; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opEORindy = function() { // opcode 0x51 // EA = WORD(BYTE(PC++))+Y; this.regEA = this.abMem[this.regPC++]; this.regEA = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8)) + this.regY; // A = A ^ ML; SET_LAZY_NZ(A); this.regRN = this.regRZ = (this.regA ^= this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opEORzpx = function() { // opcode 0x55 // EA = (BYTE(PC++)+X) & 0xff; this.regEA = (this.abMem[this.regPC++]+this.regX) & 0xff; // A = A ^ ML; SET_LAZY_NZ(A); this.regRN = this.regRZ = (this.regA ^= this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opLSRzpx = function() { // opcode 0x56 // EA = (BYTE(PC++)+X) & 0xff; this.regEAWrite = (this.abMem[this.regPC++]+this.regX) & 0xff; // SET_LAZY_C(ML0); this.regRC = ((this.regRC & 0xfeff) | ((this.abMem[this.regEAWrite] & 0x01)? 0x0100 : 0)); // ML = RCL = ML >> 1; this.abMem[this.regEAWrite] = ((this.regRC = ((this.regRC & 0xff00) | (this.abMem[this.regEAWrite] >> 1))) & 0xff); // SET_LAZY_NZ(RCL); this.regRN = this.regRZ = (this.regRC & 0xff); // W = 1; // NOTE: Consider alternatives for tracking zero-page writes (eg, regEAWriteZP) }; /** * @this {C1PCPU} */ C1PCPU.prototype.opCLI = function() { // opcode 0x58 // I = 0; this.regP &= 0xfb; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opEORabsy = function() { // opcode 0x59 // EA = WORD(PC)+Y; PC += 2; this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regY; // A = A ^ ML; SET_LAZY_NZ(A); this.regRN = this.regRZ = (this.regA ^= this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opEORabsx = function() { // opcode 0x5d // EA = WORD(PC)+X; PC += 2; this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regX; // A = A ^ ML; SET_LAZY_NZ(A) this.regRN = this.regRZ = (this.regA ^= this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opLSRabsx = function() { // opcode 0x5e // EA = WORD(PC)+X; PC += 2; this.regEAWrite = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regX; // SET_LAZY_C(ML0); this.regRC = ((this.regRC & 0xfeff) | ((this.abMem[this.regEAWrite] & 0x01)? 0x0100 : 0)); // ML = RCL = ML >> 1; this.abMem[this.regEAWrite] = ((this.regRC = ((this.regRC & 0xff00) | (this.abMem[this.regEAWrite] >> 1))) & 0xff); // SET_LAZY_NZ(RCL); this.regRN = this.regRZ = (this.regRC & 0xff); // W = 1; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opRTS = function() { // opcode 0x60 // PCL = STACK(++S); // PCH = STACK(++S); // PC++; this.regS = ((this.regS+2) & 0xff) | 0x100; this.regPC = (((this.abMem[(this.regS-1) | 0x100])) | ((this.abMem[this.regS]) << 8)) + 1; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opADCindx = function() { // opcode 0x61 // EA = WORD((BYTE(PC++)+X) & 0xff); this.regEA = ((this.abMem[this.regPC++]) + this.regX) & 0xff; this.regEA = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8)); // RC = (A + ML + LAZY_C); this.regRC = (this.regA + this.abMem[this.regEA] + ((this.regRC & 0x0100)? 1 : 0)); // SET_LAZY_OV(A,ML,RC); this.regRU = this.regA ^ this.abMem[this.regEA]; this.regRV = this.regRC; // A = RCL; SET_LAZY_NZ(RCL); this.regRN = this.regRZ = this.regA = (this.regRC & 0xff); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opADCindxBCD = function() { // opcode 0x61 // EA = WORD((BYTE(PC++)+X) & 0xff); this.regEA = ((this.abMem[this.regPC++]) + this.regX) & 0xff; this.regEA = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8)); // A = this.addBCD(A,ML); this.regA = this.addBCD(this.regA, this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opADCzp = function() { // opcode 0x65 // EA = BYTE(PC++); this.regEA = this.abMem[this.regPC++]; // RC = (A + ML + LAZY_C); this.regRC = (this.regA + this.abMem[this.regEA] + ((this.regRC & 0x0100)? 1 : 0)); // SET_LAZY_OV(A,ML,RC); this.regRU = this.regA ^ this.abMem[this.regEA]; this.regRV = this.regRC; // A = RCL; SET_LAZY_NZ(RCL); this.regRN = this.regRZ = this.regA = (this.regRC & 0xff); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opADCzpBCD = function() { // opcode 0x65 // EA = BYTE(PC++); this.regEA = this.abMem[this.regPC++]; // A = this.addBCD(A,ML); this.regA = this.addBCD(this.regA, this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opRORzp = function() { // opcode 0x66 // EA = BYTE(PC++); this.regEAWrite = this.abMem[this.regPC++]; // RCL = ML; this.regRC = ((this.regRC & 0xff00) | this.abMem[this.regEAWrite]); // RCH1 = RCL0; this.regRC = ((this.regRC & 0xfdff) | ((this.regRC & 0x0001)? 0x0200 : 0)); // RC = RC >> 1; this.regRC >>= 1; // ML = RCL; SET_LAZY_NZ(RCL); this.regRN = this.regRZ = this.abMem[this.regEAWrite] = (this.regRC & 0xff); // W = 1; // NOTE: Consider alternatives for tracking zero-page writes (eg, regEAWriteZP) }; /** * @this {C1PCPU} */ C1PCPU.prototype.opPLA = function() { // opcode 0x68 // A = STACK(++S); SET_LAZY_NZ(A); this.regS = ((this.regS+1) & 0xff) | 0x100; this.regRN = this.regRZ = this.regA = this.abMem[this.regS]; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opADCimm = function() { // opcode 0x69 // EA = PC++; this.regEA = this.regPC++; // RC = (A + ML + LAZY_C); this.regRC = (this.regA + this.abMem[this.regEA] + ((this.regRC & 0x0100)? 1 : 0)); // SET_LAZY_OV(A,ML,RC); this.regRU = this.regA ^ this.abMem[this.regEA]; this.regRV = this.regRC; // A = RCL; SET_LAZY_NZ(RCL); this.regRN = this.regRZ = this.regA = (this.regRC & 0xff); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opADCimmBCD = function() { // opcode 0x69 // EA = PC++; this.regEA = this.regPC++; // A = this.addBCD(A,ML); this.regA = this.addBCD(this.regA, this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opRORacc = function() { // opcode 0x6a // RCL = A; this.regRC = ((this.regRC & 0xff00) | this.regA); // RCH1 = RCL0; this.regRC = ((this.regRC & 0xfdff) | ((this.regRC & 0x0001)? 0x0200 : 0)); // RC = RC >> 1; this.regRC >>= 1; // A = RCL; SET_LAZY_NZ(RCL); this.regRN = this.regRZ = this.regA = (this.regRC & 0xff); }; /** * @this {C1PCPU} * * NOTE from Wikipedia: "The 6502's memory indirect jump instruction, JMP (
), is partially broken. * If
is hex xxFF (i.e., any word ending in FF), the processor will not jump to the address stored in xxFF and xxFF+1 as expected, * but rather the one defined by xxFF and xx00. This defect continued through the entire NMOS line, but was corrected in the CMOS derivatives." */ C1PCPU.prototype.opJMPabs16 = function() { // opcode 0x6c // EA = WORD(PC); PC += 2; this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)); // PC = M; this.regPC = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8)); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opADCabs = function() { // opcode 0x6d // EA = WORD(PC); PC += 2; this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)); // RC = (A + ML + LAZY_C); this.regRC =(this.regA + this.abMem[this.regEA] + ((this.regRC & 0x0100)? 1 : 0)); // SET_LAZY_OV(A,ML,RC); this.regRU = this.regA ^ this.abMem[this.regEA]; this.regRV = this.regRC; // A = RCL; SET_LAZY_NZ(RCL); this.regRN = this.regRZ = this.regA = (this.regRC & 0xff); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opADCabsBCD = function() { // opcode 0x6d // EA = WORD(PC); PC += 2; this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)); // A = this.addBCD(A,ML); this.regA = this.addBCD(this.regA, this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opRORabs = function() { // opcode 0x6e // EA = WORD(PC); PC += 2; this.regEAWrite = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)); // RCL = ML; this.regRC = ((this.regRC & 0xff00) | this.abMem[this.regEAWrite]); // RCH1 = RCL0; this.regRC = ((this.regRC & 0xfdff) | ((this.regRC & 0x0001)? 0x0200 : 0)); // RC = RC >> 1; this.regRC >>= 1; // ML = RCL; SET_LAZY_NZ(RCL); this.regRN = this.regRZ = this.abMem[this.regEAWrite] = (this.regRC & 0xff); // W = 1; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opBVS = function() { // opcode 0x70 // PC = PC + (LAZY_V != 0? SBYTE(PC) : 0) + 1; this.regPC += (((((this.regRV & 0xff) ^ this.regRU) ^ (this.regRV >> 1)) & 0x80)? (this.nStepCycles--,((this.abMem[this.regPC] << 24) >> 24)) : 0) + 1; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opADCindy = function() { // opcode 0x71 // EA = WORD(BYTE(PC++))+Y; this.regEA = (this.abMem[this.regPC++]); this.regEA = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8)) + this.regY; // RC = (A + ML + LAZY_C); this.regRC = (this.regA + this.abMem[this.regEA] + ((this.regRC & 0x0100)? 1 : 0)); // SET_LAZY_OV(A,ML,RC); this.regRU = this.regA ^ this.abMem[this.regEA]; this.regRV = this.regRC; // A = RCL; SET_LAZY_NZ(RCL); this.regRN = this.regRZ = this.regA = (this.regRC & 0xff); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opADCindyBCD = function() { // opcode 0x71 // EA = WORD(BYTE(PC++))+Y; this.regEA = (this.abMem[this.regPC++]); this.regEA = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8)) + this.regY; // A = this.addBCD(A,ML); this.regA = this.addBCD(this.regA, this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opADCzpx = function() { // opcode 0x75 // EA = (BYTE(PC++)+X) & 0xff; this.regEA = (this.abMem[this.regPC++]+this.regX) & 0xff; // RC = (A + ML + LAZY_C); this.regRC = (this.regA + this.abMem[this.regEA] + ((this.regRC & 0x0100)? 1 : 0)); // SET_LAZY_OV(A,ML,RC); this.regRU = this.regA ^ this.abMem[this.regEA]; this.regRV = this.regRC; // A = RCL; SET_LAZY_NZ(RCL); this.regRN = this.regRZ = this.regA = (this.regRC & 0xff); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opADCzpxBCD = function() { // opcode 0x75 // EA = (BYTE(PC++)+X) & 0xff; this.regEA = (this.abMem[this.regPC++]+this.regX) & 0xff; // A = this.addBCD(A,ML); this.regA = this.addBCD(this.regA, this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opRORzpx = function() { // opcode 0x76 // EA = (BYTE(PC++)+X) & 0xff; this.regEAWrite = (this.abMem[this.regPC++]+this.regX) & 0xff; // RCL = ML; this.regRC = ((this.regRC & 0xff00) | this.abMem[this.regEAWrite]); // RCH1 = RCL0; this.regRC = ((this.regRC & 0xfdff) | ((this.regRC & 0x0001)? 0x0200 : 0)); // RC = RC >> 1; this.regRC >>= 1; // ML = RCL; SET_LAZY_NZ(RCL); this.regRN = this.regRZ = this.abMem[this.regEAWrite] = (this.regRC & 0xff); // W = 1; // NOTE: Consider alternatives for tracking zero-page writes (eg, regEAWriteZP) }; /** * @this {C1PCPU} */ C1PCPU.prototype.opSEI = function() { // opcode 0x78 // I = 1; this.regP |= 0x04; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opADCabsy = function() { // opcode 0x79 // EA = WORD(PC)+Y; PC += 2; this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regY; // RC = (A + ML + LAZY_C); this.regRC = (this.regA + this.abMem[this.regEA] + ((this.regRC & 0x0100)? 1 : 0)); // SET_LAZY_OV(A,ML,RC); this.regRU = this.regA ^ this.abMem[this.regEA]; this.regRV = this.regRC; // A = RCL; SET_LAZY_NZ(RCL); this.regRN = this.regRZ = this.regA = (this.regRC & 0xff); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opADCabsyBCD = function() { // opcode 0x79 // EA = WORD(PC)+Y; PC += 2; this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regY; // A = this.addBCD(A,ML); this.regA = this.addBCD(this.regA, this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opADCabsx = function() { // opcode 0x7d // EA = WORD(PC)+X; PC += 2; this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regX; // RC = (A + ML + LAZY_C); this.regRC = (this.regA + this.abMem[this.regEA] + ((this.regRC & 0x0100)? 1 : 0)); // SET_LAZY_OV(A,ML,RC); this.regRU = this.regA ^ this.abMem[this.regEA]; this.regRV = this.regRC; // A = RCL; SET_LAZY_NZ(RCL); this.regRN = this.regRZ = this.regA = (this.regRC & 0xff); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opADCabsxBCD = function() { // opcode 0x7d // EA = WORD(PC)+X; PC += 2; this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regX; // A = this.addBCD(A,ML); this.regA = this.addBCD(this.regA, this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opRORabsx = function() { // opcode 0x7e // EA = WORD(PC)+X; PC += 2; this.regEAWrite = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regX; // RCL = ML; this.regRC = ((this.regRC & 0xff00) | this.abMem[this.regEAWrite]); // RCH1 = RCL0; this.regRC = ((this.regRC & 0xfdff) | ((this.regRC & 0x0001)? 0x0200 : 0)); // RC = RC >> 1; this.regRC >>= 1; // ML = RCL; SET_LAZY_NZ(RCL); this.regRN = this.regRZ = this.abMem[this.regEAWrite] = (this.regRC & 0xff); // W = 1; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opSTAindx = function() { // opcode 0x81 // EA = WORD((BYTE(PC++)+X) & 0xff); this.regEAWrite = ((this.abMem[this.regPC++]) + this.regX) & 0xff; this.regEAWrite = (this.abMem[this.regEAWrite] | (this.abMem[this.regEAWrite+1] << 8)); // ML = A; this.abMem[this.regEAWrite] = this.regA; // W = 1; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opSTYzp = function() { // opcode 0x84 // EA = BYTE(PC++); this.regEAWrite = this.abMem[this.regPC++]; // ML = Y; this.abMem[this.regEAWrite] = this.regY; // W = 1; // NOTE: Consider alternatives for tracking zero-page writes (eg, regEAWriteZP) }; /** * @this {C1PCPU} */ C1PCPU.prototype.opSTAzp = function() { // opcode 0x85 // EA = BYTE(PC++); this.regEAWrite = this.abMem[this.regPC++]; // ML = A; this.abMem[this.regEAWrite] = this.regA; // W = 1; // NOTE: Consider alternatives for tracking zero-page writes (eg, regEAWriteZP) }; /** * @this {C1PCPU} */ C1PCPU.prototype.opSTXzp = function() { // opcode 0x86 // EA = BYTE(PC++); this.regEAWrite = this.abMem[this.regPC++]; // ML = X; this.abMem[this.regEAWrite] = this.regX; // W = 1; // NOTE: Consider alternatives for tracking zero-page writes (eg, regEAWriteZP) }; /** * @this {C1PCPU} */ C1PCPU.prototype.opDEY = function() { // opcode 0x88 // Y = ((Y - 1) & 0xff); this.regY = ((this.regY - 1) & 0xff); // SET_LAZY_NZ(Y); this.regRN = this.regRZ = (this.regY); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opTXA = function() { // opcode 0x8a // A = X; SET_LAZY_NZ(X); this.regRN = this.regRZ = this.regA = this.regX; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opSTYabs = function() { // opcode 0x8c // EA = WORD(PC); PC += 2; this.regEAWrite = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)); // ML = Y; this.abMem[this.regEAWrite] = this.regY; // W = 1; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opSTAabs = function() { // opcode 0x8d // EA = WORD(PC); PC += 2; this.regEAWrite = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)); // ML = A; this.abMem[this.regEAWrite] = this.regA; // W = 1; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opSTXabs = function() { // opcode 0x8e // EA = WORD(PC); PC += 2; this.regEAWrite = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)); // ML = X; this.abMem[this.regEAWrite] = this.regX; // W = 1; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opBCC = function() { // opcode 0x90 // PC = PC + (LAZY_C == 0? SBYTE(PC) : 0) + 1; this.regPC += (!(this.regRC & 0x0100)? (this.nStepCycles--,((this.abMem[this.regPC] << 24) >> 24)) : 0) + 1; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opSTAindy = function() { // opcode 0x91 // EA = WORD(BYTE(PC++))+Y; this.regEAWrite = (this.abMem[this.regPC++]); this.regEAWrite = (this.abMem[this.regEAWrite] | (this.abMem[this.regEAWrite+1] << 8)) + this.regY; // ML = A; this.abMem[this.regEAWrite] = this.regA; // W = 1; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opSTYzpx = function() { // opcode 0x94 // EA = (BYTE(PC++)+X) & 0xff; this.regEAWrite = (this.abMem[this.regPC++]+this.regX) & 0xff; // ML = Y; this.abMem[this.regEAWrite] = this.regY; // W = 1; // NOTE: Consider alternatives for tracking zero-page writes (eg, regEAWriteZP) }; /** * @this {C1PCPU} */ C1PCPU.prototype.opSTAzpx = function() { // opcode 0x95 // EA = (BYTE(PC++)+X) & 0xff; this.regEAWrite = (this.abMem[this.regPC++]+this.regX) & 0xff; // ML = A; this.abMem[this.regEAWrite] = this.regA; // W = 1; // NOTE: Consider alternatives for tracking zero-page writes (eg, regEAWriteZP) }; /** * @this {C1PCPU} */ C1PCPU.prototype.opSTXzpy = function() { // opcode 0x96 // EA = (BYTE(PC++)+Y) & 0xff; this.regEAWrite = (this.abMem[this.regPC++]+this.regY) & 0xff; // ML = X; this.abMem[this.regEAWrite] = this.regX; // W = 1; // NOTE: Consider alternatives for tracking zero-page writes (eg, regEAWriteZP) }; /** * @this {C1PCPU} */ C1PCPU.prototype.opTYA = function() { // opcode 0x98 // A = Y; SET_LAZY_NZ(Y); this.regRN = this.regRZ = this.regA = this.regY; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opSTAabsy = function() { // opcode 0x99 // EA = WORD(PC)+Y; PC += 2; this.regEAWrite = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regY; // ML = A; this.abMem[this.regEAWrite] = this.regA; // W = 1; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opTXS = function() { // opcode 0x9a // S = X; this.regS = this.regX | 0x100; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opSTAabsx = function() { // opcode 0x9d // EA = WORD(PC)+X; PC += 2; this.regEAWrite = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regX; // ML = A; this.abMem[this.regEAWrite] = this.regA; // W = 1; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opLDYimm = function() { // opcode 0xa0 // EA = PC++; this.regEA = this.regPC++; // Y = ML; SET_LAZY_NZ(ML); this.regRN = this.regRZ = this.regY = this.abMem[this.regEA]; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opLDAindx = function() { // opcode 0xa1 // EA = WORD((BYTE(PC++)+X) & 0xff); this.regEA = ((this.abMem[this.regPC++]) + this.regX) & 0xff; this.regEA = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8)); // A = ML; SET_LAZY_NZ(A); this.regRN = this.regRZ = this.regA = this.abMem[this.regEA]; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opLDXimm = function() { // opcode 0xa2 // EA = PC++; this.regEA = this.regPC++; // X = ML; SET_LAZY_NZ(ML); this.regRN = this.regRZ = this.regX = this.abMem[this.regEA]; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opLDYzp = function() { // opcode 0xa4 // EA = BYTE(PC++); this.regEA = this.abMem[this.regPC++]; // Y = ML; SET_LAZY_NZ(ML); this.regRN = this.regRZ = this.regY = this.abMem[this.regEA]; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opLDAzp = function() { // opcode 0xa5 // EA = BYTE(PC++); this.regEA = this.abMem[this.regPC++]; // A = ML; SET_LAZY_NZ(ML); this.regRN = this.regRZ = this.regA = this.abMem[this.regEA]; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opLDXzp = function() { // opcode 0xa6 // EA = BYTE(PC++); this.regEA = this.abMem[this.regPC++]; // X = ML; SET_LAZY_NZ(ML); this.regRN = this.regRZ = this.regX = this.abMem[this.regEA]; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opTAY = function() { // opcode 0xa8 // Y = A; SET_LAZY_NZ(A); this.regRN = this.regRZ = this.regY = this.regA; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opLDAimm = function() { // opcode 0xa9 // EA = PC++; this.regEA = this.regPC++; // A = ML; SET_LAZY_NZ(A); this.regRN = this.regRZ = this.regA = this.abMem[this.regEA]; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opTAX = function() { // opcode 0xaa // X = A; SET_LAZY_NZ(A); this.regRN = this.regRZ = this.regX = this.regA; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opLDYabs = function() { // opcode 0xac // EA = WORD(PC); PC += 2; this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)); // Y = ML; SET_LAZY_NZ(ML); this.regRN = this.regRZ = this.regY = this.abMem[this.regEA]; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opLDAabs = function() { // opcode 0xad // EA = WORD(PC); PC += 2; this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)); // A = ML; SET_LAZY_NZ(A); this.regRN = this.regRZ = this.regA = this.abMem[this.regEA]; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opLDXabs = function() { // opcode 0xae // EA = WORD(PC); PC += 2; this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)); // X = ML; SET_LAZY_NZ(ML); this.regRN = this.regRZ = this.regX = this.abMem[this.regEA]; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opBCS = function() { // opcode 0xb0 // PC = PC + (LAZY_C != 0? SBYTE(PC) : 0) + 1; this.regPC += ((this.regRC & 0x0100)? (this.nStepCycles--,((this.abMem[this.regPC] << 24) >> 24)) : 0) + 1; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opLDAindy = function() { // opcode 0xb1 // EA = WORD(BYTE(PC++))+Y; this.regEA = (this.abMem[this.regPC++]); this.regEA = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8)) + this.regY; // A = ML; SET_LAZY_NZ(A); this.regRN = this.regRZ = this.regA = this.abMem[this.regEA]; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opLDYzpx = function() { // opcode 0xb4 // EA = (BYTE(PC++)+X) & 0xff; this.regEA = (this.abMem[this.regPC++]+this.regX) & 0xff; // Y = ML; SET_LAZY_NZ(ML); this.regRN = this.regRZ = this.regY = this.abMem[this.regEA]; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opLDAzpx = function() { // opcode 0xb5 // EA = (BYTE(PC++)+X) & 0xff; this.regEA = (this.abMem[this.regPC++]+this.regX) & 0xff; // A = ML; SET_LAZY_NZ(A); this.regRN = this.regRZ = this.regA = this.abMem[this.regEA]; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opLDXzpy = function() { // opcode 0xb6 // EA = (BYTE(PC++)+Y) & 0xff; this.regEA = (this.abMem[this.regPC++]+this.regY) & 0xff; // X = ML; SET_LAZY_NZ(ML); this.regRN = this.regRZ = this.regX = this.abMem[this.regEA]; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opCLV = function() { // opcode 0xb8 // SET_LAZY_V(0); this.regRV = 0; this.regRU = 0; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opLDAabsy = function() { // opcode 0xb9 // EA = WORD(PC)+Y; PC += 2; this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regY; // A = ML; SET_LAZY_NZ(A); this.regRN = this.regRZ = this.regA = this.abMem[this.regEA]; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opTSX = function() { // opcode 0xba // X = S; SET_LAZY_NZ(S); this.regRN = this.regRZ = this.regX = this.regS & 0xff; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opLDYabsx = function() { // opcode 0xbc // EA = WORD(PC)+X; PC += 2; this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regX; // Y = ML; SET_LAZY_NZ(ML); this.regRN = this.regRZ = this.regY = this.abMem[this.regEA]; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opLDAabsx = function() { // opcode 0xbd // EA = WORD(PC)+X; PC += 2; this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regX; // A = ML; SET_LAZY_NZ(A); this.regRN = this.regRZ = this.regA = this.abMem[this.regEA]; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opLDXabsy = function() { // opcode 0xbe // EA = WORD(PC)+Y; PC += 2; this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regY; // X = ML; SET_LAZY_NZ(ML); this.regRN = this.regRZ = this.regX = this.abMem[this.regEA]; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opCPYimm = function() { // opcode 0xc0 // EA = PC++; this.regEA = this.regPC++; // RC = Y - ML; this.regRC = this.regY - this.abMem[this.regEA]; // SET_LAZY_NZ(RC); this.regRN = this.regRZ = (this.regRC); // SET_LAZY_C(!LAZY_C); this.regRC ^= 0x0100; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opCMPindx = function() { // opcode 0xc1 // EA = WORD((BYTE(PC++)+X) & 0xff); this.regEA = ((this.abMem[this.regPC++]) + this.regX) & 0xff; this.regEA = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8)); // RC = A - ML; SET_LAZY_NZ(RC); this.regRN = this.regRZ = this.regRC = (this.regA - this.abMem[this.regEA]); // SET_LAZY_C(!LAZY_C); this.regRC ^= 0x0100; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opCPYzp = function() { // opcode 0xc4 // EA = BYTE(PC++); this.regEA = this.abMem[this.regPC++]; // RC = Y - ML; this.regRC = this.regY - this.abMem[this.regEA]; // SET_LAZY_NZ(RC); this.regRN = this.regRZ = (this.regRC); // SET_LAZY_C(!LAZY_C); this.regRC ^= 0x0100; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opCMPzp = function() { // opcode 0xc5 // EA = BYTE(PC++); this.regEA = this.abMem[this.regPC++]; // RC = A - ML; SET_LAZY_NZ(RC); this.regRN = this.regRZ = this.regRC = (this.regA - this.abMem[this.regEA]); // SET_LAZY_C(!LAZY_C); this.regRC ^= 0x0100; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opDECzp = function() { // opcode 0xc6 // EA = BYTE(PC++); this.regEAWrite = this.abMem[this.regPC++]; // ML = ML - 1; SET_LAZY_NZ(ML); this.regRN = this.regRZ = this.abMem[this.regEAWrite] = ((this.abMem[this.regEAWrite] - 1) & 0xff); // W = 1; // NOTE: Consider alternatives for tracking zero-page writes (eg, regEAWriteZP) }; /** * @this {C1PCPU} */ C1PCPU.prototype.opINY = function() { // opcode 0xc8 // Y = ((Y + 1) & 0xff); this.regY = ((this.regY + 1) & 0xff); // SET_LAZY_NZ(Y); this.regRN = this.regRZ = (this.regY); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opCMPimm = function() { // opcode 0xc9 // EA = PC++; this.regEA = this.regPC++; // RC = A - ML; SET_LAZY_NZ(RC); this.regRN = this.regRZ = this.regRC = (this.regA - this.abMem[this.regEA]); // SET_LAZY_C(!LAZY_C); this.regRC ^= 0x0100; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opDEX = function() { // opcode 0xca // X = ((X - 1) & 0xff); SET_LAZY_NZ(X); this.regRN = this.regRZ = this.regX = ((this.regX - 1) & 0xff); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opCPYabs = function() { // opcode 0xcc // EA = WORD(PC); PC += 2; this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)); // RC = Y - ML; SET_LAZY_NZ(RC); this.regRN = this.regRZ = this.regRC = this.regY - this.abMem[this.regEA]; // SET_LAZY_C(!LAZY_C); this.regRC ^= 0x0100; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opCMPabs = function() { // opcode 0xcd // EA = WORD(PC); PC += 2; this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)); // RC = A - ML; SET_LAZY_NZ(RC); this.regRN = this.regRZ = this.regRC = (this.regA - this.abMem[this.regEA]); // SET_LAZY_C(!LAZY_C); this.regRC ^= 0x0100; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opDECabs = function() { // opcode 0xce // EA = WORD(PC); PC += 2; this.regEAWrite = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)); // ML = ML - 1; SET_LAZY_NZ(ML); this.regRN = this.regRZ = this.abMem[this.regEAWrite] = ((this.abMem[this.regEAWrite] - 1) & 0xff); // W = 1; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opBNE = function() { // opcode 0xd0 // PC = PC + (LAZY_Z == 0? SBYTE(PC) : 0) + 1; this.regPC += ((this.regRZ & 0xff)? (this.nStepCycles--,((this.abMem[this.regPC] << 24) >> 24)) : 0) + 1; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opCMPindy = function() { // opcode 0xd1 // EA = WORD(BYTE(PC++))+Y; this.regEA = (this.abMem[this.regPC++]); this.regEA = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8)) + this.regY; // RC = A - ML; SET_LAZY_NZ(RC); this.regRN = this.regRZ = this.regRC = (this.regA - this.abMem[this.regEA]); // SET_LAZY_C(!LAZY_C); this.regRC ^= 0x0100; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opCMPzpx = function() { // opcode 0xd5 // EA = (BYTE(PC++)+X) & 0xff; this.regEA = (this.abMem[this.regPC++]+this.regX) & 0xff; // RC = A - ML; SET_LAZY_NZ(RC); this.regRN = this.regRZ = this.regRC = (this.regA - this.abMem[this.regEA]); // SET_LAZY_C(!LAZY_C); this.regRC ^= 0x0100; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opDECzpx = function() { // opcode 0xd6 // EA = (BYTE(PC++)+X) & 0xff; this.regEAWrite = (this.abMem[this.regPC++]+this.regX) & 0xff; // ML = ML - 1; SET_LAZY_NZ(ML); this.regRN = this.regRZ = this.abMem[this.regEAWrite] = ((this.abMem[this.regEAWrite] - 1) & 0xff); // W = 1; // NOTE: Consider alternatives for tracking zero-page writes (eg, regEAWriteZP) }; /** * @this {C1PCPU} */ C1PCPU.prototype.opCLD = function() { // opcode 0xd8 // D = 0; this.clearBCD(); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opCMPabsy = function() { // opcode 0xd9 // EA = WORD(PC)+Y; PC += 2; this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regY; // RC = A - ML; SET_LAZY_NZ(RC); this.regRN = this.regRZ = this.regRC = (this.regA - this.abMem[this.regEA]); // SET_LAZY_C(!LAZY_C); this.regRC ^= 0x0100; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opCMPabsx = function() { // opcode 0xdd // EA = WORD(PC)+X; PC += 2; this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regX; // RC = A - ML; SET_LAZY_NZ(RC); this.regRN = this.regRZ = this.regRC = (this.regA - this.abMem[this.regEA]); // SET_LAZY_C(!LAZY_C); this.regRC ^= 0x0100; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opDECabsx = function() { // opcode 0xde // EA = WORD(PC)+X; PC += 2; this.regEAWrite = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regX; // ML = ML - 1; SET_LAZY_NZ(ML); this.regRN = this.regRZ = this.abMem[this.regEAWrite] = ((this.abMem[this.regEAWrite] - 1) & 0xff); // W = 1; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opCPXimm = function() { // opcode 0xe0 // EA = PC++; this.regEA = this.regPC++; // RC = X - ML; this.regRC = this.regX - this.abMem[this.regEA]; // SET_LAZY_NZ(RC); this.regRN = this.regRZ = (this.regRC); // SET_LAZY_C(!LAZY_C); this.regRC ^= 0x0100; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opSBCindx = function() { // opcode 0xe1 // EA = WORD((BYTE(PC++)+X) & 0xff); this.regEA = ((this.abMem[this.regPC++]) + this.regX) & 0xff; this.regEA = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8)); // RC = (A - ML - !LAZY_C); this.regRC = (this.regA - this.abMem[this.regEA] - ((this.regRC & 0x0100)? 0 : 1)); // SET_LAZY_OV(A,ML,RC); this.regRU = this.regA ^ this.abMem[this.regEA]; this.regRV = this.regRC; // A = RCL; SET_LAZY_NZ(RCL); this.regRN = this.regRZ = this.regA = (this.regRC & 0xff); // SET_LAZY_C(!LAZY_C); this.regRC ^= 0x0100; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opSBCindxBCD = function() { // opcode 0xe1 // EA = WORD((BYTE(PC++)+X) & 0xff); this.regEA = ((this.abMem[this.regPC++]) + this.regX) & 0xff; this.regEA = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8)); // A = this.subBCD(A,ML); this.regA = this.subBCD(this.regA, this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opCPXzp = function() { // opcode 0xe4 // EA = BYTE(PC++); this.regEA = this.abMem[this.regPC++]; // RC = X - ML; this.regRC = this.regX - this.abMem[this.regEA]; // SET_LAZY_NZ(RC); this.regRN = this.regRZ = (this.regRC); // SET_LAZY_C(!LAZY_C); this.regRC ^= 0x0100; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opSBCzp = function() { // opcode 0xe5 // EA = BYTE(PC++); this.regEA = this.abMem[this.regPC++]; // RC = (A - ML - !LAZY_C); this.regRC = (this.regA - this.abMem[this.regEA] - ((this.regRC & 0x0100)? 0 : 1)); // SET_LAZY_OV(A,ML,RC); this.regRU = this.regA ^ this.abMem[this.regEA]; this.regRV = this.regRC; // A = RCL; SET_LAZY_NZ(RCL); this.regRN = this.regRZ = this.regA = (this.regRC & 0xff); // SET_LAZY_C(!LAZY_C); this.regRC ^= 0x0100; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opSBCzpBCD = function() { // opcode 0xe5 // EA = BYTE(PC++); this.regEA = this.abMem[this.regPC++]; // A = this.subBCD(A,ML); this.regA = this.subBCD(this.regA, this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opINCzp = function() { // opcode 0xe6 // EA = BYTE(PC++); this.regEAWrite = this.abMem[this.regPC++]; // ML = ML + 1; SET_LAZY_NZ(ML); this.regRN = this.regRZ = this.abMem[this.regEAWrite] = ((this.abMem[this.regEAWrite] + 1) & 0xff); // W = 1; // NOTE: Consider alternatives for tracking zero-page writes (eg, regEAWriteZP) }; /** * @this {C1PCPU} */ C1PCPU.prototype.opINX = function() { // opcode 0xe8 // X = ((X + 1) & 0xff); this.regX = ((this.regX + 1) & 0xff); // SET_LAZY_NZ(X); this.regRN = this.regRZ = (this.regX); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opSBCimm = function() { // opcode 0xe9 // EA = PC++; this.regEA = this.regPC++; // RC = (A - ML - !LAZY_C); this.regRC = (this.regA - this.abMem[this.regEA] - ((this.regRC & 0x0100)? 0 : 1)); // SET_LAZY_OV(A,ML,RC); this.regRU = this.regA ^ this.abMem[this.regEA]; this.regRV = this.regRC; // A = RCL; SET_LAZY_NZ(RCL); this.regRN = this.regRZ = this.regA = (this.regRC & 0xff); // SET_LAZY_C(!LAZY_C); this.regRC ^= 0x0100; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opSBCimmBCD = function() { // opcode 0xe9 // EA = PC++; this.regEA = this.regPC++; // A = this.subBCD(A,ML); this.regA = this.subBCD(this.regA, this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opNOP = function() { // opcode 0xea // }; /** * @this {C1PCPU} */ C1PCPU.prototype.opCPXabs = function() { // opcode 0xec // EA = WORD(PC); PC += 2; this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)); // RC = X - ML; SET_LAZY_NZ(RC); this.regRN = this.regRZ = this.regRC = this.regX - this.abMem[this.regEA]; // SET_LAZY_C(!LAZY_C); this.regRC ^= 0x0100; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opSBCabs = function() { // opcode 0xed // EA = WORD(PC); PC += 2; this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)); // RC = (A - ML - !LAZY_C); this.regRC = (this.regA - this.abMem[this.regEA] - ((this.regRC & 0x0100)? 0 : 1)); // SET_LAZY_OV(A,ML,RC); this.regRU = this.regA ^ this.abMem[this.regEA]; this.regRV = this.regRC; // A = RCL; SET_LAZY_NZ(RCL); this.regRN = this.regRZ = this.regA = (this.regRC & 0xff); // SET_LAZY_C(!LAZY_C); this.regRC ^= 0x0100; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opSBCabsBCD = function() { // opcode 0xed // EA = WORD(PC); PC += 2; this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)); // A = this.subBCD(A,ML); this.regA = this.subBCD(this.regA, this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opINCabs = function() { // opcode 0xee // EA = WORD(PC); PC += 2; this.regEAWrite = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)); // ML = ML + 1; SET_LAZY_NZ(ML); this.regRN = this.regRZ = this.abMem[this.regEAWrite] = ((this.abMem[this.regEAWrite] + 1) & 0xff); // W = 1; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opBEQ = function() { // opcode 0xf0 // PC = PC + (LAZY_Z == 1? SBYTE(PC) : 0) + 1; this.regPC += (!(this.regRZ & 0xff)? (this.nStepCycles--,((this.abMem[this.regPC] << 24) >> 24)) : 0) + 1; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opSBCindy = function() { // opcode 0xf1 // EA = WORD(BYTE(PC++))+Y; this.regEA = (this.abMem[this.regPC++]); this.regEA = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8)) + this.regY; // RC = (A - ML - !LAZY_C); this.regRC = (this.regA - this.abMem[this.regEA] - ((this.regRC & 0x0100)? 0 : 1)); // SET_LAZY_OV(A,ML,RC); this.regRU = this.regA ^ this.abMem[this.regEA]; this.regRV = this.regRC; // A = RCL; SET_LAZY_NZ(RCL); this.regRN = this.regRZ = this.regA = (this.regRC & 0xff); // SET_LAZY_C(!LAZY_C); this.regRC ^= 0x0100; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opSBCindyBCD = function() { // opcode 0xf1 // EA = WORD(BYTE(PC++))+Y; this.regEA = (this.abMem[this.regPC++]); this.regEA = (this.abMem[this.regEA] | (this.abMem[this.regEA+1] << 8)) + this.regY; // A = this.subBCD(A,ML); this.regA = this.subBCD(this.regA, this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opSBCzpx = function() { // opcode 0xf5 // EA = (BYTE(PC++)+X) & 0xff; this.regEA = (this.abMem[this.regPC++]+this.regX) & 0xff; // RC = (A - ML - !LAZY_C); this.regRC = (this.regA - this.abMem[this.regEA] - ((this.regRC & 0x0100)? 0 : 1)); // SET_LAZY_OV(A,ML,RC); this.regRU = this.regA ^ this.abMem[this.regEA]; this.regRV = this.regRC; // A = RCL; SET_LAZY_NZ(RCL); this.regRN = this.regRZ = this.regA = (this.regRC & 0xff); // SET_LAZY_C(!LAZY_C); this.regRC ^= 0x0100; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opSBCzpxBCD = function() { // opcode 0xf5 // EA = (BYTE(PC++)+X) & 0xff; this.regEA = (this.abMem[this.regPC++]+this.regX) & 0xff; // A = this.subBCD(A,ML); this.regA = this.subBCD(this.regA, this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opINCzpx = function() { // opcode 0xf6 // EA = (BYTE(PC++)+X) & 0xff; this.regEAWrite = (this.abMem[this.regPC++]+this.regX) & 0xff; // ML = ML + 1; SET_LAZY_NZ(ML); this.regRN = this.regRZ = this.abMem[this.regEAWrite] = ((this.abMem[this.regEAWrite] + 1) & 0xff); // W = 1; // NOTE: Consider alternatives for tracking zero-page writes (eg, regEAWriteZP) }; /** * @this {C1PCPU} */ C1PCPU.prototype.opSED = function() { // opcode 0xf8 // D = 1; this.setBCD(); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opSBCabsy = function() { // opcode 0xf9 // EA = WORD(PC)+Y; PC += 2; this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regY; // RC = (A - ML - !LAZY_C); this.regRC = (this.regA - this.abMem[this.regEA] - ((this.regRC & 0x0100)? 0 : 1)); // SET_LAZY_OV(A,ML,RC); this.regRU = this.regA ^ this.abMem[this.regEA]; this.regRV = this.regRC; // A = RCL; SET_LAZY_NZ(RCL); this.regRN = this.regRZ = this.regA = (this.regRC & 0xff); // SET_LAZY_C(!LAZY_C); this.regRC ^= 0x0100; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opSBCabsyBCD = function() { // opcode 0xf9 // EA = WORD(PC)+Y; PC += 2; this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regY; // A = this.subBCD(A,ML); this.regA = this.subBCD(this.regA, this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opSBCabsx = function() { // opcode 0xfd // EA = WORD(PC)+X; PC += 2; this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regX; // RC = (A - ML - !LAZY_C); this.regRC = (this.regA - this.abMem[this.regEA] - ((this.regRC & 0x0100)? 0 : 1)); // SET_LAZY_OV(A,ML,RC); this.regRU = this.regA ^ this.abMem[this.regEA]; this.regRV = this.regRC; // A = RCL; SET_LAZY_NZ(RCL); this.regRN = this.regRZ = this.regA = (this.regRC & 0xff); // SET_LAZY_C(!LAZY_C); this.regRC ^= 0x0100; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opSBCabsxBCD = function() { // opcode 0xfd // EA = WORD(PC)+X; PC += 2; this.regEA = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regX; // A = this.subBCD(A,ML); this.regA = this.subBCD(this.regA, this.abMem[this.regEA]); }; /** * @this {C1PCPU} */ C1PCPU.prototype.opINCabsx = function() { // opcode 0xfe // EA = WORD(PC)+X; PC += 2; this.regEAWrite = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)) + this.regX; // ML = ML + 1; SET_LAZY_NZ(ML); this.regRN = this.regRZ = this.abMem[this.regEAWrite] = ((this.abMem[this.regEAWrite] + 1) & 0xff); // W = 1; }; /** * @this {C1PCPU} */ C1PCPU.prototype.opSim = function() { var addr; var bSimOp = this.abMem[this.regPC++]; switch(bSimOp) { case this.SIMOP_HLT: this.println("HALT"); this.halt(); break; case this.SIMOP_MSG: addr = this.regPC; // currently we're using "inline" strings // addr = (this.abMem[this.regPC++] | (this.abMem[this.regPC++] << 8)); var s = ""; while (addr < this.abMem.length) { var b = this.abMem[addr++]; if (!b) break; s += String.fromCharCode(b); } this.regPC = addr; // update regPC as long as we're doing "inline" strings /* * Before simply printing the string, what kinds of handy substitutions should we provide? * * eg: %A for this.regA, %X for this.regX, etc */ s = s.replace(/%A/g, str.toHex(this.regA, 2)).replace(/%X/g, str.toHex(this.regX, 2)).replace(/%Y/g, str.toHex(this.regY, 2)); this.println(s); /* * To make printing "smoother", let's force a yield */ this.yieldCPU(); break; default: this.regPC -= 2; this.println("undefined opSim: " + str.toHexByte(bSimOp) + " at " + str.toHexWord(this.regPC)); this.halt(); } }; /** * @this {C1PCPU} */ C1PCPU.prototype.opUndefined = function() { var b = this.abMem[--this.regPC]; this.println("undefined opcode: " + str.toHexByte(b) + " at " + str.toHexWord(this.regPC)); this.halt(); }; /** * C1PCPU.init() * * This function operates on every HTML element of class "cpu", extracting the * JSON-encoded parameters for the C1PCPU constructor from the element's "data-value" * attribute, invoking the constructor to create a C1PCPU component, and then binding * any associated HTML controls to the new component. */ C1PCPU.init = function() { var aeCPUs = Component.getElementsByClass(document, C1PJSCLASS, "cpu"); for (var iCPU=0; iCPU < aeCPUs.length; iCPU++) { var eCPU = aeCPUs[iCPU]; var parmsCPU = Component.getComponentParms(eCPU); var cpu = new C1PCPU(parmsCPU); Component.bindComponentControls(cpu, eCPU, C1PJSCLASS); } }; /* * Initialize every CPU module on the page (as IF there's ever going to be more than one ;-)) */ web.onInit(C1PCPU.init);