Simplify DMA code, fix DMA read timing

This commit is contained in:
Lior Halphon 2022-01-12 00:26:18 +02:00
parent b1187919d3
commit c4a14ac4db
6 changed files with 43 additions and 46 deletions

View File

@ -470,7 +470,7 @@ static void add_object_from_index(GB_gameboy_t *gb, unsigned index)
if (gb->n_visible_objs == 10) return;
/* TODO: It appears that DMA blocks PPU access to OAM, but it needs verification. */
if (gb->dma_steps_left && (gb->dma_cycles >= 0 || gb->is_dma_restarting)) {
if (GB_is_dma_active(gb)) {
return;
}
@ -1215,7 +1215,7 @@ static inline uint16_t mode3_batching_length(GB_gameboy_t *gb)
{
if (gb->model & GB_MODEL_NO_SFC_BIT) return 0;
if (gb->hdma_on) return 0;
if (gb->dma_steps_left) return 0;
if (GB_is_dma_active(gb)) return 0;
if (gb->wy_triggered && (gb->io_registers[GB_IO_LCDC] & 0x20) && (gb->io_registers[GB_IO_WX] < 8 || gb->io_registers[GB_IO_WX] == 166)) {
return 0;
}
@ -1406,7 +1406,7 @@ void GB_display_run(GB_gameboy_t *gb, unsigned cycles, bool force)
GB_STAT_update(gb);
gb->n_visible_objs = 0;
if (!gb->dma_steps_left && !gb->oam_ppu_blocked) {
if (!GB_is_dma_active(gb) && !gb->oam_ppu_blocked) {
GB_BATCHPOINT(gb, display, 5, 80);
}
for (gb->oam_search_index = 0; gb->oam_search_index < 40; gb->oam_search_index++) {

View File

@ -1639,8 +1639,8 @@ void GB_reset(GB_gameboy_t *gb)
gb->io_registers[GB_IO_DMA] = gb->io_registers[GB_IO_OBP0] = gb->io_registers[GB_IO_OBP1] = GB_is_cgb(gb)? 0x00 : 0xFF;
gb->accessed_oam_row = -1;
gb->dma_current_dest = 0xa1;
if (GB_is_hle_sgb(gb)) {
if (!gb->sgb) {
gb->sgb = malloc(sizeof(*gb->sgb));

View File

@ -431,13 +431,11 @@ struct GB_gameboy_internal_s {
int16_t hdma_cycles; // in 8MHz units
uint16_t hdma_current_src, hdma_current_dest;
uint8_t dma_steps_left;
uint8_t dma_current_dest;
uint8_t last_dma_read;
uint16_t dma_current_src;
int16_t dma_cycles;
bool is_dma_restarting;
uint8_t dma_and_pattern;
bool dma_skip_write;
uint8_t last_opcode_read; /* Required to emulte HDMA reads from Exxx */
bool hdma_starting;
);

View File

@ -251,7 +251,7 @@ void GB_trigger_oam_bug_read(GB_gameboy_t *gb, uint16_t address)
static bool is_addr_in_dma_use(GB_gameboy_t *gb, uint16_t addr)
{
if (!gb->dma_steps_left || (gb->dma_cycles < 0 && !gb->is_dma_restarting) || addr >= 0xfe00) return false;
if (!GB_is_dma_active(gb) || addr >= 0xfe00) return false;
if (addr >= 0xfe00) return false;
if (gb->dma_current_src == addr) return false; // Shortcut for DMA access flow
if (gb->dma_current_src > 0xe000 && (gb->dma_current_src & ~0x2000) == addr) return false;
@ -482,7 +482,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr)
return 0xff;
}
if ((gb->dma_steps_left && (gb->dma_cycles > 0 || gb->is_dma_restarting))) {
if (GB_is_dma_active(gb)) {
/* Todo: Does reading from OAM during DMA causes the OAM bug? */
return 0xff;
}
@ -746,14 +746,14 @@ uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr)
if (GB_is_cgb(gb) && bus_for_addr(gb, gb->dma_current_src) != GB_BUS_RAM && addr >= 0xc000) {
// TODO: this should probably affect the DMA dest as well
addr = (gb->dma_current_src & 0x1000) | (addr & 0xFFF) | 0xC000;
addr = ((gb->dma_current_src - 1) & 0x1000) | (addr & 0xFFF) | 0xC000;
}
else if (GB_is_cgb(gb) && gb->dma_current_src >= 0xe000 && addr > 0xc000) {
// TODO: this should probably affect the DMA dest as well
addr = (gb->dma_current_src & 0x1000) | (addr & 0xFFF) | 0xC000;
addr = ((gb->dma_current_src - 1) & 0x1000) | (addr & 0xFFF) | 0xC000;
}
else {
addr = gb->dma_current_src;
addr = (gb->dma_current_src - 1);
}
}
uint8_t data = read_map[addr >> 12](gb, addr);
@ -1209,7 +1209,7 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
return;
}
if ((gb->dma_steps_left && (gb->dma_cycles > 0 || gb->is_dma_restarting))) {
if (GB_is_dma_active(gb)) {
/* Todo: Does writing to OAM during DMA causes the OAM bug? */
return;
}
@ -1459,18 +1459,16 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
return;
case GB_IO_DMA:
if (gb->dma_steps_left) {
if (GB_is_dma_active(gb)) {
/* This is not correct emulation, since we're not really delaying the second DMA.
One write that should have happened in the first DMA will not happen. However,
since that byte will be overwritten by the second DMA before it can actually be
read, it doesn't actually matter. */
gb->is_dma_restarting = true;
}
gb->dma_and_pattern = 0xFF;
gb->dma_cycles = -7;
gb->dma_cycles = -3;
gb->dma_current_dest = 0;
gb->dma_current_src = value << 8;
gb->dma_steps_left = 0xa0;
gb->io_registers[GB_IO_DMA] = value;
return;
case GB_IO_SVBK:
@ -1663,23 +1661,24 @@ void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
if (GB_is_cgb(gb) && bus_for_addr(gb, gb->dma_current_src) != GB_BUS_RAM && addr >= 0xc000) {
// TODO: this should probably affect the DMA dest as well
addr = (gb->dma_current_src & 0x1000) | (addr & 0xFFF) | 0xC000;
addr = ((gb->dma_current_src - 1) & 0x1000) | (addr & 0xFFF) | 0xC000;
}
else if (GB_is_cgb(gb) && gb->dma_current_src >= 0xe000 && addr > 0xc000) {
// TODO: this should probably affect the DMA dest as well
addr = (gb->dma_current_src & 0x1000) | (addr & 0xFFF) | 0xC000;
addr = ((gb->dma_current_src - 1) & 0x1000) | (addr & 0xFFF) | 0xC000;
}
else {
addr = gb->dma_current_src;
addr = (gb->dma_current_src - 1);
}
if (GB_is_cgb(gb) || addr > 0xc000) {
gb->dma_and_pattern = addr < 0xc000? 0x00 : 0xFF;
if ((gb->model < GB_MODEL_CGB_0 || gb->model == GB_MODEL_CGB_B) && addr > 0xc000) {
gb->dma_and_pattern = value;
if (addr < 0xc000) {
gb->oam[gb->dma_current_dest - 1] = 0;
}
else if ((gb->model < GB_MODEL_CGB_C || gb->model > GB_MODEL_CGB_E) && addr > 0xc000 && !oam_write) {
gb->dma_skip_write = true;
gb->oam[gb->dma_current_dest] = value;
else if ((gb->model < GB_MODEL_CGB_0 || gb->model == GB_MODEL_CGB_B)) {
gb->oam[gb->dma_current_dest - 1] &= value;
}
else if ((gb->model < GB_MODEL_CGB_C || gb->model > GB_MODEL_CGB_E) && !oam_write) {
gb->oam[gb->dma_current_dest - 1] = value;
}
if (gb->model < GB_MODEL_CGB_E || addr >= 0xc000) return;
}
@ -1687,34 +1686,38 @@ void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
write_map[addr >> 12](gb, addr, value);
}
bool GB_is_dma_active(GB_gameboy_t *gb)
{
return gb->dma_current_dest < 0xa1 || gb->is_dma_restarting;
}
void GB_dma_run(GB_gameboy_t *gb)
{
while (unlikely(gb->dma_cycles >= 4 && gb->dma_steps_left)) {
/* Todo: measure this value */
if (gb->dma_current_dest >= 0xa1) return;
while (unlikely(gb->dma_cycles >= 4)) {
gb->dma_cycles -= 4;
gb->dma_steps_left--;
if (gb->dma_skip_write) {
gb->dma_skip_write = false;
gb->dma_current_dest++;
if (gb->dma_current_dest >= 0xa1) {
gb->is_dma_restarting = false;
break;
}
else if (gb->dma_current_src < 0xe000) {
gb->oam[gb->dma_current_dest++] = GB_read_memory(gb, gb->dma_current_src) & gb->dma_and_pattern;
if (gb->dma_current_dest >= 0xa0) {
gb->dma_current_dest++;
continue;
}
if (gb->dma_current_src < 0xe000) {
gb->oam[gb->dma_current_dest++] = GB_read_memory(gb, gb->dma_current_src);
}
else {
if (GB_is_cgb(gb)) {
gb->oam[gb->dma_current_dest++] = gb->dma_and_pattern;
gb->oam[gb->dma_current_dest++] = 0;
}
else {
gb->oam[gb->dma_current_dest++] = GB_read_memory(gb, gb->dma_current_src & ~0x2000) & gb->dma_and_pattern;
gb->oam[gb->dma_current_dest++] = GB_read_memory(gb, gb->dma_current_src & ~0x2000);
}
}
gb->dma_and_pattern = 0xFF;
/* dma_current_src must be the correct value during GB_read_memory */
gb->dma_current_src++;
if (!gb->dma_steps_left) {
gb->is_dma_restarting = false;
}
}
}

View File

@ -13,6 +13,7 @@ uint8_t GB_safe_read_memory(GB_gameboy_t *gb, uint16_t addr); // Without side ef
void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value);
#ifdef GB_INTERNAL
internal void GB_dma_run(GB_gameboy_t *gb);
internal bool GB_is_dma_active(GB_gameboy_t *gb);
internal void GB_hdma_run(GB_gameboy_t *gb);
internal void GB_trigger_oam_bug(GB_gameboy_t *gb, uint16_t address);
#endif

View File

@ -372,11 +372,6 @@ static void sanitize_state(GB_gameboy_t *gb)
if (!GB_is_cgb(gb)) {
gb->current_tile_attributes = 0;
}
if ((unsigned)gb->dma_current_dest + (unsigned)gb->dma_steps_left >= 0xa0) {
gb->dma_current_dest = 0;
gb->dma_steps_left = 0;
}
gb->object_low_line_address &= gb->vram_size & ~1;
if (gb->lcd_x > gb->position_in_line) {