Creating a Register File using Behavioral Verilog

Behavioral Verilog is the most abstract style of Verilog code. The code looks very similar to C, but one has to remember that in hardware operations occur in parallel, and everything is running at once.

This sample register file will be a 16 entries by 8-bits per entry. It will have 2 synchronous write ports, and an asynchronous read port as well as an active low asynchronous reset.

We begin with our module header, parameters, and i/o declarations.

module register_file(data_out, clk, wr_en_1, wr_en_2, rst, data_in_1, data_in_2,	wr_addr_1, wr_addr_2, rd_addr);

	parameter ENTRIES = 16;
	parameter REG_OFFSET = 8;
	integer i;

	input clk, wr_en_1, wr_en_2, rst;
	input [7:0] data_in_1, data_in_2;
	input [2:0] wr_addr_1, wr_addr_2;
	input [3:0] rd_addr;
	output reg [7:0] data_out;

	reg [7:0] registers [ENTRIES - 1:0];

By using parameters we can easily create register files of different sizes by passing new parameter values during instantiation. This can be done as such:

register_file #(16, 32) reg_file(data_out, clk, wr_en_1, wr_en_2, rst, data_in_1, data_in_2,	wr_addr_1, wr_addr_2, rd_addr);

This will instantiate the same module with 16 32-bit entries.

Inside our module in an always block we declare that we want it to execute only in case of a clock’s positive edge, or a reset’s negative edge. Since the rest is active low, it will change states from 1->0 (negative edge).

always @(posedge clk, negedge rst) begin

We then take care of our two write ports and reset. If reset is equal to 0, then we need to reset all of the values in the register file. Otherwise if wr_en_1 is 1, we want to write to write port 1, else we want to write to write port 2.

always @(posedge clk, negedge rst) begin
		if(rst == 1'b0) begin
			for(i = 0; i < ENTRIES; i = i + 1) begin
				registers[i] <= 7'b0;
			end
		end

		else if(wr_en_1)
			registers[wr_addr_1] <= data_in_1;
		else
			registers[REG_OFFSET + wr_addr_2] <= data_in_2;
end

That take care of the two synchronous write ports. Since hardware operations occur in parallel, we can have multiple always blocs which will all execute at the same time. For the write port we simply want the always block to be triggered every time the read address (read_addr) changes.

always @(rd_addr) begin
		data_out = registers[rd_addr];
end

And this is the complete code for the register file:
register_file.v:

module register_file(data_out, clk, wr_en_1, wr_en_2, rst, data_in_1, data_in_2,	wr_addr_1, wr_addr_2, rd_addr);

	parameter ENTRIES = 16;
	parameter REG_OFFSET = 8;
	integer i;

	input clk, wr_en_1, wr_en_2, rst;
	input [7:0] data_in_1, data_in_2;
	input [2:0] wr_addr_1, wr_addr_2;
	input [3:0] rd_addr;
	output reg [7:0] data_out;

	reg [7:0] registers [ENTRIES - 1:0];

	always @(posedge clk, negedge rst) begin
		if(rst == 1'b0) begin
			for(i = 0; i < ENTRIES; i = i + 1) begin
				registers[i] <= 7'b0;
			end
		end

		else if(wr_en_1)
			registers[wr_addr_1] <= data_in_1;
		else
			registers[REG_OFFSET + wr_addr_2] <= data_in_2;
	end

	always @(rd_addr) begin
		data_out = registers[rd_addr];
	end

endmodule

Leave a Reply