[C言語]undefined reference to '__ashlsi3'(2)
デザインウェーブマガジン(以下DWM)2007年10月号で付属してたLattice Mico32開発環境を
使ったら動かなかった。
詳しく解析してないが、気が向いて、Mico System Builder(以下MSB)で生成した最新Mico32ソースを
使った環境を再度やってみようかと。
以前やって、LEDチカチカが出来なかったので止めてしまったが、最近やったobjdumpによる
解析で電源ONからスタートアップルーチン、そこからmainへ飛び、各関数へ飛ぶ度にスタックポインタ、
フレームポインタの動作などが少しづつ分かるようになってきたため、以前よりは動かない原因に近づけるかなと。
■手順
・ashlsi3をインラインアセンブラでCソースへ追加したもので、DWM2007/10データではなく、
MSB生成の環境でシミュレーション。動かず。
・シミュレーションで命令バスを追うと、スタートアップからmainへ飛ぶまでは正常だが、
そこから割り込みベクタ、割り込みハンドラ―、戻ってまた割り込みベクタへ、の無限ループ。
・シミュレータのログでは、Data bus errorとのメッセージ。
・startup.s(DWM2007/10付属)での定義では0x80のDataBusErrorを無限ループしてる。
・内蔵データRAMのアドレス設定はMSBで0x20000000にしてる。
シミュレータログは最初に「Data bus error. Address: 20001fc0」と出る。
・main関数始まってからすぐに、0x1fc0をなぜアクセスするんだ?と思ったが、
これはスタックポインタだね。memory.def(DWM2007/10付属)で定義してた。
. = 0x20000000 + (1024*8) - 4;
_sp_base = .;
・関数に入る度にスタックへPUSHして、その時にData bus errorが出てるようだ。
なぜスタックへアクセス出来ないのか?
MSBでPlatform設定見直したら、データRAMの容量が0x200007ffまでになってる。これだ。
(2015.3.3追記)
・メモリ容量修正しても、まだデータバスがアドレス範囲外へアクセスしてData bus errorが起こる。
よくよく考えると、命令/データ用ROM/RAMって、EBR定義ではなくてLM32プロパティから
Inline Memoryタブで設定すべきでは?
・Inline Memory用にアルテラRAMを接続中。でもこんなエラーが。
C:\user\onedrive\vm_share\de0_nano\mico32\micosystem\de0_nano\components\lm32_top\rtl\verilog\lm32_load_store_unit.v(200)::Error: irom_store_data_m複数のレンジの記述方法を統一してください
moduleを評価中です。
C:\user\onedrive\vm_share\de0_nano\mico32\micosystem\de0_nano\components\lm32_top\rtl\verilog\lm32_load_store_unit.v(482)::Error: irom_store_data_m [ ]内が宣言でのレンジを超えています。
C:\user\onedrive\vm_share\de0_nano\mico32\micosystem\de0_nano\components\lm32_top\rtl\verilog\lm32_load_store_unit.v(483)::Error: irom_store_data_m [ ]内が宣言でのレンジを超えています。
C:\user\onedrive\vm_share\de0_nano\mico32\micosystem\de0_nano\components\lm32_top\rtl\verilog\lm32_load_store_unit.v(484)::Error: irom_store_data_m [ ]内が宣言でのレンジを超えています。
C:\user\onedrive\vm_share\de0_nano\mico32\micosystem\de0_nano\components\lm32_top\rtl\verilog\lm32_load_store_unit.v(485)::Error: irom_store_data_m [ ]内が宣言でのレンジを超えています。
C:\user\onedrive\vm_share\de0_nano\mico32\micosystem\de0_nano\components\lm32_top\rtl\verilog\lm32_load_store_unit.v(111)::Error: irom_store_data_m port宣言が見つかりません。
・irom_store_data_m出力の宣言1bitになってないか?これが原因か?しかし修正しても動作せず。
components/lm32_top/rtl/verilog/lm32_load_store_unit.v
output irom_store_data_m; // Store data to Instruction ROM
wire [`LM32_WORD_RNG] irom_store_data_m;
・lm32_load_store_unit.v内のデータRAMもアルテラ版へ変更。まだ正常動作せず。
・startupルーチンからmainへ飛ぶ直前、uut.mico32.LM32.cpu.load_store_unit.load_store_address_x
には0x20001ffcという値が現れる。
objdump -dした時の以下の箇所が関係してると思うが。
00000100 <__startup>:
100: 78 1c 20 00 mvhi sp,0x2000
104: 3b 9c 1f fc ori sp,sp,0x1ffc
108: e0 00 01 4f bi 644
これってmemory.defで
って定義してるからだよな。1024*8だから、命令ROM/データRAMの. = 0x20000000 + (1024*8) - 4;
_sp_base = .;
メモリ容量0x3ffを超えて、0x1ffc(=0x2000-4)にアクセスしてる。
そして、MSBで定義したInline Memoryの容量はBASE=0x20000000、OFFSET=400としており、
system_conf.vの`define CFG_DRAM_LIMIT 32'h200003ffを超えているので、dram_select_xが反応しない。
<lm32_load_store_unit.v>
`ifdef CFG_DRAM_ENABLED
assign dram_select_x = (load_store_address_x >= `CFG_DRAM_BASE_ADDRESS)
&& (load_store_address_x <= `CFG_DRAM_LIMIT);
`endif
・まだこんなerrorが出る。
Instruction bus error. Address: 00000644
・この論理に至る流れは以下だ。
main関数に飛ぶ当たりで、irom_select_a=HからLへ変化してる。assign irom_select_a = ({pc_a, 2'b00} >= `CFG_IROM_BASE_ADDRESS) && ({pc_a, 2'b00} <= `CFG_IROM_LIMIT);
つまり、上の論理通り、プログラムカウンタpc_aが命令メモリの範囲を超えたと判断したからだ。
・system_conf.vの定義は以下なので、アルテラRAM定義のアドレス幅10bit=0x3ffがLIMITとしてる。
`define CFG_IROM_BASE_ADDRESS 32'h0
`define CFG_IROM_LIMIT 32'h3ff
しかし、上の論理では{pc_a, 2'b00}として、下位2bitシフトしてから判定してるから、実際にpc_aが
アクセスするメモリ空間は0x00000000から0x00000fffになる。
MSBでのInline Memory容量定義が間違ってるって事かな?
・MSBでInline Memory容量を命令/データ共に0x1000に修正。
Instruction bus errorは消えたが、「Data bus error. Address: f0000000」が出てる。
しかし、プログラムはmain関数に入れたし、動いてない原因は、Data bus errorのための
割り込みが無限ループしてるだけ。
Data bus errorの原因が解決出来れば動くようになると思われる。
・components/lm32_top/rtl/verilog/lm32_load_store_unit.vの内容から、
Data bus errorが出る原因はd_err_i=Hになってるから。
if (d_err_i == `TRUE)
$display ("Data bus error. Address: %x", d_adr_o);
・d_err_iの接続元を追うと、以下論理。
<MSBのTOPモジュール.v>
assign LM32D_ERR_I = LEDGPIOGPIO_en ? LEDGPIO_ERR_O :
uartUARTUART_en ? uartUART_ERR_O :
spiSPISPI_en ? spiSPI_ERR_O :
i2cm_ocI2CMI2CM_en ? i2cm_ocI2CM_ERR_O :
sdramSS_en ? sdramS_ERR_O :
1;
・あら? SWでレジスタアドレスの定義が古い。最新のMSB構成に合わせたらData bus error消えた。
単なるミスでした。
・LED用のGPIOへのアクセスも動いてない?と思ったけど、その原因は、GPIOデータ幅を8bitにしていて、
SWではLED=0x00000012;などとしていたから。LED=0x12000000にしたら値の変化が見れた。
(2015.3.5追記)
・ようやく本題に戻って、ビットシフト動作のために<<を使ったら、__ashlsi3が無いってerrorの対策だ。
・wr_1bit_h関数には引数として、レジスタとビット番号を渡している。
これは、Mico32のマニュアルの通り、汎用レジスタのr1=arg0とr2=arg1で渡せる。
しかし、wr_1bit_h関数の中で呼び出される__ashlsi3を自作したが、ここに引数を渡す方法が分からない。
・関数main → 関数wr_1bit_h → 関数ashlsi3と、関数が呼ばれる過程で、spが示すアドレス(データRAM)
へ積まれていく。この積まれたデータをアクセスすれば良いようだ。
本当はフレームポインタのアドレスをベースに参照した方が良いらしいが、まずはspを使ってやってみる。
・シミュレーションでspの指す値を調べると・・・
[1]sp(r28) = 0x200003b8 (wr_1bit_h関数に入った直後)
[2]sp(r28) = 0x200003a4 (wr_1bit_h関数に入り、スタック処理が終わった後らしき時間)
[3]sp(r28) = 0x200003a4 (wr_1bit_h関数で呼ばれるashls3関数に入った直後)
[4]sp(r28) = 0x20000394 (ashls3関数に入り、スタック処理が終わった後らしき時間)
・シミュレーションで実際に引数がデータRAMへ積まれる値を調べると・・・
wr_1bit_hの引数0=0x8000000c、引数1=4、がどんな流れで処理されるかが以下。
1.wr_1bit_h関数に入る。(プログラムカウンタが、wr_1bit_h関数の先頭番地になった)
2.引数0がr1レジスタへWriteされる。
3.引数1がr2レジスタへWriteされる。(2の1クロック後)
4.データRAMへのスタック処理1番目= データRAMアドレス信号0x0ecへ0x0がWriteされる。
この一番目のWrite実行クロックと同時に、スタックポインタ(r28)は0x200003b8から0x200003a4へ変化。
5.データRAMへのスタック処理2番目= データRAMアドレス信号0x0ebへ0x200003fc(この時のフレームポインタ(r27)の値)がWriteされる。
6.データRAMへのスタック処理3番目= データRAMアドレス信号0x0eaへ0x6c8(wr_1bit_hからの戻りアドレス)がWriteされる。
7.データRAMへのスタック処理4番目= データRAMアドレス信号0x0eeへ0x8000000c(引数0)がWriteされる。
8.データRAMへのスタック処理5番目= データRAMアドレス信号0x0edへ0x4(引数1)がWriteされる。
・やりたい事は引数1にアクセスしたい。
アクセスする場所はashlsi3の中で、この時、sp(r28)=0x20000394、fp(r27)=0x200003a4という状態。
なので、基準となるspアドレス0x394=916decから、アクセス先の引数1アドレス0x0ed=237decの差は679。
asm volatile ("lw r6, (sp+-679)");
こんな記述で0x20000edへアクセス出来るかな?
やってみたらWarning出た。
Warning: Non-aligned word access. Address: 0x200000ed Time: 1230000.
・データRAMも命令ROM同様、CPUコアのアドレス信号「31:0」のうち、下位2bitが除外された状態で
接続されてるのを忘れてた。そして、さっき0xedに引数1がWriteされると書いたけど、このアドレスは
データRAM自体のアドレス信号であって、CPUコアのアドレス信号は0xedに下位2bit加えた0x3b4にすべきでした。
spとあまりに差があるので??と思ったが強行してた。
なので、基準となるspアドレス0x394=916decから、アクセス先の引数1アドレス0x3b4=948decの差は32。
従って、こんなインラインアセンブラでアクセス出来るのでは?と。
asm volatile ("lw r6, (sp+32)");
(2015.3.9追記)
・上位関数wr_1bit_hから下位関数ashlsi3へどうやって引数を渡すのか?の件。
なんだかんだやって、結局は、上位関数の時点で、使わない汎用レジスタr25とかに引数(r1やr2)を
保存しておく、という方法で、強引に値を渡し、ashlsi3関数内でも引数にアクセス出来るようになった。
ほんとは「スタックポインタ+/-数値」とやってアクセスしたかったけど、上手く行かず、所望の値がRead出来ず。
・とりあえず引数を参照出来るようになったので、先へ進んでみる。
渡した引数からビットシフトを行い、所望のビットだけをHにする動作をインラインアセンブラで記述出来た。
次に出て来た問題は、main>wr_1bit_h>ashlsi3と関数を入れ子で進み、今度はashlsi3からwr_1bit_hへ戻る時、
戻りアドレスr29が0x0になってしまい、リセット番地へ飛んでしまう。
0x0なんて値を扱った覚えが無いのに、ashlsi3から戻る直前になって、r29が0x0にされる。なぜ?
解析すると、原因は、スタックを配置しているデータRAMのメモリはアルテラIP Catalogから生成してるが、
元々2portで生成してたつもりが、1portになってた事が原因。
インスタンス呼び出しは2portで宣言してるのに、parameter設定は1port。なので、関数から戻る際に、
スタック積んだ場所からReadしてるつもりが、0x0の値しかRead出来てなかった。
命令ROMもデータRAMも同じアルテラメモリを使っていて、1portになってしまっていたが、それでもそれなりに
動作してたのは、命令RAMは1portのみ使用したRead動作だけだったから?と予想。
・QuartusIIのIP catalog画面であらためて2portメモリを生成し、そちらを使用するようにした。
そうしたら、今度は、違う命令で不可解な動作。
■Latticeメモリを使って、正しいはずの動作を見る
・アルテラメモリを使って、正しいはずの動作を予想して強引に解析進めていたが、疲れて来た。
そもそも、命令ROM、データRAMはどう動くべきか?を、Latticeメモリを使って見てみよう。
正しい動きが再現出来たら、動作をサイクル毎にテキストdumpしておいてもいい。
・lm32_instruction_unit.vの命令ROMと、lm32_load_store_unit.vのデータRAMをそれぞれLatticeメモリへ戻して、
更に、Latticeメモリで読めるように.memの初期値をMicoSystemBuilderで生成してみよう。
・今まで使ってたSWソースを揃えてMSBでビルドするが、命令ROMの容量オーバー。
ビルドログ見る限り、よくわからないライブラリが一切合財ビルドされて、しかもリンクされてしまう。
これらを全てOFFにする方法が分からないので、カスタムのリンカスクリプトを使う。
MSB > C/C++ウィンドウ > プロジェクト名で右クリック > Propaties > Platform > Use custom Linker script
ここで、DWM2007/10付属のmemory.defをmemory.ldへ改名して設定する。
これで容量オーバーは消えたが、余計なライブラリが除外出来たか確認出来てない。
・MSB > Tools > Software Deployment > Mico32 On Chip memory deployment > PRJ名、elf指定、.mem指定してstartしたが以下MSGで.mem生成されず。
1 [main] bin_to_verilog 3532 find_fast_cwd: WARNING: Couldn't compute FAST_CWD pointer. Please report this problem to
the public mailing list cygwin@cygwin.com
Failed to open tmp.bin for reading
No such file or directory
(2015.3.12追記)
生成する.verを利用する事にした。
.memを作れるね。
.mif最初から.mif.memのフォーマットは、単に0番地から各行に32bit分の16進数を記載していく形式。
この.verと目的の.memの違いは、@から始まるアドレス行があるかどうか。
.memに比べ.verには@開始のアドレス行追加されてるだけなので、sedで@開始行を削除して
.memとして保存。
とか何とかやってたけど、Latticeメモリ単体でシミュレーションしてもアルテラメモリと全く
同じ動きをするような設定に出来ていたので、以降はアルテラメモリで継続。
(2015.3.19追記)
メモリ単体テストではLatticeとアルテラで違いは無いと判断した。
しかし全体シミュレーションするとアルテラメモリでは動かない。
またLatticeメモリに戻って同じ挙動になるか確かめたくなった。
しかしLatticeメモリに変えると、そもそも.memが読めず、メモリ出力がHizになってる。
Latticeメモリ単体では.mem読めてるのに。。。なぜ?
Mico32全体の中でlm32_load_store_unit.vで呼び出されているLatticeメモリだが、
lm32_load_store_unit.vでのシミュレーションはどうか?、それがOKならその1つ上の階層も含めた
シミュレーションはどうか?、と1つづつ階層を上げて行こうと思う。
けど、各module毎にいちいちテストベンチを生成するのって面倒。
って事で、トップmoduleから自動でテストベンチテンプレートを生成するRubyを作った。(以下で公開)
http://bobgosso.blog13.fc2.com/blog-entry-421.html
けど、このRubyはdefine変数に対応してないので、Mico32みたいにdefineでいろいろコンフィグしてるソースだと、
今のコンフィグ設定では存在しないport宣言がひたすらされてしまい、編集面倒だ。
define変数の有無で自動的にそのportがあるかどうか判断してテストベンチに出力するようにしたい。
[C言語]undefined reference to '__ashlsi3'
struct st_spi { /* struct SPI */
union { /* CTRL */
unsigned int DWORD;
struct {
unsigned int RESV1 :21;
unsigned char SSO :1;
unsigned char RESV4 :1;
unsigned char IE :1;
unsigned char IRRDY :1;
unsigned char ITRDY :1;
unsigned char RESV5 :1;
unsigned char ITOE :1;
unsigned char IROE :1;
unsigned char RESV6 :3;
} BIT;
} CTRL;
こんな構造体を宣言してから、BITアクセスの方法を検討。
SPI.CTRL.DWORD = SPI.CTRL.DWORD | (1<<4);
main関数内で上のように書いたら、エラー無し。
これでCTRLレジスタの4bit目をHに設定出来た。
$ bat.ccomp
INFO : gcc ---------------------------
INFO : as ---------------------------
INFO : ld ---------------------------
INFO : objcopy (srec) ----------------
INFO : objcopy (ihex) ----------------
ちなみにこのバッチは以下でCygwin上で実行してる。
#!/bin/csh -f
set src_name = uart_int
echo "INFO : gcc ---------------------------"
lm32-elf-gcc -c $src_name.c
echo "INFO : as ---------------------------"
lm32-elf-as startup.s -o startup.o
echo "INFO : ld ---------------------------"
lm32-elf-ld -Map $src_name.map -T memory.def startup.o $src_name.o -o $src_name
echo "INFO : objcopy (srec) ----------------"
lm32-elf-objcopy -O srec $src_name $src_name.srec
echo "INFO : objcopy (ihex) ----------------"
lm32-elf-objcopy -O ihex $src_name $src_name.hex
次に、main内の直書きではなくて関数化してみる。
void wr_1bit_h (int * reg_base){
*reg_base = *reg_base | (1<<4);
}
こんな関数を作って、mainから次のように呼び出し。
wr_1bit_h(&SPI.CTRL);
次のようなwarningが出るが、エラーは無い。
ちなみにこのワーニングは調べてみたが解決せず。ポインタの理解が間違っている事だと思うが。
シミュレーションでは、このままでも想定動作してたので一時的に放置。
$ bat.ccomp
INFO : gcc ---------------------------
uart_int.c: In function `main':
uart_int.c:170: warning: passing arg 1 of `wr_1bit_h' from incompatible pointer type
INFO : as ---------------------------
INFO : ld ---------------------------
INFO : objcopy (srec) ----------------
INFO : objcopy (ihex) ----------------
最後に、レジスタアドレスとbit番号を引数で渡すように関数を修正。
void wr_1bit_h (int * reg_base, int bit_num){
*reg_base = *reg_base | (1<}
関数を上のようにして、mainから下のように呼び出す。
wr_1bit_h(&SPI.CTRL, 4);
これをコンパイルすると、「undefined reference to `__ashlsi3'」と出た。
$ bat.ccomp
INFO : gcc ---------------------------
uart_int.c: In function `main':
uart_int.c:168: warning: passing arg 1 of `wr_1bit_h' discards qualifiers from pointer target type
INFO : as ---------------------------
INFO : ld ---------------------------
uart_int.o: In function `wr_1bit_h':
uart_int.c:(.text+0x220): undefined reference to `__ashlsi3'
INFO : objcopy (srec) ----------------
lm32-elf-objcopy: 'uart_int': No such file
INFO : objcopy (ihex) ----------------
lm32-elf-objcopy: 'uart_int': No such file
(2015.2.25追記)
ashlsi3不足に関して良い情報が見当たらない。
今不足している関数について、自分でアセンブラで自作出来ないものか?
そのためには、指定レジスタの指定ビットへHをライトする関数wr_1bit_hについて、
どこの機能が不足してるのか調べる。
リンカで問題あるため最終的なelfは生成されないが、オブジェクトファイルは出来てる。
objdumpで表示してみる。
lm32-elf-objdump.exe -d uart_int.o
:
000001ec:
1ec: 37 9c ff ec addi sp,sp,-20 → spに-20を加算したものをspへ格納
1f0: 5b 8b 00 0c sw (sp+12),r11 → r11の値をアドレス(sp+12)へ格納
1f4: 5b 9b 00 08 sw (sp+8),fp → fpの値をアドレス(sp+8)へ格納
1f8: 5b 9d 00 04 sw (sp+4),ra → raの値をアドレス(sp+4)へ格納
<ここまでで、この関数内で使用するr11、fp、raの値をspへ保存した>
1fc: 34 1b 00 14 mvi fp,20 → addi rd, r0, imm16の疑似命令。r0が0だから省略されてる?
200: b7 7c d8 00 add fp,fp,sp → add rX, rY, rZ。rYをrZへ加算しrXへ格納
204: 5b 61 00 00 sw (fp+0),r1 → r1の値をアドレス(fp+0)へ格納
208: 5b 62 ff fc sw (fp+-4),r2 → r2の値をアドレス(fp-4)へ格納
20c: 2b 61 00 00 lw r1,(fp+0) → fpに即値0を加えたアドレスの値をr1へ格納
210: 28 2b 00 00 lw r11,(r1+0) → r1に即値0を加えたアドレスの値をr11へ格納
214: 34 01 00 01 mvi r1,1 → addi rd, r0, imm16の疑似命令。r0が0だから省略されてる?
218: 2b 62 ff fc lw r2,(fp+-4) → (fp-4)の値をr1へ格納
21c: f8 00 00 00 calli 21c→ 戻りアドレス「PC+4」をraへ格納。それから21c(=PC+0x30)へ無条件分岐。ここが__ashlsi3に該当する模様。
220: b9 61 10 00 or r2,r11,r1 → r11とr1のORを取って、その結果をr2へ格納
224: 2b 61 00 00 lw r1,(fp+0) → (fp+0)の値をr1へ格納
228: 58 22 00 00 sw (r1+0),r2 → r2の値をアドレス(r1+0)へ格納
<ここから先で、main関数に戻るためr11、fp、raの値をspから復元させる>
22c: 2b 8b 00 0c lw r11,(sp+12) → (sp+12)の値をr11へ格納
230: 2b 9b 00 08 lw fp,(sp+8) → (fp+8)の値をfpへ格納
234: 2b 9d 00 04 lw ra,(sp+4) → (fp+4)の値をraへ格納
238: 37 9c 00 14 addi sp,sp,20 → spに20を加算したものをspへ格納
23c: c3 a0 00 00 ret → raのアドレスへ条件分岐
<スタックポインタ定義=0x20000000 +0x2000 -4>
. = 0x20000000 + (1024*8) - 4;
_sp_base = .;
<LatticeMico32 Processor Reference Manual>
ra= call命令における戻りアドレス保存用に使用される。しかしながら汎用レジスタ。
sp= 未使用領域の先頭を指す
fp= アクティブフレームで使用された領域の先頭を指す
参考:スタックポインタ、フレームポインタ
http://www.kijineko.co.jp/tech/creintro/stack.html
[C言語]iodefine.hサンプル
struct st_sci3 { /* struct SCI3 */
union { /* SMR */
unsigned char BYTE; /* Byte Access */
struct { /* Bit Access */
unsigned char COM :1; /* COM */
unsigned char CHR :1; /* CHR */
unsigned char PE :1; /* PE */
unsigned char PM :1; /* PM */
unsigned char STOP:1; /* STOP */
unsigned char MP :1; /* MP */
unsigned char CKS :2; /* CKS */
} BIT; /* */
} SMR; /* */
unsigned char BRR; /* BRR */
}; /* */
SCI.SMR.BIT.COM = 1;
SCI.SMR.BYTE |= 0x80;
[C言語]アルテラ変数宣言
typedef signed char alt_8;
typedef unsigned char alt_u8;
typedef signed short alt_16;
typedef unsigned short alt_u16;
typedef signed long alt_32;
typedef unsigned long alt_u32;
typedef long long alt_64;
typedef unsigned long long alt_u64;