From 5f2378d9d58aa72cb7a79651a7cec3c9d18f3cd0 Mon Sep 17 00:00:00 2001 From: ArthurFerreira2 Date: Tue, 19 Mar 2019 23:31:34 +0100 Subject: [PATCH] first public release --- Makefile | 5 + apple2.rom | Bin 0 -> 12288 bytes reinette-II.c | 591 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 596 insertions(+) create mode 100644 Makefile create mode 100644 apple2.rom create mode 100644 reinette-II.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3c5719d --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +all:reinette-II + +reinette-II:reinette-II.c + gcc -Wall -O3 reinette-II.c -o reinette-II -lncurses + diff --git a/apple2.rom b/apple2.rom new file mode 100644 index 0000000000000000000000000000000000000000..cb94b9f045b776a6529567d5178c3c8d26c4641f GIT binary patch literal 12288 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 Q-H8)#Xa+UQ@4OKHKm2p-{r~^~ literal 0 HcmV?d00001 diff --git a/reinette-II.c b/reinette-II.c new file mode 100644 index 0000000..d9ebf84 --- /dev/null +++ b/reinette-II.c @@ -0,0 +1,591 @@ +// Reinette II, emulates the Apple II computer +// Copyright 2018 Arthur Ferreira +// Last modified 19th of March 2019 + +#include +#include +#include // for usleep() + +#define ROMSTART 0xD000 +#define ROMSIZE 0x3000 // 12KB +#define RAMSIZE 0xC000 // 48KB + +#define CARRY 0x01 +#define ZERO 0x02 +#define INTERRUPT 0x04 +#define DECIMAL 0x08 +#define BREAK 0x10 +#define UNDEFINED 0x20 +#define OVERFLOW 0x40 +#define SIGN 0x80 + +uint8_t rom[ROMSIZE]; +uint8_t ram[RAMSIZE]; + +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; +bool videoNeedsRefresh; + + +// MEMORY AND I/O + +static uint8_t readMem(uint16_t address){ + static uint8_t queries=0; // slow down emulation when looping for a keypress + + if (address < RAMSIZE) return(ram[address]); + else if (address >= ROMSTART) return(rom[address - ROMSTART]); + + else if (address == 0xC000) { // KBD + if (! ++queries) usleep(100); // sleep 100ms every 256 requests + return(key); + } + else if (address == 0xC010){ // KBDSTRB + key &= 0x7F; // unset bit 7 + return(key); + } + else return(0); // catch all +} + +static void writeMem(uint16_t address, uint8_t value){ + if (address>=0x400 && address<=0x7FF) videoNeedsRefresh = true; + if (address < RAMSIZE) ram[address] = value; + + else if (address == 0xC010) key &= 0x7F; // KBDSTRB, similar as in readMem +} + + +// RESET + +static void reset(){ + reg.PC = readMem(0xFFFC) | (readMem(0xFFFD) << 8); + reg.SP = 0xFF; + reg.SR |= UNDEFINED; + 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){ // update 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); + if (reg.A >= ope.value) reg.SR |= CARRY; + else reg.SR &= ~CARRY; +} + +static void CPX(){ // Compare with X + setSZ(reg.X - ope.value); + if (reg.X >= ope.value) reg.SR |= CARRY; + else reg.SR &= ~CARRY; +} + +static void CPY(){ // Compare with Y + setSZ(reg.Y - ope.value); + if (reg.Y >= ope.value) 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 makeUpdates(uint8_t val){ + if (ope.setAcc) reg.A = val; + else writeMem(ope.address, val); + ope.setAcc = false; + setSZ(val); +} + +static void ASL(){ // Arithmetic Shift Left + uint16_t result = (ope.value << 1); + if (result & 0xFF00) reg.SR |= CARRY; + else reg.SR &= ~CARRY; + makeUpdates((uint8_t)(result & 0xFF)); +} + +static void LSR(){ // Logical Shift Right + if (ope.value & 1) reg.SR |= CARRY; + else reg.SR &= ~CARRY; + makeUpdates((uint8_t)((ope.value >> 1) & 0xFF)); +} + +static void ROL(){ // ROtate Left + uint16_t result = ((ope.value << 1) | (reg.SR & CARRY)); + if (result & 0x100) reg.SR |= CARRY; + else reg.SR &= ~CARRY; + makeUpdates((uint8_t)(result & 0xFF)); +} + +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; + makeUpdates((uint8_t)(result & 0xFF)); +} + +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(); +} + + +// JUMP TABLES + +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 +}; + +static int baseForLines[24] = { // helper for video generation + 0x400, 0x480, 0x500, 0x580, 0x600, 0x680, 0x700, 0x780, + 0x428, 0x4A8, 0x528, 0x5A8, 0x628, 0x6A8, 0x728, 0x7A8, + 0x450, 0x4D0, 0x550, 0x5D0, 0x650, 0x6D0, 0x750, 0x7D0 +}; + + +// PROGRAM ENTRY POINT + +int main(int argc, char *argv[]) { + int ch = 0; + uint8_t opcode = 0; + uint8_t character = 0; + + // ncurses initialization + initscr(); + raw(); + noecho(); + curs_set(0); + qiflush(); + keypad (stdscr, TRUE); + nodelay (stdscr, TRUE); + scrollok(stdscr, TRUE); + + // load the ROM + FILE *f=fopen("apple2.rom","rb"); + if (f != NULL) fread(rom, sizeof(uint8_t), ROMSIZE, f); + fclose(f); + + // processor reset + reset(); + + // main loop + while(1){ + for (int i=0; i<100; i++){ // execute 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 + + // DEBUG : print registers values and keypressed + //move(25,0); // at bottom of screen + //printw("PC-%04X A-%02X X-%02X Y-%02X S-%02X P-%02X KEY-%02X",reg.PC ,reg.A, reg.X, reg.Y, reg.SP, reg.SR, key); + } + usleep(225); // 225ms for 100 instructions gives roughly the original speed + + // keyboard controller + if (key < 0x80 && (ch = getch()) != ERR){ // non blocking read + if (ch == KEY_F( 9)) reset(); // F09, reset + else if (ch == KEY_F(10)) BRK(); // F10, break + else if (ch == KEY_F(12)) { // F12, exit program + endwin(); // reset terminal + exit(0); // and terminate + } + else { // the key is sent to the emulator after some translations + switch(key=(uint8_t)ch){ + case 0x0A: key = 0x0D; break; // LF to CR + case 0x04: key = 0x08; break; // LEFT to BS + case 0x05: key = 0x15; break; // RIGHT to LF + case 0x07: key = 0x08; break; // BELL to BS + } + key |= 0x80; // set bit 7 + } + } + + // video controller only supports page 1 text mode + if (videoNeedsRefresh){ // content changed + move(0, 0); + for(int line=0; line<24; line++){ // for each row + for(int col=0; col<40; col++){ // for each column + character = ram[baseForLines[line]+col]; // read video memory + if (character == '`') character = '_'; // change cursor + if (character < 0x40) attrset(A_REVERSE); // set REVERSE + else if (character > 0x7F) attrset(A_NORMAL); // set NORMAL + else attrset(A_BLINK); // set FLASHING + character &= 0x7F; // unset bit 7 + if (character > 0x5F) character &= 0x3F; // shifts to obtain + if (character < 0x20) character |= 0x40; // the ASCII codes + printw("%c", character); // print character + + } + printw("%c", 0x0A); // carriage return + } + videoNeedsRefresh = false; + attrset(A_NORMAL); + } + } +}