8080エミュレータを作ってCP/M-80を動かす(その2)

CP/M

8080エミュレータが完成したので、次はCP/M-80のBIOSもしくはBDOSをエミュレーションするコードを作成します。BIOSの方が楽そうなので、そちらにすることにします。実際、四つのBIOSコールをエミュレーションするだけで、多数のCP/M-80アプリを稼働できるようになりました。

BIOSのエミュレーションコードについては次回解説するとして、今回はBIOSのカスタマイズとCP/Mのシステムイメージファイル作成方法を紹介します。

BIOSのソースコードの入手と改造

BIOSのソースコードのひな型は「CBIOS.ASM」という名前のファイルで配布されています。同ファイルを含むアーカイブは、http://www.cpm.z80.de/source.html の「CP/M 2.2 ORIGINAL SOURCE」のリンクをクリックすることなどで入手できます。

メモリーサイズを変更する

CBIOS.ASMファイルの中身を見ると、先頭部分に次のような記述があります。

msize   equ     20      ;cp/m version memory size in kilobytes

ここでは、CP/M-80アプリが最大で利用できる主メモリー量を定義しています。CP/M-80のシステムは、シェルに相当する「CCP(Console Command Processor)」と、BDOS、BIOSから構成されていますが、このうちCCPとBDOSの領域はアプリが上書きして利用できます。そのため、主メモリーの総量からBIOS分を引いたサイズを定義しておくのが効率的です。今回作成するBIOSのサイズは350バイト強ですから、最大値である「64」も設定可能です。ただ、今回は後の作業が楽になるように「62」を設定しておきます。

msize   equ     62      ;cp/m version memory size in kilobytes

この場合、BIOSの開始アドレスはF200Hになります。アプリが使用可能な主メモリー量は、それより下位のアドレス領域のサイズである約6万2000バイト(62kバイト)です。この設定にしたCP/Mシステムのことを「62k CP/M」と呼びます。「64k CP/M」ではBIOSの開始アドレスはFA00Hで、アプリが使用可能な主メモリー量は6万4000バイトになります。

I/Oハンドラを書き換える

CBIOS.ASMファイルでは、次のコメントに続いて、BIOSのI/Oハンドラがアセンブリ言語で16個記述されています。

;       simple i/o handlers (must be filled in by user)
;       in each case, the entry point is provided, with space reserved
;       to insert your own code
;

例えば、コンソールのステータスを返す「const」というI/Oハンドラは、次のように記述されています。

const:  ;console status, return 0ffh if character ready, 00h if not
        ds      10h     ;space for status subroutine
        mvi     a,00h
        ret

先ほどのコメントにあるように、これらのI/Oハンドラをユーザーが書き換えればよいわけです。基本的には、「ds 10h」や「ds 256」などのアセンブリ言語で確保された空きスペースの代わりに、自作のコードを記述していきます。今回は実機を制御するわけではないので、空きスペースを削除すれば十分な場合が多く簡単です。

空きスペースは用意されていませんが、「sectran」というI/Oハンドラも書き換えます。フロッピディスクや初期のハードディスクでは、アクセスの高速化のために論理セクターを飛び飛びに配置する「セクターインタリーブ」(または「スキュー」)という手法が用いられていました。sectranは、同手法が用いられている場合に、論理セクター番号を物理セクター番号に変換する目的で使われます。今回はセクターインタリーブを使わないので、DEレジスタで指定された値をそのままHLレジスタに入れて返すコードにします。

コンソールからの入力(conin)と出力(conout)、ディスクの読み出し(read)、書き込み(write)については、特定のアドレス(FFE0H~FFE3H)にジャンプする命令を記述しておきます。それをエミュレータが補足して、エミュレーション用のコードを実行する仕組みにします。

書き換えたI/Oハンドラ部分の全コードを次に示します。

const:  ;console status, return 0ffh if character ready, 00h if not
        mvi     a,00h
        ret
conin:  ;console character into register a
        jmp     0ffe0h
        ret
conout: ;console character output from register c
        mov     a,c     ;get to accumulator
        jmp     0ffe1h
        ret
list:   ;list character from register c
        mov     a,c     ;character to register a
        ret             ;null subroutine
listst: ;return list status (0 if not ready, 1 if ready)
        xra     a       ;0 is always ok to return
        ret
punch:  ;punch character from register c
        mov     a,c     ;character to register a
        ret             ;null subroutine
reader: ;read character into register a from reader device
        mvi     a,1ah   ;enter end of file for now (replace later)
        ani     7fh     ;remember to strip parity bit
        ret
home:   ;move to the track 00 position of current drive
        mvi     c,0     ;select track 0
        call    settrk
        ret             ;we will move to 00 on first read/write
seldsk: ;select disk given by register C
        lxi     h,0000h ;error return code
        mov     a,c
        sta     diskno
        cpi     4       ;must be between 0 and 3
        rnc             ;no carry if 4,5,...
        lda     diskno
        mov     l,a     ;L=disk number 0,1,2,3
        mvi     h,0     ;high order zero
        dad     h       ;*2
        dad     h       ;*4
        dad     h       ;*8
        dad     h       ;*16 (size of each header)
        lxi     d,dpbase
        dad     d       ;HL=.dpbase(diskno*16)
        ret
settrk: ;set track given by register c
        mov     a,c
        sta     track
        ret
setsec: ;set sector given by register c
        mov     a,c
        sta     sector
        ret
sectran:
        lxi     h,0
        dad     b
        ret             ;with value in HL
