From a531330d0a33d4bd2744e45f40a75657cbee7f88 Mon Sep 17 00:00:00 2001 From: ArthurFerreira2 Date: Fri, 25 Jun 2021 00:08:30 +0200 Subject: [PATCH] fixed timing issues --- puce6502.c | 261 ++++++++++++++++++++++++++++++++++++++++++++--------- puce6502.h | 30 +++--- 2 files changed, 237 insertions(+), 54 deletions(-) diff --git a/puce6502.c b/puce6502.c index e907ab0..300c38f 100644 --- a/puce6502.c +++ b/puce6502.c @@ -1,9 +1,14 @@ /* - puce6502 - MOS 6502 cpu emulator - Last modified 1st of August 2020 + Last modified 21st of June 2021 Copyright (c) 2018 Arthur Ferreira (arthur.ferreira2@gmail.com) + This version has been modified for reinette II plus, a french Apple II plus + emulator using SDL2 (https://github.com/ArthurFerreira2/reinette-II-plus). + + Please download the latest version from + https://github.com/ArthurFerreira2/puce6502 + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights @@ -21,14 +26,39 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ + +// set to zero for 'normal' use +// or to 1 if you want to run the functionnal tests +#define _FUNCTIONNAL_TESTS 1 + #include "puce6502.h" -// function to be provided by user to handle read and writes to locations not -// in ROM or in RAM : Soft Switches, extension cards ROMs, PIA, VIA, ACIA etc... -extern uint8_t softSwitches(uint16_t address, uint8_t value); +#define CARRY 0x01 +#define ZERO 0x02 +#define INTR 0x04 +#define DECIM 0x08 +#define BREAK 0x10 +#define UNDEF 0x20 +#define OFLOW 0x40 +#define SIGN 0x80 + +#if _FUNCTIONNAL_TESTS + + // for functionnal tests, see main() + uint8_t RAM[65536]; + inline uint8_t readMem(uint16_t address) { return RAM[address]; } + inline void writeMem(uint16_t address, uint8_t value) { RAM[address] = value; } + +#else + + // user provided functions + extern uint8_t readMem(uint16_t address); + extern void writeMem(uint16_t address, uint8_t value); + +#endif + #define CARRY 0x01 @@ -40,27 +70,23 @@ extern uint8_t softSwitches(uint16_t address, uint8_t value); #define OFLOW 0x40 #define SIGN 0x80 -struct Operand { + +static struct Operand { uint8_t code; + bool setAcc; uint8_t value; uint16_t address; - bool setAcc; } ope; -struct Register { +static struct Register { uint8_t A,X,Y,SR,SP; uint16_t PC; } reg; -// instruction timing : -// http://nparker.llx.com/a2/opcodes.html -// http://wouter.bbcmicro.net/general/6502/6502_opcodes.html -// NOT IMPLEMENTED : -// Absolute-X, absolute-Y, and Zpage-Y addressing modes need an extra cycle -// if indexing crosses a page boundary, or if the instruction writes to memory. +unsigned long long int ticks; // exportable to user modules -static int cycles[256] = { // cycle count per instruction +static const int cycles[256] = { // cycles per instruction 7,6,0,0,0,3,5,0,3,2,2,0,0,4,6,0,3,5,0,0,0,4,6,0,2,4,0,0,0,4,7,0, 6,6,0,0,3,3,5,0,4,2,2,0,4,4,6,0,3,5,0,0,0,4,6,0,2,4,0,0,0,4,7,0, 6,6,0,0,0,3,5,0,3,2,2,0,3,4,6,0,3,5,0,0,0,4,6,0,2,4,0,0,0,4,7,0, @@ -72,20 +98,6 @@ static int cycles[256] = { // cycle count per instruction }; -//=============================================================== MEMORY AND I/O - -inline static uint8_t readMem(uint16_t address){ - if (address < RAMSIZE) return(ram[address]); - if (address >= ROMSTART) return(rom[address - ROMSTART]); - return softSwitches(address, 0); // MEMORY MAPPED I/O -} - -inline static void writeMem(uint16_t address, uint8_t value){ - if (address < RAMSIZE) ram[address] = value; - else if (address < ROMSTART) softSwitches(address, value); -} - - //=============================================== STACK, SIGN AND OTHER ROUTINES inline static void push(uint8_t value){ @@ -96,7 +108,7 @@ inline static uint8_t pull(){ return(readMem(0x100 + ++reg.SP)); } -inline static void setSZ(uint8_t value){ // update both the Sign & Zero FLAGS +inline 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; @@ -109,7 +121,7 @@ inline static void branch(){ // used by the 8 branch instructions reg.PC += ope.address; } -inline static void makeUpdates(uint8_t val){ // used by ASL, LSR, ROL and ROR +inline static void makeUpdates(uint8_t val){ // used by ASL, LSR, ROL and ROR if (ope.setAcc){ reg.A = val; ope.setAcc = false; @@ -145,6 +157,7 @@ static void ZPX(){ // Zero Page,X } static void ZPY(){ // Zero Page,Y + if (readMem(reg.PC) + reg.Y > 0xFF) ticks++; ope.address = (readMem(reg.PC++) + reg.Y) & 0xFF; ope.value = readMem(ope.address); } @@ -161,12 +174,14 @@ static void ABS(){ // ABSolute } static void ABX(){ // ABsolute,X + if (readMem(reg.PC) + reg.X > 0xFF) ticks++; 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 + if (readMem(reg.PC) + reg.Y > 0xFF) ticks++; ope.address = (readMem(reg.PC) | (readMem(reg.PC + 1) << 8)) + reg.Y; ope.value = readMem(ope.address); reg.PC += 2; @@ -204,7 +219,7 @@ void BRK(){ // BReaK push(reg.PC & 0xFF); push(reg.SR | BREAK); reg.SR |= INTR; - reg.PC = readMem(0xFFFE) | (readMem(0xFFFF) << 8); + reg.PC = readMem(0xFFFE) | ((readMem(0xFFFF) << 8)); // IRQ/BRK vect @FFFE/FF } static void CLD(){ // CLear Decimal @@ -482,7 +497,7 @@ static void UND(){ // UNDefined (not a valid or supported 6502 opcode) //================================================================== JUMP TABLES -static void (*instruction[])(void) = { +static void (*instruction[256])(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, @@ -501,7 +516,7 @@ static void (*instruction[])(void) = { BEQ, SBC, UND, UND, UND, SBC, INC, UND, SED, SBC, UND, UND, UND, SBC, INC, UND }; -static void (*addressing[])(void) = { +static void (*addressing[256])(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, @@ -523,15 +538,8 @@ static void (*addressing[])(void) = { //========================================================= USER INTERFACE (API) -void puce6502Reset(){ - reg.PC = readMem(0xFFFC) | (readMem(0xFFFD) << 8); - reg.SP = 0xFF; - reg.SR = (reg.SR | INTR) & ~DECIM; - ope.setAcc = false; - ticks += 7; -} -void puce6502Exec(long long int cycleCount){ +uint16_t puce6502Exec(unsigned long long int cycleCount){ cycleCount += ticks; // cycleCount becomes the target ticks value while (ticks < cycleCount) { ope.code = readMem(reg.PC++); // FETCH and increment the Program Counter @@ -539,4 +547,171 @@ void puce6502Exec(long long int cycleCount){ instruction[ope.code](); // EXECUTE the instruction ticks += cycles[ope.code]; // update ticks count } + return reg.PC; } + + +void puce6502RST() { + reg.PC = readMem(0xFFFC) | (readMem(0xFFFD) << 8); + reg.SP = 0xFF; + reg.SR = (reg.SR | INTR | BREAK | UNDEF) & ~DECIM; + ticks += 7; +} + +void puce6502NMI() { + writeMem(0x100 + reg.SP--, ((reg.PC) >> 8) & 0xFF); + writeMem(0x100 + reg.SP--, reg.PC & 0xFF); + writeMem(0x100 + reg.SP--, reg.SR); + reg.PC = readMem(0xFFFA) | (readMem(0xFFFB) << 8); + ticks += 7; +} + +void puce6502IRQ() { + if (!(reg.SR & INTR)) return; + writeMem(0x100 + reg.SP--, ((reg.PC) >> 8) & 0xFF); + writeMem(0x100 + reg.SP--, reg.PC & 0xFF); + writeMem(0x100 + reg.SP--, reg.SR); + reg.PC = readMem(0xFFFE) | (readMem(0xFFFF) << 8); + ticks += 7; +} + + + + +// ALL the code down below was used during developpment for test and debug +// and is not required for normal operation + +#include + +static const char* mn[256] = { + "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 const int am[256] = { + 0x0 , 0xC , 0x0 , 0x0 , 0x0 , 0x3 , 0x3 , 0x0 , 0x0 , 0x2 , 0x1 , 0x0 , 0x0 , 0x7 , 0x7 , 0x0 , + 0x6 , 0xD , 0x0 , 0x0 , 0x0 , 0x4 , 0x4 , 0x0 , 0x0 , 0x9 , 0x0 , 0x0 , 0x0 , 0x8 , 0x8 , 0x0 , + 0x7 , 0xC , 0x0 , 0x0 , 0x3 , 0x3 , 0x3 , 0x0 , 0x0 , 0x2 , 0x1 , 0x0 , 0x7 , 0x7 , 0x7 , 0x0 , + 0x6 , 0xD , 0x0 , 0x0 , 0x0 , 0x4 , 0x4 , 0x0 , 0x0 , 0x9 , 0x0 , 0x0 , 0x0 , 0x8 , 0x8 , 0x0 , + 0x0 , 0xC , 0x0 , 0x0 , 0x0 , 0x3 , 0x3 , 0x0 , 0x0 , 0x2 , 0x1 , 0x0 , 0x7 , 0x7 , 0x7 , 0x0 , + 0x6 , 0xD , 0x0 , 0x0 , 0x0 , 0x4 , 0x4 , 0x0 , 0x0 , 0x9 , 0x0 , 0x0 , 0x0 , 0x8 , 0x8 , 0x0 , + 0x0 , 0xC , 0x0 , 0x0 , 0x0 , 0x3 , 0x3 , 0x0 , 0x0 , 0x2 , 0x1 , 0x0 , 0xA , 0x7 , 0x7 , 0x0 , + 0x6 , 0xD , 0x0 , 0x0 , 0x0 , 0x4 , 0x4 , 0x0 , 0x0 , 0x9 , 0x0 , 0x0 , 0x0 , 0x8 , 0x8 , 0x0 , + 0x0 , 0xC , 0x0 , 0x0 , 0x3 , 0x3 , 0x3 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x7 , 0x7 , 0x7 , 0x0 , + 0x6 , 0xD , 0x0 , 0x0 , 0x4 , 0x4 , 0x5 , 0x0 , 0x0 , 0x9 , 0x0 , 0x0 , 0x0 , 0x8 , 0x0 , 0x0 , + 0x2 , 0xC , 0x2 , 0x0 , 0x3 , 0x3 , 0x3 , 0x0 , 0x0 , 0x2 , 0x0 , 0x0 , 0x7 , 0x7 , 0x7 , 0x0 , + 0x6 , 0xD , 0x0 , 0x0 , 0x4 , 0x4 , 0x5 , 0x0 , 0x0 , 0x9 , 0x0 , 0x0 , 0x8 , 0x8 , 0x9 , 0x0 , + 0x2 , 0xC , 0x0 , 0x0 , 0x3 , 0x3 , 0x3 , 0x0 , 0x0 , 0x2 , 0x0 , 0x0 , 0x7 , 0x7 , 0x7 , 0x0 , + 0x6 , 0xD , 0x0 , 0x0 , 0x0 , 0x4 , 0x4 , 0x0 , 0x0 , 0x9 , 0x0 , 0x0 , 0x0 , 0x8 , 0x8 , 0x0 , + 0x2 , 0xC , 0x0 , 0x0 , 0x3 , 0x3 , 0x3 , 0x0 , 0x0 , 0x2 , 0x0 , 0x0 , 0x7 , 0x7 , 0x7 , 0x0 , + 0x6 , 0xD , 0x0 , 0x0 , 0x0 , 0x4 , 0x4 , 0x0 , 0x0 , 0x9 , 0x0 , 0x0 , 0x0 , 0x8 , 0x8 , 0x0 + }; + + +void dasm(uint16_t address) { + + uint8_t op = readMem(address); + uint8_t b1 = readMem((address + 1) & 0xFFFF); + uint8_t b2 = readMem((address + 2) & 0xFFFF); + + printf("%04X %02X ", address, op); + + switch(am[op]) { + case 0x0: printf(" %s ", mn[op] ); break; // implied + case 0x1: printf(" %s A ", mn[op] ); break; // accumulator + case 0x2: printf("%02X %s #$%02X ", b1, mn[op],b1 ); break; // immediate + case 0x3: printf("%02X %s $%02X ", b1, mn[op],b1 ); break; // zero page + case 0x4: printf("%02X %s $%02X,X ", b1, mn[op],b1 ); break; // zero page, X indexed + case 0x5: printf("%02X %s $%02X,Y ", b1, mn[op],b1 ); break; // zero page, Y indexed + case 0x6: printf("%02X %s $%02X ", b1, mn[op],b1 ); break; // relative + case 0xC: printf("%02X %s ($%02X,X) ", b1, mn[op],b1 ); break; // X indexed, indirect + case 0xD: printf("%02X %s ($%02X),Y ", b1, mn[op],b1 ); break; // indirect, Y indexed + case 0x7: printf("%02X%02X %s $%02X%02X ",b1,b2,mn[op],b2,b1); break; // absolute + case 0x8: printf("%02X%02X %s $%02X%02X,X ",b1,b2,mn[op],b2,b1); break; // absolute, X indexed + case 0x9: printf("%02X%02X %s $%02X%02X,Y ",b1,b2,mn[op],b2,b1); break; // absolute, Y indexed + case 0xA: printf("%02X%02X %s ($%02X%02X) ",b1,b2,mn[op],b2,b1); break; // indirect + } +} + + +void printRegs() { + printf("A=%02X X=%02X Y=%02X S=%02X *S=%02X %c%c%c%c%c%c%c%c", \ + reg.A, reg.X, reg.Y, reg.SP, readMem(0x100 + reg.SP), \ + reg.SR&SIGN?'N':'-', reg.SR&OFLOW?'V':'-',reg.SR&UNDEF?'U':'.',reg.SR&BREAK?'B':'-', \ + reg.SR&DECIM?'D':'-',reg.SR&INTR?'I':'-', reg.SR&ZERO?'Z':'-', reg.SR&CARRY?'C':'-'); +} + + +void setPC(uint16_t address) { + reg.PC = address; +} + + +uint16_t getPC(){ + return reg.PC; +} + + + +#if _FUNCTIONNAL_TESTS + + // 6502 functonnal tests + // using Klaus Dormann's functonnal tests published at : + // https://github.com/Klaus2m5/6502_65C02_functional_tests + + int main(int argc, char* argv[]){ + + char *filename = "6502_functional_test.bin"; + FILE *f = fopen(filename, "rb"); + if (!f || fread(RAM, 1, 65536, f) != 65536) { + printf("ERROR : can't load %s\n", filename); + return(0); + } + fclose(f); + + puce6502RST(); // reset the CPU + reg.PC = 0x0400; // set Program Counter to start of code + + unsigned long long int oldticks = ticks; + uint16_t oldPC = 0x0400, newPC = 0x0400; // to detect the BNE $FE when an error occurs + + while(1) { + dasm(newPC); + printf(" "); + newPC = puce6502Exec(1); + printRegs(); + printf(" Cycles: %llu Total: %llu\n", ticks - oldticks, ticks); + oldticks = ticks; + + if (newPC == 0x3469){ // 6502_functional_test SUCCESS + printf("\nReached end of 6502_functional_test @ %04X : SUCCESS !\n", newPC); + break; + } + + if (newPC == oldPC ) { + printf("\n\nLoop detected @ %04X - Press ENTER to proceed with next test or CTRL to stop\n\n", newPC); + return(-1); + getchar(); + reg.PC = newPC + 2; + } + oldPC = newPC; + } + + return(0); + } + +#endif diff --git a/puce6502.h b/puce6502.h index 2d6065c..fb0132b 100644 --- a/puce6502.h +++ b/puce6502.h @@ -3,6 +3,12 @@ Last modified 1st of August 2020 Copyright (c) 2018 Arthur Ferreira (arthur.ferreira2@gmail.com) + This version has been modified for reinette II plus, a french Apple II plus + emulator using SDL2 (https://github.com/ArthurFerreira2/reinette-II-plus). + + Please download the latest version from + https://github.com/ArthurFerreira2/puce6502 + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights @@ -22,22 +28,24 @@ THE SOFTWARE. */ -#ifndef _CPU_H -#define _CPU_H + +#ifndef _PUCE6502_H +#define _PUCE6502_H typedef unsigned char uint8_t; typedef unsigned short uint16_t; -typedef enum {false, true} bool; +typedef enum { false, true } bool; -#define ROMSTART 0xD000 -#define ROMSIZE 0x3000 -#define RAMSIZE 0xC000 +extern unsigned long long int ticks; -uint8_t rom[ROMSIZE]; -uint8_t ram[RAMSIZE]; +uint16_t puce6502Exec(unsigned long long int cycleCount); +void puce6502RST(); +void puce6502IRQ(); +void puce6502NMI(); -long long int ticks; +// void printRegs(); +// void dasm(uint16_t address); +// void setPC(uint16_t address); +// uint16_t getPC(); -void puce6502Reset(); -void puce6502Exec(long long int cycleCount); #endif