目的
NECのV30CPU上にて、z80を用いて生成した機械語を実行する環境を整えます。
ゲームボーイの仕様を研究しており、z80のニーモニックの方が慣れているのでz80用のアセンブラを使用します。
※z80から追加された命令は使わないよう注意します。
ゲームボーイ研究日記
8080とz80で機械語を比較
説明
NECが製造していた8086互換CPUは8080CPUの命令を実行する互換モードがあります。
V30上でいつでもV30の8086以下のように実装しました。
配置
test.binの配置 | メモリ上の配置 | 説明
0x0000 | 0x7C00 | BIOSから読み込まれる8086のブートローダ
0x0E00 | 0x7E00 | 8080コードを呼ぶ為の8086コード
0x1000 | 0x8000 | 8080コード(今回の対象)
BIOSがブートローダを読み込む原理に関しては前に以下の記事で検証しています。
ブートローダのORG0x7C00とは何か?BIOSを改造して実験
手順
ブートローダ
- ブートローダから2セクタを読み込む(ブートローダ自体はBIOSから読み込まれている)
8086コード
2. IVT 0x80に8080コードの開始アドレス(0x8000)を登録する
3. V30のCPUが8080モードへ遷移する機械語0x0F, 0xFFを実行する
6. 8080側でVRAMに見立てた0x6000のデータをVGAのVRAM領域0xB800へ複製して画面表示する
8080コード
4. VRAMバッファ[0x6000]へ値を入れる
5. V30のCPUが8086モードへ復帰する機械語0xED,0xFDを実行する
実行結果
8080のコードで1から10の合計を計算し、その結果を8086モードで画面上に描画されている。
コード
nasm -f bin 8086.asm -o 8086.bin
z80asm -o 8080.bin 8080.asm
cat 8086.bin 8080.bin > test.bin
lsblk
sudo dd if=test.bin of=/dev/sdb bs=512 count=3 conv=notrunc
org 0x7C00
start:
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7C00
; ディスクから第2(8086モード作業場),3(8080側)セクタを 0x7E00 に読み込む
mov ah, 0x02 ; INT 13h 2: 読み込み
mov al, 2 ; 読み込むセクタ数 = 1
mov ch, 0 ; シリンダ = 0
mov cl, 2 ; セクタ番号 = 2(1始まり)
mov dh, 0 ; ヘッド = 0
mov dl, 0x80 ; ドライブ番号
mov bx, 0x7E00 ; 読み込み先アドレス
int 0x13
jc disk_error ; エラー時に無限ループ
; 読み込んだコードへ跳ぶ(第2セクタ)
jmp 0x0000:0x7E00
disk_error:
jmp $
times 510-($-$$) db 0
dw 0xAA55
%define TARGET_VECC 0x80
%define TARGET_ADDR (TARGET_VECC * 4)
BUFFER_ADDR equ 0x6000 ; 8080モード用の文字バッファ
start2:
cli
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7C00
; --- IVT 0x80 に 8080コードの開始地点を登録 ---
mov word [TARGET_ADDR], 0x8000 ; オフセット
mov word [TARGET_ADDR + 2], 0 ; セグメント(CS)
call init_8080buffer
mov si, msg_enter;;;;
call print_string
; 8080モードへ移行
db 0x0F, 0xFF, TARGET_VECC
; 8086復帰
after_8080:
xor ax, ax
mov ds, ax
mov ax, 0xB800
mov es, ax
; --- バッファからVRAMに複製 ---
call copy_8080buffer_to_vram
;mov si, msg_back
;call print_string
hlt_loop:
hlt
jmp hlt_loop
;-----------------------
; 8080用画面バッファからVRAMへ複製
;-----------------------
copy_8080buffer_to_vram:
push ax
push cx
push si
push di
push ds
push es
xor ax, ax
mov ds, ax
mov si, BUFFER_ADDR
mov ax, 0B800h
mov es, ax
xor di, di
mov cx, 4000 ; 80*25*2
rep movsb ; DS:SI → ES:DI
pop es
pop ds
pop di
pop si
pop cx
pop ax
ret
;-----------------------
; 8080画面出力用BUFFER初期化
;-----------------------
init_8080buffer:
push ax
push cx
push es
push di
; ES=0x0000 DI=0x6000
xor ax, ax
mov es, ax
mov di, BUFFER_ADDR
mov cx, 80*25
mov ax, 0x0720 ; AH=属性, AL=' '
cld
rep stosw ; ES:[DI] ← AX , DI ← DI + 2
pop di
pop es
pop cx
pop ax
ret
;-----------------------
; 文字列出力
;-----------------------
print_string:
lodsb
or al, al
jz .done
mov ah, 0x0E
int 0x10
jmp print_string
.done:
ret
msg_enter db "Entering 8080 mode...", 13, 10, 0
msg_back db "Successfully returned!", 13, 10, 0
times 512-($-start2) db 0
org 0x8000
ld a, 0
ld b, 10 ; 10回ループ
sum_loop:
add a, b
dec b
jp nz, sum_loop
; ここでAに合計が入っている (55)
; ASCII 55=7 よって7が表示される
ld (0x6000), a
ld a, 0x4f
ld (0x6001), a
db 0xed,0xfd
ds 512 - ($ - 0x8000)