From 399e18b21559e71627cac9f3859c0ae57348a928 Mon Sep 17 00:00:00 2001 From: Leah Amelia Chen Date: Fri, 12 Sep 2025 22:58:58 +0200 Subject: [PATCH] cpu: add stack operations Change-Id: tpnmozqyuktvunwoztyknrkkzyttqprl --- src/Cpu.zig | 71 ++++++++++++++++++++++++++- src/tests/cpu.zig | 1 + src/tests/cpu/assembler.zig | 34 +++++++++++++ src/tests/cpu/stack.zig | 96 +++++++++++++++++++++++++++++++++++++ 4 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 src/tests/cpu/stack.zig diff --git a/src/Cpu.zig b/src/Cpu.zig index 4ad1f1e..10b540b 100644 --- a/src/Cpu.zig +++ b/src/Cpu.zig @@ -112,6 +112,7 @@ pub fn tick(self: *Cpu, pins: *zesty.Pins, last_pins: zesty.Pins) void { 0x01 => if (self.zpXInd(pins)) |_| self.ora(pins, v), // ORA (zp,X) 0x05 => if (self.zp(pins)) |_| self.ora(pins, v), // ORA zp 0x06 => if (self.zp(pins)) |_| self.asl(pins, .mem), // ASL zp + 0x08 => self.php(pins), // PHP 0x09 => if (self.imm(pins)) |_| self.ora(pins, v), // ORA # 0x0a => if (self.imm(pins)) |_| self.asl(pins, .acc), // ASL A 0x0d => if (self.abs(pins)) |_| self.ora(pins, v), // ORA abs @@ -131,6 +132,7 @@ pub fn tick(self: *Cpu, pins: *zesty.Pins, last_pins: zesty.Pins) void { 0x24 => if (self.zp(pins)) |_| self.bit(pins), // BIT zp 0x25 => if (self.zp(pins)) |_| self._and(pins, v), // AND zp 0x26 => if (self.zp(pins)) |_| self.rol(pins, .mem), // ROL zp + 0x28 => self.plp(pins), // PLP 0x29 => if (self.imm(pins)) |_| self._and(pins, v), // AND # 0x2a => if (self.imm(pins)) |_| self.rol(pins, .acc), // ROL A 0x2c => if (self.abs(pins)) |_| self.bit(pins), // BIT abs @@ -150,6 +152,7 @@ pub fn tick(self: *Cpu, pins: *zesty.Pins, last_pins: zesty.Pins) void { 0x41 => if (self.zpXInd(pins)) |_| self.eor(pins, v), // EOR (zp,X) 0x45 => if (self.zp(pins)) |_| self.eor(pins, v), // EOR zp 0x46 => if (self.zp(pins)) |_| self.lsr(pins, .mem), // LSR zp + 0x48 => self.pha(pins), // PHA 0x49 => if (self.imm(pins)) |_| self.eor(pins, v), // EOR # 0x4a => if (self.imm(pins)) |_| self.lsr(pins, .acc), // LSR A 0x4c => self.jmp(pins), // JMP abs @@ -169,6 +172,7 @@ pub fn tick(self: *Cpu, pins: *zesty.Pins, last_pins: zesty.Pins) void { 0x61 => if (self.zpXInd(pins)) |_| self.adc(pins, v), // ADC (zp,X) 0x65 => if (self.zp(pins)) |_| self.adc(pins, v), // ADC zp 0x66 => if (self.zp(pins)) |_| self.ror(pins, .mem), // ROR zp + 0x68 => self.pla(pins), // PLA 0x69 => if (self.imm(pins)) |_| self.adc(pins, v), // ADC # 0x6a => if (self.imm(pins)) |_| self.ror(pins, .acc), // ROR A 0x6c => self.jmpInd(pins), // JMP (ind) @@ -425,8 +429,7 @@ inline fn fetchAt(self: *Cpu, pins: *zesty.Pins, pc: u16) void { } //------------------------------------------------------ -// Opcodes: Load/Store & Arithmetic -const Dst = enum { acc, mem }; +// Opcodes: Load/Store inline fn ld(self: *Cpu, pins: *zesty.Pins, to: *u8, from: u8) void { to.* = from; @@ -438,6 +441,70 @@ inline fn st(self: *Cpu, pins: *zesty.Pins, from: u8) void { pins.cpu_rw = .write; self.fetch(pins); } +inline fn push(self: *Cpu, pins: *zesty.Pins, v: u8) void { + switch (self.cycle) { + // Dummy read + 0 => pins.cpu_addr = self.pc, + 1 => { + self.hilo = .stack(self.sp); + pins.cpu_addr = self.hilo.addr(); + pins.cpu_data = v; + pins.cpu_rw = .write; + self.sp -%= 1; + }, + else => self.fetch(pins), + } +} +inline fn pull(self: *Cpu, pins: *zesty.Pins) ?u8 { + switch (self.cycle) { + // Dummy read + 0 => pins.cpu_addr = self.pc, + 1 => { + self.hilo = .stack(self.sp); + pins.cpu_addr = self.hilo.addr(); + self.sp +%= 1; + }, + 2 => { + self.hilo = .stack(self.sp); + pins.cpu_addr = self.hilo.addr(); + }, + else => { + self.fetch(pins); + return pins.cpu_data; + }, + } + return null; +} +inline fn pha(self: *Cpu, pins: *zesty.Pins) void { + self.push(pins, self.a); +} +inline fn php(self: *Cpu, pins: *zesty.Pins) void { + var status = self.status; + // BRK is always set to true here + status.brk = true; + self.push(pins, status.toByte()); +} +inline fn pla(self: *Cpu, pins: *zesty.Pins) void { + self.a = self.pull(pins) orelse return; + self.setNZ(self.a); +} +inline fn plp(self: *Cpu, pins: *zesty.Pins) void { + const status: Status = .from(self.pull(pins) orelse return); + self.status = .{ + .negative = status.negative, + .overflow = status.overflow, + .brk = self.status.brk, // Do not inherit BRK. + .decimal = status.decimal, + .irq_disabled = status.irq_disabled, + .zero = status.zero, + .carry = status.carry, + }; +} + +//------------------------------------------------------ +// Opcodes: Arithmetic + +const Dst = enum { acc, mem }; inline fn ora(self: *Cpu, pins: *zesty.Pins, v: u8) void { self.a |= v; self.setNZ(self.a); diff --git a/src/tests/cpu.zig b/src/tests/cpu.zig index 95b771b..72d4618 100644 --- a/src/tests/cpu.zig +++ b/src/tests/cpu.zig @@ -9,6 +9,7 @@ test { _ = @import("cpu/addressing.zig"); _ = @import("cpu/alu.zig"); _ = @import("cpu/control_flow.zig"); + _ = @import("cpu/stack.zig"); } //------------------------------------------------------ diff --git a/src/tests/cpu/assembler.zig b/src/tests/cpu/assembler.zig index 9ee6110..243c415 100644 --- a/src/tests/cpu/assembler.zig +++ b/src/tests/cpu/assembler.zig @@ -73,6 +73,11 @@ pub const Opcode = enum { stx, sty, + php, + plp, + pha, + pla, + ora, @"and", eor, @@ -382,6 +387,35 @@ pub const Opcode = enum { .implied => 0xea, else => null, }, + .pha => switch (opr) { + .implied => 0x48, + else => null, + }, + .pla => switch (opr) { + .implied => 0x68, + else => null, + }, + .php => switch (opr) { + .implied => 0x08, + else => null, + }, + .plp => switch (opr) { + .implied => 0x28, + else => null, + }, + .jmp => switch (opr) { + .absolute => 0x4c, + .indirect => 0x6c, + else => null, + }, + .jsr => switch (opr) { + .absolute => 0x20, + else => null, + }, + .rts => switch (opr) { + .implied => 0x60, + else => null, + }, }; } }; diff --git a/src/tests/cpu/stack.zig b/src/tests/cpu/stack.zig new file mode 100644 index 0000000..4ba28da --- /dev/null +++ b/src/tests/cpu/stack.zig @@ -0,0 +1,96 @@ +//! Stack-related tests. +const std = @import("std"); +const cpu = @import("../cpu.zig"); + +test "PHA/PLA" { + var z = cpu.testZes(.{ .rom = &.{ + .{ + 0x8000, cpu.assemble( + \\LDA #$89 + \\PHA + \\LDA #0 + \\PLA + ), + }, + } }); + cpu.run(&z); // Setup A register + + try std.testing.expectEqual(0xfd, z.cpu.sp); + cpu.run(&z); // PHA + try std.testing.expectEqual(0xfc, z.cpu.sp); + try std.testing.expectEqual(0x8003, z.cpu.pc); + try std.testing.expectEqual(0x89, z.ram[0x1fd]); + try std.testing.expect(z.cpu.status.negative); + try std.testing.expect(!z.cpu.status.zero); + + cpu.run(&z); // Reset A register + try std.testing.expectEqual(0x8005, z.cpu.pc); + try std.testing.expectEqual(0x00, z.cpu.a); + try std.testing.expect(!z.cpu.status.negative); + try std.testing.expect(z.cpu.status.zero); + + cpu.run(&z); // PLA + try std.testing.expectEqual(0x8006, z.cpu.pc); + try std.testing.expectEqual(0x89, z.cpu.a); + try std.testing.expectEqual(0xfd, z.cpu.sp); + try std.testing.expect(z.cpu.status.negative); + try std.testing.expect(!z.cpu.status.zero); +} + +test "PHP/PLP" { + var z = cpu.testZes(.{ .rom = &.{ + .{ + 0x8000, cpu.assemble( + \\CLI + \\SED + \\LDA #0 + \\PHP + \\ + \\SEI + \\CLD + \\LDA #$ff + \\PLP + ), + }, + } }); + + // Prepare flags + cpu.run(&z); // Clear I + cpu.run(&z); // Set D + cpu.run(&z); // Set Z + try std.testing.expect(!z.cpu.status.negative); + try std.testing.expect(z.cpu.status.zero); + try std.testing.expect(z.cpu.status.decimal); + try std.testing.expect(!z.cpu.status.irq_disabled); + + try std.testing.expectEqual(0xfd, z.cpu.sp); + cpu.run(&z); // PHP + try std.testing.expectEqual(0xfc, z.cpu.sp); + + try std.testing.expectEqual(0x8005, z.cpu.pc); + try std.testing.expectEqual(0b00111010, z.ram[0x1fd]); + + // Reset flags + cpu.run(&z); // Set I + cpu.run(&z); // Clear D + cpu.run(&z); // Clear Z; Set N + try std.testing.expect(z.cpu.status.negative); + try std.testing.expect(!z.cpu.status.zero); + try std.testing.expect(!z.cpu.status.decimal); + try std.testing.expect(z.cpu.status.irq_disabled); + + try std.testing.expectEqual(0xfc, z.cpu.sp); + cpu.run(&z); // PLP + try std.testing.expectEqual(0xfd, z.cpu.sp); + + try std.testing.expectEqual(0x800a, z.cpu.pc); + + // Check if flags are restored + try std.testing.expect(!z.cpu.status.negative); + try std.testing.expect(z.cpu.status.zero); + try std.testing.expect(z.cpu.status.decimal); + try std.testing.expect(!z.cpu.status.irq_disabled); + + // BRK should not be restored. + try std.testing.expect(!z.cpu.status.brk); +} -- 2.43.0