Refactor/reorganize codebase
This commit is contained in:
parent
7eb2c9bfc3
commit
26457be290
48
Makefile
48
Makefile
|
@ -8,36 +8,32 @@
|
|||
|
||||
# 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
|
||||
ROM = rom.bin
|
||||
OUT = hard
|
||||
CC = gcc
|
||||
CFLAGS ?= -Wall -Wpedantic -Wextra -std=c99
|
||||
ROM = rom.bin
|
||||
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
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
441
src/vm.h
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in New Issue