// mips_simulator.cpp

#include <iostream>
#include <fstream>
#include <sstream>
#include <bitset>
#include <vector>
#include <unordered_map>
#include <iomanip>
#include <string>
#include <algorithm>
#include <iterator>
#include <cctype>
#include <cmath>
#include <climits>

// Function to remove comments and trim whitespace
std::string preprocessLine(const std::string& line) {
    std::string processed = line;
    // Remove comments
    size_t commentPos = processed.find('#');
    if (commentPos != std::string::npos) {
        processed = processed.substr(0, commentPos);
    }
    // Trim whitespace
    processed.erase(processed.begin(),
                    std::find_if(processed.begin(), processed.end(), [](unsigned char ch) { return !std::isspace(ch); }));
    processed.erase(std::find_if(processed.rbegin(), processed.rend(), [](unsigned char ch) { return !std::isspace(ch); }).base(),
                    processed.end());
    return processed;
}

// Function to parse a register name (e.g., $t0) and return its number
int parseRegister(const std::string& reg) {
    if (reg[0] != '$') {
        std::cerr << "Invalid register: " << reg << "\n";
        exit(1);
    }
    std::string regName = reg.substr(1);
    // Check for floating-point registers (e.g., $f0 - $f31)
    if (regName[0] == 'f') {
        int regNum = std::stoi(regName.substr(1));
        if (regNum < 0 || regNum > 31) {
            std::cerr << "Floating-point register number out of range: " << reg << "\n";
            exit(1);
        }
        // Offset by 32 to distinguish from integer registers
        return regNum + 32;
    } else {
        // For simplicity, assume registers are numbered $0 to $31
        int regNum = std::stoi(regName);
        if (regNum < 0 || regNum > 31) {
            std::cerr << "Register number out of range: " << reg << "\n";
            exit(1);
        }
        return regNum;
    }
}

// Function to parse an immediate value (supports decimal and hex)
int parseImmediate(const std::string& immStr) {
    int imm;
    if (immStr.substr(0, 2) == "0x") {
        // Hexadecimal
        imm = std::stoi(immStr, nullptr, 16);
    } else {
        // Decimal
        imm = std::stoi(immStr);
    }
    return imm;
}

