Integrate the following Instructions class into the Processor code (add it as an attribute). Make it so that when the mips code is being decoded the variables are stored in the data memory. Also, replace the list in the data memory with a map so that the data is accessed with the help of addresses calculated using them as keys. Also make sure that the offsets are handled just as they are in the original mips processor (multiple of 4 etc). Also make sure to integrate things such as sign extension etc. Use the sample mips code provided to simulate it in the final processor.



class Instructions:
    def __init__(self):
        self.R_type= ['add', 'sub', 'and', 'or', 'slt']
        self.I_type= ['lw', 'sw', 'beq', 'addi']
        self.J_type= ['j']
        self.opcodes= {
            'add': '000000', 'sub': '000000', 'and': '000000', 'or': '000000', 'slt': '000000',
            'lw': '100011', 'sw': '101011', 'beq': '000100', 'addi': '001000',
            'j': '000010'
        }
        self.funct= {
            'add': '100000', 'sub': '100010', 'and': '100100', 'or': '100101', 'slt': '101010'
        }
        self.registers={
            "$zero": '00000', "$v0": '00010', "$v1": '00011',
            "$a0": '00100', "$a1": '00101', "$a2": '00110', "$a3": '00111',
            "$t0": '01000', "$t1": '01001', "$t2": '01010', "$t3": '01011',
            "$t4": '01100', "$t5": '01101', "$t6": '01110', "$t7": '01111',
            "$s0": '10000', "$s1": '10001', "$s2": '10010', "$s3": '10011',
            "$s4": '10100', "$s5": '10101', "$s6": '10110', "$s7": '10111',
            "$ra": '11111', "$1": '00001', "$at": '00001'
        }
        self.labels= {}
        self.current_address= 0
        self.memory= DataMemory()
        self.instruction_map= {}

    def mapping(self, instruction):
        if instruction in self.R_type:
            return '000000'
        elif instruction in self.I_type or instruction in self.J_type:
            return self.opcodes[instruction]
        else:
            return None

    def to_machine_code(self, instruction_line):
        instruction_line = instruction_line.strip().replace(',', '')
        parts = instruction_line.split()
        opcode = self.mapping(parts[0])

        # R-type instructions
        if parts[0] in self.R_type:
            rd = self.registers[parts[1]]
            rs = self.registers[parts[2]]
            rt = self.registers[parts[3]]
            funct = self.funct[parts[0]]
            shamt = '00000'
            return f"{opcode} {rs} {rt} {rd} {shamt} {funct}"

        # I-type instructions
        elif parts[0] in self.I_type:
            if parts[0] == 'lw':
                rt = self.registers[parts[1]]
                var_name = parts[2]
                if var_name in self.memory.memory:
                    var_address = int(self.memory.memory[var_name]['address'], 2)
                    offset = var_address - self.memory.registers["$1"]
                    return f"{opcode} {self.registers['$1']} {rt} {format(offset // 4, '016b')}"  # lw $t0, offset($1)
                else:
                    print(f"Error: Variable {var_name} not found in memory.")
                    return None

            elif parts[0] == 'addi':
                rt = self.registers[parts[1]]
                rs = self.registers[parts[2]]
                immediate = format(int(parts[3]), '016b')
                return f"{opcode} {rs} {rt} {immediate}"

            elif parts[0] == 'beq':
                rs = self.registers[parts[1]]
                rt = self.registers[parts[2]]
                offset = self.calculate_offset(parts[3])
                return f"{opcode} {rs} {rt} {offset}"

        # J-type instructions
        elif parts[0] in self.J_type:
            address = self.calculate_jump_address(parts[1])
            return f"{opcode} {address}"

    def calculate_offset(self, label):
        """Calculate the branch offset for labels."""
        target_address = self.labels[label]
        offset = (target_address - self.current_address - 4) // 4  # PC-relative offset
        return format(offset, '016b')

    def calculate_jump_address(self, label):
        """Calculate the jump address for labels."""
        print(self.labels[label])
        target_address = self.labels[label] // 4
        return format(target_address, '026b')

    def first_pass(self, program):
      address = 0
      data_section = False

      for line in program:
          line= line.strip()
          if not line:
              continue
          if line.startswith('.data'):
              data_section= True
              continue
          elif line.startswith('.text'):
              data_section= False
              continue

          if line.endswith(':'):
              label= line[:-1].strip()
              self.labels[label] = address
              print(f"Address for label '{label}': {address}")
              continue

          elif data_section:
              parts= line.split()
              var_name= parts[0].split(':')[0].strip()  #Get the variable name
              value = int(parts[2])  #Get the value after .word
              self.memory.add_variable(var_name, value)  #Add to memory
              continue
          else:
              print(f"Address: {address} for instruction: {line}")
              address += 4


      for label in self.labels:
          print(f"Address for label '{label}': {self.labels[label]}")

    def load_program_from_file(self, filename):
        """Load MIPS program from a text file."""
        with open(filename, 'r') as file:
            program = file.readlines()
        return program

    def compile_program(self, program):
        """Compile the full program into machine code."""
        binary_instructions = []
        self.first_pass(program)

        self.current_address = 0
        for line in program:
            line = line.strip()
            if not line:
              continue
            if not line or line.startswith('.'):
                continue
            if line.endswith(':'):
                continue
            machine_code = self.to_machine_code(line)
            if machine_code:
                binary_instructions.append(machine_code)
                self.instruction_map[self.current_address] = line
                self.current_address+= 4

        return binary_instructions


