汎用ロジックで作る16bitコンピュータ   

アドレッシングと命令設計

Copyright © 2013-2014 T.GATARO All Rights Reserved.

「汎用ロジックでコンピュータを作る」のトップページ(目次)はこちら   管理人のメインページはこちら

【変更記録】
2013-11-22 : 初稿(Rev 01.00)おそらく誤字脱字が大量にあると思われる
2013-12-17 : Rev 02.00 設計の見直しによる全面改訂 (Rev 02.01~Rev 02.22は詳細割愛)
2014-01-12 : Rev 03.00 回路図作成完了に伴う改訂。(回路図作成時に思いついた変更の反映)

命令設計の基礎知識

最低限の命令セット

CPUを手作りするなら、命令の種類は少ない方が設計や実装は簡単です。しかし極端に単純な命令セットにしてしまうと実用的なプログラム(例えばBASICインタプリタやインベーダーゲーム)を走らせるのが困難になります。つまり手作りコンピュータにおける命令セットの設計とは「ソフトウエア作成時の利便性を保ちつつ、どこまでハードウエアを簡略化できるか」を考える作業だと言えます。

画像は筆者がパターン描画/露光/エッチングを行ったTTLコンピュータの手作り基板と74シリーズTTL


さて「命令セットを決める」と言っても、命令の種類は幾つ位が適切なのでしょうか?
この問いに正しい答えはありません。あえて答えるならば、「命令の種類は多ければ多いほどプログラミングが楽になり、回路は複雑になる」です。極端な話、プログラマの負担やプログラムの実行時間を無視できるならば、命令数は一つでも構わないのです。

そこでまずは「命令が1種類しかないコンピュータ」を検証してみます。

古典的単一命令コンピュータ

「単一命令」の一例としては「A番地の内容から、B番地の内容を引き、C番地に格納し、その値が負ならばD番地に分岐する」があります。
アドレスが4つ必要となるので「単一命令4アドレスコンピュータ」と言います。命令長が16bitならば、命令コードは下記のビット構成となります。

単一命令コンピュータの命令形式

単一命令コンピュータの命令形式
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
アドレスA アドレスB アドレスC アドレスD

命令は1種類しかないので、命令を区別するためのビットはない。


プログラムの例

下記はこの「単一命令」のみを使って記述した「1から10までの和を求めるプログラム」です。命令が一種類ですのでニモニックによる命令の区別は不要となり、単にアドレスA~Dを順に並べたものが「命令」になります。「DS」は1ワード分のデータ領域、「DC n」は値nで初期化された1ワード分のデータ領域を意味します。

START: ZERO,ZERO,SUM,0    // 領域SUMにZERO-ZERO、すなわち0-0=0を格納する。結果は負にならないので第4オペランドは意味を持たない 
       TEN,ZERO,N,0      // 領域NにTEN-ZERO、すなわち10-0=10を格納する。結果は負にならないので第4オペランドは意味を持たない 
LOOP : ZERO,N,COMP,NEXT    // -N(ゼロからNを引いた数)をCOMPに格納する。結果が負でも次はNEXTから実行される
NEXT : SUM,COMP,SUM,0     // SUMにNを加える。加算命令は無いので、実際にはSUMから-Nを引く事で実現する
       N,ONE,N,FIN         // Nから1を引く。結果が負ならばループ終了であるのでFINへ分岐する
       ZERO,ONE,DUMMY,LOOP // 0から1を引き結果は捨てる。但し結果は常に負となるので、結果的にLOOPへの無条件分岐となる
FIN  : ZERO,ONE,DUMMY,FIN  // 前の行と同じ手法による無条件分岐。自分自身へ分岐する無限ループで、終了命令の代りとしている
ZERO : DC 0                // 定数 0
ONE  : DC 1                // 定数 1
TEN  : DC 10               // 定数 10
SUM  : DS                  // 変数 SUM の格納領域
N    : DS                  // 変数 N の格納領域
COMP : DS                  // 変数 COMP の格納領域 
DUMMY: DS                  // ダミー領域

上記のプログラムは未検証です。バグがあったら知らせてください (NOV.2013 がたろう)

単一命令コンピュータの致命的欠点

前述の単一命令コンピュータは「加算命令」が無いため「加算は負の値を作り、それを減算する」という細工が必要になります。また「無条件分岐命令」もないため、結果が必ず負となるダミー計算をすることにより「無条件分岐命令」の代りとしています。これらのテクニックはプログラマに多大な負担を強いるのは明白です。

更に、この命令セットにはアドレス空間が極端に狭いという致命的欠点があります。

