From 3cebed2377a506886b41584cfc6cc417fd2f83ea Mon Sep 17 00:00:00 2001 From: Peter Evans Date: Sat, 2 Dec 2017 13:05:53 -0600 Subject: [PATCH] Adding all of the instruction files --- include/log.h | 2 + include/mos6502.enums.h | 100 +++++++++++++++ include/mos6502.h | 183 ++++++++++++++++++++++++++ include/vm_bits.h | 9 ++ include/vm_screen.h | 1 + include/vm_segment.h | 11 +- sources.cmake | 8 ++ src/log.c | 6 +- src/mos6502.addr.c | 275 ++++++++++++++++++++++++++++++++++++++++ src/mos6502.arith.c | 67 ++++++++++ src/mos6502.bits.c | 110 ++++++++++++++++ src/mos6502.branch.c | 49 +++++++ src/mos6502.c | 157 +++++++++++++++++++++++ src/mos6502.exec.c | 39 ++++++ src/mos6502.loadstor.c | 95 ++++++++++++++ src/mos6502.stat.c | 41 ++++++ src/vm_screen.c | 6 + src/vm_segment.c | 88 +++++++++---- tests/CMakeLists.txt | 9 +- tests/log.c | 33 +++++ tests/main.c | 5 - tests/mos6502.addr.c | 201 +++++++++++++++++++++++++++++ tests/mos6502.c | 37 ++++++ tests/vm_screen.c | 39 ++++++ tests/vm_segment.c | 68 ++++++++++ 25 files changed, 1602 insertions(+), 37 deletions(-) create mode 100644 include/mos6502.enums.h create mode 100644 include/mos6502.h create mode 100644 include/vm_bits.h create mode 100644 src/mos6502.addr.c create mode 100644 src/mos6502.arith.c create mode 100644 src/mos6502.bits.c create mode 100644 src/mos6502.branch.c create mode 100644 src/mos6502.c create mode 100644 src/mos6502.exec.c create mode 100644 src/mos6502.loadstor.c create mode 100644 src/mos6502.stat.c create mode 100644 tests/log.c delete mode 100644 tests/main.c create mode 100644 tests/mos6502.addr.c create mode 100644 tests/mos6502.c create mode 100644 tests/vm_screen.c create mode 100644 tests/vm_segment.c diff --git a/include/log.h b/include/log.h index 8611788..87f90e7 100644 --- a/include/log.h +++ b/include/log.h @@ -1,6 +1,8 @@ #ifndef _LOG_H_ #define _LOG_H_ +#define LOG_FILENAME "/tmp/emp.log" + extern void log_write(int, const char *, ...); extern void log_close(); extern void log_open(); diff --git a/include/mos6502.enums.h b/include/mos6502.enums.h new file mode 100644 index 0000000..95961a2 --- /dev/null +++ b/include/mos6502.enums.h @@ -0,0 +1,100 @@ +#ifndef _MOS6502_ENUMS_H_ +#define _MOS6502_ENUMS_H_ + +/* + * mos6502.enums.h + * Enums and other symbols for use with the mos 6502 + * + * We have separated the definitions of address mode types, instruction + * types, etc. into their own file so that we can include it in our main + * source file, as well as from our unit test suite, without necessarily + * adding them to the global namespace throughout the application. + */ + +enum status_flags { + CARRY = 1, + ZERO = 2, + INTERRUPT = 4, + DECIMAL = 8, + BREAK = 16, + OVERFLOW = 64, + NEGATIVE = 128, +}; + +enum addr_mode { + NOA, // no address mode + ACC, // accumulator + ABS, // absolute + ABX, // absolute x-index + ABY, // absolute y-index + IMM, // immediate + IMP, // implied + IND, // indirect + IDX, // x-index indirect + IDY, // indirect y-index + REL, // relative + ZPG, // zero page + ZPX, // zero page x-index + ZPY, // zero page y-index +}; + +enum instruction { + ADC, // ADd with Carry + AND, // bitwise AND + ASL, // Arithmetic Shift Left + BCC, // Branch on Carry Clear + BCS, // Branch on Carry Set + BEQ, // Branch on EQual to zero + BIT, // BIT test + BMI, // Branch on MInus + BNE, // Branch on Not Equal to zero + BPL, // Branch on PLus + BRK, // BReaK (interrupt) + BVC, // Branch on oVerflow Clear + BVS, // Branch on oVerflow Set + CLC, // CLear Carry + CLD, // CLear Decimal + CLI, // CLear Interrupt disable + CLV, // CLear oVerflow + CMP, // CoMPare + CPX, // ComPare with X register + CPY, // ComPare with Y register + DEC, // DECrement + DEX, // DEcrement X + DEY, // DEcrement Y + EOR, // Exclusive OR + INC, // INCrement + INX, // INcrement X + INY, // INcrement Y + JMP, // JuMP + JSR, // Jump to SubRoutine + LDA, // LoaD Accumulator + LDX, // LoaD X + LDY, // LoaD Y + LSR, // Logical Shift Right + NOP, // NO oPeration + ORA, // OR with Accumulator + PHA, // PusH Accumulator + PHP, // PusH Predicate register + PLA, // PulL Accumulator + PLP, // PulL Predicate register + ROL, // ROtate Left + ROR, // ROtate Right + RTI, // ReTurn from Interrupt + RTS, // ReTurn from Subroutine + SBC, // SuBtract with Carry + SEC, // SEt Carry + SED, // SEt Decimal + SEI, // SEt Interrupt disable + STA, // STore Accumulator + STX, // STore X + STY, // STore Y + TAX, // Transfer Accumulator to X + TAY, // Transfer Accumulator to Y + TSX, // Transfer Stack register to X + TXA, // Transfer X to Accumulator + TXS, // Transfer X to Stack register + TYA, // Transfer Y to Accumulator +}; + +#endif diff --git a/include/mos6502.h b/include/mos6502.h new file mode 100644 index 0000000..c930fe3 --- /dev/null +++ b/include/mos6502.h @@ -0,0 +1,183 @@ +#ifndef _MOS6502_H_ +#define _MOS6502_H_ + +#include "vm_bits.h" +#include "vm_segment.h" + +#define MOS6502_MEMSIZE 65536 + +#define SET_ARITH_STATUS(v) \ + cpu->P &= ~NEGATIVE; \ + cpu->P &= ~ZERO; \ + cpu->P &= ~CARRY; \ + if ((v) == 0) cpu->P |= ZERO; \ + if ((v) > 0) cpu->P |= CARRY; \ + if ((v) & 0x80) cpu->P |= NEGATIVE + +#define SET_PC_BYTE(cpu, off, byte) \ + vm_segment_set(cpu->memory, cpu->PC + off, byte) + +#define INIT_ADDR_MODE() \ + mos6502 *cpu; \ + cpu = mos6502_create() + +#define END_ADDR_MODE() \ + mos6502_free(cpu) + +#define DEFINE_INST(inst) \ + void mos6502_handle_##inst (mos6502 *cpu, vm_8bit oper) + +typedef struct { + // Our memory. + vm_segment *memory; + + // This contains the last _effective_ address we've resolved in one + // of our address modes. In absolute mode, this would be the literal + // operand we read from memory; in indirect mode, this will be the + // address we _find_ after dereferencing the operand we read from + // memory. Another way of thinking of this is, this address is where + // we found the value we care about. + vm_16bit last_addr; + + // Our program counter register; this is what we'll use to determine + // where we're "at" in memory while executing opcodes. We use a + // 16-bit register because our memory is 64k large. + vm_16bit PC; + + // This is the accumulator register. It's used in most arithmetic + // operations, and anything like that which you need to do will end + // up storing the value here. + vm_8bit A; + + // The X and Y registers are our index registers. They're provided + // to aid looping over tables, but they can also be used for other + // purposes. + vm_8bit X, Y; + + // The P register is our status flag register. (I presume 'P' means + // 'predicate'.) Each bit stands for some kind of status. + vm_8bit P; + + // The S register is our stack counter register. It indicates how + // far into the stack we've gone. + vm_8bit S; +} mos6502; + +/* + * This is a small convenience so that we don't need to expose the + * somewhat regrettable syntax for function pointers to any main source + * file + */ +typedef vm_8bit (*mos6502_address_resolver)(mos6502 *); + +extern mos6502 *mos6502_create(); +extern void mos6502_free(mos6502 *); +extern vm_8bit mos6502_next_byte(mos6502 *); +extern void mos6502_push_stack(mos6502 *, vm_16bit); +extern vm_16bit mos6502_pop_stack(mos6502 *); +extern void mos6502_modify_status(mos6502 *, int, vm_8bit); + +extern mos6502_address_resolver mos6502_get_address_resolver(int); + +/* + * In some address mode resolution, we must factor the carry bit into + * the arithmetic we perform. In all those cases, if the carry bit is + * set, we must only add 1 to the addition. The carry variable is, + * therefore, the literal value we are adding, rather than a boolean + * signifier. + */ +#define CARRY_BIT() \ + vm_8bit carry = 0; \ + if (cpu->P & CARRY) carry = 1 + +/* + * A uniform way of declaring resolve functions for address modes, which + * is useful in the event that we need to change the function signature. + */ +#define DECL_ADDR_MODE(x) \ + extern vm_8bit mos6502_resolve_##x (mos6502 *) + +/* + * Similarly, a uniform way of declaring instruction handler functions, + * for the same reasons. + */ +#define DECL_INST(x) \ + extern void mos6502_handle_##x (mos6502 *, vm_8bit) + +/* + * All of our address modes + */ +DECL_ADDR_MODE(acc); +DECL_ADDR_MODE(abs); +DECL_ADDR_MODE(abx); +DECL_ADDR_MODE(aby); +DECL_ADDR_MODE(imm); +DECL_ADDR_MODE(ind); +DECL_ADDR_MODE(idx); +DECL_ADDR_MODE(idy); +DECL_ADDR_MODE(rel); +DECL_ADDR_MODE(zpg); +DECL_ADDR_MODE(zpx); +DECL_ADDR_MODE(zpy); + +/* + * And now, our instruction handlers + */ +DECL_INST(adc); +DECL_INST(and); +DECL_INST(asl); +DECL_INST(bcc); +DECL_INST(bcs); +DECL_INST(beq); +DECL_INST(bit); +DECL_INST(bmi); +DECL_INST(bne); +DECL_INST(bpl); +DECL_INST(brk); +DECL_INST(bvc); +DECL_INST(bvs); +DECL_INST(clc); +DECL_INST(cld); +DECL_INST(cli); +DECL_INST(clv); +DECL_INST(cmp); +DECL_INST(cpx); +DECL_INST(cpy); +DECL_INST(dec); +DECL_INST(dex); +DECL_INST(dey); +DECL_INST(eor); +DECL_INST(inc); +DECL_INST(inx); +DECL_INST(iny); +DECL_INST(jmp); +DECL_INST(jsr); +DECL_INST(lda); +DECL_INST(ldx); +DECL_INST(ldy); +DECL_INST(lsr); +DECL_INST(nop); +DECL_INST(ora); +DECL_INST(pha); +DECL_INST(php); +DECL_INST(pla); +DECL_INST(plp); +DECL_INST(rol); +DECL_INST(ror); +DECL_INST(rti); +DECL_INST(rts); +DECL_INST(sbc); +DECL_INST(sec); +DECL_INST(sed); +DECL_INST(sei); +DECL_INST(sta); +DECL_INST(stx); +DECL_INST(sty); +DECL_INST(tax); +DECL_INST(tay); +DECL_INST(tsx); +DECL_INST(txa); +DECL_INST(txs); +DECL_INST(tya); + +#endif diff --git a/include/vm_bits.h b/include/vm_bits.h new file mode 100644 index 0000000..cf28f82 --- /dev/null +++ b/include/vm_bits.h @@ -0,0 +1,9 @@ +#ifndef _VM_BITS_H_ +#define _VM_BITS_H_ + +#include + +typedef uint8_t vm_8bit; +typedef uint16_t vm_16bit; + +#endif diff --git a/include/vm_screen.h b/include/vm_screen.h index 2548cf6..f5a3e33 100644 --- a/include/vm_screen.h +++ b/include/vm_screen.h @@ -9,6 +9,7 @@ typedef struct { } vm_screen_context; extern void vm_screen_draw_rect(vm_screen_context *, int, int, int, int); +extern void vm_screen_free_context(vm_screen_context *); extern vm_screen_context *vm_screen_new_context(); extern void vm_screen_set_color(vm_screen_context *, int, int, int, int); diff --git a/include/vm_segment.h b/include/vm_segment.h index fd1e9ea..a418920 100644 --- a/include/vm_segment.h +++ b/include/vm_segment.h @@ -1,19 +1,18 @@ #ifndef _VM_SEGMENT_H_ #define _VM_SEGMENT_H_ -#define VM_SEGMENT_TABLE_MAX 16 - -typedef uint8_t vm_segment_byte; +#include "vm_bits.h" typedef struct { size_t size; - vm_segment_byte *memory; + vm_8bit *memory; } vm_segment; extern void vm_segment_copy(vm_segment *, vm_segment *, size_t, size_t, size_t); extern vm_segment *vm_segment_create(size_t); -extern vm_segment_byte vm_segment_get(vm_segment *, size_t); -extern void vm_segment_set(vm_segment *, size_t, vm_segment_byte); +extern void vm_segment_free(vm_segment *); +extern vm_8bit vm_segment_get(vm_segment *, size_t); +extern void vm_segment_set(vm_segment *, size_t, vm_8bit); #define vm_segment_bounds_check(segment, index) \ (index == index % segment->size) diff --git a/sources.cmake b/sources.cmake index 45e293f..42c2fa5 100644 --- a/sources.cmake +++ b/sources.cmake @@ -1,5 +1,13 @@ set(emp_sources log.c + mos6502.c + mos6502.addr.c + mos6502.arith.c + mos6502.bits.c + mos6502.branch.c + mos6502.exec.c + mos6502.loadstor.c + mos6502.stat.c vm_screen.c vm_segment.c ) diff --git a/src/log.c b/src/log.c index a26c427..b0a5bbe 100644 --- a/src/log.c +++ b/src/log.c @@ -2,14 +2,16 @@ #include #include +#include "log.h" + static FILE *log_stream = NULL; void log_open() { - log_stream = fopen("/tmp/emp.log", "w"); + log_stream = fopen(LOG_FILENAME, "w"); if (log_stream == NULL) { - perror("Couldn't open log file (/tmp/emp.log)"); + perror("Couldn't open log file (" LOG_FILENAME ")"); exit(1); } } diff --git a/src/mos6502.addr.c b/src/mos6502.addr.c new file mode 100644 index 0000000..ca0e695 --- /dev/null +++ b/src/mos6502.addr.c @@ -0,0 +1,275 @@ +/* + * mos6502.addr.c + * + * Here we have support for the address modes that are built into the + * MOS 6502 chip. In general, these address modes help the instruction + * figure out _what_ it is working with, which is either a value from a + * register, or from some place in memory. + */ + +#include + +#include "mos6502.h" +#include "mos6502.enums.h" + +static int addr_modes[] = { +// 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F + IMP, IDX, NOA, NOA, NOA, ZPG, ZPG, NOA, IMP, IMM, ACC, NOA, NOA, ABS, ABS, NOA, // 0x + REL, IDY, NOA, NOA, NOA, ZPX, ZPX, NOA, IMP, ABY, NOA, NOA, NOA, ABX, ABX, NOA, // 1x + ABS, IDX, NOA, NOA, ZPG, ZPG, ZPG, NOA, IMP, IMM, ACC, NOA, ABS, ABS, ABS, NOA, // 2x + REL, IDY, NOA, NOA, NOA, ZPX, ZPX, NOA, IMP, ABY, NOA, NOA, NOA, ABX, ABX, NOA, // 3x + IMP, IDX, NOA, NOA, NOA, ZPG, ZPG, NOA, IMP, IMM, ACC, NOA, ABS, ABS, ABS, NOA, // 4x + REL, IDY, NOA, NOA, NOA, ZPX, ZPX, NOA, IMP, ABY, NOA, NOA, NOA, ABX, ABX, NOA, // 5x + IMP, IDX, NOA, NOA, NOA, ZPG, ZPG, NOA, IMP, IMM, ACC, NOA, IND, ABS, ABS, NOA, // 6x + REL, IDY, NOA, NOA, NOA, ZPX, ZPX, NOA, IMP, ABY, NOA, NOA, NOA, ABX, ABX, NOA, // 7x + NOA, IDX, NOA, NOA, ZPG, ZPG, ZPG, NOA, IMP, NOA, IMP, NOA, ABS, ABS, ABS, NOA, // 8x + REL, IDY, NOA, NOA, ZPX, ZPX, ZPY, NOA, IMP, ABY, IMP, NOA, NOA, ABX, NOA, NOA, // 9x + IMM, IDX, IMM, NOA, ZPG, ZPG, ZPG, NOA, IMP, IMM, IMP, NOA, ABS, ABS, ABS, NOA, // Ax + REL, IDY, NOA, NOA, ZPX, ZPX, ZPY, NOA, IMP, ABY, IMP, NOA, ABX, ABX, ABY, NOA, // Bx + IMM, IDX, NOA, NOA, ZPG, ZPG, ZPG, NOA, IMP, IMM, IMP, NOA, ABS, ABS, ABS, NOA, // Cx + REL, IDY, NOA, NOA, NOA, ZPX, ZPX, NOA, IMP, ABY, NOA, NOA, NOA, ABX, ABX, NOA, // Dx + IMM, IDX, NOA, NOA, ZPG, ZPG, ZPG, NOA, IMP, IMM, IMP, NOA, ABS, ABS, ABS, NOA, // Ex + REL, IDY, NOA, NOA, NOA, ZPX, ZPX, NOA, IMP, ABY, NOA, NOA, NOA, ABX, ABX, NOA, // Fx +}; + +/* + * This is a _kind_ of factory method, except we're obviously not + * instantiating an object. Given an address mode, we return the + * resolver function which will give you the right value (for a given + * cpu) that an instruction will use. + */ +mos6502_address_resolver +mos6502_get_address_resolver(int addr_mode) +{ + switch (addr_mode) { + case ACC: return mos6502_resolve_acc; + case ABS: return mos6502_resolve_abs; + case ABX: return mos6502_resolve_abx; + case ABY: return mos6502_resolve_aby; + case IMM: return mos6502_resolve_imm; + case IND: return mos6502_resolve_ind; + case IDX: return mos6502_resolve_idx; + case IDY: return mos6502_resolve_idy; + case REL: return mos6502_resolve_rel; + case ZPG: return mos6502_resolve_zpg; + case ZPX: return mos6502_resolve_zpx; + case ZPY: return mos6502_resolve_zpy; + case IMP: // FALLTHRU + default: break; + } + + return NULL; +} + +/* + * Just a little macro to help us figure out what the address is for + * for 16-bit values + */ +#define ADDR_HILO(cpu) \ + vm_16bit addr; \ + vm_8bit hi, lo; \ + hi = mos6502_next_byte(cpu); \ + lo = mos6502_next_byte(cpu); \ + addr = (hi << 8) | lo + +/* + * In contrast to the ADDR_HILO macro, here we want just one byte from + * the current program counter, and it is the (only) significant byte. + */ +#define ADDR_LO(cpu) \ + vm_16bit addr; \ + addr = mos6502_next_byte(cpu) + +#define EFF_ADDR(addr) \ + vm_16bit eff_addr = addr; \ + cpu->last_addr = eff_addr + +/* + * In the ACC address mode, the instruction will consider just the A + * register. (It's probably the simplest resolution mode for us to + * execute.) + */ +vm_8bit +mos6502_resolve_acc(mos6502 *cpu) +{ + EFF_ADDR(0); + return cpu->A; +} + +/* + * This is the absolute address mode. The next two bytes are the address + * in memory at which our looked-for value resides, so we consume those + * bytes and return the value located therein. + */ +vm_8bit +mos6502_resolve_abs(mos6502 *cpu) +{ + ADDR_HILO(cpu); + EFF_ADDR(addr); + return vm_segment_get(cpu->memory, addr); +} + +/* + * The absolute x-indexed address mode is a slight modification of the + * absolute mode. Here, we consume two bytes, but add the X register + * value to what we read -- plus one if we have the carry bit set. This + * is a mode you would use if you were scanning a table, for instance. + */ +vm_8bit +mos6502_resolve_abx(mos6502 *cpu) +{ + ADDR_HILO(cpu); + CARRY_BIT(); + EFF_ADDR(addr + cpu->X + carry); + + return vm_segment_get(cpu->memory, eff_addr); +} + +/* + * Very much the mirror opposite of the ABX address mode; the only + * difference is we use the Y register, not the X. + */ +vm_8bit +mos6502_resolve_aby(mos6502 *cpu) +{ + ADDR_HILO(cpu); + CARRY_BIT(); + EFF_ADDR(addr + cpu->Y + carry); + + return vm_segment_get(cpu->memory, eff_addr); +} + +/* + * In immediate mode, the very next byte is the literal value to be used + * in the instruction. This is a mode you would use if, for instance, + * you wanted to say "foo + 5"; 5 would be the operand we return from + * here. + */ +vm_8bit +mos6502_resolve_imm(mos6502 *cpu) +{ + EFF_ADDR(0); + return mos6502_next_byte(cpu); +} + +/* + * In indirect mode, we presume that the next two bytes are an address + * at which _another_ pointer can be found. So we dereference these next + * two bytes, then dereference the two bytes found at that point, and + * _that_ is what our value will be. + */ +vm_8bit +mos6502_resolve_ind(mos6502 *cpu) +{ + vm_8bit ind_hi, ind_lo; + + ADDR_HILO(cpu); + + ind_hi = vm_segment_get(cpu->memory, addr); + ind_lo = vm_segment_get(cpu->memory, addr + 1); + EFF_ADDR((ind_hi << 8) | ind_lo); + + return vm_segment_get(cpu->memory, eff_addr); +} + +/* + * The indirect x-indexed address mode, as well as the y-indexed mode, + * are a bit complicated. The single, next byte we read is a zero-page + * address to the base of _another_ zero-page address in memory; we add + * X to it, which is the address of what we next dereference. Carry does + * not factor into the arithmetic. + */ +vm_8bit +mos6502_resolve_idx(mos6502 *cpu) +{ + ADDR_LO(cpu); + EFF_ADDR(addr + cpu->X); + + return vm_segment_get( + cpu->memory, + vm_segment_get(cpu->memory, eff_addr)); +} + +/* + * In significant contrast, the y-indexed indirect mode will read a + * zero-page address from the next byte, and dereference it immediately. + * The ensuing address will then have Y added to it, and then + * dereferenced for the final time. Carry _is_ factored in here. + */ +vm_8bit +mos6502_resolve_idy(mos6502 *cpu) +{ + ADDR_LO(cpu); + CARRY_BIT(); + EFF_ADDR(vm_segment_get(cpu->memory, addr) + cpu->Y + carry); + + return vm_segment_get(cpu->memory, eff_addr); +} + +/* + * The relative mode means we want to return an address in + * memory which is relative to PC. If bit 7 is 1, which + * means if addr > 127, then we treat the operand as though it + * were negative. + */ +vm_8bit +mos6502_resolve_rel(mos6502 *cpu) +{ + vm_16bit orig_pc; + + orig_pc = cpu->PC; + ADDR_LO(cpu); + + if (addr > 127) { + // e.g. if lo == 128, then cpu->PC + 127 - lo is the + // same as subtracting 1 from PC. + EFF_ADDR(orig_pc + 127 - addr); + return 0; + } + + // Otherwise lo is a positive offset from PC + EFF_ADDR(orig_pc + addr); + return 0; +} + +/* + * Zero page mode is very straightforward. It's very much the same as + * absolute mode, except we consider just the next byte, and dereference + * that (which is, by convention, always going to be an address in the + * zero page of memory). + */ +vm_8bit +mos6502_resolve_zpg(mos6502 *cpu) +{ + ADDR_LO(cpu); + EFF_ADDR(addr); + + return vm_segment_get(cpu->memory, eff_addr); +} + +/* + * In zero-page x-indexed mode, we read the next byte; add X to that; + * and dereference the result. Carry is not a factor here. + */ +vm_8bit +mos6502_resolve_zpx(mos6502 *cpu) +{ + ADDR_LO(cpu); + EFF_ADDR(addr + cpu->X); + + return vm_segment_get(cpu->memory, eff_addr); +} + +/* + * This is, as with absolute y-indexed mode, the mirror opposite of the + * zero-page x-indexed mode. We simply use the Y register and not the X, + * and here as well, we do not factor in the carry bit. + */ +vm_8bit +mos6502_resolve_zpy(mos6502 *cpu) +{ + ADDR_LO(cpu); + EFF_ADDR(addr + cpu->Y); + + return vm_segment_get(cpu->memory, eff_addr); +} diff --git a/src/mos6502.arith.c b/src/mos6502.arith.c new file mode 100644 index 0000000..83693a7 --- /dev/null +++ b/src/mos6502.arith.c @@ -0,0 +1,67 @@ +/* + * mos6502.inst.c + */ + +#include "mos6502.h" +#include "mos6502.enums.h" + +DEFINE_INST(adc) +{ + CARRY_BIT(); + cpu->A += oper + carry; +} + +DEFINE_INST(cmp) +{ + mos6502_modify_status(cpu, ZERO | NEGATIVE | CARRY, cpu->A - oper); +} + +DEFINE_INST(cpx) +{ + mos6502_modify_status(cpu, ZERO | NEGATIVE | CARRY, cpu->X - oper); +} + +DEFINE_INST(cpy) +{ + mos6502_modify_status(cpu, ZERO | NEGATIVE | CARRY, cpu->Y - oper); +} + +DEFINE_INST(dec) +{ + if (cpu->last_addr) { + vm_segment_set(cpu->memory, cpu->last_addr, oper - 1); + } +} + +DEFINE_INST(dex) +{ + cpu->X--; +} + +DEFINE_INST(dey) +{ + cpu->Y--; +} + +DEFINE_INST(inc) +{ + if (cpu->last_addr) { + vm_segment_set(cpu->memory, cpu->last_addr, oper + 1); + } +} + +DEFINE_INST(inx) +{ + cpu->X++; +} + +DEFINE_INST(iny) +{ + cpu->Y++; +} + +DEFINE_INST(sbc) +{ + CARRY_BIT(); + cpu->A -= oper - carry; +} diff --git a/src/mos6502.bits.c b/src/mos6502.bits.c new file mode 100644 index 0000000..8f9fbce --- /dev/null +++ b/src/mos6502.bits.c @@ -0,0 +1,110 @@ +/* + * mos6502.bits.c + */ + +#include "mos6502.h" +#include "mos6502.enums.h" + +DEFINE_INST(and) +{ + cpu->A &= oper; +} + +DEFINE_INST(asl) +{ + oper <<= 1; + + if (oper & 0x80) { + cpu->P |= CARRY; + } + + if (cpu->last_addr) { + vm_segment_set(cpu->memory, cpu->last_addr, oper); + } else { + cpu->A = oper; + } +} + +DEFINE_INST(bit) +{ + if (oper & NEGATIVE) { + cpu->P |= NEGATIVE; + } + + if (oper & OVERFLOW) { + cpu->P |= OVERFLOW; + } + + if (oper & cpu->A) { + cpu->P |= ZERO; + } else { + cpu->P &= ~ZERO; + } +} + +DEFINE_INST(eor) +{ + cpu->A ^= oper; +} + +DEFINE_INST(lsr) +{ + oper >>= 1; + + if (oper & 0x01) { + cpu->P |= CARRY; + } + + if (cpu->last_addr) { + vm_segment_set(cpu->memory, cpu->last_addr, oper); + } else { + cpu->A = oper; + } +} + +DEFINE_INST(ora) +{ + cpu->A |= oper; +} + +DEFINE_INST(rol) +{ + CARRY_BIT(); + + if (oper & 0x80) { + carry = 1; + } + + oper <<= 1; + + if (carry) { + oper |= 0x01; + } + + if (cpu->last_addr) { + vm_segment_set(cpu->memory, cpu->last_addr, oper); + } else { + cpu->A = oper; + } +} + +DEFINE_INST(ror) +{ + CARRY_BIT(); + + if (oper & 0x01) { + carry = 1; + } + + oper >>= 1; + + if (carry) { + oper |= 0x80; + } + + if (cpu->last_addr) { + vm_segment_set(cpu->memory, cpu->last_addr, oper); + } else { + cpu->A = oper; + } +} diff --git a/src/mos6502.branch.c b/src/mos6502.branch.c new file mode 100644 index 0000000..e67db60 --- /dev/null +++ b/src/mos6502.branch.c @@ -0,0 +1,49 @@ +/* + * mos6502.branch.c + */ + +#include "mos6502.h" +#include "mos6502.enums.h" + +#define JUMP_IF(cond) \ + if (cond) cpu->PC = cpu->last_addr + +DEFINE_INST(bcc) +{ + JUMP_IF(~cpu->P & CARRY); +} + +DEFINE_INST(bcs) +{ + JUMP_IF(cpu->P & CARRY); +} + +DEFINE_INST(beq) +{ + JUMP_IF(cpu->P & ZERO); +} + +DEFINE_INST(bmi) +{ + JUMP_IF(cpu->P & NEGATIVE); +} + +DEFINE_INST(bne) +{ + JUMP_IF(~cpu->P & ZERO); +} + +DEFINE_INST(bpl) +{ + JUMP_IF(~cpu->P & NEGATIVE); +} + +DEFINE_INST(bvc) +{ + JUMP_IF(~cpu->P & OVERFLOW); +} + +DEFINE_INST(bvs) +{ + JUMP_IF(cpu->P & OVERFLOW); +} diff --git a/src/mos6502.c b/src/mos6502.c new file mode 100644 index 0000000..faa60f5 --- /dev/null +++ b/src/mos6502.c @@ -0,0 +1,157 @@ +/* + * Ideas: + * + * The mos6502 code would _just_ emulate said chip. It would not be a + * technical part of the computer, and it would in other words be + * decoupled from the notion of a commadore, apple ii, etc. + * + * What you need to do in order to emulate the chip is be able to know + * about _memory_, and know about _registers_. Things like disk drives, + * screens, etc. are sort of beyond its knowledge. But memory and + * registers must be _local_ to the chip's workings; it must be able to + * directly modify those, as well as share memory/registers/etc. with + * other parts of a platform. + * + * Observations: + * - there can only be one chip at a given time; therefore we can get + * away with some kind of singleton to represent the chip + * - registers and memory need to be available to the chip, but the + * chip should not know about the larger platform; we should have + * pointers to all of that in the chip structure + */ + +#include + +#include "log.h" +#include "mos6502.h" + +// All of our address modes, instructions, etc. are defined here. +#include "mos6502.enums.h" + +static int instructions[] = { +// 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F + BRK, ORA, NOP, NOP, NOP, ORA, ASL, NOP, PHP, ORA, ASL, NOP, NOP, ORA, ASL, NOP, // 0x + BPL, ORA, NOP, NOP, NOP, ORA, ASL, NOP, CLC, ORA, NOP, NOP, NOP, ORA, ASL, NOP, // 1x + JSR, AND, NOP, NOP, BIT, AND, ROL, NOP, PLP, AND, ROL, NOP, BIT, AND, ROL, NOP, // 2x + BMI, AND, NOP, NOP, NOP, AND, ROL, NOP, SEC, AND, NOP, NOP, NOP, AND, ROL, NOP, // 3x + RTI, EOR, NOP, NOP, NOP, EOR, LSR, NOP, PHA, ADC, LSR, NOP, JMP, EOR, LSR, NOP, // 4x + BVC, EOR, NOP, NOP, NOP, EOR, LSR, NOP, CLI, EOR, NOP, NOP, NOP, EOR, LSR, NOP, // 5x + RTS, ADC, NOP, NOP, NOP, ADC, ROR, NOP, PLA, ADC, ROR, NOP, JMP, ADC, ROR, NOP, // 6x + BVS, ADC, NOP, NOP, NOP, ADC, ROR, NOP, SEI, ADC, NOP, NOP, NOP, ADC, ROR, NOP, // 7x + NOP, STA, NOP, NOP, STY, STA, STX, NOP, DEY, NOP, TXA, NOP, STY, STA, STX, NOP, // 8x + BCC, STA, NOP, NOP, STY, STA, STX, NOP, TYA, STA, TXS, NOP, NOP, STA, NOP, NOP, // 9x + LDY, LDA, LDX, NOP, LDY, LDA, LDX, NOP, TAY, LDA, TAX, NOP, LDY, LDA, LDX, NOP, // Ax + BCS, LDA, NOP, NOP, LDY, LDA, LDX, NOP, CLV, LDA, TSX, NOP, LDY, LDA, LDX, NOP, // Bx + CPY, CMP, NOP, NOP, CPY, CMP, DEC, NOP, INY, CMP, DEX, NOP, CPY, CMP, DEC, NOP, // Cx + BNE, CMP, NOP, NOP, NOP, CMP, DEC, NOP, CLD, CMP, NOP, NOP, NOP, CMP, DEC, NOP, // Dx + CPX, SBC, NOP, NOP, CPX, SBC, INC, NOP, INX, SBC, NOP, NOP, CPX, SBC, INC, NOP, // Ex + BEQ, SBC, NOP, NOP, NOP, SBC, INC, NOP, SED, SBC, NOP, NOP, NOP, SBC, INC, NOP, // Fx +}; + +/* + * Build a new mos6502 struct object, and also build the memory contents + * used therein. All registers should be zeroed out. + */ +mos6502 * +mos6502_create() +{ + mos6502 *cpu; + + cpu = malloc(sizeof(mos6502)); + if (cpu == NULL) { + log_critical("Not enough memory to allocate mos6502"); + exit(1); + } + + cpu->memory = vm_segment_create(MOS6502_MEMSIZE); + + cpu->PC = 0; + cpu->A = 0; + cpu->X = 0; + cpu->Y = 0; + cpu->P = 0; + cpu->S = 0; + + return cpu; +} + +/* + * Free the memory consumed by the mos6502 struct. + */ +void +mos6502_free(mos6502 *cpu) +{ + vm_segment_free(cpu->memory); + free(cpu); +} + +/* + * Return the next byte from the PC register position, and increment the + * PC register. + */ +vm_8bit +mos6502_next_byte(mos6502 *cpu) +{ + vm_8bit byte; + + byte = vm_segment_get(cpu->memory, cpu->PC); + cpu->PC++; + + return byte; +} + +void +mos6502_push_stack(mos6502 *cpu, vm_16bit addr) +{ + // First we need to set the hi byte, by shifting the address right 8 + // positions and using the base offset of the S register. + vm_segment_set(cpu->memory, 0x0100 + cpu->S, addr >> 8); + + // Next we must record the lo byte, this time by using a bitmask to + // capture just the low end of addr, but recording it in S + 1. + vm_segment_set(cpu->memory, 0x0100 + cpu->S + 1, addr & 0xFF); + + // And finally we need to increment S by 2 (since we've used two + // bytes in the stack). + cpu->S += 2; +} + +vm_16bit +mos6502_pop_stack(mos6502 *cpu) +{ + // The first thing we want to do here is to decrement S by 2, since + // the value we want to return is two positions back. + cpu->S -= 2; + + // We need to use a bitwise-or operation to combine the hi and lo + // bytes we retrieve from the stack into the actual position we + // would use for the PC register. + return + (vm_segment_get(cpu->memory, 0x0100 + cpu->S) << 8) | + vm_segment_get(cpu->memory, 0x0100 + cpu->S + 1); +} + +void +mos6502_modify_status(mos6502 *cpu, int statuses, vm_8bit oper) +{ + if (statuses & NEGATIVE) { + cpu->P &= ~NEGATIVE; + if (oper & 0x80) { + cpu->P |= NEGATIVE; + } + } + + if (statuses & ZERO) { + cpu->P &= ~ZERO; + if (oper == 0) { + cpu->P |= ZERO; + } + } + + if (statuses & CARRY) { + cpu->P &= ~CARRY; + if (oper > 0) { + cpu->P |= CARRY; + } + } +} diff --git a/src/mos6502.exec.c b/src/mos6502.exec.c new file mode 100644 index 0000000..6419d40 --- /dev/null +++ b/src/mos6502.exec.c @@ -0,0 +1,39 @@ +/* + * mos6502.exec.c + */ + +#include "mos6502.h" +#include "mos6502.enums.h" + +DEFINE_INST(brk) +{ + cpu->P |= INTERRUPT; + mos6502_push_stack(cpu, cpu->PC); + cpu->PC += 2; +} + +DEFINE_INST(jmp) +{ + cpu->PC = cpu->last_addr; +} + +DEFINE_INST(jsr) +{ + mos6502_push_stack(cpu, cpu->PC + 2); + cpu->PC = cpu->last_addr; +} + +DEFINE_INST(nop) +{ + // do nothing +} + +DEFINE_INST(rti) +{ + cpu->PC = mos6502_pop_stack(cpu); +} + +DEFINE_INST(rts) +{ + cpu->PC = mos6502_pop_stack(cpu); +} diff --git a/src/mos6502.loadstor.c b/src/mos6502.loadstor.c new file mode 100644 index 0000000..f7e5671 --- /dev/null +++ b/src/mos6502.loadstor.c @@ -0,0 +1,95 @@ +/* + * mos6502.loadstor.c + */ + +#include "mos6502.h" +#include "mos6502.enums.h" + +DEFINE_INST(lda) +{ + mos6502_modify_status(cpu, ZERO | NEGATIVE, oper); + cpu->A = oper; +} + +DEFINE_INST(ldx) +{ + mos6502_modify_status(cpu, ZERO | NEGATIVE, oper); + cpu->X = oper; +} + +DEFINE_INST(ldy) +{ + mos6502_modify_status(cpu, ZERO | NEGATIVE, oper); + cpu->Y = oper; +} + +DEFINE_INST(pha) +{ + mos6502_push_stack(cpu, cpu->A); +} + +DEFINE_INST(php) +{ + mos6502_push_stack(cpu, cpu->P); +} + +DEFINE_INST(pla) +{ + cpu->A = mos6502_pop_stack(cpu); +} + +DEFINE_INST(plp) +{ + cpu->P = mos6502_pop_stack(cpu); +} + +DEFINE_INST(sta) +{ + vm_segment_set(cpu->memory, cpu->last_addr, cpu->A); +} + +DEFINE_INST(stx) +{ + vm_segment_set(cpu->memory, cpu->last_addr, cpu->X); +} + +DEFINE_INST(sty) +{ + vm_segment_set(cpu->memory, cpu->last_addr, cpu->Y); +} + +DEFINE_INST(tax) +{ + mos6502_modify_status(cpu, ZERO | NEGATIVE, cpu->A); + cpu->X = cpu->A; +} + +DEFINE_INST(tay) +{ + mos6502_modify_status(cpu, ZERO | NEGATIVE, cpu->A); + cpu->Y = cpu->A; +} + +DEFINE_INST(tsx) +{ + mos6502_modify_status(cpu, ZERO | NEGATIVE, cpu->S); + cpu->X = cpu->S; +} + +DEFINE_INST(txa) +{ + mos6502_modify_status(cpu, ZERO | NEGATIVE, cpu->X); + cpu->A = cpu->X; +} + +DEFINE_INST(txs) +{ + mos6502_modify_status(cpu, ZERO | NEGATIVE, cpu->X); + cpu->S = cpu->X; +} + +DEFINE_INST(tya) +{ + mos6502_modify_status(cpu, ZERO | NEGATIVE, cpu->Y); + cpu->A = cpu->Y; +} diff --git a/src/mos6502.stat.c b/src/mos6502.stat.c new file mode 100644 index 0000000..a41ebe9 --- /dev/null +++ b/src/mos6502.stat.c @@ -0,0 +1,41 @@ +/* + * mos6502.stat.c + */ + +#include "mos6502.h" +#include "mos6502.enums.h" + +DEFINE_INST(clc) +{ + cpu->P &= ~CARRY; +} + +DEFINE_INST(cld) +{ + cpu->P &= ~DECIMAL; +} + +DEFINE_INST(cli) +{ + cpu->P &= ~INTERRUPT; +} + +DEFINE_INST(clv) +{ + cpu->P &= ~OVERFLOW; +} + +DEFINE_INST(sec) +{ + cpu->P |= CARRY; +} + +DEFINE_INST(sed) +{ + cpu->P |= DECIMAL; +} + +DEFINE_INST(sei) +{ + cpu->P |= INTERRUPT; +} diff --git a/src/vm_screen.c b/src/vm_screen.c index 8186e70..81998bf 100644 --- a/src/vm_screen.c +++ b/src/vm_screen.c @@ -18,6 +18,12 @@ vm_screen_new_context() return context; } +void +vm_screen_free_context(vm_screen_context *context) +{ + free(context); +} + void vm_screen_set_color(vm_screen_context *context, int red, diff --git a/src/vm_segment.c b/src/vm_segment.c index e6c78a7..0f147f3 100644 --- a/src/vm_segment.c +++ b/src/vm_segment.c @@ -1,43 +1,76 @@ +/* + * vm_segment.c + * memory segments for our virtual machine + * + * Memory segments can be used for almost any kind of storage we can + * imagine. The most obvious use would be for system memory, but others + * would include physical disk media (floppy disks, hard drives). + * + * You may note that we assume memory segments are organized into 8bit + * values (the `vm_8bit` type). It's certainly possible to create memory + * segments which are organized into arbitrary boundaries; 2, 3, 4 + * bytes, you know, go nuts! + * + * To do so, however, we would be adding a fair bit of complexity, and + * (at the moment of this writing at least) I am not convinced there is + * a good use-case for doing so. Your bog-standard computer of _today_ + * is still using memory organized into bytes. Your hard drive is also + * using bytes. Etc. + */ + +#include #include #include #include "log.h" #include "vm_segment.h" -static vm_segment *seg_table[VM_SEGMENT_TABLE_MAX]; -static unsigned int seg_index = 0; - +/* + * Create a new segment, such that it contains a number of bytes indicated + * by `size`. + */ vm_segment * vm_segment_create(size_t size) { - vm_segment *seg; - - // Block us from attempting to allocate any memory beyond the - // maximum defined blocks. - if (seg_index >= VM_SEGMENT_TABLE_MAX) { - log_error("Attempted to allocate more segments than we allow"); - return NULL; - } + vm_segment *segment; // Allocate memory for the current memory segment. - seg = seg_table[seg_index] = - malloc(sizeof(vm_segment) * size); + segment = malloc(sizeof(vm_segment)); // Ack! We couldn't get the memory we wanted. Let's bail. - if (seg == NULL) { + if (segment == NULL) { log_critical("Couldn't allocate enough space for vm_segment"); return NULL; } - // We want to increment the current segment index only after a - // _successful_ allocation. - seg_index++; + segment->memory = malloc(sizeof(vm_8bit) * size); + if (segment->memory == NULL) { + log_critical("Couldn't allocate enough space for vm_segment"); + return NULL; + } - return seg; + segment->size = size; + + return segment; } +/* + * Free the memory consumed by a given segment. + */ void -vm_segment_set(vm_segment *segment, size_t index, vm_segment_byte value) +vm_segment_free(vm_segment *segment) +{ + free(segment->memory); + free(segment); +} + +/* + * Set the byte in `segment`, at `index`, to the given `value`. Our + * bounds-checking here will _crash_ the program if we are + * out-of-bounds. + */ +void +vm_segment_set(vm_segment *segment, size_t index, vm_8bit value) { // Some bounds checking. if (!vm_segment_bounds_check(segment, index)) { @@ -55,7 +88,12 @@ vm_segment_set(vm_segment *segment, size_t index, vm_segment_byte value) segment->memory[index] = value; } -vm_segment_byte +/* + * Return the byte in `segment` at the given `index` point. Our + * bounds-checking will _crash_ the program if an index is requested out + * of bounds. + */ +vm_8bit vm_segment_get(vm_segment *segment, size_t index) { if (!vm_segment_bounds_check(segment, index)) { @@ -71,11 +109,15 @@ vm_segment_get(vm_segment *segment, size_t index) return segment->memory[index]; } +/* + * Copy a set of bytes from `src` (at `src_index`) to `dest` (at + * `dest_index`), such that the range is `length` bytes long. + */ void -vm_segment_copy(vm_segment *src, - vm_segment *dest, - size_t src_index, +vm_segment_copy(vm_segment *dest, + vm_segment *src, size_t dest_index, + size_t src_index, size_t length) { if (src_index + length >= src->size) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ebc6d5d..18f6f1c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -2,6 +2,8 @@ cmake_minimum_required(VERSION 3.9) project(emp-test) +set(CMAKE_BUILD_TYPE Debug) + include_directories(../include /usr/local/include) link_directories(/usr/local/lib) @@ -13,6 +15,11 @@ foreach(src ${emp_sources}) list(APPEND sources ${relsrc}) endforeach(src) -add_executable(emp-test ${sources} ./main.c) +# test_sources should also include the main source file, so we don't need to +# make any particular effort to include it in add_executable(). +file(GLOB test_sources "*.c") +add_executable(emp-test ${sources} ${test_sources}) + +# Our unit-testing library target_link_libraries(emp-test criterion) diff --git a/tests/log.c b/tests/log.c new file mode 100644 index 0000000..af68e62 --- /dev/null +++ b/tests/log.c @@ -0,0 +1,33 @@ +#include +#include +#include +#include + +#include "log.h" + +Test(log, write) { + char message[] = "we write the logs"; + char message_buffer[128]; + FILE *fp; + int message_length; + + message_length = strlen(message); + + log_open(); + log_write(0, message); + log_close(); + + fp = fopen(LOG_FILENAME, "r"); + cr_assert_neq(fp, NULL, "Unable to open " LOG_FILENAME); + + fread(message_buffer, sizeof(char), sizeof(message), fp); + message_buffer[message_length] = '\0'; + cr_assert_str_eq(message_buffer, + message, + "log_write() did not write correct data ([%s], [%s])", + message_buffer, + message); + + fclose(fp); + unlink(LOG_FILENAME); +} diff --git a/tests/main.c b/tests/main.c deleted file mode 100644 index c73e51d..0000000 --- a/tests/main.c +++ /dev/null @@ -1,5 +0,0 @@ -#include - -Test(simple, test) { - cr_assert(0, "Hello!"); -} diff --git a/tests/mos6502.addr.c b/tests/mos6502.addr.c new file mode 100644 index 0000000..b95dc9d --- /dev/null +++ b/tests/mos6502.addr.c @@ -0,0 +1,201 @@ +#include + +#include "mos6502.h" +#include "mos6502.enums.h" + +Test(mos6502, get_address_resolver) { + INIT_ADDR_MODE(); + + cr_assert_eq(mos6502_get_address_resolver(ACC), mos6502_resolve_acc); + cr_assert_eq(mos6502_get_address_resolver(ABS), mos6502_resolve_abs); + cr_assert_eq(mos6502_get_address_resolver(ABX), mos6502_resolve_abx); + cr_assert_eq(mos6502_get_address_resolver(ABY), mos6502_resolve_aby); + cr_assert_eq(mos6502_get_address_resolver(IMM), mos6502_resolve_imm); + cr_assert_eq(mos6502_get_address_resolver(IND), mos6502_resolve_ind); + cr_assert_eq(mos6502_get_address_resolver(IDX), mos6502_resolve_idx); + cr_assert_eq(mos6502_get_address_resolver(IDY), mos6502_resolve_idy); + cr_assert_eq(mos6502_get_address_resolver(REL), mos6502_resolve_rel); + cr_assert_eq(mos6502_get_address_resolver(ZPG), mos6502_resolve_zpg); + cr_assert_eq(mos6502_get_address_resolver(ZPX), mos6502_resolve_zpx); + cr_assert_eq(mos6502_get_address_resolver(ZPY), mos6502_resolve_zpy); + + // Trick question: implied mode doesn't require an operand, so there + // should be no possible resolution with it. + cr_assert_eq(mos6502_get_address_resolver(IMP), NULL); + + END_ADDR_MODE(); +} + +Test(mos6502, addr_mode_acc) { + INIT_ADDR_MODE(); + + cpu->A = 123; + cr_assert_eq(mos6502_resolve_acc(cpu), 123); + + END_ADDR_MODE(); +} + +Test(mos6502, addr_mode_abs) { + INIT_ADDR_MODE(); + + vm_segment_set(cpu->memory, 0x1234, 111); + SET_PC_BYTE(cpu, 0, 0x12); + SET_PC_BYTE(cpu, 1, 0x34); + cr_assert_eq(mos6502_resolve_abs(cpu), 111); + + END_ADDR_MODE(); +} + +Test(mos6502, addr_mode_abx_carry0) { + INIT_ADDR_MODE(); + + vm_segment_set(cpu->memory, 0x1234, 111); + SET_PC_BYTE(cpu, 0, 0x12); + SET_PC_BYTE(cpu, 1, 0x30); + cpu->X = 4; + cr_assert_eq(mos6502_resolve_abx(cpu), 111); + + END_ADDR_MODE(); +} + +Test(mos6502, addr_mode_abx_carry1) { + INIT_ADDR_MODE(); + + vm_segment_set(cpu->memory, 0x1234, 111); + SET_PC_BYTE(cpu, 0, 0x12); + SET_PC_BYTE(cpu, 1, 0x30); + cpu->X = 3; + cpu->P = cpu->P | CARRY; + cr_assert_eq(mos6502_resolve_abx(cpu), 111); + + END_ADDR_MODE(); +} + +Test(mos6502, addr_mode_aby_carry0) { + INIT_ADDR_MODE(); + + vm_segment_set(cpu->memory, 0x1234, 111); + SET_PC_BYTE(cpu, 0, 0x12); + SET_PC_BYTE(cpu, 1, 0x30); + cpu->Y = 4; + cr_assert_eq(mos6502_resolve_aby(cpu), 111); + + END_ADDR_MODE(); +} + +Test(mos6502, addr_mode_aby_carry1) { + INIT_ADDR_MODE(); + + vm_segment_set(cpu->memory, 0x1234, 111); + SET_PC_BYTE(cpu, 0, 0x12); + SET_PC_BYTE(cpu, 1, 0x30); + cpu->Y = 3; + cpu->P = cpu->P | CARRY; + cr_assert_eq(mos6502_resolve_aby(cpu), 111); + + END_ADDR_MODE(); +} + +Test(mos6502, addr_mode_imm) { + INIT_ADDR_MODE(); + + SET_PC_BYTE(cpu, 0, 0x12); + cr_assert_eq(mos6502_resolve_imm(cpu), 0x12); + + END_ADDR_MODE(); +} + +Test(mos6502, addr_mode_idx) { + INIT_ADDR_MODE(); + + vm_segment_set(cpu->memory, 0x17, 0x23); + vm_segment_set(cpu->memory, 0x23, 123); + + SET_PC_BYTE(cpu, 0, 0x12); + cpu->X = 5; + cr_assert_eq(mos6502_resolve_idx(cpu), 123); + + END_ADDR_MODE(); +} + +Test(mos6502, addr_mode_idy) { + INIT_ADDR_MODE(); + + vm_segment_set(cpu->memory, 0x12, 0x23); + vm_segment_set(cpu->memory, 0x28, 123); + + SET_PC_BYTE(cpu, 0, 0x12); + cpu->Y = 5; + cr_assert_eq(mos6502_resolve_idy(cpu), 123); + + END_ADDR_MODE(); +} + +Test(mos6502, addr_mode_ind) { + INIT_ADDR_MODE(); + + vm_segment_set(cpu->memory, 0x1234, 0x23); + vm_segment_set(cpu->memory, 0x1235, 0x45); + vm_segment_set(cpu->memory, 0x2345, 123); + + SET_PC_BYTE(cpu, 0, 0x12); + SET_PC_BYTE(cpu, 1, 0x34); + cr_assert_eq(mos6502_resolve_ind(cpu), 123); + + END_ADDR_MODE(); +} + +Test(mos6502, addr_mode_rel_positive) { + INIT_ADDR_MODE(); + + cpu->PC = 123; + SET_PC_BYTE(cpu, 0, 88); + cr_assert_eq(mos6502_resolve_rel(cpu), 0); + cr_assert_eq(cpu->last_addr, 211); + + END_ADDR_MODE(); +} + +Test(mos6502, addr_mode_rel_negative) { + INIT_ADDR_MODE(); + + cpu->PC = 123; + SET_PC_BYTE(cpu, 0, 216); + cr_assert_eq(mos6502_resolve_rel(cpu), 0); + cr_assert_eq(cpu->last_addr, 34); + + END_ADDR_MODE(); +} + +Test(mos6502, addr_mode_zpg) { + INIT_ADDR_MODE(); + + vm_segment_set(cpu->memory, 0x0034, 222); + SET_PC_BYTE(cpu, 0, 0x34); + cr_assert_eq(mos6502_resolve_zpg(cpu), 222); + + END_ADDR_MODE(); +} + +Test(mos6502, addr_mode_zpx) { + INIT_ADDR_MODE(); + + vm_segment_set(cpu->memory, 0x0034, 222); + SET_PC_BYTE(cpu, 0, 0x30); + cpu->X = 4; + cr_assert_eq(mos6502_resolve_zpx(cpu), 222); + + END_ADDR_MODE(); +} + +Test(mos6502, addr_mode_zpy) { + INIT_ADDR_MODE(); + + vm_segment_set(cpu->memory, 0x0034, 222); + SET_PC_BYTE(cpu, 0, 0x2F); + cpu->Y = 5; + cr_assert_eq(mos6502_resolve_zpy(cpu), 222); + + END_ADDR_MODE(); +} + diff --git a/tests/mos6502.c b/tests/mos6502.c new file mode 100644 index 0000000..f6f60f7 --- /dev/null +++ b/tests/mos6502.c @@ -0,0 +1,37 @@ +#include + +#include "mos6502.h" +#include "mos6502.enums.h" + +Test(mos6502, create) { + mos6502 *cpu; + + cpu = mos6502_create(); + cr_assert_neq(cpu, NULL); + + cr_assert_eq(cpu->memory->size, MOS6502_MEMSIZE); + + cr_assert_eq(cpu->PC, 0); + cr_assert_eq(cpu->A, 0); + cr_assert_eq(cpu->X, 0); + cr_assert_eq(cpu->Y, 0); + cr_assert_eq(cpu->P, 0); + cr_assert_eq(cpu->S, 0); + + mos6502_free(cpu); +} + +Test(mos6502, next_byte) { + INIT_ADDR_MODE(); + + cpu->PC = 128; + vm_segment_set(cpu->memory, cpu->PC, 123); + vm_segment_set(cpu->memory, cpu->PC + 1, 234); + vm_segment_set(cpu->memory, cpu->PC + 2, 12); + + cr_assert_eq(mos6502_next_byte(cpu), 123); + cr_assert_eq(mos6502_next_byte(cpu), 234); + cr_assert_eq(mos6502_next_byte(cpu), 12); + + END_ADDR_MODE(); +} diff --git a/tests/vm_screen.c b/tests/vm_screen.c new file mode 100644 index 0000000..eb96823 --- /dev/null +++ b/tests/vm_screen.c @@ -0,0 +1,39 @@ +#include + +#include "vm_screen.h" + +Test(vm_screen, new_context) { + vm_screen_context *context; + + context = vm_screen_new_context(); + cr_assert_neq(context, NULL); + + cr_assert_eq(context->color_red, 0); + cr_assert_eq(context->color_blue, 0); + cr_assert_eq(context->color_green, 0); + cr_assert_eq(context->color_alpha, 0); + + vm_screen_free_context(context); +} + +Test(vm_screen, set_color) { + vm_screen_context *context; + int red = 0xDE; + int green = 0xAD; + int blue = 0xBE; + int alpha = 0xEF; + + context = vm_screen_new_context(); + vm_screen_set_color(context, red, green, blue, alpha); + + cr_assert_eq(context->color_red, red); + cr_assert_eq(context->color_green, green); + cr_assert_eq(context->color_blue, blue); + cr_assert_eq(context->color_alpha, alpha); + + vm_screen_free_context(context); +} + +Test(vm_screen, draw_rect) { + // Nothing to do here... +} diff --git a/tests/vm_segment.c b/tests/vm_segment.c new file mode 100644 index 0000000..26d8ad4 --- /dev/null +++ b/tests/vm_segment.c @@ -0,0 +1,68 @@ +#include + +#include "vm_segment.h" + +Test(vm_segment, create) { + vm_segment *segment; + int length = 128; + + segment = vm_segment_create(length); + cr_assert_neq(segment, NULL); + + cr_assert_eq(segment->size, length); + + vm_segment_free(segment); +} + +Test(vm_segment, set) { + vm_segment *segment; + int length = 128; + int index = 0; + vm_8bit value = 123; + + segment = vm_segment_create(length); + cr_assert_neq(segment, NULL); + + vm_segment_set(segment, index, value); + + cr_assert_eq(segment->memory[index], value); + vm_segment_free(segment); +} + +Test(vm_segment, get) { + vm_segment *segment; + int length = 128; + int index = 0; + vm_8bit value = 123; + + segment = vm_segment_create(length); + cr_assert_neq(segment, NULL); + + segment->memory[index] = value; + cr_assert_eq(vm_segment_get(segment, index), value); + + vm_segment_free(segment); +} + +Test(vm_segment, copy) { + vm_segment *src, *dest; + int length = 128; + + src = vm_segment_create(length); + dest = vm_segment_create(length); + + vm_segment_set(src, 0, 0xDE); + vm_segment_set(src, 1, 0xAD); + vm_segment_set(src, 2, 0xBE); + vm_segment_set(src, 3, 0xEF); + + vm_segment_copy(dest, src, 8, 0, 4); + + cr_assert_eq(vm_segment_get(dest, 8), 0xDE); + cr_assert_eq(vm_segment_get(dest, 9), 0xAD); + cr_assert_eq(vm_segment_get(dest, 10), 0xBE); + cr_assert_eq(vm_segment_get(dest, 11), 0xEF); + + vm_segment_free(src); + vm_segment_free(dest); +}