Source code for SAPsim.utils.instructions

"""SAP instruction implementation.

All function docstrings (and even most implementations) are ripped straight from the SAP Instruction Set,
with only slight modifications.

This DOES NOT exist in actual SAP but for the purposes of simulation and testing,
``add(arg)`` and ``sub(arg)`` have optional kwargs ``direct_add=`` and ``direct_sub=``
that will cause ``A = A + arg``, ``A = A - arg`` instead of ``A = A + Mem(arg)``, ``A = A - Mem(arg)``.

This DOES NOT exist in actual SAP but for implementation purposes, instructions that don't need an
arg (i.e. NOP, OUT, HLT) get a default parameter so that they can still be called with an argument.
In actual SAP, all instructions (byte) have a required Arg, not a default or optional arg.

``OPCODE_TO_INSTR_PROCEDURE`` dict that maps opcodes to procedures is defined at the bottom."""

__author__ = "Jesse Wei <jesse@cs.unc.edu>"

from tabulate import tabulate
import SAPsim.utils.global_vars as global_vars
import SAPsim.utils.exceptions as exceptions
import SAPsim.utils.helpers as helpers


[docs] def nop(arg: int = 0) -> None: """Nop Opcode 0""" global_vars.PC += 1
[docs] def lda(arg: int) -> None: """``A = Mem(arg)`` Opcode 1""" if arg not in global_vars.RAM: raise exceptions.LoadFromUnmappedAddress global_vars.A = global_vars.RAM[arg] if global_vars.A > (2**global_vars.NUM_BITS_IN_REGISTERS - 1): raise exceptions.ARegisterNotEnoughBits if global_vars.A < 0: raise exceptions.ARegisterNegativeInt global_vars.PC += 1
[docs] def add(arg: int, **kwargs) -> None: """``A = A + Mem(arg)``. Accounts for ``NUM_BITS_IN_REGISTERS`` to set ``FLAG_C`` and ``FLAG_Z``. Handles overflow. Opcode 2 Parameters ---------- arg: int memory address, usually kwarg ``direct_add``: bool Set to ``True`` to directly add ``arg`` (i.e. ``A = A + arg`` instead of ``A = A + Mem(arg)``), for testing purposes and use in ``sub``. This behavior does not exist in actual SAP.""" if "direct_add" in kwargs and kwargs["direct_add"]: global_vars.B = arg else: if arg not in global_vars.RAM: raise exceptions.LoadFromUnmappedAddress global_vars.B = global_vars.RAM[arg] if global_vars.B > (2**global_vars.NUM_BITS_IN_REGISTERS - 1): raise exceptions.BRegisterNotEnoughBits if global_vars.B < 0: raise exceptions.BRegisterNegativeInt global_vars.A += global_vars.B if global_vars.A > (2**global_vars.NUM_BITS_IN_REGISTERS - 1): global_vars.FLAG_C = 1 global_vars.A -= 2**global_vars.NUM_BITS_IN_REGISTERS else: global_vars.FLAG_C = 0 global_vars.FLAG_Z = global_vars.A == 0 global_vars.PC += 1
[docs] def sub(arg: int, **kwargs) -> None: """``A = A - Mem(arg)``. Accounts for ``NUM_BITS_IN_REGISTERS`` to set ``FLAG_C`` and ``FLAG_Z``. Calls ``add()`` twice to perform 2's complement subtraction. Opcode 3 Parameters ---------- arg: int memory address, usually kwarg direct_sub: bool set to ``True`` to directly sub ``arg`` (i.e. ``A = A - arg`` instead of ``A = A - Mem(arg)``), for testing purposes. This behavior does not exist in actual SAP.""" if "direct_sub" in kwargs and kwargs["direct_sub"]: global_vars.B = arg else: if arg not in global_vars.RAM: raise exceptions.LoadFromUnmappedAddress global_vars.B = global_vars.RAM[arg] if global_vars.B > (2**global_vars.NUM_BITS_IN_REGISTERS - 1): raise exceptions.BRegisterNotEnoughBits if global_vars.B < 0: raise exceptions.BRegisterNegativeInt # Clone A and B for use later in setting FlagC. A_clone = global_vars.A B_clone = global_vars.B inverse_B = B_clone ^ (2**global_vars.NUM_BITS_IN_REGISTERS - 1) add(inverse_B, direct_add=True) add(1, direct_add=True) # add() modified globs.B, reset it global_vars.B = B_clone # FLAG_Z is correct at this point. # FLAG_C is not correct so is explicitly handled. # This uses the unsigned comparison table FlagC = A >= B (compare their values before A changed) global_vars.FLAG_C = A_clone >= B_clone # Subtract 1 from PC since there were 2 adds that each did PC += 1 # Net effect is globs.PC += 1 global_vars.PC -= 1
[docs] def sta(arg: int) -> None: """``Mem(Arg) = A``. CAN store to unmapped addr, which will simply map the addr in RAM. Opcode 4""" global_vars.RAM[arg] = global_vars.A global_vars.PC += 1
[docs] def ldi(arg: int) -> None: """``A = arg`` Opcode 5""" global_vars.A = arg if arg > (2**global_vars.NUM_BITS_IN_REGISTERS - 1): raise exceptions.ARegisterNotEnoughBits if arg < 0: raise exceptions.ARegisterNegativeInt global_vars.PC += 1
[docs] def jmp(arg: int) -> None: """``PC = arg`` Opcode 6""" if arg < 0: raise exceptions.JumpToNegativeAddress global_vars.PC = arg
[docs] def jc(arg: int) -> None: """If ``FC=1`` then ``PC=arg``; else go on Opcode 7""" if global_vars.FLAG_C: if arg < 0: raise exceptions.JumpToNegativeAddress global_vars.PC = arg else: global_vars.PC += 1
[docs] def jz(arg: int) -> None: """If ``FZ=1`` then ``PC=arg``; else go on Opcode 8""" if global_vars.FLAG_Z: if arg < 0: raise exceptions.JumpToNegativeAddress global_vars.PC = arg else: global_vars.PC += 1
[docs] def out(arg: int = 0) -> None: """``Display = OUT = A``. Prints | PC | A (dec) | A (hex) | Opcode 14""" arg = helpers.parse_arg(global_vars.RAM[global_vars.PC]) table = [[global_vars.PC, global_vars.A, helpers.pad_hex(hex(global_vars.A), 2)]] print( tabulate(table, headers=["PC", "Dec", "Hex"], tablefmt=global_vars.table_format) ) global_vars.PC += 1
[docs] def hlt(arg: int = 0) -> None: """Halt Opcode 15""" global_vars.EXECUTING = False
OPCODE_TO_INSTR_PROCEDURE = { 0: nop, 1: lda, 2: add, 3: sub, 4: sta, 5: ldi, 6: jmp, 7: jc, 8: jz, 14: out, 15: hlt, } """This ``dict`` maps opcodes to the procedures defined in this file. The syntax ``OPCODE_TO_INSTR_PROCEDURE[opcode](arg)`` will execute the correct instruction with ``arg`` passed as argument! Very cool."""