1
0
mirror of https://github.com/pevans/erc-c.git synced 2024-12-10 11:49:16 +00:00

Adding all of the instruction files

This commit is contained in:
Peter Evans 2017-12-02 13:05:53 -06:00
parent 237d5e3cb6
commit 3cebed2377
25 changed files with 1602 additions and 37 deletions

View File

@ -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
View 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
View 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
View 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

View File

@ -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);

View File

@ -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)

View File

@ -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
)

View File

@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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;
}

View File

@ -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,

View File

@ -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++;
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) {

View File

@ -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
View 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);
}

View File

@ -1,5 +0,0 @@
#include <criterion/criterion.h>
Test(simple, test) {
cr_assert(0, "Hello!");
}

201
tests/mos6502.addr.c Normal file
View 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);