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

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

//
// ページテーブルモニター
//

#include "wxpagetablemonitor.h"
#include "wxmainframe.h"
#include "wxscrollbar.h"
#include "wxtextscreen.h"
#include "mainbus.h"
#include "mpu680x0.h"
#include "m68040mmu.h"

enum {
	ID_SRP,
	ID_URP,
	ID_LADDR,
	ID_SCROLL_0 = IDGROUP_PAGETABLE,
	ID_SCROLL_4 = ID_SCROLL_0 + 4,

	ID_local_end,	// 最後に置く (チェック用)
};
static_assert(ID_local_end - 1 <= (int)IDGROUP_PAGETABLE_END, "ID exceeded");

static constexpr int HEAD_ROW = 2;	// テーブルのヘッダ行数

//
// ページテーブルの1段階
//

// コンストラクタ
PageTable::PageTable(wxWindow *parent, int lv_, int width)
{
	const int height = 32 + HEAD_ROW;

	lv = lv_;
	screen = new WXTextScreen(parent, nnSize(width, height));
	vscroll = new WXScrollBar(parent, ID_SCROLL_0 + lv, wxSB_VERTICAL);
	vscroll->SetScrollParam(0, height, height, height);
}

// パラメータをクリアする。(インデックスとコントロールはそのまま)
void
PageTable::Clear()
{
	title = "";
	head = "";
	base = 0xffffffff;
	mask = 0;
	bitpos = 0;
	bitlen = 0;
	count = 0;
	cursor = 0;
}

// 指定位置のアドレスを返す。
uint32
PageTable::GetAddr(int idx) const
{
	if (__predict_false(base == 0xffffffff)) {
		return base;
	} else {
		return base + idx * 4;
	}
}

// 表示開始位置を返す。
int
PageTable::ViewTop() const
{
	if (__predict_false(vscroll == NULL)) {
		return 0;
	}
	return vscroll->GetThumbPosition();
}

// このテーブルのコントロールの表示状態を制御する。
void
PageTable::Show(bool enable)
{
	screen->Show(enable);
	vscroll->Show(enable);
}


//
// ページテーブルウィンドウ (共通部分)
//

// +-------------------------------------------------+
// | wxPanel *ctrlpanel                              |
// | (wx 製のコントロールパネル、横目一杯まで伸びる) |
// +-------------------------------------------------+
// | WXTextScreen *stat                              |
// | (横目一杯のテキスト情報)                        |
// +-----------------+-----------------+---       ---+
// | PageTable[0]    | PageTable[1]    |     ...     |
// | (TIA 情報)      | (TIB 情報)      |             |
// +-----------------+-----------------+-------------+
//
// PageTable 内の段組み。空き地のところは下の bgpanel を見せる。
// +--------+---------+
// |        | (空き地)|
// | screen +---------+
// |        | vscroll |
// +--------+---------+

// イベントテーブル
wxBEGIN_EVENT_TABLE(WXPageTableWindow, inherited)
	EVT_TIMER(wxID_ANY, WXPageTableWindow::OnTimer)
	EVT_RADIOBUTTON(ID_SRP, WXPageTableWindow::OnRPChanged)
	EVT_RADIOBUTTON(ID_URP, WXPageTableWindow::OnRPChanged)
wxEND_EVENT_TABLE()

