トップ:http://galaxy.u-aizu.ac.jp/note/wiki/CAEX2016 = レジスタファイルについて = MIPSプロセッサにおいて、プログラマから見える汎用レジスタには $0 から $31 までの32個があります。 このうち $0 は値が常にゼロである仮想的なレジスタなので、実際のハードウェアでは31個のレジスタからなるレジスタファイルが使われます。 各レジスタの入出力データのビット幅は32ビットです。 レジスタファイルに対する書き込み口や読み出し口のことをポートと呼びます。 今回利用するレジスタファイルは2つの読み出しポートと1つの書き込みポートを備えています。 このことは、2つの異なるレジスタの読み出しと、さらに異なる1つのレジスタへの書き込みを、 1つのクロックサイクルの中で同時に行えるということを意味します。 == レジスタファイルの記述例:Registers.v == {{{ module Registers(ReadRegster1, ReadRegster2, WriteData, RegWrite, WriteRegster, CK, CLR, ReadData1, ReadData2); input[4:0] ReadRegster1, ReadRegster2, WriteRegster; input[31:0] WriteData; input RegWrite, CK, CLR; output[31:0] ReadData1, ReadData2; reg[31:0] regfile[1:31]; integer i; always @(posedge CK) begin if (CLR == 1'b1) for( i = 1; i < 31; i = i + 1) regfile[i] <= 32'h00000000; else if ( RegWrite == 1'b1 && WriteRegster != 5'b00000) regfile[WriteRegster] <= WriteData; end assign #5 ReadData1 = (ReadRegster1 == 5'b00000) ? 32'h00000000 : regfile[ReadRegster1]; assign #5 ReadData2 = (ReadRegster2 == 5'b00000) ? 32'h00000000 : regfile[ReadRegster2]; endmodule }}} === 入出力信号の説明 === * ReadRegster1 と ReadData1 は1つ目の読み出しポートのための信号です。ReadRegster1 にレジスタ番号を (0~31) を入力すると該当するレジスタの値が ReadData1 から出力されます(レジスタ番号が 0 のときは常に 0 が出力される)。 * ReadRegster2 と ReadData2 は2つ目の読み出しポートのための信号です。動作は1つ目の読み出しポートと同じです。 * !WriteData、!RegWrite、および!WriteRegsterは書き込みポートを形成します。WriteRegsterにレジスタ番号を入力し、WriteDataには書き込むデータをセットして、!RegWrite を 1 にすると、次のクロックの立ち上がりでデータが該当レジスタに書き込まれます。WriteEN が 0 のときは、クロックの立ち上がりがあってもデータは書き込まれません。また、レジスタ番号が 0 のときは、レジスタへの書き込みは行われません。 * CK はクロック信号、CLR はクリア信号です。 CLR が 1 になると全てのレジスタが 0 に初期化されます。 = メモリについて = メモリ(Memory.v)は、データを格納しておくために利用し、読み出しおよび書き込みの両方ができるメモリです。 {{{ module Memory(Address, WriteData, ReadData, MemWrite, MemRead, CK); input[31:0] Address, WriteData; input MemWrite, MemRead, CK; output[31:0] ReadData; always @(posedge CK) begin if (( MemWrite == 1'b1 ) && (32'h00000000 <= Address) && ( Address <= 32'h0000ffff) ) begin Mem.cell[Address] = WriteData; end end assign #5 ReadData = (MemRead == 1'b1) && (32'h00000000 <= Address) && ( Address <= 32'h0000ffff) ? Mem.cell[Address] : 32'bx; endmodule module Mem (); reg [31:0] cell [0:65535]; endmodule }}} === メモリMemoryの入出力信号の説明 === * 制御入力Memwriteが1のときデータの書き込みが、MemReadが1のときデータの読み込みが行われます。 * データを書き込むときは、Memwriteを1にすると、次のクロックの立ち上がりで、アドレスAddressにデータWriteDataが書き込まれます。アドレスは32'h00000000から32'h0000ffffが 有効です。これ以外の範囲の場合は書き込みは行われません。 * データを読み出すときは、MemReadを1にすると、ReadDataにそのアドレスに格納されたデータが出力されます。有効なアドレスは、書き込みの場合と同じです。 == メモリ初期化について == メモリの初期設定はテストベンチ記述で行います。 今後の演習ではメモリの初期化は、xspimが出力するテストベンチ用のVerilogHDL記述を利用します。 xspimのdumpコマンドを用いて出力されるVerilogHDL記述は以下のような形式になっています。 {{{ // text segment Mem.cell['h00000000] = 32'h08000400; // j 0x1000 Mem.cell['h00001000] = 32'h********; 以下プログラムに対するコードが続く ... // data segment Mem.cell['h00005000] = 32'h********; 以下データに対するコードが続く ... }}} この記述をテストベンチにコピーして、プログラムおよびデータ領域の初期化を行います。 {{{ Mem.cell['h00000000] = 32'h08000400; }}} という記述で、テストベンチからモジュールMemのレジスタcellに 直接アクセスしています。 = 課題を行う前の注意 = 上で説明したレジスタファイルmoduleとメモリmoduleをそれぞれ"Registers.v"と"Memory.v"としてファイルに保存すること。 = 課題1 レジスタの利用方法 = 以下のテストベンチは、レジスタファイルをインスタンス化して、$a0レジスタに"0xffffffff"を書き込んでいる。 {{{ `timescale 1ns/1ps `include "Registers.v" module RegTest; reg [4:0] read_adr1, read_adr2, write_adr; reg [31:0] write_data; reg write_enable; reg ck,clear; wire [31:0] read_data1, read_data2; Registers regs(read_adr1, read_adr2, write_data, write_enable, write_adr, ck, clear, read_data1, read_data2); initial begin $dumpfile("RegTest.vcd"); $dumpvars(0, RegTest); #0 ck = 1'b1; clear = 1'b1; #110 clear = 1'b0; #190 read_adr1 = 5'b00100; #100 write_enable = 1'b1; write_adr = 5'b00100; write_data = 32'hffffffff; $display("%x %x",read_adr1, read_data1); #100 write_enable = 1'b0; $display("%x %x",read_adr1, read_data1); #900 $finish; end // initial begin always #50 ck = ~ck; endmodule }}} これを参考にして、以下のテーブルに示すように、レジスタに値の値を書き込んで、読み出すテストベンチを作成し実行したうえで、クロック信号(ck)、読み出しているレジスタのアドレス(read_adr1)と読み出し結果(read_data1)の波形を確認しなさい。波形には、この三つの信号のみを表示すること。表示する順番は、小さいアドレスから大きいアドレスになるようにすること。なお、表示している信号名がわかるようにすること。 ||レジスタ|| 値|| || $sp || 0xfffff || || $v0 || 0xfafa || || $v1 || 0x1010 || || $t0 || 0xffffffff || || $t1 || 0xaaaaaaaa || || $ra || 0x98765432 || || $t2 || 0x98765432 || || $t3 || 0x12121 || == 波形の例 == 以下のように各レジスタにデータを書き込んだ場合: || $at || 0xf || || $v0 || 0xff || || $v1 || 0xfff || || $a0 || 0xffff || 実行結果の波形の例: [[Image(http://galaxy.u-aizu.ac.jp/note/raw-attachment/wiki/Ex06%E8%AA%B2%E9%A1%8C2015/comparch_ex6_reg.png)]] '''動作確認をするので、波形が表示できたらTAのチェックをうけること''' = 課題2 メモリの利用方法 = 以下のテストベンチファイルでは、レジスタファイルとメモリとALUをインスタンス化し、テストするためのテンプレートである。 {{{ `timescale 1ns/1ps `include "Registers.v" `include "Memory.v" `include "ALU.v" module MemTest; // for Memory reg [31:0] address; reg [31:0] write_data; reg write_enable, read_enable; reg ck; wire [31:0] read_data; // for Registers reg [4:0] read_adr1_reg, read_adr2_reg, write_adr_reg; reg [31:0] write_data_reg; reg write_enable_reg; reg clear_reg; wire [31:0] read_data1_reg, read_data2_reg; // for ALU reg [31:0] A, B; reg [3:0] ALUop; wire Zero; wire [31:0] Result; Memory mem(address, write_data, read_data, write_enable, read_enable, ck); Registers regs(read_adr1_reg, read_adr2_reg, write_data_reg, write_enable_reg, write_adr_reg, ck, clear_reg, read_data1_reg, read_data2_reg); ALU alu(A, B, ALUop, Result, Zero); initial begin $dumpfile("MemTest.vcd"); $dumpvars(0, MemTest); // data segment Mem.cell['h00005000] = 32'h0000000a; Mem.cell['h00005004] = 32'h0000000b; Mem.cell['h00005008] = 32'h00000000; Mem.cell['h00005010] = 32'h00000000; Mem.cell['h00005014] = 32'h00000000; Mem.cell['h00005018] = 32'h00000000; #0 ck = 1'b1; clear_reg = 1'b1; read_enable = 1'b0; address = 32'h00000000; write_enable_reg = 1'b0; write_adr_reg = 5'b00000; write_data_reg = 32'h00000000; #110 clear_reg = 1'b0; #190 // read memory at 0x5000 read_enable = 1'b1; address = 32'h00005000; #100 // write data to $v0 and read memory at 0x5004 write_enable_reg = 1'b1; write_adr_reg = 5'b00010; write_data_reg = read_data; #100 read_enable = 1'b0; write_enable_reg = 1'b0; #100 read_adr1_reg = 5'b00010; #100 $display("$v0 = %x",read_data1_reg); #2000 $finish; end // initial begin always #50 ck = ~ck; endmodule }}} 実行結果は以下のようになるはずである。 {{{ % ncverilog test_mem.v ....省略 ncsim> run $v0 = 0000000a ....省略 }}} ちなみに、以下の部分でメモリからデータを読み出し、レジスタファイル($v0)に書き込んでいる。 {{{ #190 // read memory at 0x5000 read_enable = 1'b1; address = 32'h00005000; #100 // write data to $v0 write_enable_reg = 1'b1; write_adr_reg = 5'b00010; write_data_reg = read_data; #100 read_enable = 1'b0; write_enable_reg = 1'b0; }}} 実行結果の波形の例: [[Image(http://galaxy.u-aizu.ac.jp/note/raw-attachment/wiki/Ex06%E8%AA%B2%E9%A1%8C2015/comparch_ex6_mem.png)]] このテンプレートを元にして、アドレス0x5000と0x5004にあるデータを演算した結果を、再びメモリに書き込むテストベンチを作成せよ。格納アドレスと演算結果の対応は以下の通りとする。特に、レジスタへの読み書き、演算をして結果をメモリに書き込む部分の波形を確認すること。 || 0x5008 || 加算 || || 0x500c || 減算 || || 0x5010 || AND演算 || || 0x5014 || 3*"0x5000のデータ"+"0x5004のデータ" ||