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 と重複する場所があるので注意)

そんなこんなで

画面が出るようになってきたので SD カードを読む関数を作るか、 CPU 周りの回路を組むことができる。