// コンストラクタ
WXPageTableWindow::WXPageTableWindow(wxWindow *parent, const wxString& name)
	: inherited(parent, wxID_ANY, name, DEFAULT_STYLE | wxRESIZE_BORDER)
{
	timer.SetOwner(this);

	mainbus = GetMainbusDevice();

	// 幅は実行時に伸ばす。
	stat = new WXTextScreen(this, nnSize(1, 1));

	// コントロールパネル(のまず下敷き)。
	ctrlpanel = new wxPanel(this);
	ctrlpanel->SetName(name + ".CtrlPanel");
	auto *ctrlbox = new wxBoxSizer(wxHORIZONTAL);

	// SRP, URP 選択ラジオボタン。
	// 68040 はこの名前で固定。68030 は実行時に表示名を切り替える。
	auto *rpbox = new wxStaticBoxSizer(wxHORIZONTAL, ctrlpanel,
		_("Root Pointer"));
	radio_srp = new wxRadioButton(ctrlpanel, ID_SRP, "SRP");
	radio_urp = new wxRadioButton(ctrlpanel, ID_URP, "URP");
	rpbox->Add(radio_srp);
	rpbox->Add(radio_urp);
	// GTK3 標準のレンダリングだとコントロールの周りの空きがなさすぎて
	// 特にスタティックボックスは読みづらいので自力で少し空ける。どうして…。
	ctrlbox->Add(rpbox, 0, wxALIGN_CENTER_VERTICAL | wxALL, 3);

#if 0 // not yet
	// アドレス欄。
	ctrlbox->Add(new wxStaticText(ctrlpanel, wxID_ANY, _("Logical Address:")),
		0, wxALIGN_CENTER_VERTICAL | wxALL, 3);
	ctrlbox->Add(new wxTextCtrl(ctrlpanel, ID_LADDR,
			wxEmptyString, wxDefaultPosition, wxDefaultSize,
			wxTE_PROCESS_ENTER),
		0, wxALIGN_CENTER_VERTICAL | wxTOP | wxRIGHT | wxBOTTOM, 3);
#endif

	// sizer と下敷きパネルを紐付ける。
	ctrlpanel->SetSizer(ctrlbox);
	ctrlbox->SetSizeHints(ctrlpanel);

	// 表部分の下敷きパネル。
	bgpanel = new WXTextPanel(this);

	// モニタと同じ更新レートを適用。
	auto mainframe = dynamic_cast<WXMainFrame *>(parent);
	SetRate(mainframe->GetMonitorRate());

	// 自前レイアウト。
	SetMyLayout();

	// 最初に一回描画を起こす。
	wxTimerEvent dummy(timer);
	AddPendingEvent(dummy);
}

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

// 継承先のコンストラクタで table 設定後に呼ぶこと。
void
WXPageTableWindow::ConnectTableEvents()
{
	for (auto& t : table) {
		// パネルで起きた LeftDClick, MouseWheel をこっちで受け取る。
		t.screen->Bind(wxEVT_LEFT_DCLICK,
			&WXPageTableWindow::OnLeftDClick, this);
		t.screen->Bind(wxEVT_MOUSEWHEEL,
			&WXPageTableWindow::OnMouseWheel, this);
	}
}

bool
WXPageTableWindow::GetMySizeHints(wxSize *newp, wxSize *minp, wxSize *maxp,
	wxSize *incp)
{
	int ctrl_minx = ctrlpanel->GetMinSize().x;
	int ctrl_y = ctrlpanel->GetSize().y;
#if 0
	int stat_y = stat->GetSize().y;
#else
	int stat_y = 0;
#endif
	int table_y = ctrl_y + stat_y;

	int new_x = 0;
	int new_y = table_y;
	int min_y = table_y;
	int max_y = table_y;

	// テーブルがない場合でも、メッセージを表示するため
	// 計算上1段分はあることにする。
	for (int i = 0, end = table_count ?: 1; i < end; i++) {
		const auto& t = table[i];
		new_x += t.screen->GetSize().x;
		new_x += t.vscroll->GetSize().x;
	}
	if (new_x < ctrl_minx) {
		new_x = ctrl_minx;
	}

	new_y += table[0].screen->GetScreenHeight(32 + HEAD_ROW);

	// 高さの最小は (ヘッダ分と) TextScreen 2行分にしておく。
	min_y += table[0].screen->GetScreenHeight(1 + HEAD_ROW);

	// 高さの最大はエントリ分。
	int row = max_rows > 0 ? (max_rows + HEAD_ROW) : (32 + HEAD_ROW);
	max_y += table[0].screen->GetScreenHeight(row);

	int inc_y = table[0].screen->GetFontHeight();

	wxSize newsize(new_x, new_y);
	wxSize minsize(new_x, min_y);
	wxSize maxsize(new_x, max_y);
	wxSize incsize(0,     inc_y);

	if (newp) *newp = newsize;
	if (minp) *minp = minsize;
	if (maxp) *maxp = maxsize;
	if (incp) *incp = incsize;
	return true;
}

