summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
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
commitc3e0ead53494abe36105587fd485e317a788ec5e (patch)
treeee7724532fef63d8f78768e6a267b1d40af3f648
parent8566a1fd10ff711d3caa09a4941f436c979df89a (diff)
gb.cpp: Some cpu, sound, and video updates (nw)
-rw-r--r--hash/snes.xml2
-rw-r--r--src/devices/bus/snes/sgb.cpp102
-rw-r--r--src/devices/bus/snes/sgb.h37
-rw-r--r--src/devices/bus/snes/snes_carts.cpp1
-rw-r--r--src/devices/cpu/lr35902/lr35902.cpp104
-rw-r--r--src/devices/cpu/lr35902/lr35902.h31
-rw-r--r--src/devices/cpu/lr35902/opc_main.hxx111
-rw-r--r--src/devices/sound/gb.cpp1471
-rw-r--r--src/devices/sound/gb.h236
-rw-r--r--src/devices/video/gb_lcd.cpp2998
-rw-r--r--src/devices/video/gb_lcd.h184
-rw-r--r--src/mame/drivers/gb.cpp308
-rw-r--r--src/mame/drivers/gba.cpp2
-rw-r--r--src/mame/drivers/vgmplay.cpp2
-rw-r--r--src/mame/includes/gb.h43
-rw-r--r--src/mame/machine/gb.cpp242
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