ARM9のJTAGデバッガを開発中しています。
ARM9は、命令バスやデータバスをJTAGで操作できるので、ホストPCから任意の命令を送り込んで実行させることができます。任意の命令を実行させられれば、メモリやレジスタ、IOの操作をパソコンから指示して自由に行うことができるわけです。
さて、ARM9には下の図のように、スキャンチェイン1という、命令とデータを送り込むための67ビットのレジスタがJTAGから操作できるようになっています。
CPUを停止させた後で、このレジスタに任意の命令を送り込めば、CPUを自由に操作することができるというわけです。
ARM7にも同じような33ビットのレジスタがありましたが、ARM9はデータバスと命令バスが分離されているため、このレジスタは67ビットに拡張されました。また、ARM7にあった「命令のビットがリバースされている」という妙な特徴はそのまま残っています。命令のビットは反転していますが、データは反転していないので注意が必要です。
また、今回、下から数えて33ビット目がDDENというレジスタになりました。この値を読み出したとき1になっていれば、データバスに表れたデータが有効であるというものでしょう。
さて、ARM7の場合は「フェッチ→デコード→実行」の3段パイプラインで実行されており、実行がメモリアクセスを伴う場合には4サイクルで実行されていました。従って、JTAGでメモリアクセス命令を実行させる場合には、
① shift dr 0xE5800000 // LDR R0,[R0]
② shift dr 0xE1A00000 // NOP
③ shift dr 0xE1A00000 // NOP ← ここで命令が実行される
④ shift dr 0x01234567 ← これは命令ではなく、LDR命令で取り込まれる値
といった具合に、命令とデータを同じ要領で送り込む必要がありました。(詳細はInterface誌を参照)
さて、ARM9ではどうなるでしょう。
実際に、次の9ステップのコードをARM9にJTAGで送り込み、実験してみました。
① MOV R0,#5500
② STR R0,[R0]
③ LDR R0,[R0]
④ STR R0,[R0]
⑤ STR PC,[R0]
⑥ NOP
⑦ NOP
⑧ NOP
⑨ NOP
結果は、下の図のようになりました。
図の左側は、JTAGで入力した値、右側はJTAGから出力された値を示しています。
ステップ2で実行されたSTR命令のメモリアクセスの結果、3ステップ後のステップ5の時にデータ系データバスにR0の値が出力されています。
しかし、ステップ4で実行されたSTR命令の結果は、4ステップ後のステップ8で出力されています。
このように、LDRを間に挟むと、データが出力されるタイミングがかわってきます。
また、LDR命令で取り込まれる値はどのステップで入力すればよいのかも悩みました。
この動作は、なかなか妙なふるまいだったので最初は理解に苦しみましたが、次の図のようにパイプラインの動作をイメージすることで、理解することができました。
ARM9の命令は、「フェッチ→デコード→実行→メモリアクセス→レジスタライト」の5段パイプラインで実行されます。
上のテストコードは、LDR命令にメモリからレジスタR0に値を読み出した後、すぐ次の命令でR0を使っています。LDR命令でR0が更新されるのはステップ7のタイミングなのに、STR命令でR0が使われるタイミングもステップ7になるため、パイプラインにインターロックが発生して、後のSTR命令の実行が待たされたと考えられます。
さらに次のSTR PC,[R0]命令でも、R0が使われます。これを普通に実行するとメモリ書き込みのタイミングがSTR命令と重なるので、結局どこかのステージで1つ待つことになります。
なお、このコードを繰り返し実行すると、PCが32番地づつ増えていくのが確認されました。インターロックが発生してパイプラインが待たされているステージでは、アドレスカウンタがインクリメントしないような気がします。
ARM7もARM9も、マニュアルやアプリケーションノートにはこの辺の考え方はおろか、JTAGデバッグの詳しい仕組みさえ全く記載されていないので、JTAGデバッグのやり方を見つけるだけで苦労します。マニュアルにかかれているのは各種レジスタの説明くらいで、どういう順序で何を与えればよいかは、すべてJTAGデバッガの設計者が考えなければなりません。
この仕組みがわかってきたので、まもなくARM9用のJTAG ICEをバリバリ作り始めます。
最近のコメント