--- nono/m680x0/m68030mmu.cpp 2026/04/29 17:05:48 1.1.1.14 +++ nono/m680x0/m68030mmu.cpp 2026/04/29 17:05:57 1.1.1.15 @@ -8,43 +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 ) - // インデント付き string_format static std::string indent_format(int indent, const char *fmt, ...) __printflike(2, 3); @@ -74,449 +48,469 @@ indent_format(int indent, const char *fm } \ } while (0) -// テーブルサーチ操作クラス -class m68030MMU final + +// +// ページテーブル操作クラスはヘッダにいらないのでここで定義する。 +// +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; + public: - m68030MMU(MPU68030Device *, int loglevel_); - ~m68030MMU(); + // 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, // デバッガによるテーブルサーチ。 + }; - // サーチを実行 - void search(int level, busaddr laddr); + public: + m68030PageTableOp(MPU68030Device *, int loglevel_, m68030PageTableOp::From); - // ATC にエントリを作成 - void make_atc(m68030ATCLine *); + // ページテーブルから laddr を検索する。 + Result Search(int maxlv, busaddr laddr); - // ATC にエントリを作成 (デバッガアクセス用) - busaddr make_atc_debugger() const; + // ページテーブルから laddr に対応する物理アドレスを調べる。デバッガ専用。 + busaddr SearchByDebugger(busaddr laddr); - private: - // リミットチェック - bool limitcheck() const; + // 検索結果から a を埋める。 + void MakeATC(m68030ATC&, m68030ATCLine *a) const; - // ディスクリプタを取得 - bool fetch_desc(uint32, int); + 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) - private: - int m_lv[MAX_LV] {}; - uint32 m_desc1 {}; // ディスクリプタ - uint32 m_desc2 {}; // ロングディスクリプタの後半部 - int m_descsize {}; // ディスクリプタサイズ (4 or 8) - int m_lastsize {}; // 前段のディスクリプタサイズ + 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 {}; - // デバッグ用文字列 - static const char * const m_lvname[MAX_LV]; + private: + // laddr の各階層ごとの値。これがテーブルを引く際のインデックス。 + std::array m_idx {}; - int loglevel {}; + int lv {}; // 現在位置(LV_*) - MPU68030Device *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 -MPU68030Device::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; - 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 -MPU68030Device::SetSRP(uint32 h, uint32 l) +// リード/フェッチ用のアドレス変換を行う。 +// 変換後の物理アドレス、もしくは BusErr を返す。 +busaddr +MPU68030Device::TranslateRead(const busaddr laddr) { - if ((h & m68030RP::DT_MASK) == 0) { - return false; - } - reg.srp.h = h & m68030RP::H_MASK; - reg.srp.l = l & m68030RP::L_MASK; - return true; -} + m68030ATCLine *a; -bool -MPU68030Device::SetCRP(uint32 h, uint32 l) -{ - if ((h & m68030RP::DT_MASK) == 0) { - return false; - } - reg.crp.h = h & m68030RP::H_MASK; - reg.crp.l = l & m68030RP::L_MASK; - return true; -} + uint32 fc = laddr.GetFC(); + putlog(3, " %s: enter %u(%s).%08x pc=%c.%08x", __func__, + fc, FCstr(fc), + laddr.Addr(), + (IsSuper() ? 'S' : 'U'), + ppc); -void -MPU68030Device::SetTT(int n, uint32 data) -{ - uint32 lbase; - uint32 lmask; - uint32 sbase; - uint32 smask; - - // ハッシュから TT#n の成分を一旦クリアする。 - if (mmu_tt[n].e) { - // 変更前の TT#n にマッチして、現在の TT#(1-n) にマッチしない - // アドレスブロックに対応する 4096 エントリをクリア。 - for (int fc = 1; fc <= 6; fc++) { - auto *table = atc.fctables[fc]; - if (table) { - busaddr laddr = busaddr(0) | busaddr::FC(fc); - for (int i = 0; i < 256; i++) { - // XXX R/W は? - if (mmu_tt[n].Match(laddr) && - mmu_tt[1 - n].Match(laddr) == false) - { - uint32 baseidx = i << (24 - 12); - memset(&table->hash[baseidx], - m68030ATCTable::HASH_NONE, 4096); - } - laddr += 0x0100'0000; - } - } +#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.tt[n] = data & m68030TT::MASK; - - // 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 | - // +---------------+---------------+-+-+-+-+-+-+-+-+-+-----+-+-----+ - - mmu_tt[n].e = (data & m68030TT::E); - mmu_tt[n].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は論理アドレスマスク - // - lbase = (data & m68030TT::LBASE_MASK); - lmask = (~data & m68030TT::LMASK_MASK) << 8; - - // 上位ロングワード - // ・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 |= M68030::SSW_RW; + 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); } - smask |= (~data & m68030TT::FCMASK_MASK) << 1; +#endif - mmu_tt[n].base = lbase | (((uint64)sbase) << 32); - mmu_tt[n].mask = lmask | (((uint64)smask) << 32); + putlog(4, " ATC match %s", table->LineToStr(a).c_str()); - // 更新後にこの TT が有効ならハッシュも更新。 - if (mmu_tt[n].e) { - // この TT#n にマッチするアドレスブロックに対応する 4096 エントリを - // TT 印にする。 - for (uint fc = 1; fc <= 6; fc++) { - auto *table = atc.fctables[fc]; - if (table) { - busaddr laddr = busaddr(0) | busaddr::FC(fc); - for (uint i = 0; i < 256; i++) { - // XXX R/W は? - if (mmu_tt[n].Match(laddr)) { - uint32 baseidx = i << (24 - 12); - memset(&table->hash[baseidx], - m68030ATCTable::HASH_TT, 4096); - } - laddr += 0x0100'0000; - } - } - } + if (__predict_false(a->IsBusError())) { + putlog(3, " %s: result BusErr", __func__); + return BusAddr::BusErr; } - - mmu_enable_changed(); + 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; } -// アクセスがこの TT とマッチするか調べる。 -// laddr の上位ワードはそのまま使える。 -// 下位32ビットが論理アドレス (下24ビットは呼び出し側でマスクしなくてよい)。 -// この TT が有効でアドレスがマッチすれば true を返す。 -// (TT:E は演算に織り込んである、このすぐ上の SetTT() のコメント参照) -bool -m68030TT::Match(busaddr laddr) const +// ライト用のアドレス変換を行う。 +// 変換後の物理アドレス、もしくは BusErr を返す。 +busaddr +MPU68030Device::TranslateWrite(const busaddr laddr) { - bool rv = (((laddr.Get() ^ base) & mask) == 0); - return rv; -} + m68030ATCLine *a; -// 成功すれば true、MMU 構成例外が起きる場合は false を返す。 -// 呼び出し側で例外を起こすこと。 -bool -MPU68030Device::SetTC(uint32 data) -{ - // マスクしてレジスタイメージにセット - reg.tc = data & m68030TC::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 を分解してメンバにセット - mmu_tc.Set(data); +#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; + } + } - // 有効にする場合は一貫性を確認 - if (mmu_tc.e) { - if (mmu_tc.Check() == false) { - // エラーなら E をクリアして MMU 構成例外。 - // (例外処理自体は呼び出し側で行う) - mmu_tc.e = false; - mmu_enable_changed(); - return false; + // 書き込み可なのにまだ 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 - // マスクをあらかじめ用意しておく - mmu_tc.MakeMask(); + putlog(4, " ATC match %s", table->LineToStr(a).c_str()); - // ATC でも PS を覚えておく必要がある。 - for (int i = 0; i < countof(atc.tables); i++) { - auto *table = atc.tables[i].get(); - table->ps4k = mmu_tc.ps - 12; + 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; } - // TC:E が変更されていればスイッチ更新 - mmu_enable_changed(); - return true; -} - -// TC レジスタへの書き込み値を m68030TC クラスにセットする。 -void -m68030TC::Set(uint32 data) -{ - 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; + 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; } -// 現在の値が MMU 構成例外になるなら false を返す。 -bool -m68030TC::Check() const +// ピーク用のアドレス変換を行う。 +// 変換できれば対応する物理アドレスを、バスエラーなら BusErr を返す。 +// テーブルサーチを行ったら TableSearched ビットを立てる。 +busaddr +MPU68030Device::TranslatePeek(const busaddr laddr) { - 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; -} + const m68030ATCLine *a; + busaddr paddr; -// 論理マスク、ページマスクを作成。(テーブルサーチで参照される) -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; + uint32 fc = laddr.GetFC(); + putlog(5, " %s: enter %u(%s).%08x", __func__, + fc, FCstr(fc), + laddr.Addr()); - // アドレスを (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 - uint sh = ps; - for (int i = TID; i >= TIA; i--) { - if (tix[i] > 0) { - shift[i] = sh; - mask[i] = (1U << tix[i]) - 1; - sh += tix[i]; +#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; } } -} - -void -MPU68030Device::SetMMUSR(uint16 data) -{ - reg.mmusr = data & m68030MMUSR::MASK; -} - -// リセット例外の CPU 固有部分。 -void -MPU68030Device::ResetMMU() -{ - SetTC(GetTC() & ~m68030TC::TC_E); - SetTT(0, GetTT(0) & ~m68030TT::E); - SetTT(1, GetTT(1) & ~m68030TT::E); -} - - -// -// ATC -// -// 今となっては独立したクラスである必要はなくなっているが、これを -// MPU68030Device にマージするとパフォーマンスが下がるので維持してある。 -// - -// コンストラクタ -m68030ATC::m68030ATC(MPU68030Device *cpu_) -{ - cpu = cpu_; - - tables[0].reset(new m68030ATCTable(cpu, 1)); - tables[1].reset(new m68030ATCTable(cpu, 2)); - tables[2].reset(new m68030ATCTable(cpu, 5)); - tables[3].reset(new m68030ATCTable(cpu, 6)); - - fctables[1] = tables[0].get(); - fctables[2] = tables[1].get(); - fctables[5] = tables[2].get(); - fctables[6] = tables[3].get(); -} + 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 -// デストラクタ -m68030ATC::~m68030ATC() -{ -} + // こっそりテーブルサーチ。 + m68030PageTableOp pt(this, loglevel, m68030PageTableOp::ByDebugger); + paddr = pt.SearchByDebugger(laddr); + paddr |= BusAddr::TableSearched; -// ログレベルの設定。 -// これは Object のオーバーライドではないが、同じ使い方をする。 -void -m68030ATC::SetLogLevel(int loglevel_) -{ - loglevel = loglevel_; - for (int i = 0; i < countof(tables); i++) { - auto *table = tables[i].get(); - table->loglevel = loglevel_; + 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; } -// ATC をすべてフラッシュする。命令からコールされる -void -m68030ATC::flush() +// TT から laddr を検索する。 +// laddr が TT0/TT1 どちらかと一致すれば CI ビットを反映したアドレスを返す。 +// 一致しなければ BusErr を返す。バスエラーではないが単に無効値の意。 +busaddr +MPU68030Device::TTMatch(const busaddr laddr) const { - for (int i = 0; i < countof(tables); i++) { - auto *table = tables[i].get(); - table->Flush(); - } -} + int match = 0; + bool ci = false; -// ATC の fc, mask が一致するエントリをフラッシュする。 -// 命令からコールされる。 -void -m68030ATC::flush(uint32 fc, uint32 mask) -{ - for (uint i = 1; i <= 6; i++) { - if (((i ^ fc) & mask) == 0) { - // FC が一致したテーブルを全フラッシュ - auto *table = fctables[i]; - if (table) { - table->Flush(); + 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; } } } + + 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; } -// ATC の fc, mask, addr が一致するエントリをフラッシュする。 -// 命令からコールされる。 +// ページテーブルから laddr を検索する。 +// 見付かっても見付からなくても、a を更新して返す。 +// ここでは指定の a を埋めるだけで a の移動は呼び出し側の責任。 void -m68030ATC::flush(uint32 fc, uint32 mask, uint32 addr) +MPU68030Device::PageTableSearch(busaddr laddr, m68030ATCLine *a) { - for (uint i = 1; i <= 6; i++) { - if (((i ^ fc) & mask) == 0) { - // FC が一致したテーブルから addr が一致したエントリをフラッシュ - auto *table = fctables[i]; - if (table) { - table->Flush(addr); - } - } - } + m68030PageTableOp pt(this, loglevel, m68030PageTableOp::ByTranslation); + pt.Search(7, laddr); + pt.MakeATC(atc, a); } +#if defined(M68030_CUSTOM_ATC) // -// ATC テーブル +// ATC テーブル (Custom) // // アドレス変換の本来のフローはこんな感じだが、 @@ -561,25 +555,62 @@ m68030ATC::flush(uint32 fc, uint32 mask, // フラッシュが起きるんじゃないかな… // コンストラクタ -m68030ATCTable::m68030ATCTable(MPU68030Device *cpu_, uint fc_) +m68030ATCTable_Custom::m68030ATCTable_Custom(MPU68030Device *cpu_, uint fc_) { cpu = cpu_; fc = fc_; - std::fill(hash.begin(), hash.end(), HASH_NONE); + ClearHash(); + Flush(); +} +// ハッシュをすべてクリアする。 +void +m68030ATCTable_Custom::ClearHash() +{ + std::fill(hash.begin(), hash.end(), HASH_NONE); latest_age = 0; - Flush(); } -// デストラクタ -m68030ATCTable::~m68030ATCTable() +// ハッシュを完全に作り直す。 +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 -m68030ATCTable::Flush() +m68030ATCTable_Custom::Flush() { for (int i = 0; i < LINES; i++) { Invalidate(&line[i]); @@ -590,7 +621,7 @@ m68030ATCTable::Flush() // この ATC テーブルから addr が一致するエントリをすべてフラッシュする。 void -m68030ATCTable::Flush(uint32 addr) +m68030ATCTable_Custom::Flush(uint32 addr) { for (int i = 0; i < LINES; i++) { m68030ATCLine *a = &line[i]; @@ -602,7 +633,7 @@ m68030ATCTable::Flush(uint32 addr) // この要素を先頭に移動。 inline void -m68030ATCTable::MoveHead(m68030ATCLine *a) +m68030ATCTable_Custom::MoveHead(m68030ATCLine *a) { a->age = ++latest_age; @@ -617,7 +648,7 @@ m68030ATCTable::MoveHead(m68030ATCLine * // このテーブルのうち最も古い要素を差し出す。 m68030ATCLine * -m68030ATCTable::RemoveTail() +m68030ATCTable_Custom::Pop() { m68030ATCLine *a = NULL; uint32 min = (uint32)~0; @@ -644,9 +675,13 @@ m68030ATCTable::RemoveTail() } // a からハッシュを作成する。 +// TT.E 有効で a は有効であること。 void -m68030ATCTable::MakeHash(const m68030ATCLine *a) +m68030ATCTable_Custom::MakeHash(const m68030ATCLine *a) { + //assert(cpu->mmu_tc.e); + //assert(a->IsValid()); + uint32 addr4k = a->GetLAddr() >> 12; int idx = a - &line[0]; @@ -662,19 +697,28 @@ m68030ATCTable::MakeHash(const m68030ATC } // ATC a を無効にする。すでに無効なら何もしない。 +// +// ハッシュから削除する際は TT を指していないか必ず確認すること。 +// ATC エントリが出来た後で、そこに TT に設定すると起きうる。 +// LRU の ATC ならそのエントリは参照されずに消えていくだけで害はないが、 +// ハッシュは TT で上書きされているので消す際に注意する必要がある。 void -m68030ATCTable::Invalidate(m68030ATCLine *a) +m68030ATCTable_Custom::Invalidate(m68030ATCLine *a) { if (a->IsValid()) { // 有効ならハッシュから削除。 if (__predict_true(ps4k == 0)) { uint32 addr4k = a->GetLAddr() >> 12; - hash[addr4k] = HASH_NONE; + 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++) { - hash[addr4k + i] = HASH_NONE; + if ((int8)hash[addr4k + i] >= 0) { + hash[addr4k + i] = HASH_NONE; + } } } else { // PS<4KB、自分が抜けることでこの枠が空くかどうか @@ -687,14 +731,15 @@ m68030ATCTable::Invalidate(m68030ATCLine // PS<4KB の場合に aa が抜けることでこの addr4k の枠が空けばハッシュから削除。 void -m68030ATCTable::InvalidateSub(const m68030ATCLine *aa) +m68030ATCTable_Custom::InvalidateSub(const m68030ATCLine *aa) { const uint32 MASK_4K = 0xfffff000 | m68030ATCLine::INVALID; uint32 target_laddr = aa->GetTag() & MASK_4K; bool in_use = false; - for (int i = 0; i < LINES; i++) { - auto *a = &line[i]; + for (auto& e : line) { + m68030ATCLine *a = &e; + // 自分自身は今から削除予定なので除く。 if (a == aa) { continue; @@ -708,19 +753,22 @@ m68030ATCTable::InvalidateSub(const m680 // 誰もいなくなれば削除。 if (in_use == false) { - hash[target_laddr >> 12] = HASH_NONE; + uint k = target_laddr >> 12; + if (hash[k] != HASH_SUB) { + hash[k] = HASH_NONE; + } } } // laddr を ATC から探す。見つからなければ NULL を返す。 m68030ATCLine * -m68030ATCTable::FindSub(const busaddr laddr) +m68030ATCTable_Custom::FindSub(const busaddr laddr) { - uint32 addr = laddr.Addr() & cpu->mmu_tc.lmask; + uint32 addr = laddr.Addr() & lmask; // 同じエントリはないはずなので、順序通りに探さなくてもいいはず。 - for (int i = 0; i < LINES; i++) { - auto *a = &line[i]; + for (auto& e : line) { + m68030ATCLine *a = &e; if (a->GetTag() == addr) { return a; } @@ -728,124 +776,569 @@ m68030ATCTable::FindSub(const busaddr la return NULL; } -// テーブルサーチを行う。 -// テーブルという用語がぶつかっているので MMU Search にしてある。 +// 統計情報をリセットする。 +void +m68030ATCTable_Custom::ResetStat() +{ + stat.Clear(); + + std::lock_guard lock(hist_mtx); + hist.Clear(); + hist_tt_once_hit = false; +} + +// デバッグ表示。FC は Table_Custom が持っている。 +std::string +m68030ATCTable_Custom::LineToStr(const m68030ATCLine *a) const +{ + return LineToStrCommon(a, fc); +} + +#else +// +// ATC テーブル (Motorola) +// + +// コンストラクタ +m68030ATCTable_Motorola::m68030ATCTable_Motorola(MPU68030Device *cpu_) +{ + cpu = cpu_; + + for (uint pri = 0; pri < LINES; pri++) { + Set(pri, pri); + } +} + +// 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; + } + order[i] &= ~(0x1fULL << (pri * 5)); + order[i] |= (uint64)idx << (pri * 5); +#else + order[pri] = idx; +#endif +} + +// 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_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; +} + +// ATC から laddr を検索する。 +// 見付かればこのエントリを先頭に移動してそれを返す。 +// 見付からなければ NULL を返す。 m68030ATCLine * -m68030ATCTable::MMUSearch(busaddr laddr) +m68030ATCTable_Motorola::LookupAndMove(const busaddr laddr) { - m68030ATCLine *a = RemoveTail(); - a->tag = laddr.Addr() & cpu->mmu_tc.lmask; + 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 (__predict_false(pri != 0)) { + MoveHead(pri); + } + return GetLine(0); + } else { + stat.miss++; + MMULOG(4, 3, "ATC.LookupAndMove: not match"); + return NULL; + } +} - m68030MMU mmu(cpu, loglevel); - mmu.search(7, laddr); - mmu.make_atc(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; + } +} - MakeHash(a); - return a; +// ATC をすべてフラッシュする。 +void +m68030ATCTable_Motorola::Flush() +{ + // 全部無効にするので、無効にしたのを最後尾に回す必要ない。 + for (auto& e : line) { + m68030ATCLine *a = &e; + a->Invalidate(); + } +} + +// 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(); + MoveTail(pri); + } + } } +// 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); + } + } +} + +// 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 + for (uint d = pri; d > 0; d--) { + order[d] = order[d - 1]; + } + order[0] = idx; +#endif +} + +// pri 番目の要素を末尾に移動する。 +// pri が末尾でも呼び出してよい。 +void +m68030ATCTable_Motorola::MoveTail(uint pri) +{ + 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 + uint d; + for (d = pri; d < LINES - 2; d++) { + order[d] = order[d + 1]; + } + order[d] = idx; +#endif +} + +// 空き要素あるいは最も古い要素を先頭に移動する。 +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()); +} + +#endif + // デバッグ表示。 -std::string -m68030ATCTable::LineToStr(const m68030ATCLine *a) const +/*static*/ std::string +m68030ATCTable::LineToStrCommon(const m68030ATCLine *a, uint fc_) { uint32 laddr = a->GetLAddr(); if (!a->IsBusError()) { uint32 paddr = a->GetPAddr(); - uint32 ci = a->IsCInhibit(); - uint32 wp = a->IsWProtect(); - uint32 mod = a->IsModified(); + char ci = a->IsCInhibit() ? 'C' : '-'; + char wp = a->IsWProtect() ? 'W' : '-'; + char mod = a->IsModified() ? 'M' : '-'; return indent_format(1, - "ATC: match %u:$%08x -> $%08x CI=%u WP=%u M=%u", - fc, laddr, paddr, ci, wp, mod); + "%u.%08x %08x %c%c%c", + fc_, laddr, paddr, ci, wp, mod); } else { return indent_format(1, - "ATC: match %u:$%08x -> BusErr", - fc, laddr); + "%u.%08x BusError", + fc_, laddr); } } // -// テーブルサーチ +// ATC // // コンストラクタ -m68030MMU::m68030MMU(MPU68030Device *cpu_, int loglevel_) +m68030ATC::m68030ATC(MPU68030Device *cpu_) { cpu = cpu_; + +#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 + table.reset(new m68030ATCTable_Motorola(cpu)); +#endif +} + +// ログレベルの設定。 +// これは Object のオーバーライドではないが、同じ使い方をする。 +void +m68030ATC::SetLogLevel(int loglevel_) +{ loglevel = loglevel_; - m_type = INVALID; - x = -1; +#if defined(M68030_CUSTOM_ATC) + for (const auto& table : alltables) { + table->loglevel = loglevel_; + } +#else + table->loglevel = loglevel_; +#endif } -// デストラクタ -m68030MMU::~m68030MMU() +#if defined(M68030_CUSTOM_ATC) +// ハッシュをすべて作り直す。 +void +m68030ATC::Rehash() { + for (const auto& table : alltables) { + table->Rehash(); + } } +#endif -// テーブルサーチのメイン部分。 -// m_type および ATC 作成に必要な情報を埋めてから帰る。 -// ATC は帰った先で作成する。 +// TC がセットされたので、こちらの二次変数も追従する。 void -m68030MMU::search(int level, busaddr laddr) +m68030ATC::SetTC(const m68030TC& tc) { - const char *rpname; - uint32 addr; - uint32 fc; - int dt; - bool is_super; +#if defined(M68030_CUSTOM_ATC) + for (const auto& table : alltables) { + table->SetMask(tc.lmask); + } +#else + table->SetMask(tc.lmask); +#endif +} + +// ATC をすべてフラッシュする。命令からコールされる +void +m68030ATC::FlushAll() +{ +#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) +{ +#if defined(M68030_CUSTOM_ATC) + for (uint i = 1; i <= 6; i++) { + if (((i ^ fc) & mask) == 0) { + // FC が一致したテーブルを全フラッシュ。 + if ((bool)fctables[i]) { + fctables[i]->Flush(); + } + } + } +#else + table->Flush(fc, mask); +#endif +} - addr = laddr.Addr(); - fc = laddr.GetFC(); +// ATC の fc, mask, addr が一致するエントリをフラッシュする。 +// 命令からコールされる。 +void +m68030ATC::Flush(uint32 fc, uint32 mask, uint32 addr) +{ +#if defined(M68030_CUSTOM_ATC) + for (uint i = 1; i <= 6; i++) { + if (((i ^ fc) & mask) == 0) { + // FC が一致したテーブルから addr が一致したエントリをフラッシュ。 + if ((bool)fctables[i]) { + fctables[i]->Flush(addr); + } + } + } +#else + table->Flush(fc, mask, addr); +#endif +} - is_super = (fc & 0x04); +// 統計情報をリセットする。 +void +m68030ATC::ResetStat() +{ +#if defined(M68030_CUSTOM_ATC) + for (const auto& table : alltables) { + table->ResetStat(); + } +#else + table->ResetStat(); +#endif +} - // アドレスを (IS,) TIA .. TID に分解。 +// 統計情報を更新する。 +void +m68030ATC::CalcStat() +{ +#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 +} + + +// +// テーブルサーチ操作。 +// + +// コンストラクタ +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); + } + + // デバッグ表示。 + 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; +} + +// デバッガからの呼び出し用。 +// 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); +} + +// laddr についてテーブルサーチを行い結果コード Result を返す。 +// その他の状態はメンバに保持する。 +m68030PageTableOp::Result +m68030PageTableOp::DoSearch(int maxlv, const busaddr laddr) +{ + const char *rpname; + uint dt; + + m_laddr = laddr; + + // 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] = (addr >> 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] = fc; - - cpu->putlogf(3, [&] { - std::string buf; - - buf = indent_format(2, "search: laddr=$%08x ->", addr); - if (cpu->mmu_tc.is > 0) - buf += string_format(" IS=%u", cpu->mmu_tc.is); - if (cpu->mmu_tc.fcl) - buf += string_format(" FC=%u", m_lv[FCL]); - for (int i = TIA; i <= TID; i++) { - if (cpu->mmu_tc.tix[i] > 0) { - buf += string_format(" %s=%u($%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 (__predict_true(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(); @@ -855,52 +1348,49 @@ m68030MMU::search(int level, busaddr lad m_desc2 = cpu->GetCRPl(); } - // ルートポインタの DT をチェック + // ルートポインタの DT をチェック。 dt = GET_DT(m_desc1); - MMULOG(3, 2, "%s: %s dt=%s", lam_func, rpname, dtname(dt)); + 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; - m_lastsize = 8; } else if (dt == DT_VALID8) { // 有効 (8バイト) m_descsize = 8; - m_lastsize = 8; } else if (dt == DT_PAGE) { // ページディスクリプタ - m_type = EARLY; - return; + return Result::Early; } else { // 無効は設定できないはずなので、ここには来ないはず。 - PANIC("%s.DT==Invalid", rpname); + PANIC("%s.DT=%u corrupted?", rpname, dt); } - // 必要ならファンクションコード・ルックアップ + // 必要ならファンクションコード・ルックアップ。 if (__predict_false(cpu->mmu_tc.fcl)) { - x = 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 = 1U << dt; break; } @@ -908,97 +1398,77 @@ m68030MMU::search(int level, busaddr lad // TIx のテーブルサーチに入る。図9-25 の下半分。 - for (x = TIA; x < level; x++) { - // リミットチェック - if (__predict_false(m_lastsize == 8) && limitcheck() == false) { - m_type = INVALID; - m_lim = true; - return; - } + for (lv = LV_TIA; ; lv++) { + // N はステージ開始時に足すが、 + // 超えたかどうかはこのステージが完了したループ末尾で調べる。 + m_n++; - // テーブルアドレス - 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 (__predict_false(m_descsize == 8)) { + if (CheckLimit() == false) { + m_lim = true; + return Result::Invalid; + } + } - // ディスクリプタをフェッチ - 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 = 1U << dt; - - if (__predict_false(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); \ @@ -1006,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; @@ -1021,215 +1492,195 @@ m68030MMU::fetch_desc(uint32 tableaddr, // インダイレクトなら idx = 0 でコールしてあるので、 // tableaddr がそのまま PA になるという寸法。 descaddr = tableaddr + idx * m_descsize; - MMULOG(3, 2, "%s: descsize=%u descaddr=$%08x", lam_func, + MMULOG(4, 4, "%s: descsize=%u descaddr=%08x", lam_func, m_descsize, descaddr); - // バスエラー発生フラグをクリアしておく - m_berr = false; - - // ディスクリプタを取得 - if (__predict_false(m_debugger)) { - // デバッガアクセス - uint64 data; - data = cpu->mainbus->Peek4(descaddr); - if ((int64)data < 0) { - goto buserr; - } - m_desc1 = data; - - if (m_descsize == 8) { - data = cpu->mainbus->Peek4(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 { - busdata bd; - - bd = cpu->read_phys_4(descaddr); - if (__predict_false(bd.IsBusErr())) { + bd = cpu->read_phys_4(descaddr + 4); + if (bd.IsBusErr()) { goto buserr; } - m_desc1 = bd.Data(); - - if (__predict_false(m_descsize == 8)) { - bd = cpu->read_phys_4(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; default: __unreachable(); } - // デバッガ自身がアドレス変換を実施している時は書き込みは行わない - if (__predict_false(m_debugger)) { - do_write = false; - } - - // ライトライクルの実行 + // ライトライクルの実行。 if (do_write) { - busdata bd = cpu->write_phys_4(descaddr, m_desc1); - if (__predict_false(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=%u", (uint)m_type); + m_desc2 = (uint32)data; } - // PS の部分は反映しない - a->paddr = paddr & cpu->mmu_tc.lmask; + // フラグやメンバーの更新は不要なのでこれで終わり。 + return true; +} - if (m_ci) +// 検索結果から 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 + + a->tag = table->MakeTag(m_laddr); + + if (result == Result::Invalid) { + a->paddr = {}; + a->data = m68030ATCLine::BUSERROR; + return; + } + + a->paddr = m_paddr.Addr(); + if (__predict_false(result == Result::Early)) { + // PERFORM LIMIT CHECK ?? + + a->paddr = AssembleEarlyRemains(a->paddr); + } + + a->data = 0; + if (m_ci) { a->data |= m68030ATCLine::CINHIBIT; - if (m_wp) + } + if (m_wp) { a->data |= m68030ATCLine::WPROTECT; - if (m_m) + } + if (m_m) { a->data |= m68030ATCLine::MODIFIED; + } } -// make_atc() と同様にテーブルサーチの結果から物理ページアドレスを返す。 -// バスエラーになるケースなら BusErr を返す。 -// デバッガアクセスなので、VM に一切干渉しない。 -busaddr -m68030MMU::make_atc_debugger() const +// リミットチェックを実行。図9-28。 +// 無効のケースに落ちたら false を返す。 +bool +m68030PageTableOp::CheckLimit() const { - busaddr paddr; - - switch (m_type) { - case INVALID: // 無効 - paddr = BusAddr::BusErr; - break; - - case NORMAL: // ノーマル - case INDIRECT: // 間接 - { - uint32 descaddr; - descaddr = (m_descsize == 4) ? m_desc1 : m_desc2; - descaddr &= 0xffffff00; - paddr = busaddr(descaddr); - break; - } + uint16 limit = (m_desc1 >> 16) & 0x7fff; - case EARLY: // アーリーターミネーション - { - uint32 descaddr; - descaddr = (m_descsize == 4) ? m_desc1 : m_desc2; - descaddr &= 0xffffff00; - // 残ってるビットも足して返す - for (int i = x + 1; i <= TID; i++) { - if (cpu->mmu_tc.tix[i] > 0) { - descaddr |= m_lv[i] << cpu->mmu_tc.shift[i]; - } + // 最上位ビットが 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; } - paddr = busaddr(descaddr); - break; - } - - default: - PANIC("corrupted m_type=%u", (uint)m_type); } + return true; +} - paddr |= BusAddr::TableSearched; - return paddr; +// 早期終了時の残ってるビットを addr に足したものを返す。 +uint32 +m68030PageTableOp::AssembleEarlyRemains(uint32 addr) const +{ + for (uint i = lv + 1; i <= cpu->mmu_tc.termlv; i++) { + addr |= m_idx[i] << cpu->mmu_tc.shift[i]; + } + return addr; } // ディスクリプタ表示用の文字列を返す。 // m_descsize によって m_desc1, m_desc2 を適切に文字列にする。 std::string -m68030MMU::sprintf_desc() +m68030PageTableOp::sprintf_desc() const { std::string buf; @@ -1242,288 +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?"; } - snprintf(buf, sizeof(buf), "(invalid DT %u)", 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 を返す。 -bool -MPU68030Device::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%u 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__, - bus.laddr.Addr(), - (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 -MPU68030Device::TranslateRead() +MPU68030Device::SetCRP(uint32 h, uint32 l) { - m68030ATCLine *a; - busaddr& laddr = bus.laddr; - uint32 addr = laddr.Addr(); - uint32 fc = laddr.GetFC(); - - // 状態表示 - putlog(3, "%s: enter %c:$%08x pc=%c:$%08x", __func__, - (laddr.IsSuper() ? 'S' : 'U'), - addr, - IsSuper() ? 'S' : 'U', - ppc); + if (GET_DT(h) == DT_INVALID) { + return false; + } + reg.crp.h = h & m68030RP::H_MASK; + reg.crp.l = l; + return true; +} - m68030ATCTable *table = atc.fctables[fc]; - uint8 n = table->hash[addr >> 12]; - if (__predict_true((int8)n >= 0)) { - // ATC #(n) - table->atcstat.total++; - a = &table->line[n]; - } else if (n == m68030ATCTable::HASH_NONE) { - if (__predict_true(mmu_tc.e)) { - // TC.E 有効で対応なしなら、テーブルサーチ。 - table->atcstat.total++; - table->atcstat.miss++; - a = table->MMUSearch(laddr); - } else { - // TC.E 無効。(total もカウントしない) - return true; - } - } else if (n == m68030ATCTable::HASH_TT) { - // TT。CheckTT で外れたかどうかはカウントしない。ほぼ 0 なので。 - table->atcstat.total++; - table->atcstat.tthit++; - return CheckTT(); - } else { - // PS<4KB でマッチしたので再検査。 - table->atcstat.total++; - a = table->FindSub(laddr); - if (a == NULL) { - table->atcstat.miss++; - a = table->MMUSearch(laddr); - } +void +MPU68030Device::SetTT(int n, uint32 data) +{ + // マスクしてレジスタイメージにセット + reg.tt[n] = data & m68030TT::MASK; + + // メンバと二次変数を作成。 + mmu_tt[n].Set(data); + +#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; - // ATC a でマッチした - table->MoveHead(a); - putlog(3, "%s", table->LineToStr(a).c_str()); - if (__predict_false(a->IsBusError())) { + base = (((uint64)baseh) << 32) | basel; + mask = (((uint64)maskh) << 32) | maskl; +} + +// アクセスがこの TT とマッチするか調べる。 +// laddr の上位ワードはそのまま使える。 +// 下位32ビットが論理アドレス (下24ビットは呼び出し側でマスクしなくてよい)。 +// この TT が有効でアドレスがマッチすれば true を返す。 +// (TT:E は演算に織り込んである、このすぐ上の SetTT() のコメント参照) +bool +m68030TT::Match(busaddr laddr) const +{ + bool rv = (((laddr.Get() ^ base) & mask) == 0); + return rv; +} + +// 成功すれば 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; } - bus.paddr.ChangeAddr(a->GetPAddr() | (addr & mmu_tc.pgmask)); - bus.ci = a->IsCInhibit(); + + // ATC にも通知。 + atc.SetTC(mmu_tc); + +#if defined(M68030_CUSTOM_ATC) + // ハッシュも全部作り直す。 + atc.Rehash(); +#endif + + // TC:E が変更されていればスイッチ更新 + mmu_enable_changed(); return true; } -// ライト用のアドレス変換を行う。 -// 変換できれば true、バスエラーなら false を返す。 +// TC レジスタへの書き込み値をこのクラスにセットして二次変数も再構成する。 +// E をオンにした場合は構成チェックを行い、問題なければ true を返す。 +// 問題があった場合は E をオフにした上で false を返す。 +// E をオフにした場合はチェックは行わず true を返す。 bool -MPU68030Device::TranslateWrite() +m68030TC::Set(uint32 data) { - m68030ATCLine *a; - busaddr& laddr = bus.laddr; - uint32 addr = laddr.Addr(); - uint32 fc = laddr.GetFC(); - - // 状態表示 - putlog(3, "%s: enter %c:$%08x pc=%c:$%08x", __func__, - (laddr.IsSuper() ? 'S' : 'U'), - addr, - IsSuper() ? 'S' : 'U', - ppc); + // 一次メンバにセット。 + 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; - m68030ATCTable *table = atc.fctables[fc]; - uint8 n = table->hash[addr >> 12]; - if (__predict_true((int8)n >= 0)) { - // ATC #(n) - table->atcstat.total++; - a = &table->line[n]; - } else if (n == m68030ATCTable::HASH_NONE) { - if (__predict_true(mmu_tc.e)) { - // TC.E 有効で対応なしなら、テーブルサーチ。 - table->atcstat.total++; - goto tablesearch; - } else { - // TC.E 無効。(total もカウントしない) - return true; - } - } else if (n == m68030ATCTable::HASH_TT) { - // TT。CheckTT で外れたかどうかはカウントしない。ほぼ 0 なので。 - table->atcstat.total++; - table->atcstat.tthit++; - return CheckTT(); - } else { - // PS<4KB でマッチしたので再検査。 - table->atcstat.total++; - a = table->FindSub(laddr); - if (a == NULL) { - goto tablesearch; - } + config_ok = Check(); + + // 構成が有効なら E に関わらず二次変数を用意しておく。 + if (config_ok) { + MakeMask(); } - // ライトアクセスなので、WP でなく Modified == 0 なら - // Modified を立てるため、このエントリを破棄して改めて - // テーブルサーチを実行する。 - if (a->IsWProtect() == false && a->IsModified() == false) { - table->Invalidate(a); - tablesearch: - table->atcstat.miss++; - a = table->MMUSearch(laddr); + // E がオンで構成例外になる場合はオフにする。 + if (e && config_ok == false) { + e = false; + return false; } - // ATC a でマッチした - table->MoveHead(a); + return true; +} - if (__predict_false(a->IsBusError())) { +// MMU 構成例外になるケースなら false を返す。 +bool +m68030TC::Check() const +{ + if (ps < 8) { return false; } - if (__predict_false(a->IsWProtect())) { - // Write アクセスで WP が立ってたらバスエラー - putlog(3, "%s: result Buserr (ATC:WP)", __func__); + 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; } - - putlog(3, "%s", table->LineToStr(a).c_str()); - bus.paddr.ChangeAddr(a->GetPAddr() | (addr & mmu_tc.pgmask)); - bus.ci = a->IsCInhibit(); return true; } -// ピーク用のアドレス変換を行う。 -// 変換できれば対応する物理アドレスを、バスエラーなら BusErr を返す。 -// テーブルサーチを行ったら TableSearched ビットを立てる。 -busaddr -MPU68030Device::TranslatePeek(busaddr laddr) +// 二次変数を作成。(E には影響されない) +void +m68030TC::MakeMask() { - const m68030ATCLine *a; - busaddr paddr; - uint32 addr = laddr.Addr(); - uint32 fc = laddr.GetFC(); - - // 状態表示 - putlog(5, "%s: enter %c:$%08x", __func__, - (laddr.IsSuper() ? 'S' : 'U'), - addr); + // 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; - m68030ATCTable *table = atc.fctables[fc]; - uint8 n = table->hash[addr >> 12]; - // PS<4KB のケースを先に解決する。 - if (__predict_false(n == m68030ATCTable::HASH_SUB)) { - a = table->FindSub(laddr); - if (a) { - n = a - &table->line[0]; - } else { - n = m68030ATCTable::HASH_NONE; + // アドレスを (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]; } } - if (__predict_true((int8)n >= 0)) { - // ATC #(n) - a = &table->line[n]; - paddr = busaddr(a->GetPAddr()); - paddr |= addr & mmu_tc.pgmask; - putlog(5, "%s: result $%08x (match atc)", __func__, paddr.Addr()); - return paddr; - } else if (n == m68030ATCTable::HASH_NONE) { - if (__predict_true(mmu_tc.e)) { - // TC.E 有効で対応なしなら、こっそりテーブルサーチ。 - // mmu.search() はさすがに bus を使うので事前に退避。 - m68kbus backup = bus; - - m68030MMU mmu(this, loglevel); - mmu.m_debugger = true; - mmu.search(7, laddr); - paddr = mmu.make_atc_debugger(); - // リストア - bus = backup; - - if (paddr.IsBusErr()) { - // TC が有効だがヒットしなかったらバスエラー - putlog(5, "%s: result BusErr", __func__); - } else { - paddr |= addr & mmu_tc.pgmask; - putlog(5, "%s: result $%08x (table search)", - __func__, paddr.Addr()); - } - return paddr; - } else { - // TC.E 無効 - paddr = laddr; - putlog(5, "%s: result $%08x (no translation)", - __func__, paddr.Addr()); - return paddr; - } - } else { - // TT をこっそりチェック。 - for (int i = 0; i < 2; i++) { - m68030TT& tt = mmu_tt[i]; - if (tt.Match(laddr)) { - // 一致した。 - // CI はここでは使わないため、一致したことだけわかればよい - putlog(5, "%s: result $%08x (match tt)", - __func__, paddr.Addr()); - return paddr; - } + // 最終段を求めておく。 + termlv = LV_TID; + for (int i = LV_TIA; i <= LV_TID; i++) { + if (tix[i] == 0) { + termlv = i - 1; + break; } - return BusAddr::BusErr; } } +void +MPU68030Device::SetMMUSR(uint16 data) +{ + reg.mmusr = data & m68030MMUSR::MASK; +} + + // // 命令 // @@ -1552,49 +2045,52 @@ MPU68030Device::get_fc() // MMU 不当命令 (2ワード目で確定) void -MPU68030Device::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 -MPU68030Device::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 -MPU68030Device::mmu_op_pflush_ea() +MPU68030Device::ops_mmu30_pflush_ea() { - uint32 ea; - uint32 mask; - uint32 fc; - - ea = cea_copro(); + uint32 mask = (ir2 >> 5) & 7; + uint32 fc = get_fc(); + uint32 ea = cea_copro(); ea &= mmu_tc.lmask; - mask = (ir2 >> 5) & 7; - fc = get_fc(); - atc.flush(fc, mask, ea); + + atc.Flush(fc, mask, ea); } // PLOADR/PLOADW 命令 void -MPU68030Device::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(); @@ -1608,16 +2104,16 @@ MPU68030Device::mmu_op_pload() laddr = busaddr(ea) | busaddr::FC(fc) | BusAddr::W; } - m68030ATCTable *table = atc.fctables[fc]; - m68030ATCLine *a; +#if defined(M68030_CUSTOM_ATC) + auto table = atc.GetTable(fc); uint8 n = table->hash[laddr.Addr() >> 12]; // PS<4KB のケースを先に解決する。 - if (__predict_false(n == m68030ATCTable::HASH_SUB)) { + if (__predict_false(n == m68030ATCTable_Custom::HASH_SUB)) { a = table->FindSub(laddr); if (a) { n = a - &table->line[0]; } else { - n = m68030ATCTable::HASH_NONE; + n = m68030ATCTable_Custom::HASH_NONE; } } if ((int8)n >= 0) { @@ -1628,139 +2124,192 @@ MPU68030Device::mmu_op_pload() a = &table->line[n]; if (a->IsWProtect() == false && a->IsModified() == false) { table->Invalidate(a); - table->MMUSearch(laddr); + PageTableSearch(laddr, a); } } - } else if (n == m68030ATCTable::HASH_NONE) { + } else if (n == m68030ATCTable_Custom::HASH_NONE) { if (__predict_true(mmu_tc.e)) { // TC.E 有効で対応なしなら、テーブルサーチを行って // ATC を作成。 - table->MMUSearch(laddr); + a = table->Pop(); + PageTableSearch(laddr, a); } else { // TC.E 無効なら何もしない? } - } else if (n == m68030ATCTable::HASH_TT) { + } 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 -MPU68030Device::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 ならテーブルサーチを実行。 - // laddr は IS と PS のところを落としたもの。 - // ssw_laddr のアドレスは IS を落とさないもの (PSは不問)。 - busaddr laddr = busaddr(ea) | busaddr::FC(fc) | rw; - uint32 addr = ea & mmu_tc.lmask; - - m68030ATCTable *table = atc.fctables[fc]; - m68030ATCLine *a = NULL; - uint8 n = table->hash[addr >> 12]; - // PS<4KB のケースを先に解決する。 - if (__predict_false(n == m68030ATCTable::HASH_SUB)) { - a = table->FindSub(laddr); - if (a) { - n = a - &table->line[0]; - } else { - n = m68030ATCTable::HASH_NONE; - } - } - if (__predict_true((int8)n >= 0)) { - // ATC #(n) で見付かった。 - a = &table->line[n]; - } else if (n == m68030ATCTable::HASH_NONE) { - // ATC に見付からなかった。Invalid を立てる。 - mmusr = m68030MMUSR::I; - } else if (n == m68030ATCTable::HASH_TT) { - for (int i = 0; i < 2; i++) { - m68030TT& tt = mmu_tt[i]; - if (tt.Match(laddr)) { - mmusr |= m68030MMUSR::T; - } - } + // A が 0 なら ARegNo も 0 でなければならない、とは書いてあるが + // どうなるかは書いていない。とりあえず例外にしておく。 + if (__predict_false(1 <= rn && rn < 8)) { + ops_mmu30_illg2(); + return; } + CYCLE(88); + mmusr = ptest_search(level, laddr, rn); + } + 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) { - // 見付かれば 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 { + 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 ならテーブルサーチを実行。 - CYCLE(88); - - m68030MMU mmu(this, loglevel); - mmu.m_ptest = true; - mmu.search(level, busaddr(ea) | busaddr::FC(fc) | rw); - - if (mmu.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; - } - if (mmu.m_lim) { - // XXX not tested - mmusr |= m68030MMUSR::L; - } - if (mmu.m_s && (fc & 4)) { - // XXX not tested - mmusr |= m68030MMUSR::S; - } - if (mmu.m_wp) { + } else if (a->IsWProtect()) { mmusr |= m68030MMUSR::W; - } - if (mmu.m_type == INVALID) { - mmusr |= m68030MMUSR::I; - } - if (mmu.m_m) { + } else if (a->IsModified()) { mmusr |= m68030MMUSR::M; } - // N を求める。 - // もともとそういう風には出来ていないので逆算している。 - int n; - if (mmu_tc.fcl) { - // たぶんファンクションテーブルで1段消費する? (未確認) - if (mmu.x == FCL) { - n = 1; - } else { - n = mmu.x + 2; - } - } else { - n = mmu.x + 1; - } - mmusr |= n; + } else { + // 見付からなかったら Invalid を立てる。 + mmusr |= m68030MMUSR::I; + } + return mmusr; +} - // アドレスレジスタ指定があれば、最後のディスクリプタアドレスを返す。 - // rn は有効なら 8-15。 - if (rn != 0) { - // XXX 途中でバスエラーが起きるケースは未実装 - if (mmu.m_berr == false) { - reg.R[rn] = mmu.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; }