|
|
nono 1.7.0
//
// nono
// Copyright (C) 2020 nono project
// Licensed under nono-license.txt
//
#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 にある
// 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 = (addr.Addr() >> 4) & 0x0000000f;
uint32 sel = (addr.Addr() >> 2) & 0x00000003;
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;
}
}
busdata bd;
if (__predict_true(cache->enable)) {
bd = read_cache(cache, addr);
} else {
bd = read_nocache(addr);
}
if (__predict_false(bd.IsBusErr())) {
throw M68K::EXCEP_BUSERR;
}
reg.pc += 2;
return bd.Data();
}
// reg.pc からロングワード(ミスアライン可)をフェッチし reg.pc を進める。
uint32
MPU68030Device::fetch_4()
{
busaddr addr = busaddr(reg.pc) | fc_inst | BusAddr::Size4;
// まず可能ならキャッシュから探す。
m68030Cache *cache = icache.get();
if (__predict_true(cache->enable)) {
uint32 tag = (addr.Get() >> 4) & 0xfffffff0;
uint32 idx = (addr.Addr() >> 4) & 0x0000000f;
uint32 sel = (addr.Addr() >> 2) & 0x00000003;
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;
}
}
fetch:
busdata bd;
if (__predict_true(cache->enable)) {
bd = read_cache(cache, addr);
} else {
bd = read_nocache(addr);
}
if (__predict_false(bd.IsBusErr())) {
throw M68K::EXCEP_BUSERR;
}
reg.pc += 4;
return bd.Data();
}
// read_1,2,4() の共通部分。ミスアライン可。
// バスエラーが起きると例外 M68K::EXCEP_BUSERR をスローする。
uint32
MPU68030Device::read_data(busaddr addr)
{
m68030Cache *cache = dcache.get();
busdata data;
if (__predict_true(cache->enable)) {
data = read_cache(cache, addr);
} else {
data = read_nocache(addr);
}
if (__predict_false(data.IsBusErr())) {
throw M68K::EXCEP_BUSERR;
}
return data.Data();
}
// write_1,2,4() の共通部分。ミスアライン可。
// バスエラーが起きると例外 M68K::EXCEP_BUSERR をスローする。
void
MPU68030Device::write_data(busaddr addr, uint32 data)
{
busdata r = write_bus(addr, data);
if (__predict_false(r.IsBusErr())) {
throw M68K::EXCEP_BUSERR;
}
}
// キャッシュ無効時の読み込み。ミスアライン可。
// バスエラーも busdata を返す。
busdata
MPU68030Device::read_nocache(busaddr addr)
{
// デバイスは、addr の下位ビットによらず、自身のデバイスのポートサイズ分
// のデータと、その応答サイズ(自分のポートサイズ) を Size フィールドに
// セットして返してくる。
//
// MPU(68030) は、A1-A0、残りバイト数、デバイスの応答サイズの情報から
// 受け取ったデータバスのうち適切なところを切り出して使う。
putlog(3, "read_nocache %u.%08x sz=%c",
addr.GetFC(), addr.Addr(), "0BW3L"[addr.GetSize()]);
uint32 data = 0;
for (;;) {
bus.laddr = addr;
busaddr paddr = TranslateRead(addr);
if (__predict_false(paddr.IsBusErr())) {
return BusData::BusErr;
}
bus.ci = paddr.IsCInhibit();
busdata bd = read_phys(paddr);
if (__predict_false(bd.IsBusErr())) {
return bd;
}
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;
}
addr += datasize;
reqsize -= datasize;
addr.ChangeSize(reqsize);
}
return data;
}
// キャッシュ有効時の読み込み。ミスアライン可。
// バスエラーも busdata を返す。
//
// 簡略化・高速化のため実機と異なる制約を設けてある。
// 1) 実際の 68030 システムでは、「ロングワードの全バイトがキャッシュ可能なら
// !CIIN をネゲートしなければならない」「いずれかのバイトでもキャッシュ可能
// でないなら CIIN をアサートしなければならない」(MC68030UM.pdf p6-15、
// 日本語本 p296 ※誤訳あり) とあり、CIIN は同一ロングワード内で変化しては
// いけないが、ロングワードごとには変化してよい。
// nono の実装ではこれを同一ライン(16バイト)単位の制約にする。
// 現状サポートしている機種ではこれで困ることはない。
busdata
MPU68030Device::read_cache(m68030Cache *cache, busaddr addr)
{
putlog(3, "read_cache %u.%08x sz=%c",
addr.GetFC(), addr.Addr(), "0BW3L"[addr.GetSize()]);
uint32 data = 0;
for (;;) {
uint32 entry_data = 0;
// まず可能ならキャッシュから探す。
uint32 tag = (addr.Get() >> 4) & 0xfffffff0;
uint32 idx = (addr.Addr() >> 4) & 0x0000000f;
uint32 sel = (addr.Addr() >> 2) & 0x00000003;
m68030CacheLine *l = &cache->line[idx];
if (__predict_true(l->tag == tag && l->valid[sel])) {
// タグが一致してエントリも有効 (キャッシュヒット)。
cache->stat_hit++;
entry_data = l->data[sel];
putlog(3, " entry_data=$%08x (%s)", entry_data, "match");
} else {
// タグが違うか、エントリが無効。
cache->stat_miss++;
bus.laddr = addr;
busaddr paddr = TranslateRead(addr);
if (__predict_false(paddr.IsBusErr())) {
putlog(3, " TranslateRead BusErr");
return BusData::BusErr;
}
bus.ci = paddr.IsCInhibit();
putlog(3, " paddr=$%08x.%c",
paddr.Addr(), "0BW3L"[paddr.GetSize()]);
// タグが違うか、全エントリが無効ならライン充填する。
// ただし要求がラインをまたぐ場合 (例えば +$e からのロングワード
// 要求) は除く。
bool is_linefill = (l->tag != tag || l->valid_all == 0) &&
((addr.Addr() & 0xf) + addr.GetSize() <= 0x10);
// 可能ならバーストリードを使う。
if (is_linefill &&
__predict_true(bus.ci == false) &&
__predict_true(cache->burst_enable) &&
__predict_true(read_phys_burst(paddr, &l->data[0])))
{
// read_phys_burst() はライン充填したら true を返している。
entry_data = l->data[sel];
putlog(3, " entry_data=$%08x (%s)", entry_data, "burst");
l->tag = tag;
l->valid_all = -1;
} else {
// シングル充填。
busdata bd = read_single(paddr);
if (__predict_false(bd.IsBusErr())) {
return bd;
}
entry_data = bd.Data();
putlog(3, " entry_data=$%08x (%s)", entry_data, "single");
if (__predict_false(bus.ci) || __predict_false(cache->freeze)) {
// エントリを更新しない。
} else {
l->data[sel] = entry_data;
if (l->tag != tag) {
// タグを更新して、このラインの他のエントリは無効。
l->tag = tag;
l->valid_all = 0;
}
l->valid[sel] = 1;
}
}
}
// addr から今回対象とするロングワードエントリに対するマスクを作成。
uint32 reqoffset = addr.Addr() & 3;
uint32 reqsize = addr.GetSize();
uint32 opersize = std::min(reqsize, 4 - reqoffset);
data <<= opersize * 8;
// このエントリから必要な部分を抜き出す。
uint32 mask = 0xffffffffU >> ((4 - opersize) * 8);
if (reqsize == opersize) {
// 要求サイズがこのエントリをはみ出していない。
entry_data >>= (4 - reqoffset - opersize) * 8;
data |= entry_data & mask;
putlog(3, " data=$%0*x (done)", opersize * 2, data);
break;
} else {
data |= entry_data & mask;
putlog(3, " data=$%0*x", opersize * 2, data);
}
addr += opersize;
addr.ChangeSize(reqsize - opersize);
}
return data;
}
// シングル充填を行い、(CI でなければ) 完全なロングワードを返す。
// CI なら第1サイクルだけを行い、ロングワードの所定の位置においたものを返す。
// バスエラーなら BusErr を返す。
busdata
MPU68030Device::read_single(busaddr paddr)
{
// 一発目。
busdata bd = read_phys(paddr);
if (__predict_true(bd.IsOK()) && bd.GetSize() != 4) {
// 必要なら残りのサイクル。
bd = fill_single(paddr, bd);
}
// いずれにしても bd を返す。
return bd;
}
// シングル充填の第2サイクル以降を行って、(CI でなければ) 完全なロングワードを
// 返す。
// 引数 paddr に第1サイクルの物理アドレス (インクリメントなどする前)。
// 引数 bd は第1サイクルの結果の busdata。
//
// 第1サイクルがバスエラーになっていれば事前に弾いておくこと。
// 第1サイクルでロングワード全部埋まったケースも事前に弾いておくこと。
busdata
MPU68030Device::fill_single(busaddr paddr, busdata bd)
{
busaddr addr_h(paddr.Get() & ~0x3ULL);
uint32 data;
//assert(bd.IsOK());
//assert(bd.GetSize() < 4);
if (bd.GetSize() == 2) {
// ワードポートアクセスだったなら、第1アクセスが上位か下位かだけ。
if ((paddr.Addr() & 2) == 0) {
data = bd.Data() << 16;
} else {
data = bd.Data();
}
if (__predict_false(bus.ci)) {
goto done;
}
// 次のアクセスはワード境界にアラインされるので
// 0x3 じゃなく0x2 でマスク。
uint32 offset = (paddr.Addr() + 2) & 0x2;
paddr = addr_h | offset;
bd = read_phys(paddr);
// CI はもう起きないはず。バスエラーは?
if (offset == 0) {
data |= bd.Data() << 16;
} else {
data |= bd.Data();
}
} else {
// バイトポートアクセスの場合。
// 第1サイクルのデータを所定のバイト位置に。
uint32 offset = paddr.Addr() & 0x3;
data = bd.Data() << ((3 - offset) * 8);
if (__predict_false(bus.ci)) {
goto done;
}
for (uint i = 1; i < 4; i++) {
// インクリメント。
offset = (offset + 1) & 0x3;
paddr = addr_h | offset;
bd = read_phys(paddr);
// CI はもう起きないはず。バスエラーは?
data |= bd.Data() << ((3 - offset) * 8);
}
}
done:
return data;
}
// キャッシュ/バスへの書き込み。ミスアライン可。
// バスエラーも busdata を返す。
busdata
MPU68030Device::write_bus(busaddr addr, uint32 data)
{
m68030Cache *cache = dcache.get();
putlog(3, "write_bus %u.%08x sz=%c data=$%0*x",
addr.GetFC(), addr.Addr(), "0BW3L"[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;
}
// まず外部バスを駆動して CI を得る。
// バスエラーなら記録だけしておく。キャッシュへは書き込むため。
busdata bd = BusData::BusErr;
bus.laddr = addr;
busaddr paddr = TranslateWrite(addr);
bus.ci = paddr.IsCInhibit();
if (__predict_true(paddr.IsOK())) {
uint32 outdata = data;
for (;;) {
bd = write_phys(paddr, outdata);
if (__predict_false(bd.IsBusErr())) {
putlog(3, " write_phys -> BusErr");
break;
}
uint32 breqsize = paddr.GetSize();
putlog(3, " write_phys $%08x data=$%0*x",
paddr.Addr(), breqsize * 2, outdata);
uint32 portsize = bd.GetSize();
uint32 offset = paddr.Addr() & (portsize - 1);
uint32 datasize = portsize - offset;
if (datasize >= breqsize) {
break;
}
// 同一ロングワード内なので paddr の更新だけでいい。
paddr += datasize;
breqsize -= datasize;
paddr.ChangeSize(breqsize);
// データは下詰めで、不要な上位ビットはクリアする仕様。
outdata &= (1U << (breqsize * 8)) - 1;
}
}
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;
}
}
}
}
// バスアクセスでバスエラーが出たらキャッシュ更新後に発報。
if (__predict_false(bd.IsBusErr())) {
return bd;
}
// ロングワード境界をまたいだ余りがあればもう一回。
if (nextsize == 0) {
break;
}
putlog(3, " nextsize=%u", nextsize);
addr += addr.GetSize();
addr.ChangeSize(nextsize);
data = nextdata;
}
return 0;
}
// mainbus の paddr から指定サイズで1回読み込む。
// ウェイトはこちらで加算して、バスエラーも含めた bd を返す。
busdata
MPU680x0Device::read_phys(const busaddr paddr)
{
busdata bd = mainbus->Read(paddr);
buswait += bd.GetWait();
return bd;
}
// mainbus の paddr に指定サイズで1回書き込む。
// ウェイトはこちらで加算して、バスエラーも含めた bd を返す。
busdata
MPU680x0Device::write_phys(const busaddr paddr, uint32 data)
{
busdata bd = mainbus->Write(paddr, data);
buswait += bd.GetWait();
return bd;
}
// バースト転送。
// paddr の下位4ビットは無視する。
// paddr がバースト転送可能なら dst に 16 バイト転送して true を返す。
// paddr がバースト転送不可なら dst は触らず false を返す。
// VM (バス) がバースト転送対応している場合のみ呼ぶこと。
bool
MPU680x0Device::read_phys_burst(busaddr paddr, uint32 *dst)
{
paddr &= 0xfffffff0U;
busdata bd = mainbus->ReadBurst16(paddr, dst);
buswait += bd.GetWait();
// BusErr ビットをバースト転送不可フラグとして使っている。
return !bd.IsBusErr();
}
// 物理メモリからのロングワード読み込み。
// 現在の bus に影響を与えず指定のアドレスを読み出す。MMU 専用。
busdata
MPU680x0Device::read_phys_4(uint32 addr)
{
m68kbus backup;
busdata bd;
// バス情報を一旦退避
backup = bus;
// 物理バスアクセス
bus.laddr = busaddr(addr) | BusAddr::SRead | BusAddr::Size4;
bd = read_phys(bus.laddr);
if (__predict_false(bd.IsBusErr())) {
return bd;
}
// バス情報を復元
bus = backup;
return bd;
}
// 物理メモリへのロングワード書き込み。
// 現在の bus に影響を与えず指定のアドレスに書き出す。MMU 専用。
busdata
MPU680x0Device::write_phys_4(uint32 addr, uint32 data)
{
m68kbus backup;
busdata bd;
// バス情報を一旦退避
backup = bus;
// 物理バスアクセス
bus.laddr = busaddr(addr) | BusAddr::SWrite | BusAddr::Size4;
bd = write_phys(bus.laddr, data);
if (__predict_false(bd.IsBusErr())) {
return bd;
}
// バス情報を復元
bus = backup;
return bd;
}
// CACR レジスタ書き込み。
void
MPU68030Device::SetCACR(uint32 newval)
{
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) {
if (force_disable_icache) {
putlog(1, "I-Cache not enabled (mpu-force-disable-icache)");
newval &= ~M68030::CACR_EI;
} else {
// 命令キャッシュ有効
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");
}
}
// FI (命令キャッシュ凍結)
if ((chgval & M68030::CACR_FI) != 0) {
icache->freeze = (newval & M68030::CACR_FI);
putlog(1, "CACR:FI <- %u", icache->freeze);
}
// 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;
}
newval &= ~M68030::CACR_CI;
}
// IBE (命令バースト有効)
if ((chgval & M68030::CACR_IBE) != 0) {
icache->burst_enable =
(newval & M68030::CACR_IBE) &&
(icache->freeze == false) &&
has_burst_access;
}
// ED (データキャッシュ有効)
if ((chgval & M68030::CACR_ED) != 0) {
if ((newval & M68030::CACR_ED) != 0) {
if (force_disable_dcache) {
putlog(1, "D-Cache not enabled (mpu-force-disable-dcache)");
newval &= ~M68030::CACR_ED;
} else {
// データキャッシュ有効。
dcache->enable = true;
dcache->hist.Clear();
putlog(1, "D-Cache enabled");
}
} else {
// データキャッシュ無効。
dcache->enable = false;
putlog(1, "D-Cache disabled");
}
}
// 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;
}
newval &= ~M68030::CACR_CD;
}
// DBE (データバースト有効)
if ((chgval & M68030::CACR_DBE) != 0) {
dcache->burst_enable =
(newval & M68030::CACR_DBE) &&
(dcache->freeze == false) &&
has_burst_access;
}
// WA (ライトアロケート)
if ((chgval & M68030::CACR_WA) != 0) {
dcache->wa = (newval & M68030::CACR_WA);
}
// とりあえずレジスタイメージも保存。
reg.cacr = newval;
}
// リセット例外の CPU 固有部分。
void
MPU68030Device::ResetCache()
{
SetCACR(M68030::CACR_CD | M68030::CACR_CI);
icache->hist.Clear();
dcache->hist.Clear();
}
// キャッシュの統計情報を計算。
void
m68030Cache::CalcStat()
{
m68030CacheStat s;
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::lock_guard<std::mutex> lock(hist_mtx);
hist.EnqueueForce(s);
}
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.