これまで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 |
関連記事
コメントを残す