Source code for tiny8.cpu

"""A simplified AVR-like 8-bit CPU simulator.

This module provides a lightweight CPU model inspired by the ATmega family.
The :class:`CPU` class is the primary export and implements a small, extensible instruction-dispatch model.
The implementation favors readability over step-accurate emulation. Add
instruction handlers by defining methods named ``op_<mnemonic>`` on ``CPU``.
"""

from typing import TYPE_CHECKING, Optional

if TYPE_CHECKING:
    from .assembler import AsmResult

from .memory import Memory
from .utils import ProgressBar

# SREG flag bit positions and short descriptions.
SREG_I = 7  # Global Interrupt Enable
SREG_T = 6  # Bit copy storage (temporary)
SREG_H = 5  # Half Carry
SREG_S = 4  # Sign (N ^ V)
SREG_V = 3  # Two's complement overflow
SREG_N = 2  # Negative
SREG_Z = 1  # Zero
SREG_C = 0  # Carry


[docs] class CPU: """In-memory 8-bit AVR-like CPU model. The CPU implements a compact instruction-dispatch model. Handlers are methods named ``op_<mnemonic>`` and are invoked by :meth:`step` for the currently-loaded program. Attributes: regs (list[int]): 32 8-bit general purpose registers (R0..R31). pc (int): Program counter (index into ``program``). sp (int): Stack pointer (index into RAM in the associated :class:`tiny8.memory.Memory`). sreg (int): Status register bits stored in a single integer (I, T, H, S, V, N, Z, C). step_count (int): Instruction execution counter. reg_trace (list[tuple[int, int, int]]): Per-step register change trace entries of the form ``(step, reg, new_value)``. mem_trace (list[tuple[int, int, int]]): Per-step memory change trace entries of the form ``(step, addr, new_value)``. step_trace (list[dict]): Full per-step snapshots useful for visualization and debugging. Note: This implementation simplifies many AVR specifics (flag semantics, exact step counts, IO mapping) in favor of clarity. Extend or replace individual ``op_`` handlers to increase fidelity. """
[docs] def __init__(self, memory: Optional[Memory] = None): self.mem = memory or Memory() self.regs: list[int] = [0] * 32 self.pc: int = 0 self.sp: int = self.mem.ram_size - 1 self.sreg: int = 0 self.step_count: int = 0 self.interrupts: dict[int, bool] = {} self.reg_trace: list[tuple[int, int, int]] = [] self.mem_trace: list[tuple[int, int, int]] = [] self.step_trace: list[dict] = [] self.program: list[tuple[str, tuple]] = [] self.labels: dict[str, int] = {} self.pc_to_line: dict[int, int] = {} self.source_lines: list[str] = [] self.running = False
[docs] def set_flag(self, bit: int, value: bool) -> None: """Set or clear a specific SREG flag bit. Args: bit: Integer bit index (0..7) representing the flag position. value: True to set the bit, False to clear it. """ if value: self.sreg |= 1 << bit else: self.sreg &= ~(1 << bit)
[docs] def get_flag(self, bit: int) -> bool: """Return the boolean value of a specific SREG flag bit. Args: bit: Integer bit index (0..7). Returns: True if the bit is set, False otherwise. """ return bool((self.sreg >> bit) & 1)
# --- AVR-like flag helpers (Z, C, N, V, S, H) --- def _set_flags_add(self, a: int, b: int, carry_in: int, result: int) -> None: """Set flags for ADD/ADC (AVR semantics). Args: a: First operand (0..255). b: Second operand (0..255). carry_in: Carry input (0 or 1). result: Integer sum result. """ r = result & 0xFF c = (result >> 8) & 1 h = (((a & 0x0F) + (b & 0x0F) + carry_in) >> 4) & 1 n = (r >> 7) & 1 v = 1 if (((~(a ^ b) & 0xFF) & (a ^ r) & 0x80) != 0) else 0 s = n ^ v z = 1 if r == 0 else 0 self.set_flag(SREG_C, bool(c)) self.set_flag(SREG_H, bool(h)) self.set_flag(SREG_N, bool(n)) self.set_flag(SREG_V, bool(v)) self.set_flag(SREG_S, bool(s)) self.set_flag(SREG_Z, bool(z)) def _set_flags_sub(self, a: int, b: int, borrow_in: int, result: int) -> None: """Set flags for SUB/CP/CPI (AVR semantics). Args: a: Minuend (0..255). b: Subtrahend (0..255). borrow_in: Borrow input (0 or 1). result: Signed difference (a - b - borrow_in). """ r = result & 0xFF c = 1 if (a - b - borrow_in) < 0 else 0 h = 1 if ((a & 0x0F) - (b & 0x0F) - borrow_in) < 0 else 0 n = (r >> 7) & 1 v = 1 if ((((a ^ b) & (a ^ r)) & 0x80) != 0) else 0 s = n ^ v z = 1 if r == 0 else 0 self.set_flag(SREG_C, bool(c)) self.set_flag(SREG_H, bool(h)) self.set_flag(SREG_N, bool(n)) self.set_flag(SREG_V, bool(v)) self.set_flag(SREG_S, bool(s)) self.set_flag(SREG_Z, bool(z)) def _set_flags_logical(self, result: int) -> None: """Set flags for logical operations (AND, OR, EOR) per AVR semantics. Args: result: Operation result. Note: Logical ops clear C and V, set N and Z, S = N ^ V, and clear H. """ r = result & 0xFF n = (r >> 7) & 1 z = 1 if r == 0 else 0 s = n # v is 0, so s = n ^ 0 = n self.set_flag(SREG_C, False) self.set_flag(SREG_V, False) self.set_flag(SREG_N, bool(n)) self.set_flag(SREG_S, bool(s)) self.set_flag(SREG_Z, bool(z)) self.set_flag(SREG_H, False) def _set_flags_inc(self, old: int, new: int) -> None: """Set flags for INC (affects V, N, Z, S). Does not affect C or H. Args: old: Value before increment. new: Value after increment. """ n = (new >> 7) & 1 v = 1 if old == 0x7F else 0 z = 1 if new == 0 else 0 s = n ^ v self.set_flag(SREG_N, bool(n)) self.set_flag(SREG_V, bool(v)) self.set_flag(SREG_S, bool(s)) self.set_flag(SREG_Z, bool(z)) def _set_flags_add16(self, a: int, b: int, carry_in: int, result: int) -> None: """Set flags for 16-bit add (ADIW semantics approximation). Args: a: First operand (0..0xFFFF). b: Second operand (0..0xFFFF). carry_in: Carry input (0 or 1). result: Full integer sum. """ r = result & 0xFFFF c = (result >> 16) & 1 n = (r >> 15) & 1 v = 1 if (((~(a ^ b) & 0xFFFF) & (a ^ r) & 0x8000) != 0) else 0 s = n ^ v z = 1 if r == 0 else 0 self.set_flag(SREG_C, bool(c)) self.set_flag(SREG_N, bool(n)) self.set_flag(SREG_V, bool(v)) self.set_flag(SREG_S, bool(s)) self.set_flag(SREG_Z, bool(z)) self.set_flag(SREG_H, False) def _set_flags_sub16(self, a: int, b: int, borrow_in: int, result: int) -> None: """Set flags for 16-bit subtraction (SBIW semantics approximation). Args: a: Minuend (0..0xFFFF). b: Subtrahend (0..0xFFFF). borrow_in: Borrow input (0 or 1). result: Integer difference a - b - borrow_in. """ r = result & 0xFFFF c = 1 if (a - b - borrow_in) < 0 else 0 n = (r >> 15) & 1 v = 1 if ((((a ^ b) & (a ^ r)) & 0x8000) != 0) else 0 s = n ^ v z = 1 if r == 0 else 0 self.set_flag(SREG_C, bool(c)) self.set_flag(SREG_N, bool(n)) self.set_flag(SREG_V, bool(v)) self.set_flag(SREG_S, bool(s)) self.set_flag(SREG_Z, bool(z)) self.set_flag(SREG_H, False) def _set_flags_dec(self, old: int, new: int) -> None: """Set flags for DEC (affects V, N, Z, S). Does not affect C or H. Args: old: Value before decrement. new: Value after decrement. """ n = (new >> 7) & 1 v = 1 if old == 0x80 else 0 z = 1 if new == 0 else 0 s = n ^ v self.set_flag(SREG_N, bool(n)) self.set_flag(SREG_V, bool(v)) self.set_flag(SREG_S, bool(s)) self.set_flag(SREG_Z, bool(z)) # Register access
[docs] def read_reg(self, r: int) -> int: """Return the 8-bit value from register ``r``. Args: r: Register index (0..31). Returns: The 8-bit value (0..255) stored in the register. """ return self.regs[r] & 0xFF
[docs] def write_reg(self, r: int, val: int) -> None: """Write an 8-bit value to register ``r`` and record the change. Args: r: Register index (0..31). val: Value to write; will be truncated to 8 bits. Note: A trace entry is appended only when the register value actually changes to avoid noisy traces. """ newv = val & 0xFF if self.regs[r] != newv: self.regs[r] = newv self.reg_trace.append((self.step_count, r, newv))
# Memory access wrappers
[docs] def read_ram(self, addr: int) -> int: """Read a byte from RAM at the given address. Args: addr: RAM address to read. Returns: Byte value stored at ``addr`` (0..255). """ return self.mem.read_ram(addr)
[docs] def write_ram(self, addr: int, val: int) -> None: """Write an 8-bit value to RAM at ``addr`` and record the trace. Args: addr: RAM address to write. val: Value to write; will be truncated to 8 bits. Note: The underlying :class:`Memory` object stores the value; a ``(step, addr, val)`` tuple is appended to ``mem_trace`` for visualizers/tests. """ self.mem.write_ram(addr, val, self.step_count) self.mem_trace.append((self.step_count, addr, val & 0xFF))
# Program loading
[docs] def load_program( self, program: "list[tuple[str, tuple]] | AsmResult", labels: Optional[dict[str, int]] = None, pc_to_line: Optional[dict[int, int]] = None, source_lines: Optional[list[str]] = None, ): """Load an assembled program into the CPU. Args: program: Either a list of ``(mnemonic, operands)`` tuples or an AsmResult object. If AsmResult, other params are ignored. labels: Mapping of label strings to instruction indices (ignored if program is AsmResult). pc_to_line: Optional mapping from PC to source line number for tracing (ignored if program is AsmResult). source_lines: Optional original assembly source lines for display (ignored if program is AsmResult). Note: After loading the program, the program counter is reset to zero. """ # Check if program is an AsmResult if hasattr(program, "program") and hasattr(program, "labels"): # It's an AsmResult asm = program self.program = asm.program self.labels = asm.labels self.pc_to_line = asm.pc_to_line self.source_lines = asm.source_lines else: # Legacy tuple-based format self.program = program self.labels = labels or {} self.pc_to_line = pc_to_line or {} self.source_lines = source_lines or [] self.pc = 0
# Instruction execution
[docs] def step(self) -> bool: """Execute a single instruction at the current program counter. Performs one fetch-decode-execute step. A pre-step snapshot of registers and non-zero RAM is recorded, the instruction handler (``op_<mnemonic>``) is invoked, and a post-step trace entry is appended to ``step_trace``. Returns: True if an instruction was executed; False if the PC is out of range and execution should stop. """ if self.pc < 0 or self.pc >= len(self.program): self.running = False return False pre_exec_pc = self.pc instr, operands = self.program[self.pc] # Build textual form of the instruction for tracing (uppercase mnemonic # and register names like R0..R31). Operands decoded for display only. def fmt_op(o): if isinstance(o, tuple) and len(o) == 2 and o[0] == "reg": return f"R{o[1]}" return str(o) try: ops_text = ", ".join(fmt_op(o) for o in operands) except Exception: ops_text = "" instr_text = f"{instr.upper()} {ops_text}".strip() # record pre-step snapshot regs_snapshot = list(self.regs) # memory snapshot: capture all non-zero RAM addresses (helps visualization of higher addresses) mem_snapshot = {} for i in range(0, self.mem.ram_size): v = self.read_ram(i) if v != 0: mem_snapshot[i] = v # Convert register operand markers back to raw ints for handlers, and # call the appropriate handler (handlers are named op_<mnemonic> and # expect plain ints/strings as originally implemented). handler = getattr(self, f"op_{instr.lower()}", None) if handler is None: raise NotImplementedError(f"Instruction {instr} not implemented") # decode operands for handler call decoded_ops = [] for o in operands: if isinstance(o, tuple) and len(o) == 2 and o[0] == "reg": decoded_ops.append(int(o[1])) else: decoded_ops.append(o) handler(*tuple(decoded_ops)) # record step trace after execution (post-state) self.step_count += 1 source_line = self.pc_to_line.get(pre_exec_pc, -1) self.step_trace.append( { "step": self.step_count, "pc": pre_exec_pc, "instr": instr_text, "regs": regs_snapshot, "mem": mem_snapshot, "sreg": self.sreg, "sp": self.sp, "source_line": source_line, } ) self.pc += 1 return True
[docs] def run(self, max_steps: int = 100000, show_progress: bool = True) -> None: """Run instructions until program end or ``max_steps`` is reached. Args: max_steps: Maximum number of instruction steps to execute (default 100000). show_progress: If True, display a progress bar during execution (default True). Note: This repeatedly calls :meth:`step` until it returns False or the maximum step count is reached. """ self.running = True steps = 0 if show_progress: pb = ProgressBar(total=max_steps, desc="CPU execution") try: while self.running and steps < max_steps: ok = self.step() if not ok: break steps += 1 if show_progress: pb.update(1) finally: if show_progress: pb.close()
[docs] def op_nop(self): """No-operation: does nothing for one step.""" pass
[docs] def op_ldi(self, reg_idx: int, imm: int): """Load an immediate value into a register. Args: reg_idx: Destination register index. imm: Immediate value to load. Note: The AVR LDI instruction is normally restricted to R16..R31; this simplified implementation accepts any register index. """ self.write_reg(reg_idx, imm)
[docs] def op_mov(self, rd: int, rr: int): """Copy the value from register ``rr`` into ``rd``. Args: rd: Destination register index. rr: Source register index. """ self.write_reg(rd, self.read_reg(rr))
[docs] def op_add(self, rd: int, rr: int): """Add register ``rr`` to ``rd`` (Rd := Rd + Rr) and update flags. Args: rd: Destination register index. rr: Source register index. Note: Sets C, H, N, V, S, Z per AVR semantics. """ a = self.read_reg(rd) b = self.read_reg(rr) res = a + b self.write_reg(rd, res & 0xFF) self._set_flags_add(a, b, 0, res)
[docs] def op_and(self, rd: int, rr: int): """Logical AND (Rd := Rd & Rr) — updates N, Z, V=0, C=0, H=0, S. Args: rd: Destination register index. rr: Source register index. """ res = self.read_reg(rd) & self.read_reg(rr) self.write_reg(rd, res) self._set_flags_logical(res)
[docs] def op_or(self, rd: int, rr: int): """Logical OR (Rd := Rd | Rr) — updates N, Z, V=0, C=0, H=0, S. Args: rd: Destination register index. rr: Source register index. """ res = self.read_reg(rd) | self.read_reg(rr) self.write_reg(rd, res) self._set_flags_logical(res)
[docs] def op_eor(self, rd: int, rr: int): """Exclusive OR (Rd := Rd ^ Rr) — updates N, Z, V=0, C=0, H=0, S. Args: rd: Destination register index. rr: Source register index. """ res = self.read_reg(rd) ^ self.read_reg(rr) self.write_reg(rd, res) self._set_flags_logical(res)
[docs] def op_sub(self, rd: int, rr: int): """Subtract (Rd := Rd - Rr) and set flags C,H,N,V,S,Z. Args: rd: Destination register index. rr: Source register index. """ a = self.read_reg(rd) b = self.read_reg(rr) res_full = a - b self.write_reg(rd, res_full & 0xFF) self._set_flags_sub(a, b, 0, res_full)
[docs] def op_inc(self, rd: int): """Increment (Rd := Rd + 1) — updates V,N,S,Z; does not change C/H. Args: rd: Destination register index. """ old = self.read_reg(rd) new = (old + 1) & 0xFF self.write_reg(rd, new) self._set_flags_inc(old, new)
[docs] def op_dec(self, rd: int): """Decrement (Rd := Rd - 1) — updates V,N,S,Z; does not change C/H. Args: rd: Destination register index. """ old = self.read_reg(rd) new = (old - 1) & 0xFF self.write_reg(rd, new) self._set_flags_dec(old, new)
[docs] def op_mul(self, rd: int, rr: int): """Multiply 8x8 -> 16: store low in Rd, high in Rd+1. Args: rd: Destination register index for low byte. rr: Source register index. Note: Updates Z and C flags. Z set if product == 0; C set if high != 0. """ a = self.read_reg(rd) b = self.read_reg(rr) prod = a * b low = prod & 0xFF high = (prod >> 8) & 0xFF self.write_reg(rd, low) if rd + 1 < 32: self.write_reg(rd + 1, high) self.set_flag(SREG_Z, prod == 0) self.set_flag(SREG_C, high != 0) self.set_flag(SREG_H, False)
[docs] def op_adc(self, rd: int, rr: int): """Add with carry (Rd := Rd + Rr + C) and update flags. Args: rd: Destination register index. rr: Source register index. """ a = self.read_reg(rd) b = self.read_reg(rr) carry_in = 1 if self.get_flag(SREG_C) else 0 res = a + b + carry_in self.write_reg(rd, res & 0xFF) self._set_flags_add(a, b, carry_in, res)
[docs] def op_clr(self, rd: int): """Clear register (Rd := 0). Behaves like EOR Rd,Rd for flags. Args: rd: Destination register index. """ self.write_reg(rd, 0) self.set_flag(SREG_N, False) self.set_flag(SREG_V, False) self.set_flag(SREG_S, False) self.set_flag(SREG_Z, True) self.set_flag(SREG_C, False) self.set_flag(SREG_H, False)
[docs] def op_ser(self, rd: int): """Set register all ones (Rd := 0xFF). Update flags conservatively. Args: rd: Destination register index. """ self.write_reg(rd, 0xFF) self.set_flag(SREG_N, True) self.set_flag(SREG_V, False) self.set_flag(SREG_S, True) self.set_flag(SREG_Z, False) self.set_flag(SREG_C, False) self.set_flag(SREG_H, False)
[docs] def op_div(self, rd: int, rr: int): """Unsigned divide convenience instruction: quotient -> Rd, remainder -> Rd+1. Args: rd: Destination register index for quotient. rr: Divisor register index. Note: If divisor is zero, sets C and Z flags to indicate error. """ a = self.read_reg(rd) b = self.read_reg(rr) if b == 0: self.write_reg(rd, 0) self.set_flag(SREG_C, True) self.set_flag(SREG_Z, True) return q = a // b r = a % b self.write_reg(rd, q) if rd + 1 < 32: self.write_reg(rd + 1, r) self.set_flag(SREG_Z, q == 0) self.set_flag(SREG_C, False) self.set_flag(SREG_H, False) self.set_flag(SREG_V, False)
[docs] def op_in(self, rd: int, port: int): """Read from I/O port into register. Args: rd: Destination register index. port: Port address to read from. """ val = self.read_ram(port) self.write_reg(rd, val)
[docs] def op_out(self, port: int, rr: int): """Write register value to I/O port. Args: port: Port address to write to. rr: Source register index. """ val = self.read_reg(rr) self.write_ram(port, val)
[docs] def op_jmp(self, label: str | int): """Jump to a given label or numeric address by updating the program counter. Args: label: The jump target. If a string, it is treated as a symbolic label and looked up in self.labels. If an int, it is used directly as the numeric address. Note: Sets PC to target - 1 because the instruction dispatcher will increment PC after the current instruction completes. """ if isinstance(label, str): if label not in self.labels: raise KeyError(f"Label {label} not found") self.pc = self.labels[label] - 1 else: self.pc = int(label) - 1
[docs] def op_cpi(self, rd: int, imm: int): """Compare register with immediate (sets flags but doesn't modify register). Args: rd: Register index to compare. imm: Immediate value to compare against. """ a = self.read_reg(rd) b = imm & 0xFF res = a - b self._set_flags_sub(a, b, 0, res)
[docs] def op_cp(self, rd: int, rr: int): """Compare two registers (sets flags but doesn't modify registers). Args: rd: First register index. rr: Second register index. """ a = self.read_reg(rd) b = self.read_reg(rr) res = a - b self._set_flags_sub(a, b, 0, res)
[docs] def op_lsl(self, rd: int): """Logical shift left (Rd := Rd << 1). Args: rd: Destination register index. """ v = self.read_reg(rd) carry = (v >> 7) & 1 nv = (v << 1) & 0xFF self.write_reg(rd, nv) self.set_flag(SREG_C, bool(carry)) n = (nv >> 7) & 1 vflag = n ^ carry self.set_flag(SREG_N, bool(n)) self.set_flag(SREG_V, bool(vflag)) self.set_flag(SREG_S, bool(n ^ vflag)) self.set_flag(SREG_Z, nv == 0) self.set_flag(SREG_H, False)
[docs] def op_lsr(self, rd: int): """Logical shift right (Rd := Rd >> 1). Args: rd: Destination register index. """ v = self.read_reg(rd) carry = v & 1 nv = (v >> 1) & 0xFF self.write_reg(rd, nv) self.set_flag(SREG_C, bool(carry)) self.set_flag(SREG_N, False) self.set_flag(SREG_V, bool(carry)) self.set_flag(SREG_S, bool(carry)) self.set_flag(SREG_Z, nv == 0) self.set_flag(SREG_H, False)
[docs] def op_rol(self, rd: int): """Rotate left through carry. Args: rd: Destination register index. """ v = self.read_reg(rd) carry_in = 1 if self.get_flag(SREG_C) else 0 carry_out = (v >> 7) & 1 nv = ((v << 1) & 0xFF) | carry_in self.write_reg(rd, nv) self.set_flag(SREG_C, bool(carry_out)) self.set_flag(SREG_N, bool((nv >> 7) & 1)) self.set_flag(SREG_Z, nv == 0) self.set_flag(SREG_V, False) self.set_flag(SREG_S, bool((nv >> 7) & 1)) self.set_flag(SREG_H, False)
[docs] def op_ror(self, rd: int): """Rotate right through carry. Args: rd: Destination register index. """ v = self.read_reg(rd) carry_in = 1 if self.get_flag(SREG_C) else 0 carry_out = v & 1 nv = (v >> 1) | (carry_in << 7) self.write_reg(rd, nv) self.set_flag(SREG_C, bool(carry_out)) self.set_flag(SREG_N, bool((nv >> 7) & 1)) self.set_flag(SREG_Z, nv == 0) self.set_flag(SREG_V, False) self.set_flag(SREG_S, bool((nv >> 7) & 1)) self.set_flag(SREG_H, False)
[docs] def op_com(self, rd: int): """One's complement: Rd := ~Rd. Updates N,V,S,Z,C per AVR-ish semantics. Args: rd: Destination register index. """ v = self.read_reg(rd) nv = (~v) & 0xFF self.write_reg(rd, nv) n = (nv >> 7) & 1 self.set_flag(SREG_N, bool(n)) self.set_flag(SREG_V, False) self.set_flag(SREG_S, bool(n)) self.set_flag(SREG_Z, nv == 0) self.set_flag(SREG_C, True) self.set_flag(SREG_H, False)
[docs] def op_neg(self, rd: int): """Two's complement (negate): Rd := 0 - Rd. Flags as subtraction from 0. Args: rd: Destination register index. """ a = self.read_reg(rd) res_full = 0 - a self.write_reg(rd, res_full & 0xFF) self._set_flags_sub(0, a, 0, res_full)
[docs] def op_swap(self, rd: int): """Swap nibbles in register: Rd[7:4] <-> Rd[3:0]. Does not affect SREG. Args: rd: Destination register index. """ v = self.read_reg(rd) nv = ((v & 0x0F) << 4) | ((v >> 4) & 0x0F) self.write_reg(rd, nv)
[docs] def op_tst(self, rd: int): """Test: perform AND Rd,Rd and update flags but do not store result. Args: rd: Register index to test. """ v = self.read_reg(rd) res = v & v self._set_flags_logical(res)
[docs] def op_andi(self, rd: int, imm: int): """Logical AND with immediate (Rd := Rd & K). Args: rd: Destination register index. imm: Immediate value. """ res = self.read_reg(rd) & (imm & 0xFF) self.write_reg(rd, res) self._set_flags_logical(res)
[docs] def op_ori(self, rd: int, imm: int): """Logical OR with immediate (Rd := Rd | K). Args: rd: Destination register index. imm: Immediate value. """ res = self.read_reg(rd) | (imm & 0xFF) self.write_reg(rd, res) self._set_flags_logical(res)
[docs] def op_eori(self, rd: int, imm: int): """Logical EOR with immediate (Rd := Rd ^ K). Args: rd: Destination register index. imm: Immediate value. """ res = self.read_reg(rd) ^ (imm & 0xFF) self.write_reg(rd, res) self._set_flags_logical(res)
[docs] def op_subi(self, rd: int, imm: int): """Subtract immediate (Rd := Rd - K). Args: rd: Destination register index. imm: Immediate value. """ a = self.read_reg(rd) b = imm & 0xFF res_full = a - b self.write_reg(rd, res_full & 0xFF) self._set_flags_sub(a, b, 0, res_full)
[docs] def op_sbc(self, rd: int, rr: int): """Subtract with carry (Rd := Rd - Rr - C). Args: rd: Destination register index. rr: Source register index. """ a = self.read_reg(rd) b = self.read_reg(rr) borrow_in = 1 if self.get_flag(SREG_C) else 0 res_full = a - b - borrow_in self.write_reg(rd, res_full & 0xFF) self._set_flags_sub(a, b, borrow_in, res_full)
[docs] def op_sbci(self, rd: int, imm: int): """Subtract immediate with carry: Rd := Rd - K - C. Args: rd: Destination register index. imm: Immediate value. """ a = self.read_reg(rd) b = imm & 0xFF borrow_in = 1 if self.get_flag(SREG_C) else 0 res_full = a - b - borrow_in self.write_reg(rd, res_full & 0xFF) self._set_flags_sub(a, b, borrow_in, res_full)
[docs] def op_sei(self): """Set Global Interrupt Enable (I bit).""" self.set_flag(SREG_I, True)
[docs] def op_cli(self): """Clear Global Interrupt Enable (I bit).""" self.set_flag(SREG_I, False)
[docs] def op_cpse(self, rd: int, rr: int): """Compare and Skip if Equal: compare Rd,Rr; if equal, skip next instruction. Args: rd: First register index. rr: Second register index. """ a = self.read_reg(rd) b = self.read_reg(rr) self._set_flags_sub(a, b, 0, a - b) if a == b: self.pc += 1
[docs] def op_sbrs(self, rd: int, bit: int): """Skip next if bit in register is set. Args: rd: Register index. bit: Bit position to test. """ v = self.read_reg(rd) if ((v >> (bit & 7)) & 1) == 1: self.pc += 1
[docs] def op_sbrc(self, rd: int, bit: int): """Skip next if bit in register is clear. Args: rd: Register index. bit: Bit position to test. """ v = self.read_reg(rd) if ((v >> (bit & 7)) & 1) == 0: self.pc += 1
[docs] def op_sbis(self, io_addr: int, bit: int): """Skip if bit in IO/RAM-mapped address is set. Args: io_addr: I/O or RAM address. bit: Bit position to test. """ v = self.read_ram(io_addr) if ((v >> (bit & 7)) & 1) == 1: self.pc += 1
[docs] def op_sbic(self, io_addr: int, bit: int): """Skip if bit in IO/RAM-mapped address is clear. Args: io_addr: I/O or RAM address. bit: Bit position to test. """ v = self.read_ram(io_addr) if ((v >> (bit & 7)) & 1) == 0: self.pc += 1
[docs] def op_sbiw(self, rd_word_low: int, imm_word: int): """Subtract immediate from word register pair (Rd:Rd+1) — simplified. Args: rd_word_low: Low register of the pair (even register index). imm_word: 16-bit immediate to subtract. """ lo = self.read_reg(rd_word_low) hi = self.read_reg(rd_word_low + 1) if (rd_word_low + 1) < 32 else 0 word = (hi << 8) | lo new = (word - (imm_word & 0xFFFF)) & 0xFFFF new_lo = new & 0xFF new_hi = (new >> 8) & 0xFF self.write_reg(rd_word_low, new_lo) if rd_word_low + 1 < 32: self.write_reg(rd_word_low + 1, new_hi) self._set_flags_sub16(word, imm_word & 0xFFFF, 0, word - (imm_word & 0xFFFF))
[docs] def op_adiw(self, rd_word_low: int, imm_word: int): """Add immediate to word register pair (Rd:Rd+1) - simplified. Args: rd_word_low: Low register of the pair (even register index). imm_word: 16-bit immediate to add. """ lo = self.read_reg(rd_word_low) hi = self.read_reg(rd_word_low + 1) if (rd_word_low + 1) < 32 else 0 word = (hi << 8) | lo new = (word + (imm_word & 0xFFFF)) & 0xFFFF new_lo = new & 0xFF new_hi = (new >> 8) & 0xFF self.write_reg(rd_word_low, new_lo) if rd_word_low + 1 < 32: self.write_reg(rd_word_low + 1, new_hi) self._set_flags_add16(word, imm_word & 0xFFFF, 0, word + (imm_word & 0xFFFF))
[docs] def op_rjmp(self, label: str): """Relative jump — label may be an int or string label. Args: label: Jump target (label name or relative offset). """ if isinstance(label, int): self.pc = self.pc + int(label) else: self.op_jmp(label)
[docs] def op_rcall(self, label: str): """Relative call — push return address and jump relatively or to label. Args: label: Call target (label name or relative offset). """ ret = self.pc + 1 self.write_ram(self.sp, (ret >> 8) & 0xFF) self.sp -= 1 self.write_ram(self.sp, ret & 0xFF) self.sp -= 1 if isinstance(label, int): target = self.pc + int(label) self.pc = int(target) - 1 else: self.op_jmp(label)
[docs] def op_sbi(self, io_addr: int, bit: int): """Set a bit in an I/O/memory-mapped address (use RAM area). Args: io_addr: I/O or RAM address to modify. bit: Bit index to set (0..7). """ # set bit in I/O location (use RAM address space) val = self.read_ram(io_addr) nval = val | (1 << (bit & 7)) self.write_ram(io_addr, nval)
[docs] def op_cbi(self, io_addr: int, bit: int): """Clear a bit in an I/O/memory-mapped address (use RAM area). Args: io_addr: I/O or RAM address to modify. bit: Bit index to clear (0..7). """ val = self.read_ram(io_addr) nval = val & ~(1 << (bit & 7)) self.write_ram(io_addr, nval)
[docs] def op_ld(self, rd: int, addr_reg: int): """Load from RAM at address contained in register ``addr_reg`` into ``rd``. Args: rd: Destination register index. addr_reg: Register index containing the RAM address to load from. """ addr = self.read_reg(addr_reg) val = self.read_ram(addr) self.write_reg(rd, val)
[docs] def op_st(self, addr_reg: int, rr: int): """Store register ``rr`` into RAM at address contained in register ``addr_reg``. Args: addr_reg: Register index containing the RAM address to write to. rr: Source register index to store. """ addr = self.read_reg(addr_reg) val = self.read_reg(rr) self.write_ram(addr, val)
[docs] def op_brne(self, label: str): """Branch to label if Zero flag is not set (BRNE). Args: label: Destination label to jump to if Z flag is not set. """ z = self.get_flag(SREG_Z) if not z: self.op_jmp(label)
[docs] def op_breq(self, label: str): """Branch to label if Zero flag is set (BREQ). Args: label: Destination label to jump to if Z flag is set. """ z = self.get_flag(SREG_Z) if z: self.op_jmp(label)
[docs] def op_brcs(self, label: str): """Branch to a label if the carry flag is set. Args: label (str): Destination label to jump to if the carry flag is set. """ c = self.get_flag(SREG_C) if c: self.op_jmp(label)
[docs] def op_brcc(self, label: str): """Branch to a label if the carry flag is clear. Args: label (str): Destination label to jump to if the carry flag is clear. """ c = self.get_flag(SREG_C) if not c: self.op_jmp(label)
[docs] def op_brge(self, label: str | int): """Branch if Greater or Equal (Signed). Args: label: Destination label or address to jump to if the condition is met. """ s = self.get_flag(SREG_S) if not s: self.op_jmp(label)
[docs] def op_brlt(self, label: str | int): """Branch if Less Than (Signed). Args: label: Destination label or address to jump to if the condition is met. """ s = self.get_flag(SREG_S) if s: self.op_jmp(label)
[docs] def op_brmi(self, label: str | int): """Branch if Minus (Negative flag set). Args: label: Destination label or address to jump to if the condition is met. """ n = self.get_flag(SREG_N) if n: self.op_jmp(label)
[docs] def op_brpl(self, label: str | int): """Branch if Plus (Negative flag clear). Args: label: Destination label or address to jump to if the condition is met. """ n = self.get_flag(SREG_N) if not n: self.op_jmp(label)
[docs] def op_push(self, rr: int): """Push a register value onto the stack. Args: rr: Source register index to push. Note: The value of register ``rr`` is written to RAM at the current stack pointer, and the stack pointer is then decremented. """ val = self.read_reg(rr) self.write_ram(self.sp, val) self.sp -= 1
[docs] def op_pop(self, rd: int): """Pop a value from the stack into a register. Args: rd: Destination register index to receive the popped value. Note: The stack pointer is incremented, the byte at the new stack pointer is read from RAM, and the value is written into register ``rd``. """ self.sp += 1 val = self.read_ram(self.sp) self.write_reg(rd, val)
[docs] def op_call(self, label: str): """Call a subroutine by pushing the return address and jumping to label. Args: label: Label to call. Note: The return address (pc+1) is pushed as two bytes (high then low) onto the stack, decrementing the stack pointer after each write. """ ret = self.pc + 1 self.write_ram(self.sp, (ret >> 8) & 0xFF) self.sp -= 1 self.write_ram(self.sp, ret & 0xFF) self.sp -= 1 self.op_jmp(label)
[docs] def op_ret(self): """Return from subroutine by popping the return address and setting PC. Note: Two bytes are popped from the stack (low then high) to reconstruct the return address, which is then loaded into the program counter. """ self.sp += 1 low = self.read_ram(self.sp) self.sp += 1 high = self.read_ram(self.sp) ret = (high << 8) | low self.pc = ret - 1
[docs] def op_reti(self): """Return from interrupt: pop return address and set I flag.""" self.sp += 1 low = self.read_ram(self.sp) self.sp += 1 high = self.read_ram(self.sp) ret = (high << 8) | low self.set_flag(SREG_I, True) self.pc = ret - 1
[docs] def trigger_interrupt(self, vector_addr: int): """Trigger an interrupt vector if it is enabled. Args: vector_addr: Interrupt vector address to jump to. Note: If the interrupt vector is enabled in ``self.interrupts``, the current PC+1 is pushed onto the stack (high then low byte) and control jumps to ``vector_addr``. """ if not self.interrupts.get(vector_addr, False): return ret = self.pc + 1 self.write_ram(self.sp, (ret >> 8) & 0xFF) self.sp -= 1 self.write_ram(self.sp, ret & 0xFF) self.sp -= 1 self.pc = vector_addr - 1