NiosIIのROM化ではまった備忘録 [IPコア]

いつのバージョンからか詳細には不明だけども、ROM化がうまく動かなくなった。
症状としてはEPCSからの自作ブートコピアからアプリケーションへのブランチがうまくいかないというもの。

NiosII Gen2のあたりからそういう報告があったので、キャッシュか、Gen2になって何かハード的に追加されたのだろうなー、とか考えていたら、原因は全然違うところにあった。

原因は大きく2つ。
(1)BSPを-O3でコンパイルするとrwdataセクションの初期化にmemcpyが使われる。
(2)elfファイルのセクションテーブルのpaddrフィールドが使われるようになっている。

NiosII SBTでBSPを作成すると、main関数が呼ばれるまでにメモリやPOSIXの初期化が行われるが、その中で組み込みC言語では必須になるrwdataセクション(初期値付きデータ領域)の初期化のために、元データからメモリへ値の転送が行われる。
この時点ではC言語が動作する前提がまだ整っていないため、原則としてはアセンブラで行わないといけないのだが、NiosII SBTでは普通のCで書かれているためGCCではメモリ間コピーだと推測して勝手にmemcpyを呼ぶように変更してしまう。
こと組み込み関係では標準関数がどう実装されているかは環境によるため、初期値付きデータ不定の状態で正しく動作するかは保証ができない(どうも今回の場合では期待通りの動きをするようだ)。

二つ目の方は既存のROM化ツールを使っていたので発覚した問題。
どのバージョンから変更になったのかは不明だが、NiosII SBT 16.0のリンカスクリプトではBSPの設定としてtext、rodata、rwdata、bssのセクション名は存在しているものの、elfファイルに結合された時にはbss以外は一つのセクションにまとめてしまう。
このときBSPで初期化ロードのオプションを付けていると、rwdataの初期値データのみをRO属性のセクションとして切り出すが、elfファイルのセクションテーブルのアドレスフィールドの設定がちょっとまずい(というか互換性の問題が出る)値になる。

GCC3まではVADDRフィールドのみが使われ、rwdataの元データとメモリ転送先の2つがそれぞれ別のセクションとしてテーブルに存在していたが、NiosII SBT 16.0ではrwdataの元データのみがセクションになっていて、ソフト実行時に参照するメモリ上のアドレスを示すVADDRフィールドと、ロード時にデータが存在するアドレスを示すPADDRフィールドが使われている。
既存のツールではVADDRフィールドの値でロードアドレスを決定しているため、これをそのまま使うと初期化前にrwdata領域に値を転送し、初期化ルーチンではからっぽの初期値データで上書きしてしまうことになる。


対策としては、アプリケーションロードに関してはrwdataの初期化を初期化ルーチンで行わない(BSPで初期化ロードのオプションを外す)ことで対処する。
こうするとrwdataの初期化は、ロードされたプログラム側ではなくロードするブートコピア側で行われることになるため、(1)(2)の問題は発生しない。

この場合、ロードされたプログラムの再エントリ時に初期値が戻らないというデメリットがあるが、そもそもEPCSからのブートコピアであればリセット後には必ずセクションのロードが行われるので運用上では問題ない。


へたにROM化するよりも、大容量メモリをつけてファイルシステム付きのブートローダーでelfを実行するIPL環境を標準にした方がいろいろ楽になると痛感した一幕でした。睡眠時間と終電をかえせ。