TMS9900

マイクロプロセッサの歴史の初期に生まれた独自仕様の16 bit CPUのひとつがTexas Instruments社のTMS9900です。マイクロプロセッサの歴史は4 bitの4004から始まり、8 bit, 16 bitと演算能力を拡大してきたわけですが、16 bitの歴史はIntel社の8086で始まるのでなく、2系統の歴史が初期から始まっていました。ひとつは、16 bitアーキテクチャのミニコンピュータをマイクロコンピュータ化する試みで、ミニコンピュータ界の王者のDigital Equipment Corp. (DEC)社がPDP-11をLSI化したLSI-11を開発したり、Data GeneralがMicroNova (MN601)を開発したり、パナファコムでPFL-16Aを開発したりといった動きがありました。もうひとつの系統として、下手に複雑な8 bit CPUを開発するのも16 bit CPUを開発するのもたいして手間に差があるわけでないという考えで、それまで適当なアーキテクチャを保持していない半導体メーカが新しいアーキテクチャの16 bit CPUを最初から開発してしまう系統です。こちらにはNECのuCOM-16シリーズなどが含まれます。
TI社のTMS9900は、TI社が先に開発販売を行っていたTI990シリーズミニコンピュータのCPUをシングルチップのLSIに納めたもので、前者のケースに含まれます。1975年11月発表です。
で、技術雑誌などで写真を初めて見たあの頃、びびりました。こんなだったからです。

TMS9900
1981年製の64ピンセラミックパッケージのTMS9900。

今でこそ普通の64ピンDIPパッケージですが、当時は40ピンDIPでもマイクロプロセッサ関係の特別なLSIにしか使われておらず(そう、マイクロプロセッサというのは普通のICじゃなかったのよ)、40ピンですら大きめの印象があったのだから、このパッケージは巨大な印象を持ちました。足の取り付け方が時代を感じます。
その他、48 MHzを分周して作成した4相クロックを要求するとか、汎用レジスタを内部に持たないアーキテクチャとか、わがままで変わった構成のマイクロプロセッサという感じを持ちました。
16 bit CPUですが、アドレスはByte単位で割り当て、アドレッシング範囲も16 bitなので、64 KByteつまり32 KWordのメモリ空間を持ちます。16 bit CPUにしては小さなメモリ空間という印象になるかもしれませんが、これが発表された当時は1 KByteのメモリチップ代だけで1万円を越える時代です。バッファや制御回路とともに基板に取り付けたメモリボードは4 KByteでも10万円程度になります。16 KByteもメモリを搭載したコンピュータというと、かなり高度な応用にしか使われず、マイクロコンピュータ化する利点が減ってしまいますから、これで良かったのでしょう。

CPUに内蔵されているレジスタは、なんと次の3個しかありません。

TMS9900 registers

PCはプログラムカウンタ。WPはワークスペースポインタというもので、STがステータスレジスタです。なんとスタックポインタすらありませんし、アキュムレータのような演算対象となりそうなレジスタもありません。この鍵はWPの役割にあるのですが、その前に注意すべき点とSTの意味について。
TMS9900とそのファミリーでは、1語の中のビットの番号付けがMotorolaやIntelのマイクロプロセッサの流儀と異なっています。図にも示した通り、MSBがビット0で、LSBがビット15です。メモリアドレスは前述の通りにバイト単位で付けられていますが、16 bitデータをメモリに格納する場合、上位バイトが指定されたメモリアドレスに、下位バイトが指定されたアドレスを+ 1したアドレスに格納されます。
ステータスレジスタSTにはフラグ類や割り込みマスクが格納されています。STのビット0 (以降ST0と略記)は論理大なりフラグで無符号2進数の大小関係によって変化します。ST1は算術大なりフラグで、符号付き2進数として演算対象を解釈した場合の大小関係によって変化します。ST2のEQフラグは演算結果が0かどうかと、ビットテストであるTB命令の結果が反映されます。ST3は他のマイクロプロセッサと同じ役割のキャリーフラグで、ST4がオーバーフローフラグになります。ST5のOPはOdd Parityの略で、奇数パリティのときにセットされます。OPに関してはワード単位の演算では変化せず、バイト単位の演算の場合に変化することに注意してください。ST6のXOPフラグはXOP命令実行中であることを示すフラグです。ST12からST15は割り込みマスクで1111ですべての割り込み許可、0000でレベル0のマスク不可の割り込みだけが許可、0001でレベル1割り込みとレベル0割り込みが許可、という意味になります。XOPとIMは条件判断ではなくプロセッサの状態を保持するためのビットで、それ以外は条件分岐用のフラグとして使われるのが主な役目です。
さて、普通のCPUでいう汎用レジスタ、演算対象としてのレジスタがどこにあるかというと、メモリ上にあります。そのレジスタの場所を指し示すのがWP、ワークスペースポインタです。レジスタは16 bit幅で、0から15までの番号が振られていて、演算対象としてだいたい同じ役目を果たします。レジスタ0以外の15個のレジスタは、インデックスレジスタとしても使用できます。TMS9900にはスタックポインタがなくて、レジスタのPUSH/POPなんかもできませんが、WPを書き換えることでレジスタを保存したり復帰したりが自在にできるわけです。CPU内部にはPC, WP, STしか存在しないというのは非力なように見えますが、割り込みなんかでレジスタをすべて退避する場合、PC, WP, STを退避してWPに別の値を入れてしまえば割り込み前の汎用レジスタを書き換える心配はなくなるわけで、コンテキストスイッチの高速化が期待できます。割り込み応答速度が重要な応用では有利になりますね。ついでに多数のトランジスタが必要なレジスタ類を内蔵しなくて済むので、ハードウェアが単純化されます。