bool
WXPageTableWindow::Layout()
{
	if (MyLayout() == false) {
		// コントロールを配置。
		wxSize client = GetClientSize();
		int ctrl_y = ctrlpanel->GetSize().y;
#if 0
		int stat_y = stat->GetSize().y;
#else
		int stat_y = 0;
#endif
		int table_y = ctrl_y + stat_y;
		int table_h = client.y - table_y;

		ctrlpanel->SetSize(0, 0, client.x, ctrl_y);
#if 0
		stat->SetSize(0, ctrl_y, client.x, stat_y);
#else
		stat->Hide();
#endif

		// 背景パネル。
		// テーブル数が 0 だと無効メッセージの表示を兼ねる。
		bgpanel->SetSize(0, table_y, client.x, table_h);
		if (table_count == 0) {
			bgpanel->DrawStringSJIS(UD_BLACK,
				WXTextScreen::DefaultPadding, WXTextScreen::DefaultPadding,
				"MMU TC Not Configured");
		} else {
			bgpanel->Fill();
		}

		int x = 0;
		int i;
		for (i = 0; i < table_count; i++) {
			auto& t = table[i];

			int screen_w = t.screen->GetSize().x;
			t.screen->SetSize(x, table_y, screen_w, table_h);
			x += screen_w;

			int vscroll_w = t.vscroll->GetSize().x;
			// スクロールバーの上端を表の上端と揃える。
			int head_h = t.screen->GetFontHeight() * HEAD_ROW
				+ t.screen->GetPaddingTop();
			t.vscroll->SetSize(x, table_y + head_h,
				vscroll_w, table_h - head_h);
			x += vscroll_w;

			int thumbsize = std::min(t.count, t.screen->GetRow() - HEAD_ROW);
			t.vscroll->SetScrollParam(thumbsize, t.count, thumbsize);

			t.Show();
		}
		for (; i < table.size(); i++) {
			auto& t = table[i];
			t.Hide();
		}

		DoRefresh();
	}
	return true;
}

// 画面更新頻度を Hz で設定する。
void
WXPageTableWindow::SetRate(int hz)
{
	timer.Start(1000 / hz);
}

void
WXPageTableWindow::OnTimer(wxTimerEvent& event)
{
	DoRefresh();
}

// ルートポインタのラジオボタン変更イベント
void
WXPageTableWindow::OnRPChanged(wxCommandEvent& event)
{
	show_urp = (event.GetId() == ID_URP);
	DoRefresh();
}

// テーブルパネル上で起きたダブルクリックイベント
void
WXPageTableWindow::OnLeftDClick(wxMouseEvent& event)
{
	// 対応するテーブルを探す。
	auto *sender = event.GetEventObject();
	for (auto& t : table) {
		if (sender == t.screen) {
			auto pxpos = event.GetPosition();
			auto y = t.screen->GetTextPosition(pxpos).y - HEAD_ROW;
			if (y >= 0) {
				// この行を選択。
				t.cursor = t.ViewTop() + y;
				DoRefresh();
			}
			break;
		}
	}
}

// テーブルパネル上で起きたマウスホイールイベント
void
WXPageTableWindow::OnMouseWheel(wxMouseEvent& event)
{
	// 対応するスクロールバーに再送して処理してもらう。
	auto *sender = event.GetEventObject();
	for (auto& t : table) {
		if (sender == t.screen) {
			wxPostEvent(t.vscroll, event);
			break;
		}
	}
}


//
// ページテーブルウィンドウ (68030)
//

// コンストラクタ
WXPageTable68030Window::WXPageTable68030Window(wxWindow *parent,
	const wxString& name)
	: inherited(parent, name)
{
	mpu = dynamic_cast<MPU68030Device *>(GetMPUDevice());

	// 68030 はバラエティ豊かすぎてフルサポートは困難なので
	// とりあえず最大 3段として、過剰分は表示で誤魔化す…。
	table.emplace_back(this, 0, 41);
	table.emplace_back(this, 1, 41);
	table.emplace_back(this, 2, 41);
	ConnectTableEvents();

	// 最初に一度 MPU の現状を適用する。
	// (以降はタイマー内で毎回チェックする)
	Reconfig(mpu->GetTC());

	// ウィンドウサイズを確定させる。
	FontChanged();
}

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

