標準TTLだけ(!)でCPUをつくろう!(組立てキットです!)
(ホントは74HC、CMOSなんだけど…)
[第106回]

●IN命令とOUT命令

IN命令は外部I/O装置からデータを読み込む命令です。
命令コードは”DB”です。
OUT命令は外部I/O装置にデータを出力する命令です。
命令コードは”D3”です。

外部I/O装置と言えば、昔はプリンタかテープレコーダが定番でした。
しかしもっと広く一般的な装置の制御を考えると、さまざまなロジック回路やリレー回路が想定されます。

テープレコーダは外部記憶装置としてはすでに過去のものになってしまいましたが、プリンタは今でも使われています。
もっともそのプリンタもパソコンショップで普通に売られているプリンタは「Windows専用」プリンタになってしまいました。
昔のプリンタはマシン語でデータを出力できたのですが、Windowsプリンタはとてもマシン語では制御できません。
接続するのも以前はパラレルケーブル(セントロニクス接続)が標準でしたが、USB接続が一般的になったので、さらにマシン語での制御が困難になりました。

そんなわけで、CPUの外部装置としては、プリンタやテープレコーダは一般的ではなくなってしまいましたが、そういう特定の装置に限定せず、CPUが外部の回路からなにかの情報を受け取ったり、逆に外部に何かの情報を出力したりするときには、通常はIN命令とOUT命令を使います。

しかし、IN命令やOUT命令を使わなければ外部のI/O回路に対して入出力はできないか、というと決してそんなことはありません。
ごく小規模な回路では、I/O回路をメモリと別の回路にするのではなく、メモリと同じアドレス空間に割り付けて、CPUから見て、あたかもメモリであるかのようにした回路構成とすることもあります(メモリマップドI/O)。

ただ外部回路はメモリに比べてアクセスが低速であることが多いので、そういう回路に対しては、やはりIN命令やOUT命令が有利になってきます。
8080やZ80のIN命令、OUT命令はメモリに対するMOV命令、LD命令に比べて、アクセスのタイミングが1クロック以上遅く設定してあります。さらに低速のI/Oに対してはWAIT信号の入力によって、十分なアクセス時間が得られるように考えられています。

しかしこの「つくるCPU」回路では、そこまでリアルなものを作ることが目的ではありませんから、いわばモデル的なIN、OUT命令の動作となるような回路にしてあります。

●メモリ空間は16ビットですがI/Oは8ビットです

8080やZ80では、メモリに対しては16ビットのアドレスを扱います。
0000〜FFFF(10進数では0〜65535)の範囲になります。
これに対してIN命令、OUT命令は、8ビットのアドレスを扱います。
00〜FF(10進数では0〜255)の範囲です。

アドレスの指定は、OPコードに続く8ビットのアドレスデータで行います。
つまりIN命令、OUT命令はアドレス部を含めると、2バイト長の命令になります。

たとえばI/Oアドレス、”12”に対するIN命令は、マシン語コードでは次のようになります。

DB12  (ニーモニックでは、IN 12)

同じように、I/Oアドレス、”12”に対するOUT命令は次のようになります。

D312  (ニーモニックでは、OUT 12)

アドレスの指定はメモリと別の空間になりますが、ハード的にはメモリと同じ外部アドレスバスを使います。
メモリは16ビットのアドレスを扱いますから外部アドレスバスはA0〜A15になりますが、I/Oの場合は8ビットですから8本のラインで間に合います。

8080やZ80ではI/Oアドレスを指定するのに、アドレスバスの下位8ビットA0〜A7を使います。
上位8ビット、A8〜A15は使いません。
8080やZ80では、IN命令やOUT命令を実行するときに使用されないA8〜A15にCPUのステータス情報などを出力しますが、「つくるCPU」回路では、その機能は考えないことにします。

●メモリとI/Oがぶつかってしまわない理由

