bus/gameboy: Moved MBC3/MBC30 to their own file as separate devices.

New working software list additions
-----------------------------------
gbcolor.xml: Juéduì Wǔlì (China), Wàixīng Tànxiǎn zhī Xīngqiú Dàzhàn (China) [Robyn A1200, taizou]
This commit is contained in:
Vas Crabb 2022-10-04 12:48:26 +11:00
parent a272716bce
commit f4b23f1737
11 changed files with 783 additions and 557 deletions

View File

@ -4041,7 +4041,7 @@ license:CC0
<software name="frltwrd">
<description>First Letters and Words (Version 1.0)</description>
<year>????</year>
<publisher>First Byte</publisher>
<publisher>First Byte</publisher>
<info name="release" value="2022-09-10"/>
<sharedfeat name="compatibility" value="A2GS"/>
<!-- It requires a 768K Apple IIgs. -->

View File

@ -26816,10 +26816,10 @@ license:CC0
<info name="alt_title" value="真三國無雙2"/>
<part name="cart" interface="gameboy_cart">
<feature name="slot" value="rom_sintax" />
<dataarea name="rom" size="2097152">
<rom name="zhen san guo wu shuang 2 - shin sangokumusou 2 (chi)(unl) [raw dump].bin" size="2097152" crc="95c322c9" sha1="e6ab58d212de83cb18e9eadac4cee5d0f6e4e31f"/>
<dataarea name="rom" size="0x200000">
<rom name="zhen san guo wu shuang 2 - shin sangokumusou 2 (chi)(unl) [raw dump].bin" size="0x200000" crc="95c322c9" sha1="e6ab58d212de83cb18e9eadac4cee5d0f6e4e31f"/>
</dataarea>
<dataarea name="nvram" size="8192">
<dataarea name="nvram" size="0x2000">
</dataarea>
</part>
</software>
@ -26831,10 +26831,37 @@ license:CC0
<info name="alt_title" value="真三國無雙2"/>
<part name="cart" interface="gameboy_cart">
<feature name="slot" value="rom_mbc5" />
<dataarea name="rom" size="2097152">
<rom name="shin san guo shi 2 (unl).bin" size="2097152" crc="33f56c90" sha1="a5111c11cf70c65341b373f484650caae3c4d29f"/>
<dataarea name="rom" size="0x200000">
<rom name="shin san guo shi 2 (unl).bin" size="0x200000" crc="33f56c90" sha1="a5111c11cf70c65341b373f484650caae3c4d29f"/>
</dataarea>
<dataarea name="nvram" size="8192">
<dataarea name="nvram" size="0x2000">
</dataarea>
</part>
</software>
<software name="jueduiwl">
<description>Juéduì Wǔlì (China)</description>
<year>200?</year>
<publisher>Sintax</publisher>
<info name="alt_title" value="絕對武力" />
<part name="cart" interface="gameboy_cart">
<feature name="slot" value="rom_sintax" />
<dataarea name="rom" size="0x200000">
<rom name="juedui wuli.gbc" size="0x200000" crc="f4d63a7e" sha1="f0b9f02335ee89aa6189e687e6e759d8c49bc2b3"/>
</dataarea>
</part>
</software>
<software name="wxtxxqdz">
<!-- not sure how the hiragana の (no) in the title should be read - assuming it's a stylised 之 -->
<description>Wàixīng Tànxiǎn zhī Xīngqiú Dàzhàn (China)</description>
<year>200?</year>
<publisher>Sintax</publisher>
<info name="alt_title" value="外星探險の星球大戰" />
<part name="cart" interface="gameboy_cart">
<feature name="slot" value="rom_sintax" />
<dataarea name="rom" size="0x200000">
<rom name="waixing tanxian xingqiu dazhan.gbc" size="0x200000" crc="4e600093" sha1="32067be39f0bdc354c31a7a02f24ba00b61ae4f1"/>
</dataarea>
</part>
</software>

View File

@ -3709,6 +3709,8 @@ if (BUSES["GAMEBOY"]~=null) then
MAME_DIR .. "src/devices/bus/gameboy/mbc.h",
MAME_DIR .. "src/devices/bus/gameboy/mbc2.cpp",
MAME_DIR .. "src/devices/bus/gameboy/mbc2.h",
MAME_DIR .. "src/devices/bus/gameboy/mbc3.cpp",
MAME_DIR .. "src/devices/bus/gameboy/mbc3.h",
MAME_DIR .. "src/devices/bus/gameboy/mbc6.cpp",
MAME_DIR .. "src/devices/bus/gameboy/mbc6.h",
MAME_DIR .. "src/devices/bus/gameboy/mbc7.cpp",

View File

@ -15,6 +15,7 @@
#include "huc3.h"
#include "mbc.h"
#include "mbc2.h"
#include "mbc3.h"
#include "mbc6.h"
#include "mbc7.h"
#include "mmm01.h"
@ -80,7 +81,7 @@ void gameboy_cartridges(device_slot_interface &device)
device.option_add_internal(slotoptions::GB_MBC1, GB_ROM_MBC1);
device.option_add_internal(slotoptions::GB_MBC2, GB_ROM_MBC2);
device.option_add_internal(slotoptions::GB_MBC3, GB_ROM_MBC3);
device.option_add_internal(slotoptions::GB_MBC30, GB_ROM_MBC3); // MBC3 and MBC30 treated as the same thing for now
device.option_add_internal(slotoptions::GB_MBC30, GB_ROM_MBC30);
device.option_add_internal(slotoptions::GB_MBC5, GB_ROM_MBC5);
device.option_add_internal(slotoptions::GB_MBC6, GB_ROM_MBC6);
device.option_add_internal(slotoptions::GB_MBC7_2K, GB_ROM_MBC7_2K);

View File