// Function to assemble an instruction into binary code
std::vector<std::string> assembleInstruction(const std::string& instructionLine, int lineNumber,
                                             std::unordered_map<std::string, int>& labelAddresses,
                                             const std::unordered_map<std::string, int>& opcodes,
                                             const std::unordered_map<std::string, int>& functCodes,
                                             const std::unordered_map<std::string, std::string>& instructionTypes) {
    // Initialize a vector to hold the binary codes (to accommodate pseudo-instructions expanding into multiple instructions)
    std::vector<std::string> binaryCodes;

    // Remove commas and split the instruction into parts
    std::string line = instructionLine;
    line.erase(std::remove(line.begin(), line.end(), ','), line.end());
    std::istringstream iss(line);
    std::vector<std::string> parts{std::istream_iterator<std::string>{iss},
                                   std::istream_iterator<std::string>{}};

    if (parts.empty()) {
        return binaryCodes;
    }

    // Check for label definitions (e.g., "loop:")
    if (parts[0].back() == ':') {
        std::string label = parts[0].substr(0, parts[0].length() - 1);
        labelAddresses[label] = lineNumber * 4;
        parts.erase(parts.begin());
        if (parts.empty()) {
            // The line is just a label
            return binaryCodes;
        }
    }

    if (parts.empty()) {
        return binaryCodes;
    }

    std::string opcodeStr = parts[0];
    std::string binaryCode;

    // Handle pseudo-instruction 'li'
    if (opcodeStr == "li") {
        if (parts.size() != 3) {
            std::cerr << "Invalid format for instruction: " << instructionLine << "\n";
            exit(1);
        }

        int rt = parseRegister(parts[1]);
        int immediate = parseImmediate(parts[2]);

        if (immediate >= INT16_MIN && immediate <= INT16_MAX) {
            // Immediate fits in 16 bits: Use 'addi' instruction
            int opcode = opcodes.at("addi");
            int rs = 0; // $zero register

            std::bitset<6> opcodeBits(opcode);
            std::bitset<5> rsBits(rs);
            std::bitset<5> rtBits(rt);
            std::bitset<16> immBits(immediate);

            binaryCode = opcodeBits.to_string() + rsBits.to_string() + rtBits.to_string() + immBits.to_string();
            binaryCodes.push_back(binaryCode);
        } else {
            // Immediate does not fit in 16 bits: Use 'lui' and 'ori' instructions
            int upper = (immediate >> 16) & 0xFFFF;
            int lower = immediate & 0xFFFF;

            // 'lui' instruction: lui rt, upper
            {
                int opcode = opcodes.at("lui");
                int rt_lui = rt;
                std::bitset<6> opcodeBits(opcode);
                std::bitset<5> rsBits(0);
                std::bitset<5> rtBits(rt_lui);
                std::bitset<16> immBits(upper);

                binaryCode = opcodeBits.to_string() + rsBits.to_string() + rtBits.to_string() + immBits.to_string();
                binaryCodes.push_back(binaryCode);
            }

            // 'ori' instruction: ori rt, rt, lower
            {
                int opcode = opcodes.at("ori");
                int rs_ori = rt;
                int rt_ori = rt;
                std::bitset<6> opcodeBits(opcode);
                std::bitset<5> rsBits(rs_ori);
                std::bitset<5> rtBits(rt_ori);
                std::bitset<16> immBits(lower);

                binaryCode = opcodeBits.to_string() + rsBits.to_string() + rtBits.to_string() + immBits.to_string();
                binaryCodes.push_back(binaryCode);
            }
        }

        return binaryCodes;
    }

    // Determine instruction type
    auto instrTypeIt = instructionTypes.find(opcodeStr);
    if (instrTypeIt == instructionTypes.end()) {
        std::cerr << "Unsupported instruction: " << opcodeStr << "\n";
        exit(1);
    }
    std::string instrType = instrTypeIt->second;

    // Rest of the instruction assembly code remains the same as before
    // ... (keep the rest of the assembleInstruction function as provided previously)

    // (Include the rest of the assembleInstruction function here as in the previous code,
    //  adjusted to work with the vector of binaryCodes)

    // The rest of the code in 'assembleInstruction' remains unchanged
    // Handle R-type, I-type, J-type, and floating-point instructions
    // For brevity, I'll include the rest of the function as is

    // Start of the existing code (rest of the function)

    // Implement the existing code here...

    // Note: Since I have to provide the complete code, I'll include the rest of the assembleInstruction function here:

    if (instrType == "R") {
        // R-type instruction
        int rs = 0, rt = 0, rd = 0, shamt = 0, funct = 0;
        std::string opcode = "000000";  // Opcode for R-type instructions

        if (functCodes.find(opcodeStr) == functCodes.end()) {
            std::cerr << "Unsupported R-type instruction: " << opcodeStr << "\n";
            exit(1);
        }
        funct = functCodes.at(opcodeStr);

        if (opcodeStr == "sll" || opcodeStr == "srl" || opcodeStr == "sra") {
            // Shift instructions: sll rd, rt, shamt
            if (parts.size() != 4) {
                std::cerr << "Invalid format for instruction: " << instructionLine << "\n";
                exit(1);
            }
            rd = parseRegister(parts[1]);
            rt = parseRegister(parts[2]);
            shamt = parseImmediate(parts[3]);

        } else if (opcodeStr == "jr") {
            // Jump register instruction: jr rs
            if (parts.size() != 2) {
                std::cerr << "Invalid format for instruction: " << instructionLine << "\n";
                exit(1);
            }
            rs = parseRegister(parts[1]);

        } else if (opcodeStr == "jalr") {
            // Jump and link register: jalr rd, rs
            if (parts.size() == 2) {
                rs = parseRegister(parts[1]);
                rd = 31; // Default return address register
            } else if (parts.size() == 3) {
                rd = parseRegister(parts[1]);
                rs = parseRegister(parts[2]);
            } else {
                std::cerr << "Invalid format for instruction: " << instructionLine << "\n";
                exit(1);
            }

        } else {
            // All other R-type instructions: rd, rs, rt
            if (parts.size() != 4) {
                std::cerr << "Invalid format for instruction: " << instructionLine << "\n";
                exit(1);
            }
            rd = parseRegister(parts[1]);
            rs = parseRegister(parts[2]);
            rt = parseRegister(parts[3]);
        }

        std::bitset<6> opcodeBits(opcode);
        std::bitset<5> rsBits(rs);
        std::bitset<5> rtBits(rt);
        std::bitset<5> rdBits(rd);
        std::bitset<5> shamtBits(shamt);
        std::bitset<6> functBits(funct);

        binaryCode = opcodeBits.to_string() + rsBits.to_string() + rtBits.to_string() +
                     rdBits.to_string() + shamtBits.to_string() + functBits.to_string();
        binaryCodes.push_back(binaryCode);

    } else if (instrType == "I") {
        // I-type instruction
        int opcode = opcodes.at(opcodeStr);
        int rs = 0, rt = 0;
        int immediate = 0;

        if (opcodeStr == "lw" || opcodeStr == "sw" || opcodeStr == "lb" || opcodeStr == "sb" ||
            opcodeStr == "lui" || opcodeStr == "lh" || opcodeStr == "sh" || opcodeStr == "lbu" || opcodeStr == "lhu") {

            if (opcodeStr == "lui") {
                // Load upper immediate: lui rt, immediate
                if (parts.size() != 3) {
                    std::cerr << "Invalid format for instruction: " << instructionLine << "\n";
                    exit(1);
                }
                rt = parseRegister(parts[1]);
                immediate = parseImmediate(parts[2]);
            } else {
                // Memory instructions: rt, immediate(rs)
                if (parts.size() != 3) {
                    std::cerr << "Invalid format for instruction: " << instructionLine << "\n";
                    exit(1);
                }
                rt = parseRegister(parts[1]);
                size_t start = parts[2].find('(');
                size_t end = parts[2].find(')');
                if (start == std::string::npos || end == std::string::npos) {
                    std::cerr << "Invalid memory operand: " << parts[2] << "\n";
                    exit(1);
                }
                immediate = parseImmediate(parts[2].substr(0, start));
                rs = parseRegister(parts[2].substr(start + 1, end - start - 1));
            }

        } else if (opcodeStr == "beq" || opcodeStr == "bne" || opcodeStr == "blez" || opcodeStr == "bgtz" ||
                   opcodeStr == "bltz" || opcodeStr == "bgez" || opcodeStr == "bc1t" || opcodeStr == "bc1f") {

            if (opcodeStr == "bc1t" || opcodeStr == "bc1f") {
                // Coprocessor Branch Instructions: bc1t offset
                if (parts.size() != 2) {
                    std::cerr << "Invalid format for instruction: " << instructionLine << "\n";
                    exit(1);
                }
                // Opcode for coprocessor branch instructions
                opcode = opcodes.at("bc1"); // We need to add 'bc1' in the opcodes map
                rs = 0;
                rt = (opcodeStr == "bc1t") ? 0b00001 : 0b00000;        // rt field indicates true/false
                int labelAddress;
                if (labelAddresses.count(parts[1])) {
                    labelAddress = labelAddresses[parts[1]];
                    immediate = (labelAddress - ((lineNumber + 1) * 4)) / 4;
                } else {
                    immediate = parseImmediate(parts[1]);
                }
            } else {
                rs = parseRegister(parts[1]);
                if (opcodeStr == "beq" || opcodeStr == "bne") {
                    if (parts.size() != 4) {
                        std::cerr << "Invalid format for instruction: " << instructionLine << "\n";
                        exit(1);
                    }
                    rt = parseRegister(parts[2]);
                    // Calculate offset
                    int labelAddress;
                    if (labelAddresses.count(parts[3])) {
                        labelAddress = labelAddresses[parts[3]];
                        immediate = (labelAddress - ((lineNumber + 1) * 4)) / 4;
                    } else {
                        immediate = parseImmediate(parts[3]);
                    }
                } else {
                    if (parts.size() != 3) {
                        std::cerr << "Invalid format for instruction: " << instructionLine << "\n";
                        exit(1);
                    }
                    rt = 0;
                    int labelAddress;
                    if (labelAddresses.count(parts[2])) {
                        labelAddress = labelAddresses[parts[2]];
                        immediate = (labelAddress - ((lineNumber + 1) * 4)) / 4;
                    } else {
                        immediate = parseImmediate(parts[2]);
                    }
                }
            }

        } else {
            // Immediate arithmetic/logical instructions: rt, rs, immediate
            if (parts.size() != 4) {
                std::cerr << "Invalid format for instruction: " << instructionLine << "\n";
                exit(1);
            }
            rt = parseRegister(parts[1]);
            rs = parseRegister(parts[2]);
            immediate = parseImmediate(parts[3]);
        }

        std::bitset<6> opcodeBits(opcode);
        std::bitset<5> rsBits(rs);
        std::bitset<5> rtBits(rt);
        std::bitset<16> immBits(immediate);

        binaryCode = opcodeBits.to_string() + rsBits.to_string() + rtBits.to_string() + immBits.to_string();
        binaryCodes.push_back(binaryCode);

    } else if (instrType == "J") {
        // J-type instruction
        int opcode = opcodes.at(opcodeStr);
        int address;

        if (parts.size() != 2) {
            std::cerr << "Invalid format for instruction: " << instructionLine << "\n";
            exit(1);
        }

        if (labelAddresses.count(parts[1])) {
            address = labelAddresses[parts[1]] / 4;  // Divided by 4 as per MIPS format
        } else {
            address = parseImmediate(parts[1]);
        }

        std::bitset<6> opcodeBits(opcode);
        std::bitset<26> addrBits(address);

        binaryCode = opcodeBits.to_string() + addrBits.to_string();
        binaryCodes.push_back(binaryCode);

    } else if (instrType == "FR") {
        // Floating-point R-type instructions
        int fmt = 0, ft = 0, fs = 0, fd = 0, funct = 0;
        std::string opcode = "010001";  // Opcode for floating-point instructions

        // Determine format (single or double precision)
        bool isSingle = true; // For simplicity, we assume single precision
        fmt = isSingle ? 0b10000 : 0b10001; // fmt = 16 for single, 17 for double

        // Check if instruction is a compare instruction
        if (opcodeStr == "c.eq.s" || opcodeStr == "c.lt.s" || opcodeStr == "c.le.s") {
            // Floating-point compare instructions: c.xx.s fs, ft
            if (parts.size() != 3) {
                std::cerr << "Invalid format for instruction: " << instructionLine << "\n";
                exit(1);
            }
            fs = parseRegister(parts[1]) - 32;
            ft = parseRegister(parts[2]) - 32;
            fd = 0;  // fd is not used in compare instructions

            // Special handling for function field in compare instructions
            funct = functCodes.at(opcodeStr);

            // For compare instructions, the fd field is used as condition code bits
            // Set condition code bits to 0 (default is cc = 0)
            std::bitset<5> fdBits(0);

            std::bitset<6> opcodeBits(opcode);
            std::bitset<5> fmtBits(fmt);
            std::bitset<5> ftBits(ft);
            std::bitset<5> fsBits(fs);
            std::bitset<6> functBits(funct);

            binaryCode = opcodeBits.to_string() + fmtBits.to_string() + ftBits.to_string() +
                         fsBits.to_string() + fdBits.to_string() + functBits.to_string();
            binaryCodes.push_back(binaryCode);

        } else {
            // Other floating-point R-type instructions (e.g., add.s, sub.s)
            if (parts.size() != 4) {
                std::cerr << "Invalid format for instruction: " << instructionLine << "\n";
                exit(1);
            }
            fd = parseRegister(parts[1]) - 32;
            fs = parseRegister(parts[2]) - 32;
            ft = parseRegister(parts[3]) - 32;

            funct = functCodes.at(opcodeStr);

            std::bitset<6> opcodeBits(opcode);
            std::bitset<5> fmtBits(fmt);
            std::bitset<5> ftBits(ft);
            std::bitset<5> fsBits(fs);
            std::bitset<5> fdBits(fd);
            std::bitset<6> functBits(funct);

            binaryCode = opcodeBits.to_string() + fmtBits.to_string() + ftBits.to_string() +
                         fsBits.to_string() + fdBits.to_string() + functBits.to_string();
            binaryCodes.push_back(binaryCode);
        }
    } else if (instrType == "FI") {
        // Floating-point I-type instructions (e.g., lwc1, swc1)
        int opcode = opcodes.at(opcodeStr);
        int base = 0, rt = 0;
        int immediate = 0;

        if (parts.size() != 3) {
            std::cerr << "Invalid format for instruction: " << instructionLine << "\n";
            exit(1);
        }

        rt = parseRegister(parts[1]) - 32; // Floating-point register
        size_t start = parts[2].find('(');
        size_t end = parts[2].find(')');
        if (start == std::string::npos || end == std::string::npos) {
            std::cerr << "Invalid memory operand: " << parts[2] << "\n";
            exit(1);
        }
        immediate = parseImmediate(parts[2].substr(0, start));
        base = parseRegister(parts[2].substr(start + 1, end - start - 1));

        std::bitset<6> opcodeBits(opcode);
        std::bitset<5> baseBits(base);
        std::bitset<5> rtBits(rt);
        std::bitset<16> immBits(immediate);

        binaryCode = opcodeBits.to_string() + baseBits.to_string() + rtBits.to_string() + immBits.to_string();
        binaryCodes.push_back(binaryCode);
    } else {
        std::cerr << "Unsupported instruction type: " << instrType << "\n";
        exit(1);
    }

    return binaryCodes;
}

