C++ 擬似コードで読む Comet2 の命令仕様

はじめに

情報処理の試験で使う Comet2 の命令仕様について、実際にどういう意味なのかを C++ 風の擬似コードを示して説明とします

アセンブラについては「C++プログラマのためのアセンブラ入門」でも説明しています
C/C++ の動作の詳細としてのアセンブラ入門は「C/C++ ポインタ入門」を見るといいでしょう

出典

IPA 情報処理推進機構
HOME >> 情報処理技術者試験 >> 試験実施案内 >> 「試験で使用する情報技術に関する用語・プログラム言語など」及び「試験要綱」改訂版の公開について

https://www.jitec.ipa.go.jp/1_02annai/annai_yogo_jis_kaitei.html

「試験で使用する情報技術に関する用語・プログラム言語など」 Ver 2.0(PDF形式)

https://www.jitec.ipa.go.jp/1_13download/shiken_yougo_ver2_0.pdf

別紙1 アセンブラ言語の仕様
1. システムCOMET IIの仕様

1.1ハードウェアの仕様


  1. 1語は 16ビットで、そのビット構成は、次のとおりである。

    上位 8ビット 下位 8ビット
    ビット番号 15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0
     ↑
    符号(負: 1、非負: 0)
  2. 主記憶の容量は 65536語で、そのアドレスは 0〜65535番地である。
  3. 数値は、16ビットの 2進数で表現する。
    負数は、2の補数で表現する。
  4. 制御方式は逐次制御で、命令語は 1語長又は 2語長である。
  5. レジスタとして、 GR(16ビット)、SP(16ビット)、PR(16ビット)、FR(3ビット)の 4種類がある。
    • GR(汎用レジスタ、General Register)は、
      GR0〜GR7の 8個があり、算術、論理、比較、シフトなどの演算に用いる。
      このうち、GR1〜GR7のレジスタは、指標レジスタ(index register)としてアドレスの修飾にも用いる。
    • SP(スタックポインタ、Stack Pointer)は、
      スタックの最上段のアドレスを保持している。
    • PR(プログラムレジスタ、Program Register)は、
      次に実行すべき命令語の先頭アドレスを保持している。
    • FR(フラグレジスタ、Flag Register)は、
      OF(Overflow Flag)、SF(Sign Flag)、ZF(Zero Flag)と呼ぶ 3個のビットからなり、演算命令などの実行によって次の値が設定される。
      これらの値は、条件付き分岐命令で参照される。
      • OF
        算術演算命令の場合は、演算結果が -32768〜 32767 に収まらなくなったとき 1になり、それ以外のとき 0になる。

        ((( r32 & 0x8000u ) << 16 ) != ( r32 & 0x80000000u ))
        (( static_cast<int16_t>(r32) < 0 ) != ( r32 < 0 ))
        

        論理演算命令の場合は、演算結果が 0 〜 65535 に収まらなくなったとき 1になり、それ以外のとき 0になる。

        (( r16 & 0xffff0000u ) != 0 )
        


      • SF
        演算結果の符号が負(ビット番号 15が 1)のとき 1、それ以外のとき 0になる。

        ( r16 < 0 )
        


      • ZF
        演算結果が零(全部のビットが 0)のとき 1、それ以外のとき 0になる。

        ( r16 == 0 )
        


  6. 論理加算又は論理減算は、被演算データを符号のない数値とみなして、加算又は減算する。

    算術計算命令は値を signed として、論理計算命令は unsigned として扱う


1.2 命令

命令の形式及びその機能を示す。
ここで、一つの命令コードに対し 2種類のオペランドがある場合、上段はレジスタ間の命令、下段はレジスタと主記憶間の命令を表す。

(1)ロード、ストア、ロードアドレス命令

命 令 書き方 命令の説明 FRの設定
命令コード オペランド
ロード
LoaD
LD r1,r2 r1 ←(r2) ○*1
r,adr[,x] r←(実効アドレス)
ストア
STore
ST r,adr[,x] (実効アドレス)←(r) -
ロードアドレス
Load ADdress
LAD r,adr[,x] r ←実効アドレス -

  • LD
