From 5a99dc7293a69792a59db273b59446898503f8c9 Mon Sep 17 00:00:00 2001 From: Karol Stasiak Date: Sun, 16 Dec 2018 19:34:33 +0100 Subject: [PATCH] 65816: various improvements --- .gitignore | 1 - .../scala/millfork/assembly/mos/Opcode.scala | 7 + .../mos/opt/SixteenOptimizations.scala | 10 +- .../compiler/mos/MosExpressionCompiler.scala | 116 +- .../compiler/mos/PseudoregisterBuiltIns.scala | 58 +- src/test/resources/cpu.js | 4097 +++++++++++++++++ src/test/resources/orig.cpu.js | 4095 ++++++++++++++++ .../scala/millfork/test/WordMathSuite.scala | 44 +- .../millfork/test/emu/EmuBenchmarkRun.scala | 3 + .../test/emu/EmuNative65816BenchmarkRun.scala | 22 + .../test/emu/EmuOptimized65816Run.scala | 12 + src/test/scala/millfork/test/emu/EmuRun.scala | 20 + .../millfork/test/emu/EmuUnoptimizedRun.scala | 4 + .../millfork/test/emu/NashornEmulator.scala | 111 + 14 files changed, 8564 insertions(+), 36 deletions(-) create mode 100644 src/test/resources/cpu.js create mode 100644 src/test/resources/orig.cpu.js create mode 100644 src/test/scala/millfork/test/emu/EmuNative65816BenchmarkRun.scala create mode 100644 src/test/scala/millfork/test/emu/NashornEmulator.scala diff --git a/.gitignore b/.gitignore index 627c21d2..2e5423b5 100644 --- a/.gitignore +++ b/.gitignore @@ -16,7 +16,6 @@ examples/lunix/ *.jar *.class *.zip -*.js # compiled Millfork files *.prg diff --git a/src/main/scala/millfork/assembly/mos/Opcode.scala b/src/main/scala/millfork/assembly/mos/Opcode.scala index 9e4c8441..609b8a4a 100644 --- a/src/main/scala/millfork/assembly/mos/Opcode.scala +++ b/src/main/scala/millfork/assembly/mos/Opcode.scala @@ -152,6 +152,13 @@ object Opcode extends Enumeration { case ASL => Some(ASL_W) case LSR => Some(LSR_W) + case PHA => Some(PHA_W) + case PLA => Some(PLA_W) + case PHX => Some(PHX_W) + case PLX => Some(PLX_W) + case PHY => Some(PHY_W) + case PLY => Some(PLY_W) + case _ => None } diff --git a/src/main/scala/millfork/assembly/mos/opt/SixteenOptimizations.scala b/src/main/scala/millfork/assembly/mos/opt/SixteenOptimizations.scala index 03ec4d44..79d0341e 100644 --- a/src/main/scala/millfork/assembly/mos/opt/SixteenOptimizations.scala +++ b/src/main/scala/millfork/assembly/mos/opt/SixteenOptimizations.scala @@ -14,20 +14,23 @@ object SixteenOptimizations { val AccumulatorSwapping = new RuleBasedAssemblyOptimization("Accumulator swapping", needsFlowInfo = FlowInfoRequirement.BothFlows, (Elidable & HasOpcode(PHA) & HasAccu8 & DoesntMatterWhatItDoesWith(State.AH, State.A, State.N, State.Z)) ~ - (Linear & Not(ConcernsStack)) ~ + (Linear & Not(ConcernsStack)).* ~ (Elidable & HasOpcode(PLA) & DoesntMatterWhatItDoesWith(State.AH)) ~~> { code => AssemblyLine.implied(XBA) :: (code.tail.init :+ AssemblyLine.implied(XBA)) }, (Elidable & HasOpcode(TAX) & HasAccu8 & HasIndex8 & DoesntMatterWhatItDoesWith(State.AH, State.A, State.N, State.Z)) ~ - (Linear & Not(ConcernsX)) ~ + (Linear & Not(ConcernsX)).* ~ (Elidable & HasOpcode(TXA) & DoesntMatterWhatItDoesWith(State.AH, State.X)) ~~> { code => AssemblyLine.implied(XBA) :: (code.tail.init :+ AssemblyLine.implied(XBA)) }, (Elidable & HasOpcode(TAY) & HasAccu8 & HasIndex8 & DoesntMatterWhatItDoesWith(State.AH, State.A, State.N, State.Z)) ~ - (Linear & Not(ConcernsY)) ~ + (Linear & Not(ConcernsY)).* ~ (Elidable & HasOpcode(TYA) & DoesntMatterWhatItDoesWith(State.AH, State.Y)) ~~> { code => AssemblyLine.implied(XBA) :: (code.tail.init :+ AssemblyLine.implied(XBA)) }, + (Elidable & HasOpcode(XBA) & DoesntMatterWhatItDoesWith(State.A, State.AH, State.N, State.Z)) ~~> { code => + Nil + }, ) val RepSepWeakening = new RuleBasedAssemblyOptimization("REP/SEP weakening", @@ -121,6 +124,7 @@ object SixteenOptimizations { val PointlessLoadAfterLoadOrStore = new RuleBasedAssemblyOptimization("Pointless 16-bit load after load or store", needsFlowInfo = FlowInfoRequirement.NoRequirement, + // TODO: flags! (HasOpcodeIn(LDA_W, STA_W) & HasAddrMode(WordImmediate) & MatchParameter(1)) ~ (Linear & Not(ChangesA) & Not(ChangesAH)).* ~ diff --git a/src/main/scala/millfork/compiler/mos/MosExpressionCompiler.scala b/src/main/scala/millfork/compiler/mos/MosExpressionCompiler.scala index e6e122d7..ff7877a7 100644 --- a/src/main/scala/millfork/compiler/mos/MosExpressionCompiler.scala +++ b/src/main/scala/millfork/compiler/mos/MosExpressionCompiler.scala @@ -45,7 +45,9 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { case 1 => List( AssemblyLine(LDA, Immediate, expr.loByte), AssemblyLine(STA, addrMode, addr)) - case 2 => List( + case 2 => if (ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) { + AssemblyLine.accu16 :: AssemblyLine(LDA_W, WordImmediate, expr) ::(AssemblyLine.variable(ctx, STA_W, m) :+ AssemblyLine.accu8) + } else List( AssemblyLine(LDA, Immediate, expr.loByte), AssemblyLine(STA, addrMode, addr), AssemblyLine(LDA, Immediate, expr.hiByte), @@ -54,13 +56,15 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { AssemblyLine(LDA, Immediate, expr.subbyte(i)), AssemblyLine(STA, addrMode, addr + i))).flatten } - case StackVariable(_, t, offset) => + case m@StackVariable(_, t, offset) => t.size match { case 0 => Nil case 1 => AssemblyLine.tsx(ctx) ++ List( AssemblyLine.immediate(LDA, expr.loByte), AssemblyLine.dataStackX(ctx, STA, offset)) - case 2 => AssemblyLine.tsx(ctx) ++ List( + case 2 => if (ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) { + AssemblyLine.accu16 :: AssemblyLine(LDA_W, WordImmediate, expr) :: (AssemblyLine.variable(ctx, STA_W, m) :+ AssemblyLine.accu8) + } else AssemblyLine.tsx(ctx) ++ List( AssemblyLine.implied(TSX), AssemblyLine.immediate(LDA, expr.loByte), AssemblyLine.dataStackX(ctx, STA, offset), @@ -371,8 +375,11 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { AssemblyLine.implied(PLA)) } else List(AssemblyLine.immediate(LDX, 0), AssemblyLine.implied(XBA)) ++ AssemblyLine.variable(ctx, LDA, source) :+ AssemblyLine.immediate(LDX, 0) case 2 => - // TODO: use LDA_W - AssemblyLine.variable(ctx, LDA, source, 1) ++ List(AssemblyLine.implied(XBA)) ++ AssemblyLine.variable(ctx, LDA, source) + if (ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) { + AssemblyLine.accu16 :: (AssemblyLine.variable(ctx, LDA_W, source) :+ AssemblyLine.accu8) + } else { + AssemblyLine.variable(ctx, LDA, source, 1) ++ List(AssemblyLine.implied(XBA)) ++ AssemblyLine.variable(ctx, LDA, source) + } } case RegisterVariable(MosRegister.X, _) => AssemblyLine.variable(ctx, LDX, source) case RegisterVariable(MosRegister.Y, _) => AssemblyLine.variable(ctx, LDY, source) @@ -422,6 +429,8 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { if (exprType.size > target.typ.size) { ctx.log.error(s"Variable `$target.name` is too small", expr.position) Nil + } else if (exprType.size == 2 && target.typ.size == 2 && ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) { + AssemblyLine.accu16 :: (AssemblyLine.variable(ctx, LDA_W, source) ++ AssemblyLine.variable(ctx, STA_W, target) :+ AssemblyLine.accu8) } else { val copyFromLo = List.tabulate(exprType.size)(i => AssemblyLine.variable(ctx, LDA, source, i) ++ AssemblyLine.variable(ctx, STA, target, i)) val copy = if (shouldCopyFromHiToLo(source.toAddress, target.toAddress)) copyFromLo.reverse else copyFromLo @@ -437,6 +446,8 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { if (exprType.size > target.typ.size) { ctx.log.error(s"Variable `$target.name` is too small", expr.position) Nil + } else if (exprType.size == 2 && target.typ.size == 2 && ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) { + AssemblyLine.accu16 :: (AssemblyLine.variable(ctx, LDA_W, source) ++ AssemblyLine.variable(ctx, STA_W, target) :+ AssemblyLine.accu8) } else { val copy = List.tabulate(exprType.size)(i => AssemblyLine.variable(ctx, LDA, source, i) :+ AssemblyLine.dataStackX(ctx, STA, target, i)) val extend = if (exprType.size == target.typ.size) Nil else if (exprType.isSigned) { @@ -507,6 +518,8 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { if (exprType.size > target.typ.size) { ctx.log.error(s"Variable `$target.name` is too small", expr.position) Nil + } else if (exprType.size == 2 && target.typ.size == 2 && ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) { + AssemblyLine.accu16 :: (AssemblyLine.variable(ctx, LDA_W, source) ++ AssemblyLine.variable(ctx, STA_W, target) :+ AssemblyLine.accu8) } else { val copy = List.tabulate(exprType.size)(i => AssemblyLine.dataStackX(ctx, LDA, offset + i) :: AssemblyLine.variable(ctx, STA, target, i)) val extend = if (exprType.size == target.typ.size) Nil else if (exprType.isSigned) { @@ -521,6 +534,8 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { if (exprType.size > target.typ.size) { ctx.log.error(s"Variable `$target.name` is too small", expr.position) Nil + } else if (exprType.size == 2 && target.typ.size == 2 && ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) { + AssemblyLine.accu16 :: (AssemblyLine.variable(ctx, LDA_W, source) ++ AssemblyLine.variable(ctx, STA_W, target) :+ AssemblyLine.accu8) } else { val copyFromLo = List.tabulate(exprType.size)(i => List(AssemblyLine.dataStackX(ctx, LDA, offset + i), AssemblyLine.dataStackX(ctx, STA, target, i))) val copy = if (shouldCopyFromHiToLo(NumericConstant(source.baseOffset, 2), NumericConstant(target.baseOffset, 2))) copyFromLo.reverse else copyFromLo @@ -662,9 +677,15 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { calculate ++ store } case 2 => - val calculate = PseudoregisterBuiltIns.compileWordAdditionToAX(ctx, params, decimal = decimal) - val store = expressionStorageFromAX(ctx, exprTypeAndVariable, expr.position) - calculate ++ store + if (ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) { + val calculate = PseudoregisterBuiltIns.compileWordAdditionToAW(ctx, params, decimal = decimal) + val store = expressionStorageFromAW(ctx, exprTypeAndVariable, expr.position) + calculate ++ store + } else { + val calculate = PseudoregisterBuiltIns.compileWordAdditionToAX(ctx, params, decimal = decimal) + val store = expressionStorageFromAX(ctx, exprTypeAndVariable, expr.position) + calculate ++ store + } } } case SeparateBytesExpression(h, l) => @@ -1302,6 +1323,85 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { } } + def expressionStorageFromAW(ctx: CompilationContext, exprTypeAndVariable: Option[(Type, Variable)], position: Option[Position]): List[AssemblyLine] = { + exprTypeAndVariable.fold(noop) { + case (VoidType, _) => ctx.log.fatal("Cannot assign word to void", position) + case (_, RegisterVariable(MosRegister.A, _)) => noop + case (_, RegisterVariable(MosRegister.AW, _)) => noop + case (_, RegisterVariable(MosRegister.X, _)) => List(AssemblyLine.implied(TAX)) + case (_, RegisterVariable(MosRegister.Y, _)) => List(AssemblyLine.implied(TAY)) + case (_, RegisterVariable(MosRegister.AX, _)) => + // TODO: sign extension + List(AssemblyLine.implied(XBA), AssemblyLine.implied(TAX), AssemblyLine.implied(XBA)) + case (_, RegisterVariable(MosRegister.XA, _)) => + // TODO: sign extension + List(AssemblyLine.implied(TAX), AssemblyLine.implied(XBA)) + case (_, RegisterVariable(MosRegister.YA, _)) => + // TODO: sign extension + List(AssemblyLine.implied(TAY), AssemblyLine.implied(XBA)) + case (_, RegisterVariable(MosRegister.AY, _)) => + // TODO: sign extension + List(AssemblyLine.implied(XBA), AssemblyLine.implied(TAY), AssemblyLine.implied(XBA)) + case (t, v: VariableInMemory) => t.size match { + case 1 => v.typ.size match { + case 1 => + AssemblyLine.variable(ctx, STA, v) + case s if s > 1 => + if (t.isSigned) { + AssemblyLine.variable(ctx, STA, v) ++ signExtendA(ctx) ++ List.tabulate(s - 1)(i => AssemblyLine.variable(ctx, STA, v, i + 1)).flatten + } else { + AssemblyLine.variable(ctx, STA, v) ++ List(AssemblyLine.immediate(LDA, 0)) ++ + List.tabulate(s - 1)(i => AssemblyLine.variable(ctx, STA, v, i + 1)).flatten + } + } + case 2 => v.typ.size match { + case 1 => + ctx.log.error(s"Variable `${v.name}` cannot hold a word", position) + Nil + case 2 => + AssemblyLine.accu16 :: (AssemblyLine.variable(ctx, STA_W, v) :+ AssemblyLine.accu8) + case s if s > 2 => + if (t.isSigned) { + AssemblyLine.accu16 :: AssemblyLine.variable(ctx, STA_W, v) ++ + List(AssemblyLine.accu8, AssemblyLine.implied(XBA)) ++ + signExtendA(ctx) ++ + List.tabulate(s - 2)(i => AssemblyLine.variable(ctx, STA, v, i + 2)).flatten + } else { + AssemblyLine.accu16 :: AssemblyLine.variable(ctx, STA_W, v) + List(AssemblyLine.accu8, AssemblyLine.immediate(LDA, 0)) ++ + List.tabulate(s - 2)(i => AssemblyLine.variable(ctx, STA, v, i + 2)).flatten + } + } + } + case (t, v: StackVariable) => t.size match { + case 1 => v.typ.size match { + case 1 => + AssemblyLine.tsx(ctx) :+ AssemblyLine.dataStackX(ctx, STA, v) + case s if s > 1 => + AssemblyLine.tsx(ctx) ++ (if (t.isSigned) { + List( + AssemblyLine.dataStackX(ctx, STA, v.baseOffset)) ++ + signExtendA(ctx) ++ + List.tabulate(s - 1)(i => AssemblyLine.dataStackX(ctx, STA, v, i + 1)) + } else { + List( + AssemblyLine.dataStackX(ctx, STA, v.baseOffset), + AssemblyLine.immediate(LDA, 0)) ++ + List.tabulate(s - 1)(i => AssemblyLine.dataStackX(ctx, STA, v, i + 1)) + }) + } + case 2 => v.typ.size match { + case 1 => + ctx.log.error(s"Variable `${v.name}` cannot hold a word", position) + Nil + case 2 => + AssemblyLine.tsx(ctx) ++ List(AssemblyLine.accu16, AssemblyLine.dataStackX(ctx, STA_W, v), AssemblyLine.accu8) + case s if s > 2 => ??? + } + } + } + } + def compileAssignment(ctx: CompilationContext, source: Expression, target: LhsExpression): List[AssemblyLine] = { val env = ctx.env val sourceType = AbstractExpressionCompiler.checkAssignmentTypeAndGetSourceType(ctx, source, target) diff --git a/src/main/scala/millfork/compiler/mos/PseudoregisterBuiltIns.scala b/src/main/scala/millfork/compiler/mos/PseudoregisterBuiltIns.scala index 5d6cc572..043fbd50 100644 --- a/src/main/scala/millfork/compiler/mos/PseudoregisterBuiltIns.scala +++ b/src/main/scala/millfork/compiler/mos/PseudoregisterBuiltIns.scala @@ -45,7 +45,7 @@ object PseudoregisterBuiltIns { if (params.isEmpty) { return List(AssemblyLine.immediate(LDA, 0), AssemblyLine.immediate(LDX, 0)) } - val reg = ctx.env.get[VariableInMemory]("__reg") + val reg = ctx.env.get[VariableInMemory]("__reg.loword") val head = params.head match { case (false, e) => MosExpressionCompiler.compile(ctx, e, Some(MosExpressionCompiler.getExpressionType(ctx, e) -> reg), BranchSpec.None) case (true, e) => ??? @@ -56,6 +56,37 @@ object PseudoregisterBuiltIns { ) } + def compileWordAdditionToAW(ctx: CompilationContext, params: List[(Boolean, Expression)], decimal: Boolean): List[AssemblyLine] = { + // assume native16 is enabled and accu16 mode is disabled + val b = ctx.env.get[Type]("byte") + val w = ctx.env.get[Type]("word") + if (!decimal) { + val (variablePart, constPart) = ctx.env.evalVariableAndConstantSubParts(SumExpression(params, decimal = false)) + variablePart match { + case None => + return MosExpressionCompiler.compileConstant(ctx, constPart, RegisterVariable(MosRegister.AW, w)) + case Some(v) => + } + } + if (ctx.options.zpRegisterSize < 2) { + ctx.log.error("Word addition or subtraction requires the zeropage pseudoregister", params.headOption.flatMap(_._2.position)) + return Nil + } + if (params.isEmpty) { + return List(AssemblyLine.accu16, AssemblyLine.immediate(LDA_W, 0), AssemblyLine.accu8) + } + val reg = ctx.env.get[VariableInMemory]("__reg.loword") + val head = params.head match { + case (false, e) => MosExpressionCompiler.compile(ctx, e, Some(MosExpressionCompiler.getExpressionType(ctx, e) -> reg), BranchSpec.None) + case (true, e) => ??? + } + params.tail.foldLeft[List[AssemblyLine]](head){case (code, (sub, param)) => code ++ addToReg(ctx, param, sub, decimal)} ++ List( + AssemblyLine.accu16, + AssemblyLine.zeropage(LDA_W, reg), + AssemblyLine.accu8 + ) + } + def addToReg(ctx: CompilationContext, r: Expression, subtract: Boolean, decimal: Boolean): List[AssemblyLine] = { if (ctx.options.zpRegisterSize < 2) { ctx.log.error("Word addition or subtraction requires the zeropage pseudoregister", r.position) @@ -65,11 +96,13 @@ object PseudoregisterBuiltIns { ctx.log.error("Unsupported decimal operation. Consider increasing the size of the zeropage register.", r.position) return Nil } + val native16 = ctx.options.flag(CompilationFlag.EmitNative65816Opcodes) val b = ctx.env.get[Type]("byte") val w = ctx.env.get[Type]("word") val reg = ctx.env.get[VariableInMemory]("__reg.loword") // TODO: smarter on 65816 val op = if (subtract) SBC else ADC + val op16 = if (subtract) SBC_W else ADC_W val prepareCarry = AssemblyLine.implied(if (subtract) SEC else CLC) val rightType = MosExpressionCompiler.getExpressionType(ctx, r) if (decimal && !ctx.options.flag(CompilationFlag.DecimalMode)) { @@ -112,6 +145,11 @@ object PseudoregisterBuiltIns { AssemblyLine0(STA, ZeroPage, _), AssemblyLine0(LDX | LDA, Immediate, NumericConstant(0, _)), AssemblyLine0(STA | STX, ZeroPage, _)) => Nil + case List( + AssemblyLine0(REP, Immediate, NumericConstant(0x20, _)), + AssemblyLine0(LDA_W, WordImmediate, NumericConstant(0, _)), + AssemblyLine0(STA_W, ZeroPage, _), + AssemblyLine0(SEP, Immediate, NumericConstant(0x20, _))) => Nil case List( l@AssemblyLine0(LDA, _, _), @@ -126,9 +164,25 @@ object PseudoregisterBuiltIns { h.copy(opcode = op), AssemblyLine.zeropage(STA, reg, 1), AssemblyLine.zeropage(LDA, reg))) + case List( + AssemblyLine0(REP, Immediate, NumericConstant(0x20, _)), + l@AssemblyLine0(LDA_W, addrMode, _), + AssemblyLine0(STA_W, ZeroPage, _), + AssemblyLine0(SEP, Immediate, NumericConstant(0x20, _))) if addrMode != ZeroPageY => + BuiltIns.wrapInSedCldIfNeeded(decimal, List( + AssemblyLine.accu16, + prepareCarry, + AssemblyLine.zeropage(LDA_W, reg), + l.copy(opcode = op16), + AssemblyLine.zeropage(STA_W, reg), + AssemblyLine.accu8)) case _ => - List( + if (native16) { + List(AssemblyLine.accu16, AssemblyLine.zeropage(LDA_W, reg), AssemblyLine.implied(PHA_W), AssemblyLine.accu8) ++ + MosExpressionCompiler.fixTsx(MosExpressionCompiler.fixTsx(compileRight)) ++ + List(AssemblyLine.accu16, prepareCarry, AssemblyLine.implied(PLA_W), AssemblyLine.zeropage(op16, reg), AssemblyLine.zeropage(STA_W, reg), AssemblyLine.accu8) + } else List( AssemblyLine.zeropage(LDA, reg, 1), AssemblyLine.implied(PHA), AssemblyLine.zeropage(LDA, reg), diff --git a/src/test/resources/cpu.js b/src/test/resources/cpu.js new file mode 100644 index 00000000..3a612b6e --- /dev/null +++ b/src/test/resources/cpu.js @@ -0,0 +1,4097 @@ +/* + * Copyright (c) 2011-2012, Preston Skupinski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +// this is a modified version + +(function(window) { +// A collection of helper functions. +var bytesToString = function(bytes) { + + if( Object.prototype.toString.call(bytes) === '[object Array]' ) { + var sReturn = ""; + var number; + for(var i = bytes.length-1; i >= 0; i--) { + number = Number(bytes[i]); + sReturn += (number < 16 ? "0" : "") + + number.toString(16).toUpperCase(); + } + return sReturn; + } else { + number = Number(bytes); + return (number < 16 ? "0" : "") + + number.toString(16).toUpperCase(); + } +} + +var cpu_lib = { + r: { + p: { + Set_p: function(value) { + this.bytes_required = 2; + this.execute = function(cpu, bytes) { + // TODO: Figure out exactly how behavior differs in emulation mode. + cpu.cycle_count+=3; + + var flags = bytes[0].toString(2), + ops = { 0: function() { cpu.p.n = value; }, + 1: function() { cpu.p.v = value; }, + 2: function() { cpu.p.m = value; }, + 3: function() { cpu.p.x = value; }, + 4: function() { cpu.p.d = value; }, + 5: function() { cpu.p.i = value; }, + 6: function() { cpu.p.z = value; }, + 7: function() { cpu.p.c = value; }}; + + // Sometimes it cuts off zeros before hand, so add those zeros back. + while(flags.length<8) { + flags = '0' + flags; + } + + for(var i = 0; i < 8; i++) { + if(flags.charAt(i)==='1') { + ops[i](); + } + } + } + }, + Flag_set: function(flag, value) { + this.bytes_required = 1; + this.execute = function(cpu) { + cpu.cycle_count+=2; + cpu.instruction_details = "flag " + flag + " set to " + value; + //cpu.instruction_translated = true; + cpu.p[flag] = value; + }; + }, + check_z: function(cpu, val) { + if(val===0) { + cpu.p.z = 1; + } else { + cpu.p.z = 0; + } + } + } + }, + branch: { + Branch: function(flag, branch_on) { + var always_branch = typeof flag === 'undefined'; + + this.bytes_required = 2; + + this.execute = function(cpu, bytes) { + cpu.cycle_count+=3; + if(cpu.p.e) + cpu.cycle_count++; + + if(always_branch||(cpu.p[flag]==branch_on)) { + if(!always_branch) + cpu.cycle_count++; + + // Handle single byte two's complement numbers as the branch argument. + if(bytes[0]<=127) { + cpu.r.pc+=bytes[0]; + cpu.r.pc&=0xffff; + } else { + cpu.r.pc-=256-bytes[0]; + if(cpu.r.pc<0) + cpu.r.pc+=0xffff; + } + } + } + } + }, + inc: { + INC_register: function(register, flag, value) { + if(typeof value === 'undefined') + value = 1; + + this.bytes_required = 1; + + this.toString = function() { + return (value == 1 ? "IN" : "DE") + " reg" + register + " flg" + flag; + }; + + this.execute = function(cpu) { + cpu.cycle_count+=2; + + cpu.r[register]+=value; + + if(cpu.p.e||cpu.p[flag]) { + cpu.r[register] &= 0xff; + cpu.p.n = cpu.r[register] >> 7; + } else { + cpu.r[register] &= 0xffff; + cpu.p.n = cpu.r[register] >> 15; + } + + cpu_lib.r.p.check_z(cpu, cpu.r[register]); + }; + }, + INC_memory: function(value) { + if(typeof value === 'undefined') + value = 1; + + this.execute = function(cpu, bytes, extra) { + cpu.cycle_count+=4; + if(cpu.p.e||cpu.p.m) { + var temp = (bytes[0] + value) & 0xff; + cpu.p.n = temp >> 7; + cpu.mmu.store_byte(extra.memory_location, temp); + } else { + cpu.cycle_count+=2; + var temp = (((bytes[1]<<8)|bytes[0]) + value) & 0xffff; + cpu.p.n = temp >> 15; + cpu.mmu.store_word(extra.memory_location, temp); + } + cpu_lib.r.p.check_z(cpu, temp); + } + } + }, + addressing: { + Direct_page: function(instruction) { + this.bytes_required = 2; + + this.execute = function(cpu, bytes) { + cpu.cycle_count++; + cpu.instruction_translate = instruction.toString() + " $" + bytesToString(bytes); + cpu.instruction_translated = true; + cpu.instruction_history += " " + instruction.toString() + " $" + bytesToString(bytes); + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0] + cpu.r.d; + if(cpu.p.e||cpu.p.m) { + instruction.execute(cpu, [cpu.mmu.read_byte(memory_location)], + {memory_location: memory_location}); + } else { + instruction.execute(cpu, [cpu.mmu.read_byte(memory_location), + cpu.mmu.read_byte(memory_location+1)], + {memory_location: memory_location}); + } + cpu.instruction_details += "
Direct Page addressing"; + cpu.instruction_translated = false; + }; + }, + Direct_page_indexed_x_indirect: function(instruction) { + this.bytes_required = 2; + + this.execute = function(cpu, bytes) { + cpu.cycle_count+=4; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + if(cpu.p.e) { + var memory_location = (bytes[0] + cpu.r.x) & 0xff, + low_byte_loc = cpu.mmu.read_byte_long(memory_location+cpu.r.d,0), + high_byte_read_loc = ((memory_location+1)&0xff)+cpu.r.d, + high_byte_loc = cpu.mmu.read_byte_long(high_byte_read_loc, 0), + address = (high_byte_loc<<8) | low_byte_loc; + instruction.execute(cpu, [cpu.mmu.read_byte(address)], + { memory_location: address }); + } else if(cpu.p.m) { + var memory_location = bytes[0] + cpu.r.d + cpu.r.x, + low_byte_loc = cpu.mmu.read_byte(memory_location), + high_byte_loc = cpu.mmu.read_byte(memory_location+1), + address = (high_byte_loc<<8)|low_byte_loc; + instruction.execute(cpu, [cpu.mmu.read_byte(address)], + { memory_location: address }); + } else { + var memory_location = bytes[0] + cpu.r.d + cpu.r.x, + absolute_location = cpu.mmu.read_word(memory_location), + low_byte = cpu.mmu.read_byte(absolute_location), + high_byte; + absolute_location++; + if(absolute_location&0x10000) { + high_byte = cpu.mmu.read_byte_long(absolute_location, cpu.r.dbr+1); + } else { + high_byte = cpu.mmu.read_byte(absolute_location); + } + instruction.execute(cpu, [low_byte, high_byte], + { memory_location: absolute_location-1 }); + } + }; + }, + Direct_page_indirect: function(instruction) { + this.bytes_required = 2; + + this.execute = function(cpu, bytes) { + cpu.cycle_count+=3; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0] + cpu.r.d, + absolute_location = cpu.mmu.read_word(memory_location); + if(cpu.p.e||cpu.p.m) { + instruction.execute(cpu, [cpu.mmu.read_byte(absolute_location)], + { memory_location: absolute_location }); + } else { + instruction.execute(cpu, [cpu.mmu.read_byte(absolute_location), + cpu.mmu.read_byte(absolute_location+1)], + { memory_location: absolute_location }); + } + }; + }, + Direct_page_indirect_indexed_y: function(instruction) { + this.bytes_required = 2; + + this.execute = function(cpu, bytes) { + cpu.cycle_count+=3; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0] + cpu.r.d, + original_location = cpu.mmu.read_word(memory_location), + absolute_location = original_location + cpu.r.y; + + if((original_location&0xff00)!==(absolute_location&0xff00)) + cpu.cycle_count++; + + if(cpu.p.e||cpu.p.m) { + instruction.execute(cpu, [cpu.mmu.read_byte(absolute_location)], + { memory_location: absolute_location }); + } else { + instruction.execute(cpu, [cpu.mmu.read_byte(absolute_location), + cpu.mmu.read_byte(absolute_location+1)], + { memory_location: absolute_location }); + } + }; + }, + Direct_page_indirect_long: function(instruction) { + this.bytes_required = 2; + + this.execute = function(cpu, bytes) { + cpu.cycle_count+=4; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0] + cpu.r.d, + bank_byte = cpu.mmu.read_byte(memory_location+2), + absolute_location = cpu.mmu.read_word(memory_location); + if(cpu.p.e||cpu.p.m) { + instruction.execute(cpu, [cpu.mmu.read_byte_long(absolute_location, + bank_byte)], + { memory_location: absolute_location }); + } else { + instruction.execute(cpu, [cpu.mmu.read_byte_long(absolute_location, + bank_byte), + cpu.mmu.read_byte_long(absolute_location+1, + bank_byte)], + { memory_location: absolute_location }); + } + }; + }, + Direct_page_indirect_long_indexed_y: function(instruction) { + this.bytes_required = 2; + + this.execute = function(cpu, bytes) { + cpu.cycle_count+=4; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0] + cpu.r.d, + bank_byte = cpu.mmu.read_byte(memory_location+2), + absolute_location = cpu.mmu.read_word(memory_location) + cpu.r.y; + if(absolute_location >> 16) { + absolute_location &= 0xffff; + bank_byte++; + } + + if(cpu.p.e||cpu.p.m) { + instruction.execute(cpu, [cpu.mmu.read_byte_long(absolute_location, + bank_byte)], + { memory_location: absolute_location }); + } else { + var low_byte = cpu.mmu.read_byte_long(absolute_location, bank_byte); + absolute_location++; + if(absolute_location >> 16) { + absolute_location &= 0xffff; + bank_byte++; + } + var high_byte = cpu.mmu.read_byte_long(absolute_location, bank_byte); + instruction.execute(cpu, [low_byte, high_byte], + { memory_location: absolute_location-1 }); + } + }; + }, + Absolute: function(instruction) { + this.bytes_required = 3; + + this.execute = function(cpu, bytes) { + cpu.cycle_count+=2; + + var memory_location = (bytes[1]<<8)|bytes[0]; + if(cpu.p.e||cpu.p.m) { + instruction.execute(cpu, [cpu.mmu.read_byte(memory_location)], + { memory_location: memory_location }); + } else { + instruction.execute(cpu, [cpu.mmu.read_byte(memory_location), + cpu.mmu.read_byte(memory_location+1)], + { memory_location: memory_location }); + } + }; + }, + Absolute_long: function(instruction) { + this.bytes_required = 4; + + this.execute = function(cpu, bytes) { + cpu.cycle_count+=3; + + var memory_location = (bytes[1]<<8)|bytes[0]; + if(cpu.p.e||cpu.p.m) { + instruction.execute(cpu, [cpu.mmu.read_byte_long(memory_location, + bytes[2])], + { memory_location: memory_location }); + } else { + instruction.execute(cpu, [cpu.mmu.read_byte_long(memory_location, + bytes[2]), + cpu.mmu.read_byte_long(memory_location+1, + bytes[2])], + { memory_location: memory_location }); + } + }; + }, + Absolute_long_indexed_x: function(instruction) { + this.bytes_required = 4; + + this.execute = function(cpu, bytes) { + cpu.cycle_count+=3; + + var memory_location = ((bytes[1]<<8)|bytes[0]) + cpu.r.x; + if(memory_location & 0x10000) { + bytes[2]++; + memory_location &= 0xffff; + } + + if(cpu.p.e||cpu.p.m) { + instruction.execute(cpu, [cpu.mmu.read_byte_long(memory_location, + bytes[2])], + { memory_location: memory_location }); + } else { + var low_byte = cpu.mmu.read_byte_long(memory_location, bytes[2]); + memory_location++; + if(memory_location & 0x10000) { + bytes[2]++; + memory_location &= 0xffff; + } + var high_byte = cpu.mmu.read_byte_long(memory_location, bytes[2]); + instruction.execute(cpu, [low_byte, high_byte], + { memory_location: memory_location - 1 }); + } + }; + }, + Absolute_indexed_x: function(absolute_instruction, other) { + this.bytes_required = 3; + + this.execute = function(cpu, bytes) { + var memory_location; + + if(other) { + cpu.cycle_count++; + memory_location = ((bytes[1]<<8)|bytes[0])+cpu.r.x; + } else { + var initial_location = (bytes[1]<<8)|bytes[0]; + memory_location = initial_location+cpu.r.x; + if((memory_location&0xff00)!==(initial_location&0xff00)) + cpu.cycle_count++; + } + absolute_instruction.execute(cpu, [memory_location&0xff, + memory_location>>8]); + }; + }, + Absolute_indexed_y: function(absolute_instruction) { + this.bytes_required = 3; + + this.execute = function(cpu, bytes) { + var initial_location = (bytes[1]<<8)|bytes[0], + memory_location = initial_location+cpu.r.y; + + if((memory_location&0xff00)!==(initial_location&0xff00)) + cpu.cycle_count++; + + absolute_instruction.execute(cpu, [memory_location&0xff, + memory_location>>8]); + }; + }, + Direct_page_indexed: function(direct_page_instruction, register) { + this.bytes_required = 2; + + this.execute = function(cpu, bytes) { + cpu.cycle_count++; + + direct_page_instruction.execute(cpu, [bytes[0]+cpu.r[register]]); + }; + }, + Stack_relative: function(instruction) { + this.bytes_required = 2; + + this.execute = function(cpu, bytes) { + cpu.cycle_count+=2; + + var memory_location = cpu.r.s + bytes[0]; + if(cpu.p.e) { + memory_location = 0x100 | (memory_location & 0xff); + } + + if(cpu.p.e || cpu.p.m) { + instruction.execute(cpu, [cpu.mmu.read_byte(memory_location)]); + } else { + instruction.execute(cpu, [cpu.mmu.read_byte(memory_location), + cpu.mmu.read_byte(memory_location+1)]); + } + }; + }, + Stack_relative_indirect_indexed_y: function(instruction) { + this.bytes_required = 2; + + this.execute = function(cpu, bytes) { + cpu.cycle_count+=5; + + if(cpu.p.e) { + var location_loc = 0x100 | ((cpu.r.s + bytes[0]) & 0xff), + low_byte = cpu.mmu.read_byte(location_loc), + high_byte; + if(location_loc===0x1ff) { + high_byte = cpu.mmu.read_byte(0x100); + } else { + high_byte = cpu.mmu.read_byte(location_loc+1); + } + var absolute_location = ((high_byte<<8)|low_byte)+cpu.r.y, + b; + if(absolute_location>=0x10000) { + b = cpu.mmu.read_byte_long(absolute_location, cpu.r.dbr+1); + } else { + b = cpu.mmu.read_byte(absolute_location); + } + instruction.execute(cpu, [b]); + } else { + var location_loc = (cpu.r.s + bytes[0]) & 0xffff, + absolute_location = cpu.mmu.read_word(location_loc) + cpu.r.y; + if(cpu.p.m) { + var b; + if(absolute_location>=0x10000) { + b = cpu.mmu.read_byte_long(absolute_location, cpu.r.dbr+1); + } else { + b = cpu.mmu.read_byte(absolute_location); + } + instruction.execute(cpu, [b]); + } else { + var low_byte, high_byte; + if(absolute_location>=0x10000) { + absolute_location &= 0xffff; + low_byte = cpu.mmu.read_byte_long(absolute_location, cpu.r.dbr+1); + high_byte = cpu.mmu.read_byte_long(absolute_location+1, + cpu.r.dbr+1); + } else { + low_byte = cpu.mmu.read_byte(absolute_location); + if(absolute_location===0xffff) { + high_byte = cpu.mmu.read_byte_long(0, cpu.r.dbr+1); + } else { + high_byte = cpu.mmu.read_byte(absolute_location); + } + } + instruction.execute(cpu, [low_byte, high_byte]); + } + } + } + } + } +}; + +var STP = { + toString: function() { return "STP" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=3; + + cpu.stopped = true; + } +}; + +var WAI = { + toString: function() { return "WAI" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=3; + + cpu.waiting = true; + } +}; + +var WDM = { + toString: function() { return "WDM" }, + bytes_required:2, + + execute:function() {} +}; + +var TXS = { + toString: function() { return "TXS" }, + bytes_required:1, + execute:function(cpu) { + cpu.cycle_count+=2; + cpu.r.s = cpu.r.x; + if(cpu.p.e||cpu.p.x) { + cpu.p.n = cpu.r.s >> 7; + } else { + cpu.p.n = cpu.r.s >> 15; + } + cpu_lib.r.p.check_z(cpu, cpu.r.s); + } +}; + +var TSX = { + toString: function() { return "TSX" }, + bytes_required:1, + execute:function(cpu) { + cpu.cycle_count+=2; + if(cpu.p.e||cpu.p.x) { + cpu.r.x = cpu.r.s & 0xff; + cpu.p.n = cpu.r.x >> 7; + } else { + cpu.r.x = cpu.r.s; + cpu.p.n = cpu.r.x >> 15; + } + cpu_lib.r.p.check_z(cpu, cpu.r.x); + } +}; + +var TRB_absolute = { + toString: function() { return "TRB" }, + bytes_required:3, + + execute:function(cpu, bytes) { + cpu.cycle_count+=6; + + var memory_location = (bytes[1]<<8)|bytes[0], + data; + if(cpu.p.e||cpu.p.m) { + data = cpu.mmu.read_byte(memory_location); + cpu_lib.r.p.check_z(cpu, data & cpu.r.a); + cpu.mmu.store_byte(memory_location, (~cpu.r.a & data)); + } else { + cpu.cycle_count+=2; + + data = cpu.mmu.read_word(memory_location); + cpu_lib.r.p.check_z(cpu, data & cpu.r.a); + data &= ~cpu.r.a; + cpu.mmu.store_word(memory_location, data); + } + } +}; + +var TRB_direct_page = { + toString: function() { return "TRB" }, + bytes_required:2, + + execute:function(cpu, bytes) { + cpu.cycle_count+=5; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0] + cpu.r.d, + data; + if(cpu.p.e||cpu.p.m) { + data = cpu.mmu.read_byte(memory_location); + cpu_lib.r.p.check_z(cpu, data & cpu.r.a); + cpu.mmu.store_byte(memory_location, (~cpu.r.a & data)); + } else { + cpu.cycle_count+=2; + + data = cpu.mmu.read_word(memory_location); + cpu_lib.r.p.check_z(cpu, data & cpu.r.a); + data &= ~cpu.r.a; + cpu.mmu.store_word(memory_location, data); + } + } +}; + +var TSB_absolute = { + toString: function() { return "TSB" }, + bytes_required:3, + + execute:function(cpu, bytes) { + cpu.cycle_count+=6; + + var memory_location = (bytes[1]<<8)|bytes[0], + data; + if(cpu.p.e||cpu.p.m) { + data = cpu.mmu.read_byte(memory_location); + cpu_lib.r.p.check_z(cpu, data & cpu.r.a); + cpu.mmu.store_byte(memory_location, (cpu.r.a | data)); + } else { + cpu.cycle_count+=2; + + data = cpu.mmu.read_word(memory_location); + cpu_lib.r.p.check_z(cpu, data & cpu.r.a); + data |= cpu.r.a; + cpu.mmu.store_word(memory_location, data); + } + } +}; + +var TSB_direct_page = { + toString: function() { return "TSB" }, + bytes_required:2, + + execute:function(cpu, bytes) { + cpu.cycle_count+=5; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0] + cpu.r.d, + data; + if(cpu.p.e||cpu.p.m) { + data = cpu.mmu.read_byte(memory_location); + cpu_lib.r.p.check_z(cpu, data & cpu.r.a); + cpu.mmu.store_byte(memory_location, (cpu.r.a | data)); + } else { + cpu.cycle_count+=2; + + data = cpu.mmu.read_word(memory_location); + cpu_lib.r.p.check_z(cpu, data & cpu.r.a); + data |= cpu.r.a; + cpu.mmu.store_word(memory_location, data); + } + } +}; + +var BIT_const = { + toString: function() { return "BIT" }, + bytes_required:function(cpu) { + if(cpu.p.e||cpu.p.m) + return 2; + else + return 3; + }, + execute:function(cpu, bytes) { + cpu.instruction_details += "
BIT test between value and accumulator"; + if(!cpu.instruction_translated) { + cpu.instruction_details += " (const)"; + cpu.instruction_translate = this.toString() + " #" + bytesToString(bytes); + cpu.instruction_history += " " + this.toString() + " #" + bytesToString(bytes); + } + cpu.cycle_count+=2; + + var and_result; + if(cpu.p.e||cpu.p.m) { + cpu.p.n = bytes[0] >> 7; + cpu.p.v = (bytes[0] >> 6) & 0x1; + and_result = cpu.r.a & bytes[0]; + } else { + cpu.cycle_count++; + cpu.p.n = bytes[1] >> 7; + cpu.p.v = (bytes[1] >> 6) & 0x1; + and_result = cpu.r.a & ((bytes[1]<<8)|bytes[0]); + } + + cpu_lib.r.p.check_z(cpu, and_result); + } +}; + +var BIT_absolute = new cpu_lib.addressing.Absolute(BIT_const); + +var BIT_direct_page = new cpu_lib.addressing.Direct_page(BIT_const); + +var BIT_direct_page_indexed_x = + new cpu_lib.addressing.Direct_page_indexed(BIT_direct_page, 'x'); + +var BIT_absolute_indexed_x = + new cpu_lib.addressing.Absolute_indexed_x(BIT_absolute); + +var COP = { + toString: function() { return "COP" }, + bytes_required:2, + + execute:function(cpu) { + cpu.cycle_count+=7; + + if(cpu.p.e===0) + cpu.cycle_count++; + + cpu.interrupt = cpu.INTERRUPT.COP; + } +}; + +var BRK = { + toString: function() { return "BRK" }, + bytes_required:2, + + execute:function(cpu) { + cpu.cycle_count+=7; + + if(cpu.p.e===0) + cpu.cycle_count++; + + cpu.interrupt = cpu.INTERRUPT.BRK; + if(cpu.p.e) + cpu.p.m = 1; + } +}; + +var RTI = { + toString: function() { return "RTI" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=6; + + if(cpu.p.e===0) + cpu.cycle_count++; + + var p_byte = cpu.mmu.pull_byte(), + pc_low = cpu.mmu.pull_byte(), + pc_high = cpu.mmu.pull_byte(); + cpu.r.pc = (pc_high<<8)|pc_low; + + cpu.p.c = p_byte & 0x01; + cpu.p.z = (p_byte & 0x02) >> 1; + cpu.p.i = (p_byte & 0x04) >> 2; + cpu.p.d = (p_byte & 0x08) >> 3; + cpu.p.x = (p_byte & 0x10) >> 4; + cpu.p.m = (p_byte & 0x20) >> 5; + cpu.p.v = (p_byte & 0x40) >> 6; + cpu.p.n = p_byte >> 7; + + if(!cpu.p.e) { + cpu.r.k = cpu.mmu.pull_byte(); + } + } +}; + +// MVN is a really weird instruction, until the accumulator underflows MVN +// will keep decrementing the program counter to have it continue to execute. +var MVN = { + toString: function() { return "MVN" }, + bytes_required:3, + + execute:function(cpu, bytes) { + cpu.cycle_count+=7; + + // TODO: One piece of reference material I've read claims that this + // operation should always work with a 16-bit accumulator even if in + // emulation mode or the m bit is set to 1, in those cases it claims that + // you should concatenate the B "hidden" register with A. I'm going to + // need to test this claim out somehow. + var b = cpu.mmu.read_byte_long(cpu.r.x,bytes[1]); + cpu.r.dbr = bytes[0]; + cpu.mmu.store_byte(cpu.r.y, b); + cpu.r.x++; + cpu.r.y++; + if(cpu.p.e||cpu.p.x) { + cpu.r.x &= 0x00ff; + cpu.r.y &= 0x00ff; + } else { + cpu.r.x &= 0xffff; + cpu.r.y &= 0xffff; + } + + if(cpu.r.a!==0) { + cpu.r.a--; + cpu.r.pc-=3; + } else { + if(cpu.p.e||cpu.p.m) + cpu.r.a = 0xff; + else + cpu.r.a = 0xffff; + } + } +}; + +// MVP is a really weird instruction, until the accumulator reaches $FFFF MVP +// will keep decrementing the program counter to have it continue to execute. +var MVP = { + toString: function() { return "MVP" }, + bytes_required:3, + + execute:function(cpu, bytes) { + cpu.cycle_count+=7; + + // TODO: One piece of reference material I've read claims that this + // operation should always work with a 16-bit accumulator even if in + // emulation mode or the m bit is set to 1, in those cases it claims that + // you should concatenate the B "hidden" register with A. I'm going to + // need to test this claim out somehow. + var b = cpu.mmu.read_byte_long(cpu.r.x,bytes[1]); + cpu.r.dbr = bytes[0]; + cpu.mmu.store_byte(cpu.r.y,b); + + var index_register_wrap; + if(cpu.p.e||cpu.p.x) { + index_register_wrap = 0xff; + } else { + index_register_wrap = 0xffff; + } + + if(cpu.r.y===index_register_wrap) { + cpu.r.y = 0; + } else { + cpu.r.y--; + } + + if(cpu.r.x===index_register_wrap) { + cpu.r.x = 0; + } else { + cpu.r.x--; + } + + if(cpu.r.a!==0) { + cpu.r.pc-=3; + cpu.r.a--; + } else { + if(cpu.p.e||cpu.p.m) + cpu.r.a = 0xff; + else + cpu.r.a = 0xffff; + } + } +}; + +var JSL = { + toString: function() { return "JSL" }, + bytes_required:4, + + execute:function(cpu, bytes) { + cpu.cycle_count+=8; + + var memory_location = cpu.r.pc - 1; + cpu.mmu.push_byte(cpu.r.k); + cpu.mmu.push_byte(memory_location>>8); + cpu.mmu.push_byte(memory_location&0x00ff); + cpu.r.k = bytes[2]; + cpu.r.pc = (bytes[1]<<8)|bytes[0]; + } +}; + +var RTL = { + toString: function() { return "RTL" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=6; + + var low_byte = cpu.mmu.pull_byte(), + high_byte = cpu.mmu.pull_byte(); + cpu.r.k = cpu.mmu.pull_byte(); + cpu.r.pc = ((high_byte<<8)|low_byte) + 1; + } +}; + +var JSR = { + toString: function() { return "JSR" }, + bytes_required:3, + + execute:function(cpu, bytes) { + cpu.cycle_count+=6; + + var memory_location = cpu.r.pc - 1; + cpu.mmu.push_byte(memory_location>>8); + cpu.mmu.push_byte(memory_location&0xff); + cpu.r.pc = (bytes[1]<<8)|bytes[0]; + } +}; + +var JSR_absolute_indexed_x_indirect = { + toString: function() { return "JSR" }, + bytes_required:3, + + execute:function(cpu, bytes) { + cpu.cycle_count+=8; + + var memory_location = ((bytes[1]<<8)|bytes[0])+cpu.r.x, + bank = cpu.r.k; + if(memory_location&0x10000) { + bank++; + } + memory_location &= 0xffff; + var indirect_location = cpu.mmu.read_word_long(memory_location, bank), + low_byte = cpu.mmu.read_byte(indirect_location); + bank = cpu.r.k; + if(indirect_location===0xffff) { + indirect_location = 0; + bank++; + } else { + indirect_location++; + } + var high_byte = cpu.mmu.read_byte_long(indirect_location, bank); + JSR.execute(cpu, [low_byte, high_byte]); + } +}; + +var RTS = { + toString: function() { return "RTS" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=6; + + var low_byte = cpu.mmu.pull_byte(), + high_byte = cpu.mmu.pull_byte(); + cpu.r.pc = ((high_byte<<8)|low_byte) + 1; + } +}; + +var PER = { + toString: function() { return "PER" }, + bytes_required:3, + + execute:function(cpu,bytes) { + cpu.cycle_count+=6; + + var address = ((bytes[1]<<8)|bytes[0]) + cpu.r.pc; + cpu.mmu.push_byte(address>>8); + cpu.mmu.push_byte(address&0xff); + } +}; + +var PHK = { + toString: function() { return "PHK" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=3; + + cpu.mmu.push_byte(cpu.r.k); + } +}; + +var PHD = { + toString: function() { return "PHD" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=4; + + cpu.mmu.push_byte(cpu.r.d>>8); + cpu.mmu.push_byte(cpu.r.d&0xff); + } +}; + +var PLD = { + toString: function() { return "PLD" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=5; + + var low_byte = cpu.mmu.pull_byte(), + high_byte = cpu.mmu.pull_byte(); + cpu.r.d = (high_byte<<8)|low_byte; + + cpu.p.n = cpu.r.d >> 15; + + cpu_lib.r.p.check_z(cpu, cpu.r.d); + } +}; + +var PHB = { + toString: function() { return "PHB" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=3; + cpu.mmu.push_byte(cpu.r.dbr); + } +}; + +var PLB = { + toString: function() { return "PLB" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=4; + + cpu.r.dbr = cpu.mmu.pull_byte(); + cpu.p.n = cpu.r.dbr >> 7; + cpu_lib.r.p.check_z(cpu, cpu.r.dbr); + } +}; + +var PEA = { + toString: function() { return "PEA" }, + bytes_required:3, + + execute:function(cpu, bytes) { + cpu.cycle_count+=5; + + cpu.mmu.push_byte(bytes[1]); + cpu.mmu.push_byte(bytes[0]); + } +}; + +var PEI = { + toString: function() { return "PEI" }, + bytes_required:2, + + execute:function(cpu, bytes) { + cpu.cycle_count+=6; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0]+cpu.r.d; + cpu.mmu.push_byte(cpu.mmu.read_byte(memory_location+1)); + cpu.mmu.push_byte(cpu.mmu.read_byte(memory_location)); + } +}; + +var PHP = { + toString: function() { return "PHP" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=3; + + var p_byte = (cpu.p.n<<7)|(cpu.p.v<<6)|(cpu.p.m<<5)|(cpu.p.x<<4)| + (cpu.p.d<<3)|(cpu.p.i<<2)|(cpu.p.z<<1)|cpu.p.c; + cpu.mmu.push_byte(p_byte); + } +}; + +var PLP = { + toString: function() { return "PLP" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=4; + + var p_byte = cpu.mmu.pull_byte(); + cpu.p.c = p_byte & 0x01; + cpu.p.z = (p_byte & 0x02) >> 1; + cpu.p.i = (p_byte & 0x04) >> 2; + cpu.p.d = (p_byte & 0x08) >> 3; + cpu.p.x = (p_byte & 0x10) >> 4; + cpu.p.m = (p_byte & 0x20) >> 5; + cpu.p.v = (p_byte & 0x40) >> 6; + cpu.p.n = p_byte >> 7; + } +}; + +var PHX = { + toString: function() { return "PHX" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=3; + + if(cpu.p.e||cpu.p.x) { + cpu.mmu.push_byte(cpu.r.x); + } else { + cpu.cycle_count++; + + cpu.mmu.push_byte(cpu.r.x>>8); + cpu.mmu.push_byte(cpu.r.x&0xff); + } + } +}; + +var PLX = { + toString: function() { return "PLX" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=4; + + if(cpu.p.e||cpu.p.x) { + cpu.r.x = cpu.mmu.pull_byte(); + cpu.p.n = cpu.r.x >> 7; + } else { + cpu.cycle_count++; + var low_byte = cpu.mmu.pull_byte(), + high_byte = cpu.mmu.pull_byte(); + cpu.r.x = (high_byte<<8)|low_byte; + cpu.p.n = cpu.r.x >> 15; + } + + cpu_lib.r.p.check_z(cpu, cpu.r.x); + } +}; + +var PHY = { + toString: function() { return "PHY" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=3; + + if(cpu.p.e||cpu.p.x) { + cpu.mmu.push_byte(cpu.r.y); + } else { + cpu.cycle_count++; + cpu.mmu.push_byte(cpu.r.y>>8); + cpu.mmu.push_byte(cpu.r.y&0xff); + } + } +}; + +var PLY = { + toString: function() { return "PLY" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=4; + + if(cpu.p.e||cpu.p.x) { + cpu.r.y = cpu.mmu.pull_byte(); + cpu.p.n = cpu.r.y >> 7; + } else { + cpu.cycle_count++; + var low_byte = cpu.mmu.pull_byte(), + high_byte = cpu.mmu.pull_byte(); + cpu.r.y = (high_byte<<8)|low_byte; + cpu.p.n = cpu.r.y >> 15; + } + + cpu_lib.r.p.check_z(cpu, cpu.r.y); + } +}; + +var PHA = { + toString: function() { return "PHA" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=3; + + if(cpu.p.e||cpu.p.m) { + cpu.mmu.push_byte(cpu.r.a); + } else { + cpu.cycle_count++; + + cpu.mmu.push_byte(cpu.r.a>>8); + cpu.mmu.push_byte(cpu.r.a&0xff); + } + } +}; + +var PLA = { + toString: function() { return "PLA" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=4; + + if(cpu.p.e||cpu.p.m) { + cpu.r.a = cpu.mmu.pull_byte(); + cpu.p.n = cpu.r.a >> 7; + } else { + cpu.cycle_count++; + + var low_byte = cpu.mmu.pull_byte(), + high_byte = cpu.mmu.pull_byte(); + cpu.r.a = (high_byte<<8)|low_byte; + cpu.p.n = cpu.r.a >> 15; + } + + cpu_lib.r.p.check_z(cpu, cpu.r.a); + } +}; + +var ROR_accumulator = { + toString: function() { return "ROR" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=2; + + var old_c = cpu.p.c; + if(cpu.p.e||cpu.p.m) { + cpu.p.c = cpu.r.a & 0x01; + cpu.r.a = cpu.r.a >> 1; + cpu.r.a &= 0x7f; + cpu.r.a |= (old_c<<7); + cpu.p.n = cpu.r.a >> 7; + } else { + cpu.p.c = cpu.r.a & 0x0001; + cpu.r.a = cpu.r.a >> 1; + cpu.r.a &= 0x7fff; + cpu.r.a |= (old_c<<15); + cpu.p.n = cpu.r.a >> 15; + } + + cpu_lib.r.p.check_z(cpu, cpu.r.a); + } +}; + +var ROR_absolute = { + toString: function() { return "ROR" }, + bytes_required:3, + + execute:function(cpu,bytes) { + cpu.cycle_count+=6; + + var memory_location = (bytes[1]<<8)|bytes[0], + old_c = cpu.p.c, + shiftee; + if(cpu.p.e||cpu.p.m) { + shiftee = cpu.mmu.read_byte(memory_location); + cpu.p.c = shiftee & 0x01; + shiftee = shiftee >> 1; + shiftee &= 0x7f; + shiftee |= (old_c<<7); + cpu.p.n = shiftee >> 7; + cpu.mmu.store_byte(memory_location, shiftee); + } else { + cpu.cycle_count+=2; + + shiftee = cpu.mmu.read_word(memory_location); + cpu.p.c = shiftee & 0x0001; + shiftee = shiftee >> 1; + shiftee &= 0x7fff; + shiftee |= (old_c<<15); + cpu.p.n = shiftee >> 15; + cpu.mmu.store_word(memory_location, shiftee); + } + + cpu_lib.r.p.check_z(cpu, shiftee); + } +}; + +var ROR_direct_page = { + toString: function() { return "ROR" }, + bytes_required:2, + + execute:function(cpu,bytes) { + cpu.cycle_count+=5; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0]+cpu.r.d, + old_c = cpu.p.c, + shiftee; + if(cpu.p.e||cpu.p.m) { + shiftee = cpu.mmu.read_byte(memory_location); + cpu.p.c = shiftee & 0x01; + shiftee = shiftee >> 1; + shiftee &= 0x7f; + shiftee |= (old_c<<7); + cpu.p.n = shiftee >> 7; + cpu.mmu.store_byte(memory_location, shiftee); + } else { + cpu.cycle_count+=2; + + shiftee = cpu.mmu.read_word(memory_location); + cpu.p.c = shiftee & 0x0001; + shiftee = shiftee >> 1; + shiftee &= 0x7fff; + shiftee |= (old_c<<15); + cpu.p.n = shiftee >> 15; + cpu.mmu.store_word(memory_location, shiftee); + } + + cpu_lib.r.p.check_z(cpu, shiftee); + } +}; + +var ROR_absolute_indexed_x = + new cpu_lib.addressing.Absolute_indexed_x(ROR_absolute, true); + +var ROR_direct_page_indexed_x = + new cpu_lib.addressing.Direct_page_indexed(ROR_direct_page, 'x'); + +var ROL_accumulator = { + toString: function() { return "ROL" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=2; + + var old_c = cpu.p.c; + if(cpu.p.e||cpu.p.m) { + cpu.p.c = cpu.r.a >> 7; + cpu.r.a = cpu.r.a << 1; + cpu.r.a &= 0xfe; + cpu.r.a |= old_c; + cpu.p.n = cpu.r.a >> 7; + } else { + cpu.p.c = cpu.r.a >> 15; + cpu.r.a = cpu.r.a << 1; + cpu.r.a &= 0xfffe; + cpu.r.a |= old_c; + cpu.p.n = cpu.r.a >> 15; + } + + cpu_lib.r.p.check_z(cpu, cpu.r.a); + } +}; + +var ROL_absolute = { + toString: function() { return "ROL" }, + bytes_required:3, + + execute:function(cpu, bytes) { + cpu.cycle_count+=6; + + var memory_location = (bytes[1]<<8)|bytes[0], + old_c = cpu.p.c, + shiftee; + if(cpu.p.e||cpu.p.m) { + shiftee = cpu.mmu.read_byte(memory_location); + cpu.p.c = shiftee >> 7; + shiftee = shiftee << 1; + shiftee &= 0xfe; + shiftee |= old_c; + cpu.p.n = shiftee >> 7; + cpu.mmu.store_byte(memory_location, shiftee); + } else { + cpu.cycle_count+=2; + + shiftee = cpu.mmu.read_word(memory_location); + cpu.p.c = shiftee >> 15; + shiftee = shiftee << 1; + shiftee &= 0xfffe; + shiftee |= old_c; + cpu.p.n = shiftee >> 15; + cpu.mmu.store_word(memory_location, shiftee); + } + + cpu_lib.r.p.check_z(cpu, shiftee); + } +}; + +var ROL_direct_page = { + toString: function() { return "ROL" }, + bytes_required:2, + + execute:function(cpu, bytes) { + cpu.cycle_count+=5; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0]+cpu.r.d, + old_c = cpu.p.c, + shiftee; + if(cpu.p.e||cpu.p.m) { + shiftee = cpu.mmu.read_byte(memory_location); + cpu.p.c = shiftee >> 7; + shiftee = shiftee << 1; + shiftee &= 0xfe; + shiftee |= old_c; + cpu.p.n = shiftee >> 7; + cpu.mmu.store_byte(memory_location, shiftee); + } else { + cpu.cycle_count+=2; + + shiftee = cpu.mmu.read_word(memory_location); + cpu.p.c = shiftee >> 15; + shiftee = shiftee << 1; + shiftee &= 0xfffe; + shiftee |= old_c; + cpu.p.n = shiftee >> 15; + cpu.mmu.store_word(memory_location, shiftee); + } + + cpu_lib.r.p.check_z(cpu, shiftee); + } +}; + +var ROL_absolute_indexed_x = + new cpu_lib.addressing.Absolute_indexed_x(ROL_absolute, true); + +var ROL_direct_page_indexed_x = + new cpu_lib.addressing.Direct_page_indexed(ROL_direct_page, 'x'); + +var ASL_accumulator = { + toString: function() { return "ASL" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=2; + + if(cpu.p.e||cpu.p.m) { + cpu.p.c = cpu.r.a >> 7; + cpu.r.a = cpu.r.a << 1; + cpu.r.a &= 0xff; + cpu.p.n = cpu.r.a >> 7; + } else { + cpu.p.c = cpu.r.a >> 15; + cpu.r.a = cpu.r.a << 1; + cpu.r.a &= 0xffff; + cpu.p.n = cpu.r.a >> 15; + } + + cpu_lib.r.p.check_z(cpu, cpu.r.a); + } +}; + +var ASL_absolute = { + toString: function() { return "ASL" }, + bytes_required:3, + + execute:function(cpu, bytes) { + cpu.instruction_details += "
Shift Accumulator with value"; + if(!cpu.instruction_translated) { + cpu.instruction_details += " (const)"; + cpu.instruction_translate = this.toString() + " #" + bytesToString(bytes); + cpu.instruction_history += " " + this.toString() + " #" + bytesToString(bytes); + } + cpu.cycle_count+=6; + + var memory_location = (bytes[1]<<8)|bytes[0], + shiftee; + if(cpu.p.e||cpu.p.m) { + shiftee = cpu.mmu.read_byte(memory_location); + cpu.p.c = shiftee >> 7; + shiftee = shiftee << 1; + shiftee &= 0xff; + cpu.p.n = shiftee >> 7; + cpu.mmu.store_byte(memory_location, shiftee); + } else { + cpu.cycle_count+=2; + shiftee = cpu.mmu.read_word(memory_location); + cpu.p.c = shiftee >> 15; + shiftee = shiftee << 1; + shiftee &= 0xffff; + cpu.p.n = shiftee >> 15; + cpu.mmu.store_word(memory_location, shiftee); + } + + cpu_lib.r.p.check_z(cpu, shiftee); + } +}; + +var ASL_direct_page = { + toString: function() { return "ASL" }, + bytes_required:2, + + execute:function(cpu,bytes) { + cpu.instruction_details += "
Shift Accumulator with value"; + if(!cpu.instruction_translated) { + cpu.instruction_details += "
Direct Page addressing"; + cpu.instruction_translate = this.toString() + " $" + bytesToString(bytes); + cpu.instruction_history += " " + this.toString() + " $" + bytesToString(bytes); + } + cpu.cycle_count+=5; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0]+cpu.r.d, + shiftee; + if(cpu.p.e||cpu.p.m) { + shiftee = cpu.mmu.read_byte(memory_location); + cpu.p.c = shiftee >> 7; + shiftee = shiftee << 1; + shiftee &= 0xff; + cpu.p.n = shiftee >> 7; + cpu.mmu.store_byte(memory_location, shiftee); + } else { + cpu.cycle_count+=2; + + shiftee = cpu.mmu.read_word(memory_location); + cpu.p.c = shiftee >> 15; + shiftee = shiftee << 1; + shiftee &= 0xffff; + cpu.p.n = shiftee >> 15; + cpu.mmu.store_word(memory_location, shiftee); + } + + cpu_lib.r.p.check_z(cpu, shiftee); + } +}; + +var ASL_absolute_indexed_x = + new cpu_lib.addressing.Absolute_indexed_x(ASL_absolute); + +var ASL_direct_page_indexed_x = + new cpu_lib.addressing.Direct_page_indexed(ASL_direct_page, 'x'); + +var LSR_accumulator= { + toString: function() { return "LSR" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=2; + + cpu.p.c = cpu.r.a & 1; + cpu.r.a = cpu.r.a >> 1; + + cpu.p.n = 0; + cpu_lib.r.p.check_z(cpu, cpu.r.a); + } +}; + +var LSR_absolute = { + toString: function() { return "LSR" }, + bytes_required:3, + + execute:function(cpu, bytes) { + cpu.cycle_count+=6; + + var memory_location = (bytes[1]<<8)|bytes[0], + shiftee; + if(cpu.p.e||cpu.p.m) { + shiftee = cpu.mmu.read_byte(memory_location); + cpu.p.c = shiftee & 0x0001; + shiftee = shiftee >> 1; + cpu.mmu.store_byte(memory_location, shiftee); + } else { + cpu.cycle_count+=2; + + shiftee = cpu.mmu.read_word(memory_location); + cpu.p.c = cpu.r.a & 0x01; + shiftee = shiftee >> 1; + cpu.mmu.store_word(memory_location, shiftee); + } + + cpu.p.n = 0; + cpu_lib.r.p.check_z(cpu, shiftee); + } +}; + +var LSR_direct_page = { + toString: function() { return "LSR" }, + bytes_required:2, + + execute:function(cpu, bytes) { + cpu.cycle_count+=5; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0] + cpu.r.d, + shiftee; + if(cpu.p.e||cpu.p.m) { + shiftee = cpu.mmu.read_byte(memory_location); + cpu.p.c = shiftee & 0x0001; + shiftee = shiftee >> 1; + cpu.mmu.store_byte(memory_location, shiftee); + } else { + cpu.cycle_count+=2; + + shiftee = cpu.mmu.read_word(memory_location); + cpu.p.c = cpu.r.a & 0x01; + shiftee = shiftee >> 1; + cpu.mmu.store_word(memory_location, shiftee); + } + + cpu.p.n = 0; + cpu_lib.r.p.check_z(cpu, shiftee); + } +}; + +var LSR_absolute_indexed_x = + new cpu_lib.addressing.Absolute_indexed_x(LSR_absolute); + +var LSR_direct_page_indexed_x = + new cpu_lib.addressing.Direct_page_indexed(LSR_direct_page, 'x'); + +var EOR_const = { + toString: function() { return "EOR" }, + bytes_required:function(cpu) { + if(cpu.p.e||cpu.p.m) { + return 2; + } else { + return 3; + } + }, + execute:function(cpu, bytes) { + cpu.instruction_details += "
Exclusive OR with accumulator"; + if(!cpu.instruction_translated) { + cpu.instruction_details += " (const)"; + cpu.instruction_translate = this.toString() + " #" + bytesToString(bytes); + cpu.instruction_history += " " + this.toString() + " #" + bytesToString(bytes); + } + cpu.cycle_count+=2; + + if(cpu.p.e||cpu.p.m) { + cpu.r.a ^= bytes[0]; + cpu.p.n = cpu.r.a >> 7; + } else { + cpu.cycle_count++; + cpu.r.a ^= (bytes[1]<<8)|bytes[0]; + cpu.p.n = cpu.r.a >> 15; + } + + cpu_lib.r.p.check_z(cpu, cpu.r.a); + } +}; + +var EOR_absolute = new cpu_lib.addressing.Absolute(EOR_const); + +var EOR_absolute_long = new cpu_lib.addressing.Absolute_long(EOR_const); + +var EOR_absolute_long_indexed_x = + new cpu_lib.addressing.Absolute_long_indexed_x(EOR_const); + +var EOR_direct_page = new cpu_lib.addressing.Direct_page(EOR_const); + +var EOR_direct_page_indirect = + new cpu_lib.addressing.Direct_page_indirect(EOR_const); + +var EOR_direct_page_indexed_x_indirect = + new cpu_lib.addressing.Direct_page_indexed_x_indirect(EOR_const); + +var EOR_direct_page_indirect_long_indexed_y = + new cpu_lib.addressing.Direct_page_indirect_long_indexed_y(EOR_const); + +var EOR_direct_page_indirect_long = + new cpu_lib.addressing.Direct_page_indirect_long(EOR_const); + +var EOR_direct_page_indirect_indexed_y = + new cpu_lib.addressing.Direct_page_indirect_indexed_y(EOR_const); + +var EOR_absolute_indexed_x = + new cpu_lib.addressing.Absolute_indexed_x(EOR_absolute); + +var EOR_absolute_indexed_y = + new cpu_lib.addressing.Absolute_indexed_y(EOR_absolute); + +var EOR_direct_page_indexed_x = + new cpu_lib.addressing.Direct_page_indexed(EOR_direct_page, 'x'); + +var EOR_stack_relative = new cpu_lib.addressing.Stack_relative(EOR_const); + +var EOR_stack_relative_indirect_indexed_y = + new cpu_lib.addressing.Stack_relative_indirect_indexed_y(EOR_const); + +var ORA_const= { + toString: function() { return "ORA" }, + bytes_required:function(cpu) { + if(cpu.p.e||cpu.p.m) { + return 2; + } else { + return 3; + } + }, + execute:function(cpu, bytes) { + cpu.instruction_details += "
OR with accumulator"; + if(!cpu.instruction_translated) { + cpu.instruction_details += " (const)"; + cpu.instruction_translate = this.toString() + " #" + bytesToString(bytes); + cpu.instruction_history += " " + this.toString() + " #" + bytesToString(bytes); + } + cpu.cycle_count+=2; + + if(cpu.p.e||cpu.p.m) { + cpu.r.a |= bytes[0]; + cpu.p.n = cpu.r.a >> 7; + } else { + cpu.cycle_count++; + + cpu.r.a |= (bytes[1]<<8)|bytes[0]; + cpu.p.n = cpu.r.a >> 15; + } + + cpu_lib.r.p.check_z(cpu, cpu.r.a); + } +}; + +var ORA_absolute = new cpu_lib.addressing.Absolute(ORA_const); + +var ORA_absolute_long = new cpu_lib.addressing.Absolute_long(ORA_const); + +var ORA_absolute_long_indexed_x = + new cpu_lib.addressing.Absolute_long_indexed_x(ORA_const); + +var ORA_direct_page = new cpu_lib.addressing.Direct_page(ORA_const); + +var ORA_direct_page_indirect = + new cpu_lib.addressing.Direct_page_indirect(ORA_const); + +var ORA_direct_page_indexed_x_indirect = + new cpu_lib.addressing.Direct_page_indexed_x_indirect(ORA_const); + +var ORA_direct_page_indirect_long = + new cpu_lib.addressing.Direct_page_indirect_long(ORA_const); + +var ORA_direct_page_indirect_long_indexed_y = + new cpu_lib.addressing.Direct_page_indirect_long_indexed_y(ORA_const); + +var ORA_direct_page_indirect_indexed_y = + new cpu_lib.addressing.Direct_page_indirect_indexed_y(ORA_const); + +var ORA_absolute_indexed_x = + new cpu_lib.addressing.Absolute_indexed_x(ORA_absolute); + +var ORA_absolute_indexed_y = + new cpu_lib.addressing.Absolute_indexed_y(ORA_absolute); + +var ORA_direct_page_indexed_x = + new cpu_lib.addressing.Direct_page_indexed(ORA_direct_page,'x'); + +var ORA_stack_relative = new cpu_lib.addressing.Stack_relative(ORA_const); + +var ORA_stack_relative_indirect_indexed_y = + new cpu_lib.addressing.Stack_relative_indirect_indexed_y(ORA_const); + +var AND_const= { + toString: function() { return "AND" }, + bytes_required:function(cpu) { + if(cpu.p.e||cpu.p.m) { + return 2; + } else { + return 3; + } + }, + execute:function(cpu, bytes) { + cpu.instruction_details += "
AND with accumulator"; + if(!cpu.instruction_translated) { + cpu.instruction_details += " (const)"; + cpu.instruction_translate = this.toString() + " #" + bytesToString(bytes); + cpu.instruction_history += " " + this.toString() + " #" + bytesToString(bytes); + } + cpu.cycle_count+=2; + + if(cpu.p.e||cpu.p.m) { + cpu.r.a &= bytes[0]; + cpu.p.n = cpu.r.a >> 7; + } else { + cpu.cycle_count++; + + cpu.r.a &= (bytes[1]<<8)|bytes[0]; + cpu.p.n = cpu.r.a >> 15; + } + + cpu_lib.r.p.check_z(cpu, cpu.r.a); + } +}; + +var AND_absolute = new cpu_lib.addressing.Absolute(AND_const); + +var AND_absolute_long = new cpu_lib.addressing.Absolute_long(AND_const); + +var AND_absolute_long_indexed_x = + new cpu_lib.addressing.Absolute_long_indexed_x(AND_const); + +var AND_direct_page = new cpu_lib.addressing.Direct_page(AND_const); + +var AND_direct_page_indirect = + new cpu_lib.addressing.Direct_page_indirect(AND_const); + +var AND_direct_page_indexed_x_indirect = + new cpu_lib.addressing.Direct_page_indexed_x_indirect(AND_const); + +var AND_direct_page_indirect_long = + new cpu_lib.addressing.Direct_page_indirect_long(AND_const); + +var AND_direct_page_indirect_long_indexed_y = + new cpu_lib.addressing.Direct_page_indirect_long_indexed_y(AND_const); + +var AND_direct_page_indirect_indexed_y = + new cpu_lib.addressing.Direct_page_indirect_indexed_y(AND_const); + +var AND_absolute_indexed_x = + new cpu_lib.addressing.Absolute_indexed_x(AND_absolute); + +var AND_absolute_indexed_y = + new cpu_lib.addressing.Absolute_indexed_y(AND_absolute); + +var AND_direct_page_indexed_x = + new cpu_lib.addressing.Direct_page_indexed(AND_direct_page, 'x'); + +var AND_stack_relative = new cpu_lib.addressing.Stack_relative(AND_const); + +var AND_stack_relative_indirect_indexed_y = + new cpu_lib.addressing.Stack_relative_indirect_indexed_y(AND_const); + +var CPX_const= { + toString: function() { return "CPX" }, + bytes_required:function(cpu) { + if(cpu.p.e||cpu.p.x) { + return 2; + } else { + return 3; + } + }, + execute:function(cpu, bytes) { + cpu.instruction_details += "
Compare value with X register"; + if(!cpu.instruction_translated) { + cpu.instruction_details += " (const)"; + cpu.instruction_translate = this.toString() + " #" + bytesToString(bytes); + cpu.instruction_history += " " + this.toString() + " #" + bytesToString(bytes); + } + cpu.cycle_count+=2; + + var result; + if(cpu.p.e||cpu.p.x) { + result = cpu.r.x - bytes[0]; + if(result<0) { + cpu.p.c = 0; + result = 0x100 + result; + } else { + cpu.p.c = 1; + } + cpu.p.n = result >> 7; + } else { + cpu.cycle_count++; + + result = cpu.r.x - ((bytes[1]<<8)|bytes[0]); + if(result<0) { + cpu.p.c = 0; + result = 0x10000 + result; + } else { + cpu.p.c = 1; + } + cpu.p.n = result >> 15; + } + + cpu_lib.r.p.check_z(cpu, result); + } +}; + +var CPX_direct_page = new cpu_lib.addressing.Direct_page(CPX_const); + +var CPX_absolute = new cpu_lib.addressing.Absolute(CPX_const); + +var CPY_const= { + toString: function() { return "CPY" }, + bytes_required:function(cpu) { + if(cpu.p.e||cpu.p.x) { + return 2; + } else { + return 3; + } + }, + execute:function(cpu, bytes) { + cpu.instruction_details += "
Compare value with Y register"; + if(!cpu.instruction_translated) { + cpu.instruction_details += " (const)"; + cpu.instruction_translate = this.toString() + " #" + bytesToString(bytes); + cpu.instruction_history += " " + this.toString() + " #" + bytesToString(bytes); + } + cpu.cycle_count+=2; + + var result; + if(cpu.p.e||cpu.p.x) { + result = cpu.r.y - bytes[0]; + if(result<0) { + cpu.p.c = 0; + result = 0x100 + result; + } else { + cpu.p.c = 1; + } + cpu.p.n = result >> 7; + } else { + cpu.cycle_count++; + result = cpu.r.y - ((bytes[1]<<8)|bytes[0]); + if(result<0) { + cpu.p.c = 0; + result = 0x10000 + result; + } else { + cpu.p.c = 1; + } + cpu.p.n = result >> 15; + } + + cpu_lib.r.p.check_z(cpu, result); + } +}; + +var CPY_direct_page = new cpu_lib.addressing.Direct_page(CPY_const); + +var CPY_absolute = new cpu_lib.addressing.Absolute(CPY_const); + +var CMP_const= { + toString: function() { return "CMP" }, + bytes_required:function(cpu) { + if(cpu.p.e||cpu.p.m) { + return 2; + } else { + return 3; + } + }, + execute:function(cpu, bytes) { + cpu.instruction_details += "
Compare value with accumulator"; + if(!cpu.instruction_translated) { + cpu.instruction_details += " (const)"; + cpu.instruction_translate = this.toString() + " #" + bytesToString(bytes); + cpu.instruction_history += " " + this.toString() + " #" + bytesToString(bytes); + } + cpu.cycle_count+=2; + + var result; + if(cpu.p.e||cpu.p.m) { + result = cpu.r.a - bytes[0]; + if(result<0) { + cpu.p.c = 0; + result = 0x100 + result; + } else { + cpu.p.c = 1; + } + cpu.p.n = result >> 7; + } else { + cpu.cycle_count++; + + result = cpu.r.a - ((bytes[1]<<8)|bytes[0]); + if(result<0) { + cpu.p.c = 0; + result = 0x10000 + result; + } else { + cpu.p.c = 1; + } + cpu.p.n = result >> 15; + } + + cpu_lib.r.p.check_z(cpu, result); + } +}; + +var CMP_direct_page = new cpu_lib.addressing.Direct_page(CMP_const); + +var CMP_direct_page_indexed_x = + new cpu_lib.addressing.Direct_page_indexed(CMP_direct_page, 'x'); + +var CMP_direct_page_indirect = + new cpu_lib.addressing.Direct_page_indirect(CMP_const); + +var CMP_direct_page_indexed_x_indirect = + new cpu_lib.addressing.Direct_page_indexed_x_indirect(CMP_const); + +var CMP_direct_page_indirect_long = + new cpu_lib.addressing.Direct_page_indirect_long(CMP_const); + +var CMP_direct_page_indirect_long_indexed_y = + new cpu_lib.addressing.Direct_page_indirect_long_indexed_y(CMP_const); + +var CMP_direct_page_indirect_indexed_y = + new cpu_lib.addressing.Direct_page_indirect_indexed_y(CMP_const); + +var CMP_absolute = new cpu_lib.addressing.Absolute(CMP_const); + +var CMP_absolute_long = new cpu_lib.addressing.Absolute_long(CMP_const); + +var CMP_absolute_long_indexed_x = + new cpu_lib.addressing.Absolute_long_indexed_x(CMP_const); + +var CMP_absolute_indexed_x = + new cpu_lib.addressing.Absolute_indexed_x(CMP_absolute); + +var CMP_absolute_indexed_y = + new cpu_lib.addressing.Absolute_indexed_y(CMP_absolute); + +var CMP_stack_relative = new cpu_lib.addressing.Stack_relative(CMP_const); + +var CMP_stack_relative_indirect_indexed_y = + new cpu_lib.addressing.Stack_relative_indirect_indexed_y(CMP_const); + +var SBC_const= { + toString: function() { return "SBC" }, + bytes_required:function(cpu) { + if(cpu.p.e||cpu.p.m) { + return 2; + } else { + return 3; + } + }, + execute:function(cpu, bytes) { + cpu.instruction_details += "
Substract value from accumulator"; + if(!cpu.instruction_translated) { + cpu.instruction_details += " (const)"; + cpu.instruction_translate = this.toString() + " #" + bytesToString(bytes); + cpu.instruction_history += " " + this.toString() + " #" + bytesToString(bytes); + } + cpu.cycle_count+=2; + + var old_a = cpu.r.a, + temp = 0; + if(cpu.p.c===0) + temp = 1; + + if(cpu.p.e||cpu.p.m) { + if(cpu.p.d) { + // Form a decimal number out of a. + var ones = cpu.r.a & 0x0f, + tens = cpu.r.a >> 4, + dec_a = (tens*10)+ones; + + // Form a decimal number out of the argument. + ones = bytes[0] & 0x0f; + tens = bytes[0] >> 4; + var result = dec_a - ((tens*10)+ones) - temp; + + // Check for decimal overflow. + if(result<0) { + result += 100; + cpu.p.c = 0; + } else { + cpu.p.c = 1; + } + var digits = result.toString(10).split(""), + i; + cpu.r.a = 0; + for(i=0;i> 7; + + // Check for signed overflow. + // If they started with the same sign and then the resulting sign is + // different then we have a signed overflow. + if((!((old_a ^ bytes[0]) & 0x80)) && ((cpu.r.a ^ old_a) & 0x80)) { + cpu.p.v = 1; + } else { + cpu.p.v = 0; + } + } + } else { + cpu.cycle_count++; + + var argument = (bytes[1]<<8)|bytes[0]; + temp = 0; + + if(cpu.p.c===0) + temp = 1; + + if(cpu.p.d) { + // Form a decimal number out of a. + var ones = cpu.r.a & 0xf, + tens = (cpu.r.a >>4) & 0xf, + hundreds = (cpu.r.a >> 8) & 0xf, + thousands = (cpu.r.a >> 12) & 0xf, + dec_a = (thousands*1000)+(hundreds*100)+(tens*10)+ones; + + // Form a decimal number out of the argument. + ones = argument & 0xf; + tens = (argument >> 4) & 0xf; + hundreds = (argument >> 8) & 0xf; + thousands = (argument >> 12) & 0xf; + var dec_arg = (thousands*1000)+(hundreds*100)+(tens*10)+ones, + result = dec_a - dec_arg - temp; + // Check for decimal overflow. + if(result<0) { + result += 10000; + cpu.p.c = 0; + } else { + cpu.p.c = 1; + } + var digits = result.toString(10).split(""), + i; + cpu.r.a = 0; + for(i=0;i> 15; + + // Check for signed overflow. + // If they started with the same sign and then the resulting sign is + // different then we have a signed overflow. + if((!((old_a ^ argument) & 0x8000)) && ((cpu.r.a ^ old_a) & 0x8000)) { + cpu.p.v = 1; + } else { + cpu.p.v = 0; + } + } + } + + cpu_lib.r.p.check_z(cpu, cpu.r.a); + } +}; + +var SBC_direct_page = new cpu_lib.addressing.Direct_page(SBC_const); + +var SBC_absolute = new cpu_lib.addressing.Absolute(SBC_const); + +var SBC_absolute_long = new cpu_lib.addressing.Absolute_long(SBC_const); + +var SBC_absolute_long_indexed_x = + new cpu_lib.addressing.Absolute_long_indexed_x(SBC_const); + +var SBC_direct_page_indexed_x_indirect = + new cpu_lib.addressing.Direct_page_indexed_x_indirect(SBC_const); + +var SBC_direct_page_indirect_long = + new cpu_lib.addressing.Direct_page_indirect_long(SBC_const); + +var SBC_direct_page_indirect_long_indexed_y = + new cpu_lib.addressing.Direct_page_indirect_long_indexed_y(SBC_const); + +var SBC_direct_page_indirect_indexed_y = + new cpu_lib.addressing.Direct_page_indirect_indexed_y(SBC_const); + +var SBC_absolute_indexed_x = + new cpu_lib.addressing.Absolute_indexed_x(SBC_absolute); + +var SBC_absolute_indexed_y = + new cpu_lib.addressing.Absolute_indexed_y(SBC_absolute); + +var SBC_direct_page_indexed_x = + new cpu_lib.addressing.Direct_page_indexed(SBC_direct_page, 'x'); + +var SBC_direct_page_indirect = + new cpu_lib.addressing.Direct_page_indirect(SBC_const); + +var SBC_stack_relative = new cpu_lib.addressing.Stack_relative(SBC_const); + +var SBC_stack_relative_indirect_indexed_y = + new cpu_lib.addressing.Stack_relative_indirect_indexed_y(SBC_const); + +var ADC_const = { + toString: function() { return "ADC" }, + bytes_required:function(cpu) { + if(cpu.p.e||cpu.p.m) { + return 2; + } else { + return 3; + } + }, + execute:function(cpu, bytes) { + cpu.instruction_details += "
Add with cary value to accumulator"; + if(!cpu.instruction_translated) { + cpu.instruction_details += " (const)"; + cpu.instruction_translate = this.toString() + " #" + bytesToString(bytes); + cpu.instruction_history += " " + this.toString() + " #" + bytesToString(bytes); + } + var old_a = cpu.r.a; + if(cpu.p.e||cpu.p.m) { + cpu.cycle_count+=2; + + if(cpu.p.d) { + // Form a decimal number out of a. + var ones = cpu.r.a & 0x0f, + tens = cpu.r.a >>4, + dec_a = (tens*10)+ones; + + // Form a decimal number out of the argument. + ones = bytes[0] & 0x0f; + tens = bytes[0] >>4; + var result = dec_a + ((tens*10)+ones) + cpu.p.c; + // Check for decimal overflow. + if(result>99) { + result -= 99; + cpu.p.c = 1; + } else { + cpu.p.c = 0; + } + var digits = result.toString(10).split(""), + i; + cpu.r.a = 0; + for(i=0;i> 7; + + // Check for signed overflow. + // If they started with the same sign and then the resulting sign is + // different then we have a signed overflow. + if((!((old_a ^ bytes[0]) & 0x80)) && ((cpu.r.a ^ old_a) & 0x80)) { + cpu.p.v = 1; + } else { + cpu.p.v = 0; + } + } + } else { + cpu.cycle_count+=3; + var argument = (bytes[1]<<8)|bytes[0]; + if(cpu.p.d) { + // Form a decimal number out of a. + var ones = cpu.r.a & 0xf, + tens = (cpu.r.a >>4) & 0xf, + hundreds = (cpu.r.a >> 8) & 0xf, + thousands = (cpu.r.a >> 12) & 0xf, + dec_a = (thousands*1000)+(hundreds*100)+(tens*10)+ones; + + // Form a decimal number out of the argument. + ones = argument & 0xf; + tens = (argument >> 4) & 0xf; + hundreds = (argument >> 8) & 0xf; + thousands = (argument >> 12) & 0xf; + var dec_arg = (thousands*1000)+(hundreds*100)+(tens*10)+ones, + result = dec_a + dec_arg + cpu.p.c; + // Check for decimal overflow. + if(result>9999) { + result -= 9999; + cpu.p.c = 1; + } else { + cpu.p.c = 0; + } + var digits = result.toString(10).split(""), + i; + cpu.r.a = 0; + for(i=0;i> 15; + + // Check for signed overflow. + // If they started with the same sign and then the resulting sign is + // different then we have a signed overflow. + if((!((old_a ^ argument) & 0x8000)) && ((cpu.r.a ^ old_a) & 0x8000)) { + cpu.p.v = 1; + } else { + cpu.p.v = 0; + } + } + } + + cpu_lib.r.p.check_z(cpu, cpu.r.a); + } +}; + +var ADC_absolute = new cpu_lib.addressing.Absolute(ADC_const); + +var ADC_absolute_long = new cpu_lib.addressing.Absolute_long(ADC_const); + +var ADC_absolute_long_indexed_x = + new cpu_lib.addressing.Absolute_long_indexed_x(ADC_const); + +var ADC_direct_page = new cpu_lib.addressing.Direct_page(ADC_const); + +var ADC_direct_page_indirect = + new cpu_lib.addressing.Direct_page_indirect(ADC_const); + +var ADC_direct_page_indexed_x_indirect = + new cpu_lib.addressing.Direct_page_indexed_x_indirect(ADC_const); + +var ADC_direct_page_indirect_long_indexed_y = + new cpu_lib.addressing.Direct_page_indirect_long_indexed_y(ADC_const); + +var ADC_direct_page_indirect_long = + new cpu_lib.addressing.Direct_page_indirect_long(ADC_const); + +var ADC_direct_page_indirect_indexed_y = + new cpu_lib.addressing.Direct_page_indirect_indexed_y(ADC_const); + +var ADC_absolute_indexed_x = + new cpu_lib.addressing.Absolute_indexed_x(ADC_absolute); + +var ADC_absolute_indexed_y = + new cpu_lib.addressing.Absolute_indexed_y(ADC_absolute); + +var ADC_direct_page_indexed_x = + new cpu_lib.addressing.Direct_page_indexed(ADC_direct_page, 'x'); + +var ADC_stack_relative = new cpu_lib.addressing.Stack_relative(ADC_const); + +var ADC_stack_relative_indirect_indexed_y = + new cpu_lib.addressing.Stack_relative_indirect_indexed_y(ADC_const); + +var BMI = new cpu_lib.branch.Branch('n', true); + +var BPL = new cpu_lib.branch.Branch('n', false); + +var BVC = new cpu_lib.branch.Branch('v', false); + +var BVS = new cpu_lib.branch.Branch('v', true); + +var BCC = new cpu_lib.branch.Branch('c', false); + +var BCS = new cpu_lib.branch.Branch('c', true); + +var BEQ = new cpu_lib.branch.Branch('z', true); + +var BNE = new cpu_lib.branch.Branch('z', false); + +var BRA = new cpu_lib.branch.Branch(); + +var BRL = { + toString: function() { return "BRL" }, + bytes_required:3, + + execute:function(cpu, bytes) { + cpu.cycle_count+=4; + + // Handle double byte two's complement numbers as the branch argument. + var num = (bytes[1]<<8)|bytes[0]; + if(num<=32767) { + cpu.r.pc+=num; + cpu.r.pc&=0xffff; + } else { + cpu.r.pc-=65536-num; + if(cpu.r.pc<0) + cpu.r.pc+=0xffff; + } + } +}; + + +var JMP_absolute_indirect= { + toString: function() { return "JMP" }, + bytes_required:3, + + execute:function(cpu, bytes) { + cpu.cycle_count+=5; + + cpu.r.pc = cpu.mmu.read_word((bytes[1]<<8)|bytes[0]); + } +}; + +var JMP_absolute_long= { + toString: function() { return "JMP" }, + bytes_required:4, + + execute:function(cpu, bytes) { + cpu.cycle_count+=4; + + cpu.r.k = bytes[2]; + cpu.r.pc = (bytes[1]<<8)|bytes[0]; + } +}; + +var JMP_absolute_indirect_long= { + toString: function() { return "JMP" }, + bytes_required:3, + + execute:function(cpu, bytes) { + cpu.cycle_count+=6; + + var memory_location = (bytes[1]<<8)|bytes[0]; + cpu.r.pc = cpu.mmu.read_word(memory_location); + cpu.r.k = cpu.mmu.read_byte(memory_location+2); + } +}; + +var JMP_absolute_indexed_x_indirect= { + toString: function() { return "JMP" }, + bytes_required:3, + + execute:function(cpu, bytes) { + cpu.cycle_count+=6; + + var memory_location = ((bytes[1]<<8)|bytes[0])+cpu.r.x, + bank = cpu.r.k; + if(memory_location&0x10000) { + bank++; + } + memory_location &= 0xffff; + var indirect_location = cpu.mmu.read_word_long(memory_location, bank), + low_byte = cpu.mmu.read_byte(indirect_location); + bank = cpu.r.k; + if(indirect_location===0xffff) { + indirect_location = 0; + bank++; + } else { + indirect_location++; + } + var high_byte = cpu.mmu.read_byte_long(indirect_location, bank); + cpu.r.pc = (high_byte<<8)|low_byte; + } +}; + +var JMP_absolute= { + toString: function() { return "JMP" }, + bytes_required:3, + + execute:function(cpu, bytes) { + cpu.cycle_count+=3; + + cpu.r.pc = (bytes[1]<<8)|bytes[0]; + } +}; + +var TYA = { + toString: function() { return "TYA" }, + bytes_required:function() { + return 1; + }, + execute:function(cpu) { + cpu.cycle_count+=2; + if(cpu.p.e||cpu.p.m) { + if(cpu.p.e||cpu.p.x) { + // 8-bit index register to 8-bit accumulator. + cpu.r.a = cpu.r.y; + } else { + // 16-bit index register to 8-bit accumulator. + cpu.r.a = cpu.r.y & 0x00ff; + } + cpu.p.n = cpu.r.a >> 7; + } else { + // 8-bit index register to 16-bit accumulator. + // 16-bit index register to 16-bit accumulator. + cpu.r.a = cpu.r.y; + cpu.p.n = cpu.r.a >> 15; + } + cpu_lib.r.p.check_z(cpu, cpu.r.a); + cpu.instruction_history += " TYA"; + cpu.instruction_details += "
Transfer Y Register to Accumulator"; + } +}; + +var TAY = { + toString: function() { return "TAY" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=2; + if(cpu.p.e||cpu.p.m) { + if(cpu.p.e||cpu.p.x) { + // 8-bit accumulator to 8-bit x index register. + cpu.r.y = cpu.r.a; + cpu.p.n = cpu.r.y >> 7; + } else { + // 8-bit accumulator to 16-bit x index register. + cpu.r.y = cpu.r.b; // Transfer b as high-byte of x. + cpu.r.y |= cpu.r.a; // Use the bitwise or to add a as the low-byte. + cpu.p.n = cpu.r.y >> 15; + } + } else { + if(cpu.p.x) { + // 16-bit accumulator to 8-bit x index register. + cpu.r.y = cpu.r.a & 0x00ff; // Transfer only the low-byte to x. + cpu.p.n = cpu.r.y >> 7; + } else { + // 16-bit accumulator to 16-bit x index register. + cpu.r.y = cpu.r.a; + cpu.p.n = cpu.r.y >> 15; + } + } + + cpu_lib.r.p.check_z(cpu, cpu.r.y); + cpu.instruction_history += " TAY"; + cpu.instruction_details += "
Transfer Accumulator to Y Register"; + } +}; + + +var TXA = { + toString: function() { return "TXA" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=2; + if(cpu.p.e||cpu.p.m) { + if(cpu.p.e||cpu.p.x) { + // 8-bit index register to 8-bit accumulator. + cpu.r.a = cpu.r.x; + } else { + // 16-bit index register to 8-bit accumulator. + cpu.r.a = cpu.r.x & 0x00ff; + } + cpu.p.n = cpu.r.a >> 7; + } else { + // 8-bit index register to 16-bit accumulator. + // 16-bit index register to 16-bit accumulator. + cpu.r.a = cpu.r.x; + cpu.p.n = cpu.r.a >> 15; + } + cpu_lib.r.p.check_z(cpu, cpu.r.a); + cpu.instruction_history += " TXA"; + cpu.instruction_details += "
Transfer X Register to Accumulator"; + } +}; + +var TAX = { + toString: function() { return "TAX" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=2; + if(cpu.p.e||cpu.p.m) { + if(cpu.p.e||cpu.p.x) { + // 8-bit accumulator to 8-bit x index register. + cpu.r.x = cpu.r.a; + cpu.p.n = cpu.r.x >> 7; + } else { + // 8-bit accumulator to 16-bit x index register. + cpu.r.x = cpu.r.b; // Transfer b as high-byte of x. + cpu.r.x |= cpu.r.a; // Use the bitwise or to add a as the low-byte. + cpu.p.n = cpu.r.x >> 15; + } + } else { + if(cpu.p.x) { + // 16-bit accumulator to 8-bit x index register. + cpu.r.x = cpu.r.a & 0x00ff; // Transfer only the low-byte to x. + cpu.p.n = cpu.r.x >> 7; + } else { + // 16-bit accumulator to 16-bit x index register. + cpu.r.x = cpu.r.a; + cpu.p.n = cpu.r.x >> 15; + } + } + cpu_lib.r.p.check_z(cpu, cpu.r.x); + cpu.instruction_history += " TAX"; + cpu.instruction_details += "
Transfer Accumulator to X Register"; + } +}; + +var TXY = { + toString: function() { return "TXY" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=2; + cpu.r.y = cpu.r.x; + cpu_lib.r.p.check_z(cpu, cpu.r.y); + + if(cpu.p.e||cpu.p.x) { + cpu.p.n = cpu.r.y >> 7; + } else { + cpu.p.n = cpu.r.y >> 15; + } + cpu.instruction_history += " TXY"; + cpu.instruction_details += "
Transfer X Register to Y Register"; + } +}; + +var TYX = { + toString: function() { return "TYX" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=2; + cpu.r.x = cpu.r.y; + cpu_lib.r.p.check_z(cpu, cpu.r.x); + + if(cpu.p.e||cpu.p.x) { + cpu.p.n = cpu.r.y >> 7; + } else { + cpu.p.n = cpu.r.y >> 15; + } + cpu.instruction_history += " TYX"; + cpu.instruction_details += "
Transfer Y Register to X Register"; + } +}; + +var TCD = { + toString: function() { return "TCD" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=2; + + // Transfers 16-bits regardless of setting. + if(cpu.p.e||cpu.p.m) { + cpu.r.d = (cpu.r.b<<8)|cpu.r.a; + } else { + cpu.r.d = cpu.r.a; + } + + cpu.p.n = cpu.r.d >> 15; + + cpu_lib.r.p.check_z(cpu, cpu.r.d); + cpu.instruction_history += " TCD"; + cpu.instruction_details += "
Transfer C acc. to Direct Register"; + } +}; + +var TDC = { + toString: function() { return "TDC" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=2; + + // Transfers 16-bits regardless of setting. + if(cpu.p.e||cpu.p.m) { + cpu.r.a = cpu.r.d & 0xff; + cpu.r.b = cpu.r.d >> 8; + cpu.p.n = cpu.r.b >> 7; + } else { + cpu.r.a = cpu.r.d; + cpu.p.n = cpu.r.a >> 7; + } + + cpu_lib.r.p.check_z(cpu, cpu.r.a); + cpu.instruction_history += " TDC"; + cpu.instruction_details += "
Transfer Direct Register to C acc."; + } +}; + +var TCS = { + toString: function() { return "TCS" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=2; + if(cpu.p.e||!cpu.p.m) { + cpu.r.s = cpu.r.a; + } else { + cpu.r.s = (cpu.r.b<<8)|cpu.r.a; + } + cpu.instruction_history += " TCS"; + cpu.instruction_details += "
Transfer C acc. to Stack Pointer"; + } +}; + +var TSC = { + toString: function() { return "TSC" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=2; + + if(cpu.p.e) { + cpu.r.b = 1; + cpu.r.a = cpu.r.s; + // TODO: Figure out if in emulation mode the z and n bits should always + // be set to zero here as a 1 is transferred to b. + cpu.p.n = 0; + cpu.p.z = 0; + } else { + if(cpu.p.m) { + cpu.r.a = cpu.r.s & 0xff; + cpu.r.b = cpu.r.s >> 8; + cpu.p.n = cpu.r.b >> 7; + } else { + cpu.r.a = cpu.r.s; + cpu.p.n = cpu.r.a >> 15; + } + + cpu_lib.r.p.check_z(cpu, cpu.r.s); + } + cpu.instruction_history += " TSC"; + cpu.instruction_details += "
Transfer Stack Pointer to C acc."; + } +}; + +var STZ_absolute= { + toString: function() { return "STZ" }, + bytes_required:3, + + execute: function(cpu, bytes) { + cpu.cycle_count+=4; + + var memory_location = (bytes[1]<<8)|bytes[0]; + if(cpu.p.e||cpu.p.m) { + cpu.mmu.store_byte(memory_location, 0); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word(memory_location, 0); + } + } +}; + +var STZ_direct_page= { + toString: function() { return "STZ" }, + bytes_required:2, + + execute: function(cpu, bytes) { + cpu.cycle_count+=3; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0]+cpu.r.d; + if(cpu.p.e||cpu.p.m) { + cpu.mmu.store_byte(memory_location, 0); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word(memory_location, 0); + } + } +}; + +var STZ_absolute_indexed_x= { + toString: function() { return "STZ" }, + bytes_required:3, + + execute: function(cpu, bytes) { + cpu.cycle_count+=5; + + var memory_location = ((bytes[1]<<8)|bytes[0])+cpu.r.x; + if(cpu.p.e||cpu.p.m) { + cpu.mmu.store_byte(memory_location, 0); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word(memory_location, 0); + } + } +}; + +var STZ_direct_page_indexed_x= { + toString: function() { return "STZ" }, + bytes_required:2, + + execute: function(cpu, bytes) { + cpu.cycle_count+=4; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0]+cpu.r.d+cpu.r.x; + if(cpu.p.e||cpu.p.m) { + cpu.mmu.store_byte(memory_location, 0); + } else { + cpu.cycle_count++; + + // Check for overflow. + var overflow_check = memory_location - 0xffff; + if(overflow_check > 0) { + memory_location = overflow_check-1; + } + cpu.mmu.store_byte(memory_location, 0); + // Check for potential overflow again. + if(memory_location===0xffff) { + memory_location = 0; + } else { + memory_location++; + } + cpu.mmu.store_byte(memory_location, 0); + } + } +}; + +var STA_direct_page_indirect= { + toString: function() { return "STA" }, + bytes_required:2, + + execute:function(cpu, bytes) { + cpu.cycle_count+=5; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0] + cpu.r.d, + absolute_location = cpu.mmu.read_word(memory_location); + if(cpu.p.e||cpu.p.m) { + cpu.mmu.store_byte(absolute_location, cpu.r.a); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word(absolute_location, cpu.r.a); + } + } +}; + +var STA_direct_page_indirect_long= { + toString: function() { return "STA" }, + bytes_required:2, + + execute:function(cpu, bytes) { + cpu.cycle_count+=6; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0] + cpu.r.d, + absolute_location = cpu.mmu.read_word(memory_location), + bank_byte = cpu.mmu.read_byte(memory_location+2); + if(cpu.p.e||cpu.p.m) { + cpu.mmu.store_byte_long(absolute_location, bank_byte, + cpu.r.a); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word_long(absolute_location, bank_byte, cpu.r.a); + } + } +}; + +var STA_direct_page_indirect_long_indexed_y= { + toString: function() { return "STA" }, + bytes_required:2, + + execute:function(cpu, bytes) { + cpu.cycle_count+=6; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0] + cpu.r.d, + bank_byte = cpu.mmu.read_byte(memory_location+2), + absolute_location = cpu.mmu.read_word(memory_location) + cpu.r.y; + if(absolute_location >> 16) { + bank_byte++; + absolute_location &= 0xffff; + } + if(cpu.p.e||cpu.p.m) { + cpu.mmu.store_byte_long(absolute_location, bank_byte, cpu.r.a); + } else { + cpu.cycle_count++; + + cpu.mmu.store_byte_long(absolute_location, bank_byte, cpu.r.a&0xff); + absolute_location++; + if(absolute_location >> 16) { + bank_byte++; + } + cpu.mmu.store_byte_long(absolute_location, bank_byte, cpu.r.a>>8); + } + } +}; + +var STA_direct_page_indirect_indexed_y= { + toString: function() { return "STA" }, + bytes_required:2, + + execute:function(cpu, bytes) { + cpu.cycle_count+=6; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0] + cpu.r.d, + absolute_location = cpu.mmu.read_word(memory_location) + cpu.r.y; + if(cpu.p.e||cpu.p.m) { + cpu.mmu.store_byte(absolute_location, cpu.r.a); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word(absolute_location, cpu.r.a); + } + } +}; + +var STA_stack_relative= { + toString: function() { return "STA" }, + bytes_required:2, + + execute:function(cpu, bytes) { + cpu.cycle_count+=4; + + if(cpu.p.e) { + cpu.mmu.store_byte(0x100 | ((cpu.r.s + bytes[0]) & 0xff), cpu.r.a); + } else { + if(cpu.p.m) { + cpu.mmu.store_byte(cpu.r.s + bytes[0], cpu.r.a); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word(cpu.r.s + bytes[0], cpu.r.a); + } + } + } +}; + +var STA_stack_relative_indirect_indexed_y= { + toString: function() { return "STA" }, + bytes_required:2, + + execute:function(cpu, bytes) { + cpu.cycle_count+=7; + + if(cpu.p.e) { + var location_loc = 0x100 | ((cpu.r.s + bytes[0]) & 0xff), + low_byte = cpu.mmu.read_byte(location_loc), + high_byte; + if(location_loc===0x1ff) { + high_byte = cpu.mmu.read_byte(0x100); + } else { + high_byte = cpu.mmu.read_byte(location_loc+1); + } + var absolute_location = ((high_byte<<8)|low_byte)+cpu.r.y, + b; + if(absolute_location>=0x10000) { + b = cpu.mmu.read_byte_long(absolute_location, cpu.r.dbr+1); + } else { + b = cpu.mmu.read_byte(absolute_location); + } + cpu.mmu.store_byte(b, cpu.r.a); + } else { + var location_loc = cpu.r.s + bytes[0], + absolute_location = cpu.mmu.read_word(location_loc)+cpu.r.y; + if(cpu.p.m) { + var b; + if(absolute_location>=0x10000) { + b = cpu.mmu.read_byte_long(absolute_location, cpu.r.dbr+1); + } else { + b = cpu.mmu.read_byte(absolute_location); + } + cpu.mmu.store_byte(b, cpu.r.a); + } else { + cpu.cycle_count++; + + var low_byte, high_byte; + if(absolute_location>=0x10000) { + low_byte = cpu.mmu.read_byte_long(absolute_location, cpu.r.dbr+1); + high_byte = cpu.mmu.read_byte_long(absolute_location+1, cpu.r.dbr+1); + } else { + low_byte = cpu.mmu.read_byte(absolute_location); + if(absolute_location===0xffff) { + high_byte = cpu.mmu.read_byte_long(0, cpu.r.dbr+1); + } else { + high_byte = cpu.mmu.read_byte(absolute_location); + } + } + cpu.mmu.store_word((high_byte<<8)|low_byte, cpu.r.a); + } + } + } +}; + +var NOP = { + toString: function() { return "NOP" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=2; + cpu.instruction_history += " NOP"; + cpu.instruction_details += "
No OPeration" + } +}; + +var LDY_const= { + toString: function() { return "LDY" }, + bytes_required:function(cpu) { + if(cpu.p.e||cpu.p.x) + return 2; + else + return 3; + }, + execute:function(cpu, bytes) { + cpu.instruction_details += "
Load value to register Y"; + if(!cpu.instruction_translated) { + cpu.instruction_details += " (const)"; + cpu.instruction_translate = this.toString() + " #" + bytesToString(bytes); + cpu.instruction_history += " " + this.toString() + " #" + bytesToString(bytes); + } + cpu.cycle_count+=2; + + if(cpu.p.e||cpu.p.x) { + cpu.r.y = bytes[0]; + cpu.p.n = cpu.r.y >> 7; + } else { + cpu.cycle_count++; + + cpu.r.y = (bytes[1]<<8)|bytes[0]; + cpu.p.n = cpu.r.y >> 15; + } + cpu_lib.r.p.check_z(cpu, cpu.r.y); + } +}; + +var LDY_absolute = new cpu_lib.addressing.Absolute(LDY_const); + +var LDY_absolute_indexed_x = + new cpu_lib.addressing.Absolute_indexed_x(LDY_absolute); + +var LDY_direct_page = new cpu_lib.addressing.Direct_page(LDY_const); + +var LDY_direct_page_indexed_x = + new cpu_lib.addressing.Direct_page_indexed(LDY_direct_page, 'x'); + +// Implement the decrement instructions as increment instructions that +// increment by negative one. +var DEX = new cpu_lib.inc.INC_register('x', 'x', -1); + +var DEY = new cpu_lib.inc.INC_register('y', 'x', -1); + +var DEC_accumulator = new cpu_lib.inc.INC_register('a', 'm', -1); + +var DEC_memory = new cpu_lib.inc.INC_memory(-1); + +var DEC_absolute = new cpu_lib.addressing.Absolute(DEC_memory); + +var DEC_absolute_indexed_x = + new cpu_lib.addressing.Absolute_indexed_x(DEC_absolute, true); + +var DEC_direct_page = new cpu_lib.addressing.Direct_page(DEC_memory); + +var DEC_direct_page_indexed_x = + new cpu_lib.addressing.Direct_page_indexed(DEC_direct_page, 'x'); + +var INX = new cpu_lib.inc.INC_register('x', 'x'); + +var INY = new cpu_lib.inc.INC_register('y', 'x'); + +var INC_accumulator = new cpu_lib.inc.INC_register('a', 'm'); + +var INC_memory = new cpu_lib.inc.INC_memory(); + +var INC_absolute = new cpu_lib.addressing.Absolute(INC_memory); + +var INC_absolute_indexed_x = + new cpu_lib.addressing.Absolute_indexed_x(INC_absolute, true); + +var INC_direct_page = new cpu_lib.addressing.Direct_page(INC_memory); + +var INC_direct_page_indexed_x = + new cpu_lib.addressing.Direct_page_indexed(INC_direct_page, 'x'); + +var STA_direct_page_indexed_x= { + toString: function() { return "STA" }, + bytes_required:2, + + execute: function(cpu, bytes) { + cpu.cycle_count+=4; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0]+cpu.p.d+cpu.r.x; + if(cpu.p.e||cpu.p.m) { + cpu.mmu.store_byte(memory_location, cpu.r.a); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word(memory_location, cpu.r.a); + } + } +}; + +var STA_direct_page_indexed_x_indirect= { + toString: function() { return "STA" }, + bytes_required:2, + + execute:function(cpu, bytes) { + cpu.cycle_count+=6; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + if(cpu.p.e) { + var memory_location = (bytes[0] + cpu.r.x) & 0xff, + low_byte_loc = cpu.mmu.read_byte_long(memory_location+cpu.r.d, 0), + high_byte_read_loc = ((memory_location+1)&0xff)+cpu.r.d, + high_byte_loc = cpu.mmu.read_byte_long(high_byte_read_loc, 0); + cpu.mmu.store_byte((high_byte_loc<<8) | low_byte_loc, cpu.r.a); + } else if(cpu.p.m) { + var memory_location = bytes[0] + cpu.r.d + cpu.r.x; + cpu.mmu.store_byte(cpu.mmu.read_word(memory_location), cpu.r.a); + } else { + cpu.cycle_count++; + + var memory_location = bytes[0] + cpu.r.d + cpu.r.x, + absolute_location = cpu.mmu.read_word(memory_location), + low_byte = cpu.mmu.read_byte(absolute_location), + high_byte; + absolute_location++; + if(absolute_location&0x10000) { + high_byte = cpu.mmu.read_byte_long(absolute_location, cpu.r.dbr+1); + } else { + high_byte = cpu.mmu.read_byte(absolute_location); + } + var storage_location = (high_byte<<8) | low_byte; + cpu.mmu.store_byte(storage_location, cpu.r.a & 0xff); + storage_location++; + if(storage_location&0x10000) { + cpu.mmu.store_byte_long(storage_location, cpu.r.dbr+1, cpu.r.a >> 8); + } else { + cpu.mmu.store_byte(storage_location, cpu.r.a >> 8); + } + } + } +}; + +var STA_direct_page= { + toString: function() { return "STA" }, + bytes_required:2, + + execute: function(cpu, bytes) { + cpu.cycle_count+=3; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0]+cpu.p.d; + if(cpu.p.e||cpu.p.m) { + cpu.mmu.store_byte(memory_location, cpu.r.a); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word(memory_location, cpu.r.a); + } + } +}; + +var STA_absolute_indexed_x= { + toString: function() { return "STA" }, + bytes_required:3, + + execute: function(cpu, bytes) { + cpu.cycle_count+=5; + + var memory_location = ((bytes[1]<<8)|bytes[0])+cpu.r.x; + if(cpu.p.e||cpu.p.m) { + cpu.mmu.store_byte(memory_location, cpu.r.a); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word(memory_location, cpu.r.a); + } + } +}; + +var STA_absolute_indexed_y= { + toString: function() { return "STA" }, + bytes_required:3, + + execute: function(cpu, bytes) { + cpu.cycle_count+=5; + + var memory_location = ((bytes[1]<<8)|bytes[0])+cpu.r.y; + if(cpu.p.e||cpu.p.m) { + cpu.mmu.store_byte(memory_location, cpu.r.a); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word(memory_location, cpu.r.a); + } + } +}; + +var STY_direct_page_indexed_x= { + toString: function() { return "STA" }, + bytes_required:2, + + execute: function(cpu, bytes) { + cpu.cycle_count+=4; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0]+cpu.p.d+cpu.r.x; + if(cpu.p.e||cpu.p.x) { + cpu.mmu.store_byte(memory_location, cpu.r.y); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word(memory_location, cpu.r.y); + } + } +}; + +var STY_direct_page= { + toString: function() { return "STY" }, + bytes_required:2, + + execute: function(cpu, bytes) { + cpu.cycle_count+=3; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0]+cpu.p.d; + if(cpu.p.e||cpu.p.x) { + cpu.mmu.store_byte(memory_location, cpu.r.y); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word(memory_location, cpu.r.y); + } + } +}; + +var STY_absolute= { + toString: function() { return "STY" }, + bytes_required:3, + + execute: function(cpu, bytes) { + cpu.cycle_count+=4; + + var memory_location = (bytes[1]<<8)|bytes[0]; + if(cpu.p.e||cpu.p.x) { + cpu.mmu.store_byte(memory_location, cpu.r.y); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word(memory_location, cpu.r.y); + } + } +}; + +var STX_direct_page_indexed_y= { + toString: function() { return "STX" }, + bytes_required:2, + + execute: function(cpu, bytes) { + cpu.cycle_count+=4; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0]+cpu.p.d+cpu.r.y; + if(cpu.p.e||cpu.p.x) { + cpu.mmu.store_byte(memory_location, cpu.r.x); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word(memory_location, cpu.r.x); + } + } +}; + +var STX_direct_page= { + toString: function() { return "STX" }, + bytes_required:2, + + execute: function(cpu, bytes) { + cpu.cycle_count+=3; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0]+cpu.p.d; + if(cpu.p.e||cpu.p.x) { + cpu.mmu.store_byte(memory_location, cpu.r.x); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word(memory_location, cpu.r.x); + } + } +}; + +var STX_absolute= { + toString: function() { return "STX" }, + bytes_required:3, + + execute: function(cpu, bytes) { + cpu.cycle_count+=4; + + var memory_location = (bytes[1]<<8)|bytes[0]; + if(cpu.p.e||cpu.p.x) { + cpu.mmu.store_byte(memory_location, cpu.r.x); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word(memory_location, cpu.r.x); + } + } +}; + +var STA_absolute_long= { + toString: function() { return "STA" }, + bytes_required:4, + + execute: function(cpu, bytes) { + cpu.cycle_count+=5; + + var memory_location = (bytes[1]<<8)|bytes[0]; + if(cpu.p.e||cpu.p.m) { + cpu.mmu.store_byte_long(memory_location, bytes[2], cpu.r.a); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word_long(memory_location, bytes[2], cpu.r.a); + } + } +}; + +var STA_absolute_long_indexed_x= { + toString: function() { return "STA" }, + bytes_required:4, + + execute: function(cpu, bytes) { + cpu.cycle_count+=5; + + var memory_location = ((bytes[1]<<8)|bytes[0]) + cpu.r.x; + if(memory_location & 0x10000) { + memory_location &= 0xffff; + bytes[2]++; + } + if(cpu.p.e||cpu.p.m) { + cpu.mmu.store_byte_long(memory_location, bytes[2], cpu.r.a); + } else { + cpu.cycle_count++; + + cpu.mmu.store_byte_long(memory_location, bytes[2], cpu.r.a&0x00ff); + memory_location++; + if(memory_location & 0x10000) { + bytes[2]++; + } + cpu.mmu.store_byte_long(memory_location, bytes[2], cpu.r.a>>8); + } + } +}; + +var STA_absolute= { + toString: function() { return "STA" }, + bytes_required:3, + + execute: function(cpu, bytes) { + cpu.cycle_count+=4; + + var memory_location = (bytes[1]<<8)|bytes[0]; + if(cpu.p.e||cpu.p.m) { + cpu.mmu.store_byte(memory_location, cpu.r.a); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word(memory_location, cpu.r.a); + } + } +}; + +var LDA_const= { + toString: function() { return "LDA" }, + bytes_required: function(cpu) { + if(cpu.p.e||cpu.p.m) { + return 2; + } else { + return 3; + } + }, + execute: function(cpu, bytes) { + cpu.instruction_details += "
Load value to accumulator"; + if(!cpu.instruction_translated) { + cpu.instruction_details += " (const)"; + cpu.instruction_translate = this.toString() + " #" + bytesToString(bytes); + cpu.instruction_history += " " + this.toString() + " #" + bytesToString(bytes); + } + cpu.cycle_count+=2; + + if(cpu.p.e||cpu.p.m) { + cpu.r.a = bytes[0]; + cpu.p.n = cpu.r.a >> 7; + } else { + cpu.cycle_count++; + + cpu.r.a = (bytes[1]<<8)|bytes[0]; + cpu.p.n = cpu.r.a >> 15; + } + cpu_lib.r.p.check_z(cpu, cpu.r.a); + } +}; + +var LDA_direct_page = new cpu_lib.addressing.Direct_page(LDA_const); + +var LDA_direct_page_indexed_x = + new cpu_lib.addressing.Direct_page_indexed(LDA_direct_page, 'x'); + +var LDA_direct_page_indirect = + new cpu_lib.addressing.Direct_page_indirect(LDA_const); + +var LDA_direct_page_indirect_indexed_y = + new cpu_lib.addressing.Direct_page_indirect_indexed_y(LDA_const); + +var LDA_absolute = new cpu_lib.addressing.Absolute(LDA_const); + +var LDA_absolute_indexed_x = + new cpu_lib.addressing.Absolute_indexed_x(LDA_absolute); + +var LDA_absolute_indexed_y = + new cpu_lib.addressing.Absolute_indexed_y(LDA_absolute); + +var LDA_stack_relative = new cpu_lib.addressing.Stack_relative(LDA_const); + +var LDA_stack_relative_indirect_indexed_y = + new cpu_lib.addressing.Stack_relative_indirect_indexed_y(LDA_const); + +var LDA_absolute_long = new cpu_lib.addressing.Absolute_long(LDA_const); + +var LDA_absolute_long_indexed_x = + new cpu_lib.addressing.Absolute_long_indexed_x(LDA_const); + +var LDA_direct_page_indexed_x_indirect = + new cpu_lib.addressing.Direct_page_indexed_x_indirect(LDA_const); + +var LDA_direct_page_indirect_long = + new cpu_lib.addressing.Direct_page_indirect_long(LDA_const); + +var LDA_direct_page_indirect_long_indexed_y = + new cpu_lib.addressing.Direct_page_indirect_long_indexed_y(LDA_const); + +var LDX_const= { + toString: function() { return "LDX" }, + bytes_required: function(cpu) { + if(cpu.p.e||cpu.p.x) { + return 2; + } else { + return 3; + } + }, + execute: function(cpu, bytes) { + cpu.instruction_details += "
Load value to register X"; + if(!cpu.instruction_translated) { + cpu.instruction_details += " (const)"; + cpu.instruction_translate = this.toString() + " #" + bytesToString(bytes); + cpu.instruction_history += " " + this.toString() + " #" + bytesToString(bytes); + } + cpu.cycle_count+=2; + + if(cpu.p.e||cpu.p.x) { + cpu.r.x = bytes[0]; + cpu.p.n = cpu.r.x >> 7; + } else { + cpu.cycle_count++; + + cpu.r.x = (bytes[1]<<8)|bytes[0]; + cpu.p.n = cpu.r.x >> 15; + } + cpu_lib.r.p.check_z(cpu, cpu.r.x); + } +}; + +var LDX_direct_page = new cpu_lib.addressing.Direct_page(LDX_const); + +var LDX_direct_page_indexed_y = + new cpu_lib.addressing.Direct_page_indexed(LDX_direct_page, 'y'); + +var LDX_absolute = new cpu_lib.addressing.Absolute(LDX_const); + +var LDX_absolute_indexed_y = + new cpu_lib.addressing.Absolute_indexed_y(LDX_absolute); + +var SEP = new cpu_lib.r.p.Set_p(1); + +var REP = new cpu_lib.r.p.Set_p(0); + +var XCE = { + toString: function() { return "XCE" }, + bytes_required:1, + + execute: function(cpu) { + cpu.cycle_count+=2; + + var temp = cpu.p.c; + cpu.p.c = cpu.p.e; + cpu.p.e = temp; + if(cpu.p.e) { + // Switching to emulation mode. + cpu.r.b = cpu.r.a >> 8; + cpu.r.a &= 0x00ff; + cpu.r.s &= 0xff; + } else { + // Switching to native mode. + cpu.r.a = (cpu.r.b<<8) | cpu.r.a; + cpu.p.m = 1; + cpu.p.x = 1; + cpu.r.s |= 0x100; + } + } +}; + +var CLC = new cpu_lib.r.p.Flag_set('c', 0); + +var SEC = new cpu_lib.r.p.Flag_set('c', 1); + +var CLI = new cpu_lib.r.p.Flag_set('i', 0); + +var SEI = new cpu_lib.r.p.Flag_set('i', 1); + +var CLD = new cpu_lib.r.p.Flag_set('d', 0); + +var SED = new cpu_lib.r.p.Flag_set('d', 1); + +var CLV = new cpu_lib.r.p.Flag_set('v', 0); + +var XBA = { + toString: function() { return "XBA" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=3; + + if(cpu.p.e) { + cpu.cycle_count+=2; + var old_a = cpu.r.a; + cpu.r.a = cpu.r.b; + cpu.r.b = old_a; + + cpu.p.n = cpu.r.a >> 7; + cpu_lib.r.p.check_z(cpu, cpu.r.a); + } else { + var low_byte = cpu.r.a & 0xff; + var high_byte = cpu.r.a >> 8; + cpu.r.a = (low_byte<<8)|high_byte; + + cpu.p.n = high_byte >> 7; + cpu_lib.r.p.check_z(cpu, high_byte); + } + } +}; + +var MMU = function() { + this.cpu = {}; + this.memory = { 0: {} }; + this.memory_mapped_io_devices = {}; + + this.reset = function() { + this.memory ={ 0: {} }; + }; + + this.add_memory_mapped_io_device = function(write_callback, read_callback, + bank, location) { + if(typeof this.memory_mapped_io_devices[bank] === 'undefined') { + this.memory_mapped_io_devices[bank] = {}; + } + this.memory_mapped_io_devices[bank][location] = { write: write_callback, + read: read_callback }; + }; + + this.pull_byte = function() { + if(this.cpu.p.e) { + if(this.cpu.r.s===0xff) { + this.cpu.r.s = 0; + return this.read_byte(0x100); + } else { + return this.read_byte(0x100|(++this.cpu.r.s)); + } + } else { + return this.read_byte(++this.cpu.r.s); + } + }; + + this.push_byte = function(b) { + if(this.cpu.p.e) { + if(this.cpu.r.s===0) { + this.store_byte(0x100, b); + this.cpu.r.s = 0xff; + } else { + this.store_byte((0x100|(this.cpu.r.s--)), b); + } + } else { + this.store_byte(this.cpu.r.s--, b); + } + }; + + this.read_byte = function(memory_location) { + memory_location &= 0xffff; // Make sure the address is 16 bits. + + var device_map_at_bank = this.memory_mapped_io_devices[this.cpu.r.dbr]; + if(typeof device_map_at_bank !== "undefined") { + var device = device_map_at_bank[memory_location]; + if(typeof device !== "undefined") + return device.read(this.cpu); + } + return this.memory[this.cpu.r.dbr][memory_location]; + }; + + this.read_byte_long = function(memory_location, bank) { + // Make sure addresses given are the proper size. + memory_location &= 0xffff; + bank &= 0xff; + + if(typeof this.memory[bank] === 'undefined') { + this.memory[bank] = {}; + } + var device_map_at_bank = this.memory_mapped_io_devices[bank]; + if(typeof device_map_at_bank !== "undefined") { + var device = device_map_at_bank[memory_location]; + if(typeof device !== "undefined") + return device.read(this.cpu); + } + return this.memory[bank][memory_location]; + }; + + this.store_byte = function(memory_location, b) { + memory_location &= 0xffff; // Make sure the address is 16 bits + b &= 0xff; // Make sure the byte is actually a byte long + + var device_map_at_bank = this.memory_mapped_io_devices[this.cpu.r.dbr]; + if(typeof device_map_at_bank !== "undefined") { + var device = device_map_at_bank[memory_location]; + if(typeof device !== "undefined") + device.write(this.cpu, b); + } + this.memory[this.cpu.r.dbr][memory_location] = b; + }; + + this.store_byte_long = function(memory_location, bank, b) { + // Make sure addresses and byte given are the proper size. + memory_location &= 0xffff; + bank &= 0xff; + b &= 0xff; + + if(typeof this.memory[bank] === 'undefined') { + this.memory[bank] = {}; + } + var device_map_at_bank = this.memory_mapped_io_devices[bank]; + if(typeof device_map_at_bank !== "undefined") { + var device = device_map_at_bank[memory_location]; + if(typeof device !== "undefined") + device.write(this.cpu, b); + } + this.memory[bank][memory_location] = b; + }; + + this.read_word = function(memory_location) { + return (this.read_byte(memory_location+1)<<8) | + this.read_byte(memory_location); + }; + + this.read_word_long = function(memory_location, bank) { + return (this.read_byte_long(memory_location+1, bank)<<8) | + this.read_byte_long(memory_location, bank); + }; + + this.store_word = function(memory_location, word_or_low_byte, high_byte) { + // If no high_byte is given then assume word_or_low_byte is a word. + if(typeof high_byte === 'undefined') { + this.store_byte(memory_location, word_or_low_byte&0xff); + this.store_byte(memory_location+1, word_or_low_byte>>8); + } else { + this.store_byte(memory_location, word_or_low_byte); + this.store_byte(memory_location+1, high_byte); + } + }; + + this.store_word_long = function(memory_location, bank, word_or_low_byte, + high_byte) { + // If no high_byte is given then assume word_or_low_byte is a word. + if(typeof high_byte === 'undefined') { + this.store_byte_long(memory_location, bank, word_or_low_byte&0xff); + this.store_byte_long(memory_location+1, bank, word_or_low_byte>>8); + } else { + this.store_byte_long(memory_location, bank, word_or_low_byte); + this.store_byte_long(memory_location+1, bank, high_byte); + } + }; +}; + +window.CPU_65816 = function() { + + // Instruction & Instruction details to print + this.instruction = ""; + this.instruction_details = ""; + this.instruction_translate = ""; + this.instruction_history = ""; + this.instruction_translated = false; + + // Registers + this.r = { + a:0, // Accumulator + b:0, // "Hidden" Accumulator Register(high byte in 8-bit mode) + x:0, // X Index Register + y:0, // Y Index Register + d:0, // Direct Page Register + s:0xff, // Stack Pointer + pc:0, // Program Counter + dbr:0, // Data Bank Register + k:0 // Program Bank Register + }; + + // P register flags. + this.p = { + e:1, // Emulator (0 = native mode) + c:0, // Carry (1 = carry) + z:0, // Zero (1 = zero) + i:0, // IRQ Disable (1 = disabled) + d:0, // Decimal Mode (1 = decimal, 0 = binary) + x:0, // Index Register Select (1 = 8-bit, 0 = 16-bit) + m:0, // Memory/Accumulator Select (1 = 8-bit, 0 = 16-bit) + v:0, // Overflow (1 = overflow) + n:0 // Negative (1 = negative) + }; + + this.INTERRUPT = { NO_INTERRUPT: 0, NMI: 1, RESET: 2, ABORT: 3, COP: 4, + IRQ: 5, BRK: 6 }; + + this.interrupt = this.INTERRUPT.NO_INTERRUPT; + + // This is used to keep the cpu going if started with start(). + this.executing = false; + + // This is set by the WAI operation to stop execution until an interrupt + // is received. + this.waiting = false; + + // This is set by the STP operation to stop execution until a RESET + // interrupt is received. + this.stopped = false; + + this.raise_interrupt = function(i) { + if(this.waiting) { + this.waiting = false; + if(this.p.i) { + if(i===this.INTERRUPT.IRQ) { + i = this.INTERRUPT.NO_INTERRUPT; + } + } + this.interrupt = i; + this.start(); + } else if(this.stopped&&(i===this.INTERRUPT.RESET)) { + this.stopped = false; + this.start(); + } else { + this.interrupt = i; + } + }; + + this.cycle_count = 0; + + this.mmu = new MMU(); + this.mmu.cpu = this; + + this.opcode_map = { 0xfb : XCE, 0x18 : CLC, 0x78 : SEI, 0x38 : SEC, + 0x58 : CLI, 0xc2 : REP, 0xe2 : SEP, 0xd8 : CLD, + 0xf8 : SED, 0xb8 : CLV, 0xeb : XBA, 0xa9 : LDA_const, + 0xad : LDA_absolute, 0xaf : LDA_absolute_long, + 0xbf : LDA_absolute_long_indexed_x, + 0xa5 : LDA_direct_page, 0xbd : LDA_absolute_indexed_x, + 0xb5 : LDA_direct_page_indexed_x, + 0xb9 : LDA_absolute_indexed_y, + 0xa1 : LDA_direct_page_indexed_x_indirect, + 0xb2 : LDA_direct_page_indirect, + 0xa7 : LDA_direct_page_indirect_long, + 0xb7 : LDA_direct_page_indirect_long_indexed_y, + 0xb1 : LDA_direct_page_indirect_indexed_y, + 0xa3 : LDA_stack_relative, + 0xb3 : LDA_stack_relative_indirect_indexed_y, + 0xa2 : LDX_const, + 0xae : LDX_absolute, 0xa6 : LDX_direct_page, + 0xa0 : LDY_const, 0xbc : LDY_absolute_indexed_x, + 0xb4 : LDY_direct_page_indexed_x, + 0xbe : LDX_absolute_indexed_y, + 0xb6 : LDX_direct_page_indexed_y, + 0xac : LDY_absolute, 0xa4 : LDY_direct_page, 0xea : NOP, + 0x8d : STA_absolute, 0x85 : STA_direct_page, + 0x8f : STA_absolute_long, + 0x9f : STA_absolute_long_indexed_x, + 0x81 : STA_direct_page_indexed_x_indirect, + 0x92 : STA_direct_page_indirect, + 0x87 : STA_direct_page_indirect_long, + 0x97 : STA_direct_page_indirect_long_indexed_y, + 0x91 : STA_direct_page_indirect_indexed_y, + 0x9d : STA_absolute_indexed_x, + 0x99 : STA_absolute_indexed_y, + 0x95 : STA_direct_page_indexed_x, + 0x83 : STA_stack_relative, + 0x93 : STA_stack_relative_indirect_indexed_y, + 0x8e : STX_absolute, 0x86 : STX_direct_page, + 0x96 : STX_direct_page_indexed_y, + 0x8c : STY_absolute, 0x84 : STY_direct_page, + 0x94 : STY_direct_page_indexed_x, + 0x1a : INC_accumulator, 0xe6 : INC_direct_page, + 0xee : INC_absolute, 0xf6 : INC_direct_page_indexed_x, + 0xfe : INC_absolute_indexed_x, + 0xe8 : INX, 0xc8 : INY, + 0x3a : DEC_accumulator, 0xce : DEC_absolute, + 0xde : DEC_absolute_indexed_x, + 0xc6 : DEC_direct_page, 0xca : DEX, 0x88 : DEY, + 0xd6 : DEC_direct_page_indexed_x, + 0x9c : STZ_absolute, 0x64 : STZ_direct_page, + 0x9e : STZ_absolute_indexed_x, + 0x74 : STZ_direct_page_indexed_x, 0x9b : TXY, + 0xbb : TYX, 0xaa : TAX, 0xa8 : TAY, 0x8a : TXA, + 0x98 : TYA, 0x5b : TCD, 0x7b : TDC, 0x1b : TCS, + 0x3b : TSC, 0x4c : JMP_absolute, + 0x5c : JMP_absolute_long, + 0xdc : JMP_absolute_indirect_long, + 0x7c : JMP_absolute_indexed_x_indirect, + 0x6c : JMP_absolute_indirect, 0x80 : BRA, 0x82 : BRL, + 0xf0 : BEQ, 0xd0 : BNE, 0x90 : BCC, 0xb0 : BCS, + 0x50 : BVC, 0x70 : BVS, 0x10 : BPL, 0x30 : BMI, + 0x69 : ADC_const, 0x6d : ADC_absolute, + 0x61 : ADC_direct_page_indexed_x_indirect, + 0x6f : ADC_absolute_long, + 0x7f : ADC_absolute_long_indexed_x, + 0x65 : ADC_direct_page, 0x72 : ADC_direct_page_indirect, + 0x67 : ADC_direct_page_indirect_long, + 0x77 : ADC_direct_page_indirect_long_indexed_y, + 0x71 : ADC_direct_page_indirect_indexed_y, + 0x7d : ADC_absolute_indexed_x, + 0x79 : ADC_absolute_indexed_y, + 0x75 : ADC_direct_page_indexed_x, + 0x63 : ADC_stack_relative, + 0x73 : ADC_stack_relative_indirect_indexed_y, + 0xe9 : SBC_const, + 0xed : SBC_absolute, 0xe5 : SBC_direct_page, + 0xef : SBC_absolute_long, + 0xff : SBC_absolute_long_indexed_x, + 0xf2 : SBC_direct_page_indirect, + 0xe1 : SBC_direct_page_indexed_x_indirect, + 0xe7 : SBC_direct_page_indirect_long, + 0xf7 : SBC_direct_page_indirect_long_indexed_y, + 0xf1 : SBC_direct_page_indirect_indexed_y, + 0xfd : SBC_absolute_indexed_x, + 0xf9 : SBC_absolute_indexed_y, + 0xf5 : SBC_direct_page_indexed_x, + 0xe3 : SBC_stack_relative, + 0xf3 : SBC_stack_relative_indirect_indexed_y, + 0xc9 : CMP_const, 0xc5 : CMP_direct_page, + 0xcd : CMP_absolute, 0xd2 : CMP_direct_page_indirect, + 0xcf : CMP_absolute_long, + 0xdf : CMP_absolute_long_indexed_x, + 0xc7 : CMP_direct_page_indirect_long, + 0xd7 : CMP_direct_page_indirect_long_indexed_y, + 0xd5 : CMP_direct_page_indexed_x, + 0xc1 : CMP_direct_page_indexed_x_indirect, + 0xdd : CMP_absolute_indexed_x, + 0xd9 : CMP_absolute_indexed_y, + 0xd1 : CMP_direct_page_indirect_indexed_y, + 0xc3 : CMP_stack_relative, + 0xd3 : CMP_stack_relative_indirect_indexed_y, + 0xe0 : CPX_const, + 0xec : CPX_absolute, 0xe4 : CPX_direct_page, + 0xc0 : CPY_const, 0xcc : CPY_absolute, + 0xc4 : CPY_direct_page, 0x29 : AND_const, + 0x2d : AND_absolute, 0x25 : AND_direct_page, + 0x2f : AND_absolute_long, + 0x3f : AND_absolute_long_indexed_x, + 0x21 : AND_direct_page_indexed_x_indirect, + 0x32 : AND_direct_page_indirect, + 0x27 : AND_direct_page_indirect_long, + 0x37 : AND_direct_page_indirect_long_indexed_y, + 0x31 : AND_direct_page_indirect_indexed_y, + 0x3d : AND_absolute_indexed_x, + 0x39 : AND_absolute_indexed_y, + 0x35 : AND_direct_page_indexed_x, + 0x23 : AND_stack_relative, + 0x33 : AND_stack_relative_indirect_indexed_y, + 0x09 : ORA_const, 0x0f : ORA_absolute_long, + 0x0d : ORA_absolute, 0x05 : ORA_direct_page, + 0x1f : ORA_absolute_long_indexed_x, + 0x12 : ORA_direct_page_indirect, + 0x01 : ORA_direct_page_indexed_x_indirect, + 0x07 : ORA_direct_page_indirect_long, + 0x17 : ORA_direct_page_indirect_long_indexed_y, + 0x11 : ORA_direct_page_indirect_indexed_y, + 0x1d : ORA_absolute_indexed_x, + 0x19 : ORA_absolute_indexed_y, + 0x15 : ORA_direct_page_indexed_x, + 0x03 : ORA_stack_relative, + 0x13 : ORA_stack_relative_indirect_indexed_y, + 0x49 : EOR_const, 0x4d : EOR_absolute, + 0x4f : EOR_absolute_long, + 0x5f : EOR_absolute_long_indexed_x, + 0x45 : EOR_direct_page, + 0x52 : EOR_direct_page_indirect, + 0x41 : EOR_direct_page_indexed_x_indirect, + 0x47 : EOR_direct_page_indirect_long, + 0x57 : EOR_direct_page_indirect_long_indexed_y, + 0x51 : EOR_direct_page_indirect_indexed_y, + 0x5d : EOR_absolute_indexed_x, + 0x59 : EOR_absolute_indexed_y, + 0x55 : EOR_direct_page_indexed_x, + 0x43 : EOR_stack_relative, + 0x53 : EOR_stack_relative_indirect_indexed_y, + 0x4a : LSR_accumulator, 0x4e : LSR_absolute, + 0x46 : LSR_direct_page, 0x5e : LSR_absolute_indexed_x, + 0x56 : LSR_direct_page_indexed_x, 0x0a : ASL_accumulator, + 0x0e : ASL_absolute, 0x06 : ASL_direct_page, + 0x1e : ASL_absolute_indexed_x, + 0x16 : ASL_direct_page_indexed_x, 0x2a : ROL_accumulator, + 0x2e : ROL_absolute, 0x26 : ROL_direct_page, + 0x3e : ROL_absolute_indexed_x, + 0x36 : ROL_direct_page_indexed_x, 0x6a : ROR_accumulator, + 0x6e : ROR_absolute, 0x66 : ROR_direct_page, + 0x7e : ROR_absolute_indexed_x, + 0x76 : ROR_direct_page_indexed_x, + 0x48 : PHA, 0x68 : PLA, 0x5a : PHY, 0x7a : PLY, + 0xda : PHX, 0xfa : PLX, 0x08 : PHP, 0x28 : PLP, + 0xf4 : PEA, 0xd4 : PEI, 0x8b : PHB, 0xab : PLB, + 0x4b : PHK, 0x0b : PHD, 0x2b : PLD, 0x62 : PER, + 0x20 : JSR, 0xfc : JSR_absolute_indexed_x_indirect, + 0x60 : RTS, 0x22 : JSL, 0x6b : RTL, + 0x54 : MVN, 0x44 : MVP, 0x00 : BRK, 0x40 : RTI, + 0x02 : COP, 0x89 : BIT_const, 0x2c : BIT_absolute, + 0x24 : BIT_direct_page, + 0x3c : BIT_absolute_indexed_x, + 0x34 : BIT_direct_page_indexed_x, + 0x0c : TSB_absolute, 0x04 : TSB_direct_page, + 0x1c : TRB_absolute, 0x14 : TRB_direct_page, + 0x9a : TXS, 0xba : TSX, 0x42: WDM, 0xcb : WAI, + 0xdb : STP }; + + /** + * Load given program into memory and prepare for execution. + * raw_hex could either be a string of hex numbers or an array + * of bytes. + */ + this.load_binary = function(raw_hex, memory_location_start, bank) { + var byte_buffer = [], + i = 0, + starting_memory = memory_location_start; + + if(typeof bank === "undefined") { + bank = 0; + } + + if(typeof raw_hex === 'string') { + for(;i < raw_hex.length; i++) { + byte_buffer.push(raw_hex.charAt(i)); + if(byte_buffer.length===2) { + this.mmu.store_byte_long(memory_location_start, bank, + parseInt(byte_buffer[0]+byte_buffer[1], + 16)); + memory_location_start++; + byte_buffer = []; + } + } + } else { + for(;i < raw_hex.length; i++) { + this.mmu.store_byte_long(memory_location_start, bank, raw_hex[i]); + memory_location_start++; + } + } + this.r.pc = starting_memory; + }; + + /** + * Step through the processing of a single instruction from the current + * location of the program counter. + */ + this.step = function() { + this.instruction_translated = false; + if(this.interrupt&&(!this.p.i||(this.interrupt===this.INTERRUPT.NMI))) { + // Load the related interrupt vector in page 0xff of bank zero. + if(!this.p.e) { + this.mmu.push_byte(this.r.k); + } + this.mmu.push_byte(this.r.pc>>8); + this.mmu.push_byte(this.r.pc&0xff); + var p_byte = (this.p.n<<7)|(this.p.v<<6)|(this.p.m<<5)|(this.p.x<<4)| + (this.p.d<<3)|(this.p.i<<2)|(this.p.z<<1)|this.p.c; + this.mmu.push_byte(p_byte); + if(!this.p.e) + this.p.d = 0; + this.p.i = 1; + this.r.k = 0; + + // Look for where to jump to for the interrupt. + if(this.p.e) { + // NMI + if(this.interrupt===this.INTERRUPT.NMI) { + this.r.pc = this.mmu.read_word_long(0xfffa, 0); + // RESET + } else if(this.interrupt===this.INTERRUPT.RESET) { + this.r.pc = this.mmu.read_word_long(0xfffc, 0); + // ABORT + } else if(this.interrupt===this.INTERRUPT.ABORT) { + this.r.pc = this.mmu.read_word_long(0xfff8, 0); + // COP + } else if(this.interrupt===this.INTERRUPT.COP) { + this.r.pc = this.mmu.read_word_long(0xfff4, 0); + // IRQ or BRK + } else if(this.interrupt===this.INTERRUPT.IRQ || + this.interrupt===this.INTERRUPT.BRK) { + this.r.pc = this.mmu.read_word_long(0xfffe, 0); + } + } else { + // NMI + if(this.interrupt===this.INTERRUPT.NMI) { + this.r.pc = this.mmu.read_word_long(0xffea, 0); + // ABORT + } else if(this.interrupt===this.INTERRUPT.ABORT) { + this.r.pc = this.mmu.read_word_long(0xffe8, 0); + // COP + } else if(this.interrupt===this.INTERRUPT.COP) { + this.r.pc = this.mmu.read_word_long(0xffe4, 0); + // IRQ + } else if(this.interrupt===this.INTERRUPT.IRQ) { + this.r.pc = this.mmu.read_word_long(0xffee, 0); + // BRK + } else if(this.interrupt===this.INTERRUPT.BRK) { + this.r.pc = this.mmu.read_word_long(0xffe6, 0); + } + } + + this.interrupt = this.INTERRUPT.NO_INTERRUPT; + } + + var b = this.mmu.read_byte_long(this.r.pc, this.r.k); + this.r.pc++; + + // If we reach the end of the code then stop everything. + if(typeof b === "undefined") { + this.executing = false; + this.instruction_details = "opcode b is undefined"; + return; + } + this.instruction = b.toString(16).toUpperCase(); + var operation = this.opcode_map[b]; + this.instruction_translate = operation.toString(); + + // Minus One on PC reg because we already read the current instruction + this.instruction_history + += bytesToString(this.r.k) + "/" + bytesToString(this.r.pc-1) + ": " + + bytesToString(b); + + // bytes_required can either be a number, or a function that resolves + // to a number using some aspect of the cpu. + var bytes_required = operation.bytes_required; + if(typeof bytes_required === 'function') { + bytes_required = bytes_required(this); + } + this.instruction_details = bytes_required + " bytes read"; + + if(bytes_required===1) { + operation.execute(this); + } else { + var bytes = []; + for(var i = 1; i < bytes_required; i++) { + var bytes_read = this.mmu.read_byte_long(this.r.pc, this.r.k); + bytes.push(bytes_read); + this.instruction += " " + bytesToString(bytes_read); + this.instruction_history += " " + bytesToString(bytes_read); + this.r.pc++; + } + operation.execute(this,bytes); + } + this.instruction_history += "
"; + + if(this.waiting||this.stopped) + this.executing = false; + }; + + this.execute = function(start_address, max_cycles_per_second) { + // Default to 1MHz if no number given. + if(typeof max_cycles_per_second === "undefined") { + max_cycles_per_second = 1000000; + } + + this.r.pc = start_address; + this.timer_run(max_cycles_per_second, 1000); + }; + + this.timer_run = function(max_cycles_per_period, period) { + var start = new Date().getTime(); + this.executing = true; + while(this.executing) { + this.step(); + // If execution stopped other than because of the cycle count + if(!this.executing) { + return; + } + if(this.cycle_count>=max_cycles_per_period) { + this.executing = false; + var now = new Date().getTime(); + var wait = period - (now - start); + this.cycle_count = 0; + if(wait>0) { + setTimeout(this.timer_run.bind(this, max_cycles_per_period, period), wait); + } else { + this.timer_run(max_cycles_per_period, period); + } + } + } + }; + + this.reset = function() { + this.executing = false; + this.waiting = false; + this.stopped = false; + this.instruction = ""; + this.instruction_translate = ""; + this.instruction_details = ""; + this.instruction_history = ""; + + this.interrupt = this.INTERRUPT.NO_INTERRUPT; + this.r = { a:0, b:0, x:0, y:0, d:0, s:0xff, pc:0, dbr:0, k:0 }; + this.p = { e:1, c:0, z:0, i:0, d:0, x:0, m:0, v:0, n:0 }; + this.mmu.reset(); + this.cycle_count = 0; + }; +}; +})((typeof module !== 'undefined' && + typeof module.exports !== 'undefined')?module.exports:this); \ No newline at end of file diff --git a/src/test/resources/orig.cpu.js b/src/test/resources/orig.cpu.js new file mode 100644 index 00000000..0d060f7e --- /dev/null +++ b/src/test/resources/orig.cpu.js @@ -0,0 +1,4095 @@ +/* + * Copyright (c) 2011-2012, Preston Skupinski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +(function(window) { +// A collection of helper functions. +var bytesToString = function(bytes) { + + if( Object.prototype.toString.call(bytes) === '[object Array]' ) { + var sReturn = ""; + var number; + for(var i = bytes.length-1; i >= 0; i--) { + number = Number(bytes[i]); + sReturn += (number < 16 ? "0" : "") + + number.toString(16).toUpperCase(); + } + return sReturn; + } else { + number = Number(bytes); + return (number < 16 ? "0" : "") + + number.toString(16).toUpperCase(); + } +} + +var cpu_lib = { + r: { + p: { + Set_p: function(value) { + this.bytes_required = 2; + this.execute = function(cpu, bytes) { + // TODO: Figure out exactly how behavior differs in emulation mode. + cpu.cycle_count+=3; + + var flags = bytes[0].toString(2), + ops = { 0: function() { cpu.p.n = value; }, + 1: function() { cpu.p.v = value; }, + 2: function() { cpu.p.m = value; }, + 3: function() { cpu.p.x = value; }, + 4: function() { cpu.p.d = value; }, + 5: function() { cpu.p.i = value; }, + 6: function() { cpu.p.z = value; }, + 7: function() { cpu.p.c = value; }}; + + // Sometimes it cuts off zeros before hand, so add those zeros back. + while(flags.length<8) { + flags = '0' + flags; + } + + for(var i = 0; i < 8; i++) { + if(flags.charAt(i)==='1') { + ops[i](); + } + } + } + }, + Flag_set: function(flag, value) { + this.bytes_required = 1; + this.execute = function(cpu) { + cpu.cycle_count+=2; + cpu.instruction_details = "flag " + flag + " set to " + value; + //cpu.instruction_translated = true; + cpu.p[flag] = value; + }; + }, + check_z: function(cpu, val) { + if(val===0) { + cpu.p.z = 1; + } else { + cpu.p.z = 0; + } + } + } + }, + branch: { + Branch: function(flag, branch_on) { + var always_branch = typeof flag === 'undefined'; + + this.bytes_required = 2; + + this.execute = function(cpu, bytes) { + cpu.cycle_count+=3; + if(cpu.p.e) + cpu.cycle_count++; + + if(always_branch||(cpu.p[flag]==branch_on)) { + if(!always_branch) + cpu.cycle_count++; + + // Handle single byte two's complement numbers as the branch argument. + if(bytes[0]<=127) { + cpu.r.pc+=bytes[0]; + cpu.r.pc&=0xffff; + } else { + cpu.r.pc-=256-bytes[0]; + if(cpu.r.pc<0) + cpu.r.pc+=0xffff; + } + } + } + } + }, + inc: { + INC_register: function(register, flag, value) { + if(typeof value === 'undefined') + value = 1; + + this.bytes_required = 1; + + this.toString = function() { + return (value == 1 ? "IN" : "DE") + " reg" + register + " flg" + flag; + }; + + this.execute = function(cpu) { + cpu.cycle_count+=2; + + cpu.r[register]+=value; + + if(cpu.p.e||cpu.p[flag]) { + cpu.r[register] &= 0xff; + cpu.p.n = cpu.r[register] >> 7; + } else { + cpu.r[register] &= 0xffff; + cpu.p.n = cpu.r[register] >> 15; + } + + cpu_lib.r.p.check_z(cpu, cpu.r[register]); + }; + }, + INC_memory: function(value) { + if(typeof value === 'undefined') + value = 1; + + this.execute = function(cpu, bytes, extra) { + cpu.cycle_count+=4; + if(cpu.p.e||cpu.p.m) { + var temp = (bytes[0] + value) & 0xff; + cpu.p.n = temp >> 7; + cpu.mmu.store_byte(extra.memory_location, temp); + } else { + cpu.cycle_count+=2; + var temp = (((bytes[1]<<8)|bytes[0]) + value) & 0xffff; + cpu.p.n = temp >> 15; + cpu.mmu.store_word(extra.memory_location, temp); + } + cpu_lib.r.p.check_z(cpu, temp); + } + } + }, + addressing: { + Direct_page: function(instruction) { + this.bytes_required = 2; + + this.execute = function(cpu, bytes) { + cpu.cycle_count++; + cpu.instruction_translate = instruction.toString() + " $" + bytesToString(bytes); + cpu.instruction_translated = true; + cpu.instruction_history += " " + instruction.toString() + " $" + bytesToString(bytes); + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0] + cpu.r.d; + if(cpu.p.e||cpu.p.m) { + instruction.execute(cpu, [cpu.mmu.read_byte(memory_location)], + {memory_location: memory_location}); + } else { + instruction.execute(cpu, [cpu.mmu.read_byte(memory_location), + cpu.mmu.read_byte(memory_location+1)], + {memory_location: memory_location}); + } + cpu.instruction_details += "
Direct Page addressing"; + cpu.instruction_translated = false; + }; + }, + Direct_page_indexed_x_indirect: function(instruction) { + this.bytes_required = 2; + + this.execute = function(cpu, bytes) { + cpu.cycle_count+=4; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + if(cpu.p.e) { + var memory_location = (bytes[0] + cpu.r.x) & 0xff, + low_byte_loc = cpu.mmu.read_byte_long(memory_location+cpu.r.d,0), + high_byte_read_loc = ((memory_location+1)&0xff)+cpu.r.d, + high_byte_loc = cpu.mmu.read_byte_long(high_byte_read_loc, 0), + address = (high_byte_loc<<8) | low_byte_loc; + instruction.execute(cpu, [cpu.mmu.read_byte(address)], + { memory_location: address }); + } else if(cpu.p.m) { + var memory_location = bytes[0] + cpu.r.d + cpu.r.x, + low_byte_loc = cpu.mmu.read_byte(memory_location), + high_byte_loc = cpu.mmu.read_byte(memory_location+1), + address = (high_byte_loc<<8)|low_byte_loc; + instruction.execute(cpu, [cpu.mmu.read_byte(address)], + { memory_location: address }); + } else { + var memory_location = bytes[0] + cpu.r.d + cpu.r.x, + absolute_location = cpu.mmu.read_word(memory_location), + low_byte = cpu.mmu.read_byte(absolute_location), + high_byte; + absolute_location++; + if(absolute_location&0x10000) { + high_byte = cpu.mmu.read_byte_long(absolute_location, cpu.r.dbr+1); + } else { + high_byte = cpu.mmu.read_byte(absolute_location); + } + instruction.execute(cpu, [low_byte, high_byte], + { memory_location: absolute_location-1 }); + } + }; + }, + Direct_page_indirect: function(instruction) { + this.bytes_required = 2; + + this.execute = function(cpu, bytes) { + cpu.cycle_count+=3; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0] + cpu.r.d, + absolute_location = cpu.mmu.read_word(memory_location); + if(cpu.p.e||cpu.p.m) { + instruction.execute(cpu, [cpu.mmu.read_byte(absolute_location)], + { memory_location: absolute_location }); + } else { + instruction.execute(cpu, [cpu.mmu.read_byte(absolute_location), + cpu.mmu.read_byte(absolute_location+1)], + { memory_location: absolute_location }); + } + }; + }, + Direct_page_indirect_indexed_y: function(instruction) { + this.bytes_required = 2; + + this.execute = function(cpu, bytes) { + cpu.cycle_count+=3; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0] + cpu.r.d, + original_location = cpu.mmu.read_word(memory_location), + absolute_location = original_location + cpu.r.y; + + if((original_location&0xff00)!==(absolute_location&0xff00)) + cpu.cycle_count++; + + if(cpu.p.e||cpu.p.m) { + instruction.execute(cpu, [cpu.mmu.read_byte(absolute_location)], + { memory_location: absolute_location }); + } else { + instruction.execute(cpu, [cpu.mmu.read_byte(absolute_location), + cpu.mmu.read_byte(absolute_location+1)], + { memory_location: absolute_location }); + } + }; + }, + Direct_page_indirect_long: function(instruction) { + this.bytes_required = 2; + + this.execute = function(cpu, bytes) { + cpu.cycle_count+=4; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0] + cpu.r.d, + bank_byte = cpu.mmu.read_byte(memory_location+2), + absolute_location = cpu.mmu.read_word(memory_location); + if(cpu.p.e||cpu.p.m) { + instruction.execute(cpu, [cpu.mmu.read_byte_long(absolute_location, + bank_byte)], + { memory_location: absolute_location }); + } else { + instruction.execute(cpu, [cpu.mmu.read_byte_long(absolute_location, + bank_byte), + cpu.mmu.read_byte_long(absolute_location+1, + bank_byte)], + { memory_location: absolute_location }); + } + }; + }, + Direct_page_indirect_long_indexed_y: function(instruction) { + this.bytes_required = 2; + + this.execute = function(cpu, bytes) { + cpu.cycle_count+=4; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0] + cpu.r.d, + bank_byte = cpu.mmu.read_byte(memory_location+2), + absolute_location = cpu.mmu.read_word(memory_location) + cpu.r.y; + if(absolute_location >> 16) { + absolute_location &= 0xffff; + bank_byte++; + } + + if(cpu.p.e||cpu.p.m) { + instruction.execute(cpu, [cpu.mmu.read_byte_long(absolute_location, + bank_byte)], + { memory_location: absolute_location }); + } else { + var low_byte = cpu.mmu.read_byte_long(absolute_location, bank_byte); + absolute_location++; + if(absolute_location >> 16) { + absolute_location &= 0xffff; + bank_byte++; + } + var high_byte = cpu.mmu.read_byte_long(absolute_location, bank_byte); + instruction.execute(cpu, [low_byte, high_byte], + { memory_location: absolute_location-1 }); + } + }; + }, + Absolute: function(instruction) { + this.bytes_required = 3; + + this.execute = function(cpu, bytes) { + cpu.cycle_count+=2; + + var memory_location = (bytes[1]<<8)|bytes[0]; + if(cpu.p.e||cpu.p.m) { + instruction.execute(cpu, [cpu.mmu.read_byte(memory_location)], + { memory_location: memory_location }); + } else { + instruction.execute(cpu, [cpu.mmu.read_byte(memory_location), + cpu.mmu.read_byte(memory_location+1)], + { memory_location: memory_location }); + } + }; + }, + Absolute_long: function(instruction) { + this.bytes_required = 4; + + this.execute = function(cpu, bytes) { + cpu.cycle_count+=3; + + var memory_location = (bytes[1]<<8)|bytes[0]; + if(cpu.p.e||cpu.p.m) { + instruction.execute(cpu, [cpu.mmu.read_byte_long(memory_location, + bytes[2])], + { memory_location: memory_location }); + } else { + instruction.execute(cpu, [cpu.mmu.read_byte_long(memory_location, + bytes[2]), + cpu.mmu.read_byte_long(memory_location+1, + bytes[2])], + { memory_location: memory_location }); + } + }; + }, + Absolute_long_indexed_x: function(instruction) { + this.bytes_required = 4; + + this.execute = function(cpu, bytes) { + cpu.cycle_count+=3; + + var memory_location = ((bytes[1]<<8)|bytes[0]) + cpu.r.x; + if(memory_location & 0x10000) { + bytes[2]++; + memory_location &= 0xffff; + } + + if(cpu.p.e||cpu.p.m) { + instruction.execute(cpu, [cpu.mmu.read_byte_long(memory_location, + bytes[2])], + { memory_location: memory_location }); + } else { + var low_byte = cpu.mmu.read_byte_long(memory_location, bytes[2]); + memory_location++; + if(memory_location & 0x10000) { + bytes[2]++; + memory_location &= 0xffff; + } + var high_byte = cpu.mmu.read_byte_long(memory_location, bytes[2]); + instruction.execute(cpu, [low_byte, high_byte], + { memory_location: memory_location - 1 }); + } + }; + }, + Absolute_indexed_x: function(absolute_instruction, other) { + this.bytes_required = 3; + + this.execute = function(cpu, bytes) { + var memory_location; + + if(other) { + cpu.cycle_count++; + memory_location = ((bytes[1]<<8)|bytes[0])+cpu.r.x; + } else { + var initial_location = (bytes[1]<<8)|bytes[0]; + memory_location = initial_location+cpu.r.x; + if((memory_location&0xff00)!==(initial_location&0xff00)) + cpu.cycle_count++; + } + absolute_instruction.execute(cpu, [memory_location&0xff, + memory_location>>8]); + }; + }, + Absolute_indexed_y: function(absolute_instruction) { + this.bytes_required = 3; + + this.execute = function(cpu, bytes) { + var initial_location = (bytes[1]<<8)|bytes[0], + memory_location = initial_location+cpu.r.y; + + if((memory_location&0xff00)!==(initial_location&0xff00)) + cpu.cycle_count++; + + absolute_instruction.execute(cpu, [memory_location&0xff, + memory_location>>8]); + }; + }, + Direct_page_indexed: function(direct_page_instruction, register) { + this.bytes_required = 2; + + this.execute = function(cpu, bytes) { + cpu.cycle_count++; + + direct_page_instruction.execute(cpu, [bytes[0]+cpu.r[register]]); + }; + }, + Stack_relative: function(instruction) { + this.bytes_required = 2; + + this.execute = function(cpu, bytes) { + cpu.cycle_count+=2; + + var memory_location = cpu.r.s + bytes[0]; + if(cpu.p.e) { + memory_location = 0x100 | (memory_location & 0xff); + } + + if(cpu.p.e || cpu.p.m) { + instruction.execute(cpu, [cpu.mmu.read_byte(memory_location)]); + } else { + instruction.execute(cpu, [cpu.mmu.read_byte(memory_location), + cpu.mmu.read_byte(memory_location+1)]); + } + }; + }, + Stack_relative_indirect_indexed_y: function(instruction) { + this.bytes_required = 2; + + this.execute = function(cpu, bytes) { + cpu.cycle_count+=5; + + if(cpu.p.e) { + var location_loc = 0x100 | ((cpu.r.s + bytes[0]) & 0xff), + low_byte = cpu.mmu.read_byte(location_loc), + high_byte; + if(location_loc===0x1ff) { + high_byte = cpu.mmu.read_byte(0x100); + } else { + high_byte = cpu.mmu.read_byte(location_loc+1); + } + var absolute_location = ((high_byte<<8)|low_byte)+cpu.r.y, + b; + if(absolute_location>=0x10000) { + b = cpu.mmu.read_byte_long(absolute_location, cpu.r.dbr+1); + } else { + b = cpu.mmu.read_byte(absolute_location); + } + instruction.execute(cpu, [b]); + } else { + var location_loc = (cpu.r.s + bytes[0]) & 0xffff, + absolute_location = cpu.mmu.read_word(location_loc) + cpu.r.y; + if(cpu.p.m) { + var b; + if(absolute_location>=0x10000) { + b = cpu.mmu.read_byte_long(absolute_location, cpu.r.dbr+1); + } else { + b = cpu.mmu.read_byte(absolute_location); + } + instruction.execute(cpu, [b]); + } else { + var low_byte, high_byte; + if(absolute_location>=0x10000) { + absolute_location &= 0xffff; + low_byte = cpu.mmu.read_byte_long(absolute_location, cpu.r.dbr+1); + high_byte = cpu.mmu.read_byte_long(absolute_location+1, + cpu.r.dbr+1); + } else { + low_byte = cpu.mmu.read_byte(absolute_location); + if(absolute_location===0xffff) { + high_byte = cpu.mmu.read_byte_long(0, cpu.r.dbr+1); + } else { + high_byte = cpu.mmu.read_byte(absolute_location); + } + } + instruction.execute(cpu, [low_byte, high_byte]); + } + } + } + } + } +}; + +var STP = { + toString: function() { return "STP" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=3; + + cpu.stopped = true; + } +}; + +var WAI = { + toString: function() { return "WAI" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=3; + + cpu.waiting = true; + } +}; + +var WDM = { + toString: function() { return "WDM" }, + bytes_required:2, + + execute:function() {} +}; + +var TXS = { + toString: function() { return "TXS" }, + bytes_required:1, + execute:function(cpu) { + cpu.cycle_count+=2; + cpu.r.s = cpu.r.x; + if(cpu.p.e||cpu.p.x) { + cpu.p.n = cpu.r.s >> 7; + } else { + cpu.p.n = cpu.r.s >> 15; + } + cpu_lib.r.p.check_z(cpu, cpu.r.s); + } +}; + +var TSX = { + toString: function() { return "TSX" }, + bytes_required:1, + execute:function(cpu) { + cpu.cycle_count+=2; + if(cpu.p.e||cpu.p.x) { + cpu.r.x = cpu.r.s & 0xff; + cpu.p.n = cpu.r.x >> 7; + } else { + cpu.r.x = cpu.r.s; + cpu.p.n = cpu.r.x >> 15; + } + cpu_lib.r.p.check_z(cpu, cpu.r.x); + } +}; + +var TRB_absolute = { + toString: function() { return "TRB" }, + bytes_required:3, + + execute:function(cpu, bytes) { + cpu.cycle_count+=6; + + var memory_location = (bytes[1]<<8)|bytes[0], + data; + if(cpu.p.e||cpu.p.m) { + data = cpu.mmu.read_byte(memory_location); + cpu_lib.r.p.check_z(cpu, data & cpu.r.a); + cpu.mmu.store_byte(memory_location, (~cpu.r.a & data)); + } else { + cpu.cycle_count+=2; + + data = cpu.mmu.read_word(memory_location); + cpu_lib.r.p.check_z(cpu, data & cpu.r.a); + data &= ~cpu.r.a; + cpu.mmu.store_word(memory_location, data); + } + } +}; + +var TRB_direct_page = { + toString: function() { return "TRB" }, + bytes_required:2, + + execute:function(cpu, bytes) { + cpu.cycle_count+=5; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0] + cpu.r.d, + data; + if(cpu.p.e||cpu.p.m) { + data = cpu.mmu.read_byte(memory_location); + cpu_lib.r.p.check_z(cpu, data & cpu.r.a); + cpu.mmu.store_byte(memory_location, (~cpu.r.a & data)); + } else { + cpu.cycle_count+=2; + + data = cpu.mmu.read_word(memory_location); + cpu_lib.r.p.check_z(cpu, data & cpu.r.a); + data &= ~cpu.r.a; + cpu.mmu.store_word(memory_location, data); + } + } +}; + +var TSB_absolute = { + toString: function() { return "TSB" }, + bytes_required:3, + + execute:function(cpu, bytes) { + cpu.cycle_count+=6; + + var memory_location = (bytes[1]<<8)|bytes[0], + data; + if(cpu.p.e||cpu.p.m) { + data = cpu.mmu.read_byte(memory_location); + cpu_lib.r.p.check_z(cpu, data & cpu.r.a); + cpu.mmu.store_byte(memory_location, (cpu.r.a | data)); + } else { + cpu.cycle_count+=2; + + data = cpu.mmu.read_word(memory_location); + cpu_lib.r.p.check_z(cpu, data & cpu.r.a); + data |= cpu.r.a; + cpu.mmu.store_word(memory_location, data); + } + } +}; + +var TSB_direct_page = { + toString: function() { return "TSB" }, + bytes_required:2, + + execute:function(cpu, bytes) { + cpu.cycle_count+=5; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0] + cpu.r.d, + data; + if(cpu.p.e||cpu.p.m) { + data = cpu.mmu.read_byte(memory_location); + cpu_lib.r.p.check_z(cpu, data & cpu.r.a); + cpu.mmu.store_byte(memory_location, (cpu.r.a | data)); + } else { + cpu.cycle_count+=2; + + data = cpu.mmu.read_word(memory_location); + cpu_lib.r.p.check_z(cpu, data & cpu.r.a); + data |= cpu.r.a; + cpu.mmu.store_word(memory_location, data); + } + } +}; + +var BIT_const = { + toString: function() { return "BIT" }, + bytes_required:function(cpu) { + if(cpu.p.e||cpu.p.m) + return 2; + else + return 3; + }, + execute:function(cpu, bytes) { + cpu.instruction_details += "
BIT test between value and accumulator"; + if(!cpu.instruction_translated) { + cpu.instruction_details += " (const)"; + cpu.instruction_translate = this.toString() + " #" + bytesToString(bytes); + cpu.instruction_history += " " + this.toString() + " #" + bytesToString(bytes); + } + cpu.cycle_count+=2; + + var and_result; + if(cpu.p.e||cpu.p.m) { + cpu.p.n = bytes[0] >> 7; + cpu.p.v = (bytes[0] >> 6) & 0x1; + and_result = cpu.r.a & bytes[0]; + } else { + cpu.cycle_count++; + cpu.p.n = bytes[1] >> 7; + cpu.p.v = (bytes[1] >> 6) & 0x1; + and_result = cpu.r.a & ((bytes[1]<<8)|bytes[0]); + } + + cpu_lib.r.p.check_z(cpu, and_result); + } +}; + +var BIT_absolute = new cpu_lib.addressing.Absolute(BIT_const); + +var BIT_direct_page = new cpu_lib.addressing.Direct_page(BIT_const); + +var BIT_direct_page_indexed_x = + new cpu_lib.addressing.Direct_page_indexed(BIT_direct_page, 'x'); + +var BIT_absolute_indexed_x = + new cpu_lib.addressing.Absolute_indexed_x(BIT_absolute); + +var COP = { + toString: function() { return "COP" }, + bytes_required:2, + + execute:function(cpu) { + cpu.cycle_count+=7; + + if(cpu.p.e===0) + cpu.cycle_count++; + + cpu.interrupt = cpu.INTERRUPT.COP; + } +}; + +var BRK = { + toString: function() { return "BRK" }, + bytes_required:2, + + execute:function(cpu) { + cpu.cycle_count+=7; + + if(cpu.p.e===0) + cpu.cycle_count++; + + cpu.interrupt = cpu.INTERRUPT.BRK; + if(cpu.p.e) + cpu.p.m = 1; + } +}; + +var RTI = { + toString: function() { return "RTI" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=6; + + if(cpu.p.e===0) + cpu.cycle_count++; + + var p_byte = cpu.mmu.pull_byte(), + pc_low = cpu.mmu.pull_byte(), + pc_high = cpu.mmu.pull_byte(); + cpu.r.pc = (pc_high<<8)|pc_low; + + cpu.p.c = p_byte & 0x01; + cpu.p.z = (p_byte & 0x02) >> 1; + cpu.p.i = (p_byte & 0x04) >> 2; + cpu.p.d = (p_byte & 0x08) >> 3; + cpu.p.x = (p_byte & 0x10) >> 4; + cpu.p.m = (p_byte & 0x20) >> 5; + cpu.p.v = (p_byte & 0x40) >> 6; + cpu.p.n = p_byte >> 7; + + if(!cpu.p.e) { + cpu.r.k = cpu.mmu.pull_byte(); + } + } +}; + +// MVN is a really weird instruction, until the accumulator underflows MVN +// will keep decrementing the program counter to have it continue to execute. +var MVN = { + toString: function() { return "MVN" }, + bytes_required:3, + + execute:function(cpu, bytes) { + cpu.cycle_count+=7; + + // TODO: One piece of reference material I've read claims that this + // operation should always work with a 16-bit accumulator even if in + // emulation mode or the m bit is set to 1, in those cases it claims that + // you should concatenate the B "hidden" register with A. I'm going to + // need to test this claim out somehow. + var b = cpu.mmu.read_byte_long(cpu.r.x,bytes[1]); + cpu.r.dbr = bytes[0]; + cpu.mmu.store_byte(cpu.r.y, b); + cpu.r.x++; + cpu.r.y++; + if(cpu.p.e||cpu.p.x) { + cpu.r.x &= 0x00ff; + cpu.r.y &= 0x00ff; + } else { + cpu.r.x &= 0xffff; + cpu.r.y &= 0xffff; + } + + if(cpu.r.a!==0) { + cpu.r.a--; + cpu.r.pc-=3; + } else { + if(cpu.p.e||cpu.p.m) + cpu.r.a = 0xff; + else + cpu.r.a = 0xffff; + } + } +}; + +// MVP is a really weird instruction, until the accumulator reaches $FFFF MVP +// will keep decrementing the program counter to have it continue to execute. +var MVP = { + toString: function() { return "MVP" }, + bytes_required:3, + + execute:function(cpu, bytes) { + cpu.cycle_count+=7; + + // TODO: One piece of reference material I've read claims that this + // operation should always work with a 16-bit accumulator even if in + // emulation mode or the m bit is set to 1, in those cases it claims that + // you should concatenate the B "hidden" register with A. I'm going to + // need to test this claim out somehow. + var b = cpu.mmu.read_byte_long(cpu.r.x,bytes[1]); + cpu.r.dbr = bytes[0]; + cpu.mmu.store_byte(cpu.r.y,b); + + var index_register_wrap; + if(cpu.p.e||cpu.p.x) { + index_register_wrap = 0xff; + } else { + index_register_wrap = 0xffff; + } + + if(cpu.r.y===index_register_wrap) { + cpu.r.y = 0; + } else { + cpu.r.y--; + } + + if(cpu.r.x===index_register_wrap) { + cpu.r.x = 0; + } else { + cpu.r.x--; + } + + if(cpu.r.a!==0) { + cpu.r.pc-=3; + cpu.r.a--; + } else { + if(cpu.p.e||cpu.p.m) + cpu.r.a = 0xff; + else + cpu.r.a = 0xffff; + } + } +}; + +var JSL = { + toString: function() { return "JSL" }, + bytes_required:4, + + execute:function(cpu, bytes) { + cpu.cycle_count+=8; + + var memory_location = cpu.r.pc - 1; + cpu.mmu.push_byte(cpu.r.k); + cpu.mmu.push_byte(memory_location>>8); + cpu.mmu.push_byte(memory_location&0x00ff); + cpu.r.k = bytes[2]; + cpu.r.pc = (bytes[1]<<8)|bytes[0]; + } +}; + +var RTL = { + toString: function() { return "RTL" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=6; + + var low_byte = cpu.mmu.pull_byte(), + high_byte = cpu.mmu.pull_byte(); + cpu.r.k = cpu.mmu.pull_byte(); + cpu.r.pc = ((high_byte<<8)|low_byte) + 1; + } +}; + +var JSR = { + toString: function() { return "JSR" }, + bytes_required:3, + + execute:function(cpu, bytes) { + cpu.cycle_count+=6; + + var memory_location = cpu.r.pc - 1; + cpu.mmu.push_byte(memory_location>>8); + cpu.mmu.push_byte(memory_location&0xff); + cpu.r.pc = (bytes[1]<<8)|bytes[0]; + } +}; + +var JSR_absolute_indexed_x_indirect = { + toString: function() { return "JSR" }, + bytes_required:3, + + execute:function(cpu, bytes) { + cpu.cycle_count+=8; + + var memory_location = ((bytes[1]<<8)|bytes[0])+cpu.r.x, + bank = cpu.r.k; + if(memory_location&0x10000) { + bank++; + } + memory_location &= 0xffff; + var indirect_location = cpu.mmu.read_word_long(memory_location, bank), + low_byte = cpu.mmu.read_byte(indirect_location); + bank = cpu.r.k; + if(indirect_location===0xffff) { + indirect_location = 0; + bank++; + } else { + indirect_location++; + } + var high_byte = cpu.mmu.read_byte_long(indirect_location, bank); + JSR.execute(cpu, [low_byte, high_byte]); + } +}; + +var RTS = { + toString: function() { return "RTS" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=6; + + var low_byte = cpu.mmu.pull_byte(), + high_byte = cpu.mmu.pull_byte(); + cpu.r.pc = ((high_byte<<8)|low_byte) + 1; + } +}; + +var PER = { + toString: function() { return "PER" }, + bytes_required:3, + + execute:function(cpu,bytes) { + cpu.cycle_count+=6; + + var address = ((bytes[1]<<8)|bytes[0]) + cpu.r.pc; + cpu.mmu.push_byte(address>>8); + cpu.mmu.push_byte(address&0xff); + } +}; + +var PHK = { + toString: function() { return "PHK" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=3; + + cpu.mmu.push_byte(cpu.r.k); + } +}; + +var PHD = { + toString: function() { return "PHD" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=4; + + cpu.mmu.push_byte(cpu.r.d>>8); + cpu.mmu.push_byte(cpu.r.d&0xff); + } +}; + +var PLD = { + toString: function() { return "PLD" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=5; + + var low_byte = cpu.mmu.pull_byte(), + high_byte = cpu.mmu.pull_byte(); + cpu.r.d = (high_byte<<8)|low_byte; + + cpu.p.n = cpu.r.d >> 15; + + cpu_lib.r.p.check_z(cpu, cpu.r.d); + } +}; + +var PHB = { + toString: function() { return "PHB" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=3; + cpu.mmu.push_byte(cpu.r.dbr); + } +}; + +var PLB = { + toString: function() { return "PLB" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=4; + + cpu.r.dbr = cpu.mmu.pull_byte(); + cpu.p.n = cpu.r.dbr >> 7; + cpu_lib.r.p.check_z(cpu, cpu.r.dbr); + } +}; + +var PEA = { + toString: function() { return "PEA" }, + bytes_required:3, + + execute:function(cpu, bytes) { + cpu.cycle_count+=5; + + cpu.mmu.push_byte(bytes[1]); + cpu.mmu.push_byte(bytes[0]); + } +}; + +var PEI = { + toString: function() { return "PEI" }, + bytes_required:2, + + execute:function(cpu, bytes) { + cpu.cycle_count+=6; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0]+cpu.r.d; + cpu.mmu.push_byte(cpu.mmu.read_byte(memory_location+1)); + cpu.mmu.push_byte(cpu.mmu.read_byte(memory_location)); + } +}; + +var PHP = { + toString: function() { return "PHP" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=3; + + var p_byte = (cpu.p.n<<7)|(cpu.p.v<<6)|(cpu.p.m<<5)|(cpu.p.x<<4)| + (cpu.p.d<<3)|(cpu.p.i<<2)|(cpu.p.z<<1)|cpu.p.c; + cpu.mmu.push_byte(p_byte); + } +}; + +var PLP = { + toString: function() { return "PLP" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=4; + + var p_byte = cpu.mmu.pull_byte(); + cpu.p.c = p_byte & 0x01; + cpu.p.z = (p_byte & 0x02) >> 1; + cpu.p.i = (p_byte & 0x04) >> 2; + cpu.p.d = (p_byte & 0x08) >> 3; + cpu.p.x = (p_byte & 0x10) >> 4; + cpu.p.m = (p_byte & 0x20) >> 5; + cpu.p.v = (p_byte & 0x40) >> 6; + cpu.p.n = p_byte >> 7; + } +}; + +var PHX = { + toString: function() { return "PHX" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=3; + + if(cpu.p.e||cpu.p.x) { + cpu.mmu.push_byte(cpu.r.x); + } else { + cpu.cycle_count++; + + cpu.mmu.push_byte(cpu.r.x>>8); + cpu.mmu.push_byte(cpu.r.x&0xff); + } + } +}; + +var PLX = { + toString: function() { return "PLX" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=4; + + if(cpu.p.e||cpu.p.x) { + cpu.r.x = cpu.mmu.pull_byte(); + cpu.p.n = cpu.r.x >> 7; + } else { + cpu.cycle_count++; + var low_byte = cpu.mmu.pull_byte(), + high_byte = cpu.mmu.pull_byte(); + cpu.r.x = (high_byte<<8)|low_byte; + cpu.p.n = cpu.r.x >> 15; + } + + cpu_lib.r.p.check_z(cpu, cpu.r.x); + } +}; + +var PHY = { + toString: function() { return "PHY" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=3; + + if(cpu.p.e||cpu.p.x) { + cpu.mmu.push_byte(cpu.r.y); + } else { + cpu.cycle_count++; + cpu.mmu.push_byte(cpu.r.y>>8); + cpu.mmu.push_byte(cpu.r.y&0xff); + } + } +}; + +var PLY = { + toString: function() { return "PLY" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=4; + + if(cpu.p.e||cpu.p.x) { + cpu.r.y = cpu.mmu.pull_byte(); + cpu.p.n = cpu.r.y >> 7; + } else { + cpu.cycle_count++; + var low_byte = cpu.mmu.pull_byte(), + high_byte = cpu.mmu.pull_byte(); + cpu.r.y = (high_byte<<8)|low_byte; + cpu.p.n = cpu.r.y >> 15; + } + + cpu_lib.r.p.check_z(cpu, cpu.r.y); + } +}; + +var PHA = { + toString: function() { return "PHA" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=3; + + if(cpu.p.e||cpu.p.m) { + cpu.mmu.push_byte(cpu.r.a); + } else { + cpu.cycle_count++; + + cpu.mmu.push_byte(cpu.r.a>>8); + cpu.mmu.push_byte(cpu.r.a&0xff); + } + } +}; + +var PLA = { + toString: function() { return "PLA" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=4; + + if(cpu.p.e||cpu.p.m) { + cpu.r.a = cpu.mmu.pull_byte(); + cpu.p.n = cpu.r.a >> 7; + } else { + cpu.cycle_count++; + + var low_byte = cpu.mmu.pull_byte(), + high_byte = cpu.mmu.pull_byte(); + cpu.r.a = (high_byte<<8)|low_byte; + cpu.p.n = cpu.r.a >> 15; + } + + cpu_lib.r.p.check_z(cpu, cpu.r.a); + } +}; + +var ROR_accumulator = { + toString: function() { return "ROR" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=2; + + var old_c = cpu.p.c; + if(cpu.p.e||cpu.p.m) { + cpu.p.c = cpu.r.a & 0x01; + cpu.r.a = cpu.r.a >> 1; + cpu.r.a &= 0x7f; + cpu.r.a |= (old_c<<7); + cpu.p.n = cpu.r.a >> 7; + } else { + cpu.p.c = cpu.r.a & 0x0001; + cpu.r.a = cpu.r.a >> 1; + cpu.r.a &= 0x7fff; + cpu.r.a |= (old_c<<15); + cpu.p.n = cpu.r.a >> 15; + } + + cpu_lib.r.p.check_z(cpu, cpu.r.a); + } +}; + +var ROR_absolute = { + toString: function() { return "ROR" }, + bytes_required:3, + + execute:function(cpu,bytes) { + cpu.cycle_count+=6; + + var memory_location = (bytes[1]<<8)|bytes[0], + old_c = cpu.p.c, + shiftee; + if(cpu.p.e||cpu.p.m) { + shiftee = cpu.mmu.read_byte(memory_location); + cpu.p.c = shiftee & 0x01; + shiftee = shiftee >> 1; + shiftee &= 0x7f; + shiftee |= (old_c<<7); + cpu.p.n = shiftee >> 7; + cpu.mmu.store_byte(memory_location, shiftee); + } else { + cpu.cycle_count+=2; + + shiftee = cpu.mmu.read_word(memory_location); + cpu.p.c = shiftee & 0x0001; + shiftee = shiftee >> 1; + shiftee &= 0x7fff; + shiftee |= (old_c<<15); + cpu.p.n = shiftee >> 15; + cpu.mmu.store_word(memory_location, shiftee); + } + + cpu_lib.r.p.check_z(cpu, shiftee); + } +}; + +var ROR_direct_page = { + toString: function() { return "ROR" }, + bytes_required:2, + + execute:function(cpu,bytes) { + cpu.cycle_count+=5; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0]+cpu.r.d, + old_c = cpu.p.c, + shiftee; + if(cpu.p.e||cpu.p.m) { + shiftee = cpu.mmu.read_byte(memory_location); + cpu.p.c = shiftee & 0x01; + shiftee = shiftee >> 1; + shiftee &= 0x7f; + shiftee |= (old_c<<7); + cpu.p.n = shiftee >> 7; + cpu.mmu.store_byte(memory_location, shiftee); + } else { + cpu.cycle_count+=2; + + shiftee = cpu.mmu.read_word(memory_location); + cpu.p.c = shiftee & 0x0001; + shiftee = shiftee >> 1; + shiftee &= 0x7fff; + shiftee |= (old_c<<15); + cpu.p.n = shiftee >> 15; + cpu.mmu.store_word(memory_location, shiftee); + } + + cpu_lib.r.p.check_z(cpu, shiftee); + } +}; + +var ROR_absolute_indexed_x = + new cpu_lib.addressing.Absolute_indexed_x(ROR_absolute, true); + +var ROR_direct_page_indexed_x = + new cpu_lib.addressing.Direct_page_indexed(ROR_direct_page, 'x'); + +var ROL_accumulator = { + toString: function() { return "ROL" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=2; + + var old_c = cpu.p.c; + if(cpu.p.e||cpu.p.m) { + cpu.p.c = cpu.r.a >> 7; + cpu.r.a = cpu.r.a << 1; + cpu.r.a &= 0xfe; + cpu.r.a |= old_c; + cpu.p.n = cpu.r.a >> 7; + } else { + cpu.p.c = cpu.r.a >> 15; + cpu.r.a = cpu.r.a << 1; + cpu.r.a &= 0xfffe; + cpu.r.a |= old_c; + cpu.p.n = cpu.r.a >> 15; + } + + cpu_lib.r.p.check_z(cpu, cpu.r.a); + } +}; + +var ROL_absolute = { + toString: function() { return "ROL" }, + bytes_required:3, + + execute:function(cpu, bytes) { + cpu.cycle_count+=6; + + var memory_location = (bytes[1]<<8)|bytes[0], + old_c = cpu.p.c, + shiftee; + if(cpu.p.e||cpu.p.m) { + shiftee = cpu.mmu.read_byte(memory_location); + cpu.p.c = shiftee >> 7; + shiftee = shiftee << 1; + shiftee &= 0xfe; + shiftee |= old_c; + cpu.p.n = shiftee >> 7; + cpu.mmu.store_byte(memory_location, shiftee); + } else { + cpu.cycle_count+=2; + + shiftee = cpu.mmu.read_word(memory_location); + cpu.p.c = shiftee >> 15; + shiftee = shiftee << 1; + shiftee &= 0xfffe; + shiftee |= old_c; + cpu.p.n = shiftee >> 15; + cpu.mmu.store_word(memory_location, shiftee); + } + + cpu_lib.r.p.check_z(cpu, shiftee); + } +}; + +var ROL_direct_page = { + toString: function() { return "ROL" }, + bytes_required:2, + + execute:function(cpu, bytes) { + cpu.cycle_count+=5; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0]+cpu.r.d, + old_c = cpu.p.c, + shiftee; + if(cpu.p.e||cpu.p.m) { + shiftee = cpu.mmu.read_byte(memory_location); + cpu.p.c = shiftee >> 7; + shiftee = shiftee << 1; + shiftee &= 0xfe; + shiftee |= old_c; + cpu.p.n = shiftee >> 7; + cpu.mmu.store_byte(memory_location, shiftee); + } else { + cpu.cycle_count+=2; + + shiftee = cpu.mmu.read_word(memory_location); + cpu.p.c = shiftee >> 15; + shiftee = shiftee << 1; + shiftee &= 0xfffe; + shiftee |= old_c; + cpu.p.n = shiftee >> 15; + cpu.mmu.store_word(memory_location, shiftee); + } + + cpu_lib.r.p.check_z(cpu, shiftee); + } +}; + +var ROL_absolute_indexed_x = + new cpu_lib.addressing.Absolute_indexed_x(ROL_absolute, true); + +var ROL_direct_page_indexed_x = + new cpu_lib.addressing.Direct_page_indexed(ROL_direct_page, 'x'); + +var ASL_accumulator = { + toString: function() { return "ASL" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=2; + + if(cpu.p.e||cpu.p.m) { + cpu.p.c = cpu.r.a >> 7; + cpu.r.a = cpu.r.a << 1; + cpu.r.a &= 0xff; + cpu.p.n = cpu.r.a >> 7; + } else { + cpu.p.c = cpu.r.a >> 15; + cpu.r.a = cpu.r.a << 1; + cpu.r.a &= 0xffff; + cpu.p.n = cpu.r.a >> 15; + } + + cpu_lib.r.p.check_z(cpu, cpu.r.a); + } +}; + +var ASL_absolute = { + toString: function() { return "ASL" }, + bytes_required:3, + + execute:function(cpu, bytes) { + cpu.instruction_details += "
Shift Accumulator with value"; + if(!cpu.instruction_translated) { + cpu.instruction_details += " (const)"; + cpu.instruction_translate = this.toString() + " #" + bytesToString(bytes); + cpu.instruction_history += " " + this.toString() + " #" + bytesToString(bytes); + } + cpu.cycle_count+=6; + + var memory_location = (bytes[1]<<8)|bytes[0], + shiftee; + if(cpu.p.e||cpu.p.m) { + shiftee = cpu.mmu.read_byte(memory_location); + cpu.p.c = shiftee >> 7; + shiftee = shiftee << 1; + shiftee &= 0xff; + cpu.p.n = shiftee >> 7; + cpu.mmu.store_byte(memory_location, shiftee); + } else { + cpu.cycle_count+=2; + shiftee = cpu.mmu.read_word(memory_location); + cpu.p.c = shiftee >> 15; + shiftee = shiftee << 1; + shiftee &= 0xffff; + cpu.p.n = shiftee >> 15; + cpu.mmu.store_word(memory_location, shiftee); + } + + cpu_lib.r.p.check_z(cpu, shiftee); + } +}; + +var ASL_direct_page = { + toString: function() { return "ASL" }, + bytes_required:2, + + execute:function(cpu,bytes) { + cpu.instruction_details += "
Shift Accumulator with value"; + if(!cpu.instruction_translated) { + cpu.instruction_details += "
Direct Page addressing"; + cpu.instruction_translate = this.toString() + " $" + bytesToString(bytes); + cpu.instruction_history += " " + this.toString() + " $" + bytesToString(bytes); + } + cpu.cycle_count+=5; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0]+cpu.r.d, + shiftee; + if(cpu.p.e||cpu.p.m) { + shiftee = cpu.mmu.read_byte(memory_location); + cpu.p.c = shiftee >> 7; + shiftee = shiftee << 1; + shiftee &= 0xff; + cpu.p.n = shiftee >> 7; + cpu.mmu.store_byte(memory_location, shiftee); + } else { + cpu.cycle_count+=2; + + shiftee = cpu.mmu.read_word(memory_location); + cpu.p.c = shiftee >> 15; + shiftee = shiftee << 1; + shiftee &= 0xffff; + cpu.p.n = shiftee >> 15; + cpu.mmu.store_word(memory_location, shiftee); + } + + cpu_lib.r.p.check_z(cpu, shiftee); + } +}; + +var ASL_absolute_indexed_x = + new cpu_lib.addressing.Absolute_indexed_x(ASL_absolute); + +var ASL_direct_page_indexed_x = + new cpu_lib.addressing.Direct_page_indexed(ASL_direct_page, 'x'); + +var LSR_accumulator= { + toString: function() { return "LSR" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=2; + + cpu.p.c = cpu.r.a & 1; + cpu.r.a = cpu.r.a >> 1; + + cpu.p.n = 0; + cpu_lib.r.p.check_z(cpu, cpu.r.a); + } +}; + +var LSR_absolute = { + toString: function() { return "LSR" }, + bytes_required:3, + + execute:function(cpu, bytes) { + cpu.cycle_count+=6; + + var memory_location = (bytes[1]<<8)|bytes[0], + shiftee; + if(cpu.p.e||cpu.p.m) { + shiftee = cpu.mmu.read_byte(memory_location); + cpu.p.c = shiftee & 0x0001; + shiftee = shiftee >> 1; + cpu.mmu.store_byte(memory_location, shiftee); + } else { + cpu.cycle_count+=2; + + shiftee = cpu.mmu.read_word(memory_location); + cpu.p.c = cpu.r.a & 0x01; + shiftee = shiftee >> 1; + cpu.mmu.store_word(memory_location, shiftee); + } + + cpu.p.n = 0; + cpu_lib.r.p.check_z(cpu, shiftee); + } +}; + +var LSR_direct_page = { + toString: function() { return "LSR" }, + bytes_required:2, + + execute:function(cpu, bytes) { + cpu.cycle_count+=5; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0] + cpu.r.d, + shiftee; + if(cpu.p.e||cpu.p.m) { + shiftee = cpu.mmu.read_byte(memory_location); + cpu.p.c = shiftee & 0x0001; + shiftee = shiftee >> 1; + cpu.mmu.store_byte(memory_location, shiftee); + } else { + cpu.cycle_count+=2; + + shiftee = cpu.mmu.read_word(memory_location); + cpu.p.c = cpu.r.a & 0x01; + shiftee = shiftee >> 1; + cpu.mmu.store_word(memory_location, shiftee); + } + + cpu.p.n = 0; + cpu_lib.r.p.check_z(cpu, shiftee); + } +}; + +var LSR_absolute_indexed_x = + new cpu_lib.addressing.Absolute_indexed_x(LSR_absolute); + +var LSR_direct_page_indexed_x = + new cpu_lib.addressing.Direct_page_indexed(LSR_direct_page, 'x'); + +var EOR_const = { + toString: function() { return "EOR" }, + bytes_required:function(cpu) { + if(cpu.p.e||cpu.p.m) { + return 2; + } else { + return 3; + } + }, + execute:function(cpu, bytes) { + cpu.instruction_details += "
Exclusive OR with accumulator"; + if(!cpu.instruction_translated) { + cpu.instruction_details += " (const)"; + cpu.instruction_translate = this.toString() + " #" + bytesToString(bytes); + cpu.instruction_history += " " + this.toString() + " #" + bytesToString(bytes); + } + cpu.cycle_count+=2; + + if(cpu.p.e||cpu.p.m) { + cpu.r.a ^= bytes[0]; + cpu.p.n = cpu.r.a >> 7; + } else { + cpu.cycle_count++; + cpu.r.a ^= (bytes[1]<<8)|bytes[0]; + cpu.p.n = cpu.r.a >> 15; + } + + cpu_lib.r.p.check_z(cpu, cpu.r.a); + } +}; + +var EOR_absolute = new cpu_lib.addressing.Absolute(EOR_const); + +var EOR_absolute_long = new cpu_lib.addressing.Absolute_long(EOR_const); + +var EOR_absolute_long_indexed_x = + new cpu_lib.addressing.Absolute_long_indexed_x(EOR_const); + +var EOR_direct_page = new cpu_lib.addressing.Direct_page(EOR_const); + +var EOR_direct_page_indirect = + new cpu_lib.addressing.Direct_page_indirect(EOR_const); + +var EOR_direct_page_indexed_x_indirect = + new cpu_lib.addressing.Direct_page_indexed_x_indirect(EOR_const); + +var EOR_direct_page_indirect_long_indexed_y = + new cpu_lib.addressing.Direct_page_indirect_long_indexed_y(EOR_const); + +var EOR_direct_page_indirect_long = + new cpu_lib.addressing.Direct_page_indirect_long(EOR_const); + +var EOR_direct_page_indirect_indexed_y = + new cpu_lib.addressing.Direct_page_indirect_indexed_y(EOR_const); + +var EOR_absolute_indexed_x = + new cpu_lib.addressing.Absolute_indexed_x(EOR_absolute); + +var EOR_absolute_indexed_y = + new cpu_lib.addressing.Absolute_indexed_y(EOR_absolute); + +var EOR_direct_page_indexed_x = + new cpu_lib.addressing.Direct_page_indexed(EOR_direct_page, 'x'); + +var EOR_stack_relative = new cpu_lib.addressing.Stack_relative(EOR_const); + +var EOR_stack_relative_indirect_indexed_y = + new cpu_lib.addressing.Stack_relative_indirect_indexed_y(EOR_const); + +var ORA_const= { + toString: function() { return "ORA" }, + bytes_required:function(cpu) { + if(cpu.p.e||cpu.p.m) { + return 2; + } else { + return 3; + } + }, + execute:function(cpu, bytes) { + cpu.instruction_details += "
OR with accumulator"; + if(!cpu.instruction_translated) { + cpu.instruction_details += " (const)"; + cpu.instruction_translate = this.toString() + " #" + bytesToString(bytes); + cpu.instruction_history += " " + this.toString() + " #" + bytesToString(bytes); + } + cpu.cycle_count+=2; + + if(cpu.p.e||cpu.p.m) { + cpu.r.a |= bytes[0]; + cpu.p.n = cpu.r.a >> 7; + } else { + cpu.cycle_count++; + + cpu.r.a |= (bytes[1]<<8)|bytes[0]; + cpu.p.n = cpu.r.a >> 15; + } + + cpu_lib.r.p.check_z(cpu, cpu.r.a); + } +}; + +var ORA_absolute = new cpu_lib.addressing.Absolute(ORA_const); + +var ORA_absolute_long = new cpu_lib.addressing.Absolute_long(ORA_const); + +var ORA_absolute_long_indexed_x = + new cpu_lib.addressing.Absolute_long_indexed_x(ORA_const); + +var ORA_direct_page = new cpu_lib.addressing.Direct_page(ORA_const); + +var ORA_direct_page_indirect = + new cpu_lib.addressing.Direct_page_indirect(ORA_const); + +var ORA_direct_page_indexed_x_indirect = + new cpu_lib.addressing.Direct_page_indexed_x_indirect(ORA_const); + +var ORA_direct_page_indirect_long = + new cpu_lib.addressing.Direct_page_indirect_long(ORA_const); + +var ORA_direct_page_indirect_long_indexed_y = + new cpu_lib.addressing.Direct_page_indirect_long_indexed_y(ORA_const); + +var ORA_direct_page_indirect_indexed_y = + new cpu_lib.addressing.Direct_page_indirect_indexed_y(ORA_const); + +var ORA_absolute_indexed_x = + new cpu_lib.addressing.Absolute_indexed_x(ORA_absolute); + +var ORA_absolute_indexed_y = + new cpu_lib.addressing.Absolute_indexed_y(ORA_absolute); + +var ORA_direct_page_indexed_x = + new cpu_lib.addressing.Direct_page_indexed(ORA_direct_page,'x'); + +var ORA_stack_relative = new cpu_lib.addressing.Stack_relative(ORA_const); + +var ORA_stack_relative_indirect_indexed_y = + new cpu_lib.addressing.Stack_relative_indirect_indexed_y(ORA_const); + +var AND_const= { + toString: function() { return "AND" }, + bytes_required:function(cpu) { + if(cpu.p.e||cpu.p.m) { + return 2; + } else { + return 3; + } + }, + execute:function(cpu, bytes) { + cpu.instruction_details += "
AND with accumulator"; + if(!cpu.instruction_translated) { + cpu.instruction_details += " (const)"; + cpu.instruction_translate = this.toString() + " #" + bytesToString(bytes); + cpu.instruction_history += " " + this.toString() + " #" + bytesToString(bytes); + } + cpu.cycle_count+=2; + + if(cpu.p.e||cpu.p.m) { + cpu.r.a &= bytes[0]; + cpu.p.n = cpu.r.a >> 7; + } else { + cpu.cycle_count++; + + cpu.r.a &= (bytes[1]<<8)|bytes[0]; + cpu.p.n = cpu.r.a >> 15; + } + + cpu_lib.r.p.check_z(cpu, cpu.r.a); + } +}; + +var AND_absolute = new cpu_lib.addressing.Absolute(AND_const); + +var AND_absolute_long = new cpu_lib.addressing.Absolute_long(AND_const); + +var AND_absolute_long_indexed_x = + new cpu_lib.addressing.Absolute_long_indexed_x(AND_const); + +var AND_direct_page = new cpu_lib.addressing.Direct_page(AND_const); + +var AND_direct_page_indirect = + new cpu_lib.addressing.Direct_page_indirect(AND_const); + +var AND_direct_page_indexed_x_indirect = + new cpu_lib.addressing.Direct_page_indexed_x_indirect(AND_const); + +var AND_direct_page_indirect_long = + new cpu_lib.addressing.Direct_page_indirect_long(AND_const); + +var AND_direct_page_indirect_long_indexed_y = + new cpu_lib.addressing.Direct_page_indirect_long_indexed_y(AND_const); + +var AND_direct_page_indirect_indexed_y = + new cpu_lib.addressing.Direct_page_indirect_indexed_y(AND_const); + +var AND_absolute_indexed_x = + new cpu_lib.addressing.Absolute_indexed_x(AND_absolute); + +var AND_absolute_indexed_y = + new cpu_lib.addressing.Absolute_indexed_y(AND_absolute); + +var AND_direct_page_indexed_x = + new cpu_lib.addressing.Direct_page_indexed(AND_direct_page, 'x'); + +var AND_stack_relative = new cpu_lib.addressing.Stack_relative(AND_const); + +var AND_stack_relative_indirect_indexed_y = + new cpu_lib.addressing.Stack_relative_indirect_indexed_y(AND_const); + +var CPX_const= { + toString: function() { return "CPX" }, + bytes_required:function(cpu) { + if(cpu.p.e||cpu.p.x) { + return 2; + } else { + return 3; + } + }, + execute:function(cpu, bytes) { + cpu.instruction_details += "
Compare value with X register"; + if(!cpu.instruction_translated) { + cpu.instruction_details += " (const)"; + cpu.instruction_translate = this.toString() + " #" + bytesToString(bytes); + cpu.instruction_history += " " + this.toString() + " #" + bytesToString(bytes); + } + cpu.cycle_count+=2; + + var result; + if(cpu.p.e||cpu.p.x) { + result = cpu.r.x - bytes[0]; + if(result<0) { + cpu.p.c = 0; + result = 0x100 + result; + } else { + cpu.p.c = 1; + } + cpu.p.n = result >> 7; + } else { + cpu.cycle_count++; + + result = cpu.r.x - ((bytes[1]<<8)|bytes[0]); + if(result<0) { + cpu.p.c = 0; + result = 0x10000 + result; + } else { + cpu.p.c = 1; + } + cpu.p.n = result >> 15; + } + + cpu_lib.r.p.check_z(cpu, result); + } +}; + +var CPX_direct_page = new cpu_lib.addressing.Direct_page(CPX_const); + +var CPX_absolute = new cpu_lib.addressing.Absolute(CPX_const); + +var CPY_const= { + toString: function() { return "CPY" }, + bytes_required:function(cpu) { + if(cpu.p.e||cpu.p.x) { + return 2; + } else { + return 3; + } + }, + execute:function(cpu, bytes) { + cpu.instruction_details += "
Compare value with Y register"; + if(!cpu.instruction_translated) { + cpu.instruction_details += " (const)"; + cpu.instruction_translate = this.toString() + " #" + bytesToString(bytes); + cpu.instruction_history += " " + this.toString() + " #" + bytesToString(bytes); + } + cpu.cycle_count+=2; + + var result; + if(cpu.p.e||cpu.p.x) { + result = cpu.r.y - bytes[0]; + if(result<0) { + cpu.p.c = 0; + result = 0x100 + result; + } else { + cpu.p.c = 1; + } + cpu.p.n = result >> 7; + } else { + cpu.cycle_count++; + result = cpu.r.y - ((bytes[1]<<8)|bytes[0]); + if(result<0) { + cpu.p.c = 0; + result = 0x10000 + result; + } else { + cpu.p.c = 1; + } + cpu.p.n = result >> 15; + } + + cpu_lib.r.p.check_z(cpu, result); + } +}; + +var CPY_direct_page = new cpu_lib.addressing.Direct_page(CPY_const); + +var CPY_absolute = new cpu_lib.addressing.Absolute(CPY_const); + +var CMP_const= { + toString: function() { return "CMP" }, + bytes_required:function(cpu) { + if(cpu.p.e||cpu.p.m) { + return 2; + } else { + return 3; + } + }, + execute:function(cpu, bytes) { + cpu.instruction_details += "
Compare value with accumulator"; + if(!cpu.instruction_translated) { + cpu.instruction_details += " (const)"; + cpu.instruction_translate = this.toString() + " #" + bytesToString(bytes); + cpu.instruction_history += " " + this.toString() + " #" + bytesToString(bytes); + } + cpu.cycle_count+=2; + + var result; + if(cpu.p.e||cpu.p.m) { + result = cpu.r.a - bytes[0]; + if(result<0) { + cpu.p.c = 0; + result = 0x100 + result; + } else { + cpu.p.c = 1; + } + cpu.p.n = result >> 7; + } else { + cpu.cycle_count++; + + result = cpu.r.a - ((bytes[1]<<8)|bytes[0]); + if(result<0) { + cpu.p.c = 0; + result = 0x10000 + result; + } else { + cpu.p.c = 1; + } + cpu.p.n = result >> 15; + } + + cpu_lib.r.p.check_z(cpu, result); + } +}; + +var CMP_direct_page = new cpu_lib.addressing.Direct_page(CMP_const); + +var CMP_direct_page_indexed_x = + new cpu_lib.addressing.Direct_page_indexed(CMP_direct_page, 'x'); + +var CMP_direct_page_indirect = + new cpu_lib.addressing.Direct_page_indirect(CMP_const); + +var CMP_direct_page_indexed_x_indirect = + new cpu_lib.addressing.Direct_page_indexed_x_indirect(CMP_const); + +var CMP_direct_page_indirect_long = + new cpu_lib.addressing.Direct_page_indirect_long(CMP_const); + +var CMP_direct_page_indirect_long_indexed_y = + new cpu_lib.addressing.Direct_page_indirect_long_indexed_y(CMP_const); + +var CMP_direct_page_indirect_indexed_y = + new cpu_lib.addressing.Direct_page_indirect_indexed_y(CMP_const); + +var CMP_absolute = new cpu_lib.addressing.Absolute(CMP_const); + +var CMP_absolute_long = new cpu_lib.addressing.Absolute_long(CMP_const); + +var CMP_absolute_long_indexed_x = + new cpu_lib.addressing.Absolute_long_indexed_x(CMP_const); + +var CMP_absolute_indexed_x = + new cpu_lib.addressing.Absolute_indexed_x(CMP_absolute); + +var CMP_absolute_indexed_y = + new cpu_lib.addressing.Absolute_indexed_y(CMP_absolute); + +var CMP_stack_relative = new cpu_lib.addressing.Stack_relative(CMP_const); + +var CMP_stack_relative_indirect_indexed_y = + new cpu_lib.addressing.Stack_relative_indirect_indexed_y(CMP_const); + +var SBC_const= { + toString: function() { return "SBC" }, + bytes_required:function(cpu) { + if(cpu.p.e||cpu.p.m) { + return 2; + } else { + return 3; + } + }, + execute:function(cpu, bytes) { + cpu.instruction_details += "
Substract value from accumulator"; + if(!cpu.instruction_translated) { + cpu.instruction_details += " (const)"; + cpu.instruction_translate = this.toString() + " #" + bytesToString(bytes); + cpu.instruction_history += " " + this.toString() + " #" + bytesToString(bytes); + } + cpu.cycle_count+=2; + + var old_a = cpu.r.a, + temp = 0; + if(cpu.p.c===0) + temp = 1; + + if(cpu.p.e||cpu.p.m) { + if(cpu.p.d) { + // Form a decimal number out of a. + var ones = cpu.r.a & 0x0f, + tens = cpu.r.a >> 4, + dec_a = (tens*10)+ones; + + // Form a decimal number out of the argument. + ones = bytes[0] & 0x0f; + tens = bytes[0] >> 4; + var result = dec_a - ((tens*10)+ones) - temp; + + // Check for decimal overflow. + if(result<0) { + result += 100; + cpu.p.c = 0; + } else { + cpu.p.c = 1; + } + var digits = result.toString(10).split(""), + i; + cpu.r.a = 0; + for(i=0;i> 7; + + // Check for signed overflow. + // If they started with the same sign and then the resulting sign is + // different then we have a signed overflow. + if((!((old_a ^ bytes[0]) & 0x80)) && ((cpu.r.a ^ old_a) & 0x80)) { + cpu.p.v = 1; + } else { + cpu.p.v = 0; + } + } + } else { + cpu.cycle_count++; + + var argument = (bytes[1]<<8)|bytes[0]; + temp = 0; + + if(cpu.p.c===0) + temp = 1; + + if(cpu.p.d) { + // Form a decimal number out of a. + var ones = cpu.r.a & 0xf, + tens = (cpu.r.a >>4) & 0xf, + hundreds = (cpu.r.a >> 8) & 0xf, + thousands = (cpu.r.a >> 12) & 0xf, + dec_a = (thousands*1000)+(hundreds*100)+(tens*10)+ones; + + // Form a decimal number out of the argument. + ones = argument & 0xf; + tens = (argument >> 4) & 0xf; + hundreds = (argument >> 8) & 0xf; + thousands = (argument >> 12) & 0xf; + var dec_arg = (thousands*1000)+(hundreds*100)+(tens*10)+ones, + result = dec_a - dec_arg - temp; + // Check for decimal overflow. + if(result<0) { + result += 10000; + cpu.p.c = 0; + } else { + cpu.p.c = 1; + } + var digits = result.toString(10).split(""), + i; + cpu.r.a = 0; + for(i=0;i> 15; + + // Check for signed overflow. + // If they started with the same sign and then the resulting sign is + // different then we have a signed overflow. + if((!((old_a ^ argument) & 0x8000)) && ((cpu.r.a ^ old_a) & 0x8000)) { + cpu.p.v = 1; + } else { + cpu.p.v = 0; + } + } + } + + cpu_lib.r.p.check_z(cpu, cpu.r.a); + } +}; + +var SBC_direct_page = new cpu_lib.addressing.Direct_page(SBC_const); + +var SBC_absolute = new cpu_lib.addressing.Absolute(SBC_const); + +var SBC_absolute_long = new cpu_lib.addressing.Absolute_long(SBC_const); + +var SBC_absolute_long_indexed_x = + new cpu_lib.addressing.Absolute_long_indexed_x(SBC_const); + +var SBC_direct_page_indexed_x_indirect = + new cpu_lib.addressing.Direct_page_indexed_x_indirect(SBC_const); + +var SBC_direct_page_indirect_long = + new cpu_lib.addressing.Direct_page_indirect_long(SBC_const); + +var SBC_direct_page_indirect_long_indexed_y = + new cpu_lib.addressing.Direct_page_indirect_long_indexed_y(SBC_const); + +var SBC_direct_page_indirect_indexed_y = + new cpu_lib.addressing.Direct_page_indirect_indexed_y(SBC_const); + +var SBC_absolute_indexed_x = + new cpu_lib.addressing.Absolute_indexed_x(SBC_absolute); + +var SBC_absolute_indexed_y = + new cpu_lib.addressing.Absolute_indexed_y(SBC_absolute); + +var SBC_direct_page_indexed_x = + new cpu_lib.addressing.Direct_page_indexed(SBC_direct_page, 'x'); + +var SBC_direct_page_indirect = + new cpu_lib.addressing.Direct_page_indirect(SBC_const); + +var SBC_stack_relative = new cpu_lib.addressing.Stack_relative(SBC_const); + +var SBC_stack_relative_indirect_indexed_y = + new cpu_lib.addressing.Stack_relative_indirect_indexed_y(SBC_const); + +var ADC_const = { + toString: function() { return "ADC" }, + bytes_required:function(cpu) { + if(cpu.p.e||cpu.p.m) { + return 2; + } else { + return 3; + } + }, + execute:function(cpu, bytes) { + cpu.instruction_details += "
Add with cary value to accumulator"; + if(!cpu.instruction_translated) { + cpu.instruction_details += " (const)"; + cpu.instruction_translate = this.toString() + " #" + bytesToString(bytes); + cpu.instruction_history += " " + this.toString() + " #" + bytesToString(bytes); + } + var old_a = cpu.r.a; + if(cpu.p.e||cpu.p.m) { + cpu.cycle_count+=2; + + if(cpu.p.d) { + // Form a decimal number out of a. + var ones = cpu.r.a & 0x0f, + tens = cpu.r.a >>4, + dec_a = (tens*10)+ones; + + // Form a decimal number out of the argument. + ones = bytes[0] & 0x0f; + tens = bytes[0] >>4; + var result = dec_a + ((tens*10)+ones) + cpu.p.c; + // Check for decimal overflow. + if(result>99) { + result -= 99; + cpu.p.c = 1; + } else { + cpu.p.c = 0; + } + var digits = result.toString(10).split(""), + i; + cpu.r.a = 0; + for(i=0;i> 7; + + // Check for signed overflow. + // If they started with the same sign and then the resulting sign is + // different then we have a signed overflow. + if((!((old_a ^ bytes[0]) & 0x80)) && ((cpu.r.a ^ old_a) & 0x80)) { + cpu.p.v = 1; + } else { + cpu.p.v = 0; + } + } + } else { + cpu.cycle_count+=3; + var argument = (bytes[1]<<8)|bytes[0]; + if(cpu.p.d) { + // Form a decimal number out of a. + var ones = cpu.r.a & 0xf, + tens = (cpu.r.a >>4) & 0xf, + hundreds = (cpu.r.a >> 8) & 0xf, + thousands = (cpu.r.a >> 12) & 0xf, + dec_a = (thousands*1000)+(hundreds*100)+(tens*10)+ones; + + // Form a decimal number out of the argument. + ones = argument & 0xf; + tens = (argument >> 4) & 0xf; + hundreds = (argument >> 8) & 0xf; + thousands = (argument >> 12) & 0xf; + var dec_arg = (thousands*1000)+(hundreds*100)+(tens*10)+ones, + result = dec_a + dec_arg + cpu.p.c; + // Check for decimal overflow. + if(result>9999) { + result -= 9999; + cpu.p.c = 1; + } else { + cpu.p.c = 0; + } + var digits = result.toString(10).split(""), + i; + cpu.r.a = 0; + for(i=0;i> 15; + + // Check for signed overflow. + // If they started with the same sign and then the resulting sign is + // different then we have a signed overflow. + if((!((old_a ^ argument) & 0x8000)) && ((cpu.r.a ^ old_a) & 0x8000)) { + cpu.p.v = 1; + } else { + cpu.p.v = 0; + } + } + } + + cpu_lib.r.p.check_z(cpu, cpu.r.a); + } +}; + +var ADC_absolute = new cpu_lib.addressing.Absolute(ADC_const); + +var ADC_absolute_long = new cpu_lib.addressing.Absolute_long(ADC_const); + +var ADC_absolute_long_indexed_x = + new cpu_lib.addressing.Absolute_long_indexed_x(ADC_const); + +var ADC_direct_page = new cpu_lib.addressing.Direct_page(ADC_const); + +var ADC_direct_page_indirect = + new cpu_lib.addressing.Direct_page_indirect(ADC_const); + +var ADC_direct_page_indexed_x_indirect = + new cpu_lib.addressing.Direct_page_indexed_x_indirect(ADC_const); + +var ADC_direct_page_indirect_long_indexed_y = + new cpu_lib.addressing.Direct_page_indirect_long_indexed_y(ADC_const); + +var ADC_direct_page_indirect_long = + new cpu_lib.addressing.Direct_page_indirect_long(ADC_const); + +var ADC_direct_page_indirect_indexed_y = + new cpu_lib.addressing.Direct_page_indirect_indexed_y(ADC_const); + +var ADC_absolute_indexed_x = + new cpu_lib.addressing.Absolute_indexed_x(ADC_absolute); + +var ADC_absolute_indexed_y = + new cpu_lib.addressing.Absolute_indexed_y(ADC_absolute); + +var ADC_direct_page_indexed_x = + new cpu_lib.addressing.Direct_page_indexed(ADC_direct_page, 'x'); + +var ADC_stack_relative = new cpu_lib.addressing.Stack_relative(ADC_const); + +var ADC_stack_relative_indirect_indexed_y = + new cpu_lib.addressing.Stack_relative_indirect_indexed_y(ADC_const); + +var BMI = new cpu_lib.branch.Branch('n', true); + +var BPL = new cpu_lib.branch.Branch('n', false); + +var BVC = new cpu_lib.branch.Branch('v', false); + +var BVS = new cpu_lib.branch.Branch('v', true); + +var BCC = new cpu_lib.branch.Branch('c', false); + +var BCS = new cpu_lib.branch.Branch('c', true); + +var BEQ = new cpu_lib.branch.Branch('z', true); + +var BNE = new cpu_lib.branch.Branch('z', false); + +var BRA = new cpu_lib.branch.Branch(); + +var BRL = { + toString: function() { return "BRL" }, + bytes_required:3, + + execute:function(cpu, bytes) { + cpu.cycle_count+=4; + + // Handle double byte two's complement numbers as the branch argument. + var num = (bytes[1]<<8)|bytes[0]; + if(num<=32767) { + cpu.r.pc+=num; + cpu.r.pc&=0xffff; + } else { + cpu.r.pc-=65536-num; + if(cpu.r.pc<0) + cpu.r.pc+=0xffff; + } + } +}; + + +var JMP_absolute_indirect= { + toString: function() { return "JMP" }, + bytes_required:3, + + execute:function(cpu, bytes) { + cpu.cycle_count+=5; + + cpu.r.pc = cpu.mmu.read_word((bytes[1]<<8)|bytes[0]); + } +}; + +var JMP_absolute_long= { + toString: function() { return "JMP" }, + bytes_required:4, + + execute:function(cpu, bytes) { + cpu.cycle_count+=4; + + cpu.r.k = bytes[2]; + cpu.r.pc = (bytes[1]<<8)|bytes[0]; + } +}; + +var JMP_absolute_indirect_long= { + toString: function() { return "JMP" }, + bytes_required:3, + + execute:function(cpu, bytes) { + cpu.cycle_count+=6; + + var memory_location = (bytes[1]<<8)|bytes[0]; + cpu.r.pc = cpu.mmu.read_word(memory_location); + cpu.r.k = cpu.mmu.read_byte(memory_location+2); + } +}; + +var JMP_absolute_indexed_x_indirect= { + toString: function() { return "JMP" }, + bytes_required:3, + + execute:function(cpu, bytes) { + cpu.cycle_count+=6; + + var memory_location = ((bytes[1]<<8)|bytes[0])+cpu.r.x, + bank = cpu.r.k; + if(memory_location&0x10000) { + bank++; + } + memory_location &= 0xffff; + var indirect_location = cpu.mmu.read_word_long(memory_location, bank), + low_byte = cpu.mmu.read_byte(indirect_location); + bank = cpu.r.k; + if(indirect_location===0xffff) { + indirect_location = 0; + bank++; + } else { + indirect_location++; + } + var high_byte = cpu.mmu.read_byte_long(indirect_location, bank); + cpu.r.pc = (high_byte<<8)|low_byte; + } +}; + +var JMP_absolute= { + toString: function() { return "JMP" }, + bytes_required:3, + + execute:function(cpu, bytes) { + cpu.cycle_count+=3; + + cpu.r.pc = (bytes[1]<<8)|bytes[0]; + } +}; + +var TYA = { + toString: function() { return "TYA" }, + bytes_required:function() { + return 1; + }, + execute:function(cpu) { + cpu.cycle_count+=2; + if(cpu.p.e||cpu.p.m) { + if(cpu.p.e||cpu.p.x) { + // 8-bit index register to 8-bit accumulator. + cpu.r.a = cpu.r.y; + } else { + // 16-bit index register to 8-bit accumulator. + cpu.r.a = cpu.r.y & 0x00ff; + } + cpu.p.n = cpu.r.a >> 7; + } else { + // 8-bit index register to 16-bit accumulator. + // 16-bit index register to 16-bit accumulator. + cpu.r.a = cpu.r.y; + cpu.p.n = cpu.r.a >> 15; + } + cpu_lib.r.p.check_z(cpu, cpu.r.a); + cpu.instruction_history += " TYA"; + cpu.instruction_details += "
Transfer Y Register to Accumulator"; + } +}; + +var TAY = { + toString: function() { return "TAY" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=2; + if(cpu.p.e||cpu.p.m) { + if(cpu.p.e||cpu.p.x) { + // 8-bit accumulator to 8-bit x index register. + cpu.r.y = cpu.r.a; + cpu.p.n = cpu.r.y >> 7; + } else { + // 8-bit accumulator to 16-bit x index register. + cpu.r.y = cpu.r.b; // Transfer b as high-byte of x. + cpu.r.y |= cpu.r.a; // Use the bitwise or to add a as the low-byte. + cpu.p.n = cpu.r.y >> 15; + } + } else { + if(cpu.p.x) { + // 16-bit accumulator to 8-bit x index register. + cpu.r.y = cpu.r.a & 0x00ff; // Transfer only the low-byte to x. + cpu.p.n = cpu.r.y >> 7; + } else { + // 16-bit accumulator to 16-bit x index register. + cpu.r.y = cpu.r.a; + cpu.p.n = cpu.r.y >> 15; + } + } + + cpu_lib.r.p.check_z(cpu, cpu.r.y); + cpu.instruction_history += " TAY"; + cpu.instruction_details += "
Transfer Accumulator to Y Register"; + } +}; + + +var TXA = { + toString: function() { return "TXA" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=2; + if(cpu.p.e||cpu.p.m) { + if(cpu.p.e||cpu.p.x) { + // 8-bit index register to 8-bit accumulator. + cpu.r.a = cpu.r.x; + } else { + // 16-bit index register to 8-bit accumulator. + cpu.r.a = cpu.r.x & 0x00ff; + } + cpu.p.n = cpu.r.a >> 7; + } else { + // 8-bit index register to 16-bit accumulator. + // 16-bit index register to 16-bit accumulator. + cpu.r.a = cpu.r.x; + cpu.p.n = cpu.r.a >> 15; + } + cpu_lib.r.p.check_z(cpu, cpu.r.a); + cpu.instruction_history += " TXA"; + cpu.instruction_details += "
Transfer X Register to Accumulator"; + } +}; + +var TAX = { + toString: function() { return "TAX" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=2; + if(cpu.p.e||cpu.p.m) { + if(cpu.p.e||cpu.p.x) { + // 8-bit accumulator to 8-bit x index register. + cpu.r.x = cpu.r.a; + cpu.p.n = cpu.r.x >> 7; + } else { + // 8-bit accumulator to 16-bit x index register. + cpu.r.x = cpu.r.b; // Transfer b as high-byte of x. + cpu.r.x |= cpu.r.a; // Use the bitwise or to add a as the low-byte. + cpu.p.n = cpu.r.x >> 15; + } + } else { + if(cpu.p.x) { + // 16-bit accumulator to 8-bit x index register. + cpu.r.x = cpu.r.a & 0x00ff; // Transfer only the low-byte to x. + cpu.p.n = cpu.r.x >> 7; + } else { + // 16-bit accumulator to 16-bit x index register. + cpu.r.x = cpu.r.a; + cpu.p.n = cpu.r.x >> 15; + } + } + cpu_lib.r.p.check_z(cpu, cpu.r.x); + cpu.instruction_history += " TAX"; + cpu.instruction_details += "
Transfer Accumulator to X Register"; + } +}; + +var TXY = { + toString: function() { return "TXY" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=2; + cpu.r.y = cpu.r.x; + cpu_lib.r.p.check_z(cpu, cpu.r.y); + + if(cpu.p.e||cpu.p.x) { + cpu.p.n = cpu.r.y >> 7; + } else { + cpu.p.n = cpu.r.y >> 15; + } + cpu.instruction_history += " TXY"; + cpu.instruction_details += "
Transfer X Register to Y Register"; + } +}; + +var TYX = { + toString: function() { return "TYX" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=2; + cpu.r.x = cpu.r.y; + cpu_lib.r.p.check_z(cpu, cpu.r.x); + + if(cpu.p.e||cpu.p.x) { + cpu.p.n = cpu.r.y >> 7; + } else { + cpu.p.n = cpu.r.y >> 15; + } + cpu.instruction_history += " TYX"; + cpu.instruction_details += "
Transfer Y Register to X Register"; + } +}; + +var TCD = { + toString: function() { return "TCD" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=2; + + // Transfers 16-bits regardless of setting. + if(cpu.p.e||cpu.p.m) { + cpu.r.d = (cpu.r.b<<8)|cpu.r.a; + } else { + cpu.r.d = cpu.r.a; + } + + cpu.p.n = cpu.r.d >> 15; + + cpu_lib.r.p.check_z(cpu, cpu.r.d); + cpu.instruction_history += " TCD"; + cpu.instruction_details += "
Transfer C acc. to Direct Register"; + } +}; + +var TDC = { + toString: function() { return "TDC" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=2; + + // Transfers 16-bits regardless of setting. + if(cpu.p.e||cpu.p.m) { + cpu.r.a = cpu.r.d & 0xff; + cpu.r.b = cpu.r.d >> 8; + cpu.p.n = cpu.r.b >> 7; + } else { + cpu.r.a = cpu.r.d; + cpu.p.n = cpu.r.a >> 7; + } + + cpu_lib.r.p.check_z(cpu, cpu.r.a); + cpu.instruction_history += " TDC"; + cpu.instruction_details += "
Transfer Direct Register to C acc."; + } +}; + +var TCS = { + toString: function() { return "TCS" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=2; + if(cpu.p.e||!cpu.p.m) { + cpu.r.s = cpu.r.a; + } else { + cpu.r.s = (cpu.r.b<<8)|cpu.r.a; + } + cpu.instruction_history += " TCS"; + cpu.instruction_details += "
Transfer C acc. to Stack Pointer"; + } +}; + +var TSC = { + toString: function() { return "TSC" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=2; + + if(cpu.p.e) { + cpu.r.b = 1; + cpu.r.a = cpu.r.s; + // TODO: Figure out if in emulation mode the z and n bits should always + // be set to zero here as a 1 is transferred to b. + cpu.p.n = 0; + cpu.p.z = 0; + } else { + if(cpu.p.m) { + cpu.r.a = cpu.r.s & 0xff; + cpu.r.b = cpu.r.s >> 8; + cpu.p.n = cpu.r.b >> 7; + } else { + cpu.r.a = cpu.r.s; + cpu.p.n = cpu.r.a >> 15; + } + + cpu_lib.r.p.check_z(cpu, cpu.r.s); + } + cpu.instruction_history += " TSC"; + cpu.instruction_details += "
Transfer Stack Pointer to C acc."; + } +}; + +var STZ_absolute= { + toString: function() { return "STZ" }, + bytes_required:3, + + execute: function(cpu, bytes) { + cpu.cycle_count+=4; + + var memory_location = (bytes[1]<<8)|bytes[0]; + if(cpu.p.e||cpu.p.m) { + cpu.mmu.store_byte(memory_location, 0); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word(memory_location, 0); + } + } +}; + +var STZ_direct_page= { + toString: function() { return "STZ" }, + bytes_required:2, + + execute: function(cpu, bytes) { + cpu.cycle_count+=3; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0]+cpu.r.d; + if(cpu.p.e||cpu.p.m) { + cpu.mmu.store_byte(memory_location, 0); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word(memory_location, 0); + } + } +}; + +var STZ_absolute_indexed_x= { + toString: function() { return "STZ" }, + bytes_required:3, + + execute: function(cpu, bytes) { + cpu.cycle_count+=5; + + var memory_location = ((bytes[1]<<8)|bytes[0])+cpu.r.x; + if(cpu.p.e||cpu.p.m) { + cpu.mmu.store_byte(memory_location, 0); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word(memory_location, 0); + } + } +}; + +var STZ_direct_page_indexed_x= { + toString: function() { return "STZ" }, + bytes_required:2, + + execute: function(cpu, bytes) { + cpu.cycle_count+=4; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0]+cpu.r.d+cpu.r.x; + if(cpu.p.e||cpu.p.m) { + cpu.mmu.store_byte(memory_location, 0); + } else { + cpu.cycle_count++; + + // Check for overflow. + var overflow_check = memory_location - 0xffff; + if(overflow_check > 0) { + memory_location = overflow_check-1; + } + cpu.mmu.store_byte(memory_location, 0); + // Check for potential overflow again. + if(memory_location===0xffff) { + memory_location = 0; + } else { + memory_location++; + } + cpu.mmu.store_byte(memory_location, 0); + } + } +}; + +var STA_direct_page_indirect= { + toString: function() { return "STA" }, + bytes_required:2, + + execute:function(cpu, bytes) { + cpu.cycle_count+=5; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0] + cpu.r.d, + absolute_location = cpu.mmu.read_word(memory_location); + if(cpu.p.e||cpu.p.m) { + cpu.mmu.store_byte(absolute_location, cpu.r.a); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word(absolute_location, cpu.r.a); + } + } +}; + +var STA_direct_page_indirect_long= { + toString: function() { return "STA" }, + bytes_required:2, + + execute:function(cpu, bytes) { + cpu.cycle_count+=6; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0] + cpu.r.d, + absolute_location = cpu.mmu.read_word(memory_location), + bank_byte = cpu.mmu.read_byte(memory_location+2); + if(cpu.p.e||cpu.p.m) { + cpu.mmu.store_byte_long(absolute_location, bank_byte, + cpu.r.a); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word_long(absolute_location, bank_byte, cpu.r.a); + } + } +}; + +var STA_direct_page_indirect_long_indexed_y= { + toString: function() { return "STA" }, + bytes_required:2, + + execute:function(cpu, bytes) { + cpu.cycle_count+=6; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0] + cpu.r.d, + bank_byte = cpu.mmu.read_byte(memory_location+2), + absolute_location = cpu.mmu.read_word(memory_location) + cpu.r.y; + if(absolute_location >> 16) { + bank_byte++; + absolute_location &= 0xffff; + } + if(cpu.p.e||cpu.p.m) { + cpu.mmu.store_byte_long(absolute_location, bank_byte, cpu.r.a); + } else { + cpu.cycle_count++; + + cpu.mmu.store_byte_long(absolute_location, bank_byte, cpu.r.a&0xff); + absolute_location++; + if(absolute_location >> 16) { + bank_byte++; + } + cpu.mmu.store_byte_long(absolute_location, bank_byte, cpu.r.a>>8); + } + } +}; + +var STA_direct_page_indirect_indexed_y= { + toString: function() { return "STA" }, + bytes_required:2, + + execute:function(cpu, bytes) { + cpu.cycle_count+=6; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0] + cpu.r.d, + absolute_location = cpu.mmu.read_word(memory_location) + cpu.r.y; + if(cpu.p.e||cpu.p.m) { + cpu.mmu.store_byte(absolute_location, cpu.r.a); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word(absolute_location, cpu.r.a); + } + } +}; + +var STA_stack_relative= { + toString: function() { return "STA" }, + bytes_required:2, + + execute:function(cpu, bytes) { + cpu.cycle_count+=4; + + if(cpu.p.e) { + cpu.mmu.store_byte(0x100 | ((cpu.r.s + bytes[0]) & 0xff), cpu.r.a); + } else { + if(cpu.p.m) { + cpu.mmu.store_byte(cpu.r.s + bytes[0], cpu.r.a); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word(cpu.r.s + bytes[0], cpu.r.a); + } + } + } +}; + +var STA_stack_relative_indirect_indexed_y= { + toString: function() { return "STA" }, + bytes_required:2, + + execute:function(cpu, bytes) { + cpu.cycle_count+=7; + + if(cpu.p.e) { + var location_loc = 0x100 | ((cpu.r.s + bytes[0]) & 0xff), + low_byte = cpu.mmu.read_byte(location_loc), + high_byte; + if(location_loc===0x1ff) { + high_byte = cpu.mmu.read_byte(0x100); + } else { + high_byte = cpu.mmu.read_byte(location_loc+1); + } + var absolute_location = ((high_byte<<8)|low_byte)+cpu.r.y, + b; + if(absolute_location>=0x10000) { + b = cpu.mmu.read_byte_long(absolute_location, cpu.r.dbr+1); + } else { + b = cpu.mmu.read_byte(absolute_location); + } + cpu.mmu.store_byte(b, cpu.r.a); + } else { + var location_loc = cpu.r.s + bytes[0], + absolute_location = cpu.mmu.read_word(location_loc)+cpu.r.y; + if(cpu.p.m) { + var b; + if(absolute_location>=0x10000) { + b = cpu.mmu.read_byte_long(absolute_location, cpu.r.dbr+1); + } else { + b = cpu.mmu.read_byte(absolute_location); + } + cpu.mmu.store_byte(b, cpu.r.a); + } else { + cpu.cycle_count++; + + var low_byte, high_byte; + if(absolute_location>=0x10000) { + low_byte = cpu.mmu.read_byte_long(absolute_location, cpu.r.dbr+1); + high_byte = cpu.mmu.read_byte_long(absolute_location+1, cpu.r.dbr+1); + } else { + low_byte = cpu.mmu.read_byte(absolute_location); + if(absolute_location===0xffff) { + high_byte = cpu.mmu.read_byte_long(0, cpu.r.dbr+1); + } else { + high_byte = cpu.mmu.read_byte(absolute_location); + } + } + cpu.mmu.store_word((high_byte<<8)|low_byte, cpu.r.a); + } + } + } +}; + +var NOP = { + toString: function() { return "NOP" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=2; + cpu.instruction_history += " NOP"; + cpu.instruction_details += "
No OPeration" + } +}; + +var LDY_const= { + toString: function() { return "LDY" }, + bytes_required:function(cpu) { + if(cpu.p.e||cpu.p.x) + return 2; + else + return 3; + }, + execute:function(cpu, bytes) { + cpu.instruction_details += "
Load value to register Y"; + if(!cpu.instruction_translated) { + cpu.instruction_details += " (const)"; + cpu.instruction_translate = this.toString() + " #" + bytesToString(bytes); + cpu.instruction_history += " " + this.toString() + " #" + bytesToString(bytes); + } + cpu.cycle_count+=2; + + if(cpu.p.e||cpu.p.x) { + cpu.r.y = bytes[0]; + cpu.p.n = cpu.r.y >> 7; + } else { + cpu.cycle_count++; + + cpu.r.y = (bytes[1]<<8)|bytes[0]; + cpu.p.n = cpu.r.y >> 15; + } + cpu_lib.r.p.check_z(cpu, cpu.r.y); + } +}; + +var LDY_absolute = new cpu_lib.addressing.Absolute(LDY_const); + +var LDY_absolute_indexed_x = + new cpu_lib.addressing.Absolute_indexed_x(LDY_absolute); + +var LDY_direct_page = new cpu_lib.addressing.Direct_page(LDY_const); + +var LDY_direct_page_indexed_x = + new cpu_lib.addressing.Direct_page_indexed(LDY_direct_page, 'x'); + +// Implement the decrement instructions as increment instructions that +// increment by negative one. +var DEX = new cpu_lib.inc.INC_register('x', 'x', -1); + +var DEY = new cpu_lib.inc.INC_register('y', 'x', -1); + +var DEC_accumulator = new cpu_lib.inc.INC_register('a', 'm', -1); + +var DEC_memory = new cpu_lib.inc.INC_memory(-1); + +var DEC_absolute = new cpu_lib.addressing.Absolute(DEC_memory); + +var DEC_absolute_indexed_x = + new cpu_lib.addressing.Absolute_indexed_x(DEC_absolute, true); + +var DEC_direct_page = new cpu_lib.addressing.Direct_page(DEC_memory); + +var DEC_direct_page_indexed_x = + new cpu_lib.addressing.Direct_page_indexed(DEC_direct_page, 'x'); + +var INX = new cpu_lib.inc.INC_register('x', 'x'); + +var INY = new cpu_lib.inc.INC_register('y', 'x'); + +var INC_accumulator = new cpu_lib.inc.INC_register('a', 'm'); + +var INC_memory = new cpu_lib.inc.INC_memory(); + +var INC_absolute = new cpu_lib.addressing.Absolute(INC_memory); + +var INC_absolute_indexed_x = + new cpu_lib.addressing.Absolute_indexed_x(INC_absolute, true); + +var INC_direct_page = new cpu_lib.addressing.Direct_page(INC_memory); + +var INC_direct_page_indexed_x = + new cpu_lib.addressing.Direct_page_indexed(INC_direct_page, 'x'); + +var STA_direct_page_indexed_x= { + toString: function() { return "STA" }, + bytes_required:2, + + execute: function(cpu, bytes) { + cpu.cycle_count+=4; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0]+cpu.p.d+cpu.r.x; + if(cpu.p.e||cpu.p.m) { + cpu.mmu.store_byte(memory_location, cpu.r.a); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word(memory_location, cpu.r.a); + } + } +}; + +var STA_direct_page_indexed_x_indirect= { + toString: function() { return "STA" }, + bytes_required:2, + + execute:function(cpu, bytes) { + cpu.cycle_count+=6; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + if(cpu.p.e) { + var memory_location = (bytes[0] + cpu.r.x) & 0xff, + low_byte_loc = cpu.mmu.read_byte_long(memory_location+cpu.r.d, 0), + high_byte_read_loc = ((memory_location+1)&0xff)+cpu.r.d, + high_byte_loc = cpu.mmu.read_byte_long(high_byte_read_loc, 0); + cpu.mmu.store_byte((high_byte_loc<<8) | low_byte_loc, cpu.r.a); + } else if(cpu.p.m) { + var memory_location = bytes[0] + cpu.r.d + cpu.r.x; + cpu.mmu.store_byte(cpu.mmu.read_word(memory_location), cpu.r.a); + } else { + cpu.cycle_count++; + + var memory_location = bytes[0] + cpu.r.d + cpu.r.x, + absolute_location = cpu.mmu.read_word(memory_location), + low_byte = cpu.mmu.read_byte(absolute_location), + high_byte; + absolute_location++; + if(absolute_location&0x10000) { + high_byte = cpu.mmu.read_byte_long(absolute_location, cpu.r.dbr+1); + } else { + high_byte = cpu.mmu.read_byte(absolute_location); + } + var storage_location = (high_byte<<8) | low_byte; + cpu.mmu.store_byte(storage_location, cpu.r.a & 0xff); + storage_location++; + if(storage_location&0x10000) { + cpu.mmu.store_byte_long(storage_location, cpu.r.dbr+1, cpu.r.a >> 8); + } else { + cpu.mmu.store_byte(storage_location, cpu.r.a >> 8); + } + } + } +}; + +var STA_direct_page= { + toString: function() { return "STA" }, + bytes_required:2, + + execute: function(cpu, bytes) { + cpu.cycle_count+=3; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0]+cpu.p.d; + if(cpu.p.e||cpu.p.m) { + cpu.mmu.store_byte(memory_location, cpu.r.a); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word(memory_location, cpu.r.a); + } + } +}; + +var STA_absolute_indexed_x= { + toString: function() { return "STA" }, + bytes_required:3, + + execute: function(cpu, bytes) { + cpu.cycle_count+=5; + + var memory_location = ((bytes[1]<<8)|bytes[0])+cpu.r.x; + if(cpu.p.e||cpu.p.m) { + cpu.mmu.store_byte(memory_location, cpu.r.a); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word(memory_location, cpu.r.a); + } + } +}; + +var STA_absolute_indexed_y= { + toString: function() { return "STA" }, + bytes_required:3, + + execute: function(cpu, bytes) { + cpu.cycle_count+=5; + + var memory_location = ((bytes[1]<<8)|bytes[0])+cpu.r.y; + if(cpu.p.e||cpu.p.m) { + cpu.mmu.store_byte(memory_location, cpu.r.a); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word(memory_location, cpu.r.a); + } + } +}; + +var STY_direct_page_indexed_x= { + toString: function() { return "STA" }, + bytes_required:2, + + execute: function(cpu, bytes) { + cpu.cycle_count+=4; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0]+cpu.p.d+cpu.r.x; + if(cpu.p.e||cpu.p.x) { + cpu.mmu.store_byte(memory_location, cpu.r.y); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word(memory_location, cpu.r.y); + } + } +}; + +var STY_direct_page= { + toString: function() { return "STY" }, + bytes_required:2, + + execute: function(cpu, bytes) { + cpu.cycle_count+=3; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0]+cpu.p.d; + if(cpu.p.e||cpu.p.x) { + cpu.mmu.store_byte(memory_location, cpu.r.y); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word(memory_location, cpu.r.y); + } + } +}; + +var STY_absolute= { + toString: function() { return "STY" }, + bytes_required:3, + + execute: function(cpu, bytes) { + cpu.cycle_count+=4; + + var memory_location = (bytes[1]<<8)|bytes[0]; + if(cpu.p.e||cpu.p.x) { + cpu.mmu.store_byte(memory_location, cpu.r.y); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word(memory_location, cpu.r.y); + } + } +}; + +var STX_direct_page_indexed_y= { + toString: function() { return "STX" }, + bytes_required:2, + + execute: function(cpu, bytes) { + cpu.cycle_count+=4; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0]+cpu.p.d+cpu.r.y; + if(cpu.p.e||cpu.p.x) { + cpu.mmu.store_byte(memory_location, cpu.r.x); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word(memory_location, cpu.r.x); + } + } +}; + +var STX_direct_page= { + toString: function() { return "STX" }, + bytes_required:2, + + execute: function(cpu, bytes) { + cpu.cycle_count+=3; + + if((cpu.r.d&0xff)!==0) + cpu.cycle_count++; + + var memory_location = bytes[0]+cpu.p.d; + if(cpu.p.e||cpu.p.x) { + cpu.mmu.store_byte(memory_location, cpu.r.x); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word(memory_location, cpu.r.x); + } + } +}; + +var STX_absolute= { + toString: function() { return "STX" }, + bytes_required:3, + + execute: function(cpu, bytes) { + cpu.cycle_count+=4; + + var memory_location = (bytes[1]<<8)|bytes[0]; + if(cpu.p.e||cpu.p.x) { + cpu.mmu.store_byte(memory_location, cpu.r.x); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word(memory_location, cpu.r.x); + } + } +}; + +var STA_absolute_long= { + toString: function() { return "STA" }, + bytes_required:4, + + execute: function(cpu, bytes) { + cpu.cycle_count+=5; + + var memory_location = (bytes[1]<<8)|bytes[0]; + if(cpu.p.e||cpu.p.m) { + cpu.mmu.store_byte_long(memory_location, bytes[2], cpu.r.a); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word_long(memory_location, bytes[2], cpu.r.a); + } + } +}; + +var STA_absolute_long_indexed_x= { + toString: function() { return "STA" }, + bytes_required:4, + + execute: function(cpu, bytes) { + cpu.cycle_count+=5; + + var memory_location = ((bytes[1]<<8)|bytes[0]) + cpu.r.x; + if(memory_location & 0x10000) { + memory_location &= 0xffff; + bytes[2]++; + } + if(cpu.p.e||cpu.p.m) { + cpu.mmu.store_byte_long(memory_location, bytes[2], cpu.r.a); + } else { + cpu.cycle_count++; + + cpu.mmu.store_byte_long(memory_location, bytes[2], cpu.r.a&0x00ff); + memory_location++; + if(memory_location & 0x10000) { + bytes[2]++; + } + cpu.mmu.store_byte_long(memory_location, bytes[2], cpu.r.a>>8); + } + } +}; + +var STA_absolute= { + toString: function() { return "STA" }, + bytes_required:3, + + execute: function(cpu, bytes) { + cpu.cycle_count+=4; + + var memory_location = (bytes[1]<<8)|bytes[0]; + if(cpu.p.e||cpu.p.m) { + cpu.mmu.store_byte(memory_location, cpu.r.a); + } else { + cpu.cycle_count++; + + cpu.mmu.store_word(memory_location, cpu.r.a); + } + } +}; + +var LDA_const= { + toString: function() { return "LDA" }, + bytes_required: function(cpu) { + if(cpu.p.e||cpu.p.m) { + return 2; + } else { + return 3; + } + }, + execute: function(cpu, bytes) { + cpu.instruction_details += "
Load value to accumulator"; + if(!cpu.instruction_translated) { + cpu.instruction_details += " (const)"; + cpu.instruction_translate = this.toString() + " #" + bytesToString(bytes); + cpu.instruction_history += " " + this.toString() + " #" + bytesToString(bytes); + } + cpu.cycle_count+=2; + + if(cpu.p.e||cpu.p.m) { + cpu.r.a = bytes[0]; + cpu.p.n = cpu.r.a >> 7; + } else { + cpu.cycle_count++; + + cpu.r.a = (bytes[1]<<8)|bytes[0]; + cpu.p.n = cpu.r.a >> 15; + } + cpu_lib.r.p.check_z(cpu, cpu.r.a); + } +}; + +var LDA_direct_page = new cpu_lib.addressing.Direct_page(LDA_const); + +var LDA_direct_page_indexed_x = + new cpu_lib.addressing.Direct_page_indexed(LDA_direct_page, 'x'); + +var LDA_direct_page_indirect = + new cpu_lib.addressing.Direct_page_indirect(LDA_const); + +var LDA_direct_page_indirect_indexed_y = + new cpu_lib.addressing.Direct_page_indirect_indexed_y(LDA_const); + +var LDA_absolute = new cpu_lib.addressing.Absolute(LDA_const); + +var LDA_absolute_indexed_x = + new cpu_lib.addressing.Absolute_indexed_x(LDA_absolute); + +var LDA_absolute_indexed_y = + new cpu_lib.addressing.Absolute_indexed_y(LDA_absolute); + +var LDA_stack_relative = new cpu_lib.addressing.Stack_relative(LDA_const); + +var LDA_stack_relative_indirect_indexed_y = + new cpu_lib.addressing.Stack_relative_indirect_indexed_y(LDA_const); + +var LDA_absolute_long = new cpu_lib.addressing.Absolute_long(LDA_const); + +var LDA_absolute_long_indexed_x = + new cpu_lib.addressing.Absolute_long_indexed_x(LDA_const); + +var LDA_direct_page_indexed_x_indirect = + new cpu_lib.addressing.Direct_page_indexed_x_indirect(LDA_const); + +var LDA_direct_page_indirect_long = + new cpu_lib.addressing.Direct_page_indirect_long(LDA_const); + +var LDA_direct_page_indirect_long_indexed_y = + new cpu_lib.addressing.Direct_page_indirect_long_indexed_y(LDA_const); + +var LDX_const= { + toString: function() { return "LDX" }, + bytes_required: function(cpu) { + if(cpu.p.e||cpu.p.x) { + return 2; + } else { + return 3; + } + }, + execute: function(cpu, bytes) { + cpu.instruction_details += "
Load value to register X"; + if(!cpu.instruction_translated) { + cpu.instruction_details += " (const)"; + cpu.instruction_translate = this.toString() + " #" + bytesToString(bytes); + cpu.instruction_history += " " + this.toString() + " #" + bytesToString(bytes); + } + cpu.cycle_count+=2; + + if(cpu.p.e||cpu.p.x) { + cpu.r.x = bytes[0]; + cpu.p.n = cpu.r.x >> 7; + } else { + cpu.cycle_count++; + + cpu.r.x = (bytes[1]<<8)|bytes[0]; + cpu.p.n = cpu.r.x >> 15; + } + cpu_lib.r.p.check_z(cpu, cpu.r.x); + } +}; + +var LDX_direct_page = new cpu_lib.addressing.Direct_page(LDX_const); + +var LDX_direct_page_indexed_y = + new cpu_lib.addressing.Direct_page_indexed(LDX_direct_page, 'y'); + +var LDX_absolute = new cpu_lib.addressing.Absolute(LDX_const); + +var LDX_absolute_indexed_y = + new cpu_lib.addressing.Absolute_indexed_y(LDX_absolute); + +var SEP = new cpu_lib.r.p.Set_p(1); + +var REP = new cpu_lib.r.p.Set_p(0); + +var XCE = { + toString: function() { return "XCE" }, + bytes_required:1, + + execute: function(cpu) { + cpu.cycle_count+=2; + + var temp = cpu.p.c; + cpu.p.c = cpu.p.e; + cpu.p.e = temp; + if(cpu.p.e) { + // Switching to emulation mode. + cpu.r.b = cpu.r.a >> 8; + cpu.r.a &= 0x00ff; + cpu.r.s &= 0xff; + } else { + // Switching to native mode. + cpu.r.a = (cpu.r.b<<8) | cpu.r.a; + cpu.p.m = 1; + cpu.p.x = 1; + cpu.r.s |= 0x100; + } + } +}; + +var CLC = new cpu_lib.r.p.Flag_set('c', 0); + +var SEC = new cpu_lib.r.p.Flag_set('c', 1); + +var CLI = new cpu_lib.r.p.Flag_set('i', 0); + +var SEI = new cpu_lib.r.p.Flag_set('i', 1); + +var CLD = new cpu_lib.r.p.Flag_set('d', 0); + +var SED = new cpu_lib.r.p.Flag_set('d', 1); + +var CLV = new cpu_lib.r.p.Flag_set('v', 0); + +var XBA = { + toString: function() { return "XBA" }, + bytes_required:1, + + execute:function(cpu) { + cpu.cycle_count+=3; + + if(cpu.p.e||cpu.p.m) { + cpu.cycle_count+=2; + var old_a = cpu.r.a; + cpu.r.a = cpu.r.b; + cpu.r.b = old_a; + + cpu.p.n = cpu.r.a >> 7; + cpu_lib.r.p.check_z(cpu, cpu.r.a); + } else { + var low_byte = cpu.r.a & 0xff; + var high_byte = cpu.r.a >> 8; + cpu.r.a = (low_byte<<8)|high_byte; + + cpu.p.n = high_byte >> 7; + cpu_lib.r.p.check_z(cpu, high_byte); + } + } +}; + +var MMU = function() { + this.cpu = {}; + this.memory = { 0: {} }; + this.memory_mapped_io_devices = {}; + + this.reset = function() { + this.memory ={ 0: {} }; + }; + + this.add_memory_mapped_io_device = function(write_callback, read_callback, + bank, location) { + if(typeof this.memory_mapped_io_devices[bank] === 'undefined') { + this.memory_mapped_io_devices[bank] = {}; + } + this.memory_mapped_io_devices[bank][location] = { write: write_callback, + read: read_callback }; + }; + + this.pull_byte = function() { + if(this.cpu.p.e) { + if(this.cpu.r.s===0xff) { + this.cpu.r.s = 0; + return this.read_byte(0x100); + } else { + return this.read_byte(0x100|(++this.cpu.r.s)); + } + } else { + return this.read_byte(++this.cpu.r.s); + } + }; + + this.push_byte = function(b) { + if(this.cpu.p.e) { + if(this.cpu.r.s===0) { + this.store_byte(0x100, b); + this.cpu.r.s = 0xff; + } else { + this.store_byte((0x100|(this.cpu.r.s--)), b); + } + } else { + this.store_byte(this.cpu.r.s--, b); + } + }; + + this.read_byte = function(memory_location) { + memory_location &= 0xffff; // Make sure the address is 16 bits. + + var device_map_at_bank = this.memory_mapped_io_devices[this.cpu.r.dbr]; + if(typeof device_map_at_bank !== "undefined") { + var device = device_map_at_bank[memory_location]; + if(typeof device !== "undefined") + return device.read(this.cpu); + } + return this.memory[this.cpu.r.dbr][memory_location]; + }; + + this.read_byte_long = function(memory_location, bank) { + // Make sure addresses given are the proper size. + memory_location &= 0xffff; + bank &= 0xff; + + if(typeof this.memory[bank] === 'undefined') { + this.memory[bank] = {}; + } + var device_map_at_bank = this.memory_mapped_io_devices[bank]; + if(typeof device_map_at_bank !== "undefined") { + var device = device_map_at_bank[memory_location]; + if(typeof device !== "undefined") + return device.read(this.cpu); + } + return this.memory[bank][memory_location]; + }; + + this.store_byte = function(memory_location, b) { + memory_location &= 0xffff; // Make sure the address is 16 bits + b &= 0xff; // Make sure the byte is actually a byte long + + var device_map_at_bank = this.memory_mapped_io_devices[this.cpu.r.dbr]; + if(typeof device_map_at_bank !== "undefined") { + var device = device_map_at_bank[memory_location]; + if(typeof device !== "undefined") + device.write(this.cpu, b); + } + this.memory[this.cpu.r.dbr][memory_location] = b; + }; + + this.store_byte_long = function(memory_location, bank, b) { + // Make sure addresses and byte given are the proper size. + memory_location &= 0xffff; + bank &= 0xff; + b &= 0xff; + + if(typeof this.memory[bank] === 'undefined') { + this.memory[bank] = {}; + } + var device_map_at_bank = this.memory_mapped_io_devices[bank]; + if(typeof device_map_at_bank !== "undefined") { + var device = device_map_at_bank[memory_location]; + if(typeof device !== "undefined") + device.write(this.cpu, b); + } + this.memory[bank][memory_location] = b; + }; + + this.read_word = function(memory_location) { + return (this.read_byte(memory_location+1)<<8) | + this.read_byte(memory_location); + }; + + this.read_word_long = function(memory_location, bank) { + return (this.read_byte_long(memory_location+1, bank)<<8) | + this.read_byte_long(memory_location, bank); + }; + + this.store_word = function(memory_location, word_or_low_byte, high_byte) { + // If no high_byte is given then assume word_or_low_byte is a word. + if(typeof high_byte === 'undefined') { + this.store_byte(memory_location, word_or_low_byte&0xff); + this.store_byte(memory_location+1, word_or_low_byte>>8); + } else { + this.store_byte(memory_location, word_or_low_byte); + this.store_byte(memory_location+1, high_byte); + } + }; + + this.store_word_long = function(memory_location, bank, word_or_low_byte, + high_byte) { + // If no high_byte is given then assume word_or_low_byte is a word. + if(typeof high_byte === 'undefined') { + this.store_byte_long(memory_location, bank, word_or_low_byte&0xff); + this.store_byte_long(memory_location+1, bank, word_or_low_byte>>8); + } else { + this.store_byte_long(memory_location, bank, word_or_low_byte); + this.store_byte_long(memory_location+1, bank, high_byte); + } + }; +}; + +window.CPU_65816 = function() { + + // Instruction & Instruction details to print + this.instruction = ""; + this.instruction_details = ""; + this.instruction_translate = ""; + this.instruction_history = ""; + this.instruction_translated = false; + + // Registers + this.r = { + a:0, // Accumulator + b:0, // "Hidden" Accumulator Register(high byte in 8-bit mode) + x:0, // X Index Register + y:0, // Y Index Register + d:0, // Direct Page Register + s:0xff, // Stack Pointer + pc:0, // Program Counter + dbr:0, // Data Bank Register + k:0 // Program Bank Register + }; + + // P register flags. + this.p = { + e:1, // Emulator (0 = native mode) + c:0, // Carry (1 = carry) + z:0, // Zero (1 = zero) + i:0, // IRQ Disable (1 = disabled) + d:0, // Decimal Mode (1 = decimal, 0 = binary) + x:0, // Index Register Select (1 = 8-bit, 0 = 16-bit) + m:0, // Memory/Accumulator Select (1 = 8-bit, 0 = 16-bit) + v:0, // Overflow (1 = overflow) + n:0 // Negative (1 = negative) + }; + + this.INTERRUPT = { NO_INTERRUPT: 0, NMI: 1, RESET: 2, ABORT: 3, COP: 4, + IRQ: 5, BRK: 6 }; + + this.interrupt = this.INTERRUPT.NO_INTERRUPT; + + // This is used to keep the cpu going if started with start(). + this.executing = false; + + // This is set by the WAI operation to stop execution until an interrupt + // is received. + this.waiting = false; + + // This is set by the STP operation to stop execution until a RESET + // interrupt is received. + this.stopped = false; + + this.raise_interrupt = function(i) { + if(this.waiting) { + this.waiting = false; + if(this.p.i) { + if(i===this.INTERRUPT.IRQ) { + i = this.INTERRUPT.NO_INTERRUPT; + } + } + this.interrupt = i; + this.start(); + } else if(this.stopped&&(i===this.INTERRUPT.RESET)) { + this.stopped = false; + this.start(); + } else { + this.interrupt = i; + } + }; + + this.cycle_count = 0; + + this.mmu = new MMU(); + this.mmu.cpu = this; + + this.opcode_map = { 0xfb : XCE, 0x18 : CLC, 0x78 : SEI, 0x38 : SEC, + 0x58 : CLI, 0xc2 : REP, 0xe2 : SEP, 0xd8 : CLD, + 0xf8 : SED, 0xb8 : CLV, 0xeb : XBA, 0xa9 : LDA_const, + 0xad : LDA_absolute, 0xaf : LDA_absolute_long, + 0xbf : LDA_absolute_long_indexed_x, + 0xa5 : LDA_direct_page, 0xbd : LDA_absolute_indexed_x, + 0xb5 : LDA_direct_page_indexed_x, + 0xb9 : LDA_absolute_indexed_y, + 0xa1 : LDA_direct_page_indexed_x_indirect, + 0xb2 : LDA_direct_page_indirect, + 0xa7 : LDA_direct_page_indirect_long, + 0xb7 : LDA_direct_page_indirect_long_indexed_y, + 0xb1 : LDA_direct_page_indirect_indexed_y, + 0xa3 : LDA_stack_relative, + 0xb3 : LDA_stack_relative_indirect_indexed_y, + 0xa2 : LDX_const, + 0xae : LDX_absolute, 0xa6 : LDX_direct_page, + 0xa0 : LDY_const, 0xbc : LDY_absolute_indexed_x, + 0xb4 : LDY_direct_page_indexed_x, + 0xbe : LDX_absolute_indexed_y, + 0xb6 : LDX_direct_page_indexed_y, + 0xac : LDY_absolute, 0xa4 : LDY_direct_page, 0xea : NOP, + 0x8d : STA_absolute, 0x85 : STA_direct_page, + 0x8f : STA_absolute_long, + 0x9f : STA_absolute_long_indexed_x, + 0x81 : STA_direct_page_indexed_x_indirect, + 0x92 : STA_direct_page_indirect, + 0x87 : STA_direct_page_indirect_long, + 0x97 : STA_direct_page_indirect_long_indexed_y, + 0x91 : STA_direct_page_indirect_indexed_y, + 0x9d : STA_absolute_indexed_x, + 0x99 : STA_absolute_indexed_y, + 0x95 : STA_direct_page_indexed_x, + 0x83 : STA_stack_relative, + 0x93 : STA_stack_relative_indirect_indexed_y, + 0x8e : STX_absolute, 0x86 : STX_direct_page, + 0x96 : STX_direct_page_indexed_y, + 0x8c : STY_absolute, 0x84 : STY_direct_page, + 0x94 : STY_direct_page_indexed_x, + 0x1a : INC_accumulator, 0xe6 : INC_direct_page, + 0xee : INC_absolute, 0xf6 : INC_direct_page_indexed_x, + 0xfe : INC_absolute_indexed_x, + 0xe8 : INX, 0xc8 : INY, + 0x3a : DEC_accumulator, 0xce : DEC_absolute, + 0xde : DEC_absolute_indexed_x, + 0xc6 : DEC_direct_page, 0xca : DEX, 0x88 : DEY, + 0xd6 : DEC_direct_page_indexed_x, + 0x9c : STZ_absolute, 0x64 : STZ_direct_page, + 0x9e : STZ_absolute_indexed_x, + 0x74 : STZ_direct_page_indexed_x, 0x9b : TXY, + 0xbb : TYX, 0xaa : TAX, 0xa8 : TAY, 0x8a : TXA, + 0x98 : TYA, 0x5b : TCD, 0x7b : TDC, 0x1b : TCS, + 0x3b : TSC, 0x4c : JMP_absolute, + 0x5c : JMP_absolute_long, + 0xdc : JMP_absolute_indirect_long, + 0x7c : JMP_absolute_indexed_x_indirect, + 0x6c : JMP_absolute_indirect, 0x80 : BRA, 0x82 : BRL, + 0xf0 : BEQ, 0xd0 : BNE, 0x90 : BCC, 0xb0 : BCS, + 0x50 : BVC, 0x70 : BVS, 0x10 : BPL, 0x30 : BMI, + 0x69 : ADC_const, 0x6d : ADC_absolute, + 0x61 : ADC_direct_page_indexed_x_indirect, + 0x6f : ADC_absolute_long, + 0x7f : ADC_absolute_long_indexed_x, + 0x65 : ADC_direct_page, 0x72 : ADC_direct_page_indirect, + 0x67 : ADC_direct_page_indirect_long, + 0x77 : ADC_direct_page_indirect_long_indexed_y, + 0x71 : ADC_direct_page_indirect_indexed_y, + 0x7d : ADC_absolute_indexed_x, + 0x79 : ADC_absolute_indexed_y, + 0x75 : ADC_direct_page_indexed_x, + 0x63 : ADC_stack_relative, + 0x73 : ADC_stack_relative_indirect_indexed_y, + 0xe9 : SBC_const, + 0xed : SBC_absolute, 0xe5 : SBC_direct_page, + 0xef : SBC_absolute_long, + 0xff : SBC_absolute_long_indexed_x, + 0xf2 : SBC_direct_page_indirect, + 0xe1 : SBC_direct_page_indexed_x_indirect, + 0xe7 : SBC_direct_page_indirect_long, + 0xf7 : SBC_direct_page_indirect_long_indexed_y, + 0xf1 : SBC_direct_page_indirect_indexed_y, + 0xfd : SBC_absolute_indexed_x, + 0xf9 : SBC_absolute_indexed_y, + 0xf5 : SBC_direct_page_indexed_x, + 0xe3 : SBC_stack_relative, + 0xf3 : SBC_stack_relative_indirect_indexed_y, + 0xc9 : CMP_const, 0xc5 : CMP_direct_page, + 0xcd : CMP_absolute, 0xd2 : CMP_direct_page_indirect, + 0xcf : CMP_absolute_long, + 0xdf : CMP_absolute_long_indexed_x, + 0xc7 : CMP_direct_page_indirect_long, + 0xd7 : CMP_direct_page_indirect_long_indexed_y, + 0xd5 : CMP_direct_page_indexed_x, + 0xc1 : CMP_direct_page_indexed_x_indirect, + 0xdd : CMP_absolute_indexed_x, + 0xd9 : CMP_absolute_indexed_y, + 0xd1 : CMP_direct_page_indirect_indexed_y, + 0xc3 : CMP_stack_relative, + 0xd3 : CMP_stack_relative_indirect_indexed_y, + 0xe0 : CPX_const, + 0xec : CPX_absolute, 0xe4 : CPX_direct_page, + 0xc0 : CPY_const, 0xcc : CPY_absolute, + 0xc4 : CPY_direct_page, 0x29 : AND_const, + 0x2d : AND_absolute, 0x25 : AND_direct_page, + 0x2f : AND_absolute_long, + 0x3f : AND_absolute_long_indexed_x, + 0x21 : AND_direct_page_indexed_x_indirect, + 0x32 : AND_direct_page_indirect, + 0x27 : AND_direct_page_indirect_long, + 0x37 : AND_direct_page_indirect_long_indexed_y, + 0x31 : AND_direct_page_indirect_indexed_y, + 0x3d : AND_absolute_indexed_x, + 0x39 : AND_absolute_indexed_y, + 0x35 : AND_direct_page_indexed_x, + 0x23 : AND_stack_relative, + 0x33 : AND_stack_relative_indirect_indexed_y, + 0x09 : ORA_const, 0x0f : ORA_absolute_long, + 0x0d : ORA_absolute, 0x05 : ORA_direct_page, + 0x1f : ORA_absolute_long_indexed_x, + 0x12 : ORA_direct_page_indirect, + 0x01 : ORA_direct_page_indexed_x_indirect, + 0x07 : ORA_direct_page_indirect_long, + 0x17 : ORA_direct_page_indirect_long_indexed_y, + 0x11 : ORA_direct_page_indirect_indexed_y, + 0x1d : ORA_absolute_indexed_x, + 0x19 : ORA_absolute_indexed_y, + 0x15 : ORA_direct_page_indexed_x, + 0x03 : ORA_stack_relative, + 0x13 : ORA_stack_relative_indirect_indexed_y, + 0x49 : EOR_const, 0x4d : EOR_absolute, + 0x4f : EOR_absolute_long, + 0x5f : EOR_absolute_long_indexed_x, + 0x45 : EOR_direct_page, + 0x52 : EOR_direct_page_indirect, + 0x41 : EOR_direct_page_indexed_x_indirect, + 0x47 : EOR_direct_page_indirect_long, + 0x57 : EOR_direct_page_indirect_long_indexed_y, + 0x51 : EOR_direct_page_indirect_indexed_y, + 0x5d : EOR_absolute_indexed_x, + 0x59 : EOR_absolute_indexed_y, + 0x55 : EOR_direct_page_indexed_x, + 0x43 : EOR_stack_relative, + 0x53 : EOR_stack_relative_indirect_indexed_y, + 0x4a : LSR_accumulator, 0x4e : LSR_absolute, + 0x46 : LSR_direct_page, 0x5e : LSR_absolute_indexed_x, + 0x56 : LSR_direct_page_indexed_x, 0x0a : ASL_accumulator, + 0x0e : ASL_absolute, 0x06 : ASL_direct_page, + 0x1e : ASL_absolute_indexed_x, + 0x16 : ASL_direct_page_indexed_x, 0x2a : ROL_accumulator, + 0x2e : ROL_absolute, 0x26 : ROL_direct_page, + 0x3e : ROL_absolute_indexed_x, + 0x36 : ROL_direct_page_indexed_x, 0x6a : ROR_accumulator, + 0x6e : ROR_absolute, 0x66 : ROR_direct_page, + 0x7e : ROR_absolute_indexed_x, + 0x76 : ROR_direct_page_indexed_x, + 0x48 : PHA, 0x68 : PLA, 0x5a : PHY, 0x7a : PLY, + 0xda : PHX, 0xfa : PLX, 0x08 : PHP, 0x28 : PLP, + 0xf4 : PEA, 0xd4 : PEI, 0x8b : PHB, 0xab : PLB, + 0x4b : PHK, 0x0b : PHD, 0x2b : PLD, 0x62 : PER, + 0x20 : JSR, 0xfc : JSR_absolute_indexed_x_indirect, + 0x60 : RTS, 0x22 : JSL, 0x6b : RTL, + 0x54 : MVN, 0x44 : MVP, 0x00 : BRK, 0x40 : RTI, + 0x02 : COP, 0x89 : BIT_const, 0x2c : BIT_absolute, + 0x24 : BIT_direct_page, + 0x3c : BIT_absolute_indexed_x, + 0x34 : BIT_direct_page_indexed_x, + 0x0c : TSB_absolute, 0x04 : TSB_direct_page, + 0x1c : TRB_absolute, 0x14 : TRB_direct_page, + 0x9a : TXS, 0xba : TSX, 0x42: WDM, 0xcb : WAI, + 0xdb : STP }; + + /** + * Load given program into memory and prepare for execution. + * raw_hex could either be a string of hex numbers or an array + * of bytes. + */ + this.load_binary = function(raw_hex, memory_location_start, bank) { + var byte_buffer = [], + i = 0, + starting_memory = memory_location_start; + + if(typeof bank === "undefined") { + bank = 0; + } + + if(typeof raw_hex === 'string') { + for(;i < raw_hex.length; i++) { + byte_buffer.push(raw_hex.charAt(i)); + if(byte_buffer.length===2) { + this.mmu.store_byte_long(memory_location_start, bank, + parseInt(byte_buffer[0]+byte_buffer[1], + 16)); + memory_location_start++; + byte_buffer = []; + } + } + } else { + for(;i < raw_hex.length; i++) { + this.mmu.store_byte_long(memory_location_start, bank, raw_hex[i]); + memory_location_start++; + } + } + this.r.pc = starting_memory; + }; + + /** + * Step through the processing of a single instruction from the current + * location of the program counter. + */ + this.step = function() { + this.instruction_translated = false; + if(this.interrupt&&(!this.p.i||(this.interrupt===this.INTERRUPT.NMI))) { + // Load the related interrupt vector in page 0xff of bank zero. + if(!this.p.e) { + this.mmu.push_byte(this.r.k); + } + this.mmu.push_byte(this.r.pc>>8); + this.mmu.push_byte(this.r.pc&0xff); + var p_byte = (this.p.n<<7)|(this.p.v<<6)|(this.p.m<<5)|(this.p.x<<4)| + (this.p.d<<3)|(this.p.i<<2)|(this.p.z<<1)|this.p.c; + this.mmu.push_byte(p_byte); + if(!this.p.e) + this.p.d = 0; + this.p.i = 1; + this.r.k = 0; + + // Look for where to jump to for the interrupt. + if(this.p.e) { + // NMI + if(this.interrupt===this.INTERRUPT.NMI) { + this.r.pc = this.mmu.read_word_long(0xfffa, 0); + // RESET + } else if(this.interrupt===this.INTERRUPT.RESET) { + this.r.pc = this.mmu.read_word_long(0xfffc, 0); + // ABORT + } else if(this.interrupt===this.INTERRUPT.ABORT) { + this.r.pc = this.mmu.read_word_long(0xfff8, 0); + // COP + } else if(this.interrupt===this.INTERRUPT.COP) { + this.r.pc = this.mmu.read_word_long(0xfff4, 0); + // IRQ or BRK + } else if(this.interrupt===this.INTERRUPT.IRQ || + this.interrupt===this.INTERRUPT.BRK) { + this.r.pc = this.mmu.read_word_long(0xfffe, 0); + } + } else { + // NMI + if(this.interrupt===this.INTERRUPT.NMI) { + this.r.pc = this.mmu.read_word_long(0xffea, 0); + // ABORT + } else if(this.interrupt===this.INTERRUPT.ABORT) { + this.r.pc = this.mmu.read_word_long(0xffe8, 0); + // COP + } else if(this.interrupt===this.INTERRUPT.COP) { + this.r.pc = this.mmu.read_word_long(0xffe4, 0); + // IRQ + } else if(this.interrupt===this.INTERRUPT.IRQ) { + this.r.pc = this.mmu.read_word_long(0xffee, 0); + // BRK + } else if(this.interrupt===this.INTERRUPT.BRK) { + this.r.pc = this.mmu.read_word_long(0xffe6, 0); + } + } + + this.interrupt = this.INTERRUPT.NO_INTERRUPT; + } + + var b = this.mmu.read_byte_long(this.r.pc, this.r.k); + this.r.pc++; + + // If we reach the end of the code then stop everything. + if(typeof b === "undefined") { + this.executing = false; + this.instruction_details = "opcode b is undefined"; + return; + } + this.instruction = b.toString(16).toUpperCase(); + var operation = this.opcode_map[b]; + this.instruction_translate = operation.toString(); + + // Minus One on PC reg because we already read the current instruction + this.instruction_history + += bytesToString(this.r.k) + "/" + bytesToString(this.r.pc-1) + ": " + + bytesToString(b); + + // bytes_required can either be a number, or a function that resolves + // to a number using some aspect of the cpu. + var bytes_required = operation.bytes_required; + if(typeof bytes_required === 'function') { + bytes_required = bytes_required(this); + } + this.instruction_details = bytes_required + " bytes read"; + + if(bytes_required===1) { + operation.execute(this); + } else { + var bytes = []; + for(var i = 1; i < bytes_required; i++) { + var bytes_read = this.mmu.read_byte_long(this.r.pc, this.r.k); + bytes.push(bytes_read); + this.instruction += " " + bytesToString(bytes_read); + this.instruction_history += " " + bytesToString(bytes_read); + this.r.pc++; + } + operation.execute(this,bytes); + } + this.instruction_history += "
"; + + if(this.waiting||this.stopped) + this.executing = false; + }; + + this.execute = function(start_address, max_cycles_per_second) { + // Default to 1MHz if no number given. + if(typeof max_cycles_per_second === "undefined") { + max_cycles_per_second = 1000000; + } + + this.r.pc = start_address; + this.timer_run(max_cycles_per_second, 1000); + }; + + this.timer_run = function(max_cycles_per_period, period) { + var start = new Date().getTime(); + this.executing = true; + while(this.executing) { + this.step(); + // If execution stopped other than because of the cycle count + if(!this.executing) { + return; + } + if(this.cycle_count>=max_cycles_per_period) { + this.executing = false; + var now = new Date().getTime(); + var wait = period - (now - start); + this.cycle_count = 0; + if(wait>0) { + setTimeout(this.timer_run.bind(this, max_cycles_per_period, period), wait); + } else { + this.timer_run(max_cycles_per_period, period); + } + } + } + }; + + this.reset = function() { + this.executing = false; + this.waiting = false; + this.stopped = false; + this.instruction = ""; + this.instruction_translate = ""; + this.instruction_details = ""; + this.instruction_history = ""; + + this.interrupt = this.INTERRUPT.NO_INTERRUPT; + this.r = { a:0, b:0, x:0, y:0, d:0, s:0xff, pc:0, dbr:0, k:0 }; + this.p = { e:1, c:0, z:0, i:0, d:0, x:0, m:0, v:0, n:0 }; + this.mmu.reset(); + this.cycle_count = 0; + }; +}; +})((typeof module !== 'undefined' && + typeof module.exports !== 'undefined')?module.exports:this); \ No newline at end of file diff --git a/src/test/scala/millfork/test/WordMathSuite.scala b/src/test/scala/millfork/test/WordMathSuite.scala index 7943199b..1e9dd61f 100644 --- a/src/test/scala/millfork/test/WordMathSuite.scala +++ b/src/test/scala/millfork/test/WordMathSuite.scala @@ -9,7 +9,7 @@ import org.scalatest.{FunSuite, Matchers} class WordMathSuite extends FunSuite with Matchers { test("Word addition") { - EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" + EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" | word output @$c000 | word a | void main () { @@ -21,7 +21,7 @@ class WordMathSuite extends FunSuite with Matchers { } test("Cast word addition") { - EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" + EmuCrossPlatformBenchmarkRun(Cpu.Sixteen/*, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp*/)(""" | byte output @$c000 | word a | void main () { @@ -34,7 +34,7 @@ class WordMathSuite extends FunSuite with Matchers { } test("Word subtraction") { - EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" + EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" | word output @$c000 | word a | void main () { @@ -46,7 +46,7 @@ class WordMathSuite extends FunSuite with Matchers { } test("Word subtraction 2") { - EmuCrossPlatformBenchmarkRun(Cpu.Cmos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" + EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Cmos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" | word output @$c000 | word a | void main () { @@ -58,7 +58,7 @@ class WordMathSuite extends FunSuite with Matchers { } test("Word subtraction 3") { - EmuCrossPlatformBenchmarkRun(Cpu.Cmos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" + EmuCrossPlatformBenchmarkRun(Cpu.Sixteen/*, Cpu.Cmos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp*/)(""" | word output @$c000 | word a | void main () { @@ -73,7 +73,7 @@ class WordMathSuite extends FunSuite with Matchers { } test("Byte-to-word addition") { - EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" + EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" | word output @$c000 | word pair | void main () { @@ -86,7 +86,7 @@ class WordMathSuite extends FunSuite with Matchers { } test("Literal addition") { - EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" + EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" | word output @$c000 | void main () { | output = 640 @@ -96,7 +96,7 @@ class WordMathSuite extends FunSuite with Matchers { } test("Array element addition") { - EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" + EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" | word output @$c000 | word pair | array b[2] @@ -113,7 +113,7 @@ class WordMathSuite extends FunSuite with Matchers { } test("nesdev.com example") { - EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" + EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" | byte output @$c000 | byte tile @$C3A6 | array map [256] @$c300 @@ -135,7 +135,7 @@ class WordMathSuite extends FunSuite with Matchers { } test("nesdev.com example 2") { - EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" + EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" | byte output @$c000 | byte tile @$C3A6 | array map [256] @$c300 @@ -153,7 +153,7 @@ class WordMathSuite extends FunSuite with Matchers { } test("hi()/lo()") { - EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" + EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" | array output [7] @$c000 | void main () { | output[0] = lo(33) @@ -185,7 +185,7 @@ class WordMathSuite extends FunSuite with Matchers { } test("Word addition 2") { - EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" + EmuCrossPlatformBenchmarkRun(Cpu.Sixteen/*, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp*/)(""" | word output @$c000 | void main () { | word v @@ -201,7 +201,7 @@ class WordMathSuite extends FunSuite with Matchers { } test("Word addition 3") { - EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" + EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" | word output @$c000 | void main () { | byte c @@ -217,7 +217,7 @@ class WordMathSuite extends FunSuite with Matchers { } test("Word addition 4") { - EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" + EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" | word output @$c000 | void main () { | word v @@ -237,7 +237,7 @@ class WordMathSuite extends FunSuite with Matchers { } test("Word bit ops 2") { - EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" + EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" | word output @$c000 | void main () { | word v @@ -253,7 +253,7 @@ class WordMathSuite extends FunSuite with Matchers { } test("Word shift") { - EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" + EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" | word output @$c000 | void main () { | word v @@ -271,7 +271,7 @@ class WordMathSuite extends FunSuite with Matchers { } test("Word shift 2") { - EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" + EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" | word output @$c000 | void main () { | output = five() @@ -286,7 +286,7 @@ class WordMathSuite extends FunSuite with Matchers { } test("Word shift 3") { - EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" + EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" | word output @$c000 | void main () { | output = five() @@ -301,7 +301,7 @@ class WordMathSuite extends FunSuite with Matchers { } test("Word shift 4") { - EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" + EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" | word output @$c000 | void main () { | output = five() @@ -316,7 +316,7 @@ class WordMathSuite extends FunSuite with Matchers { } test("Word shift 5") { - EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" + EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" | word output @$c000 | void main () { | output = five() @@ -332,7 +332,7 @@ class WordMathSuite extends FunSuite with Matchers { } test("Word multiplication 5") { - EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" + EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" | word output @$c000 | void main () { | output = alot() @@ -369,7 +369,7 @@ class WordMathSuite extends FunSuite with Matchers { } private def multiplyCase1(x: Int, y: Int): Unit = { - EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80)( + EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80)( s""" | import zp_reg | word output @$$c000 diff --git a/src/test/scala/millfork/test/emu/EmuBenchmarkRun.scala b/src/test/scala/millfork/test/emu/EmuBenchmarkRun.scala index de782249..6e7244e2 100644 --- a/src/test/scala/millfork/test/emu/EmuBenchmarkRun.scala +++ b/src/test/scala/millfork/test/emu/EmuBenchmarkRun.scala @@ -123,6 +123,9 @@ object EmuCrossPlatformBenchmarkRun { if (platforms.contains(millfork.Cpu.Cmos)) { EmuCmosBenchmarkRun.apply(source)(verifier) } + if (platforms.contains(millfork.Cpu.Sixteen)) { + EmuNative65816BenchmarkRun.apply(source)(verifier) + } if (platforms.contains(millfork.Cpu.Z80)) { EmuZ80BenchmarkRun.apply(source)(verifier) } diff --git a/src/test/scala/millfork/test/emu/EmuNative65816BenchmarkRun.scala b/src/test/scala/millfork/test/emu/EmuNative65816BenchmarkRun.scala new file mode 100644 index 00000000..fc6378ae --- /dev/null +++ b/src/test/scala/millfork/test/emu/EmuNative65816BenchmarkRun.scala @@ -0,0 +1,22 @@ +package millfork.test.emu + +import millfork.output.MemoryBank + +/** + * @author Karol Stasiak + */ +object EmuNative65816BenchmarkRun { + def apply(source:String)(verifier: MemoryBank=>Unit): Unit = { + println(f"Compiling for 65816 (unoptimized)") + val (Timings(_, t0), m0) = EmuUnoptimizedNative65816Run.apply2(source) + println(f"Compiling for 65816 (optimized)") + val (Timings(_, t2), m2) = EmuOptimizedNative65816Run.apply2(source) + println(f"Before optimization: $t0%7d") + println(f"After optimization: $t2%7d") + println(f"Gain: ${(100L*(t0-t2)/t0.toDouble).round}%7d%%") + println(f"Running 65816 unoptimized") + verifier(m0) + println(f"Running 65816 optimized") + verifier(m2) + } +} diff --git a/src/test/scala/millfork/test/emu/EmuOptimized65816Run.scala b/src/test/scala/millfork/test/emu/EmuOptimized65816Run.scala index a550d51d..5e524861 100644 --- a/src/test/scala/millfork/test/emu/EmuOptimized65816Run.scala +++ b/src/test/scala/millfork/test/emu/EmuOptimized65816Run.scala @@ -16,5 +16,17 @@ object EmuOptimized65816Run extends EmuRun( CmosOptimizations.All ++ SixteenOptimizations.All ++ OptimizationPresets.Good ++ CmosOptimizations.All ++ SixteenOptimizations.All ++ OptimizationPresets.Good) +object EmuOptimizedNative65816Run extends EmuRun( + Cpu.Sixteen, + OptimizationPresets.NodeOpt, + OptimizationPresets.AssOpt ++ + CmosOptimizations.All ++ SixteenOptimizations.All ++ OptimizationPresets.Good ++ + CmosOptimizations.All ++ SixteenOptimizations.All ++ OptimizationPresets.Good ++ + CmosOptimizations.All ++ SixteenOptimizations.All ++ OptimizationPresets.Good ++ + CmosOptimizations.All ++ SixteenOptimizations.All ++ OptimizationPresets.Good ++ + CmosOptimizations.All ++ SixteenOptimizations.All ++ OptimizationPresets.Good) { + override def native16: Boolean = true +} + diff --git a/src/test/scala/millfork/test/emu/EmuRun.scala b/src/test/scala/millfork/test/emu/EmuRun.scala index 40be3784..c2f0eebe 100644 --- a/src/test/scala/millfork/test/emu/EmuRun.scala +++ b/src/test/scala/millfork/test/emu/EmuRun.scala @@ -65,6 +65,8 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization], def softwareStack = false + def native16 = false + private val timingNmos = Array[Int]( 7, 6, 0, 8, 3, 3, 5, 5, 3, 2, 2, 2, 4, 4, 6, 6, 2, 5, 0, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7, @@ -136,6 +138,7 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization], CompilationFlag.SoftwareStack -> softwareStack, CompilationFlag.EmitCmosOpcodes -> millfork.Cpu.CmosCompatible.contains(platform.cpu), CompilationFlag.EmitEmulation65816Opcodes -> (platform.cpu == millfork.Cpu.Sixteen), + CompilationFlag.EmitNative65816Opcodes -> (platform.cpu == millfork.Cpu.Sixteen && native16), CompilationFlag.Emit65CE02Opcodes -> (platform.cpu == millfork.Cpu.CE02), CompilationFlag.EmitHudsonOpcodes -> (platform.cpu == millfork.Cpu.HuC6280), CompilationFlag.OptimizeForSize -> optimizeForSize, @@ -145,8 +148,18 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization], ), None, 4, Map(), JobContext(log, new LabelGenerator)) log.hasErrors = false log.verbosity = 999 + if (native16 && platform.cpu != millfork.Cpu.Sixteen) throw new IllegalStateException var effectiveSource = source if (!source.contains("_panic")) effectiveSource += "\n void _panic(){while(true){}}" + if (native16) effectiveSource += + """ + | + |asm void __init_16bit() @$200 { + | clc + | xce + | sep #$30 + |} + """.stripMargin log.setSource(Some(effectiveSource.lines.toIndexedSeq)) val PreprocessingResult(preprocessedSource, features, _) = Preprocessor.preprocessForTest(options, effectiveSource) val parserF = MosParser("", preprocessedSource, "", options, features) @@ -216,6 +229,8 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization], val timings = platform.cpu match { case millfork.Cpu.Cmos => runViaSymon(log, memoryBank, platform.codeAllocators("default").startAt, CpuBehavior.CMOS_6502) + case millfork.Cpu.Sixteen => + runViaJs(log, memoryBank, platform.codeAllocators("default").startAt) case millfork.Cpu.Ricoh => runViaHalfnes(log, memoryBank, platform.codeAllocators("default").startAt) case millfork.Cpu.Mos => @@ -300,4 +315,9 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization], Timings(countNmos, countCmos) -> memoryBank } + def runViaJs(log: Logger, memoryBank: MemoryBank, org: Int): (Timings, MemoryBank) = { + val (cycles, newOutput) = NashornEmulator.run(memoryBank.output, 80, 0x200) + System.arraycopy(newOutput, 0, memoryBank.output, 0, 1 << 16) + Timings(cycles, cycles) -> memoryBank + } } diff --git a/src/test/scala/millfork/test/emu/EmuUnoptimizedRun.scala b/src/test/scala/millfork/test/emu/EmuUnoptimizedRun.scala index a77a8f6e..20b5e7d4 100644 --- a/src/test/scala/millfork/test/emu/EmuUnoptimizedRun.scala +++ b/src/test/scala/millfork/test/emu/EmuUnoptimizedRun.scala @@ -12,6 +12,10 @@ object EmuUnoptimizedRicohRun extends EmuRun(Cpu.Ricoh, Nil, Nil) object EmuUnoptimizedCmosRun extends EmuRun(Cpu.Cmos, Nil, Nil) +object EmuUnoptimizedNative65816Run extends EmuRun(Cpu.Sixteen, Nil, Nil) { + override def native16: Boolean = true +} + object EmuUnoptimizedZ80Run extends EmuZ80Run(Cpu.Z80, Nil, Nil) object EmuUnoptimizedIntel8080Run extends EmuZ80Run(Cpu.Intel8080, Nil, Nil) diff --git a/src/test/scala/millfork/test/emu/NashornEmulator.scala b/src/test/scala/millfork/test/emu/NashornEmulator.scala new file mode 100644 index 00000000..c0ba9792 --- /dev/null +++ b/src/test/scala/millfork/test/emu/NashornEmulator.scala @@ -0,0 +1,111 @@ +package millfork.test.emu + +import javax.script.{Bindings, ScriptEngine, ScriptEngineManager} +import java.io.FileReader +import java.nio.file.Paths + +import jdk.nashorn.api.scripting.ScriptObjectMirror + +import scala.language.dynamics + +/** + * @author Karol Stasiak + */ +object NashornEmulator { + + lazy val engine = { + val jsFile = Paths.get(classOf[Nothing].getResource("/cpu.js").toURI).toFile + val engine: ScriptEngine = new ScriptEngineManager().getEngineByName("nashorn") + engine.eval(new FileReader(jsFile)) + engine + } + + private def newCpu(): JsObject = { + JsObject(engine.get("CPU_65816").asInstanceOf[ScriptObjectMirror]).construct() + } + + def run(memory: Array[Byte], steps: Long, start: Int): (Long, Array[Byte]) = { + val hex = memory.map(b => f"${b&0xff}%02x").mkString("") + val cpu = newCpu() + cpu.reset() + cpu.load_binary(memory.map(_ & 0xff), 0) + val memory0 = cpu.mmu.memory.!("0") + for (i <- 0 until 1 << 16) memory0.!(i.toString, memory(i) & 0xff) + cpu.r.pc = start + cpu.r.k = 0 + var count = 0L + while (count < steps && cpu.r.s.*[Number].intValue().&(0xff) > 1) { + cpu.step() + count += 1 + } + val newMemory = (0 until 1 << 16).map(i => memory0.!(i.toString).*[Number].byteValue()).toArray + val cycles = cpu.cycle_count.*[Number].longValue() + cycles -> newMemory + } + + def main(args: Array[String]): Unit = { + val cpu = newCpu() + cpu.reset() + cpu.load_binary("00", 0x200) + cpu.execute(0x200) + println(cpu.r.pc) + } +} + +case class JsObject(private val mirror: Any) extends Dynamic { + + + def !(index: JsObject): JsObject = + mirror match { + case x: ScriptObjectMirror => JsObject(x.get(index.mirror)) + case _ => throw new IllegalArgumentException(s"Accessing field $index of $getUnderlyingClass") + } + + def !(index: Any): JsObject = + mirror match { + case x: ScriptObjectMirror => JsObject(x.get(index)) + case _ => throw new IllegalArgumentException(s"Accessing field $index of $getUnderlyingClass") + } + + def !(index: String, value:Any): Unit = + mirror match { + case x: ScriptObjectMirror => x.setMember(index, value) + case _ => throw new IllegalArgumentException(s"Setting field $index of $getUnderlyingClass") + } + + def selectDynamic(name: String): JsObject = + mirror match { + case x: ScriptObjectMirror => JsObject(x.get(name).asInstanceOf[Any] match { + case y: Double => if (y.isValidInt) y.toInt else y + case y => y + }) + case _ => throw new IllegalArgumentException(s"Accessing field $name of $getUnderlyingClass") + } + + private def getUnderlyingClass = { + if (mirror.asInstanceOf[AnyRef] eq null) "null" else mirror.getClass.getSimpleName + } + + def updateDynamic(name: String)(value: Any): Unit = + mirror match { + case x: ScriptObjectMirror => x.setMember(name, value) + case _ => throw new IllegalArgumentException(s"Setting field $name of $getUnderlyingClass") + } + + def applyDynamic(name: String)(params: Any*): JsObject = + mirror match { + case x: ScriptObjectMirror => JsObject(x.callMember(name, params.toArray)) + case _ => throw new IllegalArgumentException(s"Accessing field $name of $getUnderlyingClass") + } + + def construct(params: Any*): JsObject = + mirror match { + case x: ScriptObjectMirror => JsObject(x.newObject(params.toArray)) + case _ => throw new IllegalArgumentException(s"Using $getUnderlyingClass as a constructor") + } + + @inline + def *[T]: T = mirror.asInstanceOf[T] + + override def toString: String = String.valueOf(mirror) +}