1240 lines
39 KiB
C++
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);
|
|
}
|