Initial commit

This commit is contained in:
2026-07-14 19:54:23 +10:00
commit 7aecc9c576
6 changed files with 256 additions and 0 deletions

127
emulator.cpp Normal file
View File

@@ -0,0 +1,127 @@
#include "emulator.h"
#include "instruction.h"
#include <cstdint>
#include <stdexcept>
#include <limits>
#include <cstring>
void Memory::addInstruction(const BytecodeInstruction& inst) {
// make sure we won't write an instruction out of bounds
constexpr size_t maxoffset = (GENERAL_PURPOSE_START - PROGRAM_ROM_START) / sizeof(BytecodeInstruction);
if (instructions >= maxoffset) {
throw std::runtime_error("out of space for program");
}
// get offset for next instruction
size_t offset = PROGRAM_ROM_START + (sizeof(Instruction) * instructions++);
uint8_t* memoffset = &this->memory[offset];
std::memcpy(memoffset, &inst, sizeof(Instruction));
}
void Memory::addCall(const uint16_t address) {
// make sure we're not out of call stack space
constexpr size_t maxoffset = (GENERAL_PURPOSE_START - PROGRAM_ROM_START) / sizeof(Instruction);
if (instructions >= maxoffset) {
throw std::runtime_error("out of space for call stack");
}
size_t offset = sizeof(uint16_t) * calls++;
uint8_t* memoffset = &this->memory[offset];
std::memcpy(memoffset, &address, sizeof(uint16_t));
}
uint8_t* Memory::operator[](const size_t& offset) {
if (offset >= MEMORY_SIZE) {
throw std::runtime_error("offset out of bounds");
}
return &memory[offset];
}
uint32_t combineLowAndHighBits(uint16_t low, uint16_t high) {
return (((uint32_t)high << 16) | low);
}
void Emulator::updateFlags(int32_t opResult) {
if (opResult > std::numeric_limits<uint16_t>::max()) {
FLAGS = FLAGS_CARRY;
} else if (opResult == 0) {
FLAGS = FLAGS_ZERO;
} else if (opResult < 0) {
FLAGS = FLAGS_NEGATIVE;
}
}
Instruction Emulator::fetchNextInst() {
Instruction* insts = reinterpret_cast<Instruction*>(memory.memory); \
Instruction inst = (insts + PROGRAM_ROM_START)[PC++];
return inst;
}
void Emulator::start() {
halted = false;
static const void* dispatchTable[] = {
&&MOV,
&&MVI,
&&ADD,
&&SUB,
&&LOAD,
&&STORE,
&&AND,
&&OR,
&&XOR,
&&SHF,
&&JMP,
&&JIZ,
&&CALL,
&&RET,
&&SYS,
&&HLT
};
Instruction inst;
#define DISPATCH() if (halted) return; \
inst = fetchNextInst(); \
goto *dispatchTable[static_cast<size_t>(inst.opcode)];
DISPATCH();
MOV: {
registers[inst.arg1] = registers[inst.arg2];
DISPATCH();
}
MVI: {
PC++; // the instruction reads the space where the next instruction would be as a number
registers[inst.arg1] = *reinterpret_cast<uint16_t*>(memory[PC]);
DISPATCH();
}
ADD: {
int32_t result = registers[inst.arg1] + registers[inst.arg2];
updateFlags(result);
registers[inst.arg1] = static_cast<uint16_t>(result);
DISPATCH();
}
SUB: {
int32_t result = registers[inst.arg1] - registers[inst.arg2];
updateFlags(result);
registers[inst.arg1] = static_cast<uint16_t>(result);
DISPATCH();
}
LOAD: {
registers[inst.arg1] = *reinterpret_cast<uint16_t*>(memory[combineLowAndHighBits(inst.arg2, inst.arg3)]);
DISPATCH();
}
STORE: {
*(reinterpret_cast<uint16_t*>(memory[combineLowAndHighBits(inst.arg1, inst.arg2)])) = registers[inst.arg3];
DISPATCH();
}
AND: {
}
}

