戻る プログラム例



仮想コンピュータ プログラム例

1. 加算  2. 10 までの整数の加算  3. 最大値を出力する  4. 表の集計  5. かけ算  6. 大きい数の加算  7. 車のうっかりミス防止  8. タイマ  9. 数当てゲーム


1. 加算 (123 + 100)

STORE 	15, 123
STORE	14, 100

READ	15	; 123 → Acc
ADD	14	; Acc + 100 → Acc
OUT		; Acc → output port

END

メモリ 15 番地に 123、 14 番地に 100 をストアしておきます。 アキュムレータは先ず 15 番地のデータ (123) を読み込み、 次に 14 番地のデータ (100) を加算し、 出力ポートに出力してプログラムが終了します。 実行すると出力ポートは “11011111” になります。 これは 10 進数にすると 223 です。


2. 10 までの整数の加算

	STORE	15, 10	; 10 → M(15)

	CLR		; 0 → Acc

LOOP:	ADD	15	; M(15) + Acc → Acc
	DEC_M	15	; M(15) - 1 → M(15)
	JNZ	LOOP	; if M(15) <> 0, jump to LOOP

	OUT		; Acc → output port
	END

メモリ 15 番地に 10 をストアし、 アキュムレータはクリアして 0 にしておきます。 アキュムレータに 15 番地のデータ (10) を加算し、 次に 15 番地のデータをデクリメントします。 データは 9 になり、 0 ではありませんから、 プログラムは LOOP というラベルのあるアドレスにジャンプ (分岐) します。 再びアキュムレータに 15 番地のデータ (9) を加えて、 アキュムレータは 19 になります。 15 番地のデータをデクリメントし… ということを 0 になるまで繰り返すと、 アキュムレータには 10 までの整数の和が残りますから、 これを出力ポートに出力して終了します。
「10 までの整数の加算」 と書いていますが、 実際には 10 + 9 + 8 +…+ 2 + 1 という計算をしています。


3. 最大値を出力する

	STORE	15, 207
	STORE	14, 222
	STORE	13, 94
	STORE	12, 178
	STORE	11, 144

	SET_X	11	;11→ IX
L0:	READ	IX	; M (IX) → Acc
	OUT		; Acc → output port

L1:	INC_X		;IX + 1 → IX
	JEZ	L2	; if IX=0, jump to L2
	CMP	IX	; Acc - M (IX)
	JCC	L1	; if Acc < M (IX), jump to L1

	JMP	L0	; jump to L0

L2:	END

長いプログラムに見えますが、 “STORE” はデータの準備をしているだけで、 実際のプログラムは“SET_X” からです。 11〜15 番地に納められているデータの中から、 最も大きい値を見つけて出力しますが、 こういう問題はコンピュータが得意とするところです。
一連のデータを処理する場合は、 インデックスレジスタを使います。 まずインデックスレジスタにデータがある最初の番地、 11 をセットします。 これで "READ X" 命令は、 インデックスレジスタのデータ (11) の番地にあるデータ (144) を読み込みます。 とりあえずこれを出力ポートに出力しておいて、 次にインデックスレジスタをインクリメントします。 この結果、 インデックスレジスタが 0 になったら L2 にジャンプしてプログラムは終了しますが、 今は 12 ですから、 次に "CMP X" 命令を実行します。 "CMP" 命令はアキュムレータからメモリのデータを引きますが、 アキュムレータの値は変化しません。 もしアキュムレータの値 (今は 144) よりメモリのデータ (この場合は 12 番地の 178) の方が大きければ、 キャリーフラグが 1 になります。 JCC 命令は、キャリーフラグが 0 なら L1 に分岐します。 1 なら次の "JMP  L0" 命令によって L0 にジャンプします。 今はキャリーフラグは 1 ですから、 分岐先の READ 命令で比較の結果大きかった方のデータがアキュムレータに読み込まれます。 これを出力ポートに出力して、 同じ処理をインデックスレジスタが 15 になるまで繰り返し、 次に 0 になれば修了です。
出力ポートはデータの大小によって何度か書き換えられますが、 プログラムが終了したときには最大値が書き込まれています。


4. 表の集計 (103 + 54 + 92 + 36)

	STORE	12, 71
	STORE	13, 54
	STORE	14, 92
	STORE	15, 36

	CLR		; 0 → Acc
	SET_X	12	; 12 → IX
LOOP:	ADD	IX	; Acc + M (IX) → Acc
	INC_X		; IX + 1 → IX
	JNZ	LOOP	; if IX not 0, jump to LOOP
	OUT		; Acc → Output Port

	END

表のデータの総和を計算するプログラムで、 データは C 〜 F 番地にストアしておきます (データは 10個まで増やすことができます)
データのアドレスを示すためにインデックスレジスタを使用し、ADD IX 命令を使って順次加算していきます。 加算後インデックスレジスタをインクリメントしますが、 1111 をインクリメントして 0000 になるとプログラムは終了します。


5. かけ算 (21 × 11)

	STORE	15, 21
	STORE	14, 11
	STORE	13, 0

