// Reinette, emulates the Apple 1 computer // Copyright 2018 Arthur Ferreira // Last modified 5th of March 2019 // initially developped on GNU/Linux // compiles with gcc 6.3.0-18 #include #include // for usleep() #include "woz.h" #define RAMSIZE 0xC000 // 48KB uint8_t ram[RAMSIZE]; #define CARRY 0x01 #define ZERO 0x02 #define INTERRUPT 0x04 #define DECIMAL 0x08 #define BREAK 0x10 #define UNDEFINED 0x20 #define OVERFLOW 0x40 #define SIGN 0x80 struct Operand{ bool setAcc; uint16_t value, address; }ope; struct Register{ uint8_t A,X,Y,SR,SP; uint16_t PC; }reg; uint8_t key, keyRdy; // MEMORY AND I/O static uint8_t readMem(uint16_t address){ static uint8_t queries=0; // slow down emulation when waiting for a keypress if (address < RAMSIZE) return (ram[address]); if (address >= ROMSTART) return (rom[address - ROMSTART]); if (address == 0xD011 ){ // is there a keypressed ? if (keyRdy) return(keyRdy); // yes if (! ++queries) usleep(100); // else sleep 100ms every 256 iterations return(0); // and return 0 (no keypressed) } if ((address == 0xD010) && keyRdy){ // is there a key waiting us ? keyRdy = 0; // yes, reset the keyRdy flag return(key | 0x80); // and return the key } return(0); // catch all } static void writeMem(uint16_t address, uint8_t value){ if (address < RAMSIZE) ram[address] = value; else if (address == 0xD012){ // DSP, display one char value &= 0x7F; if (value == 0x7F) value = '@'; // make DEL printable if (value == 0x0D) value = 0x0A; // CR (\r) to LF (\n) if (value == 0x5F) // erase the previous character printw("%c%c%c",0x08,0x20,0x08); // BackSpace, Space , BackSpace else printw("%c",value); } } // RESET static void reset(){ reg.PC = readMem(0xFFFC) | (readMem(0xFFFD) << 8); reg.SP = 0xFF; reg.SR |= UNDEFINED; key = 0; keyRdy = 0; ope.setAcc = false; ope.value = 0; ope.address = 0; } // STACK, SIGN AND ZERO FLAGS ROUTINES static void push(uint8_t value){ writeMem(0x100 + reg.SP--, value); } uint8_t pull(){ return(readMem(0x100 + ++reg.SP)); } static void setSZ(uint8_t value){ // updates both the Sign & Zero FLAGS if (value & 0x00FF) reg.SR &= ~ZERO; else reg.SR |= ZERO; if (value & 0x80) reg.SR |= SIGN; else reg.SR &= ~SIGN; } // ADDRESSING MODES static void IMP(){ // Implicit } static void ACC(){ // ACCumulator ope.value = reg.A; ope.setAcc = true; } static void IMM(){ // IMMediate ope.address = reg.PC++; ope.value = readMem(ope.address); } static void ZPG(){ // Zero PaGe ope.address = readMem(reg.PC++); ope.value = readMem(ope.address); } static void ZPX(){ // Zero PaGe,X ope.address = (readMem(reg.PC++) + reg.X) & 0xFF; ope.value = readMem(ope.address); } static void ZPY(){ // Zero PaGe,Y ope.address = (readMem(reg.PC++) + reg.Y) & 0xFF; ope.value = readMem(ope.address); } static void REL(){ // RELative (for branch instructions) ope.address = readMem(reg.PC++); if (ope.address & 0x80) ope.address |= 0xFF00; // branch backward } static void ABS(){ // ABSolute ope.address = readMem(reg.PC) | (readMem(reg.PC + 1) << 8); ope.value = readMem(ope.address); reg.PC += 2; } static void ABX(){ // ABsolute,X ope.address = (readMem(reg.PC) | (readMem(reg.PC + 1) << 8)) + reg.X; ope.value = readMem(ope.address); reg.PC += 2; } static void ABY(){ // ABsolute,Y ope.address = (readMem(reg.PC) | (readMem(reg.PC + 1) << 8)) + reg.Y; ope.value = readMem(ope.address); reg.PC += 2; } static void IND(){ // INDirect - JMP ($ABCD) with page-boundary wraparound bug uint16_t vector1 = readMem(reg.PC) | (readMem(reg.PC + 1) << 8); uint16_t vector2 = (vector1 & 0xFF00) | ((vector1 + 1) & 0x00FF); ope.address = readMem(vector1) | (readMem(vector2) << 8); ope.value = readMem(ope.address); reg.PC += 2; } static void IDX(){ // InDexed indirect X uint16_t vector1 = ((readMem(reg.PC++) + reg.X) & 0xFF); ope.address = readMem(vector1&0x00FF) | (readMem((vector1+1) & 0x00FF) << 8); ope.value = readMem(ope.address); } static void IDY(){ // InDirect Indexed Y uint16_t vector1 = readMem(reg.PC++); uint16_t vector2 = (vector1 & 0xFF00) | ((vector1 + 1) & 0x00FF); ope.address = (readMem(vector1) | (readMem(vector2) << 8)) + reg.Y; ope.value = readMem(ope.address); } // INSTRUCTIONS static void NOP(){ // NO Operation } static void BRK(){ // BReaK push(((++reg.PC) >> 8) & 0xFF); push(reg.PC & 0xFF); push(reg.SR | BREAK); reg.SR |= INTERRUPT; reg.PC = readMem(0xFFFE) | (readMem(0xFFFF) << 8); } static void CLD(){ // CLear Decimal reg.SR &= ~DECIMAL; } static void SED(){ // SEt Decimal reg.SR |= DECIMAL; } static void CLC(){ // CLear Carry reg.SR &= ~CARRY; } static void SEC(){ // SEt Carry reg.SR |= CARRY; } static void CLI(){ // CLear Interrupt reg.SR &= ~INTERRUPT; } static void SEI(){ // SEt Interrupt reg.SR |= INTERRUPT; } static void CLV(){ // CLear oVerflow reg.SR &= ~OVERFLOW; } static void LDA(){ // LoaD Accumulator reg.A = ope.value; setSZ(reg.A); } static void LDX(){ // LoaD X reg.X = ope.value; setSZ(reg.X);} static void LDY(){ // LoaD Y reg.Y = ope.value; setSZ(reg.Y); } static void STA(){ // STore Accumulator writeMem(ope.address, reg.A); } static void STX(){ // STore X writeMem(ope.address, reg.X); } static void STY(){ // STore Y writeMem(ope.address, reg.Y); } static void DEC(){ // DECrement writeMem(ope.address, --ope.value); setSZ(ope.value); } static void DEX(){ // DEcrement X setSZ(--reg.X); } static void DEY(){ // DEcrement Y setSZ(--reg.Y); } static void INC(){ // INCrement writeMem(ope.address, ++ope.value); setSZ(ope.value); } static void INX(){ // INcrement X setSZ(++reg.X); } static void INY(){ // INcrement Y setSZ(++reg.Y); } static void TAX(){ // Transfer Accumulator to X reg.X = reg.A; setSZ(reg.X); } static void TAY(){ // Transfer Accumulator to Y reg.Y = reg.A; setSZ(reg.Y); } static void TXA(){ // Transfer X to Accumulator reg.A = reg.X; setSZ(reg.A); } static void TYA(){ // Transfer Y to Accumulator reg.A = reg.Y; setSZ(reg.A); } static void TSX(){ // Transfer Sp to X reg.X = reg.SP; setSZ(reg.X); } static void TXS(){ // Transfer X to Sp reg.SP = reg.X; } static void BEQ(){ // Branch on EQual (zero set) if (reg.SR & ZERO) reg.PC += ope.address; } static void BNE(){ // Branch on Not Equal (zero clear) if (!(reg.SR & ZERO)) reg.PC += ope.address; } static void BMI(){ // Branch if MInus (ie when negative, when SIGN is set) if (reg.SR & SIGN) reg.PC += ope.address; } static void BPL(){ // Branch if PLus (ie when positive, when SIGN is clear) if (!(reg.SR & SIGN)) reg.PC += ope.address; } static void BVS(){ // Branch on oVerflow Set if (reg.SR & OVERFLOW) reg.PC += ope.address; } static void BVC(){ // Branch on oVerflow Clear if (!(reg.SR & OVERFLOW)) reg.PC += ope.address; } static void BCS(){ // Branch on Carry Set if (reg.SR & CARRY) reg.PC +=ope.address; } static void BCC(){ // Branch on Carry Clear if (!(reg.SR & CARRY)) reg.PC += ope.address; } static void PHA(){ // PusH A to the stack push(reg.A); } static void PLA(){ // PulL stack into A reg.A = pull(); setSZ(reg.A); } static void PHP(){ // PusH Programm (Status) register to the stack push(reg.SR | BREAK); } static void PLP(){ // PulL stack into Programm (SR) register reg.SR = pull() | UNDEFINED; } static void JMP(){ // JuMP reg.PC = ope.address; } static void JSR(){ // Jump Sub-Routine push((--reg.PC >> 8) & 0xFF); push(reg.PC & 0xFF); reg.PC = ope.address; } static void RTS(){ // ReTurn from Sub-routine reg.PC = (pull() | (pull() << 8)) + 1; } static void RTI(){ // ReTurn from Interrupt reg.SR = pull(); reg.PC = pull() | (pull() << 8); } static void CMP(){ // Compare with A setSZ((reg.A - ope.value) & 0xFF); if (reg.A >= (ope.value & 0xFF)) reg.SR |= CARRY; else reg.SR &= ~CARRY; } static void CPX(){ // Compare with X setSZ((reg.X - ope.value) & 0xFF); if (reg.X >= (ope.value & 0xFF)) reg.SR |= CARRY; else reg.SR &= ~CARRY; } static void CPY(){ // Compare with Y setSZ((reg.Y - ope.value) & 0xFF); if (reg.Y >= (ope.value & 0xFF)) reg.SR |= CARRY; else reg.SR &= ~CARRY; } static void AND(){ // AND with A reg.A &= ope.value; setSZ(reg.A); } static void ORA(){ // OR with A reg.A |= ope.value; setSZ(reg.A); } static void EOR(){ // Exclusive Or with A reg.A ^= ope.value; setSZ(reg.A); } static void BIT(){ // BIT with A - http://www.6502.org/tutorials/vflag.html if (!(reg.A & ope.value)) reg.SR |= ZERO; else reg.SR &= ~ZERO; reg.SR = (reg.SR & 0x3F) | (ope.value & 0xC0); // update SIGN & OVERFLOW } static void ASL(){ // Arithmetic Shift Left uint16_t result = (ope.value << 1); if (result & 0xFF00) reg.SR |= CARRY; else reg.SR &= ~CARRY; result &= 0xFF; if (ope.setAcc){ reg.A = result; ope.setAcc = false; } else writeMem(ope.address, result); setSZ(result); } static void LSR(){ // Logical Shift Right uint8_t result8; if (ope.value & 1) reg.SR |= CARRY; else reg.SR &= ~CARRY; result8 = (ope.value >> 1) & 0xFF; if (ope.setAcc){ reg.A = result8; ope.setAcc = false; } else writeMem(ope.address, result8); setSZ(result8); } static void ROL(){ // ROtate Left uint16_t result = ((ope.value << 1) | (reg.SR & CARRY)); if (result & 0x100) reg.SR |= CARRY; else reg.SR &= ~CARRY; result &= 0xFF; if (ope.setAcc){ reg.A = result; ope.setAcc = false; } else writeMem(ope.address, result); setSZ(result); } static void ROR(){ // ROtate Right uint16_t result = (ope.value >> 1) | ((reg.SR & CARRY) << 7); if (ope.value & 0x1) reg.SR |= CARRY; else reg.SR &= ~CARRY; result &= 0xFF; if (ope.setAcc){ reg.A = result; ope.setAcc = false; } else writeMem(ope.address, result); setSZ(result); } static void ADC(){ // ADd with Carry uint16_t result = reg.A + ope.value + (reg.SR & CARRY); setSZ(result); if (((result)^(reg.A ))&((result)^(ope.value))&0x0080) reg.SR |= OVERFLOW; else reg.SR &= ~OVERFLOW; if (reg.SR&DECIMAL) result += ((((result+0x66)^reg.A^ope.value)>>3)&0x22)*3; if (result & 0xFF00) reg.SR |= CARRY; else reg.SR &= ~CARRY; reg.A = (result & 0xFF); } static void SBC(){ // SuBtract with Carry ope.value ^= 0xFF; if (reg.SR & DECIMAL) ope.value -= 0x0066; uint16_t result = reg.A + ope.value + (reg.SR & CARRY); setSZ(result); if (((result)^(reg.A ))&((result)^(ope.value))&0x0080) reg.SR |= OVERFLOW; else reg.SR &= ~OVERFLOW; if (reg.SR&DECIMAL) result += ((((result+0x66)^reg.A^ope.value)>>3)&0x22)*3; if (result & 0xFF00) reg.SR |= CARRY; else reg.SR &= ~CARRY; reg.A = (result & 0xFF); } static void UND(){ // UNDefined (not a valid or supported 6502 opcode) printw("\n\n~ Illegal Instruction At Address $%04X ~\n", reg.PC - 1); BRK(); } static void (*instruction[])(void) = { BRK, ORA, UND, UND, UND, ORA, ASL, UND, PHP, ORA, ASL, UND, UND, ORA, ASL, UND, BPL, ORA, UND, UND, UND, ORA, ASL, UND, CLC, ORA, UND, UND, UND, ORA, ASL, UND, JSR, AND, UND, UND, BIT, AND, ROL, UND, PLP, AND, ROL, UND, BIT, AND, ROL, UND, BMI, AND, UND, UND, UND, AND, ROL, UND, SEC, AND, UND, UND, UND, AND, ROL, UND, RTI, EOR, UND, UND, UND, EOR, LSR, UND, PHA, EOR, LSR, UND, JMP, EOR, LSR, UND, BVC, EOR, UND, UND, UND, EOR, LSR, UND, CLI, EOR, UND, UND, UND, EOR, LSR, UND, RTS, ADC, UND, UND, UND, ADC, ROR, UND, PLA, ADC, ROR, UND, JMP, ADC, ROR, UND, BVS, ADC, UND, UND, UND, ADC, ROR, UND, SEI, ADC, UND, UND, UND, ADC, ROR, UND, UND, STA, UND, UND, STY, STA, STX, UND, DEY, UND, TXA, UND, STY, STA, STX, UND, BCC, STA, UND, UND, STY, STA, STX, UND, TYA, STA, TXS, UND, UND, STA, UND, UND, LDY, LDA, LDX, UND, LDY, LDA, LDX, UND, TAY, LDA, TAX, UND, LDY, LDA, LDX, UND, BCS, LDA, UND, UND, LDY, LDA, LDX, UND, CLV, LDA, TSX, UND, LDY, LDA, LDX, UND, CPY, CMP, UND, UND, CPY, CMP, DEC, UND, INY, CMP, DEX, UND, CPY, CMP, DEC, UND, BNE, CMP, UND, UND, UND, CMP, DEC, UND, CLD, CMP, UND, UND, UND, CMP, DEC, UND, CPX, SBC, UND, UND, CPX, SBC, INC, UND, INX, SBC, NOP, UND, CPX, SBC, INC, UND, BEQ, SBC, UND, UND, UND, SBC, INC, UND, SED, SBC, UND, UND, UND, SBC, INC, UND }; static void (*addressing[])(void) = { IMP, IDX, IMP, IMP, IMP, ZPG, ZPG, IMP, IMP, IMM, ACC, IMP, IMP, ABS, ABS, IMP, REL, IDY, IMP, IMP, IMP, ZPX, ZPX, IMP, IMP, ABY, IMP, IMP, IMP, ABX, ABX, IMP, ABS, IDX, IMP, IMP, ZPG, ZPG, ZPG, IMP, IMP, IMM, ACC, IMP, ABS, ABS, ABS, IMP, REL, IDY, IMP, IMP, IMP, ZPX, ZPX, IMP, IMP, ABY, IMP, IMP, IMP, ABX, ABX, IMP, IMP, IDX, IMP, IMP, IMP, ZPG, ZPG, IMP, IMP, IMM, ACC, IMP, ABS, ABS, ABS, IMP, REL, IDY, IMP, IMP, IMP, ZPX, ZPX, IMP, IMP, ABY, IMP, IMP, IMP, ABX, ABX, IMP, IMP, IDX, IMP, IMP, IMP, ZPG, ZPG, IMP, IMP, IMM, ACC, IMP, IND, ABS, ABS, IMP, REL, IDY, IMP, IMP, IMP, ZPX, ZPX, IMP, IMP, ABY, IMP, IMP, IMP, ABX, ABX, IMP, IMP, IDX, IMP, IMP, ZPG, ZPG, ZPG, IMP, IMP, IMP, IMP, IMP, ABS, ABS, ABS, IMP, REL, IDY, IMP, IMP, ZPX, ZPX, ZPY, IMP, IMP, ABY, IMP, IMP, IMP, ABX, IMP, IMP, IMM, IDX, IMM, IMP, ZPG, ZPG, ZPG, IMP, IMP, IMM, IMP, IMP, ABS, ABS, ABS, IMP, REL, IDY, IMP, IMP, ZPX, ZPX, ZPY, IMP, IMP, ABY, IMP, IMP, ABX, ABX, ABY, IMP, IMM, IDX, IMP, IMP, ZPG, ZPG, ZPG, IMP, IMP, IMM, IMP, IMP, ABS, ABS, ABS, IMP, REL, IDY, IMP, IMP, IMP, ZPX, ZPX, IMP, IMP, ABY, IMP, IMP, IMP, ABX, ABX, IMP, IMM, IDX, IMP, IMP, ZPG, ZPG, ZPG, IMP, IMP, IMM, IMP, IMP, ABS, ABS, ABS, IMP, REL, IDY, IMP, IMP, IMP, ZPX, ZPX, IMP, IMP, ABY, IMP, IMP, IMP, ABX, ABX, IMP }; // PROGRAM ENTRY POINT int main(int argc, char *argv[]) { int i = 0, ch = 0; uint8_t opcode = 0; // ncurses initialization initscr(); cbreak(); noecho(); qiflush(); scrollok(stdscr, TRUE); nodelay(stdscr, TRUE); // processor reset reset(); // main loop while(1){ for (i=0; i<100; i++){ // executes 100 instructions before a kbd scan opcode = readMem(reg.PC++); // fetch and increment the Program Counter addressing[opcode](); // decode operands against the addressing mode instruction[opcode](); // execute the instruction } // keyboard controller if (!keyRdy){ // don't miss a keystroke ch = getch(); // reads from ncurses if (ch != ERR){ key = (uint8_t)ch; // getch() returns an int if (key == 0x12) reset(); // CTRL-R, reset else if (key == 0x02) BRK(); // CTRL-B, break else { if (key == 0x0A) key = 0x0D; // LF (\n) to CR (\r) if ((key == 0x7F) || (key == 0x08)) key = 0x5F; // DEL and BS to _ if ((key >= 0x61) && (key <= 0x7A)) key &= 0xDF; // to upper case keyRdy = 0x80; } } } } }