このコンピュータの命令は1つの命令の中に4つのアドレス値を含むので「4アドレス式」です。命令長が16bitならば、アドレス1つ当りのビット長は4しか取れません。これでは最大でも16stepのプログラムしか実行できないことを意味します。もちろん命令長を32bitとすればアドレス1つ当りのビット長は8に、命令長を64bitとすればアドレス1つ当りのビット長は16になります。しかしメモリが非常に高価だった時代にはこの様に命令長を広げて問題点に対処するという方法は敬遠されました。現在はメモリが安価ですので「メモリ周りの配線量」の膨大さを我慢できるなら「命令長4bitの単一命令4アドレス式」も一考の価値はあります。しかしここでは「多アドレス式CPU」の考察は別の機会に行う事として、命令長は1970年代のミニコンピュータで多く採用された「16bit」である事を前提として話を進めます。

2命令3アドレスコンピュータ

前述の「単一命令」を「演算命令」と「分岐命令」の2つに分けると4アドレスを3アドレスに削減できます。但しこの場合は命令を区別するビットが新たに必要になります。命令を区別するビットは「オペコード」と呼ばれ、慣例的に命令コードの左側に置かれます。下記は一番左のビット(最上位ビット)が0なら演算命令、1なら分岐命令とする命令セットの一例です。

2命令3アドレスコンピュータの命令セット例

減算命令

A番地の内容から、B番地の内容を引き、C番地に格納する

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 アドレスA アドレスB     アドレスC    

先頭ビットの0はこれが演算命令であることを示す


分岐命令

A番地の内容とB番地の内容を比較し、A>BならばC番地に分岐する

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 アドレスA アドレスB     アドレスC    

先頭ビットの1はこれが分岐命令であることを示す


プログラム例

下記は「2命令3アドレスコンピュータ」の「1から10までの和を求めるプログラム」です。ニモニックは「S」が減算命令で、「B」が分岐命令です。オペランドはアドレスA~Cを順に並べます。「DS」と「DCn」の意味は前述の「単一命令コンピュータ」と同じです。
命令のステップ数はデータ領域を含めると、前述の「単一命令コンピュータ」に比べ1行だけ短くなっています。

START: S ZERO,ZERO,SUM    // 領域SUMにZERO-ZERO、すなわち0-0=0を格納する
       S TEN,ZERO,N      // 領域NにTEN-ZERO、すなわち10-0=10を格納する 
LOOP : S ZERO,N,COMP       // -N(ゼロからNを引いた数)をCOMPに格納する
       S SUM,COMP,SUM      // SUMにNを加える。加算命令は無いので、実際にはSUMから-Nを引く事で実現する
       S N,ONE,N           // Nから1を引く
       B N,ZERO,LOOP       // N>0ならLOOPから繰り返し
FIN  : B ONE,ZERO,FIN      // 1>0なので、常に自分自身へ分岐する無限ループ。終了命令の代り
ZERO : DC 0                // 定数 0
ONE  : DC 1                // 定数 1
TEN  : DC 10               // 定数 10
SUM  : DS                  // 変数 SUM の格納領域
N    : DS                  // 変数 N の格納領域
COMP : DS                  // 変数 COMP の格納領域 

上記のプログラムは未検証です。バグがあったら知らせてください (NOV.2013 がたろう)

この様に4アドレスを3アドレス式にしても、アクセス可能なアドレス空間は4bitから5bitに増えるだけで画期的な進化とは言えません。しかし「命令中のアドレスの数を減らす」という考え方はコンピュータ開発の黎明期の2アドレス式、さらには現在主流の1アドレス式への進化を加速させました。

2命令2アドレスコンピュータ

減算命令は一方の非演算子格納領域と結果格納領域のアドレスを兼用、分岐命令は比較対象の一方を常に「最後の演算結果」とする事でアドレスを1つ削減し、3アドレスを2アドレスにできます。下記はその1例です。

2命令2アドレスコンピュータの命令セット例

減算命令

A番地の内容から、B番地の内容を引き、A番地に格納する

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 X アドレスA アドレスB

14ビット目のXは使用せず(0でも1でも同じ)


分岐命令

最後に行った演算の結果が、A番地の内容より大きければ番地に分岐する

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 X アドレスA アドレスB

14ビット目のXは使用せず(0でも1でも同じ)

4命令2アドレスコンピュータ

3アドレス式の場合はオペランド1bitとアドレスが5bit×3組=15bitで、ちょうど16bitとなりますが、2アドレス式の場合はオペランド1bitとアドレスが7bit×2組=15bitで1bit余ります。前述の例では14bit目を未使用としていますが、この余ったビットをオペランドの一部にすることにより、命令を4種類とする事ができます。

4命令2アドレスコンピュータの命令セット例

減算命令

A番地の内容から、B番地の内容を引き、A番地に格納する

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 0 アドレスA アドレスB

加算命令

A番地の内容に、B番地の内容を加え、A番地に格納する

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 1 アドレスA アドレスB

条件分岐命令

最後に行った演算の結果が、A番地の内容より大きければB番地に分岐する

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 0 アドレスA アドレスB

無条件分岐命令

無条件にB番地に分岐する

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 (未使用) アドレスB

