mirror of https://github.com/xemu-project/xemu.git
243 lines
6.0 KiB
C
243 lines
6.0 KiB
C
/*
|
|
* MCPX DSP emulator
|
|
*
|
|
* Copyright (c) 2015 espes
|
|
* Copyright (c) 2020-2025 Matt Borgerson
|
|
*
|
|
* Adapted from Hatari DSP M56001 emulation
|
|
* (C) 2001-2008 ARAnyM developer team
|
|
* Adaption to Hatari (C) 2008 by Thomas Huth
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "dsp_cpu.h"
|
|
#include "dsp_dma.h"
|
|
#include "dsp_state.h"
|
|
#include "dsp.h"
|
|
#include "debug.h"
|
|
#include "trace.h"
|
|
|
|
/* Defines */
|
|
#define BITMASK(x) ((1<<(x))-1)
|
|
|
|
#define INTERRUPT_ABORT_FRAME (1 << 0)
|
|
#define INTERRUPT_START_FRAME (1 << 1)
|
|
#define INTERRUPT_DMA_EOL (1 << 7)
|
|
|
|
static uint32_t read_peripheral(dsp_core_t* core, uint32_t address);
|
|
static void write_peripheral(dsp_core_t* core, uint32_t address, uint32_t value);
|
|
|
|
DSPState *dsp_init(void *rw_opaque,
|
|
dsp_scratch_rw_func scratch_rw,
|
|
dsp_fifo_rw_func fifo_rw)
|
|
{
|
|
DPRINTF("dsp_init\n");
|
|
|
|
DSPState* dsp = (DSPState*)malloc(sizeof(DSPState));
|
|
memset(dsp, 0, sizeof(*dsp));
|
|
|
|
dsp->core.read_peripheral = read_peripheral;
|
|
dsp->core.write_peripheral = write_peripheral;
|
|
|
|
dsp->dma.core = &dsp->core;
|
|
dsp->dma.rw_opaque = rw_opaque;
|
|
dsp->dma.scratch_rw = scratch_rw;
|
|
dsp->dma.fifo_rw = fifo_rw;
|
|
|
|
dsp_reset(dsp);
|
|
|
|
return dsp;
|
|
}
|
|
|
|
void dsp_reset(DSPState* dsp)
|
|
{
|
|
dsp56k_reset_cpu(&dsp->core);
|
|
dsp->save_cycles = 0;
|
|
}
|
|
|
|
void dsp_destroy(DSPState* dsp)
|
|
{
|
|
free(dsp);
|
|
}
|
|
|
|
static uint32_t read_peripheral(dsp_core_t* core, uint32_t address) {
|
|
DSPState* dsp = container_of(core, DSPState, core);
|
|
|
|
uint32_t v = 0xababa;
|
|
switch(address) {
|
|
case 0xFFFFB3:
|
|
v = 0; // core->num_inst; // ??
|
|
break;
|
|
case 0xFFFFC5:
|
|
v = dsp->interrupts;
|
|
if (dsp->dma.eol) {
|
|
v |= INTERRUPT_DMA_EOL;
|
|
}
|
|
break;
|
|
case 0xFFFFD4:
|
|
v = dsp_dma_read(&dsp->dma, DMA_NEXT_BLOCK);
|
|
break;
|
|
case 0xFFFFD5:
|
|
v = dsp_dma_read(&dsp->dma, DMA_START_BLOCK);
|
|
break;
|
|
case 0xFFFFD6:
|
|
v = dsp_dma_read(&dsp->dma, DMA_CONTROL);
|
|
break;
|
|
case 0xFFFFD7:
|
|
v = dsp_dma_read(&dsp->dma, DMA_CONFIGURATION);
|
|
break;
|
|
}
|
|
|
|
trace_dsp_read_peripheral(address, v);
|
|
return v;
|
|
}
|
|
|
|
static void write_peripheral(dsp_core_t* core, uint32_t address, uint32_t value) {
|
|
DSPState* dsp = container_of(core, DSPState, core);
|
|
|
|
switch(address) {
|
|
case 0xFFFFC4:
|
|
if (value & 1) {
|
|
core->is_idle = true;
|
|
}
|
|
break;
|
|
case 0xFFFFC5:
|
|
dsp->interrupts &= ~value;
|
|
if (value & INTERRUPT_DMA_EOL) {
|
|
dsp->dma.eol = false;
|
|
}
|
|
break;
|
|
case 0xFFFFD4:
|
|
dsp_dma_write(&dsp->dma, DMA_NEXT_BLOCK, value);
|
|
break;
|
|
case 0xFFFFD5:
|
|
dsp_dma_write(&dsp->dma, DMA_START_BLOCK, value);
|
|
break;
|
|
case 0xFFFFD6:
|
|
dsp_dma_write(&dsp->dma, DMA_CONTROL, value);
|
|
break;
|
|
case 0xFFFFD7:
|
|
dsp_dma_write(&dsp->dma, DMA_CONFIGURATION, value);
|
|
break;
|
|
}
|
|
|
|
trace_dsp_write_peripheral(address, value);
|
|
}
|
|
|
|
|
|
void dsp_step(DSPState* dsp)
|
|
{
|
|
dsp56k_execute_instruction(&dsp->core);
|
|
}
|
|
|
|
void dsp_run(DSPState* dsp, int cycles)
|
|
{
|
|
dsp->save_cycles += cycles;
|
|
|
|
if (dsp->save_cycles <= 0) return;
|
|
|
|
int dma_timer = 0;
|
|
|
|
while (dsp->save_cycles > 0)
|
|
{
|
|
dsp56k_execute_instruction(&dsp->core);
|
|
dsp->save_cycles -= dsp->core.instr_cycle;
|
|
dsp->core.cycle_count++;
|
|
|
|
if (dsp->dma.control & DMA_CONTROL_RUNNING) {
|
|
dma_timer++;
|
|
}
|
|
|
|
if (dma_timer > 2) {
|
|
dma_timer = 0;
|
|
dsp->dma.control &= ~DMA_CONTROL_RUNNING;
|
|
dsp->dma.control |= DMA_CONTROL_STOPPED;
|
|
}
|
|
|
|
if (dsp->core.is_idle) break;
|
|
}
|
|
|
|
/* FIXME: DMA timing be done cleaner. Xbox enables running
|
|
* then polls to make sure its running. But we complete DMA instantaneously,
|
|
* so when is it supposed to be signaled that it stopped? Maybe just wait at
|
|
* least one cycle? How long does hardware wait?
|
|
*/
|
|
}
|
|
|
|
void dsp_bootstrap(DSPState* dsp)
|
|
{
|
|
// scratch memory is dma'd in to pram by the bootrom
|
|
dsp->dma.scratch_rw(dsp->dma.rw_opaque,
|
|
(uint8_t*)dsp->core.pram, 0, 0x800*4, false);
|
|
for (int i = 0; i < 0x800; i++) {
|
|
if (dsp->core.pram[i] & 0xff000000) {
|
|
DPRINTF("Bootstrap %04x: %08x\n", i, dsp->core.pram[i]);
|
|
dsp->core.pram[i] &= 0x00ffffff;
|
|
}
|
|
}
|
|
memset(dsp->core.pram_opcache, 0, sizeof(dsp->core.pram_opcache));
|
|
}
|
|
|
|
void dsp_start_frame(DSPState* dsp)
|
|
{
|
|
dsp->interrupts |= INTERRUPT_START_FRAME;
|
|
}
|
|
|
|
uint32_t dsp_read_memory(DSPState* dsp, char space, uint32_t address)
|
|
{
|
|
int space_id;
|
|
|
|
switch (space) {
|
|
case 'X':
|
|
space_id = DSP_SPACE_X;
|
|
break;
|
|
case 'Y':
|
|
space_id = DSP_SPACE_Y;
|
|
break;
|
|
case 'P':
|
|
space_id = DSP_SPACE_P;
|
|
break;
|
|
default:
|
|
assert(false);
|
|
return 0;
|
|
}
|
|
|
|
return dsp56k_read_memory(&dsp->core, space_id, address);
|
|
}
|
|
|
|
void dsp_write_memory(DSPState* dsp, char space, uint32_t address, uint32_t value)
|
|
{
|
|
int space_id;
|
|
|
|
switch (space) {
|
|
case 'X':
|
|
space_id = DSP_SPACE_X;
|
|
break;
|
|
case 'Y':
|
|
space_id = DSP_SPACE_Y;
|
|
break;
|
|
case 'P':
|
|
space_id = DSP_SPACE_P;
|
|
break;
|
|
default:
|
|
assert(false);
|
|
return;
|
|
}
|
|
|
|
dsp56k_write_memory(&dsp->core, space_id, address, value);
|
|
}
|