From 052899e196c6a2660651b9896ceed3313e7d0bac Mon Sep 17 00:00:00 2001 From: Andreas Baumann Date: Sat, 21 Nov 2020 21:08:43 +0100 Subject: started a simple emulator --- LINKS | 10 ++ README | 3 + doc/65c02_reset.txt | 9 ++ emu/6502.c | 256 ++++++++++++++++++++++++++++++++++++++++++++++++++++ emu/6502.h | 60 ++++++++++++ emu/emu.c | 21 +++++ emu/emul.h | 9 ++ emu/memory.c | 26 ++++++ emu/memory.h | 23 +++++ roms/simple.asm | 23 +++++ 10 files changed, 440 insertions(+) create mode 100644 doc/65c02_reset.txt create mode 100644 emu/6502.c create mode 100644 emu/6502.h create mode 100644 emu/emu.c create mode 100644 emu/emul.h create mode 100644 emu/memory.c create mode 100644 emu/memory.h create mode 100644 roms/simple.asm diff --git a/LINKS b/LINKS index 7a99468..b5eff15 100644 --- a/LINKS +++ b/LINKS @@ -42,6 +42,7 @@ https://github.com/alimansfield2016/ATM65C02_EMU, nice idea, c++ code quite bad https://github.com/alimansfield2016/ATM65C02_EMU.git, sadly in Javascript https://github.com/chanmix51/soft65c02, sadly in rust https://sbc.rictor.org/simulator.html +https://bbc.godbolt.org/ other/todo: https://news.ycombinator.com/item?id=22077052 @@ -104,8 +105,11 @@ apple emulator: https://damian.pecke.tt/2015/04/06/turning-the-arduino-uno-into-an-apple-ii.html 6502 emulators: +https://www.gianlucaghettini.net/mos-6502-cpu-emulator-in-c/ https://github.com/gianlucag/mos6502.git https://github.com/skilldrick/6502js +http://rubbermallet.org/fake6502.c +https://github.com/davepoo/6502Emulator other emulators: https://github.com/pdewacht/oberon-risc-emu (RISC-5 Wirth Emulator, as inspiration) @@ -116,3 +120,9 @@ https://en.wikipedia.org/wiki/Texas_Instruments_TMS9918 SPI, serial, XModem: https://coronax.wordpress.com/tag/6502/page/2/ + +documentation on the 6502: +http://www.obelisk.me.uk/6502/ +https://sta.c64.org/cbm64mem.html +https://www.pagetable.com/?p=410 +https://github.com/Klaus2m5/6502_65C02_functional_tests diff --git a/README b/README index d208914..483b070 100644 --- a/README +++ b/README @@ -102,6 +102,9 @@ certain interrupts, I get a match in the status register). Button press trigers two events (on press and release) effectively switching off/on the counter. +21.11.2020: +work on a simple 6502 emulator while watching youtube series + commands -------- diff --git a/doc/65c02_reset.txt b/doc/65c02_reset.txt new file mode 100644 index 0000000..c0a8fce --- /dev/null +++ b/doc/65c02_reset.txt @@ -0,0 +1,9 @@ +1111111111111111 11111000 ffff r f8 : SED +0000000000000101 11100100 0005 r e4 : +0000000100000000 01111000 0100 r 78 : +0000000111111111 10010101 01ff r 95 : +0000000111111110 00111001 01fe r 39 : +1111111111111100 00000000 fffc r 00 : +1111111111111101 11111000 fffd r f8 : +1111100000000000 01111000 f800 r 78 : xxx + diff --git a/emu/6502.c b/emu/6502.c new file mode 100644 index 0000000..0bb1993 --- /dev/null +++ b/emu/6502.c @@ -0,0 +1,256 @@ +#include "6502.h" +#include "memory.h" + +#include +#include + +static const uint16_t reset_vector = 0xFFFC; +static const uint16_t ZP_base = 0x0; +static const uint16_t SP_base = 0x100; + +void cpu_6502_init( cpu_6502_t *cpu, struct memory_t *memory ) +{ + cpu->memory = memory; + cpu->debug = false; +} + +uint16_t cpu_6502_read_word( cpu_6502_t *cpu, uint16_t addr ) +{ + return cpu_6502_read_byte( cpu, addr ) + ( cpu_6502_read_byte( cpu, addr + 1 ) << 8 ); +} + +void cpu_6502_push_byte( cpu_6502_t *cpu, uint8_t data ) +{ + cpu_6502_write_byte( cpu, SP_base + cpu->SP, data ); + cpu->SP--; +} + +void cpu_6502_push_word( cpu_6502_t *cpu, uint16_t data ) +{ + cpu_6502_push_byte( cpu, ( data >> 8 ) & 0xFF ); + cpu_6502_push_byte( cpu, data & 0xFF ); +} + +uint8_t cpu_6502_pop_byte( cpu_6502_t *cpu ) +{ + uint8_t data; + + cpu->SP++; + data = cpu_6502_read_byte( cpu, SP_base + cpu->SP ); + + return data; +} + +uint16_t cpu_6502_pop_word( cpu_6502_t *cpu ) +{ + uint8_t low; + uint8_t high; + uint16_t data; + + low = cpu_6502_pop_byte( cpu ); + high = cpu_6502_pop_byte( cpu ); + + data = ( high << 8 ) + low; + + return data; +} + +void cpu_6502_reset( cpu_6502_t *cpu ) +{ + cpu->PC = cpu_6502_read_word( cpu, reset_vector ); +} + +uint8_t cpu_6502_read_byte( cpu_6502_t *cpu, uint16_t addr ) +{ + return cpu->memory->read( cpu->memory, addr ); +} + +void cpu_6502_write_byte( cpu_6502_t *cpu, uint16_t addr, uint8_t data ) +{ + cpu->memory->write( cpu->memory, addr, data ); +} + +void cpu_6502_write_word( cpu_6502_t *cpu, uint16_t addr, uint16_t data ) +{ + cpu->memory->write( cpu->memory, addr, ( data && 0x00FF ) ); + cpu->memory->write( cpu->memory, addr + 1, ( data && 0x00FF ) ); +} + +void cpu_6502_run( cpu_6502_t *cpu ) +{ + while( true ) { + cpu_6502_step( cpu ); + } +} + +static void handle_zero( cpu_6502_t *cpu, uint8_t x ) +{ + if( x == 0 ) { + cpu->PS |= PS_Z; + } else { + cpu->PS &= ~PS_Z; + } +} + +static void handle_sign( cpu_6502_t *cpu, uint8_t x ) +{ + if( x & 0x80 ) { + cpu->PS |= PS_N; + } else { + cpu->PS &= ~ PS_N; + } +} + +static bool is_negative( cpu_6502_t *cpu ) +{ + return cpu->PS & PS_N; +} + +static bool is_overflow( cpu_6502_t *cpu ) +{ + return cpu->PS & PS_V; +} + +static bool is_brk( cpu_6502_t *cpu ) +{ + return cpu->PS & PS_B; +} + +static bool is_decimal( cpu_6502_t *cpu ) +{ + return cpu->PS & PS_D; +} + +static bool is_interrupt( cpu_6502_t *cpu ) +{ + return cpu->PS & PS_I; +} + +static bool is_zero( cpu_6502_t *cpu ) +{ + return cpu->PS & PS_Z; +} + +static bool is_carry( cpu_6502_t *cpu ) +{ + return cpu->PS & PS_C; +} + +void cpu_6502_print_state( cpu_6502_t *cpu, uint8_t opcode ) +{ + fprintf( stderr, "PC: %04X SP: 01%02X PS: %02X %c%c-%c%c%c%c%c A: %02X X: %02X Y: %02X OP: %02X\n", + cpu->PC, cpu->SP, cpu->PS, + is_negative( cpu ) ? 'N' : 'n', + is_overflow( cpu ) ? 'V' : 'v', + is_brk( cpu ) ? 'B' : 'b', + is_decimal( cpu ) ? 'D' : 'd', + is_interrupt( cpu ) ? 'I' : 'i', + is_zero( cpu ) ? 'Z' : 'z', + is_carry( cpu ) ? 'C' : 'c', + cpu->A, cpu->X, cpu->Y, opcode ); +} + +static void update_negative_and_sign( cpu_6502_t *cpu, uint8_t x ) +{ + handle_zero( cpu, x ); + handle_sign( cpu, x ); +} + +void cpu_6502_print_memory( cpu_6502_t *cpu, uint16_t base, uint8_t size ) +{ + for( uint16_t addr = base; addr <= base + size; addr++ ) { + fprintf( stderr, " %02X", cpu_6502_read_byte( cpu, addr ) ); + } + fputs( "\n", stderr ); +} + +void cpu_6502_print_stack( cpu_6502_t *cpu ) +{ + cpu_6502_print_memory( cpu, SP_base, 255 ); +} + +void cpu_6502_print_zerop_page( cpu_6502_t *cpu ) +{ + cpu_6502_print_memory( cpu, ZP_base, 255 ); +} + +void cpu_6502_step( cpu_6502_t *cpu ) +{ + uint8_t opcode; + uint8_t operand8; + + opcode = cpu_6502_read_byte( cpu, cpu->PC ); + cpu->PC++; + + if( cpu->debug ) { + cpu_6502_print_state( cpu, opcode ); + cpu_6502_print_stack( cpu ); + cpu_6502_print_zerop_page( cpu ); + } + + switch( opcode ) { + case LDX_IMM: + operand8 = cpu_6502_read_byte( cpu, cpu->PC ); + cpu->PC++; + cpu->X = operand8; + update_negative_and_sign( cpu, cpu->X ); + break; + + case LDX_ZERO: + operand8 = cpu_6502_read_byte( cpu, cpu->PC ); + cpu->PC++; + cpu->X = cpu_6502_read_byte( cpu, operand8 ); + update_negative_and_sign( cpu, cpu->X ); + break; + + case LDY_IMM: + operand8 = cpu_6502_read_byte( cpu, cpu->PC ); + cpu->PC++; + cpu->Y = operand8; + update_negative_and_sign( cpu, cpu->Y ); + break; + + case LDY_ZERO: + operand8 = cpu_6502_read_byte( cpu, cpu->PC ); + cpu->PC++; + cpu->Y = cpu_6502_read_byte( cpu, operand8 ); + update_negative_and_sign( cpu, cpu->Y ); + break; + + case LDA_IMM: + operand8 = cpu_6502_read_byte( cpu, cpu->PC ); + cpu->PC++; + cpu->A = operand8; + update_negative_and_sign( cpu, cpu->A ); + break; + + case LDA_ZERO: + operand8 = cpu_6502_read_byte( cpu, cpu->PC ); + cpu->PC++; + cpu->A = cpu_6502_read_byte( cpu, operand8 ); + update_negative_and_sign( cpu, cpu->A ); + break; + + case JMP_ABS: + cpu->PC = cpu_6502_read_word( cpu, cpu->PC ); + break; + + case JSR_ABS: + cpu_6502_push_word( cpu, cpu->PC + 1 ); + cpu->PC = cpu_6502_read_word( cpu, cpu->PC ); + break; + + case RTS_IMPL: + cpu->PC = cpu_6502_pop_word( cpu ); + cpu->PC++; + break; + + case TXS_IMPL: + cpu->SP = cpu->X; + break; + + default: + fprintf( stderr, "ERROR: Illegal opcode %02X at PC %04X\n", opcode, cpu->PC ); + exit( EXIT_FAILURE ); + } +} diff --git a/emu/6502.h b/emu/6502.h new file mode 100644 index 0000000..1af25bc --- /dev/null +++ b/emu/6502.h @@ -0,0 +1,60 @@ +#ifndef CPU_6502_H +#define CPU_6502_H + +#include +#include + +typedef struct +{ + uint8_t A; + uint8_t X; + uint8_t Y; + uint8_t SP; + uint16_t PC; + uint8_t PS; + + struct memory_t *memory; + + bool debug; +} cpu_6502_t; + +enum { + PS_N = 0x80, + PS_V = 0x40, + PS_B = 0x10, + PS_D = 0x08, + PS_I = 0x04, + PS_Z = 0x02, + PS_C = 0x01 +}; + +enum { + LDX_IMM = 0xA2, + LDX_ZERO = 0xA6, + LDY_IMM = 0xA0, + LDY_ZERO = 0xA4, + LDA_IMM = 0xA9, + LDA_ZERO = 0xA5, + JMP_ABS = 0x4C, + JSR_ABS = 0x20, + RTS_IMPL = 0x60, + TXS_IMPL = 0x9A +}; + +void cpu_6502_init( cpu_6502_t *cpu, struct memory_t *memory ); +void cpu_6502_reset( cpu_6502_t *cpu ); +uint8_t cpu_6502_read_byte( cpu_6502_t *cpu, uint16_t addr ); +uint16_t cpu_6502_read_word( cpu_6502_t *cpu, uint16_t addr ); +void cpu_6502_write_byte( cpu_6502_t *cpu, uint16_t addr, uint8_t data ); +void cpu_6502_push_byte( cpu_6502_t *cpu, uint8_t data ); +void cpu_6502_push_word( cpu_6502_t *cpu, uint16_t data ); +uint8_t cpu_6502_pop_byte( cpu_6502_t *cpu ); +uint16_t cpu_6502_pop_word( cpu_6502_t *cpu ); +void cpu_6502_run( cpu_6502_t *cpu ); +void cpu_6502_print_state( cpu_6502_t *cpu, uint8_t opcode ); +void cpu_6502_print_memory( cpu_6502_t *cpu, uint16_t base, uint8_t size ); +void cpu_6502_print_stack( cpu_6502_t *cpu ); +void cpu_6502_print_zerop_page( cpu_6502_t *cpu ); +void cpu_6502_step( cpu_6502_t *cpu ); + +#endif diff --git a/emu/emu.c b/emu/emu.c new file mode 100644 index 0000000..fbd9bb7 --- /dev/null +++ b/emu/emu.c @@ -0,0 +1,21 @@ +#include "emul.h" +#include "6502.h" +#include "memory.h" + +#include + +int main( int argc, char *argv[] ) +{ + cpu_6502_t cpu; + memory_t memory; + + memory_init( &memory ); + memory_load( &memory, ROM_START, ROM_SIZE, "./rom.bin" ); + + cpu_6502_init( &cpu, &memory ); + cpu.debug = true; + cpu_6502_reset( &cpu ); + cpu_6502_run( &cpu ); + + exit( EXIT_SUCCESS ); +} diff --git a/emu/emul.h b/emu/emul.h new file mode 100644 index 0000000..cefa020 --- /dev/null +++ b/emu/emul.h @@ -0,0 +1,9 @@ +#ifndef EMUL_H +#define EMUL_H + +enum { + ROM_START = 0xF800, + ROM_SIZE = 2048 +}; + +#endif diff --git a/emu/memory.c b/emu/memory.c new file mode 100644 index 0000000..c9d953b --- /dev/null +++ b/emu/memory.c @@ -0,0 +1,26 @@ +#include "memory.h" + +#include + +void memory_init( memory_t *memory ) +{ + memory->read = memory_read; + memory->write = memory_write; +} + +uint8_t memory_read( memory_t *memory, uint16_t addr ) +{ + return memory->cell[addr]; +} + +void memory_write( memory_t *memory, uint16_t addr, uint8_t data ) +{ + memory->cell[addr] = data; +} + +void memory_load( memory_t *memory, uint16_t addr, uint16_t size, const char *filename ) +{ + FILE *f = fopen( filename, "rb" ); + fread( &memory->cell[addr], size, 1, f ); + fclose( f ); +} diff --git a/emu/memory.h b/emu/memory.h new file mode 100644 index 0000000..35fd0ef --- /dev/null +++ b/emu/memory.h @@ -0,0 +1,23 @@ +#ifndef MEMORY_H +#define MEMORY_H + +#include + +enum { + MEMORY_SIZE = 65535 +}; + +typedef struct memory_t +{ + uint8_t cell[MEMORY_SIZE]; + + uint8_t (*read)( struct memory_t *memory, uint16_t addr ); + void (*write)( struct memory_t *memory, uint16_t addr, uint8_t data ); +} memory_t; + +void memory_init( memory_t *memory ); +uint8_t memory_read( memory_t *memory, uint16_t addr ); +void memory_write( memory_t *memory, uint16_t addr, uint8_t data ); +void memory_load( memory_t *memory, uint16_t addr, uint16_t size, const char *filename ); + +#endif diff --git a/roms/simple.asm b/roms/simple.asm new file mode 100644 index 0000000..7e57ee4 --- /dev/null +++ b/roms/simple.asm @@ -0,0 +1,23 @@ + .org #$f800 + +reset: + ldx #$FF + txs + lda #42 + lda $0 + ldx #41 + ldx $1 + jsr sub1 + +fini: + jmp fini + +sub1: + ldy #40 + ldy $2 + rts + + .org #$fffa + .word $0 + .word reset + .word $0 -- cgit v1.2.3-54-g00ecf