7~13ビット目は未使用(0でも1でも同じ)
ここをデコードすることにより更なる命令の拡張が可能となる

プログラム例

下記は「4命令2アドレスコンピュータ」の「1から10までの和を求めるプログラム」です。ニモニックは「S」が減算、「A」が加算、「C」が条件分岐、「B」無条件分岐です。オペランドはアドレスA、Bの順です。

START: S ZERO,ZERO,SUM    // 領域SUMにZERO-ZERO、すなわち0-0=0を格納する
       S TEN,ZERO,N      // 領域NにTEN-ZERO、すなわち10-0=10を格納する 
LOOP : A SUM,N           // SUMにNを加える
       S N,ONE             // Nから1を引く
       C ZERO,LOOP         // 全行の計算結果が0より大きければLOOPから繰り返す
FIN  : B 0,FIN             // 常に自分自身へ分岐する無限ループ。終了命令の代り
ZERO : DC 0                // 定数 0
ONE  : DC 1                // 定数 1
TEN  : DC 10               // 定数 10
SUM  : DS                  // 変数 SUM の格納領域
N    : DS                  // 変数 N の格納領域

上記のプログラムは未検証です。バグがあったら知らせてください (NOV.2013 がたろう)

この例では2命令コンピュータに対して「加算命令」と「無条件分岐命令」を加えましたが、どの様な命令を加えるのが最良なのかは一概には言えません。そのCPUをコントローラ的な目的に使うならば「加算命令」よりも「論理積命令」を加えた方が良いですし、科学計算を目的とするならば、高速乗除算を実現するための「シフト命令」を加えた方が良いと思います。
この様に実装する命令の候補数が増えてくると、その取捨選択はそのCPUが実行するソフトウエアの性格を知る事が不可欠になります。

1アドレス2命令コンピュータ

2アドレスを1アドレスにするためには、減算命令は2アドレスの一方を固定番地(例えば0番地、もしくは主メモリとは独立したレジスタ)とし、条件分岐命令は比較値を固定値(例えば0)にする方法があります。

1アドレス2命令コンピュータの命令セット例

減算命令

0番地(もしくはアキュムレータ)の内容から、A番地の内容を引き、0番地(もしくはアキュムレータ)に格納する

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 アドレスA

分岐命令

最後に行った演算の結果が、0より大きければA番地に分岐する

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 アドレスA

この命令セットは残念ながら実用にはなりません。なぜなら、任意の番地の内容を他の番地へ転送することが上記の命令の組み合わせではできないからです。2アドレス式の場合は、面倒ながらも減算命令を組み合わせることにより任意の番地間の転送が可能でしたが、1アドレス方式ではそれは不可能となります。このため、1アドレス式では「転送命令」が必須となり、最低でも3命令が必要になります。

1アドレス4命令コンピュータ

前述の1アドレス2命令コンピュータの不完全さを補うために、命令指定ビットを2ビットにし命令数を4に拡張した例です。

1アドレス4命令コンピュータの命令セット例

減算命令

0番地の内容から、A番地の内容を引き、0番地に格納する

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 0 アドレスA

Aに0番地を指定すると、0番地のゼロクリアと同義となる


加算命令

0番地の内容にA番地の内容を加え、0番地に格納する

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 1 アドレスA

0番地の内容がゼロならば、単にA番地から0番地への転送と同義となる


転送命令

0番地の内容を、A番地に格納する

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 0 アドレスA

Aにゼロを指定するとNOP(何もしない命令)となる


分岐命令

0番地の内容が0以外ならA番地に分岐する

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 アドレスA

事前に0番地をゼロクリアすると、無条件分岐となる

プログラム例

下記は「4命令1アドレスコンピュータ」の「1から10までの和を求めるプログラム」です。ニモニックは「S」が減算、「A」が加算、「M」が転送、「B」分岐です。オペランドはアドレス値です。

START: S ZERO             // 0番地をゼロクリアする
       M SUM               // SUMを0にする 
       S TEN,ZERO,N      // 0番地に10を格納する
       M SUM               // SUMを10にする 
LOOP : S ZERO             // 0番地をゼロクリアする
       A SUM               // 0番地にSUMを加える 
       A N               // 0番地にNを加える
       M SUM               // 結果をSUMに格納する
       S ZERO             // 0番地をゼロクリアする
       A N                 // 0番地にNを加える 
       A ONE               // 0番地から1を引く
       M N                 // 結果をNに格納する
    B LOOP              // Nが0以外ならばLOOPから繰り返す
       A ONE               // 0番地を0以外にする
FIN  : B FIN              // 0番地は0以外であるので常に自分自身へ分岐する無限ループ。終了命令の代り
ZERO : DC 0                // 定数 0
ONE  : DC 1                // 定数 1
TEN  : DC 10               // 定数 10
SUM  : DS                  // 変数 SUM の格納領域
N    : DS                  // 変数 N の格納領域

