{% extends "base_simulator.j2" %}

{% block register_initialization %}
        # Initialize registers
{%- for reg in isa.registers %}
        {%- if reg.is_vector_register() %}
        # Vector registers: nested lists of integers (no fields for now)
        self.{{ reg.name }} = [[0] * {{ reg.lanes }} for _ in range({{ reg.count if reg.count else 1 }})]
        {%- elif reg.is_register_file() %}
        # Register file: list of Register objects or integers
        {%- if reg.fields %}
        # Register has fields - use Register wrapper
        self.{{ reg.name }} = [
            Register({{ reg.width }}, [
                {%- for field in reg.fields %}
                ('{{ field.name }}', {{ field.msb }}, {{ field.lsb }}){% if not loop.last %},{% endif %}
                {%- endfor %}
            ]) for _ in range({{ reg.count }})
        ]
        {%- else %}
        # No fields - use plain integers
        self.{{ reg.name }} = [0] * {{ reg.count }}
        {%- endif %}
        {%- else %}
        # Single register
        {%- if reg.fields %}
        # Register has fields - use Register wrapper
        self.{{ reg.name }} = Register({{ reg.width }}, [
            {%- for field in reg.fields %}
            ('{{ field.name }}', {{ field.msb }}, {{ field.lsb }}){% if not loop.last %},{% endif %}
            {%- endfor %}
        ])
        {%- else %}
        # No fields - use plain integer
        self.{{ reg.name }} = 0
        {%- endif %}
        {%- endif %}
{%- endfor %}
        # Register aliases map (for fast lookup)
        self._register_aliases = {
{%- for alias in isa.register_aliases %}
            '{{ alias.alias_name }}': ('{{ alias.target_reg_name }}', {{ alias.target_index if alias.is_indexed() else 'None' }}),
{%- endfor %}
        }
        # Initialize external behavior handler for user-defined instruction behaviors
        self.external_behavior = ExternalBehaviorHandler(self)
{% endblock %}

{% block load_methods %}
    def load_program(self, program: List[int], start_address: int = 0):
        """Load a program into memory."""
        for i, instruction in enumerate(program):
            self.memory[start_address + i * 4] = instruction
        self.pc = start_address

    def load_binary_file(self, filename: str, start_address: int = 0):
        """Load a binary file into memory."""
        with open(filename, 'rb') as f:
            data = f.read()
            address = start_address
            i = 0
            while i < len(data):
                if i + 4 <= len(data):
                    word = int.from_bytes(data[i:i+4], byteorder='little')
                    self.memory[address] = word
                    address += 4
                    i += 4
                else:
                    # Handle partial word at end of file
                    remaining_bytes = len(data) - i
                    if remaining_bytes > 0:
                        # Pad with zeros to make a complete word
                        word_bytes = bytearray(data[i:])
                        word_bytes.extend([0] * (4 - remaining_bytes))
                        word = int.from_bytes(bytes(word_bytes), byteorder='little')
                        self.memory[address] = word
                    break
        self.pc = start_address
{% endblock %}

