--- nono/vm/romemu_luna88k.cpp 2026/04/29 17:05:43 1.1.1.16 +++ nono/vm/romemu_luna88k.cpp 2026/04/29 17:06:01 1.1.1.18 @@ -70,9 +70,14 @@ enum OPm5 { ne0 = 0x0d, }; enum OPcr { - psr = 0x01, + psr = 0x01, + ssbr = 0x03, snip = 0x05, sfip = 0x06, + sr0 = 0x11, + sr1 = 0x12, + sr2 = 0x13, + sr3 = 0x14, }; enum CMPbits { CMP_EQ = 2, @@ -129,6 +134,9 @@ USC uint32 OP_ld(OPr rd, OPr rs1, uint32 USC uint32 OP_ldcr(OPr rd, uint32 crs) { return OP4(0x80004000, (uint32)rd, 0, crs, 0); } +USC uint32 OP_mask(OPr rd, OPr rs1, uint32 imm) { + return OP3(0x48000000, (uint32)rd, (uint32)rs1, imm); +} USC uint32 __unused OP_or(OPr rd, OPr rs1, OPr rs2) { return OP3(0xf4005800, (uint32)rd, (uint32)rs1, (uint32)rs2); } @@ -156,6 +164,12 @@ USC uint32 OP_st_b(OPr rd, OPr rs1, OPr USC uint32 OP_stcr(OPr rs1, uint32 crd) { return OP4(0x80008000, 0, (uint32)rs1, crd, (uint32)rs1); } +USC uint32 OP_xcr(OPr rd, OPr rs1, uint32 cr) { + return OP4(0x8000c000, (uint32)rd, (uint32)rs1, cr, (uint32)rs1); +} +USC uint32 OP_xmem(OPr rd, OPr rs1, uint32 imm) { + return OP3(0x04000000, (uint32)rd, (uint32)rs1, imm); +} USC uint32 OP_doscall(OPr rd, OPr rs1, uint32 imm) { return OP3(0xfc000400, (uint32)rd, (uint32)rs1, imm); } @@ -189,6 +203,7 @@ Luna88kPROMEmuDevice::Init() // ROM の特定アドレスを読み出す人がいるかも知れないので、 // サイズは実 ROM と同じにしておく。LUNA-I からの類推。 if (AllocROM(256 * 1024, 0) == false) { + // エラーメッセージは表示済み。 return false; } @@ -200,6 +215,18 @@ Luna88kPROMEmuDevice::Init() MemoryStreamBE roms(imagebuf.get()); + // m88k では ROMIO_BASE は上位ワードだけで表現できれば楽。 + static_assert((ROMIO_BASE & 0xffff) == 0, ""); + + // ROM のメモリマップ: + // $41000000.L*3 リセットベクタから ROM _START へ飛ぶコード。 + // $4100000c ROM _START + // $41000200 割り込みハンドラ + // $41000270 sysclk + // $41000300 ROM GETC + // $41000400 ROM PUTC + // $41000500 データアクセス例外ハンドラ + // リセット時のブートページには ROM (これ) が見えている。 // ROMIO_INIT へのアクセスでブートページを RAM に切り替えて、同時に RAM の // 0番地付近のベクタも作り直す。そのため、ここのベクタ1 以降 ($41000008〜) @@ -211,85 +238,125 @@ Luna88kPROMEmuDevice::Init() // _START: // ここは 0番地。ここから _ROM_START へジャンプ - roms.Write4(OP_or_u (r6, r0, 0x4100)); - roms.Write4(OP_or (r6, r6, 0x000c)); - roms.Write4(OP_jmp (r6)); + roms.Write4(OP_or_u (r1, r0, 0x4100)); + roms.Write4(OP_or (r1, r1, 0x000c)); + roms.Write4(OP_jmp (r1)); // _ROM_START: (ここは ROM) + // r1: work (最後は戻りアドレスとして使う) + // r2: work (最後はジャンプ先として使う) + // r3: work + // r5: ROMIO_BASE + // RAM に切り替え - roms.Write4(OP_st (r0, r6, r0)); + roms.Write4(OP_st (r0, r1, r0)); + + // 割り込み許可 (INIT の前に必要)。 + // なお PROM が次段に処理を移す時は割り込み許可のまま進んでいるので + // ここも割り込みを許可したそのまま進む。 + roms.Write4(OP_ldcr (r1, psr)); + roms.Write4(OP_clr (r1, r1, 2, 0)); // clear IND,SFRZ + roms.Write4(OP_stcr (r1, psr)); + + // 先に ROMIO_INIT だけ実行。 + roms.Write4(OP_or_u (r5, r0, ROMIO_BASE >> 16)); + roms.Write4(OP_ld (r0, r5, (ROMIO_INIT - ROMIO_BASE))); + + // _rom_main: + uint32 rom_main = roms.GetOffset(); + // _rom_main 以降で r5 を(再)初期化する必要がある。 + roms.Write4(OP_or_u (r5, r0, ROMIO_BASE >> 16)); + roms.Write4(OP_st (r0, r5, (ROMIO_QUITSTOP - ROMIO_BASE))); - // 元の PSR を r10 に保存し、r9 に割り込み許可の値を作る - roms.Write4(OP_ldcr (r10, psr)); - roms.Write4(OP_clr (r9, r10, 2, 0)); // clear IND,SFRZ - roms.Write4(OP_stcr (r9, psr)); - roms.Write4(OP_or (r11, r0, r0)); // r11 <- 0 - - roms.Write4(OP_or_u (r3, r0, ROMIO_BASE >> 16)); - roms.Write4(OP_or (r3, r3, ROMIO_BASE & 0xffff)); - // ROMIO_INIT を実行 - roms.Write4(OP_ld (r0, r3, (ROMIO_INIT - ROMIO_BASE))); - roms.Write4(OP_ld (r0, r3, (ROMIO_LOAD - ROMIO_BASE))); + roms.Write4(OP_ld (r0, r5, (ROMIO_LOAD - ROMIO_BASE))); roms.Write4(OP_br (5)); // _entry // _prompt: uint32 prompt = roms.GetOffset(); - roms.Write4(OP_or_u (r11, r0, 0x4100)); - roms.Write4(OP_or (r11, r11, roms.GetOffset() + 8)); - roms.Write4(OP_br (0)); // STOP 相当 - roms.Write4(OP_or (r11, r0, r0)); + roms.Write4(OP_or_u (r1, r0, 0x4100)); + roms.Write4(OP_or (r1, r1, roms.GetOffset() + 12)); + roms.Write4(OP_st (r1, r5, (ROMIO_QUITSTOP - ROMIO_BASE))); + // STOP 相当の無限ループ。 + // 抜ける時は割り込みハンドラから (QUITSTOP) に rte で戻ってくる。 + roms.Write4(OP_br (0)); // _entry: - roms.Write4(OP_ld (r2, r3, (ROMIO_ENTRY - ROMIO_BASE))); - roms.Write4(OP_or_c (r5, r0, r0)); // r5 <- $ffffffff - roms.Write4(OP_cmp (r5, r2, r5)); - roms.Write4(OP_bb1 (CMP_EQ, r5, -7)); // 起動先なければループ + // 起動可能なら ENTRY が -1 以外を返す + roms.Write4(OP_ld (r2, r5, (ROMIO_ENTRY - ROMIO_BASE))); + roms.Write4(OP_or_c (r1, r0, r0)); // r1 <- $ffffffff + roms.Write4(OP_cmp (r1, r2, r1)); + // 起動不可なら _prompt へ戻る + roms.Write4(OP_bb1 (CMP_EQ, r1, (prompt - roms.GetOffset()) / 4)); + // _go: // 起動先があれば後始末して - roms.Write4(OP_ld (r0, r3, (ROMIO_CLOSE - ROMIO_BASE))); - // 割り込み禁止に戻して - roms.Write4(OP_stcr (r10, psr)); + roms.Write4(OP_ld (r0, r5, (ROMIO_CLOSE - ROMIO_BASE))); + // 使ったレジスタを一応クリア + roms.Write4(OP_or (r3, r0, r0)); + roms.Write4(OP_or (r5, r0, r0)); // 戻ってきたときのために r1 を用意して roms.Write4(OP_or_u (r1, r0, 0x4100)); roms.Write4(OP_or (r1, r1, roms.GetOffset() + 8)); // ジャンプ roms.Write4(OP_jmp (r2)); // 戻ってきたら - roms.Write4(OP_br ((prompt - roms.GetOffset()) / 4)); + roms.Write4(OP_br ((rom_main - roms.GetOffset()) / 4)); // 割り込みハンドラ。 // _intr: roms.SetOffset(0x200); - roms.Write4(OP_or_u (r6, r0, 0x6500)); - roms.Write4(OP_ld (r6, r6, 0)); - roms.Write4(OP_extu (r6, r6, 3, 29)); - roms.Write4(OP_or (r7, r0, 6)); - roms.Write4(OP_cmp (r7, r6, r7)); - roms.Write4(OP_bb1 (CMP_EQ, r7, (0x240 - roms.GetOffset()) / 4)); - // key_intr: - roms.Write4(OP_ld (r0, r3, (ROMIO_KEYIN - ROMIO_BASE))); + // r1: work + // r2: work + // r5: ROMIO_BASE + + // レジスタを退避。r1 は sr2 に退避済み。 + roms.Write4(OP_st (r2, r0, 0x2000)); + roms.Write4(OP_st (r5, r0, 0x2004)); + roms.Write4(OP_or_u (r5, r0, ROMIO_BASE >> 16)); + + roms.Write4(OP_or_u (r1, r0, 0x6500)); + roms.Write4(OP_ld (r1, r1, 0)); + roms.Write4(OP_extu (r1, r1, 3, 29)); + roms.Write4(OP_or (r2, r0, 6)); + roms.Write4(OP_cmp (r2, r1, r2)); + // 割り込みは sysclk, sio のみ許可しているので必ずどちらか。 + roms.Write4(OP_bb1 (CMP_EQ, r2, (0x270 - roms.GetOffset()) / 4)); + // sio_intr: + roms.Write4(OP_ld (r0, r5, (ROMIO_SIOINTR - ROMIO_BASE))); + // _intr_ret: - // r11 があればそこに戻る + // QUITSTOP があれば rte の戻り先をそっちに書き換える。 uint32 intr_ret = roms.GetOffset(); - roms.Write4(OP_bcnd (eq0, r11, 5)); // beq @f - roms.Write4(OP_or (r6, r11, SxIP_VALID)); - roms.Write4(OP_stcr (r6, snip)); - roms.Write4(OP_addu (r6, r6, 0x0004)); - roms.Write4(OP_stcr (r6, sfip)); + roms.Write4(OP_ld (r1, r5, (ROMIO_QUITSTOP - ROMIO_BASE))); + roms.Write4(OP_bcnd (eq0, r1, 6)); // beq @f + roms.Write4(OP_st (r0, r5, (ROMIO_QUITSTOP - ROMIO_BASE))); + roms.Write4(OP_or (r1, r1, SxIP_VALID)); + roms.Write4(OP_stcr (r1, snip)); + roms.Write4(OP_addu (r1, r1, 0x0004)); + roms.Write4(OP_stcr (r1, sfip)); // @@: + // レジスタを復帰 + roms.Write4(OP_xcr (r1, r0, sr2)); + roms.Write4(OP_or (r2, r0, r0)); + roms.Write4(OP_or (r5, r0, r0)); + roms.Write4(OP_xmem (r2, r0, 0x2000)); + roms.Write4(OP_xmem (r5, r0, 0x2004)); + roms.Write4(OP_stcr (r0, ssbr)); roms.Write4(OP_rte ()); - // Sysclk 割り込みハンドラ。 - roms.SetOffset(0x240); + // _sysclk_intr: + assertmsg(roms.GetOffset() < 0x270, "offset=$%x", roms.GetOffset()); + roms.SetOffset(0x270); // ACK - roms.Write4(OP_or_u (r6, r0, 0x6300)); - roms.Write4(OP_st_b (r0, r6, r0)); + roms.Write4(OP_or_u (r1, r0, 0x6300)); + roms.Write4(OP_st_b (r0, r1, r0)); // inc - roms.Write4(OP_ld (r6, r3, (ROMIO_CLKCNT - ROMIO_BASE))); - roms.Write4(OP_addu (r6, r6, 1)); - roms.Write4(OP_st (r6, r3, (ROMIO_CLKCNT - ROMIO_BASE))); + roms.Write4(OP_ld (r1, r5, (ROMIO_CLKCNT - ROMIO_BASE))); + roms.Write4(OP_addu (r1, r1, 1)); + roms.Write4(OP_st (r1, r5, (ROMIO_CLKCNT - ROMIO_BASE))); roms.Write4(OP_br ((intr_ret - roms.GetOffset()) / 4)); // ROM GETC エミュレータ + assertmsg(roms.GetOffset() < 0x300, "offset=$%x", roms.GetOffset()); roms.SetOffset(0x300); roms.Write4(OP_doscall(r2, r0, DOS_GETCHAR)); roms.Write4(OP_jmp (r1)); @@ -300,23 +367,34 @@ Luna88kPROMEmuDevice::Init() roms.Write4(OP_doscall(r0, r2, DOS_PUTCHAR)); roms.Write4(OP_jmp (r1)); + // データアクセス例外ハンドラ。 + // 例外を起こした命令の次の命令 (現状 snip) に戻る。 + roms.SetOffset(0x500); + // PROM-1.20 互換動作にするため sr2 に退避した r1 はそのままにする。 + // boot のオレオレ machine read コマンドがこの動作を利用している。 + roms.Write4(OP_ldcr (r1, sr2)); + roms.Write4(OP_stcr (r0, ssbr)); + roms.Write4(OP_rte ()); + // サブルーチン roms.SetOffset(0x900); - roms.Write4(OP_ld (r2, r3, (ROMIO_AP1 - ROMIO_BASE))); + roms.Write4(OP_or_u (r5, r0, ROMIO_BASE >> 16)); + roms.Write4(OP_ld (r0, r5, (ROMIO_AP1 - ROMIO_BASE))); //@@: - roms.Write4(OP_or_u (r11, r0, 0x4100)); - roms.Write4(OP_or (r11, r11, roms.GetOffset() + 8)); + uint32 subr1 = roms.GetOffset(); + roms.Write4(OP_or_u (r2, r0, 0x4100)); + roms.Write4(OP_or (r2, r2, roms.GetOffset() + 12)); + roms.Write4(OP_st (r2, r5, (ROMIO_QUITSTOP - ROMIO_BASE))); roms.Write4(OP_br (0)); // STOP 相当 - roms.Write4(OP_or (r11, r0, r0)); - roms.Write4(OP_ld (r2, r3, (ROMIO_CLKCNT - ROMIO_BASE))); - roms.Write4(OP_or (r5, r0, 0x01ff)); - roms.Write4(OP_and (r2, r2, r5)); // r2 &= 0x1ff; + roms.Write4(OP_ld (r2, r5, (ROMIO_CLKCNT - ROMIO_BASE))); + roms.Write4(OP_mask (r2, r2, 0x01ff)); // r2 &= 0x1ff; uint freq = sysclk->GetFreq(); - roms.Write4(OP_or (r5, r0, freq * 3));// r5 = freq*3; - roms.Write4(OP_cmp (r5, r2, r5)); - roms.Write4(OP_bb1 (CMP_LT, r5, -9)); // blt @b - roms.Write4(OP_ld (r0, r3, (ROMIO_AP2 - ROMIO_BASE))); + roms.Write4(OP_or (r3, r0, freq * 3));// r5 = freq*3; + roms.Write4(OP_cmp (r3, r2, r3)); + roms.Write4(OP_bb1 (CMP_LT, r3, (subr1 - roms.GetOffset()) / 4)); + // blt @b + roms.Write4(OP_ld (r0, r5, (ROMIO_AP2 - ROMIO_BASE))); roms.Write4(OP_jmp (r1)); // 変なインデントここまで @@ -335,21 +413,42 @@ Luna88kPROMEmuDevice::ROM_InitMD() auto *sysctlr = dynamic_cast(GetInterruptDevice()); sysctlr->WriteIntMask(0, 0xc0000000); + // RAM のメモリマップ: + // $00000000 .. $00000fff 例外ベクタ + // $00001000 .. $000010ff トランポリン用 (PROM もここを使っている) + // $00001100 .. ? PROM 用が使う領域があるようだ + // $00002000 .. レジスタ保存用として勝手に使うことにする + rams.SetAddr(0); - // ベクタテーブルを rte で埋めておく + // ベクタテーブルを rte で埋めておく。 for (int i = 0; i < 512 * 2; i++) { rams.Write4(OP_rte()); } - // +$08: 割り込みベクタ - rams.Write4(8, OP_br(0x003fe)); // br $00001000 - - // 割り込みトランポリン (0x1000) - // ベクタテーブルは各2ワードしかないのに、0 番地付近にあるベクタテーブル - // から2ワードでは ROM まで飛べない。一体どういう設計してんだ…。 - rams.SetAddr(0x1000); - rams.Write4(OP_or_u (r6, r0, 0x4100)); - rams.Write4(OP_or (r6, r6, 0x0200)); - rams.Write4(OP_jmp (r6)); + // 例外ベクタの2ワードとトランポリン先の4ワードで PROM まで飛ぶ + // コードを出力する。 + static const struct { + uint32 vec; + uint32 toaddr; + } vectors [] = { + { 1, 0x41000200 }, // 割り込み + { 3, 0x41000500 }, // データアクセス例外 + }; + uint32 midaddr = 0x1000; + for (int i = 0; i < countof(vectors); i++) { + uint32 vec = vectors[i].vec; + uint32 toaddr = vectors[i].toaddr; + rams.SetAddr(vec * 8); + // ベクタは定型文で書いておく。 + rams.Write4(OP_or (r0, r0, r0)); + rams.Write4(OP_br ((midaddr - rams.GetAddr()) / 4)); + // 経由地で r1 を sr2 に退避してから、toaddr にジャンプ。 + rams.SetAddr(midaddr); + rams.Write4(OP_stcr (r1, sr2)); + rams.Write4(OP_or_u (r1, r0, toaddr >> 16)); + rams.Write4(OP_or (r1, r1, toaddr & 0xffff)); + rams.Write4(OP_jmp (r1)); + midaddr = rams.GetAddr(); + } // ROM の機能テーブル // 0x00001100 ? @@ -373,6 +472,17 @@ Luna88kPROMEmuDevice::ROM_InitMD() } } +void +Luna88kPROMEmuDevice::ROM_Close() +{ + inherited::ROM_Close(); + + // 割り込みをすべてマスクに戻す。 + // XXX とりあえず CPU#0 のみ + auto *sysctlr = dynamic_cast(GetInterruptDevice()); + sysctlr->WriteIntMask(0, 0x00000000); +} + // ファイルをロードする。 // -X が指定されていればそのホストファイル。 // そうでなければゲストファイル。この場合引数 fname があれば