auto CPU::read(uint32 addr) -> uint8 {
  uint8 result = 0;

  switch(addr) {

  //DMA0CNT_H
  //DMA1CNT_H
  //DMA2CNT_H
  //DMA3CNT_H
  case 0x040000ba: case 0x040000bb:
  case 0x040000c6: case 0x040000c7:
  case 0x040000d2: case 0x040000d3:
  case 0x040000de: case 0x040000df: {
    auto& dma = regs.dma[(addr - 0x040000ba) / 12];
    unsigned shift = (addr & 1) * 8;
    return dma.control >> shift;
  }

  //TM0CNT_L
  //TM1CNT_L
  //TM2CNT_L
  //TM3CNT_L
  case 0x04000100: case 0x04000101:
  case 0x04000104: case 0x04000105:
  case 0x04000108: case 0x04000109:
  case 0x0400010c: case 0x0400010d: {
    auto& timer = regs.timer[(addr >> 2) & 3];
    unsigned shift = (addr & 1) * 8;
    return timer.period >> shift;
  }

  //TIM0CNT_H
  case 0x04000102: case 0x04000103:
  case 0x04000106: case 0x04000107:
  case 0x0400010a: case 0x0400010b:
  case 0x0400010e: case 0x0400010f: {
    auto& timer = regs.timer[(addr >> 2) & 3];
    unsigned shift = (addr & 1) * 8;
    return timer.control >> shift;
  }

  //SIOMULTI0 (SIODATA32_L)
  //SIOMULTI1 (SIODATA32_H)
  //SIOMULTI2
  //SIOMULTI3
  case 0x04000120: case 0x04000121:
  case 0x04000122: case 0x04000123:
  case 0x04000124: case 0x04000125:
  case 0x04000126: case 0x04000127: {
    if(auto data = player.read()) return data() >> ((addr & 3) << 3);
    unsigned shift = (addr & 1) * 8;
    auto& data = regs.serial.data[(addr >> 1) & 3];
    return data >> shift;
  }

  //SIOCNT
  case 0x04000128: return regs.serial.control >> 0;
  case 0x04000129: return regs.serial.control >> 8;

  //SIOMLT_SEND (SIODATA8)
  case 0x0400012a: return regs.serial.data8;
  case 0x0400012b: return 0u;

  //KEYINPUT
  case 0x04000130:
    if(auto result = player.keyinput()) return result() >> 0;
    for(unsigned n = 0; n < 8; n++) result |= interface->inputPoll(0, 0, n) << n;
    if((result & 0xc0) == 0xc0) result &= ~0xc0;  //up+down cannot be pressed simultaneously
    if((result & 0x30) == 0x30) result &= ~0x30;  //left+right cannot be pressed simultaneously
    return result ^ 0xff;
  case 0x04000131:
    if(auto result = player.keyinput()) return result() >> 8;
    result |= interface->inputPoll(0, 0, 8) << 0;
    result |= interface->inputPoll(0, 0, 9) << 1;
    return result ^ 0x03;

  //KEYCNT
  case 0x04000132: return regs.keypad.control >> 0;
  case 0x04000133: return regs.keypad.control >> 8;

  //RCNT
  case 0x04000134: return regs.joybus.settings >> 0;
  case 0x04000135: return regs.joybus.settings >> 8;

  //JOYCNT
  case 0x04000140: return regs.joybus.control >> 0;
  case 0x04000141: return regs.joybus.control >> 8;

  //JOY_RECV_L
  //JOY_RECV_H
  case 0x04000150: return regs.joybus.receive >>  0;
  case 0x04000151: return regs.joybus.receive >>  8;
  case 0x04000152: return regs.joybus.receive >> 16;
  case 0x04000153: return regs.joybus.receive >> 24;

  //JOY_TRANS_L
  //JOY_TRANS_H
  case 0x04000154: return regs.joybus.transmit >>  0;
  case 0x04000155: return regs.joybus.transmit >>  8;
  case 0x04000156: return regs.joybus.transmit >> 16;
  case 0x04000157: return regs.joybus.transmit >> 24;

  //JOYSTAT
  case 0x04000158: return regs.joybus.status >> 0;
  case 0x04000159: return regs.joybus.status >> 8;

  //IE
  case 0x04000200: return regs.irq.enable >> 0;
  case 0x04000201: return regs.irq.enable >> 8;

  //IF
  case 0x04000202: return regs.irq.flag >> 0;
  case 0x04000203: return regs.irq.flag >> 8;

  //WAITCNT
  case 0x04000204: return regs.wait.control >> 0;
  case 0x04000205: return regs.wait.control >> 8;

  //IME
  case 0x04000208: return regs.ime;
  case 0x04000209: return 0u;

  //POSTFLG + HALTCNT
  case 0x04000300: return regs.postboot;
  case 0x04000301: return 0u;

  //MEMCNT_L
  case 0x04000800: return regs.memory.control >>  0;
  case 0x04000801: return regs.memory.control >>  8;

  //MEMCNT_H
  case 0x04000802: return regs.memory.control >> 16;
  case 0x04000803: return regs.memory.control >> 24;

  }

  return 0u;
}