メモリが16ビットのアドレス、0000〜FFFFの全てに割り付けられているとすると、I/Oはいったいどこに存在することになるのでしょうか?

実は、その答えは、アドレスバスA0〜A15にあるのではなくて、その外にあります。

今まで個々の命令の回路について、ひとつひとつ説明をしてきましたが、CPUとその外部のメモリやI/Oとの接点についてははっきり説明してきませんでした。
それについては、近いうちにまとめて説明したいと思います。

メモリに対しては、A0〜A15でアドレスを指定しただけでは、データや命令を読むことも、データを書き込むこともできません。

メモリからデータや命令を読むためには、まずA0〜A15にアドレスを出力します。

CPUの外に1個だけのメモリICがある場合にはA0〜A15のアドレス線をメモリICのアドレス端子にそれぞれ接続するだけでよいのですが、メモリICが複数個ある場合には、A0〜A15のうち、上位の何本かのラインを使って、アドレスデコード回路を作り、メモリアドレス範囲を区切って、個々のメモリICに割り当てるようにします。

そのようにしたうえで、メモリからデータや命令を読み出すときには、MEMRD信号をアクティブにします。
逆にメモリにデータを書き込むときには、MEMWR信号をアクティブにします。
MEMRD、MEMWRが非アクティブなときには、メモリからはなにも出力されず、またメモリには何も書き込まれることはありません。

I/Oも、このしくみをそのまま利用しています。
メモリと同じA0〜A7をI/O回路に割り当てます(必要ならばメモリと同様に、アドレスデコード回路によって、複数のI/Oに別々のI/Oアドレスを割り当てます)。

IN命令は、A0〜A7にアドレスを出力したあとで、IORDをアクティブにします。
OUT命令では、A0〜A7にアドレス(およびD0〜D7にデータ)を出力したあとで、IOWRをアクティブにします。
IORD、IOWRが非アクティブなときには、I/Oからはなにも出力されず、またI/Oになにかが出力されることはありません。

このように、MEMRD、MEMWR、IORD、IOWRをアクティブ、非アクティブにすることによって、メモリやI/Oがぶつからないようにコントロールすることが可能になります。

8080ではMEMRD、MEMWR、IORD、IOWRという出力信号はなくて、メモリやI/Oをアクセスするための外部回路を別に必要としていました。
Z80では8080に比べてずっとシンプルになりましたが、それでもMREQ信号、IORQ信号とRD、WRから各信号を作り出す必要がありました。
しかしそんな面倒くさいところまで、オリジナルに似せてみても全く何のメリットもありませんから、「つくるCPU」では、上記の4つの信号によって、メモリ、I/Oのアクセスを行うように考えました。

●IN命令とOUT命令はAレジスタだけ

8080には汎用のレジスタとしてA、B、C、D、E、H、Lの7個の8ビットレジスタがあります。
IN命令を実行すると、I/Oから読み込まれたデータ(8ビット)はAレジスタに入れられます。
しかし他のレジスタに読み込むことはできません。
I/Oからの入力データを他のレジスタに入れたいときには、IN命令で一度Aレジスタに読み込んでから、MOV命令で他のレジスタに移すようにします。

OUT命令は、Aレジスタの値をI/Oに出力します。
他のレジスタの値をI/Oに出力したいときは、MOV命令で他のレジスタからAレジスタに値を移してから、OUT命令を実行します。

なおZ80には、Aレジスタ以外のレジスタとI/Oとの間での入出力をするための、IN、OUT命令があります。

●IN命令とOUT命令のタイミングチャートです



IN命令とOUT命令は前半は同じです。
前半のM2(T4、T5)のタイミングで、命令の次の8ビットのデータ(I/Oアドレス)をWKLレジスタに読み込みます。
d3〜d0=”0111”でregWRをアクティブにすることで、WKLレジスタが選択され、値が書き込まれます。
d3〜d0=”0111”はd3=0にするだけです。
d3〜d0とレジスタの関係については、「レジスタコード表」([第27回])を参照してください。

