--- nono/m680x0/m68030core.cpp 2026/04/29 17:04:28 1.1 +++ nono/m680x0/m68030core.cpp 2026/04/29 17:05:26 1.1.1.11 @@ -1,230 +1,843 @@ // // nono -// Copyright (C) 2017 isaki@NetBSD.org +// Copyright (C) 2020 nono project +// Licensed under nono-license.txt // -#include "header.h" -#include "m68030core.h" +#include "mpu680x0.h" +#include "m68030cache.h" #include "debugger.h" -#include "cvprompt.h" -#include "mpu.h" +#include "interrupt.h" +#include "mainbus.h" +#include "scheduler.h" +#include "vectortable.h" + +#define OP_FUNC(name) __CONCAT(op_,name)() + +// 1命令実行する。 +// 割り込みチェックなどを省いたショート版。 +void +MPU680x0Device::ExecShort(Event& ev) +{ + uint64 cycle_start = used_cycle; -#define OP_PROTO(name) static inline void __CONCAT(op_,name)(m68kcpu *cpu) -#define OP_DEF(name) static inline void __CONCAT(op_,name)(m68kcpu *cpu) -#define OP_FUNC(name) __CONCAT(op_,name)(cpu) - -// (エミュレータ的)未実装命令 -// 今はまだマクロ -#define op_unimpl(cpu) \ - PANIC("unimplemented instruction! %s()", __FUNCTION__) - -#include "m68030bus.h" -#include "m68030bus.cpp" -#include "m68030excep.h" -#include "m68030ea.h" -#include "m68030acc.h" -#include "m68030ops.h" -#include "m68030mmu.h" -#include "m68030fpu.h" -#include "m68030ops.cpp" - -static void m68030_flush_interrupt(m68kcpu *cpu); - -// request サイクル(を超えるところまで)実行する。 -uint32 -m68030_run(m68kcpu *cpu, uint64 request) -{ - uint64 goal_cycle = cpu->total_cycle + request; - - // メインループ - while (1) { - if (cpu->atomic_reqflag != 0) { - // 何かが起きた - - // デバッグ用 - if (0) { - static int oldreq = -1; - uint32 _req = cpu->atomic_reqflag; - if ((_req & (CPU_REQ_TRACE | CPU_REQ_PROMPT)) != oldreq) { - oldreq = _req & (CPU_REQ_TRACE | CPU_REQ_PROMPT); - printf("reqflag=%c%c\n", - (_req & CPU_REQ_TRACE) ? 'T' : '-', - (_req & CPU_REQ_PROMPT) ? 'P' : '-'); - } - } + buswait = 0; - // XXX 発生頻度を観測すること。 - // そして並び替えるとか、フラグの割り当てを考えるとか。 + try { + // 1命令版では switch-case を使うしかない。 + ppc = reg.pc; + flag_pi = 0; + flag_pd = 0; + ir = fetch_2(); + + // uint16 を 6ビット右シフトしたら 1024 以上になるはずは + // ないのだが、デフォルトでは gcc/clang どちらも律儀に + // 1024 以上かどうか比較して分岐しようとする。 + // gcc は __assume() を入れることでこの比較・分岐を出力 + // しないようにすることが出来た(がパフォーマンスの差は + // 見えない程度でしかなかった)。 + // clang ではどうやったら効くか不明。 + uint16 ir6 = ir >> 6; + __assume(ir6 < 1024); + switch (ir6) { +#include "m68030switch.h" + } - // このフラグ処理の優先度はたぶん次のような感じになるはず。 - // 1. スケジューラ依頼による処理の中断 - // 2. 命令途中で起きた例外の処理 - // 3. 割り込みチェック - // 4. STOP、HALT 状態の対応 - - if ((cpu->atomic_reqflag & CPU_REQ_RELEASE)) { - // ここで実行を中断してスケジューラに戻る - cpu->atomic_reqflag &= ~CPU_REQ_RELEASE; - return cpu->atomic_reqflag; + if (__predict_false(icache->enable == false)) { + used_cycle += cycle_nocache; + cycle_nocache = 0; + } + } catch (uint vector) { + // ここで(C++の)例外スローをキャッチするのは + // - メモリ空間をアクセスした先でのバスエラー例外 + // - EA パース中の不当命令例外 + // のみ。それ以外の命令途中例外はその場で処理できる。 + if (__predict_false(loglevel >= 2)) { + const char *name = vectortable->GetExceptionName(vector); + if (__predict_true(name)) { + putlogn("%s", name); + } else { + putlogn("vector 0x%x", vector); } + } + if (vector == M68K::EXCEP_BUSERR) { + ExceptionBuserr(vector); + } else { + Exception(vector); + } + } - if ((cpu->atomic_reqflag & CPU_REQ_INTR)) { - // 割り込みチェック - // 必要ならこの中で例外処理を起動する - m68030_flush_interrupt(cpu); - } + // 外部割り込みの起動は VM 側で処理している。 - if ((cpu->atomic_reqflag & CPU_REQ_STOP)) { - // STOP 状態は、残りサイクルを消費したことにして戻る - cpu->reg.state = CPU_REQ_STOP; - cpu->total_cycle = goal_cycle; - return cpu->atomic_reqflag; - } + int cycle = used_cycle - cycle_start; + ev.time = cycle * clock_nsec + buswait; + scheduler->StartEvent(ev); +} - if ((cpu->atomic_reqflag & CPU_REQ_HALT)) { - cpu->reg.state = CPU_REQ_HALT; - PANIC("未実装 HALT 状態"); - } +// 二重バスフォールト +void +MPU680x0Device::DoubleBusFault(const char *where) +{ + // where をログに出す? - if ((cpu->atomic_reqflag & CPU_REQ_TRACE)) { - // デバッガプロンプトを出すかどうかを判断する - cpu->inst_count++; - if (debugger_check()) { - cpu->atomic_reqflag |= CPU_REQ_PROMPT; - } - } + // ホールト状態へ移行 + Halt(); +} - if ((cpu->atomic_reqflag & CPU_REQ_PROMPT)) { - // デバッガプロンプトのための実行停止 +// SR (と CCR) をセットする。 +// リセット例外以外から使う (リセット例外はやや異なるため)。 +// +// 割り込みマスクが変わる場合は、ここで次の命令実行コールバックを差し替える。 +void +MPU680x0Device::SetSR(uint16 data) +{ + bool oldS = reg.s; + bool oldM = reg.m; - // プロンプト獲得を通知 - gCVPrompt->NotifyAcquire(); + // 現在の SP を保存 + if (oldS == false) { + reg.usp = reg.A[7]; + } else if (oldM == false) { + reg.isp = reg.A[7]; + } else { + reg.msp = reg.A[7]; + } - // プロンプト解放を待機 - gCVPrompt->WaitRelease(); + reg.ccr.Set(data); + reg.s = ((data & M68K::SR_S) != 0); + reg.m = ((data & M68K::SR_M) != 0); + + // 割り込みマスクレジスタを更新して.. + uint new_mask = (data >> 8) & 7; + if (reg.intr_mask != new_mask) { + reg.intr_mask = new_mask; - // プロンプトを抜けたのでここでフラグを落とす - cpu->atomic_reqflag &= ~CPU_REQ_PROMPT; - } + // 次の命令境界で割り込みをチェックさせる + MakeNextFull(); + } + + // 新しい SP を用意 + if (reg.s == false) { + reg.A[7] = reg.usp; + } else if (reg.m == false) { + reg.A[7] = reg.isp; + } else { + reg.A[7] = reg.msp; + } + + // FC2-0 + fc_inst = BusAddr::Fetch | busaddr::SU(reg.s); + fc_data = BusAddr::D | busaddr::SU(reg.s); + + // XXX 他に何か状態変えないといけないことあったっけか。 +} + +// CACR レジスタ書き込み +void +MPU680x0Device::SetCACR(uint32 newval) +{ + newval &= M68K::CACR_MASK; + uint32 oldval = reg.cacr; + uint32 chgval = oldval ^ newval; + + // EI (命令キャッシュ有効) + if ((chgval & M68K::CACR_EI) != 0) { + if ((newval & M68K::CACR_EI) != 0) { + // 命令キャッシュ有効 + icache->enable = true; + icache->hist.Clear(); + putlog(1, "I-Cache enabled"); + } else { + // 命令キャッシュ無効 + icache->enable = false; + putlog(1, "I-Cache disabled"); + // 命令キャッシュが有効だった期間 nocache は使われずに + // 積算され続けているが、キャッシュ無効になるとこれを加算 + // しようとするのでここでリセット。 + cycle_nocache = 0; } + } - try { -#define USE_COMPUTED_GOTO -#if defined(USE_COMPUTED_GOTO) - // computed-goto を使う方法。 - // JUMP が1回目の分岐。CHECK_AND_JUMP が命令実行後の分岐。 - // outer は命令実行前にチェックしてはいけないので、2つに - // 分かれている。1回目の分岐も goto.h の中で出力してある。 -#define JUMP \ - RegPPC = RegPC; \ - cpu->flag_pi = 0; \ - cpu->flag_pd = 0; \ - RegIR = m68030_fetch_16(cpu); \ - __assume((RegIR >> 6) < 1024); \ - goto *jumptable[RegIR >> 6] - -#define CHECK_AND_JUMP \ - cpu->total_cycle += cpu->cycle_cache; \ - cpu->total_cycle += cpu->cycle_nocache; \ - cpu->cycle_cache = 0; \ - cpu->cycle_nocache = 0; \ - if (cpu->total_cycle >= goal_cycle) return cpu->atomic_reqflag; \ - if (cpu->atomic_reqflag != 0) goto exit_inner_loop; \ - JUMP - -#include "m68030goto.h" - exit_inner_loop:; -#else - // switch-case を使う方法。 - do { - RegPPC = RegPC; - cpu->flag_pi = 0; - cpu->flag_pd = 0; - RegIR = m68030_fetch_16(cpu); - - // uint16 を 6ビット右シフトしたら 1024 以上になるはずは - // ないのだが、デフォルトでは gcc/clang どちらも律儀に - // 1024 以上かどうか比較して分岐しようとする。 - // gcc は __assume() を入れることでこの比較・分岐を出力 - // しないようにすることが出来た(がパフォーマンスの差は - // 見えない程度でしかなかった)。 - // clang ではどうやったら効くか不明。 - uint16 ir = RegIR >> 6; - __assume(ir < 1024); - switch (ir) { -#include "m68030switch.h" - } + // FI (命令キャッシュ凍結) + if ((chgval & M68K::CACR_FI) != 0) { + putlog(0, "CACR:FI Not Implemented"); + icache->freeze = (newval & M68K::CACR_FI); + } + + // CEI (命令キャッシュの指定エントリをクリア) + if ((chgval & M68K::CACR_CEI) != 0) { + // CAAR の b7-2 で示されるロングワードを無効にする。 + uint32 idx = (reg.caar & 0x000000f0) >> 4; + uint32 sel = (reg.caar & 0x0000000c) >> 2; + auto& l = icache->line[idx]; + l.valid[sel] = 0; + + newval &= ~M68K::CACR_CEI; + } - cpu->total_cycle += cpu->cycle_cache; - cpu->total_cycle += cpu->cycle_nocache; - cpu->cycle_cache = 0; - cpu->cycle_nocache = 0; - if (cpu->total_cycle >= goal_cycle) - return cpu->atomic_reqflag; - - } while (cpu->atomic_reqflag == 0); -#endif - } catch (int vector) { - // ここで(C++の)例外スローをキャッチするのは - // - メモリ空間をアクセスした先でのバスエラー例外 - // - EA パース中の不当命令例外 - // のみ。それ以外の命令途中例外はその場で処理できる。 - cpulog(1, "%s", m68030_get_exception_name(vector)); - m68030_exception(cpu, vector); - } - } - __unreachable(); -} - -// ペンディングの割り込みを確認 -static void -m68030_flush_interrupt(m68kcpu *cpu) -{ - // 割り込みマスクをレベル(1-7)に変換するテーブル - // b はペンディングになってる割り込みのうち今回処理する最上位の1つ - // を示すビットが立った状態。不正入力がないとすると入力は 0 〜 64。 - static uint8 mask2level[65] = { - // 0 1 2 3 4 5 6 7 8 9 A B C D E F - 0,7,6,0,5,0,0,0, 4,0,0,0,0,0,0,0, // $00 - 3,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, // $10 - 2,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, // $20 - 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, // $30 - 1, - }; - int b; - - cpu->intrmtx.lock(); - - b = cpu->intr_pending & cpu->intr_mask; - if (b != 0) { - // 立っている最下位ビットだけを残す - b &= -b; - - // 該当フラグは落としておく - cpu->intr_pending &= ~b; - - // ストップ状態を解除 - cpu->atomic_reqflag &= ~CPU_REQ_STOP; - cpu->reg.state = 0; - - // b をレベルに変換 - int level = mask2level[b]; - int vector = cpu->intr_vector[level]; + // CI (命令キャッシュクリア) + if ((chgval & M68K::CACR_CI) != 0) { + for (auto& l : icache->line) { + l.valid_all = 0; + } + + newval &= ~M68K::CACR_CI; + } + + // IBE (命令バースト有効) + if ((chgval & M68K::CACR_IBE) != 0) { + icache->burst_enable = (newval & M68K::CACR_IBE) && has_burst_access; + } + + // ED (データキャッシュ有効) + if ((chgval & M68K::CACR_ED) != 0) { + if ((newval & M68K::CACR_ED) != 0) { + // データキャッシュ有効。 + dcache->enable = true; + dcache->hist.Clear(); + putlog(1, "D-Cache enabled"); + } else { + // データキャッシュ無効。 + dcache->enable = false; + putlog(1, "D-Cache disabled"); + } + } + + // FD (データキャッシュ凍結) + if ((chgval & M68K::CACR_FD) != 0) { + dcache->freeze = (newval & M68K::CACR_FD); + putlog(1, "CACR:FD -> %u", dcache->freeze); + } - // ロックここまで。例外処理時に intrmtx は外しておくこと - cpu->intrmtx.unlock(); + // CED (データキャッシュの指定エントリをクリア) + if ((chgval & M68K::CACR_CED) != 0) { + // CAAR の b7-2 で示されるロングワードを無効にする。 + uint32 idx = (reg.caar & 0x000000f0) >> 4; + uint32 sel = (reg.caar & 0x0000000c) >> 2; + auto& l = dcache->line[idx]; + l.valid[sel] = 0; - // 例外処理を起動 - m68030_exception_interrupt(cpu, level, vector); + newval &= ~M68K::CACR_CED; + } - // 次の命令境界でも割り込みチェックを行う - // 更に下のレベルの割り込みがペンディングになっている可能性のため + // CD (データキャッシュクリア) + if ((chgval & M68K::CACR_CD) != 0) { + for (auto& l : dcache->line) { + l.valid_all = 0; + } + + newval &= ~M68K::CACR_CD; + } + + // DBE (データバースト有効) + if ((chgval & M68K::CACR_DBE) != 0) { + dcache->burst_enable = (newval & M68K::CACR_DBE) && has_burst_access; + } + + // WA (ライトアロケート) + if ((chgval & M68K::CACR_WA) != 0) { + dcache->wa = (newval & M68K::CACR_WA); + } + + // とりあえずレジスタイメージも保存。 + reg.cacr = newval; +} + +// 命令中に (An)+ で変化したアドレスレジスタを命令実行前の状態に巻き戻す。 +// flag_pi は bit7..0 が save_pi[7..0] の A7..A0 に対応している。 +void +MPU680x0Device::restore_reg_pi() +{ + for (int i = 7; flag_pi; i--) { + if ((int8)flag_pi < 0) { + reg.A[i] = save_pi[i]; + } + flag_pi <<= 1; + } +} + +// 命令中に -(An) で変化したアドレスレジスタを命令実行前の状態に巻き戻す。 +// flag_pd は bit7..0 が save_pd[7..0] の A7..A0 に対応している。 +void +MPU680x0Device::restore_reg_pd() +{ + for (int i = 7; flag_pd; i--) { + if ((int8)flag_pd < 0) { + reg.A[i] = save_pd[i]; + } + flag_pd <<= 1; + } +} + +// vector にジャンプする。vector はベクタ番号(アドレスではない)。 +void +MPU680x0Device::JumpVector(uint vector, bool from_buserr) +{ + uint32 vecaddr; + uint32 newpc; + + vecaddr = reg.vbr + vector * 4; + newpc = read_4(vecaddr); + // VBR 込みのジャンプ先が確定したところで今度はブランチ履歴に記録。 + // この時のブランチ元はベクタアドレス + // (reg.pc は fetch によって進んでしまうので先に保存) + uint32 inst = BranchHistory_m680x0::VectorJump; + brhist.AddEntry(vecaddr | (IsSuper() ? 1 : 0), newpc, inst); + + // 奇数番地ならアドレスエラー + if (__predict_false((newpc & 1) != 0)) { + if (from_buserr) { + // バスエラー処理中ならダブルバスフォールト。 + throw M68K::EXCEP_ADDRERR; + } else { + ExceptionBuserr(M68K::EXCEP_ADDRERR); + } + return; + } + reg.pc = newpc; + + // 実際はここでパイプライン充填のために3ワードフェッチするので、 + // ベクタテーブルはフェッチ出来たけどそのジャンプ先がバスエラーに + // なるケースはここでバスエラーが出なければいけないが、まだ + // キャッシュを実装していないので、ここで1ワードフェッチしてみて + // PC を戻すあほっぽい方法を取る。 + fetch_2(); + reg.pc -= 2; +} + +// 例外スタックフレーム $0 を生成する。 +// vector はベクタ番号。 +// pc はスタックに積むプログラムカウンタの値 (例外によって、この命令位置か +// 次の命令位置かが異なる)。 +// sr は例外発生時の SR。 +inline void +MPU680x0Device::GenerateExframe0(uint vector, uint32 pc, uint16 sr) +{ + // 例外スタックフレーム $0 + // SP+$00.W STATUS REGISTER + // +$02.L PROGRAM COUNTER ; 例外ごとに異なる + // +$06.W %0000 | VECTOR + + push_2((0 << 12) | (vector << 2)); + push_4(pc); + push_2(sr); +} + +// 例外スタックフレーム $2 を生成する。 +// vector はベクタ番号。 +// スタックに積む PC は常に次の命令。 +inline void +MPU680x0Device::GenerateExframe2(uint vector, uint16 sr) +{ + // 例外スタックフレーム $2 + // SP+$00.W STATUS REGISTER + // +$02.L PROGRAM COUNTER ; 次の命令位置 + // +$06.W %0010 | VECTOR + // +$08.L INSTRUCTION ADDRESS ; 例外を起こした命令のアドレス + + push_4(ppc); // 例外を起こした命令のアドレス + push_2((2 << 12) | (vector << 2)); + push_4(reg.pc); // 次の命令位置 + push_2(sr); +} + +// 例外スタックフレーム $B を生成する。 +inline void +MPU680x0Device::GenerateExframeB(uint vector, uint16 sr) +{ + uint16 ssw; + uint32 fault_addr; + uint32 stageBaddr; + + stageBaddr = reg.pc + 2; + // この後のスタック操作で bus が変化する前に保存 + fault_addr = bus.laddr.Addr(); + + // DF は常に立てる必要がある?? + ssw = bus.GetSSW() | M68K::SSW_DF; + if (mmu_enable && (ssw & M68K::FC_DATA) != 0) { + // 命令アクセスなら RB をセット。 + ssw |= M68K::SSW_RB; + } + + // 例外スタックフレーム $B + // SP+$00.W STATUS REGISTER + // +$02.L PROGRAM COUNTER + // +$06.W %1011 | VECTOR + // +$08.W (internal register) + // +$0a.W SPECIAL STATUS REGISTER ; SSW + // +$0c.W INSTRUCTION PIPE STAGE C + // +$0e.W INSTRUCTION PIPE STAGE B + // +$10.L DATA CYCLE FAULT ADDRESS + // +$14.W*2 (internal register) + // +$18.L DATA OUTPUT BUFFER + // +$1c.W*4 (internal register, 4 words) + // +$24.L STAGE B ADDRESS + // +$28.W*2 (internal register, 2 words) + // +$2c.L DATA INPUT BUFFER + // +$30.W*3 (internal register, 3 words) + // +$36.W VERSION# | internal information + // +$38.W*18 (internal register, 18 words) + // +$5a + // こっちから上に向かって PUSH + + // internal registers, 18 words + // 内部レジスタ18ワードのうち、ここでは17ワード分を save_pd[8] (16ワード)、 + // flag_pd (1ワード) として使うことにする。 + // -(An) でバスエラーが起きると An は減算された状態になるが、DF ビットが + // 立っていて再実行する時には現行構造では命令先頭から再実行せざるをえず、 + // そうするともう一度プリデクリメントが動いてしまう。そのため、RTE の時点 + // で DF=1 ならプリデクリメント発生前の An に戻すという処理をする。 + // RTE 時点で DF=0 なら命令を再実行しないため An も巻き戻してはならず、 + // そのためプリデクリメント分の巻き戻しは RTE 時点まで行えない。 + // 一方、ポストインクリメントのケースはこの少し下の + // ExceptionBuserr() 内のコメント参照。 + for (int i = 7; i >= 0; i--) { + push_4(save_pd[i]); + } + push_2(flag_pd); + reg.A[7] -= 2; + + reg.A[7] -= 2; // VERSION# + reg.A[7] -= 6; + push_4(0); // DATA INPUT BUFFER + reg.A[7] -= 4; + push_4(stageBaddr); // STAGE B ADDRESS + reg.A[7] -= 8; + push_4(0); // DATA OUTPUT BUFFER + reg.A[7] -= 4; + push_4(fault_addr); // DATA CYCLE FAULT ADDRESS + push_2(0); // INSTRUCTION PIPE STAGE B + push_2(0); // INSTRUCTION PIPE STAGE C + push_2(ssw); + reg.A[7] -= 2; + push_2(0xb000 | (vector << 2)); + // 本当は現在の PC だが、再実行のため PPC を積む。 + push_4(ppc); // PC + push_2(sr); // 例外入り口で保存した SR +} + +// 例外処理について。 +// +// 0. リセット例外 +// リセット例外は外部から ExceptionReset() を呼び出す。 +// +// 1. バスエラー例外 +// MMU による論理アドレスでのバスエラーは (Translate*() が false を返した後) +// m68030bus.cpp ルーチンが (C++の) 例外をスローする。 +// 物理アドレス空間アクセス後のバスエラーは (VM側の Read*()/Write*() が +// BusErr を返した後) m68030bus.cpp ルーチンが (C++) の例外をスローする。 +// どちらのケースも m68030core.cpp のメインループで catch し、その catch +// ブロック内で ExceptionBuserr() を呼び出す。 +// +// 2. アドレスエラー例外 +// アドレスエラーは Jump() 内でのみ発生するはずで、その場で +// ExceptionBuserr() を呼んで処理する。 +// +// 3. 命令中で起きる例外 +// 命令途中で起きる(確定的な)例外は Exception() を呼び出す。 +// これに該当するのは +// o 特権違反 +// o A系列/F系列命令 +// o TRAP* 系命令、CHK/CHK2 命令 +// o トレース例外 (未実装) +// o ゼロ除算? +// o MMU 構成例外 +// o 不当命令 (*) +// である。 +// ただし、不当命令についてはどこで不当命令と判定されるかによって +// フローが複数存在することに注意。 +// o 1ワード目が不当命令 +// -> 命令がそれぞれ Exception() で処理。 +// o 1ワード目の EA が不当命令 +// -> (C++の)例外をスローして catch 内で Exception() を実行。 +// o 2ワード目が不当命令 +// -> 命令がそれぞれ Exception() で処理。 +// +// 4. 割り込み例外 +// 別コメント(下のほう)参照。 +// + +// リセット例外処理。 +// 各レジスタを所定の状態にして、リセットベクタを読み込む。 +// リセットベクタが読み込めなければ HALT 状態に移行。 +// 戻り値は消費したサイクル数。 +int +MPU680x0Device::ExceptionReset() +{ + uint64 cycle_start = used_cycle; + + // 例外履歴に記録 (例外発生はブランチ履歴にも記録) + // m68k では今の所リセット例外発生時の PC にはあまり意味がないけど、 + // m88k と動作を揃えておく。 + const uint vector = 0; + excep_counter[0]++; + exhist.AddEntry(reg.pc | 1, 0, BranchHistory_m680x0::Exception | 0); + brhist.AddEntry(reg.pc | 1, 0, BranchHistory_m680x0::Exception | 0); + // デバッガに通知 (例外ブレーク用)。こっちのベクタは 0 のままでよい。 + debugger->NotifyExceptionMain(vector); + + // RESET 信号線をネゲートしてから SP の読み込み開始まで 4クロック + // (Section 7.8, Figure 7-64) + used_cycle += 4; + + // STOP 状態とホールト状態を解除。 + ChangeState(CPU_STATE_NORMAL); + + // 1. T0, T1 クリア + // 2. S をセット、M をクリア + // 3. 割り込みレベルを 7 + + // ここは SetSR を使わずに設定する。 + reg.s = true; + reg.m = false; + fc_inst = BusAddr::S | BusAddr::Fetch; + fc_data = BusAddr::S | BusAddr::D; + // ここは割り込みレベル変化のチェックは不要でセットだけでいい。 + reg.intr_mask = 7; + + // 4. VBR をクリア + reg.vbr = 0; + + // 5. キャッシュの EI|ED, FI|FD, IBE|DBE, WA をクリア + // 6. キャッシュエントリをすべてクリア (CI|CD) + SetCACR(M68K::CACR_CD | M68K::CACR_CI); + cycle_nocache = 0; + icache->hist.Clear(); + dcache->hist.Clear(); + + // 7. TC:E、TTn:E をクリア + SetTC(GetTC() & ~m68030TC::TC_E); + SetTT(0, GetTT(0) & ~m68030TT::E); + SetTT(1, GetTT(1) & ~m68030TT::E); + // 8. リセットベクタ生成 + + // 9. SP をロード + // 10. PC をロード + try { + reg.pc = 0; + reg.A[7] = fetch_4(); + uint32 pc = fetch_4(); + // 大丈夫のはずだけど fetch_4() が reg.pc を書き換えるので一応 + reg.pc = pc; + // リセットベクタは常に 0, 4 番地から取得したことになっている + uint32 inst = BranchHistory_m680x0::VectorJump; + brhist.AddEntry(0x00000004 | 1, reg.pc, inst); + } catch (uint) { + DoubleBusFault("reset exception"); + } + + // SP のアドレスエラーはリセット例外ではチェックしないようだ。 + // PC のアドレスエラーは本来ならここにあるはずの3ワードのプリフェッチで + // 行われるのだが、それがないので、とりあえずここで。 + if ((reg.pc & 1) != 0) { + DoubleBusFault("reset exception"); + } + + return used_cycle - cycle_start; +} + +// 汎用の例外処理を行う。vector はベクタ番号。 +// ただし以下の例外はここではなくそれぞれ固有のを呼ぶこと。 +// o リセット例外は ExceptionReset() +// o 割り込み例外は ExceptionInterrupt() +// o TRAP#15 例外は ExceptionTrap15() +// o バスエラー・アドレスエラーは ExceptionBuserr() +void +MPU680x0Device::Exception(uint vector) +{ + uint32 inst = BranchHistory_m680x0::Exception | vector; + ExceptionGeneric(vector, inst); +} + +// TRAP #15 の例外処理を行う。 +// 処理自体は汎用の Exception() とまったく同じだが、 +// IOCS コールの時には分岐履歴の出力が変わるため入口を分けてある。 +void +MPU680x0Device::ExceptionTrap15() +{ + // 今発生した TRAP #15 が IOCS コールっぽいかどうか調べる。 + // + // Human68k 方面をやってる時は TRAP #15 は IOCS コールで表示してほしい。 + // 一方、NetBSD のカーネル内では TRAP #15 は IOCS コールではない別用途 + // なので IOCS コールかのように表示されるのは紛らわしい。さらに言えば + // NetBSD でもブートローダの時点はまだ IOCS コールを使うので、出来れば + // 今呼ばれたこれが IOCS コールなのか、そうでない TRAP #15 なのかは + // 区別したい。 + // + // IOCS コールかどうかは厳密には分からないが、これが呼ばれた時点で + // ワークエリアの $cbc == 0x03、$cbe == 0xff であれば IOCS コールと + // 判定することにする。 + // $cbc は MPU 種別で 3:68030。 + // $cbe は MMU 有無で 0xff:有り。 + // 今の所 68030/MMU有り しかサポートしていないので。 + // だめならまた考える。 + + const uint vector = M68K::EXCEP_TRAP15; + uint32 inst; + if (mainbus->Peek1(0xcbc) == 0x03 && mainbus->Peek1(0xcbe) == 0xff) { + // IOCS コールっぽい + inst = BranchHistory_m680x0::IOCSCall | (reg.D[0] & 0xff); } else { - // ペンディングの割り込みはなくなったので、 - // 次の命令境界では割り込みチェックは不要 - cpu->atomic_reqflag &= ~CPU_REQ_INTR; + // TRAP#15 っぽい + inst = BranchHistory_m680x0::Exception | vector; + } + ExceptionGeneric(vector, inst); +} + +// 汎用の例外処理本体。 +void +MPU680x0Device::ExceptionGeneric(uint vector, uint32 inst) +{ + // 例外履歴に記録 (例外発生はブランチ履歴にも記録) + excep_counter[vector]++; + uint32 from = ppc | (IsSuper() ? 1 : 0); + exhist.AddEntry(from, 0, inst); + brhist.AddEntry(from, 0, inst); + // デバッガに通知 (例外ブレーク用) + debugger->NotifyExceptionMain(vector); + + // 状態を保存 + uint16 sr = GetSR(); + + // S を立てて T1,T0 を落とす + SetSR((GetSR() | M68K::SR_S) & ~M68K::SR_T); + + try { + switch (vector) { + case M68K::EXCEP_ILLEGAL: // 不当命令 + case M68K::EXCEP_PRIV: // 特権違反例外 + case M68K::EXCEP_ALINE: // Aライン命令例外 + case M68K::EXCEP_FLINE: // Fライン命令例外 + // スタックに積む PC はこの命令のアドレス + GenerateExframe0(vector, ppc, sr); + break; + + case M68K::EXCEP_TRAP0 // TRAP#0 + ... M68K::EXCEP_TRAP15: // TRAP#15 + // スタックに積む PC は次の命令 + GenerateExframe0(vector, reg.pc, sr); + break; + + case M68K::EXCEP_ZERODIV: // ゼロ除算 + case M68K::EXCEP_CHK: // CHK + case M68K::EXCEP_TRAPV: // TRAPV + case M68K::EXCEP_MMU_CONFIG: // MMU 構成例外 + GenerateExframe2(vector, sr); + break; + + default: + PANIC("$%08x exception vector %u ?", ppc, vector); + break; + } + + // ベクタにジャンプ + JumpVector(vector, false); + } catch (uint vector2) { + assertmsg(vector2 == M68K::EXCEP_BUSERR, "vector=%u", vector); + + // バスエラー処理へ + ExceptionBuserr(vector2); + } +} + +// バスエラー・アドレスエラー例外処理を行う。 +// 他の例外とは違いが多いので分けた。 +void +MPU680x0Device::ExceptionBuserr(uint vector) +{ + assertmsg((vector == M68K::EXCEP_BUSERR || vector == M68K::EXCEP_ADDRERR), + "vector=%u", vector); + + // 例外履歴に記録 (例外発生はブランチ履歴にも記録) + excep_counter[vector]++; + uint32 from = ppc | (IsSuper() ? 1 : 0); + uint32 inst = BranchHistory_m680x0::Exception | vector; + exhist.AddEntry(from, 0, inst); + brhist.AddEntry(from, 0, inst); + // デバッガに通知 (例外ブレーク用) + debugger->NotifyExceptionMain(vector); + + // 状態を保存 + uint16 sr = GetSR(); + + // ここで (An)+ で変化したレジスタを命令実行前の状態に巻き戻す。 + // EA 処理の時点で先にインクリメントしているのだが、バスエラーが + // 起きたということは実際にはポストインクリメントは発生しないので、 + // ここで無条件に巻き戻してよい。SR を書き換えると A7 が切り替わって + // しまうので、この巻き戻しは SR を書き換える前のここで行うこと。 + // 一方、プリデクリメントのケースはこの少し上の ExframeB の中の + // コメント参照。 + restore_reg_pi(); + + // S を立てて T1,T0 を落とす + SetSR((GetSR() | M68K::SR_S) & ~M68K::SR_T); + + try { + // フレームを作成。 + // 実際はショート($A)、ロング($B)の2種類あって、本来ならショートが + // 使えればショートを使うのだが、ここでは全部ロングを使う。 + GenerateExframeB(vector, sr); + // ベクタにジャンプ + JumpVector(vector, true); + } catch (uint vector2) { + // ここに来るのはバスエラーかアドレスエラーだけのはず + assertmsg(vector2 == M68K::EXCEP_BUSERR || + vector2 == M68K::EXCEP_ADDRERR, "vector=%u", vector); + + // バスエラー・アドレスエラー処理中のバスエラーは + // ダブルバスフォールト。 + DoubleBusFault("bus/address error handling"); + } +} + +// 割り込み例外処理を行う。 +// 割り込みの受け付けとレベルのチェック等の後、次命令境界で呼ばれるやつ。 +// この処理にかかる時間 [nsec] を返す。 +uint64 +MPU680x0Device::ExceptionInterrupt() +{ + uint64 cycle_start = used_cycle; + uint level = intr_level; + + // ユーザーズマニュアル(日本語) p.399 図8-5 の中ほど + // 「/IPEND をネゲート、割り込みアクノリッジサイクルを実行」 + // 以降に相当。 + + // STOP なら解除 + ChangeState(CPU_STATE_NORMAL); + + // 割り込みアクノリッジサイクルを実行 + uint32 vector; + busdata bd = interruptdev->InterruptAcknowledge(level); + if (__predict_false(bd.IsBusErr())) { + // 外部回路がバスエラーを返すと Spurious を発行する。(7.4.1.3) + vector = M68K::EXCEP_SPURIOUS; + } else { + vector = bd.Data(); + if ((int32)vector < 0) { + // オートベクタ要求 (7.4.1.2) + // (負数(-1)ならオートベクタということにしている) + vector = M68K::EXCEP_LEVEL_BASE + level; + } + } - // ロックここまで (atomic なのでその前で解放してもいいけど) - cpu->intrmtx.unlock(); + // 例外履歴に記録 (例外発生はブランチ履歴にも記録) + excep_counter[vector]++; + uint32 from = ppc | (IsSuper() ? 1 : 0); + uint32 inst = BranchHistory_m680x0::Exception | vector; + exhist.AddEntry(from, 0, inst); + brhist.AddEntry(from, 0, inst); + // デバッガに通知 (例外ブレーク用) + debugger->NotifyExceptionMain(vector); + + // 状態を保存 + uint16 oldsr = GetSR(); + + // S を立てて T1,T0 を落とす。割り込みレベルも変更。 + uint16 newsr = GetSR() & ~(M68K::SR_T | M68K::SR_IMASK); + newsr |= M68K::SR_S; + newsr |= level << 8; + SetSR(newsr); + + try { + // 例外スタックフレーム $0 を生成。 + // スタックに積む PC は次の命令位置。 + // M ビットは未実装なのでスローアウェイとか無視 + GenerateExframe0(vector, reg.pc, oldsr); + + // ベクタにジャンプ + JumpVector(vector, false); + } catch (uint vector2) { + assertmsg(vector2 == M68K::EXCEP_BUSERR, "vector=%u", vector); + + // バスエラー処理へ + ExceptionBuserr(vector2); } + + return (used_cycle - cycle_start) * clock_nsec + bd.GetWait(); +} + +// RTE 命令。 +// 例外処理の近くに置いておくほうが便利なので。 +void +MPU680x0Device::ops_rte() +{ + uint32 pc; + uint16 sr; + uint16 frame; + + try { + sr = pop_2(); + pc = pop_4(); + frame = pop_2(); + switch (frame >> 12) { + case 0: + CYCLE2(18, 20); + SetSR(sr); + Jump(pc); + break; + + case 2: + CYCLE2(18, 20); + // +08.L INSTRUCTION ADDRESS を読み捨てる + reg.A[7] += 4; + SetSR(sr); + Jump(pc); + break; + + case 0x0b: + { + uint32 stage_b; + uint16 ssw; + + CYCLE(76); + reg.A[7] += 2; // $08.W + ssw = pop_2(); // $0a.W SSW + reg.A[7] += 4 * 6; // $0c.L*6 + stage_b = pop_4(); // $24.L Stage B + reg.A[7] += 2 * 9; // $28.W*9 + // $3a..$58 ここが save_pd の取り出し + flag_pd = pop_2(); + for (int i = 0; i < 8; i++) { + save_pd[i] = pop_4(); + } + // A7 レジスタをリストアするかもしれない前に Super/User を + // 復帰しておかないといけない + SetSR(sr); + + // DFビットセットなら命令を再実行するため、-(An) で変化 + // したレジスタを巻き戻す。 + // DFビットクリアなら例外要因の命令はハンドラで処理された + // とみなして、次の命令に進む。 + if ((ssw & M68K::SSW_DF)) { + restore_reg_pd(); + } else { + pc = stage_b - 2; + } + Jump(pc); + break; + } + + default: + PANIC("RTE frame=$%x not supported", frame >> 12); + break; + } + } catch (uint vector2) { + assertmsg(vector2 == M68K::EXCEP_BUSERR, "vector2=%u", vector2); + + // RTE 処理中のバスエラーはダブルバスフォールト。 + DoubleBusFault("bus/address error handling"); + } +} + +// STOP 命令。 +void +MPU680x0Device::ops_stop() +{ + uint32 data = fetch_2(); + SetSR(data); + ChangeState(CPU_STATE_STOP); + + MakeNextFull(); }