--- nono/m88xx0/m88200.cpp 2026/04/29 17:04:58 1.1.1.8 +++ nono/m88xx0/m88200.cpp 2026/04/29 17:05:11 1.1.1.10 @@ -21,11 +21,15 @@ m88200::m88200() { // objname 等は SetID() で確定する - reg_monitor.func = (MonitorCallback_t)&m88200::MonitorUpdateReg; + reg_monitor.func = ToMonitorCallback(&m88200::MonitorUpdateReg); reg_monitor.SetSize(52, 8); - atc_monitor.func = (MonitorCallback_t)&m88200::MonitorUpdateATC; - atc_monitor.SetSize(65, 38); - cache_monitor.func = (MonitorCallback_t)&m88200::MonitorUpdateCache; + 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() で決まるので、続きは向こうでやる @@ -34,8 +38,8 @@ m88200::m88200() 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++) { @@ -161,25 +165,26 @@ m88200::MonitorUpdateATC(Monitor *, Text screen.Print(0, 0, "SAPR %05x'000 TE=%d %c%c%c", (sapr.addr >> 12) & 0xfffff, sapr.enable ? 1 : 0, - (sapr.stat & m88200::APR_WT) ? 'T' : '-', - (sapr.stat & m88200::APR_G) ? 'G' : '-', - (sapr.stat & m88200::APR_CI) ? 'C' : '-'); + (sapr.stat & APR_WT) ? 'T' : '-', + (sapr.stat & APR_G) ? 'G' : '-', + (sapr.stat & APR_CI) ? 'C' : '-'); - screen.Print(34, 0, "UAPR %05x'000 TE=%d %c%c%c", + screen.Print(40, 0, "UAPR %05x'000 TE=%d %c%c%c", (uapr.addr >> 12) & 0xfffff, uapr.enable ? 1 : 0, - (uapr.stat & m88200::APR_WT) ? 'T' : '-', - (uapr.stat & m88200::APR_G) ? 'G' : '-', - (uapr.stat & m88200::APR_CI) ? 'C' : '-'); + (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"); - screen.Puts(34, 2, "No. LBA PBA Stat"); + 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 = 34; + x = 40; y = 3; } if (i < 8) { @@ -189,45 +194,132 @@ m88200::MonitorUpdateATC(Monitor *, Text } const m88200BATC& b = batc[i]; - if ((b.lba & 1) == 0) { + if (b.IsValid()) { screen.Print(x + 4, y, "%c:%04x'0000 %04x'0000 %c%c%c%c", - (b.lba & m88200::BATC_S) ? 'S' : 'U', + b.IsS() ? 'S' : 'U', b.lba >> 16, b.pba >> 16, - (b.stat & m88200::DESC_WT) ? 'T' : '-', - (b.stat & m88200::DESC_G) ? 'G' : '-', - (b.stat & m88200::DESC_CI) ? 'C' : '-', - (b.stat & m88200::DESC_WP) ? 'P' : '-'); + (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(34, 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 = 34; + x = 40; y = 10; } screen.Print(x, y, "%2d:", i); const m88200PATC& p = patc[i]; - if ((p.lpa & 1) == 0) { + if (p.IsValid()) { screen.Print(x + 4, y, "%c:%05x'000 %05x'000 %c%c%c%c%c", - (p.lpa & m88200::PATC_S) ? 'S' : 'U', + p.IsS() ? 'S' : 'U', p.lpa >> 12, p.pfa >> 12, - (p.stat & m88200::DESC_WT) ? 'T' : '-', - (p.stat & m88200::DESC_G) ? 'G' : '-', - (p.stat & m88200::DESC_CI) ? 'C' : '-', - (p.stat & m88200::DESC_WP) ? 'P' : '-', - p.m ? 'M' : '-'); + (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 から呼ばれる) @@ -496,12 +588,12 @@ m88200::SetSCR(uint32 data) case 0x20 ... 0x23: // Probe User Address case 0x28 ... 0x2b: - putlog(0, "SCR Command: Probe User Address 未実装"); + putlog(0, "SCR Command: Probe User Address (NOT IMPLEMENTED)"); return; case 0x24 ... 0x27: // Probe Supervisor Address case 0x2c ... 0x2f: - putlog(0, "SCR Command: Probe Supervisor Address 未実装"); + putlog(0, "SCR Command: Probe Supervisor Address (NOT IMPLEMENTED)"); return; case 0x30 ... 0x3f: // Invalidate {User,Supervisor} PATC Descriptors @@ -634,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 + + // ゲスト側が指定した端数部分は有効部分でマスクする。 + // 内部では下位ビットはフラグ扱いなのでマスクしないと誤動作する。 + addr = GetSAR() & 0xfffff000; + addr |= (s) ? PATC_S : 0; - if ((command & 0x04)) { - addr |= PATC_S; - } + auto& hash = (s) ? patc_hash_s : patc_hash_u; + uint64 pi = hash[patc_hash_func(addr)]; - 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(); + 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, "Undefined 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 + } } } } @@ -679,15 +805,27 @@ 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) { - putlog(0, "SCTR <- $%08x (%s 書き込み未実装)", data, msg.c_str() + 1); + putlog(0, "SCTR <- $%08x (%s NOT IMPLEMENTED)", data, msg.c_str() + 1); } } @@ -716,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) @@ -748,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 レジスタの読み出し @@ -848,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]; + } +} + // // 物理バスアクセス @@ -869,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; } @@ -892,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; } @@ -915,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; @@ -926,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; } @@ -946,23 +1117,27 @@ m88200::load(uint32 addr) acc_laddr = addr; acc_read = true; - if (Translate() == false) { - putlog(2, "Translation BusError %c:$%08x", (acc_super ? 'S' : 'U'), acc_laddr); + 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 ((int64)data < 0) { - putlog(2, "MBus BusError %c:$%08x", (acc_super ? 'S' : 'U'), acc_laddr); + 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) { - putlog(2, "Cache BusError %c:$%08x", (acc_super ? 'S' : 'U'), acc_laddr); + if (__predict_false((int64)data < 0)) { + putlog(2, "Cache BusError %c:$%08x", + (acc_super ? 'S' : 'U'), acc_laddr); return data; } @@ -1010,8 +1185,6 @@ m88200::load_32(uint32 addr) return load<32>(addr); } -#define DEBUG_WT 1 - template uint64 m88200::store(uint32 addr, uint32 data) { @@ -1019,21 +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) { - MBusAcquire(); - MBusWrite(acc_paddrH + acc_paddrL, data, bits / 8); - MBusRelease(); - } return CacheWrite(acc_paddrH + acc_paddrL, data, bits / 8); } } @@ -1063,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; @@ -1099,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()) { @@ -1135,30 +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; + // 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; - } + parent->AddCycle(1); + return true; + } + + // 変換する分だけカウントするのでいいか + translate_total++; + + // type == Valid ここから - // type == Valid ここから + // Search BATC first + + // まずざっくりハッシュを引く + 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; } @@ -1171,16 +1351,42 @@ m88200::Translate() 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); @@ -1202,7 +1408,7 @@ m88200::Translate() // この下の PATC miss に合流する acc_patc = &p; - break; + goto patc_miss; } // Physical Address <= PFA :: LA[11-2] :: 00 @@ -1214,11 +1420,21 @@ m88200::Translate() 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 に代入してから @@ -1227,6 +1443,10 @@ m88200::Translate() // Table search operation if (TableSearch() == true) { + // フローチャートでは Select Area Descriptor まで戻るように + // 書いてあるが、PATC ミスによるテーブルサーチでは Area Descriptor + // と BATC は変化せず PATC が変化するだけなので、PATC サーチから + // やり直す。 continue; } @@ -1251,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; } } @@ -1358,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); @@ -1374,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", @@ -1401,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); @@ -1417,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", @@ -1427,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; @@ -1463,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", @@ -1473,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; @@ -1492,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; + } } // アドレス変換 (デバッガ用) @@ -1583,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); @@ -1593,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 で構成され、 @@ -1618,7 +1870,7 @@ m88200CacheSet::TryUseLine(int tmpL, int // CSSP L5-L0 の処理。 // 引数 tmpL の状態から line を最古にしたらどうなるか、を返す。 -int +/*static*/ int m88200CacheSet::TryUnuseLine(int tmpL, int line) { // Use のちょうど反転論理 @@ -1639,7 +1891,7 @@ m88200CacheSet::TryUnuseLine(int tmpL, i // CSSP L5-L0 の処理。 // 引数 tmpL の状態で、最新の line を返す。 // 表示処理で一時変数に対して処理が必要なため分離してある。 -int +/*static*/ int m88200CacheSet::TryGetOldestLine(int tmpL) { if (tmpL < 8) { @@ -1745,7 +1997,7 @@ m88200::ReadLine(m88200CacheSet& set, in addr = set.tag[line] | (set.setidx << 4); for (int i = 0; i < 4; i++) { data = vm_phys_read_32(addr); - if ((int64)data < 0) { + if (__predict_false((int64)data < 0)) { fault_code = FAULT_CODE_BUSERR; fault_addr = addr; return data; @@ -1777,7 +2029,7 @@ m88200::CopyBackLine(m88200CacheSet& set 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 ((int64)rv < 0) { + if (__predict_false((int64)rv < 0)) { fault_code = FAULT_CODE_BUSERR; fault_addr = addr; return rv; @@ -1809,7 +2061,7 @@ 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); @@ -1839,8 +2091,9 @@ m88200::CacheRead(uint32 paddr) set.Update(line, m88200CacheSet::IV); // Read line from memory + MBusMakeSnoop(paddr, 0); rv = ReadLine(set, line, tagaddr); - if ((int64)rv < 0) { + if (__predict_false((int64)rv < 0)) { goto error; } @@ -1880,7 +2133,7 @@ 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); @@ -1909,6 +2162,7 @@ m88200::CacheWrite(uint32 paddr, uint32 set.Update(line, m88200CacheSet::IV); // Read line with intent to modify + MBusMakeSnoop(paddr, 1); rv = ReadLine(set, line, tagaddr); if ((int64)rv < 0) { goto error; @@ -1944,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); @@ -1958,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(); @@ -1978,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); @@ -2013,15 +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(); parent->AddCycle(7); // Table.6-2 + MBusMakeSnoop(paddr, 1); rv = CopyBackLine(set, line); - if ((int64)rv < 0) { + if (__predict_false((int64)rv < 0)) { goto error; } @@ -2040,6 +2295,7 @@ m88200::CacheXmem(uint32 paddr, uint32 d MBusAcquire(); } + MBusMakeSnoop(paddr, 1); rv = MBusXmem(paddr, data, size); MBusRelease(); return rv; @@ -2063,8 +2319,83 @@ m88200::MBusAcquire() // アービトレーションのたびに1クロックかかる(?) // よく分からんけど、とりあえず、CMMU は一度所有権を持ったら放さない、 // 別の CMMU がバスリクエストすると所有権はその CMMU に移る、とする。 - if (mbus_master != this) { + 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 +}