上記のプログラムは未検証です。バグがあったら知らせてください (NOV.2013 がたろう)

現在は1アドレス式が主流

1アドレス式は全て0番地を経由して転送や演算が行われるため、2アドレス式のプログラム比較するとプログラムはどうしても長くなるという短所があります。しかし、1アドレス式()は2アドレス式と比べ格段に大きなアドレス空間を扱えるという長所があります。加えて当初固定番地(上記の例では0番地)とされていた記憶領域を「アキュムレータ」と呼ばれる主メモリとは独立した高速メモリ(レジスタ)に置き換えることで処理速度を速くする手法にも対応し易いため、現在では1アドレス式がCPUアーキテクチャの主流となっています。
以後、本書は1アドレス式のコンピュータについてのみ言及します。

※ 1アドレス式は「ワンアドレス式」ではなく「シングルアドレス式」と呼ぶのが正しい呼び方です。本書では便宜上「1アドレス」と表記しています。

アドレッシングモードの設計

1アドレス式の欠点

命令語の数bitを命令を区別するためのオペコードとして使い、残りのbitをアドレス指定に使う方式は比較的簡単な回路で実現できるという長所を持つ反面、命令語のビット長よりもアドレス指定ビットを長くできないという致命的な欠点があります。
例えば命令語長が16bitでオペコードに2bitを割当てた場合、アクセスできるアドレス空間は最大でも14bit(=16Kワード)にしかならないのです。

実際にBASICインタプリタやインベーダーゲームを作るとなると、論理演算命令やシフト命令等の命令も必要となり、最低でも16種類程度の命令が必要となります。仮にオペコードに4bit(16種類の命令を区別できる)を割当てた場合、アドレス指定ビットは残りの12bit(=4Kワード)になります。4KワードあればBASICインタプリタやインベーダーゲームを作る事も不可能ではありませんが、やはりアドレス空間の狭さを感じます。やはり手作りCPUであっても、ある程度の実用性を狙うならば最低32Kワードか、できればそれ以上のアドレス空間が欲しいものです。

このような命令構成の場合、16bitマシンであるのに、アクセス可能なアドレス空間は12ビット(=16Kワード)が限界となる。

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
命令指定 アドレス指定ビット(12ビット)

命令長の可変長化

命令語長を大きくとると、直接指定できるメモリ空間を自由に広げることができます。しかし、必ずしも全ての命令が広いアドレス空間をアクセスする必要がある訳ではありません。そこで考えられたのが命令の種類によって命令自体の長さを変えるという方式です。以下の例は11bit目をその命令自身の命令長を示すフラグとし、そのbitが0ならば命令長は16ビット(アドレス指定は11bit)、1ならば次のワードも命令の続きとして扱い、命令長は32ビット(アドレス指定は27bit)とする方法です。

可変長命令の例

ダイレクトアドレッシングの1ワード命令

アクセスするアドレスが11ビット以内で表現可能ならこちらを使う

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
命令指定 0 アドレス指定ビット(11ビット)

ダイレクトアドレッシングの2ワード命令

アクセスするアドレスが11ビット以内で表現できないならこちらを使う(最大は27ビットまで)

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
命令指定 1 アドレス指定ビット(27ビット)
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
左の命令のアドレス指定の続き

この方法は頻繁にアクセスするメモリをメモリ空間の上位に置き、それほどアクセスしないメモリはメモリ空間の下位に置くことでプログラムを格納するメモリを効率的に使う事ができます。

イミディエイトアドレッシング

命令コード内に「アクセスするアドレス」ではなく、必要とする値そのものを書ける命令(イミディエイトアドレッシング命令)があれば、定数を予めメモリ上に用意しておく必要は無くなりプログラミングは格段に楽になります。以下は前述の可変長命令に対し、10bit目をフラグとして用い、それが0ならダイレクトアドレッシング、1ならイミディエイトアドレッシングとする命令形態の一例です。

イミディエイトアドレッシングを追加した可変長命令の例

ダイレクトアドレッシングの1ワード命令

アドレス指定ビット(10ビットまで)で指定されるメモリに格納されている値を命令で用いる

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
命令指定 0 0 アドレス指定ビット(10ビット)

ダイレクトアドレッシングの2ワード命令

アドレス指定ビット(26ビットまで)で指定されるメモリに格納されている値を命令で用いる

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
命令指定 1 0 アドレス指定ビット(26ビット)
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
左の命令のアドレス指定の続き

イミディエイトアドレッシングの1ワード命令

値指定ビットで指定された値(10ビットまで)を命令で用いる

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
命令指定 0 1 値指定ビット(10ビット)

イミディエイトアドレッシングの2ワード命令

値指定ビットで指定された値(26ビットまで)を命令で用いる

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
命令指定 1 1 値指定ビット(26ビット)
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
左の命令の値指定の続き

インダイレクトアドレッシングの追加

