目次

CP/M80調査

 Z80を利用したCP/Mマシンを使い、実験装置や
 アームロボットを動かしていた学生時代のこと
 を思い出し、今手元にあるZ80ボードでCP/Mを
 動かすことにしました。




 20世紀の8ビットOSの中で、CP/M80は大学の中では
 頻繁に見かける、ありふれた環境でした。16ビット
 のCUIを扱うOSで、MS-DOSがありふれた環境でした。

 CP/80が動いていると、クロス環境でCP/M68kへの
 移行がスムーズにできたのです。CP/M68kは企業
 で開発したLSIのチェックに使っていました。

 AVRにシリアルEEPROM、SDカードを接続して
 FDC、仮想I/OでAVRを使い、Z80上でCP/Mを
 動かしている外国のWebサイトを見ました。

 英語、独語で書かれた内容を読むと、今ではCP/M80を
 動かすのは、それほど難しくないと思います。

 SDカードは、ホームセンターでワンコインで買え
 シリアルEEPROMは、秋月電子で128kバイトが¥300
 くらいです。これらをディスクの代わりに使えば
 CP/M80を動かせるはずです。

 本棚には、アスキー出版局の「入門CP/M」、「実習CP/M」、
 「応用CP/M」がありました。約30年も前の本ですが、捨てず
 にいたようで、本棚にPC8801のマニュアルと並んで、鎮座し
 出番を待っていました。



 この中の「応用CP/M」に、BIOSに関して詳細な説明が
 ありました。DigitalResearch社が公開していたBIOS
 の構造が書かれています。何度か読み直すと、次の
 ことを思い出しました。

 BIOSには、17種類の基本サブルーチンがある。
 そのサブルーチンを、自分のシステムに合わせて
 カスタマイズする。このBIOSだけは、自分で作成
 しなければならない。




 学生時代は、CP/M上のアプリケーションで実験装置や
 ロボットを動かしたので、CP/Mを移植するところから
 やってみることにします。

 物置には、8インチドライブ、5.25インチドライブ
 があり、メディアも新品で1箱ずつありました。
 5.25インチディスクには、PC8801用CP/Mのシステム
 ディスクが残っていたので、これを吸い上げて、EEPROM
 やSDカードに移すと動かせそうです。

 シリアルEEPROMやSDカードにシステムを移したとして
 どうやってZ80のメモリ空間に、それらを転送するかを
 考えると、ROMエミュレータでBootLoaderを入れれば
 よいと思いつきました。



 「応用CP/M」の内容を読んで、ディスクをどう扱うのかを
 理解し直しました。

 BIOSのJumpテーブルの後に、ディスクパラメータヘッダ
 と呼ぶブロックを用意し、ここに記述されたパラメータ
 に従って、ディスクドライブとやり取りします。
 目的のプログラム、ファイルは、単なる情報の塊として
 Z80のメモリ空間にロードあるいはセーブしますが、その
 入出力に、このパラメータ類を使います。

 ディスクパラメータヘッダを利用することで、ディスク
 ドライブやメディアの違いを吸収して、DOS(Disk Operating
  System)を実現しているのでした。

 標準となるディスクは、8インチで次の諸元を持ちます。

 ディスクからデータを入出力するには、1セクタごとと
 なりますが、使いにくいので、ディスクドライブが一度
 に扱えるバイト数をブロックという単位で扱います。

 1ブロックは、1024の(2のN乗)倍数で扱います。
 ブロックを扱うために、ブロックシフト、ブロックマスク
 と呼ぶパラメータを導入して、計算しやすくしています。

 標準ディスクでないディスクメディアを使うときには
 いろいろな変換をしなければならず、電卓を叩くのが
 面倒なので、Tcl/TkでGUIアプリを作成しました。



 1セクタの容量、扱うディレクトリ最大量等を指定するため
 緑のボックスに数値を入力します。ボタンCalcをクリックする
 と、黄のボックスに結果が出るようにしました。

 このTcl/Tkのソースコードは、以下。

#!/usr/local/bin/wish

wm title . "parameter generator"

# initialize global values
set txtBPS  128
set txtSPT  26
set txtTTN  77
set txtSTN  2
set txtMDN  64
set txtBBS  1024
set txtBSN  0
set txtSPB  0
set txtBMS  0
set txtXSPT 0
set txtTBN  0
set txtALO  "00000000 00000000"
set txtXALO "0 0"

#######################################
# define objects
#######################################
# label
label .lblBPS      -text "Byte Per Sector"
label .lblBPSUnit  -text "bps"
label .lblSPT      -text "Sector Per Track"
label .lblSPTUnit  -text "spt"
label .lblTTN      -text "Total Track Number"
label .lblTTNUnit  -text "count"
label .lblSTN      -text "System Track Number"
label .lblSTNUnit  -text "count"
label .lblMDN      -text "Max Directory Number"
label .lblMDNUnit  -text "count"
label .lblBBS      -text "Block Byte Size"
label .lblBBSUnit  -text "byte"
label .lblBSN      -text "Block Shift Number"
label .lblBSNUnit  -text "count"
label .lblSPB      -text "Sector Per Block"
label .lblSPBUnit  -text "count"
label .lblBMS      -text "Block MaSk"
label .lblBMSUnit  -text "count"
label .lblXSPT     -text "Sector Per Track"
label .lblXSPTUnit -text "count(/128)"
label .lblTBN      -text "Total Block Number"
label .lblTBNUnit  -text "count"
label .lblALO      -text "Allocation Table"
label .lblALOUnit  -text "binary"
label .lblXALO     -text "Allocation Table"
label .lblXALOUnit -text "count"

