picodrive/pico/carthw/svp/compiler.c
notaz cff531af94 clarify PicoDrive's license
- PicoDrive was originally released by fDave with simple
  "free for non-commercial use / For commercial use, separate licencing
  terms must be obtained" license and I kept it in my releases.
- in 2011, fDave re-released his code (same that I used as base
  many years ago) dual licensed with GPLv2 and MAME licenses:
    https://code.google.com/p/cyclone68000/

Based on the above I now proclaim that the whole source code is licensed
under the MAME license as more elaborate form of "for non-commercial use".
If that raises any doubt, I announce that all my modifications (which
is the vast majority of code by now) is licensed under the MAME license,
as it reads in COPYING file in this commit.

This does not affect ym2612.c/sn76496.c that were MAME licensed already
from the beginning.
2013-06-26 03:07:07 +03:00

1886 lines
46 KiB
C

/*
* SSP1601 to ARM recompiler
* (C) notaz, 2008,2009,2010
*
* This work is licensed under the terms of MAME license.
* See COPYING file in the top-level directory.
*/
#include "../../pico_int.h"
#include "../../../cpu/drc/cmn.h"
#include "compiler.h"
// FIXME: asm has these hardcoded
#define SSP_BLOCKTAB_ENTS (0x5090/2)
#define SSP_BLOCKTAB_IRAM_ONE (0x800/2) // table entries
#define SSP_BLOCKTAB_IRAM_ENTS (15*SSP_BLOCKTAB_IRAM_ONE)
static u32 **ssp_block_table; // [0x5090/2];
static u32 **ssp_block_table_iram; // [15][0x800/2];
static u32 *tcache_ptr = NULL;
static int nblocks = 0;
static int n_in_ops = 0;
extern ssp1601_t *ssp;
#define rPC ssp->gr[SSP_PC].h
#define rPMC ssp->gr[SSP_PMC]
#define SSP_FLAG_Z (1<<0xd)
#define SSP_FLAG_N (1<<0xf)
#ifndef ARM
//#define DUMP_BLOCK 0x0c9a
void ssp_drc_next(void){}
void ssp_drc_next_patch(void){}
void ssp_drc_end(void){}
#endif
#define COUNT_OP
#include "../../../cpu/drc/emit_arm.c"
// -----------------------------------------------------
static int get_inc(int mode)
{
int inc = (mode >> 11) & 7;
if (inc != 0) {
if (inc != 7) inc--;
inc = 1 << inc; // 0 1 2 4 8 16 32 128
if (mode & 0x8000) inc = -inc; // decrement mode
}
return inc;
}
u32 ssp_pm_read(int reg)
{
u32 d = 0, mode;
if (ssp->emu_status & SSP_PMC_SET)
{
ssp->pmac_read[reg] = rPMC.v;
ssp->emu_status &= ~SSP_PMC_SET;
return 0;
}
// just in case
ssp->emu_status &= ~SSP_PMC_HAVE_ADDR;
mode = ssp->pmac_read[reg]>>16;
if ((mode & 0xfff0) == 0x0800) // ROM
{
d = ((unsigned short *)Pico.rom)[ssp->pmac_read[reg]&0xfffff];
ssp->pmac_read[reg] += 1;
}
else if ((mode & 0x47ff) == 0x0018) // DRAM
{
unsigned short *dram = (unsigned short *)svp->dram;
int inc = get_inc(mode);
d = dram[ssp->pmac_read[reg]&0xffff];
ssp->pmac_read[reg] += inc;
}
// PMC value corresponds to last PMR accessed
rPMC.v = ssp->pmac_read[reg];
return d;
}
#define overwrite_write(dst, d) \
{ \
if (d & 0xf000) { dst &= ~0xf000; dst |= d & 0xf000; } \
if (d & 0x0f00) { dst &= ~0x0f00; dst |= d & 0x0f00; } \
if (d & 0x00f0) { dst &= ~0x00f0; dst |= d & 0x00f0; } \
if (d & 0x000f) { dst &= ~0x000f; dst |= d & 0x000f; } \
}
void ssp_pm_write(u32 d, int reg)
{
unsigned short *dram;
int mode, addr;
if (ssp->emu_status & SSP_PMC_SET)
{
ssp->pmac_write[reg] = rPMC.v;
ssp->emu_status &= ~SSP_PMC_SET;
return;
}
// just in case
ssp->emu_status &= ~SSP_PMC_HAVE_ADDR;
dram = (unsigned short *)svp->dram;
mode = ssp->pmac_write[reg]>>16;
addr = ssp->pmac_write[reg]&0xffff;
if ((mode & 0x43ff) == 0x0018) // DRAM
{
int inc = get_inc(mode);
if (mode & 0x0400) {
overwrite_write(dram[addr], d);
} else dram[addr] = d;
ssp->pmac_write[reg] += inc;
}
else if ((mode & 0xfbff) == 0x4018) // DRAM, cell inc
{
if (mode & 0x0400) {
overwrite_write(dram[addr], d);
} else dram[addr] = d;
ssp->pmac_write[reg] += (addr&1) ? 0x1f : 1;
}
else if ((mode & 0x47ff) == 0x001c) // IRAM
{
int inc = get_inc(mode);
((unsigned short *)svp->iram_rom)[addr&0x3ff] = d;
ssp->pmac_write[reg] += inc;
ssp->drc.iram_dirty = 1;
}
rPMC.v = ssp->pmac_write[reg];
}
// -----------------------------------------------------
// 14 IRAM blocks
static unsigned char iram_context_map[] =
{
0, 0, 0, 0, 1, 0, 0, 0, // 04
0, 0, 0, 0, 0, 0, 2, 0, // 0e
0, 0, 0, 0, 0, 3, 0, 4, // 15 17
5, 0, 0, 6, 0, 7, 0, 0, // 18 1b 1d
8, 9, 0, 0, 0,10, 0, 0, // 20 21 25
0, 0, 0, 0, 0, 0, 0, 0,
0, 0,11, 0, 0,12, 0, 0, // 32 35
13,14, 0, 0, 0, 0, 0, 0 // 38 39
};
int ssp_get_iram_context(void)
{
unsigned char *ir = (unsigned char *)svp->iram_rom;
int val1, val = ir[0x083^1] + ir[0x4FA^1] + ir[0x5F7^1] + ir[0x47B^1];
val1 = iram_context_map[(val>>1)&0x3f];
if (val1 == 0) {
elprintf(EL_ANOMALY, "svp: iram ctx val: %02x PC=%04x\n", (val>>1)&0x3f, rPC);
//debug_dump2file(name, svp->iram_rom, 0x800);
//exit(1);
}
return val1;
}
// -----------------------------------------------------
/* regs with known values */
static struct
{
ssp_reg_t gr[8];
unsigned char r[8];
unsigned int pmac_read[5];
unsigned int pmac_write[5];
ssp_reg_t pmc;
unsigned int emu_status;
} known_regs;
#define KRREG_X (1 << SSP_X)
#define KRREG_Y (1 << SSP_Y)
#define KRREG_A (1 << SSP_A) /* AH only */
#define KRREG_ST (1 << SSP_ST)
#define KRREG_STACK (1 << SSP_STACK)
#define KRREG_PC (1 << SSP_PC)
#define KRREG_P (1 << SSP_P)
#define KRREG_PR0 (1 << 8)
#define KRREG_PR4 (1 << 12)
#define KRREG_AL (1 << 16)
#define KRREG_PMCM (1 << 18) /* only mode word of PMC */
#define KRREG_PMC (1 << 19)
#define KRREG_PM0R (1 << 20)
#define KRREG_PM1R (1 << 21)
#define KRREG_PM2R (1 << 22)
#define KRREG_PM3R (1 << 23)
#define KRREG_PM4R (1 << 24)
#define KRREG_PM0W (1 << 25)
#define KRREG_PM1W (1 << 26)
#define KRREG_PM2W (1 << 27)
#define KRREG_PM3W (1 << 28)
#define KRREG_PM4W (1 << 29)
/* bitfield of known register values */
static u32 known_regb = 0;
/* known vals, which need to be flushed
* (only ST, P, r0-r7, PMCx, PMxR, PMxW)
* ST means flags are being held in ARM PSR
* P means that it needs to be recalculated
*/
static u32 dirty_regb = 0;
/* known values of host regs.
* -1 - unknown
* 000000-00ffff - 16bit value
* 100000-10ffff - base reg (r7) + 16bit val
* 0r0000 - means reg (low) eq gr[r].h, r != AL
*/
static int hostreg_r[4];
static void hostreg_clear(void)
{
int i;
for (i = 0; i < 4; i++)
hostreg_r[i] = -1;
}
static void hostreg_sspreg_changed(int sspreg)
{
int i;
for (i = 0; i < 4; i++)
if (hostreg_r[i] == (sspreg<<16)) hostreg_r[i] = -1;
}
#define PROGRAM(x) ((unsigned short *)svp->iram_rom)[x]
#define PROGRAM_P(x) ((unsigned short *)svp->iram_rom + (x))
void tr_unhandled(void)
{
//FILE *f = fopen("tcache.bin", "wb");
//fwrite(tcache, 1, (tcache_ptr - tcache)*4, f);
//fclose(f);
elprintf(EL_ANOMALY, "unhandled @ %04x\n", known_regs.gr[SSP_PC].h<<1);
//exit(1);
}
/* update P, if needed. Trashes r0 */
static void tr_flush_dirty_P(void)
{
// TODO: const regs
if (!(dirty_regb & KRREG_P)) return;
EOP_MOV_REG_ASR(10, 4, 16); // mov r10, r4, asr #16
EOP_MOV_REG_LSL( 0, 4, 16); // mov r0, r4, lsl #16
EOP_MOV_REG_ASR( 0, 0, 15); // mov r0, r0, asr #15
EOP_MUL(10, 0, 10); // mul r10, r0, r10
dirty_regb &= ~KRREG_P;
hostreg_r[0] = -1;
}
/* write dirty pr to host reg. Nothing is trashed */
static void tr_flush_dirty_pr(int r)
{
int ror = 0, reg;
if (!(dirty_regb & (1 << (r+8)))) return;
switch (r&3) {
case 0: ror = 0; break;
case 1: ror = 24/2; break;
case 2: ror = 16/2; break;
}
reg = (r < 4) ? 8 : 9;
EOP_BIC_IMM(reg,reg,ror,0xff);
if (known_regs.r[r] != 0)
EOP_ORR_IMM(reg,reg,ror,known_regs.r[r]);
dirty_regb &= ~(1 << (r+8));
}
/* write all dirty pr0-pr7 to host regs. Nothing is trashed */
static void tr_flush_dirty_prs(void)
{
int i, ror = 0, reg;
int dirty = dirty_regb >> 8;
if ((dirty&7) == 7) {
emith_move_r_imm(8, known_regs.r[0]|(known_regs.r[1]<<8)|(known_regs.r[2]<<16));
dirty &= ~7;
}
if ((dirty&0x70) == 0x70) {
emith_move_r_imm(9, known_regs.r[4]|(known_regs.r[5]<<8)|(known_regs.r[6]<<16));
dirty &= ~0x70;
}
/* r0-r7 */
for (i = 0; dirty && i < 8; i++, dirty >>= 1)
{
if (!(dirty&1)) continue;
switch (i&3) {
case 0: ror = 0; break;
case 1: ror = 24/2; break;
case 2: ror = 16/2; break;
}
reg = (i < 4) ? 8 : 9;
EOP_BIC_IMM(reg,reg,ror,0xff);
if (known_regs.r[i] != 0)
EOP_ORR_IMM(reg,reg,ror,known_regs.r[i]);
}
dirty_regb &= ~0xff00;
}
/* write dirty pr and "forget" it. Nothing is trashed. */
static void tr_release_pr(int r)
{
tr_flush_dirty_pr(r);
known_regb &= ~(1 << (r+8));
}
/* fush ARM PSR to r6. Trashes r1 */
static void tr_flush_dirty_ST(void)
{
if (!(dirty_regb & KRREG_ST)) return;
EOP_BIC_IMM(6,6,0,0x0f);
EOP_MRS(1);
EOP_ORR_REG_LSR(6,6,1,28);
dirty_regb &= ~KRREG_ST;
hostreg_r[1] = -1;
}
/* inverse of above. Trashes r1 */
static void tr_make_dirty_ST(void)
{
if (dirty_regb & KRREG_ST) return;
if (known_regb & KRREG_ST) {
int flags = 0;
if (known_regs.gr[SSP_ST].h & SSP_FLAG_N) flags |= 8;
if (known_regs.gr[SSP_ST].h & SSP_FLAG_Z) flags |= 4;
EOP_MSR_IMM(4/2, flags);
} else {
EOP_MOV_REG_LSL(1, 6, 28);
EOP_MSR_REG(1);
hostreg_r[1] = -1;
}
dirty_regb |= KRREG_ST;
}
/* load 16bit val into host reg r0-r3. Nothing is trashed */
static void tr_mov16(int r, int val)
{
if (hostreg_r[r] != val) {
emith_move_r_imm(r, val);
hostreg_r[r] = val;
}
}
static void tr_mov16_cond(int cond, int r, int val)
{
emith_op_imm(cond, 0, A_OP_MOV, r, val);
hostreg_r[r] = -1;
}
/* trashes r1 */
static void tr_flush_dirty_pmcrs(void)
{
u32 i, val = (u32)-1;
if (!(dirty_regb & 0x3ff80000)) return;
if (dirty_regb & KRREG_PMC) {
val = known_regs.pmc.v;
emith_move_r_imm(1, val);
EOP_STR_IMM(1,7,0x400+SSP_PMC*4);
if (known_regs.emu_status & (SSP_PMC_SET|SSP_PMC_HAVE_ADDR)) {
elprintf(EL_ANOMALY, "!! SSP_PMC_SET|SSP_PMC_HAVE_ADDR set on flush\n");
tr_unhandled();
}
}
for (i = 0; i < 5; i++)
{
if (dirty_regb & (1 << (20+i))) {
if (val != known_regs.pmac_read[i]) {
val = known_regs.pmac_read[i];
emith_move_r_imm(1, val);
}
EOP_STR_IMM(1,7,0x454+i*4); // pmac_read
}
if (dirty_regb & (1 << (25+i))) {
if (val != known_regs.pmac_write[i]) {
val = known_regs.pmac_write[i];
emith_move_r_imm(1, val);
}
EOP_STR_IMM(1,7,0x46c+i*4); // pmac_write
}
}
dirty_regb &= ~0x3ff80000;
hostreg_r[1] = -1;
}
/* read bank word to r0 (upper bits zero). Thrashes r1. */
static void tr_bank_read(int addr) /* word addr 0-0x1ff */
{
int breg = 7;
if (addr > 0x7f) {
if (hostreg_r[1] != (0x100000|((addr&0x180)<<1))) {
EOP_ADD_IMM(1,7,30/2,(addr&0x180)>>1); // add r1, r7, ((op&0x180)<<1)
hostreg_r[1] = 0x100000|((addr&0x180)<<1);
}
breg = 1;
}
EOP_LDRH_IMM(0,breg,(addr&0x7f)<<1); // ldrh r0, [r1, (op&0x7f)<<1]
hostreg_r[0] = -1;
}
/* write r0 to bank. Trashes r1. */
static void tr_bank_write(int addr)
{
int breg = 7;
if (addr > 0x7f) {
if (hostreg_r[1] != (0x100000|((addr&0x180)<<1))) {
EOP_ADD_IMM(1,7,30/2,(addr&0x180)>>1); // add r1, r7, ((op&0x180)<<1)
hostreg_r[1] = 0x100000|((addr&0x180)<<1);
}
breg = 1;
}
EOP_STRH_IMM(0,breg,(addr&0x7f)<<1); // strh r0, [r1, (op&0x7f)<<1]
}
/* handle RAM bank pointer modifiers. if need_modulo, trash r1-r3, else nothing */
static void tr_ptrr_mod(int r, int mod, int need_modulo, int count)
{
int modulo_shift = -1; /* unknown */
if (mod == 0) return;
if (!need_modulo || mod == 1) // +!
modulo_shift = 8;
else if (need_modulo && (known_regb & KRREG_ST)) {
modulo_shift = known_regs.gr[SSP_ST].h & 7;
if (modulo_shift == 0) modulo_shift = 8;
}
if (modulo_shift == -1)
{
int reg = (r < 4) ? 8 : 9;
tr_release_pr(r);
if (dirty_regb & KRREG_ST) {
// avoid flushing ARM flags
EOP_AND_IMM(1, 6, 0, 0x70);
EOP_SUB_IMM(1, 1, 0, 0x10);
EOP_AND_IMM(1, 1, 0, 0x70);
EOP_ADD_IMM(1, 1, 0, 0x10);
} else {
EOP_C_DOP_IMM(A_COND_AL,A_OP_AND,1,6,1,0,0x70); // ands r1, r6, #0x70
EOP_C_DOP_IMM(A_COND_EQ,A_OP_MOV,0,0,1,0,0x80); // moveq r1, #0x80
}
EOP_MOV_REG_LSR(1, 1, 4); // mov r1, r1, lsr #4
EOP_RSB_IMM(2, 1, 0, 8); // rsb r1, r1, #8
EOP_MOV_IMM(3, 8/2, count); // mov r3, #0x01000000
if (r&3)
EOP_ADD_IMM(1, 1, 0, (r&3)*8); // add r1, r1, #(r&3)*8
EOP_MOV_REG2_ROR(reg,reg,1); // mov reg, reg, ror r1
if (mod == 2)
EOP_SUB_REG2_LSL(reg,reg,3,2); // sub reg, reg, #0x01000000 << r2
else EOP_ADD_REG2_LSL(reg,reg,3,2);
EOP_RSB_IMM(1, 1, 0, 32); // rsb r1, r1, #32
EOP_MOV_REG2_ROR(reg,reg,1); // mov reg, reg, ror r1
hostreg_r[1] = hostreg_r[2] = hostreg_r[3] = -1;
}
else if (known_regb & (1 << (r + 8)))
{
int modulo = (1 << modulo_shift) - 1;
if (mod == 2)
known_regs.r[r] = (known_regs.r[r] & ~modulo) | ((known_regs.r[r] - count) & modulo);
else known_regs.r[r] = (known_regs.r[r] & ~modulo) | ((known_regs.r[r] + count) & modulo);
}
else
{
int reg = (r < 4) ? 8 : 9;
int ror = ((r&3) + 1)*8 - (8 - modulo_shift);
EOP_MOV_REG_ROR(reg,reg,ror);
// {add|sub} reg, reg, #1<<shift
EOP_C_DOP_IMM(A_COND_AL,(mod==2)?A_OP_SUB:A_OP_ADD,0,reg,reg, 8/2, count << (8 - modulo_shift));
EOP_MOV_REG_ROR(reg,reg,32-ror);
}
}
/* handle writes r0 to (rX). Trashes r1.
* fortunately we can ignore modulo increment modes for writes. */
static void tr_rX_write(int op)
{
if ((op&3) == 3)
{
int mod = (op>>2) & 3; // direct addressing
tr_bank_write((op & 0x100) + mod);
}
else
{
int r = (op&3) | ((op>>6)&4);
if (known_regb & (1 << (r + 8))) {
tr_bank_write((op&0x100) | known_regs.r[r]);
} else {
int reg = (r < 4) ? 8 : 9;
int ror = ((4 - (r&3))*8) & 0x1f;
EOP_AND_IMM(1,reg,ror/2,0xff); // and r1, r{7,8}, <mask>
if (r >= 4)
EOP_ORR_IMM(1,1,((ror-8)&0x1f)/2,1); // orr r1, r1, 1<<shift
if (r&3) EOP_ADD_REG_LSR(1,7,1, (r&3)*8-1); // add r1, r7, r1, lsr #lsr
else EOP_ADD_REG_LSL(1,7,1,1);
EOP_STRH_SIMPLE(0,1); // strh r0, [r1]
hostreg_r[1] = -1;
}
tr_ptrr_mod(r, (op>>2) & 3, 0, 1);
}
}
/* read (rX) to r0. Trashes r1-r3. */
static void tr_rX_read(int r, int mod)
{
if ((r&3) == 3)
{
tr_bank_read(((r << 6) & 0x100) + mod); // direct addressing
}
else
{
if (known_regb & (1 << (r + 8))) {
tr_bank_read(((r << 6) & 0x100) | known_regs.r[r]);
} else {
int reg = (r < 4) ? 8 : 9;
int ror = ((4 - (r&3))*8) & 0x1f;
EOP_AND_IMM(1,reg,ror/2,0xff); // and r1, r{7,8}, <mask>
if (r >= 4)
EOP_ORR_IMM(1,1,((ror-8)&0x1f)/2,1); // orr r1, r1, 1<<shift
if (r&3) EOP_ADD_REG_LSR(1,7,1, (r&3)*8-1); // add r1, r7, r1, lsr #lsr
else EOP_ADD_REG_LSL(1,7,1,1);
EOP_LDRH_SIMPLE(0,1); // ldrh r0, [r1]
hostreg_r[0] = hostreg_r[1] = -1;
}
tr_ptrr_mod(r, mod, 1, 1);
}
}
/* read ((rX)) to r0. Trashes r1,r2. */
static void tr_rX_read2(int op)
{
int r = (op&3) | ((op>>6)&4); // src
if ((r&3) == 3) {
tr_bank_read((op&0x100) | ((op>>2)&3));
} else if (known_regb & (1 << (r+8))) {
tr_bank_read((op&0x100) | known_regs.r[r]);
} else {
int reg = (r < 4) ? 8 : 9;
int ror = ((4 - (r&3))*8) & 0x1f;
EOP_AND_IMM(1,reg,ror/2,0xff); // and r1, r{7,8}, <mask>
if (r >= 4)
EOP_ORR_IMM(1,1,((ror-8)&0x1f)/2,1); // orr r1, r1, 1<<shift
if (r&3) EOP_ADD_REG_LSR(1,7,1, (r&3)*8-1); // add r1, r7, r1, lsr #lsr
else EOP_ADD_REG_LSL(1,7,1,1);
EOP_LDRH_SIMPLE(0,1); // ldrh r0, [r1]
}
EOP_LDR_IMM(2,7,0x48c); // ptr_iram_rom
EOP_ADD_REG_LSL(2,2,0,1); // add r2, r2, r0, lsl #1
EOP_ADD_IMM(0,0,0,1); // add r0, r0, #1
if ((r&3) == 3) {
tr_bank_write((op&0x100) | ((op>>2)&3));
} else if (known_regb & (1 << (r+8))) {
tr_bank_write((op&0x100) | known_regs.r[r]);
} else {
EOP_STRH_SIMPLE(0,1); // strh r0, [r1]
hostreg_r[1] = -1;
}
EOP_LDRH_SIMPLE(0,2); // ldrh r0, [r2]
hostreg_r[0] = hostreg_r[2] = -1;
}
// check if AL is going to be used later in block
static int tr_predict_al_need(void)
{
int tmpv, tmpv2, op, pc = known_regs.gr[SSP_PC].h;
while (1)
{
op = PROGRAM(pc);
switch (op >> 9)
{
// ld d, s
case 0x00:
tmpv2 = (op >> 4) & 0xf; // dst
tmpv = op & 0xf; // src
if ((tmpv2 == SSP_A && tmpv == SSP_P) || tmpv2 == SSP_AL) // ld A, P; ld AL, *
return 0;
break;
// ld (ri), s
case 0x02:
// ld ri, s
case 0x0a:
// OP a, s
case 0x10: case 0x30: case 0x40: case 0x60: case 0x70:
tmpv = op & 0xf; // src
if (tmpv == SSP_AL) // OP *, AL
return 1;
break;
case 0x04:
case 0x06:
case 0x14:
case 0x34:
case 0x44:
case 0x64:
case 0x74: pc++; break;
// call cond, addr
case 0x24:
// bra cond, addr
case 0x26:
// mod cond, op
case 0x48:
// mpys?
case 0x1b:
// mpya (rj), (ri), b
case 0x4b: return 1;
// mld (rj), (ri), b
case 0x5b: return 0; // cleared anyway
// and A, *
case 0x50:
tmpv = op & 0xf; // src
if (tmpv == SSP_AL) return 1;
case 0x51: case 0x53: case 0x54: case 0x55: case 0x59: case 0x5c:
return 0;
}
pc++;
}
}
/* get ARM cond which would mean that SSP cond is satisfied. No trash. */
static int tr_cond_check(int op)
{
int f = (op & 0x100) >> 8;
switch (op&0xf0) {
case 0x00: return A_COND_AL; /* always true */
case 0x50: /* Z matches f(?) bit */
if (dirty_regb & KRREG_ST) return f ? A_COND_EQ : A_COND_NE;
EOP_TST_IMM(6, 0, 4);
return f ? A_COND_NE : A_COND_EQ;
case 0x70: /* N matches f(?) bit */
if (dirty_regb & KRREG_ST) return f ? A_COND_MI : A_COND_PL;
EOP_TST_IMM(6, 0, 8);
return f ? A_COND_NE : A_COND_EQ;
default:
elprintf(EL_ANOMALY, "unimplemented cond?\n");
tr_unhandled();
return 0;
}
}
static int tr_neg_cond(int cond)
{
switch (cond) {
case A_COND_AL: elprintf(EL_ANOMALY, "neg for AL?\n"); exit(1);
case A_COND_EQ: return A_COND_NE;
case A_COND_NE: return A_COND_EQ;
case A_COND_MI: return A_COND_PL;
case A_COND_PL: return A_COND_MI;
default: elprintf(EL_ANOMALY, "bad cond for neg\n"); exit(1);
}
return 0;
}
static int tr_aop_ssp2arm(int op)
{
switch (op) {
case 1: return A_OP_SUB;
case 3: return A_OP_CMP;
case 4: return A_OP_ADD;
case 5: return A_OP_AND;
case 6: return A_OP_ORR;
case 7: return A_OP_EOR;
}
tr_unhandled();
return 0;
}
// -----------------------------------------------------
//@ r4: XXYY
//@ r5: A
//@ r6: STACK and emu flags
//@ r7: SSP context
//@ r10: P
// read general reg to r0. Trashes r1
static void tr_GR0_to_r0(int op)
{
tr_mov16(0, 0xffff);
}
static void tr_X_to_r0(int op)
{
if (hostreg_r[0] != (SSP_X<<16)) {
EOP_MOV_REG_LSR(0, 4, 16); // mov r0, r4, lsr #16
hostreg_r[0] = SSP_X<<16;
}
}
static void tr_Y_to_r0(int op)
{
if (hostreg_r[0] != (SSP_Y<<16)) {
EOP_MOV_REG_SIMPLE(0, 4); // mov r0, r4
hostreg_r[0] = SSP_Y<<16;
}
}
static void tr_A_to_r0(int op)
{
if (hostreg_r[0] != (SSP_A<<16)) {
EOP_MOV_REG_LSR(0, 5, 16); // mov r0, r5, lsr #16 @ AH
hostreg_r[0] = SSP_A<<16;
}
}
static void tr_ST_to_r0(int op)
{
// VR doesn't need much accuracy here..
EOP_MOV_REG_LSR(0, 6, 4); // mov r0, r6, lsr #4
EOP_AND_IMM(0, 0, 0, 0x67); // and r0, r0, #0x67
hostreg_r[0] = -1;
}
static void tr_STACK_to_r0(int op)
{
// 448
EOP_SUB_IMM(6, 6, 8/2, 0x20); // sub r6, r6, #1<<29
EOP_ADD_IMM(1, 7, 24/2, 0x04); // add r1, r7, 0x400
EOP_ADD_IMM(1, 1, 0, 0x48); // add r1, r1, 0x048
EOP_ADD_REG_LSR(1, 1, 6, 28); // add r1, r1, r6, lsr #28
EOP_LDRH_SIMPLE(0, 1); // ldrh r0, [r1]
hostreg_r[0] = hostreg_r[1] = -1;
}
static void tr_PC_to_r0(int op)
{
tr_mov16(0, known_regs.gr[SSP_PC].h);
}
static void tr_P_to_r0(int op)
{
tr_flush_dirty_P();
EOP_MOV_REG_LSR(0, 10, 16); // mov r0, r10, lsr #16
hostreg_r[0] = -1;
}
static void tr_AL_to_r0(int op)
{
if (op == 0x000f) {
if (known_regb & KRREG_PMC) {
known_regs.emu_status &= ~(SSP_PMC_SET|SSP_PMC_HAVE_ADDR);
} else {
EOP_LDR_IMM(0,7,0x484); // ldr r1, [r7, #0x484] // emu_status
EOP_BIC_IMM(0,0,0,SSP_PMC_SET|SSP_PMC_HAVE_ADDR);
EOP_STR_IMM(0,7,0x484);
}
}
if (hostreg_r[0] != (SSP_AL<<16)) {
EOP_MOV_REG_SIMPLE(0, 5); // mov r0, r5
hostreg_r[0] = SSP_AL<<16;
}
}
static void tr_PMX_to_r0(int reg)
{
if ((known_regb & KRREG_PMC) && (known_regs.emu_status & SSP_PMC_SET))
{
known_regs.pmac_read[reg] = known_regs.pmc.v;
known_regs.emu_status &= ~SSP_PMC_SET;
known_regb |= 1 << (20+reg);
dirty_regb |= 1 << (20+reg);
return;
}
if ((known_regb & KRREG_PMC) && (known_regb & (1 << (20+reg))))
{
u32 pmcv = known_regs.pmac_read[reg];
int mode = pmcv>>16;
known_regs.emu_status &= ~SSP_PMC_HAVE_ADDR;
if ((mode & 0xfff0) == 0x0800)
{
EOP_LDR_IMM(1,7,0x488); // rom_ptr
emith_move_r_imm(0, (pmcv&0xfffff)<<1);
EOP_LDRH_REG(0,1,0); // ldrh r0, [r1, r0]
known_regs.pmac_read[reg] += 1;
}
else if ((mode & 0x47ff) == 0x0018) // DRAM
{
int inc = get_inc(mode);
EOP_LDR_IMM(1,7,0x490); // dram_ptr
emith_move_r_imm(0, (pmcv&0xffff)<<1);
EOP_LDRH_REG(0,1,0); // ldrh r0, [r1, r0]
if (reg == 4 && (pmcv == 0x187f03 || pmcv == 0x187f04)) // wait loop detection
{
int flag = (pmcv == 0x187f03) ? SSP_WAIT_30FE06 : SSP_WAIT_30FE08;
tr_flush_dirty_ST();
EOP_LDR_IMM(1,7,0x484); // ldr r1, [r7, #0x484] // emu_status
EOP_TST_REG_SIMPLE(0,0);
EOP_C_DOP_IMM(A_COND_EQ,A_OP_SUB,0,11,11,22/2,1); // subeq r11, r11, #1024
EOP_C_DOP_IMM(A_COND_EQ,A_OP_ORR,0, 1, 1,24/2,flag>>8); // orreq r1, r1, #SSP_WAIT_30FE08
EOP_STR_IMM(1,7,0x484); // str r1, [r7, #0x484] // emu_status
}
known_regs.pmac_read[reg] += inc;
}
else
{
tr_unhandled();
}
known_regs.pmc.v = known_regs.pmac_read[reg];
//known_regb |= KRREG_PMC;
dirty_regb |= KRREG_PMC;
dirty_regb |= 1 << (20+reg);
hostreg_r[0] = hostreg_r[1] = -1;
return;
}
known_regb &= ~KRREG_PMC;
dirty_regb &= ~KRREG_PMC;
known_regb &= ~(1 << (20+reg));
dirty_regb &= ~(1 << (20+reg));
// call the C code to handle this
tr_flush_dirty_ST();
//tr_flush_dirty_pmcrs();
tr_mov16(0, reg);
emith_call(ssp_pm_read);
hostreg_clear();
}
static void tr_PM0_to_r0(int op)
{
tr_PMX_to_r0(0);
}
static void tr_PM1_to_r0(int op)
{
tr_PMX_to_r0(1);
}
static void tr_PM2_to_r0(int op)
{
tr_PMX_to_r0(2);
}
static void tr_XST_to_r0(int op)
{
EOP_ADD_IMM(0, 7, 24/2, 4); // add r0, r7, #0x400
EOP_LDRH_IMM(0, 0, SSP_XST*4+2);
}
static void tr_PM4_to_r0(int op)
{
tr_PMX_to_r0(4);
}
static void tr_PMC_to_r0(int op)
{
if (known_regb & KRREG_PMC)
{
if (known_regs.emu_status & SSP_PMC_HAVE_ADDR) {
known_regs.emu_status |= SSP_PMC_SET;
known_regs.emu_status &= ~SSP_PMC_HAVE_ADDR;
// do nothing - this is handled elsewhere
} else {
tr_mov16(0, known_regs.pmc.l);
known_regs.emu_status |= SSP_PMC_HAVE_ADDR;
}
}
else
{
EOP_LDR_IMM(1,7,0x484); // ldr r1, [r7, #0x484] // emu_status
tr_flush_dirty_ST();
if (op != 0x000e)
EOP_LDR_IMM(0, 7, 0x400+SSP_PMC*4);
EOP_TST_IMM(1, 0, SSP_PMC_HAVE_ADDR);
EOP_C_DOP_IMM(A_COND_EQ,A_OP_ORR,0, 1, 1, 0, SSP_PMC_HAVE_ADDR); // orreq r1, r1, #..
EOP_C_DOP_IMM(A_COND_NE,A_OP_BIC,0, 1, 1, 0, SSP_PMC_HAVE_ADDR); // bicne r1, r1, #..
EOP_C_DOP_IMM(A_COND_NE,A_OP_ORR,0, 1, 1, 0, SSP_PMC_SET); // orrne r1, r1, #..
EOP_STR_IMM(1,7,0x484);
hostreg_r[0] = hostreg_r[1] = -1;
}
}
typedef void (tr_read_func)(int op);
static tr_read_func *tr_read_funcs[16] =
{
tr_GR0_to_r0,
tr_X_to_r0,
tr_Y_to_r0,
tr_A_to_r0,
tr_ST_to_r0,
tr_STACK_to_r0,
tr_PC_to_r0,
tr_P_to_r0,
tr_PM0_to_r0,
tr_PM1_to_r0,
tr_PM2_to_r0,
tr_XST_to_r0,
tr_PM4_to_r0,
(tr_read_func *)tr_unhandled,
tr_PMC_to_r0,
tr_AL_to_r0
};
// write r0 to general reg handlers. Trashes r1
#define TR_WRITE_R0_TO_REG(reg) \
{ \
hostreg_sspreg_changed(reg); \
hostreg_r[0] = (reg)<<16; \
if (const_val != -1) { \
known_regs.gr[reg].h = const_val; \
known_regb |= 1 << (reg); \
} else { \
known_regb &= ~(1 << (reg)); \
} \
}
static void tr_r0_to_GR0(int const_val)
{
// do nothing
}
static void tr_r0_to_X(int const_val)
{
EOP_MOV_REG_LSL(4, 4, 16); // mov r4, r4, lsl #16
EOP_MOV_REG_LSR(4, 4, 16); // mov r4, r4, lsr #16
EOP_ORR_REG_LSL(4, 4, 0, 16); // orr r4, r4, r0, lsl #16
dirty_regb |= KRREG_P; // touching X or Y makes P dirty.
TR_WRITE_R0_TO_REG(SSP_X);
}
static void tr_r0_to_Y(int const_val)
{
EOP_MOV_REG_LSR(4, 4, 16); // mov r4, r4, lsr #16
EOP_ORR_REG_LSL(4, 4, 0, 16); // orr r4, r4, r0, lsl #16
EOP_MOV_REG_ROR(4, 4, 16); // mov r4, r4, ror #16
dirty_regb |= KRREG_P;
TR_WRITE_R0_TO_REG(SSP_Y);
}
static void tr_r0_to_A(int const_val)
{
if (tr_predict_al_need()) {
EOP_MOV_REG_LSL(5, 5, 16); // mov r5, r5, lsl #16
EOP_MOV_REG_LSR(5, 5, 16); // mov r5, r5, lsr #16 @ AL
EOP_ORR_REG_LSL(5, 5, 0, 16); // orr r5, r5, r0, lsl #16
}
else
EOP_MOV_REG_LSL(5, 0, 16);
TR_WRITE_R0_TO_REG(SSP_A);
}
static void tr_r0_to_ST(int const_val)
{
// VR doesn't need much accuracy here..
EOP_AND_IMM(1, 0, 0, 0x67); // and r1, r0, #0x67
EOP_AND_IMM(6, 6, 8/2, 0xe0); // and r6, r6, #7<<29 @ preserve STACK
EOP_ORR_REG_LSL(6, 6, 1, 4); // orr r6, r6, r1, lsl #4
TR_WRITE_R0_TO_REG(SSP_ST);
hostreg_r[1] = -1;
dirty_regb &= ~KRREG_ST;
}
static void tr_r0_to_STACK(int const_val)
{
// 448
EOP_ADD_IMM(1, 7, 24/2, 0x04); // add r1, r7, 0x400
EOP_ADD_IMM(1, 1, 0, 0x48); // add r1, r1, 0x048
EOP_ADD_REG_LSR(1, 1, 6, 28); // add r1, r1, r6, lsr #28
EOP_STRH_SIMPLE(0, 1); // strh r0, [r1]
EOP_ADD_IMM(6, 6, 8/2, 0x20); // add r6, r6, #1<<29
hostreg_r[1] = -1;
}
static void tr_r0_to_PC(int const_val)
{
/*
* do nothing - dispatcher will take care of this
EOP_MOV_REG_LSL(1, 0, 16); // mov r1, r0, lsl #16
EOP_STR_IMM(1,7,0x400+6*4); // str r1, [r7, #(0x400+6*8)]
hostreg_r[1] = -1;
*/
}
static void tr_r0_to_AL(int const_val)
{
EOP_MOV_REG_LSR(5, 5, 16); // mov r5, r5, lsr #16
EOP_ORR_REG_LSL(5, 5, 0, 16); // orr r5, r5, r0, lsl #16
EOP_MOV_REG_ROR(5, 5, 16); // mov r5, r5, ror #16
hostreg_sspreg_changed(SSP_AL);
if (const_val != -1) {
known_regs.gr[SSP_A].l = const_val;
known_regb |= 1 << SSP_AL;
} else
known_regb &= ~(1 << SSP_AL);
}
static void tr_r0_to_PMX(int reg)
{
if ((known_regb & KRREG_PMC) && (known_regs.emu_status & SSP_PMC_SET))
{
known_regs.pmac_write[reg] = known_regs.pmc.v;
known_regs.emu_status &= ~SSP_PMC_SET;
known_regb |= 1 << (25+reg);
dirty_regb |= 1 << (25+reg);
return;
}
if ((known_regb & KRREG_PMC) && (known_regb & (1 << (25+reg))))
{
int mode, addr;
known_regs.emu_status &= ~SSP_PMC_HAVE_ADDR;
mode = known_regs.pmac_write[reg]>>16;
addr = known_regs.pmac_write[reg]&0xffff;
if ((mode & 0x43ff) == 0x0018) // DRAM
{
int inc = get_inc(mode);
if (mode & 0x0400) tr_unhandled();
EOP_LDR_IMM(1,7,0x490); // dram_ptr
emith_move_r_imm(2, addr << 1);
EOP_STRH_REG(0,1,2); // strh r0, [r1, r2]
known_regs.pmac_write[reg] += inc;
}
else if ((mode & 0xfbff) == 0x4018) // DRAM, cell inc
{
if (mode & 0x0400) tr_unhandled();
EOP_LDR_IMM(1,7,0x490); // dram_ptr
emith_move_r_imm(2, addr << 1);
EOP_STRH_REG(0,1,2); // strh r0, [r1, r2]
known_regs.pmac_write[reg] += (addr&1) ? 31 : 1;
}
else if ((mode & 0x47ff) == 0x001c) // IRAM
{
int inc = get_inc(mode);
EOP_LDR_IMM(1,7,0x48c); // iram_ptr
emith_move_r_imm(2, (addr&0x3ff) << 1);
EOP_STRH_REG(0,1,2); // strh r0, [r1, r2]
EOP_MOV_IMM(1,0,1);
EOP_STR_IMM(1,7,0x494); // iram_dirty
known_regs.pmac_write[reg] += inc;
}
else
tr_unhandled();
known_regs.pmc.v = known_regs.pmac_write[reg];
//known_regb |= KRREG_PMC;
dirty_regb |= KRREG_PMC;
dirty_regb |= 1 << (25+reg);
hostreg_r[1] = hostreg_r[2] = -1;
return;
}
known_regb &= ~KRREG_PMC;
dirty_regb &= ~KRREG_PMC;
known_regb &= ~(1 << (25+reg));
dirty_regb &= ~(1 << (25+reg));
// call the C code to handle this
tr_flush_dirty_ST();
//tr_flush_dirty_pmcrs();
tr_mov16(1, reg);
emith_call(ssp_pm_write);
hostreg_clear();
}
static void tr_r0_to_PM0(int const_val)
{
tr_r0_to_PMX(0);
}
static void tr_r0_to_PM1(int const_val)
{
tr_r0_to_PMX(1);
}
static void tr_r0_to_PM2(int const_val)
{
tr_r0_to_PMX(2);
}
static void tr_r0_to_PM4(int const_val)
{
tr_r0_to_PMX(4);
}
static void tr_r0_to_PMC(int const_val)
{
if ((known_regb & KRREG_PMC) && const_val != -1)
{
if (known_regs.emu_status & SSP_PMC_HAVE_ADDR) {
known_regs.emu_status |= SSP_PMC_SET;
known_regs.emu_status &= ~SSP_PMC_HAVE_ADDR;
known_regs.pmc.h = const_val;
} else {
known_regs.emu_status |= SSP_PMC_HAVE_ADDR;
known_regs.pmc.l = const_val;
}
}
else
{
tr_flush_dirty_ST();
if (known_regb & KRREG_PMC) {
emith_move_r_imm(1, known_regs.pmc.v);
EOP_STR_IMM(1,7,0x400+SSP_PMC*4);
known_regb &= ~KRREG_PMC;
dirty_regb &= ~KRREG_PMC;
}
EOP_LDR_IMM(1,7,0x484); // ldr r1, [r7, #0x484] // emu_status
EOP_ADD_IMM(2,7,24/2,4); // add r2, r7, #0x400
EOP_TST_IMM(1, 0, SSP_PMC_HAVE_ADDR);
EOP_C_AM3_IMM(A_COND_EQ,1,0,2,0,0,1,SSP_PMC*4); // strxx r0, [r2, #SSP_PMC]
EOP_C_AM3_IMM(A_COND_NE,1,0,2,0,0,1,SSP_PMC*4+2);
EOP_C_DOP_IMM(A_COND_EQ,A_OP_ORR,0, 1, 1, 0, SSP_PMC_HAVE_ADDR); // orreq r1, r1, #..
EOP_C_DOP_IMM(A_COND_NE,A_OP_BIC,0, 1, 1, 0, SSP_PMC_HAVE_ADDR); // bicne r1, r1, #..
EOP_C_DOP_IMM(A_COND_NE,A_OP_ORR,0, 1, 1, 0, SSP_PMC_SET); // orrne r1, r1, #..
EOP_STR_IMM(1,7,0x484);
hostreg_r[1] = hostreg_r[2] = -1;
}
}
typedef void (tr_write_func)(int const_val);
static tr_write_func *tr_write_funcs[16] =
{
tr_r0_to_GR0,
tr_r0_to_X,
tr_r0_to_Y,
tr_r0_to_A,
tr_r0_to_ST,
tr_r0_to_STACK,
tr_r0_to_PC,
(tr_write_func *)tr_unhandled,
tr_r0_to_PM0,
tr_r0_to_PM1,
tr_r0_to_PM2,
(tr_write_func *)tr_unhandled,
tr_r0_to_PM4,
(tr_write_func *)tr_unhandled,
tr_r0_to_PMC,
tr_r0_to_AL
};
static void tr_mac_load_XY(int op)
{
tr_rX_read(op&3, (op>>2)&3); // X
EOP_MOV_REG_LSL(4, 0, 16);
tr_rX_read(((op>>4)&3)|4, (op>>6)&3); // Y
EOP_ORR_REG_SIMPLE(4, 0);
dirty_regb |= KRREG_P;
hostreg_sspreg_changed(SSP_X);
hostreg_sspreg_changed(SSP_Y);
known_regb &= ~KRREG_X;
known_regb &= ~KRREG_Y;
}
// -----------------------------------------------------
static int tr_detect_set_pm(unsigned int op, int *pc, int imm)
{
u32 pmcv, tmpv;
if (!((op&0xfef0) == 0x08e0 && (PROGRAM(*pc)&0xfef0) == 0x08e0)) return 0;
// programming PMC:
// ldi PMC, imm1
// ldi PMC, imm2
(*pc)++;
pmcv = imm | (PROGRAM((*pc)++) << 16);
known_regs.pmc.v = pmcv;
known_regb |= KRREG_PMC;
dirty_regb |= KRREG_PMC;
known_regs.emu_status |= SSP_PMC_SET;
n_in_ops++;
// check for possible reg programming
tmpv = PROGRAM(*pc);
if ((tmpv & 0xfff8) == 0x08 || (tmpv & 0xff8f) == 0x80)
{
int is_write = (tmpv & 0xff8f) == 0x80;
int reg = is_write ? ((tmpv>>4)&0x7) : (tmpv&0x7);
if (reg > 4) tr_unhandled();
if ((tmpv & 0x0f) != 0 && (tmpv & 0xf0) != 0) tr_unhandled();
if (is_write)
known_regs.pmac_write[reg] = pmcv;
else
known_regs.pmac_read[reg] = pmcv;
known_regb |= is_write ? (1 << (reg+25)) : (1 << (reg+20));
dirty_regb |= is_write ? (1 << (reg+25)) : (1 << (reg+20));
known_regs.emu_status &= ~SSP_PMC_SET;
(*pc)++;
n_in_ops++;
return 5;
}
tr_unhandled();
return 4;
}
static const short pm0_block_seq[] = { 0x0880, 0, 0x0880, 0, 0x0840, 0x60 };
static int tr_detect_pm0_block(unsigned int op, int *pc, int imm)
{
// ldi ST, 0
// ldi PM0, 0
// ldi PM0, 0
// ldi ST, 60h
unsigned short *pp;
if (op != 0x0840 || imm != 0) return 0;
pp = PROGRAM_P(*pc);
if (memcmp(pp, pm0_block_seq, sizeof(pm0_block_seq)) != 0) return 0;
EOP_AND_IMM(6, 6, 8/2, 0xe0); // and r6, r6, #7<<29 @ preserve STACK
EOP_ORR_IMM(6, 6, 24/2, 6); // orr r6, r6, 0x600
hostreg_sspreg_changed(SSP_ST);
known_regs.gr[SSP_ST].h = 0x60;
known_regb |= 1 << SSP_ST;
dirty_regb &= ~KRREG_ST;
(*pc) += 3*2;
n_in_ops += 3;
return 4*2;
}
static int tr_detect_rotate(unsigned int op, int *pc, int imm)
{
// @ 3DA2 and 426A
// ld PMC, (r3|00)
// ld (r3|00), PMC
// ld -, AL
if (op != 0x02e3 || PROGRAM(*pc) != 0x04e3 || PROGRAM(*pc + 1) != 0x000f) return 0;
tr_bank_read(0);
EOP_MOV_REG_LSL(0, 0, 4);
EOP_ORR_REG_LSR(0, 0, 0, 16);
tr_bank_write(0);
(*pc) += 2;
n_in_ops += 2;
return 3;
}
// -----------------------------------------------------
static int translate_op(unsigned int op, int *pc, int imm, int *end_cond, int *jump_pc)
{
u32 tmpv, tmpv2, tmpv3;
int ret = 0;
known_regs.gr[SSP_PC].h = *pc;
switch (op >> 9)
{
// ld d, s
case 0x00:
if (op == 0) { ret++; break; } // nop
tmpv = op & 0xf; // src
tmpv2 = (op >> 4) & 0xf; // dst
if (tmpv2 == SSP_A && tmpv == SSP_P) { // ld A, P
tr_flush_dirty_P();
EOP_MOV_REG_SIMPLE(5, 10);
hostreg_sspreg_changed(SSP_A);
known_regb &= ~(KRREG_A|KRREG_AL);
ret++; break;
}
tr_read_funcs[tmpv](op);
tr_write_funcs[tmpv2]((known_regb & (1 << tmpv)) ? known_regs.gr[tmpv].h : -1);
if (tmpv2 == SSP_PC) {
ret |= 0x10000;
*end_cond = -A_COND_AL;
}
ret++; break;
// ld d, (ri)
case 0x01: {
int r = (op&3) | ((op>>6)&4);
int mod = (op>>2)&3;
tmpv = (op >> 4) & 0xf; // dst
ret = tr_detect_rotate(op, pc, imm);
if (ret > 0) break;
if (tmpv != 0)
tr_rX_read(r, mod);
else {
int cnt = 1;
while (PROGRAM(*pc) == op) {
(*pc)++; cnt++; ret++;
n_in_ops++;
}
tr_ptrr_mod(r, mod, 1, cnt); // skip
}
tr_write_funcs[tmpv](-1);
if (tmpv == SSP_PC) {
ret |= 0x10000;
*end_cond = -A_COND_AL;
}
ret++; break;
}
// ld (ri), s
case 0x02:
tmpv = (op >> 4) & 0xf; // src
tr_read_funcs[tmpv](op);
tr_rX_write(op);
ret++; break;
// ld a, adr
case 0x03:
tr_bank_read(op&0x1ff);
tr_r0_to_A(-1);
ret++; break;
// ldi d, imm
case 0x04:
tmpv = (op & 0xf0) >> 4; // dst
ret = tr_detect_pm0_block(op, pc, imm);
if (ret > 0) break;
ret = tr_detect_set_pm(op, pc, imm);
if (ret > 0) break;
tr_mov16(0, imm);
tr_write_funcs[tmpv](imm);
if (tmpv == SSP_PC) {
ret |= 0x10000;
*jump_pc = imm;
}
ret += 2; break;
// ld d, ((ri))
case 0x05:
tmpv2 = (op >> 4) & 0xf; // dst
tr_rX_read2(op);
tr_write_funcs[tmpv2](-1);
if (tmpv2 == SSP_PC) {
ret |= 0x10000;
*end_cond = -A_COND_AL;
}
ret += 3; break;
// ldi (ri), imm
case 0x06:
tr_mov16(0, imm);
tr_rX_write(op);
ret += 2; break;
// ld adr, a
case 0x07:
tr_A_to_r0(op);
tr_bank_write(op&0x1ff);
ret++; break;
// ld d, ri
case 0x09: {
int r;
r = (op&3) | ((op>>6)&4); // src
tmpv2 = (op >> 4) & 0xf; // dst
if ((r&3) == 3) tr_unhandled();
if (known_regb & (1 << (r+8))) {
tr_mov16(0, known_regs.r[r]);
tr_write_funcs[tmpv2](known_regs.r[r]);
} else {
int reg = (r < 4) ? 8 : 9;
if (r&3) EOP_MOV_REG_LSR(0, reg, (r&3)*8); // mov r0, r{7,8}, lsr #lsr
EOP_AND_IMM(0, (r&3)?0:reg, 0, 0xff); // and r0, r{7,8}, <mask>
hostreg_r[0] = -1;
tr_write_funcs[tmpv2](-1);
}
ret++; break;
}
// ld ri, s
case 0x0a: {
int r;
r = (op&3) | ((op>>6)&4); // dst
tmpv = (op >> 4) & 0xf; // src
if ((r&3) == 3) tr_unhandled();
if (known_regb & (1 << tmpv)) {
known_regs.r[r] = known_regs.gr[tmpv].h;
known_regb |= 1 << (r + 8);
dirty_regb |= 1 << (r + 8);
} else {
int reg = (r < 4) ? 8 : 9;
int ror = ((4 - (r&3))*8) & 0x1f;
tr_read_funcs[tmpv](op);
EOP_BIC_IMM(reg, reg, ror/2, 0xff); // bic r{7,8}, r{7,8}, <mask>
EOP_AND_IMM(0, 0, 0, 0xff); // and r0, r0, 0xff
EOP_ORR_REG_LSL(reg, reg, 0, (r&3)*8); // orr r{7,8}, r{7,8}, r0, lsl #lsl
hostreg_r[0] = -1;
known_regb &= ~(1 << (r+8));
dirty_regb &= ~(1 << (r+8));
}
ret++; break;
}
// ldi ri, simm
case 0x0c: case 0x0d: case 0x0e: case 0x0f:
tmpv = (op>>8)&7;
known_regs.r[tmpv] = op;
known_regb |= 1 << (tmpv + 8);
dirty_regb |= 1 << (tmpv + 8);
ret++; break;
// call cond, addr
case 0x24: {
u32 *jump_op = NULL;
tmpv = tr_cond_check(op);
if (tmpv != A_COND_AL) {
jump_op = tcache_ptr;
EOP_MOV_IMM(0, 0, 0); // placeholder for branch
}
tr_mov16(0, *pc);
tr_r0_to_STACK(*pc);
if (tmpv != A_COND_AL) {
u32 *real_ptr = tcache_ptr;
tcache_ptr = jump_op;
EOP_C_B(tr_neg_cond(tmpv),0,real_ptr - jump_op - 2);
tcache_ptr = real_ptr;
}
tr_mov16_cond(tmpv, 0, imm);
if (tmpv != A_COND_AL)
tr_mov16_cond(tr_neg_cond(tmpv), 0, *pc);
tr_r0_to_PC(tmpv == A_COND_AL ? imm : -1);
ret |= 0x10000;
*end_cond = tmpv;
*jump_pc = imm;
ret += 2; break;
}
// ld d, (a)
case 0x25:
tmpv2 = (op >> 4) & 0xf; // dst
tr_A_to_r0(op);
EOP_LDR_IMM(1,7,0x48c); // ptr_iram_rom
EOP_ADD_REG_LSL(0,1,0,1); // add r0, r1, r0, lsl #1
EOP_LDRH_SIMPLE(0,0); // ldrh r0, [r0]
hostreg_r[0] = hostreg_r[1] = -1;
tr_write_funcs[tmpv2](-1);
if (tmpv2 == SSP_PC) {
ret |= 0x10000;
*end_cond = -A_COND_AL;
}
ret += 3; break;
// bra cond, addr
case 0x26:
tmpv = tr_cond_check(op);
tr_mov16_cond(tmpv, 0, imm);
if (tmpv != A_COND_AL)
tr_mov16_cond(tr_neg_cond(tmpv), 0, *pc);
tr_r0_to_PC(tmpv == A_COND_AL ? imm : -1);
ret |= 0x10000;
*end_cond = tmpv;
*jump_pc = imm;
ret += 2; break;
// mod cond, op
case 0x48: {
// check for repeats of this op
tmpv = 1; // count
while (PROGRAM(*pc) == op && (op & 7) != 6) {
(*pc)++; tmpv++;
n_in_ops++;
}
if ((op&0xf0) != 0) // !always
tr_make_dirty_ST();
tmpv2 = tr_cond_check(op);
switch (op & 7) {
case 2: EOP_C_DOP_REG_XIMM(tmpv2,A_OP_MOV,1,0,5,tmpv,A_AM1_ASR,5); break; // shr (arithmetic)
case 3: EOP_C_DOP_REG_XIMM(tmpv2,A_OP_MOV,1,0,5,tmpv,A_AM1_LSL,5); break; // shl
case 6: EOP_C_DOP_IMM(tmpv2,A_OP_RSB,1,5,5,0,0); break; // neg
case 7: EOP_C_DOP_REG_XIMM(tmpv2,A_OP_EOR,0,5,1,31,A_AM1_ASR,5); // eor r1, r5, r5, asr #31
EOP_C_DOP_REG_XIMM(tmpv2,A_OP_ADD,1,1,5,31,A_AM1_LSR,5); // adds r5, r1, r5, lsr #31
hostreg_r[1] = -1; break; // abs
default: tr_unhandled();
}
hostreg_sspreg_changed(SSP_A);
dirty_regb |= KRREG_ST;
known_regb &= ~KRREG_ST;
known_regb &= ~(KRREG_A|KRREG_AL);
ret += tmpv; break;
}
// mpys?
case 0x1b:
tr_flush_dirty_P();
tr_mac_load_XY(op);
tr_make_dirty_ST();
EOP_C_DOP_REG_XIMM(A_COND_AL,A_OP_SUB,1,5,5,0,A_AM1_LSL,10); // subs r5, r5, r10
hostreg_sspreg_changed(SSP_A);
known_regb &= ~(KRREG_A|KRREG_AL);
dirty_regb |= KRREG_ST;
ret++; break;
// mpya (rj), (ri), b
case 0x4b:
tr_flush_dirty_P();
tr_mac_load_XY(op);
tr_make_dirty_ST();
EOP_C_DOP_REG_XIMM(A_COND_AL,A_OP_ADD,1,5,5,0,A_AM1_LSL,10); // adds r5, r5, r10
hostreg_sspreg_changed(SSP_A);
known_regb &= ~(KRREG_A|KRREG_AL);
dirty_regb |= KRREG_ST;
ret++; break;
// mld (rj), (ri), b
case 0x5b:
EOP_C_DOP_IMM(A_COND_AL,A_OP_MOV,1,0,5,0,0); // movs r5, #0
hostreg_sspreg_changed(SSP_A);
known_regs.gr[SSP_A].v = 0;
known_regb |= (KRREG_A|KRREG_AL);
dirty_regb |= KRREG_ST;
tr_mac_load_XY(op);
ret++; break;
// OP a, s
case 0x10:
case 0x30:
case 0x40:
case 0x50:
case 0x60:
case 0x70:
tmpv = op & 0xf; // src
tmpv2 = tr_aop_ssp2arm(op>>13); // op
tmpv3 = (tmpv2 == A_OP_CMP) ? 0 : 5;
if (tmpv == SSP_P) {
tr_flush_dirty_P();
EOP_C_DOP_REG_XIMM(A_COND_AL,tmpv2,1,5,tmpv3, 0,A_AM1_LSL,10); // OPs r5, r5, r10
} else if (tmpv == SSP_A) {
EOP_C_DOP_REG_XIMM(A_COND_AL,tmpv2,1,5,tmpv3, 0,A_AM1_LSL, 5); // OPs r5, r5, r5
} else {
tr_read_funcs[tmpv](op);
EOP_C_DOP_REG_XIMM(A_COND_AL,tmpv2,1,5,tmpv3,16,A_AM1_LSL, 0); // OPs r5, r5, r0, lsl #16
}
hostreg_sspreg_changed(SSP_A);
known_regb &= ~(KRREG_A|KRREG_AL|KRREG_ST);
dirty_regb |= KRREG_ST;
ret++; break;
// OP a, (ri)
case 0x11:
case 0x31:
case 0x41:
case 0x51:
case 0x61:
case 0x71:
tmpv2 = tr_aop_ssp2arm(op>>13); // op
tmpv3 = (tmpv2 == A_OP_CMP) ? 0 : 5;
tr_rX_read((op&3)|((op>>6)&4), (op>>2)&3);
EOP_C_DOP_REG_XIMM(A_COND_AL,tmpv2,1,5,tmpv3,16,A_AM1_LSL,0); // OPs r5, r5, r0, lsl #16
hostreg_sspreg_changed(SSP_A);
known_regb &= ~(KRREG_A|KRREG_AL|KRREG_ST);
dirty_regb |= KRREG_ST;
ret++; break;
// OP a, adr
case 0x13:
case 0x33:
case 0x43:
case 0x53:
case 0x63:
case 0x73:
tmpv2 = tr_aop_ssp2arm(op>>13); // op
tmpv3 = (tmpv2 == A_OP_CMP) ? 0 : 5;
tr_bank_read(op&0x1ff);
EOP_C_DOP_REG_XIMM(A_COND_AL,tmpv2,1,5,tmpv3,16,A_AM1_LSL,0); // OPs r5, r5, r0, lsl #16
hostreg_sspreg_changed(SSP_A);
known_regb &= ~(KRREG_A|KRREG_AL|KRREG_ST);
dirty_regb |= KRREG_ST;
ret++; break;
// OP a, imm
case 0x14:
case 0x34:
case 0x44:
case 0x54:
case 0x64:
case 0x74:
tmpv = (op & 0xf0) >> 4;
tmpv2 = tr_aop_ssp2arm(op>>13); // op
tmpv3 = (tmpv2 == A_OP_CMP) ? 0 : 5;
tr_mov16(0, imm);
EOP_C_DOP_REG_XIMM(A_COND_AL,tmpv2,1,5,tmpv3,16,A_AM1_LSL,0); // OPs r5, r5, r0, lsl #16
hostreg_sspreg_changed(SSP_A);
known_regb &= ~(KRREG_A|KRREG_AL|KRREG_ST);
dirty_regb |= KRREG_ST;
ret += 2; break;
// OP a, ((ri))
case 0x15:
case 0x35:
case 0x45:
case 0x55:
case 0x65:
case 0x75:
tmpv2 = tr_aop_ssp2arm(op>>13); // op
tmpv3 = (tmpv2 == A_OP_CMP) ? 0 : 5;
tr_rX_read2(op);
EOP_C_DOP_REG_XIMM(A_COND_AL,tmpv2,1,5,tmpv3,16,A_AM1_LSL,0); // OPs r5, r5, r0, lsl #16
hostreg_sspreg_changed(SSP_A);
known_regb &= ~(KRREG_A|KRREG_AL|KRREG_ST);
dirty_regb |= KRREG_ST;
ret += 3; break;
// OP a, ri
case 0x19:
case 0x39:
case 0x49:
case 0x59:
case 0x69:
case 0x79: {
int r;
tmpv2 = tr_aop_ssp2arm(op>>13); // op
tmpv3 = (tmpv2 == A_OP_CMP) ? 0 : 5;
r = (op&3) | ((op>>6)&4); // src
if ((r&3) == 3) tr_unhandled();
if (known_regb & (1 << (r+8))) {
EOP_C_DOP_IMM(A_COND_AL,tmpv2,1,5,tmpv3,16/2,known_regs.r[r]); // OPs r5, r5, #val<<16
} else {
int reg = (r < 4) ? 8 : 9;
if (r&3) EOP_MOV_REG_LSR(0, reg, (r&3)*8); // mov r0, r{7,8}, lsr #lsr
EOP_AND_IMM(0, (r&3)?0:reg, 0, 0xff); // and r0, r{7,8}, <mask>
EOP_C_DOP_REG_XIMM(A_COND_AL,tmpv2,1,5,tmpv3,16,A_AM1_LSL,0); // OPs r5, r5, r0, lsl #16
hostreg_r[0] = -1;
}
hostreg_sspreg_changed(SSP_A);
known_regb &= ~(KRREG_A|KRREG_AL|KRREG_ST);
dirty_regb |= KRREG_ST;
ret++; break;
}
// OP simm
case 0x1c:
case 0x3c:
case 0x4c:
case 0x5c:
case 0x6c:
case 0x7c:
tmpv2 = tr_aop_ssp2arm(op>>13); // op
tmpv3 = (tmpv2 == A_OP_CMP) ? 0 : 5;
EOP_C_DOP_IMM(A_COND_AL,tmpv2,1,5,tmpv3,16/2,op & 0xff); // OPs r5, r5, #val<<16
hostreg_sspreg_changed(SSP_A);
known_regb &= ~(KRREG_A|KRREG_AL|KRREG_ST);
dirty_regb |= KRREG_ST;
ret++; break;
}
n_in_ops++;
return ret;
}
static void emit_block_prologue(void)
{
// check if there are enough cycles..
// note: r0 must contain PC of current block
EOP_CMP_IMM(11,0,0); // cmp r11, #0
emith_jump_cond(A_COND_LE, ssp_drc_end);
}
/* cond:
* >0: direct (un)conditional jump
* <0: indirect jump
*/
static void emit_block_epilogue(int cycles, int cond, int pc, int end_pc)
{
if (cycles > 0xff) { elprintf(EL_ANOMALY, "large cycle count: %i\n", cycles); cycles = 0xff; }
EOP_SUB_IMM(11,11,0,cycles); // sub r11, r11, #cycles
if (cond < 0 || (end_pc >= 0x400 && pc < 0x400)) {
// indirect jump, or rom -> iram jump, must use dispatcher
emith_jump(ssp_drc_next);
}
else if (cond == A_COND_AL) {
u32 *target = (pc < 0x400) ?
ssp_block_table_iram[ssp->drc.iram_context * SSP_BLOCKTAB_IRAM_ONE + pc] :
ssp_block_table[pc];
if (target != NULL)
emith_jump(target);
else {
int ops = emith_jump(ssp_drc_next);
// cause the next block to be emitted over jump instruction
tcache_ptr -= ops;
}
}
else {
u32 *target1 = (pc < 0x400) ?
ssp_block_table_iram[ssp->drc.iram_context * SSP_BLOCKTAB_IRAM_ONE + pc] :
ssp_block_table[pc];
u32 *target2 = (end_pc < 0x400) ?
ssp_block_table_iram[ssp->drc.iram_context * SSP_BLOCKTAB_IRAM_ONE + end_pc] :
ssp_block_table[end_pc];
if (target1 != NULL)
emith_jump_cond(cond, target1);
if (target2 != NULL)
emith_jump_cond(tr_neg_cond(cond), target2); // neg_cond, to be able to swap jumps if needed
#ifndef __EPOC32__
// emit patchable branches
if (target1 == NULL)
emith_call_cond(cond, ssp_drc_next_patch);
if (target2 == NULL)
emith_call_cond(tr_neg_cond(cond), ssp_drc_next_patch);
#else
// won't patch indirect jumps
if (target1 == NULL || target2 == NULL)
emith_jump(ssp_drc_next);
#endif
}
}
void *ssp_translate_block(int pc)
{
unsigned int op, op1, imm, ccount = 0;
unsigned int *block_start;
int ret, end_cond = A_COND_AL, jump_pc = -1;
//printf("translate %04x -> %04x\n", pc<<1, (tcache_ptr-tcache)<<2);
block_start = tcache_ptr;
known_regb = 0;
dirty_regb = KRREG_P;
known_regs.emu_status = 0;
hostreg_clear();
emit_block_prologue();
for (; ccount < 100;)
{
op = PROGRAM(pc++);
op1 = op >> 9;
imm = (u32)-1;
if ((op1 & 0xf) == 4 || (op1 & 0xf) == 6)
imm = PROGRAM(pc++); // immediate
ret = translate_op(op, &pc, imm, &end_cond, &jump_pc);
if (ret <= 0)
{
elprintf(EL_ANOMALY, "NULL func! op=%08x (%02x)\n", op, op1);
//exit(1);
}
ccount += ret & 0xffff;
if (ret & 0x10000) break;
}
if (ccount >= 100) {
end_cond = A_COND_AL;
jump_pc = pc;
emith_move_r_imm(0, pc);
}
tr_flush_dirty_prs();
tr_flush_dirty_ST();
tr_flush_dirty_pmcrs();
emit_block_epilogue(ccount, end_cond, jump_pc, pc);
if (tcache_ptr - (u32 *)tcache > DRC_TCACHE_SIZE/4) {
elprintf(EL_ANOMALY|EL_STATUS|EL_SVP, "tcache overflow!\n");
fflush(stdout);
exit(1);
}
// stats
nblocks++;
//printf("%i blocks, %i bytes, k=%.3f\n", nblocks, (tcache_ptr - tcache)*4,
// (double)(tcache_ptr - tcache) / (double)n_in_ops);
#ifdef DUMP_BLOCK
{
FILE *f = fopen("tcache.bin", "wb");
fwrite(tcache, 1, (tcache_ptr - tcache)*4, f);
fclose(f);
}
printf("dumped tcache.bin\n");
exit(0);
#endif
#ifdef ARM
cache_flush_d_inval_i(tcache, tcache_ptr);
#endif
return block_start;
}
// -----------------------------------------------------
static void ssp1601_state_load(void)
{
ssp->drc.iram_dirty = 1;
ssp->drc.iram_context = 0;
}
void ssp1601_dyn_exit(void)
{
free(ssp_block_table);
free(ssp_block_table_iram);
ssp_block_table = ssp_block_table_iram = NULL;
drc_cmn_cleanup();
}
int ssp1601_dyn_startup(void)
{
drc_cmn_init();
ssp_block_table = calloc(sizeof(ssp_block_table[0]), SSP_BLOCKTAB_ENTS);
if (ssp_block_table == NULL)
return -1;
ssp_block_table_iram = calloc(sizeof(ssp_block_table_iram[0]), SSP_BLOCKTAB_IRAM_ENTS);
if (ssp_block_table_iram == NULL) {
free(ssp_block_table);
return -1;
}
memset(tcache, 0, DRC_TCACHE_SIZE);
tcache_ptr = (void *)tcache;
PicoLoadStateHook = ssp1601_state_load;
n_in_ops = 0;
#ifdef ARM
// hle'd blocks
ssp_block_table[0x800/2] = (void *) ssp_hle_800;
ssp_block_table[0x902/2] = (void *) ssp_hle_902;
ssp_block_table_iram[ 7 * SSP_BLOCKTAB_IRAM_ONE + 0x030/2] = (void *) ssp_hle_07_030;
ssp_block_table_iram[ 7 * SSP_BLOCKTAB_IRAM_ONE + 0x036/2] = (void *) ssp_hle_07_036;
ssp_block_table_iram[ 7 * SSP_BLOCKTAB_IRAM_ONE + 0x6d6/2] = (void *) ssp_hle_07_6d6;
ssp_block_table_iram[11 * SSP_BLOCKTAB_IRAM_ONE + 0x12c/2] = (void *) ssp_hle_11_12c;
ssp_block_table_iram[11 * SSP_BLOCKTAB_IRAM_ONE + 0x384/2] = (void *) ssp_hle_11_384;
ssp_block_table_iram[11 * SSP_BLOCKTAB_IRAM_ONE + 0x38a/2] = (void *) ssp_hle_11_38a;
#endif
return 0;
}
void ssp1601_dyn_reset(ssp1601_t *ssp)
{
ssp1601_reset(ssp);
ssp->drc.iram_dirty = 1;
ssp->drc.iram_context = 0;
// must do this here because ssp is not available @ startup()
ssp->drc.ptr_rom = (u32) Pico.rom;
ssp->drc.ptr_iram_rom = (u32) svp->iram_rom;
ssp->drc.ptr_dram = (u32) svp->dram;
ssp->drc.ptr_btable = (u32) ssp_block_table;
ssp->drc.ptr_btable_iram = (u32) ssp_block_table_iram;
// prevent new versions of IRAM from appearing
memset(svp->iram_rom, 0, 0x800);
}
void ssp1601_dyn_run(int cycles)
{
if (ssp->emu_status & SSP_WAIT_MASK) return;
#ifdef DUMP_BLOCK
ssp_translate_block(DUMP_BLOCK >> 1);
#endif
#ifdef ARM
ssp_drc_entry(cycles);
#endif
}