File:  [Isaki's NoNo m68k/m88k emulator] / nono / m680x0 / m68030mmu.cpp
Revision 1.1.1.15 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:05:57 2026 UTC (3 months ago) by root
Branches: MAIN, Isaki
CVS tags: v027, HEAD
nono 1.7.0

//
// nono
// Copyright (C) 2020 nono project
// Licensed under nono-license.txt
//

#include "mpu680x0.h"
#include "m68030mmu.h"
#include "mainbus.h"

// ディスクリプタタイプ
#define GET_DT(x)	((x) & 0x03)
#define DT_INVALID	(0)
#define DT_PAGE		(1)
#define DT_VALID4	(2)
#define DT_VALID8	(3)

//
// デバッグ表示
//

// インデント付き string_format
static std::string indent_format(int indent, const char *fmt, ...)
	__printflike(2, 3);
static std::string
indent_format(int indent, const char *fmt, ...)
{
	va_list ap;
	char *buf;

	va_start(ap, fmt);
	vasprintf(&buf, fmt, ap);
	va_end(ap);

	std::string rv(indent, ' ');
	rv += buf;
	free(buf);

	return rv;
}

#define MMULOG(lv, indent, arg...) do { \
	if (__predict_false(loglevel >= (lv))) {	\
		cpu->putlogf(lv, [&, lam_func = __func__] { \
			(void)lam_func; \
			return indent_format(indent, arg); \
		});	\
	}	\
} while (0)


//
// ページテーブル操作クラスはヘッダにいらないのでここで定義する。
//
class m68030PageTableOp
{
	static constexpr uint32 PAGEDESC_S	= 0x00000100;
	static constexpr uint32 PAGEDESC_CI	= 0x00000040;
	static constexpr uint32 PAGEDESC_M	= 0x00000010;
	static constexpr uint32 PAGEDESC_U	= 0x00000008;
	static constexpr uint32 PAGEDESC_WP	= 0x00000004;
	static constexpr uint32 PAGEDESC_DT	= 0x00000003;

 public:
	// TIA 〜 TID で最大4階層。
	// LV_FCL を引くのはデバッグ表示だけなので番兵より後ろ。
	static constexpr int LV_TIA = 0;
	static constexpr int LV_TIB = 1;
	static constexpr int LV_TIC = 2;
	static constexpr int LV_TID = 3;
	static constexpr int MAXLV  = 4;
	static constexpr int LV_FCL = 5;

	// サーチ結果。
	enum Result {
		Normal,			// 正常に解決できた。
		Early,			// アーリーターミネーション。
		Indirect,		// 間接ディスクリプタ。
		Invalid,		// 無効エントリに当たった。
	};

	// 動作モード。整数比較もする。
	enum From {
		ByTranslation = 0,	// アドレス変換中のテーブルサーチ。(通常)
		ByPTEST = 1,		// PTEST* 命令によるテーブルサーチ。
		ByDebugger = -1,	// デバッガによるテーブルサーチ。
	};

 public:
	m68030PageTableOp(MPU68030Device *, int loglevel_, m68030PageTableOp::From);

	// ページテーブルから laddr を検索する。
	Result Search(int maxlv, busaddr laddr);

	// ページテーブルから laddr に対応する物理アドレスを調べる。デバッガ専用。
	busaddr SearchByDebugger(busaddr laddr);

	// 検索結果から a を埋める。
	void MakeATC(m68030ATC&, m68030ATCLine *a) const;

 private:
	// モード判定。
	bool IsDebugger() const noexcept	{ return (from < 0); }
	bool IsPTEST() const noexcept		{ return (from > 0); }

	Result DoSearch(int maxlv, const busaddr laddr);

	// ディスクリプタをフェッチする。
	inline bool FETCH_DESC(uint32 addr, uint idx)
	{
		if (__predict_false(IsDebugger())) {
			return PeekDesc(addr, idx);
		} else {
			return FetchDesc(addr, idx);
		}
	}
	bool FetchDesc(uint32 addr, uint idx);
	bool PeekDesc(uint32 addr, uint idx);
	bool CheckLimit() const;
	uint32 AssembleEarlyRemains(uint32 addr) const;

	// ディスクリプタを表示用に (デバッグ用)
	std::string sprintf_desc() const;

	// ディスクリプタタイプ文字列 (デバッグ用)
	static const char *dtname(uint32 dt);

 public:
	Result result {};
	busaddr m_laddr {};	// 論理アドレス (入力)
	busaddr m_paddr {};	// 物理アドレス
	bool m_berr {};		// B: ディスクリプタフェッチでバスエラー (PTEST)
	bool m_lim {};		// L: リミット
	bool m_s {};
	bool m_ci {};
	bool m_wp {};
	bool m_m {};
	int  m_n {};		// N: 検索回数
	// 最後にアクセスしたディスクリプタアドレス (PTEST)
	uint32	m_lastaddr {};

 private:
	// laddr の各階層ごとの値。これがテーブルを引く際のインデックス。
	std::array<int, MAXLV> m_idx {};

	int lv {};			// 現在位置(LV_*)

	// ディスクリプタにはショート(4バイト)とロング(8バイト)があり、
	// どちらを使うかは前段の DT で決まる (それが m_descsize)。
	// ロングディスクリプタは 1 ロングワード目がフラグ、2ロングワード目が
	// アドレスとなっている。ショートディスクリプタは S ビットを除いて
	// このフラグ部とアドレス部を OR しただけの形をしている。
	// そのため、ロングディスクリプタなら m_desc1 に1語目(フラグ部)、
	// m_desc2 に2語目(アドレス部) を置くが、ショートディスクリプタなら
	// m_desc1, m_desc2 のどちらにも同じものを置けば (どうせ使う時に
	// 必要なビットだけにマスクするので) そのまま使える。
	uint32 m_desc1 {};
	uint32 m_desc2 {};
	uint m_descsize {};	// ディスクリプタサイズ (4 or 8)

	From from {};		// 動作モード

	// M ビットを更新するかどうか。
	// PTEST でない(=通常サーチの) 書き込みアクセスなら true になる。
	bool writeback_m {};

	int loglevel {};

	MPU68030Device *cpu {};

	static const char * const m_lvname[LV_FCL + 1];
};


//
// アドレス変換。
//

// リード/フェッチ用のアドレス変換を行う。
// 変換後の物理アドレス、もしくは BusErr を返す。
busaddr
MPU68030Device::TranslateRead(const busaddr laddr)
{
	m68030ATCLine *a;

	uint32 fc = laddr.GetFC();
	putlog(3, " %s:  enter  %u(%s).%08x pc=%c.%08x", __func__,
		fc, FCstr(fc),
		laddr.Addr(),
		(IsSuper() ? 'S' : 'U'),
		ppc);

#if defined(M68030_CUSTOM_ATC)
	auto table = atc.GetTable(fc);
	uint8 n = table->hash[laddr.Addr() >> 12];
	if (__predict_true((int8)n >= 0)) {
		// ATC #(n)
		table->stat.total++;
		a = &table->line[n];
	} else if (n == m68030ATCTable_Custom::HASH_NONE) {
		if (__predict_true(mmu_tc.e)) {
			// TC.E 有効で対応なしなら、テーブルサーチ。
			table->stat.total++;
			table->stat.miss++;
			a = table->Pop();
			PageTableSearch(laddr, a);
		} else {
			// TC.E 無効。(total もカウントしない)
			return laddr;
		}
	} else if (n == m68030ATCTable_Custom::HASH_TT) {
		// TT。TTMatch() で外れたかどうかはカウントしない。ほぼ 0 なので。
		table->stat.total++;
		table->stat.tthit++;
		return TTMatch(laddr);
	} else {
		// PS<4KB でマッチしたので再検査。
		table->stat.total++;
		a = table->FindSub(laddr);
		if (a == NULL) {
			table->stat.miss++;
			a = table->Pop();
			PageTableSearch(laddr, a);
		}
	}
	table->MakeHash(a);
	table->MoveHead(a);
#else
	// TT は TC.E とは独立しているので先に調べる。
	busaddr ttaddr = TTMatch(laddr);
	if (__predict_false(ttaddr.IsOK())) {
		putlog(3, " %s:  result %08x CI=%u (TT)", __func__,
			ttaddr.Addr(), ttaddr.IsCInhibit() ? 1 : 0);
		return ttaddr;
	}
	// TC.E 無効なら PA=VA。
	if (__predict_false(mmu_tc.e == false)) {
		putlog(3, " %s:  result %08x (No translation)", __func__, laddr.Addr());
		return laddr;
	}
	// ATC を検索。なければテーブルサーチ。
	auto table = atc.GetTable();
	a = table->LookupAndMove(laddr);
	if (__predict_false(a == NULL)) {
		putlog(4, "  ATC not match");
		a = table->Pop();
		PageTableSearch(laddr, a);
	}
#endif

	putlog(4, "  ATC match %s", table->LineToStr(a).c_str());

	if (__predict_false(a->IsBusError())) {
		putlog(3, " %s:  result BusErr", __func__);
		return BusAddr::BusErr;
	}
	busaddr paddr = laddr;
	paddr.ChangeAddr(a->GetPAddr() | (laddr.Addr() & mmu_tc.pgmask));
	if (__predict_false(a->IsCInhibit())) {
		paddr |= BusAddr::CInhibit;
	}
	putlog(3, " %s:  result %08x CI=%u", __func__,
		paddr.Addr(), paddr.IsCInhibit() ? 1 : 0);
	return paddr;
}

