お世話になります。
RAMパリティ・エラーの発生について教えていただけるでしょうか。
コンパイラはCC-RLでCS+のコードジェネレータで生成したコードを基本としています。
自前のセクションなどは使用しない場合で,RAMパリティ・エラーが発生することはあり得るでしょうか。
また,cstart.asm 内の _start 直後のコードで,_stkinitがコメントアウトされていますが,スタック領域は初期化しなくても
大丈夫なのでしょうか。
;-------------------------------------------------- ; initializing stack area ;--------------------------------------------------;$IF (__RENESAS_VERSION__ >= 0x01010000); MOVW AX,#LOWW(__STACK_ADDR_END);$ELSE ; for CC-RL V1.00; MOVW AX,#LOWW(_stackend);$ENDIF; CALL !!_stkinit <--- ここです。 ;-------------------------------------------------- ; hardware initialization ;-------------------------------------------------- CALL !!_hdwinit
度々の質問で恐縮ですが,ご教授頂けますようお願いいたします。
スタック領域は通常初期化しなくても問題ありませんが、スタックに割付られた未初期化のローカル変数を参照するとパリティエラーが発生する可能性はありますね。
fujita nozomu様
ご回答ありがとうございます。
パリティエラーが発生する可能性があるんですね。本来なら未初期化のローカル変数を参照する事態は
ないはずなので,stkinit 関数はコメントされていても問題ないと言うことなんでしょうか。
実は現在ROM化状態で,この状況に直面しているんですが,未初期化のローカル変数を参照してしまって
いるところがあるという前提でコードを見直してみます。
いつもいつも,的確なご回答ありがとうございます。本当に助かります。
あと以前に CC-RL ではなくて GCC でのことですが、構造体の各メンバは初期化されていたものの構造体中のギャップ部分が未初期化だったために、構造体全体を memcpy() するとパリティエラーが生じるという報告がありました。
GR-KURUMIでメモリの読み込みで再起動
http://japan.renesasrulz.com/gr_user_forum_japanese/f/110/p/2331/9991.aspx
コンパイラが異なるので同じ現象が生ずるがわかりませんがご確認されることをお勧めします。
貴重な情報をありがとうございます。
構造体を多用していますので今一度確認してみたいと思います。ギャップ部分が生じる場合,
意図的にダミーの変数を入れて埋めてしまうのが良いのかもしれないですね。
CC-RLでは,構造体の複数バイト変数が偶数アドレスに配置されないとの情報を見た気もするのですが
そのあたりも含めつつ調査してみます。ありがとうございました。
RAMパリティ・エラーの発生について
CPUの動作ですから、コンパイラの問題ではないと思います。
RL78/G13
ユーザーズマニュアル ハードウェア編
R01UH0146JJ0320 Rev.3.20 2014.07.14 857ページ RAMパリティ・エラー検出機能 の
注意 データ書き込み時にパリティが書き込まれ,データ読み出し時にパリティをチェックします。 そのため,RAMパリティ・エラー・リセット発生を許可する(RPERDIS = 0)場合,データ・ア クセス時は「使用するRAM領域」をデータ読み出し前に必ず初期化してください。 また,RL78はパイプライン動作のためCPUが先読みを行い,使用しているRAM領域の先にある初 期化されていないRAM領域を読み込むことで,RAMパリティ・エラーが発生する場合があります。 したがって,RAMパリティ・エラー・リセット発生を許可する(RPERDIS = 0)場合,RAM領域 からの命令実行時は「使用するRAM領域+10バイト」の領域を必ず初期化してください
スタックエリアはcallされてまず戻りアドレス等が書き込まれてから、retで読みだされる順番だから良しとされているようです。callされずにretが行われるならば、errorが運悪く発生するでしょう。(実行アドレスメモリ外)
C言語で未初期化変数は、0が読める仕様で使用するならハードイニシャライズでまずRAMをすべて0クリアしておいてパリティをすべてつけておいてから、ROM化のソフトイニシャライズで再度初期化してもよいのではないでしょうか?
どちらにしてもCPUの安全機能を使用するならば、マニュアルに記述されていることは、確実に行っておくべきだと思います。
kcd様
ご指摘ありがとうございます。その通りだと思います。現在,CS+のコードジェネレータで作成したプログラムで
RAM初期化もやっているのですが,I2C通信の関連でRAMパリティ・エラーが発生してしまい,スタック関連を気に
している次第です。意図的なケースは別として,ローカル変数が書かれる前に参照されるならコンパイラが警告して
くれますし,なんだろうなと調査中です。もう少しがんばってみます。ありがとうございました。
>ハードイニシャライズでまずRAMをすべて0クリアしておいてパリティをすべてつけておいてから
デフォルトのCS+のコードジェネレータで作成したプログラムだとたぶんsp=fe20hで CALL !!_hdwinitを呼び出しているはずですから、hdwinitプログラムで、使用しているCPUのRAMの先頭からspが示している番地までのRAMクリアを行っていますか?(far callとauto変数分スタックが消費され呼ばれた中では、spはfe18ぐらいになっていると思います)これにあとRB0を除くsadderエリアfe20h~fef7hのクリアこれで全RAMクリアとなります。
RL78はK0と違い命令での(アセンブラレベルで)ポインタアクセス機能が強化されているため、関数が使用するauto変数(C言語のワークエリア)を、saddrではなくスタックで賄っているようです。
情報ありがとうございます。見てみたところ,sp=fe20hでhdwinitを呼び出し,その後,bssとsbssセクションを0でクリアや
初期値付変数の初期化が続きmainをコールしていました。使用しているRAMはすべてクリアないし初期化されているようですが,
スタック領域はクリアされていないようです。
今RAM・パリティエラーが発生しているのは,コードジェネレータが作成したIIC関連の関数なんです。
ROM化した場合にのみ発生しています。以下の関数です。オシロでチェックするためにP16を変化させるコード①②を
追加しています。正常時はP16がH→Lとなりますが,パリティエラーが発生する時は,Hになってすぐにリセットがかかり
ポートが入力状態になります。
引数の変数 wait を念のため逆アセンブルで見てみましたが,一旦スタックに書き込んでからデクリメントしていました。
エラーが発生する時はかならずこの関数なんですが何が原因なのか,奮闘中です。
------------------------------------------------------------------------------------------------------------------------------------
MD_STATUS R_IICA0_Master_Receive(uint8_t adr, uint8_t * const rx_buf, uint16_t rx_num, uint8_t wait)
{
MD_STATUS status = MD_OK;
P1_bit.no6 = 1; // ・・・①
IICAMK0 = 1U; /* disable INTIIA0 interrupt */
STT0 = 1U; /* set IICA0 start condition */
IICAMK0 = 0U; /* enable INTIIA0 interrupt */
/* Wait */
while (wait--)
;
}
if (0U == STD0)
status = MD_ERROR3;
else
/* Set parameter */
g_iica0_rx_len = rx_num;
g_iica0_rx_cnt = 0U;
gp_iica0_rx_address = rx_buf;
g_iica0_master_status_flag = _00_IICA_MASTER_FLAG_CLEAR;
adr |= 0x01U; /* set receive mode */
IICA0 = adr; /* receive address */
P1_bit.no6 = 0; // ・・・②
return (status);
ackさん、横レス失礼します。
全く論理的ではありませんが、②の位置を一行ずつ上に(逆に①の直下から一行ずつ下に)移動し、オシロでH→Lになる/ならない、の変曲点をサーチすれば、トリガコードに辿り着くと思います。ROM化が面倒ですが...
チョコです。
このプログラムで読み出しているのは、スタックで渡された引数だけのようです。
もしかすると、呼び出し元では8ビットで設定しているだけなのに、参照時に16ビットで
読み出している可能性(実際には8ビットしか使っていない)があります。
そうなると、最初のPOSTに有ったスタック領域の初期化を有効にすれば解決する可能性が
あります。
ビシ様,チョコ様
アドバイスありがとうございます。
ビシ様の仰る通り②を上にずらしていったところ,while (wait--)の直前の場合は,きれいにH→Lとなっており,
その後リセットしていましたので,wait変数が鍵となりそうです。
逆アセンブルで,この関数の引数を見てみましたが,8bitデータは8bitでアクセスしているように見えましたが,
チョコ様からご指摘頂いた通りスタック領域の初期化を有効にしたところ症状がでなくなりました。
場所は特定できないのですが,チョコ様の言われた「参照時に16ビットで読み出している可能性(実際には8ビット
しか使っていない)」が,論理的な気がいたします。
スタックを初期化するのも気持ちがいいので,これで行きたいと思います。
皆様の的確なアドバイス,本当にありがとうございました。
> このプログラムで読み出しているのは、スタックで渡された引数だけのようです。
CC-RL で R_IICA0_Master_Receive() を呼び出すと各引数は
adr: A
rx_buf: BC
rx_num: DE
wait: X
すべてレジスタで渡されるので違うと思います。
> スタックを初期化するのも気持ちがいいので,これで行きたいと思います。
現象が出なくなっても原因は確認されていないので、別の不具合が生ずる可能性は考えられます。原因の突き止めはやっておいて損はありません。
いつもアドバイスありがとうございます。確かに原因を明らかにしておくのは重要ですね。
R_IICA0_Master_Receive()でエラーが発生しているように見えても,実は何かの割り込みの中ということも
あるかもしれませんね。もう少しがんばってみます。
> ビシ様の仰る通り②を上にずらしていったところ,while (wait--)の直前の場合は,きれいにH→Lとなっており,
> その後リセットしていましたので,wait変数が鍵となりそうです。
INTIIA0 割り込み待ち中にリセットしたということは INTIIA0 割り込み処理の中でパリティエラーが発生している可能性が高いと思います。
CAとCCでかなりコードが違っていましたね。スタックに書き込んでデクリメントと
あったので、CAと勘違いしてしまいました。
ところで、R_IICA0_Master_Receive関数は単に、受信を起動するだけです。受信
完了はどのようにしてチェックしているのでしょうか。
また、パリティ・エラーでのリセットということはRESFレジスタで確認しているの
ですよね。
アドバイスしてくださった皆様,本当にありがとうございます。ようやく原因が特定できましたのでご報告いたします。
やや長文になりますがご容赦ください。
なお,パリティ・エラーの発生は間違いなく,チョコ様の仰る通りリセット時にRESFレジスタを確認して判断しており
ました。(デバッグ用のポートを出力設定してオシロで確認)
先に結論をお伝えすると,関数ポインタを使用してコールした際の memcpy が,未初期化のスタックを参照していました。
以下,詳細をお伝えします。
1.当初,R_IICA0_Master_Receive関数内で発生しているように思えましたが,ジェネレータが作成したコードを世界中の
方々が使用していて問題が報告されていないなら原因は別にあると考え,この関数をコールする直前に永久ループを作成した
ところ,やはりパリティ・エラーが発生。
2.いくつかの割り込みを使用していたので,一つ一つ禁止にしながら実験してタイマー割り込みを特定。
3.タイマー割り込みでコールされる関数の中で,引数に関数ポインタを渡しているものを特定。(自前の関数)
------------------------------------------------------------------------------------------------
typedef void (*Action)(void);
typedef struct
//
} TestStruct;
TestStruct Ts1;
void FuncA(TestStruct* this, Action func1, Action func2, Action func3);
static void Func1(void)
static void Func2(void)
static void __near r_tau0_channel0_interrupt(void)
FuncA(&Ts1, Func1, Func2, 0);
4.タイマー割り込み内で上のFuncAをコールする箇所を逆アセンブルで確認したところ,memcpy内で未初期化の
スタックを参照していることが判明
SUBW SP,#1CH
MOVW AX,#3C19H ---> Func1のアドレス
MOVW [SP+2H],AX ---> SP+2Hが19H,SP+3Hが3CH
MOV [SP+4H],#0H ---> SP+4Hが0H,SP+5Hは未初期化
MOVW AX,#3C1DH ---> Func2のアドレス
MOVW [SP+0CH],AX ---> SP+0CHが19H,SP+0DHが3CH ①
MOV [SP+0EH],#0H ---> SP+0EHが0H,SP+0FHは未初期化
CLRW AX ---> 3つめの引数はnull
MOVW [SP+18H],AX ---> SP+18Hが00H,SP+19Hが00H ②
MOV [SP+1AH],#0H ---> SP+1AHが0H,SP+1BHは未初期化
MOV A,[SP+4H]
MOV [SP+0H],A
MOVW AX,[SP+2H]
MOVW [SP+6H],AX
SUBW SP,#4H
MOVW AX,SP
ADDW AX,#1CH ---> AXは上の②のSP+18Hを指している
MOVW BC,AX
MOVW DE,#4H ---> 未初期化のSP+1BHも含めて4バイト分をmemcpy ③
CALL !!_memcpy
ADDW AX,#14H ---> AXは上の①のSP+0CHを指している
MOVW DE,#4H ---> 未初期化のSP+0FHも含めて4バイト分をmemcpy ④
MOV A,[SP+8H]
MOV C,A
MOVW AX,[SP+0EH]
MOVW DE,AX
MOVW AX,#0D008H
CALL !!_FuncA
③と④の memcpy の中では4回のループで1バイトずつコピーが実行されており,あきらかに未初期化のスタックを参照
していました。
わたしの関数ポインタの使用方法に注意が足りない部分もありそうですが,これで,スタックを初期化すればパリティ・エラー
が発生しない理由をはっきりさせることができたと思います。fujita nozomu様が教えてくださった memcpy の情報に少し
通ずるところもありそうです。
皆様のアドバイスのおかげで原因を究明でき本当にありがとうございました。
あとはこの現象を回避する方法ですが,スタックの初期化で行こうと思いますが,もし他にも良い方法がありましたらご教授
いただければ幸いです。
標準の cstart.asm ではスタック領域を初期化しないにもかかわらず未初期化の領域を memcpy() してしまうというのは CC-RL の現在のところの不具合なのではないかとおもいますが、それにしても随分効率の悪いコードを吐いてますね。
解決方法としては Action の定義を
typedef void (__near*Action)(void);
に変更し、かつ Action に渡される関数の配置を near に変更するか、あるいはコードの全体が 64kB に収まっているならば CC-RL(ビルド・ツール)のプロパティ → 「コンパイル・オプション」のタブの「メモリ・モデル」の項目「メモリ・モデル」の設定を「スモール・モデル(-memory_model=small)」に変更するのが簡単だと思います。
上記の変更で、例えば r_tau0_channel0_interrupt() のコードは下記のように効率化され、本来不要な筈の memcpy() も呼ばれなくなるのでこの箇所でのパリティエラーは発生しなくなると思われます。
void __near r_tau0_channel0_interrupt(void) _r_tau0_channel0_interrupt: f6 CLRW AX FuncA(&Ts1, Func1, Func2, 0); c1 PUSH AX ; 0 340d03 MOVW DE,#30DH ; Func2() のアドレス 320c03 MOVW BC,#30CH ; Func1() のアドレス 300caf MOVW AX,#0AF0CH ; Ts1 のアドレス fd2101 CALL !_FuncA c6 POP HL d7 RET
> ROM化した場合にのみ発生しています。
デバッガではデバッガが使用する分スタック領域を余計に使用するので、アプリケーションが使用する以前にデバッガがスタック領域を「初期化してくれてた」のかもしれないですね。