--- /dev/null
+#include <fcntl.h>
+#include <poll.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+#include <SDL2/SDL.h>
+#include "vrEmu6502/src/vrEmu6502.h"
+
+#define APPLE_WIDTH 560
+#define APPLE_HEIGHT 192
+#define APPLE_HIRES0 0x2000
+
+#define WINDOW_X_SCALE 2
+#define WINDOW_Y_SCALE 4
+#define WINDOW_WIDTH (APPLE_WIDTH * WINDOW_X_SCALE)
+#define WINDOW_HEIGHT (APPLE_HEIGHT * WINDOW_Y_SCALE)
+
+#define INSTRS_PER_UPDATE 1500
+
+#define IO_PAGE 0xc000
+#define IO_KBD 0xc000
+#define IO_KBDSTRB 0xc010
+#define IO_SPKR 0xc030
+#define IO_TXTCLR 0xc050
+#define IO_MIXCLR 0xc052
+#define IO_LOWSCR 0xc054
+#define IO_HIRES 0xc057
+#define IO_PB0 0xc061
+#define IO_PB1 0xc062
+#define IO_PADDL0 0xc064
+#define IO_PADDL1 0xc065
+#define IO_PTRIG 0xc070
+#define STDIN_DATA 0xc0f0
+#define STDOUT_DATA 0xc0f1
+#define STDERR_DATA 0xc0f2
+#define STDIN_STATUS 0xc0f3
+#define STDOUT_STATUS 0xc0f4
+#define STDERR_STATUS 0xc0f5
+#define USLEEP_LO 0xc0f6
+#define USLEEP_HI 0xc0f7
+#define SYS_EXIT 0xc0f8
+
+#define RESET_VECTOR 0xfffc
+
+#define TRACE 0
+
+struct termios termios_attr;
+
+SDL_Window *window;
+SDL_Renderer *renderer;
+SDL_Texture *texture;
+SDL_Surface *surface;
+uint32_t frame[WINDOW_HEIGHT][WINDOW_WIDTH];
+
+// linear 0.250000 -> gamma encoded 0.537099 -> 0x89
+// linear 0.500000 -> gamma encoded 0.735357 -> 0xbc
+// linear 0.750000 -> gamma encoded 0.880825 -> 0xe1
+uint32_t argb[WINDOW_X_SCALE * 4] = {
+ // hue R G B
+ 0xffff8900, // 15 1.00 0.25 0.00
+ //0xffff0000, // 0 1.00 0.00 0.00
+ //0xffff0089, // 345 1.00 0.00 0.25
+ 0xffff00bc, // 330 1.00 0.00 0.50
+ //0xffff00e1, // 315 1.00 0.00 0.75
+ //0xffff00ff, // 300 1.00 0.00 1.00
+ 0xffe100ff, // 285 0.75 0.00 1.00
+ //0xffbc00ff, // 270 0.50 0.00 1.00
+ //0xff8900ff, // 255 0.25 0.00 1.00
+ 0xff0000ff, // 289 0.00 0.00 1.00
+ //0xff0089ff, // 225 0.00 0.25 1.00
+ //0xff00bcff, // 210 0.00 0.50 1.00
+ 0xff00e1ff, // 195 0.00 0.75 1.00
+ //0xff00ffff, // 1bc 0.00 1.00 1.00
+ //0xff00ffe1, // 165 0.00 1.00 0.75
+ 0xff00ffbc, // 150 0.00 1.00 0.50
+ //0xff00ff89, // 135 0.00 1.00 0.25
+ //0xff00ff00, // 120 0.00 1.00 0.00
+ 0xff89ff00, // 105 0.25 1.00 0.00
+ //0xffbcff00, // 90 0.50 1.00 0.00
+ //0xffe1ff00, // 75 0.75 1.00 0.00
+ 0xffffff00, // 60 1.00 1.00 0.00
+ //0xffffe100, // 45 1.00 0.75 0.00
+ //0xffffbc00, // 30 1.00 0.50 0.00
+};
+
+VrEmu6502 *cpu;
+
+#define MEM_SIZE 0x10000
+uint8_t mem[MEM_SIZE];
+
+#if TRACE
+#define N_PC_PAIRS 0x10001
+struct pc_pair {
+ uint16_t pc0;
+ uint16_t pc1;
+} pc_pairs[N_PC_PAIRS];
+
+#define TRACE_REG_A 0
+#define TRACE_REG_X 1
+#define TRACE_REG_Y 2
+#define TRACE_REG_S 3
+#define TRACE_REG_P 4
+#define N_TRACE_REGS 5
+struct trace {
+ uint8_t min_unsigned;
+ int8_t min_signed;
+ uint8_t min_bitwise;
+ uint8_t max_unsigned;
+ int8_t max_signed;
+ uint8_t max_bitwise;
+} trace[MEM_SIZE][N_TRACE_REGS];
+#endif
+uint8_t key_waiting;
+uint8_t usleep_lo;
+int exit_flag;
+
+int bload(char *name) {
+ int load_address = -1;
+ char *p;
+ for (p = name; *p && *p != ','; ++p)
+ ;
+ int c = *p;
+ *p++ = 0;
+ while (c) {
+ char *q;
+ for (q = p; *q && *q != ','; ++q)
+ ;
+ c = *q;
+ *q++ = 0;
+ if (*p == 'A' || *p == 'a')
+ load_address = atoi(p + 1);
+ else {
+ fprintf(stderr, "unknown BLOAD option: %s\n", p);
+ exit(EXIT_FAILURE);
+ }
+ p = q;
+ }
+
+ int fd = open(name, O_RDONLY);
+ if (fd == -1) {
+ perror(name);
+ exit(EXIT_FAILURE);
+ }
+ uint8_t header[4];
+ if (read(fd, header, 4) != 4) {
+ perror("read()");
+ exit(EXIT_FAILURE);
+ }
+ if (load_address == -1)
+ load_address = header[0] + (header[1] << 8);
+ int load_size = header[2] + (header[3] << 8);
+ if (read(fd, mem + load_address, load_size) != load_size) {
+ perror("read()");
+ exit(EXIT_FAILURE);
+ }
+ close(fd);
+ return load_address;
+}
+
+uint8_t mem_read(uint16_t addr, bool isDbg) {
+ if ((addr & 0xff00) != IO_PAGE) {
+#if 0 // breakpoint
+ if (addr == 0x17d1) { // opcode fetch
+ int fd = open("core.bin", O_WRONLY | O_CREAT, 0666);
+ if (fd == -1) {
+ perror("core.bin");
+ exit(EXIT_FAILURE);
+ }
+ if (write(fd, mem, MEM_SIZE) != MEM_SIZE) {
+ perror("write()");
+ exit(EXIT_FAILURE);
+ }
+ close(fd);
+ exit(EXIT_FAILURE);
+ }
+#endif
+
+ return mem[addr];
+ }
+
+ switch (addr) {
+ case IO_KBD:
+ return key_waiting;
+ break;
+ case STDIN_DATA:
+ {
+ uint8_t data = 'X' - 0x40;
+ ssize_t count = read(STDIN_FILENO, &data, 1);
+ if (count == -1) {
+ perror("read()");
+ exit(EXIT_FAILURE);
+ }
+ if (data == 'X' - 0x40) { // count == 0 or ctrl-x (unassigned by hrcg)
+ exit_flag = 0x101;
+ vrEmu6502Jam(cpu);
+ }
+ return data;
+ }
+ case STDIN_STATUS:
+ {
+ struct pollfd fd = {STDIN_FILENO, POLLIN, 0};
+ if (poll(&fd, 1, 0) == -1) {
+ perror("poll()");
+ exit(EXIT_FAILURE);
+ }
+ return ((fd.revents & POLLIN) != 0) << 7;
+ }
+ case STDOUT_STATUS:
+ {
+ struct pollfd fd = {STDOUT_FILENO, POLLOUT, 0};
+ if (poll(&fd, 1, 0) == -1) {
+ perror("poll()");
+ exit(EXIT_FAILURE);
+ }
+ return ((fd.revents & POLLOUT) != 0) << 7;
+ }
+ case STDERR_STATUS:
+ {
+ struct pollfd fd = {STDERR_FILENO, POLLOUT, 0};
+ if (poll(&fd, 1, 0) == -1) {
+ perror("poll()");
+ exit(EXIT_FAILURE);
+ }
+ return ((fd.revents & POLLOUT) != 0) << 7;
+ }
+ case USLEEP_LO:
+ return usleep_lo;
+ }
+ return 0xff;
+}
+
+void mem_write(uint16_t addr, uint8_t val) {
+ if ((addr & 0xff00) != IO_PAGE) {
+ mem[addr] = val;
+ return;
+ }
+
+ switch (addr) {
+ case IO_KBDSTRB:
+ key_waiting &= 0x7f;
+ break;
+ case STDOUT_DATA:
+ if (write(STDOUT_FILENO, &val, 1) == -1) {
+ perror("write()");
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case STDERR_DATA:
+ if (write(STDERR_FILENO, &val, 1) == -1) {
+ perror("write()");
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case USLEEP_LO:
+ usleep_lo = val;
+ break;
+ case USLEEP_HI:
+ usleep(usleep_lo | (val << 8));
+ break;
+ case SYS_EXIT:
+ exit_flag = val | 0x100;
+ vrEmu6502Jam(cpu);
+ break;
+ }
+}
+
+void termios_atexit(void) {
+ if (tcsetattr(STDIN_FILENO, TCSADRAIN, &termios_attr) == -1)
+ perror("tcsetattr()");
+}
+
+int main(int argc, char **argv) {
+ int argn = 1;
+ bool timing = false;
+ if (argn < argc && strcmp(argv[argn], "-t") == 0) {
+ timing = true;
+ ++argn;
+ }
+
+ if (argn >= argc) {
+ printf("usage: %s [-t] program.bin\n", argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ int load_address = 0;
+ while (argn < argc)
+ load_address = bload(argv[argn++]);
+
+ // do this before creating the CPU
+ mem[RESET_VECTOR] = (uint8_t)(load_address & 0xff);
+ mem[RESET_VECTOR + 1] = (uint8_t)(load_address >> 8);
+
+ if (isatty(STDIN_FILENO)) {
+ if (tcgetattr(STDIN_FILENO, &termios_attr) == -1) {
+ perror("tcgetattr()");
+ exit(EXIT_FAILURE);
+ }
+ atexit(termios_atexit);
+
+ // see https://github.com/python/cpython/blob/3.10/Lib/tty.py
+ struct termios attr = termios_attr;
+ attr.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
+ attr.c_oflag &= ~OPOST;
+ attr.c_cflag &= ~(CSIZE | PARENB);
+ attr.c_cflag |= CS8;
+ attr.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
+ attr.c_cc[VMIN] = 1;
+ attr.c_cc[VTIME] = 0;
+ if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &attr) == -1) {
+ perror("tcsetattr()");
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ if (SDL_Init(SDL_INIT_VIDEO) < 0) {
+ fprintf(stderr, "SDL_Init(): %s\n\n", SDL_GetError());
+ exit(EXIT_FAILURE);
+ }
+
+ if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1")) {
+ fprintf(stderr, "SDL_SetHint(): %s\n", SDL_GetError());
+ exit(EXIT_FAILURE);
+ }
+
+ window = SDL_CreateWindow(
+ "render",
+ SDL_WINDOWPOS_UNDEFINED,
+ SDL_WINDOWPOS_UNDEFINED,
+ WINDOW_WIDTH,
+ WINDOW_HEIGHT,
+ SDL_WINDOW_SHOWN
+ );
+ if (window == NULL) {
+ fprintf(stderr, "SDL_CreateWindow(): %s\n", SDL_GetError());
+ exit(EXIT_FAILURE);
+ }
+
+ renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
+ if (renderer == NULL) {
+ fprintf(stderr, "SDL_CreateRenderer(): %s\n", SDL_GetError());
+ exit(EXIT_FAILURE);
+ }
+
+ texture = SDL_CreateTexture(
+ renderer,
+ SDL_PIXELFORMAT_ARGB8888,
+ SDL_TEXTUREACCESS_STREAMING,
+ WINDOW_WIDTH,
+ WINDOW_HEIGHT
+ );
+ if (texture == NULL) {
+ fprintf(stderr, "SDL_CreateTexture(): %s\n", SDL_GetError());
+ exit(EXIT_FAILURE);
+ }
+
+ cpu = vrEmu6502New(CPU_65C02, mem_read, mem_write);
+ if (cpu == NULL) {
+ perror("malloc()");
+ exit(EXIT_FAILURE);
+ }
+
+#if TRACE
+ memset(pc_pairs, 0xff, N_PC_PAIRS * sizeof(struct pc_pair));
+
+ for (int i = 0; i < MEM_SIZE; ++i)
+ for (int j = 0; j < N_TRACE_REGS; ++j) {
+ trace[i][j].min_unsigned = 0xff;
+ trace[i][j].min_signed = 0x7f;
+ trace[i][j].min_bitwise = 0xff;
+ trace[i][j].max_unsigned = 0x00;
+ trace[i][j].max_signed = -0x80;
+ trace[i][j].max_bitwise = 0x00;
+ }
+#endif
+
+ // main loop
+ long nb_instructions = 0, nb_cycles = 0;
+ while (true) {
+ SDL_Event event;
+ while (SDL_PollEvent(&event))
+ switch (event.type) {
+ case SDL_QUIT:
+ goto quit;
+ case SDL_KEYDOWN:
+ {
+ SDL_KeyboardEvent *e = (SDL_KeyboardEvent *)&event;
+ int i = e->keysym.sym;
+ if (i < 0x80) {
+ //switch (i) {
+ //default:
+ if (i >= 'a' && i <= 'z')
+ i -= 0x20;
+ if (i >= 0x40 && i < 0x60 && (e->keysym.mod & KMOD_CTRL))
+ i -= 0x40;
+ key_waiting = i | 0x80;
+ // break;
+ //}
+ }
+ else
+ switch (i) {
+ case SDLK_LEFT:
+ key_waiting = 0x88;
+ break;
+ case SDLK_RIGHT:
+ key_waiting = 0x95;
+ break;
+ }
+ }
+ break;
+ }
+
+ SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0xff);
+ SDL_RenderClear(renderer);
+
+ // draw
+ memset(frame, 0, WINDOW_HEIGHT * WINDOW_WIDTH * sizeof(uint32_t));
+ for (int i = 0; i < APPLE_HEIGHT; ++i) {
+ int y = i * WINDOW_Y_SCALE;
+ int line =
+ APPLE_HIRES0 |
+ (i >> 6) * 40 |
+ ((i & 0x38) << 4) |
+ ((i & 7) << 10);
+ for (int j = 0; j < 40; ++j) {
+ int data = mem[line + j];
+ int hibit = data >> 7;
+ for (int k = 0; k < 7; ++k) {
+ if (data & 1) {
+ int x = (((j * 7 + k) << 1) + hibit) * WINDOW_X_SCALE;
+ for (int l = 0; l < WINDOW_X_SCALE * 2; ++l) {
+ uint32_t v = argb[x & (WINDOW_X_SCALE * 4 - 1)];
+ for (int m = 0; m < WINDOW_Y_SCALE; ++m)
+ frame[y + m][x] = v;
+ ++x;
+ }
+ }
+ data >>= 1;
+ }
+ }
+ }
+ SDL_UpdateTexture(texture, NULL, frame, WINDOW_WIDTH * sizeof(uint32_t));
+ SDL_RenderCopy(renderer, texture, NULL, NULL);
+ SDL_RenderPresent(renderer);
+ SDL_Delay(1);
+
+ int i, j;
+#if TRACE
+ struct pc_pair pc_pair = {vrEmu6502GetPC(cpu), 0};
+ for (int k = 0; k < INSTRS_PER_UPDATE; ++k) {
+ i = vrEmu6502Run(cpu, 1, &j);
+ nb_instructions += i;
+ nb_cycles += j;
+ if (i == 0)
+ break;
+
+ pc_pair.pc1 = vrEmu6502GetPC(cpu);
+ bool show_trace = false;
+
+ int hash = (19 * pc_pair.pc0 + 37 * pc_pair.pc1) % N_PC_PAIRS;
+ for (int i = 0; i < N_PC_PAIRS; ++i) {
+ if (
+ pc_pairs[hash].pc0 == pc_pair.pc0 &&
+ pc_pairs[hash].pc1 == pc_pair.pc1
+ )
+ break;
+ if (
+ pc_pairs[hash].pc0 == 0xffff &&
+ pc_pairs[hash].pc1 == 0xffff
+ ) {
+ pc_pairs[hash] = pc_pair;
+ show_trace = true;
+ break;
+ }
+ ++hash;
+ if (hash >= N_PC_PAIRS)
+ hash = 0;
+ }
+
+ uint8_t regs[N_TRACE_REGS] = {
+ vrEmu6502GetAcc(cpu),
+ vrEmu6502GetX(cpu),
+ vrEmu6502GetY(cpu),
+ vrEmu6502GetStackPointer(cpu),
+ vrEmu6502GetStatus(cpu)
+ };
+ for (int i = 0; i < N_TRACE_REGS; ++i) {
+ if (regs[i] < trace[pc_pair.pc1][i].min_unsigned) {
+ trace[pc_pair.pc1][i].min_unsigned = regs[i];
+ show_trace = true;
+ }
+ if ((int8_t)regs[i] < trace[pc_pair.pc1][i].min_signed) {
+ trace[pc_pair.pc1][i].min_signed = (int8_t)regs[i];
+ show_trace = true;
+ }
+ if (~regs[i] & trace[pc_pair.pc1][i].min_bitwise) {
+ trace[pc_pair.pc1][i].min_bitwise &= regs[i];
+ show_trace = true;
+ }
+ if (regs[i] > trace[pc_pair.pc1][i].max_unsigned) {
+ trace[pc_pair.pc1][i].max_unsigned = regs[i];
+ show_trace = true;
+ }
+ if ((int8_t)regs[i] > trace[pc_pair.pc1][i].max_signed) {
+ trace[pc_pair.pc1][i].max_signed = (int8_t)regs[i];
+ show_trace = true;
+ }
+ if (regs[i] & ~trace[pc_pair.pc1][i].max_bitwise) {
+ trace[pc_pair.pc1][i].max_bitwise |= regs[i];
+ show_trace = true;
+ }
+ }
+
+ if (show_trace)
+ fprintf(
+ stderr,
+ "pc=%04x,%04x a=%02x%02x%02x,%02x%02x%02x x=%02x%02x%02x,%02x%02x%02x y=%02x%02x%02x,%02x%02x%02x s=%02x%02x%02x,%02x%02x%02x p=%02x%02x%02x,%02x%02x%02x\n",
+ pc_pair.pc0,
+ pc_pair.pc1,
+ trace[pc_pair.pc1][TRACE_REG_A].min_unsigned,
+ (uint8_t)trace[pc_pair.pc1][TRACE_REG_A].min_signed,
+ trace[pc_pair.pc1][TRACE_REG_A].min_bitwise,
+ trace[pc_pair.pc1][TRACE_REG_A].max_unsigned,
+ (uint8_t)trace[pc_pair.pc1][TRACE_REG_A].max_signed,
+ trace[pc_pair.pc1][TRACE_REG_A].max_bitwise,
+ trace[pc_pair.pc1][TRACE_REG_X].min_unsigned,
+ (uint8_t)trace[pc_pair.pc1][TRACE_REG_X].min_signed,
+ trace[pc_pair.pc1][TRACE_REG_X].min_bitwise,
+ trace[pc_pair.pc1][TRACE_REG_X].max_unsigned,
+ (uint8_t)trace[pc_pair.pc1][TRACE_REG_X].max_signed,
+ trace[pc_pair.pc1][TRACE_REG_X].max_bitwise,
+ trace[pc_pair.pc1][TRACE_REG_Y].min_unsigned,
+ (uint8_t)trace[pc_pair.pc1][TRACE_REG_Y].min_signed,
+ trace[pc_pair.pc1][TRACE_REG_Y].min_bitwise,
+ trace[pc_pair.pc1][TRACE_REG_Y].max_unsigned,
+ (uint8_t)trace[pc_pair.pc1][TRACE_REG_Y].max_signed,
+ trace[pc_pair.pc1][TRACE_REG_Y].max_bitwise,
+ trace[pc_pair.pc1][TRACE_REG_S].min_unsigned,
+ (uint8_t)trace[pc_pair.pc1][TRACE_REG_S].min_signed,
+ trace[pc_pair.pc1][TRACE_REG_S].min_bitwise,
+ trace[pc_pair.pc1][TRACE_REG_S].max_unsigned,
+ (uint8_t)trace[pc_pair.pc1][TRACE_REG_S].max_signed,
+ trace[pc_pair.pc1][TRACE_REG_S].max_bitwise,
+ trace[pc_pair.pc1][TRACE_REG_P].min_unsigned,
+ (uint8_t)trace[pc_pair.pc1][TRACE_REG_P].min_signed,
+ trace[pc_pair.pc1][TRACE_REG_P].min_bitwise,
+ trace[pc_pair.pc1][TRACE_REG_P].max_unsigned,
+ (uint8_t)trace[pc_pair.pc1][TRACE_REG_P].max_signed,
+ trace[pc_pair.pc1][TRACE_REG_P].max_bitwise
+ );
+ pc_pair.pc0 = pc_pair.pc1;
+ }
+#else
+ i = vrEmu6502Run(cpu, INSTRS_PER_UPDATE, &j);
+ nb_instructions += i;
+ nb_cycles += j;
+ if (i < INSTRS_PER_UPDATE)
+ break;
+#endif
+ }
+
+quit:
+ vrEmu6502Destroy(cpu);
+
+ SDL_DestroyRenderer(renderer);
+ SDL_DestroyWindow(window);
+ SDL_Quit();
+
+ if (timing)
+ fprintf(
+ stderr,
+ "%lu instructions executed on %lu cycles\n",
+ nb_instructions,
+ nb_cycles
+ );
+ exit(exit_flag & 0xff);
+}