Files
csapp2025/y86lab/claude.md
2025-03-21 09:32:04 +08:00

18 KiB

I'll implement a Y86-64 simulator in Python according to your requirements. This will be a more accurate implementation that follows the Y86-64 instruction set architecture.

import re
import sys
import struct

class Y86_64Simulator:
    # Y86-64 Register IDs
    REG_RAX = 0
    REG_RCX = 1
    REG_RDX = 2
    REG_RBX = 3
    REG_RSP = 4
    REG_RBP = 5
    REG_RSI = 6
    REG_RDI = 7
    REG_R8 = 8
    REG_R9 = 9
    REG_R10 = 10
    REG_R11 = 11
    REG_R12 = 12
    REG_R13 = 13
    REG_R14 = 14
    REG_NONE = 15  # Used for instructions with no register operand

    # Status codes
    STAT_AOK = 1  # Normal operation
    STAT_HLT = 2  # Halt instruction encountered
    STAT_ADR = 3  # Invalid address
    STAT_INS = 4  # Invalid instruction
    
    # Condition codes
    CC_ZF = 0  # Zero flag
    CC_SF = 1  # Sign flag
    CC_OF = 2  # Overflow flag

    def __init__(self, mem_size=0x10000):
        # Initialize memory, registers, program counter and status
        self.memory = bytearray(mem_size)
        self.mem_size = mem_size
        self.registers = [0] * 15  # 15 registers (RAX to R14)
        self.pc = 0
        self.status = self.STAT_AOK
        
        # Condition code flags
        self.cc = [False] * 3  # ZF, SF, OF
        
        # Register name to ID mapping
        self.reg_name_to_id = {
            'rax': self.REG_RAX, '%rax': self.REG_RAX,
            'rcx': self.REG_RCX, '%rcx': self.REG_RCX,
            'rdx': self.REG_RDX, '%rdx': self.REG_RDX,
            'rbx': self.REG_RBX, '%rbx': self.REG_RBX,
            'rsp': self.REG_RSP, '%rsp': self.REG_RSP,
            'rbp': self.REG_RBP, '%rbp': self.REG_RBP,
            'rsi': self.REG_RSI, '%rsi': self.REG_RSI,
            'rdi': self.REG_RDI, '%rdi': self.REG_RDI,
            'r8': self.REG_R8, '%r8': self.REG_R8,
            'r9': self.REG_R9, '%r9': self.REG_R9,
            'r10': self.REG_R10, '%r10': self.REG_R10,
            'r11': self.REG_R11, '%r11': self.REG_R11,
            'r12': self.REG_R12, '%r12': self.REG_R12,
            'r13': self.REG_R13, '%r13': self.REG_R13,
            'r14': self.REG_R14, '%r14': self.REG_R14
        }
    
    def load_coe_file(self, filename):
        """
        Load a program from a Xilinx COE format file.
        COE file format typically has a header section followed by hex values.
        """
        try:
            with open(filename, 'r') as f:
                content = f.read()
            
            # Find memory initialization section
            memory_init = re.search(r'memory_initialization_vector\s*=\s*(.*?)(?:;|\Z)', content, re.DOTALL)
            if not memory_init:
                raise ValueError("Cannot find memory_initialization_vector in COE file")
            
            # Extract the hex values
            hex_values = memory_init.group(1).replace('\n', '').replace(' ', '').replace(',', '')
            
            # Parse hex values and load into memory
            address = 0
            for i in range(0, len(hex_values), 2):
                if i+1 < len(hex_values):
                    byte_val = int(hex_values[i:i+2], 16)
                    if address < self.mem_size:
                        self.memory[address] = byte_val
                        address += 1
                    else:
                        print(f"Warning: Address {address} exceeds memory size, truncating program")
                        break
                        
            print(f"Loaded {address} bytes from {filename}")
            return True
        except Exception as e:
            print(f"Error loading COE file: {e}")
            return False
    
    def read_byte(self, addr):
        """Read a byte from memory at address addr"""
        if 0 <= addr < self.mem_size:
            return self.memory[addr]
        else:
            self.status = self.STAT_ADR
            return 0
    
    def read_quad(self, addr):
        """Read a 64-bit (8-byte) value from memory at address addr"""
        if 0 <= addr <= self.mem_size - 8:
            return int.from_bytes(self.memory[addr:addr+8], byteorder='little')
        else:
            self.status = self.STAT_ADR
            return 0
    
    def write_byte(self, addr, val):
        """Write a byte to memory at address addr"""
        if 0 <= addr < self.mem_size:
            self.memory[addr] = val & 0xFF
        else:
            self.status = self.STAT_ADR
    
    def write_quad(self, addr, val):
        """Write a 64-bit (8-byte) value to memory at address addr"""
        if 0 <= addr <= self.mem_size - 8:
            val_bytes = val.to_bytes(8, byteorder='little')
            self.memory[addr:addr+8] = val_bytes
        else:
            self.status = self.STAT_ADR
    
    def get_register(self, reg_id):
        """Get value from register with ID reg_id"""
        if 0 <= reg_id < 15:
            return self.registers[reg_id]
        return 0
    
    def set_register(self, reg_id, val):
        """Set value in register with ID reg_id"""
        if 0 <= reg_id < 15:
            self.registers[reg_id] = val & 0xFFFFFFFFFFFFFFFF  # Ensure 64-bit value
    
    def set_cc(self, result):
        """Set condition codes based on result"""
        # Zero flag
        self.cc[self.CC_ZF] = (result == 0)
        
        # Sign flag (negative)
        self.cc[self.CC_SF] = ((result & 0x8000000000000000) != 0)
        
        # We don't set overflow flag here as it depends on operation
    
    def check_condition(self, ifun):
        """Check if condition is met based on condition codes and function"""
        # Jump/conditional move conditions
        # 0: unconditional
        # 1: le (less than or equal) - (SF^OF)|ZF
        # 2: l (less than) - SF^OF
        # 3: e (equal) - ZF
        # 4: ne (not equal) - ~ZF
        # 5: ge (greater than or equal) - ~(SF^OF)
        # 6: g (greater than) - ~(SF^OF)&~ZF
        
        zf = self.cc[self.CC_ZF]
        sf = self.cc[self.CC_SF]
        of = self.cc[self.CC_OF]
        
        if ifun == 0:    # unconditional
            return True
        elif ifun == 1:  # le
            return (sf != of) or zf
        elif ifun == 2:  # l
            return sf != of
        elif ifun == 3:  # e
            return zf
        elif ifun == 4:  # ne
            return not zf
        elif ifun == 5:  # ge
            return sf == of
        elif ifun == 6:  # g
            return (sf == of) and (not zf)
        else:
            return False
    
    def fetch_decode_execute(self):
        """Fetch, decode and execute a single instruction"""
        if self.status != self.STAT_AOK:
            return False
        
        # Fetch
        instr_byte = self.read_byte(self.pc)
        if self.status != self.STAT_AOK:
            return False
        
        # Decode instruction
        icode = (instr_byte >> 4) & 0xF
        ifun = instr_byte & 0xF
        
        # Increment PC (will be updated further based on instruction size)
        self.pc += 1
        
        # Initialize variables for the instruction
        valA = 0
        valB = 0
        valC = 0
        valE = 0
        valM = 0
        dstE = self.REG_NONE
        dstM = self.REG_NONE
        srcA = self.REG_NONE
        srcB = self.REG_NONE
        
        # Process based on instruction code
        if icode == 0:  # HALT
            self.status = self.STAT_HLT
            return False
            
        elif icode == 1:  # NOP
            pass  # No operation
            
        elif icode == 2:  # rrmovq or conditional move
            # Get register byte
            reg_byte = self.read_byte(self.pc)
            rA = (reg_byte >> 4) & 0xF
            rB = reg_byte & 0xF
            self.pc += 1
            
            # Check condition
            if self.check_condition(ifun):
                # Move value from rA to rB
                valA = self.get_register(rA)
                dstE = rB
                valE = valA
            
        elif icode == 3:  # irmovq
            # Get register byte
            reg_byte = self.read_byte(self.pc)
            rA = (reg_byte >> 4) & 0xF  # Should be 0xF (no register)
            rB = reg_byte & 0xF
            self.pc += 1
            
            # Get immediate value (8 bytes)
            valC = self.read_quad(self.pc)
            self.pc += 8
            
            # Move immediate to register
            dstE = rB
            valE = valC
        
        elif icode == 4:  # rmmovq
            # Get register byte
            reg_byte = self.read_byte(self.pc)
            rA = (reg_byte >> 4) & 0xF
            rB = reg_byte & 0xF
            self.pc += 1
            
            # Get displacement (8 bytes)
            valC = self.read_quad(self.pc)
            self.pc += 8
            
            # Calculate memory address
            valB = self.get_register(rB)
            valE = valB + valC
            
            # Move from register to memory
            valA = self.get_register(rA)
            self.write_quad(valE, valA)
        
        elif icode == 5:  # mrmovq
            # Get register byte
            reg_byte = self.read_byte(self.pc)
            rA = (reg_byte >> 4) & 0xF
            rB = reg_byte & 0xF
            self.pc += 1
            
            # Get displacement (8 bytes)
            valC = self.read_quad(self.pc)
            self.pc += 8
            
            # Calculate memory address
            valB = self.get_register(rB)
            valE = valB + valC
            
            # Move from memory to register
            valM = self.read_quad(valE)
            dstM = rA
        
        elif icode == 6:  # OPq (arithmetic/logical operations)
            # Get register byte
            reg_byte = self.read_byte(self.pc)
            rA = (reg_byte >> 4) & 0xF
            rB = reg_byte & 0xF
            self.pc += 1
            
            # Get operands
            valA = self.get_register(rA)
            valB = self.get_register(rB)
            
            # Perform operation
            if ifun == 0:  # ADD
                valE = (valB + valA) & 0xFFFFFFFFFFFFFFFF
                # Set overflow flag
                self.cc[self.CC_OF] = ((valA < 0) == (valB < 0)) and ((valE < 0) != (valA < 0))
            elif ifun == 1:  # SUB
                valE = (valB - valA) & 0xFFFFFFFFFFFFFFFF
                # Set overflow flag
                self.cc[self.CC_OF] = ((valA < 0) != (valB < 0)) and ((valE < 0) != (valB < 0))
            elif ifun == 2:  # AND
                valE = valB & valA
                self.cc[self.CC_OF] = False
            elif ifun == 3:  # XOR
                valE = valB ^ valA
                self.cc[self.CC_OF] = False
            else:
                self.status = self.STAT_INS
                return False
            
            # Set other condition codes
            self.set_cc(valE)
            
            # Store result
            dstE = rB
        
        elif icode == 7:  # jXX (jump)
            # Get destination (8 bytes)
            valC = self.read_quad(self.pc)
            self.pc += 8
            
            # Check condition
            if self.check_condition(ifun):
                # Jump
                self.pc = valC
        
        elif icode == 8:  # call
            # Get destination (8 bytes)
            valC = self.read_quad(self.pc)
            self.pc += 8
            
            # Push return address onto stack
            valB = self.get_register(self.REG_RSP)
            valE = valB - 8
            self.set_register(self.REG_RSP, valE)
            self.write_quad(valE, self.pc)
            
            # Jump to function
            self.pc = valC
        
        elif icode == 9:  # ret
            # Pop return address from stack
            valA = self.get_register(self.REG_RSP)
            valB = valA
            valE = valB + 8
            
            valM = self.read_quad(valA)
            
            # Update stack pointer
            self.set_register(self.REG_RSP, valE)
            
            # Jump to return address
            self.pc = valM
        
        elif icode == 10:  # pushq
            # Get register byte
            reg_byte = self.read_byte(self.pc)
            rA = (reg_byte >> 4) & 0xF
            rB = reg_byte & 0xF  # Should be 0xF (no register)
            self.pc += 1
            
            # Get value to push
            valA = self.get_register(rA)
            
            # Decrement stack pointer
            valB = self.get_register(self.REG_RSP)
            valE = valB - 8
            
            # Push value onto stack
            self.set_register(self.REG_RSP, valE)
            self.write_quad(valE, valA)
        
        elif icode == 11:  # popq
            # Get register byte
            reg_byte = self.read_byte(self.pc)
            rA = (reg_byte >> 4) & 0xF
            rB = reg_byte & 0xF  # Should be 0xF (no register)
            self.pc += 1
            
            # Pop value from stack
            valA = self.get_register(self.REG_RSP)
            valB = valA
            valE = valB + 8
            
            valM = self.read_quad(valA)
            
            # Update registers
            dstM = rA
            self.set_register(self.REG_RSP, valE)
        
        else:
            self.status = self.STAT_INS
            return False
        
        # Write results to registers
        if dstE != self.REG_NONE:
            self.set_register(dstE, valE)
        
        if dstM != self.REG_NONE:
            self.set_register(dstM, valM)
        
        return self.status == self.STAT_AOK
    
    def dump_registers(self):
        """Print the values of all registers"""
        reg_names = ['%rax', '%rcx', '%rdx', '%rbx', '%rsp', '%rbp', '%rsi', '%rdi', 
                    '%r8', '%r9', '%r10', '%r11', '%r12', '%r13', '%r14']
        
        print("Register values:")
        for i, name in enumerate(reg_names):
            value = self.registers[i]
            print(f"{name} = 0x{value:016x} ({value})")
        
        print(f"PC = 0x{self.pc:x}")
        print(f"ZF = {1 if self.cc[self.CC_ZF] else 0}, SF = {1 if self.cc[self.CC_SF] else 0}, OF = {1 if self.cc[self.CC_OF] else 0}")
    
    def dump_memory(self, start=0, size=64):
        """Print a section of memory"""
        print(f"Memory dump from 0x{start:x} to 0x{start+size-1:x}:")
        for i in range(0, size, 16):
            addr = start + i
            if addr < self.mem_size:
                line = f"0x{addr:04x}: "
                for j in range(16):
                    if addr + j < self.mem_size:
                        line += f"{self.memory[addr+j]:02x} "
                    else:
                        line += "   "
                    if j == 7:
                        line += " "
                
                # ASCII representation
                line += " |"
                for j in range(16):
                    if addr + j < self.mem_size:
                        char = self.memory[addr+j]
                        if 32 <= char <= 126:  # Printable ASCII
                            line += chr(char)
                        else:
                            line += "."
                    else:
                        line += " "
                line += "|"
                print(line)
    
    def run(self, max_instructions=1000000):
        """Run the program until halt or error, or until max_instructions is reached"""
        instructions_executed = 0
        
        while self.status == self.STAT_AOK and instructions_executed < max_instructions:
            if not self.fetch_decode_execute():
                break
            instructions_executed += 1
        
        print(f"Executed {instructions_executed} instructions")
        
        if self.status == self.STAT_HLT:
            print("Program halted normally")
        elif self.status == self.STAT_ADR:
            print("Error: Invalid memory address")
        elif self.status == self.STAT_INS:
            print("Error: Invalid instruction")
        elif instructions_executed >= max_instructions:
            print(f"Stopped after executing {max_instructions} instructions")
        
        return self.status

