Reorganizing files into directories

This commit is contained in:
Peter Evans 2018-04-06 20:27:47 -05:00
parent f9d130ba97
commit 74aafb0d01
43 changed files with 550 additions and 0 deletions

17
include/mos6502/dis.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef _MOS6502_DIS_H_
#define _MOS6502_DIS_H_
#include "mos6502/mos6502.h"
#include "vm_bits.h"
extern bool mos6502_dis_is_jump_label(int);
extern int mos6502_dis_expected_bytes(int);
extern int mos6502_dis_opcode(mos6502 *, FILE *, int);
extern void mos6502_dis_instruction(char *, int, int);
extern void mos6502_dis_jump_label(mos6502 *, vm_16bit, int, int);
extern void mos6502_dis_jump_unlabel(int);
extern void mos6502_dis_label(char *, int, int);
extern void mos6502_dis_operand(mos6502 *, char *, int, int, int, vm_16bit);
extern void mos6502_dis_scan(mos6502 *, FILE *, int, int);
#endif

140
include/mos6502/enums.h Normal file
View File

@ -0,0 +1,140 @@
/*
* 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.
*/
#ifndef _MOS6502_ENUMS_H_
#define _MOS6502_ENUMS_H_
/*
* This defines all of the flags that are possible within the status (P)
* register. Note that there is intentionally _no_ definition for the
* 6th bit.
*/
enum status_flags {
MOS_CARRY = 1,
MOS_ZERO = 2,
MOS_INTERRUPT = 4,
MOS_DECIMAL = 8,
MOS_BREAK = 16,
MOS_OVERFLOW = 64,
MOS_NEGATIVE = 128,
};
#define MOS_NVZ (MOS_NEGATIVE | MOS_OVERFLOW | MOS_ZERO)
#define MOS_NVZC (MOS_NEGATIVE | MOS_OVERFLOW | MOS_ZERO | MOS_CARRY)
#define MOS_NZ (MOS_NEGATIVE | MOS_ZERO)
#define MOS_NZC (MOS_NEGATIVE | MOS_ZERO | MOS_CARRY)
#define MOS_ZC (MOS_ZERO | MOS_CARRY)
#define MOS_STATUS_DEFAULT (MOS_NEGATIVE | MOS_OVERFLOW | \
MOS_INTERRUPT | MOS_ZERO | MOS_CARRY)
/*
* Here we define the various address modes that are possible. These do
* not map to any significant numbers that are documented for the 6502
* processor; the position of these symbols don't really matter, and are
* generally (except for `NOA`, no address mode) in alphabetical order.
*/
enum addr_mode {
NOA, // no address mode
ACC, // accumulator
ABS, // absolute
ABX, // absolute x-index
ABY, // absolute y-index
BY2, // Consume 2 bytes (for NP2)
BY3, // Consume 3 bytes (for NP3)
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
};
/*
* These define the various instructions as enum symbols; again, like
* for address modes, the values of these enums are not actually
* significant to the 6502 processor, and are only useful to we, the
* programmers.
*/
enum instruction {
ADC, // ADd with Carry
AND, // bitwise AND
ASL, // Arithmetic Shift Left
BAD, // bad instruction
BCC, // Branch on Carry Clear
BCS, // Branch on Carry Set
BEQ, // Branch on EQual to zero
BIT, // BIT test
BIM, // BIt test (imMediate mode) (* not a real instruction in the processor; just used by us)
BMI, // Branch on MInus
BNE, // Branch on Not Equal to zero
BPL, // Branch on PLus
BRA, // BRanch Always
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
NP2, // No oPeration (2 bytes consumed)
NP3, // No oPeration (3 bytes consumed)
ORA, // OR with Accumulator
PHA, // PusH Accumulator
PHP, // PusH Predicate register
PHX, // PusH X register
PHY, // PusH Y register
PLA, // PulL Accumulator
PLP, // PulL Predicate register
PLX, // PulL X register
PLY, // PulL Y 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
STZ, // STore Zero
TAX, // Transfer Accumulator to X
TAY, // Transfer Accumulator to Y
TRB, // Test and Reset Bits
TSB, // Test and Set Bits
TSX, // Transfer Stack register to X
TXA, // Transfer X to Accumulator
TXS, // Transfer X to Stack register
TYA, // Transfer Y to Accumulator
};
#endif

