C プログラムの動作実験 5-1: 関数呼出・ディスプレイへの文字列出力関数

実験の概要

本実験では、FPGA を搭載した実験基板を使用し、プロセッサを FPGA 上に実現してその動作を確認する。 本動作実験では、 ジャンプ・アンド・リンク命令(jump and link:jal)が未実装なプロセッサにおいて、その命令を含む簡単な機械語のマシン・コードを実行すると、どのような動作をするかを観察する。 本実験で観察した結果は、次のプロセッサの追加設計 4 において、 jal が正しく動くプロセッサを完成させた後、 動作確認の際の比較に用いる。

C プログラムの動作実験 5-1

ディスプレイに文字列を表示する C プログラム my_print.c と、それを実行するプロセッサとして実験 4-2 で完成させたプロセッサを FPGA 上に実現し、その動作を確認せよ。

本動作実験を下記の 1, 2, 3, 4, 5 の手順で行いなさい。
1. クロスコンパイル
2. MIPS マシン・コードからのメモリ・イメージファイルの作成
3. 命令メモリに格納される命令列の確認
4. 論理合成
5. FPGA を用いた回路実現

なお、本実験で使用する C プログラム my_print.c は、下記の URL からダウンロードできる。

C プログラム my_print.c:
http://www.ice.nuie.nagoya-u.ac.jp/jikken/hard/j2hard-mips/k05_jal_5_1/my_print.c
(実験 5-1 用の C プログラム)

1. クロスコンパイル

本実験では、Cプログラムの例として文字列を表示する図1のmy_print.cを使用する(ダウンロード )。
#define EXTIO_PRINT_STROKE (*(volatile unsigned int *) 0x0300)
#define EXTIO_PRINT_ASCII  (*(volatile unsigned int *) 0x0304)

void my_print();

main()
{
  unsigned int string[64];

  string[0] = 'H';
  string[1] = 'E';
  string[2] = 'L';
  string[3] = 'L';
  string[4] = 'O';
  string[5] = '!';
  string[6] = '!';
  string[7] = '\0';

  my_print(string);

  string[0] = 'B';
  string[1] = '\0';
}

void my_print(str)
     unsigned int *str;
{
  while (*str != '\0') {
    EXTIO_PRINT_STROKE = (unsigned int)0x00000000;

    if ((*str >= 'A') && (*str <= 'Z')) {
      EXTIO_PRINT_ASCII = *str - 'A' + 1;
    } else if ((*str >= 'a') && (*str <= 'z')) {
      EXTIO_PRINT_ASCII = *str - 'a' + 1;
    } else if ((*str >= '0') && (*str <= '9')) {
      EXTIO_PRINT_ASCII = *str - '0' + 48;
    } else {
      if (*str == '@') {
	EXTIO_PRINT_ASCII = (unsigned int)0;
      } else if (*str == '[') {
	EXTIO_PRINT_ASCII = (unsigned int)27;
      } else if (*str == ']') {
	EXTIO_PRINT_ASCII = (unsigned int)29;
      } else if ((*str >= ' ') && (*str <= '/')) {
	EXTIO_PRINT_ASCII = *str - ' ' + 32;
      } else if (*str == '?') {
	EXTIO_PRINT_ASCII = (unsigned int)58;
      } else if (*str == '=') {
	EXTIO_PRINT_ASCII = (unsigned int)59;
      } else if (*str == ';') {
	EXTIO_PRINT_ASCII = (unsigned int)60;
      } else if (*str == ':') {
	EXTIO_PRINT_ASCII = (unsigned int)61;
      } else {
	EXTIO_PRINT_ASCII = (unsigned int)0x00000000;
      }
    }

    EXTIO_PRINT_STROKE = (unsigned int)0x00000001;
    str++;
  }
}
          
図 1: my_print.c

クロスコンパイルには、「cross_compile.sh」を使用する。指導書2.2.1 節に示した環境設定を行ったのち、図 2 のようにして、my_print.c から MIPS マシン・コードを作成せよ。
cross_compile.sh my_print.c
図 2: cross_compile.sh を使った MIPS マシン・コードの生成

クロスコンパイルにより、MIPS マシン・コード my_print.bin が得られる。

2. MIPS マシン・コードからのメモリ・イメージファイルの作成

本実験では、MIPS マシン・コード my_printd.bin を使用する。 また変換には、変換プログラム bin2v を使用する。図 3 のようにして、MIPS マシン・コード my_print.bin からメモリ・イメージファイルを作成せよ。
bin2v my_print.bin
図 3: bin2v を使った MIPS マシン・コードのメモリ・イメージファイルへの変換

