UMEHOSHI ITA TOP PAGE

レジスタを制御変数にした繰り返しで、hello文字列をUSBに出力する

UMEHOSHI ITA基板で使っている制御チップのPIC32MX270F256B用(MIPS32系)アセンブリに必要な情報の概要です。

このページで示したhelloを表示させる部分を、3回繰り返すプログラムコード例で示します。

s1レジスタに-3を設定してから繰り返し領域に進み、その後s1を増やして、そのs1が0より小さければば、繰り返しの分岐を行う手法です。
(他に「3から始めて、繰り返し内で減らし、その値が0より大きければ、繰り返しの戻る分岐を行う」方法もよく使われます。
  以下を少し変更することで可能です。実際に変更して試してみましょう。)


  上記で編集したアセンブリソースを、左のボタンで「アッセンブル」できます。
番地から実行するUME専用Hexコマンドを、 最後に追加埋め込みする場合にチェックする==>

上記で生成したUME専用Hexコマンドを コピー(CTRL+A CTRL+C)してUMEHOSHI ITAへ転送して動かすことができます。
(0x80005000番地から実行させる場合、上記の最後に「R00800050000061」のコードを追加追加するとよいでしょう。)

MIPS32分岐命令についての留意点

命令の実行ステージから分岐先の命令フェッチまでに1命令分の空きが生じ、分岐遅延が生じる。
MIPS32系ではこの遅延を「遅延スロット」と呼び、パイプラインを有効利用する意図で、 この空いた分岐命令の直後に命令を埋め込むことができる。
(このなにもしない待ちのサイクルを無くすために置く命令を「遅延スロットの命令」と呼んでいる。)
別の言い方をすると、ジャンプや分岐命令は「1命令遅れて分岐する」ことになる。
そして分岐命令には、「Branch Likely 命令(「直訳?:分岐する傾向がある分岐命令)」と呼ぶ命令が存在する。
つまり、分岐命令にはBranch Likely 命令対応と、そうでない命令が存在する。
Branch Likely 命令は遅延スロットの処理を、分岐命令が実際に分岐するときのみ実行するというもので. 分岐しない場合は,この遅延スロットの命令は実行されない(無視される)。
対して、Branch Likely でない命令は、「遅延スロット」を分岐に関係なく、必ず実行する。
Branch Likely 分岐命令を利用して、その直後の「遅延スロット」に命令を置くと、 分岐しない場合の実行クロック数は同じだが,分岐する場合は「遅延スロットの命令」分だけ得をするイメージになる。
しかし、Branch Likely でない分岐命令直後の「遅延スロット」に命令を置くと、 分岐に関係なく実行するので、分かり難い。
よって分かりやすくする為には、分岐命令の直後にNOPを置くよい。その場合は「遅延スロット」を有効に使わない遅延が生じる)

Branch Likely 分岐」とそうでない分岐の例を示します。
BNELとBNEはどちらもレジスタ間比較で、等しくなければ分岐する命令ですが、 BNELは「Branch Likely 」命令で、BNEはそうでない命令です。
BNELLは、Branch Likelyの意味になってます。
この命令は,遅延スロットに分岐先の先頭の 1 命令を置く,という使い方をします。

以下に分岐命令の種類を示します。(それぞれには、語尾にLを付けたBranch Likely 命令存在します。)
命令名と由来概要
beq (Branch on EQual)レジスタ間の比較で、一致したら分岐BEQ t0,zero, L02
bne (Branch on Not Equal) レジスタ間の比較で、一致しないなら分岐BNE t0, zero, L01
blez (Branch on LEss than or Equal to Zero)指定レジスタがゼロ以下なら分岐BLEZ s1,L01
bltz (Branch on Less Than Zero) 指定レジスタがゼロより小さいなら分岐BLTZ s1,L01
bgez (Branch on Greater or Equal to Zero)指定レジスタがゼロ以上で分岐bgez t0, L02
bgtz (Branch on Greater Than Zero) 指定レジスタがゼロより大きければ分岐bgtz t0, L02

do { 〜 } while( s0 != 0); の繰り返しパーターン

下は、s0レジスタを制御変数に使って、s0を1,2,3と変化させて、繰り返しを制御しています。
BNEを使った場合と、BNELを使った場合の例です、


s0=0;do { □□;s0++; } while( s0 != 0)

s0=1;do { □□; } while( s0++ != 0)
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <xc.h>	// test.S (USBへhelloの文字列を出力するプログラム)
	.set noreorder #アセンブラに命令の順序を自動変更させない。
	.section	test_main,address(0x80005000),code
	.ent	test_main