次のM3(T6、T7)の期間に、WKレジスタの値を外部アドレスバスA0〜A15に出力します。
WKレジスタの下位8ビット(WKL)にしかアドレスデータを書き込んでいませんから、A8〜A15には何が出力されるか不定です(その前に実行された、WKレジスタを使う命令によって書き込まれた値が出力されます)。
IN、OUT命令ではA8〜A15は使用されませんから、気にする必要はありません。

IN命令ではIORDをアクティブにしてデータバスからI/Oデータを読み込み、regWRをアクティブにして、Aレジスタに書き込みます。
Aレジスタはd3〜d0=”1111”で選択されますから、d信号については、なにもする必要はありません。ただregWRをアクティブにするだけで、Aレジスタに内部データバスの値が書き込まれます。

OUT命令では、regRDをアクティブにして、Aレジスタからデータをデータバスに出力したうえで、IOWRをアクティブにします。

●IN命令とOUT命令の回路図です



IOWR回路にはフリップフロップがあります。

I/O回路はメモリ回路とは異なり、バスに直接接続することはあまり一般的ではありません。
ターゲットの回路とCPUの間になんらかのインターフェース回路が入ります。
8080やZ80では、その目的では8255(82C55)がよく使われます。
82C55も初期のものに比べるとアクセスタイムはかなり速くなったのですが、それでもメモリに比べるとまだまだ遅い感じがします。

メモリも昔はアクセスタイムが遅いものしか入手できませんでした。
最近では50ns〜70nsというTTLなみの速度のものが、ごく普通に入手できるようになりました。
そこでMEMWRについては、regWRと同じタイミングにしたのですが、さすがにI/Oに対しても同じタイミングにする、というわけにはいきません。

MEMWRはTクロックの半分の長さのパルス幅にしたのですが、IOWRはその倍は必要です。ということはTクロック幅です。

82C55に対しては、IOWR信号は、アドレスバス信号やデータバス信号の立上り、立下りの間にきちんと入っている必要があります。
ちょっと考えると、regWRのように、CLKとT7、T6とのANDをとって、下図の回路にすればよいように思えます。

しかし、これはちょっとまずいのです。

クロックとT6、T7に遅れがなければよいのですが、それはありえません。
CLKとT6、T7には少なくとも50nsていどの遅れが生じます。
この遅れのために、特にCLKとT7とのANDでは困ったことがおきてしまいます。
そのことをタイミングチャートで示します。

*印をつけた、幅のせまいパルスが2箇所で発生する可能性があります。
このままでは、ひょっとするとI/Oへの書き込みが正しく行われない可能性があります。

こういう場合には、コンデンサでつぶすということもひとつの解決策ですが、せっかく数百個もICを使っていながら、こんなところでコンデンサを使ってごまかすというのも芸の無い話に思えますので、フリップフロップを使った回路に落ち着きました。

さきほどの回路図のフリップフロップ回路の部分だけを下に示します。


M3信号がCLR端子に入っているため、フリップフロップからのIOWR出力は、OUT命令のM3の期間(T6、T7の期間)以外はHになっています。

CLKがインバートされてフリップフロップのCKになっていますから、T6の中央とT7の中央の2度、D入力をラッチします。
D入力はQA信号をインバートして入力しています。
QAはT6のときはL、T7のときはHです。D入力はその反転信号になりますが、IOWRはさらにそのD入力の反転出力ですから、結局フリップフロップのCK入力時のQAの状態がそのままIOWR出力になります。

最初のCK入力はT6の中央近くのときで、QAはLですから、このタイミングでIOWR出力はLになります。
そして次のCK入力はT7の中央近くで、QAはHですから、このタイミングでIOWRはHになります。
2008.11.4upload

前へ
次へ
ホームページトップへ戻る