{% block step_method %}
    def step(self) -> bool:
        """Execute one instruction with dynamic width loading. Returns True if execution continues."""
        if self.halted:
            return False

        # Step 1: Identify instruction by loading minimum bits and matching
        # Strategy: Try formats from shortest to longest
        # Collect all format widths and their minimum identification bits
        matched_mnemonic = None
        matched_width = None
        
        # Try each unique format width (sorted shortest first)
        {%- set all_widths = [] %}
        {%- for instr in isa.instructions %}
        {%- if instr.format %}
        {%- set _ = all_widths.append(instr.format.width) %}
        {%- endif %}
        {%- if instr.bundle_format %}
        {%- set _ = all_widths.append(instr.bundle_format.width) %}
        {%- endif %}
        {%- endfor %}
        {%- set unique_widths = [] %}
        {%- for width in all_widths %}
        {%- if width not in unique_widths %}
        {%- set _ = unique_widths.append(width) %}
        {%- endif %}
        {%- endfor %}
        {%- set unique_widths = unique_widths | sort %}
        
        {%- for width in unique_widths %}
        if matched_mnemonic is None:
            # Find minimum bits needed for this width category
            {%- set min_bits_list = [] %}
            {%- for instr in isa.instructions %}
            {%- if instr.format and instr.format.width == width %}
            {%- set min_bits = instr.format.get_minimum_bits_for_identification() %}
            {%- set _ = min_bits_list.append(min_bits) %}
            {%- endif %}
            {%- if instr.bundle_format and instr.bundle_format.width == width %}
            {%- if instr.format %}
            {%- set min_bits = instr.format.get_minimum_bits_for_identification() %}
            {%- else %}
            {%- set min_bits = 32 %}
            {%- endif %}
            {%- set _ = min_bits_list.append(min_bits) %}
            {%- endif %}
            {%- endfor %}
            {%- if min_bits_list %}
            min_bits = min([{{ min_bits_list | join(', ') }}])
            peeked_bits = self._load_bits(self.pc, min_bits)
            
            # Try to match instructions with this format width
            # Check instructions with more encoding fields first (more specific matches)
            {%- for instr in isa.instructions %}
            {%- if instr.format and instr.format.width == width %}
            if matched_mnemonic is None and self._matches_{{ instr.mnemonic }}(peeked_bits):
                matched_mnemonic = '{{ instr.mnemonic }}'
                matched_width = {{ width }}
            {%- endif %}
            {%- if instr.bundle_format and instr.bundle_format.width == width %}
            if matched_mnemonic is None and self._matches_{{ instr.mnemonic }}(peeked_bits):
                matched_mnemonic = '{{ instr.mnemonic }}'
                matched_width = {{ width }}
            {%- endif %}
            {%- endfor %}
            {%- endif %}
        {%- endfor %}
        
        if matched_mnemonic is None:
            self.halted = True
            return False
        
        # Step 2: Load full instruction based on matched width
        full_instruction = self._load_bits(self.pc, matched_width)
        
        # Step 3: Execute instruction
        executed = self._execute_instruction_by_mnemonic(full_instruction, matched_mnemonic)
        
        if not executed:
            print(f"Unknown instruction at PC=0x{self.pc:08x}: 0x{full_instruction:x}")
            self.halted = True
            return False
        
        # Step 4: Update PC by instruction width (in bytes)
        self.pc += (matched_width + 7) // 8
        self.instruction_count += 1
        return True
{% endblock %}

{% block execution_methods %}
    def _execute_instruction_by_mnemonic(self, instruction_word: int, mnemonic: str) -> bool:
        """Execute instruction by mnemonic name, checking aliases."""
        # Check instruction aliases first
        {%- for alias in isa.instruction_aliases %}
        if mnemonic == '{{ alias.alias_mnemonic }}':
            # Resolve to target instruction
            return self._execute_instruction_by_mnemonic(instruction_word, '{{ alias.target_mnemonic }}')
        {%- endfor %}
        # Check direct instructions
        {%- for instr in isa.instructions %}
        if mnemonic == '{{ instr.mnemonic }}':
            self._execute_{{ instr.mnemonic }}(instruction_word)
            return True
        {%- endfor %}
        return False

    def run(self, max_steps: int = 10000):
        """Run the simulator until halt or max_steps."""
        steps = 0
        while steps < max_steps and self.step():
            steps += 1

        if steps >= max_steps:
            print(f"Reached maximum step count ({max_steps})")
{% endblock %}

