diff --git a/js/README.md b/js/README.md new file mode 100644 index 0000000..53c6422 --- /dev/null +++ b/js/README.md @@ -0,0 +1,58 @@ +### Opcodes progress by Instructions: + +
+ Opcodes for LDA - Load Accumulator with Memory + +| Dec | Opcode | Instruction | Addressing Mode | Cycles | Implemented | +|:---:|:------:|:----------------:|:---------------:|:------:|:-----------:| +| 169 | 0xA9 | LDA #immediate | Immediate | 2 | ✅️ | +| 165 | 0xA5 | LDA zeropage | Zeropage | 3 | ✅️ | +| 181 | 0xB5 | LDA zeropage,X | Zeropage,X | 4 | ✅️ | +| 173 | 0xAD | LDA absolute | Absolute | 4 | ✅ | +| 189 | 0xBD | LDA absolute,X | Absolute,X | 4 (+1) | ✅ | +| 185 | 0xB9 | LDA absolute,Y | Absolute,Y | 4 (+1) | ✅ | +| 161 | 0xA1 | LDA (indirect,X) | Indirect,X | 6 | ✅ | +| 177 | 0xB1 | LDA (indirect),Y | Indirect,Y | 5 (+1) | ✅ | + +
+ +
+ Opcodes for LDX - Load Index Register X From Memory + +| Decimal | Opcode | Instruction | Addressing Mode | Cycles | Implemented | +|:-------:|:------:|:--------------:|:---------------:|:------:|:-----------:| +| 169 | 0xA2 | LDX #immediate | Immediate | 2 | ✅ | +| 181 | 0xA6 | LDX zeropage | Zeropage | 3 | | +| 197 | 0xB6 | LDX zeropage,Y | Zeropage,Y | 4 | | +| 173 | 0xAE | LDX absolute | Absolute | 4 | | +| 189 | 0xBE | LDX absolute,Y | Absolute,Y | 4 (+1) | | + +
+ + + +### Opcodes (23/151) + +| Dec | Opcode | Instruction | Status | +|:---:|:------:|:--------------:|:------:| +| 0 | 0x00 | BRK | ✅ | +| 9 | 0x09 | ORA #immediate | ✅ | +| 29 | 0x29 | AND #immediate | ✅ | +| 41 | 0x49 | EOR #immediate | ✅ | +| 84 | 0x84 | STY zeropage | ✅ | +| 85 | 0x85 | STA zeropage | ✅ | +| 86 | 0x86 | STX zeropage | ✅ | +| 88 | 0x88 | DEY | ✅ | +| 90 | 0x90 | BCC | ✅ | +| 98 | 0x98 | TYA | ✅ | +| 105 | 0x69 | ADC #immediate | ✅ | +| 138 | 0x8A | TXA | ✅ | +| 160 | 0xA0 | LDY #immediate | ✅ | +| 162 | 0xA2 | LDX #immediate | ✅ | +| 168 | 0xA8 | TAY | ✅ | +| 170 | 0xAA | TAX | ✅ | +| 200 | 0xC8 | INY | ✅ | +| 202 | 0xCA | DEX | ✅ | +| 232 | 0xE8 | INX | ✅ | +| 233 | 0xE9 | SBC #immediate | ✅ | +| 234 | 0xEA | NOP | ✅ | diff --git a/js/opcodes.js b/js/opcodes.js index 1aa0301..9f99c96 100644 --- a/js/opcodes.js +++ b/js/opcodes.js @@ -303,7 +303,21 @@ const OPCODES = { cpu.zeroFlag = cpu.Y; } }, - 0xA1: "", + 0xA1: { + name: "LDA (indirect,X)", // Load Accumulator with Memory (Indirect Indexed Addressing with X) + t: 6, // Cycles required + code: 0xA1, + run: (cpu) => { + const baseAddress = (cpu.fetch() + cpu.X) & 0xFF; // 0x00-0xFF (zeropage) + const lowByte = cpu.memory.readByte(baseAddress); + const highByte = cpu.memory.readByte((baseAddress + 1) & 0xFF); // wrap around in zeropage + const address = (highByte << 8) | lowByte; // 16-bit address + + cpu.ACC = cpu.memory.readByte(address); + cpu.negativeFlag = cpu.ACC; + cpu.zeroFlag = cpu.ACC; + }, + }, 0xA2: { name: "LDX #immediate", // Load Index X with Memory t: 2, @@ -316,7 +330,17 @@ const OPCODES = { }, 0xA3: "", // lax("(d,x)") // illegal 0xA4: "", - 0xA5: "", + 0xA5: { + name: "LDA zeropage", // Load Accumulator with Memory from Zeropage + t: 3, + code: 0xA5, + run: (cpu) => { + const address = cpu.fetch(); + cpu.ACC = cpu.memory.readByte(address); + cpu.negativeFlag = cpu.ACC; + cpu.zeroFlag = cpu.ACC; + } + }, 0xA6: "", 0xA7: "", // illegal 0xA8: { @@ -351,23 +375,100 @@ const OPCODES = { }, 0xAB: "", // lax("#i") // illegal 0xAC: "", - 0xAD: "", + 0xAD: { + name: "LDA absolute", // Load Accumulator Absolute + t: 4, + code: 0xAD, + run: (cpu) => { + const lowByte = cpu.fetch(); + const highByte = cpu.fetch(); + const address = (highByte << 8) | lowByte; + cpu.ACC = cpu.memory.readByte(address); + cpu.negativeFlag = cpu.ACC; + cpu.zeroFlag = cpu.ACC; + } + }, 0xAE: "", 0xAF: "", // lax("a") // illegal 0xB0: "", // bcs("*+d") - 0xB1: "", // lda("(d),y") + 0xB1: { + name: "LDA (indirect),Y", // Load Accumulator with Memory (Indirect) and Indexed Addressing with Y + t: 5, // 5 (+1) + code: 0xB1, + run: (cpu) => { + const baseAddress = cpu.fetch(); + const lowByte = cpu.memory.readByte(baseAddress); + const highByte = cpu.memory.readByte((baseAddress + 1) & 0xFF); // wrap around in zeropage + const address = (highByte << 8) | lowByte + cpu.Y; // 16-bit address + + // Check if the address crosses a page boundary + if ((address & 0xFF00) !== ((highByte << 8) & 0xFF00)) { + cpu.cycles += 1; // Add an extra cycle if a page boundary is crossed + } + + + cpu.ACC = cpu.memory.readByte(address); + cpu.negativeFlag = cpu.ACC; + cpu.zeroFlag = cpu.ACC; + }, + }, 0xB2: "", // stp_implied() // illegal 0xB3: "", // lax("(d),y") // illegal 0xB4: "", - 0xB5: "", + 0xB5: { + name: "LDA zeropage,X", // Load Accumulator with X-Indexed Zero Page + t: 4, + code: 0xB5, + run: (cpu) => { + const baseAddress = cpu.fetch(); + const address = (baseAddress + cpu.X) & 0xFF; + cpu.ACC = cpu.memory.readByte(address); + cpu.negativeFlag = cpu.ACC; + cpu.zeroFlag = cpu.ACC; + } + }, 0xB6: "", 0xB7: "", // lax("d,y") // illegal 0xB8: "", // clv_implied() - 0xB9: "", // lda("a,y") + 0xB9: { + name: "LDA absolute,Y", // Load Accumulator with Memory (Absolute Addressing with Y offset) + t: 4, // 4 (+1) + code: 0xB9, + run: (cpu) => { + const lowByte = cpu.fetch(); + const highByte = cpu.fetch(); + const address = ((highByte << 8) | lowByte) + cpu.Y; // Combine bytes and add Y register value + // Check if address crosses a page boundary + if ((address & 0xFF00) !== ((highByte << 8) & 0xFF00)) { + cpu.cycles += 1; // Add an extra cycle if a page boundary is crossed + } + + cpu.ACC = cpu.memory.readByte(address); + cpu.negativeFlag = cpu.ACC; + cpu.zeroFlag = cpu.ACC; + } + }, 0xBA: "", // tsx_implied() 0xBB: "", // las("a,y") // illegal 0xBC: "", // ldy("a,x") - 0xBD: "", // lda("a,x") + 0xBD: { + name: "LDA absolute,X", // Load Accumulator with Memory (Absolute Addressing with X offset) + t: 4, // 4 (+1) + code: 0xBD, + run: (cpu) => { + const lowByte = cpu.fetch(); + const highByte = cpu.fetch(); + const address = ((highByte << 8) | lowByte) + cpu.X; // Combine bytes and add X register value + // Check if address crosses a page boundary + if ((address & 0xFF00) !== ((highByte << 8) & 0xFF00)) { + cpu.cycles += 1; // Add an extra cycle if a page boundary is crossed + } + + cpu.ACC = cpu.memory.readByte(address); + cpu.negativeFlag = cpu.ACC; + cpu.zeroFlag = cpu.ACC; + } + }, 0xBE: "", // ldx("a,y") 0xBF: "", // lax("a,y") // illegal 0xC0: "", // cpy("#i") diff --git a/js/opcodes.md b/js/opcodes.md deleted file mode 100644 index dd751bc..0000000 --- a/js/opcodes.md +++ /dev/null @@ -1,45 +0,0 @@ -### Opcodes progress (22/151) - -| Dec | Opcode | Instruction | Status | -|:---:|:------:|:--------------:|:------:| -| 0 | 0x00 | BRK | ✅ | -| 9 | 0x09 | ORA #immediate | ✅ | -| 29 | 0x29 | AND #immediate | ✅ | -| 41 | 0x49 | EOR #immediate | ✅ | -| 84 | 0x84 | STY zeropage | ✅ | -| 85 | 0x85 | STA zeropage | ✅ | -| 86 | 0x86 | STX zeropage | ✅ | -| 88 | 0x88 | DEY | ✅ | -| 90 | 0x90 | BCC | ✅ | -| 98 | 0x98 | TYA | ✅ | -| 105 | 0x69 | ADC #immediate | ✅ | -| 138 | 0x8A | TXA | ✅ | -| 160 | 0xA0 | LDY #immediate | ✅ | -| 162 | 0xA2 | LDX #immediate | ✅ | -| 168 | 0xA8 | TAY | ✅ | -| 169 | 0xA9 | LDA #immediate | ✅ | -| 170 | 0xAA | TAX | ✅ | -| 200 | 0xC8 | INY | ✅ | -| 202 | 0xCA | DEX | ✅ | -| 232 | 0xE8 | INX | ✅ | -| 233 | 0xE9 | SBC #immediate | ✅ | -| 234 | 0xEA | NOP | ✅ | - - -### Opcodes in progress: - -
- Opcodes for LDA - -| Dec | Opcode | Instruction | Mode | Cycles | Status | -|:-----:|:--------:|:------------------:|:------------:|:--------:|----------| -| 169 | 0xA9 | LDA #immediate | Immediate | 2 | ✅️ | -| 165 | 0xA5 | LDA zeropage | Zeropage | 3 | ❌ | -| 181 | 0xB5 | LDA zeropage,X | Zeropage,X | 4 | ❌ | -| 173 | 0xAD | LDA absolute | Absolute | 4 | ❌ | -| 189 | 0xBD | LDA absolute,X | Absolute,X | 4 (+1) | ❌ | -| 185 | 0xB9 | LDA absolute,Y | Absolute,Y | 4 (+1) | ❌ | -| 161 | 0xA1 | LDA (indirect,X) | Indirect,X | 6 | ❌ | -| 177 | 0xB1 | LDA (indirect),Y | Indirect,Y | 5 (+1) | ❌ | - -
\ No newline at end of file diff --git a/tests/opcodes.test.js b/tests/opcodes.test.js index 5bdb872..d302bb0 100644 --- a/tests/opcodes.test.js +++ b/tests/opcodes.test.js @@ -1,4 +1,4 @@ -import {describe, expect, test, beforeEach} from "@jest/globals"; +import {describe, expect, test, beforeEach, jest} from "@jest/globals"; import Memory from "../js/memory"; import CPU from "../js/cpu"; @@ -237,6 +237,73 @@ describe("OPCODES:", () => { expect(cpu.zeroFlag).toBe(true); }); }); + + describe("0xA1 -> LDA (indirect,X)", () => { + beforeEach(() => { + cpu.memory.readByte = jest.fn(); + }); + + test("should load the correct value into the accumulator with indirect X addressing and update flags", () => { + const instruction = OPCODES[0xA1]; + const baseAddress = 0x10; + const indirectAddressLow = 0x34; + const indirectAddressHigh = 0x12; + const finalAddress = 0x1234; + const expectedValue = 0x80; + cpu.X = 0x04; + + cpu.fetch = jest.fn() + .mockReturnValueOnce(baseAddress) + .mockReturnValueOnce(indirectAddressLow) + .mockReturnValueOnce(indirectAddressHigh); + + cpu.memory.readByte + .mockReturnValueOnce(indirectAddressLow) + .mockReturnValueOnce(indirectAddressHigh) + .mockReturnValueOnce(expectedValue); + + cpu.decode(instruction.code); + + expect(instruction.code).toBe(0xA1); + expect(cpu.memory.readByte).toHaveBeenCalledWith((baseAddress + cpu.X) & 0xFF); + expect(cpu.memory.readByte).toHaveBeenCalledWith(((baseAddress + cpu.X + 1) & 0xFF)); + expect(cpu.memory.readByte).toHaveBeenCalledWith(finalAddress); + + expect(cpu.ACC).toBe(expectedValue); + expect(cpu.negativeFlag).toBe(true); + expect(cpu.zeroFlag).toBe(false); + }); + + test("should correctly update zero flag when loaded value is 0x00", () => { + const instruction = OPCODES[0xA1]; + const baseAddress = 0x20; + const indirectAddressLow = 0x00; + const indirectAddressHigh = 0x30; + const finalAddress = 0x3000; + const expectedValue = 0x00; + cpu.X = 0x02; + + cpu.fetch = jest.fn() + .mockReturnValueOnce(baseAddress) + .mockReturnValueOnce(indirectAddressLow) + .mockReturnValueOnce(indirectAddressHigh); + + cpu.memory.readByte.mockReturnValueOnce(indirectAddressLow) + .mockReturnValueOnce(indirectAddressHigh) + .mockReturnValueOnce(expectedValue); + + cpu.decode(instruction.code); + + expect(instruction.code).toBe(0xA1); + expect(cpu.memory.readByte).toHaveBeenCalledWith((baseAddress + cpu.X) & 0xFF); + expect(cpu.memory.readByte).toHaveBeenCalledWith(((baseAddress + cpu.X + 1) & 0xFF)); + expect(cpu.memory.readByte).toHaveBeenCalledWith(finalAddress); + + expect(cpu.ACC).toBe(expectedValue); + expect(cpu.negativeFlag).toBe(false); + expect(cpu.zeroFlag).toBe(true); + }); + }); describe("0xA2 -> LDX #immediate", () => { test("decode", () => { @@ -249,6 +316,39 @@ describe("OPCODES:", () => { expect(cpu.zeroFlag).toBe(true); }); }); + + describe("0xA5 -> LDA zeropage", () => { + beforeEach(() => { + cpu.memory.readByte = jest.fn() + }); + + test("should load the correct value into the accumulator and update flags", () => { + const instruction = OPCODES[0xA5]; + + cpu.memory.readByte.mockReturnValue(0x80); + cpu.fetch = jest.fn(() => 0x42); + + cpu.decode(instruction.code); + + expect(instruction.code).toBe(0xA5); + expect(cpu.ACC).toBe(0x80); + expect(cpu.negativeFlag).toBe(true); + expect(cpu.zeroFlag).toBe(false); + }); + + test("should update zero flag correctly when loaded value is 0x00", () => { + const instruction = OPCODES[0xA5]; + + cpu.memory.readByte.mockReturnValue(0x00); + cpu.fetch = jest.fn(() => 0x42); + cpu.decode(instruction.code); + + expect(instruction.code).toBe(0xA5); + expect(cpu.ACC).toBe(0x00); + expect(cpu.negativeFlag).toBe(false); + expect(cpu.zeroFlag).toBe(true); + }); + }); describe("0xA8 -> TAY", () => { test("decode", () => { @@ -288,6 +388,257 @@ describe("OPCODES:", () => { }); }); + describe("0xAD -> LDA absolute", () => { + beforeEach(() => { + cpu.memory.readByte = jest.fn() + }); + + test("should load the correct value into the accumulator from the absolute address and update flags", () => { + const instruction = OPCODES[0xAD]; + // 16bit address 0x1234 and value 0x80 + const lowByte = 0x34; + const highByte = 0x12; + const address = 0x1234; + cpu.memory.readByte.mockReturnValue(0x80); + + cpu.fetch = jest.fn() + .mockReturnValueOnce(lowByte) + .mockReturnValueOnce(highByte); + + cpu.decode(instruction.code); + + expect(instruction.code).toBe(0xAD); + expect(cpu.memory.readByte).toHaveBeenCalledWith(address); + expect(cpu.ACC).toBe(0x80); + expect(cpu.negativeFlag).toBe(true); + expect(cpu.zeroFlag).toBe(false); + }); + + test("should correctly update zero flag when loaded value is 0x00", () => { + const instruction = OPCODES[0xAD]; + + const lowByte = 0x34; + const highByte = 0x12; + const address = 0x1234; + cpu.memory.readByte.mockReturnValue(0x00); + + cpu.fetch = jest.fn() + .mockReturnValueOnce(lowByte) + .mockReturnValueOnce(highByte); + + cpu.decode(instruction.code); + + expect(instruction.code).toBe(0xAD); + expect(cpu.memory.readByte).toHaveBeenCalledWith(address); + expect(cpu.ACC).toBe(0x00); + expect(cpu.negativeFlag).toBe(false); + expect(cpu.zeroFlag).toBe(true); + }); + }); + + describe("0xB1 -> LDA (indirect),Y", () => { + beforeEach(() => { + cpu.memory.readByte = jest.fn(); + }); + + test("should load the correct value into the accumulator with indirect Y addressing and update flags", () => { + const instruction = OPCODES[0xB1]; + cpu.Y = 0x04; + const baseAddress = 0x20; + const lowByte = 0x34; + const highByte = 0x12; + const finalAddress = 0x1234 + cpu.Y; + const expectedValue = 0x80; + + cpu.fetch = jest.fn().mockReturnValueOnce(baseAddress); + cpu.memory.readByte + .mockReturnValueOnce(lowByte) + .mockReturnValueOnce(highByte) + .mockReturnValueOnce(expectedValue); + + cpu.decode(instruction.code); + + expect(instruction.code).toBe(0xB1); + expect(cpu.memory.readByte).toHaveBeenCalledWith(baseAddress); + expect(cpu.memory.readByte).toHaveBeenCalledWith((baseAddress + 1) & 0xFF); + expect(cpu.memory.readByte).toHaveBeenCalledWith(finalAddress); + expect(cpu.ACC).toBe(expectedValue); + expect(cpu.negativeFlag).toBe(true); + expect(cpu.zeroFlag).toBe(false); + }); + + test("should correctly handle page boundary crossing", () => { + const instruction = OPCODES[0xB1]; + cpu.Y = 0xFF; + const baseAddress = 0x20; + const lowByte = 0xFF; + const highByte = 0x12; + const finalAddress = 0x12FF + cpu.Y; + const expectedValue = 0x80; + + cpu.fetch = jest.fn().mockReturnValueOnce(baseAddress); + cpu.memory.readByte + .mockReturnValueOnce(lowByte) + .mockReturnValueOnce(highByte) + .mockReturnValueOnce(expectedValue); + + cpu.decode(instruction.code); + + expect(instruction.code).toBe(0xB1); + expect(cpu.memory.readByte).toHaveBeenCalledWith(baseAddress); + expect(cpu.memory.readByte).toHaveBeenCalledWith((baseAddress + 1) & 0xFF); + expect(cpu.memory.readByte).toHaveBeenCalledWith(finalAddress); + expect(cpu.ACC).toBe(expectedValue); + expect(cpu.cycles).toBe(6); + expect(cpu.negativeFlag).toBe(true); + expect(cpu.zeroFlag).toBe(false); + }); + }); + + describe("0xB5 -> LDA zeropage,X", () => { + beforeEach(() => { + cpu.memory.readByte = jest.fn() + }); + + test("should load the correct value into the accumulator with X offset and update flags", () => { + const instruction = OPCODES[0xB5]; + cpu.X = 0x10; // value of address + const baseAddress = 0x42; + const expectedAddress = (baseAddress + cpu.X) & 0xFF; + cpu.memory.readByte.mockReturnValue(0x80); + cpu.fetch = jest.fn(() => baseAddress); + cpu.decode(instruction.code); + + expect(instruction.code).toBe(0xB5); + expect(cpu.memory.readByte).toHaveBeenCalledWith(expectedAddress); + expect(cpu.ACC).toBe(0x80); + expect(cpu.negativeFlag).toBe(true); + expect(cpu.zeroFlag).toBe(false); + }); + + test("should correctly update zero flag when loaded value is 0x00", () => { + const instruction = OPCODES[0xB5]; + cpu.X = 0x10; + const baseAddress = 0x42; + const expectedAddress = (baseAddress + cpu.X) & 0xFF; + cpu.memory.readByte.mockReturnValue(0x00); + cpu.fetch = jest.fn(() => baseAddress); + + cpu.decode(instruction.code); + + expect(instruction.code).toBe(0xB5); + expect(cpu.memory.readByte).toHaveBeenCalledWith(expectedAddress); + expect(cpu.ACC).toBe(0x00); + expect(cpu.negativeFlag).toBe(false); + expect(cpu.zeroFlag).toBe(true); + }); + }); + + describe("0xBD -> LDA absolute,X", () => { + beforeEach(() => { + cpu.memory.readByte = jest.fn(); + }); + + test("should load the correct value into the accumulator with X offset and update flags", () => { + const instruction = OPCODES[0xBD]; + const lowByte = 0x34; + const highByte = 0x12; + const baseAddress = 0x1234; + cpu.X = 0x10; + const expectedAddress = baseAddress + cpu.X; + + cpu.memory.readByte.mockReturnValue(0x80); + cpu.fetch = jest.fn() + .mockReturnValueOnce(lowByte) + .mockReturnValueOnce(highByte); + + cpu.decode(instruction.code); + + expect(instruction.code).toBe(0xBD); + expect(cpu.memory.readByte).toHaveBeenCalledWith(expectedAddress); + expect(cpu.ACC).toBe(0x80); + expect(cpu.negativeFlag).toBe(true); + expect(cpu.zeroFlag).toBe(false); + }); + + test("should correctly handle page boundary crossing", () => { + const instruction = OPCODES[0xBD]; + const lowByte = 0xFF; + const highByte = 0x12; + const baseAddress = 0x12FF; + cpu.X = 0xFF; + const expectedAddress = baseAddress + cpu.X; + + cpu.memory.readByte.mockReturnValue(0x80); + + cpu.fetch = jest.fn() + .mockReturnValueOnce(lowByte) // Повертає молодший байт адреси + .mockReturnValueOnce(highByte); // Повертає старший байт адреси + + cpu.decode(instruction.code); + + expect(instruction.code).toBe(0xBD); + expect(cpu.memory.readByte).toHaveBeenCalledWith(expectedAddress); + expect(cpu.ACC).toBe(0x80); + expect(cpu.cycles).toBe(5); // 4 cycles + 1 page boundary + expect(cpu.negativeFlag).toBe(true); + expect(cpu.zeroFlag).toBe(false); + }); + }); + + describe("0xB9 -> LDA absolute,Y", () => { + // opcode should work the same as 0xBD, but using Y register + beforeEach(() => { + cpu.memory.readByte = jest.fn(); + }); + + test("should load the correct value into the accumulator with X offset and update flags", () => { + const instruction = OPCODES[0xB9]; + const lowByte = 0x34; + const highByte = 0x12; + const baseAddress = 0x1234; + cpu.Y = 0x10; + const expectedAddress = baseAddress + cpu.Y; + + cpu.memory.readByte.mockReturnValue(0x80); + cpu.fetch = jest.fn() + .mockReturnValueOnce(lowByte) + .mockReturnValueOnce(highByte); + + cpu.decode(instruction.code); + + expect(instruction.code).toBe(0xB9); + expect(cpu.memory.readByte).toHaveBeenCalledWith(expectedAddress); + expect(cpu.ACC).toBe(0x80); + expect(cpu.negativeFlag).toBe(true); + expect(cpu.zeroFlag).toBe(false); + }); + + test("should correctly handle page boundary crossing", () => { + const instruction = OPCODES[0xB9]; + const lowByte = 0xFF; + const highByte = 0x12; + const baseAddress = 0x12FF; + cpu.Y = 0xFF; + const expectedAddress = baseAddress + cpu.Y; + + cpu.memory.readByte.mockReturnValue(0x80); + + cpu.fetch = jest.fn() + .mockReturnValueOnce(lowByte) // Повертає молодший байт адреси + .mockReturnValueOnce(highByte); // Повертає старший байт адреси + + cpu.decode(instruction.code); + + expect(instruction.code).toBe(0xB9); + expect(cpu.memory.readByte).toHaveBeenCalledWith(expectedAddress); + expect(cpu.ACC).toBe(0x80); + expect(cpu.cycles).toBe(5); // 4 cycles + 1 page boundary + expect(cpu.negativeFlag).toBe(true); + expect(cpu.zeroFlag).toBe(false); + }); + }); + describe("0xC8 -> INY", () => { test("decode", () => { const instruction = OPCODES[0xC8];