// MMU の構成を適用する。
void
WXPageTable68030Window::Reconfig(uint32 new_tc_value)
{
	tc_value = new_tc_value;

	// TC の構成が正しくて E がオフならグレーで表示とかは出来るが、
	// (E がオフで) TC の構成が無効なら出来ることは何もない。
	m68030TC new_tc;
	new_tc.Set(new_tc_value);
	if (new_tc.ConfigOK() == false) {
		// 構成が無効。
		tc_status = TC_NOT_CONFIG;
		max_rows = 8;
		for (auto& t : table) {
			t.Clear();
			t.count = max_rows;
		}
		table_count = 0;
	} else {
		if (new_tc.e) {
			tc_status = TC_ENABLE;
		} else {
			tc_status = TC_DISABLE;
		}

		for (auto& t : table) {
			t.Clear();
		}
		table_count = new_tc.termlv + 1;

		int total_bits = new_tc.is;
		max_rows = 0;
		for (uint i = 0; i < table_count; i++) {
			auto& t = table[i];

			// ビット位置。
			t.bitlen = new_tc.tix[t.lv];
			total_bits += t.bitlen;
			t.bitpos = 32 - total_bits;

			// 行数。
			t.count = 1U << t.bitlen;
			max_rows = std::max(max_rows, t.count);

			// 見出し。
			t.title = string_format("TI%c (%u bit)", 'A' + t.lv, t.bitlen);
			t.head = "LAddress   At        Address  Flag DT";

			// 次段アドレスのマスクは固定。
			t.mask = ~0x000f;
		}
	}

	// ルートポインタの表示。68030 のルートポインタは
	// (TC.SRE && スーパーバイザ状態) なら SRP、それ以外は CRP という構造。
	if (new_tc.sre) {
		radio_srp->SetLabel(_("Supervisor (SRP)"));
	} else {
		radio_srp->SetLabel(_("Supervisor (CRP)"));
	}
	radio_urp->SetLabel(_("User (CRP)"));

	Fit();
}

// 画面を更新して再描画する。
void
WXPageTable68030Window::DoRefresh()
{
	// MMU 構成に変更があれば再構築。
	uint32 new_tc_value = mpu->GetTC();
	if (tc_value != new_tc_value) {
		Reconfig(new_tc_value);
	}

	for (auto& t : table) {
		t.base = 0xffffffff;
	}

	if (tc_status != TC_NOT_CONFIG) {
		if (mpu->mmu_tc.sre && show_urp == false) {
			table[0].base = mpu->GetSRPl();
		} else {
			table[0].base = mpu->GetCRPl();
		}
		table[0].base &= table[0].mask;

		uint64 data2 = mainbus->Peek4(table[0].GetAddr());
		if ((int64)data2 >= 0 && (data2 & 0x03) != 0) {
			table[1].base = data2 & table[0].mask;
		}
	}

	uint32 laddr = 0;
	for (auto& t : table) {
		// この段の論理アドレスの可変部分の文字列長を求める。
		int ln = 8 - (t.bitpos / 4);

		auto& screen = t.screen->GetScreen();
		screen.Clear();

		TA attr_t = (tc_status == TC_DISABLE) ? TA::Disable : TA::Normal;
		screen.Puts(0, 0, attr_t, (const char *)t.title.mb_str());
		screen.Puts(1, 1, attr_t, (const char *)t.head.mb_str());
		// 構成無効ならタイトルメッセージだけ表示。
		if (tc_status == TC_NOT_CONFIG) {
			continue;
		}

		const int end = std::min(screen.GetRow(), t.count + HEAD_ROW);
		for (uint y = HEAD_ROW, i = t.ViewTop(); y < end; y++, i++) {
			uint32 addr;
			uint32 data;
			if (__predict_false(t.base == 0xffffffff)) {
				addr = t.base;
				data = 0;
			} else {
				addr = t.GetAddr(i);
				data = mainbus->Peek4(addr);
			}
			TA attr_i;
			TA attr_a;
			TA attr_v;
			if (__predict_true(tc_status == TC_ENABLE)) {
				attr_i = TA::Normal;
				attr_a = TA::Normal;
				attr_v = TA::Normal;
			} else {
				attr_i = TA::Disable;
				attr_a = TA::Disable;
				attr_v = TA::Disable;
			}
			if (__predict_false(i == t.cursor)) {
				attr_i = TA::Reverse;
				attr_a = TA::Reverse;
				attr_v = TA::Reverse;
			}
			if (__predict_false((int64)data < 0)) {
				attr_a = TA::Disable;
				attr_v = TA::Disable;
			} else if ((data & 0x03) == 0) {
				attr_v = TA::Disable;
			}

			char lbuf[16];
			snprintf(lbuf, sizeof(lbuf), "[%08x0] ", laddr | (i << t.bitpos));
			lbuf[1 + ln] = '\'';
			screen.Puts(0,   y, attr_i, lbuf);
			screen.Print(12, y, attr_a, "$%08x ", addr);
			screen.Print(22, y, attr_v, "%08x ", (uint32)data & t.mask);
			int x = 31;
			uint32 dt = data & 0x03;
			// 68030 は実際には何段目がページではなくて、
			// DT=0x01 のディスクリプタに出会ったらそれがページディスクリプタ
			// という構造なので、ディスクリプタによって書式を変えない方がいい。
			if (dt == 1) {
				screen.Putc(x++, y, attr_v, (data & 0x40) ? 'C' : '-');
				screen.Putc(x++, y, attr_v, (data & 0x10) ? 'M' : '-');
			} else {
				screen.Putc(x++, y, attr_v, ' ');
				screen.Putc(x++, y, attr_v, ' ');
			}
			screen.Putc(x++, y, attr_v, (data & 0x08) ? 'U' : '-');
			screen.Putc(x++, y, attr_v, (data & 0x04) ? 'P' : '-');
			static const char * const dtstr[4] = {
				// 反転カーソルの見栄えのため空白込みで長さを揃える。
				"-----",
				"Page ",
				"Short",
				"Long ",
			};
			screen.Putc(x++, y, attr_v, ' ');
			screen.Puts(x,   y, attr_v, dtstr[dt]);
		}

		// 次段の論理アドレス。
		laddr |= (t.cursor << t.bitpos);
	}

	Refresh();
}