test_main:
	ADDIU	sp,sp,-24	# 以下で使う24byteまでの退避サイスをスタックに作る。
	SW	ra,20(sp)	# 現在の関数の戻り番地をスタックに退避
	SW	fp,16(sp)	# 呼び出し前の関数のフレームポインタをスタックに退避
	MOVE	fp,sp		# 現在の関数のフレームポインタをこの関数用に更新

	ADDIU	s0, s0, 0	# s0に0をセット

	NOP
L01:	LUI	v0,0xa000 # 以下3行で_PTR_HANDLERS[_IDX_API_SEND_STRING]内容をv0に設定
	ORI	v0,v0,0x41a4 
	LW	v0,0(v0)
	LUI	v1,%hi(.L_STR_HELLO)	# v0の関数の引数を準備
	ADDIU	a0,v1,%lo(.L_STR_HELLO)	# v0の関数の引数をa0レジスタに設定
	JALR	v0		# Raに戻り値を設置してv0の関数を呼び出す。
	NOP

	ADDIU	s0, s0, 1		# s0++
	ADDIU	t0, s0, -3		# t0 = s0 - 3
	BNE	t0, zero, L01	# if t0 != 0 goto L01
	NOP	# 直前がBranch Likely命令でない時、分岐に関係なく実行されるので入れる

	MOVE	sp,fp		# フレームポインタで退避前でspを退避前に戻す。
	LW	ra,20(sp)	# 現在の関数の戻り番地をスタックから復元
	LW	fp,16(sp)	# この関数の呼び出し前のフレームポインタをスタックから復元
	JR	ra		# この関数の呼び出し後に移動(リターンに相当する)
	ADDIU	sp,sp,24	# この関数の呼び出し前のスタックポインタに戻る
	.end test_main

	.section	.L_STR_HELLO,address(0x80005058),code
	.align	2
.L_STR_HELLO:
	.ascii	"hello\r\n\x00"
#include <xc.h>	// test.S (USBへhelloの文字列を出力するプログラム)
	.set noreorder #アセンブラに命令の順序を自動変更させない。
	.section	test_main,address(0x80005000),code
	.ent	test_main
test_main:
	ADDIU	sp,sp,-24	# 以下で使う24byteまでの退避サイスをスタックに作る。
	SW	ra,20(sp)	# 現在の関数の戻り番地をスタックに退避
	SW	fp,16(sp)	# 呼び出し前の関数のフレームポインタをスタックに退避
	MOVE	fp,sp		# 現在の関数のフレームポインタをこの関数用に更新

	ADDIU	s0, s0, 1	# s0に0をセット

	NOP
L01:	LUI	v0,0xa000 # 以下3行で_PTR_HANDLERS[_IDX_API_SEND_STRING]内容をv0に設定
	ORI	v0,v0,0x41a4 
	LW	v0,0(v0)
	LUI	v1,%hi(.L_STR_HELLO)	# v0の関数の引数を準備
	ADDIU	a0,v1,%lo(.L_STR_HELLO)	# v0の関数の引数をa0レジスタに設定
	JALR	v0		# Raに戻り値を設置してv0の関数を呼び出す。
	NOP

	ADDIU	t0, s0, -3		# t0 = s0 - 3
	BNEL	t0, zero, L01	# if t0 != 0 goto L01
	ADDIU	s0, s0, 1		# s0++


	MOVE	sp,fp		# フレームポインタで退避前でspを退避前に戻す。
	LW	ra,20(sp)	# 現在の関数の戻り番地をスタックから復元
	LW	fp,16(sp)	# この関数の呼び出し前のフレームポインタをスタックから復元
	JR	ra		# この関数の呼び出し後に移動(リターンに相当する)
	ADDIU	sp,sp,24	# この関数の呼び出し前のスタックポインタに戻る
	.end test_main

	.section	.L_STR_HELLO,address(0x80005058),code
	.align	2
.L_STR_HELLO:
	.ascii	"hello\r\n\x00"

s0=0; while( s0 の判定){ s0++ } の繰り返しパーターン

下は、s0レジスタを制御変数に使って、s0を1,2,3と変化させて、繰り返しを制御しています。
BNEを使った場合と、BNELを使った場合の例です、


s0=0; while(s0 < 3) { s0++ } または、for(s0=0; s0 < 3; s0++){ }