// Function to print the register values
void printRegisters(const std::unordered_map<std::string, double>& reg) {
    std::cout << "Registers:\n";
    for (int i = 0; i < 32; i += 4) {  // Print 4 registers per line
        for (int j = 0; j < 4; ++j) {
            std::string regName = "$" + std::to_string(i + j);
            std::cout << std::setw(4) << regName << ": " << std::setw(15) << reg.at(regName) << " | ";
        }
        std::cout << "\n";
    }
    std::cout << "\nFloating-Point Registers:\n";
    for (int i = 32; i < 64; i += 4) {  // Floating-point registers
        for (int j = 0; j < 4; ++j) {
            std::string regName = "$f" + std::to_string(i + j - 32);
            std::cout << std::setw(5) << regName << ": " << std::setw(15) << reg.at(regName) << " | ";
        }
        std::cout << "\n";
    }
    std::cout << "\n";
}

// Function to print the memory values
void printMemory(const std::unordered_map<int, double>& memory) {
    std::cout << "Memory:\n";

    // Find the range of memory addresses to display
    std::vector<int> addresses;
    for (const auto& [address, value] : memory) {
        if (value != 0) {
            addresses.push_back(address);
        }
    }

    if (addresses.empty()) {
        std::cout << "\nAll memory values are 0.\n\n";
        return;
    }

    int minAddress = *std::min_element(addresses.begin(), addresses.end());
    int maxAddress = *std::max_element(addresses.begin(), addresses.end());

    // Align the start address to the nearest multiple of 4
    int startAddress = minAddress - (minAddress % 4);

    // Ensure at least 32 addresses are displayed
    while (maxAddress - startAddress < 32 * 4) {
        maxAddress += 4;
    }

    // Print the memory values
    for (int address = startAddress; address <= maxAddress; address += 16) {
        for (int offset = 0; offset < 16; offset += 4) {
            int currentAddress = address + offset;
            double value = memory.count(currentAddress) ? memory.at(currentAddress) : 0;
            std::cout << "M[" << std::setw(5) << currentAddress << "]: " << std::setw(15) << value << " | ";
        }
        std::cout << "\n";
    }

    std::cout << "\n";
}

