Zesty - a pin-accurate, cycle-accurate NES emulator written in Zig

cpu: jumps and subroutines #3

merged opened by pluie.me targeting main from pluie/jj-tpnmozqyuktv
Labels

None yet.

Participants 1
AT URI
at://did:plc:e4f33w5yt2m54tq6vsagpwiu/sh.tangled.repo.pull/3lyocw35qdf22
+225 -6
Diff #0
+130
src/Cpu.zig
··· 771 771 inline fn rti(self: *Cpu, pins: *zesty.Pins) void { 772 772 switch (self.cycle) { 773 773 0 => { 774 + // Dummy read 774 775 pins.cpu_addr = self.pc; 775 776 }, 776 777 1 => { ··· 807 808 } 808 809 } 809 810 811 + /// Jump (JMP) 812 + inline fn jmp(self: *Cpu, pins: *zesty.Pins) void { 813 + switch (self.cycle) { 814 + 0 => { 815 + self.pc +%= 1; 816 + pins.cpu_addr = self.pc; 817 + }, 818 + 1 => { 819 + self.pc +%= 1; 820 + pins.cpu_addr = self.pc; 821 + self.hilo = .{ .lo = pins.cpu_data }; 822 + }, 823 + else => { 824 + self.hilo.hi = pins.cpu_data; 825 + self.fetchAt(pins, self.hilo.addr()); 826 + }, 827 + } 828 + } 829 + 830 + /// Jump indirect (JMP) 831 + inline fn jmpInd(self: *Cpu, pins: *zesty.Pins) void { 832 + switch (self.cycle) { 833 + 0 => { 834 + self.pc +%= 1; 835 + pins.cpu_addr = self.pc; 836 + }, 837 + 1 => { 838 + // Fetch indirect lo 839 + self.pc +%= 1; 840 + pins.cpu_addr = self.pc; 841 + self.hilo = .{ .lo = pins.cpu_data }; 842 + }, 843 + 2 => { 844 + // Fetch indirect hi 845 + self.hilo.hi = pins.cpu_data; 846 + pins.cpu_addr = self.hilo.addr(); 847 + }, 848 + 3 => { 849 + // Fetch target lo 850 + self.hilo.lo +%= 1; 851 + pins.cpu_addr = self.hilo.addr(); 852 + self.hilo = .{ .lo = pins.cpu_data }; 853 + }, 854 + else => { 855 + // Fetch target hi 856 + self.hilo.hi = pins.cpu_data; 857 + self.fetchAt(pins, self.hilo.addr()); 858 + }, 859 + } 860 + } 861 + 862 + /// Jump to subroutine (JSR) 863 + inline fn jsr(self: *Cpu, pins: *zesty.Pins) void { 864 + const pc: Addr = .from(self.pc); 865 + const stack: Addr = .stack(self.sp); 866 + 867 + switch (self.cycle) { 868 + 0 => { 869 + self.pc +%= 1; 870 + pins.cpu_addr = self.pc; 871 + }, 872 + 1 => { 873 + // Fetch target lo 874 + self.hilo = .{ .lo = pins.cpu_data }; 875 + pins.cpu_addr = stack.addr(); 876 + }, 877 + 2 => { 878 + // Store PC hi 879 + pins.cpu_addr = stack.addr(); 880 + pins.cpu_data = pc.hi; 881 + pins.cpu_rw = .write; 882 + self.sp -%= 1; 883 + }, 884 + 3 => { 885 + // Store PC lo 886 + pins.cpu_addr = stack.addr(); 887 + pins.cpu_data = pc.lo; 888 + pins.cpu_rw = .write; 889 + self.sp -%= 1; 890 + }, 891 + 4 => { 892 + // Reposition back to PC 893 + self.pc +%= 1; 894 + pins.cpu_addr = self.pc; 895 + }, 896 + else => { 897 + // Fetch target hi 898 + self.hilo.hi = pins.cpu_data; 899 + self.fetchAt(pins, self.hilo.addr()); 900 + }, 901 + } 902 + } 903 + 904 + /// RTS (return from subroutine) 905 + inline fn rts(self: *Cpu, pins: *zesty.Pins) void { 906 + const stack: Addr = .stack(self.sp); 907 + switch (self.cycle) { 908 + 0 => { 909 + // Dummy read 910 + pins.cpu_addr = self.pc; 911 + }, 912 + 1 => { 913 + // Dummy read 914 + pins.cpu_addr = stack.addr(); 915 + self.sp +%= 1; 916 + }, 917 + 2 => { 918 + // Dummy read 919 + pins.cpu_addr = stack.addr(); 920 + self.sp +%= 1; 921 + }, 922 + 3 => { 923 + // Pop PC lo 924 + self.hilo = .{ .lo = pins.cpu_data }; 925 + pins.cpu_addr = stack.addr(); 926 + }, 927 + 4 => { 928 + // Pop PC hi 929 + self.hilo.hi = pins.cpu_data; 930 + self.pc = self.hilo.addr(); 931 + pins.cpu_addr = self.pc; 932 + self.pc +%= 1; 933 + }, 934 + else => { 935 + self.fetch(pins); 936 + }, 937 + } 938 + } 939 + 810 940 //------------------------------------------------------ 811 941 // Helpers 812 942
+3 -5
src/tests/cpu/assembler.zig
··· 42 42 .absolute, 43 43 .absolute_x, 44 44 .absolute_y, 45 - .indexed, 45 + .indirect, 46 46 => |v| try writer.writeInt(u16, v, .little), 47 47 } 48 48 } ··· 431 431 absolute_x: u16, 432 432 absolute_y: u16, 433 433 immediate: u8, 434 - indexed: u16, 434 + indirect: u16, 435 435 indexed_indirect: u8, 436 436 indirect_indexed: u8, 437 437 ··· 509 509 }; 510 510 } 511 511 512 - if (r_paren != s.len) return error.InvalidSyntax; 513 - 514 512 // (ind) 515 513 return .{ 516 - .indexed = try parseInt(u16, std.mem.trim( 514 + .indirect = try parseInt(u16, std.mem.trim( 517 515 u8, 518 516 s[1..r_paren], 519 517 whitespace,
+92 -1
src/tests/cpu/control_flow.zig
··· 114 114 } 115 115 116 116 test "branches" { 117 - // TODO: Test overflow as well 118 117 var z = cpu.testZes(.{ .rom = &.{ 119 118 .{ 120 119 0x8000, cpu.assemble( ··· 159 158 z.stepCpu(); // BMI 160 159 z.stepCpu(); // BMI 161 160 try std.testing.expectEqual(0x800c, z.cpu.pc); 161 + 162 + cpu.run(&z); // BIT 163 + try std.testing.expectEqual(0x800e, z.cpu.pc); 164 + try std.testing.expect(z.cpu.status.overflow); 165 + 166 + z.stepCpu(); // BVS 167 + z.stepCpu(); // BVS 168 + z.stepCpu(); // BVS 169 + try std.testing.expectEqual(0x8011, z.cpu.pc); 170 + 171 + // Never hit BRK 172 + try std.testing.expect(!z.cpu.status.brk); 173 + } 174 + 175 + test "subroutines" { 176 + var z = cpu.testZes(.{ .rom = &.{ 177 + .{ 178 + 0x8000, cpu.assemble( 179 + \\jsr $8007 180 + \\cmp $8008 181 + \\brk 182 + \\lda #$80 183 + \\rts 184 + ), 185 + }, 186 + } }); 187 + 188 + try std.testing.expectEqual(0xfd, z.cpu.sp); 189 + cpu.run(&z); // JSR 190 + try std.testing.expectEqual(0x8007, z.cpu.pc); 191 + try std.testing.expectEqual(0xfb, z.cpu.sp); 192 + try std.testing.expectEqual(0x01, z.ram[0x1fc]); 193 + try std.testing.expectEqual(0x80, z.ram[0x1fd]); 194 + 195 + cpu.run(&z); // LDA 196 + try std.testing.expectEqual(0x8009, z.cpu.pc); 197 + try std.testing.expectEqual(0x80, z.cpu.a); 198 + try std.testing.expect(z.cpu.status.negative); 199 + 200 + // TODO: figure out why the run harness runs two commands at once 201 + cpu.run(&z); // RTS, CMP 202 + try std.testing.expectEqual(0x8006, z.cpu.pc); 203 + try std.testing.expectEqual(0xfd, z.cpu.sp); 204 + try std.testing.expect(z.cpu.status.zero); // Should be equal 205 + 206 + // Never hit BRK 207 + try std.testing.expect(!z.cpu.status.brk); 208 + } 209 + 210 + test "JMP" { 211 + var z = cpu.testZes(.{ .rom = &.{ 212 + .{ 213 + 0x8000, cpu.assemble( 214 + \\jmp $8004 215 + \\brk 216 + \\lda $8000 217 + ), 218 + }, 219 + } }); 220 + 221 + cpu.run(&z); // JMP 222 + try std.testing.expectEqual(0x8004, z.cpu.pc); 223 + 224 + cpu.run(&z); // LDA 225 + try std.testing.expectEqual(0x8007, z.cpu.pc); 226 + try std.testing.expectEqual(0x4c, z.cpu.a); 227 + 228 + // Never hit BRK 229 + try std.testing.expect(!z.cpu.status.brk); 230 + } 231 + 232 + test "JMP indirect" { 233 + var z = cpu.testZes(.{ .rom = &.{ 234 + .{ 235 + 0x8000, cpu.assemble( 236 + \\jmp ($aabb) 237 + \\brk 238 + \\lda $8000 239 + ), 240 + }, 241 + .{ 0xaabb, &.{ 0x04, 0x80 } }, 242 + } }); 243 + 244 + cpu.run(&z); // JMP 245 + try std.testing.expectEqual(0x8004, z.cpu.pc); 246 + 247 + cpu.run(&z); // LDA 248 + try std.testing.expectEqual(0x8007, z.cpu.pc); 249 + try std.testing.expectEqual(0x6c, z.cpu.a); 250 + 251 + // Never hit BRK 252 + try std.testing.expect(!z.cpu.status.brk); 162 253 }

History

1 round 0 comments
sign up or login to add to the discussion
pluie.me submitted #0
1 commit
expand
cpu: jumps and subroutines
expand 0 comments
pull request successfully merged