├── .gitignore ├── .gitmodules ├── LICENSE ├── README ├── build.zig └── src ├── cpu.zig ├── gameboy.zig ├── gpu.zig ├── main.zig ├── mbc.zig ├── meta.zig ├── mmu.zig ├── rom.zig ├── window_dummy.zig └── window_sdl.zig /.gitignore: -------------------------------------------------------------------------------- 1 | zig-cache/ 2 | gameboy 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/zig-window"] 2 | path = lib/zig-window 3 | url = https://github.com/andrewrk/zig-window 4 | [submodule "lib/zig-sdl2"] 5 | path = lib/zig-sdl2 6 | url = https://github.com/tiehuis/zig-sdl2 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Marc Tiehuis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | NOTE: This isn't complete. 2 | 3 | # Get 4 | 5 | git clone --recursive https://github.com/tiehuis/zig-gameboy 6 | 7 | # Build 8 | 9 | zig build 10 | 11 | # Dependencies 12 | 13 | - SDL2 14 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const Builder = @import("std").build.Builder; 2 | 3 | pub fn build(b: *Builder) void { 4 | const mode = b.standardReleaseOptions(); 5 | 6 | var exe = b.addExecutable("gameboy", "src/main.zig"); 7 | exe.setBuildMode(mode); 8 | 9 | b.detectNativeSystemPaths(); 10 | 11 | exe.linkSystemLibrary("c"); 12 | exe.linkSystemLibrary("SDL2"); 13 | exe.setOutputDir("."); 14 | 15 | b.default_step.dependOn(&exe.step); 16 | b.installArtifact(exe); 17 | } 18 | -------------------------------------------------------------------------------- /src/cpu.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Mmu = @import("mmu.zig").Mmu; 3 | const assert = std.debug.assert; 4 | 5 | // 4.194304Mhz 6 | pub const clock_speed = 4194304; 7 | 8 | pub const FlagZ: u8 = 0x80; 9 | pub const FlagS: u8 = 0x40; 10 | pub const FlagH: u8 = 0x20; 11 | pub const FlagC: u8 = 0x10; 12 | 13 | pub const A = 0; 14 | pub const F = 1; 15 | pub const B = 2; 16 | pub const C = 3; 17 | pub const D = 4; 18 | pub const E = 5; 19 | pub const H = 6; 20 | pub const L = 7; 21 | 22 | pub const AF = 0; 23 | pub const BC = 1; 24 | pub const DE = 2; 25 | pub const HL = 3; 26 | pub const SP = 4; 27 | pub const PC = 5; 28 | 29 | const trace = false; 30 | 31 | pub const Z80 = struct { 32 | // A, F, B, C, D, E, H, L, PC, SP 33 | r: [12]u8, 34 | r1: []u8, 35 | r2: []u16, 36 | total_ticks: usize, 37 | ticks: usize, 38 | 39 | // Communication 40 | mmu: *Mmu, 41 | 42 | pub fn init(mmu: *Mmu) Z80 { 43 | var z = Z80{ 44 | .r = []u8{0} ** 12, 45 | .r1 = undefined, 46 | .r2 = undefined, 47 | .total_ticks = 0, 48 | .ticks = 0, 49 | .mmu = mmu, 50 | }; 51 | 52 | // Allow accessing half/whole registers somewhwat similar to anonymous unions 53 | z.r1 = z.r[0..8]; 54 | z.r2 = @alignCast(@alignOf(u16), @bytesToSlice(u16, z.r[0..12])); 55 | return z; 56 | } 57 | 58 | pub fn step(self: *Z80, opcode: u8) usize { 59 | instruction_table[opcode](self); 60 | 61 | if (trace) { 62 | if (opcode != 0xcb) { 63 | std.debug.warn("\n : {} [{x}]\n", instruction_names_table[opcode], opcode); 64 | } else { 65 | const cb_opcode = self.mmu.read(self.r2[PC] + 1); 66 | std.debug.warn("\n : {} [{x}]\n", cb_instruction_names_table[cb_opcode], cb_opcode); 67 | } 68 | self.dump(); 69 | 70 | var stdin_file = std.io.getStdIn() catch unreachable; 71 | var stdin = stdin_file.inStream(); 72 | _ = stdin.stream.readByte(); 73 | } 74 | 75 | const r = self.ticks; 76 | self.total_ticks += r; 77 | self.ticks = 0; 78 | return r; 79 | } 80 | 81 | pub fn dump(self: *Z80) void { 82 | std.debug.warn( 83 | \\ SP:{x} PC:{x} AF:{x} BC:{x} DE:{x} ({}) 84 | \\ [{x}, {x}, ... ] 85 | \\ 86 | , 87 | self.r2[SP], 88 | self.r2[PC], 89 | self.r2[AF], 90 | self.r2[BC], 91 | self.r2[DE], 92 | self.total_ticks, 93 | self.mmu.read(self.r2[PC]), 94 | self.mmu.read(self.r2[PC] + 1), 95 | ); 96 | } 97 | 98 | // Read a byte from memory in 4 cycles 99 | fn read8(self: *Z80, address: u16) u8 { 100 | self.ticks += 4; 101 | return self.mmu.read(address); 102 | } 103 | 104 | // Read a word from memory in 8 cycles 105 | fn read16(self: *Z80, address: u16) u16 { 106 | return u16(self.read8(address)) | (u16(self.read8(address + 1)) << 8); 107 | } 108 | 109 | // Write a byte to memory in 4 cycles 110 | fn write8(self: *Z80, address: u16, byte: u8) void { 111 | self.ticks += 4; 112 | self.mmu.write(address, byte); 113 | } 114 | 115 | // Write a word to memory in 8 cycles 116 | fn write16(self: *Z80, address: u16, word: u16) void { 117 | self.write8(address, @truncate(u8, word)); 118 | self.write8(address + 1, @intCast(u8, word >> 8)); 119 | } 120 | 121 | // Cycle the cpu with no other side-effects. 122 | fn cycle(self: *Z80) void { 123 | self.ticks += 4; 124 | } 125 | }; 126 | 127 | // See: http://www.pastraiser.com/cpu/gameboy/gameboy_opcodes.html 128 | 129 | const Instruction = fn (z: *Z80) void; 130 | 131 | const instruction_table = [256]Instruction{ 132 | // X0, X1, X2, X3, X4, X5, X6, X7, 133 | // X8, X9, Xa, Xb, Xc, Xd, Xe, Xf, 134 | nop_____, ld16(BC), ldXa(BC), incw(BC), inc__(B), dec__(B), ld8__(B), rlca____, // 0X 135 | ld_d16sp, adhl(BC), ldaX(BC), decw(BC), inc__(C), dec__(C), ld8__(C), rrca____, 136 | stop____, ld16(DE), ldXa(DE), incw(DE), inc__(D), dec__(D), ld8__(D), rla_____, // 1X 137 | jr______, adhl(DE), ldaX(DE), decw(DE), inc__(E), dec__(E), ld8__(E), rra_____, 138 | jr_nz___, ld16(HL), ld_hli_a, incw(HL), inc__(H), dec__(H), ld8__(H), ________, // 2X 139 | jr_z____, adhl(HL), ld_a_hli, decw(HL), inc__(L), dec__(L), ld8__(L), ________, 140 | jr_nc___, ld16(SP), ld_hld_a, incw(SP), inc_hl__, dec_hl__, ld_hl_d8, ________, // 3X 141 | jr_c____, adhl(SP), ld_a_hld, decw(SP), inc__(A), dec__(A), ld8__(A), ________, 142 | ld(B, B), ld(B, C), ld(B, D), ld(B, E), ld(B, H), ld(B, L), ldXhl(B), ld(B, A), // 4X 143 | ld(C, B), ld(C, C), ld(C, D), ld(C, E), ld(C, H), ld(C, L), ldXhl(C), ld(C, A), 144 | ld(D, B), ld(D, C), ld(D, D), ld(D, E), ld(D, H), ld(D, L), ldXhl(D), ld(D, A), // 5X 145 | ld(E, B), ld(E, C), ld(E, D), ld(E, E), ld(E, H), ld(E, L), ldXhl(E), ld(E, A), 146 | ld(H, B), ld(H, C), ld(H, D), ld(H, E), ld(H, H), ld(H, L), ldXhl(H), ld(H, A), // 6X 147 | ld(L, B), ld(L, C), ld(L, D), ld(L, E), ld(L, H), ld(L, L), ldXhl(L), ld(L, A), 148 | ldhlY(B), ldhlY(C), ldhlY(D), ldhlY(E), ldhlY(H), ldhlY(L), halt____, ldhlY(A), // 7X 149 | ld(A, B), ld(A, C), ld(A, D), ld(A, E), ld(A, H), ld(A, L), ldXhl(A), ld(A, A), 150 | add__(B), add__(C), add__(D), add__(E), add__(H), add__(L), addhl___, add__(A), // 8X 151 | adc__(B), adc__(C), adc__(D), adc__(E), adc__(H), adc__(L), adchl___, adc__(A), 152 | sub__(B), sub__(C), sub__(D), sub__(E), sub__(H), sub__(L), subhl___, sub__(A), // 9X 153 | sbc__(B), sbc__(C), sbc__(D), sbc__(E), sbc__(H), sbc__(L), sbchl___, sbc__(A), 154 | and__(B), and__(C), and__(D), and__(E), and__(H), and__(L), andhl___, and__(A), // aX 155 | xor__(B), xor__(C), xor__(D), xor__(E), xor__(H), xor__(L), xorhl___, xor__(A), 156 | or___(B), or___(C), or___(D), or___(E), or___(H), or___(L), orhl____, or___(A), // bX 157 | cp___(B), cp___(C), cp___(D), cp___(E), cp___(H), cp___(L), cphl____, cp___(A), 158 | ret_nz__, pop_(BC), jp_nz___, jp_nn___, call_nz_, push(BC), add_a_d8, rst__(0), // cX 159 | ret_z___, ret_____, jp_z____, cb______, call_z__, call____, adc_a_d8, rst__(8), 160 | ret_nc__, pop_(DE), jp_nc___, illegal_, call_nc_, push(DE), sub_a_d8, rst_(16), // dX 161 | ret_c___, ________, jp_c____, illegal_, call_c__, illegal_, sbc_a_d8, rst_(24), 162 | ldh_a8_a, pop_(HL), ld_uc_a_, illegal_, illegal_, push(HL), and_a_d8, rst_(32), // eX 163 | ________, ________, ld_a16_a, illegal_, illegal_, illegal_, xor_a_d8, rst_(40), 164 | ldh_a_a8, pop_(AF), ld_a_uc_, di______, illegal_, push(AF), or_a_d8_, rst_(48), // fX 165 | ________, ________, ld_a_a16, ei______, illegal_, illegal_, cp_a_d8_, rst_(56), 166 | }; 167 | 168 | const instruction_names_table = [256][11]u8{ 169 | "NOP ", "LD BC,d16 ", "LD (BC),A ", "INC BC ", 170 | "INC B ", "DEC B ", "LD B,d8 ", "RLCA ", 171 | "LD (a16),SP", "ADD HL,BC ", "LD A,(BC) ", "DEC BC ", 172 | "INC C ", "DEC C ", "LD C,d8 ", "RRCA ", 173 | 174 | "STOP ", "LD DE,d16 ", "LD (DE),A ", "INC DE ", 175 | "INC D ", "DEC D ", "LD D,d8 ", "RLA ", 176 | "JR r8 ", "ADD HL,DE ", "LD A,(DE) ", "DEC DE ", 177 | "INC E ", "DEC E ", "LD E,d8 ", "RRA ", 178 | 179 | "JR NZ,r8 ", "LD HL,d16 ", "LD (HL+),A ", "INC HL ", 180 | "INC H ", "DEC H ", "LD H,d8 ", "DAA ", 181 | "JR Z,r8 ", "ADD HL,HL ", "LD A,(HL+) ", "DEC HL ", 182 | "INC L ", "DEC L ", "LD L,d8 ", "CPL ", 183 | 184 | "JR NC,r8 ", "LD SP,d16 ", "LD (HL-),A ", "INC SP ", 185 | "INC (HL) ", "DEC (HL) ", "LD (HL),d8 ", "SCF ", 186 | "JR C,r8 ", "ADD HL,SP ", "LD A,(HL-) ", "DEC SP ", 187 | "INC A ", "DEC A ", "LD A,d8 ", "CCF ", 188 | 189 | "LD B,B ", "LD B,C ", "LD B,D ", "LD B,E ", 190 | "LD B,H ", "LD B,L ", "LD B,(HL) ", "LD B,A ", 191 | "LD C,B ", "LD C,C ", "LD C,D ", "LD C,E ", 192 | "LD C,H ", "LD C,L ", "LD C,(HL) ", "LD C,A ", 193 | 194 | "LD D,B ", "LD D,C ", "LD D,D ", "LD D,E ", 195 | "LD D,H ", "LD D,L ", "LD D,(HL) ", "LD D,A ", 196 | "LD E,B ", "LD E,C ", "LD E,D ", "LD E,E ", 197 | "LD E,H ", "LD E,L ", "LD E,(HL) ", "LD E,A ", 198 | 199 | "LD H,B ", "LD H,C ", "LD H,D ", "LD H,E ", 200 | "LD H,H ", "LD H,L ", "LD H,(HL) ", "LD H,A ", 201 | "LD L,B ", "LD L,C ", "LD L,D ", "LD L,E ", 202 | "LD L,H ", "LD L,L ", "LD L,(HL) ", "LD L,A ", 203 | 204 | "LD (HL),B ", "LD (HL),C ", "LD (HL),D ", "LD (HL),E ", 205 | "LD (HL),H ", "LD (HL),L ", "HALT ", "LD (HL),A ", 206 | "LD A,B ", "LD A,C ", "LD A,D ", "LD A,E ", 207 | "LD A,H ", "LD A,L ", "LD A,(HL) ", "LD A,A ", 208 | 209 | "ADD A,B ", "ADD A,C ", "ADD A,D ", "ADD A,E ", 210 | "ADD A,H ", "ADD A,L ", "ADD A,(HL) ", "ADD A,A ", 211 | "ADC A,B ", "ADC A,C ", "ADC A,D ", "ADC A,E ", 212 | "ADC A,H ", "ADC A,L ", "ADC A,(HL) ", "ADC A,A ", 213 | 214 | "SUB B ", "SUB C ", "SUB D ", "SUB E ", 215 | "SUB H ", "SUB L ", "SUB (HL) ", "SUB A ", 216 | "SBC A,B ", "SBC A,C ", "SBC A,D ", "SBC A,E ", 217 | "SBC A,H ", "SBC A,L ", "SBC A,(HL) ", "SBC A,A ", 218 | 219 | "AND B ", "AND C ", "AND D ", "AND E ", 220 | "AND H ", "AND L ", "AND (HL) ", "AND A ", 221 | "XOR B ", "XOR C ", "XOR D ", "XOR E ", 222 | "XOR H ", "XOR L ", "XOR (HL) ", "XOR A ", 223 | 224 | "OR B ", "OR C ", "OR D ", "OR E ", 225 | "OR H ", "OR L ", "OR (HL) ", "OR A ", 226 | "CP B ", "CP C ", "CP D ", "CP E ", 227 | "CP H ", "CP L ", "CP (HL) ", "CP A ", 228 | 229 | "RET NZ ", "POP BC ", "JP NZ,a16 ", "JP a16 ", 230 | "CALL NZ,a16", "PUSH BC ", "ADD A,d8 ", "RST 00h ", 231 | "RET Z ", "RET ", "JP Z,a16 ", "PREFIX CB ", 232 | "CALL Z,a16 ", "CALL a16 ", "ADC A,d8 ", "RST 08h ", 233 | 234 | "RET NC ", "POP DE ", "JP NC,a16 ", "ILLEGAL ", 235 | "CALL NC,a16", "PUSH DE ", "SUB d8 ", "RST 10h ", 236 | "RET C ", "RETI ", "JP C,a16 ", "ILLEGAL ", 237 | "CALL C,a16 ", "ILLEGAL ", "SBC A,d8 ", "RST 18h ", 238 | 239 | "LDH (a8),A ", "POP HL ", "LD (C),A ", "ILLEGAL ", 240 | "ILLEGAL ", "PUSH HL ", "AND d8 ", "RST 20h ", 241 | "ADD SP,r8 ", "JP (HL) ", "LD (a16),A ", "ILLEGAL ", 242 | "ILLEGAL ", "ILLEGAL ", "XOR d8 ", "RST 28h ", 243 | 244 | "LDH A,(a8) ", "POP AF ", "LD A,(C) ", "DI ", 245 | "ILLEGAL ", "PUSH AF ", "OR d8 ", "RST 30h ", 246 | "LD HL,SP+r8", "LD SP,HL ", "LD A,(a16) ", "EI ", 247 | "ILLEGAL ", "ILLEGAL ", "CP d8 ", "RST 38h ", 248 | }; 249 | 250 | fn ld_d16sp(z: *Z80) void { 251 | const address = z.read16(z.r2[PC]); 252 | z.r2[PC] += 2; 253 | z.write16(address, z.r2[SP]); 254 | assert(z.ticks == 20); 255 | } 256 | 257 | fn ________(z: *Z80) void { 258 | const opcode = z.mmu.read(z.r2[PC] -% 1); 259 | 260 | std.debug.warn( 261 | \\!!> UNIMPLEMENTED OPCODE: {} [{x}] 262 | \\ 263 | , 264 | instruction_names_table[opcode], 265 | opcode, 266 | ); 267 | 268 | z.dump(); 269 | std.os.exit(1); 270 | } 271 | 272 | fn nop_____(z: *Z80) void { 273 | assert(z.ticks == 4); 274 | } 275 | 276 | fn stop____(z: *Z80) void { 277 | assert(z.ticks == 4); 278 | } 279 | 280 | fn di______(z: *Z80) void { 281 | // TODO: Set flag 282 | assert(z.ticks == 4); 283 | } 284 | 285 | fn ei______(z: *Z80) void { 286 | // TODO: Set flag 287 | assert(z.ticks == 4); 288 | } 289 | 290 | // PC = (PC + 1) 291 | fn jp_nn___(z: *Z80) void { 292 | const address = z.read16(z.r2[PC]); 293 | z.cycle(); 294 | z.r2[PC] = address; 295 | assert(z.ticks == 16); 296 | } 297 | 298 | fn flagCondition(z: *Z80, comptime flag: comptime_int, comptime invert: bool) bool { 299 | const r = (z.r1[F] & flag) != 0; 300 | return if (invert) !r else r; 301 | } 302 | 303 | // PC += (PC + 1) if condition 304 | fn jr_c(comptime flag: comptime_int, comptime invert: bool) Instruction { 305 | return struct { 306 | fn impl(z: *Z80) void { 307 | const offset = z.read8(z.r2[PC]); 308 | z.r2[PC] += 1; 309 | if (flagCondition(z, flag, invert)) { 310 | z.cycle(); 311 | z.r2[PC] += offset; 312 | assert(z.ticks == 12); 313 | } else { 314 | assert(z.ticks == 8); 315 | } 316 | } 317 | }.impl; 318 | } 319 | 320 | const jr_nz___ = jr_c(FlagZ, false); 321 | const jr_nc___ = jr_c(FlagC, false); 322 | const jr_z____ = jr_c(FlagZ, true); 323 | const jr_c____ = jr_c(FlagC, true); 324 | 325 | fn jr______(z: *Z80) void { 326 | const offset = z.read8(z.r2[PC]); 327 | z.r2[PC] += 1; 328 | z.cycle(); 329 | z.r2[PC] += offset; 330 | assert(z.ticks == 12); 331 | } 332 | 333 | // PC += (PC + 1) if condition 334 | fn jp_c(comptime flag: comptime_int, comptime invert: bool) Instruction { 335 | return struct { 336 | fn impl(z: *Z80) void { 337 | const address = z.read16(z.r2[PC]); 338 | z.r2[PC] += 2; 339 | if (flagCondition(z, flag, invert)) { 340 | z.cycle(); 341 | z.r2[PC] = address; 342 | assert(z.ticks == 16); 343 | } else { 344 | assert(z.ticks == 12); 345 | } 346 | } 347 | }.impl; 348 | } 349 | 350 | const jp_nz___ = jp_c(FlagZ, false); 351 | const jp_nc___ = jp_c(FlagC, false); 352 | const jp_z____ = jp_c(FlagZ, true); 353 | const jp_c____ = jp_c(FlagC, true); 354 | 355 | // PC = (SP) if condition 356 | fn ret_c(comptime flag: comptime_int, comptime invert: bool) Instruction { 357 | return struct { 358 | fn impl(z: *Z80) void { 359 | const address = z.read16(z.r2[SP]); 360 | z.cycle(); 361 | z.r2[PC] += 2; 362 | if (flagCondition(z, flag, invert)) { 363 | z.r2[PC] = address; 364 | assert(z.ticks == 16); 365 | } else { 366 | assert(z.ticks == 12); 367 | } 368 | } 369 | }.impl; 370 | } 371 | 372 | const ret_nz__ = ret_c(FlagZ, false); 373 | const ret_nc__ = ret_c(FlagC, false); 374 | const ret_z___ = ret_c(FlagZ, true); 375 | const ret_c___ = ret_c(FlagC, true); 376 | 377 | const rst__ = rst_; 378 | 379 | fn rst_(comptime address: u8) Instruction { 380 | return struct { 381 | fn impl(z: *Z80) void { 382 | z.cycle(); 383 | z.write16(z.r2[SP] - 2, z.r2[PC]); 384 | z.r2[PC] = address; 385 | } 386 | }.impl; 387 | } 388 | 389 | fn call_c(comptime flag: comptime_int, comptime invert: bool) Instruction { 390 | return struct { 391 | fn impl(z: *Z80) void { 392 | const address = z.read16(z.r2[PC]); 393 | z.r2[PC] += 2; 394 | if (flagCondition(z, flag, invert)) { 395 | z.cycle(); 396 | z.write16(z.r2[SP] - 2, z.r2[PC]); 397 | z.r2[PC] = address; 398 | assert(z.ticks == 24); 399 | } else { 400 | assert(z.ticks == 12); 401 | } 402 | } 403 | }.impl; 404 | } 405 | 406 | const call_nz_ = call_c(FlagZ, false); 407 | const call_nc_ = call_c(FlagC, false); 408 | const call_z__ = call_c(FlagZ, true); 409 | const call_c__ = call_c(FlagC, true); 410 | 411 | fn call____(z: *Z80) void { 412 | const address = z.read16(z.r2[PC]); 413 | z.r2[PC] +%= 2; 414 | z.cycle(); 415 | z.write16(z.r2[SP] - 2, z.r2[PC]); 416 | z.r2[SP] -%= 2; 417 | z.r2[PC] = address; 418 | assert(z.ticks == 24); 419 | } 420 | 421 | fn ret_____(z: *Z80) void { 422 | // TODO: 423 | const address = z.read16(z.r2[SP]); 424 | z.cycle(); 425 | z.r2[SP] +%= 2; 426 | z.r2[PC] = address; 427 | } 428 | 429 | // Pop stack into rr register 430 | fn pop_(comptime X: comptime_int) Instruction { 431 | return struct { 432 | fn impl(z: *Z80) void { 433 | z.r2[X] = z.read16(z.r2[SP]); 434 | z.r2[SP] +%= 2; 435 | z.r1[F] &= 0xf0; 436 | assert(z.ticks == 12); 437 | } 438 | }.impl; 439 | } 440 | 441 | // Push rr register onto stack 442 | fn push(comptime X: comptime_int) Instruction { 443 | return struct { 444 | fn impl(z: *Z80) void { 445 | z.cycle(); 446 | z.write16(z.r2[SP] - 2, z.r2[X]); 447 | z.r2[SP] -%= 2; 448 | assert(z.ticks == 16); 449 | } 450 | }.impl; 451 | } 452 | 453 | fn illegal_(z: *Z80) void { 454 | const opcode = z.mmu.read(z.r2[PC] -% 1); 455 | 456 | std.debug.warn( 457 | \\ 458 | \\ILLEGAL OPCODE: {x} 459 | \\ 460 | , opcode); 461 | 462 | std.os.exit(1); 463 | } 464 | 465 | fn halt____(z: *Z80) void { 466 | // TODO: Update cycles 467 | } 468 | 469 | fn adhl(comptime X: comptime_int) Instruction { 470 | return struct { 471 | fn impl(z: *Z80) void { 472 | z.cycle(); 473 | 474 | const r = z.r2[HL]; 475 | const u = z.r2[X]; 476 | 477 | z.r2[HL] +%= z.r2[X]; 478 | assert(z.ticks == 8); 479 | z.r1[F] &= ~(FlagS | FlagC | FlagH); 480 | 481 | if (((r & 0xfff) + (u & 0xfff)) & 0x1000 != 0) { 482 | z.r1[F] |= FlagH; 483 | } 484 | if (usize(r) + usize(u) & 0x10000 != 0) { 485 | z.r1[F] |= FlagC; 486 | } 487 | } 488 | }.impl; 489 | } 490 | 491 | // rX = d8 492 | fn ldd8(comptime X: comptime_int) Instruction { 493 | return struct { 494 | fn impl(z: *Z80) void { 495 | const r = z.read8(z.r2[PC]); 496 | z.r2[PC] += 1; 497 | z.r1[X] = r; 498 | assert(z.ticks == 8); 499 | } 500 | }.impl; 501 | } 502 | 503 | fn ld_hl_d8(z: *Z80) void { 504 | const r = z.read8(z.r2[PC]); 505 | z.r2[PC] += 1; 506 | z.write8(z.r2[HL], r); 507 | assert(z.ticks == 12); 508 | } 509 | 510 | // (0xff00 + a8) = A 511 | fn ldh_a8_a(z: *Z80) void { 512 | const address = z.read8(z.r2[PC]); 513 | z.r2[PC] += 1; 514 | z.write8(0xff00 + u16(address), z.r1[A]); 515 | assert(z.ticks == 12); 516 | } 517 | 518 | // A = (0xff00 + a8) 519 | fn ldh_a_a8(z: *Z80) void { 520 | const address = z.read8(z.r2[PC]); 521 | z.r2[PC] += 1; 522 | z.r1[A] = z.read8(0xff00 + u16(address)); 523 | assert(z.ticks == 12); 524 | } 525 | 526 | // (0xff00 + C) = A 527 | fn ld_uc_a_(z: *Z80) void { 528 | z.write8(0xff00 + u16(z.r1[C]), z.r1[A]); 529 | assert(z.ticks == 8); 530 | } 531 | 532 | // A = (0xff00 + C) 533 | fn ld_a_uc_(z: *Z80) void { 534 | z.r1[A] = z.read8(0xff00 + u16(z.r1[C])); 535 | assert(z.ticks == 8); 536 | } 537 | 538 | // (X) = A 539 | fn ldXa(comptime X: comptime_int) Instruction { 540 | return struct { 541 | fn impl(z: *Z80) void { 542 | z.write8(z.r2[X], z.r1[A]); 543 | assert(z.ticks == 4); 544 | } 545 | }.impl; 546 | } 547 | 548 | // A = (X) 549 | fn ldaX(comptime X: comptime_int) Instruction { 550 | return struct { 551 | fn impl(z: *Z80) void { 552 | z.r1[A] = z.read8(z.r2[X]); 553 | assert(z.ticks == 8); 554 | } 555 | }.impl; 556 | } 557 | 558 | // (HL) = A, HL += 1 559 | fn ld_hli_a(z: *Z80) void { 560 | z.write8(z.r2[HL], z.r1[A]); 561 | z.r2[HL] +%= 1; 562 | assert(z.ticks == 8); 563 | } 564 | 565 | // A = (HL), HL += 1 566 | fn ld_a_hli(z: *Z80) void { 567 | z.r1[A] = z.read8(z.r2[HL]); 568 | z.r2[HL] +%= 1; 569 | assert(z.ticks == 8); 570 | } 571 | 572 | // (HL) = A, HL -= 1 573 | fn ld_hld_a(z: *Z80) void { 574 | z.write8(z.r2[HL], z.r1[A]); 575 | z.r2[HL] -%= 1; 576 | assert(z.ticks == 8); 577 | } 578 | 579 | // A = (HL), HL -= 1 580 | fn ld_a_hld(z: *Z80) void { 581 | z.r1[A] = z.read8(z.r2[HL]); 582 | z.r2[HL] -%= 1; 583 | assert(z.ticks == 8); 584 | } 585 | 586 | // rX = (PC) 587 | fn ld16(comptime X: comptime_int) Instruction { 588 | return struct { 589 | fn impl(z: *Z80) void { 590 | z.r2[X] = z.read16(z.r2[PC]); 591 | z.r2[PC] += 2; 592 | assert(z.ticks == 12); 593 | } 594 | }.impl; 595 | } 596 | 597 | // A = (PC) 598 | fn ld_a_a16(z: *Z80) void { 599 | const address = z.read16(z.r2[PC]); 600 | z.r2[PC] += 2; 601 | z.r1[A] = z.read8(address); 602 | assert(z.ticks == 16); 603 | } 604 | 605 | // (PC) = A 606 | fn ld_a16_a(z: *Z80) void { 607 | const address = z.read16(z.r2[PC]); 608 | z.r2[PC] += 2; 609 | z.write8(address, z.r1[A]); 610 | assert(z.ticks == 16); 611 | } 612 | 613 | // rX = (PC) 614 | fn ld8__(comptime X: comptime_int) Instruction { 615 | return struct { 616 | fn impl(z: *Z80) void { 617 | z.r1[X] = z.read8(z.r2[PC]); 618 | z.r2[PC] += 1; 619 | assert(z.ticks == 8); 620 | } 621 | }.impl; 622 | } 623 | 624 | // rX = rY 625 | fn ld(comptime X: comptime_int, comptime Y: comptime_int) Instruction { 626 | return struct { 627 | fn impl(z: *Z80) void { 628 | if (X != Y) { 629 | z.r1[X] = z.r1[Y]; 630 | } 631 | assert(z.ticks == 4); 632 | } 633 | }.impl; 634 | } 635 | 636 | // rX = (HL) 637 | fn ldXhl(comptime X: comptime_int) Instruction { 638 | return struct { 639 | fn impl(z: *Z80) void { 640 | z.r1[X] = z.read8(z.r2[HL]); 641 | assert(z.ticks == 8); 642 | } 643 | }.impl; 644 | } 645 | 646 | // (HL) = rY 647 | fn ldhlY(comptime Y: comptime_int) Instruction { 648 | return struct { 649 | fn impl(z: *Z80) void { 650 | z.write8(z.r2[HL], z.r1[Y]); 651 | assert(z.ticks == 8); 652 | } 653 | }.impl; 654 | } 655 | 656 | // rX += 1, no flags set 657 | fn incw(comptime X: comptime_int) Instruction { 658 | return struct { 659 | fn impl(z: *Z80) void { 660 | z.r2[X] +%= 1; 661 | z.cycle(); 662 | assert(z.ticks == 8); 663 | } 664 | }.impl; 665 | } 666 | 667 | // rX = rX + 1 668 | fn inc__(comptime X: comptime_int) Instruction { 669 | return struct { 670 | fn impl(z: *Z80) void { 671 | z.r1[X] +%= 1; 672 | z.r1[F] &= ~(FlagS | FlagZ | FlagH); 673 | 674 | if (z.r1[X] & 0x0f == 0) { 675 | z.r1[F] |= FlagH; 676 | } 677 | if (z.r1[X] == 0) { 678 | z.r1[F] |= FlagZ; 679 | } 680 | 681 | assert(z.ticks == 4); 682 | } 683 | }.impl; 684 | } 685 | 686 | fn inc_hl__(z: *Z80) void { 687 | const r = z.read8(z.r2[HL]) +% 1; 688 | z.r1[F] &= ~(FlagS | FlagZ | FlagH); 689 | z.write8(z.r2[HL], r); 690 | 691 | if (r & 0x0f == 0) { 692 | z.r1[F] |= FlagH; 693 | } 694 | if (r == 0) { 695 | z.r1[F] |= FlagZ; 696 | } 697 | 698 | assert(z.ticks == 12); 699 | } 700 | 701 | // rX -= 1, no flags set 702 | fn decw(comptime X: comptime_int) Instruction { 703 | return struct { 704 | fn impl(z: *Z80) void { 705 | z.r2[X] -%= 1; 706 | z.cycle(); 707 | assert(z.ticks == 8); 708 | } 709 | }.impl; 710 | } 711 | 712 | // rX = rX - 1 713 | fn dec__(comptime X: comptime_int) Instruction { 714 | return struct { 715 | fn impl(z: *Z80) void { 716 | z.r1[X] -%= 1; 717 | z.r1[F] &= ~(FlagZ | FlagH); 718 | z.r1[F] |= FlagS; 719 | 720 | if (z.r1[X] & 0xf == 0xf) { 721 | z.r1[F] |= FlagH; 722 | } 723 | if (z.r1[X] == 0) { 724 | z.r1[F] |= FlagZ; 725 | } 726 | assert(z.ticks == 4); 727 | } 728 | }.impl; 729 | } 730 | 731 | fn dec_hl__(z: *Z80) void { 732 | const r = z.read8(z.r2[HL]) -% 1; 733 | z.r1[F] &= ~(FlagZ | FlagH); 734 | z.r1[F] |= FlagS; 735 | z.write8(z.r2[HL], r); 736 | 737 | if (r & 0xf == 0xf) { 738 | z.r1[F] |= FlagH; 739 | } 740 | if (r == 0) { 741 | z.r1[F] |= FlagZ; 742 | } 743 | assert(z.ticks == 12); 744 | } 745 | 746 | // A = A + r \w carry 747 | fn adc__(comptime X: comptime_int) Instruction { 748 | return struct { 749 | fn impl(z: *Z80) void { 750 | adcv(z, z.r1[X]); 751 | assert(z.ticks == 4); 752 | } 753 | }.impl; 754 | } 755 | 756 | fn adc_a_d8(z: *Z80) void { 757 | const r = z.read8(z.r2[PC]); 758 | z.r2[PC] +%= 1; 759 | adcv(z, r); 760 | assert(z.ticks == 8); 761 | } 762 | 763 | fn adchl___(z: *Z80) void { 764 | adcv(z, z.read8(z.r2[HL])); 765 | assert(z.ticks == 8); 766 | } 767 | 768 | fn adcv(z: *Z80, r: u8) void { 769 | const a = z.r1[A]; 770 | const c = @boolToInt((z.r1[F] & FlagC) != 0); 771 | z.r1[A] = a +% r +% c; 772 | 773 | if (a +% r +% c == 0) { 774 | z.r1[F] |= FlagZ; 775 | } 776 | if ((a & 0xf) + (r & 0xf) + c > 0xf) { 777 | z.r1[F] |= FlagH; 778 | } 779 | if (usize(a) + usize(r) + c > 0xff) { 780 | z.r1[F] |= FlagC; 781 | } 782 | } 783 | 784 | // A = A - r \w carry 785 | fn sbc__(comptime X: comptime_int) Instruction { 786 | return struct { 787 | fn impl(z: *Z80) void { 788 | sbcv(z, z.r1[X]); 789 | assert(z.ticks == 4); 790 | } 791 | }.impl; 792 | } 793 | 794 | fn sbc_a_d8(z: *Z80) void { 795 | const r = z.read8(z.r2[PC]); 796 | z.r2[PC] +%= 1; 797 | sbcv(z, r); 798 | assert(z.ticks == 8); 799 | } 800 | 801 | fn sbchl___(z: *Z80) void { 802 | sbcv(z, z.read8(z.r2[HL])); 803 | assert(z.ticks == 8); 804 | } 805 | 806 | fn sbcv(z: *Z80, r: u8) void { 807 | const a = z.r1[A]; 808 | const c = @boolToInt((z.r1[F] & FlagC) != 0); 809 | z.r1[A] = a -% r -% c; 810 | z.r1[F] = FlagS; 811 | 812 | if (a -% r -% c == 0) { 813 | z.r1[F] |= FlagZ; 814 | } 815 | if ((a & 0xf) < (r & 0xf) + c) { 816 | z.r1[F] |= FlagH; 817 | } 818 | if (usize(a) -% usize(r) - c > 0xff) { 819 | z.r1[F] |= FlagC; 820 | } 821 | } 822 | 823 | // A = A + r 824 | fn add__(comptime X: comptime_int) Instruction { 825 | return struct { 826 | fn impl(z: *Z80) void { 827 | addv(z, z.r1[X]); 828 | assert(z.ticks == 4); 829 | } 830 | }.impl; 831 | } 832 | 833 | fn add_a_d8(z: *Z80) void { 834 | const r = z.read8(z.r2[PC]); 835 | z.r2[PC] +%= 1; 836 | addv(z, r); 837 | assert(z.ticks == 8); 838 | } 839 | 840 | fn addhl___(z: *Z80) void { 841 | addv(z, z.read8(z.r2[HL])); 842 | assert(z.ticks == 8); 843 | } 844 | 845 | fn addv(z: *Z80, r: u8) void { 846 | const a = z.r1[A]; 847 | z.r1[A] +%= r; 848 | 849 | if (a +% r == 0) { 850 | z.r1[F] |= FlagZ; 851 | } 852 | if ((a & 0xf) + (r & 0xf) > 0xf) { 853 | z.r1[F] |= FlagH; 854 | } 855 | if (usize(a) + usize(r) > 0xff) { 856 | z.r1[F] |= FlagC; 857 | } 858 | } 859 | 860 | // A = A - r 861 | fn sub__(comptime X: comptime_int) Instruction { 862 | return struct { 863 | fn impl(z: *Z80) void { 864 | subv(z, z.r1[X]); 865 | assert(z.ticks == 4); 866 | } 867 | }.impl; 868 | } 869 | 870 | fn sub_a_d8(z: *Z80) void { 871 | const r = z.read8(z.r2[PC]); 872 | z.r2[PC] +%= 1; 873 | subv(z, r); 874 | assert(z.ticks == 8); 875 | } 876 | 877 | fn subhl___(z: *Z80) void { 878 | subv(z, z.read8(z.r2[HL])); 879 | assert(z.ticks == 8); 880 | } 881 | 882 | fn subv(z: *Z80, r: u8) void { 883 | const a = z.r1[A]; 884 | z.r1[A] -%= r; 885 | z.r1[F] |= FlagS; 886 | 887 | if (a == r) { 888 | z.r1[F] |= FlagZ; 889 | } 890 | if ((a & 0xf) < (r & 0xf)) { 891 | z.r1[F] |= FlagH; 892 | } 893 | if (a < r) { 894 | z.r1[F] |= FlagC; 895 | } 896 | } 897 | 898 | // A = A ^ r 899 | fn xor__(comptime X: comptime_int) Instruction { 900 | return struct { 901 | fn impl(z: *Z80) void { 902 | xorv(z, z.r1[X]); 903 | assert(z.ticks == 4); 904 | } 905 | }.impl; 906 | } 907 | 908 | fn xor_a_d8(z: *Z80) void { 909 | const r = z.read8(z.r2[PC]); 910 | z.r2[PC] +%= 1; 911 | xorv(z, r); 912 | assert(z.ticks == 8); 913 | } 914 | 915 | fn xorhl___(z: *Z80) void { 916 | xorv(z, z.read8(z.r2[HL])); 917 | assert(z.ticks == 8); 918 | } 919 | 920 | fn xorv(z: *Z80, r: u8) void { 921 | z.r1[F] = if (z.r1[A] ^ r == 0) FlagZ else 0; 922 | z.r1[A] ^= r; 923 | } 924 | 925 | // A = A & r 926 | fn and__(comptime X: comptime_int) Instruction { 927 | return struct { 928 | fn impl(z: *Z80) void { 929 | andv(z, z.r1[X]); 930 | assert(z.ticks == 4); 931 | } 932 | }.impl; 933 | } 934 | 935 | fn and_a_d8(z: *Z80) void { 936 | const r = z.read8(z.r2[PC]); 937 | z.r2[PC] +%= 1; 938 | andv(z, r); 939 | assert(z.ticks == 8); 940 | } 941 | 942 | fn andhl___(z: *Z80) void { 943 | andv(z, z.read8(z.r2[HL])); 944 | assert(z.ticks == 8); 945 | } 946 | 947 | fn andv(z: *Z80, r: u8) void { 948 | z.r1[F] = FlagH | if ((z.r1[A] & r) == 0) FlagZ else 0; 949 | z.r1[A] &= r; 950 | } 951 | 952 | // A = A | r 953 | fn or___(comptime X: comptime_int) Instruction { 954 | return struct { 955 | fn impl(z: *Z80) void { 956 | orv(z, z.r1[X]); 957 | assert(z.ticks == 4); 958 | } 959 | }.impl; 960 | } 961 | 962 | fn or_a_d8_(z: *Z80) void { 963 | const r = z.read8(z.r2[PC]); 964 | z.r2[PC] +%= 1; 965 | orv(z, r); 966 | assert(z.ticks == 8); 967 | } 968 | 969 | fn orhl____(z: *Z80) void { 970 | orv(z, z.read8(z.r2[HL])); 971 | assert(z.ticks == 8); 972 | } 973 | 974 | fn orv(z: *Z80, r: u8) void { 975 | z.r1[F] = if ((z.r1[A] | r) == 0) FlagZ else 0; 976 | z.r1[A] |= r; 977 | } 978 | 979 | fn cp___(comptime X: comptime_int) Instruction { 980 | return struct { 981 | fn impl(z: *Z80) void { 982 | cpv(z, z.r1[X]); 983 | assert(z.ticks == 4); 984 | } 985 | }.impl; 986 | } 987 | 988 | fn cp_a_d8_(z: *Z80) void { 989 | const r = z.read8(z.r2[PC]); 990 | z.r2[PC] +%= 1; 991 | cpv(z, r); 992 | assert(z.ticks == 8); 993 | } 994 | 995 | fn cphl____(z: *Z80) void { 996 | cpv(z, z.read8(z.r2[HL])); 997 | assert(z.ticks == 8); 998 | } 999 | 1000 | fn cpv(z: *Z80, r: u8) void { 1001 | const a = z.r1[A]; 1002 | z.r1[F] = FlagS; 1003 | 1004 | if (a == r) { 1005 | z.r1[F] |= FlagZ; 1006 | } 1007 | if ((a & 0xf) < (r & 0xf)) { 1008 | z.r1[F] |= FlagH; 1009 | } 1010 | if (a < r) { 1011 | z.r1[F] |= FlagC; 1012 | } 1013 | } 1014 | 1015 | // These are similar to the generic rlc_ but do not modify the zero flag and are lower latency. 1016 | fn rlca____(z: *Z80) void { 1017 | const r = z.r1[A]; 1018 | const c = @boolToInt(r & 0x80 != 0); 1019 | z.r1[A] = (r << 1) | c; 1020 | z.r1[F] = 0; 1021 | 1022 | if (c != 0) { 1023 | z.r1[F] |= FlagC; 1024 | } 1025 | assert(z.ticks == 4); 1026 | } 1027 | 1028 | fn rla_____(z: *Z80) void { 1029 | const r = z.r1[A]; 1030 | const c = @boolToInt(r & 0x80 != 0); 1031 | const h = z.r1[F] & FlagC != 0; 1032 | z.r1[A] = (r << 1) | c; 1033 | z.r1[F] = 0; 1034 | 1035 | if (h) { 1036 | z.r1[F] |= FlagC; 1037 | } 1038 | assert(z.ticks == 4); 1039 | } 1040 | 1041 | fn rrca____(z: *Z80) void { 1042 | const r = z.r1[A]; 1043 | const c = r & 1; 1044 | z.r1[A] = (r >> 1) | (c << 7); 1045 | z.r1[F] = 0; 1046 | if (c != 0) { 1047 | z.r1[F] |= FlagC; 1048 | } 1049 | } 1050 | 1051 | fn rra_____(z: *Z80) void { 1052 | const r = z.r1[A]; 1053 | const c = r & 1; 1054 | const h = z.r1[F] & FlagC != 0; 1055 | z.r1[A] = (r >> 1) | (c << 7); 1056 | z.r1[F] = 0; 1057 | 1058 | if (h) { 1059 | z.r1[F] |= FlagC; 1060 | } 1061 | assert(z.ticks == 4); 1062 | } 1063 | 1064 | // TODO: Check this as it seems the wrong way around. 1065 | 1066 | const cb_instruction_table = [256]Instruction{ 1067 | // X0, X1, X2, X3, X4, X5, X6, X7, 1068 | // X8, X9, Xa, Xb, Xc, Xd, Xe, Xf, 1069 | rlc__(B), rlc__(C), rlc__(D), rlc__(E), rlc__(H), rlc__(L), rlc_hl__, rlc__(A), // 0X 1070 | rrc__(B), rrc__(C), rrc__(D), rrc__(E), rrc__(H), rrc__(L), rrc_hl__, rrc__(A), 1071 | rl___(B), rl___(C), rl___(D), rl___(E), rl___(H), rl___(L), rl_hl___, rl___(A), // 1X 1072 | rr___(B), rr___(C), rr___(D), rr___(E), rr___(H), rr___(L), rr_hl___, rr___(A), 1073 | sla__(B), sla__(C), sla__(D), sla__(E), sla__(H), sla__(L), sla_hl__, sla__(A), // 2X 1074 | sra__(B), sra__(C), sra__(D), sra__(E), sra__(H), sra__(L), sra_hl__, sra__(A), 1075 | swap_(B), swap_(C), swap_(D), swap_(E), swap_(H), swap_(L), swap_hl_, swap_(A), // 3X 1076 | srl__(B), srl__(C), srl__(D), srl__(E), srl__(H), srl__(L), srl_hl__, srl__(A), 1077 | bt(0, B), bt(0, C), bt(0, D), bt(0, E), bt(0, H), bt(0, L), bt_hl(0), bt(0, A), // 4X 1078 | bt(1, B), bt(1, C), bt(1, D), bt(1, E), bt(1, H), bt(1, L), bt_hl(1), bt(1, A), 1079 | bt(2, B), bt(2, C), bt(2, D), bt(2, E), bt(2, H), bt(2, L), bt_hl(2), bt(2, A), // 5X 1080 | bt(3, B), bt(3, C), bt(3, D), bt(3, E), bt(3, H), bt(3, L), bt_hl(3), bt(3, A), 1081 | bt(4, B), bt(4, C), bt(4, D), bt(4, E), bt(4, H), bt(4, L), bt_hl(4), bt(4, A), // 6X 1082 | bt(5, B), bt(5, C), bt(5, D), bt(5, E), bt(5, H), bt(5, L), bt_hl(5), bt(5, A), 1083 | bt(6, B), bt(6, C), bt(6, D), bt(6, E), bt(6, H), bt(6, L), bt_hl(6), bt(6, A), // 7X 1084 | bt(7, B), bt(7, C), bt(7, D), bt(7, E), bt(7, H), bt(7, L), bt_hl(7), bt(7, A), 1085 | rs(0, B), rs(0, C), rs(0, D), rs(0, E), rs(0, H), rs(0, L), rs_hl(0), rs(0, A), // 8X 1086 | rs(1, B), rs(1, C), rs(1, D), rs(1, E), rs(1, H), rs(1, L), rs_hl(1), rs(1, A), 1087 | rs(2, B), rs(2, C), rs(2, D), rs(2, E), rs(2, H), rs(2, L), rs_hl(2), rs(2, A), // 9X 1088 | rs(3, B), rs(3, C), rs(3, D), rs(3, E), rs(3, H), rs(3, L), rs_hl(3), rs(3, A), 1089 | rs(4, B), rs(4, C), rs(4, D), rs(4, E), rs(4, H), rs(4, L), rs_hl(4), rs(4, A), // aX 1090 | rs(5, B), rs(5, C), rs(5, D), rs(5, E), rs(5, H), rs(5, L), rs_hl(5), rs(5, A), 1091 | rs(6, B), rs(6, C), rs(6, D), rs(6, E), rs(6, H), rs(6, L), rs_hl(6), rs(6, A), // bX 1092 | rs(7, B), rs(7, C), rs(7, D), rs(7, E), rs(7, H), rs(7, L), rs_hl(7), rs(7, A), 1093 | st(0, B), st(0, C), st(0, D), st(0, E), st(0, H), st(0, L), st_hl(0), st(0, A), // cX 1094 | st(1, B), st(1, C), st(1, D), st(1, E), st(1, H), st(1, L), st_hl(1), st(1, A), 1095 | st(2, B), st(2, C), st(2, D), st(2, E), st(2, H), st(2, L), st_hl(2), st(2, A), // dX 1096 | st(3, B), st(3, C), st(3, D), st(3, E), st(3, H), st(3, L), st_hl(3), st(3, A), 1097 | st(4, B), st(4, C), st(4, D), st(4, E), st(4, H), st(4, L), st_hl(4), st(4, A), // eX 1098 | st(5, B), st(5, C), st(5, D), st(5, E), st(5, H), st(5, L), st_hl(5), st(5, A), 1099 | st(6, B), st(6, C), st(6, D), st(6, E), st(6, H), st(6, L), st_hl(6), st(6, A), // fX 1100 | st(7, B), st(7, C), st(7, D), st(7, E), st(7, H), st(7, L), st_hl(7), st(7, A), 1101 | }; 1102 | 1103 | const cb_instruction_names_table = [256][11]u8{ 1104 | "RLC B ", "RLC C ", "RLC D ", "RLC E ", 1105 | "RLC H ", "RLC L ", "RLC (HL) ", "RLC A ", 1106 | "RRC B ", "RRC C ", "RRC D ", "RRC E ", 1107 | "RRC H ", "RRC L ", "RRC (HL) ", "RRC A ", 1108 | 1109 | "RL B ", "RL C ", "RL D ", "RL E ", 1110 | "RL H ", "RL L ", "RL (HL) ", "RL A ", 1111 | "RR B ", "RR C ", "RR D ", "RR E ", 1112 | "RR H ", "RR L ", "RR (HL) ", "RR A ", 1113 | 1114 | "SLA B ", "SLA C ", "SLA D ", "SLA E ", 1115 | "SLA H ", "SLA L ", "SLA (HL) ", "SLA A ", 1116 | "SRA B ", "SRA C ", "SRA D ", "SRA E ", 1117 | "SRA H ", "SRA L ", "SRA (HL) ", "SRA A ", 1118 | 1119 | "SWAP B ", "SWAP C ", "SWAP D ", "SWAP E ", 1120 | "SWAP H ", "SWAP L ", "SWAP (HL) ", "SWAP A ", 1121 | "SRL B ", "SRL C ", "SRL D ", "SRL E ", 1122 | "SRL H ", "SRL L ", "SRL (HL) ", "SRL A ", 1123 | 1124 | "BIT 0,B ", "BIT 0,C ", "BIT 0,D ", "BIT 0,E ", 1125 | "BIT 0,H ", "BIT 0,L ", "BIT 0,(HL) ", "BIT 0,A ", 1126 | "BIT 1,B ", "BIT 1,C ", "BIT 1,D ", "BIT 1,E ", 1127 | "BIT 1,H ", "BIT 1,L ", "BIT 1,(HL) ", "BIT 1,A ", 1128 | 1129 | "BIT 2,B ", "BIT 2,C ", "BIT 2,D ", "BIT 2,E ", 1130 | "BIT 2,H ", "BIT 2,L ", "BIT 2,(HL) ", "BIT 2,A ", 1131 | "BIT 3,B ", "BIT 3,C ", "BIT 3,D ", "BIT 3,E ", 1132 | "BIT 3,H ", "BIT 3,L ", "BIT 3,(HL) ", "BIT 3,A ", 1133 | 1134 | "BIT 4,B ", "BIT 4,C ", "BIT 4,D ", "BIT 4,E ", 1135 | "BIT 4,H ", "BIT 4,L ", "BIT 4,(HL) ", "BIT 4,A ", 1136 | "BIT 5,B ", "BIT 5,C ", "BIT 5,D ", "BIT 5,E ", 1137 | "BIT 5,H ", "BIT 5,L ", "BIT 5,(HL) ", "BIT 5,A ", 1138 | 1139 | "BIT 6,B ", "BIT 4,C ", "BIT 4,D ", "BIT 4,E ", 1140 | "BIT 4,H ", "BIT 4,L ", "BIT 4,(HL) ", "BIT 4,A ", 1141 | "BIT 7,B ", "BIT 7,C ", "BIT 7,D ", "BIT 7,E ", 1142 | "BIT 7,H ", "BIT 7,L ", "BIT 7,(HL) ", "BIT 7,A ", 1143 | 1144 | "RES 0,B ", "RES 0,C ", "RES 0,D ", "RES 0,E ", 1145 | "RES 0,H ", "RES 0,L ", "RES 0,(HL) ", "RES 0,A ", 1146 | "RES 1,B ", "RES 1,C ", "RES 1,D ", "RES 1,E ", 1147 | "RES 1,H ", "RES 1,L ", "RES 1,(HL) ", "RES 1,A ", 1148 | 1149 | "RES 2,B ", "RES 2,C ", "RES 2,D ", "RES 2,E ", 1150 | "RES 2,H ", "RES 2,L ", "RES 2,(HL) ", "RES 2,A ", 1151 | "RES 3,B ", "RES 3,C ", "RES 3,D ", "RES 3,E ", 1152 | "RES 3,H ", "RES 3,L ", "RES 3,(HL) ", "RES 3,A ", 1153 | 1154 | "RES 4,B ", "RES 4,C ", "RES 4,D ", "RES 4,E ", 1155 | "RES 4,H ", "RES 4,L ", "RES 4,(HL) ", "RES 4,A ", 1156 | "RES 5,B ", "RES 5,C ", "RES 5,D ", "RES 5,E ", 1157 | "RES 5,H ", "RES 5,L ", "RES 5,(HL) ", "RES 5,A ", 1158 | 1159 | "RES 6,B ", "RES 4,C ", "RES 4,D ", "RES 4,E ", 1160 | "RES 4,H ", "RES 4,L ", "RES 4,(HL) ", "RES 4,A ", 1161 | "RES 7,B ", "RES 7,C ", "RES 7,D ", "RES 7,E ", 1162 | "RES 7,H ", "RES 7,L ", "RES 7,(HL) ", "RES 7,A ", 1163 | 1164 | "SET 0,B ", "SET 0,C ", "SET 0,D ", "SET 0,E ", 1165 | "SET 0,H ", "SET 0,L ", "SET 0,(HL) ", "SET 0,A ", 1166 | "SET 1,B ", "SET 1,C ", "SET 1,D ", "SET 1,E ", 1167 | "SET 1,H ", "SET 1,L ", "SET 1,(HL) ", "SET 1,A ", 1168 | 1169 | "SET 2,B ", "SET 2,C ", "SET 2,D ", "SET 2,E ", 1170 | "SET 2,H ", "SET 2,L ", "SET 2,(HL) ", "SET 2,A ", 1171 | "SET 3,B ", "SET 3,C ", "SET 3,D ", "SET 3,E ", 1172 | "SET 3,H ", "SET 3,L ", "SET 3,(HL) ", "SET 3,A ", 1173 | 1174 | "SET 4,B ", "SET 4,C ", "SET 4,D ", "SET 4,E ", 1175 | "SET 4,H ", "SET 4,L ", "SET 4,(HL) ", "SET 4,A ", 1176 | "SET 5,B ", "SET 5,C ", "SET 5,D ", "SET 5,E ", 1177 | "SET 5,H ", "SET 5,L ", "SET 5,(HL) ", "SET 5,A ", 1178 | 1179 | "SET 6,B ", "SET 4,C ", "SET 4,D ", "SET 4,E ", 1180 | "SET 4,H ", "SET 4,L ", "SET 4,(HL) ", "SET 4,A ", 1181 | "SET 7,B ", "SET 7,C ", "SET 7,D ", "SET 7,E ", 1182 | "SET 7,H ", "SET 7,L ", "SET 7,(HL) ", "SET 7,A ", 1183 | }; 1184 | 1185 | fn cb______(z: *Z80) void { 1186 | const cb_op = z.read8(z.r2[PC]); 1187 | z.r2[PC] += 1; 1188 | cb_instruction_table[cb_op](z); 1189 | } 1190 | 1191 | // TODO: Merge common paths with a generic write/read 1192 | fn rlc__(comptime X: comptime_int) Instruction { 1193 | return struct { 1194 | fn impl(z: *Z80) void { 1195 | const r = z.r1[X]; 1196 | const c = @boolToInt(r & 0x80 != 0); 1197 | z.r1[F] = 0; 1198 | z.r1[X] = (r << 1) | c; 1199 | 1200 | if (c != 0) { 1201 | z.r1[F] |= FlagC; 1202 | } 1203 | if (r << 1 == 0) { 1204 | z.r1[F] |= FlagZ; 1205 | } 1206 | assert(z.ticks == 8); 1207 | } 1208 | }.impl; 1209 | } 1210 | 1211 | fn rlc_hl__(z: *Z80) void { 1212 | const r = z.read8(z.r2[HL]); 1213 | const c = @boolToInt(r & 0x80 != 0); 1214 | z.r1[F] = 0; 1215 | z.write8(z.r2[HL], (r << 1) | c); 1216 | 1217 | if (c != 0) { 1218 | z.r1[F] |= FlagC; 1219 | } 1220 | if (r << 1 == 0) { 1221 | z.r1[F] |= FlagZ; 1222 | } 1223 | assert(z.ticks == 16); 1224 | } 1225 | 1226 | fn rrc__(comptime X: comptime_int) Instruction { 1227 | return struct { 1228 | fn impl(z: *Z80) void { 1229 | const r = z.r1[X]; 1230 | const c = @boolToInt(r & 0x01 != 0); 1231 | z.r1[F] = 0; 1232 | z.r1[X] = (r >> 1) | (u8(c) << 7); 1233 | 1234 | if (c != 0) { 1235 | z.r1[F] |= FlagC; 1236 | } 1237 | if (r == 0) { 1238 | z.r1[F] |= FlagZ; 1239 | } 1240 | assert(z.ticks == 8); 1241 | } 1242 | }.impl; 1243 | } 1244 | 1245 | fn rrc_hl__(z: *Z80) void { 1246 | const r = z.read8(z.r2[HL]); 1247 | const c = @boolToInt(r & 0x01 != 0); 1248 | z.r1[F] = 0; 1249 | z.write8(z.r2[HL], (r >> 1) | (u8(c) << 7)); 1250 | 1251 | if (c != 0) { 1252 | z.r1[F] |= FlagC; 1253 | } 1254 | if (r == 0) { 1255 | z.r1[F] |= FlagZ; 1256 | } 1257 | assert(z.ticks == 16); 1258 | } 1259 | 1260 | fn rl___(comptime X: comptime_int) Instruction { 1261 | return struct { 1262 | fn impl(z: *Z80) void { 1263 | const r = z.r1[X]; 1264 | const c = @boolToInt(z.r1[F] & FlagC != 0); 1265 | const h = r & 0x80 != 0; 1266 | z.r1[F] = 0; 1267 | z.r1[X] = (r << 1) | c; 1268 | 1269 | if (h) { 1270 | z.r1[F] |= FlagC; 1271 | } 1272 | if (r == 0) { 1273 | z.r1[F] |= FlagZ; 1274 | } 1275 | assert(z.ticks == 8); 1276 | } 1277 | }.impl; 1278 | } 1279 | 1280 | fn rl_hl___(z: *Z80) void { 1281 | const r = z.read8(z.r2[HL]); 1282 | const c = @boolToInt(z.r1[F] & FlagC != 0); 1283 | const h = r & 0x80 != 0; 1284 | z.r1[F] = 0; 1285 | z.write8(z.r2[HL], (r << 1) | c); 1286 | 1287 | if (h) { 1288 | z.r1[F] |= FlagC; 1289 | } 1290 | if (r == 0) { 1291 | z.r1[F] |= FlagZ; 1292 | } 1293 | assert(z.ticks == 16); 1294 | } 1295 | 1296 | fn rr___(comptime X: comptime_int) Instruction { 1297 | return struct { 1298 | fn impl(z: *Z80) void { 1299 | const r = z.r1[X]; 1300 | const c = @boolToInt(z.r1[F] & FlagC != 0); 1301 | const l = 0x01 != 0; 1302 | z.r1[F] = 0; 1303 | z.r1[X] = (r >> 1) | (u8(c) << 7); 1304 | 1305 | if (l) { 1306 | z.r1[F] |= FlagC; 1307 | } 1308 | if (r == 0) { 1309 | z.r1[F] |= FlagZ; 1310 | } 1311 | assert(z.ticks == 8); 1312 | } 1313 | }.impl; 1314 | } 1315 | 1316 | fn rr_hl___(z: *Z80) void { 1317 | const r = z.read8(z.r2[HL]); 1318 | const c = @boolToInt(z.r1[F] & FlagC != 0); 1319 | const l = 0x01 != 0; 1320 | z.r1[F] = 0; 1321 | z.write8(z.r2[HL], (r >> 1) | (u8(c) << 7)); 1322 | 1323 | if (l) { 1324 | z.r1[F] |= FlagC; 1325 | } 1326 | if (r == 0) { 1327 | z.r1[F] |= FlagZ; 1328 | } 1329 | assert(z.ticks == 16); 1330 | } 1331 | 1332 | fn sla__(comptime X: comptime_int) Instruction { 1333 | return struct { 1334 | fn impl(z: *Z80) void { 1335 | const r = z.r1[X]; 1336 | const c = r & 0x80 != 0; 1337 | z.r1[F] = 0; 1338 | z.r1[X] = r << 1; 1339 | 1340 | if (c) { 1341 | z.r1[F] |= FlagC; 1342 | } 1343 | if (r & 0x7f == 0) { 1344 | z.r1[F] |= FlagZ; 1345 | } 1346 | assert(z.ticks == 8); 1347 | } 1348 | }.impl; 1349 | } 1350 | 1351 | fn sla_hl__(z: *Z80) void { 1352 | const r = z.read8(z.r2[HL]); 1353 | const c = r & 0x80 != 0; 1354 | z.r1[F] = 0; 1355 | z.write8(z.r2[HL], r << 1); 1356 | 1357 | if (c) { 1358 | z.r1[F] |= FlagC; 1359 | } 1360 | if (r & 0x7f == 0) { 1361 | z.r1[F] |= FlagZ; 1362 | } 1363 | assert(z.ticks == 16); 1364 | } 1365 | 1366 | fn sra__(comptime X: comptime_int) Instruction { 1367 | return struct { 1368 | fn impl(z: *Z80) void { 1369 | const r = z.r1[X]; 1370 | const h = r & 0x80; 1371 | z.r1[F] = 0; 1372 | z.r1[X] = (r >> 1) | h; 1373 | 1374 | if (r & 1 != 0) { 1375 | z.r1[F] |= FlagC; 1376 | } 1377 | if (z.r1[X] == 0) { 1378 | z.r1[F] |= FlagZ; 1379 | } 1380 | assert(z.ticks == 8); 1381 | } 1382 | }.impl; 1383 | } 1384 | 1385 | fn sra_hl__(z: *Z80) void { 1386 | const r = z.read8(z.r2[HL]); 1387 | const h = r & 0x80; 1388 | z.r1[F] = 0; 1389 | z.write8(z.r2[HL], (r >> 1) | h); 1390 | 1391 | if (r & 1 != 0) { 1392 | z.r1[F] |= FlagC; 1393 | } 1394 | if (r == 0) { 1395 | z.r1[F] |= FlagZ; 1396 | } 1397 | assert(z.ticks == 16); 1398 | } 1399 | 1400 | fn srl__(comptime X: comptime_int) Instruction { 1401 | return struct { 1402 | fn impl(z: *Z80) void { 1403 | const r = z.r1[X]; 1404 | const h = r & 0x80; 1405 | z.r1[F] = 0; 1406 | z.r1[X] = r >> 1; 1407 | 1408 | if (r & 1 != 0) { 1409 | z.r1[F] |= FlagC; 1410 | } 1411 | if (r >> 1 == 0) { 1412 | z.r1[F] |= FlagZ; 1413 | } 1414 | assert(z.ticks == 8); 1415 | } 1416 | }.impl; 1417 | } 1418 | 1419 | fn srl_hl__(z: *Z80) void { 1420 | const r = z.read8(z.r2[HL]); 1421 | const h = r & 0x80; 1422 | z.r1[F] = 0; 1423 | z.write8(z.r2[HL], r >> 1); 1424 | 1425 | if (r & 1 != 0) { 1426 | z.r1[F] |= FlagC; 1427 | } 1428 | if (r >> 1 == 0) { 1429 | z.r1[F] |= FlagZ; 1430 | } 1431 | assert(z.ticks == 16); 1432 | } 1433 | 1434 | fn swap_(comptime X: comptime_int) Instruction { 1435 | return struct { 1436 | fn impl(z: *Z80) void { 1437 | const r = z.r1[X]; 1438 | const h = r & 0x80; 1439 | z.r1[F] = 0; 1440 | z.r1[X] = (r >> 4) | (r << 4); 1441 | 1442 | if (r == 0) { 1443 | z.r1[F] |= FlagZ; 1444 | } 1445 | assert(z.ticks == 8); 1446 | } 1447 | }.impl; 1448 | } 1449 | 1450 | fn swap_hl_(z: *Z80) void { 1451 | const r = z.read8(z.r2[HL]); 1452 | const h = r & 0x80; 1453 | z.r1[F] = 0; 1454 | z.write8(z.r2[HL], (r >> 4) | (r << 4)); 1455 | 1456 | if (r == 0) { 1457 | z.r1[F] |= FlagZ; 1458 | } 1459 | assert(z.ticks == 16); 1460 | } 1461 | 1462 | fn bt_hl(comptime i: u3) Instruction { 1463 | return struct { 1464 | fn impl(z: *Z80) void { 1465 | btv(i, z, z.read8(z.r2[HL])); 1466 | assert(z.ticks == 16); 1467 | } 1468 | }.impl; 1469 | } 1470 | 1471 | fn bt(comptime i: u3, comptime X: comptime_int) Instruction { 1472 | return struct { 1473 | fn impl(z: *Z80) void { 1474 | btv(i, z, z.r1[X]); 1475 | assert(z.ticks == 8); 1476 | } 1477 | }.impl; 1478 | } 1479 | 1480 | fn btv(comptime i: u3, z: *Z80, r: u8) void { 1481 | z.r1[F] = FlagC | FlagH; 1482 | if ((r & (u8(1) << i)) == 0) { 1483 | z.r1[F] |= FlagZ; 1484 | } 1485 | } 1486 | 1487 | fn rs_hl(comptime i: u3) Instruction { 1488 | return struct { 1489 | fn impl(z: *Z80) void { 1490 | // TODO: Check cycle counts here 1491 | var r = z.read8(z.r2[HL]); 1492 | r &= ~(u8(1) << i); 1493 | z.write8(z.r2[HL], r); 1494 | assert(z.ticks == 16); 1495 | } 1496 | }.impl; 1497 | } 1498 | 1499 | fn rs(comptime i: u3, comptime X: comptime_int) Instruction { 1500 | return struct { 1501 | fn impl(z: *Z80) void { 1502 | z.r1[X] &= ~(u8(1) << i); 1503 | assert(z.ticks == 8); 1504 | } 1505 | }.impl; 1506 | } 1507 | 1508 | fn st_hl(comptime i: u3) Instruction { 1509 | return struct { 1510 | fn impl(z: *Z80) void { 1511 | var r = z.read8(z.r2[HL]); 1512 | r |= u8(1) << i; 1513 | z.write8(z.r2[HL], r); 1514 | assert(z.ticks == 16); 1515 | } 1516 | }.impl; 1517 | } 1518 | 1519 | fn st(comptime i: u3, comptime X: comptime_int) Instruction { 1520 | return struct { 1521 | fn impl(z: *Z80) void { 1522 | z.r1[X] |= u8(1) << i; 1523 | assert(z.ticks == 8); 1524 | } 1525 | }.impl; 1526 | } 1527 | -------------------------------------------------------------------------------- /src/gameboy.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const time = std.time; 3 | 4 | const Window = @import("window_sdl.zig").Window; 5 | const irom = @import("rom.zig"); 6 | const immu = @import("mmu.zig"); 7 | const icpu = @import("cpu.zig"); 8 | const igpu = @import("gpu.zig"); 9 | const imbc = @import("mbc.zig"); 10 | 11 | pub const Gb = struct { 12 | mem: [0x10000]u8, 13 | window: Window, 14 | mmu: immu.Mmu, 15 | cpu: icpu.Z80, 16 | gpu: igpu.Gpu(Window), 17 | rom: irom.Rom, 18 | prng: std.rand.DefaultPrng, 19 | 20 | pub fn init(rom_binary: []u8) !Gb { 21 | var buf: [4]u8 = undefined; 22 | try std.crypto.randomBytes(buf[0..]); 23 | const seed = std.mem.readIntSliceLittle(u32, buf[0..4]); 24 | 25 | var gb: Gb = undefined; 26 | gb.rom = try irom.Rom.load(rom_binary); 27 | gb.mmu = immu.Mmu.init(gb.mem[0..], &gb.rom); 28 | gb.cpu = icpu.Z80.init(&gb.mmu); 29 | gb.gpu = igpu.Gpu(Window).init(&gb.mmu, &gb.window); 30 | 31 | gb.prng = std.rand.DefaultPrng.init(seed); 32 | gb.window = try Window.init(&gb.mmu); 33 | 34 | gb.rom.header.debugPrint(); 35 | return gb; 36 | } 37 | 38 | pub fn deinit(gb: *Gb) void { 39 | gb.window.deinit(); 40 | } 41 | 42 | pub fn run(gb: *Gb) void { 43 | // Having a specific boot mbc avoids an extra branch on every ROM/RAM access. 44 | const original_mbc = gb.mmu.mbc; 45 | gb.mmu.mbc = imbc.Mbc{ .Boot = imbc.MbcBoot.init() }; 46 | 47 | // TODO: Handle boot rom write to ROM/RAM 48 | 49 | while (gb.cpu.r2[icpu.PC] != 0x100) { 50 | const frame_end_ticks = gb.cpu.ticks + igpu.frame_cycle_time; 51 | 52 | while (gb.cpu.total_ticks < frame_end_ticks) { 53 | const opcode = gb.cpu.read8(gb.cpu.r2[icpu.PC]); 54 | gb.cpu.r2[icpu.PC] +%= 1; 55 | const cycles = gb.cpu.step(opcode); 56 | gb.gpu.step(cycles); 57 | } 58 | 59 | gb.window.handleEvents() catch return; 60 | time.sleep(16 * time.millisecond); // time.ns_per_s * icpu.clock_speed / igpu.frame_cycle_time); 61 | } 62 | 63 | gb.mmu.mbc = original_mbc; 64 | 65 | gb.prng.random.bytes(gb.mmu.mem[256..0xe000]); 66 | std.mem.copy(u8, gb.mmu.mem[0xe000..0xfdff], gb.mmu.mem[0xc000..0xddff]); 67 | 68 | gb.cpu.r2[icpu.AF] = 0x01b0; 69 | gb.cpu.r2[icpu.BC] = 0x0013; 70 | gb.cpu.r2[icpu.DE] = 0x00d8; 71 | gb.cpu.r2[icpu.HL] = 0x014d; 72 | gb.cpu.r2[icpu.SP] = 0xfffe; 73 | 74 | const A = immu.addresses; 75 | gb.mmu.mem[A.TIMA] = 0x00; 76 | gb.mmu.mem[A.TMA_] = 0x00; 77 | gb.mmu.mem[A.TAC_] = 0x00; 78 | gb.mmu.mem[A.NR10] = 0x80; 79 | gb.mmu.mem[A.NR11] = 0xbf; 80 | gb.mmu.mem[A.NR12] = 0xf3; 81 | gb.mmu.mem[A.NR14] = 0xbf; 82 | gb.mmu.mem[A.NR21] = 0x3f; 83 | gb.mmu.mem[A.NR22] = 0x00; 84 | gb.mmu.mem[A.NR24] = 0xbf; 85 | gb.mmu.mem[A.NR30] = 0x7f; 86 | gb.mmu.mem[A.NR31] = 0xff; 87 | gb.mmu.mem[A.NR32] = 0x9f; 88 | gb.mmu.mem[A.NR33] = 0xbf; 89 | gb.mmu.mem[A.NR41] = 0xff; 90 | gb.mmu.mem[A.NR42] = 0x00; 91 | gb.mmu.mem[A.NR43] = 0x00; 92 | gb.mmu.mem[A.NR44] = 0xbf; 93 | gb.mmu.mem[A.NR50] = 0x77; 94 | gb.mmu.mem[A.NR51] = 0xf3; 95 | gb.mmu.mem[A.NR52] = 0xf1; 96 | gb.mmu.mem[A.LCDC] = 0x91; 97 | gb.mmu.mem[A.SCY_] = 0x00; 98 | gb.mmu.mem[A.SCX_] = 0x00; 99 | gb.mmu.mem[A.LYC_] = 0x00; 100 | gb.mmu.mem[A.BGP_] = 0xfc; 101 | gb.mmu.mem[A.OBP0] = 0xff; 102 | gb.mmu.mem[A.OBP1] = 0xff; 103 | gb.mmu.mem[A.WY__] = 0x00; 104 | gb.mmu.mem[A.WX__] = 0x00; 105 | gb.mmu.mem[A.IE__] = 0x00; 106 | 107 | while (true) { 108 | const frame_end_ticks = gb.cpu.ticks + igpu.frame_cycle_time; 109 | 110 | while (gb.cpu.total_ticks < frame_end_ticks) { 111 | const opcode = gb.cpu.read8(gb.cpu.r2[icpu.PC]); 112 | gb.cpu.r2[icpu.PC] +%= 1; 113 | const cycles = gb.cpu.step(opcode); 114 | gb.gpu.step(cycles); 115 | } 116 | 117 | gb.window.handleEvents() catch return; 118 | time.sleep(16 * time.millisecond); //time.ns_per_s * icpu.clock_speed / igpu.frame_cycle_time); 119 | } 120 | } 121 | }; 122 | -------------------------------------------------------------------------------- /src/gpu.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const immu = @import("mmu.zig"); 3 | const Mmu = immu.Mmu; 4 | const A = immu.addresses; 5 | 6 | const meta = @import("meta.zig"); 7 | 8 | // number of cycles to scan and vblank 9 | pub const frame_cycle_time = 70224; 10 | 11 | pub fn Gpu(comptime Window: type) type { 12 | comptime { 13 | std.debug.assert(meta.hasField(Window, "pixels")); 14 | std.debug.assert(meta.hasFunction(Window, "init")); 15 | std.debug.assert(meta.hasFunction(Window, "render")); 16 | } 17 | 18 | return struct { 19 | const Self = @This(); 20 | 21 | window: *Window, 22 | ticks: usize, 23 | 24 | // NOTE: We bypass the mmu in most cases since we don't have the same R/W restrictions on 25 | // various registers that the mmu imposes. 26 | mmu: *Mmu, 27 | 28 | pub fn init(mmu: *Mmu, window: *Window) Self { 29 | return Self{ 30 | .window = window, 31 | .ticks = 0, 32 | .mmu = mmu, 33 | }; 34 | } 35 | 36 | // swizzle(abcdefgh, ABCDEFGH) = aAbBcCdDeEfFgGhH 37 | fn swizzle(x: u8, y: u8) u16 { 38 | return @truncate(u16, ((u64(x) *% 0x0101010101010101 & 0x8040201008040201) *% 39 | 0x0102040810204081 >> 49) & 0x5555 | 40 | ((u64(y) *% 0x0101010101010101 & 0x8040201008040201) *% 41 | 0x0102040810204081 >> 48) & 0xAAAA); 42 | } 43 | 44 | // NOTE: We could draw the entire frame at once since we don't blit until the end but this 45 | // is more authentic compared to how the screen is actually drawn in hardware. 46 | fn renderScanLine(g: *Self) void { 47 | // We have two tile-maps. Use the correct one and offset by SCY scanlines. 48 | const map_offset = ((g.mmu.mem[A.LY__] +% g.mmu.mem[A.SCY_]) >> 3) + if (g.mmu.mem[A.LCDC] & 0x08 != 0) u16(0x1c00) else 0x1800; 49 | var line_offset = g.mmu.mem[A.SCX_] >> 3; 50 | 51 | const y = (g.mmu.mem[A.LY__] +% g.mmu.mem[A.SCY_]) & 3; 52 | var x = g.mmu.mem[A.SCX_] & 7; 53 | 54 | // Look up the palette order from BGP and display 55 | const palette = [4][3]u8{ 56 | []u8{ 0xff, 0xff, 0xff }, // White 57 | []u8{ 0xaa, 0xaa, 0xaa }, // Light gray 58 | []u8{ 0x55, 0x55, 0x55 }, // Dark gray 59 | []u8{ 0x00, 0x00, 0x00 }, // Black 60 | }; 61 | 62 | // TODO: Can LCDC be modified during the write to switch tiles mid-scroll. 63 | const tile_offset = x % 8; 64 | 65 | if (tile_offset != 0) { 66 | var tile: u16 = g.mmu.vram[map_offset + line_offset]; 67 | if (g.mmu.mem[A.LCDC] & 0x04 != 0 and tile < 128) tile += 256; 68 | line_offset = (line_offset + 1) & 31; 69 | 70 | const b = swizzle(g.mmu.read(2 * tile), g.mmu.read(2 * tile + 1)); 71 | 72 | var px: usize = tile_offset; 73 | while (px < 8) : (px += 1) { 74 | const shift = @truncate(u3, b >> @intCast(u4, 2 * px)); 75 | const c = palette[(g.mmu.mem[A.BGP_] >> shift) & 3]; 76 | g.window.pixels[y][x] = (u32(c[0]) << 24) | (u32(c[1]) << 16) | (u32(c[2]) << 8); 77 | } 78 | } 79 | 80 | var i: usize = 0; 81 | while (i < 160 - tile_offset) : (i += 8) { 82 | var tile: u16 = g.mmu.vram[map_offset + line_offset]; 83 | if (g.mmu.mem[A.LCDC] & 0x04 != 0 and tile < 128) tile += 256; 84 | line_offset = (line_offset + 1) & 31; 85 | 86 | const b = swizzle(g.mmu.read(2 * tile), g.mmu.read(2 * tile + 1)); 87 | 88 | var px: usize = 0; 89 | while (px < 8) : (px += 1) { 90 | const shift = @truncate(u3, b >> @intCast(u4, 2 * px)); 91 | const c = palette[(g.mmu.mem[A.BGP_] >> shift) & 3]; 92 | g.window.pixels[y][x] = (u32(c[0]) << 24) | (u32(c[1]) << 16) | (u32(c[2]) << 8); 93 | } 94 | } 95 | 96 | if (tile_offset != 0) { 97 | var tile: u16 = g.mmu.vram[map_offset + line_offset]; 98 | if (g.mmu.mem[A.LCDC] & 0x04 != 0 and tile < 128) tile += 256; 99 | line_offset = (line_offset + 1) & 31; 100 | 101 | const b = swizzle(g.mmu.read(2 * tile), g.mmu.read(2 * tile + 1)); 102 | 103 | var px: usize = 0; 104 | while (px < 8 - tile_offset) : (px += 1) { 105 | const shift = @truncate(u3, b >> @intCast(u4, 2 * px)); 106 | const c = palette[(g.mmu.mem[A.BGP_] >> shift) & 3]; 107 | g.window.pixels[y][x] = (u32(c[0]) << 24) | (u32(c[1]) << 16) | (u32(c[2]) << 8); 108 | } 109 | } 110 | } 111 | 112 | pub fn step(g: *Self, cycles: usize) void { 113 | g.ticks += cycles; 114 | 115 | const mode = @truncate(u2, g.mmu.mem[A.STAT]); 116 | switch (mode) { 117 | // The LCD controller is in the H-Blank period and 118 | // the CPU can access both the display RAM (8000h-9FFFh) 119 | // and OAM (FE00h-FE9Fh) 120 | 0 => { 121 | if (g.ticks >= 80) { 122 | g.ticks -= 80; 123 | g.mmu.mem[A.STAT] = ~u8(0b11); 124 | g.mmu.mem[A.STAT] |= 0b11; 125 | } 126 | }, 127 | 128 | // The LCD controller is in the V-Blank period (or the 129 | // display is disabled) and the CPU can access both the 130 | // display RAM (8000h-9FFFh) and OAM (FE00h-FE9Fh) 131 | 1 => { 132 | if (g.ticks >= 172) { 133 | g.ticks -= 172; 134 | g.mmu.mem[A.STAT] = ~u8(0b11); 135 | 136 | g.renderScanLine(); 137 | } 138 | }, 139 | 140 | // The LCD controller is reading from OAM memory. 141 | // The CPU access OAM memory (FE00h-FE9Fh) 142 | // during this period. 143 | 2 => { 144 | if (g.ticks >= 204) { 145 | g.ticks -= 204; 146 | g.mmu.mem[A.LY__] += 1; 147 | 148 | if (g.mmu.mem[A.LY__] == 143) { 149 | g.mmu.mem[A.STAT] = ~u8(0b11); 150 | g.mmu.mem[A.STAT] |= 1; 151 | g.window.render(); 152 | } else { 153 | g.mmu.mem[A.STAT] = ~u8(0b11); 154 | g.mmu.mem[A.STAT] |= 2; 155 | } 156 | } 157 | }, 158 | 159 | // The LCD controller is reading from both OAM and VRAM, 160 | // The CPU access OAM and VRAM during this period. 161 | // CGB Mode: Cannot access Palette Data (FF69,FF6B) either. 162 | 3 => { 163 | if (g.ticks >= 456) { 164 | g.ticks -= 456; 165 | g.mmu.mem[A.LY__] += 1; 166 | 167 | if (g.mmu.mem[A.LY__] > 153) { 168 | g.mmu.mem[A.STAT] = ~u8(0b11); 169 | g.mmu.mem[A.STAT] |= 2; 170 | g.mmu.mem[A.LY__] = 0; 171 | } 172 | } 173 | }, 174 | } 175 | } 176 | }; 177 | } 178 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Gameboy = @import("gameboy.zig").Gb; 3 | 4 | pub fn main() !void { 5 | var direct = std.heap.DirectAllocator.init(); 6 | var allocator = &direct.allocator; 7 | 8 | const args = try std.process.argsAlloc(allocator); 9 | defer std.process.argsFree(allocator, args); 10 | 11 | if (args.len < 2) { 12 | std.debug.warn("gameboy \n"); 13 | std.os.exit(1); 14 | } 15 | 16 | const rom = try std.io.readFileAlloc(allocator, args[1]); 17 | defer allocator.free(rom); 18 | 19 | var gb = try Gameboy.init(rom); 20 | defer gb.deinit(); 21 | 22 | gb.run(); 23 | } 24 | -------------------------------------------------------------------------------- /src/mbc.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Rom = @import("rom.zig").Rom; 3 | 4 | pub const Mbc = union(enum) { 5 | Boot: MbcBoot, 6 | None: MbcNone, 7 | 8 | pub fn init(rom: *Rom) Mbc { 9 | switch (rom.header.cartridge_type) { 10 | 0x00 => return Mbc{ .None = MbcNone.init(rom.content) }, 11 | else => unreachable, 12 | } 13 | } 14 | 15 | pub fn read(mbc: *Mbc, address: u16) u8 { 16 | return switch (mbc.*) { 17 | Mbc.Boot => |*m| m.read(address), 18 | Mbc.None => |*m| m.read(address), 19 | }; 20 | } 21 | 22 | pub fn write(mbc: *Mbc, address: u16, value: u8) void { 23 | return switch (mbc.*) { 24 | Mbc.Boot => |*m| m.write(address, value), 25 | Mbc.None => |*m| m.write(address, value), 26 | }; 27 | } 28 | }; 29 | 30 | pub const MbcBoot = struct { 31 | const boot_rom = [256]u8{ 32 | 0x31, 0xFE, 0xFF, 0xAF, 0x21, 0xFF, 0x9F, 0x32, 0xCB, 0x7C, 0x20, 0xFB, 0x21, 0x26, 0xFF, 0x0E, 33 | 0x11, 0x3E, 0x80, 0x32, 0xE2, 0x0C, 0x3E, 0xF3, 0xE2, 0x32, 0x3E, 0x77, 0x77, 0x3E, 0xFC, 0xE0, 34 | 0x47, 0x11, 0x04, 0x01, 0x21, 0x10, 0x80, 0x1A, 0xCD, 0x95, 0x00, 0xCD, 0x96, 0x00, 0x13, 0x7B, 35 | 0xFE, 0x34, 0x20, 0xF3, 0x11, 0xD8, 0x00, 0x06, 0x08, 0x1A, 0x13, 0x22, 0x23, 0x05, 0x20, 0xF9, 36 | 0x3E, 0x19, 0xEA, 0x10, 0x99, 0x21, 0x2F, 0x99, 0x0E, 0x0C, 0x3D, 0x28, 0x08, 0x32, 0x0D, 0x20, 37 | 0xF9, 0x2E, 0x0F, 0x18, 0xF3, 0x67, 0x3E, 0x64, 0x57, 0xE0, 0x42, 0x3E, 0x91, 0xE0, 0x40, 0x04, 38 | 0x1E, 0x02, 0x0E, 0x0C, 0xF0, 0x44, 0xFE, 0x90, 0x20, 0xFA, 0x0D, 0x20, 0xF7, 0x1D, 0x20, 0xF2, 39 | 0x0E, 0x13, 0x24, 0x7C, 0x1E, 0x83, 0xFE, 0x62, 0x28, 0x06, 0x1E, 0xC1, 0xFE, 0x64, 0x20, 0x06, 40 | 0x7B, 0xE2, 0x0C, 0x3E, 0x87, 0xF2, 0xF0, 0x42, 0x90, 0xE0, 0x42, 0x15, 0x20, 0xD2, 0x05, 0x20, 41 | 0x4F, 0x16, 0x20, 0x18, 0xCB, 0x4F, 0x06, 0x04, 0xC5, 0xCB, 0x11, 0x17, 0xC1, 0xCB, 0x11, 0x17, 42 | 0x05, 0x20, 0xF5, 0x22, 0x23, 0x22, 0x23, 0xC9, 0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B, 43 | 0x03, 0x73, 0x00, 0x83, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x08, 0x11, 0x1F, 0x88, 0x89, 0x00, 0x0E, 44 | 0xDC, 0xCC, 0x6E, 0xE6, 0xDD, 0xDD, 0xD9, 0x99, 0xBB, 0xBB, 0x67, 0x63, 0x6E, 0x0E, 0xEC, 0xCC, 45 | 0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E, 0x3c, 0x42, 0xB9, 0xA5, 0xB9, 0xA5, 0x42, 0x4C, 46 | 0x21, 0x04, 0x01, 0x11, 0xA8, 0x00, 0x1A, 0x13, 0xBE, 0x20, 0xFE, 0x23, 0x7D, 0xFE, 0x34, 0x20, 47 | 0xF5, 0x06, 0x19, 0x78, 0x86, 0x23, 0x05, 0x20, 0xFB, 0x86, 0x20, 0xFE, 0x3E, 0x01, 0xE0, 0x50, 48 | }; 49 | 50 | pub fn init() MbcBoot { 51 | return MbcBoot{}; 52 | } 53 | 54 | pub fn read(mbc: *MbcBoot, address: u16) u8 { 55 | switch (address) { 56 | 0x0000...0x0100 => { 57 | return boot_rom[address]; 58 | }, 59 | 60 | else => { 61 | unreachable; 62 | }, 63 | } 64 | } 65 | 66 | pub fn write(mbc: *MbcBoot, address: u16, value: u8) void { 67 | switch (address) { 68 | 0x0000...0x0100 => { 69 | // read-only 70 | }, 71 | 72 | else => { 73 | unreachable; 74 | }, 75 | } 76 | } 77 | }; 78 | 79 | pub const MbcNone = struct { 80 | rom: []u8, 81 | 82 | pub fn init(rom: []u8) MbcNone { 83 | return MbcNone{ .rom = rom }; 84 | } 85 | 86 | pub fn read(mbc: *MbcNone, address: u16) u8 { 87 | switch (address) { 88 | // 32Kb ROM 89 | 0x0000...0x7FFF => { 90 | return mbc.rom[address]; 91 | }, 92 | 93 | // 8Kb RAM 94 | 0xA000...0xBFFF => { 95 | return mbc.rom[address]; 96 | }, 97 | 98 | else => { 99 | unreachable; 100 | }, 101 | } 102 | } 103 | 104 | pub fn write(mbc: *MbcNone, address: u16, value: u8) void { 105 | switch (address) { 106 | // 32Kb ROM 107 | 0x0000...0x7FFF => { 108 | // read-only 109 | }, 110 | 111 | // 8Kb RAM 112 | 0xA000...0xBFFF => { 113 | mbc.rom[address] = value; 114 | }, 115 | 116 | else => { 117 | unreachable; 118 | }, 119 | } 120 | } 121 | }; 122 | -------------------------------------------------------------------------------- /src/meta.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const builtin = @import("builtin"); 3 | 4 | pub fn hasFunction(comptime T: type, name: []const u8) bool { 5 | const info = @typeInfo(T); 6 | for (info.Struct.defs) |def| { 7 | const DataType = @TagType(builtin.TypeInfo.Definition.Data); 8 | 9 | if (DataType(def.data) != DataType.Fn) { 10 | continue; 11 | } 12 | 13 | if (!std.mem.eql(u8, def.name, name)) { 14 | continue; 15 | } 16 | 17 | return true; 18 | } 19 | 20 | return false; 21 | } 22 | 23 | pub fn hasField(comptime T: type, comptime name: []const u8) bool { 24 | const info = @typeInfo(T); 25 | const fields = switch (info) { 26 | builtin.TypeId.Struct => |s| s.fields, 27 | builtin.TypeId.Union => |u| u.fields, 28 | builtin.TypeId.Enum => |e| e.fields, 29 | else => return false, 30 | }; 31 | 32 | for (fields) |field| { 33 | if (std.mem.eql(u8, field.name, name)) { 34 | return true; 35 | } 36 | } 37 | 38 | return false; 39 | } 40 | -------------------------------------------------------------------------------- /src/mmu.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Rom = @import("rom.zig").Rom; 3 | const Mbc = @import("mbc.zig").Mbc; 4 | 5 | pub const Mmu = struct { 6 | mem: []u8, 7 | mbc: Mbc, 8 | 9 | bank0: []u8, 10 | bank1: []u8, 11 | vram: []u8, 12 | ram: []u8, 13 | wram0: []u8, 14 | wram1: []u8, 15 | echo: []u8, 16 | oam: []u8, 17 | io: []u8, 18 | hram: []u8, 19 | 20 | // The JOYP circuitry can be physically switched via a register write to switch between 21 | // diretional or other buttons. We need an extra u8 to handle this in software. 22 | joyp_bit: [2]u8, 23 | joyp_active: u1, 24 | 25 | pub fn init(mem: []u8, rom: *Rom) Mmu { 26 | var mmu: Mmu = undefined; 27 | mmu.mem = mem; 28 | mmu.mbc = Mbc.init(rom); 29 | 30 | mmu.bank0 = mmu.mem[0x0000..0x3FFF]; 31 | mmu.bank1 = mmu.mem[0x4000..0x7FFF]; 32 | mmu.vram = mmu.mem[0x8000..0x9FFF]; 33 | mmu.ram = mmu.mem[0xA000..0xBFFF]; 34 | mmu.wram0 = mmu.mem[0xC000..0xCFFF]; 35 | mmu.wram1 = mmu.mem[0xD000..0xDFFF]; 36 | mmu.echo = mmu.mem[0xE000..0xFDFF]; 37 | mmu.oam = mmu.mem[0xFE00..0xFE9F]; 38 | mmu.io = mmu.mem[0xFF00..0xFF7F]; 39 | mmu.hram = mmu.mem[0xFF80..0xFFFE]; 40 | 41 | mmu.joyp_active = 0; // bit 4 42 | 43 | std.mem.copy(u8, mmu.mem[0..0x8000], rom.content); 44 | return mmu; 45 | } 46 | 47 | pub fn read(self: *Mmu, address: u16) u8 { 48 | switch (address) { 49 | // ROM/RAM banks 50 | 0x0000...0x7FFF, 0xA000...0xBFFF => { 51 | return self.mbc.read(address); 52 | }, 53 | 54 | 0x8000...0x9FFF => { 55 | // VRAM is accessible during Mode 0-2 56 | const mode = @truncate(u2, self.mem[addresses.STAT]); 57 | switch (mode) { 58 | 0, 1, 2 => { 59 | return self.mem[address]; 60 | }, 61 | 3 => { 62 | return 0xFF; // undefined 63 | }, 64 | } 65 | }, 66 | 67 | 0xFE00...0xFE9F => { 68 | // OAM is accessible during Mode 0-1 69 | const mode = @truncate(u2, self.mem[addresses.STAT]); 70 | switch (mode) { 71 | 0, 1 => { 72 | return self.mem[address]; 73 | }, 74 | 2, 3 => { 75 | return 0xFF; // undefined 76 | }, 77 | } 78 | }, 79 | 80 | else => { 81 | return self.mem[address]; 82 | }, 83 | } 84 | } 85 | 86 | pub fn write(self: *Mmu, address: u16, value: u8) void { 87 | switch (address) { 88 | // ROM/RAM banks 89 | 0x0000...0x7FFF, 0xA000...0xBFFF => { 90 | return self.mbc.write(address, value); 91 | }, 92 | 93 | // First 1K of WRAM echos to ECHO RAM and vice-versa 94 | 0xC000...0xDDFF => { 95 | self.mem[address] = value; 96 | self.mem[address + 0x2000] = value; 97 | }, 98 | 99 | 0x8000...0x9FFF => { 100 | // VRAM is accessible during Mode 0-2 101 | const mode = @truncate(u2, self.mem[addresses.STAT]); 102 | switch (mode) { 103 | 0, 1, 2 => { 104 | self.mem[address] = value; 105 | }, 106 | 3 => {}, 107 | } 108 | }, 109 | 110 | 0xE000...0xFDFF => { 111 | self.mem[address] = value; 112 | self.mem[address - 0x2000] = value; 113 | }, 114 | 115 | 0xFE00...0xFE9F => { 116 | // OAM is accessible during Mode 0-1 117 | const mode = @truncate(u2, self.mem[addresses.STAT]); 118 | switch (mode) { 119 | 0, 1 => { 120 | self.mem[address] = value; 121 | }, 122 | 2, 3 => {}, 123 | } 124 | }, 125 | 126 | addresses.JOYP => { 127 | switch (value) { 128 | 0x10 => { 129 | self.joyp_active = 0; 130 | self.mem[addresses.JOYP] = self.joyp_bit[0]; 131 | }, 132 | 0x20 => { 133 | self.joyp_active = 1; 134 | self.mem[addresses.JOYP] = self.joyp_bit[1]; 135 | }, 136 | else => {}, 137 | } 138 | }, 139 | 140 | 0xFF01 => { 141 | self.mem[address] = value & 0xF0; 142 | }, 143 | 144 | 0xFF04 => { 145 | self.mem[address] = 0; 146 | }, 147 | 148 | else => { 149 | self.mem[address] = value; 150 | }, 151 | } 152 | } 153 | }; 154 | 155 | pub const addresses = struct { 156 | // Video Display 157 | pub const LCDC = 0xff40; // LCD Control Register (R/W) 158 | pub const STAT = 0xff41; // LCD Status Register (R/W) 159 | pub const SCY_ = 0xff42; // LCD Scroll Y (R) 160 | pub const SCX_ = 0xff43; // LCD Scroll X (R/W) 161 | pub const LY__ = 0xff44; // LCDC Y-Coordinate (R/W) 162 | pub const LYC_ = 0xff45; // LY Compare (R/W) 163 | pub const WY__ = 0xff4a; // Window Y Position (R/W) 164 | pub const WX__ = 0xff4b; // Window X Position (R/W) 165 | pub const BGP_ = 0xff47; // BG Palette Data (R/W) 166 | pub const OBP0 = 0xff48; // Object Palette 0 Data 167 | pub const OBP1 = 0xff48; // Object Palette 1 Data 168 | pub const DMA_ = 0xff46; // DMA Transfer and Start Address (R/W) 169 | 170 | // Sound Controller 171 | pub const NR10 = 0xff10; // CH1 Sweep Register (R/W) 172 | pub const NR11 = 0xff10; // CH1 Sound length/Wave pattern duty (R/w) 173 | pub const NR12 = 0xff10; // CH1 Volume Envelope (R/W) 174 | pub const NR13 = 0xff10; //CH1 Frequency lo (W) 175 | pub const NR14 = 0xff10; // CH1 Frequency hi (R/W) 176 | 177 | pub const NR21 = 0xff10; // CH2 Sound Length/Wave Pattern Duty (R/W) 178 | pub const NR22 = 0xff10; // CH2 Volume Envelope (R/W) 179 | pub const NR23 = 0xff10; // CH2 Frequency lo (W) 180 | pub const NR24 = 0xff10; // CH2 Frequency hi (R/W) 181 | 182 | pub const NR30 = 0xff10; // CH3 Sound on/off (R/W) 183 | pub const NR31 = 0xff10; // CH3 Sound Length 184 | pub const NR32 = 0xff10; // CH3 Select output level (R/W) 185 | pub const NR33 = 0xff10; // CH3 Frequency lo (W) 186 | pub const NR34 = 0xff10; // CH3 Frequency hi (R/W) 187 | 188 | pub const NR41 = 0xff10; // CH4 Sound Length (R/W) 189 | pub const NR42 = 0xff10; // CH4 Volume Envelope (R/W) 190 | pub const NR43 = 0xff10; // CH4 Polynomial Counter (R/W) 191 | pub const NR44 = 0xff10; // CH4 Counter/consecutive; Initial (R/W) 192 | 193 | pub const NR50 = 0xff10; // Channel control/ON-OFF/Volume (R/W) 194 | pub const NR51 = 0xff10; // Selection of Sound output terminal (R/W) 195 | pub const NR52 = 0xff10; // Sound on/off 196 | 197 | // Joypad Input 198 | pub const P1__ = 0xff00; // Joypad (R/W) 199 | pub const JOYP = P1__; 200 | 201 | // Serial Data Transfer (Link Cable) 202 | pub const SB__ = 0xff01; // Serial Transfer Data (R/W) 203 | pub const SC__ = 0xff02; // Serial Transfer Control (R/W) 204 | 205 | // Timer and Divider Registers 206 | pub const DIV_ = 0xff04; // Divider Register (R/W) 207 | pub const TIMA = 0xff05; // Timer Counter (R/W) 208 | pub const TMA_ = 0xff06; // Timer Modulo (R/W) 209 | pub const TAC_ = 0xff07; // Timer Control (R/W) 210 | 211 | // Interrupts 212 | pub const IE__ = 0xffff; // Interrupt Enable (R/W) 213 | pub const IF__ = 0xff0f; // Interrupt Flag (R/W) 214 | }; 215 | -------------------------------------------------------------------------------- /src/rom.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const rom_header_begin = 0x0100; 4 | const rom_header_end = 0x0150; 5 | 6 | comptime { 7 | std.debug.assert(@sizeOf(RomHeader) == rom_header_end - rom_header_begin); 8 | } 9 | 10 | pub const RomHeader = packed struct { 11 | // 0x0100 .. 0x0103 12 | entry_point: [4]u8, 13 | 14 | // 0x0104 .. 0x0133 15 | nintendo_logo: [48]u8, 16 | 17 | // 0x0134 .. 0x0142 18 | title: [15]u8, 19 | 20 | // 0x0143 21 | cgb_flag: u8, 22 | 23 | // 0x0144 .. 0x0145 24 | new_licensee_code: [2]u8, 25 | 26 | // 0x0146 27 | sgb_flag: u8, 28 | 29 | // 0x0147 30 | cartridge_type: u8, 31 | 32 | // 0x0148 33 | rom_size: u8, 34 | 35 | // 0x0149 36 | ram_size: u8, 37 | 38 | // 0x014A 39 | destination_code: u8, 40 | 41 | // 0x014B 42 | old_licensee_code: u8, 43 | 44 | // 0x014C 45 | mask_rom_version_number: u8, 46 | 47 | // 0x014D 48 | header_checksum: u8, 49 | 50 | // 0x014E .. 0x014F 51 | global_checksum: [2]u8, 52 | 53 | pub fn debugPrint(header: *const RomHeader) void { 54 | // NOTE: Evaluation exceeded 1000 backwards branches if all one printf. 55 | std.debug.warn( 56 | \\entry point : {X} 57 | \\title : {} 58 | \\cgb_flag : {X} = {} 59 | \\new_licensee_code : {X} 60 | \\sgb_flag : {X} = {} 61 | \\cartridge_type : {X} = {} 62 | \\ 63 | , 64 | header.entry_point, 65 | header.title, 66 | header.cgb_flag, 67 | Format.cgbFlag(header.cgb_flag), 68 | header.new_licensee_code, 69 | header.sgb_flag, 70 | Format.sgbFlag(header.sgb_flag), 71 | header.cartridge_type, 72 | Format.cartridgeType(header.cartridge_type), 73 | ); 74 | 75 | std.debug.warn( 76 | \\rom_size : {X} = {} 77 | \\ram_size : {X} = {} 78 | \\destination code : {X} = {} 79 | \\old_licensee code : {X} 80 | \\mask_rom_version_number : {X} 81 | \\header checksum : {X} 82 | \\global checksum : {X} 83 | \\ 84 | , 85 | header.rom_size, 86 | Format.romSize(header.rom_size), 87 | header.ram_size, 88 | Format.ramSize(header.ram_size), 89 | header.destination_code, 90 | Format.destinationCode(header.destination_code), 91 | header.old_licensee_code, 92 | header.mask_rom_version_number, 93 | header.header_checksum, 94 | header.global_checksum, 95 | ); 96 | } 97 | }; 98 | 99 | pub const Rom = struct { 100 | // Maximum 32Kb for now (no banking). 101 | content: []u8, 102 | header: *const RomHeader, 103 | 104 | pub fn load(rom_binary: []u8) !Rom { 105 | var rom: Rom = undefined; 106 | 107 | rom.content = rom_binary; 108 | rom.header = @ptrCast(*const RomHeader, &rom.content[rom_header_begin]); 109 | try verifyRom(&rom); 110 | return rom; 111 | } 112 | 113 | fn verifyRom(rom: *Rom) !void { 114 | // Only handle ROM ONLY cartridges 115 | if (rom.header.cartridge_type != 0x00) { 116 | return error.UnsupportedCartridgeType; 117 | } 118 | 119 | // Only handle 32Kb ROM size only 120 | if (rom.header.rom_size != 0x00) { 121 | return error.UnsupportedRomSize; 122 | } 123 | 124 | if (rom.content.len != 32 * 1024) { 125 | return error.InvalidRomSize; 126 | } 127 | } 128 | }; 129 | 130 | const Format = struct { 131 | fn cgbFlag(value: u8) []const u8 { 132 | return switch (value) { 133 | 0x80 => "CGB plus old gameboys", 134 | 0xC0 => "CGB only", 135 | else => "Part of Title", 136 | }; 137 | } 138 | 139 | fn sgbFlag(value: u8) []const u8 { 140 | return switch (value) { 141 | 0x03 => "SGB support", 142 | else => "No SGB support", 143 | }; 144 | } 145 | 146 | fn cartridgeType(value: u8) ![]const u8 { 147 | return switch (value) { 148 | 0x00 => "ROM ONLY", 149 | 0x01 => "MBC1", 150 | 0x02 => "MBC1+RAM", 151 | 0x03 => "MBC1+RAM+BATTERY", 152 | 0x05 => "MBC2", 153 | 0x06 => "MBC2+BATTERY", 154 | 0x08 => "ROM+RAM", 155 | 0x09 => "ROM+RAM+BATTERY", 156 | 0x0B => "MMM01", 157 | 0x0C => "MMM01+SRAM", 158 | 0x0D => "MMM01+SRAM+BATTERY", 159 | 0x0F => "MBC3+TIMER+BATTERY", 160 | 0x10 => "MBC3+TIMER+RAM+BATTERY", 161 | 0x11 => "MBC3", 162 | 0x12 => "MBC3+RAM", 163 | 0x13 => "MBC3+RAM+BATTERY", 164 | 0x15 => "MBC4", 165 | 0x16 => "MBC4+RAM", 166 | 0x17 => "MBC14+RAM+BATTERY", 167 | 0x19 => "MBC5", 168 | 0x1A => "MBC5+RAM", 169 | 0x1B => "MBC5+RAM+BATTERY", 170 | 0x1C => "MBC5+RUMBLE", 171 | 0x1D => "MBC5+RUMBLE+RAM", 172 | 0x1E => "MBC5+RUMBLE+RAM+BATTERY", 173 | 0x1F => "POCKET CAMERA", 174 | 0xFD => "BANDAI TAMA5", 175 | 0xFE => "HuC3", 176 | 0xFF => "HuC1+RAM+BATTERY", 177 | else => error.InvalidCartridgeType, 178 | }; 179 | } 180 | 181 | fn romSize(value: u8) ![]const u8 { 182 | return switch (value) { 183 | 0x00 => "32Kb (no rom banks)", 184 | 0x01 => "64Kb (4 banks)", 185 | 0x02 => "128Kb (8 banks)", 186 | 0x03 => "256Kb (16 banks)", 187 | 0x04 => "512Kb (32 banks)", 188 | 0x05 => "1Mb (64 banks)", 189 | 0x06 => "2Mb (128 banks)", 190 | 0x07 => "4Mb (256 banks)", 191 | 0x52 => "1.1Mb (72 banks)", 192 | 0x53 => "1.2Mb (80 banks)", 193 | 0x54 => "1.5Mb (96 banks)", 194 | else => error.InvalidRomSize, 195 | }; 196 | } 197 | 198 | fn ramSize(value: u8) ![]const u8 { 199 | return switch (value) { 200 | 0x00 => "None", 201 | 0x01 => "2Kb", 202 | 0x02 => "8Kb", 203 | 0x03 => "32Kb", 204 | else => error.InvalidRamSize, 205 | }; 206 | } 207 | 208 | fn destinationCode(value: u8) ![]const u8 { 209 | return switch (value) { 210 | 0x00 => "Japanese", 211 | 0x01 => "Non-Japanese", 212 | else => error.InvalidDestinationCode, 213 | }; 214 | } 215 | }; 216 | -------------------------------------------------------------------------------- /src/window_dummy.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Mmu = @import("mmu.zig").Mmu; 3 | 4 | pub const NoError = error{None}; 5 | 6 | pub const Window = struct { 7 | pixels: [140][166]u32, 8 | 9 | pub fn init(mmu: *Mmu) NoError!Window { 10 | return Window{ .pixels = undefined }; 11 | } 12 | 13 | pub fn deinit(w: *Window) void {} 14 | 15 | pub fn render(w: *Window) void {} 16 | 17 | pub fn handleEvents(w: *Window) NoError!void {} 18 | }; 19 | -------------------------------------------------------------------------------- /src/window_sdl.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const immu = @import("mmu.zig"); 3 | const Mmu = immu.Mmu; 4 | const A = immu.addresses; 5 | 6 | const c = @cImport({ 7 | @cInclude("SDL2/SDL.h"); 8 | }); 9 | 10 | pub const Window = struct { 11 | renderer: ?*c.SDL_Renderer, 12 | window: ?*c.SDL_Window, 13 | 14 | pixels: [144][160]u32, 15 | mmu: *Mmu, 16 | 17 | pub fn init(mmu: *Mmu) !Window { 18 | var w: Window = undefined; 19 | w.mmu = mmu; 20 | 21 | if (c.SDL_Init(c.SDL_INIT_VIDEO | c.SDL_INIT_AUDIO) != 0) { 22 | return error.FailedToInitSDL; 23 | } 24 | errdefer c.SDL_Quit(); 25 | 26 | if (c.SDL_CreateWindowAndRenderer(160 * 2, 144 * 2, c.SDL_WINDOW_SHOWN | c.SDL_WINDOW_ALLOW_HIGHDPI, &w.window, &w.renderer) != 0) { 27 | return error.FailedToInitWindowAndRenderer; 28 | } 29 | errdefer c.SDL_DestroyWindow(w.window); 30 | 31 | c.SDL_SetWindowResizable(w.window, c.SDL_bool.SDL_FALSE); 32 | 33 | c.SDL_SetWindowTitle(w.window, c"zig-gameboy"); 34 | _ = c.SDL_SetRenderDrawColor(w.renderer, 255, 255, 255, 255); 35 | _ = c.SDL_RenderClear(w.renderer); 36 | _ = c.SDL_RenderPresent(w.renderer); 37 | 38 | return w; 39 | } 40 | 41 | pub fn deinit(w: *Window) void { 42 | c.SDL_DestroyWindow(w.window); 43 | c.SDL_Quit(); 44 | } 45 | 46 | pub fn render(w: *Window) void { 47 | var y: usize = 0; 48 | while (y < 144) : (y += 1) { 49 | var x: usize = 0; 50 | while (x < 160) : (x += 1) { 51 | // TODO: Use surfaces instead and draw directly 52 | _ = c.SDL_SetRenderDrawColor( 53 | w.renderer, 54 | @truncate(u8, w.pixels[y][x] >> 24), 55 | @truncate(u8, w.pixels[y][x] >> 16), 56 | @truncate(u8, w.pixels[y][x] >> 8), 57 | 255, 58 | ); 59 | 60 | _ = c.SDL_RenderDrawPoint(w.renderer, @intCast(c_int, x), @intCast(c_int, y)); 61 | } 62 | } 63 | 64 | _ = c.SDL_RenderPresent(w.renderer); 65 | } 66 | 67 | pub fn handleEvents(w: *Window) !void { 68 | var ev: c.SDL_Event = undefined; 69 | while (c.SDL_PollEvent(&ev) != 0) { 70 | switch (ev.type) { 71 | c.SDL_QUIT => { 72 | return error.Quit; 73 | }, 74 | c.SDL_KEYDOWN => switch (ev.key.keysym.sym) { 75 | c.SDLK_RETURN => w.mmu.joyp_bit[0] |= 0x8, 76 | c.SDLK_SPACE => w.mmu.joyp_bit[0] |= 0x4, 77 | c.SDLK_x => w.mmu.joyp_bit[0] |= 0x2, 78 | c.SDLK_z => w.mmu.joyp_bit[0] |= 0x1, 79 | c.SDLK_DOWN => w.mmu.joyp_bit[1] |= 0x8, 80 | c.SDLK_UP => w.mmu.joyp_bit[1] |= 0x4, 81 | c.SDLK_LEFT => w.mmu.joyp_bit[1] |= 0x2, 82 | c.SDLK_RIGHT => w.mmu.joyp_bit[1] |= 0x1, 83 | else => {}, 84 | }, 85 | c.SDL_KEYUP => switch (ev.key.keysym.sym) { 86 | c.SDLK_RETURN => w.mmu.joyp_bit[0] &= 0xe, 87 | c.SDLK_SPACE => w.mmu.joyp_bit[0] &= 0xd, 88 | c.SDLK_x => w.mmu.joyp_bit[0] &= 0xb, 89 | c.SDLK_z => w.mmu.joyp_bit[0] &= 0x7, 90 | c.SDLK_DOWN => w.mmu.joyp_bit[1] &= 0xe, 91 | c.SDLK_UP => w.mmu.joyp_bit[1] &= 0xd, 92 | c.SDLK_LEFT => w.mmu.joyp_bit[1] &= 0xb, 93 | c.SDLK_RIGHT => w.mmu.joyp_bit[1] &= 0x7, 94 | else => {}, 95 | }, 96 | else => {}, 97 | } 98 | 99 | // Update active keys in joyp on any key change 100 | w.mmu.mem[A.JOYP] = w.mmu.joyp_bit[w.mmu.joyp_active]; 101 | } 102 | } 103 | }; 104 | --------------------------------------------------------------------------------