Initial commit
This commit is contained in:
127
emulator.cpp
Normal file
127
emulator.cpp
Normal 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
67
emulator.h
Normal 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
5
instruction.cpp
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#include "instruction.h"
|
||||||
|
|
||||||
|
Instruction::Instruction(BytecodeInstruction bytecodeInst) {
|
||||||
|
opcode = (InstType)(bytecodeInst & 0b00001111);
|
||||||
|
}
|
||||||
32
instruction.h
Normal file
32
instruction.h
Normal 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
17
main.cpp
Normal 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
8
meson.build
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
project('mlcpu', 'cpp', version : '0.1.0')
|
||||||
|
|
||||||
|
sources = files(
|
||||||
|
'main.cpp',
|
||||||
|
'emulator.cpp'
|
||||||
|
)
|
||||||
|
|
||||||
|
program = executable('mlcpu', sources)
|
||||||
Reference in New Issue
Block a user