wiki:Ex06課題2015

Version 9 (modified by nakasato, 11 years ago) (diff)

--

トップ:http://galaxy.u-aizu.ac.jp/note/wiki/CAEX2015

レジスタファイルについて

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に 直接アクセスしています。

課題を行う前の注意

上で説明したレジスタとメモリををそれぞれ"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

実行結果の波形の例:

http://galaxy.u-aizu.ac.jp/note/raw-attachment/wiki/Ex06%E8%AA%B2%E9%A1%8C2015/comparch_ex6_reg.png

課題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;

実行結果の波形の例:

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のデータ"

Attachments (2)

Download all attachments as: .zip