Saturday, February 20, 2021

ALU design in Verilog using MIPS Instruction Set

MIPS is a RISC (Reduced Instruction Set Computer) based architecture which is used in MIPS based processors.  Let us design a simple ALU in Verilog using few example instructions from the MIPS Instruction Set.

For details on MIPS instruction set, I have referred this Programmer's guide: MIPS Programmer's Guide

MIPS Instructions are classified into three types: R, I and J. They have the following specified formats:

R-Type: 

op - Opcode
rs, rt - Source Registers 1 and 2
rd - Destination Register
shamt - Shift Amount
funct - Function Code

I-Type:

J-Type:


Step 1: Decide the various instructions that your ALU must support.

From the MIPS Instruction Set, let us have our ALU to support the following instructions:
AND, OR, ADD, SUB, SLT, NOR, LW, SW, BEQ.

Step 2: ALU Control Signal Generation

For I-Type Instructions, we can find out the type of operation to be performed by looking at the opcode. 
However for R-Type Instructions, the type of operation is determined by the function field (opcode field will remain as all zeroes).

So we need a control signal for the ALU which determines the type of operation to be performed by looking at either the opcode for I-Type or function field for R-Type instructions.

In our case, it is as simple as: if the opcode field is all zeroes, then look at the function field.


In LW and SW, we can perform addition as the offset has to be added with the base address. ALU Control is the signal which is sent to the ALU core to indicate what type of operation is to be performed.

For BEQ, subtraction has to be performed. If the operands are equal, then the subtraction result will be 0 and the branching condition will be true. Our ALU will have a separate zero output signal to indicate output zero condition. 

This ALU can be later added to a processor design.

Verilog Module: ALU_Controller
Inputs: opcode (6 bits), func_field (6 bits)
Outputs: alu_control (3 bits)

Verilog Code for ALU Control:

module Alu_Control(
	opcode,
	func_field,
	alu_control
    );
	 
input [5:0] opcode;
input [5:0] func_field;
output reg [2:0] alu_control;
reg [2:0] func_code;

always @ (*)
begin
	case (func_field)
	6'h20: func_code = 3'h0;
	6'h22: func_code = 3'h1;
	6'h24: func_code = 3'h2;
	6'h25: func_code = 3'h3;
	6'h27: func_code = 3'h4;
	6'h2A: func_code = 3'h5;
	default: func_code = 3'h0;
	endcase

	case (opcode)
	6'h00: alu_control = func_code;
	6'h04: alu_control = 3'h1;
	6'h23: alu_control = 3'h0;
	6'h2B: alu_control = 3'h0;
	default: alu_control = 3'h0;
	endcase
end
endmodule

Step 3: ALU Core Design

Now that the control signal tells us the type of operation to be performed, the desired operation can be performed in the ALU core module. Let the input operands be A and B. The computed output is sent out as result.

Verilog module: ALU_Core
Inputs: alu_control (3 bits), A (32 bits), B (32 bits)
Outputs: result (32 bits), zero (1 bit)

Verilog Code for ALU Core:

module Alu_Core(
	A,
	B,
	alu_control,
	result,
	zero
    );

input [31:0] A;
input [31:0] B;
input [2:0] alu_control;
output reg [31:0] result;
output wire zero;

assign zero = !(|result);

always @ (*)
begin
	case(alu_control)
	3'h0: result = A + B;
	3'h1: result = A - B;
	3'h2: result = A & B;
	3'h3: result = A | B;
	3'h4: result = ~(A | B);
	3'h5: result = (A < B);
	default: result = A + B;
	endcase
end
endmodule

Step 4: Create the ALU top module.

The top module instantiates and connects both the above modules.

Verilog Module: ALU_Top
Inputs: opcode (6 bits), func_field (6 bits), A (32 bits), B (32 bits)
Outputs:  result (32 bits), zero (1 bit)

Verilog Code for ALU Top:

module Alu_Top(
	opcode,
	func_field,
	A,
	B,
	result,
	zero
    );

input [5:0] opcode;
input [5:0] func_field;
input [31:0] A;
input [31:0] B;
output [31:0] result;
output zero;
wire [2:0] alu_control;

Alu_Control alu_ctrlr_inst (
.opcode (opcode),
.func_field (func_field),
.alu_control (alu_control)
);

Alu_Core alu_core_inst (
.A (A),
.B (B),
.alu_control (alu_control),
.result (result),
.zero (zero)
);

endmodule

The above verilog code implements an ALU using certain instructions from the MIPS Instruction set. 



Testbench:

module Alu_Top_tb;

// Inputs
reg [5:0] opcode;
reg [5:0] func_field;
reg [31:0] A;
reg [31:0] B;

// Outputs
wire [31:0] result;
wire zero;

// Instantiate the Unit Under Test (UUT)
Alu_Top uut (
	.opcode(opcode), 
	.func_field(func_field), 
	.A(A), 
	.B(B), 
	.result(result),
	.zero(zero)
);

initial begin
	// Initialize Inputs
	opcode = 0;
	func_field = 0;
	A = 0;
	B = 0;
	
	#30;
	A=32'h2222; B=32'h1111;
	opcode=6'h00;func_field=6'h20; 
	#30;
	opcode=6'h00;func_field=6'h24;
	#30;
	opcode=6'h23;func_field=6'h00;
	#30;
	A=31'h5555; B=32'h5555;
	opcode=6'h04;func_field=6'h00;
	#30;
	A=32'h1111; B=32'h2222;
	opcode=6'h00;func_field=6'h2A;
	#30;
	$finish;
end
      
endmodule

Simulation Result:


As can be seen from simulation, 
Different values of opcode, function field, operands A and B are provided (as per our supported MIPS instructions) and the required results are obtained in the result field along with zero condition.

This ALU can be implemented in a simple MIPS instruction set based processor as done here:

No comments:

Post a Comment