instruction encoding and decoding working
This commit is contained in:
@@ -7,6 +7,6 @@ sources = [
|
|||||||
'src/instruction.c'
|
'src/instruction.c'
|
||||||
]
|
]
|
||||||
|
|
||||||
args = ['-Wall', '-Wextra', '-Wpedantic', '-O3', '-ggdb', '-fsanitize=address']
|
args = ['-Wall', '-Wextra', '-O3', '-ggdb', '-fsanitize=address']
|
||||||
|
|
||||||
executable('emu', sources, c_args: args)
|
executable('emu', sources, c_args: args)
|
||||||
143
src/emu.c
143
src/emu.c
@@ -1,5 +1,146 @@
|
|||||||
#include "emu.h"
|
#include "emu.h"
|
||||||
|
|
||||||
|
inline Instruction fetchInst(Emulator* emu) {
|
||||||
|
return decodeInstruction(emu->mem, &emu->pc);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void updateFlagsRegister(Emulator* emu, int32_t value) {
|
||||||
|
if (value == 0) {
|
||||||
|
emu->regs[REG_F] = FLAG_ZERO;
|
||||||
|
} else if (value < 0) {
|
||||||
|
emu->regs[REG_F] = FLAG_NEGATIVE;
|
||||||
|
} else if (value > UINT16_MAX) {
|
||||||
|
emu->regs[REG_F] = FLAG_OVERFLOW;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void startEmulator(Emulator* emu) {
|
||||||
|
static void* dispatchTable[] = {
|
||||||
|
&&MOV,
|
||||||
|
&&MVI,
|
||||||
|
&&ADD,
|
||||||
|
&&SUB,
|
||||||
|
&&LOAD,
|
||||||
|
&&STORE,
|
||||||
|
&&AND,
|
||||||
|
&&OR,
|
||||||
|
&&XOR,
|
||||||
|
&&SHF,
|
||||||
|
&&JMP,
|
||||||
|
&&JIZ,
|
||||||
|
&&CALL,
|
||||||
|
&&RET,
|
||||||
|
&&SYS,
|
||||||
|
&&HLT
|
||||||
|
};
|
||||||
|
|
||||||
|
emu->halted = false;
|
||||||
|
Instruction inst;
|
||||||
|
|
||||||
|
#define DISPATCH() inst = fetchInst(emu); \
|
||||||
|
goto *dispatchTable[inst.opcode];
|
||||||
|
|
||||||
|
DISPATCH();
|
||||||
|
|
||||||
|
MOV: {
|
||||||
|
emu->regs[inst.operands[0]] = emu->regs[inst.operands[1]];
|
||||||
|
DISPATCH();
|
||||||
|
}
|
||||||
|
MVI: {
|
||||||
|
emu->regs[inst.operands[0]] = inst.immediate;
|
||||||
|
DISPATCH();
|
||||||
|
}
|
||||||
|
ADD: {
|
||||||
|
int32_t result = emu->regs[inst.operands[0]] + emu->regs[inst.operands[1]];
|
||||||
|
updateFlagsRegister(emu, result);
|
||||||
|
emu->regs[inst.operands[0]] = result;
|
||||||
|
DISPATCH();
|
||||||
|
}
|
||||||
|
SUB: {
|
||||||
|
int32_t result = emu->regs[inst.operands[0]] - emu->regs[inst.operands[1]];
|
||||||
|
updateFlagsRegister(emu, result);
|
||||||
|
emu->regs[inst.operands[0]] = result;
|
||||||
|
DISPATCH();
|
||||||
|
}
|
||||||
|
LOAD: {
|
||||||
|
uint32_t addr = getAddress(
|
||||||
|
emu->regs[inst.operands[1]],
|
||||||
|
emu->regs[inst.operands[2]]
|
||||||
|
);
|
||||||
|
|
||||||
|
emu->regs[inst.operands[0]] = memoryLoadWord(emu->mem, addr);
|
||||||
|
DISPATCH();
|
||||||
|
}
|
||||||
|
STORE: {
|
||||||
|
uint32_t addr = getAddress(
|
||||||
|
emu->regs[inst.operands[0]],
|
||||||
|
emu->regs[inst.operands[1]]
|
||||||
|
);
|
||||||
|
memorySaveWord(emu->mem, addr, inst.operands[2]);
|
||||||
|
DISPATCH();
|
||||||
|
}
|
||||||
|
AND: {
|
||||||
|
int32_t result = emu->regs[inst.operands[0]] & emu->regs[inst.operands[1]];
|
||||||
|
updateFlagsRegister(emu, result);
|
||||||
|
emu->regs[inst.operands[0]] = result;
|
||||||
|
DISPATCH();
|
||||||
|
}
|
||||||
|
OR: {
|
||||||
|
int32_t result = emu->regs[inst.operands[0]] | emu->regs[inst.operands[1]];
|
||||||
|
updateFlagsRegister(emu, result);
|
||||||
|
emu->regs[inst.operands[0]] = result;
|
||||||
|
DISPATCH();
|
||||||
|
}
|
||||||
|
XOR: {
|
||||||
|
int32_t result = emu->regs[inst.operands[0]] ^ emu->regs[inst.operands[1]];
|
||||||
|
updateFlagsRegister(emu, result);
|
||||||
|
emu->regs[inst.operands[0]] = result;
|
||||||
|
DISPATCH();
|
||||||
|
}
|
||||||
|
SHF: {
|
||||||
|
int32_t result;
|
||||||
|
if (inst.operands[2] == 0)
|
||||||
|
result = emu->regs[inst.operands[0]] << emu->regs[inst.operands[1]];
|
||||||
|
else
|
||||||
|
result = emu->regs[inst.operands[0]] >> emu->regs[inst.operands[1]];
|
||||||
|
|
||||||
|
updateFlagsRegister(emu, result);
|
||||||
|
emu->regs[inst.operands[0]] = result;
|
||||||
|
DISPATCH();
|
||||||
|
}
|
||||||
|
JMP: {
|
||||||
|
emu->pc = getAddress(inst.operands[0], inst.operands[1]);
|
||||||
|
DISPATCH();
|
||||||
|
}
|
||||||
|
JIZ: {
|
||||||
|
if (emu->regs[REG_F] & FLAG_ZERO)
|
||||||
|
emu->pc = getAddress(inst.operands[0], inst.operands[1]);
|
||||||
|
DISPATCH();
|
||||||
|
}
|
||||||
|
CALL: {
|
||||||
|
// save the pc at the current sp and decrement the sp (stack grows downward)
|
||||||
|
((uint32_t*)emu->mem->data)[emu->sp--] = emu->pc;
|
||||||
|
|
||||||
|
// jump to given addr
|
||||||
|
emu->pc = getAddress(inst.operands[0], inst.operands[1]);
|
||||||
|
|
||||||
|
DISPATCH();
|
||||||
|
}
|
||||||
|
RET: {
|
||||||
|
// return to value at sp and increment it
|
||||||
|
emu->pc = ((uint32_t*)emu->mem->data)[++emu->sp];
|
||||||
|
DISPATCH();
|
||||||
|
}
|
||||||
|
SYS: {
|
||||||
|
// TODO
|
||||||
|
DISPATCH();
|
||||||
|
}
|
||||||
|
HLT: {
|
||||||
|
emu->halted = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Emulator* initEmulator(uint32_t memorySize) {
|
Emulator* initEmulator(uint32_t memorySize) {
|
||||||
Emulator* newEmu = malloc(sizeof(Emulator));
|
Emulator* newEmu = malloc(sizeof(Emulator));
|
||||||
if (!newEmu)
|
if (!newEmu)
|
||||||
@@ -9,7 +150,7 @@ Emulator* initEmulator(uint32_t memorySize) {
|
|||||||
memset(newEmu->regs, 0, sizeof(newEmu->regs));
|
memset(newEmu->regs, 0, sizeof(newEmu->regs));
|
||||||
|
|
||||||
// zero out special regs
|
// zero out special regs
|
||||||
newEmu->pc = 0;
|
newEmu->pc = MEM_PROGRAM_START;
|
||||||
newEmu->sp = 0;
|
newEmu->sp = 0;
|
||||||
newEmu->ihp = 0;
|
newEmu->ihp = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,13 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <stdbool.h>
|
||||||
#include "memory.h"
|
#include "memory.h"
|
||||||
|
|
||||||
|
#define FLAG_ZERO (1 << 0)
|
||||||
|
#define FLAG_NEGATIVE (1 << 1)
|
||||||
|
#define FLAG_OVERFLOW (1 << 2)
|
||||||
|
|
||||||
// -- TYPES -- //
|
// -- TYPES -- //
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint16_t regs[REG_MAX];
|
uint16_t regs[REG_MAX];
|
||||||
@@ -13,8 +18,11 @@ typedef struct {
|
|||||||
uint32_t pc;
|
uint32_t pc;
|
||||||
uint32_t ihp;
|
uint32_t ihp;
|
||||||
uint32_t sp;
|
uint32_t sp;
|
||||||
|
|
||||||
|
bool halted;
|
||||||
} Emulator;
|
} Emulator;
|
||||||
|
|
||||||
// -- FUNCTIONS -- //
|
// -- FUNCTIONS -- //
|
||||||
Emulator* initEmulator(uint32_t memorySize);
|
Emulator* initEmulator(uint32_t memorySize);
|
||||||
void freeEmulator(Emulator* emu);
|
void freeEmulator(Emulator* emu);
|
||||||
|
void startEmulator(Emulator* emu);
|
||||||
@@ -57,7 +57,7 @@ char* registerToCStr(Register reg) {
|
|||||||
|
|
||||||
char* instructionToCStr(Instruction inst) {
|
char* instructionToCStr(Instruction inst) {
|
||||||
|
|
||||||
size_t bufferSize = 64;
|
size_t bufferSize = 16;
|
||||||
char* buffer = malloc(bufferSize);
|
char* buffer = malloc(bufferSize);
|
||||||
if (!buffer)
|
if (!buffer)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
// -- TYPES -- //
|
// -- TYPES -- //
|
||||||
@@ -28,6 +29,7 @@ typedef struct {
|
|||||||
InstructionOpcode opcode;
|
InstructionOpcode opcode;
|
||||||
uint8_t operands[3];
|
uint8_t operands[3];
|
||||||
uint16_t immediate; // unused for almost all instructions
|
uint16_t immediate; // unused for almost all instructions
|
||||||
|
bool hasImm16;
|
||||||
} Instruction;
|
} Instruction;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
|
|||||||
12
src/main.c
12
src/main.c
@@ -8,8 +8,16 @@ int main() {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Instruction inst = decodeInstruction(emu->mem, &emu->pc);
|
Instruction program[] = {
|
||||||
printf("%s\n", instructionToCStr(inst));
|
{INST_MVI, {REG_A, 0, 0}, 5, true},
|
||||||
|
{INST_MVI, {REG_B, 0, 0}, 3, true},
|
||||||
|
{INST_ADD, {REG_A, REG_B, 0}, 0, false}
|
||||||
|
};
|
||||||
|
memoryLoadProgram(emu->mem, program, sizeof(program)/sizeof(program[0]));
|
||||||
|
|
||||||
|
printf("%s\n", instructionToCStr(decodeInstruction(emu->mem, &emu->pc)));
|
||||||
|
printf("%s\n", instructionToCStr(decodeInstruction(emu->mem, &emu->pc)));
|
||||||
|
printf("%s\n", instructionToCStr(decodeInstruction(emu->mem, &emu->pc)));
|
||||||
|
|
||||||
freeEmulator(emu);
|
freeEmulator(emu);
|
||||||
|
|
||||||
|
|||||||
53
src/memory.c
53
src/memory.c
@@ -23,8 +23,44 @@ inline void memorySaveWord(Memory* m, uint32_t address, uint16_t value) {
|
|||||||
((uint16_t*)m->data)[address] = value;
|
((uint16_t*)m->data)[address] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void putInst(Memory* m, SerializedInst inst, uint32_t offset) {
|
||||||
|
memcpy((SerializedInst*)(&m->data[MEM_PROGRAM_START]) + offset, &inst, sizeof(SerializedInst));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t memorySerializeInst(Memory* m, Instruction inst, uint32_t offset) {
|
||||||
|
SerializedInst 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;
|
||||||
|
|
||||||
|
memcpy(m->data + MEM_PROGRAM_START + (offset * sizeof(SerializedInst)), &packed, sizeof(SerializedInst));
|
||||||
|
|
||||||
|
|
||||||
|
offset++;
|
||||||
|
|
||||||
|
// write the imm16 if the instruction has one
|
||||||
|
if (inst.hasImm16) {
|
||||||
|
memcpy(m->data + MEM_PROGRAM_START + (offset * sizeof(SerializedInst)), &inst.immediate, sizeof(SerializedInst));
|
||||||
|
|
||||||
|
offset++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
void memoryLoadProgram(Memory* m, Instruction program[], size_t numInstructions) {
|
||||||
|
size_t offset = 0;
|
||||||
|
for (size_t i = 0; i < numInstructions; i++) {
|
||||||
|
offset = memorySerializeInst(m, program[i], offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Instruction decodeInstruction(Memory* m, uint32_t* pc) {
|
Instruction decodeInstruction(Memory* m, uint32_t* pc) {
|
||||||
SerializedInst serialized = m->data[*pc];
|
SerializedInst serialized;
|
||||||
|
memcpy(&serialized, &m->data[*pc], sizeof(SerializedInst));
|
||||||
|
|
||||||
Instruction outputInst = {};
|
Instruction outputInst = {};
|
||||||
|
|
||||||
uint8_t opcode = serialized & 0b0000000000001111; // get four lowest bits for the opcode
|
uint8_t opcode = serialized & 0b0000000000001111; // get four lowest bits for the opcode
|
||||||
@@ -38,14 +74,17 @@ Instruction decodeInstruction(Memory* m, uint32_t* pc) {
|
|||||||
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
|
||||||
outputInst.operands[offset] = (serialized >> (4 * (offset + 1))) & 0b0000000000001111;
|
outputInst.operands[i] = (serialized >> (4 * (offset + 1))) & 0b0000000000001111;
|
||||||
offset++;
|
offset++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
uint16_t nextDataOver = m->data[++*pc];
|
(*pc) += sizeof(SerializedInst);
|
||||||
|
|
||||||
|
uint16_t nextDataOver = m->data[*pc];
|
||||||
outputInst.immediate = nextDataOver;
|
outputInst.immediate = nextDataOver;
|
||||||
|
outputInst.hasImm16 = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,8 +92,12 @@ Instruction decodeInstruction(Memory* m, uint32_t* pc) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (opTypes[i] == OP_IMM16) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(*pc) += sizeof(SerializedInst);
|
||||||
|
|
||||||
return outputInst;
|
return outputInst;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,3 +105,7 @@ void freeMemory(Memory* m) {
|
|||||||
free(m->data);
|
free(m->data);
|
||||||
free(m);
|
free(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline uint32_t getAddress(uint16_t low, uint16_t high) {
|
||||||
|
return (high << 16) | low;
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
#include "instruction.h"
|
#include "instruction.h"
|
||||||
|
|
||||||
#define MEM_CALL_STACK_OFFSET 0x00000FFF
|
#define MEM_CALL_STACK_OFFSET 0x00000FFF
|
||||||
@@ -21,3 +23,6 @@ void freeMemory(Memory* m);
|
|||||||
uint16_t memoryLoadWord(Memory* m, uint32_t address);
|
uint16_t memoryLoadWord(Memory* m, uint32_t address);
|
||||||
void memorySaveWord(Memory* m, uint32_t address, uint16_t value);
|
void memorySaveWord(Memory* m, uint32_t address, uint16_t value);
|
||||||
Instruction decodeInstruction(Memory* m, uint32_t* pc);
|
Instruction decodeInstruction(Memory* m, uint32_t* pc);
|
||||||
|
uint32_t getAddress(uint16_t low, uint16_t high);
|
||||||
|
uint32_t memorySerializeInst(Memory* m, Instruction inst, uint32_t offset);
|
||||||
|
void memoryLoadProgram(Memory* m, Instruction program[], size_t numInstructions);
|
||||||
Reference in New Issue
Block a user