bsnes/higan/gba/cpu/io.cpp
Tim Allen d4876a831f Update to v103r07 release.
byuu says:

Changelog:

  - gba/cpu: massive code cleanup effort
  - gba/cpu: DMA can run in between active instructions¹
  - gba/cpu: added two-cycle startup delay between DMA activation and
    DMA transfers²
  - processor/spc700: BBC, BBC, CBNE cycle 4 is an idle cycle
  - processor/spc700: ADDW, SUBW, MOVW (read) cycle 4 is an idle cycle

¹: unfortunately, this causes yet another performance penalty for the
poor GBA core =( Also, I think I may have missed disabling DMAs while
the CPU is stopped. I'll fix that in the next WIP.

²: I put the waiting counter decrement at the wrong place, so this
doesn't actually work. Needs to be more like
this:

    auto CPU::step(uint clocks) -> void {
      for(auto _ : range(clocks)) {
        for(auto& timer : this->timer) timer.run();
        for(auto& dma : this->dma) if(dma.active && dma.waiting) dma.waiting--;
        context.clock++;
      }
      ...

    auto CPU::DMA::run() -> bool {
      if(cpu.stopped() || !active || waiting) return false;

      transfer();
      if(irq) cpu.irq.flag |= CPU::Interrupt::DMA0 << id;
      if(drq && id == 3) cpu.irq.flag |= CPU::Interrupt::Cartridge;
      return true;
    }

Of course, the real fix will be restructuring how DMA works, so that
it's always running in parallel with the CPU instead of this weird
design where it tries to run all channels in some kind of loop until no
channels are active anymore whenever one channel is activated.

Not really sure how to design that yet, however.
2017-07-05 15:29:27 +10:00

427 lines
13 KiB
C++

auto CPU::readIO(uint32 addr) -> uint8 {
auto dma = [&]() -> DMA& { return this->dma[addr / 12 & 3]; };
auto timer = [&]() -> Timer& { return this->timer[addr.bits(2,3)]; };
switch(addr) {
//DMA0CNT_L, DMA1CNT_L, DMA2CNT_L, DMA3CNT_L
case 0x0400'00b8: case 0x0400'00c4: case 0x0400'00d0: case 0x0400'00dc: return 0x00;
case 0x0400'00b9: case 0x0400'00c5: case 0x0400'00d1: case 0x0400'00dd: return 0x00;
//DMA0CNT_H, DMA1CNT_H, DMA2CNT_H, DMA3CNT_H
case 0x0400'00ba: case 0x0400'00c6: case 0x0400'00d2: case 0x0400'00de: return (
dma().targetMode << 5
| dma().sourceMode.bit(0) << 7
);
case 0x0400'00bb: case 0x0400'00c7: case 0x0400'00d3: case 0x0400'00df: return (
dma().sourceMode.bit(1) << 0
| dma().repeat << 1
| dma().size << 2
| dma().drq << 3
| dma().timingMode << 4
| dma().irq << 6
| dma().enable << 7
);
//TM0CNT_L, TM1CNT_L, TM2CNT_L, TM3CNT_L
case 0x0400'0100: case 0x0400'0104: case 0x0400'0108: case 0x0400'010c: return timer().period.byte(0);
case 0x0400'0101: case 0x0400'0105: case 0x0400'0109: case 0x0400'010d: return timer().period.byte(1);
//TM0CNT_H, TM1CNT_H, TM2CNT_H, TM3CNT_H
case 0x0400'0102: case 0x0400'0106: case 0x0400'010a: case 0x0400'010e: return (
timer().frequency << 0
| timer().cascade << 2
| timer().irq << 6
| timer().enable << 7
);
case 0x0400'0103: case 0x0400'0107: case 0x0400'010b: case 0x0400'010f: return 0;
//SIOMULTI0 (SIODATA32_L), SIOMULTI1 (SIODATA32_H), SIOMULTI2, SIOMULTI3
case 0x0400'0120: case 0x0400'0122: case 0x0400'0124: case 0x0400'0126: {
if(auto data = player.read()) return data().byte(addr.bits(0,1));
return serial.data[addr.bits(1,2)].byte(0);
}
case 0x0400'0121: case 0x0400'0123: case 0x0400'0125: case 0x0400'0127: {
if(auto data = player.read()) return data().byte(addr.bits(0,1));
return serial.data[addr.bits(1,2)].byte(1);
}
//SIOCNT
case 0x0400'0128: return (
serial.shiftClockSelect << 0
| serial.shiftClockFrequency << 1
| serial.transferEnableReceive << 2
| serial.transferEnableSend << 3
| serial.startBit << 7
);
case 0x0400'0129: return (
serial.transferLength << 4
| serial.irqEnable << 6
);
//SIOMLT_SEND (SIODATA8)
case 0x0400'012a: return serial.data8;
case 0x0400'012b: return 0;
//KEYINPUT
case 0x04000130: {
static const uint landscape[] = {5, 4, 8, 9, 3, 2, 0, 1};
static const uint portrait[] = {5, 4, 8, 9, 0, 1, 2, 3};
auto lookup = !settings.rotateLeft ? landscape : portrait;
if(auto result = player.keyinput()) return result() >> 0;
uint8 result = 0;
for(uint n = 0; n < 8; n++) result |= platform->inputPoll(0, 0, lookup[n]) << n;
if((result & 0xc0) == 0xc0) result &= (uint8)~0xc0; //up+down cannot be pressed simultaneously
if((result & 0x30) == 0x30) result &= (uint8)~0x30; //left+right cannot be pressed simultaneously
return result ^ 0xff;
}
case 0x04000131: {
if(auto result = player.keyinput()) return result() >> 8;
uint8 result = 0;
result |= platform->inputPoll(0, 0, 7) << 0;
result |= platform->inputPoll(0, 0, 6) << 1;
return result ^ 0x03;
}
//KEYCNT
case 0x0400'0132: return (
keypad.flag[0] << 0
| keypad.flag[1] << 1
| keypad.flag[2] << 2
| keypad.flag[3] << 3
| keypad.flag[4] << 4
| keypad.flag[5] << 5
| keypad.flag[6] << 6
| keypad.flag[7] << 7
);
case 0x0400'0133: return (
keypad.flag[8] << 0
| keypad.flag[9] << 1
| keypad.enable << 6
| keypad.condition << 7
);
//RCNT
case 0x0400'0134: return (
joybus.sc << 0
| joybus.sd << 1
| joybus.si << 2
| joybus.so << 3
| joybus.scMode << 4
| joybus.sdMode << 5
| joybus.siMode << 6
| joybus.soMode << 7
);
case 0x0400'0135: return (
joybus.siIRQEnable << 0
| joybus.mode << 6
);
//JOYCNT
case 0x0400'0140: return (
joybus.resetSignal << 0
| joybus.receiveComplete << 1
| joybus.sendComplete << 2
| joybus.resetIRQEnable << 6
);
case 0x0400'0141: return 0;
case 0x0400'0142: return 0;
case 0x0400'0143: return 0;
//JOY_RECV_L, JOY_RECV_H
case 0x0400'0150: return joybus.receive.byte(0);
case 0x0400'0151: return joybus.receive.byte(1);
case 0x0400'0152: return joybus.receive.byte(2);
case 0x0400'0153: return joybus.receive.byte(3);
//JOY_TRANS_L, JOY_TRANS_H
case 0x0400'0154: return joybus.transmit.byte(0);
case 0x0400'0155: return joybus.transmit.byte(1);
case 0x0400'0156: return joybus.transmit.byte(2);
case 0x0400'0157: return joybus.transmit.byte(3);
//JOYSTAT
case 0x0400'0158: return (
joybus.receiveFlag << 1
| joybus.sendFlag << 3
| joybus.generalFlag << 4
);
case 0x0400'0159: return 0;
case 0x0400'015a: return 0;
case 0x0400'015b: return 0;
//IE
case 0x0400'0200: return irq.enable.byte(0);
case 0x0400'0201: return irq.enable.byte(1);
//IF
case 0x0400'0202: return irq.flag.byte(0);
case 0x0400'0203: return irq.flag.byte(1);
//WAITCNT
case 0x0400'0204: return (
wait.nwait[3] << 0
| wait.nwait[0] << 2
| wait.swait[0] << 4
| wait.nwait[1] << 5
| wait.swait[1] << 7
);
case 0x0400'0205: return (
wait.nwait[2] << 0
| wait.swait[2] << 2
| wait.phi << 3
| wait.prefetch << 6
| wait.gameType << 7
);
//IME
case 0x0400'0208: return irq.ime;
case 0x0400'0209: return 0;
//POSTFLG + HALTCNT
case 0x0400'0300: return context.booted;
case 0x0400'0301: return 0;
//MEMCNT_L
case 0x0400'0800: return (
memory.disable << 0
| memory.unknown1 << 1
| memory.ewram << 5
);
case 0x0400'0801: return 0;
//MEMCNT_H
case 0x0400'0802: return 0;
case 0x0400'0803: return (
memory.ewramWait << 0
| memory.unknown2 << 4
);
}
return cpu.pipeline.fetch.instruction.byte(addr & 1);
}
auto CPU::writeIO(uint32 addr, uint8 data) -> void {
auto dma = [&]() -> DMA& { return this->dma[addr / 12 & 3]; };
auto timer = [&]() -> Timer& { return this->timer[addr.bits(2,3)]; };
switch(addr) {
//DMA0SAD, DMA1SAD, DMA2SAD, DMA3SAD
case 0x0400'00b0: case 0x0400'00bc: case 0x0400'00c8: case 0x0400'00d4: dma().source.byte(0) = data; return;
case 0x0400'00b1: case 0x0400'00bd: case 0x0400'00c9: case 0x0400'00d5: dma().source.byte(1) = data; return;
case 0x0400'00b2: case 0x0400'00be: case 0x0400'00ca: case 0x0400'00d6: dma().source.byte(2) = data; return;
case 0x0400'00b3: case 0x0400'00bf: case 0x0400'00cb: case 0x0400'00d7: dma().source.byte(3) = data; return;
//DMA0DAD, DMA1DAD, DMA2DAD, DMA3DAD
case 0x0400'00b4: case 0x0400'00c0: case 0x0400'00cc: case 0x0400'00d8: dma().target.byte(0) = data; return;
case 0x0400'00b5: case 0x0400'00c1: case 0x0400'00cd: case 0x0400'00d9: dma().target.byte(1) = data; return;
case 0x0400'00b6: case 0x0400'00c2: case 0x0400'00ce: case 0x0400'00da: dma().target.byte(2) = data; return;
case 0x0400'00b7: case 0x0400'00c3: case 0x0400'00cf: case 0x0400'00db: dma().target.byte(3) = data; return;
//DMA0CNT_L, DMA1CNT_L, DMA2CNT_L, DMA3CNT_L
case 0x0400'00b8: case 0x0400'00c4: case 0x0400'00d0: case 0x0400'00dc: dma().length.byte(0) = data; return;
case 0x0400'00b9: case 0x0400'00c5: case 0x0400'00d1: case 0x0400'00dd: dma().length.byte(1) = data; return;
//DMA0CNT_H, DMA1CNT_H, DMA2CNT_H, DMA3CNT_H
case 0x0400'00ba: case 0x0400'00c6: case 0x0400'00d2: case 0x0400'00de:
dma().targetMode = data.bits(5,6);
dma().sourceMode.bit(0) = data.bit (7);
return;
case 0x0400'00bb: case 0x0400'00c7: case 0x0400'00d3: case 0x0400'00df: {
bool enable = dma().enable;
if(addr != 0x0400'00df) data.bit(3) = 0; //gamepad DRQ valid for DMA3 only
dma().sourceMode.bit(1) = data.bit (0);
dma().repeat = data.bit (1);
dma().size = data.bit (2);
dma().drq = data.bit (3);
dma().timingMode = data.bits(4,5);
dma().irq = data.bit (6);
dma().enable = data.bit (7);
if(!enable && dma().enable) { //0->1 transition
if(dma().timingMode == 0) {
dma().active = true; //immediate transfer mode
dma().waiting = 2;
}
dma().latch.target = dma().target;
dma().latch.source = dma().source;
dma().latch.length = dma().length;
} else if(!dma().enable) {
dma().active = false;
}
return;
}
//TM0CNT_L, TM1CNT_L, TM2CNT_L, TM3CNT_L
case 0x0400'0100: case 0x0400'0104: case 0x0400'0108: case 0x0400'010c: timer().reload.byte(0) = data; return;
case 0x0400'0101: case 0x0400'0105: case 0x0400'0109: case 0x0400'010d: timer().reload.byte(1) = data; return;
//TM0CNT_H, TM1CNT_H, TM2CNT_H, TM3CNT_H
case 0x0400'0102: case 0x0400'0106: case 0x0400'010a: case 0x0400'010e: {
bool enable = timer().enable;
timer().frequency = data.bits(0,1);
timer().cascade = data.bit (2);
timer().irq = data.bit (6);
timer().enable = data.bit (7);
if(!enable && timer().enable) { //0->1 transition
timer().pending = true;
}
return;
}
case 0x0400'0103: case 0x0400'0107: case 0x0400'010b: case 0x0400'010f:
return;
//SIOMULTI0 (SIODATA32_L), SIOMULTI1 (SIODATA32_H), SIOMULTI2, SIOMULTI3
case 0x0400'0120: case 0x0400'0122: case 0x0400'0124: case 0x0400'0126:
player.write(addr.bits(0,1), data);
serial.data[addr.bits(1,2)].byte(0) = data;
return;
case 0x0400'0121: case 0x0400'0123: case 0x0400'0125: case 0x0400'0127:
player.write(addr.bits(0,1), data);
serial.data[addr.bits(1,2)].byte(1) = data;
return;
//SIOCNT
case 0x0400'0128:
serial.shiftClockSelect = data.bit(0);
serial.shiftClockFrequency = data.bit(1);
serial.transferEnableReceive = data.bit(2);
serial.transferEnableSend = data.bit(3);
serial.startBit = data.bit(7);
return;
case 0x0400'0129:
serial.transferLength = data.bit(4);
serial.irqEnable = data.bit(6);
return;
//SIOMLT_SEND (SIODATA8)
case 0x0400'012a: serial.data8 = data; return;
case 0x0400'012b: return;
//KEYCNT
case 0x0400'0132:
keypad.flag[0] = data.bit(0);
keypad.flag[1] = data.bit(1);
keypad.flag[2] = data.bit(2);
keypad.flag[3] = data.bit(3);
keypad.flag[4] = data.bit(4);
keypad.flag[5] = data.bit(5);
keypad.flag[6] = data.bit(6);
keypad.flag[7] = data.bit(7);
return;
case 0x0400'0133:
keypad.flag[8] = data.bit(0);
keypad.flag[9] = data.bit(1);
keypad.enable = data.bit(6);
keypad.condition = data.bit(7);
return;
//RCNT
case 0x0400'0134:
joybus.sc = data.bit(0);
joybus.sd = data.bit(1);
joybus.si = data.bit(2);
joybus.so = data.bit(3);
joybus.scMode = data.bit(4);
joybus.sdMode = data.bit(5);
joybus.siMode = data.bit(6);
joybus.soMode = data.bit(7);
return;
case 0x0400'0135:
joybus.siIRQEnable = data.bit (0);
joybus.mode = data.bits(6,7);
return;
//JOYCNT
case 0x0400'0140:
joybus.resetSignal = data.bit(0);
joybus.receiveComplete = data.bit(1);
joybus.sendComplete = data.bit(2);
joybus.resetIRQEnable = data.bit(6);
return;
case 0x0400'0141: return;
case 0x0400'0142: return;
case 0x0400'0143: return;
//JOY_RECV_L
//JOY_RECV_H
case 0x0400'0150: joybus.receive.byte(0) = data; return;
case 0x0400'0151: joybus.receive.byte(1) = data; return;
case 0x0400'0152: joybus.receive.byte(2) = data; return;
case 0x0400'0153: joybus.receive.byte(3) = data; return;
//JOY_TRANS_L
//JOY_TRANS_H
case 0x0400'0154: joybus.transmit.byte(0) = data; return;
case 0x0400'0155: joybus.transmit.byte(1) = data; return;
case 0x0400'0156: joybus.transmit.byte(2) = data; return;
case 0x0400'0157: joybus.transmit.byte(3) = data; return;
//JOYSTAT
case 0x0400'0158:
joybus.receiveFlag = data.bit (1);
joybus.sendFlag = data.bit (3);
joybus.generalFlag = data.bits(4,5);
return;
case 0x0400'0159: return;
//IE
case 0x0400'0200: irq.enable.byte(0) = data; return;
case 0x0400'0201: irq.enable.byte(1) = data; return;
//IF
case 0x0400'0202: irq.flag.byte(0) = irq.flag.byte(0) & ~data; return;
case 0x0400'0203: irq.flag.byte(1) = irq.flag.byte(1) & ~data; return;
//WAITCNT
case 0x0400'0204:
wait.swait[3] = data.bit (0); //todo: is this correct?
wait.nwait[3] = data.bits(0,1);
wait.nwait[0] = data.bits(2,3);
wait.swait[0] = data.bit (4);
wait.nwait[1] = data.bits(5,6);
wait.swait[1] = data.bit (7);
return;
case 0x0400'0205:
wait.nwait[2] = data.bits(0,1);
wait.swait[2] = data.bit (2);
wait.phi = data.bit (3);
wait.prefetch = data.bit (6);
//wait.gameType is read-only
return;
//IME
case 0x0400'0208: irq.ime = data.bit(0); return;
case 0x0400'0209: return;
//POSTFLG, HALTCNT
case 0x0400'0300:
if(data.bit(0)) context.booted = 1;
return;
case 0x0400'0301:
context.halted = data.bit(7) == 0;
context.stopped = data.bit(7) == 1;
return;
//MEMCNT_L
//MEMCNT_H
case 0x0400'0800:
memory.disable = data.bit (0);
memory.unknown1 = data.bits(1,3);
memory.ewram = data.bit (5);
return;
case 0x0400'0801: return;
case 0x0400'0802: return;
case 0x0400'0803:
memory.ewramWait = data.bits(0,3);
memory.unknown2 = data.bits(4,7);
return;
}
}