法政大学国際文化学部
情報システム概論
担当 重定 如彦
2009年11月10日
第7回 機械語によるプログラミング その2
1. 前回の課題の答え
START
LD GR1, A
ADD GR1, B
SUB GR1, C
ST GR1, D
EXIT
A DC 10
B DC 5
C DC 3
D DS 1
END
このプログラムを実行するとメモリのD番地に(000C)16という数値が格納されるはずです。(000C)16は10進数になおすと12なので、このプログラムが10+5−3=12を正しく計算できていることを確かめることができます。なお、D番地が実際に何番地になるかは、アセンブラ結果のウィンドウの中のラベルがDの行の番地の部分を見て下さい。そこに書かれている(000D)16がプログラムの中の実際のDの番地です。
2. 条件分岐
前回の授業ではアセンブラ言語の使い方と、簡単なプログラム例について解説しました。今回の授業では、もう少し複雑なプログラムについて説明します。
プログラムでは、ある条件が満たされていた場合は「A」という動作を、その条件が満たされていなければ「B」という動作を行うといった、ある条件によってプログラムの動作を変えるということが良く行われます。これを条件分岐と呼びます。例えば、ある数字が50以上だったら画面に「合格」と表示し、50未満だったら画面に「不合格」と表示するといったものです。機械語命令では、一般にフラグレジスタ(FR)の内容を分岐の条件とし、FRの値によってPC(プログラムカウンタ)の値を変化させ、次に実行する機械語命令の場所を変化させることができます。COMETでは、条件分岐のための以下の5種類の命令コードが用意されています。
命令 |
命令コード |
オペランド |
adrへ分岐する条件 |
分岐する場合 のFRの値 |
正分岐 |
JPZ |
adr |
計算結果が0以上の場合 |
(00)2(01)2 |
負分岐 |
JMI |
adr |
計算結果が負の場合 |
(10)2 |
非零分岐 |
JNZ |
adr |
計算結果が0でない場合 |
(00)2(10)2 |
零分岐 |
JZE |
adr |
計算結果が0の場合 |
(01)2 |
無条件分岐 |
JMP |
adr |
無条件でadrへ分岐 |
すべて |
|
演算結果 |
||
負の値 |
零 |
正の値 |
|
FRの値 |
(10)2 |
(01)2 |
(00)2 |
FRは、ADDやSUBなどの算術演算命令などを行った場合、演算結果の値によって上記のように変化します。条件分岐命令はこのFRの値が条件に一致する場合に、プログラムカウンタをadrの値に変化させる命令です。このままでは、わかりにくいと思いますので、メモリのA番地の値が50以上の場合はメモリのC番地に1、そうでなければ2を代入するプログラム例を挙げてみます。
1 START
2 LD GR1,A ;A番地の内容をGR1に代入
3 SUB GR1,B ;GR1からB番地の内容を引く
4 JPZ PRG1 ;演算結果が0以上ならPRG1へ
5 PRG0 LEA GR1,2 ;負の場合。GR1に2を代入
6 ST GR1,C ;GR1の内容(2)をC番地に格納
7 EXIT ;プログラム終了
8 PRG1 LEA GR1,1 ;0以上の場合。GR1に1を代入
9 ST GR1,C ;GR1の内容(1)をC番地に格納
10 EXIT ;プログラム終了
11 A DC 60 ;A番地に60を代入
12 B DC 50 ;B番地に50を代入
13 C DS 1 ;結果を格納するC番地を用意する
14 END
このプログラムを解説する前に一つ新しい命令について説明します。LEAはロードアドレス命令と呼ばれる命令で、以下のように記述します。
命令コード |
オペランド |
LEA |
GR, 数値 |
LEAはGRで指定した汎用レジスタに数値で指定した値を直接代入する命令で、
LEA GR1, 100
と記述すると汎用レジスタGR1に、100という数値が代入されます。LDとの違いは、LDが第二オペランドで指定したアドレスの内容を汎用レジスタに代入するのに対して、LEAは第二オペランドで指定した数値そのものを代入する点です。
COMETに用意されている条件判定のための命令は演算結果を「0以上」、「0未満」、「0に等しい」、「0以外」の4種類の条件のいずれかで判定します。これを使って、ある数値が50以上であるかを判定するには、その数値から50を引いた結果が「0以上」であるかどうかを判定すればOKです。従って、このプログラムでは、演算結果が0以上であるかどうかを判定する命令コードである「JPZ」を使って判定を行います。
それでは順を追ってプログラムを解説します。まず、11行目〜13行目でメモリのA番地に60、B番地に50を代入し、結果を代入するメモリのC番地を確保しています。
プログラムを実行すると、2行目と3行目でA番地の内容からB番地の内容(=50)を引き算し、4行目でその結果が0以上であるかどうかを判定します。もし0以上であった場合は、4行目のJPZ命令によって、PCの値がPRG1番地に変更され、次に8〜10行目のプログラムが実行されます。もし負であれば、4行目では何も行われずそのまま次の5〜7行目が実行されます。5〜7行目にはメモリのC番地に2を代入するプログラム、8〜10行目ではメモリのC番地に1を代入するプログラムが記述されており、これによってこのプログラムは目的の動作を行うわけです。
上記のプログラムを実際に入力し(コメントの部分は入力しなくてもかまいません)、sample1というファイル名をつけて保存し、アセンブルして下さい。アセンブル結果のウィンドウに、PRG1が(000C)16番地、Aが(0012)16番地、Bが(0013)16番地、Cが(0014)16番地であることが示されていることを確認して下さい。
次にCOMETのウィンドウで「1ステップ実行」ボタンを押し、プログラムの実行によって、GR1,FR,メモリのA、B、C番地がどのように変化するかを見て下さい。この場合、Aの内容は60で50より大きいので4行目の次に8行目が実行され、最終的にメモリのC番地、すなわち(0014)16番地に1が代入されることになります。
また、メモリのA番地の内容を20に変更し、アセンブルし同様の作業を行って下さい。今度はA番地の内容は50より大きいのでC番地に最終的に2が代入されます。
メモリの (0012)16番地 プログラムカウンタ 汎用レジスタ フラグレジスタ
メモリに表示されているカーソルは「赤色:次に実行する機械語の命令」、「青色:直前に実行した機械語の命令」を表します。
3. 条件分岐と繰り返し
条件分岐のもう少し高度な例として、乗算を行うプログラムを作ってみましょう。このプログラムでは、メモリのA番地の内容とメモリのB番地の内容を掛け算してメモリのC番地に格納するという動作を行います。ところで、CASLの命令コードの中には足し算、引き算を行う命令はありますが、乗算を行う命令はありません。そこで、A*Bという計算を、AをB回繰り返し足し算するという方法をとります。
1 START
2 LEA GR1,0 ;GR1に0を代入
3 LD GR2,B ;B番地の内容をGR2に代入
4 LOOP ADD GR1,A ;GR1にA番地の内容を足す
5 LEA GR2,-1,GR2 ;GR2から1を引く
6 JNZ LOOP ;演算結果が0でなければLOOPへ
7 ST GR1,C ;GR1の内容をC番地に格納
8 EXIT ;プログラム終了
9 A DC 7 ;A番地に7を代入
10 B DC 5 ;B番地に5を代入
11 C DS 1 ;結果を格納するC番地を用意する
12 END
なお、このプログラムの5行目で、 LEA GR2, -1, GR2 と記述されていますが、これはGR2の内容に-1を足す(GR2の内容から1を引く)という意味を持ちます。
LEA命令は実は以下のように3オペランド命令として記述することが可能です。
命令コード |
オペランド |
LEA |
GR, 数値, GR |
LEAで3つのオペランドを記述した場合、第一オペランドで指定した汎用レジスタに、第二オペランドの数値と第三オペランドで指定した汎用レジスタの値を足し算した結果を格納するという動作を行います。上記の例のように、特定の汎用レジスタの内容と具体的な数字の足し算(または引き算)を行いたい場合に使用すると良いでしょう。
次にプログラムのそれぞれの行の解説を行います。
まず、9〜11行目で、メモリのA番地に7、メモリのB番地に5が格納され、結果を格納する為にメモリのC番地が用意されます。
このプログラムでは、汎用レジスタ1(GR1)を計算の途中結果、汎用レジスタ2
(GR2)を何回足し算を行ったかを数えるために使います。プログラムの実行が開始された時点では、直前に実行したプログラムの計算結果が汎用レジスタに残っている場合などがあるため、それぞれの汎用レジスタに何の数字が入っているかはわかりません。そこで、計算結果を格納するGR1は最初に0を代入する必要があり、この作業を2行目で行っています。このような初期設定を行う作業のことを初期化呼びます。
次に3行目でGR2に足し算を行う回数、すなわちメモリのB番地の内容を代入しています。これで、前準備が整いましたので4〜6行目で足し算を繰り返す作業を行います。
4〜6行目で何が行うかをまとめると以下のようになります。
4行目 GR1 に GR1 + A番地の内容(この場合7) を代入
5行目 GR2 の内容を1減らす
6行目 直前(5行目の計算結果。すなわちGR2の値)の計算の結果が
0でなければ4行目に戻る
4〜6行目の作業が行われるたびに、GR1の値にA番地の内容が加算され、GR2の値が1減らされます。この行はGR2の値が0になるまで繰り返されるので、結果としてこの4〜6行目はメモリのB番地の値の回数だけ繰り返されることになります。従って、GR1の中には最終的にAをB回足し算した値、すなわちA*Bが格納されることになります。最後に7行目で結果(GR1)をC番地に書込みこのプログラムは終了します。
この掛け算のプログラムのように、プログラムでは同じ内容の作業を特定の回数だけ繰り返し行うという動作が頻繁に記述されます。プログラムの中でこのような繰り返しを行う部分のことをループと呼び、このプログラムのGR2のように、ループの中で繰り返しの回数を数えるための役割をもつレジスタのことをカウンタ(counter)と呼びます。
4. 繰り返しの例 その2
繰り返しを使ったプログラム例をもう一つ紹介します。次の例題では、1からメモリのA番地に格納されている数字までの合計を計算し、メモリのB番地に格納します。
このプログラムでは、汎用レジスタGR1を計算の途中経過の格納用に、GR2をカウンタとして使用します。COMETは2つのレジスタ同士を直接(1命令で)演算する命令を持っていないので、レジスタ同士の例えば GR1 + GR2 のようなレジスタ同士の足し算は以下のように、片方のレジスタの内容を一旦メモリに格納してから行う必要がある点に注意が必要です。このプログラムではメモリのC番地をこの用途に使います。
1. GR2の内容をメモリにST命令を使って格納する
2. ADD命令を使って GR1とメモリに格納したGR2の値を加算する
1 START
2 LEA GR1,0 ;GR1に0を代入
3 LD GR2,A ;A番地の内容をGR2に代入
4 LOOP ST GR2,C ;C番地にGR2の内容を代入
5 ADD GR1,C ;GR1にC番地の内容を足す
6 LEA GR2,-1,GR2 ;GR2から1を引く
7 JNZ LOOP ;演算結果が0でなければLOOPへ
8 ST GR1,B ;GR1の内容をB番地に格納
9 EXIT ;プログラム終了
10 A DC 10 ;A番地に10を代入
11 B DS 1 ;結果を格納するB番地を用意する
12 C DS 1 ;カウンタを格納するC番地を用意
13 END
次にプログラムのそれぞれの行の解説を行います。
まず、10〜12行目で、メモリのA番地に10が格納され、結果を格納する為にメモリのB番地と、汎用レジスタの中身を格納するためのメモリのC番地が用意されます。
2行目:先ほどの掛け算の例と同様に、結果を格納する汎用レジスタGR1の値を初期化する必要があるので、その作業を2行目で行います。
3行目:GR2に足し算を行う回数、すなわちメモリのB番地の内容を代入します。
これで、前準備が整いましたので4〜6行目で足し算を繰り返す作業を行います。
4〜7行目で何が行うかをまとめると以下のようになります。
4行目 メモリのC番地にGR2の値を代入する
5行目 GR1とC番地の内容を加算しGR1に格納する。4行目と5行目で GR1 ← GR1 + GR2 が実行される
6行目 GR2 の内容を1減らす
7行目 直前(6行目の計算結果。すなわちGR2の値)の計算の結果が
0でなければ4行目に戻る
4〜7行目の作業が行われるたびに、GR1の値にGR2の内容が加算され、GR2の値が1減らされます。この行はGR2の値が0になるまで繰り返されるので、結果としてこの4〜7行目はメモリのA番地の値の回数だけ繰り返されることになります。従って、GR1には 10 + 9 + 8 + 7 + 6 + 5
+ 4 + 3 + 2 + 1 が計算されることになり、1からA番地の内容(=10)までの合計の計算結果が格納されます。最後に8行目で結果(GR1)をC番地に書込みこのプログラムは終了します。このプログラムを入力し、各自COMETシミュレータを使ってプログラムの動作を確かめてみてください。
5. 課題
以下のプログラムを作り実行せよ。また、作成したプログラムをGドライブにkadai2という名前で保存し、課題のメールとして添付ファイルで送ること。
メモリのA番地の内容をメモリのB番地の内容で割り算し、商をメモリのC番地、余りをメモリのD番地に格納するプログラムを作成せよ。A番地に10、B番地に3を格納し、作成したプログラムを実行し、メモリのC番地とD番地に正しい答えが格納されていることを確かめよ。
ヒント:
式で書くと、
A / B = C 余り D
のCとDを計算するプログラムを作れということです。
CASLには割り算を行う命令はありませんので、足し算と引き算を使うことになります。A/Bの商を求めるには、AからBを何回引き算できるかを数えるプログラムを作ればOKです。プログラムでは、最初にA番地の値をGR1に格納し、答えをGR2に格納するとした場合、以下のようなループを作れば良いでしょう。
LOOP ・GR1からB番地の内容を引き算し、GR1<0 ならば
ANSへ条件分岐させる(ループの終了)
・GR2に1を足す
・LOOPへ戻る(JMPによる無条件分岐)
ANS ・GR1にB番地の内容を足す(GR1の値が負になっているため)
ループが終了した時点で、GR2には商が、GR1には余りが入っているはずです。
この方法で実際にうまくいくということを例えばA=5、B=2の場合、で示します。
ループに入る前は GR1=5、GR2=0
一回目のループで GR1=3、GR2=1
二回目のループで GR1=1、GR2=2
三回目のループで GR1=−1(<0)となるのでここでループが終了します。
ANSでGR1から余分に引いた数を戻した結果、GR2には2、GR1には1が格納されているので、5/2 = 2 余り 1という答えが上記の方法で求められます。
課題のメールは masaki.yamashita.67@gs-art.hosei.ac.jp までお願いします。
質問のメールなどは、sigesada@.hosei.ac.jpまでお願いします。
授業の資料の最新版はhttp://www.edu.i.hosei.ac.jp/~sigesada/にあります。