@ -31,34 +31,6 @@
Regular ROM aliasing rules apply.
MBC3 Mapper
===========
The MBC3 mapper cartridges can include a RTC chip.
0000-1FFF - Writing to this area enables (value 0x0A) or disables (not 0x0A) the
SRAM and RTC registers.
2000-3FFF - Writing to this area selects the ROM bank to appear at 4000-7FFF.
Bits 6-0 are used to select the bank number. If a value of
0bX0000000 is written then this is automatically changed into
0bX0000001 by the mapper.
4000-5FFF - Writing to this area selects the RAM bank or the RTC register to
read.
XXXX00bb - Select RAM bank bb.
XXXX1rrr - Select RTC register rrr. Accepted values for rrr are:
000 - Seconds (0x00-0x3B)
001 - Minutes (0x00-0x3B)
010 - Hours (0x00-0x17)
011 - Bits 7-0 of the day counter
100 - bit 0 - Bit 8 of the day counter
bit 6 - Halt RTC timer ( 0 = timer active, 1 = halted)
bit 7 - Day counter overflow flag
6000-7FFF - Writing 0x00 followed by 0x01 latches the RTC data. This latching
method is used for reading the RTC registers.
Regular ROM aliasing rules apply.
MBC5 Mapper
===========
@ -73,11 +45,6 @@
TODO:
* What does MBC3 do with the RAM bank outputs when RTC is selected?
* How do MBC3 invalid second/minute/hour values roll over?
* For convenience, MBC3 and MBC30 are emulated as one device for now.
- MBC3 only has 2 RAM bank outputs, but it will allow 3 like MBC30 here.
- MBC30 supposedly has 8 ROM bank outputs, but the one game using it only needs 7.
* MBC5 logo spoofing class implements several strategies. It's likely not all carts using it use all
the strategies. Strategies implemented by each cartridge should be identified.
* HK0701 and HK0819 seem to differ in that HK0819 fully decodes ROM addresses while HK0701 mirrors - we
@ -96,8 +63,6 @@
#include "bus/generic/slot.h"
#include "dirtc.h"
#include "corestr.h"
#include <algorithm>
@ -188,12 +153,7 @@ protected:
set_bank_ram(data & m_bank_lines[1]);
}
auto &view_aux(unsigned entry) { return m_view_ram[entry + 1]; }
void set_view_aux(unsigned entry) { m_view_ram.select(entry + 1); }
private:
static inline constexpr unsigned PAGE_RAM_SIZE = 0x2000;
memory_view m_view_ram;
u16 m_bank_lines[2];
@ -566,508 +526,6 @@ private:
//**************************************************************************
// MBC3 (max 8 MiB ROM, max 32 KiB SRAM, RTC)
//**************************************************************************
class mbc3_device : public rom_mbc_device_base, public device_rtc_interface, public device_nvram_interface
{
public:
mbc3_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) :
rom_mbc_device_base(mconfig, GB_ROM_MBC3, tag, owner, clock),
device_rtc_interface(mconfig, *this),
device_nvram_interface(mconfig, *this),
m_timer_rtc(nullptr),
m_machine_seconds(0),
m_has_rtc_xtal(false),
m_has_battery(false),
m_rtc_enable(0U),
m_rtc_select(0U),
m_rtc_latch(0U)
{
}
virtual image_init_result load(std::string &message) override ATTR_COLD
{
// check for RTC oscillator and backup battery
if (loaded_through_softlist())
{
// there's a feature tag indicating presence or absence of RTC crystal
char const *const rtcfeature(get_feature("rtc"));
if (rtcfeature)
{
// explicitly specified in software list
if (util::streqlower(rtcfeature, "yes") || util::streqlower(rtcfeature, "true"))
{
logerror("Real-time clock crystal present\n");
m_has_rtc_xtal = true;
}
else if (util::streqlower(rtcfeature, "no") || util::streqlower(rtcfeature, "false"))
{
logerror("No real-time clock crystal present\n");
m_has_rtc_xtal = false;
}
else
{
message = "Invalid 'rtc' feature value (must be yes or no)";
return image_init_result::FAIL;
}
}
else
{
logerror("No 'rtc' feature found, assuming no real-time clock crystal present\n");
m_has_rtc_xtal = false;
}
// if there's an NVRAM region, there must be a backup battery
if (cart_nvram_region())
{
logerror("Found 'nvram' region, backup battery must be present\n");
m_has_battery = true;
}
else
{
logerror("No 'nvram' region found, assuming no backup battery present\n");
m_has_battery = true;
}
}
else
{
gbxfile::leader_1_0 leader;
u8 const *extra;
u32 extralen;
if (gbxfile::get_data(gbx_footer_region(), leader, extra, extralen))
{
m_has_rtc_xtal = bool(leader.rtc);
m_has_battery = bool(leader.batt);
logerror(
"GBX format image specifies %sreal-time clock crystal present, %sbackup battery present\n",
m_has_rtc_xtal ? "" : "no ",
m_has_battery ? "" : "no ");
}
else
{
// try probing the header
memory_region *const romregion(cart_rom_region());
if (romregion && (romregion->bytes() > cartheader::OFFSET_TYPE))
{
u8 const carttype((&romregion->as_u8())[cartheader::OFFSET_TYPE]);
switch (carttype)
{
case cartheader::TYPE_MBC3_RTC_BATT:
case cartheader::TYPE_MBC3_RTC_RAM_BATT:
m_has_rtc_xtal = true;
m_has_battery = true;
break;
case cartheader::TYPE_MBC3:
m_has_rtc_xtal = false;
m_has_battery = false;
break;
case cartheader::TYPE_MBC3_RAM:
case cartheader::TYPE_MBC3_RAM_BATT:
m_has_rtc_xtal = false;
m_has_battery = true;
break;
default:
osd_printf_warning(
"[%s] Unrecognized cartridge type 0x%02X in header, assuming no real-time clock crystal or backup battery present\n",
tag(),
carttype);
m_has_rtc_xtal = false;
m_has_battery = false;
}
logerror(
"Cartridge type 0x%02X in header, %sreal-time clock crystal present, %sbackup battery present\n",
carttype,
m_has_rtc_xtal ? "" : "no ",
m_has_battery ? "" : "no ");
}
}
}
// set up ROM and RAM
if (!install_memory(message, 3, 7))
return image_init_result::FAIL;
// install bank switching handlers
cart_space()->install_write_handler(
0x0000, 0x1fff,
write8smo_delegate(*this, FUNC(mbc3_device::enable_ram_rtc)));
cart_space()->install_write_handler(
0x2000, 0x3fff,
write8smo_delegate(*this, FUNC(mbc3_device::bank_switch_fine)));
cart_space()->install_write_handler(
0x4000, 0x5fff,
write8smo_delegate(*this, FUNC(mbc3_device::select_ram_rtc)));
cart_space()->install_write_handler(
0x6000, 0x7fff,
write8smo_delegate(*this, FUNC(mbc3_device::latch_rtc)));
// install real-time clock handlers
view_aux(0).install_read_handler(
0xa000, 0xbfff,
read8mo_delegate(*this, FUNC(mbc3_device::read_rtc)));
view_aux(0).install_write_handler(
0xa000, 0xbfff,
write8smo_delegate(*this, FUNC(mbc3_device::write_rtc)));
// if real-time clock crystal is present, start it ticking
if (m_has_rtc_xtal)
{
logerror("Real-time clock crystal present, starting timer\n");
m_timer_rtc->adjust(attotime(1, 0), 0, attotime(1, 0));
}
// all good
return image_init_result::PASS;
};
protected:
virtual void device_start() override ATTR_COLD
{
rom_mbc_device_base::device_start();
m_timer_rtc = timer_alloc(FUNC(mbc3_device::rtc_advance_seconds), this);
save_item(NAME(m_rtc_regs));
save_item(NAME(m_rtc_enable));
save_item(NAME(m_rtc_select));
save_item(NAME(m_rtc_latch));
}
virtual void device_reset() override ATTR_COLD
{
rom_mbc_device_base::device_reset();
m_rtc_enable = 0U;
m_rtc_select = 0U;
m_rtc_latch = 0U;
set_bank_rom_coarse(0);
set_bank_rom_fine(1);
set_ram_enable(false);
set_bank_ram(0);
}
virtual void rtc_clock_updated(
int year,
int month,
int day,
int day_of_week,
int hour,
int minute,
int second) override ATTR_COLD
{
if (!m_has_rtc_xtal && !m_has_battery)
{
logerror("No real-time clock crystal or no battery present, not updating for elapsed time\n");
}
else if (std::numeric_limits<s64>::min() == m_machine_seconds)
{
logerror("Failed to load machine time from previous session, not updating for elapsed time\n");
}
else if (BIT(m_rtc_regs[0][4], 6))
{
logerror("Real-time clock halted, not updating for elapsed time\n");
}
else
{
// do a simple seconds elapsed since last run calculation
system_time current;
machine().current_datetime(current);
s64 delta(std::make_signed_t<decltype(current.time)>(current.time) - m_machine_seconds);
logerror("Previous session time, %d current time %d, delta %d\n", current.time, m_machine_seconds, delta);
if (0 > delta)
{
// This happens if the user runs the emulation faster
// than real time, exits, and then starts again without
// waiting for the difference between emulated and real
// time to elapse.
logerror("Previous session ended in the future, not updating for elapsed time\n");
}
else
{
logerror(
"Time before applying delta %u %02u:%02u:%02u%s\n",
(u16(BIT(m_rtc_regs[0][4], 0)) << 8) | m_rtc_regs[0][3],
m_rtc_regs[0][2],
m_rtc_regs[0][1],
m_rtc_regs[0][0],
BIT(m_rtc_regs[0][4], 7) ? " (overflow)" : "");
// annoyingly, we can get two rollovers if we started with an invalid value
unsigned seconds(delta % 60);
delta /= 60;
if (60 <= m_rtc_regs[0][0])
{
m_rtc_regs[0][0] = 0U;
--seconds;
++delta;
}
if (60 <= (m_rtc_regs[0][0] + seconds))
++delta;
m_rtc_regs[0][0] = (m_rtc_regs[0][0] + seconds) % 60;
// minutes is the same
unsigned minutes(delta % 60);
delta /= 60;
if (60 <= m_rtc_regs[0][1])
{
m_rtc_regs[0][1] = 0U;
--minutes;
++delta;
}
if (60 <= (m_rtc_regs[0][1] + minutes))
++delta;
m_rtc_regs[0][1] = (m_rtc_regs[0][1] + minutes) % 60;
// hours just has a different rollover point
unsigned hours(delta % 24);
delta /= 24;
if (24 <= m_rtc_regs[0][2])
{
m_rtc_regs[0][2] = 0U;
--hours;
++delta;
}
if (24 <= (m_rtc_regs[0][2] + hours))
++delta;
m_rtc_regs[0][2] = (m_rtc_regs[0][2] + hours) % 24;
// days has simple binary rollover
unsigned days(delta % 256);
if (256 <= (m_rtc_regs[0][3] + days))
++delta;
m_rtc_regs[0][3] += days;
// set overflow flag if appropriate
if ((1 < delta) || (BIT(m_rtc_regs[0][4], 0) && delta))
m_rtc_regs[0][4] |= 0x80;
m_rtc_regs[0][4] ^= BIT(delta, 0);
logerror(
"Time after applying delta %u %02u:%02u:%02u%s\n",
(u16(BIT(m_rtc_regs[0][4], 0)) << 8) | m_rtc_regs[0][3],
m_rtc_regs[0][2],
m_rtc_regs[0][1],
m_rtc_regs[0][0],
BIT(m_rtc_regs[0][4], 7) ? " (overflow)" : "");
}
}
}
virtual void nvram_default() override ATTR_COLD
{
// TODO: proper cold RTC state
m_machine_seconds = std::numeric_limits<s64>::min();
for (unsigned i = 0U; std::size(m_rtc_regs[0]) > i; ++i)
m_rtc_regs[0][i] = RTC_MASK[i];
}
virtual bool nvram_read(util::read_stream &file) override ATTR_COLD
{
if (m_has_battery)
{
// read previous machine time (seconds since epoch) and RTC registers
u64 seconds;
std::size_t actual;
if (file.read(&seconds, sizeof(seconds), actual) || (sizeof(seconds) != actual))
return false;
m_machine_seconds = big_endianize_int64(seconds);
if (file.read(&m_rtc_regs[0][0], sizeof(m_rtc_regs[0]), actual) || (sizeof(m_rtc_regs[0]) != actual))
return false;
}
else
{
logerror("No battery present, not loading real-time clock register contents\n");
}
return true;
}
virtual bool nvram_write(util::write_stream &file) override ATTR_COLD
{
// save current machine time as seconds since epoch and RTC registers
system_time current;
machine().current_datetime(current);
u64 const seconds(big_endianize_int64(s64(std::make_signed_t<decltype(current.time)>(current.time))));
std::size_t written;
if (file.write(&seconds, sizeof(seconds), written) || (sizeof(seconds) != written))
return false;
if (file.write(&m_rtc_regs[0][0], sizeof(m_rtc_regs[0]), written) || (sizeof(m_rtc_regs[0]) != written))
return false;
return true;
}
virtual bool nvram_can_write() const override ATTR_COLD
{
return m_has_battery;
}
private:
static inline constexpr u8 RTC_MASK[]{ 0x3f, 0x3f, 0x1f, 0xff, 0xc1 };
static inline constexpr u8 RTC_ROLLOVER[]{ 0x3c, 0x3c, 0x18, 0x00, 0x00 };
TIMER_CALLBACK_MEMBER(rtc_advance_seconds)
{
if (BIT(m_rtc_regs[0][4], 6))
return;
if (rtc_increment(0))
return;
if (rtc_increment(1))
return;
if (rtc_increment(2))
return;
if (++m_rtc_regs[0][3])
return;
if (BIT(m_rtc_regs[0][4], 0))
{
LOG("Day counter overflow");
m_rtc_regs[0][4] |= 0x80;
}
m_rtc_regs[0][4] ^= 0x01;
}
void enable_ram_rtc(u8 data)
{
m_rtc_enable = (0x0a == (data & 0x0f)) ? 1U : 0U;
if (!m_rtc_enable)
{
set_ram_enable(false);
}
else if (BIT(m_rtc_select, 3))
{
LOG(
"%s: RTC register %u enabled\n",
machine().describe_context(),
m_rtc_select & 0x07);
set_view_aux(0);
}
else
{
set_ram_enable(true);
}
}
void bank_switch_fine(u8 data)
{
data &= 0x7f;
set_bank_rom_fine(data ? data : 1);
}
void select_ram_rtc(u8 data)
{
// TODO: what happens with the RAM bank outputs when the RTC is selected?
// TODO: what happens for 4-7?
// TODO: is the high nybble ignored altogether?
bank_switch_coarse(data & 0x07);
m_rtc_select = data;
if (m_rtc_enable)
{
if (BIT(data, 3))
{
LOG(
"%s: RTC register %u enabled\n",
machine().describe_context(),
data & 0x07);
set_view_aux(0);
}
else
{
set_ram_enable(true);
}
}
}
void latch_rtc(u8 data)
{
// FIXME: does it just check the least significant bit, or does it look for 0x00 and 0x01?
LOG("Latch RTC 0x%02X -> 0x%02X\n", m_rtc_latch, data);
if (!BIT(m_rtc_latch, 0) && BIT(data, 0))
{
LOG("%s: Latching RTC registers\n", machine().describe_context());
std::copy(std::begin(m_rtc_regs[0]), std::end(m_rtc_regs[0]), std::begin(m_rtc_regs[1]));
}
m_rtc_latch = data;
}
u8 read_rtc(address_space &space)
{
u8 const reg(m_rtc_select & 0x07);
if (std::size(m_rtc_regs[1]) > reg)
{
LOG(
"%s: Read RTC register %u = 0x%02X\n",
machine().describe_context(),
reg,
m_rtc_regs[1][reg]);
return m_rtc_regs[1][reg];
}
else
{
LOG(
"%s: Read invalid RTC register %u\n",
machine().describe_context(),
reg);
return space.unmap();
}
}
void write_rtc(u8 data)
{
u8 const reg(m_rtc_select & 0x07);
if (std::size(m_rtc_regs[0]) > reg)
{
LOG(
"%s: Write RTC register %u = 0x%02X\n",
machine().describe_context(),
reg,
data);
if (4U == reg)
{
// TODO: are bits 5-1 physically present, and if not, what do they read as?
// TODO: how does halting the RTC interact with the prescaler?
data &= 0xc1;
m_rtc_regs[0][reg] = data;
}
else
{
m_rtc_regs[0][reg] = data;
}
}
else
{
LOG(
"%s: Write invalid RTC register %u = 0x%02X\n",
machine().describe_context(),
reg,
data);
}
}
u8 rtc_increment(unsigned index)
{
m_rtc_regs[0][index] = (m_rtc_regs[0][index] + 1) & RTC_MASK[index];
if (RTC_ROLLOVER[index] == (m_rtc_regs[0][index] & RTC_ROLLOVER[index]))
m_rtc_regs[0][index] = 0U;
return m_rtc_regs[0][index];
}
emu_timer *m_timer_rtc;
s64 m_machine_seconds;
bool m_has_rtc_xtal;
bool m_has_battery;
u8 m_rtc_regs[2][5];
u8 m_rtc_enable;
u8 m_rtc_select;
u8 m_rtc_latch;
};
//**************************************************************************
// MBC5 (max 128 MiB ROM, max 128 KiB SRAM)
//**************************************************************************
@ -1896,7 +1354,6 @@ private:
// device type definition
DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_MBC1, device_gb_cart_interface, bus::gameboy::mbc1_device, "gb_rom_mbc1", "Game Boy MBC1 Cartridge")
DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_MBC3, device_gb_cart_interface, bus::gameboy::mbc3_device, "gb_rom_mbc3", "Game Boy MBC3/MBC30 Cartridge")
DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_MBC5, device_gb_cart_interface, bus::gameboy::mbc5_device, "gb_rom_mbc5", "Game Boy MBC5 Cartridge")
DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_SINTAX, device_gb_cart_interface, bus::gameboy::sintax_device, "gb_rom_sintax", "Game Boy Sintax MBC5 Cartridge")
DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_CHONGWU, device_gb_cart_interface, bus::gameboy::chongwu_device, "gb_rom_chongwu", "Game Boy Chongwu Xiao Jingling Pokemon Pikecho Cartridge")

