// license:BSD-3-Clause
// copyright-holders:Aaron Giles
/***************************************************************************
mips3com.c
Common MIPS III/IV definitions and functions
***************************************************************************/
#include "emu.h"
#include "mips3com.h"
#include "ps2vu.h"
/***************************************************************************
FUNCTION PROTOTYPES
***************************************************************************/
static void tlb_entry_log_half(mips3_tlb_entry *entry, int tlbindex, int which);
/***************************************************************************
INLINE FUNCTIONS
***************************************************************************/
/*-------------------------------------------------
tlb_entry_matches_asid - true if the given
TLB entry matches the provided ASID
-------------------------------------------------*/
static inline bool tlb_entry_matches_asid(const mips3_tlb_entry *entry, uint8_t asid)
{
return (entry->entry_hi & 0xff) == asid;
}
/*-------------------------------------------------
tlb_entry_is_global - true if the given
TLB entry is global
-------------------------------------------------*/
static inline bool tlb_entry_is_global(const mips3_tlb_entry *entry)
{
return (entry->entry_lo[0] & entry->entry_lo[1] & TLB_GLOBAL);
}
void mips3_device::execute_set_input(int inputnum, int state)
{
if (inputnum >= MIPS3_IRQ0 && inputnum <= MIPS3_IRQ5)
{
if (state != CLEAR_LINE)
m_core->cpr[0][COP0_Cause] |= 0x400 << inputnum;
else
m_core->cpr[0][COP0_Cause] &= ~(0x400 << inputnum);
}
}
/***************************************************************************
INITIALIZATION AND SHUTDOWN
***************************************************************************/
/*-------------------------------------------------
mips3com_update_cycle_counting - update cycle
counts and the timers
-------------------------------------------------*/
void mips3_device::mips3com_update_cycle_counting()
{
/* modify the timer to go off */
if (m_core->compare_armed)
{
uint32_t count = (total_cycles() - m_core->count_zero_time) / 2;
uint32_t compare = m_core->cpr[0][COP0_Compare];
uint32_t delta = compare - count;
m_core->compare_armed = 0;
attotime newtime = cycles_to_attotime((uint64_t)delta * 2);
m_compare_int_timer->adjust(newtime);
return;
}
}
/***************************************************************************
TLB HANDLING
***************************************************************************/
/*-------------------------------------------------
mips3com_asid_changed - remap all non-global
TLB entries
-------------------------------------------------*/
void mips3_device::mips3com_asid_changed()
{
int tlbindex;
/* iterate over all non-global TLB entries and remap them */
for (tlbindex = 0; tlbindex < m_tlbentries; tlbindex++)
if (!tlb_entry_is_global(&m_tlb[tlbindex]))
tlb_map_entry(tlbindex);
}
/*-------------------------------------------------
mips3com_tlbr - execute the tlbr instruction
-------------------------------------------------*/
void mips3_device::mips3com_tlbr()
{
uint32_t tlbindex = m_core->cpr[0][COP0_Index] & 0x3f;
/* only handle entries within the TLB */
if (tlbindex < m_tlbentries)
{
mips3_tlb_entry *entry = &m_tlb[tlbindex];
/* copy data from the TLB entry into the COP0 registers */
m_core->cpr[0][COP0_PageMask] = entry->page_mask;
m_core->cpr[0][COP0_EntryHi] = entry->entry_hi;
m_core->cpr[0][COP0_EntryLo0] = entry->entry_lo[0];
m_core->cpr[0][COP0_EntryLo1] = entry->entry_lo[1];
}
}
/*-------------------------------------------------
mips3com_tlbwi - execute the tlbwi instruction
-------------------------------------------------*/
void mips3_device::mips3com_tlbwi()
{
/* use the common handler and write based off the COP0 Index register */
tlb_write_common(m_core->cpr[0][COP0_Index] & 0x3f);
}
/*-------------------------------------------------
mips3com_tlbwr - execute the tlbwr instruction
-------------------------------------------------*/
void mips3_device::mips3com_tlbwr()
{
uint32_t wired = m_core->cpr[0][COP0_Wired] & 0x3f;
uint32_t unwired = m_tlbentries - wired;
uint32_t tlbindex = m_tlbentries - 1;
/* "random" is based off of the current cycle counting through the non-wired pages */
if (unwired > 0)
tlbindex = ((total_cycles() - m_core->count_zero_time) % unwired + wired) & 0x3f;
/* use the common handler to write to this tlbindex */
tlb_write_common(tlbindex);
}
/*-------------------------------------------------
mips3com_tlbp - execute the tlbp instruction
-------------------------------------------------*/
void mips3_device::mips3com_tlbp()
{
uint32_t tlbindex;
/* iterate over TLB entries */
for (tlbindex = 0; tlbindex < m_tlbentries; tlbindex++)
{
mips3_tlb_entry *entry = &m_tlb[tlbindex];
uint64_t mask = ~((entry->page_mask >> 13) & 0xfff) << 13;
/* if the relevant bits of EntryHi match the relevant bits of the TLB */
if ((entry->entry_hi & mask) == (m_core->cpr[0][COP0_EntryHi] & mask))
/* and if we are either global or matching the current ASID, then stop */
if ((entry->entry_hi & 0xff) == (m_core->cpr[0][COP0_EntryHi] & 0xff) || ((entry->entry_lo[0] & entry->entry_lo[1]) & TLB_GLOBAL))
break;
}
/* validate that our tlb_table was in sync */
// vpn = ((m_cores->cpr[0][COP0_EntryHi] >> 13) & 0x07ffffff) << 1;
if (tlbindex != m_tlbentries)
m_core->cpr[0][COP0_Index] = tlbindex;
else
m_core->cpr[0][COP0_Index] = 0x80000000;
}
/***************************************************************************
INTERNAL HELPERS
***************************************************************************/
/*-------------------------------------------------
compare_int_callback - callback that fires
whenever a compare interrupt is generated
-------------------------------------------------*/
TIMER_CALLBACK_MEMBER( mips3_device::compare_int_callback )
{
m_compare_int_timer->adjust(attotime::never);
set_input_line(MIPS3_IRQ5, ASSERT_LINE);
}
/*-------------------------------------------------
compute_config_register - compute the value
of the config register
-------------------------------------------------*/
uint32_t mips3_device::compute_config_register()
{
/* set the cache line size to 32 bytes */
uint32_t configreg = 0x00026030;
int divisor;
// NEC VR series does not use a 100% compatible COP0/TLB implementation
if (m_flavor == MIPS3_TYPE_VR4300)
{
/*
For VR43xx, Config is as follows:
bit 31 = always 0
bits 28-30 = EC
bits 24-27 = EP
bits 16-23 = always b0000010
bit 15 = endian indicator as standard MIPS III
bits 4-14 = always b11001000110
bit 3 = CU
bits 0-2 = K0 ("Coherency algorithm of kseg0")
*/
configreg = 0x6460;
}
else if (m_flavor == MIPS3_TYPE_VR5500)
{
/*
For VR55xx, Config is as follows:
bit 31 = always 0
bits 28-30 = EC
bits 24-27 = EP
bits 23-22 = EM
bits 21-20 = always b11
bits 19-18 = EW
bits 17-16 = always b10
bit 15 = endian indicator as standard MIPS III
bits 3-14 = always b110011011110
bits 0-2 = K0 ("Coherency algorithm of kseg0")
*/
configreg = 0x6460;
}
else
{
/* set the data cache size */
if (c_icache_size <= 0x01000) configreg |= 0 << 6;
else if (c_icache_size <= 0x02000) configreg |= 1 << 6;
else if (c_icache_size <= 0x04000) configreg |= 2 << 6;
else if (c_icache_size <= 0x08000) configreg |= 3 << 6;
else if (c_icache_size <= 0x10000) configreg |= 4 << 6;
else if (c_icache_size <= 0x20000) configreg |= 5 << 6;
else if (c_icache_size <= 0x40000) configreg |= 6 << 6;
else configreg |= 7 << 6;
/* set the instruction cache size */
if (c_icache_size <= 0x01000) configreg |= 0 << 9;
else if (c_icache_size <= 0x02000) configreg |= 1 << 9;
else if (c_icache_size <= 0x04000) configreg |= 2 << 9;
else if (c_icache_size <= 0x08000) configreg |= 3 << 9;
else if (c_icache_size <= 0x10000) configreg |= 4 << 9;
else if (c_icache_size <= 0x20000) configreg |= 5 << 9;
else if (c_icache_size <= 0x40000) configreg |= 6 << 9;
else configreg |= 7 << 9;
/* set the system clock divider */
divisor = 2;
if (c_system_clock != 0)
{
divisor = m_cpu_clock / c_system_clock;
if (c_system_clock * divisor != m_cpu_clock)
{
configreg |= 0x80000000;
divisor = m_cpu_clock * 2 / c_system_clock;
}
}
configreg |= (((divisor < 2) ? 2 : (divisor > 8) ? 8 : divisor) - 2) << 28;
}
/* set the endianness bit */
if (m_bigendian)
configreg |= 0x00008000;
return configreg;
}
/*-------------------------------------------------
compute_prid_register - compute the value
of the PRId register
-------------------------------------------------*/
uint32_t mips3_device::compute_prid_register()
{
switch (m_flavor)
{
case MIPS3_TYPE_VR4300:
return 0x0b00;
case MIPS3_TYPE_VR5500:
return 0x5500;
case MIPS3_TYPE_R4600:
case MIPS3_TYPE_R4650:
return 0x2000;
case MIPS3_TYPE_R4700:
return 0x2100;
case MIPS3_TYPE_TX4925:
return 0x2d23;
case MIPS3_TYPE_R5000:
case MIPS3_TYPE_QED5271:
return 0x2300;
case MIPS3_TYPE_RM7000:
return 0x2700;
case MIPS3_TYPE_R5900:
return 0x2e14;
default:
fatalerror("Unknown MIPS flavor specified\n");
}
// never executed
//return 0x2000;
}
/*-------------------------------------------------
tlb_map_entry - map a single TLB
entry
-------------------------------------------------*/
void mips3_device::tlb_map_entry(int tlbindex)
{
int current_asid = m_core->cpr[0][COP0_EntryHi] & 0xff;
mips3_tlb_entry *entry = &m_tlb[tlbindex];
uint32_t count, vpn;
int which;
/* the ASID doesn't match the current ASID, and if the page isn't global, unmap it from the TLB */
if (!tlb_entry_matches_asid(entry, current_asid) && !tlb_entry_is_global(entry))
{
vtlb_load(2 * tlbindex + 0, 0, 0, 0);
vtlb_load(2 * tlbindex + 1, 0, 0, 0);
return;
}
/* extract the VPN index; ignore if the virtual address is beyond 32 bits */
vpn = ((entry->entry_hi >> 13) & 0x07ffffff) << 1;
if (vpn >= (1 << (MIPS3_MAX_PADDR_SHIFT - MIPS3_MIN_PAGE_SHIFT)))
{
vtlb_load(2 * tlbindex + 0, 0, 0, 0);
vtlb_load(2 * tlbindex + 1, 0, 0, 0);
return;
}
/* get the number of pages from the page mask */
/* R5900: if the S bit is set in EntryLo, it is the scratchpad, and is always 4 pages. */
if ((entry->entry_lo[0] & 0x80000000) && m_flavor == MIPS3_TYPE_R5900)
count = 4;
else
count = ((entry->page_mask >> 13) & 0x00fff) + 1;
/* loop over both the even and odd pages */
for (which = 0; which < 2; which++)
{
uint32_t effvpn = vpn + count * which;
uint64_t lo = entry->entry_lo[which];
uint32_t pfn;
uint32_t flags = 0;
/* compute physical page index */
pfn = (lo >> 6) & m_pfnmask;
/* valid? */
if ((lo & 2) != 0)
{
flags |= VTLB_FLAG_VALID | VTLB_READ_ALLOWED | VTLB_FETCH_ALLOWED;
/* writable? */
if ((lo & 4) != 0)
flags |= VTLB_WRITE_ALLOWED;
/* mirror the flags for user mode if the VPN is in user space */
if (effvpn < (0x80000000 >> MIPS3_MIN_PAGE_SHIFT))
flags |= (flags << 4) & (VTLB_USER_READ_ALLOWED | VTLB_USER_WRITE_ALLOWED | VTLB_USER_FETCH_ALLOWED);
}
/* load the virtual TLB with the corresponding entries */
if ((effvpn + count) <= (0x80000000 >> MIPS3_MIN_PAGE_SHIFT) || effvpn >= (0xc0000000 >> MIPS3_MIN_PAGE_SHIFT))
vtlb_load(2 * tlbindex + which, count, effvpn << MIPS3_MIN_PAGE_SHIFT, (pfn << MIPS3_MIN_PAGE_SHIFT) | flags);
else
vtlb_load(2 * tlbindex + which, 0, 0, 0);
}
}
/*-------------------------------------------------
tlb_write_common - common routine for writing
a TLB entry
-------------------------------------------------*/
void mips3_device::tlb_write_common(int tlbindex)
{
/* only handle entries within the TLB */
if (tlbindex < m_tlbentries)
{
mips3_tlb_entry *entry = &m_tlb[tlbindex];
/* fill in the new TLB entry from the COP0 registers */
entry->page_mask = m_core->cpr[0][COP0_PageMask];
entry->entry_hi = m_core->cpr[0][COP0_EntryHi] & ~(entry->page_mask & u64(0x0000000001ffe000U));
entry->entry_lo[0] = m_core->cpr[0][COP0_EntryLo0];
entry->entry_lo[1] = m_core->cpr[0][COP0_EntryLo1];
/* remap this TLB entry */
tlb_map_entry(tlbindex);
/* log the two halves once they are in */
tlb_entry_log_half(entry, tlbindex, 0);
tlb_entry_log_half(entry, tlbindex, 1);
}
}
/*-------------------------------------------------
tlb_entry_log_half - log half of a single TLB
entry
-------------------------------------------------*/
static void tlb_entry_log_half(mips3_tlb_entry *entry, int tlbindex, int which)
{
if (PRINTF_TLB)
{
uint64_t hi = entry->entry_hi;
uint64_t lo = entry->entry_lo[which];
uint32_t vpn = (((hi >> 13) & 0x07ffffff) << 1);
uint32_t asid = hi & 0xff;
uint32_t r = (hi >> 62) & 3;
uint32_t pfn = (lo >> 6) & 0x00ffffff;
uint32_t c = (lo >> 3) & 7;
uint32_t pagesize = (((entry->page_mask >> 13) & 0xfff) + 1) << MIPS3_MIN_PAGE_SHIFT;
uint64_t vaddr = (uint64_t)vpn * MIPS3_MIN_PAGE_SIZE;
uint64_t paddr = (uint64_t)pfn * MIPS3_MIN_PAGE_SIZE;
vaddr += pagesize * which;
printf("index=%08X pagesize=%08X vaddr=%08X%08X paddr=%08X%08X asid=%02X r=%X c=%X dvg=%c%c%c\n",
tlbindex, pagesize, (uint32_t)(vaddr >> 32), (uint32_t)vaddr, (uint32_t)(paddr >> 32), (uint32_t)paddr,
asid, r, c, (lo & 4) ? 'd' : '.', (lo & 2) ? 'v' : '.', (lo & 1) ? 'g' : '.');
}
}