// ライト用のアドレス変換を行う。
// 変換後の物理アドレス、もしくは BusErr を返す。
busaddr
MPU68030Device::TranslateWrite(const busaddr laddr)
{
	m68030ATCLine *a;

	uint32 fc = laddr.GetFC();
	putlog(3, " %s: enter  %u(%s).%08x pc=%c.%08x", __func__,
		fc, FCstr(fc),
		laddr.Addr(),
		(IsSuper() ? 'S' : 'U'),
		ppc);

#if defined(M68030_CUSTOM_ATC)
	auto table = atc.GetTable(fc);
	uint8 n = table->hash[laddr.Addr() >> 12];
	if (__predict_true((int8)n >= 0)) {
		// ATC #(n)
		table->stat.total++;
		a = &table->line[n];
	} else if (n == m68030ATCTable_Custom::HASH_NONE) {
		if (__predict_true(mmu_tc.e)) {
			// TC.E 有効で対応なしなら、テーブルサーチ。
			table->stat.total++;
			a = table->Pop();
			goto tablesearch;
		} else {
			// TC.E 無効。(total もカウントしない)
			return laddr;
		}
	} else if (n == m68030ATCTable_Custom::HASH_TT) {
		// TT。TTMatch() で外れたかどうかはカウントしない。ほぼ 0 なので。
		table->stat.total++;
		table->stat.tthit++;
		return TTMatch(laddr);
	} else {
		// PS<4KB でマッチしたので再検査。
		table->stat.total++;
		a = table->FindSub(laddr);
		if (a == NULL) {
			a = table->Pop();
			goto tablesearch;
		}
	}

	// 書き込み可なのにまだ Modified == 0 なら、Modified を立てるために
	// このエントリを破棄して改めてテーブルサーチを行う。
	if (a->IsWProtect() == false && a->IsModified() == false) {
		table->Invalidate(a);
 tablesearch:
		table->stat.miss++;
		PageTableSearch(laddr, a);
	}
	table->MoveHead(a);
	table->MakeHash(a);
#else
	// TT は TC.E とは独立しているので先に調べる。
	busaddr ttaddr = TTMatch(laddr);
	if (__predict_false(ttaddr.IsOK())) {
		putlog(3, " %s: result %08x CI=%u (TT)", __func__,
			ttaddr.Addr(), ttaddr.IsCInhibit() ? 1 : 0);
		return ttaddr;
	}
	// TC.E 無効なら PA=VA。
	if (__predict_false(mmu_tc.e == false)) {
		putlog(3, " %s: result %08x (No translation)", __func__, laddr.Addr());
		return laddr;
	}
	// ATC を検索。
	auto table = atc.GetTable();
	a = table->LookupAndMove(laddr);
	if (a) {
		// 書き込み可なのにまだ Modified == 0 なら、Modified を立てるために
		// このエントリを破棄して改めてテーブルサーチを行う。
		if (a->IsWProtect() == false && a->IsModified() == false) {
			putlog(4, "  ATC match but !Modified");
			PageTableSearch(laddr, a);
		}
	} else {
		// なければテーブルサーチ。
		putlog(4, "  ATC not match");
		a = table->Pop();
		PageTableSearch(laddr, a);
	}
#endif

	putlog(4, "  ATC match %s", table->LineToStr(a).c_str());

	if (__predict_false(a->IsBusError())) {
		putlog(3, " %s: result BusErr", __func__);
		return BusAddr::BusErr;
	}
	if (__predict_false(a->IsWProtect())) {
		// Write アクセスで WP が立ってたらバスエラー。
		putlog(3, " %s: result Buserr (WProtect)", __func__);
		return BusAddr::BusErr;
	}

	busaddr paddr = laddr;
	paddr.ChangeAddr(a->GetPAddr() | (laddr.Addr() & mmu_tc.pgmask));
	if (__predict_false(a->IsCInhibit())) {
		paddr |= BusAddr::CInhibit;
	}
	putlog(3, " %s: result %08x CI=%u", __func__,
		paddr.Addr(), paddr.IsCInhibit() ? 1 : 0);
	return paddr;
}

// ピーク用のアドレス変換を行う。
// 変換できれば対応する物理アドレスを、バスエラーなら BusErr を返す。
// テーブルサーチを行ったら TableSearched ビットを立てる。
busaddr
MPU68030Device::TranslatePeek(const busaddr laddr)
{
	const m68030ATCLine *a;
	busaddr paddr;

	uint32 fc = laddr.GetFC();
	putlog(5, " %s:  enter  %u(%s).%08x", __func__,
		fc, FCstr(fc),
		laddr.Addr());

#if defined(M68030_CUSTOM_ATC)
	auto table = atc.GetTable(fc);
	uint8 n = table->hash[laddr.Addr() >> 12];
	// PS<4KB のケースを先に解決する。
	if (__predict_false(n == m68030ATCTable_Custom::HASH_SUB)) {
		a = table->FindSub(laddr);
		if (a) {
			n = a - &table->line[0];
		} else {
			n = m68030ATCTable_Custom::HASH_NONE;
		}
	}
	if (__predict_true((int8)n >= 0)) {
		// ATC #(n)
		a = &table->line[n];
		paddr = busaddr(a->GetPAddr() | (laddr.Addr() & mmu_tc.pgmask));
		putlog(5, "%s:  result %08x (match atc)", __func__, paddr.Addr());
		return paddr;
	} else if (n == m68030ATCTable_Custom::HASH_TT) {
		// TT をこっそりチェック。
		if (TTMatch(laddr).IsOK()) {
			// 一致した。
			// CI はここでは使わないため、一致したことだけわかればよい
			putlog(5, "%s:  result %08x (match tt)", __func__, laddr.Addr());
			return laddr;
		}
		return BusAddr::BusErr;
	} else {
		if (__predict_false(mmu_tc.e == false)) {
			// TC.E 無効
			putlog(5, "%s:  result %08x (no translation)", __func__,
				laddr.Addr());
			return laddr;
		}
		// TC.E 有効で対応なしなら、こっそりテーブルサーチ。
		// FALLTHROUGH
	}
#else
	// まず TT をこっそりチェック。
	if (TTMatch(laddr).IsOK()) {
		// 一致した。
		// CI はここでは使わないため、一致したことだけわかればよい
		putlog(5, " %s:  result %08x (match tt)", __func__, laddr.Addr());
		return laddr;
	}

	// TC.E が無効ならここまで。
	if (__predict_false(mmu_tc.e == false)) {
		putlog(5, " %s:  result %08x (no translation)", __func__, laddr.Addr());
		return laddr;
	}

	// こっそり ATC を検索。
	auto table = atc.GetTable();
	a = table->LookupByDebugger(laddr);
	if (a) {
		paddr = busaddr(a->GetPAddr() | (laddr.Addr() & mmu_tc.pgmask));
		putlog(5, "%s:  result %08x (match atc)", __func__, paddr.Addr());
		return paddr;
	}
#endif

	// こっそりテーブルサーチ。
	m68030PageTableOp pt(this, loglevel, m68030PageTableOp::ByDebugger);
	paddr = pt.SearchByDebugger(laddr);
	paddr |= BusAddr::TableSearched;

	if (paddr.IsBusErr()) {
		putlog(5, " %s:  result BusErr (table search)", __func__);
	} else {
		paddr |= laddr.Addr() & mmu_tc.pgmask;
		putlog(5, " %s:  result %08x (table search)", __func__, paddr.Addr());
	}
	return paddr;
}

// TT から laddr を検索する。
// laddr が TT0/TT1 どちらかと一致すれば CI ビットを反映したアドレスを返す。
// 一致しなければ BusErr を返す。バスエラーではないが単に無効値の意。
busaddr
MPU68030Device::TTMatch(const busaddr laddr) const
{
	int match = 0;
	bool ci = false;

	for (int i = 0; i < 2; i++) {
		const m68030TT& tt = mmu_tt[i];
		if (tt.Match(laddr)) {
			match += i + 1;
			if (__predict_true(tt.ci)) {
				ci = true;
			}
		}
	}

	if (match != 0) {
		busaddr paddr = laddr;
		if (ci) {
			paddr |= BusAddr::CInhibit;
		}
		putlog(4, " %s: result %08x (match TT%s)", __func__,
			paddr.Addr(),
			(match == 3) ? "0 and TT1" : ((match == 1) ? "0" : "1"));
		return paddr;
	}
	// 一致しなかった。
	return BusAddr::BusErr;
}

// ページテーブルから laddr を検索する。
// 見付かっても見付からなくても、a を更新して返す。
// ここでは指定の a を埋めるだけで a の移動は呼び出し側の責任。
void
MPU68030Device::PageTableSearch(busaddr laddr, m68030ATCLine *a)
{
	m68030PageTableOp pt(this, loglevel, m68030PageTableOp::ByTranslation);
	pt.Search(7, laddr);
	pt.MakeATC(atc, a);
}


#if defined(M68030_CUSTOM_ATC)
//
// ATC テーブル (Custom)
//

// アドレス変換の本来のフローはこんな感じだが、
//
//  if (TT*.E || TC.E) {
//  	if (TT*.E が有効) {
//  		laddr,RW が TT[01] とマッチしたら、paddr = laddr,CI
//  	}
//  	if (TC.E が有効) {
//  		ATC とマッチしたら、paddr = atc[n].paddr,CI
//  		そうでなければ、 paddr = テーブルサーチ
//  	}
//  } else {
//  	paddr = laddr (アドレス変換オフ)
//  }
//
// ハッシュで一度に引く。
//	switch (hash[laddr>>12]) {
//   case HASH_TT:
//  	laddr,RW が TT[01] とマッチしたら、paddr = laddr,CI
//   case HASH_ATC:
//  	paddr = atc[n].paddr,CI
//   case HASH_NONE:
//  	TC.E なら paddr = テーブルサーチ
//  	そうでなければ、paddr = laddr (アドレス変換オフ)
//   case HASH_SUB:
//  	PS が 4KB 未満だとハッシュの1エントリに2つ以上が混在するので、
//  	この場合は諦めて ATC を線形探索。通常起きないので性能無視。
//  }

