An open API service indexing awesome lists of open source software.

https://github.com/conneroisu/single-cycle-mips-verilog

A single cycle mips processor implementation in verilog.
https://github.com/conneroisu/single-cycle-mips-verilog

Last synced: 10 months ago
JSON representation

A single cycle mips processor implementation in verilog.

Awesome Lists containing this project

README

          

# singe-cycle-mips-verilog
A single cycle mips processor implementation in verilog with reflections from writing a similar single-cycle
processor project in vhdl that can execute a subset of the MIPS
instruction set and displays the current instruction on the seven segment displays present
on the FPGA board.
The processor is implemented in Verilog and tested using a
test-bench to verify the functionality of the processor. Additionally, the processor
is tested on an FPGA board to verify the functionality of the processor.

Table of Contents


The processor is able to execute the
following instructions: LW SW
J ADD ADDI
BEQ ADDU SUBU
AND ANDI OR
ORI SUB NOR
BNE SLT



The current instruction being executed will be displayed
on the seven segment displays present on the FPGA board. The processor
will be implemented in Verilog and tested using a test-bench.


After the processor has been verified using the test-bench, the
processor will be tested on an FPGA board to verify the functionality of
the processor.


The comparison and contrast of the experience writing the same
processor in both Verilog and VHDL will be also included in the final
report.


Introduction


The project is a single-cycle MIPS processor that can at a variable
speed execute a subset of the MIPS instruction set displaying the
current instruction on the seven segment displays present on
EP4CE115F29C7 FPGA board.


Circuit Diagram.png
Circuit Diagram.png


As a result of this desired function (and it’s actualation into
reality), the processor can technically be used to do
all the other projects from other students in the
class. As I took this class whilst also taking CPRE381, I additionally
decided to compare and contrast the experience writing the same
processor in both Verilog and VHDL.


While the overall state machine will be broken down below, the main
processor state machine has five states:


Fetch: In this state, the processor fetches the next
instruction from memory. Decode: In this state, the
processor decodes the instruction to determine what operation to
perform. Execute: In this state, the processor executes
the instruction. Memory: In this state, the processor
accesses memory to read or write data. Write-back: In
this state, the processor writes the results of the instruction to a
register.


Supported Instructions: LW SW J ADD ADDI BEQ ADDU SUBU AND ANDI OR
ORI SUB NOR BNE


State Machines

![Assembly-Instructions-Architecture.png](assets/Assembly-Instructions-Architecture.png)

Each instruction type (R-type, I-type, J-type) generally follows a
similar flow with variations primarily in the Execute and Memory Access
stages depending on whether the instruction involves arithmetic, memory
access, or control flow.


This model provides a consistent framework for understanding how

different instructions are processed in the single-cycle MIPS
architecture.

![Single-Cycle-Staging.png](assets/Single-Cycle-Staging.png)

ADD (R-type instruction)


Name (format, op, function): add (R,0,32)


Syntax: add rd,rs,rt


Operation: reg(rd) := reg(rs) + reg(rt);


Instruction Overview:


The following is an overview of the operation of the add
instruction in a MIPS processor, focusing on the key stages of the
processor pipeline.




  • IF: The instruction is fetched from memory using
    the program counter (PC).


  • ID: The instruction bits are decoded to determine
    it is an ADD operation. Registers specified by the source register
    fields (rs and rt) are read.


  • EX: The ALU performs the addition of the two
    register values.


  • MEM: No action (not used by ADD).


  • WB: The result from the ALU is written back to the
    destination register (rd).


Operation Breakdown:


The following provides a detailed breakdown of the operation of the
add instruction in a MIPS processor, focusing on the key
stages of the processor pipeline.


Stages of the add
Instruction:



  1. Instruction Fetch (IF):

    • The instruction is fetched from memory using the Program Counter
      (PC).



    • This stage corresponds to reading the instruction code from the
      instruction memory. The address comes from the PC which points to the
      location of the next instruction to execute.


      i_Instruction = Imem[i_Addr>>2];





  2. Instruction Decode (ID):

    • The fetched instruction is decoded to determine it is a
      add operation.



    • The opcode part of the instruction (which is 000000
      for R-type instructions) is identified, and the source register
      identifiers (rs and rt) are used to read the
      respective registers.


      rs = i_instruction[25:21];
      
      rt = i_instruction[20:16];





  3. Execute (EX):

    • The ALU (Arithmetic Logic Unit) performs the addition of the
      values in the source registers (rs and
      rt).



    • The values from these registers are fed into the ALU where the
      addition is performed based on the control signal (ALUOp)
      from the control unit.


      o_ALUresult = i_data1 + i_data2;





  4. Memory Access (MEM):


    • For the add instruction, this stage is not utilized
      as no memory access is required (i.e., no data is read from or written
      to the memory).


      // No memory operation required for 'add'





  5. Write Back (WB):

    • The result from the ALU is written back into the destination
      register (rd).



    • This is where the output of the ALU operation is stored back into
      the register file, specifically into the register indicated by the
      rd field of the instruction.


      Reg[rd] = o_ALUresult;





Example Code Snippet:

Here is a simplified Verilog snippet that captures the essence of the
add instruction’s operation in a MIPS processor, focusing
on the key stages of the mips architecture (IF, ID, EX, MEM, WB).


module MIPS_Processor(input clk, input reset, ...);

// Registers and other declarations here
reg [31:0] PC, ALUResult, Reg[31:0];
reg [31:0] InstructionRegister, ReadData1, ReadData2;
integer rd, rs, rt;
always @(posedge clk) begin
if (reset) begin
PC <= 0; // Reset PC
end else begin
// Fetch Instruction
InstructionRegister <= Imem[PC>>2];
PC <= PC + 4;
// Decode Instruction
rs = InstructionRegister[25:21]; // Extract source register indices
rt = InstructionRegister[20:16]; // Extract target register indices
rd = InstructionRegister[15:11]; // Extract destination register index
ReadData1 <= Reg[rs];
ReadData2 <= Reg[rt];
// Execute
ALUResult <= ReadData1 + ReadData2;
// Write Back
Reg[rd] <= ALUResult;
end
end
endmodule

The add instruction demonstrates the typical use of the
R-type format in MIPS instruction set architecture, involving fetching
the instruction, decoding it, executing the operation in the ALU,
skipping memory access, and finally writing back the result to the
register file.


ADDI (I-type instruction)


Name (format, op, function): add immediate (I,8,na)


Syntax: addi rt,rs,imm


Operation: reg(rt) := reg(rs) + signext(imm);


Instruction Overview:




  • IF: Fetch the instruction from memory.


  • ID: Decode the instruction; read the source
    register (rs).


  • EX: ALU adds the value in the source register to
    the immediate value (which is sign-extended).


  • MEM: No action.


  • WB: The result is written back to the target
    register (rt).


Operation Breakdown:


The following further breaks down the operation of the
ADDI instruction in a MIPS processor across the various
stages of the processor pipeline.


Stages of the ADDI
Instruction



  1. Instruction Fetch (IF):

    • The processor retrieves the ADDI instruction from
      memory based on the current Program Counter (PC) value.

    • The instruction is then forwarded to the next stage for
      decoding.




  2. Instruction Decode (ID):

    • The instruction is decoded to identify that it is a
      ADDI operation.

    • The source register (rs) is read to obtain its value.
      The immediate value (imm) is also extracted from the
      instruction during this phase.




  3. Execute (EX):

    • The Arithmetic Logic Unit (ALU) performs the addition operation. It
      adds the value retrieved from the source register (reg(rs))
      to the sign-extended immediate value (signext(imm)).

    • This computation involves extending the immediate value to match the
      register size (typically 32 bits in MIPS), preserving its sign to handle
      negative numbers correctly.




  4. Memory Access (MEM):

    • The ADDI instruction does not involve any memory
      access, so this stage is effectively a no-op (no operation) for this
      instruction.




  5. Write Back (WB):

    • The result of the addition from the ALU is written back to the
      destination register (reg(rt)).

    • This step updates the target register with the computed value,
      completing the execution of the instruction.




Explanation of the Code
Implementation

The operation of ADDI can be modeled in a simulated or
actual MIPS processor using the following Verilog-like pseudocode:


module addi_instruction(rs, rt, imm, output rt_value);

input [4:0] rs, rt; // Source and target register indices (5 bits each)
input [15:0] imm; // 16-bit immediate value
output [31:0] rt_value; // Output to target register
wire [31:0] rs_value; // Value from source register
wire [31:0] extended_imm; // Sign-extended immediate value
assign extended_imm = {
{16{imm[15]}}, imm // Sign-extend the immediate value
};
assign rt_value = rs_value + extended_imm;
endmodule



  • rs and rt are inputs representing the
    source and destination register indices.


  • imm is the 16-bit immediate value input.

  • The immediate value is sign-extended to 32 bits using Verilog’s bit
    replication and concatenation ({{16{imm[15]}}, imm}), where
    imm[15] is the most significant bit (MSB) of the immediate
    value, replicated 16 times to fill the upper half of a 32-bit word.

  • The sum of the sign-extended immediate and the source register value
    is computed and assigned to rt_value, which would be
    written back to the register file in the actual processor hardware.


LW (Load Word)


Name (format, op, function): load word (I,35,na)


Syntax: lw rt,imm(rs)


Operation: reg(rt) := mem[reg(rs) + signext(imm)];


Instruction Overview:




  • IF: Fetch the instruction.


  • ID: Decode the instruction; read the base address
    register (rs).


  • EX: Calculate the memory address by adding the
    immediate value (offset) to the base register.


  • MEM: Access the memory at the computed address and
    read the word.


  • WB: Write the loaded word into the target register
    (rt).


Operation Breakdown:


The following provides a detailed breakdown of the operation of the
lw instruction in a MIPS processor, focusing on the key
stages of the processor pipeline.


Breakdown of
lw Instruction Execution



  1. Instruction Fetch (IF):

    • The processor fetches the lw instruction from
      instruction memory using the program counter (PC).

    • Code snippet showing fetching the instruction from memory:


      i_Instruction = Imem[i_Addr>>2];



  2. Instruction Decode (ID):

    • The fetched instruction is decoded to extract the opcode, source
      register (rs), target register (rt), and the
      immediate value.

    • The base address (content of rs) is read from the
      register file during this phase.

    • Code snippet showing the decoding and reading of the base
      address:


      read_data1 = RegData[i_rs];  // Assume i_rs is the source register index



  3. Execute (EX):

    • The effective memory address is calculated by adding the
      sign-extended immediate value to the base address read from
      rs.

    • This calculation typically happens in the ALU.

    • Code snippet that could represent the address calculation in ALU
      (not specifically shown in your snippets):


      address = read_data1 + sign_extend(imm);  // Conceptual code



  4. Memory Access (MEM):

    • The processor accesses the memory location computed in the Execute
      stage.

    • The word at this memory address is read.

    • Code snippet showing memory access to read data:


      if (i_MemRead == 1) {
    
    o_rData = Dmem[i_addr]; // Read memory at calculated address
    }



  5. Write Back (WB):

    • The data retrieved from memory is written into the target register
      (rt).

    • Code snippet showing the write-back to the register:


      RegData[i_rt] <= i_wData;  // Assume i_rt is the target register index and i_wData is data read from memory



SW (Store Word)


Name (format, op, function): store word (I,43,na)


Syntax: sw rt,imm(rs)


Operation: mem[reg(rs) + signext(imm)] := reg(rt);


Instruction Overview:




  • IF: Fetch the instruction.


  • ID: Decode the instruction; read the base address
    register (rs) and the register to be stored
    (rt).


  • EX: Calculate the memory address by adding the
    immediate value (offset) to the base register.


  • MEM: Write the value from rt into the
    calculated memory address.


  • WB: No write-back step for store instructions.


Operation Breakdown:


The SW instruction in the MIPS architecture is used to
store a 32-bit word from a register into memory. Here’s an in-depth
breakdown of how the SW instruction is executed across the
various stages in a MIPS processor.


Instruction Stages:




  1. IF (Instruction Fetch):

    • The instruction is fetched from the instruction memory using the
      current Program Counter (PC).




  2. ID (Instruction Decode):

    • The instruction is decoded to identify it as a SW
      instruction.

    • The base address register (rs) and the register
      containing data to be stored (rt) are identified and
      read.




  3. EX (Execute):

    • The effective memory address is calculated by adding the
      sign-extended immediate (offset) to the value in the base register
      (rs).




  4. MEM (Memory Access):

    • The data in register rt is written to the calculated
      memory address.




  5. WB (Write Back):

    • No write-back is performed for the SW instruction, as
      this instruction does not modify any register contents.




Verilog Implementation:

Data Memory Module (DataMemory.v):


Below is a simplified Verilog module for a data memory component that
can be used to store and retrieve data in a MIPS processor. This module
includes logic for both read and write operations.


module DataMemory (

input clk,
input memWrite,
input [31:0] address,
input [31:0] writeData,
output reg [31:0] readData
);
reg [31:0] memory [0:1023];
always @(posedge clk) begin
if (memWrite) begin
memory[address >> 2] <= writeData; // Write operation
end else begin
readData <= memory[address >> 2]; // Read operation
end
end
endmodule

Processor Control Logic
(ProcessorControl.v):


Below is an extract from the ProcessorControl module that controls
the behavior of the processor based on the opcode of the instruction
being executed. This snippet shows how the control signals are set for
the SW instruction.


