From 2bdcaf319b1d74ffbaccf08a58336f804761beab Mon Sep 17 00:00:00 2001 From: LLLL Colonq Date: Thu, 9 Jul 2026 23:51:55 -0400 Subject: Refactor into monorepo --- 6502/.gitignore | 1 + 6502/Makefile | 63 +++++++++++ 6502/include/lcq/6502.h | 37 +++++++ 6502/include/lcq/6502/ins.h | 99 +++++++++++++++++ 6502/include/lcq/6502/types.h | 17 +++ 6502/packages.nix | 11 ++ 6502/src/6502.c | 247 ++++++++++++++++++++++++++++++++++++++++++ 6502/src/main.c | 6 + 8 files changed, 481 insertions(+) create mode 100644 6502/.gitignore create mode 100644 6502/Makefile create mode 100644 6502/include/lcq/6502.h create mode 100644 6502/include/lcq/6502/ins.h create mode 100644 6502/include/lcq/6502/types.h create mode 100644 6502/packages.nix create mode 100644 6502/src/6502.c create mode 100644 6502/src/main.c (limited to '6502') diff --git a/6502/.gitignore b/6502/.gitignore new file mode 100644 index 0000000..13a055e --- /dev/null +++ b/6502/.gitignore @@ -0,0 +1 @@ +./6502 \ No newline at end of file diff --git a/6502/Makefile b/6502/Makefile new file mode 100644 index 0000000..c8119f3 --- /dev/null +++ b/6502/Makefile @@ -0,0 +1,63 @@ +CC ?= gcc +AR ?= ar +override CPPFLAGS += -MMD -MP +override CFLAGS += -fPIC --std=c23 -g -Ideps/ -Isrc/ -Iinclude/ -Wall -Wextra -Wpedantic -Wconversion -Wformat-security -Wshadow -Wpointer-arith -Wstrict-prototypes -Wmissing-prototypes -Wnull-dereference -Wfloat-equal -Wundef -Wpointer-arith -Wbad-function-cast -Wlogical-op -Wmissing-braces -Wcast-align -Wstrict-overflow=5 -ftrapv +override LDFLAGS += -g -static + +BUILD = build_$(CC) + +SRCS := src/main.c src/6502.c +CHK_SOURCES ?= $(SRCS) +OBJECTS := $(SRCS:src/%.c=$(BUILD)/%.o) +EXE ?= 6502 +LIB := libcolonq-6502.a + +prefix ?= /usr/local +exec_prefix ?= $(prefix) +bindir ?= $(exec_prefix)/bin +includedir ?= $(prefix)/include +libdir ?= $(exec_prefix)/lib + +.PHONY: all clean install install-bin install-headers install-lib check-syntax + +all: $(EXE) $(LIB) + +$(EXE): $(BUILD)/main.o $(LIB) + $(CC) -o $@ $^ $(LDFLAGS) + +$(LIB): $(OBJECTS) + $(AR) rcs $@ $^ + +$(BUILD): + mkdir $(BUILD)/ + +$(BUILD)/%.o: src/%.c | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) -o $@ -c $< + +clean: + -rm $(EXE) + -rm $(LIB) + -rm -r $(BUILD)/ + +TAGS: $(SRCS) + ctags --output-format=etags $^ + +install: install-bin install-headers install-lib + +install-bin: $(EXE) + mkdir -p $(DESTDIR)$(bindir) $(DESTDIR)$(libdir) $(DESTDIR)$(includedir) + install $(EXE) $(DESTDIR)$(bindir)/$(EXE) + +install-headers: + mkdir -p $(DESTDIR)$(bindir) $(DESTDIR)$(libdir) $(DESTDIR)$(includedir) + cp -r include/* $(DESTDIR)$(includedir) + +install-lib: $(LIB) + mkdir -p $(DESTDIR)$(bindir) $(DESTDIR)$(libdir) $(DESTDIR)$(includedir) + install $(LIB) $(DESTDIR)$(libdir)/$(LIB) + +check-syntax: TAGS + gcc $(CFLAGS) -fsyntax-only $(CHK_SOURCES) + +-include $(BUILD)/main.d +-include $(OBJECTS:.o=.d) diff --git a/6502/include/lcq/6502.h b/6502/include/lcq/6502.h new file mode 100644 index 0000000..cb3b1fa --- /dev/null +++ b/6502/include/lcq/6502.h @@ -0,0 +1,37 @@ +#ifndef LCOLONQ_6502_H +#define LCOLONQ_6502_H + +#include + +typedef enum { + L6502_FLAG_CARRY = 1 << 1, + L6502_FLAG_ZERO = 1 << 2, + L6502_FLAG_INTERRUPT_DISABLE = 1 << 3, + L6502_FLAG_DECIMAL = 1 << 4, + L6502_FLAG_BREAK_COMMAND = 1 << 5, + L6502_FLAG_OVERFLOW = 1 << 6, + L6502_FLAG_NEGATIVE = 1 << 7, +} l6502_flag; + +typedef struct { + u16 pc; + u8 sp, a, x, y, flags; +} l6502_regs; + +typedef u8 (*l6502_mem_read_handler)(void *data, u16 addr); +typedef void (*l6502_mem_write_handler)(void *data, u16 addr, u8 val); +typedef struct { + l6502_mem_read_handler rh; + l6502_mem_write_handler wh; + void *data; +} l6502_mem; +void l6502_mem_basic_new(l6502_mem *m); + +typedef struct { + l6502_regs regs; + l6502_mem mem; +} l6502_emu; + +void l6502_emulate(l6502_emu *e); + +#endif diff --git a/6502/include/lcq/6502/ins.h b/6502/include/lcq/6502/ins.h new file mode 100644 index 0000000..88eb3a8 --- /dev/null +++ b/6502/include/lcq/6502/ins.h @@ -0,0 +1,99 @@ +#ifndef LCOLONQ_6502_INS_H +#define LCOLONQ_6502_INS_H + +typedef enum { + BRK = 0x00, + PHP = 0x08, + CLC = 0x18, + JSR = 0x20, + PLP = 0x28, + SEC = 0x38, + RTI = 0x40, + PHA = 0x48, + JMP_ABSOLUTE = 0x4c, + CLI = 0x58, + RTS = 0x60, + PLA = 0x68, + JMP_INDIRECT = 0x6c, + SEI = 0x78, + DEY = 0x88, + TXA = 0x8a, + TYA = 0x98, + TXS = 0x9a, + TAY = 0xa8, + TAX = 0xaa, + CLV = 0xb8, + TSX = 0xba, + INY = 0xc8, + DEX = 0xca, + CLD = 0xd8, + INX = 0xe8, + NOP = 0xea, + SED = 0xf8, +} l6502_ins_misc; + +typedef enum { + G1 = 0b01, + G2 = 0b10, + G3 = 0b00, +} l6502_ins_group; + +typedef enum { + G1_OPCODE_ORA = 0b000, + G1_OPCODE_AND = 0b001, + G1_OPCODE_EOR = 0b010, + G1_OPCODE_ADC = 0b011, + G1_OPCODE_STA = 0b100, + G1_OPCODE_LDA = 0b101, + G1_OPCODE_CMP = 0b110, + G1_OPCODE_SBC = 0b111, +} l6502_ins_group_one_opcode; + +typedef enum { + G1_ADDRESSING_MODE_INDIRECT_ZEROPAGE_X = 0b000, + G1_ADDRESSING_MODE_ZEROPAGE = 0b001, + G1_ADDRESSING_MODE_IMMEDIATE = 0b010, + G1_ADDRESSING_MODE_ABSOLUTE = 0b011, + G1_ADDRESSING_MODE_INDIRECT_ZEROPAGE_Y = 0b100, + G1_ADDRESSING_MODE_ZEROPAGE_X = 0b101, + G1_ADDRESSING_MODE_ABSOLUTE_Y = 0b110, + G1_ADDRESSING_MODE_ABSOLUTE_X = 0b111, +} l6502_ins_group_one_addressing_mode; + +typedef enum { + G2_OPCODE_ASL = 0b000, + G2_OPCODE_ROL = 0b001, + G2_OPCODE_LSR = 0b010, + G2_OPCODE_ROR = 0b011, + G2_OPCODE_STX = 0b100, + G2_OPCODE_LDX = 0b101, + G2_OPCODE_DEC = 0b110, + G2_OPCODE_INC = 0b111, +} l6502_ins_group_two_opcode; + +typedef enum { + G2_ADDRESSING_MODE_IMMEDIATE = 0b000, + G2_ADDRESSING_MODE_ZEROPAGE = 0b001, + G2_ADDRESSING_MODE_ACCUMULATOR = 0b010, + G2_ADDRESSING_MODE_ABSOLUTE = 0b011, + G2_ADDRESSING_MODE_ZEROPAGE_X = 0b101, + G2_ADDRESSING_MODE_ABSOLUTE_X = 0b111, +} l6502_ins_group_two_addressing_mode; + +typedef enum { + G3_OPCODE_BIT = 0b001, + G3_OPCODE_STY = 0b100, + G3_OPCODE_LDY = 0b101, + G3_OPCODE_CPY = 0b110, + G3_OPCODE_CPX = 0b111, +} l6502_ins_group_three_opcode; + +typedef enum { + G3_ADDRESSING_MODE_IMMEDIATE = 0b000, + G3_ADDRESSING_MODE_ZEROPAGE = 0b001, + G3_ADDRESSING_MODE_ABSOLUTE = 0b011, + G3_ADDRESSING_MODE_ZEROPAGE_X = 0b101, + G3_ADDRESSING_MODE_ABSOLUTE_X = 0b111, +} l6502_ins_group_three_addressing_mode; + +#endif diff --git a/6502/include/lcq/6502/types.h b/6502/include/lcq/6502/types.h new file mode 100644 index 0000000..44ce546 --- /dev/null +++ b/6502/include/lcq/6502/types.h @@ -0,0 +1,17 @@ +#ifndef LCOLONQ_6502_TYPES_H +#define LCOLONQ_6502_TYPES_H + +#include +#include +#include + +typedef int8_t i8; +typedef int16_t i16; +typedef int32_t i32; +typedef int64_t i64; +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; + +#endif diff --git a/6502/packages.nix b/6502/packages.nix new file mode 100644 index 0000000..6ce8162 --- /dev/null +++ b/6502/packages.nix @@ -0,0 +1,11 @@ +pkgs: lcq: { + native = pkgs.stdenv.mkDerivation { + pname = "libcolonq-6502"; + version = "git"; + src = ./.; + hardeningDisable = ["all"]; + installPhase = '' + make prefix=$out install + ''; + }; +} diff --git a/6502/src/6502.c b/6502/src/6502.c new file mode 100644 index 0000000..c9b802d --- /dev/null +++ b/6502/src/6502.c @@ -0,0 +1,247 @@ +#include +#include + +#include + +static u8 basic_read_handler(void *data, u16 addr) { + return ((u8 *) data)[addr]; +} +static void basic_write_handler(void *data, u16 addr, u8 val) { + ((u8 *) data)[addr] = val; +} +void l6502_mem_basic_new(l6502_mem *m) { + m->rh = basic_read_handler; + m->wh = basic_write_handler; + m->data = calloc(1 << 16, sizeof(u8)); +} + +static u8 l6502_flag_get(l6502_emu *e, l6502_flag f) { return (e->regs.flags & (u8) f) != 0; } +static void l6502_flag_set(l6502_emu *e, l6502_flag f, u8 v) { + if (v) e->regs.flags |= (u8) f; + else e->regs.flags &= ~((u8) f); +} +static void l6502_flags_check_zn(l6502_emu *e, u8 v) { + l6502_flag_set(e, L6502_FLAG_ZERO, v == 0); + l6502_flag_set(e, L6502_FLAG_NEGATIVE, (v >> 7) & 0b1); +} + +static u8 l6502_read(l6502_emu *e, u16 addr) { return e->mem.rh(e->mem.data, addr); } +static void l6502_write(l6502_emu *e, u16 addr, u8 val) { e->mem.wh(e->mem.data, addr, val); } +static u16 l6502_read_addr(l6502_emu *e, u16 addr) { + u8 alo = l6502_read(e, addr); + u8 ahi = l6502_read(e, addr + 1); + return ahi << 8 | alo; +} + +static u8 l6502_pop(l6502_emu *e) { return l6502_read(e, 0x0100 + (u16) ++e->regs.sp); } +static void l6502_push(l6502_emu *e, u8 val) { l6502_write(e, 0x0100 + (u16) e->regs.sp--, val); } +static u16 l6502_pop_addr(l6502_emu *e) { + u8 ahi = l6502_pop(e); + u8 alo = l6502_pop(e); + return ahi << 8 | alo; +} +static void l6502_push_addr(l6502_emu *e, u16 addr) { + l6502_push(e, (u8) (addr & 0xff)); + l6502_push(e, (u8) (addr >> 8)); +} + +static u16 l6502_addressing_mode_immediate(l6502_emu *e) { + return e->regs.pc++; +} +static u16 l6502_addressing_mode_absolute(l6502_emu *e) { + u16 addr = l6502_read_addr(e, e->regs.pc); + e->regs.pc += 2; + return addr; +} +static u16 l6502_addressing_mode_absolute_x(l6502_emu *e) { + u16 addr = l6502_read_addr(e, e->regs.pc) + (u16) e->regs.x; + e->regs.pc += 2; + return addr; +} +static u16 l6502_addressing_mode_absolute_y(l6502_emu *e) { + u16 addr = l6502_read_addr(e, e->regs.pc) + (u16) e->regs.y; + e->regs.pc += 2; + return addr; +} +static u16 l6502_addressing_mode_zeropage(l6502_emu *e) { + return (u16) l6502_read(e, e->regs.pc++); +} +static u16 l6502_addressing_mode_zeropage_x(l6502_emu *e) { + return (u16) (l6502_read(e, e->regs.pc++) + e->regs.x); +} +static u16 l6502_addressing_mode_indirect_zeropage_x(l6502_emu *e) { + return l6502_read_addr(e, l6502_read(e, e->regs.pc++) + e->regs.x); +} +static u16 l6502_addressing_mode_indirect_zeropage_y(l6502_emu *e) { + return l6502_read_addr(e, l6502_read(e, e->regs.pc++)) + (u16) e->regs.y; +} + +void l6502_emulate(l6502_emu *e) { + u8 ins = l6502_read(e, e->regs.pc++); + u8 val = 0; + u16 res = 0; + bool set_flags = true; + bool set_result = true; + u8 *set_reg = NULL; + u16 addr = 0; + switch (ins) { /* identify some special-case instructions */ + case BRK: + l6502_push_addr(e, e->regs.pc); + l6502_push(e, e->regs.flags); + e->regs.pc = l6502_read_addr(e, 0xfffe); + l6502_flag_set(e, L6502_FLAG_BREAK_COMMAND, true); + break; + case PHP: l6502_push(e, e->regs.flags); break; + case CLC: l6502_flag_set(e, L6502_FLAG_CARRY, false); break; + case JSR: + addr = l6502_read_addr(e, e->regs.pc); + l6502_push_addr(e, e->regs.pc + 1); + e->regs.pc = addr; + break; + case PLP: e->regs.flags = l6502_pop(e); break; + case SEC: l6502_flag_set(e, L6502_FLAG_CARRY, true); break; + case RTI: + e->regs.flags = l6502_pop(e); + e->regs.pc = l6502_pop_addr(e); + break; + case PHA: l6502_push(e, e->regs.a); break; + case JMP_ABSOLUTE: + addr = l6502_read_addr(e, e->regs.pc); + e->regs.pc = addr; + break; + case CLI: l6502_flag_set(e, L6502_FLAG_INTERRUPT_DISABLE, false); break; + case RTS: e->regs.pc = l6502_pop_addr(e) + 1; break; + case PLA: e->regs.a = l6502_pop(e); break; + case JMP_INDIRECT: + addr = l6502_read_addr(e, l6502_read_addr(e, e->regs.pc)); + e->regs.pc = addr; + break; + case SEI: l6502_flag_set(e, L6502_FLAG_INTERRUPT_DISABLE, true); break; + case DEY: e->regs.y -= 1; l6502_flags_check_zn(e, e->regs.y); break; + case TXA: e->regs.a = e->regs.x; l6502_flags_check_zn(e, e->regs.a); break; + case TYA: e->regs.a = e->regs.y; l6502_flags_check_zn(e, e->regs.a); break; + case TXS: e->regs.sp = e->regs.x; break; /* note TXS does not set flags */ + case TAY: e->regs.y = e->regs.a; l6502_flags_check_zn(e, e->regs.y); break; + case TAX: e->regs.x = e->regs.a; l6502_flags_check_zn(e, e->regs.x); break; + case CLV: l6502_flag_set(e, L6502_FLAG_OVERFLOW, false); break; + case TSX: e->regs.x = e->regs.sp; l6502_flags_check_zn(e, e->regs.x); break; + case INY: e->regs.y += 1; l6502_flags_check_zn(e, e->regs.y); break; + case DEX: e->regs.x -= 1; l6502_flags_check_zn(e, e->regs.x); break; + case CLD: l6502_flag_set(e, L6502_FLAG_DECIMAL, false); break; + case INX: e->regs.x += 1; l6502_flags_check_zn(e, e->regs.x); break; + case NOP: break; + case SED: l6502_flag_set(e, L6502_FLAG_DECIMAL, true); break; + } + if ((ins & 0b11111) == 0b10000) { /* identify conditional branches */ + l6502_flag flags[4] = { L6502_FLAG_NEGATIVE, L6502_FLAG_OVERFLOW, L6502_FLAG_CARRY, L6502_FLAG_ZERO }; + u8 off = l6502_read(e, e->regs.pc++); + if (l6502_flag_get(e, flags[(ins >> 6) & 0b11]) == ((ins >> 5) & 0b1)) { + e->regs.pc = (u16) ((i16) e->regs.pc + (i16) (i8) off); + } + return; + } + switch (ins & 0b11) { /* identify instructions in common forms */ + case 0b01: { /* group one */ + set_reg = &e->regs.a; + switch ((ins >> 2) & 0b111) { + case G1_ADDRESSING_MODE_INDIRECT_ZEROPAGE_X: addr = l6502_addressing_mode_indirect_zeropage_x(e); break; + case G1_ADDRESSING_MODE_ZEROPAGE: addr = l6502_addressing_mode_zeropage(e); break; + case G1_ADDRESSING_MODE_IMMEDIATE: addr = l6502_addressing_mode_immediate(e); break; + case G1_ADDRESSING_MODE_ABSOLUTE: addr = l6502_addressing_mode_absolute(e); break; + case G1_ADDRESSING_MODE_INDIRECT_ZEROPAGE_Y: addr = l6502_addressing_mode_indirect_zeropage_y(e); break; + case G1_ADDRESSING_MODE_ZEROPAGE_X: addr = l6502_addressing_mode_zeropage_x(e); break; + case G1_ADDRESSING_MODE_ABSOLUTE_Y: addr = l6502_addressing_mode_absolute_y(e); break; + case G1_ADDRESSING_MODE_ABSOLUTE_X: addr = l6502_addressing_mode_absolute_x(e); break; + } + val = l6502_read(e, addr); + switch ((ins >> 5) & 0b111) { + case G1_OPCODE_ORA: res = (u16) e->regs.a | (u16) val; break; + case G1_OPCODE_AND: res = (u16) e->regs.a & (u16) val; break; + case G1_OPCODE_EOR: res = (u16) e->regs.a ^ (u16) val; break; + case G1_OPCODE_ADC: + res = (u16) e->regs.a + (u16) val; + res += (u16) l6502_flag_get(e, L6502_FLAG_CARRY); + l6502_flag_set(e, L6502_FLAG_OVERFLOW, ((res >> 8) != 0) != ((res >> 7) & 0b1)); + break; + case G1_OPCODE_STA: l6502_write(e, addr, e->regs.a); set_flags = set_result = false; break; + case G1_OPCODE_LDA: res = (u16) l6502_read(e, addr); break; + case G1_OPCODE_CMP: + set_result = false; + res = (u16) e->regs.a - (u16) val; + break; + case G1_OPCODE_SBC: + res = (u16) e->regs.a - (u16) val; + res -= (u16) (1 - l6502_flag_get(e, L6502_FLAG_CARRY)); + l6502_flag_set(e, L6502_FLAG_OVERFLOW, ((res >> 8) != 0) != ((res >> 7) & 0b1)); + break; + } + break; + } + case 0b10: { /* group two */ + switch ((ins >> 2) & 0b111) { + case G2_ADDRESSING_MODE_IMMEDIATE: addr = l6502_addressing_mode_immediate(e); break; + case G2_ADDRESSING_MODE_ZEROPAGE: addr = l6502_addressing_mode_zeropage(e); break; + case G2_ADDRESSING_MODE_ACCUMULATOR: addr = (u16) e->regs.a; set_reg = &e->regs.a; break; + case G2_ADDRESSING_MODE_ABSOLUTE: addr = l6502_addressing_mode_absolute(e); break; + case G2_ADDRESSING_MODE_ZEROPAGE_X: addr = l6502_addressing_mode_zeropage_x(e); break; + case G2_ADDRESSING_MODE_ABSOLUTE_X: addr = l6502_addressing_mode_absolute_x(e); break; + } + val = set_reg ? (u8) addr : l6502_read(e, addr); + switch ((ins >> 5) & 0b111) { + case G2_OPCODE_ASL: res = ((u16) val) << 1; break; + case G2_OPCODE_ROL: res = ((u16) val) << 1 | (u16) l6502_flag_get(e, L6502_FLAG_CARRY); break; + case G2_OPCODE_LSR: + res = ((u16) val) >> 1 + | (u16) (val & 0b1) << 8; /* place the previous low bit in carry */ + break; + case G2_OPCODE_ROR: + res = ((u16) val) >> 1 + | (u16) l6502_flag_get(e, L6502_FLAG_CARRY) << 7 + | (u16) (val & 0b1) << 8; /* same as above */ + break; + case G2_OPCODE_STX: l6502_write(e, addr, e->regs.x); set_flags = set_result = false; break; + case G2_OPCODE_LDX: res = (u16) l6502_read(e, addr); set_reg = &e->regs.x; break; + case G2_OPCODE_DEC: res = (u16) val - 1; break; + case G2_OPCODE_INC: res = (u16) val + 1; break; + } + break; + } + case 0b00: { /* group three */ + switch ((ins >> 2) & 0b111) { + case G3_ADDRESSING_MODE_IMMEDIATE: addr = l6502_addressing_mode_immediate(e); break; + case G3_ADDRESSING_MODE_ZEROPAGE: addr = l6502_addressing_mode_zeropage(e); break; + case G3_ADDRESSING_MODE_ABSOLUTE: addr = l6502_addressing_mode_absolute(e); break; + case G3_ADDRESSING_MODE_ZEROPAGE_X: addr = l6502_addressing_mode_zeropage_x(e); break; + case G3_ADDRESSING_MODE_ABSOLUTE_X: addr = l6502_addressing_mode_absolute_x(e); break; + } + val = l6502_read(e, addr); + switch ((ins >> 5) & 0b111) { + case G3_OPCODE_BIT: + set_flags = set_result = false; + l6502_flag_set(e, L6502_FLAG_ZERO, (e->regs.a & val) == 0); + l6502_flag_set(e, L6502_FLAG_OVERFLOW, (val >> 6) & 0b1); + l6502_flag_set(e, L6502_FLAG_NEGATIVE, (val >> 7) & 0b1); + break; + case G3_OPCODE_STY: l6502_write(e, addr, e->regs.y); set_flags = set_result = false; break; + case G3_OPCODE_LDY: res = (u16) l6502_read(e, addr); set_reg = &e->regs.y; break; + case G3_OPCODE_CPY: + set_result = false; + res = (u16) e->regs.y - (u16) val; + break; + case G3_OPCODE_CPX: + set_result = false; + res = (u16) e->regs.x - (u16) val; + break; + } + } + } + if (set_flags) { + l6502_flag_set(e, L6502_FLAG_ZERO, res == 0); + l6502_flag_set(e, L6502_FLAG_NEGATIVE, (res >> 7) & 0b1); + l6502_flag_set(e, L6502_FLAG_CARRY, (res >> 8) != 0); + } + if (set_result) { + if (set_reg) *set_reg = (u8) res; + else l6502_write(e, addr, (u8) res); + } +} diff --git a/6502/src/main.c b/6502/src/main.c new file mode 100644 index 0000000..fe54ce7 --- /dev/null +++ b/6502/src/main.c @@ -0,0 +1,6 @@ +#include + +int main(int argc, char **argv) { + (void) argc; (void) argv; + puts("hi"); +} -- cgit v1.3.1