// ATC テーブルは本来は 1つ(FC混合) x 22 ラインだが、
// 高速化のため FC別(4テーブル) x 22 ラインのモードを用意する。
// FC 混合版ではテーブルは tables[0] のみで、FC(1,0,2) は busaddr の
// 34-32 ビット部分として laddr と一緒に検索キーにする。
// FC 分離版ではテーブルは fctables[fc] で検索キーは laddr(下位32ビット部分)。
//
// ハッシュなのでライン間の順序関係はないが、LRU で古いのを捨てる時や
// PS<4K の時に前から探索する時のために、age を使った順序管理をする。
// ラインを使用するたびに age に ++latest_age を代入しておくことで、
// 順序が必要な時には age の大きい順にすれば最近使った順になる。
// 32 ビット符号なし整数が折り返すと事故るがたぶんそれより高い頻度で
// フラッシュが起きるんじゃないかな…

// コンストラクタ
m68030ATCTable_Custom::m68030ATCTable_Custom(MPU68030Device *cpu_, uint fc_)
{
	cpu = cpu_;
	fc = fc_;

	ClearHash();
	Flush();
}

// ハッシュをすべてクリアする。
void
m68030ATCTable_Custom::ClearHash()
{
	std::fill(hash.begin(), hash.end(), HASH_NONE);
	latest_age = 0;
}

// ハッシュを完全に作り直す。
void
m68030ATCTable_Custom::Rehash()
{
	ClearHash();

	// ページサイズも再調達。
	ps4k = cpu->mmu_tc.ps - 12;

	// ATCLine を適用。
	if (cpu->mmu_tc.e) {
		for (int i = 0; i < LINES; i++) {
			const m68030ATCLine *a = &line[i];
			if (a->IsValid()) {
				MakeHash(a);
			}
		}
	}

	// 続いて TT を適用。検索時は TT が優先なので TT は後から適用。
	for (int n = 0; n < 2; n++) {
		const m68030TT& tt = cpu->mmu_tt[n];
		if (tt.e) {
			busaddr laddr = busaddr(0) | busaddr::FC(fc);
			for (uint i = 0; i < 256; i++) {
				// XXX R/W は?
				if (tt.Match(laddr)) {
					uint32 baseidx = i << (24 - 12);
					memset(&hash[baseidx], HASH_TT, 4096);
				}
				laddr += 0x0100'0000;
			}
		}
	}
}

// この ATC テーブルのエントリをすべてフラッシュする。
void
m68030ATCTable_Custom::Flush()
{
	for (int i = 0; i < LINES; i++) {
		Invalidate(&line[i]);
		line[i].age = 0;
	}
	latest_age = 0;
}

// この ATC テーブルから addr が一致するエントリをすべてフラッシュする。
void
m68030ATCTable_Custom::Flush(uint32 addr)
{
	for (int i = 0; i < LINES; i++) {
		m68030ATCLine *a = &line[i];
		if (a->GetTag() == addr) {
			Invalidate(a);
		}
	}
}

// この要素を先頭に移動。
inline void
m68030ATCTable_Custom::MoveHead(m68030ATCLine *a)
{
	a->age = ++latest_age;

	if (__predict_false(a->age == 0)) {
		// latest_age が1周したので、一旦全部リセット。
		for (int i = 0; i < LINES; i++) {
			line[i].age = 0;
		}
		a->age = ++latest_age;
	}
}

// このテーブルのうち最も古い要素を差し出す。
m68030ATCLine *
m68030ATCTable_Custom::Pop()
{
	m68030ATCLine *a = NULL;
	uint32 min = (uint32)~0;
	uint32 minidx = 0;

	// 最後に使われたの (あるいは空き) を探す。
	for (int i = 0; i < LINES; i++) {
		if (line[i].IsInvalid()) {
			return &line[i];
		}
		if (line[i].age < min) {
			min = line[i].age;
			minidx = i;
		}
	}
	if (a == NULL) {
		a = &line[minidx];
	}

	// ハッシュから削除。
	Invalidate(a);

	return a;
}

// a からハッシュを作成する。
// TT.E 有効で a は有効であること。
void
m68030ATCTable_Custom::MakeHash(const m68030ATCLine *a)
{
	//assert(cpu->mmu_tc.e);
	//assert(a->IsValid());

	uint32 addr4k = a->GetLAddr() >> 12;
	int idx = a - &line[0];

	if (__predict_true(ps4k == 0)) {
		hash[addr4k] = idx;
	} else if (ps4k > 0) {
		for (int i = 0; i < (1U << ps4k); i++) {
			hash[addr4k + i] = idx;
		}
	} else {
		hash[addr4k] = HASH_SUB;
	}
}

// ATC a を無効にする。すでに無効なら何もしない。
//
// ハッシュから削除する際は TT を指していないか必ず確認すること。
// ATC エントリが出来た後で、そこに TT に設定すると起きうる。
// LRU の ATC ならそのエントリは参照されずに消えていくだけで害はないが、
// ハッシュは TT で上書きされているので消す際に注意する必要がある。
void
m68030ATCTable_Custom::Invalidate(m68030ATCLine *a)
{
	if (a->IsValid()) {
		// 有効ならハッシュから削除。
		if (__predict_true(ps4k == 0)) {
			uint32 addr4k = a->GetLAddr() >> 12;
			if ((int8)hash[addr4k] >= 0) {
				hash[addr4k] = HASH_NONE;
			}
		} else if (ps4k > 0) {
			// PS>4KB
			uint32 addr4k = a->GetLAddr() >> 12;
			for (int i = 0; i < (1U << ps4k); i++) {
				if ((int8)hash[addr4k + i] >= 0) {
					hash[addr4k + i] = HASH_NONE;
				}
			}
		} else {
			// PS<4KB、自分が抜けることでこの枠が空くかどうか
			InvalidateSub(a);
		}

		a->Invalidate();
	}
}

// PS<4KB の場合に aa が抜けることでこの addr4k の枠が空けばハッシュから削除。
void
m68030ATCTable_Custom::InvalidateSub(const m68030ATCLine *aa)
{
	const uint32 MASK_4K = 0xfffff000 | m68030ATCLine::INVALID;
	uint32 target_laddr = aa->GetTag() & MASK_4K;
	bool in_use = false;

	for (auto& e : line) {
		m68030ATCLine *a = &e;

		// 自分自身は今から削除予定なので除く。
		if (a == aa) {
			continue;
		}
		// それ以外で有効なものがあるか。
		if ((a->GetTag() & MASK_4K) == target_laddr) {
			in_use = true;
			break;
		}
	}

	// 誰もいなくなれば削除。
	if (in_use == false) {
		uint k = target_laddr >> 12;
		if (hash[k] != HASH_SUB) {
			hash[k] = HASH_NONE;
		}
	}
}

// laddr を ATC から探す。見つからなければ NULL を返す。
m68030ATCLine *
m68030ATCTable_Custom::FindSub(const busaddr laddr)
{
	uint32 addr = laddr.Addr() & lmask;

	// 同じエントリはないはずなので、順序通りに探さなくてもいいはず。
	for (auto& e : line) {
		m68030ATCLine *a = &e;
		if (a->GetTag() == addr) {
			return a;
		}
	}
	return NULL;
}

// 統計情報をリセットする。
void
m68030ATCTable_Custom::ResetStat()
{
	stat.Clear();

	std::lock_guard<std::mutex> lock(hist_mtx);
	hist.Clear();
	hist_tt_once_hit = false;
}

// デバッグ表示。FC は Table_Custom が持っている。
std::string
m68030ATCTable_Custom::LineToStr(const m68030ATCLine *a) const
{
	return LineToStrCommon(a, fc);
}

#else
//
// ATC テーブル (Motorola)
//

// コンストラクタ
m68030ATCTable_Motorola::m68030ATCTable_Motorola(MPU68030Device *cpu_)
{
	cpu = cpu_;

	for (uint pri = 0; pri < LINES; pri++) {
		Set(pri, pri);
	}
}

// pri 番目に idx をセットする。
void
m68030ATCTable_Motorola::Set(uint pri, uint idx)
{
#if defined(ATC_ORDER64)
	// セット先がクリアされてることが分かっている場合にはこれは不利。
	uint i;
	if (__predict_true(pri < ORDER0_COUNT)) {
		i = 0;
	} else {
		i = 1;
		pri -= ORDER0_COUNT;
	}
	order[i] &= ~(0x1fULL << (pri * 5));
	order[i] |= (uint64)idx << (pri * 5);
#else
	order[pri] = idx;
#endif
}

// laddr が ATC の何番目かを返す。なければ -1 を返す。
int
m68030ATCTable_Motorola::Lookup(const busaddr laddr)
{
	uint32 tag = MakeTag(laddr);

#define COMPARE(idx, pri)	do {	\
	m68030ATCLine *a = &line[(idx)];	\
	if (__predict_true(a->GetTag() == tag))	\
		return (pri);	\
} while (0)