void	LD( int16_t&	r1, int16_t	r2 ){
	r1 = r2;
	FR.OF = false;
	FR.SF = ( r1 < 0 );
	FR.ZF = ( r1 == 0 );
}
void	LD( int16_t&	r, int16_t	adr, int16_t	x ){
	LD( r, *reinterpret_cast<const int16_t*>(static_cast<uint16_t>(adr) + static_cast<uint16_t>(x)));
}
  • ST
void	ST( int16_t	r, int16_t	adr, int16_t	x ){
	*reinterpret_cast<int16_t*>(static_cast<uint16_t>(adr) + static_cast<uint16_t>(x)) = r;
}
  • LAD
void	LAD( int16_t&	r, int16_t	adr, int16_t	x ){
	r = static_cast<int16_t>(static_cast<uint16_t>(adr) + static_cast<uint16_t>(x));
}
(2)算術、論理演算命令

命 令 書き方 命令の説明 FRの設定
命令コード オペランド
算術加算
ADD Arithmetic
ADDA r1,r2 r1 ←(r1)+(r2)
r,adr[,x] r ←(r)+(実効アドレス)
論理加算
ADD Logical
ADDL r1,r2 r1 ←(r1)+L(r2)
r,adr[,x] r ←(r)+L(実効アドレス)
算術減算
SUBtract Arithmetic
SUBA r1,r2 r1 ←(r1)-(r2)
r,adr[,x] r ←(r)-(実効アドレス)
論理減算
SUBtract Logical
SUBL r1,r2 r1 ←(r1)-L(r2)
r,adr[,x] r ←(r)-L(実効アドレス)
論理積
AND
AND r1,r2 r1 ←(r1)AND(r2) ○*1
r,adr[,x] r ←(r)AND(実効アドレス)
論理和
OR
OR r1,r2 r1 ←(r1)OR(r2) ○*1
r,adr[,x] r ←(r)OR(実効アドレス)
排他的論理和
eXclusive OR
XOR r1,r2 r1 ←(r1)XOR(r2) ○*1
r,adr[,x] r ←(r)XOR(実効アドレス)

  • ADDA
void	ADDA( int16_t&	r1, int16_t	r2 ){
	int32_t	temp = static_cast<int32_t>(r1) + static_cast<int32_t>(r2);
	r1 = static_cast<int16_t>(static_cast<uint16_t>(static_cast<uint32_t>(temp)&0x0000ffffu));
	FR.OF = (( r1 < 0 ) != ( temp < 0 ));
	FR.SF = ( r1 < 0 );
	FR.ZF = ( r1 == 0 );
}
void	ADDA( int16_t&	r, int16_t	adr, int16_t	x ){
	ADDA( r, *reinterpret_cast<const int16_t*>(static_cast<uint16_t>(adr) + static_cast<uint16_t>(x)));
}
  • ADD
void	ADD( int16_t&	r1, int16_t	r2 ){
	uint32_t	temp = static_cast<uint32_t>(static_cast<uint16_t>(r1)) + static_cast<uint32_t>(static_cast<uint16_t>(r2));
	r1 = static_cast<int16_t>(static_cast<uint16_t>(temp&0x0000ffffu));
	FR.OF = !!( temp & 0xffff0000u );
	FR.SF = ( r1 < 0 );
	FR.ZF = ( r1 == 0 );
}
void	ADD( int16_t&	r, int16_t	adr, int16_t	x ){
	ADD( r, *reinterpret_cast<const int16_t*>(static_cast<uint16_t>(adr) + static_cast<uint16_t>(x)));
}
  • SUBA
void	SUBA( int16_t&	r1, int16_t	r2 ){
	int32_t	temp = static_cast<int32_t>(r1) - static_cast<int32_t>(r2);
	r1 = static_cast<int16_t>(static_cast<uint16_t>(static_cast<uint32_t>(temp)&0x0000ffffu));
	FR.OF = (( r1 < 0 ) != ( temp < 0 ));
	FR.SF = ( r1 < 0 );
	FR.ZF = ( r1 == 0 );
}
void	SUBA( int16_t&	r, int16_t	adr, int16_t	x ){
	SUBA( r, *reinterpret_cast<const int16_t*>(static_cast<uint16_t>(adr) + static_cast<uint16_t>(x)));
}
  • SUB
