diff options
Diffstat (limited to 'src/devices/bus/nes/jy.c')
-rw-r--r-- | src/devices/bus/nes/jy.c | 589 |
1 files changed, 589 insertions, 0 deletions
diff --git a/src/devices/bus/nes/jy.c b/src/devices/bus/nes/jy.c new file mode 100644 index 00000000000..31358e2ab16 --- /dev/null +++ b/src/devices/bus/nes/jy.c @@ -0,0 +1,589 @@ +// license:BSD-3-Clause +// copyright-holders:Fabio Priuli +/*********************************************************************************************************** + + + NES/Famicom cartridge emulation for JY Company + + + Here we emulate multiple PCBs by JY Company with weird IRQ mechanisms [mappers 90, 209, 211] + + TODO: long list... + - add dipswitches + - revamp IRQ system + * scanline/hblank irq should fire 8 times per line (currently not possible) + * implement CPU write IRQ (used by any games?) + * possibly implementing 'funky' IRQ mode (unused?) + + ***********************************************************************************************************/ + + +#include "emu.h" +#include "jy.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 + +#ifdef NES_PCB_DEBUG +#define VERBOSE 1 +#else +#define VERBOSE 0 +#endif + +#define LOG_MMC(x) do { if (VERBOSE) logerror x; } while (0) + + +//------------------------------------------------- +// constructor +//------------------------------------------------- + +const device_type NES_JY_TYPEA = &device_creator<nes_jy_typea_device>; +const device_type NES_JY_TYPEB = &device_creator<nes_jy_typeb_device>; +const device_type NES_JY_TYPEC = &device_creator<nes_jy_typec_device>; + + +nes_jy_typea_device::nes_jy_typea_device(const machine_config &mconfig, device_type type, const char *name, const char *tag, device_t *owner, UINT32 clock, const char *shortname, const char *source) + : nes_nrom_device(mconfig, type, name, tag, owner, clock, shortname, source) +{ +} + +nes_jy_typea_device::nes_jy_typea_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : nes_nrom_device(mconfig, NES_JY_TYPEA, "NES Cart JY Company Type A PCB", tag, owner, clock, "nes_jya", __FILE__) +{ +} + +nes_jy_typeb_device::nes_jy_typeb_device(const machine_config &mconfig, device_type type, const char *name, const char *tag, device_t *owner, UINT32 clock, const char *shortname, const char *source) + : nes_jy_typea_device(mconfig, type, name, tag, owner, clock, shortname, source) +{ +} + +nes_jy_typeb_device::nes_jy_typeb_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : nes_jy_typea_device(mconfig, NES_JY_TYPEB, "NES Cart JY Company Type B PCB", tag, owner, clock, "nes_jyb", __FILE__) +{ +} + +nes_jy_typec_device::nes_jy_typec_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : nes_jy_typeb_device(mconfig, NES_JY_TYPEC, "NES Cart JY Company Type C PCB", tag, owner, clock, "nes_jyc", __FILE__) +{ +} + + + + +void nes_jy_typea_device::device_start() +{ + common_start(); + irq_timer = timer_alloc(TIMER_IRQ); + irq_timer->reset(); + timer_freq = machine().device<cpu_device>("maincpu")->cycles_to_attotime(1); + + save_item(NAME(m_mul)); + save_item(NAME(m_latch)); + save_item(NAME(m_mmc_prg_bank)); + save_item(NAME(m_mmc_nt_bank)); + save_item(NAME(m_mmc_vrom_bank)); + save_item(NAME(m_reg)); + save_item(NAME(m_chr_latch)); + save_item(NAME(m_bank_6000)); + + save_item(NAME(m_irq_prescale)); + save_item(NAME(m_irq_prescale_mask)); + save_item(NAME(m_irq_mode)); + save_item(NAME(m_irq_count)); + save_item(NAME(m_irq_flip)); + save_item(NAME(m_irq_enable)); + save_item(NAME(m_irq_up)); + save_item(NAME(m_irq_down)); +} + +void nes_jy_typea_device::pcb_reset() +{ + m_chr_source = m_vrom_chunks ? CHRROM : CHRRAM; + prg32(0); + chr8(0, m_chr_source); + + // 0x5000-0x5fff + m_mul[0] = 0; + m_mul[1] = 0; + m_latch = 0; + + // 0x8000-0xffff + memset(m_mmc_prg_bank, 0xff, sizeof(m_mmc_prg_bank)); + memset(m_mmc_nt_bank, 0, sizeof(m_mmc_nt_bank)); + memset(m_mmc_vrom_bank, 0xffff, sizeof(m_mmc_vrom_bank)); + memset(m_reg, 0, sizeof(m_reg)); + m_chr_latch[0] = 0; + m_chr_latch[1] = 4; + m_bank_6000 = 0; + + update_prg(); + update_chr(); + update_mirror(); + + m_irq_mode = 0; + m_irq_count = 0; + m_irq_prescale = 0; + m_irq_prescale_mask = 0xff; + m_irq_flip = 0; + m_irq_enable = 0; + m_irq_up = 0; + m_irq_down = 0; +} + + +/*------------------------------------------------- + mapper specific handlers + -------------------------------------------------*/ + +/*------------------------------------------------- + + JY Company Type A board emulation + + iNES: mapper 90 + + -------------------------------------------------*/ + + +READ8_MEMBER(nes_jy_typea_device::nt_r) +{ + int page = ((offset & 0xc00) >> 10); + irq_clock(0, 2); + return m_nt_access[page][offset & 0x3ff]; +} + +READ8_MEMBER(nes_jy_typea_device::chr_r) +{ + int bank = offset >> 10; + irq_clock(0, 2); + return m_chr_access[bank][offset & 0x3ff]; +} + +void nes_jy_typea_device::irq_clock(int mode, int blanked) +{ + bool clock = FALSE, fire = FALSE; + + if (m_irq_mode != mode) + return; + + // no counter changes if both Up/Down are set or clear + if ((m_irq_down && m_irq_up) || (!m_irq_down && !m_irq_up)) + return; + + // update prescaler + if (m_irq_down) + { + if ((m_irq_prescale & m_irq_prescale_mask) == 0) + { + clock = TRUE; + m_irq_prescale = (m_irq_prescale_mask == 7) ? ((m_irq_prescale & 0xf8) | 7) : 0xff; + } + else + m_irq_prescale = (m_irq_prescale_mask == 7) ? ((m_irq_prescale & 0xf8) | ((m_irq_prescale - 1) & m_irq_prescale_mask)) : (m_irq_prescale - 1); + } + + if (m_irq_up) + { + if ((m_irq_prescale & m_irq_prescale_mask) == m_irq_prescale_mask) + { + clock = TRUE; + m_irq_prescale = (m_irq_prescale_mask == 7) ? (m_irq_prescale & 0xf8) : 0; + } + else + m_irq_prescale = (m_irq_prescale_mask == 7) ? ((m_irq_prescale & 0xf8) | ((m_irq_prescale + 1) & m_irq_prescale_mask)) : (m_irq_prescale + 1); + } + + // if prescaler wraps, update count + if (clock) + { + if (m_irq_down) + { + if (m_irq_count == 0) + { + fire = TRUE; + m_irq_count = 0xff; + } + else + m_irq_count--; + } + + if (m_irq_up) + { + if (m_irq_count == 0xff) + { + fire = TRUE; + m_irq_count = 0; + } + else + m_irq_count++; + } + + + // if count wraps, check if IRQ is enabled + if (fire && m_irq_enable && !blanked) + m_maincpu->set_input_line(M6502_IRQ_LINE, ASSERT_LINE); + + } +} + +void nes_jy_typea_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) +{ + if (id == TIMER_IRQ) + { + irq_clock(0, 0); + } +} + +void nes_jy_typea_device::scanline_irq(int scanline, int vblank, int blanked) +{ + if (scanline < PPU_BOTTOM_VISIBLE_SCANLINE) + irq_clock(blanked, 1); +} + + +// 0x5000-0x5fff : sort of protection? +READ8_MEMBER(nes_jy_typea_device::read_l) +{ + LOG_MMC(("JY Company write_m, offset: %04x\n", offset)); + offset += 0x100; + + if (offset >= 0x1000 && offset < 0x1800) + { + // bit6/bit7 DSW read + return m_open_bus & 0x3f; + } + + if (offset >= 0x1800) + { + if ((offset & 7) == 0) + return (m_mul[0] * m_mul[1]) & 0xff; + if ((offset & 7) == 1) + return ((m_mul[0] * m_mul[1]) >> 8) & 0xff; + if ((offset & 7) == 3) + return m_latch; + } + + return m_open_bus; // open bus +} + +WRITE8_MEMBER(nes_jy_typea_device::write_l) +{ + LOG_MMC(("JY Company write_m, offset: %04x, data: %02x\n", offset, data)); + offset += 0x100; + + if (offset >= 0x1800) + { + if ((offset & 7) == 0) + m_mul[0] = data; + if ((offset & 7) == 1) + m_mul[1] = data; + if ((offset & 7) == 3) + m_latch = data; + } +} + +// 0x6000-0x7fff : WRAM or open bus +READ8_MEMBER(nes_jy_typea_device::read_m) +{ + LOG_MMC(("JY Company write_m, offset: %04x\n", offset)); + + if (m_reg[0] & 0x80) + return m_prg[(m_bank_6000 & m_prg_mask) * 0x2000 + (offset & 0x1fff)]; + + return m_open_bus; // open bus +} + + +inline UINT8 nes_jy_typea_device::unscramble(UINT8 bank) +{ + return BITSWAP8(bank & 0x7f,7,0,1,2,3,4,5,6); +} + +void nes_jy_typea_device::update_prg() +{ + UINT8 exPrg = (m_reg[3] & 0x06) << 5; + UINT8 last = (m_reg[0] & 0x04) ? m_mmc_prg_bank[3] : 0x3f; + + switch (m_reg[0] & 0x03) + { + case 0: // 32KB + prg32((last & 0x0f) | (exPrg >> 2)); + m_bank_6000 = (((m_mmc_prg_bank[3] * 4) + 3) & 0x3f) | (exPrg >> 2); + break; + + case 1: // 16KB + prg16_89ab((m_mmc_prg_bank[1] & 0x1f) | (exPrg >> 1)); + prg16_cdef((last & 0x1f) | (exPrg >> 1)); + m_bank_6000 = (((m_mmc_prg_bank[3] * 2) + 1) & 0x1f) | (exPrg >> 1); + break; + + case 2: // 8KB + prg8_89(m_mmc_prg_bank[0] | exPrg); + prg8_ab(m_mmc_prg_bank[1] | exPrg); + prg8_cd(m_mmc_prg_bank[2] | exPrg); + prg8_ef(last | exPrg); + m_bank_6000 = m_mmc_prg_bank[3] | exPrg; + break; + + case 3: // 8KB Alt + prg8_89((unscramble(m_mmc_prg_bank[0]) & 0x3f) | exPrg); + prg8_ab((unscramble(m_mmc_prg_bank[1]) & 0x3f) | exPrg); + prg8_cd((unscramble(m_mmc_prg_bank[2]) & 0x3f) | exPrg); + if (m_reg[0] & 0x04) + prg8_ef((unscramble(m_mmc_prg_bank[3]) & 0x3f) | exPrg); + else + prg8_ef((unscramble(last) & 0x3f) | exPrg); + m_bank_6000 = (unscramble(m_mmc_prg_bank[3]) & 0x3f) | exPrg; + break; + } +} + +void nes_jy_typea_device::update_chr() +{ + // in 1KB & 2KB mode, PPU 0x800-0xfff always mirrors 0x000-0x7ff (0x1800-0x1fff not affected) + int chr_mirror_mode = BIT(m_reg[3], 7) << 1; + + // Case (m_reg[3] & 0x20 == 0) + // Block mode enabled: in this case lower bits select a 256KB page inside CHRROM + // and the low bytes of m_mmc_vrom_bank select the banks inside such a page + + // docs suggest m_reg[3] & 0x1f for chr_page below, + // but 45 in 1 (JY-120A) menu requires to use this (from NEStopia) + UINT8 chr_page = (m_reg[3] & 1) | ((m_reg[3] & 0x18) >> 2); + UINT32 extra_chr_base = BIT(m_reg[3], 5) ? 0 : (chr_page * 0x100); + UINT32 extra_chr_mask = BIT(m_reg[3], 5) ? 0xffffff : 0xff; + + switch (m_reg[0] & 0x18) + { + case 0x00: // 8KB + extra_chr_base >>= 3; + extra_chr_mask >>= 3; + chr8(extra_chr_base | (m_mmc_vrom_bank[0] & extra_chr_mask), m_chr_source); + break; + + case 0x08: // 4KB + extra_chr_base >>= 2; + extra_chr_mask >>= 2; + // Type A & B games have fixed m_chr_latch[0] = 0 and m_chr_latch[1] = 4 + // Type C games can change them at each CHR access! + chr4_0(extra_chr_base | (m_mmc_vrom_bank[m_chr_latch[0]] & extra_chr_mask), m_chr_source); + chr4_4(extra_chr_base | (m_mmc_vrom_bank[m_chr_latch[1]] & extra_chr_mask), m_chr_source); + break; + + case 0x10: // 2KB + extra_chr_base >>= 1; + extra_chr_mask >>= 1; + chr2_0(extra_chr_base | (m_mmc_vrom_bank[0] & extra_chr_mask), m_chr_source); + chr2_2(extra_chr_base | (m_mmc_vrom_bank[2 ^ chr_mirror_mode] & extra_chr_mask), m_chr_source); + chr2_4(extra_chr_base | (m_mmc_vrom_bank[4] & extra_chr_mask), m_chr_source); + chr2_6(extra_chr_base | (m_mmc_vrom_bank[6] & extra_chr_mask), m_chr_source); + break; + + case 0x18: // 1KB + chr1_0(extra_chr_base | (m_mmc_vrom_bank[0] & extra_chr_mask), m_chr_source); + chr1_1(extra_chr_base | (m_mmc_vrom_bank[1] & extra_chr_mask), m_chr_source); + chr1_2(extra_chr_base | (m_mmc_vrom_bank[2 ^ chr_mirror_mode] & extra_chr_mask), m_chr_source); + chr1_3(extra_chr_base | (m_mmc_vrom_bank[3 ^ chr_mirror_mode] & extra_chr_mask), m_chr_source); + chr1_4(extra_chr_base | (m_mmc_vrom_bank[4] & extra_chr_mask), m_chr_source); + chr1_5(extra_chr_base | (m_mmc_vrom_bank[5] & extra_chr_mask), m_chr_source); + chr1_6(extra_chr_base | (m_mmc_vrom_bank[6] & extra_chr_mask), m_chr_source); + chr1_7(extra_chr_base | (m_mmc_vrom_bank[7] & extra_chr_mask), m_chr_source); + break; + } +} + +void nes_jy_typea_device::update_mirror_typea() +{ + switch (m_reg[1] & 3) + { + case 0: set_nt_mirroring(PPU_MIRROR_VERT); break; + case 1: set_nt_mirroring(PPU_MIRROR_HORZ); break; + case 2: set_nt_mirroring(PPU_MIRROR_LOW); break; + case 3: set_nt_mirroring(PPU_MIRROR_HIGH); break; + } +} + +void nes_jy_typea_device::update_banks(int reg) +{ + switch (reg & 3) + { + case 0: + update_prg(); + update_chr(); + update_mirror(); + break; + case 1: + update_mirror(); + break; + case 2: + update_mirror(); + break; + case 3: + update_prg(); + update_chr(); + break; + } +} + + +WRITE8_MEMBER(nes_jy_typea_device::write_h) +{ + LOG_MMC(("JY Company write_m, offset: %04x, data: %02x\n", offset, data)); + + switch (offset & 0x7000) + { + case 0x0000: + offset &= 3; + data &= 0x3f; + if (m_mmc_prg_bank[offset] != data) + { + m_mmc_prg_bank[offset] = data; + update_prg(); + } + break; + case 0x1000: + offset &= 7; + if ((m_mmc_vrom_bank[offset] & 0xff) != data) + { + m_mmc_vrom_bank[offset] = (m_mmc_vrom_bank[offset] & 0xff00) | data; + update_chr(); + } + break; + case 0x2000: + offset &= 7; + if ((m_mmc_vrom_bank[offset] & 0xff00) != (data << 8)) + { + m_mmc_vrom_bank[offset] = (m_mmc_vrom_bank[offset] & 0x00ff) | (data << 8); + update_chr(); + } + break; + case 0x3000: + if (!(offset & 4)) + { + offset &= 3; + m_mmc_nt_bank[offset] = (m_mmc_nt_bank[offset] & 0xff00) | data; + } + else + { + offset &= 3; + m_mmc_nt_bank[offset] = (m_mmc_nt_bank[offset] & 0x00ff) | data << 8; + } + update_mirror(); + break; + case 0x4000: + switch (offset & 7) + { + case 0: + if (BIT(data, 0)) + m_irq_enable = 1; + else + { + m_maincpu->set_input_line(M6502_IRQ_LINE, CLEAR_LINE); + m_irq_enable = 0; + } + break; + case 1: + m_irq_mode = data & 3; + m_irq_prescale_mask = (data & 4) ? 0x07 : 0xff; + m_irq_down = data & 0x80; + m_irq_up = data & 0x40; + if (m_irq_mode == 0) + irq_timer->adjust(attotime::zero, 0, timer_freq); + else + irq_timer->adjust(attotime::never); + break; + case 2: + m_maincpu->set_input_line(M6502_IRQ_LINE, CLEAR_LINE); + m_irq_enable = 0; + break; + case 3: + m_irq_enable = 1; + break; + case 4: + m_irq_prescale = data ^ m_irq_flip; + break; + case 5: + m_irq_count = data ^ m_irq_flip; + break; + case 6: + m_irq_flip = data; + break; + case 7: + // this is used for the 'funky' IRQ mode, not implemented yet + break; + } + break; + case 0x5000: + if (m_reg[offset & 3] != data) + { + m_reg[offset & 3] = data; + update_banks(offset & 3); + } + break; + } +} + +/*------------------------------------------------- + + JY Company Type B board emulation + + iNES: mapper 211 + + The mirroring system is a lot more complex in this + board + + -------------------------------------------------*/ + +void nes_jy_typeb_device::update_mirror_typeb() +{ + for (int i = 0; i < 4; i++) + { + if (BIT(m_reg[0], 6)) // CHRROM + set_nt_page(i, VROM, m_mmc_nt_bank[i], 0); + else // might be either CHRROM or CIRAM + { + // CHRROM is only used if bit 7 of the NT Reg does not match bit7 of reg[2]. + if ((m_mmc_nt_bank[i] ^ m_reg[2]) & 0x80) + set_nt_page(i, VROM, m_mmc_nt_bank[i], 0); + else + set_nt_page(i, CIRAM, m_mmc_nt_bank[i] & 1, 1); + } + } +} + +/*------------------------------------------------- + + JY Company Type C board emulation + + iNES: mapper 209 + + These board can switch between the Type A and the + Type B mirroring + + -------------------------------------------------*/ + +void nes_jy_typec_device::update_mirror_typec() +{ + if (BIT(m_reg[0], 5)) + update_mirror_typeb(); + else + update_mirror_typea(); +} + +READ8_MEMBER(nes_jy_typec_device::chr_r) +{ + int bank = offset >> 10; + + irq_clock(0, 2); + switch (offset & 0xff0) + { + case 0xfd0: + m_chr_latch[BIT(offset, 12)] = (bank & 0x4); + update_chr(); + break; + case 0xfe0: + m_chr_latch[BIT(offset, 12)] = (bank & 0x4) | 0x2; + update_chr(); + break; + } + return m_chr_access[bank][offset & 0x3ff]; +} |