3894 lines
122 KiB
JavaScript
3894 lines
122 KiB
JavaScript
/**
|
|
* @fileoverview This file implements the C1Pjs 6502 CPU component.
|
|
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
|
* @version 1.0
|
|
* Created 2012-Jun-15
|
|
*
|
|
* Copyright © 2012-2016 Jeff Parsons <Jeff@pcjs.org>
|
|
*
|
|
* This file is part of C1Pjs, which is part of the JavaScript Machines Project (aka JSMachines)
|
|
* at <http://jsmachines.net/> and <http://pcjs.org/>.
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/gpl.html>.
|
|
*
|
|
* 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.aFlags.fPowered = false;
|
|
this.aFlags.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.aFlags.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.aFlags.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.aFlags.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.aFlags.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.aFlags.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.aFlags.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.aFlags.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.aFlags.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.aFlags.fRunning) {
|
|
this.aFlags.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.aFlags.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 (<address>), is partially broken.
|
|
* If <address> 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(window.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);
|