void	SUB( int16_t&	r1, int16_t	r2 ){
	uint32_t	temp = static_cast<uint32_t>(static_cast<uint16_t>(r1)) - static_cast<uint32_t>(static_cast<uint16_t>(r2));
	r1 = static_cast<int16_t>(static_cast<uint16_t>(temp&0x0000ffffu));
	FR.OF = !!( temp & 0xffff0000u );
	FR.SF = ( r1 < 0 );
	FR.ZF = ( r1 == 0 );
}
void	SUB( int16_t&	r, int16_t	adr, int16_t	x ){
	SUB( r, *reinterpret_cast<const int16_t*>(static_cast<uint16_t>(adr) + static_cast<uint16_t>(x)));
}
  • AND
void	AND( int16_t&	r1, int16_t	r2 ){
	r1 = static_cast<int16_t>(static_cast<uint16_t>(r1) & static_cast<uint16_t>(r2));
	FR.OF = 0;
	FR.SF = ( r1 < 0 );
	FR.ZF = ( r1 == 0 );
}
void	AND( int16_t&	r, int16_t	adr, int16_t	x ){
	AND( r, *reinterpret_cast<const int16_t*>(static_cast<uint16_t>(adr) + static_cast<uint16_t>(x)));
}
  • OR
void	OR( int16_t&	r1, int16_t	r2 ){
	r1 = static_cast<int16_t>(static_cast<uint16_t>(r1) | static_cast<uint16_t>(r2));
	FR.OF = 0;
	FR.SF = ( r1 < 0 );
	FR.ZF = ( r1 == 0 );
}
void	OR( int16_t&	r, int16_t	adr, int16_t	x ){
	OR( r, *reinterpret_cast<const int16_t*>(static_cast<uint16_t>(adr) + static_cast<uint16_t>(x)));
}
  • XOR
void	XOR( int16_t&	r1, int16_t	r2 ){
	r1 = static_cast<int16_t>(static_cast<uint16_t>(r1) ^ static_cast<uint16_t>(r2));
	FR.OF = 0;
	FR.SF = ( r1 < 0 );
	FR.ZF = ( r1 == 0 );
}
void	XOR( int16_t&	r, int16_t	adr, int16_t	x ){
	XOR( r, *reinterpret_cast<const int16_t*>(static_cast<uint16_t>(adr) + static_cast<uint16_t>(x)));
}
(3)比較演算命令

命 令 書き方 命令の説明 FRの設定
命令コード オペランド
算術比較
ComPare Arithmetic
CPA r1,r2 (r1)と(r2)、又は(r)と(実効アドレス)の算術比較を行い、比較結果によって、FRに次の値を設定する。 ○*1
r,adr[,x]
論理比較
ComPare Logical
CPL r1,r2 (r1)と(r2)、又は(r)と(実効アドレス)の論理比較を行い、比較結果によって、FRに次の値を設定する。 ○*1
r,adr[,x]
比較結果 FRの値
SF ZF
(r1)>(r2) 0 0
(r)>(実効アドレス)
(r1)=(r2) 0 1
(r)=(実効アドレス)
(r1)<(r2) 1 0
(r)<(実効アドレス)

void	CPA( int16_t	r1, int16_t	r2 ){
	FR.OF = 0;
	FR.SF = ( r1 <  r2 );
	FR.ZF = ( r1 == r2 );
}
void	CPA( int16_t	r, int16_t	adr, int16_t	x ){
	CPA( r, *reinterpret_cast<const int16_t*>(static_cast<uint16_t>(adr) + static_cast<uint16_t>(x)));
}
void	CPL( int16_t	r1, int16_t	r2 ){
	FR.OF = 0;
	FR.SF = (static_cast<uint16_t>(r1) <  static_cast<uint16_t>(r2));
	FR.ZF = (static_cast<uint16_t>(r1) == static_cast<uint16_t>(r2));
}
void	CPL( int16_t	r, int16_t	adr, int16_t	x ){
	CPL( r, *reinterpret_cast<const int16_t*>(static_cast<uint16_t>(adr) + static_cast<uint16_t>(x)));
}
(4)シフト演算命令

