CPU


Intel 8086 のアーキテクチャとISAについて



現在のPC界を独占しているといっても過言ではないx86系(含x64系)プロセッサ。
この祖先は遙か昔(あくまでこの業界で)の1978年に産声を上げた。
当時「8086」という型番は単に「8085の次だから」という理由でしかなかったが、「80186」「80286」「i386」「i486」と進化を続け、「x86」という呼び名を定着させた。
その後数十年経った今でも、基本アーキテクチャとISAは変わることなく使い続けられている。

Intelが8080から8800(後のiAPX432)へと販売戦略をシフトする中で、その間の“つなぎ”として開発したのが8086だ。
結果的に、8800はほとんど世に出回ることがなく、本来“つなぎ”として設計された8086とその後継の80x86(後にx86やIA-32と呼ばれる)が、その後のPC用プロセッサ界をほぼ独占することになる。

ここでは、8086がどういったところで“8080からのつなぎ”的な要素を持っているのかといったことに重点を置きながら、アーキテクチャとISAについて書いてみたいと思う。
元になった8080のアーキテクチャとISAについてはこちら

8bitのプロセッサである8080(Z80の前身)は、コード密度は特筆すべきものであったが、

演算結果を格納できるレジスタがアキュムレータしかない
汎用レジスタが少ない(当時の8bitプロセッサとしては多い方だが)
Mレジスタ(HLレジスタが指し示す先のメモリ内容)を経由しないと対メモリ演算ができない
アドレスバスが16bitと狭い
リロケータブルなプログラムが書けない
アドレッシングモードが極端に少ない

などの点から、こうした欠点を補うことのできるプロセッサが必要とされていた。
Intelはこうした欠点をすべて補うことのできる、強力かつ複雑で巨大な32bitプロセッサ“8800”の開発を開始するのだが、それが完成し販売を開始できるまでには相当な月日がかかりそうだった。(8800は後に「iAPX432」と名を変えて販売されている)
その間ライバル社に市場を奪われかねないと危惧したかどうかは知らないが、8800が世に出回るまでの間、とりいそぎ8080との互換性を最大限に考慮した上で、8080の知識があれば簡単に移行できる16bitのプロセッサを開発・販売することになった。
これが8086だ。
バイナリコードもニモニックも8080とはまったく違うものとなっているが、8080からのスムースな移行ができるよう配慮されている。
8080のアセンブリソースコードを8086のアセンブリソースコードに変換するソースコードトランスレータがあり、これとアセンブラを併用すれば8080のアセンブラソースコードから8086用のバイナリを生成する事ができた。
今も昔もそうなのだが、この「昔のものに手を加えることなく動く」ということは、プロセッサの進化の中で重要な点であり、これを最重視した姿勢が後のx86大流行に繋がることとなる。(Intelの思惑とは違うものの・・)

8086は8080に比べて、主に以下のような変更が行われている。(細かく上げれば切りがないが)

ALUが16bitに拡張されている。
レジスタが増えている。
レジスタは8bitをペアにして16bitにできるという考え方から、16bitを8bit2つに分割できるという考え方になっている。
すべてのレジスタを演算用(アキュムレータ)として使用できる。(乗除算を除く)
レジスタ対メモリ演算ができる。(乗除算を除く)
メモリ対レジスタ演算ができる。(メモリをアキュムレータのように使える。乗除算を除く)
アドレスバスが20bit(1MB)に拡張されている。
コードやデータ、スタックのリロケータブル化が可能になっている。

アドレッシングモードを一覧しておく。

1. レジスタ(レジスタ直接)
2. レジスタインダイレクト(レジスタ間接)
3. 8bitディスプレースメント付きレジスタインダイレクト
4. 16bitディスプレースメント付きレジスタインダイレクト
5. インデックス付きレジスタインダイレクト
6. 8bitディスプレースメント・インデックス付きレジスタインダイレクト
7. 16bitディスプレースメント・インデックス付きレジスタインダイレクト
8. エクステンド(オフセットアドレス値直接指定)
9. イミディエイト(即値)

