auto uPD96050::exec() -> void {
  n24 opcode = programROM[regs.pc++];
  switch(opcode >> 22) {
  case 0: execOP(opcode); break;
  case 1: execRT(opcode); break;
  case 2: execJP(opcode); break;
  case 3: execLD(opcode); break;
  }

  i32 result = (i32)regs.k * regs.l;  //sign + 30-bit result
  regs.m = result >> 15;  //store sign + top 15-bits
  regs.n = result <<  1;  //store low 15-bits + zero
}

auto uPD96050::execOP(n24 opcode) -> void {
  n2 pselect = opcode >> 20;  //P select
  n4 alu     = opcode >> 16;  //ALU operation mode
  n1 asl     = opcode >> 15;  //accumulator select
  n2 dpl     = opcode >> 13;  //DP low modify
  n4 dphm    = opcode >>  9;  //DP high XOR modify
  n1 rpdcr   = opcode >>  8;  //RP decrement
  n4 src     = opcode >>  4;  //move source
  n4 dst     = opcode >>  0;  //move destination

  n16 idb;
  switch(src) {
  case  0: idb = regs.trb; break;
  case  1: idb = regs.a; break;
  case  2: idb = regs.b; break;
  case  3: idb = regs.tr; break;
  case  4: idb = regs.dp; break;
  case  5: idb = regs.rp; break;
  case  6: idb = dataROM[regs.rp]; break;
  case  7: idb = 0x8000 - flags.a.s1; break;  //ASL ignored; always SA1
  case  8: idb = regs.dr; regs.sr.rqm = 1; break;
  case  9: idb = regs.dr; break;
  case 10: idb = regs.sr; break;
  case 11: idb = regs.si; break;  //MSB
  case 12: idb = regs.si; break;  //LSB
  case 13: idb = regs.k; break;
  case 14: idb = regs.l; break;
  case 15: idb = dataRAM[regs.dp]; break;
  }

  if(alu) {
    n16 p, q, r;
    Flag flag;
    boolean c;

    switch(pselect) {
    case 0: p = dataRAM[regs.dp]; break;
    case 1: p = idb; break;
    case 2: p = regs.m; break;
    case 3: p = regs.n; break;
    }

    switch(asl) {
    case 0: q = regs.a; flag = flags.a; c = flags.b.c; break;
    case 1: q = regs.b; flag = flags.b; c = flags.a.c; break;
    }

    switch(alu) {
    case  1: r = q | p; break;                //OR
    case  2: r = q & p; break;                //AND
    case  3: r = q ^ p; break;                //XOR
    case  4: r = q - p; break;                //SUB
    case  5: r = q + p; break;                //ADD
    case  6: r = q - p - c; break;            //SBB
    case  7: r = q + p + c; break;            //ADC
    case  8: r = q - 1; p = 1; break;         //DEC
    case  9: r = q + 1; p = 1; break;         //INC
    case 10: r = ~q; break;                   //CMP
    case 11: r = q >> 1 | q & 0x8000; break;  //SHR1 (ASR)
    case 12: r = q << 1 | c; break;           //SHL1 (ROL)
    case 13: r = q << 2 | 3; break;           //SHL2
    case 14: r = q << 4 | 15; break;          //SHL4
    case 15: r = q << 8 | q >> 8; break;      //XCHG
    }

    flag.z = r == 0;
    flag.s0 = r & 0x8000;
    if(!flag.ov1) flag.s1 = flag.s0;

    switch(alu) {

    case  1:    //OR
    case  2:    //AND
    case  3:    //XOR
    case 10:    //CMP
    case 13:    //SHL2
    case 14:    //SHL4
    case 15: {  //XCHG
      flag.ov0 = 0;
      flag.ov1 = 0;
      flag.c = 0;
      break;
    }

    case  4:    //SUB
    case  5:    //ADD
    case  6:    //SBB
    case  7:    //ADC
    case  8:    //DEC
    case  9: {  //INC
      n16 carries = q ^ p ^ r;
      n16 overflow = (q ^ r) & (p ^ (alu & 1 ? r : q));
      flag.ov0 = overflow & 0x8000;
      flag.ov1 = flag.ov0 & flag.ov1 ? flag.s0 == flag.s1 : flag.ov0 | flag.ov1;
      flag.c = (carries ^ overflow) & 0x8000;
      break;
    }

    case 11: {  //SHR1 (ASR)
      flag.ov0 = 0;
      flag.ov1 = 0;
      flag.c = q & 1;
      break;
    }

    case 12: {  //SHL1 (ROL)
      flag.ov0 = 0;
      flag.ov1 = 0;
      flag.c = q >> 15;
      break;
    }

    }

    switch(asl) {
    case 0: regs.a = r; flags.a = flag; break;
    case 1: regs.b = r; flags.b = flag; break;
    }
  }

  execLD(idb << 6 | dst);

  if(dst != 4) {  //if LD does not write to DP
    switch(dpl) {
    case 1: regs.dp = (regs.dp & 0xf0) + (regs.dp + 1 & 0x0f); break;  //DPINC
    case 2: regs.dp = (regs.dp & 0xf0) + (regs.dp - 1 & 0x0f); break;  //DPDEC
    case 3: regs.dp = (regs.dp & 0xf0); break;  //DPCLR
    }
    regs.dp ^= dphm << 4;
  }

  if(dst != 5) {  //if LD does not write to RP
    if(rpdcr) regs.rp--;
  }
}

