BizHawk/attic/PsxHawk.Core/psx.cpp

1240 lines
39 KiB
C++

/*
this file contains the core psx system and cpu emulation.
where readily possible, components have been split off into separate modules.
*/
//http://www.d.umn.edu/~gshute/spimsal/talref.html
#include "psx.h"
#include "dis.h"
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <intrin.h>
#include <Windows.h>
bool dotrace = false;
void PSX::reset()
{
cpu.p_fetch.in_fetch_addr = 0xbfc00000; //this is the reset vector
}
void PSX::poweron(eConsoleType type)
{
//the psx struct only contains blittable data, so we can just memset it here to make sure we've got complete coverage
//(we'll put any non-blittable stuff behind one pointer later so we can reconstruct it as necessary)
memset(this,0,sizeof(this));
//reset cp0 regs (should this be in reset?)
memset(&cpu.cp0,0,sizeof(cpu.cp0));
cpu.cp0.SR.BEV = 1; //taken from mednafen
cpu.cp0.SR.TS = 1; //taken from mednafen
cpu.cp0.PRid = 0x300; // PRId: FIXME(test on real thing) //taken from mednafen
//clear cpu state: pipeline setup
cpu.unit_muldiv.timer = 0;
//p_fetch will be taken care of by the bootstrapper
cpu.p_alu.out_pc.enabled = false;
cpu.p_alu.in_pc = 0;
cpu.p_alu.decode.instr.value = 0;
cpu.p_alu.decode.op = eOP_NULL;
cpu.p_mem.in_from_alu.op = CPU::eMemOp_None;
//clear sio
sio[0].Reset();
sio[1].Reset();
//reset the scheduling system
sched.null.time = 0xFFFFFFFF; //this item will never happen so it will float at the end as a sentinel (it will continually get rebased)
//actually, its already at the end due to the memset
sched.head = eScheduleItemType_null;
sched.gpu.time = PSX_CLOCK / 60;
sched.insert(eScheduleItemType_gpu);
//setup the system configuration
//programs built targeting DTL units will ask for memory near the end of 8MB at program boot-up time (maybe other times?). maybe it puts the heap there? not sure about it.
//anyway, we need to handle that much memory if we want those to work.
//be aware that right now, psxhawk will mirror 2MB across the 8MB region for retail consoles, until we get better memory mapping
if(type == eConsoleType_DTL) config.ram_size = 8*1024*1024;
else config.ram_size = 2*1024*1024;
config.ram_mask = config.ram_size - 1;
}
PSX::eScheduleItemType PSX::SCHED::dequeue()
{
eScheduleItemType ret = head;
head = items[head].next;
//todo - rmeove this for a trivial optimization (but not from debug builds)
items[ret].next = eScheduleItemType_NIL;
items[ret].prev = eScheduleItemType_NIL;
return ret;
}
void PSX::SCHED::remove(eScheduleItemType todoType)
{
IScheduleItem &todo = items[todoType];
eScheduleItemType prevType = todo.prev;
eScheduleItemType nextType = todo.next;
if(prevType != eScheduleItemType_NIL) items[prevType].next = nextType;
if(nextType != eScheduleItemType_NIL) items[nextType].prev = prevType;
if(head == todoType)
head = nextType;
}
void PSX::SCHED::insert(eScheduleItemType todoType)
{
IScheduleItem &todo = items[todoType];
//ensure that it isnt already in the list
assert(todo.prev == eScheduleItemType_NIL && todo.next == eScheduleItemType_NIL);
//insert this schedule item into the list in sorted order
u32 todo_time = todo.time;
eScheduleItemType prevType = eScheduleItemType_NIL;
eScheduleItemType currType = head;
while(items[currType].time < todo_time) //TODO - tiebreaker priorities?
{
prevType = currType;
currType = items[currType].next;
}
//now, currType and prevType are pointing correctly.
if(prevType == eScheduleItemType_NIL)
{
//handle the case where this is the new head
items[todoType].next = head;
items[head].prev = todoType;
head = todoType;
}
else
{
//handle the case where it isnt the new head
items[todoType].next = items[prevType].next;
items[prevType].next = todoType;
items[todoType].prev = prevType;
items[currType].prev = todoType;
}
}
void PSX::exec_shed(eScheduleItemType type)
{
switch(type)
{
case eScheduleItemType_null:
break;
case eScheduleItemType_test:
printf("TEST!!!\n");
sched.test.time += 100000;
sched.insert(eScheduleItemType_test);
break;
case eScheduleItemType_gpu:
sched.gpu.time += PSX_CLOCK / 60;
sched.insert(eScheduleItemType_gpu);
vblank_trigger();
break;
}
}
void PSX::exec_cycle()
{
while(counter >= sched.items[sched.head].time)
{
eScheduleItemType todo = sched.dequeue();
exec_shed(todo);
}
sched.nextTime = sched.items[sched.head].time;
if(irq.flags.value & irq.mask.value)
{
dotrace = true;
cpu_exception(CPU::eException_INT, cpu.p_alu.in_pc);
//nullify instructions which will be rerun.
//in this emulator, interrupts cancel the instruction in the alu (notice we set it as the victim in the exception logic above)
//instructions in MEM and WB will proceed normally
//this was chosen because we didnt want to have to drag the PC down the pipeline and give it to the MEM stage
//HOWEVER -- this is maybe unrealistic (maybe we should nullify MEM and ALU and make MEM the victim)
//and maybe we'll have to drag the PC down later anyway for other proper exception handling
//(see pg 94 of see mips run)
cpu.p_alu.decode.op = eOP_NULLIFIED;
}
while(counter < sched.nextTime)
cpu_exec_cycle();
}
void PSX::vblank_trigger()
{
irq.flags.vsync = 1;
irq_update();
}
void PSX::cpu_wr_quick(u8* const buf, const int size, const u32 addr, const u32 val)
{
if(size==1) buf[addr] = val;
else if(size==2) {
*(u16*)&buf[addr] = val;
}
else {
*(u32*)&buf[addr] = val;
}
}
u32 PSX::cpu_rd_quick(const u8* const buf, const int size, const u32 addr)
{
if(size==1) return buf[addr];
else if(size==2) {
return *(u16*)&buf[addr];
}
else {
return *(u32*)&buf[addr];
}
}
u32 PSX::cpu_rd_ram(const int size, const u32 addr)
{
if(size==1)
return ram[addr];
else if(size==2) {
return *(u16*)&ram[addr];
}
else {
return *(u32*)&ram[addr];
}
}
void PSX::cpu_wr_ram(const int size, const u32 addr, const u32 val)
{
if(size==1)
ram[addr] = val;
else if(size==2) {
*(u16*)&ram[addr] = val;
}
else {
*(u32*)&ram[addr] = val;
}
}
void PSX::cpu_wr_scratch(const int size, const u32 addr, const u32 val) { cpu_wr_quick(scratch,size,addr,val); }
u32 PSX::cpu_rd_scratch(const int size, const u32 addr) { return cpu_rd_quick(scratch,size,addr); }
void PSX::cpu_wr_bios(const int size, const u32 addr, const u32 val) { cpu_wr_quick(bios,size,addr,val); }
u32 PSX::cpu_rd_bios(const int size, const u32 addr) { return cpu_rd_quick(bios,size,addr); }
void PSX::cpu_wr_pio(const int size, const u32 addr, const u32 val) { cpu_wr_quick(pio,size,addr,val); }
u32 PSX::cpu_rd_pio(const int size, const u32 addr) { return cpu_rd_quick(pio,size,addr); }
template<int size> void PSX::cpu_wrmem(u32 addr, const u32 val) { cpu_wrmem<size,false>(addr,val); }
template<int size, bool POKE> void PSX::cpu_wrmem(u32 addr, const u32 val)
{
if(!POKE && cpu.cp0.SR.IsC)
return; //IsC (isolate [data] cache is set). theres no data cache here, so discard write
if(addr<0x00800000) { cpu_wr_ram(size,addr&config.ram_mask,val); return; }
if(addr<0x1F000000) goto BUS_ERROR;
if(addr<0x1F010000) { cpu_wr_pio(size,addr&PIO_MASK,val); return; }
if(addr<0x1F800000) goto BUS_ERROR;
if(addr<0x1F800400) { cpu_wr_scratch(size,addr&SCRATCH_MASK,val); return; }
if(addr<0x1F801000) goto BUS_ERROR;
if(addr<0x1FC00000) { cpu_wr_hwreg(size,addr,val); return; }
if(addr<0x1FC80000) goto BUS_ERROR; //can't write to bios?
if(addr<0x80000000) goto BUS_ERROR;
if(addr<0x80800000) { cpu_wr_ram(size,addr&config.ram_mask,val); return; }
if(addr<0x9F000000) goto BUS_ERROR;
if(addr<0x9F010000) { cpu_wr_pio(size,addr&PIO_MASK,val); return; }
if(addr<0x9FC00000) goto BUS_ERROR;
if(addr<0x9FC80000) goto BUS_ERROR; //can't write to bios?
if(addr<0xA0000000) goto BUS_ERROR;
if(addr<0xA0800000) { cpu_wr_ram(size,addr&config.ram_mask,val); return; }
if(addr<0xBF000000) goto BUS_ERROR;
if(addr<0xBF010000) { cpu_wr_pio(size,addr&PIO_MASK,val); return; }
if(addr<0xBFC00000) goto BUS_ERROR;
if(addr<0xBFC80000) { if(POKE) { cpu_wr_bios(size,addr&BIOS_MASK,val); return; } else goto BUS_ERROR; } //can't write to bios?
if(addr == 0xFFFE0130)
{
//check psx sources for information on this. it seems to make everything read only?
//DEBUG("mystery 0xFFFE0130 reg set to 0x%08X\n",val);
return;
}
goto BUS_ERROR;
BUS_ERROR:
DEBUG("bus error exception (write) at 0x%08X\n",addr);
}
void PSX::patch(const u32 addr, const u32 val)
{
//should use the poke versions one day
cpu_wrmem<4,true>(addr,val);
}
void PSX::spu_wr(const int size, const u32 addr, const u32 val)
{
//1d80/1d82 main volume left / right
//1d84/1d86 reverberation depth left / right
DEBUG_HWREG("spu write size %d addr %08X = %08X\n",size,addr,val);
}
u32 PSX::spu_rd(const int size, const u32 addr)
{
u32 ret = 0;
DEBUG_HWREG("spu read size %d addr %08X = %08X\n",size,addr,ret);
return 0;
}
void PSX::irq_wr(const int size, const u32 addr, const u32 val)
{
if(addr == 0)
{
//acknowledge the specified irq bits
irq.flags.value &= ~(val&IRQ::WIRE_MASK);
}
else if(addr == 4)
{
//set the irq mask directly
irq.mask.value = val & IRQ::WIRE_MASK;
}
else assert(false);
irq_update();
}
u32 PSX::irq_rd(const int size, const u32 addr)
{
if(addr == 0)
return irq.flags.value;
else if(addr == 4)
return irq.mask.value;
else assert(false);
}
void PSX::irq_update()
{
sched.escape();
}
void PSX::cpu_wr_hwreg(const int size, const u32 addr, const u32 val)
{
if(addr>=0x1F801C00 && addr <= 0x1F801DFF)
{
spu_wr(size,addr,val);
return;
}
else if(addr>=0x1F801040 && addr < 0x1F80105F)
{
sio_wr(size,addr,val);
return;
}
else switch(addr)
{
case 0x1F801000: sysregs.biosInit[0] = val; break;
case 0x1F801004: sysregs.biosInit[1] = val; break;
case 0x1F801008: sysregs.biosInit[2] = val; break;
case 0x1F80100C: sysregs.biosInit[3] = val; break;
case 0x1F801010: sysregs.biosInit[4] = val; break;
case 0x1F801014: sysregs.biosInit[5] = val; break;
case 0x1F801018: sysregs.biosInit[6] = val; break;
case 0x1F80101C: sysregs.biosInit[7] = val; break;
case 0x1F801020: sysregs.biosInit[8] = val; break;
//case 0x1f801080-0x1f8010ff dma
//case 0x1F801100/10/20/30 root counters
case 0x1F801070: irq_wr(size, 0, val); break;
case 0x1F801071: assert(false);
case 0x1F801072: assert(false);
case 0x1F801073: assert(false);
case 0x1F801074: irq_wr(size, 4, val); break;
case 0x1F801075: assert(false);
case 0x1F801076: assert(false);
case 0x1F801077: assert(false);
//1F801060 // unknown
//1F801D80 // unknown
//1f802041 // unknown
default:
DEBUG_HWREG("UNKNOWN ");
}
DEBUG_HWREG("hwreg write size %d addr %08X = %08X\n",size,addr,val);
}
u32 PSX::cpu_rd_hwreg(const int size, const u32 addr)
{
u32 ret = 0;
if(addr>=0x1F801C00 && addr <= 0x1F801DFF)
{
return spu_rd(size,addr);
}
else if(addr>=0x1F801040 && addr < 0x1F80105F)
{
return sio_rd(size,addr);
}
else switch(addr)
{
case 0x1F801000: ret = sysregs.biosInit[0]; break;
case 0x1F801004: ret = sysregs.biosInit[1]; break;
case 0x1F801008: ret = sysregs.biosInit[2]; break;
case 0x1F80100C: ret = sysregs.biosInit[3]; break;
case 0x1F801010: ret = sysregs.biosInit[4]; break;
case 0x1F801014: ret = sysregs.biosInit[5]; break;
case 0x1F801018: ret = sysregs.biosInit[6]; break;
case 0x1F80101C: ret = sysregs.biosInit[7]; break;
case 0x1F801020: ret = sysregs.biosInit[8]; break;
case 0x1F801070: ret = irq_rd(size, 0); break;
case 0x1F801071: assert(false);
case 0x1F801072: assert(false);
case 0x1F801073: assert(false);
case 0x1F801074: ret = irq_rd(size, 4); break;
case 0x1F801075: assert(false);
case 0x1F801076: assert(false);
case 0x1F801077: assert(false);
default:
DEBUG_HWREG("UNKNOWN ");
break;
}
DEBUG_HWREG("hwreg read size %d addr %08X = %08X\n",size,addr,ret);
return ret;
}
template<int size> u32 PSX::cpu_rdmem(const u32 addr)
{
if(addr<0x00800000) return cpu_rd_ram(size,addr&config.ram_mask);
if(addr<0x1F000000) goto BUS_ERROR;
if(addr<0x1F010000) return cpu_rd_pio(size,addr&PIO_MASK);
if(addr<0x1F800000) goto BUS_ERROR;
if(addr<0x1F800400) return cpu_rd_scratch(size,addr&SCRATCH_MASK);
if(addr<0x1F801000) goto BUS_ERROR;
if(addr<0x1FC00000) return cpu_rd_hwreg(size,addr);
if(addr<0x1FC80000) return cpu_rd_bios(size,addr&BIOS_MASK);
if(addr<0x80000000) goto BUS_ERROR;
if(addr<0x80800000) return cpu_rd_ram(size,addr&config.ram_mask);
if(addr<0x9F000000) goto BUS_ERROR;
if(addr<0x9F010000) return cpu_rd_pio(size,addr&PIO_MASK);
if(addr<0x9FC00000) goto BUS_ERROR;
if(addr<0x9FC80000) return cpu_rd_bios(size,addr&BIOS_MASK);
if(addr<0xA0000000) goto BUS_ERROR;
if(addr<0xA0800000) return cpu_rd_ram(size,addr&config.ram_mask);
if(addr<0xBF000000) goto BUS_ERROR;
if(addr<0xBF010000) return cpu_rd_pio(size,addr&PIO_MASK);
if(addr<0xBFC00000) goto BUS_ERROR;
if(addr<0xBFC80000) return cpu_rd_bios(size,addr&BIOS_MASK);
goto BUS_ERROR;
BUS_ERROR:
DEBUG("bus error exception (read) at 0x%08X\n",addr);
return 0;
}
void PSX::cpu_copz_mtc(const u32 z, const u32 rd, const u32 value)
{
//DEBUG("Writing cop%d, %d = 0x%08X\n",z,rd,value);
if(z==0) cpu_cop0_mtc(rd,value);
}
void PSX::cpu_cop0_mtc(const u32 rd, const u32 value)
{
cpu.cp0.r[rd] = value;
cpu.cp0.SR.value &= ~CPU::SR_REG::ZERO_MASK;
//TODO - other register masks
}
u32 PSX::cpu_copz_mfc(const u32 z, const u32 rd)
{
//DEBUG("Reading cop%d, %d\n",z,rd);
if(z==0) return cpu_cop0_mfc(rd);
return 0;
}
u32 PSX::cpu_cop0_mfc(const u32 rd)
{
return cpu.cp0.r[rd];
}
u32 PSX::cpu_fetch(const u32 addr)
{
return cpu_rdmem<4>(addr);
}
void PSX::cpu_run_alu_bioshack()
{
const u32 ram_pc = cpu.p_alu.in_pc & 0x0FFFFFFF;
const u32 function = cpu.regs.t1;
switch(function)
{
case 0x00: break; //open
case 0x16: break; //strncat
case 0x18: break; //strncmp
case 0x35: //lsearch?? but yet the sdk uses this for printf. maybe it depends on the bios version
//also the putchar() stdlib function uses this by dropping a character in some kind of buffer that is reserved for it
if(ENABLE_CONOUT)
{
//TODO - make this safer, if necessary, by not going straight into main ram
//a1 = address of string
//a2 = length of string
u8* const raw_addr = ram + (cpu.regs.a1 & config.ram_mask);
const u32 len = cpu.regs.a2;
for(u32 i=0;i<len;i++)
fputc(raw_addr[i],stdout);
}
break;
case 0x3C: //putchar
if(ENABLE_CONOUT) cpu_run_alu_bioshack_putchar(cpu.regs.a0);
break;
case 0x3D: //gets... ? but yet, the kernel uses this to print
if(ENABLE_CONOUT) cpu_run_alu_bioshack_putchar(cpu.regs.a0);
case 0x3F: //printf. i think putchar will get used internally, no need to handle this
break;
default:
printf("unknown bios call: 0x%02X\n", function);
break;
}
return;
}
void PSX::cpu_run_alu_bioshack_putchar(const u32 regval)
{
char c = regval;
fputc(c,stdout);
return;
}
void PSX::cpu_break(const u32 code)
{
switch(code)
{
case eFakeBreakOp_BootEXE:
{
//perform basic exe booting steps
//the program has already been loaded. all we need to do is setup the regs specified in the header
cpu.p_fetch.in_fetch_addr = exeBootHeader.init_pc;
cpu.regs.gp = exeBootHeader.init_gp;
cpu.regs.sp = exeBootHeader.stack_load_addr; //aka init_sp
break;
}
case eFakeBreakOp_BiosHack:
//first, run the effect of the patched instruction, which was: lui t0, 0x0000
cpu.regs.t0 = 0;
cpu_run_alu_bioshack();
break;
default:
break;
}
}
#define SIGNBIT(x) ((x) & 0x80000000)
void PSX::cpu_exception(CPU::eException ex, u32 pc_victim)
{
cpu.cp0.EPC = pc_victim;
cpu.cp0.Cause.ExcCode = ex;
cpu.cp0.Cause.Sw = 0; //?
cpu.cp0.Cause.IP = 0; //?
cpu.cp0.Cause.CE = 0; //TBD
cpu.cp0.Cause.BD = 0; //TODO - BD flag
//choose the rom or ram handler according to this flag
u32 handler = 0x80000080;
if(cpu.cp0.SR.BEV)
handler = 0xBFC00180;
cpu.cp0.SR.KUo = cpu.cp0.SR.KUp;
cpu.cp0.SR.IEo = cpu.cp0.SR.IEp;
cpu.cp0.SR.KUp = cpu.cp0.SR.KUc;
cpu.cp0.SR.IEp = cpu.cp0.SR.IEc;
cpu.cp0.SR.KUc = 0;
cpu.cp0.SR.IEc = 0;
cpu.p_fetch.in_fetch_addr = handler;
}
void PSX::cpu_run_muldiv()
{
if(cpu.unit_muldiv.timer > 0)
{
//TODO - think about whether this is the exactly right amount of time (maybe off by one)
cpu.unit_muldiv.timer--;
if(cpu.unit_muldiv.timer == 0)
{
cpu.regs.lo = cpu.unit_muldiv.lo;
cpu.regs.hi = cpu.unit_muldiv.hi;
cpu.stall_depends &= ~CPU::eStall_MulDiv;
}
}
}
void PSX::cpu_run_mem()
{
CPU::ALU_OUTPUT &input = cpu.p_mem.in_from_alu;
//mem stage
switch(input.op)
{
case CPU::eMemOp_StoreWord:
DEBUG_STORE("{%12lld} MEM: StoreWord [%08X] = %08X\n",abscounter,input.addr,input.value);
cpu_wrmem<4>(input.addr,input.value);
break;
case CPU::eMemOp_StoreHalfword:
DEBUG_STORE("{%12lld} MEM: StoreWord [%08X] = %04X\n",abscounter,input.addr,input.value);
cpu_wrmem<2>(input.addr,input.value);
break;
case CPU::eMemOp_StoreByte:
DEBUG_STORE("{%12lld} MEM: StoreByte [%08X] = %02X\n",abscounter,input.addr,input.value);
cpu_wrmem<1>(input.addr,input.value);
break;
case CPU::eMemOp_LoadWord:
{
const u32 temp = cpu_rdmem<4>(input.addr);
DEBUG_LOAD("{%12lld} MEM: LoadWord [%08X] == %08X\n",abscounter,input.addr,temp);
cpu.regs.r[input.rt] = temp;
break;
}
case CPU::eMemOp_LoadHalfwordSigned:
{
const u32 temp = (s16)cpu_rdmem<2>(input.addr);
DEBUG_LOAD("{%12lld} MEM: LoadHalfwordSigned [%08X] == %04X\n",abscounter,input.addr,temp);
cpu.regs.r[input.rt] = temp;
break;
}
case CPU::eMemOp_LoadHalfwordUnsigned:
{
const u32 temp = cpu_rdmem<2>(input.addr);
DEBUG_LOAD("{%12lld} MEM: LoadHalfwordUnsigned [%08X] == %04X\n",abscounter,input.addr,temp);
cpu.regs.r[input.rt] = temp;
break;
}
case CPU::eMemOp_LoadByteSigned:
{
const u32 temp = (s8)cpu_rdmem<1>(input.addr);
DEBUG_LOAD("{%12lld} MEM: LoadByte [%08X] == %02X\n",abscounter,input.addr,temp);
cpu.regs.r[input.rt] = temp;
break;
}
case CPU::eMemOp_LoadByteUnsigned:
{
const u32 temp = cpu_rdmem<1>(input.addr);
DEBUG_LOAD("{%12lld} MEM: LoadByteUnsigned [%08X] == %02X\n",abscounter,input.addr,temp);
cpu.regs.r[input.rt] = temp;
break;
}
case CPU::eMemOp_MTC:
{
const CPU::Instruction_CTYPE instr = cpu.p_mem.decode.instr.CTYPE;
cpu_copz_mtc(instr.cpnum,instr.rd,input.value);
break;
}
case CPU::eMemOp_MFC:
{
const CPU::Instruction_CTYPE instr = cpu.p_mem.decode.instr.CTYPE;
cpu.regs.r[instr.rt] = cpu_copz_mfc(instr.cpnum,instr.rd);
break;
}
case CPU::eMemOp_None:
break;
case CPU::eMemOp_Unset:
printf("*** UNHANDLED MEM OP ***\n");
break;
default:
NOCASE();
}
cpu.regs.r0 = 0;
}
void PSX::TraceALU()
{
DEBUG_TRACE("{%12lld} %08X: [%08X] (%d,%d) %s\n",abscounter,cpu.p_alu.in_pc,cpu.p_alu.decode.instr.value,
cpu.p_alu.decode.instr.value>>26,
cpu.p_alu.decode.instr.value&0x3F,
MDFN_IEN_PSX::DisassembleMIPS(cpu.p_alu.in_pc,cpu.p_alu.decode.instr.value).c_str()
);
}
void PSX::cpu_run_wb()
{
//TBD
}
static const eOp DecodeTable[] =
{
eOP_SLL, eOP_ILL, eOP_SRL, eOP_SRA, eOP_SLLV, eOP_ILL, eOP_SRLV, eOP_SRAV,
eOP_JR, eOP_JALR, eOP_ILL, eOP_ILL, eOP_SYSCALL, eOP_BREAK, eOP_ILL, eOP_ILL,
eOP_MFHI, eOP_MTHI, eOP_MFLO, eOP_MTLO, eOP_ILL, eOP_ILL, eOP_ILL, eOP_ILL,
eOP_MULT, eOP_MULTU, eOP_DIV, eOP_DIVU, eOP_ILL, eOP_ILL, eOP_ILL, eOP_ILL,
eOP_ADD, eOP_ADDU, eOP_SUB, eOP_SUBU, eOP_AND, eOP_OR, eOP_XOR, eOP_NOR,
eOP_ILL, eOP_ILL, eOP_SLT, eOP_SLTU, eOP_ILL, eOP_ILL, eOP_ILL, eOP_ILL,
eOP_ILL, eOP_ILL, eOP_ILL, eOP_ILL, eOP_ILL, eOP_ILL, eOP_ILL, eOP_ILL,
eOP_ILL, eOP_ILL, eOP_ILL, eOP_ILL, eOP_ILL, eOP_ILL, eOP_ILL, eOP_ILL,
eOP_NULL, eOP_BCOND, eOP_J, eOP_JAL, eOP_BEQ, eOP_BNE, eOP_BLEZ, eOP_BGTZ,
eOP_ADDI, eOP_ADDIU, eOP_SLTI, eOP_SLTIU, eOP_ANDI, eOP_ORI, eOP_XORI, eOP_LUI,
eOP_COPROC, eOP_COPROC, eOP_COPROC, eOP_COPROC, eOP_ILL, eOP_ILL, eOP_ILL, eOP_ILL,
eOP_ILL, eOP_ILL, eOP_ILL, eOP_ILL, eOP_ILL, eOP_ILL, eOP_ILL, eOP_ILL,
eOP_LB, eOP_LH, eOP_LWL, eOP_LW, eOP_LBU, eOP_LHU, eOP_LWR, eOP_ILL,
eOP_SB, eOP_SH, eOP_SWL, eOP_SW, eOP_ILL, eOP_ILL, eOP_SWR, eOP_ILL,
eOP_LWC0, eOP_LWC1, eOP_LWC2, eOP_LWC3, eOP_ILL, eOP_ILL, eOP_ILL, eOP_ILL,
eOP_SWC0, eOP_SWC1, eOP_SWC2, eOP_SWC3, eOP_ILL, eOP_ILL, eOP_ILL, eOP_ILL,
};
void PSX::cpu_run_fetch()
{
const u32 _pc = cpu.p_fetch.in_fetch_addr;
const u32 instr = cpu_fetch(cpu.p_fetch.in_fetch_addr);
const u32 opc = instr>>26;
cpu.p_fetch.decode.instr.value = instr;
const u32 _opc = instr>>26;
const u32 _func = instr&0x3F;
if(dotrace) DEBUG_TRACE("{%12lld} %08X: [%08X] (%d,%d) %s\n",abscounter, _pc, instr,_opc,_func,MDFN_IEN_PSX::DisassembleMIPS(_pc,instr).c_str());
//this decode table approach was taken from mednafen.
u32 opf = instr & 0x3F;
if(instr & (0x3F << 26))
opf = 0x40 | (instr >> 26);
cpu.p_fetch.decode.op = DecodeTable[opf];
//dont try to pad out these switches, it wont help
switch(cpu.p_fetch.decode.op)
{
case eOP_MFLO:
case eOP_MFHI:
cpu.stall_user |= CPU::eStall_MulDiv;
break;
}
}
#define BRANCH_SET_PC() { cpu.p_alu.out_pc.enabled = true, cpu.p_alu.out_pc.pc = cpu.p_alu.in_pc + instr.signed_target(); }
void PSX::cpu_run_alu()
{
//most alu instructions will not set the out PC
cpu.p_alu.out_pc.enabled = false;
//alu needs to have registers ready before it begins.
//some of these will write their results at the end of this stage
#ifndef NDEBUG
cpu.p_alu.out_mem.op = CPU::eMemOp_Unset;
#endif
cpu.p_alu.exception = CPU::eException_None;
switch(cpu.p_alu.decode.op)
{
case eOP_ILL:
default:
//NOCASE();
printf("*** UNHANDLED ALU OP***\n");
TraceALU();
break;
case eOP_NULLIFIED:
case eOP_NULL:
{
//?? whats this?
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_J: //j (jump)
{
const CPU::Instruction_JTYPE instr = cpu.p_alu.decode.instr.JTYPE;
cpu.p_alu.out_pc.enabled = true;
cpu.p_alu.out_pc.pc = (cpu.p_alu.in_pc&0xF0000000) | (instr.target << 2);
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_JAL: //jal (jump and link)
{
const CPU::Instruction_JTYPE instr = cpu.p_alu.decode.instr.JTYPE;
cpu.p_alu.out_pc.enabled = true;
cpu.p_alu.out_pc.pc = (cpu.p_alu.in_pc&0xF0000000) | (instr.target << 2);
cpu.regs.ra = cpu.p_alu.in_pc + 8;
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_COPROC: //coproc operations
{
const CPU::Instruction_CTYPE instr = cpu.p_alu.decode.instr.CTYPE;
switch(instr.format)
{
case 0: //mfcz (move from coprocessor z)
//TODO - should these have a delay? the psx is supposed to hav a delay for cp1, does that mean there is a delay for cp0?
cpu.p_alu.out_mem.op = CPU::eMemOp_MFC;
break;
case 4: //mtcz (move to coprocessor z)
//TODO - should these have a delay? the psx is supposed to hav a delay for cp1, does that mean there is a delay for cp0?
cpu.p_alu.out_mem.value = cpu.regs.r[instr.rt];
cpu.p_alu.out_mem.op = CPU::eMemOp_MTC;
break;
case 16: //a whole bunch of junk, maybe associated with cp0?
switch(instr.function)
{
case 16: //rfe (return from exception)
cpu.cp0.SR.IEc = cpu.cp0.SR.IEp;
cpu.cp0.SR.KUc = cpu.cp0.SR.KUp;
cpu.cp0.SR.IEp = cpu.cp0.SR.IEo;
cpu.cp0.SR.KUp = cpu.cp0.SR.KUo;
//KUo and IEo unchanged
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
default:
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
break;
default:
NOCASE();
printf("*** UNHANDLED ALU CTYPE ***\n");
TraceALU();
}
break;
}
case eOP_SLL: //sll (shift left logical) //nop
{
const CPU::Instruction_RTYPE instr = cpu.p_alu.decode.instr.RTYPE;
cpu.regs.r[instr.rd] = cpu.regs.r[instr.rt] << instr.sa;
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_SRL: //srl (shift right logical)
{
const CPU::Instruction_RTYPE instr = cpu.p_alu.decode.instr.RTYPE;
cpu.regs.r[instr.rd] = cpu.regs.r[instr.rt] >> instr.sa;
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_SRA: //sra (shift right arithmetic)
{
const CPU::Instruction_RTYPE instr = cpu.p_alu.decode.instr.RTYPE;
cpu.regs.r[instr.rd] = (s32)cpu.regs.r[instr.rt] >> instr.sa;
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_SLLV: //sllv (shift left logical variable)
{
const CPU::Instruction_RTYPE instr = cpu.p_alu.decode.instr.RTYPE;
cpu.regs.r[instr.rd] = cpu.regs.r[instr.rt] << (cpu.regs.r[instr.rs] & 0x1F);
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_SRLV: //srlv (shift right logical variable)
{
const CPU::Instruction_RTYPE instr = cpu.p_alu.decode.instr.RTYPE;
cpu.regs.r[instr.rd] = cpu.regs.r[instr.rt] >> (cpu.regs.r[instr.rs] & 0x1F);
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_SRAV: //srav (shift right arithmetic variable)
{
const CPU::Instruction_RTYPE instr = cpu.p_alu.decode.instr.RTYPE;
cpu.regs.r[instr.rd] = (s32)cpu.regs.r[instr.rt] >> (cpu.regs.r[instr.rs] & 0x1F);
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_JR: //jr (jump register)
{
const CPU::Instruction_RTYPE instr = cpu.p_alu.decode.instr.RTYPE;
cpu.p_alu.out_pc.enabled = true;
cpu.p_alu.out_pc.pc = cpu.regs.r[instr.rs];
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_JALR: //jalr (jump and link register)
{
const CPU::Instruction_RTYPE instr = cpu.p_alu.decode.instr.RTYPE;
cpu.p_alu.out_pc.enabled = true;
cpu.p_alu.out_pc.pc = cpu.regs.r[instr.rs];
cpu.regs.ra = cpu.p_alu.in_pc + 8;
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_SYSCALL: //syscall
{
const CPU::Instruction_RTYPE instr = cpu.p_alu.decode.instr.RTYPE;
cpu.p_alu.exception = CPU::eException_SYSCALL;
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_BREAK: //break
{
const CPU::Instruction_RTYPE instr = cpu.p_alu.decode.instr.RTYPE;
cpu_break(instr.break_code());
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_MFHI: //mfhi (move from hi)
{
const CPU::Instruction_RTYPE instr = cpu.p_alu.decode.instr.RTYPE;
cpu.regs.r[instr.rd] = cpu.regs.hi;
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_MTHI: //mthi (move to hi)
{
const CPU::Instruction_RTYPE instr = cpu.p_alu.decode.instr.RTYPE;
cpu.regs.hi = cpu.regs.r[instr.rs];
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_MFLO: //mflo (move from lo)
{
const CPU::Instruction_RTYPE instr = cpu.p_alu.decode.instr.RTYPE;
cpu.regs.r[instr.rd] = cpu.regs.lo;
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_MTLO: //mtlo (move to lo)
{
const CPU::Instruction_RTYPE instr = cpu.p_alu.decode.instr.RTYPE;
cpu.regs.lo = cpu.regs.r[instr.rs];
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_MULT: //mult (multiply)
{
const CPU::Instruction_RTYPE instr = cpu.p_alu.decode.instr.RTYPE;
const u32 a = cpu.regs.r[instr.rs];
const u32 b = cpu.regs.r[instr.rt];
s64 product = (s64)a * (s32)b;
cpu.unit_muldiv.lo = ((u32*)&product)[0];
cpu.unit_muldiv.hi = ((u32*)&product)[1];
cpu.unit_muldiv.timer = 12;
cpu.stall_depends |= CPU::eStall_MulDiv;
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_MULTU: //multu (multiply unsigned)
{
const CPU::Instruction_RTYPE instr = cpu.p_alu.decode.instr.RTYPE;
const u32 a = cpu.regs.r[instr.rs];
const u32 b = cpu.regs.r[instr.rt];
u64 product = (u64)a * b;
cpu.unit_muldiv.lo = ((u32*)&product)[0];
cpu.unit_muldiv.hi = ((u32*)&product)[1];
cpu.unit_muldiv.timer = 12;
cpu.stall_depends |= CPU::eStall_MulDiv;
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_DIV: //div
{
const CPU::Instruction_RTYPE instr = cpu.p_alu.decode.instr.RTYPE;
//TODO - special handling for divide by zero and such (take from mednafen)
const u32 dividend = cpu.regs.r[instr.rs];
const u32 divisor = cpu.regs.r[instr.rt];
cpu.unit_muldiv.lo = (s32)dividend / (s32)divisor;
cpu.unit_muldiv.hi = (s32)dividend % (s32)divisor;
cpu.unit_muldiv.timer = 35;
cpu.stall_depends |= CPU::eStall_MulDiv;
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_DIVU: //divu
{
const CPU::Instruction_RTYPE instr = cpu.p_alu.decode.instr.RTYPE;
//TODO - special handling for divide by zero and such (take from mednafen)
const u32 dividend = cpu.regs.r[instr.rs];
const u32 divisor = cpu.regs.r[instr.rt];
cpu.unit_muldiv.lo = dividend / divisor;
cpu.unit_muldiv.hi = dividend % divisor;
cpu.unit_muldiv.timer = 35;
cpu.stall_depends |= CPU::eStall_MulDiv;
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_ADD: //add [overflow (TBD)]
{
const CPU::Instruction_RTYPE instr = cpu.p_alu.decode.instr.RTYPE;
cpu.regs.r[instr.rd] = cpu.regs.r[instr.rs] + cpu.regs.r[instr.rt];
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_ADDU: //addu (add unsigned) [no overflow]
{
const CPU::Instruction_RTYPE instr = cpu.p_alu.decode.instr.RTYPE;
cpu.regs.r[instr.rd] = cpu.regs.r[instr.rs] + cpu.regs.r[instr.rt];
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_SUBU: //subu (subtract unsigned) [no overflow]
{
const CPU::Instruction_RTYPE instr = cpu.p_alu.decode.instr.RTYPE;
cpu.regs.r[instr.rd] = cpu.regs.r[instr.rs] - cpu.regs.r[instr.rt];
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_AND: //and
{
const CPU::Instruction_RTYPE instr = cpu.p_alu.decode.instr.RTYPE;
cpu.regs.r[instr.rd] = cpu.regs.r[instr.rs] & cpu.regs.r[instr.rt];
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_OR: //or
{
const CPU::Instruction_RTYPE instr = cpu.p_alu.decode.instr.RTYPE;
cpu.regs.r[instr.rd] = cpu.regs.r[instr.rs] | cpu.regs.r[instr.rt];
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_NOR: //nor
{
const CPU::Instruction_RTYPE instr = cpu.p_alu.decode.instr.RTYPE;
cpu.regs.r[instr.rd] = ~(cpu.regs.r[instr.rs] | cpu.regs.r[instr.rt]);
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_SLT: //slt (set on less than) [signed]
{
const CPU::Instruction_RTYPE instr = cpu.p_alu.decode.instr.RTYPE;
cpu.regs.r[instr.rd] = ((s32)cpu.regs.r[instr.rs] < (s32)cpu.regs.r[instr.rt]) ? 1 : 0;
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_SLTU://sltu (set on less than unsigned)
{
const CPU::Instruction_RTYPE instr = cpu.p_alu.decode.instr.RTYPE;
cpu.regs.r[instr.rd] = (cpu.regs.r[instr.rs] < cpu.regs.r[instr.rt]) ? 1 : 0;
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_BCOND:
{
const CPU::Instruction_ITYPE instr = cpu.p_alu.decode.instr.ITYPE;
switch(instr.rt)
{
case 0: //bltz (branch on less than zero)
if(SIGNBIT(cpu.regs.r[instr.rs])!=0)
BRANCH_SET_PC();
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
case 1: //bgez (branch on greater than or equal to zero)
if(SIGNBIT(cpu.regs.r[instr.rs])==0)
BRANCH_SET_PC();
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
break;
}
case eOP_BEQ: //beq (branch on equal)
{
const CPU::Instruction_ITYPE instr = cpu.p_alu.decode.instr.ITYPE;
if(cpu.regs.r[instr.rs] == cpu.regs.r[instr.rt])
BRANCH_SET_PC();
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_BNE: //bne (branch on not equal)
{
const CPU::Instruction_ITYPE instr = cpu.p_alu.decode.instr.ITYPE;
if(cpu.regs.r[instr.rs] != cpu.regs.r[instr.rt])
BRANCH_SET_PC();
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_BLEZ: //blez (branch on less than or equal to zero)
{
const CPU::Instruction_ITYPE instr = cpu.p_alu.decode.instr.ITYPE;
if(SIGNBIT(cpu.regs.r[instr.rs])!=0 || cpu.regs.r[instr.rs] == 0)
BRANCH_SET_PC();
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_BGTZ: //bgtz (branch on greater than zero)
{
const CPU::Instruction_ITYPE instr = cpu.p_alu.decode.instr.ITYPE;
if(SIGNBIT(cpu.regs.r[instr.rs])==0 && cpu.regs.r[instr.rs] != 0)
BRANCH_SET_PC();
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_ADDI: //addi (add immediate) [sign extend immediate and throw overflow exception (TBD)]
{
const CPU::Instruction_ITYPE instr = cpu.p_alu.decode.instr.ITYPE;
cpu.regs.r[instr.rt] = cpu.regs.r[instr.rs] + (s16)instr.immediate;
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_ADDIU: //addiu (add immediate unsigned) [sign extend immediate and no overflow exception]
{
const CPU::Instruction_ITYPE instr = cpu.p_alu.decode.instr.ITYPE;
cpu.regs.r[instr.rt] = cpu.regs.r[instr.rs] + (s16)instr.immediate;
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_SLTI: //slti (set on less than immediate)
{
const CPU::Instruction_ITYPE instr = cpu.p_alu.decode.instr.ITYPE;
if((s32)cpu.regs.r[instr.rs] < (s16)instr.immediate) cpu.regs.r[instr.rt] = 1;
else cpu.regs.r[instr.rt] = 0;
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_SLTIU: //sltiu (set on less than immediate unsigned)
{
const CPU::Instruction_ITYPE instr = cpu.p_alu.decode.instr.ITYPE;
if(cpu.regs.r[instr.rs] < (u32)(s16)instr.immediate) cpu.regs.r[instr.rt] = 1;
else cpu.regs.r[instr.rt] = 0;
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_ANDI: //andi (and immediate)
{
const CPU::Instruction_ITYPE instr = cpu.p_alu.decode.instr.ITYPE;
cpu.regs.r[instr.rt] = cpu.regs.r[instr.rs] & instr.immediate;
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_ORI: //ori (or immediate)
{
const CPU::Instruction_ITYPE instr = cpu.p_alu.decode.instr.ITYPE;
cpu.regs.r[instr.rt] = cpu.regs.r[instr.rs] | instr.immediate;
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_LUI: //lui (load upper immediate)
{
const CPU::Instruction_ITYPE instr = cpu.p_alu.decode.instr.ITYPE;
cpu.regs.r[instr.rt] = instr.immediate<<16;
cpu.p_alu.out_mem.op = CPU::eMemOp_None;
break;
}
case eOP_LB: //lb (load byte)
{
const CPU::Instruction_ITYPE instr = cpu.p_alu.decode.instr.ITYPE;
cpu.p_alu.out_mem.addr = cpu.regs.r[instr.base()] + (s16)instr.immediate;
cpu.p_alu.out_mem.rt = instr.rt;
cpu.p_alu.out_mem.op = CPU::eMemOp_LoadByteSigned;
break;
}
case eOP_LH: //lh (load halfword)
{
const CPU::Instruction_ITYPE instr = cpu.p_alu.decode.instr.ITYPE;
cpu.p_alu.out_mem.addr = cpu.regs.r[instr.base()] + (s16)instr.immediate;
cpu.p_alu.out_mem.rt = instr.rt;
cpu.p_alu.out_mem.op = CPU::eMemOp_LoadHalfwordSigned;
break;
}
case eOP_LW: //lw (load word)
{
const CPU::Instruction_ITYPE instr = cpu.p_alu.decode.instr.ITYPE;
cpu.p_alu.out_mem.addr = cpu.regs.r[instr.base()] + (s16)instr.immediate;
cpu.p_alu.out_mem.rt = instr.rt;
cpu.p_alu.out_mem.op = CPU::eMemOp_LoadWord;
break;
}
case eOP_LBU: //lbu (load byte unsigned)
{
const CPU::Instruction_ITYPE instr = cpu.p_alu.decode.instr.ITYPE;
cpu.p_alu.out_mem.addr = cpu.regs.r[instr.base()] + (s16)instr.immediate;
cpu.p_alu.out_mem.rt = instr.rt;
cpu.p_alu.out_mem.op = CPU::eMemOp_LoadByteUnsigned;
break;
}
case eOP_LHU: //lhu (load halfword unsigned)
{
const CPU::Instruction_ITYPE instr = cpu.p_alu.decode.instr.ITYPE;
cpu.p_alu.out_mem.addr = cpu.regs.r[instr.base()] + (s16)instr.immediate;
cpu.p_alu.out_mem.rt = instr.rt;
cpu.p_alu.out_mem.op = CPU::eMemOp_LoadHalfwordUnsigned;
break;
}
case eOP_SB: //sb (store byte)
{
const CPU::Instruction_ITYPE instr = cpu.p_alu.decode.instr.ITYPE;
cpu.p_alu.out_mem.addr = cpu.regs.r[instr.base()] + (s16)instr.immediate;
cpu.p_alu.out_mem.value = cpu.regs.r[instr.rt] & 0xFF;
cpu.p_alu.out_mem.op = CPU::eMemOp_StoreByte;
break;
}
case eOP_SH: //sh (store halfword)
{
const CPU::Instruction_ITYPE instr = cpu.p_alu.decode.instr.ITYPE;
cpu.p_alu.out_mem.addr = cpu.regs.r[instr.base()] + (s16)instr.immediate;
cpu.p_alu.out_mem.value = cpu.regs.r[instr.rt] & 0xFFFF;
cpu.p_alu.out_mem.op = CPU::eMemOp_StoreHalfword;
break;
}
case eOP_SW: //sw (store word)
{
const CPU::Instruction_ITYPE instr = cpu.p_alu.decode.instr.ITYPE;
cpu.p_alu.out_mem.addr = cpu.regs.r[instr.base()] + (s16)instr.immediate;
cpu.p_alu.out_mem.value = cpu.regs.r[instr.rt];
cpu.p_alu.out_mem.op = CPU::eMemOp_StoreWord;
break;
}
}
cpu.regs.r0 = 0;
//handle exceptions from alu
//if(cpu.p_alu.exception != CPU::eException_None)
//{
// cpu_exception(cpu.p_alu.exception, cpu.p_alu.in_pc);
//}
}
void PSX::cpu_exec_cycle()
{
counter++;
abscounter++;
//rd can sample registers after alu completes
//rd can sample registers after mem completes
//fetch can sample PC after first half of alu completes (branches need to be done in one early)
//we sort of pretend the RD stage isnt there, and the fetch stage is shifted over, so we have
//| IF | ALU | MEM | WB |
//the chief consequences are:
//RD can latch registers that the ALU is asserting
// (so, we will pretend RD doesnt exist and have ALU latch registers that the previous ALU cycle asserted)
// (we'll do this by having an extra buffer of registers)
//we don't worry much about the branch addresses getting passed to IF: we're logically shifted the IF to the right,
//so the ALU will naturally be asserting them
cpu_run_muldiv();
if(cpu.stall_depends & cpu.stall_user)
{
//stalled
}
else
{
cpu_run_fetch();
cpu_run_alu();
cpu_run_mem();
cpu_run_wb();
//latch mem<-alu
cpu.p_mem.decode = cpu.p_alu.decode;
cpu.p_mem.in_from_alu = cpu.p_alu.out_mem;
//latch alu<-fetch
cpu.p_alu.decode = cpu.p_fetch.decode;
cpu.p_alu.in_pc = cpu.p_fetch.in_fetch_addr;
//latch fetch<-alu
cpu.p_fetch.in_fetch_addr = cpu.p_alu.out_pc.enabled ? cpu.p_alu.out_pc.pc : (cpu.p_fetch.in_fetch_addr + 4);
//check for exceptions. this should be more sophisticated
if(cpu.p_alu.exception != CPU::eException_None)
{
cpu_exception(cpu.p_alu.exception,cpu.p_alu.in_pc);
}
}
}
void PSX::RunForever()
{
static const int work = 33*1024*1024*20;
DWORD a = timeGetTime();
for(;;)
{
exec_cycle();
if(counter == work) break;
}
DWORD b = timeGetTime();
printf("%d ms\n",b-a);
}