指定アドレスに格納されている値をアドレスと見做し、そのアドレスに格納されている値を使用するアドレッシング形式があると領域を連続してアクセスする処理(例えばC言語のmemcpy()相当の処理)の記述が格段に容易になります。
このようなアドレッシングをインダイレクトアドレッシングと呼びます。以下はアドレッシングモードを指定するビットを3bit(9ビット目から11ビット目)として、ダイレクトアドレッシング、イミディエイトアドレッシング、インダイレクトアドレッシングのそれぞれに対し、1語長命令と2語長命令を用意した命令形式の一例です。

イミディエイトアドレッシングとインダイレクトアドレッシングを追加した可変長命令の例

ダイレクトアドレッシング

アドレス指定ビット(9ビットまで)で指定されるメモリに格納されている値を命令で用いる

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
命令指定 アドレス指定ビット(9ビット)

イミディエイトアドレッシング

値指定ビットで指定された値(9ビットまで)を命令で用いる

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
命令指定 1 値(9ビット)

インダイレクトアドレッシング

アドレス指定ビット(9ビットまで)で指定されるメモリに格納されている値をアドレスと見做し、そのアドレスに格納されている値を命令で用いる

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
命令指定 2 アドレス指定ビット(9ビット)

ダイレクトアドレッシングの2ワード命令

アドレス指定ビット(25ビットまで)で指定されるメモリに格納されている値を命令で用いる

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
命令指定 3 アドレス指定ビット(25ビット)
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
左の命令のアドレス指定の続き

イミディエイトアドレッシングの2ワード命令

値指定ビットで指定された値(25ビットまで)を命令で用いる

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
命令指定 4 値指定ビット(25ビット)
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
左の命令の値指定の続き

インダイレクトアドレッシングの2ワード命令

アドレス指定ビット(25ビットまで)で指定されるメモリに格納されている値をアドレスと見做し、そのアドレスに格納されている値を命令で用いる

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
命令指定 5 アドレス指定ビット(25ビット)
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
左の命令のアドレス指定の続き

その他のアドレッシングモード

これまでに紹介したアドレッシングモード以外にも様々なアドレッシングモードがあります。代表的なものには「インデックス修飾」や「ベース相対」等があります。これらのアドレッシングモードもとても有益なのですが、実際にCPUを作る場合、思いついた機能を全て実装してしまうと膨大な回路になってしまいます。

特に標準ロジック(74シリーズ)のみで実装する場合は、全てのアドレッシングモードを実装するのは個人の趣味としての作業の限界を超えてしまいます。このためアドレッシングモードは実際の実用頻度と部品数や配線数を考慮しながら慎重に検討する必要があります。


RETROF-16のアドレッシングモード

アドレッシングモードを4種類に限定

RETROF-16は15cm×20cmの片面PCBに、50個ほどのTTL(74LSシリーズ)とSRAMを配置した16ビットコンピュータです。
前章では一般的な教科書にある様々なアドレッシングモードを紹介しましたが、それら全てのアドレッシングモードを実装するのは基板面積が限られているため現実的ではありません。そこでRETROF-16では、コンピュータとしての実用性を保ちつつ、内部回路を極力単純にするためにアドレッシングモードを4種類に絞りました。何れも1アドレス(シングルアドレス)方式です。
原則全ての命令に対して4種類のアドレッシングモードが存在しますので、命令コード中を2bitをアドレッシングモード指定ビットとしています。以下、このアドレッシングモードの指定ビットはオペコードにもオペランドにも含めないとして話を進めます。

RETROF-16の命令語のビット構成はオペランド長8bitに限定して設計しました。これは74シリーズの多くは4bit、もしくは8bit単位での処理に適す様に作られているためです。

RETROF-16のアドレッシングモード(原案)

8bitイミディエイトアドレッシング(即値アドレッシング)

オペランドには値を直接記述する
例えば加算命令をADDとするとこのモードでの ADD 1 はアキュムレータに1を加えることを意味する

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
命令指定 0 0 値(0から255)

ダイレクトアドレッシング(直接アドレッシング)

オペランドにはアドレスを記述する
例えば加算命令をADDとするとこのモードでの ADD 1 はアキュムレータに1番地の内容を加えることを意味する

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
命令指定 0 1 アドレス(0から255)

インダイレクトアドレッシング(間接アドレッシング)

オペランドには間接アドレスを記述する
例えば加算命令をADDとするとこのモードでの ADD 1 はアキュムレータに1番地の内容が示すアドレスの内容を加えることを意味する

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
命令指定 1 0 アドレス(0から255)

16bitダイレクトアドレッシング(2ワード形式の即値アドレッシング)

オペランドには8ビットでは表現できない大きな値を記述する
バンク指定ビットはメモリ空間拡張用としてリザーブするが実際には使用しない、このため指定できる値は0から65535となる

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
命令指定 1 1 バンク指定(拡張用見実装)
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
値(0から65535)

