Skip to content

Commit

Permalink
cpu/powerpc: More support for the 601's POWER/PPC dual nature, includ…
Browse files Browse the repository at this point in the history
…ing several POWER instructions. [R. Belmont]

apple/macpdm.cpp: Implemented audio DMA IRQs and some minor cleanup. [R. Belmont]
  • Loading branch information
rb6502 committed Jan 5, 2025
1 parent 8a17e0e commit 1b54e2d
Show file tree
Hide file tree
Showing 6 changed files with 381 additions and 60 deletions.
11 changes: 10 additions & 1 deletion src/devices/cpu/powerpc/ppc.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,18 @@ enum
PPC_R30,
PPC_R31,
PPC_CR,
PPC_MQ,
PPC_LR,
PPC_CTR,
PPC_XER,
PPC_BAT0U,
PPC_BAT0L,
PPC_BAT1U,
PPC_BAT1L,
PPC_BAT2U,
PPC_BAT2L,
PPC_BAT3U,
PPC_BAT3L,

PPC_F0,
PPC_F1,
Expand Down Expand Up @@ -151,7 +160,6 @@ enum
PPC_SR15
};


/* compiler-specific options */
#define PPCDRC_STRICT_VERIFY 0x0001 /* verify all instructions */
#define PPCDRC_FLUSH_PC 0x0002 /* flush the PC value before each memory access */
Expand Down Expand Up @@ -515,6 +523,7 @@ class ppc_device : public cpu_device, public device_vtlb_interface
uint32_t m_serial_clock;
uint64_t m_tb_zero_cycles;
uint64_t m_dec_zero_cycles;
uint64_t m_rtc_zero_cycles;
emu_timer * m_decrementer_int_timer;


Expand Down
48 changes: 45 additions & 3 deletions src/devices/cpu/powerpc/ppccom.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ mpc8240_device::mpc8240_device(const machine_config &mconfig, const char *tag, d
}

ppc601_device::ppc601_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: ppc_device(mconfig, PPC601, tag, owner, clock, 32, 64, PPC_MODEL_601, PPCCAP_OEA | PPCCAP_VEA | PPCCAP_FPU | PPCCAP_MISALIGNED | PPCCAP_MFIOC | PPCCAP_601BAT, 0/* no TB */, address_map_constructor())
: ppc_device(mconfig, PPC601, tag, owner, clock, 32, 64, PPC_MODEL_601, PPCCAP_OEA | PPCCAP_VEA | PPCCAP_FPU | PPCCAP_MISALIGNED | PPCCAP_MFIOC | PPCCAP_601BAT | PPCCAP_LEGACY_POWER, 0 /* no TB */, address_map_constructor())
{
}

Expand Down Expand Up @@ -727,6 +727,7 @@ void ppc_device::device_start()
m_cpu_clock = 0;
m_tb_zero_cycles = 0;
m_dec_zero_cycles = 0;
m_rtc_zero_cycles = 0;

m_arg1 = 0;
m_fastram_select = 0;
Expand Down Expand Up @@ -837,6 +838,11 @@ void ppc_device::device_start()
state_add(PPC_PC, "PC", m_core->pc).formatstr("%08X");
state_add(PPC_MSR, "MSR", m_core->msr).formatstr("%08X");
state_add(PPC_CR, "CR", m_debugger_temp).callimport().callexport().formatstr("%08X");
// If the legacy POWER instructions exist, that implies MQ is used and should be shown
if (m_cap & PPCCAP_LEGACY_POWER)
{
state_add(PPC_MQ, "MQ", m_core->spr[SPR601_MQ]).formatstr("%08X");
}
state_add(PPC_LR, "LR", m_core->spr[SPR_LR]).formatstr("%08X");
state_add(PPC_CTR, "CTR", m_core->spr[SPR_CTR]).formatstr("%08X");
state_add(PPC_XER, "XER", m_debugger_temp).callimport().callexport().formatstr("%08X");
Expand Down Expand Up @@ -1301,8 +1307,6 @@ uint32_t ppc_device::ppccom_translate_address_internal(int intention, bool debug
uint32_t lower = m_core->spr[SPROEA_IBAT0U + 2*batnum + 1];
int privbit = ((intention & TR_USER) == 0) ? 3 : 2;

// printf("bat %d upper = %08x privbit %d\n", batnum, upper, privbit);

// is this pair valid?
if (lower & 0x40)
{
Expand Down Expand Up @@ -1639,6 +1643,29 @@ void ppc_device::ppccom_execute_mfspr()
}
}

