木曜日, 5月 23, 2013

命令セット

現在設計中のプロセッサは、命令語長4ビット、データ語長32ビットの合わせて36ビット幅で内部メモリへのアクセスを行います。

命令フェッチの際、4ビット長の命令と同時に読み出された32ビット長のデータを、命令実行の際のオペランドYとします。

 

 命令(4ビット)  オペランドY(32ビット)

 

レジスタとしては、プログラムカウンタPCの他に、32ビットのレジスタXに1ビットのキャリーフラグCを付加した33ビット長のアキュムレータAccを持ちます。

 

 プログラムカウンタPC

 

アキュムレータAcc(33ビット):

 キャリーフラグC  レジスタX(32ビット)

 

ちなみにDE0-Nano上のFPGA(EP4CE22)では36ビット×16Kワードの内部メモリが確保可能なので、プログラムカウンタPCは14ビットになります。

その他、各プロセッサ要素固有の内部状態として、排他制御用のロック状態フラグlockがあります。ロック状態フラグはLOCK(TRYLOCK)命令でセットされ、UNLOCK命令でリセットされます。ロック状態のセットは対象となるプロセッサでしか実行できませんが、リセットはオペランドでプロセッサを指定できるので、任意のプロセッサで実行可能です。

命令コードとその動作内容を、内部状態の変化として表したものが以下の表になります。ただし、これはあくまでも暫定的なもので、実装の都合により変更される可能性があります。(8/16変更しました)

 

コード Y[31..28] 命令 PC関連 Acc関連 その他動作内容
0000   GET, DATA PC := X X := Y  
0001 0000  JUMP PC := Y    
0001 0001 JUMPNZ if(X!=0) PC := Y
else PC := PC + 1
   
0001 0010 JUMPNP if(X<=0) PC := Y
else PC := PC + 1
   
0001 0011  JUMPM if(X<0) PC := Y
else PC := PC + 1
   
0001 0100 JUMPNM if(X>=0) PC := Y
else PC := PC + 1
   
0001 0101 JUMPP if(X>0) PC := Y
else PC := PC + 1
   
0001 0110 JUMPZ if(X==0) PC := Y
else PC := PC + 1
   
0001 0111 UNLOCK PC := PC + 1   unlock(Y)
0001 100- JUMPNC if(C==0) PC := Y
else PC := PC + 1
   
0001 101- JUMPC if(C==1) PC := Y
else PC := PC + 1
   
0001 110- TRYLOCK if(!lock) PC := Y
else PC := PC + 1
  lock := 1
0001 111- LOCK if(lock) PC := Y
else PC := PC + 1
 
0010 code PUT if(collision) PC := PC
else PC := PC + 1
  if(!collision) [Y] := code:X
0011   HALT PC := PC    
0100   MUL PC := PC + 1 X := Y * X  
0101   MULH X := (Y * X) >> 32  
0110   SHIFT if(Y<0) Acc := X>>Y
else Acc := X<<Y
 
0111   IO Acc := io_port(Y, Acc)  
1000   LOAD X := Y  
1001   OR X := Y or X  
1010   AND X := Y and X  
1011   XOR X := Y xor X  
1100   ADD Acc := Y + X  
1101   SUB Acc := Y — X  
1110   ADC Acc := Y + X + C  
1111   SBB Acc := Y — X — C  

 

基本的に4ビットの命令コードで命令実行内容が決定されますが、一部ジャンプ命令等はオペランドYの上位4ビットを利用して命令の拡張が行われています。

ジャンプ命令は条件に応じてPCの値を変化させるものですが、その後PCの下位ビットがメモリバンクと一致しない間は内部的にHALT命令を実行し、回転待ちを行います。

PUT命令によるメモリ書込みは、実際にはスレッドが目的のメモリバンクに到達した時点まで遅延されます。ここでもし、あるバンクへの書き込みが未完了の状態で、また同一バンクへの書き込みを実行しようとした場合(collision=1)、一旦PC := PCとして一周分の回転待ちの間に全ての遅延書き込みを完了させた上で、再び書き込み命令を実行します。二度目の書き込みでは全ての遅延書き込みが完了しているため、今度は必ず成功します。

PUT命令によるオペランド書き換えの際、実際にはYの上位4ビットの値で命令コードの書き換えも行われます。これは、命令コードとオペランドを同一メモリにした方が効率が良い(パリティビットが使える)のと、排他制御の際に同時書き換えができた方が都合が良いといった理由があります。ただ、オペランドだけ書き換える際にも命令コードを指定しなければならないので、ハンドアセンブルの場合は若干面倒です。基本的には、こういった作業をコンパイラ等で自動化することを想定した設計となっています。

ここで、設計上最も悩ましいのがランダムアクセス読み出しです。このプロセッサでは配列やテーブルの機能を実現するために最低限必要な命令として、DATA命令を実装しています。DATA命令はLOAD命令と同様にオペランドYの値をレジスタXに代入しますが、同時に代入実行前のレジスタXで指定された番地へのジャンプも実行します。基本的な使い方としては、レジスタXに戻り番地を入れて目的のDATA命令へのジャンプを実行することになりますが、このジャンプの飛び先を変化させることでランダムアクセスが可能になります。とはいえ、いちいちジャンプ命令のオペランドを書き換えていたのでは、あまりに効率が悪くて話になりません。

ところがなんと、不思議なことに同じ命令を利用することでこれが解決します。

例えば、1000番地にデータとしてオペランド値123を持つDATA命令を置いたとします。100番地からのコードで、まずLOAD 1000でレジスタXに1000を代入し、次に101番地で次の命令番地の102をオペランドとするDATA命令を実行します。するとまず、101番地のDATA命令の結果、レジスタXの値は102となり、1000番地にジャンプします。次に1000番地のDATA命令の実行により、レジスタXの値は123となり、102番地へのジャンプが実行されます。この結果、101番地のDATA命令は、表面的にはレジスタXで指定した番地のデータを取得(X := [X])し、ついでにオペランドで指定された番地へのジャンプ(PC := Y)を実行する命令として動作します。101番地と1000番地のDATA命令は実行内容は同じですが、プログラム内での意味合いは全く違ったものになりますので、101番地の方は同じ命令コードでGETという名前を割り当てることにします。

 

PC 命令 Y X
100 LOAD 1000 1000
101 DATA(GET) 102 102
1000 DATA 123 123
102 次の命令…    

 

GET命令によるランダムアクセス読み出しは、ハードウェア的には簡単な命令を一つ実装するだけで済むので非常に低コストです。しかし、実行時にはジャンプを2回実行しなくてはならないため、回転待ちのコストが掛かります。特に、上の例の様にGET命令の戻り番地を命令の次の番地にした場合、必ず1周分の回転待ちが発生します。これがもし、GET命令、DATA命令、戻り番地を隣接するメモリバンクに配置することが可能であれば、回転待ちは2命令分にまで減少します。実際には、同じDATA命令にアクセスするGET命令が複数あると考えられるので、そう単純にはいきませんが、命令やデータの配置を工夫することで、実行効率の改善が期待されます。こういった最適化の技法は、今後興味深い研究対象として発展する可能性があります。