スタックポインタが存在しないと、サブルーチン呼び出しはどうするんだろうと心配になるかもしれません。一番単純なBL (Branch and Link)命令ではレジスタ11にリターンアドレスを格納します。サブルーチンから帰る場合にはレジスタ11を指定したレジスタ間接アドレッシングによる分岐命令を使用します。サブルーチンの中でレジスタ11を保存するとか、WPを書き換えるとかすれば、入れ子になったサブルーチンも実現できるという仕組みです。もう少し高度なサブルーチン呼び出しではBLWP (Branch and Load Workspace Pointer)命令を使用します。この命令では分岐先アドレスの他に16 bit定数も指定して、その定数がWPにロードされます。ロード直前のWPやPCの値は、新レジスタに格納されます。レジスタ13に旧WPが、レジスタ14に旧PCが、レジスタ15に旧STが、保存されます。この3種類の値を保存すれば、BLWP実行前のプログラムのコンテキストがすべて保存されたことになるわけです。このサブルーチンからの復帰には、専用のRTWP (Return Workspace Pointer)命令を使用します。
じつは古い大型機や最初期のミニコンピュータでは、スタックポインタを持たないアーキテクチャも多かったのです。Algol系の高級言語が普及してスタックというデータ構造が重要視される前は、わざわざコンピュータアーキテクチャの側でサポートする必要もありませんでした。ではどうしていたかというと、このTMS9900のように、特定のレジスタ(あるいは指定された任意のレジスタ)にリターンアドレスを格納するというパターンと、サブルーチンヘッダ擬似命令などをアセンブラに装備しておいて、サブルーチンの先頭アドレスにリターンアドレスを格納してしまうものがあります。後者では、サブルーチン本体はサブルーチンの先頭アドレスの次の語から始まるわけです。当然、再帰呼び出しはプログラムコードで細工をしなくては実現できません。しかし、FORTRANやCOBOLで書かれたプログラムを実行するのなら、それほど不便ではなかったのですね。マイクロプロセッサは組み込み用途が応用の中心に想定されていましたから、プログラムをROMに格納する都合上、後者のサブルーチン方式は使えませんでした。レジスタが多数あるアーキテクチャではレジスタに格納する方式も使われましたが(TMS9900の他にもuPD751などが採用しています)、大多数のマイクロプロセッサではスタックアーキテクチャの優位性が認識されていたため、スタックに戻りアドレスを格納するようになっています。L-16Aのように、サブルーチン呼び出しはスタックを使用するけれども、割り込みに関してはメモリの特定アドレスにコンテキストを保存するアーキテクチャも存在します。また、PDP-11のように、レジスタ型とスタック型を併せ持ったサブルーチン方式を使用しているアーキテクチャも存在します。PDP-11のJSR命令は、JSR  R1, addrのように、リターンアドレスを保持するレジスタも同時に指定します。レジスタにリターンアドレスを保持すると同時にスタックへもリターンアドレスをプッシュします。こうして、旧来のプログラムテクニックも使えるようになっていたわけです。しかし、普通はこれだとレジスタをひとつ書き換えてしまって無駄です。そのような場合、PDP-11ではPCもレジスタの一種であるという特徴を利用してJSR  PC, addrのようにすれば、リターンアドレスをPCとスタックトップに格納してからサブルーチンアドレスをPCに書き込みますから、PCが上書きされてレジスタを余分に消費する必要がなくなります。まぁ、普通にJSR  addrと書けば、アセンブラがJSR  PC, addrと解釈するはずですけどね。
このように、サブルーチンというごく普通の仕組みを実現する方式ですら、歴史的にいくつものやり方が提案されて実装されてきたわけです。