module ProcessorControl (

input [5:0] opcode,
output reg memWrite,
output reg aluSrc,
output reg regDst,
output reg memToReg,
output reg regWrite
);
always @(*) begin
case (opcode)
6'b101011: begin // Opcode for SW
memWrite = 1'b1;
aluSrc = 1'b1;
regDst = 1'b0;
memToReg = 1'b0;
regWrite = 1'b0;
end
default: begin
memWrite = 1'b0;
aluSrc = 1'b0;
regDst = 1'b0;
memToReg = 1'b0;
regWrite = 1'b0;
end
endcase
end
endmodule

Simplified Top-Level MIPS Module:


module MIPSProcessor (

input clk,
input reset,
output [31:0] pc,
input [31:0] instruction,
output [31:0] aluResult,
output [31:0] writeData,
output [31:0] readData
);
wire [5:0] opcode = instruction[31:26];
wire [4:0] rs = instruction[25:21];
wire [4:0] rt = instruction[20:16];
wire [15:0] imm = instruction[15:0];
wire [31:0] signExtImm = {{16{imm[15]}}, imm};
wire [31:0] regDataRs, regDataRt;
wire memWrite, aluSrc, regDst, memToReg, regWrite;
// Instantiate control logic
ProcessorControl control(opcode, memWrite, aluSrc, regDst, memToReg, regWrite);
// ALU operation (assuming already instantiated and connected)
// Data memory operation
DataMemory dataMem(clk, memWrite, aluResult, regDataRt, readData);
// Register file operations and other connections would be defined here
endmodule

BEQ (Branch if Equal)


Name (format, op, function):
branch on equal (I,4,na)


Syntax: beq rs,rt,label


Operation: if reg(rs) == reg(rt) then PC = BTA else NOP;


Instruction Overview:




  • IF: Fetch the instruction.


  • ID: Decode the instruction; read the two registers
    (rs and rt) and compare them.


  • EX: Calculate the branch target address if the
    comparison is equal (by adding the sign-extended, shifted immediate to
    the PC).


  • MEM: No memory access.


  • WB: No write-back; update the PC to the branch
    address if the condition is met, otherwise increment the PC as
    usual.


Operation Breakdown:


The following provides a detailed breakdown of the operation of the
BEQ instruction in a MIPS processor, focusing on the key
stages of the processor pipeline.


The BEQ (Branch if Equal) instruction in the MIPS
architecture follows a specific flow through the processor stages.
Here’s a step-by-step walkthrough of each stage using Verilog code
examples to illustrate how each part of the instruction’s lifecycle is
handled in hardware.


Instruction Stages for
BEQ



  1. IF (Instruction Fetch) Stage:

    • The instruction is fetched from the instruction memory using the
      current Program Counter (PC).

    • The PC is incremented to point to the next instruction (PC = PC +
      4).




 module InstructionFetch(

input clk,
input reset,
input [31:0] next_pc,
output reg [31:0] instr,
output reg [31:0] pc
);
always @(posedge clk or posedge reset) begin
if (reset) begin
pc <= 32'h00000000; // Reset PC to start
end else begin
pc <= next_pc; // Update PC to next PC
instr <= instruction_memory[pc >> 2]; // Fetch instruction from memory
end
end
endmodule



  1. ID (Instruction Decode) Stage:

    • Decode the fetched instruction to identify it as
      BEQ.

    • Read the two source registers (rs and rt)
      based on the instruction fields.

    • Set up the control signals for the ALU to perform a subtraction
      (rs - rt).




 module InstructionDecode(

input [31:0] instr,
output reg [4:0] rs,
output reg [4:0] rt,
output reg [15:0] immediate
);
always @(*) begin
rs = instr[25:21];
rt = instr[20:16];
immediate = instr[15:0]; // For branch offset
end
endmodule



  1. EX (Execute) Stage:

    • Compute the target address for branching by sign-extending the
      immediate field and shifting left by 2 bits (since it’s word-aligned),
      then adding this to the PC + 4 (already incremented PC from IF
      stage).

    • ALU checks if rs and rt are equal by
      subtracting and checking if the result is zero.




 module ALU(

input [31:0] rs_val,
input [31:0] rt_val,
input [31:0] sign_ext_imm,
input [2:0] alu_control,
output reg zero,
output reg [31:0] alu_result
);
wire [31:0] branch_target = (sign_ext_imm << 2) + pc_plus_4;
always @(*) begin
case(alu_control)
3'b010: begin // Subtract for BEQ
alu_result = rs_val - rt_val;
zero = (alu_result == 0) ? 1'b1 : 1'b0;
end
endcase
end
endmodule



  1. MEM (Memory Access) Stage:

    • For BEQ, there is no memory access or data memory
      operation.




  2. WB (Write-Back) Stage:

    • Update the PC to the branch target if rs ==
      rt (if zero flag from ALU is true).

    • If rs != rt, increment the PC to the next
      instruction (already done in IF).




 always @(posedge clk) begin

if (branch_taken) begin
pc <= branch_target; // Update PC if branch is taken
end
endmodule

Example Verilog for
Complete BEQ Control

Here’s how a simpler control unit might orchestrate these stages just
for the BEQ instruction in a MIPS processor:


module ControlUnit(

input [5:0] opcode,
output reg branch,
output reg alu_src,
output reg [2:0] alu_control
);
always @(*) begin
case(opcode)
6'b000100: begin // BEQ
branch = 1'b1;
alu_src = 1'b0; // Use rs, rt directly
alu_control = 3'b010; // Set ALU to subtract
end
default: begin
branch = 1'b0;
alu_src = 1'b0;
alu_control = 3'b000;
end
endcase
end
endmodule

J (Jump)


Name (format, op, function): jump (J,2,na)


Syntax: j


Operation: PC := JTA;


Instruction Overview:




  • IF: Fetch the instruction.


  • ID: Decode the instruction.


  • EX: Calculate the jump target address from the
    address field of the instruction.


  • MEM: No memory access.


  • WB: Update the PC to the jump address.


Operation Breakdown:


The following provides a detailed breakdown of the operation of the
J instruction in a MIPS processor, focusing on the key
stages of the processor pipeline. The J instruction in MIPS is a jump
instruction that allows the program to continue execution from a
specified address. It is used to alter the flow of control
unconditionally.


Instruction Format and
Operation:



  • Name (format, op, function):
    jump (J,2,na)


  • Syntax: j target


  • Operation: PC := JTA; where JTA (Jump
    Target Address) is calculated from the instruction itself.


Stages of J Instruction
Execution

Here’s a breakdown of how the J instruction progresses through each
stage of the MIPS pipeline:




  1. IF (Instruction Fetch):

    • The instruction is fetched from the instruction memory at the
      current program counter (PC) address.

    • The PC is then incremented by 4 to point to the next sequential
      instruction (though this increment will be overridden by the jump).




  2. ID (Instruction Decode):

    • The opcode of the instruction is decoded to identify it as a jump
      instruction.

    • No registers are read in this stage because the jump instruction
      does not involve any registers.




  3. EX (Execute):

    • The jump target address (JTA) is calculated from the address field
      of the instruction.

    • JTA is formed by taking the upper 4 bits of the PC (from the
      incremented value that points to the next instruction) and concatenating
      them with the 26-bit address field from the instruction, shifted left by
      2 bits (to word-align the address).




  4. MEM (Memory Access):

    • There is no memory access for the jump instruction.




  5. WB (Write Back):

    • The PC is updated to the new address calculated in the Execute
      stage. This is the jump target address where the program will continue
      executing.




Verilog Module for Program Counter with just Jump:


module ProgramCounter(

input clk,
input reset,
input [31:0] jump_address, // Jump target address input
input jump, // Control signal to indicate a jump
output reg [31:0] pc // Program counter output
);
always @(posedge clk or posedge reset) begin
if (reset) begin
pc <= 32'b0; // Reset the PC to 0 on reset
end else if (jump) begin
pc <= jump_address; // Update PC to the jump address if jump is asserted
end else begin
pc <= pc + 4; // Increment PC by 4 on each clock cycle otherwise
end
end
endmodule

Verilog Module for
Jump Address Calculation:

While in this project, this function is done by
NextProgramCounter module, here is a simplified version of
a module that calculates the jump address in a MIPS processor to further
illustrate the concept of jump address calculation:


module JumpAddressCalculator(

input [25:0] address_field, // Address field from the jump instruction
input [31:0] pc_plus_4, // PC + 4 (the incremented PC pointing to the next instruction)
output [31:0] jump_address // Calculated jump target address
);
assign jump_address = {pc_plus_4[31:28], address_field << 2};
endmodule


  • The ProgramCounter module handles updating the PC based
    on whether a jump is taken. If a jump is taken, it sets the PC to the
    jump address; otherwise, it simply increments the PC.

  • The JumpAddressCalculator module calculates the full
    32-bit jump address by concatenating the upper 4 bits of the incremented
    PC (PC+4) with the left-shifted 26-bit address from the jump
    instruction.


These modules collectively illustrate how the J instruction’s effect
on the program counter can be implemented in hardware using Verilog.


ADDU (Add Unsigned)


name (format, op, function): add unsigned (R,0,33)


Syntax: addu rd,rs,rt


Operation: reg(rd) := reg(rs) + reg(rt);


Instruction Overview:




  • IF (Instruction Fetch): The instruction is fetched
    from memory using the program counter (PC).


  • ID (Instruction Decode): The opcode is decoded;
    registers rs and rt are read.


  • EX (Execute): The arithmetic logic unit (ALU) adds
    the values from registers rs and rt.


  • MEM (Memory Access): No action needed
    (pass-through).


  • WB (Write Back): The result from the ALU is written
    back to the destination register rd.


Operation Breakdown:


The following provides a detailed breakdown of the operation of the
ADDU instruction in a MIPS processor, focusing on the key
stages of the processor pipeline.


The ADDU instruction in MIPS is an unsigned addition
operation that does not raise exceptions on overflow. Here’s a detailed
breakdown of how the ADDU instruction is executed across
the various stages of the processor pipeline.


IF (Instruction Fetch)

In this stage, the instruction is fetched from the instruction memory
based on the current value of the Program Counter (PC). Here’s how you
might see this operation in Verilog:


// Instruction Fetch module

module InstructionFetch(
input [31:0] i_PC, // Program Counter
output [31:0] o_Instruction // Fetched instruction
);
reg [31:0] instruction_memory[255:0]; // Memory array

// Fetch the instruction
assign o_Instruction = instruction_memory[i_PC >> 2]; // Word aligned access
endmodule

ID (Instruction Decode)

During this stage, the opcode of the fetched instruction is decoded,
and the register file is accessed to read the contents of registers
rs and rt.


// Instruction Decode module

module InstructionDecode(
input [31:0] i_Instruction, // Input from IF stage
output [4:0] o_rs, o_rt, o_rd // Register specifiers
);
// Decode the instruction
assign o_rs = i_Instruction[25:21];
assign o_rt = i_Instruction[20:16];
assign o_rd = i_Instruction[15:11];
endmodule

EX (Execute)

The ALU adds the values from registers rs and
rt. Here’s a snippet of the ALU performing this
addition:


// Arithmetic Logic Unit (ALU) module

module ALU(
input [31:0] i_data1, i_data2, // Data from registers rs and rt
input [3:0] i_ALUControl, // Control signals
output reg [31:0] o_result // Result of the ALU operation
);
always @(i_data1, i_data2, i_ALUControl) begin
case (i_ALUControl)
4'b0010: o_result = i_data1 + i_data2; // ADDU operation
// Other ALU operations...
endcase
end
endmodule

MEM (Memory Access)


This stage is a pass-through for the ADDU instruction since it does
not involve memory access.


// Memory Access Stage - No action needed for ADDU

module MemoryAccess(
input [31:0] i_ALUResult,
output [31:0] o_MemOut
);
assign o_MemOut = i_ALUResult; // Direct pass-through
endmodule

WB (Write Back)


The result from the ALU is written back to the destination register
rd.


// Write Back stage

module WriteBack(
input [31:0] i_ALUResult, // Result from ALU
input [4:0] i_rd, // Destination register
output reg [31:0] o_WriteData // Data to write back
);
// Write the data back to the register file
always @(i_ALUResult) begin
o_WriteData = i_ALUResult;
end
endmodule

SUB (Subtract)


Name (format, op, function): subtract (R,0,34)


Syntax: sub rd,rs,rt