命 令 書き方 命令の説明 FRの設定
命令コード オペランド
算術左シフト
Shift Left Arithmetic
SLA r,adr[,x] (r)を実効アドレスで指定したビット数だけ符号を除き左にシフトする。
シフトの結果、空いたビット位置には、0 が入る。
○*2
算術右シフト
Shift Right Arithmetic
SRA r,adr[,x] (r)を実効アドレスで指定したビット数だけ符号を除き右にシフトする。
シフトの結果、空いたビット位置には、符号と同じものが入る。
○*2
論理左シフト
Shift Left Logical
SLL r,adr[,x] (r)を実効アドレスで指定したビット数だけ符号を含み左にシフトする。
シフトの結果、空いたビット位置には 0 が入る。
○*2
論理右シフト
Shift Right Logical
SRL r,adr[,x] (r)を実効アドレスで指定したビット数だけ符号を含み右にシフトする。
シフトの結果、空いたビット位置には 0 が入る。
○*2

  • 参考資料 3.シフト演算命令におけるビットの動き
    1. 算術左シフトでは、
      OF には、ビット番号 14 の値が設定される。
      ビット番号 0 の値には、0 が設定される。
      ビット番号 15 の値は変化しない。
    2. 算術右シフトでは、
      OF には、ビット番号 0 の値が設定される。
      ビット番号 15 の値は変化しない。
    3. 論理左シフトでは、
      OF には、ビット番号 15 の値が設定される。
      ビット番号 0 の値には、0 が設定される。
    4. 論理右シフトでは、
      OF には、ビット番号 0 の値が設定される。
      ビット番号 15 の値には、0 が設定される。

void	SLA( int16_t&	r, int16_t	adr, int16_t	x ){
	uint16_t	shift_count = static_cast<uint16_t>(static_cast<uint16_t>(adr) + static_cast<uint16_t>(x));
	int16_t	temp = static_cast<int16_t>( static_cast<uint16_t>(r) << shift_count );
	r = static_cast<int16_t>(( static_cast<uint16_t>(r) & 0x8000u ) | ( static_cast<uint16_t>(temp) & 0x7fffu ));
	FR.OF = ( temp < 0 );
	FR.SF = ( r < 0 );
	FR.ZF = ( r == 0 );
}
  • SRA
void	SRA( int16_t&	r, int16_t	adr, int16_t	x ){
	uint16_t	shift_count = static_cast<uint16_t>(static_cast<uint16_t>(adr) + static_cast<uint16_t>(x));
	uint32_t	temp = ( static_cast<uint32_t>(static_cast<uint16_t>(r)) << 16 ) >> shift_count;
	r = static_cast<int16_t>(static_cast<uint16_t>( temp >> 16 ));
	FR.OF = !!( temp & 0x8000u );
	FR.SF = ( r < 0 );
	FR.ZF = ( r == 0 );
}
  • SLL
void	SLL( int16_t&	r, int16_t	adr, int16_t	x ){
	uint16_t	shift_count = static_cast<uint16_t>(static_cast<uint16_t>(adr) + static_cast<uint16_t>(x));
	uint32_t	temp = static_cast<uint32_t>(static_cast<uint16_t>(r) << shift_count );
	r = static_cast<int16_t>(static_cast<uint16_t>( temp & 0x0000ffffu ));
	FR.OF = !!( temp & 0x00010000u );
	FR.SF = ( r < 0 );
	FR.ZF = ( r == 0 );
}
  • SRL
void	SRL( int16_t&	r, int16_t	adr, int16_t	x ){
	uint16_t	shift_count = static_cast<uint16_t>(static_cast<uint16_t>(adr) + static_cast<uint16_t>(x));
	uint32_t	temp = ( static_cast<uint32_t>(static_cast<uint16_t>(r)) << 16 ) >> shift_count;
	r = static_cast<int16_t>(static_cast<uint16_t>( temp >> 16 ));
	FR.OF = !!( temp & 0x00008000u );
	FR.SF = ( r < 0 );
	FR.ZF = ( r == 0 );
}
(5)分岐命令