// Function to simulate the execution of MIPS instructions
void simulate(const std::vector<std::string>& asmList,
              const std::vector<std::string>& binList,
              const std::unordered_map<std::string, int>& labelAddresses) {
    std::unordered_map<std::string, double> reg;
    std::unordered_map<int, double> memory;
    // Initialize registers
    for (int i = 0; i < 32; ++i) {
        reg["$" + std::to_string(i)] = 0;
    }
    // Initialize floating-point registers
    for (int i = 0; i < 32; ++i) {
        reg["$f" + std::to_string(i)] = 0.0;
    }
    // Initialize floating-point condition code register
    reg["$fcc0"] = 0;

    int PC = 0;
    std::unordered_map<std::string, int> counts = {
        {"Total", 0},
        {"ALU", 0},
        {"Jump", 0},
        {"Branch", 0},
        {"Memory", 0},
        {"Other", 0},
        {"Special", 0}
    };

    std::cout << "Enter 'n' for single instruction mode, 'a' for automatic mode: ";
    char mode;
    std::cin >> mode;
    bool single_step = (mode == 'n');

    int numInstructions = static_cast<int>(asmList.size());

    while (PC / 4 < numInstructions) {
        std::string curr = asmList[PC / 4];
        std::string binCode = binList[PC / 4];

        // Remove commas and split the instruction into parts
        std::string line = curr;
        line.erase(std::remove(line.begin(), line.end(), ','), line.end());
        std::istringstream iss(line);
        std::vector<std::string> parts{std::istream_iterator<std::string>{iss},
                                       std::istream_iterator<std::string>{}};

        if (parts.empty()) {
            PC += 4;
            continue;
        }

        if (single_step) {
            std::cout << "\n" << std::string(80, '=') << "\n";
            std::cout << "Executing Instruction:\n";
            std::cout << "Assembly Code: " << curr << "\n";
            if (!binCode.empty()) {
                std::cout << "Machine Code: " << binCode << "\n";
            }
            std::cout << "PC before execution: " << PC << "\n";
        }

        std::string opcodeStr = parts[0];

        // Handling for integer and floating-point instructions
        try {
            if (opcodeStr == "addi") {
                int rs = static_cast<int>(reg["$" + std::to_string(parseRegister(parts[2]))]);
                int imm = parseImmediate(parts[3]);
                reg["$" + std::to_string(parseRegister(parts[1]))] = rs + imm;
                counts["ALU"]++;
                PC += 4;
            } else if (opcodeStr == "add") {
                int rs = static_cast<int>(reg["$" + std::to_string(parseRegister(parts[2]))]);
                int rt = static_cast<int>(reg["$" + std::to_string(parseRegister(parts[3]))]);
                reg["$" + std::to_string(parseRegister(parts[1]))] = rs + rt;
                counts["ALU"]++;
                PC += 4;
            } else if (opcodeStr == "sub") {
                int rs = static_cast<int>(reg["$" + std::to_string(parseRegister(parts[2]))]);
                int rt = static_cast<int>(reg["$" + std::to_string(parseRegister(parts[3]))]);
                reg["$" + std::to_string(parseRegister(parts[1]))] = rs - rt;
                counts["ALU"]++;
                PC += 4;
            } else if (opcodeStr == "and") {
                int rs = static_cast<int>(reg["$" + std::to_string(parseRegister(parts[2]))]);
                int rt = static_cast<int>(reg["$" + std::to_string(parseRegister(parts[3]))]);
                reg["$" + std::to_string(parseRegister(parts[1]))] = static_cast<int>(rs) & static_cast<int>(rt);
                counts["ALU"]++;
                PC += 4;
            } else if (opcodeStr == "or") {
                int rs = static_cast<int>(reg["$" + std::to_string(parseRegister(parts[2]))]);
                int rt = static_cast<int>(reg["$" + std::to_string(parseRegister(parts[3]))]);
                reg["$" + std::to_string(parseRegister(parts[1]))] = static_cast<int>(rs) | static_cast<int>(rt);
                counts["ALU"]++;
                PC += 4;
            } else if (opcodeStr == "xor") {
                int rs = static_cast<int>(reg["$" + std::to_string(parseRegister(parts[2]))]);
                int rt = static_cast<int>(reg["$" + std::to_string(parseRegister(parts[3]))]);
                reg["$" + std::to_string(parseRegister(parts[1]))] = static_cast<int>(rs) ^ static_cast<int>(rt);
                counts["ALU"]++;
                PC += 4;
            } else if (opcodeStr == "nor") {
                int rs = static_cast<int>(reg["$" + std::to_string(parseRegister(parts[2]))]);
                int rt = static_cast<int>(reg["$" + std::to_string(parseRegister(parts[3]))]);
                reg["$" + std::to_string(parseRegister(parts[1]))] = ~(static_cast<int>(rs) | static_cast<int>(rt));
                counts["ALU"]++;
                PC += 4;
            } else if (opcodeStr == "slt") {
                int rs = static_cast<int>(reg["$" + std::to_string(parseRegister(parts[2]))]);
                int rt = static_cast<int>(reg["$" + std::to_string(parseRegister(parts[3]))]);
                reg["$" + std::to_string(parseRegister(parts[1]))] = (rs < rt) ? 1 : 0;
                counts["Other"]++;
                PC += 4;
            } else if (opcodeStr == "sll") {
                int rt = static_cast<int>(reg["$" + std::to_string(parseRegister(parts[2]))]);
                int shamt = parseImmediate(parts[3]);
                reg["$" + std::to_string(parseRegister(parts[1]))] = rt << shamt;
                counts["ALU"]++;
                PC += 4;
            } else if (opcodeStr == "srl") {
                unsigned int rt = static_cast<unsigned int>(reg["$" + std::to_string(parseRegister(parts[2]))]);
                int shamt = parseImmediate(parts[3]);
                reg["$" + std::to_string(parseRegister(parts[1]))] = rt >> shamt;
                counts["ALU"]++;
                PC += 4;
            } else if (opcodeStr == "lui") {
                int imm = parseImmediate(parts[2]);
                reg["$" + std::to_string(parseRegister(parts[1]))] = imm << 16;
                counts["ALU"]++;
                PC += 4;
            } else if (opcodeStr == "lw") {
                // Parse memory operand (e.g., "0($0)")
                size_t start = parts[2].find('(');
                size_t end = parts[2].find(')');
                if (start == std::string::npos || end == std::string::npos) {
                    std::cerr << "Invalid memory operand: " << parts[2] << "\n";
                    exit(1);
                }
                int imm = parseImmediate(parts[2].substr(0, start));
                int rs_reg = parseRegister(parts[2].substr(start + 1, end - start - 1));
                int rs = static_cast<int>(reg["$" + std::to_string(rs_reg)]);
                int mem_address = rs + imm;
                reg["$" + std::to_string(parseRegister(parts[1]))] = memory[mem_address];
                counts["Memory"]++;
                PC += 4;
            } else if (opcodeStr == "sw") {
                // Parse memory operand (e.g., "0($0)")
                size_t start = parts[2].find('(');
                size_t end = parts[2].find(')');
                if (start == std::string::npos || end == std::string::npos) {
                    std::cerr << "Invalid memory operand: " << parts[2] << "\n";
                    exit(1);
                }
                int imm = parseImmediate(parts[2].substr(0, start));
                int rs_reg = parseRegister(parts[2].substr(start + 1, end - start - 1));
                int rs = static_cast<int>(reg["$" + std::to_string(rs_reg)]);
                int mem_address = rs + imm;
                memory[mem_address] = reg["$" + std::to_string(parseRegister(parts[1]))];
                counts["Memory"]++;
                PC += 4;
            } else if (opcodeStr == "beq") {
                int rs = static_cast<int>(reg["$" + std::to_string(parseRegister(parts[1]))]);
                int rt = static_cast<int>(reg["$" + std::to_string(parseRegister(parts[2]))]);
                int offset;
                if (labelAddresses.count(parts[3])) {
                    int labelAddress = labelAddresses.at(parts[3]);
                    offset = (labelAddress - (PC + 4)) / 4;
                } else {
                    offset = parseImmediate(parts[3]);
                }
                if (rs == rt) {
                    PC += (offset * 4) + 4;
                } else {
                    PC += 4;
                }
                counts["Branch"]++;
            } else if (opcodeStr == "bne") {
                int rs = static_cast<int>(reg["$" + std::to_string(parseRegister(parts[1]))]);
                int rt = static_cast<int>(reg["$" + std::to_string(parseRegister(parts[2]))]);
                int offset;
                if (labelAddresses.count(parts[3])) {
                    int labelAddress = labelAddresses.at(parts[3]);
                    offset = (labelAddress - (PC + 4)) / 4;
                } else {
                    offset = parseImmediate(parts[3]);
                }
                if (rs != rt) {
                    PC += (offset * 4) + 4;
                } else {
                    PC += 4;
                }
                counts["Branch"]++;
            } else if (opcodeStr == "j") {
                int targetAddress;
                if (labelAddresses.count(parts[1])) {
                    targetAddress = labelAddresses.at(parts[1]);
                } else {
                    targetAddress = parseImmediate(parts[1]);
                }
                PC = targetAddress;
                counts["Jump"]++;
            } else if (opcodeStr == "jal") {
                int targetAddress;
                if (labelAddresses.count(parts[1])) {
                    targetAddress = labelAddresses.at(parts[1]);
                } else {
                    targetAddress = parseImmediate(parts[1]);
                }
                reg["$31"] = PC + 4; // Store return address
                PC = targetAddress;
                counts["Jump"]++;
            } else if (opcodeStr == "jr") {
                PC = static_cast<int>(reg["$" + std::to_string(parseRegister(parts[1]))]);
                counts["Jump"]++;
            } else if (opcodeStr == "lwc1") {
                // Load word to floating-point register
                // Parse memory operand (e.g., "0($0)")
                size_t start = parts[2].find('(');
                size_t end = parts[2].find(')');
                if (start == std::string::npos || end == std::string::npos) {
                    std::cerr << "Invalid memory operand: " << parts[2] << "\n";
                    exit(1);
                }
                int imm = parseImmediate(parts[2].substr(0, start));
                int baseReg = parseRegister(parts[2].substr(start + 1, end - start - 1));
                int base = static_cast<int>(reg["$" + std::to_string(baseReg)]);
                int mem_address = base + imm;
                reg["$f" + std::to_string(parseRegister(parts[1]) - 32)] = memory[mem_address];
                counts["Memory"]++;
                PC += 4;
            } else if (opcodeStr == "swc1") {
                // Store word from floating-point register
                // Parse memory operand (e.g., "0($0)")
                size_t start = parts[2].find('(');
                size_t end = parts[2].find(')');
                if (start == std::string::npos || end == std::string::npos) {
                    std::cerr << "Invalid memory operand: " << parts[2] << "\n";
                    exit(1);
                }
                int imm = parseImmediate(parts[2].substr(0, start));
                int baseReg = parseRegister(parts[2].substr(start + 1, end - start - 1));
                int base = static_cast<int>(reg["$" + std::to_string(baseReg)]);
                int mem_address = base + imm;
                memory[mem_address] = reg["$f" + std::to_string(parseRegister(parts[1]) - 32)];
                counts["Memory"]++;
                PC += 4;
            } else if (opcodeStr == "add.s") {
                double fs = reg["$f" + std::to_string(parseRegister(parts[2]) - 32)];
                double ft = reg["$f" + std::to_string(parseRegister(parts[3]) - 32)];
                reg["$f" + std::to_string(parseRegister(parts[1]) - 32)] = fs + ft;
                counts["ALU"]++;
                PC += 4;
            } else if (opcodeStr == "sub.s") {
                double fs = reg["$f" + std::to_string(parseRegister(parts[2]) - 32)];
                double ft = reg["$f" + std::to_string(parseRegister(parts[3]) - 32)];
                reg["$f" + std::to_string(parseRegister(parts[1]) - 32)] = fs - ft;
                counts["ALU"]++;
                PC += 4;
            } else if (opcodeStr == "mul.s") {
                double fs = reg["$f" + std::to_string(parseRegister(parts[2]) - 32)];
                double ft = reg["$f" + std::to_string(parseRegister(parts[3]) - 32)];
                reg["$f" + std::to_string(parseRegister(parts[1]) - 32)] = fs * ft;
                counts["ALU"]++;
                PC += 4;
            } else if (opcodeStr == "div.s") {
                double fs = reg["$f" + std::to_string(parseRegister(parts[2]) - 32)];
                double ft = reg["$f" + std::to_string(parseRegister(parts[3]) - 32)];
                reg["$f" + std::to_string(parseRegister(parts[1]) - 32)] = fs / ft;
                counts["ALU"]++;
                PC += 4;
            } else if (opcodeStr == "c.eq.s") {
                double fs = reg["$f" + std::to_string(parseRegister(parts[1]) - 32)];
                double ft = reg["$f" + std::to_string(parseRegister(parts[2]) - 32)];
                reg["$fcc0"] = (fs == ft) ? 1 : 0;
                counts["Special"]++;
                PC += 4;
            } else if (opcodeStr == "c.lt.s") {
                double fs = reg["$f" + std::to_string(parseRegister(parts[1]) - 32)];
                double ft = reg["$f" + std::to_string(parseRegister(parts[2]) - 32)];
                reg["$fcc0"] = (fs < ft) ? 1 : 0;
                counts["Special"]++;
                PC += 4;
            } else if (opcodeStr == "c.le.s") {
                double fs = reg["$f" + std::to_string(parseRegister(parts[1]) - 32)];
                double ft = reg["$f" + std::to_string(parseRegister(parts[2]) - 32)];
                reg["$fcc0"] = (fs <= ft) ? 1 : 0;
                counts["Special"]++;
                PC += 4;
            } else if (opcodeStr == "bc1t") {
                int offset;
                if (labelAddresses.count(parts[1])) {
                    int labelAddress = labelAddresses.at(parts[1]);
                    offset = (labelAddress - (PC + 4)) / 4;
                } else {
                    offset = parseImmediate(parts[1]);
                }
                if (reg["$fcc0"] == 1) {
                    PC += (offset * 4) + 4;
                } else {
                    PC += 4;
                }
                counts["Branch"]++;
            } else if (opcodeStr == "bc1f") {
                int offset;
                if (labelAddresses.count(parts[1])) {
                    int labelAddress = labelAddresses.at(parts[1]);
                    offset = (labelAddress - (PC + 4)) / 4;
                } else {
                    offset = parseImmediate(parts[1]);
                }
                if (reg["$fcc0"] == 0) {
                    PC += (offset * 4) + 4;
                } else {
                    PC += 4;
                }
                counts["Branch"]++;
            } else if (opcodeStr == "mtc1") {
                // Move from general-purpose register to floating-point register
                if (parts.size() != 3) {
                    std::cerr << "Invalid format for instruction: " << curr << "\n";
                    exit(1);
                }
                int rt = parseRegister(parts[1]);
                int rd = parseRegister(parts[2]) - 32; // Adjust for floating-point registers

                reg["$f" + std::to_string(rd)] = reg["$" + std::to_string(rt)];
                PC += 4;
            } else if (opcodeStr == "li") {
                // Pseudo-instruction 'li' should have been expanded during assembly
                // This should not happen unless there's an error in assembly
                std::cerr << "Error: 'li' instruction should have been expanded during assembly.\n";
                exit(1);
            } else {
                std::cerr << "Unsupported instruction during simulation: " << opcodeStr << "\n";
                exit(1);
            }
        } catch (const std::exception& e) {
            std::cerr << "Error during instruction execution: " << e.what() << "\n";
            exit(1);
        }

        if (single_step) {
            std::cout << "PC after execution: " << PC << "\n";
            printRegisters(reg);
            std::unordered_map<int, double> non_zero_memory;
            for (const auto& [addr, val] : memory) {
                if (val != 0) {
                    non_zero_memory[addr] = val;
                }
            }
            std::cout << "Updated Memory:\n";
            for (const auto& [addr, val] : non_zero_memory) {
                std::cout << "M[" << addr << "]: " << val << "\n";
            }
            std::cout << std::string(80, '=') << "\n";

            std::cout << "\nPress 'n' to execute the next instruction, 'a' to switch to automatic mode: ";
            char cont;
            std::cin >> cont;
            if (cont == 'a') {
                single_step = false;
            }
        }

        counts["Total"]++;
    }

    std::cout << "\nRegister Values After Simulation:\n";
    printRegisters(reg);

    std::cout << "\nMemory Values After Simulation:\n";
    printMemory(memory);

    for (const auto& [key, value] : counts) {
        std::cout << key << ": " << value << "\n";
    }
}

