ここでは、皆さんが情報システム工学実験第2の「2-4.CPUの構成」で 使ったCPUと似たアーキテクチャのCPUを設計し、 実際にFPGA上で動作させてみましょう。
命令(op) | ニーモニック表記 | 動作内容 |
0000 | mov imm, r0 | immをr0に代入 |
0001 | mov imm, r1 | immをr1に代入 |
0010 | add r0, imm, r0 | r0+immをr0に代入 |
0011 | add r0, imm, r1 | r0+immをr1に代入 |
0100 | add r1, imm, r0 | r1+immをr0に代入 |
0101 | add r1, imm, r1 | r1+immをr1に代入 |
0110 | jmp imm | imm番地へジャンプ(無条件分岐) |
0111 | jz imm | Zフラグ=1ならばimm番地へ分岐、それ以外は次の命令へ |
命令メモリは、アドレスiaddrを指定するとその番地に入っている 命令をidataとして返す回路、とみることができますので、 CPUが行うべき動作としては、idataの値に応じて レジスタに書き込んだり分岐したり、といった動作を行う、ことになります。
またレジスタr0, r1やpcの値などを外部から観測できるようにしたほうが 動作確認には便利ですので、7セグメントLED(4桁)や8個のLEDに 必要なものを表示させることにします。 なお全体の動作はクロック信号clkにあわせて行うとし、 このclkはスイッチSW[0]で作成することにします。 このクロックと7セグメントLED表示は、ライブラリとして peripheral.vを用意しておきますので、これを使います。
module sample(CLK6, LED, SW, SG, SA); input CLK6; input [3:0] SW; output [7:0] LED, SG; output [3:0] SA; reg [3:0] r0, r1, pc; // register (r0/r1) & pc wire [3:0] iaddr, op, imm; // iaddr, op, imm wire [7:0] idata; // idata wire [3:0] d3, d2, d1, d0; reg st, z; wire clk, rst; imem i0(iaddr, idata); // instruction memory sw_clk iclk(CLK6, ~SW[0], clk); // clock seg7 iseg7(CLK6, d3, d2, d1, d0, SG, SA); // 7segment LED display assign rst = ~SW[3]; // SW[3]:reset assign op = idata[7:4], imm = idata[3:0]; assign iaddr = pc; // LED display assignment assign LED = {z, st, clk, 1'b0, op}; // 7segment display assignment assign d3 = 4'd0, d2 = iaddr, d1 = r1, d0 = r0; always @(posedge clk or posedge rst) begin if (rst == 1'b1) begin // reset pc <= 4'h0; st <= 1'b0; z <= 1'b0; r0 <= 4'h0; r1 <= 4'h0; end else begin // CPU state machine // ...... end end endmoduleここでクロック生成のsw_clkと7セグメントLED制御の7segは、 peripheral.vの中で記述されていて、ここではそれを インスタンス呼び出しして利用しています。 (興味がある人はperipheral.vの中身をのぞいてみてください)
seg7は、4ビットのd0〜d3に与えられる値を各桁の7セグメントLEDに 自動的に表示します。 (d0が最下位(右端)、d3が最上位(左端)。値は0〜9/A〜Fの16進表記)
演習2-1 このsample.vで、7セグメントLEDの各桁と8個のLEDに表示される情報が 何になるかを理解しましょう。 また各インスタンス間の接続や使われている信号を理解しましょう。 (例えば命令opや即値immの作り方など)
// Instruction Memory module imem(addr, data); input [3:0] addr; output [7:0] data; reg [7:0] data; always @(addr) begin case (addr) .... endcase end endmodule
演習2-2 プロジェクトに新しいファイルimem.vをFile→New→(Verilogファイル)で追加し、 以下のようなプログラムを格納する命令メモリimemのVerilogHDL記述を 追加してみましょう。 またその動作をシミュレーションで確認しておきましょう。
番地(iaddr) | 命令(idata) | 命令のニーモニック表記 |
0 | 0000 0001 | mov 1, r0 |
1 | 0001 0010 | mov 2, r1 |
2 | 0110 0010 | jmp 2 |
※この場合のように、最上位階層の回路(この場合はsample)の中にある回路に対してシミュレーションを行うには、一時的にシミュレーションを行いたい回路を以下の手順で最上位階層(Top Level)にする必要があります。(シミュレーションが終わったら、本来の最上位階層のsampleを最上位階層に戻す)
また複数の回路に対して、別々の入力信号波形を使いたい場合は、以下のように個別に指定することができます。
↑最上位階層にしたい回路(この場合はimem)を選んで右クリック→"Set as Top-Level Entity"(これでこのimemが最上位階層になる)
この回路に対して、最初に作った入力信号波形(*.vwf)とは別の入力信号波形を与えてシミュレーションを行いたい場合は、その回路を右クリック→"Settings"から、カテゴリー"Simulator Settings"で"Simulation input"のところで使いたい入力信号波形ファイル(*.vwf)を指定すればよい。
※なお入力信号波形ファイルで信号を与える入力信号を入力するとき、信号の種類(Type)が"Input"以外(たとえばBuried)となってしまうことがあるようです。その場合は、信号名を右クリック→Propertyから、"Type"を"Input"に指定しておいてください。
"Input"
"Buried"
以下は、戻すときの手順。
↑戻すときは、まず現在の最上位階層のimemを選んで右クリック→"Settings"
↑Top-level entityのところに現在のimemが表示されているので、その右の"..."ボタンを押す
↑最上位階層に変更する回路を選ぶ画面になるので、本来の最上位階層であるsamplpeを選んでOKを押す
↑最初の階層構造である、最上位階層=sample、その中にimem等、の構造となる
演習2-3 CPUの記述を完成させ、imemとあわせてインプリメンテーションし、 シミュレーションでの検証や実機での動作を確認してみましょう。
※(補足)CPU全体のシミュレーションを行うときには、クロック生成用の回路sw_clkにクロック信号(この実験で使っているFPGAボードでは6MHz)を与える必要があります。 (本来は7セグメントLED表示回路seg7にも必要)これは、sw_clkが「スイッチを押してから一定時間だけ出力clk=1とする」という動作のためにクロック信号CLK6を使っているためです。(詳しくはperipheral.v内のsw_clkの記述を参照) そのためシミュレーションでもこのCLK6を入力として与える必要がありますが、sw_clkの動作を見るとわかるように、CLK6の90万サイクル分だけclk=1とする、という動作のため、これをシミュレーションで検証するのは効率がよくありませんし現実的ではありません。
そこで、シミュレーション用にperipheral.v内に書いてあるsw_clkの記述をコメントアウトした上で次のように書き換え、CLK6を使わずにスイッチからクロック信号clkを作るようにしたうえでシミュレーションを行うとよいでしょう。
module sw_clk(CLK6, a, q); input CLK6, a; output q; assign q = a; endmoduleもちろんシミュレーションが終わってFPGAボードに書き込むときには、sw_clkを戻すのをお忘れなく。
※(補足)CPU動作のシミュレーションでは、CPUを記述しているsampleの出力がLEDとSGとSA(いずれもLEDや7セグメントLEDの信号)のため、確認がしにくいです。(特にSAとSGはseg7によってダイナミック駆動される信号のため、どのような出力が出ているのか、がわかりにくい)
そこで、一時的に、シミュレーションで観測したい信号をsampleの出力として定義してシミュレーションを行う、という方法が便利です。
たとえば信号d0を観測するならば、以下のように記述します。
//module sample(CLK6, LED, SW, SG, SA); // comment out module sample(CLK6, LED, SW, SG, SA, d0); // added output [3:0] d0; // addedもちろんシミュレーションが終わってFPGAボードに書き込むときには、sw_clkを戻すのをお忘れなく。
番地(iaddr) | 命令(idata) | 命令のニーモニック表記 |
0 | 0000 0000 | mov 0, r0 |
1 | 0001 0011 | mov 3, r1 |
2 | 0010 0100 | add r0, 4, r0 |
3 | 0101 1111 | add r1, 15, r1 |
4 | 0111 0110 | jz 6 |
5 | 0110 0010 | jmp 2 |
6 | 0110 0110 | jmp 6 |
演習2-4 上記のプログラムの動作を理解し、imemの内容をこれにあわせて変更して プログラムを実行させ、結果を確認してみましょう。
※ヒント: 3番地の"add r1, 15, r1"は、「r1+15→r1」だが、 r1は4ビットで桁あふれは無視する。 例えばr1=3でこの命令を実行すると、r1+15=18(2進数で1 0010)だが 桁あふれは無視するのでr1=2となる。すなわち-1(減算)を行っていることになる。
演習2-5 これらの命令を使って独自プログラムを作成し、その動作を確認してみましょう。