xemu/hw/xbox/mcpx/dsp/dsp.c
2025-03-29 16:05:37 -07:00

528 lines
16 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 <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <assert.h>
#include "dsp_cpu.h"
#include "dsp_dma.h"
#include "dsp_state.h"
#include "dsp.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)
// #define DEBUG_DSP
#ifdef DEBUG_DSP
#define DPRINTF(fmt, ...) \
do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
#else
#define DPRINTF(fmt, ...) \
do { } while (0)
#endif
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);
DPRINTF("read_peripheral 0x%06x", address);
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;
}
DPRINTF(" -> 0x%06x\n", v);
return v;
}
static void write_peripheral(dsp_core_t* core, uint32_t address, uint32_t value) {
DSPState* dsp = container_of(core, DSPState, core);
DPRINTF("write_peripheral [0x%06x] = 0x%06x\n", address, value);
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;
}
}
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(stderr, "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;
}
/**
* Disassemble DSP code between given addresses, return next PC address
*/
uint32_t dsp_disasm_address(DSPState* dsp, FILE *out, uint32_t lowerAdr, uint32_t UpperAdr)
{
uint32_t dsp_pc;
for (dsp_pc=lowerAdr; dsp_pc<=UpperAdr; dsp_pc++) {
dsp_pc += dsp56k_execute_one_disasm_instruction(&dsp->core, out, dsp_pc);
}
return dsp_pc;
}
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);
}
/**
* Output memory values between given addresses in given DSP address space.
* Return next DSP address value.
*/
uint32_t dsp_disasm_memory(DSPState* dsp, uint32_t dsp_memdump_addr, uint32_t dsp_memdump_upper, char space)
{
uint32_t mem, value;
for (mem = dsp_memdump_addr; mem <= dsp_memdump_upper; mem++) {
value = dsp_read_memory(dsp, space, mem);
printf("%04x %06x\n", mem, value);
}
return dsp_memdump_upper+1;
}
/**
* Show information on DSP core state which isn't
* shown by any of the other commands (dd, dm, dr).
*/
void dsp_info(DSPState* dsp)
{
int i, j;
const char *stackname[] = { "SSH", "SSL" };
printf("DSP core information:\n");
for (i = 0; i < ARRAY_SIZE(stackname); i++) {
printf("- %s stack:", stackname[i]);
for (j = 0; j < ARRAY_SIZE(dsp->core.stack[0]); j++) {
printf(" %04x", dsp->core.stack[i][j]);
}
printf("\n");
}
printf("- Interrupt IPL:");
for (i = 0; i < ARRAY_SIZE(dsp->core.interrupt_ipl); i++) {
printf(" %04x", dsp->core.interrupt_ipl[i]);
}
printf("\n");
printf("- Pending ints: ");
for (i = 0; i < ARRAY_SIZE(dsp->core.interrupt_is_pending); i++) {
printf(" %04hx", dsp->core.interrupt_is_pending[i]);
}
printf("\n");
}
/**
* Show DSP register contents
*/
void dsp_print_registers(DSPState* dsp)
{
uint32_t i;
printf("A: A2: %02x A1: %06x A0: %06x\n",
dsp->core.registers[DSP_REG_A2], dsp->core.registers[DSP_REG_A1], dsp->core.registers[DSP_REG_A0]);
printf("B: B2: %02x B1: %06x B0: %06x\n",
dsp->core.registers[DSP_REG_B2], dsp->core.registers[DSP_REG_B1], dsp->core.registers[DSP_REG_B0]);
printf("X: X1: %06x X0: %06x\n", dsp->core.registers[DSP_REG_X1], dsp->core.registers[DSP_REG_X0]);
printf("Y: Y1: %06x Y0: %06x\n", dsp->core.registers[DSP_REG_Y1], dsp->core.registers[DSP_REG_Y0]);
for (i=0; i<8; i++) {
printf("R%01x: %04x N%01x: %04x M%01x: %04x\n",
i, dsp->core.registers[DSP_REG_R0+i],
i, dsp->core.registers[DSP_REG_N0+i],
i, dsp->core.registers[DSP_REG_M0+i]);
}
printf("LA: %04x LC: %04x PC: %04x\n", dsp->core.registers[DSP_REG_LA], dsp->core.registers[DSP_REG_LC], dsp->core.pc);
printf("SR: %04x OMR: %02x\n", dsp->core.registers[DSP_REG_SR], dsp->core.registers[DSP_REG_OMR]);
printf("SP: %02x SSH: %04x SSL: %04x\n",
dsp->core.registers[DSP_REG_SP], dsp->core.registers[DSP_REG_SSH], dsp->core.registers[DSP_REG_SSL]);
}
/**
* Get given DSP register address and required bit mask.
* Works for A0-2, B0-2, LA, LC, M0-7, N0-7, R0-7, X0-1, Y0-1, PC, SR, SP,
* OMR, SSH & SSL registers, but note that the SP, SSH & SSL registers
* need special handling (in DSP*SetRegister()) when they are set.
* Return the register width in bits or zero for an error.
*/
int dsp_get_register_address(DSPState* dsp, const char *regname, uint32_t **addr, uint32_t *mask)
{
#define MAX_REGNAME_LEN 4
typedef struct {
const char name[MAX_REGNAME_LEN];
uint32_t *addr;
size_t bits;
uint32_t mask;
} reg_addr_t;
/* sorted by name so that this can be bisected */
const reg_addr_t registers[] = {
/* 56-bit A register */
{ "A0", &dsp->core.registers[DSP_REG_A0], 32, BITMASK(24) },
{ "A1", &dsp->core.registers[DSP_REG_A1], 32, BITMASK(24) },
{ "A2", &dsp->core.registers[DSP_REG_A2], 32, BITMASK(8) },
/* 56-bit B register */
{ "B0", &dsp->core.registers[DSP_REG_B0], 32, BITMASK(24) },
{ "B1", &dsp->core.registers[DSP_REG_B1], 32, BITMASK(24) },
{ "B2", &dsp->core.registers[DSP_REG_B2], 32, BITMASK(8) },
/* 16-bit LA & LC registers */
{ "LA", &dsp->core.registers[DSP_REG_LA], 32, BITMASK(16) },
{ "LC", &dsp->core.registers[DSP_REG_LC], 32, BITMASK(16) },
/* 16-bit M registers */
{ "M0", &dsp->core.registers[DSP_REG_M0], 32, BITMASK(16) },
{ "M1", &dsp->core.registers[DSP_REG_M1], 32, BITMASK(16) },
{ "M2", &dsp->core.registers[DSP_REG_M2], 32, BITMASK(16) },
{ "M3", &dsp->core.registers[DSP_REG_M3], 32, BITMASK(16) },
{ "M4", &dsp->core.registers[DSP_REG_M4], 32, BITMASK(16) },
{ "M5", &dsp->core.registers[DSP_REG_M5], 32, BITMASK(16) },
{ "M6", &dsp->core.registers[DSP_REG_M6], 32, BITMASK(16) },
{ "M7", &dsp->core.registers[DSP_REG_M7], 32, BITMASK(16) },
/* 16-bit N registers */
{ "N0", &dsp->core.registers[DSP_REG_N0], 32, BITMASK(16) },
{ "N1", &dsp->core.registers[DSP_REG_N1], 32, BITMASK(16) },
{ "N2", &dsp->core.registers[DSP_REG_N2], 32, BITMASK(16) },
{ "N3", &dsp->core.registers[DSP_REG_N3], 32, BITMASK(16) },
{ "N4", &dsp->core.registers[DSP_REG_N4], 32, BITMASK(16) },
{ "N5", &dsp->core.registers[DSP_REG_N5], 32, BITMASK(16) },
{ "N6", &dsp->core.registers[DSP_REG_N6], 32, BITMASK(16) },
{ "N7", &dsp->core.registers[DSP_REG_N7], 32, BITMASK(16) },
{ "OMR", &dsp->core.registers[DSP_REG_OMR], 32, 0x5f },
/* 16-bit program counter */
{ "PC", (uint32_t*)(&dsp->core.pc), 24, BITMASK(24) },
/* 16-bit DSP R (address) registers */
{ "R0", &dsp->core.registers[DSP_REG_R0], 32, BITMASK(16) },
{ "R1", &dsp->core.registers[DSP_REG_R1], 32, BITMASK(16) },
{ "R2", &dsp->core.registers[DSP_REG_R2], 32, BITMASK(16) },
{ "R3", &dsp->core.registers[DSP_REG_R3], 32, BITMASK(16) },
{ "R4", &dsp->core.registers[DSP_REG_R4], 32, BITMASK(16) },
{ "R5", &dsp->core.registers[DSP_REG_R5], 32, BITMASK(16) },
{ "R6", &dsp->core.registers[DSP_REG_R6], 32, BITMASK(16) },
{ "R7", &dsp->core.registers[DSP_REG_R7], 32, BITMASK(16) },
{ "SSH", &dsp->core.registers[DSP_REG_SSH], 32, BITMASK(16) },
{ "SSL", &dsp->core.registers[DSP_REG_SSL], 32, BITMASK(16) },
{ "SP", &dsp->core.registers[DSP_REG_SP], 32, BITMASK(6) },
/* 16-bit status register */
{ "SR", &dsp->core.registers[DSP_REG_SR], 32, 0xefff },
/* 48-bit X register */
{ "X0", &dsp->core.registers[DSP_REG_X0], 32, BITMASK(24) },
{ "X1", &dsp->core.registers[DSP_REG_X1], 32, BITMASK(24) },
/* 48-bit Y register */
{ "Y0", &dsp->core.registers[DSP_REG_Y0], 32, BITMASK(24) },
{ "Y1", &dsp->core.registers[DSP_REG_Y1], 32, BITMASK(24) }
};
/* left, right, middle, direction */
int l, r, m, dir = 0;
unsigned int i, len;
char reg[MAX_REGNAME_LEN];
for (i = 0; i < sizeof(reg) && regname[i]; i++) {
reg[i] = toupper(regname[i]);
}
if (i < 2 || regname[i]) {
/* too short or longer than any of the names */
return 0;
}
len = i;
/* bisect */
l = 0;
r = ARRAY_SIZE(registers) - 1;
do {
m = (l+r) >> 1;
for (i = 0; i < len; i++) {
dir = (int)reg[i] - registers[m].name[i];
if (dir) {
break;
}
}
if (dir == 0) {
*addr = registers[m].addr;
*mask = registers[m].mask;
return registers[m].bits;
}
if (dir < 0) {
r = m-1;
} else {
l = m+1;
}
} while (l <= r);
#undef MAX_REGNAME_LEN
return 0;
}
/**
* Set given DSP register value, return false if unknown register given
*/
bool dsp_disasm_set_register(DSPState* dsp, const char *arg, uint32_t value)
{
uint32_t *addr, mask, sp_value;
int bits;
/* first check registers needing special handling... */
if (arg[0]=='S' || arg[0]=='s') {
if (arg[1]=='P' || arg[1]=='p') {
dsp->core.registers[DSP_REG_SP] = value & BITMASK(6);
value &= BITMASK(4);
dsp->core.registers[DSP_REG_SSH] = dsp->core.stack[0][value];
dsp->core.registers[DSP_REG_SSL] = dsp->core.stack[1][value];
return true;
}
if (arg[1]=='S' || arg[1]=='s') {
sp_value = dsp->core.registers[DSP_REG_SP] & BITMASK(4);
if (arg[2]=='H' || arg[2]=='h') {
if (sp_value == 0) {
dsp->core.registers[DSP_REG_SSH] = 0;
dsp->core.stack[0][sp_value] = 0;
} else {
dsp->core.registers[DSP_REG_SSH] = value & BITMASK(16);
dsp->core.stack[0][sp_value] = value & BITMASK(16);
}
return true;
}
if (arg[2]=='L' || arg[2]=='l') {
if (sp_value == 0) {
dsp->core.registers[DSP_REG_SSL] = 0;
dsp->core.stack[1][sp_value] = 0;
} else {
dsp->core.registers[DSP_REG_SSL] = value & BITMASK(16);
dsp->core.stack[1][sp_value] = value & BITMASK(16);
}
return true;
}
}
}
/* ...then registers where address & mask are enough */
bits = dsp_get_register_address(dsp, arg, &addr, &mask);
switch (bits) {
case 32:
*addr = value & mask;
return true;
case 16:
*(uint16_t*)addr = value & mask;
return true;
}
return false;
}