/* handle 601 specific SPRs (POWER holdovers) */
if (m_flavor == PPC_MODEL_601)
{
switch (m_core->param0)
{
case SPR601_PWRDEC:
m_core->param1 = get_decrementer();
return;

case SPR601_RTCUR_PWR:
m_core->param1 = (total_cycles() - m_rtc_zero_cycles) / clock();
return;

case SPR601_RTCLR_PWR:
{
const uint64_t remainder = (total_cycles() - m_rtc_zero_cycles) % clock();
const double seconds = remainder / clock(); // get fractional seconds
m_core->param1 = (uint64_t)(seconds * 1'000'000'000); // and convert to nanoseconds

This comment has been minimized.

Copy link
@cuavas

cuavas Jan 5, 2025

Member

Does this actually work? It looks like it will always produce zero:

const uint64_t remainder = (total_cycles() - m_rtc_zero_cycles) % clock(); // remainder cannot be larger than clock()
const double seconds = remainder / clock(); // integer division will always result in zero as remainder is necessarily smaller than clock
m_core->param1 = (uint64_t)(seconds * 1'000'000'000); // multiplying zero by a billion produces zero

This comment has been minimized.

Copy link
@rb6502

rb6502 Jan 5, 2025

Author Contributor

Yeah, it's missing a cast to get it up to double. I just found a test case for it too, which is handy.

}
return;
}
}

/* handle 602 SPRs */
if (m_flavor == PPC_MODEL_602)
{ // TODO: Which are read/write only?
Expand Down Expand Up @@ -1782,6 +1809,21 @@ void ppc_device::ppccom_execute_mtspr()
}
}

/* handle 601 specific POWER-holdover SPRs */
if (m_flavor == PPC_MODEL_601)
{
switch (m_core->param0)
{
case SPR601_MQ:
m_core->spr[m_core->param0] = m_core->param1;
return;

case SPR601_RTCUW_PWR:
m_rtc_zero_cycles = total_cycles();
break;
}
}

/* handle 602 SPRs */
if (m_flavor == PPC_MODEL_602)
{
Expand Down
10 changes: 9 additions & 1 deletion src/devices/cpu/powerpc/ppccom.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
#define PPCCAP_601BAT 0x80 /* TRUE if we're doing 601-style BATs (unified I/D, different bit layout) */
#define PPCCAP_604_MMU 0x100 /* TRUE if we have 604-class MMU features */
#define PPCCAP_750_TLB 0x200 /* TRUE if we have the extended 740/750 series TLB */
#define PPCCAP_LEGACY_POWER 0x400 /* TRUE if we support the legacy POWER instructions */

/* exception types */
enum
Expand Down Expand Up @@ -171,6 +172,14 @@ enum
SPR4XX_PBL2 = 0x3fe, /* R/W 403GA 406GA Protection Bound Lower 2 */
SPR4XX_PBU2 = 0x3ff, /* R/W 403GA 406GA Protection Bound Upper 2 */

/* PowerPC 601 POWER back compatibility SPR indexes */
SPR601_MQ = 0x000, /* R/W Muliplicand/Quotient for 601 POWER instructions */
SPR601_RTCUR_PWR = 0x004, /* R Counts up number set in SPR 20 once per second, POWER only */
SPR601_RTCLR_PWR = 0x005, /* R Number of nanoseconds between the seconds counted in SPR 4 */
SPR601_PWRDEC = 0x006, /* R Decrementer register mirror for POWER compatibilty */
SPR601_RTCUW_PWR = 0x014, /* W Seconds counter, set here and read SPR 4 */
SPR601_RTCLW_PWR = 0x015, /* W Nanoseconds counter, not clear what writing here does */

/* PowerPC 602 SPR register indexes */
SPR602_TCR = 0x3d8, /* 602 */
SPR602_IBR = 0x3da, /* 602 */
Expand Down Expand Up @@ -198,7 +207,6 @@ enum
SPR603_HID2 = 0x3f3 /* R/W 603 */
};


/* PowerPC 4XX DCR register indexes */
enum
{
Expand Down
161 changes: 157 additions & 4 deletions src/devices/cpu/powerpc/ppcdrc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// copyright-holders:Aaron Giles
/***************************************************************************

ppcdrc.c
ppcdrc.cpp

Universal machine language-based PowerPC emulator.

Expand Down Expand Up @@ -1746,12 +1746,12 @@ void ppc_device::generate_sequence_instruction(drcuml_block &block, compiler_sta
UML_EXH(block, *m_tlb_mismatch, 0); // exh tlb_mismatch,0
}

/* validate our TLB entry at this PC; if we fail, we need to handle it */
if ((desc->flags & OPFLAG_VALIDATE_TLB) && (m_core->mode & MODE_DATA_TRANSLATION))
// validate our TLB entry at this PC; if we fail, we need to handle it
// TODO: this code is highly sus based on the PPC architecture manual, but I'll only disable for 601 for now
if ((desc->flags & OPFLAG_VALIDATE_TLB) && (m_core->mode & MODE_DATA_TRANSLATION) && !(m_cap & PPCCAP_601BAT))
{
const vtlb_entry *tlbtable = vtlb_table();

/* if we currently have a valid TLB read entry, we just verify */
if (tlbtable[desc->pc >> 12] != 0)
{
if (PRINTF_MMU)
Expand Down Expand Up @@ -2024,6 +2024,23 @@ bool ppc_device::generate_opcode(drcuml_block &block, compiler_state *compiler,
// muls rd,rd,ra,simm
return true;

case 0x9: /* DOZI (POWER) */
assert(m_cap & PPCCAP_LEGACY_POWER);

UML_AND(block, I0, op, 0xffff);
UML_CMP(block, R32(G_RA(op)), I0); // cmp ra, I0
UML_JMPc(block, COND_B, compiler->labelnum); // bae 0:

UML_XOR(block, R32(G_RD(op)), R32(G_RD(op)), R32(G_RD(op))); // xor rd, rd, rd (rd = 0)
UML_JMP(block, compiler->labelnum + 1); // jmp 1:

UML_LABEL(block, compiler->labelnum++); // 0:
UML_ADD(block, R32(G_RD(op)), R32(G_RA(op)), I0);
UML_ADD(block, R32(G_RD(op)), R32(G_RD(op)), 0x1);

UML_LABEL(block, compiler->labelnum++); // 1:
return true;

case 0x0e: /* ADDI */
UML_ADD(block, R32(G_RD(op)), R32Z(G_RA(op)), (int16_t)G_SIMM(op)); // add rd,ra,simm
return true;
Expand Down Expand Up @@ -2687,6 +2704,13 @@ bool ppc_device::generate_instruction_1f(drcuml_block &block, compiler_state *co
generate_compute_flags(block, desc, op & M_RC, ((op & M_OE) ? XER_OV : 0), false);// <update flags>
return true;

case 0x6b: /* MUL (POWER) */
assert(m_cap & PPCCAP_LEGACY_POWER);

UML_MULU(block, SPR32(SPR601_MQ), R32(G_RD(op)), R32(G_RA(op)), R32(G_RB(op))); // mulu mq, rd, ra, rb
generate_compute_flags(block, desc, op & M_RC, ((op & M_OE) ? XER_OV | XER_SO : 0), false); // <update flags>
return true;

case 0x1cb: /* DIVWUx */
case 0x3cb: /* DIVWUOx */
UML_CMP(block, R32(G_RB(op)), 0x0); // cmp rb, #0
Expand Down Expand Up @@ -2714,6 +2738,63 @@ bool ppc_device::generate_instruction_1f(drcuml_block &block, compiler_state *co
UML_LABEL(block, compiler->labelnum++); // 1:
return true;

case 0x14b: /* DIV (POWER) */
assert(m_cap & PPCCAP_LEGACY_POWER);

UML_SHL(block, I0, R32(G_RB(op)), 32); // I0 = RA << 32
UML_OR(block, I0, I0, SPR32(SPR601_MQ)); // I0 |= MQ
UML_CMP(block, I0, 0x0); // cmp I0, #0
UML_JMPc(block, COND_NZ, compiler->labelnum); // bne 0:

UML_MOV(block, R32(G_RD(op)), 0x0); // mov rd, #0
if (op & M_OE)
{
UML_OR(block, XERSO32, XERSO32, 0x1); // SO |= 1
UML_OR(block, SPR32(SPR_XER), SPR32(SPR_XER), XER_OV); // OV |= 1
}
if (op & M_RC)
{
UML_MOV(block, CR32(0), 0x2); // CR = EQ
UML_AND(block, CR32(0), CR32(0), ~0x1);
UML_OR(block, CR32(0), CR32(0), XERSO32);
}

UML_JMP(block, compiler->labelnum + 1); // jmp 1:

UML_LABEL(block, compiler->labelnum++); // 0:
UML_DIVS(block, R32(G_RD(op)), SPR32(SPR601_MQ), I0, R32(G_RB(op))); // divs rd,mq,I0,rb
generate_compute_flags(block, desc, op & M_RC, ((op & M_OE) ? XER_OV | XER_SO : 0), false); // <update flags>
UML_LABEL(block, compiler->labelnum++); // 1:
return true;

case 0x16b: /* DIVS (POWER) */
assert(m_cap & PPCCAP_LEGACY_POWER);

UML_CMP(block, R32(G_RB(op)), 0x0); // cmp rb, #0
UML_JMPc(block, COND_NZ, compiler->labelnum); // bne 0:

UML_MOV(block, R32(G_RD(op)), 0x0); // mov rd, #0
if (op & M_OE)
{
UML_OR(block, XERSO32, XERSO32, 0x1); // SO |= 1
UML_OR(block, SPR32(SPR_XER), SPR32(SPR_XER), XER_OV); // OV |= 1
}
if (op & M_RC)
{
UML_MOV(block, CR32(0), 0x2); // CR = EQ
UML_AND(block, CR32(0), CR32(0), ~0x1);
UML_OR(block, CR32(0), CR32(0), XERSO32);
}

UML_JMP(block, compiler->labelnum + 1); // jmp 1:

UML_LABEL(block, compiler->labelnum++); // 0:
UML_DIVS(block, R32(G_RD(op)), SPR32(SPR601_MQ), R32(G_RA(op)), R32(G_RB(op))); // divs rd,mq,ra,rb
generate_compute_flags(block, desc, op & M_RC, ((op & M_OE) ? XER_OV : 0), false); // <update flags>

UML_LABEL(block, compiler->labelnum++); // 1:
return true;

case 0x1eb: /* DIVWx */
case 0x3eb: /* DIVWOx */
UML_CMP(block, R32(G_RB(op)), 0x0); // cmp rb, #0
Expand Down Expand Up @@ -2767,6 +2848,78 @@ bool ppc_device::generate_instruction_1f(drcuml_block &block, compiler_state *co
UML_LABEL(block, compiler->labelnum++); // 3:
return true;

case 0x108: /* DOZ (POWER) */
assert(m_cap & PPCCAP_LEGACY_POWER);

UML_CMP(block, R32(G_RA(op)), R32(G_RB(op))); // cmp ra, rb
UML_JMPc(block, COND_B, compiler->labelnum); // bae 0:

UML_XOR(block, R32(G_RD(op)), R32(G_RD(op)), R32(G_RD(op))); // xor rd, rd, rd (rd = 0)
UML_JMP(block, compiler->labelnum+1); // jmp 1:

UML_LABEL(block, compiler->labelnum++); // 0:
UML_ADD(block, R32(G_RD(op)), R32(G_RA(op)), R32(G_RB(op)));
UML_ADD(block, R32(G_RD(op)), R32(G_RD(op)), 0x1);

UML_LABEL(block, compiler->labelnum++); // 1:
if (op & M_OE)
{
UML_OR(block, XERSO32, XERSO32, 0x1); // SO |= 1
UML_OR(block, SPR32(SPR_XER), SPR32(SPR_XER), XER_OV); // OV |= 1
}
if (op & M_RC)
{
UML_TEST(block, R32(G_RD(op)), ~0); // test rd,~0
generate_compute_flags(block, desc, op & M_RC, 0, false); // <update flags>
}
return true;

case 0x168: /* ABS (POWER) */
case 0x1e8: /* NABS (POWER) */
assert(m_cap & PPCCAP_LEGACY_POWER);

// is rA already the correct sign (positive for ABS, negative for NABS)?
UML_CMP(block, R32(G_RA(op)), 0);
if (op & 0x080)
{
UML_JMPc(block, COND_L, compiler->labelnum); // bl 0:
}
else
{
UML_JMPc(block, COND_GE, compiler->labelnum); // bge 0:
}

UML_SUB(block, I0, 0, R32(G_RA(op))); // sub 0, ra (make positive)
UML_JMP(block, compiler->labelnum + 1); // jmp 1:

UML_LABEL(block, compiler->labelnum++); // 0:
UML_MOV(block, I0, R32(G_RA(op)));

UML_LABEL(block, compiler->labelnum++); // 1:
UML_MOV(block, R32(G_RD(op)), I0); // mov rd, I0
if (op & M_RC)
{
UML_GETFLGS(block, I0, FLAG_Z | FLAG_V | FLAG_C | FLAG_S); // getflgs i0,zvcs
UML_LOAD(block, I0, m_cmp_cr_table, I0, SIZE_DWORD, SCALE_x4); // load i0,cmp_cr_table,i0,dword
}
if (op & M_OE)
{
UML_OR(block, CR32(G_CRFD(op)), I0, XERSO32); // or [crn],i0,[xerso]
}
return true;

case 0x21d: /* MASKIR (POWER) */
UML_AND(block, I0, R32(G_RS(op)), R32(G_RB(op))); // and i0, rs, rb
UML_XOR(block, I1, R32(G_RB(op)), 0xffffffff); // xor i1, rb, 0xffffffff
UML_AND(block, I1, I1, R32(G_RA(op))); // and i1, i1, ra
UML_OR(block, R32(G_RA(op)), I0, I1); // or ra, i0, i1
if (op & M_RC)
{
UML_GETFLGS(block, R32(G_RA(op)), FLAG_Z | FLAG_V | FLAG_C | FLAG_S); // getflgs i0,zvcs
UML_LOAD(block, I0, m_cmp_cr_table, I0, SIZE_DWORD, SCALE_x4); // load i0,cmp_cr_table,i0,dword
}
return true;

case 0x01c: /* ANDx */
UML_AND(block, R32(G_RA(op)), R32(G_RS(op)), R32(G_RB(op))); // and ra,rs,rb
generate_compute_flags(block, desc, op & M_RC, 0, false); // <update flags>
Expand Down
Loading

0 comments on commit 1b54e2d

Please sign in to comment.