--- nono/m680x0/m68030mmu.cpp 2026/04/29 17:05:08 1.1.1.6 +++ nono/m680x0/m68030mmu.cpp 2026/04/29 17:05:57 1.1.1.15 @@ -4,53 +4,21 @@ // Licensed under nono-license.txt // -#include "m68030core.h" -#include "m68030bus.h" -#include "m68030ea.h" +#include "mpu680x0.h" #include "m68030mmu.h" -#include "bus.h" -#include "mpu.h" - -#define TIA (0) -#define TIB (1) -#define TIC (2) -#define TID (3) -#define FCL (4) // 便宜上ね -#define MAX_LV (5) +#include "mainbus.h" // ディスクリプタタイプ -#define GET_DT(x) (x & 0x03) +#define GET_DT(x) ((x) & 0x03) #define DT_INVALID (0) #define DT_PAGE (1) #define DT_VALID4 (2) #define DT_VALID8 (3) -// ディスクリプタ -#define MMU_DESC_S (0x00000100) -#define MMU_DESC_CI (0x00000040) -#define MMU_DESC_M (0x00000010) -#define MMU_DESC_U (0x00000008) -#define MMU_DESC_WP (0x00000004) -#define MMU_DESC_DT (0x00000003) - -// エントリの状態 -enum ATCtype { - INVALID, - NORMAL, - EARLY, - INDIRECT, -}; - // // デバッグ表示 // -// デバッグ表示のため値の表示に必要な桁数を返す -#define NIBBLE(x) ( (x) == 0 ? 1 : ((x) + 3) / 4 ) - -// MMU デバッグモードなら ATC エントリ a を表示 -#define FILL_PRINT(fc, a) cpulogf(3, [&] { return (a)->fill_print(fc); }) - // インデント付き string_format static std::string indent_format(int indent, const char *fmt, ...) __printflike(2, 3); @@ -71,1024 +39,1306 @@ indent_format(int indent, const char *fm return rv; } -#define MMULOG(lv, indent, arg...) \ - cpulogf(lv, [&] { return indent_format(indent, arg); }) - -// テーブルサーチ操作クラス -class m68030MMU final -{ - public: - m68030MMU(m68kcpu *); - ~m68030MMU() { } +#define MMULOG(lv, indent, arg...) do { \ + if (__predict_false(loglevel >= (lv))) { \ + cpu->putlogf(lv, [&, lam_func = __func__] { \ + (void)lam_func; \ + return indent_format(indent, arg); \ + }); \ + } \ +} while (0) - // サーチを実行 - void search(); - // ATC にエントリを作成 - void make_atc(m68030ATCLine *); +// +// ページテーブル操作クラスはヘッダにいらないのでここで定義する。 +// +class m68030PageTableOp +{ + static constexpr uint32 PAGEDESC_S = 0x00000100; + static constexpr uint32 PAGEDESC_CI = 0x00000040; + static constexpr uint32 PAGEDESC_M = 0x00000010; + static constexpr uint32 PAGEDESC_U = 0x00000008; + static constexpr uint32 PAGEDESC_WP = 0x00000004; + static constexpr uint32 PAGEDESC_DT = 0x00000003; - // ATC にエントリを作成 (デバッガアクセス用) - uint64 make_atc_debugger(); + public: + // TIA 〜 TID で最大4階層。 + // LV_FCL を引くのはデバッグ表示だけなので番兵より後ろ。 + static constexpr int LV_TIA = 0; + static constexpr int LV_TIB = 1; + static constexpr int LV_TIC = 2; + static constexpr int LV_TID = 3; + static constexpr int MAXLV = 4; + static constexpr int LV_FCL = 5; + + // サーチ結果。 + enum Result { + Normal, // 正常に解決できた。 + Early, // アーリーターミネーション。 + Indirect, // 間接ディスクリプタ。 + Invalid, // 無効エントリに当たった。 + }; + + // 動作モード。整数比較もする。 + enum From { + ByTranslation = 0, // アドレス変換中のテーブルサーチ。(通常) + ByPTEST = 1, // PTEST* 命令によるテーブルサーチ。 + ByDebugger = -1, // デバッガによるテーブルサーチ。 + }; - private: - // リミットチェック - bool limitcheck() const; + public: + m68030PageTableOp(MPU68030Device *, int loglevel_, m68030PageTableOp::From); - // ディスクリプタを取得 - bool fetch_desc(uint32, int); + // ページテーブルから laddr を検索する。 + Result Search(int maxlv, busaddr laddr); - // MMU による物理メモリ読み込み - uint64 phys_read_32(uint32 addr); + // ページテーブルから laddr に対応する物理アドレスを調べる。デバッガ専用。 + busaddr SearchByDebugger(busaddr laddr); - // MMU による物理メモリ書き込み - uint64 phys_write_32(uint32 addr, uint32 data); + // 検索結果から a を埋める。 + void MakeATC(m68030ATC&, m68030ATCLine *a) const; - // デバッガ読み込み - uint64 phys_peek_32(uint32 addr); + private: + // モード判定。 + bool IsDebugger() const noexcept { return (from < 0); } + bool IsPTEST() const noexcept { return (from > 0); } + + Result DoSearch(int maxlv, const busaddr laddr); + + // ディスクリプタをフェッチする。 + inline bool FETCH_DESC(uint32 addr, uint idx) + { + if (__predict_false(IsDebugger())) { + return PeekDesc(addr, idx); + } else { + return FetchDesc(addr, idx); + } + } + bool FetchDesc(uint32 addr, uint idx); + bool PeekDesc(uint32 addr, uint idx); + bool CheckLimit() const; + uint32 AssembleEarlyRemains(uint32 addr) const; // ディスクリプタを表示用に (デバッグ用) - std::string sprintf_desc(); + std::string sprintf_desc() const; // ディスクリプタタイプ文字列 (デバッグ用) - const char *dtname(uint32 dt); + static const char *dtname(uint32 dt); public: - ATCtype m_type; // 状態 - int m_s; // S ビット - int m_wp; // WP ビット - int m_m; // ページディスクリプタの M が立っているか - int m_l; // サーチ中にリミットを越えた - bool m_berr; // テーブルサーチ中にバスエラー - bool m_ptest; // PTEST[RW] 命令中なら真 - bool m_debugger; // デバッガからのアクセスなら true にする + Result result {}; + busaddr m_laddr {}; // 論理アドレス (入力) + busaddr m_paddr {}; // 物理アドレス + bool m_berr {}; // B: ディスクリプタフェッチでバスエラー (PTEST) + bool m_lim {}; // L: リミット + bool m_s {}; + bool m_ci {}; + bool m_wp {}; + bool m_m {}; + int m_n {}; // N: 検索回数 + // 最後にアクセスしたディスクリプタアドレス (PTEST) + uint32 m_lastaddr {}; + private: - int x; // 段数 TIA〜TID, FCL - int m_lv[MAX_LV]; - uint32 m_laddr; - int m_fc; - uint32 m_desc1; // ディスクリプタ - uint32 m_desc2; // ロングディスクリプタの後半部 - int m_descsize; // ディスクリプタサイズ (4 or 8) - int m_lastsize; // 前段のディスクリプタサイズ + // laddr の各階層ごとの値。これがテーブルを引く際のインデックス。 + std::array m_idx {}; - // デバッグ用文字列 - static const char * const m_lvname[MAX_LV]; + int lv {}; // 現在位置(LV_*) - m68kcpu *cpu; -}; + // ディスクリプタにはショート(4バイト)とロング(8バイト)があり、 + // どちらを使うかは前段の DT で決まる (それが m_descsize)。 + // ロングディスクリプタは 1 ロングワード目がフラグ、2ロングワード目が + // アドレスとなっている。ショートディスクリプタは S ビットを除いて + // このフラグ部とアドレス部を OR しただけの形をしている。 + // そのため、ロングディスクリプタなら m_desc1 に1語目(フラグ部)、 + // m_desc2 に2語目(アドレス部) を置くが、ショートディスクリプタなら + // m_desc1, m_desc2 のどちらにも同じものを置けば (どうせ使う時に + // 必要なビットだけにマスクするので) そのまま使える。 + uint32 m_desc1 {}; + uint32 m_desc2 {}; + uint m_descsize {}; // ディスクリプタサイズ (4 or 8) + + From from {}; // 動作モード + + // M ビットを更新するかどうか。 + // PTEST でない(=通常サーチの) 書き込みアクセスなら true になる。 + bool writeback_m {}; -// 現在の TC:E、TT:E の状態に応じてアドレス変換の有無を切り替える。 -// mmu_enable ならアドレス変換ありのバスアクセスを使う。see m68030bus.cpp -void -m68kcpu::mmu_enable_changed() -{ - bool enable; + int loglevel {}; - // TC、TT0、TT1 のいずれかが有効ならアドレス変換あり - enable = (mmu_tc.e || mmu_tt[0].e || mmu_tt[1].e); + MPU68030Device *cpu {}; - // 変化した時だけ - if (mmu_enable && !enable) { - // 無効にする - mmu_enable = false; - cpulog(1, "MMU アドレス変換停止"); - } else if (!mmu_enable && enable) { - // 有効にする - mmu_enable = true; - cpulog(1, "MMU アドレス変換開始"); - } -} + static const char * const m_lvname[LV_FCL + 1]; +}; // -// レジスタクラス +// アドレス変換。 // -void -m68kcpu::SetSRP(uint32 h, uint32 l) +// リード/フェッチ用のアドレス変換を行う。 +// 変換後の物理アドレス、もしくは BusErr を返す。 +busaddr +MPU68030Device::TranslateRead(const busaddr laddr) { - reg.srp.h = h & m68030RP::H_MASK; - reg.srp.l = l & m68030RP::L_MASK; -} + m68030ATCLine *a; -void -m68kcpu::SetCRP(uint32 h, uint32 l) -{ - reg.crp.h = h & m68030RP::H_MASK; - reg.crp.l = l & m68030RP::L_MASK; + uint32 fc = laddr.GetFC(); + putlog(3, " %s: enter %u(%s).%08x pc=%c.%08x", __func__, + fc, FCstr(fc), + laddr.Addr(), + (IsSuper() ? 'S' : 'U'), + ppc); + +#if defined(M68030_CUSTOM_ATC) + auto table = atc.GetTable(fc); + uint8 n = table->hash[laddr.Addr() >> 12]; + if (__predict_true((int8)n >= 0)) { + // ATC #(n) + table->stat.total++; + a = &table->line[n]; + } else if (n == m68030ATCTable_Custom::HASH_NONE) { + if (__predict_true(mmu_tc.e)) { + // TC.E 有効で対応なしなら、テーブルサーチ。 + table->stat.total++; + table->stat.miss++; + a = table->Pop(); + PageTableSearch(laddr, a); + } else { + // TC.E 無効。(total もカウントしない) + return laddr; + } + } else if (n == m68030ATCTable_Custom::HASH_TT) { + // TT。TTMatch() で外れたかどうかはカウントしない。ほぼ 0 なので。 + table->stat.total++; + table->stat.tthit++; + return TTMatch(laddr); + } else { + // PS<4KB でマッチしたので再検査。 + table->stat.total++; + a = table->FindSub(laddr); + if (a == NULL) { + table->stat.miss++; + a = table->Pop(); + PageTableSearch(laddr, a); + } + } + table->MakeHash(a); + table->MoveHead(a); +#else + // TT は TC.E とは独立しているので先に調べる。 + busaddr ttaddr = TTMatch(laddr); + if (__predict_false(ttaddr.IsOK())) { + putlog(3, " %s: result %08x CI=%u (TT)", __func__, + ttaddr.Addr(), ttaddr.IsCInhibit() ? 1 : 0); + return ttaddr; + } + // TC.E 無効なら PA=VA。 + if (__predict_false(mmu_tc.e == false)) { + putlog(3, " %s: result %08x (No translation)", __func__, laddr.Addr()); + return laddr; + } + // ATC を検索。なければテーブルサーチ。 + auto table = atc.GetTable(); + a = table->LookupAndMove(laddr); + if (__predict_false(a == NULL)) { + putlog(4, " ATC not match"); + a = table->Pop(); + PageTableSearch(laddr, a); + } +#endif + + putlog(4, " ATC match %s", table->LineToStr(a).c_str()); + + if (__predict_false(a->IsBusError())) { + putlog(3, " %s: result BusErr", __func__); + return BusAddr::BusErr; + } + busaddr paddr = laddr; + paddr.ChangeAddr(a->GetPAddr() | (laddr.Addr() & mmu_tc.pgmask)); + if (__predict_false(a->IsCInhibit())) { + paddr |= BusAddr::CInhibit; + } + putlog(3, " %s: result %08x CI=%u", __func__, + paddr.Addr(), paddr.IsCInhibit() ? 1 : 0); + return paddr; } -void -m68kcpu::SetTT(int n, uint32 data) +// ライト用のアドレス変換を行う。 +// 変換後の物理アドレス、もしくは BusErr を返す。 +busaddr +MPU68030Device::TranslateWrite(const busaddr laddr) { - uint32 lbase; - uint32 lmask; - uint32 sbase; - uint32 smask; - - // マスクしてレジスタイメージにセット - reg.tt[n] = data & m68030TT::MASK; + m68030ATCLine *a; - // data を分解してメンバにセット。 - // - // 3 2 1 0 - // 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 - // +---------------+---------------+-+-+-+-+-+-+-+-+-+-----+-+-----+ - // |LogicalAddrBase|LogicalAddrMask|E| |C|R|M| | FCB | | FCM | - // +---------------+---------------+-+-+-+-+-+-+-+-+-+-----+-+-----+ + uint32 fc = laddr.GetFC(); + putlog(3, " %s: enter %u(%s).%08x pc=%c.%08x", __func__, + fc, FCstr(fc), + laddr.Addr(), + (IsSuper() ? 'S' : 'U'), + ppc); + +#if defined(M68030_CUSTOM_ATC) + auto table = atc.GetTable(fc); + uint8 n = table->hash[laddr.Addr() >> 12]; + if (__predict_true((int8)n >= 0)) { + // ATC #(n) + table->stat.total++; + a = &table->line[n]; + } else if (n == m68030ATCTable_Custom::HASH_NONE) { + if (__predict_true(mmu_tc.e)) { + // TC.E 有効で対応なしなら、テーブルサーチ。 + table->stat.total++; + a = table->Pop(); + goto tablesearch; + } else { + // TC.E 無効。(total もカウントしない) + return laddr; + } + } else if (n == m68030ATCTable_Custom::HASH_TT) { + // TT。TTMatch() で外れたかどうかはカウントしない。ほぼ 0 なので。 + table->stat.total++; + table->stat.tthit++; + return TTMatch(laddr); + } else { + // PS<4KB でマッチしたので再検査。 + table->stat.total++; + a = table->FindSub(laddr); + if (a == NULL) { + a = table->Pop(); + goto tablesearch; + } + } - mmu_tt[n].e = (data & m68030TT::E); - mmu_tt[n].ci = (data & m68030TT::CI); + // 書き込み可なのにまだ Modified == 0 なら、Modified を立てるために + // このエントリを破棄して改めてテーブルサーチを行う。 + if (a->IsWProtect() == false && a->IsModified() == false) { + table->Invalidate(a); + tablesearch: + table->stat.miss++; + PageTableSearch(laddr, a); + } + table->MoveHead(a); + table->MakeHash(a); +#else + // TT は TC.E とは独立しているので先に調べる。 + busaddr ttaddr = TTMatch(laddr); + if (__predict_false(ttaddr.IsOK())) { + putlog(3, " %s: result %08x CI=%u (TT)", __func__, + ttaddr.Addr(), ttaddr.IsCInhibit() ? 1 : 0); + return ttaddr; + } + // TC.E 無効なら PA=VA。 + if (__predict_false(mmu_tc.e == false)) { + putlog(3, " %s: result %08x (No translation)", __func__, laddr.Addr()); + return laddr; + } + // ATC を検索。 + auto table = atc.GetTable(); + a = table->LookupAndMove(laddr); + if (a) { + // 書き込み可なのにまだ Modified == 0 なら、Modified を立てるために + // このエントリを破棄して改めてテーブルサーチを行う。 + if (a->IsWProtect() == false && a->IsModified() == false) { + putlog(4, " ATC match but !Modified"); + PageTableSearch(laddr, a); + } + } else { + // なければテーブルサーチ。 + putlog(4, " ATC not match"); + a = table->Pop(); + PageTableSearch(laddr, a); + } +#endif - // 下位ロングワード - // ・lbase は比較先アドレスで、ff000000の位置。 - // ・lmask はアドレスマスク。%1 が無視を表しているので NOT しておく。 - // - // target AAAAAAAA xxxxxxxx xxxxxxxx xxxxxxxx : Aは論理アドレス - // ^ : xは不問 - // lbase BBBBBBBB 00000000 00000000 00000000 : Bは論理アドレスベース - // & - // lmask MMMMMMMM 00000000 00000000 00000000 : Mは論理アドレスマスク - // - lbase = (data & m68030TT::LBASE_MASK); - lmask = (~data & m68030TT::LMASK_MASK) << 8; + putlog(4, " ATC match %s", table->LineToStr(a).c_str()); - // 上位ロングワード - // ・E ビットは位置はどこでもいいので TT:E ビットの - // 位置をそのまま使用する。target 側のこの位置は必ずビットが落ちている - // ため base 側にビットを立てておくと必ず不一致になることを利用して、 - // E ビットの評価も一度に行う。 - // ・RW ビットは SSW の RW と比較するので、SSW_RW のビット位置を使う - // (0x00000040 の位置)。 - // また TT::RW は Write が %1 だが SSW_RW は Read が %1 なので反転。 - // ・RWM が %1 なら R/W は無視、RWM が %0 なら R/W の一致も検査するので - // RWM が %0 の時は RW のマスクビットを立てておく。 - // ・FCMask は %1 が無視を表しているので NOT しておく。 - // - // target 00000000 00000000 00000000 0R000FFF : R はアクセス R/~W - // ^ : F はアクセス FC - // base 00000000 00000000 E0000000 0R000FFF : R は期待する R/~W - // & : F は期待する FC - // : E は ~TT:E - // mask 00000000 00000000 10000000 0M000CCC : M は R/~W マスク - // : C は FC マスク - sbase = ~data & m68030TT::E; - sbase |= (~data & m68030TT::RW) >> 3; - sbase |= ( data & m68030TT::FCBASE_MASK) >> 4; - smask = m68030TT::E; - if ((data & m68030TT::RWM) == 0) { - smask |= SSW_RW; + if (__predict_false(a->IsBusError())) { + putlog(3, " %s: result BusErr", __func__); + return BusAddr::BusErr; + } + if (__predict_false(a->IsWProtect())) { + // Write アクセスで WP が立ってたらバスエラー。 + putlog(3, " %s: result Buserr (WProtect)", __func__); + return BusAddr::BusErr; } - smask |= ~data & m68030TT::FCMASK_MASK; - mmu_tt[n].base = lbase | (((uint64)sbase) << 32); - mmu_tt[n].mask = lmask | (((uint64)smask) << 32); + busaddr paddr = laddr; + paddr.ChangeAddr(a->GetPAddr() | (laddr.Addr() & mmu_tc.pgmask)); + if (__predict_false(a->IsCInhibit())) { + paddr |= BusAddr::CInhibit; + } + putlog(3, " %s: result %08x CI=%u", __func__, + paddr.Addr(), paddr.IsCInhibit() ? 1 : 0); + return paddr; +} - // 論理アドレスとEnableビットだけの早見表を作成。 - // TT0 が一致するなら bit0 をセット、TT1 が一致するなら bit1 をセット - // しておく。 - for (int i = 0; i < countof(mmu_tt_quick); i++) { - uint32 laddr = ((uint32)i) << 24; - if (mmu_tt[n].e && ((laddr ^ lbase) & lmask) == 0) { - mmu_tt_quick[i] |= (1 << n); +// ピーク用のアドレス変換を行う。 +// 変換できれば対応する物理アドレスを、バスエラーなら BusErr を返す。 +// テーブルサーチを行ったら TableSearched ビットを立てる。 +busaddr +MPU68030Device::TranslatePeek(const busaddr laddr) +{ + const m68030ATCLine *a; + busaddr paddr; + + uint32 fc = laddr.GetFC(); + putlog(5, " %s: enter %u(%s).%08x", __func__, + fc, FCstr(fc), + laddr.Addr()); + +#if defined(M68030_CUSTOM_ATC) + auto table = atc.GetTable(fc); + uint8 n = table->hash[laddr.Addr() >> 12]; + // PS<4KB のケースを先に解決する。 + if (__predict_false(n == m68030ATCTable_Custom::HASH_SUB)) { + a = table->FindSub(laddr); + if (a) { + n = a - &table->line[0]; } else { - mmu_tt_quick[i] &= ~(1 << n); + n = m68030ATCTable_Custom::HASH_NONE; + } + } + if (__predict_true((int8)n >= 0)) { + // ATC #(n) + a = &table->line[n]; + paddr = busaddr(a->GetPAddr() | (laddr.Addr() & mmu_tc.pgmask)); + putlog(5, "%s: result %08x (match atc)", __func__, paddr.Addr()); + return paddr; + } else if (n == m68030ATCTable_Custom::HASH_TT) { + // TT をこっそりチェック。 + if (TTMatch(laddr).IsOK()) { + // 一致した。 + // CI はここでは使わないため、一致したことだけわかればよい + putlog(5, "%s: result %08x (match tt)", __func__, laddr.Addr()); + return laddr; } + return BusAddr::BusErr; + } else { + if (__predict_false(mmu_tc.e == false)) { + // TC.E 無効 + putlog(5, "%s: result %08x (no translation)", __func__, + laddr.Addr()); + return laddr; + } + // TC.E 有効で対応なしなら、こっそりテーブルサーチ。 + // FALLTHROUGH + } +#else + // まず TT をこっそりチェック。 + if (TTMatch(laddr).IsOK()) { + // 一致した。 + // CI はここでは使わないため、一致したことだけわかればよい + putlog(5, " %s: result %08x (match tt)", __func__, laddr.Addr()); + return laddr; + } + + // TC.E が無効ならここまで。 + if (__predict_false(mmu_tc.e == false)) { + putlog(5, " %s: result %08x (no translation)", __func__, laddr.Addr()); + return laddr; + } + + // こっそり ATC を検索。 + auto table = atc.GetTable(); + a = table->LookupByDebugger(laddr); + if (a) { + paddr = busaddr(a->GetPAddr() | (laddr.Addr() & mmu_tc.pgmask)); + putlog(5, "%s: result %08x (match atc)", __func__, paddr.Addr()); + return paddr; } +#endif - mmu_enable_changed(); -} + // こっそりテーブルサーチ。 + m68030PageTableOp pt(this, loglevel, m68030PageTableOp::ByDebugger); + paddr = pt.SearchByDebugger(laddr); + paddr |= BusAddr::TableSearched; -// アクセスがこの TT とマッチするか調べる。 -// ssw_laddr は上位32ビットが cpu->bus.ssw、下位32ビットが論理アドレス -// (下24ビットは呼び出し側でマスクしなくてよい)。 -// この TT が有効でアドレスがマッチすれば true を返す。 -// (TT:E は演算に織り込んである、このすぐ上の SetTT() のコメント参照) -bool -m68030TT::Match(uint64 ssw_laddr) -{ - bool rv = (((ssw_laddr ^ base) & mask) == 0); - return rv; + if (paddr.IsBusErr()) { + putlog(5, " %s: result BusErr (table search)", __func__); + } else { + paddr |= laddr.Addr() & mmu_tc.pgmask; + putlog(5, " %s: result %08x (table search)", __func__, paddr.Addr()); + } + return paddr; } -// 成功すれば true、MMU 構成例外が起きる場合は false を返す。 -// 呼び出し側で例外を起こすこと。 -bool -m68kcpu::SetTC(uint32 data) +// TT から laddr を検索する。 +// laddr が TT0/TT1 どちらかと一致すれば CI ビットを反映したアドレスを返す。 +// 一致しなければ BusErr を返す。バスエラーではないが単に無効値の意。 +busaddr +MPU68030Device::TTMatch(const busaddr laddr) const { - // マスクしてレジスタイメージにセット - reg.tc = data & m68030TC::MASK; - - // data を分解してメンバにセット - mmu_tc.Set(data); + int match = 0; + bool ci = false; - // 有効にする場合は一貫性を確認 - if (mmu_tc.e) { - if (mmu_tc.Check() == false) { - // エラーなら E をクリアして MMU 構成例外。 - // (例外処理自体は呼び出し側で行う) - mmu_tc.e = 0; - mmu_enable_changed(); - return false; + for (int i = 0; i < 2; i++) { + const m68030TT& tt = mmu_tt[i]; + if (tt.Match(laddr)) { + match += i + 1; + if (__predict_true(tt.ci)) { + ci = true; + } } } - // マスクをあらかじめ用意しておく - mmu_tc.MakeMask(); - - // TC:E が変更されていればスイッチ更新 - mmu_enable_changed(); - return true; - + if (match != 0) { + busaddr paddr = laddr; + if (ci) { + paddr |= BusAddr::CInhibit; + } + putlog(4, " %s: result %08x (match TT%s)", __func__, + paddr.Addr(), + (match == 3) ? "0 and TT1" : ((match == 1) ? "0" : "1")); + return paddr; + } + // 一致しなかった。 + return BusAddr::BusErr; } -// TC レジスタへの書き込み値を m68030TC クラスにセットする。 +// ページテーブルから laddr を検索する。 +// 見付かっても見付からなくても、a を更新して返す。 +// ここでは指定の a を埋めるだけで a の移動は呼び出し側の責任。 void -m68030TC::Set(uint32 data) +MPU68030Device::PageTableSearch(busaddr laddr, m68030ATCLine *a) { - e = data & TC_E; - sre = data & TC_SRE; - fcl = data & TC_FCL; - tid = (data) & 0x0f; - tic = (data >>= 4) & 0x0f; - tib = (data >>= 4) & 0x0f; - tia = (data >>= 4) & 0x0f; - is = (data >>= 4) & 0x0f; - ps = (data >>= 4) & 0x0f; + m68030PageTableOp pt(this, loglevel, m68030PageTableOp::ByTranslation); + pt.Search(7, laddr); + pt.MakeATC(atc, a); } -// 現在の値が MMU 構成例外になるなら false を返す。 -bool -m68030TC::Check() const + +#if defined(M68030_CUSTOM_ATC) +// +// ATC テーブル (Custom) +// + +// アドレス変換の本来のフローはこんな感じだが、 +// +// if (TT*.E || TC.E) { +// if (TT*.E が有効) { +// laddr,RW が TT[01] とマッチしたら、paddr = laddr,CI +// } +// if (TC.E が有効) { +// ATC とマッチしたら、paddr = atc[n].paddr,CI +// そうでなければ、 paddr = テーブルサーチ +// } +// } else { +// paddr = laddr (アドレス変換オフ) +// } +// +// ハッシュで一度に引く。 +// switch (hash[laddr>>12]) { +// case HASH_TT: +// laddr,RW が TT[01] とマッチしたら、paddr = laddr,CI +// case HASH_ATC: +// paddr = atc[n].paddr,CI +// case HASH_NONE: +// TC.E なら paddr = テーブルサーチ +// そうでなければ、paddr = laddr (アドレス変換オフ) +// case HASH_SUB: +// PS が 4KB 未満だとハッシュの1エントリに2つ以上が混在するので、 +// この場合は諦めて ATC を線形探索。通常起きないので性能無視。 +// } + +// ATC テーブルは本来は 1つ(FC混合) x 22 ラインだが、 +// 高速化のため FC別(4テーブル) x 22 ラインのモードを用意する。 +// FC 混合版ではテーブルは tables[0] のみで、FC(1,0,2) は busaddr の +// 34-32 ビット部分として laddr と一緒に検索キーにする。 +// FC 分離版ではテーブルは fctables[fc] で検索キーは laddr(下位32ビット部分)。 +// +// ハッシュなのでライン間の順序関係はないが、LRU で古いのを捨てる時や +// PS<4K の時に前から探索する時のために、age を使った順序管理をする。 +// ラインを使用するたびに age に ++latest_age を代入しておくことで、 +// 順序が必要な時には age の大きい順にすれば最近使った順になる。 +// 32 ビット符号なし整数が折り返すと事故るがたぶんそれより高い頻度で +// フラッシュが起きるんじゃないかな… + +// コンストラクタ +m68030ATCTable_Custom::m68030ATCTable_Custom(MPU68030Device *cpu_, uint fc_) { - if (ps < 8) - return false; - if (tia == 0) - return false; - if (tib == 0 && (tic > 0 || tid > 0)) - return false; - if (tic == 0 && tid > 0) - return false; - if (ps + is + tia + tib + tic + tid != 32) - return false; + cpu = cpu_; + fc = fc_; - return true; + ClearHash(); + Flush(); } -// 論理マスク、ページマスクを作成。(テーブルサーチで参照される) +// ハッシュをすべてクリアする。 void -m68030TC::MakeMask() +m68030ATCTable_Custom::ClearHash() { - // lmask (論理マスク) は TIA〜TID 部分のマスク。 - // pgmask (ページマスク) は PS 部分のマスク。 - // - // 例えば IS=$8, TIA=$3, TIB=$4, TIC=$4, TID=$0, PS=$d - // (X68030 IPL 内の MMU 判定のケース) なら - // - // 3 2 1 0 - // 10987654 32109876 54321098 76543210 - // +--------+--------+--------+--------+ - // |IIIIIIII AAABBBBC CCCPPPPP PPPPPPPP| - // +--------+--------+--------+--------+ - // lmask = 00000000 11111111 11100000 00000000 - // pgmask = 00000000 00000000 00011111 11111111 - lmask = (1 << (tia + tib + tic + tid)) - 1; - lmask <<= ps; - pgmask = (1 << ps) - 1; + std::fill(hash.begin(), hash.end(), HASH_NONE); + latest_age = 0; +} - // アドレスを (IS,) TIA 〜 TID に分解。 - // - // TIA=$C, TIB=$A, (TIC = TID = 0), PS=$A の場合以下のようにする。 - // 使ってない TIC, TID については tix[TIC] == 0 で判断する。 - // - // A B PS - // +----------------+--------------+--------------+ - // $00A01A00 | 0000 0000 1010 | 0000 0001 10 | xx xxxx xxxx | - // +----------------+--------------+--------------+ - // shift[TIA]=20 shift[TIB]=10 - // mask[TIA]=0xfff mask[TIB]=0x3ff - int sh = ps; - for (int i = TID; i >= TIA; i--) { - if (tix[i] > 0) { - shift[i] = sh; - mask[i] = (1 << tix[i]) - 1; - sh += tix[i]; +// ハッシュを完全に作り直す。 +void +m68030ATCTable_Custom::Rehash() +{ + ClearHash(); + + // ページサイズも再調達。 + ps4k = cpu->mmu_tc.ps - 12; + + // ATCLine を適用。 + if (cpu->mmu_tc.e) { + for (int i = 0; i < LINES; i++) { + const m68030ATCLine *a = &line[i]; + if (a->IsValid()) { + MakeHash(a); + } + } + } + + // 続いて TT を適用。検索時は TT が優先なので TT は後から適用。 + for (int n = 0; n < 2; n++) { + const m68030TT& tt = cpu->mmu_tt[n]; + if (tt.e) { + busaddr laddr = busaddr(0) | busaddr::FC(fc); + for (uint i = 0; i < 256; i++) { + // XXX R/W は? + if (tt.Match(laddr)) { + uint32 baseidx = i << (24 - 12); + memset(&hash[baseidx], HASH_TT, 4096); + } + laddr += 0x0100'0000; + } } } } +// この ATC テーブルのエントリをすべてフラッシュする。 void -m68kcpu::SetMMUSR(uint16 data) +m68030ATCTable_Custom::Flush() { - reg.mmusr = data & m68030MMUSR::MASK; + for (int i = 0; i < LINES; i++) { + Invalidate(&line[i]); + line[i].age = 0; + } + latest_age = 0; } - -// -// ATC テーブル -// - -// コンストラクタ -m68030ATCTable::m68030ATCTable() +// この ATC テーブルから addr が一致するエントリをすべてフラッシュする。 +void +m68030ATCTable_Custom::Flush(uint32 addr) { -#if !defined(ATC_SINGLE) - for (int i = 0; i < countof(line); i++) { - line[i].Invalidate(); - } - head = &line[0]; -#else - for (int i = 0; i < countof(line); i++) { - line[i].Invalidate(); - insert_tail(&line[i]); + for (int i = 0; i < LINES; i++) { + m68030ATCLine *a = &line[i]; + if (a->GetTag() == addr) { + Invalidate(a); + } } -#endif } -#if !defined(ATC_SINGLE) -// a のエントリを先頭にする。 -m68030ATCLine * -m68030ATCTable::MoveHead(m68030ATCLine *a) +// この要素を先頭に移動。 +inline void +m68030ATCTable_Custom::MoveHead(m68030ATCLine *a) { - // head を向け直す - head = a; - - // 更新 - head->age = counter++; + a->age = ++latest_age; - // そのエントリを返す - return head; + if (__predict_false(a->age == 0)) { + // latest_age が1周したので、一旦全部リセット。 + for (int i = 0; i < LINES; i++) { + line[i].age = 0; + } + a->age = ++latest_age; + } } -#endif +// このテーブルのうち最も古い要素を差し出す。 +m68030ATCLine * +m68030ATCTable_Custom::Pop() +{ + m68030ATCLine *a = NULL; + uint32 min = (uint32)~0; + uint32 minidx = 0; -// -// ATC -// + // 最後に使われたの (あるいは空き) を探す。 + for (int i = 0; i < LINES; i++) { + if (line[i].IsInvalid()) { + return &line[i]; + } + if (line[i].age < min) { + min = line[i].age; + minidx = i; + } + } + if (a == NULL) { + a = &line[minidx]; + } -// コンストラクタ -m68030ATC::m68030ATC(m68kcpu *parent) -{ - cpu = parent; + // ハッシュから削除。 + Invalidate(a); + + return a; } -#if defined(ATC_SINGLE) -// item をリストの先頭に挿入。 +// a からハッシュを作成する。 +// TT.E 有効で a は有効であること。 void -m68030ATCTable::insert_head(m68030ATCLine *item) +m68030ATCTable_Custom::MakeHash(const m68030ATCLine *a) { - item->prev = NULL; - item->next = head; - if (head) { - head->prev = item; + //assert(cpu->mmu_tc.e); + //assert(a->IsValid()); + + uint32 addr4k = a->GetLAddr() >> 12; + int idx = a - &line[0]; + + if (__predict_true(ps4k == 0)) { + hash[addr4k] = idx; + } else if (ps4k > 0) { + for (int i = 0; i < (1U << ps4k); i++) { + hash[addr4k + i] = idx; + } + } else { + hash[addr4k] = HASH_SUB; } - head = item; } -// item をリストの先頭から2番目に挿入。 -void -m68030ATCTable::insert_second(m68030ATCLine *item) -{ - item->prev = head; - if (head) { - item->next = head->next; - if (head->next) { - head->next->prev = item; +// ATC a を無効にする。すでに無効なら何もしない。 +// +// ハッシュから削除する際は TT を指していないか必ず確認すること。 +// ATC エントリが出来た後で、そこに TT に設定すると起きうる。 +// LRU の ATC ならそのエントリは参照されずに消えていくだけで害はないが、 +// ハッシュは TT で上書きされているので消す際に注意する必要がある。 +void +m68030ATCTable_Custom::Invalidate(m68030ATCLine *a) +{ + if (a->IsValid()) { + // 有効ならハッシュから削除。 + if (__predict_true(ps4k == 0)) { + uint32 addr4k = a->GetLAddr() >> 12; + if ((int8)hash[addr4k] >= 0) { + hash[addr4k] = HASH_NONE; + } + } else if (ps4k > 0) { + // PS>4KB + uint32 addr4k = a->GetLAddr() >> 12; + for (int i = 0; i < (1U << ps4k); i++) { + if ((int8)hash[addr4k + i] >= 0) { + hash[addr4k + i] = HASH_NONE; + } + } + } else { + // PS<4KB、自分が抜けることでこの枠が空くかどうか + InvalidateSub(a); } - head->next = item; - } else { - item->next = NULL; + + a->Invalidate(); } } -// item をリストの末尾に追加。 +// PS<4KB の場合に aa が抜けることでこの addr4k の枠が空けばハッシュから削除。 void -m68030ATCTable::insert_tail(m68030ATCLine *item) +m68030ATCTable_Custom::InvalidateSub(const m68030ATCLine *aa) { - item->next = NULL; - if (head == NULL) { - item->prev = NULL; - head = item; - } else { - m68030ATCLine *a = head; - while (a->next) - a = a->next; - a->next = item; - item->prev = a; + const uint32 MASK_4K = 0xfffff000 | m68030ATCLine::INVALID; + uint32 target_laddr = aa->GetTag() & MASK_4K; + bool in_use = false; + + for (auto& e : line) { + m68030ATCLine *a = &e; + + // 自分自身は今から削除予定なので除く。 + if (a == aa) { + continue; + } + // それ以外で有効なものがあるか。 + if ((a->GetTag() & MASK_4K) == target_laddr) { + in_use = true; + break; + } + } + + // 誰もいなくなれば削除。 + if (in_use == false) { + uint k = target_laddr >> 12; + if (hash[k] != HASH_SUB) { + hash[k] = HASH_NONE; + } } } -// item をリストから外す -void -m68030ATCTable::remove(m68030ATCLine *item) +// laddr を ATC から探す。見つからなければ NULL を返す。 +m68030ATCLine * +m68030ATCTable_Custom::FindSub(const busaddr laddr) { - if (item->prev) - item->prev->next = item->next; - if (item->next) - item->next->prev = item->prev; - if (item == head) - head = item->next; + uint32 addr = laddr.Addr() & lmask; + + // 同じエントリはないはずなので、順序通りに探さなくてもいいはず。 + for (auto& e : line) { + m68030ATCLine *a = &e; + if (a->GetTag() == addr) { + return a; + } + } + return NULL; } -// item をリストの先頭に移す。 -// fill_fetch() から呼ばれるもので、リストは空でなく、item は先頭から -// 3番目以内ではない、という前提で高速化。 +// 統計情報をリセットする。 void -m68030ATCTable::move_head(m68030ATCLine *item) +m68030ATCTable_Custom::ResetStat() { - // 先頭でない前提の remove - item->prev->next = item->next; - if (item->next) - item->next->prev = item->prev; + stat.Clear(); - // 先頭がある前提の insert_head - item->prev = NULL; - item->next = head; - head->prev = item; - head = item; + std::lock_guard lock(hist_mtx); + hist.Clear(); + hist_tt_once_hit = false; } -// item を先頭から2番目に移す。 -// fill_{read,write} から呼ばれるもので、リストは空でなく、item は先頭から -// 3番目以内ではない、という前提で高速化。 -void -m68030ATCTable::move_second(m68030ATCLine *item) +// デバッグ表示。FC は Table_Custom が持っている。 +std::string +m68030ATCTable_Custom::LineToStr(const m68030ATCLine *a) const { - // 先頭でない前提の remove - item->prev->next = item->next; - if (item->next) - item->next->prev = item->prev; - - // 先頭2つがある前提の insert_second - item->prev = head; - item->next = head->next; - head->next->prev = item; - head->next = item; + return LineToStrCommon(a, fc); } -#endif // ATC_SINGLE -// fill_{fetch,read,write}() が各アクセス用の ATC サーチ。 -// 論理アドレス fc:addr が ATC にあればそのエントリを返す。 -// なければ ATC を更新してそのエントリを返す。 -// fill_peek() はデバッグ用なので一切状態を更新せず結果だけ返す。 +#else +// +// ATC テーブル (Motorola) +// -const m68030ATCLine& -m68030ATC::fill_fetch() +// コンストラクタ +m68030ATCTable_Motorola::m68030ATCTable_Motorola(MPU68030Device *cpu_) { - uint32 laddr = cpu->bus.laddr & cpu->mmu_tc.lmask; - uint fc = cpu->bus.GetFC(); - m68030ATCTable *table; - m68030ATCLine *a; - int i; + cpu = cpu_; -#if !defined(ATC_SINGLE) - table = &tables[fc]; - // キャッシュとマッチすればそれを返す - a = table->head; - if (a->GetTag() == laddr) { - FILL_PRINT(fc, a); - table->hit_head++; - return *a; - } - for (i = 0; i < countof(table->line); i++) { - a = &table->line[i]; - if (a->GetTag() == laddr) { - // ヒットしたので先頭に移動。fetch は常に入れ替え - a = table->MoveHead(a); - - FILL_PRINT(fc, a); - table->hit[i]++; - return *a; - } + for (uint pri = 0; pri < LINES; pri++) { + Set(pri, pri); } +} - // 見付からなかったので、古いエントリを1つ更新。 - a = table->MoveHead(table->Lookup()); - table->hit[m68030ATCTable::LINES]++; - // タグはこっちで初期化 - a->tag = laddr; -#else - table = &tables[0]; - // キャッシュとマッチすればそれを返す - a = table->head; - for (i = 0; a; a = a->next, i++) { - if (a->GetTag() == laddr && a->fc == fc) { - // ヒットしたので先頭に移動。 - if (i > 2) { - table->move_head(a); - } - - FILL_PRINT(fc, a); - table->hit[i]++; - return *a; - } +// pri 番目に idx をセットする。 +void +m68030ATCTable_Motorola::Set(uint pri, uint idx) +{ +#if defined(ATC_ORDER64) + // セット先がクリアされてることが分かっている場合にはこれは不利。 + uint i; + if (__predict_true(pri < ORDER0_COUNT)) { + i = 0; + } else { + i = 1; + pri -= ORDER0_COUNT; } - - // 見付からなかったので、古いエントリを1つ更新。 - a = table->lookup(); - table->remove(a); - table->insert_head(a); - table->hit[m68030ATCTable::LINES]++; - // タグはこっちで初期化 - a->tag = laddr; - a->fc = fc; + order[i] &= ~(0x1fULL << (pri * 5)); + order[i] |= (uint64)idx << (pri * 5); +#else + order[pri] = idx; #endif +} - // テーブルサーチして結果を ATC に入れる - m68030MMU mmu(cpu); - mmu.search(); - mmu.make_atc(a); +// laddr が ATC の何番目かを返す。なければ -1 を返す。 +int +m68030ATCTable_Motorola::Lookup(const busaddr laddr) +{ + uint32 tag = MakeTag(laddr); + +#define COMPARE(idx, pri) do { \ + m68030ATCLine *a = &line[(idx)]; \ + if (__predict_true(a->GetTag() == tag)) \ + return (pri); \ +} while (0) - FILL_PRINT(fc, a); - return *a; +#if defined(ATC_ORDER64) + uint j; + uint64 order_ = order[0]; + for (j = 0; j < ORDER0_COUNT; j++) { + uint idx = order_ & 0x1f; + order_ >>= 5; + COMPARE(idx, j); + } + order_ = order[1]; + for (; j < M68030_ATC_LINES; j++) { + uint idx = order_ & 0x1f; + order_ >>= 5; + COMPARE(idx, j); + } +#else + for (uint pri = 0; pri < LINES; pri++) { + uint idx = GetIdx(pri); + COMPARE(idx, pri); + } +#endif + return -1; } -const m68030ATCLine& -m68030ATC::fill_read() +// ATC から laddr を検索する。 +// 見付かればこのエントリを先頭に移動してそれを返す。 +// 見付からなければ NULL を返す。 +m68030ATCLine * +m68030ATCTable_Motorola::LookupAndMove(const busaddr laddr) { - uint32 laddr = cpu->bus.laddr & cpu->mmu_tc.lmask; - uint fc = cpu->bus.GetFC(); - m68030ATCTable *table; - m68030ATCLine *a; - int i; + int pri = Lookup(laddr); + if (__predict_true(pri >= 0)) { + // 先頭から何番目で見付かったか記録し、先頭に移動。 + stat.hit[pri]++; + + const m68030ATCLine *a = GetLine(pri); + MMULOG(4, 3, "ATC.LookupAndMove: [%u]@%u: %u(%s)%08x", + GetIdx(pri), pri, + a->GetFC(), + MPU68030Device::FCstr(a->GetFC()), + a->GetPAddr()); -#if !defined(ATC_SINGLE) - table = &tables[fc]; - // キャッシュとマッチすればそれを返す - a = table->head; - if (a->GetTag() == laddr) { - FILL_PRINT(fc, a); - table->hit_head++; - return *a; - } - for (i = 0; i < countof(table->line); i++) { - a = &table->line[i]; - if (a->GetTag() == laddr) { - // ヒットしたので先頭に移動。 - a = table->MoveHead(a); - - FILL_PRINT(fc, a); - table->hit[i]++; - return *a; + if (__predict_false(pri != 0)) { + MoveHead(pri); } + return GetLine(0); + } else { + stat.miss++; + MMULOG(4, 3, "ATC.LookupAndMove: not match"); + return NULL; } +} - // 見付からなかったので、古いエントリを1つ更新 - a = table->MoveHead(table->Lookup()); - table->hit[m68030ATCTable::LINES]++; - // タグはこっちで初期化 - a->tag = laddr; -#else - table = &tables[0]; - // キャッシュとマッチすればそれを返す - a = table->head; - for (i = 0; a; a = a->next, i++) { - if ((a->GetTag() == laddr) && (a->fc == fc)) { - // ヒットしたので先頭から2番目に移動 - if (i > 2) { - table->move_second(a); - } - - FILL_PRINT(fc, a); - table->hit[i]++; - return *a; - } +// ATC から laddr を検索する。エントリは変更しない。(PTEST 用) +const m68030ATCLine * +m68030ATCTable_Motorola::LookupOnly(const busaddr laddr) +{ + int pri = Lookup(laddr); + if (__predict_true(pri >= 0)) { + return GetLine(pri); + } else { + return NULL; } - - // 見付からなかったので、古いエントリを1つ更新 - a = table->lookup(); - table->remove(a); - table->insert_head(a); - table->hit[m68030ATCTable::LINES]++; - // タグはこっちで初期化 - a->tag = laddr; - a->fc = fc; -#endif - - // テーブルサーチして結果を ATC に入れる - m68030MMU mmu(cpu); - mmu.search(); - mmu.make_atc(a); - - FILL_PRINT(fc, a); - return *a; } -const m68030ATCLine& -m68030ATC::fill_write() +// ATC をすべてフラッシュする。 +void +m68030ATCTable_Motorola::Flush() { - uint32 laddr = cpu->bus.laddr & cpu->mmu_tc.lmask; - uint fc = cpu->bus.GetFC(); - m68030ATCTable *table; - m68030ATCLine *a; - int i; + // 全部無効にするので、無効にしたのを最後尾に回す必要ない。 + for (auto& e : line) { + m68030ATCLine *a = &e; + a->Invalidate(); + } +} -#if !defined(ATC_SINGLE) - table = &tables[fc]; - // キャッシュとマッチすればそれを返す - a = table->head; - if (a->GetTag() == laddr) { - // ライトアクセスなので、WP でなく Modified == 0 なら - // Modified を立てるため、このエントリを破棄して改めて - // テーブルサーチを実行する。 - if (!a->IsWProtect() && !a->IsModified()) { - FILL_PRINT(fc, a); - MMULOG(3, 1, "ATC_fill: write access but M is not set, " - "so invalidate it"); +// ATC の fc, mask が一致するエントリをフラッシュする。 +void +m68030ATCTable_Motorola::Flush(uint32 fc, uint32 mask) +{ + for (uint pri = 0; pri < LINES; pri++) { + m68030ATCLine *a = GetLine(pri); + if (((a->GetFC() ^ fc) & mask) == 0) { a->Invalidate(); - goto search; + MoveTail(pri); } - FILL_PRINT(fc, a); - table->hit_head++; - return *a; - } - for (i = 0; i < countof(table->line); i++) { - a = &table->line[i]; - if (a->GetTag() == laddr) { - // ライトアクセスなので、WP でなく Modified == 0 なら - // Modified を立てるため、このエントリを破棄して改めて - // テーブルサーチを実行する。 - if (!a->IsWProtect() && !a->IsModified()) { - FILL_PRINT(fc, a); - MMULOG(3, 1, "ATC_fill: write access but M is not set, " - "so invalidate it"); - a->Invalidate(); - break; - } - // ヒットしたので先頭に移動。 - a = table->MoveHead(a); + } +} - FILL_PRINT(fc, a); - table->hit[i]++; - return *a; +// ATC の fc, mask, addr が一致するエントリをフラッシュする。 +void +m68030ATCTable_Motorola::Flush(uint32 fc, uint32 mask, uint32 addr) +{ + for (uint pri = 0; pri < LINES; pri++) { + m68030ATCLine *a = GetLine(pri); + if (a->GetLAddr() == addr && ((a->GetFC() ^ fc) & mask) == 0) { + a->Invalidate(); + MoveTail(pri); } } - search:; +} - // 見付からなかったので、古いエントリを1つ更新 - a = table->MoveHead(table->Lookup()); - table->hit[m68030ATCTable::LINES]++; - // タグはこっちで初期化 - a->tag = laddr; +// pri 番目の要素を先頭に移動する。 +// pri がゼロ(先頭)のケースは呼び出し元で事前に弾いておくこと。 +void +m68030ATCTable_Motorola::MoveHead(uint pri) +{ + uint idx = GetIdx(pri); +#if defined(ATC_ORDER64) + if (__predict_true(pri < ORDER0_COUNT)) { + uint64 maskl = (~0ULL) << (pri * 5); + uint64 maskh = maskl << 5; + uint64 tmpl = (order[0] & ~maskl) << 5; + uint64 tmph = (order[0] & maskh); + order[0] = tmph | tmpl | idx; + } else { + uint j = pri - ORDER0_COUNT; + uint64 maskl = (~0ULL) << (j * 5); + uint64 maskh = maskl << 5; + uint64 tmpl = (order[1] & ~maskl) << 5; + uint64 tmph = (order[1] & maskh); + uint64 tmpp = (order[0] >> ((ORDER0_COUNT - 1) * 5)) & 0x1f; + order[1] = tmph | tmpl | tmpp; + constexpr uint64 mask = (1ULL << (ORDER0_COUNT * 5)) - 1; + order[0] = ((order[0] << 5) & mask) | idx; + } #else - table = &tables[0]; - // キャッシュとマッチすればそれを返す - a = table->head; - for (i = 0; a; a = a->next, i++) { - if ((a->GetTag() == laddr) && (a->fc == fc)) { - // ライトアクセスなので、WP でなく Modified == 0 なら - // Modified を立てるため、このエントリを破棄して改めて - // テーブルサーチを実行する。 - if (!a->IsWProtect() && !a->IsModified()) { - FILL_PRINT(fc, a); - MMULOG(3, 1, "ATC_fill: write access but M is not set, " - "so invalidate it"); - a->Invalidate(); - break; - } - - // ヒットしたので、先頭から2番目に移動 - if (i > 2) { - table->move_second(a); - } - - FILL_PRINT(fc, a); - table->hit[i]++; - return *a; - } + for (uint d = pri; d > 0; d--) { + order[d] = order[d - 1]; } - - // 見付からなかったので、古いエントリを1つ更新 - a = table->lookup(); - table->remove(a); - table->insert_head(a); - // 論理部分(タグ部分)を初期化 - a->tag = laddr; - a->fc = fc; - table->hit[m68030ATCTable::LINES]++; + order[0] = idx; #endif - - // テーブルサーチして結果を ATC に入れる - m68030MMU mmu(cpu); - mmu.search(); - mmu.make_atc(a); - - FILL_PRINT(fc, a); - return *a; } -// ピークアクセス用に ATC を検索する。 -// ATC になければ (uint64)-1 を返す。ここでは疑似テーブルサーチは行わない。 -// laddr は論理ページアドレスにマスクしておくこと。 -// 戻り値はいずれの場合も物理ページアドレスか、バスエラーなら (uint64)-1。 -uint64 -m68030ATC::fill_peek(uint32 laddr, uint fc) +// pri 番目の要素を末尾に移動する。 +// pri が末尾でも呼び出してよい。 +void +m68030ATCTable_Motorola::MoveTail(uint pri) { - m68030ATCTable *table; - m68030ATCLine *a; - -#if !defined(ATC_SINGLE) - // キャッシュとマッチすればそれを返す - table = &tables[fc]; - for (int i = 0; i < countof(table->line); i++) { - a = &table->line[i]; - if (a->GetTag() == laddr) { - // ヒットした - uint32 paddr = a->GetPAddr(); - MMULOG(3, 1, "fill_peek: match %d:$%08x -> $%08x", - fc, laddr, paddr); - return paddr; - } + if (pri == LINES - 1) { + return; + } + uint idx = GetIdx(pri); +#if defined(ATC_ORDER64) + // コメント欄は 5ビットx22エントリを1文字x8桁で模式的に表現したもの。 + // 110ビットより上に unused なビットがあるが、0 のままのはず。 + // order = x'87654321 + constexpr uint TAIL0 = (ORDER0_COUNT - 1) * 5; + constexpr uint TAIL1 = (ORDER1_COUNT - 1) * 5; + if (pri < ORDER0_COUNT) { + uint64 mask = (~0ULL) << (pri * 5); // F'FFFFF000 (ex. pri=3) + uint64 tmph = (order[0] >> 5) & mask; // x'x8765000 (xは0のはず) + uint64 tmpl = (order[0] & ~mask); // 0'00000321 + uint64 tmpb = (order[1] & 0x1f) << TAIL0; // 0'n0000000 + order[0] = tmph | tmpl | tmpb; + order[1] = (order[1] >> 5) | ((uint64)idx << TAIL1); + } else { + uint j = pri - ORDER0_COUNT; + uint64 mask = (~0ULL) << (j * 5); + uint64 tmph = (order[1] >> 5) & mask; + uint64 tmpl = (order[1] & ~mask); + uint64 tmpb = (uint64)idx << TAIL1; + order[1] = tmph | tmpl | tmpb; } #else - // キャッシュとマッチすればそれを返す - table = &tables[0]; - for (a = table->head; a; a = a->next) { - if ((a->GetTag() == laddr) && ((a->fc & 4) == fc)) { - // ヒットした - uint32 paddr = a->GetPAddr(); - MMULOG(3, 1, "fill_peek: match %d:$%08x -> $%08x", - a->fc, laddr, paddr); - return paddr; - } + uint d; + for (d = pri; d < LINES - 2; d++) { + order[d] = order[d + 1]; } + order[d] = idx; #endif +} - return (uint64)-1; +// 空き要素あるいは最も古い要素を先頭に移動する。 +m68030ATCLine * +m68030ATCTable_Motorola::Pop() +{ + MoveHead(LINES - 1); + return GetLine(0); +} + +// 統計情報をリセットする。 +void +m68030ATCTable_Motorola::ResetStat() +{ + stat.Clear(); +} + +// デバッグ表示。FC は a 自身が持っている。 +/*static*/ std::string +m68030ATCTable_Motorola::LineToStr(const m68030ATCLine *a) +{ + return LineToStrCommon(a, a->GetFC()); } -// デバッグ表示。 -// ATC_SINGLE なら fc は ATCLine に含まれているため、引数はダミーで -// 変数名が衝突するため仮引数名も変えてある。なんだかなあ。 -// XXX print といいつつ string を返す -#if defined(ATC_SINGLE) -std::string -m68030ATCLine::fill_print(uint dummyfc) -#else -std::string -m68030ATCLine::fill_print(uint fc) #endif + +// デバッグ表示。 +/*static*/ std::string +m68030ATCTable::LineToStrCommon(const m68030ATCLine *a, uint fc_) { - uint32 laddr = GetLAddr(); + uint32 laddr = a->GetLAddr(); - if (!IsBusError()) { - uint32 wp = IsWProtect(); - uint32 mod = IsModified(); + if (!a->IsBusError()) { + uint32 paddr = a->GetPAddr(); + char ci = a->IsCInhibit() ? 'C' : '-'; + char wp = a->IsWProtect() ? 'W' : '-'; + char mod = a->IsModified() ? 'M' : '-'; return indent_format(1, - "ATC_fill: match %d:$%08x -> $%08x WP=%d M=%d", - fc, laddr, GetPAddr(), wp, mod); + "%u.%08x %08x %c%c%c", + fc_, laddr, paddr, ci, wp, mod); } else { return indent_format(1, - "ATC_fill: match %d:$%08x -> BusErr", - fc, laddr); + "%u.%08x BusError", + fc_, laddr); } } -#if !defined(ATC_SINGLE) -// 更新用にエントリを1つ差し出す。 -m68030ATCLine * -m68030ATCTable::Lookup() + +// +// ATC +// + +// コンストラクタ +m68030ATC::m68030ATC(MPU68030Device *cpu_) { - uint32 minage; - int minidx; + cpu = cpu_; - // 空きエントリを探しながら、最古エントリを探しておく。 - // 最古エントリは age が最も小さいもの。 - minage = (uint32)-1; - minidx = 0; - for (int i = 0; i < countof(line); i++) { - m68030ATCLine& a = line[i]; - if (a.IsInvalid()) { - MMULOG(3, 1, "ATC_lookup got free entry [%d]", i); - return &a; - } - if (a.age < minage) { - minage = a.age; - minidx = i; - } - } - // 空きがなければ、最古のエントリを差し出す - MMULOG(3, 1, "ATC_lookup got oldest entry [%d]", minidx); - return &line[minidx]; -} +#if defined(M68030_CUSTOM_ATC) + alltables[0].reset(new m68030ATCTable_Custom(cpu, 1)); + alltables[1].reset(new m68030ATCTable_Custom(cpu, 2)); + alltables[2].reset(new m68030ATCTable_Custom(cpu, 5)); + alltables[3].reset(new m68030ATCTable_Custom(cpu, 6)); + + fctables[1] = alltables[0].get(); + fctables[2] = alltables[1].get(); + fctables[5] = alltables[2].get(); + fctables[6] = alltables[3].get(); #else -// 更新用にエントリを1つ差し出す。 -m68030ATCLine * -m68030ATCTable::lookup() + table.reset(new m68030ATCTable_Motorola(cpu)); +#endif +} + +// ログレベルの設定。 +// これは Object のオーバーライドではないが、同じ使い方をする。 +void +m68030ATC::SetLogLevel(int loglevel_) { - m68030ATCLine *a; + loglevel = loglevel_; - // 空きエントリを探す - for (int i = 0; i < LINES; i++) { - a = &line[i]; - if (a->IsInvalid()) { - MMULOG(3, 1, "ATC_lookup got free entry [%d]", i); - goto found; - } +#if defined(M68030_CUSTOM_ATC) + for (const auto& table : alltables) { + table->loglevel = loglevel_; } +#else + table->loglevel = loglevel_; +#endif +} - // 空きがなければ、リストの末尾が最古なのでこれを差し出す - a = head; - while (a->next) { - a = a->next; +#if defined(M68030_CUSTOM_ATC) +// ハッシュをすべて作り直す。 +void +m68030ATC::Rehash() +{ + for (const auto& table : alltables) { + table->Rehash(); } - MMULOG(3, 1, "ATC_lookup got oldest entry"); - - found: - return a; } #endif +// TC がセットされたので、こちらの二次変数も追従する。 +void +m68030ATC::SetTC(const m68030TC& tc) +{ +#if defined(M68030_CUSTOM_ATC) + for (const auto& table : alltables) { + table->SetMask(tc.lmask); + } +#else + table->SetMask(tc.lmask); +#endif +} + // ATC をすべてフラッシュする。命令からコールされる void -m68030ATC::flush() +m68030ATC::FlushAll() { - for (int i = 0; i < countof(tables); i++) { - tables[i].flush(); +#if defined(M68030_CUSTOM_ATC) + for (const auto& table : alltables) { + table->Flush(); } +#else + table->Flush(); +#endif } // ATC の fc, mask が一致するエントリをフラッシュする。 // 命令からコールされる。 void -m68030ATC::flush(uint32 fc, uint32 mask) +m68030ATC::Flush(uint32 fc, uint32 mask) { -#if !defined(ATC_SINGLE) - for (int i = 0; i < countof(tables); i++) { +#if defined(M68030_CUSTOM_ATC) + for (uint i = 1; i <= 6; i++) { if (((i ^ fc) & mask) == 0) { - // FC が一致したテーブルを全フラッシュ - tables[i].flush(); + // FC が一致したテーブルを全フラッシュ。 + if ((bool)fctables[i]) { + fctables[i]->Flush(); + } } } #else - for (int i = 0; i < 8; i++) { - if (((i ^ fc) & mask) == 0) { - // この FC をテーブルからフラッシュ - tables[0].flush_fc(i); - } - } + table->Flush(fc, mask); #endif } // ATC の fc, mask, addr が一致するエントリをフラッシュする。 // 命令からコールされる。 void -m68030ATC::flush(uint32 fc, uint32 mask, uint32 addr) +m68030ATC::Flush(uint32 fc, uint32 mask, uint32 addr) { -#if !defined(ATC_SINGLE) - for (int i = 0; i < countof(tables); i++) { +#if defined(M68030_CUSTOM_ATC) + for (uint i = 1; i <= 6; i++) { if (((i ^ fc) & mask) == 0) { - // FC が一致したテーブルから addr が一致したエントリをフラッシュ - tables[i].flush(addr); + // FC が一致したテーブルから addr が一致したエントリをフラッシュ。 + if ((bool)fctables[i]) { + fctables[i]->Flush(addr); + } } } #else - for (int i = 0; i < 8; i++) { - if (((i ^ fc) & mask) == 0) { - // FC と addr が一致したエントリをフラッシュ - tables[0].flush(i, addr); - } - } + table->Flush(fc, mask, addr); #endif } -// この ATC テーブルのエントリをすべてフラッシュする。 +// 統計情報をリセットする。 void -m68030ATCTable::flush() +m68030ATC::ResetStat() { - for (int i = 0; i < countof(line); i++) { - m68030ATCLine& a = line[i]; - a.Invalidate(); +#if defined(M68030_CUSTOM_ATC) + for (const auto& table : alltables) { + table->ResetStat(); } +#else + table->ResetStat(); +#endif } -#if !defined(ATC_SINGLE) -// addr が一致するエントリをすべてフラッシュ +// 統計情報を更新する。 void -m68030ATCTable::flush(uint32 addr) +m68030ATC::CalcStat() { - for (int i = 0; i < countof(line); i++) { - m68030ATCLine& a = line[i]; - if (a.GetLAddr() == addr) { - a.Invalidate(); +#if defined(M68030_CUSTOM_ATC) + for (const auto& table : alltables) { + // ここはロックなしでやってしまう + m68030ATCStat s; + s.total = table->stat.total; + s.tthit = table->stat.tthit; + s.miss = table->stat.miss; + table->stat.total = 0; + table->stat.tthit = 0; + table->stat.miss = 0; + + // こっちはロックする + { + std::lock_guard lock(table->hist_mtx); + + table->hist.EnqueueForce(s); + if (__predict_false(s.tthit != 0)) { + table->hist_tt_once_hit = true; + } } } -} #else -// FC が一致するエントリをすべてフラッシュする -void -m68030ATCTable::flush_fc(uint32 fc) +#endif +} + + +// +// テーブルサーチ操作。 +// + +// コンストラクタ +m68030PageTableOp::m68030PageTableOp(MPU68030Device *cpu_, int loglevel_, + m68030PageTableOp::From from_) { - for (int i = 0; i < countof(line); i++) { - m68030ATCLine& a = line[i]; - if (a.fc == fc) { - a.Invalidate(); - } - } + cpu = cpu_; + loglevel = loglevel_; + from = from_; } -// FC と addr が一致するエントリをすべてフラッシュ -void -m68030ATCTable::flush(uint32 fc, uint32 addr) +// ページテーブルから laddr を検索する。 +// 結果はメンバ変数に保持する。 +m68030PageTableOp::Result +m68030PageTableOp::Search(int maxlv, busaddr laddr) { - for (int i = 0; i < countof(line); i++) { - m68030ATCLine& a = line[i]; - if (a.fc == fc && a.GetLAddr() == addr) { - a.Invalidate(); + // テーブルサーチで書き込みの時だけ、ディスクリプタの M ビットを更新。 + if (__predict_false(laddr.IsWrite()) && + __predict_true(from == ByTranslation)) + { + writeback_m = true; + } + + // テーブルサーチを行う。 + result = DoSearch(maxlv, laddr); + if (__predict_true(result != Result::Invalid)) { + m_paddr = busaddr(m_desc2 & cpu->mmu_tc.lmask); + } + + // デバッグ表示。 + if (__predict_false(loglevel >= 4)) { + std::string buf; + switch (result) { + case Result::Invalid: + buf = "Invalid -> BusErr"; + break; + case Result::Normal: + buf = string_format("Normal paddr=%08x", m_paddr.Addr()); + break; + case Result::Indirect: + buf = string_format("Indirect paddr=%08x", m_paddr.Addr()); + break; + case Result::Early: + { + // 残ってるビットも表示。 + uint32 paddr = m_paddr.Addr(); + buf = string_format("Early paddr=%08x", paddr); + for (uint i = lv + 1; i <= cpu->mmu_tc.termlv; i++) { + paddr |= m_idx[i] << cpu->mmu_tc.shift[i]; + buf += string_format(" +%s=%08x", m_lvname[i], paddr); + } + break; + } } + MMULOG(4, 2, "PageTable.%s: %s", lam_func, buf.c_str()); } + + return result; } -#endif +// デバッガからの呼び出し用。 +// maxlv 不要なのと、ATC を作って返す必要もないので、単に変換後のアドレス +// (変換出来なければ BusErr) を返す。 +busaddr +m68030PageTableOp::SearchByDebugger(busaddr laddr) +{ + // writeback_m は false のまま。 -// -// テーブルサーチ -// + // テーブルサーチを行う。 + result = DoSearch(7, laddr); -// コンストラクタ -m68030MMU::m68030MMU(m68kcpu *parent) -{ - cpu = parent; + // ここからサーチ結果によって、結果の物理アドレスを作成。 + if (result == Result::Invalid) { + return BusAddr::BusErr; + } - m_type = INVALID; - m_s = 0; - m_wp = 0; - m_m = 0; - m_l = 0; - m_berr = false; - m_debugger = false; - m_ptest = false; - - x = -1; - memset(m_lv, 0, sizeof(m_lv)); - m_laddr = 0; - m_fc = -1; - m_desc1 = 0; - m_desc2 = 0; - m_descsize = 0; - m_lastsize = 0; -} - -// テーブルサーチのメイン部分。 -// m_type および ATC 作成に必要な情報を埋めてから帰る。 -// ATC は帰った先で作成する。 -void -m68030MMU::search() + uint32 paddr = m_desc2 & cpu->mmu_tc.lmask; + if (result == Result::Early) { + paddr = AssembleEarlyRemains(paddr); + } + + return busaddr(paddr); +} + +// laddr についてテーブルサーチを行い結果コード Result を返す。 +// その他の状態はメンバに保持する。 +m68030PageTableOp::Result +m68030PageTableOp::DoSearch(int maxlv, const busaddr laddr) { const char *rpname; - int dt; - bool is_super; - - m_laddr = cpu->bus.laddr; - m_fc = cpu->bus.GetFC(); + uint dt; - is_super = (m_fc & 0x04); + m_laddr = laddr; - // アドレスを (IS,) TIA .. TID に分解。 + // m_laddr のビットを TIA .. TID に分解。 // - // TIA=$C, TIB=$A, (TIC = TID = 0), PS=$A の場合以下のようにする。 - // 使ってない TIC, TID については cpu->mmu_tc->{tic,tid} == 0 で判断する。 + // 例えば TIA=$C, TIB=$A, (TIC = TID = 0), PS=$A の場合以下のようにする。 + // TIB の次の段は番兵。 // // A B PS // +----------------+--------------+--------------+ // $00A01A00 | 0000 0000 1010 | 0000 0001 10 | xx xxxx xxxx | // +----------------+--------------+--------------+ - // shift[TIA] =20 shift[TIB] =10 - // lv[TIA].idx=0xA lv[TIB].idx=0x6 + // shift[TIA]=20 shift[TIB]=10 + // mask[TIA]=0xfff mask[TIB]=0x3ff + // idx[TIA]=0xa idx[TIB]=0x6 // - for (int i = TID; i >= TIA; i--) { - if (cpu->mmu_tc.tix[i] > 0) { - uint32 mask = cpu->mmu_tc.mask[i]; - m_lv[i] = (m_laddr >> cpu->mmu_tc.shift[i]) & mask; - } + for (int i = LV_TIA, end = cpu->mmu_tc.termlv; i <= end; i++) { + uint shift = cpu->mmu_tc.shift[i]; + uint mask = cpu->mmu_tc.mask[i]; + m_idx[i] = (m_laddr.Addr() >> shift) & mask; } - m_lv[FCL] = m_fc; - cpulogf(3, [&] { - std::string buf; - - buf = indent_format(2, "search: laddr=$%08x ->", m_laddr); - if (cpu->mmu_tc.is > 0) - buf += string_format(" IS=%d", cpu->mmu_tc.is); - if (cpu->mmu_tc.fcl) - buf += string_format(" FC=%d", m_lv[FCL]); - for (int i = TIA; i <= TID; i++) { - if (cpu->mmu_tc.tix[i] > 0) { - buf += string_format(" %s=%d($%0*x)", - m_lvname[i], cpu->mmu_tc.tix[i], - NIBBLE(cpu->mmu_tc.tix[i]), m_lv[i]); - } - } - return buf; - }); - - // サーチ時の初期化処理。図9-26 - m_wp = 0; - m_s = 0; + // この辺に図9-26 の初期化だがコンストラクタで初期化済み。 // ここから図9-25。 - // 使用するルートポインタを決定 - if (cpu->mmu_tc.sre && is_super) { + // 使用するルートポインタを決定。 + // m_desc2 のマスクは使うところで行っている。 + if (__predict_true(cpu->mmu_tc.sre && laddr.IsSuper())) { rpname = "SRP"; m_desc1 = cpu->GetSRPh(); m_desc2 = cpu->GetSRPl(); @@ -1098,140 +1348,127 @@ m68030MMU::search() m_desc2 = cpu->GetCRPl(); } - // ルートポインタの DT をチェック + // ルートポインタの DT をチェック。 dt = GET_DT(m_desc1); - MMULOG(3, 2, "search: %s dt=%s", rpname, dtname(dt)); - switch (dt) { - case DT_PAGE: // ページディスクリプタ - m_type = EARLY; - return; - - case DT_VALID4: // 有効(4バイト) - case DT_VALID8: // 有効(8バイト) - // descsize は DT=$2 なら 4、DT=$3 なら 8 になる - m_descsize = 1 << dt; - m_lastsize = 8; - break; + MMULOG((__predict_false(IsDebugger()) ? 5 : 4), + 3, "%s: %s dt=%s", lam_func, rpname, dtname(dt)); + if (__predict_true(dt == DT_VALID4)) { + // 有効 4 バイト。基本的にはこれしか使われないので優先。 + m_descsize = 4; + } else if (dt == DT_VALID8) { // 有効 (8バイト) + m_descsize = 8; + } else if (dt == DT_PAGE) { // ページディスクリプタ + return Result::Early; + } else { + // 無効は設定できないはずなので、ここには来ないはず。 + PANIC("%s.DT=%u corrupted?", rpname, dt); } - // 必要ならファンクションコード・ルックアップ - if (cpu->mmu_tc.fcl) { - x = FCL; - MMULOG(3, 2, "search: %s tableaddr=$%08x idx=$%x", - m_lvname[x], m_desc2, m_lv[x]); + // 必要ならファンクションコード・ルックアップ。 + if (__predict_false(cpu->mmu_tc.fcl)) { + // たぶん検索レベルを1つ消費するはず。未確認。 + m_n++; - // ディスクリプタをフェッチ - if (fetch_desc(m_desc2, m_lv[x]) == false) { - return; + uint32 tableaddr = m_desc2 & 0xfffffff0U; + uint idx = m_laddr.GetFC(); + MMULOG((__predict_false(IsDebugger()) ? 5 : 4), + 3, "%s: %s tableaddr=%08x idx=$%x", lam_func, + m_lvname[lv], tableaddr, idx); + + // ディスクリプタをフェッチ。 + if (FETCH_DESC(tableaddr, idx) == false) { + return Result::Invalid; } - // ディスクリプタタイプをチェック + // ディスクリプタタイプをチェック。 dt = GET_DT(m_desc1); switch (dt) { case DT_INVALID: // 無効 - m_type = INVALID; - return; + return Result::Invalid; case DT_PAGE: // ページディスクリプタ - m_type = EARLY; - return; + return Result::Early; case DT_VALID4: // 有効4バイト case DT_VALID8: // 有効8バイト - m_lastsize = m_descsize; - m_descsize = 1 << dt; + m_descsize = 1U << dt; break; } } // TIx のテーブルサーチに入る。図9-25 の下半分。 - x = TIA; - for (;;) { - // リミットチェック - if (m_lastsize == 8 && limitcheck() == false) { - m_type = INVALID; - m_l = 1; - return; + for (lv = LV_TIA; ; lv++) { + // N はステージ開始時に足すが、 + // 超えたかどうかはこのステージが完了したループ末尾で調べる。 + m_n++; + + // ロングディスクリプタなら、リミットチェック。 + if (__predict_false(m_descsize == 8)) { + if (CheckLimit() == false) { + m_lim = true; + return Result::Invalid; + } } - // テーブルアドレス - uint32 tableaddr; - tableaddr = (m_lastsize == 4) ? m_desc1 : m_desc2; - tableaddr &= 0xfffffff0; - MMULOG(3, 2, "search: %s tableaddr=$%08x idx=$%x", - m_lvname[x], tableaddr, m_lv[x]); - - // ディスクリプタをフェッチ - if (fetch_desc(tableaddr, m_lv[x]) == false) { - return; + // テーブルアドレス。 + uint32 tableaddr = m_desc2 & 0xfffffff0U; + MMULOG((__predict_false(IsDebugger()) ? 5 : 4), + 3, "%s: %s tableaddr=%08x idx=$%x", lam_func, + m_lvname[lv], tableaddr, m_idx[lv]); + + // ディスクリプタをフェッチ。 + if (FETCH_DESC(tableaddr, m_idx[lv]) == false) { + return Result::Invalid; } - // ディスクリプタタイプをチェック + // ディスクリプタタイプをチェック。 dt = GET_DT(m_desc1); switch (dt) { case DT_INVALID: // 無効 - m_type = INVALID; - return; + return Result::Invalid; case DT_PAGE: // ページディスクリプタ - if (x == TID) { - m_type = NORMAL; + // 最終段ならノーマル。 + // 最終段じゃないのにページが来たらアーリーターミネーション。 + if (__predict_true(lv == cpu->mmu_tc.termlv)) { + return Result::Normal; } else { - if (cpu->mmu_tc.tix[x + 1] == 0) { - m_type = NORMAL; - } else { - m_type = EARLY; - } + return Result::Early; } - return; case DT_VALID4: // 有効4バイト case DT_VALID8: // 有効8バイト - m_lastsize = m_descsize; - m_descsize = 1 << dt; - - if (x == TID || cpu->mmu_tc.tix[x + 1] == 0) { - // 間接 - m_type = INDIRECT; - - // ディスクリプタをフェッチ - tableaddr = (m_lastsize == 4) ? m_desc1 : m_desc2; - tableaddr &= 0xfffffff0; - if (fetch_desc(tableaddr, 0) == false) { - return; + // 最終段なら、間接ディスクリプタ。 + if (__predict_false(lv == cpu->mmu_tc.termlv)) { + // ディスクリプタをフェッチ。 + tableaddr = m_desc2 & 0xfffffff0U; + if (FETCH_DESC(tableaddr, 0) == false) { + return Result::Invalid; } if (GET_DT(m_desc1) != DT_PAGE) { - m_type = INVALID; + return Result::Invalid; } - return; + + return Result::Indirect; } - // 次段のサーチを繰り返す - x++; - continue; - } - } - __unreachable(); -} -// リミットチェックを実行。図9-28。 -// 無効のケースに落ちたら false を返す。 -bool -m68030MMU::limitcheck() const -{ - int lu = (m_desc1 & 0x80000000); - uint16 limit = (m_desc1 >> 16) & 0x7fff; + // 次段のサーチを繰り返す。 + m_descsize = 1U << dt; + break; + } - if (lu != 0 && m_lv[x] < limit) { - return false; - } - if (lu == 0 && m_lv[x] > limit) { - return false; + if (__predict_false(m_n >= maxlv)) { + MMULOG((__predict_false(IsDebugger()) ? 5 : 4), + 3, "%s: n reaches maxlv(%u)", lam_func, maxlv); + break; + } } - return true; + + return Result::Normal; } -// ディスクリプタ中のフラグの更新 (を予約する) +// ディスクリプタ中のフラグの更新 (を予約する)。 #define UPDATE_DESC(flag) do { \ if ((m_desc1 & (flag)) == 0) { \ m_desc1 |= (flag); \ @@ -1239,14 +1476,15 @@ m68030MMU::limitcheck() const } \ } while (0) -// ディスクリプタのフェッチと、ヒストリおよびステータスの更新。 +// ディスクリプタのフェッチと更新を行う。 // 図9-29。 -// ディスクリプタを取得できれば true、 -// ATC エントリの作成に向かう場合は false。 +// ディスクリプタを取得出来れば true を返す。 +// ATC エントリの作成に向かう場合は false を返す。 bool -m68030MMU::fetch_desc(uint32 tableaddr, int idx) +m68030PageTableOp::FetchDesc(uint32 tableaddr, uint idx) { uint32 descaddr; + busdata bd; bool do_write; uint dt; @@ -1254,267 +1492,195 @@ m68030MMU::fetch_desc(uint32 tableaddr, // インダイレクトなら idx = 0 でコールしてあるので、 // tableaddr がそのまま PA になるという寸法。 descaddr = tableaddr + idx * m_descsize; - MMULOG(3, 2, "fetch_desc: descsize=%d descaddr=$%08x", + MMULOG(4, 4, "%s: descsize=%u descaddr=%08x", lam_func, m_descsize, descaddr); - // バスエラー発生フラグをクリアしておく - m_berr = false; - - // ディスクリプタを取得 - if (m_debugger) { - // デバッガアクセス - uint64 data; - data = phys_peek_32(descaddr); - if ((int64)data < 0) { - goto buserr; - } - m_desc1 = data; - - if (m_descsize == 8) { - data = phys_peek_32(descaddr + 4); - if ((int64)data < 0) { - goto buserr; - } - m_desc2 = data; - } + bd = cpu->read_phys_4(descaddr); + if (__predict_false(bd.IsBusErr())) { + goto buserr; + } + m_desc1 = bd.Data(); + + if (__predict_true(m_descsize == 4)) { + // ショートディスクリプタは m_desc1 と m_desc2 の各フィールドが + // 衝突せず共存している状態とみなすことが出来る。 + m_desc2 = m_desc1; } else { - uint64 data; - - data = phys_read_32(descaddr); - if ((int64)data < 0) { + bd = cpu->read_phys_4(descaddr + 4); + if (bd.IsBusErr()) { goto buserr; } - m_desc1 = data; - - if (m_descsize == 8) { - data = phys_read_32(descaddr + 4); - if ((int64)data < 0) { - goto buserr; - } - m_desc2 = data; - } + m_desc2 = bd.Data(); } + m_lastaddr = descaddr; - // ディスクリプタタイプをチェック - // DT は必ず最初のロングワードの下位2ビット。 + // ディスクリプタタイプをチェック。 do_write = false; dt = GET_DT(m_desc1); - MMULOG(3, 2, "fetch_desc: desc=%s dt=%s", + MMULOG(4, 4, "%s: desc=%s dt=%s", lam_func, sprintf_desc().c_str(), dtname(dt)); switch (dt) { case DT_INVALID: // 無効 return true; case DT_PAGE: // ページディスクリプタ - // U ビットを更新 - UPDATE_DESC(MMU_DESC_U); + // U ビットを更新。 + UPDATE_DESC(PAGEDESC_U); - // 書き込み操作なら M を更新 - if (cpu->bus.IsWrite() && !m_ptest) { - UPDATE_DESC(MMU_DESC_M); + // テーブルサーチ中の書き込み操作なら M を更新。 + if (writeback_m) { + UPDATE_DESC(PAGEDESC_M); } // 図にはないけど、ATC 更新のためと PTEST のために // ページディスクリプタの M ビットを記録。 - if ((m_desc1 & MMU_DESC_M)) - m_m = 1; + if ((m_desc1 & PAGEDESC_M)) { + m_m = true; + } break; case DT_VALID4: // 有効4バイト case DT_VALID8: // 有効8バイト - // U ビットを更新 - UPDATE_DESC(MMU_DESC_U); + // U ビットを更新。 + UPDATE_DESC(PAGEDESC_U); break; - } - // デバッガ自身がアドレス変換を実施している時は書き込みは行わない - if (m_debugger) { - do_write = false; + default: + __unreachable(); } - // ライトライクルの実行 + // ライトライクルの実行。 if (do_write) { - int64 berr = phys_write_32(descaddr, m_desc1); - if (berr < 0) { + busdata r = cpu->write_phys_4(descaddr, m_desc1); + if (__predict_false(r.IsBusErr())) { goto buserr; } - // バス動作正常終了 - MMULOG(3, 2, "fetch_desc: desc=%s updated", sprintf_desc().c_str()); + // バス動作正常終了。 + MMULOG(4, 4, "%s: desc=%s updated", lam_func, sprintf_desc().c_str()); } - // ステータス更新 - // XXX Cache Inhibit ビットは未実装なので、ページディスクリプタと - // 有効4/8バイトのケースは同じでよくなる。 + // ステータス更新 (OR していく)。 // XXX 実際には CI はページディスクリプタにしかないけど - m_wp |= (m_desc1 & MMU_DESC_WP) ? 1 : 0; - if (m_descsize == 8) { - m_s |= (m_desc1 & MMU_DESC_S) ? 1 : 0; + m_wp |= (m_desc1 & PAGEDESC_WP); + m_ci |= (m_desc1 & PAGEDESC_CI); + if (__predict_false(m_descsize == 8)) { + m_s |= (m_desc1 & PAGEDESC_S); } - return true; // バスエラー buserr: - m_type = INVALID; m_berr = true; return false; } -// ATC エントリを作成。実行系用。 -// 図9-27 だが雑すぎ。 -void -m68030MMU::make_atc(m68030ATCLine *a) +// ディスクリプタをこっそり取得する。更新操作は行わない。 +// またディスクリプタの状態を内部フラグに反映するところも行わない。 +// 取得できれば m_desc1, m_desc2 に置いて true を返す。 +// 取得できなければ false を返す。 +bool +m68030PageTableOp::PeekDesc(uint32 tableaddr, uint idx) { - std::string buf; - uint32 paddr; + uint32 descaddr; + uint64 data; - a->data = 0; - switch (m_type) { - case INVALID: // 無効 - a->data |= m68030ATCLine::BUSERROR; - MMULOG(3, 2, "make_atc: type=INVALID -> BusErr"); - return; + // ディスクリプタのアドレスを計算。 + descaddr = tableaddr + idx * m_descsize; - case NORMAL: // ノーマル - case INDIRECT: // 間接 - paddr = (m_descsize == 4) ? m_desc1 : m_desc2; - MMULOG(3, 2, "make_atc: type=NORMAL paddr=$%08x", paddr & 0xffffff00); - break; + data = cpu->mainbus->Peek4(descaddr); + if (__predict_false((int64)data < 0)) { + return false; + } + m_desc1 = (uint32)data; - case EARLY: // アーリーターミネーション - paddr = (m_descsize == 4) ? m_desc1 : m_desc2; - buf = string_format("make_atc: type=EARLY paddr=$%08x", - paddr & 0xffffff00); - - // 残ってるビットも足して返す - for (int i = x + 1; i <= TID; i++) { - if (cpu->mmu_tc.tix[i] > 0) { - paddr |= m_lv[i] << cpu->mmu_tc.shift[i]; - buf += string_format(" +%s=%08x", m_lvname[i], paddr); - } + if (__predict_true(m_descsize == 4)) { + m_desc2 = m_desc1; + } else { + data = cpu->mainbus->Peek4(descaddr + 4); + if ((int64)data < 0) { + return false; } - MMULOG(3, 2, "%s", buf.c_str()); - break; - default: - __unreachable(); + m_desc2 = (uint32)data; } - // PS の部分は反映しない - a->paddr = paddr & cpu->mmu_tc.lmask; - - //if (m_ci) a->data |= m68030ATCLine::CINHIBIT; - if (m_wp) - a->data |= m68030ATCLine::WPROTECT; - if (m_m) - a->data |= m68030ATCLine::MODIFIED; + // フラグやメンバーの更新は不要なのでこれで終わり。 + return true; } -// make_atc() と同様にテーブルサーチの結果から物理ページアドレスを返す。 -// バスエラーになるケースなら (uint64)-1 を返す。 -// デバッガアクセスなので、VM に一切干渉しない。 -uint64 -m68030MMU::make_atc_debugger() -{ - uint64 paddr; - - switch (m_type) { - case INVALID: // 無効 - return (uint64)-1; - break; +// 検索結果から ATC エントリを作成。 +// 図9-27 だが雑すぎ。 +void +m68030PageTableOp::MakeATC(m68030ATC& atc, m68030ATCLine *a) const +{ +#if defined(M68030_CUSTOM_ATC) + // lmask はどのインスタンスでも同じ。 + auto table = atc.GetTable(6); +#else + auto table = atc.GetTable(); +#endif - case NORMAL: // ノーマル - case INDIRECT: // 間接 - paddr = (m_descsize == 4) ? m_desc1 : m_desc2; - paddr &= 0xffffff00; - break; + a->tag = table->MakeTag(m_laddr); - case EARLY: // アーリーターミネーション - paddr = (m_descsize == 4) ? m_desc1 : m_desc2; - paddr &= 0xffffff00; - // 残ってるビットも足して返す - for (int i = x + 1; i <= TID; i++) { - if (cpu->mmu_tc.tix[i] > 0) { - paddr |= m_lv[i] << cpu->mmu_tc.shift[i]; - } - } - break; - default: - __unreachable(); + if (result == Result::Invalid) { + a->paddr = {}; + a->data = m68030ATCLine::BUSERROR; + return; } - return paddr; -} -// 物理メモリからの読み込み。 -// 正常に読み込めれば 32bit の値。バスエラーなら (uint64)-1 を返す。 -uint64 -m68030MMU::phys_read_32(uint32 addr) -{ - m68kbus backup; - uint64 data; - - // バス情報を一旦退避 - backup = cpu->bus; + a->paddr = m_paddr.Addr(); + if (__predict_false(result == Result::Early)) { + // PERFORM LIMIT CHECK ?? - // 物理バスアクセス - cpu->bus.ssw = SSW_BUS_R | FC_SD; - data = vm_phys_read_32(addr); - if ((int64)data < 0) - return data; + a->paddr = AssembleEarlyRemains(a->paddr); + } - // バス情報を復元 - cpu->bus = backup; - return data; + a->data = 0; + if (m_ci) { + a->data |= m68030ATCLine::CINHIBIT; + } + if (m_wp) { + a->data |= m68030ATCLine::WPROTECT; + } + if (m_m) { + a->data |= m68030ATCLine::MODIFIED; + } } -// 物理メモリへの書き込み。 -// 正常に書き込めれば 0、バスエラーなら (uint64)-1 を返す。 -uint64 -m68030MMU::phys_write_32(uint32 addr, uint32 data) +// リミットチェックを実行。図9-28。 +// 無効のケースに落ちたら false を返す。 +bool +m68030PageTableOp::CheckLimit() const { - m68kbus backup; - uint64 berr; - - // バス情報を一旦退避 - backup = cpu->bus; + uint16 limit = (m_desc1 >> 16) & 0x7fff; - // 物理バスアクセス - cpu->bus.ssw = SSW_BUS_W | FC_SD; - berr = vm_phys_write_32(addr, data); - if ((int64)berr < 0) - return berr; - - // バス情報を復元 - cpu->bus = backup; - return 0; + // 最上位ビットが L/U + if ((m_desc1 & 0x80000000U)) { + if (m_idx[lv] < limit) { + MMULOG(4, 4, "%s: $%04x < $%04x", lam_func, m_idx[lv], limit); + return false; + } + } else { + if (m_idx[lv] > limit) { + MMULOG(4, 4, "%s: $%04x > $%04x", lam_func, m_idx[lv], limit); + return false; + } + } + return true; } -// デバッガによるメモリ読み込み -uint64 -m68030MMU::phys_peek_32(uint32 addr) +// 早期終了時の残ってるビットを addr に足したものを返す。 +uint32 +m68030PageTableOp::AssembleEarlyRemains(uint32 addr) const { - uint64 peekdata; - uint32 data; - - // 1バイト目はバスエラーを検査。 - peekdata = vm_phys_peek_8(addr); - if ((int64)peekdata < 0) { - // バスエラー - return -1; + for (uint i = lv + 1; i <= cpu->mmu_tc.termlv; i++) { + addr |= m_idx[i] << cpu->mmu_tc.shift[i]; } - data = peekdata << 24; - - // ここはロングワード境界でアクセスしてるはずなので、 - // 先頭バイト以外のバスエラーチェックは不要。 - data |= vm_phys_peek_8(addr + 1) << 16; - data |= vm_phys_peek_8(addr + 2) << 8; - data |= vm_phys_peek_8(addr + 3); - - return data; + return addr; } // ディスクリプタ表示用の文字列を返す。 // m_descsize によって m_desc1, m_desc2 を適切に文字列にする。 std::string -m68030MMU::sprintf_desc() +m68030PageTableOp::sprintf_desc() const { std::string buf; @@ -1527,448 +1693,623 @@ m68030MMU::sprintf_desc() // フラグの出現条件がややこしいので注意 int dt = GET_DT(m_desc1); buf += string_format(" (%s%s%s%c%c)", - ((m_descsize == 4) ? "x" : ((m_desc1 & MMU_DESC_S) ? "S" : "-")), - ((dt != DT_PAGE) ? "x" : ((m_desc1 & MMU_DESC_CI) ? "C" : "-")), - ((dt != DT_PAGE) ? "x" : ((m_desc1 & MMU_DESC_M) ? "M" : "-")), - (m_desc1 & MMU_DESC_U) ? 'U' : '-', - (m_desc1 & MMU_DESC_WP) ? 'W' : '-' + ((m_descsize == 4) ? "x" : ((m_desc1 & PAGEDESC_S) ? "S" : "-")), + ((dt != DT_PAGE) ? "x" : ((m_desc1 & PAGEDESC_CI) ? "C" : "-")), + ((dt != DT_PAGE) ? "x" : ((m_desc1 & PAGEDESC_M) ? "M" : "-")), + (m_desc1 & PAGEDESC_U) ? 'U' : '-', + (m_desc1 & PAGEDESC_WP) ? 'W' : '-' ); return buf; } // ディスクリプタタイプ文字列 (デバッグ用) -const char * -m68030MMU::dtname(uint32 dt) +/*static*/ const char * +m68030PageTableOp::dtname(uint32 dt) { - static char buf[32]; switch (dt) { case DT_INVALID: return "invalid"; case DT_PAGE: return "page"; case DT_VALID4: return "valid4"; case DT_VALID8: return "valid8"; + default: + return "corrupted?"; } - sprintf(buf, "(invalid DT %d)", dt); - return buf; } -// デバッグ用文字列 -const char * const m68030MMU::m_lvname[MAX_LV] = { +/*static*/ const char * const +m68030PageTableOp::m_lvname[] = { "TIA", "TIB", "TIC", "TID", - "FCL", // この順ではないが、便宜上ここにおいてある + "bad", // 番兵なので表示されないはず。 + "FCL", // 検索順ではなく、こいつだけ処理が違うのでここにある。 }; + // -// アドレス変換 +// レジスタ // -// 論理アドレスが TT0/TT1 どちらかと一致すれば true を返す。 -// 一致しなければ false を返す。 -// 高速化のため mmu_tt_quick による評価は呼び出し側で行なっている。 -static bool -m68030_check_tt(m68kcpu *cpu) +// リセット例外の CPU 固有部分。 +void +MPU68030Device::ResetMMU() { - bool ci; - int match; + SetTC(GetTC() & ~m68030TC::TC_E); + SetTT(0, GetTT(0) & ~m68030TT::E); + SetTT(1, GetTT(1) & ~m68030TT::E); +} - match = 0; - ci = false; - for (int i = 0; i < 2; i++) { - m68030TT& tt = cpu->mmu_tt[i]; - if (tt.Match(cpu->bus.ssw_laddr)) { - // 一致した - match += i + 1; - ci |= tt.ci != 0; - MMULOG(3, 1, "check_tt: TT%d match", i); - } - } +// 現在の TC:E、TT:E の状態に応じてアドレス変換の有無を切り替える。 +// mmu_enable ならアドレス変換ありのバスアクセスを使う。see m68030bus.cpp +void +MPU68030Device::mmu_enable_changed() +{ + bool enable; - // CI は計算はするけど現状未実装なので放置 - (void)ci; + // TC、TT0、TT1 のいずれかが有効ならアドレス変換あり + enable = (mmu_tc.e || mmu_tt[0].e || mmu_tt[1].e); - // 一致したら PA <- LA - if (match != 0) { - MMULOG(3, 0, "check_tt: result %08x (match TT%s)", - cpu->bus.laddr, - (match == 3) ? "0 and TT1" : ((match == 1) ? "0" : "1")); - return true; + // 変化した時だけ + if (mmu_enable && !enable) { + // 無効にする + mmu_enable = false; + putlog(1, "MMU Translation Disabled"); + } else if (!mmu_enable && enable) { + // 有効にする + mmu_enable = true; + putlog(1, "MMU Translation Enabled"); } - return false; } -// フェッチ用のアドレス変換を行う。 -// 変換できれば true、バスエラーなら false を返す。 bool -m68030_mmu_translate_fetch(m68kcpu *cpu) +MPU68030Device::SetSRP(uint32 h, uint32 l) { - // 状態表示 - MMULOG(3, 0, "translate_fetch: enter %c:$%08x pc=$%08x", - (cpu->bus.GetFC() & 0x04) ? 'S' : 'U', - cpu->bus.laddr, - RegPPC); + if (GET_DT(h) == DT_INVALID) { + return false; + } + reg.srp.h = h & m68030RP::H_MASK; + reg.srp.l = l; + return true; +} - // まず TT をチェック - if (__predict_false(cpu->mmu_tt_quick[cpu->bus.laddr >> 24] != 0)) { - if (m68030_check_tt(cpu)) { - return true; - } +bool +MPU68030Device::SetCRP(uint32 h, uint32 l) +{ + if (GET_DT(h) == DT_INVALID) { + return false; } + reg.crp.h = h & m68030RP::H_MASK; + reg.crp.l = l; + return true; +} - // ATC からアドレスを取得 - if (cpu->mmu_tc.e) { - const m68030ATCLine& a = cpu->atc.fill_fetch(); - if (a.IsBusError()) { - MMULOG(3, 0, "translate_fetch: result BusErr (ATC:B)"); - return false; - } +void +MPU68030Device::SetTT(int n, uint32 data) +{ + // マスクしてレジスタイメージにセット + reg.tt[n] = data & m68030TT::MASK; + + // メンバと二次変数を作成。 + mmu_tt[n].Set(data); - cpu->bus.paddr = a.GetPAddr(); - cpu->bus.paddr |= cpu->bus.laddr & cpu->mmu_tc.pgmask; - //cpu->bus.ci = a->ci; +#if defined(M68030_CUSTOM_ATC) + atc.Rehash(); +#endif + + mmu_enable_changed(); +} + +// data を分解してメンバにセットする。 +void +m68030TT::Set(uint32 data) +{ + // 3 2 1 0 + // 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + // +---------------+---------------+-+-+-+-+-+-+-+-+-+-----+-+-----+ + // |LogicalAddrBase|LogicalAddrMask|E| |C|R|M| | FCB | | FCM | + // +---------------+---------------+-+-+-+-+-+-+-+-+-+-----+-+-----+ + + e = (data & m68030TT::E); + ci = (data & m68030TT::CI); + + // 下位ロングワード + // ・lbase は比較先アドレスで、ff000000の位置。 + // ・lmask はアドレスマスク。%1 が無視を表しているので NOT しておく。 + // + // target AAAAAAAA xxxxxxxx xxxxxxxx xxxxxxxx : Aは論理アドレス + // ^ : xは不問 + // lbase BBBBBBBB 00000000 00000000 00000000 : Bは論理アドレスベース + // & + // lmask MMMMMMMM 00000000 00000000 00000000 : Mは論理アドレスマスク + // + uint32 basel = (data & m68030TT::LBASE_MASK); + uint32 maskl = (~data & m68030TT::LMASK_MASK) << 8; + + // 上位ロングワード + // ・E ビットは位置はどこでもいいので TT:E ビットの + // 位置をそのまま使用する。target 側のこの位置は必ずビットが落ちている + // ため base 側にビットを立てておくと必ず不一致になることを利用して、 + // E ビットの評価も一度に行う。 + // ・RW ビットは BusAddr::R と比較するので 0x00000080 の位置。 + // ・RWM が %1 なら R/W は無視、RWM が %0 なら R/W の一致も検査するので + // RWM が %0 の時は RW のマスクビットを立てておく。 + // ・FC は busaddr の配置に合わせて1ビット左シフトしたところを使う。 + // ・FCMask は %1 が無視を表しているので NOT しておく。 + // + // target 00000000 00000000 00000000 R000FFFS : R はアクセス R/~W + // ^ : F はアクセス FC + // : S は FC2 のコピー + // base 00000000 00000000 E0000000 R000FFF0 : R は期待する R/~W + // & : F は期待する FC + // : E は ~TT:E + // mask 00000000 00000000 10000000 M000CCC0 : M は R/~W マスク + // : C は FC マスク + uint32 baseh; + uint32 maskh; + baseh = ~data & m68030TT::E; + baseh |= (data & m68030TT::RW) >> 2; + baseh |= (data & m68030TT::FCBASE_MASK) >> 3; + maskh = m68030TT::E; + if ((data & m68030TT::RWM) == 0) { + maskh |= (uint64)BusAddr::R >> 32; } + maskh |= (~data & m68030TT::FCMASK_MASK) << 1; - MMULOG(3, 0, "translate_fetch: result $%08x", cpu->bus.paddr); - return true; + base = (((uint64)baseh) << 32) | basel; + mask = (((uint64)maskh) << 32) | maskl; } -// リード用のアドレス変換を行う。 -// 変換できれば true、バスエラーなら false を返す。 +// アクセスがこの TT とマッチするか調べる。 +// laddr の上位ワードはそのまま使える。 +// 下位32ビットが論理アドレス (下24ビットは呼び出し側でマスクしなくてよい)。 +// この TT が有効でアドレスがマッチすれば true を返す。 +// (TT:E は演算に織り込んである、このすぐ上の SetTT() のコメント参照) bool -m68030_mmu_translate_read(m68kcpu *cpu) +m68030TT::Match(busaddr laddr) const { - // 状態表示 - MMULOG(3, 0, "translate_read: enter %c:$%08x pc=%c:$%08x", - (cpu->bus.GetFC() & 0x04) ? 'S' : 'U', - cpu->bus.laddr, - (cpu->reg.s) ? 'S' : 'U', - RegPPC); + bool rv = (((laddr.Get() ^ base) & mask) == 0); + return rv; +} - // まず TT をチェック - if (__predict_false(cpu->mmu_tt_quick[cpu->bus.laddr >> 24] != 0)) { - if (m68030_check_tt(cpu)) { - return true; - } +// 成功すれば true、MMU 構成例外が起きる場合は false を返す。 +// 呼び出し側で例外を起こすこと。 +bool +MPU68030Device::SetTC(uint32 data) +{ + // マスクしてレジスタイメージにセット + reg.tc = data & m68030TC::MASK; + + // data を分解してメンバにセット。 + if (mmu_tc.Set(data) == false) { + // エラーなら E をクリアして MMU 構成例外。 + // (例外処理自体は呼び出し側で行う) + mmu_enable_changed(); + return false; } - // ATC からアドレスを取得 - if (cpu->mmu_tc.e) { - const m68030ATCLine& a = cpu->atc.fill_read(); - if (a.IsBusError()) { - MMULOG(3, 0, "translate_read: result BusErr (ATC:B)"); - return false; - } + // ATC にも通知。 + atc.SetTC(mmu_tc); - cpu->bus.paddr = a.GetPAddr(); - cpu->bus.paddr |= cpu->bus.laddr & cpu->mmu_tc.pgmask; - //cpu->bus.ci = a->ci; - } +#if defined(M68030_CUSTOM_ATC) + // ハッシュも全部作り直す。 + atc.Rehash(); +#endif - MMULOG(3, 0, "translate_read: result $%08x", cpu->bus.paddr); + // TC:E が変更されていればスイッチ更新 + mmu_enable_changed(); return true; } -// ライト用のアドレス変換を行う。 -// 変換できれば true、バスエラーなら false を返す。 +// TC レジスタへの書き込み値をこのクラスにセットして二次変数も再構成する。 +// E をオンにした場合は構成チェックを行い、問題なければ true を返す。 +// 問題があった場合は E をオフにした上で false を返す。 +// E をオフにした場合はチェックは行わず true を返す。 bool -m68030_mmu_translate_write(m68kcpu *cpu) +m68030TC::Set(uint32 data) { - // 状態表示 - MMULOG(3, 0, "translate_write: enter %c:$%08x pc=%c:$%08x", - (cpu->bus.GetFC() & 0x04) ? 'S' : 'U', - cpu->bus.laddr, - (cpu->reg.s) ? 'S' : 'U', - RegPPC); + // 一次メンバにセット。 + e = data & TC_E; + sre = data & TC_SRE; + fcl = data & TC_FCL; + tid = (data) & 0x0f; + tic = (data >>= 4) & 0x0f; + tib = (data >>= 4) & 0x0f; + tia = (data >>= 4) & 0x0f; + is = (data >>= 4) & 0x0f; + ps = (data >>= 4) & 0x0f; - // まず TT をチェック - if (__predict_false(cpu->mmu_tt_quick[cpu->bus.laddr >> 24] != 0)) { - if (m68030_check_tt(cpu)) { - return true; - } - } + config_ok = Check(); - // ATC からアドレスを取得 - if (cpu->mmu_tc.e) { - const m68030ATCLine& a = cpu->atc.fill_write(); - if (a.IsBusError()) { - MMULOG(3, 0, "translate_write: result BusErr (ATC:B)"); - return false; - } - if (a.IsWProtect()) { - // Write アクセスで WP が立ってたらバスエラー - MMULOG(3, 0, "translate_write: result Buserr (ATC:WP)"); - return false; - } + // 構成が有効なら E に関わらず二次変数を用意しておく。 + if (config_ok) { + MakeMask(); + } - cpu->bus.paddr = a.GetPAddr(); - cpu->bus.paddr |= cpu->bus.laddr & cpu->mmu_tc.pgmask; - //cpu->bus.ci = a->ci; + // E がオンで構成例外になる場合はオフにする。 + if (e && config_ok == false) { + e = false; + return false; } - MMULOG(3, 0, "translate_write: result $%08x", cpu->bus.paddr); return true; } -// ピーク用のアドレス変換を行う。 -// ATC にエントリが見付からない場合、do_search が true ならテーブル -// サーチまで行う。false ならテーブルサーチせずに帰る。 -// 変換できれば対応する物理アドレスを、バスエラーなら (uint64)-1 を返す。 -uint64 -m68030_mmu_translate_peek(m68kcpu *cpu, uint32 laddr, uint fc, bool do_search) -{ - uint64 paddr; - - // 状態表示 - MMULOG(3, 0, "translate_peek: enter %c:$%08x", - (fc & 0x04) ? 'S' : 'U', - laddr); - - // まず TT をチェック - paddr = laddr; - if (__predict_false(cpu->mmu_tt_quick[laddr >> 24] != 0)) { - uint64 ssw_laddr = ((uint64)(SSW_BUS_R | fc) << 32) | laddr; - for (int i = 0; i < 2; i++) { - m68030TT& tt = cpu->mmu_tt[i]; - if (tt.Match(ssw_laddr)) { - // 一致した。 - // CI はここでは使わないため、一致したことだけわかればよい - MMULOG(3, 0, "translate_peek: result $%08x (match tt)", - (uint32)paddr); - return paddr; - } - } +// MMU 構成例外になるケースなら false を返す。 +bool +m68030TC::Check() const +{ + if (ps < 8) { + return false; + } + if (tia == 0) { + return false; + } + if (tib == 0 && (tic > 0 || tid > 0)) { + return false; } + if (tic == 0 && tid > 0) { + return false; + } + if (ps + is + tia + tib + tic + tid != 32) { + return false; + } + return true; +} - // 次に TC (ATC と MMU テーブルサーチ)。 - if (cpu->mmu_tc.e) { - // ATC からこっそりアドレスを取得 - paddr = cpu->atc.fill_peek(laddr & cpu->mmu_tc.lmask, fc); - if ((int64)paddr >= 0) { - paddr |= laddr & cpu->mmu_tc.pgmask; - MMULOG(3, 0, "translate_peek: result $%08x (match atc)", - (uint32)paddr); - return paddr; - } - - // 見付からなかったのでこっそりテーブルサーチ。 - if (do_search) { - // mmu.search() はさすがに cpu->bus を使うので事前に退避。 - m68kbus backup = cpu->bus; - - cpu->bus.laddr = laddr; - cpu->bus.ssw = fc; - m68030MMU mmu(cpu); - mmu.m_debugger = true; - mmu.search(); - paddr = mmu.make_atc_debugger(); - // リストア - cpu->bus = backup; - - if ((int64)paddr >= 0) { - paddr |= laddr & cpu->mmu_tc.pgmask; - MMULOG(3, 0, "translate_peek: result $%08x (table search)", - (uint32)paddr); - return paddr; - } +// 二次変数を作成。(E には影響されない) +void +m68030TC::MakeMask() +{ + // lmask (論理マスク) は TIA〜TID 部分のマスク。 + // pgmask (ページマスク) は PS 部分のマスク。 + // + // 例えば IS=$8, TIA=$3, TIB=$4, TIC=$4, TID=$0, PS=$d + // (X68030 IPL 内の MMU 判定のケース) なら + // + // 3 2 1 0 + // 10987654 32109876 54321098 76543210 + // +--------+--------+--------+--------+ + // |IIIIIIII AAABBBBC CCCPPPPP PPPPPPPP| + // +--------+--------+--------+--------+ + // lmask = 00000000 11111111 11100000 00000000 + // pgmask = 00000000 00000000 00011111 11111111 + lmask = (1U << (tia + tib + tic + tid)) - 1; + lmask <<= ps; + pgmask = (1U << ps) - 1; + + // アドレスを (IS,) TIA 〜 TID に分解。 + // + // TIA=$C, TIB=$A, (TIC = TID = 0), PS=$A の場合以下のようにする。 + // + // A B PS + // +----------------+--------------+--------------+ + // $00A01A00 | 0000 0000 1010 | 0000 0001 10 | xx xxxx xxxx | + // +----------------+--------------+--------------+ + // shift[TIA]=20 shift[TIB]=10 + // mask[TIA]=0xfff mask[TIB]=0x3ff + constexpr int LV_TIA = m68030PageTableOp::LV_TIA; + constexpr int LV_TID = m68030PageTableOp::LV_TID; + uint sh = ps; + for (int i = LV_TID; i >= LV_TIA; i--) { + if (tix[i] != 0) { + shift[i] = sh; + mask[i] = (1U << tix[i]) - 1; + sh += tix[i]; } + } - // TC が有効だがヒットしなかったらバスエラー - MMULOG(3, 0, "translate_peek: result BusErr"); - return (uint64)-1; + // 最終段を求めておく。 + termlv = LV_TID; + for (int i = LV_TIA; i <= LV_TID; i++) { + if (tix[i] == 0) { + termlv = i - 1; + break; + } } +} - // TT が無効かまたは有効でもヒットせず、 - // かつ TC も無効だったら (こっちは「有効でヒットせず」は含まない)、 - // 論理アドレスをそのまま返す - MMULOG(3, 0, "translate_peek: result $%08x (no translation)", laddr); - return laddr; +void +MPU68030Device::SetMMUSR(uint16 data) +{ + reg.mmusr = data & m68030MMUSR::MASK; } + // // 命令 // -// MMU 命令からの呼び出し用。 -// XXX 命令によってこの中のいずれかの EA に対応していないものがあれば調査。 -// XXX クロック未調査。 -static inline uint32 -cea_mmu(m68kcpu *cpu) -{ - uint mm = eamode(RegIR); - uint rr = eanum(RegIR); - - switch (mm) { - case 0: // Dn - case 1: // An - break; - - case 2: // (An) - return cea_anin(cpu, rr); - - case 3: // (An)+ - case 4: // -(An) - break; - - case 5: // d16(An) - return cea_andi(cpu, rr); - case 6: // (An,IX) - return cea_anix(cpu, rr); - case 7: - switch (rr) { - case 0: // Abs.W - return cea_absw(cpu); - case 1: // Abs.L - return cea_absl(cpu); - default: - break; - } - break; - } - // 不当命令 - throw M68K_EXCEP_ILLEGAL; -} - -// RegIR2 の下位5ビットから FC を返す。 +// ir2 の下位5ビットから FC を返す。 // 不当パターンなら C++ の例外をスローする。 -static uint32 -get_fc(m68kcpu *cpu) +uint32 +MPU68030Device::get_fc() { - uint fcfield = RegIR2 & 0x1f; + uint fcfield = ir2 & 0x1f; if (fcfield == 0) { - return RegSFC; + return reg.GetSFC(); } else if (fcfield == 1) { - return RegDFC; + return reg.GetDFC(); } else if (fcfield < 0x08) { // break; } else if (fcfield < 0x10) { - return RegD(fcfield & 7) & 7; + return reg.D[fcfield & 7] & 7; } else if (fcfield < 0x18) { return fcfield & 7; } // 不当命令 - throw M68K_EXCEP_ILLEGAL; + throw M68K::EXCEP_ILLEGAL; } // MMU 不当命令 (2ワード目で確定) -static void -mmu_op_illg2(m68kcpu *cpu) +void +MPU68030Device::ops_mmu30_illg2() { - cpulog(1, "MMU 不当命令 %04x %04x", RegIR, RegIR2); - m68030_exception(cpu, M68K_EXCEP_FLINE); + putlog(1, "MMU illegal instruction %04x %04x", ir, ir2); + Exception(M68K::EXCEP_FLINE); +} + +// PFLUSHA 命令 +void +MPU68030Device::ops_mmu30_pflusha() +{ + atc.FlushAll(); } // PFLUSH fc,#mask 命令 (EA を持たない方) void -mmu_op_pflush(m68kcpu *cpu) +MPU68030Device::ops_mmu30_pflush() { - uint32 mask; - uint32 fc; + uint32 mask = (ir2 >> 5) & 7; + uint32 fc = get_fc(); - mask = (RegIR2 >> 5) & 7; - fc = get_fc(cpu); - cpu->atc.flush(fc, mask); + atc.Flush(fc, mask); } // PFLUSH fc,#mask,ea 命令 void -mmu_op_pflush_ea(m68kcpu *cpu) +MPU68030Device::ops_mmu30_pflush_ea() { - uint32 ea; - uint32 mask; - uint32 fc; + uint32 mask = (ir2 >> 5) & 7; + uint32 fc = get_fc(); + uint32 ea = cea_copro(); + ea &= mmu_tc.lmask; - ea = cea_mmu(cpu); - mask = (RegIR2 >> 5) & 7; - fc = get_fc(cpu); - cpu->atc.flush(fc, mask, ea); + atc.Flush(fc, mask, ea); } // PLOADR/PLOADW 命令 void -mmu_op_pload(m68kcpu *cpu) +MPU68030Device::ops_mmu30_pload() { uint32 ea; uint32 fc; + busaddr laddr; + m68030ATCLine *a; - if ((RegIR2 & 0x01e0) != 0) { - mmu_op_illg2(cpu); + if ((ir2 & 0x01e0) != 0) { + ops_mmu30_illg2(); return; } - ea = cea_mmu(cpu); - fc = get_fc(cpu); + ea = cea_copro(); + fc = get_fc(); // 実効アドレスを論理アドレスとする ATC を作成。 // ってこれでいいのか? - cpu->bus.laddr = ea; - if ((RegIR2 & 0x0200)) { - cpu->bus.ssw = SSW_BUS_R | fc; - cpu->atc.fill_read(); + if ((ir2 & 0x0200)) { + laddr = busaddr(ea) | busaddr::FC(fc) | BusAddr::R; + } else { + laddr = busaddr(ea) | busaddr::FC(fc) | BusAddr::W; + } + +#if defined(M68030_CUSTOM_ATC) + auto table = atc.GetTable(fc); + uint8 n = table->hash[laddr.Addr() >> 12]; + // PS<4KB のケースを先に解決する。 + if (__predict_false(n == m68030ATCTable_Custom::HASH_SUB)) { + a = table->FindSub(laddr); + if (a) { + n = a - &table->line[0]; + } else { + n = m68030ATCTable_Custom::HASH_NONE; + } + } + if ((int8)n >= 0) { + // すでにエントリがある場合 + if (laddr.IsWrite()) { + // 命令が ploadw でエントリが WProtect==0 && Modified==0 なら、 + // 一回破棄して書き込み用に作り直す。 + a = &table->line[n]; + if (a->IsWProtect() == false && a->IsModified() == false) { + table->Invalidate(a); + PageTableSearch(laddr, a); + } + } + } else if (n == m68030ATCTable_Custom::HASH_NONE) { + if (__predict_true(mmu_tc.e)) { + // TC.E 有効で対応なしなら、テーブルサーチを行って + // ATC を作成。 + a = table->Pop(); + PageTableSearch(laddr, a); + } else { + // TC.E 無効なら何もしない? + } + } else if (n == m68030ATCTable_Custom::HASH_TT) { + // TT の領域なら何もしてはいけない。 + } +#else + // TT の領域なら何もしてはいけない。 + if (TTMatch(laddr).IsOK()) { + return; + } + // TE.E 無効なら何もしない? + if (__predict_false(mmu_tc.e == false)) { + return; + } + auto table = atc.GetTable(); + a = table->LookupAndMove(laddr); + if (a) { + if (laddr.IsWrite()) { + // 命令が ploadw でエントリが Modified==0 (&& WProtect==0) なら、 + // 一回破棄して Modified で作り直す。 + if (a->IsWProtect() == false && a->IsModified() == false) { + PageTableSearch(laddr, a); + } + } } else { - cpu->bus.ssw = SSW_BUS_W | fc; - cpu->atc.fill_write(); + // テーブルサーチを行って ATC を作成。 + a = table->Pop(); + PageTableSearch(laddr, a); } +#endif } // PTESTR/PTESTW 命令 -// XXX てきとー void -mmu_op_ptest(m68kcpu *cpu) +MPU68030Device::ops_mmu30_ptest() { - uint level = (RegIR2 >> 10) & 7; - uint rw = (RegIR2 & 0x0200) ? SSW_BUS_R : SSW_BUS_W; - uint32 ea; - uint32 fc; + // 第2ワード + // 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + // | 1 | 0 | 0 | Level |R/W| A | ARegNo | FC | + // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + uint level = (ir2 >> 10) & 7; + bool isread = (ir2 & 0x0200); + uint rn = (ir2 >> 5) & 0xf; + + uint32 ea = cea_copro(); + uint32 fc = get_fc(); uint16 mmusr; - ea = cea_mmu(cpu); - fc = get_fc(cpu); + busaddr laddr = busaddr(ea) + | busaddr::FC(fc) + | (isread ? BusAddr::R : BusAddr::W); if (level == 0) { - cpulog(0, "未実装 PTEST %04x %04x", RegIR, RegIR2); - return; - } - if ((RegIR2 & 0x0100)) { - cpulog(0, "未実装 PTEST %04x %04x", RegIR, RegIR2); - return; + // レベル0で A 指定なら Fライン例外。 + // A が 0 なら ARegNo も 0 でなければならない、とは書いてあるが + // どうなるかは書いていない。とりあえず例外にしておく。 + if (rn != 0) { + ops_mmu30_illg2(); + return; + } + CYCLE(22); + mmusr = ptest_atc(laddr); + } else { + // レベル 1-7 ならテーブルサーチを実行。 + + // A が 0 なら ARegNo も 0 でなければならない、とは書いてあるが + // どうなるかは書いていない。とりあえず例外にしておく。 + if (__predict_false(1 <= rn && rn < 8)) { + ops_mmu30_illg2(); + return; + } + CYCLE(88); + mmusr = ptest_search(level, laddr, rn); } - // テーブルサーチを実行 - cpu->bus.laddr = ea; - cpu->bus.ssw = rw | fc; - m68030MMU table(cpu); - table.m_ptest = true; - table.search(); - // 結果を MMUSR に格納 - mmusr = 0; - if (level == 0) { - if (table.m_type == INVALID) { - mmusr |= m68030MMUSR::B; + SetMMUSR(mmusr); +} + +// PTESTR/PTESTW 命令の #0 の場合は ATC。 +uint32 +MPU68030Device::ptest_atc(const busaddr laddr) +{ + const m68030ATCLine *a = NULL; + uint16 mmusr = 0; + +#if defined(M68030_CUSTOM_ATC) + uint fc = laddr.GetFC(); + auto table = atc.GetTable(fc); + uint8 n = table->hash[laddr.Addr() >> 12]; + // PS<4KB のケースを先に解決する。 + if (__predict_false(n == m68030ATCTable_Custom::HASH_SUB)) { + a = table->FindSub(laddr); + if (a) { + n = a - &table->line[0]; + } else { + n = m68030ATCTable_Custom::HASH_NONE; + a = NULL; } + } + if (__predict_true((int8)n >= 0)) { + // ATC #(n) で見付かった。 + a = &table->line[n]; + } else if (n == m68030ATCTable_Custom::HASH_TT) { + mmusr |= m68030MMUSR::T; } else { - if (table.m_berr) { + // ATC に見付からなかった。 + } +#else + if (TTMatch(laddr).IsOK()) { + mmusr |= m68030MMUSR::T; + } + auto table = atc.GetTable(); + a = table->LookupOnly(laddr); +#endif + + if (a) { + // 見付かれば Modified 等の状態を返す。 + if (a->IsBusError()) { mmusr |= m68030MMUSR::B; + } else if (a->IsWProtect()) { + mmusr |= m68030MMUSR::W; + } else if (a->IsModified()) { + mmusr |= m68030MMUSR::M; } + } else { + // 見付からなかったら Invalid を立てる。 + mmusr |= m68030MMUSR::I; } - if (table.m_l) { + return mmusr; +} + +// PTESTR/PTESTW 命令の #1-7 の場合はテーブルサーチ。 +uint32 +MPU68030Device::ptest_search(uint level, const busaddr laddr, uint rn) +{ + uint fc = laddr.GetFC(); + uint16 mmusr = 0; + + putlog(3, "ptest_search lv=%u %u(%s).%08x", level, + fc, FCstr(fc), + laddr.Addr()); + + m68030PageTableOp pt(this, loglevel, m68030PageTableOp::ByPTEST); + pt.Search(level, laddr); + + if (pt.m_berr) { + mmusr |= m68030MMUSR::B; + } + if (pt.m_lim) { + // XXX not tested mmusr |= m68030MMUSR::L; } - if (table.m_s && (fc & 4)) { + if (pt.m_s && (fc & 4)) { + // XXX not tested mmusr |= m68030MMUSR::S; } - if (table.m_wp) { + if (pt.m_wp) { mmusr |= m68030MMUSR::W; } - if (table.m_type == INVALID) { + if (pt.result == m68030PageTableOp::Result::Invalid) { mmusr |= m68030MMUSR::I; } - if (table.m_m) { + if (pt.m_m) { mmusr |= m68030MMUSR::M; } - // XXX N 未実装 - mmusr |= 1; + mmusr |= pt.m_n; + + // アドレスレジスタ指定があれば、最後のディスクリプタアドレスを返す。 + // rn は有効なら 8-15。 + if (rn != 0) { + // XXX 途中でバスエラーが起きるケースは未実装 + if (pt.m_berr == false) { + reg.R[rn] = pt.m_lastaddr; + } + } - cpu->SetMMUSR(mmusr); + return mmusr; }