diff --git a/tests/manyloc/gbemulator/cpu.nim b/tests/manyloc/gbemulator/cpu.nim new file mode 100644 index 0000000000..da615065a4 --- /dev/null +++ b/tests/manyloc/gbemulator/cpu.nim @@ -0,0 +1,392 @@ +# Part of the Nimrod Gameboy emulator. +# Copyright (C) Dominik Picheta. +import mem, gpu +import strutils +type + # m (machine cycles), t (time cycles). + # Ref: http://www.zilog.com/docs/z80/um0080.pdf + TClock = tuple[m, t: int] + + PRegister = ref object + pc, sp: int32 # 16-bit + a, b, c, d, e, H, L, f: int32 # 8-bit + clock: TClock + + PCPU = ref object + clock: TClock + r: PRegister + mem: PMem + + TFlagState = enum + FUnchanged, FSet, FUnset + +const + BitZ = 1 shl 7 + BitN = 1 shl 6 + BitH = 1 shl 5 + BitC = 1 shl 4 + +## Flags +## ----- +## 0x80 (1 shl 7) - (Zero|Z) Last result was zero. +## 0x40 (1 shl 6) - (Operation|N) Set if last operation was a subtraction +## 0x20 (1 shl 5) - (Half carry|H) +## 0x10 (1 shl 4) - (Carry|C) +## 0x08 (1 shl 3) - (Sign flag|S) NOT USED IN GB's Z80 +## 0x04 (1 shl 2) - (Parity/Overflow Flag|P/V) NOT USED IN GB's Z80 + +proc newCPU(mem: PMem): PCPU = + new(result) + new(result.r) + result.mem = mem + +# Split a 16-bit into 8-bit: let (a, b) = (x shr 8 and 0xff, x and 0xff) + +template changeFlag(f: var int32, state: TFlagState, bit: int32) = + case state + of FSet: + f = f or bit + of FUnset: + f = f and (not bit) + else: assert false + +template changeFlags(cpu: PCPU, Z = FUnchanged, N = FUnchanged, + H = FUnchanged, C = FUnchanged) = + if Z != FUnchanged: changeFlag(cpu.r.f, Z, BitZ) + if N != FUnchanged: changeFlag(cpu.r.f, N, BitN) + if H != FUnchanged: changeFlag(cpu.r.f, H, BitH) + if C != FUnchanged: changeFlag(cpu.r.f, C, BitC) + +template isFSet(cpu: PCPU, bit: int32): bool = (cpu.r.f and bit) != 0 + +proc `>>`(b: bool): TFlagState = + if b: return FSet + else: return FUnset + +proc `/<>(cpu.r.register == 0), H = FUnset, N = FUnset, + C = >>(bit7 == 1)) + cpu.r.clock.m = 2 + +template INCrr(cpu: PCPU, r1, r2: expr, flags = false) {.immediate.} = + let x = ((cpu.r.r1 shl 8) or cpu.r.r2) + 1 + let (hi, low) = (x shr 8 and 0xFF, x and 0xFF) + cpu.r.r2 = low; cpu.r.r1 = hi + if flags: + cpu.changeFlags(Z = >>(x == 0), H = >>((x and 0xF) == 0), N = FUnset) + cpu.r.clock.m = 3 + +template DECr(cpu: PCPU, register: expr) {.immediate.} = + cpu.r.register = (cpu.r.register - 1) and 0xFF + cpu.changeFlags(Z = >>(cpu.r.register == 0), + H = >>((cpu.r.register and 0xF) == 0xF), + N = FSet) + + cpu.r.clock.m = 1 + +proc LDSimple(cpu: PCPU, opcode: int32) = + ## All (simple) variants of the LD opcodes end up here. + ## Essentially what we want is a register to be copied to another register. + + case opcode + of 0x40 .. 0x45: + # LD B, B .. LD B, L + # TODO JUST USE A MACRO + assert false + + else: assert false + +proc exec(cpu: PCPU) = + ## Executes the next instruction + let opcode = cpu.mem.readByte(cpu.r.pc) + #echo("OPCODE: 0x", toHex(opcode, 2)) + cpu.r.pc.inc() + case opcode + of 0x06: + # LD B, n + # Load 8-bit immediate into B + LDrn(cpu, b) + of 0x0E: + # LD C, n + # Load 8-bit immediate into C. + LDrn(cpu, c) + of 0x1E: + # LD E, n + LDrn(cpu, e) + of 0x2E: + # LD L, n + LDrn(cpu, L) + of 0x3E: + # LD A, n + # Load 8-bit immediate into A. + LDrn(cpu, a) + + of 0x0C: + # INC c + # Increment C + cpu.r.c = (cpu.r.c + 1) and 0xFF + cpu.r.clock.m = 1 + cpu.changeFlags(Z = >>(cpu.r.c == 0), H = >>((cpu.r.c and 0xF) == 0), + N = FUnset) + + of 0x03: + # INC BC + INCrr(cpu, b, c, true) + of 0x13: + # INC DE + INCrr(cpu, d, e, true) + of 0x23: + # INC HL + # Increment 16-bit HL + INCrr(cpu, H, L, true) + + of 0x05: + # DEC B + # Decrement B + DECr(cpu, b) + of 0x0D: + # DEC C + DECr(cpu, c) + of 0x1D: + # DEC E + DECr(cpu, e) + of 0x2D: + # DEC L + DECr(cpu, L) + of 0x3D: + # DEC A + DECr(cpu, a) + + of 0x11: + # LD DE, nn + # Load 16-bit immediate into DE + cpu.r.e = cpu.mem.readByte(cpu.r.pc) + cpu.r.d = cpu.mem.readByte(cpu.r.pc+1) + cpu.r.pc.inc(2) + cpu.r.clock.m = 3 + + of 0x17: + # RL A + # Rotate A left. + RLr(cpu, a) + + of 0x1A: + # LD A, (DE) + # Load A from address pointed to by DE + cpu.r.a = cpu.mem.readByte((cpu.r.d shl 8) or cpu.r.e) + cpu.r.clock.m = 2 + + of 0x20, 0x28: + # (0x20) JR NZ, n; Relative jump by signed immediate if last result was not zero + # (0x28) JR Z, n; Same as above, but when last result *was* zero. + var x = cpu.mem.readByte(cpu.r.pc) + if x > 127: x = -(((not x) + 1) and 255) + cpu.r.pc.inc + cpu.r.clock.m = 2 + if (opcode == 0x20 and (not isFSet(cpu, BitZ))) or + (opcode == 0x28 and isFSet(cpu, BitZ)): + cpu.r.pc.inc(x); cpu.r.clock.m.inc + of 0x18: + # JR n + # Relative jump by signed immediate + var x = cpu.mem.readByte(cpu.r.pc) + if x > 127: x = -(((not x) + 1) and 255) + cpu.r.pc.inc + cpu.r.pc.inc(x) + cpu.r.clock.m = 3 + + of 0x21: + # LD HL, nn + # Load 16-bit immediate into (registers) H and L + cpu.r.L = cpu.mem.readByte(cpu.r.pc) + cpu.r.H = cpu.mem.readByte(cpu.r.pc+1) + cpu.r.pc.inc(2) + cpu.r.clock.m = 3 + of 0x22: + # LDI (HL), A + # Save A to address pointed by HL and increment HL. + cpu.mem.writeByte((cpu.r.h shl 8) or cpu.r.l, cpu.r.a) + INCrr(cpu, H, L) + # TODO: Should flags be changed? (Z80 ref says they should.) + # cpu.changeFlags(H = FUnset, N = FUnset) + cpu.r.clock.m = 2 + + of 0x31: + # LD SP, nn + # Load 16-bit immediate into (register) SP + cpu.r.sp = cpu.mem.readWord(cpu.r.pc) + cpu.r.pc.inc(2) + cpu.r.clock.m = 3 + of 0x32: + # LDD (HL), A + # Save A to address pointed by HL, and decrement HL + cpu.mem.writeByte((cpu.r.h shl 8) or cpu.r.l, cpu.r.a) + let x = ((cpu.r.h shl 8) or cpu.r.l) - 1 + let (hi, low) = (x shr 8 and 0xFF, x and 0xFF) + cpu.r.L = low; cpu.r.H = hi + # TODO: Should flags be changed? (Z80 ref says they should.) + cpu.r.clock.m = 2 + + + of 0x7B: + # LD A, E; Copy E into A + cpu.r.a = cpu.r.e + cpu.r.clock.m = 1 + + of 0x77: + # LD (HL), A + # Copy A to address pointed by HL + let HL = ((cpu.r.h shl 8) or cpu.r.L) + cpu.mem.writeByte(HL, cpu.r.a) + cpu.r.clock.m = 2 + + of 0xAF: + # XOR A + # Logical XOR against (register) A + cpu.r.a = (cpu.r.a xor cpu.r.a) and 255 # If result is bigger than 255, will be set to 0 + cpu.changeFlags(Z = >>(cpu.r.a == 0), H = FUnset, C = FUnset) + cpu.r.clock.m = 1 + + of 0xC1: + # POP BC + # Pop 16-bit value into BC + POPqq(cpu, b, c) + of 0xD1: + # POP DE + POPqq(cpu, d, e) + of 0xE1: + # POP HL + POPqq(cpu, H, L) + of 0xF1: + # POP AF + POPqq(cpu, a, f) + of 0xC5: + # PUSH BC + # Push 16-bit BC onto stack. + PUSHqq(cpu, b, c) + of 0xD5: + # PUSH DE + PUSHqq(cpu, d, e) + of 0xE5: + # PUSH HL + PUSHqq(cpu, H, L) + of 0xF5: + # PUSH AF + PUSHqq(cpu, a, f) + + of 0xC9: + # RET + # Return to calling routine. + cpu.r.pc = cpu.mem.readWord(cpu.r.sp) + cpu.r.sp.inc(2) + cpu.r.clock.m = 3 + + of 0xCB: + # Extended Ops + let extop = cpu.mem.readByte(cpu.r.pc) + cpu.r.pc.inc + case extop + of 0x11: + # RL C + # Rotate C left. + RLr(cpu, c) + of 0x7C: + # BIT 7, H + # Test whether bit 7 of H is zero + cpu.changeFlags(Z = >>((cpu.r.h and (1 shl 7)) == 0), H = FSet, N = FUnset) + cpu.r.clock.m = 2 + else: + echo "Unknown extended op: 0x", extop.toHex(2) + assert false + + of 0xCD: + # CALL nn + # Call routine at 16-bit location + cpu.r.sp.dec(2) + + # We pushing pc+2 onto the stack because the next two bits are used. Below next line. + cpu.mem.writeWord(cpu.r.sp, cpu.r.pc+2) + cpu.r.pc = cpu.mem.readWord(cpu.r.pc) + cpu.r.clock.m = 5 + of 0xE0: + # LDH (0xFF00 + n), A + # Save A at address pointed to by (0xFF00 + 8-bit immediate). + cpu.mem.writeByte(0xFF00 + cpu.mem.readByte(cpu.r.pc), cpu.r.a) + cpu.r.pc.inc + cpu.r.clock.m = 3 + + of 0xE2: + # LDH (0xFF00 + C), A + # Save A at address pointed to by 0xFF00+C + cpu.mem.writeByte(0xFF00 + cpu.r.c, cpu.r.a) + cpu.r.clock.m = 2 + + of 0xFE: + # CP n; compare 8-bit immediate against A. + # TODO: This may be wrong. Review. + + var n = cpu.mem.readByte(cpu.r.pc) + var sum = cpu.r.a - n + let isNegative = sum < 0 + sum = sum and 0xFF + + cpu.r.pc.inc + cpu.changeFlags(C = >>isNegative, Z = >>(sum == 0), N = FSet, + H = >>((sum and 0xF) > (cpu.r.a and 0xF))) + cpu.r.clock.m = 2 + + of 0xEA: + # LD (nn), A; Save A at given 16-bit address. + cpu.mem.writeByte(cpu.mem.readWord(cpu.r.pc), cpu.r.a) + cpu.r.pc.inc(2) + cpu.r.clock.m = 4 + + of 0x40 .. 0x45, 0x50 .. 0x55, 0x60 .. 0x65, 0x47 .. 0x4D, 0x57 .. 0x5D, + 0x67 .. 0x6D, 0x78 .. 0x7D, 0x4F, 0x5F, 0x6F, 0x7F: + # Simple LD instructions. (Copy register1 to register2) + LDSimple(cpu, opcode) + + else: + echo "Unknown opcode: 0x", opcode.toHex(2) + assert false + +proc next*(cpu: PCPU) = + cpu.exec() + cpu.mem.gpu.next(cpu.r.clock.m) + + +when isMainModule: + var cpu = newCpu(mem.load("/home/dom/code/nimrod/gbemulator/Pokemon_Red.gb")) + while True: + cpu.next() \ No newline at end of file diff --git a/tests/manyloc/gbemulator/cpu.nimrod.cfg b/tests/manyloc/gbemulator/cpu.nimrod.cfg new file mode 100644 index 0000000000..1119d356ba --- /dev/null +++ b/tests/manyloc/gbemulator/cpu.nimrod.cfg @@ -0,0 +1,2 @@ +# This config file marks 'cpu' as the main module. + diff --git a/tests/manyloc/gbemulator/gpu.nim b/tests/manyloc/gbemulator/gpu.nim new file mode 100644 index 0000000000..1f59d79042 --- /dev/null +++ b/tests/manyloc/gbemulator/gpu.nim @@ -0,0 +1,110 @@ +# Part of the Nimrod Gameboy emulator. +# Copyright (C) Dominik Picheta. + +import colors, strutils + +type + TGPUMode = enum + HBlank = 0, VBlank = 1, OAMRead = 2, VRAMRead = 3, + + TBGTile = enum + TileSet0, TileSet1 + TBGMap = enum + TileMap0, TileMap1 + + TPaletteKind = enum + PaletteBG, PaletteObj, PaletteObj1 + + PGPU* = ref object + vram*: array[0 .. 8191, int32] + mode*: TGPUMode + clock: int32 + line: int + palette: array[TPaletteKind, array[0..3, TColor]] # 4 colors. 0-1 (Lightest), 7-8 (Darkest) + scrollY, scrollX: int32 + bgtilemap: TBGMap + bgtileset: TBGTile + +proc newGPU*(): PGPU = + new(result) + + # Set default palette + result.palette[PaletteBG] = [colWhite, rgb(192, 192, 192), rgb(96, 96, 96), colBlack] + +proc getTileAddr(gpu: PGPU, index: int32): int = + ## Returns the address of the beginning of the tile at ``index``. + case gpu.bgtileset + of TileSet0: + return 0x1000+(index*16) # Each tile is 2 bytes (16 bits) + of TileSet1: + return 0x0000+(index*16) + +proc getTileRow(gpu: PGPU, index: int32): array[0..7, int32] = + let tileAddr = getTileAddr(gpu, index) + let y = (gpu.line + gpu.scrollY) and 7 + for px in 0 .. 7: + result[px] = gpu.vram[tileAddr+px] or (gpu.vram[tileAddr+px+1] shl 8) + +proc renderLine*(gpu: PGPU) = + echo("Render Line: ", gpu.line, " scrollX: ", gpu.scrollX, " scrollY: ", gpu.scrollY) + var mapOffset = if gpu.bgTileMap == TileMap0: 0x1800 else: 0x1C00 + # Get the line of tiles to use. + mapOffset.inc(((gpu.line + gpu.scrollY) and 0xFF) shr 3) + echo("MapOffset: ", mapOffset.toHex(4)) + + let lineOffset = (gpu.scrollX shr 3) # Which tile to start with in the map line + + # Get tile index from background map + var tileIndex = gpu.vram[mapOffset + lineOffset] + + echo("Tile index: ", tileIndex) + + echo("bgtileset: ", gpu.bgtileset) + + + let tile = getTileRow(gpu, tileIndex) + + let y = (gpu.line + gpu.scrollY) and 7 + let x = gpu.scrollX and 7 + let surfaceOffset = gpu.line * 160 * 4 + +proc next*(gpu: PGPU, time: int) = + gpu.clock.inc(time) + #echo("GPU Mode: ", gpu.mode) + case gpu.mode + of OAMRead: + if gpu.clock >= 80: + # Enter VRAMRead + gpu.mode = VRAMRead + gpu.clock = 0 + of VRAMRead: + if gpu.clock >= 172: + # Enter HBlank + gpu.mode = HBlank + gpu.clock = 0 + + # Render scanline + gpu.renderLine() + of HBlank: + if gpu.clock >= 204: + gpu.clock = 0 + gpu.line.inc + #echo("HBlank line: ", gpu.line) + if gpu.line == 143: + # We reached the bottom edge of the screen (screen is 144 pixels in height.) + # Enter VBlank + gpu.mode = VBlank + + # TODO: Render surface on screen. + else: + gpu.mode = OAMRead + of VBlank: + if gpu.clock >= 456: + gpu.clock = 0 + + # TODO: according to ref, line should be increased and, should wait + # for line to be greater than 153? then restart the line and mode? + echo("Vblank done. Line = ", gpu.line) + gpu.mode = OAMRead + gpu.line = 0 + \ No newline at end of file diff --git a/tests/manyloc/gbemulator/mem.nim b/tests/manyloc/gbemulator/mem.nim new file mode 100644 index 0000000000..953f97dfee --- /dev/null +++ b/tests/manyloc/gbemulator/mem.nim @@ -0,0 +1,173 @@ +# Part of the Nimrod Gameboy emulator. +# Copyright (C) Dominik Picheta. + +import os, strutils, unsigned +import gpu + +type + PMem* = ref object + rom: string + gameName*: string + cartType*, romSize*, ramSize*: char + bios: array[0 .. 255, int32] # 255 Bytes + extRAM: array[0 .. 8191, int32] # 8KB + workRAM: array[0 .. 8191, int32] # 8KB + zeroRAM: array[0 .. 127, int32] + gpu*: PGPU + +proc hexdump(s: string) = + for c in s: + stdout.write(c.ord.BiggestInt.toHex(2) & " ") + echo("") + +const + NintendoGraphic = + [ + '\xCE', '\xED', '\x66', '\x66', '\xCC', '\x0D', '\x00', '\x0B', + '\x03', '\x73', '\x00', '\x83', '\x00', '\x0C', '\x00', '\x0D', + '\x00', '\x08', '\x11', '\x1F', '\x88', '\x89', '\x00', '\x0E', + '\xDC', '\xCC', '\x6E', '\xE6', '\xDD', '\xDD', '\xD9', '\x99', + '\xBB', '\xBB', '\x67', '\x63', '\x6E', '\x0E', '\xEC', '\xCC', + '\xDD', '\xDC', '\x99', '\x9F', '\xBB', '\xB9', '\x33', '\x3E' + ] + bios = [ + 0x31'i32, 0xFE, 0xFF, 0xAF, 0x21, 0xFF, 0x9F, 0x32, 0xCB, 0x7C, 0x20, 0xFB, 0x21, 0x26, 0xFF, 0x0E, + 0x11, 0x3E, 0x80, 0x32, 0xE2, 0x0C, 0x3E, 0xF3, 0xE2, 0x32, 0x3E, 0x77, 0x77, 0x3E, 0xFC, 0xE0, + 0x47, 0x11, 0x04, 0x01, 0x21, 0x10, 0x80, 0x1A, 0xCD, 0x95, 0x00, 0xCD, 0x96, 0x00, 0x13, 0x7B, + 0xFE, 0x34, 0x20, 0xF3, 0x11, 0xD8, 0x00, 0x06, 0x08, 0x1A, 0x13, 0x22, 0x23, 0x05, 0x20, 0xF9, + 0x3E, 0x19, 0xEA, 0x10, 0x99, 0x21, 0x2F, 0x99, 0x0E, 0x0C, 0x3D, 0x28, 0x08, 0x32, 0x0D, 0x20, + 0xF9, 0x2E, 0x0F, 0x18, 0xF3, 0x67, 0x3E, 0x64, 0x57, 0xE0, 0x42, 0x3E, 0x91, 0xE0, 0x40, 0x04, + 0x1E, 0x02, 0x0E, 0x0C, 0xF0, 0x44, 0xFE, 0x90, 0x20, 0xFA, 0x0D, 0x20, 0xF7, 0x1D, 0x20, 0xF2, + 0x0E, 0x13, 0x24, 0x7C, 0x1E, 0x83, 0xFE, 0x62, 0x28, 0x06, 0x1E, 0xC1, 0xFE, 0x64, 0x20, 0x06, + 0x7B, 0xE2, 0x0C, 0x3E, 0x87, 0xF2, 0xF0, 0x42, 0x90, 0xE0, 0x42, 0x15, 0x20, 0xD2, 0x05, 0x20, + 0x4F, 0x16, 0x20, 0x18, 0xCB, 0x4F, 0x06, 0x04, 0xC5, 0xCB, 0x11, 0x17, 0xC1, 0xCB, 0x11, 0x17, + 0x05, 0x20, 0xF5, 0x22, 0x23, 0x22, 0x23, 0xC9, 0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B, + 0x03, 0x73, 0x00, 0x83, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x08, 0x11, 0x1F, 0x88, 0x89, 0x00, 0x0E, + 0xDC, 0xCC, 0x6E, 0xE6, 0xDD, 0xDD, 0xD9, 0x99, 0xBB, 0xBB, 0x67, 0x63, 0x6E, 0x0E, 0xEC, 0xCC, + 0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E, 0x3c, 0x42, 0xB9, 0xA5, 0xB9, 0xA5, 0x42, 0x4C, + 0x21, 0x04, 0x01, 0x11, 0xA8, 0x00, 0x1A, 0x13, 0xBE, 0x20, 0xFE, 0x23, 0x7D, 0xFE, 0x34, 0x20, + 0xF5, 0x06, 0x19, 0x78, 0x86, 0x23, 0x05, 0x20, 0xFB, 0x86, 0x20, 0xFE, 0x3E, 0x01, 0xE0, 0x50 + ] + +proc verifyBytes[R](raw: var string, bytes: array[R, char], start: int): bool = + result = true + for i in 0 .. bytes.high: + if raw[i+start] != bytes[i]: + return false + +proc getBytes(raw: var string, dest: var string, slice: TSlice[int]) = + assert slice.a < slice.b + assert slice.a > 0 + let len = slice.b - slice.a + dest = newString(len) + for i in 0 .. len: + dest[i] = raw[slice.a + i] + +proc load*(path: string): PMem = + new result + result.rom = readFile(path) + + # Verify Nintendo graphic + # TODO: Proper exceptions. + #doAssert result.rom.verifyBytes(NintendoGraphic, 0x0104) + + # Gather meta data. + result.gameName = result.rom[0x0134 .. 0x0142] + echo("Game is: " & result.gameName) + + doAssert result.rom[0x0143].ord != 0x80 # 0x80 here means the ROM is for Color GB. + + result.cartType = result.rom[0x0147] + result.romSize = result.rom[0x0148] + result.ramSize = result.rom[0x0149] + result.bios = bios + + result.GPU = newGPU() + +proc reset*(mem: PMem) = + for i in 0..mem.extRam.len: mem.extRam[i] = 0 + for i in 0..mem.workRam.len: mem.workRam[i] = 0 + +proc readByte*(mem: PMem, address: int32): int32 = + if (address in {0x104 .. 0x133}): + echo("Bios is accessing the location of the nintendo logo: ", address.toHex(4)) + case (address and 0xF000) + of 0x0000: + # BIOS + if address > mem.bios.high: return 0 # TODO: Correct? + return mem.bios[address] + of 0x1000, 0x2000, 0x3000: + return mem.rom[address.int].ord + of 0x4000, 0x5000, 0x6000, 0x7000: + # ROM Bank 1 + of 0x8000, 0x9000: + # VRAM + return mem.GPU.vram[address and 0x1FFF] + of 0xF000: + case address and 0x0F00 + of 0x0F00: + if address > 0xFF7F: + return mem.zeroRAM[address and 0x007F] + + assert false + else: + assert false + else: + echo("Read ", address.toHex(4)) + assert false + +proc readWord*(mem: PMem, address: int32): int32 = + return readByte(mem, address) or (readByte(mem, address+1) shl 8) + +proc writeByte*(mem: PMem, address: int32, b: int32) = + case (address and 0xF000) + of 0x8000, 0x9000: + # VRAM + # Each pixel is 2 bits. VRAM is the tileset. + echo("VRAM. Address: 0x", toHex(address, 4), " Value: ", toHex(b, 4)) + mem.GPU.vram[address and 0x1FFF] = b + + of 0xF000: + case address and 0x0F00 + of 0x0F00: + if address > 0xFF7F: + #echo("ZeroRam. Address: ", toHex(address, 4), " Value: ", toHex(b, 4)) + mem.zeroRAM[address and 0x007F] = b + return + + case address + of 0xFF11: + # TODO: + echo("Sound Mode 1 register (0xFF11): ", b.toHex(4)) + of 0xFF26: + # TODO: + echo("Sound on/off (0xFF26): ", b.toHex(4)) + of 0xFF47: + # TODO: + echo("BG Palette (0xFF47): ", b.toHex(4)) + else: + echo("Interrupts. Address: 0x", toHex(Address, 4), " Value: ", toHex(b, 4)) + else: + echo("0xF000. Address: 0x", toHex(Address, 4), " Value: ", toHex(b, 4)) + + else: + echo("writeByte. Address: 0x", toHex(address, 4), " Value: ", toHex(b, 4)) + +proc writeWord*(mem: PMem, address: int32, w: int32) = + mem.writeByte(address, w and 255) + mem.writeByte(address+1, w shr 8) + +when isMainModule: + var rom = load("/home/dom/code/nimrod/gbemulator/Pokemon_Red.gb") + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/manyloc/gbemulator/readme.markdown b/tests/manyloc/gbemulator/readme.markdown new file mode 100644 index 0000000000..7961912241 --- /dev/null +++ b/tests/manyloc/gbemulator/readme.markdown @@ -0,0 +1,12 @@ +# gbemulator + +Inspired by the Rust NES emulator I have decided to write a Gameboy emulator in +my favourite programming language. + +## References + +http://www.devrs.com/gb/files/gbspec.txt +http://www.zilog.com/docs/z80/um0080.pdf +https://github.com/Two9A/jsGB/blob/master/js +https://github.com/grantgalitz/GameBoy-Online/blob/master/js/GameBoyCore.js +http://imrannazar.com/Gameboy-Z80-Opcode-Map \ No newline at end of file diff --git a/tests/specials.nim b/tests/specials.nim index b5c49ec3c0..173ebd2f80 100644 --- a/tests/specials.nim +++ b/tests/specials.nim @@ -178,7 +178,9 @@ proc runJsTests(r: var TResults, options: string) = for t in os.walkFiles("tests/js/t*.nim"): test(t) - for testfile in ["texceptions", "texcpt1", "texcsub", "tfinally", "tfinally2", "tfinally3", "tactiontable", "tmultim1", "tmultim3", "tmultim4"]: + for testfile in ["texceptions", "texcpt1", "texcsub", "tfinally", + "tfinally2", "tfinally3", "tactiontable", "tmultim1", + "tmultim3", "tmultim4"]: test "tests/run/" & testfile & ".nim" # ------------------------- register special tests here ----------------------- @@ -195,6 +197,26 @@ proc runSpecialTests(r: var TResults, options: string) = proc rejectSpecialTests(r: var TResults, options: string) = rejectThreadTests(r, options) +proc findMainFile(dir: string): string = + # finds the file belonging to ".nimrod.cfg"; if there is no such file + # it returns the some ".nim" file if there is only one: + const cfgExt = ".nimrod.cfg" + result = "" + var nimFiles = 0 + for kind, file in os.walkDir(dir): + if kind == pcFile: + if file.endsWith(cfgExt): return file[.. -(cfgExt.len+1)] & ".nim" + elif file.endsWith(".nim"): + if result.len == 0: result = file + inc nimFiles + if nimFiles != 1: result.setlen(0) + +proc compileManyLoc(r: var TResults, options: string) = + for kind, dir in os.walkDir("tests/manyloc"): + if kind == pcDir: + let mainfile = findMainFile(dir) + compileSingleTest(r, mainfile, options) + proc compileSpecialTests(r: var TResults, options: string) = compileRodFiles(r, options) @@ -203,6 +225,8 @@ proc compileSpecialTests(r: var TResults, options: string) = compileDLLTests(r, options) compileDebuggerTests(r, options) + + compileManyLoc(r, options) #var given = callCompiler("nimrod i", "nimrod i", options) #r.addResult("nimrod i", given.msg, if given.err: reFailure else: reSuccess) diff --git a/todo.txt b/todo.txt index 74e6188847..0639844c87 100644 --- a/todo.txt +++ b/todo.txt @@ -18,6 +18,7 @@ version 0.9.2 Bugs ==== +- docgen: sometimes effects are listed twice - even the easiest range checking is not performed anymore - 'result' is not properly cleaned for NRVO - instantiated generics are listed in error messages