新しいことを書く前に(その4)まで について整理しておこう。
- AVR 互換のコアを Verilog で 0 から作成している。AVR の種類は、Tiny40 (もどき)。
CPU コアは、従来のどの AVR とも違う AVR8L と言われているもの。『Reduced CORE tinyAVR』という表記も見られる。LDS/STS 命令に互換性がなく、レジスタも r16-r31 の 16 個しかない。
それでも、gcc サポート済みで gcc からは avrtiny10 という アーキテクチャになっている。
追記: メモリマップも違う。IOレジスタは 0 - 0x3f で GPR はマップされない。LPM 命令がなくなった代わりに 0x4000 から FLASH がマップされるのも大きな違い。他にも eeprom がないとか違いは結構ある。 - (その4) では、実装を進めていったが、最終的に全命令と割り込みの実装が完了した。
- rtavr-wk05g.tar.gz
(その4) の最終版は、これ。(その5) では、これを整理したもの(論理は変えない)をベースにする。
個々の命令の検証は未完了で、特にフラグの計算が未チェック。あとデコードも甘く、他の命令を誤認している所があるかも知れない。
ToDo: 実際のコードを動かしながら、 デバッグしていく。
- rtavr-wk05g.tar.gz
- シミュレータとして、『Icarus Verilog for Windows』-- Windows 版 setup (GTKWave 同梱) を 使用。
rtavr では、make を使っているので MSYS 併用を推奨。()
ただ、Icarus Verilog は、シンタックスチェックが甘いのと ループした論理を書くと シミュレータ自体無限ループしてしまうという問題があって、ISE も併用している。
ISE を使うときは、Spartan-3A XC3S200A-4ft256 への Implement をやって 規模 や 遅延も見ている。 - 規模は、ROM なし / PORT なし / タイマー 0 あり / 割り込みコントローラあり で、537 スライス。これに PORTC と INT0 を組み込むと 562 スライス。
コアのみの規模で 300 スライスという目標にしたが、まだ分離できないので規模はわからない。今は たぶん 400 スライスぐらい?
ToDo: 規模を見るために コアだけを Implement できるようにする。 - 性能は、2 倍速で動かしている GPR がボトルネック。最大遅延は、7.468ns で 2X クロック 133MHz / 実行 66 MHz ぐらい。
目標は、40-50 MHz ということにしたのでクリア。
ToDo: GPR を 16bit 幅にして 普通に作ったらどうなるか見てみたい。 - ゴールは、XC3S50A で SPI-FLASH ライタにできること。
ToDo: USART を必須としているが未実装。SPI も入れたい。最終的には TWI も。
コア単体の規模
まずは、コードの整理をしているので、コア単体の規模を見てみることにした。
Number of Slice Flip Flops 191 3,584 5%
Number of 4 input LUTs 745 3,584 19%
Number of occupied Slices 421 1,792 23%
Total Number of 4 input LUTs 762 3,584 21%
Number used as logic 729
Number used as a route-thru 17
Number used for Dual Port RAMs 16
421 スライス。まぁこんなものか。ちなみに、INT0 しかない 割り込みコントローラを 組み込むと +8 の429。
目標より随分大きくなったが、最適化は当面先。まずはきちんと動かさないことには。
そもそも、とうやったら最適化できるのだろう? それが分かったとして Spartan-3 向けとSpartan-6 向け で全然違ってくると面白くない。
ポイントのひとつは、IOR のアドレスデコーダと 出力のセレクタだとは思う。いつか いろいろ試行錯誤してみよう。
コードの整理
大分進んだ。
設定ファイル rtavr_defs.v の中身はいまこうなっている。結構な数だが、説明を残しておく。
// CLOCK options
// CLK is generated by GPR
`define USE_DMY_CLOCK
// use tri-state bus (emulation for SP3)
//`define OUTPUT_MUX_TRI
`define OUTPUT_MUX_NONE
// tuning option
`define NO_STALL_IDX
`define GPR_WB_TUNE
// for checking performance
//`define GPR_2X_ONLY
// top layer configuration
`define RTAVR_INCLUDE_IOR
`define RTAVR_INCLUDE_RAM
//`define RTAVR_INCLUDE_ROM
`define RTAVR_INCLUDE_INTCONT
// IOR configuration
//`define IOR_HAVE_PORTA
//`define IOR_HAVE_PORTB
//`define IOR_HAVE_PORTC
`define IOR_HAVE_SREG_SP
//`define IOR_HAVE_INT0
`define IOR_HAVE_TIM0
// ROM configuration
//`define ROM_SIZE 1024
`define ROM_SIZE 2048
//`define ROM_SIZE 4096
// S1 options :
//`define OUTPUT_WB_ADDR
//`define CHECK_CONFLICT
(それぞれの説明)
// CLOCK options
// CLK is generated by GPR
汎用レジスタ(GPR) モジュールで CLK2X を分周して CLK を生成する。
もともとは、DCM を使う予定だったので ダミー としたが本採用するつもり。
// use tri-state bus (emulation for SP3)
OUTPUT_MUX_TRI は、各モジュール が tri-state で出力をつなげる。記述は tri-state でも実際は、ロジックに変換されることを期待している。OUTPUT_MUX_NONE は、ROM/RAM の OE を使わない指定で デフォルト。なにも指定しないと OE==0 のとき 0 が出力される。OR で接続可能。
// tuning option
チューニングオプションで、両方 define するのが前提。詳しくは (その4) 参照。
// for checking performance
GPR の CLK2X で動く部分の遅延を見るためのオプション。
GPR 単独で Implement する。
// top layer configuration
トップレベルでの定義。各モジュールを 外に出すかどうかを切り分ける。
INTCONTは、割り込みコントローラで モジュール としては独立していない。
define を外すと単に使わない意味になる。
// IOR configuration
IOR のサブモジュールを使うかどうかの定義。
SREG_SP/INT0 は内部モジュール。TIM0 は割り込み部分が内部で、他は外部。
SREG_SP は rtavr では必須だが、他は外すことができる。INT0 を外し、INTCONT を使うと 外部から割り込み線が使えることになる。(テストで使用)
サブモジュールはこれから増やしていく。
SREG_SP を 外せるようにしているのは、他の AVR CORE に IOR を組み込んでみたいため。
// ROM configuration
ROM のサイズ指定。1024/2048/4096 ワードのどれかを指定。
これから、include する データファイル の指定を追加する予定。
データファイルは、
initial
begin
rom[12'h000] = 16'h0000;
:
:
end
という形式で、HEX ファイルを変換するスクリプトを用意するつもり。
// S1 options
使っていない。
割り込みのクロック数
(その4) で割り込みのシーケンスを示した。割り込みに 3 クロック、RETI に 3 クロックかかり 最短 6 クロックで復帰する。割り込みを 2 クロック でできるかもと書いたが.. 間違いだった。
割り込みはもとより 2 クロックだった。
- (1) 割り込まれる最後の命令の実行
- (2) PCL を PUSH
- (3) PCH を PUSH
- (4) 割り込みベクタの命令の実行
となっていて、RETIが 4 クロックだった。
- (1) PCH を POP
- (2) PCL を POP
- (3) POP した値を PC へロード
- (4) (分岐先の命令をロード: S0)
- (5) 分岐先の命令の実行
POP は、S2 への指示なので値が得られるのは、1 クロック後。だから (3) でようやく PC が分かり (4) で INST が分かる。分岐先の処理は、(4) では S0 をやっていて、S1 に来るのは (5) 。
... というわけで、最短 6 クロックで合っている。
ただ、このチェックをしていたら 有効・無効の指示 S0_VALID と s1_invlid がバグっているのが分かった。
GPRの検討
今のボトルネックは、2 倍速で動かす GPR 。レジスタを分散メモリで実装したいがために 2 倍速にしたわけだが、普通に作るとどうなるのだろう?
// 2X
// Number of Slice Flip Flops: 67
// Number of 4 input LUTs: 112
// Number of occupied Slices: 76
// Total Number of 4 input LUTs: 113
// Number used for Dual Port RAMs: 16
// Number of bonded IOBs: 96
// Post-PAR Static Timing Report
// Clock to Setup (CLK2X) MAX 7.468
現在は、76 スライス。そのうち レジスタは 16 (? 1 個しか置けない) 多少増える程度なら置き換えたい。
まず、分散RAM を絶対に使うことにすると、16 が 32 ? 48 とかに増える。インデックスレジスタを分離することになりそう。分散 RAM は、1bit しかないようだから 完全に分離すると多分 48 。
それで速くなるなら、従来のと同じ仕様にして差し替えるのも良いだろう。
本当に 普通に作ると AVR Core のように全部 FF にマップされるだろう。だが 8x16 = 128 bit だ。意外に規模が増えないかもしれない。
これは、まぁいずれ。
ROMの使用の検討
『UG62 - XSTユーザガイド』 と 『UG687 -- XSTユーザガイド(Spartan-6用)』を見ると Verilog で ROM の初期値を 設定する方法は、主に 2 通りあるようだ。
ひとつは、データファイルを用意して、 $readmemh を使う方法。もうひとつは、initial で 記述する方法。もともと予定していたのは、後者で 記述自体を 別ファイルとして生成する方法。
シミュレータによっては、どちらかしか使えないかも知れないので、両方とも生成してみることにした。
以前 awk で ihex を変換するツールを作っていたので、生成は awk スクリプト。Windows なら MSYS に最初から含まれているので 今回から MSYS 必須ということにする。
ちょっと中身を紹介しておくと、
`ifdef ROM_INCLUDE_V
`include "rom_data.v"
`elsif ROM_INCLUDE_DAT
initial
begin
$readmemh("rom_data.dat" , rom, 0 , SIZE-1);
end
`endif
readmemh を使う場合は、1 エントリ 1 行で 16 進のデータ 4 桁のみのデータファイルを用意する。行数は、ROM のサイズに一致しなくてはならない。
"rom_data.v" の方はこれではサッパリ分からないので 中身を紹介すると
integer i;
initial
begin
//
rom[ 0] <= 16'hC010;
rom[ 1] <= 16'hC017;
:
:
rom[ 45] <= 16'hCFFF;
for (i = 46 ; i < SIZE ; i = i + 1)
rom[i] <= 16'hFFFF;
end
こんな風になっている。これも全部きっちり初期化しないとならない。が、for 文が使えるのでサイズを減らせる。
awk には、SIZE を渡さないといけない。Makefile でこんな風に指定している。
awk --assign rom_size=2048 -f ihex2v.awk $@ > rom_data.v
awk --assign rom_size=2048 -f ihex2dat.awk $@ > rom_data.dat
avr-objdump -d $< > rom_data.inst
さて、この データをどうやって作るかというと.. AVR Toolchain を使う。WinAVR では、Tiny40 に対応していないので注意。
これを シミュレータにかけたいわけだが、一部を紹介すると こんな風に作った。
initial begin : test
sys_reset();
while ( CLK_COUNT < 20)
begin
@(posedge CLK2X);
@(posedge CLK2X);
CLK_COUNT = CLK_COUNT + 1;
end
$display("*** Simulation Complete! ***");
$finish;
end
クロック数を指定してそこまで動かす。
試したところ 両方ともシミュレータにかけることができた。ISE の方は、うまくいっていない。"rom_data.dat" の方 は map でエラーになる。"rom_data.v" の方 はエラーにならないのだが、終わらない。まぁ、基本は合っているはずだから、ISE は、おいおいやっていくことにしよう。
うまくいかない理由は、PORTC を diable にしていたせいだった。入力しかないので、ほとんどが最適化で削除される。"rom_data.dat" の方が早々にエラーになるわけで 早く終るようだ。"rom_data.v" の方 は余計な最適化をやろうとして時間を浪費しているのだろう。
ちなみに "rom_data.v" では、XST ユーザーズガイドと(ちょっと)違う記述にしている。どうも 全部 <= で代入しないと変な Warning が出るようだ。 あと rom の定義は、rom[SIZE-1:0] が正しいらしいのだが、rom[0:SIZE-1] でないとダメらしい。
それはともかく、"rom_data.v" も残すが、"rom_data.dat" の方をデフォルトにしようと思う。
06

- rtavr-wk06.tar.gz
これが、整理した最初のバージョン。みるからにうまくいっていない。最初の C010 は、リセットの割り込みベクタの命令で 011 番地に RJMP している。これは良いのだが... C011 を 2 回実行して 022 番地に飛んでしまっている。明らかにスタートアップに問題がある。
ちなみに、最初に実行されるのは次の命令列。(バイトアドレスなので注意)
00000022 <__ctors_end>:
22: 11 27 eor r17, r17
24: 1f bf out 0x3f, r17 ; 63
26: cf e3 ldi r28, 0x3F ; 63
28: d1 e0 ldi r29, 0x01 ; 1
2a: de bf out 0x3e, r29 ; 62
2c: cd bf out 0x3d, r28 ; 61
2e: 12 d0 rcall .+36 ; 0x54 <main>
30: 13 c0 rjmp .+38 ; 0x58 <_exit>
こんな風にバグがあるが、ここまでこぎつけた。バグは直せば良いわけだ。当面は、このコードを動かせることを目標にする。
ちなみに、C のソースと ihex2v.awk , ihex2dat.awk は、tn40test ディレクトリに置いてある。テストベンチと rom_data.v, rom_data.dat , rom_data.inst は、tb_rom ディレクトリにもコピーしてある。上記を再現するだけなら AVR Toolchain も MSYS (awk) も必須ではない。
06A

- rtavr-wk06a.tar.gz
スタートアップは直った。これは、スタートアップのバグではなく、s1_invalid なのに jump してしまうというバグだった。あと jump 系は 2 命令なので、PC を進めて(+ 1) おいて ディスプレースメントを足すという処理に変更した。(RET/RETI でバグッたかも)
で、最初の命令が xor r17,r17 。r17 が不定値でも結果は 0 なのだが、 Icarus Verilog は、結果を不定値とみなしてしまう。これでは困るので、GPR をゼロクリアしておくことにした。
さて、やっていることは、スタックポインタを設定して main を call しているだけ。SP が 13F になって 2A を call しているので OK 。
次は main だが、たった 2 命令だった。
00000054:
54: e0 9a sbi 0x1c, 0 ; 28
56: ff cf rjmp .-2 ; 0x56 <main+0x2>
sbi して、無限ループ。
ここで sbi にバグがあった。IOR から ロードして、1bit セットして 書き戻すわけだが、ロードデータとストアデータがつながってなかった。
つなげるための指令もない。これは、CMD_LOAD & CMD_STORE で代替することにした。ロードしてストアするものは、SBI/CBI (BCLR/BSET) しかないので これで良いし、なにより ロードしてストア するという条件そのもので 妥当そうに思える。
ロードデータは ior から読みだして bit 操作した v_ior_dob2 と ROM/RAM も MUX した load_deta があるのだが どちらをつなげるのが良いか悩んだ。ISE で implement して比べたところ v_ior_dob2 の方が 4 スライス少なかったので v_ior_dob2 にした。
どちらも同じデータになるので単に配線の問題なのだが、この程度で 4 スライスも変わってしまう。よくよく気をつけることにしよう。
気になっていた OR での MUX を試してみた。IOR の サブモジュール PORTC と TIMER0 を OR で MUX したところ 17 スライスも減った。
無限ループの方は問題なし。当面と書いたのに終わってしまった。この調子でどんどんコードを作って試したいが、06 のディレクトリ構造だとスナップショットを取るのに不便なので、C のソースとテストベンチを 1 つのディレクトリに置くよう変更した。
メモ: 未修正のバグ: - RET/RETI での s1_invalid の設定。
(上述) - 割り込みコントローラの INT_VEC ハンドリング。
割り込み処理中に 別の割り込みが来ると値が変わってしまい、新たな割り込みの方を RST してしまう。
06B
- rtavr-wk06b.tar.gz
C のプログラムを書き換えて グローバル変数を使ってみた。この場合、
<__ctors_end>: が変わり次のようになる。
00000022 <__ctors_end>:
11: 2711 eor r17, r17
12: bf1f out 0x3f, r17 ; 63
13: e3cf ldi r28, 0x3F ; 63
14: e0d1 ldi r29, 0x01 ; 1
15: bfde out 0x3e, r29 ; 62
16: bfcd out 0x3d, r28 ; 61
0000002e <__do_clear_bss>:
17: e010 ldi r17, 0x00 ; 0
18: e6a0 ldi r26, 0x60 ; 96
19: e0b0 ldi r27, 0x00 ; 0
t;
00000036 <.do_clear_bss_loop>:
1b: 931d st X+, r17
00000038 <.do_clear_bss_start>:
1c: 36a2 cpi r26, 0x62 ; 98
1d: 07b1 cpc r27, r17
;
1f: d00f rcall .+30 ; 0x5e <main>
20: c01b rjmp .+54 ; 0x78 <_exit>
まずはここまで、確認してみる。
その前に、... 次で動く main はこんな風になった。
0000005e <main>:
2f: e081 ldi r24, 0x01 ; 1
30: e090 ldi r25, 0x00 ; 0
31: 9390 0061 sts 0x0061, r25
33: 9380 0060 sts 0x0060, r24
35: dfec rcall .-40 ; 0x44 <func_a>
36: 9310 0061 sts 0x0061, r17
38: 9310 0060 sts 0x0060, r17
3a: dfe7 rcall .-50 ; 0x44 <func_a>
3b: cfff rjmp .-2 ; 0x76 <main+0x18>
なんと、2 バイト命令の sts ( や lds ) を使っている。もちろん gcc のバグで ググると報告されている。(『ATtiny10 problems』スレッド)
だが、最新版はどうやって入手できるのだろう? ベータサイトでは Published: 2010-12-03 の as4e-ide-2.7.0.851-XX しか入手できないのだが ... ソースコードも入手できていない。... 待つしかないのか?
いっそのこと 2 word 命令の LDS/STS を実装してしまうという手はある。これだけが問題で、待ちきれなくなったら検討してみよう。1 word 命令の LDS/STS は従来の命令をオーバライトしているが、2 word の方は単に サポートしていないだけなので、可能ではある。
これは非常に問題だが、まずは目の前の命令から。0x60 と 0x61 に 0 をストアするコードだから、2 回ループするだけのはずが... 実は無限ループしている。
詳しく検証してみよう。1b(931d) の実行時の IE が ストアアドレス。1回目は 0x60 だが、2 回目は、FF9E ? ... なんだろう?
続く命令は、cpi , cpc どちらもレジスタには書き戻さない。... が cpc で CMD_OP3_WR が 1 になっている。... cpse / nop はフラグも書き戻さないが cp/cpc は レジスタだけ書き戻さないのか。
さらに、cpi は CMD_OP2 だが、全部書き戻すように コーディングしていた。
これは直した。ちゃんと 61 になっている。... が ループは止まらない。
まずは、CPI を見てみると .. 0 - IMM_DATA としていた。Rd_in - IMM_DATA が正しく、書き戻さないという点を除いて SUBI と同じ。
SUBI と同じことをするよう変更したら、規模がすこし小さくなった。ならば、SUBIと SBCI をひとつにすると ... 15 スライスも減った。
ううむ。ひとつの ALU にした方が規模が小さくなるのかも知れない。
これを直したら先に進んだ。次は、1f: RCALL(d00f) で、main(0x2f) にちゃんと飛んでいる。で、main は動かないわけだ。
さてどうしよう。どうせ 16bit の LDS/STS で届く範囲しかメモリがないはずだから、
32bit LDS/STS を 16bit LDS/STS + NOP に書き換えることは可能だ。
32bit:
LDS 1001:000d:dddd:0000 # load direct (+ kkkk:kkkk:kkkk:kkkk)
STS 1001:001d:dddd:0000 # store direct (+ kkkk:kkkk:kkkk:kkkk)
16bit:
LDS 1010:0kkK:dddd:kkkk # 16bit
STS 1010:1kkK:dddd:kkkk # 16bit
2 word 命令は 1 つしかなく、91r0/93r0 で始まる。これを aXrX に直してやれば良いわけだ
ちょうど 逆アセンブルリストを バイト表記から ワード表記に変換するスクリプトを書いているので、細工して 変更するデータが分かるようにしよう。
00000044 <func_a>:
# 22: 9180 0060 lds r24, 0x0060
22: a480 0000 lds r24, 0x0060
# 24: 9190 0061 lds r25, 0x0061
24: a491 0000 lds r25, 0x0061
26: 1781 cp r24, r17
27: 0791 cpc r25, r17
28: f019 breq .+6 ; 0x58
29: 9a28 sbi 0x05, 0 ; 5
2a: 9a30 sbi 0x06, 0 ; 6
2b: 9508 ret
2c: 9830 cbi 0x06, 0 ; 6
2d: 9828 cbi 0x05, 0 ; 5
2e: 9508 ret
0000005e <main>:
2f: e081 ldi r24, 0x01 ; 1
30: e090 ldi r25, 0x00 ; 0
# 31: 9390 0061 sts 0x0061, r25
31: ac91 0000 sts 0x0061, r25
# 33: 9380 0060 sts 0x0060, r24
33: ac80 0000 sts 0x0060, r24
35: dfec rcall .-40 ; 0x44 <func_a>
# 36: 9310 0061 sts 0x0061, r17
36: ac11 0000 sts 0x0061, r17
# 38: 9310 0060 sts 0x0060, r17
38: ac10 0000 sts 0x0060, r17
3a: dfe7 rcall .-50 ; 0x44 <func_a>
3b: cfff rjmp .-2 ; 0x76 <main+0x18>
こんな風にチェックして、変更するデータを作ることは awk で出来た。だが、実際のデータはいちいち手で修正しないといけない。
すごく面倒だが、かと言って変更まで自動化するつもりはない。どうせ 文字列とグローバル変数を使わなければ、STS/LDS は出ないのだ。このテストと文字列のテストだけは、手で修正することにしよう。
変更はうまくいったようだ。61 に 0 を 60 に 1 をストアしている。
そして、<func_a> (022) を call 。画面では分からないが、00 と 36 を push している。
次にいこう。 <func_a> では if 文があり、028 の breq(f019) がそれに相当する。1 回目は分岐せず sbi , sbi , ret (02b:9508) 。
ret では、なぜか 0xff , 0xff を POP してきて fff に 飛んでいる。なにかエンバグしているようだ。
これは、ROM と RAM の出力を OR でつなぐオプションが有効になってしまったため。ROM/RAM に OE があるときだけしか使えない。
0xff は ROM の値。
とにもかくにも、最後の無限ループにたどりついたようだ。
06C

- rtavr-wk06c.tar.gz
ret がおかしいのを直した。いままで ret が 3 クロックだと思っていたので そこで間違っていた。
ret は 4 クロックで、3 クロック分 命令の実行を抑止する。最初の 2 つは s0_inv_skip で 単なる抑止。最後の 1 つは、s0_inv_jump で 他の 分岐命令と同じ。画面は、最初の ret 命令で、そのようになっている。
戻ったところで、9310 / 0061 になっているが、実は、32bit LDS/STS をサポートした。
上記のリンクでは、
(Jan 14, 2011 - 08:07 AM)
Post subject: Re: RE: ATtiny10 problems
lfmorrison wrote:
There's more wrong than just that:
The LDS and STS instructions are clearly the 32-bit variants, which don't exist in an ATtiny10. For an ATtiny10, it should have been using the 16-bit variant of the LDS and STS instructions.
Now that I've read up on the instruction set I actually understand what you mean. Clearly something is going wrong. I'm going to try with IAR KickStart edition in the mean time.
こう書いてある。1/14 に問題を認識しているからいつか直る。だが、32bit 版があると 本当はすごく便利なのだ。ROM にある STRING や定数を直接アクセスできる(= RAM にコピーしなくて良い)し、グローバル変数を沢山持てる。
まぁこういうことをするには、avr-gcc のソースを入手してさらに改造しないといけないから、なかなか難しいのだが サポートできるようにしておくのも良いかも知れない。
それに、規模は大きくなるとしても、コード自体については define で切り分けて対応することは 難しくなかった。
32bit LDS/STS は、v_ldst のうちに入っている。未定義命令だったから 論理の少ない方を選んでこうなったわけだ。あらたに v_ldst32 を作るのは簡単。
処理も簡単なのだ。v_ldst32 の 次のクロックで s2_ldst32 を 1 にして、そのときは EA を使わずに INST を使う。あとは、s0_inv_skip に v_ldst32 も加える。
いちいち命令を 変換するのも面倒なので、当面このままにする。ちなみに、ここまでのスナップショットは、tbr_003 。命令が変わったから 別にする。
次、ストリングを out する風にしてみた。
void func_a(int8_t sel) {
if (sel) {
bit_set(DDRC, PB0);
bit_set(PORTC, PB0);
} else {
bit_clear(PORTC, PB0);
bit_clear(DDRC, PB0);
}
}
main() {
int8_t i;
char *p = "abc";
for (i=0; i<3; i++) {
func_a(i & 1);
}
while (*p) {
PORTB = *p;
p++;
}
return 0;
}
main は、return すると、最後に無限ループする。今回はこれを試してみよう。
00000022 <__ctors_end>:
11: 2711 eor r17, r17
12: bf1f out 0x3f, r17 ; 63
13: e3cf ldi r28, 0x3F ; 63
14: e0d1 ldi r29, 0x01 ; 1
15: bfde out 0x3e, r29 ; 62
16: bfcd out 0x3d, r28 ; 61
17: d00a rcall .+20 ; 0x44 <main>
18: c01b rjmp .+54 ; 0x68 <_exit>
00000034 <func_a>:
1a: 2388 and r24, r24
1b: f019 breq .+6 ; 0x3e <__SP_H__>
1c: 9ae0 sbi 0x1c, 0 ; 28
1d: 9ae8 sbi 0x1d, 0 ; 29
1e: 9508 ret
1f: 98e8 cbi 0x1d, 0 ; 29
20: 98e0 cbi 0x1c, 0 ; 28
21: 9508 ret
00000044 <main>:
22: e080 ldi r24, 0x00 ; 0
23: dff6 rcall .-20 ; 0x34 <func_a>
24: e081 ldi r24, 0x01 ; 1
25: dff4 rcall .-24 ; 0x34 <func_a>
26: e080 ldi r24, 0x00 ; 0
27: dff2 rcall .-28 ; 0x34 <func_a>
28: e6e0 ldi r30, 0x60 ; 96
29: e0f0 ldi r31, 0x00 ; 0
2a: c003 rjmp .+6 ; 0x5c <main+0x18>
2b: b986 out 0x06, r24 ; 6
2c: 5fef subi r30, 0xFF ; 255
2d: 4fff sbci r31, 0xFF ; 255
2e: 8180 ld r24, Z
2f: 2388 and r24, r24
30: f7d1 brne .-12 ; 0x56 <main+0x12>
31: e080 ldi r24, 0x00 ; 0
32: e090 ldi r25, 0x00 ; 0
33: 9508 ret
00000068 <_exit>:
34: 94f8 cli
0000006a <__stop_program>:
35: cfff rjmp .-2 ; 0x6a <__stop_program>
出てきたコードはこれ。最初のループは inline 展開されている。次に -1 を sub しながら string をたどり、out している。
これコードはあっているが、string をメモリに展開しない。展開しないのなら、ROM にあるデータのアドレスを Z に入れないとだめ。
rom[ 54] <= 16'h6261;
rom[ 55] <= 16'h0063;
文字列はここにある。アドレスは、0x406C 。(ROM を 参照する場合 + 0x4000 する。アドレスはバイトアドレス)
# 28: e6e0 ldi r30, 0x60 ; 96
28: e6ec ldi r30, 0x6c ; 108
# 29: e0f0 ldi r31, 0x00 ; 0
29: e4f0 ldi r31, 0x40 ; 64
こう直さないと 動かない。-- さすがにこれを手で修正するのはきつい。だが、まぁやってみよう。
すこし説明しておく。今までの AVR は、LD 命令で ROM を見れなかったので、リードオンリーデータを初期値つきデータとして定義して RAM にコピーして使っていた。領域を使われるのが嫌なら LPM を使う。この AVR (AVR8L) は、インデックスを使った間接なら LD 命令で ROM も見れる。gcc はそういう風なコードを出すが、アドレスは以前のままなわけだが、これは、binutils の ld か (avr-libc での )ldの設定も関係あり gcc 自体の問題ではないかも知れない。
avr-gcc が出すアセンブラを確認してみた。
.data
.LC0:
.string "abc"
:
:
ldi r30,lo8(.LC0)
ldi r31,hi8(.LC0)
これの .data を外すと .LC0 が ROM 相対値に変わる。.LC0 となっているところを .LC0 + 0x4000 とすると期待する値にはなる。
.LC0:
.string "abc"
:
:
ldi r30,lo8(.LC0 + 0x4000)
ldi r31,hi8(.LC0 + 0x4000)
こう。
これなら、C のソースで 合うように書ける。
char *p = PSTR("abc") + 0x4000;
これで期待した バイナリは作れることは作れる。しばらくこれで凌ぐか。

02F の LD ,Z(8180) で 0x61 を読み込んでいる。そして 0 でないから 02B に 分岐。で 02B の OUT(B986) でちゃんと 0x61 を 出力している。次に +1 して ちゃんと 0x406D から 次の 0x62 を読み込んでいる。ここまで OK 。

で、先を見ていくと .... だめだ止まっていない。0 を out してその後もずっと続く。
06D
- rtavr-wk06d.tar.gz

この原因は、FLAGS_BIT_IN のデータだった。bit を選択するのに、SBIX_BIT という skip 命令用の index を使っていたのだが、これは S2 での判断なので、1 クロック遅れた値。分岐命令は、S1 で 判断するので、これを使ったらダメだった。FLAG 用に FLAGS_BIT を作って対処。
さて、これで OK かと思ったら main からの ret で 0x800 に飛んでいる。(正確には 0x1800)。0x0018 に戻るのが正しいのだが... rcall はその前でも使っていてちゃんと動いている。なぜこれだけおかしいのか?
最初の main への ICALL は、0x13F/0x13E に対して 0018 を書いている。最後の ret は、0x13D/0x13E -- ということはずれているわけだ。
この問題は、PUSH/POP での スタック操作の条件を見直すことで直った。最後に 034 がみえているが、<_exit> に到達している。cli があるわけだが、もともと I フラグは 0 なので状態が変わらない。035 の無限ループは検証済みで実際問題ない。
2 つばかり C での検証をこなしたわけだが、string データの アドレスが違う (avr-gcc の)問題は致命的だ。もう少し続けたいが、大きな値の定数や string を使わないようにしようと思う。それと並行して 最新版を手に入れるようにしないと 。手に入らないようなら C での検証を一旦打ち切りにしようと思う。
string は、PSTR + 0x4000 で凌げることが分かった。初期値 0 のグローバル変数も無理やり使えるようにした。だが、いろいろコードを作ってみると変なところが多々あるようだ。やはりちょっと待ってみることにしたい。
命令毎にどこまで評価が終わったかの表を作らないと実用的なプログラムが動くとは思えない。avr-gcc 自体も信用できないから こまめに avr-gcc の version を上げたくない。デバイスも組みたいので、しばらく 検証自体を 先送りにするかも。
やはり、割り込み周りのデバッグとデバイスのデバッグ・作成に移ろうかと思う。タイマーは作っただけだし、INT0 もおかしい。これらのテストに CPU はほとんど関係ない。というか CPU はジャマ。IOR だけのテストベンチに 割り込みコントローラだけで良い。
で、デバイスは SPI → USART の順にすることにした。 SPI の方がまだ簡単。あと、デバイス編は別記事にしようと思う。
(続く)
関連記事:
- AVR互換コアの仕様(メモ)
- AVR互換コアの仕様(その2)
- AVR互換コアの仕様(その3)
- AVR互換コア(その4)
- AVR互換コア(その5) この記事
- Tiny20/Tiny40
- AVR Toolchain
著作権について
ここで提示しているコードは正しく動作しないとはいえ、既に著作権は発生しています。
著作権は、すzが保持しており放棄はしていません。
教育目的および私用目的では、もともと著作権の範囲外なので自由につかえます。また、ライセンスとして、GPL を適用しています。GPL に従う範囲において 個別の許可なく使用することができます。許諾のための連絡も不要です。
なお、GPL なので、生成したバイナリを作り直せる範囲のソース開示が必要になります。FPGA だと 通常 チップ全体になってしまいます。また、開示したソースを GPL の範囲で再利用されることを妨げることはできなくなります。このコードを利用する場合この点に留意してください。
個別の許可を得れば、GPL 以外の条件での使用は可能です。が、作業中のものには許可は出さない予定です。作業が完了(もしくは中断)したとき、ライセンスは見直します。
なお、すでに公開してしまったものの、ライセンスを取り消すことはできないと考えていますが、ライセンスの追加は可能です。また、新しく公開するものについては、ライセンスしないことすら可能で、どのような制限もありません。変更する可能性がありますので、この点にも留意してください。
今なら解説できますが、何年か経つと忘れてしまうので、できたら あれ? と思ったところとか 違和感を感じたところを 教えて頂けると幸いです。既に分かってしまった所でもかまいません。後々のために 説明を残しておきたいです。
ところで、最初の質問は インデックスレジスタの 書き戻しの所のことでしょうか? ここは色々あったのでまとめておきたいところですね。他にもあったらよろしくお願いします。もちろんバグ報告は大歓迎です。