mirror of
https://github.com/pevans/erc-c.git
synced 2024-11-27 05:49:24 +00:00
Adding all of the instruction files
This commit is contained in:
parent
237d5e3cb6
commit
3cebed2377
@ -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();
|
||||
|
100
include/mos6502.enums.h
Normal file
100
include/mos6502.enums.h
Normal file
@ -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
|
183
include/mos6502.h
Normal file
183
include/mos6502.h
Normal file
@ -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
|
9
include/vm_bits.h
Normal file
9
include/vm_bits.h
Normal file
@ -0,0 +1,9 @@
|
||||
#ifndef _VM_BITS_H_
|
||||
#define _VM_BITS_H_
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef uint8_t vm_8bit;
|
||||
typedef uint16_t vm_16bit;
|
||||
|
||||
#endif
|
@ -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);
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -2,14 +2,16 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
}
|
||||
|
275
src/mos6502.addr.c
Normal file
275
src/mos6502.addr.c
Normal file
@ -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 <stdlib.h>
|
||||
|
||||
#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);
|
||||
}
|
67
src/mos6502.arith.c
Normal file
67
src/mos6502.arith.c
Normal file
@ -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;
|
||||
}
|
110
src/mos6502.bits.c
Normal file
110
src/mos6502.bits.c
Normal file
@ -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;
|
||||
}
|
||||
}
|
49
src/mos6502.branch.c
Normal file
49
src/mos6502.branch.c
Normal file
@ -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);
|
||||
}
|
157
src/mos6502.c
Normal file
157
src/mos6502.c
Normal file
@ -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 <stdlib.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
}
|
39
src/mos6502.exec.c
Normal file
39
src/mos6502.exec.c
Normal file
@ -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);
|
||||
}
|
95
src/mos6502.loadstor.c
Normal file
95
src/mos6502.loadstor.c
Normal file
@ -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;
|
||||
}
|
41
src/mos6502.stat.c
Normal file
41
src/mos6502.stat.c
Normal file
@ -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;
|
||||
}
|
@ -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,
|
||||
|
@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#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++;
|
||||
|
||||
return seg;
|
||||
segment->memory = malloc(sizeof(vm_8bit) * size);
|
||||
if (segment->memory == NULL) {
|
||||
log_critical("Couldn't allocate enough space for vm_segment");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -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)
|
||||
|
33
tests/log.c
Normal file
33
tests/log.c
Normal file
@ -0,0 +1,33 @@
|
||||
#include <criterion/criterion.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#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);
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
#include <criterion/criterion.h>
|
||||
|
||||
Test(simple, test) {
|
||||
cr_assert(0, "Hello!");
|
||||
}
|
201
tests/mos6502.addr.c
Normal file
201
tests/mos6502.addr.c
Normal file
@ -0,0 +1,201 @@
|
||||
#include <criterion/criterion.h>
|
||||
|
||||
#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();
|
||||
}
|
||||
|
37
tests/mos6502.c
Normal file
37
tests/mos6502.c
Normal file
@ -0,0 +1,37 @@
|
||||
#include <criterion/criterion.h>
|
||||
|
||||
#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();
|
||||
}
|
39
tests/vm_screen.c
Normal file
39
tests/vm_screen.c
Normal file
@ -0,0 +1,39 @@
|
||||
#include <criterion/criterion.h>
|
||||
|
||||
#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...
|
||||
}
|
68
tests/vm_segment.c
Normal file
68
tests/vm_segment.c
Normal file
@ -0,0 +1,68 @@
|
||||
#include <criterion/criterion.h>
|
||||
|
||||
#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);
|
||||
}
|
Loading…
Reference in New Issue
Block a user