8080に比べて飛躍的にアドレッシングモードが増えている。
レジスタはレジスタ、メモリはメモリと正確に区別されており、8080のMレジスタのような「特定のレジスタが指し示す先のメモリはレジスタ扱いになる」といった仕様も存在しない。
8080よりも直交性が高く記述の自由度が高いISAだと思えるが、実際のところはレジスタインダイレクト系でのレジスタ直交がかなり低い。
また、演算や即値の移動などでは、同じ命令でもレジスタの種類によってコードサイズが変わることがある。
オペコード内にデータの方向(レジスタ→メモリか、メモリ→レジスタか)とデータサイズ(8bit/16bit)を指定するビットが入っていて、アドレッシングモードとオペランドの一部(レジスタ直接とレジスタ関節)はModR/M(モードアーエム)と呼ばれる1バイトで指定する。
ModR/Mは「addressingMODe, Register / Memory」の略。
AXとALは特待生であり、これらとメモリの移動や即値の演算、論理演算などはModR/Mが存在せず最小バイトにアセンブルされるが、他のレジスタになるとModR/Mが付く。
なので、ADD CX,DXやMOV DX,[BX]といったコードは、オペコード6bit+データ方向1bit+データサイズ1bit+ModR/M8bitの合計16bitになる。
ModR/Mで指定できるのは、レジスタまたはメモリのオペランドだが、片方はレジスタ固定であり、もう片方にレジスタまたはメモリを指定できる。
つまりメモリ同士は指定できないので、メモリ間の移動や演算は必ず何かのレジスタを介してから行うことになる。
レジスタに対するINC命令やDEC命令などには、1バイトのもの(ModR/Mがない命令)と2バイトのもの(ModR/Mがある命令)が重複して存在する。
セグメントオーバーライド(通常、レジスタの種類によってどのセグメントレジスタとオフセットを加算するかが決まっているが、規定以外のセグメントレジスタと加算したい場合に使用する機能)を行う際には、専用のプリフィックスが1バイト前置される。(CS:26、SS:2E、DS:36、ES:3E)

全レジスタを一覧しておく。

AX / CX / DX / BX / SP / BP / SI / DI / CS / SS / DS / ES

AX/CX/DXの3本がデータレジスタ専用、BX/BPの2本がデータレジスタ・ベースポインタ兼用、SI/DIの2本がデータレジスタ・インデックスレジスタ兼用、SPがデータレジスタ・スタックポインタ兼用、CS/SS/DS/ESの4本がセグメントレジスタ専用となっている。
これらに加えて、IP(インストラクションポインタ)とF(フラグ)がある。
レジスタ長はすべて16bit。
AX/CX/DX/BXの4本は、AL/AHのようにして上位8bitと下位8bitに分けて、それぞれ個別のレジスタとしても使える。(8080のBC/DE/HLレジスタようなイメージ)
つまり8本の8bitレジスタとしても使える。
この4本のうちBXだけはアドレスレジスタとして使用でき、レジスタインダイレクトと、インデックス付きレジスタインダイレクトのベースポインタ部分にのみ使用できる。
分割したBL/BHはアドレスレジスタとしては使えず、データレジスタ専用となる。
SIとDIはインデックスレジスタであり、レジスタインダイレクトと、インデックス付きレジスタインダイレクトのインデックス部分にのみ使用できる。
BPはベースポインタであり、BXと同等に使用できるが、8bitに分割することはできない。
またBPのみ、ディスプレースメント無しのレジスタインダイレクトアドレッシングができない(対応するコードはエクステンドアドレッシングになっている)
上記のとおり、アドレスレジスタとしてのレジスタ直交が低いが、データレジスタとしての直交性は高く、8本すべてをデータレジスタとしてもアキュムレータとしても使用できる。

