1. ホーム
  2. c++

c++ write シンプルなmipsコンパイラ

2022-02-09 19:59:38

グループ実験を書くときには、まずmips文をバイナリ・マシンコードに変換する必要があるので、バイナリ・マシンコードはコンパイラの力を借りずに、コード表に対して一つ一つ手作業でコンパイルするしかない。気をつけないと間違うし、間違ったら間違いを見つけるのが大変で、再コンパイルが必要になることもあります。

そこで、32ビット長のバイナリ・マシン・コードに必要な基本的なmips文をコンパイルできる簡単なmipsコンパイラをc++で実装することにしたのです。


まず、別のブログ(Multi-cycle cpu design and implementation)で紹介したmips命令を使って、以下のようにテキストファイルtxtの中にmips命令を記述する必要があります。


この後、c++ファイルにmips命令を読み込んで、文字列を解析してデコードする必要があります。

int main() {
	int binary[32][32];
	ifstream fin;
	fin.open("mips.txt", ios_base::in);
	if(!fin) {
		cerr << "Open Error!" << endl;
		exit(1);
	}
	int i, j;
	char buffer[256];
	char* q = NULL;
	char* op;
	string oop;
	for(i = 0; i < 32; i++) {
		for(j = 0; j < 32; j++) {
			binary[i][j] = 0;
		}
	}
	j = 0;
	while(!fin.eof()) {
		fin.getline(buffer, 100);
		if(buffer[0] ! = 0) {
			q = NULL;
			op = strtok(buffer, ", $(), () ");
			oop = op;
		    int* newline = fromooptobinary(oop);
		    for(i = 0; i < 32; i++) {
		    	binary[j][i] = newline[i];
			}
		}
		j++;
	}
	int count = j;



ここでは、mips命令の行を文字列としてバッファに読み込むために、getlineメソッドを使用しています。
次に、strtokメソッドで文字列をパースします。ここで使われているstrtokメソッドはあまり安全ではなく、ネット上の情報ではstrtok_rを使うことが勧められています。
mips命令の最初の値はstrtokで取得するため、"add"、"sub"、"jal"などの命令名である必要があります。
ここでは、関数int* fromooptobinary (string oop)をカスタマイズして、命令名をシェイプシフト配列に変換し、その変換結果である機械語を2次元シェイプシフト配列バイナリに格納するようにしています。
ここでのデコードは、すべて私の他のブログ(マルチサイクルCPUの設計と実装)を参考にしています:http://blog.csdn.net/zhongzi_l/article/details/51485113

int* fromooptobinary (string oop)の実装は以下の通りです。


oopディレクティブ名は大文字と小文字が混在する可能性があるため、string tolower (string oop) のカスタムメソッドを使用して、大文字を小文字に変換してください。
ここで推奨されているのは、stlメソッドの遷移関数を使って大文字と小文字を変換することですが、それは手間がかかるし、大文字と小文字を変換するだけなら簡単な気がします。

string tolower(string s) {
	int len = s.size();
    for (int i=0; i<len; i++) {
        if ( s[i] >= 'A' && s[i] <= 'Z' ) {
            s[i] += ('a' - 'A' ) ;
        }
    }
    return s;
}



次に、"j 0x00000010"ジャンプ命令のように、次の00000010の16進数アドレスに対して32ビット長のバイナリ・マシンコードに変換する必要がある命令があるため、以下のカスタム関数を作成します。

int* chartobinary(char *p) {
	int i = 0;
	int j = -1;
	int k = 0;
	int *q = new int[32];
	for (i = 3; i < 10; i++) {
		int a = p[i] - '0';
		j += 4;
		for(k = 0; k < 4; k++) {
			q[j] = a%2;
			a = a/2;
			j--;
		}
		j += 4;
	}
	return q;
}



用途は int* fromooptobinary (string oop) 関数です。

if(oop == "j" || oop == "jal") {
		binary[0] = binary[1] = binary[2] = 1;
		if(oop == "jal") binary[4] = 1;
		p = strtok(NULL, ", $(), () ");
		int *k = chartobinary(p);
		for(i = 6; i < 32; i++) {
			binary[i] = k[i-6];
		}
	}



次に、レジスタ番号を5ビットのバイナリ・マシン・コード、または16ビットのバイナリ・マシン・コードに変換する必要があるデコードがあり、カスタム関数があります。

int* inttobinary(int t, bool choose) {
	int i = 0;
	if(choose) {
		int* q = new int[5];
		for(i = 4; i >= 0; i--) {
			q[i] = t%2;
			t = t/2;
		}
		return q;
	} else {
		bool check = false;
		if(t<0) {
			check = true;
			t = -t;
		}
		int* p = new int[16];
		for(i = 15; i >= 0; i--) {
			p[i] = t%2;
			t = t/2;
		}
		if(check) {
			for(i = 15; i >= 0; i--) {
				if(p[i] == 0) p[i] = 1;
				else p[i] = 0;
			}
			int j = 15;
			while(p[j] ! = 0) {
				p[j] = 0;
				j--;
			}
			if(j>=0&&p[j]==0) {
				p[j] = 1;
			}
		}
		return p;
	}
}



この関数の使用は、例えば int* fromooptobinary (string oop) 関数の中にも見られます。

else if(oop == "move") {
		binary[0] = 1;
		p = strtok(NULL, ", $(), () ");
		int k = atoi(p);
		int* newline = inttobinary(k, true);
		p = strtok(NULL, ", $(), () ");
		k = atoi(p);
		int* newline2 = inttobinary(k, true);
		for(i = 6; i < 11; i++) {
			binary[i] = newline2[i-6];
			binary[i+10] = newline[i-6];
		}
	}



この時点で基本的にデコードは完了しているので、次にやるべきことはバイナリ配列を別のテキストファイルに書き出すことだけです。

/*write to txt*/
	ofstream out;
	out.open("data.txt", ios::out|ios::trunc);
	if(out.is_open()) {
		for(i = 0; i < count; i++) {
			for(j = 0; j < 32; j++) {
				out << binary[i][j];
				if((j+1)%8 == 0 && j!=31) {
					out << " ";
				}
			}
			if(i ! = count - 1) {
				out << "\n";
			}
		}
		cout << "compile success!" <<endl;
		out.close();
	}




ios::truncは、最初に内容をクリアしてファイルを開くことを意味します。

c++ のファイル読み書きの具体的な方法は、http://blog.csdn.net/zhongzi_l/article/details/51541351 を参照してください。


このように、単純なmipsコンパイラが嬉しいことに完成したわけですが、もちろん、入力が基本入力フォーマットに沿っていない場合、デコードに予測できないエラーが発生する可能性があるなど、不完全な点も多々あります。

ここでは、その集計結果を見てみましょう。


この結果は、マルチサイクルCPUの設計・実装における値と整合しています。コンパイルは成功しました。

次に、この自作簡易mipsコンパイラを使って、他のブログ(シングルサイクルCPUの設計と実装)のmips命令も、もちろん少し手を加えてコンパイルしてみましたが、結果は正しく、このようになりました。


今回、簡単なmipsコンパイラを作る過程はかなり大変でしたが、全体的には自分の中で満足度が高く、この後もっと強力なコンパイラを作りたいと考えています。