67
emulator.h Normal file
View File

@@ -0,0 +1,67 @@
#pragma once
#include <cstdint>
#include <vector>
#include <array>
#include "instruction.h"
#define MEMORY_SIZE 0x00040000 // 256 kibibytes, or 262144
#define PROGRAM_ROM_START 0x00001000
#define GENERAL_PURPOSE_START 0x00010000
#define PC_REG_OFFSET 7
#define IHP_REG_OFFSET 8
#define SP_REG_OFFSET 9
#define FLAGS_REG_OFFSET 10
#define PC registers[PC_REG_OFFSET]
#define IHP registers[PC_REG_OFFSET]
#define SP registers[PC_REG_OFFSET]
#define FLAGS registers[PC_REG_OFFSET]
#define FLAGS_CARRY (1 << 0)
#define FLAGS_ZERO (1 << 1)
#define FLAGS_NEGATIVE (1 << 2)
/*
* All-purpose memory used by the emulator
* 4kb for call stack (0x00000000 -> 0x00000FFF)
* 65kb for program ROM (0x00001000 -> 0x0000FFFF)
* Rest for general purpose (0x00010000 -> MEMORY_SIZE)
*/
struct Memory {
size_t instructions = 0;
size_t calls = 0;
uint8_t memory[MEMORY_SIZE]{};
void addInstruction(const Instruction& inst);
void addCall(const uint16_t address);
uint8_t* operator[](const size_t& offset);
Memory() = default;
Memory(const std::vector<Instruction>& instructions) {
for (const auto& instruction : instructions) {
addInstruction(instruction.asBytecodeInstruction());
}
}
};
using Program = std::vector<Instruction>;
class Emulator {
private:
std::array<uint16_t, 11> registers = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
Memory memory = {};
bool halted = false;
Instruction fetchNextInst();
void updateFlags(int32_t opResult);
public:
void start();
Emulator() = delete;
Emulator(const Memory& memory) : memory(std::move(memory)) {}
};

5
instruction.cpp Normal file
View File

@@ -0,0 +1,5 @@
#include "instruction.h"
Instruction::Instruction(BytecodeInstruction bytecodeInst) {
opcode = (InstType)(bytecodeInst & 0b00001111);
}

32
instruction.h Normal file
View File

@@ -0,0 +1,32 @@
#pragma once
#include <cstdint>
#include <unordered_map>
#include <vector>
enum class InstType : uint8_t {
MOV, MVI, ADD, SUB, LOAD, STORE, AND, OR, XOR, SHF, JMP, JIZ, CALL, RET, SYS, HLT
};
enum class OperandType {
ImmNone, Imm4, Imm8
};
using BytecodeInstruction = uint16_t;
static std::unordered_map<InstType, std::vector<OperandType>> instructionOperandTypes = {
{InstType::MOV, {OperandType::Imm4, OperandType::Imm4, OperandType::Imm4}}
};
struct Instruction {
InstType opcode = InstType::MOV;
uint16_t arg1 = 0;
uint16_t arg2 = 0;
uint16_t arg3 = 0;
BytecodeInstruction asBytecodeInstruction() const;
Instruction() = default;
Instruction(InstType opcode, uint16_t arg1, uint16_t arg2, uint16_t arg3)
: opcode(opcode), arg1(arg1), arg2(arg2), arg3(arg3) {}
Instruction(BytecodeInstruction bytecodeInst);
};

17
main.cpp Normal file
View File

@@ -0,0 +1,17 @@
#include "emulator.h"
#include <iostream>
// I'm too lazy to write these every time
using in = Instruction;
using it = InstType;
int main() {
std::cout << "Starting VM..." << std::endl;
Memory memory({
in()
});
Emulator emulator(memory);
emulator.start();
}

8
meson.build Normal file
View File

@@ -0,0 +1,8 @@
project('mlcpu', 'cpp', version : '0.1.0')
sources = files(
'main.cpp',
'emulator.cpp'
)
program = executable('mlcpu', sources)