class Control_Signals():
    def __init__(self):
       self.RegDst = 0
       self.RegWrite = 0
       self.ALUsrc = 0
       self.MemWrite = 0
       self.MemRead = 0
       self.MemtoReg = 0
       self.Branch = 0
       self.Jump = 0
       self.ALUOp = "00"  # Add ALUOp for more complex ALU control

class Program_Counter():
    def __init__(self):
        self.count = 0

class Instruction_Memory():
    def __init__(self, machine_code):
        self.instructions = list(machine_code)

    def read_instruction(self, address):
        return self.instructions[address]

class Data_Memory():
    def __init__(self):
        self.data = [0] * 1024

    def data_read(self, address):
        return self.data[address]

    def data_write(self, address, data_val):
        self.data[address] = data_val

class Registers():
    def __init__(self):
        self.registers = [0] * 32

    def read_register(self, reg_num):
        return self.registers[reg_num]

    def write_register(self, reg_num, data):
        if reg_num != 0:  # Prevent writing to $zero
            self.registers[reg_num] = data

class ALU():
    def execute(self, op1, op2, alu_control):
        if alu_control == "add":
            return op1 + op2
        elif alu_control == "sub":
            return op1 - op2
        elif alu_control == "and":
            return op1 & op2
        elif alu_control == "or":
            return op1 | op2
        elif alu_control == "slt":
            return 1 if op1 < op2 else 0