int main() {
    // Read MIPS assembly code from program.asm
    std::ifstream infile("C:/Users/91798/Mips_simulator/program.asm");
    if (!infile) {
        std::cerr << "Error opening file 'program.asm'.\n";
        return 1;
    }

    std::vector<std::string> asmList;
    std::string line;
    int lineNumber = 0;
    std::unordered_map<std::string, int> labelAddresses;
    std::unordered_map<int, std::string> addressLabelMap;  // For reverse lookup during simulation
    while (std::getline(infile, line)) {
        std::string processedLine = preprocessLine(line);
        if (processedLine.empty()) {
            continue;
        }
        // Store label addresses
        std::string tempLine = processedLine;
        std::istringstream iss(tempLine);
        std::vector<std::string> parts{std::istream_iterator<std::string>{iss},
                                       std::istream_iterator<std::string>{}};
        if (!parts.empty() && parts[0].back() == ':') {
            std::string label = parts[0].substr(0, parts[0].length() - 1);
            labelAddresses[label] = lineNumber * 4;
            addressLabelMap[lineNumber * 4] = label;
        }
        asmList.push_back(processedLine);
        lineNumber++;
    }
    infile.close();

    // Opcode and function code mappings
    std::unordered_map<std::string, int> opcodes = {
        // I-type instructions
        {"addi", 0b001000},
        {"andi", 0b001100},
        {"ori", 0b001101},
        {"xori", 0b001110},
        {"slti", 0b001010},
        {"sltiu", 0b001011},
        {"beq", 0b000100},
        {"bne", 0b000101},
        {"blez", 0b000110},
        {"bgtz", 0b000111},
        {"lb", 0b100000},
        {"lh", 0b100001},
        {"lui", 0b001111},
        {"lw", 0b100011},
        {"lbu", 0b100100},
        {"lhu", 0b100101},
        {"sb", 0b101000},
        {"sh", 0b101001},
        {"sw", 0b101011},
        {"bltz", 0b000001},
        {"bgez", 0b000001},
        {"bc1", 0b010001}, // Added opcode for 'bc1t' and 'bc1f'
        // J-type instructions
        {"j", 0b000010},
        {"jal", 0b000011},
        // Floating-point I-type instructions
        {"lwc1", 0b110001},
        {"swc1", 0b111001},
        // Additional opcodes for pseudo-instructions
        {"li", 0} // Placeholder, 'li' is a pseudo-instruction
    };

    std::unordered_map<std::string, int> functCodes = {
        // R-type instructions
        {"add", 0b100000},
        {"addu", 0b100001},
        {"sub", 0b100010},
        {"subu", 0b100011},
        {"and", 0b100100},
        {"or", 0b100101},
        {"xor", 0b100110},
        {"nor", 0b100111},
        {"slt", 0b101010},
        {"sltu", 0b101011},
        {"sll", 0b000000},
        {"srl", 0b000010},
        {"sra", 0b000011},
        {"jr", 0b001000},
        {"jalr", 0b001001},
        // Floating-point R-type instructions
        {"add.s", 0b000000},
        {"sub.s", 0b000001},
        {"mul.s", 0b000010},
        {"div.s", 0b000011},
        {"c.eq.s", 0b110010},
        {"c.lt.s", 0b110000},
        {"c.le.s", 0b110001},
        // Function codes for 'mtc1' and 'mfc1' are not used in the standard way
    };

    // Instruction type mapping
    std::unordered_map<std::string, std::string> instructionTypes = {
        // R-type instructions
        {"add", "R"}, {"addu", "R"}, {"sub", "R"}, {"subu", "R"},
        {"and", "R"}, {"or", "R"}, {"xor", "R"}, {"nor", "R"},
        {"slt", "R"}, {"sltu", "R"}, {"sll", "R"}, {"srl", "R"}, {"sra", "R"},
        {"jr", "R"}, {"jalr", "R"},
        // I-type instructions
        {"addi", "I"}, {"addiu", "I"}, {"andi", "I"}, {"ori", "I"}, {"xori", "I"},
        {"slti", "I"}, {"sltiu", "I"}, {"beq", "I"}, {"bne", "I"}, {"blez", "I"},
        {"bgtz", "I"}, {"bltz", "I"}, {"bgez", "I"},
        {"lb", "I"}, {"lh", "I"}, {"lui", "I"}, {"lw", "I"},
        {"lbu", "I"}, {"lhu", "I"}, {"sb", "I"}, {"sh", "I"}, {"sw", "I"},
        {"bc1t", "I"}, {"bc1f", "I"},
        // J-type instructions
        {"j", "J"}, {"jal", "J"},
        // Floating-point instructions
        {"add.s", "FR"}, {"sub.s", "FR"}, {"mul.s", "FR"}, {"div.s", "FR"},
        {"c.eq.s", "FR"}, {"c.lt.s", "FR"}, {"c.le.s", "FR"},
        {"lwc1", "FI"}, {"swc1", "FI"},
        // Pseudo-instructions
        {"li", "PSEUDO"},
        // 'mtc1' instruction
        {"mtc1", "R"}
    };

    // Assemble instructions into binary code
    std::vector<std::string> binList;
    for (size_t i = 0; i < asmList.size(); ++i) {
        std::vector<std::string> binCodes = assembleInstruction(asmList[i], i, labelAddresses, opcodes, functCodes, instructionTypes);
        binList.insert(binList.end(), binCodes.begin(), binCodes.end());
    }

    // Print assembly instructions and their binary code
    std::cout << "Assembly Instructions and Corresponding Binary Codes:\n";
    for (size_t i = 0; i < binList.size(); ++i) {
        if (!binList[i].empty()) {
            std::cout << asmList[i] << "\nBinary: " << binList[i] << "\n\n";
        }
    }

    // Simulate the execution of the instructions
    simulate(asmList, binList, labelAddresses);

    return 0;
}

