for 

"class MIPSCompiler:
    def __init__(self):
        # Initialize registers and opcodes using helper methods
        self.register_map = self._initialize_registers()
        self.opcodes = self._initialize_opcodes()
        self.data_mem = {}
        self.symbol_table = {}
        self.text_start = 0x00400000
        self.data_start = 0x10010000

    def _initialize_registers(self):
        """Initialize the register mappings."""
        return {
            '$zero': 0, '$0': 0,
            '$at': 1, '$v0': 2, '$v1': 3,
            '$a0': 4, '$a1': 5, '$a2': 6, '$a3': 7,
            '$t0': 8, '$t1': 9, '$t2': 10, '$t3': 11,
            '$t4': 12, '$t5': 13, '$t6': 14, '$t7': 15,
            '$s0': 16, '$s1': 17, '$s2': 18, '$s3': 19,
            '$s4': 20, '$s5': 21, '$s6': 22, '$s7': 23,
            '$t8': 24, '$t9': 25,
            '$k0': 26, '$k1': 27,
            '$gp': 28, '$sp': 29, '$fp': 30, '$ra': 31
        }

    def _initialize_opcodes(self):
        """Initialize the opcode and function code mappings."""
        return {
            'R': {
                'add': {'opcode': '000000', 'funct': '100000'},
                'sub': {'opcode': '000000', 'funct': '100010'},
                'and': {'opcode': '000000', 'funct': '100100'},
                'or':  {'opcode': '000000', 'funct': '100101'},
                'slt': {'opcode': '000000', 'funct': '101010'}
            },
            'I': {
                'lw':   '100011',
                'sw':   '101011',
                'beq':  '000100',
                'addi': '001000'
            },
            'J': {
                'j': '000010'
            }
        }

    def compile_file(self, filename="task1.asm"):
        """Compile the entire assembly file."""
        try:
            with open(filename, 'r') as file:
                lines = file.readlines()
        except FileNotFoundError:
            print(f"Error: {filename} not found.")
            return [], {}

        # First pass: parse data and collect labels
        self._first_pass(lines)

        # Second pass: compile instructions
        compiled_instructions = self._second_pass(lines)

        return compiled_instructions, self.data_mem

    def _first_pass(self, lines):
        """First pass to parse data sections and collect labels."""
        current_address = self.text_start
        section = None

        for line in lines:
            stripped = line.strip()
            if not stripped or stripped.startswith('#'):
                continue

            if stripped.startswith('.data'):
                section = 'data'
                continue
            elif stripped.startswith('.text'):
                section = 'text'
                continue

            if section == 'data':
                self._parse_data_line(stripped)
            elif section == 'text':
                if ':' in stripped:
                    label, _ = stripped.split(':', 1)
                    self.symbol_table[label.strip()] = current_address
                else:
                    current_address += 4  # Increment address for each instruction

    def _parse_data_line(self, line):
        """Parse individual lines in the data section."""
        tokens = line.split()
        if len(tokens) >= 3 and tokens[1] == '.word':
            label = tokens[0].rstrip(':')
            value = int(tokens[2])
            self.data_mem[label] = {
                'type': '.word',
                'value': value,
                'address': self.data_start
            }
            self.data_start += 4

    def _second_pass(self, lines):
        """Second pass to compile text instructions."""
        compiled = []
        current_address = self.text_start
        section = None

        for line in lines:
            stripped = line.strip()
            if not stripped or stripped.startswith('#'):
                continue

            if stripped.startswith('.data'):
                section = 'data'
                continue
            elif stripped.startswith('.text'):
                section = 'text'
                continue

            if section == 'text':
                if ':' in stripped:
                    continue  # Labels are already processed in the first pass
                binary = self._compile_instruction(stripped, current_address)
                if binary:
                    compiled.append({
                        'binary': binary,
                        'original': stripped,
                        'address': f"{current_address:08x}"
                    })
                    current_address += 4

        return compiled

    def _compile_instruction(self, instruction, address):
        """Compile a single instruction line into binary."""
        parts = instruction.replace(',', '').split()
        if not parts:
            return None

        op = parts[0].lower()
        if op in self.opcodes['R']:
            return self._encode_r_type(op, parts[1:])
        elif op in self.opcodes['I']:
            return self._encode_i_type(op, parts[1:], address)
        elif op in self.opcodes['J']:
            return self._encode_j_type(op, parts[1:])
        return None

    def _encode_r_type(self, op, operands):
        """Encode R-type instructions."""
        funct = self.opcodes['R'][op]['funct']
        opcode = self.opcodes['R'][op]['opcode']
        rd, rs, rt = operands
        rs_bin = format(self.register_map.get(rs, 0), '05b')
        rt_bin = format(self.register_map.get(rt, 0), '05b')
        rd_bin = format(self.register_map.get(rd, 0), '05b')
        shamt = '00000'
        return f"{opcode}{rs_bin}{rt_bin}{rd_bin}{shamt}{funct}"

    def _encode_i_type(self, op, operands, current_address):
        """Encode I-type instructions."""
        opcode = self.opcodes['I'][op]
        if op in ['lw', 'sw']:
            rt, expr = operands
            offset, base = self._parse_memory_operand(expr)
            rs_bin = format(self.register_map.get(base, 0), '05b')
            rt_bin = format(self.register_map.get(rt, 0), '05b')
            imm = format(offset & 0xFFFF, '016b')
            return f"{opcode}{rs_bin}{rt_bin}{imm}"
        elif op == 'addi':
            rs, rt, imm = operands
            rs_bin = format(self.register_map.get(rs, 0), '05b')
            rt_bin = format(self.register_map.get(rt, 0), '05b')
            imm_bin = format(int(imm) & 0xFFFF, '016b')
            return f"{opcode}{rs_bin}{rt_bin}{imm_bin}"
        elif op == 'beq':
            rs, rt, label = operands
            rs_bin = format(self.register_map.get(rs, 0), '05b')
            rt_bin = format(self.register_map.get(rt, 0), '05b')
            offset = self._calculate_branch_offset(label, current_address)
            imm = format(offset & 0xFFFF, '016b')
            return f"{opcode}{rs_bin}{rt_bin}{imm}"
        return None

    def _parse_memory_operand(self, operand):
        """Parse memory operands in the format offset(base)."""
        if '(' in operand and ')' in operand:
            offset, base = operand.split('(')
            base = base.rstrip(')')
            return int(offset), base
        else:
            # Assume label-based operand
            label = operand
            if label in self.data_mem:
                offset = self.data_mem[label]['address'] - self.data_start
                base = '$zero'
            else:
                offset = 0
                base = '$zero'
            return offset, base

    def _calculate_branch_offset(self, label, current_address):
        """Calculate the offset for branch instructions."""
        if label in self.symbol_table:
            target_address = self.symbol_table[label]
            offset = (target_address - (current_address + 4)) // 4
        else:
            offset = 0
        return offset

    def _encode_j_type(self, op, operands):
        """Encode J-type instructions."""
        opcode = self.opcodes['J'][op]
        target = operands[0]
        if target.isdigit():
            target_address = int(target) >> 2
        else:
            target_address = self.symbol_table.get(target, 0) >> 2
        addr_bin = format(target_address & 0x3FFFFFF, '026b')
        return f"{opcode}{addr_bin}"


