auto SA1::readIO(uint24 addr, uint8) -> uint8 {
  cpu.active() ? cpu.synchronize(sa1) : synchronize(cpu);

  switch(0x2300 | addr.bits(0,7)) {

  //(SFR) S-CPU flag read
  case 0x2300: {
    uint8 data;
    data  = mmio.cpu_irqfl   << 7;
    data |= mmio.cpu_ivsw    << 6;
    data |= mmio.chdma_irqfl << 5;
    data |= mmio.cpu_nvsw    << 4;
    data |= mmio.cmeg;
    return data;
  }

  //(CFR) SA-1 flag read
  case 0x2301: {
    uint8 data;
    data  = mmio.sa1_irqfl   << 7;
    data |= mmio.timer_irqfl << 6;
    data |= mmio.dma_irqfl   << 5;
    data |= mmio.sa1_nmifl   << 4;
    data |= mmio.smeg;
    return data;
  }

  //(HCR) hcounter read
  case 0x2302: {
    //latch counters
    mmio.hcr = status.hcounter >> 2;
    mmio.vcr = status.vcounter;
    return mmio.hcr >> 0;
  }

  case 0x2303: {
    return mmio.hcr >> 8;
  }

  //(VCR) vcounter read
  case 0x2304: return mmio.vcr >> 0;
  case 0x2305: return mmio.vcr >> 8;

  //(MR) arithmetic result
  case 0x2306: return mmio.mr >>  0;
  case 0x2307: return mmio.mr >>  8;
  case 0x2308: return mmio.mr >> 16;
  case 0x2309: return mmio.mr >> 24;
  case 0x230a: return mmio.mr >> 32;

  //(OF) arithmetic overflow flag
  case 0x230b: return mmio.overflow << 7;

  //(VDPL) variable-length data read port low
  case 0x230c: {
    uint24 data;
    data.byte(0) = vbrRead(mmio.va + 0);
    data.byte(1) = vbrRead(mmio.va + 1);
    data.byte(2) = vbrRead(mmio.va + 2);
    data >>= mmio.vbit;

    return data >> 0;
  }

  //(VDPH) variable-length data read port high
  case 0x230d: {
    uint24 data;
    data.byte(0) = vbrRead(mmio.va + 0);
    data.byte(1) = vbrRead(mmio.va + 1);
    data.byte(2) = vbrRead(mmio.va + 2);
    data >>= mmio.vbit;

    if(mmio.hl == 1) {
      //auto-increment mode
      mmio.vbit += mmio.vb;
      mmio.va += (mmio.vbit >> 3);
      mmio.vbit &= 7;
    }

    return data >> 8;
  }

  //(VC) version code register
  case 0x230e: {
    return 0x01;  //true value unknown
  }

  }

  return 0x00;
}

