VAXでアセンブラ短歌

VAXでのアセンブラ短歌を説明します. VAXは可変長命令で,さらに奇数長命令を持っています. マイコン系以外(x86の命令セットは,わたし的にはマイコン系の分類) でアセンブラ短歌ができる,数少ない(kozos.jp/books/asmの中では唯一の)存在です.

なおここではFreeBSDでの例を主に説明します.UbuntuやCentOSなどの GNU/Linuxディストリビューションでも,同様の手順でできるかと思います. apt-getなどでインストールできるツールを探してそれを利用するのが楽で いいでしょう.

目次

VAXアセンブラ

■ 開発環境

開発環境は以下のcross-20121216.tgzをインストールすることで, VAX向け開発環境が利用できる.

インストールすると /usr/local/cross/bin/vax-netbsdelf-gcc が利用できる ようになる.
% /usr/local/cross/bin/vax-netbsdelf-gcc -v
Reading specs from /usr/local/cross/lib/gcc/vax-netbsdelf/3.4.6/specs
Configured with: ../../../toolchain/gcc-3.4.6/configure --target=vax-netbsdelf --prefix=/usr/local/cross --disable-werror --disable-nls --disable-threads --disable-shared --enable-languages=c
Thread model: single
gcc version 3.4.6
% 

VAXシミュレータ

■ simhを利用する

FreeBSDのportsにsimhというシミュレータがあるみたいなのでそれを利用する. VAX向けのエミュレータみたいなのは,simh以外には見あたらなかった.

simhをインストールすると「vax」というコマンドが利用可能になります. 他にも「pdp11」とかのコマンドがあって,アーキテクチャごとにコマンドが 分かれているみたい.

vaxを起動してhでヘルプを出すと,loadというコマンドがあってこれでなんらかの バイナリ?をロードできるみたいに思える.

% vax

VAX simulator V3.8-1
sim> h
r{eset} {ALL|}   reset simulator
e{xamine}          examine memory or registers
...
l{oad}  {}   load binary file
...
h{elp}          type help for command
!                        execute local command interpreter
!               execute local host command
sim> 

■ simhのソースコードを見る

simhのソースコードは /usr/ports/emulators/simh で make fetch することで ダウンロードできる.(/usr/ports/distfiles/simhv38-1.zip)

展開して(カレントディレクトリにいきなりファイルが作成されるので注意. ディレクトリ掘ってそこで展開する),grep load でloadコマンドの処理箇所を 探すと以下のようなのが見つかる.

~>% mkdir simh
~>% cd simh
~/simh>% unzip /usr/ports/distfiles/simhv38-1.zip
~/simh>% cd VAX
~/simh/VAX>% grep load *
...
vax_syslist.c:t_stat sim_load (FILE *fileref, char *cptr, char *fnam, int flag)
...
~/simh/VAX>% 
vax_syslist.c の sim_load() の箇所を見てみるとコメントとかがあって, 以下のようなことがわかる. grep ROMBASE で探ると,以下のことがわかる. ということでベタバイナリをloadコマンドでロードして,simhで boot cpu の ようにしてブートすることで,ブートできそうだ.

■ 簡単なプログラムを動かしてみる

実際にhaltで終了するような簡単なプログラムをアセンブラで書いて, ベタバイナリにしてロードして実行してみよう.

vax.S を以下の内容で作成する.最後にhaltで終了するだけの簡単なもの.

	.section .text

	.globl	_start
	.type	_start, @function
_start:
	halt
	ret

ld.scr を以下の内容で作成する.アドレスをROMBASEの 0x20040000 に合わせてある.

ENTRY("_start")

SECTIONS
{
	.text 0x20040000 : {
		*(.text .text.*)
	}
}

以下のようにしてアセンブルしてベタバイナリを作成する.

% /usr/local/cross/bin/vax-netbsdelf-gcc -fno-builtin -nostdinc -nostdlib -static -O -Wall -g  -o vax.o -c vax.S
% /usr/local/cross/bin/vax-netbsdelf-gcc -fno-builtin -nostdinc -nostdlib -static -O -Wall -g -Wl,-Tld.scr -o vax.x vax.o
% /usr/local/cross/bin/vax-netbsdelf-objdump -d vax.x > vax.d
% /usr/local/cross/bin/vax-netbsdelf-objcopy -O binary vax.x vax.bin

以下のようにして実行する.

