Simplifications to CPU interrupt handling.

This commit is contained in:
byuu 2019-08-02 11:23:31 +09:00
parent db988d9588
commit b577e6d5d0
12 changed files with 99 additions and 90 deletions

View File

@ -19,6 +19,9 @@ struct WDC65816 {
virtual auto readDisassembler(uint addr) -> uint8 { return 0; } virtual auto readDisassembler(uint addr) -> uint8 { return 0; }
inline auto irq() const -> bool { return r.irq; }
virtual inline auto irq(bool line) -> void { r.irq = line; }
using r8 = uint8; using r8 = uint8;
union r16 { union r16 {

View File

@ -23,7 +23,7 @@ auto HitachiDSP::step(uint clocks) -> void {
auto HitachiDSP::halt() -> void { auto HitachiDSP::halt() -> void {
HG51B::halt(); HG51B::halt();
if(io.irq == 0) r.i = 1, cpu.r.irq = 1; if(io.irq == 0) cpu.irq(r.i = 1);
} }
auto HitachiDSP::unload() -> void { auto HitachiDSP::unload() -> void {

View File

@ -223,7 +223,7 @@ auto HitachiDSP::writeIO(uint address, uint8 data) -> void {
case 0x7f51: case 0x7f51:
io.irq = data & 1; io.irq = data & 1;
if(io.irq == 1) r.i = 0, cpu.r.irq = 0; if(io.irq == 1) cpu.irq(r.i = 0);
return; return;
case 0x7f52: case 0x7f52:

View File

@ -51,7 +51,7 @@ auto SA1::dmaCC1() -> void {
mmio.chdma_irqfl = true; mmio.chdma_irqfl = true;
if(mmio.chdma_irqen) { if(mmio.chdma_irqen) {
mmio.chdma_irqcl = 0; mmio.chdma_irqcl = 0;
cpu.r.irq = 1; cpu.irq(1);
} }
} }

View File

@ -136,14 +136,14 @@ auto SA1::writeIOCPU(uint address, uint8 data) -> void {
if(!mmio.cpu_irqen && (data & 0x80)) { if(!mmio.cpu_irqen && (data & 0x80)) {
if(mmio.cpu_irqfl) { if(mmio.cpu_irqfl) {
mmio.cpu_irqcl = 0; mmio.cpu_irqcl = 0;
cpu.r.irq = 1; cpu.irq(1);
} }
} }
if(!mmio.chdma_irqen && (data & 0x20)) { if(!mmio.chdma_irqen && (data & 0x20)) {
if(mmio.chdma_irqfl) { if(mmio.chdma_irqfl) {
mmio.chdma_irqcl = 0; mmio.chdma_irqcl = 0;
cpu.r.irq = 1; cpu.irq(1);
} }
} }
@ -160,7 +160,7 @@ auto SA1::writeIOCPU(uint address, uint8 data) -> void {
if(mmio.cpu_irqcl ) mmio.cpu_irqfl = false; if(mmio.cpu_irqcl ) mmio.cpu_irqfl = false;
if(mmio.chdma_irqcl) mmio.chdma_irqfl = false; if(mmio.chdma_irqcl) mmio.chdma_irqfl = false;
if(!mmio.cpu_irqfl && !mmio.chdma_irqfl) cpu.r.irq = 0; if(!mmio.cpu_irqfl && !mmio.chdma_irqfl) cpu.irq(0);
return; return;
} }
@ -251,7 +251,7 @@ auto SA1::writeIOSA1(uint address, uint8 data) -> void {
mmio.cpu_irqfl = true; mmio.cpu_irqfl = true;
if(mmio.cpu_irqen) { if(mmio.cpu_irqen) {
mmio.cpu_irqcl = 0; mmio.cpu_irqcl = 0;
cpu.r.irq = 1; cpu.irq(1);
} }
} }

View File

@ -1,5 +1,5 @@
auto SuperFX::stop() -> void { auto SuperFX::stop() -> void {
cpu.r.irq = 1; cpu.irq(1);
} }
auto SuperFX::color(uint8 source) -> uint8 { auto SuperFX::color(uint8 source) -> uint8 {

View File

@ -18,7 +18,7 @@ auto SuperFX::readIO(uint addr, uint8) -> uint8 {
case 0x3031: { case 0x3031: {
uint8 r = regs.sfr >> 8; uint8 r = regs.sfr >> 8;
regs.sfr.irq = 0; regs.sfr.irq = 0;
cpu.r.irq = 0; cpu.irq(0);
return r; return r;
} }

View File

@ -33,34 +33,34 @@ auto CPU::Enter() -> void {
} }
} }
auto CPU::boot() -> void {
}
auto CPU::main() -> void { auto CPU::main() -> void {
if(r.wai) return instructionWait(); if(r.wai) return instructionWait();
if(r.stp) return instructionStop(); if(r.stp) return instructionStop();
if(!status.interruptPending) return instruction();
if(status.interruptPending) { if(status.nmiPending) {
status.interruptPending = false; status.nmiPending = 0;
if(status.nmiPending) { r.vector = r.e ? 0xfffa : 0xffea;
status.nmiPending = false; return interrupt();
r.vector = r.e ? 0xfffa : 0xffea;
interrupt();
} else if(status.irqPending) {
status.irqPending = false;
r.vector = r.e ? 0xfffe : 0xffee;
interrupt();
} else if(status.resetPending) {
status.resetPending = false;
for(uint repeat : range(22)) step<6,0>(); //step(132);
r.vector = 0xfffc;
interrupt();
} else if(status.powerPending) {
status.powerPending = false;
for(uint repeat : range(31)) step<6,0>(); //step(186);
r.pc.l = bus.read(0xfffc, r.mdr);
r.pc.h = bus.read(0xfffd, r.mdr);
}
} }
instruction(); if(status.irqPending) {
status.irqPending = 0;
r.vector = r.e ? 0xfffe : 0xffee;
return interrupt();
}
if(status.resetPending) {
status.resetPending = 0;
for(uint repeat : range(22)) step<6,0>(); //step(132);
r.vector = 0xfffc;
return interrupt();
}
status.interruptPending = 0;
} }
auto CPU::load() -> bool { auto CPU::load() -> bool {
@ -109,13 +109,11 @@ auto CPU::power(bool reset) -> void {
alu = {}; alu = {};
status = {}; status = {};
status.lineClocks = hperiod();
status.dramRefreshPosition = (version == 1 ? 530 : 538); status.dramRefreshPosition = (version == 1 ? 530 : 538);
status.hdmaSetupPosition = (version == 1 ? 12 + 8 - dmaCounter() : 12 + dmaCounter()); status.hdmaSetupPosition = (version == 1 ? 12 + 8 - dmaCounter() : 12 + dmaCounter());
status.hdmaPosition = 1104; status.hdmaPosition = 1104;
status.powerPending = reset == 0; status.resetPending = 1;
status.resetPending = reset == 1; status.interruptPending = 1;
status.interruptPending = true;
} }
} }

View File

@ -9,6 +9,7 @@ struct CPU : Processor::WDC65816, Thread, PPUcounter {
auto synchronizePPU() -> void; auto synchronizePPU() -> void;
auto synchronizeCoprocessors() -> void; auto synchronizeCoprocessors() -> void;
static auto Enter() -> void; static auto Enter() -> void;
auto boot() -> void;
auto main() -> void; auto main() -> void;
auto load() -> bool; auto load() -> bool;
auto power(bool reset) -> void; auto power(bool reset) -> void;
@ -51,9 +52,9 @@ struct CPU : Processor::WDC65816, Thread, PPUcounter {
alwaysinline auto aluEdge() -> void; alwaysinline auto aluEdge() -> void;
alwaysinline auto dmaEdge() -> void; alwaysinline auto dmaEdge() -> void;
alwaysinline auto lastCycle() -> void;
//irq.cpp //irq.cpp
auto irq(bool line) -> void override;
alwaysinline auto pollInterrupts() -> void; alwaysinline auto pollInterrupts() -> void;
auto nmitimenUpdate(uint8 data) -> void; auto nmitimenUpdate(uint8 data) -> void;
auto rdnmi() -> bool; auto rdnmi() -> bool;
@ -61,6 +62,7 @@ struct CPU : Processor::WDC65816, Thread, PPUcounter {
alwaysinline auto nmiTest() -> bool; alwaysinline auto nmiTest() -> bool;
alwaysinline auto irqTest() -> bool; alwaysinline auto irqTest() -> bool;
alwaysinline auto lastCycle() -> void;
//joypad.cpp //joypad.cpp
auto joypadEdge() -> void; auto joypadEdge() -> void;
@ -86,18 +88,17 @@ private:
struct Status { struct Status {
uint clockCount = 0; uint clockCount = 0;
uint lineClocks = 0;
bool irqLock = false; bool irqLock = 0;
uint dramRefreshPosition = 0; uint dramRefreshPosition = 0;
uint dramRefresh = 0; //0 = not refreshed; 1 = refresh active; 2 = refresh inactive uint dramRefresh = 0; //0 = not refreshed; 1 = refresh active; 2 = refresh inactive
uint hdmaSetupPosition = 0; uint hdmaSetupPosition = 0;
bool hdmaSetupTriggered = false; bool hdmaSetupTriggered = 0;
uint hdmaPosition = 0; uint hdmaPosition = 0;
bool hdmaTriggered = false; bool hdmaTriggered = 0;
boolean nmiValid = 0; boolean nmiValid = 0;
boolean nmiLine = 0; boolean nmiLine = 0;
@ -111,18 +112,16 @@ private:
boolean irqPending = 0; boolean irqPending = 0;
boolean irqHold = 0; boolean irqHold = 0;
bool powerPending = false; bool resetPending = 0;
bool resetPending = false; bool interruptPending = 0;
bool interruptPending = false; bool dmaActive = 0;
bool dmaPending = 0;
bool dmaActive = false; bool hdmaPending = 0;
bool dmaPending = false;
bool hdmaPending = false;
bool hdmaMode = 0; //0 = init, 1 = run bool hdmaMode = 0; //0 = init, 1 = run
bool autoJoypadActive = false; bool autoJoypadActive = 0;
bool autoJoypadLatch = false; bool autoJoypadLatch = 0;
uint autoJoypadCounter = 0; uint autoJoypadCounter = 0;
} status; } status;

View File

@ -1,3 +1,12 @@
//external interrupt line changed.
auto CPU::irq(bool line) -> void {
WDC65816::irq(line);
if(line) {
status.irqTransition = 1;
r.wai = 0;
}
}
//called once every four clock cycles; //called once every four clock cycles;
//as NMI steps by scanlines (divisible by 4) and IRQ by PPU 4-cycle dots. //as NMI steps by scanlines (divisible by 4) and IRQ by PPU 4-cycle dots.
// //
@ -5,48 +14,56 @@
//it is used to emulate hardware communication delay between opcode and interrupt units. //it is used to emulate hardware communication delay between opcode and interrupt units.
auto CPU::pollInterrupts() -> void { auto CPU::pollInterrupts() -> void {
//NMI hold //NMI hold
if(status.nmiHold.lower() && io.nmiEnable) status.nmiTransition = true; if(status.nmiHold.lower() && io.nmiEnable) {
status.nmiTransition = 1;
r.wai = 0;
}
//NMI test //NMI test
if(status.nmiValid.flip(vcounter(2) >= ppu.vdisp())) { if(status.nmiValid.flip(vcounter(2) >= ppu.vdisp())) {
if(status.nmiLine = status.nmiValid) status.nmiHold = true; //hold /NMI for four cycles if(status.nmiLine = status.nmiValid) status.nmiHold = 1; //hold /NMI for four cycles
} }
//IRQ hold //IRQ hold
status.irqHold = false; status.irqHold = 0;
if(status.irqLine && io.irqEnable) status.irqTransition = true; if(status.irqLine && io.irqEnable) {
status.irqTransition = 1;
r.wai = 0;
}
//IRQ test //IRQ test
if(status.irqValid.raise(io.irqEnable if(status.irqValid.raise(io.irqEnable
&& (!io.virqEnable || vcounter(10) == io.vtime) && (!io.virqEnable || vcounter(10) == io.vtime)
&& (!io.hirqEnable || hcounter(10) == io.htime) && (!io.hirqEnable || hcounter(10) == io.htime)
&& (vcounter(6) || hcounter(6)) //IRQs cannot trigger on last dot of fields && (vcounter(6) || hcounter(6)) //IRQs cannot trigger on last dot of fields
)) status.irqLine = status.irqHold = true; //hold /IRQ for four cycles )) status.irqLine = status.irqHold = 1; //hold /IRQ for four cycles
} }
auto CPU::nmitimenUpdate(uint8 data) -> void { auto CPU::nmitimenUpdate(uint8 data) -> void {
io.hirqEnable = bit1(data,4); io.hirqEnable = data & 0x10;
io.virqEnable = bit1(data,5); io.virqEnable = data & 0x20;
io.irqEnable = io.hirqEnable || io.virqEnable; io.irqEnable = io.hirqEnable || io.virqEnable;
if(io.virqEnable && !io.hirqEnable && status.irqLine) { if(io.virqEnable && !io.hirqEnable && status.irqLine) {
status.irqTransition = true; status.irqTransition = 1;
r.wai = 0;
} else if(!io.irqEnable) { } else if(!io.irqEnable) {
status.irqLine = false; status.irqLine = 0;
status.irqTransition = false; status.irqTransition = 0;
} }
if(io.nmiEnable.raise(bit1(data,7)) && status.nmiLine) { if(io.nmiEnable.raise(data & 0x80) && status.nmiLine) {
status.nmiTransition = true; status.nmiTransition = 1;
r.wai = 0;
} }
status.irqLock = true; status.irqLock = 1;
} }
auto CPU::rdnmi() -> bool { auto CPU::rdnmi() -> bool {
bool result = status.nmiLine; bool result = status.nmiLine;
if(!status.nmiHold) { if(!status.nmiHold) {
status.nmiLine = false; status.nmiLine = 0;
} }
return result; return result;
} }
@ -54,22 +71,32 @@ auto CPU::rdnmi() -> bool {
auto CPU::timeup() -> bool { auto CPU::timeup() -> bool {
bool result = status.irqLine; bool result = status.irqLine;
if(!status.irqHold) { if(!status.irqHold) {
status.irqLine = false; status.irqLine = 0;
status.irqTransition = false; status.irqTransition = 0;
} }
return result; return result;
} }
auto CPU::nmiTest() -> bool { auto CPU::nmiTest() -> bool {
if(!status.nmiTransition) return false; if(!status.nmiTransition) return 0;
status.nmiTransition = false; status.nmiTransition = 0;
r.wai = false; return 1;
return true;
} }
auto CPU::irqTest() -> bool { auto CPU::irqTest() -> bool {
if(!status.irqTransition && !r.irq) return false; if(!status.irqTransition) return 0;
status.irqTransition = false; status.irqTransition = 0;
r.wai = false;
return !r.p.i; return !r.p.i;
} }
//used to test for NMI/IRQ, which can trigger on the edge of every opcode.
//test one cycle early to simulate two-stage pipeline of the 65816 CPU.
//
//status.irqLock is used to simulate hardware delay before interrupts can
//trigger during certain events (immediately after DMA, writes to $4200, etc)
auto CPU::lastCycle() -> void {
if(!status.irqLock) {
if(nmiTest()) status.nmiPending = 1, status.interruptPending = 1;
if(irqTest()) status.irqPending = 1, status.interruptPending = 1;
}
}

View File

@ -11,7 +11,6 @@ auto CPU::serialize(serializer& s) -> void {
s.integer(counter.dma); s.integer(counter.dma);
s.integer(status.clockCount); s.integer(status.clockCount);
s.integer(status.lineClocks);
s.integer(status.irqLock); s.integer(status.irqLock);
@ -36,9 +35,7 @@ auto CPU::serialize(serializer& s) -> void {
s.boolean(status.irqPending); s.boolean(status.irqPending);
s.boolean(status.irqHold); s.boolean(status.irqHold);
s.integer(status.powerPending);
s.integer(status.resetPending); s.integer(status.resetPending);
s.integer(status.interruptPending); s.integer(status.interruptPending);
s.integer(status.dmaActive); s.integer(status.dmaActive);

View File

@ -82,8 +82,6 @@ auto CPU::step(uint clocks) -> void {
//called by ppu.tick() when Hcounter=0 //called by ppu.tick() when Hcounter=0
auto CPU::scanline() -> void { auto CPU::scanline() -> void {
status.lineClocks = hperiod();
//forcefully sync S-CPU to other processors, in case chips are not communicating //forcefully sync S-CPU to other processors, in case chips are not communicating
synchronizeSMP(); synchronizeSMP();
synchronizePPU(); synchronizePPU();
@ -232,16 +230,3 @@ auto CPU::joypadEdge() -> void {
status.autoJoypadCounter++; status.autoJoypadCounter++;
} }
} }
//used to test for NMI/IRQ, which can trigger on the edge of every opcode.
//test one cycle early to simulate two-stage pipeline of x816 CPU.
//
//status.irq_lock is used to simulate hardware delay before interrupts can
//trigger during certain events (immediately after DMA, writes to $4200, etc)
auto CPU::lastCycle() -> void {
if(!status.irqLock) {
if(nmiTest()) status.nmiPending = true;
if(irqTest()) status.irqPending = true;
status.interruptPending = status.nmiPending || status.irqPending;
}
}