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