命 令 書き方 命令の説明 FRの設定
命令コード オペランド
正分岐
Jump on PLus
JPL adr[,x] FRのSFとZFが共に 0 なら指定の実効アドレスに分岐する。
分岐しないときは、次の命令に進む。
-
負分岐
Jump on MInus
JMI adr[,x] FRのSFが 1 なら指定の実効アドレスに分岐する。
分岐しないときは、次の命令に進む。
-
非零分岐
Jump on Non Zero
JNZ adr[,x] FRのZFが 0 なら指定の実効アドレスに分岐する。
分岐しないときは、次の命令に進む。
-
零分岐
Jump on ZEro
JZE adr[,x] FRのZFが 1 なら指定の実効アドレスに分岐する。
分岐しないときは、次の命令に進む。
-
オーバフロー分岐
Jump on OVerflow
JOV adr[,x] FRのOFが 1 なら指定の実効アドレスに分岐する。
分岐しないときは、次の命令に進む。
-
無条件分岐
unconditional JUMP
JUMP adr[,x] 無条件に指定の実効アドレスに分岐する。 -
命令 分岐するときの FRの値
OF SF ZF
JPL 0 0
JMI 1
JNZ 0
JZE 1
JOV 1

void	JPL( int16_t	adr, int16_t	x ){
	if( !FR.SF && !FR.ZF ){
		PR = static_cast<int16_t>(static_cast<uint16_t>(adr) + static_cast<uint16_t>(x));
	}
}
  • JMI
void	JMI( int16_t	adr, int16_t	x ){
	if( FR.SF ){
		PR = static_cast<int16_t>(static_cast<uint16_t>(adr) + static_cast<uint16_t>(x));
	}
}
  • JNZ
void	JNZ( int16_t	adr, int16_t	x ){
	if( !FR.ZF ){
		PR = static_cast<int16_t>(static_cast<uint16_t>(adr) + static_cast<uint16_t>(x));
	}
}
  • JZE
void	JZE( int16_t	adr, int16_t	x ){
	if( FR.ZF ){
		PR = static_cast<int16_t>(static_cast<uint16_t>(adr) + static_cast<uint16_t>(x));
	}
}
  • JOV
void	JOV( int16_t	adr, int16_t	x ){
	if( FR.OF ){
		PR = static_cast<int16_t>(static_cast<uint16_t>(adr) + static_cast<uint16_t>(x));
	}
}
  • JUMP
void	JUMP( int16_t	adr, int16_t	x ){
	PR = static_cast<int16_t>(static_cast<uint16_t>(adr) + static_cast<uint16_t>(x));
}
(6)スタック操作命令

命 令 書き方 命令の説明 FRの設定
命令コード オペランド
プッシュ
PUSH
PUSH adr[,x] SP ←(SP)-L 1、
(SP)←実効アドレス
-
ぽップ
POP
POP r r ←((SP))、
SP ←(SP)+L 1
-

  • PUSH
void	PUSH( int16_t	adr, int16_t	x ){
	--SP;
	*reinterpret_cast<int16_t*>(SP) = static_cast<int16_t>(static_cast<uint16_t>(adr) + static_cast<uint16_t>(x));
}
  • POP
void	POP( int16_t&	r ){
	r = *reinterpret_cast<const int16_t*>(SP);
	++SP;
}
(7)コール、リターン命令

命 令 書き方 命令の説明 FRの設定
命令コード オペランド
コール
CALL subroutine
CALL adr[,x] SP ←(SP)-L 1、
(SP)←(PR)、
PR ←実効アドレス
-
リターン
RETurn from subroutine
RET   PR ←((SP))、
SP ←(SP)+L 1
-

  • CALL
void	CALL( int16_t	adr, int16_t	x ){
	--SP;
	*reinterpret_cast<int16_t*>(SP) = PR;
	PR = static_cast<int16_t>(static_cast<uint16_t>(adr) + static_cast<uint16_t>(x));
}
  • RET
void	RET(){
	PR = *reinterpret_cast<const int16_t*>(SP);
	++SP;
}
(8)その他

命 令 書き方 命令の説明 FRの設定
命令コード オペランド
スーパバイザコール
SuperVisor Call
SVC adr[,x] 実効アドレスを引数として割出しを行う。
実行後の GRと FRは不定となる。
-
ノーオペレーション
No OPeration
NOP   何もしない。 -

void	SVC( int16_t	adr, int16_t	x ){
	SVC_implementation( static_cast<int16_t>(static_cast<uint16_t>(adr) + static_cast<uint16_t>(x)));
}
  • NOP