仮想レジスタの概念

RETROF-16では、アドレス空間の上位256ワードをレジスタとして見做します。正確にはレジスタではなく、仮想レジスタなのですが、これにより、プログラミング時におけるアドレッシングモードの使い分けがより直感的になります。
以下は前述の命令セットを「レジスタ」という言葉を用いて書き直したものです。これは「メモリ上位256ワード」を「レジスタ」としただけであり、ハードウェアそのものは何も変わりません。

RETROF-16のアドレッシングモード(レジスタの概念を導入後)

8bitイミディエイトアドレッシング

オペランドには値を直接記述する
例えば加算命令をADDとするとこのモードでの ADD 1 はアキュムレータに1を加えることを意味する

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
命令指定 0 0 値(0から255)

レジスタアドレッシング

オペランドにはレジスタ番号を記述する
例えば加算命令をADDとするとこのモードでの ADD 1 はアキュムレータに1番レジスタの内容を加えることを意味する

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
命令指定 0 1 レジスタ番号(0から255)

レジスタ間接アドレッシング

オペランドにはレジスタ番号を記述する
例えば加算命令をADDとするとこのモードでの ADD 1 はアキュムレータに1番レジスタが示すアドレスの内容を加えることを意味する

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
命令指定 1 0 レジスタ番号(0から255)

16bitダイレクトアドレッシング(2ワード形式の即値アドレッシング)

オペランドには8ビットでは表現できない大きな値を記述する
バンク指定ビットに対応する回路は実装していない、このため指定できる値は0から65535となる

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
命令指定 1 1 バンク指定(将来用)
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
値(0から65535)

1ワード分岐命令の改良

RETROF-16は全ての命令に対してアドレッシングモードを決めるビットが存在するため、アドレッシングモードは転送命令や演算命令だけではなく、分岐命令にもそのまま適用されます。

その場合、8bitイミディエイト分岐命令なるのが発生してしまいます。つまり0~255番地への分岐命令です。数Kワードあるいは十数Kワードからなるプログラムを想定した場合、分岐先のアドレスが0~255番地に収まることは稀ですので、8bitイミディエイト分岐命令は無意味な命令となります。そこで分岐命令に限り8bitイミディエイトモードはプログラムの下位8bitのみを更新することにします。
これにより分岐命令とその他の命令で「イミディエイト」の意味が異なることになりますが、1ワード分岐命令の利用価値が飛躍的に向上します。

RETROF-16の8bitイミディエイトアドレッシングモードの改良

8bitイミディエイトアドレッシング

値を直接指定する。但し分岐命令の場合は分岐先アドレスの下位8ビットをしていする(上位8ビットは変化しない)
例えば加算命令をADDとするとこのモードでの ADD 1 はアキュムレータに1を加えることを意味する
また無条件分岐命令をJMPとすると JMP 1 は現在のプログラムカウンタがnnnn(16進)の場合、nn01番地への分岐を意味する

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
命令指定 0 0 オペランド(0から255)

RETROF-16の命令

個々の命令の詳細決定

アドレッシングモードの次は実装する個々の命令を決めます。この作業も慎重に進めないと、利用価値の無い命令ばかりが増えてしまいソフトウエアを作成する際に苦労する事になります。
ここまでにアドレッシングモードの指定に2ビット、オペランドの指定に8ビットを使用したので、命令の区別には残りの6ビットが使えます。
これは64種類の命令を実装できる事を意味しますが、漠然と64種類の命令を考えて実装したのでは回路が実装不可能なほど複雑になってしまいます。この点に注意を払いながら必要な命令を順次決めていきます。

演算命令(ロード命令を含む)

まずは演算命令から考えます。演算の種類は加減算と論理演算(AND/OR/EXOR等)が考えられますが、実装できる演算の種類は使用するALU(算術/論理演算ユニット)に左右されます。RETROF-16では74LS382を使い演算の種類は8種類としました

(参考)ALU(算術/論理演算ユニット)74S382

 ピン配置(上から見た図) ピン略称 I/O 説明


74LS382のピン配置
画像はTIのHPより入手
Vcc / GND 電源(5V)とGND
A3~A0 テータ(被演算子)入力A(A0が最下位ビット)
B3~B0  データ(被演算子)入力B(B0が最下位ビット)
Cn キャリー(ボロー)入力。加算時にHならば演算結果は+1され、減算時にLならば演算結果は-1される
F3~F0 演算結果出力(F0が最下位ビット)
Cn+4 キャリー(ボロー)出力。加算時ならキャリーが発生する時に、減算時ならボローが発生しない時にHとなる。加減算以外の場合の本端子の真理値は本表欄外(注1)参照
OVR 入力ABを符号付と見做しての加減算時に、キャリー(ボロー)は発生しないが符号ビットが正しくない時にHとなる。加減算以外の場合の本端子の真理値は本表欄外(注1)参照
S2~S0(注2) 演算種別指定。加減算及び論理演算(AND/OR/EXOR)を指定可能(詳細は下記参照)
000:出力Fは入力A/Bの値に拘らず常に出力Fの全ビットがLとなる
001:出力F = 入力B - 入力A
010:出力F = 入力A - 入力B
011:出力F = 入力A + 入力B
100:出力F = 入力A XOR 入力B
101:出力F = 入力A OR 入力B
110:出力F = 入力A AND 入力B
111:出力Fは入力A/Bの値に拘らず常に出力Fの全ビットがHとなる