auto SA1::writeIO(uint24 addr, uint8 data) -> void {
  cpu.active() ? cpu.synchronize(sa1) : synchronize(cpu);

  switch(0x2200 | addr.bits(0,7)) {

  //(CCNT) SA-1 control
  case 0x2200: {
    if(mmio.sa1_resb && !(data & 0x80)) {
      //reset SA-1 CPU (PC bank set to 0x00)
      r.pc = mmio.crv;
    }

    mmio.sa1_irq  = (data & 0x80);
    mmio.sa1_rdyb = (data & 0x40);
    mmio.sa1_resb = (data & 0x20);
    mmio.sa1_nmi  = (data & 0x10);
    mmio.smeg     = (data & 0x0f);

    if(mmio.sa1_irq) {
      mmio.sa1_irqfl = true;
      if(mmio.sa1_irqen) mmio.sa1_irqcl = 0;
    }

    if(mmio.sa1_nmi) {
      mmio.sa1_nmifl = true;
      if(mmio.sa1_nmien) mmio.sa1_nmicl = 0;
    }

    return;
  }

  //(SIE) S-CPU interrupt enable
  case 0x2201: {
    if(!mmio.cpu_irqen && (data & 0x80)) {
      if(mmio.cpu_irqfl) {
        mmio.cpu_irqcl = 0;
        cpu.r.irq = 1;
      }
    }

    if(!mmio.chdma_irqen && (data & 0x20)) {
      if(mmio.chdma_irqfl) {
        mmio.chdma_irqcl = 0;
        cpu.r.irq = 1;
      }
    }

    mmio.cpu_irqen   = (data & 0x80);
    mmio.chdma_irqen = (data & 0x20);
    return;
  }

  //(SIC) S-CPU interrupt clear
  case 0x2202: {
    mmio.cpu_irqcl   = (data & 0x80);
    mmio.chdma_irqcl = (data & 0x20);

    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;
    return;
  }

  //(CRV) SA-1 reset vector
  case 0x2203: { mmio.crv = (mmio.crv & 0xff00) | data; return; }
  case 0x2204: { mmio.crv = (data << 8) | (mmio.crv & 0xff); return; }

  //(CNV) SA-1 NMI vector
  case 0x2205: { mmio.cnv = (mmio.cnv & 0xff00) | data; return; }
  case 0x2206: { mmio.cnv = (data << 8) | (mmio.cnv & 0xff); return; }

  //(CIV) SA-1 IRQ vector
  case 0x2207: { mmio.civ = (mmio.civ & 0xff00) | data; return; }
  case 0x2208: { mmio.civ = (data << 8) | (mmio.civ & 0xff); return; }

  //(SCNT) S-CPU control
  case 0x2209: {
    mmio.cpu_irq  = (data & 0x80);
    mmio.cpu_ivsw = (data & 0x40);
    mmio.cpu_nvsw = (data & 0x10);
    mmio.cmeg     = (data & 0x0f);

    if(mmio.cpu_irq) {
      mmio.cpu_irqfl = true;
      if(mmio.cpu_irqen) {
        mmio.cpu_irqcl = 0;
        cpu.r.irq = 1;
      }
    }

    return;
  }

  //(CIE) SA-1 interrupt enable
  case 0x220a: {
    if(!mmio.sa1_irqen   && (data & 0x80) && mmio.sa1_irqfl  ) mmio.sa1_irqcl   = 0;
    if(!mmio.timer_irqen && (data & 0x40) && mmio.timer_irqfl) mmio.timer_irqcl = 0;
    if(!mmio.dma_irqen   && (data & 0x20) && mmio.dma_irqfl  ) mmio.dma_irqcl   = 0;
    if(!mmio.sa1_nmien   && (data & 0x10) && mmio.sa1_nmifl  ) mmio.sa1_nmicl   = 0;

    mmio.sa1_irqen   = (data & 0x80);
    mmio.timer_irqen = (data & 0x40);
    mmio.dma_irqen   = (data & 0x20);
    mmio.sa1_nmien   = (data & 0x10);
    return;
  }

  //(CIC) SA-1 interrupt clear
  case 0x220b: {
    mmio.sa1_irqcl   = (data & 0x80);
    mmio.timer_irqcl = (data & 0x40);
    mmio.dma_irqcl   = (data & 0x20);
    mmio.sa1_nmicl   = (data & 0x10);

    if(mmio.sa1_irqcl)   mmio.sa1_irqfl   = false;
    if(mmio.timer_irqcl) mmio.timer_irqfl = false;
    if(mmio.dma_irqcl)   mmio.dma_irqfl   = false;
    if(mmio.sa1_nmicl)   mmio.sa1_nmifl   = false;
    return;
  }

  //(SNV) S-CPU NMI vector
  case 0x220c: { mmio.snv = (mmio.snv & 0xff00) | data; return; }
  case 0x220d: { mmio.snv = (data << 8) | (mmio.snv & 0xff); return; }

  //(SIV) S-CPU IRQ vector
  case 0x220e: { mmio.siv = (mmio.siv & 0xff00) | data; return; }
  case 0x220f: { mmio.siv = (data << 8) | (mmio.siv & 0xff); return; }

  //(TMC) H/V timer control
  case 0x2210: {
    mmio.hvselb = (data & 0x80);
    mmio.ven    = (data & 0x02);
    mmio.hen    = (data & 0x01);
    return;
  }

  //(CTR) SA-1 timer restart
  case 0x2211: {
    status.vcounter = 0;
    status.hcounter = 0;
    return;
  }

  //(HCNT) H-count
  case 0x2212: { mmio.hcnt = (mmio.hcnt & 0xff00) | (data << 0); return; }
  case 0x2213: { mmio.hcnt = (mmio.hcnt & 0x00ff) | (data << 8); return; }

  //(VCNT) V-count
  case 0x2214: { mmio.vcnt = (mmio.vcnt & 0xff00) | (data << 0); return; }
  case 0x2215: { mmio.vcnt = (mmio.vcnt & 0x00ff) | (data << 8); return; }

  //(CXB) Super MMC bank C
  case 0x2220: {
    mmio.cbmode = (data & 0x80);
    mmio.cb     = (data & 0x07);
    return;
  }

  //(DXB) Super MMC bank D
  case 0x2221: {
    mmio.dbmode = (data & 0x80);
    mmio.db     = (data & 0x07);
    return;
  }

  //(EXB) Super MMC bank E
  case 0x2222: {
    mmio.ebmode = (data & 0x80);
    mmio.eb     = (data & 0x07);
    return;
  }

  //(FXB) Super MMC bank F
  case 0x2223: {
    mmio.fbmode = (data & 0x80);
    mmio.fb     = (data & 0x07);
    return;
  }

  //(BMAPS) S-CPU BW-RAM address mapping
  case 0x2224: {
    mmio.sbm = (data & 0x1f);
    return;
  }

  //(BMAP) SA-1 BW-RAM address mapping
  case 0x2225: {
    mmio.sw46 = (data & 0x80);
    mmio.cbm  = (data & 0x7f);
    return;
  }

  //(SWBE) S-CPU BW-RAM write enable
  case 0x2226: {
    mmio.swen = (data & 0x80);
    return;
  }

  //(CWBE) SA-1 BW-RAM write enable
  case 0x2227: {
    mmio.cwen = (data & 0x80);
    return;
  }

  //(BWPA) BW-RAM write-protected area
  case 0x2228: {
    mmio.bwp = (data & 0x0f);
    return;
  }

  //(SIWP) S-CPU I-RAM write protection
  case 0x2229: {
    mmio.siwp = data;
    return;
  }

  //(CIWP) SA-1 I-RAM write protection
  case 0x222a: {
    mmio.ciwp = data;
    return;
  }

  //(DCNT) DMA control
  case 0x2230: {
    mmio.dmaen = (data & 0x80);
    mmio.dprio = (data & 0x40);
    mmio.cden  = (data & 0x20);
    mmio.cdsel = (data & 0x10);
    mmio.dd    = (data & 0x04);
    mmio.sd    = (data & 0x03);

    if(mmio.dmaen == 0) dma.line = 0;
    return;
  }

  //(CDMA) character conversion DMA parameters
  case 0x2231: {
    mmio.chdend  = (data & 0x80);
    mmio.dmasize = (data >> 2) & 7;
    mmio.dmacb   = (data & 0x03);

    if(mmio.chdend) cpubwram.dma = false;
    if(mmio.dmasize > 5) mmio.dmasize = 5;
    if(mmio.dmacb   > 2) mmio.dmacb   = 2;
    return;
  }

  //(SDA) DMA source device start address
  case 0x2232: { mmio.dsa = (mmio.dsa & 0xffff00) | (data <<  0); return; }
  case 0x2233: { mmio.dsa = (mmio.dsa & 0xff00ff) | (data <<  8); return; }
  case 0x2234: { mmio.dsa = (mmio.dsa & 0x00ffff) | (data << 16); return; }

  //(DDA) DMA destination start address
  case 0x2235: { mmio.dda = (mmio.dda & 0xffff00) | (data <<  0); return; }
  case 0x2236: { mmio.dda = (mmio.dda & 0xff00ff) | (data <<  8);
    if(mmio.dmaen) {
      if(mmio.cden == 0 && mmio.dd == DMA::DestIRAM) {
        dmaNormal();
      } else if(mmio.cden == 1 && mmio.cdsel == 1) {
        dmaCC1();
      }
    }
    return;
  }
  case 0x2237: { mmio.dda = (mmio.dda & 0x00ffff) | (data << 16);
    if(mmio.dmaen) {
      if(mmio.cden == 0 && mmio.dd == DMA::DestBWRAM) {
        dmaNormal();
      }
    }
    return;
  }

  //(DTC) DMA terminal counter
  case 0x2238: { mmio.dtc = (mmio.dtc & 0xff00) | (data << 0); return; }
  case 0x2239: { mmio.dtc = (mmio.dtc & 0x00ff) | (data << 8); return; }

  //(BBF) BW-RAM bitmap format
  case 0x223f: { mmio.bbf = (data & 0x80); return; }

  //(BRF) bitmap register files
  case 0x2240: { mmio.brf[ 0] = data; return; }
  case 0x2241: { mmio.brf[ 1] = data; return; }
  case 0x2242: { mmio.brf[ 2] = data; return; }
  case 0x2243: { mmio.brf[ 3] = data; return; }
  case 0x2244: { mmio.brf[ 4] = data; return; }
  case 0x2245: { mmio.brf[ 5] = data; return; }
  case 0x2246: { mmio.brf[ 6] = data; return; }
  case 0x2247: { mmio.brf[ 7] = data;
    if(mmio.dmaen) {
      if(mmio.cden == 1 && mmio.cdsel == 0) {
        dmaCC2();
      }
    }
    return;
  }
  case 0x2248: { mmio.brf[ 8] = data; return; }
  case 0x2249: { mmio.brf[ 9] = data; return; }
  case 0x224a: { mmio.brf[10] = data; return; }
  case 0x224b: { mmio.brf[11] = data; return; }
  case 0x224c: { mmio.brf[12] = data; return; }
  case 0x224d: { mmio.brf[13] = data; return; }
  case 0x224e: { mmio.brf[14] = data; return; }
  case 0x224f: { mmio.brf[15] = data;
    if(mmio.dmaen) {
      if(mmio.cden == 1 && mmio.cdsel == 0) {
        dmaCC2();
      }
    }
    return;
  }

  //(MCNT) arithmetic control
  case 0x2250: {
    mmio.acm = (data & 0x02);
    mmio.md  = (data & 0x01);

    if(mmio.acm) mmio.mr = 0;
    return;
  }

  //(MAL) multiplicand / dividend low
  case 0x2251: {
    mmio.ma = (mmio.ma & 0xff00) | data;
    return;
  }

  //(MAH) multiplicand / dividend high
  case 0x2252: {
    mmio.ma = (data << 8) | (mmio.ma & 0x00ff);
    return;
  }

  //(MBL) multiplier / divisor low
  case 0x2253: {
    mmio.mb = (mmio.mb & 0xff00) | data;
    return;
  }

  //(MBH) multiplier / divisor high
  //multiplication / cumulative sum only resets MB
  //division resets both MA and MB
  case 0x2254: {
    mmio.mb = (data << 8) | (mmio.mb & 0x00ff);

    if(mmio.acm == 0) {
      if(mmio.md == 0) {
        //signed multiplication
        mmio.mr = (int16)mmio.ma * (int16)mmio.mb;
        mmio.mb = 0;
      } else {
        //unsigned division
        if(mmio.mb == 0) {
          mmio.mr = 0;
        } else {
          int16  quotient  = (int16)mmio.ma / (uint16)mmio.mb;
          uint16 remainder = (int16)mmio.ma % (uint16)mmio.mb;
          mmio.mr = (remainder << 16) | quotient;
        }
        mmio.ma = 0;
        mmio.mb = 0;
      }
    } else {
      //sigma (accumulative multiplication)
      mmio.mr += (int16)mmio.ma * (int16)mmio.mb;
      mmio.overflow = (mmio.mr >= (1ULL << 40));
      mmio.mr &= (1ULL << 40) - 1;
      mmio.mb = 0;
    }
    return;
  }

  //(VBD) variable-length bit processing
  case 0x2258: {
    mmio.hl = (data & 0x80);
    mmio.vb = (data & 0x0f);
    if(mmio.vb == 0) mmio.vb = 16;

    if(mmio.hl == 0) {
      //fixed mode
      mmio.vbit += mmio.vb;
      mmio.va += (mmio.vbit >> 3);
      mmio.vbit &= 7;
    }
    return;
  }

  //(VDA) variable-length bit game pak ROM start address
  case 0x2259: { mmio.va = (mmio.va & 0xffff00) | (data <<  0); return; }
  case 0x225a: { mmio.va = (mmio.va & 0xff00ff) | (data <<  8); return; }
  case 0x225b: { mmio.va = (mmio.va & 0x00ffff) | (data << 16); mmio.vbit = 0; return; }

  }
}