#if defined(ATC_ORDER64)
	uint j;
	uint64 order_ = order[0];
	for (j = 0; j < ORDER0_COUNT; j++) {
		uint idx = order_ & 0x1f;
		order_ >>= 5;
		COMPARE(idx, j);
	}
	order_ = order[1];
	for (; j < M68030_ATC_LINES; j++) {
		uint idx = order_ & 0x1f;
		order_ >>= 5;
		COMPARE(idx, j);
	}
#else
	for (uint pri = 0; pri < LINES; pri++) {
		uint idx = GetIdx(pri);
		COMPARE(idx, pri);
	}
#endif
	return -1;
}

// ATC から laddr を検索する。
// 見付かればこのエントリを先頭に移動してそれを返す。
// 見付からなければ NULL を返す。
m68030ATCLine *
m68030ATCTable_Motorola::LookupAndMove(const busaddr laddr)
{
	int pri = Lookup(laddr);
	if (__predict_true(pri >= 0)) {
		// 先頭から何番目で見付かったか記録し、先頭に移動。
		stat.hit[pri]++;

		const m68030ATCLine *a = GetLine(pri);
		MMULOG(4, 3, "ATC.LookupAndMove: [%u]@%u: %u(%s)%08x",
			GetIdx(pri), pri,
			a->GetFC(),
			MPU68030Device::FCstr(a->GetFC()),
			a->GetPAddr());

		if (__predict_false(pri != 0)) {
			MoveHead(pri);
		}
		return GetLine(0);
	} else {
		stat.miss++;
		MMULOG(4, 3, "ATC.LookupAndMove: not match");
		return NULL;
	}
}

// ATC から laddr を検索する。エントリは変更しない。(PTEST 用)
const m68030ATCLine *
m68030ATCTable_Motorola::LookupOnly(const busaddr laddr)
{
	int pri = Lookup(laddr);
	if (__predict_true(pri >= 0)) {
		return GetLine(pri);
	} else {
		return NULL;
	}
}

// ATC をすべてフラッシュする。
void
m68030ATCTable_Motorola::Flush()
{
	// 全部無効にするので、無効にしたのを最後尾に回す必要ない。
	for (auto& e : line) {
		m68030ATCLine *a = &e;
		a->Invalidate();
	}
}

// ATC の fc, mask が一致するエントリをフラッシュする。
void
m68030ATCTable_Motorola::Flush(uint32 fc, uint32 mask)
{
	for (uint pri = 0; pri < LINES; pri++) {
		m68030ATCLine *a = GetLine(pri);
		if (((a->GetFC() ^ fc) & mask) == 0) {
			a->Invalidate();
			MoveTail(pri);
		}
	}
}

// ATC の fc, mask, addr が一致するエントリをフラッシュする。
void
m68030ATCTable_Motorola::Flush(uint32 fc, uint32 mask, uint32 addr)
{
	for (uint pri = 0; pri < LINES; pri++) {
		m68030ATCLine *a = GetLine(pri);
		if (a->GetLAddr() == addr && ((a->GetFC() ^ fc) & mask) == 0) {
			a->Invalidate();
			MoveTail(pri);
		}
	}
}

// pri 番目の要素を先頭に移動する。
// pri がゼロ(先頭)のケースは呼び出し元で事前に弾いておくこと。
void
m68030ATCTable_Motorola::MoveHead(uint pri)
{
	uint idx = GetIdx(pri);
#if defined(ATC_ORDER64)
	if (__predict_true(pri < ORDER0_COUNT)) {
		uint64 maskl = (~0ULL) << (pri * 5);
		uint64 maskh = maskl << 5;
		uint64 tmpl = (order[0] & ~maskl) << 5;
		uint64 tmph = (order[0] & maskh);
		order[0] = tmph | tmpl | idx;
	} else {
		uint j = pri - ORDER0_COUNT;
		uint64 maskl = (~0ULL) << (j * 5);
		uint64 maskh = maskl << 5;
		uint64 tmpl = (order[1] & ~maskl) << 5;
		uint64 tmph = (order[1] & maskh);
		uint64 tmpp = (order[0] >> ((ORDER0_COUNT - 1) * 5)) & 0x1f;
		order[1] = tmph | tmpl | tmpp;
		constexpr uint64 mask = (1ULL << (ORDER0_COUNT * 5)) - 1;
		order[0] = ((order[0] << 5) & mask) | idx;
	}
#else
	for (uint d = pri; d > 0; d--) {
		order[d] = order[d - 1];
	}
	order[0] = idx;
#endif
}

// pri 番目の要素を末尾に移動する。
// pri が末尾でも呼び出してよい。
void
m68030ATCTable_Motorola::MoveTail(uint pri)
{
	if (pri == LINES - 1) {
		return;
	}
	uint idx = GetIdx(pri);
#if defined(ATC_ORDER64)
	// コメント欄は 5ビットx22エントリを1文字x8桁で模式的に表現したもの。
	// 110ビットより上に unused なビットがあるが、0 のままのはず。
	// order = x'87654321
	constexpr uint TAIL0 = (ORDER0_COUNT - 1) * 5;
	constexpr uint TAIL1 = (ORDER1_COUNT - 1) * 5;
	if (pri < ORDER0_COUNT) {
		uint64 mask = (~0ULL) << (pri * 5);			// F'FFFFF000 (ex. pri=3)
		uint64 tmph = (order[0] >> 5) & mask;		// x'x8765000 (xは0のはず)
		uint64 tmpl = (order[0] & ~mask);			// 0'00000321
		uint64 tmpb = (order[1] & 0x1f) << TAIL0;	// 0'n0000000
		order[0] = tmph | tmpl | tmpb;
		order[1] = (order[1] >> 5) | ((uint64)idx << TAIL1);
	} else {
		uint j = pri - ORDER0_COUNT;
		uint64 mask = (~0ULL) << (j * 5);
		uint64 tmph = (order[1] >> 5) & mask;
		uint64 tmpl = (order[1] & ~mask);
		uint64 tmpb = (uint64)idx << TAIL1;
		order[1] = tmph | tmpl | tmpb;
	}
#else
	uint d;
	for (d = pri; d < LINES - 2; d++) {
		order[d] = order[d + 1];
	}
	order[d] = idx;
#endif
}

// 空き要素あるいは最も古い要素を先頭に移動する。
m68030ATCLine *
m68030ATCTable_Motorola::Pop()
{
	MoveHead(LINES - 1);
	return GetLine(0);
}

// 統計情報をリセットする。
void
m68030ATCTable_Motorola::ResetStat()
{
	stat.Clear();
}

// デバッグ表示。FC は a 自身が持っている。
/*static*/ std::string
m68030ATCTable_Motorola::LineToStr(const m68030ATCLine *a)
{
	return LineToStrCommon(a, a->GetFC());
}

#endif

// デバッグ表示。
/*static*/ std::string
m68030ATCTable::LineToStrCommon(const m68030ATCLine *a, uint fc_)
{
	uint32 laddr = a->GetLAddr();

	if (!a->IsBusError()) {
		uint32 paddr = a->GetPAddr();
		char   ci    = a->IsCInhibit() ? 'C' : '-';
		char   wp    = a->IsWProtect() ? 'W' : '-';
		char   mod   = a->IsModified() ? 'M' : '-';
		return indent_format(1,
			"%u.%08x %08x %c%c%c",
			fc_, laddr, paddr, ci, wp, mod);
	} else {
		return indent_format(1,
			"%u.%08x BusError",
			fc_, laddr);
	}
}


//
// ATC
//

// コンストラクタ
m68030ATC::m68030ATC(MPU68030Device *cpu_)
{
	cpu = cpu_;

#if defined(M68030_CUSTOM_ATC)
	alltables[0].reset(new m68030ATCTable_Custom(cpu, 1));
	alltables[1].reset(new m68030ATCTable_Custom(cpu, 2));
	alltables[2].reset(new m68030ATCTable_Custom(cpu, 5));
	alltables[3].reset(new m68030ATCTable_Custom(cpu, 6));

	fctables[1] = alltables[0].get();
	fctables[2] = alltables[1].get();
	fctables[5] = alltables[2].get();
	fctables[6] = alltables[3].get();
#else
	table.reset(new m68030ATCTable_Motorola(cpu));
#endif
}

// ログレベルの設定。
// これは Object のオーバーライドではないが、同じ使い方をする。
void
m68030ATC::SetLogLevel(int loglevel_)
{
	loglevel = loglevel_;

#if defined(M68030_CUSTOM_ATC)
	for (const auto& table : alltables) {
		table->loglevel = loglevel_;
	}
#else
	table->loglevel = loglevel_;
#endif
}

#if defined(M68030_CUSTOM_ATC)
// ハッシュをすべて作り直す。
void
m68030ATC::Rehash()
{
	for (const auto& table : alltables) {
		table->Rehash();
	}
}
#endif

// TC がセットされたので、こちらの二次変数も追従する。
void
m68030ATC::SetTC(const m68030TC& tc)
{
#if defined(M68030_CUSTOM_ATC)
	for (const auto& table : alltables) {
		table->SetMask(tc.lmask);
	}
#else
	table->SetMask(tc.lmask);
#endif
}

// ATC をすべてフラッシュする。命令からコールされる
void
m68030ATC::FlushAll()
{
#if defined(M68030_CUSTOM_ATC)
	for (const auto& table : alltables) {
		table->Flush();
	}
#else
	table->Flush();
#endif
}