auto CPU::write(uint32 addr, uint8 byte) -> void {
  switch(addr) {

  //DMA0SAD
  //DMA1SAD
  //DMA2SAD
  //DMA3SAD
  case 0x040000b0: case 0x040000b1: case 0x040000b2: case 0x040000b3:
  case 0x040000bc: case 0x040000bd: case 0x040000be: case 0x040000bf:
  case 0x040000c8: case 0x040000c9: case 0x040000ca: case 0x040000cb:
  case 0x040000d4: case 0x040000d5: case 0x040000d6: case 0x040000d7: {
    auto& dma = regs.dma[(addr - 0x040000b0) / 12];
    unsigned shift = (addr & 3) * 8;
    dma.source = (dma.source & ~(255 << shift)) | (byte << shift);
    return;
  }

  //DMA0DAD
  //DMA1DAD
  //DMA2DAD
  //DMA3DAD
  case 0x040000b4: case 0x040000b5: case 0x040000b6: case 0x040000b7:
  case 0x040000c0: case 0x040000c1: case 0x040000c2: case 0x040000c3:
  case 0x040000cc: case 0x040000cd: case 0x040000ce: case 0x040000cf:
  case 0x040000d8: case 0x040000d9: case 0x040000da: case 0x040000db: {
    auto& dma = regs.dma[(addr - 0x040000b4) / 12];
    unsigned shift = (addr & 3) * 8;
    dma.target = (dma.target & ~(255 << shift)) | (byte << shift);
    return;
  }

  //DMA0CNT_L
  //DMA1CNT_L
  //DMA2CNT_L
  //DMA3CNT_L
  case 0x040000b8: case 0x040000b9:
  case 0x040000c4: case 0x040000c5:
  case 0x040000d0: case 0x040000d1:
  case 0x040000dc: case 0x040000dd: {
    auto& dma = regs.dma[(addr - 0x040000b8) / 12];
    unsigned shift = (addr & 1) * 8;
    dma.length = (dma.length & ~(255 << shift)) | (byte << shift);
    return;
  }

  //DMA0CNT_H
  //DMA1CNT_H
  //DMA2CNT_H
  //DMA3CNT_H
  case 0x040000ba: case 0x040000bb:
  case 0x040000c6: case 0x040000c7:
  case 0x040000d2: case 0x040000d3:
  case 0x040000de: case 0x040000df: {
    auto& dma = regs.dma[(addr - 0x040000ba) / 12];
    unsigned shift = (addr & 1) * 8;
    bool enable = dma.control.enable;
    dma.control = (dma.control & ~(255 << shift)) | (byte << shift);
    if(enable == 0 && dma.control.enable) {
      if(dma.control.timingmode == 0) dma.pending = true;  //immediate transfer mode
      dma.run.target = dma.target;
      dma.run.source = dma.source;
      dma.run.length = dma.length;
    } else if(dma.control.enable == 0) {
      dma.pending = false;
    }
    return;
  }

  //TM0CNT_L
  //TM1CNT_L
  //TM2CNT_L
  //TM3CNT_L
  case 0x04000100: case 0x04000101:
  case 0x04000104: case 0x04000105:
  case 0x04000108: case 0x04000109:
  case 0x0400010c: case 0x0400010d: {
    auto& timer = regs.timer[(addr >> 2) & 3];
    unsigned shift = (addr & 1) * 8;
    timer.reload = (timer.reload & ~(255 << shift)) | (byte << shift);
    return;
  }

  //TM0CNT_H
  //TM1CNT_H
  //TM2CNT_H
  //TM3CNT_H
  case 0x04000102:
  case 0x04000106:
  case 0x0400010a:
  case 0x0400010e: {
    auto& timer = regs.timer[(addr >> 2) & 3];
    bool enable = timer.control.enable;
    timer.control = byte;
    if(enable == 0 && timer.control.enable == 1) {
      timer.pending = true;
    }
    return;
  }

  //SIOMULTI0 (SIODATA32_L)
  //SIOMULTI1 (SIODATA32_H)
  //SIOMULTI2
  //SIOMULTI3
  case 0x04000120: case 0x04000121:
  case 0x04000122: case 0x04000123:
  case 0x04000124: case 0x04000125:
  case 0x04000126: case 0x04000127: {
    player.write(byte, addr & 3);
    auto& data = regs.serial.data[(addr >> 1) & 3];
    unsigned shift = (addr & 1) * 8;
    data = (data & ~(255 << shift)) | (byte << shift);
    return;
  }

  //SIOCNT
  case 0x04000128: regs.serial.control = (regs.serial.control & 0xff00) | (byte << 0); return;
  case 0x04000129: regs.serial.control = (regs.serial.control & 0x00ff) | (byte << 8); return;

  //SIOMLT_SEND (SIODATA8)
  case 0x0400012a: regs.serial.data8 = byte; return;
  case 0x0400012b: return;

  //KEYCNT
  case 0x04000132: regs.keypad.control = (regs.keypad.control & 0xff00) | (byte << 0); return;
  case 0x04000133: regs.keypad.control = (regs.keypad.control & 0x00ff) | (byte << 8); return;

  //RCNT
  case 0x04000134: regs.joybus.settings = (regs.joybus.settings & 0xff00) | (byte << 0); return;
  case 0x04000135: regs.joybus.settings = (regs.joybus.settings & 0x00ff) | (byte << 8); return;

  //JOYCNT
  case 0x04000140: regs.joybus.control = (regs.joybus.control & 0xff00) | (byte << 0); return;
  case 0x04000141: regs.joybus.control = (regs.joybus.control & 0x00ff) | (byte << 8); return;

  //JOY_RECV_L
  //JOY_RECV_H
  case 0x04000150: regs.joybus.receive = (regs.joybus.receive & 0xffffff00) | (byte <<  0); return;
  case 0x04000151: regs.joybus.receive = (regs.joybus.receive & 0xffff00ff) | (byte <<  8); return;
  case 0x04000152: regs.joybus.receive = (regs.joybus.receive & 0xff00ffff) | (byte << 16); return;
  case 0x04000153: regs.joybus.receive = (regs.joybus.receive & 0x00ffffff) | (byte << 24); return;

  //JOY_TRANS_L
  //JOY_TRANS_H
  case 0x04000154: regs.joybus.transmit = (regs.joybus.transmit & 0xffffff00) | (byte <<  0); return;
  case 0x04000155: regs.joybus.transmit = (regs.joybus.transmit & 0xffff00ff) | (byte <<  8); return;
  case 0x04000156: regs.joybus.transmit = (regs.joybus.transmit & 0xff00ffff) | (byte << 16); return;
  case 0x04000157: regs.joybus.transmit = (regs.joybus.transmit & 0x00ffffff) | (byte << 24); return;

  //JOYSTAT
  case 0x04000158: regs.joybus.status = (regs.joybus.status & 0xff00) | (byte << 0); return;
  case 0x04000159: regs.joybus.status = (regs.joybus.status & 0x00ff) | (byte << 8); return;

  //IE
  case 0x04000200: regs.irq.enable = (regs.irq.enable & 0xff00) | (byte << 0); return;
  case 0x04000201: regs.irq.enable = (regs.irq.enable & 0x00ff) | (byte << 8); return;

  //IF
  case 0x04000202: regs.irq.flag = regs.irq.flag & ~(byte << 0); return;
  case 0x04000203: regs.irq.flag = regs.irq.flag & ~(byte << 8); return;

  //WAITCNT
  case 0x04000204: regs.wait.control = (regs.wait.control & 0xff00) | ((byte & 0xff) << 0); return;
  case 0x04000205: regs.wait.control = (regs.wait.control & 0x00ff) | ((byte & 0x7f) << 8); return;

  //IME
  case 0x04000208: regs.ime = byte >> 0; return;
  case 0x04000209: return;

  //POSTFLG, HALTCNT
  case 0x04000300: regs.postboot |= byte >> 0; return;
  case 0x04000301: regs.mode = byte & 0x80 ? Registers::Mode::Stop : Registers::Mode::Halt; return;

  //MEMCNT_L
  //MEMCNT_H
  case 0x04000800: regs.memory.control = (regs.memory.control & 0xffffff00) | (byte <<  0); return;
  case 0x04000801: regs.memory.control = (regs.memory.control & 0xffff00ff) | (byte <<  8); return;
  case 0x04000802: regs.memory.control = (regs.memory.control & 0xff00ffff) | (byte << 16); return;
  case 0x04000803: regs.memory.control = (regs.memory.control & 0x00ffffff) | (byte << 24); return;

  }
}
