diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index dc3d4229..a6d11ee1 100644 --- a/higan/emulator/emulator.hpp +++ b/higan/emulator/emulator.hpp @@ -6,7 +6,7 @@ using namespace nall; namespace Emulator { static const string Name = "higan"; - static const string Version = "097.10"; + static const string Version = "097.11"; static const string Author = "byuu"; static const string License = "GPLv3"; static const string Website = "http://byuu.org/"; diff --git a/higan/processor/v30mz/instructions-exec.cpp b/higan/processor/v30mz/instructions-exec.cpp index 74f61b95..54d59cfa 100644 --- a/higan/processor/v30mz/instructions-exec.cpp +++ b/higan/processor/v30mz/instructions-exec.cpp @@ -83,11 +83,13 @@ auto V30MZ::opReturnFarImm() { r.sp += offset; } +//cf iret auto V30MZ::opReturnInt() { wait(9); r.ip = pop(); r.cs = pop(); r.f = pop(); + state.poll = false; } auto V30MZ::opInt3() { @@ -136,6 +138,7 @@ auto V30MZ::opPushReg(uint16& reg) { auto V30MZ::opPopReg(uint16& reg) { reg = pop(); + if(® == &r.ss) state.poll = false; } //9c pushf @@ -148,6 +151,7 @@ auto V30MZ::opPushFlags() { auto V30MZ::opPopFlags() { wait(2); r.f = pop(); + state.poll = false; } //60 pusha diff --git a/higan/processor/v30mz/instructions-flag.cpp b/higan/processor/v30mz/instructions-flag.cpp index d814105e..f1f66c0a 100644 --- a/higan/processor/v30mz/instructions-flag.cpp +++ b/higan/processor/v30mz/instructions-flag.cpp @@ -24,4 +24,5 @@ auto V30MZ::opClearFlag(bool& flag) { auto V30MZ::opSetFlag(bool& flag) { wait(3); flag = true; + if(&flag == &r.f.i) state.poll = false; } diff --git a/higan/processor/v30mz/instructions-misc.cpp b/higan/processor/v30mz/instructions-misc.cpp index ad48c58d..0fa4c105 100644 --- a/higan/processor/v30mz/instructions-misc.cpp +++ b/higan/processor/v30mz/instructions-misc.cpp @@ -3,22 +3,25 @@ //36 ss: //3e ds: auto V30MZ::opSegment(uint16 segment) { - state.prefix = true; prefix.segment = segment; + state.prefix = true; + state.poll = false; } -//f2 repnz -//f3 repz +//f2 repnz: +//f3 repz: auto V30MZ::opRepeat(bool flag) { wait(4); if(r.cx == 0) return; - state.prefix = true; prefix.repeat = flag; + state.prefix = true; + state.poll = false; } -//f0 lock +//f0 lock: auto V30MZ::opLock() { state.prefix = true; + state.poll = false; } //9b wait diff --git a/higan/processor/v30mz/instructions-move.cpp b/higan/processor/v30mz/instructions-move.cpp index 509338f7..4c44597e 100644 --- a/higan/processor/v30mz/instructions-move.cpp +++ b/higan/processor/v30mz/instructions-move.cpp @@ -8,23 +8,27 @@ auto V30MZ::opMoveRegMem(Size size) { setReg(size, getMem(size)); } +//8c mov memw,seg auto V30MZ::opMoveMemSeg() { modRM(); setMem(Word, getSeg()); + state.poll = false; } +//8e mov seg,memw auto V30MZ::opMoveSegMem() { wait(1); modRM(); setSeg(getMem(Word)); + if((modrm.reg & 3) == 3) state.poll = false; } auto V30MZ::opMoveAccMem(Size size) { - setAcc(size, read(size, r.ds, fetch(Word))); + setAcc(size, read(size, segment(r.ds), fetch(Word))); } auto V30MZ::opMoveMemAcc(Size size) { - write(size, r.ds, fetch(Word), getAcc(size)); + write(size, segment(r.ds), fetch(Word), getAcc(size)); } auto V30MZ::opMoveRegImm(uint8& reg) { diff --git a/higan/processor/v30mz/modrm.cpp b/higan/processor/v30mz/modrm.cpp index 32df2ce0..06b3bb3b 100644 --- a/higan/processor/v30mz/modrm.cpp +++ b/higan/processor/v30mz/modrm.cpp @@ -20,7 +20,7 @@ auto V30MZ::modRM() -> void { case 3: modrm.segment = segment(r.ss); modrm.address = r.bp + r.di; break; case 4: modrm.segment = segment(r.ds); modrm.address = r.si; break; case 5: modrm.segment = segment(r.ds); modrm.address = r.di; break; - case 6: modrm.segment = segment(r.ds); modrm.address = r.bp; break; + case 6: modrm.segment = segment(r.ss); modrm.address = r.bp; break; case 7: modrm.segment = segment(r.ds); modrm.address = r.bx; break; } if(modrm.mod == 1) modrm.address += (int8)fetch(Byte); diff --git a/higan/processor/v30mz/v30mz.cpp b/higan/processor/v30mz/v30mz.cpp index a6fc46d6..386b4f3a 100644 --- a/higan/processor/v30mz/v30mz.cpp +++ b/higan/processor/v30mz/v30mz.cpp @@ -18,9 +18,11 @@ namespace Processor { #include "disassembler.cpp" auto V30MZ::exec() -> void { + state.poll = true; if(state.halt) return wait(1); #if 0 + static uint counter = 0; static uint16 cs = 0, ip = 0; if(cs != r.cs || ip != r.ip) print(disassemble(cs = r.cs, ip = r.ip), "\n"); #endif @@ -30,8 +32,8 @@ auto V30MZ::exec() -> void { if(state.prefix) { state.prefix = false; } else { - prefix.segment = nothing; prefix.repeat = nothing; + prefix.segment = nothing; } } @@ -302,8 +304,8 @@ auto V30MZ::instruction() -> void { auto V30MZ::interrupt(uint8 vector) -> void { state.halt = false; - auto ip = read(Word, 0x0000, vector * 2 + 0); - auto cs = read(Word, 0x0000, vector * 2 + 2); + auto ip = read(Word, 0x0000, vector * 4 + 0); + auto cs = read(Word, 0x0000, vector * 4 + 2); push(r.f); push(r.cs); @@ -318,25 +320,26 @@ auto V30MZ::interrupt(uint8 vector) -> void { } auto V30MZ::power() -> void { - state.halt = false; + state.halt = false; + state.poll = true; state.prefix = false; + prefix.repeat = nothing; prefix.segment = nothing; - prefix.repeat = nothing; - r.ip = 0x0000; r.ax = 0x0000; - r.bx = 0x0000; r.cx = 0x0000; r.dx = 0x0000; + r.bx = 0x0000; + r.sp = 0x0000; + r.bp = 0x0000; r.si = 0x0000; r.di = 0x0000; - r.bp = 0x0000; - r.sp = 0x0000; - r.cs = 0xffff; - r.ds = 0x0000; r.es = 0x0000; + r.cs = 0xffff; r.ss = 0x0000; + r.ds = 0x0000; + r.ip = 0x0000; r.f = 0x8000; } diff --git a/higan/processor/v30mz/v30mz.hpp b/higan/processor/v30mz/v30mz.hpp index ee9d2ed1..22adfbe2 100644 --- a/higan/processor/v30mz/v30mz.hpp +++ b/higan/processor/v30mz/v30mz.hpp @@ -7,7 +7,6 @@ namespace Processor { struct V30MZ { using Size = uint; enum : uint { Byte = 1, Word = 2, Long = 4 }; - struct ModRM; virtual auto wait(uint clocks = 1) -> void = 0; virtual auto read(uint20 addr) -> uint8 = 0; @@ -197,13 +196,14 @@ struct V30MZ { auto disassemble(uint16 cs, uint16 ip, bool registers = true, bool bytes = true) -> string; struct State { - bool halt; - bool prefix; + bool halt; //set to true for hlt instruction; blocks execution until next interrupt + bool poll; //set to false to suppress interrupt polling between CPU instructions + bool prefix; //set to true for prefix instructions; prevents flushing of Prefix struct } state; struct Prefix { - maybe segment; - maybe repeat; + maybe repeat; //repnz, repz + maybe segment; //cs, es, ss, ds } prefix; struct ModRM { @@ -211,24 +211,24 @@ struct V30MZ { uint3 reg; uint3 mem; - uint16 address; uint16 segment; + uint16 address; } modrm; struct Registers { - uint16 ip; union { uint16 ax; struct { uint8 order_lsb2(al, ah); }; }; - union { uint16 bx; struct { uint8 order_lsb2(bl, bh); }; }; union { uint16 cx; struct { uint8 order_lsb2(cl, ch); }; }; union { uint16 dx; struct { uint8 order_lsb2(dl, dh); }; }; + union { uint16 bx; struct { uint8 order_lsb2(bl, bh); }; }; + uint16 sp; + uint16 bp; uint16 si; uint16 di; - uint16 bp; - uint16 sp; - uint16 cs; - uint16 ds; uint16 es; + uint16 cs; uint16 ss; + uint16 ds; + uint16 ip; uint8* b[8]{&al, &cl, &dl, &bl, &ah, &ch, &dh, &bh}; uint16* w[8]{&ax, &cx, &dx, &bx, &sp, &bp, &si, &di}; diff --git a/higan/ws/cpu/cpu.cpp b/higan/ws/cpu/cpu.cpp index e90cf093..3080f5de 100644 --- a/higan/ws/cpu/cpu.cpp +++ b/higan/ws/cpu/cpu.cpp @@ -5,6 +5,7 @@ namespace WonderSwan { CPU cpu; #include "memory.cpp" #include "io.cpp" +#include "interrupt.cpp" #include "dma.cpp" auto CPU::Enter() -> void { @@ -18,6 +19,7 @@ auto CPU::main() -> void { scheduler.exit(Scheduler::ExitReason::SynchronizeEvent); } + poll(); exec(); } } @@ -55,17 +57,24 @@ auto CPU::power() -> void { create(CPU::Enter, 3072000); iomap[0x00a0] = this; + iomap[0x00b0] = this; + iomap[0x00b2] = this; + iomap[0x00b4] = this; + iomap[0x00b6] = this; if(WSC() || SC()) { for(uint p = 0x0040; p <= 0x0049; p++) iomap[p] = this; iomap[0x0062] = this; } - s.dmaSource = 0x00000; - s.dmaTarget = 0x0000; - s.dmaLength = 0x0000; - s.dmaEnable = false; - s.dmaMode = 0; + r.dmaSource = 0x00000; + r.dmaTarget = 0x0000; + r.dmaLength = 0x0000; + r.dmaEnable = false; + r.dmaMode = 0; + r.interruptBase = 0x00; + r.interruptEnable = 0x00; + r.interruptStatus = 0x00; } } diff --git a/higan/ws/cpu/cpu.hpp b/higan/ws/cpu/cpu.hpp index 91bc7c85..8ad7427d 100644 --- a/higan/ws/cpu/cpu.hpp +++ b/higan/ws/cpu/cpu.hpp @@ -1,4 +1,15 @@ struct CPU : Processor::V30MZ, Thread, IO { + enum class Interrupt : uint { + SerialSend, + Input, + Cartridge, + SerialReceive, + LineCompare, + VblankTimer, + Vblank, + HblankTimer, + }; + static auto Enter() -> void; auto main() -> void; @@ -20,23 +31,37 @@ struct CPU : Processor::V30MZ, Thread, IO { auto portRead(uint16 addr) -> uint8 override; auto portWrite(uint16 addr, uint8 data) -> void override; + //interrupt.cpp + auto poll() -> void; + auto raise(Interrupt) -> void; + auto lower(Interrupt) -> void; + //dma.cpp auto dmaTransfer() -> void; - struct State { - //$0040-0042 DMA_SRC + struct Registers { + //$0040-0042 DMA_SRC uint20 dmaSource; - //$0044-0045 DMA_DST + //$0044-0045 DMA_DST uint16_ dmaTarget; - //$0046-0047 DMA_LEN + //$0046-0047 DMA_LEN uint16_ dmaLength; - //$0048 DMA_CTRL + //$0048 DMA_CTRL bool dmaEnable; bool dmaMode; //0 = increment; 1 = decrement - } s; + + //$00b0 INT_BASE + uint8 interruptBase; + + //$00b2 INT_ENABLE + uint8 interruptEnable; + + //$00b4 INT_STATUS + uint8 interruptStatus; + } r; }; extern CPU cpu; diff --git a/higan/ws/cpu/dma.cpp b/higan/ws/cpu/dma.cpp index f8b25794..6bf97b28 100644 --- a/higan/ws/cpu/dma.cpp +++ b/higan/ws/cpu/dma.cpp @@ -1,29 +1,29 @@ auto CPU::dmaTransfer() -> void { //length of 0 or SRAM source address cause immediate termination - if(s.dmaLength == 0 || s.dmaSource.b2 == 1) { - s.dmaEnable = false; + if(r.dmaLength == 0 || r.dmaSource.b2 == 1) { + r.dmaEnable = false; return; } wait(5); - while(s.dmaLength) { + while(r.dmaLength) { wait(2); uint16 data = 0; //once DMA is started; SRAM reads still incur time penalty, but do not transfer - if(s.dmaSource.b2 != 1) { - data |= read(s.dmaSource + 0) << 0; - data |= read(s.dmaSource + 1) << 8; - write(s.dmaTarget + 0, data >> 0); - write(s.dmaTarget + 1, data >> 8); + if(r.dmaSource.b2 != 1) { + data |= read(r.dmaSource + 0) << 0; + data |= read(r.dmaSource + 1) << 8; + write(r.dmaTarget + 0, data >> 0); + write(r.dmaTarget + 1, data >> 8); } - if(s.dmaMode == 0) { - s.dmaSource += 2; - s.dmaTarget += 2; + if(r.dmaMode == 0) { + r.dmaSource += 2; + r.dmaTarget += 2; } else { - s.dmaSource -= 2; - s.dmaTarget -= 2; + r.dmaSource -= 2; + r.dmaTarget -= 2; } - s.dmaLength -= 2; + r.dmaLength -= 2; }; - s.dmaEnable = false; + r.dmaEnable = false; } diff --git a/higan/ws/cpu/interrupt.cpp b/higan/ws/cpu/interrupt.cpp new file mode 100644 index 00000000..a5db055e --- /dev/null +++ b/higan/ws/cpu/interrupt.cpp @@ -0,0 +1,21 @@ +auto CPU::poll() -> void { + if(!state.poll || !(r.interruptStatus & r.interruptEnable)) return; + state.halt = false; + if(!V30MZ::r.f.i) return; + + for(int n = 7; n >= 0; n--) { + if(r.interruptStatus & r.interruptEnable & (1 << n)) { + interrupt(r.interruptBase + n); + } + } +} + +auto CPU::raise(Interrupt i) -> void { + auto mask = 1 << (uint)i; + r.interruptStatus |= mask; +} + +auto CPU::lower(Interrupt i) -> void { + auto mask = 1 << (uint)i; + r.interruptStatus &= ~mask; +} diff --git a/higan/ws/cpu/io.cpp b/higan/ws/cpu/io.cpp index 81c7512e..7a3bea99 100644 --- a/higan/ws/cpu/io.cpp +++ b/higan/ws/cpu/io.cpp @@ -1,19 +1,19 @@ auto CPU::portRead(uint16 addr) -> uint8 { //DMA_SRC - if(addr == 0x0040) return s.dmaSource.b0; - if(addr == 0x0041) return s.dmaSource.b1; - if(addr == 0x0042) return s.dmaSource.b2; + if(addr == 0x0040) return r.dmaSource.b0; + if(addr == 0x0041) return r.dmaSource.b1; + if(addr == 0x0042) return r.dmaSource.b2; //DMA_DST - if(addr == 0x0044) return s.dmaTarget.b0; - if(addr == 0x0045) return s.dmaTarget.b1; + if(addr == 0x0044) return r.dmaTarget.b0; + if(addr == 0x0045) return r.dmaTarget.b1; //DMA_LEN - if(addr == 0x0046) return s.dmaLength.b0; - if(addr == 0x0047) return s.dmaLength.b1; + if(addr == 0x0046) return r.dmaLength.b0; + if(addr == 0x0047) return r.dmaLength.b1; //DMA_CTRL - if(addr == 0x0048) return s.dmaEnable << 7 | s.dmaMode << 0; + if(addr == 0x0048) return r.dmaEnable << 7 | r.dmaMode << 0; //WSC_SYSTEM if(addr == 0x0062) { @@ -22,44 +22,78 @@ auto CPU::portRead(uint16 addr) -> uint8 { //HW_FLAGS if(addr == 0x00a0) { - return 1 << 7 //1 = built-in self-test passed - | 1 << 2 //0 = 8-bit bus width; 1 = 16-bit bus width - | !WS() << 1 //0 = WonderSwan; 1 = WonderSwan Color or SwanCrystal - | 1 << 0; //0 = BIOS mapped; 1 = cartridge mapped + return ( + 1 << 7 //1 = built-in self-test passed + | 1 << 2 //0 = 8-bit bus width; 1 = 16-bit bus width + | !WS() << 1 //0 = WonderSwan; 1 = WonderSwan Color or SwanCrystal + | 1 << 0 //0 = BIOS mapped; 1 = cartridge mapped + ); } + //INT_BASE + if(addr == 0x00b0) { + if(WS()) return r.interruptBase | 3; + return r.interruptBase; + } + + //INT_ENABLE + if(addr == 0x00b2) return r.interruptEnable; + + //INT_STATUS + if(addr == 0x00b4) return r.interruptStatus; + return 0x00; } auto CPU::portWrite(uint16 addr, uint8 data) -> void { //DMA_SRC - if(addr == 0x0040) { s.dmaSource.b0 = data & ~1; return; } - if(addr == 0x0041) { s.dmaSource.b1 = data; return; } - if(addr == 0x0042) { s.dmaSource.b2 = data; return; } + if(addr == 0x0040) { r.dmaSource.b0 = data & ~1; return; } + if(addr == 0x0041) { r.dmaSource.b1 = data; return; } + if(addr == 0x0042) { r.dmaSource.b2 = data; return; } //DMA_DST - if(addr == 0x0044) { s.dmaTarget.b0 = data & ~1; return; } - if(addr == 0x0045) { s.dmaTarget.b1 = data; return; } + if(addr == 0x0044) { r.dmaTarget.b0 = data & ~1; return; } + if(addr == 0x0045) { r.dmaTarget.b1 = data; return; } //DMA_LEN - if(addr == 0x0046) { s.dmaLength.b0 = data & ~1; return; } - if(addr == 0x0047) { s.dmaLength.b1 = data; return; } + if(addr == 0x0046) { r.dmaLength.b0 = data & ~1; return; } + if(addr == 0x0047) { r.dmaLength.b1 = data; return; } //DMA_CTRL if(addr == 0x0048) { - s.dmaEnable = (uint1)(data >> 7); - s.dmaMode = (uint1)(data >> 0); - if(s.dmaEnable) dmaTransfer(); + r.dmaEnable = (uint1)(data >> 7); + r.dmaMode = (uint1)(data >> 0); + if(r.dmaEnable) dmaTransfer(); return; } //WSC_SYSTEM if(addr == 0x0062) { //todo: d0 = 1 powers off system + return; } //HW_FLAGS if(addr == 0x00a0) { //todo: d2 (bus width) bit is writable; but ... it will do very bad things + return; + } + + //INT_BASE + if(addr == 0x00b0) { + r.interruptBase = WS() ? (data & ~7) : (data & ~1); + return; + } + + //INT_ENABLE + if(addr == 0x00b2) { + r.interruptEnable = data; + return; + } + + //INT_ACK + if(addr == 0x00b6) { + r.interruptStatus &= ~data; + return; } } diff --git a/higan/ws/ppu/ppu.cpp b/higan/ws/ppu/ppu.cpp index f8873512..b903c9fc 100644 --- a/higan/ws/ppu/ppu.cpp +++ b/higan/ws/ppu/ppu.cpp @@ -17,15 +17,24 @@ auto PPU::main() -> void { } step(256); - - status.hclk = 0; - if(++status.vclk == 159) { - status.vclk = 0; - video.refresh(); - } + scanline(); } } +auto PPU::scanline() -> void { + status.hclk = 0; + status.vclk++; + if(status.vclk == 144) { + cpu.raise(CPU::Interrupt::Vblank); + } + if(status.vclk == 159) frame(); +} + +auto PPU::frame() -> void { + status.vclk = 0; + video.refresh(); +} + auto PPU::step(uint clocks) -> void { status.hclk += clocks; diff --git a/higan/ws/ppu/ppu.hpp b/higan/ws/ppu/ppu.hpp index 8dd8c306..c2674ef0 100644 --- a/higan/ws/ppu/ppu.hpp +++ b/higan/ws/ppu/ppu.hpp @@ -4,6 +4,8 @@ struct PPU : Thread, IO { static auto Enter() -> void; auto main() -> void; + auto scanline() -> void; + auto frame() -> void; auto step(uint clocks) -> void; auto power() -> void;