Repository: nkanderson/MIPS-lite
Pull request #32: Decode and execute stages working with forwarding
Author: login: reecewayt
Created: 2025-05-18T00:42:08Z | Merged: 2025-05-24T19:38:49Z

Alright, this isn't perfect yet, but I wanted to get it up asap so others could see it since we are all working on the pipeline at this point. Hoping this will help us get to a cohesive structure and such. The current implementation doesn't do forwarding, but I plan to add it soon. 

I've left a few questions in the source code for discussion. Let me know what you all think. 


Diff:
diff --git a/include/functional_simulator.h b/include/functional_simulator.h
index 9e29d9b..ebc884b 100644
--- a/include/functional_simulator.h
+++ b/include/functional_simulator.h
@@ -24,7 +24,7 @@ struct PipelineStageData {
                            ///< to accurately calculate PC relative addressing
     uint32_t rs_value;     ///< Value read from rs register
     uint32_t rt_value;     ///< Value read from rt register
-    uint32_t alu_result;   ///< Result of ALU operation or effective address
+    int32_t alu_result;    ///< Result of ALU operation or effective address (signed)
     uint32_t memory_data;  ///< Data read from memory (if applicable)
     std::optional<uint8_t> dest_reg;  ///< Destination register (if any)
     uint32_t branch_target;           ///< Target address for branch/jump
@@ -53,6 +53,8 @@ struct PipelineStageData {
 
     // Check if stage is a bubble (no instruction)
     bool isEmpty() const { return instruction == nullptr; }
+    int32_t getRsValueSigned() const { return static_cast<int32_t>(rs_value); }
+    int32_t getRtValueSigned() const { return static_cast<int32_t>(rt_value); }
 };
 
 /**
@@ -76,6 +78,7 @@ class FunctionalSimulator {
     FunctionalSimulator(RegisterFile* rf, Stats* st, IMemoryParser* mem,
                         bool enable_forwarding = false);
 
+
     // Getter methods
 
     /**
@@ -106,6 +109,8 @@ class FunctionalSimulator {
      */
     const PipelineStageData* getPipelineStage(int stage) const;
 
+    bool isBranchTaken() const { return branch_taken; }
+
     // Setter methods
 
     /**
@@ -226,24 +231,20 @@ class FunctionalSimulator {
      */
     bool isRegisterWriteInstruction(const Instruction* instr) const;
 
-    /**
-     * @brief Helper method to determine the destination register for an instruction.
-     * @param instr Pointer to the instruction to check.
-     * @return The destination register number, or std::nullopt if none.
-     */
-    std::optional<uint8_t> getDestinationRegister(const Instruction* instr) const;
 
     /**
-     * @brief Helper method to implement forwarding logic.
-     * @param stage Stage index that needs the value.
-     * @param reg_num Register number to forward.
-     * @return Forwarded value if available, otherwise register value.
+     * @brief Helper method to get the value of a register.
+     * @param reg_num Register number to read. Handles forwarding if needed. Note that this
+     * function should only be called when the pipeline is not stalled. If stalled we shouldn't be 
+     * reading any registers.
+     * @return Value of the register.
      */
-    uint32_t getForwardedValue(int stage, uint8_t reg_num);
+    uint32_t readRegisterValue(uint8_t reg_num);
 
-// Allow functional simulator tests access to private class member pipeline
 #ifdef UNIT_TEST
+     // Allow functional simulator tests access to private class member pipeline
    public:
     std::array<std::unique_ptr<PipelineStageData>, NUM_STAGES>& getPipeline() { return pipeline; }
 #endif
+
 };
diff --git a/src/functional_simulator.cpp b/src/functional_simulator.cpp
index f10a59a..9ed18ae 100644
--- a/src/functional_simulator.cpp
+++ b/src/functional_simulator.cpp
@@ -1,10 +1,14 @@
 #include "functional_simulator.h"
 
-#include <cstddef>
 #include <cstdint>
 
+#include <sys/types.h>
+
+#include <optional>
+
 #include "memory_interface.h"
 #include "mips_instruction.h"
+#include "mips_lite_defs.h"
 #include "register_file.h"
 #include "stats.h"
 
@@ -46,12 +50,146 @@ void FunctionalSimulator::instructionFetch() {
 }
 
 void FunctionalSimulator::instructionDecode() {
-    // Stub: Decode the instruction (parse operands, maybe identify hazards)
-    // Set dirty bit on registers which will be written back to later
+    // Operation: Decode instruction and access register file to read the register sources
+    
+    PipelineStageData* id_data = pipeline[PipelineStage::DECODE].get();
+
+    if (id_data == nullptr || stall > 0) {
+        // No instruction to decode
+        return;
+    }
+
+    uint8_t rs = id_data->instruction->getRs();
+    uint8_t rt = id_data->instruction->getRt();
+    
+    id_data->rs_value = readRegisterValue(rs);
+
+    // Determine register Destination & optional second source register
+    if(id_data->instruction->hasRd()) {
+        id_data->dest_reg = id_data->instruction->getRd();
+        //R-type instruction has two source registers, get second source register
+        id_data->rt_value = readRegisterValue(rt);
+    }
+    else if(!id_data->instruction->hasRd() && isRegisterWriteInstruction(id_data->instruction.get())) {
+        // Destination register is rt 
+        id_data->dest_reg = id_data->instruction->getRt();
+    }
+    else {
+        // No destination register
+        id_data->dest_reg = std::nullopt;
+        if(id_data->instruction->getOpcode() == mips_lite::opcode::BEQ) {
+            // BEQ has two source registers
+            id_data->rt_value = readRegisterValue(rt);
+        }
+    }
 }
 
 void FunctionalSimulator::execute() {
-    // Stub: Perform ALU operation specified or calculate branch effective address
+    if(isStageEmpty(PipelineStage::EXECUTE)) {
+        // No instruction to execute
+        return;
+    }
+    PipelineStageData* ex_data = pipeline[PipelineStage::EXECUTE].get(); 
+
+    switch (ex_data->instruction->getOpcode()) {
+        // Arithmetic Operations
+        // Operands are always signed
+        case mips_lite::opcode::ADD:
+            ex_data->alu_result = ex_data->getRsValueSigned() + ex_data->getRtValueSigned();
+            break;
+        case mips_lite::opcode::ADDI:
+            ex_data->alu_result = ex_data->getRsValueSigned() + ex_data->instruction->getImmediate();
+            break;
+
+        case mips_lite::opcode::SUB:
+            ex_data->alu_result = ex_data->getRsValueSigned() - ex_data->getRtValueSigned();
+            break;
+        case mips_lite::opcode::SUBI:
+            ex_data->alu_result = ex_data->getRsValueSigned() - ex_data->instruction->getImmediate();
+            break;
+
+        case mips_lite::opcode::MUL:
+            ex_data->alu_result = ex_data->getRsValueSigned() * ex_data->getRtValueSigned();
+            break;
+        case mips_lite::opcode::MULI:
+            ex_data->alu_result = ex_data->getRsValueSigned() * ex_data->instruction->getImmediate();
+            break;
+
+        // Logical Operations
+        // Sign of operands doesn't matter
+        case mips_lite::opcode::OR:
+            ex_data->alu_result = ex_data->rs_value | ex_data->rt_value;
+            break;
+        case mips_lite::opcode::ORI:
+            ex_data->alu_result = ex_data->rs_value | ex_data->instruction->getImmediate();
+            break;
+
+        case mips_lite::opcode::AND:
+            ex_data->alu_result = ex_data->rs_value & ex_data->rt_value;
+            break;
+        case mips_lite::opcode::ANDI:
+            ex_data->alu_result = ex_data->rs_value & ex_data->instruction->getImmediate();
+            break;
+
+        case mips_lite::opcode::XOR:
+            ex_data->alu_result = ex_data->rs_value ^ ex_data->rt_value;
+            break;
+        case mips_lite::opcode::XORI:
+            ex_data->alu_result = ex_data->rs_value ^ ex_data->instruction->getImmediate();
+            break;
+
+        // Memory Effective Address Calculation -> Load and Store
+        // Effective Address Calculation is signed
+        case mips_lite::opcode::LDW:
+            // Load word - calculate effective address
+            ex_data->alu_result = ex_data->getRsValueSigned() + ex_data->instruction->getImmediate();
+            break;
+
+        case mips_lite::opcode::STW:
+            ex_data->alu_result = ex_data->getRsValueSigned() + ex_data->instruction->getImmediate();
+            break;
+
+        // Control Flow Ops
+        case mips_lite::opcode::BZ:
+            if (ex_data->rs_value == 0) {
+                ex_data->alu_result = ex_data->pc + (ex_data->instruction->getImmediate() * 4);
+                branch_taken = true;  // Indicate that a branch was taken
+                                      // TODO: Controller will need to clear this flag after the branch is taken
+                break;
+            }
+            branch_taken = false;  // No branch taken
+            ex_data->alu_result = ex_data->pc;  // No change (PC should be updated in fetch stage)
+            break;
+
+        case mips_lite::opcode::BEQ:
+            if (ex_data->rs_value == ex_data->rt_value) {
+                // Branch if equal - calculate target address
+                
+                ex_data->alu_result = ex_data->pc + (ex_data->instruction->getImmediate() * 4);
+                branch_taken = true;  // Indicate that a branch was taken
+            } else {
+                // No branch, PC will be updated normally
+                branch_taken = false;  // No branch taken
+                ex_data->alu_result = ex_data->pc;  // No change (PC should be updated in fetch stage)
+            }
+            break;
+
+        case mips_lite::opcode::JR:
+            // Jump register - use the value in Rs as the new PC
+            ex_data->alu_result = ex_data->rs_value;  // Set PC to value in Rs 
+            branch_taken = true;
+            break;
+
+        case mips_lite::opcode::HALT:
+            // Set a flag to indicate program termination
+            // This might require adding a 'halted' flag to your FunctionalSimulator class
+            // For now, we'll just pass the current PC
+            ex_data->alu_result = pc;
+            break;
+
+        default:
+            throw std::invalid_argument("Invalid opcode for execute stage");
+    }
 }
 
 void FunctionalSimulator::memory() {
@@ -160,24 +298,23 @@ bool FunctionalSimulator::isRegisterWriteInstruction(const Instruction* instr) c
            opcode == mips_lite::opcode::LDW;
 }
 
