You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
646 lines
17 KiB
646 lines
17 KiB
/*****
|
|
* SPC7110 emulator - version 0.03 (2008-08-10)
|
|
* Copyright (c) 2008, byuu and neviksti
|
|
*
|
|
* Permission to use, copy, modify, and/or distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* The software is provided "as is" and the author disclaims all warranties
|
|
* with regard to this software including all implied warranties of
|
|
* merchantibility and fitness, in no event shall the author be liable for
|
|
* any special, direct, indirect, or consequential damages or any damages
|
|
* whatsoever resulting from loss of use, data or profits, whether in an
|
|
* action of contract, negligence or other tortious action, arising out of
|
|
* or in connection with the use or performance of this software.
|
|
*****/
|
|
|
|
|
|
#define _SPC7110EMU_CPP_
|
|
|
|
#include "spc7110dec.cpp"
|
|
|
|
const unsigned SPC7110::months[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
|
|
|
|
|
|
void SPC7110::power() {
|
|
reset();
|
|
}
|
|
|
|
void SPC7110::reset() {
|
|
r4801 = 0x00;
|
|
r4802 = 0x00;
|
|
r4803 = 0x00;
|
|
r4804 = 0x00;
|
|
r4805 = 0x00;
|
|
r4806 = 0x00;
|
|
r4807 = 0x00;
|
|
r4808 = 0x00;
|
|
r4809 = 0x00;
|
|
r480a = 0x00;
|
|
r480b = 0x00;
|
|
r480c = 0x00;
|
|
|
|
decomp.reset();
|
|
|
|
r4811 = 0x00;
|
|
r4812 = 0x00;
|
|
r4813 = 0x00;
|
|
r4814 = 0x00;
|
|
r4815 = 0x00;
|
|
r4816 = 0x00;
|
|
r4817 = 0x00;
|
|
r4818 = 0x00;
|
|
|
|
r481x = 0x00;
|
|
r4814_latch = false;
|
|
r4815_latch = false;
|
|
|
|
r4820 = 0x00;
|
|
r4821 = 0x00;
|
|
r4822 = 0x00;
|
|
r4823 = 0x00;
|
|
r4824 = 0x00;
|
|
r4825 = 0x00;
|
|
r4826 = 0x00;
|
|
r4827 = 0x00;
|
|
r4828 = 0x00;
|
|
r4829 = 0x00;
|
|
r482a = 0x00;
|
|
r482b = 0x00;
|
|
r482c = 0x00;
|
|
r482d = 0x00;
|
|
r482e = 0x00;
|
|
r482f = 0x00;
|
|
|
|
r4830 = 0x00;
|
|
mmio_write(0x4831, 0);
|
|
mmio_write(0x4832, 1);
|
|
mmio_write(0x4833, 2);
|
|
r4834 = 0x00;
|
|
|
|
r4840 = 0x00;
|
|
r4841 = 0x00;
|
|
r4842 = 0x00;
|
|
|
|
if(cartridge_info_spc7110rtc) {
|
|
rtc_state = RTCS_Inactive;
|
|
rtc_mode = RTCM_Linear;
|
|
rtc_index = 0;
|
|
}
|
|
}
|
|
|
|
unsigned SPC7110::datarom_addr(unsigned addr) {
|
|
unsigned size = memory_cartrom_size() > 0x500000 ? memory_cartrom_size() - 0x200000 : memory_cartrom_size() - 0x100000;
|
|
while(addr >= size) addr -= size;
|
|
return addr + 0x100000;
|
|
}
|
|
|
|
unsigned SPC7110::data_pointer() { return r4811 + (r4812 << 8) + (r4813 << 16); }
|
|
unsigned SPC7110::data_adjust() { return r4814 + (r4815 << 8); }
|
|
unsigned SPC7110::data_increment() { return r4816 + (r4817 << 8); }
|
|
void SPC7110::set_data_pointer(unsigned addr) { r4811 = addr; r4812 = addr >> 8; r4813 = addr >> 16; }
|
|
void SPC7110::set_data_adjust(unsigned addr) { r4814 = addr; r4815 = addr >> 8; }
|
|
|
|
void SPC7110::update_time(int offset) {
|
|
time_t rtc_time
|
|
= (memory_cartrtc_read(16) << 0)
|
|
| (memory_cartrtc_read(17) << 8)
|
|
| (memory_cartrtc_read(18) << 16)
|
|
| (memory_cartrtc_read(19) << 24);
|
|
time_t current_time = time(0) - offset;
|
|
|
|
//sizeof(time_t) is platform-dependent; though memory::cartrtc needs to be platform-agnostic.
|
|
//yet platforms with 32-bit signed time_t will overflow every ~68 years. handle this by
|
|
//accounting for overflow at the cost of 1-bit precision (to catch underflow). this will allow
|
|
//memory::cartrtc timestamp to remain valid for up to ~34 years from the last update, even if
|
|
//time_t overflows. calculation should be valid regardless of number representation, time_t size,
|
|
//or whether time_t is signed or unsigned.
|
|
time_t diff
|
|
= (current_time >= rtc_time)
|
|
? (current_time - rtc_time)
|
|
: (std::numeric_limits<time_t>::max() - rtc_time + current_time + 1); //compensate for overflow
|
|
if(diff > std::numeric_limits<time_t>::max() / 2) diff = 0; //compensate for underflow
|
|
|
|
bool update = true;
|
|
if(memory_cartrtc_read(13) & 1) update = false; //do not update if CR0 timer disable flag is set
|
|
if(memory_cartrtc_read(15) & 3) update = false; //do not update if CR2 timer disable flags are set
|
|
|
|
if(diff > 0 && update == true) {
|
|
unsigned second = memory_cartrtc_read( 0) + memory_cartrtc_read( 1) * 10;
|
|
unsigned minute = memory_cartrtc_read( 2) + memory_cartrtc_read( 3) * 10;
|
|
unsigned hour = memory_cartrtc_read( 4) + memory_cartrtc_read( 5) * 10;
|
|
unsigned day = memory_cartrtc_read( 6) + memory_cartrtc_read( 7) * 10;
|
|
unsigned month = memory_cartrtc_read( 8) + memory_cartrtc_read( 9) * 10;
|
|
unsigned year = memory_cartrtc_read(10) + memory_cartrtc_read(11) * 10;
|
|
unsigned weekday = memory_cartrtc_read(12);
|
|
|
|
day--;
|
|
month--;
|
|
year += (year >= 90) ? 1900 : 2000; //range = 1990-2089
|
|
|
|
second += diff;
|
|
while(second >= 60) {
|
|
second -= 60;
|
|
|
|
minute++;
|
|
if(minute < 60) continue;
|
|
minute = 0;
|
|
|
|
hour++;
|
|
if(hour < 24) continue;
|
|
hour = 0;
|
|
|
|
day++;
|
|
weekday = (weekday + 1) % 7;
|
|
unsigned days = months[month % 12];
|
|
if(days == 28) {
|
|
bool leapyear = false;
|
|
if((year % 4) == 0) {
|
|
leapyear = true;
|
|
if((year % 100) == 0 && (year % 400) != 0) leapyear = false;
|
|
}
|
|
if(leapyear) days++;
|
|
}
|
|
if(day < days) continue;
|
|
day = 0;
|
|
|
|
month++;
|
|
if(month < 12) continue;
|
|
month = 0;
|
|
|
|
year++;
|
|
}
|
|
|
|
day++;
|
|
month++;
|
|
year %= 100;
|
|
|
|
memory_cartrtc_write( 0, second % 10);
|
|
memory_cartrtc_write( 1, second / 10);
|
|
memory_cartrtc_write( 2, minute % 10);
|
|
memory_cartrtc_write( 3, minute / 10);
|
|
memory_cartrtc_write( 4, hour % 10);
|
|
memory_cartrtc_write( 5, hour / 10);
|
|
memory_cartrtc_write( 6, day % 10);
|
|
memory_cartrtc_write( 7, day / 10);
|
|
memory_cartrtc_write( 8, month % 10);
|
|
memory_cartrtc_write( 9, month / 10);
|
|
memory_cartrtc_write(10, year % 10);
|
|
memory_cartrtc_write(11, (year / 10) % 10);
|
|
memory_cartrtc_write(12, weekday % 7);
|
|
}
|
|
|
|
memory_cartrtc_write(16, current_time >> 0);
|
|
memory_cartrtc_write(17, current_time >> 8);
|
|
memory_cartrtc_write(18, current_time >> 16);
|
|
memory_cartrtc_write(19, current_time >> 24);
|
|
}
|
|
|
|
uint8 SPC7110::mmio_read(unsigned addr) {
|
|
addr &= 0xffff;
|
|
|
|
switch(addr) {
|
|
//==================
|
|
//decompression unit
|
|
//==================
|
|
|
|
case 0x4800: {
|
|
uint16 counter = (r4809 + (r480a << 8));
|
|
counter--;
|
|
r4809 = counter;
|
|
r480a = counter >> 8;
|
|
return decomp.read();
|
|
}
|
|
case 0x4801: return r4801;
|
|
case 0x4802: return r4802;
|
|
case 0x4803: return r4803;
|
|
case 0x4804: return r4804;
|
|
case 0x4805: return r4805;
|
|
case 0x4806: return r4806;
|
|
case 0x4807: return r4807;
|
|
case 0x4808: return r4808;
|
|
case 0x4809: return r4809;
|
|
case 0x480a: return r480a;
|
|
case 0x480b: return r480b;
|
|
case 0x480c: {
|
|
uint8 status = r480c;
|
|
r480c &= 0x7f;
|
|
return status;
|
|
}
|
|
|
|
//==============
|
|
//data port unit
|
|
//==============
|
|
|
|
case 0x4810: {
|
|
if(r481x != 0x07) return 0x00;
|
|
|
|
unsigned addr = data_pointer();
|
|
unsigned adjust = data_adjust();
|
|
if(r4818 & 8) adjust = (int16)adjust; //16-bit sign extend
|
|
|
|
unsigned adjustaddr = addr;
|
|
if(r4818 & 2) {
|
|
adjustaddr += adjust;
|
|
set_data_adjust(adjust + 1);
|
|
}
|
|
|
|
uint8 data = memory_cartrom_read(datarom_addr(adjustaddr));
|
|
if(!(r4818 & 2)) {
|
|
unsigned increment = (r4818 & 1) ? data_increment() : 1;
|
|
if(r4818 & 4) increment = (int16)increment; //16-bit sign extend
|
|
|
|
if((r4818 & 16) == 0) {
|
|
set_data_pointer(addr + increment);
|
|
} else {
|
|
set_data_adjust(adjust + increment);
|
|
}
|
|
}
|
|
|
|
return data;
|
|
}
|
|
case 0x4811: return r4811;
|
|
case 0x4812: return r4812;
|
|
case 0x4813: return r4813;
|
|
case 0x4814: return r4814;
|
|
case 0x4815: return r4815;
|
|
case 0x4816: return r4816;
|
|
case 0x4817: return r4817;
|
|
case 0x4818: return r4818;
|
|
case 0x481a: {
|
|
if(r481x != 0x07) return 0x00;
|
|
|
|
unsigned addr = data_pointer();
|
|
unsigned adjust = data_adjust();
|
|
if(r4818 & 8) adjust = (int16)adjust; //16-bit sign extend
|
|
|
|
uint8 data = memory_cartrom_read(datarom_addr(addr + adjust));
|
|
if((r4818 & 0x60) == 0x60) {
|
|
if((r4818 & 16) == 0) {
|
|
set_data_pointer(addr + adjust);
|
|
} else {
|
|
set_data_adjust(adjust + adjust);
|
|
}
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
//=========
|
|
//math unit
|
|
//=========
|
|
|
|
case 0x4820: return r4820;
|
|
case 0x4821: return r4821;
|
|
case 0x4822: return r4822;
|
|
case 0x4823: return r4823;
|
|
case 0x4824: return r4824;
|
|
case 0x4825: return r4825;
|
|
case 0x4826: return r4826;
|
|
case 0x4827: return r4827;
|
|
case 0x4828: return r4828;
|
|
case 0x4829: return r4829;
|
|
case 0x482a: return r482a;
|
|
case 0x482b: return r482b;
|
|
case 0x482c: return r482c;
|
|
case 0x482d: return r482d;
|
|
case 0x482e: return r482e;
|
|
case 0x482f: {
|
|
uint8 status = r482f;
|
|
r482f &= 0x7f;
|
|
return status;
|
|
}
|
|
|
|
//===================
|
|
//memory mapping unit
|
|
//===================
|
|
|
|
case 0x4830: return r4830;
|
|
case 0x4831: return r4831;
|
|
case 0x4832: return r4832;
|
|
case 0x4833: return r4833;
|
|
case 0x4834: return r4834;
|
|
|
|
//====================
|
|
//real-time clock unit
|
|
//====================
|
|
|
|
case 0x4840: return r4840;
|
|
case 0x4841: {
|
|
if(rtc_state == RTCS_Inactive || rtc_state == RTCS_ModeSelect) return 0x00;
|
|
|
|
r4842 = 0x80;
|
|
uint8 data = memory_cartrtc_read(rtc_index);
|
|
rtc_index = (rtc_index + 1) & 15;
|
|
return data;
|
|
}
|
|
case 0x4842: {
|
|
uint8 status = r4842;
|
|
r4842 &= 0x7f;
|
|
return status;
|
|
}
|
|
}
|
|
|
|
return cpu_regs_mdr;
|
|
}
|
|
|
|
void SPC7110::mmio_write(unsigned addr, uint8 data) {
|
|
addr &= 0xffff;
|
|
|
|
switch(addr) {
|
|
//==================
|
|
//decompression unit
|
|
//==================
|
|
|
|
case 0x4801: r4801 = data; break;
|
|
case 0x4802: r4802 = data; break;
|
|
case 0x4803: r4803 = data; break;
|
|
case 0x4804: r4804 = data; break;
|
|
case 0x4805: r4805 = data; break;
|
|
case 0x4806: {
|
|
r4806 = data;
|
|
|
|
unsigned table = (r4801 + (r4802 << 8) + (r4803 << 16));
|
|
unsigned index = (r4804 << 2);
|
|
//unsigned length = (r4809 + (r480a << 8));
|
|
unsigned addr = datarom_addr(table + index);
|
|
unsigned mode = (memory_cartrom_read(addr + 0));
|
|
unsigned offset = (memory_cartrom_read(addr + 1) << 16)
|
|
+ (memory_cartrom_read(addr + 2) << 8)
|
|
+ (memory_cartrom_read(addr + 3) << 0);
|
|
|
|
decomp.init(mode, offset, (r4805 + (r4806 << 8)) << mode);
|
|
r480c = 0x80;
|
|
} break;
|
|
|
|
case 0x4807: r4807 = data; break;
|
|
case 0x4808: r4808 = data; break;
|
|
case 0x4809: r4809 = data; break;
|
|
case 0x480a: r480a = data; break;
|
|
case 0x480b: r480b = data; break;
|
|
|
|
//==============
|
|
//data port unit
|
|
//==============
|
|
|
|
case 0x4811: r4811 = data; r481x |= 0x01; break;
|
|
case 0x4812: r4812 = data; r481x |= 0x02; break;
|
|
case 0x4813: r4813 = data; r481x |= 0x04; break;
|
|
case 0x4814: {
|
|
r4814 = data;
|
|
r4814_latch = true;
|
|
if(!r4815_latch) break;
|
|
if(!(r4818 & 2)) break;
|
|
if(r4818 & 0x10) break;
|
|
|
|
if((r4818 & 0x60) == 0x20) {
|
|
unsigned increment = data_adjust() & 0xff;
|
|
if(r4818 & 8) increment = (int8)increment; //8-bit sign extend
|
|
set_data_pointer(data_pointer() + increment);
|
|
} else if((r4818 & 0x60) == 0x40) {
|
|
unsigned increment = data_adjust();
|
|
if(r4818 & 8) increment = (int16)increment; //16-bit sign extend
|
|
set_data_pointer(data_pointer() + increment);
|
|
}
|
|
} break;
|
|
case 0x4815: {
|
|
r4815 = data;
|
|
r4815_latch = true;
|
|
if(!r4814_latch) break;
|
|
if(!(r4818 & 2)) break;
|
|
if(r4818 & 0x10) break;
|
|
|
|
if((r4818 & 0x60) == 0x20) {
|
|
unsigned increment = data_adjust() & 0xff;
|
|
if(r4818 & 8) increment = (int8)increment; //8-bit sign extend
|
|
set_data_pointer(data_pointer() + increment);
|
|
} else if((r4818 & 0x60) == 0x40) {
|
|
unsigned increment = data_adjust();
|
|
if(r4818 & 8) increment = (int16)increment; //16-bit sign extend
|
|
set_data_pointer(data_pointer() + increment);
|
|
}
|
|
} break;
|
|
case 0x4816: r4816 = data; break;
|
|
case 0x4817: r4817 = data; break;
|
|
case 0x4818: {
|
|
if(r481x != 0x07) break;
|
|
|
|
r4818 = data;
|
|
r4814_latch = r4815_latch = false;
|
|
} break;
|
|
|
|
//=========
|
|
//math unit
|
|
//=========
|
|
|
|
case 0x4820: r4820 = data; break;
|
|
case 0x4821: r4821 = data; break;
|
|
case 0x4822: r4822 = data; break;
|
|
case 0x4823: r4823 = data; break;
|
|
case 0x4824: r4824 = data; break;
|
|
case 0x4825: {
|
|
r4825 = data;
|
|
|
|
if(r482e & 1) {
|
|
//signed 16-bit x 16-bit multiplication
|
|
int16 r0 = (int16)(r4824 + (r4825 << 8));
|
|
int16 r1 = (int16)(r4820 + (r4821 << 8));
|
|
|
|
signed result = r0 * r1;
|
|
r4828 = result;
|
|
r4829 = result >> 8;
|
|
r482a = result >> 16;
|
|
r482b = result >> 24;
|
|
} else {
|
|
//unsigned 16-bit x 16-bit multiplication
|
|
uint16 r0 = (uint16)(r4824 + (r4825 << 8));
|
|
uint16 r1 = (uint16)(r4820 + (r4821 << 8));
|
|
|
|
unsigned result = r0 * r1;
|
|
r4828 = result;
|
|
r4829 = result >> 8;
|
|
r482a = result >> 16;
|
|
r482b = result >> 24;
|
|
}
|
|
|
|
r482f = 0x80;
|
|
} break;
|
|
case 0x4826: r4826 = data; break;
|
|
case 0x4827: {
|
|
r4827 = data;
|
|
|
|
if(r482e & 1) {
|
|
//signed 32-bit x 16-bit division
|
|
int32 dividend = (int32)(r4820 + (r4821 << 8) + (r4822 << 16) + (r4823 << 24));
|
|
int16 divisor = (int16)(r4826 + (r4827 << 8));
|
|
|
|
int32 quotient;
|
|
int16 remainder;
|
|
|
|
if(divisor) {
|
|
quotient = (int32)(dividend / divisor);
|
|
remainder = (int32)(dividend % divisor);
|
|
} else {
|
|
//illegal division by zero
|
|
quotient = 0;
|
|
remainder = dividend & 0xffff;
|
|
}
|
|
|
|
r4828 = quotient;
|
|
r4829 = quotient >> 8;
|
|
r482a = quotient >> 16;
|
|
r482b = quotient >> 24;
|
|
|
|
r482c = remainder;
|
|
r482d = remainder >> 8;
|
|
} else {
|
|
//unsigned 32-bit x 16-bit division
|
|
uint32 dividend = (uint32)(r4820 + (r4821 << 8) + (r4822 << 16) + (r4823 << 24));
|
|
uint16 divisor = (uint16)(r4826 + (r4827 << 8));
|
|
|
|
uint32 quotient;
|
|
uint16 remainder;
|
|
|
|
if(divisor) {
|
|
quotient = (uint32)(dividend / divisor);
|
|
remainder = (uint16)(dividend % divisor);
|
|
} else {
|
|
//illegal division by zero
|
|
quotient = 0;
|
|
remainder = dividend & 0xffff;
|
|
}
|
|
|
|
r4828 = quotient;
|
|
r4829 = quotient >> 8;
|
|
r482a = quotient >> 16;
|
|
r482b = quotient >> 24;
|
|
|
|
r482c = remainder;
|
|
r482d = remainder >> 8;
|
|
}
|
|
|
|
r482f = 0x80;
|
|
} break;
|
|
|
|
case 0x482e: {
|
|
//reset math unit
|
|
r4820 = r4821 = r4822 = r4823 = 0;
|
|
r4824 = r4825 = r4826 = r4827 = 0;
|
|
r4828 = r4829 = r482a = r482b = 0;
|
|
r482c = r482d = 0;
|
|
|
|
r482e = data;
|
|
} break;
|
|
|
|
//===================
|
|
//memory mapping unit
|
|
//===================
|
|
|
|
case 0x4830: r4830 = data; break;
|
|
|
|
case 0x4831: {
|
|
r4831 = data;
|
|
dx_offset = datarom_addr((data & 7) * 0x100000);
|
|
} break;
|
|
|
|
case 0x4832: {
|
|
r4832 = data;
|
|
ex_offset = datarom_addr((data & 7) * 0x100000);
|
|
} break;
|
|
|
|
case 0x4833: {
|
|
r4833 = data;
|
|
fx_offset = datarom_addr((data & 7) * 0x100000);
|
|
} break;
|
|
|
|
case 0x4834: r4834 = data; break;
|
|
|
|
//====================
|
|
//real-time clock unit
|
|
//====================
|
|
|
|
case 0x4840: {
|
|
r4840 = data;
|
|
if(!(r4840 & 1)) {
|
|
//disable RTC
|
|
rtc_state = RTCS_Inactive;
|
|
update_time();
|
|
} else {
|
|
//enable RTC
|
|
r4842 = 0x80;
|
|
rtc_state = RTCS_ModeSelect;
|
|
}
|
|
} break;
|
|
|
|
case 0x4841: {
|
|
r4841 = data;
|
|
|
|
switch(rtc_state) {
|
|
case RTCS_ModeSelect: {
|
|
if(data == RTCM_Linear || data == RTCM_Indexed) {
|
|
r4842 = 0x80;
|
|
rtc_state = RTCS_IndexSelect;
|
|
rtc_mode = (RTC_Mode)data;
|
|
rtc_index = 0;
|
|
}
|
|
} break;
|
|
|
|
case RTCS_IndexSelect: {
|
|
r4842 = 0x80;
|
|
rtc_index = data & 15;
|
|
if(rtc_mode == RTCM_Linear) rtc_state = RTCS_Write;
|
|
} break;
|
|
|
|
case RTCS_Write: {
|
|
r4842 = 0x80;
|
|
|
|
//control register 0
|
|
if(rtc_index == 13) {
|
|
//increment second counter
|
|
if(data & 2) update_time(+1);
|
|
|
|
//round minute counter
|
|
if(data & 8) {
|
|
update_time();
|
|
|
|
unsigned second = memory_cartrtc_read( 0) + memory_cartrtc_read( 1) * 10;
|
|
//clear seconds
|
|
memory_cartrtc_write(0, 0);
|
|
memory_cartrtc_write(1, 0);
|
|
|
|
if(second >= 30) update_time(+60);
|
|
}
|
|
}
|
|
|
|
//control register 2
|
|
if(rtc_index == 15) {
|
|
//disable timer and clear second counter
|
|
if((data & 1) && !(memory_cartrtc_read(15) & 1)) {
|
|
update_time();
|
|
|
|
//clear seconds
|
|
memory_cartrtc_write(0, 0);
|
|
memory_cartrtc_write(1, 0);
|
|
}
|
|
|
|
//disable timer
|
|
if((data & 2) && !(memory_cartrtc_read(15) & 2)) {
|
|
update_time();
|
|
}
|
|
}
|
|
|
|
memory_cartrtc_write(rtc_index, data & 15);
|
|
rtc_index = (rtc_index + 1) & 15;
|
|
} break;
|
|
|
|
case RTCS_Inactive: {
|
|
} break;
|
|
} //switch(rtc_state)
|
|
} break;
|
|
}
|
|
}
|
|
|
|
SPC7110::SPC7110() {
|
|
}
|