setdma: ;set dma address given by registers b and c
        mov     l,c     ;low order address
        mov     h,b     ;high order address
        shld    dmaad   ;save the address
        ret
read:   ;perform read operation (usually this is similar to write
        jmp     0ffe2h
write:  ;perform a write operation
        jmp     0ffe3h
waitio: ;enter here from read and write to perform the actual i/o
        mvi     a,0
        ret             ;replaced when filled-in

ディスクパラメータを設定する

続いて、ディスクパラメータを設定します。今回は、桂庵さんの「CP/Mのページ」で設定例が挙げられている、「64セクターで構成されるトラックが128個あるディスク」(CP/M-80では1セクターが128バイトなので、容量1Miバイト)のパラメータを設定します。

ディスクパラメータは、「dpblk」というラベルで示されるディスクパラメータブロックで設定します。CBIOS.ASMファイル内のディスクパラメータブロックを次のように書き換えてください。

dpblk:  ;disk parameter block, common to all disks
        dw      64              ;sectors per track
        db      4               ;block shift factor
        db      15              ;block mask
        db      0               ;null mask
        dw      507             ;disk size-1
        dw      127             ;directory max
        db      192             ;alloc 0
        db      0               ;alloc 1
        dw      32              ;check size
        dw      1               ;track offset

最後の「track offset」は、ファイルシステムの開始トラックを示します。最初のトラック(トラック0)には、CP/Mのシステムイメージ(ブートローダー+CCP+BDOS+BIOS)を記録しますから、ファイルシステムの開始トラックは「1」になります。BIOSや(今回は記録しませんが)ブートローダーのサイズが大きくて1トラックでは収まらない場合には、それに合わせてファイルシステムの開始トラックをずらす必要があります。

続いて、CBIOS.ASMファイルの後半にある「all00」~「all03」、「chk00」~「chk03」というラベルで示される部分のデータを書き換えます。前述のディスクパラメータの場合、「all00」~「all03」には「64」、「chk00」~「chk03」には「32」を設定するそうです。書き換え後の該当部分は次のようになります。

all00:  ds      64      ;allocation vector 0
all01:  ds      64      ;allocation vector 1
all02:  ds      64      ;allocation vector 2
all03:  ds      64      ;allocation vector 3
chk00:  ds      32      ;check vector 0
chk01:  ds      32      ;check vector 1
chk02:  ds      32      ;check vector 2
chk03:  ds      32      ;check vector 3

ブートローダーを記録しないことに合わせた変更

CBIOS.ASMファイルのウォームブート用のルーチン(wboot)には、トラック0にある、ブートローダーが記録されているセクターをスキップするために次の命令が記述されています。

        mvi     c,0             ;c has the current track numbe
        mvi     d,2             ;d has the next sector to read
;       note that we begin by reading track 0, sector 2 since sector 1
;       contains the cold start loader, which is skipped in a warm start

なお、この部分のコメントを読むとセクター番号が「1」から始まるように思えますが、実際にはトラック番号などと同じように「0」から始まりますので注意してください。

上でも少し触れましたが、今回は、システムイメージデータにブートローダーのコードを含めませんので、この部分を次のように書き換えて、スキップしないようにします。

        mvi     c,0             ;c has the current track numbe
        mvi     d,0             ;d has the next sector to read
;       note that we begin by reading track 0, sector 2 since sector 1
;       contains the cold start loader, which is skipped in a warm start

BIOSのアセンブルとCP/Mシステムイメージファイルの作成

CBIOS.ASMファイルを書き換えたら、アセンブルします。Linuxで稼働するIntel 8080向けのアセンブラならどれを使ってもよいと思いますが、私は「Intel 8080 Assembler」を使いました。インストール後、次のコマンドを実行すればBIOSをアセンブルできます。

$ asm8080 -I CBIOS.ASM -l

最後に指定している「-l」オプションは、アセンブル結果とアセンブリ言語を併記した「アセンブルリストファイル」を生成するためのものです。これにより、「CBIOS.lst」というアセンブルリストファイルが生成されます。このファイルは次回で使います。

コマンド実行によって生成される「CBIOS.bin」が実行バイナリファイル、「CBIOS.hex」が実行バイナリファイルをASCIIテキスト形式(Intel HEX形式)にしたものです。

BIOSをアセンブルできたので、CCP+BDOSとマージしてCP/Mのシステムイメージを作成します。まず、CCPとBDOSを含む62k CP/Mのシステムイメージファイル「CPM.SYS」を入手します。CPM.SYSファイルは、http://www.cpm.z80.de/binary.html#operating の「CP/M 2.2」項目にある「CP/M 2.2 BINARY」リンクをクリックして入手できる「cpm22-b.zip」ファイルに含まれています。このファイルを入手できるので、CBIOS.ASMのメモリーサイズ設定を「62」にしたわけです。

入手したCPM.SYSファイルにはBIOSも含まれていますが、その部分はXerox 1800システム用なので不要です。CCPとBDOSは先頭の5632バイト(0000H~15FFHの部分)にあり、その部分は次のコマンドで切り出せます。

$ dd if=CPM.SYS of=CCPBDOS.SYS bs=512 count=11

最後に次のコマンドを実行して、CCPBDOS.SYSとCBIOS.binファイルをマージします。これでCP/Mのシステムイメージファイル「CPM.img」を作成できました。

$ cat CCPBDOS.SYS CBIOS.bin > CPM.img

コメント

タイトルとURLをコピーしました