話題をTMS9900の命令体系に戻しましょう。TMS9900は16 bit幅の汎用レジスタを16個備えていますから、たいていの演算はレジスタ−レジスタ間演算で済みそうです。でもアーキテクチャ上はメモリ−メモリ間演算が基本です。もちろんアドレッシングモードとしてレジスタも指定できますから、レジスタ−メモリ間の演算やメモリ−レジスタ間の演算も可能で、PDP-11に近いくらいの強力な命令セットになっています。

TMS9900のアドレッシングモードは以下の8種類あります。

  1. レジスタ
  2. レジスタ間接
  3. レジスタ間接オートインクリメント
  4. 直接
  5. インデックス
  6. イミディエート
  7. プログラムカウンタ相対
  8. CRU相対
1から5までは一般の命令で使用されるアドレッシングモードです。イミディエートはイミディエート専用の命令に、プログラムカウンタ相対はジャンプ命令で使用されます。CRU相対はCRU方式のI/O命令で使用されます。CRU相対アドレッシングはR12を基準に8 bitディスプレースメントを演算してCRUアドレスを算出するモードですが、CRU I/Oにはビット単位で操作できると言う特徴がありますが専用のI/Oチップや回路が必要になって、最近はあまり使われないと思うので、省略します。TMS9900にはメモリマップの形で8080系列のI/O LSIが簡単に接続できますし。
レジスタアドレッシングはレジスタを直接操作対象とするもので、アセンブリ言語ではR0からR15のレジスタ名をそのまま記述します。
レジスタ間接はレジスタに納められている内容をアドレスと解釈して、そのアドレスが指しているメモリを操作対象にするものです。R0からR15のレジスタにアスタリスクを前置して記述します。つまり*R7というような形ですね。
レジスタ間接オートインクリメントはレジスタ間接アドレッシングモードと同じアドレスを操作対象にします。ただし、その後でバイト命令のときにはレジスタを+1、ワード命令のときには+2します。R0からR15に対し使用可能で、*R0+のようにレジスタ間接の表記の後ろに+を付けて記述します。
直接アドレッシングモードは16 bitアドレスを直接指定する方法で、ラベルや数値に@を前置して記述します。OPコードの後に16 bitアドレスデータが付加されます。
インデックスアドレッシングモードはインデックスレジスタに16 bitディスプレースメントを加算して操作対象アドレスを決定する方法で、OPコードに16 bitデータが付加されます。記述は@DISP(Rn)という形になり、R1からR15までがインデックスレジスタとして使用できます。仮にR0を指定したとすると、命令のビットパターンがちょうど直接アドレッシングモードのビットパターンになるため、R0をインデックスレジスタとして使用することはできません。
イミディエートアドレッシングは16 bitの定数をOPコードの後に置き、その定数を直接操作対象とします。単に定数やラベルをそのまま記述します。
プログラムカウンタ相対はジャンプ命令専用で8 bitディスプレースメントをとり、その命令のアドレスに2を加えたものにディスプレースメントの2倍を加えたアドレスがジャンプ先になります。TMS9900の命令はワード単位で必ず偶数バイトから始まりますから、ディスプレースメントを2倍するのは合理的です。アセンブリ言語での記述は単にラベルを書き込むだけです。

TMS9900の命令フォーマットは9種類に分類されます。そのフォーマットごとに命令を見ていくことにします。