View File

@ -9,7 +9,6 @@
DECLARE_DEVICE_TYPE(GB_ROM_MBC1, device_gb_cart_interface)
DECLARE_DEVICE_TYPE(GB_ROM_MBC3, device_gb_cart_interface)
DECLARE_DEVICE_TYPE(GB_ROM_MBC5, device_gb_cart_interface)
DECLARE_DEVICE_TYPE(GB_ROM_MMM01, device_gb_cart_interface)
DECLARE_DEVICE_TYPE(GB_ROM_SINTAX, device_gb_cart_interface)

View File

@ -0,0 +1,721 @@
// license:BSD-3-Clause
// copyright-holders:Vas Crabb
/***************************************************************************
Nintendo Game Boy Memory Bank Controller 3
Includes a real-time clock in addition to the memory controller features.
The real-time clock can be used with or without a backup battery. Without
a backup battery, it just keeps time while the game is running. Several
games also used this memory controller without a real-time clock crystal.
The MBC3 supports up to 2 MiB ROM (128 16 KiB pages) and 32 KiB static RAM
(4 8 KiB pages). The MBC30 supports up to 4 MiB ROM (256 16 KiB pages) and
64 KiB static RAM (8 8 KiB pages). If RAM bank lines are used for coarse
ROM banking, up to 8 MiB ROM (512 16 KiB pages) can be supported by the
MBC3, and up to 32 MiB ROM (2048 16 KiB pages) can be supported by the
MBC30.
The MBC30 was only used for a single game. In practice, 128K*8 static RAM
chips were used, but only half the space is accessible as the MBC30 chip
only has three RAM bank output lines.
0x0000-3FFF R - Fixed ROM bank, always first page of ROM.
0x4000-7FFF R - Selectable ROM bank, page 0-127 (MBC) or 0-255 (MBC30) of
ROM.
0xA000-BFFF RW - Static RAM or real-time clock registers.
0x0000-1FFF W - I/O enable - write 0x0A on low nybble to enable static RAM
or real-time clock registers, any other value to disable.
0x2000-3FFF W - Select ROM page mapped at 0x4000. Bit 7 is ignored by
MBC3; all bits are significant for MBC30 Writing 0 selects
page 1.
0x4000-5FFF W - Select RAM page or real-time clock register mapped at
0xA000 (if enabled). If bit 3 is clear, static RAM is
selected; if bit 3 is set, real-time clock registers are
selected. Bits 2-0 select the RAM page or register.
0x6000-7FFF W - Write 0x00 followed by 0x01 to latch real-time clock
registers.
Real-time clock registers:
0x0 - Seconds.
0x1 - Minutes.
0x2 - Hours.
0x3 - Bits 7-0 of days.
0x4 - X------- Day counter overflow (set on carry out of bit 8, cleared
manually).
-X------ Set to halt real-time clock.
-------X Bit 8 of days.
TODO:
* How are the static RAM bank outputs set when real-time clock registers
are selected?
* What happens if invalid real-time clock registers 5-7 are selected?
* How do invalid seconds, minutes and hours values roll over?
* Does MBC30 really have eight ROM bank outputs? The one game using it
only uses seven.
***************************************************************************/
#include "emu.h"
#include "mbc3.h"
#include "cartbase.ipp"
#include "cartheader.h"
#include "gbxfile.h"
#include "dirtc.h"
#include "corestr.h"
#include <string>
//#define VERBOSE 1
//#define LOG_OUTPUT_FUNC osd_printf_info
#include "logmacro.h"
namespace bus::gameboy {
namespace {
//**************************************************************************
// Class declarations
//**************************************************************************
class mbc3_device_base : public mbc_ram_device_base<mbc_dual_device_base>, public device_rtc_interface, public device_nvram_interface
{
protected:
mbc3_device_base(machine_config const &mconfig, device_type type, char const *tag, device_t *owner, u32 clock);
virtual void device_start() override ATTR_COLD;
virtual void device_reset() override ATTR_COLD;
virtual void rtc_clock_updated(int year, int month, int day, int day_of_week, int hour, int minute, int second) override ATTR_COLD;
virtual void nvram_default() override ATTR_COLD;
virtual bool nvram_read(util::read_stream &file) override ATTR_COLD;
virtual bool nvram_write(util::write_stream &file) override ATTR_COLD;
virtual bool nvram_can_write() const override ATTR_COLD;
bool install_memory(std::string &message, unsigned highbits, unsigned lowbits) ATTR_COLD;
private:
static inline constexpr u8 RTC_MASK[]{ 0x3f, 0x3f, 0x1f, 0xff, 0xc1 };
static inline constexpr u8 RTC_ROLLOVER[]{ 0x3c, 0x3c, 0x18, 0x00, 0x00 };
TIMER_CALLBACK_MEMBER(rtc_advance_seconds);
void enable_ram_rtc(u8 data);
void bank_switch_fine(u8 data);
void select_ram_rtc(u8 data);
void latch_rtc(u8 data);
u8 read_rtc(address_space &space);
void write_rtc(u8 data);
u8 rtc_increment(unsigned index)
{
m_rtc_regs[0][index] = (m_rtc_regs[0][index] + 1) & RTC_MASK[index];
if (RTC_ROLLOVER[index] == (m_rtc_regs[0][index] & RTC_ROLLOVER[index]))
m_rtc_regs[0][index] = 0U;
return m_rtc_regs[0][index];
}
memory_view m_view_ram;
emu_timer *m_timer_rtc;
s64 m_machine_seconds;
bool m_has_rtc_xtal;
bool m_has_battery;
u8 m_rtc_regs[2][5];
u8 m_rtc_enable;
u8 m_rtc_select;
u8 m_rtc_latch;
};
class mbc3_device : public mbc3_device_base
{
public:
mbc3_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock);
virtual image_init_result load(std::string &message) override ATTR_COLD;
};
class mbc30_device : public mbc3_device_base
{
public:
mbc30_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock);
virtual image_init_result load(std::string &message) override ATTR_COLD;
};
//**************************************************************************
// mbc3_device_base
//**************************************************************************
mbc3_device_base::mbc3_device_base(
machine_config const &mconfig,
device_type type,
char const *tag,
device_t *owner,
u32 clock) :
mbc_ram_device_base<mbc_dual_device_base>(mconfig, type, tag, owner, clock),
device_rtc_interface(mconfig, *this),
device_nvram_interface(mconfig, *this),
m_view_ram(*this, "ram"),
m_timer_rtc(nullptr),
m_machine_seconds(0),
m_has_rtc_xtal(false),
m_has_battery(false),
m_rtc_enable(0U),
m_rtc_select(0U),
m_rtc_latch(0U)
{
}
bool mbc3_device_base::install_memory(
std::string &message,
unsigned highbits,
unsigned lowbits)
{
// check for RTC oscillator and backup battery
if (loaded_through_softlist())
{
// there's a feature tag indicating presence or absence of RTC crystal
char const *const rtcfeature(get_feature("rtc"));
if (rtcfeature)
{
// explicitly specified in software list
if (util::streqlower(rtcfeature, "yes") || util::streqlower(rtcfeature, "true"))
{
logerror("Real-time clock crystal present\n");
m_has_rtc_xtal = true;
}
else if (util::streqlower(rtcfeature, "no") || util::streqlower(rtcfeature, "false"))
{
logerror("No real-time clock crystal present\n");
m_has_rtc_xtal = false;
}
else
{
message = "Invalid 'rtc' feature value (must be yes or no)";
return false;
}
}
else
{
logerror("No 'rtc' feature found, assuming no real-time clock crystal present\n");
m_has_rtc_xtal = false;
}
// if there's an NVRAM region, there must be a backup battery
if (cart_nvram_region())
{
logerror("Found 'nvram' region, backup battery must be present\n");
m_has_battery = true;
}
else
{
logerror("No 'nvram' region found, assuming no backup battery present\n");
m_has_battery = true;
}
}
else
{
gbxfile::leader_1_0 leader;
u8 const *extra;
u32 extralen;
if (gbxfile::get_data(gbx_footer_region(), leader, extra, extralen))
{
m_has_rtc_xtal = bool(leader.rtc);
m_has_battery = bool(leader.batt);
logerror(
"GBX format image specifies %sreal-time clock crystal present, %sbackup battery present\n",
m_has_rtc_xtal ? "" : "no ",
m_has_battery ? "" : "no ");
}
else
{
// try probing the header
memory_region *const romregion(cart_rom_region());
if (romregion && (romregion->bytes() > cartheader::OFFSET_TYPE))
{
u8 const carttype((&romregion->as_u8())[cartheader::OFFSET_TYPE]);
switch (carttype)
{
case cartheader::TYPE_MBC3_RTC_BATT:
case cartheader::TYPE_MBC3_RTC_RAM_BATT:
m_has_rtc_xtal = true;
m_has_battery = true;
break;
case cartheader::TYPE_MBC3:
m_has_rtc_xtal = false;
m_has_battery = false;
break;
case cartheader::TYPE_MBC3_RAM:
case cartheader::TYPE_MBC3_RAM_BATT:
m_has_rtc_xtal = false;
m_has_battery = true;
break;
default:
osd_printf_warning(
"[%s] Unrecognized cartridge type 0x%02X in header, assuming no real-time clock crystal or backup battery present\n",
tag(),
carttype);
m_has_rtc_xtal = false;
m_has_battery = false;
}
logerror(
"Cartridge type 0x%02X in header, %sreal-time clock crystal present, %sbackup battery present\n",
carttype,
m_has_rtc_xtal ? "" : "no ",
m_has_battery ? "" : "no ");
}
}
}
// check for valid ROM/RAM regions
set_bank_bits_rom(highbits, lowbits);
set_bank_bits_ram(highbits);
if (!check_rom(message) || !check_ram(message))
return false;
// set up ROM and RAM
cart_space()->install_view(0xa000, 0xbfff, m_view_ram);
install_rom();
install_ram(m_view_ram[0]);
// install bank switching handlers
cart_space()->install_write_handler(
0x0000, 0x1fff,
write8smo_delegate(*this, FUNC(mbc3_device_base::enable_ram_rtc)));
cart_space()->install_write_handler(
0x2000, 0x3fff,
write8smo_delegate(*this, FUNC(mbc3_device_base::bank_switch_fine)));
cart_space()->install_write_handler(
0x4000, 0x5fff,
write8smo_delegate(*this, FUNC(mbc3_device_base::select_ram_rtc)));
cart_space()->install_write_handler(
0x6000, 0x7fff,
write8smo_delegate(*this, FUNC(mbc3_device_base::latch_rtc)));
// install real-time clock handlers
m_view_ram[1].install_read_handler(
0xa000, 0xbfff,
read8mo_delegate(*this, FUNC(mbc3_device_base::read_rtc)));
m_view_ram[1].install_write_handler(
0xa000, 0xbfff,
write8smo_delegate(*this, FUNC(mbc3_device_base::write_rtc)));
// if real-time clock crystal is present, start it ticking
if (m_has_rtc_xtal)
{
logerror("Real-time clock crystal present, starting timer\n");
m_timer_rtc->adjust(attotime(1, 0), 0, attotime(1, 0));
}
// all good
return true;
}
void mbc3_device_base::device_start()
{
mbc_ram_device_base<mbc_dual_device_base>::device_start();
m_timer_rtc = timer_alloc(FUNC(mbc3_device_base::rtc_advance_seconds), this);
save_item(NAME(m_rtc_regs));
save_item(NAME(m_rtc_enable));
save_item(NAME(m_rtc_select));
save_item(NAME(m_rtc_latch));
}
void mbc3_device_base::device_reset()
{
mbc_ram_device_base<mbc_dual_device_base>::device_reset();
m_rtc_enable = 0U;
m_rtc_select = 0U;
m_rtc_latch = 0U;
set_bank_rom_coarse(0);
set_bank_rom_fine(1);
set_bank_ram(0);
m_view_ram.disable();
}
void mbc3_device_base::rtc_clock_updated(
int year,
int month,
int day,
int day_of_week,
int hour,
int minute,
int second)
{
if (!m_has_rtc_xtal && !m_has_battery)
{
logerror("No real-time clock crystal or no battery present, not updating for elapsed time\n");
}
else if (std::numeric_limits<s64>::min() == m_machine_seconds)
{
logerror("Failed to load machine time from previous session, not updating for elapsed time\n");
}
else if (BIT(m_rtc_regs[0][4], 6))
{
logerror("Real-time clock halted, not updating for elapsed time\n");
}
else
{
// do a simple seconds elapsed since last run calculation
system_time current;
machine().current_datetime(current);
s64 delta(std::make_signed_t<decltype(current.time)>(current.time) - m_machine_seconds);
logerror("Previous session time, %d current time %d, delta %d\n", current.time, m_machine_seconds, delta);
if (0 > delta)
{
// This happens if the user runs the emulation faster
// than real time, exits, and then starts again without
// waiting for the difference between emulated and real
// time to elapse.
logerror("Previous session ended in the future, not updating for elapsed time\n");
}
else
{
logerror(
"Time before applying delta %u %02u:%02u:%02u%s\n",
(u16(BIT(m_rtc_regs[0][4], 0)) << 8) | m_rtc_regs[0][3],
m_rtc_regs[0][2],
m_rtc_regs[0][1],
m_rtc_regs[0][0],
BIT(m_rtc_regs[0][4], 7) ? " (overflow)" : "");
// annoyingly, we can get two rollovers if we started with an invalid value
unsigned seconds(delta % 60);
delta /= 60;
if (60 <= m_rtc_regs[0][0])
{
m_rtc_regs[0][0] = 0U;
--seconds;
++delta;
}
if (60 <= (m_rtc_regs[0][0] + seconds))
++delta;
m_rtc_regs[0][0] = (m_rtc_regs[0][0] + seconds) % 60;
// minutes is the same
unsigned minutes(delta % 60);
delta /= 60;
if (60 <= m_rtc_regs[0][1])
{
m_rtc_regs[0][1] = 0U;
--minutes;
++delta;
}
if (60 <= (m_rtc_regs[0][1] + minutes))
++delta;
m_rtc_regs[0][1] = (m_rtc_regs[0][1] + minutes) % 60;
// hours just has a different rollover point
unsigned hours(delta % 24);
delta /= 24;
if (24 <= m_rtc_regs[0][2])
{
m_rtc_regs[0][2] = 0U;
--hours;
++delta;
}
if (24 <= (m_rtc_regs[0][2] + hours))
++delta;
m_rtc_regs[0][2] = (m_rtc_regs[0][2] + hours) % 24;
// days has simple binary rollover
unsigned days(delta % 256);
if (256 <= (m_rtc_regs[0][3] + days))
++delta;
m_rtc_regs[0][3] += days;
// set overflow flag if appropriate
if ((1 < delta) || (BIT(m_rtc_regs[0][4], 0) && delta))
m_rtc_regs[0][4] |= 0x80;
m_rtc_regs[0][4] ^= BIT(delta, 0);
logerror(
"Time after applying delta %u %02u:%02u:%02u%s\n",
(u16(BIT(m_rtc_regs[0][4], 0)) << 8) | m_rtc_regs[0][3],
m_rtc_regs[0][2],
m_rtc_regs[0][1],
m_rtc_regs[0][0],
BIT(m_rtc_regs[0][4], 7) ? " (overflow)" : "");
}
}
}
void mbc3_device_base::nvram_default()
{
// TODO: proper cold RTC state
m_machine_seconds = std::numeric_limits<s64>::min();
for (unsigned i = 0U; std::size(m_rtc_regs[0]) > i; ++i)
m_rtc_regs[0][i] = RTC_MASK[i];
}
bool mbc3_device_base::nvram_read(util::read_stream &file)
{
if (m_has_battery)
{
// read previous machine time (seconds since epoch) and RTC registers
u64 seconds;
std::size_t actual;
if (file.read(&seconds, sizeof(seconds), actual) || (sizeof(seconds) != actual))
return false;
m_machine_seconds = big_endianize_int64(seconds);
if (file.read(&m_rtc_regs[0][0], sizeof(m_rtc_regs[0]), actual) || (sizeof(m_rtc_regs[0]) != actual))
return false;
}
else
{
logerror("No battery present, not loading real-time clock register contents\n");
}
return true;
}
bool mbc3_device_base::nvram_write(util::write_stream &file)
{
// save current machine time as seconds since epoch and RTC registers
system_time current;
machine().current_datetime(current);
u64 const seconds(big_endianize_int64(s64(std::make_signed_t<decltype(current.time)>(current.time))));
std::size_t written;
if (file.write(&seconds, sizeof(seconds), written) || (sizeof(seconds) != written))
return false;
if (file.write(&m_rtc_regs[0][0], sizeof(m_rtc_regs[0]), written) || (sizeof(m_rtc_regs[0]) != written))
return false;
return true;
}
bool mbc3_device_base::nvram_can_write() const
{
return m_has_battery;
}
TIMER_CALLBACK_MEMBER(mbc3_device_base::rtc_advance_seconds)
{
if (BIT(m_rtc_regs[0][4], 6))
return;
if (rtc_increment(0))
return;
if (rtc_increment(1))
return;
if (rtc_increment(2))
return;
if (++m_rtc_regs[0][3])
return;
if (BIT(m_rtc_regs[0][4], 0))
{
LOG("Day counter overflow");
m_rtc_regs[0][4] |= 0x80;
}
m_rtc_regs[0][4] ^= 0x01;
}
void mbc3_device_base::enable_ram_rtc(u8 data)
{
m_rtc_enable = (0x0a == (data & 0x0f)) ? 1U : 0U;
if (!m_rtc_enable)
{
LOG(
"%s: Cartridge RAM and RTC registers disabled\n",
machine().describe_context());
m_view_ram.disable();
}
else if (BIT(m_rtc_select, 3))
{
LOG(
"%s: RTC register %u enabled\n",
machine().describe_context(),
m_rtc_select & 0x07);
m_view_ram.select(1);
}
else
{
LOG("%s: Cartridge RAM enabled\n", machine().describe_context());
m_view_ram.select(0);
}
}
void mbc3_device_base::bank_switch_fine(u8 data)
{
data &= 0x7f;
set_bank_rom_fine(data ? data : 1);
}
void mbc3_device_base::select_ram_rtc(u8 data)
{
// TODO: what happens with the RAM bank outputs when the RTC is selected?
// TODO: what happens if RTC register 5-7 is selected?
// TODO: is the high nybble ignored altogether?
set_bank_rom_coarse(data & 0x07);
set_bank_ram(data & 0x07);
m_rtc_select = data;
if (m_rtc_enable)
{
if (BIT(data, 3))
{
LOG(
"%s: RTC register %u enabled\n",
machine().describe_context(),
data & 0x07);
m_view_ram.select(1);
}
else
{
LOG("%s: Cartridge RAM enabled\n", machine().describe_context());
m_view_ram.select(0);
}
}
}
void mbc3_device_base::latch_rtc(u8 data)
{
// FIXME: does it just check the least significant bit, or does it look for 0x00 and 0x01?
LOG("Latch RTC 0x%02X -> 0x%02X\n", m_rtc_latch, data);
if (!BIT(m_rtc_latch, 0) && BIT(data, 0))
{
LOG("%s: Latching RTC registers\n", machine().describe_context());
std::copy(std::begin(m_rtc_regs[0]), std::end(m_rtc_regs[0]), std::begin(m_rtc_regs[1]));
}
m_rtc_latch = data;
}
u8 mbc3_device_base::read_rtc(address_space &space)
{
u8 const reg(m_rtc_select & 0x07);
if (std::size(m_rtc_regs[1]) > reg)
{
LOG(
"%s: Read RTC register %u = 0x%02X\n",
machine().describe_context(),
reg,
m_rtc_regs[1][reg]);
return m_rtc_regs[1][reg];
}
else
{
LOG(
"%s: Read invalid RTC register %u\n",
machine().describe_context(),
reg);
return space.unmap();
}
}
void mbc3_device_base::write_rtc(u8 data)
{
u8 const reg(m_rtc_select & 0x07);
if (std::size(m_rtc_regs[0]) > reg)
{
LOG(
"%s: Write RTC register %u = 0x%02X\n",
machine().describe_context(),
reg,
data);
if (4U == reg)
{
// TODO: are bits 5-1 physically present, and if not, what do they read as?
// TODO: how does halting the RTC interact with the prescaler?
data &= 0xc1;
m_rtc_regs[0][reg] = data;
}
else
{
m_rtc_regs[0][reg] = data;
}
}
else
{
LOG(
"%s: Write invalid RTC register %u = 0x%02X\n",
machine().describe_context(),
reg,
data);
}
}
//**************************************************************************
// mbc3_device
//**************************************************************************
mbc3_device::mbc3_device(
machine_config const &mconfig,
char const *tag,
device_t *owner,
u32 clock) :
mbc3_device_base(mconfig, GB_ROM_MBC3, tag, owner, clock)
{
}
image_init_result mbc3_device::load(std::string &message)
{
if (!install_memory(message, 2, 7))
return image_init_result::FAIL;
else
return image_init_result::PASS;
}
//**************************************************************************
// mbc30_device
//**************************************************************************
mbc30_device::mbc30_device(
machine_config const &mconfig,
char const *tag,
device_t *owner,
u32 clock) :
mbc3_device_base(mconfig, GB_ROM_MBC30, tag, owner, clock)
{
}
image_init_result mbc30_device::load(std::string &message)
{
if (!install_memory(message, 3, 8))
return image_init_result::FAIL;
else
return image_init_result::PASS;
}
} // anonymous namespace
} // namespace bus::gameboy
DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_MBC3, device_gb_cart_interface, bus::gameboy::mbc3_device, "gb_rom_mbc3", "Game Boy MBC3 Cartridge")
DEFINE_DEVICE_TYPE_PRIVATE(GB_ROM_MBC30, device_gb_cart_interface, bus::gameboy::mbc30_device, "gb_rom_mbc30", "Game Boy MBC30 Cartridge")

