File:  [Isaki's NoNo m68k/m88k emulator] / nono / m88xx0 / m88200.cpp
Revision 1.1.1.17 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:05:56 2026 UTC (2 months, 3 weeks 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
//

// MC88200(CMMU)

#include "m88200.h"
#include "mainbus.h"
#include "monitor.h"
#include "mpu88xx0.h"

#if defined(M88200_STAT)
#define STAT(stmt)	(stmt)++
#else
#define STAT(stmt)	/**/
#endif

// static 変数
m88200 *m88200::mbus_master = NULL;

// m88200 コンストラクタ
m88200::m88200(MPU88xx0Device *parent_, uint id_)
	: inherited(OBJ_M88200(id_))
{
	parent = parent_;

	// ID は IDR レジスタへの書き込みで後から変更できるように読めるが、
	// ハードウェアパラメータのはずだし変更する意味はないのと、仮に変更しても
	// 実際訳の分からんことにしかならんと思う。
	// そして PROM も OpenBSD/luna88k も IDR へ書き込んでいないので、やはり
	// あらかじめ決まっているハードウェアパラメータだと思うことにする。
	// それと、コンストラクタ時点で ID が決まってないのは何かと困るという
	// こちらの都合もある。
	id = id_;

	ClearAlias();
	AddAlias(string_format("CMMU%u", id));

	reg_monitor = gMonitorManager->Regist(ID_MONITOR_CMMU(id), this);
	reg_monitor->SetCallback(&m88200::MonitorScreenReg);
	reg_monitor->SetSize(52, 8);

	atc_monitor = gMonitorManager->Regist(ID_MONITOR_ATC(id), this);
	atc_monitor->SetCallback(&m88200::MonitorScreenATC);
	uint h = 44;
#if defined(M88200_STAT)
	h += 16;
#endif
	atc_monitor->SetSize(77, h);

	cache_monitor = gMonitorManager->Regist(ID_SUBWIN_CACHE(id), this);
	cache_monitor->SetCallback(&m88200::MonitorScreenCache);
	cache_monitor->SetSize(82, 23);

	// ログ表示用の名前
	sapr.name = "SAPR";
	uapr.name = "UAPR";
}

// m88200 デストラクタ
m88200::~m88200()
{
}

// 初期化
bool
m88200::Init()
{
	if (inherited::Init() == false) {
		return false;
	}

	mainbus = GetMainbusDevice();

	// 暗黙 BATC を初期化
	SetBATC(8, 0xfff00000U, 0xfff00000U, BWP_S | BWP_WT | BWP_CI | BWP_V);
	SetBATC(9, 0xfff80000U, 0xfff80000U, BWP_S | BWP_WT | BWP_CI | BWP_V);

	// セットインデックスを初期化
	for (uint i = 0; i < setarray.size(); i++) {
		auto& set = setarray[i];
		set.setidx = i;
	}

	return true;
}

// リセット
void
m88200::Reset()
{
	const uint32 Undefined = 0xccccccccU;

	// レジスタ (p6-4, Table6-3)
	// ID, version は別途 Set される。
	command = 0;
	ssr = 0;
	sar = Undefined;
	sctr = 0;
	fault_code = 0;
	fault_addr = Undefined;
	sapr.enable = false;
	sapr.stat = APR_CI;
	uapr.enable = false;
	uapr.stat = APR_CI;

	// BATC, PATC はリセットで初期化されないようだが確認のしようがない。
}

// モニター更新 (CMMU レジスタ)
void
m88200::MonitorScreenReg(Monitor *, TextScreen& screen)
{
	uint32 baseaddr;
	uint32 reg;
	int x;
	int y;

	baseaddr = 0xfff00000U + (id << 12);

	screen.Clear();
	x = 24;

	y = 0;
	screen.Print(0, y++, "$%08x  IDR:%08x ID=$%02x Type=5(88200) Ver=$%x",
		baseaddr + 0, GetIDR(), id, version);

	reg = GetSSR();
	screen.Print(0, y, "$%08x  SSR:%08x", baseaddr + 0x008, reg);
	screen.Puts(x,      y, TA::OnOff(reg & SSR_CE), "CE");
	screen.Puts(x + 3,  y, TA::OnOff(reg & SSR_BE), "BE");
	screen.Puts(x + 6,  y, TA::OnOff(reg & SSR_WT), "WT");
	screen.Puts(x + 9,  y, TA::OnOff(reg & SSR_SP), "SP");
	screen.Puts(x + 12, y, TA::OnOff(reg & SSR_G),  "G");
	screen.Puts(x + 14, y, TA::OnOff(reg & SSR_CI), "CI");
	screen.Puts(x + 17, y, TA::OnOff(reg & SSR_M),  "M");
	screen.Puts(x + 19, y, TA::OnOff(reg & SSR_U),  "U");
	screen.Puts(x + 21, y, TA::OnOff(reg & SSR_WP), "WP");
	screen.Puts(x + 24, y, TA::OnOff(reg & SSR_BH), "BH");
	screen.Puts(x + 27, y, TA::OnOff(reg & SSR_V),  "V");
	y++;

	screen.Print(0, y++, "$%08x  SAR:%08x", baseaddr + 0x00c, GetSAR());

	reg = GetSCTR();
	screen.Print(0, y, "$%08x SCTR:%08x", baseaddr + 0x104, GetSCTR());
	screen.Puts(x,      y, TA::OnOff(reg & SCTR_PE), "PE");
	screen.Puts(x + 3,  y, TA::OnOff(reg & SCTR_SE), "SE");
	screen.Puts(x + 6,  y, TA::OnOff(reg & SCTR_PR), "PR");
	y++;

	reg = GetPFSR();
	static const char * const codestr[] = {
		"Success",
		"1?",
		"2?",
		"Bus Error",
		"Segment Fault",
		"Page Fault",
		"Supervisor Violation",
		"Write Violation",
	};
	screen.Print(0, y++, "$%08x PFSR:%08x %s", baseaddr + 0x108,
		reg, codestr[fault_code]);

	screen.Print(0, y++, "$%08x PFAR:%08x", baseaddr + 0x10c, GetPFAR());

	for (uint i = 0; i < 2; i++) {
		uint s = 1 - i;
		reg = GetAPR(s);
		screen.Print(0, y, "$%08x %cAPR:%08x", baseaddr + 0x200 + i * 4,
			s ? 'S' : 'U', reg);
		screen.Print(x, y, "STBA=%05x'000 %c%c%c%c",
			reg >> 12,
			(reg & APR_WT) ? 'T' : '-',
			(reg & APR_G)  ? 'G' : '-',
			(reg & APR_CI) ? 'C' : '-',
			(reg & APR_TE) ? 'E' : '-');
		y++;
	}
}

// モニター更新 (ATC)
void
m88200::MonitorScreenATC(Monitor *, TextScreen& screen)
{
	int x;
	int y;

	screen.Clear();

	screen.Print(0, 0,  "SAPR  %05x'000 TE=%u      %c%c%c",
		(sapr.addr >> 12) & 0xfffff,
		 sapr.enable ? 1 : 0,
		(sapr.stat & APR_WT) ? 'T' : '-',
		(sapr.stat & APR_G)  ? 'G' : '-',
		(sapr.stat & APR_CI) ? 'C' : '-');

	screen.Print(40, 0, "UAPR  %05x'000 TE=%u      %c%c%c",
		(uapr.addr >> 12) & 0xfffff,
		 uapr.enable ? 1 : 0,
		(uapr.stat & APR_WT) ? 'T' : '-',
		(uapr.stat & APR_G)  ? 'G' : '-',
		(uapr.stat & APR_CI) ? 'C' : '-');

	screen.Puts(0,  1, "<BATC>");
	screen.Puts(0,  2, "No. LBA         PBA       Stat  Hit%");
	screen.Puts(40, 2, "No. LBA         PBA       Stat  Hit%");
	x = 0;
	y = 3;
	uint64 batc_total = 0;
	for (uint i = 0; i < 10; i++) {
		if (i == batc.size() / 2) {
			x = 40;
			y = 3;
		}
		if (i < 8) {
			screen.Print(x, y, " %u:", i);
		} else {
			screen.Print(x, y, "(%u)", i);
		}

		const m88200BATC& b = batc[i];
		if (b.IsValid()) {
			screen.Print(x + 4, y, "%c.%04x'0000 %04x'0000 %c%c%c%c",
				b.IsS() ? 'S' : 'U',
				b.lba >> 16, b.pba >> 16,
				(b.stat & DESC_WT) ? 'T' : '-',
				(b.stat & DESC_G)  ? 'G' : '-',
				(b.stat & DESC_CI) ? 'C' : '-',
				(b.stat & DESC_WP) ? 'P' : '-');
		}
		if (batc_hit[i] == 0) {
			screen.Puts(x + 32, y, "--.-%");
		} else {
			screen.Print(x + 32, y, "%4.1f%%",
				(double)batc_hit[i] / translate_total * 100);
			batc_total += batc_hit[i];
		}
		y++;
	}

	screen.Puts(0,  8, "<PATC>");
	screen.Puts(0,  9, "No. LPA         PFA       Stat");
	screen.Puts(40, 9, "No. LPA         PFA       Stat");

	x = 0;
	y = 10;
	for (uint i = 0; i < patc.size(); i++) {
		if (i == patc.size() / 2) {
			x = 40;
			y = 10;
		}
		screen.Print(x, y, "%2u:", i);

		const m88200PATC& p = patc[i];
		if (p.IsValid()) {
			screen.Print(x + 4,  y, "%c.%05x'000 %05x'000 %c%c%c%c%c",
				p.IsS() ? 'S' : 'U',
				p.lpa >> 12, p.pfa >> 12,
				(p.stat & DESC_WT) ? 'T' : '-',
				(p.stat & DESC_G)  ? 'G' : '-',
				(p.stat & DESC_CI) ? 'C' : '-',
				(p.stat & DESC_WP) ? 'P' : '-',
				p.m                ? 'M' : '-');
		}
		y++;
	}

	y++;
	screen.Puts(0, y++, "<Statistics>");
	x = 49;
	screen.Print(0, y++, "Translate            %26s",
		format_number(translate_total).c_str());
	screen.Print(0, y, " BATC hit            %26s (",
		format_number(batc_total).c_str());
	if (__predict_false(batc_total == 0)) {
		screen.Puts(x, y, "---.-%)");
	} else {
		screen.Print(x, y, "%5.1f%%)",
			(double)batc_total / translate_total * 100);
	}
	y++;
	screen.Print(0, y, " PATC hit            %26s (",
		format_number(patc_hit).c_str());
	if (__predict_false(patc_hit == 0)) {
		screen.Puts(x, y, "---.-%)");
	} else {
		screen.Print(x, y, "%5.1f%%)",
			(double)patc_hit / translate_total * 100);
	}
	y++;
	screen.Print(0, y, " BATC/PATC miss      %26s (",
		format_number(atc_miss).c_str());
	if (__predict_false(atc_miss == 0)) {
		screen.Puts(x, y, "---.-%)");
	} else {
		screen.Print(x, y, "%5.1f%%)",
			(double)atc_miss / translate_total * 100);
	}
	y++;
#if defined(M88200_STAT)
	// 開発用の統計情報。
	y++;
	screen.Print(0, y++, "PATC create          %26s",
		format_number(stat_patc_create).c_str());
	screen.Print(0, y++, "PATC invalidate      %26s",
		format_number(stat_patc_invalidate).c_str());
	screen.Print(0, y++, "PATC SCR InvAll      %26s",
		format_number(stat_patc_invcmd_all).c_str());
	screen.Print(0, y++, "PATC SCR InvSegment  %26s",
		format_number(stat_patc_invcmd_seg).c_str());
	screen.Print(0, y++, "PATC SCR InvPage     %26s",
		format_number(stat_patc_invcmd_page).c_str());

	y++;
	double v;
	screen.Print(0, y++, "PATC hash search     %26s",
		format_number(stat_patc_search).c_str());
	// ヒット率
	screen.Print(0, y, "PATC hash hit        %26s (hit/search ",
		format_number(patc_hit).c_str());
	if (stat_patc_search == 0) {
		screen.Puts(60, y, "---.-%)");
	} else {
		screen.Print(60, y, "%5.1f%%)",
			(double)patc_hit / stat_patc_search * 100);
	}
	y++;
	// ミスのうち1回目のハッシュ比較で外れが確定した率
	screen.Print(0, y, "PATC hash miss 1     %26s (miss1/miss ",
		format_number(stat_patc_miss1).c_str());
	v = stat_patc_miss1 + stat_patc_miss2;
	if (v == 0) {
		screen.Puts(60, y, "---.-%)");
	} else {
		screen.Print(60, y, "%5.1f%%)", (double)stat_patc_miss1 / v * 100);
	}
	y++;
	screen.Print(0, y++, "PATC hash miss 2     %26s",
		format_number(stat_patc_miss2).c_str());

	screen.Puts(0, y++, "Hash        0   1   2   3   4   5   6   >=7");
	// BATC ハッシュの衝突状況は BATC 更新時に計算してある
	// (BATC の更新頻度は低いため)
	screen.Puts(0, y++, "BATC HASH.S");
	for (auto n : stat_batc_hash_s) {
		screen.Print(" %3d", n);
	}
	screen.Puts(0, y++, "BATC HASH.U");
	for (auto n : stat_batc_hash_u) {
		screen.Print(" %3d", n);
	}
	// PATC ハッシュの衝突状況は、表示時に都度計算する
	// (PATC ハッシュの更新頻度はおそらく表示周期より速いため)
	auto print_phash = [&](const auto& hash) {
		std::array<int, 8> st {};
		for (const auto hashmap : hash) {
			int n = __builtin_popcount(hashmap);
			if (n >= st.size()) {
				n = st.size() - 1;
			}
			st[n]++;
		}
		for (auto n : st) {
			screen.Print(" %3d", n);
		}
	};
	screen.Puts(0, y++, "PATC HASH.S");
	print_phash(patc_hash_s);
	screen.Puts(0, y++, "PATC HASH.U");
	print_phash(patc_hash_u);
#endif
}

// モニター更新 (キャッシュ、概要と詳細の両方、GUI から呼ばれる)
// screen.userdata は注目しているセット番号。
void
m88200::MonitorScreenCache(Monitor *, TextScreen& screen)
{
	uint setidx = screen.userdata;

	// 上半分(概要)
	MonitorCacheOverview(screen, 0, setidx, true);
	// 下半分(セット詳細)
	MonitorCacheSet(screen, 18, setidx);
}

// データキャッシュの特定セットの詳細を TextScreen に出力する。
// y は開始オフセット。
// TextScreen は (70, 5) 必要。
void
m88200::MonitorCacheSet(TextScreen& s, int y, uint setidx)
{
	assertmsg(setidx < setarray.size(), "setidx=%u", setidx);
	const m88200CacheSet& set = setarray[setidx];

	/*
	012345678901234567890123456789012345678901234567890123456789
	L Tag        Status
	0 $11223'344 VV     12345678 12345678 12345678 12345678
	*/
	s.Puts(0, y, "L Tag        Status Word");
	s.Puts(58, y, "Order");
	y++;

	// ラインの古い順に評価して順序を0-3でつける
	int Lorder[4];
	uint tmpL = set.L;
	for (uint i = 0; i < 4; i++) {
		int line = m88200CacheSet::TryGetOldestLine(tmpL);
		Lorder[line] = 3 - i;
		tmpL = m88200CacheSet::TryUseLine(tmpL, line);
	}

	for (uint line = 0; line < 4; line++, y++) {
		TA attr;
		// ステータスによって属性を選択
		switch (set.vv[line]) {
		 case m88200CacheSet::Status::IV:
			attr = TA::Disable;
			break;
		 case m88200CacheSet::Status::EU:
		 case m88200CacheSet::Status::SU:
			attr = TA::Normal;
			break;
		 case m88200CacheSet::Status::EM:
			attr = TA::Em;
			break;
		}
		s.Print(0, y, attr, "%u", line);
		static const char statusstr[][4] = { "EU", "EM", "SU", "IV" };
		s.Print(2, y, attr, "$%05x'%03x %s",
			(set.tag[line] >> 12),
			(setidx << 4),
			statusstr[set.vv[line]]);

		if (set.vv[line] == m88200CacheSet::Status::EM) {
			// メモリに対してダーティならボールドにする
			for (uint w = 0; w < 4; w++) {
				uint32 addr;
				uint32 m;
				addr = (set.tag[line] & 0xfffff000U) | (setidx << 4) | (w << 2);
				m = (mainbus->Peek1(addr) << 24)
				  | (mainbus->Peek1(addr + 1) << 16)
				  | (mainbus->Peek1(addr + 2) <<  8)
				  | (mainbus->Peek1(addr + 3));
				if (set.word[line * 4 + w] != m) {
					attr = TA::Em;
				} else {
					attr = TA::Off;
				}
				s.Print(20 + w * 9, y, attr, "%08x", set.word[line * 4 + w]);
			}
		} else {
			// Unmodified (or Invalid) ならメモリとの比較は不要
			for (uint w = 0; w < 4; w++) {
				s.Print(20 + w * 9, y, "%08x", set.word[line * 4 + w]);
			}
		}

		// 順序
		s.Print(58, y, "%u", Lorder[line]);
	}
}

// データキャッシュの概要を指定の TextScreen に出力する。
// y は開始オフセット。CLI では単独コマンドとして、GUI ではページの一部と
// して描画するためこうなっている。
// cursor で指定された番号のセットは反転表示する。GUI でのカーソル用。
// 負数など範囲外の値を指定すればカーソルは表示されない。
// is_gui は GUI かどうか。CLI では80桁を微妙に越えるのは嫌だしどうせ表示
// だけなので間を詰めてあるが、GUI では 80桁制約はない代わりにマウス操作が
// あるので 1セットごとに間を空けて等間隔にしたい、という違いから。
// TextScreen は CLI なら (70, 17)、GUI なら (82, 17) 必要。
void
m88200::MonitorCacheOverview(TextScreen& s, int y, uint cursor, bool is_gui)
{
	// X ガイド
	for (uint i = 0; i < 16; i++) {
		uint x;
		if (is_gui) {
			x = 3 + i * 5;
		} else {
			x = 3 + i * 4 + (i / 4);
		}
		s.Print(x, y, "+0%x", i);
	}
	y++;

	// Y ガイド
	for (uint i = 0; i < 16; i++) {
		s.Print(0, y + i, "%02x", i * 16);
	}

	for (uint i = 0; i < setarray.size(); i++) {
		const auto& set = setarray[i];
		const char str[] = "EMS-";
		uint col = i % 16;
		uint row = i / 16;
		uint x;
		if (is_gui) {
			x = 3 + col * 5;
		} else {
			x = 3 + col * 4 + col / 4;
		}

		s.Print(x, y + row, TA::OnOff(i == cursor),
			"%c%c%c%c",
			str[set.vv[0]],
			str[set.vv[1]],
			str[set.vv[2]],
			str[set.vv[3]]);
	}
}

// IDR の Version フィールドを設定する
void
m88200::SetVersion(uint version_)
{
	version = version_;
}

// コマンド名
// (0-15 は全部 No Operation なので、16以降のみ)
/*static*/ const char * const
m88200::commandname[] = {
	"No Operation",						// $10
	"No Operation",						// $11
	"No Operation",						// $12
	"No Operation",						// $13
	"Inv DCache Line",					// $14
	"Inv DCache Page",					// $15
	"Inv DCache Seg",					// $16
	"Inv DCache All",					// $17

	"Copyback DCache Line",				// $18
	"Copyback DCache Page",				// $19
	"Copyback DCache Seg",				// $1a
	"Copyback DCache All",				// $1b
	"Copy&Inv DCache Line",				// $1c
	"Copy&Inv DCache Page",				// $1d
	"Copy&Inv DCache Seg",				// $1e
	"Copy&Inv DCache All",				// $1f

	"Probe.U",							// $20
	"Probe.U",							// $21
	"Probe.U",							// $22
	"Probe.U",							// $23
	"Probe.S",							// $24
	"Probe.S",							// $25
	"Probe.S",							// $26
	"Probe.S",							// $27

	"Probe.U",							// $28
	"Probe.U",							// $29
	"Probe.U",							// $2a
	"Probe.U",							// $2b
	"Probe.S",							// $2c
	"Probe.S",							// $2d
	"Probe.S",							// $2e
	"Probe.S",							// $2f

	"InvPATC.U Line",					// $30
	"InvPATC.U Page",					// $31
	"InvPATC.U Seg",					// $32
	"InvPATC.U All",					// $33
	"InvPATC.S Line",					// $34
	"InvPATC.S Page",					// $35
	"InvPATC.S Seg",					// $36
	"InvPATC.S All",					// $37

	"InvPATC.U Line",					// $38
	"InvPATC.U Page",					// $39
	"InvPATC.U Seg",					// $3a
	"InvPATC.U All",					// $3b
	"InvPATC.S Line",					// $3c
	"InvPATC.S Page",					// $3d
	"InvPATC.S Seg",					// $3e
	"InvPATC.S All",					// $3f
};

// SCR の読み出し
uint32
m88200::GetSCR() const
{
	// b31-b6 (reserved) の読み出し値は未定義らしい。
	// b5-b0 (Command Code) の読み出し値はマニュアルに記載がないけど
	// たぶんそのまま読めるのかな。
	return command;
}

// SCR への書き込み
void
m88200::SetSCR(uint32 data)
{
	command = data & 0x3f;

	// %00'XXXX No Operation
	// %01'00XX No Operation
	// %01'01gg Data Cache Invalidate
	// %01'10gg Data Cache Copyback to Memory
	// %01'11gg Data Cache Copyback and Invalidate
	// %10'X0XX Probe User Address
	// %10'X1XX Probe Supervisor Address
	// %11'X0gg Invalidate User PATC Descriptors
	// %11'X1gg Invalidate Supervisor PATC Descriptors

	if (__predict_false(loglevel >= 1)) {
		char sarbuf[16];
		const char *name;
		if (command < 0x10) {
			name = commandname[0];
		} else {
			name = commandname[command - 0x10];
		}

		// ログレベル1ならまとめて表示。
		if (loglevel == 1) {
			snprintf(sarbuf, sizeof(sarbuf), " SAR=$%08x", GetSAR());
		} else {
			sarbuf[0] = '\0';
		}

		putlogn("SCR  <- $%0*x (%s)%s",
			(data > 0x3f ? 8 : 2), data, name, sarbuf);
	}

	switch (command) {
	 case 0x00 ... 0x13:	// No Operation
		return;

	 case 0x14 ... 0x1f:	// Flush Data Cache
		FlushCacheCmd();
		return;

	 case 0x20 ... 0x23:	// Probe User Address
	 case 0x28 ... 0x2b:
		putlog(0, "SCR Command: Probe User Address (NOT IMPLEMENTED)");
		return;

	 case 0x24 ... 0x27:	// Probe Supervisor Address
	 case 0x2c ... 0x2f:
		putlog(0, "SCR Command: Probe Supervisor Address (NOT IMPLEMENTED)");
		return;

	 case 0x30 ... 0x3f:	// Invalidate {User,Supervisor} PATC Descriptors
		InvalidatePATCCmd();
		return;
	}
	PANIC("should not reach: command=$%02x", command);
}

// command に応じてデータキャッシュをフラッシュする。SetSCR() の下請け。
// command が $14..$1f でのみ呼ぶこと。
// p3-18, Section 3.7
void
m88200::FlushCacheCmd()
{
	uint32 op = command & 0x3c;
	uint32 gg = command & 0x03;
	uint32 addr;
	bool copyback;
	bool invalidate;

	assert(0x14 <= op && op <= 0x1f);

	// op       CopyBack Invalidate
	// %01'01gg false    true       | Data Cache Invalidate
	// %01'10gg true     false      | Data Cache Copyback to Memory
	// %01'11gg true     true       | Data Cache Copyback and invalidate

	// op によって書き戻しと無効化の組み合わせが異なる
	invalidate = (bool)(op & 0x04);
	copyback   = (bool)(op & 0x08);

	// 影響範囲
	addr = GetSAR();
	switch (gg) {
	 case GG_LINE:
		addr &= 0xfffffff0U;
		break;
	 case GG_PAGE:
		addr &= 0xfffff000U;
		break;
	 case GG_SEG:
		addr &= 0xffc00000U;
		break;
	 case GG_ALL:
		break;
	 default:
		__unreachable();
	}

	// サイクル数のうち固定費部分 (Table.6-1)。
	// Copyback&Invalidate の場合どうなるのか不明だが、
	// とりあえず悪い方に見積もって単純加算しておくか。
	uint32 cycle = 0;
	if (invalidate) {
		static uint32 invalidate_cycles[4] = { 1, 256, 1024, 256 };
		cycle += invalidate_cycles[gg];
	}
	if (copyback) {
		static uint32 copyback_cycles[4] = { 1, 256, 1024, 1024 };
		cycle += copyback_cycles[gg];
	}

	for (auto& set : setarray) {
		for (uint line = 0; line < 4; line++) {
			// 条件にマッチするか
			bool match;
			switch (gg) {
			 case GG_LINE:
				match = (addr == (set.tag[line] | (set.setidx << 4)));
				break;
			 case GG_PAGE:
				match = (addr == set.tag[line]);
				break;
			 case GG_SEG:
				match = (addr == (set.tag[line] & 0xffc00001U));
				break;
			 case GG_ALL:
				match = true;
				break;
			 default:
				__unreachable();
			}
			if (!match)
				continue;

			// Copyback ならまず EM なエントリを書き戻す。
			if (copyback) {
				// 本文には見当たらないが Fig.3-10、Fig.3-11 の状態遷移図を
				// 見ると、Unmodified なら変化なし、EM なら EU に移行するはず。
				if (set.vv[line] == m88200CacheSet::EM) {
					cycle += 7;	// Table.6-1
					CopyBackLine(set, line);
					set.Update(line, m88200CacheSet::EU);
				}
			}

			// Invalidate なら無効化する
			if (invalidate) {
				set.Update(line, m88200CacheSet::IV);
			}
		}
	}

	parent->AddCycle(cycle);
}

// command に応じて PATC を無効化する。SetSCR() の下請け。
// command が $30..$3f で呼ぶこと。
// p2-9, Section 2.2.4
void
m88200::InvalidatePATCCmd()
{
	uint32 gg = command & 0x03;
	bool s = (command & 0x04);
	uint32 addr;
	uint32 mask;

	if (__predict_true(gg == GG_PAGE)) {
		// 指定の1本だけ無効にする。
		// Page は呼び出し回数が多いのと1本だけならハッシュで引けるので別対応。
		STAT(stat_patc_invcmd_page);

		// ゲスト側が指定した端数部分は有効部分でマスクする。
		// 内部では下位ビットはフラグ扱いなのでマスクしないと誤動作する。
		addr = GetSAR() & 0xfffff000U;
		addr |= (s) ? PATC_S : 0;

		auto& hash = (s) ? patc_hash_s : patc_hash_u;
		uint64 pi = hash[patc_hash_func(addr)];

		for (; pi; pi &= pi - 1) {
			int i = __builtin_ctzll(pi);
			m88200PATC& p = patc[i];
			if (p.lpa == addr) {
				p.lpa |= PATC_INVALID;
				InvalidatePATCHash(i);
				patc_free |= 1ULL << i;
				STAT(stat_patc_invalidate);
				// 1本ヒットすればこれ以上一致することはないはず。
				break;
			}
		}
	} else {
		// 指定範囲を無効にする。All と Segment はマスクが違うだけ。

		if (gg == GG_ALL) {
			// S/U 指定したほう全体を無効にする
			STAT(stat_patc_invcmd_all);
			mask = 0;
		} else if (gg == GG_SEG) {
			// S/U 指定したほうの指定セグメント範囲全部を無効にする
			STAT(stat_patc_invcmd_seg);
			mask = 0xffc00000U;
		} else {
			// GG_LINE はマニュアルにどうなるか書いてない
			putlog(0, "Undefined Invalidate PATC Line");
			return;
		}

		addr = GetSAR() & mask;
		addr |= (s) ? PATC_S : 0;

		mask |= PATC_S | PATC_INVALID;

		for (uint i = 0; i < patc.size(); i++) {
			m88200PATC& p = patc[i];
			if ((p.lpa & mask) == addr) {
				// 無効化
				// XXX 削除した結果穴が空いても詰める処理は未実装
				p.lpa |= PATC_INVALID;
				InvalidatePATCHash(i);
				patc_free |= 1ULL << i;
				STAT(stat_patc_invalidate);
			}
		}
	}
}

// SCTR レジスタへの書き込み
void
m88200::SetSCTR(uint32 data)
{
	sctr = data & (SCTR_PE | SCTR_SE | SCTR_PR);

	// 全 CMMU について、それぞれスヌープ相手になる CMMU リストを更新。
	// どの CMMU の SCTR への書き込みでも毎回全ての CMMU を書き換える。
	std::array<m88200*, 8> cmmu {};
	uint n = cmmu.size();
	for (uint i = 0; i < n; i++) {
		cmmu[i] = gMainApp.FindObject<m88200>(OBJ_M88200(i));
	}
	for (uint i = 0; i < n; i++) {
		if (cmmu[i] == NULL)
			continue;
		cmmu[i]->other_cmmu.clear();
		for (uint j = 0; j < n; j++) {
			if (cmmu[j] == NULL || i == j)
				continue;
			if ((cmmu[j]->sctr & SCTR_SE)) {
				cmmu[i]->other_cmmu.push_back(cmmu[j]);
			}
		}
	}

	std::string msg;
	if ((sctr & SCTR_PE))
		msg += ",PE";
	if ((sctr & SCTR_PR))
		msg += ",PR";
	if (msg.length() > 0) {
		putlog(0, "SCTR <- $%08x (%s NOT IMPLEMENTED)", data, msg.c_str() + 1);
	}
}

// SAPR, UAPR レジスタ値の読み出し (S/U ビット指定)
uint32
m88200::GetAPR(uint issuper) const
{
	if (issuper) {
		return GetAPR(sapr);
	} else {
		return GetAPR(uapr);
	}
}

// SAPR, UAPR レジスタ値の読み出し (実体指定)
uint32
m88200::GetAPR(const m88200APR& xapr) const
{
	uint32 data;

	data  = xapr.addr & APR_ADDR_MASK;
	data |= xapr.stat & (APR_WT | APR_G | APR_CI);
	if (xapr.enable) {
		data |= APR_TE;
	}
	return data;
}

// SAPR, UAPR レジスタへの書き込み (実体指定)
void
m88200::SetAPR(m88200APR& xapr, uint32 data)
{
	bool old_enable = xapr.enable;
	xapr.addr = data & APR_ADDR_MASK;
	xapr.stat = data & (APR_WT | APR_G | APR_CI);
	xapr.enable = data & APR_TE;

	if (old_enable == false && xapr.enable == true) {
		// 変換開始
		putlog(1, "%s <- $%08x (Translation Enabled)", xapr.name, data);
	} else if (old_enable == true && xapr.enable == false) {
		// 変換停止
		putlog(1, "%s <- $%08x (Translation Disabled)", xapr.name, data);
	} else {
		// 変化なし
		putlog(1, "%s <- $%08x", xapr.name, data);
	}
}

// BWP(BATC Write Port) #n への書き込み
void
m88200::SetBWP(uint bn, uint32 data)
{
	assert(bn < 8);
	putlog(1, "BWP%u <- $%08x", bn, data);

	uint32 laddr =  data & BWP_LBA_MASK;
	uint32 paddr = (data & BWP_PBA_MASK) << 13;
	uint32 flags =  data & BWP_FLAG_MASK;

	// BATC の LBA は衝突してはいけない (が、どうなるかは書いてない)。
	// XXX 要実機検証…

	// とりあえず暗黙 BATC (#8, #9) と衝突する設定は無視しておく。
	if (laddr >= 0xfff00000U) {
		return;
	}

	SetBATC(bn, laddr, paddr, flags);
}

// BATC #n を更新する。
// n は 0-9 (暗黙 BATC も含む)。
// laddr の衝突は呼び出し側で回避してある。
void
m88200::SetBATC(uint n, uint32 laddr, uint32 paddr, uint32 flags)
{
	m88200BATC& b = batc[n];

	// BWP レジスタへの書き込み値と内部データ構造(stat)ではビット位置が違う
	// ことに注意。
	//
	//       31      10   9    8    7    6    5    4    3    2    1    0
	//      +----..-----+----+----+----+----+----+----+----+----+----+----+
	// BWP  |LBA              PBA           | S  | WT | G  | CI | WP | V  |
	//      +----..-----+----+----+----+----+----+----+----+----+----+----+
	//
	//       31      10   9    8    7    6    5    4    3    2    1    0
	//      +----..-----+----+----+----+----+----+----+----+----+----+----+
	// stat | 0      0  | WT | SP | G  | CI | 0  | M  | U  | WP | 0  | V  |
	//      +----..-----+----+----+----+----+----+----+----+----+----+----+

	memset(&b, 0, sizeof(b));
	b.lba  = laddr;
	b.lba |= (flags & BWP_S);
	if ((flags & BWP_V) == 0) {
		b.lba |= BATC_INVALID;
	}
	b.pba = paddr;
	if ((flags & BWP_WT))
		b.stat |= DESC_WT;
	if ((flags & BWP_G))
		b.stat |= DESC_G;
	if ((flags & BWP_CI))
		b.stat |= DESC_CI;
	if ((flags & BWP_WP)) {
		b.stat |= DESC_WP;
		b.wp = true;
	}

	MakeBATCHash();
}

// BATC ハッシュを作り直す。
void
m88200::MakeBATCHash()
{
	std::fill(batc_hash_s.begin(), batc_hash_s.end(), 0);
	std::fill(batc_hash_u.begin(), batc_hash_u.end(), 0);

	for (uint i = 0; i < batc.size(); i++) {
		m88200BATC& b = batc[i];

		if (b.IsValid()) {
			// LBA の下位 8 ビットだけでハッシュを作る。
			uint32 hashkey = batc_hash_func(b.lba);

			// S によって該当するハッシュの該当するビットを立てておく。
			if (b.IsS()) {
				batc_hash_s[hashkey] |= 1U << i;
			} else {
				batc_hash_u[hashkey] |= 1U << i;
			}
		}
	}

#if defined(M88200_STAT)
	// ハッシュの各要素が何個の衝突を持つか。
	// (2以上が極力現れないことが望ましい)
	auto counting = [](auto& stat, const auto& hash) {
		std::fill(stat.begin(), stat.end(), 0);
		for (const auto hashmap : hash) {
			int n = __builtin_popcount(hashmap);
			if (n >= stat.size()) {
				n = stat.size() - 1;
			}
			stat[n]++;
		}
	};
	counting(stat_batc_hash_s, batc_hash_s);
	counting(stat_batc_hash_u, batc_hash_u);
#endif
}

// CSSP レジスタの読み出し
uint32
m88200::GetCSSP() const
{
	uint32 setidx = (GetSAR() >> 4) & 0xff;
	const m88200CacheSet& set = setarray[setidx];

	uint32 data = 0;
	// L5-L0
	data |= set.L << 24;

	// XXX D3-D0 未実装

	// VV3-VV0
	data |= set.vv[3] << CSSP_VV3_OFFSET;
	data |= set.vv[2] << CSSP_VV2_OFFSET;
	data |= set.vv[1] << CSSP_VV1_OFFSET;
	data |= set.vv[0] << CSSP_VV0_OFFSET;

	return data;
}

// CSSP レジスタへの書き込み
void
m88200::SetCSSP(uint32 data)
{
	uint32 setidx = (GetSAR() >> 4) & 0xff;
	m88200CacheSet& set = setarray[setidx];

	putlog(1, "CSSP <- $%08x (set=$%02x)", data,setidx);

	// L5-L0 書き込み
	set.L = (data >> 24) & 0x3f;

	// XXX D3-D0 未実装

	// VV3-VV0
	set.vv[3] = (m88200CacheSet::Status)((data >> CSSP_VV3_OFFSET) & 3);
	set.vv[2] = (m88200CacheSet::Status)((data >> CSSP_VV2_OFFSET) & 3);
	set.vv[1] = (m88200CacheSet::Status)((data >> CSSP_VV1_OFFSET) & 3);
	set.vv[0] = (m88200CacheSet::Status)((data >> CSSP_VV0_OFFSET) & 3);
}

// S/U 信号線を設定。true なら Super、false なら User。
void
m88200::SetSuper(bool super)
{
	// acc_super は S/U ビットだけだと知っているので OR ではなく代入。
	if (super) {
		acc_super = BusAddr::S;
		acc_apr = &sapr;
		batc_hash = &batc_hash_s[0];
		patc_hash = &patc_hash_s[0];
	} else {
		acc_super = BusAddr::U;
		acc_apr = &uapr;
		batc_hash = &batc_hash_u[0];
		patc_hash = &patc_hash_u[0];
	}
}


//
// 物理バスアクセス
//

// MBus から読み込みを行う。
// paddr はアドレス、サイズ、S/U ビットのみを参照する。
// paddr のアドレスはそのサイズによって 1, 2, 4 バイト境界に整列していること。
// バスエラーなら SetFault() して BusData::BusErr を返す。
busdata
m88200::MBusRead(busaddr paddr)
{
	busdata bd;

	// m88k システムに接続しているデバイスはすべて、m68030 システムでいう
	// ところのロングワードポートデバイスなので応答は常に 4 バイト分ある。
	bd = mainbus->Read(paddr);
	parent->AddWait(bd.GetWait());
	if (__predict_false(bd.IsBusErr())) {
		SetFault(FAULT_CODE_BUSERR, paddr.Addr());
		return bd;
	}

	uint size = paddr.GetSize();
	if (size == 4) {
	} else if (size == 2) {
		if ((paddr.Addr() & 2) == 0) {
			bd >>= 16;
		} else {
			bd &= 0xffff;
		}
	} else {
		bd >>= (3 - (paddr.Addr() & 3)) * 8;
		bd &= 0xff;
	}

	return bd;
}

// MBus に書き込みを行う。
// paddr はアドレス、サイズ、S/U ビットのみを参照する。
// paddr のアドレスはそのサイズによって 1, 2, 4 バイト境界に整列していること。
// バスエラーなら SetFault() して BusData::BusErr を返す。
busdata
m88200::MBusWrite(busaddr paddr, uint32 data)
{
	busdata bd;

	bd = mainbus->Write(paddr, data);
	parent->AddWait(bd.GetWait());
	if (__predict_false(bd.IsBusErr())) {
		SetFault(FAULT_CODE_BUSERR, paddr.Addr());
	}
	return bd;
}

// MBus に xmem トランザクションを行う。
// paddr はアドレス、サイズ、S/U ビットのみを参照する。
// paddr のアドレスはそのサイズによって 1, 4 バイト境界に整列していること
// (2バイトはない)。
// MBus Acquire された状態で呼び出すこと。
// 成功すれば読み込めた値を返す。
// バスエラーなら SetFault() して BusData::BusErr を返す。
// read/write どちらでエラーが起きたかは acc_read で判別できる。
// p3-13, Figure3-7 の下半分のメインラインあたりに該当。
busdata
m88200::MBusXmem(busaddr paddr, uint32 data)
{
	busdata fetched;
	busdata bd;

	// Read data with intent to modify
	acc_read = true;
	bd = MBusRead(paddr);
	parent->AddWait(bd.GetWait());
	if (__predict_false(bd.IsBusErr())) {
		return bd;
	}
	fetched = bd;

	// Supply data to PBus
	// Reply = Success

	// Write data to memory
	acc_read = false;
	bd = MBusWrite(paddr, data);
	parent->AddWait(bd.GetWait());
	if (__predict_false(bd.IsBusErr())) {
		return bd;
	}

	return fetched;
}

//
// アクセス関数
//

template <uint size> busdata
m88200::load(uint32 addr)
{
	acc_laddr = addr;
	acc_read  = true;
	if (__predict_false(Translate() == false)) {
		putlog(2, "Translation BusError %c.$%08x",
			(IsSuper() ? 'S' : 'U'), acc_laddr);
		return BusData::BusErr;
	}

	busaddr baddr = busaddr(acc_paddr) | acc_super | busaddr::Size(size);
	busdata bd = PhysRead(baddr, acc_stat);
	return bd;
}

busdata
m88200::load_1(uint32 addr)
{
	return load<1>(addr);
}

busdata
m88200::load_2(uint32 addr)
{
	return load<2>(addr);
}

busdata
m88200::load_4(uint32 addr)
{
	return load<4>(addr);
}

template <uint size> busdata
m88200::store(uint32 addr, uint32 data)
{
	acc_laddr = addr;
	acc_read  = false;
	if (__predict_false(Translate() == false)) {
		return BusData::BusErr;
	}

	busaddr baddr = busaddr(acc_paddr) | acc_super | busaddr::Size(size);
	busdata r = PhysWrite(baddr, data, acc_stat);
	return r;
}

busdata
m88200::store_1(uint32 addr, uint32 data)
{
	return store<1>(addr, data);
}

busdata
m88200::store_2(uint32 addr, uint32 data)
{
	return store<2>(addr, data);
}

busdata
m88200::store_4(uint32 addr, uint32 data)
{
	return store<4>(addr, data);
}

template <uint size> busdata
m88200::xmem(uint32 addr, uint32 data)
{
	acc_laddr = addr;
	acc_read  = false;
	if (__predict_false(Translate() == false)) {
		return BusData::BusErr;
	}

	busdata bd = CacheXmem(acc_paddr, data, size);
	return bd;
}

busdata
m88200::xmem_1(uint32 addr, uint32 data)
{
	return xmem<1>(addr, data);
}

busdata
m88200::xmem_4(uint32 addr, uint32 data)
{
	return xmem<4>(addr, data);
}

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

// デバッグ用
/*static*/ std::string
m88200::stat2str(uint32 stat)
{
	std::string buf;

	if ((stat & DESC_WT))
		buf += ",WT";
	if ((stat & DESC_G))
		buf += ",G";
	if ((stat & DESC_CI))
		buf += ",CI";
	if ((stat & DESC_M))
		buf += ",M";
	if ((stat & DESC_U))
		buf += ",U";
	if ((stat & DESC_WP))
		buf += ",WP";

	if (buf.empty()) {
		buf = ",0";
	}
	return buf.substr(1);
}

// アドレス変換。
// 事前に acc_laddr, acc_super, acc_read を設定しておくこと。
// 成功すれば acc_paddr, acc_stat をセットして true を返す。
// 失敗なら SetFault() して false を返す。
// p2-3, Figure2-1
bool
m88200::Translate()
{
	uint32 la;
	uint8 n;
	uint32 bi;

	// Logical address(LA) presented on PBus

	putlog(3, "Translate %c.$%08x", (IsSuper() ? 'S' : 'U'), acc_laddr);

	// Select Area Descriptor
	if (SelectAreaDesc() == false) {
		// 変換しない場合でも暗黙 BWP は有効…。
		// マッチまではここで独自に行い、マッチしたら下の BWP へ合流。
		if (__predict_false(acc_laddr >= 0xfff00000U) && IsSuper()) {
			n = (acc_laddr < 0xfff80000U) ? 8 : 9;
			goto batc;
		}

		// XXX 図のこれはたぶん間違いだよなあ…
		// 誤: Physical Address <= LA[18-2] :: 00
		// 正: Physical Address <= LA[31-2] :: 00
		acc_paddr = acc_laddr;

		parent->AddCycle(1);
		return true;
	}

	// 変換する分だけカウントするのでいいか
	translate_total++;

	// type == Valid ここから

	// Search BATC first

	// まずざっくりハッシュを引く。
	bi = batc_hash[batc_hash_func(acc_laddr)];

	if (__predict_false(bi != 0)) {
		// ハッシュで一部がヒットしたので、
		// 次に LBA + S 全体との完全一致で探す。
		la = acc_laddr & 0xfff80000U;
		la |= IsSuper() ? BATC_S : 0;

		do {
			n = __builtin_ctz(bi);
			if (batc[n].lba == la) {
				// 無変換時に暗黙 BATC にヒットしたのはカウントしない。
				// ここは変換の何割が BATC にヒットしたかを知りたいので。
				batc_hit[n]++;
 batc:
				m88200BATC& b = batc[n];
				putlog(4, " BATC[%d] hit", n);
				if (b.wp && acc_IsWrite()) {
					goto write_violation;
				}

				// Physical Address <= PBA :: LA[18-2] :: 00
				acc_paddr = b.pba | (acc_laddr & 0x0007ffffU);
				acc_stat |= b.stat;
				putlog(4, " BATC hit acc=%s paddr=$%08x",
					stat2str(acc_stat).c_str(), acc_paddr);

				parent->AddCycle(1);
				return true;
			}
			// ヒットしたビットを 0 にする。
			bi &= bi - 1;
		} while (bi);
	}
	// 見付からなかったので PATC へ。

	// 2回しかループしないはず。
	for (uint loop = 0; loop < 3; loop++) {
		// BATC miss, search PATC then
		acc_patc = NULL;

		uint64 pi = patc_hash[patc_hash_func(acc_laddr)];
		STAT(stat_patc_search);

		if (pi == 0) {
			// 単純なサーチミスのみカウントし、M bit による合流は除きたい。
			STAT(stat_patc_miss1);
			goto patc_miss;
		}

		la = acc_laddr & 0xfffff000U;
		la |= IsSuper() ? PATC_S : 0;

		do {
			int i = __builtin_ctzll(pi);
			m88200PATC& p = patc[i];
			if (p.lpa == la) {
				// PATC hit
				patc_hit++;
				if (acc_IsWrite() && p.wp) {
					putlog(4, " PATC[%d] hit; stat=%s m=%d wp=%d", i,
						stat2str(p.stat).c_str(), p.m, p.wp);
					goto write_violation;
				}
				if (acc_IsWrite() && p.m == false) {
					// Update M bit
					// フローチャートでは PATC[M] はこの後のテーブルサーチ中
					// の Update Page Descriptor 処理中で更新するように書いて
					// あるように読めるのだが、その通りに実装すると、メモリ
					// 上の Page Descriptor に最初から M ビットが立っていると
					// (というか OpenBSD カーネルが用意した Page Descriptor
					// には立っているので) PATC[M] の更新も行われず、PATC[M]
					// が立っていないので再びここに来てしまい無限ループになる。
					// 誰が間違ってるのか分からないけど、とりあえずここで
					// PATC[M] を立てれば問題は起きない。
					putlog(4, " PATC[%d] hit; Need to update M bit", i);
					p.m = true;

					// この下の PATC miss に合流する。
					acc_patc = &p;
					goto patc_miss;
				}

				// Physical Address <= PFA :: LA[11-2] :: 00
				acc_paddr = p.pfa | (acc_laddr & 0x00000fffU);
				acc_stat |= p.stat;
				putlog(4, " PATC[%d] hit acc=%s paddr=$%08x", i,
					stat2str(acc_stat).c_str(), acc_paddr);

				parent->AddCycle(1);
				return true;
			}
			// ヒットしたビットを 0 にする。
			pi &= pi - 1;
		} while (pi);

		// 単純なサーチミスのみカウントし、M bit による合流は除きたい。
		STAT(stat_patc_miss2);
 patc_miss:
		putlog(4, " BATC/PATC miss");

		// PATC miss

		// PATC にヒットして M bit 更新するものは両方カウントされるけど。
		atc_miss++;

		// フローチャートでは PATC miss というラベルが付いてるが、コード
		// では acc_patc は PATC ループ前に NULL にしてあり(=miss)、
		// ループで PATC エントリが見付かれば acc_patc に代入してから
		// break してここに来るので、ちょっとだけ見た目と違うが意図してる
		// 動作は同じはず。

		// Table search operation
		if (TableSearch() == false) {
			goto invalid;
		}

		// フローチャートでは Select Area Descriptor まで戻るように
		// 書いてあるが、PATC ミスによるテーブルサーチでは Area Descriptor
		// と BATC は変化せず PATC が変化するだけなので、PATC サーチから
		// やり直す。
		continue;
	}
	PANIC("loop detected");

 write_violation:
	// この場合アドレス (PFAR) は破棄 (Table 2-2)。
	SetFault(FAULT_CODE_WRITE, 0xccccccccU);
 invalid:
	// Reply with fault (Figure2-10 だがここではすべて不要)
	return false;
}

// Select Area Descriptor.
// 結果が Type=Valid なら acc_sdaddr にセグメントアドレス、acc_stat に
// WT, G, C ビットを代入し true を返す。
// Type=Untranslated なら acc_stat のみ代入し false を返す。
// Probe Command からも呼ばれる (はずだが未実装)。
// p2-21, Figure2-5
bool
m88200::SelectAreaDesc()
{
	// TE=0 の場合でも有効。
	acc_stat = acc_apr->stat;

	if (acc_apr->enable) {
		// セグメントディスクリプタのアドレスはここで決まるが、
		// 次に行う BATC、PATC サーチでは使わず、それらが全部ミスして
		// テーブルサーチまで来た所で初めて使うので、ここでは表示しない。
		acc_sdaddr = acc_apr->addr | ((acc_laddr >> 20) & ~3U);
		putlog(4, " %s acc=%s", acc_apr->name, stat2str(acc_stat).c_str());
		return true;
	} else {
		putlog(3, " %s acc=%s Untranslated",
			acc_apr->name, stat2str(acc_stat).c_str());
		return false;
	}
}

// Table Search
// Type=Valid なら true、Type=Invalid なら SetFault() して false を返す。
// Type=Retry はない。
// p2-20, Figure2-4
bool
m88200::TableSearch()
{
	bool rv = false;

	putlog(4, " %s $%08x/%s acc=%s", __func__, acc_laddr,
		(acc_read ? "Read" : "Write"), stat2str(acc_stat).c_str());

	// XXX フローチャートを厳密に追っかけると MBus の Acquire/Release が
	// 対応していないので(orz)、ここでは
	// TableSearch() に入ったところで Acquire、
	// TableSearch() から出るところで Release だけに統一する。

	MBusAcquire();

	if (FetchSegmentDesc() == false) {
		// SetFault() 済み。
		parent->AddCycle(7);	// Table.6-2
		goto done;
	}

	if (FetchPageDesc() == false) {
		// SetFault() 済み。
		parent->AddCycle(11);	// Table.6-2
		goto done;
	}

	// 中央の TYPE = VALID のところ
	//                       ( )
	//            +----------+ +----------+
	//            |                       |
	//      DESC[U]=0 ||              Otherwise
	//  (DESC[M]=0 && WRITE)              |
	//            |                       |
	//     UpdatePageDesc                 |
	//            |                       |
	// <- RETRY -( )-- INVALID ---------- | -->
	//            |                       |
	//          VALID                     |
	//            |                       |
	//           ( ) <--------------------+
	//           | |
	//       +---+ +------------+
	//   Otherwise    TableSearch due to PATC miss
	//       |                  |
	//  (Only U/M bits     CreatePATCEntry
	//   Updated)               |
	//       |                  v
	//       +---------------> ( )

	if ((tmp_desc & DESC_U) == 0 ||
	    ((tmp_desc & DESC_M) == 0 && acc_IsWrite()))
	{
		parent->AddCycle(15);	// Table.6-2

		if (UpdatePageDesc() == false) {
			// SetFault() 済み。
			goto done;
		}
	} else {
		parent->AddCycle(11);	// Table.6-2
	}

	if (acc_patc == NULL) {
		// Table search due to PATC miss
		CreatePATCEntry();
	}

	rv = true;
 done:
	MBusRelease();
	return rv;
}

// Fetch Segment Descriptor
// Type=Valid なら true、Type=Invalid なら SetFault() して false を返す。
// Type=Retry はない。
// 公式フローチャートと違って MBus Acquire された状態で呼び出すこと。
// p2-22, Figure2-6
bool
m88200::FetchSegmentDesc()
{
	busdata data;

	MBusMakeSnoop(acc_sdaddr, IM_0);
	busaddr baddr = busaddr(acc_sdaddr) | BusAddr::S | BusAddr::Size4;
	// ディスクリプタの読み込みは自動的に CI (p3-18 3.6)。
	data = PhysRead(baddr, DESC_CI);
	if (__predict_false(data.IsBusErr())) {
		// SetFault() 済み。
		return false;
	}
	tmp_desc = data.Data();
	tmp_desc &= (0xfffff000U |
		DESC_WT | DESC_SP | DESC_G | DESC_CI | DESC_WP | DESC_V);

	if (__predict_false((tmp_desc & DESC_V) == 0)) {
		SetFault(FAULT_CODE_SEGMENT, acc_sdaddr);
		putlog(4, "  SD $%08x desc=$%08x SegFault", acc_sdaddr, tmp_desc);
		return false;
	} else {
		// Descriptor is valid

		if (__predict_false((tmp_desc & DESC_SP) && IsUser())) {
			SetFault(FAULT_CODE_SUPERVISOR, acc_sdaddr);
			putlog(4, "  SD $%08x desc=$%08x SupervisorFault",
				acc_sdaddr, tmp_desc);
			return false;
		}

		acc_pdaddr =
			busaddr((tmp_desc & 0xfffff000U) | ((acc_laddr >> 10) & 0xffc))
			| BusAddr::S | BusAddr::Size4;
		acc_stat |= tmp_desc & ACC_STAT_MASK;
		putlog(4, "  SD $%08x pdaddr=$%08x stat=%s acc=%s",
			acc_sdaddr, acc_pdaddr.Addr(),
			stat2str(tmp_desc).c_str(),
			stat2str(acc_stat).c_str());
		return true;
	}
}

// Fetch Page Descriptor
// Type=Valid なら true、Type=Invalid なら SetFault() して false を返す。
// Type=Retry はない。
// 公式フローチャートと違って MBus Acquire された状態で呼び出すこと。
// p2-23, Figure2-7
bool
m88200::FetchPageDesc()
{
	busdata data;

	MBusMakeSnoop(acc_pdaddr.Addr(), IM_0);
	// ディスクリプタの読み込みは自動的に CI (p3-18 3.6)。
	data = PhysRead(acc_pdaddr, DESC_CI);
	if (__predict_false(data.IsBusErr())) {
		// SetFault() 済み。
		return false;
	}
	tmp_desc = data.Data();
	tmp_desc &= (0xfffff000U | DESC_WT | DESC_SP | DESC_G | DESC_CI |
		DESC_M | DESC_U | DESC_WP | DESC_V);

	if (__predict_false((tmp_desc & DESC_V) == 0)) {
		SetFault(FAULT_CODE_PAGE, acc_pdaddr.Addr());
		putlog(4, "  PD $%08x desc=$%08x PageFault",
			acc_pdaddr.Addr(), tmp_desc);
		return false;
	} else {
		// Descriptor is valid

		if (__predict_false((tmp_desc & DESC_SP) && IsUser())) {
			SetFault(FAULT_CODE_SUPERVISOR, acc_pdaddr.Addr());
			putlog(4, "  PD $%08x desc=$%08x SupervisorFault",
				acc_pdaddr.Addr(), tmp_desc);
			return false;
		}

		acc_stat |= tmp_desc & ACC_STAT_MASK;

		// Fig.2-7 ではここで Write Violation のテストをするとある。
		// 一方、p2-19 の本文ではここではテストせず、PATC エントリを作って
		// 再サーチでこの PATC に当たってそこでテストすると書いてある
		// (Table 2-2 にもディスクリプタ取得中の Write Violation はないので
		// 本文の内容を支持しているように見える) が、
		// そうすると動かないので、本文が誤りだと思うことにする…。
		if (__predict_false((acc_stat & DESC_WP) && acc_IsWrite())) {
			// fault_addr は invalid data
			SetFault(FAULT_CODE_WRITE, 0xccccccccU);
			putlog(4, "  PD $%08x desc=$%08x WriteFault",
				acc_pdaddr.Addr(), tmp_desc);
			return false;
		} else {
			putlog(4, "  PD $%08x stat=%s acc=%s", acc_pdaddr.Addr(),
				stat2str(tmp_desc).c_str(),
				stat2str(acc_stat).c_str());
			return true;
		}
	}
}

// Update Page Descriptor
// Type=Valid なら true、Type=Invalid なら SetFault() して false を返す。
// Type=Retry はない。
// p2-24, Figure2-8
bool
m88200::UpdatePageDesc()
{
	if ((tmp_desc & DESC_M) == 0 && acc_IsWrite()) {
		// Update Modified bit and accrue status

		// XXX 図中 PATC[M] をセットするとあるが、ここでは遅い気がする。
		// Translate() 中の Update M bit のところのコメントも参照。

		// XXX 図中 ACC_STATUS[M] は TEMP_DESCR[M] じゃないの?
		tmp_desc |= DESC_M;
	}

	// Update Used bit
	tmp_desc |= DESC_U;

	// ディスクリプタへのアクセスは自動的に CI (p3-18 3.6)。
	busdata r = PhysWrite(acc_pdaddr, tmp_desc, DESC_CI);
	if (__predict_false(r.IsBusErr())) {
		// SetFault() 済み。
		return false;
	}

	putlog(4, "  Update PD $%08x desc=$%08x stat=%s",
		acc_pdaddr.Addr(), tmp_desc, stat2str(tmp_desc).c_str());

	return true;
}

// patc[patc_index] に対応する PATC ハッシュを無効にする。
void
m88200::InvalidatePATCHash(int patc_index)
{
	m88200PATC& p = patc[patc_index];
	int hashkey = patc_hash_func(p.lpa);

	uint64 bit = 1ULL << patc_index;
	if (p.IsS()) {
		patc_hash_s[hashkey] &= ~bit;
	} else {
		patc_hash_u[hashkey] &= ~bit;
	}
}

// Create PATC Entry
// 公式フローチャートと違って MBus Acquire したまま戻ること。
// p2-25, Figure2-9
void
m88200::CreatePATCEntry()
{
	// 空きエントリがあればそれを使う。なければ一番古いエントリを使う。
	// 検索は線形探索ではなくハッシュで実装してあるので、途中に空きエントリが
	// あっても (線形探索する時のような) デメリットはない。
	// 最初は全エントリが空きで、エントリが埋まるまでは patc_next は使われない。
	// 一度エントリが埋まると patc_next が生贄になる。

	STAT(stat_patc_create);

	uint pi;
	if (patc_free != 0) {
		// 空きがあればそれを使う。
		pi = __builtin_ctzll(patc_free);
		patc_free &= ~(1ULL << pi);
	} else {
		// 空きがなければ FIFO。
		pi = patc_next;
		if (__predict_false(++patc_next >= patc.size())) {
			patc_next = 0;
		}

		// 有効なはず。
		InvalidatePATCHash(pi);
	}
	m88200PATC& p = patc[pi];

	// このエントリを潰して作る
	memset(&p, 0, sizeof(p));
	p.lpa = acc_laddr & 0xfffff000U;
	p.pfa = tmp_desc & 0xfffff000U;
	if (IsSuper()) {
		p.lpa |= PATC_S;
	}
	p.stat = acc_stat;
	putlog(4, "  %s %c.$%08x:$%08x stat=%s", __func__,
		p.IsS() ? 'S' : 'U', (p.lpa & 0xfffff000U), p.pfa,
		stat2str(p.stat).c_str());

	uint64 hashkey = patc_hash_func(p.lpa);
	uint64 bit = 1ULL << pi;
	patc_hash[hashkey] |= bit;
}

// アドレス変換 (デバッガ用)
busaddr
m88200::TranslatePeek(busaddr addr_) const
{
	const m88200APR *xapr;
	uint32 laddr = addr_.Addr();
	bool issuper = addr_.IsSuper();
	uint32 la;
	uint n;

	// Select Area Descriptor
	if (issuper) {
		xapr = &sapr;
	} else {
		xapr = &uapr;
	}
	if (xapr->enable == 0) {
		// 変換しない場合でも暗黙 BWP は有効…。
		if (laddr >= 0xfff00000U && issuper) {
			// 必ず成立するはず。
			n = 8;
			goto batc;
		}
		return busaddr(laddr);
	}

	// Search BATC first
	n = 0;
 batc:
	la = laddr & 0xfff80000U;
	la |= issuper ? BATC_S : 0;
	for (; n < batc.size(); n++) {
		const auto& b = batc[n];
		if (b.lba == la) {
			// BATC hit
			return busaddr(b.pba | (laddr & 0x0007ffffU));
		}
	}

	// BATC miss, search PATC then
	la = laddr & 0xfffff000U;
	la |= issuper ? PATC_S : 0;
	for (const auto& p : patc) {
		if (p.lpa == la) {
			// PATC hit
			return busaddr(p.pfa | (laddr & 0x00000fffU));
		}
	}

	// ここからテーブルサーチ
	uint64 data;
	uint32 desc_addr;
	uint32 desc;

	// Peek Segment Descriptor
	desc_addr = xapr->addr | ((laddr >> 20) & ~3U);
	data = mainbus->Peek4(desc_addr);
	if ((int64)data < 0) {
		return BusAddr::BusErr | BusAddr::TableSearched;
	}
	desc = (uint32)data;
	if ((desc & DESC_V) == 0) {
		return BusAddr::BusErr | BusAddr::TableSearched;
	}
	if ((desc & DESC_SP) && issuper == false) {
		return BusAddr::BusErr | BusAddr::TableSearched;
	}

	// Peek Page Descriptor
	desc_addr = (desc & 0xfffff000U) | ((laddr >> 10) & 0xffc);
	data = mainbus->Peek4(desc_addr);
	if ((int64)data < 0) {
		return BusAddr::BusErr | BusAddr::TableSearched;
	}
	desc = (uint32)data;
	if ((desc & DESC_V) == 0) {
		return BusAddr::BusErr | BusAddr::TableSearched;
	}
	if ((desc & DESC_SP) && issuper == false) {
		return BusAddr::BusErr | BusAddr::TableSearched;
	}

	busaddr r((desc & 0xfffff000U) | (laddr & 0x00000fffU));
	r |= BusAddr::TableSearched;
	return r;
}


//
// 物理アドレスアクセス
//

// paddr はアドレス、サイズ、S/U ビットのみを参照する。
// paddr のアドレスはそのサイズによって 1, 2, 4 バイト境界に整列していること。
// stat はこのアクセスの制御ビット DESC_WT, DESC_G, DESC_CI。
// 戻り値は、読み込めればその値 (右詰め)。
// バスエラーが起きれば SetFault して BusData::BusErr を返す。
busdata
m88200::PhysRead(busaddr paddr, uint32 stat)
{
	if ((stat & DESC_CI)) {
		// キャッシュ禁止の場合の説明が少ない…。
		// メモリから読み込んで、キャッシュには置かない。
		// その際キャッシュに同エントリが存在していれば
		// *たとえ EM でもコピーバックせずに* 破棄する (p3-18 の 3.6 あたり)。

		uint32 tagaddr = (paddr.Addr() & 0xfffff000U);
		uint32 setidx  = (paddr.Addr() >> 4) & 0xff;
		m88200CacheSet& set = setarray[setidx];

		int line = set.Lookup(tagaddr);
		if (__predict_false(line >= 0)) {
			set.Update(line, m88200CacheSet::IV);
		}

		// とりあえず。Table 6-2
		parent->AddCycle(7);

		MBusAcquire();
		MBusMakeSnoop(paddr.Addr(), IM_0);
		busdata bd = MBusRead(paddr);
		MBusRelease();
		// bd.Data は下詰めになっている。エラー時は SetFault() 済み。
		return bd;
	} else {
		// キャッシュ許可の場合。
		busdata data;

		data = CacheRead(paddr.Addr() & ~3U);
		if (__predict_false(data.IsBusErr())) {
			// SetFault() 済み。
			return data;
		}

		uint size = paddr.GetSize();
		if (__predict_true(size == 4)) {
			return data;
		} else if (__predict_true(size == 1)) {
			switch (paddr.Addr() & 3U) {
			 case 0:
				return (data >> 24);
			 case 1:
				return (data >> 16) & 0xff;
			 case 2:
				return (data >>  8) & 0xff;
			 case 3:
				return data & 0xff;
			 default:
				__unreachable();
			}
		} else if (__predict_true(size == 2)) {
			if ((paddr.Addr() & 3U) == 0) {
				return data >> 16;
			} else {
				return data & 0xffff;
			}
		} else {
			PANIC("size must be 1, 2, 4");
		}
	}
}

// paddr はアドレス、サイズ、S/U ビットのみを参照する。
// paddr のアドレスはそのサイズによって 1, 2, 4 バイト境界に整列していること。
// data は右詰め。
// stat はこのアクセスの制御ビット DESC_WT, DESC_G, DESC_CI。
// 書き込めれば 0、バスエラーが起きれば SetFault() して BusData::BusErr を返す。
busdata
m88200::PhysWrite(busaddr paddr, uint32 data, uint32 stat)
{
	if ((stat & DESC_CI)) {
		// キャッシュ禁止の場合の説明が少ない…。
		// キャッシュには置かずにメモリに書き出す。
		// その際キャッシュに同エントリが存在していれば
		// *たとえ EM でもコピーバックせずに* 破棄する (p3-18 の 3.6 あたり)。

		uint32 tagaddr = (paddr.Addr() & 0xfffff000U);
		uint32 setidx  = (paddr.Addr() >> 4) & 0xff;
		m88200CacheSet& set = setarray[setidx];

		int line = set.Lookup(tagaddr);
		if (__predict_false(line >= 0)) {
			set.Update(line, m88200CacheSet::IV);
		}

		// とりあえず。Table 6-2
		parent->AddCycle(7);

		MBusAcquire();
		MBusMakeSnoop(paddr.Addr(), IM_1);
		busdata bd = MBusWrite(paddr, data);
		MBusRelease();
		return bd;
	} else {
		// キャッシュ許可の場合。
		return CacheWrite(paddr.Addr(), data, paddr.GetSize());
	}
}


//
// キャッシュ
//

// このラインの状態を更新
void
m88200CacheSet::Update(uint line, m88200CacheSet::Status status)
{
	vv[line] = status;
	if (__predict_false(status == IV)) {
		// 無効なら
		tag[line] |= TAG_INVALID;
		L = TryUnuseLine(L, line);
	}
}

// CSSP L5-L0 の処理。
// 引数 tmpL の状態から line を最新にしたらどうなるか、を返す。
// 表示処理で一時変数に対して処理が必要なため分離してある。
/*static*/ uint
m88200CacheSet::TryUseLine(uint tmpL, int line)
{
	// L フィールドは 3bit, 2bit, 1bit で構成され、
	// 他のラインの対応ビットを落として、
	// 自分のラインの対応ビットを全部立てれば、
	// 他のラインの順序を保存した状態で、自分が最新になるように
	// 順序をつけられる。

	if (line == 3) {
		tmpL |= 0b111'00'0;
	} else if (line == 2) {
		tmpL &= 0b011'11'1;
		tmpL |= 0b000'11'0;
	} else if (line == 1) {
		tmpL &= 0b101'01'1;
		tmpL |= 0b000'00'1;
	} else {
		tmpL &= 0b110'10'0;
	}
	return tmpL;
}

// CSSP L5-L0 の処理。
// 引数 tmpL の状態から line を最古にしたらどうなるか、を返す。
/*static*/ uint
m88200CacheSet::TryUnuseLine(uint tmpL, int line)
{
	// Use のちょうど反転論理
	if (line == 3) {
		tmpL &= 0b000'11'1;
	} else if (line == 2) {
		tmpL |= 0b100'00'0;
		tmpL &= 0b111'00'1;
	} else if (line == 1) {
		tmpL |= 0b010'10'0;
		tmpL &= 0b111'11'0;
	} else {
		tmpL |= 0b001'01'1;
	}
	return tmpL;
}

// CSSP L5-L0 の処理。
// 引数 tmpL の状態で、最新の line を返す。
// 表示処理で一時変数に対して処理が必要なため分離してある。
/*static*/ int
m88200CacheSet::TryGetOldestLine(uint tmpL)
{
	if (tmpL < 8) {
		return 3;
	}
	tmpL &= 7;
	if (tmpL < 2) {
		return 2;
	}
	tmpL &= 1;
	if (tmpL == 0) {
		return 1;
	} else {
		return 0;
	}
}

// 更新用に最も古いラインを選んで差し出す
int
m88200CacheSet::SelectOldestLine() const
{
	// なければ、最も古いラインを差し出す
	return TryGetOldestLine(L);
}

// キャッシュの指定の line, word に data を書き込む。
// size は 1, 2, 4 バイト。
void
m88200CacheSet::Write(uint line, uint32 paddr, uint32 data, uint size)
{
	uint32 wordidx = (paddr >> 2) & 0x03;
	uint32 data32;

	if (__predict_true(size == 4)) {
		word[line * 4 + wordidx] = data;
		return;
	}

	data32 = word[line * 4 + wordidx];
	if (size == 2) {
		if ((paddr & 2) == 0) {
			data32 = (data32 & 0x0000ffffU) | (data << 16);
		} else {
			data32 = (data32 & 0xffff0000U) | data;
		}
	} else {
		switch (paddr & 3) {
		 case 0:
			data32 = (data32 & 0x00ffffffU) | (data << 24);
			break;
		 case 1:
			data32 = (data32 & 0xff00ffffU) | (data << 16);
			break;
		 case 2:
			data32 = (data32 & 0xffff00ffU) | (data << 8);
			break;
		 case 3:
			data32 = (data32 & 0xffffff00U) | data;
			break;
		 default:
			__unreachable();
		}
	}
	word[line * 4 + wordidx] = data32;
}

// キャッシュを検索。
// ヒットすれば line 番号(0..3) を返す。ヒットしなければ -1 を返す。
int
m88200CacheSet::Lookup(uint32 tagaddr) const
{
	for (uint line = 0; line < 4; line++) {
		if (tag[line] == tagaddr) {
			return line;
		}
	}
	return -1;
}


// キャッシュの指定の set, line にメモリから 1 ライン(4word) 読み込む。
// 成功すれば true を返す。
// バスエラーが起きれば SetFault() して false を返す。
bool
m88200::ReadLine(m88200CacheSet& set, uint line, uint32 tagaddr)
{
	// タグを更新
	set.tag[line] = tagaddr;

	busaddr addr = busaddr(set.tag[line] | (set.setidx << 4)) | acc_super;
	busdata r = mainbus->ReadBurst16(addr, &set.word[line * 4]);
	if (__predict_true(r.IsBusErr() == false)) {
		parent->AddWait(r.GetWait());
		return true;
	} else {
		addr |= BusAddr::Size4;
		for (uint i = 0; i < 4; i++) {
			busdata bd = mainbus->Read(addr);
			parent->AddWait(r.GetWait());
			if (__predict_false(bd.IsBusErr())) {
				SetFault(FAULT_CODE_BUSERR, addr.Addr());
				return false;
			}
			set.word[line * 4 + i] = bd.Data();
			addr += 4;
		}
	}
	return true;
}

// キャッシュの指定の set, line の1ライン(4word) を書き出す (コピーバック)。
// 成功すれば true を返す。
// バスエラーが起きれば SetFault() して false を返す。
// コピーバックの成功/失敗など状況に応じて、呼び出し元が set.Update() すること。
bool
m88200::CopyBackLine(m88200CacheSet& set, uint line)
{
	busaddr addr = busaddr(set.tag[line] | (set.setidx << 4)) | acc_super;
	busdata r = mainbus->WriteBurst16(addr, &set.word[line * 4]);
	if (__predict_true(r.IsBusErr() == false)) {
		parent->AddWait(r.GetWait());
		return true;
	} else {
		addr |= BusAddr::Size4;
		for (uint i = 0; i < 4; i++) {
			busdata bd = mainbus->Write(addr, set.word[line * 4 + i]);
			parent->AddWait(bd.GetWait());
			if (__predict_false(bd.IsBusErr())) {
				SetFault(FAULT_CODE_BUSERR, addr.Addr());
				return false;
			}
			addr += 4;
		}
	}
	return true;
}

// キャッシュに対して paddr の読み込みを行う。
// paddr は 32bit 境界のアドレスであること。
// 読み込めれば該当の32bitワードを返す。
// バスエラーが起きれば SetFault() して BusData::BusErr を返す。
// p.3-8 Figure 3-3
busdata
m88200::CacheRead(uint32 paddr)
{
	int line;

	assert((paddr & 3) == 0);

	// タグとセット番号
	uint32 tagaddr = (paddr & 0xfffff000U);
	uint32 setidx  = (paddr >> 4) & 0xff;
	uint32 wordidx = (paddr >> 2) & 0x03;

	m88200CacheSet& set = setarray[setidx];
	putlog(3, "CacheRead paddr=$%08x (set=$%02x)", paddr, setidx);

	line = set.Lookup(tagaddr);
	if (__predict_true(line >= 0)) {
		// Cache Hit
		putlog(4, " CacheRead hit (line=%u,word=%u)", line, wordidx);
		parent->AddCycle(1);
		goto success;
	}

	// Cache Miss

	parent->AddCycle(10);	// Table.6-2

	// reply <- Wait
	MBusAcquire();

	// Select cache line for replacement
	line = set.SelectOldestLine();
	putlog(4, " CacheRead miss (replace line=%u)", line);

	if (set.vv[line] == m88200CacheSet::EM) {
		parent->AddCycle(7);	// Table.6-2
		if (CopyBackLine(set, line) == false) {
			// SetFault() 済み。
			goto error;
		}
	}

	// Mark cache line invalid
	set.Update(line, m88200CacheSet::IV);

	// Read line from memory
	MBusMakeSnoop(paddr, IM_0);
	if (ReadLine(set, line, tagaddr) == false) {
		// SetFault() 済み。
		goto error;
	}

	MBusRelease();

	// Update cache line
	set.Update(line, m88200CacheSet::SU);

	putlog(4, " CacheRead updated (line=%u,word=%u)", line, wordidx);
 success:
	set.Use(line);
	return set.word[line * 4 + wordidx];

 error:
	MBusRelease();
	return BusData::BusErr;
}

// キャッシュに対して paddr への data の書き込みを行う。size は 1, 2, 4 バイト。
// paddr は size に応じた境界にあること。
// 書き込めれば 0、バスエラーが起きれば SetFault() して BusData::BusErr を返す。
// p.3-10 Figure 3-5
busdata
m88200::CacheWrite(uint32 paddr, uint32 data, uint size)
{
	int line;
	busaddr baddr;
	busdata bd;

	assert((paddr & (size - 1)) == 0);

	// タグとセット番号
	uint32 tagaddr = (paddr & 0xfffff000U);
	uint32 setidx  = (paddr >> 4) & 0xff;

	m88200CacheSet& set = setarray[setidx];
	putlog(3, "CacheWrite paddr=$%08x (set=$%02x)", paddr, setidx);

	line = set.Lookup(tagaddr);
	if (__predict_true(line >= 0)) {
		// Cache Hit
		parent->AddCycle(1);
		return CacheWriteHit(set, line, paddr, data, size);
	}

	// Cache Miss

	parent->AddCycle(14);	// Table.6-2

	// reply <- Wait
	MBusAcquire();

	// Select cache line for replacement
	line = set.SelectOldestLine();
	putlog(4, " CacheWrite miss (replace line=%u)", line);

	if (set.vv[line] == m88200CacheSet::EM) {
		parent->AddCycle(7);	// Table.6-2
		if (CopyBackLine(set, line) == false) {
			// SetFault() 済み。
			goto error;
		}
	}

	// Mark line invalid
	set.Update(line, m88200CacheSet::IV);

	// Read line with intent to modify
	MBusMakeSnoop(paddr, IM_1);
	if (ReadLine(set, line, tagaddr) == false) {
		// SetFault() 済み。
		goto error;
	}

	// Write data to memory
	baddr = busaddr(paddr) | acc_super | busaddr::Size(size);
	bd = MBusWrite(baddr, data);
	if (__predict_false(bd.IsBusErr())) {
		// SetFault() 済み。
		goto error;
	}

	// Write data into cache
	set.Write(line, paddr, data, size);

	// Mark line exclusive unmodified
	// ただし Fig.3-12 によると、WriteThrough の時は常に IV と SU の
	// 二状態しかないはずなので、ここは正しくは
	//  Mark line shared unmodified (If WT),
	//  Mark line exclusive unmodified (Otherwise)
	// なのでは。これなら Fig.3-6 とも整合する。
	if ((acc_stat & DESC_WT)) {
		set.Update(line, m88200CacheSet::SU);
	} else {
		set.Update(line, m88200CacheSet::EU);
	}
	set.Use(line);

	MBusRelease();
	return 0;

 error:
	MBusRelease();
	return BusData::BusErr;
}

// キャッシュがヒットした場合。
// 書き込めれば 0、バスエラーが起きれば SetFault して BusData::BusErr を返す。
// p3-11 Figure 3-6
busdata
m88200::CacheWriteHit(m88200CacheSet& set, uint line,
	uint32 paddr, uint32 data, uint size)
{
	if (__predict_false(set.vv[line] == m88200CacheSet::SU)) {
		// Line Shared Unmodified の場合

		putlog(4, " CacheWrite hit shared unmodified (line=%u)", line);

		if ((acc_stat & DESC_WT)) {
			// reply = Wait;
			MBusAcquire();

			// Write data to cache
			set.Write(line, paddr, data, size);

			// Write data to memory
			MBusMakeSnoop(paddr, IM_1);
			busaddr baddr = busaddr(paddr) | acc_super | busaddr::Size(size);
			busdata bd = MBusWrite(baddr, data);

			if (__predict_true(bd.IsOK())) {
				// Mark line shared unmodified
				set.Update(line, m88200CacheSet::SU);
				set.Use(line);
			}

			MBusRelease();
			return bd;
		} else if ((acc_stat & DESC_G)) {
			// reply = Wait;
			MBusAcquire();

			// Write data to cache
			set.Write(line, paddr, data, size);

			// Write data to memory
			MBusMakeSnoop(paddr, IM_1);
			busaddr baddr = busaddr(paddr) | acc_super | busaddr::Size(size);
			busdata bd = MBusWrite(baddr, data);

			if (__predict_true(bd.IsOK())) {
				// Mark line exclusive unmodified
				set.Update(line, m88200CacheSet::EU);
				set.Use(line);
			}

			MBusRelease();
			return bd;
		}

		// どちらでもない場合は Line Exclusive と同じ処理に落ちる
	} else {
		// Line Exclusive の場合
		putlog(4, " CacheWrite hit exclusive (line=%u)", line);
	}

	// Write data into cache
	putlog(4, " CacheWrite line=%u $%08x sz=%u", line, paddr, size);
	set.Write(line, paddr, data, size);

	// Mark line exclusive modified
	set.Update(line, m88200CacheSet::EM);
	set.Use(line);
	return 0;
}

// paddr への xmem を行う。size は 1, 4 バイト (2バイトはない)。
// 成功すれば読み出し値、エラーが起きれば SetFault して BusData::BusErr を返す。
// p3-13, Figure3-7
busdata
m88200::CacheXmem(uint32 paddr, uint32 data, uint size)
{
	int line;
	busdata bd;

	assert((paddr & (size - 1)) == 0);

	// タグとセット番号
	uint32 tagaddr = (paddr & 0xfffff000U);
	uint32 setidx  = (paddr >> 4) & 0xff;

	m88200CacheSet& set = setarray[setidx];
	putlog(3, "CacheXmem paddr=$%08x (set=$%02x)", paddr, setidx);

	// ちょっとフローチャートとは一対一対応しないけど、
	// 要はどのパスを通っても必要になる前に一度だけ MBusAcquire + Snoop する
	// ということのはず。
	// うちではキャッシュ操作と Mbus 操作の順を入れ替えても影響はない。
	MBusAcquire();
	MBusMakeSnoop(paddr, IM_1);

	line = set.Lookup(tagaddr);
	if (__predict_true(line >= 0)) {
		// Cache Hit

		// xmem はそもそも CI 相当だが、Read/Write と違って EM なら
		// コピーバックする。(p3-12 3.4.3)
		if (set.vv[line] == m88200CacheSet::EM) {
			// Line exclusive modified
			putlog(4, " CacheXmem hit and EM (line=%u)", line);

			parent->AddCycle(7);	// Table.6-2
			if (CopyBackLine(set, line) == false) {
				// SetFault() 済み。
				MBusRelease();
				return BusData::BusErr;
			}
		} else {
			// Otherwise
			putlog(4, " CacheXmem hit and unmodified (line=%u)", line);
		}
		set.Update(line, m88200CacheSet::IV);
	} else {
		// Cache Miss
		putlog(4, " CacheXmem miss");
	}

	busaddr baddr = busaddr(paddr) | acc_super | busaddr::Size(size);
	bd = MBusXmem(baddr, data);
	MBusRelease();
	// 失敗なら SetFault() 済み。
	return bd;
}

// MBus 使用権を取得する
void
m88200::MBusAcquire()
{
	// アービトレーションのたびに1クロックかかる(?)
	// よく分からんけど、とりあえず、CMMU は一度所有権を持ったら放さない、
	// 別の CMMU がバスリクエストすると所有権はその CMMU に移る、とする。
	if (__predict_false(mbus_master != this)) {
		mbus_master = this;
		parent->AddCycle(1);
	}
}

// 他 CMMU にバススヌープさせるポイント。
// 本来はバスマスタ CMMU が MBus にアドレスと IM を出すと、それを監視して
// いる他 CMMU が必要に応じて反応するのだが、ここでは全部マスタ主導で行う。
// Figure 3-8, 3-9
void
m88200::MBusMakeSnoop(uint32 paddr, bool im)
{
	if ((acc_stat & DESC_G) == 0) {
		return;
	}

	// MBus Status <- Wait (2 clocks)
	// 本来はスヌープをするスレーブ側が 2 クロックかかるのだが、
	// 個別の CMMU がクロックを持っていない実装なのでマスタ側で
	// クロックを消費したことにする。
	parent->AddCycle(2);

	// 本来はここで解放ではなく、スレーブ側が反応したい時にバスリクエストを
	// 出してそれによって手放すのだが、その機構はないので、こちらが自主的に
	// 一旦手放したようにしておく。とは言っても現状 MBusRelease() はダミーで
	// 実際には他の誰かが MBusAcquire() を呼ぶまで握りっぱなしなので、
	// あまり問題ないはず。
	MBusRelease();
	for (const auto other : other_cmmu) {
		other->Snoop(paddr, im);
	}
	MBusAcquire();
}

// バススヌープ (スレーブ側)。
// 本来はバスマスタでない CMMU は、バスマスタが MBus に出すアドレス情報を
// 監視し、必要ならそれに反応するのだが、ここでは全部マスタ主導で行う。
// こっちはそのマスタから呼ばれるスレーブ側。
// Figure 3-8, 3-9
void
m88200::Snoop(uint32 paddr, bool im)
{
	assert((sctr & SCTR_SE));

	uint32 tagaddr = (paddr & 0xfffff000U);
	uint32 setidx  = (paddr >> 4) & 0xff;

	m88200CacheSet& set = setarray[setidx];
	int line = set.Lookup(tagaddr);

	if (line >= 0) {
		// Cache hit
		if (set.vv[line] == m88200CacheSet::EM) {
			// Line exclusive modified

			// Assert retry

			// Request MBus
			// 実際にはここで MBus 使用権を要求して獲得するのだが
			// その機構はなくその代わり Acquire を呼ぶだけでいける。
			MBusAcquire();
			bool r = CopyBackLine(set, line);
			MBusRelease();
			if (r == false) {
				// Set CE bit in system status register
				return;
			}
		} else {
			// Otherwise
		}

		// Modified でも Unmodified でも IM によって SU/IV に変えるところは同じ。
		if (im == IM_0) {
			// Mark line shared unmodified (if IM=0)
			set.Update(line, m88200CacheSet::SU);
			// よそによって使われただけなのでこちらの L は更新しなくていいか。
		} else {
			// Mark line invalid (if IM=1)
			set.Update(line, m88200CacheSet::IV);
		}
	}
	// Resume servicing PBus
}

unix.superglobalmegacorp.com

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