--- nono/vm/pio.cpp 2026/04/29 17:05:14 1.1.1.11 +++ nono/vm/pio.cpp 2026/04/29 17:05:18 1.1.1.12 @@ -28,25 +28,22 @@ #include "pio.h" #include "bitops.h" -#include "config.h" +#include "dipsw.h" +#include "interrupt.h" #include "lcd.h" #include "mainapp.h" #include "mpu.h" +#include "mpu64180.h" #include "power.h" #include "scheduler.h" -// グローバル参照用 -PPIDevice *gPPI; -PIO0Device *gPIO0; -PIO1Device *gPIO1; - // // i8255 // // コンストラクタ -i8255Device::i8255Device(const std::string& objname_) - : inherited(objname_) +i8255Device::i8255Device(int objid_) + : inherited(objid_) { } @@ -73,13 +70,19 @@ i8255Device::WriteCtrl(uint32 data) if ((data & 0x80)) { // 最上位が立っていればモード設定 ctrl = data; - putlog(1, - "Set mode: groupA=%d groupB=%d portA=%s portB=%s portC=%s/%s", - GetGroupAMode(), GetGroupBMode(), - PortADir() == DIR_IN ? "IN" : "OUT", - PortBDir() == DIR_IN ? "IN" : "OUT", - PortCHDir() == DIR_IN ? "IN" : "OUT", - PortCLDir() == DIR_IN ? "IN" : "OUT"); + if (__predict_false(loglevel >= 1)) { + if (loglevel >= 2) { + putlogn("Ctrl <- $%02x: GrA/Bmode=%d/%d PA=%s PB=%s PC=%s:%s", + data, + GetGroupAMode(), GetGroupBMode(), + PortADir() == DIR_IN ? "IN" : "OUT", + PortBDir() == DIR_IN ? "IN" : "OUT", + PortCHDir() == DIR_IN ? "IN" : "OUT", + PortCLDir() == DIR_IN ? "IN" : "OUT"); + } else { + putlogn("Ctrl <- $%02x: SetMode", data); + } + } // モード設定すると PC はゼロクリアされる for (int pc = 0; pc < 8; pc++) { @@ -94,32 +97,67 @@ i8255Device::WriteCtrl(uint32 data) int pc = (data >> 1) & 7; int val = data & 1; + putlog(2, "Ctrl <- $%02x: SetPC%d=%d", data, pc, val); SetPC(pc, val); } } +// 共通部分を表示。 +// 戻り値は表示を終えた次の行。 +int +i8255Device::MonitorUpdateCommon(TextScreen& screen, int y) +{ + // Control = $xx + // GroupA (PortA,PortCH) Mode: 0(Basic I/O) + // GroupB (PortB,PortCL) Mode: 1(Strobed I/O) + // Port Direction: PA=OUT PB=IN PC(H)=OUT PC(L)=OUT + + screen.Print(1, y, "Control = $%02x:", ctrl); + static const char * const modestr[] = { + "Basic I/O", + "Strobed I/O", + "BiDirection", + "BiDirection", + }; + const int x = 16; + screen.Print(x, y++, "GroupA (PortA,PortCH) Mode: %d(%s)", + GetGroupAMode(), + modestr[GetGroupAMode()]); + screen.Print(x, y++, "GroupB (PortB,PortCL) Mode: %d(%s)", + GetGroupBMode(), + modestr[GetGroupBMode()]); + static const char *inoutstr[] = { + "OUT", + "IN ", + }; + screen.Print(x, y++, "Port Direction: PA=%s PB=%s PC(H)=%s PC(L)=%s", + inoutstr[PortADir()], + inoutstr[PortBDir()], + inoutstr[PortCHDir()], + inoutstr[PortCLDir()]); + + return y; +} + // // X68k PPI // // コンストラクタ PPIDevice::PPIDevice() - : inherited("PPI") + : inherited(OBJ_PPI) { - devaddr = baseaddr; - devlen = 0x2000; } // デストラクタ PPIDevice::~PPIDevice() { - gPPI = NULL; } uint64 PPIDevice::Read(uint32 offset) { - gMPU->AddCycle(19); // InsideOut p.135 + mpu->AddCycle(19); // InsideOut p.135 switch (offset) { case 0: @@ -132,14 +170,14 @@ PPIDevice::Read(uint32 offset) putlog(0, "Read $e9a007 Ctrl (NOT IMPLEMENTED)"); return 0xff; default: - __unreachable(); + VMPANIC("corrupted offset=%d", offset); } } uint64 PPIDevice::Write(uint32 offset, uint32 data) { - gMPU->AddCycle(19); // InsideOut p.135 + mpu->AddCycle(19); // InsideOut p.135 switch (offset) { case 0: @@ -155,7 +193,7 @@ PPIDevice::Write(uint32 offset, uint32 d WriteCtrl(data); break; default: - __unreachable(); + VMPANIC("corrupted offset=%d", offset); } return 0; } @@ -192,7 +230,7 @@ PPIDevice::SetPC(int pc, int val) putlog(1, "SetPC(%d) <- %d (NOT IMPLEMENTED)", pc, val); break; default: - __unreachable(); + VMPANIC("corrupted pc=%d", pc); } } @@ -200,21 +238,13 @@ PPIDevice::SetPC(int pc, int val) // LUNA PIO0 // // 仕様は 0x49000000 から 4バイトだが、 -// 0x48000000 から 0x4c000000 の手前まで 4バイト単位でミラーが見える。 -// -// LUNA の DIP-SW は #1, #2 にそれぞれ8個、計16個ある。 -// DIP-SW #1, #2 が設定ファイルの luna-dipsw{1,2} に対応する。 -// luna-dipsw{1,2} はいずれも "0"/"1" を8つ並べた文字列であり、 -// 先頭が本体表記でいう 1番、末尾側が本体表記でいう 8番に対応する。 -// 値は "0" が down、"1" が up を表す。 -// -// PIO0Device 内ではこの設定を読み込んで bool dipsw1[8], dipsw2[8] の配列で -// 値を保持している。dipsw1[0] が本体表記1番、dipsw1[7] が本体表記 8番に対応 -// する。値は false が down("0")、true が up("1") に対応する。 +// LUNA-I では 0x48000000 から 0x4bffffff まで 4バイト単位のミラーが見える。 +// LUNA-88K では 0x48000000 から 0x49ffffff までが +// (こちらはロングワード配置なので) 16バイト単位のミラーに見える。 // // PIO から読み出せる値のビットの並び順が機種によって異なる。 -// LUNA-1 では 1番が LSB、8番が MSB の並び順、 -// LUNA88K では 1番が MSB、8番が LSB の並び順。 +// LUNA-I では DIPSW 1番が LSB、8番が MSB の並び順、 +// LUNA-88K では DIPSW 1番が MSB、8番が LSB の並び順。 // 値は、並び順に関わらずどちらも // %0 が false = down = "0"、%1 が true = up = "1" である。 // @@ -237,17 +267,17 @@ PPIDevice::SetPC(int pc, int val) // // b7 b6 b5 b4 b3 b2 b1 b0 // +----+----+----+----+----+----+----+----+ -// LUNA-1 | #8 | #7 | #6 | #5 | #4 | #3 | #2 | #1 | +// LUNA-I | #8 | #7 | #6 | #5 | #4 | #3 | #2 | #1 | // PortA | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | %1=UP, %0=DOWN // +----+----+----+----+----+----+----+----+ // // b7 b6 b5 b4 b3 b2 b1 b0 // +----+----+----+----+----+----+----+----+ -// LUNA88K | #1 | #2 | #3 | #4 | #5 | #6 | #7 | #8 | +// LUNA-88K | #1 | #2 | #3 | #4 | #5 | #6 | #7 | #8 | // PortA | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | %1=UP, %0=DOWN // +----+----+----+----+----+----+----+----+ // -// LUNA-1: +// LUNA-I: // #1-1 up なら UNIX をロードして移行。down なら ROM モニタで起動。 // #1-2 up ならディスプレイコンソール。down ならシリアルコンソール。 // #1-3 up - color display, down - force to have monochrome display @@ -265,7 +295,7 @@ PPIDevice::SetPC(int pc, int val) // ローダの話。 // どこか由来のソースコードらしいがたぶんビット演算を間違えている。 // -// LUNA88K: +// LUNA-88K: // #1-1 up なら ROM モニタで停止、down ならマルチユーザブート。 // OpenBSD カーネルも up なら UserKernelConfig で止まる模様?。 // #1-2 up なら外付けシリアルコンソール、down なら内蔵ビットマップ。 @@ -275,66 +305,59 @@ PPIDevice::SetPC(int pc, int val) // コンストラクタ PIO0Device::PIO0Device() - : inherited("PIO0") + : inherited(OBJ_PIO0) { - devaddr = 0x49000000; - devlen = 4; - - // 機種固有パラメータ - - // LUNA-1 と LUNA88K で使いやすいように初期値を変えておく。 - switch (gMainApp.GetVMType()) { - case VMType::LUNA1: - gConfig->SetDefault("luna-dipsw1", "11110111"); - msb_first = false; - break; - case VMType::LUNA88K: - gConfig->SetDefault("luna-dipsw1", "11111111"); + // LUNA-I と LUNA-88K とでポートのビット並びが逆。どうして… + if (gMainApp.IsLUNA88K()) { msb_first = true; - break; - default: - __unreachable(); + } else { + msb_first = false; } - // DIP-SW#2 はユーザ定義(?)なので(今の所?)どちらも共通。 - gConfig->SetDefault("luna-dipsw2", "11111111"); + // PIO0/PIO1 monitor.func = ToMonitorCallback(&PIO0Device::MonitorUpdate); - monitor.SetSize(22, 11); - monitor.Regist(ID_MONITOR_PIO0); + monitor.SetSize(76, 15); + monitor.Regist(ID_MONITOR_PIO); } // デストラクタ PIO0Device::~PIO0Device() { - gPIO0 = NULL; } +// 初期化 bool PIO0Device::Init() { - // XXX 本当はラッチのタイミングが違う気もするけど、とりあえずね - - const ConfigItem& item1 = gConfig->Find("luna-dipsw1"); - const std::string& str1 = item1.AsString(); - if (str1.size() != 8) { - item1.Err("must be 8 digits"); + if (inherited::Init() == false) { return false; } - const ConfigItem& item2 = gConfig->Find("luna-dipsw2"); - const std::string& str2 = item2.AsString(); - if (str2.size() != 8) { - item2.Err("must be 8 digits"); - return false; - } - for (int i = 0; i < 8; i++) { - dipsw1[i] = (str1[i] != '0'); - dipsw2[i] = (str2[i] != '0'); - } + interrupt = GetInterruptDevice(); + pio1 = GetPIO1Device(); + return true; } -// XXX ResetHard 未調査 +// リセット +void +PIO0Device::ResetHard(bool poweron) +{ + auto dipswdev = GetDipswDevice(); + + // XXX 本当は XP から割り込みが上がったタイミングとからしいけど、 + // XP が出来るまでは当面リセットでラッチにしておく。 + for (int i = 0; i < 8; i++) { + dipsw1[i] = dipswdev->Get(i); + dipsw2[i] = dipswdev->Get(i + 8); + } + + // XXX どうなる? + for (int i = 0; i < intreq.size(); i++) { + intreq[i] = false; + inten[i] = false; + } +} uint64 PIO0Device::Read(uint32 offset) @@ -345,12 +368,22 @@ PIO0Device::Read(uint32 offset) case 0: // PortA data = GetPA(); + + // 割り込み要求(High)をクリア + intreq[INT_H] = false; + ChangeInterrupt(); + putlog(1, "DIP-SW1 -> $%02x", data); return data; case 1: // PortB data = GetPB(); + + // 割り込み要求(Low)をクリア + intreq[INT_L] = false; + ChangeInterrupt(); + putlog(1, "DIP-SW2 -> $%02x", data); return data; @@ -366,7 +399,7 @@ PIO0Device::Read(uint32 offset) return data; default: - __unreachable(); + VMPANIC("corrupted offset=%d", offset); } } @@ -390,7 +423,7 @@ PIO0Device::Write(uint32 offset, uint32 return 0; default: - __unreachable(); + VMPANIC("corrupted offset=%d", offset); } } @@ -411,10 +444,11 @@ PIO0Device::Peek(uint32 offset) return ReadCtrl(); default: - __unreachable(); + return 0xff; } } +// PIO0 が PIO0/PIO1 のモニタ両方を担当する。 void PIO0Device::MonitorUpdate(Monitor *, TextScreen& screen) { @@ -422,34 +456,43 @@ PIO0Device::MonitorUpdate(Monitor *, Tex screen.Clear(); - y = 0; - screen.Print(0, y++, "PortA(DIPSW1):%c%c%c%c%c%c%c%c", - (dipsw1[0] ? '1' : '0'), - (dipsw1[1] ? '1' : '0'), - (dipsw1[2] ? '1' : '0'), - (dipsw1[3] ? '1' : '0'), - (dipsw1[4] ? '1' : '0'), - (dipsw1[5] ? '1' : '0'), - (dipsw1[6] ? '1' : '0'), - (dipsw1[7] ? '1' : '0')); - screen.Print(0, y++, "PortB(DIPSW2):%c%c%c%c%c%c%c%c", - (dipsw2[0] ? '1' : '0'), - (dipsw2[1] ? '1' : '0'), - (dipsw2[2] ? '1' : '0'), - (dipsw2[3] ? '1' : '0'), - (dipsw2[4] ? '1' : '0'), - (dipsw2[5] ? '1' : '0'), - (dipsw2[6] ? '1' : '0'), - (dipsw2[7] ? '1' : '0')); - screen.Puts(0, y++, "PortC"); - screen.Puts(1, y++, TA::OnOff(xp_reset), "b7: XP Reset"); - screen.Puts(1, y++, TA::OnOff(parity), "b6: Parity"); - screen.Puts(1, y++, TA::Disable, "b5: ---"); - screen.Puts(1, y++, TA::OnOff(int5en), "b4: Int5 Enable"); - screen.Puts(1, y++, TA::OnOff(int5rq), "b3: Int5 Request"); - screen.Puts(1, y++, TA::OnOff(int1en), "b2: Int1 Enable"); - screen.Puts(1, y++, TA::Disable, "b1: ---"); - screen.Puts(1, y++, TA::OnOff(int1rq), "b0: Intq Request"); + // PIO0 + screen.Puts(0, 0, "PIO0 (BaseAddr: $49000000)"); + y = MonitorUpdateCommon(screen, 1); + + // PIO0 固有部 + uint32 pa = GetPA(); + uint32 pb = GetPB(); + screen.Print(1, y, "PortA(DIP-SW1)=$%02x:", pa); + screen.Print(1, y + 1, "PortB(DIP-SW2)=$%02x:", pb); + for (int i = 0; i < 8; i++) { + // この図は bit7..0 をこの順で表示するので bitpos の向きは固定。 + int bitpos = 7 - i; + // DIPSW の物理位置 #1 をバイトの下にするか上にするかが機種によって + // 違う。どうして…。 + int label = (msb_first ? i : bitpos) + 1; + + bool d1 = (pa & (1U << bitpos)); + bool d2 = (pb & (1U << bitpos)); + screen.Print(21 + i * 7, y, TA::OnOff(d1), "DIP1-%d", label); + screen.Print(21 + i * 7, y + 1, TA::OnOff(d2), "DIP2-%d", label); + } + y += 2; + + uint32 pc = GetPC(); + screen.Print(1, y, "PortC(XP etc.)=$%02x:", pc); + static const char * const pcstr[] = { + "XP RST", "PARITY", "------", "INT5EN", + "INT5RQ", "INT1EN", "------", "INT1RQ", + }; + for (int i = 0; i < 8; i++) { + screen.Puts(21 + i * 7, y, TA::OnOff(pc & (1U << (7 - i))), pcstr[i]); + } + y++; + + // PIO1 移譲 + y++; + pio1->MonitorUpdatePIO1(screen, y); } // PortA (DIP-SW 1) 読み込み @@ -487,12 +530,12 @@ PIO0Device::GetPC() const uint32 data; data = 0x00 // XXX 空きビットは何が読めるか? - | (int1rq ? 0x01 : 0) - | (int1en ? 0x04 : 0) - | (int5rq ? 0x08 : 0) - | (int5en ? 0x10 : 0) - | (parity ? 0x40 : 0) - | (xp_reset?0x80 : 0); + | (intreq[INT_L] ? 0x01 : 0) + | (inten[INT_L] ? 0x04 : 0) + | (intreq[INT_H] ? 0x08 : 0) + | (inten[INT_H] ? 0x10 : 0) + | (parity ? 0x40 : 0) + | (xp_reset ? 0x80 : 0); return data; } @@ -502,31 +545,21 @@ PIO0Device::SetPC(int pc, int val) { switch (pc) { case 0: - if ((val ^ int1rq)) { - int1rq = val; - putlog(0, "INT1 Interrupt Request %d (NOT IMPLEMENTED)", int1rq); - } + // INT1 割り込み要求 (Read Only) return; case 1: // Not Connected. return; case 2: - if ((val ^ int1en)) { - int1en = val; - putlog(0, "INT1 Interrupt Enable %d (NOT IMPLEMENTED)", int1en); - } + inten[INT_L] = val; + ChangeInterrupt(); return; case 3: - if ((val ^ int5rq)) { - int5rq = val; - putlog(0, "INT5 Interrupt Request %d (NOT IMPLEMENTED)", int5rq); - } + // INT5 割り込み要求 (Read Only) return; case 4: - if ((val ^ int5en)) { - int5en = val; - putlog(0, "INT5 Interrupt Enable %d (NOT IMPLEMENTED)", int5en); - } + inten[INT_H] = val; + ChangeInterrupt(); return; case 5: // Not Connected. @@ -540,33 +573,82 @@ PIO0Device::SetPC(int pc, int val) // XP リセット if ((val ^ xp_reset)) { xp_reset = val; - putlog(0, "XP Reset %d (NOT IMPLEMENTED)", xp_reset); + putlog(1, "XP Reset %d", xp_reset); + auto mpu64180 = GetMPU64180Device(); + mpu64180->ChangeRESET(xp_reset); } return; } VMPANIC("invalid pc=%d", pc); } +// とりあえず、XP からここまではエッジトリガーで、 +// ここから割り込みコントローラへはレベルトリガーとしておく。 + +// XP からの割り込み (High) +void +PIO0Device::InterruptXPHigh() +{ + intreq[INT_H] = true; + ChangeInterrupt(); +} + + +// XP からの割り込み (Low) +void +PIO0Device::InterruptXPLow() +{ + intreq[INT_L] = true; + ChangeInterrupt(); +} + +// 割り込み状態を変更。 +// intreq[], inten[] の変更で呼ぶこと。 +void +PIO0Device::ChangeInterrupt() +{ + bool val; + + val = (inten[INT_H] && intreq[INT_H]); + interrupt->ChangeINT(DEVICE_XPINT_HIGH, val); + + val = (inten[INT_L] && intreq[INT_L]); + interrupt->ChangeINT(DEVICE_XPINT_LOW, val); +} + // // LUNA PIO1 // // 仕様は 0x4d000000 から 4バイトだが、 -// 0x4c000000 から 0x50000000 の手前まで 4バイト単位でミラーが見える。 +// LUNA-I では 0x4c000000 から 0x4fffffff まで 4バイト単位でミラーが見える。 +// LUNA-88K では 0x4c000000 から 0x4dffffff までが +// (こちらはロングワード配置なので) 16バイト単位のミラーに見える。 // コンストラクタ PIO1Device::PIO1Device() - : inherited("PIO1") + : inherited(OBJ_PIO1) { - devaddr = 0x4d000000; - devlen = 4; - } // デストラクタ PIO1Device::~PIO1Device() { - gPIO1 = NULL; +} + +// 初期化 +bool +PIO1Device::Init() +{ + if (inherited::Init() == false) { + return false; + } + + lcd = GetLCDDevice(); + mpu64180 = GetMPU64180Device(); + powerdev = GetPowerDevice(); + + return true; } // リセット @@ -578,7 +660,8 @@ PIO1Device::ResetHard(bool poweron) // デフォルトで PIO 出力は「1msec 後に電源オフ」という状態なので、 // 電源オンから 1msec 以内にソフトウェアがこれをキャンセルしないと // いけないらしい。どうしてこうなった…。 - gPower->SetSystemPowerOn(false); + power = false; + powerdev->SetSystemPowerOn(false); } uint64 @@ -589,7 +672,7 @@ PIO1Device::Read(uint32 offset) switch (offset) { case 0: // PIO1 PortA: LCD データ - return gLCD->Read(); + return lcd->Read(); case 1: // PIO1 PortB: 無効 @@ -597,7 +680,7 @@ PIO1Device::Read(uint32 offset) case 2: // PortC - data = gLCD->Get(); + data = lcd->Get(); data |= xp_intreq ? 0 : 0x02; // 残りのビットは %1 だろうか? data |= 0x0d; @@ -610,7 +693,7 @@ PIO1Device::Read(uint32 offset) return data; default: - __unreachable(); + VMPANIC("corrupted offset=%d", offset); } } @@ -619,11 +702,14 @@ PIO1Device::Write(uint32 offset, uint32 { switch (offset) { case 0: // PIO1 PortA: LCD データ - gLCD->Write(data); + lcd->Write(data); return 0; - case 1: // PIO1 PortB: ローカル割り込み - break; + case 1: // PIO1 PortB: XP の INT0 をアサート + // データ不問でアサートする。 + putlog(1, "Assert XP INT0"); + mpu64180->AssertINT0(); + return 0; case 2: // PIO1 PortC: LCD/パワーオフ制御 for (int i = 0; i < 8; i++) { @@ -637,39 +723,68 @@ PIO1Device::Write(uint32 offset, uint32 return 0; default: - __unreachable(); + VMPANIC("corrupted offset=%d", offset); } - VMPANIC("$%08X <- $%02X 未サポート書き込み", gMPU->GetPaddr(), data); - return 0; } uint64 PIO1Device::Peek(uint32 offset) { - uint32 data; - switch (offset) { case 0: // PIO1 PortA LCD データ - return gLCD->Peek(); + return lcd->Peek(); case 1: // PIO1 PortB 無効 return 0xff; case 2: // PIO1 PortC - data = gLCD->Get(); - data |= xp_intreq ? 0 : 0x02; - // 残りのビットは %1 だろうか? - data |= 0x0d; - return data; + return GetPC(); case 3: return ReadCtrl(); default: - __unreachable(); + return 0xff; } } +// PIO0 の下請け。 +void +PIO1Device::MonitorUpdatePIO1(TextScreen& screen, int y) +{ + screen.Puts(0, y++, "PIO1 (BaseAddr: $4d000000)"); + + // 共通部分 + y = MonitorUpdateCommon(screen, y++); + + // PIO1 固有部 + screen.Print(1, y++, "PortA(LCDDATA)=$%02x", lcd->Peek()); + screen.Print(1, y++, "PortB(NotConn)=$%02x", 0xff); + uint32 pc = GetPC(); + screen.Print(1, y, "PortC(LCD etc)=$%02x:", pc); + static const char * const pcstr[] = { + "LCD E", "LCD RS", "LCD RD", "!PWOFF", + "------", "------", "!XPINT", "------", + }; + for (int i = 0; i < 8; i++) { + screen.Puts(21 + i * 7, y, TA::OnOff(pc & (1U << (7 - i))), pcstr[i]); + } +} + +uint32 +PIO1Device::GetPC() const +{ + uint32 data; + + data = lcd->Get(); + data |= xp_intreq ? 0 : 0x02; + data |= power ? 0x10 : 0; + // 残りのビットは %1 だろうか? + data |= 0x0d; + + return data; +} + void PIO1Device::SetPC(int pc, int val) { @@ -687,21 +802,21 @@ PIO1Device::SetPC(int pc, int val) if (power) { // 電源オフ取り消し putlog(1, "Power-off canceled"); - gPower->SetSystemPowerOn(true); + powerdev->SetSystemPowerOn(true); } else { // 1msec 後に電源オフ putlog(1, "Power-off scheduled"); - gPower->SetSystemPowerOn(false); + powerdev->SetSystemPowerOn(false); } return; case 5: - gLCD->SetRW(val); + lcd->SetRW(val); return; case 6: - gLCD->SetRS(val); + lcd->SetRS(val); return; case 7: - gLCD->SetE(val); + lcd->SetE(val); return; default: