From 14dcf504cd7f6c57a5188e921840817660ae50d1 Mon Sep 17 00:00:00 2001 From: Leah Amelia Chen Date: Fri, 12 Sep 2025 23:30:20 +0200 Subject: [PATCH] cpu: jumps and subroutines Change-Id: xxlzyuskntqoqtyqtyuwypvpnkpzqvpv --- src/Cpu.zig | 130 +++++++++++++++++++++++++++++++++ src/tests/cpu/assembler.zig | 8 +- src/tests/cpu/control_flow.zig | 93 ++++++++++++++++++++++- 3 files changed, 225 insertions(+), 6 deletions(-) diff --git a/src/Cpu.zig b/src/Cpu.zig index 10b540b..6112c13 100644 --- a/src/Cpu.zig +++ b/src/Cpu.zig @@ -771,6 +771,7 @@ inline fn brk(self: *Cpu, pins: *zesty.Pins) void { inline fn rti(self: *Cpu, pins: *zesty.Pins) void { switch (self.cycle) { 0 => { + // Dummy read pins.cpu_addr = self.pc; }, 1 => { @@ -807,6 +808,135 @@ inline fn rti(self: *Cpu, pins: *zesty.Pins) void { } } +/// Jump (JMP) +inline fn jmp(self: *Cpu, pins: *zesty.Pins) void { + switch (self.cycle) { + 0 => { + self.pc +%= 1; + pins.cpu_addr = self.pc; + }, + 1 => { + self.pc +%= 1; + pins.cpu_addr = self.pc; + self.hilo = .{ .lo = pins.cpu_data }; + }, + else => { + self.hilo.hi = pins.cpu_data; + self.fetchAt(pins, self.hilo.addr()); + }, + } +} + +/// Jump indirect (JMP) +inline fn jmpInd(self: *Cpu, pins: *zesty.Pins) void { + switch (self.cycle) { + 0 => { + self.pc +%= 1; + pins.cpu_addr = self.pc; + }, + 1 => { + // Fetch indirect lo + self.pc +%= 1; + pins.cpu_addr = self.pc; + self.hilo = .{ .lo = pins.cpu_data }; + }, + 2 => { + // Fetch indirect hi + self.hilo.hi = pins.cpu_data; + pins.cpu_addr = self.hilo.addr(); + }, + 3 => { + // Fetch target lo + self.hilo.lo +%= 1; + pins.cpu_addr = self.hilo.addr(); + self.hilo = .{ .lo = pins.cpu_data }; + }, + else => { + // Fetch target hi + self.hilo.hi = pins.cpu_data; + self.fetchAt(pins, self.hilo.addr()); + }, + } +} + +/// Jump to subroutine (JSR) +inline fn jsr(self: *Cpu, pins: *zesty.Pins) void { + const pc: Addr = .from(self.pc); + const stack: Addr = .stack(self.sp); + + switch (self.cycle) { + 0 => { + self.pc +%= 1; + pins.cpu_addr = self.pc; + }, + 1 => { + // Fetch target lo + self.hilo = .{ .lo = pins.cpu_data }; + pins.cpu_addr = stack.addr(); + }, + 2 => { + // Store PC hi + pins.cpu_addr = stack.addr(); + pins.cpu_data = pc.hi; + pins.cpu_rw = .write; + self.sp -%= 1; + }, + 3 => { + // Store PC lo + pins.cpu_addr = stack.addr(); + pins.cpu_data = pc.lo; + pins.cpu_rw = .write; + self.sp -%= 1; + }, + 4 => { + // Reposition back to PC + self.pc +%= 1; + pins.cpu_addr = self.pc; + }, + else => { + // Fetch target hi + self.hilo.hi = pins.cpu_data; + self.fetchAt(pins, self.hilo.addr()); + }, + } +} + +/// RTS (return from subroutine) +inline fn rts(self: *Cpu, pins: *zesty.Pins) void { + const stack: Addr = .stack(self.sp); + switch (self.cycle) { + 0 => { + // Dummy read + pins.cpu_addr = self.pc; + }, + 1 => { + // Dummy read + pins.cpu_addr = stack.addr(); + self.sp +%= 1; + }, + 2 => { + // Dummy read + pins.cpu_addr = stack.addr(); + self.sp +%= 1; + }, + 3 => { + // Pop PC lo + self.hilo = .{ .lo = pins.cpu_data }; + pins.cpu_addr = stack.addr(); + }, + 4 => { + // Pop PC hi + self.hilo.hi = pins.cpu_data; + self.pc = self.hilo.addr(); + pins.cpu_addr = self.pc; + self.pc +%= 1; + }, + else => { + self.fetch(pins); + }, + } +} + //------------------------------------------------------ // Helpers diff --git a/src/tests/cpu/assembler.zig b/src/tests/cpu/assembler.zig index 243c415..5759afc 100644 --- a/src/tests/cpu/assembler.zig +++ b/src/tests/cpu/assembler.zig @@ -42,7 +42,7 @@ pub fn assemble( .absolute, .absolute_x, .absolute_y, - .indexed, + .indirect, => |v| try writer.writeInt(u16, v, .little), } } @@ -431,7 +431,7 @@ pub const Operand = union(enum) { absolute_x: u16, absolute_y: u16, immediate: u8, - indexed: u16, + indirect: u16, indexed_indirect: u8, indirect_indexed: u8, @@ -509,11 +509,9 @@ pub const Operand = union(enum) { }; } - if (r_paren != s.len) return error.InvalidSyntax; - // (ind) return .{ - .indexed = try parseInt(u16, std.mem.trim( + .indirect = try parseInt(u16, std.mem.trim( u8, s[1..r_paren], whitespace, diff --git a/src/tests/cpu/control_flow.zig b/src/tests/cpu/control_flow.zig index 30d2038..4d909f1 100644 --- a/src/tests/cpu/control_flow.zig +++ b/src/tests/cpu/control_flow.zig @@ -114,7 +114,6 @@ test "set and clear" { } test "branches" { - // TODO: Test overflow as well var z = cpu.testZes(.{ .rom = &.{ .{ 0x8000, cpu.assemble( @@ -159,4 +158,96 @@ test "branches" { z.stepCpu(); // BMI z.stepCpu(); // BMI try std.testing.expectEqual(0x800c, z.cpu.pc); + + cpu.run(&z); // BIT + try std.testing.expectEqual(0x800e, z.cpu.pc); + try std.testing.expect(z.cpu.status.overflow); + + z.stepCpu(); // BVS + z.stepCpu(); // BVS + z.stepCpu(); // BVS + try std.testing.expectEqual(0x8011, z.cpu.pc); + + // Never hit BRK + try std.testing.expect(!z.cpu.status.brk); +} + +test "subroutines" { + var z = cpu.testZes(.{ .rom = &.{ + .{ + 0x8000, cpu.assemble( + \\jsr $8007 + \\cmp $8008 + \\brk + \\lda #$80 + \\rts + ), + }, + } }); + + try std.testing.expectEqual(0xfd, z.cpu.sp); + cpu.run(&z); // JSR + try std.testing.expectEqual(0x8007, z.cpu.pc); + try std.testing.expectEqual(0xfb, z.cpu.sp); + try std.testing.expectEqual(0x01, z.ram[0x1fc]); + try std.testing.expectEqual(0x80, z.ram[0x1fd]); + + cpu.run(&z); // LDA + try std.testing.expectEqual(0x8009, z.cpu.pc); + try std.testing.expectEqual(0x80, z.cpu.a); + try std.testing.expect(z.cpu.status.negative); + + // TODO: figure out why the run harness runs two commands at once + cpu.run(&z); // RTS, CMP + try std.testing.expectEqual(0x8006, z.cpu.pc); + try std.testing.expectEqual(0xfd, z.cpu.sp); + try std.testing.expect(z.cpu.status.zero); // Should be equal + + // Never hit BRK + try std.testing.expect(!z.cpu.status.brk); +} + +test "JMP" { + var z = cpu.testZes(.{ .rom = &.{ + .{ + 0x8000, cpu.assemble( + \\jmp $8004 + \\brk + \\lda $8000 + ), + }, + } }); + + cpu.run(&z); // JMP + try std.testing.expectEqual(0x8004, z.cpu.pc); + + cpu.run(&z); // LDA + try std.testing.expectEqual(0x8007, z.cpu.pc); + try std.testing.expectEqual(0x4c, z.cpu.a); + + // Never hit BRK + try std.testing.expect(!z.cpu.status.brk); +} + +test "JMP indirect" { + var z = cpu.testZes(.{ .rom = &.{ + .{ + 0x8000, cpu.assemble( + \\jmp ($aabb) + \\brk + \\lda $8000 + ), + }, + .{ 0xaabb, &.{ 0x04, 0x80 } }, + } }); + + cpu.run(&z); // JMP + try std.testing.expectEqual(0x8004, z.cpu.pc); + + cpu.run(&z); // LDA + try std.testing.expectEqual(0x8007, z.cpu.pc); + try std.testing.expectEqual(0x6c, z.cpu.a); + + // Never hit BRK + try std.testing.expect(!z.cpu.status.brk); } -- 2.43.0