//DMA clock divider auto CPU::dmaCounter() const -> uint { return counter.cpu & 7; } //joypad auto-poll clock divider auto CPU::joypadCounter() const -> uint { return counter.cpu & 255; } auto CPU::stepOnce() -> void { counter.cpu += 2; tick(); if(hcounter() & 2) nmiPoll(), irqPoll(); if(joypadCounter() == 0) joypadEdge(); } template auto CPU::step() -> void { static_assert(Clocks == 2 || Clocks == 4 || Clocks == 6 || Clocks == 8 || Clocks == 10 || Clocks == 12); for(auto coprocessor : coprocessors) { if(coprocessor == &icd || coprocessor == &msu1) continue; coprocessor->clock -= Clocks * (uint64)coprocessor->frequency; } if(overclocking.target) { overclocking.counter += Clocks; if(overclocking.counter < overclocking.target) { if constexpr(Synchronize) { if(configuration.hacks.coprocessor.delayedSync) return; synchronizeCoprocessors(); } return; } } if constexpr(Clocks >= 2) stepOnce(); if constexpr(Clocks >= 4) stepOnce(); if constexpr(Clocks >= 6) stepOnce(); if constexpr(Clocks >= 8) stepOnce(); if constexpr(Clocks >= 10) stepOnce(); if constexpr(Clocks >= 12) stepOnce(); smp.clock -= Clocks * (uint64)smp.frequency; ppu.clock -= Clocks; for(auto coprocessor : coprocessors) { if(coprocessor != &icd && coprocessor != &msu1) continue; coprocessor->clock -= Clocks * (uint64)coprocessor->frequency; } if(!status.dramRefresh && hcounter() >= status.dramRefreshPosition) { //note: pattern should technically be 5-3, 5-3, 5-3, 5-3, 5-3 per logic analyzer //result averages out the same as no coprocessor polls refresh() at > frequency()/2 status.dramRefresh = 1; step<6,0>(); status.dramRefresh = 2; step<2,0>(); aluEdge(); status.dramRefresh = 1; step<6,0>(); status.dramRefresh = 2; step<2,0>(); aluEdge(); status.dramRefresh = 1; step<6,0>(); status.dramRefresh = 2; step<2,0>(); aluEdge(); status.dramRefresh = 1; step<6,0>(); status.dramRefresh = 2; step<2,0>(); aluEdge(); status.dramRefresh = 1; step<6,0>(); status.dramRefresh = 2; step<2,0>(); aluEdge(); } if(!status.hdmaSetupTriggered && hcounter() >= status.hdmaSetupPosition) { status.hdmaSetupTriggered = true; hdmaReset(); if(hdmaEnable()) { status.hdmaPending = true; status.hdmaMode = 0; } } if(!status.hdmaTriggered && hcounter() >= status.hdmaPosition) { status.hdmaTriggered = true; if(hdmaActive()) { status.hdmaPending = true; status.hdmaMode = 1; } } if constexpr(Synchronize) { if(configuration.hacks.coprocessor.delayedSync) return; synchronizeCoprocessors(); } } auto CPU::step(uint clocks) -> void { switch(clocks) { case 2: return step< 2,1>(); case 4: return step< 4,1>(); case 6: return step< 6,1>(); case 8: return step< 8,1>(); case 10: return step<10,1>(); case 12: return step<12,1>(); } } //called by ppu.tick() when Hcounter=0 auto CPU::scanline() -> void { //forcefully sync S-CPU to other processors, in case chips are not communicating synchronizeSMP(); synchronizePPU(); synchronizeCoprocessors(); if(vcounter() == 0) { //HDMA setup triggers once every frame status.hdmaSetupPosition = (version == 1 ? 12 + 8 - dmaCounter() : 12 + dmaCounter()); status.hdmaSetupTriggered = false; status.autoJoypadCounter = 0; } //DRAM refresh occurs once every scanline if(version == 2) status.dramRefreshPosition = 530 + 8 - dmaCounter(); status.dramRefresh = 0; //HDMA triggers once every visible scanline if(vcounter() < ppu.vdisp()) { status.hdmaPosition = 1104; status.hdmaTriggered = false; } //overclocking if(vcounter() == (Region::NTSC() ? 261 : 311)) { overclocking.counter = 0; overclocking.target = 0; double overclock = configuration.hacks.cpu.overclock / 100.0; if(overclock > 1.0) { int clocks = (Region::NTSC() ? 262 : 312) * 1364; overclocking.target = clocks * overclock - clocks; } } //handle video frame events from the CPU core to prevent a race condition between //games polling inputs during NMI and the PPU thread not being 100% synchronized. if(vcounter() == ppu.vdisp()) { if(auto device = controllerPort2.device) device->latch(); //light guns synchronizePPU(); if(system.fastPPU()) PPUfast::Line::flush(); scheduler.leave(Scheduler::Event::Frame); } } auto CPU::aluEdge() -> void { if(alu.mpyctr) { alu.mpyctr--; if(io.rddiv & 1) io.rdmpy += alu.shift; io.rddiv >>= 1; alu.shift <<= 1; } if(alu.divctr) { alu.divctr--; io.rddiv <<= 1; alu.shift >>= 1; if(io.rdmpy >= alu.shift) { io.rdmpy -= alu.shift; io.rddiv |= 1; } } } auto CPU::dmaEdge() -> void { //H/DMA pending && DMA inactive? //.. Run one full CPU cycle //.. HDMA pending && HDMA enabled ? DMA sync + HDMA run //.. DMA pending && DMA enabled ? DMA sync + DMA run //.... HDMA during DMA && HDMA enabled ? DMA sync + HDMA run //.. Run one bus CPU cycle //.. CPU sync if(status.dmaActive) { if(status.hdmaPending) { status.hdmaPending = false; if(hdmaEnable()) { if(!dmaEnable()) { step(counter.dma = 8 - dmaCounter()); } status.hdmaMode == 0 ? hdmaSetup() : hdmaRun(); if(!dmaEnable()) { step(status.clockCount - counter.dma % status.clockCount); status.dmaActive = false; } } } if(status.dmaPending) { status.dmaPending = false; if(dmaEnable()) { step(counter.dma = 8 - dmaCounter()); dmaRun(); step(status.clockCount - counter.dma % status.clockCount); status.dmaActive = false; } } } if(!status.dmaActive) { if(status.dmaPending || status.hdmaPending) { status.dmaActive = true; } } } //called every 256 clocks; see CPU::step() auto CPU::joypadEdge() -> void { //fast joypad polling is a hack to work around edge cases not currently emulated in auto-joypad polling. //below is a list of games that have had input issues over the years. //Nuke (PD): inputs do not work //Super Conflict: sends random inputs even with no buttons pressed //Super Star Wars: Start button auto-unpauses //Taikyoku Igo - Goliath: start button not acknowledged //Tatakae Genshijin 2: attract sequence ends early //Williams Arcade's Greatest Hits: inputs fire on their own; or menu items sometimes skipped //World Masters Golf: inputs fail to register; or holding D-pad should only move the cursor once, not continuously if(configuration.hacks.cpu.fastJoypadPolling) { //Taikyoku Igo - Goliath //Williams Arcade's Greatest Hits //World Masters Golf if(!status.autoJoypadCounter && vcounter() >= ppu.vdisp()) { controllerPort1.device->latch(1); controllerPort2.device->latch(1); controllerPort1.device->latch(0); controllerPort2.device->latch(0); io.joy1 = 0; io.joy2 = 0; io.joy3 = 0; io.joy4 = 0; for(uint index : range(16)) { uint2 port0 = controllerPort1.device->data(); uint2 port1 = controllerPort2.device->data(); io.joy1 = io.joy1 << 1 | port0.bit(0); io.joy2 = io.joy2 << 1 | port1.bit(0); io.joy3 = io.joy3 << 1 | port0.bit(1); io.joy4 = io.joy4 << 1 | port1.bit(1); } status.autoJoypadCounter = 16; } } else { if(vcounter() >= ppu.vdisp()) { //cache enable state at first iteration if(status.autoJoypadCounter == 0) status.autoJoypadLatch = io.autoJoypadPoll; status.autoJoypadActive = status.autoJoypadCounter <= 15; if(status.autoJoypadActive && status.autoJoypadLatch) { if(status.autoJoypadCounter == 0) { controllerPort1.device->latch(1); controllerPort2.device->latch(1); controllerPort1.device->latch(0); controllerPort2.device->latch(0); //shift registers are cleared at start of auto joypad polling io.joy1 = 0; io.joy2 = 0; io.joy3 = 0; io.joy4 = 0; } uint2 port0 = controllerPort1.device->data(); uint2 port1 = controllerPort2.device->data(); io.joy1 = io.joy1 << 1 | port0.bit(0); io.joy2 = io.joy2 << 1 | port1.bit(0); io.joy3 = io.joy3 << 1 | port0.bit(1); io.joy4 = io.joy4 << 1 | port1.bit(1); } status.autoJoypadCounter++; } } }