Compile time CPU In-Depth

This project was a small experiment I did with C++ and it's compiler. The goal was to be able to run the given assembly of a custom specification at compile time in a very simple Virtual Machine and then extract the result from the cpu state.

The following code shows a simple program that fills the entire memory of the cpu with number from 0 to 127.

#include "ctcpu.h"

int main()
{
	constexpr std::array<op, 6U> code{
		op { 0x07, REG_A, 0x00 },   // MOV REG_A 0x00   ; Move 0 in to Register A
		op { 0x07, REG_B, 0x7F },   // MOV REG_B 0x7F   ; Move 127 in to Register B
		op { 0x0A, REG_A, REG_A },  // STA REG_A REG_A  ; Store contents of Register A at address stored in Register A
		op { 0x08, REG_A },         // INC REG_A        ; Increment contents of register A
		op { 0x05 },                // CMP              ; Compare Register A and Register B and store result in Register C
		op { 0x03, 0x02 }           // JNZ 0x02         ; Jump to 0x02 if Register C != 0U
	};
	
    constexpr auto c = run(code, cpu{});
    return c.memory.ram[0x33];
}

The following code is the implementation of the compile time virtual machine that runs the given assembly.

#ifndef CTCPU_H
#define CTCPU_H

#include <array>

constexpr int8_t REG_A = 0x00;
constexpr int8_t REG_B = 0x01;
constexpr int8_t REG_C = 0x02;

struct op
{
	constexpr op(int8_t _op, int8_t _o1, int8_t _o2) : opCode(_op), o1(_o1), o2(_o2) {}
	constexpr op(int8_t _op, int8_t _o1) : opCode(_op), o1(_o1), o2(0x00) {}
	constexpr op(int8_t _op) : opCode(_op), o1(0x00), o2(0x00) {}
    
	int8_t opCode, o1, o2;
};

// Cpu state containing the Registers, Ram and Flags
struct cpu
{
	struct memory
	{
		int8_t registers[3]{};
		int8_t ram[128]{};
		struct
		{
			int8_t carry : 1;
			int8_t hlt : 1;
		}flags;
	}memory;
    
    uint8_t pc = 0U;
};

template<uint64_t S>
constexpr cpu run(const std::array<op, S>& ops, cpu c)
{
  // Instructions supported by VM 
	constexpr std::array<void(*)(int8_t, int8_t, cpu*), 256> op_solvers
	{
		[](int8_t, int8_t, cpu* c) constexpr { c->memory.registers[REG_C] = c->memory.registers[REG_A] + c->memory.registers[REG_B]; }, // 0x00, ADD => REG_C = REG_A + REG_B
		[](int8_t, int8_t, cpu* c) constexpr { c->memory.registers[REG_C] = c->memory.registers[REG_A] + c->memory.registers[REG_B]; }, // 0x01, SUB => REG_C = REG_A - REG_B
		[](int8_t o1, int8_t, cpu* c) constexpr { c->pc = (uint8_t)o1; }, // 0x02, JMP OA => PC = OA
		[](int8_t o1, int8_t, cpu* c) constexpr { if (c->memory.registers[REG_C] != 0U) c->pc = (uint8_t)o1; }, // 0x03, JNZ OA => PC = OA if REG_C != 0
		[](int8_t o1, int8_t, cpu* c) constexpr { if (c->memory.registers[REG_C] == 0U) c->pc = (uint8_t)o1; }, // 0x04, JZ OA => PC = OA if REG_C == 0
		[](int8_t, int8_t, cpu* c) constexpr { c->memory.registers[REG_C] = (c->memory.registers[REG_A] == c->memory.registers[REG_B]) ? 0x00 : 0x01; }, // 0x05, CMP => REG_C = REG_A == REG_B = 0 : 1
		[](int8_t o1, int8_t o2, cpu* c) constexpr { c->memory.ram[o2] = c->memory.registers[o1]; }, // 0x06, STO OA OB => RAM[OB] = OA
		[](int8_t o1, int8_t o2, cpu* c) constexpr { c->memory.registers[o1] = o2; }, // 0x07, MOV OA OB => REG[OA] = OB
		[](int8_t o1, int8_t, cpu* c) constexpr { c->memory.registers[o1]++; }, // 0x08, INC OA OB => REG[OA]++
		[](int8_t o1, int8_t, cpu* c) constexpr { c->memory.registers[o1]--; }, // 0x09, DEC OA OB => REG[OA]--
		[](int8_t o1, int8_t o2, cpu* c) constexpr { c->memory.ram[c->memory.registers[o1]] = c->memory.registers[o2]; }, // 0x0A, STA OA OB => RAM[REG[OA]] = REG[OB]
	};
    
	while (c.pc < ops.size())
	{
    // Fetch instruction
		const auto o = ops[c.pc++]; 
    
    // Decode and execute
		op_solvers[o.opCode](o.o1, o.o2, &c);
	}
    
	return c;
}

#endif // !CTCPU_H

Click here for the repository of the project.