--- nono/m88xx0/m88100core.cpp 2026/04/29 17:04:31 1.1.1.1 +++ nono/m88xx0/m88100core.cpp 2026/04/29 17:04:49 1.1.1.7 @@ -1,6 +1,7 @@ // // nono -// Copyright (C) 2020 isaki@NetBSD.org +// Copyright (C) 2020 nono project +// Licensed under nono-license.txt // #include "m88100.h" @@ -8,46 +9,102 @@ #include "debugger.h" #include "cvprompt.h" #include "mpu.h" +#include "scheduler.h" #define OP_DEF(name) void __CONCAT(m88kcpu::op_,name)() #define OP_FUNC(name) __CONCAT(op_,name)() #include "m88100ops.cpp" +// 文字列を作る前にレベルを評価するためマクロ +#define cpulog(lv, fmt...) do { \ + if (__predict_false(gMPU->loglevel >= (lv))) { \ + gMPU->putlogn(fmt); \ + } \ +} while (0) + // リセット例外のようなもの? void -m88kcpu::Reset(uint32 reset_vector) +m88kcpu::Reset() { - // TODO: 未完成 + // 内部の割り込み状態をクリア + intr_pending = 0; + atomic_reqflag &= ~(CPU_REQ_RESET | CPU_REQ_INTR | CPU_REQ_STOP); + + // Table 6-8 (p6-47) psr = 0x800003ff; - // SFD1 = 0; + // XXX ただし SFU1 は未実装なので下げておく psr &= ~PSR_SFD1; -#if TEST_INTERRUPT -psr &= ~PSR_IND; -psr &= ~PSR_SFRZ; +#if defined(TEST_INTERRUPT) + psr &= ~PSR_IND; + psr &= ~PSR_SFRZ; #endif - vbr = 0; + SetPSR(); // 実際には m88k MPU はリセット直後は 0 番地から実行を開始するので、 // 大抵の場合システムでリセット直後は 0 番地付近に ROM を見せるように // してあるのだと思うが、ここではその代わりに ROM のアドレスを指定して、 // 直接そこから実行を開始することにする。 fip = reset_vector; + + // レジスタのうち明記されてるものをクリア。 + // 不定と明記されてるものはリセットでは触らないでおく。 + // scoreboard: cleared nip = 0; xip = 0; + vbr = 0; + fpecr = 0; + fpcr = 0; + fpsr = 0; + // DMTx は bit0(Valid) をクリア。他は不定 + dmt0 &= ~DM_VALID; + dmt1 &= ~DM_VALID; + dmt2 &= ~DM_VALID; + + cmmu[0].Reset(); + cmmu[1].Reset(); + + // 履歴を初期化 + exhist.Clear(); + brhist.Clear(); + + // 例外履歴に記録 (例外発生はブランチ履歴にも記録) + // リセット例外発生時の from にはあまり意味がないので 0 にするか。 + exhist.AddEntry(0 | 1, 0, 0xfc000000 | 0); + brhist.AddEntry(0 | 1, 0, 0xfc000000 | 0); + // デバッガに通知 (例外ブレーク用) + debugger_notify_exception(0); fetch(); } -// request サイクル分実行する。 +// 仮想時間 delta [nsec] だけ CPU を実行する。実際には大抵行き過ぎる。 +// 戻り値は CPU 状態 (Scheduler::SCHED_CPU_*)。 uint32 -m88kcpu::Run(uint64 request) +m88kcpu::Run(uint32 delta) { - uint64 goal_cycle = total_cycle + request; bool is_release = false; bool is_intr = false; + assert(used_cycle == 0); + + goal_cycle = Vtime2Cycle(delta); + if (__predict_false(goal_cycle == 0)) { + goal_cycle = 1; + } + + if ((atomic_reqflag & CPU_REQ_STOP)) { + if ((atomic_reqflag & (CPU_REQ_INTR | CPU_REQ_PROMPT | CPU_REQ_RESET))) { + // 割り込み、デバッガ、リセットが来たら STOP 解除。 + atomic_reqflag &= ~CPU_REQ_STOP; + } else { + // STOP 継続として戻る。 + used_cycle = goal_cycle; + goto exit; + } + } + while (!is_release) { // shift pipeline opX = opF; @@ -61,13 +118,24 @@ m88kcpu::Run(uint64 request) if (atomic_reqflag != 0) { // 何かが起きた + if ((atomic_reqflag & CPU_REQ_RESET)) { + // リセット例外。 + Reset(); + // XIP がリセットベクタを指すためにはもう一回フェッチが必要 + // なので、ここは continue が必要。 + continue; + } + // 割り込みの認識 // 割り込みの実行は命令実行完了後、次のプリフェッチの前 if ((atomic_reqflag & CPU_REQ_INTR)) { atomic_reqflag &= ~CPU_REQ_INTR; - is_intr = true; -#if TEST_INTERRUPT - atomic_reqflag |= CPU_REQ_PROMPT; + if (intr_pending && IsIntrEnable()) { + // この命令のあとに割り込み処理 + is_intr = true; + } +#if defined(TEST_INTERRUPT) + atomic_reqflag |= CPU_REQ_PROMPT; #endif } @@ -90,6 +158,11 @@ m88kcpu::Run(uint64 request) // プロンプトを抜けたのでここでフラグを落とす atomic_reqflag &= ~CPU_REQ_PROMPT; + + // プロンプトから戻ってきたら一旦スケジューラまで戻る(?)。 + // reset コマンドのようにデバッガからスケジューラに出した + // リクエストを(なるはやで?)回収するため。 + atomic_reqflag |= CPU_REQ_RELEASE; } if ((atomic_reqflag & CPU_REQ_RELEASE)) { @@ -111,26 +184,41 @@ m88kcpu::Run(uint64 request) switch (op12) { #include "m88100switch.inc" default: - __unreachable(); + OP_FUNC(illegal); + break; } - if (is_intr) { + if (is_intr && IsIntrEnable()) { + // 割り込み処理要求が来ていて、直前の命令が割り込みを禁止していなければ割り込み + // XXX 実際は違う気がする is_intr = false; - if (!(psr & PSR_IND)) { - gMPU->putlog(4, "INTR take"); - ExceptionCore(M88K_EXCEP_INTERRUPT, ExceptionKind::INTR); - continue; - } else { - gMPU->putlog(4, "INTR DISABLE"); + cpulog(4, "INTR take"); + ExceptionCore(M88K_EXCEP_INTERRUPT, ExceptionKind::INTR); + continue; + } + + used_cycle++; + + if ((atomic_reqflag & CPU_REQ_STOP)) { + // STOP 状態は、残りサイクルを消費したことにして戻る + cpulog(2, "STOP 状態検出"); + if (used_cycle < goal_cycle) { + used_cycle = goal_cycle; } } - total_cycle++; - if (total_cycle >= goal_cycle) + if (used_cycle >= goal_cycle) break; } - return atomic_reqflag; + exit: + total_vtime += Cycle2Vtime(used_cycle); + used_cycle = 0; + + // HALT 状態がないので NORMAL か STOP かだけでいい + return (atomic_reqflag & CPU_REQ_STOP) + ? Scheduler::SCHED_CPU_STOP + : Scheduler::SCHED_CPU_NORMAL; } // この命令後に実行を中断して VM に戻る @@ -140,8 +228,15 @@ m88kcpu::Release() atomic_reqflag |= CPU_REQ_RELEASE; } +// リセット例外を発行する +void +m88kcpu::RequestReset() +{ + atomic_reqflag |= CPU_REQ_RESET; +} + // 通常の例外。 -// エラー、割り込み、TRAP からは使いません。 +// DataAccessException, エラー、割り込み、TRAP からは使わない。 // vec はベクタ番号。 void m88kcpu::Exception(int vec) @@ -154,23 +249,52 @@ m88kcpu::Exception(int vec) void m88kcpu::ExceptionCore(int vec, ExceptionKind cause) { + uint syscall = 0; + + // 例外履歴に記録 (例外発生はブランチ履歴にも記録) + // OpenBSD システムコールなら番号もついでに記録。 + // システムコール番号は tb0 発行時点で r13 にセットされている。 + // ただし 0 番は syscall(2) (間接システムコール) で、実際の番号は r2。 + // 198番 __syscall(2) だと r2:r3 (なので実質 r3)。 + if (vec == 450) { + syscall = r[13]; + if (syscall == 0) { + syscall = r[2]; + } else if (syscall == 198) { + syscall = r[3]; + } + syscall &= 0xfff; + } + uint32 from = xip | (IsSuper() ? 1 : 0); + uint32 inst = 0xfc000000 | (syscall << 12) | vec; + exhist.AddEntry(from, 0, inst); + brhist.AddEntry(from, 0, inst); + // デバッガに通知 (例外ブレーク用) + debugger_notify_exception(vec); + + // 命令中で起きる例外はとりあえず全部2クロック追加? (Table.7-5) + if (cause != ExceptionKind::INTR) { + AddCycle(2); + } + if (psr & PSR_SFRZ) { if (cause != ExceptionKind::TRAP) { - gMPU->putlog(2, "SFRZ Error Exception"); + cpulog(2, "SFRZ Error Exception"); vec = M88K_EXCEP_ERROR; cause = ExceptionKind::ERROR; } } else { - gMPU->putlog(2, "Exception %x", vec); + cpulog(2, "Exception %x", vec); + if (vec != 1) { + cpulog(1, "Exception %x x=%x n=%x f=%x", vec, xip, nip, fip); + } sxip = xip; snip = nip; - sfip = fip; + sfip = fip; sxip |= SIP_V; snip |= SIP_V; - // XXX:実機と異なり、fip は常にフェッチ前なので V=0 - // ^^^^ コレ自体怪しい. INTERRUPT とか。 - // でもたぶん例外ハンドラでここのビット見ないだろうなあ + sfip |= SIP_V; if (OpIsBusErr(opX)) { sxip |= SIP_E; // E bit @@ -184,11 +308,57 @@ m88kcpu::ExceptionCore(int vec, Exceptio } } + if (cause != ExceptionKind::DATA) { + // データアクセス例外以外は、 + // データパイプラインはフラッシュ状態とする。 + // ほんとうは違う。かも。 + dmt0 = 0; + } else { + // xip: ld/st/xmem + // nip: next + // fip: fetch + + // rom1.2 は DAE が起きると、 + + // ROM挙動 CPU の RTE 挙動 + // sfip = sxip + // sxip -> ~V (INVALID) CPU はそもそも sxip を評価しない + // snip = 0 (INVALID) CPU はこれを nop 相当に処理する + // rte + + // してくる。そのまま読むと命令を再実行しようとしているように + // 読めるが、再実行すると無限ループになってしまう。 + // DAE の時点で xip が進んでいることを期待して利用している? + + // openbsd は + // xmem の write バスエラーを検知すると命令を再実行するために + // fip = nip, nip = xip して rte する。ということは xip = xmem に + // なっていることを期待している。 + + // というわけで + // ld で DataAccessException のときは、実CPUはすでに次の命令を + // 実行している? + // もう少し考えると、スコアボードでレジスタの干渉がなければ、 + // 実行している? + + // 仕方がないのでハックでごまかす。 + // rte のほうも参照のこと。 + + if (IsSuper() && xip >= 0x41000000 && xip < 0x42000000) { + // 遅延例外機構がないので、ROM 合わせのハック。 + sxip = snip; + } + } + epsr = psr; - psr |= PSR_SUPER | PSR_SFD1 | PSR_IND | PSR_SFRZ; + SetPSR(psr | PSR_SUPER | PSR_SFD1 | PSR_IND | PSR_SFRZ); fip = vbr + (vec << 3); fetch(); + // ここでベクタに飛ぶのだが、ベクタ内の2命令でおそらく必ずもう一度 + // 分岐してそっちでも履歴が残るので、ここでブランチ履歴を残すのは + // ちょっと冗長という気もする。 + if (OpIsBusErr(opF)) { if (cause == ExceptionKind::ERROR) { // double bus fault @@ -200,62 +370,192 @@ m88kcpu::ExceptionCore(int vec, Exceptio } } +inline +uint32 +m88kcpu::Calcdmt(uint32 flag) +{ + return + ((psr & PSR_BO_LE) ? DM_BO : 0) + | ((IsSuper() && !(flag & DM_USR)) ? DM_DAS : 0) + | DM_VALID; +} + // データアクセス例外(Read) +// ld.b, ld.h, ld.hu, ld 用 void -m88kcpu::ReadDataException(uint32 addr, uint32 flag) +m88kcpu::ReadDataException32(uint32 addr, uint32 flag) { // ミスアラインドはこの例外より前にチェックされていて、 // ここにはアラインドしかこない。 dma0 = addr & ~0x3; - dmt0 = ((psr & PSR_BO_LE) ? DM_BO : 0) - | (((psr & PSR_SUPER) && !(flag & DM_USR)) ? DM_DAS : 0) - | (flag & DM_DOUB1) // ダブルワードの第1アクセスのときセット - | (flag & DM_LOCK) // xmem でセット + dmt0 = Calcdmt(flag) | (FLD_D << 7) | (flag & DM_SIGNED) | ((flag & DM_EN_MASK) >> (addr & 0x03)) - | 0 // DM_WRITE = 0 - | DM_VALID; - Exception(M88K_EXCEP_DATA); + ; + dmt1 &= ~DM_VALID; + dmt2 &= ~DM_VALID; + cpulog(1, "ReadDAE xip=%x addr=%x", xip, addr); + ExceptionCore(M88K_EXCEP_DATA, ExceptionKind::DATA); +} + +// データアクセス例外(Read) +// ld.d 用 +void +m88kcpu::ReadDataException64(uint32 addr, uint32 flag) +{ + // ミスアラインドはこの例外より前にチェックされていて、 + // ここにはアラインドしかこない。 + dma0 = addr; + dmt0 = Calcdmt(flag) + | DM_DOUB1 // ダブルワードの第1アクセスのときセット + | (FLD_D << 7) + | DM_EN_MASK // 32ビット有効 + ; + + dma1 = dma0 + 4; + dmt1 = Calcdmt(flag) + | (FLD_D2 << 7) + | DM_EN_MASK // 32ビット有効 + ; + + dmt2 &= ~DM_VALID; + + if ((flag & DM_DOUB1) == 0) { + // .d の2ワード目がバスエラー。第1ワードのアクセスは成功した。 + dmt0 &= ~DM_VALID; + } + + cpulog(1, "ReadDAE64 xip=%x addr=%x", xip, addr); + ExceptionCore(M88K_EXCEP_DATA, ExceptionKind::DATA); } // データアクセス例外(Write) +// st.b, st.h, st.hu, st 用 void -m88kcpu::WriteDataException(uint32 addr, uint32 data, uint32 flag) +m88kcpu::WriteDataException32(uint32 addr, uint32 flag) { // ミスアラインドはこの例外より前にチェックされていて、 // ここにはアラインドしかこない。 dma0 = addr & ~0x3; - dmt0 = ((psr & PSR_BO_LE) ? DM_BO : 0) - | (((psr & PSR_SUPER) && !(flag & DM_USR)) ? DM_DAS : 0) - | (flag & DM_DOUB1) // ダブルワードの第1アクセスのときセット - | (flag & DM_LOCK) // xmem でセット + dmt0 = Calcdmt(flag) + | (FLD_D << 7) + | ((flag & DM_EN_MASK) >> (addr & 0x03)) + | DM_WRITE + ; + dmd0 = rD; + + dmt1 &= ~DM_VALID; + dmt2 &= ~DM_VALID; + cpulog(1, "WriteDAE xip=%x addr=%x", xip, addr); + ExceptionCore(M88K_EXCEP_DATA, ExceptionKind::DATA); +} + +// データアクセス例外(Write) +// st.d 用 +void +m88kcpu::WriteDataException64(uint32 addr, uint32 flag) +{ + // ミスアラインドはこの例外より前にチェックされていて、 + // ここにはアラインドしかこない。 + dma0 = addr & ~0x3; + dmt0 = Calcdmt(flag) + | DM_DOUB1 // ダブルワードの第1アクセスのときセット + | (FLD_D << 7) + | DM_EN_MASK + | DM_WRITE + ; + dmd0 = rD; + + dma1 = dma0 + 4; + dmt1 = Calcdmt(flag) + | (FLD_D2 << 7) + | DM_EN_MASK + | DM_WRITE + ; + dmd1 = rD2; + + dmt2 &= ~DM_VALID; + + if ((flag & DM_DOUB1) == 0) { + // .d の2ワード目がバスエラー。第1ワードのアクセスは成功した。 + dmt0 &= ~DM_VALID; + } + + cpulog(1, "WriteDAE64 xip=%x addr=%x", xip, addr); + ExceptionCore(M88K_EXCEP_DATA, ExceptionKind::DATA); +} + +// データアクセス例外(xmem) +// xmem には .d は無い +void +m88kcpu::XmemDataException(uint32 addr, uint32 flag) +{ + dma0 = addr & ~0x3; + dmt0 = Calcdmt(flag) + | DM_LOCK // xmem でセット + | (FLD_D << 7) + | (flag & DM_SIGNED) + | ((flag & DM_EN_MASK) >> (addr & 0x03)) + ; + + dma1 = dma0; + dmt1 = Calcdmt(flag) + | DM_LOCK // xmem でセット | (FLD_D << 7) | (flag & DM_SIGNED) | ((flag & DM_EN_MASK) >> (addr & 0x03)) | DM_WRITE - | DM_VALID; + ; + dmd1 = rD; - // dmd は Byte Enable 位置に配置される - if (flag & DM_SIZE_B) { - dmd0 = data << ((3 - (addr & 0x03)) * 8); - } else if (flag & DM_SIZE_H) { - dmd0 = data << ((2 - (addr & 0x03)) * 16); + dmt2 &= ~DM_VALID; + + // ミスアラインドはこの例外より前にチェックされていて、 + // ここにはアラインドしかこない。 + if (cmmu[1].fault_code != m88200::FAULT_CODE_BUSERR + || cmmu[1].acc_read) { + // アドレス変換フォルトまたは read 時点 + // nop } else { - dmd0 = data; + // xmem の write 時点 + dmt0 &= ~DM_VALID; } - Exception(M88K_EXCEP_DATA); + + cpulog(1, "xmemDAE xip=%x addr=%x", xip, addr); + ExceptionCore(M88K_EXCEP_DATA, ExceptionKind::DATA); +} + +// FP 例外 +// fpex: FP例外コード M88K_FPEXCEP +void +m88kcpu::FPException(int fpex) +{ + // FDVZ 以上の例外が PRECISE。 + // 未満の FUNF, FOVF, FINX は IMPRECISE。 + int vec = (fpex >= M88K_FPEXCEP_FDVZ) ? M88K_EXCEP_SFU1_PRECISE : M88K_EXCEP_SFU1_IMPRECISE; + fpecr = fpex; + + Exception(vec); } void -m88kcpu::Interrupt() +m88kcpu::Interrupt(int level) { + // level は 0 か 1。 + intr_pending = level; atomic_reqflag |= CPU_REQ_INTR; - gMPU->putlog(4, "INTERRUPT ACK (アドレスは不正確)"); } void m88kcpu::fpu_unimpl() { printf("%s\n", __func__); + FPException(M88K_FPEXCEP_FUNIMP); +} + +OP_DEF(illegal) +{ + // 本来は不当命令例外? + cpulog(0, "Illegal instruction %08x", (uint32)opX); }