Spaces:
Sleeping
Sleeping
Upload 2 files
Browse files- gb_console.py +373 -0
- gb_sm83.py +237 -0
gb_console.py
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
gb_console.py -- the full DMG console around the SM83 core: MMU with MBC1/MBC5
|
| 3 |
+
banking, the complete PPU (background + window + sprites, per-dot pixel emission,
|
| 4 |
+
variable-length mode 3 with the SCX fine-scroll penalty, STAT/LYC and VBlank
|
| 5 |
+
interrupt lines, OAM DMA), the DIV/TIMA timer, joypad, and serial-out capture.
|
| 6 |
+
|
| 7 |
+
The PPU's pixel datapath runs through the units API: tile-row decode, palette
|
| 8 |
+
apply, and the sprite-priority mux are gb_units units (golden fns or verified
|
| 9 |
+
neural nets); the rest is address wiring and scheduling -- the orchestrator the
|
| 10 |
+
README describes as standard cycle-accurate emulator engineering.
|
| 11 |
+
|
| 12 |
+
Simplifications (documented, identical in golden and neural runs so comparisons
|
| 13 |
+
stay meaningful): OAM DMA copies instantly at the write; no sprite-fetch mode-3
|
| 14 |
+
stalls (mode 3 length = 172 + SCX%8); no OAM/VRAM access blocking by mode.
|
| 15 |
+
"""
|
| 16 |
+
import numpy as np
|
| 17 |
+
from gb_sm83 import SM83, IF_VBLANK, IF_STAT, IF_TIMER
|
| 18 |
+
|
| 19 |
+
DOTS_PER_LINE, LINES_TOTAL, LINES_VIS = 456, 154, 144
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class PPU:
|
| 23 |
+
def __init__(self, bus, units, render=True):
|
| 24 |
+
self.bus = bus; self.u = units; self.render = render
|
| 25 |
+
self.fb = np.zeros((LINES_VIS, 160), np.uint8)
|
| 26 |
+
self.ly = 0; self.lx = 0; self.mode = 2
|
| 27 |
+
self.win_line = 0; self.stat_line = 0
|
| 28 |
+
self.frame = 0; self.frame_done = False
|
| 29 |
+
self.line_sprites = []
|
| 30 |
+
self.bg_cache = None; self.win_cache = None
|
| 31 |
+
|
| 32 |
+
# ---- per-line setup ----
|
| 33 |
+
def oam_scan(self):
|
| 34 |
+
io = self.bus.io; oam = self.bus.oam
|
| 35 |
+
h = 16 if io[0x40] & 0x04 else 8
|
| 36 |
+
sel = []
|
| 37 |
+
for i in range(40):
|
| 38 |
+
sy = int(oam[i * 4])
|
| 39 |
+
if sy <= self.ly + 16 < sy + h:
|
| 40 |
+
sel.append(i)
|
| 41 |
+
if len(sel) == 10: break
|
| 42 |
+
# DMG priority: lower X wins, ties by OAM order (stable sort)
|
| 43 |
+
self.line_sprites = sorted(sel, key=lambda i: int(oam[i * 4 + 1]))
|
| 44 |
+
|
| 45 |
+
def sprite_pixel(self, x):
|
| 46 |
+
"""Winning sprite (idx, palette#, bg-priority) at screen x, or None."""
|
| 47 |
+
io = self.bus.io; oam = self.bus.oam; vram = self.bus.vram
|
| 48 |
+
h = 16 if io[0x40] & 0x04 else 8
|
| 49 |
+
for i in self.line_sprites:
|
| 50 |
+
sx = int(oam[i * 4 + 1])
|
| 51 |
+
if not (sx <= x + 8 < sx + 8):
|
| 52 |
+
continue
|
| 53 |
+
sy, tile, attr = int(oam[i * 4]), int(oam[i * 4 + 2]), int(oam[i * 4 + 3])
|
| 54 |
+
row = self.ly + 16 - sy
|
| 55 |
+
if attr & 0x40: row = h - 1 - row # Y flip
|
| 56 |
+
if h == 16: tile = (tile & 0xFE) | (row >> 3); row &= 7
|
| 57 |
+
base = tile * 16 + row * 2
|
| 58 |
+
idx = self.u.tilerow(int(vram[base]), int(vram[base + 1]))
|
| 59 |
+
col = x + 8 - sx
|
| 60 |
+
if attr & 0x20: col = 7 - col # X flip
|
| 61 |
+
ci = idx[col]
|
| 62 |
+
if ci != 0:
|
| 63 |
+
return ci, (attr >> 4) & 1, (attr >> 7) & 1
|
| 64 |
+
return None
|
| 65 |
+
|
| 66 |
+
def bg_pixel(self, x):
|
| 67 |
+
io = self.bus.io; vram = self.bus.vram
|
| 68 |
+
lcdc = int(io[0x40])
|
| 69 |
+
use_win = (lcdc & 0x20) and self.ly >= int(io[0x4A]) and x >= int(io[0x4B]) - 7
|
| 70 |
+
if use_win:
|
| 71 |
+
wy = self.win_line; wx = x - (int(io[0x4B]) - 7)
|
| 72 |
+
map_base = 0x1C00 if lcdc & 0x40 else 0x1800
|
| 73 |
+
ty, tx, fy, fx = wy >> 3, wx >> 3, wy & 7, wx & 7
|
| 74 |
+
key = ("w", tx, ty)
|
| 75 |
+
cache = self.win_cache
|
| 76 |
+
else:
|
| 77 |
+
sy = (self.ly + int(io[0x42])) & 0xFF; sx = (x + int(io[0x43])) & 0xFF
|
| 78 |
+
map_base = 0x1C00 if lcdc & 0x08 else 0x1800
|
| 79 |
+
ty, tx, fy, fx = sy >> 3, sx >> 3, sy & 7, sx & 7
|
| 80 |
+
key = ("b", tx, ty)
|
| 81 |
+
cache = self.bg_cache
|
| 82 |
+
if cache is None or cache[0] != key:
|
| 83 |
+
tid = int(vram[map_base + ty * 32 + tx])
|
| 84 |
+
if lcdc & 0x10: base = tid * 16 + fy * 2
|
| 85 |
+
else: base = 0x1000 + (tid - 256 if tid > 127 else tid) * 16 + fy * 2
|
| 86 |
+
row = self.u.tilerow(int(vram[base]), int(vram[base + 1]))
|
| 87 |
+
cache = (key, row, fy)
|
| 88 |
+
if use_win: self.win_cache = cache
|
| 89 |
+
else: self.bg_cache = cache
|
| 90 |
+
elif cache[2] != fy:
|
| 91 |
+
tid = int(vram[map_base + ty * 32 + tx])
|
| 92 |
+
if lcdc & 0x10: base = tid * 16 + fy * 2
|
| 93 |
+
else: base = 0x1000 + (tid - 256 if tid > 127 else tid) * 16 + fy * 2
|
| 94 |
+
cache = (key, self.u.tilerow(int(vram[base]), int(vram[base + 1])), fy)
|
| 95 |
+
if use_win: self.win_cache = cache
|
| 96 |
+
else: self.bg_cache = cache
|
| 97 |
+
return cache[1][fx], use_win
|
| 98 |
+
|
| 99 |
+
def emit_pixel(self, x):
|
| 100 |
+
io = self.bus.io
|
| 101 |
+
lcdc = int(io[0x40])
|
| 102 |
+
bg_ci = 0; used_win = False
|
| 103 |
+
if lcdc & 0x01:
|
| 104 |
+
bg_ci, used_win = self.bg_pixel(x)
|
| 105 |
+
sp = self.sprite_pixel(x) if lcdc & 0x02 else None
|
| 106 |
+
if sp is not None and self.u.sprmux(bg_ci, sp[0], sp[2]):
|
| 107 |
+
shade = self.u.palette(sp[0], int(io[0x49 if sp[1] else 0x48]))
|
| 108 |
+
else:
|
| 109 |
+
shade = self.u.palette(bg_ci, int(io[0x47])) if lcdc & 0x01 else 0
|
| 110 |
+
self.fb[self.ly, x] = shade
|
| 111 |
+
return used_win
|
| 112 |
+
|
| 113 |
+
# ---- STAT machinery ----
|
| 114 |
+
def update_stat(self):
|
| 115 |
+
io = self.bus.io
|
| 116 |
+
coinc = int(self.ly == int(io[0x45]))
|
| 117 |
+
io[0x41] = (io[0x41] & 0xF8) | (coinc << 2) | self.mode
|
| 118 |
+
line = ((int(io[0x41] >> 6) & 1) and coinc) or \
|
| 119 |
+
(((io[0x41] >> 5) & 1) and self.mode == 2) or \
|
| 120 |
+
(((io[0x41] >> 4) & 1) and self.mode == 1) or \
|
| 121 |
+
(((io[0x41] >> 3) & 1) and self.mode == 0)
|
| 122 |
+
line = int(bool(line))
|
| 123 |
+
if line and not self.stat_line:
|
| 124 |
+
self.bus.req_if(IF_STAT)
|
| 125 |
+
self.stat_line = line
|
| 126 |
+
|
| 127 |
+
def set_mode(self, m):
|
| 128 |
+
if m != self.mode:
|
| 129 |
+
self.mode = m
|
| 130 |
+
self.update_stat()
|
| 131 |
+
|
| 132 |
+
# ---- the dot clock ----
|
| 133 |
+
def tick(self, dots):
|
| 134 |
+
io = self.bus.io
|
| 135 |
+
if not (io[0x40] & 0x80): # LCD off
|
| 136 |
+
self.ly = 0; self.lx = 0; self.mode = 0
|
| 137 |
+
io[0x44] = 0
|
| 138 |
+
return
|
| 139 |
+
if not self.render:
|
| 140 |
+
self.tick_fast(dots)
|
| 141 |
+
return
|
| 142 |
+
for _ in range(dots):
|
| 143 |
+
lx, ly = self.lx, self.ly
|
| 144 |
+
if ly < LINES_VIS:
|
| 145 |
+
if lx == 0:
|
| 146 |
+
self.oam_scan(); self.set_mode(2)
|
| 147 |
+
self.bg_cache = self.win_cache = None
|
| 148 |
+
self.win_used = False
|
| 149 |
+
elif lx == 80:
|
| 150 |
+
self.set_mode(3)
|
| 151 |
+
scx_pen = int(io[0x43]) & 7
|
| 152 |
+
px = lx - 80 - scx_pen
|
| 153 |
+
if self.render and 80 <= lx and 0 <= px < 160:
|
| 154 |
+
if self.emit_pixel(px):
|
| 155 |
+
self.win_used = True
|
| 156 |
+
if lx == 80 + scx_pen + 172:
|
| 157 |
+
self.set_mode(0)
|
| 158 |
+
if not self.render:
|
| 159 |
+
# window line counter must advance even without rendering
|
| 160 |
+
lcdc = int(io[0x40])
|
| 161 |
+
if (lcdc & 0x20) and ly >= int(io[0x4A]) and int(io[0x4B]) - 7 < 160:
|
| 162 |
+
self.win_used = True
|
| 163 |
+
self.lx += 1
|
| 164 |
+
if self.lx == DOTS_PER_LINE:
|
| 165 |
+
self.lx = 0
|
| 166 |
+
if ly < LINES_VIS and getattr(self, "win_used", False):
|
| 167 |
+
self.win_line += 1
|
| 168 |
+
self.ly += 1
|
| 169 |
+
if self.ly == LINES_VIS:
|
| 170 |
+
self.set_mode(1); self.bus.req_if(IF_VBLANK)
|
| 171 |
+
elif self.ly == LINES_TOTAL:
|
| 172 |
+
self.ly = 0; self.win_line = 0
|
| 173 |
+
self.frame += 1; self.frame_done = True
|
| 174 |
+
io[0x44] = self.ly
|
| 175 |
+
self.update_stat()
|
| 176 |
+
|
| 177 |
+
def tick_fast(self, dots):
|
| 178 |
+
"""No pixel emission: jump between mode boundaries instead of per-dot.
|
| 179 |
+
LY/mode/STAT/VBlank/window-line behavior identical to the per-dot path."""
|
| 180 |
+
io = self.bus.io
|
| 181 |
+
while dots:
|
| 182 |
+
if self.lx == 0 and self.ly < LINES_VIS:
|
| 183 |
+
self.set_mode(2)
|
| 184 |
+
scx_pen = int(io[0x43]) & 7
|
| 185 |
+
m3_end = 80 + scx_pen + 172
|
| 186 |
+
if self.ly < LINES_VIS:
|
| 187 |
+
nxt = 80 if self.lx < 80 else (m3_end if self.lx < m3_end else DOTS_PER_LINE)
|
| 188 |
+
else:
|
| 189 |
+
nxt = DOTS_PER_LINE
|
| 190 |
+
step = min(dots, nxt - self.lx)
|
| 191 |
+
self.lx += step; dots -= step
|
| 192 |
+
if self.ly < LINES_VIS:
|
| 193 |
+
if self.lx == 80: self.set_mode(3)
|
| 194 |
+
elif self.lx == m3_end and self.mode == 3:
|
| 195 |
+
self.set_mode(0)
|
| 196 |
+
lcdc = int(io[0x40])
|
| 197 |
+
if (lcdc & 0x20) and self.ly >= int(io[0x4A]) and int(io[0x4B]) - 7 < 160:
|
| 198 |
+
self.win_line += 1
|
| 199 |
+
if self.lx == DOTS_PER_LINE:
|
| 200 |
+
self.lx = 0; self.ly += 1
|
| 201 |
+
if self.ly == LINES_VIS:
|
| 202 |
+
self.set_mode(1); self.bus.req_if(IF_VBLANK)
|
| 203 |
+
elif self.ly == LINES_TOTAL:
|
| 204 |
+
self.ly = 0; self.win_line = 0
|
| 205 |
+
self.frame += 1; self.frame_done = True
|
| 206 |
+
io[0x44] = self.ly
|
| 207 |
+
self.update_stat()
|
| 208 |
+
|
| 209 |
+
|
| 210 |
+
class Bus:
|
| 211 |
+
def __init__(self, rom, units, render=True):
|
| 212 |
+
self.rom = np.frombuffer(bytes(rom), dtype=np.uint8)
|
| 213 |
+
cart = self.rom[0x147]
|
| 214 |
+
self.mbc = 5 if cart in (0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E) else \
|
| 215 |
+
(1 if cart in (0x01, 0x02, 0x03) else 0)
|
| 216 |
+
self.rom_bank = 1; self.ram_bank = 0; self.ram_on = False
|
| 217 |
+
self.cart_ram = np.zeros(0x8000, np.uint8)
|
| 218 |
+
self.vram = np.zeros(0x2000, np.uint8)
|
| 219 |
+
self.wram = np.zeros(0x2000, np.uint8)
|
| 220 |
+
self.oam = np.zeros(0xA0, np.uint8)
|
| 221 |
+
self.hram = np.zeros(0x7F, np.uint8)
|
| 222 |
+
self.io = np.zeros(0x80, np.uint8)
|
| 223 |
+
self.ie = 0
|
| 224 |
+
self.io[0x40] = 0x91; self.io[0x41] = 0x85; self.io[0x47] = 0xFC
|
| 225 |
+
self.io[0x00] = 0xCF; self.io[0x0F] = 0xE1
|
| 226 |
+
self.divt = 0xAB00
|
| 227 |
+
self.serial = []
|
| 228 |
+
self.keys = set() # subset of {"right","left","up","down","a","b","select","start"}
|
| 229 |
+
self.ppu = PPU(self, units, render)
|
| 230 |
+
self.cycles = 0
|
| 231 |
+
|
| 232 |
+
def req_if(self, bit): self.io[0x0F] |= bit
|
| 233 |
+
def clear_IF(self, bit): self.io[0x0F] &= ~bit & 0xFF
|
| 234 |
+
def io_IE(self): return self.ie
|
| 235 |
+
def io_IF(self): return int(self.io[0x0F])
|
| 236 |
+
|
| 237 |
+
# ---- timer: divt is the t-cycle counter; DIV is bits 8..15. TIMA increments on
|
| 238 |
+
# falling edges of the TAC-selected bit, i.e. once per 2^(bit+1) t-cycles ----
|
| 239 |
+
TAC_BITS = {0: 9, 1: 3, 2: 5, 3: 7}
|
| 240 |
+
def timer_tick(self, t):
|
| 241 |
+
old = self.divt
|
| 242 |
+
self.divt += t
|
| 243 |
+
tac = int(self.io[0x07])
|
| 244 |
+
if tac & 4:
|
| 245 |
+
period = 1 << (self.TAC_BITS[tac & 3] + 1)
|
| 246 |
+
n = self.divt // period - old // period
|
| 247 |
+
if n:
|
| 248 |
+
tima = int(self.io[0x05]) + n
|
| 249 |
+
if tima > 0xFF:
|
| 250 |
+
tma = int(self.io[0x06])
|
| 251 |
+
tima = tma + (tima - 0x100) % max(1, 0x100 - tma)
|
| 252 |
+
self.req_if(IF_TIMER)
|
| 253 |
+
self.io[0x05] = tima
|
| 254 |
+
|
| 255 |
+
def tick(self, m):
|
| 256 |
+
self.cycles += m
|
| 257 |
+
self.timer_tick(4 * m)
|
| 258 |
+
self.ppu.tick(4 * m)
|
| 259 |
+
|
| 260 |
+
# ---- address decode ----
|
| 261 |
+
def read(self, a):
|
| 262 |
+
self.tick(1)
|
| 263 |
+
return self._read(a)
|
| 264 |
+
def _read(self, a):
|
| 265 |
+
a &= 0xFFFF
|
| 266 |
+
if a < 0x4000:
|
| 267 |
+
return int(self.rom[a])
|
| 268 |
+
if a < 0x8000:
|
| 269 |
+
bank = self.rom_bank % max(1, len(self.rom) // 0x4000)
|
| 270 |
+
return int(self.rom[bank * 0x4000 + (a - 0x4000)])
|
| 271 |
+
if a < 0xA000: return int(self.vram[a - 0x8000])
|
| 272 |
+
if a < 0xC000:
|
| 273 |
+
if not self.ram_on: return 0xFF
|
| 274 |
+
return int(self.cart_ram[(self.ram_bank * 0x2000 + (a - 0xA000)) % 0x8000])
|
| 275 |
+
if a < 0xE000: return int(self.wram[a - 0xC000])
|
| 276 |
+
if a < 0xFE00: return int(self.wram[a - 0xE000])
|
| 277 |
+
if a < 0xFEA0: return int(self.oam[a - 0xFE00])
|
| 278 |
+
if a < 0xFF00: return 0xFF
|
| 279 |
+
if a == 0xFF00:
|
| 280 |
+
sel = int(self.io[0x00]); low = 0x0F
|
| 281 |
+
if not sel & 0x10: # d-pad selected (low = pressed)
|
| 282 |
+
for bit, k in enumerate(("right", "left", "up", "down")):
|
| 283 |
+
if k in self.keys: low &= ~(1 << bit)
|
| 284 |
+
if not sel & 0x20: # buttons selected
|
| 285 |
+
for bit, k in enumerate(("a", "b", "select", "start")):
|
| 286 |
+
if k in self.keys: low &= ~(1 << bit)
|
| 287 |
+
return (sel & 0x30) | 0xC0 | low
|
| 288 |
+
if a == 0xFF04: return (self.divt >> 8) & 0xFF
|
| 289 |
+
if a == 0xFFFF: return self.ie
|
| 290 |
+
if a < 0xFF80: return int(self.io[a - 0xFF00])
|
| 291 |
+
return int(self.hram[a - 0xFF80])
|
| 292 |
+
|
| 293 |
+
def write(self, a, v):
|
| 294 |
+
self.tick(1)
|
| 295 |
+
self._write(a, v)
|
| 296 |
+
def _write(self, a, v):
|
| 297 |
+
a &= 0xFFFF; v &= 0xFF
|
| 298 |
+
if a < 0x8000:
|
| 299 |
+
if self.mbc == 5:
|
| 300 |
+
if a < 0x2000: self.ram_on = (v & 0x0F) == 0x0A
|
| 301 |
+
elif a < 0x3000: self.rom_bank = (self.rom_bank & 0x100) | v
|
| 302 |
+
elif a < 0x4000: self.rom_bank = (self.rom_bank & 0xFF) | ((v & 1) << 8)
|
| 303 |
+
elif a < 0x6000: self.ram_bank = v & 0x0F
|
| 304 |
+
elif self.mbc == 1:
|
| 305 |
+
if a < 0x2000: self.ram_on = (v & 0x0F) == 0x0A
|
| 306 |
+
elif a < 0x4000:
|
| 307 |
+
v &= 0x1F; self.rom_bank = (self.rom_bank & 0x60) | (v if v else 1)
|
| 308 |
+
elif a < 0x6000: self.rom_bank = (self.rom_bank & 0x1F) | ((v & 3) << 5)
|
| 309 |
+
return
|
| 310 |
+
if a < 0xA000: self.vram[a - 0x8000] = v; return
|
| 311 |
+
if a < 0xC000:
|
| 312 |
+
if self.ram_on:
|
| 313 |
+
self.cart_ram[(self.ram_bank * 0x2000 + (a - 0xA000)) % 0x8000] = v
|
| 314 |
+
return
|
| 315 |
+
if a < 0xE000: self.wram[a - 0xC000] = v; return
|
| 316 |
+
if a < 0xFE00: self.wram[a - 0xE000] = v; return
|
| 317 |
+
if a < 0xFEA0: self.oam[a - 0xFE00] = v; return
|
| 318 |
+
if a < 0xFF00: return
|
| 319 |
+
if a == 0xFF04: self.divt = 0; return
|
| 320 |
+
if a == 0xFF46: # OAM DMA (instant copy)
|
| 321 |
+
self.io[0x46] = v
|
| 322 |
+
src = v << 8
|
| 323 |
+
for i in range(0xA0):
|
| 324 |
+
self.oam[i] = self._read(src + i)
|
| 325 |
+
return
|
| 326 |
+
if a == 0xFF02 and v & 0x80: # serial transfer start
|
| 327 |
+
self.serial.append(int(self.io[0x01]))
|
| 328 |
+
self.io[0x02] = v & 0x7F
|
| 329 |
+
return
|
| 330 |
+
if a == 0xFF41: # STAT: low 3 bits read-only
|
| 331 |
+
self.io[0x41] = (self.io[0x41] & 0x07) | (v & 0xF8)
|
| 332 |
+
self.ppu.update_stat(); return
|
| 333 |
+
if a == 0xFF44: return # LY read-only
|
| 334 |
+
if a == 0xFFFF: self.ie = v; return
|
| 335 |
+
if a < 0xFF80: self.io[a - 0xFF00] = v; return
|
| 336 |
+
self.hram[a - 0xFF80] = v
|
| 337 |
+
|
| 338 |
+
|
| 339 |
+
class Console:
|
| 340 |
+
def __init__(self, rom, units, render=True):
|
| 341 |
+
self.bus = Bus(rom, units, render)
|
| 342 |
+
self.cpu = SM83(self.bus, units)
|
| 343 |
+
def run_frame(self, max_steps=5_000_000):
|
| 344 |
+
self.bus.ppu.frame_done = False
|
| 345 |
+
for _ in range(max_steps):
|
| 346 |
+
self.cpu.step()
|
| 347 |
+
if self.bus.ppu.frame_done:
|
| 348 |
+
return
|
| 349 |
+
raise RuntimeError("frame did not complete")
|
| 350 |
+
def serial_text(self):
|
| 351 |
+
return bytes(self.bus.serial).decode("ascii", "replace")
|
| 352 |
+
|
| 353 |
+
# ---- state snapshot, for switching units mid-run ----
|
| 354 |
+
def snapshot(self):
|
| 355 |
+
c, b = self.cpu, self.bus
|
| 356 |
+
return dict(
|
| 357 |
+
regs=(c.A, c.B, c.C, c.D, c.E, c.H, c.L, c.fZ, c.fN, c.fH, c.fC,
|
| 358 |
+
c.SP, c.PC, c.IME, c.ei_pending, c.halted),
|
| 359 |
+
bank=(b.rom_bank, b.ram_bank, b.ram_on, b.divt),
|
| 360 |
+
vram=b.vram.copy(), wram=b.wram.copy(), oam=b.oam.copy(),
|
| 361 |
+
hram=b.hram.copy(), io=b.io.copy(), ie=b.ie, cart_ram=b.cart_ram.copy(),
|
| 362 |
+
ppu=(b.ppu.ly, b.ppu.lx, b.ppu.mode, b.ppu.win_line, b.ppu.stat_line,
|
| 363 |
+
b.ppu.frame))
|
| 364 |
+
def restore(self, s):
|
| 365 |
+
c, b = self.cpu, self.bus
|
| 366 |
+
(c.A, c.B, c.C, c.D, c.E, c.H, c.L, c.fZ, c.fN, c.fH, c.fC,
|
| 367 |
+
c.SP, c.PC, c.IME, c.ei_pending, c.halted) = s["regs"]
|
| 368 |
+
b.rom_bank, b.ram_bank, b.ram_on, b.divt = s["bank"]
|
| 369 |
+
b.vram[:] = s["vram"]; b.wram[:] = s["wram"]; b.oam[:] = s["oam"]
|
| 370 |
+
b.hram[:] = s["hram"]; b.io[:] = s["io"]; b.ie = s["ie"]
|
| 371 |
+
b.cart_ram[:] = s["cart_ram"]
|
| 372 |
+
(b.ppu.ly, b.ppu.lx, b.ppu.mode, b.ppu.win_line, b.ppu.stat_line,
|
| 373 |
+
b.ppu.frame) = s["ppu"]
|
gb_sm83.py
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
gb_sm83.py -- the COMPLETE SM83 (LR35902) core: all 256 base + 256 CB opcodes,
|
| 3 |
+
stack/CALL/RET/RST, 16-bit arithmetic, EI/DI/RETI/HALT and the full interrupt
|
| 4 |
+
sequence, at M-CYCLE bus granularity.
|
| 5 |
+
|
| 6 |
+
Two properties carry the methodology through:
|
| 7 |
+
1. Every data transformation goes through the units API (gb_units.GoldenUnits or
|
| 8 |
+
gb_units.NeuralUnits) -- decode, ALU, rotates, BIT/RES/SET, DAA, all of it.
|
| 9 |
+
One step() implementation serves both, so golden-vs-neural comparison tests
|
| 10 |
+
the units, never two different orchestrators.
|
| 11 |
+
2. 16-bit arithmetic is COMPOSED from the verified 8-bit ADC/SBC by rippling the
|
| 12 |
+
carry lo->hi (INC/DEC rr, ADD HL,rr, ADD SP,e, LD HL,SP+e), exactly like the
|
| 13 |
+
hardware. No unit ever sees a domain it wasn't exhaustively verified on.
|
| 14 |
+
|
| 15 |
+
M-cycle commit: the core never touches memory directly -- every access goes through
|
| 16 |
+
bus.read/bus.write, and the bus ticks the rest of the machine (PPU dots, timer) one
|
| 17 |
+
m-cycle per access plus explicit internal cycles. A write therefore lands on the dot
|
| 18 |
+
stream at its true position INSIDE the instruction, closing the gap the per-dot brick
|
| 19 |
+
documented.
|
| 20 |
+
"""
|
| 21 |
+
from gb_units import (NOP, LD_NN_SP, STOP, JR, JRcc, LDrrnn, ADDHLrr, LDmA, LDAm,
|
| 22 |
+
INCrr, DECrr, INCr, DECr, LDrn, ROTA, DAAc, CPLc, SCFc, CCFc,
|
| 23 |
+
LDrr_, HALTc, ALUr, ALUn, RETcc, LDHnA, ADDSPe, LDHAn, LDHLSPe,
|
| 24 |
+
POPrr, RETc, RETIc, JPHL, LDSPHL, JPcc, LDCA, LDnnA, LDAC,
|
| 25 |
+
LDAnn, JPnn, CBPREF, DIc, EIc, CALLcc, PUSHrr, CALLnn, RSTc, ILL)
|
| 26 |
+
|
| 27 |
+
IF_VBLANK, IF_STAT, IF_TIMER, IF_SERIAL, IF_JOYPAD = 1, 2, 4, 8, 16
|
| 28 |
+
ROTNAMES = ["RLC", "RRC", "RL", "RR", "SLA", "SRA", "SWAP", "SRL"]
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class SM83:
|
| 32 |
+
def __init__(self, bus, units):
|
| 33 |
+
self.bus = bus; self.u = units
|
| 34 |
+
# post-bootrom DMG state
|
| 35 |
+
self.A, self.B, self.C, self.D, self.E, self.H, self.L = 0x01, 0x00, 0x13, 0x00, 0xD8, 0x01, 0x4D
|
| 36 |
+
self.fZ, self.fN, self.fH, self.fC = 1, 0, 1, 1 # F = 0xB0
|
| 37 |
+
self.SP, self.PC = 0xFFFE, 0x0100
|
| 38 |
+
self.IME = False; self.ei_pending = False; self.halted = False
|
| 39 |
+
self.instr_count = 0
|
| 40 |
+
|
| 41 |
+
# ---- register plumbing (wiring, not logic) ----
|
| 42 |
+
def get_hl(self): return (self.H << 8) | self.L
|
| 43 |
+
def set_hl(self, v): self.H, self.L = (v >> 8) & 0xFF, v & 0xFF
|
| 44 |
+
def F(self): return (self.fZ << 7) | (self.fN << 6) | (self.fH << 5) | (self.fC << 4)
|
| 45 |
+
def setF(self, v):
|
| 46 |
+
self.fZ, self.fN, self.fH, self.fC = (v >> 7) & 1, (v >> 6) & 1, (v >> 5) & 1, (v >> 4) & 1
|
| 47 |
+
|
| 48 |
+
def get_r(self, i):
|
| 49 |
+
if i == 6: return self.bus.read(self.get_hl())
|
| 50 |
+
return (self.B, self.C, self.D, self.E, self.H, self.L, None, self.A)[i]
|
| 51 |
+
def set_r(self, i, v):
|
| 52 |
+
v &= 0xFF
|
| 53 |
+
if i == 0: self.B = v
|
| 54 |
+
elif i == 1: self.C = v
|
| 55 |
+
elif i == 2: self.D = v
|
| 56 |
+
elif i == 3: self.E = v
|
| 57 |
+
elif i == 4: self.H = v
|
| 58 |
+
elif i == 5: self.L = v
|
| 59 |
+
elif i == 6: self.bus.write(self.get_hl(), v)
|
| 60 |
+
else: self.A = v
|
| 61 |
+
|
| 62 |
+
def get_rr(self, p): # BC DE HL SP
|
| 63 |
+
return [(self.B << 8) | self.C, (self.D << 8) | self.E, self.get_hl(), self.SP][p]
|
| 64 |
+
def set_rr(self, p, v):
|
| 65 |
+
v &= 0xFFFF
|
| 66 |
+
if p == 0: self.B, self.C = v >> 8, v & 0xFF
|
| 67 |
+
elif p == 1: self.D, self.E = v >> 8, v & 0xFF
|
| 68 |
+
elif p == 2: self.set_hl(v)
|
| 69 |
+
else: self.SP = v
|
| 70 |
+
|
| 71 |
+
def cond(self, cc):
|
| 72 |
+
return [self.fZ == 0, self.fZ == 1, self.fC == 0, self.fC == 1][cc]
|
| 73 |
+
|
| 74 |
+
# ---- bus helpers ----
|
| 75 |
+
def fetch(self):
|
| 76 |
+
v = self.bus.read(self.PC); self.PC = (self.PC + 1) & 0xFFFF; return v
|
| 77 |
+
def fetch16(self):
|
| 78 |
+
lo = self.fetch(); return lo | (self.fetch() << 8)
|
| 79 |
+
def push16(self, v):
|
| 80 |
+
self.bus.tick(1)
|
| 81 |
+
self.SP = (self.SP - 1) & 0xFFFF; self.bus.write(self.SP, (v >> 8) & 0xFF)
|
| 82 |
+
self.SP = (self.SP - 1) & 0xFFFF; self.bus.write(self.SP, v & 0xFF)
|
| 83 |
+
def pop16(self):
|
| 84 |
+
lo = self.bus.read(self.SP); self.SP = (self.SP + 1) & 0xFFFF
|
| 85 |
+
hi = self.bus.read(self.SP); self.SP = (self.SP + 1) & 0xFFFF
|
| 86 |
+
return lo | (hi << 8)
|
| 87 |
+
|
| 88 |
+
# ---- composed 16-bit arithmetic: verified 8-bit units, rippled carry ----
|
| 89 |
+
def add16_hl(self, rr):
|
| 90 |
+
lo, _, _, c = self.u.adc(self.L, rr & 0xFF, 0)
|
| 91 |
+
hi, _, h, c2 = self.u.adc(self.H, rr >> 8, c)
|
| 92 |
+
self.H, self.L = hi, lo
|
| 93 |
+
self.fN, self.fH, self.fC = 0, h, c2
|
| 94 |
+
self.bus.tick(1)
|
| 95 |
+
def inc16(self, v):
|
| 96 |
+
lo, _, _, c = self.u.adc(v & 0xFF, 1, 0)
|
| 97 |
+
hi, _, _, _ = self.u.adc(v >> 8, 0, c)
|
| 98 |
+
return (hi << 8) | lo
|
| 99 |
+
def dec16(self, v):
|
| 100 |
+
lo, _, _, c = self.u.sbc(v & 0xFF, 1, 0)
|
| 101 |
+
hi, _, _, _ = self.u.sbc(v >> 8, 0, c)
|
| 102 |
+
return (hi << 8) | lo
|
| 103 |
+
def sp_plus_e(self, e):
|
| 104 |
+
"""SP + signed e. H/C from the LOW byte add (documented SM83 behavior)."""
|
| 105 |
+
lo, _, h, c = self.u.adc(self.SP & 0xFF, e, 0)
|
| 106 |
+
hi, _, _, _ = self.u.adc(self.SP >> 8, 0xFF if e & 0x80 else 0x00, c)
|
| 107 |
+
self.fZ, self.fN, self.fH, self.fC = 0, 0, h, c
|
| 108 |
+
return (hi << 8) | lo
|
| 109 |
+
|
| 110 |
+
def alu(self, kind, v):
|
| 111 |
+
"""8-bit ALU group dispatch through verified units."""
|
| 112 |
+
if kind == 0: self.A, self.fZ, self.fH, self.fC = self.u.adc(self.A, v, 0); self.fN = 0
|
| 113 |
+
elif kind == 1: self.A, self.fZ, self.fH, self.fC = self.u.adc(self.A, v, self.fC); self.fN = 0
|
| 114 |
+
elif kind == 2: self.A, self.fZ, self.fH, self.fC = self.u.sbc(self.A, v, 0); self.fN = 1
|
| 115 |
+
elif kind == 3: self.A, self.fZ, self.fH, self.fC = self.u.sbc(self.A, v, self.fC); self.fN = 1
|
| 116 |
+
elif kind == 4: self.A, self.fZ = self.u.logic("AND", self.A, v); self.fN, self.fH, self.fC = 0, 1, 0
|
| 117 |
+
elif kind == 5: self.A, self.fZ = self.u.logic("XOR", self.A, v); self.fN, self.fH, self.fC = 0, 0, 0
|
| 118 |
+
elif kind == 6: self.A, self.fZ = self.u.logic("OR", self.A, v); self.fN, self.fH, self.fC = 0, 0, 0
|
| 119 |
+
else: _, self.fZ, self.fH, self.fC = self.u.sbc(self.A, v, 0); self.fN = 1 # CP
|
| 120 |
+
|
| 121 |
+
# ---- interrupts ----
|
| 122 |
+
def service_interrupt(self):
|
| 123 |
+
pend = self.bus.io_IE() & self.bus.io_IF() & 0x1F
|
| 124 |
+
if not pend:
|
| 125 |
+
return False
|
| 126 |
+
bit = (pend & -pend).bit_length() - 1
|
| 127 |
+
self.bus.clear_IF(1 << bit)
|
| 128 |
+
self.IME = False
|
| 129 |
+
self.bus.tick(2)
|
| 130 |
+
self.push16(self.PC) # push16 includes 1 internal tick
|
| 131 |
+
self.PC = 0x40 + 8 * bit
|
| 132 |
+
return True
|
| 133 |
+
|
| 134 |
+
# ---- one instruction (or interrupt entry / halt cycle) ----
|
| 135 |
+
def step(self):
|
| 136 |
+
if self.ei_pending:
|
| 137 |
+
self.ei_pending = False; self.IME = True
|
| 138 |
+
if self.halted:
|
| 139 |
+
if self.bus.io_IE() & self.bus.io_IF() & 0x1F:
|
| 140 |
+
self.halted = False
|
| 141 |
+
else:
|
| 142 |
+
self.bus.tick(1); return
|
| 143 |
+
if self.IME and self.service_interrupt():
|
| 144 |
+
return
|
| 145 |
+
op = self.fetch()
|
| 146 |
+
cls, y, z = self.u.decode(op)
|
| 147 |
+
self.instr_count += 1
|
| 148 |
+
u = self.u; p = y >> 1
|
| 149 |
+
|
| 150 |
+
if cls == NOP: pass
|
| 151 |
+
elif cls == LD_NN_SP:
|
| 152 |
+
a = self.fetch16(); self.bus.write(a, self.SP & 0xFF)
|
| 153 |
+
self.bus.write((a + 1) & 0xFFFF, self.SP >> 8)
|
| 154 |
+
elif cls == STOP: self.bus.tick(2)
|
| 155 |
+
elif cls == JR:
|
| 156 |
+
e = self.fetch(); e = e - 256 if e > 127 else e
|
| 157 |
+
self.PC = (self.PC + e) & 0xFFFF; self.bus.tick(1)
|
| 158 |
+
elif cls == JRcc:
|
| 159 |
+
e = self.fetch(); e = e - 256 if e > 127 else e
|
| 160 |
+
if self.cond(y - 4): self.PC = (self.PC + e) & 0xFFFF; self.bus.tick(1)
|
| 161 |
+
elif cls == LDrrnn: self.set_rr(p, self.fetch16())
|
| 162 |
+
elif cls == ADDHLrr: self.add16_hl(self.get_rr(p))
|
| 163 |
+
elif cls == LDmA or cls == LDAm:
|
| 164 |
+
addr = [(self.B << 8) | self.C, (self.D << 8) | self.E,
|
| 165 |
+
self.get_hl(), self.get_hl()][p]
|
| 166 |
+
if cls == LDmA: self.bus.write(addr, self.A)
|
| 167 |
+
else: self.A = self.bus.read(addr)
|
| 168 |
+
if p == 2: self.set_hl(self.inc16(addr))
|
| 169 |
+
elif p == 3: self.set_hl(self.dec16(addr))
|
| 170 |
+
elif cls == INCrr: self.set_rr(p, self.inc16(self.get_rr(p))); self.bus.tick(1)
|
| 171 |
+
elif cls == DECrr: self.set_rr(p, self.dec16(self.get_rr(p))); self.bus.tick(1)
|
| 172 |
+
elif cls == INCr:
|
| 173 |
+
r, self.fZ, self.fH = u.inc(self.get_r(y)); self.set_r(y, r); self.fN = 0
|
| 174 |
+
elif cls == DECr:
|
| 175 |
+
r, self.fZ, self.fH = u.dec(self.get_r(y)); self.set_r(y, r); self.fN = 1
|
| 176 |
+
elif cls == LDrn: self.set_r(y, self.fetch())
|
| 177 |
+
elif cls == ROTA:
|
| 178 |
+
self.A, _, self.fC = u.rot(ROTNAMES[y], self.A, self.fC)
|
| 179 |
+
self.fZ = self.fN = self.fH = 0
|
| 180 |
+
elif cls == DAAc:
|
| 181 |
+
self.A, self.fZ, self.fC = u.daa(self.A, self.fN, self.fH, self.fC); self.fH = 0
|
| 182 |
+
elif cls == CPLc: self.A = u.cpl(self.A); self.fN = self.fH = 1
|
| 183 |
+
elif cls == SCFc: self.fN = self.fH = 0; self.fC = 1
|
| 184 |
+
elif cls == CCFc: self.fN = self.fH = 0; self.fC ^= 1
|
| 185 |
+
elif cls == HALTc: self.halted = True
|
| 186 |
+
elif cls == LDrr_: self.set_r(y, self.get_r(z))
|
| 187 |
+
elif cls == ALUr: self.alu(y, self.get_r(z))
|
| 188 |
+
elif cls == ALUn: self.alu(y, self.fetch())
|
| 189 |
+
elif cls == RETcc:
|
| 190 |
+
self.bus.tick(1)
|
| 191 |
+
if self.cond(y): self.PC = self.pop16(); self.bus.tick(1)
|
| 192 |
+
elif cls == LDHnA: self.bus.write(0xFF00 | self.fetch(), self.A)
|
| 193 |
+
elif cls == LDHAn: self.A = self.bus.read(0xFF00 | self.fetch())
|
| 194 |
+
elif cls == ADDSPe: self.SP = self.sp_plus_e(self.fetch()); self.bus.tick(2)
|
| 195 |
+
elif cls == LDHLSPe: self.set_hl(self.sp_plus_e(self.fetch())); self.bus.tick(1)
|
| 196 |
+
elif cls == POPrr:
|
| 197 |
+
v = self.pop16()
|
| 198 |
+
if p == 3: self.A = v >> 8; self.setF(v & 0xFF) # AF
|
| 199 |
+
else: self.set_rr(p, v)
|
| 200 |
+
elif cls == RETc: self.PC = self.pop16(); self.bus.tick(1)
|
| 201 |
+
elif cls == RETIc: self.PC = self.pop16(); self.bus.tick(1); self.IME = True
|
| 202 |
+
elif cls == JPHL: self.PC = self.get_hl()
|
| 203 |
+
elif cls == LDSPHL: self.SP = self.get_hl(); self.bus.tick(1)
|
| 204 |
+
elif cls == JPcc:
|
| 205 |
+
a = self.fetch16()
|
| 206 |
+
if self.cond(y): self.PC = a; self.bus.tick(1)
|
| 207 |
+
elif cls == LDCA: self.bus.write(0xFF00 | self.C, self.A)
|
| 208 |
+
elif cls == LDAC: self.A = self.bus.read(0xFF00 | self.C)
|
| 209 |
+
elif cls == LDnnA: self.bus.write(self.fetch16(), self.A)
|
| 210 |
+
elif cls == LDAnn: self.A = self.bus.read(self.fetch16())
|
| 211 |
+
elif cls == JPnn: self.PC = self.fetch16(); self.bus.tick(1)
|
| 212 |
+
elif cls == CBPREF: self.step_cb()
|
| 213 |
+
elif cls == DIc: self.IME = False; self.ei_pending = False
|
| 214 |
+
elif cls == EIc: self.ei_pending = True
|
| 215 |
+
elif cls == CALLcc:
|
| 216 |
+
a = self.fetch16()
|
| 217 |
+
if self.cond(y): self.push16(self.PC); self.PC = a
|
| 218 |
+
elif cls == PUSHrr:
|
| 219 |
+
v = ((self.A << 8) | self.F()) if p == 3 else self.get_rr(p)
|
| 220 |
+
self.push16(v)
|
| 221 |
+
elif cls == CALLnn:
|
| 222 |
+
a = self.fetch16(); self.push16(self.PC); self.PC = a
|
| 223 |
+
elif cls == RSTc: self.push16(self.PC); self.PC = y * 8
|
| 224 |
+
else:
|
| 225 |
+
raise NotImplementedError(f"illegal opcode {op:#04x} at {self.PC - 1:#06x}")
|
| 226 |
+
|
| 227 |
+
def step_cb(self):
|
| 228 |
+
kind, sub, tgt = self.u.cbdecode(self.fetch())
|
| 229 |
+
u = self.u
|
| 230 |
+
if kind == 0: # rotates/shifts
|
| 231 |
+
res, z, c = u.rot(ROTNAMES[sub], self.get_r(tgt), self.fC)
|
| 232 |
+
self.set_r(tgt, res)
|
| 233 |
+
self.fZ, self.fN, self.fH, self.fC = z, 0, 0, c
|
| 234 |
+
elif kind == 1: # BIT
|
| 235 |
+
self.fZ = u.bit(self.get_r(tgt), sub); self.fN, self.fH = 0, 1
|
| 236 |
+
else: # RES / SET
|
| 237 |
+
self.set_r(tgt, u.setres(kind == 3, self.get_r(tgt), sub))
|