diff options
author | Wilbert Pol <wilbertpol@users.noreply.github.com> | 2016-09-30 22:06:39 +0200 |
---|---|---|
committer | Wilbert Pol <wilbertpol@users.noreply.github.com> | 2016-09-30 22:06:39 +0200 |
commit | c3e0ead53494abe36105587fd485e317a788ec5e (patch) | |
tree | ee7724532fef63d8f78768e6a267b1d40af3f648 | |
parent | 8566a1fd10ff711d3caa09a4941f436c979df89a (diff) |
gb.cpp: Some cpu, sound, and video updates (nw)
-rw-r--r-- | hash/snes.xml | 2 | ||||
-rw-r--r-- | src/devices/bus/snes/sgb.cpp | 102 | ||||
-rw-r--r-- | src/devices/bus/snes/sgb.h | 37 | ||||
-rw-r--r-- | src/devices/bus/snes/snes_carts.cpp | 1 | ||||
-rw-r--r-- | src/devices/cpu/lr35902/lr35902.cpp | 104 | ||||
-rw-r--r-- | src/devices/cpu/lr35902/lr35902.h | 31 | ||||
-rw-r--r-- | src/devices/cpu/lr35902/opc_main.hxx | 111 | ||||
-rw-r--r-- | src/devices/sound/gb.cpp | 1471 | ||||
-rw-r--r-- | src/devices/sound/gb.h | 236 | ||||
-rw-r--r-- | src/devices/video/gb_lcd.cpp | 2998 | ||||
-rw-r--r-- | src/devices/video/gb_lcd.h | 184 | ||||
-rw-r--r-- | src/mame/drivers/gb.cpp | 308 | ||||
-rw-r--r-- | src/mame/drivers/gba.cpp | 2 | ||||
-rw-r--r-- | src/mame/drivers/vgmplay.cpp | 2 | ||||
-rw-r--r-- | src/mame/includes/gb.h | 43 | ||||
-rw-r--r-- | src/mame/machine/gb.cpp | 242 |
16 files changed, 3816 insertions, 2058 deletions
diff --git a/hash/snes.xml b/hash/snes.xml index 056e738dd60..29723ca5dea 100644 --- a/hash/snes.xml +++ b/hash/snes.xml @@ -25823,7 +25823,7 @@ more investigation needed... <feature name="led2" value="LED2" /> <!-- green, connected to CN2? --> <feature name="cart_model" value="SHVC-042" /> <feature name="cart_back_label" value="SHVC-SGB2-JPN" /> - <feature name="slot" value="lorom_sgb" /> + <feature name="slot" value="lorom_sgb2" /> <dataarea name="rom" size="524288"> <rom name="sys-sgb2-10.u4" size="524288" crc="cb176e45" sha1="e5b2922ca137051059e4269b236d07a22c07bc84" offset="0x000000" /> </dataarea> diff --git a/src/devices/bus/snes/sgb.cpp b/src/devices/bus/snes/sgb.cpp index 0c6abd79490..5a2888a8294 100644 --- a/src/devices/bus/snes/sgb.cpp +++ b/src/devices/bus/snes/sgb.cpp @@ -19,15 +19,17 @@ // sns_rom_sgb_device - constructor //------------------------------------------------- -const device_type SNS_LOROM_SUPERGB = &device_creator<sns_rom_sgb_device>; +const device_type SNS_LOROM_SUPERGB = &device_creator<sns_rom_sgb1_device>; +const device_type SNS_LOROM_SUPERGB2 = &device_creator<sns_rom_sgb2_device>; -sns_rom_sgb_device::sns_rom_sgb_device(const machine_config& mconfig, const char* tag, device_t* owner, UINT32 clock) : - sns_rom_device(mconfig, SNS_LOROM_SUPERGB, "SNES Super Game Boy Cart", tag, owner, clock, "sns_rom_sgb", __FILE__), - m_gb_cpu(*this, "sgb_cpu"), - m_gb_snd(*this, "sgb_snd"), - m_gb_lcd(*this, "sgb_lcd"), +sns_rom_sgb_device::sns_rom_sgb_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) + : sns_rom_device(mconfig, type, name, tag, owner, clock, shortname, source), + m_sgb_cpu(*this, "sgb_cpu"), + m_sgb_apu(*this, "sgb_apu"), + m_sgb_ppu(*this, "sgb_ppu"), m_cartslot(*this, "gb_slot"), + m_region_bios(*this, "sgb_cpu"), m_sgb_ly(0), m_sgb_row(0), m_vram(0), @@ -39,7 +41,20 @@ sns_rom_sgb_device::sns_rom_sgb_device(const machine_config& mconfig, const char m_vram_offs(0), m_mlt_req(0), m_lcd_row(0), - m_packetsize(0) + m_packetsize(0), + m_bios_disabled(false) +{ +} + + +sns_rom_sgb1_device::sns_rom_sgb1_device(const machine_config& mconfig, const char* tag, device_t* owner, UINT32 clock) : + sns_rom_sgb_device(mconfig, SNS_LOROM_SUPERGB, "SNES Super Game Boy Cart", tag, owner, clock, "sns_rom_sgb", __FILE__) +{ +} + + +sns_rom_sgb2_device::sns_rom_sgb2_device(const machine_config& mconfig, const char* tag, device_t* owner, UINT32 clock) : + sns_rom_sgb_device(mconfig, SNS_LOROM_SUPERGB2, "SNES Super Game Boy 2 Cart", tag, owner, clock, "sns_rom_sgb2", __FILE__) { } @@ -62,6 +77,10 @@ void sns_rom_sgb_device::device_reset() READ8_MEMBER(sns_rom_sgb_device::gb_cart_r) { + if (offset < 0x100 && !m_bios_disabled) + { + return m_region_bios->base()[offset]; + } return m_cartslot->read_rom(space, offset); } @@ -101,12 +120,12 @@ WRITE8_MEMBER(sns_rom_sgb_device::gb_io_w) READ8_MEMBER(sns_rom_sgb_device::gb_ie_r) { - return m_gb_cpu->get_ie(); + return m_sgb_cpu->get_ie(); } WRITE8_MEMBER(sns_rom_sgb_device::gb_ie_w) { - m_gb_cpu->set_ie(data & 0x1f); + m_sgb_cpu->set_ie(data); } @@ -114,16 +133,16 @@ WRITE8_MEMBER(sns_rom_sgb_device::gb_ie_w) static ADDRESS_MAP_START(supergb_map, AS_PROGRAM, 8, sns_rom_sgb_device ) ADDRESS_MAP_UNMAP_HIGH AM_RANGE(0x0000, 0x7fff) AM_READWRITE(gb_cart_r, gb_bank_w) - AM_RANGE(0x8000, 0x9fff) AM_DEVREADWRITE("sgb_lcd", sgb_lcd_device, vram_r, vram_w) /* 8k VRAM */ + AM_RANGE(0x8000, 0x9fff) AM_DEVREADWRITE("sgb_ppu", sgb_ppu_device, vram_r, vram_w) /* 8k VRAM */ AM_RANGE(0xa000, 0xbfff) AM_READWRITE(gb_ram_r, gb_ram_w ) /* 8k switched RAM bank (cartridge) */ - AM_RANGE(0xc000, 0xdfff) AM_RAM /* 8k low RAM */ - AM_RANGE(0xe000, 0xfdff) AM_READWRITE(gb_echo_r, gb_echo_w) /* echo RAM */ + AM_RANGE(0xc000, 0xdfff) AM_RAM /* 8k low RAM */ + AM_RANGE(0xe000, 0xfdff) AM_READWRITE(gb_echo_r, gb_echo_w) + AM_RANGE(0xfe00, 0xfeff) AM_DEVREADWRITE("sgb_ppu", sgb_ppu_device, oam_r, oam_w) /* OAM RAM */ AM_RANGE(0xff00, 0xff0f) AM_READWRITE(gb_io_r, gb_io_w) /* I/O */ - AM_RANGE(0xff10, 0xff26) AM_DEVREADWRITE("sgb_snd", gameboy_sound_device, sound_r, sound_w) /* sound registers */ - AM_RANGE(0xfe00, 0xfeff) AM_DEVREADWRITE("sgb_lcd", sgb_lcd_device, oam_r, oam_w) /* OAM RAM */ + AM_RANGE(0xff10, 0xff26) AM_DEVREADWRITE("sgb_apu", gameboy_sound_device, sound_r, sound_w) /* sound registers */ AM_RANGE(0xff27, 0xff2f) AM_NOP /* unused */ - AM_RANGE(0xff30, 0xff3f) AM_DEVREADWRITE("sgb_snd", gameboy_sound_device, wave_r, wave_w) /* Wave RAM */ - AM_RANGE(0xff40, 0xff7f) AM_DEVREADWRITE("sgb_lcd", sgb_lcd_device, video_r, video_w) /* also disable bios?? */ /* Video controller & BIOS flip-flop */ + AM_RANGE(0xff30, 0xff3f) AM_DEVREADWRITE("sgb_apu", gameboy_sound_device, wave_r, wave_w) /* Wave RAM */ + AM_RANGE(0xff40, 0xff7f) AM_DEVREADWRITE("sgb_ppu", sgb_ppu_device, video_r, video_w) /* also disable bios?? */ /* Video controller & BIOS flip-flop */ AM_RANGE(0xff80, 0xfffe) AM_RAM /* High RAM */ AM_RANGE(0xffff, 0xffff) AM_READWRITE(gb_ie_r, gb_ie_w) /* Interrupt enable register */ ADDRESS_MAP_END @@ -140,26 +159,71 @@ static SLOT_INTERFACE_START(supergb_cart) SLOT_INTERFACE_INTERNAL("rom_mbc1", GB_ROM_MBC1) SLOT_INTERFACE_END + static MACHINE_CONFIG_FRAGMENT( supergb ) MCFG_CPU_ADD("sgb_cpu", LR35902, 4295454) /* 4.295454 MHz */ MCFG_CPU_PROGRAM_MAP(supergb_map) MCFG_LR35902_TIMER_CB(WRITE8(sns_rom_sgb_device, gb_timer_callback)) MCFG_LR35902_HALT_BUG - MCFG_GB_LCD_SGB_ADD("sgb_lcd") + MCFG_SGB_PPU_ADD("sgb_ppu", "sgb_cpu") - MCFG_SOUND_ADD("sgb_snd", GAMEBOY, 0) + MCFG_SOUND_ADD("sgb_apu", DMG_APU, 4295454) MCFG_GB_CARTRIDGE_ADD("gb_slot", supergb_cart, nullptr) MACHINE_CONFIG_END -machine_config_constructor sns_rom_sgb_device::device_mconfig_additions() const +machine_config_constructor sns_rom_sgb1_device::device_mconfig_additions() const { return MACHINE_CONFIG_NAME( supergb ); } +ROM_START( supergb ) + ROM_REGION(0x100, "sgb_cpu", 0) + ROM_LOAD("sgb_boot.bin", 0x0000, 0x0100, CRC(ec8a83b9) SHA1(aa2f50a77dfb4823da96ba99309085a3c6278515)) +ROM_END + + +const tiny_rom_entry *sns_rom_sgb1_device::device_rom_region() const +{ + return ROM_NAME( supergb ); +} + + +static MACHINE_CONFIG_FRAGMENT( supergb2 ) + MCFG_CPU_ADD("sgb_cpu", LR35902, XTAL_4_194304Mhz) /* 4.194MHz derived from clock on sgb2 pcb */ + MCFG_CPU_PROGRAM_MAP(supergb_map) + MCFG_LR35902_TIMER_CB(WRITE8(sns_rom_sgb_device, gb_timer_callback)) + MCFG_LR35902_HALT_BUG + + MCFG_SGB_PPU_ADD("sgb_ppu", "sgb_cpu") + + MCFG_SOUND_ADD("sgb_apu", DMG_APU, XTAL_4_194304Mhz) + + MCFG_GB_CARTRIDGE_ADD("gb_slot", supergb_cart, nullptr) +MACHINE_CONFIG_END + + +machine_config_constructor sns_rom_sgb2_device::device_mconfig_additions() const +{ + return MACHINE_CONFIG_NAME( supergb2 ); +} + + +ROM_START( supergb2 ) + ROM_REGION(0x100, "sgb_cpu", 0) + ROM_LOAD("sgb2_boot.bin", 0x0000, 0x0100, CRC(53d0dd63) SHA1(93407ea10d2f30ab96a314d8eca44fe160aea734)) +ROM_END + + +const tiny_rom_entry *sns_rom_sgb2_device::device_rom_region() const +{ + return ROM_NAME( supergb2 ); +} + + /*------------------------------------------------- mapper specific handlers -------------------------------------------------*/ diff --git a/src/devices/bus/snes/sgb.h b/src/devices/bus/snes/sgb.h index 71d4bbf0d83..dc959109923 100644 --- a/src/devices/bus/snes/sgb.h +++ b/src/devices/bus/snes/sgb.h @@ -20,12 +20,11 @@ class sns_rom_sgb_device : public sns_rom_device { public: // construction/destruction - sns_rom_sgb_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); + sns_rom_sgb_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); // device-level overrides virtual void device_start() override; virtual void device_reset() override; - virtual machine_config_constructor device_mconfig_additions() const override; // reading and writing virtual DECLARE_READ8_MEMBER(read_l) override; @@ -45,10 +44,12 @@ public: virtual DECLARE_WRITE8_MEMBER(gb_ie_w); virtual DECLARE_WRITE8_MEMBER(gb_timer_callback); - required_device<lr35902_cpu_device> m_gb_cpu; - required_device<gameboy_sound_device> m_gb_snd; - required_device<sgb_lcd_device> m_gb_lcd; +protected: + required_device<lr35902_cpu_device> m_sgb_cpu; + required_device<gameboy_sound_device> m_sgb_apu; + required_device<sgb_ppu_device> m_sgb_ppu; required_device<gb_cart_slot_device> m_cartslot; + required_memory_region m_region_bios; void lcd_render(UINT32 *source); @@ -69,10 +70,36 @@ public: // input bits int m_packetsize; UINT8 m_packet_data[64][16]; + + bool m_bios_disabled; +}; + + +class sns_rom_sgb1_device : public sns_rom_sgb_device +{ +public: + // construction/destruction + sns_rom_sgb1_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); + + // device-level overrides + virtual machine_config_constructor device_mconfig_additions() const override; + virtual const tiny_rom_entry *device_rom_region() const override; }; +class sns_rom_sgb2_device : public sns_rom_sgb_device +{ +public: + // construction/destruction + sns_rom_sgb2_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); + + // device-level overrides + virtual machine_config_constructor device_mconfig_additions() const override; + virtual const tiny_rom_entry *device_rom_region() const override; +}; + // device type definition extern const device_type SNS_LOROM_SUPERGB; +extern const device_type SNS_LOROM_SUPERGB2; #endif diff --git a/src/devices/bus/snes/snes_carts.cpp b/src/devices/bus/snes/snes_carts.cpp index 478dd6af83c..840a441e9fe 100644 --- a/src/devices/bus/snes/snes_carts.cpp +++ b/src/devices/bus/snes/snes_carts.cpp @@ -19,6 +19,7 @@ SLOT_INTERFACE_START(snes_cart) SLOT_INTERFACE_INTERNAL("lorom_sdd1", SNS_LOROM_SDD1) SLOT_INTERFACE_INTERNAL("lorom_sfx", SNS_LOROM_SUPERFX) SLOT_INTERFACE_INTERNAL("lorom_sgb", SNS_LOROM_SUPERGB) // SuperGB base cart - unsupported + SLOT_INTERFACE_INTERNAL("lorom_sgb2", SNS_LOROM_SUPERGB2) // SuperGB2 base cart - unsupported SLOT_INTERFACE_INTERNAL("lorom_st010", SNS_LOROM_SETA10) SLOT_INTERFACE_INTERNAL("lorom_st011", SNS_LOROM_SETA11) SLOT_INTERFACE_INTERNAL("lorom_st018", SNS_LOROM) // Cart + ST018 - unsupported diff --git a/src/devices/cpu/lr35902/lr35902.cpp b/src/devices/cpu/lr35902/lr35902.cpp index 3c9080ce3c8..d86c490179f 100644 --- a/src/devices/cpu/lr35902/lr35902.cpp +++ b/src/devices/cpu/lr35902/lr35902.cpp @@ -80,6 +80,7 @@ lr35902_cpu_device::lr35902_cpu_device(const machine_config &mconfig, const char , m_IF(0) , m_enable(0) , m_has_halt_bug(false) + , m_entering_halt(false) , m_timer_func(*this) , m_incdec16_func(*this) { @@ -156,7 +157,7 @@ void lr35902_cpu_device::device_start() save_item(NAME(m_gb_speed)); save_item(NAME(m_gb_speed_change_pending)); save_item(NAME(m_enable)); - save_item(NAME(m_handle_halt_bug)); + save_item(NAME(m_entering_halt)); // Register state for debugger state_add( LR35902_PC, "PC", m_PC ).callimport().callexport().formatstr("%04X"); @@ -218,10 +219,10 @@ void lr35902_cpu_device::device_reset() m_IF = 0; m_execution_state = 0; - m_handle_halt_bug = false; m_handle_ei_delay = false; m_gb_speed_change_pending = 0; m_gb_speed = 1; + m_entering_halt = false; } @@ -254,6 +255,7 @@ void lr35902_cpu_device::check_interrupts() logerror("LR35902 Interrupt IRQ $%02X\n", irq); */ + bool was_halted = (m_enable & HALTED); for( ; irqline < 5; irqline++ ) { if( irq & (1<<irqline) ) @@ -262,17 +264,42 @@ void lr35902_cpu_device::check_interrupts() { m_enable &= ~HALTED; m_PC++; + // In general there seems to be a 4 cycle delay to leave the halt state; except when the + // trigger is caused by the VBlank interrupt (on DMG/MGB/SGB?/SGB2?). + // + // On CGB/AGB/AGS this delay to leave the halt seems to always be 4 cycles. + // if ( m_has_halt_bug ) { if ( ! ( m_enable & IME ) ) { /* Old cpu core (dmg/mgb/sgb) */ - m_handle_halt_bug = true; + m_PC--; + } + // TODO: Properly detect when the delay should be skipped. Cases seen so far: + // - Vblank irq + // - STAT mode 1 irq (triggered at same time as vblank) + // - STAT mode 2 irq (8 cycles?, breaks gambatte halt/m2irq_ly tests when always applied but fix other gambatte halt/m2irq and halt/m2int cases) + // No delay: + // - LY=LYC irq + // STAT and not vblank just triggered (this on dmg/mgb/sgb only)? or Timer IRQ + // + // This is a bit hacky, more testing is needed to determine exact + // hardware behavior. + if ((irqline == 1 && !(m_IF & 0x01)) || irqline == 2) + { + // Cycles needed for leaving the halt state + cycles_passed(4); + if (irqline == 2) + { + cycles_passed(2); + } } } else { /* New cpu core (cgb/agb/ags) */ - /* Adjust for internal syncing with video core */ - /* This feature needs more investigation */ - if ( irqline < 2 ) { - cycles_passed( 4 ); + // Leaving halt state seems to take 4 cycles. + cycles_passed(4); + if (!(m_enable & IME) && !m_entering_halt) + { + cycles_passed(4); } } } @@ -284,6 +311,9 @@ void lr35902_cpu_device::check_interrupts() mem_write_word( m_SP, m_PC ); m_PC = 0x40 + irqline * 8; /*logerror("LR35902 Interrupt PC $%04X\n", m_PC );*/ + if (was_halted) { + m_op = mem_read_byte( m_PC ); + } return; } } @@ -299,32 +329,50 @@ void lr35902_cpu_device::execute_run() { do { - if ( m_execution_state ) { - UINT8 x; - /* Execute instruction */ - switch( m_op ) { -#include "opc_main.hxx" - default: - // actually this should lock up the cpu! - logerror("LR35902: Illegal opcode $%02X @ %04X\n", m_op, m_PC); - break; + if (m_dma_cycles_to_burn > 0) + { + if (m_dma_cycles_to_burn < 4) + { + cycles_passed(m_dma_cycles_to_burn); + m_dma_cycles_to_burn = 0; + } + else + { + cycles_passed(4); + m_dma_cycles_to_burn -= 4; } - } else { - /* Fetch and count cycles */ - check_interrupts(); - debugger_instruction_hook(this, m_PC); - if ( m_enable & HALTED ) { - cycles_passed( 4 ); - m_execution_state = 1; + } + else + { + if ( m_execution_state ) { + UINT8 x; + /* Execute instruction */ + switch( m_op ) { +#include "opc_main.hxx" + default: + // actually this should lock up the cpu! + logerror("LR35902: Illegal opcode $%02X @ %04X\n", m_op, m_PC); + break; + } } else { - m_op = mem_read_byte( m_PC++ ); - if ( m_handle_halt_bug ) { - m_PC--; - m_handle_halt_bug = false; + /* Fetch and count cycles */ + bool was_halted = (m_enable & HALTED); + check_interrupts(); + debugger_instruction_hook(this, m_PC); + if ( m_enable & HALTED ) { + cycles_passed(m_has_halt_bug ? 2 : 4); + m_execution_state = 1; + m_entering_halt = false; + } else { + if (was_halted) { + m_PC++; + } else { + m_op = mem_read_byte( m_PC++ ); + } } } + m_execution_state ^= 1; } - m_execution_state ^= 1; } while (m_icount > 0); } diff --git a/src/devices/cpu/lr35902/lr35902.h b/src/devices/cpu/lr35902/lr35902.h index 74ad24d07f8..326247c6ad8 100644 --- a/src/devices/cpu/lr35902/lr35902.h +++ b/src/devices/cpu/lr35902/lr35902.h @@ -45,13 +45,30 @@ public: static void set_halt_bug(device_t &device) { downcast<lr35902_cpu_device &>(device).m_has_halt_bug = true; } UINT8 get_speed(); - void set_speed( UINT8 speed_request ); + void set_speed(UINT8 speed_request); - UINT8 get_ie() { return m_IE; } - void set_ie( UINT8 data ) { m_IE = data; } + inline UINT8 get_ie() { return m_IE; } + inline void set_ie(UINT8 data) { m_IE = data; } - UINT8 get_if() { return m_IF; } - void set_if( UINT8 data ) { m_IF = data; } + inline UINT8 get_if() { return m_IF; } + inline void set_if(UINT8 data) { m_IF = data; } + + inline void dma_cycles_to_burn(UINT16 cycles_to_burn) { m_dma_cycles_to_burn += cycles_to_burn; } + + // Needed for some gameboy operation which needs to read the results + // of setting an input during the currently running timeslice. + // Can become protected again once this core becomes cycle accurate. + virtual void execute_set_input(int inputnum, int state) override; + + enum + { + /* Interrupts */ + VBL_INT = 0, /* V-Blank */ + LCD_INT = 1, /* LCD Status */ + TIM_INT = 2, /* Timer */ + SIO_INT = 3, /* Serial I/O */ + EXT_INT = 4 /* Joypad */ + }; protected: @@ -64,7 +81,6 @@ protected: virtual UINT32 execute_max_cycles() const override { return 16; } virtual UINT32 execute_input_lines() const override { return 5; } virtual void execute_run() override; - virtual void execute_set_input(int inputnum, int state) override; // device_memory_interface overrides virtual const address_space_config *memory_space_config(address_spacenum spacenum = AS_0) const override { return (spacenum == AS_PROGRAM) ? &m_program_config : nullptr; } @@ -115,8 +131,9 @@ protected: int m_gb_speed; int m_gb_speed_change_pending; int m_enable; - bool m_handle_halt_bug; bool m_has_halt_bug; + UINT32 m_dma_cycles_to_burn; + bool m_entering_halt; /* Callbacks */ devcb_write8 m_timer_func; diff --git a/src/devices/cpu/lr35902/opc_main.hxx b/src/devices/cpu/lr35902/opc_main.hxx index 16a99d1d695..029ba822427 100644 --- a/src/devices/cpu/lr35902/opc_main.hxx +++ b/src/devices/cpu/lr35902/opc_main.hxx @@ -229,8 +229,21 @@ case 0x0F: /* RRCA */ } break; case 0x10: /* STOP */ - if ( m_gb_speed_change_pending ) { - m_gb_speed = ( m_gb_speed == 1 ) ? 2 : 1; + if ( m_gb_speed_change_pending ) + { + if (m_gb_speed == 1) + { + // Quite a lot of time for a simple input clock change... + // And still not all speedchange related tests are passing. + UINT32 cycles = ( 2 * 45 + 1 ) * 65536 + 8; + + do { + cycles_passed(128); + cycles -= 128; + } while (cycles > 128); + cycles_passed(cycles); + } + m_gb_speed = ( m_gb_speed == 1 ) ? 2 : 1; } m_gb_speed_change_pending = 0; break; @@ -717,6 +730,9 @@ case 0x75: /* LD (HL),L */ break; case 0x76: /* HALT */ m_enable |= HALTED; + m_entering_halt = true; + // Prefetch the next instruction + m_op = mem_read_byte( m_PC ); m_PC--; break; case 0x77: /* LD (HL),A */ @@ -1060,16 +1076,17 @@ case 0xC4: /* CALL NZ,n16 */ if ( ! (m_F & FLAG_Z) ) { - m_SP -= 2; - mem_write_word( m_SP, m_PC ); - m_PC = addr; + // Internal delay cycles_passed( 4 ); + PUSH( m_PC >> 8, m_PC & 0xff ); + m_PC = addr; } } break; case 0xC5: /* PUSH BC */ - PUSH( m_B, m_C ); + // Internal delay cycles_passed( 4 ); + PUSH( m_B, m_C ); break; case 0xC6: /* ADD A,n8 */ @@ -1077,10 +1094,10 @@ case 0xC6: /* ADD A,n8 */ ADD_A_X (x) break; case 0xC7: /* RST 0 */ - m_SP -= 2; - mem_write_word( m_SP, m_PC ); - m_PC = 0; + // Internal delay cycles_passed( 4 ); + PUSH( m_PC >> 8, m_PC & 0xff ); + m_PC = 0; break; case 0xC8: /* RET Z */ cycles_passed( 4 ); @@ -1122,10 +1139,10 @@ case 0xCC: /* CALL Z,n16 */ if (m_F & FLAG_Z) { - m_SP -= 2; - mem_write_word( m_SP, m_PC ); - m_PC = addr; + // Internal delay cycles_passed( 4 ); + PUSH( m_PC >> 8, m_PC & 0xff ); + m_PC = addr; } } break; @@ -1134,10 +1151,11 @@ case 0xCD: /* CALL n16 */ UINT16 addr = mem_read_word( m_PC ); m_PC += 2; - m_SP -= 2; - mem_write_word( m_SP, m_PC ); - m_PC = addr; + // Internal delay cycles_passed( 4 ); + + PUSH( m_PC >> 8, m_PC & 0xff ); + m_PC = addr; } break; case 0xCE: /* ADC A,n8 */ @@ -1146,10 +1164,10 @@ case 0xCE: /* ADC A,n8 */ ADC_A_X (x) break; case 0xCF: /* RST 8 */ - m_SP -= 2; - mem_write_word( m_SP, m_PC ); - m_PC = 8; + // Internal delay cycles_passed( 4 ); + PUSH( m_PC >> 8, m_PC & 0xff ); + m_PC = 8; break; case 0xD0: /* RET NC */ cycles_passed( 4 ); @@ -1182,16 +1200,17 @@ case 0xD4: /* CALL NC,n16 */ if ( ! (m_F & FLAG_C) ) { - m_SP -= 2; - mem_write_word( m_SP, m_PC ); - m_PC = addr; + // Internal delay cycles_passed( 4 ); + PUSH( m_PC >> 8, m_PC & 0xff ); + m_PC = addr; } } break; case 0xD5: /* PUSH DE */ - PUSH( m_D, m_E ); + // Internal delay cycles_passed( 4 ); + PUSH( m_D, m_E ); break; case 0xD6: /* SUB A,n8 */ @@ -1199,10 +1218,10 @@ case 0xD6: /* SUB A,n8 */ SUB_A_X (x) break; case 0xD7: /* RST $10 */ - m_SP -= 2; - mem_write_word( m_SP, m_PC ); - m_PC = 0x10; + // Internal delay cycles_passed( 4 ); + PUSH( m_PC >> 8, m_PC & 0xff ); + m_PC = 0x10; break; case 0xD8: /* RET C */ cycles_passed( 4 ); @@ -1238,10 +1257,10 @@ case 0xDC: /* CALL C,n16 */ if (m_F & FLAG_C) { - m_SP -= 2; - mem_write_word( m_SP, m_PC ); - m_PC = addr; + // Internal delay cycles_passed( 4 ); + PUSH( m_PC >> 8, m_PC & 0xff ); + m_PC = addr; } } break; @@ -1251,10 +1270,10 @@ case 0xDE: /* SBC A,n8 */ SBC_A_X (x) break; case 0xDF: /* RST $18 */ - m_SP -= 2; - mem_write_word( m_SP, m_PC ); - m_PC = 0x18; + // Internal delay cycles_passed( 4 ); + PUSH( m_PC >> 8, m_PC & 0xff ); + m_PC = 0x18; break; case 0xE0: /* LD ($FF00+n8),A */ { @@ -1270,8 +1289,9 @@ case 0xE2: /* LD ($FF00+C),A */ mem_write_byte( 0xFF00 + m_C, m_A ); break; case 0xE5: /* PUSH HL */ - PUSH( m_H, m_L ); + // Internal delay cycles_passed( 4 ); + PUSH( m_H, m_L ); break; case 0xE6: /* AND A,n8 */ @@ -1279,10 +1299,10 @@ case 0xE6: /* AND A,n8 */ AND_A_X (x) break; case 0xE7: /* RST $20 */ - m_SP -= 2; - mem_write_word( m_SP, m_PC ); - m_PC = 0x20; + // Internal delay cycles_passed( 4 ); + PUSH( m_PC >> 8, m_PC & 0xff ); + m_PC = 0x20; break; case 0xE8: /* ADD SP,n8 */ /* @@ -1329,10 +1349,10 @@ case 0xEE: /* XOR A,n8 */ XOR_A_X (x) break; case 0xEF: /* RST $28 */ - m_SP -= 2; - mem_write_word( m_SP, m_PC ); - m_PC = 0x28; + // Internal delay cycles_passed( 4 ); + PUSH( m_PC >> 8, m_PC & 0xff ); + m_PC = 0x28; break; case 0xF0: /* LD A,($FF00+n8) */ { @@ -1353,9 +1373,10 @@ case 0xF3: /* DI */ m_enable &= ~IME; break; case 0xF5: /* PUSH AF */ + // Internal delay + cycles_passed( 4 ); m_F &= 0xF0; PUSH( m_A, m_F ); - cycles_passed( 4 ); break; case 0xF6: /* OR A,n8 */ @@ -1363,10 +1384,10 @@ case 0xF6: /* OR A,n8 */ OR_A_X (x) break; case 0xF7: /* RST $30 */ - m_SP -= 2; - mem_write_word( m_SP, m_PC ); - m_PC = 0x30; + // Internal delay cycles_passed( 4 ); + PUSH( m_PC >> 8, m_PC & 0xff ); + m_PC = 0x30; break; case 0xF8: /* LD HL,SP+n8 */ /* @@ -1422,8 +1443,8 @@ case 0xFE: /* CP A,n8 */ CP_A_X (x) break; case 0xFF: /* RST $38 */ - m_SP -= 2; - mem_write_word( m_SP, m_PC ); - m_PC = 0x38; + // Internal delay cycles_passed( 4 ); + PUSH( m_PC >> 8, m_PC & 0xff ); + m_PC = 0x38; break; diff --git a/src/devices/sound/gb.cpp b/src/devices/sound/gb.cpp index 181eb7ad2fb..5ed8ec05939 100644 --- a/src/devices/sound/gb.cpp +++ b/src/devices/sound/gb.cpp @@ -1,5 +1,6 @@ // license:BSD-3-Clause -// copyright-holders:Anthony Kruize +// copyright-holders:Wilbert Pol, Anthony Kruize +// thanks-to:Shay Green /************************************************************************************** * Game Boy sound emulation (c) Anthony Kruize (trandor@labyrinth.net.au) * @@ -38,7 +39,17 @@ * 14/5/2002 AK - Removed magic numbers in the fixed point math. * 12/6/2002 AK - Merged SOUNDx structs into one SOUND struct. * 26/10/2002 AK - Finally fixed channel 3! -* +* xx/4-5/2016 WP - Rewrote sound core. Most of the code is not optimized yet. + +TODO: +- Implement different behavior of CGB-02. +- Implement different behavior of CGB-05. +- Perform more tests on real hardware to figure out when the frequency counters are + reloaded. +- Perform more tests on real hardware to understand when changes to the noise divisor + and shift kick in. +- Optimize the channel update methods. + ***************************************************************************************/ #include "emu.h" @@ -49,53 +60,21 @@ CONSTANTS ***************************************************************************/ -#define NR10 0x00 -#define NR11 0x01 -#define NR12 0x02 -#define NR13 0x03 -#define NR14 0x04 -#define NR21 0x06 -#define NR22 0x07 -#define NR23 0x08 -#define NR24 0x09 -#define NR30 0x0A -#define NR31 0x0B -#define NR32 0x0C -#define NR33 0x0D -#define NR34 0x0E -#define NR41 0x10 -#define NR42 0x11 -#define NR43 0x12 -#define NR44 0x13 -#define NR50 0x14 -#define NR51 0x15 -#define NR52 0x16 -#define AUD3W0 0x20 -#define AUD3W1 0x21 -#define AUD3W2 0x22 -#define AUD3W3 0x23 -#define AUD3W4 0x24 -#define AUD3W5 0x25 -#define AUD3W6 0x26 -#define AUD3W7 0x27 -#define AUD3W8 0x28 -#define AUD3W9 0x29 -#define AUD3WA 0x2A -#define AUD3WB 0x2B -#define AUD3WC 0x2C -#define AUD3WD 0x2D -#define AUD3WE 0x2E -#define AUD3WF 0x2F - -#define LEFT 1 -#define RIGHT 2 -#define FIXED_POINT 16 /* Represents wave duties of 12.5%, 25%, 50% and 75% */ -static const float wave_duty_table[4] = { 8.0f, 4.0f, 2.0f, 1.33f }; - -// device type definition -const device_type GAMEBOY = &device_creator<gameboy_sound_device>; +const int gameboy_sound_device::wave_duty_table[4][8] = +{ + { -1, -1, -1, -1, -1, -1, -1, 1}, + { 1, -1, -1, -1, -1, -1, -1, 1}, + { 1, -1, -1, -1, -1, 1, 1, 1}, + { -1, 1, 1, 1, 1, 1, 1, -1} +}; + +// device type definitions +const device_type DMG_APU = &device_creator<dmg_apu_device>; +//const device_type CGB02_APU = &device_creator<cgb02_apu_device>; +const device_type CGB04_APU = &device_creator<cgb04_apu_device>; +//const device_type CGB05_APU = &device_creator<cgb05_apu_device>; //************************************************************************** // LIVE DEVICE @@ -105,30 +84,67 @@ const device_type GAMEBOY = &device_creator<gameboy_sound_device>; // gameboy_sound_device - constructor //------------------------------------------------- -gameboy_sound_device::gameboy_sound_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) - : device_t(mconfig, GAMEBOY, "LR35902 Sound", tag, owner, clock, "gameboy_sound", __FILE__), - device_sound_interface(mconfig, *this) +gameboy_sound_device::gameboy_sound_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) + : device_t(mconfig, type, name, tag, owner, clock, shortname, source) + , device_sound_interface(mconfig, *this) { } -//------------------------------------------------- -// device_config_complete - perform any -// operations now that the configuration is -// complete -//------------------------------------------------- -void gameboy_sound_device::device_config_complete() +dmg_apu_device::dmg_apu_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : gameboy_sound_device(mconfig, DMG_APU, "LR35902 APU", tag, owner, clock, "dmg_apu", __FILE__) { } + +cgb04_apu_device::cgb04_apu_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : gameboy_sound_device(mconfig, CGB04_APU, "CGB04 APU", tag, owner, clock, "cgb04_apu", __FILE__) +{ +} + + //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- +#define SAVE_CHANNEL(snd) \ + save_item(NAME(snd.reg)); \ + save_item(NAME(snd.on)); \ + save_item(NAME(snd.channel)); \ + save_item(NAME(snd.length)); \ + save_item(NAME(snd.length_mask)); \ + save_item(NAME(snd.length_counting)); \ + save_item(NAME(snd.length_enabled)); \ + save_item(NAME(snd.cycles_left)); \ + save_item(NAME(snd.duty)); \ + save_item(NAME(snd.envelope_enabled)); \ + save_item(NAME(snd.envelope_value)); \ + save_item(NAME(snd.envelope_direction)); \ + save_item(NAME(snd.envelope_time)); \ + save_item(NAME(snd.envelope_count)); \ + save_item(NAME(snd.signal)); \ + save_item(NAME(snd.frequency)); \ + save_item(NAME(snd.frequency_counter)); \ + save_item(NAME(snd.sweep_enabled)); \ + save_item(NAME(snd.sweep_neg_mode_used)); \ + save_item(NAME(snd.sweep_shift)); \ + save_item(NAME(snd.sweep_direction)); \ + save_item(NAME(snd.sweep_time)); \ + save_item(NAME(snd.sweep_count)); \ + save_item(NAME(snd.level)); \ + save_item(NAME(snd.offset)); \ + save_item(NAME(snd.duty_count)); \ + save_item(NAME(snd.current_sample)); \ + save_item(NAME(snd.sample_reading)); \ + save_item(NAME(snd.noise_short)); \ + save_item(NAME(snd.noise_lfsr)); + + void gameboy_sound_device::device_start() { m_channel = machine().sound().stream_alloc(*this, 0, 2, machine().sample_rate()); - m_rate = machine().sample_rate(); + m_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(gameboy_sound_device::timer_callback),this)); + m_timer->adjust(clocks_to_attotime(FRAME_CYCLES/128), 0, clocks_to_attotime(FRAME_CYCLES/128)); save_item(NAME(m_snd_regs)); // sound control @@ -143,104 +159,14 @@ void gameboy_sound_device::device_start() save_item(NAME(m_snd_control.mode3_right)); save_item(NAME(m_snd_control.mode4_left)); save_item(NAME(m_snd_control.mode4_right)); - // sound 1 - save_item(NAME(m_snd_1.on)); - save_item(NAME(m_snd_1.channel)); - save_item(NAME(m_snd_1.length)); - save_item(NAME(m_snd_1.pos)); - save_item(NAME(m_snd_1.period)); - save_item(NAME(m_snd_1.count)); - save_item(NAME(m_snd_1.mode)); - save_item(NAME(m_snd_1.duty)); - save_item(NAME(m_snd_1.env_value)); - save_item(NAME(m_snd_1.env_direction)); - save_item(NAME(m_snd_1.env_length)); - save_item(NAME(m_snd_1.env_count)); - save_item(NAME(m_snd_1.signal)); - save_item(NAME(m_snd_1.frequency)); - save_item(NAME(m_snd_1.swp_shift)); - save_item(NAME(m_snd_1.swp_direction)); - save_item(NAME(m_snd_1.swp_time)); - save_item(NAME(m_snd_1.swp_count)); - save_item(NAME(m_snd_1.level)); - save_item(NAME(m_snd_1.offset)); - save_item(NAME(m_snd_1.dutycount)); - save_item(NAME(m_snd_1.ply_step)); - save_item(NAME(m_snd_1.ply_value)); - // sound 2 - save_item(NAME(m_snd_2.on)); - save_item(NAME(m_snd_2.channel)); - save_item(NAME(m_snd_2.length)); - save_item(NAME(m_snd_2.pos)); - save_item(NAME(m_snd_2.period)); - save_item(NAME(m_snd_2.count)); - save_item(NAME(m_snd_2.mode)); - save_item(NAME(m_snd_2.duty)); - save_item(NAME(m_snd_2.env_value)); - save_item(NAME(m_snd_2.env_direction)); - save_item(NAME(m_snd_2.env_length)); - save_item(NAME(m_snd_2.env_count)); - save_item(NAME(m_snd_2.signal)); - save_item(NAME(m_snd_2.frequency)); - save_item(NAME(m_snd_2.swp_shift)); - save_item(NAME(m_snd_2.swp_direction)); - save_item(NAME(m_snd_2.swp_time)); - save_item(NAME(m_snd_2.swp_count)); - save_item(NAME(m_snd_2.level)); - save_item(NAME(m_snd_2.offset)); - save_item(NAME(m_snd_2.dutycount)); - save_item(NAME(m_snd_2.ply_step)); - save_item(NAME(m_snd_2.ply_value)); - // sound 3 - save_item(NAME(m_snd_3.on)); - save_item(NAME(m_snd_3.channel)); - save_item(NAME(m_snd_3.length)); - save_item(NAME(m_snd_3.pos)); - save_item(NAME(m_snd_3.period)); - save_item(NAME(m_snd_3.count)); - save_item(NAME(m_snd_3.mode)); - save_item(NAME(m_snd_3.duty)); - save_item(NAME(m_snd_3.env_value)); - save_item(NAME(m_snd_3.env_direction)); - save_item(NAME(m_snd_3.env_length)); - save_item(NAME(m_snd_3.env_count)); - save_item(NAME(m_snd_3.signal)); - save_item(NAME(m_snd_3.frequency)); - save_item(NAME(m_snd_3.swp_shift)); - save_item(NAME(m_snd_3.swp_direction)); - save_item(NAME(m_snd_3.swp_time)); - save_item(NAME(m_snd_3.swp_count)); - save_item(NAME(m_snd_3.level)); - save_item(NAME(m_snd_3.offset)); - save_item(NAME(m_snd_3.dutycount)); - save_item(NAME(m_snd_3.ply_step)); - save_item(NAME(m_snd_3.ply_value)); - // sound 4 - save_item(NAME(m_snd_4.on)); - save_item(NAME(m_snd_4.channel)); - save_item(NAME(m_snd_4.length)); - save_item(NAME(m_snd_4.pos)); - save_item(NAME(m_snd_4.period)); - save_item(NAME(m_snd_4.count)); - save_item(NAME(m_snd_4.mode)); - save_item(NAME(m_snd_4.duty)); - save_item(NAME(m_snd_4.env_value)); - save_item(NAME(m_snd_4.env_direction)); - save_item(NAME(m_snd_4.env_length)); - save_item(NAME(m_snd_4.env_count)); - save_item(NAME(m_snd_4.signal)); - save_item(NAME(m_snd_4.frequency)); - save_item(NAME(m_snd_4.swp_shift)); - save_item(NAME(m_snd_4.swp_direction)); - save_item(NAME(m_snd_4.swp_time)); - save_item(NAME(m_snd_4.swp_count)); - save_item(NAME(m_snd_4.level)); - save_item(NAME(m_snd_4.offset)); - save_item(NAME(m_snd_4.dutycount)); - save_item(NAME(m_snd_4.ply_step)); - save_item(NAME(m_snd_4.ply_value)); + + SAVE_CHANNEL(m_snd_1); + SAVE_CHANNEL(m_snd_2); + SAVE_CHANNEL(m_snd_3); + SAVE_CHANNEL(m_snd_4); } + //------------------------------------------------- // device_reset //------------------------------------------------- @@ -252,37 +178,14 @@ void gameboy_sound_device::device_reset() memset(&m_snd_3, 0, sizeof(m_snd_3)); memset(&m_snd_4, 0, sizeof(m_snd_4)); - /* Calculate the envelope and sweep tables */ - for (int i = 0; i < 8; i++) - { - m_env_length_table[i] = (i * ((1 << FIXED_POINT) / 64) * m_rate) >> FIXED_POINT; - m_swp_time_table[i] = (((i << FIXED_POINT) / 128) * m_rate) >> (FIXED_POINT - 1); - } - - /* Calculate the period tables */ - for (int i = 0; i < MAX_FREQUENCIES; i++) - { - m_period_table[i] = ((1 << FIXED_POINT) / (131072 / (2048 - i))) * m_rate; - m_period_mode3_table[i] = ((1 << FIXED_POINT) / (65536 / (2048 - i))) * m_rate; - } - /* Calculate the period table for mode 4 */ - for (int i = 0; i < 8; i++) - { - for (int j = 0; j < 16; j++) - { - // i is the dividing ratio of frequencies - // j is the shift clock frequency - m_period_mode4_table[i][j] = ((1 << FIXED_POINT) / (524288 / ((i == 0) ? 0.5 : i) / (1 << (j + 1)))) * m_rate; - } - } - - /* Calculate the length table */ - for (int i = 0; i < 64; i++) - m_length_table[i] = ((64 - i) * ((1 << FIXED_POINT)/256) * m_rate) >> FIXED_POINT; - - /* Calculate the length table for mode 3 */ - for (int i = 0; i < 256; i++) - m_length_mode3_table[i] = ((256 - i) * ((1 << FIXED_POINT)/256) * m_rate) >> FIXED_POINT; + m_snd_1.channel = 1; + m_snd_1.length_mask = 0x3F; + m_snd_2.channel = 2; + m_snd_2.length_mask = 0x3F; + m_snd_3.channel = 3; + m_snd_3.length_mask = 0xFF; + m_snd_4.channel = 4; + m_snd_4.length_mask = 0x3F; sound_w_internal(NR52, 0x00); m_snd_regs[AUD3W0] = 0xac; @@ -304,39 +207,543 @@ void gameboy_sound_device::device_reset() } +void cgb04_apu_device::device_reset() +{ + gameboy_sound_device::device_reset(); + + m_snd_regs[AUD3W0] = 0x00; + m_snd_regs[AUD3W1] = 0xFF; + m_snd_regs[AUD3W2] = 0x00; + m_snd_regs[AUD3W3] = 0xFF; + m_snd_regs[AUD3W4] = 0x00; + m_snd_regs[AUD3W5] = 0xFF; + m_snd_regs[AUD3W6] = 0x00; + m_snd_regs[AUD3W7] = 0xFF; + m_snd_regs[AUD3W8] = 0x00; + m_snd_regs[AUD3W9] = 0xFF; + m_snd_regs[AUD3WA] = 0x00; + m_snd_regs[AUD3WB] = 0xFF; + m_snd_regs[AUD3WC] = 0x00; + m_snd_regs[AUD3WD] = 0xFF; + m_snd_regs[AUD3WE] = 0x00; + m_snd_regs[AUD3WF] = 0xFF; +} + + /*************************************************************************** IMPLEMENTATION ***************************************************************************/ -READ8_MEMBER( gameboy_sound_device::wave_r ) +TIMER_CALLBACK_MEMBER(gameboy_sound_device::timer_callback) +{ + m_channel->update(); + update_state(); +} + + +void gameboy_sound_device::tick_length(struct SOUND &snd) +{ + if (snd.length_enabled) + { + snd.length = (snd.length + 1) & snd.length_mask; + if (snd.length == 0) + { + snd.on = false; + snd.length_counting = false; + } + } +} + + +INT32 gameboy_sound_device::calculate_next_sweep(struct SOUND &snd) +{ + snd.sweep_neg_mode_used = (snd.sweep_direction < 0); + INT32 new_frequency = snd.frequency + snd.sweep_direction * (snd.frequency >> snd.sweep_shift); + + if (new_frequency > 0x7FF) + { + snd.on = false; + } + + return new_frequency; +} + + +void gameboy_sound_device::apply_next_sweep(struct SOUND &snd) +{ + INT32 new_frequency = calculate_next_sweep(snd); + + if (snd.on && snd.sweep_shift > 0) + { + snd.frequency = new_frequency; + snd.reg[3] = snd.frequency & 0xFF; + } +} + + +void gameboy_sound_device::tick_sweep(struct SOUND &snd) +{ + snd.sweep_count = (snd.sweep_count - 1) & 0x07; + if (snd.sweep_count == 0) + { + snd.sweep_count = snd.sweep_time; + + if (snd.sweep_enabled && snd.sweep_time > 0) + { + apply_next_sweep(snd); + calculate_next_sweep(snd); + } + } +} + + +void gameboy_sound_device::tick_envelope(struct SOUND &snd) +{ + if (snd.envelope_enabled) + { + snd.envelope_count = (snd.envelope_count - 1) & 0x07; + + if (snd.envelope_count == 0) + { + snd.envelope_count = snd.envelope_time; + + if (snd.envelope_count) + { + INT8 new_envelope_value = snd.envelope_value + snd.envelope_direction; + + if (new_envelope_value >= 0 && new_envelope_value <= 15) + { + snd.envelope_value = new_envelope_value; + } + else + { + snd.envelope_enabled = false; + } + } + } + } +} + + +bool gameboy_sound_device::dac_enabled(struct SOUND &snd) +{ + return (snd.channel != 3) ? snd.reg[2] & 0xF8 : snd.reg[0] & 0x80; +} + + +void gameboy_sound_device::update_square_channel(struct SOUND &snd, UINT64 cycles) +{ + if (snd.on) + { + // compensate for left over cycles + if (snd.cycles_left > 0) + { + // Emit sample(s) + if (cycles <= snd.cycles_left) + { + snd.cycles_left -= cycles; + cycles = 0; + } + else + { + cycles -= snd.cycles_left; + snd.cycles_left = 0; + } + } + + while (cycles > 0) + { + // Emit sample(s) + if (cycles < 4) + { + snd.cycles_left = 4 - cycles; + cycles = 0; + } + else + { + cycles -= 4; + snd.frequency_counter = (snd.frequency_counter + 1) & 0x7FF; + if (snd.frequency_counter == 0) + { + snd.duty_count = (snd.duty_count + 1) & 0x07; + snd.signal = wave_duty_table[snd.duty][snd.duty_count]; + + // Reload frequency counter + snd.frequency_counter = snd.frequency; + } + } + } + } +} + + +void dmg_apu_device::update_wave_channel(struct SOUND &snd, UINT64 cycles) { - /* TODO: properly emulate scrambling of wave ram area when playback is active */ - return m_snd_regs[AUD3W0 + offset] | m_snd_3.on; + if (snd.on) + { + // compensate for left over cycles + if (snd.cycles_left > 0) + { + if (cycles <= snd.cycles_left) + { + // Emit samples + snd.cycles_left -= cycles; + cycles = 0; + } + else + { + // Emit samples + + cycles -= snd.cycles_left; + snd.cycles_left = 0; + } + } + + while (cycles > 0) + { + // Emit current sample + + // cycles -= 2 + if (cycles < 2) + { + snd.cycles_left = 2 - cycles; + cycles = 0; + } + else + { + cycles -= 2; + + // Calculate next state + snd.frequency_counter = (snd.frequency_counter + 1) & 0x7FF; + snd.sample_reading = false; + if (snd.frequency_counter == 0x7ff) + { + snd.offset = (snd.offset + 1) & 0x1F; + } + if (snd.frequency_counter == 0) + { + // Read next sample + snd.sample_reading = true; + snd.current_sample = m_snd_regs[AUD3W0 + (snd.offset/2)]; + if (!(snd.offset & 0x01)) + { + snd.current_sample >>= 4; + } + snd.current_sample = (snd.current_sample & 0x0F) - 8; + + snd.signal = snd.level ? snd.current_sample / (1 << (snd.level - 1)) : 0; + + // Reload frequency counter + snd.frequency_counter = snd.frequency; + } + } + } + } } + +void cgb04_apu_device::update_wave_channel(struct SOUND &snd, UINT64 cycles) +{ + if (snd.on) + { + // compensate for left over cycles + if (snd.cycles_left > 0) + { + if (cycles <= snd.cycles_left) + { + // Emit samples + snd.cycles_left -= cycles; + cycles = 0; + } + else + { + // Emit samples + + cycles -= snd.cycles_left; + snd.cycles_left = 0; + } + } + + while (cycles > 0) + { + // Emit current sample + + // cycles -= 2 + if (cycles < 2) + { + snd.cycles_left = 2 - cycles; + cycles = 0; + } + else + { + cycles -= 2; + + // Calculate next state + snd.frequency_counter = (snd.frequency_counter + 1) & 0x7FF; + snd.sample_reading = false; + if (snd.frequency_counter == 0) + { + // Read next sample + snd.sample_reading = true; + snd.offset = (snd.offset + 1) & 0x1F; + snd.current_sample = m_snd_regs[AUD3W0 + (snd.offset/2)]; + if (!(snd.offset & 0x01)) + { + snd.current_sample >>= 4; + } + snd.current_sample = (snd.current_sample & 0x0F) - 8; + + snd.signal = snd.level ? snd.current_sample / (1 << (snd.level - 1)) : 0; + + // Reload frequency counter + snd.frequency_counter = snd.frequency; + } + } + } + } +} + + +void gameboy_sound_device::update_noise_channel(struct SOUND &snd, UINT64 cycles) +{ + while (cycles > 0) + { + if (cycles < snd.cycles_left) + { + if (snd.on) + { + // generate samples + } + + snd.cycles_left -= cycles; + cycles = 0; + } + else + { + if (snd.on) + { + // generate samples + } + + cycles -= snd.cycles_left; + snd.cycles_left = noise_period_cycles(); + + /* Using a Polynomial Counter (aka Linear Feedback Shift Register) + Mode 4 has a 15 bit counter so we need to shift the + bits around accordingly */ + UINT16 feedback = ((snd.noise_lfsr >> 1) ^ snd.noise_lfsr) & 1; + snd.noise_lfsr = (snd.noise_lfsr >> 1) | (feedback << 14); + if (snd.noise_short) + { + snd.noise_lfsr = (snd.noise_lfsr & ~(1 << 6)) | (feedback << 6); + } + snd.signal = (snd.noise_lfsr & 1) ? -1 : 1; + } + } +} + + +void gameboy_sound_device::update_state() +{ + attotime now = machine().time(); + + // No time travelling + if (now <= m_last_updated) + { + return; + } + + if (m_snd_control.on) + { + UINT64 cycles = attotime_to_clocks(now - m_last_updated); + + UINT64 old_cycles = m_snd_control.cycles; + m_snd_control.cycles += cycles; + + if ((old_cycles / FRAME_CYCLES) != (m_snd_control.cycles / FRAME_CYCLES)) + { + // Left over cycles in current frame + UINT64 cycles_current_frame = FRAME_CYCLES - (old_cycles & (FRAME_CYCLES - 1)); + + update_square_channel(m_snd_1, cycles_current_frame); + update_square_channel(m_snd_2, cycles_current_frame); + update_wave_channel(m_snd_3, cycles_current_frame); + update_noise_channel(m_snd_4, cycles_current_frame); + + cycles -= cycles_current_frame; + + // Switch to next frame + switch ((m_snd_control.cycles / FRAME_CYCLES) & 0x07) + { + case 0: + // length + tick_length(m_snd_1); + tick_length(m_snd_2); + tick_length(m_snd_3); + tick_length(m_snd_4); + break; + case 2: + // sweep + tick_sweep(m_snd_1); + // length + tick_length(m_snd_1); + tick_length(m_snd_2); + tick_length(m_snd_3); + tick_length(m_snd_4); + break; + case 4: + // length + tick_length(m_snd_1); + tick_length(m_snd_2); + tick_length(m_snd_3); + tick_length(m_snd_4); + break; + case 6: + // sweep + tick_sweep(m_snd_1); + // length + tick_length(m_snd_1); + tick_length(m_snd_2); + tick_length(m_snd_3); + tick_length(m_snd_4); + break; + case 7: + // update envelope + tick_envelope(m_snd_1); + tick_envelope(m_snd_2); + tick_envelope(m_snd_4); + break; + } + } + + update_square_channel(m_snd_1, cycles); + update_square_channel(m_snd_2, cycles); + update_wave_channel(m_snd_3, cycles); + update_noise_channel(m_snd_4, cycles); + } + + m_last_updated = now; +} + + +UINT64 gameboy_sound_device::noise_period_cycles() +{ + static const int divisor[8] = { 8, 16,32, 48, 64, 80, 96, 112 }; + return divisor[m_snd_4.reg[3] & 7] << (m_snd_4.reg[3] >> 4); +} + + +READ8_MEMBER( dmg_apu_device::wave_r ) +{ + m_channel->update(); + update_state(); + + if (m_snd_3.on) + { + return m_snd_3.sample_reading ? m_snd_regs[AUD3W0 + (m_snd_3.offset/2)] : 0xFF; + } + + return m_snd_regs[AUD3W0 + offset]; +} + + +READ8_MEMBER( cgb04_apu_device::wave_r ) +{ + m_channel->update(); + update_state(); + + if (m_snd_3.on) + { + return m_snd_regs[AUD3W0 + (m_snd_3.offset/2)]; + } + + return m_snd_regs[AUD3W0 + offset]; +} + + READ8_MEMBER( gameboy_sound_device::sound_r ) { - switch (offset) + static const UINT8 read_mask[0x40] = { - case 0x05: - case 0x0f: - return 0xff; - case NR52: - return 0x70 | m_snd_regs[offset]; - default: - return m_snd_regs[offset]; + 0x80,0x3F,0x00,0xFF,0xBF,0xFF,0x3F,0x00,0xFF,0xBF,0x7F,0xFF,0x9F,0xFF,0xBF,0xFF, + 0xFF,0x00,0x00,0xBF,0x00,0x00,0x70,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + // Make sure we are up to date. + m_channel->update(); + update_state(); + + if (m_snd_control.on) + { + if (offset == NR52) + { + return (m_snd_regs[NR52]&0xf0) | (m_snd_1.on ? 1 : 0) | (m_snd_2.on ? 2 : 0) | (m_snd_3.on ? 4 : 0) | (m_snd_4.on ? 8 : 0) | 0x70; + } + return m_snd_regs[offset] | read_mask[offset & 0x3F]; + } + else + { + return read_mask[offset & 0x3F]; } } -WRITE8_MEMBER( gameboy_sound_device::wave_w ) + +WRITE8_MEMBER(dmg_apu_device::wave_w) { - m_snd_regs[AUD3W0 + offset] = data; + m_channel->update(); + update_state(); + + if (m_snd_3.on) + { + if (m_snd_3.sample_reading) + { + m_snd_regs[AUD3W0 + (m_snd_3.offset/2)] = data; + } + } + else + { + m_snd_regs[AUD3W0 + offset] = data; + } } -WRITE8_MEMBER( gameboy_sound_device::sound_w ) + +WRITE8_MEMBER(cgb04_apu_device::wave_w) +{ + m_channel->update(); + update_state(); + + if (m_snd_3.on) + { + m_snd_regs[AUD3W0 + (m_snd_3.offset/2)] = data; + } + else + { + m_snd_regs[AUD3W0 + offset] = data; + } +} + + +WRITE8_MEMBER( dmg_apu_device::sound_w ) +{ + /* change in registers so update first */ + m_channel->update(); + update_state(); + + /* Only register NR52 is accessible if the sound controller is disabled */ + if (!m_snd_control.on && offset != NR52 && offset != NR11 && offset != NR21 && offset != NR31 && offset != NR41) + return; + + sound_w_internal(offset, data); +} + + +WRITE8_MEMBER( cgb04_apu_device::sound_w ) { /* change in registers so update first */ m_channel->update(); + update_state(); /* Only register NR52 is accessible if the sound controller is disabled */ if (!m_snd_control.on && offset != NR52) @@ -346,141 +753,307 @@ WRITE8_MEMBER( gameboy_sound_device::sound_w ) } +void dmg_apu_device::corrupt_wave_ram() +{ + if (m_snd_3.offset < 8) + { + m_snd_regs[AUD3W0] = m_snd_regs[AUD3W0 + (m_snd_3.offset/2)]; + } + else + { + for (int i = 0; i < 4; i++) + { + m_snd_regs[AUD3W0 + i] = m_snd_regs[AUD3W0 + ((m_snd_3.offset / 2) & ~0x03) + i]; + } + } +} + + void gameboy_sound_device::sound_w_internal( int offset, UINT8 data ) { /* Store the value */ - m_snd_regs[offset] = data; + UINT8 old_data = m_snd_regs[offset]; + + if (m_snd_control.on) + { + m_snd_regs[offset] = data; + } switch (offset) { /*MODE 1 */ case NR10: /* Sweep (R/W) */ - m_snd_1.swp_shift = data & 0x7; - m_snd_1.swp_direction = (data & 0x8) >> 3; - m_snd_1.swp_direction |= m_snd_1.swp_direction - 1; - m_snd_1.swp_time = m_swp_time_table[ (data & 0x70) >> 4 ]; + m_snd_1.reg[0] = data; + m_snd_1.sweep_shift = data & 0x7; + m_snd_1.sweep_direction = (data & 0x8) ? -1 : 1; + m_snd_1.sweep_time = (data & 0x70) >> 4; + if ((old_data & 0x08) && !(data & 0x08) && m_snd_1.sweep_neg_mode_used) + { + m_snd_1.on = false; + } break; case NR11: /* Sound length/Wave pattern duty (R/W) */ - m_snd_1.duty = (data & 0xc0) >> 6; - m_snd_1.length = m_length_table[data & 0x3f]; + m_snd_1.reg[1] = data; + if (m_snd_control.on) + { + m_snd_1.duty = (data & 0xc0) >> 6; + } + m_snd_1.length = data & 0x3f; + m_snd_1.length_counting = true; break; case NR12: /* Envelope (R/W) */ - m_snd_1.env_value = data >> 4; - m_snd_1.env_direction = (data & 0x8) >> 3; - m_snd_1.env_direction |= m_snd_1.env_direction - 1; - m_snd_1.env_length = m_env_length_table[data & 0x7]; + m_snd_1.reg[2] = data; + m_snd_1.envelope_value = data >> 4; + m_snd_1.envelope_direction = (data & 0x8) ? 1 : -1; + m_snd_1.envelope_time = data & 0x07; + if (!dac_enabled(m_snd_1)) + { + m_snd_1.on = false; + } break; case NR13: /* Frequency lo (R/W) */ - m_snd_1.frequency = ((m_snd_regs[NR14] & 0x7) << 8) | m_snd_regs[NR13]; - m_snd_1.period = m_period_table[m_snd_1.frequency]; + m_snd_1.reg[3] = data; break; case NR14: /* Frequency hi / Initialize (R/W) */ - m_snd_1.mode = (data & 0x40) >> 6; - m_snd_1.frequency = ((m_snd_regs[NR14] & 0x7) << 8) | m_snd_regs[NR13]; - m_snd_1.period = m_period_table[m_snd_1.frequency]; - if (data & 0x80) - { - if (!m_snd_1.on) - m_snd_1.pos = 0; - m_snd_1.on = 1; - m_snd_1.count = 0; - m_snd_1.env_value = m_snd_regs[NR12] >> 4; - m_snd_1.env_count = 0; - m_snd_1.swp_count = 0; - m_snd_1.signal = 0x1; - m_snd_regs[NR52] |= 0x1; + m_snd_1.reg[4] = data; + { + bool length_was_enabled = m_snd_1.length_enabled; + + m_snd_1.length_enabled = (data & 0x40) ? true : false; + m_snd_1.frequency = ((m_snd_regs[NR14] & 0x7) << 8) | m_snd_1.reg[3]; + + if (!length_was_enabled && !(m_snd_control.cycles & FRAME_CYCLES) && m_snd_1.length_counting) + { + if (m_snd_1.length_enabled) + { + tick_length(m_snd_1); + } + } + + if (data & 0x80) + { + m_snd_1.on = true; + m_snd_1.envelope_enabled = true; + m_snd_1.envelope_value = m_snd_1.reg[2] >> 4; + m_snd_1.envelope_count = m_snd_1.envelope_time; + m_snd_1.sweep_count = m_snd_1.sweep_time; + m_snd_1.sweep_neg_mode_used = false; + m_snd_1.signal = 0; + m_snd_1.length_counting = true; + m_snd_1.frequency = ((m_snd_1.reg[4] & 0x7) << 8) | m_snd_1.reg[3]; + m_snd_1.frequency_counter = m_snd_1.frequency; + m_snd_1.cycles_left = 0; + m_snd_1.duty_count = 0; + m_snd_1.sweep_enabled = (m_snd_1.sweep_shift != 0) || (m_snd_1.sweep_time != 0); + if (!dac_enabled(m_snd_1)) + { + m_snd_1.on = false; + } + if (m_snd_1.sweep_shift > 0) + { + calculate_next_sweep(m_snd_1); + } + + if (m_snd_1.length == 0 && m_snd_1.length_enabled && !(m_snd_control.cycles & FRAME_CYCLES)) + { + tick_length(m_snd_1); + } + } } break; /*MODE 2 */ case NR21: /* Sound length/Wave pattern duty (R/W) */ - m_snd_2.duty = (data & 0xc0) >> 6; - m_snd_2.length = m_length_table[data & 0x3f]; + m_snd_2.reg[1] = data; + if (m_snd_control.on) + { + m_snd_2.duty = (data & 0xc0) >> 6; + } + m_snd_2.length = data & 0x3f; + m_snd_2.length_counting = true; break; case NR22: /* Envelope (R/W) */ - m_snd_2.env_value = data >> 4; - m_snd_2.env_direction = (data & 0x8) >> 3; - m_snd_2.env_direction |= m_snd_2.env_direction - 1; - m_snd_2.env_length = m_env_length_table[data & 0x7]; + m_snd_2.reg[2] = data; + m_snd_2.envelope_value = data >> 4; + m_snd_2.envelope_direction = (data & 0x8) ? 1 : -1; + m_snd_2.envelope_time = data & 0x07; + if (!dac_enabled(m_snd_2)) + { + m_snd_2.on = false; + } break; case NR23: /* Frequency lo (R/W) */ - m_snd_2.period = m_period_table[((m_snd_regs[NR24] & 0x7) << 8) | m_snd_regs[NR23]]; + m_snd_2.reg[3] = data; + m_snd_2.frequency = ((m_snd_2.reg[4] & 0x7) << 8) | m_snd_2.reg[3]; break; case NR24: /* Frequency hi / Initialize (R/W) */ - m_snd_2.mode = (data & 0x40) >> 6; - m_snd_2.period = m_period_table[((m_snd_regs[NR24] & 0x7) << 8) | m_snd_regs[NR23]]; - if (data & 0x80) - { - if (!m_snd_2.on) - m_snd_2.pos = 0; - m_snd_2.on = 1; - m_snd_2.count = 0; - m_snd_2.env_value = m_snd_regs[NR22] >> 4; - m_snd_2.env_count = 0; - m_snd_2.signal = 0x1; - m_snd_regs[NR52] |= 0x2; + m_snd_2.reg[4] = data; + { + bool length_was_enabled = m_snd_2.length_enabled; + + m_snd_2.length_enabled = (data & 0x40) ? true : false; + + if (!length_was_enabled && !(m_snd_control.cycles & FRAME_CYCLES) && m_snd_2.length_counting) + { + if (m_snd_2.length_enabled) + { + tick_length(m_snd_2); + } + } + + if (data & 0x80) + { + m_snd_2.on = true; + m_snd_2.envelope_enabled = true; + m_snd_2.envelope_value = m_snd_2.reg[2] >> 4; + m_snd_2.envelope_count = m_snd_2.envelope_time; + m_snd_2.frequency = ((m_snd_2.reg[4] & 0x7) << 8) | m_snd_2.reg[3]; + m_snd_2.frequency_counter = m_snd_2.frequency; + m_snd_2.cycles_left = 0; + m_snd_2.duty_count = 0; + m_snd_2.signal = 0; + m_snd_2.length_counting = true; + + if (!dac_enabled(m_snd_2)) + { + m_snd_2.on = false; + } + + if (m_snd_2.length == 0 && m_snd_2.length_enabled && !(m_snd_control.cycles & FRAME_CYCLES)) + { + tick_length(m_snd_2); + } + } } break; /*MODE 3 */ case NR30: /* Sound On/Off (R/W) */ - m_snd_3.on = (data & 0x80) >> 7; + m_snd_3.reg[0] = data; + if (!dac_enabled(m_snd_3)) + { + m_snd_3.on = false; + } break; case NR31: /* Sound Length (R/W) */ - m_snd_3.length = m_length_mode3_table[data]; + m_snd_3.reg[1] = data; + m_snd_3.length = data; + m_snd_3.length_counting = true; break; case NR32: /* Select Output Level */ + m_snd_3.reg[2] = data; m_snd_3.level = (data & 0x60) >> 5; break; case NR33: /* Frequency lo (W) */ - m_snd_3.period = m_period_mode3_table[((m_snd_regs[NR34] & 0x7) << 8) + m_snd_regs[NR33]]; + m_snd_3.reg[3] = data; + m_snd_3.frequency = ((m_snd_3.reg[4] & 0x7) << 8) + m_snd_3.reg[3]; break; case NR34: /* Frequency hi / Initialize (W) */ - m_snd_3.mode = (data & 0x40) >> 6; - m_snd_3.period = m_period_mode3_table[((m_snd_regs[NR34] & 0x7) << 8) + m_snd_regs[NR33]]; - if (data & 0x80) + m_snd_3.reg[4] = data; { - if (!m_snd_3.on) + bool length_was_enabled = m_snd_3.length_enabled; + + m_snd_3.length_enabled = (data & 0x40) ? true : false; + + if (!length_was_enabled && !(m_snd_control.cycles & FRAME_CYCLES) && m_snd_3.length_counting) + { + if (m_snd_3.length_enabled) + { + tick_length(m_snd_3); + } + } + + if (data & 0x80) { - m_snd_3.pos = 0; + if (m_snd_3.on && m_snd_3.frequency_counter == 0x7ff) + { + corrupt_wave_ram(); + } + m_snd_3.on = true; m_snd_3.offset = 0; - m_snd_3.duty = 0; + m_snd_3.duty = 1; + m_snd_3.duty_count = 0; + m_snd_3.length_counting = true; + m_snd_3.frequency = ((m_snd_3.reg[4] & 0x7) << 8) + m_snd_3.reg[3]; + m_snd_3.frequency_counter = m_snd_3.frequency; + // There is a tiny bit of delay in starting up the wave channel(?) + // + // Results from older code where corruption of wave ram was triggered when sample_reading == true: + // 4 breaks test 09 (read wram), fixes test 10 (write trigger), breaks test 12 (write wram) + // 6 fixes test 09 (read wram), breaks test 10 (write trigger), fixes test 12 (write wram) + m_snd_3.cycles_left = 0 + 6; + m_snd_3.sample_reading = false; + + if (!dac_enabled(m_snd_3)) + { + m_snd_3.on = false; + } + + if (m_snd_3.length == 0 && m_snd_3.length_enabled && !(m_snd_control.cycles & FRAME_CYCLES)) + { + tick_length(m_snd_3); + } } - m_snd_3.on = 1; - m_snd_3.count = 0; - m_snd_3.duty = 1; - m_snd_3.dutycount = 0; - m_snd_regs[NR52] |= 0x4; } break; /*MODE 4 */ case NR41: /* Sound Length (R/W) */ - m_snd_4.length = m_length_table[data & 0x3f]; + m_snd_4.reg[1] = data; + m_snd_4.length = data & 0x3f; + m_snd_4.length_counting = true; break; case NR42: /* Envelope (R/W) */ - m_snd_4.env_value = data >> 4; - m_snd_4.env_direction = (data & 0x8) >> 3; - m_snd_4.env_direction |= m_snd_4.env_direction - 1; - m_snd_4.env_length = m_env_length_table[data & 0x7]; + m_snd_4.reg[2] = data; + m_snd_4.envelope_value = data >> 4; + m_snd_4.envelope_direction = (data & 0x8) ? 1 : -1; + m_snd_4.envelope_time = data & 0x07; + if (!dac_enabled(m_snd_4)) + { + m_snd_4.on = false; + } break; case NR43: /* Polynomial Counter/Frequency */ - m_snd_4.period = m_period_mode4_table[data & 0x7][(data & 0xF0) >> 4]; - m_snd_4.ply_step = (data & 0x8) >> 3; + m_snd_4.reg[3] = data; + m_snd_4.noise_short = (data & 0x8); break; case NR44: /* Counter/Consecutive / Initialize (R/W) */ - m_snd_4.mode = (data & 0x40) >> 6; - if (data & 0x80) - { - if (!m_snd_4.on) - m_snd_4.pos = 0; - m_snd_4.on = 1; - m_snd_4.count = 0; - m_snd_4.env_value = m_snd_regs[NR42] >> 4; - m_snd_4.env_count = 0; - m_snd_4.signal = machine().rand(); - m_snd_4.ply_value = 0x7fff; - m_snd_regs[NR52] |= 0x8; + m_snd_4.reg[4] = data; + { + bool length_was_enabled = m_snd_4.length_enabled; + + m_snd_4.length_enabled = (data & 0x40) ? true : false; + + if (!length_was_enabled && !(m_snd_control.cycles & FRAME_CYCLES) && m_snd_4.length_counting) + { + if (m_snd_4.length_enabled) + { + tick_length(m_snd_4); + } + } + + if (data & 0x80) + { + m_snd_4.on = true; + m_snd_4.envelope_enabled = true; + m_snd_4.envelope_value = m_snd_4.reg[2] >> 4; + m_snd_4.envelope_count = m_snd_4.envelope_time; + m_snd_4.frequency_counter = 0; + m_snd_4.cycles_left = noise_period_cycles(); + m_snd_4.signal = -1; + m_snd_4.noise_lfsr = 0x7fff; + m_snd_4.length_counting = true; + + if (!dac_enabled(m_snd_4)) + { + m_snd_4.on = false; + } + + if (m_snd_4.length == 0 && m_snd_4.length_enabled && !(m_snd_control.cycles & FRAME_CYCLES)) + { + tick_length(m_snd_4); + } + } } break; @@ -499,123 +1072,136 @@ void gameboy_sound_device::sound_w_internal( int offset, UINT8 data ) m_snd_control.mode4_right = (data & 0x8) >> 3; m_snd_control.mode4_left = (data & 0x80) >> 7; break; - case NR52: /* Sound On/Off (R/W) */ - /* Only bit 7 is writable, writing to bits 0-3 does NOT enable or - disable sound. They are read-only */ - m_snd_control.on = (data & 0x80) >> 7; - if (!m_snd_control.on) - { - sound_w_internal(NR10, 0x80); - sound_w_internal(NR11, 0xBF); - sound_w_internal(NR12, 0xF3); - sound_w_internal(NR13, 0xFF); - sound_w_internal(NR14, 0xBF); - //sound_w_internal(NR20, 0xFF); - sound_w_internal(NR21, 0x3F); - sound_w_internal(NR22, 0x00); - sound_w_internal(NR23, 0xFF); - sound_w_internal(NR24, 0xBF); - sound_w_internal(NR30, 0x7F); - sound_w_internal(NR31, 0xFF); - sound_w_internal(NR32, 0x9F); - sound_w_internal(NR33, 0xFF); - sound_w_internal(NR34, 0xBF); - //sound_w_internal(NR40, 0xFF); - sound_w_internal(NR41, 0xFF); - sound_w_internal(NR42, 0x00); - sound_w_internal(NR43, 0x00); - sound_w_internal(NR44, 0xBF); - sound_w_internal(NR50, 0x77); - sound_w_internal(NR51, 0xF3); - m_snd_1.on = 0; - m_snd_2.on = 0; - m_snd_3.on = 0; - m_snd_4.on = 0; - m_snd_regs[offset] = 0; + case NR52: // Sound On/Off (R/W) + // Only bit 7 is writable, writing to bits 0-3 does NOT enable or disable sound. They are read-only. + if (!(data & 0x80)) + { + // On DMG the length counters are not affected and not clocked + // powering off should actually clear all registers + apu_power_off(); } + else + { + if (!m_snd_control.on) + { + // When switching on, the next step should be 0. + m_snd_control.cycles |= 7 * FRAME_CYCLES; + } + } + m_snd_control.on = (data & 0x80) ? true : false; + m_snd_regs[NR52] = data & 0x80; break; } } +void dmg_apu_device::apu_power_off() +{ + sound_w_internal(NR10, 0x00); + m_snd_1.duty = 0; + m_snd_regs[NR11] = 0; + sound_w_internal(NR12, 0x00); + sound_w_internal(NR13, 0x00); + sound_w_internal(NR14, 0x00); + m_snd_1.length_counting = false; + m_snd_1.sweep_neg_mode_used = false; + + m_snd_regs[NR21] = 0; + sound_w_internal(NR22, 0x00); + sound_w_internal(NR23, 0x00); + sound_w_internal(NR24, 0x00); + m_snd_2.length_counting = false; + + sound_w_internal(NR30, 0x00); + sound_w_internal(NR32, 0x00); + sound_w_internal(NR33, 0x00); + sound_w_internal(NR34, 0x00); + m_snd_3.length_counting = false; + m_snd_3.current_sample = 0; + + m_snd_regs[NR41] = 0; + sound_w_internal(NR42, 0x00); + sound_w_internal(NR43, 0x00); + sound_w_internal(NR44, 0x00); + m_snd_4.length_counting = false; + m_snd_4.cycles_left = noise_period_cycles(); + + m_snd_1.on = false; + m_snd_2.on = false; + m_snd_3.on = false; + m_snd_4.on = false; + + m_snd_control.wave_ram_locked = false; + + for (int i = NR44 + 1; i < NR52; i++) + { + sound_w_internal(i, 0x00); + } +} + + +void cgb04_apu_device::apu_power_off() +{ + sound_w_internal(NR10, 0x00); + m_snd_1.duty = 0; + sound_w_internal(NR11, 0x00); + sound_w_internal(NR12, 0x00); + sound_w_internal(NR13, 0x00); + sound_w_internal(NR14, 0x00); + m_snd_1.length_counting = false; + m_snd_1.sweep_neg_mode_used = false; + + sound_w_internal(NR21, 0x00); + sound_w_internal(NR22, 0x00); + sound_w_internal(NR23, 0x00); + sound_w_internal(NR24, 0x00); + m_snd_2.length_counting = false; + + sound_w_internal(NR30, 0x00); + sound_w_internal(NR31, 0x00); + sound_w_internal(NR32, 0x00); + sound_w_internal(NR33, 0x00); + sound_w_internal(NR34, 0x00); + m_snd_3.length_counting = false; + m_snd_3.current_sample = 0; + + sound_w_internal(NR41, 0x00); + sound_w_internal(NR42, 0x00); + sound_w_internal(NR43, 0x00); + sound_w_internal(NR44, 0x00); + m_snd_4.length_counting = false; + m_snd_4.cycles_left = noise_period_cycles(); + + m_snd_1.on = false; + m_snd_2.on = false; + m_snd_3.on = false; + m_snd_4.on = false; + + m_snd_control.wave_ram_locked = false; + + for (int i = NR44 + 1; i < NR52; i++) + { + sound_w_internal(i, 0x00); + } +} + + //------------------------------------------------- // sound_stream_update - handle a stream update //------------------------------------------------- void gameboy_sound_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples) { - stream_sample_t sample, left, right, mode4_mask; - while (samples-- > 0) { - left = right = 0; + stream_sample_t sample; + stream_sample_t left = 0; + stream_sample_t right = 0; /* Mode 1 - Wave with Envelope and Sweep */ if (m_snd_1.on) { - sample = m_snd_1.signal * m_snd_1.env_value; - m_snd_1.pos++; - if (m_snd_1.pos == (UINT32)(m_snd_1.period / wave_duty_table[m_snd_1.duty]) >> FIXED_POINT) - { - m_snd_1.signal = -m_snd_1.signal; - } - else if (m_snd_1.pos > (m_snd_1.period >> FIXED_POINT)) - { - m_snd_1.pos = 0; - m_snd_1.signal = -m_snd_1.signal; - } - - if (m_snd_1.length && m_snd_1.mode) - { - m_snd_1.count++; - if (m_snd_1.count >= m_snd_1.length) - { - m_snd_1.on = 0; - m_snd_regs[NR52] &= 0xFE; - } - } - - if (m_snd_1.env_length) - { - m_snd_1.env_count++; - if (m_snd_1.env_count >= m_snd_1.env_length) - { - m_snd_1.env_count = 0; - m_snd_1.env_value += m_snd_1.env_direction; - if (m_snd_1.env_value < 0) - m_snd_1.env_value = 0; - if (m_snd_1.env_value > 15) - m_snd_1.env_value = 15; - } - } - - if (m_snd_1.swp_time) - { - m_snd_1.swp_count++; - if (m_snd_1.swp_count >= m_snd_1.swp_time) - { - m_snd_1.swp_count = 0; - if (m_snd_1.swp_direction > 0) - { - m_snd_1.frequency -= m_snd_1.frequency / (1 << m_snd_1.swp_shift); - if (m_snd_1.frequency <= 0) - { - m_snd_1.on = 0; - m_snd_regs[NR52] &= 0xFE; - } - } - else - { - m_snd_1.frequency += m_snd_1.frequency / (1 << m_snd_1.swp_shift); - if (m_snd_1.frequency >= MAX_FREQUENCIES) - { - m_snd_1.frequency = MAX_FREQUENCIES - 1; - } - } - - m_snd_1.period = m_period_table[m_snd_1.frequency]; - } - } + sample = m_snd_1.signal * m_snd_1.envelope_value; if (m_snd_control.mode1_left) left += sample; @@ -626,42 +1212,7 @@ void gameboy_sound_device::sound_stream_update(sound_stream &stream, stream_samp /* Mode 2 - Wave with Envelope */ if (m_snd_2.on) { - sample = m_snd_2.signal * m_snd_2.env_value; - m_snd_2.pos++; - if( m_snd_2.pos == (UINT32)(m_snd_2.period / wave_duty_table[m_snd_2.duty]) >> FIXED_POINT) - { - m_snd_2.signal = -m_snd_2.signal; - } - else if (m_snd_2.pos > (m_snd_2.period >> FIXED_POINT)) - { - m_snd_2.pos = 0; - m_snd_2.signal = -m_snd_2.signal; - } - - if (m_snd_2.length && m_snd_2.mode) - { - m_snd_2.count++; - if (m_snd_2.count >= m_snd_2.length) - { - m_snd_2.on = 0; - m_snd_regs[NR52] &= 0xFD; - } - } - - if (m_snd_2.env_length) - { - m_snd_2.env_count++; - if (m_snd_2.env_count >= m_snd_2.env_length) - { - m_snd_2.env_count = 0; - m_snd_2.env_value += m_snd_2.env_direction; - if (m_snd_2.env_value < 0) - m_snd_2.env_value = 0; - if (m_snd_2.env_value > 15) - m_snd_2.env_value = 15; - } - } - + sample = m_snd_2.signal * m_snd_2.envelope_value; if (m_snd_control.mode2_left) left += sample; if (m_snd_control.mode2_right) @@ -671,49 +1222,7 @@ void gameboy_sound_device::sound_stream_update(sound_stream &stream, stream_samp /* Mode 3 - Wave patterns from WaveRAM */ if (m_snd_3.on) { - /* NOTE: This is extremely close, but not quite right. - The problem is for GB frequencies above 2000 the frequency gets - clipped. This is caused because m_snd_3.pos is never 0 at the test.*/ - sample = m_snd_regs[AUD3W0 + (m_snd_3.offset/2)]; - if (!(m_snd_3.offset % 2)) - { - sample >>= 4; - } - sample = (sample & 0xF) - 8; - - if (m_snd_3.level) - sample >>= (m_snd_3.level - 1); - else - sample = 0; - - m_snd_3.pos++; - if (m_snd_3.pos >= ((UINT32)(((m_snd_3.period) >> 21)) + m_snd_3.duty)) - { - m_snd_3.pos = 0; - if (m_snd_3.dutycount == ((UINT32)(((m_snd_3.period) >> FIXED_POINT)) % 32)) - { - m_snd_3.duty--; - } - m_snd_3.dutycount++; - m_snd_3.offset++; - if (m_snd_3.offset > 31) - { - m_snd_3.offset = 0; - m_snd_3.duty = 1; - m_snd_3.dutycount = 0; - } - } - - if (m_snd_3.length && m_snd_3.mode) - { - m_snd_3.count++; - if (m_snd_3.count >= m_snd_3.length) - { - m_snd_3.on = 0; - m_snd_regs[NR52] &= 0xFB; - } - } - + sample = m_snd_3.signal; if (m_snd_control.mode3_left) left += sample; if (m_snd_control.mode3_right) @@ -723,54 +1232,7 @@ void gameboy_sound_device::sound_stream_update(sound_stream &stream, stream_samp /* Mode 4 - Noise with Envelope */ if (m_snd_4.on) { - /* Similar problem to Mode 3, we seem to miss some notes */ - sample = m_snd_4.signal & m_snd_4.env_value; - m_snd_4.pos++; - if (m_snd_4.pos == (m_snd_4.period >> (FIXED_POINT + 1))) - { - /* Using a Polynomial Counter (aka Linear Feedback Shift Register) - Mode 4 has a 7 bit and 15 bit counter so we need to shift the - bits around accordingly */ - mode4_mask = (((m_snd_4.ply_value & 0x2) >> 1) ^ (m_snd_4.ply_value & 0x1)) << (m_snd_4.ply_step ? 6 : 14); - m_snd_4.ply_value >>= 1; - m_snd_4.ply_value |= mode4_mask; - m_snd_4.ply_value &= (m_snd_4.ply_step ? 0x7f : 0x7fff); - m_snd_4.signal = (INT8)m_snd_4.ply_value; - } - else if (m_snd_4.pos > (m_snd_4.period >> FIXED_POINT)) - { - m_snd_4.pos = 0; - mode4_mask = (((m_snd_4.ply_value & 0x2) >> 1) ^ (m_snd_4.ply_value & 0x1)) << (m_snd_4.ply_step ? 6 : 14); - m_snd_4.ply_value >>= 1; - m_snd_4.ply_value |= mode4_mask; - m_snd_4.ply_value &= (m_snd_4.ply_step ? 0x7f : 0x7fff); - m_snd_4.signal = (INT8)m_snd_4.ply_value; - } - - if (m_snd_4.length && m_snd_4.mode) - { - m_snd_4.count++; - if (m_snd_4.count >= m_snd_4.length) - { - m_snd_4.on = 0; - m_snd_regs[NR52] &= 0xF7; - } - } - - if (m_snd_4.env_length) - { - m_snd_4.env_count++; - if (m_snd_4.env_count >= m_snd_4.env_length) - { - m_snd_4.env_count = 0; - m_snd_4.env_value += m_snd_4.env_direction; - if (m_snd_4.env_value < 0) - m_snd_4.env_value = 0; - if (m_snd_4.env_value > 15) - m_snd_4.env_value = 15; - } - } - + sample = m_snd_4.signal * m_snd_4.envelope_value; if (m_snd_control.mode4_left) left += sample; if (m_snd_control.mode4_right) @@ -789,7 +1251,4 @@ void gameboy_sound_device::sound_stream_update(sound_stream &stream, stream_samp *(outputs[0]++) = left; *(outputs[1]++) = right; } - - m_snd_regs[NR52] = (m_snd_regs[NR52]&0xf0) | m_snd_1.on | (m_snd_2.on << 1) | (m_snd_3.on << 2) | (m_snd_4.on << 3); - } diff --git a/src/devices/sound/gb.h b/src/devices/sound/gb.h index 5ce8cb389f9..2ccc6d69d27 100644 --- a/src/devices/sound/gb.h +++ b/src/devices/sound/gb.h @@ -1,105 +1,199 @@ // license:BSD-3-Clause -// copyright-holders:Anthony Kruize +// copyright-holders:Anthony Kruize, Wilbert Pol +// thanks-to:Shay Green #ifndef __GBSOUND_H__ #define __GBSOUND_H__ -#define MAX_FREQUENCIES 2048 - - -struct SOUND -{ - /* Common */ - UINT8 on; - UINT8 channel; - INT32 length; - INT32 pos; - UINT32 period; - INT32 count; - INT8 mode; - /* Mode 1, 2, 3 */ - INT8 duty; - /* Mode 1, 2, 4 */ - INT32 env_value; - INT8 env_direction; - INT32 env_length; - INT32 env_count; - INT8 signal; - /* Mode 1 */ - UINT32 frequency; - INT32 swp_shift; - INT32 swp_direction; - INT32 swp_time; - INT32 swp_count; - /* Mode 3 */ - INT8 level; - UINT8 offset; - UINT32 dutycount; - /* Mode 4 */ - INT32 ply_step; - INT16 ply_value; -}; - -struct SOUNDC -{ - UINT8 on; - UINT8 vol_left; - UINT8 vol_right; - UINT8 mode1_left; - UINT8 mode1_right; - UINT8 mode2_left; - UINT8 mode2_right; - UINT8 mode3_left; - UINT8 mode3_right; - UINT8 mode4_left; - UINT8 mode4_right; -}; - - class gameboy_sound_device : public device_t, - public device_sound_interface + public device_sound_interface { public: gameboy_sound_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); + gameboy_sound_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); DECLARE_READ8_MEMBER(sound_r); - DECLARE_READ8_MEMBER(wave_r); - DECLARE_WRITE8_MEMBER(sound_w); - DECLARE_WRITE8_MEMBER(wave_w); + virtual DECLARE_READ8_MEMBER(wave_r) = 0; + virtual DECLARE_WRITE8_MEMBER(sound_w) = 0; + virtual DECLARE_WRITE8_MEMBER(wave_w) = 0; protected: // device-level overrides - virtual void device_config_complete() override; virtual void device_start() override; - virtual void device_reset() override; // sound stream update overrides virtual void sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples) override; -private: - void sound_w_internal(int offset, UINT8 data); +protected: + enum + { + NR10 = 0x00, + NR11 = 0x01, + NR12 = 0x02, + NR13 = 0x03, + NR14 = 0x04, + // 0x05 + NR21 = 0x06, + NR22 = 0x07, + NR23 = 0x08, + NR24 = 0x09, + NR30 = 0x0A, + NR31 = 0x0B, + NR32 = 0x0C, + NR33 = 0x0D, + NR34 = 0x0E, + // 0x0F + NR41 = 0x10, + NR42 = 0x11, + NR43 = 0x12, + NR44 = 0x13, + NR50 = 0x14, + NR51 = 0x15, + NR52 = 0x16, + // 0x17 - 0x1F + AUD3W0 = 0x20, + AUD3W1 = 0x21, + AUD3W2 = 0x22, + AUD3W3 = 0x23, + AUD3W4 = 0x24, + AUD3W5 = 0x25, + AUD3W6 = 0x26, + AUD3W7 = 0x27, + AUD3W8 = 0x28, + AUD3W9 = 0x29, + AUD3WA = 0x2A, + AUD3WB = 0x2B, + AUD3WC = 0x2C, + AUD3WD = 0x2D, + AUD3WE = 0x2E, + AUD3WF = 0x2F + }; + + static const unsigned int FRAME_CYCLES = 8192; + static const int wave_duty_table[4][8]; sound_stream *m_channel; - int m_rate; - INT32 m_env_length_table[8]; - INT32 m_swp_time_table[8]; - UINT32 m_period_table[MAX_FREQUENCIES]; - UINT32 m_period_mode3_table[MAX_FREQUENCIES]; - UINT32 m_period_mode4_table[8][16]; - UINT32 m_length_table[64]; - UINT32 m_length_mode3_table[256]; + struct SOUND + { + /* Common */ + UINT8 reg[5]; + bool on; + UINT8 channel; + UINT8 length; + UINT8 length_mask; + bool length_counting; + bool length_enabled; + /* Mode 1, 2, 3 */ + UINT64 cycles_left; + INT8 duty; + /* Mode 1, 2, 4 */ + bool envelope_enabled; + INT8 envelope_value; + INT8 envelope_direction; + UINT8 envelope_time; + UINT8 envelope_count; + INT8 signal; + /* Mode 1 */ + UINT16 frequency; + UINT16 frequency_counter; + bool sweep_enabled; + bool sweep_neg_mode_used; + UINT8 sweep_shift; + INT32 sweep_direction; + UINT8 sweep_time; + UINT8 sweep_count; + /* Mode 3 */ + UINT8 level; + UINT8 offset; + UINT32 duty_count; + INT8 current_sample; + bool sample_reading; + /* Mode 4 */ + bool noise_short; + UINT16 noise_lfsr; + }; struct SOUND m_snd_1; struct SOUND m_snd_2; struct SOUND m_snd_3; struct SOUND m_snd_4; - struct SOUNDC m_snd_control; + + struct + { + UINT8 on; + UINT8 vol_left; + UINT8 vol_right; + UINT8 mode1_left; + UINT8 mode1_right; + UINT8 mode2_left; + UINT8 mode2_right; + UINT8 mode3_left; + UINT8 mode3_right; + UINT8 mode4_left; + UINT8 mode4_right; + UINT64 cycles; + bool wave_ram_locked; + } m_snd_control; UINT8 m_snd_regs[0x30]; + attotime m_last_updated; + emu_timer *m_timer; + + virtual void apu_power_off() = 0; + void sound_w_internal(int offset, UINT8 data); + void update_square_channel(struct SOUND &snd, UINT64 cycles); + virtual void update_wave_channel(struct SOUND &snd, UINT64 cycles) = 0; + void update_noise_channel(struct SOUND &snd, UINT64 cycles); + INT32 calculate_next_sweep(struct SOUND &snd); + void apply_next_sweep(struct SOUND &snd); + void tick_length(struct SOUND &snd); + void tick_sweep(struct SOUND &snd); + void tick_envelope(struct SOUND &snd); + void update_state(); + bool dac_enabled(struct SOUND &snd); + virtual void corrupt_wave_ram() { }; + UINT64 noise_period_cycles(); + TIMER_CALLBACK_MEMBER(timer_callback); }; -extern const device_type GAMEBOY; + +class dmg_apu_device : public gameboy_sound_device +{ +public: + dmg_apu_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); + + virtual DECLARE_READ8_MEMBER(wave_r) override; + virtual DECLARE_WRITE8_MEMBER(wave_w) override; + virtual DECLARE_WRITE8_MEMBER(sound_w) override; + +protected: + virtual void apu_power_off() override; + virtual void corrupt_wave_ram() override; + virtual void update_wave_channel(struct SOUND &snd, UINT64 cycles) override; +}; + + +class cgb04_apu_device : public gameboy_sound_device +{ +public: + cgb04_apu_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); + + virtual DECLARE_READ8_MEMBER(wave_r) override; + virtual DECLARE_WRITE8_MEMBER(wave_w) override; + virtual DECLARE_WRITE8_MEMBER(sound_w) override; + +protected: + virtual void device_reset() override; + virtual void apu_power_off() override; + virtual void update_wave_channel(struct SOUND &snd, UINT64 cycles) override; +}; + + +extern const device_type DMG_APU; +//extern const device_type CGB02_APU; +extern const device_type CGB04_APU; +//extern const device_type CGB05_APU; #endif diff --git a/src/devices/video/gb_lcd.cpp b/src/devices/video/gb_lcd.cpp index e4e34e8e2e7..c9551e6348c 100644 --- a/src/devices/video/gb_lcd.cpp +++ b/src/devices/video/gb_lcd.cpp @@ -14,19 +14,152 @@ Timing is not accurate enough: - Mode 3 takes 172 cycles (measuered with logic analyzer by costis) +The following timing of the first frame when the LCD is turned on, is with +no sprites being displayed. If sprites are displayed then the timing of mode +3 and mode 0 will change. + +* LCD turn on for a DMG (on a CGB the timing is a little bit different) +our state LY Mode #Cycles +(GB_LCD_STATE_LY00_M2) 0 0 80 (?) The unit is actually in mode 0 here and the OAM area is unlocked +GB_LCD_STATE_LYXX_M3 0 3 169-172 Draw line 0 during this period + - Lock OAM area + - Lock VRAM area +GB_LCD_STATE_LYXX_M0 0 0 1 + - Unlock OAM area + - Unlock VRAM area +GB_LCD_STATE_LYXX_M0_2 0 0 199-202 + - Check for mode 0 interrupt +GB_LCD_STATE_LYXX_M0_INC 1 0 4 + - clear mode 0 interrupt + - check mode 2 interrupt + - clear LY=LYC incidence flag + - check LY=LYC interrupt +GB_LCD_STATE_LYXX_M2 1 2 8 + - clear mode 0 interrupt + - set LY=LYC incidence flag + - Lock OAM area + - latch WNDPOSY +GB_LCD_STATE_LYXX_M2_WND 1 2 72 + - check whether window could trigger this line +GB_LCD_STATE_LYXX_M3 1 3 169-172 +etc, until * * * +GB_LCD_STATE_LYXX_M0_2 143 0 199-202 +GB_LCD_STATE_LYXX_M0_INC 144 0 4 + - check mode 2 interrupt + - clear LY=LYC incidence flag + - check LY=LYC interrupt +GB_LCD_STATE_LY9X_M1 144 1 452 + - clear mode 0 interrupt + - clear mode 2 interrupt + - trigger VBlank interrupt + - check mode 1 interrupt + - set LY=LYC incidence flag + - check LY=LYC interrupt +GB_LCD_STATE_LY9X_M1_INC 145 1 4 + - clear LY=LYC incidence flag +GB_LCD_STATE_LY9X_M1 145 1 452 + - set LY=LYC incidence flag + - check LY=LYC interrupt +etc, until * 1 * +GB_LCD_STATE_LY9X_M1_INC 153 1 4 + - clear LY=LYC incidence flag +GB_LCD_STATE_LY00_M1 0 1 4 + - set LY=LYC incidence flag (for line 153) + - check LY=LYC interrupt (for line 153) +GB_LCD_STATE_LY00_M1_1 0 1 4 + - clear LY=LYC incidence flag +GB_LCD_STATE_LY00_M1_2 0 1 444 + - check LY=LYC interrupt (for line 0) + - set LY=LYC incidence flag (for line 0) +GB_LCD_STATE_LY00_M0 0 0 4 +GB_LCD_STATE_LY00_M2 0 2 8 + - clear mode 1 interrupt + - check mode 2 interrupt + - lock OAM area + - latch WNDPOSY +GB_LCD_STATE_LY00_M2_WND 0 2 72 + - check whether window could trigger this line +GB_LCD_STATE_LYXX_M3 0 3 169-172 + + + + +From kevtris' gameboy documentation: + +Accessing VRAM while drawing a regular line (window is off): +- B01 - 6 cycles ($9800, thrown away) +- B01s - 20x8 cycles ($9800, $9801, $9802, $9803, etc) +- B01s - 7.5 cycles +total: 6 + 167.5 + xscroll & 7 + + +Window is on; 0 < xwindow < $a6 +- B01 - 6 cycles ($9800, thrown away) +- B01s - 1-172 cycles (xscroll & 7 + xwindow + 1) ($9800, $9801, $9802, etc) +- W01 - 6 cycles ($9c00) +- W01s - 1.5-166.5 cycles (166.5 - xwindow) ($9c01, $9c02, $9c03, etc) +Total: 6 + 6 + 167.5 + xscroll & 7 + + +Window is on; xwindow = 0, xscroll & 7 = 0 - 6 +- B01B - 7 cycles ($9800, thrown away) +- W01 - 6 cycles ($9c00) +- W01s - 167.5 + xscroll & 7 ($9c01, $9c02, $9c03, $9c04, etc) +Total: 7 + 6 + 167.5 + xscroll & 7 + + +Window is on; xwindow = 0; xscroll & 7 = 7 +- B01B - 7 cycles ($9800, thrown away) +- W01 - 6 cycles ($9c00) +- W01s - 167.5 + xscroll & 7 + 1 cycle delay during first sprite window ($9c01, $9c02, $9c03, etc) +Total: 7 + 6 + 167.5 + xscroll & 7 + 1 + + +Window is on; xwindow = $a6 +- First scanline displays first scanline of the window +- ywindow values: + 00 : window line 0, window line 1, window line 2, window line 3 + 01 : window line 0, background line 1, window line 2, window line 3 + 02 : window line 0, background line 1, background line 2, window line 2, window line 3 + 03 : window line 0, background line 1, background line 2, background line 3, window line 2, window line 3 +- W01 - 6 cycles ($9c00, thrown away) +- W01s - 20x8 cycles ($9c01, $9c02, $9c03, $9c04, etc) +- W01s - 7.5 cycles +total: 6 + 167.5 + xscroll & 7 + + +From gambatte scx_m3_extend test a precisely triggered write can extend the m3 +period with the xscroll & 7 delay getting applied twice! +The write to the xscroll register occurs during the first B01s sequence for +this to happen or when it is almost/just done applying the xscroll & 7 cycles; +it is still unclear exactly when the xscroll delay cycles are applied: +B01 apply-scx B01sB01sB01s... +or +B01 B01 apply-scx sB01sB01s... + +The first option seems the most logical setup since the xscroll & 7 delay is +applied in all cases including when xwindow is low, like 0, 1, or 2. In those +cases the B01 from the first B01s sequence is not completed yet. Unless it is +applied but only in the form of wait cycles before the window is started. + + +TODO: +- Add sprite support to new rendering engine. +- Replace memory map during OAM operation. +- Fix more test cases. +- Convert CGB code to new rendering engine. +- Simplify code. The code was built up adding more and more support for + several cases, so it has become a bit more complex than is necessary. Once + we have a baseline for passing testcases code simplifications can be + more easily tested. + ***************************************************************************/ #include "emu.h" -//#include "cpu/lr35902/lr35902.h" #include "video/gb_lcd.h" -/* Interrupts (copied from includes/gb.h)... */ -#define VBL_INT 0 /* V-Blank */ -#define LCD_INT 1 /* LCD Status */ -#define TIM_INT 2 /* Timer */ -#define SIO_INT 3 /* Serial I/O */ -#define EXT_INT 4 /* Joypad */ - +#define ENABLE_LOGGING 0 +#define LOG(x) do { if (ENABLE_LOGGING) logerror x; } while(0) #define LCDCONT m_vid_regs[0x00] /* LCD control register */ @@ -65,14 +198,17 @@ enum { enum { GB_LCD_STATE_LYXX_M3=1, + GB_LCD_STATE_LYXX_M3_2, GB_LCD_STATE_LYXX_PRE_M0, GB_LCD_STATE_LYXX_M0, - GB_LCD_STATE_LYXX_M0_SCX3, + GB_LCD_STATE_LYXX_M0_2, GB_LCD_STATE_LYXX_M0_GBC_PAL, GB_LCD_STATE_LYXX_M0_PRE_INC, GB_LCD_STATE_LYXX_M0_INC, GB_LCD_STATE_LY00_M2, + GB_LCD_STATE_LY00_M2_WND, GB_LCD_STATE_LYXX_M2, + GB_LCD_STATE_LYXX_M2_WND, GB_LCD_STATE_LY9X_M1, GB_LCD_STATE_LY9X_M1_INC, GB_LCD_STATE_LY00_M1, @@ -152,7 +288,7 @@ static const UINT8 cgb_oam_fingerprint[0x100] = { /* For an AGS in CGB mode this data is: */ #if 0 -static const UINT8 abs_oam_fingerprint[0x100] = { +static const UINT8 ags_oam_fingerprint[0x100] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -173,40 +309,51 @@ static const UINT8 abs_oam_fingerprint[0x100] = { #endif -const device_type GB_LCD_DMG = &device_creator<gb_lcd_device>; -const device_type GB_LCD_MGB = &device_creator<mgb_lcd_device>; -const device_type GB_LCD_SGB = &device_creator<sgb_lcd_device>; -const device_type GB_LCD_CGB = &device_creator<cgb_lcd_device>; +const device_type DMG_PPU = &device_creator<dmg_ppu_device>; +const device_type MGB_PPU = &device_creator<mgb_ppu_device>; +const device_type SGB_PPU = &device_creator<sgb_ppu_device>; +const device_type CGB_PPU = &device_creator<cgb_ppu_device>; -gb_lcd_device::gb_lcd_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) - : device_t(mconfig, type, name, tag, owner, clock, shortname, source), - device_video_interface(mconfig, *this), - m_sgb_border_hack(0) +dmg_ppu_device::dmg_ppu_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, UINT32 vram_size) + : device_t(mconfig, type, name, tag, owner, clock, shortname, source) + , device_video_interface(mconfig, *this) + , m_lr35902(*this, finder_base::DUMMY_TAG) + , m_sgb_border_hack(0) + , m_enable_experimental_engine(false) + , m_oam_size(0x100) + , m_vram_size(vram_size) { } -gb_lcd_device::gb_lcd_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) - : device_t(mconfig, GB_LCD_DMG, "DMG LCD", tag, owner, clock, "dmg_lcd", __FILE__), - device_video_interface(mconfig, *this), - m_sgb_border_hack(0) +dmg_ppu_device::dmg_ppu_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : device_t(mconfig, DMG_PPU, "DMG PPU", tag, owner, clock, "dmg_ppu", __FILE__) + , device_video_interface(mconfig, *this) + , m_lr35902(*this, finder_base::DUMMY_TAG) + , m_sgb_border_hack(0) + , m_oam_size(0x100) + , m_vram_size(0x2000) { + m_enable_experimental_engine = true; } -mgb_lcd_device::mgb_lcd_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) - : gb_lcd_device(mconfig, GB_LCD_MGB, "MGB LCD", tag, owner, clock, "mgb_lcd", __FILE__) +mgb_ppu_device::mgb_ppu_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : dmg_ppu_device(mconfig, MGB_PPU, "MGB PPU", tag, owner, clock, "mgb_ppu", __FILE__, 0x2000) { + m_enable_experimental_engine = true; } -sgb_lcd_device::sgb_lcd_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) - : gb_lcd_device(mconfig, GB_LCD_SGB, "SGB LCD", tag, owner, clock, "sgb_lcd", __FILE__) +sgb_ppu_device::sgb_ppu_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : dmg_ppu_device(mconfig, SGB_PPU, "SGB PPU", tag, owner, clock, "sgb_ppu", __FILE__, 0x2000) { + m_enable_experimental_engine = false; } -cgb_lcd_device::cgb_lcd_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) - : gb_lcd_device(mconfig, GB_LCD_CGB, "CGB LCD", tag, owner, clock, "cgb_lcd", __FILE__) +cgb_ppu_device::cgb_ppu_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : dmg_ppu_device(mconfig, CGB_PPU, "CGB PPU", tag, owner, clock, "cgb_ppu", __FILE__, 0x4000) { + m_enable_experimental_engine = false; } @@ -214,17 +361,20 @@ cgb_lcd_device::cgb_lcd_device(const machine_config &mconfig, const char *tag, d // device_start - device-specific startup //------------------------------------------------- -void gb_lcd_device::common_start() +void dmg_ppu_device::common_start() { m_screen->register_screen_bitmap(m_bitmap); save_item(NAME(m_bitmap)); - m_oam = make_unique_clear<UINT8[]>(0x100); + m_oam = make_unique_clear<UINT8[]>(m_oam_size); + m_vram = make_unique_clear<UINT8[]>(m_vram_size); - machine().save().register_postload(save_prepost_delegate(FUNC(gb_lcd_device::videoptr_restore), this)); + machine().save().register_postload(save_prepost_delegate(FUNC(dmg_ppu_device::videoptr_restore), this)); + m_lcd_timer = timer_alloc(); - m_maincpu = machine().device<cpu_device>("maincpu"); + m_program_space = &m_lr35902->space(AS_PROGRAM); - save_pointer(NAME(m_oam.get()), 0x100); + save_pointer(NAME(m_oam.get()), m_oam_size); + save_pointer(NAME(m_vram.get()), m_vram_size); save_item(NAME(m_window_lines_drawn)); save_item(NAME(m_vid_regs)); save_item(NAME(m_bg_zbuf)); @@ -245,46 +395,93 @@ void gb_lcd_device::common_start() save_item(NAME(m_end_x)); save_item(NAME(m_mode)); save_item(NAME(m_state)); - save_item(NAME(m_lcd_irq_line)); - save_item(NAME(m_triggering_line_irq)); - save_item(NAME(m_line_irq)); - save_item(NAME(m_triggering_mode_irq)); - save_item(NAME(m_mode_irq)); - save_item(NAME(m_delayed_line_irq)); save_item(NAME(m_sprite_cycles)); + save_item(NAME(m_window_cycles)); save_item(NAME(m_scrollx_adjust)); save_item(NAME(m_oam_locked)); + save_item(NAME(m_oam_locked_reading)); save_item(NAME(m_vram_locked)); save_item(NAME(m_pal_locked)); save_item(NAME(m_hdma_enabled)); save_item(NAME(m_hdma_possible)); + save_item(NAME(m_hdma_cycles_to_start)); + save_item(NAME(m_hdma_length)); + save_item(NAME(m_oam_dma_start_cycles)); + save_item(NAME(m_oam_dma_cycles_left)); + save_item(NAME(m_oam_dma_source_address)); save_item(NAME(m_gbc_mode)); + save_item(NAME(m_window_x)); + save_item(NAME(m_window_y)); + save_item(NAME(m_stat_mode0_int)); + save_item(NAME(m_stat_mode1_int)); + save_item(NAME(m_stat_mode2_int)); + save_item(NAME(m_stat_lyc_int)); + save_item(NAME(m_stat_lyc_int_prev)); + save_item(NAME(m_stat_write_int)); + save_item(NAME(m_stat_int)); save_item(NAME(m_gb_tile_no_mod)); - save_item(NAME(m_vram_bank)); - + save_item(NAME(m_oam_dma_processing)); save_item(NAME(m_gb_chrgen_offs)); save_item(NAME(m_gb_bgdtab_offs)); save_item(NAME(m_gb_wndtab_offs)); save_item(NAME(m_gbc_chrgen_offs)); save_item(NAME(m_gbc_bgdtab_offs)); save_item(NAME(m_gbc_wndtab_offs)); + save_item(NAME(m_vram_bank)); + save_item(NAME(m_last_updated)); + save_item(NAME(m_cycles_left)); + save_item(NAME(m_next_state)); + save_item(NAME(m_old_curline)); + + for (int i = 0; i < ARRAY_LENGTH(m_layer); i++) { + save_item(NAME(m_layer[i].enabled), i); + save_item(NAME(m_layer[i].xindex), i); + save_item(NAME(m_layer[i].xshift), i); + save_item(NAME(m_layer[i].xstart), i); + save_item(NAME(m_layer[i].xend), i); + save_item(NAME(m_layer[i].bgline), i); + } - save_item(NAME(m_layer[0].enabled)); - save_item(NAME(m_layer[0].xindex)); - save_item(NAME(m_layer[0].xshift)); - save_item(NAME(m_layer[0].xstart)); - save_item(NAME(m_layer[0].xend)); - save_item(NAME(m_layer[0].bgline)); - save_item(NAME(m_layer[1].enabled)); - save_item(NAME(m_layer[1].xindex)); - save_item(NAME(m_layer[1].xshift)); - save_item(NAME(m_layer[1].xstart)); - save_item(NAME(m_layer[1].xend)); - save_item(NAME(m_layer[1].bgline)); + save_item(NAME(m_line.tile_cycle)); + save_item(NAME(m_line.tile_count)); + save_item(NAME(m_line.y)); + save_item(NAME(m_line.pattern_address)); + save_item(NAME(m_line.pattern)); + save_item(NAME(m_line.tile_address)); + save_item(NAME(m_line.plane0)); + save_item(NAME(m_line.plane1)); + save_item(NAME(m_line.shift_register)); + save_item(NAME(m_line.sprite_delay_cycles)); + save_item(NAME(m_line.starting)); + save_item(NAME(m_line.sequence_counter)); + save_item(NAME(m_line.drawing)); + save_item(NAME(m_line.start_drawing)); + save_item(NAME(m_line.scrollx_delay)); + save_item(NAME(m_line.scrollx_to_apply)); + save_item(NAME(m_line.pixels_drawn)); + save_item(NAME(m_line.window_compare_position)); + save_item(NAME(m_line.window_active)); + save_item(NAME(m_line.scrollx)); + save_item(NAME(m_line.window_start_y)); + save_item(NAME(m_line.window_start_x)); + save_item(NAME(m_line.window_start_y_index)); + save_item(NAME(m_line.window_enable)); + save_item(NAME(m_line.window_enable_index)); + save_item(NAME(m_line.window_should_trigger)); + for (int i = 0; i < ARRAY_LENGTH(m_line.sprite); i++) { + save_item(NAME(m_line.sprite[i].enabled), i); + save_item(NAME(m_line.sprite[i].x), i); + save_item(NAME(m_line.sprite[i].y), i); + save_item(NAME(m_line.sprite[i].pattern), i); + save_item(NAME(m_line.sprite[i].flags), i); + save_item(NAME(m_line.sprite[i].tile_plane_0), i); + save_item(NAME(m_line.sprite[i].tile_plane_1), i); + } + save_item(NAME(m_frame_window_active)); } -void gb_lcd_device::videoptr_restore() +void dmg_ppu_device::videoptr_restore() { m_layer[0].bg_map = m_vram.get() + m_gb_bgdtab_offs; m_layer[0].bg_tiles = m_vram.get() + m_gb_chrgen_offs; @@ -292,7 +489,7 @@ void gb_lcd_device::videoptr_restore() m_layer[1].bg_tiles = m_vram.get() + m_gb_chrgen_offs; } -void cgb_lcd_device::videoptr_restore() +void cgb_ppu_device::videoptr_restore() { m_layer[0].bg_map = m_vram.get() + m_gb_bgdtab_offs; m_layer[0].gbc_map = m_vram.get() + m_gbc_bgdtab_offs; @@ -301,35 +498,23 @@ void cgb_lcd_device::videoptr_restore() } -void gb_lcd_device::device_start() +void dmg_ppu_device::device_start() { common_start(); - m_lcd_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(gb_lcd_device::lcd_timer_proc),this)); - - m_vram = make_unique_clear<UINT8[]>(0x2000); - save_pointer(NAME(m_vram.get()), 0x2000); memcpy(m_oam.get(), dmg_oam_fingerprint, 0x100); } -void mgb_lcd_device::device_start() +void mgb_ppu_device::device_start() { common_start(); - m_lcd_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(mgb_lcd_device::lcd_timer_proc),this)); - - m_vram = make_unique_clear<UINT8[]>(0x2000); - save_pointer(NAME(m_vram.get()), 0x2000); memcpy(m_oam.get(), mgb_oam_fingerprint, 0x100); } -void sgb_lcd_device::device_start() +void sgb_ppu_device::device_start() { common_start(); - m_lcd_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(sgb_lcd_device::lcd_timer_proc),this)); - - m_vram = make_unique_clear<UINT8[]>(0x2000); - save_pointer(NAME(m_vram.get()), 0x2000); m_sgb_tile_data = make_unique_clear<UINT8[]>(0x2000); save_pointer(NAME(m_sgb_tile_data.get()), 0x2000); @@ -354,22 +539,22 @@ void sgb_lcd_device::device_start() save_item(NAME(m_sgb_window_mask)); } -void cgb_lcd_device::device_start() +void cgb_ppu_device::device_start() { common_start(); - m_lcd_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(cgb_lcd_device::lcd_timer_proc),this)); - - m_vram = make_unique_clear<UINT8[]>(0x4000); - save_pointer(NAME(m_vram.get()), 0x4000); memcpy(m_oam.get(), cgb_oam_fingerprint, 0x100); /* Background is initialised as white */ for (int i = 0; i < 32; i++) + { m_cgb_bpal[i] = 32767; + } /* Sprites are supposed to be uninitialized, but we'll make them black */ for (int i = 0; i < 32; i++) + { m_cgb_spal[i] = 0; + } } @@ -377,7 +562,7 @@ void cgb_lcd_device::device_start() // device_reset - device-specific reset //------------------------------------------------- -void gb_lcd_device::common_reset() +void dmg_ppu_device::common_reset() { m_window_lines_drawn = 0; @@ -389,20 +574,28 @@ void gb_lcd_device::common_reset() m_end_x = 0; m_mode = 0; m_state = 0; - m_lcd_irq_line = 0; - m_triggering_line_irq = 0; - m_line_irq = 0; - m_triggering_mode_irq = 0; - m_mode_irq = 0; - m_delayed_line_irq = 0; m_sprite_cycles = 0; + m_window_cycles = 0; m_scrollx_adjust = 0; m_oam_locked = 0; + m_oam_locked_reading = 0; m_vram_locked = 0; m_pal_locked = 0; m_gbc_mode = 0; m_gb_tile_no_mod = 0; m_vram_bank = 0; + m_oam_dma_processing = false; + m_oam_dma_start_cycles = 0; + m_oam_dma_cycles_left = 0; + m_updating_state = false; + m_stat_mode0_int = false; + m_stat_mode1_int = false; + m_stat_mode2_int = false; + m_stat_lyc_int = false; + m_stat_lyc_int_prev = false; + m_stat_write_int = false; + m_stat_int = false; + m_hdma_cycles_to_start = 0; m_gb_chrgen_offs = 0; m_gb_bgdtab_offs = 0x1c00; @@ -420,7 +613,9 @@ void gb_lcd_device::common_reset() m_vid_regs[0x06] = 0xff; for (int i = 0x0c; i < NR_GB_VID_REGS; i++) + { m_vid_regs[i] = 0xff; + } LCDSTAT = 0x80; LCDCONT = 0x00; /* Video hardware is turned off at boot time */ @@ -431,19 +626,23 @@ void gb_lcd_device::common_reset() // Initialize palette arrays for (int i = 0; i < 4; i++) + { m_gb_bpal[i] = m_gb_spal0[i] = m_gb_spal1[i] = i; + } + m_last_updated = machine().time(); } -void gb_lcd_device::device_reset() +void dmg_ppu_device::device_reset() { common_reset(); - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(456)); + m_cycles_left = 456; + m_lcd_timer->adjust(m_lr35902->cycles_to_attotime(456)); } -void sgb_lcd_device::device_reset() +void sgb_ppu_device::device_reset() { common_reset(); @@ -455,7 +654,7 @@ void sgb_lcd_device::device_reset() memset(m_sgb_atf_data, 0, sizeof(m_sgb_atf_data)); } -void cgb_lcd_device::device_reset() +void cgb_ppu_device::device_reset() { common_reset(); @@ -472,58 +671,384 @@ void cgb_lcd_device::device_reset() -inline void gb_lcd_device::plot_pixel(bitmap_ind16 &bitmap, int x, int y, UINT32 color) +inline void dmg_ppu_device::plot_pixel(int x, int y, UINT16 color) +{ + m_bitmap.pix16(y, x) = color; +} + + +void dmg_ppu_device::calculate_window_cycles() +{ + m_window_cycles = 0; + + LOG(("m_window_x = %d, m_window_y = %d\n", m_window_x, m_window_y)); + + if ((LCDCONT & WINDOW_ENABLED) && m_window_x < 167 && m_window_y < 144) + { + // This is not good enough yet + m_window_cycles = 4; + if (m_window_x == 0x0f) + { + m_window_cycles = 12; + } + } +} + + +void dmg_ppu_device::clear_line_state() { - bitmap.pix16(y, x) = (UINT16)color; + for (int i = 0; i < 10; i++) + { + m_line.sprite[i].enabled = false; + } + m_line.sprite_delay_cycles = 0; + m_line.starting = true; + m_line.sequence_counter = 0; + m_line.start_drawing = false; + m_line.drawing = false; + m_line.scrollx_delay = 0; + m_line.scrollx_to_apply = 0; + m_line.pixels_drawn = 0; + m_line.tile_count = SCROLLX >> 3; + m_line.tile_cycle = 0; + m_line.window_compare_position = 0x100; + m_line.window_active = false; + m_line.window_should_trigger = false; + + if (m_enable_experimental_engine) + { + m_scrollx_adjust = 0; + } } + /* - Select which sprites should be drawn for the current scanline and return the - number of sprites selected. + Select which sprites should be drawn for the current scanline. */ -void gb_lcd_device::select_sprites() +void dmg_ppu_device::select_sprites() { - int /*yindex,*/ line, height; - UINT8 *oam = m_oam.get() + 39 * 4; - m_sprCount = 0; + m_sprite_cycles = 0; /* If video hardware is enabled and sprites are enabled */ - if ((LCDCONT & 0x80) && (LCDCONT & 0x02)) + if ((LCDCONT & ENABLED) && (LCDCONT & SPRITES_ENABLED)) { - /* Check for stretched sprites */ - if (LCDCONT & 0x04) - height = 16; - else - height = 8; + UINT8 sprite_occurs[32]; + + memset(sprite_occurs, 0, sizeof(sprite_occurs)); - //yindex = m_current_line; - line = m_current_line + 16; + /* Check for stretched sprites */ + int height = (LCDCONT & 0x04) ? 16 : 8; + int line = m_current_line + 16; - for (int i = 39; i >= 0; i--) + for (int i = 0; i < 160; i+= 4) { - if (line >= oam[0] && line < (oam[0] + height) && oam[1] && oam[1] < 168) + if (line >= m_oam[i] && line < (m_oam[i] + height)) { - /* We limit the sprite count to max 10 here; - proper games should not exceed this... */ if (m_sprCount < 10) { - m_sprite[m_sprCount] = i; + m_sprite[m_sprCount] = i / 4; + + if (m_oam[i + 1] < 168) + { + m_line.sprite[m_sprCount].enabled = true; + m_line.sprite[m_sprCount].y = m_oam[i]; + m_line.sprite[m_sprCount].x = m_oam[i + 1]; + m_line.sprite[m_sprCount].pattern = m_oam[i + 2]; + m_line.sprite[m_sprCount].flags = m_oam[i + 3]; + + // X=0 is special + int spr_x = m_oam[i + 1] ? m_oam[i + 1] + (SCROLLX & 0x07) : 0; + + if (sprite_occurs[spr_x >> 3]) + { + m_sprite_cycles += 3; + } + m_sprite_cycles += 3; + + sprite_occurs[spr_x >> 3] |= (1 << (spr_x & 0x07)); + } + m_sprCount++; } } - oam -= 4; } + + if (m_sprCount > 0) + { + for (int i = 0; i < 22; i++) + { + if (sprite_occurs[i]) + { + LOG(("sprite_occurs[%d] = %02x\n", i, sprite_occurs[i])); + } + + if (sprite_occurs[i]) + { + static int cycles[32] = + { + 3, 8, 7, 8, 6, 8, 7, 8, 5, 8, 7, 8, 6, 8, 7, 8, + 4, 8, 7, 8, 6, 8, 7, 8, 5, 8, 7, 8, 6, 8, 7, 8 + }; + m_sprite_cycles += cycles[sprite_occurs[i] & 0x1f]; + } + } + + LOG(("m_sprite_cycles = %d\n", m_sprite_cycles)); + } + } +} + + +void dmg_ppu_device::update_line_state(UINT64 cycles) +{ + // Can the bg tilemap select bit be changed while drawing the screen? => yes + // Can scroll-y be changed while drawing the screen? => yes + // Can bits 3-8 of scroll-x be changed while drawing the screen? => yes + + while (cycles > 0 && m_line.pixels_drawn < 160) + { + // Not sure if delaying during the first B01s sequence is the right time + if (m_line.scrollx_delay > 0) + { + m_line.scrollx_delay -= 1; + m_line.scrollx_to_apply++; + } + +//LOG(("tile_cycle = %u, starting = %s, drawing = %s\n", m_line.tile_cycle, m_line.starting ? "true" : "false", m_line.drawing ? "true" : "false")); + // output next pixel + if (m_line.drawing) + { + if (m_line.scrollx_to_apply > 0) + { + // TODO: Determine when the scrollx shifts are applied when window-x is <= 0x07 +//logerror("scrollx_to_apply: %u\n", m_line.scrollx_to_apply); + if (!m_line.window_active) + { + m_line.shift_register <<= 2; + } + m_line.window_compare_position--; + m_line.scrollx_to_apply--; + m_cycles_left++; + m_scrollx_adjust++; + } + else + { + if (!m_line.starting && m_line.tile_cycle < 8) + { + if (m_line.pixels_drawn < 8) + { + LOG(("draw pixel %u\n", m_line.pixels_drawn)); + } + plot_pixel(m_line.pixels_drawn, m_current_line, m_gb_bpal[m_line.shift_register >> 14]); + m_bg_zbuf[m_line.pixels_drawn] = m_line.shift_register >> 14; + m_line.shift_register <<= 2; + m_line.pixels_drawn++; + + if (m_line.pixels_drawn == 160 && (LCDCONT & SPRITES_ENABLED)) + { + update_sprites(); + } + } + } + } + + UINT8 next_tile_cycle = m_line.tile_cycle + 1; + + switch (m_line.tile_cycle) + { + case 0: // Set pattern address, latch data into shift register(s) + if (!m_line.window_active && !(LCDCONT & BACKGROUND_ENABLED)) + { + m_line.shift_register = 0; + } + else + { + // Interleave bits from plane0 and plane1 + m_line.shift_register = (((((m_line.plane0 * U64(0x0101010101010101)) & U64(0x8040201008040201)) * U64(0x0102040810204081)) >> 49) & 0x5555) + | (((((m_line.plane1 * U64(0x0101010101010101)) & U64(0x8040201008040201)) * U64(0x0102040810204081)) >> 48) & 0xAAAA); + } + if (m_line.pixels_drawn < 8) + { + LOG(("m_current_line: %u, tile_count: %02x, plane0 = %02x, plane1 = %02x, shift_register = %04x\n", m_current_line, m_line.tile_count, m_line.plane0, m_line.plane1, m_line.shift_register)); + } + if (m_line.sequence_counter >= 2) + { + if (!m_line.starting) + { + m_line.drawing = true; + } + } + else if (m_line.sequence_counter == 1) + { + // start counting for start of window + m_line.window_compare_position = 0; + } + m_line.sequence_counter++; + if (m_line.window_active) + { + m_line.y = m_window_lines_drawn; + m_line.pattern_address = m_gb_wndtab_offs | ((m_line.y & 0xF8) << 2) | (m_line.tile_count & 0x1f); + } + else + { + m_line.y = SCROLLY + m_current_line; + m_line.pattern_address = m_gb_bgdtab_offs | ((m_line.y & 0xF8) << 2) | (((SCROLLX >> 3) + m_line.tile_count) & 0x1f); + } + + m_line.tile_count++; + break; + + case 1: // Read pattern data + m_line.pattern = m_vram.get()[m_line.pattern_address] ^ m_gb_tile_no_mod; + if (m_line.tile_count < 8) + { + LOG(("tile_count = %u, y = %u, pattern = %02x, pattern_address = %04x\n", m_line.tile_count, m_current_line, m_line.pattern, m_line.pattern_address)); + } + break; + + case 2: // Set plane 0 address + m_line.tile_address = m_gb_chrgen_offs + ((m_line.pattern << 4) | ((m_line.y & 0x07) << 1)); + if (m_line.tile_count < 8) + { + LOG(("tile_count = %u, tile_address = %04x, pattern = %02x, y = %u, m_gb_chrgen_offs = %04x\n", m_line.tile_count, m_line.tile_address, m_line.pattern, m_line.y & 7, m_gb_chrgen_offs)); + } + break; + + case 3: // Read plane 0 data + m_line.plane0 = m_vram.get()[m_line.tile_address]; + if (m_line.starting && !m_line.window_active) + { + m_line.scrollx = SCROLLX; + } + break; + + case 4: // Set plane 1 address + m_line.tile_address = m_line.tile_address + 1; + break; + + case 5: // Read plane 1 data + m_line.plane1 = m_vram.get()[m_line.tile_address]; + if (m_line.starting) + { + // TODO: (review) Do not reset tile_count and scroll when restarting for window + // TODO: Check for window at pos 0 + if (m_line.window_active) + { + // Force line_drawing to true + m_line.sequence_counter = 2; + if (m_line.scrollx_delay > 0) + { + } + } + else + { + m_line.tile_count = 0; + m_line.scrollx_delay = m_line.scrollx & 0x07; + } + + m_line.starting = false; + next_tile_cycle = 0; + } + break; + + case 6: // sprite stuff + break; + + case 7: // more sprite stuff + if (m_line.sprite_delay_cycles == 0) + { + next_tile_cycle &= 7; + } + break; + + case 8: // even more sprite stuff/delay + m_line.sprite_delay_cycles--; + m_cycles_left++; + m_sprite_cycles++; + next_tile_cycle = m_line.sprite_delay_cycles == 0 ? 0 : 8; + break; + + default: + next_tile_cycle &= 7; + break; + } + m_line.tile_cycle = next_tile_cycle; + cycles--; + + check_start_of_window(); + } + + if (m_line.pixels_drawn == 160 && m_line.window_active) + { + m_window_lines_drawn++; + m_line.pixels_drawn++; + m_line.window_active = false; } } -void gb_lcd_device::update_sprites() + +void dmg_ppu_device::check_start_of_window() { - bitmap_ind16 &bitmap = m_bitmap; - UINT8 height, tilemask, line, *oam, *vram; +// +// WY=1 +// ly = 2 +// late_enable_afterVblank_3_dmg08_cgb04c_out3.gbc - 4 cycles into M2 -> gb+cgb: triggering in both line 2 and 3 still returns STAT mode 0 +// late_enable_afterVblank_4_dmg08_out3_cgb04c_out0.gbc - start of M2 -> gb: should not trigger, cgb: should trigger +// late_enable_afterVblank_5_dmg08_cgb04c_out0.gbc - enable 4 cycles before M2 -> gb+cgb: should trigger on line 2 and 3 +// +// Mid frame enable: +// - at start of frame window disabled +// - enabling at line of WY triggers when window is enabled before M2 of WY + 1 + + // Check for start of window + if (m_line.window_compare_position < 16) + { + LOG(("check window this line, m_current_line = %u, WNDPOSY = %u, WNDPOSX = %u, m_line.window_compare_position = %u, tile_cycle = %u, window_start_y = %u, pixels_drawn = %u\n", m_current_line, WNDPOSY, WNDPOSX, m_line.window_compare_position, m_line.tile_cycle, m_line.window_start_y[m_line.window_start_y_index], m_line.pixels_drawn)); + } + + if (/*LCDCONT*/(m_line.window_enable[m_line.window_enable_index] & WINDOW_ENABLED) && !m_line.window_active && (m_frame_window_active || /*m_current_line >= m_window_y*/ m_line.window_should_trigger || m_current_line == m_line.window_start_y[m_line.window_start_y_index]) && m_line.window_compare_position == /*WNDPOSX*/ m_line.window_start_x[m_line.window_start_y_index] && m_line.window_compare_position < 0xA6) + { +LOG(("enable window, m_current_line = %u, WNDPOSY = %u, WNDPOSX = %u, m_line.window_compare_position = %u, pixels_drawn = %u\n", m_current_line, WNDPOSY, WNDPOSX, m_line.window_compare_position, m_line.pixels_drawn)); + m_line.starting = true; + m_line.window_active = true; + m_frame_window_active = true; + m_line.tile_cycle = 0; + m_line.tile_count = 0; + m_window_cycles = 6; + m_cycles_left += 6; + + if (m_line.window_start_x[m_line.window_start_y_index] == 0) + { + // TODO: WX=00 should trigger a 1 cycle delay during the first sprite frame + // Does not fix failing late_scx_late_wy_FFto4_ly4_wx00_2_dmg08_out3_cgb04c_out0.gbc yet + m_line.sprite_delay_cycles += 1; + } + } + + // 4 makes most tests pass + m_line.window_start_y[(m_line.window_start_y_index + 4) % ARRAY_LENGTH(m_line.window_start_y)] = WNDPOSY; + // 2-4 makes most tests pass + m_line.window_start_x[(m_line.window_start_y_index + 4) % ARRAY_LENGTH(m_line.window_start_x)] = WNDPOSX; + m_line.window_start_y_index = (m_line.window_start_y_index + 1) % ARRAY_LENGTH(m_line.window_start_y); + + // 3 makes most tests pass + m_line.window_enable[(m_line.window_enable_index + 3) % ARRAY_LENGTH(m_line.window_enable)] = LCDCONT; + m_line.window_enable_index = (m_line.window_enable_index + 1) % ARRAY_LENGTH(m_line.window_enable); + + if (!m_line.starting && m_line.tile_cycle < 8) + { + m_line.window_compare_position++; + } +} + + +void dmg_ppu_device::update_sprites() +{ + UINT8 height, tilemask, line, *vram; int yindex; - if (LCDCONT & 0x04) + if (LCDCONT & LARGE_SPRITES) { height = 16; tilemask = 0xFE; @@ -537,95 +1062,95 @@ void gb_lcd_device::update_sprites() yindex = m_current_line; line = m_current_line + 16; - oam = m_oam.get() + 39 * 4; vram = m_vram.get(); - for (int i = 39; i >= 0; i--) + for (int i = m_sprCount - 1; i >= 0; i--) { - /* if sprite is on current line && x-coordinate && x-coordinate is < 168 */ - if (line >= oam[0] && line < (oam[0] + height) && oam[1] && oam[1] < 168) + int oam_address = m_sprite[i] * 4; + UINT16 *spal = (m_oam[oam_address + 3] & 0x10) ? m_gb_spal1 : m_gb_spal0; + int xindex = m_oam[oam_address + 1] - 8; + int adr = (m_oam[oam_address + 2] & tilemask) * 16; + + if (xindex < -7 || xindex > 160) { - UINT16 data; - UINT8 bit, *spal; - int xindex, adr; + continue; + } - spal = (oam[3] & 0x10) ? m_gb_spal1 : m_gb_spal0; - xindex = oam[1] - 8; - if (oam[3] & 0x40) /* flip y ? */ + if (m_oam[oam_address + 3] & 0x40) /* flip y ? */ + { + adr += (height - 1 - line + m_oam[oam_address]) * 2; + } + else + { + adr += (line - m_oam[oam_address]) * 2; + } + UINT16 data = (vram[adr + 1] << 8) | vram[adr]; + + switch (m_oam[oam_address + 3] & 0xA0) + { + case 0xA0: /* priority is set (behind bgnd & wnd, flip x) */ + for (int bit = 0; bit < 8; bit++, xindex++) { - adr = (oam[2] & tilemask) * 16 + (height - 1 - line + oam[0]) * 2; + int colour = ((data & 0x0100) ? 2 : 0) | ((data & 0x0001) ? 1 : 0); + if (colour && !m_bg_zbuf[xindex] && xindex >= 0 && xindex < 160) + plot_pixel(xindex, yindex, spal[colour]); + data >>= 1; } - else + break; + case 0x20: /* priority is not set (overlaps bgnd & wnd, flip x) */ + for (int bit = 0; bit < 8; bit++, xindex++) { - adr = (oam[2] & tilemask) * 16 + (line - oam[0]) * 2; + int colour = ((data & 0x0100) ? 2 : 0) | ((data & 0x0001) ? 1 : 0); + if (colour && xindex >= 0 && xindex < 160) + plot_pixel(xindex, yindex, spal[colour]); + data >>= 1; } - data = (vram[adr + 1] << 8) | vram[adr]; - - switch (oam[3] & 0xA0) + break; + case 0x80: /* priority is set (behind bgnd & wnd, don't flip x) */ + for (int bit = 0; bit < 8 && xindex < 160; bit++, xindex++) { - case 0xA0: /* priority is set (behind bgnd & wnd, flip x) */ - for (bit = 0; bit < 8; bit++, xindex++) - { - int colour = ((data & 0x0100) ? 2 : 0) | ((data & 0x0001) ? 1 : 0); - if (colour && !m_bg_zbuf[xindex] && xindex >= 0 && xindex < 160) - plot_pixel(bitmap, xindex, yindex, spal[colour]); - data >>= 1; - } - break; - case 0x20: /* priority is not set (overlaps bgnd & wnd, flip x) */ - for (bit = 0; bit < 8; bit++, xindex++) - { - int colour = ((data & 0x0100) ? 2 : 0) | ((data & 0x0001) ? 1 : 0); - if (colour && xindex >= 0 && xindex < 160) - plot_pixel(bitmap, xindex, yindex, spal[colour]); - data >>= 1; - } - break; - case 0x80: /* priority is set (behind bgnd & wnd, don't flip x) */ - for (bit = 0; bit < 8 && xindex < 160; bit++, xindex++) - { - int colour = ((data & 0x8000) ? 2 : 0) | ((data & 0x0080) ? 1 : 0); - if (colour && !m_bg_zbuf[xindex] && xindex >= 0 && xindex < 160) - plot_pixel(bitmap, xindex, yindex, spal[colour]); - data <<= 1; - } - break; - case 0x00: /* priority is not set (overlaps bgnd & wnd, don't flip x) */ - for (bit = 0; bit < 8 && xindex < 160; bit++, xindex++) - { - int colour = ((data & 0x8000) ? 2 : 0) | ((data & 0x0080) ? 1 : 0); - if (colour && xindex >= 0 && xindex < 160) - plot_pixel(bitmap, xindex, yindex, spal[colour]); - data <<= 1; - } - break; + int colour = ((data & 0x8000) ? 2 : 0) | ((data & 0x0080) ? 1 : 0); + if (colour && !m_bg_zbuf[xindex] && xindex >= 0 && xindex < 160) + plot_pixel(xindex, yindex, spal[colour]); + data <<= 1; } + break; + case 0x00: /* priority is not set (overlaps bgnd & wnd, don't flip x) */ + for (int bit = 0; bit < 8 && xindex < 160; bit++, xindex++) + { + int colour = ((data & 0x8000) ? 2 : 0) | ((data & 0x0080) ? 1 : 0); + if (colour && xindex >= 0 && xindex < 160) + plot_pixel(xindex, yindex, spal[colour]); + data <<= 1; + } + break; } - oam -= 4; } } -void gb_lcd_device::update_scanline() + +void dmg_ppu_device::update_scanline(UINT32 cycles_to_go) { - bitmap_ind16 &bitmap = m_bitmap; + if (m_enable_experimental_engine) + { + return; + } g_profiler.start(PROFILER_VIDEO); /* Make sure we're in mode 3 */ if ((LCDSTAT & 0x03) == 0x03) { - /* Calculate number of pixels to render based on time still left on the timer */ - UINT32 cycles_to_go = m_maincpu->attotime_to_cycles(m_lcd_timer->remaining()); int l = 0; if (m_start_x < 0) { /* Window is enabled if the hardware says so AND the current scanline is * within the window AND the window X coordinate is <=166 */ - m_layer[1].enabled = ((LCDCONT & 0x20) && (m_current_line >= WNDPOSY) && (WNDPOSX <= 166)) ? 1 : 0; + m_layer[1].enabled = ((LCDCONT & WINDOW_ENABLED) && (m_current_line >= m_window_y) && (m_window_x <= 166)) ? 1 : 0; /* BG is enabled if the hardware says so AND (window_off OR (window_on * AND window's X position is >=7)) */ - m_layer[0].enabled = ((LCDCONT & 0x01) && ((!m_layer[1].enabled) || (m_layer[1].enabled && (WNDPOSX >= 7)))) ? 1 : 0; + m_layer[0].enabled = ((LCDCONT & BACKGROUND_ENABLED) && ((!m_layer[1].enabled) || (m_layer[1].enabled && (m_window_x >= 7)))) ? 1 : 0; if (m_layer[0].enabled) { @@ -640,7 +1165,7 @@ void gb_lcd_device::update_scanline() if (m_layer[1].enabled) { - int xpos = WNDPOSX - 7; /* Window is offset by 7 pixels */ + int xpos = m_window_x - 7; /* Window is offset by 7 pixels */ if (xpos < 0) xpos = 0; @@ -660,10 +1185,10 @@ void gb_lcd_device::update_scanline() { m_end_x = std::min(int(160 - cycles_to_go), 160); /* Draw empty pixels when the background is disabled */ - if (!(LCDCONT & 0x01)) + if (!(LCDCONT & BACKGROUND_ENABLED)) { rectangle r(m_start_x, m_end_x - 1, m_current_line, m_current_line); - bitmap.fill(m_gb_bpal[0], r); + m_bitmap.fill(m_gb_bpal[0], r); } while (l < 2) { @@ -695,7 +1220,7 @@ void gb_lcd_device::update_scanline() while ((m_layer[l].xshift < 8) && i) { int colour = ((data & 0x8000) ? 2 : 0) | ((data & 0x0080) ? 1 : 0); - plot_pixel(bitmap, xindex, m_current_line, m_gb_bpal[colour]); + plot_pixel(xindex, m_current_line, m_gb_bpal[colour]); m_bg_zbuf[xindex] = colour; xindex++; data <<= 1; @@ -720,7 +1245,7 @@ void gb_lcd_device::update_scanline() } l++; } - if (m_end_x == 160 && LCDCONT & 0x02) + if (m_end_x == 160 && LCDCONT & SPRITES_ENABLED) { update_sprites(); } @@ -729,7 +1254,7 @@ void gb_lcd_device::update_scanline() } else { - if (!(LCDCONT & 0x80)) + if (!(LCDCONT & ENABLED)) { /* Draw an empty line when LCD is disabled */ if (m_previous_line != m_current_line) @@ -738,7 +1263,7 @@ void gb_lcd_device::update_scanline() { const rectangle &r = m_screen->visible_area(); rectangle r1(r.min_x, r.max_x, m_current_line, m_current_line); - bitmap.fill(0, r1); + m_bitmap.fill(0, r1); } m_previous_line = m_current_line; } @@ -750,13 +1275,12 @@ void gb_lcd_device::update_scanline() /* --- Super Game Boy Specific --- */ -void sgb_lcd_device::update_sprites() +void sgb_ppu_device::update_sprites() { - bitmap_ind16 &bitmap = m_bitmap; - UINT8 height, tilemask, line, *oam, *vram, pal; + UINT8 height, tilemask, line, *vram; INT16 yindex; - if (LCDCONT & 0x04) + if (LCDCONT & LARGE_SPRITES) { height = 16; tilemask = 0xFE; @@ -771,83 +1295,76 @@ void sgb_lcd_device::update_sprites() yindex = m_current_line + SGB_YOFFSET; line = m_current_line + 16; - oam = m_oam.get() + 39 * 4; vram = m_vram.get(); - for (int i = 39; i >= 0; i--) + + for (int i = m_sprCount - 1; i >= 0; i--) { - /* if sprite is on current line && x-coordinate && x-coordinate is < 168 */ - if (line >= oam[0] && line < (oam[0] + height) && oam[1] && oam[1] < 168) + int oam_address = m_sprite[i] * 4; + int adr = (m_oam[oam_address + 2] & tilemask) * 16; + UINT16 *spal = (m_oam[oam_address + 3] & 0x10) ? m_gb_spal1 : m_gb_spal0; + UINT16 xindex = m_oam[oam_address + 1] - 8; + + if (m_oam[oam_address + 3] & 0x40) /* flip y ? */ { - UINT16 data; - UINT8 bit, *spal; - INT16 xindex; - int adr; + adr += (height - 1 - line + m_oam[oam_address]) * 2; + } + else + { + adr += (line - m_oam[oam_address]) * 2; + } + UINT16 data = (vram[adr + 1] << 8) | vram[adr]; - spal = (oam[3] & 0x10) ? m_gb_spal1 : m_gb_spal0; - xindex = oam[1] - 8; - if (oam[3] & 0x40) /* flip y ? */ + /* Find the palette to use */ + // If sprite started before the start of the line we may need to pick a different pal_map entry? + UINT8 pal = m_sgb_pal_map[(xindex < 0) ? 0 : (xindex >> 3)][((yindex - SGB_YOFFSET) >> 3)] << 2; + + /* Offset to center of screen */ + xindex += SGB_XOFFSET; + + switch (m_oam[oam_address + 3] & 0xA0) + { + case 0xA0: /* priority is set (behind bgnd & wnd, flip x) */ + for (int bit = 0; bit < 8; bit++, xindex++) { - adr = (oam[2] & tilemask) * 16 + (height - 1 - line + oam[0]) * 2; + int colour = ((data & 0x0100) ? 2 : 0) | ((data & 0x0001) ? 1 : 0); + if ((xindex >= SGB_XOFFSET && xindex < SGB_XOFFSET + 160) && colour && !m_bg_zbuf[xindex - SGB_XOFFSET]) + plot_pixel(xindex, yindex, m_sgb_pal[pal + spal[colour]]); + data >>= 1; } - else + break; + case 0x20: /* priority is not set (overlaps bgnd & wnd, flip x) */ + for (int bit = 0; bit < 8; bit++, xindex++) { - adr = (oam[2] & tilemask) * 16 + (line - oam[0]) * 2; + int colour = ((data & 0x0100) ? 2 : 0) | ((data & 0x0001) ? 1 : 0); + if ((xindex >= SGB_XOFFSET && xindex < SGB_XOFFSET + 160) && colour) + plot_pixel(xindex, yindex, m_sgb_pal[pal + spal[colour]]); + data >>= 1; } - data = (vram[adr + 1] << 8) | vram[adr]; - - /* Find the palette to use */ - // If sprite started before the start of the line we may need to pick a different pal_map entry? - pal = m_sgb_pal_map[(xindex < 0) ? 0 : (xindex >> 3)][((yindex - SGB_YOFFSET) >> 3)] << 2; - - /* Offset to center of screen */ - xindex += SGB_XOFFSET; - - switch (oam[3] & 0xA0) + break; + case 0x80: /* priority is set (behind bgnd & wnd, don't flip x) */ + for (int bit = 0; bit < 8; bit++, xindex++) { - case 0xA0: /* priority is set (behind bgnd & wnd, flip x) */ - for (bit = 0; bit < 8; bit++, xindex++) - { - int colour = ((data & 0x0100) ? 2 : 0) | ((data & 0x0001) ? 1 : 0); - if ((xindex >= SGB_XOFFSET && xindex < SGB_XOFFSET + 160) && colour && !m_bg_zbuf[xindex - SGB_XOFFSET]) - plot_pixel(bitmap, xindex, yindex, m_sgb_pal[pal + spal[colour]]); - data >>= 1; - } - break; - case 0x20: /* priority is not set (overlaps bgnd & wnd, flip x) */ - for (bit = 0; bit < 8; bit++, xindex++) - { - int colour = ((data & 0x0100) ? 2 : 0) | ((data & 0x0001) ? 1 : 0); - if ((xindex >= SGB_XOFFSET && xindex < SGB_XOFFSET + 160) && colour) - plot_pixel(bitmap, xindex, yindex, m_sgb_pal[pal + spal[colour]]); - data >>= 1; - } - break; - case 0x80: /* priority is set (behind bgnd & wnd, don't flip x) */ - for (bit = 0; bit < 8; bit++, xindex++) - { - int colour = ((data & 0x8000) ? 2 : 0) | ((data & 0x0080) ? 1 : 0); - if ((xindex >= SGB_XOFFSET && xindex < SGB_XOFFSET + 160) && colour && !m_bg_zbuf[xindex - SGB_XOFFSET]) - plot_pixel(bitmap, xindex, yindex, m_sgb_pal[pal + spal[colour]]); - data <<= 1; - } - break; - case 0x00: /* priority is not set (overlaps bgnd & wnd, don't flip x) */ - for (bit = 0; bit < 8; bit++, xindex++) - { - int colour = ((data & 0x8000) ? 2 : 0) | ((data & 0x0080) ? 1 : 0); - if ((xindex >= SGB_XOFFSET && xindex < SGB_XOFFSET + 160) && colour) - plot_pixel(bitmap, xindex, yindex, m_sgb_pal[pal + spal[colour]]); - data <<= 1; - } - break; + int colour = ((data & 0x8000) ? 2 : 0) | ((data & 0x0080) ? 1 : 0); + if ((xindex >= SGB_XOFFSET && xindex < SGB_XOFFSET + 160) && colour && !m_bg_zbuf[xindex - SGB_XOFFSET]) + plot_pixel(xindex, yindex, m_sgb_pal[pal + spal[colour]]); + data <<= 1; + } + break; + case 0x00: /* priority is not set (overlaps bgnd & wnd, don't flip x) */ + for (int bit = 0; bit < 8; bit++, xindex++) + { + int colour = ((data & 0x8000) ? 2 : 0) | ((data & 0x0080) ? 1 : 0); + if ((xindex >= SGB_XOFFSET && xindex < SGB_XOFFSET + 160) && colour) + plot_pixel(xindex, yindex, m_sgb_pal[pal + spal[colour]]); + data <<= 1; } + break; } - oam -= 4; } } -void sgb_lcd_device::refresh_border() +void sgb_ppu_device::refresh_border() { UINT16 data, data2; UINT8 *tiles, *tiles2; @@ -908,7 +1425,7 @@ void sgb_lcd_device::refresh_border() if (!((yidx >= SGB_YOFFSET && yidx < SGB_YOFFSET + 144) && (xindex >= SGB_XOFFSET && xindex < SGB_XOFFSET + 160))) { - plot_pixel(m_bitmap, xindex, yidx, m_sgb_pal[pal + colour]); + plot_pixel(xindex, yidx, m_sgb_pal[pal + colour]); } xindex++; } @@ -916,27 +1433,23 @@ void sgb_lcd_device::refresh_border() } } -void sgb_lcd_device::update_scanline() +void sgb_ppu_device::update_scanline(UINT32 cycles_to_go) { - bitmap_ind16 &bitmap = m_bitmap; - g_profiler.start(PROFILER_VIDEO); if ((LCDSTAT & 0x03) == 0x03) { - /* Calcuate number of pixels to render based on time still left on the timer */ - UINT32 cycles_to_go = m_maincpu->attotime_to_cycles(m_lcd_timer->remaining()); int l = 0; if (m_start_x < 0) { /* Window is enabled if the hardware says so AND the current scanline is * within the window AND the window X coordinate is <=166 */ - m_layer[1].enabled = ((LCDCONT & 0x20) && m_current_line >= WNDPOSY && WNDPOSX <= 166) ? 1 : 0; + m_layer[1].enabled = ((LCDCONT & WINDOW_ENABLED) && m_current_line >= m_window_y && m_window_x <= 166) ? 1 : 0; /* BG is enabled if the hardware says so AND (window_off OR (window_on * AND window's X position is >=7 )) */ - m_layer[0].enabled = ((LCDCONT & 0x01) && ((!m_layer[1].enabled) || (m_layer[1].enabled && WNDPOSX >= 7))) ? 1 : 0; + m_layer[0].enabled = ((LCDCONT & BACKGROUND_ENABLED) && ((!m_layer[1].enabled) || (m_layer[1].enabled && m_window_x >= 7))) ? 1 : 0; if (m_layer[0].enabled) { @@ -954,7 +1467,7 @@ void sgb_lcd_device::update_scanline() int xpos; /* Window X position is offset by 7 so we'll need to adjust */ - xpos = WNDPOSX - 7; + xpos = m_window_x - 7; if (xpos < 0) xpos = 0; @@ -981,13 +1494,13 @@ void sgb_lcd_device::update_scanline() case 2: /* Blank screen (black) */ { rectangle r(SGB_XOFFSET, SGB_XOFFSET + 160-1, SGB_YOFFSET, SGB_YOFFSET + 144 - 1); - bitmap.fill(0, r); + m_bitmap.fill(0, r); } return; case 3: /* Blank screen (white - or should it be color 0?) */ { rectangle r(SGB_XOFFSET, SGB_XOFFSET + 160 - 1, SGB_YOFFSET, SGB_YOFFSET + 144 - 1); - bitmap.fill(32767, r); + m_bitmap.fill(32767, r); } return; } @@ -1003,10 +1516,10 @@ void sgb_lcd_device::update_scanline() m_end_x = std::min(int(160 - cycles_to_go),160); /* if background or screen disabled clear line */ - if (!(LCDCONT & 0x01)) + if (!(LCDCONT & BACKGROUND_ENABLED)) { rectangle r(SGB_XOFFSET, SGB_XOFFSET + 160 - 1, m_current_line + SGB_YOFFSET, m_current_line + SGB_YOFFSET); - bitmap.fill(0, r); + m_bitmap.fill(0, r); } while (l < 2) { @@ -1042,7 +1555,7 @@ void sgb_lcd_device::update_scanline() while ((m_layer[l].xshift < 8) && i) { int colour = ((data & 0x8000) ? 2 : 0) | ((data & 0x0080) ? 1 : 0); - plot_pixel(bitmap, xindex + SGB_XOFFSET, m_current_line + SGB_YOFFSET, m_sgb_pal[sgb_palette + m_gb_bpal[colour]]); + plot_pixel(xindex + SGB_XOFFSET, m_current_line + SGB_YOFFSET, m_sgb_pal[sgb_palette + m_gb_bpal[colour]]); m_bg_zbuf[xindex] = colour; xindex++; data <<= 1; @@ -1067,7 +1580,7 @@ void sgb_lcd_device::update_scanline() } l++; } - if ((m_end_x == 160) && (LCDCONT & 0x02)) + if ((m_end_x == 160) && (LCDCONT & SPRITES_ENABLED)) { update_sprites(); } @@ -1076,7 +1589,7 @@ void sgb_lcd_device::update_scanline() } else { - if (!(LCDCONT * 0x80)) + if (!(LCDCONT & ENABLED)) { /* if screen disabled clear line */ if (m_previous_line != m_current_line) @@ -1085,7 +1598,7 @@ void sgb_lcd_device::update_scanline() if (m_current_line < 144) { rectangle r(SGB_XOFFSET, SGB_XOFFSET + 160 - 1, m_current_line + SGB_YOFFSET, m_current_line + SGB_YOFFSET); - bitmap.fill(0, r); + m_bitmap.fill(0, r); } m_previous_line = m_current_line; } @@ -1097,13 +1610,12 @@ void sgb_lcd_device::update_scanline() /* --- Game Boy Color Specific --- */ -void cgb_lcd_device::update_sprites() +void cgb_ppu_device::update_sprites() { - bitmap_ind16 &bitmap = m_bitmap; - UINT8 height, tilemask, line, *oam; - int xindex, yindex; + UINT8 height, tilemask, line; + int yindex; - if (LCDCONT & 0x04) + if (LCDCONT & LARGE_SPRITES) { height = 16; tilemask = 0xFE; @@ -1117,121 +1629,117 @@ void cgb_lcd_device::update_sprites() yindex = m_current_line; line = m_current_line + 16; - oam = m_oam.get() + 39 * 4; - for (int i = 39; i >= 0; i--) + for (int i = m_sprCount - 1; i >= 0; i--) { - /* if sprite is on current line && x-coordinate && x-coordinate is < 168 */ - if (line >= oam[0] && line < (oam[0] + height) && oam[1] && oam[1] < 168) + const UINT16 oam_address = m_sprite[i] * 4; + UINT8 pal; + int xindex = m_oam[oam_address + 1] - 8; + UINT16 adr = ((m_oam[oam_address + 3] & 0x08) << 10) + (m_oam[oam_address + 2] & tilemask) * 16; + + if (xindex < -7 || xindex > 160) { - UINT16 data; - UINT8 bit, pal; + continue; + } - /* Handle mono mode for GB games */ - if (!m_gbc_mode) - pal = (oam[3] & 0x10) ? 4 : 0; - else - pal = ((oam[3] & 0x7) * 4); + /* Handle mono mode for GB games */ + if (!m_gbc_mode) + pal = (m_oam[oam_address + 3] & 0x10) ? 4 : 0; + else + pal = ((m_oam[oam_address + 3] & 0x7) * 4); - xindex = oam[1] - 8; - if (oam[3] & 0x40) /* flip y ? */ - { - data = *((UINT16 *) &m_vram[((oam[3] & 0x8)<<10) + (oam[2] & tilemask) * 16 + (height - 1 - line + oam[0]) * 2]); - } - else - { - data = *((UINT16 *) &m_vram[((oam[3] & 0x8)<<10) + (oam[2] & tilemask) * 16 + (line - oam[0]) * 2]); - } + if (m_oam[oam_address + 3] & 0x40) /* flip y ? */ + { + adr += (height - 1 - line + m_oam[oam_address]) * 2; + } + else + { + adr += (line - m_oam[oam_address]) * 2; + } - data = little_endianize_int16(data); + UINT16 data = (m_vram[adr + 1] << 8) | m_vram[adr]; - switch (oam[3] & 0xA0) + switch (m_oam[oam_address + 3] & 0xA0) + { + case 0xA0: /* priority is set (behind bgnd & wnd, flip x) */ + for (int bit = 0; bit < 8; bit++, xindex++) { - case 0xA0: /* priority is set (behind bgnd & wnd, flip x) */ - for (bit = 0; bit < 8; bit++, xindex++) + int colour = ((data & 0x0100) ? 2 : 0) | ((data & 0x0001) ? 1 : 0); + if (colour && !m_bg_zbuf[xindex] && xindex >= 0 && xindex < 160) { - int colour = ((data & 0x0100) ? 2 : 0) | ((data & 0x0001) ? 1 : 0); - if (colour && !m_bg_zbuf[xindex] && xindex >= 0 && xindex < 160) - { - if (!m_gbc_mode) - colour = pal ? m_gb_spal1[colour] : m_gb_spal0[colour]; - plot_pixel(bitmap, xindex, yindex, m_cgb_spal[pal + colour]); - } - data >>= 1; + if (!m_gbc_mode) + colour = pal ? m_gb_spal1[colour] : m_gb_spal0[colour]; + plot_pixel(xindex, yindex, m_cgb_spal[pal + colour]); } - break; - case 0x20: /* priority is not set (overlaps bgnd & wnd, flip x) */ - for (bit = 0; bit < 8; bit++, xindex++) + data >>= 1; + } + break; + case 0x20: /* priority is not set (overlaps bgnd & wnd, flip x) */ + for (int bit = 0; bit < 8; bit++, xindex++) + { + int colour = ((data & 0x0100) ? 2 : 0) | ((data & 0x0001) ? 1 : 0); + if ((m_bg_zbuf[xindex] & 0x80) && (m_bg_zbuf[xindex] & 0x7f) && (LCDCONT & BACKGROUND_ENABLED)) + colour = 0; + if (colour && xindex >= 0 && xindex < 160) { - int colour = ((data & 0x0100) ? 2 : 0) | ((data & 0x0001) ? 1 : 0); - if ((m_bg_zbuf[xindex] & 0x80) && (m_bg_zbuf[xindex] & 0x7f) && (LCDCONT & 0x1)) - colour = 0; - if (colour && xindex >= 0 && xindex < 160) - { - if (!m_gbc_mode) - colour = pal ? m_gb_spal1[colour] : m_gb_spal0[colour]; - plot_pixel(bitmap, xindex, yindex, m_cgb_spal[pal + colour]); - } - data >>= 1; + if (!m_gbc_mode) + colour = pal ? m_gb_spal1[colour] : m_gb_spal0[colour]; + plot_pixel(xindex, yindex, m_cgb_spal[pal + colour]); } - break; - case 0x80: /* priority is set (behind bgnd & wnd, don't flip x) */ - for (bit = 0; bit < 8; bit++, xindex++) + data >>= 1; + } + break; + case 0x80: /* priority is set (behind bgnd & wnd, don't flip x) */ + for (int bit = 0; bit < 8; bit++, xindex++) + { + int colour = ((data & 0x8000) ? 2 : 0) | ((data & 0x0080) ? 1 : 0); + if (colour && !m_bg_zbuf[xindex] && xindex >= 0 && xindex < 160) + { + if (!m_gbc_mode) + colour = pal ? m_gb_spal1[colour] : m_gb_spal0[colour]; + plot_pixel(xindex, yindex, m_cgb_spal[pal + colour]); + } + data <<= 1; + } + break; + case 0x00: /* priority is not set (overlaps bgnd & wnd, don't flip x) */ + for (int bit = 0; bit < 8; bit++, xindex++) + { + if (xindex >= 0 && xindex < 160) { int colour = ((data & 0x8000) ? 2 : 0) | ((data & 0x0080) ? 1 : 0); - if (colour && !m_bg_zbuf[xindex] && xindex >= 0 && xindex < 160) + if ((m_bg_zbuf[xindex] & 0x80) && (m_bg_zbuf[xindex] & 0x7f) && (LCDCONT & BACKGROUND_ENABLED)) + colour = 0; + if (colour) { if (!m_gbc_mode) colour = pal ? m_gb_spal1[colour] : m_gb_spal0[colour]; - plot_pixel(bitmap, xindex, yindex, m_cgb_spal[pal + colour]); + plot_pixel(xindex, yindex, m_cgb_spal[pal + colour]); } - data <<= 1; } - break; - case 0x00: /* priority is not set (overlaps bgnd & wnd, don't flip x) */ - for (bit = 0; bit < 8; bit++, xindex++) - { - if (xindex >= 0 && xindex < 160) - { - int colour = ((data & 0x8000) ? 2 : 0) | ((data & 0x0080) ? 1 : 0); - if ((m_bg_zbuf[xindex] & 0x80) && (m_bg_zbuf[xindex] & 0x7f) && (LCDCONT & 0x1)) - colour = 0; - if (colour) - { - if (!m_gbc_mode) - colour = pal ? m_gb_spal1[colour] : m_gb_spal0[colour]; - plot_pixel(bitmap, xindex, yindex, m_cgb_spal[pal + colour]); - } - } - data <<= 1; - } - break; + data <<= 1; } + break; } - oam -= 4; } } -void cgb_lcd_device::update_scanline() +void cgb_ppu_device::update_scanline(UINT32 cycles_to_go) { - bitmap_ind16 &bitmap = m_bitmap; - g_profiler.start(PROFILER_VIDEO); if ((LCDSTAT & 0x03) == 0x03) { - /* Calcuate number of pixels to render based on time still left on the timer */ - UINT32 cycles_to_go = m_maincpu->attotime_to_cycles(m_lcd_timer->remaining()); int l = 0; if (m_start_x < 0) { /* Window is enabled if the hardware says so AND the current scanline is * within the window AND the window X coordinate is <=166 */ - m_layer[1].enabled = ((LCDCONT & 0x20) && (m_current_line >= WNDPOSY) && (WNDPOSX <= 166)) ? 1 : 0; + m_layer[1].enabled = ((LCDCONT & WINDOW_ENABLED) && (m_current_line >= m_window_y) && (m_window_x <= 166)) ? 1 : 0; /* BG is enabled if the hardware says so AND (window_off OR (window_on * AND window's X position is >=7 )) */ - m_layer[0].enabled = ((LCDCONT & 0x01) && ((!m_layer[1].enabled) || (m_layer[1].enabled && (WNDPOSX >= 7)))) ? 1 : 0; + m_layer[0].enabled = ((LCDCONT & BACKGROUND_ENABLED) && ((!m_layer[1].enabled) || (m_layer[1].enabled && (m_window_x >= 7)))) ? 1 : 0; if (m_layer[0].enabled) { @@ -1249,7 +1757,7 @@ void cgb_lcd_device::update_scanline() int xpos; /* Window X position is offset by 7 so we'll need to adust */ - xpos = WNDPOSX - 7; + xpos = m_window_x - 7; if (xpos < 0) xpos = 0; @@ -1269,10 +1777,10 @@ void cgb_lcd_device::update_scanline() { m_end_x = std::min(int(160 - cycles_to_go), 160); /* Draw empty line when the background is disabled */ - if (!(LCDCONT & 0x01)) + if (!(LCDCONT & BACKGROUND_ENABLED)) { rectangle r(m_start_x, m_end_x - 1, m_current_line, m_current_line); - bitmap.fill((!m_gbc_mode) ? 0 : 32767, r); + m_bitmap.fill((!m_gbc_mode) ? 0 : 32767, r); } while (l < 2) { @@ -1334,7 +1842,7 @@ void cgb_lcd_device::update_scanline() colour = ((data & 0x8000) ? 2 : 0) | ((data & 0x0080) ? 1 : 0); data <<= 1; } - plot_pixel(bitmap, xindex, m_current_line, m_cgb_bpal[(!m_gbc_mode) ? m_gb_bpal[colour] : (((gbcmap[m_layer[l].xindex] & 0x07) * 4) + colour)]); + plot_pixel(xindex, m_current_line, m_cgb_bpal[(!m_gbc_mode) ? m_gb_bpal[colour] : (((gbcmap[m_layer[l].xindex] & 0x07) * 4) + colour)]); m_bg_zbuf[xindex] = colour + (gbcmap[m_layer[l].xindex] & 0x80); xindex++; m_layer[l].xshift++; @@ -1369,7 +1877,7 @@ void cgb_lcd_device::update_scanline() } l++; } - if (m_end_x == 160 && (LCDCONT & 0x02)) + if (m_end_x == 160 && (LCDCONT & SPRITES_ENABLED)) { update_sprites(); } @@ -1378,7 +1886,7 @@ void cgb_lcd_device::update_scanline() } else { - if (!(LCDCONT & 0x80)) + if (!(LCDCONT & ENABLED)) { /* Draw an empty line when LCD is disabled */ if (m_previous_line != m_current_line) @@ -1387,7 +1895,7 @@ void cgb_lcd_device::update_scanline() { const rectangle &r1 = m_screen->visible_area(); rectangle r(r1.min_x, r1.max_x, m_current_line, m_current_line); - bitmap.fill((!m_gbc_mode) ? 0 : 32767 , r); + m_bitmap.fill((!m_gbc_mode) ? 0 : 32767 , r); } m_previous_line = m_current_line; } @@ -1398,22 +1906,17 @@ void cgb_lcd_device::update_scanline() } -TIMER_CALLBACK_MEMBER(gb_lcd_device::video_init_vbl) -{ - m_maincpu->set_input_line(VBL_INT, ASSERT_LINE); -} - -UINT32 gb_lcd_device::screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect) +UINT32 dmg_ppu_device::screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect) { copybitmap(bitmap, m_bitmap, 0, 0, 0, 0, cliprect); return 0; } -void gb_lcd_device::increment_scanline() +void dmg_ppu_device::increment_scanline() { m_current_line = (m_current_line + 1) % 154; - if (LCDCONT & 0x80) + if (LCDCONT & ENABLED) { CURLINE = m_current_line; } @@ -1423,265 +1926,450 @@ void gb_lcd_device::increment_scanline() } } -TIMER_CALLBACK_MEMBER(gb_lcd_device::lcd_timer_proc) + +void dmg_ppu_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) { - static const int gb_sprite_cycles[] = { 0, 8, 20, 32, 44, 52, 64, 76, 88, 96, 108 }; + update_state(); +} - m_state = param; - if (LCDCONT & 0x80) +void dmg_ppu_device::update_oam_dma_state(UINT64 cycles) +{ + if (m_oam_dma_cycles_left > 0) { - switch (m_state) + if (cycles >= m_oam_dma_cycles_left) { - case GB_LCD_STATE_LYXX_PRE_M0: /* Just before switching to mode 0 */ - m_mode = 0; - if (LCDSTAT & 0x08) + m_oam_dma_cycles_left = 0; + m_oam_dma_processing = false; + // TODO: reenable real program map... + } + else + { + m_oam_dma_cycles_left -= cycles; + } + } + + if (m_oam_dma_start_cycles > 0) + { + if (cycles >= m_oam_dma_start_cycles) + { + for (int i = 0; i < 0xA0; i++) { - if (!m_mode_irq) - { - if (!m_line_irq && !m_delayed_line_irq) - { - m_mode_irq = 1; - m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); - } - } - else - { - m_mode_irq = 0; - } + m_oam[i] = m_program_space->read_byte(m_oam_dma_source_address + i); } - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LYXX_M0); - break; - case GB_LCD_STATE_LYXX_M0: /* Switch to mode 0 */ - /* update current scanline */ - update_scanline(); - /* Increment the number of window lines drawn if enabled */ - if (m_layer[1].enabled) + + m_oam_dma_start_cycles = 0; + m_oam_dma_cycles_left = 160 * 4; + m_oam_dma_processing = true; + // TODO: all reads should start to return 0xFF? from here + } + else + { + m_oam_dma_start_cycles -= cycles; + } + } +} + + +static const char* state_to_string(int state) +{ + switch (state) + { + case GB_LCD_STATE_LYXX_M3: + return "GB_LCD_STATE_LYXX_M3"; + case GB_LCD_STATE_LYXX_M3_2: + return "GB_LCD_STATE_LYXX_M3_2"; + case GB_LCD_STATE_LYXX_PRE_M0: + return "GB_LCD_STATE_LYXX_PRE_M0"; + case GB_LCD_STATE_LYXX_M0: + return "GB_LCD_STATE_LYXX_M0"; + case GB_LCD_STATE_LYXX_M0_2: + return "GB_LCD_STATE_LYXX_M0_2"; + case GB_LCD_STATE_LYXX_M0_GBC_PAL: + return "GB_LCD_STATE_LYXX_M0_GBC_PAL"; + case GB_LCD_STATE_LYXX_M0_PRE_INC: + return "GB_LCD_STATE_LYXX_M0_PRE_INC"; + case GB_LCD_STATE_LYXX_M0_INC: + return "GB_LCD_STATE_LYXX_M0_INC"; + case GB_LCD_STATE_LY00_M2: + return "GB_LCD_STATE_LY00_M2"; + case GB_LCD_STATE_LY00_M2_WND: + return "GB_LCD_STATE_LY00_M2_WND"; + case GB_LCD_STATE_LYXX_M2_WND: + return "GB_LCD_STATE_LYXX_M2_WND"; + case GB_LCD_STATE_LYXX_M2: + return "GB_LCD_STATE_LYXX_M2"; + case GB_LCD_STATE_LY9X_M1: + return "GB_LCD_STATE_LY9X_M1"; + case GB_LCD_STATE_LY9X_M1_INC: + return "GB_LCD_STATE_LY9X_M1_INC"; + case GB_LCD_STATE_LY00_M1: + return "GB_LCD_STATE_LY00_M1"; + case GB_LCD_STATE_LY00_M1_1: + return "GB_LCD_STATE_LY00_M1_1"; + case GB_LCD_STATE_LY00_M1_2: + return "GB_LCD_STATE_LY00_M1_2"; + case GB_LCD_STATE_LY00_M0: + return "GB_LCD_STATE_LY00_M0"; + default: + return "unknown state"; + } +} + + +void dmg_ppu_device::update_state() +{ + if (m_updating_state) + { + return; + } + + m_updating_state = true; + + attotime now = machine().time(); + + assert(now >= m_last_updated); + + UINT64 cycles = m_lr35902->attotime_to_cycles(now - m_last_updated); + + update_oam_dma_state(cycles); + + if (LCDCONT & ENABLED) + { + LOG(("m_cycles_left = %u, cycles = %u, CURLINE = %u, m_next_state = %s\n", m_cycles_left, cycles, CURLINE, state_to_string(m_next_state))); + + if (m_cycles_left > 0) + { + if (m_state == GB_LCD_STATE_LYXX_M3 || m_state == GB_LCD_STATE_LYXX_M3_2 || m_state == GB_LCD_STATE_LYXX_M0 || m_state == GB_LCD_STATE_LYXX_M0_2) { - m_window_lines_drawn++; + // Execute <cycles> M3 cycles + if (m_enable_experimental_engine) + { + update_line_state(cycles); + } } - m_previous_line = m_current_line; - /* Set Mode 0 lcdstate */ - m_mode = 0; - LCDSTAT &= 0xFC; - m_oam_locked = UNLOCKED; - m_vram_locked = UNLOCKED; - /* - There seems to a kind of feature in the Game Boy hardware when the lowest bits of the - SCROLLX register equals 3 or 7, then the delayed M0 irq is triggered 4 cycles later - than usual. - The SGB probably has the same bug. - */ - if ((SCROLLX & 0x03) == 0x03) - { - m_scrollx_adjust += 4; - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LYXX_M0_SCX3); - break; + + if (cycles >= m_cycles_left) { + cycles -= m_cycles_left; + m_cycles_left = 0; } - case GB_LCD_STATE_LYXX_M0_SCX3: - /* Generate lcd interrupt if requested */ - if (!m_mode_irq && (LCDSTAT & 0x08) && - ((!m_line_irq && m_delayed_line_irq) || !(LCDSTAT & 0x40))) + else { - m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + m_cycles_left -= cycles; + cycles = 0; } - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(196 - m_scrollx_adjust - m_sprite_cycles), GB_LCD_STATE_LYXX_M0_PRE_INC); - break; - case GB_LCD_STATE_LYXX_M0_PRE_INC: /* Just before incrementing the line counter go to mode 2 internally */ - if (CURLINE < 143) + } + + while (m_cycles_left == 0) + { + UINT16 state_cycles = 0; + + m_state = m_next_state; + + switch (m_state) { - m_mode = 2; - m_triggering_mode_irq = (LCDSTAT & 0x20) ? 1 : 0; - if (m_triggering_mode_irq) + case GB_LCD_STATE_LYXX_PRE_M0: /* Just before switching to mode 0 */ + m_next_state = GB_LCD_STATE_LYXX_M0; + state_cycles = 4; + break; + + case GB_LCD_STATE_LYXX_M0: /* Switch to mode 0 */ + /* update current scanline */ + update_scanline(m_lr35902->attotime_to_cycles(m_lcd_timer->remaining())); + /* Increment the number of window lines drawn if enabled */ + if (m_layer[1].enabled) { - if (!m_mode_irq) - { - if (!m_line_irq && !m_delayed_line_irq) - { - m_mode_irq = 1; - m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); - } - } - else + if (!m_enable_experimental_engine) { - m_mode_irq = 0; + m_window_lines_drawn++; } } - } - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LYXX_M0_INC); - break; - case GB_LCD_STATE_LYXX_M0_INC: /* Increment LY, stay in M0 for 4 more cycles */ - increment_scanline(); - m_delayed_line_irq = m_line_irq; - m_triggering_line_irq = ((CMPLINE == CURLINE) && (LCDSTAT & 0x40)) ? 1 : 0; - m_line_irq = 0; - if (!m_mode_irq && !m_delayed_line_irq && m_triggering_line_irq && !m_triggering_mode_irq) - { - m_line_irq = m_triggering_line_irq; - m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); - } - /* Reset LY==LYC STAT bit */ - LCDSTAT &= 0xFB; - /* Check if we're going into VBlank next */ - if (CURLINE == 144) - { - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LY9X_M1); - } - else - { - /* Internally switch to mode 2 */ + m_previous_line = m_current_line; + /* Set Mode 0 lcdstate */ + LCDSTAT &= 0xFC; + m_oam_locked = UNLOCKED; + m_oam_locked_reading = UNLOCKED; + m_vram_locked = UNLOCKED; + m_next_state = GB_LCD_STATE_LYXX_M0_2; + state_cycles = 1; + break; + + case GB_LCD_STATE_LYXX_M0_2: + m_stat_mode0_int = (LCDSTAT & MODE_0_INT_ENABLED) ? true : false; + check_stat_irq(); + m_mode = 0; + m_next_state = GB_LCD_STATE_LYXX_M0_INC; + state_cycles = 200 - 1 + 3 - m_scrollx_adjust - m_sprite_cycles - m_window_cycles; + break; + + case GB_LCD_STATE_LYXX_M0_INC: /* Increment LY, stay in M0 for 4 more cycles */ + increment_scanline(); + m_window_y = WNDPOSY; + m_stat_lyc_int_prev = m_stat_lyc_int; + m_stat_lyc_int = false; + if (CURLINE < 144) + { + m_stat_mode0_int = false; + } + m_stat_mode2_int = (LCDSTAT & MODE_2_INT_ENABLED) ? true : false; + check_stat_irq(); + + m_stat_lyc_int = ((CMPLINE == CURLINE) && (LCDSTAT & LY_LYC_INT_ENABLED)) ? true : false; + LOG(("GB_LCD_STATE_LYXX_M0_INC: CMPLINE = %02x, CURLINE = %02x, LCDSTAT = %02x, m_stat_lyc_int = %s\n", CMPLINE, CURLINE, LCDSTAT, m_stat_lyc_int ? "true":"false")); + /* Reset LY==LYC STAT bit */ + LCDSTAT &= ~LY_LYC_FLAG; + /* Check if we're going into VBlank next */ + if (CURLINE == 144) + { + m_next_state = GB_LCD_STATE_LY9X_M1; + state_cycles = 4; + } + else + { + m_next_state = GB_LCD_STATE_LYXX_M2; + m_oam_locked_reading = LOCKED; + state_cycles = 4; + } + break; + + case GB_LCD_STATE_LY00_M2: /* Switch to mode 2 on line #0 */ + /* Set Mode 2 lcdstate */ m_mode = 2; + LCDSTAT = (LCDSTAT & 0xFC) | 0x02; + m_oam_locked = LOCKED; /* Generate lcd interrupt if requested */ - if (!m_mode_irq && m_triggering_mode_irq && - ((!m_triggering_line_irq && !m_delayed_line_irq) || !(LCDSTAT & 0x40))) + m_stat_mode1_int = false; + m_stat_mode2_int = (LCDSTAT & MODE_2_INT_ENABLED) ? true : false; + check_stat_irq(); + /* Check for regular compensation of x-scroll register */ + if (!m_enable_experimental_engine) { - m_mode_irq = 1; - m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + m_scrollx_adjust = SCROLLX & 0x07; } - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LYXX_M2); - } - break; - case GB_LCD_STATE_LY00_M2: /* Switch to mode 2 on line #0 */ - /* Set Mode 2 lcdstate */ - m_mode = 2; - LCDSTAT = (LCDSTAT & 0xFC) | 0x02; - m_oam_locked = LOCKED; - /* Generate lcd interrupt if requested */ - if ((LCDSTAT & 0x20) && !m_line_irq) - { - m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); - } - /* Check for regular compensation of x-scroll register */ - m_scrollx_adjust = (SCROLLX & 0x04) ? 4 : 0; - /* Mode 2 lasts approximately 80 clock cycles */ - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(80), GB_LCD_STATE_LYXX_M3); - break; - case GB_LCD_STATE_LYXX_M2: /* Switch to mode 2 */ - /* Update STAT register to the correct state */ - LCDSTAT = (LCDSTAT & 0xFC) | 0x02; - m_oam_locked = LOCKED; - /* Generate lcd interrupt if requested */ - if ((m_delayed_line_irq && m_triggering_line_irq && !(LCDSTAT & 0x20)) || - (!m_mode_irq && !m_line_irq && !m_delayed_line_irq && m_triggering_mode_irq)) - { - m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); - } - m_line_irq = m_triggering_line_irq; - m_triggering_mode_irq = 0; - /* Check if LY==LYC STAT bit should be set */ - if (CURLINE == CMPLINE) - { - LCDSTAT |= 0x04; - } - /* Check for regular compensation of x-scroll register */ - m_scrollx_adjust = (SCROLLX & 0x04) ? 4 : 0; - /* Mode 2 last for approximately 80 clock cycles */ - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(80), GB_LCD_STATE_LYXX_M3); - break; - case GB_LCD_STATE_LYXX_M3: /* Switch to mode 3 */ - select_sprites(); - m_sprite_cycles = gb_sprite_cycles[m_sprCount]; - /* Set Mode 3 lcdstate */ - m_mode = 3; - LCDSTAT = (LCDSTAT & 0xFC) | 0x03; - m_vram_locked = LOCKED; - /* Check for compensations of x-scroll register */ - /* Mode 3 lasts for approximately 172+cycles needed to handle sprites clock cycles */ - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(168 + m_scrollx_adjust + m_sprite_cycles), GB_LCD_STATE_LYXX_PRE_M0); - m_start_x = -1; - break; - case GB_LCD_STATE_LY9X_M1: /* Switch to or stay in mode 1 */ - if (CURLINE == 144) - { - /* Trigger VBlank interrupt */ - m_maincpu->set_input_line(VBL_INT, ASSERT_LINE); - /* Set VBlank lcdstate */ - m_mode = 1; - LCDSTAT = (LCDSTAT & 0xFC) | 0x01; - /* Trigger LCD interrupt if requested */ - if (LCDSTAT & 0x10) + /* Mode 2 lasts approximately 80 clock cycles */ + m_next_state = GB_LCD_STATE_LYXX_M3; + clear_line_state(); + select_sprites(); +// if (!m_enable_experimental_engine) { - m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + m_window_y = WNDPOSY; } - } - /* Check if LY==LYC STAT bit should be set */ - if (CURLINE == CMPLINE) - { - LCDSTAT |= 0x04; - } - if (m_delayed_line_irq && m_triggering_line_irq) - { - m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); - } - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(452), GB_LCD_STATE_LY9X_M1_INC); - break; - case GB_LCD_STATE_LY9X_M1_INC: /* Increment scanline counter */ - increment_scanline(); - m_delayed_line_irq = m_line_irq; - m_triggering_line_irq = ((CMPLINE == CURLINE) && (LCDSTAT & 0x40)) ? 1 : 0; - m_line_irq = 0; - if (!m_delayed_line_irq && m_triggering_line_irq) - { - m_line_irq = m_triggering_line_irq; - m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); - } - /* Reset LY==LYC STAT bit */ - LCDSTAT &= 0xFB; - if (m_current_line == 153) - { - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LY00_M1); - } - else - { - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LY9X_M1); - } - break; - case GB_LCD_STATE_LY00_M1: /* we stay in VBlank but current line counter should already be incremented */ - /* Check LY=LYC for line #153 */ - if (m_delayed_line_irq) - { - if (m_triggering_line_irq) + state_cycles = 80; + state_cycles = 8; + m_next_state = GB_LCD_STATE_LY00_M2_WND; + break; + + case GB_LCD_STATE_LY00_M2_WND: + // Check window active for current/previous line + if ((LCDCONT & WINDOW_ENABLED) && m_current_line == m_window_y) { - m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + m_line.window_should_trigger = true; } +LOG(("window should trigger = %s, m_current_line = %u, m_window_y = %u\n", m_line.window_should_trigger ? "true" : "false", m_current_line, m_window_y)); + + state_cycles = 80 - 8; + m_next_state = GB_LCD_STATE_LYXX_M3; + break; + + case GB_LCD_STATE_LYXX_M2: /* Switch to mode 2 */ + m_stat_mode0_int = false; + check_stat_irq(); + /* Update STAT register to the correct state */ + m_mode = 2; + LCDSTAT = (LCDSTAT & 0xFC) | 0x02; + m_oam_locked = LOCKED; + /* Check if LY==LYC STAT bit should be set */ + if (CURLINE == CMPLINE) + { + LCDSTAT |= LY_LYC_FLAG; + } + /* Check for regular compensation of x-scroll register */ + if (!m_enable_experimental_engine) + { + m_scrollx_adjust = SCROLLX & 0x07; + } + /* Mode 2 last for approximately 80 clock cycles */ + m_next_state = GB_LCD_STATE_LYXX_M3; + clear_line_state(); + select_sprites(); + if (!m_enable_experimental_engine) + { + m_window_y = WNDPOSY; + } + state_cycles = 80; + m_next_state = GB_LCD_STATE_LYXX_M2_WND; + state_cycles = 8; + break; + + case GB_LCD_STATE_LYXX_M2_WND: + // Check window active for current/previous line + if ((LCDCONT & WINDOW_ENABLED) && m_current_line == m_window_y + 1) + { + m_line.window_should_trigger = true; + } +LOG(("window should trigger = %s, m_current_line = %u, m_window_y = %u\n", m_line.window_should_trigger ? "true" : "false", m_current_line, m_window_y)); + m_next_state = GB_LCD_STATE_LYXX_M3; + state_cycles = 80 - 8; + break; + + case GB_LCD_STATE_LYXX_M3: /* Switch to mode 3 */ + for (int i = 0; i < ARRAY_LENGTH(m_line.window_start_y); i++) + { + m_line.window_start_y[i] = WNDPOSY; + m_line.window_start_x[i] = WNDPOSX; + } + m_line.window_start_y_index = 0; + for (int i = 0; i < ARRAY_LENGTH(m_line.window_enable); i++) + { + m_line.window_enable[i] = LCDCONT; + } + m_line.window_enable_index = 0; + m_stat_mode2_int = false; + check_stat_irq(); + /* Set Mode 3 lcdstate */ + m_mode = 3; + LCDSTAT = (LCDSTAT & 0xFC) | 0x03; + // Normally OAM is already locked by mode 2, except when the lcd unit is just switched on. + m_oam_locked = LOCKED; + m_vram_locked = LOCKED; + /* Check for compensations of x-scroll register */ + /* Mode 3 lasts for approximately 172+cycles needed to handle sprites */ + if (m_enable_experimental_engine) + { + m_next_state = GB_LCD_STATE_LYXX_M0; + state_cycles = 4 - 3 + 168 + m_sprite_cycles; + } + else + { + // old + m_next_state = GB_LCD_STATE_LYXX_PRE_M0; + state_cycles = 168 + m_scrollx_adjust + m_sprite_cycles; + // new +// WX write at 164 cycles left still taken into account +// WX write at 160 cycles left not taken into account + m_next_state = GB_LCD_STATE_LYXX_M3_2; + state_cycles = 12; + m_start_x = -1; + } + break; + + case GB_LCD_STATE_LYXX_M3_2: + m_window_x = WNDPOSX; + if (!m_enable_experimental_engine) + { + calculate_window_cycles(); + } + m_next_state = GB_LCD_STATE_LYXX_M0; + state_cycles = 4 - 3 + 168 - 12 + m_scrollx_adjust + m_sprite_cycles + m_window_cycles; + break; + + case GB_LCD_STATE_LY9X_M1: /* Switch to or stay in mode 1 */ + m_stat_lyc_int = ((CMPLINE == CURLINE) && (LCDSTAT & LY_LYC_INT_ENABLED)) ? true : false; + m_stat_mode2_int = false; + m_stat_mode0_int = false; + if (CURLINE == 144) + { + /* Trigger VBlank interrupt */ + m_lr35902->set_input_line(lr35902_cpu_device::VBL_INT, ASSERT_LINE); + // Make sure the state is updated during the current timeslice in case it is read. + m_lr35902->execute_set_input(lr35902_cpu_device::VBL_INT, ASSERT_LINE); + /* Set VBlank lcdstate */ + m_mode = 1; + LCDSTAT = (LCDSTAT & 0xFC) | 0x01; + /* Trigger LCD interrupt if requested */ + m_stat_mode1_int = (LCDSTAT & MODE_1_INT_ENABLED) ? true : false; + } + check_stat_irq(); + /* Check if LY==LYC STAT bit should be set */ + if (CURLINE == CMPLINE) + { + LCDSTAT |= LY_LYC_FLAG; + } + m_next_state = GB_LCD_STATE_LY9X_M1_INC; + state_cycles = 452; + break; + + case GB_LCD_STATE_LY9X_M1_INC: /* Increment scanline counter */ + increment_scanline(); + LOG(("GB_LCD_STATE_LY9X_M1_INC: m_stat_lyc_int = %s\n", m_stat_lyc_int ? "true" : "false")); + /* Reset LY==LYC STAT bit */ + LCDSTAT &= ~LY_LYC_FLAG; + if (m_current_line == 153) + { + m_next_state = GB_LCD_STATE_LY00_M1; + state_cycles = 4; + } + else + { + m_next_state = GB_LCD_STATE_LY9X_M1; + state_cycles = 4; + } + break; + + case GB_LCD_STATE_LY00_M1: /* we stay in VBlank but current line counter should already be incremented */ + /* Check LY=LYC for line #153 */ + if (CURLINE == CMPLINE) + { + LCDSTAT |= LY_LYC_FLAG; + } + else + { + LCDSTAT &= ~LY_LYC_FLAG; + } + m_stat_lyc_int = ((CMPLINE == CURLINE) && (LCDSTAT & LY_LYC_INT_ENABLED)) ? true : false; + check_stat_irq(); + + increment_scanline(); + m_next_state = GB_LCD_STATE_LY00_M1_1; + state_cycles = 4; + break; + + case GB_LCD_STATE_LY00_M1_1: + LCDSTAT &= ~LY_LYC_FLAG; + m_next_state = GB_LCD_STATE_LY00_M1_2; + state_cycles = 4; + break; + + case GB_LCD_STATE_LY00_M1_2: /* Rest of line #0 during VBlank */ + m_frame_window_active = false; + m_stat_lyc_int = ((CMPLINE == CURLINE) && (LCDSTAT & LY_LYC_INT_ENABLED)) ? true : false; + check_stat_irq(); + if (CURLINE == CMPLINE) + { + LCDSTAT |= LY_LYC_FLAG; + } + m_next_state = GB_LCD_STATE_LY00_M0; + state_cycles = 444; + break; + + case GB_LCD_STATE_LY00_M0: /* The STAT register seems to go to 0 for about 4 cycles */ + m_window_y = WNDPOSY; + /* Set Mode 0 lcdstat */ + LCDSTAT = (LCDSTAT & 0xFC); + m_next_state = GB_LCD_STATE_LY00_M2; + state_cycles = 4; + break; } - m_delayed_line_irq = m_delayed_line_irq | m_line_irq; - if (CURLINE == CMPLINE) - { - LCDSTAT |= 0x04; - } - increment_scanline(); - m_triggering_line_irq = ((CMPLINE == CURLINE) && (LCDSTAT & 0x40)) ? 1 : 0; - m_line_irq = 0; - LCDSTAT &= 0xFB; - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4/*8*/), GB_LCD_STATE_LY00_M1_1); - break; - case GB_LCD_STATE_LY00_M1_1: - if (!m_delayed_line_irq && m_triggering_line_irq) + assert(state_cycles > 0); + + if (m_state == GB_LCD_STATE_LYXX_M3 || m_state == GB_LCD_STATE_LYXX_M3_2 || m_state == GB_LCD_STATE_LYXX_M0 || m_state == GB_LCD_STATE_LYXX_M0) { - m_line_irq = m_triggering_line_irq; - m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + // Execute <cycles> M3 cycles + if (m_enable_experimental_engine) + { + update_line_state(cycles); + } } - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LY00_M1_2); - break; - case GB_LCD_STATE_LY00_M1_2: /* Rest of line #0 during VBlank */ - if (m_delayed_line_irq && m_triggering_line_irq) + + if (cycles >= state_cycles) { - m_line_irq = m_triggering_line_irq; - m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + cycles -= state_cycles; + m_cycles_left = 0; } - if (CURLINE == CMPLINE) + else { - LCDSTAT |= 0x04; + m_cycles_left = state_cycles - cycles; } - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(444), GB_LCD_STATE_LY00_M0); - break; - case GB_LCD_STATE_LY00_M0: /* The STAT register seems to go to 0 for about 4 cycles */ - /* Set Mode 0 lcdstat */ - m_mode = 0; - LCDSTAT = (LCDSTAT & 0xFC); - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LY00_M2); - break; } } else @@ -1689,332 +2377,411 @@ TIMER_CALLBACK_MEMBER(gb_lcd_device::lcd_timer_proc) increment_scanline(); if (m_current_line < 144) { - update_scanline(); + // Force draw of an empty line + update_scanline(0); } - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(456)); + m_cycles_left = 456; } + assert(m_cycles_left > 0); + + m_last_updated = machine().time(); + + int next_cycles = m_cycles_left; + + if (m_oam_dma_start_cycles > 0 && m_oam_dma_start_cycles < next_cycles) + { + next_cycles = m_oam_dma_start_cycles; + } + + if (m_oam_dma_cycles_left > 0 && m_oam_dma_cycles_left < next_cycles) + { + next_cycles = m_oam_dma_cycles_left; + } + + m_lcd_timer->adjust(m_lr35902->cycles_to_attotime(next_cycles)); + + m_updating_state = false; } // CGB specific code -void cgb_lcd_device::hdma_trans(UINT16 length) +void cgb_ppu_device::update_hdma_state(UINT64 cycles) { + if (m_hdma_cycles_to_start > 0) + { + if (cycles >= m_hdma_cycles_to_start) + { + m_hdma_cycles_to_start = 0; + hdma_trans_execute(); + } + else + { + m_hdma_cycles_to_start -= cycles; + } + } +} + + +void cgb_ppu_device::hdma_trans(UINT16 length) +{ + LOG(("hdma_trans\n")); + m_hdma_length = length; + m_hdma_cycles_to_start = 4; + update_state(); +} + + +void cgb_ppu_device::hdma_trans_execute() +{ + LOG(("hdma_trans_execute\n")); + UINT16 length = m_hdma_length; UINT16 src, dst; - address_space &space = m_maincpu->space(AS_PROGRAM); - src = ((UINT16)HDMA1 << 8) | (HDMA2 & 0xF0); - dst = ((UINT16)(HDMA3 & 0x1F) << 8) | (HDMA4 & 0xF0); - dst |= 0x8000; + src = (HDMA1 << 8) | (HDMA2 & 0xF0); + // 102 Dalmatians uses destination 0000 and expects data to be DMAed. + dst = 0x8000 | (HDMA3 << 8) | (HDMA4 & 0xF0); + + //LOG(("length = %04x, src = %04x, dst = %04x\n", length, src, dst)); while (length > 0) { - space.write_byte(dst++, space.read_byte(src++)); + if (dst & 0x8000) + { + UINT16 src_high = src & 0xF000; + UINT8 source = 0xFF; + if (src_high < 0x8000 || (src_high >= 0xA000 && src_high < 0xE000)) + { + source = m_program_space->read_byte(src); + } + m_program_space->write_byte(dst & 0x9FFF, source); + } + src++; + dst++; + length--; } HDMA1 = src >> 8; - HDMA2 = src & 0xF0; - HDMA3 = 0x1f & (dst >> 8); - HDMA4 = dst & 0xF0; + HDMA2 = src & 0xFF; + HDMA3 = dst >> 8; + HDMA4 = dst & 0xFF; HDMA5--; if ((HDMA5 & 0x7f) == 0x7f) { HDMA5 = 0xff; m_hdma_enabled = 0; } + + m_lr35902->dma_cycles_to_burn(4 + m_hdma_length * 2); } -TIMER_CALLBACK_MEMBER(cgb_lcd_device::lcd_timer_proc) +void cgb_ppu_device::update_state() { - static const int cgb_sprite_cycles[] = { 0, 8, 20, 32, 44, 52, 64, 76, 88, 96, 108 }; + if (m_updating_state) + { + return; + } + + m_updating_state = true; - m_state = param; + attotime now = machine().time(); - if (LCDCONT & 0x80) + UINT64 cycles = m_lr35902->attotime_to_cycles(now - m_last_updated); + + update_oam_dma_state(cycles); + update_hdma_state(cycles); + + if (LCDCONT & ENABLED) { - switch (m_state) + LOG(("m_cycles_left = %d, cycles = %d, m_next_state = %s\n", m_cycles_left, cycles, state_to_string(m_next_state))); + + if (m_cycles_left > 0) + { + if (cycles >= m_cycles_left) { + cycles -= m_cycles_left; + m_cycles_left = 0; + } + else + { + m_cycles_left -= cycles; + cycles = 0; + } + } + + while (m_cycles_left == 0) { - case GB_LCD_STATE_LYXX_PRE_M0: /* Just before switching to mode 0 */ - m_mode = 0; - if (LCDSTAT & 0x08) + UINT16 state_cycles = 0; + + m_state = m_next_state; + + switch (m_state) { - if (!m_mode_irq) + case GB_LCD_STATE_LYXX_PRE_M0: /* Just before switching to mode 0 */ + m_next_state = GB_LCD_STATE_LYXX_M0; + state_cycles = 4; + break; + + case GB_LCD_STATE_LYXX_M0: /* Switch to mode 0 */ + m_stat_mode0_int = (LCDSTAT & MODE_0_INT_ENABLED) ? true : false; + /* update current scanline */ + update_scanline(m_lr35902->attotime_to_cycles(m_lcd_timer->remaining())); + /* Increment the number of window lines drawn if enabled */ + if (m_layer[1].enabled) { - if (!m_line_irq && !m_delayed_line_irq) - { - m_mode_irq = 1; - m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); - } + m_window_lines_drawn++; + } + m_previous_line = m_current_line; + /* Set Mode 0 lcdstate */ + m_mode = 0; + LCDSTAT &= 0xFC; + m_oam_locked = UNLOCKED; + m_oam_locked_reading = UNLOCKED; + m_vram_locked = UNLOCKED; + //m_pal_locked = UNLOCKED; + m_next_state = GB_LCD_STATE_LYXX_M0_GBC_PAL; + state_cycles = 4; + m_next_state = GB_LCD_STATE_LYXX_M0_2; + state_cycles = 1; + break; + + case GB_LCD_STATE_LYXX_M0_2: + check_stat_irq(); + m_next_state = GB_LCD_STATE_LYXX_M0_GBC_PAL; + state_cycles = 3; + break; + + case GB_LCD_STATE_LYXX_M0_GBC_PAL: + m_pal_locked = UNLOCKED; + /* Check for HBLANK DMA */ + if (m_hdma_enabled) + { + hdma_trans(0x10); } else { - m_mode_irq = 0; + m_hdma_possible = 1; } - } - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LYXX_M0); - break; - case GB_LCD_STATE_LYXX_M0: /* Switch to mode 0 */ - /* update current scanline */ - update_scanline(); - /* Increment the number of window lines drawn if enabled */ - if (m_layer[1].enabled) - { - m_window_lines_drawn++; - } - m_previous_line = m_current_line; - /* Set Mode 0 lcdstate */ - m_mode = 0; - LCDSTAT &= 0xFC; - m_oam_locked = UNLOCKED; - m_vram_locked = UNLOCKED; - /* - There seems to a kind of feature in the Game Boy hardware when the lowest bits of the - SCROLLX register equals 3 or 7, then the delayed M0 irq is triggered 4 cycles later - than usual. - The SGB probably has the same bug. - */ - m_triggering_mode_irq = (LCDSTAT & 0x08) ? 1 : 0; - if ((SCROLLX & 0x03) == 0x03) - { - m_scrollx_adjust += 4; - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LYXX_M0_SCX3); + m_next_state = GB_LCD_STATE_LYXX_M0_PRE_INC; + state_cycles = 192 + 3 - m_scrollx_adjust - m_sprite_cycles - m_window_cycles; break; - } - case GB_LCD_STATE_LYXX_M0_SCX3: - /* Generate lcd interrupt if requested */ - if (!m_mode_irq && m_triggering_mode_irq && - ((!m_line_irq && m_delayed_line_irq) || !(LCDSTAT & 0x40))) - { - m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); - m_triggering_mode_irq = 0; - } - if ((SCROLLX & 0x03) == 0x03) - { - m_pal_locked = UNLOCKED; - } - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LYXX_M0_GBC_PAL); - break; - case GB_LCD_STATE_LYXX_M0_GBC_PAL: - m_pal_locked = UNLOCKED; - /* Check for HBLANK DMA */ - if (m_hdma_enabled) - { - hdma_trans(0x10); -// cpunum_set_reg(0, LR35902_DMA_CYCLES, 36); - } - else - { - m_hdma_possible = 1; - } - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(192 - m_scrollx_adjust - m_sprite_cycles), GB_LCD_STATE_LYXX_M0_PRE_INC); - break; - case GB_LCD_STATE_LYXX_M0_PRE_INC: /* Just before incrementing the line counter go to mode 2 internally */ - m_cmp_line = CMPLINE; - if (CURLINE < 143) - { - m_mode = 2; - if (LCDSTAT & 0x20) + + case GB_LCD_STATE_LYXX_M0_PRE_INC: /* Just before incrementing the line counter */ + m_cmp_line = CMPLINE; + m_next_state = GB_LCD_STATE_LYXX_M0_INC; + state_cycles = 4; + break; + + case GB_LCD_STATE_LYXX_M0_INC: /* Increment LY, stay in M0 for 4 more cycles */ + increment_scanline(); + m_stat_lyc_int = false; + if (CURLINE < 144) { - if (!m_mode_irq) - { - if (!m_line_irq && !m_delayed_line_irq) - { - m_mode_irq = 1; - m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); - } - } - else - { - m_mode_irq = 0; - } + m_stat_mode0_int = false; } - } - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LYXX_M0_INC); - break; - case GB_LCD_STATE_LYXX_M0_INC: /* Increment LY, stay in M0 for 4 more cycles */ - increment_scanline(); - m_delayed_line_irq = m_line_irq; - m_triggering_line_irq = ((m_cmp_line == CURLINE) && (LCDSTAT & 0x40)) ? 1 : 0; - m_line_irq = 0; - if (!m_mode_irq && !m_delayed_line_irq && m_triggering_line_irq && !(LCDSTAT & 0x20)) - { - m_line_irq = m_triggering_line_irq; - m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); - } - m_hdma_possible = 0; - /* Check if we're going into VBlank next */ - if (CURLINE == 144) - { - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LY9X_M1); - } - else - { - /* Internally switch to mode 2 */ + m_stat_mode2_int = (LCDSTAT & MODE_2_INT_ENABLED) ? true : false; + check_stat_irq(); + m_stat_lyc_int = ((m_cmp_line == CURLINE) && (LCDSTAT & LY_LYC_INT_ENABLED)) ? true : false; + LOG(("GB_LCD_STATE_LYXX_M0_INC: m_cmp_line = %u, CURLINE = %u, LCDSTAT = %02x, m_stat_lyc_int = %s\n", m_cmp_line, CURLINE, LCDSTAT, m_stat_lyc_int ? "true" : "false")); + m_hdma_possible = 0; + /* Check if we're going into VBlank next */ + if (CURLINE == 144) + { + m_next_state = GB_LCD_STATE_LY9X_M1; + state_cycles = 4; + } + else + { + /* Internally switch to mode 2 */ + m_next_state = GB_LCD_STATE_LYXX_M2; + m_oam_locked_reading = LOCKED; + state_cycles = 4; + } + break; + + case GB_LCD_STATE_LY00_M2: /* Switch to mode 2 on line #0 */ + /* Set Mode 2 lcdstate */ m_mode = 2; + LCDSTAT = (LCDSTAT & 0xFC) | 0x02; + m_oam_locked = LOCKED; /* Generate lcd interrupt if requested */ - if (!m_mode_irq && (LCDSTAT & 0x20) && - ((!m_triggering_line_irq && !m_delayed_line_irq) || !(LCDSTAT & 0x40))) + m_stat_mode1_int = false; + m_stat_mode2_int = (LCDSTAT & MODE_2_INT_ENABLED) ? true : false; + check_stat_irq(); + /* Mode 2 lasts approximately 80 clock cycles */ + m_next_state = GB_LCD_STATE_LYXX_M3; + clear_line_state(); + /* Check for regular compensation of x-scroll register */ + m_scrollx_adjust = SCROLLX & 0x07; + select_sprites(); + m_window_y = WNDPOSY; + state_cycles = 80; + break; + + case GB_LCD_STATE_LYXX_M2: /* Switch to mode 2 */ + m_stat_mode0_int = false; + check_stat_irq(); + /* Update STAT register to the correct state */ + m_mode = 2; + LCDSTAT = (LCDSTAT & 0xFC) | 0x02; + m_oam_locked = LOCKED; + /* Check if LY==LYC STAT bit should be set */ + if (CURLINE == CMPLINE) { - m_mode_irq = 1; - m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + LCDSTAT |= LY_LYC_FLAG; } - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LYXX_M2); - } - break; - case GB_LCD_STATE_LY00_M2: /* Switch to mode 2 on line #0 */ - /* Set Mode 2 lcdstate */ - m_mode = 2; - LCDSTAT = (LCDSTAT & 0xFC) | 0x02; - m_oam_locked = LOCKED; - /* Generate lcd interrupt if requested */ - if ((LCDSTAT & 0x20) && !m_line_irq) - { - m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); - } - /* Check for regular compensation of x-scroll register */ - m_scrollx_adjust = (SCROLLX & 0x04) ? 4 : 0; - /* Mode 2 lasts approximately 80 clock cycles */ - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(80), GB_LCD_STATE_LYXX_M3); - break; - case GB_LCD_STATE_LYXX_M2: /* Switch to mode 2 */ - /* Update STAT register to the correct state */ - LCDSTAT = (LCDSTAT & 0xFC) | 0x02; - m_oam_locked = LOCKED; - /* Generate lcd interrupt if requested */ - if ((m_delayed_line_irq && m_triggering_line_irq && !(LCDSTAT & 0x20)) || - (!m_mode_irq && !m_line_irq && !m_delayed_line_irq && (LCDSTAT & 0x20))) - { - m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); - } - m_line_irq = m_triggering_line_irq; - /* Check if LY==LYC STAT bit should be set */ - if (CURLINE == CMPLINE) - { - LCDSTAT |= 0x04; - } - else - { - LCDSTAT &= ~0x04; - } - /* Check for regular compensation of x-scroll register */ - m_scrollx_adjust = (SCROLLX & 0x04) ? 4 : 0; - /* Mode 2 last for approximately 80 clock cycles */ - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(80), GB_LCD_STATE_LYXX_M3); - break; - case GB_LCD_STATE_LYXX_M3: /* Switch to mode 3 */ - select_sprites(); - m_sprite_cycles = cgb_sprite_cycles[m_sprCount]; - /* Set Mode 3 lcdstate */ - m_mode = 3; - LCDSTAT = (LCDSTAT & 0xFC) | 0x03; - m_vram_locked = LOCKED; - m_pal_locked = LOCKED; - /* Check for compensations of x-scroll register */ - /* Mode 3 lasts for approximately 172+cycles needed to handle sprites clock cycles */ - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(168 + m_scrollx_adjust + m_sprite_cycles), GB_LCD_STATE_LYXX_PRE_M0); - m_start_x = -1; - break; - case GB_LCD_STATE_LY9X_M1: /* Switch to or stay in mode 1 */ - if (CURLINE == 144) - { - /* Trigger VBlank interrupt */ - m_maincpu->set_input_line(VBL_INT, ASSERT_LINE); - /* Set VBlank lcdstate */ - m_mode = 1; - LCDSTAT = (LCDSTAT & 0xFC) | 0x01; - /* Trigger LCD interrupt if requested */ - if (LCDSTAT & 0x10) + else { - m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + LCDSTAT &= ~LY_LYC_FLAG; } - } - /* Check if LY==LYC STAT bit should be set */ - if (CURLINE == CMPLINE) - { - LCDSTAT |= 0x04; - } - else - { - LCDSTAT &= ~0x04; - } - if (m_delayed_line_irq && m_triggering_line_irq) - { - m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); - } - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(452), GB_LCD_STATE_LY9X_M1_INC); - break; - case GB_LCD_STATE_LY9X_M1_INC: /* Increment scanline counter */ - increment_scanline(); - m_delayed_line_irq = m_line_irq; - m_triggering_line_irq = ((CMPLINE == CURLINE) && (LCDSTAT & 0x40)) ? 1 : 0; - m_line_irq = 0; - if (!m_delayed_line_irq && m_triggering_line_irq) - { - m_line_irq = m_triggering_line_irq; - m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); - } - if (m_current_line == 153) - { - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LY00_M1); - } - else - { - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LY9X_M1); - } - break; - case GB_LCD_STATE_LY00_M1: /* we stay in VBlank but current line counter should already be incremented */ - /* Check LY=LYC for line #153 */ - if (m_delayed_line_irq) - { - if (m_triggering_line_irq) + /* Mode 2 last for approximately 80 clock cycles */ + m_next_state = GB_LCD_STATE_LYXX_M3; + clear_line_state(); + /* Check for regular compensation of x-scroll register */ + m_scrollx_adjust = SCROLLX & 0x07; + select_sprites(); + m_window_y = WNDPOSY; + state_cycles = 80; + break; + + case GB_LCD_STATE_LYXX_M3: /* Switch to mode 3 */ + for (int i = 0; i < ARRAY_LENGTH(m_line.window_start_y); i++) { - m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + m_line.window_start_y[i] = WNDPOSY; + m_line.window_start_x[i] = WNDPOSX; } + m_line.window_start_y_index = 0; + for (int i = 0; i < ARRAY_LENGTH(m_line.window_enable); i++) + { + m_line.window_enable[i] = LCDCONT; + } + m_line.window_enable_index = 0; + m_stat_mode2_int = false; + check_stat_irq(); + /* Set Mode 3 lcdstate */ + m_mode = 3; + LCDSTAT = (LCDSTAT & 0xFC) | 0x03; + m_oam_locked = LOCKED; + m_vram_locked = LOCKED; + m_pal_locked = LOCKED; + /* Check for compensations of x-scroll register */ + /* Mode 3 lasts for approximately 172+cycles needed to handle sprites clock cycles */ + m_next_state = GB_LCD_STATE_LYXX_PRE_M0; + state_cycles = 168 + m_scrollx_adjust + m_sprite_cycles; + + // This magic 12 needs to be improved as it fixes just one test. We have + // to also support mid-scanline WX write at other times. + m_next_state = GB_LCD_STATE_LYXX_M3_2; + state_cycles = 12; + m_start_x = -1; + break; + + case GB_LCD_STATE_LYXX_M3_2: + m_window_x = WNDPOSX; + calculate_window_cycles(); + m_next_state = GB_LCD_STATE_LYXX_PRE_M0; + state_cycles = 168 - 12 + m_scrollx_adjust + m_sprite_cycles + m_window_cycles; + m_next_state = GB_LCD_STATE_LYXX_M0; + state_cycles = 4 - /*2*/3 + 168 - 12 + m_scrollx_adjust + m_sprite_cycles + m_window_cycles; + break; + + case GB_LCD_STATE_LY9X_M1: /* Switch to or stay in mode 1 */ + m_stat_mode0_int = false; + m_stat_mode2_int = false; + if (CURLINE == 144) + { + /* Trigger VBlank interrupt */ + m_lr35902->set_input_line(lr35902_cpu_device::VBL_INT, ASSERT_LINE); + m_lr35902->execute_set_input(lr35902_cpu_device::VBL_INT, ASSERT_LINE); + /* Set VBlank lcdstate */ + m_mode = 1; + LCDSTAT = (LCDSTAT & 0xFC) | 0x01; + /* Trigger LCD interrupt if requested */ + m_stat_mode1_int = (LCDSTAT & MODE_1_INT_ENABLED) ? true : false; + } + check_stat_irq(); + /* Check if LY==LYC STAT bit should be set */ + if (CURLINE == CMPLINE) + { + LCDSTAT |= LY_LYC_FLAG; + } + else + { + LCDSTAT &= ~LY_LYC_FLAG; + } + m_next_state = GB_LCD_STATE_LY9X_M1_INC; + state_cycles = 452; + break; + + case GB_LCD_STATE_LY9X_M1_INC: /* Increment scanline counter */ + increment_scanline(); + m_stat_lyc_int = ((CMPLINE == CURLINE) && (LCDSTAT & LY_LYC_INT_ENABLED)) ? true : false; + if (m_current_line == 153) + { + m_next_state = GB_LCD_STATE_LY00_M1; + state_cycles = 4; + } + else + { + m_next_state = GB_LCD_STATE_LY9X_M1; + state_cycles = 4; + } + break; + + case GB_LCD_STATE_LY00_M1: /* we stay in VBlank but current line counter should already be incremented */ + LOG(("GB_LCD_STATE_LY00_M1, CURLINE=%u, CMPLINE=%u, m_stat_lyc_int=%s\n", CURLINE, CMPLINE, m_stat_lyc_int ? "true" : "false")); + /* Check LY=LYC for line #153 */ + if (CURLINE == CMPLINE) + { + LCDSTAT |= LY_LYC_FLAG; + } + else + { + LCDSTAT &= ~LY_LYC_FLAG; + } + check_stat_irq(); + m_next_state = GB_LCD_STATE_LY00_M1_1; + state_cycles = 4; + break; + + case GB_LCD_STATE_LY00_M1_1: + increment_scanline(); + m_stat_lyc_int = ((CMPLINE == CURLINE) && (LCDSTAT & LY_LYC_INT_ENABLED)) ? true : false; + LOG(("GB_LCD_STATE_LY00_M1_1, m_stat_lyc_int = %s\n", m_stat_lyc_int ? "true" : "false")); + m_next_state = GB_LCD_STATE_LY00_M1_2; + state_cycles = 4; + break; + + case GB_LCD_STATE_LY00_M1_2: /* Rest of line #0 during VBlank */ + check_stat_irq(); + if (CURLINE == CMPLINE) + { + LCDSTAT |= LY_LYC_FLAG; + } + else + { + LCDSTAT &= ~LY_LYC_FLAG; + } + m_next_state = GB_LCD_STATE_LY00_M0; + state_cycles = 444; + break; + + case GB_LCD_STATE_LY00_M0: /* Just before going to mode 2 for LY 0 */ + m_mode = 2; + m_next_state = GB_LCD_STATE_LY00_M2; + state_cycles = 4; + break; } - m_delayed_line_irq = m_delayed_line_irq | m_line_irq; - if (CURLINE == CMPLINE) - { - LCDSTAT |= 0x04; - } - else - { - LCDSTAT &= ~0x04; - } - increment_scanline(); - m_triggering_line_irq = ((CMPLINE == CURLINE) && (LCDSTAT & 0x40)) ? 1 : 0; - m_line_irq = 0; - LCDSTAT &= 0xFB; - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LY00_M1_1); - break; - case GB_LCD_STATE_LY00_M1_1: - if (!m_delayed_line_irq && m_triggering_line_irq) - { - m_line_irq = m_triggering_line_irq; - m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); - } - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LY00_M1_2); - break; - case GB_LCD_STATE_LY00_M1_2: /* Rest of line #0 during VBlank */ - if (m_delayed_line_irq && m_triggering_line_irq) - { - m_line_irq = m_triggering_line_irq; - m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); - } - if (CURLINE == CMPLINE) + assert(state_cycles > 0); + + if (cycles >= state_cycles) { - LCDSTAT |= 0x04; + cycles -= state_cycles; + m_cycles_left = 0; } else { - LCDSTAT &= ~0x04; + m_cycles_left = state_cycles - cycles; } - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(444), GB_LCD_STATE_LY00_M0); - break; - case GB_LCD_STATE_LY00_M0: /* The STAT register seems to go to 0 for about 4 cycles */ - /* Set Mode 0 lcdstat */ - m_mode = 0; - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(4), GB_LCD_STATE_LY00_M2); - break; } } else @@ -2022,60 +2789,125 @@ TIMER_CALLBACK_MEMBER(cgb_lcd_device::lcd_timer_proc) increment_scanline(); if (m_current_line < 144) { - update_scanline(); + // Force draw of empty line + update_scanline(0); } - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(456)); + m_cycles_left = 456; + } + assert(m_cycles_left > 0); + + m_last_updated = now; + + int next_cycles = m_cycles_left; + + if (m_oam_dma_start_cycles > 0 && m_oam_dma_start_cycles < next_cycles) + { + next_cycles = m_oam_dma_start_cycles; + } + + if (m_oam_dma_cycles_left > 0 && m_oam_dma_cycles_left < next_cycles) + { + next_cycles = m_oam_dma_cycles_left; + } + + if (m_hdma_cycles_to_start > 0 && m_hdma_cycles_to_start < next_cycles) + { + next_cycles = m_hdma_cycles_to_start; } + + m_lcd_timer->adjust(m_lr35902->cycles_to_attotime(next_cycles)); + + m_updating_state = false; } -void gb_lcd_device::lcd_switch_on() +void dmg_ppu_device::lcd_switch_on(UINT8 new_data) { m_current_line = 0; m_previous_line = 153; m_window_lines_drawn = 0; - m_line_irq = 0; - m_delayed_line_irq = 0; - m_mode = 0; - m_oam_locked = LOCKED; /* TODO: Investigate whether this OAM locking is correct. */ - /* Check for LY=LYC coincidence */ - if (CURLINE == CMPLINE) + m_window_cycles = 0; + m_mode = 4; // Starting up + m_sprCount = 0; + m_sprite_cycles = 0; + m_oam_locked = UNLOCKED; + m_oam_locked_reading = UNLOCKED; + m_window_y = 0xFF; + m_stat_mode0_int = false; + m_stat_mode1_int = false; + m_stat_mode2_int = false; + m_stat_lyc_int = false; + m_stat_lyc_int_prev = false; + m_stat_write_int = false; + m_stat_int = false; + m_hdma_cycles_to_start = 0; + m_frame_window_active = false; + // Check for LY=LYC coincidence + if (CURLINE == CMPLINE && CURLINE != m_old_curline) { - LCDSTAT |= 0x04; - /* Generate lcd interrupt if requested */ - if (LCDSTAT & 0x40) + LCDSTAT |= LY_LYC_FLAG; + // Generate lcd interrupt if requested + if (LCDSTAT & LY_LYC_INT_ENABLED) { - m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + m_stat_lyc_int = true; + check_stat_irq(); } } + else + { + LCDSTAT &= ~LY_LYC_FLAG; + } + clear_line_state(); + m_window_y = WNDPOSY; + if ((new_data & WINDOW_ENABLED) && m_current_line == m_window_y) + { + m_line.window_should_trigger = true; + } m_state = GB_LCD_STATE_LY00_M2; - m_lcd_timer->adjust(m_maincpu->cycles_to_attotime(80), GB_LCD_STATE_LYXX_M3); + m_next_state = GB_LCD_STATE_LYXX_M3; + m_cycles_left = 80; + m_lcd_timer->adjust(m_lr35902->cycles_to_attotime(m_cycles_left)); } - - -READ8_MEMBER(gb_lcd_device::vram_r) +READ8_MEMBER(dmg_ppu_device::vram_r) { + if (!space.debugger_access()) + { + update_state(); + LOG(("vram_r: offset=0x%04x\n", offset)); + } + return (m_vram_locked == LOCKED) ? 0xff : m_vram[offset + (m_vram_bank * 0x2000)]; } -WRITE8_MEMBER(gb_lcd_device::vram_w) + +WRITE8_MEMBER(dmg_ppu_device::vram_w) { + update_state(); if (m_vram_locked == LOCKED) return; m_vram[offset + (m_vram_bank * 0x2000)] = data; } -READ8_MEMBER(gb_lcd_device::oam_r) + +READ8_MEMBER(dmg_ppu_device::oam_r) { - return (m_oam_locked == LOCKED) ? 0xff : m_oam[offset]; + if (!space.debugger_access()) + { + update_state(); + LOG(("oam_r: offset=0x%02x\n", offset)); + } + + return (m_oam_locked == LOCKED || m_oam_locked_reading == LOCKED || m_oam_dma_processing) ? 0xff : m_oam[offset]; } -WRITE8_MEMBER(gb_lcd_device::oam_w) + +WRITE8_MEMBER(dmg_ppu_device::oam_w) { - if (m_oam_locked == LOCKED || offset >= 0xa0) + update_state(); + if (m_oam_locked == LOCKED || offset >= 0xa0 || m_oam_dma_processing) return; m_oam[offset] = data; @@ -2083,13 +2915,148 @@ WRITE8_MEMBER(gb_lcd_device::oam_w) -READ8_MEMBER(gb_lcd_device::video_r) +READ8_MEMBER(dmg_ppu_device::video_r) { + if (!space.debugger_access()) + { + update_state(); + if (offset == 1) LOG(("STAT read\n")); + if (offset == 0x28) LOG(("BCPS read, palette is %s\n", m_pal_locked == LOCKED ? "LOCKED" : "UNLOCKED")); + if (offset == 0x29) LOG(("BCPD read, palette is %s\n", m_pal_locked == LOCKED ? "LOCKED" : "UNLOCKED")); + } + return m_vid_regs[offset]; } -WRITE8_MEMBER(gb_lcd_device::video_w) + +bool dmg_ppu_device::stat_write(UINT8 new_data) +{ + LOG(("stat_write: old_data = %02x, new_data = %02x\n", LCDSTAT & 0x78, new_data & 0x78)); + + bool new_lyc_int = m_stat_lyc_int; + + /* Check if line irqs are being enabled */ + if (new_data & 0x40) + { + if ((LCDSTAT & (LY_LYC_INT_ENABLED | LY_LYC_FLAG)) == LY_LYC_FLAG) + { + new_lyc_int = true; + } + } + else + { + new_lyc_int = false; + } + + switch (m_mode) + { + case 0: + m_stat_mode0_int = (new_data & MODE_0_INT_ENABLED) ? true : false; + if (!m_stat_int) + { + if (!(LCDSTAT & MODE_0_INT_ENABLED)) + { + m_stat_write_int = true; + } + } + else + { + if (!(LCDSTAT & MODE_0_INT_ENABLED)) + { + if (!m_stat_lyc_int && !new_lyc_int) + { + // Force an irq + m_stat_int = false; + m_stat_write_int = true; + } + } + } + break; + case 1: + m_stat_mode1_int = (new_data & MODE_1_INT_ENABLED) ? true : false; + if (!m_stat_int) + { + m_stat_write_int = true; + } + else + { + if (!(LCDSTAT & MODE_1_INT_ENABLED)) + { + if (!m_stat_lyc_int && !new_lyc_int) + { + // Force an irq + m_stat_int = false; + m_stat_write_int = true; + } + } + } + break; + case 2: + // 0x20 -> 0x40 with LYC -> trigger + // 0x20 -> 0x60 with LYC -> trigger + //m_stat_mode2_int = (new_data & MODE_2_INT_ENABLED) ? true : false; + if (LCDSTAT & MODE_2_INT_ENABLED) + { + if (!m_stat_lyc_int && new_lyc_int) + { + // Force an irq + m_stat_int = false; + } + } + // Weird trigger for stat irqs + if ((LCDSTAT & (LY_LYC_INT_ENABLED | LY_LYC_FLAG)) == LY_LYC_FLAG) + { + m_stat_write_int = true; + // Force an irq + m_stat_int = false; + } + break; + default: + break; + } + + m_stat_lyc_int = new_lyc_int; + check_stat_irq(); + + return false; +} + + +void dmg_ppu_device::check_stat_irq() +{ + bool new_stat_int = m_stat_mode0_int || m_stat_mode1_int || m_stat_mode2_int || m_stat_lyc_int || m_stat_write_int; + + LOG(("m_mode = %d, m_stat_mode0_int = %s, m_stat_mode1_int = %s, m_stat_mode2_int = %s, m_stat_lyc_int = %s\n", + m_mode, + m_stat_mode0_int ? "true" : "false", + m_stat_mode1_int ? "true" : "false", + m_stat_mode2_int ? "true" : "false", + m_stat_lyc_int ? "true" : "false" + )); + + if (new_stat_int && !m_stat_int) + { + LOG(("--m_stat_mode0_int = %s, m_stat_mode1_int = %s, m_stat_mode2_int = %s, m_stat_lyc_int = %s\n", + m_stat_mode0_int ? "true" : "false", + m_stat_mode1_int ? "true" : "false", + m_stat_mode2_int ? "true" : "false", + m_stat_lyc_int ? "true" : "false" + )); + + m_lr35902->set_input_line(lr35902_cpu_device::LCD_INT, ASSERT_LINE); + m_lr35902->execute_set_input(lr35902_cpu_device::LCD_INT, ASSERT_LINE); + } + + m_stat_int = new_stat_int; + m_stat_write_int = false; +} + + +WRITE8_MEMBER(dmg_ppu_device::video_w) { + update_state(); + LOG(("video_w: offset = %02x, data = %02x\n", offset, data)); + switch (offset) { case 0x00: /* LCDC - LCD Control */ @@ -2098,17 +3065,29 @@ WRITE8_MEMBER(gb_lcd_device::video_w) m_gb_bgdtab_offs = (data & 0x08) ? 0x1c00 : 0x1800; m_gb_wndtab_offs = (data & 0x40) ? 0x1c00 : 0x1800; /* if LCD controller is switched off, set STAT and LY to 00 */ - if (!(data & 0x80)) + if (!(data & ENABLED)) { LCDSTAT &= ~0x03; + m_old_curline = CURLINE; CURLINE = 0; m_oam_locked = UNLOCKED; m_vram_locked = UNLOCKED; } - /* If LCD is being switched on */ - if (!(LCDCONT & 0x80) && (data & 0x80)) + else { - lcd_switch_on(); + /* If LCD is being switched on */ + if (!(LCDCONT & ENABLED)) + { + lcd_switch_on(data); + } + else + { + if (m_line.window_active && !(data & WINDOW_ENABLED)) + { + m_window_lines_drawn++; + m_line.window_active = false; + } + } } break; case 0x01: /* STAT - LCD Status */ @@ -2118,41 +3097,9 @@ WRITE8_MEMBER(gb_lcd_device::video_w) Writing to STAT when the LCD controller is active causes a STAT interrupt to be triggered. */ - if (LCDCONT & 0x80) - { - /* Triggers seen so far: - - 0x40 -> 0x00 - trigger - - 0x00 -> 0x08 - trigger - - 0x08 -> 0x00 - don't trigger - - 0x00 -> 0x20 (mode 3) - trigger - - 0x00 -> 0x60 (mode 2) - don't trigger - - 0x20 -> 0x60 (mode 3) - trigger - - 0x20 -> 0x40 (mode 3) - trigger - - 0x40 -> 0x20 (mode 2) - don't trigger - - 0x40 -> 0x08 (mode 0) - don't trigger - - 0x00 -> 0x40 - trigger only if LY==LYC - - 0x20 -> 0x00/0x08/0x10/0x20/0x40 (mode 2, after m2int) - don't trigger - - 0x20 -> 0x00/0x08/0x10/0x20/0x40 (mode 3, after m2int) - don't trigger - */ - if (!m_mode_irq && ((m_mode == 1) || - ((LCDSTAT & 0x40) && !(data & 0x68)) || - (!(LCDSTAT & 0x40) && (data & 0x40) && (LCDSTAT & 0x04)) || - (!(LCDSTAT & 0x48) && (data & 0x08)) || - ((LCDSTAT & 0x60) == 0x00 && (data & 0x60) == 0x20) || - ((LCDSTAT & 0x60) == 0x20 && (data & 0x40)) - )) - { - m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); - } - /* - - 0x20 -> 0x08/0x18/0x28/0x48 (mode 0, after m2int) - trigger - - 0x20 -> 0x00/0x10/0x20/0x40 (mode 0, after m2int) - trigger (stat bug) - - 0x00 -> 0xXX (mode 0) - trigger stat bug - */ - if (m_mode_irq && m_mode == 0) - { - m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); - } + if (LCDCONT & ENABLED) + { + stat_write(data); } break; case 0x04: /* LY - LCD Y-coordinate */ @@ -2160,60 +3107,79 @@ WRITE8_MEMBER(gb_lcd_device::video_w) case 0x05: /* LYC */ if (CMPLINE != data) { - if (CURLINE == data) + if (CURLINE == data || (m_state == GB_LCD_STATE_LY00_M1 && CURLINE == 0 && data == 153)) { - if (m_state != GB_LCD_STATE_LYXX_M0_INC && m_state != GB_LCD_STATE_LY9X_M1_INC) + LOG(("write LYC, if\n")); + LCDSTAT |= LY_LYC_FLAG; + /* Generate lcd interrupt if requested */ + if (LCDSTAT & LY_LYC_INT_ENABLED) { - LCDSTAT |= 0x04; - /* Generate lcd interrupt if requested */ - if (LCDSTAT & 0x40) + if (m_state != GB_LCD_STATE_LYXX_M0_INC || !m_stat_lyc_int_prev) { - m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + m_stat_lyc_int = true; + // Force an irq? + if (m_stat_mode2_int) + { + m_stat_int = false; + } + check_stat_irq(); } } } else { - LCDSTAT &= 0xFB; - m_triggering_line_irq = 0; + LOG(("write LYC, else\n")); + LCDSTAT &= ~LY_LYC_FLAG; + m_stat_lyc_int = false; + check_stat_irq(); } } break; case 0x06: /* DMA - DMA Transfer and Start Address */ - { - UINT8 *P = m_oam.get(); - offset = (UINT16) data << 8; - for (data = 0; data < 0xA0; data++) - *P++ = space.read_byte(offset++); - } + m_oam_dma_source_address = data << 8; + m_oam_dma_start_cycles = 8; + update_state(); return; case 0x07: /* BGP - Background Palette */ - update_scanline(); + update_scanline(m_lr35902->attotime_to_cycles(m_lcd_timer->remaining())); m_gb_bpal[0] = data & 0x3; m_gb_bpal[1] = (data & 0xC) >> 2; m_gb_bpal[2] = (data & 0x30) >> 4; m_gb_bpal[3] = (data & 0xC0) >> 6; break; case 0x08: /* OBP0 - Object Palette 0 */ -// update_scanline(); +// update_scanline(m_lr35902->attotime_to_cycles(m_lcd_timer->remaining())); m_gb_spal0[0] = data & 0x3; m_gb_spal0[1] = (data & 0xC) >> 2; m_gb_spal0[2] = (data & 0x30) >> 4; m_gb_spal0[3] = (data & 0xC0) >> 6; break; case 0x09: /* OBP1 - Object Palette 1 */ -// update_scanline(); +// update_scanline(m_lr35902->attotime_to_cycles(m_lcd_timer->remaining())); m_gb_spal1[0] = data & 0x3; m_gb_spal1[1] = (data & 0xC) >> 2; m_gb_spal1[2] = (data & 0x30) >> 4; m_gb_spal1[3] = (data & 0xC0) >> 6; break; case 0x02: /* SCY - Scroll Y */ + update_scanline(m_lr35902->attotime_to_cycles(m_lcd_timer->remaining())); + break; case 0x03: /* SCX - Scroll X */ - update_scanline(); + update_scanline(m_lr35902->attotime_to_cycles(m_lcd_timer->remaining())); + LOG(("SCX: scrollx_delay = %d, m_cycles_left = %d\n", m_line.scrollx_delay, m_cycles_left)); + if (m_line.scrollx_delay > 0) + { + // Additional delay cycles; not sure if this is correct. + int adjust = (data & 0x07); + + m_line.scrollx_delay += adjust; + } break; case 0x0A: /* WY - Window Y position */ + LOG(("WY write, m_cycles_left = %d\n", m_cycles_left)); + break; case 0x0B: /* WX - Window X position */ + LOG(("WX write, m_cycles_left = %d\n", m_cycles_left)); break; default: /* Unknown register, no change */ return; @@ -2221,8 +3187,16 @@ WRITE8_MEMBER(gb_lcd_device::video_w) m_vid_regs[offset] = data; } -READ8_MEMBER(cgb_lcd_device::video_r) +READ8_MEMBER(cgb_ppu_device::video_r) { + if (!space.debugger_access()) + { + update_state(); + if (offset == 1) LOG(("STAT read\n")); + if (offset == 0x28) LOG(("BCPS read, palette is %s\n", m_pal_locked == LOCKED ? "LOCKED" : "UNLOCKED")); + if (offset == 0x29) LOG(("BCPD read, palette is %s\n", m_pal_locked == LOCKED ? "LOCKED" : "UNLOCKED")); + } + switch (offset) { case 0x11: /* FF51 */ @@ -2241,8 +3215,71 @@ READ8_MEMBER(cgb_lcd_device::video_r) return m_vid_regs[offset]; } -WRITE8_MEMBER(cgb_lcd_device::video_w) + +bool cgb_ppu_device::stat_write(UINT8 new_data) { + LOG(("stat_write: old_data = %02x, new_data = %02x\n", LCDSTAT & 0x78, new_data & 0x78)); + + bool new_lyc_int = m_stat_lyc_int; + + /* Check if line irqs are being enabled */ + if (m_state != GB_LCD_STATE_LYXX_M0_INC && m_state != GB_LCD_STATE_LY00_M1_1) + { + if (new_data & LY_LYC_INT_ENABLED) + { + if ((LCDSTAT & (LY_LYC_INT_ENABLED | LY_LYC_FLAG)) == LY_LYC_FLAG) + { + new_lyc_int = true; + } + } + else + { + new_lyc_int = false; + } + } + + switch (m_mode) + { + case 0: + m_stat_mode0_int = (new_data & MODE_0_INT_ENABLED) ? true : false; + break; + case 1: + // 0x40 -> 0x50, during incrementing line counter and LY=LYC check getting de-asserted + if (m_state == GB_LCD_STATE_LY9X_M1_INC) + { + check_stat_irq(); + } + m_stat_mode1_int = (new_data & MODE_1_INT_ENABLED) ? true : false; + break; + case 2: + // 0x20 -> 0x40 with LYC -> trigger + //m_stat_mode2_int = (new_data & MODE_2_INT_ENABLED) ? true : false; + if (LCDSTAT & MODE_2_INT_ENABLED) + { + if (!m_stat_lyc_int && new_lyc_int) + { + // Force an irq + m_stat_int = false; + } + } + break; + + default: + break; + } + + m_stat_lyc_int = new_lyc_int; + check_stat_irq(); + + return false; +} + + +WRITE8_MEMBER(cgb_ppu_device::video_w) +{ + update_state(); + LOG(("video_w\n")); + switch (offset) { case 0x00: /* LCDC - LCD Control */ @@ -2257,67 +3294,65 @@ WRITE8_MEMBER(cgb_lcd_device::video_w) if (!(data & 0x80)) { LCDSTAT &= ~0x03; + m_old_curline = CURLINE; CURLINE = 0; m_oam_locked = UNLOCKED; m_vram_locked = UNLOCKED; m_pal_locked = UNLOCKED; } - /* If LCD is being switched on */ - if (!(LCDCONT & 0x80) && (data & 0x80)) - { - lcd_switch_on(); - } - break; - case 0x01: /* STAT - LCD Status */ - data = 0x80 | (data & 0x78) | (LCDSTAT & 0x07); - if (LCDCONT & 0x80) + else { - /* - - 0x20 -> 0x08/0x18/0x28/0x48 (mode 0, after m2int) - trigger - */ - if (m_mode_irq && m_mode == 0 && (LCDSTAT & 0x28) == 0x20 && (data & 0x08)) - { - m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); - } - /* Check if line irqs are being disabled */ - if (!(data & 0x40)) + /* If LCD is being switched on */ + if (!(LCDCONT & ENABLED)) { - m_delayed_line_irq = 0; + lcd_switch_on(data); } - /* Check if line irqs are being enabled */ - if (!(LCDSTAT & 0x40) && (data & 0x40)) + else { - if (CMPLINE == CURLINE) + if (m_line.window_active && !(data & WINDOW_ENABLED)) { - m_line_irq = 1; - m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + m_window_lines_drawn++; + m_line.window_active = false; } } } break; + case 0x01: /* STAT - LCD Status */ + data = 0x80 | (data & 0x78) | (LCDSTAT & 0x07); + if (LCDCONT & ENABLED) + { + stat_write(data); + } + break; case 0x05: /* LYC */ if (CMPLINE != data) { - if ((m_state != GB_LCD_STATE_LYXX_M0_PRE_INC && CURLINE == data) || - (m_state == GB_LCD_STATE_LYXX_M0_INC && m_triggering_line_irq)) + if (CURLINE == data && m_state != GB_LCD_STATE_LY00_M1 && m_state != GB_LCD_STATE_LYXX_M0_PRE_INC) { - LCDSTAT |= 0x04; + LOG(("write LYC, if, CURLINE=%u\n", CURLINE)); + + LCDSTAT |= LY_LYC_FLAG; /* Generate lcd interrupt if requested */ - if (LCDSTAT & 0x40) + if (LCDSTAT & LY_LYC_INT_ENABLED) { - m_maincpu->set_input_line(LCD_INT, ASSERT_LINE); + m_stat_lyc_int = true; + check_stat_irq(); } } else { - LCDSTAT &= 0xFB; - m_triggering_line_irq = 0; + LOG(("write LYC, else, CURLINE=%u\n", CURLINE)); + + LCDSTAT &= ~LY_LYC_FLAG; + check_stat_irq(); m_cmp_line = data; + m_stat_lyc_int = false; + check_stat_irq(); } } break; case 0x07: /* BGP - GB background palette */ - update_scanline(); + update_scanline(m_lr35902->attotime_to_cycles(m_lcd_timer->remaining())); m_gb_bpal[0] = data & 0x3; m_gb_bpal[1] = (data & 0xC) >> 2; m_gb_bpal[2] = (data & 0x30) >> 4; @@ -2345,13 +3380,10 @@ WRITE8_MEMBER(cgb_lcd_device::video_w) case 0x11: /* HDMA1 - HBL General DMA - Source High */ break; case 0x12: /* HDMA2 - HBL General DMA - Source Low */ - data &= 0xF0; break; case 0x13: /* HDMA3 - HBL General DMA - Destination High */ - data &= 0x1F; break; case 0x14: /* HDMA4 - HBL General DMA - Destination Low */ - data &= 0xF0; break; case 0x15: /* HDMA5 - HBL General DMA - Mode, Length */ if (!(data & 0x80)) @@ -2365,7 +3397,6 @@ WRITE8_MEMBER(cgb_lcd_device::video_w) { /* General DMA */ hdma_trans(((data & 0x7F) + 1) * 0x10); -// cpunum_set_reg(0, LR35902_DMA_CYCLES, 4 + (((data & 0x7F) + 1) * 32)); data = 0xff; } } @@ -2379,12 +3410,13 @@ WRITE8_MEMBER(cgb_lcd_device::video_w) if (m_hdma_possible) { hdma_trans(0x10); -// cpunum_set_reg(0, LR35902_DMA_CYCLES, 36); m_hdma_possible = 0; } } break; case 0x28: /* BCPS - Background palette specification */ + LOG(("BCPS write %02x\n", data)); + GBCBCPS = data; if (data & 0x01) GBCBCPD = m_cgb_bpal[(data >> 1) & 0x1F] >> 8; @@ -2392,6 +3424,8 @@ WRITE8_MEMBER(cgb_lcd_device::video_w) GBCBCPD = m_cgb_bpal[(data >> 1) & 0x1F] & 0xFF; break; case 0x29: /* BCPD - background palette data */ + LOG(("BCPD write %02x, palette is %s\n", data, m_pal_locked == LOCKED ? "LOCKED" : "UNLOCKED")); + if (m_pal_locked == LOCKED) { return; @@ -2457,7 +3491,7 @@ WRITE8_MEMBER(cgb_lcd_device::video_w) return; default: /* we didn't handle the write, so pass it to the GB handler */ - gb_lcd_device::video_w(space, offset, data); + dmg_ppu_device::video_w(space, offset, data); return; } @@ -2466,7 +3500,7 @@ WRITE8_MEMBER(cgb_lcd_device::video_w) // Super Game Boy -void sgb_lcd_device::sgb_io_write_pal(int offs, UINT8 *data) +void sgb_ppu_device::sgb_io_write_pal(int offs, UINT8 *data) { switch (offs) { diff --git a/src/devices/video/gb_lcd.h b/src/devices/video/gb_lcd.h index 4c443d827e2..a275bd820ce 100644 --- a/src/devices/video/gb_lcd.h +++ b/src/devices/video/gb_lcd.h @@ -10,6 +10,7 @@ #define __GB_LCD_H__ #include "emu.h" +#include "cpu/lr35902/lr35902.h" struct layer_struct { @@ -26,13 +27,14 @@ struct layer_struct { }; -class gb_lcd_device : public device_t, +class dmg_ppu_device : public device_t, public device_video_interface { public: - gb_lcd_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); - gb_lcd_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); + dmg_ppu_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, UINT32 vram_size); + dmg_ppu_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); + static void static_set_lr35902_tag(device_t &device, const char *tag) { downcast<dmg_ppu_device &>(device).m_lr35902.set_tag(tag); } UINT32 screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect); @@ -46,21 +48,42 @@ public: // FIXME: remove it when proper sgb support is added void set_sgb_hack(bool val) { m_sgb_border_hack = val ? 1 : 0; } + virtual void update_state(); + protected: - inline void plot_pixel(bitmap_ind16 &bitmap, int x, int y, UINT32 color); + enum { + // bits in LCD Control register + ENABLED = 0x80, + WINDOW_ENABLED = 0x20, + LARGE_SPRITES = 0x04, + SPRITES_ENABLED = 0x02, + BACKGROUND_ENABLED = 0x01, + // bits in LCD Status register + LY_LYC_INT_ENABLED = 0x40, + MODE_2_INT_ENABLED = 0x20, + MODE_1_INT_ENABLED = 0x10, + MODE_0_INT_ENABLED = 0x08, + LY_LYC_FLAG = 0x04 + }; + + inline void plot_pixel(int x, int y, UINT16 color); void select_sprites(); + void calculate_window_cycles(); virtual void update_sprites(); - virtual void update_scanline(); + virtual void update_scanline(UINT32 cycles_to_go); // device-level overrides virtual void device_start() override; virtual void device_reset() override; + virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override; + void common_start(); void common_reset(); - // pointer to the main system - cpu_device *m_maincpu; + required_device<lr35902_cpu_device> m_lr35902; + + address_space *m_program_space; // state variables bitmap_ind16 m_bitmap; @@ -87,11 +110,56 @@ protected: UINT16 m_cgb_bpal[32]; /* CGB current background palette table */ UINT16 m_cgb_spal[32]; /* CGB current sprite palette table */ - UINT8 m_gb_bpal[4]; /* Background palette */ - UINT8 m_gb_spal0[4]; /* Sprite 0 palette */ - UINT8 m_gb_spal1[4]; /* Sprite 1 palette */ + UINT16 m_gb_bpal[4]; /* Background palette */ + UINT16 m_gb_spal0[4]; /* Sprite 0 palette */ + UINT16 m_gb_spal1[4]; /* Sprite 1 palette */ + + /* WIP Things used to render current line */ + struct { + // Background/window data + UINT8 tile_cycle; + UINT8 tile_count; + UINT8 y; + UINT16 pattern_address; + UINT8 pattern; + UINT16 tile_address; + UINT8 plane0; + UINT8 plane1; + UINT16 shift_register; + + // Sprite data + struct { + bool enabled; + UINT8 x; + UINT8 y; + UINT8 pattern; + UINT8 flags; + UINT8 tile_plane_0; + UINT8 tile_plane_1; + } sprite[10]; + UINT8 sprite_delay_cycles; + // other internal data + bool starting; // Inital fetches when (re)starting the rendering engine. + UINT8 sequence_counter; + bool drawing; + bool start_drawing; + UINT8 scrollx_delay; + UINT8 scrollx_to_apply; + UINT8 pixels_drawn; + UINT16 window_compare_position; + bool window_active; + UINT8 scrollx; + // To keep track of when changes to WNDPOSY/WNDPOSX should kick in + UINT8 window_start_y[16]; + UINT8 window_start_x[16]; + int window_start_y_index; + // To keep track of when changes to LCDCONT should kick in for window + UINT8 window_enable[16]; + int window_enable_index; + bool window_should_trigger; + } m_line; + bool m_frame_window_active; - /* Things used to render current line */ int m_current_line; /* Current line */ int m_cmp_line; /* Compare line */ int m_sprCount; /* Number of sprites on current line */ @@ -101,25 +169,38 @@ protected: int m_end_x; /* Pixel to end drawing (exclusive) */ int m_mode; /* Keep track of internal STAT mode */ int m_state; /* Current state of the video state machine */ - int m_lcd_irq_line; - int m_triggering_line_irq; - int m_line_irq; - int m_triggering_mode_irq; - int m_mode_irq; - int m_delayed_line_irq; int m_sprite_cycles; + int m_window_cycles; int m_scrollx_adjust; int m_oam_locked; + int m_oam_locked_reading; int m_vram_locked; int m_pal_locked; int m_hdma_enabled; int m_hdma_possible; + int m_hdma_cycles_to_start; + UINT16 m_hdma_length; struct layer_struct m_layer[2]; emu_timer *m_lcd_timer; + int m_oam_dma_start_cycles; + int m_oam_dma_cycles_left; + UINT16 m_oam_dma_source_address; int m_gbc_mode; + UINT8 m_window_x; + UINT8 m_window_y; + UINT8 m_old_curline; + // Interrupt related + bool m_stat_mode0_int; + bool m_stat_mode1_int; + bool m_stat_mode2_int; + bool m_stat_lyc_int; + bool m_stat_lyc_int_prev; + bool m_stat_write_int; + bool m_stat_int; std::unique_ptr<UINT8[]> m_vram; // Pointer to VRAM std::unique_ptr<UINT8[]> m_oam; // Pointer to OAM memory + bool m_oam_dma_processing; UINT8 m_gb_tile_no_mod; UINT32 m_gb_chrgen_offs; // GB Character generator UINT32 m_gb_bgdtab_offs; // GB Background character table @@ -129,18 +210,32 @@ protected: UINT32 m_gbc_wndtab_offs; // CGB Window character table int m_vram_bank; - TIMER_CALLBACK_MEMBER(video_init_vbl); - virtual TIMER_CALLBACK_MEMBER(lcd_timer_proc); + attotime m_last_updated; + UINT64 m_cycles_left; + int m_next_state; + bool m_updating_state; + bool m_enable_experimental_engine; + virtual void videoptr_restore(); + virtual bool stat_write(UINT8 new_data); void increment_scanline(); - void lcd_switch_on(); + void lcd_switch_on(UINT8 new_data); + void update_oam_dma_state(UINT64 cycles); + void check_stat_irq(); + void clear_line_state(); + void update_line_state(UINT64 cycles); + void check_start_of_window(); + +private: + UINT32 m_oam_size; + UINT32 m_vram_size; }; -class mgb_lcd_device : public gb_lcd_device +class mgb_ppu_device : public dmg_ppu_device { public: - mgb_lcd_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); + mgb_ppu_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); protected: @@ -149,10 +244,10 @@ protected: }; -class sgb_lcd_device : public gb_lcd_device +class sgb_ppu_device : public dmg_ppu_device { public: - sgb_lcd_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); + sgb_ppu_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); void sgb_io_write_pal(int offs, UINT8 *data); @@ -163,15 +258,15 @@ protected: virtual void device_reset() override; virtual void update_sprites() override; - virtual void update_scanline() override; + virtual void update_scanline(UINT32 cycles_to_go) override; void refresh_border(); }; -class cgb_lcd_device : public gb_lcd_device +class cgb_ppu_device : public dmg_ppu_device { public: - cgb_lcd_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); + cgb_ppu_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); virtual DECLARE_READ8_MEMBER(video_r) override; virtual DECLARE_WRITE8_MEMBER(video_w) override; @@ -183,31 +278,38 @@ protected: virtual void device_reset() override; virtual void update_sprites() override; - virtual void update_scanline() override; + virtual void update_scanline(UINT32 cycles_to_go) override; - virtual TIMER_CALLBACK_MEMBER(lcd_timer_proc) override; + virtual void update_state() override; virtual void videoptr_restore() override; + virtual bool stat_write(UINT8 new_data) override; + void update_hdma_state(UINT64 cycles); void hdma_trans(UINT16 length); + void hdma_trans_execute(); }; -extern const device_type GB_LCD_DMG; -extern const device_type GB_LCD_MGB; -extern const device_type GB_LCD_SGB; -extern const device_type GB_LCD_CGB; +extern const device_type DMG_PPU; +extern const device_type MGB_PPU; +extern const device_type SGB_PPU; +extern const device_type CGB_PPU; -#define MCFG_GB_LCD_DMG_ADD(_tag ) \ - MCFG_DEVICE_ADD( _tag, GB_LCD_DMG, 0 ) +#define MCFG_DMG_PPU_ADD(_tag, _cpu_tag ) \ + MCFG_DEVICE_ADD( _tag, DMG_PPU, 0 ) \ + dmg_ppu_device::static_set_lr35902_tag(*device, "^" _cpu_tag); -#define MCFG_GB_LCD_MGB_ADD(_tag ) \ - MCFG_DEVICE_ADD( _tag, GB_LCD_MGB, 0 ) +#define MCFG_MGB_PPU_ADD(_tag, _cpu_tag ) \ + MCFG_DEVICE_ADD( _tag, MGB_PPU, 0 ) \ + dmg_ppu_device::static_set_lr35902_tag(*device, "^" _cpu_tag); -#define MCFG_GB_LCD_SGB_ADD(_tag ) \ - MCFG_DEVICE_ADD( _tag, GB_LCD_SGB, 0 ) +#define MCFG_SGB_PPU_ADD(_tag, _cpu_tag ) \ + MCFG_DEVICE_ADD( _tag, SGB_PPU, 0 ) \ + dmg_ppu_device::static_set_lr35902_tag(*device, "^" _cpu_tag); -#define MCFG_GB_LCD_CGB_ADD(_tag ) \ - MCFG_DEVICE_ADD( _tag, GB_LCD_CGB, 0 ) +#define MCFG_CGB_PPU_ADD(_tag, _cpu_tag ) \ + MCFG_DEVICE_ADD( _tag, CGB_PPU, 0 ) \ + dmg_ppu_device::static_set_lr35902_tag(*device, "^" _cpu_tag); #endif /* GB_LCD_H_ */ diff --git a/src/mame/drivers/gb.cpp b/src/mame/drivers/gb.cpp index 34446d4b5d3..6ecf6b78ae8 100644 --- a/src/mame/drivers/gb.cpp +++ b/src/mame/drivers/gb.cpp @@ -18,134 +18,6 @@ - Emulate OAM corruption bug on 16bit inc/dec in $fe** region -Timers -====== - -There seems to be some kind of selectable internal clock divider which is used to drive -the timer increments. This causes the first timer cycle to now always be a full cycle. -For instance in 1024 clock cycle mode, the first timer cycle could easily only take 400 -clock cycles. The next timer cycle will take the full 1024 clock cycles though. - -Writes to the DIV register seem to cause this internal clock divider/register to be -reset in such a way that the next stimulus cause a timer increment (in any mode). - - -Interrupts -========== - -Taking an interrupt seems to take around 20 clock cycles. - - -Stat timing -=========== - -This timing table is accurate within 4 cycles: - | stat = 2 | stat = 3 | stat = 0 | -No sprites | 80 | 172 | 204 | -1 sprite | 80 | 182 | 194 | -2 sprites | 80 | 192 | 184 | -3 sprites | 80 | 202 | 174 | -4 sprites | 80 | 212 | 164 | -5 sprites | 80 | 222 | 154 | -6 sprites | 80 | 232 | 144 | -7 sprites | 80 | 242 | 134 | -8 sprites | 80 | 252 | 124 | -9 sprites | 80 | 262 | 114 | -10 sprites | 80 | 272 | 104 | - -In other words, each sprite on a line makes stat 3 last 10 cycles longer. - - -For lines 1 - 143 when stat changes to 2 the line counter is incremented. - -Line 153 is little odd timing wise. The line counter stays 153 for ~4 clock cycles -and is then rolls over to 0. - -When the line counter is changed it gets checked against the lyc register. - -Here is a detailed run of the STAT and LY register together with LYC set to 3 on a -dmg and mgb. The time between each sample is 4 clock cycles: -STAT: -22222222 22233333 33333333 33333333 33333333 33333333 33333300 00000000 00000000 00000000 -00000000 00000000 00000000 06666666 66666666 66666777 77777777 77777777 77777777 77777777 -77777777 44444444 44444444 44444444 44444444 44444444 44444444 44022222 22222222 - - LY: -33333333 33333333 33333333 33333333 33333333 33333333 33333333 33333333 33333333 33333333 -33333333 33333333 33333333 44444444 44444444 44444444 44444444 44444444 44444444 44444444 -44444444 44444444 44444444 44444444 44444444 44444444 44444444 44555555 55555555 - ^ ^ - -As you can see, it seems as though the LY register is incremented slightly before the STAT -register is changed, resulting in a short period where STAT goes 0 before going to 2. This -bug/feature has been fixed in the CGB and AGB. - - - -Around lines 152-153-0 the picture becomes as follows: -STAT: -11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 -11111111 11111111 11111111 15555555 55555555 55555555 55555555 55555555 55555555 55555555 -55555555 55555555 55555555 55555555 55555555 55555555 55555555 55111111 11111111 11111111 -11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 -11111111 11110222 22222222 22222222 23333333 33333333 33333333 33333333 33333333 - - LY: -77777777 77777777 77777777 77777777 77777777 77777777 77777777 77777777 77777777 77777777 -77777777 77777777 77777777 88888888 88888888 88888888 88888888 88888888 88888888 88888888 -88888888 88888888 88888888 88888888 88888888 88888888 88888888 88900000 00000000 00000000 -00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 -00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 - - - -The full STAT/LY value state machine. -===================================== - -The timing information below is with sprites disabled. - -For STAT we only show the lower 3 bits and for LY only the lower 5 bits of the full -register. Each digit stands for 4 clock cycles (the smallest measurable unit on a -dmg or mgb). When the video hardware is switched on the LY register is set 0 and -the STAT mode is 0. The values for STAT and LY will change as follows: - -STAT 000000000000000000003333333333333333333333333333333333333333333000000000000000000000000000000000000000000000000000 - LY 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001 line #0 - ^LY=LYC bit can get set here LY=LYC bit is reset here^ - -STAT 222222222222222222223333333333333333333333333333333333333333333000000000000000000000000000000000000000000000000000 - LY 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111112 line #1 - - : - : - -STAT 222222222222222222223333333333333333333333333333333333333333333000000000000000000000000000000000000000000000000000 - LY FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0 line #143 - -STAT 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 - LY 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001 line #144 - - : - : - -STAT 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 - LY 888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888889 line #152 - -STAT 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111110 - LY 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 line #153 - ^ - LY=LYC interrupt for 153 can get triggered here - -STAT 222222222222222222223333333333333333333333333333333333333333333000000000000000000000000000000000000000000000000000 - LY 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001 line #0 - -STAT 222222222222222222223333333333333333333333333333333333333333333000000000000000000000000000000000000000000000000000 - LY 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111112 line #1 - - : - etc - - Mappers used in the Game Boy =========================== @@ -429,6 +301,11 @@ space. This mapper uses 32KB sized banks. #include "bus/gameboy/mbc.h" #include "softlist.h" + +#define DMG_FRAMES_PER_SECOND 59.732155 +#define SGB_FRAMES_PER_SECOND 61.17 + + READ8_MEMBER(gb_state::gb_cart_r) { if (m_bios_disable && m_cartslot) @@ -545,75 +422,75 @@ WRITE8_MEMBER(megaduck_state::bank2_w) } -static ADDRESS_MAP_START(gameboy_map, AS_PROGRAM, 8, gb_state ) +static ADDRESS_MAP_START(gameboy_map, AS_PROGRAM, 8, gb_state) ADDRESS_MAP_UNMAP_HIGH AM_RANGE(0x0000, 0x7fff) AM_READWRITE(gb_cart_r, gb_bank_w) - AM_RANGE(0x8000, 0x9fff) AM_DEVREADWRITE("lcd", gb_lcd_device, vram_r, vram_w) /* 8k VRAM */ - AM_RANGE(0xa000, 0xbfff) AM_READWRITE(gb_ram_r, gb_ram_w) /* 8k switched RAM bank (cartridge) */ - AM_RANGE(0xc000, 0xdfff) AM_RAM /* 8k low RAM */ - AM_RANGE(0xe000, 0xfdff) AM_READWRITE(gb_echo_r, gb_echo_w) /* echo RAM */ - AM_RANGE(0xfe00, 0xfeff) AM_DEVREADWRITE("lcd", gb_lcd_device, oam_r, oam_w) /* OAM RAM */ - AM_RANGE(0xff00, 0xff0f) AM_READWRITE(gb_io_r, gb_io_w) /* I/O */ - AM_RANGE(0xff10, 0xff26) AM_DEVREADWRITE("custom", gameboy_sound_device, sound_r, sound_w) /* sound registers */ - AM_RANGE(0xff27, 0xff2f) AM_NOP /* unused */ - AM_RANGE(0xff30, 0xff3f) AM_DEVREADWRITE("custom", gameboy_sound_device, wave_r, wave_w) /* Wave ram */ - AM_RANGE(0xff40, 0xff7f) AM_DEVREAD("lcd", gb_lcd_device, video_r) AM_WRITE(gb_io2_w) /* Video controller & BIOS flip-flop */ - AM_RANGE(0xff80, 0xfffe) AM_RAM /* High RAM */ - AM_RANGE(0xffff, 0xffff) AM_READWRITE(gb_ie_r, gb_ie_w) /* Interrupt enable register */ + AM_RANGE(0x8000, 0x9fff) AM_DEVREADWRITE("ppu", dmg_ppu_device, vram_r, vram_w) /* 8k VRAM */ + AM_RANGE(0xa000, 0xbfff) AM_READWRITE(gb_ram_r, gb_ram_w) /* 8k switched RAM bank (cartridge) */ + AM_RANGE(0xc000, 0xdfff) AM_RAM /* 8k low RAM */ + AM_RANGE(0xe000, 0xfdff) AM_READWRITE(gb_echo_r, gb_echo_w) + AM_RANGE(0xfe00, 0xfeff) AM_DEVREADWRITE("ppu", dmg_ppu_device, oam_r, oam_w) /* OAM RAM */ + AM_RANGE(0xff00, 0xff0f) AM_READWRITE(gb_io_r, gb_io_w) /* I/O */ + AM_RANGE(0xff10, 0xff26) AM_DEVREADWRITE("apu", gameboy_sound_device, sound_r, sound_w) /* sound registers */ + AM_RANGE(0xff27, 0xff2f) AM_NOP /* unused */ + AM_RANGE(0xff30, 0xff3f) AM_DEVREADWRITE("apu", gameboy_sound_device, wave_r, wave_w) /* Wave ram */ + AM_RANGE(0xff40, 0xff7f) AM_DEVREAD("ppu", dmg_ppu_device, video_r) AM_WRITE(gb_io2_w) /* Video controller & BIOS flip-flop */ + AM_RANGE(0xff80, 0xfffe) AM_RAM /* High RAM */ + AM_RANGE(0xffff, 0xffff) AM_READWRITE(gb_ie_r, gb_ie_w) /* Interrupt enable register */ ADDRESS_MAP_END -static ADDRESS_MAP_START(sgb_map, AS_PROGRAM, 8, gb_state ) +static ADDRESS_MAP_START(sgb_map, AS_PROGRAM, 8, gb_state) ADDRESS_MAP_UNMAP_HIGH AM_RANGE(0x0000, 0x7fff) AM_READWRITE(gb_cart_r, gb_bank_w) - AM_RANGE(0x8000, 0x9fff) AM_DEVREADWRITE("lcd", sgb_lcd_device, vram_r, vram_w) /* 8k VRAM */ - AM_RANGE(0xa000, 0xbfff) AM_READWRITE(gb_ram_r, gb_ram_w) /* 8k switched RAM bank (cartridge) */ - AM_RANGE(0xc000, 0xdfff) AM_RAM /* 8k low RAM */ - AM_RANGE(0xe000, 0xfdff) AM_READWRITE(gb_echo_r, gb_echo_w) /* echo RAM */ - AM_RANGE(0xfe00, 0xfeff) AM_DEVREADWRITE("lcd", sgb_lcd_device, oam_r, oam_w) /* OAM RAM */ - AM_RANGE(0xff00, 0xff0f) AM_READWRITE(gb_io_r, sgb_io_w) /* I/O */ - AM_RANGE(0xff10, 0xff26) AM_DEVREADWRITE("custom", gameboy_sound_device, sound_r, sound_w) /* sound registers */ - AM_RANGE(0xff27, 0xff2f) AM_NOP /* unused */ - AM_RANGE(0xff30, 0xff3f) AM_DEVREADWRITE("custom", gameboy_sound_device, wave_r, wave_w) /* Wave RAM */ - AM_RANGE(0xff40, 0xff7f) AM_DEVREAD("lcd", sgb_lcd_device, video_r) AM_WRITE(gb_io2_w) /* Video controller & BIOS flip-flop */ - AM_RANGE(0xff80, 0xfffe) AM_RAM /* High RAM */ - AM_RANGE(0xffff, 0xffff) AM_READWRITE(gb_ie_r, gb_ie_w) /* Interrupt enable register */ + AM_RANGE(0x8000, 0x9fff) AM_DEVREADWRITE("ppu", sgb_ppu_device, vram_r, vram_w) /* 8k VRAM */ + AM_RANGE(0xa000, 0xbfff) AM_READWRITE(gb_ram_r, gb_ram_w) /* 8k switched RAM bank (cartridge) */ + AM_RANGE(0xc000, 0xdfff) AM_RAM /* 8k low RAM */ + AM_RANGE(0xe000, 0xfdff) AM_READWRITE(gb_echo_r, gb_echo_w) + AM_RANGE(0xfe00, 0xfeff) AM_DEVREADWRITE("ppu", sgb_ppu_device, oam_r, oam_w) /* OAM RAM */ + AM_RANGE(0xff00, 0xff0f) AM_READWRITE(gb_io_r, sgb_io_w) /* I/O */ + AM_RANGE(0xff10, 0xff26) AM_DEVREADWRITE("apu", gameboy_sound_device, sound_r, sound_w) /* sound registers */ + AM_RANGE(0xff27, 0xff2f) AM_NOP /* unused */ + AM_RANGE(0xff30, 0xff3f) AM_DEVREADWRITE("apu", gameboy_sound_device, wave_r, wave_w) /* Wave RAM */ + AM_RANGE(0xff40, 0xff7f) AM_DEVREAD("ppu", sgb_ppu_device, video_r) AM_WRITE(gb_io2_w) /* Video controller & BIOS flip-flop */ + AM_RANGE(0xff80, 0xfffe) AM_RAM /* High RAM */ + AM_RANGE(0xffff, 0xffff) AM_READWRITE(gb_ie_r, gb_ie_w) /* Interrupt enable register */ ADDRESS_MAP_END -static ADDRESS_MAP_START(gbc_map, AS_PROGRAM, 8, gb_state ) +static ADDRESS_MAP_START(gbc_map, AS_PROGRAM, 8, gb_state) ADDRESS_MAP_UNMAP_HIGH AM_RANGE(0x0000, 0x7fff) AM_READWRITE(gbc_cart_r, gb_bank_w) - AM_RANGE(0x8000, 0x9fff) AM_DEVREADWRITE("lcd", cgb_lcd_device, vram_r, vram_w) /* 8k VRAM */ - AM_RANGE(0xa000, 0xbfff) AM_READWRITE(gb_ram_r, gb_ram_w) /* 8k switched RAM bank (cartridge) */ - AM_RANGE(0xc000, 0xcfff) AM_RAM /* 4k fixed RAM bank */ - AM_RANGE(0xd000, 0xdfff) AM_RAMBANK("cgb_ram") /* 4k switched RAM bank */ - AM_RANGE(0xe000, 0xfdff) AM_READWRITE(gb_echo_r, gb_echo_w) /* echo RAM */ - AM_RANGE(0xfe00, 0xfeff) AM_DEVREADWRITE("lcd", cgb_lcd_device, oam_r, oam_w) /* OAM RAM */ - AM_RANGE(0xff00, 0xff0f) AM_READWRITE(gb_io_r, gb_io_w) /* I/O */ - AM_RANGE(0xff10, 0xff26) AM_DEVREADWRITE("custom", gameboy_sound_device, sound_r, sound_w) /* sound controller */ - AM_RANGE(0xff27, 0xff2f) AM_NOP /* unused */ - AM_RANGE(0xff30, 0xff3f) AM_DEVREADWRITE("custom", gameboy_sound_device, wave_r, wave_w) /* Wave RAM */ - AM_RANGE(0xff40, 0xff7f) AM_READWRITE(gbc_io2_r, gbc_io2_w) /* Other I/O and video controller */ - AM_RANGE(0xff80, 0xfffe) AM_RAM /* high RAM */ - AM_RANGE(0xffff, 0xffff) AM_READWRITE(gb_ie_r, gb_ie_w) /* Interrupt enable register */ + AM_RANGE(0x8000, 0x9fff) AM_DEVREADWRITE("ppu", cgb_ppu_device, vram_r, vram_w) /* 8k banked VRAM */ + AM_RANGE(0xa000, 0xbfff) AM_READWRITE(gb_ram_r, gb_ram_w) /* 8k switched RAM bank (cartridge) */ + AM_RANGE(0xc000, 0xcfff) AM_RAM /* 4k fixed RAM bank */ + AM_RANGE(0xd000, 0xdfff) AM_RAMBANK("cgb_ram") /* 4k switched RAM bank */ + AM_RANGE(0xe000, 0xfdff) AM_READWRITE(gb_echo_r, gb_echo_w) + AM_RANGE(0xfe00, 0xfeff) AM_DEVREADWRITE("ppu", cgb_ppu_device, oam_r, oam_w) /* OAM RAM */ + AM_RANGE(0xff00, 0xff0f) AM_READWRITE(gb_io_r, gbc_io_w) /* I/O */ + AM_RANGE(0xff10, 0xff26) AM_DEVREADWRITE("apu", gameboy_sound_device, sound_r, sound_w) /* sound controller */ + AM_RANGE(0xff27, 0xff2f) AM_NOP /* unused */ + AM_RANGE(0xff30, 0xff3f) AM_DEVREADWRITE("apu", gameboy_sound_device, wave_r, wave_w) /* Wave RAM */ + AM_RANGE(0xff40, 0xff7f) AM_READWRITE(gbc_io2_r, gbc_io2_w) /* Other I/O and video controller */ + AM_RANGE(0xff80, 0xfffe) AM_RAM /* high RAM */ + AM_RANGE(0xffff, 0xffff) AM_READWRITE(gb_ie_r, gb_ie_w) /* Interrupt enable register */ ADDRESS_MAP_END -static ADDRESS_MAP_START(megaduck_map, AS_PROGRAM, 8, megaduck_state ) +static ADDRESS_MAP_START(megaduck_map, AS_PROGRAM, 8, megaduck_state) ADDRESS_MAP_UNMAP_HIGH AM_RANGE(0x0000, 0x7fff) AM_READWRITE(cart_r, bank1_w) - AM_RANGE(0x8000, 0x9fff) AM_DEVREADWRITE("lcd", gb_lcd_device, vram_r, vram_w) /* 8k VRAM */ - AM_RANGE(0xa000, 0xafff) AM_NOP /* unused? */ + AM_RANGE(0x8000, 0x9fff) AM_DEVREADWRITE("ppu", dmg_ppu_device, vram_r, vram_w) /* 8k VRAM */ + AM_RANGE(0xa000, 0xafff) AM_NOP /* unused? */ AM_RANGE(0xb000, 0xb000) AM_WRITE(bank2_w) - AM_RANGE(0xb001, 0xbfff) AM_NOP /* unused? */ - AM_RANGE(0xc000, 0xfe9f) AM_RAM /* 8k low RAM, echo RAM */ - AM_RANGE(0xfe00, 0xfeff) AM_DEVREADWRITE("lcd", gb_lcd_device, oam_r, oam_w) /* OAM RAM */ - AM_RANGE(0xff00, 0xff0f) AM_READWRITE(gb_io_r, gb_io_w) /* I/O */ - AM_RANGE(0xff10, 0xff1f) AM_READWRITE(megaduck_video_r, megaduck_video_w) /* video controller */ - AM_RANGE(0xff20, 0xff2f) AM_READWRITE(megaduck_sound_r1, megaduck_sound_w1) /* sound controller pt1 */ - AM_RANGE(0xff30, 0xff3f) AM_DEVREADWRITE("custom", gameboy_sound_device, wave_r, wave_w) /* wave ram */ - AM_RANGE(0xff40, 0xff46) AM_READWRITE(megaduck_sound_r2, megaduck_sound_w2) /* sound controller pt2 */ - AM_RANGE(0xff47, 0xff7f) AM_NOP /* unused */ - AM_RANGE(0xff80, 0xfffe) AM_RAM /* high RAM */ - AM_RANGE(0xffff, 0xffff) AM_READWRITE(gb_ie_r, gb_ie_w) /* interrupt enable register */ + AM_RANGE(0xb001, 0xbfff) AM_NOP /* unused? */ + AM_RANGE(0xc000, 0xfe9f) AM_RAM /* 8k/16k? RAM */ + AM_RANGE(0xfe00, 0xfeff) AM_DEVREADWRITE("ppu", dmg_ppu_device, oam_r, oam_w) /* OAM RAM */ + AM_RANGE(0xff00, 0xff0f) AM_READWRITE(gb_io_r, gb_io_w) /* I/O */ + AM_RANGE(0xff10, 0xff1f) AM_READWRITE(megaduck_video_r, megaduck_video_w) /* video controller */ + AM_RANGE(0xff20, 0xff2f) AM_READWRITE(megaduck_sound_r1, megaduck_sound_w1) /* sound controller pt1 */ + AM_RANGE(0xff30, 0xff3f) AM_DEVREADWRITE("apu", gameboy_sound_device, wave_r, wave_w) /* wave ram */ + AM_RANGE(0xff40, 0xff46) AM_READWRITE(megaduck_sound_r2, megaduck_sound_w2) /* sound controller pt2 */ + AM_RANGE(0xff47, 0xff7f) AM_NOP /* unused */ + AM_RANGE(0xff80, 0xfffe) AM_RAM /* high RAM */ + AM_RANGE(0xffff, 0xffff) AM_READWRITE(gb_ie_r, gb_ie_w) /* interrupt enable register */ ADDRESS_MAP_END static GFXDECODE_START( gb ) @@ -758,7 +635,7 @@ static MACHINE_CONFIG_START( gameboy, gb_state ) MCFG_SCREEN_ADD("screen", LCD) MCFG_SCREEN_REFRESH_RATE(DMG_FRAMES_PER_SECOND) MCFG_SCREEN_VBLANK_TIME(0) - MCFG_SCREEN_UPDATE_DEVICE("lcd", gb_lcd_device, screen_update) + MCFG_SCREEN_UPDATE_DEVICE("ppu", dmg_ppu_device, screen_update) MCFG_SCREEN_PALETTE("palette") MCFG_DEFAULT_LAYOUT(layout_lcd) @@ -770,11 +647,11 @@ static MACHINE_CONFIG_START( gameboy, gb_state ) MCFG_PALETTE_ADD("palette", 4) MCFG_PALETTE_INIT_OWNER(gb_state,gb) - MCFG_GB_LCD_DMG_ADD("lcd") + MCFG_DMG_PPU_ADD("ppu", "maincpu") /* sound hardware */ MCFG_SPEAKER_STANDARD_STEREO("lspeaker", "rspeaker") - MCFG_SOUND_ADD("custom", GAMEBOY, 0) + MCFG_SOUND_ADD("apu", DMG_APU, XTAL_4_194304Mhz) MCFG_SOUND_ROUTE(0, "lspeaker", 0.50) MCFG_SOUND_ROUTE(1, "rspeaker", 0.50) @@ -785,13 +662,11 @@ static MACHINE_CONFIG_START( gameboy, gb_state ) MCFG_SOFTWARE_LIST_COMPATIBLE_ADD("gbc_list","gbcolor") MACHINE_CONFIG_END -static MACHINE_CONFIG_DERIVED( supergb, gameboy ) +static MACHINE_CONFIG_START( supergb, gb_state ) /* basic machine hardware */ - MCFG_CPU_REPLACE("maincpu", LR35902, 4295454) /* 4.295454 MHz, derived from SNES xtal */ + MCFG_CPU_ADD("maincpu", LR35902, 4295454) /* 4.295454 MHz, derived from SNES xtal */ MCFG_CPU_PROGRAM_MAP(sgb_map) - - MCFG_CPU_MODIFY("maincpu") MCFG_LR35902_TIMER_CB( WRITE8(gb_state, gb_timer_callback ) ) MCFG_LR35902_HALT_BUG @@ -799,29 +674,41 @@ static MACHINE_CONFIG_DERIVED( supergb, gameboy ) MCFG_MACHINE_RESET_OVERRIDE(gb_state, sgb) /* video hardware */ - MCFG_DEFAULT_LAYOUT(layout_horizont) /* runs on a TV, not an LCD */ + MCFG_SCREEN_ADD("screen", LCD) + MCFG_SCREEN_REFRESH_RATE(SGB_FRAMES_PER_SECOND) + MCFG_SCREEN_VBLANK_TIME(0) + MCFG_SCREEN_UPDATE_DEVICE("ppu", dmg_ppu_device, screen_update) + MCFG_SCREEN_PALETTE("palette") - MCFG_SCREEN_MODIFY("screen") + MCFG_DEFAULT_LAYOUT(layout_horizont) /* runs on a TV, not an LCD */ MCFG_SCREEN_SIZE(32*8, 28*8) MCFG_SCREEN_VISIBLE_AREA(0*8, 32*8-1, 0*8, 28*8-1) - MCFG_PALETTE_MODIFY("palette") - MCFG_PALETTE_ENTRIES(32768) + MCFG_GFXDECODE_ADD("gfxdecode", "palette", gb) + MCFG_PALETTE_ADD("palette", 32768) MCFG_PALETTE_INIT_OWNER(gb_state,sgb) - MCFG_DEVICE_REMOVE("lcd") - MCFG_GB_LCD_SGB_ADD("lcd") + MCFG_SGB_PPU_ADD("ppu", "maincpu") + + /* sound hardware */ + MCFG_SPEAKER_STANDARD_STEREO("lspeaker", "rspeaker") + MCFG_SOUND_ADD("apu", DMG_APU, 4295454) + MCFG_SOUND_ROUTE(0, "lspeaker", 0.50) + MCFG_SOUND_ROUTE(1, "rspeaker", 0.50) + + /* cartslot */ + MCFG_GB_CARTRIDGE_ADD("gbslot", gb_cart, nullptr) + + MCFG_SOFTWARE_LIST_ADD("cart_list","gameboy") + MCFG_SOFTWARE_LIST_COMPATIBLE_ADD("gbc_list","gbcolor") MACHINE_CONFIG_END + static MACHINE_CONFIG_DERIVED( supergb2, gameboy ) /* basic machine hardware */ MCFG_CPU_MODIFY("maincpu") MCFG_CPU_PROGRAM_MAP(sgb_map) - MCFG_CPU_MODIFY("maincpu") - MCFG_LR35902_TIMER_CB( WRITE8(gb_state, gb_timer_callback ) ) - MCFG_LR35902_HALT_BUG - MCFG_MACHINE_START_OVERRIDE(gb_state, sgb) MCFG_MACHINE_RESET_OVERRIDE(gb_state, sgb) @@ -836,18 +723,19 @@ static MACHINE_CONFIG_DERIVED( supergb2, gameboy ) MCFG_PALETTE_ENTRIES(32768) MCFG_PALETTE_INIT_OWNER(gb_state,sgb) - MCFG_DEVICE_REMOVE("lcd") - MCFG_GB_LCD_SGB_ADD("lcd") + MCFG_DEVICE_REMOVE("ppu") + MCFG_SGB_PPU_ADD("ppu", "maincpu") MACHINE_CONFIG_END + static MACHINE_CONFIG_DERIVED( gbpocket, gameboy ) /* video hardware */ MCFG_PALETTE_MODIFY("palette") MCFG_PALETTE_INIT_OWNER(gb_state,gbp) - MCFG_DEVICE_REMOVE("lcd") - MCFG_GB_LCD_MGB_ADD("lcd") + MCFG_DEVICE_REMOVE("ppu") + MCFG_MGB_PPU_ADD("ppu", "maincpu") MACHINE_CONFIG_END static MACHINE_CONFIG_START( gbcolor, gb_state ) @@ -864,7 +752,7 @@ static MACHINE_CONFIG_START( gbcolor, gb_state ) MCFG_SCREEN_ADD("screen", LCD) MCFG_SCREEN_REFRESH_RATE(DMG_FRAMES_PER_SECOND) MCFG_SCREEN_VBLANK_TIME(0) - MCFG_SCREEN_UPDATE_DEVICE("lcd", gb_lcd_device, screen_update) + MCFG_SCREEN_UPDATE_DEVICE("ppu", dmg_ppu_device, screen_update) MCFG_SCREEN_PALETTE("palette") MCFG_DEFAULT_LAYOUT(layout_lcd) @@ -877,11 +765,11 @@ static MACHINE_CONFIG_START( gbcolor, gb_state ) MCFG_PALETTE_ADD("palette", 32768) MCFG_PALETTE_INIT_OWNER(gb_state,gbc) - MCFG_GB_LCD_CGB_ADD("lcd") + MCFG_CGB_PPU_ADD("ppu", "maincpu") /* sound hardware */ MCFG_SPEAKER_STANDARD_STEREO("lspeaker", "rspeaker") - MCFG_SOUND_ADD("custom", GAMEBOY, 0) + MCFG_SOUND_ADD("apu", CGB04_APU, XTAL_4_194304Mhz) MCFG_SOUND_ROUTE(0, "lspeaker", 0.50) MCFG_SOUND_ROUTE(1, "rspeaker", 0.50) @@ -899,7 +787,7 @@ MACHINE_CONFIG_END static MACHINE_CONFIG_START( megaduck, megaduck_state ) /* basic machine hardware */ - MCFG_CPU_ADD("maincpu", LR35902, 4194304) /* 4.194304 MHz */ + MCFG_CPU_ADD("maincpu", LR35902, XTAL_4_194304Mhz) /* 4.194304 MHz */ MCFG_CPU_PROGRAM_MAP(megaduck_map) MCFG_LR35902_TIMER_CB( WRITE8(gb_state, gb_timer_callback ) ) MCFG_LR35902_HALT_BUG @@ -913,7 +801,7 @@ static MACHINE_CONFIG_START( megaduck, megaduck_state ) MCFG_MACHINE_START_OVERRIDE(megaduck_state, megaduck) MCFG_MACHINE_RESET_OVERRIDE(megaduck_state, megaduck) - MCFG_SCREEN_UPDATE_DEVICE("lcd", gb_lcd_device, screen_update) + MCFG_SCREEN_UPDATE_DEVICE("ppu", dmg_ppu_device, screen_update) MCFG_SCREEN_SIZE(20*8, 18*8) MCFG_SCREEN_VISIBLE_AREA(0*8, 20*8-1, 0*8, 18*8-1) @@ -923,11 +811,11 @@ static MACHINE_CONFIG_START( megaduck, megaduck_state ) MCFG_PALETTE_ADD("palette", 4) MCFG_PALETTE_INIT_OWNER(megaduck_state,megaduck) - MCFG_GB_LCD_DMG_ADD("lcd") + MCFG_DMG_PPU_ADD("ppu", "maincpu") /* sound hardware */ MCFG_SPEAKER_STANDARD_STEREO("lspeaker", "rspeaker") - MCFG_SOUND_ADD("custom", GAMEBOY, 0) + MCFG_SOUND_ADD("apu", DMG_APU, XTAL_4_194304Mhz) MCFG_SOUND_ROUTE(0, "lspeaker", 0.50) MCFG_SOUND_ROUTE(1, "rspeaker", 0.50) diff --git a/src/mame/drivers/gba.cpp b/src/mame/drivers/gba.cpp index 2bb96e8df53..aa6ac9fa745 100644 --- a/src/mame/drivers/gba.cpp +++ b/src/mame/drivers/gba.cpp @@ -1408,7 +1408,7 @@ static MACHINE_CONFIG_START( gbadv, gba_state ) MCFG_GBA_LCD_DMA_VBLANK(WRITELINE(gba_state, dma_vblank_callback)) MCFG_SPEAKER_STANDARD_STEREO("spkleft", "spkright") - MCFG_SOUND_ADD("custom", GAMEBOY, 0) + MCFG_SOUND_ADD("custom", CGB04_APU, XTAL_16_777216MHz/4) MCFG_SOUND_ROUTE(0, "spkleft", 0.50) MCFG_SOUND_ROUTE(1, "spkright", 0.50) MCFG_SOUND_ADD("direct_a_left", DAC, 0) // GBA direct sound A left diff --git a/src/mame/drivers/vgmplay.cpp b/src/mame/drivers/vgmplay.cpp index a5f40be8e8e..d06178e826d 100644 --- a/src/mame/drivers/vgmplay.cpp +++ b/src/mame/drivers/vgmplay.cpp @@ -1328,7 +1328,7 @@ static MACHINE_CONFIG_START( vgmplay, vgmplay_state ) MCFG_SOUND_ROUTE(0, "lspeaker", 1) MCFG_SOUND_ROUTE(1, "rspeaker", 1) - MCFG_SOUND_ADD("dmg", GAMEBOY, 0) + MCFG_SOUND_ADD("dmg", DMG_APU, XTAL_4_194304Mhz) MCFG_SOUND_ROUTE(0, "lspeaker", 1) MCFG_SOUND_ROUTE(1, "rspeaker", 1) diff --git a/src/mame/includes/gb.h b/src/mame/includes/gb.h index 3a08c307a8c..9b8c3e89656 100644 --- a/src/mame/includes/gb.h +++ b/src/mame/includes/gb.h @@ -15,33 +15,6 @@ #include "machine/ram.h" #include "video/gb_lcd.h" -/* Interrupts */ -#define VBL_INT 0 /* V-Blank */ -#define LCD_INT 1 /* LCD Status */ -#define TIM_INT 2 /* Timer */ -#define SIO_INT 3 /* Serial I/O */ -#define EXT_INT 4 /* Joypad */ - -#ifdef TIMER -#undef TIMER -#endif - -/* Cartridge types */ -#define CART_RAM 0x01 /* Cartridge has RAM */ -#define BATTERY 0x02 /* Cartridge has a battery to save RAM */ -#define TIMER 0x04 /* Cartridge has a real-time-clock (MBC3 only) */ -#define RUMBLE 0x08 /* Cartridge has a rumble motor (MBC5 only) */ -#define SRAM 0x10 /* Cartridge has SRAM */ -#define UNKNOWN 0x80 /* Cartridge is of an unknown type */ - -#define DMG_FRAMES_PER_SECOND 59.732155 -#define SGB_FRAMES_PER_SECOND 61.17 - - -#define MAX_ROMBANK 512 -#define MAX_RAMBANK 256 - - class gb_state : public driver_device { @@ -50,13 +23,13 @@ public: : driver_device(mconfig, type, tag), m_cartslot(*this, "gbslot"), m_maincpu(*this, "maincpu"), - m_custom(*this, "custom"), + m_apu(*this, "apu"), m_region_maincpu(*this, "maincpu"), m_rambank(*this, "cgb_ram"), m_inputs(*this, "INPUTS"), m_bios_hack(*this, "SKIP_CHECK"), m_ram(*this, RAM_TAG), - m_lcd(*this, "lcd") { } + m_ppu(*this, "ppu") { } //gb_state driver_data; UINT8 m_gb_io[0x10]; @@ -69,8 +42,9 @@ public: UINT8 m_reloading; /* Serial I/O related */ + UINT16 m_internal_serial_clock; + UINT16 m_internal_serial_frequency; UINT32 m_sio_count; /* Serial I/O counter */ - emu_timer *m_gb_serial_timer; /* SGB variables */ INT8 m_sgb_packets; @@ -86,7 +60,7 @@ public: UINT8 *m_gbc_rammap[8]; /* (CGB) Addresses of internal RAM banks */ UINT8 m_gbc_rambank; /* (CGB) Current CGB RAM bank */ - int m_bios_disable; + bool m_bios_disable; DECLARE_WRITE8_MEMBER(gb_io_w); DECLARE_WRITE8_MEMBER(gb_io2_w); @@ -94,6 +68,7 @@ public: DECLARE_READ8_MEMBER(gb_ie_r); DECLARE_WRITE8_MEMBER(gb_ie_w); DECLARE_READ8_MEMBER(gb_io_r); + DECLARE_WRITE8_MEMBER(gbc_io_w); DECLARE_WRITE8_MEMBER(gbc_io2_w); DECLARE_READ8_MEMBER(gbc_io2_r); DECLARE_PALETTE_INIT(gb); @@ -104,7 +79,6 @@ public: DECLARE_MACHINE_START(gbc); DECLARE_MACHINE_RESET(gbc); DECLARE_PALETTE_INIT(gbc); - TIMER_CALLBACK_MEMBER(gb_serial_timer_proc); DECLARE_WRITE8_MEMBER(gb_timer_callback); DECLARE_READ8_MEMBER(gb_cart_r); @@ -118,18 +92,19 @@ public: protected: required_device<lr35902_cpu_device> m_maincpu; - required_device<gameboy_sound_device> m_custom; + required_device<gameboy_sound_device> m_apu; required_memory_region m_region_maincpu; optional_memory_bank m_rambank; // cgb required_ioport m_inputs; required_ioport m_bios_hack; optional_device<ram_device> m_ram; - required_device<gb_lcd_device> m_lcd; + required_device<dmg_ppu_device> m_ppu; void gb_timer_increment(); void gb_timer_check_irq(); void gb_init(); void gb_init_regs(); + void gb_serial_timer_tick(); void save_gb_base(); void save_gbc_only(); diff --git a/src/mame/machine/gb.cpp b/src/mame/machine/gb.cpp index 1ba32e66ed1..2dcdb09413d 100644 --- a/src/mame/machine/gb.cpp +++ b/src/mame/machine/gb.cpp @@ -80,36 +80,27 @@ TODO: 13/6/2005 WP - Added support for bootstrap rom banking. ***************************************************************************/ -#define __MACHINE_GB_C #include "emu.h" #include "includes/gb.h" +#define ENABLE_LOGGING 0 +#define LOG(x) do { if (ENABLE_LOGGING) logerror x; } while(0) + + /* RAM layout defines */ -#define CGB_START_VRAM_BANKS 0x0000 #define CGB_START_RAM_BANKS ( 2 * 8 * 1024 ) + #define JOYPAD m_gb_io[0x00] /* Joystick: 1.1.P15.P14.P13.P12.P11.P10 */ #define SIODATA m_gb_io[0x01] /* Serial IO data buffer */ #define SIOCONT m_gb_io[0x02] /* Serial IO control register */ -#define DIVREG m_gb_io[0x04] /* Divider register (???) */ #define TIMECNT m_gb_io[0x05] /* Timer counter. Gen. int. when it overflows */ #define TIMEMOD m_gb_io[0x06] /* New value of TimeCount after it overflows */ #define TIMEFRQ m_gb_io[0x07] /* Timer frequency and start/stop switch */ - -/* - Prototypes -*/ - - -#ifdef MAME_DEBUG -/* #define V_GENERAL*/ /* Display general debug information */ -/* #define V_BANK*/ /* Display bank switching debug information */ -#endif - //------------------------- // handle save state //------------------------- @@ -160,29 +151,31 @@ void gb_state::gb_init_regs() void gb_state::gb_init() { address_space &space = m_maincpu->space(AS_PROGRAM); - m_custom->sound_w(space, 0x16, 0x00); /* Initialize sound hardware */ + m_apu->sound_w(space, 0x16, 0x00); /* Initialize sound hardware */ - m_divcount = 0; + m_divcount = 8; + m_internal_serial_clock = 0; + m_internal_serial_frequency = 512 / 2; m_triggering_irq = 0; + m_shift = 10; // slowest timer? + m_shift_cycles = 1 << m_shift; + + /* Set registers to default/startup values */ + m_gb_io[0x00] = 0xCF; + m_gb_io[0x01] = 0x00; + m_gb_io[0x02] = 0x7E; + m_gb_io[0x03] = 0xFF; m_gb_io[0x07] = 0xF8; /* Upper bits of TIMEFRQ register are set to 1 */ } void gb_state::machine_start() { - /* Allocate the serial timer, and disable it */ - m_gb_serial_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(gb_state::gb_serial_timer_proc),this)); - m_gb_serial_timer->enable( 0 ); - save_gb_base(); } MACHINE_START_MEMBER(gb_state,gbc) { - /* Allocate the serial timer, and disable it */ - m_gb_serial_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(gb_state::gb_serial_timer_proc),this)); - m_gb_serial_timer->enable( 0 ); - for (int i = 0; i < 8; i++) m_gbc_rammap[i] = m_ram->pointer() + CGB_START_RAM_BANKS + i * 0x1000; @@ -195,15 +188,11 @@ MACHINE_START_MEMBER(gb_state,sgb) { m_sgb_packets = -1; - /* Allocate the serial timer, and disable it */ - m_gb_serial_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(gb_state::gb_serial_timer_proc),this)); - m_gb_serial_timer->enable( 0 ); - save_gb_base(); save_sgb_only(); if (m_cartslot && m_cartslot->get_sgb_hack()) { - dynamic_cast<sgb_lcd_device*>(m_lcd.target())->set_sgb_hack(TRUE); + dynamic_cast<sgb_ppu_device*>(m_ppu.target())->set_sgb_hack(TRUE); } } @@ -212,9 +201,7 @@ void gb_state::machine_reset() gb_init(); /* Enable BIOS rom */ - m_bios_disable = 0; - - m_divcount = 0x0004; + m_bios_disable = false; } MACHINE_RESET_MEMBER(gb_state,gbc) @@ -224,7 +211,7 @@ MACHINE_RESET_MEMBER(gb_state,gbc) gb_init_regs(); /* Enable BIOS rom */ - m_bios_disable = 0; + m_bios_disable = false; for (auto & elem : m_gbc_rammap) memset(elem, 0, 0x1000); @@ -237,9 +224,7 @@ MACHINE_RESET_MEMBER(gb_state,sgb) gb_init_regs(); /* Enable BIOS rom */ - m_bios_disable = 0; - - m_divcount = 0x0004; + m_bios_disable = false; } @@ -259,37 +244,42 @@ WRITE8_MEMBER(gb_state::gb_io_w) case 0x01: /* SB - Serial transfer data */ break; case 0x02: /* SC - SIO control */ - switch( data & 0x81 ) + switch (data & 0x81) { case 0x00: case 0x01: - case 0x80: /* enabled & external clock */ m_sio_count = 0; + case 0x80: /* enabled & external clock */ + m_sio_count = 16; break; case 0x81: /* enabled & internal clock */ - SIODATA = 0xFF; - m_sio_count = 8; - m_gb_serial_timer->adjust(m_maincpu->cycles_to_attotime(512), 0, m_maincpu->cycles_to_attotime(512)); - m_gb_serial_timer->enable( 1 ); + m_sio_count = 16; break; } +logerror("SIOCONT write, serial clock is %04x\n", m_internal_serial_clock); + data |= 0x7E; // unused bits stay high break; + case 0x03: + return; case 0x04: /* DIV - Divider register */ - /* Force increment of TIMECNT register */ - if ( m_divcount >= 16 ) + /* Force increment of TIMECNT register when the 'highest' bit is set */ + if ((m_divcount >> (m_shift - 1)) & 1) + { gb_timer_increment(); + } + LOG(("DIV write\n")); m_divcount = 0; return; case 0x05: /* TIMA - Timer counter */ /* Check if the counter is being reloaded in this cycle */ - if ( m_reloading && ( m_divcount & ( m_shift_cycles - 1 ) ) == 4 ) + if ((TIMEFRQ & 0x04) && TIMECNT == TIMEMOD && (m_divcount & (m_shift_cycles - 1)) == 4) { - data = TIMECNT; + data = TIMEMOD; } break; case 0x06: /* TMA - Timer module */ /* Check if the counter is being reloaded in this cycle */ - if ( m_reloading && ( m_divcount & ( m_shift_cycles - 1 ) ) == 4 ) + if ((TIMEFRQ & 0x04) && TIMECNT == TIMEMOD && (m_divcount & (m_shift_cycles - 1)) == 4) { TIMECNT = data; } @@ -297,10 +287,10 @@ WRITE8_MEMBER(gb_state::gb_io_w) case 0x07: /* TAC - Timer control */ data |= 0xF8; /* Check if timer is just disabled or the timer frequency is changing */ - if ( ( ! ( data & 0x04 ) && ( TIMEFRQ & 0x04 ) ) || ( ( data & 0x04 ) && ( TIMEFRQ & 0x04 ) && ( data & 0x03 ) != ( TIMEFRQ & 0x03 ) ) ) + if ((!(data & 0x04) && (TIMEFRQ & 0x04)) || ((data & 0x04) && (TIMEFRQ & 0x04) && (data & 0x03) != (TIMEFRQ & 0x03))) { /* Check if TIMECNT should be incremented */ - if ( ( m_divcount & ( m_shift_cycles - 1 ) ) >= ( m_shift_cycles >> 1 ) ) + if ((m_divcount & (m_shift_cycles - 1)) >= (m_shift_cycles >> 1)) { gb_timer_increment(); } @@ -309,8 +299,10 @@ WRITE8_MEMBER(gb_state::gb_io_w) m_shift_cycles = 1 << m_shift; break; case 0x0F: /* IF - Interrupt flag */ + m_ppu->update_state(); + LOG(("write if\n")); data &= 0x1F; - m_maincpu->set_if( data ); + m_maincpu->set_if(data); break; } @@ -322,11 +314,10 @@ WRITE8_MEMBER(gb_state::gb_io2_w) if (offset == 0x10) { /* disable BIOS ROM */ - m_bios_disable = 1; - //printf("here again?\n"); + m_bios_disable = true; } else - m_lcd->video_w(space, offset, data); + m_ppu->video_w(space, offset, data); } #ifdef MAME_DEBUG @@ -371,7 +362,7 @@ WRITE8_MEMBER(gb_state::sgb_io_w) { UINT8 *sgb_data = m_sgb_data; - switch( offset ) + switch (offset) { case 0x00: switch (data & 0x30) @@ -411,15 +402,13 @@ WRITE8_MEMBER(gb_state::sgb_io_w) case 0x20: /* data false */ if (m_sgb_rest) { - if( m_sgb_bytecount == 16 && m_sgb_packets == -1 ) + if (m_sgb_bytecount == 16 && m_sgb_packets == -1) { -#ifdef MAME_DEBUG - logerror("SGB: %s (%02X) pkts: %d data: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n", + LOG(("SGB: %s (%02X) pkts: %d data: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n", sgbcmds[sgb_data[0] >> 3],sgb_data[0] >> 3, sgb_data[0] & 0x07, sgb_data[1], sgb_data[2], sgb_data[3], sgb_data[4], sgb_data[5], sgb_data[6], sgb_data[7], sgb_data[8], sgb_data[9], sgb_data[10], sgb_data[11], - sgb_data[12], sgb_data[13], sgb_data[14], sgb_data[15]); -#endif + sgb_data[12], sgb_data[13], sgb_data[14], sgb_data[15])); m_sgb_packets = sgb_data[0] & 0x07; m_sgb_start = 0; } @@ -434,14 +423,14 @@ WRITE8_MEMBER(gb_state::sgb_io_w) m_sgb_controller_mode = 2; break; default: - dynamic_cast<sgb_lcd_device*>(m_lcd.target())->sgb_io_write_pal(sgb_data[0] >> 3, &sgb_data[0]); + dynamic_cast<sgb_ppu_device*>(m_ppu.target())->sgb_io_write_pal(sgb_data[0] >> 3, &sgb_data[0]); break; } m_sgb_start = 0; m_sgb_bytecount = 0; m_sgb_packets = -1; } - if( m_sgb_start ) + if (m_sgb_start) { sgb_data[m_sgb_bytecount] >>= 1; m_sgb_bitcount++; @@ -469,14 +458,14 @@ WRITE8_MEMBER(gb_state::sgb_io_w) JOYPAD = 0x3F; /* Hack to let cartridge know it's running on an SGB */ - if ( (sgb_data[0] >> 3) == 0x1F ) + if ((sgb_data[0] >> 3) == 0x1F) JOYPAD = 0x3E; break; } return; default: /* we didn't handle the write, so pass it to the GB handler */ - gb_io_w( space, offset, data ); + gb_io_w(space, offset, data); return; } @@ -491,7 +480,7 @@ READ8_MEMBER(gb_state::gb_ie_r) WRITE8_MEMBER(gb_state::gb_ie_w) { - m_maincpu->set_ie( data & 0x1F ); + m_maincpu->set_ie(data); } /* IO read */ @@ -500,7 +489,8 @@ READ8_MEMBER(gb_state::gb_io_r) switch(offset) { case 0x04: - return ( m_divcount >> 8 ) & 0xFF; + LOG(("read DIV, divcount = %04x\n", m_divcount)); + return (m_divcount >> 8) & 0xFF; case 0x00: case 0x01: case 0x02: @@ -511,39 +501,55 @@ READ8_MEMBER(gb_state::gb_io_r) return m_gb_io[offset]; case 0x0F: /* Make sure the internal states are up to date */ + m_ppu->update_state(); + LOG(("read if\n")); +logerror("IF read, serial clock is %04x\n", m_internal_serial_clock); return 0xE0 | m_maincpu->get_if(); default: - /* It seems unsupported registers return 0xFF */ + /* Unsupported registers return 0xFF */ return 0xFF; } } -TIMER_CALLBACK_MEMBER(gb_state::gb_serial_timer_proc) +/* Called when 512 internal cycles are passed */ +void gb_state::gb_serial_timer_tick() { - /* Shift in a received bit */ - SIODATA = (SIODATA << 1) | 0x01; - /* Decrement number of handled bits */ - m_sio_count--; - /* If all bits done, stop timer and trigger interrupt */ - if ( ! m_sio_count ) + if (SIOCONT & 0x80) { - SIOCONT &= 0x7F; - m_gb_serial_timer->enable( 0 ); - m_maincpu->set_input_line(SIO_INT, ASSERT_LINE); + if (m_sio_count & 1) + { + /* Shift in a received bit */ + SIODATA = (SIODATA << 1) | 0x01; + } + /* Decrement number of handled bits */ + m_sio_count--; + + LOG(("%04x - gb_serial_timer_proc: SIODATA = %02x, sio_count = %u\n", m_maincpu->pc(), SIODATA, m_sio_count)); + /* If all bits done, stop timer and trigger interrupt */ + if (m_sio_count == 0) + { + SIOCONT &= 0x7F; + m_maincpu->set_input_line(lr35902_cpu_device::SIO_INT, ASSERT_LINE); + // Make sure the state is updated during the current timeslice in case it is read. + m_maincpu->execute_set_input(lr35902_cpu_device::SIO_INT, ASSERT_LINE); + } } } + void gb_state::gb_timer_check_irq() { m_reloading = 0; - if ( m_triggering_irq ) + if (m_triggering_irq) { m_triggering_irq = 0; - if ( TIMECNT == 0 ) + if (TIMECNT == 0) { TIMECNT = TIMEMOD; - m_maincpu->set_input_line(TIM_INT, ASSERT_LINE); + m_maincpu->set_input_line(lr35902_cpu_device::TIM_INT, ASSERT_LINE); + // Make sure the state is updated during the current timeslice in case it is read. + m_maincpu->execute_set_input(lr35902_cpu_device::TIM_INT, ASSERT_LINE); m_reloading = 1; } } @@ -553,49 +559,75 @@ void gb_state::gb_timer_increment() { gb_timer_check_irq(); + LOG(("increment timer\n")); TIMECNT += 1; - if ( TIMECNT == 0 ) + if (TIMECNT == 0) { m_triggering_irq = 1; } } -WRITE8_MEMBER( gb_state::gb_timer_callback ) +// This gets called while the cpu is executing instructions to keep the timer state in sync +WRITE8_MEMBER(gb_state::gb_timer_callback) { UINT16 old_gb_divcount = m_divcount; + UINT16 old_internal_serial_clock = m_internal_serial_clock; m_divcount += data; + m_internal_serial_clock += data; + if ( (old_gb_divcount >> 8) != (m_divcount >> 8)) { + //LOG(("DIV became %02x\n", m_divcount >> 8)); + } gb_timer_check_irq(); - if ( TIMEFRQ & 0x04 ) + if (TIMEFRQ & 0x04) { UINT16 old_count = old_gb_divcount >> m_shift; UINT16 new_count = m_divcount >> m_shift; - if ( data > m_shift_cycles ) + if (data > m_shift_cycles) { gb_timer_increment(); old_count++; } - if ( new_count != old_count ) + if (new_count != old_count) { gb_timer_increment(); + if (new_count << m_shift < m_divcount) + { + gb_timer_check_irq(); + } } - if ( new_count << m_shift < m_divcount ) - { - gb_timer_check_irq(); - } + } + + if ((m_internal_serial_clock ^ old_internal_serial_clock) & m_internal_serial_frequency) + { + gb_serial_timer_tick(); + } +} + + +WRITE8_MEMBER(gb_state::gbc_io_w) +{ + gb_io_w(space, offset, data); + + // On CGB the internal serial transfer clock is selectable + if (offset == 0x02) + { + m_internal_serial_frequency = ((data & 0x02) ? 16 : 512) / 2; + SIOCONT = (SIOCONT & ~0x02) | (data & 0x02); } } + WRITE8_MEMBER(gb_state::gbc_io2_w) { - switch( offset ) + switch (offset) { case 0x0D: /* KEY1 - Prepare speed switch */ m_maincpu->set_speed(data); return; case 0x10: /* BFF - Bios disable */ - m_bios_disable = 1; + m_bios_disable = true; return; case 0x16: /* RP - Infrared port */ break; @@ -608,12 +640,12 @@ WRITE8_MEMBER(gb_state::gbc_io2_w) default: break; } - m_lcd->video_w(space, offset, data); + m_ppu->video_w(space, offset, data); } READ8_MEMBER(gb_state::gbc_io2_r) { - switch( offset ) + switch (offset) { case 0x0D: /* KEY1 */ return m_maincpu->get_speed(); @@ -624,7 +656,7 @@ READ8_MEMBER(gb_state::gbc_io2_r) default: break; } - return m_lcd->video_r(space, offset); + return m_ppu->video_r(space, offset); } /**************************************************************************** @@ -635,10 +667,6 @@ READ8_MEMBER(gb_state::gbc_io2_r) MACHINE_START_MEMBER(megaduck_state,megaduck) { - /* Allocate the serial timer, and disable it */ - m_gb_serial_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(gb_state::gb_serial_timer_proc),this)); - m_gb_serial_timer->enable( 0 ); - save_gb_base(); } @@ -647,7 +675,7 @@ MACHINE_RESET_MEMBER(megaduck_state,megaduck) /* We may have to add some more stuff here, if not then it can be merged back into gb */ gb_init(); - m_bios_disable = 1; + m_bios_disable = true; } /* @@ -694,8 +722,8 @@ READ8_MEMBER(megaduck_state::megaduck_video_r) { offset ^= 0x0C; } - data = m_lcd->video_r(space, offset); - if ( offset ) + data = m_ppu->video_r(space, offset); + if (offset) return data; return BITSWAP8(data,7,0,5,4,6,3,2,1); } @@ -710,7 +738,7 @@ WRITE8_MEMBER(megaduck_state::megaduck_video_w) { offset ^= 0x0C; } - m_lcd->video_w(space, offset, data); + m_ppu->video_w(space, offset, data); } /* Map megaduck audio offset to game boy audio offsets */ @@ -721,14 +749,14 @@ static const UINT8 megaduck_sound_offsets[16] = { 0, 2, 1, 3, 4, 6, 5, 7, 8, 9, WRITE8_MEMBER(megaduck_state::megaduck_sound_w1) { if ((offset == 0x01) || (offset == 0x07)) - m_custom->sound_w(space, megaduck_sound_offsets[offset], ((data & 0x0f)<<4) | ((data & 0xf0)>>4)); + m_apu->sound_w(space, megaduck_sound_offsets[offset], ((data & 0x0f)<<4) | ((data & 0xf0)>>4)); else - m_custom->sound_w(space, megaduck_sound_offsets[offset], data); + m_apu->sound_w(space, megaduck_sound_offsets[offset], data); } READ8_MEMBER(megaduck_state::megaduck_sound_r1) { - UINT8 data = m_custom->sound_r(space, megaduck_sound_offsets[offset]); + UINT8 data = m_apu->sound_r(space, megaduck_sound_offsets[offset]); if ((offset == 0x01) || (offset == 0x07)) return ((data & 0x0f)<<4) | ((data & 0xf0)>>4); else @@ -738,14 +766,14 @@ READ8_MEMBER(megaduck_state::megaduck_sound_r1) WRITE8_MEMBER(megaduck_state::megaduck_sound_w2) { if ((offset == 0x01) || (offset == 0x02)) - m_custom->sound_w(space, 0x10 + megaduck_sound_offsets[offset], ((data & 0x0f)<<4) | ((data & 0xf0)>>4)); + m_apu->sound_w(space, 0x10 + megaduck_sound_offsets[offset], ((data & 0x0f)<<4) | ((data & 0xf0)>>4)); else - m_custom->sound_w(space, 0x10 + megaduck_sound_offsets[offset], data); + m_apu->sound_w(space, 0x10 + megaduck_sound_offsets[offset], data); } READ8_MEMBER(megaduck_state::megaduck_sound_r2) { - UINT8 data = m_custom->sound_r(space, 0x10 + megaduck_sound_offsets[offset]); + UINT8 data = m_apu->sound_r(space, 0x10 + megaduck_sound_offsets[offset]); if ((offset == 0x01) || (offset == 0x02)) return ((data & 0x0f)<<4) | ((data & 0xf0)>>4); else |