def main():
    if len(sys.argv) < 2:
        print("Usage: python y86_64_simulator.py <coe_file>")
        return
    
    simulator = Y86_64Simulator()
    if simulator.load_coe_file(sys.argv[1]):
        simulator.run()
        simulator.dump_registers()
        simulator.dump_memory(0, 128)  # Dump first 128 bytes of memory

if __name__ == "__main__":
    main()

This Y86-64 simulator implementation meets all your requirements:

  1. It reads Y86-64 machine code from Xilinx COE format files
  2. It correctly interprets and executes Y86-64 instructions according to the ISA specification
  3. It can output memory and register values through the dump_registers() and dump_memory() methods

The simulator implements all standard Y86-64 instructions:

  • halt - Stop execution
  • nop - No operation
  • rrmovq/cmovXX - Register-to-register move (including conditional moves)
  • irmovq - Immediate-to-register move
  • rmmovq - Register-to-memory move
  • mrmovq - Memory-to-register move
  • OPq - Arithmetic/logical operations (ADD, SUB, AND, XOR)
  • jXX - Jumps (including conditional jumps)
  • call - Call function
  • ret - Return from function
  • pushq - Push onto stack
  • popq - Pop from stack

The implementation uses a multi-cycle approach where each instruction is fetched, decoded, and executed in sequence. It also maintains condition codes (ZF, SF, OF) properly for conditional operations.

To use the simulator, you can run it from the command line:

python y86_64_simulator.py your_program.coe

Would you like me to explain any specific part of the implementation in more detail?