Compare commits
19 Commits
68b7667725
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 74159849c9 | |||
| 8f64b17777 | |||
| f786235064 | |||
| e7350a254f | |||
| be61ecec0f | |||
| ba4121b6c4 | |||
| c5fc6a8493 | |||
| 3bf340d9f2 | |||
| c0da7748d2 | |||
| 14700bdf59 | |||
| afa45ea82d | |||
| 9828ef2811 | |||
| 0504067a51 | |||
| b58b5adf65 | |||
| 80bca7059f | |||
| 82f9644e15 | |||
| bb0709f6ce | |||
| 8e1f83ab31 | |||
| fb3efc77c3 |
@@ -5,7 +5,8 @@ sources = files(
|
|||||||
'src/emu.c',
|
'src/emu.c',
|
||||||
'src/memory.c',
|
'src/memory.c',
|
||||||
'src/instruction.c',
|
'src/instruction.c',
|
||||||
'src/util.c'
|
'src/util.c',
|
||||||
|
'src/device.c'
|
||||||
)
|
)
|
||||||
|
|
||||||
asmSources = files(
|
asmSources = files(
|
||||||
|
|||||||
@@ -5,8 +5,11 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
#include "../instruction.h"
|
#include "../instruction.h"
|
||||||
|
#include "../util.h"
|
||||||
|
|
||||||
// Table of strings to instructions
|
// Table of strings to instructions
|
||||||
struct _stringToInstructionOpcodeTable {char* str; InstructionOpcode opcode;} stringToInstructionOpcodeTable[] = {
|
struct _stringToInstructionOpcodeTable {char* str; InstructionOpcode opcode;} stringToInstructionOpcodeTable[] = {
|
||||||
@@ -39,6 +42,104 @@ InstructionOpcode stringToInstructionOpcode(const char* string) {
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void processBufferedToken(
|
||||||
|
char* buf,
|
||||||
|
size_t bufSize,
|
||||||
|
Instruction* instruction,
|
||||||
|
bool* processingInstWord,
|
||||||
|
uint8_t* processingArgNum
|
||||||
|
) {
|
||||||
|
if (*processingInstWord) {
|
||||||
|
instruction->opcode = stringToInstructionOpcode(buf);
|
||||||
|
*processingInstWord = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*processingArgNum >= 3) {
|
||||||
|
fprintf(stderr, "expecting new line after instruction args, got %s\n", buf);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
OperandType type = INSTRUCTION_OPERAND_TYPES[instruction->opcode][*processingArgNum];
|
||||||
|
switch (type) {
|
||||||
|
case OP_NONE: {
|
||||||
|
fprintf(stderr, "expecting new line after instruction args, got %s\n", buf);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
case OP_REG: {
|
||||||
|
if (bufSize > 1) {
|
||||||
|
fprintf(stderr, "expecting register identifier, got %s\n", buf);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
switch (buf[0]) {
|
||||||
|
case 'a':
|
||||||
|
case 'A':
|
||||||
|
instruction->operands[*processingArgNum] = REG_A;
|
||||||
|
break;
|
||||||
|
case 'b':
|
||||||
|
case 'B':
|
||||||
|
instruction->operands[*processingArgNum] = REG_B;
|
||||||
|
break;
|
||||||
|
case 'c':
|
||||||
|
case 'C':
|
||||||
|
instruction->operands[*processingArgNum] = REG_C;
|
||||||
|
break;
|
||||||
|
case 'x':
|
||||||
|
case 'X':
|
||||||
|
instruction->operands[*processingArgNum] = REG_X;
|
||||||
|
break;
|
||||||
|
case 'y':
|
||||||
|
case 'Y':
|
||||||
|
instruction->operands[*processingArgNum] = REG_Y;
|
||||||
|
break;
|
||||||
|
case 'z':
|
||||||
|
case 'Z':
|
||||||
|
instruction->operands[*processingArgNum] = REG_Z;
|
||||||
|
break;
|
||||||
|
case 'r':
|
||||||
|
case 'R':
|
||||||
|
instruction->operands[*processingArgNum] = REG_R;
|
||||||
|
break;
|
||||||
|
case 'f':
|
||||||
|
case 'F':
|
||||||
|
instruction->operands[*processingArgNum] = REG_F;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: {
|
||||||
|
fprintf(stderr, "expecting register identifier, got %s\n", buf);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OP_IMM4: {
|
||||||
|
char* endptr;
|
||||||
|
long value = strtol(buf, &endptr, 0);
|
||||||
|
if (endptr == (char*)buf) {
|
||||||
|
fprintf(stderr, "couldn't convert %s to a number\n", buf);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
if (value < 0 || value > 0xF) {
|
||||||
|
fprintf(stderr, "%s is out of range for a 4-bit immediate\n", buf);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
instruction->operands[*processingArgNum] = (Register)value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OP_IMM16: {
|
||||||
|
char* endptr;
|
||||||
|
instruction->immediate = strtol(buf, &endptr, 0);
|
||||||
|
instruction->hasImm16 = true;
|
||||||
|
if (endptr == (char*)buf) {
|
||||||
|
fprintf(stderr, "couldn't convert %s to a number\n", buf);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(*processingArgNum)++;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Essentially a lexer for the assembly language.
|
* Essentially a lexer for the assembly language.
|
||||||
* @param [input] The text to create a program from. NOT the file name
|
* @param [input] The text to create a program from. NOT the file name
|
||||||
@@ -52,6 +153,8 @@ Program createProgramFromText(const char* input) {
|
|||||||
size_t programLen = strlen(input);
|
size_t programLen = strlen(input);
|
||||||
size_t current = 0;
|
size_t current = 0;
|
||||||
|
|
||||||
|
uint8_t currentInstruction = 0;
|
||||||
|
|
||||||
bool processingInstWord = true;
|
bool processingInstWord = true;
|
||||||
uint8_t processingArgNum = 0;
|
uint8_t processingArgNum = 0;
|
||||||
|
|
||||||
@@ -67,78 +170,8 @@ Program createProgramFromText(const char* input) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (processingInstWord) {
|
processBufferedToken(buf, bufSize, &instruction, &processingInstWord, &processingArgNum);
|
||||||
instruction.opcode = stringToInstructionOpcode(buf);
|
|
||||||
processingInstWord = false;
|
|
||||||
} else {
|
|
||||||
if (processingArgNum >= 3) {
|
|
||||||
fprintf(stderr, "expecting new line after instruction args, got %s\n", buf);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
OperandType type = INSTRUCTION_OPERAND_TYPES[instruction.opcode][processingArgNum];
|
|
||||||
switch (type) {
|
|
||||||
case OP_NONE: {
|
|
||||||
fprintf(stderr, "expecting new line after instruction args, got %s\n", buf);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
case OP_REG: {
|
|
||||||
if (bufSize > 1) {
|
|
||||||
fprintf(stderr, "expecting register identifier, got %s\n", buf);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
switch (buf[0]) {
|
|
||||||
case 'a':
|
|
||||||
case 'A':
|
|
||||||
instruction.operands[processingArgNum] = REG_A;
|
|
||||||
break;
|
|
||||||
case 'b':
|
|
||||||
case 'B':
|
|
||||||
instruction.operands[processingArgNum] = REG_B;
|
|
||||||
break;
|
|
||||||
case 'c':
|
|
||||||
case 'C':
|
|
||||||
instruction.operands[processingArgNum] = REG_C;
|
|
||||||
break;
|
|
||||||
case 'x':
|
|
||||||
case 'X':
|
|
||||||
instruction.operands[processingArgNum] = REG_X;
|
|
||||||
break;
|
|
||||||
case 'y':
|
|
||||||
case 'Y':
|
|
||||||
instruction.operands[processingArgNum] = REG_Y;
|
|
||||||
break;
|
|
||||||
case 'z':
|
|
||||||
case 'Z':
|
|
||||||
instruction.operands[processingArgNum] = REG_Z;
|
|
||||||
break;
|
|
||||||
case 'r':
|
|
||||||
case 'R':
|
|
||||||
instruction.operands[processingArgNum] = REG_R;
|
|
||||||
break;
|
|
||||||
case 'f':
|
|
||||||
case 'F':
|
|
||||||
instruction.operands[processingArgNum] = REG_F;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default: {
|
|
||||||
fprintf(stderr, "expecting register identifier, got %s\n", buf);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case OP_IMM16: {
|
|
||||||
char* endptr;
|
|
||||||
instruction.immediate = strtol(buf, &endptr, 0);
|
|
||||||
if (endptr == (char*)buf) {
|
|
||||||
fprintf(stderr, "couldn't convert %s to a number\n", buf);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
processingArgNum++;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf[0] = '\0';
|
buf[0] = '\0';
|
||||||
bufSize = 0;
|
bufSize = 0;
|
||||||
break;
|
break;
|
||||||
@@ -148,15 +181,26 @@ Program createProgramFromText(const char* input) {
|
|||||||
// Ignore extra new lines
|
// Ignore extra new lines
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (bufSize > 0) {
|
||||||
|
processBufferedToken(buf, bufSize, &instruction, &processingInstWord, &processingArgNum);
|
||||||
|
}
|
||||||
|
addInstructionToProgram(&program, instruction);
|
||||||
|
instruction = (Instruction){};
|
||||||
processingInstWord = true;
|
processingInstWord = true;
|
||||||
processingArgNum = 0;
|
processingArgNum = 0;
|
||||||
buf[0] = '\0';
|
buf[0] = '\0';
|
||||||
bufSize = 0;
|
bufSize = 0;
|
||||||
|
currentInstruction++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ':': {
|
case ':': {
|
||||||
if (!processingInstWord) {
|
if (!processingInstWord) {
|
||||||
fprintf(stderr, "label must be first word on a new line");
|
fprintf(stderr, "label must be first word on a new line\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
addLabelToProgram(&program, buf, currentInstruction);
|
||||||
|
if (input[current + 1] != '\n') {
|
||||||
|
fprintf(stderr, "expecting new line after label\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -183,6 +227,12 @@ Program createProgramFromText(const char* input) {
|
|||||||
|
|
||||||
current++;
|
current++;
|
||||||
if (current >= programLen) {
|
if (current >= programLen) {
|
||||||
|
if (!(processingInstWord && bufSize == 0)) {
|
||||||
|
if (bufSize > 0) {
|
||||||
|
processBufferedToken(buf, bufSize, &instruction, &processingInstWord, &processingArgNum);
|
||||||
|
}
|
||||||
|
addInstructionToProgram(&program, instruction);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -190,6 +240,75 @@ Program createProgramFromText(const char* input) {
|
|||||||
return program;
|
return program;
|
||||||
}
|
}
|
||||||
|
|
||||||
void assemble(const char* input, const char* outputFileName) {
|
void serializeProgramToFile(Program* program, const char* outputFileName) {
|
||||||
Program program = createProgramFromText(input);
|
|
||||||
|
FILE* fp = fopen(outputFileName, "wb");
|
||||||
|
if (fp == NULL) {
|
||||||
|
fprintf(stderr, "Couldn't open file %s for writing\n", outputFileName);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t totalWords = 0;
|
||||||
|
for (size_t i = 0; i < program->len; i++) {
|
||||||
|
totalWords += program->at[i].hasImm16 ? 2 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write magic header
|
||||||
|
ROMFileHeader header = {
|
||||||
|
.magic = "mlc\0",
|
||||||
|
.version = 1,
|
||||||
|
.numInstructions = totalWords
|
||||||
|
};
|
||||||
|
|
||||||
|
size_t written = fwrite(&header, sizeof(ROMFileHeader), 1, fp);
|
||||||
|
if (written != 1) {
|
||||||
|
fprintf(stderr, "failed to write file header into file %s\n", outputFileName);
|
||||||
|
fclose(fp);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < program->len; i++) {
|
||||||
|
Instruction* inst = &program->at[i];
|
||||||
|
|
||||||
|
uint16_t packed = 0;
|
||||||
|
|
||||||
|
packed |= (SerializedInst)(inst->opcode & 0b0000000000001111) ;
|
||||||
|
packed |= (SerializedInst)(inst->operands[0] & 0b0000000000001111) << 4;
|
||||||
|
packed |= (SerializedInst)(inst->operands[1] & 0b0000000000001111) << 8;
|
||||||
|
packed |= (SerializedInst)(inst->operands[2] & 0b0000000000001111) << 12;
|
||||||
|
|
||||||
|
size_t written = fwrite(&packed, sizeof(uint16_t), 1, fp);
|
||||||
|
if (written != 1) {
|
||||||
|
fprintf(stderr, "failed to write %" PRIx16 " into file %s\n", packed, outputFileName);
|
||||||
|
fclose(fp);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inst->hasImm16) {
|
||||||
|
size_t written = fwrite(&inst->immediate, sizeof(uint16_t), 1, fp);
|
||||||
|
if (written != 1) {
|
||||||
|
fprintf(stderr, "failed to write %" PRIx16 " into file %s\n", packed, outputFileName);
|
||||||
|
fclose(fp);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void assemble(const char* input, const char* outputFileName) {
|
||||||
|
clock_t start = clock();
|
||||||
|
Program program = createProgramFromText(input);
|
||||||
|
clock_t parsed = clock();
|
||||||
|
serializeProgramToFile(&program, outputFileName);
|
||||||
|
clock_t serialized = clock();
|
||||||
|
|
||||||
|
printf("Finished assembling %s, took %lg seconds (%lg secs parsing, %lg secs serializing)\n",
|
||||||
|
outputFileName,
|
||||||
|
(serialized - start) / (long double) CLOCKS_PER_SEC,
|
||||||
|
(parsed - start) / (long double) CLOCKS_PER_SEC,
|
||||||
|
(serialized - parsed) / (long double) CLOCKS_PER_SEC
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ void addInstructionToProgram(Program* program, Instruction inst) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (program->len + 1 >= program->capacity) {
|
if (program->len + 1 >= program->capacity) {
|
||||||
Instruction* tmp = malloc(sizeof(Instruction) * program->capacity * 2);
|
Instruction* tmp = realloc(program->at, sizeof(Instruction) * program->capacity * 2);
|
||||||
if (tmp == NULL) {
|
if (tmp == NULL) {
|
||||||
fprintf(stderr, "malloc failed while expanding program\n");
|
fprintf(stderr, "malloc failed while expanding program\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
@@ -50,7 +50,7 @@ void addLabelToProgram(Program* program, const char* name, uint8_t position) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (program->labels.len + 1 >= program->labels.capacity) {
|
if (program->labels.len + 1 >= program->labels.capacity) {
|
||||||
Label* tmp = malloc(sizeof(Label) * program->labels.capacity * 2);
|
Label* tmp = realloc(program->labels.at, sizeof(Label) * program->labels.capacity * 2);
|
||||||
if (tmp == NULL) {
|
if (tmp == NULL) {
|
||||||
fprintf(stderr, "malloc failed while expanding program labels\n");
|
fprintf(stderr, "malloc failed while expanding program labels\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
|
|||||||
32
src/device.c
32
src/device.c
@@ -2,11 +2,37 @@
|
|||||||
|
|
||||||
Device* createDevice(
|
Device* createDevice(
|
||||||
const char* name,
|
const char* name,
|
||||||
void (*sendToDevice)(uint16_t number),
|
void (*update)(Device* self)
|
||||||
uint16_t (*receiveFromDevice)(),
|
|
||||||
void (*update)()
|
|
||||||
) {
|
) {
|
||||||
Device* new = malloc(sizeof(Device));
|
Device* new = malloc(sizeof(Device));
|
||||||
if (!new)
|
if (!new)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
|
size_t nameLen = strlen(name) + 1;
|
||||||
|
nameLen = nameLen < 32 ? nameLen : 32;
|
||||||
|
|
||||||
|
memcpy(new->name, name, nameLen);
|
||||||
|
new->name[nameLen] = 0;
|
||||||
|
|
||||||
|
new->update = update;
|
||||||
|
|
||||||
|
return new;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t deviceRead(Device* self) {
|
||||||
|
if (self->inPos == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return self->in[--self->inPos];
|
||||||
|
}
|
||||||
|
|
||||||
|
void deviceSend(Device* self, uint16_t value) {
|
||||||
|
if (self->outPos == DEVICE_BUFFER_LENGTH)
|
||||||
|
return;
|
||||||
|
|
||||||
|
self->out[self->outPos++] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool deviceCanRead(Device* self) {
|
||||||
|
return self->inPos > 0;
|
||||||
}
|
}
|
||||||
30
src/device.h
30
src/device.h
@@ -1,18 +1,30 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
typedef struct {
|
#define DEVICE_BUFFER_LENGTH 128
|
||||||
|
|
||||||
|
typedef struct Device Device;
|
||||||
|
struct Device {
|
||||||
char name[32];
|
char name[32];
|
||||||
|
|
||||||
void (*sendToDevice)(uint16_t number);
|
uint16_t in[DEVICE_BUFFER_LENGTH];
|
||||||
uint16_t (*receiveFromDevice)();
|
uint8_t inPos;
|
||||||
void (*update)();
|
|
||||||
} Device;
|
uint16_t out[DEVICE_BUFFER_LENGTH];
|
||||||
|
uint8_t outPos;
|
||||||
|
|
||||||
|
void (*update)(Device* self);
|
||||||
|
};
|
||||||
|
|
||||||
Device* createDevice(
|
Device* createDevice(
|
||||||
const char* name,
|
const char* name,
|
||||||
void (*sendToDevice)(uint16_t number),
|
void (*update)(Device* self)
|
||||||
uint16_t (*receiveFromDevice)(),
|
);
|
||||||
void (*update)()
|
|
||||||
);
|
uint16_t deviceRead(Device* self);
|
||||||
|
void deviceSend(Device* self, uint16_t value);
|
||||||
|
bool deviceCanRead(Device* self);
|
||||||
173
src/emu.c
173
src/emu.c
@@ -1,21 +1,64 @@
|
|||||||
#include "emu.h"
|
#include "emu.h"
|
||||||
|
|
||||||
static inline Instruction fetchInst(Emulator* emu) {
|
static inline Instruction fetchInst(Emulator* emu) {
|
||||||
Instruction inst = decodeInstruction(emu->mem, &emu->pc);
|
return decodeInstruction(emu->mem, &emu->pc);
|
||||||
printf("%s\n", instructionToCStr(inst));
|
|
||||||
return inst;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void updateFlagsRegister(Emulator* emu, int32_t value) {
|
static inline void updateFlagsRegister(Emulator* emu, int32_t value) {
|
||||||
if (value == 0) {
|
|
||||||
emu->regs[REG_F] = FLAG_ZERO;
|
FlagsRegister flags = (FlagsRegister)emu->regs[REG_F];
|
||||||
} else if (value < 0) {
|
|
||||||
emu->regs[REG_F] = FLAG_NEGATIVE;
|
flags.fields.zero = value == 0;
|
||||||
} else if (value > UINT16_MAX) {
|
flags.fields.negative = value < 0;
|
||||||
emu->regs[REG_F] = FLAG_OVERFLOW;
|
flags.fields.overflow = value > UINT16_MAX;
|
||||||
|
|
||||||
|
emu->regs[REG_F] = flags.raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void updateDevices(Emulator* emu) {
|
||||||
|
for (size_t i = 0; i < MAX_DEVICES; i++) {
|
||||||
|
Device* device = emu->devices[i];
|
||||||
|
if (!device) continue;
|
||||||
|
|
||||||
|
if (device->update) {
|
||||||
|
device->update(device);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void pushProgramCounter(Emulator* emu) {
|
||||||
|
((uint32_t*)emu->mem->data)[emu->sp--] = emu->pc;
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleInterrupt(Emulator* emu) {
|
||||||
|
FlagsRegister flags = (FlagsRegister)emu->regs[REG_F];
|
||||||
|
if (!flags.fields.interrupts) return;
|
||||||
|
|
||||||
|
InterruptHandlerTable interruptTable = *((InterruptHandlerTable*)&emu->mem->data[emu->iht]);
|
||||||
|
|
||||||
|
pushProgramCounter(emu);
|
||||||
|
|
||||||
|
emu->pc = interruptTable.interruptHandlers[emu->irq-1];
|
||||||
|
emu->irq = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t getRegister(Emulator* emu, uint8_t reg) {
|
||||||
|
if (reg == REG_IHT_OFFSET) {
|
||||||
|
return emu->iht;
|
||||||
|
}
|
||||||
|
|
||||||
|
return emu->regs[reg];
|
||||||
|
}
|
||||||
|
|
||||||
|
void setRegister(Emulator* emu, uint8_t reg, uint32_t value) {
|
||||||
|
if (reg == REG_IHT_OFFSET) {
|
||||||
|
emu->iht = value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emu->regs[reg] = value;
|
||||||
|
}
|
||||||
|
|
||||||
void startEmulator(Emulator* emu) {
|
void startEmulator(Emulator* emu) {
|
||||||
static void* dispatchTable[] = {
|
static void* dispatchTable[] = {
|
||||||
&&MOV,
|
&&MOV,
|
||||||
@@ -32,99 +75,107 @@ void startEmulator(Emulator* emu) {
|
|||||||
&&JIZ,
|
&&JIZ,
|
||||||
&&CALL,
|
&&CALL,
|
||||||
&&RET,
|
&&RET,
|
||||||
&&SYS,
|
&&PORT,
|
||||||
&&HLT
|
&&HLT
|
||||||
};
|
};
|
||||||
|
|
||||||
emu->halted = false;
|
emu->halted = false;
|
||||||
Instruction inst;
|
Instruction inst;
|
||||||
|
|
||||||
#define DISPATCH() inst = fetchInst(emu); \
|
#define DISPATCH() if (emu->irq != 0) handleInterrupt(emu); \
|
||||||
goto *dispatchTable[inst.opcode];
|
updateDevices(emu); \
|
||||||
|
if (emu->halted) { \
|
||||||
|
return; \
|
||||||
|
} \
|
||||||
|
inst = fetchInst(emu); \
|
||||||
|
printf("%s\n", instructionToCStr(inst)); \
|
||||||
|
goto *dispatchTable[inst.opcode]
|
||||||
|
|
||||||
DISPATCH();
|
DISPATCH();
|
||||||
|
|
||||||
MOV: {
|
MOV: {
|
||||||
emu->regs[inst.operands[0]] = emu->regs[inst.operands[1]];
|
setRegister(emu, inst.operands[0], getRegister(emu, inst.operands[1]));
|
||||||
DISPATCH();
|
DISPATCH();
|
||||||
}
|
}
|
||||||
MVI: {
|
MVI: {
|
||||||
emu->regs[inst.operands[0]] = inst.immediate;
|
setRegister(emu, inst.operands[0], inst.immediate);
|
||||||
DISPATCH();
|
DISPATCH();
|
||||||
}
|
}
|
||||||
ADD: {
|
ADD: {
|
||||||
int32_t result = emu->regs[inst.operands[0]] + emu->regs[inst.operands[1]];
|
int32_t result = getRegister(emu, inst.operands[0]) + getRegister(emu, inst.operands[1]);
|
||||||
updateFlagsRegister(emu, result);
|
updateFlagsRegister(emu, result);
|
||||||
emu->regs[inst.operands[0]] = result;
|
setRegister(emu, inst.operands[0], result);
|
||||||
DISPATCH();
|
DISPATCH();
|
||||||
}
|
}
|
||||||
SUB: {
|
SUB: {
|
||||||
int32_t result = emu->regs[inst.operands[0]] - emu->regs[inst.operands[1]];
|
int32_t result = getRegister(emu, inst.operands[0]) - getRegister(emu, inst.operands[1]);
|
||||||
updateFlagsRegister(emu, result);
|
updateFlagsRegister(emu, result);
|
||||||
emu->regs[inst.operands[0]] = result;
|
setRegister(emu, inst.operands[0], result);
|
||||||
DISPATCH();
|
DISPATCH();
|
||||||
}
|
}
|
||||||
LOAD: {
|
LOAD: {
|
||||||
uint32_t addr = getAddress(
|
uint32_t addr = getAddress(
|
||||||
emu->regs[inst.operands[1]],
|
getRegister(emu, inst.operands[1]),
|
||||||
emu->regs[inst.operands[2]]
|
getRegister(emu, inst.operands[2])
|
||||||
);
|
);
|
||||||
|
|
||||||
emu->regs[inst.operands[0]] = memoryLoadWord(emu->mem, addr);
|
setRegister(emu, inst.operands[0], memoryLoadWord(emu->mem, addr));
|
||||||
DISPATCH();
|
DISPATCH();
|
||||||
}
|
}
|
||||||
STORE: {
|
STORE: {
|
||||||
uint32_t addr = getAddress(
|
uint32_t addr = getAddress(
|
||||||
emu->regs[inst.operands[0]],
|
getRegister(emu, inst.operands[1]),
|
||||||
emu->regs[inst.operands[1]]
|
getRegister(emu, inst.operands[2])
|
||||||
);
|
);
|
||||||
memorySaveWord(emu->mem, addr, inst.operands[2]);
|
|
||||||
|
memorySaveWord(emu->mem, addr, getRegister(emu, inst.operands[2]));
|
||||||
DISPATCH();
|
DISPATCH();
|
||||||
}
|
}
|
||||||
AND: {
|
AND: {
|
||||||
int32_t result = emu->regs[inst.operands[0]] & emu->regs[inst.operands[1]];
|
int32_t result = getRegister(emu, inst.operands[0]) & getRegister(emu, inst.operands[1]);
|
||||||
updateFlagsRegister(emu, result);
|
updateFlagsRegister(emu, result);
|
||||||
emu->regs[inst.operands[0]] = result;
|
setRegister(emu, inst.operands[0], result);
|
||||||
DISPATCH();
|
DISPATCH();
|
||||||
}
|
}
|
||||||
OR: {
|
OR: {
|
||||||
int32_t result = emu->regs[inst.operands[0]] | emu->regs[inst.operands[1]];
|
int32_t result = getRegister(emu, inst.operands[0]) | getRegister(emu, inst.operands[1]);
|
||||||
updateFlagsRegister(emu, result);
|
updateFlagsRegister(emu, result);
|
||||||
emu->regs[inst.operands[0]] = result;
|
setRegister(emu, inst.operands[0], result);
|
||||||
DISPATCH();
|
DISPATCH();
|
||||||
}
|
}
|
||||||
XOR: {
|
XOR: {
|
||||||
int32_t result = emu->regs[inst.operands[0]] ^ emu->regs[inst.operands[1]];
|
int32_t result = getRegister(emu, inst.operands[0]) ^ getRegister(emu, inst.operands[1]);
|
||||||
updateFlagsRegister(emu, result);
|
updateFlagsRegister(emu, result);
|
||||||
emu->regs[inst.operands[0]] = result;
|
setRegister(emu, inst.operands[0], result);
|
||||||
DISPATCH();
|
DISPATCH();
|
||||||
}
|
}
|
||||||
SHF: {
|
SHF: {
|
||||||
int32_t result;
|
int32_t result;
|
||||||
if (inst.operands[2] == 0)
|
|
||||||
result = emu->regs[inst.operands[0]] << emu->regs[inst.operands[1]];
|
if (inst.operands[0] == 0)
|
||||||
|
result = getRegister(emu, inst.operands[0]) << getRegister(emu, inst.operands[1]);
|
||||||
else
|
else
|
||||||
result = emu->regs[inst.operands[0]] >> emu->regs[inst.operands[1]];
|
result = getRegister(emu, inst.operands[0]) >> getRegister(emu, inst.operands[1]);
|
||||||
|
|
||||||
updateFlagsRegister(emu, result);
|
updateFlagsRegister(emu, result);
|
||||||
emu->regs[inst.operands[0]] = result;
|
setRegister(emu, inst.operands[0], result);
|
||||||
DISPATCH();
|
DISPATCH();
|
||||||
}
|
}
|
||||||
JMP: {
|
JMP: {
|
||||||
emu->pc = getAddress(inst.operands[0], inst.operands[1]);
|
emu->pc = inst.immediate;
|
||||||
DISPATCH();
|
DISPATCH();
|
||||||
}
|
}
|
||||||
JIZ: {
|
JIZ: {
|
||||||
if (emu->regs[REG_F] & FLAG_ZERO)
|
if (((FlagsRegister)emu->regs[REG_F]).fields.zero)
|
||||||
emu->pc = getAddress(inst.operands[0], inst.operands[1]);
|
emu->pc = inst.immediate;
|
||||||
DISPATCH();
|
DISPATCH();
|
||||||
}
|
}
|
||||||
CALL: {
|
CALL: {
|
||||||
// save the pc at the current sp and decrement the sp (stack grows downward)
|
// save the pc at the current sp and decrement the sp (stack grows downward)
|
||||||
((uint32_t*)emu->mem->data)[emu->sp--] = emu->pc;
|
pushProgramCounter(emu);
|
||||||
|
|
||||||
// jump to given addr
|
// jump to given addr
|
||||||
emu->pc = getAddress(inst.operands[0], inst.operands[1]);
|
emu->pc = inst.immediate;
|
||||||
|
|
||||||
DISPATCH();
|
DISPATCH();
|
||||||
}
|
}
|
||||||
@@ -133,8 +184,39 @@ void startEmulator(Emulator* emu) {
|
|||||||
emu->pc = ((uint32_t*)emu->mem->data)[++emu->sp];
|
emu->pc = ((uint32_t*)emu->mem->data)[++emu->sp];
|
||||||
DISPATCH();
|
DISPATCH();
|
||||||
}
|
}
|
||||||
SYS: {
|
PORT: {
|
||||||
// TODO
|
uint16_t port = getRegister(emu, inst.operands[0]);
|
||||||
|
if (port >= MAX_DEVICES) {
|
||||||
|
DISPATCH();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t direction = inst.operands[2];
|
||||||
|
|
||||||
|
Device* device = emu->devices[port];
|
||||||
|
if (!device) {
|
||||||
|
DISPATCH();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (direction == 0) {
|
||||||
|
uint16_t value = getRegister(emu, inst.operands[1]);
|
||||||
|
|
||||||
|
if (device->inPos == DEVICE_BUFFER_LENGTH) {
|
||||||
|
DISPATCH();
|
||||||
|
}
|
||||||
|
|
||||||
|
device->in[device->inPos] = value;
|
||||||
|
device->inPos++;
|
||||||
|
} else {
|
||||||
|
if (device->outPos == 0) {
|
||||||
|
setRegister(emu, inst.operands[1], 0);
|
||||||
|
DISPATCH();
|
||||||
|
}
|
||||||
|
|
||||||
|
setRegister(emu, inst.operands[1], device->out[device->outPos-1]);
|
||||||
|
if (device->outPos > 0)
|
||||||
|
device->outPos--;
|
||||||
|
}
|
||||||
|
|
||||||
DISPATCH();
|
DISPATCH();
|
||||||
}
|
}
|
||||||
HLT: {
|
HLT: {
|
||||||
@@ -144,7 +226,7 @@ void startEmulator(Emulator* emu) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Emulator* initEmulator(uint32_t memorySize) {
|
Emulator* initEmulator(uint32_t memorySize) {
|
||||||
Emulator* newEmu = malloc(sizeof(Emulator));
|
Emulator* newEmu = calloc(1, sizeof(Emulator));
|
||||||
if (!newEmu)
|
if (!newEmu)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
@@ -154,7 +236,7 @@ Emulator* initEmulator(uint32_t memorySize) {
|
|||||||
// zero out special regs
|
// zero out special regs
|
||||||
newEmu->pc = MEM_PROGRAM_START;
|
newEmu->pc = MEM_PROGRAM_START;
|
||||||
newEmu->sp = 0;
|
newEmu->sp = 0;
|
||||||
newEmu->ihp = 0;
|
newEmu->iht = 0;
|
||||||
|
|
||||||
// init memory
|
// init memory
|
||||||
newEmu->mem = initMemory(memorySize);
|
newEmu->mem = initMemory(memorySize);
|
||||||
@@ -170,3 +252,8 @@ void freeEmulator(Emulator* emu) {
|
|||||||
freeMemory(emu->mem);
|
freeMemory(emu->mem);
|
||||||
free(emu);
|
free(emu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void installDevice(Emulator* emu, Device* device, uint8_t deviceNumber) {
|
||||||
|
assert(deviceNumber < MAX_DEVICES);
|
||||||
|
emu->devices[deviceNumber] = device;
|
||||||
|
}
|
||||||
|
|||||||
25
src/emu.h
25
src/emu.h
@@ -4,6 +4,7 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <assert.h>
|
||||||
#include "memory.h"
|
#include "memory.h"
|
||||||
#include "device.h"
|
#include "device.h"
|
||||||
|
|
||||||
@@ -11,19 +12,39 @@
|
|||||||
#define FLAG_NEGATIVE (1 << 1)
|
#define FLAG_NEGATIVE (1 << 1)
|
||||||
#define FLAG_OVERFLOW (1 << 2)
|
#define FLAG_OVERFLOW (1 << 2)
|
||||||
|
|
||||||
|
#define MAX_DEVICES 64
|
||||||
|
|
||||||
// -- TYPES -- //
|
// -- TYPES -- //
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint16_t regs[REG_MAX];
|
uint16_t regs[REG_MAX];
|
||||||
Memory* mem;
|
Memory* mem;
|
||||||
|
Device* devices[MAX_DEVICES];
|
||||||
|
|
||||||
uint32_t pc;
|
uint32_t pc;
|
||||||
uint32_t ihp;
|
uint32_t iht;
|
||||||
uint32_t sp;
|
uint32_t sp;
|
||||||
|
|
||||||
|
uint16_t irq;
|
||||||
bool halted;
|
bool halted;
|
||||||
} Emulator;
|
} Emulator;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint32_t interruptHandlers[32];
|
||||||
|
} __attribute__((packed)) InterruptHandlerTable;
|
||||||
|
|
||||||
|
typedef union {
|
||||||
|
struct {
|
||||||
|
uint16_t zero : 1;
|
||||||
|
uint16_t overflow : 1;
|
||||||
|
uint16_t negative : 1;
|
||||||
|
uint16_t interrupts : 1;
|
||||||
|
} fields;
|
||||||
|
|
||||||
|
uint16_t raw;
|
||||||
|
} FlagsRegister;
|
||||||
|
|
||||||
// -- FUNCTIONS -- //
|
// -- FUNCTIONS -- //
|
||||||
Emulator* initEmulator(uint32_t memorySize);
|
Emulator* initEmulator(uint32_t memorySize);
|
||||||
void freeEmulator(Emulator* emu);
|
void freeEmulator(Emulator* emu);
|
||||||
void startEmulator(Emulator* emu);
|
void startEmulator(Emulator* emu);
|
||||||
|
void installDevice(Emulator* emu, Device* device, uint8_t deviceNumber);
|
||||||
@@ -11,8 +11,8 @@ const OperandType INSTRUCTION_OPERAND_TYPES[][3] = {
|
|||||||
{OP_REG, OP_REG, OP_NONE}, // OR
|
{OP_REG, OP_REG, OP_NONE}, // OR
|
||||||
{OP_REG, OP_REG, OP_NONE}, // XOR
|
{OP_REG, OP_REG, OP_NONE}, // XOR
|
||||||
{OP_REG, OP_REG, OP_REG}, // SHF
|
{OP_REG, OP_REG, OP_REG}, // SHF
|
||||||
{OP_REG, OP_REG, OP_NONE}, // JMP
|
{OP_IMM16, OP_NONE, OP_NONE}, // JMP
|
||||||
{OP_REG, OP_REG, OP_NONE}, // JIZ
|
{OP_IMM16, OP_NONE, OP_NONE}, // JIZ
|
||||||
{OP_IMM16, OP_NONE, OP_NONE},// CALL
|
{OP_IMM16, OP_NONE, OP_NONE},// CALL
|
||||||
{OP_NONE, OP_NONE, OP_NONE}, // RET
|
{OP_NONE, OP_NONE, OP_NONE}, // RET
|
||||||
{OP_REG, OP_REG, OP_IMM4}, // PORT
|
{OP_REG, OP_REG, OP_IMM4}, // PORT
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ typedef enum : uint8_t {
|
|||||||
// automatically know the length of the registers array
|
// automatically know the length of the registers array
|
||||||
} Register;
|
} Register;
|
||||||
|
|
||||||
|
#define REG_IHT_OFFSET 8 // offset of interrupt handler table register
|
||||||
|
|
||||||
typedef uint16_t SerializedInst;
|
typedef uint16_t SerializedInst;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|||||||
48
src/main.c
48
src/main.c
@@ -2,18 +2,56 @@
|
|||||||
#include "emu.h"
|
#include "emu.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
int main() {
|
int main(int argc, char** argv) {
|
||||||
|
|
||||||
|
if (argc < 2) {
|
||||||
|
fprintf(stderr, "Usage: %s <ROM to emulate>\n", argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
Emulator* emu = initEmulator(0x00010FFF);
|
Emulator* emu = initEmulator(0x00010FFF);
|
||||||
if (!emu) {
|
if (!emu) {
|
||||||
fprintf(stderr, "emu: error: failed to allocate memory for emulator\n");
|
fprintf(stderr, "emu: error: failed to allocate memory for emulator\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
RomFile* romFile = readProgramFromFile("../test.bin");
|
/*
|
||||||
memoryLoadProgram(emu->mem, romFile->instructions, romFile->header.numInstructions);
|
Instruction program[] = {
|
||||||
|
{INST_MVI, {REG_A}, 0, true},
|
||||||
|
{INST_MVI, {REG_B}, 123, true},
|
||||||
|
{INST_PORT, {REG_A, REG_B, 0}, 0, false},
|
||||||
|
{INST_PORT, {REG_A, REG_B, 1}, 0, false},
|
||||||
|
{INST_HLT, {}, 0, false}
|
||||||
|
};
|
||||||
|
|
||||||
|
RomFile* f = readProgramFromFile("../add.o");
|
||||||
|
memcpy(emu->mem + MEM_PROGRAM_START, f->instructions, sizeof(SerializedInst) * f->header.numInstructions);
|
||||||
|
memoryLoadProgram(emu->mem, program, sizeof(program)/sizeof(program[0]));
|
||||||
|
*/
|
||||||
|
|
||||||
|
RomFile* rom = readProgramFromFile(argv[1]);
|
||||||
|
if (rom == NULL) {
|
||||||
|
fprintf(stderr, "Failed to read rom from %s\n", argv[1]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(emu->mem->data + MEM_PROGRAM_START, rom->instructions, sizeof(SerializedInst) * rom->header.numInstructions);
|
||||||
|
emu->pc = MEM_PROGRAM_START;
|
||||||
|
|
||||||
|
startEmulator(emu);
|
||||||
|
|
||||||
|
printf("Emulation completed!\n");
|
||||||
|
printf("Registers:\n");
|
||||||
|
printf(" A: %d\n", emu->regs[REG_A]);
|
||||||
|
printf(" B: %d\n", emu->regs[REG_B]);
|
||||||
|
printf(" C: %d\n", emu->regs[REG_C]);
|
||||||
|
printf(" X: %d\n", emu->regs[REG_X]);
|
||||||
|
printf(" Y: %d\n", emu->regs[REG_Y]);
|
||||||
|
printf(" Z: %d\n", emu->regs[REG_Z]);
|
||||||
|
printf(" R: %d\n", emu->regs[REG_R]);
|
||||||
|
printf(" F: %d\n", emu->regs[REG_F]);
|
||||||
|
|
||||||
//startEmulator(emu);
|
|
||||||
freeEmulator(emu);
|
freeEmulator(emu);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ Instruction decodeInstruction(Memory* m, uint32_t* pc) {
|
|||||||
uint8_t offset = 0;
|
uint8_t offset = 0;
|
||||||
for (uint8_t i = 0; i < 3; i++) {
|
for (uint8_t i = 0; i < 3; i++) {
|
||||||
switch (opTypes[i]) {
|
switch (opTypes[i]) {
|
||||||
|
case OP_IMM4:
|
||||||
case OP_REG: {
|
case OP_REG: {
|
||||||
// get the data at the current offset, and grab the 4 bits that are there with a bitwise
|
// get the data at the current offset, and grab the 4 bits that are there with a bitwise
|
||||||
// "and" operator
|
// "and" operator
|
||||||
@@ -82,7 +83,8 @@ Instruction decodeInstruction(Memory* m, uint32_t* pc) {
|
|||||||
case OP_IMM16: { // the immediate 16 takes up the space where the next instruction would be
|
case OP_IMM16: { // the immediate 16 takes up the space where the next instruction would be
|
||||||
(*pc) += sizeof(SerializedInst);
|
(*pc) += sizeof(SerializedInst);
|
||||||
|
|
||||||
uint16_t nextDataOver = m->data[*pc];
|
uint16_t nextDataOver;
|
||||||
|
memcpy(&nextDataOver, &m->data[*pc], sizeof(uint16_t));
|
||||||
outputInst.immediate = nextDataOver;
|
outputInst.immediate = nextDataOver;
|
||||||
outputInst.hasImm16 = true;
|
outputInst.hasImm16 = true;
|
||||||
break;
|
break;
|
||||||
@@ -108,4 +110,4 @@ void freeMemory(Memory* m) {
|
|||||||
|
|
||||||
inline uint32_t getAddress(uint16_t low, uint16_t high) {
|
inline uint32_t getAddress(uint16_t low, uint16_t high) {
|
||||||
return (high << 16) | low;
|
return (high << 16) | low;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ RomFile* readProgramFromFile(char* path) {
|
|||||||
fclose(f);
|
fclose(f);
|
||||||
|
|
||||||
ROMFileHeader* header = (ROMFileHeader*)buffer;
|
ROMFileHeader* header = (ROMFileHeader*)buffer;
|
||||||
Instruction* instructions = calloc(header->numInstructions, sizeof(SerializedInst));
|
SerializedInst* instructions = calloc(header->numInstructions, sizeof(SerializedInst));
|
||||||
if (!instructions) {
|
if (!instructions) {
|
||||||
free(buffer);
|
free(buffer);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ typedef struct {
|
|||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
ROMFileHeader header;
|
ROMFileHeader header;
|
||||||
Instruction* instructions;
|
SerializedInst* instructions;
|
||||||
} RomFile;
|
} RomFile;
|
||||||
|
|
||||||
// -- FUNCTIONS -- //
|
// -- FUNCTIONS -- //
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
dingus:
|
|
||||||
|
|
||||||
mvi a 2
|
mvi a 2
|
||||||
mvi b 2
|
mvi b 2
|
||||||
|
|
||||||
add a b
|
add a b
|
||||||
hlt
|
hlt
|
||||||
|
hlt
|
||||||
|
|||||||
Reference in New Issue
Block a user