タイプ1 : 一般2項演算
これに属する命令はソースとディスティネーションの指定をオペランドに持ちます。フォーマットは
XXXXddDDDDssSSSS
という形です。ここでXXXXは命令ごとに違うビットパターンで、ddはディスティネーションのアドレッシングモード指定ビット、DDDDはディスティネーションのレジスタ指定、ssはソースのアドレッシングモード、SSSSはソースのレジスタ指定です。レジスタアドレッシングの場合、アドレッシングモード指定ビットは00に、レジスタ間接アドレッシングの場合は01に、レジスタ間接オートインクリメントの場合は11に、インデックスか直接アドレッシングの場合は10になります。インデックスアドレッシングと直接アドレッシングの違いは、レジスタ指定が0000以外か0000になっているかという点です。
アドレッシングモードによって修飾ワードが付加されるものがあります(インデックスアドレッシングなど)。その場合、命令の直後に配置されますが、ソースに関する修飾ワードが命令の直後に配置されます。ソース修飾ワードがなくてディスティネーション修飾ワードだけ存在する場合はそれが命令の直後に配置されますが、両方とも存在する場合はソース、ディスティネーションの順になります。
タイプ1の命令は12種類あります。
A    s, d  A000  Add                               s + d -> d
AB   s, d  B000  Add Bytes                         s.b + d.b -> d.b
C    s, d  8000  Compare                           s - d
CB   s, d  9000  Compare Bytes                     s.b - d.b
MOV  s, d  C000  Move                              s -> d
MOVB s, d  D000  Move Bytes                        s.b -> d.b
S    s, d  6000  Subtract                          d - s -> d
SB   s, d  7000  Subtract Bytes                    d.b - s.b -> d.b
SOC  s, d  E000  Set Ones Corresponding            s OR d -> d
SOCB s, d  F000  Set Ones Corresponding Bytes      s.b OR d.b -> d.b
SZC  s, d  4000  Set Zeros Corresponding           NOT(s) AND d -> d
SZCB s, d  5000  Set Zeros Corresponding Bytes     NOT(s.b) AND d.b -> d.b
2項演算としてANDやORといった名称がありませんが、ORはSOC命令として、ANDの変形がSZC命令として備わっています。XORに関してはタイプ3のレジスタ−メモリ演算として定義されています。ただし、ANDやORのイミディエート命令に関してはANDIやORIとして定義されていますから、少し変な感じもしますけど。
あと、ディスティネーションが後ろに書かれることに注意してください。8080系のアセンブリ言語とは逆になっています。

タイプ2 : Jump命令グループ
これはJMP系かCRU I/Oに関する命令で、相対アドレッシングを行うものです。すべて1 Word命令で、次のフォーマットになります。
0001XXXXDDDDDDDD
ここでXXXXが命令によって変化する部分で、DDDDDDDDがディスプレースメントです。属する命令は次の16種類です。
JEQ  a     1300  Jump if Equal                     on EQ = 1
JGT  a     1500  Jump if Greater Than              on A> = 1
JH   a     1B00  Jump if High                      on L> = 1 AND EQ = 0
JHE  a     1400  Jump if High or Equal             on L> = 1 OR EQ = 1
JL   a     1A00  Jump if Low                       on L> = 0 AND EQ = 0
JLE  a     1200  Jump if Low or Equal              on L> = 0 OR EQ = 1
JLT  a     1100  Jump if Less Than                 on A> = 0 AND EQ = 0
JMP  a     1000  Jump unconditionally              always
JNC  a     1700  Jump if No Carry                  on C = 0
JNE  a     1600  Jump if Not Equal                 on EQ = 0
JNO  a     1900  Jump if No Overflow               on OV = 0
JOC  a     1800  Jump On Carry                     on C = 1
JOP  a     1C00  Jump if Odd Parity                on OP = 1
SBO  a     1D00  Set Bit to One                    Set CRU bit
SBZ  a     1E00  Set Bit to Zero                   Reset CRU bit
TB   a     1F00  Test Bit                          Test CRU bit
ほとんどは分岐命令ですが、SBO, SBZ, TB命令はCRU関係のビット操作命令です。

タイプ3 : レジスタ−メモリ間演算
このタイプの命令はタイプ1のディスティネーションがレジスタに固定されているものです。
001XXXRRRRssSSSS
という形式で、次の5種類があります。
COC  s, r  2000  Compare Ones Corresponding        s AND NOT(r)
CZC  s, r  2400  Compare Zeros Corresponding       NOT(s) AND r
DIV  d, r  3C00  Divide                            (r, r+1)/s -> (r, r+1)
MPY  d, r  3800  Multiply                          r * s -> (r, r+1)
XOR  s, r  2800  Exclusive OR                      s EOR r -> r
DIVはRnとRn+1で表される32 bitの数値をソースの内容で割り、商をRnに、剰余をRn+1に格納します。MPYは16 bit同士の乗算です。COCとCZCは比較用の命令で、EQフラグだけが影響を受けます。

タイプ4 : CRU命令
この命令はCRU I/Oと複数ビットの転送を行います。命令フォーマットは
00110XCCCCssSSSS
となっていて、ソースで指示されたワードのLSBから順にCCCCで指定される任意のビット数だけ、転送します。
LDCR s, c  3000  Load Communication Register
STCR s, c  3400  Store Communication Register

