Z80 を再現してみる

フラフラとネットの海をさまよっていたら、とあるページで Z80 という文字を発見。そーいえば昔少しだけマシン語をやったことがあるな〜、懐かしいなぁなんて思い出しニーモニック表を検索で探して読みふけってしまいました。こういうのが速攻で手に入るのは有り難いですねぇ。感謝。

んで、そんなページを見ながら「Z80 のエミュレーションくらいならオレでも簡単に出来るんじゃねーの?」なんてことを思ってしまったので、早速取り掛かることに。

言語は C# を利用、Z80 は一応フルスペックというか割り込み、未定義命令辺りは使えるようにして、簡単な IO(テキストRAMとディスクアクセス、キー入力くらい)も実装する、ということで考え中。互換性は特に必要無いので、IO はかなり適当になる予定。
以下、ソースから抜粋。

namespace Emulator
{
    #region Z80 レジスタ表現用列挙型
    public enum r8
    {
        a,
        f,
        b,
        c,
        d,
        e,
        h,
        l,
        end,
    }

    public enum pr8
    {
        r,
        i,
    }

    public enum r16
    {
        af,
        bc,
        de,
        hl,
        end,
    }

    public enum pr16
    {
        ix = r16.end + 1,
        iy,
        sp,
        pc,
        end,
    }

    public enum ar16
    {
        af = r16.af,
        bc = r16.bc,
        de = r16.de,
        hl = r16.hl,
        ix = pr16.ix,
        iy = pr16.iy,
        sp = pr16.sp,
        pc = pr16.pc,
        end,
    }

    // F レジスタ 各フラグ そのままマスクとして使えるように値指定
    public enum fbit
    {
        C = 1,      // キャリーフラグ
        N = 2,      // 減算フラグ
        P = 4,      // パリティフラグ
     // none1 = 8,  // 未使用
        H = 16,     // ハーフキャリーフラグ
     // none2 = 32, // 未使用
        Z = 64,     // ゼロフラグ
        S = 128,    // サインフラグ
    }
    #endregion

    public class cpu_z80
    {
        // 8Bit レジスタ A F B C D E H L 表現用配列
        private byte[,] R8 = new byte[(int)r8.end, 2];

        // 16Bit レジスタ IX IY SP PC 表現用配列
        private ushort[] PR16 = new ushort[(int)pr16.end];

        // レジスタ状態フラグ
        private byte stateAF = 0; // AF:AF' 状態
        private byte stateHL = 0; // BC DE HL:BC' DE' HL' 状態

        // 64KB メモリ
        byte[] memory = new byte[65536];

        // 8Bit 演算命令
        private void _add8(r8 rcode, byte d)
        {
            byte s = get_register(rcode);
            set_register(rcode, (byte)(s + d));
            f_set(fbit.C, (0xFF - s) < d);
            f_set(fbit.Z, (s + d) == 0);
            f_set(fbit.S, ((s + d)>> 7) == 1);
        }
        private void _adc8(r8 rcode, byte d)
        {
            byte s = get_register(rcode);
            int fbit_c = f_get(fbit.C);
            byte r = (byte)(s + d + fbit_c);
            set_register(rcode, r);
            f_set(fbit.C, (0xFF - s + fbit_c) < d);
            f_set(fbit.Z, r == 0);
            f_set(fbit.S, (r >> 7) == 1);
        }

        // ADx R, n
        public void add8(r8 rcode, ushort addr)
        {
            _add8(rcode, memory[addr]);
        }
        public void adc8(r8 rcode, ushort addr)
        {
            _adc8(rcode, memory[addr]);
        }

        private void _sub8(byte d)
        {
            byte a = get_register(r8.a);

            f_set(fbit.C, a < d);
            f_set(fbit.Z, a == d);
            set_register(r8.a, (byte)(a - d));
        }

        // SUB R
        public void sub8(r8 rcode)
        {
            _sub8(get_register(rcode));
        }
    }
}

こんな感じ。r8.a とかでレジスタにアクセスする感じでいいかなと思います。クラスにしてしまっても良いような気もしますが、一応速度もそれなりに出て欲しいので、遅くなる方法はなるべく避けるという方針でやってみたいと思います。とはいえ、F レジスタの操作は既に泥沼になってるような…。ADC A,n とか、物凄くコストが高くなってます。まぁ遊びなので気にしないことにしますが。

現在 5% くらいは出来たのですが、こういうプログラムを作る時は「ここでポインタが使えればすっきり書けるのになぁ」とか思うこともかなり多いですね*1。使わなくてもプログラムは組めるんですが、効率とかを考えると使いたいケースは結構あるので。

現在実装したのは LD ADD ADC SUB INC DEC EX EXX PUSH POP 辺りの楽そうな所から。JP 系の命令を実装すればそれなりにプログラムが動くかなと思ってます。IO + 簡単なシステムコールの整備まで行ければかなり楽しくなりそうです!

*1:C# でもポインタは使えますが、使わないのが C# 流。