# Simple MIPS Assembly Program to Test the 'add' Instruction

        .data
# No data needed for this simple program

        .text
# Code section starts here

main:
        # Initialize registers with immediate values
        li $t0, 5      # Load immediate value 5 into $t0 (register $8)
        li $t1, 10     # Load immediate value 10 into $t1 (register $9)

        # Perform addition
        add $t2, $t0, $t1     # $t2 = $t0 + $t1

        # End of program// 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;
}PS C:\Users\91798\Mips_simulator\output> & .\'Main.exe'
Unsupported instruction: .data