--- nono/vm/mainbus.cpp 2026/04/29 17:05:17 1.1 +++ nono/vm/mainbus.cpp 2026/04/29 17:05:59 1.1.1.8 @@ -5,177 +5,479 @@ // // -// メインバス +// メインバス (共通部分) // +// メインバス付近に必要な機能は次のようになっている。 +// +// Luna News X68k X68kIO XPbus +// InitMainbus o o o o x +// FC access o o o o x +// SwitchBoot o o o o x +// ResetByMPU o o o *1 x (*1:どっちでもいい) +// +// Access Map o o o x x +// HV access o o o x ? +// Peek4 o o o x x +// Singleton o o o x x +// +// もともと FC 付きアクセス機能を Mainbus からX68kIO に伸ばすために +// MainbusDevice を導入したが、一方モニタの登録などでは Mainbus は +// システムに複数存在してはならないため、これらは同時に成立しない。 +// そのため、表の上半分の機能を MainbusBaseDevice として独立させ、 +// X68kIO はここから派生。表の下半分の機能を MainbusDevice とする。 +// +// XPbus はどちらも不要なので (XP プロセッサから見ればメインバスだが、 +// ここの Mainbus はそういう意味ではないので) ただの IODevice。 + +// IODevice +// | +// | +-------------------+ +// +--| MainbusBaseDevice | (InitMainbus() と FC アクセスを持つ) +// | +-------------------+ +// | | +// | | +---------------+ +// | +--| MainbusDevice | (これがメインバス、システムに1つ) +// | | +---------------+ +// | | | +// | | | +-----------------+ +// | | +--| Mainbus24Device | (上位8ビットがテーブルで表せるメインバス) +// | | | +-----------------+ +// | | | | +// | | | +-- LunaMainbus +// | | | | +-- Luna1Mainbus +// | | | | +-- Luna88kMainbus +// | | | +-- NewsMainbus +// | | | +-- Virt68kMainbus +// | | +-- X68kMainbus (上位8ビットが IODevice で表せないため) +// | | +// | +-- X68kIODevice +// | +// +-- XPbusDevice (これはただの IODevice) + #include "mainbus.h" -#include "mainram.h" +#include "monitor.h" + +// +// メインバスの素質を持つ基本クラス +// // コンストラクタ -MainbusDevice::MainbusDevice() - : inherited(OBJ_MAINBUS) +MainbusBaseDevice::MainbusBaseDevice(uint objid_) + : inherited(objid_) { } // デストラクタ -MainbusDevice::~MainbusDevice() +MainbusBaseDevice::~MainbusBaseDevice() { } -uint64 -MainbusDevice::Read8(uint32 addr) +// MPU の RESET 命令によるリセット +void +MainbusBaseDevice::ResetByMPU() { - uint64 data; +} - if (addr < direct_ram_size) { - data = MainRAMDevice::mainram[HLB(addr)]; +// ブートページを切り替える際の共通処理。 +// 派生クラスから呼ぶこと。 +void +MainbusBaseDevice::SwitchBootPage(bool isrom) +{ + // ログ表示 + if (isrom) { + putlog(1, "SwitchBootPage ROM (Boot)"); } else { - IODevice *dev = devtable[addr >> 24]; - data = dev->Read8(addr); + putlog(1, "SwitchBootPage RAM (Normal)"); } - return data; } -uint64 -MainbusDevice::Read16(uint32 addr) +// モニタ表示用にデバイス名を整形。 +// 7文字に切り詰めるのと、括弧があれば取り除いて Disable 色にする。 +/*static*/ std::pair +MainbusBaseDevice::FormatDevName(const Device *dev) +{ + std::string name = dev->GetName(); + TA attr; + + if (name[0] == '(') { + // 括弧付きなら、括弧を取り除いて Disable 色で表示 + int len = std::min(7, (int)name.size() - 2); + name = name.substr(1, len); + attr = TA::Disable; + } else { + name = name.substr(0, 7); + attr = TA::Normal; + } + + return std::make_pair(name, attr); +} + + +// +// メインバス (基本クラス) +// + +// コンストラクタ +MainbusDevice::MainbusDevice(uint objid_) + : inherited(objid_) { - uint64 data; + accstat_monitor = gMonitorManager->Regist(ID_MONITOR_ACCSTAT, this); + accstat_monitor->SetCallback(&MainbusDevice::MonitorScreenAccStat); + // 表示行数が機種ごとに異なるので継承側でセットしている。 + + // 通常は 32bit 空間全体 (そうでない機種はコンストラクタで上書きする) + accstat_baseaddr = 0; + accstat_bitlen = 32; +} - if (__predict_false((addr & 1) != 0)) - VMPANIC("unaligned access $%08x", addr); +// デストラクタ +MainbusDevice::~MainbusDevice() +{ +} - if (addr < direct_ram_size) { - data = *(uint16 *)&MainRAMDevice::mainram[HLW(addr)]; - } else { - IODevice *dev = devtable[addr >> 24]; - data = dev->Read16(addr); - } - return data; +// 初期化 +bool +MainbusDevice::Init() +{ + // アクセス状況の表示用配列。 + accstat_rw.resize(AccStat::MAINLEN >> (32 - accstat_bitlen)); + + return true; } -uint64 -MainbusDevice::Read32(uint32 addr) +// リセット +void +MainbusDevice::ResetHard(bool poweron) { - uint64 data; + inherited::ResetHard(poweron); - if (__predict_false((addr & 3) != 0)) - VMPANIC("unaligned access $%08x", addr); + std::fill(accstat_read.begin(), accstat_read.end(), 0); + std::fill(accstat_write.begin(), accstat_write.end(), 0); +} - if (addr < direct_ram_size) { - data = *(uint32 *)&MainRAMDevice::mainram[addr]; - } else { - IODevice *dev = devtable[addr >> 24]; - data = dev->Read32(addr); - } +// ハイパーバイザ的読み込み (ミスアライン可)。 +// addr はアドレスとサイズを指定すること。 +// バスエラーが起きればその時点で BusData::BusErr を返す。 +busdata +MainbusDevice::HVReadN(busaddr addr) +{ + addr |= BusAddr::SRead; + + uint32 reqsize = addr.GetSize(); + busdata data; + do { + busdata bd = Read(addr); + if (__predict_false(bd.IsBusErr())) { + return bd; + } + data |= DYNAMIC_BUS_SIZING_R(addr, bd); + } while (addr.GetSize() != 0); + + data |= busdata::Size(reqsize); return data; } -uint64 -MainbusDevice::Write8(uint32 addr, uint32 data) +// ハイパーバイザ的読み込み +busdata +MainbusDevice::HVRead1(uint32 paddr) { - if (addr < direct_ram_size) { - MainRAMDevice::mainram[HLB(addr)] = data; - return 0; - } else { - IODevice *dev = devtable[addr >> 24]; - return dev->Write8(addr, data); - } + busaddr addr = busaddr(paddr) | BusAddr::Size1; + return HVReadN(addr); } -uint64 -MainbusDevice::Write16(uint32 addr, uint32 data) +// ハイパーバイザ的読み込み (ミスアライン可) +busdata +MainbusDevice::HVRead2(uint32 paddr) { - if (__predict_false((addr & 1) != 0)) - VMPANIC("unaligned access $%08x", addr); + busaddr addr = busaddr(paddr) | BusAddr::Size2; + return HVReadN(addr); +} - if (addr < direct_ram_size) { - *(uint16 *)&MainRAMDevice::mainram[HLW(addr)] = data; - return 0; - } else { - IODevice *dev = devtable[addr >> 24]; - return dev->Write16(addr, data); - } +// ハイパーバイザ的読み込み (ミスアライン可) +busdata +MainbusDevice::HVRead4(uint32 paddr) +{ + busaddr addr = busaddr(paddr) | BusAddr::Size4; + return HVReadN(addr); } -uint64 -MainbusDevice::Write32(uint32 addr, uint32 data) +// ハイパーバイザ的書き込み (ミスアライン可)。 +// addr はアドレスとサイズを指定すること。属性は SRead 固定。 +// バスエラーが起きればその時点で busdata::BusErr を返す。 +busdata +MainbusDevice::HVWriteN(busaddr addr, uint32 data) { - if (__predict_false((addr & 3) != 0)) - VMPANIC("unaligned access $%08x", addr); + addr |= BusAddr::SWrite; + do { + busdata r = Write(addr, data); + if (__predict_false(r.IsBusErr())) { + return r; + } + DYNAMIC_BUS_SIZING_W(addr, data, r); + } while (addr.GetSize() != 0); - if (addr < direct_ram_size) { - *(uint32 *)&MainRAMDevice::mainram[addr] = data; - return 0; - } else { - IODevice *dev = devtable[addr >> 24]; - return dev->Write32(addr, data); - } + return 0; +} + +// ハイパーバイザ的書き込み +busdata +MainbusDevice::HVWrite1(uint32 paddr, uint32 data) +{ + busaddr addr = busaddr(paddr) | BusAddr::Size1; + return HVWriteN(addr, data); +} + +// ハイパーバイザ的書き込み (ミスアライン可) +busdata +MainbusDevice::HVWrite2(uint32 paddr, uint32 data) +{ + busaddr addr = busaddr(paddr) | BusAddr::Size2; + return HVWriteN(addr, data); +} + +// ハイパーバイザ的書き込み (ミスアライン可) +busdata +MainbusDevice::HVWrite4(uint32 paddr, uint32 data) +{ + busaddr addr = busaddr(paddr) | BusAddr::Size4; + return HVWriteN(addr, data); } +// 連続する4バイトを Peek する。(ミスアライン可) uint64 -MainbusDevice::Peek8(uint32 addr) +MainbusDevice::Peek4(uint32 addr) { uint64 data; - if (addr < direct_ram_size) { - data = MainRAMDevice::mainram[HLB(addr)]; + data = Peek1(addr++) << 24; + data |= Peek1(addr++) << 16; + data |= Peek1(addr++) << 8; + data |= Peek1(addr); + return data; +} + +void +MainbusDevice::MonitorScreenAccStat(Monitor *, TextScreen& screen) +{ + std::array buf; + +#define UPDATE_RW(rw_ptr) do { \ + uint64 dst = *(uint64 *)(rw_ptr); \ + dst <<= 2; \ + dst &= 0xfcfcfcfc'fcfcfcfc; \ + dst |= src; \ + *(uint64 *)(rw_ptr) = dst; \ +} while (0) + + // R|W をマージしながらコピー。 + if (__predict_true(accstat_rw.size() == AccStat::MAINLEN)) { + // 全域。 + for (int i = 0; i < AccStat::MAINLEN; i += 8) { + uint64 src = *(uint64 *)&accstat_read[i] + | *(uint64 *)&accstat_write[i]; + UPDATE_RW(&accstat_rw[i]); + } } else { - IODevice *dev = devtable[addr >> 24]; - data = dev->Peek8(addr); + // NEWS なら表示に使うのは前 1/4 のみで、後ろはミラー。 + // ただしアドレス表記は $c000'0000 以降(後ろの 1/4)。 + uint offset = accstat_rw.size(); + uint ndiv = accstat_read.size() / offset; + for (int i = 0, iend = accstat_rw.size(); i < iend; i += 8) { + uint64 src = 0; + for (int j = 0; j < ndiv; j++) { + src |= *(uint64 *)&accstat_read[i + j * offset]; + src |= *(uint64 *)&accstat_write[i + j * offset]; + } + UPDATE_RW(&accstat_rw[i]); + } + } + + // 読んだのでリセット。 + std::fill(accstat_read.begin(), accstat_read.end(), 0); + std::fill(accstat_write.begin(), accstat_write.end(), 0); + + screen.Clear(); + + static_assert(AccStat::SHIFT >= 20, ""); + screen.Print(0, 0, + "Shows %ubit space. %uMB/char. 'R':Read, 'W':Write, 'A':Read+Write", + accstat_bitlen, 1U << (AccStat::SHIFT - 20)); + screen.Print(12, 1, + "+$00 +$01 +$02 +$03 +$04 +$05 +$06 +$07"); + + uint32 baseaddr = accstat_baseaddr; + for (int y = 0, yend = accstat_rw.size() / 64; y < yend; y++) { + const uint8 *a = &accstat_rw[y * 64]; + uint8 avail = accstat_avail[y]; + char *d = &buf[0]; + std::fill(buf.begin(), buf.end() - 1, ' '); + for (int i = 0; i < 8; i++) { + // 先頭に空白を入れといて、参照時は buf+1 から参照する。 + if ((i & 1) == 0) { + d++; + } + if ((int8)avail < 0) { + // 何かしらデバイスがある。16MB、8文字分。 + for (int j = 0; j < 8; j++) { + uint op = *a & 3; + *d++ = ".RWA"[op]; + a++; + } + } else { + // この 16MB には何のデバイスもない。 + d += 8; + a += 8; + } + avail <<= 1; + } + *d = '\0'; + screen.Print(0, y + 2, "$%02x00'0000: %s", + baseaddr + y * 0x08, &buf[1]); } - return data; } -// これだけオーバーライドではないので注意。 -uint64 -MainbusDevice::Peek32(uint32 addr) + +// +// 上位8ビットがテーブルで表せる構造のメインバス。 +// + +// コンストラクタ (オブジェクト名指定なし) +Mainbus24Device::Mainbus24Device() + : MainbusDevice(OBJ_MAINBUS) +{ + monitor = gMonitorManager->Regist(ID_MONITOR_MAINBUS, this); + monitor->SetCallback(&Mainbus24Device::MonitorScreen); + monitor->SetSize(75, 33); +} + +Mainbus24Device::~Mainbus24Device() { - uint64 data; +} - if (__predict_false((addr & 3) != 0)) - VMPANIC("unaligned access $%08x", addr); +// アクセス状況のエリア情報初期化。 +// 継承側の InitMainbus() の終わりで呼ぶこと。 +void +Mainbus24Device::InitAccStat() +{ + for (int i = 0; i < accstat_avail.size(); i++) { + uint bits = 0; + for (int j = 0; j < 8; j++) { + int n = i * 8 + j; + int id = devtable[n]->GetId(); + bits <<= 1; + if (id != OBJ_BUSERR && id != OBJ_NOPIO) { + bits |= 1; + } + } + accstat_avail[i] = bits; + } - if (addr < direct_ram_size) { - data = *(uint32 *)&MainRAMDevice::mainram[addr]; - } else { - IODevice *dev = devtable[addr >> 24]; - data = dev->Peek8(addr++) << 24; - data |= dev->Peek8(addr++) << 16; - data |= dev->Peek8(addr++) << 8; - data |= dev->Peek8(addr); + // デバッグ用 + if (0) { + for (int i = 0; i < accstat_avail.size(); i++) { + printf("%02x: ", i); + uint8 a = accstat_avail[i]; + for (int j = 0; j < 8; j++) { + if ((int8)a < 0) { + printf("1"); + } else { + printf("0"); + } + a <<= 1; + } + printf("\n"); + } } - return data; } -// このアドレスを担当する次段の IODevice を返す (IOContainerDevice 用) -IODevice * -MainbusDevice::GetDevice(uint32 addr) const +inline IODevice * +Mainbus24Device::Decoder(uint32 addr) const { return devtable[addr >> 24]; } -// デバイスをセットする。すでにあれば更新する。 -// idx は devtable[] のインデックス (16MB 単位)。 -/*static*/ void -MainbusDevice::SetDevice(int idx, IODevice *dev) +busdata +Mainbus24Device::Read(busaddr addr) +{ + uint32 pa = addr.Addr(); + lastaddr = pa; + accstat_read[pa >> AccStat::SHIFT] = AccStat::READ; + IODevice *dev = Decoder(pa); + return dev->Read(addr); +} + +busdata +Mainbus24Device::Write(busaddr addr, uint32 data) +{ + uint32 pa = addr.Addr(); + lastaddr = pa; + accstat_write[pa >> AccStat::SHIFT] = AccStat::WRITE; + IODevice *dev = Decoder(pa); + return dev->Write(addr, data); +} + +busdata +Mainbus24Device::ReadBurst16(busaddr addr, uint32 *dst) +{ + uint32 pa = addr.Addr(); + lastaddr = pa; // 開始アドレスしか残らない… + IODevice *dev = Decoder(pa); + busdata r = dev->ReadBurst16(addr, dst); + if (__predict_true(r.IsBusErr() == false)) { + accstat_read[pa >> AccStat::SHIFT] = AccStat::READ; + } + return r; +} + +busdata +Mainbus24Device::WriteBurst16(busaddr addr, const uint32 *src) { - assert(idx < devtable.size()); + uint32 pa = addr.Addr(); + lastaddr = pa; // 開始アドレスしか残らない… + IODevice *dev = Decoder(pa); + busdata r = dev->WriteBurst16(addr, src); + if (__predict_true(r.IsBusErr() == false)) { + accstat_write[pa >> AccStat::SHIFT] = AccStat::WRITE; + } + return r; +} - devtable[idx] = dev; +busdata +Mainbus24Device::Peek1(uint32 addr) +{ + IODevice *dev = Decoder(addr); + return dev->Peek1(addr); } -// デバイステーブルの src から len 個を dst にコピーする。(ミラーを作る) -/*static*/ void -MainbusDevice::CopyDevtable(int dst, int src, int len) +bool +Mainbus24Device::Poke1(uint32 addr, uint32 data) { - memcpy(&devtable[dst], &devtable[src], sizeof(devtable[0]) * len); + IODevice *dev = Decoder(addr); + return dev->Poke1(addr, data); } -// Mainbus は1つしかないので、スタティック変数にしておく。 -/*static*/ std::array MainbusDevice::devtable; +void +Mainbus24Device::MonitorScreen(Monitor *, TextScreen& screen) +{ + screen.Clear(); + + // 012345678901234567890 + // $0000'0000: 1234567 + + for (int i = 0; i < 8; i++) { + screen.Print(12 + i * 8, 0, "+$0%x", i); + } -// RAM へのアクセスは、アクセスアドレスが direct_ram_size 未満なら ram[] を -// 直接アクセスし、そうでなければデバイスクラス経由でアクセスする。 -// 高速モードでは RAM デバイスを経由すること自体とその中でアクセスウェイトを -// 計算する分を回避したい。 -/*static*/ int MainbusDevice::direct_ram_size; + int idx = 0; + for (int i = 0; i < devtable.size() / 8; i++) { + screen.Print(0, i + 1, "$%02x00'0000:", idx); + for (int j = 0; j < 8; j++) { + auto pair = FormatDevName(devtable[idx]); + const std::string& name = pair.first; + TA attr = pair.second; + screen.Print(12 + j * 8, i + 1, attr, "%s", name.c_str()); + idx++; + } + } +}