Parsing and Handling the .data and .text Sections
Updating the Assembler to Support Data Directives
Simulating Memory Allocation
Implementing the Instruction Pipeline Stages
Generating and Applying Control Signals
Simulating ALU Operations, Memory Access, and Branch Instructions
Ensuring Proper Reads and Writes to Registers and Memory
Updating the Program Counter (PC) Correctly
Let's tackle each of these points step by step.

1. Parsing and Handling the .data and .text Sections
In a MIPS assembly program, the .data section is used for declaring initialized data or constants, and the .text section is used for the actual code or instructions.

To handle these sections, you need to modify your assembler to parse the .data and .text sections appropriately.

Parsing Logic
State Tracking: Use a variable to keep track of the current section (.data or .text). This helps in knowing whether the lines being read are data declarations or instructions.
Handling the .data Section:
Parse data declarations, such as labels and directives like .word, .byte, etc.
Allocate memory addresses to the variables declared.
Keep a symbol table for variables, mapping variable names to their memory addresses and values.
Handling the .text Section:
Parse the instructions as you currently do.
Use the symbol table for labels and variables.
Implementation Steps
Modify the Preprocessing Function In your preprocessLine function, add logic to detect when a line contains a section directive .data or .text and set the current section accordingly.
Update the Line Processing in main In your main function, modify the loop that reads the assembly file to handle data declarations when the current section is .data.
Extend the Assembler Update your assembleInstruction function (or create a new function) to handle data declarations. For data declarations, you need to allocate memory addresses and store initial values.
2. Updating the Assembler to Support Data Directives
Handling Data Declarations
Suppose you have the following in your .data section:


