diff options
author | 2015-11-08 12:56:12 +0100 | |
---|---|---|
committer | 2015-11-08 12:56:12 +0100 | |
commit | 7c19aac60e12d6f5ea301bdb34d7826a01e0b06f (patch) | |
tree | f310d86aa2c6bfc19d115307dedde4eb0cd52dad /src/devices/bus/nes/mmc5.cpp | |
parent | a57b46ae933badd7441ce1644711dbb851e2b504 (diff) |
Rename *.c -> *.cpp in our source (nw)
Diffstat (limited to 'src/devices/bus/nes/mmc5.cpp')
-rw-r--r-- | src/devices/bus/nes/mmc5.cpp | 679 |
1 files changed, 679 insertions, 0 deletions
diff --git a/src/devices/bus/nes/mmc5.cpp b/src/devices/bus/nes/mmc5.cpp new file mode 100644 index 00000000000..995b71080a7 --- /dev/null +++ b/src/devices/bus/nes/mmc5.cpp @@ -0,0 +1,679 @@ +// license:BSD-3-Clause +// copyright-holders:Fabio Priuli +/*********************************************************************************************************** + + + NES/Famicom cartridge emulation for Nintendo MMC-5 / ExROM + + + Here we emulate the Nintendo MMC-5 / ExROM PCBs [mapper 5] + + + TODO: + - improve PPU code in order to support the two sets of registers used by MMC5 + + ***********************************************************************************************************/ + + +#include "emu.h" +#include "mmc5.h" + +#include "cpu/m6502/m6502.h" +#include "video/ppu2c0x.h" // this has to be included so that IRQ functions can access PPU_BOTTOM_VISIBLE_SCANLINE +#include "sound/nes_apu.h" // temp hack to pass the additional sound regs to APU... + + +#ifdef NES_PCB_DEBUG +#define VERBOSE 1 +#else +#define VERBOSE 0 +#endif + +#define LOG_MMC(x) do { if (VERBOSE) logerror x; } while (0) + + +#define LAST_CHR_REG_A 0 +#define LAST_CHR_REG_B 1 + +static const int m_mmc5_attrib[4] = {0x00, 0x55, 0xaa, 0xff}; + +//------------------------------------------------- +// constructor +//------------------------------------------------- + +const device_type NES_EXROM = &device_creator<nes_exrom_device>; + + +nes_exrom_device::nes_exrom_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : nes_nrom_device(mconfig, NES_EXROM, "NES Cart ExROM (MMC-5) PCB", tag, owner, clock, "nes_exrom", __FILE__) +{ +} + + +void nes_exrom_device::device_start() +{ + common_start(); + save_item(NAME(m_irq_count)); + save_item(NAME(m_irq_status)); + save_item(NAME(m_irq_enable)); + save_item(NAME(m_mult1)); + save_item(NAME(m_mult2)); + save_item(NAME(m_vrom_page_a)); + save_item(NAME(m_vrom_page_b)); + save_item(NAME(m_floodtile)); + save_item(NAME(m_floodattr)); + save_item(NAME(m_prg_mode)); + save_item(NAME(m_chr_mode)); + save_item(NAME(m_wram_base)); + save_item(NAME(m_wram_protect_1)); + save_item(NAME(m_wram_protect_2)); + save_item(NAME(m_vrom_bank)); + save_item(NAME(m_last_chr)); + save_item(NAME(m_ex1_chr)); + save_item(NAME(m_split_chr)); + save_item(NAME(m_prg_regs)); + save_item(NAME(m_prg_ram_mapped)); + save_item(NAME(m_ex1_bank)); + save_item(NAME(m_high_chr)); + save_item(NAME(m_split_scr)); + save_item(NAME(m_split_rev)); + save_item(NAME(m_split_ctrl)); + save_item(NAME(m_split_yst)); + save_item(NAME(m_split_bank)); + save_item(NAME(m_vcount)); + save_item(NAME(m_exram)); + save_item(NAME(m_ram_hi_banks)); + +} + +void nes_exrom_device::pcb_reset() +{ + m_chr_source = m_vrom_chunks ? CHRROM : CHRRAM; + prg16_89ab(m_prg_chunks - 2); + prg16_cdef(m_prg_chunks - 1); + chr8(0, m_chr_source); + + m_irq_count = 0; + m_irq_status = 0; + m_irq_enable = 0; + + m_mult1 = m_mult2 = 0; + m_vrom_page_a = m_vrom_page_b = 0; + + m_floodtile = m_floodattr = 0; + + m_prg_mode = 3; + m_chr_mode = 0; + m_wram_base = 0; + m_wram_protect_1 = 0; + m_wram_protect_2 = 0; + m_high_chr = 0; + m_split_scr = 0; + m_split_rev = 0; + m_split_ctrl = 0; + m_split_yst = 0; + m_split_bank = 0; + m_last_chr = LAST_CHR_REG_A; + m_ex1_chr = 0; + m_split_chr = 0; + m_ex1_bank = 0; + m_vcount = 0; + + for (int i = 0; i < 12; i++) + m_vrom_bank[i] = 0x3ff; + + m_prg_regs[0] = 0xfc; + m_prg_regs[1] = 0xfd; + m_prg_regs[2] = 0xfe; + m_prg_regs[3] = 0xff; + m_prg_ram_mapped[0] = 0; + m_prg_ram_mapped[1] = 0; + m_prg_ram_mapped[2] = 0; + m_prg_ram_mapped[3] = 0; + + m_ram_hi_banks[0] = 0; + m_ram_hi_banks[1] = 0; + m_ram_hi_banks[2] = 0; + m_ram_hi_banks[3] = 0; +} + + +/*------------------------------------------------- + mapper specific handlers + -------------------------------------------------*/ + +/*------------------------------------------------- + + ExROM (MMC5 based) board emulation + + Games: Castlevania III, Just Breed, many Koei titles + + iNES: mapper 5 + + MESS status: Partially supported + + -------------------------------------------------*/ + +void nes_exrom_device::update_prg() +{ + int bank0, bank1, bank2, bank3; + + switch (m_prg_mode) + { + case 0: // 32k banks + bank3 = m_prg_regs[3] >> 2; + prg32(bank3); + break; + + case 1: // 16k banks + bank1 = m_prg_regs[1] >> 1; + bank3 = m_prg_regs[3] >> 1; + + if (m_prg_ram_mapped[1]) + { + m_ram_hi_banks[0] = ((bank1 << 1) & 0x07); + m_ram_hi_banks[1] = ((bank1 << 1) & 0x07) | 1; + } + else + prg16_89ab(bank1); + + prg16_cdef(bank3); + break; + + case 2: // 16k-8k banks + bank1 = m_prg_regs[1] >> 1; + bank2 = m_prg_regs[2]; + bank3 = m_prg_regs[3]; + + if (m_prg_ram_mapped[1]) + { + m_ram_hi_banks[0] = ((bank1 << 1) & 0x07); + m_ram_hi_banks[1] = ((bank1 << 1) & 0x07) | 1; + } + else + prg16_89ab(bank1); + + if (m_prg_ram_mapped[2]) + m_ram_hi_banks[2] = (bank2 & 0x07); + else + prg8_cd(bank2); + + prg8_ef(bank3); + break; + + case 3: // 8k banks + bank0 = m_prg_regs[0]; + bank1 = m_prg_regs[1]; + bank2 = m_prg_regs[2]; + bank3 = m_prg_regs[3]; + + if (m_prg_ram_mapped[0]) + m_ram_hi_banks[0] = (bank0 & 0x07); + else + prg8_89(bank0); + + if (m_prg_ram_mapped[1]) + m_ram_hi_banks[1] = (bank1 & 0x07); + else + prg8_ab(bank1); + + if (m_prg_ram_mapped[2]) + m_ram_hi_banks[2] = (bank2 & 0x07); + else + prg8_cd(bank2); + + prg8_ef(bank3); + break; + } +} + +void nes_exrom_device::hblank_irq(int scanline, int vblank, int blanked ) +{ + m_vcount = scanline; + + if (scanline == m_irq_count) + { + if (m_irq_enable) + m_maincpu->set_input_line(M6502_IRQ_LINE, ASSERT_LINE); + + m_irq_status = 0xff; + } + + // "In Frame" flag + if (scanline == 0) + m_irq_status |= 0x40; + else if (scanline > PPU_BOTTOM_VISIBLE_SCANLINE) + m_irq_status &= ~0x40; +} + + +void nes_exrom_device::set_mirror(int page, int src) +{ + switch (src) + { + case 0: + set_nt_page(page, CIRAM, 0, 1); + break; + case 1: + set_nt_page(page, CIRAM, 1, 1); + break; + case 2: + set_nt_page(page, EXRAM, 0, 1); + break; + case 3: + set_nt_page(page, MMC5FILL, 0, 0); + break; + default: + fatalerror("This should never happen\n"); + } +} + +inline bool nes_exrom_device::in_split() +{ + ppu2c0x_device *ppu = machine().device<ppu2c0x_device>("ppu"); + int tile = ppu->get_tilenum(); + + if (tile < 34) + { + if (!m_split_rev && tile < m_split_ctrl) + return TRUE; + if (m_split_rev && tile >= m_split_ctrl) + return TRUE; + } + return FALSE; +} + +READ8_MEMBER(nes_exrom_device::nt_r) +{ + int page = ((offset & 0xc00) >> 10); + + switch (m_nt_src[page]) + { + case MMC5FILL: + if ((offset & 0x3ff) >= 0x3c0) + return m_floodattr; + return m_floodtile; + + case EXRAM: + // to investigate: can split screen affect this too? + if (!BIT(m_exram_control, 1)) + return m_exram[offset & 0x3ff]; + else + return 0x00; + + case CIRAM: + default: + // Uchuu Keibitai SDF uses extensively split screen for its intro, + // but it does not work yet + if (m_split_scr && !(m_exram_control & 0x02) && in_split()) + { + ppu2c0x_device *ppu = machine().device<ppu2c0x_device>("ppu"); + int tile = ppu->get_tilenum(); + + if ((offset & 0x3ff) >= 0x3c0) + { + int pos = (((m_split_yst + m_vcount) & ~0x1f) | (tile & 0x1f)) >> 2; + return m_exram[0x3c0 | pos]; + } + else + { + int pos = (((m_split_yst + m_vcount) & 0xf8) << 2) | (tile & 0x1f); + return m_exram[pos]; + } + } + + if (m_exram_control == 1) + { + if ((offset & 0x3ff) >= 0x3c0) + return m_mmc5_attrib[(m_exram[offset & 0x3ff] >> 6) & 0x03]; + else // in this case, we write Ex1 CHR bank, but then access NT normally! + { + m_ex1_chr = 1; + m_ex1_bank = (m_exram[offset & 0x3ff] & 0x3f) | (m_high_chr << 6); + } + } + return m_nt_access[page][offset & 0x3ff]; + } +} + +WRITE8_MEMBER(nes_exrom_device::nt_w) +{ + int page = ((offset & 0xc00) >> 10); + + if (!m_nt_writable[page]) + return; + + switch (m_nt_src[page]) + { + case EXRAM: + m_exram[offset & 0x3ff] = data; + break; + + case CIRAM: + default: + m_nt_access[page][offset & 0x3ff] = data; + break; + } +} + +inline UINT8 nes_exrom_device::base_chr_r(int bank, UINT32 offset) +{ + UINT32 helper = 0; + + switch (m_chr_mode) + { + case 0: + if (bank < 8) + helper = ((m_vrom_bank[bank | 7] & 0xff) * 0x2000) + (offset & 0x1fff); + else + helper = ((m_vrom_bank[bank | 3] & 0xff) * 0x2000) + (offset & 0xfff); + break; + case 1: + helper = ((m_vrom_bank[bank | 3] & 0xff) * 0x1000) + (offset & 0xfff); + break; + case 2: + helper = (m_vrom_bank[bank | 1] * 0x800) + (offset & 0x7ff); + break; + case 3: + helper = (m_vrom_bank[bank] * 0x400) + (offset & 0x3ff); + break; + } + + return m_vrom[helper & (m_vrom_size - 1)]; +} + +inline UINT8 nes_exrom_device::split_chr_r(UINT32 offset) +{ + UINT32 helper = (m_split_bank * 0x1000) + (offset & 0x3f8) + (m_split_yst & 7); + return m_vrom[helper & (m_vrom_size - 1)]; +} + +inline UINT8 nes_exrom_device::bg_ex1_chr_r(UINT32 offset) +{ + UINT32 helper = (m_ex1_bank * 0x1000) + (offset & 0xfff); + return m_vrom[helper & (m_vrom_size - 1)]; +} + +READ8_MEMBER(nes_exrom_device::chr_r) +{ + int bank = offset >> 10; + ppu2c0x_device *ppu = machine().device<ppu2c0x_device>("ppu"); + + // Extended Attribute Mode (Ex1) does affect BG drawing even for 8x16 sprites (JustBreed uses it extensively!) + // However, if a game enables Ex1 but does not write a new m_ex1_bank, I'm not sure here we get the correct behavior + if (m_exram_control == 1 && ppu->get_draw_phase() == PPU_DRAW_BG && m_ex1_chr) + return bg_ex1_chr_r(offset & 0xfff); + + if (m_split_scr && !(m_exram_control & 0x02) && in_split() && ppu->get_draw_phase() == PPU_DRAW_BG && m_split_chr) + return split_chr_r(offset & 0xfff); + + if (ppu->is_sprite_8x16()) + { + if (ppu->get_draw_phase() == PPU_DRAW_OAM) + return base_chr_r(bank & 7, offset & 0x1fff); + + if (ppu->get_draw_phase() == PPU_DRAW_BG) + return base_chr_r((bank & 3) + 8, offset & 0x1fff); + } + + if (m_last_chr == LAST_CHR_REG_A) + return base_chr_r(bank & 7, offset & 0x1fff); + else + return base_chr_r((bank & 3) + 8, offset & 0x1fff); +} + + +READ8_MEMBER(nes_exrom_device::read_l) +{ + int value; + LOG_MMC(("exrom read_l, offset: %04x\n", offset)); + offset += 0x100; + + if ((offset >= 0x1c00) && (offset <= 0x1fff)) + { + // EXRAM + if (BIT(m_exram_control, 1)) // Modes 2,3 = read + return m_exram[offset - 0x1c00]; + else + return m_open_bus; // Modes 0,1 = open bus + } + + switch (offset) + { + case 0x1204: + value = m_irq_status; + m_irq_status &= ~0x80; + m_maincpu->set_input_line(M6502_IRQ_LINE, CLEAR_LINE); + return value; + + case 0x1205: + return (m_mult1 * m_mult2) & 0xff; + case 0x1206: + return ((m_mult1 * m_mult2) & 0xff00) >> 8; + + default: + logerror("MMC5 uncaught read, offset: %04x\n", offset + 0x4100); + return m_open_bus; + } +} + + +WRITE8_MEMBER(nes_exrom_device::write_l) +{ + LOG_MMC(("exrom write_l, offset: %04x, data: %02x\n", offset, data)); + offset += 0x100; + + if ((offset >= 0x1000) && (offset <= 0x1015)) + { + // SOUND + nesapu_device *m_sound = machine().device<nesapu_device>("maincpu::nessound"); + m_sound->write(space, offset & 0x1f, data); + return; + } + + if ((offset >= 0x1c00) && (offset <= 0x1fff)) + { + // EXRAM + if (m_exram_control == 0x02) // Mode 2 = write data + m_exram[offset - 0x1c00] = data; + else if (m_exram_control != 0x03) // Modes 0,1 = write data in frame / write 0 otherwise + { + if (m_irq_status & 0x40) + m_exram[offset - 0x1c00] = data; + else + m_exram[offset - 0x1c00] = 0x00; + } + // Mode 3 = read only + return; + } + + switch (offset) + { + case 0x1100: + m_prg_mode = data & 0x03; + update_prg(); + //LOG_MMC(("MMC5 rom bank mode: %02x\n", data)); + break; + + case 0x1101: + m_chr_mode = data & 0x03; + m_ex1_chr = 0; + m_split_chr = 0; + //LOG_MMC(("MMC5 vrom bank mode: %02x\n", data)); + break; + + case 0x1102: + m_wram_protect_1 = data & 0x03; + LOG_MMC(("MMC5 vram protect 1: %02x\n", data)); + break; + case 0x1103: + m_wram_protect_2 = data & 0x03; + LOG_MMC(("MMC5 vram protect 2: %02x\n", data)); + break; + + case 0x1104: // Extra VRAM (EXRAM) + m_exram_control = data & 0x03; + LOG_MMC(("MMC5 exram control: %02x\n", data)); + break; + + case 0x1105: + set_mirror(0, (data & 0x03) >> 0); + set_mirror(1, (data & 0x0c) >> 2); + set_mirror(2, (data & 0x30) >> 4); + set_mirror(3, (data & 0xc0) >> 6); + break; + + case 0x1106: + m_floodtile = data; + break; + + case 0x1107: + m_floodattr = m_mmc5_attrib[data & 3]; + break; + + case 0x1113: + LOG_MMC(("MMC5 mid RAM bank select: %02x\n", data & 0x07)); + m_wram_base = data & 0x07; + break; + + + case 0x1114: + case 0x1115: + case 0x1116: + case 0x1117: + m_prg_regs[offset & 3] = data & 0x7f; + m_prg_ram_mapped[offset & 3] = !BIT(data, 7); // m_prg_ram_mapped[3] is not used, in fact! + update_prg(); + break; + + case 0x1120: + case 0x1121: + case 0x1122: + case 0x1123: + case 0x1124: + case 0x1125: + case 0x1126: + case 0x1127: + m_vrom_bank[offset & 0x07] = data | (m_high_chr << 8); + m_last_chr = LAST_CHR_REG_A; + m_ex1_chr = 0; + m_split_chr = 0; + break; + + case 0x1128: + case 0x1129: + case 0x112a: + case 0x112b: + m_vrom_bank[offset & 0x0f] = data | (m_high_chr << 8); + m_last_chr = LAST_CHR_REG_B; + m_ex1_chr = 0; + m_split_chr = 0; + break; + + case 0x1130: + m_high_chr = data & 0x03; + m_ex1_chr = 0; + m_split_chr = 0; + break; + + + case 0x1200: + // in EX2 and EX3 modes, no split screen + m_split_scr = BIT(data, 7); + m_split_rev = BIT(data, 6); + m_split_ctrl = data & 0x1f; + break; + + case 0x1201: + m_split_yst = (data >= 240) ? data - 16 : data; + break; + + case 0x1202: + m_split_bank = data; + m_split_chr = 1; + break; + + case 0x1203: + m_irq_count = data; + LOG_MMC(("MMC5 irq scanline: %d\n", m_irq_count)); + break; + case 0x1204: + m_irq_enable = data & 0x80; + LOG_MMC(("MMC5 irq enable: %02x\n", data)); + break; + case 0x1205: + m_mult1 = data; + break; + case 0x1206: + m_mult2 = data; + break; + + default: + logerror("MMC5 uncaught write, offset: %04x, data: %02x\n", offset + 0x4100, data); + break; + } +} + +// 3bits are used to access the "WRAM" banks +// bit3 select the chip (2 of them can be accessed, each up to 32KB) +// bit1 & bit2 select the 8KB banks inside the chip +// same mechanism is used also when "WRAM" is mapped in higher banks +READ8_MEMBER(nes_exrom_device::read_m) +{ + LOG_MMC(("exrom read_m, offset: %04x\n", offset)); + if (!m_battery.empty() && !m_prgram.empty()) // 2 chips present: first is BWRAM, second is WRAM + { + if (m_wram_base & 0x04) + return m_prgram[(offset + (m_wram_base & 0x03) * 0x2000) & (m_prgram.size() - 1)]; + else + return m_battery[(offset + (m_wram_base & 0x03) * 0x2000) & (m_battery.size() - 1)]; + } + else if (!m_prgram.empty()) // 1 chip, WRAM + return m_prgram[(offset + (m_wram_base & 0x03) * 0x2000) & (m_prgram.size() - 1)]; + else if (!m_battery.empty()) // 1 chip, BWRAM + return m_battery[(offset + (m_wram_base & 0x03) * 0x2000) & (m_battery.size() - 1)]; + else + return m_open_bus; +} + +WRITE8_MEMBER(nes_exrom_device::write_m) +{ + LOG_MMC(("exrom write_m, offset: %04x, data: %02x\n", offset, data)); + if (m_wram_protect_1 != 0x02 || m_wram_protect_2 != 0x01) + return; + + if (!m_battery.empty() && m_wram_base < 4) + m_battery[(offset + m_wram_base * 0x2000) & (m_battery.size() - 1)] = data; + else if (!m_prgram.empty()) + m_prgram[(offset + (m_wram_base & 0x03) * 0x2000) & (m_prgram.size() - 1)] = data; +} + +// some games (e.g. Bandit Kings of Ancient China) write to PRG-RAM through 0x8000-0xdfff +READ8_MEMBER(nes_exrom_device::read_h) +{ + LOG_MMC(("exrom read_h, offset: %04x\n", offset)); + int bank = offset / 0x2000; + + if (bank < 3 && offset >= bank * 0x2000 && offset < (bank + 1) * 0x2000 && m_prg_ram_mapped[bank]) + { + if (!m_battery.empty() && m_ram_hi_banks[bank] < 4) + return m_battery[((m_ram_hi_banks[bank] * 0x2000) + (offset & 0x1fff)) & (m_battery.size() - 1)]; + else if (!m_prgram.empty()) + return m_prgram[(((m_ram_hi_banks[bank] & 3) * 0x2000) + (offset & 0x1fff)) & (m_prgram.size() - 1)]; + } + + return hi_access_rom(offset); +} + +WRITE8_MEMBER(nes_exrom_device::write_h) +{ + LOG_MMC(("exrom write_h, offset: %04x, data: %02x\n", offset, data)); + int bank = offset / 0x2000; + if (m_wram_protect_1 != 0x02 || m_wram_protect_2 != 0x01 || bank == 3 || !m_prg_ram_mapped[bank]) + return; + + if (!m_battery.empty() && m_ram_hi_banks[bank] < 4) + m_battery[((m_ram_hi_banks[bank] * 0x2000) + (offset & 0x1fff)) & (m_battery.size() - 1)] = data; + else if (!m_prgram.empty()) + m_prgram[(((m_ram_hi_banks[bank] & 3) * 0x2000) + (offset & 0x1fff)) & (m_prgram.size() - 1)] = data; +} |