L0:	READ	14	; M(14) → Acc
	JEZ	L2	; if Acc = 0, jump to L2
	SFT_R		; shift right
	WRITE	14	; Acc → M(14)
	JCC	L1	; if Carry = 0, jump to L1

	READ	13	; M(13) → Acc
	ADD	15	; Acc + M(15) → Acc
	WRITE	13	; Acc → M(13)

L1:	READ	15	; M(15) → Acc
	SFT_L		; shift left
	WRITE	15	; Acc → M(15)
	JMP	L0	; jump to L0

L2:	END

掛け算のプログラムです。
コンピュータですから 2 進数で計算しますが、 このプログラムを理解するために、 2 進数での掛け算はどうすればいいのかを考えてみます。 といっても、 次のように、 ふだん行っている10 進数の掛け算と比べてみると、 まったく同じ、 というより、 むしろ 2 進数の方が簡単です。


プログラムは、 これとまったく同じことをコンピュータに行わせています。
この例では被乗数 21 を 15 番地に、 乗数 11 を 14 番地にストアします。 13 番地には積を累算するため、 あらかじめクリアしておきます。 まず、 乗数 (14 番地:1011) を右にシフトして、 最下位ビット (キャリーフラグに入る) が 1 であれば被乗数 (15番地:10101) を 13 番地に加えて (最下位ビットが 0 なら加えずに)、 被乗数を左にシフトします。 右にシフトされた乗数が 0 になれば計算終了です。
プログラムを実際に仮想コンピュータに入力して、 掛け算が進行していく様子を確認して下さい。
なお、 このプログラムでは、 答えはメモリ 13 番地にありますが、 出力できていません。 出力するためのプログラムを書き加えるには、 メモリが少し (1バイト…) 足りません。


6. 大きい数の加算 (12345 + 43210)

	STORE	15, #00111001
	STORE	14, #00110000
	STORE	13, #11001010
	STORE	12, #10101000

	READ	13	; M(13) → Acc
	ADD	15	; Acc + M(15) → Acc
	WRITE	11	; Acc → M(11)

	CLR		; 0 → Acc
	JCC	LOOP	; if C=0 (ADD), jump to LOOP
	INC		; Acc + 1 → Acc

LOOP:	ADD	12	; Acc + M(12) → Acc
	ADD	14	; Acc + M(14) → Acc
	WRITE	10	; Acc → M(10)
	END


この仮想コンピュータは 8 ビットですが、 大きい数の計算もできます。 例として、 12345 + 43210 を計算します。

12345 は 2 進数にすると "00110000 00111001"、43210 は "10101000 11001010" ですから、 これを 2 つに分けてそれぞれ 14、 15 番地と 12、 13 番地に STORE します。 12345 を 256 で割った商 48 と余り 57、同じく 43210 を256 で割って、商 168 と余り 202 を、

	STORE	15, 57
	STORE	14, 48
	STORE	13, 202
	STORE	12, 168

という風に書いても、 もちろん、 どちらでも構いません。
まず、 下位 8 ビット同士を加算して和を 11 番地に書き込み、 次に上位 8 ビット同士を加算しますが、 その前に、 今の加算でできたキャリーを加えなくてはなりませんので、 アキュムレータをクリアして、 キャリーが 1 であればインクリメントした上で 12 番地を加算し、 更に 14 番地のデータを加算して、 和を 10 番地に書き込んでいます。

	CLR
	JCC	LOOP
	INC

を削除すると、 和は "11011000 00000011" 、10 進数では 55299 となって、 256 だけ少なくなってしまいます。

この仮想コンピュータではキャリーの処理が面倒ですが、 一般の CPU にはキャリーも一緒に加算する命令があります。


7. 車のうっかりミス防止

これは
車のうっかりミス防止と同じ働きをするプログラムです。
入力ポートの 0 ビット目にはドアのセンサからの信号が、 1 ビット目にはエンジン、 2 ビット目はキー、 3 ビット目にはライトの信号が接続されています。 それぞれの動きと 0, 1 の対応は 車のうっかりミス防止 のページの表の通りです。 また、 出力ポートの 0 ビット目はブザーに接続されていて、 これが 1 になればブザーが鳴るようになっているものとします。

	STORE	15, #00000011

LOOP:	IN		; input port → Acc
	SFT_R		; shift right
	JCC	LOOP	; if Carry = 0, jump to LOOP
	SFT_R		; shift right
	JCS	LOOP	; if Carry = 1, jump to LOOP
	AND	15	; Acc・M(15) → Acc
	JEZ	LOOP	; if Acc = 0, jump to LOOP

	CLR		; 0 → Acc
	INC		; Acc + 1 → Acc
	OUT		; Acc → output port

	END

このプログラムの考え方は、 「論理回路」 で作ったものとは少し違います。