class Processor():
    def __init__(self, instructions):
        self.control_unit = Control_Signals()
        self.program_counter = Program_Counter()
        self.instruction_memory = Instruction_Memory(instructions)
        self.data_memory = Data_Memory()
        self.registers = Registers()
        self.alu = ALU()

    def instruction_fetch(self):
        pc_value = self.program_counter.count
        if pc_value >= len(self.instruction_memory.instructions):
            return None  # No more instructions
        instruction = self.instruction_memory.read_instruction(pc_value)
        return instruction

    def instruction_decode(self, instruction):
        opcode = instruction[:6]
        rs = int(instruction[6:11], 2)
        rt = int(instruction[11:16], 2)
        rd = int(instruction[16:21], 2)
        imm = int(instruction[16:], 2) if instruction[16] == '0' else int(instruction[16:], 2) - (1 << 16)
        
        # Reset control signals
        self.control_unit = Control_Signals()
        
        # Set control signals based on opcode
        if opcode == "000000":  # R-type
            self.control_unit.RegDst = 1
            self.control_unit.RegWrite = 1
            self.control_unit.ALUOp = "10"
        elif opcode == "100011":  # lw
            self.control_unit.ALUsrc = 1
            self.control_unit.MemRead = 1
            self.control_unit.RegWrite = 1
            self.control_unit.MemtoReg = 1
        elif opcode == "101011":  # sw
            self.control_unit.ALUsrc = 1
            self.control_unit.MemWrite = 1
        elif opcode == "000100":  # beq
            self.control_unit.Branch = 1
            self.control_unit.ALUOp = "01"
        elif opcode == "000101":  # bne
            self.control_unit.Branch = 1
            self.control_unit.ALUOp = "01"
        elif opcode == "001000":  # addi
            self.control_unit.ALUsrc = 1
            self.control_unit.RegWrite = 1
        elif opcode == "001010":  # slti
            self.control_unit.ALUsrc = 1
            self.control_unit.RegWrite = 1
            self.control_unit.ALUOp = "11"
        elif opcode == "000010":  # j
            self.control_unit.Jump = 1
        elif opcode == "001101":  # ori
            self.control_unit.ALUsrc = 1
            self.control_unit.RegWrite = 1
            self.control_unit.ALUOp = "11"

        return opcode, rs, rt, rd, imm

    def execution(self, rs_val, rt_val, imm, alu_control):
        if self.control_unit.ALUsrc == 1:
            alu_result = self.alu.execute(rs_val, imm, alu_control)
        else:
            alu_result = self.alu.execute(rs_val, rt_val, alu_control)
        return alu_result

    def memory_access(self, alu_result, rt_val):
        if self.control_unit.MemWrite:
            self.data_memory.data_write(alu_result, rt_val)
        elif self.control_unit.MemRead:
            return self.data_memory.data_read(alu_result)
        return None

    def write_back(self, alu_result, mem_data, rd, rt):
        if self.control_unit.RegWrite:
            write_data = mem_data if self.control_unit.MemtoReg else alu_result
            write_reg = rd if self.control_unit.RegDst else rt
            self.registers.write_register(write_reg, write_data)

    def run(self):
        while True:
            pc_value = self.program_counter.count
            instruction = self.instruction_fetch()
            if instruction is None:
                break  # Stop execution when no more instructions

            opcode, rs, rt, rd, imm = self.instruction_decode(instruction)
            rs_val = self.registers.read_register(rs)
            rt_val = self.registers.read_register(rt)

            alu_control = "add"  # Default ALU operation
            if self.control_unit.ALUOp == "01":
                alu_control = "sub"
            elif self.control_unit.ALUOp == "11":
                alu_control = "slt" if opcode == "001010" else "or"

            alu_result = self.execution(rs_val, rt_val, imm, alu_control)
            mem_data = self.memory_access(alu_result, rt_val)
            self.write_back(alu_result, mem_data, rd, rt)

            # Handle branching and jumping
            if self.control_unit.Branch:
                if (opcode == "000100" and alu_result == 0) or (opcode == "000101" and alu_result != 0):
                    self.program_counter.count = pc_value + 1 + imm
                else:
                    self.program_counter.count = pc_value + 1
            elif self.control_unit.Jump:
                self.program_counter.count = int(instruction[6:], 2)
            else:
                self.program_counter.count = pc_value + 1

            self.debug()

    def debug(self):
        print(f"PC: {self.program_counter.count}, Registers: {self.registers.registers}")
        print(f"PC: {self.program_counter.count}, Data Memory: {self.data_memory.data[:20]}")



MIPS CODE:

.data
    num1: .word 10
    num2: .word 20
.text
    lw $t0, num1
    lw $t1, num2
    add $t2, $t0, $t1
    sub $t3, $t1, $t0
    and $t4, $t0, $t1
    or  $t5, $t0, $t1
    slt $t6, $t0, $t1