C プログラムの動作実験 6-1: 関数からの復帰・キーボードからの文字列入力を受ける関数

実験の概要

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

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

キーボードからの文字列入力を受ける C プログラム my_scan.c と、それを実行するプロセッサとして実験 5-2 で完成させたプロセッサを FPGA 上に実現し、その動作を確認せよ。

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

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

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

1. クロスコンパイル

本実験では、C プログラムの例としてキーボードからの文字列入力を受ける図 1 の my_scan.c を使用する(ダウンロード )。
#define EXTIO_SCAN_ASCII (*(volatile unsigned int *)0x0310)
#define EXTIO_SCAN_REQ (*(volatile unsigned int *)0x030c)
#define EXTIO_SCAN_STROKE (*(volatile unsigned int *)0x0308)

#define SCAN_STRORING (unsigned int)0xffffffff

#define EXTIO_PRINT_STROKE (*(volatile unsigned int *) 0x0300)
#define EXTIO_PRINT_ASCII  (*(volatile unsigned int *) 0x0304)

void my_print();
void my_scan();

main()
{
  unsigned int string1[32];
  unsigned int string2[32];

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

  my_print(string1);

  while (1) {
    string1[0] = 'S';
    string1[1] = 'T';
    string1[2] = 'R';
    string1[3] = 'I';
    string1[4] = 'N';
    string1[5] = 'G';
    string1[6] = '=';
    string1[7] = '\0';

    my_print(string1);

    my_scan(string2);

    string1[0] = 'E';
    string1[1] = 'C';
    string1[2] = 'H';
    string1[3] = 'O';
    string1[4] = ' ';
    string1[5] = '\0';

    my_print(string1);

    my_print(string2);

    string1[0] = '\n';
    string1[1] = '\0';

    my_print(string1);
  }
}

void my_scan(str)
     unsigned int *str;
{
    EXTIO_SCAN_STROKE = (unsigned int)0x00000000;
    EXTIO_SCAN_REQ = (unsigned int)0x00000001;
    EXTIO_SCAN_STROKE = (unsigned int)0x00000001;

    EXTIO_SCAN_STROKE = (unsigned int)0x00000000;
    EXTIO_SCAN_STROKE = (unsigned int)0x00000001;
    while (EXTIO_SCAN_ASCII == SCAN_STRORING) {
      EXTIO_SCAN_STROKE = (unsigned int)0x00000000;
      EXTIO_SCAN_STROKE = (unsigned int)0x00000001;
    }

    while ((*str = EXTIO_SCAN_ASCII) != (unsigned int)0x3e) {    // 0x3e=RETURN
      if ((*str >= 1) && (*str <= 26)) {
	*str = 'A' + *str - 1;
      } else if ((*str >= 48) && (*str <= 57)) {
	*str = '0' + *str - 48;
      } else {
	if (*str == 0) {
	  *str = '@';
	} else if (*str == 27) {
	  *str = '[';
	} else if (*str == 29) {
	  *str = ']';
	} else if ((*str >= 32) && (*str <= 47)) {
	  *str = ' ' + *str - 32;
	} else if (*str == 58) {
	  *str = '?';
	} else if (*str == 59) {
	  *str = '=';
	} else if (*str == 60) {
	  *str = ';';
	} else if (*str == 61) {
	  *str = ':';
	} else if (*str == 62) {
	  *str = '\n';
	} else {
	  *str = '@';
	}
      }
      EXTIO_SCAN_STROKE = (unsigned int)0x00000000;
      EXTIO_SCAN_STROKE = (unsigned int)0x00000001;
      str++;
    }
    *str = '\0';

    EXTIO_SCAN_STROKE = (unsigned int)0x00000000;
    EXTIO_SCAN_REQ = (unsigned int)0x00000000;
    EXTIO_SCAN_STROKE = (unsigned int)0x00000001;

    EXTIO_SCAN_STROKE = (unsigned int)0x00000000;
}

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 if (*str == '\n') {
	EXTIO_PRINT_ASCII = (unsigned int)62;
      } else {
	EXTIO_PRINT_ASCII = (unsigned int)0x00000000;
      }
    }

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

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

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

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

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

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

なお、本実験で使用する MIPS マシン・コード my_scan.bin は、正しいプロセッサ(ジャンプレジスタ命令jrが実装済みのプロセッサ)で動作させると、以下のような動作をする命令列を含んだバイナリ・ファイルである。
  1. PC = 0x04004d8 番地の命令にジャンプ&リンク
    jal 0x04004d8
  2. PC = REG[31] 番地の命令にジャンプレジスタ
    jr

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'h01f: data = 32'h0c100136; // 0040007c: JAL, PC<=0x00100136*4(=0x004004d8); REG[31]<=PC+4
<省略>
10'h136: data = 32'h27bdfff8; // 004004d8: ADDIU, REG[29]<=REG[29]+65528(=0x0000fff8);
10'h137: data = 32'hafbe0000; // 004004dc: SW, RAM[REG[29]+0]<=REG[30];
10'h138: data = 32'h03a0f021; // 004004e0: ADDU, REG[30]<=REG[29]+REG[0];
10'h139: data = 32'hafc40008; // 004004e4: SW, RAM[REG[30]+8]<=REG[4];
10'h13a: data = 32'h081001f8; // 004004e8: J, PC<=0x001001f8*4(=0x004007e0);
<省略>
10'h1f8: data = 32'h8fc20008; // 004007e0: LW, REG[2]<=RAM[REG[30]+8];
10'h1f9: data = 32'h00000000; // 004007e4: SLL, REG[0]<=REG[0]<<0;
10'h1fa: data = 32'h8c420000; // 004007e8: LW, REG[2]<=RAM[REG[2]+0];
10'h1fb: data = 32'h00000000; // 004007ec: SLL, REG[0]<=REG[0]<<0;
10'h1fc: data = 32'h1440ff3f; // 004007f0: BNE, PC<=(REG[2] != REG[0])?PC+4+65343*4:PC+4;
10'h1fd: data = 32'h00000000; // 004007f4: SLL, REG[0]<=REG[0]<<0;
10'h1fe: data = 32'h03c0e821; // 004007f8: ADDU, REG[29]<=REG[30]+REG[0];
10'h1ff: data = 32'h8fbe0000; // 004007fc: LW, REG[30]<=RAM[REG[29]+0];
10'h200: data = 32'h27bd0008; // 00400800: ADDIU, REG[29]<=REG[29]+8(=0x00000008);
10'h201: data = 32'h03e00008; // 00400804: JR, PC<=REG[31];  ここが 命令メモリ 0x201 の命令
<省略>
endcase
<省略>
図 4: rom8x1024_sim.v の一部

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

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

3. 論理合成

本実験では、jr 命令が未実装なプロセッサならびに命令メモリ、その他周辺回路の論理合成を行う。 論理合成には、bin2v により生成された論理合成用のメモリ・イメージファイル rom8x1024_DE2.mif と実験 5-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 を用いた回路実現

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

今回プロセッサが実行するマシン・コード my_scan.bin はキーボードからの文字列入力を受けるプログラムである。 プロセッサにクロックパルスを次々送り、プロセッサにPC=0x0000 番地からの命令を実行させ、ディスプレイ下部に文字列が 2 つ表示されるかどうかを確認せよ。 ディスプレイ下部に文字列は 1 つしか表示されないはずである。

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

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

参考書