Mercurial > repos > blastem
diff segacd.c @ 2072:cc13c100b027
Merge Sega CD branch now that it sort of works
author | Michael Pavone <pavone@retrodev.com> |
---|---|
date | Sun, 30 Jan 2022 22:29:29 -0800 |
parents | 8e51c0c3f2e3 |
children | c69e42444f96 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/segacd.c Sun Jan 30 22:29:29 2022 -0800 @@ -0,0 +1,1208 @@ +#include <stdlib.h> +#include <string.h> +#include "cd_graphics.h" +#include "genesis.h" +#include "util.h" + +#define SCD_MCLKS 50000000 +#define SCD_PERIPH_RESET_CLKS (SCD_MCLKS / 10) +#define TIMER_TICK_CLKS 1536 + +enum { + GA_SUB_CPU_CTRL, + GA_MEM_MODE, + GA_CDC_CTRL, + GA_CDC_REG_DATA, + GA_CDC_HOST_DATA, + GA_CDC_DMA_ADDR, + GA_STOP_WATCH, + GA_COMM_FLAG, + GA_COMM_CMD0, + GA_COMM_CMD1, + GA_COMM_CMD2, + GA_COMM_CMD3, + GA_COMM_CMD4, + GA_COMM_CMD5, + GA_COMM_CMD6, + GA_COMM_CMD7, + GA_COMM_STATUS0, + GA_COMM_STATUS1, + GA_COMM_STATUS2, + GA_COMM_STATUS3, + GA_COMM_STATUS4, + GA_COMM_STATUS5, + GA_COMM_STATUS6, + GA_COMM_STATUS7, + GA_TIMER, + GA_INT_MASK, + GA_CDD_FADER, + GA_CDD_CTRL, + GA_CDD_STATUS0, + GA_CDD_STATUS1, + GA_CDD_STATUS2, + GA_CDD_STATUS3, + GA_CDD_STATUS4, + GA_CDD_CMD0, + GA_CDD_CMD1, + GA_CDD_CMD2, + GA_CDD_CMD3, + GA_CDD_CMD4, + GA_FONT_COLOR, + GA_FONT_BITS, + GA_FONT_DATA0, + GA_FONT_DATA1, + GA_FONT_DATA2, + GA_FONT_DATA3, + + GA_HINT_VECTOR = GA_CDC_REG_DATA +}; +//GA_SUB_CPU_CTRL +#define BIT_IEN2 0x8000 +#define BIT_IFL2 0x0100 +#define BIT_LEDG 0x0200 +#define BIT_LEDR 0x0100 +#define BIT_SBRQ 0x0002 +#define BIT_SRES 0x0001 +#define BIT_PRES 0x0001 +//GA_MEM_MODE +#define MASK_PROG_BANK 0x00C0 +#define BIT_OVERWRITE 0x0010 +#define BIT_UNDERWRITE 0x0008 +#define MASK_PRIORITY (BIT_OVERWRITE|BIT_UNDERWRITE) +#define BIT_MEM_MODE 0x0004 +#define BIT_DMNA 0x0002 +#define BIT_RET 0x0001 + +//GA_CDC_CTRL +#define BIT_EDT 0x8000 +#define BIT_DSR 0x4000 + +enum { + DST_MAIN_CPU = 2, + DST_SUB_CPU, + DST_PCM_RAM, + DST_PROG_RAM, + DST_WORD_RAM = 7 +}; + +//GA_INT_MASK +#define BIT_MASK_IEN1 0x0002 +#define BIT_MASK_IEN2 0x0004 +#define BIT_MASK_IEN3 0x0008 +#define BIT_MASK_IEN4 0x0010 +#define BIT_MASK_IEN5 0x0020 +#define BIT_MASK_IEN6 0x0040 + +//GA_CDD_CTRL +#define BIT_HOCK 0x0004 + +static void *prog_ram_wp_write16(uint32_t address, void *vcontext, uint16_t value) +{ + m68k_context *m68k = vcontext; + segacd_context *cd = m68k->system; + //if (!(cd->gate_array[GA_MEM_MODE] & (1 << ((address >> 9) + 8)))) { + if (address >= ((cd->gate_array[GA_MEM_MODE] & 0xFF00) << 1)) { + cd->prog_ram[address >> 1] = value; + m68k_invalidate_code_range(m68k, address, address + 2); + } + return vcontext; +} + +static void *prog_ram_wp_write8(uint32_t address, void *vcontext, uint8_t value) +{ + m68k_context *m68k = vcontext; + segacd_context *cd = m68k->system; + if (address >= ((cd->gate_array[GA_MEM_MODE] & 0xFF00) << 1)) { + ((uint8_t *)cd->prog_ram)[address ^ 1] = value; + m68k_invalidate_code_range(m68k, address, address + 1); + } + return vcontext; +} + +static uint16_t word_ram_2M_read16(uint32_t address, void *vcontext) +{ + m68k_context *m68k = vcontext; + //TODO: fixme for interleaving + uint16_t* bank = m68k->mem_pointers[1]; + uint16_t raw = bank[address >> 2]; + if (address & 2) { + return (raw & 0xF) | (raw << 4 & 0xF00); + } else { + return (raw >> 4 & 0xF00) | (raw >> 8 & 0xF); + } +} + +static uint8_t word_ram_2M_read8(uint32_t address, void *vcontext) +{ + uint16_t word = word_ram_2M_read16(address, vcontext); + if (address & 1) { + return word; + } + return word >> 8; +} + +static void *word_ram_2M_write8(uint32_t address, void *vcontext, uint8_t value) +{ + m68k_context *m68k = vcontext; + segacd_context *cd = m68k->system; + value &= 0xF; + uint16_t priority = cd->gate_array[GA_MEM_MODE] & MASK_PRIORITY; + + if (priority == BIT_OVERWRITE && !value) { + return vcontext; + } + if (priority == BIT_UNDERWRITE) { + if (!value) { + return vcontext; + } + uint8_t old = word_ram_2M_read8(address, vcontext); + if (old) { + return vcontext; + } + } + uint16_t* bank = m68k->mem_pointers[1]; + uint16_t raw = bank[address >> 2]; + uint16_t shift = ((address & 3) * 4); + raw &= ~(0xF000 >> shift); + raw |= value << (12 - shift); + bank[address >> 2] = raw; + return vcontext; +} + + +static void *word_ram_2M_write16(uint32_t address, void *vcontext, uint16_t value) +{ + word_ram_2M_write8(address, vcontext, value >> 8); + return word_ram_2M_write8(address + 1, vcontext, value); +} + +static uint16_t word_ram_1M_read16(uint32_t address, void *vcontext) +{ + return 0; +} + +static uint8_t word_ram_1M_read8(uint32_t address, void *vcontext) +{ + return 0; +} + +static void *word_ram_1M_write16(uint32_t address, void *vcontext, uint16_t value) +{ + return vcontext; +} + +static void *word_ram_1M_write8(uint32_t address, void *vcontext, uint8_t value) +{ + return vcontext; +} + + +static uint16_t unmapped_prog_read16(uint32_t address, void *vcontext) +{ + return 0xFFFF; +} + +static uint8_t unmapped_prog_read8(uint32_t address, void *vcontext) +{ + return 0xFF; +} + +static void *unmapped_prog_write16(uint32_t address, void *vcontext, uint16_t value) +{ + return vcontext; +} + +static void *unmapped_prog_write8(uint32_t address, void *vcontext, uint8_t value) +{ + return vcontext; +} + +static uint16_t unmapped_word_read16(uint32_t address, void *vcontext) +{ + return 0xFFFF; +} + +static uint8_t unmapped_word_read8(uint32_t address, void *vcontext) +{ + return 0xFF; +} + +static void *unmapped_word_write16(uint32_t address, void *vcontext, uint16_t value) +{ + return vcontext; +} + +static void *unmapped_word_write8(uint32_t address, void *vcontext, uint8_t value) +{ + return vcontext; +} + +static uint16_t cell_image_read16(uint32_t address, void *vcontext) +{ + return 0xFFFF; +} + +static uint8_t cell_image_read8(uint32_t address, void *vcontext) +{ + return 0xFF; +} + +static void *cell_image_write16(uint32_t address, void *vcontext, uint16_t value) +{ + return vcontext; +} + +static void *cell_image_write8(uint32_t address, void *vcontext, uint8_t value) +{ + return vcontext; +} + +static uint8_t pcm_read8(uint32_t address, void *vcontext) +{ + return 0; +} + +static uint16_t pcm_read16(uint32_t address, void *vcontext) +{ + return 0xFF00 | pcm_read8(address+1, vcontext); +} + +static void *pcm_write8(uint32_t address, void *vcontext, uint8_t value) +{ + return vcontext; +} + +static void *pcm_write16(uint32_t address, void *vcontext, uint16_t value) +{ + return pcm_write8(address+1, vcontext, value); +} + + +static void timers_run(segacd_context *cd, uint32_t cycle) +{ + if (cycle <= cd->stopwatch_cycle) { + return; + } + uint32_t ticks = (cycle - cd->stopwatch_cycle) / TIMER_TICK_CLKS; + cd->stopwatch_cycle += ticks * TIMER_TICK_CLKS; + cd->gate_array[GA_STOP_WATCH] += ticks; + cd->gate_array[GA_STOP_WATCH] &= 0xFFF; + if (ticks && !cd->timer_value) { + --ticks; + cd->timer_value = cd->gate_array[GA_TIMER]; + } + if (ticks && cd->timer_value) { + while (ticks >= (cd->timer_value + 1)) { + ticks -= cd->timer_value + 1; + cd->timer_value = cd->gate_array[GA_TIMER]; + cd->timer_pending = 1; + } + cd->timer_value -= ticks; + if (!cd->timer_value) { + cd->timer_pending = 1; + } + } +} + +static void cdd_run(segacd_context *cd, uint32_t cycle) +{ + cdd_mcu_run(&cd->cdd, cycle, cd->gate_array + GA_CDD_CTRL, &cd->cdc); + lc8951_run(&cd->cdc, cycle); +} + +static uint32_t next_timer_int(segacd_context *cd) +{ + if (cd->timer_pending) { + return cd->stopwatch_cycle; + } + if (cd->timer_value) { + return cd->stopwatch_cycle + TIMER_TICK_CLKS * cd->timer_value; + } + if (cd->gate_array[GA_TIMER]) { + return cd->stopwatch_cycle + TIMER_TICK_CLKS * (cd->gate_array[GA_TIMER] + 1); + } + return CYCLE_NEVER; +} + +static void calculate_target_cycle(m68k_context * context) +{ + segacd_context *cd = context->system; + context->int_cycle = CYCLE_NEVER; + uint8_t mask = context->status & 0x7; + if (mask < 5) { + if (cd->gate_array[GA_INT_MASK] & BIT_MASK_IEN5) { + uint32_t cdc_cycle = lc8951_next_interrupt(&cd->cdc); + //CDC interrupts only generated on falling edge of !INT signal + if (cd->cdc_int_ack) { + if (cdc_cycle > cd->cdc.cycle) { + cd->cdc_int_ack = 0; + } else { + cdc_cycle = CYCLE_NEVER; + } + } + if (cdc_cycle < context->int_cycle) { + context->int_cycle = cdc_cycle; + context->int_num = 5; + } + } + if (mask < 4) { + if (cd->gate_array[GA_INT_MASK] & BIT_MASK_IEN4) { + uint32_t cdd_cycle = cd->cdd.int_pending ? context->current_cycle : cd->cdd.next_int_cycle; + if (cdd_cycle < context->int_cycle) { + context->int_cycle = cdd_cycle; + context->int_num = 4; + } + } + if (mask < 3) { + uint32_t next_timer; + if (cd->gate_array[GA_INT_MASK] & BIT_MASK_IEN3) { + uint32_t next_timer_cycle = next_timer_int(cd); + if (next_timer_cycle < context->int_cycle) { + context->int_cycle = next_timer_cycle; + context->int_num = 3; + } + } + if (mask < 2) { + if (cd->int2_cycle < context->int_cycle && (cd->gate_array[GA_INT_MASK] & BIT_MASK_IEN2)) { + context->int_cycle = cd->int2_cycle; + context->int_num = 2; + } + if (mask < 1) { + if (cd->graphics_int_cycle < context->int_cycle && (cd->gate_array[GA_INT_MASK] & BIT_MASK_IEN1)) { + context->int_cycle = cd->graphics_int_cycle; + context->int_num = 1; + } + } + } + } + } + } + if (context->int_cycle > context->current_cycle && context->int_pending == INT_PENDING_SR_CHANGE) { + context->int_pending = INT_PENDING_NONE; + } + if (context->current_cycle >= context->sync_cycle) { + context->should_return = 1; + context->target_cycle = context->current_cycle; + return; + } + if (context->status & M68K_STATUS_TRACE || context->trace_pending) { + context->target_cycle = context->current_cycle; + return; + } + context->target_cycle = context->sync_cycle < context->int_cycle ? context->sync_cycle : context->int_cycle; +} + +static uint16_t sub_gate_read16(uint32_t address, void *vcontext) +{ + m68k_context *m68k = vcontext; + segacd_context *cd = m68k->system; + uint32_t reg = address >> 1; + switch (reg) + { + case GA_SUB_CPU_CTRL: { + uint16_t value = cd->gate_array[reg] & 0xFFFE; + if (cd->periph_reset_cycle == CYCLE_NEVER || (m68k->current_cycle - cd->periph_reset_cycle) > SCD_PERIPH_RESET_CLKS) { + value |= BIT_PRES; + } + return value; + } + case GA_MEM_MODE: + return cd->gate_array[reg] & 0xFF1F; + case GA_CDC_CTRL: + cdd_run(cd, m68k->current_cycle); + return cd->gate_array[reg] | cd->cdc.ar; + case GA_CDC_REG_DATA: + cdd_run(cd, m68k->current_cycle); + return lc8951_reg_read(&cd->cdc); + case GA_CDC_HOST_DATA: { + cdd_run(cd, m68k->current_cycle); + uint16_t dst = cd->gate_array[GA_CDC_CTRL] >> 8 & 0x7; + if (dst == DST_SUB_CPU) { + if (cd->gate_array[GA_CDC_CTRL] & BIT_DSR) { + cd->gate_array[GA_CDC_CTRL] &= ~BIT_DSR; + lc8951_resume_transfer(&cd->cdc, cd->cdc.cycle); + } + calculate_target_cycle(cd->m68k); + + } + return cd->gate_array[reg]; + } + case GA_STOP_WATCH: + case GA_TIMER: + timers_run(cd, m68k->current_cycle); + return cd->gate_array[reg]; + case GA_CDD_STATUS0: + case GA_CDD_STATUS1: + case GA_CDD_STATUS2: + case GA_CDD_STATUS3: + case GA_CDD_STATUS4: + cdd_run(cd, m68k->current_cycle); + return cd->gate_array[reg]; + break; + case GA_FONT_DATA0: + case GA_FONT_DATA1: + case GA_FONT_DATA2: + case GA_FONT_DATA3: { + uint16_t shift = 4 * (3 - (reg - GA_FONT_DATA0)); + uint16_t value = 0; + uint16_t fg = cd->gate_array[GA_FONT_COLOR] >> 4; + uint16_t bg = cd->gate_array[GA_FONT_COLOR] & 0xF; + for (int i = 0; i < 4; i++) { + uint16_t pixel = 0; + if (cd->gate_array[GA_FONT_BITS] & 1 << (shift + i)) { + pixel = fg; + } else { + pixel = bg; + } + value |= pixel << (i * 4); + } + return value; + case GA_STAMP_SIZE: + case GA_IMAGE_BUFFER_LINES: + //these two have bits that change based on graphics operations + cd_graphics_run(cd, m68k->current_cycle); + return cd->gate_array[reg]; + case GA_TRACE_VECTOR_BASE: + //write only + return 0xFFFF; + } + default: + return cd->gate_array[reg]; + } +} + +static uint8_t sub_gate_read8(uint32_t address, void *vcontext) +{ + uint16_t val = sub_gate_read16(address, vcontext); + return address & 1 ? val : val >> 8; +} + +static void *sub_gate_write16(uint32_t address, void *vcontext, uint16_t value) +{ + m68k_context *m68k = vcontext; + segacd_context *cd = m68k->system; + uint32_t reg = address >> 1; + switch (reg) + { + case GA_SUB_CPU_CTRL: + cd->gate_array[reg] &= 0xF0; + cd->gate_array[reg] |= value & (BIT_LEDG|BIT_LEDR); + if (value & BIT_PRES) { + cd->periph_reset_cycle = m68k->current_cycle; + } + break; + case GA_MEM_MODE: { + uint16_t changed = value ^ cd->gate_array[reg]; + genesis_context *gen = cd->genesis; + if (changed & BIT_MEM_MODE) { + //FIXME: ram banks are supposed to be interleaved when in 2M mode + cd->gate_array[reg] &= ~BIT_DMNA; + if (value & BIT_MEM_MODE) { + //switch to 1M mode + gen->m68k->mem_pointers[cd->memptr_start_index + 1] = (value & BIT_RET) ? cd->word_ram + 0x10000 : cd->word_ram; + gen->m68k->mem_pointers[cd->memptr_start_index + 2] = NULL; + m68k->mem_pointers[0] = NULL; + m68k->mem_pointers[1] = (value & BIT_RET) ? cd->word_ram : cd->word_ram + 0x10000; + } else { + //switch to 2M mode + if (value & BIT_RET) { + //Main CPU will have word ram + gen->m68k->mem_pointers[cd->memptr_start_index + 1] = cd->word_ram; + gen->m68k->mem_pointers[cd->memptr_start_index + 2] = cd->word_ram + 0x10000; + m68k->mem_pointers[0] = NULL; + m68k->mem_pointers[1] = NULL; + } else { + //sub cpu will have word ram + gen->m68k->mem_pointers[cd->memptr_start_index + 1] = NULL; + gen->m68k->mem_pointers[cd->memptr_start_index + 2] = NULL; + m68k->mem_pointers[0] = cd->word_ram; + m68k->mem_pointers[1] = NULL; + } + } + m68k_invalidate_code_range(gen->m68k, cd->base + 0x200000, cd->base + 0x240000); + m68k_invalidate_code_range(m68k, 0x080000, 0x0E0000); + } else if (changed & BIT_RET) { + if (value & BIT_MEM_MODE) { + cd->gate_array[reg] &= ~BIT_DMNA; + //swapping banks in 1M mode + gen->m68k->mem_pointers[cd->memptr_start_index + 1] = (value & BIT_RET) ? cd->word_ram + 0x10000 : cd->word_ram; + m68k->mem_pointers[1] = (value & BIT_RET) ? cd->word_ram : cd->word_ram + 0x10000; + m68k_invalidate_code_range(gen->m68k, cd->base + 0x200000, cd->base + 0x240000); + m68k_invalidate_code_range(m68k, 0x080000, 0x0E0000); + } else if (value & BIT_RET) { + cd->gate_array[reg] &= ~BIT_DMNA; + //giving word ram to main CPU in 2M mode + gen->m68k->mem_pointers[cd->memptr_start_index + 1] = cd->word_ram; + gen->m68k->mem_pointers[cd->memptr_start_index + 2] = cd->word_ram + 0x10000; + m68k->mem_pointers[0] = NULL; + m68k_invalidate_code_range(gen->m68k, cd->base + 0x200000, cd->base + 0x240000); + m68k_invalidate_code_range(m68k, 0x080000, 0x0E0000); + } else { + value |= BIT_RET; + } + } + cd->gate_array[reg] &= 0xFFC2; + cd->gate_array[reg] |= value & (BIT_RET|BIT_MEM_MODE|MASK_PRIORITY); + break; + } + case GA_CDC_CTRL: + cdd_run(cd, m68k->current_cycle); + lc8951_ar_write(&cd->cdc, value); + //cd->gate_array[reg] &= 0xC000; + //apparently this clears EDT, should it also clear DSR? + cd->gate_array[reg] = value & 0x0700; + cd->cdc_dst_low = 0; + break; + case GA_CDC_REG_DATA: + cdd_run(cd, m68k->current_cycle); + printf("CDC write %X: %X @ %u\n", cd->cdc.ar, value, m68k->current_cycle); + lc8951_reg_write(&cd->cdc, value); + calculate_target_cycle(m68k); + break; + case GA_CDC_DMA_ADDR: + cdd_run(cd, m68k->current_cycle); + cd->gate_array[reg] = value; + cd->cdc_dst_low = 0; + break; + case GA_STOP_WATCH: + //docs say you should only write zero to reset + //mcd-verificator comments suggest any value will reset + timers_run(cd, m68k->current_cycle); + cd->gate_array[reg] = 0; + break; + case GA_COMM_FLAG: + cd->gate_array[reg] &= 0xFF00; + cd->gate_array[reg] |= value & 0xFF; + break; + case GA_COMM_STATUS0: + case GA_COMM_STATUS1: + case GA_COMM_STATUS2: + case GA_COMM_STATUS3: + case GA_COMM_STATUS4: + case GA_COMM_STATUS5: + case GA_COMM_STATUS6: + case GA_COMM_STATUS7: + //no effects for these other than saving the value + cd->gate_array[reg] = value; + break; + case GA_TIMER: + timers_run(cd, m68k->current_cycle); + cd->gate_array[reg] = value & 0xFF; + calculate_target_cycle(m68k); + break; + case GA_INT_MASK: + cd->gate_array[reg] = value & (BIT_MASK_IEN6|BIT_MASK_IEN5|BIT_MASK_IEN4|BIT_MASK_IEN3|BIT_MASK_IEN2|BIT_MASK_IEN1); + calculate_target_cycle(m68k); + break; + case GA_CDD_CTRL: { + cdd_run(cd, m68k->current_cycle); + uint16_t changed = cd->gate_array[reg] ^ value; + cd->gate_array[reg] &= ~BIT_HOCK; + cd->gate_array[reg] |= value & BIT_HOCK; + if (changed & BIT_HOCK) { + if (value & BIT_HOCK) { + cdd_hock_enabled(&cd->cdd); + } else { + cdd_hock_disabled(&cd->cdd); + } + calculate_target_cycle(m68k); + } + break; + } + case GA_CDD_CMD0: + case GA_CDD_CMD1: + case GA_CDD_CMD2: + case GA_CDD_CMD3: + cdd_run(cd, m68k->current_cycle); + cd->gate_array[reg] = value & 0x0F0F; + break; + case GA_CDD_CMD4: + cdd_run(cd, m68k->current_cycle); + cd->gate_array[reg] = value & 0x0F0F; + cdd_mcu_start_cmd_recv(&cd->cdd, cd->gate_array + GA_CDD_CTRL); + break; + case GA_FONT_COLOR: + cd->gate_array[reg] = value & 0xFF; + break; + case GA_FONT_BITS: + cd->gate_array[reg] = value; + break; + case GA_STAMP_SIZE: + cd_graphics_run(cd, m68k->current_cycle); + cd->gate_array[reg] &= BIT_GRON; + cd->gate_array[reg] |= value & (BIT_SMS|BIT_STS|BIT_RPT); + break; + case GA_STAMP_MAP_BASE: + cd_graphics_run(cd, m68k->current_cycle); + cd->gate_array[reg] = value & 0xFFE0; + break; + case GA_IMAGE_BUFFER_VCELLS: + cd_graphics_run(cd, m68k->current_cycle); + cd->gate_array[reg] = value & 0x1F; + break; + case GA_IMAGE_BUFFER_START: + cd_graphics_run(cd, m68k->current_cycle); + cd->gate_array[reg] = value & 0xFFF8; + break; + case GA_IMAGE_BUFFER_OFFSET: + cd_graphics_run(cd, m68k->current_cycle); + cd->gate_array[reg] = value & 0x3F; + break; + case GA_IMAGE_BUFFER_HDOTS: + cd_graphics_run(cd, m68k->current_cycle); + cd->gate_array[reg] = value & 0x1FF; + break; + case GA_IMAGE_BUFFER_LINES: + cd_graphics_run(cd, m68k->current_cycle); + cd->gate_array[reg] = value & 0xFF; + break; + case GA_TRACE_VECTOR_BASE: + cd_graphics_run(cd, m68k->current_cycle); + cd->gate_array[reg] = value & 0xFFFE; + cd_graphics_start(cd); + break; + default: + printf("Unhandled gate array write %X:%X\n", address, value); + } + return vcontext; +} + +static void *sub_gate_write8(uint32_t address, void *vcontext, uint8_t value) +{ + m68k_context *m68k = vcontext; + segacd_context *cd = m68k->system; + uint32_t reg = (address & 0x1FF) >> 1; + uint16_t value16; + switch (address >> 1) + { + case GA_CDC_HOST_DATA: + case GA_CDC_DMA_ADDR: + case GA_STOP_WATCH: + case GA_COMM_FLAG: + case GA_TIMER: + case GA_CDD_FADER: + case GA_FONT_COLOR: + //these registers treat all writes as word-wide + value16 = value | (value << 8); + break; + case GA_CDC_CTRL: + if (address & 1) { + lc8951_ar_write(&cd->cdc, value); + return vcontext; + } else { + value16 = cd->cdc.ar | (value << 8); + } + break; + case GA_CDD_CMD4: + if (!address) { + //byte write to $FF804A should not trigger transfer + cdd_run(cd, m68k->current_cycle); + cd->gate_array[reg] &= 0x0F; + cd->gate_array[reg] |= (value << 8 & 0x0F00); + return vcontext; + } + //intentional fallthrough for $FF804B + default: + if (address & 1) { + value16 = cd->gate_array[reg] & 0xFF00 | value; + } else { + value16 = cd->gate_array[reg] & 0xFF | (value << 8); + } + } + return sub_gate_write16(address, vcontext, value16); +} + +static uint8_t handle_cdc_byte(void *vsys, uint8_t value) +{ + segacd_context *cd = vsys; + if (cd->gate_array[GA_CDC_CTRL] & BIT_DSR) { + //host reg is already full, pause transfer + return 0; + } + if (cd->cdc.cycle == cd->cdc.transfer_end) { + cd->gate_array[GA_CDC_CTRL] |= BIT_EDT; + printf("EDT set at %u\n", cd->cdc.cycle); + } + uint16_t dest = cd->gate_array[GA_CDC_CTRL] >> 8 & 0x7; + if (!(cd->cdc_dst_low & 1)) { + cd->gate_array[GA_CDC_HOST_DATA] &= 0xFF; + cd->gate_array[GA_CDC_HOST_DATA] |= value << 8; + cd->cdc_dst_low++; + if (dest != DST_PCM_RAM) { + //PCM RAM writes a byte at a time + return 1; + } + } else { + cd->gate_array[GA_CDC_HOST_DATA] &= 0xFF00; + cd->gate_array[GA_CDC_HOST_DATA] |= value; + } + + uint32_t dma_addr = cd->gate_array[GA_CDC_DMA_ADDR] << 3; + dma_addr |= cd->cdc_dst_low; + switch (dest) + { + case DST_MAIN_CPU: + case DST_SUB_CPU: + cd->cdc_dst_low = 0; + cd->gate_array[GA_CDC_CTRL] |= BIT_DSR; + printf("DSR set at %u, (transfer_end %u, dbcl %X, dbch %X)\n", cd->cdc.cycle, cd->cdc.transfer_end, cd->cdc.regs[2], cd->cdc.regs[3]); + break; + case DST_PCM_RAM: + dma_addr &= (1 << 13) - 1; + //TODO: write to currently visible 8K bank of PCM RAM I guess? + dma_addr += 2; + cd->cdc_dst_low = dma_addr & 7; + cd->gate_array[GA_CDC_DMA_ADDR] = dma_addr >> 3; + break; + case DST_PROG_RAM: + cd->prog_ram[dma_addr >> 1] = cd->gate_array[GA_CDC_HOST_DATA]; + m68k_invalidate_code_range(cd->m68k, dma_addr - 1, dma_addr + 1); + dma_addr++; + cd->cdc_dst_low = dma_addr & 7; + cd->gate_array[GA_CDC_DMA_ADDR] = dma_addr >> 3; + break; + case DST_WORD_RAM: + if (cd->gate_array[GA_MEM_MODE] & BIT_MEM_MODE) { + //1M mode, write to bank assigned to Sub CPU + dma_addr &= (1 << 17) - 1; + cd->m68k->mem_pointers[1][dma_addr >> 1] = cd->gate_array[GA_CDC_HOST_DATA]; + m68k_invalidate_code_range(cd->m68k, 0x0C0000 + dma_addr - 1, 0x0C0000 + dma_addr + 1); + } else { + //2M mode, check if Sub CPU has access + if (!(cd->gate_array[GA_MEM_MODE] & BIT_RET)) { + dma_addr &= (1 << 18) - 1; + cd->word_ram[dma_addr >> 1] = cd->gate_array[GA_CDC_HOST_DATA]; + m68k_invalidate_code_range(cd->m68k, 0x080000 + dma_addr, 0x080000 + dma_addr + 1); + } + } + dma_addr++; + cd->cdc_dst_low = dma_addr & 7; + cd->gate_array[GA_CDC_DMA_ADDR] = dma_addr >> 3; + break; + default: + printf("Invalid CDC transfer destination %d\n", dest); + } + return 1; +} + +static uint8_t can_main_access_prog(segacd_context *cd) +{ + //TODO: use actual busack + return cd->busreq || !cd->reset; +} + +static void scd_peripherals_run(segacd_context *cd, uint32_t cycle) +{ + timers_run(cd, cycle); + cdd_run(cd, cycle); + cd_graphics_run(cd, cycle); +} + +static m68k_context *sync_components(m68k_context * context, uint32_t address) +{ + segacd_context *cd = context->system; + scd_peripherals_run(cd, context->current_cycle); + switch (context->int_ack) + { + case 1: + cd->graphics_int_cycle = CYCLE_NEVER; + break; + case 2: + cd->int2_cycle = CYCLE_NEVER; + break; + case 3: + cd->timer_pending = 0; + break; + case 4: + cd->cdd.int_pending = 0; + break; + case 5: + cd->cdc_int_ack = 1; + break; + } + context->int_ack = 0; + calculate_target_cycle(context); + return context; +} + +void scd_run(segacd_context *cd, uint32_t cycle) +{ + uint8_t m68k_run = !can_main_access_prog(cd); + if (cycle > cd->m68k->current_cycle) { + if (m68k_run) { + uint32_t start = cd->m68k->current_cycle; + cd->m68k->sync_cycle = cycle; + if (cd->need_reset) { + cd->need_reset = 0; + m68k_reset(cd->m68k); + } else { + calculate_target_cycle(cd->m68k); + resume_68k(cd->m68k); + } + } else { + cd->m68k->current_cycle = cycle; + } + } + scd_peripherals_run(cd, cycle); +} + +uint32_t gen_cycle_to_scd(uint32_t cycle, genesis_context *gen) +{ + return ((uint64_t)cycle) * ((uint64_t)SCD_MCLKS) / ((uint64_t)gen->normal_clock); +} + +void scd_adjust_cycle(segacd_context *cd, uint32_t deduction) +{ + deduction = gen_cycle_to_scd(deduction, cd->genesis); + cd->m68k->current_cycle -= deduction; + cd->stopwatch_cycle -= deduction; + if (deduction >= cd->int2_cycle) { + cd->int2_cycle = 0; + } else if (cd->int2_cycle != CYCLE_NEVER) { + cd->int2_cycle -= deduction; + } + if (deduction >= cd->periph_reset_cycle) { + cd->periph_reset_cycle = CYCLE_NEVER; + } else if (cd->periph_reset_cycle != CYCLE_NEVER) { + cd->periph_reset_cycle -= deduction; + } + cdd_mcu_adjust_cycle(&cd->cdd, deduction); + lc8951_adjust_cycles(&cd->cdc, deduction); + cd->graphics_cycle -= deduction; + if (cd->graphics_int_cycle != CYCLE_NEVER) { + if (cd->graphics_int_cycle > deduction) { + cd->graphics_int_cycle -= deduction; + } else { + cd->graphics_int_cycle = 0; + } + } +} + +static uint16_t main_gate_read16(uint32_t address, void *vcontext) +{ + m68k_context *m68k = vcontext; + genesis_context *gen = m68k->system; + segacd_context *cd = gen->expansion; + uint32_t scd_cycle = gen_cycle_to_scd(m68k->current_cycle, gen); + scd_run(cd, scd_cycle); + uint32_t offset = (address & 0x1FF) >> 1; + switch (offset) + { + case GA_SUB_CPU_CTRL: { + uint16_t value = 0; + if (cd->gate_array[GA_INT_MASK] & BIT_MASK_IEN2) { + value |= BIT_IEN2; + } + if (cd->int2_cycle != CYCLE_NEVER) { + value |= BIT_IFL2; + } + if (can_main_access_prog(cd)) { + value |= BIT_SBRQ; + } + if (cd->reset) { + value |= BIT_SRES; + } + return value; + } + case GA_MEM_MODE: + //Main CPU can't read priority mode bits + return cd->gate_array[offset] & 0xFFE7; + case GA_HINT_VECTOR: + return cd->rom_mut[0x72/2]; + case GA_CDC_HOST_DATA: { + uint16_t dst = cd->gate_array[GA_CDC_CTRL] >> 8 & 0x7; + if (dst == DST_MAIN_CPU) { + if (cd->gate_array[GA_CDC_CTRL] & BIT_DSR) { + printf("DSR cleared at %u (%u)\n", scd_cycle, cd->cdc.cycle); + cd->gate_array[GA_CDC_CTRL] &= ~BIT_DSR; + lc8951_resume_transfer(&cd->cdc, scd_cycle); + } else { + printf("Read of CDC host data with DSR clear at %u\n", scd_cycle); + } + calculate_target_cycle(cd->m68k); + } + return cd->gate_array[offset]; + } + case GA_CDC_DMA_ADDR: + //TODO: open bus maybe? + return 0xFFFF; + default: + if (offset < GA_TIMER) { + if (offset == GA_CDC_CTRL) { + printf("CDC read(main): %X - %X @ %u (%u)\n", address, cd->gate_array[offset], m68k->current_cycle, scd_cycle); + } else if (offset >= GA_COMM_FLAG && offset <= GA_COMM_STATUS7) { + printf("COMM read(main): %X - %X @ %u (%u)\n", address, cd->gate_array[offset], m68k->current_cycle, scd_cycle); + } + return cd->gate_array[offset]; + } + //TODO: open bus maybe? + return 0xFFFF; + } +} + +static uint8_t main_gate_read8(uint32_t address, void *vcontext) +{ + uint16_t val = main_gate_read16(address & 0xFE, vcontext); + return address & 1 ? val : val >> 8; +} + +static void dump_prog_ram(segacd_context *cd) +{ + static int dump_count; + char fname[256]; + sprintf(fname, "prog_ram_%d.bin", dump_count++); + FILE *f = fopen(fname, "wb"); + if (f) { + uint32_t last = 256*1024-1; + for(; last > 0; --last) + { + if (cd->prog_ram[last]) { + break; + } + } + for (uint32_t i = 0; i <= last; i++) + { + uint8_t pair[2]; + pair[0] = cd->prog_ram[i] >> 8; + pair[1] = cd->prog_ram[i]; + fwrite(pair, 1, sizeof(pair), f); + } + + fclose(f); + } +} + +static void *main_gate_write16(uint32_t address, void *vcontext, uint16_t value) +{ + m68k_context *m68k = vcontext; + genesis_context *gen = m68k->system; + segacd_context *cd = gen->expansion; + uint32_t scd_cycle = gen_cycle_to_scd(m68k->current_cycle, gen); + scd_run(cd, scd_cycle); + uint32_t reg = (address & 0x1FF) >> 1; + switch (reg) + { + case GA_SUB_CPU_CTRL: { + uint8_t old_access = can_main_access_prog(cd); + cd->busreq = value & BIT_SBRQ; + uint8_t old_reset = cd->reset; + cd->reset = value & BIT_SRES; + if (cd->reset && !old_reset) { + cd->need_reset = 1; + } + if (value & BIT_IFL2) { + cd->int2_cycle = scd_cycle; + } + /*cd->gate_array[reg] &= 0x7FFF; + cd->gate_array[reg] |= value & 0x8000;*/ + uint8_t new_access = can_main_access_prog(cd); + uint32_t bank = cd->gate_array[GA_MEM_MODE] >> 6 & 0x3; + if (new_access) { + if (!old_access) { + m68k->mem_pointers[cd->memptr_start_index] = cd->prog_ram + bank * 0x10000; + m68k_invalidate_code_range(m68k, cd->base + 0x220000, cd->base + 0x240000); + } + } else if (old_access) { + m68k->mem_pointers[cd->memptr_start_index] = NULL; + m68k_invalidate_code_range(m68k, cd->base + 0x220000, cd->base + 0x240000); + m68k_invalidate_code_range(cd->m68k, bank * 0x20000, (bank + 1) * 0x20000); + if (!new_access) { + dump_prog_ram(cd); + } + } + break; + } + case GA_MEM_MODE: { + uint16_t changed = cd->gate_array[reg] ^ value; + //Main CPU can't write priority mode bits, MODE or RET + cd->gate_array[reg] &= 0x001F; + cd->gate_array[reg] |= value & 0xFFC0; + if ((cd->gate_array[reg] & BIT_MEM_MODE)) { + //1M mode + if (!(value & BIT_DMNA)) { + cd->gate_array[reg] |= BIT_DMNA; + } + } else { + //2M mode + if (changed & value & BIT_DMNA) { + cd->gate_array[reg] |= BIT_DMNA; + m68k->mem_pointers[cd->memptr_start_index + 1] = NULL; + m68k->mem_pointers[cd->memptr_start_index + 2] = NULL; + cd->m68k->mem_pointers[0] = cd->word_ram; + cd->gate_array[reg] &= ~BIT_RET; + + m68k_invalidate_code_range(m68k, cd->base + 0x200000, cd->base + 0x240000); + m68k_invalidate_code_range(cd->m68k, 0x080000, 0x0C0000); + } + } + if (changed & MASK_PROG_BANK && can_main_access_prog(cd)) { + uint32_t bank = cd->gate_array[GA_MEM_MODE] >> 6 & 0x3; + m68k->mem_pointers[cd->memptr_start_index] = cd->prog_ram + bank * 0x10000; + m68k_invalidate_code_range(m68k, cd->base + 0x220000, cd->base + 0x240000); + } + break; + } + case GA_HINT_VECTOR: + cd->rom_mut[0x72/2] = value; + break; + case GA_COMM_FLAG: + //Main CPU can only write the upper byte; + cd->gate_array[reg] &= 0xFF; + cd->gate_array[reg] |= value & 0xFF00; + printf("COMM write(main): %X - %X @ %u (%u)\n", address, value, m68k->current_cycle, scd_cycle); + break; + case GA_COMM_CMD0: + case GA_COMM_CMD1: + case GA_COMM_CMD2: + case GA_COMM_CMD3: + case GA_COMM_CMD4: + case GA_COMM_CMD5: + case GA_COMM_CMD6: + case GA_COMM_CMD7: + //no effects for these other than saving the value + printf("COMM write(main): %X - %X @ %u (%u)\n", address, value, m68k->current_cycle, scd_cycle); + cd->gate_array[reg] = value; + break; + default: + printf("Unhandled gate array write %X:%X\n", address, value); + } + return vcontext; +} + +static void *main_gate_write8(uint32_t address, void *vcontext, uint8_t value) +{ + m68k_context *m68k = vcontext; + genesis_context *gen = m68k->system; + segacd_context *cd = gen->expansion; + uint32_t reg = (address & 0x1FF) >> 1; + uint16_t value16; + switch (reg >> 1) + { + case GA_SUB_CPU_CTRL: + if (address & 1) { + value16 = value; + } else { + value16 = value << 8; + if (cd->reset) { + value16 |= BIT_SRES; + } + if (cd->busreq) { + value16 |= BIT_SBRQ; + } + } + break; + case GA_HINT_VECTOR: + case GA_COMM_FLAG: + //writes to these regs are always treated as word wide + value16 = value | (value << 8); + break; + default: + if (address & 1) { + value16 = cd->gate_array[reg] & 0xFF00 | value; + } else { + value16 = cd->gate_array[reg] & 0xFF | (value << 8); + } + } + return main_gate_write16(address, vcontext, value16); +} + +segacd_context *alloc_configure_segacd(system_media *media, uint32_t opts, uint8_t force_region, rom_info *info) +{ + static memmap_chunk sub_cpu_map[] = { + {0x000000, 0x01FEFF, 0xFFFFFF, .flags=MMAP_READ | MMAP_CODE, .write_16 = prog_ram_wp_write16, .write_8 = prog_ram_wp_write8}, + {0x01FF00, 0x07FFFF, 0xFFFFFF, .flags=MMAP_READ | MMAP_WRITE | MMAP_CODE}, + {0x080000, 0x0BFFFF, 0x03FFFF, .flags=MMAP_READ | MMAP_WRITE | MMAP_CODE | MMAP_PTR_IDX | MMAP_FUNC_NULL, .ptr_index = 0, + .read_16 = word_ram_2M_read16, .write_16 = word_ram_2M_write16, .read_8 = word_ram_2M_read8, .write_8 = word_ram_2M_write8}, + {0x0C0000, 0x0DFFFF, 0x01FFFF, .flags=MMAP_READ | MMAP_WRITE | MMAP_CODE | MMAP_PTR_IDX | MMAP_FUNC_NULL, .ptr_index = 1, + .read_16 = word_ram_1M_read16, .write_16 = word_ram_1M_write16, .read_8 = word_ram_1M_read8, .write_8 = word_ram_1M_write8}, + {0xFE0000, 0xFEFFFF, 0x003FFF, .flags=MMAP_READ | MMAP_WRITE | MMAP_ONLY_ODD}, + {0xFF0000, 0xFF7FFF, 0x003FFF, .read_16 = pcm_read16, .write_16 = pcm_write16, .read_8 = pcm_read8, .write_8 = pcm_write8}, + {0xFF8000, 0xFF81FF, 0x0001FF, .read_16 = sub_gate_read16, .write_16 = sub_gate_write16, .read_8 = sub_gate_read8, .write_8 = sub_gate_write8} + }; + + segacd_context *cd = calloc(sizeof(segacd_context), 1); + FILE *f = fopen("cdbios.bin", "rb"); + if (!f) { + fatal_error("Failed to open CD firmware for reading"); + } + long firmware_size = file_size(f); + uint32_t adjusted_size = nearest_pow2(firmware_size); + cd->rom = malloc(adjusted_size); + if (firmware_size != fread(cd->rom, 1, firmware_size, f)) { + fatal_error("Failed to read CD firmware"); + } + cd->rom_mut = malloc(adjusted_size); + byteswap_rom(adjusted_size, cd->rom); + memcpy(cd->rom_mut, cd->rom, adjusted_size); + cd->rom_mut[0x72/2] = 0xFFFF; + + //memset(info, 0, sizeof(*info)); + //tern_node *db = get_rom_db(); + //*info = configure_rom(db, media->buffer, media->size, media->chain ? media->chain->buffer : NULL, media->chain ? media->chain->size : 0, NULL, 0); + + cd->prog_ram = malloc(512*1024); + cd->word_ram = malloc(256*1024); + cd->pcm_ram = malloc(64*1024); + //TODO: Load state from file + cd->bram = malloc(8*1024); + + + sub_cpu_map[0].buffer = sub_cpu_map[1].buffer = cd->prog_ram; + sub_cpu_map[4].buffer = cd->bram; + m68k_options *mopts = malloc(sizeof(m68k_options)); + init_m68k_opts(mopts, sub_cpu_map, sizeof(sub_cpu_map) / sizeof(*sub_cpu_map), 4, sync_components); + cd->m68k = init_68k_context(mopts, NULL); + cd->m68k->system = cd; + cd->int2_cycle = CYCLE_NEVER; + cd->busreq = 1; + cd->busack = 1; + cd->need_reset = 1; + cd->reset = 1; //active low, so reset is not active on start + cd->memptr_start_index = 0; + cd->gate_array[1] = 1; + cd->gate_array[0x1B] = 0x100; + lc8951_init(&cd->cdc, handle_cdc_byte, cd); + if (media->chain && media->type != MEDIA_CDROM) { + media = media->chain; + } + cdd_mcu_init(&cd->cdd, media); + cd_graphics_init(cd); + + return cd; +} + +memmap_chunk *segacd_main_cpu_map(segacd_context *cd, uint8_t cart_boot, uint32_t *num_chunks) +{ + static memmap_chunk main_cpu_map[] = { + {0x000000, 0x01FFFF, 0x01FFFF, .flags=MMAP_READ}, + {0x020000, 0x03FFFF, 0x01FFFF, .flags=MMAP_READ|MMAP_WRITE|MMAP_PTR_IDX|MMAP_FUNC_NULL|MMAP_CODE, .ptr_index = 0, + .read_16 = unmapped_prog_read16, .write_16 = unmapped_prog_write16, .read_8 = unmapped_prog_read8, .write_8 = unmapped_prog_write8}, + {0x040000, 0x05FFFF, 0x01FFFF, .flags=MMAP_READ}, //first ROM alias + //TODO: additional ROM/prog RAM aliases + {0x200000, 0x21FFFF, 0x01FFFF, .flags=MMAP_READ|MMAP_WRITE|MMAP_PTR_IDX|MMAP_FUNC_NULL|MMAP_CODE, .ptr_index = 1, + .read_16 = unmapped_word_read16, .write_16 = unmapped_word_write16, .read_8 = unmapped_word_read8, .write_8 = unmapped_word_write8}, + {0x220000, 0x23FFFF, 0x01FFFF, .flags=MMAP_READ|MMAP_WRITE|MMAP_PTR_IDX|MMAP_FUNC_NULL|MMAP_CODE, .ptr_index = 2, + .read_16 = cell_image_read16, .write_16 = cell_image_write16, .read_8 = cell_image_read8, .write_8 = cell_image_write8}, + {0xA12000, 0xA12FFF, 0xFFFFFF, .read_16 = main_gate_read16, .write_16 = main_gate_write16, .read_8 = main_gate_read8, .write_8 = main_gate_write8} + }; + for (int i = 0; i < sizeof(main_cpu_map) / sizeof(*main_cpu_map); i++) + { + if (main_cpu_map[i].start < 0x800000) { + if (cart_boot) { + main_cpu_map[i].start |= 0x400000; + main_cpu_map[i].end |= 0x400000; + } else { + main_cpu_map[i].start &= 0x3FFFFF; + main_cpu_map[i].end &= 0x3FFFFF; + } + } + } + //TODO: support BRAM cart + main_cpu_map[0].buffer = cd->rom_mut; + main_cpu_map[2].buffer = cd->rom; + main_cpu_map[1].buffer = cd->prog_ram; + main_cpu_map[3].buffer = cd->word_ram; + main_cpu_map[4].buffer = cd->word_ram + 0x10000; + *num_chunks = sizeof(main_cpu_map) / sizeof(*main_cpu_map); + return main_cpu_map; +}