[C#]Pic16f84シミュレータを作成する

これまで1ヶ月ほど掛けて、パタヘネ本でCPUの動作原理を勉強してみたり、PICによるアセンブラプログラムの作成を行ってきました。そこで、今回は勉強した成果として、Microchipから発売されているPic16f84のシミュレーターを作成してみます。


いきなり全部作ると大変になるので、今回は大枠を作るために、ADDLW(Wレジスタの加算)とGOTO命令しか解析できない最低限のCPUシミュレータを、C#で構築します。

このシミュレータは、C#上ではクラス(Pic16f84Simulatorクラス)として実装していきます。

まずはプロパティの定義を行います。

    class Pic16f84Simulator {
        //-------------------------------------------
        /// プロパティの定義
        //-------------------------------------------
        /// <summary> プログラムカウンタ</summary>
        public int Pc { get; private set; }
 
        /// <summary> ワーキングレジスタ</summary>
        public int Wreg{ get; private set; }
 
        /// <summary> プログラムメモリ</summary>
        public int[] ProgMem { get; private set; }
 
        /// <summary> ファイルレジスタ</summary>
        public int[] FileReg { get; private set; }
 
        /// <summary> CPUサイクル</summary>
        public long CpuCycle { get; private set; }
 
        public Pic16f84Simulator() {
            ProgMem = new int[1024];
            FileReg = new int[256];
        }



PICの構成要素である、pc,wreg,プログラムメモリ,ファイルレジスタの定義を行います。
PICのはハーバードアーキテクチャなので、プログラムの保存領域とデータの保存領域は、同一メモリ空間ではなく、別個に存在する事になります。各メモリ領域を意味する配列は,コンストラクタで領域確保しておきます。

また、将来タイマー処理を実装する時のために、CPUサイクルを管理する変数を用意しておきます。



次に、プログラムメモリの読み込みです。

        //*********************************************************************
        /// <summary> プログラムメモリの読み込みを行う
        /// </summary>
        //*********************************************************************
        public void loadProgram( int[] inProgMem ) {
            Array.Copy( inProgMem, this.ProgMem, inProgMem.Length );
        }


これは、単にパラメータで渡された情報をプログラムメモリにコピーしているだけです。
特記すべき事項は有りません。




シミュレータのメインである実行処理です。
今回は、メソッドが一回呼ばれるたびに命令を1つ実行する処理を実装します。
これはMPLABのシミュレータにおけるステップインの機能に相当します。

        //*********************************************************************
        /// <summary> ステップ実行を行う
        /// </summary>
        //*********************************************************************
        public void step() {
            OpeInfo opeInfo;
 
            //-------------------------------
            // 命令のフェッチ
            //-------------------------------
            int opeCode = ProgMem[ Pc ];
            Pc++;
 
            //-------------------------------
            // フェッチされた命令のデコード
            //-------------------------------
            opeInfo = decode( opeCode );
 
            //-------------------------------
            // 命令の実行
            //-------------------------------
            execute( opeInfo );
 
            CpuCycle++;
 
 
            // デバッグ出力
            Console.WriteLine( "命令内容 :" + Pic16f84.toNimonic( opeCode, false ) );
            Console.WriteLine( "   cycle={0}: Pc={1}, Wreg={2}" + Environment.NewLine, CpuCycle, Pc, Wreg );
        }



CPUシミュレータと聞くと大変そうですが、肝となる処理はたったこれだけです。
プログラムカウンタで指定されている命令を読み込み、デコード/実行を行います。

最後のWriteLine()は、動作確認用なので深い意味は有りません。




step()から呼ばれているdecode()メソッドでは、命令のデコード処理を担当します。

        //*********************************************************************
        /// <summary>指定された機械語命令を解析する
        /// </summary>
        /// <param name="mCode">機械語命令</param>
        /// <returns>           解析結果</returns>
        //*********************************************************************
        private OpeInfo decode( int mCode ) {
            OpeInfo opeInfo = new OpeInfo();
 
 
            // GOTO  :  0010 1kkk kkkk kkkk
            if ( ( mCode & 0xF800 ) == 0x2800 ) {
                opeInfo.OpeCode = OPCODE.GOTO;
                opeInfo.Literal = getLiteral11( mCode );
                return opeInfo;
            }
 
            // ADDLW :  0011 111x kkkk kkkk
            if ( ( mCode & 0xFE00 ) == 0x3E00 ) {
                opeInfo.OpeCode = OPCODE.ADDLW;
                opeInfo.Literal = getLiteral8( mCode );
                return opeInfo;
            }
 
            opeInfo.OpeCode = OPCODE.UNKNOWN;
            return opeInfo;
        }


今回は、”最小限のシミュレーターを作る”が目的なので、2命令だけ対応しています。

本メソッドで使用されているOpeInco, OPCODEは、以下の定義になっています。

        //***************************************************************************
        /// <summary>デコード後の命令情報
        /// </summary>
        //***************************************************************************
        class OpeInfo {
            public OPCODE   OpeCode{ get; set; }
            public int      DestReg{ get; set; }
            public int      FileReg{ get; set; }
            public int      BitAddr{ get; set; }
            public int      Literal{ get; set; }
 
            public OpeInfo() {
                OpeCode = OPCODE.UNKNOWN;
            }
        }
 
        //***************************************************************************
        /// <summary>命令コード一覧
        /// </summary>
        //***************************************************************************
        enum OPCODE {
            GOTO,
            ADDLW,
            UNKNOWN,
        };






execute()メソッドは、命令の実行です。

        //***************************************************************************
        /// <summary>   機械語命令の実行を行う
        /// </summary>
        /// <param name="opeInfo"></param>
        //***************************************************************************
        private void execute( OpeInfo opeInfo ) {
            switch ( opeInfo.OpeCode ) {
                case OPCODE.ADDLW:
                    Wreg += opeInfo.Literal;
                    break;
                case OPCODE.GOTO:
                    Pc    = opeInfo.Literal;
                    break;
 
                default:
                    break;
            }
        }


それぞれ命令の内容に従い、Wレジスタの加算やPCの変更処理を行っています。



プログラムのdecode時に使用するメソッド郡です。
命令フォーマットに従い必要な情報を取得する、ユーティリティメソッドになります。

        //***************************************************************************
        /// <summary> BCF,BSF, BTFSC命令などで指定されるbit位置情報を返す
        ///           ビット位置情報はOPCODE<9-7>で指定される。
        /// </summary>
        /// <returns></returns>
        //***************************************************************************
        static private int getOperandBit( int mCode ) {
            int bitOffset = ( mCode & 0x0380 ) >> 7;
            return bitOffset;
        }
 
        //***************************************************************************
        /// <summary> 命令実行結果の格納先(W or F)を返す
        /// </summary>
        /// <returns></returns>
        //***************************************************************************
        static private int getDestination( int mCode ) {
            int dest = ( mCode & 0x0080 ) >> 7;
            return dest;
        }
 
        //***************************************************************************
        /// <summary> ADDWF, MOVF命令などで指定される処理対象レジスタの情報を返す
        ///           レジスタの場所情報はOPCODE<6-0>で指定される。
        /// </summary>
        /// <returns></returns>
        //***************************************************************************
        static private int getOperandRegister( int mCode ) {
            int regAddress = ( mCode & 0x007F );
            return regAddress;
        }
 
 
        //***************************************************************************
        /// <summary> 11桁のリテラル値を返す
        ///           リテラル値の場所はOPCODE<10-0>
        /// </summary>
        /// <param name="mCode"></param>
        /// <returns></returns>
        //***************************************************************************
        static private int getLiteral11( int mCode ) {
            int literal = ( mCode & 0x07FF );
            return literal;
        }
 
        //***************************************************************************
        /// <summary> 8桁のリテラル値を返す
        ///           リテラル値の場所はOPCODE<7-0>
        /// </summary>
        /// <param name="mCode"></param>
        /// <returns></returns>
        //***************************************************************************
        static private int getLiteral8( int mCode ) {
            int literal = ( mCode & 0x00FF );
            return literal;
        }



以上で最低限のシミュレータの作成は完了です。



動作確認するために、以下のPICプログラムを作成しました。

    LIST    P=16F84
    INCLUDE P16F84A.INC
 
    ORG 0
    ADDLW       D'08'
MAIN
    ADDLW       D'01'
    GOTO        MAIN
 
    END



上記のプログラムをアセンブルし、hexファイルにすると以下の内容になります。

:020000040000FA
:06000000083E013E01284C
:04000600003400348E
:00000001FF




これを、今回作成したシミュレータで10ステップほど実行してみました。
とりあえず、問題なく動作できているようです。

 
命令内容 :ADDLW 0x8
   cycle=1: Pc=1, Wreg=8
 
命令内容 :ADDLW 0x1
   cycle=2: Pc=2, Wreg=9
 
命令内容 :GOTO 0x1
   cycle=3: Pc=1, Wreg=9
 
命令内容 :ADDLW 0x1
   cycle=4: Pc=2, Wreg=10
 
命令内容 :GOTO 0x1
   cycle=5: Pc=1, Wreg=10
 
命令内容 :ADDLW 0x1
   cycle=6: Pc=2, Wreg=11
 
命令内容 :GOTO 0x1
   cycle=7: Pc=1, Wreg=11
 
命令内容 :ADDLW 0x1
   cycle=8: Pc=2, Wreg=12
 
命令内容 :GOTO 0x1
   cycle=9: Pc=1, Wreg=12
 
命令内容 :ADDLW 0x1
   cycle=10: Pc=2, Wreg=13


関連記事

コメントを残す

メールアドレスが公開されることはありません。