tester supports large tests
This commit is contained in:
parent
8281bd9f0f
commit
09fc94c4c2
7 changed files with 715 additions and 1 deletions
392
tests/manyloc/gbemulator/cpu.nim
Normal file
392
tests/manyloc/gbemulator/cpu.nim
Normal file
|
|
@ -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 `/<</`(v: int32, interval: int): int32 =
|
||||||
|
## Circular shift 8-bit value left ``interval`` times.
|
||||||
|
let leftMost = v shr (8-interval)
|
||||||
|
result = ((v shl interval) or leftMost).int32
|
||||||
|
result = result and 0xFF
|
||||||
|
|
||||||
|
template LDrn(cpu: PCPU, register: expr) {.immediate.} =
|
||||||
|
cpu.r.register = cpu.mem.readByte(cpu.r.pc)
|
||||||
|
inc(cpu.r.pc)
|
||||||
|
cpu.r.clock.m = 2
|
||||||
|
|
||||||
|
template PUSHqq(cpu: PCPU, r1, r2: expr) {.immediate.} =
|
||||||
|
cpu.r.sp.dec
|
||||||
|
cpu.mem.writeByte(cpu.r.sp, cpu.r.r1)
|
||||||
|
cpu.r.sp.dec
|
||||||
|
cpu.mem.writeByte(cpu.r.sp, cpu.r.r2)
|
||||||
|
cpu.r.clock.m = 3
|
||||||
|
|
||||||
|
template POPqq(cpu: PCPU, r1, r2: expr) {.immediate.} =
|
||||||
|
## r1 = High, r2 = Low
|
||||||
|
cpu.r.r2 = cpu.mem.readByte(cpu.r.sp)
|
||||||
|
cpu.r.sp.inc
|
||||||
|
cpu.r.r1 = cpu.mem.readByte(cpu.r.sp)
|
||||||
|
cpu.r.sp.inc
|
||||||
|
cpu.r.clock.m = 3
|
||||||
|
|
||||||
|
template RLr(cpu: PCPU, register: expr) {.immediate.} =
|
||||||
|
let bit7 = cpu.r.register shl 7
|
||||||
|
let prevCarry = cpu.isFSet(BitC)
|
||||||
|
cpu.r.register = cpu.r.register /<</ 1
|
||||||
|
if prevCarry: cpu.r.register = cpu.r.register or 1 # Set Bit 0
|
||||||
|
else: cpu.r.register = cpu.r.register and (not 1) # Unset bit 0
|
||||||
|
cpu.changeFlags(Z = >>(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()
|
||||||
2
tests/manyloc/gbemulator/cpu.nimrod.cfg
Normal file
2
tests/manyloc/gbemulator/cpu.nimrod.cfg
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
# This config file marks 'cpu' as the main module.
|
||||||
|
|
||||||
110
tests/manyloc/gbemulator/gpu.nim
Normal file
110
tests/manyloc/gbemulator/gpu.nim
Normal file
|
|
@ -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
|
||||||
|
|
||||||
173
tests/manyloc/gbemulator/mem.nim
Normal file
173
tests/manyloc/gbemulator/mem.nim
Normal file
|
|
@ -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")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
12
tests/manyloc/gbemulator/readme.markdown
Normal file
12
tests/manyloc/gbemulator/readme.markdown
Normal file
|
|
@ -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
|
||||||
|
|
@ -178,7 +178,9 @@ proc runJsTests(r: var TResults, options: string) =
|
||||||
|
|
||||||
for t in os.walkFiles("tests/js/t*.nim"):
|
for t in os.walkFiles("tests/js/t*.nim"):
|
||||||
test(t)
|
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"
|
test "tests/run/" & testfile & ".nim"
|
||||||
|
|
||||||
# ------------------------- register special tests here -----------------------
|
# ------------------------- register special tests here -----------------------
|
||||||
|
|
@ -195,6 +197,26 @@ proc runSpecialTests(r: var TResults, options: string) =
|
||||||
proc rejectSpecialTests(r: var TResults, options: string) =
|
proc rejectSpecialTests(r: var TResults, options: string) =
|
||||||
rejectThreadTests(r, options)
|
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) =
|
proc compileSpecialTests(r: var TResults, options: string) =
|
||||||
compileRodFiles(r, options)
|
compileRodFiles(r, options)
|
||||||
|
|
||||||
|
|
@ -204,6 +226,8 @@ proc compileSpecialTests(r: var TResults, options: string) =
|
||||||
compileDLLTests(r, options)
|
compileDLLTests(r, options)
|
||||||
compileDebuggerTests(r, options)
|
compileDebuggerTests(r, options)
|
||||||
|
|
||||||
|
compileManyLoc(r, options)
|
||||||
|
|
||||||
#var given = callCompiler("nimrod i", "nimrod i", options)
|
#var given = callCompiler("nimrod i", "nimrod i", options)
|
||||||
#r.addResult("nimrod i", given.msg, if given.err: reFailure else: reSuccess)
|
#r.addResult("nimrod i", given.msg, if given.err: reFailure else: reSuccess)
|
||||||
#if not given.err: inc(r.passed)
|
#if not given.err: inc(r.passed)
|
||||||
|
|
|
||||||
1
todo.txt
1
todo.txt
|
|
@ -18,6 +18,7 @@ version 0.9.2
|
||||||
Bugs
|
Bugs
|
||||||
====
|
====
|
||||||
|
|
||||||
|
- docgen: sometimes effects are listed twice
|
||||||
- even the easiest range checking is not performed anymore
|
- even the easiest range checking is not performed anymore
|
||||||
- 'result' is not properly cleaned for NRVO
|
- 'result' is not properly cleaned for NRVO
|
||||||
- instantiated generics are listed in error messages
|
- instantiated generics are listed in error messages
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue