1 B10 CPU を作る 1 日目 解説 TA 高田正法
2 はじめに 下のページに目を通すようにしてくださ い。 tokyo.ac.jp/~mtakada/jikken_b10/ tokyo.ac.jp/~mtakada/jikken_b10/ このスライドも上記の場所にあります。
3 今日説明する内容 実験の目的、内容 この実験で扱う道具について SPIM MIPS アーキテクチャ 課題 1 ~ 3 を行うために必要な知識 レジスタ 命令 システムコール MIPS 用プログラム記述の例
4 実験の目的 RISC 型プロセッサの構造、動作を理解す る 命令セットを理解する 命令を MIPS シミュレータ ”SPIM” に追加する ことによって、プロセッサの動作に関する理 解を深める
5 実験の内容 MIPS の命令セットを理解する アセンブラでプログラムを書く 課題 1 ~ 3 プロセッサの動作を理解する 構成図を見て考える 課題 4 ~ 5,8 シミュレータを用いて、命令追加を行う 課題 6 ~ 7 シミュレータと実機の違いについて考える 課題 8
6 SPIM とは? RISC プロセッサ MIPS R2000 のシミュ レータ 以下の機能をサポート アセンブリ言語からの直接読み込み プロセッサの状態表示 ステップ実行 簡単な入出力
7 MIPS とは? RISC 型プロセッサの一種 機能が単純な命令のみを持ち、複雑な機能は 命令の組み合わせでサポートする メモリを扱う命令が、他の命令と分離されて いるのが特徴 32 個の 32bit レジスタを持つ レジスタ : 計算をするための作業領域
8 メモリとレジスタの違い メモリ ○ 大きい (2^32Byte など ) ○ アクセスする領域 ( アドレス ) も変数 × 演算命令で直接扱えない × 遅い レジスタ ○ 演算命令で直接扱える ○ 速い × 小さい (32bit レジスタが 32 個 ) × アクセスする領域 ( レジスタ番号 ) は命令内で明示
9 レジスタの種類 (1) – 汎用レジス タ $t0 ~ $t9 ユーザー用のレジスタ サブルーチン ( 関数 ) を呼び出す際には、呼び 出す側で値を保存しておく必要がある $s0 ~ $s7 ユーザー用のレジスタ 呼び出されたサブルーチン側で、中身を保存 する必要がある
10 レジスタの種類 (2) – 関数呼び出し 用 $a0 ~ $a3 サブルーチン ( 関数 ) を呼び出す際に、引数を これに入れる $v0 ~ $v1 サブルーチン ( 関数 ) からの戻り値をここに入 れる $ra サブルーチン ( 関数 ) を呼び出した側の、プロ グラム番地を保存する
11 レジスタの種類 (3) – 特殊なレジス タ $zero 常に 0 が入っているレジスタ $sp スタックの先頭のアドレスが格納されるレジ スタ
12 レジスタの種類 (4) – 使わないもの 使ってはいけないレジスタ $at 疑似命令を展開する時に使われるレジスタ ここでいう疑似命令 : ソースファイルに書くことが可能であ るが、実際には複数の実在する命令に展開される命令のこと ( 例 :blt) $k0 ~ $k1 OS 予約レジスタ 気にしなくて良いレジスタ $gp, $fp
13 プロセッサの命令の種類 メモリ ←→ レジスタのデータのやり取りをする 命令 いわゆる load/store レジスタを用いて計算をする命令 算術演算、論理演算その他 次に実行する命令を変える命令 条件分岐命令 C 言語の if, for, while などで使われる 無条件ジャンプ命令 C 言語での関数呼び出しなどで使われる
14 lw$t0, 4($t1) メモリの ($t1 + 4) 番地に書かれている値 (32bit) を、 $t0 レジスタに格納する $t0 = *($t1 + 1); +1 なのは、 +4 番地 =32bit 向こう =1 要素進んだところだから sw$s2, 0($t0) $s2 の中身 (32bit) を、メモリの ($t0+0) 番地に格納する *($t0) = $s2; load/store の例 1Byte 1Word(32bit) x x+1 x+2 x+3 x+4…( 番地 ) 1Word(32bit)
15 演算命令の例 addi$t0, $t1, 1 $t0 = $t1 + 1; or$s0, $s1, $s2 $s0 = $s1 | $s2;
16 制御命令の例 bne$t0, $t1, LABEL $t0 != $t1 ならば、次からは、 LABEL 番地か ら始まる命令列を実行する if( $t0 != $t1 ) goto LABEL; jalLABEL $ra に、 jal 命令の番地 +4 を格納し、次からは LABEL 番地から始まる命令列を実行する サブルーチン ( 関数 ) 呼び出しに使われる
17 syscall の使い方 SPIM では、キーボードからの入力 / 画面へ の出力を、 syscall を使って行います $v0 にシステムコール番号を入れることに よって、数種類の機能を実現できます 詳しくは、 SPIM マニュアルの 8 ページを 参照してください
18 システムコールの例 $t0( 整数型 ) の中身を画面に表示 li$v0, 1# $v0 = 1; move$a0, $t0# $a0 = $t0; syscall メモリの $t0 番地から始まる文字列を表示 li$v0, 4# $v0 = 4; move$a0, $t0# $a0 = $t0; syscall プログラムの実行を終了 li$v0, 10 syscall
19 MIPS 用プログラム の記述例
20 具体例 このような関数を考えましょう num という配列が渡されたときに、先頭 n 要素の和を返す関数 int sum(int n, int num[]){ int sum = 0; int i; for( i = 0; i < n; i++ ){ sum += num[i]; } return sum; }
21 C 言語からアセンブリ言語へ C 言語の変数は、主に 2 種類 static 変数 プログラムのどこからでも見ることのできる変数 コンパイル時に、静的にメモリに割り当てられる local 変数 同じ関数内でのみ参照可能 実際には以下のいずれかに割り当てます レジスタ ← ほとんどこちらだけで事足ります メモリのスタック領域(※坂井先生の資料 8 ページ参照)
22 ソースファイルの書式.data# お約束 ここに、 static 変数に対応する部分を ”.word” 等の疑似命令を使って書く.text# お約束.globlmain# お約束 main:# お約束 ここからプログラムを書く # “#” から行末まではコメントになります
23 for 文の展開 for( i = 0; i < n; i++) をアセンブリ言語で書きま しょう n は、 $a0 に入っていると仮定 i は $t0 に割り当てましょう li$t0, 0# i = 0; loop_start: 何か処理 … addi$t0, $t0, 1# i++ bne$t0, $a0, loop_start# if( i != n ) goto loop_start;
24 メモリからの読み出し $a1 に、先頭の要素のアドレスが入っていると仮定しましょう $t1 に、現在の要素のアドレスを入れましょう $t2 に、各要素の値を一時的に格納しましょう $v0 に、合計の値を格納しましょう li$t0, 0# i =0; li$v0, 0# sum = 0; move$t1, $a1# $t1 = $a1; loop_start: lw$t2, 0($t1)# $t2 = *($t1); add$v0, $v0, $t2# $v0 = $v0 + $t2; addi$t1, $t1, 4# 次の要素は 4 番地 (32bit) 先 addi$t0, $t0, 1# i++; bne$t0, $a0, loop_start# if( I != n ) goto loop_start;
25 関数ができました sum: li$t0, 0# i =0; li$v0, 0# sum = 0; move$t1, $a1# $t1 = $a1; loop_start: lw$t2, 0($t1)# $t2 = *($t1); add$v0, $v0, $t2# $v0 = $v0 + $t2; addi$t1, $t1, 4# 次の要素は 4 番地 (32bit) 先 addi$t0, $t0, 1# i++; bne$t0, $a0, loop_start# if( i != n ) goto loop_start; jr$ra# 関数呼び出し元へ
26 この関数の仕様 配列に入っている各要素の和を計算する $a0: 要素数 $a1: 先頭のアドレス $v0: 計算結果
27 呼び出し元を書きましょう こんなプログラムだとしましょう int numOfElements=4;// static 変数 int elements[] = {1, 2, 3, 4};// static 変数 void main(){ int s = sum( numOfElements, elements); printf( “%d”, s); }
28 呼び出し元 main: lw$a0, numOfElements# $a0 = numOfElements; la$a1, elements# $a1 = &(elements[0]); jalsum# $v0 = sum($a0, $a1); move$a0, $v0# $a0 = s; li$v0, 1# syscall# prinf(“%d”, s); li$v0, 10 syscall# exit();
29 static 変数部分 今回は、 numOfElements と elements が static 変数.data numOfElements:.word 4# int numOfElements=4; elements:.word 1, 2, 3, 4# int elements[]={1, 2, 3, 4}; これで、 numOfElements が ”4” の入ったアド レス、 elements が 1, 2, 3, 4 が入ったメモリ の先頭のアドレスを指すようになります
30 完了 tokyo.ac.jp/~mtakada/jikken_b10/ にある sum.s がこのプログラムの完成品です tokyo.ac.jp/~mtakada/jikken_b10/ SPIM で読み込んで、実行してみましょう .word 部分の値を変えて、結果が変わることを 確認してみましょう
31 駆け足になりましたが 各内容についての詳細は以下を参照してくださ い レジスタの一覧 SPIM マニュアル 10 ページ 命令の一覧 SPIM マニュアル 13 ページ~ システムコールの一覧 SPIM マニュアル 8 ページ~ .data 部分に書くことのできるもの SPIM マニュアル 7 ページ~