LSI-C86 講座その2
startup, vector
STARTUP CSEG start:: CLI JMPF to_init dw 0ffffh,0ffffh ;vector table VECTOR ESEG rs 80h db "RAKUTEN EAGLES" TEXT CSEG to_init:: mov ax,4000h mov ss,ax mov sp,4000h mov ax,3800h mov ds,ax call init_## tt: hlt jmp tt
CPU reset 直後に start:: に飛ぶようにする。この領域は 0xfffff0 に参照される命令で 0x10 byte しかないので直後に通常の text 領域に jump するだけにとどめる。 jmpf は他の segment に飛ぶために CS レジスタが変更される。
start:: のように :: をふたつつけると外部ソースからも参照できるようになる。 STARTUP CSEG のように書いておくと code segment の管轄になる。
vector table は割り込み時に書くのだがいまは使わないので適当にしてある。ESEG (extra segment)を使うのが正しいのかは不明。
to_init には SS (stack segment), SP (stack pointer), DS (data segment) をリンカに指定したとおりの値を入れる。 ES (extra segment) は C コンパイラが都度代入して勝手に使っているので初期化しなくていいらしい。
メモリアドレスは (segment << 4) + offset で算出され、 offset は符号無しで加算される。このために SS は RAM の先頭, SP には RAM の末尾が来るように設定する必要がある(符号付きだと思いこんではまった)。
このあとに C で作った関数 init() (アセンブラ的には init_)を呼び出す。
RAM 初期化と far pointer
void init(void) { static const struct cleandata{ uint16_t far *address; uint16_t length; } LIST [] = { {SETABS(0x40000), 0x3f00 / 2}, //RAM {SETABS(0xc0000), 0x0400 / 2}, //spritemap {SETABS(0xc8000), 0x0c00 / 2}, //palette for sprite {SETABS(0xcc000), 0x0c00 / 2}, //pallete for tile {SETABS(0xd0000), 0x4000 / 2}, //tile #0 map {SETABS(0xd8000), 0x4000 / 2}, //tile #1 map {SETABS(0xe0000), 0x10000 / 2}, //z80 memory {NULL, 0} }; const struct cleandata *d = LIST; port_write(0x02, 1 << 3); //sound cpu reset, video hide while(d->address != NULL){ memclear(d->address, 0, d->length); d++; }
ひとまず RAM を初期化するのでRAMアドレスと長さをデータ化して memclear で 0 を埋めるようにするが、ここでも far pointer が機能していなくてはまった。 far pointer は CPU のアドレス 20bit を表現するための 32bit (!)の値で上位16bit が segment, 下位16bit が offset となる。マニュアルに書いてなくて検索したら書いてあった。仕様を知ったときにちょっと発狂した。
offset とか無視してアドレスをいれるマクロを組む。
#define SETABS(abs) (uint16_t *)((abs) << 12)
こうすることによってようやくまともに RAM にデータが書かれるようになった。コンパイラが吐くコードをみると far pointer を 1 つ increment すると segment 制限を抜けるようなまどろっこしいコードが書かれるようだ。処理速度を求める場合は *pointer++ = 0; とかかずに pointer[i++] = 0; と書いた方がよさそう。
port io とインラインアセンブラ
現状では out 命令だけ。
static void _asm_out(const char *op, uint16_t d); #define port_write(port, d) _asm_out("\n\tout\t" #port ",AX\n", d)
_asm_xxx と関数のプロトタイプを書く。 op にはその文字列をいれ、 d には引数を入れる。これは通常の関数の呼び出し方と同じなので、各レジスタの取り回しはマニュアルを確認する必要がある。
in 命令を使う場合は、関数の型が char とか short になるということで、複数の出力値はレジスタ渡しではとれない。
と、かいておいてから include/machine.h に io ポートや割り込みのアセンブラが定義されていた...
far pointer の算出
#define TILE0_OFFSET(y, x) SETABS(0xd1020 + y * 0x100 + x * 2) str_print("HELLO M72 at 2012/09/22" , 25, TILE0_OFFSET(10, 0x10)); render_colorbar(TILE0_OFFSET(0x10, 0x10), 1);
現状はこんな感じに定数をいれているが、変数をいれたらポインタの算出がおかしかった。 far pointer は 32bit だが、変数は16bit計算なのでこけているものと思われるので変数を long にしたり、定数に ul を付けないといけないかもしれない。
デバッグで使う特殊マクロ系
__LINE__, __FILE__ は使えるとマニュアルに書いてあるものの、 __FUNCTION__, __DATE__, __TIME__ などはつかえなかった。全て C89 あたりで規格化されているものだと思いこんでいたが違うらしい。
リンカの設定
LDFLAG = -M -g -TSTARTUP 3fff0 -TVECTOR 00000 -TTEXT 00400 -TDATA 38000 -TBSS 40000
STARTUP はこのシステムの設計で ROM の末尾と 0xffff0 がミラーになるようになっている。 vector は CPU の設定、 text は vector を抜けたところ。昨日書いた data と rodata と bss の関係だが、 ROM と RAM のアドレスがつながっていることに着目して1つのセグメントで ROM と RAM をまたげるようにした。おそらく、基本的なテクニックだと思う。 (ただし STARTUP と重複する場所があるので注意)