タイプ5 : シフト命令
これに属するのはシフト関係の4種類で、
000010XXCCCCRRRR
という命令形式を持ちます。
SLA  r, c  0A00  Shift Left Arithmetic             bit 0 -> C, 0 -> bit 15
SRA  r, c  0800  Shift Right Arithmetic            bit 0 -> bit 0, bit 15 -> C
SRC  r, c  0B00  Shift Right Circular              bit 15 -> bit 0, bit 15 -> C
SRL  r, c  0900  Shift Right Logical               0 -> bit 0, bit 15 -> C
cで指定した回数だけシフトを行えますが、cに0が指定された場合に限り、R0の内容に従ってシフト量が決められます。

タイプ6 : 単項演算
この型の命令は、操作対象がひとつだけのもので、インクリメントなどのおなじみのものが多いのですが、分岐先アドレスも操作対象の一種だということで分岐やサブルーチン呼び出しが含まれています。
000001XXXXssSSSS
というフォーマットです。
ABS  d     0740  Absolute value                    | d | -> d
B    s     0440  Branch                            s -> PC
BL   s     0680  Branch and Link                   PC -> R11, s -> PC
BLWP s     0400  Branch & Load Workspace Pointer   WP -> R13, PC -> R14, ST -> R15, s -> WP, s + 2 -> PC
CLR  d     04C0  Clear                             0 -> d
DEC  d     0600  Decrement                         d - 1 -> d
DECT d     0640  Decrement by Two                  d - 2 -> d
INC  d     0580  Increment                         d + 1 -> d
INCT d     05C0  Increment by Two                  d + 2 -> d
INV  d     0540  Invert                            NOT(d) -> d
NEG  d     0500  Negate                            - d -> d
SETO d     0700  Set to Ones                       FFFF -> d
SWPB d     06C0  Swap Bytes                        d.0-7 <-> d.8-15
X    s     0480  Execute the instruction at s
BLWP命令についてはすでに説明しましたね。インクリメントやデクリメントに2を単位とする専用の命令があって、ワード単位のポインタ操作が容易になっています。SETOは全ビットを1にする命令で、SWPB命令は上下バイトを入れ替える命令です。X命令は他のプロセッサには見られない変わった命令かもしれません。ソースとして指定された場所の内容を、命令として解釈して実行する命令です。

タイプ7 : オペランドなし
以下の命令はオペランドを持たず、オペコードが修飾されることはありません。
CKOF       03C0  Clock Off
CKON       03A0  Clock On
IDLE       0340  Computer Idle                     Wait for Interrupt
LREX       03E0  Load or Restart Execution
RSET       0360  Reset External Diveces            0 -> IM0-3
RTWP       0380  Return Workspace Pointer          R15 -> ST, R14 -> PC, R13 -> WP

タイプ8 : イミディエート命令
タイプ8は主にレジスタにイミディエート値を適用するものですが、レジスタ指定のないものや、レジスタ指定だけでイミディエート値のないものも含まれます。一般的な命令フォーマットは
0000001XXXX0RRRR
という形で、RRRRがレジスタ指定、イミディエート値をオペランドにもつ命令は、この後に16 bit定数が続きます。
AI   r, i  0220  Add Immediate                     r + i -> r
ANDI r, i  0240  AND Immediate                     r AND i -> r
CI   r, i  0280  Compare Immediate                 r - i
LI   r, i  0200  Load Immediate                    i -> r
LIMI i     0300  Load Interrupt Mask Immediate     i -> IM0-3
LWPI i     02E0  Load Workspace Pointer Immediate  i -> WP
ORI  r, i  0260  OR Immediate                      r OR i -> r
STST r     02C0  Store Status Register             ST -> r
STWP r     02A0  Store Workspace Pointer           WP -> r

タイプ9 : XOP命令
このグループに属するのはXOP命令だけで、要するにソフトウェア割り込みです。
001011CCCCssSSSS
という形式で、CCCCに割り込みベクタを示す定数が入ります。
XOP  s, c  2C00  Extended Operation                s -> R11, WP -> R13, PC -> R14, ST -> R15, (40 + 4*c) -> WP, (42 + 4*c) -> PC
16進数表示の40にcを4倍したものを加えたアドレスが割り込みベクタです。

その他
たいていのTMS9900用アセンブラには次の組み込みマクロが備わっていると思います。
NOP        1000  No Operation                      JMP 0
RT         045B  Return                            B   *R11
仮にアセンブリ言語にサポートが無かったとしても、機械語でのデバッグなんかで必要となるビットパターンだとは思いますけど。

Return to IC Collection