プログラミングの基本テクニック

アルゴリズム


■16ビット数(2バイト数)の加算

アドレスARG1とアドレスARG2に記憶されている2つの16ビット数を加算し、 和をアドレスARG3に保存する。

       LD   AC, ARG1+1   <- 被加数の下位8ビットをACに読み込み
       ADD  AC, ARG2+1   <- 加数の下位8ビットをACに加算(桁上げがCフラグに保存される)
       LD   ARG3+1, AC   <- 和の下位8ビットを保存
       LD   AC, ARG1     <- 被加数の上位8ビットをACに読み込み
       ADC  AC, ARG2     <- 加数の上位8ビットをACに桁上げを含んで加算
       LD   ARG3, AC     <- 和の上位8ビットを保存
       HLT

       org  200
ARG1:  dw   100          <- 被加数(上位8ビットが低メモリアドレス)
ARG2:  dw   250          <- 加数(上位8ビットが低メモリアドレス)
ARG3:  dw     0          <- 和の保存領域

別の方法

       LD   X, ARG2+1    <- 加数の下位8ビットのアドレスをXに読み込み
       LD   AC, ARG1+1   <- 被加数の下位8ビットをACに読み込み
       ADD  AC, (X)      <- 加数の下位8ビットをACに加算(桁上げがCフラグに保存される)
       LD   ARG3+1, AC   <- 和の下位8ビットを保存
       DEC  X            <- Xを加数の上位8ビットのアドレスに変更
       LD   AC, ARG1     <- 被加数の上位8ビットをACに読み込み
       ADC  AC, (X)      <- 加数の上位8ビットをACに桁上げを含んで加算
       LD   ARG3, AC     <- 和の上位8ビットを保存
       HLT

       org  200
ARG1:  dw   100          <- 被加数(上位8ビットが低メモリアドレス)
ARG2:  dw   250          <- 加数(上位8ビットが低メモリアドレス)
ARG3:  dw     0          <- 和の保存領域

■繰り返し

指定された回数だけ処理を繰り返し実行する。

       LD   AC, 10       <- 10回繰り返し
       LD   COUNT, AC    <- メモリCOUNTをカウンタとして使用
L1:    OP1
       OP2               <- OP1~OPn を繰り返し実行
       …
       OPn
       LD   AC, COUNT    <- カウンタの内容をACに転送
       DEC  AC           <- ACを1減
       LD   COUNT, AC    <- ACをカウンタに戻す(フラグ不変)
       JNZ  L1           <- カウンタが0でなければ繰り返し

■条件判断

変数の値によって処理内容を切り替える。 C言語のif~then~else~に相当する条件判断を行う。

       LD   AC, VAR      <- 変数VARの値をACに読み込み
       CPR  AC, 20       <- ACの値と20を比較
       JS   ELSE
THEN:  OP1
       OP2               <- VAR≧20のとき、OP1~OP5を実行
       …
       OP5
       JMP  ENDIF
ELSE:  OP6
       OP7               <- VAR<20のとき、OP6~OP9を実行
       …
       OP9
ENDIF: OPxxx

■便利な命令

基本命令セットには含まれていないが、市販の一般的なマイクロプロセッサには 共通に存在する命令として以下のものを挙げることができる。

条件実行命令

ある条件が成り立つときに1つだけ命令を実行したいことがある。 例えば、Cフラグが1のときにアキュムレータACをシフトし、Cフラグが0のときには何も行わない場合を考える。
通常は、条件分岐命令を用いて以下のようにプログラムを記述するだろう。

       JNC    L1
       SRL    AC
L1:    ...
条件が成り立つときには分岐命令とシフト命令の2つの命令を実行するためのクロック数を消費する。

ここで、実行条件を備え、条件が成り立つときのみ演算操作を行う命令を考える。 例えば、Cフラグが1のときにアキュムレータACをシフトする命令SRLC ACを用意すれば、 先のプログラムは以下のように1命令で記述できる。

       SRLC    AC