// ATC の fc, mask が一致するエントリをフラッシュする。
// 命令からコールされる。
void
m68030ATC::Flush(uint32 fc, uint32 mask)
{
#if defined(M68030_CUSTOM_ATC)
	for (uint i = 1; i <= 6; i++) {
		if (((i ^ fc) & mask) == 0) {
			// FC が一致したテーブルを全フラッシュ。
			if ((bool)fctables[i]) {
				fctables[i]->Flush();
			}
		}
	}
#else
	table->Flush(fc, mask);
#endif
}

// ATC の fc, mask, addr が一致するエントリをフラッシュする。
// 命令からコールされる。
void
m68030ATC::Flush(uint32 fc, uint32 mask, uint32 addr)
{
#if defined(M68030_CUSTOM_ATC)
	for (uint i = 1; i <= 6; i++) {
		if (((i ^ fc) & mask) == 0) {
			// FC が一致したテーブルから addr が一致したエントリをフラッシュ。
			if ((bool)fctables[i]) {
				fctables[i]->Flush(addr);
			}
		}
	}
#else
	table->Flush(fc, mask, addr);
#endif
}

// 統計情報をリセットする。
void
m68030ATC::ResetStat()
{
#if defined(M68030_CUSTOM_ATC)
	for (const auto& table : alltables) {
		table->ResetStat();
	}
#else
	table->ResetStat();
#endif
}

// 統計情報を更新する。
void
m68030ATC::CalcStat()
{
#if defined(M68030_CUSTOM_ATC)
	for (const auto& table : alltables) {
		// ここはロックなしでやってしまう
		m68030ATCStat s;
		s.total = table->stat.total;
		s.tthit = table->stat.tthit;
		s.miss  = table->stat.miss;
		table->stat.total = 0;
		table->stat.tthit = 0;
		table->stat.miss  = 0;

		// こっちはロックする
		{
			std::lock_guard<std::mutex> lock(table->hist_mtx);

			table->hist.EnqueueForce(s);
			if (__predict_false(s.tthit != 0)) {
				table->hist_tt_once_hit = true;
			}
		}
	}
#else
#endif
}


//
// テーブルサーチ操作。
//

// コンストラクタ
m68030PageTableOp::m68030PageTableOp(MPU68030Device *cpu_, int loglevel_,
	m68030PageTableOp::From from_)
{
	cpu = cpu_;
	loglevel = loglevel_;
	from = from_;
}

// ページテーブルから laddr を検索する。
// 結果はメンバ変数に保持する。
m68030PageTableOp::Result
m68030PageTableOp::Search(int maxlv, busaddr laddr)
{
	// テーブルサーチで書き込みの時だけ、ディスクリプタの M ビットを更新。
	if (__predict_false(laddr.IsWrite()) &&
		__predict_true(from == ByTranslation))
	{
		writeback_m = true;
	}

	// テーブルサーチを行う。
	result = DoSearch(maxlv, laddr);
	if (__predict_true(result != Result::Invalid)) {
		m_paddr = busaddr(m_desc2 & cpu->mmu_tc.lmask);
	}

	// デバッグ表示。
	if (__predict_false(loglevel >= 4)) {
		std::string buf;
		switch (result) {
		 case Result::Invalid:
			buf = "Invalid -> BusErr";
			break;
		 case Result::Normal:
			buf = string_format("Normal paddr=%08x", m_paddr.Addr());
			break;
		 case Result::Indirect:
			buf = string_format("Indirect paddr=%08x", m_paddr.Addr());
			break;
		 case Result::Early:
		 {
			// 残ってるビットも表示。
			uint32 paddr = m_paddr.Addr();
			buf = string_format("Early paddr=%08x", paddr);
			for (uint i = lv + 1; i <= cpu->mmu_tc.termlv; i++) {
				paddr |= m_idx[i]  << cpu->mmu_tc.shift[i];
				buf += string_format(" +%s=%08x", m_lvname[i], paddr);
			}
			break;
		 }
		}
		MMULOG(4, 2, "PageTable.%s: %s", lam_func, buf.c_str());
	}

	return result;
}

// デバッガからの呼び出し用。
// maxlv 不要なのと、ATC を作って返す必要もないので、単に変換後のアドレス
// (変換出来なければ BusErr) を返す。
busaddr
m68030PageTableOp::SearchByDebugger(busaddr laddr)
{
	// writeback_m は false のまま。

	// テーブルサーチを行う。
	result = DoSearch(7, laddr);

	// ここからサーチ結果によって、結果の物理アドレスを作成。
	if (result == Result::Invalid) {
		return BusAddr::BusErr;
	}

	uint32 paddr = m_desc2 & cpu->mmu_tc.lmask;
	if (result == Result::Early) {
		paddr = AssembleEarlyRemains(paddr);
	}

	return busaddr(paddr);
}

// laddr についてテーブルサーチを行い結果コード Result を返す。
// その他の状態はメンバに保持する。
m68030PageTableOp::Result
m68030PageTableOp::DoSearch(int maxlv, const busaddr laddr)
{
	const char *rpname;
	uint dt;

	m_laddr = laddr;

	// m_laddr のビットを TIA .. TID に分解。
	//
	// 例えば TIA=$C, TIB=$A, (TIC = TID = 0), PS=$A の場合以下のようにする。
	// TIB の次の段は番兵。
	//
	//                    A              B             PS
	//           +----------------+--------------+--------------+
	// $00A01A00 | 0000 0000 1010 | 0000 0001 10 | xx xxxx xxxx |
	//           +----------------+--------------+--------------+
	//            shift[TIA]=20    shift[TIB]=10
	//            mask[TIA]=0xfff  mask[TIB]=0x3ff
	//            idx[TIA]=0xa     idx[TIB]=0x6
	//
	for (int i = LV_TIA, end = cpu->mmu_tc.termlv; i <= end; i++) {
		uint shift = cpu->mmu_tc.shift[i];
		uint mask  = cpu->mmu_tc.mask[i];
		m_idx[i] = (m_laddr.Addr() >> shift) & mask;
	}

	// この辺に図9-26 の初期化だがコンストラクタで初期化済み。

	// ここから図9-25。

	// 使用するルートポインタを決定。
	// m_desc2 のマスクは使うところで行っている。
	if (__predict_true(cpu->mmu_tc.sre && laddr.IsSuper())) {
		rpname = "SRP";
		m_desc1 = cpu->GetSRPh();
		m_desc2 = cpu->GetSRPl();
	} else {
		rpname = "CRP";
		m_desc1 = cpu->GetCRPh();
		m_desc2 = cpu->GetCRPl();
	}

	// ルートポインタの DT をチェック。
	dt = GET_DT(m_desc1);
	MMULOG((__predict_false(IsDebugger()) ? 5 : 4),
		3, "%s: %s dt=%s", lam_func, rpname, dtname(dt));
	if (__predict_true(dt == DT_VALID4)) {
		// 有効 4 バイト。基本的にはこれしか使われないので優先。
		m_descsize = 4;
	} else if (dt == DT_VALID8) {	// 有効 (8バイト)
		m_descsize = 8;
	} else if (dt == DT_PAGE) {		// ページディスクリプタ
		return Result::Early;
	} else {
		// 無効は設定できないはずなので、ここには来ないはず。
		PANIC("%s.DT=%u corrupted?", rpname, dt);
	}

	// 必要ならファンクションコード・ルックアップ。
	if (__predict_false(cpu->mmu_tc.fcl)) {
		// たぶん検索レベルを1つ消費するはず。未確認。
		m_n++;

		uint32 tableaddr = m_desc2 & 0xfffffff0U;
		uint idx = m_laddr.GetFC();
		MMULOG((__predict_false(IsDebugger()) ? 5 : 4),
			3, "%s: %s tableaddr=%08x idx=$%x", lam_func,
			m_lvname[lv], tableaddr, idx);

		// ディスクリプタをフェッチ。
		if (FETCH_DESC(tableaddr, idx) == false) {
			return Result::Invalid;
		}

		// ディスクリプタタイプをチェック。
		dt = GET_DT(m_desc1);
		switch (dt) {
		 case DT_INVALID:	// 無効
			return Result::Invalid;

		 case DT_PAGE:		// ページディスクリプタ
			return Result::Early;

		 case DT_VALID4:	// 有効4バイト
		 case DT_VALID8:	// 有効8バイト
			m_descsize = 1U << dt;
			break;
		}
	}

	// TIx のテーブルサーチに入る。図9-25 の下半分。

	for (lv = LV_TIA; ; lv++) {
		// N はステージ開始時に足すが、
		// 超えたかどうかはこのステージが完了したループ末尾で調べる。
		m_n++;

		// ロングディスクリプタなら、リミットチェック。
		if (__predict_false(m_descsize == 8)) {
			if (CheckLimit() == false) {
				m_lim = true;
				return Result::Invalid;
			}
		}

		// テーブルアドレス。
		uint32 tableaddr = m_desc2 & 0xfffffff0U;
		MMULOG((__predict_false(IsDebugger()) ? 5 : 4),
			3, "%s: %s tableaddr=%08x idx=$%x", lam_func,
			m_lvname[lv], tableaddr, m_idx[lv]);

		// ディスクリプタをフェッチ。
		if (FETCH_DESC(tableaddr, m_idx[lv]) == false) {
			return Result::Invalid;
		}

		// ディスクリプタタイプをチェック。
		dt = GET_DT(m_desc1);
		switch (dt) {
		 case DT_INVALID:	// 無効
			return Result::Invalid;

		 case DT_PAGE:		// ページディスクリプタ
			// 最終段ならノーマル。
			// 最終段じゃないのにページが来たらアーリーターミネーション。
			if (__predict_true(lv == cpu->mmu_tc.termlv)) {
				return Result::Normal;
			} else {
				return Result::Early;
			}

		 case DT_VALID4:	// 有効4バイト
		 case DT_VALID8:	// 有効8バイト
			// 最終段なら、間接ディスクリプタ。
			if (__predict_false(lv == cpu->mmu_tc.termlv)) {
				// ディスクリプタをフェッチ。
				tableaddr = m_desc2 & 0xfffffff0U;
				if (FETCH_DESC(tableaddr, 0) == false) {
					return Result::Invalid;
				}
				if (GET_DT(m_desc1) != DT_PAGE) {
					return Result::Invalid;
				}

				return Result::Indirect;
			}

			// 次段のサーチを繰り返す。
			m_descsize = 1U << dt;
			break;
		}

		if (__predict_false(m_n >= maxlv)) {
			MMULOG((__predict_false(IsDebugger()) ? 5 : 4),
				3, "%s: n reaches maxlv(%u)", lam_func, maxlv);
			break;
		}
	}

	return Result::Normal;
}