s0=1; while(s0 <= 3) { s0++ } または、for(s0=0; s0 <= 3; s0++){ }
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <xc.h>	// test.S (USBへhelloの文字列を出力するプログラム)
	.set noreorder #アセンブラに命令の順序を自動変更させない。
	.section	test_main,address(0x80005000),code
	.ent	test_main
test_main:
	ADDIU	sp,sp,-24	# 以下で使う24byteまでの退避サイスをスタックに作る。
	SW	ra,20(sp)	# 現在の関数の戻り番地をスタックに退避
	SW	fp,16(sp)	# 呼び出し前の関数のフレームポインタをスタックに退避
	MOVE	fp,sp		# 現在の関数のフレームポインタをこの関数用に更新

	ADDIU	s0, zero, 0	# s0に0をセット
L01:	SLTI 	t0, s0, 3	# if s0 < 3 : t0=1 else t0=0
	BEQ	t0,zero, L02 # または「beqz	t0,L02」で繰り返し終了分岐



	NOP
	LUI	v0,0xa000 # 以下3行で_PTR_HANDLERS[_IDX_API_SEND_STRING]内容をv0に設定
	ORI	v0,v0,0x41a4 
	LW	v0,0(v0)
	LUI	v1,%hi(.L_STR_HELLO)	# v0の関数の引数を準備
	ADDIU	a0,v1,%lo(.L_STR_HELLO)	# v0の関数の引数をa0レジスタに設定
	JALR	v0		# Raに戻り値を設置してv0の関数を呼び出す。
	NOP


	ADDIU	s0, s0, 1		# s0++
	J	L01
	NOP

L02:	MOVE	sp,fp		# フレームポインタで退避前でspを退避前に戻す。
	LW	ra,20(sp)	# 現在の関数の戻り番地をスタックから復元
	LW	fp,16(sp)	# この関数の呼び出し前のフレームポインタをスタックから復元
	JR	ra		# この関数の呼び出し後に移動(リターンに相当する)
	ADDIU	sp,sp,24	# この関数の呼び出し前のスタックポインタに戻る
	.end test_main

	.section	.L_STR_HELLO,address(0x80005060,code
	.align	2
.L_STR_HELLO:
	.ascii	"hello\r\n\x00"
#include <xc.h>	// test.S (USBへhelloの文字列を出力するプログラム)
	.set noreorder #アセンブラに命令の順序を自動変更させない。
	.section	test_main,address(0x80005000),code
	.ent	test_main
test_main:
	ADDIU	sp,sp,-24	# 以下で使う24byteまでの退避サイスをスタックに作る。
	SW	ra,20(sp)	# 現在の関数の戻り番地をスタックに退避
	SW	fp,16(sp)	# 呼び出し前の関数のフレームポインタをスタックに退避
	MOVE	fp,sp		# 現在の関数のフレームポインタをこの関数用に更新

	ADDIU	s0, zero, 1	# s0に1をセット
L01:	ADDIU	t1, zero, 3	# t1に3をセット
	SUB 	t0, s0, t1	# t0 = s0 - t1
	# bgez	t0, L02 	# t0が0以上で、繰り返し終了(s0-3>=0で分岐)で2回の繰り返し
	bgtz	t0, L02 	# t0が0より大きい時、繰り返し終了(s0-3>0で分岐)


	NOP
	LUI	v0,0xa000 # 以下3行で_PTR_HANDLERS[_IDX_API_SEND_STRING]内容をv0に設定
	ORI	v0,v0,0x41a4 
	LW	v0,0(v0)
	LUI	v1,%hi(.L_STR_HELLO)	# v0の関数の引数を準備
	ADDIU	a0,v1,%lo(.L_STR_HELLO)	# v0の関数の引数をa0レジスタに設定
	JALR	v0		# Raに戻り値を設置してv0の関数を呼び出す。
	NOP

	ADDIU	s0, s0, 1		# s0++
	J	L01
	NOP

L02:	MOVE	sp,fp		# フレームポインタで退避前でspを退避前に戻す。
	LW	ra,20(sp)	# 現在の関数の戻り番地をスタックから復元
	LW	fp,16(sp)	# この関数の呼び出し前のフレームポインタをスタックから復元
	JR	ra		# この関数の呼び出し後に移動(リターンに相当する)
	ADDIU	sp,sp,24	# この関数の呼び出し前のスタックポインタに戻る
	.end test_main

	.section	.L_STR_HELLO,address(0x80005060,code
	.align	2
.L_STR_HELLO:
	.ascii	"hello\r\n\x00"