前回、仮にも32ビットのALUを「でっち上げ」たので、次はALUの対面となるレジスタファイルです。「でっち上げる」にせよ32ビット。最低でも2R1W型のマルチポートで32本にするしかないんでないの。泥縄式に作っているので、今回は雰囲気だけ、同時に2個の32ビット値を呼び出せて、1個の32ビット値を書き込めるっと。
※「帰らざるMOS回路」投稿順Index はこちら
レジスタファイル
ALUに引っ付けるレジスタファイルを作るとなると、いやでも前後の「命令」を意識しないとなりませんな。前の命令の演算結果を次の命令で使いたいときにはどうするのとか、コマケー話がいろいろ、ちらちらするんであります。でもま、泥縄式を標榜?しておる本シリーズであります。まあ、コマケー話はそれが出てきたところで「やっつける」ことにして、とりあえずレジスタファイルの原型を書いてしまうことにいたしました。諸元は以下のとおり。
-
- 32ビット幅(ALUと一緒、当然)
- 32本(後でゼロレジスタとか実装するかもだけれど、とりあえず32本)
- 2R1W(同一クロックで、2つのレジスタを読み出し、1つのレジスタに書き込める)
「プレーン」な「バニラ味」とでもいいましょうか。フツーな感じ。
レジスタファイルのVerilog実装
一応、モジュールは2階層にしてみました。
-
- ram2r1w32b、下位の実装
- regfile32、上位へのインタフェース
下位の方は、後で?ホンモノの2R1Wセルとかで実装することを想定しつつ、単なる配列に出口を2つ、入口を1つ取り付けたもの。32本のレジスタなのでレジスタアドレスは5ビットであります。
上位の方は、ALUと接続する際の受け渡しを意識してタイミングをとれるように、といってまだ何も考えてないです。とりあえず形だけ。
下位の方のソースが以下に
/** @file 32bit x 32 word multiport RAM 2 read port, 1 write port */ `ifndef RAM2R1W32B_V_ `define RAM2R1W32B_V_ /** The RAM(register file) module @param[in] ADRA [4:0] read port A address input @param[in] ADRB [4:0] read port B address input @param[in] ADRW [4:0] write port address input @param[in] DATW [31:0] write port data input @param[out] DATA [31:0] read port A data output @param[out] DATB [31:0] read port B data output */ module ram2r1w32b( CLK, ADRA, ADRB, ADRW, DATW, DATA, DATB ); input CLK; input [4:0] ADRA; input [4:0] ADRB; input [4:0] ADRW; input [31:0] DATW; output[31:0] DATA; output[31:0] DATB; reg [31:0] mem [0:31]; always @( posedge CLK) begin mem[ADRW] <= DATW; end assign DATA = mem[ADRA]; assign DATB = mem[ADRB]; endmodule `endif // RAM2R1W32B_V_
上位の方がこちら。
/** @file 32bit x 32 register file */ `ifndef REGFILE32_V_ `define REGFILE32_V_ /** The REGFILE32 module @param[in] CLK clock input @param[in] ADDRA [4:0] read port A address input @param[in] ADDRB [4:0] read port B address input @param[in] ADDRW [4:0] write port address input @param[in] DINW [31:0] write port data input @param[out] DOUTA [31:0] read port A data output @param[out] DOUTB [31:0] read port B data output */ module regfile32( CLK, ADDRA, ADDRB, ADDRW, DINW, DOUTA, DOUTB ); input CLK; input [4:0] ADDRA; input [4:0] ADDRB; input [4:0] ADDRW; input [31:0] DINW; output [31:0] DOUTA; output [31:0] DOUTB; reg [4:0] alat; reg [4:0] blat; reg [4:0] wlat; reg [31:0] dlat; ram2r1w32b regfile(.CLK(CLK), .ADRA(alat), .ADRB(blat), .ADRW(wlat), .DATW(dlat), .DATA(DOUTA), .DATB(DOUTB)); always @( posedge CLK ) begin alat <= ADDRA; blat <= ADDRB; wlat <= ADDRW; dlat <= DINW; end endmodule `endif // REGFILE32_V_
テストベンチ
今回、テストベンチを書いていて、2回もソースを失ってしまい(単なる不注意)心が折れたので、以下は真のやっつけです。とりあえず動いている雰囲気を醸すだけ。
/** @file regfile32 Test bench */ `timescale 1 ns / 1 ps module regfile32_tb; parameter CYCLE = 100; parameter HALF_CYCLE = 50; parameter DELAY1 = 10; reg clk; reg [4:0] aA; reg [4:0] aB; reg [4:0] aW; reg [31:0] dW; wire [31:0] dA; wire [31:0] dB; regfile32 dut( .CLK(clk), .ADDRA(aA), .ADDRB(aB), .ADDRW(aW), .DINW(dW), .DOUTA(dA), .DOUTB(dB) ); initial begin $dumpfile("regfile_tb.vcd"); $dumpvars(-1, dut); $monitor("%d aW=%h aA=%h aB=%h dW=%h dA=%h dB=%h", $stime, aW, aA, aB, dW, dA, dB); end always begin clk = 1'b1; #HALF_CYCLE clk = 1'b0; #HALF_CYCLE; end initial begin #DELAY1 aW = 5'b00000; dW = 32'h5a5a0001; #CYCLE #DELAY1 aW = 5'b00001; aA = 5'b00000; aB = 5'b00001; dW = 32'h5a5a0002; #CYCLE #DELAY1 aW = 5'b00010; aA = 5'b00000; aB = 5'b00001; dW = 32'h5a5a0003; #CYCLE #DELAY1 aW = 5'b00011; aA = 5'b00000; aB = 5'b00001; dW = 32'h5a5a0004; #CYCLE #DELAY1 aW = 5'b00100; aA = 5'b00000; aB = 5'b00001; dW = 32'h5a5a0005; #CYCLE $finish; end endmodule //regfile32_tb
実験結果
新パソコン(Windows11機)にインストールした Icarus Verilog を使ってシミュレーションしてみたところが以下に。
「1個書いて2個読んで」できてるからまあいいか。でもタイミングはバグっぽい。直すのはまた今度だな。