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];