~/simple>% vax

VAX simulator V3.8-1
sim> load -r vax.bin
sim> boot cpu

HALT instruction, PC: 20040001 (RET)
sim> quit
Goodbye
~/simple>% 
haltで終了できたので,プログラム実行はできているみたい.

■ 文字の出力方法を調べる

アセンブラ短歌のために,simhのソースコードを調べて文字出力の方法を探る.

まずは grep putchar とかいろいろやってみると,VAX/vax_stddev.c に 以下のようなのが見つかる.

t_stat tto_svc (UNIT *uptr)
{
int32 c;
t_stat r;

c = sim_tt_outcvt (tto_unit.buf, TT_GET_MODE (uptr->flags));
if (c >= 0) {
    if ((r = sim_putchar_s (c)) != SCPE_OK) {           /* output; error? */
tto_unit.bufというバッファの内容を出力しているみたい. grep tto_unit.buf でバッファを操作している場所を探ると, VAX/vax_stddev.c に以下のようなのが見つかる.
void txdb_wr (int32 data)
{
tto_unit.buf = data & 0377;
tto_csr = tto_csr & ~CSR_DONE;
CLR_INT (TTO);
sim_activate (&tto_unit, tto_unit.wait);
return;
}
で,この txdb_wr() の呼び出しを探ると,以下のような手順で呼ばれているのが わかる.
(VAX/vax_cpu.c:sim_instr())
    case MTPR:
        cc = (cc & CC_C) | op_mtpr (opnd);

↓

(VAX/vax_cpu1.c:op_mtpr())
    default:
        WriteIPR (prn, val);                            /* others */
        break;

↓

(VAX/vax_sysdev.c:WriteIPR())

    case MT_TXDB:                                       /* TXDB */
        txdb_wr (val);
        break;
ということでmtprという命令でTXDBというレジスタ?に書き込みすれば文字出力 できるみたい.ただ実際には単に書き込みだけではダメだった. tto_svc()が呼び出されるところを探っても,深くて追いきれなくてよくわからない.

他にも,VAX/vax_sysdev.c に cso_svc() っていう似たような出力処理があって putc()を使っていて同様にたどれるのだけど,上記TXDBのほうがコンソール出力の ようなので,とりあえずこちらは保留.

■ NetBSDのコンソール出力処理を調べる

よくわからんのでNetBSD/VAXのコンソール出力処理を見てみる. NetBSDをsimhで動かすみたいは話がネット上で検索するといっぱい出てくるので, simhで動作するはずだ. TXDBというキーワードでgrepして探すと, sys/arch/vax/vax/gencons.c:gencnputc() に以下のようなのが見つかる.

        while ((mfpr(PR_TXCS) & GC_RDY) == 0) /* Wait until xmit ready */
                ;
        mtpr(ch, PR_TXDB);      /* xmit character */
        if(ch == 10)
                gencnputc(dev, 13); /* CR/LF */
どうやらTXCSのRDYビットというのを見張って,それが立ったらTXDBに書き込んで いいみたい.RDYはおそらくreadyの意味で,レディービットなのだろう.

ということで以下のような待ち合わせ処理をすることで,文字出力ができた. VAXのアセンブラは慣れないので,while (!(TXCS & RDY)) {} みたいなCのソースを コンパイル・逆アセンブルして出てきたアセンブラコードを参考にして書いてみた.

ちなみに mtpr での出力文字の設定後,TXCS を見張ってウェイトするという処理を 入れないとsimhでは出力されなかった.おそらく実際の出力前にhaltでシミュレータが 終了してしまうからだろう.なので通常はウェイト後にmtprという順番にすべきだが, 以下の例では順番が逆になっていて,これはシミュレータ向けのコードと言える.

#define TXCS_RDY (1<<7)
#define TXCS 34
#define TXDB 35

	.section .text

	.globl	_start
	.type	_start, @function
_start:
1:	
	mfpr	$TXCS, %r0
	bicb2	$~TXCS_RDY, %r0
	tstb	%r0
	bneq	2f
	brb	1b
2:	
	mtpr	$'W', $TXDB
1:	
	mfpr	$TXCS, %r0
	bicb2	$~TXCS_RDY, %r0
	tstb	%r0
	bneq	2f
	brb	1b
2:	
	halt
	ret

■ 関数呼び出しの対応

このままでは関数呼び出しを行うと正常に動作しない. 以下のようにしてスタックポインタを適当に設定してみる.

(ld.scrの.textの後に以下を追加)
        .stack ALIGN(16) : {
                . += 1024;
                _estack = .;
        }

(vax.Sの_startの直後に以下のスタックポインタの設定を追加)
        movl    $_estack, %sp

でもダメ. たぶんスタックの扱いがおかしいせいだろう.

VAX/vaxmod_defs.h では以下のような判定をしている. なので現状のld.scrを流用してスタックが.textのすぐ後ろに配置されるようにすると, スタックがROM上になってしまうみたいだ.

#define ROMBASE         0x20040000                      /* ROM base */
#define ADDR_IS_ROM(x)  ((((uint32) (x)) >= ROMBASE) && \
                        (((uint32) (x)) < (ROMBASE + ROMSIZE + ROMSIZE)))
で,VAX/vaxmod_defs.h を眺めてみると,以下の定義がある. どうもゼロ番地付近をRAMとして利用できるみたい.
#define MAXMEMWIDTH     26                              /* max mem, std KA655 */
#define MAXMEMSIZE      (1 << MAXMEMWIDTH)              /* max mem size */
#define MAXMEMWIDTH_X   29                              /* max mem, KA655X */
#define MAXMEMSIZE_X    (1 << MAXMEMWIDTH_X)
#define INITMEMSIZE     (1 << 24)                       /* initial memory size */
#define MEMSIZE         (cpu_unit.capac)
#define ADDR_IS_MEM(x)  (((uint32) (x)) < MEMSIZE)
ということで,ld.scrでスタックを 0x00004000 付近に移動したら正常に 関数呼出しできた.

VAXでアセンブラ短歌

■ アセンブラ短歌を作る

で,作ってみたアセンブラ短歌は以下のようなもの.VAXの3文字が出力される.

#define TXCS_RDY (1<<7)
#define TXCS 34
#define TXDB 35

	.section .text

	.globl	_start
	.type	_start, @function
_start:
	.word	0x0101
	movl	$_estack, %sp
	calls	$0x0, startmain
	halt
	ret

	.globl	startmain
	.type	startmain, @function
startmain:
	.word	0x0000
	nop
	/* fall through */

	.globl	main
	/* no specify type for ignore top word */
main:
/*5*/	movzwl	$'V', %r0
/*7*/	calls	$0x0, putc
/*5*/	movzwl	$'A', %r0
/*7*/	calls	$0x0, putc
/*7*/	mtpr	$'X', $TXDB
	/* fall through */

	.globl	wait
	.type	wait, @function
wait:
	.word	0x0101 /* dummy bitmap as nop for fall through */
1:	/* while (!(TXCS & RDY)) {} */
	mfpr	$TXCS, %r0
	bicb2	$~TXCS_RDY, %r0
	tstb	%r0
	bneq	2f
	brb	1b
2:	ret

	.globl	putc
	.type	putc, @function
putc:
	.word	0x0000
	mtpr	%r0, $TXDB
	calls	$0x0, wait
	ret
ポイントは以下. 逆アセンブルの結果は以下.5命令で5・7・5・7・7が実現できている. 命令サイズが5・7・5・7・7になっていることがわかりやすいように, objdumpに-wをつけて逆アセンブルすることで,1命令が複数行に分割されない ようにしている.
% /usr/local/cross/bin/vax-netbsdelf-objdump -w -d vax.x 
...(中略)...
20040015 <main>:
20040015:       3c 8f 56 00 50  movzwl $0x0056,r0
2004001a:       fb 00 ef 23 00 00 00    calls $0x0,20040044 <putc>
20040021:       3c 8f 41 00 50  movzwl $0x0041,r0
20040026:       fb 00 ef 17 00 00 00    calls $0x0,20040044 <putc>
2004002d:       da 8f 58 00 00 00 23    mtpr $0x00000058,$0x23
...(後略)...
うーん美しい.

ビルドして以下で実行できる.

~/asm>% vax

VAX simulator V3.8-1
sim> load -r vax.bin
sim> boot cpu
VAX
HALT instruction, PC: 20040011 (RET)
sim> quit
Goodbye
~/asm>% 
ちなみに実行前に以下をすることで,telnetで接続してそっちに出力することも可能.
sim> set console telnet=12345

メールは kozos(アットマーク)kozos.jp まで