{% block utility_methods %}
    def _load_bits(self, address: int, num_bits: int) -> int:
        """
        Load specified number of bits from memory starting at address.
        Handles instructions that span multiple word boundaries.
        
        Args:
            address: Starting address (byte-aligned)
            num_bits: Number of bits to load
            
        Returns:
            Integer value of loaded bits (little-endian)
        """
        num_bytes = (num_bits + 7) // 8
        value = 0
        for i in range(num_bytes):
            byte_addr = address + i
            # Memory stores 32-bit words, need to extract bytes
            word_addr = (byte_addr // 4) * 4
            byte_offset = byte_addr % 4
            if word_addr in self.memory:
                word = self.memory[word_addr]
                byte_val = (word >> (byte_offset * 8)) & 0xFF
                value |= (byte_val << (i * 8))
        # Mask to requested number of bits
        if num_bits < 64:
            return value & ((1 << num_bits) - 1)
        else:
            return value

    def _get_instruction_width(self, instruction) -> int:
        """Get the full width of an instruction in bits."""
        if hasattr(instruction, 'bundle_format') and instruction.bundle_format:
            return instruction.bundle_format.width
        elif hasattr(instruction, 'format') and instruction.format:
            return instruction.format.width
        else:
            return 32  # Default
    
    def _resolve_register_alias(self, name: str, index: Optional[int] = None) -> tuple[str, Optional[int]]:
        """Resolve a register alias to the actual register name and index."""
        if name in self._register_aliases:
            target_name, target_index = self._register_aliases[name]
            # If alias has an index, use it; otherwise use provided index
            return (target_name, target_index if target_index is not None else index)
        return (name, index)
    
    def _read_virtual_register(self, vreg_name: str) -> int:
        """Read virtual register by concatenating component registers."""
        {%- for vreg in isa.virtual_registers %}
        if vreg_name == '{{ vreg.name }}':
            value = 0
            bit_offset = 0
            # Read components in order: first component is LSB, last is MSB
            {%- for comp in vreg.components %}
            {%- if comp.is_indexed() %}
            # Component: {{ comp.reg_name }}[{{ comp.index }}]
            reg_value = self.{{ comp.reg_name }}[{{ comp.index }}] & ((1 << {{ isa.get_register(comp.reg_name).width }}) - 1)
            {%- else %}
            # Component: {{ comp.reg_name }}
            reg_value = self.{{ comp.reg_name }} & ((1 << {{ isa.get_register(comp.reg_name).width }}) - 1)
            {%- endif %}
            value |= (reg_value << bit_offset)
            bit_offset += {{ isa.get_register(comp.reg_name).width }}
            {%- endfor %}
            return value & ((1 << {{ vreg.width }}) - 1)
        {%- endfor %}
        raise ValueError(f"Unknown virtual register: {vreg_name}")
    
    def _write_virtual_register(self, vreg_name: str, value: int):
        """Write virtual register by splitting to component registers."""
        {%- for vreg in isa.virtual_registers %}
        if vreg_name == '{{ vreg.name }}':
            bit_offset = 0
            # Write components in order: first component is LSB, last is MSB
            {%- for comp in vreg.components %}
            {%- set reg = isa.get_register(comp.reg_name) %}
            mask = (1 << {{ reg.width }}) - 1
            reg_value = (value >> bit_offset) & mask
            {%- if comp.is_indexed() %}
            # Component: {{ comp.reg_name }}[{{ comp.index }}]
            self.{{ comp.reg_name }}[{{ comp.index }}] = reg_value
            {%- else %}
            # Component: {{ comp.reg_name }}
            self.{{ comp.reg_name }} = reg_value
            {%- endif %}
            bit_offset += {{ reg.width }}
            {%- endfor %}
            return
        {%- endfor %}
        raise ValueError(f"Unknown virtual register: {vreg_name}")
    
    def _sign_extend(self, value: int, from_bits: int, to_bits: int = 32) -> int:
        """Sign extend a value from from_bits to to_bits. Returns a signed integer value."""
        # Validate parameters to prevent negative shift counts
        if from_bits <= 0 or to_bits <= 0:
            raise ValueError(f"sign_extend: from_bits and to_bits must be positive, got from_bits={from_bits}, to_bits={to_bits}")
        if from_bits > 64 or to_bits > 64:
            raise ValueError(f"sign_extend: from_bits and to_bits must be <= 64, got from_bits={from_bits}, to_bits={to_bits}")
        
        if from_bits >= to_bits:
            # Truncate to to_bits
            if to_bits > 0:
                result = value & ((1 << to_bits) - 1)
            else:
                result = 0
        else:
            # Extract the sign bit (from_bits - 1 must be >= 0 since from_bits > 0)
            sign_bit = (value >> (from_bits - 1)) & 1
            if sign_bit:
                # Negative: extend with 1s
                extend_bits = to_bits - from_bits
                if extend_bits > 0:
                    mask = ((1 << extend_bits) - 1) << from_bits
                    result = value | mask
                else:
                    result = value
            else:
                # Positive: extend with 0s (already done)
                if to_bits > 0:
                    result = value & ((1 << to_bits) - 1)
                else:
                    result = 0
        
        # Convert to signed integer for Python comparisons
        # If the result has the sign bit set (>= 0x80000000 for 32-bit), convert to negative
        if to_bits == 32:
            if result >= 0x80000000:
                result = result - 0x100000000
        elif to_bits < 32 and to_bits > 0:
            sign_mask = 1 << (to_bits - 1)
            if result & sign_mask:
                result = result - (1 << to_bits)
        
        return result
    
    def _zero_extend(self, value: int, from_bits: int, to_bits: int = 32) -> int:
        """Zero extend a value from from_bits to to_bits."""
        if from_bits >= to_bits:
            return value & ((1 << to_bits) - 1)
        # Just mask to the target width (zero extension)
        return value & ((1 << to_bits) - 1)

    def _execute_non_bundle_instruction(self, instruction_word: int) -> bool:
        """Execute a single instruction word, skipping bundle checks (used for bundle slots)."""
        # Only try non-bundle instructions
{%- for instr in isa.instructions %}
        {%- if not instr.is_bundle() %}
        {%- if instr.matches_encoding %}
        # Check {{ instr.mnemonic }}
        if self._matches_{{ instr.mnemonic }}(instruction_word):
            self._execute_{{ instr.mnemonic }}(instruction_word)
            return True
        {%- else %}
        # {{ instr.mnemonic }}
        if self._matches_{{ instr.mnemonic }}(instruction_word):
            self._execute_{{ instr.mnemonic }}(instruction_word)
            return True
        {%- endif %}
        {%- endif %}
{%- endfor %}
        return False

    def _execute_instruction(self, instruction_word: int) -> bool:
        """Execute a single instruction word."""
        # First, check if this might be a wide bundle by checking the first byte
        # If it matches a bundle_opcode, construct the full bundle_word from memory
        # Calculate max bundle width
        {%- set bundle_widths = [] %}
        {%- for instr in isa.instructions %}
        {%- if instr.is_bundle() and instr.bundle_format %}
        {%- set bundle_bytes = (instr.bundle_format.width // 8) %}
        {%- set _ = bundle_widths.append(bundle_bytes) %}
        {%- endif %}
        {%- endfor %}
        {%- if bundle_widths %}
        {%- set max_bundle_width = bundle_widths | max %}
        {%- else %}
        {%- set max_bundle_width = 4 %}
        {%- endif %}
        {%- if max_bundle_width > 4 %}
        # Check if first byte matches any bundle_opcode - if so, load full bundle
        first_byte = instruction_word & 0xFF
        if first_byte == 255:
            # This might be a bundle - construct full bundle_word from memory
            {%- set bundle_words_needed = (max_bundle_width + 3) // 4 %}
            bundle_word_wide = 0
            wide_bundle_available = True
            for i in range({{ bundle_words_needed }}):
                addr = self.pc + i * 4
                if addr not in self.memory:
                    wide_bundle_available = False
                    break
                word_val = self.memory[addr]
                bundle_word_wide |= (word_val << (i * 32))
            
            if wide_bundle_available:
                {%- for instr in isa.instructions %}
                {%- if instr.is_bundle() %}
                if self._matches_{{ instr.mnemonic }}(bundle_word_wide):
                    self._execute_{{ instr.mnemonic }}(bundle_word_wide)
                    return True
                {%- endif %}
                {%- endfor %}
{%- endif %}
        # Check if this is a bundle instruction (using the 32-bit word) - only for small bundles
{%- for instr in isa.instructions %}
        {%- if instr.is_bundle() %}
        {%- if not instr.bundle_format or (instr.bundle_format.width // 8) <= 4 %}
        if self._matches_{{ instr.mnemonic }}(instruction_word):
            self._execute_{{ instr.mnemonic }}(instruction_word)
            return True
        {%- endif %}
        {%- endif %}
{%- endfor %}
        
        # If not a bundle, try regular instructions
        return self._execute_non_bundle_instruction(instruction_word)
{% endblock %}

{% block instruction_matching %}
{%- for instr in isa.instructions %}
    def _matches_{{ instr.mnemonic }}(self, instruction_word: int) -> bool:
        """Check if instruction word matches {{ instr.mnemonic }} encoding."""
        {%- if instr.is_bundle() %}
        # Bundle instruction - check encoding using format (not bundle_format)
        {%- if instr.format and instr.encoding %}
        # Check format constant fields first
        {%- for field in instr.format.fields %}
        {%- if field.has_constant() %}
        # Check format constant field {{ field.name }} == {{ field.constant_value }}
        if (instruction_word >> {{ field.lsb }}) & {{ field.width() | mask }} != {{ field.constant_value }}:
            return False
        {%- endif %}
        {%- endfor %}
        {%- set id_fields = instr.format.get_identification_fields() %}
        {%- if id_fields %}
        # Use identification fields: {{ id_fields | map(attribute='name') | join(', ') }}
        {%- for id_field in id_fields %}
        {%- set encoding_assignment = None %}
        {%- for assignment in instr.encoding.assignments %}
        {%- if assignment.field == id_field.name %}
        {%- set encoding_assignment = assignment %}
        {%- endif %}
        {%- endfor %}
        {%- if encoding_assignment %}
        # Check identification field {{ id_field.name }} == {{ encoding_assignment.value }}
        if (instruction_word >> {{ id_field.lsb }}) & {{ id_field.width() | mask }} != {{ encoding_assignment.value }}:
            return False
        {%- endif %}
        {%- endfor %}
        return True
        {%- else %}
        # No identification fields specified - use all encoding fields (backward compatible)
        {%- for assignment in instr.encoding.assignments %}
        {%- set field = instr.format.get_field(assignment.field) %}
        {%- if field %}
        # Check {{ assignment.field }} == {{ assignment.value }}
        if (instruction_word >> {{ field.lsb }}) & {{ field.width() | mask }} != {{ assignment.value }}:
            return False
        {%- endif %}
        {%- endfor %}
        return True
        {%- endif %}
        {%- else %}
        return False
        {%- endif %}
        {%- elif instr.format and instr.encoding %}
        # Check format constant fields first
        {%- for field in instr.format.fields %}
        {%- if field.has_constant() %}
        # Check format constant field {{ field.name }} == {{ field.constant_value }}
        if (instruction_word >> {{ field.lsb }}) & {{ field.width() | mask }} != {{ field.constant_value }}:
            return False
        {%- endif %}
        {%- endfor %}
        # Always check ALL encoding fields to ensure exact match
        # (Identification fields are for quick filtering, but we need exact match)
        {%- for assignment in instr.encoding.assignments %}
        {%- set field = instr.format.get_field(assignment.field) %}
        {%- if field %}
        # Check {{ assignment.field }} == {{ assignment.value }}
        if (instruction_word >> {{ field.lsb }}) & {{ field.width() | mask }} != {{ assignment.value }}:
            return False
        {%- endif %}
        {%- endfor %}
        return True
        {%- elif instr.format %}
        # Instruction with format but no encoding - check format constants only
        {%- for field in instr.format.fields %}
        {%- if field.has_constant() %}
        # Check format constant field {{ field.name }} == {{ field.constant_value }}
        if (instruction_word >> {{ field.lsb }}) & {{ field.width() | mask }} != {{ field.constant_value }}:
            return False
        {%- endif %}
        {%- endfor %}
        return True
        {%- else %}
        return False
        {%- endif %}

{%- endfor %}
{% endblock %}

{% block instruction_execution %}
{%- for instr in isa.instructions %}
    def _execute_{{ instr.mnemonic }}(self, instruction_word: int):
        """Execute {{ instr.mnemonic }} instruction."""
        {%- if instr.is_bundle() %}
        # Bundle instruction - extract and execute sub-instructions
        {%- if instr.bundle_format %}
        # Extract sub-instructions from bundle
        # Instructions start at instruction_start bit position
        {%- for slot in instr.bundle_format.slots %}
        {{ slot.name }}_word = (instruction_word >> {{ slot.lsb }}) & {{ slot | slot_mask }}
        {%- endfor %}
        
        # Execute each sub-instruction in sequence
        # Note: We execute sub-instructions directly without checking for bundles again
        # to avoid recursion. Sub-instructions are regular instructions, not bundles.
        # Save PC before executing bundle slots (instructions in bundles shouldn't update PC)
        saved_pc = self.pc
        {%- for slot in instr.bundle_format.slots %}
        # Execute instruction in {{ slot.name }} slot using dynamic identification
        # The simulator will identify any instruction that fits in this slot width
        # Use _execute_non_bundle_instruction to avoid recursion
        self.pc = saved_pc
        self._execute_non_bundle_instruction({{ slot.name }}_word)
        saved_pc = self.pc
        {%- endfor %}
        # Restore PC to value before bundle execution (step() will update PC by bundle width)
        self.pc = saved_pc
        {%- else %}
        # No bundle format - step() will update PC
        {%- endif %}
        {%- elif instr.format %}
        # Decode operands
        {%- for op_spec in (instr.operand_specs if instr.operand_specs else []) %}
        {%- if op_spec.is_distributed() %}
        # Distributed operand: {{ op_spec.name }} from fields {{ op_spec.field_names }}
        {{ op_spec.name }} = 0
        {%- for field_idx, field_name in enumerate(op_spec.field_names) %}
        {%- set field = instr.format.get_field(field_name) %}
        {%- if field %}
        {%- if field_idx > 0 %}
        {%- set prev_widths = [] %}
        {%- for prev_idx in range(field_idx) %}
        {%- set prev_field = instr.format.get_field(op_spec.field_names[prev_idx]) %}
        {%- if prev_field %}
        {%- set _ = prev_widths.append(prev_field.width()) %}
        {%- endif %}
        {%- endfor %}
        {%- set current_bit = prev_widths | sum %}
        {%- else %}
        {%- set current_bit = 0 %}
        {%- endif %}
        {{ op_spec.name }} |= ((instruction_word >> {{ field.lsb }}) & {{ field.width() | mask }}) << {{ current_bit }}
        {%- endif %}
        {%- endfor %}
        {%- else %}
        # Simple operand: {{ op_spec.name }}
        {%- set field = instr.format.get_field(op_spec.name) %}
        {%- if field %}
        {{ op_spec.name }} = (instruction_word >> {{ field.lsb }}) & {{ field.width() | mask }}
        {%- endif %}
        {%- endif %}
        {%- endfor %}
        {%- if not instr.operand_specs %}
        # Legacy: use operands list
        {%- for operand in instr.operands %}
        {%- set field = instr.format.get_field(operand) %}
        {%- if field %}
        {{ operand }} = (instruction_word >> {{ field.lsb }}) & {{ field.width() | mask }}
        {%- endif %}
        {%- endfor %}
        {%- endif %}
        
        # Decode format fields that are not operands but may be used in behavior
        {%- set operand_names = [] %}
        {%- if instr.operand_specs %}
        {%- for op_spec in instr.operand_specs %}
        {%- if op_spec.is_distributed() %}
        {%- for field_name in op_spec.field_names %}
        {%- set _ = operand_names.append(field_name) %}
        {%- endfor %}
        {%- else %}
        {%- set _ = operand_names.append(op_spec.name) %}
        {%- endif %}
        {%- endfor %}
        {%- else %}
        {%- for operand in instr.operands %}
        {%- set _ = operand_names.append(operand) %}
        {%- endfor %}
        {%- endif %}
        {%- for field in instr.format.fields %}
        {%- if field.name not in operand_names %}
        # Decode format field {{ field.name }} (not an operand, but may be used in behavior)
        {{ field.name }} = (instruction_word >> {{ field.lsb }}) & {{ field.width() | mask }}
        {%- endif %}
        {%- endfor %}
        
        # Execute behavior
        {%- if instr.external_behavior %}
        # Externally defined behavior - call user implementation
        {%- set external_func_name = instr.mnemonic.lower() %}
        self.external_behavior.{{ external_func_name }}(
            {%- if instr.operand_specs %}
            {%- for op_spec in instr.operand_specs %}
            {{ op_spec.name }}{% if not loop.last %}, {% endif %}
            {%- endfor %}
            {%- elif instr.operands %}
            {%- for operand in instr.operands %}
            {{ operand }}{% if not loop.last %}, {% endif %}
            {%- endfor %}
            {%- endif %}
        )
        {%- elif instr.behavior %}
        # Temporary variables for this instruction
        {%- for stmt in instr.behavior.statements %}
{{ generate_rtl_code(stmt, instr) }}
        {%- endfor %}
        {%- endif %}
        
        # PC update is handled by step() method
        {%- else %}
        # No format defined - PC update is handled by step() method
        {%- endif %}

{%- endfor %}
{% endblock %}

{% block print_state %}
    # Print state method - part of Simulator class
    def print_state(self):
        """Print the current simulator state."""
        print("=== Simulator State ===")
        print(f"PC: 0x{self.pc:08x}")
        print(f"Instructions executed: {self.instruction_count}")
        print("\\nRegisters:")
{%- for reg in isa.registers %}
        {%- if reg.is_vector_register() %}
        for i in range({{ reg.count if reg.count else 1 }}):
            print(f"  {{ reg.name }}[{i}]: ", end="")
            for lane in range({{ reg.lanes }}):
                print(f"[{lane}]=0x{self.{{ reg.name }}[i][lane]:08x} ", end="")
            print()
        {%- elif reg.is_register_file() %}
        for i in range({{ reg.count }}):
            reg_val = int(self.{{ reg.name }}[i])
            print(f"  {{ reg.name }}[{i}]: 0x{reg_val:08x}", end="")
            {%- if reg.fields %}
            print(" (", end="")
            {%- for field in reg.fields %}
            print(f"{{ field.name }}=0x{self.{{ reg.name }}[i].{{ field.name }}:x}", end="")
            {%- if not loop.last %}, {% endif %}
            {%- endfor %}
            print(")", end="")
            {%- endif %}
            print()
        {%- else %}
        reg_val = int(self.{{ reg.name }})
        print(f"  {{ reg.name }}: 0x{reg_val:08x}", end="")
        {%- if reg.fields %}
        print(" (", end="")
        {%- for field in reg.fields %}
        print(f"{{ field.name }}=0x{self.{{ reg.name }}.{{ field.name }}:x}", end="")
        {%- if not loop.last %}, {% endif %}
        {%- endfor %}
        print(")", end="")
        {%- endif %}
        print()
        {%- endif %}
{%- endfor %}
        print()

{% endblock %}

{% block helper_class %}
class ExternalBehaviorHandler:
    """Handler class for externally defined instruction behaviors.
    
    This class provides stub methods for instructions with external_behavior=True.
    Users must subclass this and implement the methods to provide custom behavior
    for instructions that cannot be fully specified in RTL.
    """
    
    def __init__(self, simulator):
        """Initialize external behavior handler with reference to simulator."""
        self.simulator = simulator
    
{%- for instr in isa.instructions %}
{%- if instr.external_behavior %}
    def {{ instr.mnemonic.lower() }}(self{% if instr.operand_specs %}{% for op_spec in instr.operand_specs %}, {{ op_spec.name }}: int{% endfor %}{% elif instr.operands %}{% for operand in instr.operands %}, {{ operand }}: int{% endfor %}{% endif %}):
        """
        Externally defined behavior for {{ instr.mnemonic }} instruction.
        
        This method must be implemented by subclassing ExternalBehaviorHandler.
        {%- if instr.operand_specs %}
        Args:
        {%- for op_spec in instr.operand_specs %}
            {{ op_spec.name }}: Operand {{ op_spec.name }}
        {%- endfor %}
        {%- elif instr.operands %}
        Args:
        {%- for operand in instr.operands %}
            {{ operand }}: Operand {{ operand }}
        {%- endfor %}
        {%- endif %}
        """
        raise NotImplementedError(
            f"Externally defined behavior for {{ instr.mnemonic.lower() }} must be implemented. "
            "Subclass ExternalBehaviorHandler and override this method."
        )
    
{%- endif %}
{%- endfor %}

{% endblock %}

{% block main_function %}
def main():
    """Main entry point."""
    if len(sys.argv) < 2:
        print("Usage: simulator.py <binary_file> [start_address]")
        sys.exit(1)

    filename = sys.argv[1]
    start_address = int(sys.argv[2], 16) if len(sys.argv) > 2 else 0

    sim = Simulator()
    sim.load_binary_file(filename, start_address)
    
    print("Starting simulation...")
    sim.run()
    sim.print_state()
{% endblock %}

{% block if_main %}
if __name__ == "__main__":
    main()
{% endblock %}
