Sunday, February 28, 2021

MIPS Processor Design Using Verilog: Part 2

In Part 1 of the Processor Design series, we saw the descriptions of the individual datapath components of a typical Processor.

In the current Part 2 of the series, let us put them together, generate the Control Unit and understand how the overall processor works.

Overall Schematic

Here you can find the overall schematic of the complete datapath. All datapath components have been connected accordingly. Control signals have not been included here.

Image Source: Computer Organization and Design by David A. Patterson and John L. Henessy

Working of the Processor

Starting with the Program Counter

1. At the first clock cycle, the Program Counter will be pointing to the address of the first instruction to be fetched. 

2. This address goes to the Instruction Memory where the instruction will be fetched.

3. The instruction will consist of different fields depending on the MIPS instruction format as shown below:

Instruction [31:26] ⟶ opcode (To decide the type of instruction)

Instruction [25:21] rs (address of register 1 to be read)

Instruction [20:16] rt (address of register 2 to be read for R-Type instrn) or
                                         (address of register to be written with result for I-Type instrn)

Instruction [15:0] ⟶ const (offset value for I-Type Instruction)

For R-Type, Instruction [15:0] can be further split as:

Instruction [15:11] ⟶ rd (address of register to be written with result)

Instruction [10:6] ⟶ shamt (shift amount, not required for our case)

Instruction [5:0] ⟶ funct (function code)

4. All register operations are handled by the Register file. Hence, appropriate connections are made to it. Register file consists of the Register memory which addresses the inbuilt MIPS registers as we saw in the previous post. 

5. The output data from Register file will go to any of the different modules depending on the type of instruction and is controlled by the control unit which we will discuss at the end.

For R-Type Instructions, the output from Register file goes to the ALU for performing the required computation and is written back to the Register file.

For LW and SW, it goes to the Sign Extension unit to extend the offset to 32-bits and then access the Data memory.

For BEQ, it goes to the shifter module where data is shifted left by 2 bits to convert the offset value into a processor address (aligned to 4-bytes).

Top Module

Let us look at the top module of the processor in Verilog. Here, we can see all the instances of each module and how they are connected as per the schematic.

Verilog Code:

module Processor_Top(
	clk,
	rst_n
    );

input clk;
input rst_n;

wire [31:0] ctrl_in_address;
wire [31:0] out_address;
wire [31:0] addr_incr;
wire [31:0] address_plus_4;
wire [31:0] branch_addr_offset;
wire [31:0] branch_address;
wire [31:0] instrn;
wire ctrl_write_en;
wire final_write_en;
wire [4:0] ctrl_write_addr;
wire [31:0] ctrl_regwrite_data;
wire [31:0] read_data1;
wire [31:0] read_data2;
wire [31:0] sign_ext_out;
wire [31:0] ctrl_aluin2;
wire [31:0] alu_result;
wire zero_out;
wire [31:0] datamem_read_data;

assign addr_incr = (!rst_n) ? 32'd0 : 32'd4;
assign final_write_en = (!rst_n) ? 1'b0 : ctrl_write_en;

Program_Counter prg_cntr (
.clk (clk),
.rst_n (rst_n),
.in_address (ctrl_in_address),
.out_address (out_address)	
);

Adder adder_next_addr (
.in1 (out_address),
.in2 (addr_incr),
.out (address_plus_4)
);

Adder adder_branch_addr (
.in1 (address_plus_4),
.in2 (branch_addr_offset),
.out (branch_address)
);

Instruction_Memory instr_mem (
.instrn_address (out_address),
.instrn (instrn[31:0])
);

Register_File regfile (
.clk (clk),
.rst_n (rst_n),
.read_addr1 (instrn[25:21]),
.read_addr2 (instrn[20:16]), 
.write_en   (final_write_en),
.write_addr (ctrl_write_addr),
.write_data (ctrl_regwrite_data),
.read_data1 (read_data1),
.read_data2 (read_data2)
);