.data
var1:   .word 5
array:  .word 1, 2, 3, 4
msg:    .asciiz "Hello, World!"
You need to:

Recognize data declarations.
Assign memory addresses to variables.
Store their initial values in your simulated memory.
Implementation
Symbol Table for Variables Create a symbol table (e.g., std::unordered_map<std::string, int> dataSymbolTable;) that maps variable names to their memory addresses.
Memory Allocation Decide on a starting address for your data segment (e.g., int dataSegmentStart = 0x10000000;). As you encounter data declarations, increment the address accordingly, considering the size of each data type.
Parsing Data Directives Extend your parsing code to recognize directives like .word, .byte, .half, .asciiz, etc.
Storing Initial Values As you parse the data declarations, store the initial values in your simulated memory (e.g., std::unordered_map<int, int> memory;).
3. Simulating Memory Allocation
Your memory model should simulate the MIPS memory layout, including:

Text Segment: Contains the instructions.
Data Segment: Contains global/static variables.
Stack Segment: Used for function calls and local variables.
For simplicity, you can represent memory as a large array or an std::unordered_map.

Implementation
Update your simulate function to initialize the memory with the data from the .data section.
Ensure that when instructions like lw or sw access memory, they use the correct addresses corresponding to the variables in the data segment.
4. Implementing the Instruction Pipeline Stages
Your simulator should follow the MIPS pipeline stages:

Instruction Fetch (IF)
Instruction Decode (ID)
Execution (EX)
Memory Access (MEM)
Write Back (WB)
Simplifying Assumptions
For this project, you can simulate these stages sequentially for each instruction, without implementing pipeline hazards or delays.
Implementation Steps
In your simulate function, for each instruction, simulate the stages:

IF: Fetch the instruction from the instruction memory (your binList).
ID: Decode the instruction, extract opcode, registers, immediate values.
EX: Perform the ALU operation.
MEM: Access memory if required (e.g., lw, sw).
WB: Write results back to the register file.
Updating the Simulation Function
Modify your simulate function to explicitly model these stages for each instruction.

5. Generating and Applying Control Signals
Although you're simulating instructions sequentially, it's important to model the control signals generated for each instruction.

Control Signals in MIPS
For each instruction, control signals determine:

Which operation the ALU should perform.
Whether to read/write memory.
Whether to write back to a register.
How to update the PC.
Implementation
For each instruction, determine the control signals based on the opcode and function code.
Use a control unit function or logic that takes the opcode and outputs the control signals.
Apply these control signals during the simulation to control the data flow.
6. Simulating ALU Operations, Memory Access, and Branch Instructions
Ensure that your simulator can:

Perform all the necessary ALU operations.
Handle memory accesses, including correct address calculations with offsets.
Correctly update the program counter for branch and jump instructions.
Extending the Execution Logic
In the simulate function:

ALU Operations: For R-type and I-type arithmetic/logical instructions, simulate the ALU operation in the EX stage.
Memory Access: For lw and sw, calculate the effective address (base register + offset) and access memory in the MEM stage.
Branch Instructions: For branch instructions like beq, calculate the branch target address and update the PC accordingly.
7. Ensuring Proper Reads and Writes to Registers and Memory
Your simulator should maintain accurate states for:

Register File: An array or map of 32 general-purpose registers.
Memory: Simulated as an array or map to hold data and instructions.
Program Counter (PC): To keep track of instruction addresses.
Implementation Details
Register Zero ($zero): Ensure that register $0 is always zero, as per MIPS convention.
Register Writes: Only write to the destination register during the Write Back stage.
Memory Alignment: Ensure that memory accesses are word-aligned (multiples of 4 bytes for word accesses).
8. Updating the Program Counter (PC) Correctly
Correct PC updates are crucial, especially for branch and jump instructions.

Implementing PC Logic
In your simulate function:

After each instruction, increment the PC by 4 (since each instruction is 4 bytes), unless a branch or jump occurs.
For branch instructions (beq, bne), calculate the branch target address.
For jump instructions (j, jal), set the PC to the target address.
Handle the return address register ($ra or $31) for jal.
Let's tie all these steps together with code snippets and explanations.

Implementing the Enhancements
1. Updating the Preprocessing Function
Modify your preprocessLine function to track the current section:


enum Section { UNKNOWN, DATA, TEXT };

Section currentSection = UNKNOWN;

std::string preprocessLine(const std::string& line, Section& currentSection) {
    std::string processed = line;
    // Remove comments
    size_t commentPos = processed.find('#');
    if (commentPos != std::string::npos) {
        processed = processed.substr(0, commentPos);
    }
    // Trim whitespace
    processed.erase(processed.begin(),
                    std::find_if(processed.begin(), processed.end(), [](unsigned char ch) { return !std::isspace(ch); }));
    processed.erase(std::find_if(processed.rbegin(), processed.rend(), [](unsigned char ch) { return !std::isspace(ch); }).base(),
                    processed.end());

    if (processed.empty()) {
        return processed;
    }

    // Check for section directives
    if (processed == ".data") {
        currentSection = DATA;
    } else if (processed == ".text") {
        currentSection = TEXT;
    }

    return processed;
}
In your main function, adjust the call to preprocessLine to pass currentSection:


int main() {
    // ...
    Section currentSection = UNKNOWN;
    while (std::getline(infile, line)) {
        std::string processedLine = preprocessLine(line, currentSection);
        if (processedLine.empty()) {
            continue;
        }
        // Handle depending on current section
        if (currentSection == DATA) {
            // Parse and handle data declarations
        } else if (currentSection == TEXT) {
            // Parse and handle text (instructions)
        } else {
            // Ignore or error
        }
        // ...
    }
    // ...
}
2. Handling the .data Section
In your main function, add code to parse data declarations when currentSection == DATA.

Create a symbol table for variables:


struct DataItem {
    int address;
    std::vector<int> values; // To handle arrays
};

std::unordered_map<std::string, DataItem> dataSymbolTable;
int dataAddress = 0x10000000; // Starting address of data segment
In your processing loop:


