往年の 8ビット・マイクロプロセッサー Z80。 最近の若い IT エンジニアだと知らない人も多い? 現在でも組み込み用途で使われているのに、 プログラマの高齢化が進んでいるらしい。 私がコンピュータを学んだ思い出深い CPU なので、 このまま忘れ去ってしまうのもモッタイナイ。 思い出せる限り記録に残しておこうと思う。 コンピュータを原理から学ぼうとする人の参考になれば幸い。
以下は、私が大学一回生のとき (1986年, 昭和61年) 独学で作った CP/M (Control Program for Microcomputer, パソコン用シングルタスク OS) マシンの記録。
私は高校生のとき (1983年)、 シャープ製パソコン MZ-80K2E を改造しながら独力でデジタル回路を学んだ。 当時のコンピュータ雑誌 (工学社 「I/O」 誌) に掲載された MZ-80K の回路図が大いに参考になった。 1983年版 TTL IC 規格表を片手に MZ-80K の回路図を読み解いて、 コンピュータの仕組みを学んだ。
受験勉強そっちのけでデジタル回路の勉強にのめり込んでしまったので一浪する (1985年) はめになったが、 無事大学に合格した後はたっぷり時間があったので、 Z80 コンピュータを作ってみた。 MZ-80 に倣って当時これを HZ-80 と呼んでいたので、 以下 HZ-80 と呼ぶ。
HZ-80 にはディスプレイもキーボードも無いので、 改造した MZ-80K2E とパラレル通信ケーブルで結び、 MZ-80K2E を端末として使用した。 写真 ↑ 中央に見える黒色の 40ピン ソケットに、 パラレル通信ケーブルをつなぐ。 MZ-80K シリーズは画面が横 40文字しか表示できないので、 (MZ 本体とは別に) 80文字表示できるビデオ信号生成回路を作り、 MZ-80K2E の CRT へ出力して、 80桁x25行キャラクタ表示端末として使っていた。
写真左手に見える 36ピン アンフェノール ソケット (セントロニクス仕様のプリンタ用と同じ形状) に、 (PC-8001/8801シリーズ用の) 5インチ フロッピー ディスク ドライブ (以下 FDD と略記) をつなぐ。
写真右手に見える赤と黒の端子に +5V のスイッチング電源をつなぐ。
写真から見て取れるように、 HZ-80 は ケージに収めた 5枚の基板から構成されている。 各基板を外して並べてみた (写真 ↓)。
上段左から、 FDD インタフェース基板、 6809 基板 (6809 マシン, 後述)、 I/O 基板 (パラレル通信インタフェース)。 下段左から D-RAM 基板、 そして Z80 CPU 基板。
Z80 CPU 基板
コンピュータ全体をいっぺんに設計し、 半田付けして一発で動かせるとは (もちろん) 思っていなかったので、 可能な限り小さい回路で動作確認しつつ、 徐々に大きな回路に組み上げていき、 最終的に CP/M が動くマシンを作ることを目指した。 すなわち、 この Z80 CPU 基板は、 (電源をつなげば) この基板単体で (ケージに入れなくても) 動作する (写真 ↓ 裏面)。
動作確認手段としては赤色LED (写真右上隅) が一つあるのみだが、 LED を一定パターンで点滅させるプログラムを走らせることによって、 少なくとも正常にプログラムが実行できていることを確認することはできる。 厳密な動作確認は、 コンピュータ全体が完成し、 プログラムの開発環境が整ってからやればよい。
写真右側、1.4A と書かれた紙が貼ってある LSI が、 UV EPROM 2764。 「UV」 すなわち紫外線 (Ultra Violet) で消去可能 (Erasable) な 64kビット (つまり 8k バイト) Programmable ROM。 黒いビニールテープが貼ってあるが、 これを剥がすと IC チップ表面を覗けるガラス窓があり、 そこに紫外線を照射すると記憶内容を消去できる (全バイト 0xFF になる)。
ROM writer (高校生の時に自作した) を使って ROM にプログラムを書込み (2764 の場合 21V の電圧を印加すると書込める。 書き込みは 1 → 0 方向のみ可能で、 0 → 1 は紫外線でないとできない)、 この Z80 CPU 基板に差し込んで実行する。 プログラムを書き換えるたびに、 ROM を抜く → 殺菌灯を仕込んだ箱 (これも自作) に入れて紫外線を 20分ほど照射 → ROM writer にセットして新しいプログラムを書込む → ROM を差す、 という作業が必要になる (1バイト書き換えるだけでもトータル 1時間くらいかかる)。
ちなみに、 紫外線をあてなくても任意のバイトを 0x00 にすることと、 0xFF なバイトを任意の数値に書き換えることはできる (どちらも 1 → 0 方向だから) ので、 古いプログラムを 0x00 で書きつぶし、 その後の 0xFF の領域に新しいプログラムを書くことはできる (紫外線で消去する時間を節約できる)。 Z80 では 0x00 は NOP 命令 (No Operation, 何もしない命令) なので、 書きつぶした古いプログラムの領域をスルーして、 新しいプログラムが実行される。
なお、 プログラムの開発は、 キャリーラボ製 Z80 アセンブラ 「BASE-80」 を用いて MZ-80K2E 上で行なった。
昔の (1980年前後) パソコン雑誌には、 BASIC などで書いたソースコードや、 実行形式のバイナリデータが数多く掲載されていた。 読者は何ページにもわたるプログラムを大変な労力をかけてパソコンに入力する。 バイナリデータは 16進ダンプで 256バイトのブロックごとに縦横 16バイトずつのチェックサムが付いていた。 チェックサムのおかげで入力したデータにミスがないか確認できたが、 BASIC などで書かれたソースコードの場合はどうやって入力ミスを修正していたのだろう? 私は滅多に入力しなかったが、 数少ない例外がこの BASE-80 だった。 当時高校生だった私にとって、 シャープ純正の開発環境を買い揃える余裕はなく、 雑誌を買うだけで入手できた BASE-80 は非常に有難かった。 このあたりの話は、 日経Linux 連載の枕にも書いた。
ROM の左隣にある大きな LSI が Z80A CPU で、 その下が 8MHz 水晶発振子。 LED の下にある押しボタンはリセットスイッチ。 配線はラッピング用ワイヤを半田付けしている。
当時書いた手書きの回路図が残っていた ↓ 。 日付 (昭和61年 = 1986年) は設計した日。 この日付以降に半田付けを開始して、 動作確認できるようになるまで一週間以上 (一ヶ月以上? ^^;) かかった。
TTL の多くは、IC パッケージの中に複数のゲートが入っている。 例えば LS04, LS05, LS14 は、 それぞれ NOT ゲートが 6個ずつ入っているし、 LS260 は AND ゲート が 2個入ってる。 基板上の IC 数を減らすため、 できるだけ全てのゲートを使いきるようにした。 例えば、 LED を光らせるためにオープンコレクタな LS05 を用いたので、 残りの NOT ゲート 4個を使って、 ワイヤード AND ゲートを構成している。
「AC15」 など、 添字 「C」 付の信号線は Z80 CPU の信号線。 「KILL」 は、 D-RAM 基板 (後述) 上の RAM を無効にするためのエッジコネクタ (後述) 信号線。 KILL をアサート (この信号線は負論理なので低電圧が 「真」 状態) すると、 D-RAM 基板のデータバスがハイ・インピーダンスになるので、 データバスを RAM 以外が利用できるようになる。
Z80 CPU のメモリ空間は 64KB しかないので、 32KB ずつメモリバンクを切り替えている。 すなわち、 I/O 0x00番地に書込んだ 8bit データ D7〜0 のうち、 D7〜5 (第7bit から第5bit までの上位3bit) が Z80 の高位 32KB のメモリ空間 (0x8000〜0xFFFF) に、 D4〜2 が低位 32KB のメモリ空間 (0x0000〜0x7FFF) に、 それぞれ割当てられる物理メモリのアドレス A17〜A15 になる。 また、 D1 が 0 のとき、低位 8KB のメモリ空間 (0x0000〜0x1FFF) に CPU 基板上の ROM (8KB UV-EPROM) が割当てられる。 さらに、 D0 (最下位ビット) の値 1/0 が LED の点灯/消灯に対応する (D0 が 1 のとき点灯、0 のとき消灯)。
クロックは、 8MHz の水晶発振子を分周することで CPU の 4MHz クロックを生成している。
アドレスバス A14〜A0 およびデータバス D7〜D0 など、 CPU の信号線のほとんどをそのままエッジコネクタに接続している。 各基板のエッジコネクタは、 ケージのソケットを介して互いに接続される。 エッジコネクタの端子接続表 ↓
IEI は Interrupt Enable In の略で、 この信号線がアサート (High レベル) されているときのみ、 優先順位の低いデバイスは割り込みを要求できる。 現代の感覚からすると信じがたいが、 Z80 の時代は、 割り込みの優先順位は Hard Wired Logic だった。
D-RAM 基板
CPU 基板だけでは書き換え可能なメモリが無いので、 プログラムを書くことすらままならない。 この手の簡易なコンピュータでは、 まず小容量の Static RAM を搭載するのが普通だが、 TTL IC の数を減らすため、 小容量の RAM を使わず、 いきなり (当時としては ;-) 大容量の 256Kbit D-RAM を使うことにした (写真 ↓ 裏面)。
大阪日本橋で見つけたメモリーモジュール基板を使って 2階建てにしている (写真 ↓ 反対側)。 D-RAM LSI は両基板に挟まれて見えないが、 三菱の 256Kbit D-RAM M5M4256P-12 を 8個使用している (つまり合計 256KB)。
ラッピング用ワイヤを一本一本半田付けするのは大変な手間なので、 このモジュール基板のおかげで大変助かった。
D-RAM LSI は一般にピン数を減らすため、 アドレス線を上位/下位ビットに分けて入力する。 上位 (Row) ビットを入力するときは RAS (Row Address Strobe) をアサートし、 下位 (Column) ビットを入力するときは CAS (Column Address Strobe) をアサートする。 さらに D-RAM はリフレッシュ用のアドレスを与える必要があってややこしい。 D-RAM 基板はタイミングチャート ↓ を作成した上で設計した。
D-RAM タイミング回路 ↓ 。 LS157 (2 to 1 Data Selectors) が、 MREQ がアサートされた後のクロックの立ち上がりエッジで、 アドレス線を上位ビットから下位ビットへ切り替える。 「Ar8」 など、 添字 「r」 付の信号線は D-RAM の信号線。 KILL がアサートされている場合は、 CAS をアサートしないので、 D-RAM のデータ線がハイインピーダンスになる。
Z80 は 7bit 分のリフレッシュアドレス (A6〜A0) しか出力しないので、 カウンタを使って 1bit 分 (A7R) を追加生成する。 カウントアップが変則的なのは、TTL IC を節約するため。
簡易なメモリチェックプログラムを (CPU 基板の) ROM に書込んで、 D-RAM の動作確認を行なった。 出力は相変わらず LED のみであるが、 (普通の) AM ラジオを使うと、 プログラムの動作状況が (なんとなく) 確認できる。 すなわち、 正常にメモリチェックプログラムが動いている場合と、 暴走してデタラメに動いている場合とでは (漏洩する) 電波のパターンが異なるので、 ラジオから聞こえるノイズで区別することが可能。
簡易コンソール基板
以上で、 コンピュータとしての最低限の機能は揃ったので、 次に必要なのは端末 (キーボード & ディスプレイ) との通信機能。 といっても、 当時の私は端末なんて持ってない。 パソコンに通信機能 (RS-232C インタフェース等) が標準搭載されるようになるのはもうちょっと後の話。 きちんと通信できる端末がないとデバッグもままならないので、 簡易な端末を作ってみた (写真 ↓ 裏面)。
7セグと押しボタンを並べただけ。 あまりに簡易すぎて回路図も残ってない (というか回路図も書かずに作ったような記憶が... ^^;)。 4個の 7セグと、4列の 16進キーの中から 1個と 1列を、 TTL LS139 (2 to 4 Demultiplexer) で選んで点灯 & キー入力する仕組み。 つまり一度に点灯できるのは 1個の 7セグだけだが、 10msec 程度の間隔で順に点灯させることにより、 同時に光っているように見える (ダイナミック点灯)。 パラレル通信線は 16本あって、 うち 2本を LS139 の入力に、 うち 8本を 1個の 7セグを点灯するために、 うち 4本を 16進キー 4個の状態の読み取りに、 それぞれ使う。 すると 2本余るので、 白と赤の押しボタン (写真左下隅) を追加したのだろう。
パラレル通信インタフェースのデバッグのために作った簡易コンソールだが、 プログラムを 16進で入力したり、 メモリの状態を 7セグに表示させたりする役にも立った。
I/O 基板
続いて作ったのが、 パラレル通信のための I/O 基板 (写真 ↓ 裏面)。
LSI が 3つ並んでいるが、 一番右がパラレル通信用の Z80 PIO (Parallel I/O)。 16本の入出力線を持っているので、 これをそのまま 40ピン ソケットにつないでパラレル通信線にする。
その隣が時間を測るための Z80 CTC (Counter Timer Circuit)。 一定間隔で Z80 CPU に割り込みをかけて、 時計機能を実装したり、 あるいは前述した簡易コンソールの 7セグをダイナミック点灯させたりするのに使う。
一番左の LSI が Z80 DMA (Direct Memory Access Controller)。
TTL IC が 2つしか載ってないことからも分かる通り、 回路図 ↓ はいたって簡単。
3 to 8 Demultiplexer LS138 を使って、 I/O 0x10〜0x13 番地をアクセスすると Z80 DMA へ、 0x14〜0x17 番地をアクセスすると Z80 PIO へ、 0x18〜0x1B 番地をアクセスすると Z80 CTC へ、 それぞれアクセスできる。
40pin コネクタ端子接続表 ↓ 。 Z80 PIO の 16本の入出力ピンをそのまま出しているだけ。
この I/O 基板を、 前述した簡易コンソール基板と 40芯フラットケーブルで結んだ。 Z80 CTC を使って 10msec の間隔で Z80 CPU に割り込みをかけ、 7セグをダイナミック点灯させるプログラムを書いた。 さらに、 機械語モニタを書いて、 16進キーでプログラムを入力して実行させたり、 メモリの内容を 7セグに表示できるようにした。 この段階で、 TK-80 (RAM 1KB) とほぼ同等のことが実現できた (カセットテープレコーダにプログラムを保存することはできないが)。
そして、 同様のパラレル通信機能を Z80 PIO を使って MZ-80K2E 側にも作り込み、 MZ-80K2E を端末として HZ-80 への入出力に使うことができるようになった。 単にキーボードから入力/ディスプレイへ出力できるようになったというだけでなく、 MZ-80K2E 上で BASE-80 を用いて開発したプログラムを、 ROM に書込まずに実行できるようになったという点で、 開発効率が劇的に向上した。
FDD インタフェース基板
プログラムを入力できるようになったといっても、 それを保存する手段がないので電源を切れば消えてしまう。 そこで次に FDD (フロッピー ディスク ドライブ) をつなぐことにした。 当時 FDD は高価だったが、 幸い PC-8001/8801シリーズ用のミニ ディスク ユニット PC-80S31 の中古品を 5万円程度で入手することができた (定価は 168,000円)。 PC-80S31 は 5.25インチ 2D (両面倍密度) で、 ディスク一枚あたり 320KB の容量があった。
5万円といえど当時大学生だった (入学直後でまだアルバイトもしていなかった) 私には大金で、 果たして HZ-80 につないで活用できるのか不安だったが、 フロッピディスク入門ハンドブック (秀和システム 1984年発行) にインタフェース仕様の詳細が解説されていて、 FDD インタフェース基板を作ることができそうだったので、 大阪日本橋で思い切って購入した。
PC-80S31 は内部に専用の CPU を持ち、 コンピュータ本体とパラレル通信するインテリジェント タイプ。 Intel 8255 (PPI, Programmable Peripheral Interface) で通信する仕様なので、 8255 を使ったインタフェース基板を作った (写真 ↓ 裏面)。
I/O デコードするためだけに TTL IC を 3個も使っていて、 しかも LS32 と LS04 は半分以上のゲートが未使用だが、 この基板の残りのエリアに RS-232C 機能を実装する予定で、 その時に残りのゲートを使用すればよいと考えていたため。
こうして FDD と通信ができるようになり、 MZ-80K2E で開発したプログラムを、 フロッピー ディスクに保存できるようになった。 といっても、 「ファイル」 などの概念はなく、 単に保存先のセクタ番号を指定して読み書きする方式。 セクタ番号を手で管理する (「紙の」ノートに、どのセクタに何を書込んだか手書きで記入していた) 必要があるものの、 それまでプログラムの保存はカセットテープで行なっていたわけで、 save/load 時間が劇的に短縮された。
これ以降、 CP/M を動かすための BIOS の開発に注力した。 1986年の秋までには CP/M を動かすことに成功し、 インターリーブやトラックバッファなどの手法を用いたディスク入出力の高速化に熱中した。 HZ-80 のメモリバンク切り替え機能を活用して BIOS を裏バンクへまわし、 CP/M の TPA (Transient Program Area) を最大化した。 さらに、 通常はフロッピー・ディスクのシステム領域から読み込まれる CCP (Console Command Processor, コマンド・シェル) と BDOS (Basic Disk Operating System, CP/M 本体) を ROM 内に保存することにより、 CP/M の起動と (コマンド終了毎に行なわれる) 再ロードを、 ほぼ瞬時に行なえるようにして、 操作性を圧倒的に向上させたりした。 当時 WWW があったら、 ブログネタには事欠かなかっただろう。
なお、 回路図 ↑ に 「RS232C (8251 を用いる) を実装する予定」 とメモ書きがあるが、 結局実装はしなかった。 この基板に RS-232C 機能を追加すること自体は容易だが、 RS-232C で通信する端末をどうやって調達するか、 あるいは自作するか、 迷っているうちにソフトウェア開発のアルバイトを始めてしまい (1987年)、 興味がそちらへ移ってしまったため。
6809 基板
CP/M で一通り遊んだ後、 (FM-7 を持っていた友人から影響を受けて) OS-9 に興味を持ってしまい、 6809 基板を作った (写真 ↓ 裏面)。
HZ-80 のケージには空きが 1つしか無かったため、 6809 MPU と 256KB RAM および HZ-80 との通信機能を一枚の基板に詰め込むことにした。 写真から分かる通り、 HZ-80 の他の基板と比べ実装密度が高い。 1986年11月に設計を始めたが、 何度も設計をやり直したため、 最終的に設計を固めて半田付けを開始したのは年が明けてから。
6809 は Z80 のようにリフレッシュアドレスを出力したりはしないので、 D-RAM 内部の CAS before RAS self refresh 回路を利用することにした。 すなわち、 RAS をアサートするより早く CAS をアサートすると (タイミングチャート ↓ 破線部分)、 D-RAM 内部でリフレッシュアドレスが順に生成されリフレッシュが行なわれる。
タイミングチャート ↑ 右上隅に 「没」 と書いてあるのは、 これを書いた後に設計のやり直しをしたから。 最終版のタイミングチャートは残ってない。 最終版はタイミングチャートも書かずに設計したのかも? (^^;)
6809 は Z80 とは異なりメモリマップドI/O であり、 Z80 の MREQ のようなメモリアクセスを明示する信号線はなく、 Qクロック (Quadrature Clock, Eクロックより位相が 90度先行するクロック) の立ち上がりエッジでアドレスバスが有効になる。 6809 がメモリアクセスを行なわないときは、 アドレスバスに 0xFFFF が出力される。
そこで、 A4 以外が 1 (つまり 0xFFEF か 0xFFFF) のとき、 CAS を (RAS より早く) アサートして (CASref 信号線) D-RAM のセルフリフレッシュを行なう。 つまり、 A4 が 1 (0xFFFF) のときは、 6809 がメモリアクセスを行なわないので D-RAM へのアクセスは必要ないし、 A4 が 0 (0xFFEF) のときは、 Z80 PIO の入出力線への I/O を行なう (ので D-RAM へのアクセスは必要ない)。
RAS は Eクロック (Enable Clock) の立ち上がりに合わせてアサートされるが、 アドレスバスは (Eクロックより位相が 90度先行する) Qクロックの立ち上がりのタイミングで安定しているので、 1/4 クロック (125ns) の分だけ無駄になっている。 RAS が遅いおかげで、 CAS を RAS より先に余裕を持ってアサートできるわけであるが。
D-RAM のアドレス入力を上位ビットから下位ビットに切り替えるために LS158 (2 to 1 Data Selectors) を使っている。 HZ-80 の D-RAM 基板では LS157 を使っていたのに対し、 LS158 は出力が負論理。 D-RAM のアドレス入力は正論理でも負論理でも構わないが、 Data Selectors の出力に LS04 (NOT ゲートなので論理が反転する) を挟んで D-RAM の CAS 入力がアサートされるのを少しでも遅延させたかったのだろう。 LS04 と LS02 を介すことによって 19ns 程度の遅延が生じ、 下位ビットへ切り替わったアドレス入力が安定するまでの時間を確保することができる。
HZ-80 と同様、 32KB ずつメモリバンクを切り替えている。 高位 32KB のメモリ空間 (0x8000〜0xFFFF) は、 固定的に 0x38FFF〜0x3FFFF の物理メモリが割当てられるが、 下位 32KB のメモリ空間 (0x0000〜0x7FFF) は、 A17〜A15 を HZ-80 側から設定できる (回路図 ↑ SEG2P〜SEG0P)。 TTL IC の数を節約するためとはいえ、 6809 が自力で自身のメモリバンクを切り替えることができないので、 少し (かなり? ^^;) 不便。
(メモリマップド) I/O 番地 0xFFEF へデータを書込むと、 回路図 ↓ PIOWRP がアサートされ、 Z80 PIO がパラレル通信線 AD7〜0 の 7bit データ、 すなわち 6809 のデータバスを読み取る。 逆に 0xFFEF からデータを読もうとすると、 PIORDP がアサートされ、 Z80 PIO がパラレル通信線 AD7〜0 すなわち 6809 のデータバスへ出力するので、 6809 がそのデータを読むことができる。
つまり 6809 のデータバスが、 HZ-80 とのパラレル通信線と直結している。 無茶な設計だなぁと当時も思いつつ設計したのであるが、 きちんと動いたので自分でも驚いた。
6809 マシン丸ごと、 しかも HZ-80 側の Z80 PIO (およびそのデコード部) も含めて、 わずか 12個の TTL IC で実現したのは記録的ではなかろうか (自画自賛 ;-)。 この 6809 基板は 6809 を走らせることに自体に関しては成功したが、 OS-9 を走らせることはできなかった。 割り込みを HZ-80 との通信に固定的に割当ててしまったため。 マルチタスク OS にはタイムスライスのために割り込みが必要なんて、 当たり前のことなのに... (>_<)
OS-9 を動かすべく改修する計画もあったのだけど、 大学 2回生になって (1987年) 始めたソフトウェア開発のアルバイトのほうに熱中するようになって、 FDD インタフェース基板上の RS-232C 機能と同様、 作らずじまいになってしまった。