// ディスクリプタ中のフラグの更新 (を予約する)。
#define UPDATE_DESC(flag)	do { \
	if ((m_desc1 & (flag)) == 0) { \
		m_desc1 |= (flag); \
		do_write = true; \
	} \
} while (0)

// ディスクリプタのフェッチと更新を行う。
// 図9-29。
// ディスクリプタを取得出来れば true を返す。
// ATC エントリの作成に向かう場合は false を返す。
bool
m68030PageTableOp::FetchDesc(uint32 tableaddr, uint idx)
{
	uint32 descaddr;
	busdata bd;
	bool do_write;
	uint dt;

	// ディスクリプタのアドレスを計算。ここだけマニュアルの説明が雑すぎ。
	// インダイレクトなら idx = 0 でコールしてあるので、
	// tableaddr がそのまま PA になるという寸法。
	descaddr = tableaddr + idx * m_descsize;
	MMULOG(4, 4, "%s: descsize=%u descaddr=%08x", lam_func,
		m_descsize, descaddr);

	bd = cpu->read_phys_4(descaddr);
	if (__predict_false(bd.IsBusErr())) {
		goto buserr;
	}
	m_desc1 = bd.Data();

	if (__predict_true(m_descsize == 4)) {
		// ショートディスクリプタは m_desc1 と m_desc2 の各フィールドが
		// 衝突せず共存している状態とみなすことが出来る。
		m_desc2 = m_desc1;
	} else {
		bd = cpu->read_phys_4(descaddr + 4);
		if (bd.IsBusErr()) {
			goto buserr;
		}
		m_desc2 = bd.Data();
	}
	m_lastaddr = descaddr;

	// ディスクリプタタイプをチェック。
	do_write = false;
	dt = GET_DT(m_desc1);
	MMULOG(4, 4, "%s: desc=%s dt=%s", lam_func,
		sprintf_desc().c_str(), dtname(dt));
	switch (dt) {
	 case DT_INVALID:	// 無効
		return true;

	 case DT_PAGE:		// ページディスクリプタ
		// U ビットを更新。
		UPDATE_DESC(PAGEDESC_U);

		// テーブルサーチ中の書き込み操作なら M を更新。
		if (writeback_m)  {
			UPDATE_DESC(PAGEDESC_M);
		}
		// 図にはないけど、ATC 更新のためと PTEST のために
		// ページディスクリプタの M ビットを記録。
		if ((m_desc1 & PAGEDESC_M)) {
			m_m = true;
		}
		break;

	 case DT_VALID4:	// 有効4バイト
	 case DT_VALID8:	// 有効8バイト
		// U ビットを更新。
		UPDATE_DESC(PAGEDESC_U);
		break;

	 default:
		__unreachable();
	}

	// ライトライクルの実行。
	if (do_write) {
		busdata r = cpu->write_phys_4(descaddr, m_desc1);
		if (__predict_false(r.IsBusErr())) {
			goto buserr;
		}
		// バス動作正常終了。
		MMULOG(4, 4, "%s: desc=%s updated", lam_func, sprintf_desc().c_str());
	}

	// ステータス更新 (OR していく)。
	// XXX 実際には CI はページディスクリプタにしかないけど
	m_wp |= (m_desc1 & PAGEDESC_WP);
	m_ci |= (m_desc1 & PAGEDESC_CI);
	if (__predict_false(m_descsize == 8)) {
		m_s |= (m_desc1 & PAGEDESC_S);
	}
	return true;

	// バスエラー
 buserr:
	m_berr = true;
	return false;
}

// ディスクリプタをこっそり取得する。更新操作は行わない。
// またディスクリプタの状態を内部フラグに反映するところも行わない。
// 取得できれば m_desc1, m_desc2 に置いて true を返す。
// 取得できなければ false を返す。
bool
m68030PageTableOp::PeekDesc(uint32 tableaddr, uint idx)
{
	uint32 descaddr;
	uint64 data;

	// ディスクリプタのアドレスを計算。
	descaddr = tableaddr + idx * m_descsize;

	data = cpu->mainbus->Peek4(descaddr);
	if (__predict_false((int64)data < 0)) {
		return false;
	}
	m_desc1 = (uint32)data;

	if (__predict_true(m_descsize == 4)) {
		m_desc2 = m_desc1;
	} else {
		data = cpu->mainbus->Peek4(descaddr + 4);
		if ((int64)data < 0) {
			return false;
		}
		m_desc2 = (uint32)data;
	}

	// フラグやメンバーの更新は不要なのでこれで終わり。
	return true;
}

// 検索結果から ATC エントリを作成。
// 図9-27 だが雑すぎ。
void
m68030PageTableOp::MakeATC(m68030ATC& atc, m68030ATCLine *a) const
{
#if defined(M68030_CUSTOM_ATC)
	// lmask はどのインスタンスでも同じ。
	auto table = atc.GetTable(6);
#else
	auto table = atc.GetTable();
#endif

	a->tag = table->MakeTag(m_laddr);

	if (result == Result::Invalid) {
		a->paddr = {};
		a->data = m68030ATCLine::BUSERROR;
		return;
	}

	a->paddr = m_paddr.Addr();
	if (__predict_false(result == Result::Early)) {
		// PERFORM LIMIT CHECK ??

		a->paddr = AssembleEarlyRemains(a->paddr);
	}

	a->data = 0;
	if (m_ci) {
		a->data |= m68030ATCLine::CINHIBIT;
	}
	if (m_wp) {
		a->data |= m68030ATCLine::WPROTECT;
	}
	if (m_m) {
		a->data |= m68030ATCLine::MODIFIED;
	}
}

// リミットチェックを実行。図9-28。
// 無効のケースに落ちたら false を返す。
bool
m68030PageTableOp::CheckLimit() const
{
	uint16 limit = (m_desc1 >> 16) & 0x7fff;

	// 最上位ビットが L/U
	if ((m_desc1 & 0x80000000U)) {
		if (m_idx[lv] < limit) {
			MMULOG(4, 4, "%s: $%04x < $%04x", lam_func, m_idx[lv], limit);
			return false;
		}
	} else {
		if (m_idx[lv] > limit) {
			MMULOG(4, 4, "%s: $%04x > $%04x", lam_func, m_idx[lv], limit);
			return false;
		}
	}
	return true;
}

// 早期終了時の残ってるビットを addr に足したものを返す。
uint32
m68030PageTableOp::AssembleEarlyRemains(uint32 addr) const
{
	for (uint i = lv + 1; i <= cpu->mmu_tc.termlv; i++) {
		addr |= m_idx[i] << cpu->mmu_tc.shift[i];
	}
	return addr;
}

// ディスクリプタ表示用の文字列を返す。
// m_descsize によって m_desc1, m_desc2 を適切に文字列にする。
std::string
m68030PageTableOp::sprintf_desc() const
{
	std::string buf;

	if (m_descsize == 4) {
		buf = string_format("%08x", m_desc1);
	} else {
		buf = string_format("%08x_%08x", m_desc1, m_desc2);
	}

	// フラグの出現条件がややこしいので注意
	int dt = GET_DT(m_desc1);
	buf += string_format(" (%s%s%s%c%c)",
		((m_descsize == 4) ? "x" : ((m_desc1 & PAGEDESC_S) ? "S" : "-")),
		((dt != DT_PAGE)   ? "x" : ((m_desc1 & PAGEDESC_CI) ? "C" : "-")),
		((dt != DT_PAGE)   ? "x" : ((m_desc1 & PAGEDESC_M)  ? "M" : "-")),
		(m_desc1 & PAGEDESC_U)  ? 'U' : '-',
		(m_desc1 & PAGEDESC_WP) ? 'W' : '-'
	);

	return buf;
}

// ディスクリプタタイプ文字列 (デバッグ用)
/*static*/ const char *
m68030PageTableOp::dtname(uint32 dt)
{
	switch (dt) {
	 case DT_INVALID:	return "invalid";
	 case DT_PAGE:		return "page";
	 case DT_VALID4:	return "valid4";
	 case DT_VALID8:	return "valid8";
	 default:
		return "corrupted?";
	}
}

/*static*/ const char * const
m68030PageTableOp::m_lvname[] = {
	"TIA",
	"TIB",
	"TIC",
	"TID",
	"bad",	// 番兵なので表示されないはず。
	"FCL",	// 検索順ではなく、こいつだけ処理が違うのでここにある。
};


//
// レジスタ
//