(注1)加減算以外の演算モードでは、Cn+4とOVRは両者共に結果的に出力F3がHになる入力ABを与え、かつ入力CnをHとした時にのみHとなる

(注2)オペランドで指定された演算コードは、再配置して演算種別指定端子に与えるため、命令の持つ演算種別と74LS382の持つ演算種別は一致しない

演算命令のビット構成

演算命令(ロード命令を含む)

上位3ビットの001が演算命令であることを意味する
演算指定の3ビットは74LS382の演算種別選択端子に直結されており、74LS382の仕様に従って演算種別が決まる
MODEでアドレッシングモードが決まり、それによって指定された値とアキュムレータとの演算を行い、結果はアキュムレータ自身に格納する。

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 0 1 演算指定 MODE オペランド

演算指定
000:【ロード】実効値(モードとオペランドで決まる値)をアキュムレータに格納する。(下記「74LS382演算種類の拡張」参照)
001:【インクリメント】実効値に1を加え、アキュムレータに格納する。(下記「74LS382演算種類の拡張」参照)
010:【デクリメント】実効値から1を引き、アキュムレータに格納する。(下記「74LS382演算種類の拡張」参照)
001:【加算】アキュムレータに実効値を加え、アキュムレータに格納する。
011:【減算】アキュムレータから実効値を引き、アキュムレータに格納する。
001:【論理積】アキュムレータに実効値との論理積をとり、アキュムレータに格納する。
001:【論理和】アキュムレータに実効値との論理和をとり、アキュムレータに格納する。
001:【排他的論理和】アキュムレータに実効値との排他的論理和をとり、アキュムレータに格納する。

74LS382演算種類の拡張

演算指定のうち、ロード、インクリメント、デクリメントの3つは74LS382が有している演算モードではありません。これは以下の様にして実現しています。

【ロード】    アキュムレータをクリアしてから、実行値とアキュムレータの値との論理和をとる。
【インクリメント】アキュムレータをクリアしてから、実行値とアキュムレータの値をキャリー付で加算する。
【デクリメント】 アキュムレータをクリアしてから、実行値からアキュムレータの値をボロー付で減算する。

74LS382ではなく74LS181を使用すると、この様な小細工をせずに各種命令を実現できます。しかし74LS181は24pinであり74LS382よりも多くの基板面積を占有します。この点は一長一短なのですが、74LS382を8個ほど入手できたので今回は74LS382を用いることにしました。


分岐命令

RETROF-16は(無条件分岐も含め8種類)の条件分岐命令を備えています。この8種類は他の命令では演算指定ビットとして用いている3ビットを条件指定として使います。

分岐命令の詳細

分岐命令

上位3ビットの100が分岐命令であることを意味する
条件に一致しているならば、MODEとオペランドで決定する実効値のアドレスに分岐する

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 0 0 条件指定 MODE オペランド

分岐条件

条件指定
000:無条件に分岐する
001:VRAMが帰線処理中でなければ分岐する(帰線処理に関する解説は別途)
010:最後に行った演算がキャリー、もしくはボローを発生していたなら分岐する
011:最後に行った演算がキャリー、もしくはボローを発生していなければ分岐する
100:最後に行った演算がオーバーフローを起こしていたなら分岐する
101:最後に行った演算がオーバーフローを起こしていなければ分岐する
110:現在のアキュムレータの最上位bitがLならば分岐する
111:現在のアキュムレータの最上位bitがHならば分岐する

ストア命令

アキュムレータの内容をメモリに書込むのがストア命令であり、これも1アドレス式の命令体系では必須命令です。
演算モードとオペランドで決まる実効値が主メモリに対するアドレス値となります。

尚、ストア命令には「演算」という概念はありません。このためストア命令における演算指定ビットは無効(何を指定しても同じ)となります。これはでは無駄な命令が生じますので、RETROF16ではこの3bitを分岐命令と同じく条件指定ビットとしています。このビットを000以外にすると「条件付ストア命令」となります。

ストア命令のビット構成

ストア命令

上位3ビットの101がストア命令であることを意味する
MODEとオペランドでアドレスが決まり、そこにアキュムレータの値を格納する。条件指定の3ビットの詳細は分岐命令と同じ

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 0 1 条件指定 MODE オペランド

ビデオRAMアクセス命令

