Refactor/reorganize codebase

This commit is contained in:
Horseshoe Crab 2021-04-27 02:20:19 -04:00
parent 7eb2c9bfc3
commit 26457be290
5 changed files with 442 additions and 436 deletions

View File

@ -9,35 +9,31 @@
# Note: I understand this makefile is not as optimal as it could/should be
CC = gcc
CFLAGS ?= -Wall
DEBUG = -g -ggdb -D DEBUG -lreadline
#HEADERS = vm.h
SRCS = main.c
CFLAGS ?= -Wall -Wpedantic -Wextra -std=c99
ROM = rom.bin
OUT = hard
TRACE_SUFFIX =
all: $(OUT)
all: hard
# Useful for some projects
#%.o: %c %.h
# $(CC) $(CLFAGS) -c $^
trace: CFLAGS += -g -ggdb -DTRACE -lreadline
trace: TRACE_SUFFIX = -trace
trace: hard
$(OUT): ./src/$(SRCS)
./bin/ass.sh ./src/rom.asm ./src/zeropage.incbin
xxd -i ./src/$(ROM) > ./src/rom.h
$(CC) $(CFLAGS) -o ./bin/$@ $^
strip ./bin/$@
# TODO: add rule to make rom.h
debug: ./src/$(SRCS)
./bin/ass.sh ./src/rom.asm ./src/zeropage.incbin
xxd -i ./src/$(ROM) > ./src/rom.h
$(CC) $(CFLAGS) $(DEBUG) -o ./bin/$(OUT)-$@ $^
bin/%.o: src/%.c
$(CC) $(CFLAGS) -o $@ -c $^
disass: ./src/disass.c
$(CC) $(CFLAGS) $(DEBUG) -o ./bin/$@ $^
bin/vm.o:
bin/main.o:
.PHONY: clean
clean:
rm -f ./bin/$(BIN)*
rm -f ./bin/disass
hard: bin/main.o bin/vm.o
$(CC) $(CFLAGS) -o bin/$@$(TRACE_SUFFIX) $^
clean: cleano
rm -f bin/hard bin/hard-trace
cleano:
rm -f bin/main.o bin/vm.o
.PHONY: all trace clean

View File

View File

@ -33,9 +33,8 @@ int main()
for (uint8_t i = 0; i < strlen(flag_input); ++i)
memory[128+i] = (uint8_t)flag_input[i];
#ifdef DEBUG
printf("[DEBUG MODE]: Running interpreter...\n"); // DEBUG
is_being_traced = 1;
#ifdef TRACE
printf("[DEBUG MODE]: Running VM in debugger...\n");
#endif
vm_run(&cpu, memory);

384
src/vm.c Normal file
View File

