/* class A2Computer (category CPU) Routines and tables for emulating the 65c02 microprocessor, and the low-level I/O behavior of the Apple II. */ #import "LibAppleII-Priv.h" #import "CPU-Macros.h" //mport "CPU-Journal.h" @implementation A2Computer (CPU) //--------------------------------------------------------------------------- #if !JOURNALING #define JOURNAL_OP #define JOURNAL_EA #endif enum { DFLAG(0, D) DFLAG(1, C) DFLAG(2, V) DFLAG(3, ZF) // when ZF = 1, forces Z = 1 DFLAG(4, LSB) kfCD = kfC | kfD, kfDV = kfD | kfV, kfCDV = kfC | kfDV, kfN = 0x80 << ksLSB, kmZ8 = 0xFF << ksLSB, kmPHP = LENGTH(A2T.tPHP) - 1, // kCyclesPerStep = 17030, // CPU cycles in one time step (262 * 65) }; static uint8_t gOldTimer, // residual cycles from last step gSpkrState; // speaker state: 0 or 0xFF static uint32_t gSpkrOut[kA2SamplesPerStep + kTapRatio - 1]; //--------------------------------------------------------------------------- static void FillMemoryMapRow(int8_t map[0x81], uint32_t f, BOOL wmap) {/* Utility function used by +_InitCPU (below) to initialize one row of a memory mapping table -- either 'A2T.rmaps' or 'A2T.wmaps'. */ #define ORAM(BANK) \ offsetof(A2Memory, RAM[page>>5][BANK][page<<8 & 0x1F00]) #define OROM(BANK) \ offsetof(A2Memory, ROM[BANK][(page-0xC0)*256L]) #define PUT_PAGES(PGLO, PGHI, OFFSET) \ for (int page = PGLO; page <= PGHI; page += 2) \ map[page/2] = ((OFFSET) - ozp - 256L*page) / kChunkSize enum { kChunkSize = 1L << 11, // = 0x800 OWOM = offsetof(A2Memory, WOM), }; int ramrw = f>>(wmap? ksRAMWRT : ksRAMRD) & 1, altzp = f>>ksALTZP & 1, cxrom = f>>ksCXROM & 1, hotSlot = f>>ksHotSlot & 7; long ozp = offsetof(A2Memory, RAM[0][altzp][0]); map[0] = 0; map[0x80] = -(k64KB / kChunkSize); PUT_PAGES(0x02, 0xBF, ORAM(ramrw)); if (f & kf80STOREm) { int page2 = f>>ksPAGE2m & 1; PUT_PAGES(0x04, 0x07, ORAM(page2)); if (f & kfHIRESm) PUT_PAGES(0x20, 0x3F, ORAM(page2)); } if (wmap) // then setting write-map { PUT_PAGES(0xC0, 0xFF, OWOM); } else if (hotSlot == 0) // then setting read-map on IIc { PUT_PAGES(0xC0, 0xFF, OROM(cxrom)); } else // setting read-map on IIe or earlier { int c3rom = f>>ksC3ROM & 1; BOOL c8_internal; PUT_PAGES(0xD0, 0xFF, OROM(0)); PUT_PAGES(0xC0, 0xC7, OROM(!cxrom)); PUT_PAGES(0xC3, 0xC3, OROM(!cxrom & c3rom)); if (cxrom) // CXROM on, C3ROM irrelevant c8_internal = YES; else if (c3rom) // CXROM off, C3ROM on c8_internal = NO; else // CXROM and C3ROM both off c8_internal = YES; //!! (hotSlot == 3); if (c8_internal) PUT_PAGES(0xC8, 0xCF, OROM(0)); else PUT_PAGES(0xC8, 0xCF, OROM(1) + 0x800*(hotSlot-1)); } if (f & (wmap? kfLCWRThi : kfLCRD)) // then $D0-FF sees LC RAM { int shiftDx = (f & kfLCBANK2)? 0x1000 : 0; PUT_PAGES(0xD0, 0xDF, ORAM(altzp) - shiftDx); PUT_PAGES(0xE0, 0xFF, ORAM(altzp)); } #undef ORAM #undef OROM #undef PUT_PAGES } //--------------------------------------------------------------------------- static int16_t FixAP(uint16_t oap) { unsigned p = kfLSB; if (oap & 0x80) p |= 0x80; if (oap & 0x40) p |= kfV; if (oap & 0x08) p |= kfD; if (oap & 0x02) p |= kfZF; if (oap & 0x01) p |= kfC; return (p << 8) | (oap >> 8); } //--------------------------------------------------------------------------- static BOOL InitADC_SBC(void) { // Fill in the ADC and SBC lookup tables in global structure 'A2T'. static uint8_t tbl [2/*D*/][2/*C*/][256/*B*/][2/*ADC,SBC*/][2/*A,P*/][256/*A*/]; static uint16_t tadc[0xD00], tsbc[0xD00]; BOOL adc_good = YES, sbc_good = YES; FILE* fin; fin = fopen([[[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"../ADSBC.dat"] fileSystemRepresentation], "rb"); if (fin == NULL) return NO; fread(tbl, 1, sizeof(tbl), fin); fclose(fin); memset(tadc, 0xFF, sizeof(tadc)); memset(tsbc, 0xFF, sizeof(tsbc)); for (int i = 256; --i >= 0;) tadc[i] = tsbc[i] = 128 + 2 * ( (i & 0x80)*15/4 + (i & 0x70)*2 + (i & 0xF) ); for (int d = 2; --d >= 0;) for (int c = 2; --c >= 0;) for (int b = 256; --b >= 0;) for (int a = 256; --a >= 0;) { unsigned adc, sbc, i; adc = tbl[d][c][b][0][0][a] << 8 | tbl[d][c][b][0][1][a] & 0xCB; sbc = tbl[d][c][b][1][0][a] << 8 | tbl[d][c][b][1][1][a] & 0xCB; i = tadc[a] + tadc[b] + 2*c + d; if (tadc[i] == 0xFFFF) tadc[i] = adc; else if (tadc[i] != adc) adc_good = NO; i = tsbc[a] + tsbc[b^0xFF] + 2*c + d; if (tsbc[i] == 0xFFFF) tsbc[i] = sbc; else if (tsbc[i] != sbc) sbc_good = NO; } NSLog(@"adc_good? %c sbc_good? %c", "ny"[adc_good], "ny"[sbc_good]); if (adc_good and sbc_good) { memcpy(A2T.tADC, tadc, 2*256); memcpy(A2T.tSBC, tsbc, 2*256); for (int i = 256; i < LENGTH(A2T.tADC); ++i) { A2T.tADC[i] = FixAP(tadc[i]); A2T.tSBC[i] = FixAP(tsbc[i]); } memcpy(A2T.tADCo, A2T.tADC, sizeof(A2T.tADC)); memcpy(A2T.tSBCo, A2T.tSBC, sizeof(A2T.tSBC)); if (NO) for (int i = 256; i < LENGTH(A2T.tADCo); i += 2) { A2T.tADCo[i+1] = A2T.tADCo[i ] & 0xFF00 | A2T.tADCo[i+1] & 0x00FF; A2T.tSBCo[i+1] = A2T.tSBCo[i ] & 0xFF00 | A2T.tSBCo[i+1] & 0x00FF; } } return YES; } //--------------------------------------------------------------------------- + (void)_InitCPU {/* Initializes various lookup tables used in method '-RunForOneStep', defined below. Called only once, from '+initialize'. */ #if JOURNALING atexit(LogJournal); #endif #if 0 for (int p = LENGTH(A2T.tPHP); --p >= 0;) { uint8_t php = 0x30; // the true P reg: NV1BDIZC if (p & kfC) php |= 0x01; if (Z_SET) php |= 0x02; if (p & kfD) php |= 0x08; if (p & kfV) php |= 0x40; if (p & kfN) php |= 0x80; A2T.tPHP[p] = php; } for (int i = LENGTH(A2T.tPLP); --i >= 0;) { unsigned plp = kfLSB; // assume Z = 0 (datum non-zero) if (i & 0x01) plp |= kfC; if (i & 0x02) plp |= kfZF; if (i & 0x08) plp |= kfD; if (i & 0x40) plp |= kfV; if (i & 0x80) plp |= kfN; A2T.tPLP[i] = plp; } for (long i = LENGTH(A2T.tROR), j; --i >= 0;) { j = i>>3 | (i&kfC)<<7; j = (j<<9 | j) << 3; A2T.tROR[i] = j&0xFF0 | i&kfDV | j>>2&kfC; j >>= 7; A2T.tROL[i] = j&0xFF0 | i&kfDV | j>>2&kfC; } InitADC_SBC(); for (long i = kmRMap+1; --i >= 0;) FillMemoryMapRow(A2T.rmaps[i], i<= 0;) FillMemoryMapRow(A2T.wmaps[i], i<> 7 & d) { /*-------------------------------------------------------- BRK, JSR, JMP, RTI, and RTS. */ #define PHPC2 ea = pc+2; PUSH(ea>>8); PUSH(ea) #define PLPC pc = PULL; pc |= PULL<<8 case 0x00: if (t >= 0) continue; // next scan line /* BRK */ PHPC2; PHP; mI = 4; ea = 0xFFFE; ++t; goto IndirJMP; case 0x7C: EAAX; goto IndirJMP; case 0x6C: EAA; // and fall into IndirJMP IndirJMP: pc = READ(ea) - 1; ++ea; t += 6; pc += READ(ea) << 8; goto NewOpcode; case 0x20: PHPC2; t += 3; // and fall into JMP-abs case 0x4C: EAA; pc = ea - 1; t += 3; goto NewOpcode; case 0x60: PLPC; t += 6; goto NewOpcode; case 0x40: PLP; PLPC; --pc; t += 6; goto NewOpcode; #undef PHPC2 #undef PLPC /*-------------------------------------------------------- No-op instructions. */ case 0x54: case 0xD4: case 0xF4: ++t; case 0x44: ++t; case 0x02: case 0x22: case 0x42: case 0x62: case 0x82: case 0xC2: case 0xE2: ++pc; case 0xEA: t += 2; goto NewOpcode; case 0x5C: t += 4; case 0xDC: case 0xFC: t += 4; pc += 2; goto NewOpcode; default: CASE16(0x03,16): CASE16(0x07,16): CASE16(0x0B,16): CASE16(0x0F,16): ++t; goto NewOpcode; /*-------------------------------------------------------- Relative branches. */ #define BRANCHES(OP, COND) \ case OP: if (COND) goto DoBRA; \ ++pc; t += 2; goto NewOpcode; \ case OP ^ 0x20: if (not (COND)) goto DoBRA; \ ++pc; t += 2; goto NewOpcode; BRANCHES(0xB0, p & kfC) // BCS & BCC BRANCHES(0x70, p & kfV) // BVS & BVC BRANCHES(0x30, p & kfN) // BMI & BPL BRANCHES(0xF0, Z_SET ) // BEQ & BNE DoBRA: case 0x80: pc += (signed char) READ_PC; t += 3; goto NewOpcode; #undef BRANCHES /*-------------------------------------------------------- Implied- and Accumulator-mode instructions: */ #define IMPI(OP, DT, STMT) \ case OP: t += DT; STMT; goto NewOpcode; #define LD_(REG,VAL) REG = d = (VAL); SET_NZ IMPI(0xB8, 2, p &= ~kfV) IMPI(0x18, 2, p &= ~kfC) IMPI(0x38, 2, p |= kfC) IMPI(0xD8, 2, p &= ~kfD) IMPI(0xF8, 2, p |= kfD) IMPI(0x58, 2, mI = 0) IMPI(0x78, 2, mI = 4) IMPI(0x1A, 2, LD_(mA, mA+1)) IMPI(0x3A, 2, LD_(mA, mA-1)) IMPI(0xE8, 2, LD_(mX, mX+1)) IMPI(0xCA, 2, LD_(mX, mX-1)) IMPI(0xC8, 2, LD_(mY, mY+1)) IMPI(0x88, 2, LD_(mY, mY-1)) IMPI(0x8A, 2, LD_(mA, mX)) IMPI(0xAA, 2, LD_(mX, mA)) IMPI(0x98, 2, LD_(mA, mY)) IMPI(0xA8, 2, LD_(mY, mA)) IMPI(0xBA, 2, LD_(mX, mS)) IMPI(0x9A, 2, mS = mX) IMPI(0x08, 3, PHP) IMPI(0x28, 4, PLP) IMPI(0x48, 3, PUSH(mA)) IMPI(0x68, 4, LD_(mA, PULL)) IMPI(0xDA, 3, PUSH(mX)) IMPI(0xFA, 4, LD_(mX, PULL)) IMPI(0x5A, 3, PUSH(mY)) IMPI(0x7A, 4, LD_(mY, PULL)) IMPI(0x0A, 2, ASL(mA)) IMPI(0x4A, 2, LSR(mA)) IMPI(0x2A, 2, ROL(mA)) IMPI(0x6A, 2, ROR(mA)) #undef IMPI #undef LD_ /*-------------------------------------------------------- Read and modify instructions of the Immediate and Zero-Page addressing modes. */ #define RIMM(OP, STMT) case 0x##OP: \ d = READ_PC; t += 2; STMT; goto NewOpcode; #define RZP(OP, STMT) case 0x##OP: \ EAZ; t += 3; d = zp[ea]; STMT; goto NewOpcode; #define RZPX(OP, STMT) case 0x##OP: \ EAZX; t += 4; d = zp[ea]; STMT; goto NewOpcode; #define MZP(OP, STMT) case 0x##OP: \ EAZ; t+=5; d=zp[ea]; STMT; zp[ea]=d; goto NewOpcode; #define MZPX(OP, STMT) case 0x##OP: \ EAZX; t+=6; d=zp[ea]; STMT; zp[ea]=d; goto NewOpcode; RIMM(69, ADC) RZP(65, ADC) RZPX(75, ADC) RIMM(29, AND) RZP(25, AND) RZPX(35, AND) MZP(06, ASL(d)) MZPX(16, ASL(d)) RIMM(89, BITZ) RZP(24, BIT) RZPX(34, BIT) RIMM(C9, CMP) RZP(C5, CMP) RZPX(D5, CMP) RIMM(E0, CPX) RZP(E4, CPX) RIMM(C0, CPY) RZP(C4, CPY) MZP(C6, DEC) MZPX(D6, DEC) RIMM(49, EOR) RZP(45, EOR) RZPX(55, EOR) MZP(E6, INC) MZPX(F6, INC) RIMM(A9, LDA) RZP(A5, LDA) RZPX(B5, LDA) RIMM(A2, LDX) RZP(A6, LDX) RIMM(A0, LDY) RZP(A4, LDY) RZPX(B4, LDY) MZP(46, LSR(d)) MZPX(56, LSR(d)) RIMM(09, ORA) RZP(05, ORA) RZPX(15, ORA) MZP(26, ROL(d)) MZPX(36, ROL(d)) MZP(66, ROR(d)) MZPX(76, ROR(d)) RIMM(E9, SBC) RZP(E5, SBC) RZPX(F5, SBC) MZP(14, TRB) MZP(04, TSB) case 0xB6: // LDX zp,Y EAZY; t += 4; d = zp[ea]; LDX; goto NewOpcode; #undef RIMM #undef RZP #undef RZPX #undef MZP #undef MZPX /*-------------------------------------------------------- STA, STX, STY, and STZ */ case 0x85: EAZ ; t += 3; zp[ea] = mA; goto NewOpcode; case 0x86: EAZ ; t += 3; zp[ea] = mX; goto NewOpcode; case 0x84: EAZ ; t += 3; zp[ea] = mY; goto NewOpcode; case 0x64: EAZ ; t += 3; zp[ea] = 0; goto NewOpcode; case 0x95: EAZX; t += 4; zp[ea] = mA; goto NewOpcode; case 0x96: EAZY; t += 4; zp[ea] = mX; goto NewOpcode; case 0x94: EAZX; t += 4; zp[ea] = mY; goto NewOpcode; case 0x74: EAZX; t += 4; zp[ea] = 0; goto NewOpcode; case 0x8D: EAA ; t += 4; d = mA; goto Write; case 0x8E: EAA ; t += 4; d = mX; goto Write; case 0x8C: EAA ; t += 4; d = mY; goto Write; case 0x9C: EAA ; t += 4; d = 0; goto Write; case 0x9D: EAAX; t += 5; d = mA; goto Write; case 0x99: EAAY; t += 5; d = mA; goto Write; case 0x92: EAI ; t += 5; d = mA; goto Write; case 0x81: EAIX; t += 6; d = mA; goto Write; case 0x91: EAIY; t += 6; d = mA; goto Write; case 0x9E: EAAX; t += 5; d = 0; goto Write; /*-------------------------------------------------------- "Prologs" for read and modify instructions that work on general addresses. The effective address is computed, and the clock is bumped. Execution then proceeds to the Read and Epilog phases below. */ case 0x6D: case 0x2D: case 0x0E: case 0x2C: case 0xCD: case 0xEC: case 0xCC: case 0xCE: case 0x4D: case 0xEE: case 0xAD: case 0xAE: case 0xAC: case 0x4E: case 0x0D: case 0x2E: case 0x6E: case 0xED: case 0x1C: case 0x0C: EAA; t += 4; break; case 0x7D: case 0x3D: case 0x1E: case 0x3C: case 0xDD: case 0xDE: case 0x5D: case 0xFE: case 0xBD: case 0xBC: case 0x5E: case 0x1D: case 0x3E: case 0x7E: case 0xFD: EAAX; t += 4; break; case 0x79: case 0x39: case 0xD9: case 0x59: case 0xB9: case 0xBE: case 0x19: case 0xF9: EAAY; t += 4; break; case 0x72: case 0x32: case 0xD2: case 0x52: case 0xB2: case 0x12: case 0xF2: EAI; t += 5; break; case 0x61: case 0x21: case 0xC1: case 0x41: case 0xA1: case 0x01: case 0xE1: EAIX; t += 6; break; case 0x71: case 0x31: case 0xD1: case 0x51: case 0xB1: case 0x11: case 0xF1: EAIY; t += 5; break; } // end of switch (t>>16 & d) //---------------------------------------------------------------- Read: curOp = d; JOURNAL_EA #define READ_PHASE 1 #include "CPU-RW.h" #undef READ_PHASE d = READ(ea); // default read behavior, when 'ea' not in I/O area // fall into Epilog... //---------------------------------------------------------------- Epilog: switch (curOp) { #define OP_ACC(OP, STMT) case OP+0x12: \ case OP+0x01: case OP+0x05: case OP+0x09: case OP+0x0D: \ case OP+0x11: case OP+0x15: case OP+0x19: case OP+0x1D: \ STMT; goto NewOpcode; OP_ACC(0x00, ORA) OP_ACC(0x20, AND) OP_ACC(0x40, EOR) OP_ACC(0x60, ADC) OP_ACC(0xA0, LDA) OP_ACC(0xC0, CMP) OP_ACC(0xE0, SBC) case 0x2C: case 0x3C: BIT; goto NewOpcode; case 0xEC: CPX; goto NewOpcode; case 0xCC: CPY; goto NewOpcode; case 0xAE: case 0xBE: LDX; goto NewOpcode; case 0xAC: case 0xBC: LDY; goto NewOpcode; case 0x0E: case 0x1E: ASL(d); break; case 0x4E: case 0x5E: LSR(d); break; case 0x2E: case 0x3E: ROL(d); break; case 0x6E: case 0x7E: ROR(d); break; case 0xCE: case 0xDE: DEC; break; case 0xEE: case 0xFE: INC; break; case 0x0C: TSB; break; case 0x1C: TRB; break; case 0x4C: case 0x5C: case 0x6C: case 0x7C: case 0x8C: case 0x9C: case 0xDC: case 0xFC: case 0x8E: case 0x9E: CASE16(0x00, 0x10): CASE8 (0x02, 0x20): CASE16(0x03, 0x10): CASE16(0x04, 0x10): CASE16(0x06, 0x10): CASE16(0x07, 0x10): CASE16(0x08, 0x10): CASE16(0x0A, 0x10): CASE16(0x0B, 0x10): CASE16(0x0F, 0x10): default: OP_ACC(0x80, ;) // goto NewOpcode #undef OP_ACC } // Modify instructions reach here. We need to burn 2 more // cycles before falling into Write. t += 2; //---------------------------------------------------------------- Write: #include "CPU-RW.h" JOURNAL_EA WRITE(ea) = d; // default write, when 'ea' not in I/O area goto NewOpcode; } // end of for (scanLine ...) mPC = pc; mP = p; gOldTimer = t; p = gSpkrState; for (int i = 0; i < kA2SamplesPerStep; ++i) { t = gSpkrOut[i]; p ^= t>>8; audioOut[i] = p ^ t>>24; p ^= t; } gSpkrState = p; for (int i = kTapRatio-1; --i >= 0;) gSpkrOut[i] = (gSpkrOut + kA2SamplesPerStep)[i]; t = A2T.audio.flat; for (int i = kA2SamplesPerStep; --i >= 0;) (gSpkrOut + kTapRatio - 1)[i] = t; } //--------------------------------------------------------------------------- @end