// リセット例外の CPU 固有部分。
void
MPU68030Device::ResetMMU()
{
	SetTC(GetTC() & ~m68030TC::TC_E);
	SetTT(0, GetTT(0) & ~m68030TT::E);
	SetTT(1, GetTT(1) & ~m68030TT::E);
}

// 現在の TC:E、TT:E の状態に応じてアドレス変換の有無を切り替える。
// mmu_enable ならアドレス変換ありのバスアクセスを使う。see m68030bus.cpp
void
MPU68030Device::mmu_enable_changed()
{
	bool enable;

	// TC、TT0、TT1 のいずれかが有効ならアドレス変換あり
	enable = (mmu_tc.e || mmu_tt[0].e || mmu_tt[1].e);

	// 変化した時だけ
	if (mmu_enable && !enable) {
		// 無効にする
		mmu_enable = false;
		putlog(1, "MMU Translation Disabled");
	} else if (!mmu_enable && enable) {
		// 有効にする
		mmu_enable = true;
		putlog(1, "MMU Translation Enabled");
	}
}

bool
MPU68030Device::SetSRP(uint32 h, uint32 l)
{
	if (GET_DT(h) == DT_INVALID) {
		return false;
	}
	reg.srp.h = h & m68030RP::H_MASK;
	reg.srp.l = l;
	return true;
}

bool
MPU68030Device::SetCRP(uint32 h, uint32 l)
{
	if (GET_DT(h) == DT_INVALID) {
		return false;
	}
	reg.crp.h = h & m68030RP::H_MASK;
	reg.crp.l = l;
	return true;
}

void
MPU68030Device::SetTT(int n, uint32 data)
{
	// マスクしてレジスタイメージにセット
	reg.tt[n] = data & m68030TT::MASK;

	// メンバと二次変数を作成。
	mmu_tt[n].Set(data);

#if defined(M68030_CUSTOM_ATC)
	atc.Rehash();
#endif

	mmu_enable_changed();
}

// data を分解してメンバにセットする。
void
m68030TT::Set(uint32 data)
{
	//    3                   2                   1                   0
	//  1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
	// +---------------+---------------+-+-+-+-+-+-+-+-+-+-----+-+-----+
	// |LogicalAddrBase|LogicalAddrMask|E|       |C|R|M| | FCB | | FCM |
	// +---------------+---------------+-+-+-+-+-+-+-+-+-+-----+-+-----+

	e  = (data & m68030TT::E);
	ci = (data & m68030TT::CI);

	// 下位ロングワード
	// ・lbase は比較先アドレスで、ff000000の位置。
	// ・lmask はアドレスマスク。%1 が無視を表しているので NOT しておく。
	//
	//  target  AAAAAAAA xxxxxxxx xxxxxxxx xxxxxxxx : Aは論理アドレス
	//        ^                                     : xは不問
	//  lbase   BBBBBBBB 00000000 00000000 00000000 : Bは論理アドレスベース
	//        &
	//  lmask   MMMMMMMM 00000000 00000000 00000000 : Mは論理アドレスマスク
	//
	uint32 basel =  (data & m68030TT::LBASE_MASK);
	uint32 maskl = (~data & m68030TT::LMASK_MASK) << 8;

	// 上位ロングワード
	// ・E ビットは位置はどこでもいいので TT:E ビットの
	//   位置をそのまま使用する。target 側のこの位置は必ずビットが落ちている
	//   ため base 側にビットを立てておくと必ず不一致になることを利用して、
	//   E ビットの評価も一度に行う。
	// ・RW ビットは BusAddr::R と比較するので 0x00000080 の位置。
	// ・RWM が %1 なら R/W は無視、RWM が %0 なら R/W の一致も検査するので
	//   RWM が %0 の時は RW のマスクビットを立てておく。
	// ・FC は busaddr の配置に合わせて1ビット左シフトしたところを使う。
	// ・FCMask は %1 が無視を表しているので NOT しておく。
	//
	//  target  00000000 00000000 00000000 R000FFFS : R はアクセス R/~W
	//        ^                                     : F はアクセス FC
	//                                              : S は FC2 のコピー
	//  base    00000000 00000000 E0000000 R000FFF0 : R は期待する R/~W
	//        &                                     : F は期待する FC
	//                                              : E は ~TT:E
	//  mask    00000000 00000000 10000000 M000CCC0 : M は R/~W マスク
	//                                              : C は FC マスク
	uint32 baseh;
	uint32 maskh;
	baseh  = ~data & m68030TT::E;
	baseh |= (data & m68030TT::RW) >> 2;
	baseh |= (data & m68030TT::FCBASE_MASK) >> 3;
	maskh  = m68030TT::E;
	if ((data & m68030TT::RWM) == 0) {
		maskh |= (uint64)BusAddr::R >> 32;
	}
	maskh |= (~data & m68030TT::FCMASK_MASK) << 1;

	base = (((uint64)baseh) << 32) | basel;
	mask = (((uint64)maskh) << 32) | maskl;
}

// アクセスがこの TT とマッチするか調べる。
// laddr の上位ワードはそのまま使える。
// 下位32ビットが論理アドレス (下24ビットは呼び出し側でマスクしなくてよい)。
// この TT が有効でアドレスがマッチすれば true を返す。
// (TT:E は演算に織り込んである、このすぐ上の SetTT() のコメント参照)
bool
m68030TT::Match(busaddr laddr) const
{
	bool rv = (((laddr.Get() ^ base) & mask) == 0);
	return rv;
}

// 成功すれば true、MMU 構成例外が起きる場合は false を返す。
// 呼び出し側で例外を起こすこと。
bool
MPU68030Device::SetTC(uint32 data)
{
	// マスクしてレジスタイメージにセット
	reg.tc = data & m68030TC::MASK;

	// data を分解してメンバにセット。
	if (mmu_tc.Set(data) == false) {
		// エラーなら E をクリアして MMU 構成例外。
		// (例外処理自体は呼び出し側で行う)
		mmu_enable_changed();
		return false;
	}

	// ATC にも通知。
	atc.SetTC(mmu_tc);

#if defined(M68030_CUSTOM_ATC)
	// ハッシュも全部作り直す。
	atc.Rehash();
#endif

	// TC:E が変更されていればスイッチ更新
	mmu_enable_changed();
	return true;
}

// TC レジスタへの書き込み値をこのクラスにセットして二次変数も再構成する。
// E をオンにした場合は構成チェックを行い、問題なければ true を返す。
// 問題があった場合は E をオフにした上で false を返す。
// E をオフにした場合はチェックは行わず true を返す。
bool
m68030TC::Set(uint32 data)
{
	// 一次メンバにセット。
	e   = data & TC_E;
	sre = data & TC_SRE;
	fcl = data & TC_FCL;
	tid = (data)       & 0x0f;
	tic = (data >>= 4) & 0x0f;
	tib = (data >>= 4) & 0x0f;
	tia = (data >>= 4) & 0x0f;
	is  = (data >>= 4) & 0x0f;
	ps  = (data >>= 4) & 0x0f;

	config_ok = Check();

	// 構成が有効なら E に関わらず二次変数を用意しておく。
	if (config_ok) {
		MakeMask();
	}

	// E がオンで構成例外になる場合はオフにする。
	if (e && config_ok == false) {
		e = false;
		return false;
	}

	return true;
}

// MMU 構成例外になるケースなら false を返す。
bool
m68030TC::Check() const
{
	if (ps < 8) {
		return false;
	}
	if (tia == 0) {
		return false;
	}
	if (tib == 0 && (tic > 0 || tid > 0)) {
		return false;
	}
	if (tic == 0 && tid > 0) {
		return false;
	}
	if (ps + is + tia + tib + tic + tid != 32) {
		return false;
	}
	return true;
}

// 二次変数を作成。(E には影響されない)
void
m68030TC::MakeMask()
{
	// lmask (論理マスク) は TIA〜TID 部分のマスク。
	// pgmask (ページマスク) は PS 部分のマスク。
	//
	// 例えば IS=$8, TIA=$3, TIB=$4, TIC=$4, TID=$0, PS=$d
	// (X68030 IPL 内の MMU 判定のケース) なら
	//
	//           3          2          1          0
	//          10987654 32109876 54321098 76543210
	//         +--------+--------+--------+--------+
	//         |IIIIIIII AAABBBBC CCCPPPPP PPPPPPPP|
	//         +--------+--------+--------+--------+
	// lmask  = 00000000 11111111 11100000 00000000
	// pgmask = 00000000 00000000 00011111 11111111
	lmask = (1U << (tia + tib + tic + tid)) - 1;
	lmask <<= ps;
	pgmask = (1U << ps) - 1;

	// アドレスを (IS,) TIA 〜 TID に分解。
	//
	// TIA=$C, TIB=$A, (TIC = TID = 0), PS=$A の場合以下のようにする。
	//
	//                    A              B             PS
	//           +----------------+--------------+--------------+
	// $00A01A00 | 0000 0000 1010 | 0000 0001 10 | xx xxxx xxxx |
	//           +----------------+--------------+--------------+
	//            shift[TIA]=20     shift[TIB]=10
	//            mask[TIA]=0xfff   mask[TIB]=0x3ff
	constexpr int LV_TIA = m68030PageTableOp::LV_TIA;
	constexpr int LV_TID = m68030PageTableOp::LV_TID;
	uint sh = ps;
	for (int i = LV_TID; i >= LV_TIA; i--) {
		if (tix[i] != 0) {
			shift[i] = sh;
			mask[i] = (1U << tix[i]) - 1;
			sh += tix[i];
		}
	}

	// 最終段を求めておく。
	termlv = LV_TID;
	for (int i = LV_TIA; i <= LV_TID; i++) {
		if (tix[i] == 0) {
			termlv = i - 1;
			break;
		}
	}
}

