mirror of
https://github.com/minkcv/vm.git
synced 2026-10-23 17:18:38 +00:00
406 lines
14 KiB
C
406 lines
14 KiB
C
#include "vm.h"
|
|
#include "opcodes.h"
|
|
#include "instruction.h"
|
|
#include <SDL.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
|
|
// Creates a vm with the supplied code
|
|
VM* createVM(uint16_t* code, uint8_t* rom, Display* display, int debugMode)
|
|
{
|
|
VM* vm = (VM*)malloc(sizeof(VM));
|
|
vm->code = code;
|
|
vm->pc = code;
|
|
vm->debugMode = debugMode;
|
|
vm->breakState = 0;
|
|
vm->step = 0;
|
|
vm->display = display;
|
|
vm->gpu = createGPU();
|
|
vm->ipu = createIPU();
|
|
memset(vm->memory, 0, sizeof(vm->memory[0][0]) * MEMORY_SEGMENT_COUNT * MEMORY_SEGMENT_SIZE);
|
|
memset(vm->regs, 0, sizeof(vm->regs[0]) * REGISTER_COUNT);
|
|
if (rom != NULL) // ROM is optional
|
|
memcpy(vm->memory[128], rom, 128 * MEMORY_SEGMENT_SIZE);
|
|
return vm;
|
|
}
|
|
|
|
// Starts the fetch, decode, execute cycle
|
|
// of a vm from the start of its code
|
|
void run(VM* vm)
|
|
{
|
|
uint32_t displayStartTime = SDL_GetTicks();
|
|
uint32_t displayWaitTime = 16; // 16ms = about 60 refreshes per second
|
|
uint32_t cpuStartTime = SDL_GetTicks();
|
|
uint32_t cpuInstructionCount = 0;
|
|
|
|
// Enforce the instructions per second limit in sync with the display refreshes
|
|
// 500,000 instructions per second is almost the same as 8064 instructions per 16 milliseconds
|
|
uint32_t instructionsPerSecondFactor = 62;
|
|
|
|
int wait = 0;
|
|
SDL_Event event;
|
|
SDL_PollEvent(&event);
|
|
while (event.type != SDL_QUIT)
|
|
{
|
|
while (SDL_PollEvent(&event))
|
|
{
|
|
if (event.key.keysym.sym == SDLK_ESCAPE && event.type == SDL_KEYUP)
|
|
{
|
|
printf("Exiting at user escape\n");
|
|
exit(0);
|
|
}
|
|
if (vm->debugMode && event.type == SDL_KEYUP)
|
|
{
|
|
handleDebugKey(vm, event.key.keysym.sym);
|
|
}
|
|
if (event.type == SDL_KEYDOWN || event.type == SDL_KEYUP)
|
|
{
|
|
if (event.key.repeat == 0)
|
|
{
|
|
updateIPU(vm->ipu, event.key, vm->memory);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (vm->breakState)
|
|
continue;
|
|
|
|
// Executing
|
|
if (!wait)
|
|
{
|
|
uint16_t instr = *(vm->pc);
|
|
Instruction* decoded = decode(instr);
|
|
exec(vm, decoded);
|
|
vm->pc++;
|
|
cpuInstructionCount++;
|
|
free(decoded);
|
|
|
|
if (cpuInstructionCount > INSTRUCTIONS_PER_SECOND / instructionsPerSecondFactor)
|
|
wait = 1;
|
|
}
|
|
if ((SDL_GetTicks() - cpuStartTime) > 1000 / instructionsPerSecondFactor)
|
|
{
|
|
// 16 milliseconds have passed
|
|
// Reset the instruction count and timer
|
|
if (cpuInstructionCount < INSTRUCTIONS_PER_SECOND / instructionsPerSecondFactor)
|
|
{
|
|
//printf("Running below desired instructions per second\n");
|
|
//printf("Desired: %d Actual: %d\n", INSTRUCTIONS_PER_SECOND / instructionsPerSecondFactor, cpuInstructionCount);
|
|
}
|
|
cpuInstructionCount = 0;
|
|
cpuStartTime = SDL_GetTicks();
|
|
wait = 0;
|
|
}
|
|
if (displayStartTime + displayWaitTime < SDL_GetTicks())
|
|
{
|
|
updateGPU(vm->gpu, vm->memory);
|
|
if (vm->gpu->active)
|
|
{
|
|
readSpritesFromMem(vm->gpu, vm->memory);
|
|
drawSprites(vm->gpu, vm->memory);
|
|
updateDisplay(vm->display, vm->gpu);
|
|
}
|
|
displayStartTime = SDL_GetTicks();
|
|
}
|
|
if (vm->step)
|
|
vm->breakState = 1;
|
|
}
|
|
}
|
|
|
|
void handleDebugKey(VM* vm, SDL_Keycode key)
|
|
{
|
|
if (key == SDLK_F2)
|
|
{
|
|
// Continue
|
|
vm->breakState = 0;
|
|
vm->step = 0;
|
|
printf("Resuming execution\n");
|
|
}
|
|
if (key == SDLK_F3)
|
|
{
|
|
// Break all
|
|
vm->breakState = 1;
|
|
printf("Entering break state\n");
|
|
}
|
|
if (key == SDLK_F4)
|
|
{
|
|
// Step
|
|
vm->breakState = 0;
|
|
vm->step = 1;
|
|
printf("Stepping\n");
|
|
}
|
|
if (key == SDLK_F9)
|
|
{
|
|
// Print program counter as segment.offset, disassembly of current instruction, and registers
|
|
int instructionCount = vm->pc - vm->code;
|
|
int instructionSegment = instructionCount / JUMP_SEGMENT_SIZE;
|
|
int instructionOffset = instructionCount % JUMP_SEGMENT_SIZE;
|
|
printf("PC: %03d.%03d 0x%02X.0x%02X\n",
|
|
instructionSegment, instructionOffset,
|
|
instructionSegment, instructionOffset);
|
|
char* assembly = malloc(sizeof(char) * 256);
|
|
memset(assembly, 0, sizeof(char) * 256);
|
|
Instruction* instr = decode(*(vm->pc));
|
|
disassemble(instr, assembly);
|
|
free(instr);
|
|
printf("ASM: %s\n", assembly);
|
|
free(assembly);
|
|
int i;
|
|
for (i = 0; i < REGISTER_COUNT; i++)
|
|
printf("r%02d: %03d (0x%02X)\n", i, vm->regs[i], vm->regs[i]);
|
|
}
|
|
if (key == SDLK_F10)
|
|
{
|
|
// Dump memory segments 0 through 63 (inclusive)
|
|
int segment;
|
|
int offset;
|
|
int rowLength = 16;
|
|
for (segment = 0; segment < 64; segment++)
|
|
{
|
|
for (offset = 0; offset < 256; offset++)
|
|
{
|
|
if (offset % rowLength == 0)
|
|
printf("\n0x%02X.0x%02X: 0x", segment, offset);
|
|
printf("%02X", vm->memory[segment][offset]);
|
|
}
|
|
}
|
|
printf("\n");
|
|
}
|
|
if (key == SDLK_F11)
|
|
{
|
|
// Dump memory segments 64 through 127 (inclusive)
|
|
int segment;
|
|
int offset;
|
|
int rowLength = 16;
|
|
for (segment = 64; segment < 128; segment++)
|
|
{
|
|
for (offset = 0; offset < 256; offset++)
|
|
{
|
|
if (offset % rowLength == 0)
|
|
printf("\n0x%02X.0x%02X: 0x", segment, offset);
|
|
printf("%02X", vm->memory[segment][offset]);
|
|
}
|
|
}
|
|
printf("\n");
|
|
}
|
|
if (key == SDLK_F12)
|
|
{
|
|
// Disassemble next 16 instructions
|
|
int i;
|
|
for (i = 0; i < 16; i++)
|
|
{
|
|
int instructionCount = vm->pc - vm->code + i;
|
|
if (instructionCount > JUMP_SEGMENT_SIZE * JUMP_SEGMENT_SIZE)
|
|
continue; // End of program, can't keep reading instructions.
|
|
int instructionSegment = instructionCount / JUMP_SEGMENT_SIZE;
|
|
int instructionOffset = instructionCount % JUMP_SEGMENT_SIZE;
|
|
char* assembly = malloc(sizeof(char) * 256);
|
|
memset(assembly, 0, sizeof(char) * 256);
|
|
Instruction* instr = decode(*(vm->pc + i));
|
|
disassemble(instr, assembly);
|
|
free(instr);
|
|
// Print star for current instruction
|
|
if (i == 0)
|
|
printf("*");
|
|
else
|
|
printf(" ");
|
|
|
|
// Print disassembly with segment.offset address
|
|
printf(" %-30s ; %03d.%03d 0x%02X.0x%02X\n", assembly,
|
|
instructionSegment, instructionOffset,
|
|
instructionSegment, instructionOffset);
|
|
free(assembly);
|
|
}
|
|
}
|
|
}
|
|
|
|
void disassemble(Instruction* instr, char* assembly)
|
|
{
|
|
// If the instructions reads the last 2 arguments as a constant
|
|
uint8_t constant = ((instr->arg1 << 4) & 0x00F0) + instr->arg2;
|
|
switch(instr->opcode)
|
|
{
|
|
case EXT:
|
|
switch(instr->arg0)
|
|
{
|
|
case EXT_HALT:
|
|
sprintf(assembly, "HALT");
|
|
break;
|
|
case EXT_CPY:
|
|
sprintf(assembly, "CPY r%02d r%02d", instr->arg1, instr->arg2);
|
|
break;
|
|
case EXT_NOT:
|
|
sprintf(assembly, "NOT %02d %02d", instr->arg1, instr->arg2);
|
|
break;
|
|
case EXT_LSL:
|
|
sprintf(assembly, "LSL r%02d r%02d", instr->arg1, instr->arg2);
|
|
break;
|
|
case EXT_LSR:
|
|
sprintf(assembly, "LSR r%02d r%02d", instr->arg1, instr->arg2);
|
|
break;
|
|
case EXT_JMP:
|
|
sprintf(assembly, "JMP r%02d r%02d", instr->arg1, instr->arg2);
|
|
break;
|
|
case EXT_NOP:
|
|
sprintf(assembly, "NOP");
|
|
break;
|
|
case EXT_BRKP:
|
|
sprintf(assembly, "BRKP");
|
|
break;
|
|
}
|
|
break;
|
|
case ADD:
|
|
sprintf(assembly, "ADD r%02d r%02d r%02d", instr->arg0, instr->arg1, instr->arg2);
|
|
break;
|
|
case SUB:
|
|
sprintf(assembly, "SUB r%02d r%02d r%02d", instr->arg0, instr->arg1, instr->arg2);
|
|
break;
|
|
case ADDC:
|
|
sprintf(assembly, "ADDC r%02d #%03d ; (0x%04X)", instr->arg0, constant, constant);
|
|
break;
|
|
case SUBC:
|
|
sprintf(assembly, "SUBC r%02d #%03d ; (0x%04X)", instr->arg0, constant, constant);
|
|
break;
|
|
case CMP:
|
|
sprintf(assembly, "CMP r%02d r%02d r%02d", instr->arg0, instr->arg1, instr->arg2);
|
|
break;
|
|
case JLT:
|
|
sprintf(assembly, "JLT r%02d r%02d r%02d", instr->arg0, instr->arg1, instr->arg2);
|
|
break;
|
|
case JGT:
|
|
sprintf(assembly, "JGT r%02d r%02d r%02d", instr->arg0, instr->arg1, instr->arg2);
|
|
break;
|
|
case JEQ:
|
|
sprintf(assembly, "JEQ r%02d r%02d r%02d", instr->arg0, instr->arg1, instr->arg2);
|
|
break;
|
|
case LDR:
|
|
sprintf(assembly, "LDR r%02d r%02d r%02d", instr->arg0, instr->arg1, instr->arg2);
|
|
break;
|
|
case STR:
|
|
sprintf(assembly, "STR r%02d r%02d r%02d", instr->arg0, instr->arg1, instr->arg2);
|
|
break;
|
|
case LRC:
|
|
sprintf(assembly, "LRC r%02d #%03d ; (0x%04X)", instr->arg0, constant, constant);
|
|
break;
|
|
case AND:
|
|
sprintf(assembly, "AND r%02d r%02d r%02d", instr->arg0, instr->arg1, instr->arg2);
|
|
break;
|
|
case OR:
|
|
sprintf(assembly, "OR r%02d r%02d r%02d", instr->arg0, instr->arg1, instr->arg2);
|
|
break;
|
|
case XOR:
|
|
sprintf(assembly, "XOR r%02d r%02d r%02d", instr->arg0, instr->arg1, instr->arg2);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Decodes a uint16_t instruction into a 16 bit instruction struct
|
|
Instruction* decode(uint16_t instr)
|
|
{
|
|
Instruction* decoded = malloc(sizeof(Instruction));
|
|
uint16_t clean = 0x000F;
|
|
decoded->opcode = instr >> 12 & clean;
|
|
decoded->arg0 = instr >> 8 & clean;
|
|
decoded->arg1 = instr >> 4 & clean;
|
|
decoded->arg2 = instr & clean;
|
|
return decoded;
|
|
}
|
|
|
|
// Executes an instruction
|
|
void exec(VM* vm, Instruction* instr)
|
|
{
|
|
switch(instr->opcode)
|
|
{
|
|
case EXT:
|
|
switch(instr->arg0)
|
|
{
|
|
case EXT_HALT:
|
|
printf("Exiting at halt instruction (instruction %ld)\n", (vm->pc - vm->code));
|
|
exit(0);
|
|
break;
|
|
case EXT_CPY:
|
|
vm->regs[instr->arg1] = vm->regs[instr->arg2];
|
|
break;
|
|
case EXT_NOT:
|
|
vm->regs[instr->arg1] = ~(vm->regs[instr->arg2]);
|
|
break;
|
|
case EXT_LSL:
|
|
vm->regs[instr->arg1] = vm->regs[instr->arg1] << vm->regs[instr->arg2];
|
|
break;
|
|
case EXT_LSR:
|
|
vm->regs[instr->arg1] = vm->regs[instr->arg1] >> vm->regs[instr->arg2];
|
|
break;
|
|
case EXT_JMP:
|
|
vm->pc = vm->code + (vm->regs[instr->arg1] * JUMP_SEGMENT_SIZE) + vm->regs[instr->arg2] - 1;
|
|
break;
|
|
case EXT_NOP:
|
|
break;
|
|
case EXT_BRKP:
|
|
if (vm->debugMode)
|
|
{
|
|
vm->breakState = 1;
|
|
printf("Entering break state\n");
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
case ADD:
|
|
vm->regs[instr->arg0] = vm->regs[instr->arg1] + vm->regs[instr->arg2];
|
|
break;
|
|
case SUB:
|
|
vm->regs[instr->arg0] = vm->regs[instr->arg1] - vm->regs[instr->arg2];
|
|
break;
|
|
case ADDC:
|
|
vm->regs[instr->arg0] += ((instr->arg1 << 4) & 0x00F0) + instr->arg2;
|
|
break;
|
|
case SUBC:
|
|
vm->regs[instr->arg0] -= ((instr->arg1 << 4) & 0x00F0) + instr->arg2;
|
|
break;
|
|
case CMP:
|
|
if (vm->regs[instr->arg1] < vm->regs[instr->arg2])
|
|
vm->regs[instr->arg0] = 0;
|
|
else if (vm->regs[instr->arg1] > vm->regs[instr->arg2])
|
|
vm->regs[instr->arg0] = 2;
|
|
else
|
|
vm->regs[instr->arg0] = 1;
|
|
break;
|
|
case JLT:
|
|
if (vm->regs[instr->arg0] == 0)
|
|
vm->pc = vm->code + (vm->regs[instr->arg1] * JUMP_SEGMENT_SIZE) + vm->regs[instr->arg2] - 1;
|
|
break;
|
|
case JGT:
|
|
if (vm->regs[instr->arg0] == 2)
|
|
vm->pc = vm->code + (vm->regs[instr->arg1] * JUMP_SEGMENT_SIZE) + vm->regs[instr->arg2] - 1;
|
|
break;
|
|
case JEQ:
|
|
if (vm->regs[instr->arg0] == 1)
|
|
vm->pc = vm->code + (vm->regs[instr->arg1] * JUMP_SEGMENT_SIZE) + vm->regs[instr->arg2] - 1;
|
|
break;
|
|
case LDR:
|
|
vm->regs[instr->arg0] = vm->memory[vm->regs[instr->arg1]][vm->regs[instr->arg2]];
|
|
break;
|
|
case STR:
|
|
if (vm->regs[instr->arg1] < 128) // Segment is not part of ROM
|
|
vm->memory[vm->regs[instr->arg1]][vm->regs[instr->arg2]] = vm->regs[instr->arg0];
|
|
else
|
|
{
|
|
printf("Attempted illegal write to ROM\n");
|
|
exit(1);
|
|
}
|
|
break;
|
|
case LRC:
|
|
vm->regs[instr->arg0] = ((instr->arg1 << 4) & 0x00F0) + instr->arg2;
|
|
break;
|
|
case AND:
|
|
vm->regs[instr->arg0] = vm->regs[instr->arg1] & vm->regs[instr->arg2];
|
|
break;
|
|
case OR:
|
|
vm->regs[instr->arg0] = vm->regs[instr->arg1] | vm->regs[instr->arg2];
|
|
break;
|
|
case XOR:
|
|
vm->regs[instr->arg0] = vm->regs[instr->arg1] ^ vm->regs[instr->arg2];
|
|
break;
|
|
}
|
|
}
|
|
|