Sign_Extension sign_ext (
.bits16_in (instrn[15:0]),
.bits32_out (sign_ext_out)
);

Shifter shifter (
.indata (sign_ext_out),
.shift_amt (2'd2),
.shift_left (1'b1),
.outdata (branch_addr_offset)
);

Alu_Top alu (
.opcode (instrn[31:26]),
.func_field (instrn[5:0]),
.A (read_data1),
.B (ctrl_aluin2),
.result (alu_result),
.zero (zero_out)
);

Data_Memory data_mem (
.clk		(clk),
.address (alu_result),
.write_en (ctrl_datamem_write_en),
.write_data (read_data2),
.read_data (datamem_read_data)
);

Control_Logic ctrl_logic (
.instrn			  (instrn),
.instrn_opcode   (instrn[31:26]),
.address_plus_4  (address_plus_4),
.branch_address  (branch_address),
.ctrl_in_address (ctrl_in_address),
.alu_result      (alu_result),
.zero_out        (zero_out),
.ctrl_write_en   (ctrl_write_en),
.ctrl_write_addr (ctrl_write_addr),
.read_data2      (read_data2),
.sign_ext_out    (sign_ext_out),
.ctrl_aluin2     (ctrl_aluin2),
.ctrl_datamem_write_en (ctrl_datamem_write_en),
.datamem_read_data (datamem_read_data),
.ctrl_regwrite_data (ctrl_regwrite_data)
);

endmodule

Let us discuss the control logic part of the code.

The Control Unit

  • As the name suggests, the control unit is that part of the processor which generates control signals in order to operate in the desired way depending on the instruction.
  • For example: The next instruction address to be executed is PC+4 for normal cases but is calculated differently if it is a BEQ instruction.
  • Let us now look at the Verilog code for the control logic, to see how much of control we have applied in the present design.
Verilog Code:

module Control_Logic(
        instrn,
	instrn_opcode,
	address_plus_4,
	branch_address,
	ctrl_in_address,
	alu_result,
	zero_out,
	ctrl_write_en,
	ctrl_write_addr,
	read_data2,
	sign_ext_out,
	ctrl_aluin2,
	ctrl_datamem_write_en,
	datamem_read_data,
	ctrl_regwrite_data
    );

input [31:0] instrn;	 
input [5:0] instrn_opcode;
input [31:0] address_plus_4;
input [31:0] branch_address;
input [31:0] datamem_read_data;
input [31:0] alu_result;
input zero_out;
input [31:0] read_data2;
input [31:0] sign_ext_out;

output wire [31:0] ctrl_in_address;
output wire ctrl_write_en;
output wire [4:0] ctrl_write_addr;
output wire [31:0] ctrl_aluin2;
output wire ctrl_datamem_write_en;
output wire [31:0] ctrl_regwrite_data;

//Select either branch address (for BEQ) or address+4 (for other cases)
assign ctrl_in_address = (instrn_opcode==6'h04 && zero_out) ? branch_address : address_plus_4;

//Write enable for regfile only for R-Type and LW instructions
assign ctrl_write_en = (instrn_opcode==6'h00) || (instrn_opcode==6'h23);

//Write address for regfile selection based on R-Type and I-Type instructions
assign ctrl_write_addr = (instrn_opcode==6'h00) ? instrn[15:11] : instrn[20:16];

//Write data for regfile is from Data mem (LW) or from ALU
assign ctrl_regwrite_data = (instrn_opcode==6'h23) ? datamem_read_data : alu_result;

//Second input of ALU is either from regfile (R_Type) or sign ext unit (LW & SW)
assign ctrl_aluin2 = (instrn_opcode==6'h23 || instrn_opcode==6'h2B) ? sign_ext_out : read_data2;

//Write enable to datamem only for LW instruction
assign ctrl_datamem_write_en = (instrn_opcode==6'h2B); 

endmodule

With this, we have completed the design of a simple MIPS processor in Verilog.
Let us now simulate the processor and check the results in Part 3 of the series.

No comments:

Post a Comment