Operation: reg(rd) := reg(rs) [ reg(rt);


Instruction Overview:




  • IF: Fetch the instruction using the PC.


  • ID: Decode the instruction; read registers rs and
    rt.


  • EX: The ALU subtracts the value in rt from rs.


  • MEM: No action needed (pass-through).


  • WB: The ALU result is written back to register
    rd.


Operation Breakdown:


The following provides a detailed breakdown of the operation of the
SUB instruction in a MIPS processor, focusing on the key
stages of the processor pipeline.




  1. Instruction Fetch (IF) Stage: In this stage, the
    processor fetches the instruction from instruction memory using the
    Program Counter (PC).


// Instruction Fetch (IF) stage

module InstructionFetch(
input [31:0] i_pc,
output reg [31:0] o_instruction
);
// Assume IMem is an array storing instructions
reg [31:0] IMem[0:1023];

// Fetch instruction
always @(i_pc) begin
o_instruction = IMem[i_pc >> 2]; // Word aligned fetch
end
endmodule



  1. Instruction Decode (ID) Stage: Here, the
    instruction is decoded, and the relevant registers are read. The
    operation is identified, and signals are prepared for the execution
    stage.


// Instruction Decode (ID) stage

module InstructionDecode(
input [31:0] i_instruction,
output reg [4:0] o_rs, o_rt, o_rd,
output reg [5:0] o_opcode, o_funct
);
always @(i_instruction) begin
o_opcode = i_instruction[31:26];
o_rs = i_instruction[25:21];
o_rt = i_instruction[20:16];
o_rd = i_instruction[15:11];
o_funct = i_instruction[5:0];
end
endmodule



  1. Execution (EX) Stage: The ALU performs the
    subtraction based on the decoded instruction. The operands are taken
    from the registers identified in the ID stage.


// Execution (EX) stage - ALU for SUB operation

module ALU(
input [31:0] i_data1, i_data2,
input [3:0] i_ALUcontrol,
output reg [31:0] o_result
);
always @(*) begin
case(i_ALUcontrol)
4'b0110: o_result = i_data1 - i_data2; // SUB operation
// Additional cases for other ALU operations
endcase
end
endmodule



  1. Memory (MEM) Stage: For the SUB
    instruction, there is no memory operation needed. This stage can be
    passed through or handled with a control signal that disables memory
    operations.


// Memory (MEM) stage pass-through for SUB

module MemoryStage(
input i_MemRead, i_MemWrite,
input [31:0] i_address, i_writeData,
output reg [31:0] o_readData
);
// Memory array
reg [31:0] DMem[0:1023];

always @(*) begin
if (i_MemWrite) DMem[i_address >> 2] = i_writeData;
if (i_MemRead) o_readData = DMem[i_address >> 2];
end
endmodule



  1. Write Back (WB) Stage: The result of the ALU
    operation is written back to the register file, particularly in the
    register specified by rd.


// Write Back (WB) Stage

module WriteBack(
input [31:0] i_ALUresult,
input [4:0] i_rd,
input i_RegWrite,
output reg [31:0] o_writeData
);
always @(i_ALUresult) begin
if (i_RegWrite) begin
o_writeData = i_ALUresult;
end
end
endmodule

SUBU (Subtract Unsigned)


Name (format, op, function):
subtract unsigned (R,0,35)


Syntax: subu rd,rs,rt


Operation: reg(rd) := reg(rs) [ reg(rt);


Instruction Overview:




  • IF: Fetch the instruction using the PC.


  • ID: Decode the instruction; read registers rs and
    rt.


  • EX: The ALU subtracts the value in rt from rs.


  • MEM: No action needed (pass-through).


  • WB: The ALU result is written back to register
    rd.


Operation Breakdown:




  1. Instruction Fetch (IF)

    • The instruction is fetched from the instruction memory using the
      Program Counter (PC).

    • Verilog snippet:


      // IF Stage
    
    always @(posedge clk) begin
    if (reset)
    pc <= 0;
    else if (pc_src)
    pc <= pc_next;
    else
    pc <= pc + 4;
    end



  2. Instruction Decode (ID)

    • The fetched instruction is decoded to identify the operation as
      SUBU and the source (rs, rt) and
      destination (rd) registers are identified.

    • Verilog snippet:


      // ID Stage
    
    reg [31:0] instruction;
    wire [4:0] rs, rt, rd;
    assign rs = instruction[25:21];
    assign rt = instruction[20:16];
    assign rd = instruction[15:11];



  3. Execution (EX)

    • The actual subtraction of the contents of the registers
      rs and rt is performed. The result does not
      account for overflow because it is unsigned.

    • Verilog snippet:


      // EX Stage
    
    reg [31:0] reg_data[31:0]; // Register file
    wire [31:0] rs_value, rt_value, result;
    assign rs_value = reg_data[rs];
    assign rt_value = reg_data[rt];
    assign result = rs_value - rt_value;



  4. Memory Access (MEM)


    • SUBU does not require a memory operation, so this stage
      can be considered a pass-through.

    • No action required for SUBU




  5. Write Back (WB)

    • The result of the subtraction is written back to the destination
      register rd.

    • Verilog snippet:


      // WB Stage
    
    always @(posedge clk) begin
    if (reg_write)
    reg_data[rd] <= result;
    end



module MIPS_Processor(input clk, input reset, output [31:0] pc);

reg [31:0] pc, next_pc;
reg [31:0] reg_file[31:0]; // Register file

// Instruction Fetch
always @(posedge clk) begin
if (reset)
pc <= 0;
else
pc <= next_pc;
end

// Instruction Decode
reg [31:0] instruction;
wire [4:0] rs, rt, rd;
wire [5:0] opcode;
assign opcode = instruction[31:26];
assign rs = instruction[25:21];
assign rt = instruction[20:16];
assign rd = instruction[15:11];
// Execute
wire [31:0] rs_value, rt_value, alu_result;
assign rs_value = reg_file[rs];
assign rt_value = reg_file[rt];
assign alu_result = (opcode == 6'b000000) ? (rs_value - rt_value) : 32'b0; // SUBU Opcode assumed
// Memory Access
// No memory access for SUBU
// Write Back
always @(posedge clk) begin
if (opcode == 6'b100011) // SUBU Opcode
reg_file[rd] <= alu_result;
end
// Program Counter Update
always @(*) begin
next_pc = pc + 4; // Simple sequential execution
end
endmodule

In this example, opcode == 6'b100011' is the actual
opcode for SUBU.


AND (Bitwise AND)




  • IF: Instruction is fetched.


  • ID: Instruction is decoded. For AND, rs and rt are
    read;


  • EX: The ALU performs an AND operation between
    operands.


  • MEM: No action needed.


  • WB: Result is written back to rd (AND)


Instruction Breakdown


The AND instruction performs a bitwise AND operation between two
operands and stores the result in a destination register.




  1. Instruction Fetch (IF) stage:

    • The Program Counter (PC) contains the address of the AND
      instruction.

    • The Instruction Memory module (InstructionMemory.v)
      fetches the instruction from the memory location pointed to by the
      PC.

    • The fetched instruction is passed to the next stage.




  2. Instruction Decode (ID) stage:

    • The Control Unit module (ControlUnit.v) decodes the
      opcode of the AND instruction.

    • Based on the opcode, the Control Unit generates the appropriate
      control signals for the data-path components.

    • The Register File module reads the values of the source registers
      specified in the AND instruction.




  3. Execution (EX) stage:

    • The ALU module (ALU.v) performs the bitwise AND
      operation between the values of the source registers.

    • The ALU control signal generated by the Control Unit determines the
      specific operation to be performed (AND in this case).




  4. Memory Access (MEM) stage:

    • The AND instruction does not require any memory access, so this
      stage is a pass-through.




  5. Write Back (WB) stage:

    • The result of the AND operation from the ALU is written back to the
      destination register specified in the AND instruction.

    • The RegWrite control signal generated by the Control Unit enables
      the writing of the result to the Register File.




Within the control unit, the AND instruction is identified by its
opcode, and the appropriate control signals are set to execute the AND
operation.


The ALU module performs the bitwise AND operation between the source
register values, and the result is written back to the destination
register.


Here’s an example of how the AND instruction flows through the
different stages of the single-cycle MIPS processor starting within the
control unit.


// Control Unit

always @(i_instruction) begin
case (i_instruction[31:26])
// ...
6'b001100: begin // andi
o_RegDst = 0; // Destination register is rt
o_ALUSrc = 1; // Second operand is immediate value
o_MemtoReg = 0; // ALU result is written to register
o_RegWrite = 1; // Write to register file
o_MemRead = 0; // No memory read
o_MemWrite = 0; // No memory write
o_Branch = 0; // No branch
o_Bne = 0; // No branch if not equal
o_ALUOp = 2'b11; // ALU operation is AND
o_Jump = 0; // No jump
// ...
end
// ...
endcase
end

// ALU
always @(i_data1, data2, i_ALUcontrol) begin
case (i_ALUcontrol)
// ...
4'b0000: // AND
o_ALUresult = i_data1 & data2;
// ...
endcase
// ...
end

In the Control Unit module, when the opcode of the instruction
matches the AND opcode (6'b001100 in this case), the
appropriate control signals are set.


The ALUSrc signal is set to 1 to select the immediate
value as the second operand, and the ALUOp signal is set to
indicate an AND operation.


In the ALU module, when the ALUcontrol signal matches
the AND operation (4’b0000), the bitwise AND operation is performed
between the two input operands (i_data1 and data2), and the result is
assigned to o_ALUresult.


ANDI (AND Immediate)


and immediate (I,12,na)


andi rt,rs,imm


reg(rt) := reg(rs) & zeroext(imm);


Instruction Overview:




  • IF: Instruction is fetched.


  • ID: Opcode decoded. Registers rs and immediate for
    ANDI are read.


  • EX: ALU performs an AND operation.


  • MEM: No memory access.


  • WB: Result written to or rt (ANDI).


Instruction Breakdown


The ANDI (AND Immediate) instruction performs a bitwise AND operation
between a register value and an immediate value.


Here’s an explanation of how the ANDI instruction goes through each
stage of the MIPS processor pipeline:




  1. Instruction Fetch (IF):

    • The Program Counter (PC) contains the address of the ANDI
      instruction in the Instruction Memory.

    • The instruction is fetched from the Instruction Memory using the PC
      value.

    • Example code in the Instruction Memory module
      (InstructionMemory.v):




     always @(i_Addr) begin

if (i_Addr == -4) begin // init
i_Instruction = 32'b11111100000000000000000000000000;
end else begin
i_Instruction = Imem[i_Addr>>2];
end
i_Ctr = i_Instruction[31:26];
i_Funcode = i_Instruction[5:0];
end



  1. Instruction Decode (ID):

    • The fetched instruction is decoded to determine the operation to be
      performed.

    • The Control Unit generates the necessary control signals based on
      the opcode and function code of the instruction.

    • The register to be read (rs) is determined from the instruction, and
      the immediate value is sign-extended.

    • Example code in the Control Unit module
      (ControlUnit.v):




     6'b001100: begin  // andi

o_RegDst = 0;
o_ALUSrc = 1;
o_MemtoReg = 0;
o_RegWrite = 1;
o_MemRead = 0;
o_MemWrite = 0;
o_Branch = 0;
o_Bne = 0;
o_ALUOp = 2'b11;
o_Jump = 0;
// ...
end



  1. Execute (EX):

    • The ALU performs the bitwise AND operation between the value of
      register rs and the sign-extended immediate value.

    • The result of the AND operation is stored in a temporary
      register.

    • Example code in the ALU module (ALU.v):


      always @(i_data1, data2, i_ALUcontrol) begin
    
    case (i_ALUcontrol)
    // ...
    4'b0000: // AND
    o_ALUresult = i_data1 & data2; // bitwise AND
    // ...
    endcase
    // ...
    end



  2. Memory Access (MEM):

    • The ANDI instruction does not involve memory access, so no action is
      needed in this stage.

    • The result from the Execute stage is simply passed through to the
      next stage.




  3. Write Back (WB):

    • The result of the AND operation, stored in the temporary register,
      is written back to the destination register (rt) in the Register
      File.

    • Example code in the Register File module
      (RegisterFile.v):




     always @(posedge i_Clk or posedge i_Rst) begin

if (i_Rst) begin
// Reset all registers to zero
for (j = 0; j < 32; j = j + 1) begin
RegData[j] = 32'b0;
end
end else if (i_RegWrite) begin
// Write data to the specified register
RegData[i_wReg] = i_wData;
end
end

Here’s an example of how the ANDI instruction would look in machine
code:


001100 01000 01010 0000000000001111

In this example: - 001100 is the opcode for the ANDI
instruction. - 01000 represents the source register (rs),
which is $8 in this case. - 01010 represents the
destination register (rt), which is $10 in this case. -
0000000000001111 is the immediate value, which is 15 in
decimal.


OR




  • IF: Instruction is fetched.


  • ID: Opcode decoded. Registers rs and rt are read
    for OR; rs and immediate for ORI.


  • EX: ALU performs an OR operation.


  • MEM: No action needed.


  • WB: Result written to rd (OR) or rt (ORI).


Instruction Breakdown


The OR instruction in the MIPS architecture performs a
bitwise OR operation on two register values and stores the result in a
destination register.




  1. Instruction Fetch (IF) Stage:



    • The Program Counter (PC) holds the address of the OR
      instruction to be fetched.

    • The Instruction Memory module (InstructionMemory.v)
      retrieves the instruction from the memory based on the PC value.

    • The fetched instruction is passed to the next stage.


    Example code from InstructionMemory.v:
    verilog always @(i_Addr) begin if (i_Addr == -4) begin i_Instruction = 32'b11111100000000000000000000000000; end else begin i_Instruction = Imem[i_Addr>>2]; end i_Ctr = i_Instruction[31:26]; i_Funcode = i_Instruction[5:0]; end



  2. Instruction Decode (ID) Stage:




  • The fetched instruction is decoded to identify the opcode and
    register operands.

  • The Control Unit module (ControlUnit.v) sets the
    appropriate control signals based on the opcode.

  • For the OR instruction, the ALUOp control
    signal is set to indicate an OR operation.

  • The register operands (rs and rt) are read
    from the Register File.


Example code from ControlUnit.v:


always @(i_instruction) begin

case (i_instruction[31:26])
// ...
6'b000000: begin // ARITHMETIC
o_RegDst = 1;
o_ALUSrc = 0;
o_MemtoReg = 0;
o_RegWrite = 1;
o_MemRead = 0;
o_MemWrite = 0;
o_Branch = 0;
o_Bne = 0;
o_ALUOp = 2'b10;
o_Jump = 0;
// ...
end
// ...
endcase
end


  1. Execute (EX) Stage:



  • The ALU module (ALU.v) performs the bitwise OR
    operation on the values of rs and rt based on
    the ALUOp control signal.

  • The result of the OR operation is stored in a temporary
    register.


Example code from ALU.v:


always @(i_data1, data2, i_ALUcontrol) begin

case (i_ALUcontrol)
// ...
4'b0001: // OR
o_ALUresult = i_data1 | data2; // bitwise OR
// ...
endcase
// ...
end



  1. Memory Access (MEM) Stage:

    • For the OR instruction, no memory access is needed, so
      this stage is a pass-through.



  2. Write Back (WB) Stage:



  • The ALU result, which is the result of the OR operation, is written
    back to the destination register (rd) in the Register
    File.


Example code for writing back to the Register File:


always @(posedge i_clk) begin

if (i_RegWrite) begin
RegData[i_wReg] = i_wData;
end
end

Throughout the execution of the OR instruction, the
control signals generated by the Control Unit module
(ControlUnit.v) orchestrate the flow of data and the
operations performed in each stage.


The ALU Control module (ALUControl.v) decodes the
ALUOp signal and the function code of the instruction to
generate the appropriate ALUControl signal for the ALU
module (ALU.v) to perform the OR operation.


ORI (OR Immediate)




  • IF: Instruction is fetched.


  • ID: Opcode is decoded. Registers rs and rt are read
    for OR; rs and immediate for ORI.


  • EX: ALU performs an OR operation.


  • MEM: No action needed.


  • WB: Result is written to rd (OR) or rt (ORI).


Instruction Breakdown


The ORI (OR Immediate) instruction in MIPS is an I-type
instruction that performs a bitwise OR operation between a register and
a zero-extended immediate value.


Instruction Format:


ORI rt, rs, immediate



  • rt: The destination register where the result will be
    stored.


  • rs: The source register containing one of the
    operands.


  • immediate: A 16-bit immediate value that will be
    zero-extended to 32 bits.


Instruction Encoding:


| opcode (6 bits) | rs (5 bits) | rt (5 bits) | immediate (16 bits) |

Processor Stages: 1. Instruction Fetch (IF): - The instruction is
fetched from the instruction memory using the PC (Program Counter). -
The PC is incremented by 4 to point to the next instruction.



  1. Instruction Decode (ID):

    • The instruction is decoded by the control unit.

    • The register file is accessed to read the values of the source
      register rs and the destination register
      rt.

    • The 16-bit immediate value is zero-extended to 32 bits.




Verilog code for the instruction decode stage:


// Control Unit

always @(i_instruction) begin
case (i_instruction[31:26])
// ...
6'b001101: begin // ORI
o_RegDst = 0;
o_ALUSrc = 1;
o_MemtoReg = 0;
o_RegWrite = 1;
o_MemRead = 0;
o_MemWrite = 0;
o_Branch = 0;
o_ALUOp = 2'b11; // ALU control for OR operation
o_Jump = 0;
end
// ...
endcase
end


  1. Execute (EX):

    • The ALU performs the bitwise OR operation between the value in the
      source register rs and the zero-extended immediate
      value.

    • The ALU control unit generates the appropriate control signal for
      the OR operation based on the ALUOp signal from the control
      unit.




Verilog code for the ALU control unit:


// ALU Control

always @(i_ALUOp or i_Funcode) begin
case (i_ALUOp)
// ...
2'b11: begin // ORI
o_ALUcontrol = 4'b0001; // ALU control for OR operation
end
// ...
endcase
end


  1. Memory (MEM):

    • No memory access is needed for the ORI instruction, so
      this stage is a pass-through.



  2. Write Back (WB):

    • The ALU result is written back to the destination register
      rt in the register file.




Verilog code for the write-back stage:


// Register File

always @(posedge i_clk) begin
if (i_RegWrite) begin
RegData[i_writeReg] <= i_writeData;
end
end

Here’s an example of how the ORI instruction would be
processed in the single-cycle MIPS processor:


ORI $t0, $s0, 0xFFFF

This instruction performs a bitwise OR operation between the value in
register $s0 and the immediate value 0xFFFF
(16 bits), and stores the result in register $t0.


In the IF stage, the instruction is fetched from the instruction
memory using the PC.


In the ID stage, the instruction is decoded, and the values of
$s0 and $t0 are read from the register file.
The immediate value 0xFFFF is zero-extended to 32 bits.


In the EX stage, the ALU performs the bitwise OR operation between
the value in $s0 and the zero-extended immediate value. The
ALU control unit generates the appropriate control signal for the OR
operation based on the ALUOp signal from the control
unit.


NOR


Intruction Overview




  • IF: Fetch instruction.


  • ID: Decode opcode; read rs and rt.


  • EX: ALU performs NOR operation on rs and rt.


  • MEM: No action needed.


  • WB: Result is written back to rd.


Instruction Breakdown


The NOR instruction in the MIPS architecture performs a bitwise NOR
operation on the values of two registers and stores the result in a
destination register.




  1. Instruction Fetch (IF) Stage:

    • The instruction is fetched from the instruction memory using the
      program counter (PC).

    • The instruction memory module (InstructionMemory.v)
      retrieves the instruction based on the address provided by the PC.




 // Inside the InstructionMemory module

always @(i_Addr) begin
if (i_Addr == -4) begin // init
i_Instruction = 32'b11111100000000000000000000000000;
end else begin
i_Instruction = Imem[i_Addr>>2];
end
i_Ctr = i_Instruction[31:26];
i_Funcode = i_Instruction[5:0];
end



  1. Instruction Decode (ID) Stage:

    • The fetched instruction is decoded by the control unit
      (ControlUnit.v).

    • The opcode of the instruction (bits [31:26]) is used to determine
      the type of instruction.

    • For the NOR instruction, the opcode is 6’b100111 (binary
      representation of 39).




 // Inside the ControlUnit module

always @(i_instruction) begin
case (i_instruction[31:26])
// ...
6'b100111: begin // NOR
o_RegDst = 1;
o_ALUSrc = 0;
o_MemtoReg = 0;
o_RegWrite = 1;
o_MemRead = 0;
o_MemWrite = 0;
o_Branch = 0;
o_ALUOp = 2'b11;
o_Jump = 0;
// ...
end
// ...
endcase
end



  1. Execute (EX) Stage:

    • The ALU performs the bitwise NOR operation on the values of the
      source registers (rs and rt).

    • The ALU control unit generates the appropriate control signal
      (4’b1100) for the NOR operation based on the ALUOp bits from the control
      unit.




 // Inside the ALU module

always @(i_data1, data2, i_ALUcontrol) begin
case (i_ALUcontrol)
// ...
4'b1100: // NOR
o_ALUresult = i_data1 | ~data2;
// ...
endcase
// ...
end



  1. Memory (MEM) Stage:

    • For the NOR instruction, no memory access is required, so this stage
      is a pass-through.




  2. Write Back (WB) Stage:

    • The result of the NOR operation from the ALU is written back to the
      destination register (rd) in the register file.




// Inside the RegisterFile module

always @(posedge i_clk) begin
if (i_wEn == 1) begin
RegData[i_wDst] <= i_wData;
end
end

Here’s an example of how the NOR instruction would be encoded in MIPS
assembly and its corresponding machine code:


# MIPS Assembly

nor $t0, $s1, $s2 # Perform bitwise NOR of $s1 and $s2 and store the result in $t0

# Machine Code (in binary)

000000 10001 10010 01000 00000 100111

In the machine code, the bits are organized as follows: - Bits
[31:26]: Opcode (000000 for R-type instructions) - Bits [25:21]: Source
register 1 (rs) - Bits [20:16]: Source register 2 (rt) - Bits [15:11]:
Destination register (rd) BNE (Branch Not Equal)




  • IF: Fetch instruction.


  • ID: Decode instruction; read registers rs and
    rt.


  • EX: Compare values of rs and rt.


  • MEM: No action needed.


  • WB: If rs != rt, PC is updated to branch address
    (PC + offset); otherwise, move to next sequential instruction.


Instruction Breakdown


The BNE (Branch Not Equal) instruction is a conditional branch
instruction in the MIPS architecture.


It compares the values of two registers and transfers control to a
target address if the values are not equal.




  1. Instruction Fetch (IF) Stage: In the IF stage, the
    instruction is fetched from the Instruction Memory using the Program
    Counter (PC).


// Fetch the instruction from the Instruction Memory

i_Instruction = Imem[i_Addr>>2];



  1. Instruction Decode (ID) Stage: In the ID stage, the
    instruction is decoded, and the registers rs and rt are read from the
    Register File.


// Decode the instruction

i_Ctr = i_Instruction[31:26];
i_Funcode = i_Instruction[5:0];

// Read registers rs and rt from the Register File
wire [4:0] rs = i_Instruction[25:21];
wire [4:0] rt = i_Instruction[20:16];
wire [31:0] rs_data = RegData[rs];
wire [31:0] rt_data = RegData[rt];



  1. Execution (EX) Stage: In the EX stage, the values
    of rs and rt are compared using the ALU.


// Compare the values of rs and rt using the ALU

wire ALU_zero;
ALU alu (
.i_data1(rs_data),
.i_read2(rt_data),
.i_ALUcontrol(ALU_control),
.o_Zero(ALU_zero),
.o_ALUresult(ALU_result)
);


  1. Memory Access (MEM) Stage:


The BNE instruction does not require any memory access, so this stage
is a pass-through.



  1. Write Back (WB) Stage:


In the WB stage, if the values of rs and rt are not equal (ALU_zero
is 0), the Program Counter (PC) is updated to the branch target address
(PC + offset). Otherwise, the PC moves to the next sequential
instruction.


// Update the PC based on the branch condition

wire [31:0] branch_target = PC + {{14{i_Instruction[15]}}, i_Instruction[15:0], 2'b00};
wire branch_taken = i_Branch & ~ALU_zero;
assign PC_next = branch_taken ? branch_target : PC + 4;

Here’s an example of how the BNE instruction can be represented in
Verilog:


module BNE_example (

input [31:0] rs_data,
input [31:0] rt_data,
input [15:0] offset,
input [31:0] PC,
output reg [31:0] PC_next
);
wire ALU_zero;
wire [31:0] branch_target = PC + {{14{offset[15]}}, offset, 2'b00};
// Compare rs and rt using ALU
assign ALU_zero = (rs_data == rt_data) ? 1 : 0;
// Update PC based on branch condition
always @(*) begin
if (~ALU_zero)
PC_next = branch_target;
else
PC_next = PC + 4;
end
endmodule

In this example, the BNE_example module takes the values
of rs and rt (rs_data and rt_data), the branch
offset (offset), and the current Program Counter
(PC) as inputs.


It compares rs and rt using the ALU and updates the
PC_next output based on the branch condition.


If rs and rt are not equal (ALU_zero is 0),
PC_next is set to the branch target address
(branch_target).


Otherwise, PC_next is set to the next sequential
instruction address (PC + 4).


Comparing Verilog vs VHDL


The following section will compare the experience of developing a
single-cycle processor in Verilog vs VHDL.


Interesting notes about
verilog


The loose typing of Verilog can lead to some useful modules that can
be defined in small amounts of code.


Additionally, in verilog, you do not have to declare your components
in the component that uses them which also decreases the amount of code
that is needed to be written.


For example, the following module is the mux module that is used in
the processor to select between two inputs based on a control
signal.


module mux #(parameter size = 1) (

input select,
input [size - 1:0] in_0,
input [size - 1:0] in_1,
output [size - 1:0] out
);
assign out = (select) ? in_1 : in_0;
endmodule

Another example of this is the sign extender module that is used to
extend the sign of a 16-bit number to a 32-bit number.


module signextender (

input [15:0] in,
output [31:0] out
);
assign out = {{16{in[15]}}, {in}};
endmodule

Additionally, the fact that in Verilog you do not need to
preemptively define components before using them allows for a more
flexible design and faster development.


Another nice feature of Verilog (specifically VerilogHDL) is the
ability to print out the values of signals without having to worry about
their types in the waveform viewer inside modelsim/questasim.


This is a feature that is not present in VHDL and is very useful for
debugging and understanding the behavior of the processor.


Interesting notes about VHDL


While you can do this “print debugging” in VHDL, it is not as easy as
it is in Verilog because in VHDL you must deal with the typings of the
signals and the fact that you must declare the signals before you can
use them.


As an example, the following is the code in VHDL that prints out the
values of the signals in the waveform viewer for an n-bit register which
was used in a project for CPRE381.


The following test-bench shows the code that can be executed inside
modelsim/questasim to print out the values of the signals in the
waveform viewer.


It displays the additional hurdles that must be overcome in VHDL to
print out the values of the signals in the waveform viewer because of
the strict typing of the language.


LIBRARY IEEE;

USE IEEE.std_logic_1164.ALL;
ENTITY tb_nbitregister IS
GENERIC (
gCLK_HPER : TIME := 50 ns;
N : INTEGER := 32);
END tb_nbitregister;
ARCHITECTURE behavior OF tb_nbitregister IS

-- Calculate the clock period as twice the half-period
CONSTANT cCLK_PER : TIME := gCLK_HPER * 2;
COMPONENT nbitregister
PORT (
i_CLK : IN STD_LOGIC; -- Clock input
i_RST : IN STD_LOGIC; -- Reset input
i_WE : IN STD_LOGIC; -- Write enable input
i_D : IN STD_LOGIC_VECTOR(N - 1 DOWNTO 0); -- Data value input
o_Q : OUT STD_LOGIC_VECTOR(N - 1 DOWNTO 0) -- Data value output
);
END COMPONENT;
-- Temporary Signals to connect to the nbitregister component.
SIGNAL s_CLK, s_RST, s_WE : STD_LOGIC;
SIGNAL s_D, s_Q : STD_LOGIC_VECTOR(N - 1 DOWNTO 0);
BEGIN
DUT : nbitregister
PORT MAP(
i_CLK => s_CLK,
i_RST => s_RST,
i_WE => s_WE,
i_D => s_D,
o_Q => s_Q
);
P_CLK : PROCESS
BEGIN
s_CLK <= '0';
WAIT FOR gCLK_HPER;
s_CLK <= '1';
WAIT FOR gCLK_HPER;
END PROCESS;
P_TB : PROCESS
BEGIN
s_RST <= '1';
s_WE <= '0';
s_D <= "00000000000000000000000000000000";
WAIT FOR cCLK_PER;
-- TEST CASE 1 - STORE '1'
-- DESCRIPTION: The register should store the new data value
-- EXPECTED RESULT: The new data value should be stored in the register
s_RST <= '0';
s_WE <= '1';
s_D <= "11111111111111111111111111111111";
WAIT FOR cCLK_PER;
IF (s_Q /= "11111111111111111111111111111111") THEN
REPORT "Test 1 failed";
REPORT "Expected: 11111111111111111111111111111111";
REPORT "Actual: " & STD_LOGIC_VECTOR'image(s_Q);
ELSE
REPORT "TEST 1 PASSED - STORE '1'";
END IF;
// ...
END behavior;

Conclusion


I think that VHDL actually provides more flexibility within the
development of the processor.


While the language is more verbose because you must reinstantiate a
component within another component to use it, it is more type-safe, and
allows for more control over the design of the processor.


Verilog is more concise and easier to read, but I think that VHDL is
more powerful and allows for more control over the design of the
processor because of it’s type-safety.


To support this, I present the result of the actual lines of code
that were written for the processor in VHDL and Verilog.


VHDL Single
Cycle MIPS Processor Code Statistics:


Language                         files          blank        comment           code

-----------------------------------------------------------------------------------
VHDL 65 694 1085 4677

Verilog
Single Cycle MIPS Processor Code Statistics:


Language                         files          blank        comment           code

-----------------------------------------------------------------------------------
Verilog-SystemVerilog 10 0 9 555

As you can see, the VHDL processor has almost 10
times the amount of code as the Verilog processor.


Furthermore, I think that the fact that the name of a file in verilog
must match the module name is a limitation that VHDL does not have
(atleast in our Quartus simulator).


To summarize, I think that VHDL is more suited for larger projects
and more complex designs where the type-safety and , while Verilog is
more suited for smaller projects and simpler designs.


Breaking
down decoding a signal to 7-segment displays


As the signal representing the instruction is 5 bits long inside the
controller.v file, we need to decode this signal to display
the current instruction on the 7-segment displays.


This means that we need to decode a 5-bit signal to a 35-bit signal
that will be used to display the current instruction on the 7-segment
displays.


If 5-bits are used to represent the instruction, and 7-bits are
needed to represent a character on a 7-segment display, then 35-bits are
needed to represent the current instruction on 5 7-segment displays as 7
* 5 = 35.


Furthermore, the longest word that can be displayed on the 7-segment
displays is 5 characters long, so 5 * 7 = 35 bits are needed to
represent the current instruction on the 7-segment displays.

Func_in
O_out
Operation
Description

1000
ox
(A+B)
ADD

1000
1X
(A−B)
SuB

1001
00
(A&B)
AND

1001
01
(AB)
OR

1001
π
 ∼ (A∣B)
NOR

101
xx0
signed (A) < signed (B)
Set-Less-Than signed

101
xx1
A < B
Set-Less-Than unsigned

111
000
A
BLTZ (Branch if Less Than Zero)

111
001
A
BGEZ (Branch if Greater or Equal to
Zero)

111
010
A
J/AL (Jump and Link)

111
011
A
JR/AL (Jump Register and Link)

111
100
A
BEQ(
(Branch if Equal)

111
101
A
BNE (Branch if Not Equal)

The following is the wave-diagram from modelsim/questasim for my
test-bench of my processor without the added seven segment displays.

WaveDiagramWithoutSevenSegment.png
WaveDiagramWithoutSevenSegment.png

Below is the captured wave-diagram from modelsim/questasim with the
seven segment ports included:

SevenSegmentWaveDiagram.png
SevenSegmentWaveDiagram.png

The wave-forms show the output of the following test-bench,
mips_tb.v, which is used to test the single-cycle MIPS
processor, mips.v.


`timescale 1ns / 1ps

`define CYCLE_TIME 20
module mips_tb;
reg clk;
reg rst;
// segments for the 7-segment displays
wire [6:0] seg_first, seg_second, seg_third, seg_fourth, seg_fifth;
integer i;
always #(`CYCLE_TIME / 2) clk = ~clk;
mips uut (
.i_Clk(clk),
.i_Rst(rst),
.o_Seg_first(seg_first),
.o_Seg_second(seg_second),
.o_Seg_third(seg_third),
.o_Seg_fourth(seg_fourth),
.o_Seg_fifth(seg_fifth)
);
initial begin
// Initialize data memory
for (i = 0; i < 32; i = i + 1) begin
uut.inst_DataMemory.Dmem[i] = 32'b0;
end
// Initialize Register File
for (i = 0; i < 32; i = i + 1) begin
uut.inst_RegisterFile.RegData[i] = 32'b0;
end
clk = 0;
end
initial begin
#1800 $finish;
end
endmodule

The given Verilog code represents my test-bench module that was used
for testing the single-cycle MIPS processor, mips.v.



  1. The test-bench module is named mips_tb (titled
    mips_tb.v), and it operates on a timescale of
    1ns/1ps.



  2. The module declares two reg variables:




    • clk: Represents the clock signal for the
      processor.


    • rst: Represents the reset signal for the
      processor.



  3. It also declares five wire variables (seg_first,
    seg_second, seg_third,
    seg_fourth, seg_fifth) to represent the
    segments for the 7-segment displays. These wires are used to display the
    current instruction being executed by the processor.


  4. The integer variable i is declared as a
    loop variable for initializing memory.


  5. The always block generates the clock signal by
    toggling the clk variable every half of the clock cycle
    time (CYCLE_TIME/2).


  6. The mips module (the actual MIPS processor) is
    instantiated as uut (unit under test) with the following
    connections:





  • i_Clk is connected to the clk signal.


  • i_Rst is connected to the rst signal.

  • The 7-segment display outputs (o_Seg_first,
    o_Seg_second, o_Seg_third,
    o_Seg_fourth, o_Seg_fifth) are connected to
    the corresponding wires in the test-bench.



  1. The first initial block is used to initialize the data
    memory and the register file of the MIPS processor:

    • It uses a for loop to iterate over the first 32
      locations of the data memory (Dmem) and initializes each
      location to zero.

    • Similarly, it initializes the first 32 registers in the register
      file (RegData) to zero.

    • Finally, it sets the clk variable to 0.



  2. The second initial block is used to specify the
    duration of the simulation. It uses the $finish system task
    to terminate the simulation after 1800 time units.


The purpose of this test-bench is to provide a simulation environment
for the MIPS processor. It initializes the necessary components (data
memory and register file), generates the clock signal, and instantiates
the MIPS processor module. The testbench also specifies the duration of
the simulation.


The test-bench interacts with other components of the processor
through the instantiated mips module (uut). It
provides the clock and reset signals to the processor and observes the
output signals for the 7-segment displays.


Overall, this test-bench serves as a framework to verify the
functionality of the single-cycle MIPS processor by providing the
necessary inputs, initializing the memory, and specifying the simulation
duration.


Schematics


The following section will include the schematics for the various
components of the MIPS processor as written in verilog.


Control Unit Schematic


The following is the schematic for the control unit of the MIPS
processor.


[[Pasted image 20240503123506.png]]


Register File


The following is the schematic for the register file of the MIPS
processor.


[[RegisterFile.png]]


Data Memory


The following is the schematic for the data memory of the MIPS
processor.

SchematicDataMemory.png
SchematicDataMemory.png

ALU Control


The following is the schematic for the ALU control of the MIPS
processor.

SchematicALUControl.png
SchematicALUControl.png

Program Counter Control


The following is the schematic for the program counter control of the
MIPS processor.

SchematicNextProgramCounter.png
SchematicNextProgramCounter.png

ALU


The following is the schematic for the ALU of the MIPS processor.

SchematicALU.png
SchematicALU.png

Instruction Memory


The following is the schematic for the instruction memory of the MIPS
processor.

SchematicInstructionMemory.png
SchematicInstructionMemory.png

Program Counter


The following is the schematic for the program counter of the MIPS
processor.

SchematicProgramCounter.png
SchematicProgramCounter.png

Waveform


The following is the wave form of the Processor from
modelsim/questasim:

TestbenchWaveform.png
TestbenchWaveform.png

Tooling


First, as learned in CPRE381, I enjoy having test-benches for my
code.


Test-benches allow for faster debugging and more efficient
development by allowing you to test your code without having to run it
on the FPGA board, directly seeing the signals, how they interact with
one another, and allows for testing to make sure that progress is being
made.


I used a test-bench to test my processor and ensure that it was
working correctly.


The following is the main test-bench that I used to test my
processor, mips_tb.v.


`timescale 1ns / 1ps

`define CYCLE_TIME 20
module mips_tb;
reg clk;
reg rst;
wire [6:0] seg_first, seg_second, seg_third, seg_fourth, seg_fifth;
integer i;
always #(`CYCLE_TIME / 2) clk = ~clk;
mips uut (
.i_Clk(clk),
.i_Rst(rst),
.o_Seg_first(seg_first),
.o_Seg_second(seg_second),
.o_Seg_third(seg_third),
.o_Seg_fourth(seg_fourth),
.o_Seg_fifth(seg_fifth)
);
initial begin
// Initialize data memory
for (i = 0; i < 32; i = i + 1) begin
uut.inst_DataMemory.Dmem[i] = 32'b0;
end
// Initialize Register File
for (i = 0; i < 32; i = i + 1) begin
uut.inst_RegisterFile.RegData[i] = 32'b0;
end
clk = 0;
end
initial begin
#1800 $finish;
end
endmodule

I used do files to more easily compile and run my code.


The following is the main .do file, run.do
that I used to compile and run my code.


set target "mips_tb"

set file "proj/${target}.v"
if { [file exists "work"] } {
vdel -all
}
vlog *.v
vsim -voptargs=+acc -debugDB $target
force -freeze sim:/$target/clk 1 0, 0 {5 ps} -r 10
# force -freeze sim:/$target/rst 0 0, 1 {80 ps} -r 100
add wave -position insertpoint \ ../$target/*
run 1200

In addition to the tooling already mentioned, I used
modelsim/questasim to simulate my processor and test-bench.


Furthermore, I used the Quartus Prime software to compile my code and
program my FPGA board.


Even further, I used tools like git, GitHub, and markdown to manage my code,
version control, and documentation.


For editor tooling, I used NeoVim
with a combination of popular language servers that are used for
VerilogHDL.


These language servers that I used for development include verible, Tree-Sitter, veridian, and more
to provide completion, syntax highlighting, code actions, linting, and
more.


I think that using these language servers and tools in combination
with NeoVim (my personal open-sourced config has a startup time of
<80ms) allowed me to develop my processor more efficiently and
effectively.


Components and Explanations


The following section explains the components of the MIPS processor
and their functionalities.


This is done by providing the Verilog code for each component and
explaining its role in the processor.


ALU


The following is the code for the ALU module of the MIPS processor
called ALU.v. (It can be found in the ./proj/
directory)


`timescale 1ns / 1ps

module ALU (
input [31:0] i_data1, // data 1
input [31:0] i_read2, // data 2 from MUX
input [31:0] i_Instruction, // used for sign-extension
input i_ALUSrc,
input [ 3:0] i_ALUcontrol,
output reg o_Zero,
output reg [31:0] o_ALUresult
);
reg [31:0] data2;
always @(i_ALUSrc, i_read2, i_Instruction) begin
if (i_ALUSrc == 0) begin
data2 = i_read2;
end else begin
if (i_Instruction[15] == 1'b0) begin
data2 = {16'b0, i_Instruction[15:0]};
end else begin
data2 = {{16{1'b1}}, i_Instruction[15:0]};
end
end
end
always @(i_data1, data2, i_ALUcontrol) begin
case (i_ALUcontrol)
4'b0000: // AND
o_ALUresult = i_data1 & data2; // bitwise AND
4'b0001: // OR
o_ALUresult = i_data1 | data2; // bitwise OR
4'b0010: // ADD
o_ALUresult = i_data1 + data2; // addition
4'b0110: // SUB
o_ALUresult = i_data1 - data2; // subtraction
4'b0111: // SLT
o_ALUresult = (i_data1 < data2) ? 1 : 0; // set-on-less-than
4'b1100: // NOR
o_ALUresult = i_data1 | ~data2; // bitwise NOR
default: ;
endcase
if (o_ALUresult == 0) begin
o_Zero = 1;
end else begin
o_Zero = 0;
end
end
endmodule

The above code represents the ALU (Arithmetic Logic Unit) module of
the single-cycle MIPS processor.


The ALU is responsible for performing arithmetic and logical
operations on the input data based on the ALU control signal.


Inputs: - i_data1 (32-bit): The first input data for the
ALU operation. - i_read2 (32-bit): The second input data
from the MUX. - i_Instruction (32-bit): The instruction
used for sign-extension. - i_ALUSrc (1-bit): A control
signal indicating whether to use the second input data from the MUX or
the sign-extended immediate value. - i_ALUcontrol (4-bit):
The ALU control signal that determines the specific operation to be
performed.


Outputs: - o_Zero (1-bit): A flag indicating whether the
ALU result is zero. - o_ALUresult (32-bit): The result of
the ALU operation.


Functionality: 1. Data Selection: - The module first determines the
second input data for the ALU operation based on the
i_ALUSrc control signal. - If i_ALUSrc is 0,
the second input data is taken directly from i_read2. - If
i_ALUSrc is 1, the second input data is obtained by
sign-extending the 16-bit immediate value from the
i_Instruction.



  1. ALU Operation:

    • Based on the i_ALUcontrol signal, the module performs
      the corresponding ALU operation on i_data1 and the selected
      second input data (data2).

    • The supported ALU operations include AND, OR, ADD, SUB (subtract),
      SLT (set-on-less-than), and NOR.

    • The result of the ALU operation is stored in
      o_ALUresult.



  2. Zero Flag:

    • After performing the ALU operation, the module checks if the result
      is zero.

    • If the result is zero, the o_Zero flag is set to 1;
      otherwise, it is set to 0.




Significance in the MIPS Processor: - The ALU is a crucial component
in the MIPS processor’s data-path. - It performs arithmetic and logical
operations on the input data based on the instructions being executed. -
The ALU receives input data from the register file or the immediate
value in the instruction, depending on the i_ALUSrc control
signal. - The ALU control signal (i_ALUcontrol) determines
the specific operation to be performed, which is decoded by the ALU
control module based on the instruction opcode and function code. - The
result of the ALU operation is used for various purposes, such as
storing it back to the register file, using it as a memory address, or
making branching decisions based on the zero flag.


Interaction with Other Components: - The ALU receives input data from
the register file (i_data1 and i_read2) and
the instruction (i_Instruction). - The ALU control module
generates the i_ALUcontrol signal based on the instruction
opcode and function code, which determines the ALU operation to be
performed. - The ALU result (o_ALUresult) is used by other
components, such as the data memory for memory operations or the
register file for storing the result. - The zero flag
(o_Zero) is used by the control unit to make branching
decisions based on the comparison result.


Overall, the ALU module performs the necessary arithmetic and logical
operations in the MIPS processor based on the instruction being
executed.


The ALU allows the processor to execute instructions and produce the
desired results.


Control Unit


The following is the code for the control unit of the MIPS processor
called ControlUnit.v. (It can be found in the
./proj/ directory)


As named, the ControlUnit, ControlUnit.v is
responsible for decoding the instruction and generating the control
signals for the various components of the processor.


`timescale 1ns / 1ps

module ControlUnit (
input [31:0] i_instruction,
output reg o_RegDst,
output reg o_Jump,
output reg o_Branch,
output reg o_Bne,
output reg o_MemRead,
output reg o_MemtoReg,
output reg [1:0] o_ALUOp,
output reg o_MemWrite,
output reg o_ALUSrc,
output reg o_RegWrite,
output reg [6:0] o_seg_first,
output reg [6:0] o_seg_second,
output reg [6:0] o_seg_third,
output reg [6:0] o_seg_fourth,
output reg [6:0] o_seg_fifth
);
initial begin
o_RegDst = 0;
o_Jump = 0;
o_Branch = 0;
o_MemRead = 0;
o_MemtoReg = 0;
o_ALUOp = 2'b00;
o_MemWrite = 0;
o_ALUSrc = 0;
o_RegWrite = 0;
o_seg_first = 7'b1111111; // Blank
o_seg_second = 7'b1111111; // Blank
o_seg_third = 7'b1111111; // Blank
o_seg_fourth = 7'b1111111; // Blank
o_seg_fifth = 7'b1111111; // Blank
end
always @(i_instruction) begin
case (i_instruction[31:26])
6'b000000: begin // ARITHMETIC
o_RegDst = 1;
o_ALUSrc = 0;
o_MemtoReg = 0;
o_RegWrite = 1;
o_MemRead = 0;
o_MemWrite = 0;
o_Branch = 0;
o_Bne = 0;
o_ALUOp = 2'b10;
o_Jump = 0;
o_seg_first = 7'b0001000; // A
o_seg_second = 7'b1111010; // R
o_seg_third = 7'b1111001; // I
o_seg_fourth = 7'b0001111; // T
o_seg_fifth = 7'b0001001; // H
end
6'b001000: begin // addi
o_RegDst = 0;
o_ALUSrc = 1;
o_MemtoReg = 0;
o_RegWrite = 1;
o_MemRead = 0;
o_MemWrite = 0;
o_Branch = 0;
o_Bne = 0;
o_ALUOp = 2'b00;
o_Jump = 0;
o_seg_first = 7'b0001000; // A
o_seg_second = 7'b1000010; // d
o_seg_third = 7'b1000010; // d
o_seg_fourth = 7'b1001111; // i
o_seg_fifth = 7'b1111111; // Blank
end
6'b001100: begin // andi
o_RegDst = 0;
o_ALUSrc = 1;
o_MemtoReg = 0;
o_RegWrite = 1;
o_MemRead = 0;
o_MemWrite = 0;
o_Branch = 0;
o_Bne = 0;
o_ALUOp = 2'b11;
o_Jump = 0;
o_seg_first = 7'b0001000; // A
o_seg_second = 7'b0101011; // n
o_seg_third = 7'b1000010; // d
o_seg_fourth = 7'b1001111; // i
o_seg_fifth = 7'b1111111; // Blank
end
6'b100011: begin // lw
o_RegDst = 0;
o_ALUSrc = 1;
o_MemtoReg = 1;
o_RegWrite = 1;
o_MemRead = 1;
o_MemWrite = 0;
o_Branch = 0;
o_Bne = 0;
o_ALUOp = 2'b00;
o_Jump = 0;
o_seg_first = 7'b1000111; // L
o_seg_second = 7'b1001001; // w
o_seg_third = 7'b1111111; // Blank
o_seg_fourth = 7'b1111111; // Blank
o_seg_fifth = 7'b1111111; // Blank
end
6'b101011: begin // sw
o_RegDst = 0; // X
o_ALUSrc = 1;
o_MemtoReg = 0; // X
o_RegWrite = 0;
o_MemRead = 0;
o_MemWrite = 1;
o_Branch = 0;
o_Bne = 0;
o_ALUOp = 2'b00;
o_Jump = 0;
o_seg_first = 7'b0010010; // S
o_seg_second = 7'b1001001; // w
o_seg_third = 7'b1111111; // Blank
o_seg_fourth = 7'b1111111; // Blank
o_seg_fifth = 7'b1111111; // Blank
end
6'b000100: begin // beq
o_RegDst = 0; // X
o_ALUSrc = 0;
o_MemtoReg = 0; // X
o_RegWrite = 0;
o_MemRead = 0;
o_MemWrite = 0;
o_Branch = 1;
o_Bne = 0;
o_ALUOp = 2'b01;
o_Jump = 0;
o_seg_first = 7'b1100000; // b
o_seg_second = 7'b0110000; // e
o_seg_third = 7'b0001100; // q
o_seg_fourth = 7'b1111111; // Blank
o_seg_fifth = 7'b1111111; // Blank
end
6'b000101: begin // bne
o_RegDst = 0; // X
o_ALUSrc = 0;
o_MemtoReg = 0; // X
o_RegWrite = 0;
o_MemRead = 0;
o_MemWrite = 0;
o_Branch = 1;
o_Bne = 1;
o_ALUOp = 2'b01;
o_Jump = 0;
o_seg_first = 7'b1100000; // b
o_seg_second = 7'b0101011; // n
o_seg_third = 7'b0110000; // e
o_seg_fourth = 7'b1111111; // Blank
o_seg_fifth = 7'b1111111; // Blank
end
6'b000010: begin // j
o_RegDst = 0; // X
o_ALUSrc = 0;
o_MemtoReg = 0; // X
o_RegWrite = 0;
o_MemRead = 0;
o_MemWrite = 0;
o_Branch = 0;
o_Bne = 0;
o_ALUOp = 2'b01;
o_Jump = 1;
o_seg_first = 7'b1100001; // J
o_seg_second = 7'b1111111; // Blank
o_seg_third = 7'b1111111; // Blank
o_seg_fourth = 7'b1111111; // Blank
o_seg_fifth = 7'b1111111; // Blank
end
default: begin
o_RegDst = 0; // X
o_ALUSrc = 0;
o_MemtoReg = 0; // X
o_RegWrite = 0;
o_MemRead = 0;
o_MemWrite = 0;
o_Branch = 0;
o_Bne = 0;
o_ALUOp = 2'b00;
o_Jump = 0;
o_seg_first = 7'b1111111; // Blank
o_seg_second = 7'b1111111; // Blank
o_seg_third = 7'b1111111; // Blank
o_seg_fourth = 7'b1111111; // Blank
o_seg_fifth = 7'b1111111; // Blank
end
endcase
end
endmodule

The above Verilog code represents the Control Unit component of the
single-cycle MIPS processor.


The Control Unit is responsible for generating control signals based
on the input instruction, which determine the behavior of various
components within the processor.


IO

The Control Unit, ControlUnit.v, has the following
inputs and outputs:


Inputs: - i_instruction: The 32-bit instruction fetched
from the Instruction Memory.


Outputs: - Various control signals: - o_RegDst: Selects
the destination register for the instruction (0 for rt, 1 for rd). -
o_Jump: Indicates if the instruction is a jump instruction.
- o_Branch: Indicates if the instruction is a branch
instruction. - o_Bne: Indicates if the instruction is a
“branch not equal” instruction. - o_MemRead: Enables
reading from the Data Memory. - o_MemtoReg: Selects the
source of data to be written to the register (0 for ALU result, 1 for
memory data). - o_ALUOp: A 2-bit signal that specifies the
ALU operation. - o_MemWrite: Enables writing to the Data
Memory. - o_ALUSrc: Selects the second source for the ALU
(0 for register, 1 for immediate). - o_RegWrite: Enables
writing to the Register File. - 7-segment display outputs: -
o_seg_first to o_seg_fifth: Control signals
for displaying the instruction type on 7-segment displays.


Processor Context:

The following is the context (the purpose and functionality) of the
Control Unit in the single-cycle MIPS processor:



  1. The Control Unit initializes all control signals to default
    values in the initial block.


  2. The always block is triggered whenever the
    i_instruction changes. It uses a case statement to
    determine the type of instruction based on the opcode (bits 31 to 26 of
    the instruction).



  3. Depending on the instruction type, the Control Unit sets the
    appropriate control signals:



    • For R-type instructions (arithmetic), it sets RegDst to
      1, enables RegWrite, sets ALUOp to 2’b10, and
      displays “ARITH” on the 7-segment displays.

    • For I-type instructions (addi, andi, lw, sw), it sets
      ALUSrc to 1, enables RegWrite (except for sw),
      sets ALUOp based on the instruction, and displays the
      instruction type on the 7-segment displays.

    • For branch instructions (beq, bne), it sets Branch to
      1, sets ALUOp to 2’b01, and displays the instruction type
      on the 7-segment displays.

    • For the jump instruction, it sets Jump to 1, sets
      ALUOp to 2’b01, and displays “J” on the 7-segment
      displays.



  4. If the instruction does not match any of the defined cases, the
    Control Unit sets all control signals to their default values and
    displays blank on the 7-segment displays.



The Control Unit is critial to correctly orchestrating the operation
of the single-cycle MIPS processor.


It interprets the instruction and generates the necessary control
signals to control the data-path components, such as the ALU, Register
File, and Data Memory.


The control signals determine the flow of data and the operations
performed in each stage of the processor pipeline.



  • The Control Unit receives the instruction from the Instruction
    Memory.

  • It sends control signals to various components, such as the ALU,
    Register File, and Data Memory, to control their behavior based on the
    instruction being executed.

  • The control signals generated by the Control Unit are used by the
    data-path components to perform the required operations and route the
    data accordingly.


Data Memory


The following is the code for the Data Memory module in the MIPS
processor called DataMemory.v. (It can be found in the
./proj/ directory)


`timescale 1ns / 1ps

module DataMemory (
input i_clk,
input [31:0] i_addr,
input [31:0] i_wData,
input [31:0] i_ALUresult,
input i_MemWrite,
input i_MemRead,
input i_MemtoReg,
output reg [31:0] o_rData
);
parameter SIZE_DM = 128; // size of this memory, by default 128*32
reg [31:0] Dmem[SIZE_DM-1:0]; // instruction memory
integer i;
initial begin
for (i = 0; i < SIZE_DM; i = i + 1) begin
Dmem[i] = 32'b0;
end
end
always @(i_addr or i_MemRead or i_MemtoReg or i_ALUresult) begin
if (i_MemRead == 1) begin
if (i_MemtoReg == 1) begin
o_rData = Dmem[i_addr];
end else begin
o_rData = i_ALUresult;
end
end else begin
o_rData = i_ALUresult;
end
end
always @(posedge i_clk) begin // MemWrite, wData, addr
if (i_MemWrite == 1) begin
Dmem[i_addr] = i_wData;
end
end
endmodule

The provided code snippet is the implementation of the Data Memory
module (DataMemory.v) in the single-cycle MIPS
processor.


The Data Memory module serves as the main memory for storing and
retrieving data in the processor.


IO

The following are the detailed input and output ports of the Data
Memory module, DataMemory.v: - i_clk: Input
clock signal. - i_addr: Input address for reading or
writing data. - i_wData: Input write data to be stored in
memory. - i_ALUresult: Input ALU result, which can be used
as the address or data depending on the control signals. -
i_MemWrite: Input control signal indicating a memory write
operation. - i_MemRead: Input control signal indicating a
memory read operation. - i_MemtoReg: Input control signal
indicating whether to pass the memory read data or ALU result to the
output. - o_rData: Output read data from the memory.


Functionality

Memory Initialization:



  • The module defines a parameter SIZE_DM representing the
    size of the data memory (default is 128 words).

  • It declares a register array Dmem of size
    SIZE_DM to store the memory contents.

  • In the initial block, all memory locations are initialized to zero
    using a loop.


Memory Read Operation:



  • The first always block is triggered whenever the input signals
    i_addr, i_MemRead, i_MemtoReg, or
    i_ALUresult change.

  • If i_MemRead is asserted (equals 1), it indicates a
    memory read operation.

    • If i_MemtoReg is also asserted, the data at memory
      location i_addr is assigned to the output
      o_rData.

    • Otherwise, the ALU result i_ALUresult is assigned to
      o_rData.



  • If i_MemRead is not asserted, the ALU result
    i_ALUresult is directly assigned to
    o_rData.


Memory Write Operation:



  • The second always block is triggered on the positive edge of the
    clock signal i_clk.

  • If i_MemWrite is asserted (equals 1), it indicates a
    memory write operation.

  • The data i_wData is written to the memory location
    specified by i_addr.


Significance in the
Processor

Interaction with Other Components: - The Data Memory module interacts
with the ALU and the Control Unit in the processor. - The ALU provides
the address (i_ALUresult) for memory read or write
operations. - The Control Unit generates the control signals
(i_MemWrite, i_MemRead,
i_MemtoReg) to control the behavior of the Data Memory
module. - The Register File provides the data to be written to memory
(i_wData) during a memory write operation. - The output
read data (o_rData) is passed back to the Register File or
used as needed in subsequent stages of the processor pipeline.


Simply put, the Data Memory module allows storing and retrieving data
in the MIPS processor.


It responds to memory read and write requests based on the provided
address and control signals, and it interacts with other components such
as the ALU, Control Unit, and Register File to facilitate data storage
and retrieval operations.


Instruction Memory


The following is the code for the Instruction Memory module in the
MIPS processor called InstructionMemory.v. (It can be found
in the ./proj/ directory)


`timescale 1ns / 1ps

module InstructionMemory (
input [31:0] i_Addr,
output reg [5:0] i_Ctr, // [31-26]
output reg [5:0] i_Funcode, // [5-0]
output reg [31:0] i_Instruction // [31-0]
);
parameter SIZE_IM = 128; // size of this memory, by default 128*32
reg [31:0] Imem[SIZE_IM-1:0]; // instruction memory
integer n;
initial begin
for (n = 0; n < SIZE_IM; n = n + 1) begin
Imem[n] = 32'b11111100000000000000000000000000;
end
$readmemb("instructions.mem", Imem);
i_Instruction = 32'b11111100000000000000000000000000;
end
always @(i_Addr) begin
if (i_Addr == -4) begin // init
i_Instruction = 32'b11111100000000000000000000000000;
end else begin
i_Instruction = Imem[i_Addr>>2];
end
i_Ctr = i_Instruction[31:26];
i_Funcode = i_Instruction[5:0];
end
endmodule

The provided code represents “the Instruction Memory module
InstructionMemory.v for the single-cycle MIPS
processor.”


Purpose:

The Instruction Memory module is responsible for storing the
processor’s instructions and providing them to the other components of
the processor.


It acts as a read-only memory (ROM) that holds the program
instructions.


IO

Inputs and Outputs:




  • i_Addr (input, 32-bit): Represents the memory address
    from which the instruction should be fetched.


  • i_Ctr (output, 6-bit): Outputs the control bits of the
    fetched instruction (bits [31:26]).


  • i_Funcode (output, 6-bit): Outputs the function code of
    the fetched instruction (bits [5:0]).


  • i_Instruction (output, 32-bit): Outputs the complete
    32-bit fetched instruction.


Functionality


  1. The module defines a parameter SIZE_IM which represents
    the size of the instruction memory.


By default, it is set to 128, meaning the memory can hold 128 32-bit
instructions.



  1. The module declares a register array Imem of size
    SIZE_IM to store the instructions.



  2. In the initial block:



    • The memory is initialized with a default instruction
      (32’b11111100000000000000000000000000) using a loop.

    • The instructions are then loaded from a file named
      “instructions.mem” using the $readmemb system task. This
      file contains the binary representation of the instructions.

    • The i_Instruction output is initialized with the
      default instruction.




  3. The module has an “always” block that is triggered whenever the
    i_Addr input changes:



    • If i_Addr is equal to -4 (used for initialization), the
      i_Instruction output is set to the default
      instruction.

    • Otherwise, the instruction is fetched from the Imem
      array using the address i_Addr shifted right by 2 bits
      (assuming word-aligned addresses).

    • The control bits (i_Ctr) and function code
      (i_Funcode) are extracted from the fetched instruction and
      assigned to the respective outputs.




Processor Context

The following is the context of the Instruction Memory module,
InstructionMemory.v in the single-cycle MIPS processor,
mips.v:



  • The Program Counter (PC) module provides the memory
    address (i_Addr) to the Instruction Memory module to fetch
    the instruction at that address.

  • The fetched instruction (i_Instruction) is then passed
    to other components of the processor, such as the Control Unit and the
    Register File, for further processing and execution.

  • The control bits (i_Ctr) and function code
    (i_Funcode) are used by the Control Unit to generate
    appropriate control signals for the processor’s data-path.


Significance

The Instruction Memory module is a crucial component of the MIPS
processor as it holds the program instructions that the processor
executes.


It provides the instructions to the processor’s data-path, enabling
the processor to perform the desired operations and execute the program
stored in the memory.


Summary

Essentially, the Instruction Memory module in the single-cycle MIPS
processor acts as a read-only memory that stores the program
instructions. It fetches instructions based on the provided memory
address and outputs the complete instruction along with its control bits
and function code for further processing by other components of the
processor.


Verbose Components Code


The following section shows detailed, commented code files for each
of the components of the processor.


It includes detailed code comments to better explain the
functionality and purpose of each component within the actual verilog
code.


Data Memory


The following is the commented code for the Data Memory module in the
MIPS processor called DataMemory.v:


// File: DataMemory.v

// Description: This file contains the data memory module for the MIPS processor.
// Purpose: The data memory stores data values and provides read and write access to the processor.
// It is responsible for handling memory read and write operations based on the control signals
// received from the control unit.
`timescale 1ns / 1ps
module DataMemory (
input i_clk, // Clock input
input [31:0] i_addr, // Address input for memory access
input [31:0] i_wData, // Write data input
input [31:0] i_ALUresult, // ALU result input (used for memory address calculation)
input i_MemWrite, // Control signal for memory write operation
input i_MemRead, // Control signal for memory read operation
input i_MemtoReg, // Control signal for selecting memory or ALU result as the output
output reg [31:0] o_rData // Read data output
);
parameter SIZE_DM = 128; // Size of the data memory (default: 128 * 32 bits)
reg [31:0] Dmem[SIZE_DM-1:0]; // Data memory array
integer i;
// Initialize the data memory
initial begin
// Fill the data memory with zeros
for (i = 0; i < SIZE_DM; i = i + 1) begin
Dmem[i] = 32'b0;
end
end
// Memory read operation
always @(i_addr or i_MemRead or i_MemtoReg or i_ALUresult) begin
if (i_MemRead == 1) begin // If memory read is enabled
if (i_MemtoReg == 1) begin // If MemtoReg is 1, select memory data as output
o_rData = Dmem[i_addr]; // Read data from the memory array
end else begin
o_rData = i_ALUresult; // If MemtoReg is 0, select ALU result as output
end
end else begin
o_rData = i_ALUresult; // If memory read is not enabled, select ALU result as output
end
end
// Memory write operation
always @(posedge i_clk) begin // Triggered on the positive edge of the clock
if (i_MemWrite == 1) begin // If memory write is enabled
Dmem[i_addr] = i_wData; // Write data to the memory array
end
end
endmodule

Interactions with other components: - The DataMemory
module receives the address (i_addr), write data
(i_wData), and control signals (i_MemWrite,
i_MemRead, i_MemtoReg) from the
ControlUnit and ALU modules. - It provides the
read data (o_rData) to the RegisterFile module
for store instructions or to the ALU for load instructions.
- The i_ALUresult input is used as the memory address for
read and write operations. - The i_MemWrite control signal
determines whether a memory write operation should be performed. - The
i_MemRead control signal determines whether a memory read
operation should be performed. - The i_MemtoReg control
signal selects whether the memory data or the ALU result should be
output as the read data.


The DataMemory module is critical for providing data
storage and handling memory read and write operations.


It interacts with the control unit, ALU, and register file to
facilitate data movement and manipulation in the processor.


Instruction Memory


Here the ProgramCounter.v file with detailed code
comments explaining its purpose, functionality, and interactions with
other components in the MIPS processor:


// File: ProgramCounter.v

// Description: This file contains the program counter module for the MIPS processor.
// Purpose: The program counter keeps track of the current instruction address and updates it
// to the next instruction address on each clock cycle. It is responsible for providing
// the address of the instruction to be fetched from the instruction memory.
`timescale 1ns / 1ps
module ProgramCounter (
input i_Clk, // Input clock signal
input [31:0] i_Next, // Input next instruction address
output reg [31:0] o_Out // Output current instruction address
);
// Initialize the program counter
initial begin
o_Out = -4; // Set the initial address to -4 (used for reset or initialization)
end
// Update the program counter on the positive edge of the clock
always @(posedge i_Clk) begin
o_Out = i_Next; // Update the current address with the next address
end
endmodule

Interactions with other components: - The ProgramCounter
module receives the next instruction address (i_Next) from
the NextProgramCounter module. - It provides the current
instruction address (o_Out) to the
InstructionMemory module to fetch the corresponding
instruction. - The ProgramCounter is updated on the
positive edge of the clock signal (i_Clk), which is
typically connected to the global clock signal of the processor.


The ProgramCounter module is a critical component in the
MIPS processor pipeline. It keeps track of the current instruction
address and updates it on each clock cycle to fetch the next
instruction. The program counter ensures the sequential execution of
instructions and enables the processor to navigate through the
program.


Program Counter


Here the ProgramCounter.v file with detailed code
comments explaining its purpose, functionality, and interactions with
other components in the MIPS processor:


// File: ProgramCounter.v

// Description: This file contains the program counter module for the MIPS processor.
// Purpose: The program counter keeps track of the current instruction address and updates it to the next address.
// It is responsible for providing the current instruction address to the instruction memory and updating
// the address based on the next address input.
`timescale 1ns / 1ps
module ProgramCounter (
input i_Clk, // Input clock signal
input [31:0] i_Next, // Input next instruction address
output reg [31:0] o_Out // Output current instruction address
);
// Initialize the program counter
initial begin
o_Out = -4; // Set the initial instruction address to -4
end
// Update the program counter on the positive edge of the clock
always @(posedge i_Clk) begin
o_Out = i_Next; // Update the current instruction address with the next address
end
endmodule

Purpose and Functionality: - The ProgramCounter module
keeps track of the current instruction address in the MIPS processor. -
It is responsible for providing the current instruction address to the
instruction memory (InstructionMemory) for fetching the
corresponding instruction. - The program counter is updated on the
positive edge of the clock signal (i_Clk). - The next
instruction address (i_Next) is provided as an input to the
module, which is used to update the current instruction address
(o_Out) on each clock cycle. - The initial value of the
program counter is set to -4, which represents the initial state before
the first instruction is fetched.


Interactions with other components: - The ProgramCounter
module receives the next instruction address (i_Next) from
the NextProgramCounter module, which calculates the next
address based on the current instruction and control signals. - It
provides the current instruction address (o_Out) to the
InstructionMemory module to fetch the corresponding
instruction. - The ProgramCounter is updated on the
positive edge of the clock signal (i_Clk), which is
typically connected to the main processor clock.


The ProgramCounter module is to manage the flow of
execution by keeping track of the current instruction address.


It ensures that instructions are fetched and executed in the correct
order by updating the address on each clock cycle based on the next
address input provided by the NextProgramCounter
module.


ALU


Here the ALU.v file with detailed code comments
explaining its purpose, functionality, and interactions with other
components in the MIPS processor:


// File: ALU.v

// Description: This file contains the Arithmetic Logic Unit (ALU) module for the MIPS processor.
// Purpose: The ALU performs arithmetic and logic operations based on the ALU control signals.
// It takes two input operands (i_data1 and i_read2/immediate value) and performs the specified operation.
// The ALU also generates a zero flag (o_Zero) to indicate if the result is zero.

`timescale 1ns / 1ps

module ALU (
input [31:0] i_data1, // Input operand 1 (from RegisterFile)
input [31:0] i_read2, // Input operand 2 (from RegisterFile or immediate value)
input [31:0] i_Instruction, // Input instruction (used for sign-extension of immediate value)
input i_ALUSrc, // Control signal to select between i_read2 or immediate value
input [ 3:0] i_ALUcontrol, // Control signal to specify the ALU operation
output reg o_Zero, // Output zero flag (1 if the ALU result is zero, 0 otherwise)
output reg [31:0] o_ALUresult // Output ALU result
);
reg [31:0] data2;
// Determine the second operand based on the ALUSrc control signal
always @(i_ALUSrc, i_read2, i_Instruction) begin
if (i_ALUSrc == 0) begin
data2 = i_read2; // Use i_read2 as the second operand
end else begin
// Sign-extend the immediate value
if (i_Instruction[15] == 1'b0) begin
data2 = {16'b0, i_Instruction[15:0]}; // Zero-extend if the immediate value is positive
end else begin
data2 = {{16{1'b1}}, i_Instruction[15:0]}; // Sign-extend if the immediate value is negative
end
end
end
// Perform the ALU operation based on the ALUcontrol signal
always @(i_data1, data2, i_ALUcontrol) begin
case (i_ALUcontrol)
4'b0000: // AND
o_ALUresult = i_data1 & data2;
4'b0001: // OR
o_ALUresult = i_data1 | data2;
4'b0010: // ADD
o_ALUresult = i_data1 + data2;
4'b0110: // SUB
o_ALUresult = i_data1 - data2;
4'b0111: // SLT (Set Less Than)
o_ALUresult = (i_data1 < data2) ? 1 : 0;
4'b1100: // NOR
o_ALUresult = ~(i_data1 | data2);
default: ;
endcase
// Set the zero flag if the ALU result is zero
o_Zero = (o_ALUresult == 0) ? 1 : 0;
end

endmodule

Interactions with other components: - The ALU module
receives input operands (i_data1 and i_read2)
from the RegisterFile module. - The i_ALUSrc
control signal from the ControlUnit determines whether the
second operand is i_read2 or an immediate value from the
instruction (i_Instruction). - The
i_ALUcontrol signal from the ALUControl module
specifies the ALU operation to be performed. - The ALU
module outputs the result (o_ALUresult) to the
DataMemory and RegisterFile modules for memory
access and register writeback. - The zero flag (o_Zero) is
used by


Control Unit


Certainly! Here’s a detailed explanation of the Verilog module
provided for a Control Unit in a single cycle MIPS processor. Each line
of the module is annotated to explain its function and relevance.


`timescale 1ns / 1ps

// Defines the time unit as 1 nanosecond and the simulation time precision as 1 picosecond.
module ControlUnit (
input [31:0] i_instruction, // 32-bit input for the instruction.
output reg o_RegDst, // Determines if rd (1) or rt (0) should be the destination register.
output reg o_Jump, // Control signal for jumping to an instruction address.
output reg o_Branch, // Control signal for branching (beq).
output reg o_Bne, // Control signal for branching not equal (bne).
output reg o_MemRead, // Enables reading from memory (used by lw).
output reg o_MemtoReg, // Determines if the value should come from memory (1) or ALU (0).
output reg [1:0] o_ALUOp, // Control signal for ALU operation type.
output reg o_MemWrite, // Enables writing to memory (used by sw).
output reg o_ALUSrc, // Determines if the second ALU operand is an immediate (1) or register (0).
output reg o_RegWrite, // Enables writing to the register file.
output reg [6:0] o_seg_first, // Segment display outputs to visually represent instruction types or states.
output reg [6:0] o_seg_second, // Each segment holds a 7-segment representation.
output reg [6:0] o_seg_third,
output reg [6:0] o_seg_fourth,
output reg [6:0] o_seg_fifth
);
initial begin
// Initialize all control signals and display outputs to their default (usually disabled) states.
o_RegDst = 0;
o_Jump = 0;
o_Branch = 0;
o_MemRead = 0;
o_MemtoReg = 0;
o_ALUOp = 2'b00; // Default ALU operation, no operation specified.
o_MemWrite = 0;
o_ALUSrc = 0;
o_RegWrite = 0;
o_seg_first = 7'b1111111; // All segments off (blank).
o_seg_second = 7'b1111111;
o_seg_third = 7'b1111111;
o_seg_fourth = 7'b1111111;
o_seg_fifth = 7'b1111111;
end
always @(i_instruction) begin
// Control logic triggered by any change in the instruction input.
case (i_instruction[31:26]) // Decode the opcode part of the instruction.
6'b000000: begin // ARITHMETIC (R-type instructions)
o_RegDst = 1;
o_ALUSrc = 0;
o_MemtoReg = 0;
o_RegWrite = 1;
o_MemRead = 0;
o_MemWrite = 0;
o_Branch = 0;
o_Bne = 0;
o_ALUOp = 2'b10; // Specific ALU operation for arithmetic.
o_Jump = 0;
// Display setup for ARITHMETIC.
o_seg_first = 7'b0001000; // A
o_seg_second = 7'b1111010; // R
o_seg_third = 7'b1111001; // I
o_seg_fourth = 7'b0001111; // T
o_seg_fifth = 7'b0001001; // H
end
6'b001000: begin // addi
o_RegDst = 0;
o_ALUSrc = 1;
o_MemtoReg = 0;
o_RegWrite = 1;
o_MemRead = 0;
o_MemWrite = 0;
o_Branch = 0;
o_Bne = 0;
o_ALUOp = 2'b00;
o_Jump = 0;
o_seg_first = 7'b0001000; // A
o_seg_second = 7'b1000010; // d
o_seg_third = 7'b1000010; // d
o_seg_fourth = 7'b1001111; // i
o_seg_fifth = 7'b1111111; // Blank
end
6'b001100: begin // andi
o_RegDst = 0;
o_ALUSrc = 1;
o_MemtoReg = 0;
o_RegWrite = 1;
o_MemRead = 0;
o_MemWrite = 0;
o_Branch = 0;
o_Bne = 0;
o_ALUOp = 2'b11;
o_Jump = 0;
o_seg_first = 7'b0001000; // A
o_seg_second = 7'b0101011; // n
o_seg_third = 7'b1000010; // d
o_seg_fourth = 7'b1001111; // i
o_seg_fifth = 7'b1111111; // Blank
end
6'b100011: begin // lw
o_RegDst = 0;
o_ALUSrc = 1;
o_MemtoReg = 1;
o_RegWrite = 1;
o_MemRead = 1;
o_MemWrite = 0;
o_Branch = 0;
o_Bne = 0;
o_ALUOp = 2'b00;
o_Jump = 0;
o_seg_first = 7'b1000111; // L
o_seg_second = 7'b1001001; // w
o_seg_third = 7'b1111111; // Blank
o_seg_fourth = 7'b1111111; // Blank
o_seg_fifth = 7'b1111111; // Blank
end
6'b101011: begin // sw
o_RegDst = 0; // X
o_ALUSrc = 1;
o_MemtoReg = 0; // X
o_RegWrite = 0;
o_MemRead = 0;
o_MemWrite = 1;
o_Branch = 0;
o_Bne = 0;
o_ALUOp = 2'b00;
o_Jump = 0;
o_seg_first = 7'b0010010; // S
o_seg_second = 7'b1001001; // w
o_seg_third = 7'b1111111; // Blank
o_seg_fourth = 7'b1111111; // Blank
o_seg_fifth = 7'b1111111; // Blank
end
6'b000100: begin // beq
o_RegDst = 0; // X
o_ALUSrc = 0;
o_MemtoReg = 0; // X
o_RegWrite = 0;
o_MemRead = 0;
o_MemWrite = 0;
o_Branch = 1;
o_Bne = 0;
o_ALUOp = 2'b01;
o_Jump = 0;
o_seg_first = 7'b1100000; // b
o_seg_second = 7'b0110000; // e
o_seg_third = 7'b0001100; // q
o_seg_fourth = 7'b1111111; // Blank
o_seg_fifth = 7'b1111111; // Blank
end
6'b000101: begin // bne
o_RegDst = 0; // X
o_ALUSrc = 0;
o_MemtoReg = 0; // X
o_RegWrite = 0;
o_MemRead = 0;
o_MemWrite = 0;
o_Branch = 1;
o_Bne = 1;
o_ALUOp = 2'b01;
o_Jump = 0;
o_seg_first = 7'b1100000; // b
o_seg_second = 7'b0101011; // n
o_seg_third = 7'b0110000; // e
o_seg_fourth = 7'b1111111; // Blank
o_seg_fifth = 7'b1111111; // Blank
end
6'b000010: begin // j
o_RegDst = 0; // X
o_ALUSrc = 0;
o_MemtoReg = 0; // X
o_RegWrite = 0;
o_MemRead = 0;
o_MemWrite = 0;
o_Branch = 0;
o_Bne = 0;
o_ALUOp = 2'b01;
o_Jump = 1;
o_seg_first = 7'b1100001; // J
o_seg_second = 7'b1111111; // Blank
o_seg_third = 7'b1111111; // Blank
o_seg_fourth = 7'b1111111; // Blank
o_seg_fifth = 7'b1111111; // Blank
end
default: begin
// Default case sets all outputs to zero or disables them, providing a safe default state.
o_RegDst = 0;
o_ALUSrc = 0;
o_MemtoReg = 0;
o_RegWrite = 0;
o_MemRead = 0;
o_MemWrite = 0;
o_Branch = 0;
o_Bne = 0;
o_ALUOp = 2'b00;
o_Jump = 0;
// Display all segments off for undefined instructions.
o_seg_first = 7'b1111111; // Blank
o_seg_second = 7'b1111111; // Blank
o_seg_third = 7'b1111111; // Blank
o_seg_fourth = 7'b1111111; // Blank
o_seg_fifth = 7'b1111111; // Blank
end
endcase
end

endmodule

This code serves as the control logic for a single cycle MIPS
processor, managing the routing and operations of data based on the
instruction being executed. It adjusts the path and operation of the
data in various parts of the processor according to the opcode of the
instruction, with added visual output for debugging or educational
purposes through a 7-segment display configuration.


Testbench


Here is the mips_tb.v file with detailed code comments
explaining its purpose, functionality, and interactions with other
components in the MIPS processor:


// File: mips_tb.v

// Description: This file contains the testbench for the MIPS processor.
// Purpose: The testbench is used to simulate and verify the functionality of the MIPS processor.
// It instantiates the MIPS processor module, provides clock and reset signals, and
// initializes the data memory and register file. It also displays the output on 7-segment displays.
`timescale 1ns / 1ps
`define CYCLE_TIME 20
module mips_tb;
reg clk; // Clock signal
reg rst; // Reset signal
// Segments for the 7-segment displays
wire [6:0] seg_first, seg_second, seg_third, seg_fourth, seg_fifth;
integer i; // Loop variable
// Generate clock signal
always #(`CYCLE_TIME / 2) clk = ~clk;
// Instantiate the MIPS processor module
mips uut (
.i_Clk(clk),
.i_Rst(rst),
.o_Seg_first(seg_first),
.o_Seg_second(seg_second),
.o_Seg_third(seg_third),
.o_Seg_fourth(seg_fourth),
.o_Seg_fifth(seg_fifth)
);
// Initialize data memory and register file
initial begin
// Initialize data memory
for (i = 0; i < 32; i = i + 1) begin
uut.inst_DataMemory.Dmem[i] = 32'b0;
end
// Initialize register file
for (i = 0; i < 32; i = i + 1) begin
uut.inst_RegisterFile.RegData[i] = 32'b0;
end
clk = 0; // Initialize clock signal
end
initial begin
#1800 $finish;
end
endmodule

Interactions with other components:



  • The mips_tb module instantiates the mips
    module, which represents the MIPS processor.

  • It provides the clock signal (clk) to the
    mips module for synchronization.

  • The reset signal (rst) is not used in this testbench
    but can be used to reset the processor if needed.

  • The testbench initializes the data memory
    (inst_DataMemory.Dmem) and register file
    (inst_RegisterFile.RegData) of the mips module
    to zero.

  • The 7-segment display outputs (seg_first,
    seg_second, seg_third,
    seg_fourth, seg_fifth) from the
    mips module are connected to the testbench for monitoring
    purposes.


The mips_tb module serves as a testbench to simulate and
verify the functionality of the MIPS processor.


It provides the necessary inputs (clock and reset) and initializes
the memory and registers. The testbench can be modified to apply
different test cases through loading different binary converted assembly
files and allows one to monitor the processor’s behavior through the
7-segment display outputs.


Conclusion


Throughout this project, I gained valuable experience in designing and
implementing a processor using Verilog. I further learned about the different
stages of the processor pipeline, including instruction fetch, decode,
execute, memory access, and write-back. I also gained a deeper
understanding of the MIPS instruction set architecture and how
instructions are encoded and executed.


Implementing the processor in Verilog allowed me to apply my
knowledge of digital design and hardware description languages. I
utilized various Verilog constructs, such as modules, always blocks, and
case statements, to model the behavior of the processor components. I
also learned about the importance of proper synchronization and timing
in hardware design.


To ensure the correctness of the processor implementation, I
developed test-benches in Verilog to verify the functionality of
individual components as well as the overall processor. The test-benches
allowed me to simulate the processor’s behavior and debug any issues
that arose during the development process.


In addition to the Verilog implementation, I also compared my experience with writing a similar single-cycle processor
in VHDL. This comparison provided insights into the
differences and similarities between the two hardware description
languages. While Verilog offers a more concise and flexible syntax, VHDL
provides stronger typing and more explicit component instantiation. Both
languages have their strengths and weaknesses, and the choice between
them often depends on the specific project requirements and personal
preference.