|
|
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();
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.