# entry 
entry .edtBPS  -textvariable txtBPS  -bg green
entry .edtSPT  -textvariable txtSPT  -bg green
entry .edtTTN  -textvariable txtTTN  -bg green
entry .edtSTN  -textvariable txtSTN  -bg green
entry .edtMDN  -textvariable txtMDN  -bg green
entry .edtBBS  -textvariable txtBBS  -bg green
entry .edtBSN  -textvariable txtBSN  -bg yellow
entry .edtSPB  -textvariable txtSPB  -bg yellow
entry .edtBMS  -textvariable txtBMS  -bg yellow
entry .edtXSPT -textvariable txtXSPT -bg yellow
entry .edtTBN  -textvariable txtTBN  -bg yellow
entry .edtALO  -textvariable txtALO  -bg yellow
entry .edtXALO -textvariable txtXALO -bg yellow

# button
button .btnExit -text "Exit" -command "exit"

button .btnCalc -text "Calc" -command {
  # computing block shift number
  set tmp [expr $txtBBS % 1024]
  if { $tmp > 0 } {
    tk_messageBox -type ok -message "* illegal block byte size => $txtBBS *"
    return 
  }
  set txtBSN [expr int(log($txtBBS / 128) / log(2))]
  # computing sector per block
  set txtSPB [expr $txtBBS / $txtBPS]
  # computing block mask
  set txtBMS [expr int(pow(2,$txtBSN)) - 1]
  # computing sector per track
  set txtXSPT [expr $txtSPT * ($txtBPS / 128)]
  # computing total block number
  set tmp [expr ($txtTTN - $txtSTN) * $txtBPS * $txtSPT]
  set tmp [expr $tmp / $txtBBS]
  set txtTBN [expr $tmp - 1 ]
  # computing allocation table
  set tmp [expr ($txtMDN * 32) / $txtBBS]
  set xtmp ""
  set zero "0"
  set one  "1"
  for {set i 0} {$i < 16} {incr i} {
    # insert space
    if { $i < $tmp } {
      set xtmp "$xtmp$one"
      if { $i == 7 } {
        set xtmp "$xtmp "
      }
    } else {
      set xtmp "$xtmp$zero"
      if { $i == 7 } {
        set xtmp "$xtmp "
      }
    }
  }
  set txtALO $xtmp
  set tmp [lindex $xtmp 0]
  set xtmp ""
  for {set i 0} {$i < 8} {incr i} {
    set xtmp "$xtmp [string range $tmp $i $i]"
  }
  set tmp 0
  foreach e $xtmp {
    set tmp [expr $tmp * 2 + $e]
  }
  # tk_messageBox -type ok -message " $tmp"
  set txtXALO "$tmp 0"
}

#######################################
# window area placing
#######################################

grid .lblBPS      -column 0 -row 0
grid .lblBPSUnit  -column 2 -row 0
grid .lblSPT      -column 0 -row 1
grid .lblSPTUnit  -column 2 -row 1
grid .lblTTN      -column 0 -row 2
grid .lblTTNUnit  -column 2 -row 2
grid .lblSTN      -column 0 -row 3
grid .lblSTNUnit  -column 2 -row 3
grid .lblMDN      -column 0 -row 4
grid .lblMDNUnit  -column 2 -row 4
grid .lblBBS      -column 0 -row 5
grid .lblBBSUnit  -column 2 -row 5
grid .lblBSN      -column 0 -row 6
grid .lblBSNUnit  -column 2 -row 6
grid .lblSPB      -column 0 -row 7
grid .lblSPBUnit  -column 2 -row 7
grid .lblBMS      -column 0 -row 8
grid .lblBMSUnit  -column 2 -row 8
grid .lblXSPT     -column 0 -row 9
grid .lblXSPTUnit -column 2 -row 9
grid .lblTBN      -column 0 -row 10
grid .lblTBNUnit  -column 2 -row 10
grid .lblALO      -column 0 -row 11
grid .lblALOUnit  -column 2 -row 11
grid .lblXALO     -column 0 -row 12
grid .lblXALOUnit -column 2 -row 12

grid .edtBPS  -column 1 -row 0
grid .edtSPT  -column 1 -row 1
grid .edtTTN  -column 1 -row 2
grid .edtSTN  -column 1 -row 3
grid .edtMDN  -column 1 -row 4
grid .edtBBS  -column 1 -row 5
grid .edtBSN  -column 1 -row 6
grid .edtSPB  -column 1 -row 7
grid .edtBMS  -column 1 -row 8
grid .edtXSPT -column 1 -row 9
grid .edtTBN  -column 1 -row 10
grid .edtALO  -column 1 -row 11
grid .edtXALO -column 1 -row 12

grid .btnCalc -column 4 -row 8
grid .btnExit -column 4 -row 9

 このGUIアプリを作成することで、CP/M80はディスクを
 次のように扱うことを理解できました。



 データブロックは、ディレクトリを含んだファイルを
 入れる領域です。CP/M80では、ディスクドライブAに
 システムを入れたディスクメディアを入れ、リセット
 するのがシステム起動のデフォルトです。

 ディレクトリの中に、ファイルがどのブロックの
 中に含まれているのかの情報を入れています。

 16ビットのDOSの代表であるMS-DOSでは、FAT
  (File Allocation Table)を利用し、ファイル
 を入出力しますが、CP/M80ではFATを利用して
 いません。

 CP/M80がDOSの全盛時代には、FATを使うような
 大きなディスクは、ハードディスクに限定され
 フロッピーディスクベースのDOSでは、不要と
 考えていたのでしょう。

 シリアルEEPROMにCP/M80システムを入れるときは
 FATを考える必要はないですが、SDカードを使うと
 なると、FATの操作をしないとシステムのリロード
 やファイル操作ができないことになります。


目次

inserted by FC2 system