256 lines
7.3 KiB
C++
256 lines
7.3 KiB
C++
//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 & 127;
|
|
}
|
|
|
|
auto CPU::stepOnce() -> void {
|
|
counter.cpu += 2;
|
|
TotalExecutedCycles += 2;
|
|
tick();
|
|
if(hcounter() & 2) nmiPoll(), irqPoll();
|
|
if(joypadCounter() == 0) joypadEdge();
|
|
}
|
|
|
|
template<uint Clocks, bool Synchronize>
|
|
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 = 33; //33 = inactive
|
|
}
|
|
|
|
//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 (!alu.mpyctr)
|
|
alu.mpylast = 1;
|
|
if(io.rddiv & 1) io.rdmpy += alu.shift;
|
|
io.rddiv >>= 1;
|
|
alu.shift <<= 1;
|
|
}
|
|
else
|
|
alu.mpylast = 0;
|
|
|
|
if(alu.divctr) {
|
|
alu.divctr--;
|
|
if (!alu.divctr)
|
|
alu.divlast = 1;
|
|
io.rddiv <<= 1;
|
|
alu.shift >>= 1;
|
|
if(io.rdmpy >= alu.shift) {
|
|
io.rdmpy -= alu.shift;
|
|
io.rddiv |= 1;
|
|
}
|
|
}
|
|
else
|
|
alu.divlast = 0;
|
|
}
|
|
|
|
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 128 clocks from inside the CPU::stepOnce() function
|
|
auto CPU::joypadEdge() -> void {
|
|
//it is not yet confirmed if polling can be stopped early and/or (re)started later
|
|
if(!io.autoJoypadPoll) return;
|
|
|
|
if(vcounter() == ppu.vdisp() && hcounter() >= 130 && hcounter() <= 256) {
|
|
//begin new polling sequence
|
|
status.autoJoypadCounter = 0;
|
|
}
|
|
|
|
//stop after polling has been completed for this frame
|
|
if(status.autoJoypadCounter >= 33) return;
|
|
|
|
if(status.autoJoypadCounter == 0) {
|
|
//latch controller states on the first polling cycle
|
|
controllerPort1.device->latch(1);
|
|
controllerPort2.device->latch(1);
|
|
}
|
|
|
|
if(status.autoJoypadCounter == 1) {
|
|
//release latch and begin reading on the second cycle
|
|
controllerPort1.device->latch(0);
|
|
controllerPort2.device->latch(0);
|
|
|
|
//shift registers are cleared to zero at start of auto-joypad polling
|
|
io.joy1 = 0;
|
|
io.joy2 = 0;
|
|
io.joy3 = 0;
|
|
io.joy4 = 0;
|
|
}
|
|
|
|
if(status.autoJoypadCounter >= 2 && !(status.autoJoypadCounter & 1)) {
|
|
//sixteen bits are shifted into joy{1-4}, one bit per 256 clocks
|
|
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++;
|
|
}
|