Refactor: Fetch-Decode phase is instruction-agnostic and operand-encoding dependant
This commit is contained in:
80
src/CPU.cpp
80
src/CPU.cpp
@@ -11,13 +11,11 @@
|
|||||||
CPU::CPU(std::shared_ptr<Bus>& bus) : m_Bus(bus), m_IsHalted(false), m_Context({m_Instruction, m_InstructionPointer, m_Flags, m_Registers, m_Bus, m_IsHalted}) {
|
CPU::CPU(std::shared_ptr<Bus>& bus) : m_Bus(bus), m_IsHalted(false), m_Context({m_Instruction, m_InstructionPointer, m_Flags, m_Registers, m_Bus, m_IsHalted}) {
|
||||||
m_InstructionPointer = 0x00008000;
|
m_InstructionPointer = 0x00008000;
|
||||||
|
|
||||||
for(int i = 0; i < 8; i++)
|
for(int i = 0; i < 8; i++) {
|
||||||
{
|
|
||||||
m_Registers[i] = 0;
|
m_Registers[i] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
for(int i = 0; i < 8; i++)
|
for(int i = 0; i < 8; i++) {
|
||||||
{
|
|
||||||
m_SegmentRegisters[i] = 0;
|
m_SegmentRegisters[i] = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,6 +48,7 @@ void CPU::Reset() {
|
|||||||
m_Registers[i] = 0;
|
m_Registers[i] = 0;
|
||||||
m_SegmentRegisters[i] = 0;
|
m_SegmentRegisters[i] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_InstructionPointer = 0x8000;
|
m_InstructionPointer = 0x8000;
|
||||||
m_Flags = 0;
|
m_Flags = 0;
|
||||||
|
|
||||||
@@ -58,6 +57,7 @@ void CPU::Reset() {
|
|||||||
m_Instruction.m_Opcode = (Opcode)0;
|
m_Instruction.m_Opcode = (Opcode)0;
|
||||||
m_Instruction.m_Operand1 = 0;
|
m_Instruction.m_Operand1 = 0;
|
||||||
m_Instruction.m_Operand2 = 0;
|
m_Instruction.m_Operand2 = 0;
|
||||||
|
m_Instruction.m_Func = nullptr;
|
||||||
std::memset(m_Instruction.m_Displacement, 0, 4);
|
std::memset(m_Instruction.m_Displacement, 0, 4);
|
||||||
m_Instruction.optional.m_ModRM = (x86::ModRM){ .m_State = x86::ModRMState::INVALID, .m_Reg = 0, .m_Rm = 0 };
|
m_Instruction.optional.m_ModRM = (x86::ModRM){ .m_State = x86::ModRMState::INVALID, .m_Reg = 0, .m_Rm = 0 };
|
||||||
}
|
}
|
||||||
@@ -70,44 +70,60 @@ void CPU::FetchDecode() {
|
|||||||
uint8_t opcode_raw = m_Bus->AccessX<uint8_t>(m_InstructionPointer);
|
uint8_t opcode_raw = m_Bus->AccessX<uint8_t>(m_InstructionPointer);
|
||||||
Opcode opcode = static_cast<Opcode>(opcode_raw);
|
Opcode opcode = static_cast<Opcode>(opcode_raw);
|
||||||
|
|
||||||
switch(opcode_raw) {
|
auto& instruction_table = GetInstructionTable();
|
||||||
case Opcode::MOV_R32_IMM32 ... 0xBF: // 0xB8 to 0xBF
|
if(instruction_table[opcode_raw].m_Executor == nullptr) {
|
||||||
m_Instruction.m_Opcode = Opcode::MOV_R32_IMM32;
|
std::cout << "Encoding: " << opcode_raw << std::endl;
|
||||||
m_Instruction.m_Operand1 = opcode_raw - 0xB8;
|
throw std::runtime_error("[CPU] [FetchDecode] Opcode is not in instruction table!");
|
||||||
m_Instruction.m_Operand2 = m_Bus->AccessX<uint32_t>(m_InstructionPointer + 1);
|
|
||||||
m_Instruction.m_Length = 5;
|
|
||||||
break;
|
|
||||||
case Opcode::NOP:
|
|
||||||
case Opcode::HLT:
|
|
||||||
m_Instruction.m_Opcode = opcode;
|
|
||||||
m_Instruction.m_Length = 1;
|
|
||||||
break;
|
|
||||||
case Opcode::ADD_RM32_R32:
|
|
||||||
m_Instruction.m_Opcode = opcode;
|
|
||||||
m_Instruction.optional.m_ModRM = x86::process_modrm(m_Bus->AccessX<uint8_t>(m_InstructionPointer + 1));
|
|
||||||
m_Instruction.m_Length = 2;
|
|
||||||
FetchModRMFields();
|
|
||||||
break;
|
|
||||||
case Opcode::MOV_RM32_R32:
|
|
||||||
m_Instruction.m_Opcode = opcode;
|
|
||||||
m_Instruction.optional.m_ModRM = x86::process_modrm(m_Bus->AccessX<uint8_t>(m_InstructionPointer + 1));
|
|
||||||
m_Instruction.m_Length = 2;
|
|
||||||
FetchModRMFields();
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto& instruction = instruction_table[opcode_raw];
|
||||||
|
|
||||||
|
uint8_t encoding_mask = 0b00001111;
|
||||||
|
uint8_t encoding = instruction.m_Encoding & encoding_mask;
|
||||||
|
uint8_t immediate = instruction.m_Encoding & ~encoding_mask;
|
||||||
|
|
||||||
|
switch(encoding) {
|
||||||
|
case OI:
|
||||||
|
if (immediate == I32) {
|
||||||
|
m_Instruction.m_Operand1 = m_Bus->AccessX<uint32_t>(m_InstructionPointer + 1);
|
||||||
|
m_Instruction.m_Length = 5;
|
||||||
|
} else if (immediate = I16) {
|
||||||
|
m_Instruction.m_Operand1 = m_Bus->AccessX<uint16_t>(m_InstructionPointer + 1);
|
||||||
|
m_Instruction.m_Length = 3;
|
||||||
|
} else if (immediate = I8) {
|
||||||
|
m_Instruction.m_Operand1 = m_Bus->AccessX<uint8_t>(m_InstructionPointer + 1);
|
||||||
|
m_Instruction.m_Length = 2;
|
||||||
|
} else {
|
||||||
|
throw std::runtime_error("[CPU] [FetchDecode] OI instruction encoding only supports imm8,imm16,imm32.");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ZO:
|
||||||
|
m_Instruction.m_Length = 1;
|
||||||
|
break;
|
||||||
|
case RM:
|
||||||
|
case MR:
|
||||||
|
m_Instruction.optional.m_ModRM = x86::process_modrm(m_Bus->AccessX<uint8_t>(m_InstructionPointer + 1));
|
||||||
|
m_Instruction.m_Length = 2;
|
||||||
|
FetchModRMFields();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("[CPU] [FetchDecode] Encoding was not found!");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Instruction.m_Func = instruction.m_Executor;
|
||||||
|
m_Instruction.m_Opcode = opcode;
|
||||||
m_InstructionPointer += m_Instruction.m_Length;
|
m_InstructionPointer += m_Instruction.m_Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPU::Execute() {
|
void CPU::Execute() {
|
||||||
uint8_t opcode_value = static_cast<uint8_t>(m_Instruction.m_Opcode);
|
uint8_t opcode_value = static_cast<uint8_t>(m_Instruction.m_Opcode);
|
||||||
auto& exec_table = GetExecutorTable();
|
|
||||||
if(exec_table[opcode_value])
|
if(m_Instruction.m_Func != nullptr)
|
||||||
{
|
{
|
||||||
exec_table[opcode_value](m_Context);
|
m_Instruction.m_Func(m_Context);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
throw std::runtime_error("Opcode not found!");
|
throw std::runtime_error("[CPU] [Execute] m_Func is nullptr!");
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPU::FetchModRMFields() {
|
void CPU::FetchModRMFields() {
|
||||||
|
|||||||
@@ -8,10 +8,6 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
namespace executor_cases {
|
namespace executor_cases {
|
||||||
void Mov_r32_imm32(CPUContext& cc) {
|
|
||||||
std::cout << "[Instruction] mov " << x86::Register2Str((x86::Register)cc.m_Instruction.m_Operand1) << ", " << std::hex << cc.m_Instruction.m_Operand2 << std::endl;
|
|
||||||
cc.m_Registers[cc.m_Instruction.m_Operand1] = cc.m_Instruction.m_Operand2;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Mov_rm32_r32(CPUContext& cc) {
|
void Mov_rm32_r32(CPUContext& cc) {
|
||||||
x86::ModRM modrm = cc.m_Instruction.optional.m_ModRM;
|
x86::ModRM modrm = cc.m_Instruction.optional.m_ModRM;
|
||||||
|
|||||||
@@ -1,8 +1,22 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
struct CPUContext;
|
#include <type_traits>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <iostream>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "CPUContext.h"
|
||||||
|
#include "Instruction.h"
|
||||||
|
|
||||||
namespace executor_cases {
|
namespace executor_cases {
|
||||||
void Mov_r32_imm32(CPUContext& cc);
|
template<typename T, int O>
|
||||||
|
void Mov_rX_immX(CPUContext& cc) {
|
||||||
|
static_assert(std::is_unsigned_v<T>, "Mov_rX_immX requires an unsigned type!");
|
||||||
|
uint8_t reg = cc.m_Instruction.m_Opcode - O;
|
||||||
|
std::cout << "[Instruction] mov " << x86::Register2Str((x86::Register)reg) << ", " << std::hex << cc.m_Instruction.m_Operand1 << std::endl;
|
||||||
|
std::memcpy(&cc.m_Registers[reg], &cc.m_Instruction.m_Operand1, sizeof(T));
|
||||||
|
// cc.m_Registers[reg] = cc.m_Instruction.m_Operand1;
|
||||||
|
}
|
||||||
|
|
||||||
void Mov_rm32_r32(CPUContext& cc);
|
void Mov_rm32_r32(CPUContext& cc);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,21 +12,40 @@
|
|||||||
#include <bitset>
|
#include <bitset>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
constexpr std::array<ExecutorCase, 255> GenerateExecutorTable(){
|
constexpr std::array<InstructionEntry, 255> GenerateInstructionTable(){
|
||||||
std::array<ExecutorCase, 255> table{};
|
std::array<InstructionEntry, 255> table{};
|
||||||
table[Opcode::NOP] = executor_cases::Nop;
|
|
||||||
table[Opcode::HLT] = executor_cases::Hlt;
|
for(uint8_t i = 0; i < 255; i++) {
|
||||||
table[Opcode::MOV_R32_IMM32] = executor_cases::Mov_r32_imm32;
|
table[i] = (InstructionEntry){nullptr, (OperandEncoding)0};
|
||||||
table[Opcode::MOV_RM32_R32] = executor_cases::Mov_rm32_r32;
|
}
|
||||||
table[Opcode::ADD_RM32_R32] = executor_cases::Add_rm32_r32;
|
|
||||||
table[Opcode::ADD_R32_RM32] = executor_cases::Add_r32_rm32;
|
table[Opcode::NOP] = (InstructionEntry){executor_cases::Nop, ZO};
|
||||||
|
table[Opcode::HLT] = (InstructionEntry){executor_cases::Hlt, ZO};
|
||||||
|
// table[Opcode::MOV_R32_IMM16] = (InstructionEntry){executor_cases::Mov_rX_immX<uint16_t, 0xB8>, (OperandEncoding)(I16 | OI)};
|
||||||
|
table[Opcode::MOV_R32_IMM32] = (InstructionEntry){executor_cases::Mov_rX_immX<uint32_t, 0xB8>, (OperandEncoding)(I32 | OI)};
|
||||||
|
table[Opcode::MOV_RM32_R32] = (InstructionEntry){executor_cases::Mov_rm32_r32, MR};
|
||||||
|
table[Opcode::ADD_RM32_R32] = (InstructionEntry){executor_cases::Add_rm32_r32, MR};
|
||||||
|
table[Opcode::ADD_R32_RM32] = (InstructionEntry){executor_cases::Add_r32_rm32, RM};
|
||||||
|
|
||||||
|
for(uint8_t i = 0; i < 255;)
|
||||||
|
{
|
||||||
|
if((table[i].m_Encoding & OI) != 0)
|
||||||
|
{
|
||||||
|
for(int j = 1; j < 8 && (i + j) < 255; ++j) {
|
||||||
|
table[i+j] = table[i];
|
||||||
|
}
|
||||||
|
i += 8;
|
||||||
|
} else {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
return table;
|
return table;
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr std::array<ExecutorCase, 255> s_ExecutorTable = GenerateExecutorTable();
|
static constexpr std::array<InstructionEntry, 255> s_InstructionTable = GenerateInstructionTable();
|
||||||
|
|
||||||
const std::array<ExecutorCase, 255>& GetExecutorTable() {
|
const std::array<InstructionEntry, 255>& GetInstructionTable() {
|
||||||
return s_ExecutorTable;
|
return s_InstructionTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace executor_cases::helpers {
|
namespace executor_cases::helpers {
|
||||||
|
|||||||
@@ -8,6 +8,24 @@ struct CPUContext;
|
|||||||
|
|
||||||
typedef void (*ExecutorCase)(CPUContext&);
|
typedef void (*ExecutorCase)(CPUContext&);
|
||||||
|
|
||||||
|
enum OperandEncoding : uint8_t {
|
||||||
|
ZO = 0b00000000,
|
||||||
|
RM = 0b00000001,
|
||||||
|
MR = 0b00000010,
|
||||||
|
MI = 0b00000100,
|
||||||
|
OI = 0b00001000,
|
||||||
|
I32 = 0b00100000,
|
||||||
|
I16 = 0b01000000,
|
||||||
|
I8 = 0b10000000,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct InstructionEntry {
|
||||||
|
ExecutorCase m_Executor;
|
||||||
|
OperandEncoding m_Encoding;
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::array<InstructionEntry, 255>& GetInstructionTable();
|
||||||
|
|
||||||
const std::array<ExecutorCase, 255>& GetExecutorTable();
|
const std::array<ExecutorCase, 255>& GetExecutorTable();
|
||||||
|
|
||||||
namespace executor_cases::helpers {
|
namespace executor_cases::helpers {
|
||||||
|
|||||||
@@ -53,13 +53,13 @@ namespace x86 {
|
|||||||
|
|
||||||
std::string Opcode2Str(Opcode op) {
|
std::string Opcode2Str(Opcode op) {
|
||||||
switch(op) {
|
switch(op) {
|
||||||
case 0: return "EMPTY";
|
case Opcode::INVALID: return "INVALID";
|
||||||
case Opcode::ADD_RM32_R32: return "ADD_RM32_R32";
|
case Opcode::ADD_RM32_R32: return "ADD_RM32_R32";
|
||||||
case Opcode::ADD_R32_RM32: return "ADD_R32_RM32";
|
case Opcode::ADD_R32_RM32: return "ADD_R32_RM32";
|
||||||
case Opcode::MOV_R32_IMM32: return "MOV_R32_IMM32";
|
case Opcode::MOV_R32_IMM32 ... 0xBF: return "MOV_R32_IMM32";
|
||||||
case Opcode::MOV_RM32_R32: return "MOV_RM32_R32";
|
case Opcode::MOV_RM32_R32: return "MOV_RM32_R32";
|
||||||
case Opcode::NOP: return "NOP";
|
case Opcode::NOP: return "NOP";
|
||||||
case Opcode::HLT: return "HLT";
|
case Opcode::HLT: return "HLT";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string what = "Opcode '" + std::to_string(op) + "' invalid!";
|
std::string what = "Opcode '" + std::to_string(op) + "' invalid!";
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ namespace x86 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum Opcode : uint8_t {
|
enum Opcode : uint8_t {
|
||||||
|
INVALID = 0,
|
||||||
NOP = 0x90,
|
NOP = 0x90,
|
||||||
HLT = 0xF4,
|
HLT = 0xF4,
|
||||||
MOV_R32_IMM32 = 0xB8,
|
MOV_R32_IMM32 = 0xB8,
|
||||||
@@ -45,6 +46,8 @@ enum Opcode : uint8_t {
|
|||||||
std::string Opcode2Str(Opcode op);
|
std::string Opcode2Str(Opcode op);
|
||||||
|
|
||||||
struct Instruction{
|
struct Instruction{
|
||||||
|
ExecutorCase m_Func;
|
||||||
|
uint8_t m_Prefix;
|
||||||
Opcode m_Opcode;
|
Opcode m_Opcode;
|
||||||
size_t m_Length;
|
size_t m_Length;
|
||||||
uint32_t m_Operand1;
|
uint32_t m_Operand1;
|
||||||
|
|||||||
Reference in New Issue
Block a user