COSMAC

RCA社は半導体というよりオーディオ装置などの家電関係の大企業として有名でしょうね。そのRCA社にも半導体部門があって、1970年代初頭にはCMOSロジックICファミリの盟主としてデジタルIC製造にも力を入れていました。当然、マイクロプロセッサも作っていて、それがCOSMACファミリです。
さすが4000シリーズCMOSファミリを開発していたメーカだけあって、完全CMOSマイクロプロセッサファミリとなっています。4000シリーズのCMOSファミリは3 Vから18 Vまでの広い電源電圧範囲とTTLファミリとは桁違いの低消費電力と低速度が特徴です。COSMACも、その特徴を完全ではありませんが、受け継いでいます。
電源電圧は、資料によって異なるのですが、一番広く書いてある初期の資料ではCDP1802Dで3 - 12 Vで動作するとあります。その後の資料では4 - 10.5 Vで動作するというのが一般的です。どちらにしろ、1970年代のマイクロプロセッサでは仮に5 V単一電源で動作するとしても±5 %の範囲でしか保証されないのが普通ですから、非常に広い電源電圧範囲で動作することは確かです。なお、同じCOSMACでも廉価版のCDP1802CEなどでは4 - 6.5 Vの電源電圧範囲しか保証されていません。それでも±20 %以上の範囲で動作可能です。
また、最高クロック周波数で5 V動作させた場合、最大4 mAしか消費しません。100 mA以上消費するのがあたりまえのn-MOSマイクロプロセッサと比べると、非常に低消費電力であることがわかります。
ただし、これらの広い電源電圧範囲や低消費電力を活かしてシステムを構成するには、メモリやI/O用LSIにもCOSMACファミリの製品を利用するしかありません。他の一般的なメモリや他のマイクロプロセッサファミリのI/O用LSIをコスト面で採用しようとすれば、それらのLSIは5 V ±5 %の範囲の電源電圧でしか動作保証されていませんから、システム全体の電源条件はそちらのLSIによって決められてしまいます。
動作速度は当時のCMOSの割りには高速なのですが、3.2 MHzクロック動作で1命令の標準的な実行時間が5 usと、当時の一般的なn-MOSマイクロプロセッサより倍以上遅くなっています。しかしCOSMACの魅力というか採用動機となる長所は速度以外のところにあり、それほどの問題ではありません。普通のCMOSデバイスと同様に、クロック周波数に比例した消費電流が流れるので、速度条件が許される範囲でできるだけ低いクロック周波数で駆動することも設計時の重要な検討事項です。

CDP1802
上はRCA社のCDP1802BCEで、4 - 6.5V動作、5 MHzクロック動作の高速品です。鈴木様よりの頂きものです。下はIntersil社のCDP1802ACEで4 - 6.5V動作、3.2 MHzクロック動作のもの。現在も製造されているのですね。共に-40度から+85度で動作と、使用温度範囲が広いのも特徴です。

CDP1802のプログラマから見たレジスタモデルはこのようになっています。
COSMAC registers

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(2)に再び割り込みサービスルーチンの先頭アドレスをセットしておかなくてはなりません。つまり、割り込みを使用する場合は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