instruction decoding

This commit is contained in:
2026-07-15 15:54:51 +10:00
parent 8e26d3e19d
commit 9d0c1de3f5
8 changed files with 195 additions and 46 deletions

View File

@@ -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)

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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);