View File

@ -0,0 +1,19 @@
// license:BSD-3-Clause
// copyright-holders:Vas Crabb
/***************************************************************************
Nintendo Game Boy Memory Bank Controller 3
***************************************************************************/
#ifndef MAME_BUS_GAMEBOY_MBC3_H
#define MAME_BUS_GAMEBOY_MBC3_H
#pragma once
#include "slot.h"
DECLARE_DEVICE_TYPE(GB_ROM_MBC3, device_gb_cart_interface)
DECLARE_DEVICE_TYPE(GB_ROM_MBC30, device_gb_cart_interface)
#endif // MAME_BUS_GAMEBOY_MBC3_H

View File

@ -66,7 +66,7 @@
incorrectly reported for writes.
* Due to the pipelining of writes, an interrupt may be accepted during
an instruction which attempts to disable interrupts by setting the
IEMASK bit, which may be cleared insted when control reaches the next
IEMASK bit, which may be cleared instead when control reaches the next
sequential instruction from a RETI. This sequencing glitch is
documented in the MN187XX23 user's manual, along with a failsafe way
of setting IEMASK, and similar workarounds in extant program code

View File

@ -1194,7 +1194,7 @@ ROM_START( tmek20 )
ROM_END
ROM_START( primrage ) // still shows 'version 2.3' on the title screen but build is newer than the primrage set
ROM_START( primrage ) // still shows 'version 2.3' on the title screen but build is newer than the primrageo set
ROM_REGION( 0x200000, "maincpu", 0 ) /* 8*64k for 68000 code, differ from the primrageo set */
ROM_LOAD32_BYTE( "rage_136102-2044a_pgmuu.29l", 0x000000, 0x80000, CRC(85556b91) SHA1(5f4f5d0bf68bd17b7bff230b521a5dcfff414a50) )
ROM_LOAD32_BYTE( "rage_136102-2043a_pgmum.28l", 0x000001, 0x80000, CRC(4d3414d0) SHA1(b6465c0fbee4e67f74185e9ea048e40f4f443efa) )

View File

@ -54,6 +54,8 @@ protected:
virtual void video_start() override;
private:
static constexpr XTAL MASTER_CLOCK = 12_MHz_XTAL;
uint32_t screen_update_armchamp(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect);
void main_prg(address_map &map);
@ -87,8 +89,6 @@ private:
uint8_t m_adpcm_byte = 0;
uint8_t m_msm5205_vclk_toggle = 0;
uint16_t m_paletteram[0x100] = { };
static constexpr XTAL MASTER_CLOCK = 12_MHz_XTAL;
};
@ -196,7 +196,7 @@ void armchamp_state::io1_w(uint8_t data)
// .xxx x... - Tile ROM bank
// x... .... - Speech ROM banking enable
m_rombank->set_entry(((data & 0x80) >> 5) | (data & 3));
m_rombank->set_entry(bitswap<3>(data, 7, 1, 0));
if ((m_io1 ^ data) & 0x78)
{