class MIPSSimulator:
    def __init__(self, compiler):
        self.compiler = compiler
        self.registers = self._initialize_registers()
        self.memory = {}
        self.pc = compiler.text_start
        self.alu_ops = self._initialize_alu_operations()

    def _initialize_registers(self):
        """Initialize registers with default values."""
        regs = {f'${i}': 0 for i in range(32)}
        regs['$zero'] = 0
        regs['$sp'] = 0x7FFFFFFC
        return regs

    def _initialize_alu_operations(self):
        """Define ALU operations."""
        return {
            'add': lambda x, y: x + y,
            'sub': lambda x, y: x - y,
            'and': lambda x, y: x & y,
            'or':  lambda x, y: x | y,
            'slt': lambda x, y: 1 if x < y else 0,
        }

    def load_data(self):
        """Load data section into memory."""
        for label, data in self.compiler.data_mem.items():
            self.memory[data['address']] = data['value']
            print(f"Loaded {data['value']} into memory at address 0x{data['address']:08x}")

    def execute_program(self, instructions):
        """Execute the compiled MIPS program."""
        self.load_data()
        for instr in instructions:
            if self.pc != int(instr['address'], 16):
                self.pc = int(instr['address'], 16)
            print(f"Executing instruction at 0x{self.pc:08x}: {instr['original']}")
            self.execute_instruction(instr['binary'])
            self.pc += 4
        self.display_final_state()

    def execute_instruction(self, binary):
        """Execute a single binary instruction."""
        op = binary[:6]
        if op == '000000':  # R-type
            self._execute_r_type(binary)
        elif op in {'001000', '100011', '101011', '000100'}:  # I-type
            self._execute_i_type(op, binary)
        elif op == '000010':  # J-type
            self._execute_j_type(binary)

    def _execute_r_type(self, binary):
        """Execute R-type instructions."""
        rs = int(binary[6:11], 2)
        rt = int(binary[11:16], 2)
        rd = int(binary[16:21], 2)
        funct = binary[26:32]
        rs_val = self.registers.get(f'${rs}', 0)
        rt_val = self.registers.get(f'${rt}', 0)
        rd_name = f'${rd}'

        operation = next((op for op, codes in self.compiler.opcodes['R'].items() if codes['funct'] == funct), None)
        if operation and operation in self.alu_ops:
            result = self.alu_ops[operation](rs_val, rt_val)
            self.registers[rd_name] = result & 0xFFFFFFFF  # Ensure 32-bit
            print(f"{operation} executed: {rd_name} = {rs_val} {operation} {rt_val} -> {result}")
        else:
            print(f"Unknown R-type funct code: {funct}")

    def _execute_i_type(self, op, binary):
        """Execute I-type instructions."""
        if op == '001000':  # addi
            rs = int(binary[6:11], 2)
            rt = int(binary[11:16], 2)
            imm = self._sign_extend(binary[16:])
            rs_val = self.registers.get(f'${rs}', 0)
            self.registers[f'${rt}'] = (rs_val + imm) & 0xFFFFFFFF
            print(f"addi: ${rt} = {rs_val} + {imm} -> {self.registers[f'${rt}']}")
        elif op == '100011':  # lw
            rs = int(binary[6:11], 2)
            rt = int(binary[11:16], 2)
            imm = self._sign_extend(binary[16:])
            address = (self.registers.get(f'${rs}', 0) + imm) & 0xFFFFFFFF
            self.registers[f'${rt}'] = self.memory.get(address, 0)
            print(f"lw: Loaded {self.registers[f'${rt}']} into ${rt} from address 0x{address:08x}")
        elif op == '101011':  # sw
            rs = int(binary[6:11], 2)
            rt = int(binary[11:16], 2)
            imm = self._sign_extend(binary[16:])
            address = (self.registers.get(f'${rs}', 0) + imm) & 0xFFFFFFFF
            self.memory[address] = self.registers.get(f'${rt}', 0)
            print(f"sw: Stored {self.registers.get(f'${rt}', 0)} from ${rt} into address 0x{address:08x}")
        elif op == '000100':  # beq
            rs = int(binary[6:11], 2)
            rt = int(binary[11:16], 2)
            imm = self._sign_extend(binary[16:])
            rs_val = self.registers.get(f'${rs}', 0)
            rt_val = self.registers.get(f'${rt}', 0)
            if rs_val == rt_val:
                branch_addr = (self.pc + (imm << 2)) & 0xFFFFFFFF
                print(f"beq: Branching to 0x{branch_addr:08x} because ${rs} == ${rt}")
                self.pc = branch_addr - 4  # Adjust because PC will be incremented by 4 later
            else:
                print(f"beq: No branch taken because ${rs} != ${rt}")

    def _execute_j_type(self, binary):
        """Execute J-type instructions."""
        target = int(binary[6:], 2) << 2
        self.pc = (self.pc & 0xF0000000) | target
        print(f"j: Jumping to address 0x{self.pc:08x}")

    def _sign_extend(self, binary_str):
        """Sign-extend a 16-bit binary string to a signed integer."""
        value = int(binary_str, 2)
        if binary_str[0] == '1':
            value -= (1 << 16)
        return value

    def display_final_state(self):
        """Display the final state of registers and memory."""
        print("\nFinal Register State:")
        # Sort registers based on their register numbers using register_map
        sorted_regs = sorted(
            self.registers.keys(),
            key=lambda x: self.compiler.register_map.get(x, 32)  # Default to 32 for undefined registers
        )
        for reg in sorted_regs:
            print(f"{reg}: {self.registers[reg]}")

        print("\nFinal Memory State:")
        for addr in sorted(self.memory.keys()):
            print(f"0x{addr:08x}: {self.memory[addr]}")
        print("-" * 30)

