summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/machine/mc88200.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/machine/mc88200.cpp')
-rw-r--r--src/devices/machine/mc88200.cpp993
1 files changed, 993 insertions, 0 deletions
diff --git a/src/devices/machine/mc88200.cpp b/src/devices/machine/mc88200.cpp
new file mode 100644
index 00000000000..ecfdf5d6517
--- /dev/null
+++ b/src/devices/machine/mc88200.cpp
@@ -0,0 +1,993 @@
+// license:BSD-3-Clause
+// copyright-holders:Patrick Mackinlay
+
+/*
+ * Motorola MC88200 Cache/Memory Management Unit (CMMU).
+ *
+ * Sources:
+ * - MC88200 Cache/Memory Management User's Manual, Second Edition (MC88200UM/AD, Rev 1)
+ *
+ * TODO:
+ * - probe commands
+ * - mbus snooping
+ * - cycle counting
+ * - cache inhibited accesses invalidate matching cache tags (no writeback)
+ * - mc88204 64k variant
+ * - find out where patc valid flag is stored
+ */
+
+#include "emu.h"
+
+#include "mc88200.h"
+
+//#define VERBOSE (LOG_GENERAL)
+#include "logmacro.h"
+
+DEFINE_DEVICE_TYPE(MC88200, mc88200_device, "mc88200", "Motorola MC88200 Cache/Memory Management Unit")
+
+mc88200_device::mc88200_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock, u8 id)
+ : device_t(mconfig, MC88200, tag, owner, clock)
+ , m_mbus(*this, finder_base::DUMMY_TAG, -1, 32)
+ , m_id(u32(id) << 24)
+{
+}
+
+enum idr_mask : u32
+{
+ IDR_VERSION = 0x001f0000,
+ IDR_TYPE = 0x00e00000,
+ IDR_ID = 0xff000000,
+};
+
+enum idr_type_mask : u32
+{
+ TYPE_MC88200 = 0x00a00000, // 16k cache
+ TYPE_MC88204 = 0x00c00000, // 64k cache
+};
+
+enum ssr_mask : u32
+{
+ SSR_V = 0x00000001, // valid
+ SSR_BH = 0x00000002, // batc hit
+ SSR_WP = 0x00000004, // write protection
+ SSR_U = 0x00000008, // used
+ SSR_M = 0x00000010, // modified
+ SSR_CI = 0x00000040, // cache inhibit
+ SSR_G = 0x00000080, // global
+ SSR_SP = 0x00000100, // supervisor privilege
+ SSR_WT = 0x00000200, // writethrough
+ SSR_BE = 0x00004000, // bus error
+ SSR_CE = 0x00008000, // copyback error
+
+ SSR_WM = 0x0000c3df,
+};
+
+enum sctr_mask : u32
+{
+ SCTR_PR = 0x00010000, // priority arbitration
+ SCTR_SE = 0x00020000, // snoop enable
+ SCTR_PE = 0x00040000, // parity enable
+};
+
+enum pfsr_mask : u32
+{
+ PFSR_OK = 0x00000000, // success (no fault)
+ PFSR_BE = 0x00030000, // bus error
+ PFSR_SF = 0x00040000, // segment fault
+ PFSR_PF = 0x00050000, // page fault
+ PFSR_SV = 0x00060000, // supervisor violation
+ PFSR_WV = 0x00070000, // write violation
+
+ PFSR_WM = 0x00070000,
+};
+
+enum apr_mask : u32
+{
+ APR_TE = 0x00000001, // translation enable
+ APR_CI = 0x00000040, // cache inhibit
+ APR_G = 0x00000080, // global
+ APR_WT = 0x00000200, // writethrough
+ APR_STBA = 0xfffff000, // segment table base address
+
+ APR_WM = 0xfffff2c1,
+};
+
+enum batc_mask : u32
+{
+ BATC_V = 0x00000001, // valid
+ BATC_WP = 0x00000002, // write protect
+ BATC_CI = 0x00000004, // cache inhibit
+ BATC_G = 0x00000008, // global
+ BATC_WT = 0x00000010, // writethrough
+ BATC_S = 0x00000020, // supervisor
+ BATC_PBA = 0x0007ffc0, // physical block address
+ BATC_LBA = 0xfff80000, // logical block address
+};
+
+enum cssp_mask : u32
+{
+ CSSP_VV0 = 0x00003000, // line valid 0
+ CSSP_VV1 = 0x0000c000, // line valid 1
+ CSSP_VV2 = 0x00030000, // line valid 2
+ CSSP_VV3 = 0x000c0000, // line valid 3
+ CSSP_D0 = 0x00100000, // line disable 0
+ CSSP_D1 = 0x00200000, // line disable 1
+ CSSP_D2 = 0x00400000, // line disable 2
+ CSSP_D3 = 0x00800000, // line disable 3
+ CSSP_L0 = 0x01000000, // line 1 more recently used than line 0
+ CSSP_L1 = 0x02000000, // line 2 more recently used than line 0
+ CSSP_L2 = 0x04000000, // line 2 more recently used than line 1
+ CSSP_L3 = 0x08000000, // line 3 more recently used than line 0
+ CSSP_L4 = 0x10000000, // line 3 more recently used than line 1
+ CSSP_L5 = 0x20000000, // line 3 more recently used than line 2
+
+ CSSP_WM = 0x3ffff000,
+};
+
+enum valid : unsigned
+{
+ EXU = 0, // exclusive unmodified
+ EXM = 1, // exclusive modified
+ SHU = 2, // shared unmodified
+ INV = 3, // invalid
+};
+
+enum patc_mask : u64
+{
+ PATC_WP = 0x0000'00000001, // write protect
+ PATC_M = 0x0000'00000002, // modified
+ PATC_CI = 0x0000'00000004, // cache inhibit
+ PATC_G = 0x0000'00000008, // global
+ PATC_WT = 0x0000'00000010, // writethrough
+ PATC_S = 0x0000'00000020, // supervisor
+ PATC_PFA = 0x0000'03ffffc0, // page frame address
+ PATC_LPA = 0x3fff'fc000000, // logical page address
+
+ PATC_V = 0x8000'00000000, // valid (??)
+};
+
+enum segment_descriptor_mask : u32
+{
+ SGD_V = 0x00000001, // valid
+ SGD_WP = 0x00000004, // write protect
+ SGD_CI = 0x00000040, // cache inhibit
+ SGD_G = 0x00000080, // global
+ SGD_SP = 0x00000100, // supervisor protection
+ SGD_WT = 0x00000200, // writethrough
+ SGD_PTBA = 0xfffff000, // page table base address
+};
+
+enum page_descriptor_mask : u32
+{
+ PGD_V = 0x00000001, // valid
+ PGD_WP = 0x00000004, // write protect
+ PGD_U = 0x00000008, // used
+ PGD_M = 0x00000010, // modified
+ PGD_CI = 0x00000040, // cache inhibit
+ PGD_G = 0x00000080, // global
+ PGD_SP = 0x00000100, // supervisor protection
+ PGD_WT = 0x00000200, // writethrough
+ PGD_PFA = 0xfffff000, // page frame address
+};
+
+enum logical_address_mask : u32
+{
+ LA_OFS = 0x00000fff,
+ LA_PAG = 0x003ff000,
+ LA_SEG = 0xffc00000,
+};
+
+void mc88200_device::device_start()
+{
+ m_cache = std::make_unique<cache_set[]>(CACHE_SETS);
+
+ save_item(NAME(m_idr));
+ save_item(NAME(m_scr));
+ save_item(NAME(m_ssr));
+ save_item(NAME(m_sar));
+ save_item(NAME(m_sctr));
+ save_item(NAME(m_pfsr));
+ save_item(NAME(m_pfar));
+ save_item(NAME(m_sapr));
+ save_item(NAME(m_uapr));
+
+ save_item(NAME(m_batc));
+ save_item(NAME(m_patc));
+ save_item(NAME(m_patc_ptr));
+
+ save_item(NAME(m_bus_error));
+
+ save_pointer(STRUCT_MEMBER(m_cache, status), CACHE_SETS);
+ // TODO: save state for cache lines
+
+ m_idr = m_id | TYPE_MC88200;
+ m_mbus->install_device(0xfff00000U | ((m_idr & IDR_ID) >> 12), 0xfff00fffU | ((m_idr & IDR_ID) >> 12), *this, &mc88200_device::map);
+}
+
+void mc88200_device::device_reset()
+{
+ m_scr = 0;
+ m_ssr = 0;
+ m_sar = 0; // undefined
+ m_sctr = 0;
+ m_pfsr = 0;
+ m_pfar = 0; // undefined
+ m_sapr = APR_CI;
+ m_uapr = APR_CI;
+
+ std::fill_n(m_batc, std::size(m_batc), 0U);
+
+ // batc contains two hard-wired entries
+ m_batc[8] = 0xfff7ffb5;
+ m_batc[9] = 0xfffffff5;
+
+ std::fill_n(m_patc, std::size(m_patc), 0U);
+ m_patc_ptr = 0;
+
+ m_bus_error = false;
+
+ idr_w(m_id);
+}
+
+void mc88200_device::map(address_map &map)
+{
+ // system interface registers
+ map(0x000, 0x003).rw(FUNC(mc88200_device::idr_r), FUNC(mc88200_device::idr_w));
+ map(0x004, 0x007).rw(FUNC(mc88200_device::scr_r), FUNC(mc88200_device::scr_w));
+ map(0x008, 0x00b).rw(FUNC(mc88200_device::ssr_r), FUNC(mc88200_device::ssr_w));
+ map(0x00c, 0x00f).rw(FUNC(mc88200_device::sar_r), FUNC(mc88200_device::sar_w));
+ map(0x104, 0x107).rw(FUNC(mc88200_device::sctr_r), FUNC(mc88200_device::sctr_w));
+
+ // p bus fault registers
+ map(0x108, 0x10b).rw(FUNC(mc88200_device::pfsr_r), FUNC(mc88200_device::pfsr_w));
+ map(0x10c, 0x10f).rw(FUNC(mc88200_device::pfar_r), FUNC(mc88200_device::pfar_w));
+
+ // area pointers
+ map(0x200, 0x203).rw(FUNC(mc88200_device::sapr_r), FUNC(mc88200_device::sapr_w));
+ map(0x204, 0x207).rw(FUNC(mc88200_device::uapr_r), FUNC(mc88200_device::uapr_w));
+
+ // batc write ports
+ map(0x400, 0x41f).w(FUNC(mc88200_device::bwp_w)).mirror(0x20);
+
+ // cache diagnostic ports
+ map(0x800, 0x80f).rw(FUNC(mc88200_device::cdp_r), FUNC(mc88200_device::cdp_w)).mirror(0x30);
+ map(0x840, 0x84f).rw(FUNC(mc88200_device::ctp_r), FUNC(mc88200_device::ctp_w)).mirror(0x30);
+ map(0x880, 0x883).rw(FUNC(mc88200_device::cssp_r), FUNC(mc88200_device::cssp_w)).mirror(0x30);
+}
+
+void mc88200_device::idr_w(u32 data)
+{
+ if ((data ^ m_idr) & IDR_ID)
+ {
+ LOG("idr_w 0x%08x (%s)\n", data, machine().describe_context());
+
+ m_mbus->unmap_readwrite(0xfff00000U | ((m_idr & IDR_ID) >> 12), 0xfff00fffU | ((m_idr & IDR_ID) >> 12));
+ m_idr = (m_idr & ~IDR_ID) | (data & IDR_ID);
+ m_mbus->install_device(0xfff00000U | ((m_idr & IDR_ID) >> 12), 0xfff00fffU | ((m_idr & IDR_ID) >> 12), *this, &mc88200_device::map);
+ }
+}
+
+void mc88200_device::scr_w(u32 data)
+{
+ LOG("scr_w 0x%08x (%s)\n", data, machine().describe_context());
+
+ char const *const action[] = { nullptr, "invalidate", "copy back", "copy back and invalidate" };
+
+ switch (data & 0x3f)
+ {
+ case 0x00: case 0x01: case 0x02: case 0x03:
+ case 0x04: case 0x05: case 0x06: case 0x07:
+ case 0x08: case 0x09: case 0x0a: case 0x0b:
+ case 0x0c: case 0x0d: case 0x0e: case 0x0f:
+ case 0x10: case 0x11: case 0x12: case 0x13:
+ LOG("no operation\n");
+ break;
+ case 0x14: // invalidate line
+ case 0x18: // copy back line
+ case 0x1c: // copy back and invalidate line
+ LOG("data cache %s line 0x%08x\n", action[BIT(data, 2, 2)], m_sar);
+ cache_flush(BIT(m_sar, 4, 8), BIT(m_sar, 4, 8) + 1, &mc88200_device::cache_set::cache_line::match_page, BIT(data, 3), BIT(data, 2));
+ break;
+ case 0x15: // invalidate page
+ case 0x19: // copy back page
+ case 0x1d: // copy back and invalidate page
+ LOG("data cache %s page 0x%08x\n", action[BIT(data, 2, 2)], m_sar & ~LA_OFS);
+ cache_flush(0, CACHE_SETS, &mc88200_device::cache_set::cache_line::match_page, BIT(data, 3), BIT(data, 2));
+ break;
+ case 0x16: // invalidate segment
+ case 0x1a: // copy back segment
+ case 0x1e: // copy back and invalidate segment
+ LOG("data cache %s segment 0x%08x\n", action[BIT(data, 2, 2)], m_sar & LA_SEG);
+ cache_flush(0, CACHE_SETS, &mc88200_device::cache_set::cache_line::match_segment, BIT(data, 3), BIT(data, 2));
+ break;
+ case 0x17: // invalidate all
+ case 0x1b: // copy back all
+ case 0x1f: // copy back and invalidate all
+ LOG("data cache %s all\n", action[BIT(data, 2, 2)]);
+ cache_flush(0, CACHE_SETS, nullptr, BIT(data, 3), BIT(data, 2));
+ break;
+ case 0x20: case 0x21: case 0x22: case 0x23:
+ case 0x28: case 0x29: case 0x2a: case 0x2b:
+ logerror("probe user address (unemulated)\n");
+ break;
+ case 0x24: case 0x25: case 0x26: case 0x27:
+ case 0x2c: case 0x2d: case 0x2e: case 0x2f:
+ logerror("probe supervisor (unemulated)\n");
+ break;
+ case 0x31: case 0x39: // page
+ LOG("invalidate PATC descriptors (user, page 0x%08x)\n", m_sar & ~LA_OFS);
+ for (u64 &patc : m_patc)
+ if (!(patc & PATC_S) && BIT(patc, 26, 20) == BIT(m_sar, 12, 20))
+ patc &= ~PATC_V;
+ break;
+ case 0x32: case 0x3a: // segment
+ LOG("invalidate PATC descriptors (user, segment 0x%08x)\n", m_sar & LA_SEG);
+ for (u64 &patc : m_patc)
+ if (!(patc & PATC_S) && BIT(patc, 36, 10) == BIT(m_sar, 22, 10))
+ patc &= ~PATC_V;
+ break;
+ case 0x33: case 0x3b: // all
+ LOG("invalidate PATC descriptors (user, all)\n");
+ for (u64 &patc : m_patc)
+ if (!(patc & PATC_S))
+ patc &= ~PATC_V;
+ break;
+ case 0x35: case 0x3d: // page
+ LOG("invalidate PATC descriptors (supervisor, page 0x%08x)\n", m_sar & ~LA_OFS);
+ for (u64 &patc : m_patc)
+ if ((patc & PATC_S) && BIT(patc, 26, 20) == BIT(m_sar, 12, 20))
+ patc &= ~PATC_V;
+ break;
+ case 0x36: case 0x3e: // segment
+ LOG("invalidate PATC descriptors (supervisor, segment 0x%08x)\n", m_sar & LA_SEG);
+ for (u64 &patc : m_patc)
+ if ((patc & PATC_S) && BIT(patc, 36, 10) == BIT(m_sar, 22, 10))
+ patc &= ~PATC_V;
+ break;
+ case 0x37: case 0x3f: // all
+ LOG("invalidate PATC cache descriptors (supervisor, all)\n");
+ for (u64 &patc : m_patc)
+ if (patc & PATC_S)
+ patc &= ~PATC_V;
+ break;
+ default:
+ logerror("unknown operation 0x%08x (%s)\n", data, machine().describe_context());
+ break;
+ }
+
+ m_scr = data & 0x3f;
+}
+
+void mc88200_device::ssr_w(u32 data) { LOG("ssr_w 0x%08x (%s)\n", data, machine().describe_context()); m_ssr = data & SSR_WM; }
+void mc88200_device::sar_w(u32 data) { LOG("sar_w 0x%08x (%s)\n", data, machine().describe_context()); m_sar = data; }
+void mc88200_device::sctr_w(u32 data) { LOG("sctr_w 0x%08x (%s)\n", data, machine().describe_context()); m_sctr = data; }
+
+void mc88200_device::pfsr_w(u32 data) { LOG("pfsr_w 0x%08x (%s)\n", data, machine().describe_context()); m_pfsr = data & PFSR_WM; }
+void mc88200_device::pfar_w(u32 data) { LOG("pfar_w 0x%08x (%s)\n", data, machine().describe_context()); m_pfar = data; }
+void mc88200_device::sapr_w(u32 data) { LOG("sapr_w 0x%08x (%s)\n", data, machine().describe_context()); m_sapr = data & APR_WM; }
+void mc88200_device::uapr_w(u32 data) { LOG("uapr_w 0x%08x (%s)\n", data, machine().describe_context()); m_uapr = data & APR_WM; }
+
+void mc88200_device::bwp_w(offs_t offset, u32 data)
+{
+ LOG("bwp_w %x,0x%08x (%s)\n", offset, data, machine().describe_context());
+
+ if (data & BATC_V)
+ {
+ for (unsigned i = 0; i < std::size(m_batc); i++)
+ {
+ if ((i != offset) && (m_batc[i] & BATC_V) && BIT(m_batc[i], 19, 13) == BIT(data, 19, 13))
+ {
+ logerror("duplicate batc entry 0x%08x invalidated (%s)\n", data, machine().describe_context());
+ data &= ~BATC_V;
+ }
+ }
+ }
+
+ m_batc[offset] = data;
+}
+
+void mc88200_device::cdp_w(offs_t offset, u32 data)
+{
+ LOG("cdp_w set %d line %d word %d data 0x%08x (%s)\n",
+ BIT(m_sar, 4, 8), offset, BIT(m_sar, 2, 2), data, machine().describe_context());
+
+ m_cache[BIT(m_sar, 4, 8)].line[offset].data[BIT(m_sar, 2, 2)] = data;
+}
+
+void mc88200_device::ctp_w(offs_t offset, u32 data)
+{
+ LOG("ctp_w set %d line %d data 0x%08x (%s)\n",
+ BIT(m_sar, 4, 8), offset, data, machine().describe_context());
+
+ m_cache[BIT(m_sar, 4, 8)].line[offset].tag = data & ~LA_OFS;
+}
+
+void mc88200_device::cssp_w(u32 data)
+{
+ LOG("cssp_w 0x%08x (%s)\n", data, machine().describe_context());
+
+ m_cache[BIT(m_sar, 4, 8)].status = data & CSSP_WM;
+}
+
+// abbreviated, side-effect free address translation for debugger
+bool mc88200_device::translate(int intention, u32 &address, bool supervisor)
+{
+ // select area descriptor
+ u32 const apr = supervisor ? m_sapr : m_uapr;
+ if (apr & APR_TE)
+ {
+ // search block address translation cache
+ for (u32 const &batc : m_batc)
+ {
+ if ((batc & BATC_V) && bool(batc & BATC_S) == supervisor && BIT(address, 19, 13) == BIT(batc, 19, 13))
+ {
+ address = ((batc & BATC_PBA) << 13) | (address & ~BATC_LBA);
+ return true;
+ }
+ }
+
+ // search page address translation cache
+ for (u64 const &patc : m_patc)
+ {
+ if ((patc & PATC_V) && (bool(patc & PATC_S) == supervisor && BIT(address, 12, 20) == BIT(patc, 26, 20)))
+ {
+ address = ((patc & PATC_PFA) << 6) | (address & LA_OFS);
+ return true;
+ }
+ }
+
+ // load and check segment descriptor
+ std::optional<u32> const sgd = mbus_read<u32>((apr & APR_STBA) | ((address & LA_SEG) >> 20));
+ if (!sgd.has_value() || !(sgd.value() & SGD_V) || ((sgd.value() & SGD_SP) && !supervisor))
+ return false;
+
+ // load and check page descriptor
+ std::optional<u32> pgd = mbus_read<u32>((sgd.value() & SGD_PTBA) | ((address & LA_PAG) >> 10));
+ if (!pgd.has_value() || !(pgd.value() & PGD_V) || ((pgd.value() & PGD_SP) && !supervisor))
+ return false;
+
+ address = (pgd.value() & PGD_PFA) | (address & LA_OFS);
+ }
+ else
+ {
+ // apply hard-wired batc entries
+ if (BIT(address, 19, 13) == BIT(m_batc[8], 19, 13))
+ address = ((m_batc[8] & BATC_PBA) << 13) | (address & ~BATC_LBA);
+ else if (BIT(address, 19, 13) == BIT(m_batc[9], 19, 13))
+ address = ((m_batc[9] & BATC_PBA) << 13) | (address & ~BATC_LBA);
+ }
+
+ return true;
+}
+
+std::optional<mc88200_device::translate_result> mc88200_device::translate(u32 virtual_address, bool supervisor, bool write)
+{
+ // select area descriptor
+ u32 const apr = supervisor ? m_sapr : m_uapr;
+ if (apr & APR_TE)
+ {
+ // search block address translation cache
+ for (u32 const &batc : m_batc)
+ {
+ if ((batc & BATC_V) && bool(batc & BATC_S) == supervisor && BIT(virtual_address, 19, 13) == BIT(batc, 19, 13))
+ {
+ if (!write || !(batc & BATC_WP))
+ return translate_result(((batc & BATC_PBA) << 13) | (virtual_address & ~BATC_LBA), batc & BATC_CI, batc & BATC_G, batc & BATC_WT);
+ else
+ {
+ // write violation
+ m_pfsr = PFSR_WV;
+
+ return std::nullopt;
+ }
+ }
+ }
+
+ // search page address translation cache
+ bool patc_hit = false;
+ for (u64 &patc : m_patc)
+ {
+ if ((patc & PATC_V) && (bool(patc & PATC_S) == supervisor && BIT(virtual_address, 12, 20) == BIT(patc, 26, 20)))
+ {
+ if (!write || !(patc & PATC_WP))
+ {
+ if (!write || (patc & PATC_M))
+ return translate_result(((patc & PATC_PFA) << 6) | (virtual_address & LA_OFS), patc & PATC_CI, patc & PATC_G, patc & PATC_WT);
+
+ patc |= PATC_M;
+ patc_hit = true;
+ break;
+ }
+ else
+ {
+ // write violation
+ m_pfsr = PFSR_WV;
+
+ return std::nullopt;
+ }
+ }
+ }
+
+ // load and check segment descriptor
+ std::optional<u32> const sgd = mbus_read<u32>((apr & APR_STBA) | ((virtual_address & LA_SEG) >> 20));
+ if (!sgd.has_value())
+ return std::nullopt;
+ if (!(sgd.value() & SGD_V))
+ {
+ m_pfsr = PFSR_SF;
+ m_pfar = (apr & APR_STBA) | ((virtual_address & LA_SEG) >> 20);
+
+ return std::nullopt;
+ }
+ if ((sgd.value() & SGD_SP) && !supervisor)
+ {
+ m_pfsr = PFSR_SV;
+ m_pfar = (apr & APR_STBA) | ((virtual_address & LA_SEG) >> 20);
+
+ return std::nullopt;
+ }
+
+ // load and check page descriptor
+ std::optional<u32> pgd = mbus_read<u32>((sgd.value() & SGD_PTBA) | ((virtual_address & LA_PAG) >> 10));
+ if (!pgd.has_value())
+ return std::nullopt;
+ if (!(pgd.value() & PGD_V))
+ {
+ m_pfsr = PFSR_PF;
+ m_pfar = (sgd.value() & SGD_PTBA) | ((virtual_address & LA_PAG) >> 10);
+
+ return std::nullopt;
+ }
+ if ((pgd.value() & PGD_SP) && !supervisor)
+ {
+ m_pfsr = PFSR_SV;
+ m_pfar = (sgd.value() & SGD_PTBA) | ((virtual_address & LA_PAG) >> 10);
+
+ return std::nullopt;
+ }
+
+ // check write protect
+ if (write && ((sgd.value() | pgd.value()) & PGD_WP))
+ {
+ m_pfsr = PFSR_WV;
+ return std::nullopt;
+ }
+
+ // update page descriptor used and modified bits
+ if (!(pgd.value() & PGD_U) || (write && !(pgd.value() & PGD_M)))
+ {
+ pgd.value() |= (write ? PGD_M : 0) | PGD_U;
+
+ if (!mbus_write((sgd.value() & SGD_PTBA) | ((virtual_address & LA_PAG) >> 10), pgd.value()))
+ return std::nullopt;
+ }
+
+ if (!patc_hit)
+ {
+ // create patc entry (lpa,pfa,s,wt,g,ci,m,wp)
+ m_patc[m_patc_ptr++] = PATC_V | (u64(virtual_address & ~LA_OFS) << 14) | ((pgd.value() & PGD_PFA) >> 6) | (supervisor ? PATC_S : 0) | bitswap<u64>(apr | sgd.value() | pgd.value(), 9, 7, 6, 4, 2);
+ if (m_patc_ptr == std::size(m_patc))
+ m_patc_ptr = 0;
+ }
+
+ return translate_result((pgd.value() & PGD_PFA) | (virtual_address & LA_OFS),
+ (apr | sgd.value() | pgd.value()) & PGD_CI,
+ (apr | sgd.value() | pgd.value()) & PGD_G,
+ (apr | sgd.value() | pgd.value()) & PGD_WT);
+ }
+ else
+ {
+ /*
+ * The user manual states that the hardwired BATC entries are used in
+ * supervisor mode even when translation is disabled.
+ *
+ * Despite statements indicating that CMMU control space should be part
+ * of the supervisor address space, the MVME181 firmware "Cache Inhibit
+ * Bits" diagnostic expects that CMMU registers also be accessible from
+ * user mode when translation is disabled.
+ *
+ * The following logic assumes that the hardwired entries are applied
+ * when translation is disabled without regard to the active mode,
+ * ensuring cache inhibit is activated.
+ */
+ for (unsigned i = 8; i < 10; i++)
+ if (BIT(virtual_address, 19, 13) == BIT(m_batc[i], 19, 13))
+ return translate_result(((m_batc[i] & BATC_PBA) << 13) | (virtual_address & ~BATC_LBA), m_batc[i] & BATC_CI, m_batc[i] & BATC_G, m_batc[i] & BATC_WT);
+
+ return translate_result(virtual_address, apr & APR_CI, apr & APR_G, apr & APR_WT);
+ }
+}
+
+void mc88200_device::cache_set::set_mru(unsigned const line)
+{
+ static const struct
+ {
+ u32 clr;
+ u32 set;
+ }
+ flags[] =
+ {
+ { CSSP_L3 | CSSP_L1 | CSSP_L0, 0 },
+ { CSSP_L4 | CSSP_L2, CSSP_L0 },
+ { CSSP_L5, CSSP_L2 | CSSP_L1 },
+ { 0, CSSP_L5 | CSSP_L4 | CSSP_L3 },
+ };
+
+ status = (status & ~flags[line].clr) | flags[line].set;
+}
+
+void mc88200_device::cache_set::set_unmodified(unsigned const line)
+{
+ status &= ~(INV << (12 + line * 2));
+}
+
+void mc88200_device::cache_set::set_modified(unsigned const line)
+{
+ status &= ~(INV << (12 + line * 2));
+ status |= EXM << (12 + line * 2);
+}
+
+void mc88200_device::cache_set::set_shared(unsigned const line)
+{
+ status &= ~(INV << (12 + line * 2));
+ status |= SHU << (12 + line * 2);
+}
+
+void mc88200_device::cache_set::set_invalid(unsigned const line)
+{
+ status |= INV << (12 + line * 2);
+}
+
+bool mc88200_device::cache_set::modified(unsigned const line) const
+{
+ return BIT(status, 12 + line * 2, 2) == EXM;
+}
+
+bool mc88200_device::cache_set::shared(unsigned const line) const
+{
+ return BIT(status, 12 + line * 2, 2) == SHU;
+}
+
+bool mc88200_device::cache_set::invalid(unsigned const line) const
+{
+ return BIT(status, 12 + line * 2, 2) == INV;
+}
+
+bool mc88200_device::cache_set::enabled(unsigned const line) const
+{
+ return !BIT(status, 20 + line);
+}
+
+bool mc88200_device::cache_set::cache_line::match_segment(u32 const address) const
+{
+ return BIT(tag, 22, 10) == BIT(address, 22, 10);
+}
+
+bool mc88200_device::cache_set::cache_line::match_page(u32 const address) const
+{
+ return BIT(tag, 12, 20) == BIT(address, 12, 20);
+}
+
+bool mc88200_device::cache_set::cache_line::load_line(mc88200_device &cmmu, u32 const address)
+{
+ for (unsigned i = 0; i < 4; i++)
+ {
+ std::optional<u32> const read = cmmu.mbus_read<u32>(address | i * 4);
+
+ if (read.has_value())
+ data[i] = read.value();
+ else
+ return false;
+ }
+
+ return true;
+}
+
+bool mc88200_device::cache_set::cache_line::copy_back(mc88200_device &cmmu, u32 const address, bool const flush)
+{
+ for (unsigned i = 0; i < 4; i++)
+ if (!cmmu.mbus_write(address | i * 4, data[i], flush))
+ return false;
+
+ return true;
+}
+
+std::optional<unsigned> mc88200_device::cache_replace(cache_set const &cs)
+{
+ // check for enabled invalid lines
+ for (unsigned l = 0; l < std::size(cs.line); l++)
+ if (cs.enabled(l) && cs.invalid(l))
+ return l;
+
+ /*
+ * This table encodes the cache line usage sequence for each combination of
+ * LRU flags. A zero value indicates an invalid flag combination, otherwise
+ * the two most-significant bits correspond to the most recently-used line
+ * and the two least-significant bits correspond to the least recently-used.
+ */
+ static u8 const usage_table[] =
+ {
+ 0x1b, 0x4b, 0x00, 0x63, 0x27, 0x00, 0x87, 0x93, // 00-07
+ 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x9c, // 08-0f
+ 0x00, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x8d, 0x00, // 10-17
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb1, 0xb4, // 18-1f
+ 0x1e, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 20-27
+ 0x00, 0x72, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, // 28-2f
+ 0x36, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, // 30-37
+ 0xc6, 0xd2, 0x00, 0xd8, 0xc9, 0x00, 0xe1, 0xe4, // 38-3f
+ };
+
+ u8 const usage = usage_table[BIT(cs.status, 24, 6)];
+ if (usage)
+ {
+ // find least-recently used enabled line
+ for (unsigned i = 0; i < 4; i++)
+ {
+ unsigned const l = BIT(usage, i * 2, 2);
+
+ if (cs.enabled(i))
+ return l;
+ }
+ }
+
+ // invalid flags or no enabled lines
+ return std::nullopt;
+}
+
+void mc88200_device::cache_flush(unsigned const start, unsigned const limit, match_function match, bool const copyback, bool const invalidate)
+{
+ for (unsigned s = start; s < limit; s++)
+ {
+ cache_set &cs = m_cache[s];
+
+ for (unsigned l = 0; l < std::size(cs.line); l++)
+ {
+ if (!match || std::invoke(match, cs.line[l], m_sar))
+ {
+ // copy back
+ if (copyback && cs.modified(l))
+ if (!cs.line[l].copy_back(*this, cs.line[l].tag | (s << 4), true))
+ return;
+
+ // invalidate
+ if (invalidate)
+ cs.set_invalid(l);
+ }
+ }
+ }
+}
+
+template <typename T> std::optional<T> mc88200_device::read(u32 virtual_address, bool supervisor)
+{
+ std::optional<mc88200_device::translate_result> result = translate(virtual_address, supervisor, false);
+ if (!result.has_value())
+ return std::nullopt;
+
+ u32 const physical_address = result.value().address;
+ if (!result.value().ci)
+ {
+ unsigned const s = BIT(physical_address, 4, 8);
+ cache_set &cs = m_cache[s];
+
+ for (unsigned l = 0; l < std::size(cs.line); l++)
+ {
+ // cache line hit: tag match, enabled, not invalid
+ if (cs.line[l].match_page(physical_address) && cs.enabled(l) && !cs.invalid(l))
+ {
+ // set most recently used
+ cs.set_mru(l);
+
+ // return data
+ u32 const data = cs.line[l].data[BIT(physical_address, 2, 2)];
+
+ switch (sizeof(T))
+ {
+ case 1: return T(data >> (24 - (physical_address & 3) * 8));
+ case 2: return T(data >> (16 - (physical_address & 3) * 8));
+ case 4: return T(data);
+ }
+ }
+ }
+
+ // select cache line for replacement
+ std::optional<unsigned> const l = cache_replace(cs);
+
+ if (l.has_value())
+ {
+ // copy back modified line
+ if (cs.modified(l.value()))
+ if (!cs.line[l.value()].copy_back(*this, cs.line[l.value()].tag | (s << 4)))
+ return std::nullopt;
+
+ // mark line invalid
+ cs.set_invalid(l.value());
+
+ // update tag
+ cs.line[l.value()].tag = physical_address & ~LA_OFS;
+
+ // load line from memory
+ if (!cs.line[l.value()].load_line(*this, physical_address & 0xfffffff0U))
+ return std::nullopt;
+
+ // mark line shared unmodified
+ cs.set_shared(l.value());
+
+ // set most recently used
+ cs.set_mru(l.value());
+
+ // return data
+ u32 const data = cs.line[l.value()].data[BIT(physical_address, 2, 2)];
+
+ switch (sizeof(T))
+ {
+ case 1: return T(data >> (24 - (physical_address & 3) * 8));
+ case 2: return T(data >> (16 - (physical_address & 3) * 8));
+ case 4: return T(data);
+ }
+ }
+ }
+
+ return mbus_read<T>(physical_address);
+}
+
+template <typename T> bool mc88200_device::write(u32 virtual_address, T data, bool supervisor)
+{
+ std::optional<mc88200_device::translate_result> result = translate(virtual_address, supervisor, true);
+ if (!result.has_value())
+ return false;
+
+ u32 const physical_address = result.value().address;
+ if (!result.value().ci)
+ {
+ unsigned const s = BIT(physical_address, 4, 8);
+ cache_set &cs = m_cache[s];
+
+ for (unsigned l = 0; l < std::size(cs.line); l++)
+ {
+ // cache line hit: tag match, enabled, not invalid
+ if (cs.line[l].match_page(physical_address) && cs.enabled(l) && !cs.invalid(l))
+ {
+ // write data to cache
+ u32 &cache_data = cs.line[l].data[BIT(physical_address, 2, 2)];
+ switch (sizeof(T))
+ {
+ case 1: cache_data = (cache_data & ~(0x000000ffU << (24 - (physical_address & 3) * 8))) | (u32(data) << (24 - (physical_address & 3) * 8)); break;
+ case 2: cache_data = (cache_data & ~(0x0000ffffU << (16 - (physical_address & 3) * 8))) | (u32(data) << (16 - (physical_address & 3) * 8)); break;
+ case 4: cache_data = data; break;
+ }
+
+ // set most recently used
+ cs.set_mru(l);
+
+ // write data to memory
+ if (result.value().wt || result.value().g)
+ if (!mbus_write(physical_address, data))
+ return false;
+
+ // update line status
+ if (cs.shared(l))
+ {
+ if (result.value().g)
+ cs.set_unmodified(l);
+ else if (!result.value().wt)
+ cs.set_modified(l);
+ }
+ else
+ cs.set_modified(l);
+
+ return true;
+ }
+ }
+
+ // select cache line for replacement
+ std::optional<unsigned> const l = cache_replace(cs);
+
+ if (l.has_value())
+ {
+ // copy back modified line
+ if (cs.modified(l.value()))
+ if (!cs.line[l.value()].copy_back(*this, cs.line[l.value()].tag | (s << 4)))
+ return false;
+
+ // mark line invalid
+ cs.set_invalid(l.value());
+
+ // load line from memory
+ if (!cs.line[l.value()].load_line(*this, physical_address & 0xfffffff0U))
+ return false;
+ }
+
+ // write data to memory
+ if (!mbus_write(physical_address, data))
+ return false;
+
+ if (l.has_value())
+ {
+ // update tag
+ cs.line[l.value()].tag = physical_address & ~LA_OFS;
+
+ // write data into cache
+ u32 &cache_data = cs.line[l.value()].data[BIT(physical_address, 2, 2)];
+ switch (sizeof(T))
+ {
+ case 1: cache_data = (cache_data & ~(0x000000ffU << (24 - (physical_address & 3) * 8))) | (u32(data) << (24 - (physical_address & 3) * 8)); break;
+ case 2: cache_data = (cache_data & ~(0x0000ffffU << (16 - (physical_address & 3) * 8))) | (u32(data) << (16 - (physical_address & 3) * 8)); break;
+ case 4: cache_data = data; break;
+ }
+
+ // mark line exclusive unmodified
+ cs.set_unmodified(l.value());
+
+ // set most recently used
+ cs.set_mru(l.value());
+ }
+
+ return true;
+ }
+
+ return mbus_write(result.value().address, data);
+}
+
+template std::optional<u8> mc88200_device::read(u32 virtual_address, bool supervisor);
+template std::optional<u16> mc88200_device::read(u32 virtual_address, bool supervisor);
+template std::optional<u32> mc88200_device::read(u32 virtual_address, bool supervisor);
+
+template bool mc88200_device::write(u32 virtual_address, u8 data, bool supervisor);
+template bool mc88200_device::write(u32 virtual_address, u16 data, bool supervisor);
+template bool mc88200_device::write(u32 virtual_address, u32 data, bool supervisor);
+
+template <typename T> std::optional<T> mc88200_device::mbus_read(u32 address)
+{
+ std::optional<T> data;
+
+ m_bus_error = false;
+
+ switch (sizeof(T))
+ {
+ case 1: data = m_mbus->read_byte(address); break;
+ case 2: data = m_mbus->read_word(address); break;
+ case 4: data = m_mbus->read_dword(address); break;
+ }
+
+ if (m_bus_error)
+ {
+ if (!machine().side_effects_disabled())
+ {
+ m_pfar = address;
+ m_pfsr = PFSR_BE;
+ }
+ data = std::nullopt;
+ }
+
+ return data;
+}
+
+template <typename T> bool mc88200_device::mbus_write(u32 address, T data, bool flush)
+{
+ m_bus_error = false;
+
+ switch (sizeof(T))
+ {
+ case 1: m_mbus->write_byte(address, data); break;
+ case 2: m_mbus->write_word(address, data); break;
+ case 4: m_mbus->write_dword(address, data); break;
+ }
+
+ if (m_bus_error)
+ {
+ if (!flush)
+ {
+ m_pfar = address;
+ m_pfsr = PFSR_BE;
+ }
+ else
+ {
+ m_sar = address;
+ m_ssr |= SSR_BE;
+ }
+
+ return false;
+ }
+ else
+ return true;
+}