入力ポートの 0 ビット目にはドアの信号が接続されていますから、 ドアが開かない限り (1 にならない限り) ブザーをならす必要はありません。 入力ポートのデータをアキュムレータに読み込んで、 右にシフトすると最下位ビットがキャリーフラグに入りますから、 キャリーフラグが 0 であれば LOOP に分岐して入力命令を繰り返します。
ドアが開けば、 エンジンがどうなっているかをチェックするために、 もう一度右にシフトしてキャリーフラグを検査します。 エンジンが動いていればブザーは鳴らさなくていいので、 この場合も LOOP に分岐します。
ドアが開いてエンジンが止まったとき、 キーやライトがどうなっているかを調べるために、 15 番地のデータとの論理積 (AND) を取ります。 15 番地のデータは #00000011 で、 アキュムレータのデータはこのときには 2 度シフトされていますから、 2 ビット目、 3 ビット目に接続されているキーとライトのデータは 0、1 ビット目に移動しています。 従って、 これと #00000011 との論理積をとると、 キーとライトのデータがどちらも 0 であればアキュムレータはゼロになって、 やはりブザーは鳴らさなくていいので LOOP に分岐します。
キーとライトのデータのうち、 少なくともひとつが 1 であれば、 #00000011 との論理積をとるとアキュムレータの値はゼロになりません。 この場合はブザーを鳴らさなくてはなりませんから、 出力ポートの 0 ビット目を 1 にするために 先ずアキュムレータをクリアして、 その後インクリメントして出力ポートに出力します。

このプログラムは、 仕組みが理解できたら Clock を少し早めにして (動作表示も止めて)、 "RUN" をクリックして下さい。 入力ポートをマウスでクリックして、 "00001001" や "00000101"、 "00001101" にすると、 出力ポートが "00000001" になって停止します。

このように、 コンピュータは単に数値演算ばかりでなく、 論理演算もでき、 入力ポートにセンサの信号、 出力ポートにはブザーの他ライトやモーターなどのアクチュエータ (actuator) を接続すれば、 さまざまの機械、 機器を制御することができます。


8. タイマ

コンピュータが、 命令を実行するのに一定の時間がかかることを利用したタイマです。

STORE	14, 10

	READ	14	; M(14) → Acc
	WRITE	15	; Acc → M(15)

L0:	IN		; input port → Acc
	JEZ	L0	; if Acc = 0, jump to L0

L1:	READ	14	; M(14) → Acc
L2:	DEC		; Acc - 1 → Acc
	JNZ	L2	; if Acc ≒ 0, jump to L2

	READ	15	; M(15) → Acc
	DEC		; Acc - 1 → Acc
	WRITE	15	; Acc → M(15)

	JNZ	L1	; if Acc ≒ 0, jump to L1

	INC		; Acc + 1 → Acc
	OUT		; Acc → output port

	END

Clock を 64Hz に、 動作表示を OFF にして実行し、 入力ポートをクリックすると、 約 10 秒後に出力ポートが "00000001" になって停止します。

このプログラムでは、 まず 14 番地のデータが 15 番地にコピーされています。 その後、 14 番地のデータを読んで、 それが 0 になるまでデクリメントを繰り返すことで時間を稼ぎ、 0 になったら 15 番地をデクリメントして L1 に戻りますから、 ここで 2 重の時間稼ぎをしています。
従って、 最初の疑似命令 "STORE" で設定する値が 10 の時は約 10 秒のタイマですが、 2 倍の 20 にすると 22 倍の、 約 40 秒のタイマになります。 多重ループにすることで、 長時間のタイマを短いプログラムで作ることができます。

たとえば、 トースターのボタンを押してから 150 秒後にヒーターのスイッチを切る、 というような制御を、 コンピュータに行わせることができます (いまどき、こんな単純なことはしていないでしょうが…)


9. 数当てゲーム (?)

ゲーム (?) を作ってみました。

	STORE	15, #00110100

L0:	IN		; inputport → Acc
	CMP	15	; Acc - M(15)
	JEZ	L2	; if Acc = M(15), jump to L2
	JCC	L1	; if Acc > M(15), jump to L1

	CLR		; 0  → Acc
	OUT		; Acc  → output port
	JMP	L0	; jump to L0

L1:	CLR		; 0 → Acc
	NOT		; not Acc (="11111111")  → Acc
	OUT		; Acc → output port
	JMP	L0	; jump to L0

L2:	OUT		; Acc → output port
	END

クロックを早めにしておいて実行し、 入力ポートに何か数値をセットします。
その数値がある数より大きければ、 コンピュータは "#11111111" を出力し、 小さければ "#00000000" を出力します。 ある数と等しくなれば、 その数値を出力して停止します。
「ある数」 は 15 番地にストアしている "#00110100" ですから、 この仮想コンピュータでは正解が見えていますし、 そうでなくても当て方は簡単で、 とてもゲームといえる代物ではありません。 しかしまあ、 メモリが 16バイトでは、 できることといえばこの程度ですか…。



関連事項:  仮想コンピュータ  仮想コンピュータの使い方  アセンブラ仕様  命令セット  命令  資料 (PDF) 


情報処理概論 に戻る   目次 に戻る  仮想コンピュータ に戻る   戻る  

自由利用マーク
update: 2008.10.23  address