L1:    ...

スタック操作命令

スタック(stack)はデータ記憶構造の一種であり、複数のデータを記憶することができる。 スタックのアクセスは、データの書き込みとデータの読み出しがある。 通常は、スタック内のどこにデータを書き込むか、スタック内のどのデータを読み出すかといった指定はせず、最後に書き込んだデータが最初に読み出される後入れ先出し(Last-In First-Out, LIFO)となっている。
スタックへのデータの書き込みをプッシュ(push)、スタックからのデータの読み出しをポップ(pop)と呼ぶことが多い。

スタックの記憶領域はマイクロプロセッサに接続したメモリ上にあり、 プッシュはメモリへの書き込み、ポップはメモリからの読み出しによって実現される。 プッシュとポップの際にはスタックのどこのデータというような場所を考慮する必要はないが、実際にはプッシュとポップを行うためにはメモリの読み書き位置を示すメモリアドレスが必要である。 そこで、このメモリアドレスを保持する特別なレジスタを使用し、これをスタックポインタ(stack pointer)と呼ぶ。
スタックにデータをプッシュするとスタックポインタの値は増加(あるいは減少)される。スタックからデータをポップするとスタックポインタの値は減少(あるいは増加)される。これにより、後入れ先出しが実現できる。
スタックの使用を開始する前にスタックポインタに適切なメモリアドレスを設定すれば、それ以降ではプッシュとポップの際にいちいちスタックポインタの値を気にする必要はない。

一般的には、メモリ空間内の低位アドレスにはプログラムや初期データが格納され、高位アドレスは自由に利用できる場合が多い。 どれだけのメモリ量が必要となるか処理内容によって変化するスタックは、その記憶領域をプログラムや初期データの間に確保することは好ましくない。 なぜならば、あらかじめ想定したメモリ量よりもスタックに記憶するデータが多くなった場合に、スタックの記憶領域の前後のメモリ内容がスタックにプッシュしたデータによって上書きされて破壊される恐れがあるためである。
そこでスタックポインタの初期値をメモリ空間の最高位アドレスとし、プッシュを行うたびに低位アドレスに向かってスタックポインタの値を変更することで、プログラムや初期データとスタックによって効率良くメモリ空間を共有することができる。
メモリ空間の最上位アドレスは、マイクロプロセッサとメモリのシステム構成によって変化するものであり、使用するシステム構成に合わせてスタックポインタに適切な値を設定する必要がある。

◆スタック操作命令の定義例 (Xレジスタをスタックポインタとして利用)および実行の様子

PUSH AC
	nbyte 1
	opcode 0011xxx0
	0: MAR <- PC, PC <- inc
	1: MDR <- mem
	2: IR <- MDR
	3: X <- dec
	4: MDR <- AC, MAR <- X
	5: WR = 1

POP AC
	nbyte 1
	opcode 0011xxx1
	0: MAR <- PC, PC <- inc
	1: MDR <- mem
	2: IR <- MDR
	3: MAR <- X, X <- inc
	4: MDR <- mem
	5: AC <- MDR

サブルーチン呼び出しとサブルーチンからの復帰

プログラムの中では、同じ処理を複数回実行することが良くある。 同じ処理を実行するために同じ機械語を複数回プログラム中に記述することは無駄である。 そこで、機械語はただ1つだけプログラム中に置き、必要なときにその処理部分を実行し、処理が終われば元に戻ることができれば便利である。
このようにプログラム中の複数の場所から飛んで来て実行し、実行が終われば元の位置へ戻る処理部分をサブルーチン(subroutine)と呼ぶ。
サブルーチンへ実行を移すことをサブルーチン呼び出し(subroutine call)と呼び、サブルーチンを終了して元の処理へ実行を戻すことをサブルーチンからの復帰(return)と呼ぶ。