auto uPD96050::execRT(n24 opcode) -> void {
  execOP(opcode);
  regs.pc = regs.stack[--regs.sp];
}

auto uPD96050::execJP(n24 opcode) -> void {
  n9 brch = opcode >> 13;  //branch
  n11 na  = opcode >>  2;  //next address
  n2 bank = opcode >>  0;  //bank address

  n14 jp = regs.pc & 0x2000 | bank << 11 | na << 0;

  switch(brch) {
  case 0x000: regs.pc = regs.so; return;  //JMPSO

  case 0x080: if(flags.a.c == 0) regs.pc = jp; return;  //JNCA
  case 0x082: if(flags.a.c == 1) regs.pc = jp; return;  //JCA
  case 0x084: if(flags.b.c == 0) regs.pc = jp; return;  //JNCB
  case 0x086: if(flags.b.c == 1) regs.pc = jp; return;  //JCB

  case 0x088: if(flags.a.z == 0) regs.pc = jp; return;  //JNZA
  case 0x08a: if(flags.a.z == 1) regs.pc = jp; return;  //JZA
  case 0x08c: if(flags.b.z == 0) regs.pc = jp; return;  //JNZB
  case 0x08e: if(flags.b.z == 1) regs.pc = jp; return;  //JZB

  case 0x090: if(flags.a.ov0 == 0) regs.pc = jp; return;  //JNOVA0
  case 0x092: if(flags.a.ov0 == 1) regs.pc = jp; return;  //JOVA0
  case 0x094: if(flags.b.ov0 == 0) regs.pc = jp; return;  //JNOVB0
  case 0x096: if(flags.b.ov0 == 1) regs.pc = jp; return;  //JOVB0

  case 0x098: if(flags.a.ov1 == 0) regs.pc = jp; return;  //JNOVA1
  case 0x09a: if(flags.a.ov1 == 1) regs.pc = jp; return;  //JOVA1
  case 0x09c: if(flags.b.ov1 == 0) regs.pc = jp; return;  //JNOVB1
  case 0x09e: if(flags.b.ov1 == 1) regs.pc = jp; return;  //JOVB1

  case 0x0a0: if(flags.a.s0 == 0) regs.pc = jp; return;  //JNSA0
  case 0x0a2: if(flags.a.s0 == 1) regs.pc = jp; return;  //JSA0
  case 0x0a4: if(flags.b.s0 == 0) regs.pc = jp; return;  //JNSB0
  case 0x0a6: if(flags.b.s0 == 1) regs.pc = jp; return;  //JSB0

  case 0x0a8: if(flags.a.s1 == 0) regs.pc = jp; return;  //JNSA1
  case 0x0aa: if(flags.a.s1 == 1) regs.pc = jp; return;  //JSA1
  case 0x0ac: if(flags.b.s1 == 0) regs.pc = jp; return;  //JNSB1
  case 0x0ae: if(flags.b.s1 == 1) regs.pc = jp; return;  //JSB1

  case 0x0b0: if((regs.dp & 0x0f) == 0x00) regs.pc = jp; return;  //JDPL0
  case 0x0b1: if((regs.dp & 0x0f) != 0x00) regs.pc = jp; return;  //JDPLN0
  case 0x0b2: if((regs.dp & 0x0f) == 0x0f) regs.pc = jp; return;  //JDPLF
  case 0x0b3: if((regs.dp & 0x0f) != 0x0f) regs.pc = jp; return;  //JDPLNF

  //serial input/output acknowledge not emulated
  case 0x0b4: if(regs.sr.siack == 0) regs.pc = jp; return;  //JNSIAK
  case 0x0b6: if(regs.sr.siack == 1) regs.pc = jp; return;  //JSIAK
  case 0x0b8: if(regs.sr.soack == 0) regs.pc = jp; return;  //JNSOAK
  case 0x0ba: if(regs.sr.soack == 1) regs.pc = jp; return;  //JSOAK

  case 0x0bc: if(regs.sr.rqm == 0) regs.pc = jp; return;  //JNRQM
  case 0x0be: if(regs.sr.rqm == 1) regs.pc = jp; return;  //JRQM

  case 0x100: regs.pc = jp & ~0x2000; return;  //LJMP
  case 0x101: regs.pc = jp |  0x2000; return;  //HJMP

  case 0x140: regs.stack[regs.sp++] = regs.pc; regs.pc = jp & ~0x2000; return;  //LCALL
  case 0x141: regs.stack[regs.sp++] = regs.pc; regs.pc = jp |  0x2000; return;  //HCALL
  }
}

auto uPD96050::execLD(n24 opcode) -> void {
  n16 id = opcode >> 6;  //immediate data
  n4 dst = opcode >> 0;  //destination

  switch(dst) {
  case  0: break;
  case  1: regs.a = id; break;
  case  2: regs.b = id; break;
  case  3: regs.tr = id; break;
  case  4: regs.dp = id; break;
  case  5: regs.rp = id; break;
  case  6: regs.dr = id; regs.sr.rqm = 1; break;
  case  7: regs.sr = regs.sr & 0x907c | id & ~0x907c; break;
  case  8: regs.so = id; break;  //LSB
  case  9: regs.so = id; break;  //MSB
  case 10: regs.k = id; break;
  case 11: regs.k = id; regs.l = dataROM[regs.rp]; break;
  case 12: regs.l = id; regs.k = dataRAM[regs.dp | 0x40]; break;
  case 13: regs.l = id; break;
  case 14: regs.trb = id; break;
  case 15: dataRAM[regs.dp] = id; break;
  }
}
