cpu: add stack operations #2

merged
opened by pluie.me targeting main from pluie/jj-tpnmozqyuktv
Changed files
+200 -2
src
+69 -2
src/Cpu.zig
···
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
···
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
···
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
···
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)
···
}
//------------------------------------------------------
-
// 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;
···
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);
+1
src/tests/cpu.zig
···
_ = @import("cpu/addressing.zig");
_ = @import("cpu/alu.zig");
_ = @import("cpu/control_flow.zig");
+
_ = @import("cpu/stack.zig");
}
//------------------------------------------------------
+34
src/tests/cpu/assembler.zig
···
stx,
sty,
+
php,
+
plp,
+
pha,
+
pla,
+
ora,
@"and",
eor,
···
.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,
+
},
};
}
};
+96
src/tests/cpu/stack.zig
···
+
//! 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);
+
}