Skip to content
Snippets Groups Projects
Commit 1d16fdad authored by Geo Ster's avatar Geo Ster
Browse files

Introducing the IOP

* So after a week, it's finally here! The initial implementation of the
IOP has been added to the emulator. You might wonder why did it take so
long? This was mostly because I wanted to make the implementation as complete
as possible and also test it to ensure it's bug free. So this is actually
based on the MIPS R3000A interpreter I wrote last year for my PS1 emulator.
So did I just copy the code and call it a day? Hell no, the code in that
ancient project is awful, even if it works. So I completely rewrote the
interpreter by using our modern techiniques of storing state. So rewriting the old
code allowed me to test if it actually worked in that environment
and could boot PSX games.

* Due to this, the implementation is a bit more complete than the EE
as it includes interrupt support. In addition we have to account for
the fact that the IOP runs at 36.864MHz, in constrast to the EE which
clocks at 295MHz. This maps approximatly to an 1/8 ratio, which means
that 1 IOP instruction will run every 8 EE cycles. The current implementation
of this is hacky and a bit inaccurate because some EE instructions
can take more than 1 cycle to execute, but it's good enough for now
(Play! assumes this as well and can boot 40%+ of games).

* Because both the CPU emulators can share a lot of naming conventions,
to avoid confusion each processor has been seperated into a namespace
so we can always know which CPU we are refering to. Finally, for now
reads/writes except for the BIOS and IOP RAM, haven't been implemented
but will come soon.
parent 6355a7f8
No related branches found
No related tags found
No related merge requests found
......@@ -10,7 +10,10 @@ include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup()
# Create executable target
add_executable(${PROJECT_NAME} src/main.cpp src/common/manager.cpp src/cpu/ee.cpp )
add_executable(${PROJECT_NAME} src/main.cpp
src/common/manager.cpp
src/cpu/ee/ee.cpp
src/cpu/iop/iop.cpp)
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)
# Link to all the required conan libs
......
#include <common/manager.hpp>
#include <common/memmap.hpp>
#include <fstream>
#include <fmt/core.h>
#include <cpu/ee/ee.hpp>
#include <cpu/iop/iop.hpp>
std::ofstream console("console.txt", std::ios::out);
int cycles_executed = 0;
ComponentManager::ComponentManager()
{
ee = std::make_unique<EmotionEngine>(this);
/* Allocate the entire 32MB of RAM */
ram = new uint8_t[RAM.length];
bios = new uint8_t[BIOS.length];
std::memset(ram, 0, RAM.length);
ram = new uint8_t[ee::RAM.length];
iop_ram = new uint8_t[iop::RAM.length];
bios = new uint8_t[ee::BIOS.length];
std::memset(ram, 0, ee::RAM.length);
std::memset(iop_ram, 0, iop::RAM.length);
console.open("console.txt", std::ios::out);
/* Load the BIOS in our memory */
/* NOTE: Must make a GUI for this someday */
this->read_bios();
/* Finally construct our components */
ee = std::make_unique<ee::EmotionEngine>(this);
iop = std::make_unique<iop::IOProcessor>(this);
}
ComponentManager::~ComponentManager()
{
delete[] ram;
delete[] iop_ram;
delete[] bios;
}
void ComponentManager::read_bios()
{
{
/* Yes it's hardcoded for now, don't bite me, I'll change it eventually */
std::ifstream reader;
reader.open("SCPH-10000.BIN", std::ios::in | std::ios::binary);
if (!reader.is_open())
exit(1);
std::abort();
reader.seekg(0);
reader.read((char*)bios, BIOS.length);
reader.read((char*)bios, ee::BIOS.length);
reader.close();
}
void ComponentManager::tick()
{
ee->tick();
}
/* Instanciate the templates here so we can limit their types below */
template <typename T>
T ComponentManager::read(uint32_t addr)
{
uint32_t vaddr = addr & KUSEG_MASKS[addr >> 29];
/* Check if it's any memory ranges we care */
if (RAM.contains(vaddr))
return *(T*)&ram[RAM.offset(vaddr)];
else if (BIOS.contains(vaddr))
return *(T*)&bios[BIOS.offset(vaddr)];
else /* Otherwise handle specific address diferently. */
{
switch (vaddr)
{
case 0x1000f440:
{
uint8_t SOP = (MCH_RICM >> 6) & 0xF;
uint8_t SA = (MCH_RICM >> 16) & 0xFFF;
if (!SOP)
{
switch (SA)
{
case 0x21:
if (rdram_sdevid < 2)
{
rdram_sdevid++;
return 0x1F;
}
return 0;
case 0x23:
return 0x0D0D;
case 0x24:
return 0x0090;
case 0x40:
return MCH_RICM & 0x1F;
}
}
return 0;
}
default:
fmt::print("[WARNING] {:d}bit read from unknown address {:#x}\n", sizeof(T) * 8, addr);
return 0;
}
}
}
/* Instanciate the templates here so we can limit their types below */
template <typename T>
void ComponentManager::write(uint32_t addr, T data)
{
uint32_t vaddr = addr & KUSEG_MASKS[addr >> 29];
/* Check if it's any memory ranges we care */
if (RAM.contains(vaddr))
*(T*)&ram[RAM.offset(vaddr)] = data;
else
{
switch (vaddr)
{
/* Record any console output */
case 0x1000f180:
{
console << (char)data;
console.flush();
break;
}
case 0x1000f430:
{
uint8_t SA = (data >> 16) & 0xFFF;
uint8_t SBC = (data >> 6) & 0xF;
if (SA == 0x21 && SBC == 0x1 && ((MCH_DRD >> 7) & 1) == 0)
rdram_sdevid = 0;
MCH_RICM = data & ~0x80000000;
break;
}
case 0x1000f440:
{
MCH_DRD = data;
break;
}
default:
fmt::print("[WARNING] {:d}bit write {:#x} to unknown address {:#x}\n", sizeof(T) * 8, data, addr);
break;
};
}
}
cycles_executed++;
/* Template definitions. */
template uint32_t ComponentManager::read<uint32_t>(uint32_t);
template uint64_t ComponentManager::read<uint64_t>(uint32_t);
template uint8_t ComponentManager::read<uint8_t>(uint32_t);
template uint16_t ComponentManager::read<uint16_t>(uint32_t);
template void ComponentManager::write<uint32_t>(uint32_t, uint32_t);
template void ComponentManager::write<uint64_t>(uint32_t, uint64_t);
template void ComponentManager::write<uint8_t>(uint32_t, uint8_t);
template void ComponentManager::write<uint16_t>(uint32_t, uint16_t);
\ No newline at end of file
if (cycles_executed % 8 == 0)
iop->tick();
}
\ No newline at end of file
#pragma once
#include <common/memmap.hpp>
#include <fmt/core.h>
#include <memory>
#include <vector>
#include <cpu/ee.hpp>
#include <fstream>
namespace ee {
class EmotionEngine;
}
namespace iop {
class IOProcessor;
}
enum Component
{
EE = 0x0,
IOP = 0x1
};
/* This class act as the "motherboard" of sorts */
class ComponentManager {
......@@ -13,21 +29,124 @@ public:
/* Memory operations */
template <typename T>
T read(uint32_t addr);
T read(uint32_t addr, Component comp);
template <typename T>
void write(uint32_t addr, T data);
void write(uint32_t addr, T data, Component comp);
protected:
void read_bios();
public:
/* Components */
std::unique_ptr<EmotionEngine> ee;
std::unique_ptr<ee::EmotionEngine> ee;
std::unique_ptr<iop::IOProcessor> iop;
/* Memory - Registers */
uint8_t* ram, * bios;
uint8_t* ram, * iop_ram, * bios;
uint32_t MCH_RICM = 0, MCH_DRD = 0;
uint8_t rdram_sdevid = 0;
};
\ No newline at end of file
/* Utilities */
const char* component_name[2] = { "EE", "IOP" };
std::atomic_bool stop_thread = false;
std::ofstream console;
};
/* Instanciate the templates here so we can limit their types below */
template <typename T>
T ComponentManager::read(uint32_t addr, Component comp)
{
uint32_t vaddr = addr & KUSEG_MASKS[addr >> 29];
/* Check if it's any memory ranges we care */
if (ee::RAM.contains(vaddr))
return *(T*)&ram[ee::RAM.offset(vaddr)];
else if (ee::BIOS.contains(vaddr))
return *(T*)&bios[ee::BIOS.offset(vaddr)];
else if (iop::RAM.contains(vaddr))
return *(T*)&iop_ram[iop::RAM.offset(vaddr)];
else /* Otherwise handle specific address diferently. */
{
switch (vaddr)
{
case 0x1000f130:
case 0x1000f430:
return 0;
case 0x1000f440:
{
uint8_t SOP = (MCH_RICM >> 6) & 0xF;
uint8_t SA = (MCH_RICM >> 16) & 0xFFF;
if (!SOP)
{
switch (SA)
{
case 0x21:
if (rdram_sdevid < 2)
{
rdram_sdevid++;
return 0x1F;
}
return 0;
case 0x23:
return 0x0D0D;
case 0x24:
return 0x0090;
case 0x40:
return MCH_RICM & 0x1F;
}
}
return 0;
}
default:
fmt::print("[{}] {:d}bit read from unknown address {:#x}\n", component_name[comp], sizeof(T) * 8, addr);
return 0;
}
}
}
/* Instanciate the templates here so we can limit their types below */
template <typename T>
void ComponentManager::write(uint32_t addr, T data, Component comp)
{
uint32_t vaddr = addr & KUSEG_MASKS[addr >> 29];
/* Check if it's any memory ranges we care */
if (ee::RAM.contains(vaddr))
*(T*)&ram[ee::RAM.offset(vaddr)] = data;
else if (iop::RAM.contains(vaddr))
*(T*)&iop_ram[iop::RAM.offset(vaddr)] = data;
else
{
switch (vaddr)
{
/* Record any console output */
case 0x1000f180:
{
console << (char)data;
console.flush();
break;
}
case 0x1000f430:
{
uint8_t SA = (data >> 16) & 0xFFF;
uint8_t SBC = (data >> 6) & 0xF;
if (SA == 0x21 && SBC == 0x1 && ((MCH_DRD >> 7) & 1) == 0)
rdram_sdevid = 0;
MCH_RICM = data & ~0x80000000;
break;
}
case 0x1000f440:
{
MCH_DRD = data;
break;
}
default:
fmt::print("[{}] {:d}bit write {:#x} to unknown address {:#x}\n", component_name[comp], sizeof(T) * 8, data, addr);
break;
};
}
}
\ No newline at end of file
......@@ -39,10 +39,24 @@ struct Range {
uint64_t length = 0;
};
/* Memory ranges. */
constexpr Range RAM = Range(0x00000000, 32 * 1024 * 1024LL);
constexpr Range IO_REGISTERS = Range(0x10000000, 64 * 1024LL);
constexpr Range VU_MEMORY = Range(0x11000000, 40 * 1024LL);
constexpr Range GS_PRIV_REGS = Range(0x12000000, 8 * 1024LL);
constexpr Range IOP_RAM = Range(0x1c000000, 2 * 1024 * 1024LL);
constexpr Range BIOS = Range(0x1fc00000, 4 * 1024 * 1024LL);
/* EE memory ranges */
namespace ee
{
constexpr Range RAM = Range(0x00000000, 32 * 1024 * 1024LL);
constexpr Range IO_REGISTERS = Range(0x10000000, 64 * 1024LL);
constexpr Range VU_MEMORY = Range(0x11000000, 40 * 1024LL);
constexpr Range GS_PRIV_REGS = Range(0x12000000, 8 * 1024LL);
constexpr Range IOP_RAM = Range(0x1c000000, 2 * 1024 * 1024LL);
constexpr Range BIOS = Range(0x1fc00000, 4 * 1024 * 1024LL);
};
/* IOP memory ranges */
namespace iop
{
constexpr Range RAM = Range(0x1c000000, 2 * 1024 * 1024LL);
constexpr Range KSEG = Range(0x00000000, 2048 * 1024 * 1024LL);
constexpr Range KSEG0 = Range(0x80000000, 512 * 1024 * 1024LL);
constexpr Range KSEG1 = Range(0xA0000000, 512 * 1024 * 1024LL);
constexpr Range KSEG2 = Range(0xC0000000, 1024 * 1024 * 1024LL);
constexpr Range INTERRUPT = Range(0x1f801070, 8);
};
#pragma once
#include <cstdint>
/* All COP0 instructions have this */
constexpr uint8_t COP0_OPCODE = 0b010000;
union COP0Instr {
uint32_t value;
struct {
uint32_t : 21; /* This differs too much between instructions, requires manual parsing */
uint32_t type : 5;
uint32_t opcode : 6;
};
COP0Instr& operator=(uint32_t val)
{
value = val;
return *this;
}
};
/* Instruction types */
constexpr uint8_t COP0_MF0 = 0b00000;
constexpr uint8_t COP0_MT0 = 0b00100;
constexpr uint8_t COP0_C0 = 0b10000;
constexpr uint8_t COP0_TLB = 0b10000;
/* The status register fields */
union COP0Status {
uint32_t value;
struct {
uint32_t ie : 1; /* Interrupt Enable */
uint32_t exl : 1; /* Exception Level */
uint32_t erl : 1; /* Error Level */
uint32_t ksu : 2; /* Kernel/Supervisor/User Mode bits */
uint32_t : 5;
uint32_t im : 2; /* Int[1:0] signals */
uint32_t bem : 1; /* Bus Error Mask */
uint32_t : 2;
uint32_t im1 : 1; /* Internal timer interrupt */
uint32_t eie : 1; /* Enable IE */
uint32_t edi : 1; /* EI/DI instruction Enable */
uint32_t ch : 1; /* Cache Hit */
uint32_t : 3;
uint32_t bev : 1; /* Location of TLB refill */
uint32_t dev : 1; /* Location of Performance counter */
uint32_t : 2;
uint32_t fr : 1; /* Additional floating point registers */
uint32_t : 1;
uint32_t cu : 4; /* Usability of each of the four coprocessors */
};
};
union COP0Cause {
uint32_t value;
struct {
uint32_t : 2;
uint32_t exccode : 5;
uint32_t : 3;
uint32_t ip23 : 2;
uint32_t siop : 1;
uint32_t : 2;
uint32_t ip7 : 1;
uint32_t exc2 : 3;
uint32_t : 9;
uint32_t ce : 2;
uint32_t bd2 : 1;
uint32_t bd : 1;
};
};
enum OperatingMode {
USER_MODE = 0b10,
SUPERVISOR_MODE = 0b01,
KERNEL_MODE = 0b00
};
/* The COP0 registers */
union COP0 {
uint32_t regs[32] = {0};
struct {
uint32_t index;
uint32_t random;
uint32_t entry_lo0;
uint32_t entry_lo1;
uint32_t context;
uint32_t page_mask;
uint32_t wired;
uint32_t reserved0[1];
uint32_t bad_vaddr;
uint32_t count;
uint32_t entryhi;
uint32_t compare;
COP0Status status;
COP0Cause cause;
uint32_t epc;
uint32_t prid;
uint32_t config;
uint32_t reserved1[6];
uint32_t bad_paddr;
uint32_t debug;
uint32_t perf;
uint32_t reserved2[2];
uint32_t tag_lo;
uint32_t tag_hi;
uint32_t error_epc;
uint32_t reserved3[1];
};
OperatingMode get_operating_mode()
{
if (status.exl || status.erl) /* Setting one of these enforces kernel mode */
return OperatingMode::KERNEL_MODE;
else
return (OperatingMode)status.ksu;
}
};
\ No newline at end of file
This diff is collapsed.
#pragma once
#include <cpu/cop0.hpp>
#include <array>
/* Nice interface for instructions */
struct Instruction {
union {
uint32_t value;
struct { /* Used when polling for the opcode */
uint32_t : 26;
uint32_t opcode : 6;
};
struct {
uint32_t immediate : 16;
uint32_t rt : 5;
uint32_t rs : 5;
uint32_t opcode : 6;
} i_type;
struct {
uint32_t target : 26;
uint32_t opcode : 6;
} j_type;
struct {
uint32_t funct : 6;
uint32_t sa : 5;
uint32_t rd : 5;
uint32_t rt : 5;
uint32_t rs : 5;
uint32_t opcode : 6;
} r_type;
};
uint32_t pc;
bool is_delay_slot = false;
Instruction operator=(const Instruction& instr)
{
value = instr.value;
pc = instr.pc;
return *this;
}
};
union Register {
uint64_t dword[2] = {};
uint32_t word[4];
};
constexpr uint8_t SPECIAL_OPCODE = 0b000000;
constexpr const char* const REG[] =
{
"zero", "at", "v0", "v1",
"a0", "a1", "a2", "a3",
"t0", "t1", "t2", "t3",
"t4", "t5", "t6", "t7",
"s0", "s1", "s2", "s3",
"s4", "s5", "s6", "s7",
"t8", "t9", "k0", "k1",
"gp", "sp", "fp", "ra"
};
enum class ExceptionVector
{
V_TLB_REFILL = 0x0,
V_COMMON = 0x180,
V_INTERRUPT = 0x200
};
enum class Exception
{
Interrupt = 0,
TLBModified = 1,
TLBLoad = 2,
TLBStore = 3,
AddrErrorLoad = 4,
AddrErrorStore = 5,
Syscall = 8,
Break = 9,
Reserved = 10,
CopUnusable = 11,
Overflow = 12,
Trap = 13,
};
class ComponentManager;
/* A class implemeting the MIPS R5900 CPU. */
class EmotionEngine {
public:
EmotionEngine(ComponentManager* parent);
~EmotionEngine();
/* CPU functionality. */
void tick();
void reset_state();
void fetch_instruction();
void exception(Exception exception);
/* Memory operations */
template <typename T>
T read(uint32_t addr);
template <typename T>
void write(uint32_t addr, T data);
/* Opcodes */
void op_cop0(); void op_mfc0(); void op_sw();
void op_special(); void op_sll(); void op_slti();
void op_bne(); void op_ori(); void op_addi();
void op_lq(); void op_lui(); void op_jr(); void op_addiu();
void op_tlbwi(); void op_mtc0(); void op_lw(); void op_mmi();
void op_madd1(); void op_jalr(); void op_sd(); void op_jal();
void op_sra(); void op_regimm(); void op_bgez(); void op_addu();
void op_daddu(); void op_andi(); void op_beq(); void op_or();
void op_mult(); void op_divu(); void op_beql(); void op_mflo();
void op_sltiu(); void op_bnel(); void op_sync(); void op_lb();
void op_swc1(); void op_lbu(); void op_ld(); void op_j();
void op_sb(); void op_div(); void op_mfhi(); void op_sltu();
void op_blez(); void op_subu(); void op_bgtz(); void op_movn();
void op_slt(); void op_and(); void op_srl(); void op_dsll32();
void op_dsra32(); void op_dsll(); void op_lhu(); void op_bltz();
void op_sh(); void op_madd(); void op_divu1(); void op_mflo1();
void op_dsrav(); void op_xori(); void op_mult1(); void op_movz();
void op_dsllv(); void op_daddiu(); void op_sq();
protected:
ComponentManager* manager;
/* Registers. */
Register gpr[32];
uint32_t pc;
uint64_t hi0, hi1, lo0, lo1;
uint32_t sa;
Instruction instr, next_instr;
/* FPU (COP1) registers */
Register fpr[32];
uint64_t fcr0, fcr31;
bool skip_branch_delay = false;
/* Scratchpad */
uint8_t scratchpad[16 * 1024];
/* Coprocessors */
COP0 cop0;
std::FILE* disassembly;
};
\ No newline at end of file
#pragma once
#include <cstdint>
namespace ee
{
/* All COP0 instructions have this */
constexpr uint8_t COP0_OPCODE = 0b010000;
/* Instruction types */
constexpr uint8_t COP0_MF0 = 0b00000;
constexpr uint8_t COP0_MT0 = 0b00100;
constexpr uint8_t COP0_C0 = 0b10000;
constexpr uint8_t COP0_TLB = 0b10000;
/* The status register fields */
union COP0Status {
uint32_t value;
struct {
uint32_t ie : 1; /* Interrupt Enable */
uint32_t exl : 1; /* Exception Level */
uint32_t erl : 1; /* Error Level */
uint32_t ksu : 2; /* Kernel/Supervisor/User Mode bits */
uint32_t : 5;
uint32_t im : 2; /* Int[1:0] signals */
uint32_t bem : 1; /* Bus Error Mask */
uint32_t : 2;
uint32_t im1 : 1; /* Internal timer interrupt */
uint32_t eie : 1; /* Enable IE */
uint32_t edi : 1; /* EI/DI instruction Enable */
uint32_t ch : 1; /* Cache Hit */
uint32_t : 3;
uint32_t bev : 1; /* Location of TLB refill */
uint32_t dev : 1; /* Location of Performance counter */
uint32_t : 2;
uint32_t fr : 1; /* Additional floating point registers */
uint32_t : 1;
uint32_t cu : 4; /* Usability of each of the four coprocessors */
};
};
union COP0Cause {
uint32_t value;
struct {
uint32_t : 2;
uint32_t exccode : 5;
uint32_t : 3;
uint32_t ip23 : 2;
uint32_t siop : 1;
uint32_t : 2;
uint32_t ip7 : 1;
uint32_t exc2 : 3;
uint32_t : 9;
uint32_t ce : 2;
uint32_t bd2 : 1;
uint32_t bd : 1;
};
};
enum OperatingMode {
USER_MODE = 0b10,
SUPERVISOR_MODE = 0b01,
KERNEL_MODE = 0b00
};
/* The COP0 registers */
union COP0 {
uint32_t regs[32] = { 0 };
struct {
uint32_t index;
uint32_t random;
uint32_t entry_lo0;
uint32_t entry_lo1;
uint32_t context;
uint32_t page_mask;
uint32_t wired;
uint32_t reserved0[1];
uint32_t bad_vaddr;
uint32_t count;
uint32_t entryhi;
uint32_t compare;
COP0Status status;
COP0Cause cause;
uint32_t epc;
uint32_t prid;
uint32_t config;
uint32_t reserved1[6];
uint32_t bad_paddr;
uint32_t debug;
uint32_t perf;
uint32_t reserved2[2];
uint32_t tag_lo;
uint32_t tag_hi;
uint32_t error_epc;
uint32_t reserved3[1];
};
OperatingMode get_operating_mode()
{
if (status.exl || status.erl) /* Setting one of these enforces kernel mode */
return OperatingMode::KERNEL_MODE;
else
return (OperatingMode)status.ksu;
}
};
};
\ No newline at end of file
This diff is collapsed.
#pragma once
#include <common/manager.hpp>
#include <cpu/ee/cop0.hpp>
#include <array>
namespace ee
{
/* Nice interface for instructions */
struct Instruction {
union {
uint32_t value;
struct { /* Used when polling for the opcode */
uint32_t : 26;
uint32_t opcode : 6;
};
struct {
uint32_t immediate : 16;
uint32_t rt : 5;
uint32_t rs : 5;
uint32_t opcode : 6;
} i_type;
struct {
uint32_t target : 26;
uint32_t opcode : 6;
} j_type;
struct {
uint32_t funct : 6;
uint32_t sa : 5;
uint32_t rd : 5;
uint32_t rt : 5;
uint32_t rs : 5;
uint32_t opcode : 6;
} r_type;
};
uint32_t pc;
bool is_delay_slot = false;
Instruction operator=(const Instruction& instr)
{
value = instr.value;
pc = instr.pc;
is_delay_slot = instr.is_delay_slot;
return *this;
}
};
union Register {
uint64_t dword[2] = {};
uint32_t word[4];
};
constexpr uint8_t SPECIAL_OPCODE = 0b000000;
constexpr const char* const REG[] =
{
"zero", "at", "v0", "v1",
"a0", "a1", "a2", "a3",
"t0", "t1", "t2", "t3",
"t4", "t5", "t6", "t7",
"s0", "s1", "s2", "s3",
"s4", "s5", "s6", "s7",
"t8", "t9", "k0", "k1",
"gp", "sp", "fp", "ra"
};
enum class ExceptionVector
{
V_TLB_REFILL = 0x0,
V_COMMON = 0x180,
V_INTERRUPT = 0x200
};
enum class Exception
{
Interrupt = 0,
TLBModified = 1,
TLBLoad = 2,
TLBStore = 3,
AddrErrorLoad = 4,
AddrErrorStore = 5,
Syscall = 8,
Break = 9,
Reserved = 10,
CopUnusable = 11,
Overflow = 12,
Trap = 13,
};
/* A class implemeting the MIPS R5900 CPU. */
class EmotionEngine {
public:
EmotionEngine(ComponentManager* parent);
~EmotionEngine();
/* CPU functionality. */
void tick();
void reset();
void fetch();
void exception(Exception exception);
void direct_jump();
/* Memory operations */
template <typename T>
T read(uint32_t addr);
template <typename T>
void write(uint32_t addr, T data);
/* Opcodes */
void op_cop0(); void op_mfc0(); void op_sw();
void op_special(); void op_sll(); void op_slti();
void op_bne(); void op_ori(); void op_addi();
void op_lq(); void op_lui(); void op_jr(); void op_addiu();
void op_tlbwi(); void op_mtc0(); void op_lw(); void op_mmi();
void op_madd1(); void op_jalr(); void op_sd(); void op_jal();
void op_sra(); void op_regimm(); void op_bgez(); void op_addu();
void op_daddu(); void op_andi(); void op_beq(); void op_or();
void op_mult(); void op_divu(); void op_beql(); void op_mflo();
void op_sltiu(); void op_bnel(); void op_sync(); void op_lb();
void op_swc1(); void op_lbu(); void op_ld(); void op_j();
void op_sb(); void op_div(); void op_mfhi(); void op_sltu();
void op_blez(); void op_subu(); void op_bgtz(); void op_movn();
void op_slt(); void op_and(); void op_srl(); void op_dsll32();
void op_dsra32(); void op_dsll(); void op_lhu(); void op_bltz();
void op_sh(); void op_madd(); void op_divu1(); void op_mflo1();
void op_dsrav(); void op_xori(); void op_mult1(); void op_movz();
void op_dsllv(); void op_daddiu(); void op_sq();
protected:
ComponentManager* manager;
/* Registers. */
Register gpr[32];
uint32_t pc;
uint64_t hi0, hi1, lo0, lo1;
uint32_t sa;
Instruction instr, next_instr;
/* FPU (COP1) registers */
Register fpr[32];
uint64_t fcr0, fcr31;
bool skip_branch_delay = false;
/* Scratchpad */
uint8_t scratchpad[16 * 1024];
/* Coprocessors */
COP0 cop0;
std::FILE* disassembly;
};
template <typename T>
T EmotionEngine::read(uint32_t addr)
{
if (addr >= 0x70000000 && addr < 0x70004000) /* Read from scratchpad */
return *(T*)&scratchpad[addr & 0x3FFF];
else
return manager->read<T>(addr, Component::EE);
}
template <typename T>
void EmotionEngine::write(uint32_t addr, T data)
{
if (addr >= 0x70000000 && addr < 0x70004000)
*(T*)&scratchpad[addr & 0x3FFF] = data;
else
manager->write<T>(addr, data, Component::EE);
}
};
\ No newline at end of file
#pragma once
#include <cstdint>
namespace iop
{
/* All COP0 instructions have this */
constexpr uint8_t COP0_OPCODE = 0b010000;
/* Instruction types */
constexpr uint8_t COP0_MF0 = 0b00000;
constexpr uint8_t COP0_MT0 = 0b00100;
constexpr uint8_t COP0_C0 = 0b10000;
constexpr uint8_t COP0_TLB = 0b10000;
union COP0STAT {
uint32_t value;
struct {
uint32_t IEc : 1; /* Interrupt Enable (current) */
uint32_t KUc : 1; /* Kernel-User Mode (current) */
uint32_t IEp : 1; /* Interrupt Enable (previous) */
uint32_t KUp : 1; /* Kernel-User Mode (previous) */
uint32_t IEo : 1; /* Interrupt Enable (old) */
uint32_t KUo : 1; /* Kernel-User Mode (old) */
uint32_t reserved1 : 2;
uint32_t Sw : 2; /* Software Interrupt Mask */
uint32_t Intr : 6; /* Hardware Interrupt Mask */
uint32_t IsC : 1; /* Isolate Cache */
uint32_t reserved2 : 1;
uint32_t PZ : 1; /* Parity Zero */
uint32_t reserved3 : 1;
uint32_t PE : 1; /* Parity Error */
uint32_t TS : 1; /* TLB Shutdown */
uint32_t BEV : 1; /* Bootstrap Exception Vectors */
uint32_t reserved4 : 5;
uint32_t Cu : 4; /* Coprocessor Usability */
};
};
union COP0CAUSE {
uint32_t value;
struct {
uint32_t : 2;
uint32_t exc_code : 5; /* Exception Code */
uint32_t : 1;
uint32_t Sw : 2; /* Software Interrupts */
uint32_t IP : 6; /* Interrupt Pending */
uint32_t : 12;
uint32_t CE : 2; /* Coprocessor Error */
uint32_t BT : 1; /* Branch Taken */
uint32_t BD : 1; /* Branch Delay */
};
};
union COP0 {
uint32_t regs[64];
struct {
uint32_t r0;
uint32_t r1;
uint32_t r2;
uint32_t Bpc; /* Breakpoint Program Counter */
uint32_t r4;
uint32_t BDA; /* Breakpoint Data Address */
uint32_t TAR; /* Target Address */
uint32_t DCIC; /* Debug and Cache Invalidate Control */
uint32_t BadA; /* Bad Address */
uint32_t BDAM; /* Breakpoint Data Address Mask */
uint32_t r10;
uint32_t BpcM; /* Breakpoint Program Counter Mask */
COP0STAT sr; /* Status */
COP0CAUSE cause; /* Cause */
uint32_t epc; /* Exception Program Counter */
uint32_t PRId; /* Processor Revision Identifier */
uint32_t reserved[32];
};
};
};
\ No newline at end of file
This diff is collapsed.
#pragma once
#include <cpu/iop/cop0.hpp>
#include <common/manager.hpp>
namespace iop
{
/* Used for storing load-delay slots */
struct LoadInfo {
uint32_t reg = 0;
uint32_t value = 0;
};
/* Nice interface for instructions */
struct Instruction {
union {
uint32_t value;
struct { /* Used when polling for the opcode */
uint32_t : 26;
uint32_t opcode : 6;
};
struct {
uint32_t immediate : 16;
uint32_t rt : 5;
uint32_t rs : 5;
uint32_t opcode : 6;
} i_type;
struct {
uint32_t target : 26;
uint32_t opcode : 6;
} j_type;
struct {
uint32_t funct : 6;
uint32_t sa : 5;
uint32_t rd : 5;
uint32_t rt : 5;
uint32_t rs : 5;
uint32_t opcode : 6;
} r_type;
};
uint32_t pc;
bool is_delay_slot = false;
bool branch_taken = false;
Instruction operator=(const Instruction& instr)
{
value = instr.value;
pc = instr.pc;
is_delay_slot = instr.is_delay_slot;
branch_taken = instr.branch_taken;
return *this;
}
};
enum class Exception
{
Interrupt = 0x0,
ReadError = 0x4,
WriteError = 0x5,
BusError = 0x6,
Syscall = 0x8,
Break = 0x9,
IllegalInstr = 0xA,
CoprocessorError = 0xB,
Overflow = 0xC
};
/* A class implemeting the PS2 IOP, a MIPS R3000A CPU. */
class IOProcessor {
public:
IOProcessor(ComponentManager* manager);
~IOProcessor();
/* CPU functionality */
void tick();
void reset();
void fetch();
void branch();
void handle_load_delay();
/* Call this after setting the PC to skip delay slot */
void direct_jump();
void exception(Exception cause, uint32_t cop = 0);
void set_reg(uint32_t regN, uint32_t value);
void load(uint32_t regN, uint32_t value);
/* Bus communication. */
template <typename T>
T read(uint32_t addr);
template <typename T>
void write(uint32_t addr, T data);
/* Opcodes. */
void op_special(); void op_cop0();
/* Read/Write instructions. */
void op_lhu(); void op_lh(); void op_lbu(); void op_sw();
void op_swr(); void op_swl(); void op_lwr(); void op_lwl();
void op_lui(); void op_sb(); void op_lw(); void op_lb();
void op_sh();
/* Bitwise manipulation instructions. */
void op_and(); void op_andi(); void op_xor(); void op_xori();
void op_nor(); void op_or(); void op_ori();
/* Jump and branch instructions. */
void op_bcond(); void op_jalr(); void op_blez(); void op_bgtz();
void op_j(); void op_beq(); void op_jr(); void op_jal();
void op_bne();
/* Arithmetic instructions. */
void op_add(); void op_addi(); void op_addu(); void op_addiu();
void op_sub(); void op_subu(); void op_mult(); void op_multu();
void op_div(); void op_divu();
/* Bit shift-rotation instructions. */
void op_srl(); void op_srlv(); void op_sra(); void op_srav();
void op_sll(); void op_sllv(); void op_slt(); void op_slti();
void op_sltu(); void op_sltiu();
/* HI/LO instructions. */
void op_mthi(); void op_mtlo(); void op_mfhi(); void op_mflo();
/* Exception instructions. */
void op_break(); void op_syscall(); void op_rfe();
/* Coprocessor instructions. */
void op_mfc0(); void op_mtc0();
public:
ComponentManager* manager;
uint32_t pc;
uint32_t i_stat, i_mask;
uint32_t gpr[32];
uint32_t hi, lo;
uint32_t exception_addr[2] = { 0x80000080, 0xBFC00180 };
/* Coprocessors. */
COP0 cop0;
/* Pipeline stuff. */
LoadInfo write_back, memory_load, delayed_memory_load;
Instruction instr, next_instr;
FILE* disassembly;
};
template<typename T>
inline T IOProcessor::read(uint32_t addr)
{
return manager->read<T>(addr, Component::IOP);
}
template<typename T>
inline void IOProcessor::write(uint32_t addr, T data)
{
manager->write<T>(addr, data, Component::IOP);
}
};
\ No newline at end of file
......@@ -28,7 +28,7 @@ int main()
/* Move ComponentManger to its own thread for maximum performance. */
ComponentManager manager;
std::thread thread([&]() { while (true) { manager.tick(); } });
std::thread thread([&]() { while (!manager.stop_thread) { manager.tick(); } });
while (!glfwWindowShouldClose(window))
{
......@@ -42,6 +42,9 @@ int main()
glfwPollEvents();
}
manager.stop_thread = true;
thread.join();
glfwTerminate();
return 0;
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment