From b577e6d5d007cf8e8d66a465106331aac245ad68 Mon Sep 17 00:00:00 2001 From: byuu <2107894+byuu@users.noreply.github.com> Date: Fri, 2 Aug 2019 11:23:31 +0900 Subject: [PATCH] Simplifications to CPU interrupt handling. --- bsnes/processor/wdc65816/wdc65816.hpp | 3 + .../sfc/coprocessor/hitachidsp/hitachidsp.cpp | 2 +- bsnes/sfc/coprocessor/hitachidsp/memory.cpp | 2 +- bsnes/sfc/coprocessor/sa1/dma.cpp | 2 +- bsnes/sfc/coprocessor/sa1/io.cpp | 8 +- bsnes/sfc/coprocessor/superfx/core.cpp | 2 +- bsnes/sfc/coprocessor/superfx/io.cpp | 2 +- bsnes/sfc/cpu/cpu.cpp | 50 ++++++------- bsnes/sfc/cpu/cpu.hpp | 27 ++++--- bsnes/sfc/cpu/irq.cpp | 73 +++++++++++++------ bsnes/sfc/cpu/serialization.cpp | 3 - bsnes/sfc/cpu/timing.cpp | 15 ---- 12 files changed, 99 insertions(+), 90 deletions(-) diff --git a/bsnes/processor/wdc65816/wdc65816.hpp b/bsnes/processor/wdc65816/wdc65816.hpp index eac2b55f..80556a63 100755 --- a/bsnes/processor/wdc65816/wdc65816.hpp +++ b/bsnes/processor/wdc65816/wdc65816.hpp @@ -19,6 +19,9 @@ struct WDC65816 { 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; union r16 { diff --git a/bsnes/sfc/coprocessor/hitachidsp/hitachidsp.cpp b/bsnes/sfc/coprocessor/hitachidsp/hitachidsp.cpp index 18cd3236..ec4f90ef 100644 --- a/bsnes/sfc/coprocessor/hitachidsp/hitachidsp.cpp +++ b/bsnes/sfc/coprocessor/hitachidsp/hitachidsp.cpp @@ -23,7 +23,7 @@ auto HitachiDSP::step(uint clocks) -> void { auto HitachiDSP::halt() -> void { 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 { diff --git a/bsnes/sfc/coprocessor/hitachidsp/memory.cpp b/bsnes/sfc/coprocessor/hitachidsp/memory.cpp index 655749ad..6fa7b056 100644 --- a/bsnes/sfc/coprocessor/hitachidsp/memory.cpp +++ b/bsnes/sfc/coprocessor/hitachidsp/memory.cpp @@ -223,7 +223,7 @@ auto HitachiDSP::writeIO(uint address, uint8 data) -> void { case 0x7f51: 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; case 0x7f52: diff --git a/bsnes/sfc/coprocessor/sa1/dma.cpp b/bsnes/sfc/coprocessor/sa1/dma.cpp index b67a77cb..ba494a6b 100644 --- a/bsnes/sfc/coprocessor/sa1/dma.cpp +++ b/bsnes/sfc/coprocessor/sa1/dma.cpp @@ -51,7 +51,7 @@ auto SA1::dmaCC1() -> void { mmio.chdma_irqfl = true; if(mmio.chdma_irqen) { mmio.chdma_irqcl = 0; - cpu.r.irq = 1; + cpu.irq(1); } } diff --git a/bsnes/sfc/coprocessor/sa1/io.cpp b/bsnes/sfc/coprocessor/sa1/io.cpp index 43f520de..3a5633a6 100644 --- a/bsnes/sfc/coprocessor/sa1/io.cpp +++ b/bsnes/sfc/coprocessor/sa1/io.cpp @@ -136,14 +136,14 @@ auto SA1::writeIOCPU(uint address, uint8 data) -> void { if(!mmio.cpu_irqen && (data & 0x80)) { if(mmio.cpu_irqfl) { mmio.cpu_irqcl = 0; - cpu.r.irq = 1; + cpu.irq(1); } } if(!mmio.chdma_irqen && (data & 0x20)) { if(mmio.chdma_irqfl) { 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.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; } @@ -251,7 +251,7 @@ auto SA1::writeIOSA1(uint address, uint8 data) -> void { mmio.cpu_irqfl = true; if(mmio.cpu_irqen) { mmio.cpu_irqcl = 0; - cpu.r.irq = 1; + cpu.irq(1); } } diff --git a/bsnes/sfc/coprocessor/superfx/core.cpp b/bsnes/sfc/coprocessor/superfx/core.cpp index d92f5f0c..eb44044a 100644 --- a/bsnes/sfc/coprocessor/superfx/core.cpp +++ b/bsnes/sfc/coprocessor/superfx/core.cpp @@ -1,5 +1,5 @@ auto SuperFX::stop() -> void { - cpu.r.irq = 1; + cpu.irq(1); } auto SuperFX::color(uint8 source) -> uint8 { diff --git a/bsnes/sfc/coprocessor/superfx/io.cpp b/bsnes/sfc/coprocessor/superfx/io.cpp index 8bdc40cc..e3808e7f 100644 --- a/bsnes/sfc/coprocessor/superfx/io.cpp +++ b/bsnes/sfc/coprocessor/superfx/io.cpp @@ -18,7 +18,7 @@ auto SuperFX::readIO(uint addr, uint8) -> uint8 { case 0x3031: { uint8 r = regs.sfr >> 8; regs.sfr.irq = 0; - cpu.r.irq = 0; + cpu.irq(0); return r; } diff --git a/bsnes/sfc/cpu/cpu.cpp b/bsnes/sfc/cpu/cpu.cpp index 5020183a..ad776880 100644 --- a/bsnes/sfc/cpu/cpu.cpp +++ b/bsnes/sfc/cpu/cpu.cpp @@ -33,34 +33,34 @@ auto CPU::Enter() -> void { } } +auto CPU::boot() -> void { +} + auto CPU::main() -> void { if(r.wai) return instructionWait(); if(r.stp) return instructionStop(); + if(!status.interruptPending) return instruction(); - if(status.interruptPending) { - status.interruptPending = false; - if(status.nmiPending) { - status.nmiPending = false; - 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); - } + if(status.nmiPending) { + status.nmiPending = 0; + r.vector = r.e ? 0xfffa : 0xffea; + return interrupt(); } - 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 { @@ -109,13 +109,11 @@ auto CPU::power(bool reset) -> void { alu = {}; status = {}; - status.lineClocks = hperiod(); status.dramRefreshPosition = (version == 1 ? 530 : 538); status.hdmaSetupPosition = (version == 1 ? 12 + 8 - dmaCounter() : 12 + dmaCounter()); status.hdmaPosition = 1104; - status.powerPending = reset == 0; - status.resetPending = reset == 1; - status.interruptPending = true; + status.resetPending = 1; + status.interruptPending = 1; } } diff --git a/bsnes/sfc/cpu/cpu.hpp b/bsnes/sfc/cpu/cpu.hpp index 5ecacad9..d65de3b2 100644 --- a/bsnes/sfc/cpu/cpu.hpp +++ b/bsnes/sfc/cpu/cpu.hpp @@ -9,6 +9,7 @@ struct CPU : Processor::WDC65816, Thread, PPUcounter { auto synchronizePPU() -> void; auto synchronizeCoprocessors() -> void; static auto Enter() -> void; + auto boot() -> void; auto main() -> void; auto load() -> bool; auto power(bool reset) -> void; @@ -51,9 +52,9 @@ struct CPU : Processor::WDC65816, Thread, PPUcounter { alwaysinline auto aluEdge() -> void; alwaysinline auto dmaEdge() -> void; - alwaysinline auto lastCycle() -> void; //irq.cpp + auto irq(bool line) -> void override; alwaysinline auto pollInterrupts() -> void; auto nmitimenUpdate(uint8 data) -> void; auto rdnmi() -> bool; @@ -61,6 +62,7 @@ struct CPU : Processor::WDC65816, Thread, PPUcounter { alwaysinline auto nmiTest() -> bool; alwaysinline auto irqTest() -> bool; + alwaysinline auto lastCycle() -> void; //joypad.cpp auto joypadEdge() -> void; @@ -86,18 +88,17 @@ private: struct Status { uint clockCount = 0; - uint lineClocks = 0; - bool irqLock = false; + bool irqLock = 0; uint dramRefreshPosition = 0; uint dramRefresh = 0; //0 = not refreshed; 1 = refresh active; 2 = refresh inactive uint hdmaSetupPosition = 0; - bool hdmaSetupTriggered = false; + bool hdmaSetupTriggered = 0; uint hdmaPosition = 0; - bool hdmaTriggered = false; + bool hdmaTriggered = 0; boolean nmiValid = 0; boolean nmiLine = 0; @@ -111,18 +112,16 @@ private: boolean irqPending = 0; boolean irqHold = 0; - bool powerPending = false; - bool resetPending = false; + bool resetPending = 0; + bool interruptPending = 0; - bool interruptPending = false; - - bool dmaActive = false; - bool dmaPending = false; - bool hdmaPending = false; + bool dmaActive = 0; + bool dmaPending = 0; + bool hdmaPending = 0; bool hdmaMode = 0; //0 = init, 1 = run - bool autoJoypadActive = false; - bool autoJoypadLatch = false; + bool autoJoypadActive = 0; + bool autoJoypadLatch = 0; uint autoJoypadCounter = 0; } status; diff --git a/bsnes/sfc/cpu/irq.cpp b/bsnes/sfc/cpu/irq.cpp index 1933d878..d97983a3 100644 --- a/bsnes/sfc/cpu/irq.cpp +++ b/bsnes/sfc/cpu/irq.cpp @@ -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; //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. auto CPU::pollInterrupts() -> void { //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 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 - status.irqHold = false; - if(status.irqLine && io.irqEnable) status.irqTransition = true; + status.irqHold = 0; + if(status.irqLine && io.irqEnable) { + status.irqTransition = 1; + r.wai = 0; + } //IRQ test if(status.irqValid.raise(io.irqEnable && (!io.virqEnable || vcounter(10) == io.vtime) && (!io.hirqEnable || hcounter(10) == io.htime) && (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 { - io.hirqEnable = bit1(data,4); - io.virqEnable = bit1(data,5); + io.hirqEnable = data & 0x10; + io.virqEnable = data & 0x20; io.irqEnable = io.hirqEnable || io.virqEnable; if(io.virqEnable && !io.hirqEnable && status.irqLine) { - status.irqTransition = true; + status.irqTransition = 1; + r.wai = 0; } else if(!io.irqEnable) { - status.irqLine = false; - status.irqTransition = false; + status.irqLine = 0; + status.irqTransition = 0; } - if(io.nmiEnable.raise(bit1(data,7)) && status.nmiLine) { - status.nmiTransition = true; + if(io.nmiEnable.raise(data & 0x80) && status.nmiLine) { + status.nmiTransition = 1; + r.wai = 0; } - status.irqLock = true; + status.irqLock = 1; } auto CPU::rdnmi() -> bool { bool result = status.nmiLine; if(!status.nmiHold) { - status.nmiLine = false; + status.nmiLine = 0; } return result; } @@ -54,22 +71,32 @@ auto CPU::rdnmi() -> bool { auto CPU::timeup() -> bool { bool result = status.irqLine; if(!status.irqHold) { - status.irqLine = false; - status.irqTransition = false; + status.irqLine = 0; + status.irqTransition = 0; } return result; } auto CPU::nmiTest() -> bool { - if(!status.nmiTransition) return false; - status.nmiTransition = false; - r.wai = false; - return true; + if(!status.nmiTransition) return 0; + status.nmiTransition = 0; + return 1; } auto CPU::irqTest() -> bool { - if(!status.irqTransition && !r.irq) return false; - status.irqTransition = false; - r.wai = false; + if(!status.irqTransition) return 0; + status.irqTransition = 0; 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; + } +} diff --git a/bsnes/sfc/cpu/serialization.cpp b/bsnes/sfc/cpu/serialization.cpp index e2cfe57f..298079b1 100644 --- a/bsnes/sfc/cpu/serialization.cpp +++ b/bsnes/sfc/cpu/serialization.cpp @@ -11,7 +11,6 @@ auto CPU::serialize(serializer& s) -> void { s.integer(counter.dma); s.integer(status.clockCount); - s.integer(status.lineClocks); s.integer(status.irqLock); @@ -36,9 +35,7 @@ auto CPU::serialize(serializer& s) -> void { s.boolean(status.irqPending); s.boolean(status.irqHold); - s.integer(status.powerPending); s.integer(status.resetPending); - s.integer(status.interruptPending); s.integer(status.dmaActive); diff --git a/bsnes/sfc/cpu/timing.cpp b/bsnes/sfc/cpu/timing.cpp index d7b8c056..8d48fbc1 100644 --- a/bsnes/sfc/cpu/timing.cpp +++ b/bsnes/sfc/cpu/timing.cpp @@ -82,8 +82,6 @@ auto CPU::step(uint clocks) -> void { //called by ppu.tick() when Hcounter=0 auto CPU::scanline() -> void { - status.lineClocks = hperiod(); - //forcefully sync S-CPU to other processors, in case chips are not communicating synchronizeSMP(); synchronizePPU(); @@ -232,16 +230,3 @@ auto CPU::joypadEdge() -> void { 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; - } -}