auto PPU::vramAddress(uint16 addr) const -> uint { return status.vramBank << 13 | (uint13)addr; } auto PPU::readIO(uint16 addr) -> uint8 { if(addr >= 0x8000 && addr <= 0x9fff) { return vram[vramAddress(addr)]; } if(addr >= 0xfe00 && addr <= 0xfe9f) { if(status.dmaActive && status.dmaClock >= 8) return 0xff; return oam[addr & 0xff]; } if(addr == 0xff40) { //LCDC return (status.displayEnable << 7) | (status.windowTilemapSelect << 6) | (status.windowDisplayEnable << 5) | (status.bgTiledataSelect << 4) | (status.bgTilemapSelect << 3) | (status.obSize << 2) | (status.obEnable << 1) | (status.bgEnable << 0); } if(addr == 0xff41) { //STAT return (status.interruptLYC << 6) | (status.interruptOAM << 5) | (status.interruptVblank << 4) | (status.interruptHblank << 3) | ((status.ly == status.lyc) << 2) | (status.mode << 0); } if(addr == 0xff42) { //SCY return status.scy; } if(addr == 0xff43) { //SCX return status.scx; } if(addr == 0xff44) { //LY return status.ly; } if(addr == 0xff45) { //LYC return status.lyc; } if(addr == 0xff47) { //BGP return (bgp[3] << 6) | (bgp[2] << 4) | (bgp[1] << 2) | (bgp[0] << 0); } if(addr == 0xff48) { //OBP0 return (obp[0][3] << 6) | (obp[0][2] << 4) | (obp[0][1] << 2) | (obp[0][0] << 0); } if(addr == 0xff49) { //OBP1 return (obp[1][3] << 6) | (obp[1][2] << 4) | (obp[1][1] << 2) | (obp[1][0] << 0); } if(addr == 0xff4a) { //WY return status.wy; } if(addr == 0xff4b) { //WX return status.wx; } if(addr == 0xff4f) { //VBK return status.vramBank; } if(addr == 0xff68) { //BGPI return status.bgpiIncrement << 7 | status.bgpi; } if(addr == 0xff69) { //BGPD return bgpd[status.bgpi]; } if(addr == 0xff6a) { //OBPI return status.obpiIncrement << 7 | status.obpi; } if(addr == 0xff6b) { //OBPD return obpd[status.obpi]; } return 0xff; //should never occur } auto PPU::writeIO(uint16 addr, uint8 data) -> void { if(addr >= 0x8000 && addr <= 0x9fff) { vram[vramAddress(addr)] = data; return; } if(addr >= 0xfe00 && addr <= 0xfe9f) { if(status.dmaActive && status.dmaClock >= 8) return; oam[addr & 0xff] = data; return; } if(addr == 0xff40) { //LCDC if(status.displayEnable && !data.bit(7)) { status.mode = 0; status.ly = 0; status.lx = 0; //restart cothread to begin new frame auto clock = Thread::clock(); create(Enter, 4 * 1024 * 1024); Thread::setClock(clock); } status.displayEnable = data & 0x80; status.windowTilemapSelect = data & 0x40; status.windowDisplayEnable = data & 0x20; status.bgTiledataSelect = data & 0x10; status.bgTilemapSelect = data & 0x08; status.obSize = data & 0x04; status.obEnable = data & 0x02; status.bgEnable = data & 0x01; return; } if(addr == 0xff41) { //STAT status.interruptLYC = data & 0x40; status.interruptOAM = data & 0x20; status.interruptVblank = data & 0x10; status.interruptHblank = data & 0x08; //hardware bug: writes to STAT on DMG,SGB during vblank triggers STAT IRQ //note: this behavior isn't entirely correct; more research is needed ... if(!Model::GameBoyColor() && status.mode == 1) { cpu.raise(CPU::Interrupt::Stat); } return; } if(addr == 0xff42) { //SCY status.scy = data; return; } if(addr == 0xff43) { //SCX status.scx = data; return; } if(addr == 0xff44) { //LY status.ly = 0; return; } if(addr == 0xff45) { //LYC status.lyc = data; return; } if(addr == 0xff46) { //DMA status.dmaActive = true; status.dmaClock = 0; status.dmaBank = data; return; } if(addr == 0xff47) { //BGP bgp[3] = (data >> 6) & 3; bgp[2] = (data >> 4) & 3; bgp[1] = (data >> 2) & 3; bgp[0] = (data >> 0) & 3; return; } if(addr == 0xff48) { //OBP0 obp[0][3] = (data >> 6) & 3; obp[0][2] = (data >> 4) & 3; obp[0][1] = (data >> 2) & 3; obp[0][0] = (data >> 0) & 3; return; } if(addr == 0xff49) { //OBP1 obp[1][3] = (data >> 6) & 3; obp[1][2] = (data >> 4) & 3; obp[1][1] = (data >> 2) & 3; obp[1][0] = (data >> 0) & 3; return; } if(addr == 0xff4a) { //WY status.wy = data; return; } if(addr == 0xff4b) { //WX status.wx = data; return; } if(addr == 0xff4f) { //VBK status.vramBank = data & 1; return; } if(addr == 0xff68) { //BGPI status.bgpiIncrement = data & 0x80; status.bgpi = data & 0x3f; return; } if(addr == 0xff69) { //BGPD bgpd[status.bgpi] = data; if(status.bgpiIncrement) status.bgpi++; return; } if(addr == 0xff6a) { //OBPI status.obpiIncrement = data & 0x80; status.obpi = data & 0x3f; } if(addr == 0xff6b) { //OBPD obpd[status.obpi] = data; if(status.obpiIncrement) status.obpi++; } }