上はRCA社のCDP1802BCEで、4 - 6.5V動作、5 MHzクロック動作の高速品です。鈴木様よりの頂きものです。下はIntersil社のCDP1802ACEで4
- 6.5V動作、3.2 MHzクロック動作のもの。現在も製造されているのですね。共に-40度から+85度で動作と、使用温度範囲が広いのも特徴です。
CDP1802のプログラマから見たレジスタモデルはこのようになっています。
RCAの資料と少し異なるところがありますけど。
目立つのはR(0).1からR(F).1とR(0).0からR(F).0の32個の8 bitレジスタです。この表示の.0と.1は、それぞれ16
bitデータの下位と上位を表す記法です。つまり、本来はR(0)という16 bitレジスタの下位8
bitをR(0).0、上位8 bitをR(0).1と表記しているわけです。結局16 bitレジスタとしては、R(0)からR(F)の16個が内蔵されています。1970年代のマイクロプロセッサとしてはずいぶん大量の内蔵レジスタです。基本的に16個のレジスタは同等ですが、R(0)とR(2)だけは特別な機能が割り当てられています。データの一時退避にも使えますが、アドレスを保持するポインタレジスタとしての役割が重要です。
Dレジスタは一般のマイクロプロセッサのアキュムレータに相当するレジスタで、データレジスタとして演算操作の中心となります。DFは一般のマイクロプロセッサのキャリーフラグに相当する1
bitのフラグです。
Nレジスタは図に含めるかどうするか悩みましたが 、命令解説時にあったほうが良さそうなので、書き入れておきました。常に実行中の命令の下位4
bitが入っているレジスタです。命令実行のたびに書き換えられます。なお、RCAのドキュメントには実行中の命令の上位4
bitが入れられるIレジスタがありますが、プログラマからは特に意識しなくてもかまわないので、省略しました。
XレジスタとPレジスタはそれぞれ4 bit幅のレジスタです。特別な命令によって値を設定できます。また、割り込み時にはTレジスタにX,
Pレジスタ対の内容が転送され、保存されます。
QフラグはプロセッサのQ出力端子に直結しているフラグで、要は1 bitの出力ポートのレジスタです。
IEフラグは割り込みイネーブルです。
さて、このレジスタセットには、他のプロセッサには見られない特徴があります。というか、他のプロセッサには見られるけれど、このレジスタセットには見られないレジスタ類があります。まず、プログラムカウンタが見当たりません。スタックポインタもどこにもありません。そういえば、ゼロフラグとかオーバーフローフラグなどの条件判定用フラグ類も事実上ありませんね。
プログラムカウンタやスタックポインタがないかわりにR(0) - R(F)といったレジスタ群が多数存在するのが、COSMACのアーキテクチャの特徴です。
プログラムカウンタの役目はPレジスタの指すポインタレジスタが果たします。たとえば、Pレジスタに3が入っていればR(3)がプログラムカウンタです。つまりR(P)が常にプログラムカウンタとして動作するわけです。すると、サブルーチン呼び出しは、たとえばR(4)に前もってサブルーチンの先頭アドレスを格納しておけば、Pに4を設定することによって実現できます。そのとたんにR(4)が新たなプログラムカウンタとして使われるわけですから。直前のPレジスタの内容が3なら、R(3)にサブルーチン呼び出し時のプログラムカウンタの内容がそのまま保持されています。ですから、Pに3を設定すれば、サブルーチンから戻ることができます。これならスタックポインタがなくても、サブルーチン呼び出しが可能ですね。数レベルのサブルーチン呼び出しなら、内蔵アドレスレジスタだけでも何とかなります。さらに多重レベルのサブルーチン呼び出しを行いたいなら、プログラムで以前のプログラムカウンタの内容をメモリに退避する必要があります。同様にしてコルーチン呼び出しも可能です。
同様にXレジスタで指すポインタレジスタR(X)によって、メモリ上のデータを一種のポインタアドレッシングで参照することもできます。また、Nレジスタで指すポインタレジスタR(N)もメモリ上のデータ参照に利用できます。このようにして、メモリ上のデータをポイントしながらデータ処理するように考えられている点にCOSMACアーキテクチャの特徴があります。
これから命令を見ていきますが、その前に命令動作を説明するための表記法を説明します。
まず、ポインタレジスタはR(0)、その上位バイトをR(0).1、下位バイトをR(0).0のように表記するのはレジスタの説明のときにも書きました。そのポインタレジスタでPレジスタに指されたものをR(P)、Xレジスタに指されたポインタレジスタをR(X)、Nレジスタ、つまり当該命令の下位4
bitで指示されたポインタレジスタをR(N)と表記します。
特定のアドレスnのメモリをM(n)と表記することにします。すると、R(0)で指されたメモリはM(R(0))となります。また、命令をフェッチするメモリはM(R(P))となりますね。
では、メモリとデータレジスタの間のデータ転送命令から説明します。
Instruction
Mne. OpCode 動作
Load via N
LDN 0N 1 2 M(R(N)) -> D (Nは0以外)
Load Advance
LDA 4N 1 2 M(R(N)) -> D, R(N)++
Load via X
LDX F0 1 2 M(R(X)) -> D
Load via X and Advance
LDXA 72 1 2 M(R(X)) -> D, R(X)++
Load Immediate
LDI F8 2 2 M(R(P)) -> D, R(P)++
Store via N
STR 5N 1 2 D -> M(R(N))
Store via X and Decrement STXD
73 1 2 D -> M(R(X)), R(X)--
オペコードの欄は、左が実際のオペコード、中央が命令のバイト数、右がサイクル数です。1サイクルは8クロックで構成されます。Nでポインタレジスタを指定する場合、オペコードの下位4
bitにそのNを入れます。
ロードとストアで機能が非対称だったり、XとNでも扱いが異なる部分があったり、覚えにくいところがあります。
一種のオートインクリメント付きのロード命令がありますが、イミディエートモードの動作と比べれば、PをXやNに置き換えただけで、プロセッサ内部の回路にはそれほど負担にならないであろうと推測できます。
ストアのほうが命令の種類が少なく、単純なストアはNで指示するものしかなく、オートデクリメント相当のものはXで指示するものしかありません。これでは、単純なメモリのブロックコピーにも不便しそうですが、すぐ後に紹介する命令にポインタレジスタのインクリメントやデクリメント命令がありますから、問題はありません。オートインクリメントやデクリメント機能付のデータ転送命令は、便利な複合命令として使えるときに使うだけで充分です。
次はポインタレジスタを操作する命令です。
Instruction
Mne. OpCode 動作
Increment Reg. N
INC 1N 1 2 R(N)++
Decrement Reg. N
DEC 2N 1 2 R(N)--
Increment Reg. X
IRX 60 1 2 R(X)++
Get Low Reg. N
GLO 8N 1 2 R(N).0 -> D
Put Low Reg. N
PLO AN 1 2 D -> R(N).0
Get High Reg. N
GHI 9N 1 2 R(N).1 -> D
Put High Reg. N
PHI BN 1 2 D -> R(N).1
ポインタレジスタをインクリメントしたりデクリメントする命令があります。Xで指示したポインタレジスタをデクリメントする命令はありませんが、そのポインタレジスタを使ってストアする場面ならSTXD命令のデクリメント機能で代用できます。というか、STXD命令を使えるようにプログラムロジックを組むのがコツというべきでしょうか。
ほかにDレジスタとポインタレジスタの間でのデータ転送命令があります。もちろんDレジスタは8
bit幅ですからポインタレジスタの上位下位別々に転送することになります。
ポインタレジスタに関してはこれ以外の命令はありません。ですから、ポインタレジスタにアドレス定数をセットする場合にはLDI命令でDレジスタに定数をセットしてからPLO命令やPHI命令でポインタレジスタに転送する手順を踏まなくてはなりません。同様にインデックスアドレッシングと同じことを行いたい場合には、ポインタレジスタの内容を一度Dレジスタに転送してから加減算を行い、再度ポインタレジスタに転送しなおす必要があります。
次は論理・シフト演算です。
Instruction
Mne. OpCode 動作
Or
OR F1 1 2 M(R(X)) | D -> D
Or Immediate
ORI F9 2 2 M(R(P)) | D -> D, R(P)++
Exclusive Or
XOR F3 1 2 M(R(X)) ^ D -> D
Exclusive Or Immediate
XRI FB 2 2 M(R(P)) ^ D -> D, R(P)++
And
AND F2 1 2 M(R(X)) & D -> D
And Immediate
ANI FA 2 2 M(R(P)) & D -> D, R(P)++
Shift Right
SHR F6 1 2 shift D right logical, LSB(D) -> DF
Shift Right with Carry
SHRC 76 1 2 shift D right, DF -> MSB(D), LSB(D) -> DF
Ring Shift Right
RSHR 76 1 2 same above
Shift Left
SHL FE 1 2 D + D -> (DF, D)
Shift Left with Carry
SHLC 7E 1 2 shift D left, DF -> LSB(D), MSB(D) -> DF
Ring Shift Left
RSHL 7E 1 2 same above
論理演算にはAND, OR, XORがありまして、それぞれにオペランドがR(X)で指示されるものとイミディエートアドレッシング、つまりはR(P)で指示されるものがあります。R(N)で指示する命令はありません。そのため、演算を続けて行いたい場合には、ポインタレジスタかXレジスタをひんぱんに変更する必要があるかもしれません。他のアドレッシングはありませんから、絶対アドレッシングですむ場合にも、ポインタレジスタにアドレスをロードする必要が出てきます。そのためにはDレジスタを経由してポインタレジスタを変更するので、演算対象が先にDレジスタに入っていると、その値を退避させなくてはならず……と、簡単な作業ではありません。
シフト関係は実質的に4命令だけです。SHRC命令とRSHR命令は同じもので、異なるニーモニックが用意されています。同様にSHLC命令とRSHL命令もニーモニックしか異なりません。
SHR命令は単純右シフトで、Dレジスタの最上位ビット(MSB)には0が入ります。Dレジスタの最下位ビット(LSB)はDFに入ります。COSMACでDFを変更する命令は、これらのシフト命令と加減算命令だけです。
SHRC命令ないしRSHR命令は、DとDFを連結して右回転する命令です。
SHL命令は単純左シフトで、DレジスタのLSBには0が入り、MSBはDFに入ります。ちょうどDレジスタを2倍するのと同じ動作です。
SHLC命令とRSHL命令はDとDFを連結して左回転する命令です。
どのシフト命令もDから押し出されたビットがDFに入りますから、うまく命令を組み合わせれば16
bitやそれ以上のデータのシフトも可能となっています。
論理演算が終われば算術演算命令となりますが、その中身は加減算だけです。
Instruction
Mne. OpCode 動作
Add
ADD F4 1 2 M(R(X)) + D -> {DF, D}
Add Immediate
ADI FC 2 2 M(R(P)) + D -> {DF, D}, R(P)++
Add with Carry
ADC 74 1 2 M(R(X)) + D + DF -> {DF, D}
Add with Carry Immediate ADCI
7C 2 2 M(R(P)) + D + DF -> {DF, D}, R(P)++
Subtract D
SD F5 1 2 M(R(X)) - D -> {DF, D}
Subtract D Immediate
SDI FD 2 2 M(R(P)) - D -> {DF, D}, R(P)++
Subtract D with Borrow
SDB 75 1 2 M(R(X)) - D - !DF -> {DF, D}
Subtract D with Borrow Imm. SDBI 7D 2 2
M(R(P)) - D - !DF -> {DF, D}, R(P)++
Subtract Memory
SM F7 1 2 D - M(R(X)) -> {DF, D}
Subtract Memory Immediate SMI
FF 2 2 D - M(R(P)) -> {DF, D}, R(P)++
Subtract Memory with Borrow SMB 77
1 2 D - M(R(X)) - !DF -> {DF, D}
Subtract Mem. with Borrow Imm. SMBI 7F 2 2 D - M(R(P))
- !DF -> {DF, D}, R(P)++
加減算命令ではDFが変化します。減算命令にデータレジスタとメモリのどちらからどちらを引くかに応じて2通りの命令が用意されているのは珍しいかもしれません。減算命令のDFの使われ方ですが、ボローがない場合にDFが1になります。
論理演算の場合と同じく、R(X)で指されたメモリかイミディエートモードでオペランドが指示される命令しかありません。単純な加減算と桁上がり付きの加減算のバリエーションで、全12種類の命令です。
次は256 Byte単位の同一ページ内への条件分岐命令について紹介します。
Instruction
Mne. OpCode 動作
Short Branch
BR 30 2 2 M(R(P)) -> R(P).0
No Short Branch
NBR 38 2 2 R(P)++
Short Branch IF D = 0
BZ 32 2 2 If D = 0, M(R(P)) -> R(P).0
Short Branch IF D != 0
BNZ 3A 2 2 If D != 0, M(R(P)) -> R(P).0
Short Branch IF DF = 1
BDF 33 2 2 If DF = 1, M(R(P)) -> R(P).0
Short Branch IF Pos. or Zero BPZ 33 2 2
same above
Short Branch IF Eq. or Greater BGE 33 2 2 same
above
Short Branch IF DF = 0
BNF 38 2 2 If DF = 0, M(R(P)) -> R(P).0
Short Branch IF Minus
BM 38 2 2 same above
Short Branch IF Less
BL 38 2 2 same above
Short Branch IF Q = 1
BQ 31 2 2 If Q = 1, M(R(P)) -> R(P).0
Short Branch IF Q = 0
BNQ 39 2 2 If Q = 0, M(R(P)) -> R(P).0
Short Branch IF EF1 = 1
B1 34 2 2 If EF1 = 1, M(R(P)) -> R(P).0
Short Branch IF EF1 = 0
BN1 3C 2 2 If EF1 = 0, M(R(P)) -> R(P).0
Short Branch IF EF2 = 1
B2 35 2 2 If EF2 = 1, M(R(P)) -> R(P).0
Short Branch IF EF2 = 0
BN2 3D 2 2 If EF2 = 0, M(R(P)) -> R(P).0
Short Branch IF EF3 = 1
B3 36 2 2 If EF3 = 1, M(R(P)) -> R(P).0
Short Branch IF EF3 = 0
BN3 3E 2 2 If EF3 = 0, M(R(P)) -> R(P).0
Short Branch IF EF4 = 1
B4 37 2 2 If EF4 = 1, M(R(P)) -> R(P).0
Short Branch IF EF4 = 0
BN4 3F 2 2 If EF4 = 0, M(R(P)) -> R(P).0
以上の短形式の分岐命令では、分岐する場合には現在プログラムカウンタとして使用しているポインタレジスタの下位8
bitをオペランドで上書きします。上位8 bitは変化しません。結果として同一ページ内への分岐となります。相対アドレッシングではありません。
BR命令は無条件分岐で、NBR命令は分岐しない分岐命令です。NBR命令は矛盾した表現になってしまいますが、要は38Hというオペコードの次の1
Byteを無視してさらに次のアドレスのメモリに含まれるデータを命令として実行します。つまりNBR命令のアドレス+2へと無条件分岐する、あるいは1
Byteスキップする命令ということになります。
COSMACで演算結果によって変化するフラグはDFだけです。ゼロフラグはありません。そのため、BZ命令やBNZ命令では、その時点のDレジスタの内容が0かそれ以外かを判定します。
DFが0か1かを判定するBNF命令やBDF命令には、それぞれふたつの別名が用意されています。Dレジスタから何かを減算した結果、DFが1なら桁借りが発生しなかったわけですから、その値は負でない値ということでBPZ命令で判定することもできるし、減算をふたつの数値の比較と考えれば等しいか大きいということでBGE命令で判定して意図を明確にできるということでしょう。BM命令やBL命令も減算結果のDFが0ということですね。
それ以降の分岐命令はプロセッサ内蔵の入出力機能を利用した条件分岐命令です。
BQ命令とBNQ命令はプロセッサの出力端子に引き出されているQフラグの状態を判定する命令です。Qフラグをセットしたりクリアする命令はありますけれど、反転したり他のレジスタに読み出す命令はありません。そのため、Qフラグの状態を知りたければ、こういった条件分岐命令を利用するほかありません。
B1, BN1, B2, ..., BN4の各命令は、プロセッサの入力端子として用意されているEF1*,
EF2*, EF3*, EF4*の各信号の状態によって条件分岐を行う命令です。たとえばB1命令はEF1が1のとき分岐するのですが、EF1*端子は負論理の信号のため、EF1*端子がVSS、つまり0
VのときにEF1が1と解釈されて分岐することになります。BN1命令はEF1*端子がVCCと等しい電圧のときに分岐です。これらの入力端子もレジスタに値を直接読み込む命令はなく、この分岐命令でしか状態を判定することができません。
2 Byte長のオペランドを持つ絶対アドレッシングの分岐命令は種類が少なくなっています。
Instruction
Mne. OpCode 動作
Long Branch
LBR C0 3 3 M(R(P)) -> R(P).1, M(R(P) + 1) -> R(P).0
No Long Branch
NLBR C8 3 3 R(P) + 2 -> R(P)
Long Branch IF D = 0
LBZ C2 3 3 If D = 0, M(R(P)) -> R(P)
Long Branch IF D != 0
LBNZ CA 3 3 If D != 0, M(R(P)) -> R(P)
Long Branch IF DF = 1
LBDF C3 3 3 If DF = 1, M(R(P)) -> R(P)
Long Branch IF DF = 0
LBNF CB 3 3 If DF = 0, M(R(P)) -> R(P)
Long Branch IF Q = 1
LBQ C1 3 3 If Q = 1, M(R(P)) -> R(P)
Long Branch IF Q = 0
LBNQ C9 3 3 If Q = 0, M(R(P)) -> R(P)
LBR命令の動作のところだけ詳しく書いてありますが、LBR命令のオペコードの次のアドレスのメモリから読み出した値がプログラムカウンタとして使用しているポインタレジスタの上位8
bitにロードされ、さらに次のアドレスのメモリからポインタレジスタの下位8
bitにロードされます。つまり16 bitアドレスの格納法はMC6800などと同様の順序となっています。COSMACで3
Byte命令は、この長形式の分岐命令だけです。
COSMACで16 bitアドレス空間の任意の場所に分岐するには、これらの長形式分岐命令を使用するか、あらかじめポインタレジスタのどれかに分岐したいアドレスを入れておいてPレジスタの内容を書き換える方式のどちらかしかありません。
COSMACでは、さらに条件判定にも使用できるスキップ命令が用意されています。
Instruction
Mne. OpCode 動作
Short Skip
SKP 38 1 2 R(P)++
Long Skip
LSKP C8 1 3 R(P) + 2 -> R(P)
Long Skip IF D = 0
LSZ CE 1 3 If D = 0, R(P) + 2 -> R(P)
Long Skip IF D != 0
LSNZ C6 1 3 If D != 0, R(P) + 2 -> R(P)
Long Skip IF DF = 1
LSDF CF 1 3 If DF = 1, R(P) + 2 -> R(P)
Long Skip IF DF = 0
LSNF C7 1 3 If DF = 0, R(P) + 2 -> R(P)
Long Skip IF Q = 1
LSQ CD 1 3 If Q = 1, R(P) + 2 -> R(P)
Long Skip IF Q = 0
LSNQ C5 1 3 If Q = 0, R(P) + 2 -> R(P)
Long Skip IF IE = 1
LSIE CC 1 3 If IE = 1, R(P) + 2 -> R(P)
SKP命令とLSKP命令は無条件スキップですが、NBR命令およびNLBR命令と同一のオペコードです。常に分岐しないのなら、オペランド部分を無視してその先に無条件でスキップするというわけで、同じ命令の別名と考えることもできます。ただし、アセンブリ言語上ではSKP命令とNBR命令、LSKP命令とNLBR命令の扱いは異なります。NBR命令やNLBR命令はオペランドとしてラベルをとり、あくまで分岐命令の一種として2
Byteないし3 Byte命令として扱われます。しかし、SKP命令やLSKP命令は1 Byte命令としてアセンブルされ、続けて別の命令が分岐命令ならアドレス部の場所に配置できるようになっています。
条件つきスキップ命令はすべて2 Byteスキップするようになっています。サイクル数は3サイクルで、どうやら条件が不成立の場合にも3サイクル必要なようです。
条件の中にIEに関するものが含まれています。IEの内容を読み出したりする命令は他になく、LSIE命令を使って判定するしか調べる手段はありません。
残りは制御命令と入出力命令です。
Instruction
Mne. OpCode 動作
Idle
IDL 00 1 * Wait for DMA or Interrupt
No Operation
NOP C4 1 3
Set P
SEP DN 1 2 N -> P
Set X
SEX EN 1 2 N -> X
Set Q
SEQ 7B 1 2 1 -> Q
Reset Q
REQ 7A 1 2 0 -> Q
Save
SAV 78 1 2 T -> M(R(X))
Push X, P to Stack
MARK 79 1 2 (X, P) -> {T, M(R(2))}, P -> X, R(2)--
Return
RET 70 1 2 M(R(X)) -> (X, P), R(X)++, 1 -> IE
Disable
DIS 71 1 2 M(R(X)) -> (X, P), R(X)++, 0 -> IE
Output
OUT 6p 1 2 M(R(X)) -> BUS, R(X)++
Input
INP 6P 1 2 BUS -> {M(R(X)), D}
IDL命令は命令実行を一時中断して、割り込みやDMA要求を待つ命令です。それらの要求が発生するまで、命令実行サイクルを繰り返します。割り込み要求があれば割り込みサービスルーチンを実行し、そこから戻るとIDL命令の次の命令から命令実行が再開されます。DMA要求があればそのDMAサイクルを実行し、その後でIDL命令の次の命令から命令実行を再開します。
NOP命令は半端なビットパターンが割り当てられています。どうやら、絶対にスキップしない条件スキップ命令として実装されているらしくて、3サイクルの実行時間を必要とします。
SEP命令とSEX命令はそれぞれPレジスタとXレジスタに値をセットする命令です。ほかにPレジスタやXレジスタの値を操作する命令は割り込みに関係したものしかありません。特にSEP命令はサブルーチン呼び出しに重要な命令です。
SEQ命令とREQ命令はQフラグの操作用です。1命令で反転させる命令はありませんが、条件分岐命令も使えますから不都合はないでしょう。
SAV, MARK, RET, DIS命令は割り込みメカニズムと関係した命令です。SAV命令は割り込みサービスルーチンで以前のプログラムの状態を保存するための命令で、RET命令は割り込みサービスルーチンから戻るための命令です。DIS命令は割り込みを禁止したまま割り込みサービスルーチンから戻る命令となっていて、コンテキストを戻さずに割り込みを禁止するわけには行きません。単に割り込みを禁止したい場合は、割り込みメカニズムを模擬するようなMARK命令と併用する必要があります。詳細は割り込みの解説時に。
OUT命令は出力ポートに出力を行う命令で、1から7までの出力ポート番号をオペランドに持ちます。オペコードの下位4
bitのpの位置には出力ポート番号がそのまま埋めこまれます。OUT命令の実行サイクルでは、R(X)で指示される読み出しサイクルとまったく同じ信号が出力されますが、それと同時にN0,
N1, N2という信号線にポート番号が出力されています。このN0, N1, N2には、普段は0が出力されていますが、0以外の値が出力されている場合は入出力命令の実行サイクルとしてバスにメモリから読み出されているデータを出力ポートに取り込めるように外部回路を設計しておきます。結果として出力ポートに指示されたメモリの内容が書き込まれます。Dレジスタを介さずにメモリから直接出力されるのが特徴で、逆にいえばDレジスタで計算を行った結果をそのまま出力できず、一度メモリに書き込まなくてはなりません。
INP命令はOUT命令の逆を行います。オペコードの下位4 bitのPの位置には、入力ポート番号に8を加えた値をはめ込みます。INP命令の実行サイクルではメモリへの書き込みサイクルと同じ信号と共にN0,
N1, N2にポート番号が出力されます。0以外の有効なポート番号が出力されている場合に、入力ポートからバスへ入力される値を出力しておけば、その値がメモリに書き込まれます。同時にプロセッサはDレジスタに同じ値を取り込みます。このようにして、メモリとDレジスタに入力ポートからの値が読み込まれます。Dレジスタだけ、あるいはメモリだけに入力することはできません。したがって、Dレジスタにだけ値を読み込みたい場合には、変更してもかまわないメモリアドレスを指すようにM(R(X))をセットしておいて、そのメモリの内容を無視する必要があります。
COSMACでは、ハードウエアの方にもなかなか特徴があります。メモリアドレスは上下バイト2回にわかれて出力されるマルチプレクスバスで、アドレスラッチが必要なことを除けばそれほど複雑な外付け回路にはなりません。非常に簡単なものではありますが、DMAコントローラに相当する回路が内蔵されていて、DMA要求信号を与えるだけでDMA入出力が可能になっています。しかも、そのDMAコントローラをリセット直後のプログラム実行前に操作することにより、簡単な外付け回路、たとえばコンソールパネルから、メモリにプログラムをダウンロードできるように考えられています。
具体的に信号を見ていきましょう。
CLOCK 1 40 VDD
WAIT* 2 39 XTAL*
CLEAR* 3 38 DMA IN*
Q 4
37 DMA OUT*
SC1 5 36 INTERRUPT*
SC0 6 35 MWR*
MRD* 7 34 TPA
BUS7 8 33 TPB
BUS6 9 32 MA7
BUS5 10 31 MA6
BUS4 11 30 MA5
BUS3 12 29 MA4
BUS2 13 28 MA3
BUS1 14 27 MA2
BUS0 15 26 MA1
VCC 16 25 MA0
N2 17 24 EF1*
N1 18 23 EF2*
N0 19 22 EF3*
VSS 20 21 EF4*
VSSが電圧の基準で、VCCとVDDが電源となっています。VDDが内部演算回路の電源で、VCCが入出力バッファ用の電源です。許された電圧範囲の中でVCCはVDDと同じか低い電圧を与えることができます。そのため、広い電源電圧範囲が許されているCDP1802では、VDDに10
Vを与えて内部回路の遅延を小さくして、VCCに5 Vを与えて入出力電圧をTTLコンパチブルにすることもできます。そうすれば、共に5
Vを与えるより高速化できるはずですし、メモリなどの外部回路に一般的な電源電圧範囲の製品を利用できます。しかし、CDP1802に保証されている最高クロック周波数は、共に5
Vを与えた場合に2.5 MHz、共に10 Vを与えた場合に5 MHzなのに対し、VCCに5 V,
VDDに10 Vを与えた場合でも3.1 MHzと、それほど高速化されるわけではありません。
CLOCKとXTAL*はクロックジェネレータの端子で、目的のクロック周波数と同じ周波数の水晶発振子を接続して発振させることができます。外部からクロック信号を与える場合にはCLOCK端子に接続します。
BUS0 - BUS7がデータバスで、MA0 - MA7がメモリアドレスです。16 bitのメモリアドレスは、先に上位8
bitがMA0 - MA7に出力された後、下位8 bitがMA0 - MA7に出力されます。アドレス上位8
bitをラッチするタイミングを示すのが、タイミングパルスAのTPAです。TPBはBUS0
- BUS7に有効なデータが存在するタイミングくらいの意味になります。また、MRD*信号がメモリからの読み出しストローブ信号、MWR*信号がメモリへの書き込みストローブ信号です。
N0, N1, N2は入出力ポートアドレスを指示するための信号で、EF1*, EF2*,
EF3*, EF4*は条件分岐で使われるフラグ入力、QはQフラグの状態がそのまま出力される1
bitの出力ポートです。
SC0, SC1はプロセッサのステートコード出力端子で、どのようなバスサイクルを実行中か示しています。意味は次の表のようになります。
SC1 | SC0 | ステート |
L | L | S0; 命令フェッチ |
L | H | S1; 命令実行 |
H | L | S2; DMA |
H | H | S3; 割り込み応答 |
残りはWAIT*, CLEAR*, INTERRUPT*, DMA OUT*, DMA IN*の5入力ですね。今までの信号は比較的一般的な役割や動作に対応していましたが、これらの入力端子はなかなか特徴的な動作を要求するものです。
WAIT*信号は何か遅いメモリを利用する際のウェイトサイクル挿入用の信号名に思えますが、COSMACにはそのような機能はありません。実はCLEAR*信号と対になってプロセッサの動作モードを指示するための信号です。意味は次の表のとおりです。
CLEAR* | WAIT* | モード |
L | L | LOAD |
L | H | RESET |
H | L | PAUSE |
H | H | RUN |
RUNモードはプロセッサが順番に命令をフェッチして普通にプログラムを実行するモードで、PAUSEモードは内部クロックを停止して(クロックジェネレータ自身は動作)プログラム実行を一時停止するモードです。
RESETモードは名称通りプロセッサをリセットするモードで、その結果、P,
X, R(0)が0クリアされます。さらにQフラグはリセットされて、IEは1にセットされます。つまり、リセット直後はプログラムカウンタとしてR(0)が選ばれ、その内容が0ですから、アドレス0からプログラム実行が始まります。他のマイクロプロセッサと比べると珍しいのですが、割り込みはリセットによって禁止されずに必ず許可されて始まります。割り込みを禁止しておきたければ、アドレス0からのメモリに71H,
00Hというコードを入れます。
LOADモードは外部回路からCOSMAC内蔵DMA機能を利用してプログラムをメモリに書き込むモードです。
では残ったDMA機能と割り込みについて説明します。
COSMACのDMA機能は、他のマイクロプロセッサとは異なっています。一般的なマイクロプロセッサではDMA要求を行うと、コントロールバスの一部とアドレスバスやデータバスの信号をハイインピーダンスにして、外部に増設したDMAコントローラがバスの支配権を掌握してデータ転送を行います。DMAコントローラは特殊な入出力デバイスとしてCPUからアクセスされます。最近の組み込み用マイクロコンピュータの中にはDMAコントローラの機能を内蔵しているものも多くなっていますが、それでもプログラマが扱ううえではCPUとは別モジュールのDMAコントローラを専用レジスタにアクセスすることによって利用するという考え方は変わりません。
しかしCOSMACの方は、CPU機能に寄生したようなDMA方式となっています。
DMA IN*信号がアサートされると、DMAバスサイクル表示をステートコードに出力し、アドレスバスにR(0)の内容を出力してメモリへの書き込みを実行します。この書き込みタイミングでDMA入力転送を行いたいデバイスからデータバスへデータを出力しておけば、R(0)で指されたメモリアドレスにそのデータが書き込まれます。その後、R(0)はインクリメントされます。
同様にDMA OUT*信号がアサートされると、メモリからの読み出しが行われる以外はDMA
IN*と同様のDMAバスサイクルが実行されます。やはりメモリアドレスはR(0)の内容で、その後R(0)はインクリメントされます。メモリからの読み出しタイミングでDMA出力転送を行うデバイスがデータをラッチします。
以上がCOSMACのDMA機能です。DMA転送アドレスはR(0)に保持されています。リセット直後はR(0)がプログラムカウンタとして使われていることに注意が必要です。RUNモードでプログラム実行中にDMA転送を行う場合、DMA転送を始める前にPに0以外の値を入れて、R(0)を適切に設定する必要があります。プログラムカウンタがR(0)のままでDMA
IN*やDMA OUT*をアサートすると、正常にプログラムを動作させられなくなるでしょう。プロセッサ機能だけでプログラマがDMA転送を禁止する手段はありませんので、R(0)が適切に設定されるまでDMA転送が発生しないような回路を外部に用意しないと危険かもしれません。また、転送カウンタのようなものも存在しませんし、R(0)のリロード機能のような同じバッファ領域を繰り返し転送するための機能も存在しません。必要に応じて外部回路にカウンタをもうけて転送終了を検出したりする必要があります。複数のDMA要求の受け付けも不可能です。
このように、簡単に使えそうですが案外と手間のかかるのがCOSMACのDMA機能です。
通常はDMAはプログラム実行時のRUNモードで使用しますが、LOADモードでも利用できます。そのため、イニシャルプログラムローダなどをハードウエアリセット後に手動あるいは簡単な外部回路によって自動的にRWMに書き込むことができます。つまり、CLEAR*とWAIT*を操作して、まずプログラムをリセットします。その結果、R(0)には0が入ります。リセットモードからWAIT*をLに戻せばLOADモードに入ります。LOADモードでDMA
IN*をアサートするたびにバスの内容がRWMに書き込まれていきます。そのアドレスは0番地から順番にインクリメントされていき、必要なだけメモリに書き込んだら、再びRESETモードにしてR(0)を0クリアし、今度はそのままの状態からCLEAR*をHに戻せばRUNモードとなり、0番地からプログラムが実行開始されます。DMA
IN*のタイミングに合わせてコンソールパネルのスイッチからのデータをデータバスに乗せれば、コンソールパネルからイニシャルプログラムローダなどをわずかな外部回路でロードできます。もっとも途中で書き込むべきデータを誤った場合、RESETしなおすしかR(0)を元に戻す手段がなく、最初からやり直しになりますけど。また、ROMなどから自動的にプログラムをRWMにコピーするハードウエアも、比較的わずかなハードウエアで実現可能でしょう。
割り込みメカニズムも独特です。関係する信号線はINTERRUPT*だけで、ほかに特別な外部回路は必要ありません。割り込み受け付け時に割り込み応答バスサイクルが生じますが、これは割り込みが受け付けられたことを外部に知らせるためだけのもので、普通は無視してもかまいません。
さて、INTERRUPT*がアサートされて割り込みがプロセッサに受け付けられると、XとPを合わせた8
bitの内容がTレジスタに転送されます。その後、Pに1が、Xには2が入ります。また、IEは0クリアされて以後の割り込みが禁止されます。以上が割り込み受け付け時に自動的にCOSMAC内で実行される処理です。結果、R(1)に入っていたアドレスから始まる割り込みサービスルーチンの処理が始まり、それ以前の重要なコンテキストがTレジスタに保存されています。
割り込みサービスルーチンはただちにDEC 2命令とSAV命令を続けて実行すれば、Tレジスタの内容をR(2)で指されたメモリに保存することができます。こうしておいて、割り込みサービスルーチンの末尾にXに2が入っている状態でRET命令を実行すれば、割り込まれたプログラムに次の割り込みを許可した状態で復帰できます。ただし、その場合には復帰前にR(1)に再び割り込みサービスルーチンの先頭アドレスをセットしておかなくてはなりません。つまり、割り込みを使用する場合はR(1)は割り込みサービスルーチンへのベクタを常に入れておく必要があり、通常のプログラムでは使用できなくなります。またR(2)は割り込みサービスルーチン用のスタックポインタとして扱われます。その他に割り込みサービスルーチン内でR(1)にベクタを設定しなおすときに使用するプログラムカウンタが必要で、そのプログラムカウンタは割り込みサービスルーチンから復帰する際に元の値を入れなおすことはできませんから、そのレジスタも割り込みサービスルーチン専用となります。それ以外のポインタレジスタやDレジスタは、R(2)をスタックポインタとするスタックに保存しておいてサービスルーチンから戻る直前に復元できますから、割り込みサービスルーチン用にあらかじめ確保する必要はありません。もちろん、いくつかの作業用レジスタを割り込みサービスルーチン専用としておけば保存や復元の手間が不要となりますから、それだけ割り込みサービスを高速化できます。まぁ、何にせよ、割り込みを使用するかぎりは少なくとも3本のポインタレジスタを割り込みサービス専用に確保しなくてはなりませんから、通常のプログラムで使用できるポインタレジスタは最大13本までとなります。同時にDMA機能も使用する場合には最大12本となりますね。
このような割込み機構ですから、COSMACでは多重割り込みは困難です。複数の割り込み要求信号を外部ハードウエアでなんとかして、複数のサービスルーチンに自動的に分岐させようとかすると、割り込みサポート用の外部ハードウエアには事実上のメモリ管理ユニットが必要となります。つまり、割り込みサービスルーチンが格納されるメモリをバンク切り替えできるようにしておいて、割り込み要求信号に応じてメモリバンクを自動的に切り替えるようなハードウエアが必要でしょう。そうすれば、すべての割り込みサービスルーチンの先頭アドレスを共通のアドレスにできます。もっとも割り込みサービスルーチンからの復帰には、さらに手間が必要でしょうけど。
RESETモードではIEが1になり割り込み許可された状態です。これを禁止するには、リセット直後に71H,
00Hという命令を実行させることについては触れました。これは、DIS命令と0という定数値の連続です。実際には何がおきるのでしょうか。リセットによってPもXも0になります。DIS命令はM(R(X))からXとPの値を読み出したあと、R(X)をインクリメントし、IEを0にする命令です。つまり、この場合、Xが0ですからM(R(0))から読み出すのですが、プログラムカウンタもR(0)で、DIS命令の実行時にはDIS命令の次のアドレスのメモリを指しています。したがって、00Hを読み出して、それをXとPに入れますから、XとPの内容は実質的に変化しません。その後R(0)がインクリメントされますから、きちんと次のアドレスの命令をフェッチできます。このようにして、他のレジスタなど内部状態を変化させずにIEだけを変更できます。リセット直後からXの内容を0以外の値にしたければ、71Hの直後のデータを変更するだけで済みます。Xの値とPの値が等しい点にこのテクニックの本質がありますから、そのようにXを設定すればプログラムの任意の位置でRET命令やDIS命令を用いて割り込みの許可や禁止を行えます。
ひととおりCOSMACのアーキテクチャや特徴を眺めてきましたが、あまり複雑で高度なプログラムを実行させるのには向いていないということがわかるでしょう。割り込みやDMAも複雑なものは無理ですし、16本のポインタレジスタも最初は多く思えたとしても、割り込みやDMAで使用されたり、サブルーチン呼び出しのために使用されることを考えると、それほど余裕があるわけではありません。ポインタレジスタの保存やロードはDレジスタを介さないといけないというのも面倒です。どちらかというと、小規模な組み込み用途に使用するのが向いているといえるでしょう。そう考えると、電源電圧や消費電力や使用温度範囲などが広い(温度範囲は通常で-40度から+85度、広い方では-55度から+125度というものがあります)といった仕様が活きてきそうです。屋外の無人環境で連続稼働する必要があり、場合によってはバックアップバッテリの電力だけで長時間動作しつづけなくてはならない用途などにはぴったりです。気象や自然環境の何かを長時間連続して計測する装置などが想像されます。特殊仕様になりますが、RCAは宇宙環境用のCOSMACも製造していました。人工衛星の制御に使用するようなマイクロプロセッサですね。そういった、当時のn-MOSマイクロプロセッサでは実装しにくい応用になら、なかなかCOSMAC以外の選択肢は見つからないでしょう。
Return to IC Collection