//
// ページテーブルウィンドウ (68040)
//

// コンストラクタ
WXPageTable68040Window::WXPageTable68040Window(wxWindow *parent,
	const wxString& name)
	: inherited(parent, name)
{
	mpu = dynamic_cast<MPU68040Device *>(GetMPUDevice());

	// 68040 は 3段固定。
	table.emplace_back(this, 0, 33);
	table.emplace_back(this, 1, 33);
	table.emplace_back(this, 2, 36);
	ConnectTableEvents();

	// 最初に一度 MPU の現状を適用する。
	// (以降はタイマー内で毎回チェックする)
	Reconfig(mpu->GetTC());

	// ウィンドウサイズを確定させる。
	FontChanged();
}

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

// MMU の構成を適用する。
void
WXPageTable68040Window::Reconfig(uint32 new_tc_value)
{
	tc_value = new_tc_value;

	// 68040 の MMU に構成不能状態はない。
	if ((new_tc_value & m68040TC::E)) {
		tc_status = TC_ENABLE;
	} else {
		tc_status = TC_DISABLE;
	}
	if ((new_tc_value & m68040TC::P)) {
		ps4k = false;
	} else {
		ps4k = true;
	}

	// 各テーブルの行数と見出し。
	table_count = 3;
	table[0].bitlen = 7;
	table[1].bitlen = 7;
	table[0].bitpos = 25;
	table[1].bitpos = 18;
	table[0].title = "TIA (7 bit)";
	table[1].title = "TIB (7 bit)";
	if (ps4k) {
		table[2].bitlen = 6;
		table[2].bitpos = 12;
		table[2].title = "TIC (6 bit,PS=4K)";
	} else {
		table[2].bitlen = 5;
		table[2].bitpos = 13;
		table[2].title = "TIC (5 bit,PS=8K)";
	}

	for (auto& t : table) {
		t.count = 1U << t.bitlen;
	}
	max_rows = table[0].count;

	table[0].head = "LAddress   At        TableAddr";
	table[1].head = "LAddress   At        TableAddr";
	table[2].head = "LAddress   At        PageAddr";

	// 各テーブルのアドレスマスク。
	table[0].mask = ~0x1ff;
	if (ps4k) {
		table[1].mask = ~0x00ff;
		table[2].mask = ~0x0fff;
	} else {
		table[1].mask = ~0x007f;
		table[2].mask = ~0x1fff;
	}

	Fit();
}


