mame/src/emu/cpu/sh2/sh2comn.c
2008-10-13 08:38:32 +00:00

728 lines
16 KiB
C

/*****************************************************************************
*
* sh2common.c
*
* SH-2 non-specific components
*
*****************************************************************************/
#include "debugger.h"
#include "deprecat.h"
#include "sh2.h"
#include "sh2comn.h"
extern SH2 *sh2;
#define VERBOSE 0
#define LOG(x) do { if (VERBOSE) logerror x; } while (0)
// Atrocious hack that makes the soldivid music correct
//static const int div_tab[4] = { 3, 5, 7, 0 };
static const int div_tab[4] = { 3, 5, 3, 0 };
INLINE UINT32 RL(offs_t A)
{
if (A >= 0xe0000000)
return sh2_internal_r(Machine, (A & 0x1fc)>>2, 0xffffffff);
if (A >= 0xc0000000)
return program_read_dword_32be(A);
if (A >= 0x40000000)
return 0xa5a5a5a5;
return program_read_dword_32be(A & AM);
}
INLINE void WL(offs_t A, UINT32 V)
{
if (A >= 0xe0000000)
{
sh2_internal_w(Machine, (A & 0x1fc)>>2, V, 0xffffffff);
return;
}
if (A >= 0xc0000000)
{
program_write_dword_32be(A,V);
return;
}
if (A >= 0x40000000)
return;
program_write_dword_32be(A & AM,V);
}
static void sh2_timer_resync(void)
{
int divider = div_tab[(sh2->m[5] >> 8) & 3];
UINT64 cur_time = cpunum_gettotalcycles(sh2->cpu_number);
if(divider)
sh2->frc += (cur_time - sh2->frc_base) >> divider;
sh2->frc_base = cur_time;
}
static void sh2_timer_activate(void)
{
int max_delta = 0xfffff;
UINT16 frc;
timer_adjust_oneshot(sh2->timer, attotime_never, 0);
frc = sh2->frc;
if(!(sh2->m[4] & OCFA)) {
UINT16 delta = sh2->ocra - frc;
if(delta < max_delta)
max_delta = delta;
}
if(!(sh2->m[4] & OCFB) && (sh2->ocra <= sh2->ocrb || !(sh2->m[4] & 0x010000))) {
UINT16 delta = sh2->ocrb - frc;
if(delta < max_delta)
max_delta = delta;
}
if(!(sh2->m[4] & OVF) && !(sh2->m[4] & 0x010000)) {
int delta = 0x10000 - frc;
if(delta < max_delta)
max_delta = delta;
}
if(max_delta != 0xfffff) {
int divider = div_tab[(sh2->m[5] >> 8) & 3];
if(divider) {
max_delta <<= divider;
sh2->frc_base = cpunum_gettotalcycles(sh2->cpu_number);
timer_adjust_oneshot(sh2->timer, ATTOTIME_IN_CYCLES(max_delta, sh2->cpu_number), sh2->cpu_number);
} else {
logerror("SH2.%d: Timer event in %d cycles of external clock", sh2->cpu_number, max_delta);
}
}
}
TIMER_CALLBACK( sh2_timer_callback )
{
UINT16 frc;
int cpunum = param;
cpuintrf_push_context(cpunum);
sh2_timer_resync();
frc = sh2->frc;
if(frc == sh2->ocrb)
sh2->m[4] |= OCFB;
if(frc == 0x0000)
sh2->m[4] |= OVF;
if(frc == sh2->ocra)
{
sh2->m[4] |= OCFA;
if(sh2->m[4] & 0x010000)
sh2->frc = 0;
}
sh2_recalc_irq();
sh2_timer_activate();
cpuintrf_pop_context();
}
TIMER_CALLBACK( sh2_dmac_callback )
{
int cpunum = param >> 1;
int dma = param & 1;
cpuintrf_push_context(cpunum);
LOG(("SH2.%d: DMA %d complete\n", cpunum, dma));
sh2->m[0x63+4*dma] |= 2;
sh2->dma_timer_active[dma] = 0;
sh2_recalc_irq();
cpuintrf_pop_context();
}
static void sh2_dmac_check(int dma)
{
if(sh2->m[0x63+4*dma] & sh2->m[0x6c] & 1)
{
if(!sh2->dma_timer_active[dma] && !(sh2->m[0x63+4*dma] & 2))
{
int incs, incd, size;
UINT32 src, dst, count;
incd = (sh2->m[0x63+4*dma] >> 14) & 3;
incs = (sh2->m[0x63+4*dma] >> 12) & 3;
size = (sh2->m[0x63+4*dma] >> 10) & 3;
if(incd == 3 || incs == 3)
{
logerror("SH2: DMA: bad increment values (%d, %d, %d, %04x)\n", incd, incs, size, sh2->m[0x63+4*dma]);
return;
}
src = sh2->m[0x60+4*dma];
dst = sh2->m[0x61+4*dma];
count = sh2->m[0x62+4*dma];
if(!count)
count = 0x1000000;
LOG(("SH2: DMA %d start %x, %x, %x, %04x, %d, %d, %d\n", dma, src, dst, count, sh2->m[0x63+4*dma], incs, incd, size));
sh2->dma_timer_active[dma] = 1;
timer_adjust_oneshot(sh2->dma_timer[dma], ATTOTIME_IN_CYCLES(2*count+1, sh2->cpu_number), (sh2->cpu_number<<1)|dma);
src &= AM;
dst &= AM;
switch(size)
{
case 0:
for(;count > 0; count --)
{
if(incs == 2)
src --;
if(incd == 2)
dst --;
program_write_byte_32be(dst, program_read_byte_32be(src));
if(incs == 1)
src ++;
if(incd == 1)
dst ++;
}
break;
case 1:
src &= ~1;
dst &= ~1;
for(;count > 0; count --)
{
if(incs == 2)
src -= 2;
if(incd == 2)
dst -= 2;
program_write_word_32be(dst, program_read_word_32be(src));
if(incs == 1)
src += 2;
if(incd == 1)
dst += 2;
}
break;
case 2:
src &= ~3;
dst &= ~3;
for(;count > 0; count --)
{
if(incs == 2)
src -= 4;
if(incd == 2)
dst -= 4;
program_write_dword_32be(dst, program_read_dword_32be(src));
if(incs == 1)
src += 4;
if(incd == 1)
dst += 4;
}
break;
case 3:
src &= ~3;
dst &= ~3;
count &= ~3;
for(;count > 0; count -= 4)
{
if(incd == 2)
dst -= 16;
program_write_dword_32be(dst, program_read_dword_32be(src));
program_write_dword_32be(dst+4, program_read_dword_32be(src+4));
program_write_dword_32be(dst+8, program_read_dword_32be(src+8));
program_write_dword_32be(dst+12, program_read_dword_32be(src+12));
src += 16;
if(incd == 1)
dst += 16;
}
break;
}
}
}
else
{
if(sh2->dma_timer_active[dma])
{
logerror("SH2: DMA %d cancelled in-flight", dma);
timer_adjust_oneshot(sh2->dma_timer[dma], attotime_never, 0);
sh2->dma_timer_active[dma] = 0;
}
}
}
WRITE32_HANDLER( sh2_internal_w )
{
UINT32 old;
#ifdef USE_SH2DRC
offset &= 0x7f;
#endif
old = sh2->m[offset];
COMBINE_DATA(sh2->m+offset);
// if(offset != 0x20)
// logerror("sh2_internal_w: Write %08x (%x), %08x @ %08x\n", 0xfffffe00+offset*4, offset, data, mem_mask);
// if(offset != 0x20)
// printf("sh2_internal_w: Write %08x (%x), %08x @ %08x (PC %x)\n", 0xfffffe00+offset*4, offset, data, mem_mask, activecpu_get_pc());
switch( offset )
{
// Timers
case 0x04: // TIER, FTCSR, FRC
if((mem_mask & 0x00ffffff) != 0)
sh2_timer_resync();
// printf("SH2.%d: TIER write %04x @ %04x\n", sh2->cpu_number, data >> 16, mem_mask>>16);
sh2->m[4] = (sh2->m[4] & ~(ICF|OCFA|OCFB|OVF)) | (old & sh2->m[4] & (ICF|OCFA|OCFB|OVF));
COMBINE_DATA(&sh2->frc);
if((mem_mask & 0x00ffffff) != 0)
sh2_timer_activate();
sh2_recalc_irq();
break;
case 0x05: // OCRx, TCR, TOCR
// printf("SH2.%d: TCR write %08x @ %08x\n", sh2->cpu_number, data, mem_mask);
sh2_timer_resync();
if(sh2->m[5] & 0x10)
sh2->ocrb = (sh2->ocrb & (~mem_mask >> 16)) | ((data & mem_mask) >> 16);
else
sh2->ocra = (sh2->ocra & (~mem_mask >> 16)) | ((data & mem_mask) >> 16);
sh2_timer_activate();
break;
case 0x06: // ICR
break;
// Interrupt vectors
case 0x18: // IPRB, VCRA
case 0x19: // VCRB, VCRC
case 0x1a: // VCRD
sh2_recalc_irq();
break;
// DMA
case 0x1c: // DRCR0, DRCR1
break;
// Watchdog
case 0x20: // WTCNT, RSTCSR
break;
// Standby and cache
case 0x24: // SBYCR, CCR
break;
// Interrupt vectors cont.
case 0x38: // ICR, IRPA
break;
case 0x39: // VCRWDT
break;
// Division box
case 0x40: // DVSR
break;
case 0x41: // DVDNT
{
INT32 a = sh2->m[0x41];
INT32 b = sh2->m[0x40];
LOG(("SH2 #%d div+mod %d/%d\n", cpu_getactivecpu(), a, b));
if (b)
{
sh2->m[0x45] = a / b;
sh2->m[0x44] = a % b;
}
else
{
sh2->m[0x42] |= 0x00010000;
sh2->m[0x45] = 0x7fffffff;
sh2->m[0x44] = 0x7fffffff;
sh2_recalc_irq();
}
break;
}
case 0x42: // DVCR
sh2->m[0x42] = (sh2->m[0x42] & ~0x00001000) | (old & sh2->m[0x42] & 0x00010000);
sh2_recalc_irq();
break;
case 0x43: // VCRDIV
sh2_recalc_irq();
break;
case 0x44: // DVDNTH
break;
case 0x45: // DVDNTL
{
INT64 a = sh2->m[0x45] | ((UINT64)(sh2->m[0x44]) << 32);
INT64 b = (INT32)sh2->m[0x40];
LOG(("SH2 #%d div+mod %lld/%lld\n", cpu_getactivecpu(), a, b));
if (b)
{
INT64 q = a / b;
if (q != (INT32)q)
{
sh2->m[0x42] |= 0x00010000;
sh2->m[0x45] = 0x7fffffff;
sh2->m[0x44] = 0x7fffffff;
sh2_recalc_irq();
}
else
{
sh2->m[0x45] = q;
sh2->m[0x44] = a % b;
}
}
else
{
sh2->m[0x42] |= 0x00010000;
sh2->m[0x45] = 0x7fffffff;
sh2->m[0x44] = 0x7fffffff;
sh2_recalc_irq();
}
break;
}
// DMA controller
case 0x60: // SAR0
case 0x61: // DAR0
break;
case 0x62: // DTCR0
sh2->m[0x62] &= 0xffffff;
break;
case 0x63: // CHCR0
sh2->m[0x63] = (sh2->m[0x63] & ~2) | (old & sh2->m[0x63] & 2);
sh2_dmac_check(0);
break;
case 0x64: // SAR1
case 0x65: // DAR1
break;
case 0x66: // DTCR1
sh2->m[0x66] &= 0xffffff;
break;
case 0x67: // CHCR1
sh2->m[0x67] = (sh2->m[0x67] & ~2) | (old & sh2->m[0x67] & 2);
sh2_dmac_check(1);
break;
case 0x68: // VCRDMA0
case 0x6a: // VCRDMA1
sh2_recalc_irq();
break;
case 0x6c: // DMAOR
sh2->m[0x6c] = (sh2->m[0x6c] & ~6) | (old & sh2->m[0x6c] & 6);
sh2_dmac_check(0);
sh2_dmac_check(1);
break;
// Bus controller
case 0x78: // BCR1
case 0x79: // BCR2
case 0x7a: // WCR
case 0x7b: // MCR
case 0x7c: // RTCSR
case 0x7d: // RTCNT
case 0x7e: // RTCOR
break;
default:
logerror("sh2_internal_w: Unmapped write %08x, %08x @ %08x\n", 0xfffffe00+offset*4, data, mem_mask);
break;
}
}
READ32_HANDLER( sh2_internal_r )
{
#ifdef USE_SH2DRC
offset &= 0x7f;
#endif
// logerror("sh2_internal_r: Read %08x (%x) @ %08x\n", 0xfffffe00+offset*4, offset, mem_mask);
switch( offset )
{
case 0x04: // TIER, FTCSR, FRC
if ( mem_mask == 0x00ff0000 )
if ( sh2->ftcsr_read_callback != NULL )
sh2->ftcsr_read_callback( (sh2->m[4] & 0xffff0000) | sh2->frc );
sh2_timer_resync();
return (sh2->m[4] & 0xffff0000) | sh2->frc;
case 0x05: // OCRx, TCR, TOCR
if(sh2->m[5] & 0x10)
return (sh2->ocrb << 16) | (sh2->m[5] & 0xffff);
else
return (sh2->ocra << 16) | (sh2->m[5] & 0xffff);
case 0x06: // ICR
return sh2->icr << 16;
case 0x38: // ICR, IPRA
return (sh2->m[0x38] & 0x7fffffff) | (sh2->nmi_line_state == ASSERT_LINE ? 0 : 0x80000000);
case 0x78: // BCR1
return sh2->is_slave ? 0x00008000 : 0;
case 0x41: // dvdntl mirrors
case 0x47:
return sh2->m[0x45];
case 0x46: // dvdnth mirror
return sh2->m[0x44];
}
return sh2->m[offset];
}
void sh2_set_frt_input(int cpunum, int state)
{
if(state == PULSE_LINE)
{
sh2_set_frt_input(cpunum, ASSERT_LINE);
sh2_set_frt_input(cpunum, CLEAR_LINE);
return;
}
cpuintrf_push_context(cpunum);
if(sh2->frt_input == state) {
cpuintrf_pop_context();
return;
}
sh2->frt_input = state;
if(sh2->m[5] & 0x8000) {
if(state == CLEAR_LINE) {
cpuintrf_pop_context();
return;
}
} else {
if(state == ASSERT_LINE) {
cpuintrf_pop_context();
return;
}
}
sh2_timer_resync();
sh2->icr = sh2->frc;
sh2->m[4] |= ICF;
logerror("SH2.%d: ICF activated (%x)\n", sh2->cpu_number, sh2->pc & AM);
sh2_recalc_irq();
cpuintrf_pop_context();
}
void sh2_set_irq_line(int irqline, int state)
{
if (irqline == INPUT_LINE_NMI)
{
if (sh2->nmi_line_state == state)
return;
sh2->nmi_line_state = state;
if( state == CLEAR_LINE )
{
LOG(("SH-2 #%d cleared nmi\n", cpu_getactivecpu()));
}
else
{
LOG(("SH-2 #%d assert nmi\n", cpu_getactivecpu()));
sh2_exception("Set IRQ line", 16);
#ifdef USE_SH2DRC
sh2->pending_nmi = 1;
#endif
}
}
else
{
if (sh2->irq_line_state[irqline] == state)
return;
sh2->irq_line_state[irqline] = state;
if( state == CLEAR_LINE )
{
LOG(("SH-2 #%d cleared irq #%d\n", cpu_getactivecpu(), irqline));
sh2->pending_irq &= ~(1 << irqline);
}
else
{
LOG(("SH-2 #%d assert irq #%d\n", cpu_getactivecpu(), irqline));
sh2->pending_irq |= 1 << irqline;
#ifdef USE_SH2DRC
sh2->test_irq = 1;
#else
if(sh2->delay)
sh2->test_irq = 1;
else
CHECK_PENDING_IRQ("sh2_set_irq_line");
#endif
}
}
}
void sh2_recalc_irq(void)
{
int irq = 0, vector = -1;
int level;
// Timer irqs
if((sh2->m[4]>>8) & sh2->m[4] & (ICF|OCFA|OCFB|OVF))
{
level = (sh2->m[0x18] >> 24) & 15;
if(level > irq)
{
int mask = (sh2->m[4]>>8) & sh2->m[4];
irq = level;
if(mask & ICF)
vector = (sh2->m[0x19] >> 8) & 0x7f;
else if(mask & (OCFA|OCFB))
vector = sh2->m[0x19] & 0x7f;
else
vector = (sh2->m[0x1a] >> 24) & 0x7f;
}
}
// DMA irqs
if((sh2->m[0x63] & 6) == 6) {
level = (sh2->m[0x38] >> 8) & 15;
if(level > irq) {
irq = level;
vector = (sh2->m[0x68] >> 24) & 0x7f;
}
}
if((sh2->m[0x67] & 6) == 6) {
level = (sh2->m[0x38] >> 8) & 15;
if(level > irq) {
irq = level;
vector = (sh2->m[0x6a] >> 24) & 0x7f;
}
}
sh2->internal_irq_level = irq;
sh2->internal_irq_vector = vector;
sh2->test_irq = 1;
}
void sh2_exception(const char *message, int irqline)
{
int vector;
if (irqline != 16)
{
if (irqline <= ((sh2->sr >> 4) & 15)) /* If the cpu forbids this interrupt */
return;
// if this is an sh2 internal irq, use its vector
if (sh2->internal_irq_level == irqline)
{
vector = sh2->internal_irq_vector;
LOG(("SH-2 #%d exception #%d (internal vector: $%x) after [%s]\n", cpu_getactivecpu(), irqline, vector, message));
}
else
{
if(sh2->m[0x38] & 0x00010000)
{
vector = sh2->irq_callback(irqline);
LOG(("SH-2 #%d exception #%d (external vector: $%x) after [%s]\n", cpu_getactivecpu(), irqline, vector, message));
}
else
{
sh2->irq_callback(irqline);
vector = 64 + irqline/2;
LOG(("SH-2 #%d exception #%d (autovector: $%x) after [%s]\n", cpu_getactivecpu(), irqline, vector, message));
}
}
}
else
{
vector = 11;
LOG(("SH-2 #%d nmi exception (autovector: $%x) after [%s]\n", cpu_getactivecpu(), vector, message));
}
#ifdef USE_SH2DRC
sh2->evec = RL( sh2->vbr + vector * 4 );
sh2->evec &= AM;
sh2->irqsr = sh2->sr;
/* set I flags in SR */
if (irqline > SH2_INT_15)
sh2->sr = sh2->sr | I;
else
sh2->sr = (sh2->sr & ~I) | (irqline << 4);
// printf("sh2_exception [%s] irqline %x evec %x save SR %x new SR %x\n", message, irqline, sh2->evec, sh2->irqsr, sh2->sr);
#else
sh2->r[15] -= 4;
WL( sh2->r[15], sh2->sr ); /* push SR onto stack */
sh2->r[15] -= 4;
WL( sh2->r[15], sh2->pc ); /* push PC onto stack */
/* set I flags in SR */
if (irqline > SH2_INT_15)
sh2->sr = sh2->sr | I;
else
sh2->sr = (sh2->sr & ~I) | (irqline << 4);
/* fetch PC */
sh2->pc = RL( sh2->vbr + vector * 4 );
change_pc(sh2->pc & AM);
#endif
}
void sh2_common_init(int alloc, int index, int clock, const void *config, int (*irqcallback)(int))
{
const sh2_cpu_core *conf = config;
if (alloc)
{
sh2 = (SH2 *)auto_malloc(sizeof(SH2));
memset(sh2, 0, sizeof(SH2));
}
sh2->timer = timer_alloc(sh2_timer_callback, NULL);
timer_adjust_oneshot(sh2->timer, attotime_never, 0);
sh2->dma_timer[0] = timer_alloc(sh2_dmac_callback, NULL);
timer_adjust_oneshot(sh2->dma_timer[0], attotime_never, 0);
sh2->dma_timer[1] = timer_alloc(sh2_dmac_callback, NULL);
timer_adjust_oneshot(sh2->dma_timer[1], attotime_never, 0);
sh2->m = auto_malloc(0x200);
if(conf)
sh2->is_slave = conf->is_slave;
else
sh2->is_slave = 0;
sh2->cpu_number = index;
sh2->irq_callback = irqcallback;
state_save_register_item("sh2", index, sh2->pc);
state_save_register_item("sh2", index, sh2->r[15]);
state_save_register_item("sh2", index, sh2->sr);
state_save_register_item("sh2", index, sh2->pr);
state_save_register_item("sh2", index, sh2->gbr);
state_save_register_item("sh2", index, sh2->vbr);
state_save_register_item("sh2", index, sh2->mach);
state_save_register_item("sh2", index, sh2->macl);
state_save_register_item("sh2", index, sh2->r[ 0]);
state_save_register_item("sh2", index, sh2->r[ 1]);
state_save_register_item("sh2", index, sh2->r[ 2]);
state_save_register_item("sh2", index, sh2->r[ 3]);
state_save_register_item("sh2", index, sh2->r[ 4]);
state_save_register_item("sh2", index, sh2->r[ 5]);
state_save_register_item("sh2", index, sh2->r[ 6]);
state_save_register_item("sh2", index, sh2->r[ 7]);
state_save_register_item("sh2", index, sh2->r[ 8]);
state_save_register_item("sh2", index, sh2->r[ 9]);
state_save_register_item("sh2", index, sh2->r[10]);
state_save_register_item("sh2", index, sh2->r[11]);
state_save_register_item("sh2", index, sh2->r[12]);
state_save_register_item("sh2", index, sh2->r[13]);
state_save_register_item("sh2", index, sh2->r[14]);
state_save_register_item("sh2", index, sh2->ea);
}