--- nono/m680x0/m68030bus.cpp 2026/04/29 17:04:28 1.1 +++ nono/m680x0/m68030bus.cpp 2026/04/29 17:05:40 1.1.1.8 @@ -1,483 +1,842 @@ // // nono -// Copyright (C) 2017 isaki@NetBSD.org -// -// 論理アクセス +// Copyright (C) 2020 nono project +// Licensed under nono-license.txt // -// -// 1回の(アラインド)物理アクセスを担当するマクロ。 -// これらは続く _mmu テンプレートで使用する。 -// goto を含むことに注意。gcc 拡張式(?)に注意。 -// mmu_translate_* は fetch, read, write の3種だが、m68030_phys_ は -// read, write しかないことに注意。物理バスに FC0 と FC1、つまりプログラム -// 空間かどうかの情報は出していないため。 -// +#include "mpu680x0.h" +#include "mainbus.h" +#include "m68030cache.h" + +// 現在の FC での通常アクセス (MPU 内部から使用する) +// ミスアライン可。 +// バスエラーが起きると例外 M68K::EXCEP_BUSERR をスローする。 +// uint32 fetch_{2,4}() +// uint32 read_{1,2,4}(laddr) .. vm/mpu680x0.h にある +// void write_{1,2,4}(laddr, data) .. vm/mpu680x0.h にある -#define FETCH(size) ({ \ - cpu->bus.paddr = cpu->bus.laddr; \ - if (use_mmu && m68030_mmu_translate_fetch(cpu) == false) \ - goto buserr; \ - uint64 data_ = m68030_phys_read_##size(cpu->bus.paddr); \ - if ((int64)data_ < 0) \ - goto buserr; \ - data_; /* これが戻り値 */ \ -}) - -#define READ(size) ({ \ - cpu->bus.paddr = cpu->bus.laddr; \ - if (use_mmu && m68030_mmu_translate_read(cpu) == false) \ - goto buserr; \ - uint64 data_ = m68030_phys_read_##size(cpu->bus.paddr); \ - if ((int64)data_ < 0) \ - goto buserr; \ - data_; /* これが戻り値 */ \ -}) - -#define WRITE(size, data) ({ \ - cpu->bus.paddr = cpu->bus.laddr; \ - if (use_mmu && m68030_mmu_translate_write(cpu) == false) \ - goto buserr; \ - int64 berr_ = m68030_phys_write_##size(cpu->bus.paddr, (data)); \ - if (berr_ < 0) \ - goto buserr; \ - 0; /* 戻り値不要だけど一応 */ \ -}) - -// バスエラー時に SSW をセットする。 -// m68030_*_NN_mmu() 関数の下請け。 -static inline void -SET_SSW(m68kcpu *cpu, bool use_mmu, uint size) -{ - // サイズを設定 - cpu->bus.ssw &= ~SSW_SIZE_MASK; - cpu->bus.ssw |= size; - - if (use_mmu) { - // データアクセスなら再試行のため DataFault ビットをセット。 - // 命令アクセスなら RB をセット。 - if ((cpu->bus.ssw & FC_DATA)) { - cpu->bus.ssw |= SSW_DF; - } else { - cpu->bus.ssw |= SSW_RB; +// reg.pc からワードをフェッチし reg.pc を進める。 +uint32 +MPU68030Device::fetch_2() +{ + busaddr addr = busaddr(reg.pc) | fc_inst | BusAddr::Size2; + + // まず可能ならキャッシュから探す。 + m68030Cache *cache = icache.get(); + if (__predict_true(cache->enable)) { + uint32 tag = (addr.Get() >> 4) & 0xfffffff0; + uint32 idx = (reg.pc & 0x000000f0) >> 4; + uint32 sel = (reg.pc & 0x0000000c) >> 2; + m68030CacheLine *l = &cache->line[idx]; + + if (__predict_true(l->tag == tag && l->valid[sel])) { + cache->stat_fasthit++; + uint32 data = l->data[sel]; + if ((reg.pc & 2) == 0) { + data >>= 16; + } else { + data &= 0xffff; + } + reg.pc += 2; + return data; } } -} -// -// MMU を考慮した下請けテンプレート。 -// 1回分のミスアラインドありのアクセスを担当。 -// + busdata bd; + if (__predict_true(cache->enable)) { + bd = read_cache(cache, addr); + } else { + bd = read_nocache(addr); + } + if (__predict_false(bd.IsBusErr())) { + bus.size = addr.GetSize(); + throw M68K::EXCEP_BUSERR; + } + reg.pc += 2; + return bd.Data(); +} -template uint32 -m68030_fetch_16_mmu(m68kcpu *cpu) +// reg.pc からロングワード(ミスアライン可)をフェッチし reg.pc を進める。 +uint32 +MPU68030Device::fetch_4() { - uint64 data; - - data = FETCH(16); - // XXX バスエラー時に PC をインクリメントするタイミングは? - RegPC += 2; + busaddr addr = busaddr(reg.pc) | fc_inst | BusAddr::Size4; - return data; + // まず可能ならキャッシュから探す。 + m68030Cache *cache = icache.get(); + if (__predict_true(cache->enable)) { + uint32 tag = (addr.Get() >> 4) & 0xfffffff0; + uint32 idx = (reg.pc & 0x000000f0) >> 4; + uint32 sel = (reg.pc & 0x0000000c) >> 2; + m68030CacheLine *l = &cache->line[idx]; + + if (__predict_true(l->tag == tag && l->valid[sel])) { + uint32 data; + if ((reg.pc & 2) == 0) { + cache->stat_fasthit++; + data = l->data[sel]; + } else { + // 次のエントリにまたがる場合。 + if (sel < 3 && l->valid[sel + 1]) { + cache->stat_fasthit += 2; + data = l->data[sel] << 16; + data |= l->data[sel + 1] >> 16; + } else { + // 片方でキャッシュミスした。 + // この場合は fast* はカウントしない。 + goto fetch; + } + } + reg.pc += 4; + return data; + } + } - buserr: - SET_SSW(cpu, use_mmu, SSW_SIZE_WORD); - throw M68K_EXCEP_BUSERR; + fetch: + busdata bd; + if (__predict_true(cache->enable)) { + bd = read_cache(cache, addr); + } else { + bd = read_nocache(addr); + } + if (__predict_false(bd.IsBusErr())) { + bus.size = addr.GetSize(); + throw M68K::EXCEP_BUSERR; + } + reg.pc += 4; + return bd.Data(); } -template uint32 -m68030_fetch_32_mmu(m68kcpu *cpu) +// read_1,2,4() の共通部分。ミスアライン可。 +// バスエラーが起きると例外 M68K::EXCEP_BUSERR をスローする。 +uint32 +MPU68030Device::read_data(busaddr addr) { - uint64 data; - - if ((cpu->bus.laddr & 3) == 0) { - data = FETCH(32); + m68030Cache *cache = dcache.get(); + busdata data; + if (__predict_true(cache->enable)) { + data = read_cache(cache, addr); } else { - uint64 h, l; - - h = FETCH(16); - cpu->bus.laddr += 2; - - l = FETCH(16); - data = (h << 16) | l; + data = read_nocache(addr); } - // XXX バスエラー時に PC をインクリメントするタイミングは? - RegPC += 4; - return data; - - buserr: - SET_SSW(cpu, use_mmu, SSW_SIZE_LONG); - throw M68K_EXCEP_BUSERR; + if (__predict_false(data.IsBusErr())) { + bus.size = addr.GetSize(); + throw M68K::EXCEP_BUSERR; + } + return data.Data(); } -template uint32 -m68030_read_8_mmu(m68kcpu *cpu) +// write_1,2,4() の共通部分。ミスアライン可。 +// バスエラーが起きると例外 M68K::EXCEP_BUSERR をスローする。 +void +MPU68030Device::write_data(busaddr addr, uint32 data) { - uint64 data; - - data = READ(8); - - if (1) - cpulog(2, "m68030_read_8 %08X -> %02X", cpu->bus.laddr, (uint32)data); - return data; - - buserr: - SET_SSW(cpu, use_mmu, SSW_SIZE_BYTE); - throw M68K_EXCEP_BUSERR; + busdata r = write_bus(addr, data); + if (__predict_false(r.IsBusErr())) { + bus.size = addr.GetSize(); + throw M68K::EXCEP_BUSERR; + } } -template uint32 -m68030_read_16_mmu(m68kcpu *cpu) -{ - uint64 data; - - if ((cpu->bus.laddr & 1) == 0) { - data = READ(16); - } else { - uint64 h, l; +// キャッシュ無効時の読み込み。ミスアライン可。 +// バスエラーも busdata を返す。 +busdata +MPU68030Device::read_nocache(busaddr addr) +{ + // デバイスは、addr の下位ビットによらず、自身のデバイスのポートサイズ分 + // のデータと、その応答サイズ(自分のポートサイズ) を Size フィールドに + // セットして返してくる。 + // + // MPU(68030) は、A1-A0、残りバイト数、デバイスの応答サイズの情報から + // 受け取ったデータバスのうち適切なところを切り出して使う。 + + uint32 data = 0; + for (;;) { + bus.laddr = addr; + bus.paddr = addr; + if (__predict_false(TranslateRead() == false)) { + return BusData::BusErr; + } + busdata bd = read_phys(); + if (__predict_false(bd.IsBusErr())) { + return bd; + } - h = READ(8); - cpu->bus.laddr += 1; + uint32 reqsize = addr.GetSize(); + // デバイスのポートサイズ(応答サイズ) + uint32 portsize = bd.GetSize(); + // 今回のアドレスオフセット + uint32 offset = addr.Addr() & (portsize - 1); + uint32 datasize = portsize - offset; + uint32 tmp = bd.Data(); + // 下詰めされたデータに対するマスク + uint32 mask = 0xffffffffU >> ((4 - datasize) * 8); + // 今回必要なところ + tmp &= mask; + if (datasize == reqsize) { + data |= tmp; + break; + } else if (datasize > reqsize) { + tmp >>= (datasize - reqsize) * 8; + data |= tmp; + break; + } else { + tmp <<= (reqsize - datasize) * 8; + data |= tmp; + } - l = READ(8); - data = (h << 8) | l; + addr += datasize; + reqsize -= datasize; + addr.ChangeSize(reqsize); } - - if (1) - cpulog(2, "m68030_read_16 %08X -> %04X", cpu->bus.laddr, (uint32)data); return data; - - buserr: - SET_SSW(cpu, use_mmu, SSW_SIZE_WORD); - throw M68K_EXCEP_BUSERR; } -template uint32 -m68030_read_32_mmu(m68kcpu *cpu) +// offset [0, 4) から size [1, 4] バイトのマスクを作成する。 +// マスクは +0, +1, +2, +3 バイト目がビット 3, 2, 1, 0 に対応している。 +static inline uint32 +OPERMASK(uint32 offset, uint32 size) { - uint64 data; - - if ((cpu->bus.laddr & 3) == 0) { - data = READ(32); - } else if ((cpu->bus.laddr & 1) == 0) { - uint64 h, l; - - h = READ(16); - cpu->bus.laddr += 2; - - l = READ(16); - data = (h << 16) | l; - } else { - uint64 h, m, l; + return ((1U << size) - 1) << (4 - offset - size); +} - h = READ(8); - cpu->bus.laddr += 1; +// キャッシュ有効時の読み込み。ミスアライン可。 +// バスエラーも busdata を返す。 +busdata +MPU68030Device::read_cache(m68030Cache *cache, busaddr addr) +{ + putlog(2, "read_cache $%u.%08x.%c", + addr.GetFC(), addr.Addr(), "0BW3L"[addr.GetSize()]); + + uint32 data = 0; + bus.ci = false; + for (;;) { + uint32 entry_data = 0; + + // addr から今回対象とするロングワードエントリに対するマスクを作成。 + uint32 reqoffset = addr.Addr() & 3; + uint32 reqsize = addr.GetSize(); + uint32 opersize = std::min(reqsize, 4 - reqoffset); + + // まず可能ならキャッシュから探す。 + uint32 tag = (addr.Get() >> 4) & 0xfffffff0; + uint32 idx = (addr.Addr() & 0x000000f0) >> 4; + uint32 sel = (addr.Addr() & 0x0000000c) >> 2; + m68030CacheLine *l = &cache->line[idx]; + if (__predict_true(l->tag == tag && l->valid[sel])) { + cache->stat_hit++; + entry_data = l->data[sel]; + putlog(2, " entry_data=$%08x (match)", entry_data); + } else { + cache->stat_miss++; - m = READ(16); - cpu->bus.laddr += 2; + // キャッシュになければバスアクセス。 + bus.laddr = addr; + bus.paddr = addr; + if (__predict_false(TranslateRead() == false)) { + putlog(2, " TranslateRead BusErr"); + return BusData::BusErr; + } + putlog(2, " paddr=$%08x.%c", + bus.paddr.Addr(), "0BW3L"[bus.paddr.GetSize()]); + + busdata bd; + if (__predict_true(cache->freeze == false && bus.ci == false)) { + // キャッシュ可。 + if (l->tag != tag) { + // タグが違う場合、出来ればバーストリードを使う。 + // XXX: タグが同じでもエントリがすべて無効ならバーストリード + // するのだが、パフォーマンス落ちるので保留。 + // TODO: 要求オペランドがラインをまたぐ場合の1エントリ目は + // バースト転送しない。 + if (__predict_true(cache->burst_enable) && + __predict_true(read_phys_burst(&l->data[0]))) + { + l->tag = tag; + l->valid_all = -1; + entry_data = l->data[sel]; + putlog(2, " entry_data=$%08x (burst)", entry_data); + } else { + // シングル充填を4回。 + uint32 opermask = OPERMASK(reqoffset, opersize); + bd = read_line(l, tag, opermask); + if (__predict_false(bd.IsBusErr())) { + return bd; + } + entry_data = bd.Data(); + putlog(2, " entry_data=$%08x (line)", entry_data); + } + } else { + // このエントリだけ充填。 + bd = read_phys(); + if (__predict_false(bd.IsBusErr())) { + return bd; + } + if (__predict_false(bus.ci)) { + goto nofill; + } + if (__predict_false(bd.GetSize() != 4)) { + uint32 opermask = OPERMASK(reqoffset, opersize); + bd = read_single(bd, 0xf, opermask); + if (__predict_false(bd.IsBusErr())) { + return bd; + } + } + l->valid[sel] = 1; + l->data[sel] = bd.Data(); + entry_data = bd.Data(); + } + } else { + // キャッシュに充填しないので、要求サイズだけ読み出す。 + bd = read_phys(); + if (__predict_false(bd.IsBusErr())) { + return bd; + } + nofill: + if (__predict_false(bd.GetSize() != 4)) { + uint32 opermask = OPERMASK(reqoffset, opersize); + bd = read_single(bd, opermask, opermask); + if (__predict_false(bd.IsBusErr())) { + return bd; + } + } + entry_data = bd.Data(); + } + } - l = READ(8); - data = (h << 24) | (m << 8) | l; + data <<= opersize * 8; + // このエントリから必要な部分を抜き出す。 + uint32 mask = 0xffffffffU >> ((4 - opersize) * 8); + if (reqsize == opersize) { + // 要求サイズがこのエントリをはみ出していない。 + entry_data >>= (4 - reqoffset - opersize) * 8; + data |= entry_data & mask; + putlog(2, " data=$%0*x (done)", opersize * 2, data); + break; + } else { + data |= entry_data & mask; + putlog(2, " data=$%0*x", opersize * 2, data); + } + addr += opersize; + addr.ChangeSize(reqsize - opersize); } - if (1) - cpulog(2, "m68030_read_32 %08X -> %08X", cpu->bus.laddr, (uint32)data); return data; - - buserr: - SET_SSW(cpu, use_mmu, SSW_SIZE_LONG); - throw M68K_EXCEP_BUSERR; } -template void -m68030_write_8_mmu(m68kcpu *cpu) -{ - uint32 data = cpu->bus.data; +// シングル充填によるライン充填。 +// 要求オペランドが2エントリ目にかかっていても、バスエラーを起こさないようだ。 +busdata +MPU68030Device::read_line(m68030CacheLine *l, uint32 tag, uint32 opermask) +{ + uint32 sel = (bus.paddr.Addr() & 0x0c) >> 2; + + // 1エントリ目。 + busdata bd = read_phys(); + if (__predict_false(bd.IsBusErr())) { + // 初手は必ず要求オペランドなのでバスエラーは発報。 + return bd; + } + if (__predict_false(bd.GetSize() != 4)) { + // このエントリの残りを埋める。 + bd = read_single(bd, 0xf, opermask); + if (__predict_false(bd.IsBusErr())) { + return bd; + } + } + if (__predict_false(bus.ci)) { + // CI ならキャッシュを更新せず、現状のデータを返す。 + // 要求オペランド外でのバスエラーもここに来る。 + return bd.Data(); + } + // そうでなければ、ここでタグとエントリを更新。 + l->tag = tag; + l->valid_all = 0; + l->valid[sel] = 1; + l->data[sel] = bd.Data(); + + // 2エントリ目以降は充填してもしなくても1エントリ目のこれが戻り値。 + uint32 data = bd.Data(); + + // 2エントリ目以降。 + // 要求オペランドが2エントリ目にまたがっていても、 + // 2エントリ目以降はバスエラーを起こさないらしい。 + for (int i = 1; i < 4; i++) { + // アドレスを更新。 + if (++sel >= 4) { + sel = 0; + } + bus.paddr.ChangeAddr((bus.paddr.Addr() & ~0xf) | (sel << 2)); + bus.paddr.ChangeSize(4); - if (1) - cpulog(2, "m68030_write_8 %08X <- %02X", cpu->bus.laddr, data); + bd = read_phys(); + if (__predict_false(bd.IsBusErr())) { + break; + } + if (__predict_false(bd.GetSize() != 4)) { + // このエントリの残りを埋める。 + bd = read_single(bd, 0xf, 0); + if (__predict_false(bd.IsBusErr())) { + // バスエラーならエントリを無効にして充填をアボート。 + // エントリはまだ有効にしていないので何もしなくていい。 + break; + } + } + if (__predict_false(bus.ci)) { + // CI は1ライン中で変化しないと思うが、 + // 要求オペランド外でのバスエラーもここに来る。 + break; + } - WRITE(8, data); - return; + l->valid[sel] = 1; + l->data[sel] = bd.Data(); + } - buserr: - SET_SSW(cpu, use_mmu, SSW_SIZE_BYTE); - throw M68K_EXCEP_BUSERR; + return data; } -template void -m68030_write_16_mmu(m68kcpu *cpu) -{ - uint32 data = cpu->bus.data; - - if (1) - cpulog(2, "m68030_write_16 %08X <- %04X", cpu->bus.laddr, data); +// シングル充填の残りの読み出し。 +// まず bus.paddr を1回読んでみて、ロングワードポートならそのまま使える +// (し、大抵はこのケースなので早期に抜けたい) のでここには来ない。 +// ロングワードでない時だけここへ来て残りを埋める、という分担になっている。 +// +// readmask は読み込みを行うバイト (キャッシュ充填なら %1111)。 +// opermask はバスエラーを起こすバイト。 +// readmask、opermask はともに下位の 4 ビットで、 +// ビット 3, 2, 1, 0 がそれぞれ +0, +1, +2, +3 バイト目を示す。 +// +// bd = read_phys(); +// if (BusErr) ...; +// if (bd.GetSize() != 4) { +// bd = read_single(bd, readmask, opermask); +// : +// } +// if (bus.ci) ...; +// のように呼ぶ。 +// +// 応答は、 +// a) 成功すれば、readmask で示した読み出しバイトをロングワードで返す。 +// 下詰めではなくロングワード境界からの所定のバイト位置。 +// b) opermask で示したバイトでバスエラーが起きれば、その場で処理を中断して +// BusErr を返す (上位に発報するため)。 +// c) opermask で示したバイト以外でバスエラーが起きれば、その場で処理を中断 +// し bus.ci を立てて、現状のデータを返す (エントリを更新しないため)。 +// d) バスアクセスで CI が立てば、読み込んだデータは使うがエントリは更新しない +// ので、これは c) と同じ。 +busdata +MPU68030Device::read_single(busdata bd, uint32 readmask, uint32 opermask) +{ + uint32 data = 0; + uint32 portsize; + + // この時点で bd は一回目の読み込み後で、 + // バスエラーは起きておらず、ロングワードポートでなかった。 + portsize = bd.GetSize(); + + uint32 offset = bus.paddr.Addr() & 3; + for (;;) { + // bd.Data をエントリの所定の位置に移動。 + // set には今回読めたところのビットを立てていく。 + uint32 set; + if (portsize == 2) { + uint32 off2 = offset & 2; + data |= bd.Data() << ((2 - off2) * 8); + set = 0x3 << (2 - off2); + // 次の読み込みにむけて更新。 + offset = 2 - off2; + } else { + data |= bd.Data() << ((3 - offset) * 8); + set = 0x1 << (3 - offset); + // 次の読み込みにむけて更新。 + offset = (offset + 1) & 3; + } - if ((cpu->bus.laddr & 1) == 0) { - WRITE(16, data); - } else { - WRITE(8, data >> 8); + // readmask を全部下ろせたら終了。 + readmask &= ~set; + if (readmask == 0) { + break; + } - cpu->bus.laddr += 1; - WRITE(8, data & 0xff); + // 次の読み込み。 + // 同一エントリ内なので paddr の更新だけでいい。 + bus.paddr.ChangeAddr((bus.paddr.Addr() & ~3U) | offset); + bus.paddr.ChangeSize(__builtin_popcount(readmask)); + + bd = read_phys(); + portsize = bd.GetSize(); + if (__predict_false(bd.IsBusErr())) { + if ((set & opermask) != 0) { + // ターゲットオペランドと重なっていれば即バスエラー。 + return BusData::BusErr; + } else { + // オペランド外の充填中なら CI と同じパスになる。 + bus.ci = true; + break; + } + } } - return; - buserr: - SET_SSW(cpu, use_mmu, SSW_SIZE_WORD); - throw M68K_EXCEP_BUSERR; + return data; } -template void -m68030_write_32_mmu(m68kcpu *cpu) -{ - uint32 data = cpu->bus.data; - - if (1) - cpulog(2, "m68030_write_32 %08X <- %08X", cpu->bus.laddr, data); +// キャッシュ/バスへの書き込み。ミスアライン可。 +// バスエラーも busdata を返す。 +busdata +MPU68030Device::write_bus(busaddr addr, uint32 data) +{ + m68030Cache *cache = dcache.get(); + + putlog(2, "write_bus %u.%08x sz=%u data=$%0*x", + addr.GetFC(), addr.Addr(), addr.GetSize(), addr.GetSize() * 2, data); + for (;;) { + uint32 nextdata; + uint32 reqoffset = addr.Addr() & 3; + uint32 reqsize = addr.GetSize(); + + // ロングワード境界をはみ出す場合は持ち越し分を覚えておく。 + int nextsize = reqoffset + reqsize - 4; + if (nextsize > 0) { + // 後ろにはみ出すケース。 + nextdata = data & ((1U << (nextsize * 8)) - 1); + data >>= nextsize * 8; + reqsize -= nextsize; + addr.ChangeSize(reqsize); + } else { + nextsize = 0; + } - if ((cpu->bus.laddr & 3) == 0) { - WRITE(32, data); - } else if ((cpu->bus.laddr & 1) == 0) { - WRITE(16, data >> 16); + // まず外部バスを駆動して CI を得る。 + // バスエラーなら記録だけしておく。キャッシュへは書き込むため。 + busdata bd = BusData::BusErr; + bus.ci = false; + bus.laddr = addr; + bus.paddr = addr; + if (__predict_true(TranslateWrite())) { + uint32 outdata = data; + for (;;) { + bd = write_phys(outdata); + if (__predict_false(bd.IsBusErr())) { + putlog(2, " write_phys -> BusErr"); + break; + } + uint32 breqsize = bus.paddr.GetSize(); + putlog(2, " write_phys $%08x data=$%0*x", + bus.paddr.Addr(), breqsize * 2, outdata); + uint32 portsize = bd.GetSize(); + uint32 offset = bus.paddr.Addr() & (portsize - 1); + uint32 datasize = portsize - offset; + if (datasize >= breqsize) { + break; + } + // 同一ロングワード内なので bus.paddr の更新だけでいい。 + bus.paddr += datasize; + breqsize -= datasize; + bus.paddr.ChangeSize(breqsize); + // データは下詰めで、不要な上位ビットはクリアする仕様。 + outdata &= (1U << (breqsize * 8)) - 1; + } + } - cpu->bus.laddr += 2; - WRITE(16, data & 0xffff); - } else { - WRITE(8, data >> 24); + bool cache_writable = (cache->enable && bus.ci == false); + if (cache_writable) { + uint32 tag = (addr.Get() >> 4) & 0xfffffff0; + uint32 idx = (addr.Addr() & 0x000000f0) >> 4; + uint32 sel = (addr.Addr() & 0x0000000c) >> 2; + auto *l = &cache->line[idx]; + + // Hit Frz WA Tag LA + // 1 0 - - - : UpdateEntry + // 1 1 - - - : UpdateEntry (ex1, ex2のb8-b9 + // 0 1 - - - : Nop + // 0 0 0 - - : Nop + // 0 0 1 1 1 : (ex3 + // 0 0 1 1 0 : Nop (ex2のb6-b7 + // 0 0 1 0 1 : (ex4 + // 0 0 1 0 0 : (ex5 + bool tag_match = (l->tag == tag); + bool cache_hit = (tag_match && l->valid[sel]); + if (cache_hit) { + // エントリの更新。 + // reqsize はロングワード境界を超えないよう調整済み。 + // data は下詰め(上はクリア)なので、所定の位置まで左シフト。 + // 整列ロングワードもここに来ることに注意。 + // Fig.6-4 の ex1, ex2のb8-b9 + int lshift = (4 - reqoffset - reqsize) * 8; + uint32 mask = (0xffffffffU >> ((4 - reqsize) * 8)) << lshift; + l->data[sel] = (data << lshift) | (l->data[sel] & ~mask); + } else if (cache->freeze == false && cache->wa) { + bool long_aligned = (reqoffset == 0 && reqsize == 4); + // Fig.6-4 + if (tag_match) { + if (long_aligned) { + // ex3 + l->valid[sel] = 1; + l->data[sel] = data; + } else { + // ex2 の b6-b7 + // nop + } + } else { + if (long_aligned) { + // ex4 + l->tag = tag; + l->valid_all = 0; + l->valid[sel] = 1; + l->data[sel] = data; + } else { + // ex5 + l->valid[sel] = 0; + } + } + } + } - cpu->bus.laddr += 1; - WRITE(16, (data >> 8) & 0xffff); + // バスアクセスでバスエラーが出たらキャッシュ更新後に発報。 + if (__predict_false(bd.IsBusErr())) { + return bd; + } - cpu->bus.laddr += 2; - WRITE(8, data & 0xff); + // ロングワード境界をまたいだ余りがあればもう一回。 + if (nextsize == 0) { + break; + } + putlog(2, " nextsize=%u", nextsize); + addr += addr.GetSize(); + addr.ChangeSize(nextsize); + data = nextdata; } - return; - buserr: - SET_SSW(cpu, use_mmu, SSW_SIZE_LONG); - throw M68K_EXCEP_BUSERR; + return 0; } -// m68030_{fetch,read,write}_NN() と m68030_{read,write}_NN_fc() の共通部分。 -// fetch には FC 指定版はないので分ける必要は本来ないけど対称性のため。 -// cpu->bus に必要な情報はセットされている。 -// _bus 接尾辞はバスアクセスというより bus 構造体を使ったアクセスという感じ。 - -static inline uint32 -m68030_fetch_16_bus(m68kcpu *cpu) +// mainbus の bus.paddr から指定サイズで1回読み込む。 +// ウェイトはこちらで加算して、バスエラーも含めた bd を返す。 +busdata +MPU680x0Device::read_phys() { - if (cpu->mmu_enable) { - return m68030_fetch_16_mmu(cpu); - } else { - return m68030_fetch_16_mmu(cpu); - } + busaddr paddr = bus.paddr; + busdata bd = mainbus->Read(paddr); + buswait += bd.GetWait(); + return bd; } -static inline uint32 -m68030_fetch_32_bus(m68kcpu *cpu) +// mainbus の bus.paddr に指定サイズで1回書き込む。 +// ウェイトはこちらで加算して、バスエラーも含めた bd を返す。 +busdata +MPU680x0Device::write_phys(uint32 data) { - if (cpu->mmu_enable) { - return m68030_fetch_32_mmu(cpu); - } else { - return m68030_fetch_32_mmu(cpu); - } + busaddr paddr = bus.paddr; + busdata bd = mainbus->Write(paddr, data); + buswait += bd.GetWait(); + return bd; } -static uint32 -m68030_read_8_bus(m68kcpu *cpu) +// バースト転送。 +// bus.paddr の下位4ビットは無視する。 +// addr がバースト転送可能なら dst に 16 バイト転送して true を返す。 +// addr がバースト転送不可なら dst は触らず false を返す。 +// VM (バス) がバースト転送対応している場合のみ呼ぶこと。 +bool +MPU680x0Device::read_phys_burst(uint32 *dst) { -if(1)cpulog(2, "%s $%08x", __func__, cpu->bus.laddr); - if (cpu->mmu_enable) { - return m68030_read_8_mmu(cpu); - } else { - return m68030_read_8_mmu(cpu); - } -} + busaddr paddr = bus.paddr; + paddr &= 0xfffffff0U; + busdata bd = mainbus->ReadBurst16(paddr, dst); + buswait += bd.GetWait(); -static uint32 -m68030_read_16_bus(m68kcpu *cpu) -{ -if(1)cpulog(2, "%s $%08x", __func__, cpu->bus.laddr); - if (cpu->mmu_enable) { - return m68030_read_16_mmu(cpu); - } else { - return m68030_read_16_mmu(cpu); - } + // BusErr ビットをバースト転送不可フラグとして使っている。 + return !bd.IsBusErr(); } -static uint32 -m68030_read_32_bus(m68kcpu *cpu) +// 物理メモリからのロングワード読み込み。 +// 現在の bus に影響を与えず指定のアドレスを読み出す。MMU 専用。 +busdata +MPU680x0Device::read_phys_4(uint32 addr) { -if(1)cpulog(2, "%s $%08x", __func__, cpu->bus.laddr); - if (cpu->mmu_enable) { - return m68030_read_32_mmu(cpu); - } else { - return m68030_read_32_mmu(cpu); - } -} + m68kbus backup; + busdata bd; -static void -m68030_write_8_bus(m68kcpu *cpu) -{ -if(1)cpulog(2, "%s $%08x", __func__, cpu->bus.laddr); - if (cpu->mmu_enable) { - m68030_write_8_mmu(cpu); - } else { - m68030_write_8_mmu(cpu); - } -} + // バス情報を一旦退避 + backup = bus; -static void -m68030_write_16_bus(m68kcpu *cpu) -{ -if(1)cpulog(2, "%s $%08x", __func__, cpu->bus.laddr); - if (cpu->mmu_enable) { - m68030_write_16_mmu(cpu); - } else { - m68030_write_16_mmu(cpu); + // 物理バスアクセス + bus.laddr = busaddr(addr) | BusAddr::SRead | BusAddr::Size4; + bus.paddr = bus.laddr; + bd = read_phys(); + if (__predict_false(bd.IsBusErr())) { + bus.size = 4; + return bd; } + + // バス情報を復元 + bus = backup; + return bd; } -static void -m68030_write_32_bus(m68kcpu *cpu) +// 物理メモリへのロングワード書き込み。 +// 現在の bus に影響を与えず指定のアドレスに書き出す。MMU 専用。 +busdata +MPU680x0Device::write_phys_4(uint32 addr, uint32 data) { -if(1)cpulog(2, "%s $%08x", __func__, cpu->bus.laddr); - if (cpu->mmu_enable) { - m68030_write_32_mmu(cpu); - } else { - m68030_write_32_mmu(cpu); - } -} + m68kbus backup; + busdata bd; -// -// CPU 内部からのメモリアクセスは常にこの8つ (またはこれを呼び出すヘルパー -// 関数) を呼ぶこと。 -// uint32 m68030_fetch_{16,32}(cpu) -// uint32 m68030_read_{8,16,32}(cpu, laddr) -// void m68030_write_{8,16,32}(cpu, laddr, data) -// + // バス情報を一旦退避 + backup = bus; -uint32 -m68030_fetch_16(m68kcpu *cpu) -{ - cpu->bus.ssw = SSW_BUS_R | cpu->bus.fc2 | FC_PROG; - cpu->bus.laddr = RegPC; - return m68030_fetch_16_bus(cpu); -} + // 物理バスアクセス + bus.laddr = busaddr(addr) | BusAddr::SWrite | BusAddr::Size4; + bus.paddr = bus.laddr; + bd = write_phys(data); + if (__predict_false(bd.IsBusErr())) { + bus.size = 4; + return bd; + } -uint32 -m68030_fetch_32(m68kcpu *cpu) -{ - cpu->bus.ssw = SSW_BUS_R | cpu->bus.fc2 | FC_PROG; - cpu->bus.laddr = RegPC; - return m68030_fetch_32_bus(cpu); + // バス情報を復元 + bus = backup; + return bd; } -uint32 -m68030_read_8(m68kcpu *cpu, uint32 addr) +// CACR レジスタ書き込み。 +void +MPU68030Device::SetCACR(uint32 newval) { - cpu->bus.ssw = SSW_BUS_R | cpu->bus.fc2 | FC_DATA; - cpu->bus.laddr = addr; - return m68030_read_8_bus(cpu); -} + newval &= M68030::CACR_MASK; + uint32 oldval = reg.cacr; + uint32 chgval = oldval ^ newval; + + // EI (命令キャッシュ有効) + if ((chgval & M68030::CACR_EI) != 0) { + if ((newval & M68030::CACR_EI) != 0) { + // 命令キャッシュ有効 + icache->enable = true; + icache->hist.Clear(); + cycle_table = cycle_table_030cc; + putlog(1, "I-Cache enabled"); + } else { + // 命令キャッシュ無効 + icache->enable = false; + cycle_table = cycle_table_030ncc; + putlog(1, "I-Cache disabled"); + } + } -uint32 -m68030_read_16(m68kcpu *cpu, uint32 addr) -{ - cpu->bus.ssw = SSW_BUS_R | cpu->bus.fc2 | FC_DATA; - cpu->bus.laddr = addr; - return m68030_read_16_bus(cpu); -} + // FI (命令キャッシュ凍結) + if ((chgval & M68030::CACR_FI) != 0) { + putlog(0, "CACR:FI Not Implemented"); + icache->freeze = (newval & M68030::CACR_FI); + } + + // CEI (命令キャッシュの指定エントリをクリア) + if ((chgval & M68030::CACR_CEI) != 0) { + // CAAR の b7-2 で示されるロングワードを無効にする。 + uint32 idx = (reg.caar & 0x000000f0) >> 4; + uint32 sel = (reg.caar & 0x0000000c) >> 2; + auto& l = icache->line[idx]; + l.valid[sel] = 0; + + newval &= ~M68030::CACR_CEI; + } + + // CI (命令キャッシュクリア) + if ((chgval & M68030::CACR_CI) != 0) { + for (auto& l : icache->line) { + l.valid_all = 0; + } -uint32 -m68030_read_32(m68kcpu *cpu, uint32 addr) -{ - cpu->bus.ssw = SSW_BUS_R | cpu->bus.fc2 | FC_DATA; - cpu->bus.laddr = addr; - return m68030_read_32_bus(cpu); -} + newval &= ~M68030::CACR_CI; + } -void -m68030_write_8(m68kcpu *cpu, uint32 addr, uint32 data) -{ - cpu->bus.ssw = SSW_BUS_W | cpu->bus.fc2 | FC_DATA; - cpu->bus.laddr = addr; - cpu->bus.data = data; - m68030_write_8_bus(cpu); -} + // IBE (命令バースト有効) + if ((chgval & M68030::CACR_IBE) != 0) { + icache->burst_enable = (newval & M68030::CACR_IBE) && has_burst_access; + } -void -m68030_write_16(m68kcpu *cpu, uint32 addr, uint32 data) -{ - cpu->bus.ssw = SSW_BUS_W | cpu->bus.fc2 | FC_DATA; - cpu->bus.laddr = addr; - cpu->bus.data = data; - m68030_write_16_bus(cpu); -} + // ED (データキャッシュ有効) + if ((chgval & M68030::CACR_ED) != 0) { + if ((newval & M68030::CACR_ED) != 0) { + // データキャッシュ有効。 + dcache->enable = true; + dcache->hist.Clear(); + putlog(1, "D-Cache enabled"); + } else { + // データキャッシュ無効。 + dcache->enable = false; + putlog(1, "D-Cache disabled"); + } + } -void -m68030_write_32(m68kcpu *cpu, uint32 addr, uint32 data) -{ - cpu->bus.ssw = SSW_BUS_W | cpu->bus.fc2 | FC_DATA; - cpu->bus.laddr = addr; - cpu->bus.data = data; - m68030_write_32_bus(cpu); -} + // FD (データキャッシュ凍結) + if ((chgval & M68030::CACR_FD) != 0) { + dcache->freeze = (newval & M68030::CACR_FD); + putlog(1, "CACR:FD -> %u", dcache->freeze); + } + + // CED (データキャッシュの指定エントリをクリア) + if ((chgval & M68030::CACR_CED) != 0) { + // CAAR の b7-2 で示されるロングワードを無効にする。 + uint32 idx = (reg.caar & 0x000000f0) >> 4; + uint32 sel = (reg.caar & 0x0000000c) >> 2; + auto& l = dcache->line[idx]; + l.valid[sel] = 0; + + newval &= ~M68030::CACR_CED; + } + + // CD (データキャッシュクリア) + if ((chgval & M68030::CACR_CD) != 0) { + for (auto& l : dcache->line) { + l.valid_all = 0; + } -// -// MOVES 用の FC 指定版。 -// 通常アクセスが現在の FC 空間に対してだが、 -// こちらは SFC/DFC で指定された FC 空間に対してアクセスする。 -// + newval &= ~M68030::CACR_CD; + } -uint32 -m68030_read_8_fc(m68kcpu *cpu, uint32 addr) -{ - cpu->bus.ssw = SSW_BUS_R | RegSFC; - cpu->bus.laddr = addr; - return m68030_read_8_bus(cpu); -} + // DBE (データバースト有効) + if ((chgval & M68030::CACR_DBE) != 0) { + dcache->burst_enable = (newval & M68030::CACR_DBE) && has_burst_access; + } -uint32 -m68030_read_16_fc(m68kcpu *cpu, uint32 addr) -{ - cpu->bus.ssw = SSW_BUS_R | RegSFC; - cpu->bus.laddr = addr; - return m68030_read_16_bus(cpu); -} + // WA (ライトアロケート) + if ((chgval & M68030::CACR_WA) != 0) { + dcache->wa = (newval & M68030::CACR_WA); + } -uint32 -m68030_read_32_fc(m68kcpu *cpu, uint32 addr) -{ - cpu->bus.ssw = SSW_BUS_R | RegSFC; - cpu->bus.laddr = addr; - return m68030_read_32_bus(cpu); + // とりあえずレジスタイメージも保存。 + reg.cacr = newval; } +// リセット例外の CPU 固有部分。 void -m68030_write_8_fc(m68kcpu *cpu, uint32 addr, uint32 data) +MPU68030Device::ResetCache() { - cpu->bus.ssw = SSW_BUS_W | RegDFC; - cpu->bus.laddr = addr; - cpu->bus.data = data; - m68030_write_8_bus(cpu); + SetCACR(M68030::CACR_CD | M68030::CACR_CI); + icache->hist.Clear(); + dcache->hist.Clear(); } +// キャッシュの統計情報を計算。 void -m68030_write_16_fc(m68kcpu *cpu, uint32 addr, uint32 data) +m68030Cache::CalcStat() { - cpu->bus.ssw = SSW_BUS_W | RegDFC; - cpu->bus.laddr = addr; - cpu->bus.data = data; - m68030_write_16_bus(cpu); -} + m68030CacheStat s; -void -m68030_write_32_fc(m68kcpu *cpu, uint32 addr, uint32 data) -{ - cpu->bus.ssw = SSW_BUS_W | RegDFC; - cpu->bus.laddr = addr; - cpu->bus.data = data; - m68030_write_32_bus(cpu); + memset(&s, 0, sizeof(s)); + + // fasthit はファストパスでヒットした回数。 + uint32 total_read = stat_fasthit + stat_hit + stat_miss; + if (__predict_true(total_read != 0)) { + s.hit = (float)(stat_fasthit + stat_hit) * 100 / total_read; + stat_fasthit = 0; + stat_hit = 0; + stat_miss = 0; + } + + // 前回から今回までの間に disable になったのなら有効な値がある。 + if (__predict_true(enable || total_read != 0)) { + // ここはロックする。 + std::unique_lock lock(hist_mtx); + hist.EnqueueForce(s); + } }