この変換により、論理合成用のメモリ・イメージファイル rom8x1024_DE2.mif と、機能レベルシミュレーション用の命令メモリの Verilog HDL 記述 rom8x1024_sim.v が得られる。

なお、本実験で使用する MIPS マシン・コード my_print.bin は、正しいプロセッサ(ジャンプ&リンク命令jalが実装済みのプロセッサ)で動作させると、以下のような動作をする命令列を含んだバイナリ・ファイルである。
  1. データメモリ(RAM)の $s30+16 番地に 'H' を格納
    addiu $s2, $s0, 0x0048
    sw $s2, 0x0010($s30)
  2. データメモリ(RAM)の $s30+20 番地に 'E' を格納
    addiu $s2, $s0, 0x0045
    sw $s2, 0x0014($s30)
  3. データメモリ(RAM)の $s30+24 番地に 'L' を格納
    addiu $s2, $s0, 0x004c
    sw $s2, 0x0018($s30)
  4. データメモリ(RAM)の $s30+28 番地に 'L' を格納
    addiu $s2, $s0, 0x004c
    sw $s2, 0x001c($s30)
  5. データメモリ(RAM)の $s30+32 番地に 'O' を格納
    addiu $s2, $s0, 0x004f
    sw $s2, 0x0020($s30)
  6. データメモリ(RAM)の $s30+36 番地に '!' を格納
    addiu $s2, $s0, 0x0021
    sw $s2, 0x0024($s30)
  7. データメモリ(RAM)の $s30+40 番地に '!' を格納
    addiu $s2, $s0, 0x0021
    sw $s2, 0x0028($s30)
  8. データメモリ(RAM)の $s30+44 番地に '\0' を格納
    sw $s0, 0x002c($s30)
  9. PC = 0x04000a0 番地の命令にジャンプ&リンク
    jal 0x04000a0

3. 命令メモリに格納される命令列の確認

本実験では、プロセッサの命令メモリに格納される命令列の確認を行う。この確認には、bin2v により生成された機能レベルシミュレーション用の Verilog HDL 記述 rom8x1024_sim.v を使用する。

図 4 に rom8x1024_sim.v の一部を示す。図 4 の case ブロック内の各行は、本実験で設計するプロセッサにおける、命令メモリの 10-bit アドレスとそこに格納される 32-bit 命令の機械語の記述である。また、各行の // 以降のコメント部には、その行に記述されているアドレスと命令に関する説明が記述されている。コメント部には、実際の MIPS の命令メモリにおけるアドレスと、命令名、命令の意味が記述されている。命令の意味の記述では、シンボル REG[0], REG[1], ..., REG[31] により、レジスタ 0 番から 31 番、$s0, ..., $s31 を表す。また、シンボル RAM[w] により、データメモリの w 番地を表す。

<省略>
case (word_addr)
<省略>
10'h00b: data = 32'h03a0f021; // 0040002c: ADDU, REG[30]<=REG[29]+REG[0];  ここが PC=0x002c の命令
10'h00c: data = 32'h24020048; // 00400030: ADDIU, REG[2]<=REG[0]+72(=0x00000048);
10'h00d: data = 32'hafc20010; // 00400034: SW, RAM[REG[30]+16]<=REG[2];
10'h00e: data = 32'h24020045; // 00400038: ADDIU, REG[2]<=REG[0]+69(=0x00000045);
10'h00f: data = 32'hafc20014; // 0040003c: SW, RAM[REG[30]+20]<=REG[2];
10'h010: data = 32'h2402004c; // 00400040: ADDIU, REG[2]<=REG[0]+76(=0x0000004c);
10'h011: data = 32'hafc20018; // 00400044: SW, RAM[REG[30]+24]<=REG[2];
10'h012: data = 32'h2402004c; // 00400048: ADDIU, REG[2]<=REG[0]+76(=0x0000004c);
10'h013: data = 32'hafc2001c; // 0040004c: SW, RAM[REG[30]+28]<=REG[2];
10'h014: data = 32'h2402004f; // 00400050: ADDIU, REG[2]<=REG[0]+79(=0x0000004f);
10'h015: data = 32'hafc20020; // 00400054: SW, RAM[REG[30]+32]<=REG[2];
10'h016: data = 32'h24020021; // 00400058: ADDIU, REG[2]<=REG[0]+33(=0x00000021);
10'h017: data = 32'hafc20024; // 0040005c: SW, RAM[REG[30]+36]<=REG[2];
10'h018: data = 32'h24020021; // 00400060: ADDIU, REG[2]<=REG[0]+33(=0x00000021);
10'h019: data = 32'hafc20028; // 00400064: SW, RAM[REG[30]+40]<=REG[2];
10'h01a: data = 32'hafc0002c; // 00400068: SW, RAM[REG[30]+44]<=REG[0];
10'h01b: data = 32'h27c20010; // 0040006c: ADDIU, REG[2]<=REG[30]+16(=0x00000010);
10'h01c: data = 32'h00402021; // 00400070: ADDU, REG[4]<=REG[2]+REG[0];
10'h01d: data = 32'h0c100028; // 00400074: JAL, PC<=0x00100028*4(=0x004000a0); REG[31]<=PC+4  ここが 命令メモリ 0x01d の命令
<省略>
endcase
<省略>
図 4: rom8x1024_sim.v の一部

