--- nono/m88xx0/m88200.cpp 2026/04/29 17:04:38 1.1.1.2 +++ nono/m88xx0/m88200.cpp 2026/04/29 17:05:07 1.1.1.9 @@ -8,22 +8,38 @@ #include "m88100.h" #include "m88200.h" -#include "mystring.h" + +// static 変数 +m88200 *m88200::mbus_master = NULL; // m88200 コンストラクタ +// +// ここのオブジェクト名は仮で、ID が確定した時点でもう一度セットする +// (see SetID()) m88200::m88200() + : inherited("CMMU") { - // logname, devname は Init() で確定する + // objname 等は SetID() で確定する - monitor_size = nnSize(48, 8); + reg_monitor.func = ToMonitorCallback(&m88200::MonitorUpdateReg); + reg_monitor.SetSize(52, 8); + atc_monitor.func = ToMonitorCallback(&m88200::MonitorUpdateATC); +#if defined(M88200_STAT) + atc_monitor.SetSize(77, 39 + 15); +#else + atc_monitor.SetSize(77, 39); +#endif + cache_monitor.func = ToMonitorCallback(&m88200::MonitorUpdateCache); + cache_monitor.SetSize(82, 23); + // モニタ名も SetID() で決まるので、続きは向こうでやる // ログ表示用の名前 sapr.name = "SAPR"; uapr.name = "UAPR"; // 暗黙 BATC を初期化 - SetBATC(8, 0xfff7ffb5); // $fff8xxxx,S,WT,CI,V - SetBATC(9, 0xfffffff5); // $ffffxxxx,S,WT,CI,V + SetBWP(8, 0xfff7ffb5); // $fff8xxxx,S,WT,CI,V + SetBWP(9, 0xfffffff5); // $ffffxxxx,S,WT,CI,V // セットインデックスを初期化 for (int i = 0; i < setarray.size(); i++) { @@ -48,55 +64,62 @@ m88200::Ctor(m88kcpu *parent_) void m88200::Reset() { + const uint32 Undefined = 0xcccccccc; + // レジスタ (p6-4, Table6-3) + // ID, version は別途 Set される。 command = 0; ssr = 0; - sar = 0xdeadbeef; // undefined + sar = Undefined; sctr = 0; fault_code = 0; - fault_addr = 0xdeadbeef; // undefined + fault_addr = Undefined; sapr.enable = false; sapr.stat = APR_CI; uapr.enable = false; uapr.stat = APR_CI; } +// モニター更新 (CMMU レジスタ) void -m88200::MonitorUpdate(TextScreen& monitor) +m88200::MonitorUpdateReg(Monitor *, TextScreen& screen) { + uint32 baseaddr; uint32 reg; int x; int y; - monitor.Clear(); - x = 20; + baseaddr = 0xfff00000 + (id << 12); + + screen.Clear(); + x = 24; y = 0; - monitor.Print(0, y++, "+$000 IDR:%08x ID=$%02x Type=5(88200) Ver=$%x", - GetIDR(), id, version); + screen.Print(0, y++, "$%08x IDR:%08x ID=$%02x Type=5(88200) Ver=$%x", + baseaddr + 0, GetIDR(), id, version); reg = GetSSR(); - monitor.Print(0, y, "+$008 SSR:%08x", reg); - monitor.Puts(x, y, TA::OnOff(reg & SSR_CE), "CE"); - monitor.Puts(x + 3, y, TA::OnOff(reg & SSR_BE), "BE"); - monitor.Puts(x + 6, y, TA::OnOff(reg & SSR_WT), "WT"); - monitor.Puts(x + 9, y, TA::OnOff(reg & SSR_SP), "SP"); - monitor.Puts(x + 12, y, TA::OnOff(reg & SSR_G), "G"); - monitor.Puts(x + 14, y, TA::OnOff(reg & SSR_CI), "CI"); - monitor.Puts(x + 17, y, TA::OnOff(reg & SSR_M), "M"); - monitor.Puts(x + 19, y, TA::OnOff(reg & SSR_U), "U"); - monitor.Puts(x + 21, y, TA::OnOff(reg & SSR_WP), "WP"); - monitor.Puts(x + 24, y, TA::OnOff(reg & SSR_BH), "BH"); - monitor.Puts(x + 27, y, TA::OnOff(reg & SSR_V), "V"); + screen.Print(0, y, "$%08x SSR:%08x", baseaddr + 0x008, reg); + screen.Puts(x, y, TA::OnOff(reg & SSR_CE), "CE"); + screen.Puts(x + 3, y, TA::OnOff(reg & SSR_BE), "BE"); + screen.Puts(x + 6, y, TA::OnOff(reg & SSR_WT), "WT"); + screen.Puts(x + 9, y, TA::OnOff(reg & SSR_SP), "SP"); + screen.Puts(x + 12, y, TA::OnOff(reg & SSR_G), "G"); + screen.Puts(x + 14, y, TA::OnOff(reg & SSR_CI), "CI"); + screen.Puts(x + 17, y, TA::OnOff(reg & SSR_M), "M"); + screen.Puts(x + 19, y, TA::OnOff(reg & SSR_U), "U"); + screen.Puts(x + 21, y, TA::OnOff(reg & SSR_WP), "WP"); + screen.Puts(x + 24, y, TA::OnOff(reg & SSR_BH), "BH"); + screen.Puts(x + 27, y, TA::OnOff(reg & SSR_V), "V"); y++; - monitor.Print(0, y++, "+$00c SAR:%08x", GetSAR()); + screen.Print(0, y++, "$%08x SAR:%08x", baseaddr + 0x00c, GetSAR()); reg = GetSCTR(); - monitor.Print(0, y, "+$104 SCTR:%08x", GetSCTR()); - monitor.Puts(x, y, TA::OnOff(reg & SCTR_PE), "PE"); - monitor.Puts(x + 3, y, TA::OnOff(reg & SCTR_SE), "SE"); - monitor.Puts(x + 6, y, TA::OnOff(reg & SCTR_PR), "PR"); + screen.Print(0, y, "$%08x SCTR:%08x", baseaddr + 0x104, GetSCTR()); + screen.Puts(x, y, TA::OnOff(reg & SCTR_PE), "PE"); + screen.Puts(x + 3, y, TA::OnOff(reg & SCTR_SE), "SE"); + screen.Puts(x + 6, y, TA::OnOff(reg & SCTR_PR), "PR"); y++; reg = GetPFSR(); @@ -110,15 +133,17 @@ m88200::MonitorUpdate(TextScreen& monito "Supervisor Violation", "Write Violation", }; - monitor.Print(0, y++, "+$108 PFSR:%08x %s", reg, codestr[fault_code]); + screen.Print(0, y++, "$%08x PFSR:%08x %s", baseaddr + 0x108, + reg, codestr[fault_code]); - monitor.Print(0, y++, "+$10c PFAR:%08x", GetPFAR()); + screen.Print(0, y++, "$%08x PFAR:%08x", baseaddr + 0x10c, GetPFAR()); for (int i = 0; i < 2; i++) { int s = 1 - i; reg = GetAPR(s); - monitor.Print(0, y, "+$20%d %cAPR:%08x", i * 4, s ? 'S' : 'U', reg); - monitor.Print(x, y, "STBA=%05x'000 %c%c%c%c", + screen.Print(0, y, "$%08x %cAPR:%08x", baseaddr + 0x200 + i * 4, + s ? 'S' : 'U', reg); + screen.Print(x, y, "STBA=%05x'000 %c%c%c%c", reg >> 12, (reg & APR_WT) ? 'T' : '-', (reg & APR_G) ? 'G' : '-', @@ -128,12 +153,195 @@ m88200::MonitorUpdate(TextScreen& monito } } +// モニター更新 (ATC) +void +m88200::MonitorUpdateATC(Monitor *, TextScreen& screen) +{ + int x; + int y; + + screen.Clear(); + + screen.Print(0, 0, "SAPR %05x'000 TE=%d %c%c%c", + (sapr.addr >> 12) & 0xfffff, + sapr.enable ? 1 : 0, + (sapr.stat & APR_WT) ? 'T' : '-', + (sapr.stat & APR_G) ? 'G' : '-', + (sapr.stat & APR_CI) ? 'C' : '-'); + + screen.Print(40, 0, "UAPR %05x'000 TE=%d %c%c%c", + (uapr.addr >> 12) & 0xfffff, + uapr.enable ? 1 : 0, + (uapr.stat & APR_WT) ? 'T' : '-', + (uapr.stat & APR_G) ? 'G' : '-', + (uapr.stat & APR_CI) ? 'C' : '-'); + + screen.Puts(0, 1, ""); + screen.Puts(0, 2, "No. LBA PBA Stat Hit%"); + screen.Puts(40, 2, "No. LBA PBA Stat Hit%"); + x = 0; + y = 3; + uint64 batc_total = 0; + for (int i = 0; i < 10; i++) { + if (i == batc.size() / 2) { + x = 40; + y = 3; + } + if (i < 8) { + screen.Print(x, y, " %d:", i); + } else { + screen.Print(x, y, "(%d)", i); + } + + const m88200BATC& b = batc[i]; + if (b.IsValid()) { + screen.Print(x + 4, y, "%c:%04x'0000 %04x'0000 %c%c%c%c", + b.IsS() ? 'S' : 'U', + b.lba >> 16, b.pba >> 16, + (b.stat & DESC_WT) ? 'T' : '-', + (b.stat & DESC_G) ? 'G' : '-', + (b.stat & DESC_CI) ? 'C' : '-', + (b.stat & DESC_WP) ? 'P' : '-'); + } + if (batc_hit[i] == 0) { + screen.Puts(x + 32, y, "--.-%"); + } else { + screen.Print(x + 32, y, "%4.1f%%", + (double)batc_hit[i] / translate_total * 100); + batc_total += batc_hit[i]; + } + y++; + } + + screen.Puts(0, 8, ""); + screen.Puts(0, 9, "No. LPA PFA Stat"); + screen.Puts(40, 9, "No. LPA PFA Stat"); + + x = 0; + y = 10; + for (int i = 0; i < patc.size(); i++) { + if (i == patc.size() / 2) { + x = 40; + y = 10; + } + screen.Print(x, y, "%2d:", i); + + const m88200PATC& p = patc[i]; + if (p.IsValid()) { + screen.Print(x + 4, y, "%c:%05x'000 %05x'000 %c%c%c%c%c", + p.IsS() ? 'S' : 'U', + p.lpa >> 12, p.pfa >> 12, + (p.stat & DESC_WT) ? 'T' : '-', + (p.stat & DESC_G) ? 'G' : '-', + (p.stat & DESC_CI) ? 'C' : '-', + (p.stat & DESC_WP) ? 'P' : '-', + p.m ? 'M' : '-'); + } + y++; + } + + if (__predict_false(translate_total == 0)) { + screen.Puts(40, y, "BATC --.--%, PATC --.--%, Miss --.--%"); + } else { + screen.Print(40, y, "BATC%6.2f%%, PATC%6.2f%%, Miss%6.2f%%", + (double)batc_total / translate_total * 100, + (double)patc_hit / translate_total * 100, + (double)atc_miss / translate_total * 100); + } + +#if defined(M88200_STAT) + y++; + double v; + screen.Puts(0, y++, ""); + screen.Print(0, y++, "PATC hash search %26s", + format_number(stat_patc_search).c_str()); + // ヒット率 + screen.Print(0, y, "PATC hash hit %26s (hit/search ", + format_number(stat_patc_hit).c_str()); + if (stat_patc_search == 0) { + screen.Puts(60, y, "---.-%)"); + } else { + screen.Print(60, y, "%5.1f%%)", + (double)stat_patc_hit / stat_patc_search * 100); + } + y++; + // ミスのうち1回目のハッシュ比較で外れが確定した率 + screen.Print(0, y, "PATC hash miss 1 %26s (miss1/miss ", + format_number(stat_patc_miss1).c_str()); + v = stat_patc_miss1 + stat_patc_miss2; + if (v == 0) { + screen.Puts(60, y, "---.-%)"); + } else { + screen.Print(60, y, "%5.1f%%)", (double)stat_patc_miss1 / v * 100); + } + y++; + screen.Print(0, y++, "PATC hash miss 2 %26s", + format_number(stat_patc_miss2).c_str()); + screen.Print(0, y++, "PATC create %26s", + format_number(stat_patc_create).c_str()); + screen.Print(0, y++, "PATC invalidate %26s", + format_number(stat_patc_invalidate).c_str()); + screen.Print(0, y++, "PATC SCR InvAll %26s", + format_number(stat_patc_invcmd_all).c_str()); + screen.Print(0, y++, "PATC SCR InvSegment %26s", + format_number(stat_patc_invcmd_seg).c_str()); + screen.Print(0, y++, "PATC SCR InvPage %26s", + format_number(stat_patc_invcmd_page).c_str()); + + screen.Puts(0, y++, "Hash 0 1 2 3 4 5 6 >=7"); + + // BATC ハッシュの衝突状況は BATC 更新時に計算してある + // (BATC の更新頻度は低いため) + screen.Puts(0, y++, "BATC HASH.S"); + for (auto n : stat_batc_hash_s) { + screen.Print(" %3d", n); + } + screen.Puts(0, y++, "BATC HASH.U"); + for (auto n : stat_batc_hash_u) { + screen.Print(" %3d", n); + } + // PATC ハッシュの衝突状況は、表示時に都度計算する + // (PATC ハッシュの更新頻度はおそらく表示周期より速いため) + auto print_phash = [&](const auto& hash) { + std::array st {}; + for (const auto hashmap : hash) { + int n = __builtin_popcount(hashmap); + if (n >= st.size()) { + n = st.size() - 1; + } + st[n]++; + } + for (auto n : st) { + screen.Print(" %3d", n); + } + }; + screen.Puts(0, y++, "PATC HASH.S"); + print_phash(patc_hash_s); + screen.Puts(0, y++, "PATC HASH.U"); + print_phash(patc_hash_u); +#endif +} + +// モニター更新 (キャッシュ、概要と詳細の両方、GUI から呼ばれる) +// screen.userdata は注目しているセット番号。 +void +m88200::MonitorUpdateCache(Monitor *, TextScreen& screen) +{ + int setidx = screen.userdata; + + // 上半分(概要) + MonitorCacheOverview(screen, 0, setidx, true); + // 下半分(セット詳細) + MonitorCacheSet(screen, 18, setidx); +} + // データキャッシュの特定セットの詳細を TextScreen に出力する。 // y は開始オフセット。 // TextScreen は (70, 5) 必要。 void m88200::MonitorCacheSet(TextScreen& s, int y, int setidx) { + assertmsg(0 <= setidx && setidx < setarray.size(), "setidx=%d", setidx); const m88200CacheSet& set = setarray[setidx]; /* @@ -170,7 +378,7 @@ m88200::MonitorCacheSet(TextScreen& s, i break; } s.Print(0, y, attr, "%d", line); - static const char * const statusstr[] = { "EU", "EM", "SU", "IV" }; + static const char statusstr[][4] = { "EU", "EM", "SU", "IV" }; s.Print(2, y, attr, "$%05x'%03x %s", (set.tag[line] >> 12), (setidx << 4), @@ -236,7 +444,7 @@ m88200::MonitorCacheOverview(TextScreen& for (int i = 0; i < setarray.size(); i++) { const auto& set = setarray[i]; - const char * const str = "EMS-"; + const char str[] = "EMS-"; int col = i % 16; int row = i / 16; int x; @@ -261,8 +469,13 @@ m88200::SetID(uint id_) { id = id_; - logname = string_format("cmmu%u", id); - devname = string_format("CMMU#%u", id); + SetName(string_format("CMMU#%u", id)); + ClearAlias(); + AddAlias(string_format("CMMU%u", id)); + + reg_monitor.Regist(ID_MONITOR_CMMU(id)); + atc_monitor.Regist(ID_MONITOR_ATC(id)); + cache_monitor.Regist(ID_SUBWIN_CACHE(id)); } // IDR の Version フィールドを設定する @@ -425,19 +638,45 @@ m88200::FlushCache() switch (gg) { case GG_LINE: addr &= 0xfffffffc; + parent->AddCycle(1); // Table.6-1 break; case GG_PAGE: addr &= 0xfffff000; + parent->AddCycle(256); // Table.6-1 break; case GG_SEG: addr &= 0xffc00000; + parent->AddCycle(1024); // Table.6-1 break; case GG_ALL: + parent->AddCycle(1024); // Table.6-1 break; default: __unreachable(); } + // サイクル(基本部分) Table.6-1 + uint32 cycle = 0; + if (invalidate) { + switch (gg) { + case GG_LINE: cycle = 1; break; + case GG_PAGE: cycle = 256; break; + case GG_SEG: cycle = 1024; break; + case GG_ALL: cycle = 256; break; + default: __unreachable(); + } + } + if (copyback) { + switch (gg) { + case GG_LINE: cycle = 1; break; + case GG_PAGE: cycle = 256; break; + case GG_SEG: cycle = 1024; break; + case GG_ALL: cycle = 1024; break; + default: __unreachable(); + } + } + // XXX ループ中のサイクル数は正しくないかも。よく分からん + for (auto& set : setarray) { for (int line = 0; line < 4; line++) { // 条件にマッチするか @@ -464,16 +703,20 @@ m88200::FlushCache() // Copyback ならまず EM なエントリを書き戻す。 if (copyback) { if (set.vv[line] == m88200CacheSet::EM) { - set.CopyBackLine(line); + cycle += 7; // Table.6-1 + CopyBackLine(set, line); } } // Invalidate なら無効化する if (invalidate) { + cycle += 1; // Table.6-1 set.Update(line, m88200CacheSet::IV); } } } + + parent->AddCycle(cycle); } // command に応じて PATC を無効化する。SetSCR() の下請け。 @@ -483,41 +726,75 @@ void m88200::InvalidatePATC() { uint32 gg = command & 0x03; - uint32 addr = GetSAR(); + bool s = (command & 0x04); + uint32 addr; + uint32 mask; - // ゲスト側が指定した端数部分は有効部分でマスクする。 - // 内部では下位ビットはフラグ扱いなのでマスクしないと誤動作する。 - if (gg == GG_SEG) { - addr &= 0xffc00000; - } else { - addr &= 0xfffff000; - } + if (__predict_true(gg == GG_PAGE)) { + // 指定の1本だけ無効にする。 + // Page は呼び出し回数が多いのと1本だけならハッシュで引けるので別対応。 +#if defined(M88200_STAT) + stat_patc_invcmd_page++; +#endif - if ((command & 0x04)) { - addr |= PATC_S; - } + // ゲスト側が指定した端数部分は有効部分でマスクする。 + // 内部では下位ビットはフラグ扱いなのでマスクしないと誤動作する。 + addr = GetSAR() & 0xfffff000; + addr |= (s) ? PATC_S : 0; - for (auto& p : patc) { - bool match; - switch (command & 0x03) { - case GG_LINE: - match = false; // XXX 書いてないけどどうなる? - break; - case GG_PAGE: - match = (addr == p.lpa); - break; - case GG_SEG: - match = (addr == (p.lpa & 0xffc00fff)); - break; - case GG_ALL: - match = true; - break; - default: - __unreachable(); + auto& hash = (s) ? patc_hash_s : patc_hash_u; + uint64 pi = hash[patc_hash_func(addr)]; + + for (; pi; pi &= pi - 1) { + int i = __builtin_ctzll(pi); + m88200PATC& p = patc[i]; + if (p.lpa == addr) { + InvalidatePATCHash(i); + p.lpa |= PATC_INVALID; +#if defined(M88200_STAT) + stat_patc_invalidate++; +#endif + // 1本ヒットすればこれ以上一致することはないはず + break; + } } - if (match) { - // 無効化 - p.lpa |= 0x01; + } else { + // 指定範囲を無効にする。All と Segment はマスクが違うだけ。 + + if (gg == GG_ALL) { + // S/U 指定したほう全体を無効にする +#if defined(M88200_STAT) + stat_patc_invcmd_all++; +#endif + mask = 0; + } else if (gg == GG_SEG) { + // S/U 指定したほうの指定セグメント範囲全部を無効にする +#if defined(M88200_STAT) + stat_patc_invcmd_seg++; +#endif + mask = 0xffc00000; + } else { + // GG_LINE はマニュアルにどうなるか書いてない + putlog(0, "未定義 Invalidate PATC Line"); + return; + } + + addr = GetSAR() & mask; + addr |= (s) ? PATC_S : 0; + + mask |= PATC_S | PATC_INVALID; + + for (int i = 0; i < patc.size(); i++) { + m88200PATC& p = patc[i]; + if ((p.lpa & mask) == addr) { + // 無効化 + // XXX 削除した結果穴が空いても詰める処理は未実装 + InvalidatePATCHash(i); + p.lpa |= PATC_INVALID; +#if defined(M88200_STAT) + stat_patc_invalidate++; +#endif + } } } } @@ -528,11 +805,23 @@ m88200::SetSCTR(uint32 data) { sctr = data & (SCTR_PE | SCTR_SE | SCTR_PR); + // 全 CMMU について、それぞれスヌープ相手になる CMMU リストを更新。 + // どの CMMU の SCTR への書き込みでも毎回全ての CMMU を書き換える。 + int n = parent->cmmu.size(); + for (int i = 0; i < n; i++) { + parent->cmmu[i].other_cmmu.clear(); + for (int j = 0; j < n; j++) { + if (i == j) + continue; + if ((parent->cmmu[j].sctr & SCTR_SE)) { + parent->cmmu[i].other_cmmu.push_back(&parent->cmmu[j]); + } + } + } + std::string msg; if ((sctr & SCTR_PE)) msg += ",PE"; - if ((sctr & SCTR_SE)) - msg += ",SE"; if ((sctr & SCTR_PR)) msg += ",PR"; if (msg.length() > 0) { @@ -565,17 +854,6 @@ m88200::GetAPR(const m88200APR& xapr) co return data; } -// SAPR, UAPR レジスタへの書き込み (S/U ビット指定) -void -m88200::SetAPR(uint issuper, uint32 data) -{ - if (issuper) { - return SetAPR(sapr, data); - } else { - return SetAPR(uapr, data); - } -} - // SAPR, UAPR レジスタへの書き込み (実体指定) void m88200::SetAPR(m88200APR& xapr, uint32 data) @@ -597,61 +875,88 @@ m88200::SetAPR(m88200APR& xapr, uint32 d } } -// BATC(BWP) #n の読み出し -uint32 -m88200::GetBATC(uint n) const -{ - uint32 data; - - assert(n < 8); - const m88200BATC& b = batc[n]; - - // 9 8 7 6 5 4 3 2 1 0 - // BATC は S WT G CI WP V - // stat は WT SP G CI 0 M U WP 0 V - - data = b.lba & (BATC_LBA_MASK | BATC_S); - data |= (b.pba >> 13); - if ((b.stat & DESC_WT)) - data |= BATC_WT; - if ((b.stat & DESC_G)) - data |= BATC_G; - if ((b.stat & DESC_CI)) - data |= BATC_CI; - if (b.wp) - data |= BATC_WP; - if ((b.lba & 1) == 0) - data |= BATC_V; - return data; -} - -// BATC (BWP) #n への書き込み +// BWP(BATC Write Port) #n への書き込み // (暗黙 BATC にも書き込めるよう n = 8,9 も受け付ける) void -m88200::SetBATC(uint n, uint32 data) +m88200::SetBWP(uint n, uint32 data) { assert(n < 10); m88200BATC& b = batc[n]; putlog(1, "BWP%u <- $%08x", n, data); + // BWP レジスタへの書き込み値と内部データ構造(stat)ではビット位置が違う + // ことに注意。 + // + // 31 10 9 8 7 6 5 4 3 2 1 0 + // +----..-----+----+----+----+----+----+----+----+----+----+----+ + // BWP |LBA PBA | S | WT | G | CI | WP | V | + // +----..-----+----+----+----+----+----+----+----+----+----+----+ + // + // 31 10 9 8 7 6 5 4 3 2 1 0 + // +----..-----+----+----+----+----+----+----+----+----+----+----+ + // stat | 0 0 | WT | SP | G | CI | 0 | M | U | WP | 0 | V | + // +----..-----+----+----+----+----+----+----+----+----+----+----+ + memset(&b, 0, sizeof(b)); - b.lba = data & BATC_LBA_MASK; - b.lba |= (data & BATC_S); - if ((data & BATC_V) == 0) { - b.lba |= 1; - } - b.pba = (data & BATC_PBA_MASK) << 13; - // BATC と stat はビット順が違うので注意。すぐ上の GetBATC() 参照。 - if ((data & BATC_WT)) - b.stat = DESC_WT; - if ((data & BATC_G)) - b.stat = DESC_G; - if ((data & BATC_CI)) - b.stat = DESC_CI; - if ((data & BATC_WP)) { - b.stat = DESC_WP; + b.lba = data & BWP_LBA_MASK; + b.lba |= (data & BWP_S); + if ((data & BWP_V) == 0) { + b.lba |= BATC_INVALID; + } + b.pba = (data & BWP_PBA_MASK) << 13; + if ((data & BWP_WT)) + b.stat |= DESC_WT; + if ((data & BWP_G)) + b.stat |= DESC_G; + if ((data & BWP_CI)) + b.stat |= DESC_CI; + if ((data & BWP_WP)) { + b.stat |= DESC_WP; b.wp = true; } + + MakeBATCHash(); +} + +// BATC ハッシュを作り直す。 +void +m88200::MakeBATCHash() +{ + std::fill(batc_hash_s.begin(), batc_hash_s.end(), 0); + std::fill(batc_hash_u.begin(), batc_hash_u.end(), 0); + + for (int i = 0; i < batc.size(); i++) { + m88200BATC& b = batc[i]; + + if (b.IsValid()) { + // LBA の下位 8 ビットだけでハッシュを作る + uint32 hashkey = batc_hash_func(b.lba); + + // S によって該当するハッシュの該当するビットを立てておく + if (b.IsS()) { + batc_hash_s[hashkey] |= 1U << i; + } else { + batc_hash_u[hashkey] |= 1U << i; + } + } + } + +#if defined(M88200_STAT) + // ハッシュの各要素が何個の衝突を持つか。 + // (2以上が極力現れないことが望ましい) + auto counting = [](auto& stat, const auto& hash) { + std::fill(stat.begin(), stat.end(), 0); + for (const auto hashmap : hash) { + int n = __builtin_popcount(hashmap); + if (n >= stat.size()) { + n = stat.size() - 1; + } + stat[n]++; + } + }; + counting(stat_batc_hash_s, batc_hash_s); + counting(stat_batc_hash_u, batc_hash_u); +#endif } // CSSP レジスタの読み出し @@ -697,6 +1002,23 @@ m88200::SetCSSP(uint32 data) set.vv[0] = (m88200CacheSet::Status)((data >> CSSP_VV0_OFFSET) & 3); } +// S/U 信号線を設定。true なら Super、false なら User。 +void +m88200::SetSuper(bool super) +{ + acc_super = super; + + if (acc_super) { + acc_apr = &sapr; + batc_hash = &batc_hash_s[0]; + patc_hash = &patc_hash_s[0]; + } else { + acc_apr = &uapr; + batc_hash = &batc_hash_u[0]; + patc_hash = &patc_hash_u[0]; + } +} + // // 物理バスアクセス @@ -718,7 +1040,7 @@ m88200::MBusRead(uint32 paddr, int size) } else { data = vm_phys_read_8(paddr); } - if ((int64)data < 0) { + if (__predict_false((int64)data < 0)) { fault_code = FAULT_CODE_BUSERR; fault_addr = paddr; } @@ -741,7 +1063,7 @@ m88200::MBusWrite(uint32 paddr, uint32 d } else { rv = vm_phys_write_8(paddr, data); } - if ((int64)rv < 0) { + if (__predict_false((int64)rv < 0)) { fault_code = FAULT_CODE_BUSERR; fault_addr = paddr; } @@ -749,7 +1071,7 @@ m88200::MBusWrite(uint32 paddr, uint32 d } // MBus に xmem トランザクションを行う。 -// paddr は size によって 1, 2, 4 バイト境界に整列していること。 +// paddr は size によって 1, 4 バイト境界に整列していること (2バイトはない)。 // MBus Acquire された状態で呼び出すこと。 // 成功すれば読み込めた値を返す。 // バスエラーが起きれば fault_code、fault_addr をセットして (uint64)-1 を返す。 @@ -764,7 +1086,7 @@ m88200::MBusXmem(uint32 paddr, uint32 da // Read data with intent to modify acc_read = true; rv = MBusRead(paddr, size); - if ((int64)rv < 0) { + if (__predict_false((int64)rv < 0)) { goto error; } fetched = (uint32)rv; @@ -775,7 +1097,7 @@ m88200::MBusXmem(uint32 paddr, uint32 da // Write data to memory acc_read = false; rv = MBusWrite(paddr, data, size); - if ((int64)rv < 0) { + if (__predict_false((int64)rv < 0)) { goto error; } @@ -795,17 +1117,27 @@ m88200::load(uint32 addr) acc_laddr = addr; acc_read = true; - if (Translate() == false) { + if (__predict_false(Translate() == false)) { + putlog(2, "Translation BusError %c:$%08x", + (acc_super ? 'S' : 'U'), acc_laddr); return (uint64)-1; } - if ((acc_stat & DESC_CI)) { + if (__predict_false((acc_stat & DESC_CI))) { + parent->AddCycle(7); // Table.6-2 MBusAcquire(); + MBusMakeSnoop(acc_paddrH, 0); data = MBusRead(acc_paddrH + acc_paddrL, bits / 8); MBusRelease(); + if (__predict_false((int64)data < 0)) { + putlog(2, "MBus BusError %c:$%08x", + (acc_super ? 'S' : 'U'), acc_laddr); + } return data; } else { data = CacheRead(acc_paddrH); - if ((int64)data < 0) { + if (__predict_false((int64)data < 0)) { + putlog(2, "Cache BusError %c:$%08x", + (acc_super ? 'S' : 'U'), acc_laddr); return data; } @@ -853,8 +1185,6 @@ m88200::load_32(uint32 addr) return load<32>(addr); } -#define DEBUG_WT 1 - template uint64 m88200::store(uint32 addr, uint32 data) { @@ -862,17 +1192,17 @@ m88200::store(uint32 addr, uint32 data) acc_laddr = addr; acc_read = false; - if (Translate() == false) { + if (__predict_false(Translate() == false)) { return (uint64)-1; } - if ((acc_stat & DESC_CI)) { + if (__predict_false((acc_stat & DESC_CI))) { + parent->AddCycle(7); // Table.6-2 MBusAcquire(); + MBusMakeSnoop(acc_paddrH, 1); rv = MBusWrite(acc_paddrH + acc_paddrL, data, bits / 8); MBusRelease(); return rv; } else { - if (DEBUG_WT) - MBusWrite(acc_paddrH + acc_paddrL, data, bits / 8); return CacheWrite(acc_paddrH + acc_paddrL, data, bits / 8); } } @@ -902,12 +1232,13 @@ m88200::xmem(uint32 addr, uint32 data) acc_laddr = addr; acc_read = false; - if (Translate() == false) { + if (__predict_false(Translate() == false)) { return (uint64)-1; } - if ((acc_stat & DESC_CI)) { + if (__predict_false((acc_stat & DESC_CI))) { // XXX キャッシュ禁止領域だとどうなる? MBusAcquire(); + MBusMakeSnoop(acc_paddrH, 1); rv = MBusXmem(acc_paddrH + acc_paddrL, data, bits / 8); MBusRelease(); return rv; @@ -923,12 +1254,6 @@ m88200::xmem_8(uint32 addr, uint32 data) } uint64 -m88200::xmem_16(uint32 addr, uint32 data) -{ - return xmem<16>(addr, data); -} - -uint64 m88200::xmem_32(uint32 addr, uint32 data) { return xmem<32>(addr, data); @@ -944,17 +1269,17 @@ m88200::stat2str(uint32 stat) { std::string buf; - if ((stat & m88200::DESC_WT)) + if ((stat & DESC_WT)) buf += ",WT"; - if ((stat & m88200::DESC_G)) + if ((stat & DESC_G)) buf += ",G"; - if ((stat & m88200::DESC_CI)) + if ((stat & DESC_CI)) buf += ",CI"; - if ((stat & m88200::DESC_M)) + if ((stat & DESC_M)) buf += ",M"; - if ((stat & m88200::DESC_U)) + if ((stat & DESC_U)) buf += ",U"; - if ((stat & m88200::DESC_WP)) + if ((stat & DESC_WP)) buf += ",WP"; if (buf.empty()) { @@ -980,29 +1305,40 @@ m88200::Translate() // 下位2ビットは常にこうなる。 acc_paddrL = acc_laddr & 3; - // 2回しかループしないはず - for (int loop = 0; loop < 3; loop++) { - // Select Area Descriptor - if (SelectAreaDesc() == false) { - // XXX 図には - // Physical Address <= LA[18-2] :: 00 - // と書いてあるけどたぶんこうだよなあ... - // Physical Address <= LA[31-2] :: 00 - acc_paddrH = acc_laddr - acc_paddrL; - return true; - } + // Select Area Descriptor + if (SelectAreaDesc() == false) { + // XXX 図のこれはたぶん間違いだよなあ… + // 誤: Physical Address <= LA[18-2] :: 00 + // 正: Physical Address <= LA[31-2] :: 00 + acc_paddrH = acc_laddr - acc_paddrL; + + parent->AddCycle(1); + return true; + } + + // 変換する分だけカウントするのでいいか + translate_total++; + + // type == Valid ここから + + // Search BATC first - // type == Valid ここから + // まずざっくりハッシュを引く + uint32 bi = batc_hash[batc_hash_func(acc_laddr)]; - // Search BATC first + if (__predict_false(bi != 0)) { + // ハッシュで一部がヒットしたので、 + // 次に LBA + S 全体との完全一致で探す la = acc_laddr & 0xfff80000; la |= acc_super ? BATC_S : 0; - for (int i = 0; i < batc.size(); i++) { - m88200BATC& b = batc[i]; - if (b.lba == la) { - // BATC hit + + do { + int i = __builtin_ctz(bi); + if (batc[i].lba == la) { + m88200BATC& b = batc[i]; putlog(4, " BATC[%d] hit", i); - if (acc_IsWrite() && b.wp) { + batc_hit[i]++; + if (b.wp && acc_IsWrite()) { goto write_violation; } @@ -1011,18 +1347,46 @@ m88200::Translate() acc_stat |= b.stat; putlog(4, " BATC hit acc=%s paddr=$%08x", stat2str(acc_stat).c_str(), acc_paddrH); + + parent->AddCycle(1); return true; } - } + // ヒットしたビットを 0 にする + bi &= bi - 1; + } while (bi); + } + // 見付からなかったので PATC へ + // 2回しかループしないはず + for (int loop = 0; loop < 3; loop++) { // BATC miss, search PATC then + acc_patc = NULL; + + uint64 pi = patc_hash[patc_hash_func(acc_laddr)]; +#if defined(M88200_STAT) + stat_patc_search++; +#endif + + if (pi == 0) { +#if defined(M88200_STAT) + // 単純なサーチミスのみカウントし、M bit による合流は除きたい + stat_patc_miss1++; +#endif + goto patc_miss; + } + la = acc_laddr & 0xfffff000; la |= acc_super ? PATC_S : 0; - acc_patc = NULL; - for (int i = 0; i < patc.size(); i++) { + + do { + int i = __builtin_ctzll(pi); m88200PATC& p = patc[i]; if (p.lpa == la) { // PATC hit + patc_hit++; +#if defined(M88200_STAT) + stat_patc_hit++; +#endif if (acc_IsWrite() && p.wp) { putlog(4, " PATC[%d] hit; stat=%s m=%d wp=%d", i, stat2str(p.stat).c_str(), p.m, p.wp); @@ -1044,7 +1408,7 @@ m88200::Translate() // この下の PATC miss に合流する acc_patc = &p; - break; + goto patc_miss; } // Physical Address <= PFA :: LA[11-2] :: 00 @@ -1052,13 +1416,25 @@ m88200::Translate() acc_stat |= p.stat; putlog(4, " PATC[%d] hit acc=%s paddr=$%08x", i, stat2str(acc_stat).c_str(), acc_paddrH); + + parent->AddCycle(1); return true; } - } + // ヒットしたビットを 0 にする + pi &= pi - 1; + } while (pi); +#if defined(M88200_STAT) + // 単純なサーチミスのみカウントし、M bit による合流は除きたい + stat_patc_miss2++; +#endif + patc_miss: putlog(4, " BATC/PATC miss"); // PATC miss + // PATC にヒットして M bit 更新するものは両方カウントされるけど + atc_miss++; + // フローチャートでは PATC miss というラベルが付いてるが、コード // では acc_patc は PATC ループ前に NULL にしてあり(=miss)、 // ループで PATC エントリが見付かれば acc_patc に代入してから @@ -1067,6 +1443,10 @@ m88200::Translate() // Table search operation if (TableSearch() == true) { + // フローチャートでは Select Area Descriptor まで戻るように + // 書いてあるが、PATC ミスによるテーブルサーチでは Area Descriptor + // と BATC は変化せず PATC が変化するだけなので、PATC サーチから + // やり直す。 continue; } @@ -1091,25 +1471,17 @@ m88200::Translate() bool m88200::SelectAreaDesc() { - const m88200APR *xapr; - - if (acc_super) { - xapr = &sapr; - } else { - xapr = &uapr; - } - - acc_stat = xapr->stat & ACC_STAT_MASK; - if (xapr->enable) { + acc_stat = acc_apr->stat & ACC_STAT_MASK; + if (acc_apr->enable) { // セグメントディスクリプタのアドレスはここで決まるが、 // 次に行う BATC、PATC サーチでは使わず、それらが全部ミスして // テーブルサーチまで来た所で初めて使うので、ここでは表示しない。 - acc_sdaddr = xapr->addr | ((acc_laddr >> 20) & ~3U); - putlog(4, " %s acc=%s", xapr->name, stat2str(acc_stat).c_str()); + acc_sdaddr = acc_apr->addr | ((acc_laddr >> 20) & ~3U); + putlog(4, " %s acc=%s", acc_apr->name, stat2str(acc_stat).c_str()); return true; } else { putlog(3, " %s acc=%s Untranslated", - xapr->name, stat2str(acc_stat).c_str()); + acc_apr->name, stat2str(acc_stat).c_str()); return false; } } @@ -1132,11 +1504,15 @@ m88200::TableSearch() if (FetchSegmentDesc() == false) { MBusRelease(); + + parent->AddCycle(7); // Table.6-2 return false; } if (FetchPageDesc() == false) { MBusRelease(); + + parent->AddCycle(11); // Table.6-2 return false; } @@ -1166,10 +1542,14 @@ m88200::TableSearch() if ((tmp_desc & DESC_U) == 0 || ((tmp_desc & DESC_M) == 0 && acc_IsWrite())) { + parent->AddCycle(15); // Table.6-2 + if (UpdatePageDesc() == false) { MBusRelease(); return false; } + } else { + parent->AddCycle(11); // Table.6-2 } if (acc_patc == NULL) { @@ -1190,15 +1570,16 @@ m88200::FetchSegmentDesc() { uint64 data; + MBusMakeSnoop(acc_sdaddr, 0); data = MBusRead(acc_sdaddr, 4); - if ((int64)data < 0) { + if (__predict_false((int64)data < 0)) { return false; } tmp_desc = (uint32)data; tmp_desc &= (0xfffff000 | DESC_WT | DESC_SP | DESC_G | DESC_CI | DESC_WP | DESC_V); - if ((tmp_desc & DESC_V) == 0) { + if (__predict_false((tmp_desc & DESC_V) == 0)) { fault_code = FAULT_CODE_SEGMENT; fault_addr = acc_sdaddr; putlog(4, " SD $%08x desc=$%08x SegFault", acc_sdaddr, tmp_desc); @@ -1206,7 +1587,7 @@ m88200::FetchSegmentDesc() } else { // Descriptor is valid - if ((tmp_desc & DESC_SP) && acc_super == 0) { + if (__predict_false((tmp_desc & DESC_SP) && acc_super == 0)) { fault_code = FAULT_CODE_SUPERVISOR; fault_addr = acc_sdaddr; putlog(4, " SD $%08x desc=$%08x SupervisorFault", @@ -1233,15 +1614,16 @@ m88200::FetchPageDesc() { uint64 data; + MBusMakeSnoop(acc_pdaddr, 0); data = MBusRead(acc_pdaddr, 4); - if ((int64)data < 0) { + if (__predict_false((int64)data < 0)) { return false; } tmp_desc = (uint32)data; tmp_desc &= (0xfffff000 | DESC_WT | DESC_SP | DESC_G | DESC_CI | DESC_M | DESC_U | DESC_WP | DESC_V); - if ((tmp_desc & DESC_V) == 0) { + if (__predict_false((tmp_desc & DESC_V) == 0)) { fault_code = FAULT_CODE_PAGE; fault_addr = acc_pdaddr; putlog(4, " PD $%08x desc=$%08x PageFault", acc_pdaddr, tmp_desc); @@ -1249,7 +1631,7 @@ m88200::FetchPageDesc() } else { // Descriptor is valid - if ((tmp_desc & DESC_SP) && acc_super == 0) { + if (__predict_false((tmp_desc & DESC_SP) && acc_super == 0)) { fault_code = FAULT_CODE_SUPERVISOR; fault_addr = acc_pdaddr; putlog(4, " PD $%08x desc=$%08x SupervisorFault", @@ -1259,7 +1641,7 @@ m88200::FetchPageDesc() acc_stat |= tmp_desc & ACC_STAT_MASK; - if ((acc_stat & DESC_WP) && acc_IsWrite()) { + if (__predict_false((acc_stat & DESC_WP) && acc_IsWrite())) { fault_code = FAULT_CODE_WRITE; // fault_addr は invalid data fault_addr = 0xdeadbeef; @@ -1295,8 +1677,9 @@ m88200::UpdatePageDesc() // Update Used bit tmp_desc |= DESC_U; + MBusMakeSnoop(acc_pdaddr, 1); rv = MBusWrite(acc_pdaddr, tmp_desc, 4); - if ((int64)rv < 0) { + if (__predict_false((int64)rv < 0)) { return false; } putlog(4, " Update PD $%08x desc=$%08x stat=%s", @@ -1305,17 +1688,45 @@ m88200::UpdatePageDesc() return true; } +// patc[patc_index] に対応する PATC ハッシュを無効にする +void +m88200::InvalidatePATCHash(int patc_index) +{ + m88200PATC& p = patc[patc_index]; + int hashkey = patc_hash_func(p.lpa); + + uint64 bit = 1ULL << patc_index; + if (p.IsS()) { + patc_hash_s[hashkey] &= ~bit; + } else { + patc_hash_u[hashkey] &= ~bit; + } +} + // Create PATC Entry // 公式フローチャートと違って MBus Acquire したまま戻ること。 // p2-25, Figure2-9 void m88200::CreatePATCEntry() { - // とりあえず線形探索で memcpy しとくべ - memmove(&patc[1], &patc[0], sizeof(patc[0]) * (patc.size() - 1)); - // で、先頭に追加 - m88200PATC& p = patc[0]; + int hashkey; + + // 空きがあろうがなかろうが、とにかく一番古いエントリを使う。 + // おそらく実機も空きエントリを前に詰めるような複雑な処理はして + // ないだろうという推測。 + // 検索はハッシュを使って実装してあるため、途中に空きエントリが + // あっても (線形探索する時のような) デメリットはない。 + +#if defined(M88200_STAT) + stat_patc_create++; +#endif + m88200PATC& p = patc[patc_next]; + // 有効なら無効化 + if (p.IsValid()) { + InvalidatePATCHash(patc_next); + } + // このエントリを潰して作る memset(&p, 0, sizeof(p)); p.lpa = acc_laddr & 0xfffff000; p.pfa = tmp_desc & 0xfffff000; @@ -1324,8 +1735,17 @@ m88200::CreatePATCEntry() } p.stat = acc_stat; putlog(4, " %s %c:$%08x:$%08x stat=%s", __func__, - (p.lpa & PATC_S) ? 'S' : 'U', (p.lpa & 0xfffff000), p.pfa, + p.IsS() ? 'S' : 'U', (p.lpa & 0xfffff000), p.pfa, stat2str(p.stat).c_str()); + + hashkey = patc_hash_func(p.lpa); + uint64 bit = 1ULL << patc_next; + patc_hash[hashkey] |= bit; + + patc_next++; + if (__predict_false(patc_next >= patc.size())) { + patc_next = 0; + } } // アドレス変換 (デバッガ用) @@ -1415,7 +1835,7 @@ void m88200CacheSet::Update(int line, m88200CacheSet::Status status) { vv[line] = status; - if (status == IV) { + if (__predict_false(status == IV)) { // 無効なら tag[line] |= TAG_INVALID; L = TryUnuseLine(L, line); @@ -1425,7 +1845,7 @@ m88200CacheSet::Update(int line, m88200C // CSSP L5-L0 の処理。 // 引数 tmpL の状態から line を最新にしたらどうなるか、を返す。 // 表示処理で一時変数に対して処理が必要なため分離してある。 -int +/*static*/ int m88200CacheSet::TryUseLine(int tmpL, int line) { // L フィールドは 3bit, 2bit, 1bit で構成され、 @@ -1450,7 +1870,7 @@ m88200CacheSet::TryUseLine(int tmpL, int // CSSP L5-L0 の処理。 // 引数 tmpL の状態から line を最古にしたらどうなるか、を返す。 -int +/*static*/ int m88200CacheSet::TryUnuseLine(int tmpL, int line) { // Use のちょうど反転論理 @@ -1471,7 +1891,7 @@ m88200CacheSet::TryUnuseLine(int tmpL, i // CSSP L5-L0 の処理。 // 引数 tmpL の状態で、最新の line を返す。 // 表示処理で一時変数に対して処理が必要なため分離してある。 -int +/*static*/ int m88200CacheSet::TryGetOldestLine(int tmpL) { if (tmpL < 8) { @@ -1493,48 +1913,10 @@ m88200CacheSet::TryGetOldestLine(int tmp int m88200CacheSet::SelectOldestLine() const { -#if 0 - int line; - - // Invalid にしたときに順序を逆回しすればこの比較は不要? - - // Invalid line があればそれを選択 - for (line = 0; line < 4; line++) { - if (vv[line] == Status::IV) { - return line; - } - } -#endif - // なければ、最も古いラインを差し出す return TryGetOldestLine(L); } -// 指定の line に1ライン(4word)読み込む。 -// 成功すれば 0 を返す。 -// バスエラーなら (uint64)-1 を返す。この場合呼び出し側で fault_code, -// fault_addr を設定すること。(ここは m88200 クラスではないのでセットできない) -uint64 -m88200CacheSet::ReadLine(int line, uint32 tagaddr) -{ - uint32 addr; - uint64 data; - - // タグを更新 - tag[line] = tagaddr; - - addr = tag[line] | (setidx << 4); - for (int i = 0; i < 4; i++) { - data = vm_phys_read_32(addr); - if ((int64)data < 0) { - return data; - } - word[line * 4 + i] = data; - addr += 4; - } - return 0; -} - // キャッシュの指定の line, word に data を書き込む。 // size は 1, 2, 4 バイト。 void @@ -1576,41 +1958,87 @@ m88200CacheSet::Write(int line, uint32 p word[line * 4 + wordidx] = data32; } -// 指定の set,line をコピーバックする。 +// キャッシュを検索。 +// ヒットすれば line 番号(0..3) を返す。ヒットしなければ -1 を返す。 +int +m88200CacheSet::Lookup(uint32 tagaddr) const +{ + for (int line = 0; line < 4; line++) { + if (tag[line] == tagaddr) { + return line; + } + } + return -1; +} + + +// キャッシュの指定の set, line にメモリから 1 ライン(4word) 読み込む。 // 成功すれば 0 を返す。 -// バスエラーなら (uint64)-1 を返す。この場合呼び出し側で fault_code, -// fault_addr を設定すること。(ここは m88200 クラスではないのでセットできない) +// バスエラーなら fault_code, fault_addr をセットし (uint64)-1 を返す。 uint64 -m88200CacheSet::CopyBackLine(int line) +m88200::ReadLine(m88200CacheSet& set, int line, uint32 tagaddr) { uint32 addr; + uint64 data; + + // キャッシュラインの充填は実機では Burst Read されているらしいが + // nono では Burst Read 用のメソッドは用意しておらず通常の 32bit + // アクセスをしているため、このままでは所要ウェイト数が正しくない。 + // そこでここで差を調整することにする。 + // vm_phys_read_32() 4回なのでこれによって 12 wait が加算される。一方 + // 実機の Burst Read は取扱説明書によると 320ns (8 サイクル) で完了 + // するようなので、その差 4 をあらかじめ引いておく。 + // 途中でバスエラーが起きたらとかそこまでは考慮しない。 + AddCycle(-4); + + // タグを更新 + set.tag[line] = tagaddr; - addr = tag[line] | (setidx << 4); + addr = set.tag[line] | (set.setidx << 4); for (int i = 0; i < 4; i++) { - uint64 rv = vm_phys_write_32(addr, word[line * 4 + i]); - if ((int64)rv < 0) { - return rv; + data = vm_phys_read_32(addr); + if (__predict_false((int64)data < 0)) { + fault_code = FAULT_CODE_BUSERR; + fault_addr = addr; + return data; } + set.word[line * 4 + i] = data; addr += 4; } return 0; } -// キャッシュを検索。 -// ヒットすれば line 番号(0..3) を返す。ヒットしなければ -1 を返す。 -int -m88200CacheSet::Lookup(uint32 tagaddr) const +// キャッシュの指定の set, line を1ライン、メモリに書き出す(コピーバック)。 +// 成功すれば 0 を返す。 +// バスエラーなら fault_code, fault_addr をセットし (uint64)-1 を返す。 +uint64 +m88200::CopyBackLine(m88200CacheSet& set, int line) { - for (int line = 0; line < 4; line++) { - if (tag[line] == tagaddr) { - return line; + uint32 addr; + + // キャッシュラインの書き出しは実機では Burst Write されているらしいが + // nono では Burst Write 用のメソッドは用意しておらず通常の 32bit + // アクセスをしているため、このままでは所要ウェイト数が正しくない。 + // そこでここで差を調整することにする。 + // vm_phys_write_32() 4回なのでこれによって 8 wait が加算される。一方 + // 実機の Burst Write は取扱説明書によると 280ns (7サイクル) で完了する + // ようなので、その差 1 をあらかじめて引いておく。 + // 途中でバスエラーが起きたらとかそこまでは考慮しない。 + AddCycle(-1); + + addr = set.tag[line] | (set.setidx << 4); + for (int i = 0; i < 4; i++) { + uint64 rv = vm_phys_write_32(addr, set.word[line * 4 + i]); + if (__predict_false((int64)rv < 0)) { + fault_code = FAULT_CODE_BUSERR; + fault_addr = addr; + return rv; } + addr += 4; } - return -1; + return 0; } - - // キャッシュに対して paddr の読み込みを行う。 // paddr は 32bit 境界のアドレスであること。 // 読み込めれば該当の32bitワードを返す。 @@ -1633,14 +2061,17 @@ m88200::CacheRead(uint32 paddr) putlog(3, "CacheRead paddr=$%08x (set=$%02x)", paddr, setidx); line = set.Lookup(tagaddr); - if (line >= 0) { + if (__predict_true(line >= 0)) { // Cache Hit putlog(4, " CacheRead hit (line=%d,word=%d)", line, wordidx); + parent->AddCycle(1); goto success; } // Cache Miss + parent->AddCycle(10); // Table.6-2 + // reply <- Wait MBusAcquire(); @@ -1649,7 +2080,8 @@ m88200::CacheRead(uint32 paddr) putlog(4, " CacheRead miss (replace line=%d)", line); if (set.vv[line] == m88200CacheSet::EM) { - rv = set.CopyBackLine(line); + parent->AddCycle(7); // Table.6-2 + rv = CopyBackLine(set, line); if ((int64)rv < 0) { goto error; } @@ -1659,8 +2091,9 @@ m88200::CacheRead(uint32 paddr) set.Update(line, m88200CacheSet::IV); // Read line from memory - rv = set.ReadLine(line, tagaddr); - if ((int64)rv < 0) { + MBusMakeSnoop(paddr, 0); + rv = ReadLine(set, line, tagaddr); + if (__predict_false((int64)rv < 0)) { goto error; } @@ -1676,8 +2109,6 @@ m88200::CacheRead(uint32 paddr) error: MBusRelease(); - fault_code = FAULT_CODE_BUSERR; - fault_addr = paddr; return (uint64)-1; } @@ -1702,13 +2133,16 @@ m88200::CacheWrite(uint32 paddr, uint32 putlog(3, "CacheWrite paddr=$%08x (set=$%02x)", paddr, setidx); line = set.Lookup(tagaddr); - if (line >= 0) { + if (__predict_true(line >= 0)) { // Cache Hit + parent->AddCycle(1); return CacheWriteHit(set, line, paddr, data, size); } // Cache Miss + parent->AddCycle(14); // Table.6-2 + // reply <- Wait MBusAcquire(); @@ -1717,7 +2151,8 @@ m88200::CacheWrite(uint32 paddr, uint32 putlog(4, " CacheWrite miss (replace line=%d)", line); if (set.vv[line] == m88200CacheSet::EM) { - rv = set.CopyBackLine(line); + parent->AddCycle(7); // Table.6-2 + rv = CopyBackLine(set, line); if ((int64)rv < 0) { goto error; } @@ -1727,7 +2162,8 @@ m88200::CacheWrite(uint32 paddr, uint32 set.Update(line, m88200CacheSet::IV); // Read line with intent to modify - rv = set.ReadLine(line, tagaddr); + MBusMakeSnoop(paddr, 1); + rv = ReadLine(set, line, tagaddr); if ((int64)rv < 0) { goto error; } @@ -1735,7 +2171,7 @@ m88200::CacheWrite(uint32 paddr, uint32 // Write data to memory rv = MBusWrite(paddr, data, size); if ((int64)rv < 0) { - return rv; + goto error; } // Write data into cache @@ -1750,8 +2186,6 @@ m88200::CacheWrite(uint32 paddr, uint32 error: MBusRelease(); - fault_code = FAULT_CODE_BUSERR; - fault_addr = paddr; return (uint64)-1; } @@ -1764,7 +2198,7 @@ m88200::CacheWriteHit(m88200CacheSet& se { uint64 rv; - if (set.vv[line] == m88200CacheSet::SU) { + if (__predict_false(set.vv[line] == m88200CacheSet::SU)) { // Line Shared Unmodified の場合 putlog(4, " CacheWrite hit shared unmodified (line=%d)", line); @@ -1778,6 +2212,7 @@ m88200::CacheWriteHit(m88200CacheSet& se set.Write(line, paddr, data, size); // Write data to memory + MBusMakeSnoop(paddr, 1); rv = MBusWrite(paddr, data, size); if ((int64)rv < 0) { MBusRelease(); @@ -1798,12 +2233,11 @@ m88200::CacheWriteHit(m88200CacheSet& se } // どちらでもない場合は Line Exclusive と同じ処理に落ちる + } else { + // Line Exclusive の場合 + putlog(4, " CacheWrite hit exclusive (line=%d)", line); } - // Line Exclusive の場合 - - putlog(4, " CacheWrite hit exclusive (line=%d)", line); - // Write data into cache putlog(4, " CacheWrite line=%d $%08x sz=%d", line, paddr, size); set.Write(line, paddr, data, size); @@ -1833,14 +2267,16 @@ m88200::CacheXmem(uint32 paddr, uint32 d putlog(3, "CacheXmem paddr=$%08x (set=$%02x)", paddr, setidx); line = set.Lookup(tagaddr); - if (line >= 0 && set.vv[line] == m88200CacheSet::EM) { + if (__predict_false(line >= 0 && set.vv[line] == m88200CacheSet::EM)) { // Cache Hit (and Line exclusive modified) putlog(4, " CacheXmem hit and EM (line=%d)", line); MBusAcquire(); - rv = set.CopyBackLine(line); - if ((int64)rv < 0) { + parent->AddCycle(7); // Table.6-2 + MBusMakeSnoop(paddr, 1); + rv = CopyBackLine(set, line); + if (__predict_false((int64)rv < 0)) { goto error; } @@ -1859,6 +2295,7 @@ m88200::CacheXmem(uint32 paddr, uint32 d MBusAcquire(); } + MBusMakeSnoop(paddr, 1); rv = MBusXmem(paddr, data, size); MBusRelease(); return rv; @@ -1867,3 +2304,98 @@ m88200::CacheXmem(uint32 paddr, uint32 d MBusRelease(); return rv; } + +// サイクルを加算する (VM から使う) +void +m88200::AddCycle(int32 cycle) +{ + parent->AddCycle(cycle); +} + +// MBus 使用権を取得する +void +m88200::MBusAcquire() +{ + // アービトレーションのたびに1クロックかかる(?) + // よく分からんけど、とりあえず、CMMU は一度所有権を持ったら放さない、 + // 別の CMMU がバスリクエストすると所有権はその CMMU に移る、とする。 + if (__predict_false(mbus_master != this)) { + mbus_master = this; + parent->AddCycle(1); + } +} + +// 他 CMMU にバススヌープさせるポイント。 +// 本来はバスマスタ CMMU が MBus にアドレスと IM を出すと、それを監視して +// いる他 CMMU が必要に応じて反応するのだが、ここでは全部マスタ主導で行う。 +// Figure 3-8, 3-9 +void +m88200::MBusMakeSnoop(uint32 paddr, int im) +{ + if ((acc_stat & DESC_G) == 0) { + return; + } + + // MBus Status <- Wait (2 clocks) + // 本来はスヌープをするスレーブ側が 2 クロックかかるのだが、 + // 個別の CMMU がクロックを持っていない実装なのでマスタ側で + // クロックを消費したことにする。 + parent->AddCycle(2); + + // 本来はここで解放ではなく、スレーブ側が反応したい時にバスリクエストを + // 出してそれによって手放すのだが、その機構はないので、こちらが自主的に + // 一旦手放したようにしておく。とは言っても現状 MBusRelease() はダミーで + // 実際には他の誰かが MBusAcquire() を呼ぶまで握りっぱなしなので、 + // あまり問題ないはず。 + MBusRelease(); + for (const auto other : other_cmmu) { + other->Snoop(paddr, im); + } + MBusAcquire(); +} + +// バススヌープ (スレーブ側)。 +// 本来はバスマスタでない CMMU は、バスマスタが MBus に出すアドレス情報を +// 監視し、必要ならそれに反応するのだが、ここでは全部マスタ主導で行う。 +// こっちはそのマスタから呼ばれるスレーブ側。 +// Figure 3-8, 3-9 +void +m88200::Snoop(uint32 paddr, int im) +{ + assert((sctr & SCTR_SE)); + + uint32 tagaddr = (paddr & 0xfffff000); + uint32 setidx = (paddr >> 4) & 0xff; + + m88200CacheSet& set = setarray[setidx]; + int line = set.Lookup(tagaddr); + + if (line >= 0) { + // Cache hit + if (set.vv[line] == m88200CacheSet::EM) { + // Line exclusive modified + + // Assert retry + + // Request MBus + // 実際にはここで MBus 使用権を要求して獲得するのだが + // その機構はなくその代わり Acquire を呼ぶだけでいける。 + MBusAcquire(); + uint64 rv = CopyBackLine(set, line); + MBusRelease(); + if ((int64)rv < 0) { + // Set CE bit in system status register + return; + } + + if (im == 0) { + // Mark line shared unmodified (if IM=0) + set.Update(line, m88200CacheSet::SU); + } else { + // Mark line invalid (if IM=1) + set.Update(line, m88200CacheSet::IV); + } + } + } + // Resume servicing PBus +}