@ -0,0 +1,384 @@
/*
3ByteBadVM: a bad virtual machine using 3-byte instructions
x1phosura 2021
VM specifications
TODO: document VM stuff here once I write it in Markdown
use assert()s to make sure pc, sp, etc... are valid
*/
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef TRACE
// readline is L I T
#include <readline/readline.h>
#include <readline/history.h>
#endif
#include "vm.h"
#ifdef TRACE // TRACE_VARS
struct TRACE_T trace_state;
char *trace_bad_cmd = "Unrecognized command.\n";
char *trace_cmd_help = "Supported commands are:\n"
"s: step single instruction\n"
"c: continue code execution until breakpoint or end\n"
"b <addr>: set breakpoint at given memory address\n"
"pb: print current breakpoints\n"
"pr: print register contents\n"
"ps: print stack contents\n"
"pm <addr> <num>: print <num> bytes of memory starting at <addr>\n"
"pz: print zero page (1st 256 bytes)\n"
"h: print VM debugger command help message\n"
"q: quit VM debugger\n";
#endif // TRACE_VARS
static inline void push(struct CPU *cpu, uint8_t val)
{
cpu->stack[cpu->sp] = val;
if (cpu->sp < STACK_LIM-1)
++cpu->sp;
}
static inline uint8_t pop(struct CPU *cpu)
{
uint8_t val;
if (cpu->sp > 0) {
val = cpu->stack[cpu->sp-1];
--cpu->sp;
} else
return 0;
return val;
}
/* vm_do_instruction:
*/
uint16_t vm_do_instruction(struct CPU *cpu, uint8_t *mem, uint8_t instr[3])
{
uint16_t pc = cpu->pc;
uint16_t sp = cpu->sp;
uint8_t val = 0;
uint8_t operands[2];
uint8_t opcode = instr[0];
operands[0] = instr[1];
operands[1] = instr[2];
// vvv- this format needed if operands treated as single 16-bit value
uint16_t operand = ((uint16_t)instr[2] * 256) + instr[1];
#ifdef TRACE
printf("0x%04x: ", pc);
print_op_decoded(instr, true);
#endif
/* Note: the below code will likely be very unsafe. */
switch (opcode) {
case HALT:
cpu->state = STOPPED;
break;
case PUSH:
push(cpu, mem[operand]);
pc += 3;
break;
case POP:
mem[operand] = pop(cpu);
pc += 3;
break;
case PUSHI:
push(cpu, operands[0]);
pc += 3;
break;
case LDLR: // TODO: fix!! This is broken
// read little-endian
cpu->lr = (uint16_t)(mem[operand+1]); // set MSB
cpu->lr = cpu->lr << 8; // set MSB
cpu->lr += (uint16_t)(mem[operand]); // set LSB
pc += 3;
break;
case STLR:
// write little-endian
mem[operand] = (uint8_t)((cpu->lr) & 0x00ff); // set LSB
mem[operand+1] = (uint8_t)((cpu->lr >> 8) & 0x00ff); // set MSB
pc += 3;
break;
case SETI: // peek ToS, write to memory
mem[operand] = cpu->stack[sp-1];
pc += 3;
break;
case DUP:
val = cpu->stack[sp-1];
push(cpu, val);
pc += 3;
break;
case ADD:
val = pop(cpu);
val += pop(cpu);
push(cpu, val);
pc += 3;
break;
case SUB:
val = pop(cpu);
val = ~val;
++val;
val += pop(cpu);
push(cpu, val); // subtract 2nd from top by top of stack
pc += 3;
break;
case XOR:
val = pop(cpu);
val ^= pop(cpu);
push(cpu, val);
pc += 3;
break;
case CALL:
cpu->lr = pc+3;
return operand;
break;
case RET:
return cpu->lr;
break;
case JMP:
return operand;
break;
case BEQ:
if (sp > 1) // if 2 or more items on stack
if (sp > 0 && cpu->stack[sp-1] == cpu->stack[sp-2])
return operand;
pc += 3;
break;
case BNQ:
if (sp > 1) // if 2 or more items on stack
if (sp > 0 && cpu->stack[sp-1] != cpu->stack[sp-2])
return operand;
pc += 3;
break;
case NOP:
pc += 3;
break;
default:
/* EVIL idea: have illegal instructions act as NOPs! */
pc += 3;
}
return pc;
}
static inline void vm_fetch_instruction(uint16_t pc, uint8_t *mem,
uint8_t instr[3])
{
assert(pc < RAM_SIZE - 3);
instr[0] = mem[pc];
instr[1] = mem[pc+1];
instr[2] = mem[pc+2]; // finish instruction fetch
}
#ifdef TRACE // TRACE_FUNCS
void print_op_decoded(uint8_t i[3], bool pargs)
{
char fmt[] = " 0x%02x 0x%02x";
uint8_t opcode = i[0];
switch(opcode) {
case HALT: printf("HALT"); break;
case PUSH: printf("PUSH"); if (pargs) printf(fmt, i[1], i[2]); break;
case POP: printf("POP"); if (pargs) printf(fmt, i[1], i[2]); break;
case PUSHI: printf("PUSHI"); if (pargs) printf(fmt, i[1], i[2]); break;
case LDLR: printf("LDLR"); if (pargs) printf(fmt, i[1], i[2]); break;
case STLR: printf("STLR"); if (pargs) printf(fmt, i[1], i[2]); break;
case SETI: printf("SETI"); if (pargs) printf(fmt, i[1], i[2]); break;
case DUP: printf("DUP"); break;
case ADD: printf("ADD"); break;
case SUB: printf("SUB"); break;
case XOR: printf("XOR"); break;
case CALL: printf("CALL"); if (pargs) printf(fmt, i[1], i[2]); break;
case RET: printf("RET"); break;
case JMP: printf("JMP"); if (pargs) printf(fmt, i[1], i[2]); break;
case BEQ: printf("BEQ"); if (pargs) printf(fmt, i[1], i[2]); break;
case BNQ: printf("BNQ"); if (pargs) printf(fmt, i[1], i[2]); break;
case NOP: printf("NOP"); break;
default: printf("0x%02x 0x%02x 0x%02x", i[0], i[1], i[2]);
}
putchar('\n');
}
void print_vm_registers(struct CPU *cpu)
{
uint16_t pc = cpu->pc;
uint16_t lr = cpu->lr;
uint16_t sp = cpu->sp;
//uint16_t a = cpu->a;
printf("pc = %#x, lr = %#x, sp = %#x\n", pc, lr, sp);
}
void print_vm_stack(struct CPU *cpu)
{
uint16_t sp = cpu->sp;
for (uint8_t i = 0; i < sp; ++i) {
if (i % 16 == 0)
putchar('\n');
printf("%02x ", cpu->stack[i]);
}
putchar('\n');
for (int8_t j = 0; j < sp % 17; ++j)
printf(" ");
printf("^sp = 0x%02x\n", sp);
}
void print_vm_memory(uint8_t *mem, uint16_t start_addr, uint16_t num_bytes)
{
for (uint16_t i = 0; i < num_bytes; ++i) {
if (i % 16 == 0)
printf("\n0x%04x: ", start_addr+i);
printf("%02x ", mem[start_addr + i]);
}
putchar('\n');
}
void vm_trace(struct CPU *cpu, uint8_t *mem, struct TRACE_T *tstate)
{
char *command;
//char command[24];
int mem_start = 0;
int num_bytes = 0;
rl_bind_key('\t', rl_insert); // make readline treat tabs and tabs
if (cpu->pc == tstate->breakpoint) {
printf("Breakpoint reached!\n");
tstate->mode = STEP;
}
while(tstate->mode == STEP) {
if ((command = readline("trace> ")) == NULL) {
printf("Error: readline returned NULL. Aborting...\n");
exit(0);
}
// Note: technically, command should eventually be freed, but
// it's not actually that important here, and the OS will
// reclaim heap space when the program finishes anyway.
if (strlen(command) > 0)
add_history(command);
// yeah, yeah, I KNOW a switch-case would be better for this
if (command[0] == 's') {
break;
} else if (command[0] == 'b') {
int addr = 0;
if (sscanf(command+1, "%i", &addr) == EOF)
printf("%s\n%s", trace_bad_cmd, trace_cmd_help);
tstate->breakpoint = (uint16_t)addr;
printf("Set breakpoint at address 0x%2x\n", addr);
} else if (command[0] == 'p') {
switch (command[1]) {
case 'b':
printf("Current breakpoint at 0x%2x\n",
tstate->breakpoint);
break;
case 'r':
print_vm_registers(cpu);
break;
case 's':
print_vm_stack(cpu);
break;
case 'z':
print_vm_memory(mem, 0, 0x100);
break;
case 'm':
if (sscanf(command+2,"%i %i", &mem_start,
&num_bytes) == EOF)
; // So far, sscanf always returns EOF
// (idk why). TODO: fix eventually
printf("Dumping %d bytes of memory starting "
"at 0x%04x\n", num_bytes, mem_start);
print_vm_memory(mem, (uint16_t)mem_start,
(uint16_t)num_bytes);
break;
default:
printf("%s\n%s", trace_bad_cmd, trace_cmd_help);
}
} else if (command[0] == 'd' && command[1] == 'b') {
printf("Deleted breakpoint at 0x%x\n",
tstate->breakpoint);
// vvv - disable breakpoint by not point to instr start
tstate->breakpoint = 0x01;
} else if (command[0] == 'c') {
tstate->mode = CONT;
} else if (command[0] == 'h') {
printf("%s", trace_cmd_help);
} else if (command[0] == 'q') {
printf("Exiting debugger and virtual machine...\n");
free(command);
exit(0);
} else {
printf("%s\n%s", trace_bad_cmd, trace_cmd_help);
}
}
// Idea: maybe return value to exit debugger?
}
#endif // TRACE_FUNCS
void vm_run(struct CPU *cpu, uint8_t *mem)
{
uint16_t pc = cpu->pc;
uint8_t curr_instr[3]; // current instruction
cpu->state = RUNNING;
#ifdef TRACE
trace_state.breakpoint = 0xffff;
trace_state.mode = STEP;
#endif
while (cpu->state == RUNNING) {
#ifdef TRACE
vm_trace(cpu, mem, &trace_state);
#endif
vm_fetch_instruction(pc, mem, curr_instr);
pc = vm_do_instruction(cpu, mem, curr_instr);
cpu->pc = pc;
}
// eventually, maybe make it return some kind of CPU status code
}
/* vm_init(): initialize CPU/memory starting states
*/
void vm_init(struct CPU *cpu, uint8_t *mem, uint8_t *memimage, uint16_t imgsize)
{
uint8_t placeholder_memimage[] = {65, 66, 66, 67, 68, 68, 69, 70, 70,0};
cpu->pc = 0x100; // default: start code execution after zero page
cpu->lr = 0;
cpu->sp = 0;
//cpu->a = 0;
cpu->state = STOPPED;
memset(cpu->stack, 0, 64);
if (memimage == NULL) {
imgsize = sizeof(placeholder_memimage);
memimage = (uint8_t *)placeholder_memimage;
}
for (uint16_t i = 0; i < imgsize; ++i)
mem[i] = memimage[i];
}