void	NOP(){
}
(注)

  • r、r1、r2
    いずれも GRを示す。
    指定できる GRは GR0〜 GR7
  • adr
    アドレスを示す。
    指定できる値の範囲は 0〜 65535
  • x
    指標レジスタとして用いる GRを示す。
    指定できる GRは GR1〜 GR7
  • [ ]
    [ ]内の指定は省略できることを示す。
  • ( )
    ( )内のレジスタ又はアドレスに格納されている内容を示す。
  • 実効アドレス
    adrと xの内容との論理加算値又はその値が示す番地

  • 演算結果を、左辺のレジスタ又はアドレスに格納することを示す。
  • +L、-L
    論理加算、論理減算を示す。
  • FRの設定

    • 設定されることを示す。
    • ○*1
      設定されることを示す。
      ただし、OFには 0が設定される。
    • ○*2
      設定されることを示す。
      ただし、OFにはレジスタから最後に送り出されたビットの値が設定される。
    • -
      実行前の値が保持されることを示す。

参考資料 4. プログラムの例

COUNT1	START			;
;	入力	GR1:検索する語
;	処理	GR1中の'1'のビットの個数を求める
;	出力	GR0:GR1 中の'1'のビットの個数
	PUSH	0,GR1		;
	PUSH	0,GR2		;
	SUBA	GR2,GR2		; Count = 0
	AND	GR1,GR1		; 全部のビットが'0'?
	JZE	RETURN		; 全部のビットが'0'なら終了
MORE	LAD	GR2,1,GR2	; Count = Count + 1
	LAD	GR0,-1,GR1	; 最下位の'1'のビット1 個を
	AND	GR1,GR0		; '0'に変える
	JNZ	MORE		; '1'のビットが残っていれば繰返し
RETURN	LD	GR0,GR2		; GR0 = Count
	POP	GR2		;
	POP	GR1		;
	RET			; 呼出しプログラムへ戻る
	END			;

void	COUNT1(){
//	入力	GR1:検索する語
//	処理	GR1中の'1'のビットの個数を求める
//	出力	GR0:GR1 中の'1'のビットの個数
	PUSH( 0, GR1 );		//	
	PUSH( 0, GR2 );		//
	SUBA( GR2, GR2 );	// Count = 0
	AND( GR1, GR1 );	// 全部のビットが'0'?
	JZE( RETURN );		// 全部のビットが'0'なら終了
MORE:;
	LAD( GR2, 1, GR2 );	// Count = Count + 1
	LAD( GR0, -1, GR1 );	// 最下位の'1'のビット1 個を
	AND( GR1, GR0 );	// '0'に変える
	JNZ( MORE );		// '1'のビットが残っていれば繰返し
RETURN:;
	LD( GR0, GR2 );		// GR0 = Count
	POP( GR2 );		//
	POP( GR1 );		//
	RET();			// 呼出しプログラムへ戻る
}

void	COUNT1(){
	auto	prev_GR1 = GR1;
	auto	prev_GR2 = GR2;
	GR2 -= GR2;			// Count = 0
	GR1 &= GR1;
	if( GR1 ){			// 全部のビットが'0'?
		do{
			++GR2;		// Count = Count + 1
			GR0 = GR1 - 1;	// 最下位の '1' のビット1 個を
			GR1 &= GR0;	// '0' に変える
		}while( GR1 );		// '1'のビットが残っていれば繰返し
	}
	GR0 = GR2;			// GR0 = Count
	GR2 = prev_GR2;
	GR1 = prev_GR1;
}

int16_t	COUNT1( int16_t	r1 ){
	int16_t	r2 = 0;				// Count = 0
	if( r1 ){				// 全部のビットが'0'?
		do{
			++r2;			// Count = Count + 1
			int16_t	r0 = r1 - 1;	// 最下位の '1' のビット1 個を
			r1 &= r0;		// '0' に変える
		}while( r1 );			// '1'のビットが残っていれば繰返し
	}
	return	r2;				// GR0 = Count
}

int16_t	COUNT1( int16_t	r1 ){
	int16_t	r2 = 0;				// Count = 0
	while( r1 ){				// 全部のビットが'0'?
		++r2;				// Count = Count + 1
		r1 &= r1 - 1;			// 最下位の '1' のビット1 個を '0' に変える
						// '1'のビットが残っていれば繰返し
	}
	return	r2;				// GR0 = Count
}