--- nono/m680x0/m68030mmu.cpp 2026/04/29 17:05:22 1.1.1.9 +++ nono/m680x0/m68030mmu.cpp 2026/04/29 17:05:57 1.1.1.15 @@ -8,50 +8,17 @@ #include "m68030mmu.h" #include "mainbus.h" -#define TIA (0) -#define TIB (1) -#define TIC (2) -#define TID (3) -#define FCL (4) // 便宜上ね -#define MAX_LV (5) - // ディスクリプタタイプ -#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) do { \ - if (__predict_false(loglevel >= 3)) { \ - cpu->putlogf(3, [&] { return (a)->fill_print(fc); }); \ - } \ -} while (0) - // インデント付き string_format static std::string indent_format(int indent, const char *fmt, ...) __printflike(2, 3); @@ -81,1042 +48,1297 @@ indent_format(int indent, const char *fm } \ } while (0) -// テーブルサーチ操作クラス -class m68030MMU final -{ - public: - m68030MMU(MPU680x0Device *, int loglevel_); - ~m68030MMU(); - // サーチを実行 - void search(int level); +// +// ページテーブル操作クラスはヘッダにいらないのでここで定義する。 +// +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 にエントリを作成 - void make_atc(m68030ATCLine *); + 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, // デバッガによるテーブルサーチ。 + }; - // ATC にエントリを作成 (デバッガアクセス用) - busaddr make_atc_debugger() const; + public: + m68030PageTableOp(MPU68030Device *, int loglevel_, m68030PageTableOp::From); - private: - // リミットチェック - bool limitcheck() const; + // ページテーブルから laddr を検索する。 + Result Search(int maxlv, busaddr laddr); - // ディスクリプタを取得 - bool fetch_desc(uint32, int); + // ページテーブルから laddr に対応する物理アドレスを調べる。デバッガ専用。 + busaddr SearchByDebugger(busaddr laddr); - // MMU による物理メモリ読み込み - busdata phys_read_32(uint32 addr); + // 検索結果から a を埋める。 + void MakeATC(m68030ATC&, m68030ATCLine *a) const; - // MMU による物理メモリ書き込み - busdata phys_write_32(uint32 addr, uint32 data); + 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 {}; // 状態 - bool m_s {}; // S ビット - bool m_wp {}; // WP ビット - bool m_ci {}; // CI ビット - bool m_m {}; // ページディスクリプタの M が立っているか - bool m_lim {}; // サーチ中にリミットを越えた - bool m_berr {}; // テーブルサーチ中にバスエラー - bool m_ptest {}; // PTEST[RW] 命令中なら真 - bool m_debugger {}; // デバッガからのアクセスなら true にする - int x {}; // 段数 TIA〜TID, FCL - uint32 m_lastaddr {}; // 最後にアクセスしたディスクリプタアドレス(PTEST) + 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 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_*) - int loglevel {}; + // ディスクリプタにはショート(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 {}; - MPU680x0Device *cpu; -}; + int loglevel {}; -// 現在の TC:E、TT:E の状態に応じてアドレス変換の有無を切り替える。 -// mmu_enable ならアドレス変換ありのバスアクセスを使う。see m68030bus.cpp -void -MPU680x0Device::mmu_enable_changed() -{ - bool enable; + MPU68030Device *cpu {}; - // TC、TT0、TT1 のいずれかが有効ならアドレス変換あり - enable = (mmu_tc.e || mmu_tt[0].e || mmu_tt[1].e); - - // 変化した時だけ - 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"); - } -} + static const char * const m_lvname[LV_FCL + 1]; +}; // -// レジスタクラス +// アドレス変換。 // -bool -MPU680x0Device::SetSRP(uint32 h, uint32 l) +// リード/フェッチ用のアドレス変換を行う。 +// 変換後の物理アドレス、もしくは BusErr を返す。 +busaddr +MPU68030Device::TranslateRead(const busaddr laddr) { - if ((h & m68030RP::DT_MASK) == 0) { - return false; + m68030ATCLine *a; + + 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); + } } - reg.srp.h = h & m68030RP::H_MASK; - reg.srp.l = l & m68030RP::L_MASK; - return true; -} + 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 -bool -MPU680x0Device::SetCRP(uint32 h, uint32 l) -{ - if ((h & m68030RP::DT_MASK) == 0) { - return false; + 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; } - reg.crp.h = h & m68030RP::H_MASK; - reg.crp.l = l & m68030RP::L_MASK; - return true; + putlog(3, " %s: result %08x CI=%u", __func__, + paddr.Addr(), paddr.IsCInhibit() ? 1 : 0); + return paddr; } -void -MPU680x0Device::SetTT(int n, uint32 data) +// ライト用のアドレス変換を行う。 +// 変換後の物理アドレス、もしくは BusErr を返す。 +busaddr +MPU68030Device::TranslateWrite(const busaddr laddr) { - uint32 lbase; - uint32 lmask; - uint32 sbase; - uint32 smask; + m68030ATCLine *a; - // マスクしてレジスタイメージにセット - reg.tt[n] = data & m68030TT::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); - // 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 | - // +---------------+---------------+-+-+-+-+-+-+-+-+-+-----+-+-----+ +#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 も SSW_RW も Read が %1。 - // ・RWM が %1 なら R/W は無視、RWM が %0 なら R/W の一致も検査するので - // RWM が %0 の時は RW のマスクビットを立てておく。 - // ・FC は busaddr の配置に合わせて1ビット左シフトしたところを使う。 - // ・FCMask は %1 が無視を表しているので NOT しておく。 - // - // target 00000000 00000000 00000000 0R00FFFS : R はアクセス R/~W - // ^ : F はアクセス FC - // : S は FC2 のコピー - // base 00000000 00000000 E0000000 0R00FFF0 : R は期待する R/~W - // & : F は期待する FC - // : E は ~TT:E - // mask 00000000 00000000 10000000 0M00CCC0 : M は R/~W マスク - // : C は FC マスク - sbase = ~data & m68030TT::E; - sbase |= (data & m68030TT::RW) >> 3; - sbase |= (data & m68030TT::FCBASE_MASK) >> 3; - smask = m68030TT::E; - if ((data & m68030TT::RWM) == 0) { - smask |= M68K::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) << 1; - 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 とマッチするか調べる。 -// laddr の上位ワードはそのまま使える。 -// 下位32ビットが論理アドレス (下24ビットは呼び出し側でマスクしなくてよい)。 -// この TT が有効でアドレスがマッチすれば true を返す。 -// (TT:E は演算に織り込んである、このすぐ上の SetTT() のコメント参照) -bool -m68030TT::Match(busaddr laddr) const -{ - bool rv = (((laddr.Get() ^ 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 -MPU680x0Device::SetTC(uint32 data) +// TT から laddr を検索する。 +// laddr が TT0/TT1 どちらかと一致すれば CI ビットを反映したアドレスを返す。 +// 一致しなければ BusErr を返す。バスエラーではないが単に無効値の意。 +busaddr +MPU68030Device::TTMatch(const busaddr laddr) const { - // マスクしてレジスタイメージにセット - reg.tc = data & m68030TC::MASK; + int match = 0; + bool ci = false; - // data を分解してメンバにセット - mmu_tc.Set(data); - - // 有効にする場合は一貫性を確認 - if (mmu_tc.e) { - if (mmu_tc.Check() == false) { - // エラーなら E をクリアして MMU 構成例外。 - // (例外処理自体は呼び出し側で行う) - mmu_tc.e = false; - 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 -MPU680x0Device::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 } -// デストラクタ -m68030ATCTable::~m68030ATCTable() +// この要素を先頭に移動。 +inline void +m68030ATCTable_Custom::MoveHead(m68030ATCLine *a) { + a->age = ++latest_age; + + if (__predict_false(a->age == 0)) { + // latest_age が1周したので、一旦全部リセット。 + for (int i = 0; i < LINES; i++) { + line[i].age = 0; + } + a->age = ++latest_age; + } } -#if !defined(ATC_SINGLE) -// a のエントリを先頭にする。 +// このテーブルのうち最も古い要素を差し出す。 m68030ATCLine * -m68030ATCTable::MoveHead(m68030ATCLine *a) +m68030ATCTable_Custom::Pop() { - // head を向け直す - head = a; - - // 更新 - head->age = counter++; + m68030ATCLine *a = NULL; + uint32 min = (uint32)~0; + uint32 minidx = 0; - // そのエントリを返す - return head; -} -#endif + // 最後に使われたの (あるいは空き) を探す。 + 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]; + } + // ハッシュから削除。 + Invalidate(a); -// -// ATC -// + return a; +} -// コンストラクタ -m68030ATC::m68030ATC(MPU680x0Device *cpu_) +// a からハッシュを作成する。 +// TT.E 有効で a は有効であること。 +void +m68030ATCTable_Custom::MakeHash(const m68030ATCLine *a) { - cpu = cpu_; + //assert(cpu->mmu_tc.e); + //assert(a->IsValid()); - for (auto& table : tables) { - table.cpu = cpu; + 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; } } -// デストラクタ -m68030ATC::~m68030ATC() -{ -} +// 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); + } -// ログレベルの設定。 -// これは Object のオーバーライドではないが、同じ使い方をする。 -void -m68030ATC::SetLogLevel(int loglevel_) -{ - loglevel = loglevel_; - for (auto& table : tables) { - table.loglevel = loglevel_; + a->Invalidate(); } } -#if defined(ATC_SINGLE) -// item をリストの先頭に挿入。 +// PS<4KB の場合に aa が抜けることでこの addr4k の枠が空けばハッシュから削除。 void -m68030ATCTable::insert_head(m68030ATCLine *item) +m68030ATCTable_Custom::InvalidateSub(const m68030ATCLine *aa) { - item->prev = NULL; - item->next = head; - if (head) { - head->prev = item; + 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; + } } - head = item; } -// item をリストの先頭から2番目に挿入。 -void -m68030ATCTable::insert_second(m68030ATCLine *item) +// laddr を ATC から探す。見つからなければ NULL を返す。 +m68030ATCLine * +m68030ATCTable_Custom::FindSub(const busaddr laddr) { - item->prev = head; - if (head) { - item->next = head->next; - if (head->next) { - head->next->prev = item; + uint32 addr = laddr.Addr() & lmask; + + // 同じエントリはないはずなので、順序通りに探さなくてもいいはず。 + for (auto& e : line) { + m68030ATCLine *a = &e; + if (a->GetTag() == addr) { + return a; } - head->next = item; - } else { - item->next = NULL; } + return NULL; } -// item をリストの末尾に追加。 +// 統計情報をリセットする。 void -m68030ATCTable::insert_tail(m68030ATCLine *item) +m68030ATCTable_Custom::ResetStat() { - 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; - } + stat.Clear(); + + std::lock_guard lock(hist_mtx); + hist.Clear(); + hist_tt_once_hit = false; } -// item をリストから外す -void -m68030ATCTable::remove(m68030ATCLine *item) +// デバッグ表示。FC は Table_Custom が持っている。 +std::string +m68030ATCTable_Custom::LineToStr(const m68030ATCLine *a) const { - if (item->prev) - item->prev->next = item->next; - if (item->next) - item->next->prev = item->prev; - if (item == head) - head = item->next; + return LineToStrCommon(a, fc); } -// item をリストの先頭に移す。 -// fill_fetch() から呼ばれるもので、リストは空でなく、item は先頭から -// 3番目以内ではない、という前提で高速化。 -void -m68030ATCTable::move_head(m68030ATCLine *item) +#else +// +// ATC テーブル (Motorola) +// + +// コンストラクタ +m68030ATCTable_Motorola::m68030ATCTable_Motorola(MPU68030Device *cpu_) { - // 先頭でない前提の remove - item->prev->next = item->next; - if (item->next) - item->next->prev = item->prev; + cpu = cpu_; - // 先頭がある前提の insert_head - item->prev = NULL; - item->next = head; - head->prev = item; - head = item; + for (uint pri = 0; pri < LINES; pri++) { + Set(pri, pri); + } } -// item を先頭から2番目に移す。 -// fill_{read,write} から呼ばれるもので、リストは空でなく、item は先頭から -// 3番目以内ではない、という前提で高速化。 +// pri 番目に idx をセットする。 void -m68030ATCTable::move_second(m68030ATCLine *item) +m68030ATCTable_Motorola::Set(uint pri, uint idx) { - // 先頭でない前提の 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; +#if defined(ATC_ORDER64) + // セット先がクリアされてることが分かっている場合にはこれは不利。 + uint i; + if (__predict_true(pri < ORDER0_COUNT)) { + i = 0; + } else { + i = 1; + pri -= ORDER0_COUNT; + } + order[i] &= ~(0x1fULL << (pri * 5)); + order[i] |= (uint64)idx << (pri * 5); +#else + order[pri] = idx; +#endif } -#endif // ATC_SINGLE -// fill_{fetch,read,write}() が各アクセス用の ATC サーチ。 -// あらかじめ論理ページにマスクされたアドレス fc:addr が ATC にあれば -// そのエントリを返す。なければ ATC を更新してそのエントリを返す。 -// fill_peek() はデバッグ用なので一切状態を更新せず結果だけ返す。 - -const m68030ATCLine& -m68030ATC::fill_fetch(busaddr addr_) -{ - uint32 laddr = addr_.Addr(); - uint fc = addr_.GetFC(); - m68030ATCTable *table; - m68030ATCLine *a; - int i; +// 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) -#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; - } +#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); } - - // 見付からなかったので、古いエントリを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; - } + for (uint pri = 0; pri < LINES; pri++) { + uint idx = GetIdx(pri); + COMPARE(idx, pri); } - - // 見付からなかったので、古いエントリを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, loglevel); - mmu.search(7); - mmu.make_atc(a); - - FILL_PRINT(fc, a); - return *a; + return -1; } -const m68030ATCLine& -m68030ATC::fill_read(busaddr addr_) +// ATC から laddr を検索する。 +// 見付かればこのエントリを先頭に移動してそれを返す。 +// 見付からなければ NULL を返す。 +m68030ATCLine * +m68030ATCTable_Motorola::LookupAndMove(const busaddr laddr) { - uint32 laddr = addr_.Addr(); - uint fc = addr_.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, loglevel); - mmu.search(7); - mmu.make_atc(a); - - FILL_PRINT(fc, a); - return *a; } -const m68030ATCLine& -m68030ATC::fill_write(busaddr addr_) +// ATC をすべてフラッシュする。 +void +m68030ATCTable_Motorola::Flush() { - uint32 laddr = addr_.Addr(); - uint fc = addr_.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, "%s: write access but M is not set, " - "so invalidate it", lam_func); +// 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, "%s: write access but M is not set, " - "so invalidate it", lam_func); - 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, "%s: write access but M is not set, " - "so invalidate it", lam_func); - 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, loglevel); - mmu.search(7); - mmu.make_atc(a); - - FILL_PRINT(fc, a); - return *a; } -// ピークアクセス用に ATC を検索する。 -// ATC になければ NULL を返す。ここでは疑似テーブルサーチは行わない。 -// laddr は論理ページアドレスにマスクしておくこと。 -const m68030ATCLine * -m68030ATC::fill_peek(uint32 laddr, uint fc) +// pri 番目の要素を末尾に移動する。 +// pri が末尾でも呼び出してよい。 +void +m68030ATCTable_Motorola::MoveTail(uint pri) { - m68030ATCTable *table; - const 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) { - // ヒットした - MMULOG(3, 1, "%s: match %d:$%08x -> $%08x", lam_func, - fc, laddr, (uint32)a->GetPAddr()); - return a; - } + 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)) { - // ヒットした - MMULOG(3, 1, "%s: match %d:$%08x -> $%08x", lam_func, - a->fc, laddr, (uint32)a->GetPAddr()); - return a; - } + uint d; + for (d = pri; d < LINES - 2; d++) { + order[d] = order[d + 1]; } + order[d] = idx; #endif +} - return NULL; +// 空き要素あるいは最も古い要素を先頭に移動する。 +m68030ATCLine * +m68030ATCTable_Motorola::Pop() +{ + MoveHead(LINES - 1); + return GetLine(0); } -// デバッグ表示。 -// ATC_SINGLE なら fc は this に含まれている。 -// !ATC_SINGLE なら引数で渡される。 -// XXX print といいつつ string を返す -std::string -m68030ATCLine::fill_print(uint fc_) +// 統計情報をリセットする。 +void +m68030ATCTable_Motorola::ResetStat() +{ + stat.Clear(); +} + +// デバッグ表示。FC は a 自身が持っている。 +/*static*/ std::string +m68030ATCTable_Motorola::LineToStr(const m68030ATCLine *a) { -#if !defined(ATC_SINGLE) - uint fc = fc_; + return LineToStrCommon(a, a->GetFC()); +} + #endif - uint32 laddr = GetLAddr(); - if (!IsBusError()) { - uint32 ci = IsCInhibit(); - uint32 wp = IsWProtect(); - uint32 mod = IsModified(); +// デバッグ表示。 +/*static*/ std::string +m68030ATCTable::LineToStrCommon(const m68030ATCLine *a, uint fc_) +{ + uint32 laddr = a->GetLAddr(); + + 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 CI=%d WP=%d M=%d", - fc, laddr, GetPAddr(), ci, 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(); } -} - -#if !defined(ATC_SINGLE) -// addr が一致するエントリをすべてフラッシュ -void -m68030ATCTable::flush(uint32 addr) -{ - for (int i = 0; i < countof(line); i++) { - m68030ATCLine& a = line[i]; - if (a.GetLAddr() == addr) { - a.Invalidate(); - } - } -} #else -// FC が一致するエントリをすべてフラッシュする -void -m68030ATCTable::flush_fc(uint32 fc) -{ - for (int i = 0; i < countof(line); i++) { - m68030ATCLine& a = line[i]; - if (a.fc == fc) { - a.Invalidate(); - } - } + table->ResetStat(); +#endif } -// FC と addr が一致するエントリをすべてフラッシュ +// 統計情報を更新する。 void -m68030ATCTable::flush(uint32 fc, uint32 addr) +m68030ATC::CalcStat() { - for (int i = 0; i < countof(line); i++) { - m68030ATCLine& a = line[i]; - if (a.fc == fc && 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 #endif +} // -// テーブルサーチ +// テーブルサーチ操作。 // // コンストラクタ -m68030MMU::m68030MMU(MPU680x0Device *cpu_, int loglevel_) +m68030PageTableOp::m68030PageTableOp(MPU68030Device *cpu_, int loglevel_, + m68030PageTableOp::From from_) { cpu = cpu_; loglevel = loglevel_; + from = from_; +} + +// ページテーブルから laddr を検索する。 +// 結果はメンバ変数に保持する。 +m68030PageTableOp::Result +m68030PageTableOp::Search(int maxlv, busaddr laddr) +{ + // テーブルサーチで書き込みの時だけ、ディスクリプタの 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); + } - m_type = INVALID; - x = -1; - m_fc = -1; + // デバッグ表示。 + 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; } -// デストラクタ -m68030MMU::~m68030MMU() +// デバッガからの呼び出し用。 +// maxlv 不要なのと、ATC を作って返す必要もないので、単に変換後のアドレス +// (変換出来なければ BusErr) を返す。 +busaddr +m68030PageTableOp::SearchByDebugger(busaddr laddr) { + // writeback_m は false のまま。 + + // テーブルサーチを行う。 + result = DoSearch(7, laddr); + + // ここからサーチ結果によって、結果の物理アドレスを作成。 + if (result == Result::Invalid) { + return BusAddr::BusErr; + } + + uint32 paddr = m_desc2 & cpu->mmu_tc.lmask; + if (result == Result::Early) { + paddr = AssembleEarlyRemains(paddr); + } + + return busaddr(paddr); } -// テーブルサーチのメイン部分。 -// m_type および ATC 作成に必要な情報を埋めてから帰る。 -// ATC は帰った先で作成する。 -void -m68030MMU::search(int level) +// laddr についてテーブルサーチを行い結果コード Result を返す。 +// その他の状態はメンバに保持する。 +m68030PageTableOp::Result +m68030PageTableOp::DoSearch(int maxlv, const busaddr laddr) { const char *rpname; - int dt; - bool is_super; - - m_laddr = cpu->bus.laddr.Addr(); - 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; - - cpu->putlogf(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 の初期化だがコンストラクタで初期化済み。 // ここから図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(); @@ -1126,153 +1348,127 @@ m68030MMU::search(int level) m_desc2 = cpu->GetCRPl(); } - // ルートポインタの DT をチェック + // ルートポインタの DT をチェック。 dt = GET_DT(m_desc1); - MMULOG(3, 2, "%s: %s dt=%s", lam_func, rpname, dtname(dt)); - switch (dt) { - case DT_INVALID: - // 無効は設定できないはずなので、ここに来ないはず - PANIC("%s.DT==Invalid", rpname); - break; - - 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; + // 必要ならファンクションコード・ルックアップ。 + if (__predict_false(cpu->mmu_tc.fcl)) { // たぶん検索レベルを1つ消費するはず。未確認。 - level--; + m_n++; - MMULOG(3, 2, "%s: %s tableaddr=$%08x idx=$%x", lam_func, - m_lvname[x], m_desc2, m_lv[x]); + 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(m_desc2, m_lv[x]) == false) { - return; + // ディスクリプタをフェッチ。 + 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 の下半分。 - for (x = TIA; x < level; x++) { - // リミットチェック - if (m_lastsize == 8 && limitcheck() == false) { - m_type = INVALID; - m_lim = true; - 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, "%s: %s tableaddr=$%08x idx=$%x", lam_func, - 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; } - // 次段のサーチを繰り返す + + // 次段のサーチを繰り返す。 + m_descsize = 1U << dt; break; } - } - - // レベル上限にひっかかった場合、x は過ぎているので一つ戻しておく。 - x--; -} - -// リミットチェックを実行。図9-28。 -// 無効のケースに落ちたら false を返す。 -bool -m68030MMU::limitcheck() const -{ - uint16 limit = (m_desc1 >> 16) & 0x7fff; - // 最上位ビットが L/U - if ((int32)m_desc1 < 0) { - if (m_lv[x] < limit) { - MMULOG(3, 2, "%s: $%04x < $%04x", lam_func, m_lv[x], limit); - return false; - } - } else { - if (m_lv[x] > limit) { - MMULOG(3, 2, "%s: $%04x > $%04x", lam_func, 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); \ @@ -1280,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; @@ -1295,254 +1492,195 @@ m68030MMU::fetch_desc(uint32 tableaddr, // インダイレクトなら idx = 0 でコールしてあるので、 // tableaddr がそのまま PA になるという寸法。 descaddr = tableaddr + idx * m_descsize; - MMULOG(3, 2, "%s: descsize=%d descaddr=$%08x", lam_func, + MMULOG(4, 4, "%s: descsize=%u descaddr=%08x", lam_func, m_descsize, descaddr); - // バスエラー発生フラグをクリアしておく - m_berr = false; - - // ディスクリプタを取得 - if (m_debugger) { - // デバッガアクセス - uint64 data; - data = cpu->mainbus->Peek32(descaddr); - if ((int64)data < 0) { - goto buserr; - } - m_desc1 = data; + bd = cpu->read_phys_4(descaddr); + if (__predict_false(bd.IsBusErr())) { + goto buserr; + } + m_desc1 = bd.Data(); - if (m_descsize == 8) { - data = cpu->mainbus->Peek32(descaddr + 4); - if ((int64)data < 0) { - goto buserr; - } - m_desc2 = data; - } + if (__predict_true(m_descsize == 4)) { + // ショートディスクリプタは m_desc1 と m_desc2 の各フィールドが + // 衝突せず共存している状態とみなすことが出来る。 + m_desc2 = m_desc1; } else { - busdata bd; - - bd = phys_read_32(descaddr); + bd = cpu->read_phys_4(descaddr + 4); if (bd.IsBusErr()) { goto buserr; } - m_desc1 = bd.Data(); - - if (m_descsize == 8) { - bd = phys_read_32(descaddr + 4); - if (bd.IsBusErr()) { - goto buserr; - } - m_desc2 = bd.Data(); - } - - m_lastaddr = descaddr; + m_desc2 = bd.Data(); } + m_lastaddr = descaddr; - // ディスクリプタタイプをチェック - // DT は必ず最初のロングワードの下位2ビット。 + // ディスクリプタタイプをチェック。 do_write = false; dt = GET_DT(m_desc1); - MMULOG(3, 2, "%s: desc=%s dt=%s", lam_func, + 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)) + 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) { - busdata bd = phys_write_32(descaddr, m_desc1); - if (bd.IsBusErr()) { + busdata r = cpu->write_phys_4(descaddr, m_desc1); + if (__predict_false(r.IsBusErr())) { goto buserr; } - // バス動作正常終了 - MMULOG(3, 2, "%s: desc=%s updated", lam_func, sprintf_desc().c_str()); + // バス動作正常終了。 + MMULOG(4, 4, "%s: desc=%s updated", lam_func, sprintf_desc().c_str()); } - // ステータス更新 (OR していく) + // ステータス更新 (OR していく)。 // XXX 実際には CI はページディスクリプタにしかないけど - m_wp |= (m_desc1 & MMU_DESC_WP); - m_ci |= (m_desc1 & MMU_DESC_CI); + m_wp |= (m_desc1 & PAGEDESC_WP); + m_ci |= (m_desc1 & PAGEDESC_CI); if (__predict_false(m_descsize == 8)) { - m_s |= (m_desc1 & MMU_DESC_S); + 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, "%s: type=INVALID -> BusErr", lam_func); - return; + // ディスクリプタのアドレスを計算。 + descaddr = tableaddr + idx * m_descsize; - case NORMAL: // ノーマル - case INDIRECT: // 間接 - paddr = (m_descsize == 4) ? m_desc1 : m_desc2; - MMULOG(3, 2, "%s: type=NORMAL paddr=$%08x", lam_func, - 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("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: %s", lam_func, buf.c_str()); - break; - default: - PANIC("corrupted m_type=%d", (int)m_type); + 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() と同様にテーブルサーチの結果から物理ページアドレスを返す。 -// バスエラーになるケースなら BusErr を返す。 -// デバッガアクセスなので、VM に一切干渉しない。 -busaddr -m68030MMU::make_atc_debugger() const +// 検索結果から ATC エントリを作成。 +// 図9-27 だが雑すぎ。 +void +m68030PageTableOp::MakeATC(m68030ATC& atc, m68030ATCLine *a) const { - uint64 paddr; +#if defined(M68030_CUSTOM_ATC) + // lmask はどのインスタンスでも同じ。 + auto table = atc.GetTable(6); +#else + auto table = atc.GetTable(); +#endif - switch (m_type) { - case INVALID: // 無効 - paddr = busaddr::BusErr; - break; + a->tag = table->MakeTag(m_laddr); - case NORMAL: // ノーマル - case INDIRECT: // 間接 - paddr = (m_descsize == 4) ? m_desc1 : m_desc2; - paddr &= 0xffffff00; - break; + if (result == Result::Invalid) { + a->paddr = {}; + a->data = m68030ATCLine::BUSERROR; + return; + } - 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; + a->paddr = m_paddr.Addr(); + if (__predict_false(result == Result::Early)) { + // PERFORM LIMIT CHECK ?? - default: - PANIC("corrupted m_type=%d", (int)m_type); + a->paddr = AssembleEarlyRemains(a->paddr); } - paddr |= busaddr::TableSearched; - return paddr; + a->data = 0; + if (m_ci) { + a->data |= m68030ATCLine::CINHIBIT; + } + if (m_wp) { + a->data |= m68030ATCLine::WPROTECT; + } + if (m_m) { + a->data |= m68030ATCLine::MODIFIED; + } } -// 物理メモリからの読み込み。 -// 正常に読み込めれば 32bit の値。バスエラーなら (uint64)-1 を返す。 -busdata -m68030MMU::phys_read_32(uint32 addr) +// リミットチェックを実行。図9-28。 +// 無効のケースに落ちたら false を返す。 +bool +m68030PageTableOp::CheckLimit() const { - m68kbus backup; - busdata bd; - - // バス情報を一旦退避 - backup = cpu->bus; + uint16 limit = (m_desc1 >> 16) & 0x7fff; - // 物理バスアクセス - cpu->bus.laddr.Set(addr, busaddr::Read | busaddr::S); - cpu->bus.paddr = cpu->bus.laddr; - bd = cpu->mainbus->Read32(cpu->bus.paddr); - if (__predict_false(bd.IsBusErr())) { - cpu->bus.size = M68K::SSW_SIZE_LONG; - return bd; + // 最上位ビットが 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; + } } - - // バス情報を復元 - cpu->bus = backup; - return bd; + return true; } -// 物理メモリへの書き込み。 -// 正常に書き込めれば 0、バスエラーなら (uint64)-1 を返す。 -busdata -m68030MMU::phys_write_32(uint32 addr, uint32 data) +// 早期終了時の残ってるビットを addr に足したものを返す。 +uint32 +m68030PageTableOp::AssembleEarlyRemains(uint32 addr) const { - m68kbus backup; - busdata bd; - - // バス情報を一旦退避 - backup = cpu->bus; - - // 物理バスアクセス - cpu->bus.laddr.Set(addr, busaddr::Write | busaddr::S); - cpu->bus.paddr = cpu->bus.laddr; - bd = cpu->mainbus->Write32(cpu->bus.paddr, data); - if (__predict_false(bd.IsBusErr())) { - cpu->bus.size = M68K::SSW_SIZE_LONG; - return bd; + for (uint i = lv + 1; i <= cpu->mmu_tc.termlv; i++) { + addr |= m_idx[i] << cpu->mmu_tc.shift[i]; } - - // バス情報を復元 - cpu->bus = backup; - return bd; + return addr; } // ディスクリプタ表示用の文字列を返す。 // m_descsize によって m_desc1, m_desc2 を適切に文字列にする。 std::string -m68030MMU::sprintf_desc() +m68030PageTableOp::sprintf_desc() const { std::string buf; @@ -1555,270 +1693,330 @@ 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 どちらかと一致すれば (bus.ci を更新して) true を返す。 -// 一致しなければ false を返す。 -// 高速化のため mmu_tt_quick による評価は呼び出し側で行なっている。 -bool -MPU680x0Device::CheckTT() +// リセット例外の 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++) { - const m68030TT& tt = mmu_tt[i]; - if (tt.Match(bus.laddr)) { - // 一致した - match += i + 1; - ci |= tt.ci != 0; - putlog(3, " %s: TT%d match", __func__, i); - } +// 現在の TC:E、TT:E の状態に応じてアドレス変換の有無を切り替える。 +// mmu_enable ならアドレス変換ありのバスアクセスを使う。see m68030bus.cpp +void +MPU68030Device::mmu_enable_changed() +{ + bool enable; + + // TC、TT0、TT1 のいずれかが有効ならアドレス変換あり + enable = (mmu_tc.e || mmu_tt[0].e || mmu_tt[1].e); + + // 変化した時だけ + 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"); } +} - // 一致したら PA <- LA - if (match != 0) { - putlog(3, "%s: result %08x (match TT%s)", __func__, - (uint32)bus.laddr, - (match == 3) ? "0 and TT1" : ((match == 1) ? "0" : "1")); - bus.ci = ci; - return true; +bool +MPU68030Device::SetSRP(uint32 h, uint32 l) +{ + if (GET_DT(h) == DT_INVALID) { + return false; } - return false; + reg.srp.h = h & m68030RP::H_MASK; + reg.srp.l = l; + return true; } -// フェッチ用のアドレス変換を行う。 -// 変換できれば true、バスエラーなら false を返す。 bool -MPU680x0Device::TranslateFetch() +MPU68030Device::SetCRP(uint32 h, uint32 l) { - busaddr baddr = bus.laddr; - uint32 laddr = baddr.Addr(); + if (GET_DT(h) == DT_INVALID) { + return false; + } + reg.crp.h = h & m68030RP::H_MASK; + reg.crp.l = l; + return true; +} - // 状態表示 - putlog(3, "%s: enter %c:$%08x pc=$%08x", __func__, - (baddr.IsSuper() ? 'S' : 'U'), - laddr, - ppc); +void +MPU68030Device::SetTT(int n, uint32 data) +{ + // マスクしてレジスタイメージにセット + reg.tt[n] = data & m68030TT::MASK; - // まず TT をチェック - if (__predict_false(mmu_tt_quick[laddr >> 24] != 0)) { - if (CheckTT()) { - return true; - } - } + // メンバと二次変数を作成。 + mmu_tt[n].Set(data); - // ATC からアドレスを取得 - if (__predict_true(mmu_tc.e)) { - baddr &= mmu_tc.lmask; - const m68030ATCLine& a = atc.fill_fetch(baddr); - if (__predict_false(a.IsBusError())) { - putlog(3, "%s: result BusErr (ATC:B)", __func__); - return false; - } +#if defined(M68030_CUSTOM_ATC) + atc.Rehash(); +#endif - bus.paddr.SetAddr(a.GetPAddr() | (laddr & mmu_tc.pgmask)); - bus.ci = a.data & m68030ATCLine::CINHIBIT; + 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; - putlog(3, "%s: result $%08x", __func__, bus.paddr.Addr()); - return true; + base = (((uint64)baseh) << 32) | basel; + mask = (((uint64)maskh) << 32) | maskl; } -// リード用のアドレス変換を行う。 -// 変換できれば true、バスエラーなら false を返す。 +// アクセスがこの TT とマッチするか調べる。 +// laddr の上位ワードはそのまま使える。 +// 下位32ビットが論理アドレス (下24ビットは呼び出し側でマスクしなくてよい)。 +// この TT が有効でアドレスがマッチすれば true を返す。 +// (TT:E は演算に織り込んである、このすぐ上の SetTT() のコメント参照) bool -MPU680x0Device::TranslateRead() +m68030TT::Match(busaddr laddr) const { - busaddr baddr = bus.laddr; - uint32 laddr = baddr.Addr(); + bool rv = (((laddr.Get() ^ base) & mask) == 0); + return rv; +} - // 状態表示 - putlog(3, "%s: enter %c:$%08x pc=%c:$%08x", __func__, - (baddr.IsSuper() ? 'S' : 'U'), - laddr, - IsSuper() ? 'S' : 'U', - ppc); +// 成功すれば true、MMU 構成例外が起きる場合は false を返す。 +// 呼び出し側で例外を起こすこと。 +bool +MPU68030Device::SetTC(uint32 data) +{ + // マスクしてレジスタイメージにセット + reg.tc = data & m68030TC::MASK; - // まず TT をチェック - if (__predict_false(mmu_tt_quick[laddr >> 24] != 0)) { - if (CheckTT()) { - return true; - } + // data を分解してメンバにセット。 + if (mmu_tc.Set(data) == false) { + // エラーなら E をクリアして MMU 構成例外。 + // (例外処理自体は呼び出し側で行う) + mmu_enable_changed(); + return false; } - // ATC からアドレスを取得 - if (__predict_true(mmu_tc.e)) { - baddr &= mmu_tc.lmask; - const m68030ATCLine& a = atc.fill_read(baddr); - if (__predict_false(a.IsBusError())) { - putlog(3, "%s: result BusErr (ATC:B)", __func__); - return false; - } + // ATC にも通知。 + atc.SetTC(mmu_tc); - bus.paddr.SetAddr(a.GetPAddr() | (laddr & mmu_tc.pgmask)); - bus.ci = a.data & m68030ATCLine::CINHIBIT; - } +#if defined(M68030_CUSTOM_ATC) + // ハッシュも全部作り直す。 + atc.Rehash(); +#endif - putlog(3, "%s: result $%08x", __func__, bus.paddr.Addr()); + // TC:E が変更されていればスイッチ更新 + mmu_enable_changed(); return true; } -// ライト用のアドレス変換を行う。 -// 変換できれば true、バスエラーなら false を返す。 +// TC レジスタへの書き込み値をこのクラスにセットして二次変数も再構成する。 +// E をオンにした場合は構成チェックを行い、問題なければ true を返す。 +// 問題があった場合は E をオフにした上で false を返す。 +// E をオフにした場合はチェックは行わず true を返す。 bool -MPU680x0Device::TranslateWrite() +m68030TC::Set(uint32 data) { - busaddr baddr = bus.laddr; - uint32 laddr = baddr.Addr(); + // 一次メンバにセット。 + 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; - // 状態表示 - putlog(3, "%s: enter %c:$%08x pc=%c:$%08x", __func__, - (baddr.IsSuper() ? 'S' : 'U'), - laddr, - IsSuper() ? 'S' : 'U', - ppc); + config_ok = Check(); - // まず TT をチェック - if (__predict_false(mmu_tt_quick[laddr >> 24] != 0)) { - if (CheckTT()) { - return true; - } + // 構成が有効なら E に関わらず二次変数を用意しておく。 + if (config_ok) { + MakeMask(); } - // ATC からアドレスを取得 - if (__predict_true(mmu_tc.e)) { - baddr &= mmu_tc.lmask; - const m68030ATCLine& a = atc.fill_write(baddr); - if (__predict_false(a.IsBusError())) { - putlog(3, "%s: result BusErr (ATC:B)", __func__); - return false; - } - if (__predict_false(a.IsWProtect())) { - // Write アクセスで WP が立ってたらバスエラー - putlog(3, "%s: result Buserr (ATC:WP)", __func__); - return false; - } - - bus.paddr.SetAddr(a.GetPAddr() | (laddr & mmu_tc.pgmask)); - bus.ci = a.data & m68030ATCLine::CINHIBIT; + // E がオンで構成例外になる場合はオフにする。 + if (e && config_ok == false) { + e = false; + return false; } - putlog(3, "%s: result $%08x", __func__, bus.paddr.Addr()); return true; } -// ピーク用のアドレス変換を行う。 -// 変換できれば対応する物理アドレスを、バスエラーなら BusErr を返す。 -// テーブルサーチを行ったら TableSearched ビットを立てる。 -busaddr -MPU680x0Device::TranslatePeek(busaddr baddr) +// MMU 構成例外になるケースなら false を返す。 +bool +m68030TC::Check() const { - busaddr paddr; - uint32 laddr = baddr.Addr(); + 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; +} - // 状態表示 - putlog(3, "%s: enter %c:$%08x", __func__, - (baddr.IsSuper() ? 'S' : 'U'), - laddr); - - // まず TT をチェック - paddr = laddr; - if (__predict_false(mmu_tt_quick[laddr >> 24] != 0)) { - for (int i = 0; i < 2; i++) { - m68030TT& tt = mmu_tt[i]; - if (tt.Match(baddr)) { - // 一致した。 - // CI はここでは使わないため、一致したことだけわかればよい - putlog(3, "%s: result $%08x (match tt)", - __func__, (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 (ATC と MMU テーブルサーチ)。 - if (mmu_tc.e) { - // ATC からこっそりアドレスを取得 - const m68030ATCLine *a; - a = atc.fill_peek(laddr & mmu_tc.lmask, baddr.GetFC()); - if (a != NULL) { - paddr = a->GetPAddr(); - paddr |= laddr & mmu_tc.pgmask; - putlog(3, "%s: result $%08x (match atc)", - __func__, (uint32)paddr); - return paddr; - } - - // 見付からなかったのでこっそりテーブルサーチ。 - - // mmu.search() はさすがに bus を使うので事前に退避。 - m68kbus backup = bus; - - bus.laddr = baddr; - m68030MMU mmu(this, loglevel); - mmu.m_debugger = true; - mmu.search(7); - paddr = mmu.make_atc_debugger(); - // リストア - bus = backup; - - if (paddr.IsBusErr()) { - // TC が有効だがヒットしなかったらバスエラー - putlog(3, "%s: result BusErr", __func__); - } else { - paddr |= laddr & mmu_tc.pgmask; - putlog(3, "%s: result $%08x (table search)", - __func__, paddr.Addr()); + // 最終段を求めておく。 + termlv = LV_TID; + for (int i = LV_TIA; i <= LV_TID; i++) { + if (tix[i] == 0) { + termlv = i - 1; + break; } - return paddr; } +} - // TT が無効かまたは有効でもヒットせず、 - // かつ TC も無効だったら (こっちは「有効でヒットせず」は含まない)、 - // 論理アドレスをそのまま返す - putlog(3, "%s: result $%08x (no translation)", __func__, laddr); - return laddr; +void +MPU68030Device::SetMMUSR(uint16 data) +{ + reg.mmusr = data & m68030MMUSR::MASK; } + // // 命令 // @@ -1826,7 +2024,7 @@ MPU680x0Device::TranslatePeek(busaddr ba // ir2 の下位5ビットから FC を返す。 // 不当パターンなら C++ の例外をスローする。 uint32 -MPU680x0Device::get_fc() +MPU68030Device::get_fc() { uint fcfield = ir2 & 0x1f; @@ -1847,47 +2045,52 @@ MPU680x0Device::get_fc() // MMU 不当命令 (2ワード目で確定) void -MPU680x0Device::mmu_op_illg2() +MPU68030Device::ops_mmu30_illg2() { 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 -MPU680x0Device::mmu_op_pflush() +MPU68030Device::ops_mmu30_pflush() { - uint32 mask; - uint32 fc; + uint32 mask = (ir2 >> 5) & 7; + uint32 fc = get_fc(); - mask = (ir2 >> 5) & 7; - fc = get_fc(); - atc.flush(fc, mask); + atc.Flush(fc, mask); } // PFLUSH fc,#mask,ea 命令 void -MPU680x0Device::mmu_op_pflush_ea() +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_copro(); - mask = (ir2 >> 5) & 7; - fc = get_fc(); - atc.flush(fc, mask, ea); + atc.Flush(fc, mask, ea); } // PLOADR/PLOADW 命令 void -MPU680x0Device::mmu_op_pload() +MPU68030Device::ops_mmu30_pload() { uint32 ea; uint32 fc; + busaddr laddr; + m68030ATCLine *a; if ((ir2 & 0x01e0) != 0) { - mmu_op_illg2(); + ops_mmu30_illg2(); return; } ea = cea_copro(); @@ -1896,115 +2099,217 @@ MPU680x0Device::mmu_op_pload() // 実効アドレスを論理アドレスとする ATC を作成。 // ってこれでいいのか? if ((ir2 & 0x0200)) { - busaddr addr(ea, busaddr::FC(fc) | busaddr::R); - atc.fill_read(addr); + laddr = busaddr(ea) | busaddr::FC(fc) | BusAddr::R; } else { - busaddr addr(ea, busaddr::FC(fc) | busaddr::W); - atc.fill_write(addr); + 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 { + // テーブルサーチを行って ATC を作成。 + a = table->Pop(); + PageTableSearch(laddr, a); + } +#endif } // PTESTR/PTESTW 命令 void -MPU680x0Device::mmu_op_ptest() +MPU68030Device::ops_mmu30_ptest() { - uint level = (ir2 >> 10) & 7; - busaddr rw = (ir2 & 0x0200) ? busaddr::R : busaddr::W; - uint rn = (ir2 >> 5) & 0xf; - 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_copro(); - fc = get_fc(); - mmusr = 0; + busaddr laddr = busaddr(ea) + | busaddr::FC(fc) + | (isread ? BusAddr::R : BusAddr::W); if (level == 0) { + // レベル0で A 指定なら Fライン例外。 + // A が 0 なら ARegNo も 0 でなければならない、とは書いてあるが + // どうなるかは書いていない。とりあえず例外にしておく。 if (rn != 0) { - // レベル0で An 指定なら Fライン例外 - mmu_op_illg2(); + 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); + } - // laddr は IS と PS のところを落としたもの。 - // ssw_laddr のアドレスは IS を落とさないもの (PSは不問)。 - busaddr ssw_laddr(ea, busaddr::FC(fc) | rw); - uint32 laddr = ea & mmu_tc.lmask; + SetMMUSR(mmusr); +} - const m68030ATCLine *a = atc.fill_peek(laddr, fc); +// 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) { - // ATC に見付かった。Modified の状態を返す。 - if (a->IsBusError()) { - mmusr |= m68030MMUSR::B; - } else if (a->IsWProtect()) { - mmusr |= m68030MMUSR::W; - } else if (a->IsModified()) { - mmusr |= m68030MMUSR::M; - } + n = a - &table->line[0]; } else { - // ATC に見付からなかった。Invalid を立てる。 - mmusr = m68030MMUSR::I; - } - - // どちらにしても TT を評価。 - for (int i = 0; i < 2; i++) { - m68030TT& tt = mmu_tt[i]; - if (tt.Match(ssw_laddr)) { - mmusr |= m68030MMUSR::T; - } + 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 { - // レベル 1-7 ならテーブルサーチを実行。 - - bus.laddr.Set(ea, busaddr::FC(fc) | rw); - m68030MMU table(this, loglevel); - table.m_ptest = true; - table.search(level); + // ATC に見付からなかった。 + } +#else + if (TTMatch(laddr).IsOK()) { + mmusr |= m68030MMUSR::T; + } + auto table = atc.GetTable(); + a = table->LookupOnly(laddr); +#endif - if (table.m_berr) { + if (a) { + // 見付かれば Modified 等の状態を返す。 + if (a->IsBusError()) { mmusr |= m68030MMUSR::B; - } - if (table.m_lim) { - // XXX not tested - mmusr |= m68030MMUSR::L; - } - if (table.m_s && (fc & 4)) { - // XXX not tested - mmusr |= m68030MMUSR::S; - } - if (table.m_wp) { + } else if (a->IsWProtect()) { mmusr |= m68030MMUSR::W; - } - if (table.m_type == INVALID) { - mmusr |= m68030MMUSR::I; - } - if (table.m_m) { + } else if (a->IsModified()) { mmusr |= m68030MMUSR::M; } - // N を求める。 - // もともとそういう風には出来ていないので逆算している。 - int n; - if (mmu_tc.fcl) { - // たぶんファンクションテーブルで1段消費する? (未確認) - if (table.x == FCL) { - n = 1; - } else { - n = table.x + 2; - } - } else { - n = table.x + 1; - } - mmusr |= n; + } else { + // 見付からなかったら Invalid を立てる。 + mmusr |= m68030MMUSR::I; + } + return mmusr; +} - // アドレスレジスタ指定があれば、最後のディスクリプタアドレスを返す。 - // rn は有効なら 8-15。 - if (rn != 0) { - // XXX 途中でバスエラーが起きるケースは未実装 - if (table.m_berr == false) { - reg.R[rn] = table.m_lastaddr; - } +// 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 (pt.m_s && (fc & 4)) { + // XXX not tested + mmusr |= m68030MMUSR::S; + } + if (pt.m_wp) { + mmusr |= m68030MMUSR::W; + } + if (pt.result == m68030PageTableOp::Result::Invalid) { + mmusr |= m68030MMUSR::I; + } + if (pt.m_m) { + mmusr |= m68030MMUSR::M; + } + mmusr |= pt.m_n; + + // アドレスレジスタ指定があれば、最後のディスクリプタアドレスを返す。 + // rn は有効なら 8-15。 + if (rn != 0) { + // XXX 途中でバスエラーが起きるケースは未実装 + if (pt.m_berr == false) { + reg.R[rn] = pt.m_lastaddr; } } - SetMMUSR(mmusr); + return mmusr; }