// 画面を更新して再描画する。
//
// 68040 のツリーは 3段階固定で 7:7:6 (4K/page) か 7:7:5 (8K/page)。
//
//  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
// +-------+-------+-------+-------+-------+-------+-------+-------+
// |A A A A A A A|B B B B B B B|C C C C C:C|                       |
// +-------+-------+-------+-------+-------+-------+-------+-------+
void
WXPageTable68040Window::DoRefresh()
{
	// MMU 構成に変更があれば再構築。
	uint32 new_tc_value = mpu->GetTC();
	if (tc_value != new_tc_value) {
		Reconfig(new_tc_value);
	}

	table[1].base = 0xffffffff;
	table[2].base = 0xffffffff;
	if (mpu->mmu_tc->e == false) {
		table[0].base = 0xffffffff;
	} else {
		if (show_urp) {
			table[0].base = mpu->GetURP();
		} else {
			table[0].base = mpu->GetSRP();
		}
		uint64 data2 = mainbus->Peek4(table[0].GetAddr());
		if ((int64)data2 >= 0 && (data2 & 0x02)) {
			table[1].base = data2 & table[0].mask;
		}
		if (table[1].base != 0xffffffff) {
			uint64 data3 = mainbus->Peek4(table[1].GetAddr());
			if ((int64)data3 >= 0 && (data3 & 0x02)) {
				table[2].base = data3 & table[1].mask;
			}
		}
	}

	for (auto& t : table) {
		// 論理アドレスの上位部分 (の表示用に下のほうを切り落としたやつ)。
		uint32 laddr;
		uint32 valid;
		switch (t.lv) {
		 case 0:
			laddr = 0;
			valid = 0x0002;
			break;
		 case 1:
			laddr = (table[0].cursor << 9);
			valid = 0x0002;
			break;
		 case 2:
			laddr = (table[0].cursor << 13) | (table[1].cursor << 6);
			valid = 0x0001;
			break;
		 default:
			__unreachable();
		}

		auto& screen = t.screen->GetScreen();
		screen.Clear();

		TA attr_t = (tc_status == TC_DISABLE) ? TA::Disable : TA::Normal;
		screen.Puts(0, 0, attr_t, (const char *)t.title.mb_str());
		screen.Puts(1, 1, attr_t, (const char *)t.head.mb_str());

		const int end = std::min(screen.GetRow(), t.count + HEAD_ROW);
		for (uint y = HEAD_ROW, i = t.ViewTop(); y < end; y++, i++) {
			uint32 addr;
			uint32 data;
			if (__predict_false(t.base == 0xffffffff)) {
				addr = t.base;
				data = 0;
			} else {
				addr = t.GetAddr(i);
				data = mainbus->Peek4(addr);
			}
			TA attr_i;
			TA attr_a;
			TA attr_v;
			if (__predict_true(tc_status == TC_ENABLE)) {
				attr_i = TA::Normal;
				attr_a = TA::Normal;
				attr_v = TA::Normal;
			} else {
				attr_i = TA::Disable;
				attr_a = TA::Disable;
				attr_v = TA::Disable;
			}
			if (__predict_false(i == t.cursor)) {
				attr_i = TA::Reverse;
				attr_a = TA::Reverse;
				attr_v = TA::Reverse;
			}
			if (__predict_false((int64)data < 0)) {
				attr_a = TA::Disable;
				attr_v = TA::Disable;
			} else if ((data & valid) == 0) {
				attr_v = TA::Disable;
			}

			// 表示は段ごとに微妙に違う。
			char lbuf[16];
			switch (t.lv) {
			 case 0:
				snprintf(lbuf, sizeof(lbuf), "[%02x'000000] ", (i << 1));
				break;
			 case 1:
				snprintf(lbuf, sizeof(lbuf), "[%04x'0000] ", laddr | (i << 2));
				break;
			 case 2:
				snprintf(lbuf, sizeof(lbuf), "[%05x'000] ",
					laddr | (__predict_true(ps4k) ? i : (i << 1)));
				break;
			 default:
				__unreachable();
			}
			screen.Puts( 0,  y, attr_i, lbuf);
			screen.Print(12, y, attr_a, "$%08x ", addr);
			screen.Print(22, y, attr_v, "%08x ", (uint32)data & t.mask);
			int x = 31;
			if (t.lv == 2) {
				screen.Putc(x++, y, attr_v, (data & 0x400) ? 'G' : '-');
				screen.Putc(x++, y, attr_v, (data & 0x080) ? 'S' : '-');
				screen.Putc(x++, y, attr_v, (data & 0x010) ? 'M' : '-');
			}
			screen.Putc(x++, y, attr_v, (data & 0x08) ? 'U' : '-');
			screen.Putc(x++, y, attr_v, (data & 0x04) ? 'W' : '-');
		}
	}

	Refresh();
}

unix.superglobalmegacorp.com

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