ちょっと長くなりますが、悩んでいますので、ご存じの方がおられたらよろしくお願いします。内容ですが、以下のようなことです。
ある割り込み処理ルーチンAから、別の割り込み処理ルーチンBを呼ぶようなコードが書かれていました。おそらくこのようなコードでは、PSWが破壊されると思いますが、割り込みがかかったメイン処理ルーチンmainになぜに影響があるのかが理解できません。でも、実際に、AからBを呼ばないようにすると、安定して動作するのです。以下はその現象のサンプルです。
#pragma interrupt A_interrupt(vect=XX1)#pragma interrupt B_interrupt(vect=XX2)
static void A_interrupt(void){ B_interrupt();}
static void B_Interrupt(void){ // 処理 // ..}
static unsigned char ip=0;static unsigned char op=0;
void main(){ while(1){ if( ip != op ) { sub_func(); } }}
条件は以下となります。Aの割り込みは頻繁にかかります。Bの割り込みは決してかかりません。
上記処理では、ip,opが変化がないとすると、main()では決してsub_func()は呼ばれないはずですが、Aの割り込みがかかるとなぜかsub_func()が呼ばれるようになります。そして、Aの割り込み処理からBの割り込み処理を呼ばないようにするとsub_func()は呼ばれないようになります。
動作の推察:1.Aの割り込みでは、スタックに PSW(1バイト)、PC(3バイト)が保存されます。2.AからBを呼ぶと、スタックに、0値(1バイト)、PC(3バイト)が保存されます。3.B処理から戻るときRETIが実行され、PSWには0が取り込まれて、コールした時とは異なる値でPSWが破壊されます。4.Aの処理終わりで、再びRETIが実行され、スタック上に保存されたPSWが復帰されます。
以上の状態となると、main()でおかしな動作にはならないと思われるのです。ちゃんと、Aの割り込みの終わりで、PSWが戻っているので。でも、実際にはPSWが破壊されて、おかしな動作になっていると思われるのです。
どうして、上記のような処理でおかしくなるのか、どなたかご存じでしょうか。
実を言えば、Aから割り込み処理Bを呼んだ事が不具合の原因だと思われ、呼ばないようにすると改善することはテストで確認しました。
しかし、CPUの動作仕様からすると、不具合も起こらないはずにしか思えません。これでは、本当に不具合が修正できているのかわからないのです。
割り込みハンドラ(割り込みベクタ)からの復帰と通常の関数(サブルーチン)からの復帰はCPU内部で動作が違うのでコンパイルされた関数からの戻りとして使われる機械語が違います。RL78ならRETIとRETがあり、割り込みハンドラではRETIが戻りとして使われるので割り込みハンドラを直接関数のように呼び出すと異常な状態になります。もし、ある割り込みハンドラの内部処理を他の割り込みや通常の処理から呼び出したいならその処理を関数として作り直して呼び出すようにすれば解決します…
チョコです。
>それに、この機能は未初期化のメモリアクセスをチェックするものではなくて、RAMのハード的なビット化けのチェックらしいので、今回の件には当てはまらないでしょう。
RL78/F13のハードウェア マニュアルをECCで検索してみたのですが、DTCの所に以下のような記述がありました。ECCは通常は、データ領域に対して使うものですが、どこにもアドレスの記述はありません。DTCでの読み出しで割り込みが発生するなら…
ちょっと気になったので、実際にどの様に動くかやってみました。
簡単な割り込み関数を定義して動作を見る限り、mus.さんの仰っている通り、メイン関数に戻るところでスタックに書かれている値をPSWに戻すので、メイン関数の動作がおかしくなるという現象は見られませんでした。
そこで、どのようなケースでおかしくなるか。意図的に動作がおかしくなるなケースを作ってみました。
#pragma interrupt…
すいません。チップは、RL78/F3です。
割り込みハンドラ(割り込みベクタ)からの復帰と通常の関数(サブルーチン)からの復帰はCPU内部で動作が違うのでコンパイルされた関数からの戻りとして使われる機械語が違います。RL78ならRETIとRETがあり、割り込みハンドラではRETIが戻りとして使われるので割り込みハンドラを直接関数のように呼び出すと異常な状態になります。もし、ある割り込みハンドラの内部処理を他の割り込みや通常の処理から呼び出したいならその処理を関数として作り直して呼び出すようにすれば解決します。www.renesas.com/.../78k0-series-instructions
Yamamoto様ご回答ありがとうございます。
リンクを頂いている78K/0シリーズと、今回使用しているRL78/F13とは仕様が異なるようです。リンクされている78K/0シリーズでは、スタックをCALLで2バイト、割り込みでは3バイト消費し、RETでは2バイト戻し、RETIでは3バイト戻しますので、SPがずれてしまい明らかにおかしな動作になるでしょう。しかし、RL78/F13では、PCは20ビットあり、CALLでも、割り込みでもSPは4バイト消費します。つまりSPに関してはずれが無い仕様なのです。
したがって、今回の問い合わせの場合、そのようなコードを書いても不具合にならないように見えるのが問題なのです。つまり、
1.割り込み処理ルーチンから割り込み処理ルーチンをコールしたことで不具合が発生するのか?2.発生するとしたらどのような仕組みから起こっているのか。3.Bを割り込み処理ルーチンから通常のサブルーチンにしたら改善することが証明できるか?
ということが知りたいのです。
わわいです
それが知りたいのなら、デバッガで、割り込み関数の入り口でブレークポイントを設定し、逆アセンブリレベルでワンステップづつ実行させてみましょう。
そうやってレジスタの内容、コードの挙動などを追いかけていけば何が起こるのか、がわかると思いますよ
78K/0しかオペコード一覧がわかるのが見つからなくて、わかりづらくてすみません。割り込み時はPSWの待避動作があります。RETIとRETBは呼ばれるとPSWをスタックから値を取り出してPSWにセットします。RETはそういう動作をしません。
これは、懐かしい「RAM パリティ・エラー検出」によるリセットがかかっているものと考えられます。
CALL命令では、PSWをスタックには書かれていないので、RAMの値は不定、それをRETIで読み出したものだから、たまたまパリティのエラーが発生したてたものでしょう。そのため、リセットがかかり、動作がおかしくなったものです。
この状態はデバッガでも確認は難しいですね。
以上
かわい様
返信ありがとうございます。
使用している開発環境のe2Studioでは、逆アセンブル表示はできることがありますが、
そのアセンブラリスト上でのステップ実行はできないようです。
なにか方法があるんでしょうか?
チョコ様
RL78/F13には、RAMのパリティチェック機能はないようです。ハードウェアマニュアルには記載がありませんでした。
ただし、ECC機能はあるようですが、ECCチェックエラーで割り込みを発生できるのは
デバック中でないときのみのようです。現在は開発中なので、常時デバッカーをつないで動作させています。
それに、この機能は未初期化のメモリアクセスをチェックするものではなくて、RAMのハード的なビット化けのチェックらしいので、今回の件には当てはまらないでしょう。
逆アセンブリリスト上でもブレークポイントを設定できますんで、それでいろいろやってみてください
RL78/F13のハードウェア マニュアルをECCで検索してみたのですが、DTCの所に以下のような記述がありました。ECCは通常は、データ領域に対して使うものですが、どこにもアドレスの記述はありません。DTCでの読み出しで割り込みが発生するなら、RETIでの読み出しでも同様ではないかと考えられます。
ECCの所には、以下のような記述もあります。
割り込みの設定をどうしているかは、分かりませんが、スタック領域は初期化しておいた方がいいのではないかいと思います。確かスタートアップにスタック領域の初期化サブルーチンがあり、そこを呼ぶところがコメントアウトされていたような記憶があります。
#pragma interrupt r_tau0_channel0_interrupt(vect=INTTM00, bank=RB1) //割り込み関数A#pragma interrupt r_tau1_channel0_interrupt(vect=INTTM10) //割り込み関数B
割り込み関数Aに相当するのは、r_tau0_channel0_interrupt()です、この関数にレジスタバンクの設定を行っています。割り込み関数Bに相当するのがr_tau1_channel0_interrupt()です。
普通に、割り込み関数Aから割り込み関数Bを呼ぶと、コンパイラがエラーを返しましたので、Aからはdummy_func()(通常関数)を呼ぶ様にしておき、後からdummy_func()のCALL命令のアドレスを割り込み関数Bに置き換えました。
static void __near r_tau0_channel0_interrupt(void){ /* Start user code. Do not edit comment generated here */ g_tau0_count++; P7_bit.no1 = ~P7_bit.no1; dummy_func(); //これの実体をr_tau1_channel0_interrupt()に置き換え /* End user code. Do not edit comment generated here */}
CALLで割り込み関数Bを呼んだ時は、RL78のソフトウェアマニュアルでは、0x00とPC 3バイトがスタックに保存されると書いてある「RL78 ユーザズマニュアル ソフトウェア編 図4-25 CALL, CALLTの例」のですが、デバッガでステップ実行する限りでは、0x00は書かれませんでした(元から入っている値を書き換えない)。もちろん、割り込み関数BのRETIではSP+3のデータをPSWに戻していました。
このとき(PSWの値を不正に戻す動作)、割り込み関数Aがレジスタバンク指定していれば、割り込み関数BのRETIの際に、PSWの値がおかしくなり、RBS0=RBS1=0となる事があり、メイン関数の汎用レジスタを壊しに行く動作となります。
具体的には、66行目、176Hの"POP AX"で、レジスタバンク0の(=メイン関数で使用している)AXが上書きされています。(POP AX実行前は、AX=1でした)
メイン関数では
AXが上書きされたので、比較命令で期待値と逆の結果になり、呼ばれるはずのないsub_func()が呼ばれました。(この例では、AXが0で上書きされたので、初期値a=b=1と、わざわざ動作がおかしくなる様に誘導しています。)
デバッガでステップ実行した場合、呼ばれるはずのないsub_func()が実行されました。また、motファイルを(CALL命令のアドレスを割り込み関数に)書き換えてROMに書き込み、デバッガ接続しない状態で実行してみましたが、この場合もsub_func()が呼ばれました。
割り込み関数をレジスタバンク指定すれば、メイン関数でおかしな動作が起こる事が、容易に再現しました。
…オリジナルのプログラムで、レジスタバンクを使っていないとすれば、別な原因かと思いますが。
(Zフラグではなく、RBSが悪さをしているということも考えられるかと思います。)
tfさん
詳細に解説いただき感謝です。
私も、なんとか試してみたのですが、おっしゃられるとおりCALLでのSP-1にはマニュアルの00Hではなく、不定な値が残ったままでした。これを、B割り込み処理のRETIで読み込んでPWSを書き換えてしまうと、不正な動作になりえますね。特にAXの値が変わると、判断処理ではAX経由で行っていますから。
大変助かりました。ありがとうございました。
CALLで、「(SP-1)に00Hが書かれる」と言うのは、昔から問題になった項目です。結論として、G13、G12、G14、G10等の初期の製品では、以下に示すようにそのような記述は削除されています。
その後のデバイスでは、「(SP-1)に00Hが書かれる」の記述が残っていました。
新しい、RL78/G23、G22、G24も「(SP-1)に00Hが書かれる」とあったので、期待しいて、RL78/G23-064FPBで確認しましたが、00Hが書かれることはありませんでした。なぜか、RL78/G15、G16ではそのような記述はありませんでした。
ということで、ルネサス内部でもバラバラでした、
ところで、以下に示すmus.さんの最初の書き込みでは、(この記述はコンパイラではじかれると思いますが、) B_interrupt()から戻ってきたら(戻れたら)、PSW(やAX)の値がどうなっていてもその後には影響しないはずです。tfさんは、強引に分岐先を書き換えていましたが、mus.さんは、そのようなことをやっていないなら、面白いですが、あまり意味はないのではないかと思います。
実動作としては、「(SP-1)に00Hが書かれる」事がないということですね。
F13で00Hが書かれなかったので、(RL78では一番一般的かと思われる)G13でも試してみましたが同じでした。
割り込みルーチンから、他の割り込みルーチンをCALL命令で呼ぶという状況を、どの様に実現しているのかは、微妙に気になっていました。GCCやLLVMならエラーにならずに、その様なコードを生成する?などとも考えていました。
レジスタバンクの機能を使っていなければ、汎用レジスタが書き換わって動作がおかしくなるという現象は起こらないはずですし、割り込みルーチンAから割り込みルーチンBを呼べている所に、誤動作の鍵が隠されているのかも知れない、とも思いました。
チョコさん、tfさん
私が勝手に、納得してしてしまっていて説明が足りませんでした。
この現象は、Bルーチンの返りで、REIが実行されて、RBSレジスタが書き換えられて、レジスタバンクが切り替わると、Aルーチンの終了処理で(POPで)復帰されるCPUのレジスタ群は、別バンクのレジスタということになります。そして、AルーチンのREIでRBSレジスタが戻って、レジスタバンクが本来のものに切り替わりますが、そのCPUレジスタ群は元に戻っていないので、動作がおかしくなる。
という仕組みだと理解しています。
ここでの肝は、CALLの時にSP-1に00Hが書き込まれず、値が不定だということなのです。