RETROF-16は32KBのビデオRAMを有しており、液晶モニタに対し解像度256×256で16色カラーのグラフィック表示が可能です。
このビデオメモリに対しても主メモリと同様にロードとストア命令を用意します。
この命令は入出力命令の一形態でありCPUとして必須の命令ではありません。しかしRETROF-16は液晶ディスプレイに自由に画像を表示できる機能を内蔵することが製作の大きな目的の一つであった為、ビデオRAMアクセス命令を他の命令とは独立した専用命令として実装します。

ビデオRAMアクセス命令のビット構成

ビデオRAMロード命令(演算命令の一種として実装)

上位3ビットの000がビデオRAMとの演算命令であることを意味する
演算指定等の詳細は主メモリに対する演算命令と全く同じ。ビデオRAMのアドレスと画面上のドットとの対応の詳細に関しては別途解説予定

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 0 0 演算指定 MODE オペランド

ビデオRAMストア命令

上位3ビットの110がビデオRAMへのストア命令であることを意味する
モードとオペランドでアドレスが決まり、そこにアキュムレータの値を格納する。条件指定の3ビットの詳細は分岐命令と同じ

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 0 条件指定 MODE オペランド

プログラムカウンタ取得命令

プログラムカウンタの値を取得する命令は、サブルーチンコール時の戻り番地を保存するために不可欠な命令です。

プログラムカウンタ取得命令のビット構成

プログラムカウンタ取得命令

上位3ビットの010がプログラムカウンタ取得で命令であることを意味する
プログラムカウンタとアキュムレータとの演算を行い、結果をアキュムレータに格納する
0~9ビット目は意味をもたない(任意でよい)

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 1 0 演算指定 未使用

入出力命令

RETROF-16は16bitの入力ポート2組と、同じく16bitの出力ポートを2組を有してます。
入力ポートの1組は基板上のトグルスイッチ(16個)に接続、出力ポートは2組とも基板上の32個のLED(32個)に接続されています。

これらのトグルスイッチとLEDはプログラムの実行停止時に手動でメモリ内容を書き変えるために用意したものですが、プログラムの実行時には、入出力機器としても利用できる様に回路を工夫しています。

入出力命令一覧

IN(入力)命令

上位3ビットの011が入力命令であることを意味する
MODEとオペランドで決定するアドレスに入力ポートの値を格納する

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 1 1 ポート MODE オペランド

ポートは最大8組実装できるが、基板上には2組(16個のトグルスイッチと16bitの外部入力)しか実装していない
このため演算指定bitに000を指定した場合はポート1(基板上のトグルスイッチ)、000以外を指定した時はポート2(外部入力端子)となる

OUT(出力)命令

上位3ビットの111が出力命令であることを意味する
MODEとオペランドで決定する実効値と、アキュムレータの値を3基板上の32個のLEDに出力する(点灯=H、消灯=L)
条件指定の3ビットの詳細は分岐命令と同じ

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 条件指定 MODE オペランド

プログラムカウンタ取得命令の拡張による命令追加

命令コードの上位3ビットで命令の種類を区別する方法では、これまでに紹介した「ロード」「セーブ」「ビデオロード」「ビデオセーブ」「分岐」「プログラムカウンタ取得」「入力」「出力」の8種類以上の命令は実装できません。
しかし命令によっては使用していないビットを有するものがありますので、これをデコードすることで命令追加は可能です。
RETROF-16はプログラムカウンタ取得命令の未使用ビットをデコードし、「右シフト」と「停止」を追加しています。

【再掲】プログラムカウンタ取得命令
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 1 0 演算指定 未使用
この命令の8ビット目と9ビット目をデコードして
命令を追加します

拡張命令

プログラムカウンタ取得命令
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 1 0 演算指定 1 1 未使用

元々のプログラムカウンタ取得命令

シフト命令
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 1 0 演算指定 1 0 未使用

アキュムレータのビット列を右に1ビットシフトする命令、
除算サブルーチンを作るのに必要

HALT(停止)命令
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 1 0 演算指定 0 - 未使用

プログラムを停止する命令、8ビット目は任意


命令設計を終えて

実用性を保ちつつ部品数を減らすことを第一優先としたため、かなり「いびつ」な命令セットとなりました。
スタック操作命令も、相対ジャンプ命令も無く、任意のレジスタをインクリメントする普通なら当たり前の命令すらありません。
レジスタをインクリメントしたい場合は「アキュムレータにレジスタ値を転送して、1加えてから、レジスタに戻す」という3ステップを要します。それでも部品数を極限まで削減したCPUの命令セットとしてはまずまずだと自負しています。
しかし命令セットの評価は、実際にその命令セットを用いて大規模なプログラムを実際に構築するまで、評価をする事はできません。
この件に関しては、「コンパイラの自作編」や「アプリケーション作成編」で詳しく述べたく思います。

2013年11月吉日初出