441
src/vm.h
View File

@ -1,54 +1,31 @@
/*
3ByteBadVM: a bad virtual machine using 3-byte instructions
x1phosura 2021
VM specifications
TODO: document VM stuff here once I write it in Markdown
use assert()s to make sure pc, sp, etc... are valid
*/
/* x1phosura 2021 */
#pragma once // WAAAAAY better than a dumb header guard ;)
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef DEBUG
// readline is L I T
#include <readline/readline.h>
#include <readline/history.h>
#endif
#define RAM_SIZE 16384 // highest it can be given 16-bit addresses
#define STACK_LIM 64
bool is_being_traced = 0;
enum instructions_t {
HALT = 0,
PUSH = 1,
POP = 2,
PUSHI = 3,
LDLR = 4,
STLR = 5,
SETI = 6,
DUP = 7,
ADD = 8,
SUB = 9,
XOR = 10,
CALL = 11,
RET = 12,
JMP = 13,
BEQ = 14,
BNQ = 15,
// every opcode 0x10-0xfe basically acts like a valid NOP just to be annoying
NOP = 0xff
} instructions;
enum instructions {
HALT = 0,
PUSH = 1,
POP = 2,
PUSHI = 3,
LDLR = 4,
STLR = 5,
SETI = 6,
DUP = 7,
ADD = 8,
SUB = 9,
XOR = 10,
CALL = 11,
RET = 12,
JMP = 13,
BEQ = 14,
BNQ = 15,
// every opcode 0x10-0xfe is basically just a NOP just to be annoying
NOP = 0xff
};
struct CPU {
@ -60,373 +37,23 @@ struct CPU {
enum state_t {STOPPED, RUNNING, PAUSED} state;
};
#ifdef DEBUG // TRACE_STRUCT_DECODE
#ifdef TRACE // TRACE
struct TRACE_T {
uint16_t breakpoint;
enum tmode_t {STEP, CONT} mode;
} trace_state;
};
char *trace_bad_cmd = "Unrecognized command.\n";
char *trace_cmd_help = "Supported commands are:\n"
"s: step single instruction\n"
"c: continue code execution until breakpoint or end\n"
"b <addr>: set breakpoint at given memory address\n"
"pb: print current breakpoints\n"
"pr: print register contents\n"
"ps: print stack contents\n"
"pm <addr> <num>: print <num> bytes of memory starting at <addr>\n"
"pz: print zero page (1st 256 bytes)\n"
"h: print VM debugger command help message\n"
"q: quit VM debugger\n";
void print_op_decoded(uint8_t i[3], bool pargs);
void print_vm_registers(struct CPU *cpu);
void print_vm_stack(struct CPU *cpu);
void print_vm_memory(uint8_t *mem, uint16_t start_addr, uint16_t num_bytes);
void vm_trace(struct CPU *cpu, uint8_t *mem, struct TRACE_T *tstate);
#endif // TRACE
uint16_t vm_do_instruction(struct CPU *cpu, uint8_t *mem, uint8_t instr[3]);
void print_op_decoded(uint8_t i[3], bool pargs)
{
char fmt[] = " 0x%02x 0x%02x";
uint8_t opcode = i[0];
switch(opcode) {
case HALT: printf("HALT"); break;
case PUSH: printf("PUSH"); if (pargs) printf(fmt, i[1], i[2]); break;
case POP: printf("POP"); if (pargs) printf(fmt, i[1], i[2]); break;
case PUSHI: printf("PUSHI"); if (pargs) printf(fmt, i[1], i[2]); break;
case LDLR: printf("LDLR"); if (pargs) printf(fmt, i[1], i[2]); break;
case STLR: printf("STLR"); if (pargs) printf(fmt, i[1], i[2]); break;
case SETI: printf("SETI"); if (pargs) printf(fmt, i[1], i[2]); break;
case DUP: printf("DUP"); break;
case ADD: printf("ADD"); break;
case SUB: printf("SUB"); break;
case XOR: printf("XOR"); break;
case CALL: printf("CALL"); if (pargs) printf(fmt, i[1], i[2]); break;
case RET: printf("RET"); break;
case JMP: printf("JMP"); if (pargs) printf(fmt, i[1], i[2]); break;
case BEQ: printf("BEQ"); if (pargs) printf(fmt, i[1], i[2]); break;
case BNQ: printf("BNQ"); if (pargs) printf(fmt, i[1], i[2]); break;
case NOP: printf("NOP"); break;
default: printf("0x%02x 0x%02x 0x%02x", i[0], i[1], i[2]);
}
putchar('\n');
}
#endif // TRACE_STRUCT_DECODE
static inline void push(struct CPU *cpu, uint8_t val)
{
cpu->stack[cpu->sp] = val;
if (cpu->sp < STACK_LIM-1)
++cpu->sp;
}
static inline uint8_t pop(struct CPU *cpu)
{
uint8_t val;
if (cpu->sp > 0) {
val = cpu->stack[cpu->sp-1];
--cpu->sp;
} else
return 0;
return val;
}
/* vm_do_instruction:
*/
uint16_t vm_do_instruction(struct CPU *cpu, uint8_t *mem, uint8_t instr[3])
{
uint16_t pc = cpu->pc;
uint16_t sp = cpu->sp;
uint8_t val = 0;
uint8_t operands[2];
uint8_t opcode = instr[0];
operands[0] = instr[1];
operands[1] = instr[2];
// vvv- this format needed if operands treated as single 16-bit value
uint16_t operand = ((uint16_t)instr[2] * 256) + instr[1];
#ifdef DEBUG
printf("0x%04x: ", pc);
print_op_decoded(instr, true);
#endif
/* Note: the below code will likely be very unsafe. */
switch (opcode) {
case HALT:
cpu->state = STOPPED;
break;
case PUSH:
push(cpu, mem[operand]);
pc += 3;
break;
case POP:
mem[operand] = pop(cpu);
pc += 3;
break;
case PUSHI:
push(cpu, operands[0]);
pc += 3;
break;
case LDLR: // TODO: fix!! This is broken
// read little-endian
cpu->lr = (uint16_t)(mem[operand+1]); // set MSB
cpu->lr = cpu->lr << 8; // set MSB
cpu->lr += (uint16_t)(mem[operand]); // set LSB
pc += 3;
break;
case STLR:
// write little-endian
mem[operand] = (uint8_t)((cpu->lr) & 0x00ff); // set LSB
mem[operand+1] = (uint8_t)((cpu->lr >> 8) & 0x00ff); // set MSB
pc += 3;
break;
case SETI: // peek ToS, write to memory
mem[operand] = cpu->stack[sp-1];
pc += 3;
break;
case DUP:
val = cpu->stack[sp-1];
push(cpu, val);
pc += 3;
break;
case ADD:
val = pop(cpu);
val += pop(cpu);
push(cpu, val);
pc += 3;
break;
case SUB:
val = pop(cpu);
val = ~val;
++val;
val += pop(cpu);
push(cpu, val); // subtract 2nd from top by top of stack
pc += 3;
break;
case XOR:
val = pop(cpu);
val ^= pop(cpu);
push(cpu, val);
pc += 3;
break;
case CALL:
cpu->lr = pc+3;
return operand;
break;
case RET:
return cpu->lr;
break;
case JMP:
return operand;
break;
case BEQ:
if (sp > 1) // if 2 or more items on stack
if (sp > 0 && cpu->stack[sp-1] == cpu->stack[sp-2])
return operand;
pc += 3;
break;
case BNQ:
if (sp > 1) // if 2 or more items on stack
if (sp > 0 && cpu->stack[sp-1] != cpu->stack[sp-2])
return operand;
pc += 3;
break;
case NOP:
pc += 3;
break;
default:
/* EVIL idea: have illegal instructions act as NOPs! */
pc += 3;
}
return pc;
}
static inline void vm_fetch_instruction(uint16_t pc, uint8_t *mem,
uint8_t instr[3])
{
assert(pc < RAM_SIZE - 3);
instr[0] = mem[pc];
instr[1] = mem[pc+1];
instr[2] = mem[pc+2]; // finish instruction fetch
}
#ifdef DEBUG // TRACE_FUNCS
void print_vm_registers(struct CPU *cpu)
{
uint16_t pc = cpu->pc;
uint16_t lr = cpu->lr;
uint16_t sp = cpu->sp;
//uint16_t a = cpu->a;
printf("pc = %#x, lr = %#x, sp = %#x\n", pc, lr, sp);
}
void print_vm_stack(struct CPU *cpu)
{
uint16_t sp = cpu->sp;
for (uint8_t i = 0; i < sp; ++i) {
if (i % 16 == 0)
putchar('\n');
printf("%02x ", cpu->stack[i]);
}
putchar('\n');
for (int8_t j = 0; j < sp % 17; ++j)
printf(" ");
printf("^sp = 0x%02x\n", sp);
}
void print_vm_memory(uint8_t *mem, uint16_t start_addr, uint16_t num_bytes)
{
for (uint16_t i = 0; i < num_bytes; ++i) {
if (i % 16 == 0)
printf("\n0x%04x: ", start_addr+i);
printf("%02x ", mem[start_addr + i]);
}
putchar('\n');
}
#endif // TRACE_FUNCS
#ifdef DEBUG // VMTRACE
void vm_trace(struct CPU *cpu, uint8_t *mem, struct TRACE_T *tstate)
{
char *command;
//char command[24];
int mem_start = 0;
int num_bytes = 0;
rl_bind_key('\t', rl_insert); // make readline treat tabs and tabs
if (cpu->pc == tstate->breakpoint) {
printf("Breakpoint reached!\n");
tstate->mode = STEP;
}
while(tstate->mode == STEP) {
if ((command = readline("trace> ")) == NULL) {
printf("Error: readline returned NULL. Aborting...\n");
exit(0);
}
// Note: technically, command should eventually be freed, but
// it's not actually that important here, and the OS will
// reclaim heap space when the program finishes anyway.
if (strlen(command) > 0)
add_history(command);
// yeah, yeah, I KNOW a switch-case would be better for this
if (command[0] == 's') {
break;
} else if (command[0] == 'b') {
int addr = 0;
if (sscanf(command+1, "%i", &addr) == EOF)
printf("%s\n%s", trace_bad_cmd, trace_cmd_help);
tstate->breakpoint = (uint16_t)addr;
printf("Set breakpoint at address 0x%2x\n", addr);
} else if (command[0] == 'p') {
switch (command[1]) {
case 'b':
printf("Current breakpoint at 0x%2x\n",
tstate->breakpoint);
break;
case 'r':
print_vm_registers(cpu);
break;
case 's':
print_vm_stack(cpu);
break;
case 'z':
print_vm_memory(mem, 0, 0x100);
break;
case 'm':
if (sscanf(command+2,"%i %i", &mem_start,
&num_bytes) == EOF)
; // So far, sscanf always returns EOF
// (idk why). TODO: fix eventually
printf("Dumping %d bytes of memory starting "
"at 0x%04x\n", num_bytes, mem_start);
print_vm_memory(mem, (uint16_t)mem_start,
(uint16_t)num_bytes);
break;
default:
printf("%s\n%s", trace_bad_cmd, trace_cmd_help);
}
} else if (command[0] == 'd' && command[1] == 'b') {
printf("Deleted breakpoint at 0x%x\n",
tstate->breakpoint);
// vvv - disable breakpoint by not point to instr start
tstate->breakpoint = 0x01;
} else if (command[0] == 'c') {
tstate->mode = CONT;
} else if (command[0] == 'h') {
printf("%s", trace_cmd_help);
} else if (command[0] == 'q') {
printf("Exiting debugger and virtual machine...\n");
free(command);
exit(0);
} else {
printf("%s\n%s", trace_bad_cmd, trace_cmd_help);
}
}
// Idea: maybe return value to exit debugger?
}
#endif // VMTRACE
void vm_run(struct CPU *cpu, uint8_t *mem)
{
uint16_t pc = cpu->pc;
uint8_t curr_instr[3]; // current instruction
cpu->state = RUNNING;
#ifdef DEBUG
trace_state.mode = STEP;
#endif
while (cpu->state == RUNNING) {
#ifdef DEBUG
if (is_being_traced) {
vm_trace(cpu, mem, &trace_state);
}
#endif
vm_fetch_instruction(pc, mem, curr_instr);
pc = vm_do_instruction(cpu, mem, curr_instr);
cpu->pc = pc;
}
// eventually, make it return some kind of CPU status code
}
/* vm_init(): initialize CPU/memory starting states
*/
void vm_init(struct CPU *cpu, uint8_t *mem, uint8_t *memimage, uint16_t imgsize)
{
uint8_t placeholder_memimage[] = {65, 66, 66, 67, 68, 68, 69, 70, 70,0};
cpu->pc = 0x100; // default: start code execution after zero page
cpu->lr = 0;
cpu->sp = 0;
//cpu->a = 0;
cpu->state = STOPPED;
memset(cpu->stack, 0, 64);
if (memimage == NULL) {
imgsize = sizeof(placeholder_memimage);
memimage = (uint8_t *)placeholder_memimage;
}
for (uint16_t i = 0; i < imgsize; ++i)
mem[i] = memimage[i];
//mem[0x100+i] = memimage[i];
#ifdef DEBUG
trace_state.breakpoint = 0xffff;
trace_state.mode = CONT;
#endif
}
void vm_run(struct CPU *cpu, uint8_t *mem);
void vm_init(struct CPU *cpu, uint8_t *mem, uint8_t *memimage,
uint16_t imgsize);