シャープPWM方式のお話
Tweetいわゆる隔世の感と言いますか、昔のパソコンは記憶媒体としてカセットテープを使用しており、それが遅い上にエラーがよく発生したなんて話を、現代のHDDやUSBメモリなんかと比較して「いくら昔話だからって信じられない」なんて言いたい/言わせたいがために引き合いに出されるなんてことが、ちょくちょくありますね。
我らがMZ/X1シリーズは「クリーンコンピュータ」というコンセプトが故に、他のパソコンでは「遊ぶためにはカセットテープからプログラムをロードする」という作業をするだけのところを、前準備としてカセットテープからシステムをロードするというステップが余計に必要です。
これがテープは遅い・エラー多発と悪魔合体して、MZで遊ぶためにはまずBASICを起動するのに30分以上かけてテープからロードし、そこから本命のゲームのテープをまた30分以上かけてロードする必要があって、しかも最後の最後にエラーが発生してロード失敗なんてこともよくあるので、ものすごく忍耐が必要だった…などという話を、当時それで遊んでいたなどという人が訳知り顔で吹聴したりすることがあります。
確かに、当時若かった(幼かった)我々はロード完了を今か今かと待ち焦がれ、リールの一回転が永遠かと思えるほどの我慢を経てゲームを楽しんでいましたが…いくらなんでも、そこまでの時間はかかりませんよ? え? 間違いなくかかってた? 具体的な時間までは憶えてないけど?
さぁ果たしてどうでしょうか。MZとX1で採用されていたカセットテープI/Fについて、その仕様から検証してみましょう。
カセットテープのI/F
1970年代のワンボードマイコン時代から、外部記憶装置としてFSK方式のカセットテープI/Fが採用されるのが一般的でした。Wikipediaの「カンサスシティスタンダード」の項には、テープI/Fがどのように生まれ発展したかの歴史が簡潔に記されています。
1978年に開発が進められていたMZ-80Kは、当初は当時の他のメーカー製品と同様にROMからBASICが起動する設計でした。しかし、もしBASICにバグが発見され、NECがTK-80BSでやったようにROMの交換サービスを実施するとなると多大なコストが必要になってしまうことを懸念していました。
そこで、ROMの機能をブートローダーとBIOSに留め、BASIC本体は外部記憶装置から読み込んで起動する、後に「クリーンコンピュータ」と銘打つ方式に変更することにしました。外部記憶装置といってもフロッピーディスクは装置もメディアも高価なので、ひとまずはカセットテープ。カセットテープからBASICインタープリタを読み込むとなると結構な時間を要します。
開発者の中西馨氏の述懐によれば、元々のボーレートは300ボーだったそうです。同じシャープのSM-B-80Tという評価用ワンボードマイコン製品ではFSK(カンサスシティスタンダード)方式が採用されていましたし、おそらく同様のI/Fが搭載されていたのでしょう。これを改良し、1200ボーにまで引き上げることで起動時間短縮を図りました(FSKも後に改良されてMSXでは2400ボーになりますが、当時はその前の状況です)。
この時編み出されたのが「シャープPWM方式」と呼ばれるテープI/Fです。ノイズに強く、速度を上げやすく、X1シリーズにも採用されて2700ボーにまで至りました。内蔵または専用データレコーダの高信頼性とも相まってロード/セーブ時間以外の不満が少なく、ユーザーがなかなかディスク環境に移行しなかった原因にもなったと言われているほど、高性能でした。
ではそのシャープPWM方式とはどんなI/Fだったのでしょうか。
シャープPWM方式とは
シャープPWM方式を簡単に文章で表現すれば、ビット値(0か1)に応じた長さのパルスを記録し、再生時はパルス幅を検出することでビット値に復元する…ということになります。次の図を見てください。
これはシャープPWM方式における、0と1の値を表すパルス波形のモデル図です。波形自体はシンプルな矩形波で、そのひとつだけをとって見てみる分にはデューティ比50%(HとLのパルス幅が同じ)、ただし1は0の倍の長さで表されています。1の半周期分が0の一周期ですね。このように、0と1とでパルスの幅に変化があるのでPWM(パルス幅変調)の一種ということになるわけです。
書き込み(セーブ)では。0ではa時間分のHとL、1ではb時間分のHとLをそれぞれ記録し、読み出し(ロード)ではL→Hの信号の変化を捉えて(エッジセンス)、x時間分経過した時の入力の値によってLなら0、Hなら1と判別するようになっています。
時間に関するパラメータは次のように定義またはプログラムされています。
機種 | MZ-80K/700 | MZ-80B/2000 | S-OS | X1/turbo |
ボーレート | 1200 | 2000 | 2400 | 2700 |
a | 240us | 166.5us | 142us | 125us |
b | 480us | 333us | 282us | 250us |
x | 300us | 232us | 200us | 185us |
既に図を見ればわかるように、0と1とで1ビットが占める時間が倍ほどの違いがあるので、0と1の割合次第で同じバイト数のデータでも全体の時間に違いが発生します。同じ1バイトなのに0x00だと3840us、0xFFでは7680us(1200ボーの場合)になるわけですからね。
「ボー(baud)」というのもつまりは「bps(bits per second)」ですから、0と1で長さが違うなら中身次第で1秒あたりのビット数も変わってくるはずです。1200ボーで計算してみると、全て0の場合は2083ビット、1の場合は1041ビットとなります。1200などの公称ボーレートは、経験則的な便宜上の数値だと捉えるのが良いのでしょう。
なお、0と1の判別にはHレベルの部分しか必要なく、続くLレベルの部分は次のエッジを検出するために十分な長さがあればいいので、規定通りの長さでなくても成立するような気がします。実際、MZ-80BやMZ-2000ではモニタのソースリストのコメントに記述されている1200ボー用パラメータはLレベル部分が少し短縮されてあります。
この波形を記録・再生するインターフェース回路について、MZ-700のものを抜き書きしてみました。MZ-700を選んだのは、外部端子にラジカセなどのテープレコーダーが接続できるようになっており、そのためのインターフェース回路を搭載しているからです。内蔵CMT相手ではCMT側にどんな回路があるのか(あるいはないのか)表現できませんからね。
外部のテープレコーダーに対しては、右下のWRITE端子とMIC端子、EAR端子と左上のREAD端子とそれぞれコードで接続します。
「変調」というからには「復調」する必要があるわけですが、READ端子から入った信号は波形整形はされるものの、ある意味そのままで8255に入力されています。WRITE端子への出力はもっとシンプルにバッファひとつです。バッファとして使われているインバータ(CD4069)はシュミットトリガではない(ヒステリシス特性はない)のですが、他の4000シリーズCMOSロジックICと比べて高いVIH電圧・低いVIL電圧という特性を持っており、また高耐圧(20V)で、汎用のインバータ用途(パルス成形など)に向いているとされています。
では変調・復調とはどういうことかと言えば、それは純粋にソフトによるタイミングということになります。上記のパルス幅や入力レベルチェックまでの待ち時間は、プログラムでループを回して時間待ちした結果作られたものです。記録データはビット単位に送られますから一種のシリアル信号ということになるのですが、それをソフトでタイミングを取って作っているというのはスマートとは言えませんね。
でも逆にそれは簡単にボーレートを変え得るということでもあります。MZ-80B/2000ではモニタ内のパラメータを書き換えればMZ-80K/C時代のテープに記録されたデータをロードできますし、雑誌には元のスペックより高速にプログラムを読み書きするユーティリティが掲載されたこともあります。例としてOh!MZ誌1983年3月号に掲載された記事を簡単に紹介しましょう。
MZ-700が起動直後に走り出すモニタ・1Z-009AはROMに書かれており、パラメータを書き換えるわけにはいきません(書き換えたROMに差し換えるという手もあるが、手持ちの記録済みテープのデータを高速化されたボーレートでSAVEしなおす必要がでてくる)。そこで、短いローダ(高ボーレートに対応)を1200ボーで書き込み、直後に目的のボーレートで本体を書き込むようにするわけです。
ローダは1200ボーでロードされるので高速じゃないですし本来ロードしたかったデータに余計なものが付加されてしまいますが、そのオーバーヘッド分を上回る時間短縮が図れるのなら、トータルで得をするということになります。記事には3分40秒ほどかかるS-BASICのロードが2分ほどに短縮できるとされています。
つまり、このロードの高速化はハードを一切改変することなく、ソフトのみで実現しているということです(高速化の場合は回路や部品の性能マージンが削れることになるが)。こういった芸当が容易に可能であることもシャープPWM方式の利点と言えるでしょう。
フォーマット
ではこのシャープPWM方式のインターフェースを用いてどうテープにデータを記録したか、そのフォーマットを見てみましょう。
データを記録するにあたり、その先頭がどこから始まるのか、そしてデータはメモリのどこに格納されるべきかなどの情報がないと、ビットの0/1が判別できたところで使い物になりません。そこでそれらを判別するためのフォーマットが定義されています。読み出し時はそのフォーマットに従って先頭や区切りを見つけ、しかるべき番地のメモリにデータを格納するわけです。
そのフォーマットですが、大きく分けてヘッダとデータの二つの部分で構成されています。ヘッダがファイル名や格納情報、データが実際のデータ本体です。
フォーマットはMZとX1とで異なっていますので、それぞれをここから見ていきます。
●MZ
値 | "0" | "1" | "0" | "1" | インフォメーション | チェックサム | "1" | "0" | インフォメーション | チェックサム | "1" |
長さ | 22000 | 40 | 40 | 1 | 128バイト | 2バイト | 1 | 256 | 128バイト | 2バイト | 1 |
まずヘッダ部。最初に0が22000個、1200ボーだと10.56秒もの間0ビットが続くということになります。これはテープの最初にあるリーダーテープにヘッダの主要部分がかかって記録されないということがないようにするためと思われます(参考:Wikipedia)。
インフォメーションとはこのファイルの情報をまとめたものです。構成は次のとおりです。
オフセット 内容 0 属性 01h=バイナリ、02h=BASICテキスト、03h=シーケンシャルデータ 1~16 ファイル名 17 00h 18~19 サイズ 20~21 先頭アドレス 22~23 実行アドレス 24~127 未使用
これはモニタのワークエリアでファイル名などを管理する領域そのもののコピーです。エミュレータで使われているテープイメージのMZT(海外はMZF)ファイルの先頭128バイトもこれですね。
「バイト」の構成はこのようになっています。
"1" | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 |
つまりまずスタートビットとしての1があって、あとは8ビットのデータがMSBファーストで送られるというぐあいです。ソフトでやってますから、各ビットが順番に送られるタイミングと、ビット0が送られてから次のバイトを用意してスタートビットが送られるまでのタイミングは若干違うと思われますが特に何らかの時間待ちがあるわけではなく、ビット0の次にすぐスタートビットがあることになっています。
チェックサムとはインフォメーションのデータのうち1の個数を数え上げたものです。65536個まで数えられるということはフルカウント状態で最小8KB、ヘッダでは問題になりませんがデータの大きさによっては何回かラップラウンドする可能性があるということですね。
インフォメーション(とチェックサム)が2回繰り返されていますが、これはリードエラー時のバックアップです。1回目でエラーになっても2回目で読めればそのままデータ部の読み取りに進みます。
値 | "0" | "1" | "0" | "1" | データ | チェックサム | "1" | "0" | データ | チェックサム | "1" |
長さ | 11000 | 20 | 20 | 1 | 2バイト | 1 | 256 | 2バイト | 1 |
次にデータ部。ヘッダ部の先頭と同じように0の連続がありますが、半分の11000個になっています。テープの頭でもないのにちょっと長いですが、ヘッダ部の記録が終了した段階で一度モーターを止めるシステムがあり、再度のモーターONから記録状態が安定するまでの時間を稼ぐ目的があるのかもしれません。
データ部先頭の方の個数(長さ)が違うことを除けばヘッダ部と同じ構成になっています。2回同じデータが記録されているのも同様です。なお1回目のリードでエラーがなかった場合、その時点でリードが終了しテープがストップします。さすがにそれだとそのまま何かセーブしてしまうと2回目の記録部分が上書きされてしまうので、MZ-80B/2000では自動で次の空白部分まで早送りしてくれるようになりました。
後からよく考えてみれば…という話ではありますが、ヘッダ部・データ部先頭の0の長さといい、インフォメーションの長さといい、冗長というかもっと短くしても何も問題ありませんでしたね。リーダーテープがやたら長いカセットでは10.56秒送ったところでまだリーダーテープが終わらなかったでしょうし、どうせ手でリーダーテープを巻き取りきってからセーブしていた人が多数だったと思われます。
インフォメーションとデータ本体を2回セーブするのは安定性の向上に貢献したとは思いますが、どうせならヘッダ+ヘッダ+データ+データではなくヘッダ+データ+ヘッダ+データとなっていれば良かったですね。1回目のデータが擦り切れてリードエラーになるぐらいの状態なら、2回目のインフォメーションも同様に擦り切れているでしょうから…。
●X1
値 | "1" | "0" | "1" | "1" | インフォメーション | チェックサム | "1" |
長さ | 1000 | 40 | 40 | 1 | 32バイト | 2バイト | 1 |
こちらもまずはヘッダ部。MZと比べて先頭の1の数が慎ましやかになっています。ただし、これに先立ち8秒間の無音時間があることになっています。APSS(自動選曲システム)による頭出しのためです。
インフォメーションは必要最小限の32バイトに詰められました。構成は次のとおり。
オフセット 内容 0 属性 01h=バイナリ、02h=BASICテキスト、03h=シーケンシャルデータ 1~13 ファイル名 14~16 拡張子 17 パスワード 18~19 サイズ 20~21 先頭アドレス 22~23 実行アドレス 24~28 日付 29~31 00h
MZもそうですがインフォメーションはディスクのディレクトリエントリに記録されている内容と全く同じです。
値 | "1" | "0" | "1" | "1" | データ | チェックサム | "1" |
長さ | 3000 | 20 | 20 | 1 | 2バイト | 1 |
そしてデータ部。先頭の1の長さがヘッダ部の3倍になってるのがちょっと不思議なところですね。チェックサムはMZと同じくデータ中の1の総数です。
MZと大きく違うのは2回セーブになっていないことですね。MZもモニタにパッチを当ててデータ部を1回セーブにすることもできましたが…。
計算してみる
では、セーブしたデータがどれだけの時間の長さになるのか計算してみましょうか。公称ボーレート値を用いて、0と1のビット数の割合もそれほど偏っていない(というか公称ボーレートが既に補正された値のようなもの)という前提で概算することもできますが、せっかくビットの長さもフォーマットもわかったのですからここは厳密に全てのビットを数えて正確に算出してみることにします。
計算の対象はMZ-80K/Cの1200ボーです。一応MZ-1500とかまで含まれますが、やはりテープでのオペレーションのみになりがちだった初期MZをターゲットにする方が記憶との比較ということではより意味があると思います。
まずはSP-5030。MZ-80K2/K2Eに添付された標準BASICです。3バージョンありますが新版(12096バイト)を使います。
値 長さ(ビット) 時間(us) 備考 ヘッダ "0" 22000 10560000 "1" 40 38400 "0" 40 19200 "1" 1 960 インフォメーション "0" 573 275040 "1" 579 555840 チェックサム "0" 11 5280 01C3h "1" 7 6720 "1" 1 960 "0" 256 122880 インフォメーション "0" 573 275040 2回目 "1" 579 555840 チェックサム "0" 11 5280 "1" 7 6720 "1" 1 960 小計 24679 12429120 データ "0" 11000 5280000 "1" 20 19200 "0" 20 9600 "1" 1 960 データ "0" 53494 25677120 "1" 55370 41543040 チェックサム "0" 10 4800 A90Ah "1" 8 7680 "1" 1 960 "0" 256 122880 データ "0" 53494 25677120 2回目 "1" 55370 41543040 チェックサム "0" 10 4800 "1" 8 7680 "1" 1 960 小計 351687 145209600 合計 229286 157638720 約2分38秒
だいたい2分半ちょっとといったところですね。これは正確にはセーブにかかる時間で、ヘッダの0連続のあたりからテープをスタートさせることもよくあったでしょうし、エラーなくロードできればデータ部は約半分で終わった(1分以上を残してテープが止まる)はずです。
次は大きなBASICテキストをということで、シャープ純正スタートレック(14391バイト)です。
値 長さ(ビット) 時間(us) 備考 ヘッダ "0" 22000 10560000 "1" 40 38400 "0" 40 19200 "1" 1 960 インフォメーション "0" 550 264000 "1" 602 577920 チェックサム "0" 10 4800 01DAh "1" 8 7680 "1" 1 960 "0" 256 122880 インフォメーション "0" 550 264000 2回目 "1" 602 577920 チェックサム "0" 10 4800 "1" 8 7680 "1" 1 960 小計 24679 12452160 データ "0" 11000 5280000 "1" 20 19200 "0" 20 9600 "1" 1 960 データ "0" 72425 34764000 "1" 57094 54810240 チェックサム "0" 6 2880 A6CFh "1" 12 11520 "1" 1 960 "0" 256 122880 データ "0" 72425 34764000 2回目 "1" 57094 54810240 チェックサム "0" 6 2880 "1" 12 11520 "1" 1 960 小計 270373 184611840 合計 266006 197064000 約3分17秒
BASICより大きいのでこちらは3分ちょっとというところですが、BASICと合わせても6分弱しかかかっていません。データにエラーがなければこちらも1分ほど短縮できたでしょうから、問題なくロードできれば電源を入れてからゲームを始められるまで4~5分しかかからないことになります。
ではもっと大きなプログラム…マッピー(46608バイト)ならどうでしょう?
値 長さ(ビット) 時間(us) 備考 ヘッダ(MAPPY) "0" 22000 10560000 "1" 40 38400 "0" 40 19200 "1" 1 960 インフォメーション "0" 573 275040 "1" 579 555840 チェックサム "0" 11 5280 01C3h "1" 7 6720 "1" 1 960 "0" 256 122880 インフォメーション "0" 573 275040 2回目 "1" 579 555840 チェックサム "0" 11 5280 "1" 7 6720 "1" 1 960 小計 24679 12429120 データ(MAPPY) "0" 11000 5280000 "1" 20 19200 "0" 20 9600 "1" 1 960 データ "0" 217083 104199840 "1" 193173 185446080 チェックサム "0" 12 5760 4085h "1" 6 5760 "1" 1 960 "0" 256 122880 データ "0" 217083 104199840 2回目 "1" 193173 185446080 チェックサム "0" 12 5760 "1" 6 5760 "1" 1 960 小計 954471 584749440 ヘッダ(CRT) "0" 22000 10560000 "1" 40 38400 "0" 40 19200 "1" 1 960 インフォメーション "0" 581 278880 "1" 571 548160 チェックサム "0" 9 4320 01BBh "1" 9 8640 "1" 1 960 "0" 256 122880 インフォメーション "0" 581 278880 2回目 "1" 571 548160 チェックサム "0" 9 4320 "1" 9 8640 "1" 1 960 小計 24679 12423360 データ(CRT) "0" 11000 5280000 "1" 20 19200 "0" 20 9600 "1" 1 960 データ "0" 10642 5108160 "1" 3434 3296640 チェックサム "0" 9 4320 074Eh "1" 9 8640 "1" 1 960 "0" 256 122880 データ "0" 10642 5108160 2回目 "1" 3434 3296640 チェックサム "0" 9 4320 "1" 9 8640 "1" 1 960 小計 39487 22270080 合計 1043316 631872000 約10分32秒
マッピーは本体プログラムとタイトル画面のプログラムとの二段ロード構成になっています。合計するとさすがに10分超えますね(もちろんロードが1回で成功したら5分ほどで完了する)。とは言え、それでも30分には遠く及びません。
逆に、30分かけてロードする場合、どれだけのデータ量になるのか考えてみます。具体的なデータの中身はわかりませんから公称ボーレートの1200ボーだとすると、30分=1800秒ですから1200ボーで送れるのは2160000ビット。1バイトはここでは9ビットで考えますから240000バイト。234KB以上になっちゃいますね。
Z80など主要な8ビットマイコンのメモリ空間は64KB。バンク切り替えしてもっと搭載しているものもありますが、そんな時代のマシンの話をしているわけではありません。いったいMZ-80Kのどこに234KBやそれ以上のメモリを積んでいるというのでしょうか。これまでいろいろがんばって計算してみましたが、実はそんなちょっとした検算で矛盾が発覚するような、シンプルな話だったのです…。
余談ですが、上記の計算値を用いて「実効ボーレート値」を計算してみると、1500~1600ボーやそれ以上のレートになっているようです。「実効」の定義もいろいろありそうで、ヘッダは除くとか2回セーブの時間で1回分の転送と見做すとか考えるとだいぶ落ちてしまいますが…。
未来で抱く思い出のために
30分とか1時間とかじゃなくてもテープの読み書きは決して速くなく、当時の我々が本当に我慢強くパソコンを使っていたのは事実でしょう。初めてフロッピーディスクを体験した時の感動といったら…。
時間がかかったのは事実なんだから、別に数十分とか言ってもたいした問題ではないのでは? と思ってしまいそうになるところですが…いやいや、やはり歴史としてはおろそかにできないと思います。なぜなら、いくらテープの読み書きが遅いといっても、それが理由で当時のパソコンは全く使い物にならなかったわけではなかったからです。
テープの読み書き時間は問題ではなかった…というのではなくて、遅いけれども、まだデータがそれほど大きくなかったので、それなりに実用的に使えた時代ではなかったかと思うのです。そうした時に、どうにか使っていたテープの読み書き時間が、長くても10分なのか、30分や1時間は当たり前なのかは印象を大きく変えてしまいます。当時の先人達(私みたいな子供じゃなくて、PCA会計みたいにいち早く個人向けビジネスにパソコンを応用しようとした人達)がどう思いながらパソコンを使っていたのか、その気持ちを見誤ってしまうでしょう。
長くても10分とは、現代でパソコンを使うことを考えればやはりあり得ないくらい長い時間です。でも「30分や1時間は当たり前だったけど当時としては実用的」などと考えるよりはよっぽど現代の我々の感覚に近いと言えます。パソコンの黎明期にいろいろな経験や実績を積み上げてきた先人のおかげで今がありますが、その先人と我々は地続きでもあるということ、それを理解するためには正確な情報が不可欠であること、これを肝に銘じていきたいと思います。