-std::optional<uint8_t> FunctionalSimulator::getDestinationRegister(const Instruction* instr) const {
-    if (instr == nullptr || !isRegisterWriteInstruction(instr)) {
-        return std::nullopt;
+uint32_t FunctionalSimulator::readRegisterValue(uint8_t reg_num) {
+    
+    if (reg_num == 0) {
+        return 0;  // $0 register always returns 0
+    }               // And never has hazards
+    if(stall > 0) {
+        throw std::runtime_error("Stall detected in ID stage but wasn't properly handled by control logic. Should never attempt to read during a stall");
     }
-
-    // R-type instructions write to rd
-    if (instr->getInstructionType() == mips_lite::InstructionType::R_TYPE) {
-        return instr->getRd();
+    // Check EXE stage for hazards
+    if(!isStageEmpty(PipelineStage::EXECUTE) && pipeline[PipelineStage::EXECUTE]->dest_reg.value() == reg_num) {
+        return pipeline[PipelineStage::EXECUTE]->alu_result;
+    }
+    // Check MEM stage for hazards
+    if(!isStageEmpty(PipelineStage::MEMORY) && pipeline[PipelineStage::MEMORY]->dest_reg.value() == reg_num) {
+        return pipeline[PipelineStage::MEMORY]->alu_result;
+    }
+    else {
+        return register_file->read(reg_num);
     }
-
-    // I-type instructions that write to a register use rt as destination
-    return instr->getRt();
-}
-
-uint32_t FunctionalSimulator::getForwardedValue(int stage, uint8_t reg_num) {
-    (void)stage;
-    (void)reg_num;
-    // TODO: Implement forwarding logic
-    // Check if the register is being written in the pipeline stages
-    return 0;
 }
diff --git a/tests/functional_simulator/CMakeLists.txt b/tests/functional_simulator/CMakeLists.txt
index aeace7d..d1cbffb 100644
--- a/tests/functional_simulator/CMakeLists.txt
+++ b/tests/functional_simulator/CMakeLists.txt
@@ -2,8 +2,8 @@
 set(TEST_NAME  functional_simulator_test)
 add_executable(${TEST_NAME} functional_simulator_tests.cpp)
 # add_executable(${TEST_NAME} functional_simulator_tests.cpp functional_simulator_integration_tests.cpp)
-target_compile_definitions(functional_simulator_test PRIVATE UNIT_TEST)
 
+add_compile_definitions(UNIT_TEST)
 # Set C++ standard for the test (redundant but kept for clarity)
 target_compile_features(${TEST_NAME} PRIVATE cxx_std_17)
 
@@ -16,6 +16,30 @@ target_link_libraries(${TEST_NAME}
     mips_lite_lib
 )
 
+### Create tests for decode stage
+set(ID_TEST_NAME decode_stage_tests)
+add_executable(${ID_TEST_NAME} decode_stage_tests.cpp)
+target_link_libraries(${ID_TEST_NAME}
+    PRIVATE
+    gtest
+    gmock
+    gtest_main
+    mips_lite_lib
+)
+
+### Create tests for execute stage
+set(EXE_TEST_NAME exe_stage_tests)
+add_executable(${EXE_TEST_NAME} exe_stage_tests.cpp)
+target_link_libraries(${EXE_TEST_NAME}
+    PRIVATE
+    gtest
+    gmock
+    gtest_main
+    mips_lite_lib
+)
+
 # Register with CTest using Google Test's discovery
 include(GoogleTest)
 gtest_discover_tests(${TEST_NAME})
+gtest_discover_tests(${ID_TEST_NAME})
+gtest_discover_tests(${EXE_TEST_NAME})
diff --git a/tests/functional_simulator/decode_stage_tests.cpp b/tests/functional_simulator/decode_stage_tests.cpp
new file mode 100644
index 0000000..f653dc5
--- /dev/null
+++ b/tests/functional_simulator/decode_stage_tests.cpp
@@ -0,0 +1,225 @@
+/**
+ * @file decode_stage_tests.cpp
+ * @brief Tests for Decode stage of pipeline
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <memory>
+
+#include "functional_simulator.h"
+#include "memory_interface.h"
+#include "mips_instruction.h"
+#include "register_file.h"
+#include "stats.h"
+
+using ::testing::NiceMock;
+
+// Only mock the memory parser
+class MockMemoryParser : public IMemoryParser {
+   public:
+    MOCK_METHOD(uint32_t, readInstruction, (uint32_t address), (override));
+    MOCK_METHOD(uint32_t, readMemory, (uint32_t address), (override));
+    MOCK_METHOD(void, writeMemory, (uint32_t address, uint32_t value), (override));
+};
+
+class DecodeStageTest : public ::testing::Test {
+   protected:
+    RegisterFile rf;
+    Stats stats;
+    NiceMock<MockMemoryParser> mem;
+    std::unique_ptr<FunctionalSimulator> sim;
+
+    // Sample instructions to use in tests
+    // R-type: ADD $rd, $rs, $rt (opcode=0, rs=1, rt=2, rd=3)
+    const uint32_t r_type_add_instr = 0x00221800;  // ADD $3, $1, $2
+    // I-type: ADDI $rt, $rs, immediate (opcode=1, rs=4, rt=5, imm=100)
+    const uint32_t i_type_addi_pos_instr = 0x04850064;  // ADDI $5, $4, 100
+    // I-type with negative immediate: ADDI $rt, $rs, immediate (opcode=1, rs=6, rt=7, imm=-100)
+    const uint32_t i_type_addi_neg_instr = 0x04C7FF9C;  // ADDI $7, $6, -100
+    // I-type memory: LDW $rt, imm($rs) (opcode=12, rs=8, rt=9, imm=200)
+    const uint32_t i_type_ldw_instr = 0x310900C8;  // LDW $9, 200($8)
+    // I-type branch: BEQ $rs, $rt, imm (opcode=15, rs=10, rt=11, imm=-50)
+    const uint32_t i_type_beq_instr = 0x3D4BFFCE;  // BEQ $10, $11, -50
+
+    void SetUp() override {
+        sim = std::make_unique<FunctionalSimulator>(&rf, &stats, &mem, true);
+
+        // Initialize some register values for testing
+        rf.write(1, 100);  // $1 = 100
+        rf.write(2, 200);  // $2 = 200
+        rf.write(3, 300);  // $3 = 300
+        rf.write(4, 400);  // $4 = 400
+        rf.write(5, 500);  // $5 = 500
+        rf.write(6, 600);  // $6 = 600
+        rf.write(7, 700);  // $7 = 700
+        rf.write(8, 800);  // $8 = 800
+        rf.write(9, 900);  // $9 = 900
+        rf.write(10, 1000);  // $10 = 1000
+        rf.write(11, 1100);  // $11 = 1100
+
+        
+    }
+
+    // Helper to properly initialize the decode stage with an instruction
+    void setupDecodeStage(uint32_t instruction_word) {
+        // Create a new instruction object
+        auto instr = std::make_unique<Instruction>(instruction_word);
+
+        // Create pipeline stage data for decode stage
+        auto decode_data = std::make_unique<PipelineStageData>();
+        decode_data->instruction = std::move(instr);
+        decode_data->pc = sim->getPC();  // Use current PC
+
+        // Place the instruction in the decode stage using getPipeline()
+        sim->getPipeline()[FunctionalSimulator::PipelineStage::DECODE] = std::move(decode_data);
+    }
+
+    // Helper to get decode stage data
+    const PipelineStageData* getDecodeStageData() {
+        return sim->getPipelineStage(FunctionalSimulator::PipelineStage::DECODE);
+    }
+
+    // Helper to get execute stage data (after decode processes)
+    const PipelineStageData* getExecuteStageData() {
+        return sim->getPipelineStage(FunctionalSimulator::PipelineStage::EXECUTE);
+    }
+};
+
+// Test R-type instruction decode (ADD) - Most basic case
+// This validates: register reading, destination register setting, instruction decoding
+TEST_F(DecodeStageTest, DecodeBasicRTypeInstruction) {
+    // This instruction reads $1 (rs=1), adds immediate 50, writes result to $3 (rt=3)
+    
+    // Set up the decode stage with our instruction
+    setupDecodeStage(r_type_add_instr);
+
+    // Verify the instruction is in decode stage before processing
+    const PipelineStageData* decode_data = getDecodeStageData();
+    ASSERT_NE(decode_data, nullptr);
+    ASSERT_NE(decode_data->instruction, nullptr);
+
+    // Decode the instruction - this should:
+    // 1. Read Rs register value ($1 = 100)
+    // 2. Read Rt register value ($2 = 200)
+    // 3. Set destination register to Rt (register 3)
+    sim->instructionDecode();
+
+    // Verify the decode stage now has the correct values set
+    EXPECT_EQ(decode_data->rs_value, 100);              // Should have read $1 value
+    EXPECT_EQ(decode_data->rt_value, 200);              // Should have read $2 value
+    EXPECT_TRUE(decode_data->dest_reg.has_value());     // Should have destination register
+    EXPECT_EQ(decode_data->dest_reg.value(), 3);        // Destination should be $3 (rt)
+    
+}
+
+TEST_F(DecodeStageTest, DecodeITypeInstructionWithImmediate) {
+    // Set up the decode stage with our instruction
+    setupDecodeStage(i_type_addi_pos_instr);
+
+    // Decode the instruction
+    sim->instructionDecode();
+
+    // Verify the decode stage now has the correct values set
+    const PipelineStageData* decode_data = getDecodeStageData();
+    ASSERT_NE(decode_data, nullptr);
+    ASSERT_NE(decode_data->instruction, nullptr);
+
+    // This instruction should 
+    // 1. Read Rs register value ($4 = 400)
+    // 2. Set Rt as destination register (register 5)
+
+    EXPECT_EQ(decode_data->rs_value, 400);              // Should have read $4 value
+    EXPECT_TRUE(decode_data->dest_reg.has_value());     // Should have destination register
+    EXPECT_EQ(decode_data->dest_reg.value(), 5);        // Destination should be $5 (rt)
+}
+
+TEST_F(DecodeStageTest, DecodeBEQTypeInstruction) {
+    // Set up the decode stage with our instruction
+    setupDecodeStage(i_type_beq_instr);
+
+    // Decode the instruction
+    sim->instructionDecode();
+
+    // This instruction should
+    // 1. Read Rs register value ($10 = 1000)
+    // 2. Read Rt register value ($11 = 1100)
+    // 3. Set destination register to null (no writeback)
+
+    const PipelineStageData* decode_data = getDecodeStageData();
+    ASSERT_NE(decode_data, nullptr);
+    ASSERT_NE(decode_data->instruction, nullptr);
+    EXPECT_EQ(decode_data->rs_value, 1000);             // Should have read $10 value
+    EXPECT_EQ(decode_data->rt_value, 1100);             // Should have read $11 value
+    EXPECT_FALSE(decode_data->dest_reg.has_value());    // No destination register
+    
+}
+
+TEST_F(DecodeStageTest, ForwardingToRsRegister) {
+    // Set up execute stage with instruction that writes to $1
+    auto execute_data = std::make_unique<PipelineStageData>();
+    execute_data->instruction = std::make_unique<Instruction>(0x00000000);
+    execute_data->pc = sim->getPC();
+    execute_data->alu_result = 1234;
+    execute_data->dest_reg = 1;
+
+    sim->getPipeline()[FunctionalSimulator::PipelineStage::EXECUTE] = std::move(execute_data);
+
+    // Decode instruction that reads $1
+    setupDecodeStage(r_type_add_instr);
+    sim->instructionDecode();
+
+    const PipelineStageData* decode_data = getDecodeStageData();
+    EXPECT_EQ(decode_data->rs_value, 1234);  // Forwarded value
+    EXPECT_EQ(decode_data->rt_value, 200);   // Normal register value
+}
+
+// Test forwarding to Rt register  
+TEST_F(DecodeStageTest, ForwardingToRtRegister) {
+    // Set up execute stage with instruction that writes to $2
+    auto execute_data = std::make_unique<PipelineStageData>();
+    execute_data->instruction = std::make_unique<Instruction>(0x00000000);
+    execute_data->pc = sim->getPC();
+    execute_data->alu_result = 5678;
+    execute_data->dest_reg = 2;
+
+    sim->getPipeline()[FunctionalSimulator::PipelineStage::EXECUTE] = std::move(execute_data);
+
+    // Decode instruction that reads $2
+    setupDecodeStage(r_type_add_instr);
+    sim->instructionDecode();
+
+    const PipelineStageData* decode_data = getDecodeStageData();
+    EXPECT_EQ(decode_data->rs_value, 100);   // Normal register value
+    EXPECT_EQ(decode_data->rt_value, 5678);  // Forwarded value
+}
+
+// No forwarding when register numbers don't match
+TEST_F(DecodeStageTest, NoForwardingWhenRegistersDontMatch) {
+    // Set up execute stage with instruction that writes to $5 (not used in our decode instruction)
+    auto execute_data = std::make_unique<PipelineStageData>();
+    execute_data->instruction = std::make_unique<Instruction>(0x00000000); // Dummy instruction
+    execute_data->pc = sim->getPC();
+    execute_data->alu_result = 9999;  // Some value that shouldn't be forwarded
+    execute_data->dest_reg = 5;       // Writing to register $5
+
+    // Place the instruction in the execute stage
+    sim->getPipeline()[FunctionalSimulator::PipelineStage::EXECUTE] = std::move(execute_data);
+
+    // Set up decode stage with ADD $3, $1, $2 (doesn't use $5)
+    setupDecodeStage(r_type_add_instr);
+
+    // Decode the instruction
+    sim->instructionDecode();
+
+    // Verify NO forwarding occurred
+    const PipelineStageData* decode_data = getDecodeStageData();
+    ASSERT_NE(decode_data, nullptr);
+    ASSERT_NE(decode_data->instruction, nullptr);
+
+    EXPECT_EQ(decode_data->rs_value, 100);  // Normal register file value for $1
+    EXPECT_EQ(decode_data->rt_value, 200);  // Normal register file value for $2
+    EXPECT_TRUE(decode_data->dest_reg.has_value());
+    EXPECT_EQ(decode_data->dest_reg.value(), 3);
+}
diff --git a/tests/functional_simulator/exe_stage_tests.cpp b/tests/functional_simulator/exe_stage_tests.cpp
new file mode 100644
index 0000000..a85a636
--- /dev/null
+++ b/tests/functional_simulator/exe_stage_tests.cpp
@@ -0,0 +1,244 @@
+/**
+ * @file exe_stage_tests.cpp
+ * @brief Tests for Execute stage of pipeline
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <cstdint>
+#include <memory>
+
+#include "functional_simulator.h"
+#include "memory_interface.h"
+#include "mips_instruction.h"
+#include "mips_lite_defs.h"
+#include "register_file.h"
+#include "stats.h"
+
+using ::testing::NiceMock;
+
+// Only mock the memory parser
+class MockMemoryParser : public IMemoryParser {
+   public:
+    MOCK_METHOD(uint32_t, readInstruction, (uint32_t address), (override));
+    MOCK_METHOD(uint32_t, readMemory, (uint32_t address), (override));
+    MOCK_METHOD(void, writeMemory, (uint32_t address, uint32_t value), (override));
+};
+
+class ExecuteStageTest : public ::testing::Test {
+   protected:
+    RegisterFile rf;
+    Stats stats;
+    NiceMock<MockMemoryParser> mem;
+    std::unique_ptr<FunctionalSimulator> sim;
+
+    static constexpr uint32_t ADD_INSTR = 0x00221800;      // ADD $3, $1, $2
+    static constexpr uint32_t SUB_INSTR = 0x08221800;      // SUB $3, $1, $2
+    static constexpr uint32_t MUL_INSTR = 0x10221800;      // MUL $3, $1, $2
+    static constexpr uint32_t AND_INSTR = 0x20221800;      // AND $3, $1, $2
+    static constexpr uint32_t BEQ_INSTR = 0x3C280032;      // BEQ $1, $8, 50
+    static constexpr uint32_t BZ_INSTR = 0x38280032;       // BZ $1, 50
+    static constexpr uint32_t JR_INSTR = 0x40280000;       // JR $5
+    static constexpr uint32_t LDW_INSTR = 0x30480064;      // LDW $8, 100($2)
+
+    void SetUp() override { 
+        sim = std::make_unique<FunctionalSimulator>(&rf, &stats, &mem, true);
+    }
+
+    // Helper to set up execute stage with an instruction and data
+    void setupExecuteStage(uint32_t instruction_word, uint32_t rs_value, uint32_t rt_value, 
+                          std::optional<uint8_t> dest_reg = std::nullopt, uint32_t pc_value = 1000) {
+        // Create instruction
+        auto instr = std::make_unique<Instruction>(instruction_word);
+
+        // Create pipeline stage data for execute stage
+        auto execute_data = std::make_unique<PipelineStageData>();
+        execute_data->instruction = std::move(instr);
+        execute_data->rs_value = rs_value;
+        execute_data->rt_value = rt_value;
+        execute_data->dest_reg = dest_reg;
+        execute_data->pc = pc_value;
+
+        // Place the instruction in the execute stage
+        sim->getPipeline()[FunctionalSimulator::PipelineStage::EXECUTE] = std::move(execute_data);
+    }
+
+    // Helper to get execute stage data
+    const PipelineStageData* getExecuteStageData() {
+        return sim->getPipelineStage(FunctionalSimulator::PipelineStage::EXECUTE);
+    }
+
+    // Helper to get memory stage data (after execute processes)
+    const PipelineStageData* getMemoryStageData() {
+        return sim->getPipelineStage(FunctionalSimulator::PipelineStage::MEMORY);
+    }
+};
+
+// Test ADD instruction execution
+TEST_F(ExecuteStageTest, ExecuteADD) {
+    // Set up execute stage with ADD instruction and test values
+    setupExecuteStage(ADD_INSTR, 10, 20, 3);  // rs=10, rt=20, dest=3
+
+    // Verify instruction is in execute stage
+    const PipelineStageData* execute_data = getExecuteStageData();
+    ASSERT_NE(execute_data, nullptr);
+    ASSERT_NE(execute_data->instruction, nullptr);
+
+    // Execute the instruction
+    sim->execute();
+
+    // Verify the ALU result is correct (10 + 20 = 30)
+    EXPECT_EQ(execute_data->alu_result, 30);
+    EXPECT_TRUE(execute_data->dest_reg.has_value());
+    EXPECT_EQ(execute_data->dest_reg.value(), 3);
+}
+
+// Test ADD instruction but make operands negative
+TEST_F(ExecuteStageTest, ExecuteADDNegative) {
+    // Set up execute stage with ADD instruction and negative values
+    setupExecuteStage(ADD_INSTR, static_cast<uint32_t>(-10), static_cast<uint32_t>(-20), 3);  // rs=-10, rt=-20, dest=3
+
+    // Execute the instruction
+    sim->execute();
+
+    // Verify the ALU result is correct (-10 + -20 = -30)
+    const PipelineStageData* execute_data = getExecuteStageData();
+    EXPECT_EQ(execute_data->alu_result, -30);
+    EXPECT_TRUE(execute_data->dest_reg.has_value());
+    EXPECT_EQ(execute_data->dest_reg.value(), 3);
+}
+
+// Test SUB instruction execution
+TEST_F(ExecuteStageTest, ExecuteSUB) {
+    // Set up execute stage with SUB instruction and test values
+    setupExecuteStage(SUB_INSTR, 30, 12, 3);  // rs=30, rt=12, dest=3
+
+    // Execute the instruction
+    sim->execute();
+
+    // Verify the ALU result is correct (30 - 12 = 18)
+    const PipelineStageData* execute_data = getExecuteStageData();
+    EXPECT_EQ(execute_data->alu_result, 18);
+    EXPECT_TRUE(execute_data->dest_reg.has_value());
+    EXPECT_EQ(execute_data->dest_reg.value(), 3);
+}
+
+// Test MUL instruction execution
+TEST_F(ExecuteStageTest, ExecuteMUL) {
+    // Set up execute stage with MUL instruction and test values
+    setupExecuteStage(MUL_INSTR, 5, 7, 3);  // rs=5, rt=7, dest=3
+
+    // Execute the instruction
+    sim->execute();
+
+    // Verify the ALU result is correct (5 * 7 = 35)
+    const PipelineStageData* execute_data = getExecuteStageData();
+    EXPECT_EQ(execute_data->alu_result, 35);
+    EXPECT_TRUE(execute_data->dest_reg.has_value());
+    EXPECT_EQ(execute_data->dest_reg.value(), 3);
+}
+
+// Test AND instruction execution
+TEST_F(ExecuteStageTest, ExecuteAND) {
+    // Set up execute stage with AND instruction and test values
+    setupExecuteStage(AND_INSTR, 0b1010, 0b1100, 3);  // rs=10, rt=12, dest=3
+
+    // Execute the instruction
+    sim->execute();
+
+    // Verify the ALU result is correct (0b1010 & 0b1100 = 0b1000 = 8)
+    const PipelineStageData* execute_data = getExecuteStageData();
+    EXPECT_EQ(execute_data->alu_result, 8);
+    EXPECT_TRUE(execute_data->dest_reg.has_value());
+    EXPECT_EQ(execute_data->dest_reg.value(), 3);
+}
+
+// Test BEQ instruction execution with branch taken
+TEST_F(ExecuteStageTest, ExecuteBEQTaken) {
+    // Set up execute stage with BEQ instruction and equal values
+    setupExecuteStage(BEQ_INSTR, 25, 25, std::nullopt, 1000);  // rs=25, rt=25, no dest, pc=1000
+
+    // Execute the instruction
+    sim->execute();
+
+    // Verify branch was taken and target address calculated correctly
+    const PipelineStageData* execute_data = getExecuteStageData();
+    EXPECT_EQ(execute_data->alu_result, 1200);  // 1000 + (50*4) = 1200
+    EXPECT_TRUE(sim->isBranchTaken());          // Use getter method
+    EXPECT_FALSE(execute_data->dest_reg.has_value());  // No writeback for branches
+}
+
+// Test BEQ instruction execution with branch not taken
+TEST_F(ExecuteStageTest, ExecuteBEQNotTaken) {
+    // Set up execute stage with BEQ instruction and unequal values
+    setupExecuteStage(BEQ_INSTR, 25, 30, std::nullopt, 1000);  // rs=25, rt=30, no dest, pc=1000
+
+    // Execute the instruction
+    sim->execute();
+
+    // Verify branch was not taken
+    const PipelineStageData* execute_data = getExecuteStageData();
+    EXPECT_FALSE(sim->isBranchTaken());         // Use getter method
+    EXPECT_FALSE(execute_data->dest_reg.has_value());
+}
+
+// Test BZ instruction with branch taken
+TEST_F(ExecuteStageTest, ExecuteBZTaken) {
+    // Set up execute stage with BZ instruction and zero value
+    setupExecuteStage(BZ_INSTR, 0, 0, std::nullopt, 1000);  // rs=0, immediate=50 (offset), no dest, no rt, pc=1000
+
+    // Execute the instruction
+    sim->execute();
+
+    // Verify branch was taken (50*4 + 1000 = 1200)
+    const PipelineStageData* execute_data = getExecuteStageData();
+    EXPECT_EQ(execute_data->alu_result, 1200);
+    EXPECT_TRUE(sim->isBranchTaken());          // Use getter method
+    EXPECT_FALSE(execute_data->dest_reg.has_value());
+}
+
+// Test BZ instruction with branch not taken
+TEST_F(ExecuteStageTest, ExecuteBZNotTaken) {
+    // Set up execute stage with BZ instruction and non-zero value
+    setupExecuteStage(BZ_INSTR, 5, 0, std::nullopt, 1000);  // rs=5 (non-zero), immediate=50, no dest, no rt pc=1000
+
+    // Execute the instruction
+    sim->execute();
+
+    // Verify branch was not taken
+    const PipelineStageData* execute_data = getExecuteStageData();
+    EXPECT_FALSE(sim->isBranchTaken());         // Use getter method
+    EXPECT_FALSE(execute_data->dest_reg.has_value());
+}
+
+// Test JR instruction execution
+TEST_F(ExecuteStageTest, ExecuteJR) {
+    // Set up execute stage with JR instruction
+    setupExecuteStage(JR_INSTR, 2048, 0, std::nullopt);  // rs=2048 (jump target), rt=0, no dest
+
+    // Execute the instruction
+    sim->execute();
+
+    // Verify jump address is correct (should be value in rs)
+    const PipelineStageData* execute_data = getExecuteStageData();
+    EXPECT_EQ(execute_data->alu_result, 2048);
+    EXPECT_TRUE(sim->isBranchTaken());          // Use getter method
+    EXPECT_FALSE(execute_data->dest_reg.has_value());
+}
+
+// Test LDW instruction execution (Load Word)
+TEST_F(ExecuteStageTest, ExecuteLDW) {
+    // Set up execute stage with LDW instruction
+    // Rt is initialized to 0, but is target destination register
+    setupExecuteStage(LDW_INSTR, 1000, 0, 8);  // rt=dest_reg (8), rs= 1000 ,imm=100 (offset)
+
+    // Execute the instruction
+    sim->execute();
+
+    // Verify the effective address is calculated correctly (1000 + 100 = 1100)
+    const PipelineStageData* execute_data = getExecuteStageData();
+    EXPECT_EQ(execute_data->alu_result, 1100);
+    EXPECT_TRUE(execute_data->dest_reg.has_value());
+    EXPECT_EQ(execute_data->dest_reg.value(), 8);
+}
\ No newline at end of file


Reviews:
user: login: reecewayt
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-18T00:43:20Z
user: login: reecewayt
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-18T00:49:01Z
user: login: nkanderson
body: Hey, great work, and thanks for getting this up for discussion. I think the main things for us to decide on are:
- Where instruction pointers live (only in pipeline registers, or only in the pipeline array) and the implications for that decision
- Our hazard check logic
- How and where stalls are inserted

My feeling on the hazard check (and future forwarding logic) is that we can simplify what you've got here if we do the dirty bit thing. I kind of want to think more on the instruction pointers and how stalls are managed, but I'm definitely interested in getting other opinions on the questions I posted.
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-18T23:43:27Z
user: login: reecewayt
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-19T18:10:04Z
user: login: reecewayt
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-19T18:12:34Z
user: login: reecewayt
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-19T18:42:06Z
user: login: reecewayt
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-19T18:52:47Z
user: login: reecewayt
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-19T18:56:14Z
user: login: reecewayt
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-19T19:08:23Z
user: login: reecewayt
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-19T19:13:59Z
user: login: nkanderson
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-19T20:16:33Z
user: login: nkanderson
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-19T20:25:44Z
user: login: nkanderson
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-19T20:28:17Z
user: login: nkanderson
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-19T20:31:17Z
user: login: nkanderson
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-19T20:32:31Z
user: login: tnl3pdx
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-19T20:58:53Z
user: login: tnl3pdx
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-19T20:59:43Z
user: login: reecewayt
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-22T20:21:43Z
user: login: nkanderson
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-22T21:30:21Z
user: login: reecewayt
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-22T21:45:16Z
user: login: nkanderson
body: Hey, looking great! There are a few areas in the tests where comments need cleaning up, but once that's done, I'd say this looks good to go.
state: APPROVED
author_association: OWNER
submitted_at: 2025-05-22T22:56:55Z
user: login: reecewayt
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-23T00:25:07Z
user: login: nkanderson
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-23T01:31:41Z
user: login: reecewayt
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-23T01:31:59Z
user: login: nkanderson
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-23T01:33:54Z
user: login: nkanderson
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-23T01:43:47Z
user: login: reecewayt
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-23T01:54:35Z
user: login: nkanderson
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-23T02:11:58Z
user: login: nkanderson
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-23T02:16:11Z
user: login: reecewayt
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-23T02:17:37Z

Review comments:
diff_hunk: @@ -42,14 +55,198 @@ void FunctionalSimulator::instructionFetch() {
 }
 
 void FunctionalSimulator::instructionDecode(Instruction* instr) {
-    // Stub: Decode the instruction (parse operands, maybe identify hazards)
-    // Set dirty bit on registers which will be written back to later
-    (void)instr;  // Silence unused parameter warning
path: src/functional_simulator.cpp
user: login: reecewayt
body: @nkanderson, @tnl3pdx  Maybe we should consider taking out the instr argument for these pipeline functions since they operate on the instructions already stored in the pipeline registers. 
created_at: 2025-05-18T00:43:19Z
author_association: COLLABORATOR
diff_hunk: @@ -16,6 +21,14 @@ FunctionalSimulator::FunctionalSimulator(RegisterFile* rf, Stats* st, IMemoryPar
     for (auto& stage : pipeline) {
path: src/functional_simulator.cpp
user: login: reecewayt
body: @nkanderson, @tnl3pdx with my changes, do you think we need the pipeline array since I've added the instruction data class to the reg type container? 
created_at: 2025-05-18T00:49:01Z
author_association: COLLABORATOR
diff_hunk: @@ -42,14 +55,198 @@ void FunctionalSimulator::instructionFetch() {
 }
 
 void FunctionalSimulator::instructionDecode(Instruction* instr) {
-    // Stub: Decode the instruction (parse operands, maybe identify hazards)
-    // Set dirty bit on registers which will be written back to later
-    (void)instr;  // Silence unused parameter warning
+    // Operation: Decode instruction and access register file to read the register sources
+    // Store in temporary A and B registers
+    // Checks Stall conditions
+    (void)instr;  // TODO: Probably remove this
+
+    // TODO: Implement forwarding logic but raise error for now
+    if (forward) {
+        throw std::runtime_error("Forwarding not implemented yet");
+    }
+
+    PipelineData data = ifid_reg.current();
+    if (stall > 0) {
+        stall--;
+        // Bubble insert
+        data.instr = nullptr;  // Set instruction to null
+        data.reg_a = 0;
+        data.reg_b = 0;
+        data.result = 0;
+        data.wb_reg = 0;
+        idex_reg.setNext(data);
+        return;
+    }
+
+    // Identify source registers
+    uint8_t rs = data.instr->getRs();
+    uint8_t rt = data.instr->getRt();
+    bool rs_hazard = false;
+    bool rt_hazard = false;
+
+    // TODO: Consider putting this in a separate function
+    // TODO: Must consider all cases of hazards even with forwarding
+    // Note: Need to consider data hazards on $0 which will always = 0, hence is not
+    // a true dependency and needs to be ignored.
+    // TODO: Load instructions still result in a one cycle stall even with forwarding
+    // be sure to check this when implmenting forwarding
+    // Check EX/MEM pipeline register for hazards
+    if (rs != 0) {
+        if (exmem_reg.current().instr && exmem_reg.current().wb_reg.has_value()) {
+            if (rs == exmem_reg.current().wb_reg.value()) rs_hazard = true;
+            if (rt == exmem_reg.current().wb_reg.value()) rt_hazard = true;
+            if ((rs_hazard || rt_hazard) && !forward) setStall(2);
+            return;
+        }
+    }
+    // Check MEM/WB pipeline register for hazards
+    else if (rt != 0) {
+        if (memwb_reg.current().instr && memwb_reg.current().wb_reg.has_value()) {
+            if (rs == memwb_reg.current().wb_reg.value()) rs_hazard = true;
+            if (rt == memwb_reg.current().wb_reg.value()) rt_hazard = true;
+            if ((rs_hazard || rt_hazard) && !forward) setStall(1);
+            return;
+        }
+    }
+
+    // No hazards or forwarding is ON, continue with decode
+    data.reg_a = register_file->read(rs);
+    data.branch_taken = std::nullopt;
+
+    uint8_t opcode = data.instr->getOpcode();
+    // Determine what to put in reg_b (i.e. ALU Source 2) and wb_reg based on instruction type
+    if (mips_lite::is_branch_instruction(opcode)) {
+        // Branch instructions
+        data.wb_reg = std::nullopt;  // No write-back for branches
+        data.branch_taken = false;   // Initialize branch flag
+
+        if (opcode == mips_lite::opcode::BEQ) {
+            // BEQ needs rt value for comparison
+            data.reg_b = register_file->read(rt);
+        } else {
+            // BZ uses immediate for branch offset
+            data.reg_b = data.instr->getImmediate();
+        }
+    } else if (mips_lite::is_jump_instruction(opcode)) {
+        // Jump instructions
+        data.wb_reg = std::nullopt;  // No write-back for jumps
+        data.branch_taken = false;   // Initialize branch flag
+
+        // JR uses rs as jump target
+        // No need to set reg_b for JR
+    } else if (opcode == mips_lite::opcode::STW) {
+        // Store instructions
+        data.wb_reg = std::nullopt;               // No write-back for stores
+        data.reg_b = data.instr->getImmediate();  // Offset
+        data.str_val = register_file->read(rt);   // Value to store
+    } else if (data.instr->hasRd()) {
+        // R-type instructions (register-register)
+        data.reg_b = register_file->read(rt);
+        data.wb_reg = data.instr->getRd();
+    } else if (data.instr->hasImmediate()) {
+        // I-type instructions (register-immediate)
+        data.reg_b = data.instr->getImmediate();
+        data.wb_reg = data.instr->getRt();
+    } else {
+        throw std::invalid_argument("Invalid instruction type for decode stage");
+    }
+
+    // Pass data to ID/EX pipeline register
+    idex_reg.setNext(data);
 }
 
 void FunctionalSimulator::execute(Instruction* instr) {
-    // Stub: Perform ALU operation specified or calculate branch effective address
-    (void)instr;
+    (void)instr;  // TODO: Probably remove this
+    PipelineData data = idex_reg.current();
path: src/functional_simulator.cpp
user: login: nkanderson
body: ~~Similar question as above, regarding this being a pointer.~~ This got placed out of order - see question *below* on this pointer question.
created_at: 2025-05-18T14:31:24Z
author_association: OWNER
diff_hunk: @@ -42,14 +55,198 @@ void FunctionalSimulator::instructionFetch() {
 }
 
 void FunctionalSimulator::instructionDecode(Instruction* instr) {
-    // Stub: Decode the instruction (parse operands, maybe identify hazards)
-    // Set dirty bit on registers which will be written back to later
-    (void)instr;  // Silence unused parameter warning
+    // Operation: Decode instruction and access register file to read the register sources
+    // Store in temporary A and B registers
+    // Checks Stall conditions
+    (void)instr;  // TODO: Probably remove this
+
+    // TODO: Implement forwarding logic but raise error for now
+    if (forward) {
+        throw std::runtime_error("Forwarding not implemented yet");
+    }
path: src/functional_simulator.cpp
user: login: nkanderson
body: So is the conditional here something like "this current instruction needs forwarded data", in which case it would then search the other pipeline registers for what it needs?
created_at: 2025-05-18T22:40:43Z
author_association: OWNER
diff_hunk: @@ -42,14 +55,198 @@ void FunctionalSimulator::instructionFetch() {
 }
 
 void FunctionalSimulator::instructionDecode(Instruction* instr) {
-    // Stub: Decode the instruction (parse operands, maybe identify hazards)
-    // Set dirty bit on registers which will be written back to later
-    (void)instr;  // Silence unused parameter warning
+    // Operation: Decode instruction and access register file to read the register sources
+    // Store in temporary A and B registers
+    // Checks Stall conditions
+    (void)instr;  // TODO: Probably remove this
+
+    // TODO: Implement forwarding logic but raise error for now
+    if (forward) {
+        throw std::runtime_error("Forwarding not implemented yet");
+    }
+
+    PipelineData data = ifid_reg.current();
path: src/functional_simulator.cpp
user: login: nkanderson
body: ~~Since this returns a pointer to `current_value`, does the code following then update `current_value` directly? It seems like we'd need to copy the `ifid_reg` `PipelineData` struct and modify that.~~ Ok, I think I was looking at these out of order and got mixed up. This is where the `ifid_reg` current value can be used and modified. Sorry for the mixup!
created_at: 2025-05-18T22:43:01Z
author_association: OWNER
diff_hunk: @@ -42,14 +55,198 @@ void FunctionalSimulator::instructionFetch() {
 }
 
 void FunctionalSimulator::instructionDecode(Instruction* instr) {
-    // Stub: Decode the instruction (parse operands, maybe identify hazards)
-    // Set dirty bit on registers which will be written back to later
-    (void)instr;  // Silence unused parameter warning
+    // Operation: Decode instruction and access register file to read the register sources
+    // Store in temporary A and B registers
+    // Checks Stall conditions
+    (void)instr;  // TODO: Probably remove this
+
+    // TODO: Implement forwarding logic but raise error for now
+    if (forward) {
+        throw std::runtime_error("Forwarding not implemented yet");
+    }
+
+    PipelineData data = ifid_reg.current();
+    if (stall > 0) {
+        stall--;
+        // Bubble insert
+        data.instr = nullptr;  // Set instruction to null
+        data.reg_a = 0;
+        data.reg_b = 0;
+        data.result = 0;
+        data.wb_reg = 0;
+        idex_reg.setNext(data);
+        return;
+    }
path: src/functional_simulator.cpp
user: login: nkanderson
body: I think we do need to insert the bubble here as you're doing, but we'll also need to insert one that would end up in `ifid_reg`, right? If that's the case, we may want to have that control outside of these specific methods, maybe something the functional simulator would do prior to calling this method. Or maybe if there are stall cycles, the functional simulator doesn't call instructionFetch or instructionDecode at all for that cycle. What do you think? Would that make sense?
created_at: 2025-05-18T22:48:13Z
author_association: OWNER
diff_hunk: @@ -42,14 +55,198 @@ void FunctionalSimulator::instructionFetch() {
 }
 
 void FunctionalSimulator::instructionDecode(Instruction* instr) {
-    // Stub: Decode the instruction (parse operands, maybe identify hazards)
-    // Set dirty bit on registers which will be written back to later
-    (void)instr;  // Silence unused parameter warning
+    // Operation: Decode instruction and access register file to read the register sources
+    // Store in temporary A and B registers
+    // Checks Stall conditions
+    (void)instr;  // TODO: Probably remove this
+
+    // TODO: Implement forwarding logic but raise error for now
+    if (forward) {
+        throw std::runtime_error("Forwarding not implemented yet");
+    }
+
+    PipelineData data = ifid_reg.current();
+    if (stall > 0) {
+        stall--;
+        // Bubble insert
+        data.instr = nullptr;  // Set instruction to null
+        data.reg_a = 0;
+        data.reg_b = 0;
+        data.result = 0;
+        data.wb_reg = 0;
+        idex_reg.setNext(data);
+        return;
+    }
+
+    // Identify source registers
+    uint8_t rs = data.instr->getRs();
+    uint8_t rt = data.instr->getRt();
+    bool rs_hazard = false;
+    bool rt_hazard = false;
+
+    // TODO: Consider putting this in a separate function
+    // TODO: Must consider all cases of hazards even with forwarding
+    // Note: Need to consider data hazards on $0 which will always = 0, hence is not
+    // a true dependency and needs to be ignored.
+    // TODO: Load instructions still result in a one cycle stall even with forwarding
+    // be sure to check this when implmenting forwarding
path: src/functional_simulator.cpp
user: login: nkanderson
body: So if we have a dirty bit, the instruction in this decode stage can just keep checking the downstream pipeline registers for the data it needs until it's present, or until the dirty bit is cleared. If no forwarding is enabled, it would be the dirty bit check only. That way I don't think we need to approach load instructions as a special case, because it's the same mechanism either way. And then that would mean we don't need the checks you have below.
created_at: 2025-05-18T22:52:23Z
author_association: OWNER
diff_hunk: @@ -42,14 +55,198 @@ void FunctionalSimulator::instructionFetch() {
 }
 
 void FunctionalSimulator::instructionDecode(Instruction* instr) {
-    // Stub: Decode the instruction (parse operands, maybe identify hazards)
-    // Set dirty bit on registers which will be written back to later
-    (void)instr;  // Silence unused parameter warning
+    // Operation: Decode instruction and access register file to read the register sources
+    // Store in temporary A and B registers
+    // Checks Stall conditions
+    (void)instr;  // TODO: Probably remove this
+
+    // TODO: Implement forwarding logic but raise error for now
+    if (forward) {
+        throw std::runtime_error("Forwarding not implemented yet");
+    }
+
+    PipelineData data = ifid_reg.current();
+    if (stall > 0) {
+        stall--;
+        // Bubble insert
+        data.instr = nullptr;  // Set instruction to null
+        data.reg_a = 0;
+        data.reg_b = 0;
+        data.result = 0;
+        data.wb_reg = 0;
+        idex_reg.setNext(data);
+        return;
+    }
+
+    // Identify source registers
+    uint8_t rs = data.instr->getRs();
+    uint8_t rt = data.instr->getRt();
+    bool rs_hazard = false;
+    bool rt_hazard = false;
+
+    // TODO: Consider putting this in a separate function
+    // TODO: Must consider all cases of hazards even with forwarding
+    // Note: Need to consider data hazards on $0 which will always = 0, hence is not
+    // a true dependency and needs to be ignored.
+    // TODO: Load instructions still result in a one cycle stall even with forwarding
+    // be sure to check this when implmenting forwarding
+    // Check EX/MEM pipeline register for hazards
+    if (rs != 0) {
+        if (exmem_reg.current().instr && exmem_reg.current().wb_reg.has_value()) {
+            if (rs == exmem_reg.current().wb_reg.value()) rs_hazard = true;
+            if (rt == exmem_reg.current().wb_reg.value()) rt_hazard = true;
+            if ((rs_hazard || rt_hazard) && !forward) setStall(2);
+            return;
+        }
+    }
+    // Check MEM/WB pipeline register for hazards
+    else if (rt != 0) {
+        if (memwb_reg.current().instr && memwb_reg.current().wb_reg.has_value()) {
+            if (rs == memwb_reg.current().wb_reg.value()) rs_hazard = true;
+            if (rt == memwb_reg.current().wb_reg.value()) rt_hazard = true;
+            if ((rs_hazard || rt_hazard) && !forward) setStall(1);
+            return;
+        }
+    }
path: src/functional_simulator.cpp
user: login: nkanderson
body: With this approach, would we also need to check `idex_reg.current().wb_reg.value()`?
created_at: 2025-05-18T23:01:29Z
author_association: OWNER
diff_hunk: @@ -42,14 +55,198 @@ void FunctionalSimulator::instructionFetch() {
 }
 
 void FunctionalSimulator::instructionDecode(Instruction* instr) {
-    // Stub: Decode the instruction (parse operands, maybe identify hazards)
-    // Set dirty bit on registers which will be written back to later
-    (void)instr;  // Silence unused parameter warning
+    // Operation: Decode instruction and access register file to read the register sources
+    // Store in temporary A and B registers
+    // Checks Stall conditions
+    (void)instr;  // TODO: Probably remove this
+
+    // TODO: Implement forwarding logic but raise error for now
+    if (forward) {
+        throw std::runtime_error("Forwarding not implemented yet");
+    }
+
+    PipelineData data = ifid_reg.current();
+    if (stall > 0) {
+        stall--;
+        // Bubble insert
+        data.instr = nullptr;  // Set instruction to null
+        data.reg_a = 0;
+        data.reg_b = 0;
+        data.result = 0;
+        data.wb_reg = 0;
+        idex_reg.setNext(data);
+        return;
+    }
+
+    // Identify source registers
+    uint8_t rs = data.instr->getRs();
+    uint8_t rt = data.instr->getRt();
+    bool rs_hazard = false;
+    bool rt_hazard = false;
+
+    // TODO: Consider putting this in a separate function
+    // TODO: Must consider all cases of hazards even with forwarding
+    // Note: Need to consider data hazards on $0 which will always = 0, hence is not
+    // a true dependency and needs to be ignored.
+    // TODO: Load instructions still result in a one cycle stall even with forwarding
+    // be sure to check this when implmenting forwarding
+    // Check EX/MEM pipeline register for hazards
+    if (rs != 0) {
+        if (exmem_reg.current().instr && exmem_reg.current().wb_reg.has_value()) {
+            if (rs == exmem_reg.current().wb_reg.value()) rs_hazard = true;
+            if (rt == exmem_reg.current().wb_reg.value()) rt_hazard = true;
+            if ((rs_hazard || rt_hazard) && !forward) setStall(2);
+            return;
+        }
+    }
+    // Check MEM/WB pipeline register for hazards
+    else if (rt != 0) {
+        if (memwb_reg.current().instr && memwb_reg.current().wb_reg.has_value()) {
+            if (rs == memwb_reg.current().wb_reg.value()) rs_hazard = true;
+            if (rt == memwb_reg.current().wb_reg.value()) rt_hazard = true;
+            if ((rs_hazard || rt_hazard) && !forward) setStall(1);
+            return;
+        }
+    }
+
+    // No hazards or forwarding is ON, continue with decode
+    data.reg_a = register_file->read(rs);
+    data.branch_taken = std::nullopt;
+
+    uint8_t opcode = data.instr->getOpcode();
+    // Determine what to put in reg_b (i.e. ALU Source 2) and wb_reg based on instruction type
+    if (mips_lite::is_branch_instruction(opcode)) {
+        // Branch instructions
+        data.wb_reg = std::nullopt;  // No write-back for branches
+        data.branch_taken = false;   // Initialize branch flag
+
+        if (opcode == mips_lite::opcode::BEQ) {
+            // BEQ needs rt value for comparison
+            data.reg_b = register_file->read(rt);
+        } else {
+            // BZ uses immediate for branch offset
+            data.reg_b = data.instr->getImmediate();
+        }
+    } else if (mips_lite::is_jump_instruction(opcode)) {
+        // Jump instructions
+        data.wb_reg = std::nullopt;  // No write-back for jumps
+        data.branch_taken = false;   // Initialize branch flag
+
+        // JR uses rs as jump target
+        // No need to set reg_b for JR
+    } else if (opcode == mips_lite::opcode::STW) {
+        // Store instructions
+        data.wb_reg = std::nullopt;               // No write-back for stores
+        data.reg_b = data.instr->getImmediate();  // Offset
+        data.str_val = register_file->read(rt);   // Value to store
+    } else if (data.instr->hasRd()) {
+        // R-type instructions (register-register)
+        data.reg_b = register_file->read(rt);
+        data.wb_reg = data.instr->getRd();
+    } else if (data.instr->hasImmediate()) {
+        // I-type instructions (register-immediate)
+        data.reg_b = data.instr->getImmediate();
+        data.wb_reg = data.instr->getRt();
+    } else {
+        throw std::invalid_argument("Invalid instruction type for decode stage");
+    }
+
+    // Pass data to ID/EX pipeline register
+    idex_reg.setNext(data);
 }
 
 void FunctionalSimulator::execute(Instruction* instr) {
-    // Stub: Perform ALU operation specified or calculate branch effective address
-    (void)instr;
+    (void)instr;  // TODO: Probably remove this
+    PipelineData data = idex_reg.current();
+    if (data.instr == nullptr) {
+        exmem_reg.setNext(data);
+        return;
+    }
+
+    switch (data.instr->getOpcode()) {
+        // Arithmetic Operations
+        case mips_lite::opcode::ADD:
+        case mips_lite::opcode::ADDI:
+            data.result = data.reg_a + data.reg_b;
+            break;
+
+        case mips_lite::opcode::SUB:
+        case mips_lite::opcode::SUBI:
+            data.result = data.reg_a - data.reg_b;
+            break;
+
+        case mips_lite::opcode::MUL:
+        case mips_lite::opcode::MULI:
+            data.result = data.reg_a * data.reg_b;
+            break;
+
+        // Logical Operations
+        case mips_lite::opcode::OR:
+        case mips_lite::opcode::ORI:
+            data.result = data.reg_a | data.reg_b;
+            break;
+
+        case mips_lite::opcode::AND:
+        case mips_lite::opcode::ANDI:
+            data.result = data.reg_a & data.reg_b;
+            break;
+
+        case mips_lite::opcode::XOR:
+        case mips_lite::opcode::XORI:
+            data.result = data.reg_a ^ data.reg_b;
+            break;
+
+        // Memory Effective Address Calculation -> Load and Store
+        case mips_lite::opcode::LDW:
+            data.result = data.reg_a + data.reg_b;
+            break;
+
+        case mips_lite::opcode::STW:
+            data.result = data.reg_a + data.reg_b;
+            break;
+
+        // Control Flow Ops
+        case mips_lite::opcode::BZ:
+            if (data.reg_a == 0) {
+                data.result = pc + (data.reg_b * 4);
+                data.branch_taken = true;  // Indicate that a branch was taken
+                break;
+            }
+            data.branch_taken = false;
+            break;
+
+        case mips_lite::opcode::BEQ:
+            if (data.reg_a == data.reg_b) {
+                // Branch if equal - calculate target address
+                int32_t offset = data.instr->getImmediate();
+                data.result = pc + (offset * 4);
+                data.branch_taken = true;  // Indicate that a branch was taken
path: src/functional_simulator.cpp
user: login: nkanderson
body: Do you remember, is this new PC the value that should be used for the very next fetch, or for the one after that?
created_at: 2025-05-18T23:10:37Z
author_association: OWNER
diff_hunk: @@ -42,14 +55,198 @@ void FunctionalSimulator::instructionFetch() {
 }
 
 void FunctionalSimulator::instructionDecode(Instruction* instr) {
-    // Stub: Decode the instruction (parse operands, maybe identify hazards)
-    // Set dirty bit on registers which will be written back to later
-    (void)instr;  // Silence unused parameter warning
+    // Operation: Decode instruction and access register file to read the register sources
+    // Store in temporary A and B registers
+    // Checks Stall conditions
+    (void)instr;  // TODO: Probably remove this
+
+    // TODO: Implement forwarding logic but raise error for now
+    if (forward) {
+        throw std::runtime_error("Forwarding not implemented yet");
+    }
+
+    PipelineData data = ifid_reg.current();
+    if (stall > 0) {
+        stall--;
+        // Bubble insert
+        data.instr = nullptr;  // Set instruction to null
+        data.reg_a = 0;
+        data.reg_b = 0;
+        data.result = 0;
+        data.wb_reg = 0;
+        idex_reg.setNext(data);
+        return;
+    }
+
+    // Identify source registers
+    uint8_t rs = data.instr->getRs();
+    uint8_t rt = data.instr->getRt();
+    bool rs_hazard = false;
+    bool rt_hazard = false;
+
+    // TODO: Consider putting this in a separate function
+    // TODO: Must consider all cases of hazards even with forwarding
+    // Note: Need to consider data hazards on $0 which will always = 0, hence is not
+    // a true dependency and needs to be ignored.
+    // TODO: Load instructions still result in a one cycle stall even with forwarding
+    // be sure to check this when implmenting forwarding
+    // Check EX/MEM pipeline register for hazards
+    if (rs != 0) {
+        if (exmem_reg.current().instr && exmem_reg.current().wb_reg.has_value()) {
+            if (rs == exmem_reg.current().wb_reg.value()) rs_hazard = true;
+            if (rt == exmem_reg.current().wb_reg.value()) rt_hazard = true;
+            if ((rs_hazard || rt_hazard) && !forward) setStall(2);
+            return;
+        }
+    }
+    // Check MEM/WB pipeline register for hazards
+    else if (rt != 0) {
+        if (memwb_reg.current().instr && memwb_reg.current().wb_reg.has_value()) {
+            if (rs == memwb_reg.current().wb_reg.value()) rs_hazard = true;
+            if (rt == memwb_reg.current().wb_reg.value()) rt_hazard = true;
+            if ((rs_hazard || rt_hazard) && !forward) setStall(1);
+            return;
+        }
+    }
+
+    // No hazards or forwarding is ON, continue with decode
+    data.reg_a = register_file->read(rs);
+    data.branch_taken = std::nullopt;
+
+    uint8_t opcode = data.instr->getOpcode();
+    // Determine what to put in reg_b (i.e. ALU Source 2) and wb_reg based on instruction type
+    if (mips_lite::is_branch_instruction(opcode)) {
+        // Branch instructions
+        data.wb_reg = std::nullopt;  // No write-back for branches
+        data.branch_taken = false;   // Initialize branch flag
+
+        if (opcode == mips_lite::opcode::BEQ) {
+            // BEQ needs rt value for comparison
+            data.reg_b = register_file->read(rt);
+        } else {
+            // BZ uses immediate for branch offset
+            data.reg_b = data.instr->getImmediate();
+        }
+    } else if (mips_lite::is_jump_instruction(opcode)) {
+        // Jump instructions
+        data.wb_reg = std::nullopt;  // No write-back for jumps
+        data.branch_taken = false;   // Initialize branch flag
+
+        // JR uses rs as jump target
+        // No need to set reg_b for JR
+    } else if (opcode == mips_lite::opcode::STW) {
+        // Store instructions
+        data.wb_reg = std::nullopt;               // No write-back for stores
+        data.reg_b = data.instr->getImmediate();  // Offset
+        data.str_val = register_file->read(rt);   // Value to store
+    } else if (data.instr->hasRd()) {
+        // R-type instructions (register-register)
+        data.reg_b = register_file->read(rt);
+        data.wb_reg = data.instr->getRd();
+    } else if (data.instr->hasImmediate()) {
+        // I-type instructions (register-immediate)
+        data.reg_b = data.instr->getImmediate();
+        data.wb_reg = data.instr->getRt();
+    } else {
+        throw std::invalid_argument("Invalid instruction type for decode stage");
+    }
+
+    // Pass data to ID/EX pipeline register
+    idex_reg.setNext(data);
 }
 
 void FunctionalSimulator::execute(Instruction* instr) {
-    // Stub: Perform ALU operation specified or calculate branch effective address
-    (void)instr;
+    (void)instr;  // TODO: Probably remove this
+    PipelineData data = idex_reg.current();
+    if (data.instr == nullptr) {
+        exmem_reg.setNext(data);
+        return;
+    }
+
+    switch (data.instr->getOpcode()) {
+        // Arithmetic Operations
+        case mips_lite::opcode::ADD:
+        case mips_lite::opcode::ADDI:
+            data.result = data.reg_a + data.reg_b;
+            break;
+
+        case mips_lite::opcode::SUB:
+        case mips_lite::opcode::SUBI:
+            data.result = data.reg_a - data.reg_b;
+            break;
+
+        case mips_lite::opcode::MUL:
+        case mips_lite::opcode::MULI:
+            data.result = data.reg_a * data.reg_b;
+            break;
+
+        // Logical Operations
+        case mips_lite::opcode::OR:
+        case mips_lite::opcode::ORI:
+            data.result = data.reg_a | data.reg_b;
+            break;
+
+        case mips_lite::opcode::AND:
+        case mips_lite::opcode::ANDI:
+            data.result = data.reg_a & data.reg_b;
+            break;
+
+        case mips_lite::opcode::XOR:
+        case mips_lite::opcode::XORI:
+            data.result = data.reg_a ^ data.reg_b;
+            break;
+
+        // Memory Effective Address Calculation -> Load and Store
+        case mips_lite::opcode::LDW:
+            data.result = data.reg_a + data.reg_b;
+            break;
+
+        case mips_lite::opcode::STW:
+            data.result = data.reg_a + data.reg_b;
+            break;
+
+        // Control Flow Ops
+        case mips_lite::opcode::BZ:
+            if (data.reg_a == 0) {
+                data.result = pc + (data.reg_b * 4);
+                data.branch_taken = true;  // Indicate that a branch was taken
+                break;
+            }
+            data.branch_taken = false;
+            break;
+
+        case mips_lite::opcode::BEQ:
+            if (data.reg_a == data.reg_b) {
+                // Branch if equal - calculate target address
+                int32_t offset = data.instr->getImmediate();
+                data.result = pc + (offset * 4);
+                data.branch_taken = true;  // Indicate that a branch was taken
+            } else {
+                // No branch, PC will be updated normally
+                data.branch_taken = false;
+                data.result = pc;  // No change (PC should be updated in fetch stage)
+            }
+            break;
+
+        case mips_lite::opcode::JR:
+            // Jump register - use the value in Rs as the new PC
+            data.result = data.reg_a;
path: src/functional_simulator.cpp
user: login: nkanderson
body: Is this the case where the new PC needs to take the top 4 bits of the current PC and also shift left the immediate value by 2 bits?
created_at: 2025-05-18T23:17:38Z
author_association: OWNER
diff_hunk: @@ -16,6 +21,14 @@ FunctionalSimulator::FunctionalSimulator(RegisterFile* rf, Stats* st, IMemoryPar
     for (auto& stage : pipeline) {
path: src/functional_simulator.cpp
user: login: nkanderson
body: This is a good question, and I agree that we probably don't need both. I'm not sure I totally understand the need to move each actual instruction through the pipeline registers though. Nothing should change between stages on the instruction itself right?

I think one benefit to the pipeline array is that it allows the functional simulator to have access to the instruction pointers for each stage directly, so if it needs to flush, it can do that it a pretty simple manner. It probably does need to clear out the associated pipeline registers though too, so I sort of see a case for just having them there.

If the instructions were all just in pipeline registers, in the case of a branch instruction, we'd need to flush `ifid_reg.next` and `ifid_reg.current`, right? I'm wondering where that logic would live. I think since we're not currently using `new`, having the instruction that just completed the writeback simply be overwritten with the next one is probably ok, but we should be aware if we start allocating memory on the heap that we'd need explicit deletion.

I generally gravitate towards having the control of the overall pipeline in the hands of the functional simulator instead of interweaving it into the methods. But I'm interested in what you all think about the pros and cons of the different options, or if there's anything I'm missing about why we'd need the instructions in the pipeline registers.
created_at: 2025-05-18T23:30:58Z
author_association: OWNER
diff_hunk: @@ -42,14 +55,198 @@ void FunctionalSimulator::instructionFetch() {
 }
 
 void FunctionalSimulator::instructionDecode(Instruction* instr) {
-    // Stub: Decode the instruction (parse operands, maybe identify hazards)
-    // Set dirty bit on registers which will be written back to later
-    (void)instr;  // Silence unused parameter warning
+    // Operation: Decode instruction and access register file to read the register sources
+    // Store in temporary A and B registers
+    // Checks Stall conditions
+    (void)instr;  // TODO: Probably remove this
+
+    // TODO: Implement forwarding logic but raise error for now
+    if (forward) {
+        throw std::runtime_error("Forwarding not implemented yet");
+    }
path: src/functional_simulator.cpp
user: login: reecewayt
body: Sorry, I think adding this was confusing. The forwarding logic would be after hazard detection, lower in the method:

```cpp
if (rs != 0) {
        if (exmem_reg.current().instr && exmem_reg.current().wb_reg.has_value()) {
            if (rs == exmem_reg.current().wb_reg.value()) rs_hazard = true;
            if (rt == exmem_reg.current().wb_reg.value()) rt_hazard = true;
            if ((rs_hazard || rt_hazard) && !forward) setStall(2);
            return;
        }
    }
// If hazard is true here, we would grab `exmem` result to complete forwarding. 
```
```cpp 
// Check MEM/WB pipeline register for hazards
    else if (rt != 0) {
        if (memwb_reg.current().instr && memwb_reg.current().wb_reg.has_value()) {
            if (rs == memwb_reg.current().wb_reg.value()) rs_hazard = true;
            if (rt == memwb_reg.current().wb_reg.value()) rt_hazard = true;
            if ((rs_hazard || rt_hazard) && !forward) setStall(1);
            return;
        }
    }
// If hazard is true here, we would grab `memwb` result to complete forwarding. 
```


created_at: 2025-05-19T18:10:04Z
author_association: COLLABORATOR
diff_hunk: @@ -42,14 +55,198 @@ void FunctionalSimulator::instructionFetch() {
 }
 
 void FunctionalSimulator::instructionDecode(Instruction* instr) {
-    // Stub: Decode the instruction (parse operands, maybe identify hazards)
-    // Set dirty bit on registers which will be written back to later
-    (void)instr;  // Silence unused parameter warning
+    // Operation: Decode instruction and access register file to read the register sources
+    // Store in temporary A and B registers
+    // Checks Stall conditions
+    (void)instr;  // TODO: Probably remove this
+
+    // TODO: Implement forwarding logic but raise error for now
+    if (forward) {
+        throw std::runtime_error("Forwarding not implemented yet");
+    }
+
+    PipelineData data = ifid_reg.current();
+    if (stall > 0) {
+        stall--;
+        // Bubble insert
+        data.instr = nullptr;  // Set instruction to null
+        data.reg_a = 0;
+        data.reg_b = 0;
+        data.result = 0;
+        data.wb_reg = 0;
+        idex_reg.setNext(data);
+        return;
+    }
path: src/functional_simulator.cpp
user: login: reecewayt
body: That could work. I had a thought that came from the textbook, which mentions that in a stall scenario that an instruction earlier in the pipeline (i.e. IF, and ID) simply recirculates its current instruction. I was thinking it would look like this:  
```cpp
// For IF stage
if(stall > 0) {
   ifid_reg.next = curr_instruction; 
}
```

So in a stall scenario, the ID stage passes along a bubble down the pipeline (i.e. NOP). The IF stage statically holds the same instruction until the stall is complete. 
created_at: 2025-05-19T18:12:33Z
author_association: COLLABORATOR
diff_hunk: @@ -42,14 +55,198 @@ void FunctionalSimulator::instructionFetch() {
 }
 
 void FunctionalSimulator::instructionDecode(Instruction* instr) {
-    // Stub: Decode the instruction (parse operands, maybe identify hazards)
-    // Set dirty bit on registers which will be written back to later
-    (void)instr;  // Silence unused parameter warning
+    // Operation: Decode instruction and access register file to read the register sources
+    // Store in temporary A and B registers
+    // Checks Stall conditions
+    (void)instr;  // TODO: Probably remove this
+
+    // TODO: Implement forwarding logic but raise error for now
+    if (forward) {
+        throw std::runtime_error("Forwarding not implemented yet");
+    }
+
+    PipelineData data = ifid_reg.current();
+    if (stall > 0) {
+        stall--;
+        // Bubble insert
+        data.instr = nullptr;  // Set instruction to null
+        data.reg_a = 0;
+        data.reg_b = 0;
+        data.result = 0;
+        data.wb_reg = 0;
+        idex_reg.setNext(data);
+        return;
+    }
+
+    // Identify source registers
+    uint8_t rs = data.instr->getRs();
+    uint8_t rt = data.instr->getRt();
+    bool rs_hazard = false;
+    bool rt_hazard = false;
+
+    // TODO: Consider putting this in a separate function
+    // TODO: Must consider all cases of hazards even with forwarding
+    // Note: Need to consider data hazards on $0 which will always = 0, hence is not
+    // a true dependency and needs to be ignored.
+    // TODO: Load instructions still result in a one cycle stall even with forwarding
+    // be sure to check this when implmenting forwarding
path: src/functional_simulator.cpp
user: login: reecewayt
body: I think the complicated piece here is that a data hazard caused by a Load instruction requires a one-cycle stall penalty because forwarding cannot prevent this. Yuchen had a couple of slides on this in Lecture 6 Data Hazards, slides 11 and 12.  I think she is expecting us to consider this special case. Also page C-16 of the textbook (6th edition) talks about this too. 
![image](https://github.com/user-attachments/assets/62c2be5f-244b-49e4-8c97-0cce1e579f7a)

created_at: 2025-05-19T18:42:06Z
author_association: COLLABORATOR
diff_hunk: @@ -42,14 +55,198 @@ void FunctionalSimulator::instructionFetch() {
 }
 
 void FunctionalSimulator::instructionDecode(Instruction* instr) {
-    // Stub: Decode the instruction (parse operands, maybe identify hazards)
-    // Set dirty bit on registers which will be written back to later
-    (void)instr;  // Silence unused parameter warning
+    // Operation: Decode instruction and access register file to read the register sources
+    // Store in temporary A and B registers
+    // Checks Stall conditions
+    (void)instr;  // TODO: Probably remove this
+
+    // TODO: Implement forwarding logic but raise error for now
+    if (forward) {
+        throw std::runtime_error("Forwarding not implemented yet");
+    }
+
+    PipelineData data = ifid_reg.current();
+    if (stall > 0) {
+        stall--;
+        // Bubble insert
+        data.instr = nullptr;  // Set instruction to null
+        data.reg_a = 0;
+        data.reg_b = 0;
+        data.result = 0;
+        data.wb_reg = 0;
+        idex_reg.setNext(data);
+        return;
+    }
+
+    // Identify source registers
+    uint8_t rs = data.instr->getRs();
+    uint8_t rt = data.instr->getRt();
+    bool rs_hazard = false;
+    bool rt_hazard = false;
+
+    // TODO: Consider putting this in a separate function
+    // TODO: Must consider all cases of hazards even with forwarding
+    // Note: Need to consider data hazards on $0 which will always = 0, hence is not
+    // a true dependency and needs to be ignored.
+    // TODO: Load instructions still result in a one cycle stall even with forwarding
+    // be sure to check this when implmenting forwarding
+    // Check EX/MEM pipeline register for hazards
+    if (rs != 0) {
+        if (exmem_reg.current().instr && exmem_reg.current().wb_reg.has_value()) {
+            if (rs == exmem_reg.current().wb_reg.value()) rs_hazard = true;
+            if (rt == exmem_reg.current().wb_reg.value()) rt_hazard = true;
+            if ((rs_hazard || rt_hazard) && !forward) setStall(2);
+            return;
+        }
+    }
+    // Check MEM/WB pipeline register for hazards
+    else if (rt != 0) {
+        if (memwb_reg.current().instr && memwb_reg.current().wb_reg.has_value()) {
+            if (rs == memwb_reg.current().wb_reg.value()) rs_hazard = true;
+            if (rt == memwb_reg.current().wb_reg.value()) rt_hazard = true;
+            if ((rs_hazard || rt_hazard) && !forward) setStall(1);
+            return;
+        }
+    }
+
+    // No hazards or forwarding is ON, continue with decode
+    data.reg_a = register_file->read(rs);
+    data.branch_taken = std::nullopt;
+
+    uint8_t opcode = data.instr->getOpcode();
+    // Determine what to put in reg_b (i.e. ALU Source 2) and wb_reg based on instruction type
+    if (mips_lite::is_branch_instruction(opcode)) {
+        // Branch instructions
+        data.wb_reg = std::nullopt;  // No write-back for branches
+        data.branch_taken = false;   // Initialize branch flag
+
+        if (opcode == mips_lite::opcode::BEQ) {
+            // BEQ needs rt value for comparison
+            data.reg_b = register_file->read(rt);
+        } else {
+            // BZ uses immediate for branch offset
+            data.reg_b = data.instr->getImmediate();
+        }
+    } else if (mips_lite::is_jump_instruction(opcode)) {
+        // Jump instructions
+        data.wb_reg = std::nullopt;  // No write-back for jumps
+        data.branch_taken = false;   // Initialize branch flag
+
+        // JR uses rs as jump target
+        // No need to set reg_b for JR
+    } else if (opcode == mips_lite::opcode::STW) {
+        // Store instructions
+        data.wb_reg = std::nullopt;               // No write-back for stores
+        data.reg_b = data.instr->getImmediate();  // Offset
+        data.str_val = register_file->read(rt);   // Value to store
+    } else if (data.instr->hasRd()) {
+        // R-type instructions (register-register)
+        data.reg_b = register_file->read(rt);
+        data.wb_reg = data.instr->getRd();
+    } else if (data.instr->hasImmediate()) {
+        // I-type instructions (register-immediate)
+        data.reg_b = data.instr->getImmediate();
+        data.wb_reg = data.instr->getRt();
+    } else {
+        throw std::invalid_argument("Invalid instruction type for decode stage");
+    }
+
+    // Pass data to ID/EX pipeline register
+    idex_reg.setNext(data);
 }
 
 void FunctionalSimulator::execute(Instruction* instr) {
-    // Stub: Perform ALU operation specified or calculate branch effective address
-    (void)instr;
+    (void)instr;  // TODO: Probably remove this
+    PipelineData data = idex_reg.current();
+    if (data.instr == nullptr) {
+        exmem_reg.setNext(data);
+        return;
+    }
+
+    switch (data.instr->getOpcode()) {
+        // Arithmetic Operations
+        case mips_lite::opcode::ADD:
+        case mips_lite::opcode::ADDI:
+            data.result = data.reg_a + data.reg_b;
+            break;
+
+        case mips_lite::opcode::SUB:
+        case mips_lite::opcode::SUBI:
+            data.result = data.reg_a - data.reg_b;
+            break;
+
+        case mips_lite::opcode::MUL:
+        case mips_lite::opcode::MULI:
+            data.result = data.reg_a * data.reg_b;
+            break;
+
+        // Logical Operations
+        case mips_lite::opcode::OR:
+        case mips_lite::opcode::ORI:
+            data.result = data.reg_a | data.reg_b;
+            break;
+
+        case mips_lite::opcode::AND:
+        case mips_lite::opcode::ANDI:
+            data.result = data.reg_a & data.reg_b;
+            break;
+
+        case mips_lite::opcode::XOR:
+        case mips_lite::opcode::XORI:
+            data.result = data.reg_a ^ data.reg_b;
+            break;
+
+        // Memory Effective Address Calculation -> Load and Store
+        case mips_lite::opcode::LDW:
+            data.result = data.reg_a + data.reg_b;
+            break;
+
+        case mips_lite::opcode::STW:
+            data.result = data.reg_a + data.reg_b;
+            break;
+
+        // Control Flow Ops
+        case mips_lite::opcode::BZ:
+            if (data.reg_a == 0) {
+                data.result = pc + (data.reg_b * 4);
+                data.branch_taken = true;  // Indicate that a branch was taken
+                break;
+            }
+            data.branch_taken = false;
+            break;
+
+        case mips_lite::opcode::BEQ:
+            if (data.reg_a == data.reg_b) {
+                // Branch if equal - calculate target address
+                int32_t offset = data.instr->getImmediate();
+                data.result = pc + (offset * 4);
+                data.branch_taken = true;  // Indicate that a branch was taken
path: src/functional_simulator.cpp
user: login: reecewayt
body: Yes, it will be the PC to be used for the very next fetch, and if branch is taken we will need to flush IF and ID stages. 
created_at: 2025-05-19T18:52:47Z
author_association: COLLABORATOR
diff_hunk: @@ -42,14 +55,198 @@ void FunctionalSimulator::instructionFetch() {
 }
 
 void FunctionalSimulator::instructionDecode(Instruction* instr) {
-    // Stub: Decode the instruction (parse operands, maybe identify hazards)
-    // Set dirty bit on registers which will be written back to later
-    (void)instr;  // Silence unused parameter warning
+    // Operation: Decode instruction and access register file to read the register sources
+    // Store in temporary A and B registers
+    // Checks Stall conditions
+    (void)instr;  // TODO: Probably remove this
+
+    // TODO: Implement forwarding logic but raise error for now
+    if (forward) {
+        throw std::runtime_error("Forwarding not implemented yet");
+    }
+
+    PipelineData data = ifid_reg.current();
+    if (stall > 0) {
+        stall--;
+        // Bubble insert
+        data.instr = nullptr;  // Set instruction to null
+        data.reg_a = 0;
+        data.reg_b = 0;
+        data.result = 0;
+        data.wb_reg = 0;
+        idex_reg.setNext(data);
+        return;
+    }
+
+    // Identify source registers
+    uint8_t rs = data.instr->getRs();
+    uint8_t rt = data.instr->getRt();
+    bool rs_hazard = false;
+    bool rt_hazard = false;
+
+    // TODO: Consider putting this in a separate function
+    // TODO: Must consider all cases of hazards even with forwarding
+    // Note: Need to consider data hazards on $0 which will always = 0, hence is not
+    // a true dependency and needs to be ignored.
+    // TODO: Load instructions still result in a one cycle stall even with forwarding
+    // be sure to check this when implmenting forwarding
+    // Check EX/MEM pipeline register for hazards
+    if (rs != 0) {
+        if (exmem_reg.current().instr && exmem_reg.current().wb_reg.has_value()) {
+            if (rs == exmem_reg.current().wb_reg.value()) rs_hazard = true;
+            if (rt == exmem_reg.current().wb_reg.value()) rt_hazard = true;
+            if ((rs_hazard || rt_hazard) && !forward) setStall(2);
+            return;
+        }
+    }
+    // Check MEM/WB pipeline register for hazards
+    else if (rt != 0) {
+        if (memwb_reg.current().instr && memwb_reg.current().wb_reg.has_value()) {
+            if (rs == memwb_reg.current().wb_reg.value()) rs_hazard = true;
+            if (rt == memwb_reg.current().wb_reg.value()) rt_hazard = true;
+            if ((rs_hazard || rt_hazard) && !forward) setStall(1);
+            return;
+        }
+    }
+
+    // No hazards or forwarding is ON, continue with decode
+    data.reg_a = register_file->read(rs);
+    data.branch_taken = std::nullopt;
+
+    uint8_t opcode = data.instr->getOpcode();
+    // Determine what to put in reg_b (i.e. ALU Source 2) and wb_reg based on instruction type
+    if (mips_lite::is_branch_instruction(opcode)) {
+        // Branch instructions
+        data.wb_reg = std::nullopt;  // No write-back for branches
+        data.branch_taken = false;   // Initialize branch flag
+
+        if (opcode == mips_lite::opcode::BEQ) {
+            // BEQ needs rt value for comparison
+            data.reg_b = register_file->read(rt);
+        } else {
+            // BZ uses immediate for branch offset
+            data.reg_b = data.instr->getImmediate();
+        }
+    } else if (mips_lite::is_jump_instruction(opcode)) {
+        // Jump instructions
+        data.wb_reg = std::nullopt;  // No write-back for jumps
+        data.branch_taken = false;   // Initialize branch flag
+
+        // JR uses rs as jump target
+        // No need to set reg_b for JR
+    } else if (opcode == mips_lite::opcode::STW) {
+        // Store instructions
+        data.wb_reg = std::nullopt;               // No write-back for stores
+        data.reg_b = data.instr->getImmediate();  // Offset
+        data.str_val = register_file->read(rt);   // Value to store
+    } else if (data.instr->hasRd()) {
+        // R-type instructions (register-register)
+        data.reg_b = register_file->read(rt);
+        data.wb_reg = data.instr->getRd();
+    } else if (data.instr->hasImmediate()) {
+        // I-type instructions (register-immediate)
+        data.reg_b = data.instr->getImmediate();
+        data.wb_reg = data.instr->getRt();
+    } else {
+        throw std::invalid_argument("Invalid instruction type for decode stage");
+    }
+
+    // Pass data to ID/EX pipeline register
+    idex_reg.setNext(data);
 }
 
 void FunctionalSimulator::execute(Instruction* instr) {
-    // Stub: Perform ALU operation specified or calculate branch effective address
-    (void)instr;
+    (void)instr;  // TODO: Probably remove this
+    PipelineData data = idex_reg.current();
+    if (data.instr == nullptr) {
+        exmem_reg.setNext(data);
+        return;
+    }
+
+    switch (data.instr->getOpcode()) {
+        // Arithmetic Operations
+        case mips_lite::opcode::ADD:
+        case mips_lite::opcode::ADDI:
+            data.result = data.reg_a + data.reg_b;
+            break;
+
+        case mips_lite::opcode::SUB:
+        case mips_lite::opcode::SUBI:
+            data.result = data.reg_a - data.reg_b;
+            break;
+
+        case mips_lite::opcode::MUL:
+        case mips_lite::opcode::MULI:
+            data.result = data.reg_a * data.reg_b;
+            break;
+
+        // Logical Operations
+        case mips_lite::opcode::OR:
+        case mips_lite::opcode::ORI:
+            data.result = data.reg_a | data.reg_b;
+            break;
+
+        case mips_lite::opcode::AND:
+        case mips_lite::opcode::ANDI:
+            data.result = data.reg_a & data.reg_b;
+            break;
+
+        case mips_lite::opcode::XOR:
+        case mips_lite::opcode::XORI:
+            data.result = data.reg_a ^ data.reg_b;
+            break;
+
+        // Memory Effective Address Calculation -> Load and Store
+        case mips_lite::opcode::LDW:
+            data.result = data.reg_a + data.reg_b;
+            break;
+
+        case mips_lite::opcode::STW:
+            data.result = data.reg_a + data.reg_b;
+            break;
+
+        // Control Flow Ops
+        case mips_lite::opcode::BZ:
+            if (data.reg_a == 0) {
+                data.result = pc + (data.reg_b * 4);
+                data.branch_taken = true;  // Indicate that a branch was taken
+                break;
+            }
+            data.branch_taken = false;
+            break;
+
+        case mips_lite::opcode::BEQ:
+            if (data.reg_a == data.reg_b) {
+                // Branch if equal - calculate target address
+                int32_t offset = data.instr->getImmediate();
+                data.result = pc + (offset * 4);
+                data.branch_taken = true;  // Indicate that a branch was taken
+            } else {
+                // No branch, PC will be updated normally
+                data.branch_taken = false;
+                data.result = pc;  // No change (PC should be updated in fetch stage)
+            }
+            break;
+
+        case mips_lite::opcode::JR:
+            // Jump register - use the value in Rs as the new PC
+            data.result = data.reg_a;
path: src/functional_simulator.cpp
user: login: reecewayt
body: Hmm, good question. The instructions only mention to take the contents of the register and put that as the next PC. We might need to get clarification on this. 
 
created_at: 2025-05-19T18:56:14Z
author_association: COLLABORATOR
diff_hunk: @@ -16,6 +21,14 @@ FunctionalSimulator::FunctionalSimulator(RegisterFile* rf, Stats* st, IMemoryPar
     for (auto& stage : pipeline) {
path: src/functional_simulator.cpp
user: login: reecewayt
body: I set it up this way because in a pipeline, usually the physical instruction bits are transferred between pipeline stages. It sort of makes sense to me because certain stages use different parts of the instruction bits. ID uses the register bits for register file access, while EXE uses the opcode, etc. I think we need to either have an array storing our pipeline data (i.e. a struct with all necessary fields) or do the pipeline register thing I've set up here. I think having instructions in one place and intermediate results in another makes it harder to maintain. For example, if we have both, we not only need to shift intermediate results along the pipeline, but we also need to move instructions along in the array. Consolidating to either an array or pipeline registers will make it easier overall.
created_at: 2025-05-19T19:08:22Z
author_association: COLLABORATOR
diff_hunk: @@ -42,14 +55,198 @@ void FunctionalSimulator::instructionFetch() {
 }
 
 void FunctionalSimulator::instructionDecode(Instruction* instr) {
-    // Stub: Decode the instruction (parse operands, maybe identify hazards)
-    // Set dirty bit on registers which will be written back to later
-    (void)instr;  // Silence unused parameter warning
+    // Operation: Decode instruction and access register file to read the register sources
+    // Store in temporary A and B registers
+    // Checks Stall conditions
+    (void)instr;  // TODO: Probably remove this
+
+    // TODO: Implement forwarding logic but raise error for now
+    if (forward) {
+        throw std::runtime_error("Forwarding not implemented yet");
+    }
+
+    PipelineData data = ifid_reg.current();
+    if (stall > 0) {
+        stall--;
+        // Bubble insert
+        data.instr = nullptr;  // Set instruction to null
+        data.reg_a = 0;
+        data.reg_b = 0;
+        data.result = 0;
+        data.wb_reg = 0;
+        idex_reg.setNext(data);
+        return;
+    }
+
+    // Identify source registers
+    uint8_t rs = data.instr->getRs();
+    uint8_t rt = data.instr->getRt();
+    bool rs_hazard = false;
+    bool rt_hazard = false;
+
+    // TODO: Consider putting this in a separate function
+    // TODO: Must consider all cases of hazards even with forwarding
+    // Note: Need to consider data hazards on $0 which will always = 0, hence is not
+    // a true dependency and needs to be ignored.
+    // TODO: Load instructions still result in a one cycle stall even with forwarding
+    // be sure to check this when implmenting forwarding
+    // Check EX/MEM pipeline register for hazards
+    if (rs != 0) {
+        if (exmem_reg.current().instr && exmem_reg.current().wb_reg.has_value()) {
+            if (rs == exmem_reg.current().wb_reg.value()) rs_hazard = true;
+            if (rt == exmem_reg.current().wb_reg.value()) rt_hazard = true;
+            if ((rs_hazard || rt_hazard) && !forward) setStall(2);
+            return;
+        }
+    }
+    // Check MEM/WB pipeline register for hazards
+    else if (rt != 0) {
+        if (memwb_reg.current().instr && memwb_reg.current().wb_reg.has_value()) {
+            if (rs == memwb_reg.current().wb_reg.value()) rs_hazard = true;
+            if (rt == memwb_reg.current().wb_reg.value()) rt_hazard = true;
+            if ((rs_hazard || rt_hazard) && !forward) setStall(1);
+            return;
+        }
+    }
path: src/functional_simulator.cpp
user: login: reecewayt
body: Good question, and I'm not sure. I think this question points out an important drawback of the pipeline register thing we've set up, which is that it complicates things with the use of `.next` and `.current` values. I don't think there is any real benefit of using this type of approach, and it starts to make me lean towards just doing a simple array that stores our instructions and intermediate results. 
created_at: 2025-05-19T19:13:59Z
author_association: COLLABORATOR
diff_hunk: @@ -16,6 +21,14 @@ FunctionalSimulator::FunctionalSimulator(RegisterFile* rf, Stats* st, IMemoryPar
     for (auto& stage : pipeline) {
path: src/functional_simulator.cpp
user: login: nkanderson
body: Yeah, it makes sense that a hardware pipeline needs to shift through the instructions, since it doesn't have access to different methods and pointers. My main thought with having the pipeline data separate from the instructions is that the functional simulator controls the overall flow and control of instructions, and individual pipeline stages manage the intermediate data. But I'd be up for trying the option of shifting through the instructions instead of having them in a separate array. I think that will just mean each of these methods deals with both control and data, which might get a little messy if different methods are managing the same parts of the control.
created_at: 2025-05-19T20:16:32Z
author_association: OWNER
diff_hunk: @@ -42,14 +55,198 @@ void FunctionalSimulator::instructionFetch() {
 }
 
 void FunctionalSimulator::instructionDecode(Instruction* instr) {
-    // Stub: Decode the instruction (parse operands, maybe identify hazards)
-    // Set dirty bit on registers which will be written back to later
-    (void)instr;  // Silence unused parameter warning
+    // Operation: Decode instruction and access register file to read the register sources
+    // Store in temporary A and B registers
+    // Checks Stall conditions
+    (void)instr;  // TODO: Probably remove this
+
+    // TODO: Implement forwarding logic but raise error for now
+    if (forward) {
+        throw std::runtime_error("Forwarding not implemented yet");
+    }
+
+    PipelineData data = ifid_reg.current();
+    if (stall > 0) {
+        stall--;
+        // Bubble insert
+        data.instr = nullptr;  // Set instruction to null
+        data.reg_a = 0;
+        data.reg_b = 0;
+        data.result = 0;
+        data.wb_reg = 0;
+        idex_reg.setNext(data);
+        return;
+    }
+
+    // Identify source registers
+    uint8_t rs = data.instr->getRs();
+    uint8_t rt = data.instr->getRt();
+    bool rs_hazard = false;
+    bool rt_hazard = false;
+
+    // TODO: Consider putting this in a separate function
+    // TODO: Must consider all cases of hazards even with forwarding
+    // Note: Need to consider data hazards on $0 which will always = 0, hence is not
+    // a true dependency and needs to be ignored.
+    // TODO: Load instructions still result in a one cycle stall even with forwarding
+    // be sure to check this when implmenting forwarding
path: src/functional_simulator.cpp
user: login: nkanderson
body: The goal with the dirty bit is that we'd have the same outcome without needing to check for that specific case. So at the decode stage, we'd check to see if either operand is marked as `dirty` in the register file. If it is, in the case of forwarding, we can check if the necessary data exists yet in any of the pipeline registers (i.e. is there an available result for the writeback register our instruction needs data from), starting with the nearest. For a load instruction, it would be available after the memory stage; for an add, for example, it would be available after the execute stage. With no forwarding, the instruction needs to keep checking for the data it needs in the register file until it is no longer marked as `dirty`. In either forwarding or no-forwarding, if the instruction can't get the data it needs yet, it inserts a stall.

Does that make sense? Are you seeing any gaps in the logic with that option? It seems like it would simplify things for us to have one mechanism that would cover the different cases.
created_at: 2025-05-19T20:25:44Z
author_association: OWNER
diff_hunk: @@ -42,14 +55,198 @@ void FunctionalSimulator::instructionFetch() {
 }
 
 void FunctionalSimulator::instructionDecode(Instruction* instr) {
-    // Stub: Decode the instruction (parse operands, maybe identify hazards)
-    // Set dirty bit on registers which will be written back to later
-    (void)instr;  // Silence unused parameter warning
+    // Operation: Decode instruction and access register file to read the register sources
+    // Store in temporary A and B registers
+    // Checks Stall conditions
+    (void)instr;  // TODO: Probably remove this
+
+    // TODO: Implement forwarding logic but raise error for now
+    if (forward) {
+        throw std::runtime_error("Forwarding not implemented yet");
+    }
+
+    PipelineData data = ifid_reg.current();
+    if (stall > 0) {
+        stall--;
+        // Bubble insert
+        data.instr = nullptr;  // Set instruction to null
+        data.reg_a = 0;
+        data.reg_b = 0;
+        data.result = 0;
+        data.wb_reg = 0;
+        idex_reg.setNext(data);
+        return;
+    }
+
+    // Identify source registers
+    uint8_t rs = data.instr->getRs();
+    uint8_t rt = data.instr->getRt();
+    bool rs_hazard = false;
+    bool rt_hazard = false;
+
+    // TODO: Consider putting this in a separate function
+    // TODO: Must consider all cases of hazards even with forwarding
+    // Note: Need to consider data hazards on $0 which will always = 0, hence is not
+    // a true dependency and needs to be ignored.
+    // TODO: Load instructions still result in a one cycle stall even with forwarding
+    // be sure to check this when implmenting forwarding
+    // Check EX/MEM pipeline register for hazards
+    if (rs != 0) {
+        if (exmem_reg.current().instr && exmem_reg.current().wb_reg.has_value()) {
+            if (rs == exmem_reg.current().wb_reg.value()) rs_hazard = true;
+            if (rt == exmem_reg.current().wb_reg.value()) rt_hazard = true;
+            if ((rs_hazard || rt_hazard) && !forward) setStall(2);
+            return;
+        }
+    }
+    // Check MEM/WB pipeline register for hazards
+    else if (rt != 0) {
+        if (memwb_reg.current().instr && memwb_reg.current().wb_reg.has_value()) {
+            if (rs == memwb_reg.current().wb_reg.value()) rs_hazard = true;
+            if (rt == memwb_reg.current().wb_reg.value()) rt_hazard = true;
+            if ((rs_hazard || rt_hazard) && !forward) setStall(1);
+            return;
+        }
+    }
path: src/functional_simulator.cpp
user: login: nkanderson
body: If we were just to have an array for intermediate results, what would it contain? It seems like we still need some way of accessing data set in a prior stage, while identifying data that needs to be set for the next cycle. Like would the intermediate results array shift data through?
created_at: 2025-05-19T20:28:17Z
author_association: OWNER
diff_hunk: @@ -42,14 +55,198 @@ void FunctionalSimulator::instructionFetch() {
 }
 
 void FunctionalSimulator::instructionDecode(Instruction* instr) {
-    // Stub: Decode the instruction (parse operands, maybe identify hazards)
-    // Set dirty bit on registers which will be written back to later
-    (void)instr;  // Silence unused parameter warning
+    // Operation: Decode instruction and access register file to read the register sources
+    // Store in temporary A and B registers
+    // Checks Stall conditions
+    (void)instr;  // TODO: Probably remove this
+
+    // TODO: Implement forwarding logic but raise error for now
+    if (forward) {
+        throw std::runtime_error("Forwarding not implemented yet");
+    }
+
+    PipelineData data = ifid_reg.current();
+    if (stall > 0) {
+        stall--;
+        // Bubble insert
+        data.instr = nullptr;  // Set instruction to null
+        data.reg_a = 0;
+        data.reg_b = 0;
+        data.result = 0;
+        data.wb_reg = 0;
+        idex_reg.setNext(data);
+        return;
+    }
+
+    // Identify source registers
+    uint8_t rs = data.instr->getRs();
+    uint8_t rt = data.instr->getRt();
+    bool rs_hazard = false;
+    bool rt_hazard = false;
+
+    // TODO: Consider putting this in a separate function
+    // TODO: Must consider all cases of hazards even with forwarding
+    // Note: Need to consider data hazards on $0 which will always = 0, hence is not
+    // a true dependency and needs to be ignored.
+    // TODO: Load instructions still result in a one cycle stall even with forwarding
+    // be sure to check this when implmenting forwarding
+    // Check EX/MEM pipeline register for hazards
+    if (rs != 0) {
+        if (exmem_reg.current().instr && exmem_reg.current().wb_reg.has_value()) {
+            if (rs == exmem_reg.current().wb_reg.value()) rs_hazard = true;
+            if (rt == exmem_reg.current().wb_reg.value()) rt_hazard = true;
+            if ((rs_hazard || rt_hazard) && !forward) setStall(2);
+            return;
+        }
+    }
+    // Check MEM/WB pipeline register for hazards
+    else if (rt != 0) {
+        if (memwb_reg.current().instr && memwb_reg.current().wb_reg.has_value()) {
+            if (rs == memwb_reg.current().wb_reg.value()) rs_hazard = true;
+            if (rt == memwb_reg.current().wb_reg.value()) rt_hazard = true;
+            if ((rs_hazard || rt_hazard) && !forward) setStall(1);
+            return;
+        }
+    }
+
+    // No hazards or forwarding is ON, continue with decode
+    data.reg_a = register_file->read(rs);
+    data.branch_taken = std::nullopt;
+
+    uint8_t opcode = data.instr->getOpcode();
+    // Determine what to put in reg_b (i.e. ALU Source 2) and wb_reg based on instruction type
+    if (mips_lite::is_branch_instruction(opcode)) {
+        // Branch instructions
+        data.wb_reg = std::nullopt;  // No write-back for branches
+        data.branch_taken = false;   // Initialize branch flag
+
+        if (opcode == mips_lite::opcode::BEQ) {
+            // BEQ needs rt value for comparison
+            data.reg_b = register_file->read(rt);
+        } else {
+            // BZ uses immediate for branch offset
+            data.reg_b = data.instr->getImmediate();
+        }
+    } else if (mips_lite::is_jump_instruction(opcode)) {
+        // Jump instructions
+        data.wb_reg = std::nullopt;  // No write-back for jumps
+        data.branch_taken = false;   // Initialize branch flag
+
+        // JR uses rs as jump target
+        // No need to set reg_b for JR
+    } else if (opcode == mips_lite::opcode::STW) {
+        // Store instructions
+        data.wb_reg = std::nullopt;               // No write-back for stores
+        data.reg_b = data.instr->getImmediate();  // Offset
+        data.str_val = register_file->read(rt);   // Value to store
+    } else if (data.instr->hasRd()) {
+        // R-type instructions (register-register)
+        data.reg_b = register_file->read(rt);
+        data.wb_reg = data.instr->getRd();
+    } else if (data.instr->hasImmediate()) {
+        // I-type instructions (register-immediate)
+        data.reg_b = data.instr->getImmediate();
+        data.wb_reg = data.instr->getRt();
+    } else {
+        throw std::invalid_argument("Invalid instruction type for decode stage");
+    }
+
+    // Pass data to ID/EX pipeline register
+    idex_reg.setNext(data);
 }
 
 void FunctionalSimulator::execute(Instruction* instr) {
-    // Stub: Perform ALU operation specified or calculate branch effective address
-    (void)instr;
+    (void)instr;  // TODO: Probably remove this
+    PipelineData data = idex_reg.current();
+    if (data.instr == nullptr) {
+        exmem_reg.setNext(data);
+        return;
+    }
+
+    switch (data.instr->getOpcode()) {
+        // Arithmetic Operations
+        case mips_lite::opcode::ADD:
+        case mips_lite::opcode::ADDI:
+            data.result = data.reg_a + data.reg_b;
+            break;
+
+        case mips_lite::opcode::SUB:
+        case mips_lite::opcode::SUBI:
+            data.result = data.reg_a - data.reg_b;
+            break;
+
+        case mips_lite::opcode::MUL:
+        case mips_lite::opcode::MULI:
+            data.result = data.reg_a * data.reg_b;
+            break;
+
+        // Logical Operations
+        case mips_lite::opcode::OR:
+        case mips_lite::opcode::ORI:
+            data.result = data.reg_a | data.reg_b;
+            break;
+
+        case mips_lite::opcode::AND:
+        case mips_lite::opcode::ANDI:
+            data.result = data.reg_a & data.reg_b;
+            break;
+
+        case mips_lite::opcode::XOR:
+        case mips_lite::opcode::XORI:
+            data.result = data.reg_a ^ data.reg_b;
+            break;
+
+        // Memory Effective Address Calculation -> Load and Store
+        case mips_lite::opcode::LDW:
+            data.result = data.reg_a + data.reg_b;
+            break;
+
+        case mips_lite::opcode::STW:
+            data.result = data.reg_a + data.reg_b;
+            break;
+
+        // Control Flow Ops
+        case mips_lite::opcode::BZ:
+            if (data.reg_a == 0) {
+                data.result = pc + (data.reg_b * 4);
+                data.branch_taken = true;  // Indicate that a branch was taken
+                break;
+            }
+            data.branch_taken = false;
+            break;
+
+        case mips_lite::opcode::BEQ:
+            if (data.reg_a == data.reg_b) {
+                // Branch if equal - calculate target address
+                int32_t offset = data.instr->getImmediate();
+                data.result = pc + (offset * 4);
+                data.branch_taken = true;  // Indicate that a branch was taken
path: src/functional_simulator.cpp
user: login: nkanderson
body: This relates to the above discussion, deciding where we want the control to take place. Do you think the flush and setting the new PC should happen from within this method? Or external to it, performed directly by the functional simulator?
created_at: 2025-05-19T20:31:17Z
author_association: OWNER
diff_hunk: @@ -42,14 +55,198 @@ void FunctionalSimulator::instructionFetch() {
 }
 
 void FunctionalSimulator::instructionDecode(Instruction* instr) {
-    // Stub: Decode the instruction (parse operands, maybe identify hazards)
-    // Set dirty bit on registers which will be written back to later
-    (void)instr;  // Silence unused parameter warning
+    // Operation: Decode instruction and access register file to read the register sources
+    // Store in temporary A and B registers
+    // Checks Stall conditions
+    (void)instr;  // TODO: Probably remove this
+
+    // TODO: Implement forwarding logic but raise error for now
+    if (forward) {
+        throw std::runtime_error("Forwarding not implemented yet");
+    }
+
+    PipelineData data = ifid_reg.current();
+    if (stall > 0) {
+        stall--;
+        // Bubble insert
+        data.instr = nullptr;  // Set instruction to null
+        data.reg_a = 0;
+        data.reg_b = 0;
+        data.result = 0;
+        data.wb_reg = 0;
+        idex_reg.setNext(data);
+        return;
+    }
+
+    // Identify source registers
+    uint8_t rs = data.instr->getRs();
+    uint8_t rt = data.instr->getRt();
+    bool rs_hazard = false;
+    bool rt_hazard = false;
+
+    // TODO: Consider putting this in a separate function
+    // TODO: Must consider all cases of hazards even with forwarding
+    // Note: Need to consider data hazards on $0 which will always = 0, hence is not
+    // a true dependency and needs to be ignored.
+    // TODO: Load instructions still result in a one cycle stall even with forwarding
+    // be sure to check this when implmenting forwarding
+    // Check EX/MEM pipeline register for hazards
+    if (rs != 0) {
+        if (exmem_reg.current().instr && exmem_reg.current().wb_reg.has_value()) {
+            if (rs == exmem_reg.current().wb_reg.value()) rs_hazard = true;
+            if (rt == exmem_reg.current().wb_reg.value()) rt_hazard = true;
+            if ((rs_hazard || rt_hazard) && !forward) setStall(2);
+            return;
+        }
+    }
+    // Check MEM/WB pipeline register for hazards
+    else if (rt != 0) {
+        if (memwb_reg.current().instr && memwb_reg.current().wb_reg.has_value()) {
+            if (rs == memwb_reg.current().wb_reg.value()) rs_hazard = true;
+            if (rt == memwb_reg.current().wb_reg.value()) rt_hazard = true;
+            if ((rs_hazard || rt_hazard) && !forward) setStall(1);
+            return;
+        }
+    }
+
+    // No hazards or forwarding is ON, continue with decode
+    data.reg_a = register_file->read(rs);
+    data.branch_taken = std::nullopt;
+
+    uint8_t opcode = data.instr->getOpcode();
+    // Determine what to put in reg_b (i.e. ALU Source 2) and wb_reg based on instruction type
+    if (mips_lite::is_branch_instruction(opcode)) {
+        // Branch instructions
+        data.wb_reg = std::nullopt;  // No write-back for branches
+        data.branch_taken = false;   // Initialize branch flag
+
+        if (opcode == mips_lite::opcode::BEQ) {
+            // BEQ needs rt value for comparison
+            data.reg_b = register_file->read(rt);
+        } else {
+            // BZ uses immediate for branch offset
+            data.reg_b = data.instr->getImmediate();
+        }
+    } else if (mips_lite::is_jump_instruction(opcode)) {
+        // Jump instructions
+        data.wb_reg = std::nullopt;  // No write-back for jumps
+        data.branch_taken = false;   // Initialize branch flag
+
+        // JR uses rs as jump target
+        // No need to set reg_b for JR
+    } else if (opcode == mips_lite::opcode::STW) {
+        // Store instructions
+        data.wb_reg = std::nullopt;               // No write-back for stores
+        data.reg_b = data.instr->getImmediate();  // Offset
+        data.str_val = register_file->read(rt);   // Value to store
+    } else if (data.instr->hasRd()) {
+        // R-type instructions (register-register)
+        data.reg_b = register_file->read(rt);
+        data.wb_reg = data.instr->getRd();
+    } else if (data.instr->hasImmediate()) {
+        // I-type instructions (register-immediate)
+        data.reg_b = data.instr->getImmediate();
+        data.wb_reg = data.instr->getRt();
+    } else {
+        throw std::invalid_argument("Invalid instruction type for decode stage");
+    }
+
+    // Pass data to ID/EX pipeline register
+    idex_reg.setNext(data);
 }
 
 void FunctionalSimulator::execute(Instruction* instr) {
-    // Stub: Perform ALU operation specified or calculate branch effective address
-    (void)instr;
+    (void)instr;  // TODO: Probably remove this
+    PipelineData data = idex_reg.current();
+    if (data.instr == nullptr) {
+        exmem_reg.setNext(data);
+        return;
+    }
+
+    switch (data.instr->getOpcode()) {
+        // Arithmetic Operations
+        case mips_lite::opcode::ADD:
+        case mips_lite::opcode::ADDI:
+            data.result = data.reg_a + data.reg_b;
+            break;
+
+        case mips_lite::opcode::SUB:
+        case mips_lite::opcode::SUBI:
+            data.result = data.reg_a - data.reg_b;
+            break;
+
+        case mips_lite::opcode::MUL:
+        case mips_lite::opcode::MULI:
+            data.result = data.reg_a * data.reg_b;
+            break;
+
+        // Logical Operations
+        case mips_lite::opcode::OR:
+        case mips_lite::opcode::ORI:
+            data.result = data.reg_a | data.reg_b;
+            break;
+
+        case mips_lite::opcode::AND:
+        case mips_lite::opcode::ANDI:
+            data.result = data.reg_a & data.reg_b;
+            break;
+
+        case mips_lite::opcode::XOR:
+        case mips_lite::opcode::XORI:
+            data.result = data.reg_a ^ data.reg_b;
+            break;
+
+        // Memory Effective Address Calculation -> Load and Store
+        case mips_lite::opcode::LDW:
+            data.result = data.reg_a + data.reg_b;
+            break;
+
+        case mips_lite::opcode::STW:
+            data.result = data.reg_a + data.reg_b;
+            break;
+
+        // Control Flow Ops
+        case mips_lite::opcode::BZ:
+            if (data.reg_a == 0) {
+                data.result = pc + (data.reg_b * 4);
+                data.branch_taken = true;  // Indicate that a branch was taken
+                break;
+            }
+            data.branch_taken = false;
+            break;
+
+        case mips_lite::opcode::BEQ:
+            if (data.reg_a == data.reg_b) {
+                // Branch if equal - calculate target address
+                int32_t offset = data.instr->getImmediate();
+                data.result = pc + (offset * 4);
+                data.branch_taken = true;  // Indicate that a branch was taken
+            } else {
+                // No branch, PC will be updated normally
+                data.branch_taken = false;
+                data.result = pc;  // No change (PC should be updated in fetch stage)
+            }
+            break;
+
+        case mips_lite::opcode::JR:
+            // Jump register - use the value in Rs as the new PC
+            data.result = data.reg_a;
path: src/functional_simulator.cpp
user: login: nkanderson
body: Yeah, what you have for the branch instructions looks correct to me, but I think this value is only going to be 26 bits (for jump instructions), so we'd need to make it into a complete address.
created_at: 2025-05-19T20:32:31Z
author_association: OWNER
diff_hunk: @@ -42,14 +55,198 @@ void FunctionalSimulator::instructionFetch() {
 }
 
 void FunctionalSimulator::instructionDecode(Instruction* instr) {
-    // Stub: Decode the instruction (parse operands, maybe identify hazards)
-    // Set dirty bit on registers which will be written back to later
-    (void)instr;  // Silence unused parameter warning
+    // Operation: Decode instruction and access register file to read the register sources
+    // Store in temporary A and B registers
+    // Checks Stall conditions
+    (void)instr;  // TODO: Probably remove this
+
+    // TODO: Implement forwarding logic but raise error for now
+    if (forward) {
+        throw std::runtime_error("Forwarding not implemented yet");
+    }
+
+    PipelineData data = ifid_reg.current();
+    if (stall > 0) {
+        stall--;
+        // Bubble insert
+        data.instr = nullptr;  // Set instruction to null
+        data.reg_a = 0;
+        data.reg_b = 0;
+        data.result = 0;
+        data.wb_reg = 0;
+        idex_reg.setNext(data);
+        return;
+    }
+
+    // Identify source registers
+    uint8_t rs = data.instr->getRs();
+    uint8_t rt = data.instr->getRt();
+    bool rs_hazard = false;
+    bool rt_hazard = false;
+
+    // TODO: Consider putting this in a separate function
+    // TODO: Must consider all cases of hazards even with forwarding
+    // Note: Need to consider data hazards on $0 which will always = 0, hence is not
+    // a true dependency and needs to be ignored.
+    // TODO: Load instructions still result in a one cycle stall even with forwarding
+    // be sure to check this when implmenting forwarding
+    // Check EX/MEM pipeline register for hazards
+    if (rs != 0) {
+        if (exmem_reg.current().instr && exmem_reg.current().wb_reg.has_value()) {
+            if (rs == exmem_reg.current().wb_reg.value()) rs_hazard = true;
+            if (rt == exmem_reg.current().wb_reg.value()) rt_hazard = true;
+            if ((rs_hazard || rt_hazard) && !forward) setStall(2);
+            return;
+        }
+    }
+    // Check MEM/WB pipeline register for hazards
+    else if (rt != 0) {
+        if (memwb_reg.current().instr && memwb_reg.current().wb_reg.has_value()) {
+            if (rs == memwb_reg.current().wb_reg.value()) rs_hazard = true;
+            if (rt == memwb_reg.current().wb_reg.value()) rt_hazard = true;
+            if ((rs_hazard || rt_hazard) && !forward) setStall(1);
+            return;
+        }
+    }
+
+    // No hazards or forwarding is ON, continue with decode
+    data.reg_a = register_file->read(rs);
+    data.branch_taken = std::nullopt;
+
+    uint8_t opcode = data.instr->getOpcode();
+    // Determine what to put in reg_b (i.e. ALU Source 2) and wb_reg based on instruction type
+    if (mips_lite::is_branch_instruction(opcode)) {
+        // Branch instructions
+        data.wb_reg = std::nullopt;  // No write-back for branches
+        data.branch_taken = false;   // Initialize branch flag
+
+        if (opcode == mips_lite::opcode::BEQ) {
+            // BEQ needs rt value for comparison
+            data.reg_b = register_file->read(rt);
+        } else {
+            // BZ uses immediate for branch offset
+            data.reg_b = data.instr->getImmediate();
+        }
+    } else if (mips_lite::is_jump_instruction(opcode)) {
+        // Jump instructions
+        data.wb_reg = std::nullopt;  // No write-back for jumps
+        data.branch_taken = false;   // Initialize branch flag
+
+        // JR uses rs as jump target
+        // No need to set reg_b for JR
+    } else if (opcode == mips_lite::opcode::STW) {
+        // Store instructions
+        data.wb_reg = std::nullopt;               // No write-back for stores
+        data.reg_b = data.instr->getImmediate();  // Offset
+        data.str_val = register_file->read(rt);   // Value to store
+    } else if (data.instr->hasRd()) {
+        // R-type instructions (register-register)
+        data.reg_b = register_file->read(rt);
+        data.wb_reg = data.instr->getRd();
+    } else if (data.instr->hasImmediate()) {
+        // I-type instructions (register-immediate)
+        data.reg_b = data.instr->getImmediate();
+        data.wb_reg = data.instr->getRt();
+    } else {
+        throw std::invalid_argument("Invalid instruction type for decode stage");
+    }
+
+    // Pass data to ID/EX pipeline register
+    idex_reg.setNext(data);
 }
 
 void FunctionalSimulator::execute(Instruction* instr) {
-    // Stub: Perform ALU operation specified or calculate branch effective address
-    (void)instr;
+    (void)instr;  // TODO: Probably remove this
+    PipelineData data = idex_reg.current();
+    if (data.instr == nullptr) {
+        exmem_reg.setNext(data);
+        return;
+    }
+
+    switch (data.instr->getOpcode()) {
+        // Arithmetic Operations
+        case mips_lite::opcode::ADD:
+        case mips_lite::opcode::ADDI:
+            data.result = data.reg_a + data.reg_b;
+            break;
+
+        case mips_lite::opcode::SUB:
+        case mips_lite::opcode::SUBI:
+            data.result = data.reg_a - data.reg_b;
+            break;
+
+        case mips_lite::opcode::MUL:
+        case mips_lite::opcode::MULI:
+            data.result = data.reg_a * data.reg_b;
+            break;
+
+        // Logical Operations
+        case mips_lite::opcode::OR:
+        case mips_lite::opcode::ORI:
+            data.result = data.reg_a | data.reg_b;
+            break;
+
+        case mips_lite::opcode::AND:
+        case mips_lite::opcode::ANDI:
+            data.result = data.reg_a & data.reg_b;
+            break;
+
+        case mips_lite::opcode::XOR:
+        case mips_lite::opcode::XORI:
+            data.result = data.reg_a ^ data.reg_b;
+            break;
+
+        // Memory Effective Address Calculation -> Load and Store
+        case mips_lite::opcode::LDW:
+            data.result = data.reg_a + data.reg_b;
+            break;
+
+        case mips_lite::opcode::STW:
+            data.result = data.reg_a + data.reg_b;
+            break;
+
+        // Control Flow Ops
+        case mips_lite::opcode::BZ:
+            if (data.reg_a == 0) {
+                data.result = pc + (data.reg_b * 4);
+                data.branch_taken = true;  // Indicate that a branch was taken
+                break;
+            }
+            data.branch_taken = false;
+            break;
+
+        case mips_lite::opcode::BEQ:
+            if (data.reg_a == data.reg_b) {
+                // Branch if equal - calculate target address
+                int32_t offset = data.instr->getImmediate();
+                data.result = pc + (offset * 4);
+                data.branch_taken = true;  // Indicate that a branch was taken
+            } else {
+                // No branch, PC will be updated normally
+                data.branch_taken = false;
+                data.result = pc;  // No change (PC should be updated in fetch stage)
+            }
+            break;
+
+        case mips_lite::opcode::JR:
+            // Jump register - use the value in Rs as the new PC
+            data.result = data.reg_a;
path: src/functional_simulator.cpp
user: login: tnl3pdx
body: This may be one of the simplications of MIPS-lite compared to regular MIPS. In class, I think Yuchen was explaining the process of getting the new memory address for MIPS using the top 4 bits and the shift left of the IMM value. It wouldn't be a bad idea to ask to get this checked.
created_at: 2025-05-19T20:58:52Z
author_association: COLLABORATOR
diff_hunk: @@ -42,14 +55,198 @@ void FunctionalSimulator::instructionFetch() {
 }
 
 void FunctionalSimulator::instructionDecode(Instruction* instr) {
-    // Stub: Decode the instruction (parse operands, maybe identify hazards)
-    // Set dirty bit on registers which will be written back to later
-    (void)instr;  // Silence unused parameter warning
+    // Operation: Decode instruction and access register file to read the register sources
+    // Store in temporary A and B registers
+    // Checks Stall conditions
+    (void)instr;  // TODO: Probably remove this
+
+    // TODO: Implement forwarding logic but raise error for now
+    if (forward) {
+        throw std::runtime_error("Forwarding not implemented yet");
+    }
+
+    PipelineData data = ifid_reg.current();
+    if (stall > 0) {
+        stall--;
+        // Bubble insert
+        data.instr = nullptr;  // Set instruction to null
+        data.reg_a = 0;
+        data.reg_b = 0;
+        data.result = 0;
+        data.wb_reg = 0;
+        idex_reg.setNext(data);
+        return;
+    }
+
+    // Identify source registers
+    uint8_t rs = data.instr->getRs();
+    uint8_t rt = data.instr->getRt();
+    bool rs_hazard = false;
+    bool rt_hazard = false;
+
+    // TODO: Consider putting this in a separate function
+    // TODO: Must consider all cases of hazards even with forwarding
+    // Note: Need to consider data hazards on $0 which will always = 0, hence is not
+    // a true dependency and needs to be ignored.
+    // TODO: Load instructions still result in a one cycle stall even with forwarding
+    // be sure to check this when implmenting forwarding
+    // Check EX/MEM pipeline register for hazards
+    if (rs != 0) {
+        if (exmem_reg.current().instr && exmem_reg.current().wb_reg.has_value()) {
+            if (rs == exmem_reg.current().wb_reg.value()) rs_hazard = true;
+            if (rt == exmem_reg.current().wb_reg.value()) rt_hazard = true;
+            if ((rs_hazard || rt_hazard) && !forward) setStall(2);
+            return;
+        }
+    }
+    // Check MEM/WB pipeline register for hazards
+    else if (rt != 0) {
+        if (memwb_reg.current().instr && memwb_reg.current().wb_reg.has_value()) {
+            if (rs == memwb_reg.current().wb_reg.value()) rs_hazard = true;
+            if (rt == memwb_reg.current().wb_reg.value()) rt_hazard = true;
+            if ((rs_hazard || rt_hazard) && !forward) setStall(1);
+            return;
+        }
+    }
+
+    // No hazards or forwarding is ON, continue with decode
+    data.reg_a = register_file->read(rs);
+    data.branch_taken = std::nullopt;
+
+    uint8_t opcode = data.instr->getOpcode();
+    // Determine what to put in reg_b (i.e. ALU Source 2) and wb_reg based on instruction type
+    if (mips_lite::is_branch_instruction(opcode)) {
+        // Branch instructions
+        data.wb_reg = std::nullopt;  // No write-back for branches
+        data.branch_taken = false;   // Initialize branch flag
+
+        if (opcode == mips_lite::opcode::BEQ) {
+            // BEQ needs rt value for comparison
+            data.reg_b = register_file->read(rt);
+        } else {
+            // BZ uses immediate for branch offset
+            data.reg_b = data.instr->getImmediate();
+        }
+    } else if (mips_lite::is_jump_instruction(opcode)) {
+        // Jump instructions
+        data.wb_reg = std::nullopt;  // No write-back for jumps
+        data.branch_taken = false;   // Initialize branch flag
+
+        // JR uses rs as jump target
+        // No need to set reg_b for JR
+    } else if (opcode == mips_lite::opcode::STW) {
+        // Store instructions
+        data.wb_reg = std::nullopt;               // No write-back for stores
+        data.reg_b = data.instr->getImmediate();  // Offset
+        data.str_val = register_file->read(rt);   // Value to store
+    } else if (data.instr->hasRd()) {
+        // R-type instructions (register-register)
+        data.reg_b = register_file->read(rt);
+        data.wb_reg = data.instr->getRd();
+    } else if (data.instr->hasImmediate()) {
+        // I-type instructions (register-immediate)
+        data.reg_b = data.instr->getImmediate();
+        data.wb_reg = data.instr->getRt();
+    } else {
+        throw std::invalid_argument("Invalid instruction type for decode stage");
+    }
+
+    // Pass data to ID/EX pipeline register
+    idex_reg.setNext(data);
 }
 
 void FunctionalSimulator::execute(Instruction* instr) {
-    // Stub: Perform ALU operation specified or calculate branch effective address
-    (void)instr;
+    (void)instr;  // TODO: Probably remove this
+    PipelineData data = idex_reg.current();
+    if (data.instr == nullptr) {
+        exmem_reg.setNext(data);
+        return;
+    }
+
+    switch (data.instr->getOpcode()) {
+        // Arithmetic Operations
+        case mips_lite::opcode::ADD:
+        case mips_lite::opcode::ADDI:
+            data.result = data.reg_a + data.reg_b;
+            break;
+
+        case mips_lite::opcode::SUB:
+        case mips_lite::opcode::SUBI:
+            data.result = data.reg_a - data.reg_b;
+            break;
+
+        case mips_lite::opcode::MUL:
+        case mips_lite::opcode::MULI:
+            data.result = data.reg_a * data.reg_b;
+            break;
+
+        // Logical Operations
+        case mips_lite::opcode::OR:
+        case mips_lite::opcode::ORI:
+            data.result = data.reg_a | data.reg_b;
+            break;
+
+        case mips_lite::opcode::AND:
+        case mips_lite::opcode::ANDI:
+            data.result = data.reg_a & data.reg_b;
+            break;
+
+        case mips_lite::opcode::XOR:
+        case mips_lite::opcode::XORI:
+            data.result = data.reg_a ^ data.reg_b;
+            break;
+
+        // Memory Effective Address Calculation -> Load and Store
+        case mips_lite::opcode::LDW:
+            data.result = data.reg_a + data.reg_b;
+            break;
+
+        case mips_lite::opcode::STW:
+            data.result = data.reg_a + data.reg_b;
+            break;
+
+        // Control Flow Ops
+        case mips_lite::opcode::BZ:
+            if (data.reg_a == 0) {
+                data.result = pc + (data.reg_b * 4);
+                data.branch_taken = true;  // Indicate that a branch was taken
+                break;
+            }
+            data.branch_taken = false;
+            break;
+
+        case mips_lite::opcode::BEQ:
+            if (data.reg_a == data.reg_b) {
+                // Branch if equal - calculate target address
+                int32_t offset = data.instr->getImmediate();
+                data.result = pc + (offset * 4);
+                data.branch_taken = true;  // Indicate that a branch was taken
+            } else {
+                // No branch, PC will be updated normally
+                data.branch_taken = false;
+                data.result = pc;  // No change (PC should be updated in fetch stage)
+            }
+            break;
+
+        case mips_lite::opcode::JR:
+            // Jump register - use the value in Rs as the new PC
+            data.result = data.reg_a;
path: src/functional_simulator.cpp
user: login: tnl3pdx
body: This is a screenshot from Lecture 2B, page 15.

![image](https://github.com/user-attachments/assets/07823152-c866-4f6e-b4b8-e7e1f7de9b9f)

created_at: 2025-05-19T20:59:43Z
author_association: COLLABORATOR
diff_hunk: @@ -46,12 +50,146 @@ void FunctionalSimulator::instructionFetch() {
 }
 
 void FunctionalSimulator::instructionDecode() {
-    // Stub: Decode the instruction (parse operands, maybe identify hazards)
-    // Set dirty bit on registers which will be written back to later
+    // Operation: Decode instruction and access register file to read the register sources
+    
+    PipelineStageData* id_data = pipeline[PipelineStage::DECODE].get();
+
+    if (id_data == nullptr || stall > 0) {
+        // No instruction to decode
+        return;
+    }
+
+    uint8_t rs = id_data->instruction->getRs();
+    uint8_t rt = id_data->instruction->getRt();
+    
+    id_data->rs_value = readRegisterValue(rs);
+
+    // Determine register Destination & optional second source register
+    if(id_data->instruction->hasRd()) {
+        id_data->dest_reg = id_data->instruction->getRd();
+        //R-type instruction has two source registers, get second source register
+        id_data->rt_value = readRegisterValue(rt);
+    }
+    else if(!id_data->instruction->hasRd() && isRegisterWriteInstruction(id_data->instruction.get())) {
+        // Destination register is rt 
+        id_data->dest_reg = id_data->instruction->getRt();
+    }
+    else {
+        // No destination register
+        id_data->dest_reg = std::nullopt;
+        if(id_data->instruction->getOpcode() == mips_lite::opcode::BEQ) {
+            // BEQ has two source registers
+            id_data->rt_value = readRegisterValue(rt);
+        }
+    }
 }
 
 void FunctionalSimulator::execute() {
-    // Stub: Perform ALU operation specified or calculate branch effective address
+    if(isStageEmpty(PipelineStage::EXECUTE)) {
+        // No instruction to execute
+        return;
+    }
+    PipelineStageData* ex_data = pipeline[PipelineStage::EXECUTE].get(); 
+
+    switch (ex_data->instruction->getOpcode()) {
+        // Arithmetic Operations
+        // Operands are always signed
+        case mips_lite::opcode::ADD:
+            ex_data->alu_result = ex_data->getRsValueSigned() + ex_data->getRtValueSigned();
path: src/functional_simulator.cpp
user: login: reecewayt
body: @nkanderson I ran into this when going through the project specs but she says that all arithmetic instructions, effective address calculations, or branch target computations should be treated as signed integers. I originally create the reg file with uint32 types and I'm wondering if it makes more sense to store signed values in our register file? I can't think of any direct consequences of changing this? I'm thinking we refactor the register file and remove these casting methods from our functional simulator. 
created_at: 2025-05-22T20:21:42Z
author_association: COLLABORATOR
diff_hunk: @@ -46,12 +50,146 @@ void FunctionalSimulator::instructionFetch() {
 }
 
 void FunctionalSimulator::instructionDecode() {
-    // Stub: Decode the instruction (parse operands, maybe identify hazards)
-    // Set dirty bit on registers which will be written back to later
+    // Operation: Decode instruction and access register file to read the register sources
+    
+    PipelineStageData* id_data = pipeline[PipelineStage::DECODE].get();
+
+    if (id_data == nullptr || stall > 0) {
+        // No instruction to decode
+        return;
+    }
+
+    uint8_t rs = id_data->instruction->getRs();
+    uint8_t rt = id_data->instruction->getRt();
+    
+    id_data->rs_value = readRegisterValue(rs);
+
+    // Determine register Destination & optional second source register
+    if(id_data->instruction->hasRd()) {
+        id_data->dest_reg = id_data->instruction->getRd();
+        //R-type instruction has two source registers, get second source register
+        id_data->rt_value = readRegisterValue(rt);
+    }
+    else if(!id_data->instruction->hasRd() && isRegisterWriteInstruction(id_data->instruction.get())) {
+        // Destination register is rt 
+        id_data->dest_reg = id_data->instruction->getRt();
+    }
+    else {
+        // No destination register
+        id_data->dest_reg = std::nullopt;
+        if(id_data->instruction->getOpcode() == mips_lite::opcode::BEQ) {
+            // BEQ has two source registers
+            id_data->rt_value = readRegisterValue(rt);
+        }
+    }
 }
 
 void FunctionalSimulator::execute() {
-    // Stub: Perform ALU operation specified or calculate branch effective address
+    if(isStageEmpty(PipelineStage::EXECUTE)) {
+        // No instruction to execute
+        return;
+    }
+    PipelineStageData* ex_data = pipeline[PipelineStage::EXECUTE].get(); 
+
+    switch (ex_data->instruction->getOpcode()) {
+        // Arithmetic Operations
+        // Operands are always signed
+        case mips_lite::opcode::ADD:
+            ex_data->alu_result = ex_data->getRsValueSigned() + ex_data->getRtValueSigned();
path: src/functional_simulator.cpp
user: login: nkanderson
body: Hmm, yeah, that seems like it makes sense to me. Since we're not likely to manipulate the bits directly, it seems like it should work out best if they are all of the same signedness. And hopefully if we have good unit tests, it will catch any edge cases where it's not behaving as we expect.
created_at: 2025-05-22T21:30:21Z
author_association: OWNER
diff_hunk: @@ -46,12 +50,146 @@ void FunctionalSimulator::instructionFetch() {
 }
 
 void FunctionalSimulator::instructionDecode() {
-    // Stub: Decode the instruction (parse operands, maybe identify hazards)
-    // Set dirty bit on registers which will be written back to later
+    // Operation: Decode instruction and access register file to read the register sources
+    
+    PipelineStageData* id_data = pipeline[PipelineStage::DECODE].get();
+
+    if (id_data == nullptr || stall > 0) {
+        // No instruction to decode
+        return;
+    }
+
+    uint8_t rs = id_data->instruction->getRs();
+    uint8_t rt = id_data->instruction->getRt();
+    
+    id_data->rs_value = readRegisterValue(rs);
+
+    // Determine register Destination & optional second source register
+    if(id_data->instruction->hasRd()) {
+        id_data->dest_reg = id_data->instruction->getRd();
+        //R-type instruction has two source registers, get second source register
+        id_data->rt_value = readRegisterValue(rt);
+    }
+    else if(!id_data->instruction->hasRd() && isRegisterWriteInstruction(id_data->instruction.get())) {
+        // Destination register is rt 
+        id_data->dest_reg = id_data->instruction->getRt();
+    }
+    else {
+        // No destination register
+        id_data->dest_reg = std::nullopt;
+        if(id_data->instruction->getOpcode() == mips_lite::opcode::BEQ) {
+            // BEQ has two source registers
+            id_data->rt_value = readRegisterValue(rt);
+        }
+    }
 }
 
 void FunctionalSimulator::execute() {
-    // Stub: Perform ALU operation specified or calculate branch effective address
+    if(isStageEmpty(PipelineStage::EXECUTE)) {
+        // No instruction to execute
+        return;
+    }
+    PipelineStageData* ex_data = pipeline[PipelineStage::EXECUTE].get(); 
+
+    switch (ex_data->instruction->getOpcode()) {
+        // Arithmetic Operations
+        // Operands are always signed
+        case mips_lite::opcode::ADD:
+            ex_data->alu_result = ex_data->getRsValueSigned() + ex_data->getRtValueSigned();
path: src/functional_simulator.cpp
user: login: reecewayt
body: Cool I opened an issue as a reminder to address in another PR #37 
created_at: 2025-05-22T21:45:16Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,226 @@
+/**
+ * @file decode_stage_tests.cpp
+ * @brief Tests for Decode stage of pipeline
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <memory>
+
+#include "functional_simulator.h"
+#include "memory_interface.h"
+#include "mips_instruction.h"
+#include "register_file.h"
+#include "stats.h"
+
+using ::testing::NiceMock;
+
+// Only mock the memory parser
+class MockMemoryParser : public IMemoryParser {
+   public:
+    MOCK_METHOD(uint32_t, readInstruction, (uint32_t address), (override));
+    MOCK_METHOD(uint32_t, readMemory, (uint32_t address), (override));
+    MOCK_METHOD(void, writeMemory, (uint32_t address, uint32_t value), (override));
+};
+
+class DecodeStageTest : public ::testing::Test {
+   protected:
+    RegisterFile rf;
+    Stats stats;
+    NiceMock<MockMemoryParser> mem;
+    std::unique_ptr<FunctionalSimulator> sim;
+
+    // Sample instructions to use in tests
+    // R-type: ADD $rd, $rs, $rt (opcode=0, rs=1, rt=2, rd=3)
+    const uint32_t r_type_add_instr = 0x00221800;  // ADD $3, $1, $2
+    // I-type: ADDI $rt, $rs, immediate (opcode=1, rs=4, rt=5, imm=100)
+    const uint32_t i_type_addi_pos_instr = 0x04850064;  // ADDI $5, $4, 100
+    // I-type with negative immediate: ADDI $rt, $rs, immediate (opcode=1, rs=6, rt=7, imm=-100)
+    const uint32_t i_type_addi_neg_instr = 0x04C7FF9C;  // ADDI $7, $6, -100
+    // I-type memory: LDW $rt, imm($rs) (opcode=12, rs=8, rt=9, imm=200)
+    const uint32_t i_type_ldw_instr = 0x310900C8;  // LDW $9, 200($8)
+    // I-type branch: BEQ $rs, $rt, imm (opcode=15, rs=10, rt=11, imm=-50)
+    const uint32_t i_type_beq_instr = 0x3D4BFFCE;  // BEQ $10, $11, -50
+
+    void SetUp() override {
+        sim = std::make_unique<FunctionalSimulator>(&rf, &stats, &mem, true);
+
+        // Initialize some register values for testing
+        rf.write(1, 100);  // $1 = 100
+        rf.write(2, 200);  // $2 = 200
+        rf.write(3, 300);  // $3 = 300
+        rf.write(4, 400);  // $4 = 400
+        rf.write(5, 500);  // $5 = 500
+        rf.write(6, 600);  // $6 = 600
+        rf.write(7, 700);  // $7 = 700
+        rf.write(8, 800);  // $8 = 800
+        rf.write(9, 900);  // $9 = 900
+        rf.write(10, 1000);  // $10 = 1000
+        rf.write(11, 1100);  // $11 = 1100
+
+        
+    }
+
+    // Helper to properly initialize the decode stage with an instruction
+    void setupDecodeStage(uint32_t instruction_word) {
+        // Create a new instruction object
+        auto instr = std::make_unique<Instruction>(instruction_word);
+
+        // Create pipeline stage data for decode stage
+        auto decode_data = std::make_unique<PipelineStageData>();
+        decode_data->instruction = std::move(instr);
+        decode_data->pc = sim->getPC();  // Use current PC
+
+        // Place the instruction in the decode stage using getPipeline()
+        sim->getPipeline()[FunctionalSimulator::PipelineStage::DECODE] = std::move(decode_data);
+    }
+
+    // Helper to get decode stage data
+    const PipelineStageData* getDecodeStageData() {
+        return sim->getPipelineStage(FunctionalSimulator::PipelineStage::DECODE);
+    }
+
+    // Helper to get execute stage data (after decode processes)
+    const PipelineStageData* getExecuteStageData() {
+        return sim->getPipelineStage(FunctionalSimulator::PipelineStage::EXECUTE);
+    }
+};
+
+// Test I-type instruction decode (ADDI) - Most basic case
+// This validates: register reading, destination register setting, instruction decoding
+TEST_F(DecodeStageTest, DecodeBasicITypeInstruction) {
+    // This instruction reads $1 (rs=1), adds immediate 50, writes result to $3 (rt=3)
+    
+    
+    // Set up the decode stage with our instruction
+    setupDecodeStage(r_type_add_instr);
path: tests/functional_simulator/decode_stage_tests.cpp
user: login: nkanderson
body: Looks like these first few lines need to be updated, since this is a test for an R-type, right?
created_at: 2025-05-22T21:45:23Z
author_association: OWNER
diff_hunk: @@ -126,24 +264,23 @@ bool FunctionalSimulator::isRegisterWriteInstruction(const Instruction* instr) c
            opcode == mips_lite::opcode::LDW;
 }
 
-std::optional<uint8_t> FunctionalSimulator::getDestinationRegister(const Instruction* instr) const {
-    if (instr == nullptr || !isRegisterWriteInstruction(instr)) {
-        return std::nullopt;
+uint32_t FunctionalSimulator::readRegisterValue(uint8_t reg_num) {
+    
+    if (reg_num == 0) {
+        return 0;  // $0 register always returns 0
+    }               // And never has hazards
+    if(stall > 0) {
+        throw std::runtime_error("Stall detected in ID stage but wasn't properly handled by control logic. Should never attempt to read during a stall");
     }
-
-    // R-type instructions write to rd
-    if (instr->getInstructionType() == mips_lite::InstructionType::R_TYPE) {
-        return instr->getRd();
+    // Check EXE stage for hazards
+    if(!isStageEmpty(PipelineStage::EXECUTE) && pipeline[PipelineStage::EXECUTE]->dest_reg.value() == reg_num) {
+        return pipeline[PipelineStage::EXECUTE]->alu_result;
+    }
+    // Check MEM stage for hazards
+    if(!isStageEmpty(PipelineStage::MEMORY) && pipeline[PipelineStage::MEMORY]->dest_reg.value() == reg_num) {
+        return pipeline[PipelineStage::MEMORY]->alu_result;
+    }
+    else {
+        return register_file->read(reg_num);
path: src/functional_simulator.cpp
user: login: nkanderson
body: I think this will now introduce dependencies between our methods, in terms of their ordering of execution in a given cycle (we were discussing this in #33). So I believe we'll need to make sure we call the decode method prior to calling the execute or memory methods, because otherwise, it would be possible for an `alu_result` to be set ahead of when it actually would be available in a real pipeline with concurrent execution of stages. Does that sound correct to you @reecewayt?
created_at: 2025-05-22T22:04:44Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,244 @@
+/**
+ * @file exe_stage_tests.cpp
+ * @brief Tests for Execute stage of pipeline
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <cstdint>
+#include <memory>
+
+#include "functional_simulator.h"
+#include "memory_interface.h"
+#include "mips_instruction.h"
+#include "mips_lite_defs.h"
+#include "register_file.h"
+#include "stats.h"
+
+using ::testing::NiceMock;
+using ::testing::Return;
+
+// Only mock the memory parser
+class MockMemoryParser : public IMemoryParser {
+   public:
+    MOCK_METHOD(uint32_t, readInstruction, (uint32_t address), (override));
+    MOCK_METHOD(uint32_t, readMemory, (uint32_t address), (override));
+    MOCK_METHOD(void, writeMemory, (uint32_t address, uint32_t value), (override));
+};
+
+class ExecuteStageTest : public ::testing::Test {
+   protected:
+    RegisterFile rf;
+    Stats stats;
+    NiceMock<MockMemoryParser> mem;
+    std::unique_ptr<FunctionalSimulator> sim;
+
+    static constexpr uint32_t ADD_INSTR = 0x00221800;      // ADD $3, $1, $2
+    static constexpr uint32_t SUB_INSTR = 0x08221800;      // SUB $3, $1, $2
+    static constexpr uint32_t MUL_INSTR = 0x10221800;      // MUL $3, $1, $2
+    static constexpr uint32_t AND_INSTR = 0x20221800;      // AND $3, $1, $2
+    static constexpr uint32_t BEQ_INSTR = 0x3C280032;      // BEQ $1, $8, 50
+    static constexpr uint32_t BZ_INSTR = 0x38280032;       // BZ $1, 50
+    static constexpr uint32_t JR_INSTR = 0x40280000;       // JR $5
+    static constexpr uint32_t LDW_INSTR = 0x30480064;      // LDW $8, 100($2)
path: tests/functional_simulator/exe_stage_tests.cpp
user: login: nkanderson
body: Not something that has to be done in this PR, but since the decode tests are using some of these same instructions and we may want them for other tests, it would be nice if we could set these up as shared test fixtures (at least shared amongst the functional simulator tests).
created_at: 2025-05-22T22:07:14Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,244 @@
+/**
+ * @file exe_stage_tests.cpp
+ * @brief Tests for Execute stage of pipeline
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <cstdint>
+#include <memory>
+
+#include "functional_simulator.h"
+#include "memory_interface.h"
+#include "mips_instruction.h"
+#include "mips_lite_defs.h"
+#include "register_file.h"
+#include "stats.h"
+
+using ::testing::NiceMock;
+using ::testing::Return;
+
+// Only mock the memory parser
+class MockMemoryParser : public IMemoryParser {
+   public:
+    MOCK_METHOD(uint32_t, readInstruction, (uint32_t address), (override));
+    MOCK_METHOD(uint32_t, readMemory, (uint32_t address), (override));
+    MOCK_METHOD(void, writeMemory, (uint32_t address, uint32_t value), (override));
+};
+
+class ExecuteStageTest : public ::testing::Test {
+   protected:
+    RegisterFile rf;
+    Stats stats;
+    NiceMock<MockMemoryParser> mem;
+    std::unique_ptr<FunctionalSimulator> sim;
+
+    static constexpr uint32_t ADD_INSTR = 0x00221800;      // ADD $3, $1, $2
+    static constexpr uint32_t SUB_INSTR = 0x08221800;      // SUB $3, $1, $2
+    static constexpr uint32_t MUL_INSTR = 0x10221800;      // MUL $3, $1, $2
+    static constexpr uint32_t AND_INSTR = 0x20221800;      // AND $3, $1, $2
+    static constexpr uint32_t BEQ_INSTR = 0x3C280032;      // BEQ $1, $8, 50
+    static constexpr uint32_t BZ_INSTR = 0x38280032;       // BZ $1, 50
+    static constexpr uint32_t JR_INSTR = 0x40280000;       // JR $5
+    static constexpr uint32_t LDW_INSTR = 0x30480064;      // LDW $8, 100($2)
+
+    void SetUp() override { 
+        sim = std::make_unique<FunctionalSimulator>(&rf, &stats, &mem, true);
+    }
+
+    // Helper to set up execute stage with an instruction and data
+    void setupExecuteStage(uint32_t instruction_word, uint32_t rs_value, uint32_t rt_value, 
+                          std::optional<uint8_t> dest_reg = std::nullopt, uint32_t pc_value = 1000) {
+        // Create instruction
+        auto instr = std::make_unique<Instruction>(instruction_word);
+
+        // Create pipeline stage data for execute stage
+        auto execute_data = std::make_unique<PipelineStageData>();
+        execute_data->instruction = std::move(instr);
+        execute_data->rs_value = rs_value;
+        execute_data->rt_value = rt_value;
+        execute_data->dest_reg = dest_reg;
+        execute_data->pc = pc_value;
+
+        // Place the instruction in the execute stage
+        sim->getPipeline()[FunctionalSimulator::PipelineStage::EXECUTE] = std::move(execute_data);
+    }
+
+    // Helper to get execute stage data
+    const PipelineStageData* getExecuteStageData() {
+        return sim->getPipelineStage(FunctionalSimulator::PipelineStage::EXECUTE);
+    }
+
+    // Helper to get memory stage data (after execute processes)
+    const PipelineStageData* getMemoryStageData() {
+        return sim->getPipelineStage(FunctionalSimulator::PipelineStage::MEMORY);
+    }
+};
+
+// Test ADD instruction execution
+TEST_F(ExecuteStageTest, ExecuteADD) {
+    // Set up execute stage with ADD instruction and test values
+    setupExecuteStage(ADD_INSTR, 10, 20, 3);  // rs=10, rt=20, dest=3
+
+    // Verify instruction is in execute stage
+    const PipelineStageData* execute_data = getExecuteStageData();
+    ASSERT_NE(execute_data, nullptr);
+    ASSERT_NE(execute_data->instruction, nullptr);
+
+    // Execute the instruction
+    sim->execute();
+
+    // Verify the ALU result is correct (10 + 20 = 30)
+    EXPECT_EQ(execute_data->alu_result, 30);
+    EXPECT_TRUE(execute_data->dest_reg.has_value());
+    EXPECT_EQ(execute_data->dest_reg.value(), 3);
+}
+
+// Test ADD instruction but make operands negative
+TEST_F(ExecuteStageTest, ExecuteADDNegative) {
+    // Set up execute stage with ADD instruction and negative values
+    setupExecuteStage(ADD_INSTR, static_cast<uint32_t>(-10), static_cast<uint32_t>(-20), 3);  // rs=-10, rt=-20, dest=3
+
+    // Execute the instruction
+    sim->execute();
+
+    // Verify the ALU result is correct (-10 + -20 = -30)
+    const PipelineStageData* execute_data = getExecuteStageData();
+    EXPECT_EQ(execute_data->alu_result, -30);
+    EXPECT_TRUE(execute_data->dest_reg.has_value());
+    EXPECT_EQ(execute_data->dest_reg.value(), 3);
+}
+
+// Test SUB instruction execution
+TEST_F(ExecuteStageTest, ExecuteSUB) {
+    // Set up execute stage with SUB instruction and test values
+    setupExecuteStage(SUB_INSTR, 30, 12, 3);  // rs=30, rt=12, dest=3
+
+    // Execute the instruction
+    sim->execute();
+
+    // Verify the ALU result is correct (30 - 12 = 18)
+    const PipelineStageData* execute_data = getExecuteStageData();
+    EXPECT_EQ(execute_data->alu_result, 18);
+    EXPECT_TRUE(execute_data->dest_reg.has_value());
+    EXPECT_EQ(execute_data->dest_reg.value(), 3);
+}
+
+// Test MUL instruction execution
+TEST_F(ExecuteStageTest, ExecuteMUL) {
+    // Set up execute stage with MUL instruction and test values
+    setupExecuteStage(MUL_INSTR, 5, 7, 3);  // rs=5, rt=7, dest=3
+
+    // Execute the instruction
+    sim->execute();
+
+    // Verify the ALU result is correct (5 * 7 = 35)
+    const PipelineStageData* execute_data = getExecuteStageData();
+    EXPECT_EQ(execute_data->alu_result, 35);
+    EXPECT_TRUE(execute_data->dest_reg.has_value());
+    EXPECT_EQ(execute_data->dest_reg.value(), 3);
+}
+
+// Test AND instruction execution
+TEST_F(ExecuteStageTest, ExecuteAND) {
+    // Set up execute stage with AND instruction and test values
+    setupExecuteStage(AND_INSTR, 0b1010, 0b1100, 3);  // rs=10, rt=12, dest=3
+
+    // Execute the instruction
+    sim->execute();
+
+    // Verify the ALU result is correct (0b1010 & 0b1100 = 0b1000 = 8)
+    const PipelineStageData* execute_data = getExecuteStageData();
+    EXPECT_EQ(execute_data->alu_result, 8);
+    EXPECT_TRUE(execute_data->dest_reg.has_value());
+    EXPECT_EQ(execute_data->dest_reg.value(), 3);
+}
+
+// Test BEQ instruction execution with branch taken
+TEST_F(ExecuteStageTest, ExecuteBEQTaken) {
+    // Set up execute stage with BEQ instruction and equal values
+    setupExecuteStage(BEQ_INSTR, 25, 25, std::nullopt, 1000);  // rs=25, rt=25, no dest, pc=1000
+
+    // Execute the instruction
+    sim->execute();
+
+    // Verify branch was taken and target address calculated correctly
+    const PipelineStageData* execute_data = getExecuteStageData();
+    EXPECT_EQ(execute_data->alu_result, 1200);  // 1000 + (50*4) = 1200
+    EXPECT_TRUE(sim->isBranchTaken());          // Use getter method
+    EXPECT_FALSE(execute_data->dest_reg.has_value());  // No writeback for branches
+}
+
+// Test BEQ instruction execution with branch not taken
+TEST_F(ExecuteStageTest, ExecuteBEQNotTaken) {
+    // Set up execute stage with BEQ instruction and unequal values
+    setupExecuteStage(BEQ_INSTR, 25, 30, std::nullopt, 1000);  // rs=25, rt=30, no dest, pc=1000
+
+    // Execute the instruction
+    sim->execute();
+
+    // Verify branch was not taken
+    const PipelineStageData* execute_data = getExecuteStageData();
+    EXPECT_FALSE(sim->isBranchTaken());         // Use getter method
+    EXPECT_FALSE(execute_data->dest_reg.has_value());
+}
+
+// Test BZ instruction with branch taken
+TEST_F(ExecuteStageTest, ExecuteBZTaken) {
+    // Set up execute stage with BZ instruction and zero value
+    setupExecuteStage(BZ_INSTR, 0, 50, std::nullopt, 1000);  // rs=0, rt=50 (offset), no dest, pc=1000
+
+    // Execute the instruction
+    sim->execute();
+
+    // Verify branch was taken (50*4 + 1000 = 1200)
+    const PipelineStageData* execute_data = getExecuteStageData();
+    EXPECT_EQ(execute_data->alu_result, 1200);
+    EXPECT_TRUE(sim->isBranchTaken());          // Use getter method
+    EXPECT_FALSE(execute_data->dest_reg.has_value());
+}
+
+// Test BZ instruction with branch not taken
+TEST_F(ExecuteStageTest, ExecuteBZNotTaken) {
+    // Set up execute stage with BZ instruction and non-zero value
+    setupExecuteStage(BZ_INSTR, 5, 50, std::nullopt, 1000);  // rs=5 (non-zero), rt=50, no dest, pc=1000
+
+    // Execute the instruction
+    sim->execute();
+
+    // Verify branch was not taken
+    const PipelineStageData* execute_data = getExecuteStageData();
+    EXPECT_FALSE(sim->isBranchTaken());         // Use getter method
+    EXPECT_FALSE(execute_data->dest_reg.has_value());
+}
+
+// Test JR instruction execution
+TEST_F(ExecuteStageTest, ExecuteJR) {
+    // Set up execute stage with JR instruction
+    setupExecuteStage(JR_INSTR, 2048, 0, std::nullopt);  // rs=2048 (jump target), rt=0, no dest
+
+    // Execute the instruction
+    sim->execute();
+
+    // Verify jump address is correct (should be value in rs)
+    const PipelineStageData* execute_data = getExecuteStageData();
+    EXPECT_EQ(execute_data->alu_result, 2048);
+    EXPECT_TRUE(sim->isBranchTaken());          // Use getter method
+    EXPECT_FALSE(execute_data->dest_reg.has_value());
+}
+
+// Test LDW instruction execution (Load Word)
+TEST_F(ExecuteStageTest, ExecuteLDW) {
+    // Set up execute stage with LDW instruction
+    setupExecuteStage(LDW_INSTR, 1000, 100, 8);  // rs=1000 (base), rt=100 (offset), dest=8
path: tests/functional_simulator/exe_stage_tests.cpp
user: login: nkanderson
body: For load, rt should be used as the destination register, and the offset is taken from the immediate value. This does seem to execute as intended with the instruction you created above (with `LDW $8, 100($2)`), but these comments are a bit misleading. I think in an ideal world, we might update `setupExecuteStage` to use some of the same functions or basic logic as used in decode to get the setup without having to pass in the values, because that's a bit more prone to error. But that might take a minute to set up, so maybe we could just update the comments here for now?

More generally, looking at this test has got me wondering about the purpose of checking `dest_reg` in these tests. Is it to ensure that the execute method doesn't accidentally modify it? Since the execute method only directly sets `alu_result` and the branch taken control, checking `dest_reg` here otherwise just checks that the `setupExecuteStage` assigned the value. 
created_at: 2025-05-22T22:49:19Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,244 @@
+/**
+ * @file exe_stage_tests.cpp
+ * @brief Tests for Execute stage of pipeline
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <cstdint>
+#include <memory>
+
+#include "functional_simulator.h"
+#include "memory_interface.h"
+#include "mips_instruction.h"
+#include "mips_lite_defs.h"
+#include "register_file.h"
+#include "stats.h"
+
+using ::testing::NiceMock;
+using ::testing::Return;
+
+// Only mock the memory parser
+class MockMemoryParser : public IMemoryParser {
+   public:
+    MOCK_METHOD(uint32_t, readInstruction, (uint32_t address), (override));
+    MOCK_METHOD(uint32_t, readMemory, (uint32_t address), (override));
+    MOCK_METHOD(void, writeMemory, (uint32_t address, uint32_t value), (override));
+};
+
+class ExecuteStageTest : public ::testing::Test {
+   protected:
+    RegisterFile rf;
+    Stats stats;
+    NiceMock<MockMemoryParser> mem;
+    std::unique_ptr<FunctionalSimulator> sim;
+
+    static constexpr uint32_t ADD_INSTR = 0x00221800;      // ADD $3, $1, $2
+    static constexpr uint32_t SUB_INSTR = 0x08221800;      // SUB $3, $1, $2
+    static constexpr uint32_t MUL_INSTR = 0x10221800;      // MUL $3, $1, $2
+    static constexpr uint32_t AND_INSTR = 0x20221800;      // AND $3, $1, $2
+    static constexpr uint32_t BEQ_INSTR = 0x3C280032;      // BEQ $1, $8, 50
+    static constexpr uint32_t BZ_INSTR = 0x38280032;       // BZ $1, 50
+    static constexpr uint32_t JR_INSTR = 0x40280000;       // JR $5
+    static constexpr uint32_t LDW_INSTR = 0x30480064;      // LDW $8, 100($2)
+
+    void SetUp() override { 
+        sim = std::make_unique<FunctionalSimulator>(&rf, &stats, &mem, true);
+    }
+
+    // Helper to set up execute stage with an instruction and data
+    void setupExecuteStage(uint32_t instruction_word, uint32_t rs_value, uint32_t rt_value, 
+                          std::optional<uint8_t> dest_reg = std::nullopt, uint32_t pc_value = 1000) {
+        // Create instruction
+        auto instr = std::make_unique<Instruction>(instruction_word);
+
+        // Create pipeline stage data for execute stage
+        auto execute_data = std::make_unique<PipelineStageData>();
+        execute_data->instruction = std::move(instr);
+        execute_data->rs_value = rs_value;
+        execute_data->rt_value = rt_value;
+        execute_data->dest_reg = dest_reg;
+        execute_data->pc = pc_value;
+
+        // Place the instruction in the execute stage
+        sim->getPipeline()[FunctionalSimulator::PipelineStage::EXECUTE] = std::move(execute_data);
+    }
+
+    // Helper to get execute stage data
+    const PipelineStageData* getExecuteStageData() {
+        return sim->getPipelineStage(FunctionalSimulator::PipelineStage::EXECUTE);
+    }
+
+    // Helper to get memory stage data (after execute processes)
+    const PipelineStageData* getMemoryStageData() {
+        return sim->getPipelineStage(FunctionalSimulator::PipelineStage::MEMORY);
+    }
+};
+
+// Test ADD instruction execution
+TEST_F(ExecuteStageTest, ExecuteADD) {
+    // Set up execute stage with ADD instruction and test values
+    setupExecuteStage(ADD_INSTR, 10, 20, 3);  // rs=10, rt=20, dest=3
+
+    // Verify instruction is in execute stage
+    const PipelineStageData* execute_data = getExecuteStageData();
+    ASSERT_NE(execute_data, nullptr);
+    ASSERT_NE(execute_data->instruction, nullptr);
+
+    // Execute the instruction
+    sim->execute();
+
+    // Verify the ALU result is correct (10 + 20 = 30)
+    EXPECT_EQ(execute_data->alu_result, 30);
+    EXPECT_TRUE(execute_data->dest_reg.has_value());
+    EXPECT_EQ(execute_data->dest_reg.value(), 3);
+}
+
+// Test ADD instruction but make operands negative
+TEST_F(ExecuteStageTest, ExecuteADDNegative) {
+    // Set up execute stage with ADD instruction and negative values
+    setupExecuteStage(ADD_INSTR, static_cast<uint32_t>(-10), static_cast<uint32_t>(-20), 3);  // rs=-10, rt=-20, dest=3
+
+    // Execute the instruction
+    sim->execute();
+
+    // Verify the ALU result is correct (-10 + -20 = -30)
+    const PipelineStageData* execute_data = getExecuteStageData();
+    EXPECT_EQ(execute_data->alu_result, -30);
+    EXPECT_TRUE(execute_data->dest_reg.has_value());
+    EXPECT_EQ(execute_data->dest_reg.value(), 3);
+}
+
+// Test SUB instruction execution
+TEST_F(ExecuteStageTest, ExecuteSUB) {
+    // Set up execute stage with SUB instruction and test values
+    setupExecuteStage(SUB_INSTR, 30, 12, 3);  // rs=30, rt=12, dest=3
+
+    // Execute the instruction
+    sim->execute();
+
+    // Verify the ALU result is correct (30 - 12 = 18)
+    const PipelineStageData* execute_data = getExecuteStageData();
+    EXPECT_EQ(execute_data->alu_result, 18);
+    EXPECT_TRUE(execute_data->dest_reg.has_value());
+    EXPECT_EQ(execute_data->dest_reg.value(), 3);
+}
+
+// Test MUL instruction execution
+TEST_F(ExecuteStageTest, ExecuteMUL) {
+    // Set up execute stage with MUL instruction and test values
+    setupExecuteStage(MUL_INSTR, 5, 7, 3);  // rs=5, rt=7, dest=3
+
+    // Execute the instruction
+    sim->execute();
+
+    // Verify the ALU result is correct (5 * 7 = 35)
+    const PipelineStageData* execute_data = getExecuteStageData();
+    EXPECT_EQ(execute_data->alu_result, 35);
+    EXPECT_TRUE(execute_data->dest_reg.has_value());
+    EXPECT_EQ(execute_data->dest_reg.value(), 3);
+}
+
+// Test AND instruction execution
+TEST_F(ExecuteStageTest, ExecuteAND) {
+    // Set up execute stage with AND instruction and test values
+    setupExecuteStage(AND_INSTR, 0b1010, 0b1100, 3);  // rs=10, rt=12, dest=3
+
+    // Execute the instruction
+    sim->execute();
+
+    // Verify the ALU result is correct (0b1010 & 0b1100 = 0b1000 = 8)
+    const PipelineStageData* execute_data = getExecuteStageData();
+    EXPECT_EQ(execute_data->alu_result, 8);
+    EXPECT_TRUE(execute_data->dest_reg.has_value());
+    EXPECT_EQ(execute_data->dest_reg.value(), 3);
+}
+
+// Test BEQ instruction execution with branch taken
+TEST_F(ExecuteStageTest, ExecuteBEQTaken) {
+    // Set up execute stage with BEQ instruction and equal values
+    setupExecuteStage(BEQ_INSTR, 25, 25, std::nullopt, 1000);  // rs=25, rt=25, no dest, pc=1000
+
+    // Execute the instruction
+    sim->execute();
+
+    // Verify branch was taken and target address calculated correctly
+    const PipelineStageData* execute_data = getExecuteStageData();
+    EXPECT_EQ(execute_data->alu_result, 1200);  // 1000 + (50*4) = 1200
+    EXPECT_TRUE(sim->isBranchTaken());          // Use getter method
+    EXPECT_FALSE(execute_data->dest_reg.has_value());  // No writeback for branches
+}
+
+// Test BEQ instruction execution with branch not taken
+TEST_F(ExecuteStageTest, ExecuteBEQNotTaken) {
+    // Set up execute stage with BEQ instruction and unequal values
+    setupExecuteStage(BEQ_INSTR, 25, 30, std::nullopt, 1000);  // rs=25, rt=30, no dest, pc=1000
+
+    // Execute the instruction
+    sim->execute();
+
+    // Verify branch was not taken
+    const PipelineStageData* execute_data = getExecuteStageData();
+    EXPECT_FALSE(sim->isBranchTaken());         // Use getter method
+    EXPECT_FALSE(execute_data->dest_reg.has_value());
+}
+
+// Test BZ instruction with branch taken
+TEST_F(ExecuteStageTest, ExecuteBZTaken) {
+    // Set up execute stage with BZ instruction and zero value
+    setupExecuteStage(BZ_INSTR, 0, 50, std::nullopt, 1000);  // rs=0, rt=50 (offset), no dest, pc=1000
+
+    // Execute the instruction
+    sim->execute();
+
+    // Verify branch was taken (50*4 + 1000 = 1200)
+    const PipelineStageData* execute_data = getExecuteStageData();
+    EXPECT_EQ(execute_data->alu_result, 1200);
+    EXPECT_TRUE(sim->isBranchTaken());          // Use getter method
+    EXPECT_FALSE(execute_data->dest_reg.has_value());
+}
+
+// Test BZ instruction with branch not taken
+TEST_F(ExecuteStageTest, ExecuteBZNotTaken) {
+    // Set up execute stage with BZ instruction and non-zero value
+    setupExecuteStage(BZ_INSTR, 5, 50, std::nullopt, 1000);  // rs=5 (non-zero), rt=50, no dest, pc=1000
path: tests/functional_simulator/exe_stage_tests.cpp
user: login: nkanderson
body: Both of these BZ test comments are a bit misleading too, since BZ doesn't use Rt, the immediate value is used for the address calculation. The logic in the method seems great, but it might be worth not setting rt to the same value as the immediate value and making a note of how the calculation is performed.
created_at: 2025-05-22T22:54:42Z
author_association: OWNER
diff_hunk: @@ -126,24 +264,23 @@ bool FunctionalSimulator::isRegisterWriteInstruction(const Instruction* instr) c
            opcode == mips_lite::opcode::LDW;
 }
 
-std::optional<uint8_t> FunctionalSimulator::getDestinationRegister(const Instruction* instr) const {
-    if (instr == nullptr || !isRegisterWriteInstruction(instr)) {
-        return std::nullopt;
+uint32_t FunctionalSimulator::readRegisterValue(uint8_t reg_num) {
+    
+    if (reg_num == 0) {
+        return 0;  // $0 register always returns 0
+    }               // And never has hazards
+    if(stall > 0) {
+        throw std::runtime_error("Stall detected in ID stage but wasn't properly handled by control logic. Should never attempt to read during a stall");
     }
-
-    // R-type instructions write to rd
-    if (instr->getInstructionType() == mips_lite::InstructionType::R_TYPE) {
-        return instr->getRd();
+    // Check EXE stage for hazards
+    if(!isStageEmpty(PipelineStage::EXECUTE) && pipeline[PipelineStage::EXECUTE]->dest_reg.value() == reg_num) {
+        return pipeline[PipelineStage::EXECUTE]->alu_result;
+    }
+    // Check MEM stage for hazards
+    if(!isStageEmpty(PipelineStage::MEMORY) && pipeline[PipelineStage::MEMORY]->dest_reg.value() == reg_num) {
+        return pipeline[PipelineStage::MEMORY]->alu_result;
+    }
+    else {
+        return register_file->read(reg_num);
path: src/functional_simulator.cpp
user: login: reecewayt
body: I think it would be opposite. Execute needs to be called before decode so that decode can get the ALU result if there is a RAW. 
created_at: 2025-05-23T00:25:07Z
author_association: COLLABORATOR
diff_hunk: @@ -126,24 +264,23 @@ bool FunctionalSimulator::isRegisterWriteInstruction(const Instruction* instr) c
            opcode == mips_lite::opcode::LDW;
 }
 
-std::optional<uint8_t> FunctionalSimulator::getDestinationRegister(const Instruction* instr) const {
-    if (instr == nullptr || !isRegisterWriteInstruction(instr)) {
-        return std::nullopt;
+uint32_t FunctionalSimulator::readRegisterValue(uint8_t reg_num) {
+    
+    if (reg_num == 0) {
+        return 0;  // $0 register always returns 0
+    }               // And never has hazards
+    if(stall > 0) {
+        throw std::runtime_error("Stall detected in ID stage but wasn't properly handled by control logic. Should never attempt to read during a stall");
     }
-
-    // R-type instructions write to rd
-    if (instr->getInstructionType() == mips_lite::InstructionType::R_TYPE) {
-        return instr->getRd();
+    // Check EXE stage for hazards
+    if(!isStageEmpty(PipelineStage::EXECUTE) && pipeline[PipelineStage::EXECUTE]->dest_reg.value() == reg_num) {
+        return pipeline[PipelineStage::EXECUTE]->alu_result;
+    }
+    // Check MEM stage for hazards
+    if(!isStageEmpty(PipelineStage::MEMORY) && pipeline[PipelineStage::MEMORY]->dest_reg.value() == reg_num) {
+        return pipeline[PipelineStage::MEMORY]->alu_result;
+    }
+    else {
+        return register_file->read(reg_num);
path: src/functional_simulator.cpp
user: login: nkanderson
body: In hardware though, the ALU result wouldn't be available until the end of the cycle. So we should stall if the data the decoded instruction needs is currently in execute or memory. The instruction may get forwarded data that was computed in the previous cycle - so actually I don't think a valid `alu_result` would be available for the instruction currently in execute. I think we need to check the instruction in the memory and writeback stages for the `alu_result`, and the writeback stage for a memory fetch result.

I think this is where the registers with current and next start to make more sense 🙃 I'm not saying (yet) that I want us to revert to that, but it makes this a bit clearer conceptually.
created_at: 2025-05-23T01:31:41Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,244 @@
+/**
+ * @file exe_stage_tests.cpp
+ * @brief Tests for Execute stage of pipeline
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <cstdint>
+#include <memory>
+
+#include "functional_simulator.h"
+#include "memory_interface.h"
+#include "mips_instruction.h"
+#include "mips_lite_defs.h"
+#include "register_file.h"
+#include "stats.h"
+
+using ::testing::NiceMock;
+using ::testing::Return;
+
+// Only mock the memory parser
+class MockMemoryParser : public IMemoryParser {
+   public:
+    MOCK_METHOD(uint32_t, readInstruction, (uint32_t address), (override));
+    MOCK_METHOD(uint32_t, readMemory, (uint32_t address), (override));
+    MOCK_METHOD(void, writeMemory, (uint32_t address, uint32_t value), (override));
+};
+
+class ExecuteStageTest : public ::testing::Test {
+   protected:
+    RegisterFile rf;
+    Stats stats;
+    NiceMock<MockMemoryParser> mem;
+    std::unique_ptr<FunctionalSimulator> sim;
+
+    static constexpr uint32_t ADD_INSTR = 0x00221800;      // ADD $3, $1, $2
+    static constexpr uint32_t SUB_INSTR = 0x08221800;      // SUB $3, $1, $2
+    static constexpr uint32_t MUL_INSTR = 0x10221800;      // MUL $3, $1, $2
+    static constexpr uint32_t AND_INSTR = 0x20221800;      // AND $3, $1, $2
+    static constexpr uint32_t BEQ_INSTR = 0x3C280032;      // BEQ $1, $8, 50
+    static constexpr uint32_t BZ_INSTR = 0x38280032;       // BZ $1, 50
+    static constexpr uint32_t JR_INSTR = 0x40280000;       // JR $5
+    static constexpr uint32_t LDW_INSTR = 0x30480064;      // LDW $8, 100($2)
path: tests/functional_simulator/exe_stage_tests.cpp
user: login: reecewayt
body: I agree, maybe we should start a nice to have list of things to fix up if we have time. I have some other thoughts for how I'd like to improve the instruction class and mips lite namespace. 
created_at: 2025-05-23T01:31:59Z
author_association: COLLABORATOR
diff_hunk: @@ -126,24 +264,23 @@ bool FunctionalSimulator::isRegisterWriteInstruction(const Instruction* instr) c
            opcode == mips_lite::opcode::LDW;
 }
 
-std::optional<uint8_t> FunctionalSimulator::getDestinationRegister(const Instruction* instr) const {
-    if (instr == nullptr || !isRegisterWriteInstruction(instr)) {
-        return std::nullopt;
+uint32_t FunctionalSimulator::readRegisterValue(uint8_t reg_num) {
+    
+    if (reg_num == 0) {
+        return 0;  // $0 register always returns 0
+    }               // And never has hazards
+    if(stall > 0) {
+        throw std::runtime_error("Stall detected in ID stage but wasn't properly handled by control logic. Should never attempt to read during a stall");
     }
-
-    // R-type instructions write to rd
-    if (instr->getInstructionType() == mips_lite::InstructionType::R_TYPE) {
-        return instr->getRd();
+    // Check EXE stage for hazards
+    if(!isStageEmpty(PipelineStage::EXECUTE) && pipeline[PipelineStage::EXECUTE]->dest_reg.value() == reg_num) {
+        return pipeline[PipelineStage::EXECUTE]->alu_result;
+    }
+    // Check MEM stage for hazards
+    if(!isStageEmpty(PipelineStage::MEMORY) && pipeline[PipelineStage::MEMORY]->dest_reg.value() == reg_num) {
+        return pipeline[PipelineStage::MEMORY]->alu_result;
+    }
+    else {
+        return register_file->read(reg_num);
path: src/functional_simulator.cpp
user: login: nkanderson
body: What makes it a bit more confusing with this combination of hazard check and forwarding is that I think you can check for hazards as you've done here, but you can't directly forward these values.
created_at: 2025-05-23T01:33:54Z
author_association: OWNER
diff_hunk: @@ -126,24 +264,23 @@ bool FunctionalSimulator::isRegisterWriteInstruction(const Instruction* instr) c
            opcode == mips_lite::opcode::LDW;
 }
 
-std::optional<uint8_t> FunctionalSimulator::getDestinationRegister(const Instruction* instr) const {
-    if (instr == nullptr || !isRegisterWriteInstruction(instr)) {
-        return std::nullopt;
+uint32_t FunctionalSimulator::readRegisterValue(uint8_t reg_num) {
+    
+    if (reg_num == 0) {
+        return 0;  // $0 register always returns 0
+    }               // And never has hazards
+    if(stall > 0) {
+        throw std::runtime_error("Stall detected in ID stage but wasn't properly handled by control logic. Should never attempt to read during a stall");
     }
-
-    // R-type instructions write to rd
-    if (instr->getInstructionType() == mips_lite::InstructionType::R_TYPE) {
-        return instr->getRd();
+    // Check EXE stage for hazards
+    if(!isStageEmpty(PipelineStage::EXECUTE) && pipeline[PipelineStage::EXECUTE]->dest_reg.value() == reg_num) {
+        return pipeline[PipelineStage::EXECUTE]->alu_result;
+    }
+    // Check MEM stage for hazards
+    if(!isStageEmpty(PipelineStage::MEMORY) && pipeline[PipelineStage::MEMORY]->dest_reg.value() == reg_num) {
+        return pipeline[PipelineStage::MEMORY]->alu_result;
+    }
+    else {
+        return register_file->read(reg_num);
path: src/functional_simulator.cpp
user: login: nkanderson
body: Hmm, I guess I am looking at some of the slides, and they show forwarding to the EX stage, which I guess implies the decode can get the value in the same cycle. Or the forwarded value is selected in the EX stage. I wish I could find a diagram that included the hardware used for the forwarding control.
created_at: 2025-05-23T01:43:47Z
author_association: OWNER
diff_hunk: @@ -126,24 +264,23 @@ bool FunctionalSimulator::isRegisterWriteInstruction(const Instruction* instr) c
            opcode == mips_lite::opcode::LDW;
 }
 
-std::optional<uint8_t> FunctionalSimulator::getDestinationRegister(const Instruction* instr) const {
-    if (instr == nullptr || !isRegisterWriteInstruction(instr)) {
-        return std::nullopt;
+uint32_t FunctionalSimulator::readRegisterValue(uint8_t reg_num) {
+    
+    if (reg_num == 0) {
+        return 0;  // $0 register always returns 0
+    }               // And never has hazards
+    if(stall > 0) {
+        throw std::runtime_error("Stall detected in ID stage but wasn't properly handled by control logic. Should never attempt to read during a stall");
     }
-
-    // R-type instructions write to rd
-    if (instr->getInstructionType() == mips_lite::InstructionType::R_TYPE) {
-        return instr->getRd();
+    // Check EXE stage for hazards
+    if(!isStageEmpty(PipelineStage::EXECUTE) && pipeline[PipelineStage::EXECUTE]->dest_reg.value() == reg_num) {
+        return pipeline[PipelineStage::EXECUTE]->alu_result;
+    }
+    // Check MEM stage for hazards
+    if(!isStageEmpty(PipelineStage::MEMORY) && pipeline[PipelineStage::MEMORY]->dest_reg.value() == reg_num) {
+        return pipeline[PipelineStage::MEMORY]->alu_result;
+    }
+    else {
+        return register_file->read(reg_num);
path: src/functional_simulator.cpp
user: login: reecewayt
body: I see what you're saying but in hardware we wouldn't be forwarding in the decode stage. Instead a MUX selects which value to use as the input into the ALU. As you said, the ALU result is available at the end of the clock cycle. This works though because the result isn't needed until the next clock. I did this in the decode stage because it made more sense to me with our current code. But, it would be pretty easy to move to exe stage if we wanted. I'm linking the below photo because I think its a good example and helped me conceptually understand forwarding. 
![image](https://github.com/user-attachments/assets/e0fdf6b4-2944-4c76-a28e-57d9267e2f96)




created_at: 2025-05-23T01:54:35Z
author_association: COLLABORATOR
diff_hunk: @@ -126,24 +264,23 @@ bool FunctionalSimulator::isRegisterWriteInstruction(const Instruction* instr) c
            opcode == mips_lite::opcode::LDW;
 }
 
-std::optional<uint8_t> FunctionalSimulator::getDestinationRegister(const Instruction* instr) const {
-    if (instr == nullptr || !isRegisterWriteInstruction(instr)) {
-        return std::nullopt;
+uint32_t FunctionalSimulator::readRegisterValue(uint8_t reg_num) {
+    
+    if (reg_num == 0) {
+        return 0;  // $0 register always returns 0
+    }               // And never has hazards
+    if(stall > 0) {
+        throw std::runtime_error("Stall detected in ID stage but wasn't properly handled by control logic. Should never attempt to read during a stall");
     }
-
-    // R-type instructions write to rd
-    if (instr->getInstructionType() == mips_lite::InstructionType::R_TYPE) {
-        return instr->getRd();
+    // Check EXE stage for hazards
+    if(!isStageEmpty(PipelineStage::EXECUTE) && pipeline[PipelineStage::EXECUTE]->dest_reg.value() == reg_num) {
+        return pipeline[PipelineStage::EXECUTE]->alu_result;
+    }
+    // Check MEM stage for hazards
+    if(!isStageEmpty(PipelineStage::MEMORY) && pipeline[PipelineStage::MEMORY]->dest_reg.value() == reg_num) {
+        return pipeline[PipelineStage::MEMORY]->alu_result;
+    }
+    else {
+        return register_file->read(reg_num);
path: src/functional_simulator.cpp
user: login: nkanderson
body: Ah, thanks for the diagram.

> I see what you're saying but in hardware we wouldn't be forwarding in the fetch stage.

I wasn't intending to refer to the instruction fetch stage, I was thinking about the decode stage data retrieval from the register file. Maybe that's what you're describing as fetch though, in this context?

Yeah, I think what was confusing initially was that the hardware doesn't forward to instructions in decode, but like you're saying, to the ALU in the execute stage. We're getting the actual data in the decode stage, which does mean we need to get it one stage earlier than would be feasible in hardware, essentially in the same "clock cycle" that it is calculated in.

> it would be pretty easy to move to exe stage if we wanted

I think now that I see what we're doing, it makes sense. That said, this method might be a good candidate for some additional unit tests, but maybe we should see how it goes when adding the stall logic and overall program flow.
created_at: 2025-05-23T02:11:58Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,226 @@
+/**
+ * @file decode_stage_tests.cpp
+ * @brief Tests for Decode stage of pipeline
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <memory>
+
+#include "functional_simulator.h"
+#include "memory_interface.h"
+#include "mips_instruction.h"
+#include "register_file.h"
+#include "stats.h"
+
+using ::testing::NiceMock;
+
+// Only mock the memory parser
+class MockMemoryParser : public IMemoryParser {
+   public:
+    MOCK_METHOD(uint32_t, readInstruction, (uint32_t address), (override));
+    MOCK_METHOD(uint32_t, readMemory, (uint32_t address), (override));
+    MOCK_METHOD(void, writeMemory, (uint32_t address, uint32_t value), (override));
+};
+
+class DecodeStageTest : public ::testing::Test {
+   protected:
+    RegisterFile rf;
+    Stats stats;
+    NiceMock<MockMemoryParser> mem;
+    std::unique_ptr<FunctionalSimulator> sim;
+
+    // Sample instructions to use in tests
+    // R-type: ADD $rd, $rs, $rt (opcode=0, rs=1, rt=2, rd=3)
+    const uint32_t r_type_add_instr = 0x00221800;  // ADD $3, $1, $2
+    // I-type: ADDI $rt, $rs, immediate (opcode=1, rs=4, rt=5, imm=100)
+    const uint32_t i_type_addi_pos_instr = 0x04850064;  // ADDI $5, $4, 100
+    // I-type with negative immediate: ADDI $rt, $rs, immediate (opcode=1, rs=6, rt=7, imm=-100)
+    const uint32_t i_type_addi_neg_instr = 0x04C7FF9C;  // ADDI $7, $6, -100
+    // I-type memory: LDW $rt, imm($rs) (opcode=12, rs=8, rt=9, imm=200)
+    const uint32_t i_type_ldw_instr = 0x310900C8;  // LDW $9, 200($8)
+    // I-type branch: BEQ $rs, $rt, imm (opcode=15, rs=10, rt=11, imm=-50)
+    const uint32_t i_type_beq_instr = 0x3D4BFFCE;  // BEQ $10, $11, -50
+
+    void SetUp() override {
+        sim = std::make_unique<FunctionalSimulator>(&rf, &stats, &mem, true);
+
+        // Initialize some register values for testing
+        rf.write(1, 100);  // $1 = 100
+        rf.write(2, 200);  // $2 = 200
+        rf.write(3, 300);  // $3 = 300
+        rf.write(4, 400);  // $4 = 400
+        rf.write(5, 500);  // $5 = 500
+        rf.write(6, 600);  // $6 = 600
+        rf.write(7, 700);  // $7 = 700
+        rf.write(8, 800);  // $8 = 800
+        rf.write(9, 900);  // $9 = 900
+        rf.write(10, 1000);  // $10 = 1000
+        rf.write(11, 1100);  // $11 = 1100
+
+        
+    }
+
+    // Helper to properly initialize the decode stage with an instruction
+    void setupDecodeStage(uint32_t instruction_word) {
+        // Create a new instruction object
+        auto instr = std::make_unique<Instruction>(instruction_word);
+
+        // Create pipeline stage data for decode stage
+        auto decode_data = std::make_unique<PipelineStageData>();
+        decode_data->instruction = std::move(instr);
+        decode_data->pc = sim->getPC();  // Use current PC
+
+        // Place the instruction in the decode stage using getPipeline()
+        sim->getPipeline()[FunctionalSimulator::PipelineStage::DECODE] = std::move(decode_data);
+    }
+
+    // Helper to get decode stage data
+    const PipelineStageData* getDecodeStageData() {
+        return sim->getPipelineStage(FunctionalSimulator::PipelineStage::DECODE);
+    }
+
+    // Helper to get execute stage data (after decode processes)
+    const PipelineStageData* getExecuteStageData() {
+        return sim->getPipelineStage(FunctionalSimulator::PipelineStage::EXECUTE);
+    }
+};
+
+// Test I-type instruction decode (ADDI) - Most basic case
+// This validates: register reading, destination register setting, instruction decoding
+TEST_F(DecodeStageTest, DecodeBasicITypeInstruction) {
+    // This instruction reads $1 (rs=1), adds immediate 50, writes result to $3 (rt=3)
+    
+    
+    // Set up the decode stage with our instruction
+    setupDecodeStage(r_type_add_instr);
path: tests/functional_simulator/decode_stage_tests.cpp
user: login: nkanderson
body: @reecewayt just this one other set of comments and test name fixup, then I'd say you should merge this one!
created_at: 2025-05-23T02:16:11Z
author_association: OWNER
diff_hunk: @@ -126,24 +264,23 @@ bool FunctionalSimulator::isRegisterWriteInstruction(const Instruction* instr) c
            opcode == mips_lite::opcode::LDW;
 }
 
-std::optional<uint8_t> FunctionalSimulator::getDestinationRegister(const Instruction* instr) const {
-    if (instr == nullptr || !isRegisterWriteInstruction(instr)) {
-        return std::nullopt;
+uint32_t FunctionalSimulator::readRegisterValue(uint8_t reg_num) {
+    
+    if (reg_num == 0) {
+        return 0;  // $0 register always returns 0
+    }               // And never has hazards
+    if(stall > 0) {
+        throw std::runtime_error("Stall detected in ID stage but wasn't properly handled by control logic. Should never attempt to read during a stall");
     }
-
-    // R-type instructions write to rd
-    if (instr->getInstructionType() == mips_lite::InstructionType::R_TYPE) {
-        return instr->getRd();
+    // Check EXE stage for hazards
+    if(!isStageEmpty(PipelineStage::EXECUTE) && pipeline[PipelineStage::EXECUTE]->dest_reg.value() == reg_num) {
+        return pipeline[PipelineStage::EXECUTE]->alu_result;
+    }
+    // Check MEM stage for hazards
+    if(!isStageEmpty(PipelineStage::MEMORY) && pipeline[PipelineStage::MEMORY]->dest_reg.value() == reg_num) {
+        return pipeline[PipelineStage::MEMORY]->alu_result;
+    }
+    else {
+        return register_file->read(reg_num);
path: src/functional_simulator.cpp
user: login: reecewayt
body: Sorry I meant decode stage. 
created_at: 2025-05-23T02:17:36Z
author_association: COLLABORATOR

PR comments:
user: login: reecewayt
created_at: 2025-05-18T00:51:35Z
body: Sorry, my changes messed up a couple of your tests @nkanderson . I can fix it if these changes look good to you. 
author_association: COLLABORATOR
user: login: reecewayt
created_at: 2025-05-19T19:11:35Z
body: > Hey, great work, and thanks for getting this up for discussion. I think the main things for us to decide on are:
> 
>     * Where instruction pointers live (only in pipeline registers, or only in the pipeline array) and the implications for that decision
> 
>     * Our hazard check logic
> 
>     * How and where stalls are inserted
> 
> 
> My feeling on the hazard check (and future forwarding logic) is that we can simplify what you've got here if we do the dirty bit thing. I kind of want to think more on the instruction pointers and how stalls are managed, but I'm definitely interested in getting other opinions on the questions I posted.

author_association: COLLABORATOR
user: login: reecewayt
created_at: 2025-05-19T19:36:11Z
body: Did not mean to close this PR. 
author_association: COLLABORATOR
user: login: nkanderson
created_at: 2025-05-19T20:38:43Z
body: @reecewayt I'm wondering if we should try to figure out a time to get together and talk through some of this with a whiteboard. I think talking through it in real-time with some visual aids could be helpful.
author_association: OWNER
user: login: reecewayt
created_at: 2025-05-22T20:29:57Z
body: Alright I've posted my changes which reflect recent changes to the pipeline data structure in https://github.com/nkanderson/MIPS-lite/pull/33. Other changes are as follows:
- Updated exe and decode testing suites to reflect changes
- Created a dedicated readRegisterValue() helper method that will simply get the "correct value" either from EXE stage, MEM stage, or register file. Hence, it abstracts away forwarding logic from the decode stage.
### Current TODO:
- We need to possibly refactor the register file to store signed numbers instead of unsigned
- We need to integrate pipeline by creating more control logic, possibly as part of the cycle() method.
author_association: COLLABORATOR
user: login: reecewayt
created_at: 2025-05-23T01:54:48Z
body: @nkanderson I fixed up those comments. 
author_association: COLLABORATOR
user: login: reecewayt
created_at: 2025-05-24T02:58:33Z
body: @nkanderson I merged this but it failed because I updated the JR instruction. I'll update the test tomorrow...about to run to dinner :). Wanted to tag you though so you see the changes I made to the JR execute logic. 
author_association: COLLABORATOR
user: login: nkanderson
created_at: 2025-05-24T15:38:29Z
body: > Wanted to tag you though so you see the changes I made to the JR execute logic.

@reecewayt I confirmed with Kai that JR should just set the value from the register directly, like you originally had it. Sorry, I had thought we were going to merge this as it was, so didn't report that back here. I think you could just revert that change and we'd be good to go with this PR.
author_association: OWNER