void
MPU68030Device::SetMMUSR(uint16 data)
{
	reg.mmusr = data & m68030MMUSR::MASK;
}


//
// 命令
//

// ir2 の下位5ビットから FC を返す。
// 不当パターンなら C++ の例外をスローする。
uint32
MPU68030Device::get_fc()
{
	uint fcfield = ir2 & 0x1f;

	if (fcfield == 0) {
		return reg.GetSFC();
	} else if (fcfield == 1) {
		return reg.GetDFC();
	} else if (fcfield < 0x08) {
		// break;
	} else if (fcfield < 0x10) {
		return reg.D[fcfield & 7] & 7;
	} else if (fcfield < 0x18) {
		return fcfield & 7;
	}
	// 不当命令
	throw M68K::EXCEP_ILLEGAL;
}

// MMU 不当命令 (2ワード目で確定)
void
MPU68030Device::ops_mmu30_illg2()
{
	putlog(1, "MMU illegal instruction %04x %04x", ir, ir2);
	Exception(M68K::EXCEP_FLINE);
}

// PFLUSHA 命令
void
MPU68030Device::ops_mmu30_pflusha()
{
	atc.FlushAll();
}

// PFLUSH fc,#mask 命令 (EA を持たない方)
void
MPU68030Device::ops_mmu30_pflush()
{
	uint32 mask = (ir2 >> 5) & 7;
	uint32 fc = get_fc();

	atc.Flush(fc, mask);
}

// PFLUSH fc,#mask,ea 命令
void
MPU68030Device::ops_mmu30_pflush_ea()
{
	uint32 mask = (ir2 >> 5) & 7;
	uint32 fc = get_fc();
	uint32 ea = cea_copro();
	ea &= mmu_tc.lmask;

	atc.Flush(fc, mask, ea);
}

// PLOADR/PLOADW 命令
void
MPU68030Device::ops_mmu30_pload()
{
	uint32 ea;
	uint32 fc;
	busaddr laddr;
	m68030ATCLine *a;

	if ((ir2 & 0x01e0) != 0) {
		ops_mmu30_illg2();
		return;
	}
	ea = cea_copro();
	fc = get_fc();

	// 実効アドレスを論理アドレスとする ATC を作成。
	// ってこれでいいのか?
	if ((ir2 & 0x0200)) {
		laddr = busaddr(ea) | busaddr::FC(fc) | BusAddr::R;
	} else {
		laddr = busaddr(ea) | busaddr::FC(fc) | BusAddr::W;
	}

#if defined(M68030_CUSTOM_ATC)
	auto table = atc.GetTable(fc);
	uint8 n = table->hash[laddr.Addr() >> 12];
	// PS<4KB のケースを先に解決する。
	if (__predict_false(n == m68030ATCTable_Custom::HASH_SUB)) {
		a = table->FindSub(laddr);
		if (a) {
			n = a - &table->line[0];
		} else {
			n = m68030ATCTable_Custom::HASH_NONE;
		}
	}
	if ((int8)n >= 0) {
		// すでにエントリがある場合
		if (laddr.IsWrite()) {
			// 命令が ploadw でエントリが WProtect==0 && Modified==0 なら、
			// 一回破棄して書き込み用に作り直す。
			a = &table->line[n];
			if (a->IsWProtect() == false && a->IsModified() == false) {
				table->Invalidate(a);
				PageTableSearch(laddr, a);
			}
		}
	} else if (n == m68030ATCTable_Custom::HASH_NONE) {
		if (__predict_true(mmu_tc.e)) {
			// TC.E 有効で対応なしなら、テーブルサーチを行って
			// ATC を作成。
			a = table->Pop();
			PageTableSearch(laddr, a);
		} else {
			// TC.E 無効なら何もしない?
		}
	} else if (n == m68030ATCTable_Custom::HASH_TT) {
		// TT の領域なら何もしてはいけない。
	}
#else
	// TT の領域なら何もしてはいけない。
	if (TTMatch(laddr).IsOK()) {
		return;
	}
	// TE.E 無効なら何もしない?
	if (__predict_false(mmu_tc.e == false)) {
		return;
	}
	auto table = atc.GetTable();
	a = table->LookupAndMove(laddr);
	if (a) {
		if (laddr.IsWrite()) {
			// 命令が ploadw でエントリが Modified==0 (&& WProtect==0) なら、
			// 一回破棄して Modified で作り直す。
			if (a->IsWProtect() == false && a->IsModified() == false) {
				PageTableSearch(laddr, a);
			}
		}
	} else {
		// テーブルサーチを行って ATC を作成。
		a = table->Pop();
		PageTableSearch(laddr, a);
	}
#endif
}

// PTESTR/PTESTW 命令
void
MPU68030Device::ops_mmu30_ptest()
{
	// 第2ワード
	//  15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
	// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
	// | 1 | 0 | 0 |   Level   |R/W| A |   ARegNo  |       FC          |
	// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
	uint level  = (ir2 >> 10) & 7;
	bool isread = (ir2 & 0x0200);
	uint rn     = (ir2 >> 5) & 0xf;

	uint32 ea = cea_copro();
	uint32 fc = get_fc();
	uint16 mmusr;

	busaddr laddr = busaddr(ea)
		| busaddr::FC(fc)
		| (isread ? BusAddr::R : BusAddr::W);

	if (level == 0) {
		// レベル0で A 指定なら Fライン例外。
		// A が 0 なら ARegNo も 0 でなければならない、とは書いてあるが
		// どうなるかは書いていない。とりあえず例外にしておく。
		if (rn != 0) {
			ops_mmu30_illg2();
			return;
		}
		CYCLE(22);
		mmusr = ptest_atc(laddr);
	} else {
		// レベル 1-7 ならテーブルサーチを実行。

		// A が 0 なら ARegNo も 0 でなければならない、とは書いてあるが
		// どうなるかは書いていない。とりあえず例外にしておく。
		if (__predict_false(1 <= rn && rn < 8)) {
			ops_mmu30_illg2();
			return;
		}
		CYCLE(88);
		mmusr = ptest_search(level, laddr, rn);
	}

	SetMMUSR(mmusr);
}

// PTESTR/PTESTW 命令の #0 の場合は ATC。
uint32
MPU68030Device::ptest_atc(const busaddr laddr)
{
	const m68030ATCLine *a = NULL;
	uint16 mmusr = 0;

#if defined(M68030_CUSTOM_ATC)
	uint fc = laddr.GetFC();
	auto table = atc.GetTable(fc);
	uint8 n = table->hash[laddr.Addr() >> 12];
	// PS<4KB のケースを先に解決する。
	if (__predict_false(n == m68030ATCTable_Custom::HASH_SUB)) {
		a = table->FindSub(laddr);
		if (a) {
			n = a - &table->line[0];
		} else {
			n = m68030ATCTable_Custom::HASH_NONE;
			a = NULL;
		}
	}
	if (__predict_true((int8)n >= 0)) {
		// ATC #(n) で見付かった。
		a = &table->line[n];
	} else if (n == m68030ATCTable_Custom::HASH_TT) {
		mmusr |= m68030MMUSR::T;
	} else {
		// ATC に見付からなかった。
	}
#else
	if (TTMatch(laddr).IsOK()) {
		mmusr |= m68030MMUSR::T;
	}
	auto table = atc.GetTable();
	a = table->LookupOnly(laddr);
#endif

	if (a) {
		// 見付かれば Modified 等の状態を返す。
		if (a->IsBusError()) {
			mmusr |= m68030MMUSR::B;
		} else if (a->IsWProtect()) {
			mmusr |= m68030MMUSR::W;
		} else if (a->IsModified()) {
			mmusr |= m68030MMUSR::M;
		}
	} else {
		// 見付からなかったら Invalid を立てる。
		mmusr |= m68030MMUSR::I;
	}
	return mmusr;
}

// PTESTR/PTESTW 命令の #1-7 の場合はテーブルサーチ。
uint32
MPU68030Device::ptest_search(uint level, const busaddr laddr, uint rn)
{
	uint fc = laddr.GetFC();
	uint16 mmusr = 0;

	putlog(3, "ptest_search lv=%u %u(%s).%08x", level,
		fc, FCstr(fc),
		laddr.Addr());

	m68030PageTableOp pt(this, loglevel, m68030PageTableOp::ByPTEST);
	pt.Search(level, laddr);

	if (pt.m_berr) {
		mmusr |= m68030MMUSR::B;
	}
	if (pt.m_lim) {
		// XXX not tested
		mmusr |= m68030MMUSR::L;
	}
	if (pt.m_s && (fc & 4)) {
		// XXX not tested
		mmusr |= m68030MMUSR::S;
	}
	if (pt.m_wp) {
		mmusr |= m68030MMUSR::W;
	}
	if (pt.result == m68030PageTableOp::Result::Invalid) {
		mmusr |= m68030MMUSR::I;
	}
	if (pt.m_m) {
		mmusr |= m68030MMUSR::M;
	}
	mmusr |= pt.m_n;

	// アドレスレジスタ指定があれば、最後のディスクリプタアドレスを返す。
	// rn は有効なら 8-15。
	if (rn != 0) {
		// XXX 途中でバスエラーが起きるケースは未実装
		if (pt.m_berr == false) {
			reg.R[rn] = pt.m_lastaddr;
		}
	}

	return mmusr;
}

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.