diff --git a/inc/peanut_gb.h b/inc/peanut_gb.h index 7edd26e..98b7ff1 100644 --- a/inc/peanut_gb.h +++ b/inc/peanut_gb.h @@ -176,8 +176,8 @@ /** LCD characteristics **/ /* PPU cycles through modes every 456 cycles. */ #define LCD_LINE_CYCLES 456 -/* Mode 0 starts on cycle 0. */ -#define LCD_MODE_0_CYCLES 0 +/* Mode 0 starts on cycle 372. */ +#define LCD_MODE_0_CYCLES 372 /* Mode 2 starts on cycle 204. */ #define LCD_MODE_2_CYCLES 204 /* Mode 3 starts on cycle 284. */ @@ -745,10 +745,8 @@ uint8_t __gb_read(struct gb_s *gb, const uint16_t addr) return 0xFF; case 0xC: - return gb->wram[addr - WRAM_0_ADDR]; - case 0xD: - return gb->wram[addr - WRAM_1_ADDR + WRAM_BANK_SIZE]; + return gb->wram[addr - WRAM_0_ADDR]; case 0xE: return gb->wram[addr - ECHO_ADDR]; @@ -792,7 +790,7 @@ uint8_t __gb_read(struct gb_s *gb, const uint16_t addr) /* Return address that caused read error. */ (gb->gb_error)(gb, GB_INVALID_READ, addr); - // PGB_UNREACHABLE(); + PGB_UNREACHABLE(); } /** @@ -991,35 +989,37 @@ void __gb_write(struct gb_s *gb, const uint_fast16_t addr, const uint8_t val) /* LCD Registers */ case 0x40: - if(((gb->hram_io[IO_LCDC] & LCDC_ENABLE) == 0) && - (val & LCDC_ENABLE)) - { - gb->counter.lcd_count = 0; - gb->lcd_blank = 1; - } + { + uint8_t lcd_enabled; + + /* Check if LCD is already enabled. */ + lcd_enabled = (gb->hram_io[IO_LCDC] & LCDC_ENABLE); gb->hram_io[IO_LCDC] = val; - /* LY fixed to 0 when LCD turned off. */ - if((gb->hram_io[IO_LCDC] & LCDC_ENABLE) == 0) + /* Check if LCD is going to be switched on. */ + if (!lcd_enabled && (val & LCDC_ENABLE)) { - /* Do not turn off LCD outside of VBLANK. This may - * happen due to poor timing in this emulator. */ - if((gb->hram_io[IO_STAT] & STAT_MODE) != IO_STAT_MODE_VBLANK) - { - gb->hram_io[IO_LCDC] |= LCDC_ENABLE; - return; - } + gb->lcd_blank = 1; + } + /* Check if LCD is being switched off. */ + else if (lcd_enabled && !(val & LCDC_ENABLE)) + { + /* Peanut-GB will happily turn off LCD outside + * of VBLANK even though this damages real + * hardware. */ /* Set LCD to Mode 0. */ - gb->hram_io[IO_STAT] = (gb->hram_io[IO_STAT] & ~0x03); - /* Set to line 0. */ + gb->hram_io[IO_STAT] = + (gb->hram_io[IO_STAT] & ~STAT_MODE) | + IO_STAT_MODE_HBLANK; + /* LY fixed to 0 when LCD turned off. */ gb->hram_io[IO_LY] = 0; /* Reset LCD timer. */ gb->counter.lcd_count = 0; } - return; + } case 0x41: gb->hram_io[IO_STAT] = (val & STAT_USER_BITS) | (gb->hram_io[IO_STAT] & STAT_MODE); @@ -2355,7 +2355,7 @@ void __gb_step_cpu(struct gb_s *gb) /* This may be intentional, but this is required to stop an infinite * loop. */ (gb->gb_error)(gb, GB_HALT_FOREVER, gb->cpu_reg.pc.reg - 1); - // PGB_UNREACHABLE(); + PGB_UNREACHABLE(); } if(gb->hram_io[IO_SC] & SERIAL_SC_TX_START) @@ -2376,22 +2376,31 @@ void __gb_step_cpu(struct gb_s *gb) halt_cycles = tac_cycles; } - if((gb->hram_io[IO_LCDC] & LCDC_ENABLE) != 0) + if((gb->hram_io[IO_LCDC] & LCDC_ENABLE)) { int lcd_cycles; - if((gb->hram_io[IO_STAT] & STAT_MODE) == IO_STAT_MODE_SEARCH_OAM) + /* If LCD is in HBlank, calculate the number of cycles + * until the end of HBlank and the start of mode 2 or + * mode 1. */ + if((gb->hram_io[IO_STAT] & STAT_MODE) == IO_STAT_MODE_HBLANK) + { + lcd_cycles = LCD_MODE_2_CYCLES - + gb->counter.lcd_count; + } + else if((gb->hram_io[IO_STAT] & STAT_MODE) == IO_STAT_MODE_SEARCH_OAM) { lcd_cycles = LCD_MODE_3_CYCLES - gb->counter.lcd_count; } - else if((gb->hram_io[IO_STAT] & STAT_MODE) == IO_STAT_MODE_HBLANK) + else if((gb->hram_io[IO_STAT] & STAT_MODE) == IO_STAT_MODE_SEARCH_TRANSFER) { - lcd_cycles = LCD_MODE_2_CYCLES - + lcd_cycles = LCD_MODE_0_CYCLES - gb->counter.lcd_count; } else { + /* VBlank */ lcd_cycles = LCD_LINE_CYCLES - gb->counter.lcd_count; } @@ -3153,9 +3162,9 @@ void __gb_step_cpu(struct gb_s *gb) break; default: - /* Return address where invalid opcode that was read. */ + /* Return address where invlid opcode that was read. */ (gb->gb_error)(gb, GB_INVALID_OPCODE, gb->cpu_reg.pc.reg - 1); - // PGB_UNREACHABLE(); + PGB_UNREACHABLE(); } do @@ -3239,9 +3248,9 @@ void __gb_step_cpu(struct gb_s *gb) } } - /* TODO Check behaviour of LCD during LCD power off state. */ - /* If LCD is off, don't update LCD state. */ - if((gb->hram_io[IO_LCDC] & LCDC_ENABLE) == 0) + /* If LCD is off, don't update LCD state or increase the LCD + * ticks. */ + if(!(gb->hram_io[IO_LCDC] & LCDC_ENABLE)) continue; /* LCD Timing */ @@ -3279,7 +3288,6 @@ void __gb_step_cpu(struct gb_s *gb) gb->hram_io[IO_IF] |= LCDC_INTR; #if ENABLE_LCD - /* If frame skip is activated, check if we need to draw * the frame or skip it. */ if(gb->direct.frame_skip) @@ -3292,15 +3300,15 @@ void __gb_step_cpu(struct gb_s *gb) * updated. Also, only update lines on frames that are * actually drawn when frame skip is enabled. */ if(gb->direct.interlace && - (!gb->direct.frame_skip || - gb->display.frame_skip_count)) + (!gb->direct.frame_skip || + gb->display.frame_skip_count)) { gb->display.interlace_count = !gb->display.interlace_count; } #endif } - /* Normal Line */ + /* Normal Line */ else if(gb->hram_io[IO_LY] < LCD_HEIGHT) { if(gb->hram_io[IO_LY] == 0) @@ -3321,9 +3329,9 @@ void __gb_step_cpu(struct gb_s *gb) inst_cycles = LCD_MODE_2_CYCLES - gb->counter.lcd_count; } } - /* OAM access */ + /* OAM access */ else if((gb->hram_io[IO_STAT] & STAT_MODE) == IO_STAT_MODE_HBLANK && - gb->counter.lcd_count >= LCD_MODE_2_CYCLES) + gb->counter.lcd_count >= LCD_MODE_2_CYCLES) { gb->hram_io[IO_STAT] = (gb->hram_io[IO_STAT] & ~STAT_MODE) | IO_STAT_MODE_SEARCH_OAM; @@ -3335,9 +3343,9 @@ void __gb_step_cpu(struct gb_s *gb) if (gb->counter.lcd_count < LCD_MODE_3_CYCLES) inst_cycles = LCD_MODE_3_CYCLES - gb->counter.lcd_count; } - /* Update LCD */ + /* Update LCD */ else if((gb->hram_io[IO_STAT] & STAT_MODE) == IO_STAT_MODE_SEARCH_OAM && - gb->counter.lcd_count >= LCD_MODE_3_CYCLES) + gb->counter.lcd_count >= LCD_MODE_3_CYCLES) { gb->hram_io[IO_STAT] = (gb->hram_io[IO_STAT] & ~STAT_MODE) | IO_STAT_MODE_SEARCH_TRANSFER; @@ -3346,8 +3354,8 @@ void __gb_step_cpu(struct gb_s *gb) __gb_draw_line(gb); #endif /* If halted immediately jump to next LCD mode. */ - if (gb->counter.lcd_count < LCD_LINE_CYCLES) - inst_cycles = LCD_LINE_CYCLES - gb->counter.lcd_count; + if (gb->counter.lcd_count < LCD_MODE_0_CYCLES) + inst_cycles = LCD_MODE_0_CYCLES - gb->counter.lcd_count; } } while(gb->gb_halt && (gb->hram_io[IO_IF] & gb->hram_io[IO_IE]) == 0); /* If halted, loop until an interrupt occurs. */ @@ -3687,6 +3695,14 @@ enum gb_init_error_e gb_init(struct gb_s *gb, */ void gb_run_frame(struct gb_s *gb); +/** + * Internal function used to step the CPU. Used mainly for testing. + * Use gb_run_frame() instead. + * + * \param An initialised emulator context. Must not be NULL. + */ +void __gb_step_cpu(struct gb_s *gb); + /** Function prototypes: Optional Functions **/ /** * Reset the emulator, like turning the Game Boy off and on again.