|
プログラム例 |
仮想コンピュータ プログラム例
1.
加算
2.
10 までの整数の加算
3.
最大値を出力する
4.
表の集計
5.
かけ算
6.
大きい数の加算
7.
車のうっかりミス防止
8.
タイマ
9.
数当てゲーム
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 です。
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 という計算をしています。
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 になるとプログラムは終了します。
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 番地に書き込んでいます。
を削除すると、 和は "11011000 00000011" 、10 進数では 55299 となって、 256 だけ少なくなってしまいます。
この仮想コンピュータではキャリーの処理が面倒ですが、 一般の CPU にはキャリーも一緒に加算する命令があります。
これは 車のうっかりミス防止と同じ働きをするプログラムです。
入力ポートの 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) を接続すれば、
さまざまの機械、 機器を制御することができます。
コンピュータが、 命令を実行するのに一定の時間がかかることを利用したタイマです。
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 秒後にヒーターのスイッチを切る、
というような制御を、 コンピュータに行わせることができます
(いまどき、こんな単純なことはしていないでしょうが…)。
ゲーム (?) を作ってみました。
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バイトでは、 できることといえばこの程度ですか…。
情報処理概論 に戻る
目次 に戻る
仮想コンピュータ に戻る
戻る
update: 2008.10.23