278
include/mos6502/mos6502.h Normal file
View File

@ -0,0 +1,278 @@
#ifndef _MOS6502_H_
#define _MOS6502_H_
#include <stdbool.h>
#include "vm_bits.h"
#include "vm_segment.h"
/*
* The size of memory that the MOS 6502 supports is 64k (the limit of
* values that a 16-bit address could possibly map to).
*/
#define MOS6502_MEMSIZE 65536
/*
* This is a small macro to make it a bit simpler to set bytes ahead of
* the PC register position; useful in testing.
*/
#define SET_PC_BYTE(cpu, off, byte) \
mos6502_set(cpu, cpu->PC + off, byte)
/*
* This macro is used to define new instruction handler functions.
*/
#define DEFINE_INST(inst) \
void mos6502_handle_##inst (mos6502 *cpu, vm_8bit oper)
/*
* 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 MOS_CARRY_BIT() \
vm_8bit carry = 0; \
if (cpu->P & MOS_CARRY) carry = 1
#define SET_RESULT(op) \
int result = op
/*
* 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)
#define MOS_CHECK_Z(result) \
cpu->P &= ~MOS_ZERO; \
if ((vm_8bit)(result) == 0) cpu->P |= MOS_ZERO
#define MOS_CHECK_N(result) \
cpu->P &= ~MOS_NEGATIVE; \
if ((vm_8bit)(result) & 0x80) cpu->P |= MOS_NEGATIVE
#define MOS_CHECK_V(orig, result) \
cpu->P &= ~MOS_OVERFLOW; \
do { \
vm_8bit r = result; \
vm_8bit o = orig; \
if ((o & 0x80) ^ (r & 0x80)) { \
cpu->P |= MOS_OVERFLOW; \
} \
} while (0)
#define MOS_CHECK_NV(orig, result) \
MOS_CHECK_N(result); \
MOS_CHECK_V(orig, result)
#define MOS_CHECK_NZ(result) \
MOS_CHECK_N(result); \
MOS_CHECK_Z(result)
#define MOS_CHECK_NVZ(orig, result) \
MOS_CHECK_N(result); \
MOS_CHECK_V(orig, result); \
MOS_CHECK_Z(result)
typedef struct {
/*
* There are two different segment pointers for reading and writing,
* because it's possible for there to be two different banks in
* which an action occurs. These memory segments must be injected at
* creation time, and can be changed later.
*/
vm_segment *rmem;
vm_segment *wmem;
/*
* This contains the _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 eff_addr;
/*
* These are the last opcode and last effective address that was
* used in the instruction previous to the one currently being
* executed. Some things (notably soft switches) may need to
* the last opcode.
*/
vm_8bit last_opcode;
vm_8bit last_operand;
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 *);
/*
* Another convenience; this type definition is for the functions we
* write to handle instruction logic.
*/
typedef void (*mos6502_instruction_handler)(mos6502 *, vm_8bit);
extern bool mos6502_would_jump(int);
extern int mos6502_cycles(mos6502 *, vm_8bit);
extern int mos6502_instruction(vm_8bit);
extern mos6502 *mos6502_create(vm_segment *, vm_segment *);
extern mos6502_instruction_handler mos6502_get_instruction_handler(vm_8bit);
extern vm_16bit mos6502_get16(mos6502 *, size_t);
extern vm_8bit mos6502_get(mos6502 *, size_t);
extern vm_8bit mos6502_pop_stack(mos6502 *);
extern void mos6502_execute(mos6502 *);
extern void mos6502_free(mos6502 *);
extern void mos6502_last_executed(mos6502 *, vm_8bit *, vm_8bit *, vm_16bit *);
extern void mos6502_push_stack(mos6502 *, vm_8bit);
extern void mos6502_set(mos6502 *, size_t, vm_8bit);
extern void mos6502_set16(mos6502 *, size_t, vm_16bit);
extern void mos6502_set_memory(mos6502 *, vm_segment *, vm_segment *);
extern void mos6502_set_status(mos6502 *, vm_8bit);
/*
* Below are some functions that are defined in mos6502.addr.c
*/
extern int mos6502_addr_mode(vm_8bit);
extern mos6502_address_resolver mos6502_get_address_resolver(int);
/*
* 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; held generally in mos6502.*.c
* (excepting mos6502.addr.c).
*/
DECL_INST(adc);
DECL_INST(adc_dec);
DECL_INST(and);
DECL_INST(asl);
DECL_INST(bad);
DECL_INST(bcc);
DECL_INST(bcs);
DECL_INST(beq);
DECL_INST(bit);
DECL_INST(bim);
DECL_INST(bmi);
DECL_INST(bne);
DECL_INST(bpl);
DECL_INST(bra);
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(np2);
DECL_INST(np3);
DECL_INST(ora);
DECL_INST(pha);
DECL_INST(php);
DECL_INST(phx);
DECL_INST(phy);
DECL_INST(pla);
DECL_INST(plp);
DECL_INST(plx);
DECL_INST(ply);
DECL_INST(rol);
DECL_INST(ror);
DECL_INST(rti);
DECL_INST(rts);
DECL_INST(sbc);
DECL_INST(sbc_dec);
DECL_INST(sec);
DECL_INST(sed);
DECL_INST(sei);
DECL_INST(sta);
DECL_INST(stx);
DECL_INST(sty);
DECL_INST(stz);
DECL_INST(tax);
DECL_INST(tay);
DECL_INST(trb);
DECL_INST(tsb);
DECL_INST(tsx);
DECL_INST(txa);
DECL_INST(txs);
DECL_INST(tya);
#endif