def main():
    compiler = MIPSCompiler()
    asm_file = "test_code_3_mips_sim.asm"
    instructions, data_section = compiler.compile_file(asm_file)
    if not instructions:
        print("No instructions compiled. Exiting.")
        return
    simulator = MIPSSimulator(compiler)
    simulator.execute_program(instructions)


if __name__ == "__main__":
    main()"

and test_code_3_misp_sim.asm
.data
    num1: .word 8
    num2: .word 3

.text
    lw $a0, num1        # Load the value of num1 into $a0
    lw $a1, num2        # Load the value of num2 into $a1
    addi $a2, $a0, 5    # Add 5 to the value in $a0 and store in $a2
    beq $a0, $a1, case_equal  # Branch if num1 equals num2
    sub $t0, $a0, $a1   # Subtract num2 from num1, result in $t0
    j end_program        # Jump to end_program
case_equal:
    add $t0, $a0, $a1   # If equal, add num1 and num2, result in $t0

end_program:

Traceback (most recent call last):
  File "c:\Users\auspi\OneDrive\Desktop\CArch_Assignment_5\B22CS004._B22CS087.py", line 371, in <module>
    main()
  File "c:\Users\auspi\OneDrive\Desktop\CArch_Assignment_5\B22CS004._B22CS087.py", line 362, in main
    instructions, data_section = compiler.compile_file(asm_file)
                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "c:\Users\auspi\OneDrive\Desktop\CArch_Assignment_5\B22CS004._B22CS087.py", line 60, in compile_file
    compiled_instructions = self._second_pass(lines)
                            ^^^^^^^^^^^^^^^^^^^^^^^^
  File "c:\Users\auspi\OneDrive\Desktop\CArch_Assignment_5\B22CS004._B22CS087.py", line 124, in _second_pass
    binary = self._compile_instruction(stripped, current_address)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "c:\Users\auspi\OneDrive\Desktop\CArch_Assignment_5\B22CS004._B22CS087.py", line 145, in _compile_instruction
    return self._encode_i_type(op, parts[1:], address)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "c:\Users\auspi\OneDrive\Desktop\CArch_Assignment_5\B22CS004._B22CS087.py", line 165, in _encode_i_type
    rt, expr = operands
    ^^^^^^^^
ValueError: too many values to unpack (expected 2)