instruction decoding
This commit is contained in:
@@ -7,6 +7,6 @@ sources = [
|
||||
'src/instruction.c'
|
||||
]
|
||||
|
||||
args = ['-Wall', '-Wextra', '-Wpedantic', '-O3']
|
||||
args = ['-Wall', '-Wextra', '-Wpedantic', '-O3', '-ggdb', '-fsanitize=address']
|
||||
|
||||
executable('emu', sources, c_args: args)
|
||||
10
src/emu.c
10
src/emu.c
@@ -8,6 +8,11 @@ Emulator* initEmulator(uint32_t memorySize) {
|
||||
// zero out registers
|
||||
memset(newEmu->regs, 0, sizeof(newEmu->regs));
|
||||
|
||||
// zero out special regs
|
||||
newEmu->pc = 0;
|
||||
newEmu->sp = 0;
|
||||
newEmu->ihp = 0;
|
||||
|
||||
// init memory
|
||||
newEmu->mem = initMemory(memorySize);
|
||||
if (!newEmu->mem) {
|
||||
@@ -17,3 +22,8 @@ Emulator* initEmulator(uint32_t memorySize) {
|
||||
|
||||
return newEmu;
|
||||
}
|
||||
|
||||
void freeEmulator(Emulator* emu) {
|
||||
freeMemory(emu->mem);
|
||||
free(emu);
|
||||
}
|
||||
17
src/emu.h
17
src/emu.h
@@ -6,22 +6,15 @@
|
||||
#include "memory.h"
|
||||
|
||||
// -- TYPES -- //
|
||||
typedef enum {
|
||||
REG_A, REG_B, REG_C,
|
||||
|
||||
REG_X, REG_Y, REG_Z,
|
||||
REG_R,
|
||||
|
||||
REG_PC, REG_IHP, REG_SP, REG_F,
|
||||
|
||||
REG_MAX // all registers should come before this one, this is so we can
|
||||
// automatically know the length of the registers array
|
||||
} Register;
|
||||
|
||||
typedef struct {
|
||||
uint16_t regs[REG_MAX];
|
||||
Memory* mem;
|
||||
|
||||
uint32_t pc;
|
||||
uint32_t ihp;
|
||||
uint32_t sp;
|
||||
} Emulator;
|
||||
|
||||
// -- FUNCTIONS -- //
|
||||
Emulator* initEmulator(uint32_t memorySize);
|
||||
void freeEmulator(Emulator* emu);
|
||||
@@ -1,20 +1,110 @@
|
||||
#include "instruction.h"
|
||||
|
||||
const InstructionOperands INSTRUCTION_OPERAND_TYPES[] = {
|
||||
{ INST_MVI, {OP_IMM4, OP_IMM4, OP_NONE} },
|
||||
{ INST_MOV, {OP_IMM4, OP_NONE, OP_NONE} },
|
||||
{ INST_ADD, {OP_IMM4, OP_IMM4, OP_NONE} },
|
||||
{ INST_SUB, {OP_IMM4, OP_IMM4, OP_NONE} },
|
||||
{ INST_LOAD, {OP_IMM4, OP_IMM4, OP_IMM4} },
|
||||
{ INST_STORE, {OP_IMM4, OP_IMM4, OP_IMM4} },
|
||||
{ INST_AND, {OP_IMM4, OP_IMM4, OP_NONE} },
|
||||
{ INST_OR, {OP_IMM4, OP_IMM4, OP_NONE} },
|
||||
{ INST_XOR, {OP_IMM4, OP_IMM4, OP_NONE} },
|
||||
{ INST_SHF, {OP_IMM4, OP_IMM4, OP_IMM4} },
|
||||
{ INST_JMP, {OP_IMM4, OP_IMM4, OP_NONE} },
|
||||
{ INST_JIZ, {OP_IMM4, OP_IMM4, OP_NONE} },
|
||||
{ INST_CALL, {OP_IMM4, OP_IMM4, OP_NONE} },
|
||||
{ INST_RET, {OP_NONE, OP_NONE, OP_NONE} },
|
||||
{ INST_SYS, {OP_IMM4, OP_IMM4, OP_IMM4} },
|
||||
{ INST_HLT, {OP_NONE, OP_NONE, OP_NONE} },
|
||||
const OperandType INSTRUCTION_OPERAND_TYPES[][3] = {
|
||||
{OP_REG, OP_REG, OP_NONE},
|
||||
{OP_REG, OP_IMM16, OP_NONE},
|
||||
{OP_REG, OP_REG, OP_NONE},
|
||||
{OP_REG, OP_REG, OP_NONE},
|
||||
{OP_REG, OP_REG, OP_REG},
|
||||
{OP_REG, OP_REG, OP_REG},
|
||||
{OP_REG, OP_REG, OP_NONE},
|
||||
{OP_REG, OP_REG, OP_NONE},
|
||||
{OP_REG, OP_REG, OP_NONE},
|
||||
{OP_REG, OP_REG, OP_REG},
|
||||
{OP_REG, OP_REG, OP_NONE},
|
||||
{OP_REG, OP_REG, OP_NONE},
|
||||
{OP_REG, OP_REG, OP_NONE},
|
||||
{OP_NONE, OP_NONE, OP_NONE},
|
||||
{OP_REG, OP_REG, OP_REG},
|
||||
{OP_NONE, OP_NONE, OP_NONE}
|
||||
};
|
||||
|
||||
char* opcodeToCStr(InstructionOpcode opcode) {
|
||||
switch (opcode) {
|
||||
case INST_MVI: return "MVI";
|
||||
case INST_MOV: return "MOV";
|
||||
case INST_ADD: return "ADD";
|
||||
case INST_SUB: return "SUB";
|
||||
case INST_STORE: return "STORE";
|
||||
case INST_LOAD: return "LOAD";
|
||||
case INST_OR: return "OR";
|
||||
case INST_AND: return "AND";
|
||||
case INST_XOR: return "XOR";
|
||||
case INST_SHF: return "SHF";
|
||||
case INST_JMP: return "JMP";
|
||||
case INST_JIZ: return "JIZ";
|
||||
case INST_CALL: return "CALL";
|
||||
case INST_RET: return "RET";
|
||||
case INST_SYS: return "SYS";
|
||||
case INST_HLT: return "HLT";
|
||||
default: return "FIXME";
|
||||
}
|
||||
}
|
||||
|
||||
char* registerToCStr(Register reg) {
|
||||
switch (reg) {
|
||||
case REG_A: return "A";
|
||||
case REG_B: return "B";
|
||||
case REG_C: return "C";
|
||||
case REG_X: return "X";
|
||||
case REG_Y: return "Y";
|
||||
case REG_Z: return "Z";
|
||||
case REG_R: return "R";
|
||||
case REG_F: return "F";
|
||||
default: return "FIXME";
|
||||
}
|
||||
}
|
||||
|
||||
char* instructionToCStr(Instruction inst) {
|
||||
|
||||
size_t bufferSize = 64;
|
||||
char* buffer = malloc(bufferSize);
|
||||
if (!buffer)
|
||||
return NULL;
|
||||
buffer[0] = 0;
|
||||
|
||||
int written = 0;
|
||||
int remaining = bufferSize;
|
||||
|
||||
written += snprintf(buffer + written, remaining, "%s ", opcodeToCStr(inst.opcode));
|
||||
remaining = bufferSize - written;
|
||||
|
||||
const OperandType* operandTypes = INSTRUCTION_OPERAND_TYPES[inst.opcode];
|
||||
|
||||
for (uint8_t opIdx = 0; opIdx < 3; opIdx++) {
|
||||
switch (operandTypes[opIdx]) {
|
||||
case OP_REG:
|
||||
written += snprintf(
|
||||
buffer + written,
|
||||
remaining,
|
||||
"%s",
|
||||
registerToCStr(inst.operands[opIdx])
|
||||
);
|
||||
break;
|
||||
case OP_IMM16:
|
||||
written += snprintf(
|
||||
buffer + written,
|
||||
remaining,
|
||||
"%d",
|
||||
inst.immediate
|
||||
);
|
||||
break;
|
||||
case OP_NONE:
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
remaining = bufferSize - written;
|
||||
|
||||
if (opIdx == 3 || operandTypes[opIdx+1] == OP_NONE)
|
||||
break;
|
||||
else
|
||||
written += snprintf(buffer + written, remaining, ", ");
|
||||
|
||||
remaining = bufferSize - written;
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
@@ -1,30 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
// -- TYPES -- //
|
||||
typedef enum {
|
||||
typedef enum : uint8_t {
|
||||
INST_MOV, INST_MVI, INST_ADD, INST_SUB, INST_LOAD, INST_STORE, INST_AND, INST_OR,
|
||||
INST_XOR, INST_SHF, INST_JMP, INST_JIZ, INST_CALL, INST_RET, INST_SYS, INST_HLT
|
||||
} InstructionOpcode;
|
||||
|
||||
typedef enum {
|
||||
REG_A, REG_B, REG_C,
|
||||
|
||||
REG_X, REG_Y, REG_Z,
|
||||
REG_R,
|
||||
|
||||
REG_F,
|
||||
|
||||
REG_MAX // all registers should come before this one, this is so we can
|
||||
// automatically know the length of the registers array
|
||||
} Register;
|
||||
|
||||
typedef uint16_t SerializedInst;
|
||||
|
||||
typedef struct {
|
||||
InstructionOpcode opcode;
|
||||
uint16_t a;
|
||||
uint16_t b;
|
||||
uint16_t c;
|
||||
uint8_t operands[3];
|
||||
uint16_t immediate; // unused for almost all instructions
|
||||
} Instruction;
|
||||
|
||||
typedef enum {
|
||||
OP_NONE, OP_IMM4
|
||||
OP_NONE, OP_REG, OP_IMM16
|
||||
} OperandType;
|
||||
|
||||
typedef struct {
|
||||
InstructionOpcode opcode;
|
||||
OperandType operandTypes[3];
|
||||
} InstructionOperands;
|
||||
|
||||
// -- CONSTANTS -- //
|
||||
extern const InstructionOperands INSTRUCTION_OPERAND_TYPES[];
|
||||
extern const OperandType INSTRUCTION_OPERAND_TYPES[][3];
|
||||
|
||||
char* opcodeToCStr(InstructionOpcode opcode);
|
||||
char* instructionToCStr(Instruction inst);
|
||||
char* registerToCStr(Register reg);
|
||||
@@ -8,7 +8,10 @@ int main() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
free(emu);
|
||||
Instruction inst = decodeInstruction(emu->mem, &emu->pc);
|
||||
printf("%s\n", instructionToCStr(inst));
|
||||
|
||||
freeEmulator(emu);
|
||||
|
||||
return 0;
|
||||
}
|
||||
40
src/memory.c
40
src/memory.c
@@ -22,3 +22,43 @@ inline uint16_t memoryLoadWord(Memory* m, uint32_t address) {
|
||||
inline void memorySaveWord(Memory* m, uint32_t address, uint16_t value) {
|
||||
((uint16_t*)m->data)[address] = value;
|
||||
}
|
||||
|
||||
Instruction decodeInstruction(Memory* m, uint32_t* pc) {
|
||||
SerializedInst serialized = m->data[*pc];
|
||||
Instruction outputInst = {};
|
||||
|
||||
uint8_t opcode = serialized & 0b0000000000001111; // get four lowest bits for the opcode
|
||||
outputInst.opcode = opcode;
|
||||
|
||||
const OperandType* opTypes = INSTRUCTION_OPERAND_TYPES[opcode];
|
||||
|
||||
uint8_t offset = 0;
|
||||
for (uint8_t i = 0; i < 3; i++) {
|
||||
switch (opTypes[i]) {
|
||||
case OP_REG: {
|
||||
// get the data at the current offset, and grab the 4 bits that are there with a bitwise
|
||||
// "and" operator
|
||||
outputInst.operands[offset] = (serialized >> (4 * (offset + 1))) & 0b0000000000001111;
|
||||
offset++;
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_IMM16: { // the immediate 16 takes up the space where the next instruction would be
|
||||
uint16_t nextDataOver = m->data[++*pc];
|
||||
outputInst.immediate = nextDataOver;
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_NONE: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return outputInst;
|
||||
}
|
||||
|
||||
void freeMemory(Memory* m) {
|
||||
free(m->data);
|
||||
free(m);
|
||||
}
|
||||
@@ -17,6 +17,7 @@ typedef struct {
|
||||
|
||||
// -- FUNCTIONS -- //
|
||||
Memory* initMemory(uint32_t size);
|
||||
void freeMemory(Memory* m);
|
||||
uint16_t memoryLoadWord(Memory* m, uint32_t address);
|
||||
void memorySaveWord(Memory* m, uint32_t address, uint16_t value);
|
||||
Instruction decodeInstruction(Memory* m, uint32_t pc);
|
||||
Instruction decodeInstruction(Memory* m, uint32_t* pc);
|
||||
Reference in New Issue
Block a user