21
include/mos6502/tests.h Normal file
View File

@ -0,0 +1,21 @@
#ifndef _MOS6502_TESTS_H
#define _MOS6502_TESTS_H
static mos6502 *cpu;
static vm_segment *mem;
static void
setup()
{
mem = vm_segment_create(MOS6502_MEMSIZE);
cpu = mos6502_create(mem, mem);
}
static void
teardown()
{
mos6502_free(cpu);
vm_segment_free(mem);
}
#endif

94
src/mos6502/branch.c Normal file
View File

@ -0,0 +1,94 @@
/*
* mos6502.branch.c
*
* This is all the logic we use for branch instructions, which are used
* for conditional expressions.
*/
#include "mos6502/mos6502.h"
#include "mos6502/enums.h"
/*
* This is just a minor convenience macro to wrap the logic we use in
* branch situations, which is if `cond` is true, then we set the
* program counter to the last effective address.
*/
#define JUMP_IF(cond) \
if (cond) cpu->PC = cpu->eff_addr; else cpu->PC += 2
/*
* Branch if the carry flag is clear.
*/
DEFINE_INST(bcc)
{
JUMP_IF(~cpu->P & MOS_CARRY);
}
/*
* Branch if carry is set.
*/
DEFINE_INST(bcs)
{
JUMP_IF(cpu->P & MOS_CARRY);
}
/*
* Branch if the zero flag is set (that is, if our last instruction
* resulted in something being _equal to zero_).
*/
DEFINE_INST(beq)
{
JUMP_IF(cpu->P & MOS_ZERO);
}
/*
* Branch if the negative ("minus") flag is set.
*/
DEFINE_INST(bmi)
{
JUMP_IF(cpu->P & MOS_NEGATIVE);
}
/*
* Branch if the zero flag is not set; which is to say, that the last
* operation was _not equal_ to zero.
*/
DEFINE_INST(bne)
{
JUMP_IF(~cpu->P & MOS_ZERO);
}
/*
* Branch if the negative flag is not set (meaning the last operation
* was "plus", which includes zero).
*/
DEFINE_INST(bpl)
{
JUMP_IF(~cpu->P & MOS_NEGATIVE);
}
/*
* This instruction will branch in all cases. It's not a true
* conditional; it's analagous to a relative address mode JMP.
*/
DEFINE_INST(bra)
{
// Always jump!
JUMP_IF(1);
}
/*
* Branch if the overflow bit is clear.
*/
DEFINE_INST(bvc)
{
JUMP_IF(~cpu->P & MOS_OVERFLOW);
}
/*
* Branch if the overflow bit is set.
*/
DEFINE_INST(bvs)
{
JUMP_IF(cpu->P & MOS_OVERFLOW);
}