図4のcaseブロック内の最後の記述は、本実験で設計するプロセッサの命令メモリの0x01d番地に機械語0x0c1000028が格納されることを表している。 また、この命令は実際のMIPSでは0x00400074に格納され、命令名はjal、レジスタ31番にPC+4をセットし、PCに0x00100028*4をセットする命令であることを表している。

my_print.bin から生成された rom8x1024_sim.v, または、図 4 の Verilog HDL 記述を解析し、以下の 1, 2 について答えよ。 なお、jal はジャンプ・アンド・リンク命令はである。
  1. プロセッサが最初に PC=0x0074 番地の命令を実行した直後のレジスタ 31 番目の値を予想せよ。
  2. プロセッサが最初に PC=0x0074 番地の命令を実行した直後の PC の値を予想せよ。

3. 論理合成

本実験では、jal 命令が未実装なプロセッサならびに命令メモリ、その他周辺回路の論理合成を行う。 論理合成には、bin2v により生成された論理合成用のメモリ・イメージファイル rom8x1024_DE2.mif と実験 4-2 で完成させたプロセッサの Verilog HDL 記述一式を使用する。

命令メモリのメモリ・イメージファイル rom8x1024_DE2.mif をディレクトリ mips_de2-115 にコピーし、ディレクトリ mips_de2-115 に cd して、図 5 のように論理合成を行う。

quartus_sh --flow compile DE2_115_Default
図 5: quartus_sh を使ったプロセッサの論理合成

なお、論理合成には計算機の性能により 5 分から 20 分程度の時間がかかる。 論理合成が完了すると、ディレクトリ mips_de2-115 内に FPGA にダウンロード可能なプロセッサ等の回路一式のストリーム・アウト・ファイル DE2_115_Default.sof が生成される。

4. FPGA を用いた回路実現

本実験では、jal 命令が未実装なプロセッサの実際の動作を観察する。 観察した結果は、次のプロセッサの追加設計 4 において、jal が正しく動くプロセッサを完成させた後、動作確認の際の比較に用いる。 本実験には、論理合成により生成されたプロセッサなど回路一式のストリーム・アウト・ファイル DE2_115_Default.sof を使用する。 DE2_115_Default.sof を quartus_pgm を用いて DE2-115 ボード上の FPGA にダウンロードし、動作させよ。

今回プロセッサが実行するマシン・コードmy_print.binはディスプレイに文字列"HELLO!!"を表示するプログラムである。 key3を連打してプロセッサにクロックパルスを送り、プロセッサにPC=0x0000番地から70個程度の命令を実行させ、ディスプレイ下部に文字列"HELLO!!"の一部が表示されるかどうかを確認せよ。 ディスプレイ下部に"HELLO!!"に含まれる文字は1つも表示されないはずである。

ディスプレイ上部にはプロセッサ内部の主な信号線の現在の値が表示されている。 各信号線は、プロセッサのブロック図中の名前の似た信号線と、それぞれ対応している。 ブロック図中の線の幅はビット幅と対応しており、一番細い線は 1-bit の線、一番太い線は 32-bit の配線を表している。 また、ブロック図左下の ROM が、命令メモリである。 プロセッサはここから命令を読み、命令毎に決められた処理を行う。 ブロック図右下の RAM は、データメモリである。 3 の命令メモリに格納される命令列の確認の、1, 2 で予想した結果と同じ正しい動作かどうかを確認せよ。 予想と異なる正しくない動作のはずである。

プロセッサが、jal 命令を正しく実行できていないことが分かる。 これらの命令を正しく実行するために、プロセッサ内部で行われるデータ転送や演算などを制御しているメイン制御回路を、この命令を適切に処理できるものにする必要がある。

参考書