CS/SS/DS/ESはセグメントレジスタであり、コードやデータが置かれた実アドレスは、このレジスタを4bit左にシフトしたものとアドレスレジスタを加算して、20bitとして得られる。
こうして得られるアドレスは、「セグメント:オフセット」という具合に表記される。(例えば、「DS:BX」という具合)
セグメントレジスタには直接アドレス値を入れることができないため、他のレジスタを介して代入することになる。
また、データレジスタやアキュムレータとして使うことはできない。
どのセグメントレジスタとどのアドレスレジスタを加算するかは、プログラムで指定できるが、何も指定しなければ下のようになる。

CSはコードセグメントであり、通常IPと加算される。
SSはスタックセグメントであり、通常BPやSPと加算される。
DSはデータセグメントであり、通常BXやSI、DIと加算される。
ESはエクストラセグメントであり、通常どれとも加算されない。



セグメントとは仮想メモリの実装手段のうちのひとつであり、コード・データ・エクストラ(一般には第2のデータ)・スタックをそれぞれ1MBのエリアのどこに置いても動く、という仕様になっている。
セグメント:オフセットは4bitズレているので16バイト単位ではあるものの、プログラムやデータのアドレスをオフセットのみで指定しているようなコードであれば、コード・データ・エクストラ・スタック各々をリロケータブルに配置することが可能というわけだ。
つまりオフセットアドレスは、セグメント内では絶対アドレスであり、全メモリからは相対アドレスであるということになる。
オフセットを指定するアドレスレジスタは16bitなので、コード・データ・エクストラ・スタックはそれぞれ64KBの容量を持つことが出来る。
すべての領域は重なっても良いが、重ならないように配置すれば256KBのリロケータブルな領域を作ることが出来る。
8080からして見ればこれは大きな拡張であり、64KB内にコード・データ・スタックすべてを置かなければならなかった狭苦しさから解放されるCPUだったということだ。
CS/SS/DS/ESすべてを同じ値にすると、8080とまったく同じメモリモデルが出来上がり、リロケータブルな64KBの世界が作れることになる。
つまり、ソースコードトランスレータとアセンブラを使って8080から8086のバイナリを生成し、CS/SS/DS/ESすべてを同じ値にすれば、8080のソースコードに何の手を加えることなく8086上で動作させることができ、それを1MB領域の中のどこに配置しても動く、ということこそが「8080との最大の互換性」だった。
しかし逆を言えばこの仕様は、いざ64KBを超えるコードを作成しようとしたり、64KBを超えるデータやスタックを扱おうとしたりしようものなら、突然リロケータブルでなくなる上に、頭が痛くなるような複雑なコーディングが必要となってくるということを意味する。
具体的に言うと、セグメントレジスタの値をリアルタイムに操作するようなコーディングを強いられる羽目になる。
本来は、セグメントという仕組みは「オフセットアドレスのみ意識すればリロケータブルなコードを書くことができ、1MB空間の実アドレスを意識する必要はない」という「仮想メモリ」を実現するためのものだった。
しかし世の中はより便利なソフトウェアを求め、出回るOSやアプリケーションのサイズは肥大を続け、当時のプログラマーは64KBの壁を超えるために「実アドレス」を意識せざるを得なくなってしまった。(68000などのリニアなメモリ空間を持っていたプロセッサと比較されたのも大きな原因かもしれないが)
その結果、「12000h番地」を示すのに「1000:2000」「1100:1000」「1200:0000」と何通りも組み合わせが出来てしまうということが意識の中に生まれてしまい、「あーややこしい!」となってしまうことになった。
結果、「8086のセグメントは悪」というイメージが世の中に定着してしまうこととなる。
1MBが狭いのでもなく、64KBの壁が困るのでもなく、セグメントが悪なのでもない。
8086と68000は根幹から設計哲学がまったく違うものであり、これらを“同じ16bitプロセッサ”として“比較”することはできないと思う。





[ 戻る ]