Path: blob/master/libsnes/bsnes/snes/chip/spc7110/spc7110.cpp
2 views
#include <snes/snes.hpp>12#define SPC7110_CPP3namespace SNES {45SPC7110 spc7110;67#include "serialization.cpp"8#include "decomp.cpp"910const unsigned SPC7110::months[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };1112void SPC7110::init() {13}1415void SPC7110::load() {16for(unsigned n = 0; n < 20; n++) rtc[n] = 0xff;17if(cartridge.has_spc7110rtc()) cartridge.nvram.append({ "program.rtc", rtc, 20 });18}1920void SPC7110::unload() {21}2223void SPC7110::power() {24}2526void SPC7110::reset() {27r4801 = 0x00;28r4802 = 0x00;29r4803 = 0x00;30r4804 = 0x00;31r4805 = 0x00;32r4806 = 0x00;33r4807 = 0x00;34r4808 = 0x00;35r4809 = 0x00;36r480a = 0x00;37r480b = 0x00;38r480c = 0x00;3940decomp.reset();4142r4811 = 0x00;43r4812 = 0x00;44r4813 = 0x00;45r4814 = 0x00;46r4815 = 0x00;47r4816 = 0x00;48r4817 = 0x00;49r4818 = 0x00;5051r481x = 0x00;52r4814_latch = false;53r4815_latch = false;5455r4820 = 0x00;56r4821 = 0x00;57r4822 = 0x00;58r4823 = 0x00;59r4824 = 0x00;60r4825 = 0x00;61r4826 = 0x00;62r4827 = 0x00;63r4828 = 0x00;64r4829 = 0x00;65r482a = 0x00;66r482b = 0x00;67r482c = 0x00;68r482d = 0x00;69r482e = 0x00;70r482f = 0x00;7172r4830 = 0x00;73mmio_write(0x4831, 0);74mmio_write(0x4832, 1);75mmio_write(0x4833, 2);76r4834 = 0x00;7778r4840 = 0x00;79r4841 = 0x00;80r4842 = 0x00;8182if(cartridge.has_spc7110rtc()) {83rtc_state = RTCS_Inactive;84rtc_mode = RTCM_Linear;85rtc_index = 0;86}87}8889unsigned SPC7110::datarom_addr(unsigned addr) {90unsigned size = cartridge.rom.size() - data_rom_offset;91while(addr >= size) addr -= size;92return data_rom_offset + addr;93}9495unsigned SPC7110::data_pointer() { return r4811 + (r4812 << 8) + (r4813 << 16); }96unsigned SPC7110::data_adjust() { return r4814 + (r4815 << 8); }97unsigned SPC7110::data_increment() { return r4816 + (r4817 << 8); }98void SPC7110::set_data_pointer(unsigned addr) { r4811 = addr; r4812 = addr >> 8; r4813 = addr >> 16; }99void SPC7110::set_data_adjust(unsigned addr) { r4814 = addr; r4815 = addr >> 8; }100101void SPC7110::update_time(int offset) {102time_t rtc_time = (rtc[16] << 0) | (rtc[17] << 8) | (rtc[18] << 16) | (rtc[19] << 24);103time_t current_time = SNES::interface()->currentTime() - offset;104105//sizeof(time_t) is platform-dependent; though rtc[] needs to be platform-agnostic.106//yet platforms with 32-bit signed time_t will overflow every ~68 years. handle this by107//accounting for overflow at the cost of 1-bit precision (to catch underflow). this will allow108//rtc[] timestamp to remain valid for up to ~34 years from the last update, even if109//time_t overflows. calculation should be valid regardless of number representation, time_t size,110//or whether time_t is signed or unsigned.111time_t diff112= (current_time >= rtc_time)113? (current_time - rtc_time)114: (std::numeric_limits<time_t>::max() - rtc_time + current_time + 1); //compensate for overflow115if(diff > std::numeric_limits<time_t>::max() / 2) diff = 0; //compensate for underflow116117bool update = true;118if(rtc[13] & 1) update = false; //do not update if CR0 timer disable flag is set119if(rtc[15] & 3) update = false; //do not update if CR2 timer disable flags are set120121if(diff > 0 && update == true) {122unsigned second = rtc[ 0] + rtc[ 1] * 10;123unsigned minute = rtc[ 2] + rtc[ 3] * 10;124unsigned hour = rtc[ 4] + rtc[ 5] * 10;125unsigned day = rtc[ 6] + rtc[ 7] * 10;126unsigned month = rtc[ 8] + rtc[ 9] * 10;127unsigned year = rtc[10] + rtc[11] * 10;128unsigned weekday = rtc[12];129130day--;131month--;132year += (year >= 90) ? 1900 : 2000; //range = 1990-2089133134second += diff;135while(second >= 60) {136second -= 60;137138minute++;139if(minute < 60) continue;140minute = 0;141142hour++;143if(hour < 24) continue;144hour = 0;145146day++;147weekday = (weekday + 1) % 7;148unsigned days = months[month % 12];149if(days == 28) {150bool leapyear = false;151if((year % 4) == 0) {152leapyear = true;153if((year % 100) == 0 && (year % 400) != 0) leapyear = false;154}155if(leapyear) days++;156}157if(day < days) continue;158day = 0;159160month++;161if(month < 12) continue;162month = 0;163164year++;165}166167day++;168month++;169year %= 100;170171rtc[ 0] = second % 10;172rtc[ 1] = second / 10;173rtc[ 2] = minute % 10;174rtc[ 3] = minute / 10;175rtc[ 4] = hour % 10;176rtc[ 5] = hour / 10;177rtc[ 6] = day % 10;178rtc[ 7] = day / 10;179rtc[ 8] = month % 10;180rtc[ 9] = month / 10;181rtc[10] = year % 10;182rtc[11] = (year / 10) % 10;183rtc[12] = weekday % 7;184}185186rtc[16] = current_time >> 0;187rtc[17] = current_time >> 8;188rtc[18] = current_time >> 16;189rtc[19] = current_time >> 24;190}191192uint8 SPC7110::mmio_read(unsigned addr) {193addr &= 0xffff;194195switch(addr) {196//==================197//decompression unit198//==================199200case 0x4800: {201uint16 counter = (r4809 + (r480a << 8));202counter--;203r4809 = counter;204r480a = counter >> 8;205return decomp.read();206}207case 0x4801: return r4801;208case 0x4802: return r4802;209case 0x4803: return r4803;210case 0x4804: return r4804;211case 0x4805: return r4805;212case 0x4806: return r4806;213case 0x4807: return r4807;214case 0x4808: return r4808;215case 0x4809: return r4809;216case 0x480a: return r480a;217case 0x480b: return r480b;218case 0x480c: {219uint8 status = r480c;220r480c &= 0x7f;221return status;222}223224//==============225//data port unit226//==============227228case 0x4810: {229if(r481x != 0x07) return 0x00;230231unsigned addr = data_pointer();232unsigned adjust = data_adjust();233if(r4818 & 8) adjust = (int16)adjust; //16-bit sign extend234235unsigned adjustaddr = addr;236if(r4818 & 2) {237adjustaddr += adjust;238set_data_adjust(adjust + 1);239}240241uint8 data = cartridge.rom.read(datarom_addr(adjustaddr));242if(!(r4818 & 2)) {243unsigned increment = (r4818 & 1) ? data_increment() : 1;244if(r4818 & 4) increment = (int16)increment; //16-bit sign extend245246if((r4818 & 16) == 0) {247set_data_pointer(addr + increment);248} else {249set_data_adjust(adjust + increment);250}251}252253return data;254}255case 0x4811: return r4811;256case 0x4812: return r4812;257case 0x4813: return r4813;258case 0x4814: return r4814;259case 0x4815: return r4815;260case 0x4816: return r4816;261case 0x4817: return r4817;262case 0x4818: return r4818;263case 0x481a: {264if(r481x != 0x07) return 0x00;265266unsigned addr = data_pointer();267unsigned adjust = data_adjust();268if(r4818 & 8) adjust = (int16)adjust; //16-bit sign extend269270uint8 data = cartridge.rom.read(datarom_addr(addr + adjust));271if((r4818 & 0x60) == 0x60) {272if((r4818 & 16) == 0) {273set_data_pointer(addr + adjust);274} else {275set_data_adjust(adjust + adjust);276}277}278279return data;280}281282//=========283//math unit284//=========285286case 0x4820: return r4820;287case 0x4821: return r4821;288case 0x4822: return r4822;289case 0x4823: return r4823;290case 0x4824: return r4824;291case 0x4825: return r4825;292case 0x4826: return r4826;293case 0x4827: return r4827;294case 0x4828: return r4828;295case 0x4829: return r4829;296case 0x482a: return r482a;297case 0x482b: return r482b;298case 0x482c: return r482c;299case 0x482d: return r482d;300case 0x482e: return r482e;301case 0x482f: {302uint8 status = r482f;303r482f &= 0x7f;304return status;305}306307//===================308//memory mapping unit309//===================310311case 0x4830: return r4830;312case 0x4831: return r4831;313case 0x4832: return r4832;314case 0x4833: return r4833;315case 0x4834: return r4834;316317//====================318//real-time clock unit319//====================320321case 0x4840: return r4840;322case 0x4841: {323if(rtc_state == RTCS_Inactive || rtc_state == RTCS_ModeSelect) return 0x00;324325r4842 = 0x80;326uint8 data = rtc[rtc_index];327rtc_index = (rtc_index + 1) & 15;328return data;329}330case 0x4842: {331uint8 status = r4842;332r4842 &= 0x7f;333return status;334}335}336337return cpu.regs.mdr;338}339340void SPC7110::mmio_write(unsigned addr, uint8 data) {341addr &= 0xffff;342343switch(addr) {344//==================345//decompression unit346//==================347348case 0x4801: r4801 = data; break;349case 0x4802: r4802 = data; break;350case 0x4803: r4803 = data; break;351case 0x4804: r4804 = data; break;352case 0x4805: r4805 = data; break;353case 0x4806: {354r4806 = data;355356unsigned table = (r4801 + (r4802 << 8) + (r4803 << 16));357unsigned index = (r4804 << 2);358unsigned length = (r4809 + (r480a << 8));359unsigned addr = datarom_addr(table + index);360unsigned mode = (cartridge.rom.read(addr + 0));361unsigned offset = (cartridge.rom.read(addr + 1) << 16)362+ (cartridge.rom.read(addr + 2) << 8)363+ (cartridge.rom.read(addr + 3) << 0);364365decomp.init(mode, offset, (r4805 + (r4806 << 8)) << mode);366r480c = 0x80;367} break;368369case 0x4807: r4807 = data; break;370case 0x4808: r4808 = data; break;371case 0x4809: r4809 = data; break;372case 0x480a: r480a = data; break;373case 0x480b: r480b = data; break;374375//==============376//data port unit377//==============378379case 0x4811: r4811 = data; r481x |= 0x01; break;380case 0x4812: r4812 = data; r481x |= 0x02; break;381case 0x4813: r4813 = data; r481x |= 0x04; break;382case 0x4814: {383r4814 = data;384r4814_latch = true;385if(!r4815_latch) break;386if(!(r4818 & 2)) break;387if(r4818 & 0x10) break;388389if((r4818 & 0x60) == 0x20) {390unsigned increment = data_adjust() & 0xff;391if(r4818 & 8) increment = (int8)increment; //8-bit sign extend392set_data_pointer(data_pointer() + increment);393} else if((r4818 & 0x60) == 0x40) {394unsigned increment = data_adjust();395if(r4818 & 8) increment = (int16)increment; //16-bit sign extend396set_data_pointer(data_pointer() + increment);397}398} break;399case 0x4815: {400r4815 = data;401r4815_latch = true;402if(!r4814_latch) break;403if(!(r4818 & 2)) break;404if(r4818 & 0x10) break;405406if((r4818 & 0x60) == 0x20) {407unsigned increment = data_adjust() & 0xff;408if(r4818 & 8) increment = (int8)increment; //8-bit sign extend409set_data_pointer(data_pointer() + increment);410} else if((r4818 & 0x60) == 0x40) {411unsigned increment = data_adjust();412if(r4818 & 8) increment = (int16)increment; //16-bit sign extend413set_data_pointer(data_pointer() + increment);414}415} break;416case 0x4816: r4816 = data; break;417case 0x4817: r4817 = data; break;418case 0x4818: {419if(r481x != 0x07) break;420421r4818 = data;422r4814_latch = r4815_latch = false;423} break;424425//=========426//math unit427//=========428429case 0x4820: r4820 = data; break;430case 0x4821: r4821 = data; break;431case 0x4822: r4822 = data; break;432case 0x4823: r4823 = data; break;433case 0x4824: r4824 = data; break;434case 0x4825: {435r4825 = data;436437if(r482e & 1) {438//signed 16-bit x 16-bit multiplication439int16 r0 = (int16)(r4824 + (r4825 << 8));440int16 r1 = (int16)(r4820 + (r4821 << 8));441442signed result = r0 * r1;443r4828 = result;444r4829 = result >> 8;445r482a = result >> 16;446r482b = result >> 24;447} else {448//unsigned 16-bit x 16-bit multiplication449uint16 r0 = (uint16)(r4824 + (r4825 << 8));450uint16 r1 = (uint16)(r4820 + (r4821 << 8));451452unsigned result = r0 * r1;453r4828 = result;454r4829 = result >> 8;455r482a = result >> 16;456r482b = result >> 24;457}458459r482f = 0x80;460} break;461case 0x4826: r4826 = data; break;462case 0x4827: {463r4827 = data;464465if(r482e & 1) {466//signed 32-bit x 16-bit division467int32 dividend = (int32)(r4820 + (r4821 << 8) + (r4822 << 16) + (r4823 << 24));468int16 divisor = (int16)(r4826 + (r4827 << 8));469470int32 quotient;471int16 remainder;472473if(divisor) {474quotient = (int32)(dividend / divisor);475remainder = (int32)(dividend % divisor);476} else {477//illegal division by zero478quotient = 0;479remainder = dividend & 0xffff;480}481482r4828 = quotient;483r4829 = quotient >> 8;484r482a = quotient >> 16;485r482b = quotient >> 24;486487r482c = remainder;488r482d = remainder >> 8;489} else {490//unsigned 32-bit x 16-bit division491uint32 dividend = (uint32)(r4820 + (r4821 << 8) + (r4822 << 16) + (r4823 << 24));492uint16 divisor = (uint16)(r4826 + (r4827 << 8));493494uint32 quotient;495uint16 remainder;496497if(divisor) {498quotient = (uint32)(dividend / divisor);499remainder = (uint16)(dividend % divisor);500} else {501//illegal division by zero502quotient = 0;503remainder = dividend & 0xffff;504}505506r4828 = quotient;507r4829 = quotient >> 8;508r482a = quotient >> 16;509r482b = quotient >> 24;510511r482c = remainder;512r482d = remainder >> 8;513}514515r482f = 0x80;516} break;517518case 0x482e: {519//reset math unit520r4820 = r4821 = r4822 = r4823 = 0;521r4824 = r4825 = r4826 = r4827 = 0;522r4828 = r4829 = r482a = r482b = 0;523r482c = r482d = 0;524525r482e = data;526} break;527528//===================529//memory mapping unit530//===================531532case 0x4830: r4830 = data; break;533534case 0x4831: {535r4831 = data;536dx_offset = datarom_addr(data * 0x100000);537} break;538539case 0x4832: {540r4832 = data;541ex_offset = datarom_addr(data * 0x100000);542} break;543544case 0x4833: {545r4833 = data;546fx_offset = datarom_addr(data * 0x100000);547} break;548549case 0x4834: r4834 = data; break;550551//====================552//real-time clock unit553//====================554555case 0x4840: {556r4840 = data;557if(!(r4840 & 1)) {558//disable RTC559rtc_state = RTCS_Inactive;560update_time();561} else {562//enable RTC563r4842 = 0x80;564rtc_state = RTCS_ModeSelect;565}566} break;567568case 0x4841: {569r4841 = data;570571switch(rtc_state) {572case RTCS_ModeSelect: {573if(data == RTCM_Linear || data == RTCM_Indexed) {574r4842 = 0x80;575rtc_state = RTCS_IndexSelect;576rtc_mode = (RTC_Mode)data;577rtc_index = 0;578}579} break;580581case RTCS_IndexSelect: {582r4842 = 0x80;583rtc_index = data & 15;584if(rtc_mode == RTCM_Linear) rtc_state = RTCS_Write;585} break;586587case RTCS_Write: {588r4842 = 0x80;589590//control register 0591if(rtc_index == 13) {592//increment second counter593if(data & 2) update_time(+1);594595//round minute counter596if(data & 8) {597update_time();598599unsigned second = rtc[ 0] + rtc[ 1] * 10;600//clear seconds601rtc[0] = 0;602rtc[1] = 0;603604if(second >= 30) update_time(+60);605}606}607608//control register 2609if(rtc_index == 15) {610//disable timer and clear second counter611if((data & 1) && !(rtc[15] & 1)) {612update_time();613614//clear seconds615rtc[0] = 0;616rtc[1] = 0;617}618619//disable timer620if((data & 2) && !(rtc[15] & 2)) {621update_time();622}623}624625rtc[rtc_index] = data & 15;626rtc_index = (rtc_index + 1) & 15;627} break;628} //switch(rtc_state)629} break;630}631}632633SPC7110::SPC7110() :634rtc(nullptr)635{636637}638639SPC7110::~SPC7110()640{641interface()->freeSharedMemory(rtc);642}643644void SPC7110::initialize()645{646rtc = (uint8*)interface()->allocSharedMemory("SPC7110_RTC", 20);647}648//============649//SPC7110::MCU650//============651652uint8 SPC7110::mcu_read(unsigned addr) {653if(addr <= 0xdfffff) return cartridge.rom.read(dx_offset + (addr & 0x0fffff));654if(addr <= 0xefffff) return cartridge.rom.read(ex_offset + (addr & 0x0fffff));655if(addr <= 0xffffff) return cartridge.rom.read(fx_offset + (addr & 0x0fffff));656return cpu.regs.mdr;657}658659void SPC7110::mcu_write(unsigned addr, uint8 data) {660}661662//============663//SPC7110::DCU664//============665666uint8 SPC7110::dcu_read(unsigned) {667return mmio_read(0x4800);668}669670void SPC7110::dcu_write(unsigned, uint8) {671}672673//============674//SPC7110::RAM675//============676677uint8 SPC7110::ram_read(unsigned addr) {678return cartridge.ram.read(addr & 0x1fff);679}680681void SPC7110::ram_write(unsigned addr, uint8 data) {682if(r4830 & 0x80) cartridge.ram.write(addr & 0x1fff, data);683}684685}686687688