サブルーチン呼び出し命令では、この命令の次の命令のアドレスをスタックにプッシュしてからサブルーチンの先頭アドレスへジャンプする。
サブルーチンからの復帰命令では、スタックからアドレスをポップして、そのアドレスにジャンプする。
サブルーチンからの復帰命令を実行することで、サブルーチン呼び出し命令の次の命令から処理を再開することができる。 また復帰アドレスをスタックに保存することで、サブルーチンの中から自分自身あるいは他のサブルーチンを呼び出すといったサブルーチンの多重呼び出しを行った場合でも、正しく復帰ができる。

◆CALL命令、RETRUN命令の定義例 (Xレジスタをスタックポインタとして利用)および実行の様子

CALL arg
	nbyte 3
	opcode 1000xxxx
	0: MAR <- PC, PC <- inc
	1: MDR <- mem
	2: IR <- MDR
	3: MAR <- PC, PC <- inc
	4: MDR <- mem, MAR <- PC, PC <- inc
	5: MDRH <- MDR, MDR <- mem, X <- dec
	6: PC <- MDRW, MAR <- PC, MDR <-H PC
	7: MDRH <- MDR, MDR <- MAR, MAR <- X, X <- dec
	8: WR = 1, MDR <-H MDRW, MAR <- X
	9: WR = 1

CALL label
	alias CALL arg

RETURN
	nbyte 1
	opcode 1001xxxx
	0: MAR <- PC, PC <- inc
	1: MDR <- mem
	2: IR <- MDR
	3: MAR <- X, X <- inc
	4: MDR <- mem, MAR <- X, X <- inc
	5: MDRH <- MDR, MDR <- mem
	6: PC <- MDRW

単純サブルーチン

上で説明するCALL命令とRETURN命令によるサブルーチン呼び出しでは、サブルーチンからさらにサブルーチンを呼び出すことが可能であるが、戻りアドレスはスタック、すなわちメモリに記録している。
サブルーチンからさらにサブルーチンを呼び出すといった多重呼び出しがないサブルーチンを単純なサブルーチンと呼ぶこととする。
単純サブルーチンの場合には、戻りアドレスの保存にレジスタを用いることができる。
サブルーチンの多重呼び出しがないので、レジスタに保存した戻りアドレスを次のサブルーチンの戻りアドレスで上書きしてしまうことがないためである。
戻りアドレスの保存と復元がスタック(メモリ)の読み書きではなく、レジスタの読み書きであるため、サブルーチン呼び出しと復帰のクロックサイクル数が少ない利点がある。

戻りアドレスをレジスタに保存してサブルーチンを呼び出すことを「Jump and Link」といい、そのための命令を「JAL」命令と呼ぶ。
また、サブルーチンからの復帰はレジスタに保存した戻りアドレスへのジャンプとなるため「Jump for Return」といい、そのための命令を「JR」命令と呼ぶ。

なお、戻りアドレスを保存しているレジスタは、サブルーチン中は値を維持する必要がある。サブルーチンの中でそのレジスタを使用したい場合には、いったんレジスタ値をメモリや他のレジスタに退避し、サブルーチンから戻る前にレジスタ値を退避しておいた値に復元する必要がある。

戻りアドレス保存にXレジスタを利用する場合は、サブルーチンからの復帰は「JMP X」命令で行うことができる。

◆JAL命令、JR命令の定義例 (Xレジスタを戻りアドレス保存に利用)

JAL arg
	nbyte 3
	opcode xxxxxxxx
	0: MAR <- PC, PC <- inc
	1: MDR <- mem
	2: IR <- MDR
	3: MAR <- PC, PC <- inc
	4: MDR <- mem, MAR <- PC, PC <- inc
	5: MDRH <- MDR, MDR <- mem, X <- PC
	6: PC <- MDRW

JAL label
	alias JAL arg

JR
	alias JMP X