cpu: jumps and subroutines #3

merged
opened by pluie.me targeting main from pluie/jj-tpnmozqyuktv
Changed files
+225 -6
src
+130
src/Cpu.zig
···
inline fn rti(self: *Cpu, pins: *zesty.Pins) void {
switch (self.cycle) {
0 => {
+
// Dummy read
pins.cpu_addr = self.pc;
},
1 => {
···
}
}
+
/// 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
+3 -5
src/tests/cpu/assembler.zig
···
.absolute,
.absolute_x,
.absolute_y,
-
.indexed,
+
.indirect,
=> |v| try writer.writeInt(u16, v, .little),
}
}
···
absolute_x: u16,
absolute_y: u16,
immediate: u8,
-
indexed: u16,
+
indirect: u16,
indexed_indirect: u8,
indirect_indexed: u8,
···
};
}
-
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,
+92 -1
src/tests/cpu/control_flow.zig
···
}
test "branches" {
-
// TODO: Test overflow as well
var z = cpu.testZes(.{ .rom = &.{
.{
0x8000, cpu.assemble(
···
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);
}