From c941db6990485b925e142ea98727429959bdf2b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20K=C3=A4ll=C3=A9n?= Date: Mon, 12 Aug 2024 13:02:42 +0200 Subject: [PATCH] Enhancement: more 6502 love Added support for more 6502 instructions. Fixed bugs in 6502 emulator. --- src/Arch/Mos6502/Assembler.cs | 114 ++++- src/Arch/Mos6502/Mos6502Emulator.cs | 144 +++++-- src/UnitTests/Arch/Mos6502/EmulatorTests.cs | 434 +++++++++++++++++++- 3 files changed, 650 insertions(+), 42 deletions(-) diff --git a/src/Arch/Mos6502/Assembler.cs b/src/Arch/Mos6502/Assembler.cs index ec4d5d534c..6f6b41105f 100644 --- a/src/Arch/Mos6502/Assembler.cs +++ b/src/Arch/Mos6502/Assembler.cs @@ -18,17 +18,15 @@ */ #endregion -using System; -using System.Collections.Generic; -using System.ComponentModel.Design; -using System.IO; -using System.Text; using Reko.Core; using Reko.Core.Assemblers; using Reko.Core.Expressions; using Reko.Core.Loading; using Reko.Core.Memory; using Reko.Core.Types; +using System; +using System.Collections.Generic; +using System.IO; namespace Reko.Arch.Mos6502 { @@ -38,7 +36,6 @@ public class Assembler : IAssembler { private static readonly Dictionary instrOpcodes; - private readonly IServiceProvider services; private readonly Mos6502Architecture arch; private readonly Address addrBase; private readonly IEmitter m; @@ -47,7 +44,6 @@ public class Assembler : IAssembler public Assembler(IServiceProvider services, Mos6502Architecture arch, Address addrBase, List symbols) { - this.services = services; this.arch = arch; this.addrBase = addrBase; this.symbols = symbols; @@ -55,6 +51,19 @@ public Assembler(IServiceProvider services, Mos6502Architecture arch, Address ad this.symtab = new SymbolTable(); } + public Assembler(Program program, Address addrStart) + { + this.addrBase = addrStart; + this.symbols = new(); + this.symtab = new SymbolTable(); + this.arch = (Mos6502Architecture) program.Architecture; + if (!program.SegmentMap.TryFindSegment(addrStart, out var segmentToMutate)) + throw new InvalidOperationException($"Address {addrStart} is not a valid location in the program."); + var offset = addrStart - segmentToMutate.MemoryArea.BaseAddress; + this.m = new Emitter(segmentToMutate.MemoryArea); + m.Position = (int) offset; + } + public Program GetImage() { var mem = new ByteMemoryArea(addrBase, m.GetBytes()); @@ -177,11 +186,21 @@ public void Clc() m.EmitByte(0x18); } + public void Cld() + { + m.EmitByte(0xD8); + } + public void Cmp(ParsedOperand op) { EmitOpcodeOperand(Mnemonic.cmp, op.Operand); } + public void Cpx(ParsedOperand op) + { + EmitOpcodeOperand(Mnemonic.cpx, op.Operand); + } + public void Cpy(ParsedOperand op) { EmitOpcodeOperand(Mnemonic.cpy, op.Operand); @@ -197,6 +216,11 @@ public void Dey() m.EmitByte(0x88); } + public void Eor(ParsedOperand op) + { + EmitOpcodeOperand(Mnemonic.eor, op.Operand); + } + public void Inc(ParsedOperand op) { EmitOpcodeOperand(Mnemonic.inc, op.Operand); @@ -248,6 +272,34 @@ public void Ldy(ParsedOperand op) EmitOpcodeOperand(Mnemonic.ldy, op.Operand); } + public void Lsr() + { + m.EmitByte(0x4A); + } + + public void Lsr(ParsedOperand op) + { + EmitOpcodeOperand(Mnemonic.lsr, op.Operand); + } + + public void Org(ushort uAddr) + { + var newOffset = uAddr - (int) addrBase.Offset; + if (newOffset < m.Position) + throw new InvalidOperationException("org directive has a lower address than the current address."); + m.EmitBytes(0, newOffset - m.Position); + } + + public void Ora(ParsedOperand op) + { + EmitOpcodeOperand(Mnemonic.ora, op.Operand); + } + + public void Pha() + { + m.EmitByte(0x48); + } + public void Rol(RegisterStorage reg) { if (reg != Registers.a) @@ -260,6 +312,16 @@ public void Rol(ParsedOperand op) EmitOpcodeOperand(Mnemonic.rol, op.Operand); } + public void Ror() + { + m.EmitByte(0x6A); + } + + public void Rti() + { + m.EmitByte(0x40); + } + public void Rts() { m.EmitByte(0x60); @@ -270,6 +332,16 @@ public void Sbc(ParsedOperand op) EmitOpcodeOperand(Mnemonic.sbc, op.Operand); } + public void Sec() + { + m.EmitByte(0x38); + } + + public void Sed() + { + m.EmitByte(0xF8); + } + public void Sei() { m.EmitByte(0x78); @@ -400,7 +472,6 @@ public int AssembleFragmentAt(Program program, Address addr, string asm) throw new NotImplementedException(); } - public class ParsedOperand { public Operand Operand; @@ -470,6 +541,17 @@ static Assembler() Zp = 0xC4, Abs = 0xCC, }}, + { Mnemonic.eor, new InstrOpcodes + { + Imm = 0x49, + Abs = 0x4D, + AbsX = 0x5D, + AbsY = 0x59, + Zp = 0x45, + ZpX = 0x55, + IndX = 0x41, + IndY = 0x51, + }}, { Mnemonic.inc, new InstrOpcodes{ Zp = 0xE6, ZpX = 0xF6, @@ -500,6 +582,22 @@ static Assembler() Abs = 0xAC, AbsX = 0xBC, } }, + { Mnemonic.lsr, new InstrOpcodes { + Abs = 0x4E, + AbsX = 0x5E, + Zp = 0x46, + ZpX = 0x56, + } }, + { Mnemonic.ora, new InstrOpcodes { + Imm = 0x09, + Abs = 0x0D, + AbsX = 0x1D, + AbsY = 0x19, + Zp = 0x05, + ZpX = 0x15, + IndX = 0x01, + IndY = 0x11, + } }, { Mnemonic.rol, new InstrOpcodes { Zp = 0x26, diff --git a/src/Arch/Mos6502/Mos6502Emulator.cs b/src/Arch/Mos6502/Mos6502Emulator.cs index 713d74d5cd..5ce789a9ed 100644 --- a/src/Arch/Mos6502/Mos6502Emulator.cs +++ b/src/Arch/Mos6502/Mos6502Emulator.cs @@ -41,7 +41,7 @@ public class Mos6502Emulator : EmulatorBase public const byte Vmask = 0x40; public const byte Nmask = 0x80; - private static readonly TraceSwitch trace = new TraceSwitch(nameof(Mos6502Emulator), "Trace execution of 6502 Emulator") { Level = TraceLevel.Error }; + private static readonly TraceSwitch trace = new TraceSwitch(nameof(Mos6502Emulator), "Trace execution of 6502 Emulator") { Level = TraceLevel.Verbose }; private static readonly RegisterStorage[] dumpRegs = new[] { @@ -76,10 +76,7 @@ public override Address InstructionPointer set { UpdatePc(value); - if (!map.TryFindSegment(value, out ImageSegment? segment)) - throw new AccessViolationException(); - var rdr = arch.CreateImageReader(segment.MemoryArea, value); - dasm = new Disassembler(arch, rdr).GetEnumerator(); + dasm = null; } } @@ -90,28 +87,33 @@ private void UpdatePc(Address value) protected override void Run() { - while (IsRunning) + while (IsRunning && DisassembleNextInstruction() is Instruction instr) { - Instruction? instr = null; - while (IsRunning && dasm!.MoveNext()) - { - TraceCurrentInstruction(); - instr = dasm.Current; - var pc = instr.Address; - UpdatePc(pc); - ulong linPc = pc.ToLinear(); - if (!TestForBreakpoint(linPc)) - break; - Execute(instr); - } - if (instr is null) - break; - var addr = instr.Address + instr.Length; - if (!map.TryFindSegment(addr, out _)) + TraceCurrentInstruction(); + var pc = instr.Address; + UpdatePc(pc); + ulong linPc = pc.ToLinear(); + if (!TestForBreakpoint(linPc)) return; - this.InstructionPointer = addr; + Execute(instr); + } + } + + private Instruction? DisassembleNextInstruction() + { + if (dasm is null) + { + var uAddr = InstructionPointer; + if (!map.TryFindSegment(uAddr, out ImageSegment? segment)) + throw new AccessViolationException(); + var rdr = arch.CreateImageReader(segment.MemoryArea, uAddr); + dasm = new Disassembler(arch, rdr).GetEnumerator(); } - } + if (dasm.MoveNext()) + return dasm.Current; + else + return null; + } public override ulong ReadRegister(RegisterStorage reg) { @@ -194,7 +196,8 @@ private void Execute(Instruction instr) case Mnemonic.asl: b = (byte) Read(op); SetFlag((b & 0x80) != 0, Cmask); - Write(op, (byte)(b << 1)); + b = (byte)(b << 1); + Write(op, b); NZ(b); return; case Mnemonic.bcc: if ((regs[Registers.p.Number] & Cmask) == 0) Jump(op); return; @@ -202,9 +205,12 @@ private void Execute(Instruction instr) case Mnemonic.beq: if ((regs[Registers.p.Number] & Zmask) != 0) Jump(op); return; case Mnemonic.bne: if ((regs[Registers.p.Number] & Zmask) == 0) Jump(op); return; case Mnemonic.bpl: if ((regs[Registers.p.Number] & Nmask) == 0) Jump(op); return; + case Mnemonic.brk: Brk(); return; case Mnemonic.clc: regs[Registers.p.Number] &= unchecked((ushort)~Cmask); return; + case Mnemonic.cld: regs[Registers.p.Number] &= unchecked((ushort)~Dmask); return; case Mnemonic.cli: regs[Registers.p.Number] &= unchecked((ushort)~Imask); return; case Mnemonic.cmp: Cmp(Registers.a, op);return; + case Mnemonic.cpx: Cmp(Registers.x, op);return; case Mnemonic.cpy: Cmp(Registers.y, op);return; case Mnemonic.dec: b = (byte) (Read(op) - 1); @@ -213,6 +219,7 @@ private void Execute(Instruction instr) return; case Mnemonic.dex: NZ(regs[Registers.x.Number] = (byte)(ReadRegister(Registers.x)-1)); return; case Mnemonic.dey: NZ(regs[Registers.y.Number] = (byte)(ReadRegister(Registers.y)-1)); return; + case Mnemonic.eor: Eor(op); return; case Mnemonic.inc: b = (byte) (Read(op) + 1); Write(op, b); @@ -225,20 +232,33 @@ private void Execute(Instruction instr) case Mnemonic.lda: NZ(regs[Registers.a.Number] = Read(op)); return; case Mnemonic.ldx: NZ(regs[Registers.x.Number] = Read(op)); return; case Mnemonic.ldy: NZ(regs[Registers.y.Number] = Read(op)); return; + case Mnemonic.lsr: Lsr(op); return; case Mnemonic.nop: return; + case Mnemonic.ora: Ora(op); return; case Mnemonic.pha: Push((byte)regs[Registers.a.Number]); return; + case Mnemonic.php: Push((byte)regs[Registers.p.Number]); return; + case Mnemonic.phx: Push((byte)regs[Registers.x.Number]); return; + case Mnemonic.phy: Push((byte)regs[Registers.y.Number]); return; case Mnemonic.pla: Pop(Registers.a); return; + case Mnemonic.plp: Pop(Registers.p); return; + case Mnemonic.plx: Pop(Registers.x); return; + case Mnemonic.ply: Pop(Registers.y); return; case Mnemonic.rol: Rol(op); return; + case Mnemonic.ror: Ror(op); return; + case Mnemonic.rti: Rti(); return; case Mnemonic.rts: Rts(); return; case Mnemonic.sbc: Sbc(op); return; case Mnemonic.sec: regs[Registers.p.Number] |= Cmask; return; + case Mnemonic.sed: regs[Registers.p.Number] |= Dmask; return; case Mnemonic.sei: regs[Registers.p.Number] |= Imask; return; case Mnemonic.sta: Write(op, regs[Registers.a.Number]); return; case Mnemonic.stx: Write(op, regs[Registers.x.Number]); return; case Mnemonic.sty: Write(op, regs[Registers.y.Number]); return; case Mnemonic.tax: TransferNZ(Registers.a, Registers.x); return; case Mnemonic.tay: TransferNZ(Registers.a, Registers.y); return; + case Mnemonic.tsx: TransferNZ(Registers.s, Registers.x); return; case Mnemonic.txa: TransferNZ(Registers.x, Registers.a); return; + case Mnemonic.txs: TransferNZ(Registers.x, Registers.s); return; case Mnemonic.tya: TransferNZ(Registers.y, Registers.a); return; } } @@ -272,6 +292,16 @@ private void Sbc(MachineOperand op) regs[Registers.p.Number] = p; } + private void Brk() + { + var brkVector = ReadLeUInt16(0xFFFE); + var pcNext = this.InstructionPointer.ToUInt16() + 2; + Push((byte) (pcNext >> 8)); + Push((byte) pcNext); + Push((byte) ReadRegister(Registers.p)); + InstructionPointer = Address.Ptr16(brkVector); + } + private void Cmp(RegisterStorage reg, MachineOperand op) { var a = (byte)regs[reg.Number]; @@ -284,6 +314,15 @@ private void Cmp(RegisterStorage reg, MachineOperand op) regs[Registers.p.Number] = p; } + private void Eor(MachineOperand op) + { + var a = (byte) regs[Registers.a.Number]; + var b = Read(op); + var r = (byte) (a ^ b); + NZ(r); + regs[Registers.a.Number] = r; + } + private void Jump(MachineOperand op) { InstructionPointer = Address.Ptr16(((Operand) op).Offset!.ToUInt16()); @@ -297,17 +336,62 @@ private void Jsr(MachineOperand op) InstructionPointer = Address.Ptr16(((Operand) op).Offset!.ToUInt16()); } + private void Lsr(MachineOperand op) + { + var p = regs[Registers.p.Number]; + byte b = (byte)Read(op); + p = SetFlag((b & 1) != 0, Cmask, p); + var r = (byte) (b >> 1); + Write(op, r); + p = NZ(r, p); + regs[Registers.p.Number] = p; + Write(op, r); + } + + private void Ora(MachineOperand op) + { + var a = (byte) regs[Registers.a.Number]; + var b = Read(op); + var r = (byte) (a | b); + NZ(r); + regs[Registers.a.Number] = r; + } + private void Rol(MachineOperand op) { var p = regs[Registers.p.Number]; var oldC = p & Cmask; byte b = (byte) Read(op); p = SetFlag((b & 0x80) != 0, Cmask, p); - Write(op, (byte) ((b << 1) | oldC)); - p = NZ(b, p); + byte r = (byte) ((b << 1) | oldC); + Write(op, r); + p = NZ(r, p); + regs[Registers.p.Number] = p; + } + + private void Ror(MachineOperand op) + { + var p = regs[Registers.p.Number]; + var oldC = p & Cmask; + byte b = (byte) Read(op); + p = SetFlag((b & 1) != 0, Cmask, p); + byte r = (byte) ((b >> 1) | (oldC << 7)); + Write(op, r); + p = NZ(r, p); regs[Registers.p.Number] = p; return; } + + private void Rti() + { + var p = Pop(); + var lsb = Pop(); + var msb = Pop(); + var pcNext = (ushort) (((msb << 8) | lsb)); + regs[Registers.p.Number] = p; + InstructionPointer = Address.Ptr16(pcNext); + } + private void Rts() { var lsb = Pop(); @@ -363,6 +447,12 @@ private ushort Read(MachineOperand mop) case AddressMode.ZeroPage: ea = op.Offset!.ToByte(); break; + case AddressMode.ZeroPageX: + ea = (ushort)((op.Offset!.ToByte() + regs[Registers.x.Number]) & 0xFF); + break; + case AddressMode.ZeroPageY: + ea = (ushort)((op.Offset!.ToByte() + regs[Registers.y.Number]) & 0xFF); + break; case AddressMode.Absolute: ea = op.Offset!.ToUInt16(); break; diff --git a/src/UnitTests/Arch/Mos6502/EmulatorTests.cs b/src/UnitTests/Arch/Mos6502/EmulatorTests.cs index 4b91a038a1..60fcda51c2 100644 --- a/src/UnitTests/Arch/Mos6502/EmulatorTests.cs +++ b/src/UnitTests/Arch/Mos6502/EmulatorTests.cs @@ -27,9 +27,6 @@ using System; using System.Collections.Generic; using System.ComponentModel.Design; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Reko.UnitTests.Arch.Mos6502 { @@ -74,6 +71,50 @@ private void Given_Code(Action p) emu.ExceptionRaised += (sender, e) => { throw e.Exception; }; } + private void Given_Code(ushort uAddr, Action p) + { + var program = new Program(); + var addrBase = Address.Ptr16(uAddr); + program.Architecture = arch; + program.SegmentMap = new SegmentMap( + new ImageSegment("zeroPage", new ByteMemoryArea(Address.Ptr16(0), new byte[256]), AccessMode.ReadWriteExecute), + new ImageSegment("Low memory", new ByteMemoryArea(Address.Ptr16(0x100), new byte[512]), AccessMode.ReadWriteExecute)); + var asm = new Assembler(program, addrBase); + p(asm); + + var envEmu = new DefaultPlatformEmulator(); + + emu = (Mos6502Emulator) arch.CreateEmulator(program.SegmentMap, envEmu); + emu.InstructionPointer = addrBase; + emu.WriteRegister(Registers.s, 0xFF); + emu.ExceptionRaised += (sender, e) => { throw e.Exception; }; + } + + + private bool DecimalFlag() + { + var p = emu.ReadRegister(Registers.p); + return (p & Registers.D.FlagGroupBits) != 0; + } + + private bool CarryFlag() + { + var p = emu.ReadRegister(Registers.p); + return (p & Registers.C.FlagGroupBits) != 0; + } + + private bool NegativeFlag() + { + var p = emu.ReadRegister(Registers.p); + return (p & Registers.N.FlagGroupBits) != 0; + } + + private bool ZeroFlag() + { + var p = emu.ReadRegister(Registers.p); + return (p & Registers.Z.FlagGroupBits) != 0; + } + [Test] public void Emu6502_adc_carry() { @@ -89,6 +130,7 @@ public void Emu6502_adc_carry() Assert.AreEqual(0x02, emu.ReadRegister(Registers.a)); Assert.AreEqual(0x00, emu.ReadRegister(Registers.p)); } + [Test] public void Emu6502_adc_overflow() { @@ -105,6 +147,31 @@ public void Emu6502_adc_overflow() Assert.AreEqual(0xC0, emu.ReadRegister(Registers.p)); } + [TestCase(254, 1, false, false)] + [TestCase(254, 1, true, true)] + [TestCase(253, 1, true, false)] + [TestCase(255, 1, false, true)] + [TestCase(255, 1, true, true)] + public void Emu6502_adc_C( + byte accumlatorIntialValue, + byte amountToAdd, + bool setCarryFlag, + bool expectedValue) + { + Given_Code(m => + { + if (setCarryFlag) + m.Sec(); + else + m.Clc(); + m.Lda(m.i8(accumlatorIntialValue)); + m.Adc(m.i8(amountToAdd)); + }); + emu.Start(); + + Assert.AreEqual(expectedValue, CarryFlag()); + } + [Test] public void Emu6502_asl_acc() { @@ -118,7 +185,7 @@ public void Emu6502_asl_acc() emu.Start(); Assert.AreEqual(0x54, emu.ReadRegister(Registers.a)); - Assert.AreEqual(0x81, emu.ReadRegister(Registers.p)); + Assert.AreEqual(0x1, emu.ReadRegister(Registers.p)); } [Test] @@ -151,7 +218,24 @@ public void Emu6502_asl_zp() emu.TryReadByte(0xF0, out var b); Assert.AreEqual(0x54, b); - Assert.AreEqual(0x81, emu.ReadRegister(Registers.p)); + Assert.AreEqual(0x01, emu.ReadRegister(Registers.p)); + } + + [TestCase(0x80, 0x00, true)] + public void Emu6502_asl_Z_flag(byte beforeValue, byte afterValue, bool zeroFlag) + { + Given_Code(m => + { + m.Asl(m.zp(0xF0)); + }); + emu.WriteByte(0xF0, beforeValue); + emu.WriteRegister(Registers.p, 0); + + emu.Start(); + + emu.TryReadByte(0xF0, out var b); + Assert.AreEqual(afterValue, b); + Assert.AreEqual(zeroFlag, ZeroFlag()); } [Test] @@ -168,6 +252,20 @@ public void Emu6502_clc() Assert.AreEqual(0x00, emu.ReadRegister(Registers.p)); } + [Test] + public void Mos6502_sed_cld() + { + Given_Code(m => + { + m.Sed(); + m.Cld(); + }); + emu.Start(); + + Assert.IsFalse(DecimalFlag()); + } + + [Test] public void Emu6502_cmp() { @@ -182,6 +280,22 @@ public void Emu6502_cmp() Assert.AreEqual(NC, emu.ReadRegister(Registers.p)); } + [TestCase(0xFE, 0xFF, true)] + [TestCase(0x81, 0x1, true)] + [TestCase(0x81, 0x2, false)] + [TestCase(0x79, 0x1, false)] + [TestCase(0x00, 0x1, true)] + public void Enum6502_cpx_N_flag(byte xValue, byte memoryValue, bool expectedResult) + { + Given_Code(m => + { + m.Ldx(m.i8(xValue)); + m.Cpx(m.i8(memoryValue)); + }); + emu.Start(); + + Assert.AreEqual(expectedResult, NegativeFlag()); + } [Test] public void Emu6502_cpy() @@ -198,6 +312,23 @@ public void Emu6502_cpy() Assert.AreEqual(C, emu.ReadRegister(Registers.p)); } + [TestCase(0xFE, 0xFF, true)] + [TestCase(0x81, 0x1, true)] + [TestCase(0x81, 0x2, false)] + [TestCase(0x79, 0x1, false)] + [TestCase(0x00, 0x1, true)] + public void Emu6502_cpy_N_flag(byte yValue, byte memoryValue, bool expectedResult) + { + Given_Code(m => + { + m.Ldy(m.i8(yValue)); + m.Cpy(m.i8(memoryValue)); + }); + emu.Start(); + + Assert.AreEqual(expectedResult, NegativeFlag()); + } + [Test] public void Emu6502_inc_zp() { @@ -304,7 +435,6 @@ public void Emu6502_sta_indirect_indexed() Assert.AreEqual(0x42, b); } - [Test] public void Emu6502_dey() { @@ -318,6 +448,37 @@ public void Emu6502_dey() Assert.AreEqual(Mos6502Emulator.Nmask, emu.ReadRegister(Registers.p)); } + [TestCase(0x49, 0x00, true)] + [TestCase(0x4A, 0x03, false)] + public void Emu6502_eor_z_flag(byte beforeValue, byte afterValue, bool zeroFlag) + { + Given_Code(m => + { + m.Eor(m.i8(0x49)); // 49 eor + }); + emu.WriteRegister(Registers.a, beforeValue); + emu.Start(); + + Assert.AreEqual(afterValue, (byte) emu.ReadRegister(Registers.a)); + Assert.AreEqual(zeroFlag, ZeroFlag()); + } + + [TestCase(0xFF, 0xFF, false)] + [TestCase(0x80, 0x7F, true)] + [TestCase(0x40, 0x3F, false)] + [TestCase(0xFF, 0x7F, true)] + public void Emu6502_eor_n_flag(byte accumulatorValue, byte memoryValue, bool expectedResult) + { + Given_Code(m => + { + m.Lda(m.i8(accumulatorValue)); + m.Eor(m.i8(memoryValue)); // 49 eor + }); + emu.Start(); + + Assert.AreEqual(expectedResult, NegativeFlag()); + } + [Test] public void Emu6502_inx() { @@ -332,6 +493,21 @@ public void Emu6502_inx() Assert.AreEqual(Mos6502Emulator.Zmask, emu.ReadRegister(Registers.p)); } + [TestCase(0x00, false)] + [TestCase(0xFF, true)] + [TestCase(0xFE, false)] + public void Emu6502_inx_Z_flag(byte initialXRegister, bool expectedResult) + { + Given_Code(m => + { + m.Ldx(m.i8(initialXRegister)); + m.Inx(); + }); + emu.Start(); + + Assert.AreEqual(expectedResult, ZeroFlag()); + } + [Test] public void Emu6502_bne() { @@ -407,6 +583,85 @@ public void Emu6502_jsr() Assert.AreEqual(0x0804, emu.ReadLeUInt16(0x1FE)); } + [TestCase(0x1, true)] + [TestCase(0x2, false)] + public void Emu6502_lsr_C_flag(byte accumulatorValue, bool expectedResult) + { + Given_Code(m => + { + m.Lda(m.i8(accumulatorValue)); + m.Lsr(); + }); + emu.Start(); + + Assert.AreEqual(expectedResult, CarryFlag()); + } + + [TestCase(0xFF, false, false)] + [TestCase(0xFE, false, false)] + [TestCase(0xFF, true, false)] + [TestCase(0x00, true, false)] + public void Emu6502_lsr_N_flag(byte accumulatorValue, bool carryBitSet, bool expectedValue) + { + Given_Code(m => + { + if (carryBitSet) + m.Sec(); + else + m.Clc(); + m.Lda(m.i8(accumulatorValue)); + m.Lsr(); + }); + emu.Start(); + + Assert.AreEqual(expectedValue, NegativeFlag()); + } + + [TestCase(0x7F, 0x80, true)] + [TestCase(0x79, 0x00, false)] + [TestCase(0xFF, 0xFF, true)] + public void Emu6502_ora_N_flag(byte accumulatorValue, byte immValue, bool expectedResult) + { + Given_Code(m => + { + m.Lda(m.i8(accumulatorValue)); + m.Ora(m.i8(immValue)); + }); + emu.Start(); + + Assert.AreEqual(expectedResult, NegativeFlag()); + } + + [TestCase(0x00, 0x00, 0x00)] + [TestCase(0xFF, 0xFF, 0xFF)] + [TestCase(0x55, 0xAA, 0xFF)] + [TestCase(0xAA, 0x55, 0xFF)] + public void Emu6502_ora(byte accumulatorValue, byte memoryValue, byte expectedResult) + { + Given_Code(m => + { + m.Lda(m.i8(accumulatorValue)); + m.Ora(m.i8(memoryValue)); + }); + emu.Start(); + + Assert.AreEqual(expectedResult, (byte) emu.ReadRegister(Registers.a)); + } + + [TestCase(0x00, 0x00, true)] + [TestCase(0xFF, 0xFF, false)] + [TestCase(0x00, 0x01, false)] + public void Emu6502_ora_Z_flag(byte accumulatorValue, byte memoryValue, bool expectedResult) + { + Given_Code(m => + { + m.Lda(m.i8(accumulatorValue)); + m.Ora(m.i8(memoryValue)); + }); + emu.Start(); + Assert.AreEqual(expectedResult, ZeroFlag()); + } + [Test] public void Emu6502_rol() { @@ -421,7 +676,146 @@ public void Emu6502_rol() emu.TryReadByte(0x42, out byte b); Assert.AreEqual(0x55, b); - Assert.AreEqual(0x81, emu.ReadRegister(Registers.p)); + Assert.AreEqual(0x01, emu.ReadRegister(Registers.p)); + } + + [TestCase(0x40, true)] + [TestCase(0x3F, false)] + [TestCase(0x80, false)] + public void Emu6502_rol_N_flag(byte accumulatorValue, bool expectedValue) + { + Given_Code(m => + { + m.Lda(m.i8(accumulatorValue)); + m.Rol(Registers.a); + }); + emu.Start(); + + Assert.AreEqual(expectedValue, NegativeFlag()); + } + + [Test] + public void Emu6502_pha() + { + Given_Code(m => + { + m.Lda(m.i8(3)); + m.Pha(); + }); + emu.Start(); + + var sp = 0x101 + emu.ReadRegister(Registers.s); + + Assert.IsTrue(emu.TryReadByte(sp, out byte b)); + + Assert.AreEqual(0x03, b); + } + + [TestCase(0x80, true)] + [TestCase(0x7F, false)] + public void Emu6502_rol_C_flag(byte accumulatorValue, bool expectedResult) + { + Given_Code(m => + { + m.Lda(m.i8(accumulatorValue)); + m.Rol(Registers.a); + }); + emu.Start(); + + Assert.AreEqual(expectedResult, CarryFlag()); + } + + [TestCase(true, false)] + [TestCase(false, true)] + public void Emu6502_rol_Z_flag(bool carryFlagSet, bool expectedResult) + { + Given_Code(m => + { + if (carryFlagSet) + m.Sec(); + else + m.Clc(); + m.Rol(Registers.a); + }); + emu.Start(); + + Assert.AreEqual(expectedResult, ZeroFlag()); + } + + [TestCase(0xFF, false, false)] + [TestCase(0xFE, false, false)] + [TestCase(0xFF, true, true)] + [TestCase(0x00, true, true)] + public void Emu6502_ror_N_flag(byte accumulatorValue, bool carryBitSet, bool expectedValue) + { + Given_Code(m => + { + if (carryBitSet) + m.Sec(); + else + m.Clc(); + m.Lda(m.i8(accumulatorValue)); + m.Ror(); + }); + emu.Start(); + + Assert.AreEqual(expectedValue, NegativeFlag()); + } + + [TestCase(0x00, false, true)] + [TestCase(0x00, true, false)] + [TestCase(0x01, false, true)] + [TestCase(0x01, true, false)] + public void Emu6502_ror_Z_flag(byte accumulatorValue, bool carryBitSet, bool expectedResult) + { + Given_Code(m => + { + if (carryBitSet) + m.Sec(); + else + m.Clc(); + m.Lda(m.i8(accumulatorValue)); + m.Ror(); + }); + emu.Start(); + + Assert.AreEqual(expectedResult, ZeroFlag()); + } + + [TestCase(0x01, true)] + [TestCase(0x02, false)] + public void Emu6502_ror_C_flag(byte accumulatorValue, bool expectedResult) + { + Given_Code(m => + { + m.Lda(m.i8(accumulatorValue)); + m.Ror(); + }); + emu.Start(); + + Assert.AreEqual(expectedResult, CarryFlag()); + } + + + [Test] + public void Emu6502_rti_C_flag() + { + Given_Code(m => + { + m.Lda(m.i8(0x08)); + m.Pha(); + m.Lda(m.i8(0x10)); + m.Pha(); + m.Lda(m.i8(1)); + m.Pha(); + m.Rti(); + m.Org(0x810); + m.Nop(); + }); + emu.Start(); + + //Accounting for the Offest in memory + Assert.IsTrue(CarryFlag()); } [Test] @@ -463,6 +857,32 @@ public void Emu6502_sbc() Assert.AreEqual(0x02, emu.ReadRegister(Registers.a)); } + [TestCase(0x0, 0x0, false, 0xFF)] + [TestCase(0x0, 0x0, true, 0x00)] + [TestCase(0x50, 0xf0, false, 0x5F)] + [TestCase(0x50, 0xB0, true, 0xA0)] + [TestCase(0xff, 0xff, false, 0xff)] + [TestCase(0xff, 0xff, true, 0x00)] + [TestCase(0xff, 0x80, false, 0x7e)] + [TestCase(0xff, 0x80, true, 0x7f)] + [TestCase(0x80, 0xff, false, 0x80)] + [TestCase(0x80, 0xff, true, 0x81)] + public void Emu6502_sbc_2(byte accumlatorIntialValue, byte amountToSubtract, bool carryFlagSet, byte expectedValue) + { + Given_Code(m => + { + if (carryFlagSet) + m.Sec(); + else + m.Clc(); + m.Lda(m.i8(accumlatorIntialValue)); + m.Sbc(m.i8(amountToSubtract)); + }); + emu.Start(); + + Assert.AreEqual(expectedValue, emu.ReadRegister(Registers.a)); + } + [Test] public void Emu6502_sei() {