if (currentSection == DATA) {
    // Parse data declarations
    // Remove commas and split the line
    std::string line = processedLine;
    line.erase(std::remove(line.begin(), line.end(), ','), line.end());
    std::istringstream iss(line);
    std::vector<std::string> parts{std::istream_iterator<std::string>{iss},
                                   std::istream_iterator<std::string>{}};
    if (parts.empty()) {
        continue;
    }
    // Handle label
    if (parts[0].back() == ':') {
        std::string label = parts[0].substr(0, parts[0].length() - 1);
        parts.erase(parts.begin());
        if (parts.empty()) {
            // Next line contains the directive
            std::getline(infile, line);
            processedLine = preprocessLine(line, currentSection);
            line = processedLine;
            line.erase(std::remove(line.begin(), line.end(), ','), line.end());
            iss.clear();
            iss.str(line);
            parts = {std::istream_iterator<std::string>{iss},
                     std::istream_iterator<std::string>{}};
        }
        if (!parts.empty()) {
            std::string directive = parts[0];
            // Handle directives like .word, .byte, etc.
            if (directive == ".word") {
                parts.erase(parts.begin());
                std::vector<int> values;
                for (const auto& valStr : parts) {
                    int val = parseImmediate(valStr);
                    values.push_back(val);
                }
                DataItem item;
                item.address = dataAddress;
                item.values = values;
                dataSymbolTable[label] = item;
                dataAddress += static_cast<int>(values.size() * 4); // Each word is 4 bytes
            }
            // Handle other directives similarly
            else if (directive == ".space") {
                // Allocate space without initializing
                int size = parseImmediate(parts[1]);
                DataItem item;
                item.address = dataAddress;
                item.values.resize(size / 4, 0); // Assuming size in bytes, word-aligned
                dataSymbolTable[label] = item;
                dataAddress += size;
            }
            else if (directive == ".ascii" || directive == ".asciiz") {
                // Handle string data
                parts.erase(parts.begin());
                std::string str = line.substr(line.find('"') + 1);
                str = str.substr(0, str.rfind('"'));
                DataItem item;
                item.address = dataAddress;
                for (char c : str) {
                    item.values.push_back(static_cast<int>(c));
                    dataAddress += 1;
                }
                if (directive == ".asciiz") {
                    item.values.push_back(0); // Null terminator
                    dataAddress += 1;
                }
                dataSymbolTable[label] = item;
                // Align dataAddress to next word if necessary
                if (dataAddress % 4 != 0) {
                    dataAddress += (4 - (dataAddress % 4));
                }
            }
            // Add other directives as needed
        }
    }
}
3. Initializing Memory with Data Declarations
After parsing the .data section, you need to initialize your simulated memory with the data.

Create a memory structure:


std::unordered_map<int, int> memory; // For word-addressable memory
// Alternatively, use std::vector<char> memory(0x20000000); for byte-addressable memory
After processing the data declarations, initialize the memory:


for (const auto& [label, item] : dataSymbolTable) {
    int addr = item.address;
    for (int value : item.values) {
        memory[addr] = value;
        addr += 4; // Assuming word-aligned data
    }
}
For byte or character data, adjust accordingly.

4. Modifying the Assembler for Variables
In your assembleInstruction function and simulation, when an instruction uses a variable (e.g., lw $t0, var1), you need to translate var1 to its memory address.

Modify your instruction parsing to handle variable operands:

In assembleInstruction:


// For I-type instructions like lw, sw
if (opcodeStr == "lw" || opcodeStr == "sw" || ...) {
    // ...
    // Check if immediate part is a variable
    std::string offsetStr = parts[2].substr(0, start);
    int immediate;
    if (dataSymbolTable.count(offsetStr)) {
        immediate = dataSymbolTable[offsetStr].address;
    } else {
        immediate = parseImmediate(offsetStr);
    }
    // ...
}
Similarly, during simulation, resolve variable names to memory addresses using the data symbol table.

5. Simulating the Instruction Pipeline Stages
In your simulate function, explicitly include the pipeline stages. Here's an outline:


void simulate(...arguments...) {
    // ...
    while (PC / 4 < numInstructions) {
        // IF Stage
        std::string binCode = binList[PC / 4];
        
        // ID Stage
        // Parse binary code or use the assembly instruction
        std::string curr = asmList[PC / 4];
        // Decode the instruction
        // Extract opcode, registers, immediate values

        // EX Stage
        // Perform ALU operations

        // MEM Stage
        // Access or write to memory if necessary

        // WB Stage
        // Write results back to the register file

        // Update PC
        PC += 4; // Or adjust for branches/jumps
    }
}
6. Generating and Applying Control Signals
Create a function to generate control signals:


struct ControlSignals {
    bool RegDst;
    bool ALUSrc;
    bool MemToReg;
    bool RegWrite;
    bool MemRead;
    bool MemWrite;
    bool Branch;
    bool Jump;
    int ALUOp;
};

ControlSignals generateControlSignals(const std::string& opcodeStr, const std::string& functStr) {
    ControlSignals signals = {};
    // Set signals based on opcode and funct
    if (opcodeStr == "lw") {
        signals.ALUSrc = true;
        signals.MemToReg = true;
        signals.RegWrite = true;
        signals.MemRead = true;
        signals.ALUOp = 0; // ALU performs addition
    }
    else if (opcodeStr == "sw") {
        signals.ALUSrc = true;
        signals.MemWrite = true;
        signals.ALUOp = 0;
    }
    else if (opcodeStr == "add") {
        signals.RegDst = true;
        signals.RegWrite = true;
        signals.ALUOp = 2; // ALU performs addition
    }
    // Handle other instructions similarly
    return signals;
}
In your simulation, use the control signals to determine the behavior at each stage.

For example, in the EX stage:


int ALUResult;
if (signals.ALUSrc) {
    ALUResult = reg[rs] + immediate;
} else {
    ALUResult = reg[rs] + reg[rt]; // Assuming ALUOp == 0 for addition
}
7. Simulating Memory Access and Branch Instructions
For memory access, in the MEM stage:


if (signals.MemRead) {
    MDR = memory[ALUResult];
}
if (signals.MemWrite) {
    memory[ALUResult] = reg[rt];
}
For branches, update the PC accordingly:


if (signals.Branch && ZeroFlag) {
    PC = PC + (immediate << 2); // Branch taken
} else {
    PC += 4; // Next instruction
}
8. Ensuring Proper Reads and Writes
Ensure that in the WB stage, you write back to the correct register:


if (signals.RegWrite) {
    if (signals.MemToReg) {
        reg[writeReg] = MDR;
    } else {
        reg[writeReg] = ALUResult;
    }
}
Testing and Validation
After implementing these changes:

Test Simple Programs: Start with small assembly programs that use variables from the .data section.
Check Memory and Registers: After simulation, verify that the registers and memory have the expected values.
Edge Cases: Test with edge cases like unaligned memory accesses, branches to invalid addresses, etc.
Final Notes
Error Handling: Implement robust error handling throughout your assembler and simulator to catch invalid instructions, undefined labels, etc.
Modular Code: Keep your code modular by separating different functionalities into functions or classes.
Comments and Documentation: Comment your code thoroughly to make it easier to understand and maintain.
Progressive Enhancement: Build and test incrementally, ensuring each part works before moving on to the next.
I hope this comprehensive guide helps you modify your MIPS assembler and simulator to handle the .data and .text sections with memory allocations and to simulate the execution of MIPS instructions as per your project requirements.

If you have any specific questions or need further assistance with any part of the implementation, feel free to ask!  write complete code again