;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 'TMSR MIPS' PROTOTYPE. NOT FOR BATTLEFIELD USE!                          ;;
;; The ONLY currently-supported device is the console UART.                 ;;
;;                                                                          ;;
;; Dependencies/Libraries required : NONE!!!                                ;;
;; Where Runs: Any AMD64 linux.                                             ;;
;;                                                                          ;;
;; To build:                                                                ;;
;; (1) yasm -f elf64 -g null tmsrmips.asm                                   ;;
;; (2) ld tmsrmips.o -o tmsrmips                                            ;;
;; (3) strip tmsrmips                                                       ;;
;; At the time of writing, yields a <12kB ELF.                              ;;
;;                                                                          ;;
;; To run:                                                                  ;;
;; ./tmsrmips memoryimage.bin                                               ;;
;; NOTE: presently there is no way to exit the program, you must kill it.   ;;
;;                                                                          ;;
;; Tested with 'cmips' author's ad-hoc kernel:                              ;;
;; SHA256: 3100ee44ce203f17a299d6c8907366b261e28e62794f1e170f61711f90b6c2bc ;;
;;                                                                          ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;                                                                          ;;
;; (C) 2019 Stanislav Datskovskiy ( www.loper-os.org )                      ;;
;; http://wot.deedbot.org/17215D118B7239507FAFED98B98228A001ABFFC7.html     ;;
;;                                                                          ;;
;; You do not have, nor can you ever acquire the right to use, copy or      ;;
;; distribute this software ; Should you use this software for any purpose, ;;
;; or copy and distribute it to anyone or in any manner, you are breaking   ;;
;; the laws of whatever soi-disant jurisdiction, and you promise to         ;;
;; continue doing so for the indefinite future. In any case, please         ;;
;; always : read and understand any software ; verify any PGP signatures    ;;
;; that you use - for any purpose.                                          ;;
;;                                                                          ;;
;; See also http://trilema.com/2015/a-new-software-licensing-paradigm .     ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;-----------------------------------------------------------------------------
; Endianism. LITTLE_ENDIAN -- sane; otherwise cmipsian retarded pseudo-big
;-----------------------------------------------------------------------------
;; %define LITTLE_ENDIAN 1
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Alignment Grain
;-----------------------------------------------------------------------------
%define GRAIN 32
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Value of Program Counter (PC) on Warmup
; TODO: Official MIPS doc gives 0xBFC00000 as reset vector.
;       Why did 'cmips' tardlinux builder use 0x801E4B10 ?
;-----------------------------------------------------------------------------
%define INIT_PC 0x801E4B10
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Memory-Mapped Periphs
;-----------------------------------------------------------------------------
;; Console UART:
%define UARTBASE  0x140003F8
%define UARTSIZE  20
;-----------------------------------------------------------------------------
; %define POWERBASE 0x1FBF0004
; %define POWERSIZE 4
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Tick Limit
;-----------------------------------------------------------------------------
%define LIMIT_TICKS   0
; %define TICK_LIMIT    0x1a783677
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Eggog on Undone insts?
;-----------------------------------------------------------------------------
; %define STOP_UNDONE   1
; %macro  UNDONE_INST   0
; %if STOP_UNDONE
;         Write   "Ticks=", STDERR
;         mov     eax, [Ticks]
;         call    _Dump_EAX

;         Write   " PC=", STDERR
;         mov     eax, PC
;         call    _Dump_EAX
;         Write   " : ", STDERR
;         EGGOG "Undone Instruction!"
; %endif
; %endmacro
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Toggle All Debug Prints
%define DBG        0

; Toggle Instruction Debug Prints
%define DBG_INSTRS 0

;-----------------------------------------------------------------------------
%macro PUSHA 0
        push rax
        push rbx
        push rcx
        push rdx
        push rbp
        push rdi
        push rsi
        push r8
        push r9
        push r10
        push r11
        push r12
        push r13
        push r14
        push r15
%endmacro
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
%macro POPA 0
        pop  r15
        pop  r14
        pop  r13
        pop  r12
        pop  r11
        pop  r10
        pop  r9
        pop  r8
        pop  rsi
        pop  rdi
        pop  rbp
        pop  rdx
        pop  rcx
        pop  rbx
        pop  rax
%endmacro
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
%macro Write 2+
        [section .rodata]
%%str:  db %1
%%endstr:
        __SECT__
        mov     rdx, %%endstr - %%str
        mov     rax, SYS_WRITE
        mov     rdi, %2
        mov     rsi, %%str
        syscall
%endmacro
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
%macro WriteLn 2+
        [section .rodata]
%%str:  db %1, 10
%%endstr:
        __SECT__
        mov     rdx, %%endstr - %%str
        mov     rax, SYS_WRITE
        mov     rdi, %2
        mov     rsi, %%str
        syscall
%endmacro
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
%macro EGGOG 1+
        WriteLn %1, STDERR
        jmp     _exit
%endmacro;
;-----------------------------------------------------------------------------
%macro DEBUGLN 1+
%if DBG
        PUSHA
        WriteLn %1, STDOUT
        POPA
%endif
%endmacro
;-----------------------------------------------------------------------------
%macro DEBUG   1+
%if DBG
        PUSHA
        Write  %1, STDOUT
        POPA
%endif
%endmacro
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; x64 sys calls
%define SYS_BRK          12
%define SYS_EXIT         60
%define SYS_READ          0
%define SYS_WRITE         1
%define SYS_OPEN          2
%define SYS_CLOSE         3
%define SYS_FSTAT         5
%define SYS_MMAP          9
%define SYS_MUNMAP       11
; for threadism:
%define SYS_CLONE        56
; for tty cure:
%define SYS_IOCTL        16

; for sys_mmap
%define PROT_READ         1
%define PROT_WRITE	  2
%define MAP_PRIVATE       2

; output fd's
%define STDOUT            1
%define STDERR            2
;-----------------------------------------------------------------------------
; for threadism:
;; sched.h
%define CLONE_VM	0x00000100
%define CLONE_FS	0x00000200
%define CLONE_FILES	0x00000400
%define CLONE_SIGHAND	0x00000800
%define CLONE_PARENT	0x00008000
%define CLONE_THREAD	0x00010000
%define CLONE_IO	0x80000000

;; sys/mman.h
%define MAP_GROWSDOWN	0x0100
%define MAP_ANONYMOUS	0x0020
%define MAP_PRIVATE	0x0002
%define PROT_READ	0x1
%define PROT_WRITE	0x2
%define PROT_EXEC	0x4

%define THREAD_FLAGS \
        CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_PARENT | \
        CLONE_THREAD | CLONE_IO

%define SLAVE_STACK_SIZE  (4 * 1024) ;; 4kB
;-----------------------------------------------------------------------------
; pill for linux's tty buffering retardation
%define ICANON            2
%define ECHO              8
%define TCGETS        21505 ; to read back the termios config
%define TCPUTS        21508 ; to set the termios config

;; tty state
struc   TTY_Config
        .stty           resb    12
        .slflag         resb     4
        .srest          resb    44
        .tty            resb    12
        .lflag          resb     4
        .brest          resb    44
endstruc
;----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; for output of sys_fstat
struc   statbuf
        .st_dev         resq    1
        .st_ino         resq    1
        .st_nlink       resq    1
        .st_mode        resd    1
        .st_uid         resd    1
        .st_gid         resd    1
        .__pad0         resd    1
        .st_rdev        resq    1
        .st_size        resq    1 ; file size in bytes
        .st_blksize     resq    1
        .st_blocks      resq    1
        .st_atime       resq    1
        .st_atime_nsec  resq    1
        .st_mtime       resq    1
        .st_mtime_nsec  resq    1
        .st_ctime       resq    1
        .st_ctime_nsec  resq    1
        .__unused       resq    3
        .size
endstruc
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; UARTism
;-----------------------------------------------------------------------------
;; UART State
struc   UART_State
        .LCR            resb    1 ; Line Control
        .LSR            resb    1 ; Line Status
        .MSR            resb    1 ; Modem Status
        .IIR            resb    1 ; Interrupt ID
        .IER            resb    1 ; Interrupt Enable
        .DLL            resb    1 ;
        .DLH            resb    1 ;
        .FCR            resb    1 ; FIFO Control
        .MCR            resb    1 ; Modem Control
        .SCR            resb    1 ; Modem Control
        ;; FIFO:
        .FIFO           resb   32 ; The FIFO itself
        .FIFO_First     resb    1 ; index of 1st byte in the FIFO
        .FIFO_Last      resb    1 ; index of last byte in the FIFO
        .FIFO_Count     resb    1 ; number of bytes in the FIFO
endstruc
;-----------------------------------------------------------------------------

%define UART_LSR_DATA_READY          0x1
%define UART_LSR_FIFO_EMPTY         0x20
%define UART_LSR_TRANSMITTER_EMPTY  0x40

; Enable Transmitter holding register int.
%define UART_IER_THRI               0x02

; Enable receiver data interrupt
%define UART_IER_RDI                0x01

; Modem status interrupt (Low priority)
%define UART_IIR_MSI                0x00

%define UART_IIR_NO_INT             0x01

; Transmitter holding register empty
%define UART_IIR_THRI               0x02

; Receiver data interrupt
%define UART_IIR_RDI                0x04

; Receiver line status interrupt (High p.)
%define UART_IIR_RLSI               0x06

; Character timeout
%define UART_IIR_CTI                0x0c

; Divisor latch access bit
%define UART_LCR_DLAB               0x80

; R/W: Divisor Latch Low, DLAB=1
%define UART_DLL                       0

; R/W: Divisor Latch High, DLAB=1
%define UART_DLH                       1

; R/W: Interrupt Enable Register
%define UART_IER                       1

; R: Interrupt ID Register
%define UART_IIR                       2

; W: FIFO Control Register
%define UART_FCR                       2

; R/W: Line Control Register
%define UART_LCR                       3

; W: Modem Control Register
%define UART_MCR                       4

; R: Line Status Register
%define UART_LSR                       5

; R: Modem Status Register
%define UART_MSR                       6

; R/W:
%define UART_SCR                       7
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; TLB Entry.
; We don't give a shit about C0 and C1, and so we can put all of it in 32bits:
;-----------------------------------------------------------------------------
; 11111111111111110000000000000000
; FEDCBA9876543210FEDCBA9876543210
; --------------------------------
; GVVDDAAAAAAAAVVVVVVVVVVVVVVVVVVV
; |1010| ASID ||      VPN2       |

%define TLB_VPN2_Mask    0x7FFFF ; 19 bits
%define TLB_ASID_Mask       0xFF ; 8 bits
%define TLB_ASID_Shift        19 ; sits after VPN2 Mask
%define TLB_D0                27 ; 27th bit
%define TLB_D1                28 ; 28th bit
%define TLB_V0                29 ; 29th bit
%define TLB_V1                30 ; 30th bit
%define TLB_G                 31 ; 31st bit (last)

; # of entries.
%define TLB_ENTRIES_COUNT     16 ; in principle can have more
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
;; MIPS Processor State
struc   MCPU
        .Regs           resd    32 ; Register Set

        ;; TODO: can LO/HI live in XMM regs??
        .LO             resd    1 ; Multiplication/division results - Low Half
        .HI             resd    1 ; Multiplication/division results - High Half
        .CP0_Index      resd    1
        .CP0_EntryHi    resd    1
        .CP0_EntryLo0   resd    1
        .CP0_EntryLo1   resd    1
        .CP0_Context    resd    1
        .CP0_Wired      resd    1
        ; .CP0_Status     resd    1
        .CP0_Epc        resd    1 ; Exception program counter return address
        .CP0_BadVAddr   resd    1
        .CP0_ErrorEpc   resd    1
        ; .CP0_Cause      resd    1
        ; .CP0_Count      resd    1
        ; .CP0_Compare    resd    1
        .CP0_PageMask   resd    1
        
        ;; The TLB:
        .TLB_Entries    resd    TLB_ENTRIES_COUNT ; TLB entries (without PFN)
        .TLB_PFN_Even   resd    TLB_ENTRIES_COUNT ; TLB PFN0
        .TLB_PFN_Odd    resd    TLB_ENTRIES_COUNT ; TLB PFN0
endstruc
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Refer to the N-th TLB Entry:
;-----------------------------------------------------------------------------
%define TLB_E(N)        dword [M_Base_32 + MCPU.TLB_Entries  + 4 * (N)]
; N-th PFN :
%define TLB_PFN_E(N)    dword [M_Base_32 + MCPU.TLB_PFN_Even + 4 * (N)]
%define TLB_PFN_O(N)    dword [M_Base_32 + MCPU.TLB_PFN_Odd  + 4 * (N)]
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
section .bss
        arg0            resb 8 ; first cmdline arg
        fd              resb 8 ; FD of file containing mmap'd space
        tty             resb TTY_Config_size ; Linux tty state
        M               resb 8 ; Addr mmap'd M (message) buffer
        Mbytes          resb 8 ; Length of M (bytes)
        IOBUF           resb 1 ; I/O buffer for print
        Ticks           resb 4 ; Tick Counter for Debug Tick Cap
        UART0           resb    UART_State_size ;; UART-0:
        SlaveIRQ        resb 4 ; External Interrupt from Slaves
        R_Ticks         resd 1
align GRAIN ; MIPS CPU State
        MIPS_State      resb MCPU_size
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Helper macros for MIPS instruction operands.
; All presume that current MIPS instruction is found in r_I.
; Note that variable shifts on x86 require slide amount to reside in CL.
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; These constants are fixed for the MIPS32 architecture and will NEVER change:
;-----------------------------------------------------------------------------
; Masks for Instruction Fields:
%define rD_Mask       0xF800       ; Mask for rD Field
%define rS_Mask       0x3E00000    ; Mask for rS Field
%define rT_Mask       0x1F0000     ; Mask for rT Field
%define Imm_Mask      0xFFFF       ; Mask for Imm Field
%define Shamt_Mask    0x7C0        ; Mask for Shamt Field
%define J_Mask        0x3FFFFFF    ; Mask for J Field
%define Q_Mask        0x7          ; Mask for Q Field (MFC0/MTC0 instructions)
;-----------------------------------------------------------------------------
; Shifts for Instruction Fields (Imm doesn't need one) :
%define rD_Shr        11           ; Right Shift for rD Field
%define rS_Shr        21           ; Right Shift for rS Field
%define rT_Shr        16           ; Right Shift for rT Field
%define Shamt_Shr      6           ; Right Shift for Shamt Field
%define J_Shl          2           ; Left  Shift for J Field
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Register Placements
;-----------------------------------------------------------------------------

; To point to MIPS state:
%define M_Base_64     rbp          ; Pointer to base of MIPS data structure
%define M_Base_32     ebp          ; 32-bit component of above

;-----------------------------------------------------------------------------
; Scratch Registers used inside Opcodes:
;-----------------------------------------------------------------------------
%define rD            eax          ; Where to extract rD Field
%define Imm           rD           ; Where IType's Imm goes (they have no rD)
%define JTarg         rD           ; Where JType's J field goes

; x86 is braindamaged and DEMANDS variable shift slides in CL. So rS is ECX.
%define rS            ecx          ; Where to extract rS Field
%define rS_LowByte    cl           ; Low byte of rS, for some 'flag' ops
%define r_Shamt       rS           ; must be also this (see above)
%define r_Q           rS           ; Q field in the coprocessor instructions

%define rT            ebx          ; Where to extract rT Field

; Scratch Register for intermediates:
%define TMP           edx          ; Scratch register used in all ops
%define TMP_LowByte   dl           ; Low byte of TMP, for some 'flag' ops

; After we decode all fields, we no longer need the original instruction :
%define r_I           TMP          ; Copy of currently-executing instruction
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Global 'fast' MIPS State:
; TODO: is it possible to use the upper halves of the 64bit regs for anything?
;       ... or entirely worthless from intel's idiocy of 'auto-zero on mov' ?
;-----------------------------------------------------------------------------
%define Flag_Reg      edi          ; Delay, Exception, etc flags
%define RAM_Floor     rsi          ; Physical (x86) address of 1st RAM word
%define RAM_Ceiling   r8           ; Physical (x86) address of last RAM word
%define PC            r9d          ; Current Program Counter
%define nPC           r10d         ; 'Next' Program Counter
%define CP0_Status    r11d         ; CP0_Status
%define CP0_Cause     r12d         ; CP0_Cause
%define CP0_Count     r13d         ; CP0_Count
%define CP0_Compare   r14d         ; CP0_Compare
%define AUX           r15d         ; Additional TMP for certain ops

; TODO: init and load/save routine from/to RAM for all of the above.
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Access to MIPS Registers that live in MCPU (Emulator State) :
;-----------------------------------------------------------------------------
; Refer to given MIPS special Reg:
;-----------------------------------------------------------------------------
%define Sr(N)         dword [M_Base_32 + MCPU. %+ N]

;;;;;; if we were to address via ds... much bulkier bin
;;;; %define Sr(N)         dword [MIPS_State + MCPU. %+ N]
;-----------------------------------------------------------------------------
; Refer to Nth Reg:
;-----------------------------------------------------------------------------
%define R(N)          Sr(Regs + 4 * (N))
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Instruction Field Manipulations. Arranged so as to satisfy the x86 pipeline.
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Extract rD, rS, rT fields from the currently-executing MIPS instruction
; For instructions encoded as ???? ??ss ssst tttt dddd d??? ???? ????
;-----------------------------------------------------------------------------
%macro RType    0 ; no params
        mov     rD, rD_Mask       ; Mask for rD Field
        mov     rS, rS_Mask       ; Mask for rS Field
        mov     rT, rT_Mask       ; Mask for rT Field
        and     rD, r_I           ; Mask orig. inst. to get rD Field
        and     rS, r_I           ; Mask orig. inst. to get rS Field
        and     rT, r_I           ; Mask orig. inst. to get rT Field
        shr     rD, rD_Shr        ; rD is now rD Index
        shr     rS, rS_Shr        ; rS is now rS Index
        shr     rT, rT_Shr        ; rT is now rT Index
%endmacro
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Extract only rS field from the currently-executing MIPS instruction
; For instructions encoded as ???? ??ss sss? ???? ???? ???? ???? ????
;-----------------------------------------------------------------------------
%macro RType_S_Only   0 ; no params
        mov     rS, rS_Mask       ; Mask for rS Field
        and     rS, r_I           ; Mask orig. inst. to get rS Field
        shr     rS, rS_Shr        ; rS is now rS Index
%endmacro
;-----------------------------------------------------------------------------

; Works same
%define IType_S_Only RType_S_Only

;-----------------------------------------------------------------------------
; Extract only rS and rT fields from the currently-executing MIPS instruction
; For instructions encoded as ???? ??ss ssst tttt ???? ???? ???? ????
;-----------------------------------------------------------------------------
%macro RType_S_T_Only    0 ; no params
        mov     rS, rS_Mask       ; Mask for rS Field
        mov     rT, rT_Mask       ; Mask for rT Field
        and     rS, r_I           ; Mask orig. inst. to get rS Field
        and     rT, r_I           ; Mask orig. inst. to get rT Field
        shr     rS, rS_Shr        ; rS is now rS Index
        shr     rT, rT_Shr        ; rT is now rT Index
%endmacro
;-----------------------------------------------------------------------------

; Works same
%define IType_S_T_Only RType_S_T_Only

;-----------------------------------------------------------------------------
; Extract only rS and rD fields from the currently-executing MIPS instruction
; For instructions encoded as ???? ??ss sss? ???? dddd d??? ???? ????
;-----------------------------------------------------------------------------
%macro RType_S_D_Only    0 ; no params
        mov     rD, rD_Mask       ; Mask for rD Field
        mov     rS, rS_Mask       ; Mask for rS Field
        and     rD, r_I           ; Mask orig. inst. to get rD Field
        and     rS, r_I           ; Mask orig. inst. to get rS Field
        shr     rD, rD_Shr        ; rD is now rD Index
        shr     rS, rS_Shr        ; rS is now rS Index
%endmacro
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Extract only rD field from the currently-executing MIPS instruction
; For instructions encoded as ???? ???? ???? ???? dddd d??? ???? ????
;-----------------------------------------------------------------------------
%macro RType_D_Only   0 ; no params
        mov     rD, rD_Mask       ; Mask for rD Field
        and     rD, r_I           ; Mask orig. inst. to get rD Field
        shr     rD, rD_Shr        ; rD is now rD Index
%endmacro
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Extract rD, rT, Shamt fields from the currently-executing MIPS instruction
; For instructions encoded as ???? ???? ???t tttt dddd dhhh hh?? ????
;-----------------------------------------------------------------------------
%macro RType_D_T_Shamt 0 ; no params
        mov     rD,      rD_Mask    ; Mask for rD Field
        mov     r_Shamt, Shamt_Mask ; Mask for Shamt Field
        mov     rT,      rT_Mask    ; Mask for rT Field
        and     rD,      r_I        ; Mask orig. inst. to get rD Field
        and     r_Shamt, r_I        ; Mask orig. inst. to get Shamt Field
        and     rT,      r_I        ; Mask orig. inst. to get rT Field
        shr     rD,      rD_Shr     ; rD is now rD Index
        shr     r_Shamt, Shamt_Shr  ; r_Shamt is now Shamt value
        shr     rT,      rT_Shr     ; rT is now rT Index
%endmacro
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Extract rD, rT, Q fields from the currently-executing MIPS instruction
; For MFC/MTC instructions encoded as 0100 0000 ?00t tttt dddd d000 0000 0qqq
;-----------------------------------------------------------------------------
%macro CPType 0 ; no params
        mov     rD,      rD_Mask    ; Mask for rD Field
        mov     r_Q,     Q_Mask     ; Mask for Q Field
        mov     rT,      rT_Mask    ; Mask for rT Field
        and     rD,      r_I        ; Mask orig. inst. to get rD Field
        and     r_Q,     r_I        ; Mask orig. inst. to get Q Field
        and     rT,      r_I        ; Mask orig. inst. to get rT Field
        shr     rD,      rD_Shr     ; rD is now rD Index
        shr     rT,      rT_Shr     ; rT is now rT Index
%endmacro
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Extract Imm, rS, rT fields from the currently-executing MIPS instruction
;-----------------------------------------------------------------------------
%macro IType    0 ; no params
        mov     Imm,  Imm_Mask      ; Mask for Imm Field; needs no shift
        mov     rS,   rS_Mask       ; Mask for rS Field
        mov     rT,   rT_Mask       ; Mask for rT Field
        and     Imm,  r_I           ; Mask orig. inst. to get Imm Field
        and     rS,   r_I           ; Mask orig. inst. to get rS Field
        and     rT,   r_I           ; Mask orig. inst. to get rT Field
        shr     rS,   rS_Shr        ; rS is now rS Index
        shr     rT,   rT_Shr        ; rT is now rT Index
%endmacro
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Extract only Imm, rS fields from the currently-executing MIPS instruction
;-----------------------------------------------------------------------------
%macro IType_I_S_Only 0 ; no params
        mov     Imm,  Imm_Mask      ; Mask for Imm Field; needs no shift
        mov     rS,   rS_Mask       ; Mask for rS Field
        and     Imm,  r_I           ; Mask orig. inst. to get Imm Field
        and     rS,   r_I           ; Mask orig. inst. to get rS Field
        shr     rS,   rS_Shr        ; rS is now rS Index
%endmacro
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Extract only Imm field from the currently-executing MIPS instruction
;-----------------------------------------------------------------------------
%macro IType_I_Only 0 ; no params
        mov     Imm,  Imm_Mask      ; Mask for Imm Field; needs no shift
        and     Imm,  r_I           ; Mask orig. inst. to get Imm Field
%endmacro
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Extract Imm, rT fields from the currently-executing MIPS instruction
;-----------------------------------------------------------------------------
%macro IType_I_T_Only 0 ; no params
        mov     Imm,  Imm_Mask      ; Mask for Imm Field; needs no shift
        mov     rT,   rT_Mask       ; Mask for rT Field
        and     Imm,  r_I           ; Mask orig. inst. to get Imm Field
        and     rT,   r_I           ; Mask orig. inst. to get rT Field
        shr     rT,   rT_Shr        ; rT is now rT Index
%endmacro
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Extract J field from the currently-executing MIPS instruction
; Encoding:  ???? 11ii iiii iiii iiii iiii iiii iiii
;-----------------------------------------------------------------------------
%macro JType 0 ; no params
        mov     JTarg,  J_Mask      ; Mask for J Field; needs no shift
        and     JTarg,  r_I         ; Mask orig. inst. to get J Field
        shl     JTarg,  J_Shl       ; Shift the J Field to get Target Address
%endmacro
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Sign-Extend((16bit Address) * 4) (where 18th bit is considered 'sign')
;-----------------------------------------------------------------------------
%macro SX18_4N  1+         ; param is A, the address to extend
        shl     %1,      16         ; upper 16 bits are now equal to A
        sar     %1,      14         ; Shift back to A*4, extending sign bit.
%endmacro
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Sign-Extend(16bit Address) (where 16th bit is considered 'sign')
;-----------------------------------------------------------------------------
%macro SX16     1+         ; param is A, the address to extend
        shl     %1,      16         ; upper 16 bits are now equal to A
        sar     %1,      16         ; Shift back to A, extending sign bit.
%endmacro
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Update given MIPS Reg # with new value, but always avoid overwriting R(0)
;-----------------------------------------------------------------------------
; TODO: measure if this is actually faster than simply R(31) := 0 every time
%macro Wr_Reg   2+         ; params: %1: Reg # %2: PC reg with new value
        xor     AUX,   AUX          ; Clear AUX
        test    %1,    %1           ; Set Z if destination reg # is R(0)
        cmovz   %2,    AUX          ; If Z: Replace written value with zero
        mov     R(%1), %2           ; Regs[rD] := new value
        ;;;; alt variant:
        ; mov     R(%1), %2           ; Regs[rD] := new value
        ; mov     R(0),  0
        ;;;; TODO: which is actually faster ?
%endmacro
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Emulator Flags (kept in Flag_Reg)
;-----------------------------------------------------------------------------
%define InDelaySlot        0
%define RunningDelaySlot   1
%define Waiting            2
%define LL_Bit             3
%define UART_Input_Ready   4
%define IsWriting          5
%define ExcWasTLBNoMatch   6
%define Shutdown           7
;-----------------------------------------------------------------------------
; Set a given Flag:
%macro Flg_On  1+
        bts     Flag_Reg, %1        ; Set the given Flag.
%endmacro
;-----------------------------------------------------------------------------
; Clear a given Flag:
%macro Flg_Off 1+
        btr     Flag_Reg, %1        ; Clear the given Flag.
%endmacro

%define Flg_GOF Flg_Off             ; BTR inst also gives the old value
;-----------------------------------------------------------------------------
; Get a given Flag (into CF) :
%macro Flg_Get 1+
        bt      Flag_Reg, %1        ; Read the given Flag.
%endmacro
;-----------------------------------------------------------------------------
; Clear all Flags:
%macro Flg_Clear_All 0
        xor     Flag_Reg, Flag_Reg  ; Clear all Flags.
%endmacro
;-----------------------------------------------------------------------------
; Copy one Flag to another Flag. Uses EAX for scratch.
%macro Flg_Cpy  2+ ; %1 : destination Flag; %2 : source Flag.
        Flg_Off %1                  ; Clear the Destination Flag
        xor     eax, eax            ; eax := 0
        Flg_Get %2                  ; CF  := Source Flag
        setc    al                  ; AL  := CF (value of Source Flag)
        shl     eax, %1             ; Slide this bit up to destination pos.
        or      Flag_Reg, eax       ; Write the destination Flag
%endmacro
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Exceptions:
;-----------------------------------------------------------------------------
%define EXC_Int      0 ; Interrupt
%define EXC_Mod      1 ; TLB modification exception
%define EXC_TLBL     2 ; TLB exception (load or instruction fetch)
%define EXC_TLBS     3 ; TLB exception (store)
%define EXC_AdEL     4 ; Address error exception (load or instruction fetch)
%define EXC_AdES     5 ; Address error exception (store)
%define EXC_IBE      6 ; Bus error exception (instruction fetch)
%define EXC_DBE      7 ; Bus error exception (data reference: load or store)
%define EXC_SYS      8 ; Syscall exception
%define EXC_BP       9 ; Breakpoint exception
%define EXC_RI      10 ; Reserved instruction exception
%define EXC_CpU     11 ; Coprocessor Unusable exception
%define EXC_Ov      12 ; Arithmetic Overflow exception
%define EXC_Tr      13 ; Trap exception
%define EXC_Watch   23 ; Reference to WatchHi/WatchLo address
%define EXC_MCheck  24 ; Machine check
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Get the current Exception Code, write to the specified destination
;-----------------------------------------------------------------------------
%macro GetEXC   1+ ; param is destination to write the exception code
        mov     %1,  CP0_Cause         ; Dest := CP0_Cause
        shr     %1,  2                 ; Dest := Dest >> 2
        and     %1,  0x1F              ; Dest := Dest & 0x1F
%endmacro
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Set the current Exception Code from the given constant
;-----------------------------------------------------------------------------
%macro SetEXC  1+ ; param is the exception code to set
        and     CP0_Cause, ~(0x1F << 2)     ; Clear the current Exception Code
        or      CP0_Cause, (%1 & 0x1F) << 2 ; set the new one
%endmacro

;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; SetIRQ : Trigger External Interrupt ( Parameter: constant IRQ # )
;-----------------------------------------------------------------------------

%macro SetIRQ  1+ ; param is the IRQ #
        or      CP0_Cause, (((1 << %1) & 0x3F) << 10)
%endmacro
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; ClrIRQ : Clear External Interrupt ( Parameter: constant IRQ # )
;-----------------------------------------------------------------------------

%macro ClrIRQ  1+ ; param is the IRQ #
        and     CP0_Cause, ~(((1 << %1) & 0x3F) << 10)
%endmacro
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; IRQ 0 from Slave
;; TODO: locking and wait/sleep
;-----------------------------------------------------------------------------
%macro SetSlaveIRQ  0
        mov     dword [SlaveIRQ], 1
%endmacro
;-----------------------------------------------------------------------------
%macro GetSlaveIRQ  0
        mov     eax, [SlaveIRQ]
        test    eax, eax
        jz      %%skip
        mov     dword [SlaveIRQ], 0
        SetIRQ(0)
%%skip:
%endmacro
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; CP0_Status Flags
;-----------------------------------------------------------------------------
%define CP0St_CU3   31
%define CP0St_CU2   30
%define CP0St_CU1   29
%define CP0St_CU0   28
%define CP0St_RP    27
%define CP0St_FR    26
%define CP0St_RE    25
%define CP0St_MX    24
%define CP0St_PX    23
%define CP0St_BEV   22
%define CP0St_TS    21
%define CP0St_SR    20
%define CP0St_NMI   19
%define CP0St_IM    8
%define CP0St_KX    7
%define CP0St_SX    6
%define CP0St_UX    5
%define CP0St_UM    4
%define CP0St_KSU   3
%define CP0St_ERL   2
%define CP0St_EXL   1
%define CP0St_IE    0
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Start of Program.
;-----------------------------------------------------------------------------
section .text
global  _start
_start:
; get argc
        mov     rax, [rsp]
        cmp     rax, 2             ; required arg count
        je      _run
; not 1 args? print usage, exit:
_usage:
        EGGOG   "Usage: FILE"
_run:
        ;; Test if SSE2 instructions are available on this machine:
        mov     eax, 1
        cpuid
        test    edx, 0x4000000
        jnz     _xmm_ok
        EGGOG   "Needs SSE2!"
_xmm_ok:

; get 1st cmdline arg (path)
        mov     rdi, [rsp + 16]
        mov     [arg0], rdi

; fd = open(path, O_RDONLY)
        mov     rax, SYS_OPEN
        mov     rdi, [arg0]        ; first arg
        mov     rsi, 0             ; O_RDONLY
        syscall
        test    rax, rax           ; see if eggog
        jns     _ok                ; if worked
        EGGOG   "Could not read snapshot file!"
_ok:
        mov     [fd], rax          ; else, save fd

; Mbytes = fstat(fd).st_size
	mov	rax, SYS_FSTAT
        sub     rsp, statbuf.size  ; make scratch
        mov     rsi, rsp
        mov     rdi, [fd]
	syscall
        mov     rax, [rsp + statbuf.st_size]
        mov     [Mbytes], rax
        add     rsi, statbuf.size  ; unmake scratch

; mmap(null, size, PROT_READ, MAP_PRIVATE, fd, 0)
        mov     rax, SYS_MMAP
        xor     r9, r9             ; offset=0
        mov     r8, [fd]
        mov     r10, MAP_PRIVATE
        mov     rdx, PROT_READ | PROT_WRITE
        mov     rsi, [Mbytes]
        xor     rdi, rdi           ; os will give addr
        syscall
        mov     [M], rax

; cure the tty:
        call    _Cure_TTY

; set up master and slave threads:
        mov     rdi, _UART_Slave_Thread ; for now, just UART
        call    _Create_Thread
        
        ;; jump into master
        jmp     _Master_Thread

;-----------------------------------------------------------------------------

_Cure_TTY:
        ;; get old tty state:
        mov     rdx, tty + TTY_Config.tty
        mov     rax, SYS_IOCTL
        mov     rdi, 0
        mov     rsi, TCGETS
        syscall
        
        ;; switch off linux console 'cooked mode' idiocy:
        and     dword [tty + TTY_Config.lflag], ~(0xF)
        
        ;; set new tty state:
        mov     rdx, tty + TTY_Config.tty
        mov     rax, SYS_IOCTL
        mov     rdi, 0
        mov     rsi, TCPUTS
        syscall
        ret

;-----------------------------------------------------------------------------
_Create_Thread:
        push    rdi
        ;; allocate stack for the new thread:
        mov     rdi, 0
	mov     rsi, SLAVE_STACK_SIZE
	mov     rdx, PROT_WRITE | PROT_READ
	mov     r10, MAP_ANONYMOUS | MAP_PRIVATE | MAP_GROWSDOWN
	mov     rax, SYS_MMAP
	syscall
        ;; actually spawn the thread:
        lea     rsi, [rax + SLAVE_STACK_SIZE - 8]
        pop     qword [rsi]
	mov     rdi, THREAD_FLAGS
	mov     rax, SYS_CLONE
	syscall
        ret

;-----------------------------------------------------------------------------
_UART_Slave_Thread:
        call    _Read_Char_Blocking
        mov     dl, byte [IOBUF]
        call    _UART_Receive_Byte ; put DL in the UART receiver queue
        SetSlaveIRQ ; trigger the slave IRQ
        jmp     _UART_Slave_Thread ; rinse & repeat
;-----------------------------------------------------------------------------


;-----------------------------------------------------------------------------
_Master_Thread:
        call    _UART_Reset
        call    _ram_init
        call    _cpu_init
        call    _cpu_run
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
_shutdown:

;-----------------------------------------------------------------------------
; The End
;-----------------------------------------------------------------------------
; munmap(M, Mbytes)
        mov     rax, SYS_MUNMAP
        mov     rsi, [Mbytes]
        mov     rdi, [M]
        syscall

; close(fd)
        mov     rax, SYS_CLOSE
        mov     rdi, [fd]
        syscall

; quit
_exit:
        mov     rax, SYS_EXIT
        mov     rdi, 0 ; exit code
        syscall
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Translate_Address : virtual address in eax; output (physical addr) in eax
%define V2P_Reg eax
;-----------------------------------------------------------------------------
; TODO: 1) should align case targets to 32 or not? for now, let's not.
;       2) do we want to inline this?
;       3) how about moving frequently-used TLB entries upward in the table?
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_Virt_To_Phys:
        bt      V2P_Reg, 31             ; CF := 31st (top) bit of vAddr
        jc      _Above_7FFFFFFF         ; If 31st bit = 1, kseg 0/1/2; else:
        ;; 0x00000000 <= vAddr <= 0x7FFFFFFF (kuseg) :
        bt      CP0_Status, CP0St_ERL   ; CF := CP0St_ERL Flag
        jnc     _TLB_Lookup             ; If ERL = 0: TLB Lookup required; else:
        jmp     _No_Tlb_Lookup          ; pAddr is equal to vAddr, return.
_Above_7FFFFFFF:
        bt      V2P_Reg, 30             ; CF := 30th (2nd from top) bt of vAddr
        jc      _Above_BFFFFFFF         ; If 30th bit = 1 : kseg2; else:
        ;; 0x80000000 <= vAddr <= 0x9FFFFFFF (kseg0) :
        ;; 0xA0000000 <= vAddr <= 0xBFFFFFFF (kseg1) :
        and     V2P_Reg, 0x1FFFFFFF     ; kseg0 and kseg1: clear top 3 bits,
        jmp     _No_Tlb_Lookup          ; i.e. pAddr := bottom 29 bts of vAddr.
_Above_BFFFFFFF: ;; 0xC0000000 <= vAddr <= 0xFFFFFFFF (kseg2) :
        bt      CP0_Status, CP0St_UM    ; CF := CP0St_UM Flag
        jnc     _TLB_Lookup             ; If UM = 0: K. Mode, so do TLB; else:
        test    CP0_Status, (1 << CP0St_EXL) | (1 << CP0St_ERL) ; EXL or ERL
        jnz     _TLB_Lookup             ; If EXL && ERL, K. Mode, do TLB
        ;; Else: vAddr is in kseg2, but we are NOT in Kernel Mode:
        EGGOG   "Virt_To_Phys: unhandled exception"
        ;; TODO: handle these like civilized people.
_No_Tlb_Lookup:
        Flg_Off IsWriting               ; Consume 'is writing' flag.
        ret                             ; Done.
_TLB_Lookup:                            ; TLB Lookup Required:
        ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
        ;; Save ebx, ecx, edx, AUX, to xmm ;;
        ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
        movd    xmm0, ebx
        movd    xmm1, ecx
        movd    xmm2, edx
        movd    xmm3, AUX
        ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
        Flg_Off ExcWasTLBNoMatch        ; Clear the ExcWasTLBNoMatch Flag
        ;; Get the active ASID:
        mov     edx,   Sr(CP0_EntryHi)  ; edx := CP0_EntryHi
        and     edx,   0xFF             ; edx := edx & 0xFF (get current ASID)
        ;; For each slot in table (0 .. 15), attempt lookup
        xor     AUX,   AUX              ; Start with the 0-th entry in table
_Lookup_TLB_E:
        mov     ecx,   eax              ; ecx := eax (vAddr)
        and     ecx,   0xFFFFF000       ; ecx := ecx & 0xFFFFF000
        shr     ecx,   13               ; ecx := ecx >> 13 (get vAddr's Tag)
        mov     ebx,   TLB_E(AUX)       ; ebx := current TLB entry
        and     ebx,   TLB_VPN2_Mask    ; get VPN2 of this entry
        cmp     ebx,   ecx              ; cmp(entry.VPN2, vAddr.tag)
        jne     _Lookup_TLB_E_Not_Here  ; if entry.VPN2 != vAddr.tag: no match
        mov     ebx,   TLB_E(AUX)       ; ebx := current TLB entry
        bt      ebx,   TLB_G            ; is entry.G = 1?
        jc      _Lookup_TLB_E_Match     ; then match.
        shr     ebx,   TLB_ASID_Shift   ; ebx := ebx >> TLB_ASID_Shift
        and     ebx,   TLB_ASID_Mask    ; ebx := entry.ASID
        cmp     ebx,   edx              ; entry.ASID = current ASID ?
        jne     _Lookup_TLB_E_Not_Here  ; if neither G=1 nor ASID match.
        mov     ebx,   TLB_E(AUX)       ; ebx := current TLB entry
_Lookup_TLB_E_Match:                    ; TLB Match:
        bt      eax,   12               ; Test odd/even junior bit
        jc      _Lookup_TLB_E_Match_Odd ; If odd:  test V1, D1
_Lookup_TLB_E_Match_Even:               ; If even: test V0, D0
        lea     ecx,   TLB_PFN_E(AUX)   ; prepare to load even PFN entry
        bt      ebx,   TLB_V0           ; Is entry.V0=1 ?
        jnc     _Lookup_TLB_E_Invalid   ; If not, TLBRET_INVALID
        bt      ebx,   TLB_D0           ; Is entry.D0=1 ?
        jc      _Lookup_TLB_E_Match_Yes ; If entry.D0=1, then Match Yes
        jmp     _Lookup_TLB_E_Match_Wr  ; else, go to 'is writing?'
_Lookup_TLB_E_Match_Odd:                ; Odd bit:
        lea     ecx,   TLB_PFN_O(AUX)   ; prepare to load odd PFN entry
        bt      ebx,   TLB_V1           ; Is entry.V1=1 ?
        jnc     _Lookup_TLB_E_Invalid   ; If not, TLBRET_INVALID
        bt      ebx,   TLB_D1           ; Is entry.D1=1 ?
        jc      _Lookup_TLB_E_Match_Yes ; If entry.D1=1, then Match Yes
_Lookup_TLB_E_Match_Wr:
        Flg_Get IsWriting               ; Is Writing?
        jnc     _Lookup_TLB_E_Match_Yes ; If not writing, go to Match Yes
_Lookup_TLB_E_Dirty:                    ; ... else, Dirty:
        SetEXC  EXC_Mod                 ; Set the EXC_Mod Exception
        jmp     _Lookup_TLB_E_WriteExtr ; Write the 'extra data' and finish.
_Lookup_TLB_E_Match_Yes:                ; This is the 'success' case
        mov     ebx, dword [ecx]        ; Actually load the current PFN entry
        and     eax, 0xFFF              ; vAddr := vAddr & 0xFFF
        or      eax, ebx                ; vAddr := vAddr | entry.PFN[lowbit]
        jmp     _Lookup_TLB_Done        ; vAddr is now correct pAddr, done.
_Lookup_TLB_E_Not_Here:                 ; try next one in the table, if any
        inc     AUX                     ; index := index + 1
        cmp     AUX, TLB_ENTRIES_COUNT  ; see if still in range 0 .. n-1
        jb      _Lookup_TLB_E           ; if in range, go to next entry
        ;; ... else:
        Flg_On  ExcWasTLBNoMatch        ; Set the ExcWasTLBNoMatch Flag
        ;; ... now drop down into 'invalid' :
_Lookup_TLB_E_Invalid:
        Flg_Get IsWriting               ; Was Writing?
        jc      _Lookup_TLB_E_Invalid_W ; If so, we want to set EXC_TLBS
_Lookup_TLB_E_Invalid_R:                ; Otherwise, set EXC_TLBL exception
        SetEXC  EXC_TLBL                ; Set the EXC_TLBL Exception
        jmp     _Lookup_TLB_E_WriteExtr ; Write the 'extra data' and finish.
_Lookup_TLB_E_Invalid_W:
        SetEXC  EXC_TLBS                ; Set the EXC_TLBS Exception
        ;; then drop down to 'write extra' :
_Lookup_TLB_E_WriteExtr:                ; Write the 'extra data' and finish
        mov     Sr(CP0_BadVAddr), eax   ; CP0_BadVAddr := vAddr
        mov     ecx, eax                ; ecx := vAddr
        mov     ebx, Sr(CP0_Context)    ; ebx := CP0_Context
        and     ebx, ~0x007FFFFF        ; ebx := ebx & ~0x007FFFFF
        shr     ecx, 9                  ; ecx := ecx >> 9
        and     ecx, 0x007FFFF0         ; ecx := ecx & 0x007FFFF0
        or      ebx, ecx                ; ebx := ebx | ecx
        mov     Sr(CP0_Context), ebx    ; CP0_Context := ebx
        mov     ecx, eax                ; ecx := vAddr
        mov     ebx, Sr(CP0_EntryHi)    ; ebx := CP0_EntryHi
        and     ebx, 0xFF               ; ebx := ebx & 0xFF
        and     ecx, 0xFFFFE000         ; ecx := ecx & 0xFFFFE000
        or      ebx, ecx                ; ebx := ebx | ecx
        mov     Sr(CP0_EntryHi), ebx    ; CP0_EntryHi := ebx
        ;; Will go into exception handler instead of back to _Virt_xxx etc
        add     rsp, 16           ; squelch return to _Virt_xxx and its caller
        push    _Handle_Exception       ; 'return' directly to exc handler.
        ;; and drop into 'done' :
_Lookup_TLB_Done:
        Flg_Off IsWriting               ; Consume 'is writing' flag.
        ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
        ;; Restore ebx, ecx, edx, AUX, from xmm ;;
        ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
        movd    ebx, xmm0
        movd    ecx, xmm1
        movd    edx, xmm2
        movd    AUX, xmm3
        ret
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
;; Access to UART regs:
%define U0r(R)         byte [UART0 + UART_State. %+ R]
;-----------------------------------------------------------------------------
;; Clear FIFO
;-----------------------------------------------------------------------------
_UART_FIFO_Clear:
        mov     U0r(FIFO_Last),  0
        mov     U0r(FIFO_First), 0
        mov     U0r(FIFO_Count), 0
        ret
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
;; Take a byte from the UART FIFO. Byte will be in DL.
;-----------------------------------------------------------------------------
_UART_FIFO_Get:
        xor     edx, edx              ; edx        := 0
        cmp     U0r(FIFO_Count), 0    ; is FIFO nonempty?
        je      _UART_FIFO_Get_Nope   ; ... if empty, skip and return 0
        ;; if FIFO is nonempty:
        xor     eax, eax              ; eax        := 0
        mov     al, U0r(FIFO_First)   ; al         := fifo_first
        mov     dl, U0r(FIFO + eax)   ; dl         := FIFO[al]
        inc     eax                   ; eax        := eax + 1
        and     eax, 0x1F             ; eax        := eax & 0x1f
        mov     U0r(FIFO_First), al   ; fifo_first := al
        mov     al, U0r(FIFO_Count)   ; al         := fifo_count
        test    eax, eax              ; is al 0 ?
        jz      _UART_FIFO_Get_Nope   ; ... if 0, skip and do not update count
        dec     eax                   ; eax        := eax - 1
        mov     U0r(FIFO_Count), al   ; fifo_count := al
_UART_FIFO_Get_Nope:
        ret
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
;; Reset UART-0
;-----------------------------------------------------------------------------
_UART_Reset:
        mov     U0r(LCR), 3
        mov     U0r(LSR), UART_LSR_TRANSMITTER_EMPTY | UART_LSR_FIFO_EMPTY
        mov     U0r(MSR), 0
        mov     U0r(IIR), UART_IIR_NO_INT
        mov     U0r(IER), 0
        mov     U0r(DLL), 0
        mov     U0r(DLH), 0
        mov     U0r(FCR), 0
        mov     U0r(MCR), 0
        mov     U0r(SCR), 0
        call    _UART_FIFO_Clear ; zap the FIFO
        ret
;-----------------------------------------------------------------------------
;; Update UART IRQ
;-----------------------------------------------------------------------------
_UART_IRQ:
        test    U0r(LSR), UART_LSR_DATA_READY
        jz      _UART_IRQ_Not_RDI
        test    U0r(IER), UART_IER_RDI
        jz      _UART_IRQ_Not_RDI
_UART_IRQ_RDI:
        mov     U0r(IIR), UART_IIR_RDI         ; IIR=UART_IIR_RDI
        jmp     _UART_IRQ_Maybe_Pending        ; See if interrupt pending
_UART_IRQ_Not_RDI:
        test    U0r(LSR), UART_LSR_FIFO_EMPTY
        jz      _UART_IRQ_Not_THRI
        test    U0r(IER), UART_IER_THRI
        jz      _UART_IRQ_Not_THRI
_UART_IRQ_THRI:
        mov     U0r(IIR), UART_IIR_THRI        ; IIR=UART_IIR_THRI
        jmp     _UART_IRQ_Maybe_Pending        ; See if interrupt pending
_UART_IRQ_Not_THRI:
        mov     U0r(IIR), UART_IIR_NO_INT      ; IIR=UART_IIR_NO_INT
_UART_IRQ_Maybe_Pending:                       ; See if interrupt pending
        ;; see if interrupt pending
        cmp     U0r(IIR), UART_IIR_NO_INT      ; IIR==UART_IIR_NO_INT ?
        jne     _UART_IRQ_Pending              ; if !=, interrupt is pending
_UART_IRQ_Not_Pending: ; interrupt is not pending:
        ClrIRQ  0
        ret
_UART_IRQ_Pending:     ; interrupt is pending:
        SetIRQ  0
        ret
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
;; Place a byte (DL) into the UART's receiver buffer.
; TODO: mutex?
;-----------------------------------------------------------------------------
_UART_Receive_Byte:
        xor     eax, eax                      ; eax        := 0
        mov     al, U0r(FIFO_Last)            ; al         := fifo_last
        mov     U0r(FIFO + eax), dl           ; FIFO[al]   := dl
        inc     eax                           ; eax        := eax + 1
        and     eax, 0x1F                     ; eax        := eax & 0x1f
        mov     U0r(FIFO_Last), al            ; fifo_last  := al
        mov     al,  U0r(FIFO_Count)          ; eax        := fifo_count
        inc     eax                           ; eax        := eax + 1
        mov     edx, 32                       ; edx        := 32
        cmp     eax, edx                      ; is eax > 32 ?
        cmovg   eax, edx                      ; ... if yes, eax := 32
        mov     U0r(FIFO_Count), al           ; fifo_count := al
        or      U0r(LSR), UART_LSR_DATA_READY ; set the 'ready' flag
        call    _UART_IRQ                     ; update IRQ
        ret
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
;; Read UART Register. Offset in EAX, result in DL.
;-----------------------------------------------------------------------------
_UART_Read_Reg:
        xor     edx,   edx              ; result := 0
        sub     eax,   UARTBASE         ; Adjust for base of UART
        and     eax,   0x7              ; Adjusted offset is a 4-bit quantity
        test    U0r(LCR), UART_LCR_DLAB
        jz      _UART_Read_Reg_Not_DLAB
_UART_Read_Reg_DLAB:
        cmp     eax,   UART_DLL         ; offset=DLL?
        je      _UART_Read_Reg_DLAB_DLL
        cmp     eax,   UART_DLH         ; offset=DLH?
        je      _UART_Read_Reg_DLAB_DLH
        jmp     _UART_Read_Reg_Not_DLAB ; neither of these
_UART_Read_Reg_DLAB_DLL:
        mov     dl,    U0r(DLL)         ; return DLL
        ret
_UART_Read_Reg_DLAB_DLH:
        mov     dl,    U0r(DLH)         ; return DLH
        ret
_UART_Read_Reg_Not_DLAB:
        cmp     eax,   0                ; offset=0 ?
        je      _UART_Read_Reg_0
        cmp     eax, UART_IER           ; offset=IER ?
        je      _UART_Read_Reg_IER
        cmp     eax, UART_MSR           ; offset=MSR ?
        je      _UART_Read_Reg_MSR
        cmp     eax, UART_MCR           ; offset=MCR ?
        je      _UART_Read_Reg_MCR
        cmp     eax, UART_IIR           ; offset=IIR ?
        je      _UART_Read_Reg_IIR
        cmp     eax, UART_LCR           ; offset=LCR ?
        je      _UART_Read_Reg_LCR
        cmp     eax, UART_LSR           ; offset=LSR ?
        je      _UART_Read_Reg_LSR
        cmp     eax, UART_SCR           ; offset=SCR ?
        je      _UART_Read_Reg_SCR
        EGGOG   "Bad UART Read Command"
        ;; TODO: handle like civilized people

_UART_Read_Reg_0:
        cmp     U0r(FIFO_Count), 0      ; is FIFO nonempty?
        je      _UART_Read_Reg_0_Empty  ; ... if empty, skip
        ;; FIFO is not empty:
        call    _UART_FIFO_Get          ; get byte from FIFO
        and     U0r(LSR), ~UART_LSR_DATA_READY ; lower 'ready' flag
        cmp     U0r(FIFO_Count), 0      ; is FIFO still nonempty?
        je      _UART_Read_Reg_0_Empty  ; ... if empty, leave 'ready' flag down
        ;; FIFO still has data:
        or      U0r(LSR), UART_LSR_DATA_READY ; else, raise the 'ready' flag.
_UART_Read_Reg_0_Empty:
        call    _UART_IRQ               ; update IRQ
        ret                             ; return DL

_UART_Read_Reg_IER:
        mov     dl,  U0r(IER)
        and     edx, 0xF
        ret                             ; return IER & 0xF

_UART_Read_Reg_MSR:
        mov     dl, U0r(MSR)
        ret                             ; return MSR

_UART_Read_Reg_MCR:
        mov     dl, U0r(MCR)
        ret                             ; return MCR

_UART_Read_Reg_IIR:
        mov     dl, U0r(IIR)
        ret                             ; return IIR

_UART_Read_Reg_LCR:
        mov     dl, U0r(LCR)
        ret                             ; return LCR

_UART_Read_Reg_LSR:
        cmp     U0r(FIFO_Count), 0      ; is FIFO nonempty?
        je      _UART_Read_Reg_LSR_Empty ; if empty, go to 'empty'
        ;; FIFO is not empty:
        or      U0r(LSR), UART_LSR_DATA_READY ; raise the 'ready' flag.
        jmp     _UART_Read_Reg_LSR_Done ; return
_UART_Read_Reg_LSR_Empty:
        and     U0r(LSR), ~UART_LSR_DATA_READY ; lower the 'ready' flag
_UART_Read_Reg_LSR_Done:
        mov     dl, U0r(LSR)
        ret                             ; return LSR

_UART_Read_Reg_SCR:
        mov     dl, U0r(SCR)
        ret                             ; return SCR
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
;; Write UART Register. Offset in EAX, payload in DL.
;-----------------------------------------------------------------------------
_UART_Write_Reg:
        sub     eax,   UARTBASE         ; Adjust for base of UART
        and     eax,   0x7              ; Adjusted offset is a 4-bit quantity
        test    U0r(LCR), UART_LCR_DLAB
        jz      _UART_Write_Reg_Not_DLAB
_UART_Write_Reg_DLAB:
        cmp     eax,   UART_DLL         ; offset=DLL?
        je      _UART_Write_Reg_DLAB_DLL
        cmp     eax,   UART_DLH         ; offset=DLH?
        je      _UART_Write_Reg_DLAB_DLH
        jmp     _UART_Write_Reg_Not_DLAB ; neither of these
_UART_Write_Reg_DLAB_DLL:
        mov     U0r(DLL), dl            ; write DLL
        ret
_UART_Write_Reg_DLAB_DLH:
        mov     U0r(DLH), dl            ; write DLH
        ret
_UART_Write_Reg_Not_DLAB:
        cmp     eax,   0                ; offset=0 ?
        je      _UART_Write_Reg_0
        cmp     eax,   UART_IER         ; offset=IER?
        je      _UART_Write_Reg_IER
        cmp     eax,   UART_FCR         ; offset=FCR?
        je      _UART_Write_Reg_FCR
        cmp     eax,   UART_LCR         ; offset=LCR?
        je      _UART_Write_Reg_LCR
        cmp     eax,   UART_MCR         ; offset=MCR?
        je      _UART_Write_Reg_MCR
        cmp     eax,   UART_SCR         ; offset=SCR?
        je      _UART_Write_Reg_SCR
        EGGOG   "Bad UART Write Command"
        ret
        ;; TODO: handle like civilized people

_UART_Write_Reg_0:
        and     U0r(LSR), ~UART_LSR_FIFO_EMPTY
        test    U0r(MCR), (1 << 4)
        jz      _UART_Write_Reg_0_Not_Loopback
_UART_Write_Reg_0_Loopback:
        call    _UART_Receive_Byte      ; put DL in the receiver FIFO
        jmp     _UART_Write_Reg_0_Fin   ; wrap up
_UART_Write_Reg_0_Not_Loopback:         ; if NOT loopback :
        call    _Write_Char             ; ... then write char in DL to console
_UART_Write_Reg_0_Fin:
        or      U0r(LSR), UART_LSR_FIFO_EMPTY ; mark the send buffer as empty
        call    _UART_IRQ               ; refresh IRQ
        ret                             ; fin

_UART_Write_Reg_IER:
        and     edx,      0xF           ; 4-bit qty
        mov     U0r(IER), dl            ; IER := dl & 0xF
        call    _UART_IRQ               ; refresh IRQ
        ret                             ; fin

_UART_Write_Reg_FCR:
        mov     U0r(FCR), dl            ; FCR := dl
        test    dl, 0x2                 ; if FCR & 2:
        jz      _UART_Write_Reg_FCR_No_Clear
        call    _UART_FIFO_Clear        ; ... then zap the FIFO.
_UART_Write_Reg_FCR_No_Clear:
        ret

_UART_Write_Reg_LCR:
        mov     U0r(LCR), dl            ; LCR := dl
        ret                             ; fin

_UART_Write_Reg_MCR:
        mov     U0r(MCR), dl            ; MCR := dl
        ret                             ; fin

_UART_Write_Reg_SCR:
        mov     U0r(SCR), dl            ; SCR := dl
        ret                             ; fin
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; UART : read and write byte
; TODO : save/restore the killed regs ?
;-----------------------------------------------------------------------------

;; Write byte in DL to console
_UART_Write_Byte:
        call    _UART_Write_Reg
        ret

_UART_Read_Byte:
        call    _UART_Read_Reg
        mov     eax, edx ; save result
        ret
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; ReadWord - Source Address (Virtual) is EAX; Resulting Word Read is EAX.
;-----------------------------------------------------------------------------
; TODO: 1) is the 'unaligned' case in fact physically possible?
;       2) would go faster if used tmp instead of self-clobbering rax ?
align GRAIN, db 0x90
_Virt_Read_Word:
        call    _Virt_To_Phys          ; Transform vAddr to pAddr
        test    eax, 0x3               ; Are any of the bottom 2 bits set?
        jnz     _V_Rd_Word_Unaligned   ; If yes, go to eggog. Else:
        ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
        ;; TODO -- if UART, read a null
        ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
        add     rax, RAM_Floor         ; Calculate the target PC RAM addr
        cmp     rax, RAM_Ceiling       ; compare the above with RAM ceiling
        jnb     _V_Rd_Word_Hit_Ceiling ; if not below: RAM ceiling eggog!
        ;; Address is valid, so load the word:
        mov     eax, dword [rax]       ; eax := dword [rax]
        ret                            ; Done, return with read result in eax.
_V_Rd_Word_Unaligned:
        EGGOG   "Virt_Read_Word: Unaligned Physical Address!"
        ;; TODO: handle these like civilized people.
        ret
_V_Rd_Word_Hit_Ceiling:
        EGGOG   "Virt_Read_Word: Hit RAM Ceiling!"
        ;; TODO: handle these like civilized people.
        ret
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; WriteWord - Destination Address (Virtual) is EAX; Word to be Written is TMP.
;-----------------------------------------------------------------------------
; TODO: 1) is the 'unaligned' case in fact physically possible?
;       2) would go faster if used tmp instead of self-clobbering rax ?
;       3) do we need to explicitly zero-extend rax here?
align GRAIN, db 0x90
_Virt_Write_Word:
        Flg_On  IsWriting              ; Tell the translator that we're writing
        call    _Virt_To_Phys          ; Transform vAddr to pAddr
        test    eax, 0x3               ; Are any of the bottom 2 bits set?
        jnz     _V_Wr_Word_Unaligned   ; If yes, go to eggog. Else:
        ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
        ;; TODO -- if UART, write a null
        ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
        add     rax, RAM_Floor         ; Calculate the target PC RAM addr
        cmp     rax, RAM_Ceiling       ; compare the above with RAM ceiling
        jnb     _V_Wr_Word_Hit_Ceiling ; if not below: RAM ceiling eggog!
        ;; Address is valid, so write the word:
        mov     dword [rax], TMP       ; dword [rax] := TMP
        ret                            ; Done.
_V_Wr_Word_Unaligned:
        EGGOG   "Virt_Write_Word: Unaligned Physical Address!"
        ;; TODO: handle these like civilized people.
        ret
_V_Wr_Word_Hit_Ceiling:
        EGGOG   "Virt_Write_Word: Hit RAM Ceiling!"
        ;; TODO: handle these like civilized people. (EXC_DBE)
        ret
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; ReadByte - Source Address (Virtual) is EAX; Resulting Byte Read is EAX.
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_Virt_Read_Byte:
        call    _Virt_To_Phys          ; Transform vAddr to pAddr
        ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
        ;; UART
        cmp     eax, UARTBASE             ; is it in UART space?
        jb      _Virt_Read_Byte_Not_UART  ; if below, then not
        cmp     eax, UARTBASE + UARTSIZE  ;
        ja      _Virt_Read_Byte_Not_UART  ; if above, then not
        ;; otherwise, in uart:
        call    _UART_Read_Byte           ; read this byte from UART
        ret
        ;; if not in UART:
_Virt_Read_Byte_Not_UART:
        ;;;;;;;;;;;;;;;;;; ENDIANISM ;;;;;;;;;;;;;;;;;;
%ifdef LITTLE_ENDIAN
        ;; little -- do nothing
%else
        ;; big -- flip :
        xor     eax, 0x3               ; Flip the 3 unalignment bits.
%endif
        ;;;;;;;;;;;;;;;;;; ENDIANISM ;;;;;;;;;;;;;;;;;;
        add     rax, RAM_Floor         ; Calculate the target PC RAM addr
        cmp     rax, RAM_Ceiling       ; compare the above with RAM ceiling
        jnb     _V_Rd_Byte_Hit_Ceiling ; if not below: RAM ceiling eggog!
        ;; Address is valid, so proceed to load:
        movzx   ax, byte [rax]         ; ax := byte [rax]
        and     eax, 0xFF              ; keep only bottom byte
        ret                            ; Done, return with read result in eax.
_V_Rd_Byte_Hit_Ceiling:
        EGGOG   "Virt_Read_Byte: Hit RAM Ceiling!"
        ;; TODO: handle these like civilized people.
        ret
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; WriteByte - Destination Address (Virtual) is EAX; Byte to be Written is DL.
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_Virt_Write_Byte:
        Flg_On  IsWriting              ; Tell the translator that we're writing
        call    _Virt_To_Phys          ; Transform vAddr to pAddr
        ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
        ;; UART
        cmp     eax, UARTBASE             ; is it in UART space?
        jb      _Virt_Write_Byte_Not_UART  ; if below, then not
        cmp     eax, UARTBASE + UARTSIZE  ;
        ja      _Virt_Write_Byte_Not_UART  ; if above, then not
        ;; otherwise, in uart:
        call    _UART_Write_Byte           ; write this byte to UART
        ret
        ;; if not in UART:
_Virt_Write_Byte_Not_UART:
        ;;;;;;;;;;;;;;;;;; ENDIANISM ;;;;;;;;;;;;;;;;;;
%ifdef LITTLE_ENDIAN
        ;; little -- do nothing
%else
        ;; big -- flip :
        xor     eax, 0x3               ; Flip the 3 unalignment bits.
%endif
        ;;;;;;;;;;;;;;;;;; ENDIANISM ;;;;;;;;;;;;;;;;;;
        add     rax, RAM_Floor         ; Calculate the target PC RAM addr
        cmp     rax, RAM_Ceiling       ; compare the above with RAM ceiling
        jnb     _V_Wr_Byte_Hit_Ceiling ; if not below: RAM ceiling eggog!
        ;; Address is valid, so write the byte:
        mov     byte [rax], DL         ; dword [rax] := TMP
        ret                            ; Done.
_V_Wr_Byte_Hit_Ceiling:
        EGGOG   "Virt_Write_Byte: Hit RAM Ceiling!"
        ;; TODO: handle these like civilized people. (EXC_DBE)
        ret
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Write TLB Entry at the given index. Index (0 .. 15) is in AUX.
; Kills eax, ebx, ecx, edx.
;-----------------------------------------------------------------------------
_write_tlb_entry:
        mov     edx,   Sr(CP0_EntryHi)  ; edx := CP0_EntryHi
        mov     ecx,   edx              ; ecx := edx
        shr     ecx,   13               ; ecx := ecx >> 13 to get VPN2
        and     edx,   TLB_ASID_Mask    ; edx := edx & 0xFF to get ASID
        shl     edx,   TLB_ASID_Shift   ; edx := edx << 19 to put ASID in place
        or      ecx,   edx              ; now we have VPN2 and ASID in ecx
        ;; done with edx, can reuse
        mov     edx,   Sr(CP0_EntryLo0) ; edx := CP0_EntryLo0
        mov     ebx,   Sr(CP0_EntryLo1) ; ebx := CP0_EntryLo1
        ;; get G:
        mov     eax,   edx              ; eax := CP0_EntryLo0
        and     eax,   ebx              ; eax := eax & CP0_EntryLo1
        and     eax,   0x1              ; eax := eax & 1 to get G
        shl     eax,   TLB_G            ; move G bit into position
        or      ecx,   eax              ; ecx := ecx | eax to put in G bit
        ;; now ecx contains VPN2, ASID, G
        ;; Get V0 from CP0_EntryLo0 and put in ecx where belongs:
        mov     eax,   edx              ; eax := CP0_EntryLo0
        and     eax,   0x2              ; eax := eax & 0x2 to get V0 bit
        shl     eax,   (TLB_V0 - 1)     ; put V0 bit in position
        or      ecx,   eax              ; ecx := ecx | eax to put in V0 bit
        ;; Get D0 from CP0_EntryLo0 and put in ecx where belongs:
        mov     eax,   edx              ; eax := CP0_EntryLo0
        and     eax,   0x4              ; eax := eax & 0x4 to get D0 bit
        shl     eax,   (TLB_D0 - 2)     ; put D0 bit in position
        or      ecx,   eax              ; ecx := ecx | eax to put in D0 bit
        ;; Get V1 from CP0_EntryLo1 and put in ecx where belongs:
        mov     eax,   ebx              ; eax := CP0_EntryLo1
        and     eax,   0x2              ; eax := eax & 0x2 to get V1 bit
        shl     eax,   (TLB_V1 - 1)     ; put V1 bit in position
        or      ecx,   eax              ; ecx := ecx | eax to put in V1 bit
        ;; Get D1 from CP0_EntryLo1 and put in ecx where belongs:
        mov     eax,   ebx              ; eax := CP0_EntryLo1
        and     eax,   0x4              ; eax := eax & 0x4 to get D1 bit
        shl     eax,   (TLB_D1 - 2)     ; put D1 bit in position
        or      ecx,   eax              ; ecx := ecx | eax to put in D1 bit
        ;; Write the TLB entry to the given index (in AUX) :
        and     AUX,   0xF              ; Index of TLB entry is bottom 4 bits
        mov     TLB_E(AUX), ecx         ; Write TLB entry in ecx to n-th slot.
        ;; Transform CP0_EntryLo0 (edx) into PFN0:
        shr     edx,   6
        and     edx,   0xFFFFF
        shl     edx,   12
        ;; Transform CP0_EntryLo1 (ebx) into PFN1:
        shr     ebx,   6
        and     ebx,   0xFFFFF
        shl     ebx,   12
        ;; Store PFN:
        mov     TLB_PFN_E(AUX), edx     ; Store PFN0
        mov     TLB_PFN_O(AUX), ebx     ; Store PFN1
        ret                             ; Done.
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Init RAM:
;-----------------------------------------------------------------------------
_ram_init:
        mov     RAM_Floor,   [M]       ; Set 'floor' of RAM
        mov     RAM_Ceiling, RAM_Floor ; 'ceiling'
        add     RAM_Ceiling, [Mbytes]  ; ... size of the mmap
        ;; TODO: test for unaligned floor or ceiling
        ret
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Init MIPS CPU:
;-----------------------------------------------------------------------------
_cpu_init:
        xor     eax,  eax              ; eax := 0
        mov     AUX,  eax              ; Clear AUX register
        mov     M_Base_64, MIPS_State  ; Set RBP to point to MIPS CPU State
        ;; Init 'fast' MIPS Regs:
        mov     PC,               INIT_PC
        mov     nPC,              eax
        mov     CP0_Status,       eax
        mov     CP0_Cause,        eax
        mov     CP0_Count,        eax 
        mov     CP0_Compare,      eax
        ;; Init 'slow' MIPS Regs:
        mov     ecx, 0
_init_reg:
        mov     R(ecx), eax
        inc     ecx
        cmp     ecx, 32
        jb      _init_reg
        xor     ecx, ecx

        mov     Sr(HI),           eax
        mov     Sr(LO),           eax
        mov     Sr(CP0_Index),    eax
        mov     Sr(CP0_EntryHi),  eax
        mov     Sr(CP0_EntryLo0), eax
        mov     Sr(CP0_EntryLo1), eax
        mov     Sr(CP0_Context),  eax
        mov     Sr(CP0_Wired),    eax
        mov     Sr(CP0_Epc),      eax
        mov     Sr(CP0_BadVAddr), eax
        mov     Sr(CP0_ErrorEpc), eax
        Flg_Clear_All                  ; Reset all misc Flags to 0
        bts     CP0_Status, CP0St_ERL  ; Start in kernel mode w/ unmapped useg
        ret
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
_cpu_run:
        xor     eax, eax
        mov     [Ticks], eax
        mov     [R_Ticks], eax
        jmp     _cycle
;-----------------------------------------------------------------------------

;----------------------------
; Start of MIPS CPU Cycle : |
;-----------------------------------------------------------------------------
_wait_cycle:

_cycle:
        ;; Advance tick limit counter, and terminated if exhausted life:
        mov     eax, [Ticks]
        inc     eax
        mov     [Ticks], eax
%if LIMIT_TICKS
        cmp     eax, TICK_LIMIT
        ja      _shutdown
%endif
        inc     CP0_Count            ; Timer: CP0_Count := CP0_Count + 1
        
        ;; Timer Interrupt
        cmp     CP0_Count, CP0_Compare ; Has timer reached limit?
        jne     _cycle_no_irq5       ; If not, do not invoke interrupt
        SetIRQ  5                    ; Timer reached limit, invoke IRQ 5
_cycle_no_irq5:

        ;; Test if Interrupts are Disabled:
        bt      CP0_Status, CP0St_IE ; CF := CP0St_IE
        jnc     _cycle_no_irq        ; If 0, IRQs are disabled, go to no_irq
        test    CP0_Status, (1 << CP0St_ERL) | (1 << CP0St_EXL) ; If ERL/EXL:
        jnz     _cycle_no_irq        ; ... then also interrupts are disabled
        
        GetSlaveIRQ                  ; See if slave threw interrupt
        
        ;; Interrupts are Enabled; handle any pending Interrupt:
        mov     eax, 0xFC00          ; ( 0xFC00    &
        and     eax, CP0_Cause       ;   CP0_Cause &
        and     eax, CP0_Status      ;   CP0_Status )
        jz      _cycle_no_irq        ; == 0 ? Then no pending IRQ, go to no_irq.

        ;; If here, there is a pending interrupt, service it:
_cycle_irq_handler:

        Flg_Off Waiting              ; Clear Waiting Flag
        SetEXC  EXC_Int              ; Set the EXC_Int Exception Code

        ;; Copy InDelaySlot Flag to RunningDelaySlot Flag:
        Flg_Cpy RunningDelaySlot, InDelaySlot
        
        jmp     _Handle_Exception    ; Handle exception and end cycle.

_cycle_no_irq:                       ; If ints disabled or none pending

        Flg_Get Waiting              ; CF := Waiting Flag
        jc      _wait_cycle          ; If Waiting, start next cycle, else:
        
        ;; Copy InDelaySlot Flag to RunningDelaySlot Flag:
        Flg_Cpy RunningDelaySlot, InDelaySlot
        
        ;; Fetch the instruction to execute:
        mov     eax, PC              ; vAddr := PC
        call    _Virt_Read_Word      ; EAX   := MEM[Virt2Phys(PC)]
        mov     r_I, eax             ; Store fetched instruction
        
        ;; Dispatch the current instruction:
        mov     eax, r_I             ; Get the original instruction :

        ;; 'Accelerated NOPs'
        test    eax, eax             ; Is the current instruction null?
        jnz     _not_nop             ; ... if so, skip dispatch entirely!
        jmp     _end_cycle           ; go to tail of this cycle
_not_nop:                            ; If not a NOP:

        shr     eax, 26              ; Get I-field (upper 6 bits of instr) ;
        jmp     [_I_Table + 8 * eax] ; Dispatch on I-field via I-Table.
        ;;; Instruction executes, after which goes to _end_cycle

;-----------------------------------------------------------------------------

;---------------------------------
; Tail End of a MIPS CPU Cycle : |
;-----------------------------------------------------------------------------
_end_cycle:
        Flg_GOF RunningDelaySlot     ; Were we running the delay slot?
        jnc     _end_cycle_was_not_in_delay_slot ; if not, skip, else:
        ;; We WERE running in the delay slot:
_end_cycle_was_in_delay_slot:        ; If we were running the delay slot:
        mov     PC, nPC              ; PC := nPC
        Flg_Off InDelaySlot          ; Clear the InDelaySlot Flag
        jmp     _cycle               ; Go to next cycle without advancing PC
_end_cycle_was_not_in_delay_slot:    ; If we were NOT running the delay slot:
        add     PC,  0x4             ; PC := PC + 4
        ;; Test for Shutdown Condition:
        Flg_Get Shutdown             ; CF := Shutdown Flag
        jc      _shutdown            ; ... if CF, then shut down.
        jmp     _cycle               ; ... otherwise, go to next cycle.

;-----------------------------------------------------------------------------

_Handle_Exception:
        Flg_Off InDelaySlot           ; Clear the InDelaySlot Flag
        bt      CP0_Status, CP0St_EXL ; CF := EXL
        jc     _H_Exc_Off_Is_180      ; If EXL is 1, Offset := 0x180; else:
        ;; EXL is 0:
        mov     Sr(CP0_Epc), PC       ; CP0_Epc := PC
        Flg_Get RunningDelaySlot      ; Are we running the delay slot?
        jnc     _H_Exc_Not_R_Delay    ; If yes, then skip, else:
_H_Exc_R_Delay:                       ; We ARE running the delay slot:
        sub     Sr(CP0_Epc), 0x4      ; CP0_Epc := CP0_Epc - 0x4
        bts     CP0_Cause, 31         ; Set BD Flag in CP0_Cause
        jmp     _H_Exc_Test_TLB       ; Proceed to test for TLBism
_H_Exc_Not_R_Delay:                   ; We are NOT running the delay slot:
        btr     CP0_Cause, 31         ; Clear BD Flag in CP0_Cause
_H_Exc_Test_TLB:
        ;; Test whether this was a TLB Exception:
        GetEXC  eax                   ; EAX := the current exception code
        cmp     eax, EXC_TLBL         ; was it EXC_TLBL ?
        je      __H_Exc_Was_TLB       ; ... if yes, go to H_Exc_Was_TLB
        cmp     eax, EXC_TLBS         ; was it EXC_TLBS ?
        je      __H_Exc_Was_TLB       ; ... if yes, go to H_Exc_Was_TLB
        ;; This was NOT a TLB Exception:
        cmp     eax, EXC_Int          ; was code EXC_Int ?
        jne     _H_Exc_Off_Is_180     ; if not, Offset := 0x180
        bt      CP0_Cause, 23         ; Was CP0_Cause bit 23 zero?
        jnc     _H_Exc_Off_Is_180     ; if was zero, Offset := 0x180
        ;; If CP0_Cause bit 23 != 0: Offset := 0x200
        mov     eax, 0x200            ; Offset := 0x200
        jmp     _H_Exc_Have_Offset    ; Go to H_Exc_Have_Offset
__H_Exc_Was_TLB:                      ; This WAS a TLB Exception:
        Flg_Get ExcWasTLBNoMatch      ; CF := 'TLB No Match'
        ;; TODO: in 'cmips', this case was reversed? why?
        ;; For now, we'll do likewise:
        jnc     _H_Exc_Off_Is_180     ; ... if 0, Offset := 0x180
        ;; Else: Offset := 0x0
        xor     eax, eax              ; Clear EAX (Offset := 0)
        jmp     _H_Exc_Have_Offset    ; Finish up
_H_Exc_Off_Is_180:                    ; Offset := 0x180
        mov     eax, 0x180            ; Set the Offset
_H_Exc_Have_Offset:                   ; We finished calculating the Offset:
        bts     CP0_Status, CP0St_EXL ; Set the EXL Flag
        mov     PC,  eax              ; PC := Offset (eax)
        mov     eax, 0x80000000       ; Base that will be used if BEV=0
        mov     ebx, 0xBFC00200       ; Base that will be used if BEV=1
        bt      CP0_Status, CP0St_BEV ; CF := the BEV Flag
        cmovc   eax, ebx              ; If CF = 1, use the BEV=1 Base
        add     PC,  eax              ; PC := PC + Base
        ; Flg_Off Exception             ; Clear the Exception Flag
        ;; Done handling exception
        jmp     _cycle                ; Start next cycle.
;-----------------------------------------------------------------------------

;-------------------------
; Invalid Instructions : |
;-------------------------
align GRAIN, db 0x90
_bad:
        SetEXC  EXC_RI               ; Set the EXC_RI Exception
        jmp     _Handle_Exception    ; Go straight to exception handler.
;-----------------------------------------------------------------------------

;------------------------
; R-Type Instructions : |
;-----------------------------------------------------------------------------
; Eat 000000?????????????????????????? (R-Type) Instructions:
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_i_000000:
        mov     eax, r_I             ; Get the original instruction :
        and     eax, 0x3F            ; Get funct field, (lower 6 bits) ;
        jmp     [_R_Table + 8 * eax] ; Dispatch on funct-field via R-Table.
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Eat 000001?????QQQQQ???????????????? Branch Instructions :
; i.e. bltz, bgez, bltzl, bgezl, bltzal, bgezal
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_i_000001:
        mov     eax, r_I             ; Get the original instruction :
        shr     eax, 16              ; Get the 5-bit field QQQQQ
        and     eax, 0x1F            ; which distinguishes these;
        jmp     [_B_Table + 8 * eax] ; Dispatch via B-Table.
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Eat 010000?????????????????????????? Misc. Instructions :
; mfc0, mtc0, tlbwi, tlbwr, tlbp, eret, wait
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_i_010000:
        mov     eax, r_I             ; Get the original instruction :
        shr     eax, 5               ; Get the top 27 bits
        cmp     eax, 0x2100000       ; Equal 010000100000000000000000000..?
        je      _tlbwi_tlbwr_tlbp_eret ;  010000100000000000000000000QQQQQ
        ;; At this point, we have either mfc0, mtc0, wait, or a bad instr:
        shr     eax, 16              ; We already shifted by 5, so now 21:
        cmp     eax, 0x200           ; ... are they equal to 0b01000000000 ?
        je      _m_mfc0              ; mfc0 (01000000000?????????????????????)
        cmp     eax, 0x204           ; ... are they equal to 0b01000000100 ?
        je      _m_mtc0              ; mtc0 (01000000100?????????????????????)
        ;; Now determine if it is 'wait' (0100001???????????????????100000) :
        mov     eax, r_I             ; Get the original instruction :
        and     eax, 0xFE00003F      ; Get top 7 bits and bottom 6 bits only
        cmp     eax, 0x42000020      ; Top must be 0b0100001, bottom 0b100000
        je      _m_wait              ; wait (0100001???????????????????100000)
        ;; Now, if it wasn't the 'wait' instruction, we must then have a turd:
        jmp     _bad                 ; If we're here : undefined inst.
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Eat 010000100000000000000000000QQQQQ Instructions (via _i_010000) :
; tlbwi, tlbwr, tlbp, eret
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_tlbwi_tlbwr_tlbp_eret:
        mov     eax, r_I             ; Get the original instruction :
        and     eax, 0x1F            ; Get the the 5-bit field QQQQQ from it
        jmp     [_M_Table + 8 * eax] ; Dispatch via M-Table.
;-----------------------------------------------------------------------------

;---------------------
; MIPS Instructions: |
;---------------------

;-----------------------------------------------------------------------------
; SLL -- Shift left logical.
; Shifts a register value left by the shift amount listed in the instruction
; and places the result in a third register. Zeroes are shifted in.
; Operation: $d = $t << h; advance_pc(4);
; Syntax:    sll $d, $t, h
; Encoding:  0000 00-- ---t tttt dddd dhhh hh00 0000
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_r_sll:
        RType_D_T_Shamt              ; load rS, rT, Shamt Fields
        mov     TMP,   R(rT)         ; TMP := Regs[rT]
        shl     TMP,   cl            ; TMP := TMP << CL (Shamt is in ECX)
        Wr_Reg  rD,    TMP           ; Regs[rD] := TMP (result)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; SRL -- Shift right logical.
; Shifts a register value right by the shift amount (shamt) and places the
; value in the destination register. Zeroes are shifted in.
; Operation: $d = $t >> h; advance_pc(4);
; Syntax:    srl $d, $t, h
; Encoding:  0000 00-- ---t tttt dddd dhhh hh00 0010
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_r_srl:
        RType_D_T_Shamt              ; load rS, rT, Shamt Fields
        mov     TMP,   R(rT)         ; TMP := Regs[rT]
        shr     TMP,   cl            ; TMP := TMP >> CL (Shamt is in ECX)
        Wr_Reg  rD,    TMP           ; Regs[rD] := TMP (result)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; SRA -- Shift right arithmetic.
; Shifts a register value right by the shift amount (shamt) and places the
; value in the destination register. The sign bit is shifted in.
; Operation: $d = $t >> h; advance_pc(4);
; Syntax:    sra $d, $t, h
; Encoding:  0000 00-- ---t tttt dddd dhhh hh00 0011
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_r_sra:
        RType_D_T_Shamt              ; load rS, rT, Shamt Fields
        mov     TMP,   R(rT)         ; TMP := Regs[rT]
        sar     TMP,   cl            ; TMP := TMP >>(sgn) CL (Shamt is in ECX)
        Wr_Reg  rD,    TMP           ; Regs[rD] := TMP (result)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; SLLV -- Shift left logical variable.
; Shifts a register value left by the value in a second register and places
; the result in a third register. Zeroes are shifted in.
; Operation: $d = $t << $s; advance_pc(4);
; Syntax:    sllv $d, $t, $s
; Encoding:  0000 00ss ssst tttt dddd d--- --00 0100
;-----------------------------------------------------------------------------
; TODO: Could avoid clobbering ecx?
align GRAIN, db 0x90
_r_sllv:
        RType                        ; load rD, rS, rT Fields
        mov     ecx,   R(rS)         ; Now CL is the shift amount
        mov     TMP,   R(rT)         ; TMP := Regs[rT]
        shl     TMP,   cl            ; TMP := TMP << CL
        Wr_Reg  rD,    TMP           ; Regs[rD] := TMP (result)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; SRLV -- Shift right logical variable.
; Shifts a register value right by the amount specified in $s and places the
; value in the destination register. Zeroes are shifted in.
; Operation: $d = $t >> $s; advance_pc(4);
; Syntax:    srlv $d, $t, $s
; Encoding:  0000 00ss ssst tttt dddd d000 0000 0110
;-----------------------------------------------------------------------------
; TODO: Could avoid clobbering ecx?
align GRAIN, db 0x90
_r_srlv:
        RType                        ; load rD, rS, rT Fields
        mov     ecx,   R(rS)         ; Now CL is the shift amount
        mov     TMP,   R(rT)         ; TMP := Regs[rT]
        shr     TMP,   cl            ; TMP := TMP >> CL
        Wr_Reg  rD,    TMP           ; Regs[rD] := TMP (result)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; SRAV - Shift Word Right Arithmetic Variable.
; Execute an arithmetic right-shift of a word by a variable number of bits.
; The contents of the low-order 32-bit word of rT are shifted right,
; duplicating the sign-bit (bit 31) in the emptied bits; the word result is
; placed in rD. The bit-shift amount is specified by the low-order 5 bits
; of rS.
; Operation: $d = $t >> $s; advance_pc(4);
; Syntax:    srav $d, $t, $s
; Encoding:  0000 00ss ssst tttt dddd d000 0000 0111
;-----------------------------------------------------------------------------
; TODO: Could avoid clobbering ecx?
align GRAIN, db 0x90
_r_srav:
        RType                        ; load rD, rS, rT Fields
        mov     ecx,   R(rS)         ; Now CL is the shift amount
        mov     TMP,   R(rT)         ; TMP := Regs[rT]
        sar     TMP,   cl            ; TMP := TMP >> CL (extends sign bit)
        Wr_Reg  rD,    TMP           ; Regs[rD] := TMP (result)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; JR -- Jump register.
; Jump to the address contained in register $s
; Operation: PC = nPC; nPC = $s;
; Syntax:    jr $s
; Encoding:  0000 00ss sss0 0000 0000 0000 0000 1000
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_r_jr:
        RType_S_Only                 ; Load rS Field
        mov     nPC,   R(rS)         ; nPC := Regs[rS]        
        Flg_On  InDelaySlot          ; Set 'Delay Slot' Flag.
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; JALR - Jump and Link Register.
; Execute a procedure call to an instruction address in a register.
; Place the return address link in rD. The return link is the address of the
; second instruction following the branch, where execution continues after a
; procedure call.  Jump to the effective target address in rS. Execute the
; instruction that follows the jump, in the branch delay slot, before
; executing the jump itself.
; Operation: $d = PC + 8; PC = nPC; nPC = $s
; Syntax:    jalr $s
; Encoding:  0000 00ss sss0 0000 dddd 0000 0000 1001
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_r_jalr:
        RType_S_D_Only               ; Load rD and rS Fields
        mov     nPC,     R(rS)       ; nPC      := Regs[rS]
        mov     TMP,     PC          ; TMP      := PC
        add     TMP,     0x8         ; TMP      := TMP + 8
        Wr_Reg  rD,      TMP         ; Regs[rD] := TMP (result)
        Flg_On  InDelaySlot          ; Set 'Delay Slot' Flag.
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; MOVZ - Move Conditional on Zero.
; Conditionally move a register after testing a register value.
; If the value in rT is equal to zero, then the contents of rS are placed
; into rD.
; Operation: if $t = 0 then $d = $s
; Syntax:    movz $d, $s, $t
; Encoding:  0000 00ss ssst tttt dddd d000 0000 1010
;-----------------------------------------------------------------------------
; TODO: measure if the cmov is actually faster than a branch here !
; TODO: untested
align GRAIN, db 0x90
_r_movz:
        ; UNDONE_INST
        RType                        ; load rD, rS, rT Fields
        mov     TMP,   R(rT)         ; TMP := Regs[rT]
        test    TMP,   TMP           ; Set Z flag if TMP = 0
        mov     TMP,   R(rD)         ; First, move Regs[rD] to TMP
        cmovz   TMP,   R(rS)         ; If Z flag set, overwrite with Regs[rS]
        Wr_Reg  rD,    TMP           ; Regs[rD] := TMP (result)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; MOVN - Move Conditional on Not Zero.
; Conditionally move a register after testing a register value.
; If the value in rT is NOT equal to zero, then the contents of rS are placed
; into rD.
; Operation: if $t != 0 then $d = $s
; Syntax:    movn $d, $s, $t
; Encoding:  0000 00ss ssst tttt dddd d000 0000 1011
;-----------------------------------------------------------------------------
; TODO: measure if the cmov is actually faster than a branch here !
; TODO: untested
align GRAIN, db 0x90
_r_movn:
        ; UNDONE_INST
        RType                        ; load rD, rS, rT Fields
        mov     TMP,   R(rT)         ; TMP := Regs[rT]
        test    TMP,   TMP           ; Set Z flag if TMP = 0
        mov     TMP,   R(rD)         ; First, move Regs[rD] to TMP
        cmovnz  TMP,   R(rS)         ; If Z not set, overwrite with Regs[rS]
        Wr_Reg  rD,    TMP           ; Regs[rD] := TMP (result)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; SYSCALL -- System call.
; Generates a software interrupt.
; Operation: advance_pc(4);
; Syntax:    syscall
; Encoding:  0000 00-- ---- ---- ---- ---- --00 1100
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_r_syscall:
        ; no fields
        SetEXC  EXC_SYS              ; Set the EXC_SYS Exception
        jmp     _Handle_Exception    ; Go straight to exception handler.
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; SYNC - Synchronize Shared Memory.
; Operation: This is a NOP in this emulator.
; Syntax:    sync
; Encoding:  0000 0000 0000 0000 0000 0000 0000 1111
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_r_sync:
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; MFHI -- Move from HI.
; The contents of special register HI are moved to the specified register.
; Operation: $d = $HI; advance_pc(4);
; Syntax:    mfhi $d
; Encoding:  0000 0000 0000 0000 dddd d000 0001 0000
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_r_mfhi:
        RType_D_Only                 ; Load rD Field
        mov     TMP,   Sr(HI)        ; TMP      := Sr(HI)
        Wr_Reg  rD,    TMP           ; Regs[rD] := TMP (result)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; MTHI - Move to HI Register.
; The contents of rS are loaded to the special register HI.
; Operation: $HI = $s
; Syntax:    mthi $s
; Encoding:  0000 00ss sss0 0000 0000 0000 0001 0001
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_r_mthi:
        RType_S_Only                 ; Load rS Field
        mov     TMP,    R(rS)        ; TMP := Regs[rS]
        mov     Sr(HI), TMP          ; HI  := TMP
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; MFLO -- Move from LO.
; The contents of register LO are moved to the specified register.
; Operation: $d = $LO; advance_pc(4);
; Syntax:    mflo $d
; Encoding:  0000 0000 0000 0000 dddd d000 0001 0010
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_r_mflo:
        RType_D_Only                 ; Load rD Field
        mov     TMP,   Sr(LO)        ; TMP      := Sr(LO)
        Wr_Reg  rD,    TMP           ; Regs[rD] := TMP (result)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; MTLO - Move to LO.
; The contents of rS are loaded to the special register LO.
; Operation: $LO = $s
; Syntax:    mtlo $s
; Encoding:  0000 00ss sss0 0000 0000 0000 0001 0011
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_r_mtlo:
        RType_S_Only                 ; Load rS Field
        mov     TMP,    R(rS)        ; TMP := Regs[rS]
        mov     Sr(LO), TMP          ; LO  := TMP
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; MULT -- Multiply signed.
; Multiplies $s by $t and stores the result in $HI:$LO.
; Operation: $HI:$LO = $s * $t; advance_pc(4);
; Syntax:    mult $s, $t
; Encoding:  0000 00ss ssst tttt 0000 0000 0001 1000
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_r_mult:
        RType_S_T_Only
        mov     eax,    R(rT)        ; eax := Regs[rT]
        imul    R(rS)                ; edx:eax := Regs[rS] * eax (signed)
        mov     Sr(LO), eax          ; LO := Bottom 32 bits of product
        mov     Sr(HI), edx          ; HI := Top 32 bits of product
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; MULTU -- Multiply unsigned.
; Multiplies $s by $t and stores the result in $HI:$LO.
; Operation: $HI:$LO = $s * $t; advance_pc(4);
; Syntax:    multu $s, $t
; Encoding:  0000 00ss ssst tttt 0000 0000 0001 1001
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_r_multu:
        RType_S_T_Only               ; Load rS and rT Fields
        mov     eax,    R(rT)        ; eax := Regs[rT]
        mul     R(rS)                ; edx:eax := Regs[rS] * eax (unsigned)
        mov     Sr(LO), eax          ; LO := Bottom 32 bits of product
        mov     Sr(HI), edx          ; HI := Top 32 bits of product
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; DIV -- Divide signed.
; Divides $s by $t and stores the quotient in $LO and the remainder in $HI
; Operation: $LO = $s / $t; $HI = $s % $t; advance_pc(4);
; Syntax:    div $s, $t
; Encoding:  0000 00ss ssst tttt 0000 0000 0001 1010
;-----------------------------------------------------------------------------
; TODO: detect div0 and other special cases
align GRAIN, db 0x90
_r_div:
        RType_S_T_Only               ; Load rS and rT Fields
        mov     eax,    R(rS)        ; eax := Regs[rS]
        cdq                          ; extend sign of eax into edx
        idiv    R(rT)                ; edx:eax / Regs[rT]; edx := r, eax := q
        mov     Sr(LO), eax          ; LO := edx:eax /(signed) ecx (quotient)
        mov     Sr(HI), edx          ; HI := edx:eax %(signed) ecx (remainder)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; DIVU -- Divide unsigned.
; Divides $s by $t and stores the quotient in $LO and the remainder in $HI
; Operation: $LO = $s / $t; $HI = $s % $t; advance_pc(4);
; Syntax:    divu $s, $t
; Encoding:  0000 00ss ssst tttt 0000 0000 0001 1011
;-----------------------------------------------------------------------------
; TODO: detect div0 and other special cases
align GRAIN, db 0x90
_r_divu:
        RType_S_T_Only               ; Load rS and rT Fields
        xor     edx,    edx          ; edx := 0
        mov     eax,    R(rS)        ; eax := Regs[rS]
        div     R(rT)                ; edx:eax / Regs[rT]; edx := r, eax := q
        mov     Sr(LO), eax          ; LO := edx:eax / ecx (quotient)
        mov     Sr(HI), edx          ; HI := edx:eax % ecx (remainder)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; ADD – Add (with overflow).
; Adds two registers and stores the result in a register
; Operation: $d = $s + $t; advance_pc(4);
; Syntax:    add $d, $s, $t
; Encoding:  0000 00ss ssst tttt dddd d000 0010 0000
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_r_add:
        RType                        ; load rD, rS, rT Fields
        mov     TMP,   R(rS)         ; TMP      := Regs[rS]
        add     TMP,   R(rT)         ; TMP      := Regs[rS] + Regs[rT]
        ; TODO: detect/trap overflow
        Wr_Reg  rD,    TMP           ; Regs[rD] := TMP (result)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; ADDU -- Add unsigned (no overflow).
; Adds two registers and stores the result in a register
; Operation: $d = $s + $t; advance_pc(4);
; Syntax:    addu $d, $s, $t
; Encoding:  0000 00ss ssst tttt dddd d000 0010 0001
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_r_addu:
        RType                        ; load rD, rS, rT Fields
        mov     TMP,   R(rS)         ; TMP      := Regs[rS]
        add     TMP,   R(rT)         ; TMP      := Regs[rS] + Regs[rT]
        Wr_Reg  rD,    TMP           ; Regs[rD] := TMP (result)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; SUB -- Subtract.
; Subtracts two registers and stores the result in a register
; Operation: $d = $s - $t; advance_pc(4);
; Syntax:    sub $d, $s, $t
; Encoding:  0000 00ss ssst tttt dddd d000 0010 0010
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_r_sub:
        RType                        ; load rD, rS, rT Fields
        mov     TMP,   R(rS)         ; TMP      := Regs[rS]
        sub     TMP,   R(rT)         ; TMP      := Regs[rS] - Regs[rT]
        ; TODO: detect and trap overflow
        Wr_Reg  rD,    TMP           ; Regs[rD] := TMP (result)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; SUBU -- Subtract unsigned.
; Subtracts two registers and stores the result in a register
; Operation: $d = $s - $t; advance_pc(4);
; Syntax:    subu $d, $s, $t
; Encoding:  0000 00ss ssst tttt dddd d000 0010 0011
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_r_subu:
        RType                        ; load rD, rS, rT Fields
        mov     TMP,   R(rS)         ; TMP      := Regs[rS]
        sub     TMP,   R(rT)         ; TMP      := Regs[rS] - Regs[rT]
        Wr_Reg  rD,    TMP           ; Regs[rD] := TMP (result)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; AND -- Bitwise AND.
; Bitwise ANDs two registers and stores the result in a register
; Operation: $d = $s & $t; advance_pc(4);
; Syntax:    and $d, $s, $t
; Encoding:  0000 00ss ssst tttt dddd d000 0010 0100
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_r_and:
        RType                        ; load rD, rS, rT Fields
        mov     TMP,   R(rS)         ; TMP      := Regs[rS]
        and     TMP,   R(rT)         ; TMP      := TMP & Regs[rT]
        Wr_Reg  rD,    TMP           ; Regs[rD] := TMP (result)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; OR -- Bitwise OR.
; Bitwise logical ORs two registers and stores the result in a register
; Operation: $d = $s | $t; advance_pc(4);
; Syntax:    or $d, $s, $t
; Encoding:  0000 00ss ssst tttt dddd d000 0010 0101
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_r_or:
        RType                        ; load rD, rS, rT Fields
        mov     TMP,   R(rS)         ; TMP      := Regs[rS]
        or      TMP,   R(rT)         ; TMP      := TMP | Regs[rT]
        Wr_Reg  rD,    TMP           ; Regs[rD] := TMP (result)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; XOR -- Bitwise Exclusive OR.
; Exclusive ORs two registers and stores the result in a register
; Operation: $d = $s ^ $t; advance_pc(4);
; Syntax:    xor $d, $s, $t
; Encoding:  0000 00ss ssst tttt dddd d--- --10 0110
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_r_xor:
        RType                        ; load rD, rS, rT Fields
        mov     TMP,   R(rS)         ; TMP      := Regs[rS]
        xor     TMP,   R(rT)         ; TMP      := TMP ^ Regs[rT]
        Wr_Reg  rD,    TMP           ; Regs[rD] := TMP (result)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; NOR - Not OR.
; Do a bitwise logical NOT OR.
; The contents of rS are combined with the contents of rT in a bitwise logical
; NOR operation. The result is placed into rD.
; Operation: $d = $s NOR $t
; Syntax:    nor $d, $s, $t
; Encoding:  0000 00ss ssst tttt dddd d000 0010 0111
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_r_nor:
        RType                        ; load rD, rS, rT Fields
        mov     TMP,   R(rS)         ; TMP      := Regs[rS]
        or      TMP,   R(rT)         ; TMP      := TMP | Regs[rT]
        not     TMP                  ; TMP      := ~TMP
        Wr_Reg  rD,    TMP           ; Regs[rD] := TMP (result)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; SLT -- Set on less than (signed).
; If $s is less than $t, $d is set to one. It gets zero otherwise.
; Operation: if $s < $t $d = 1; advance_pc(4); else $d = 0; advance_pc(4);
; Syntax:    slt $d, $s, $t
; Encoding:  0000 00ss ssst tttt dddd d000 0010 1010
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_r_slt:
        RType                        ; load rD, rS, rT Fields
        mov     TMP,   R(rS)         ; TMP      := Regs[rS]
        cmp     TMP,   R(rT)         ; CMP(TMP, Regs[rT])
        ; We do not need rS, so can reuse it here:
        setl    rS_LowByte           ; 1 if R(rS) <(signed) R(rT), else 0
        and     rS,    0x1           ; Only want the bit that we have set
        Wr_Reg  rD,    rS            ; Regs[rD] := result
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; SLTU -- Set on less than unsigned.
; If $s is less than $t, $d is set to one. It gets zero otherwise.
; Operation: if $s < $t $d = 1; advance_pc(4); else $d = 0; advance_pc(4);
; Syntax:    sltu $d, $s, $t
; Encoding:  0000 00ss ssst tttt dddd d000 0010 1011
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_r_sltu:
        RType                        ; load rD, rS, rT Fields
        mov     TMP,   R(rS)         ; TMP      := Regs[rS]
        cmp     TMP,   R(rT)         ; CMP(TMP, Regs[rT])
        ; We do not need rS, so can reuse it here:
        setb    rS_LowByte           ; 1 if R(rS) <(signed) R(rT), else 0
        and     rS,    0x1           ; Only want the bit that we have set
        Wr_Reg  rD,    rS            ; Regs[rD] := result
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; TNE - Trap if Not Equal.
; Compare registers and do a conditional trap.
; Compare the contents of rS and rT as signed integers;
; if rS is not equal to rT, then take a Trap exception.
; The contents of the code field are ignored by hardware and may be used to
; encode information for system software. To retrieve the information,
; system software must load the instruction word from memory.
; Operation: if $s != $t then Trap
; Syntax:    TNE $s, $t
; Encoding:  0000 00ss ssst tttt cccc cccc cc11 0110
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_r_tne:
        RType_S_T_Only               ; Load rS and rT Fields
        mov     TMP,   R(rS)         ; TMP      := Regs[rS]
        cmp     TMP,   R(rT)         ; CMP(TMP, Regs[rT])
        je      _r_tne_eql
        SetEXC  EXC_Tr               ; Spring the Trap Exception.
        jmp     _Handle_Exception    ; Go straight to exception handler.
_r_tne_eql:
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; B-Type Instructions :
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; BLTZ -- Branch on less than zero.
; Branches if the register is less than zero
; Operation: if $s < 0 advance_pc(offset << 2)); else advance_pc(4);
; Syntax:    bltz $s, offset
; Encoding:  0000 01ss sss0 0000 iiii iiii iiii iiii
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_b_bltz:
        IType_I_S_Only               ; load rS and Imm Fields
        mov     nPC,     PC          ; nPC      := PC
        add     nPC,     0x4         ; nPC      := nPC + 4
        SX18_4N Imm                  ; Sign Extend Imm * 4
        mov     TMP,     0x4         ; TMP      := 4
        cmp     R(rS),   0           ; If Regs[rS] < 0:
        cmovl   TMP,     Imm         ; ... then TMP := Imm
        add     nPC,     TMP         ; nPC      := nPC + TMP
        Flg_On  InDelaySlot          ; Set 'Delay Slot' Flag.
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; BGEZ -- Branch on greater than or equal to zero.
; Branches if the register is greater than or equal to zero
; Operation: if $s >= 0 advance_pc(offset << 2)); else advance_pc(4);
; Syntax:    bgez $s, offset
; Encoding:  0000 01ss sss0 0001 iiii iiii iiii iiii
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_b_bgez:
        IType_I_S_Only               ; load rS and Imm Fields
        mov     nPC,     PC          ; nPC      := PC
        add     nPC,     0x4         ; nPC      := nPC + 4
        SX18_4N Imm                  ; Sign Extend Imm * 4
        mov     TMP,     0x4         ; TMP      := 4
        cmp     R(rS),   0           ; If Regs[rS] >= 0:
        cmovge  TMP,     Imm         ; ... then TMP := Imm
        add     nPC,     TMP         ; nPC      := nPC + TMP
        Flg_On  InDelaySlot          ; Set 'Delay Slot' Flag.
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; BLTZL - Branch on Less Than Zero Likely.
; Test a register, then do a PC-relative conditional branch;
; execute the delay slot only if the branch is taken.
; An 18-bit signed offset (the 16-bit offset field shifted left 2 bits) is
; added to the address of the instruction following the branch (not the branch
; itself), in the branch delay slot, to form a PC-relative effective target
; address. If the contents of rS are less than zero (sign bit is 1), branch to
; the effective target address after the instruction in the delay slot is
; executed. If the branch is not taken, the instruction in the delay slot is
; not executed.
; Operation: if rS < 0 then PC = PC + sign_extend(offset)
;            else: nullify_current_instruction
; Syntax:    bltzl $s, offset
; Encoding:  0000 01ss sss0 0010 iiii iiii iiii iiii
;-----------------------------------------------------------------------------
; TODO: branch-free variant?
align GRAIN, db 0x90
_b_bltzl:
        IType_S_Only                 ; load only rS Field just now
        cmp     R(rS),   0           ; CMP(Regs[rS], 0)
        jnl    _i_bltzl_not_taken    ; If !(Regs[rS] < 0) then NotTaken
        ; Taken:
        IType_I_Only                 ; load only Imm Field just now
        SX18_4N Imm                  ; Sign Extend Imm * 4
        mov     nPC,     PC          ; nPC      := PC
        add     nPC,     0x4         ; nPC      := nPC + 4
        add     nPC,     Imm         ; nPC      := nPC + Offset
        Flg_On  InDelaySlot          ; Set 'Delay Slot' Flag.
        jmp     _end_cycle
        ; Not Taken:
_i_bltzl_not_taken:
        add     PC,      0x4         ; PC       := PC + 4
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; BGEZL - Branch on Greater Than or Equal to Zero Likely.
; Test a register, then do a PC-relative conditional branch;
; execute the delay slot only if the branch is taken.
; An 18-bit signed offset (the 16-bit offset field shifted left 2 bits) is
; added to the address of the instruction following the branch (not the branch
; itself), in the branch delay slot, to form a PC-relative effective target
; address. If the contents of rS are greater than or equal to zero
; (sign bit is 0), branch to the effective target address after the
; instruction in the delay slot is executed. If the branch is not taken, the
; instruction in the delay slot is not executed.
; Operation: if rS >= 0 then PC = PC + sign_extend(offset)
;            else: nullify_current_instruction
; Syntax:    bgezl $s, offset
; Encoding:  0000 01ss sss0 0011 iiii iiii iiii iiii
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_b_bgezl:
        IType_S_Only                 ; Load only rS Field just now
        cmp     R(rS),   0           ; CMP(Regs[rS], 0)
        jnge    _i_bgezl_not_taken   ; If !(Regs[rS] >= 0) then NotTaken
        ; Taken:
        IType_I_Only                 ; Load only Imm Field just now
        SX18_4N Imm                  ; Sign Extend Imm * 4
        mov     nPC,     PC          ; nPC      := PC
        add     nPC,     0x4         ; nPC      := nPC + 4
        add     nPC,     Imm         ; nPC      := nPC + Offset
        Flg_On  InDelaySlot          ; Set 'Delay Slot' Flag.
        jmp     _end_cycle
        ; Not Taken:
_i_bgezl_not_taken:
        add     PC,      0x4         ; PC       := PC + 4
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; BLTZAL -- Branch on less than zero and link.
; Branches if the register is less than zero and saves the return address
; in $31
; Operation: if $s < 0 $31 = PC + 8 (or nPC + 4); advance_pc(offset << 2));
;            else advance_pc(4);
; Syntax:    bltzal $s, offset
; Encoding:  0000 01ss sss1 0000 iiii iiii iiii iiii
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_b_bltzal:
        IType_I_S_Only               ; Load rS and Imm Fields
        mov     nPC,       PC        ; nPC      := PC
        add     nPC,       0x4       ; nPC      := nPC + 4
        mov     TMP,       nPC       ; TMP      := nPC
        add     TMP,       0x4       ; TMP      := TMP + 4
        mov     R(31),     TMP       ; Regs[31] := TMP
        SX18_4N Imm                  ; Sign Extend Imm * 4
        mov     TMP,       0x4       ; TMP      := 4
        cmp     R(rS),     0         ; If Regs[rS] < 0:
        cmovl   TMP,       Imm       ; ... then TMP := Imm
        add     nPC,       TMP       ; nPC      := nPC + TMP
        Flg_On  InDelaySlot          ; Set 'Delay Slot' Flag.
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; BGEZAL -- Branch on greater than or equal to zero and link.
; Branches if the register is greater than or equal to zero and saves the
; return address in $31
; Operation: if $s >= 0 $31 = PC + 8 (or nPC + 4); advance_pc(offset << 2));
;            else advance_pc(4);
; Syntax:    bgezal $s, offset
; Encoding:  0000 01ss sss1 0001 iiii iiii iiii iiii
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_b_bgezal:
        IType_I_S_Only               ; Load rS and Imm Fields
        mov     nPC,       PC        ; nPC      := PC
        add     nPC,       0x4       ; nPC      := nPC + 4
        mov     TMP,       nPC       ; TMP      := nPC
        add     TMP,       0x4       ; TMP      := TMP + 4
        mov     R(31),     TMP       ; Regs[31] := TMP
        SX18_4N Imm                  ; Sign Extend Imm * 4
        mov     TMP,       0x4       ; TMP      := 4
        cmp     R(rS),     0         ; If Regs[rS] < 0:
        cmovnl  TMP,       Imm       ; ... then TMP := Imm
        add     nPC,       TMP       ; nPC      := nPC + TMP
        Flg_On  InDelaySlot          ; Set 'Delay Slot' Flag.
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;------------------------
; I-Type Instructions : |
;------------------------

;-----------------------------------------------------------------------------
; J -- Jump.
; Jumps to the calculated address.
; Operation: PC = nPC; nPC = (PC & 0xF0000000) | (target << 2);
; Syntax:    j target
; Encoding:  0000 10ii iiii iiii iiii iiii iiii iiii
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_i_j:
        JType                        ; load J Field (ii..iii << 2)
        mov     nPC, PC              ; nPC := PC
        and     nPC, 0xF0000000      ; keep only top 4 bits of nPC, zero rest
        or      nPC, JTarg           ; nPC := nPC | ( target << 2 )
        Flg_On  InDelaySlot          ; Set 'Delay Slot' Flag.
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; JAL -- Jump and link.
; Jumps to the calculated address and stores the return address in $31.
; Operation: $31 = PC + 8 (or nPC + 4);
;            PC = nPC;
;            nPC = (PC & 0xF0000000) | (target << 2);
; Syntax:    jal target
; Encoding:  0000 11ii iiii iiii iiii iiii iiii iiii
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_i_jal:
        JType                         ; load J Field (ii..iii << 2)
        mov     TMP,       PC         ; TMP      := PC
        add     TMP,       0x8        ; TMP      := TMP + 8
        mov     R(31),     TMP        ; Regs[31] := TMP
        sub     TMP,       0x8        ; TMP      := TMP - 8
        and     TMP,       0xF0000000 ; keep only top 4 bits of TMP, zero rest
        or      TMP,       JTarg      ; TMP      := TMP | ( target << 2 )
        mov     nPC,       TMP        ; nPC      := TMP
        Flg_On  InDelaySlot           ; Set 'Delay Slot' Flag.
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; BEQ -- Branch on equal.
; Branches if the two registers are equal.
; Operation: if $s == $t advance_pc(offset << 2));
;            else advance_pc(4);
; Syntax:    beq $s, $t, offset
; Encoding:  0001 00ss ssst tttt iiii iiii iiii iiii
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_i_beq:
        IType                        ; Load Imm, rS, rT Fields
        mov     nPC,     PC          ; nPC      := PC
        add     nPC,     0x4         ; nPC      := nPC + 4
        SX18_4N Imm                  ; Sign Extend Imm * 4
        mov     TMP,     R(rS)       ; TMP      := Regs[rS]
        cmp     TMP,     R(rT)       ; CMP(TMP, Regs[rT])
        mov     TMP,     0x4         ; TMP      := 4
        cmove   TMP,     Imm         ; If Regs[rS] = Regs[rT], TMP := Imm
        add     nPC,     TMP         ; nPC      := nPC + TMP
        Flg_On  InDelaySlot          ; Set 'Delay Slot' Flag.
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; BNE -- Branch on not equal.
; Branches if the two registers are not equal.
; Operation: if $s != $t advance_pc(offset << 2));
;            else advance_pc(4);
; Syntax:    bne $s, $t, offset
; Encoding:  0001 01ss ssst tttt iiii iiii iiii iiii
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_i_bne:
        IType                        ; Load Imm, rS, rT Fields
        mov     nPC,     PC          ; nPC      := PC
        add     nPC,     0x4         ; nPC      := nPC + 4
        SX18_4N Imm                  ; Sign Extend Imm * 4
        mov     TMP,     R(rS)       ; TMP      := Regs[rS]
        cmp     TMP,     R(rT)       ; CMP(TMP, Regs[rT])
        mov     TMP,     0x4         ; TMP      := 4
        cmovne  TMP,     Imm         ; If Regs[rS] != Regs[rT], TMP := Imm
        add     nPC,     TMP         ; nPC      := nPC + TMP
        Flg_On  InDelaySlot          ; Set 'Delay Slot' Flag.
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; BLEZ -- Branch on less than or equal to zero.
; Branches if the register is less than or equal to zero
; Operation: if $s <= 0 advance_pc(offset << 2));
;            else advance_pc(4);
; Syntax:    blez $s, offset
; Encoding:  0001 10ss sss0 0000 iiii iiii iiii iiii
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_i_blez:
        IType_I_S_Only               ; load rS and Imm Fields
        mov     nPC,     PC          ; nPC      := PC
        add     nPC,     0x4         ; nPC      := nPC + 4
        SX18_4N Imm                  ; Sign Extend Imm * 4
        mov     TMP,     0x4         ; TMP      := 4
        cmp     R(rS),   0           ; If Regs[rS] <= 0:
        cmovle  TMP,     Imm         ; ... then TMP := Imm
        add     nPC,     TMP         ; nPC      := nPC + TMP
        Flg_On  InDelaySlot          ; Set 'Delay Slot' Flag.
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; BGTZ -- Branch on greater than zero.
; Branches if the register is greater than zero
; Operation: if $s > 0 advance_pc(offset << 2));
;            else advance_pc(4);
; Syntax:    bgtz $s, offset
; Encoding:  0001 11ss sss0 0000 iiii iiii iiii iiii
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_i_bgtz:
        IType_I_S_Only               ; load rS and Imm Fields
        mov     nPC,     PC          ; nPC      := PC
        add     nPC,     0x4         ; nPC      := nPC + 4
        SX18_4N Imm                  ; Sign Extend Imm * 4
        mov     TMP,     0x4         ; TMP      := 4
        cmp     R(rS),   0           ; If Regs[rS] > 0:
        cmovg   TMP,     Imm         ; ... then TMP := Imm
        add     nPC,     TMP         ; nPC      := nPC + TMP
        Flg_On  InDelaySlot          ; Set 'Delay Slot' Flag.
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; ADDI -- Add immediate (with overflow).
; Adds a register and a sign-extended immediate value and stores the
; result in a register
; Operation: $t = $s + imm; advance_pc(4);
; Syntax:    addi $t, $s, imm
; Encoding:  0010 00ss ssst tttt iiii iiii iiii iiii
;-----------------------------------------------------------------------------
; TODO: untested
align GRAIN, db 0x90
_i_addi:
        ; UNDONE_INST
        IType                        ; Load Imm, rS, rT Fields
        SX16    Imm                  ; Sign-extend the Imm offset
        mov     TMP,   R(rS)         ; TMP      := Regs[rS]
        add     TMP,   Imm           ; TMP      := TMP + Imm
        ; TODO: detect and trap overflow
        Wr_Reg  rT,    TMP           ; Regs[rT] := TMP (result)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; ADDIU -- Add immediate unsigned (no overflow).
; Adds a register and a sign-extended immediate value and stores the
; result in a register
; Operation: $t = $s + imm; advance_pc(4);
; Syntax:    addiu $t, $s, imm
; Encoding:  0010 01ss ssst tttt iiii iiii iiii iiii
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_i_addiu:
        IType                        ; Load Imm, rS, rT Fields
        SX16    Imm                  ; Sign-extend the Imm offset
        mov     TMP,   R(rS)         ; TMP      := Regs[rS]
        add     TMP,   Imm           ; TMP      := TMP + Imm
        Wr_Reg  rT,    TMP           ; Regs[rT] := TMP (result)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; SLTI -- Set on less than immediate (signed).
; If $s is less than immediate, $t is set to one. It gets zero otherwise.
; Operation: if $s < imm $t = 1; advance_pc(4);
;            else $t = 0; advance_pc(4);
; Syntax:    slti $t, $s, imm
; Encoding:  0010 10ss ssst tttt iiii iiii iiii iiii
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_i_slti:
        IType                        ; Load Imm, rS, rT Fields
        SX16    Imm                  ; Sign-extend the Imm offset
        xor     TMP,   TMP           ; Clear TMP
        cmp     R(rS), Imm           ; CMP(R(rS), Imm)
        setl    TMP_LowByte          ; if $s <(sgn) imm then TMP := 1; else 0
        Wr_Reg  rT,    TMP           ; Regs[rT] := TMP (result)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; SLTIU -- Set on less than immediate unsigned.
; If $s is less than the unsigned immediate, $t is set to one.
; It gets zero otherwise.
; Operation: if $s < imm $t = 1; advance_pc(4);
;            else $t = 0; advance_pc(4);
; Syntax:    sltiu $t, $s, imm
; Encoding:  0010 11ss ssst tttt iiii iiii iiii iiii
;-----------------------------------------------------------------------------
; Apparently even here we sign-extend the immediate.
align GRAIN, db 0x90
_i_sltiu:
        IType                        ; Load Imm, rS, rT Fields
        SX16    Imm                  ; Sign-extend the Imm offset
        xor     TMP,   TMP           ; Clear TMP
        cmp     R(rS), Imm           ; CMP(R(rS), Imm)
        setb    TMP_LowByte          ; if $s < imm then TMP := 1; else 0
        Wr_Reg  rT,    TMP           ; Regs[rT] := TMP (result)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; ANDI -- Bitwise AND immediate.
; Bitwise ANDs a register and an immediate value and stores the result
; in a register
; Operation: $t = $s & imm; advance_pc(4);
; Syntax:    andi $t, $s, imm
; Encoding:  0011 00ss ssst tttt iiii iiii iiii iiii
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_i_andi:
        IType                        ; Load Imm, rS, rT Fields
        mov     TMP,   R(rS)         ; TMP      := Regs[rS]
        and     TMP,   Imm           ; TMP      := TMP & Imm
        Wr_Reg  rT,    TMP           ; Regs[rT] := TMP (result)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; ORI -- Bitwise OR immediate.
; Bitwise OrS a register and an immediate value and stores the result
; in a register
; Operation: $t = $s | imm; advance_pc(4);
; Syntax:    ori $t, $s, imm
; Encoding:  0011 01ss ssst tttt iiii iiii iiii iiii
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_i_ori:
        IType                        ; Load Imm, rS, rT Fields
        mov     TMP,   R(rS)         ; TMP      := Regs[rS]
        or      TMP,   Imm           ; TMP      := TMP | Imm
        Wr_Reg  rT,    TMP           ; Regs[rT] := TMP (result)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; XORI -- Bitwise Exclusive Or immediate.
; Bitwise XOrS a register and an immediate value and stores the
; result in a register
; Operation: $t = $s ^ imm; advance_pc(4);
; Syntax:    xori $t, $s, imm
; Encoding:  0011 10ss ssst tttt iiii iiii iiii iiii
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_i_xori:
        IType                        ; Load Imm, rS, rT Fields
        mov     TMP,   R(rS)         ; TMP      := Regs[rS]
        xor     TMP,   Imm           ; TMP      := TMP ^ Imm
        Wr_Reg  rT,    TMP           ; Regs[rT] := TMP (result)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; LUI -- Load upper immediate.
; The immediate value is shifted left 16 bits and stored in the register.
; The lower 16 bits are zeroes.
; Operation: $t = (imm << 16); advance_pc(4);
; Syntax:    lui $t, imm
; Encoding:  0011 11-- ---t tttt iiii iiii iiii iiii
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_i_lui:
        IType_I_T_Only               ; Load Imm and rT Fields
        mov     TMP,   Imm           ; TMP      :=  Imm
        shl     TMP,   16            ; TMP      :=  TMP << 16
        Wr_Reg  rT,    TMP           ; Regs[rT] :=  TMP (result)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------
; 'M-Type' Instructions (tlbwi, tlbwr, tlbp, eret, mfc0, mtc0, wait) : |
;-----------------------------------------------------------------------

;-----------------------------------------------------------------------------
; TLBWI - Write Indexed TLB Entry.
; Write a TLB entry indexed by the Index register.
; The TLB entry pointed to by the Index register is written from the contents
; of the EntryHi, EntryLo0, EntryLo1, and PageMask registers.
; Syntax:    tlbwi
; Encoding:  0100 0010 0000 0000 0000 0000 0000 0010
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_m_tlbwi:
        ; no fields
        mov     AUX,   Sr(CP0_Index) ; Get CP0_Index register
        call    _write_tlb_entry     ; Write the indexed TLB entry.
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; TLBWR - Write Random TLB Entry.
; Write a TLB entry indexed by the Random register.
; The TLB entry pointed to by the Random register is written from the contents
; of the EntryHi, EntryLo0, EntryLo1, and PageMask registers.
; Syntax:    tlbwr
; Encoding:  0100 0010 0000 0000 0000 0000 0000 0110
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_m_tlbwr:
        ; no fields
        mov     ecx,   Sr(CP0_Wired)      ; ecx := CP0_Wired
        mov     ebx,   TLB_ENTRIES_COUNT  ; ebx := #tlbentries
        sub     ebx,   ecx                ; ebx := #tlbentries - Wired
        mov     edx,   0                  ; edx (upper half of dividend) := 0
        ; mov     eax,   CP0_Count          ; eax (lower half of divident) := cnt
        inc     dword [R_Ticks]
        mov     eax,   [R_Ticks]            ; eax (lower half of divident) := cnt
        div     ebx                       ; edx:eax / ebx
        add     edx,   ecx                ; edx (remainder) := edx + wired
        mov     AUX,   edx                ; make edx the index for tlb write
        call    _write_tlb_entry          ; Write the indexed TLB entry.
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; TLBP - Probe TLB for Matching Entry.
; Find a matching entry in the TLB.
; The Index register is loaded with the address of the TLB entry whose
; contents match the contents of the EntryHi register. If no TLB entry
; matches, the high-order bit of the Index register is set.
; Syntax:    tlbp
; Encoding:  0100 0010 0000 0000 0000 0000 0000 1000
;-----------------------------------------------------------------------------
; TODO: boojum?
align GRAIN, db 0x90
_m_tlbp:
        ; no fields
        mov     Sr(CP0_Index), 0x80000000 ; CP0_Index := 0x80000000
        ;; Get the active ASID:
        mov     edx,   Sr(CP0_EntryHi)  ; edx := CP0_EntryHi
        mov     ecx,   edx              ; ecx := edx
        and     edx,   0xFF             ; edx := edx & 0xFF (get current ASID)
        ;; Get the desired tag:
        and     ecx,   0xFFFFF000       ; ecx := ecx & 0xFFFFF000
        shr     ecx,   13               ; ecx := ecx >> 13 (current Tag)
        ;; For each slot in table (0 .. 15), attempt lookup
        xor     AUX,   AUX              ; Start with the 0-th entry in table
_m_tlbp_lookup_entry:
        mov     eax,   TLB_E(AUX)       ; eax := current TLB entry
        mov     ebx,   eax              ; ebx := eax
        and     ebx,   TLB_VPN2_Mask    ; get VPN2 of this entry
        cmp     ebx,   ecx              ; cmp(entry.VPN2, vAddr.tag)
        jne     _m_tlbp_lookup_nope     ; if entry.VPN2 != vAddr.tag: no match
        bt      eax,   TLB_G            ; is entry.G = 1?
        jc      _m_tlbp_lookup_match    ; then match.
        shr     eax,   TLB_ASID_Shift   ; eax := eax >> TLB_ASID_Shift
        and     eax,   TLB_ASID_Mask    ; eax := entry.ASID
        cmp     eax,   edx              ; entry.ASID = current ASID ?
        jne     _m_tlbp_lookup_nope     ; if neither G=1 nor ASID match.
        ;; otherwise:
_m_tlbp_lookup_match:                   ; TLB Match:
        mov     Sr(CP0_Index), AUX      ; Save the index
        jmp     _end_cycle              ; Fin.
_m_tlbp_lookup_nope:                    ; try next one in the table, if any
        inc     AUX                     ; index := index + 1
        cmp     AUX, TLB_ENTRIES_COUNT  ; see if still in range 0 .. n-1
        jb      _m_tlbp_lookup_entry    ; if in range, go to next entry
        ;; if we found nothing, we end up with CP0_Index = 0x80000000
        jmp     _end_cycle              ; Fin.
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; ERET - Exception Return.
; Return from interrupt, exception, or error trap.
; Syntax:    eret
; Encoding:  0100 0010 0000 0000 0000 0000 0001 1000
;;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_m_eret:
        ; no fields
        Flg_Get InDelaySlot           ; CF := whether we are in delay slot
        jc      _m_eret_abort         ; ... if so, abort this instruction; else:
        ;; proceed:
        Flg_Off LL_Bit                ; Clear the LL_Bit Flag
        btr     CP0_Status, CP0St_ERL ; CF := CP0St_ERL Flag, and clear it
        jnc     _m_eret_erl_false     ; If ERL was not set, do erl_false; else:
_m_eret_erl_true:
        mov     PC, Sr(CP0_ErrorEpc)  ; PC := CP0_ErrorEpc
        jmp     _m_eret_done          ; Done with this case
_m_eret_erl_false:
        mov     PC, Sr(CP0_Epc)       ; PC := CP0_Epc
        btr     CP0_Status, CP0St_EXL ; Clear the EXL Flag
_m_eret_done:
        sub     PC, 0x4               ; Counteract the usual PC = PC + 4
_m_eret_abort:
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; MFC0 - Move from Coprocessor 0.
; Move the contents of a coprocessor 0 register to a general register.
; The contents of the coprocessor 0 register specified by the combination of
; rd and sel (q) are loaded into general register rt. Note that not all
; coprocessor 0 registers support the sel field. In those instances, the sel
; field must be zero.
; Operation: $t = CPR[0, rD, q]
; Syntax:    mfc0 $t, $d, q
; Encoding:  0100 0000 000t tttt dddd d000 0000 0qqq
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_m_mfc0:
        CPType                           ; Load Q, rD, rT (ecx, eax, ebx)
        jmp     [_MFC0_Table + 8 * eax]  ; Dispatch on rD via MFC0 Table.
        ;; We continue in _mfc0_rXX where XX is rD.
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; MTC0 - Move to Coprocessor 0.
; Move the contents of a general register to a coprocessor 0 register.
; The contents of general register rt are loaded into the coprocessor 0
; register specified by the combination of rd and sel (q). Not all
; coprocessor 0 registers support the the sel field. In those instances, the
; sel field must be set to zero.
; Operation: CPR[0, rD, q] = $t
; Syntax:    mfc0 $t, $d, q
; Encoding:  0100 0000 100t tttt dddd d000 0000 0qqq
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_m_mtc0:
        CPType                           ; Load Q, rD, rT (ecx, eax, ebx)
        mov     rT, R(rT)                ; ebx := Regs[rT]
        jmp     [_MTC0_Table + 8 * eax]  ; Dispatch on rD via MFC0 Table.
        ;; We continue in _mtc0_rXX where XX is rD.
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; WAIT - Enter Standby Mode.
; Wait for Event.
; Puts the emulator into 'wait' mode.
; Encoding:  0100 001- ---- ---- ---- ---- --10 0000
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_m_wait:
        ; no fields
        Flg_On  Waiting              ; Set 'Waiting' Flag.
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;--------------------------------------
; The remaining I-Type Instructions : |
;--------------------------------------

;-----------------------------------------------------------------------------
; BEQL - Branch on Equal Likely.
; Compare registers, then do a PC-relative conditional branch;
; execute the delay slot only if the branch is taken.
; An 18-bit signed offset (the 16-bit offset field shifted left 2 bits) is
; added to the address of the instruction following the branch (not the
; branch itself), in the branch delay slot, to form a PC-relative effective
; target address. If the contents of rS and rT are equal, branch to the target
; address after the instruction in the delay slot is executed. If the branch
; is not taken, the instruction in the delay slot is not executed.
; Operation: if rS = rT then branch_likely
; Syntax:    beql $s, $t, offset
; Encoding:  0101 00ss ssst tttt iiii iiii iiii iiii
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_i_beql:
        IType                        ; Load Imm, rS, rT Fields
        mov     TMP,     R(rS)       ; TMP      := Regs[rS]
        cmp     TMP,     R(rT)       ; CMP(TMP, Regs[rT])
        jne     _i_beql_not_taken    ; If !(TMP = Regs[rT]) then NotTaken
        ; Taken:
        SX18_4N Imm                  ; Sign Extend Imm * 4
        mov     nPC,     PC          ; nPC      := PC
        add     nPC,     0x4         ; nPC      := nPC + 4
        add     nPC,     Imm         ; nPC      := nPC + Offset
        Flg_On  InDelaySlot          ; Set 'Delay Slot' Flag.
        jmp     _end_cycle
        ; Not Taken:
_i_beql_not_taken:
        add     PC,      0x4         ; PC       := PC + 4
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; BNEL - Branch on Not Equal Likely.
; Compare registers, then do a PC-relative conditional branch;
; execute the delay slot only if the branch is taken.
; An 18-bit signed offset (the 16-bit offset field shifted left 2 bits) is
; added to the address of the instruction following the branch (not the
; branch itself), in the branch delay slot, to form a PC-relative effective
; target address. If the contents of rS and rT are NOT equal, branch to the
; target address after the instruction in the delay slot is executed. If the
; branch is not taken, the instruction in the delay slot is not executed.
; Operation: if rS != rT then branch_likely
; Syntax:    bnel $s, $t, offset
; Encoding:  0101 01ss ssst tttt iiii iiii iiii iiii
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_i_bnel:
        IType                        ; Load Imm, rS, rT Fields
        mov     TMP,     R(rS)       ; TMP      := Regs[rS]
        cmp     TMP,     R(rT)       ; CMP(TMP, Regs[rT])
        je      _i_bnel_not_taken    ; If !(TMP != Regs[rT]) then NotTaken
        ; Taken:
        SX18_4N Imm                  ; Sign Extend Imm * 4
        mov     nPC,     PC          ; nPC      := PC
        add     nPC,     0x4         ; nPC      := nPC + 4
        add     nPC,     Imm         ; nPC      := nPC + Offset
        Flg_On  InDelaySlot          ; Set 'Delay Slot' Flag.
        jmp     _end_cycle
        ; Not Taken:
_i_bnel_not_taken:
        add     PC,      0x4         ; PC       := PC + 4
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; BLEZL - Branch on Less Than or Equal to Zero Likely.
; Test a register, then do a PC-relative conditional branch;
; execute the delay slot only if the branch is taken.
; An 18-bit signed offset (the 16-bit offset field shifted left 2 bits) is
; added to the address of the instruction following the branch (not the branch
; itself), in the branch delay slot, to form a PC-relative effective target
; address. If the contents of rS are less than or equal to zero (sign bit is
; 1 or value is zero), branch to the effective target address after the
; instruction in the delay slot is executed. If the branch is not taken, the
; instruction in the delay slot is not executed.
; Operation: If rS <= 0 then branch_likely
; Syntax:    bltzl $s, offset
; Encoding:  0101 10ss sss0 0000 iiii iiii iiii iiii
;-----------------------------------------------------------------------------
; TODO: branch-free variant?
align GRAIN, db 0x90
_i_blezl:
        IType_S_Only                 ; load only rS Field just now
        cmp     R(rS),   0           ; CMP(Regs[rS], 0)
        jnle    _i_blezl_not_taken   ; If !(Regs[rS] <= 0) then NotTaken
        ; Taken:
        IType_I_Only                 ; load only Imm Field just now
        SX18_4N Imm                  ; Sign Extend Imm * 4
        mov     nPC,     PC          ; nPC      := PC
        add     nPC,     0x4         ; nPC      := nPC + 4
        add     nPC,     Imm         ; nPC      := nPC + Offset
        Flg_On  InDelaySlot          ; Set 'Delay Slot' Flag.
        jmp     _end_cycle
        ; Not Taken:
_i_blezl_not_taken:
        add     PC,      0x4         ; PC       := PC + 4
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; BGTZL - Branch on Greater Than Zero Likely.
; Test a register, then do a PC-relative conditional branch;
; execute the delay slot only if the branch is taken.
; An 18-bit signed offset (the 16-bit offset field shifted left 2 bits) is
; added to the address of the instruction following the branch (not the branch
; itself), in the branch delay slot, to form a PC-relative effective target
; address. If the contents of rS are greater than zero (sign bit is 0 but
; value not zero), branch to the effective target address after the
; instruction in the delay slot is executed. If the branch is not taken, the
; instruction in the delay slot is not executed.
; Operation: If rS > 0 then branch_likely
; Syntax:    bgtzl $s, offset
; Encoding:  0101 11ss sss0 0000 iiii iiii iiii iiii
;-----------------------------------------------------------------------------
; TODO: branch-free variant?
align GRAIN, db 0x90
_i_bgtzl:
        IType_S_Only                 ; load only rS Field just now
        cmp     R(rS),   0           ; CMP(Regs[rS], 0)
        jng     _i_bgtzl_not_taken   ; If !(Regs[rS] > 0) then NotTaken
        ; Taken:
        IType_I_Only                 ; load only Imm Field just now
        SX18_4N Imm                  ; Sign Extend Imm * 4
        mov     nPC,     PC          ; nPC      := PC
        add     nPC,     0x4         ; nPC      := nPC + 4
        add     nPC,     Imm         ; nPC      := nPC + Offset
        Flg_On  InDelaySlot          ; Set 'Delay Slot' Flag.
        jmp     _end_cycle
        ; Not Taken:
_i_bgtzl_not_taken:
        add     PC,      0x4         ; PC       := PC + 4
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; MUL - Multiply Word to Register.
; The 32-bit word value in rS is multiplied by the 32-bit value in rT,
; treating both operands as signed values, to produce a 64-bit result.
; The least significant 32 bits of the product are written to rD.
; The contents of HI and LO are UNPREDICTABLE after the operation.
; No arithmetic exception occurs under any circumstances.
; Syntax:    mul $d, $s, $t
; Encoding:  0111 00ss ssst tttt dddd d000 0000 0010
;-----------------------------------------------------------------------------
; TODO: Find whether an unclobbered variant of this is faster
; TODO: untested
align GRAIN, db 0x90
_i_mul:
        RType                        ; load rD, rS, rT Fields
        mov     TMP,   R(rT)         ; TMP := Regs[rT]
        imul    TMP,   R(rS)         ; TMP := TMP * Regs[rS] (Bottom 32 bits)
        Wr_Reg  rD,    TMP           ; Regs[rD] := Bottom 32 bits of prod
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; LB -- Load Byte.
; A byte is loaded into a register from the specified address.
; Operation: $t = MEM[$s + offset]; advance_pc(4);
; Syntax:    lb $t, offset($s)
; Encoding:  1000 00ss ssst tttt iiii iiii iiii iiii
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_i_lb:
        IType                        ; Load Imm, rS, rT Fields
        SX16    Imm                  ; Sign-extend the Imm offset
        add     Imm,   R(rS)         ; Imm (EAX) := Imm + Regs[rS] (vAddr)
        call    _Virt_Read_Byte      ; AL := Virt_Read_Byte(EAX)
        shl     eax,   24            ; Shift left for sign-extension:
        sar     eax,   24            ; Shift back right, dragging sign bit:
        Wr_Reg  rT,    EAX           ; Regs[rT]  := EAX
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; LH - Load Halfword.
; Load a halfword from memory as a signed value.
; The contents of the 16-bit halfword at the memory location specified by the
; aligned effective address are fetched, sign-extended, and placed in rT.
; The 16-bit signed offset is added to the contents of rS to form the
; effective address. The effective address must be naturally-aligned.
; If the least-significant bit of the address is non-zero, an Address
; Error exception occurs
; Operation: $t = MEM[$s + offset]; advance_pc(4);
; Syntax:    lh $t, offset($s)
; Encoding:  1000 01ss ssst tttt iiii iiii iiii iiii
;-----------------------------------------------------------------------------
; NOTE: LHU is exactly same thing as LH, but without sign-extend.
align GRAIN, db 0x90
_i_lh:
        IType                        ; Load Imm, rS, rT Fields
        SX16    Imm                  ; Sign-extend the Imm offset
        add     Imm,   R(rS)         ; Imm (EAX) := Imm + Regs[rS] (vAddr)
        ;; rS (ecx) is not needed any more, can be reused
        bt      Imm,   0             ; Get low bit of vAddr
        jnc     _i_lh_aligned_ok     ; If zero, vAddr was properly aligned;
        SetEXC  EXC_AdEL             ; If vAddr was NOT properly aligned:
        jmp     _Handle_Exception    ; Go straight to exception handler.
_i_lh_aligned_ok:                    ; Load is to proceed normally:
        mov     ecx,   Imm           ; Save vAddr to ECX
        xor     TMP,   TMP           ; Clear TMP, where we will put the HW
%ifdef LITTLE_ENDIAN
        ;; Read the low byte of the HW first:
        call    _Virt_Read_Byte      ; EAX := Virt_Read_Byte(vAddr)
        mov     TMP,   eax           ; TMP := eax (save the low byte)
        ;; Now read the high byte:
        mov     eax,   ecx           ; Restore the original saved vAddr
        or      eax,   1             ; Will read vAddr + 1
        call    _Virt_Read_Byte      ; EAX := Virt_Read_Byte(vAddr + 1)
%else
        or      Imm,   1             ; First, read vAddr + 1 :
        call    _Virt_Read_Byte      ; EAX := Virt_Read_Byte(vAddr + 1)
        mov     TMP,   eax           ; TMP := eax (save low byte)
        mov     eax,   ecx           ; Restore the original saved vAddr
        call    _Virt_Read_Byte      ; EAX := Virt_Read_Byte(vAddr)
%endif
        shl     eax,   8             ; eax := eax << 8
        or      eax,   TMP           ; eax := eax | TMP (high | low)
        shl     eax,   16            ; left shift eax to put the HW on top
        sar     eax,   16            ; right shift it back, to sign-extend.
        Wr_Reg  rT,    eax           ; Regs[rT] := EAX (write back result)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; LWL - Load Word Left.
; Load the most-significant part of a word as a signed value from an unaligned
; memory address.
; Operation: $t = $t MERGE MEM[$s + offset]; advance_pc(4);
; Syntax:    lwl $t, offset($s)
; Encoding:  1000 10ss ssst tttt iiii iiii iiii iiii
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_i_lwl:
        IType                        ; Load Imm, rS, rT Fields
        SX16    Imm                  ; Sign-extend the Imm offset
        add     Imm,   R(rS)         ; Imm (EAX) := Imm + Regs[rS] (vAddr)
        ;; rS (ecx) is not needed any more, can be reused
        mov     TMP,   R(rT)         ; TMP := Regs[rT] (old reg value)
        mov     AUX,   Imm           ; Save the original unaligned address
        and     Imm,   ~(0x3)        ; Clear bottom two bits of address
        call    _Virt_Read_Word      ; EAX := Virt_Read_Word(EAX (aligned))
        ;; Proceed with write. EAX : old word, TMP : new word, ebx : still rT
        mov     ecx,   AUX           ; ecx := original unaligned address
        and     ecx,   0x3           ; keep only bottom two bits of it
        shl     ecx,   3             ; ecx := ecx (8 * (addr[0..2]))
        shl     eax,   cl            ; left shift eax (orig word) by above
        xor     ecx,   24            ; ecx := 24 - ecx
        mov     AUX,   0xFFFFFF      ; AUX := 0xFFFFFF
        shr     AUX,   cl            ; AUX := AUX >> cl
        and     TMP,   AUX           ; TMP := TMP & AUX
        or      TMP,   eax           ; TMP := TMP | eax (the writeback value)
        Wr_Reg  rT,    TMP           ; Regs[rT] := TMP (the writeback value)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; LW -- Load Word.
; The contents of the 32-bit word at the memory location specified by the
; aligned effective address are fetched, signextended to the register length
; if necessary, and placed in register rT. The 16-bit signed offset is added
; to the contents of the base to form the effective address.
; Operation: $t = MEM[$s + offset]; advance_pc(4);
; Syntax:    lw $t, offset($s)
; Encoding:  1000 11ss ssst tttt iiii iiii iiii iiii
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_i_lw:
        IType                        ; Load Imm, rS, rT Fields
        SX16    Imm                  ; Sign-extend the Imm offset
        add     Imm,   R(rS)         ; Imm (EAX) := Imm + Regs[rS] (vAddr)
        call    _Virt_Read_Word      ; EAX       := Virt_Read_Word(EAX)
        Wr_Reg  rT,    EAX           ; Regs[rT]  := EAX
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; LBU - Load Byte Unsigned.
; Load a byte from memory as an unsigned value.
; The contents of the 8-bit byte at the memory location specified by the
; effective address are fetched, zero-extended, and placed in rT.
; The 16-bit signed offset is added to the contents of rS to form the
; effective address.
; Operation: $t = MEM[$s + offset]; advance_pc(4);
; Syntax:    lbu $t, offset($s)
; Encoding:  1001 00ss ssst tttt iiii iiii iiii iiii
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_i_lbu:
        IType                        ; Load Imm, rS, rT Fields
        SX16    Imm                  ; Sign-extend the Imm offset
        add     Imm,   R(rS)         ; Imm (EAX) := Imm + Regs[rS] (vAddr)
        call    _Virt_Read_Byte      ; AL := Virt_Read_Byte(EAX)
        Wr_Reg  rT,    EAX           ; Regs[rT]  := EAX
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; LHU - Load Halfword Unsigned.
; Load a halfword from memory as an unsigned value.
; The contents of the 16-bit halfword at the memory location specified by the
; aligned effective address are fetched, zero-extended, and placed in reg rT.
; The 16-bit signed offset is added to the contents of register rS to form the
; effective address. The effective address must be naturally-aligned.
; If the least-significant bit of the address is non-zero,
; an Address Error exception occurs.
; Operation: $t = MEM[$s + offset]; advance_pc(4);
; Syntax:    lhu $t, offset($s)
; Encoding:  1001 01ss ssst tttt iiii iiii iiii iiii
;-----------------------------------------------------------------------------
; NOTE: exactly same thing as LH, but WITHOUT sign-extend.
align GRAIN, db 0x90
_i_lhu:
        IType                        ; Load Imm, rS, rT Fields
        SX16    Imm                  ; Sign-extend the Imm offset
        add     Imm,   R(rS)         ; Imm (EAX) := Imm + Regs[rS] (vAddr)
        ;; rS (ecx) is not needed any more, can be reused
        bt      Imm,   0             ; Get low bit of vAddr
        jnc     _i_lhu_aligned_ok    ; If zero, vAddr was properly aligned;
        SetEXC  EXC_AdEL             ; If vAddr was NOT properly aligned:
        jmp     _Handle_Exception    ; Go straight to exception handler.
_i_lhu_aligned_ok:                   ; Load is to proceed normally:
        mov     ecx,   Imm           ; Save vAddr to ECX
        xor     TMP,   TMP           ; Clear TMP, where we will put the HW
%ifdef LITTLE_ENDIAN
        ;; Read the low byte of the HW first:
        call    _Virt_Read_Byte      ; EAX := Virt_Read_Byte(vAddr + 1)
        mov     TMP,   eax           ; TMP := eax (save the low byte)
        ;; Now read the high byte:
        mov     eax,   ecx           ; Restore the original saved vAddr
        or      eax,   1             ; Will read vAddr + 1
        call    _Virt_Read_Byte      ; EAX := Virt_Read_Byte(vAddr + 1)
%else
        or      Imm,   1             ; First, read vAddr + 1 :
        call    _Virt_Read_Byte      ; EAX := Virt_Read_Byte(vAddr + 1)
        mov     TMP,   eax           ; TMP := eax (save low byte)
        mov     eax,   ecx           ; Restore the original saved vAddr
        call    _Virt_Read_Byte      ; EAX := Virt_Read_Byte(vAddr)
%endif
        shl     eax,   8             ; eax := eax << 8
        or      eax,   TMP           ; eax := eax | TMP (high | low)
        Wr_Reg  rT,    eax           ; Regs[rT] := EAX (write back result)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; LWL - Load Word Right.
; Load the least-significant part of a word from an unaligned memory address
; as a signed value. The 16-bit signed offset is added to the contents of rS
; to form an effective address (EffAddr). EffAddr is the address of the
; least-significant of 4 consecutive bytes forming a word (W) in memory
; starting at an arbitrary byte boundary. A part of W, the least-significant
; 1 to 4 bytes, is in the aligned word containing EffAddr. This part of W is
; loaded into the least-significant (right) part of the word in register rT.
; The remaining most-significant part of the word in register rT is unchanged.
; Executing both LWR and LWL, in either order, delivers a sign-extended word
; value in the destination register.
; Operation: $t = $t MERGE MEM[$s + offset]; advance_pc(4);
; Syntax:    lwr $t, offset($s)
; Encoding:  1001 10ss ssst tttt iiii iiii iiii iiii
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_i_lwr:
        IType                        ; Load Imm, rS, rT Fields
        SX16    Imm                  ; Sign-extend the Imm offset
        add     Imm,   R(rS)         ; Imm (EAX) := Imm + Regs[rS] (vAddr)
        ;; rS (ecx) is not needed any more, can be reused
        mov     TMP,   R(rT)         ; TMP := Regs[rT] (value to write)
        mov     AUX,   Imm           ; Save the original unaligned address
        and     Imm,   ~(0x3)        ; Clear bottom two bits of address
        call    _Virt_Read_Word      ; EAX := Virt_Read_Word(EAX (aligned))
        ;; Proceed with write. EAX : old word, TMP : new word, ebx : still rT
        mov     ecx,   AUX           ; ecx := original unaligned address
        and     ecx,   0x3           ; keep only bottom two bits of it
        shl     ecx,   3             ; ecx := ecx (8 * (addr[0..2]))
        mov     AUX,   0xFFFFFF00    ; AUX := 0xFFFFFF00
        shl     AUX,   cl            ; AUX := AUX << cl
        and     TMP,   AUX           ; TMP := TMP & AUX
        xor     ecx,   24            ; ecx := 24 - ecx
        shr     eax,   cl            ; right shift eax (orig word) by above
        or      TMP,   eax           ; TMP := TMP | eax (the writeback value)
        Wr_Reg  rT,    TMP           ; Regs[rT] := TMP (the writeback value)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; SB -- Store Byte.
; The least significant byte of $t is stored at the specified address.
; Operation: MEM[$s + offset] = (0xff & $t); advance_pc(4);
; Syntax:    sb $t, offset($s)
; Encoding:  1010 00ss ssst tttt iiii iiii iiii iiii
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_i_sb:
        IType                        ; Load Imm, rS, rT Fields
        SX16    Imm                  ; Sign-extend the Imm offset
        add     Imm,   R(rS)         ; Imm (EAX) := Imm + Regs[rS] (vAddr)
        mov     TMP,   R(rT)         ; TMP (EDX) := Regs[rT]
        and     TMP,   0xFF          ; Keep only low byte of TMP
        call    _Virt_Write_Byte     ; Virt_Write_Byte(EAX, DL)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; SH - Store Halfword.
; Store a halfword to memory. The least-significant 16-bit halfword of
; register rT is stored in memory at the location specified by the aligned
; effective address. The 16-bit signed offset is added to the contents of rS
; to form the effective address.
; The effective address must be naturally-aligned. If the least-significant
; bit of the address is non-zero, an Address Error exception occurs.
; Operation: MEM[$s + offset] = $t; advance_pc(4);
; Syntax:    sh $t, offset($s)
; Encoding:  1010 01ss ssst tttt iiii iiii iiii iiii
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_i_sh:
        IType                        ; Load Imm, rS, rT Fields
        SX16    Imm                  ; Sign-extend the Imm offset
        add     Imm,   R(rS)         ; Imm (EAX) := Imm + Regs[rS] (vAddr)
        bt      Imm,   0             ; Get low bit of vAddr
        jnc     _i_sh_aligned_ok     ; If zero, vAddr was properly aligned;
        SetEXC  EXC_AdES             ; If vAddr was NOT properly aligned:
        jmp     _Handle_Exception    ; Go straight to exception handler.
_i_sh_aligned_ok:
        mov     AUX,   Imm           ; Save Imm (vAddr) to AUX
        ;; rS (ecx) is not needed any more, can be reused
        mov     ecx,   R(rT)         ; ecx := Regs[rT] (HW to be written)
        mov     TMP,   ecx           ; TMP := ecx
%ifdef LITTLE_ENDIAN
        and     TMP,   0xFF          ; isolate low byte
        call    _Virt_Write_Byte     ; Virt_Write_Byte(vAddr, DL)
        mov     TMP,   ecx           ; TMP := the whole HW again
        and     TMP,   0xFF00        ; isolate high byte
        shr     TMP,   8             ; TMP := TMP >> 8 (get high byte)
%else ;; Big-Endian
        shr     TMP,   8             ; TMP := TMP >> 8 (get high byte)
        call    _Virt_Write_Byte     ; Virt_Write_Byte(vAddr, DL)
        mov     TMP,   ecx           ; TMP := ecx again, now for low byte
        and     TMP,   0xFF          ; isolate low byte
%endif
        mov     eax,   AUX           ; restore vAddr from AUX
        or      eax,   1             ; set bottom bit, for vAddr + 1
        call    _Virt_Write_Byte     ; Virt_Write_Byte(vAddr + 1, DL)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; SWL - Store Word Left.
; Store the most-significant part of a word to an unaligned memory address.
; Operation: MEM[$s + offset] = $t; advance_pc(4);
; Syntax:    swl $t, offset($s)
; Encoding:  1010 10ss ssst tttt iiii iiii iiii iiii
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_i_swl:
        IType                        ; Load Imm, rS, rT Fields
        SX16    Imm                  ; Sign-extend the Imm offset
        add     Imm,   R(rS)         ; Imm (EAX) := Imm + Regs[rS] (vAddr)
        ;; rS (ecx) is not needed any more, can be reused
        mov     TMP,   R(rT)         ; TMP := Regs[rT] (old reg value)
        ;; rT (ebx) is not needed any more, can be reused
        mov     AUX,   Imm           ; Save the original unaligned address
        and     Imm,   ~(0x3)        ; Clear bottom two bits of address
        mov     ebx,   Imm           ; Save the aligned address for writeback
        call    _Virt_Read_Word      ; EAX := Virt_Read_Word(EAX (aligned))
        ;; Proceed with write. EAX : old word, TMP : new word, ebx : WB addr.
        mov     ecx,   AUX           ; ecx := original unaligned address
        and     ecx,   0x3           ; keep only bottom two bits
        shl     ecx,   3             ; ecx := ecx (8 * (addr[0..2]))
        shr     TMP,   cl            ; right shift TMP by above
        xor     ecx,   24            ; ecx := 24 - ecx
        mov     AUX,   0xFFFFFF00    ; AUX := 0xFFFFFF00
        shl     AUX,   cl            ; AUX := AUX << cl
        and     eax,   AUX           ; eax := eax & AUX
        or      TMP,   eax           ; TMP := TMP | eax (the writeback value)
        mov     eax,   ebx           ; restore aligned address for writeback
        call    _Virt_Write_Word     ; Virt_Write_Word(EAX, TMP)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; SW -- Store Word.
; The contents of $t is stored at the specified address.
; The effective address must be naturally-aligned.
; If either of the 2 least-significant bits of the address is non-zero,
; an Address Error exception occurs.
; Operation: MEM[$s + offset] = $t; advance_pc(4);
; Syntax:    sw $t, offset($s)
; Encoding:  1010 11ss ssst tttt iiii iiii iiii iiii
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_i_sw:
        IType                        ; Load Imm, rS, rT Fields
        SX16    Imm                  ; Sign-extend the Imm offset
        add     Imm,   R(rS)         ; Imm (EAX) := Imm + Regs[rS] (vAddr)
        mov     TMP,   R(rT)         ; TMP := Regs[rT]
        call    _Virt_Write_Word     ; Virt_Write_Word(EAX, TMP)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; SWR - Store Word Right.
; Store the least-significant part of a word to an unaligned memory address.
; Operation: MEM[$s + offset] = $t; advance_pc(4);
; Syntax:    swr $t, offset($s)
; Encoding:  1011 10ss ssst tttt iiii iiii iiii iiii
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_i_swr:
        IType                        ; Load Imm, rS, rT Fields
        SX16    Imm                  ; Sign-extend the Imm offset
        add     Imm,   R(rS)         ; Imm (EAX) := Imm + Regs[rS] (vAddr)
        ;; rS (ecx) is not needed any more, can be reused
        mov     TMP,   R(rT)         ; TMP := Regs[rT] (value to write)
        ;; rT (ebx) is not needed any more, can be reused
        mov     AUX,   Imm           ; Save the original unaligned address
        and     Imm,   ~(0x3)        ; Clear bottom two bits of address
        mov     ebx,   Imm           ; Save the aligned address for writeback
        call    _Virt_Read_Word      ; EAX := Virt_Read_Word(EAX (aligned))
        ;; Proceed with write. EAX : old word, TMP : new word, ebx : WB addr.
        mov     ecx,   AUX           ; ecx := original unaligned address
        and     ecx,   0x3           ; keep only bottom two bits
        shl     ecx,   3             ; ecx := ecx * 8
        mov     AUX,   0xFFFFFF      ; AUX := 0xFFFFFF
        shr     AUX,   cl            ; AUX := AUX >> cl
        and     eax,   AUX           ; eax := eax & AUX
        xor     ecx,   24            ; ecx := 24 - ecx (24 - 8 * (addr[0..2]))
        shl     TMP,   cl            ; left shift TMP (value to write) by above
        or      TMP,   eax           ; TMP := TMP | eax (the writeback value)
        mov     eax,   ebx           ; restore aligned address for writeback
        call    _Virt_Write_Word     ; Virt_Write_Word(EAX, TMP)
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; CACHE.
; Operation: This is a NOP in this emulator.
; Syntax:    cache $t, offset($s)
; Encoding:  1011 11ss ssst tttt iiii iiii iiii iiii
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_i_cache:
        ; no fields
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; LL - Load Linked Word.
; Load a word from memory for an atomic read-modify-write.
; The contents of the 32-bit word at the memory location specified by the
; aligned effective address are fetched and written into register rT.
; The 16-bit signed offset is added to the contents of rS to form
; an effective address.
; Operation: $t = MEM[$s + offset]; llbit = 1; advance_pc(4);
; Syntax:    ll $t, offset($s)
; Encoding:  1100 00ss ssst tttt iiii iiii iiii iiii
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_i_ll:
        IType                        ; Load Imm, rS, rT Fields
        SX16    Imm                  ; Sign-extend the Imm offset
        add     Imm,   R(rS)         ; Imm (EAX) := Imm + Regs[rS] (vAddr)
        call    _Virt_Read_Word      ; EAX       := Virt_Read_Word(EAX)
        Wr_Reg  rT,    EAX           ; Regs[rT]  := EAX
        Flg_On  LL_Bit               ; Set the LL_Bit Flag
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; PREF - Prefetch.
; Operation: This is a NOP in this emulator.
; Syntax:    pref $t, offset($s)
; Encoding:  1100 11ss ssst tttt iiii iiii iiii iiii
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_i_pref:
        ; no fields
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; SC - Store Conditional Word.
; Store a word to memory to complete an atomic read-modify-write.
; The 32-bit word in rT is conditionally stored in memory at the location
; specified by the aligned effective address.
; The 16-bit signed offset is added to the contents of rS to form an
; effective address
; Operation: if llbit == 1 then: MEM[$s + offset] = $t; rt = 1; advance_pc(4);
;            else: rt = 0; advance_pc(4);
; Syntax:    sc $t, offset($s)
; Encoding:  1110 00ss ssst tttt iiii iiii iiii iiii
;-----------------------------------------------------------------------------
; TODO: can haz fewer jumps here?
align GRAIN, db 0x90
_i_sc:
        IType                        ; Load Imm, rS, rT Fields
        Flg_Get LL_Bit               ; CF := LL_Bit
        jnc     _i_sc_not_taken      ; If LL_Bit = 0, then do not store; else:
        SX16    Imm                  ; Sign-extend the Imm offset
        add     Imm,   R(rS)         ; Imm (EAX) := Imm + Regs[rS] (vAddr)
        mov     TMP,   R(rT)         ; TMP := Regs[rT]
        call    _Virt_Write_Word     ; Virt_Write_Word(EAX, TMP)
        mov     R(rT), 1             ; Regs[rT] := 1 ('yes, stored word')
        jmp     _i_sc_done           ; Done.
_i_sc_not_taken:
        mov     R(rT), 0             ; Regs[rT] := 0 ('no, did not store')
_i_sc_done:        
        jmp     _end_cycle
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; MFC0 Cases. Parameters: Q (ecx), rD (eax), rT (ebx)
;-----------------------------------------------------------------------------
_mfc0_r00: ;  0x00
        test    ecx,   ecx              ; Sel != 0 ?
        jnz     _mfc0_unknown           ; ... then unknown; else:
        mov     TMP,   Sr(CP0_Index)    ; return CP0_Index
        jmp     _mfc0_writeback         ; Done
;-----------------------------------------------------------------------------
_mfc0_r01: ;  0x01
        jmp     _mfc0_unknown
;-----------------------------------------------------------------------------
_mfc0_r02: ;  0x02
        mov     TMP,   Sr(CP0_EntryLo0) ; return CP0_EntryLo0
        jmp     _mfc0_writeback         ; Done
;-----------------------------------------------------------------------------
_mfc0_r03: ;  0x03
        mov     TMP,   Sr(CP0_EntryLo1) ; return CP0_EntryLo1
        jmp     _mfc0_writeback         ; Done
;-----------------------------------------------------------------------------
_mfc0_r04: ;  0x04
        test    ecx,   ecx              ; Sel != 0 ?
        jnz     _mfc0_unknown           ; ... then unknown; else:
        mov     TMP,   Sr(CP0_Context)  ; return CP0_Context
        jmp     _mfc0_writeback         ; Done
;-----------------------------------------------------------------------------
_mfc0_r05: ;  0x05
        test    ecx,   ecx              ; Sel != 0 ?
        jnz     _mfc0_unknown           ; ... then unknown; else:
        mov     TMP,   Sr(CP0_PageMask) ; return CP0_PageMask
        jmp     _mfc0_writeback         ; Done
;-----------------------------------------------------------------------------
_mfc0_r06: ;  0x06
        test    ecx,   ecx              ; Sel != 0 ?
        jnz     _mfc0_unknown           ; ... then unknown; else:
        mov     TMP,   Sr(CP0_Wired)    ; return CP0_Wired
        jmp     _mfc0_writeback         ; Done
;-----------------------------------------------------------------------------
_mfc0_r07: ;  0x07
        jmp     _mfc0_unknown
;-----------------------------------------------------------------------------
_mfc0_r08: ;  0x08
        test    ecx,   ecx              ; Sel != 0 ?
        jnz     _mfc0_unknown           ; ... then unknown; else:
        mov     TMP,   Sr(CP0_BadVAddr) ; return CP0_BadVAddr
        jmp     _mfc0_writeback         ; Done
;-----------------------------------------------------------------------------
_mfc0_r09: ;  0x09
        test    ecx,   ecx              ; Sel != 0 ?
        jnz     _mfc0_unknown           ; ... then unknown; else:
        mov     TMP,   CP0_Count        ; return CP0_Count ('fast reg')
        jmp     _mfc0_writeback         ; Done
;-----------------------------------------------------------------------------
_mfc0_r10: ;  0x0a
        test    ecx,   ecx              ; Sel != 0 ?
        jnz     _mfc0_unknown           ; ... then unknown; else:
        mov     TMP,   Sr(CP0_EntryHi)  ; return CP0_EntryHi
        jmp     _mfc0_writeback         ; Done
;-----------------------------------------------------------------------------
_mfc0_r11: ;  0x0b
        test    ecx,   ecx              ; Sel != 0 ?
        jnz     _mfc0_unknown           ; ... then unknown; else:
        mov     TMP,   CP0_Compare      ; return CP0_Compare ('fast reg')
        jmp     _mfc0_writeback         ; Done
;-----------------------------------------------------------------------------
_mfc0_r12: ;  0x0c
        test    ecx,   ecx              ; Sel != 0 ?
        jnz     _mfc0_unknown           ; ... then unknown; else:
        mov     TMP,   CP0_Status       ; return CP0_Status ('fast reg')
        jmp     _mfc0_writeback         ; Done
;-----------------------------------------------------------------------------
_mfc0_r13: ;  0x0d
        test    ecx,   ecx              ; Sel != 0 ?
        jnz     _mfc0_unknown           ; ... then unknown; else:
        mov     TMP,   CP0_Cause        ; return CP0_Cause ('fast reg')
        jmp     _mfc0_writeback         ; Done
;-----------------------------------------------------------------------------
_mfc0_r14: ;  0x0e
        test    ecx,   ecx              ; Sel != 0 ?
        jnz     _mfc0_unknown           ; ... then unknown; else:
        mov     TMP,   Sr(CP0_Epc)      ; return CP0_Epc
        jmp     _mfc0_writeback         ; Done
;-----------------------------------------------------------------------------
_mfc0_r15: ;  0x0f
        mov     TMP, 0x00018000         ; processor id (qemu 4kc)
        jmp     _mfc0_writeback         ; Done
;-----------------------------------------------------------------------------
_mfc0_r16: ;  0x10
        cmp     r_Q,   0                ; Is sel = 0?
        je      _mfc0_r16_q_0           ; If sel = 0...
        cmp     r_Q,   1                ; Is sel = 1?
        je      _mfc0_r16_q_1           ; If sel = 1...
        jmp     _mfc0_unknown           ; Unknown sel
_mfc0_r16_q_0:
        mov     TMP,   0x80008082       ; return 0x80008082
        jmp     _mfc0_writeback         ; Done
_mfc0_r16_q_1:
        mov     TMP,   0x1e190c8a       ; return 0x1e190c8a
        jmp     _mfc0_writeback         ; Done
;-----------------------------------------------------------------------------
_mfc0_r17: ;  0x11
        jmp     _mfc0_unknown
;-----------------------------------------------------------------------------
_mfc0_r18: ;  0x12
        xor     TMP, TMP                ; TMP = 0
        jmp     _mfc0_writeback         ; Done
;-----------------------------------------------------------------------------
_mfc0_r19: ;  0x13
        xor     TMP, TMP                ; TMP = 0
        jmp     _mfc0_writeback         ; Done
;-----------------------------------------------------------------------------
_mfc0_r20: ;  0x14
        jmp     _mfc0_unknown
;-----------------------------------------------------------------------------
_mfc0_r21: ;  0x15
        jmp     _mfc0_unknown
;-----------------------------------------------------------------------------
_mfc0_r22: ;  0x16
        jmp     _mfc0_unknown
;-----------------------------------------------------------------------------
_mfc0_r23: ;  0x17
        jmp     _mfc0_unknown
;-----------------------------------------------------------------------------
_mfc0_r24: ;  0x18
        jmp     _mfc0_unknown
;-----------------------------------------------------------------------------
_mfc0_r25: ;  0x19
        jmp     _mfc0_unknown
;-----------------------------------------------------------------------------
_mfc0_r26: ;  0x1a
        jmp     _mfc0_unknown
;-----------------------------------------------------------------------------
_mfc0_r27: ;  0x1b
        jmp     _mfc0_unknown
;-----------------------------------------------------------------------------
_mfc0_r28: ;  0x1c
        jmp     _mfc0_unknown
;-----------------------------------------------------------------------------
_mfc0_r29: ;  0x1d
        jmp     _mfc0_unknown
;-----------------------------------------------------------------------------
_mfc0_r30: ;  0x1e
        jmp     _mfc0_unknown
;-----------------------------------------------------------------------------
_mfc0_r31: ;  0x1f
        jmp     _mfc0_unknown
;-----------------------------------------------------------------------------
_mfc0_writeback:                        ; Write result and go to next cycle
        Wr_Reg  rT,    TMP              ; Regs[rT] :=  TMP (result)
        jmp     _end_cycle              ; Fin
;-----------------------------------------------------------------------------
_mfc0_unknown:
        EGGOG   "Unknown CP0 Reg Selector in MFC0!"
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; MTC0 Cases. Parameters: Q (ecx), rD (eax), Regs[rT] (ebx)
;-----------------------------------------------------------------------------
_mtc0_r00: ;  0x00
        test    ecx,   ecx                   ; Sel != 0 ?
        jnz     _mtc0_unknown                ; ... then unknown; else:
        mov     eax,           Sr(CP0_Index) ; eax       := CP0_Index
        and     ebx,           0xF           ; T         := T & 0xF
        and     eax,           0x80000000    ; eax       := eax & 0x80000000
        or      eax,           ebx           ; eax       := eax | T
        mov     Sr(CP0_Index), eax           ; CP0_Index := eax
        jmp     _end_cycle              ; Done
;-----------------------------------------------------------------------------
_mtc0_r01: ;  0x01
        jmp     _mtc0_unknown
;-----------------------------------------------------------------------------
_mtc0_r02: ;  0x02
        and     ebx, 0x3FFFFFF          ; T            := T & 0x3FFFFFF
        mov     Sr(CP0_EntryLo0), ebx   ; CP0_EntryLo0 := T
        jmp     _end_cycle              ; Done
;-----------------------------------------------------------------------------
_mtc0_r03: ;  0x03
        and     ebx, 0x3FFFFFF          ; T            := T & 0x3FFFFFF
        mov     Sr(CP0_EntryLo1), ebx   ; CP0_EntryLo1 := T
        jmp     _end_cycle              ; Done
;-----------------------------------------------------------------------------
_mtc0_r04: ;  0x04
%define CONTEXTMASK  0xFF800000
%define nCONTEXTMASK (0xFFFFFFFF ^ 0xFF800000)
        mov     eax,             Sr(CP0_Context) ; eax     := CP0_Context
        and     ebx,             CONTEXTMASK  ; T         := T   & CONTEXTMASK
        and     eax,             nCONTEXTMASK ; eax       := eax & ~CONTEXTMASK
        or      eax,             ebx          ; eax       := eax | T
        mov     Sr(CP0_Context), eax          ; CP0_Context := eax
        jmp     _end_cycle                    ; Done
;-----------------------------------------------------------------------------
_mtc0_r05: ;  0x05
        test    ecx,   ecx              ; Sel != 0 ?
        jnz     _mtc0_unknown           ; ... then unknown; else:
        test    ebx,   ebx              ; T != 0 ?
        jnz     _mtc0_r05_untest_pgmask ; If so, eggog
        and     ebx,   0x1FFE000        ; T            := T & 0x1FFE000
        mov     Sr(CP0_PageMask), ebx   ; CP0_PageMask := T
        jmp     _end_cycle              ; Done
_mtc0_r05_untest_pgmask:
        EGGOG   "MTC0: Untested Page Mask!"

;-----------------------------------------------------------------------------
_mtc0_r06: ;  0x06
        test    ecx,   ecx              ; Sel != 0 ?
        jnz     _mtc0_unknown           ; ... then unknown; else:
        and     ebx,   0xF              ; T         := T & 0xF
        mov     Sr(CP0_Wired), ebx      ; CP0_Wired := T
        jmp     _end_cycle              ; Done
;-----------------------------------------------------------------------------
_mtc0_r07: ;  0x07
        jmp     _mtc0_unknown
;-----------------------------------------------------------------------------
_mtc0_r08: ;  0x08
        jmp     _mtc0_unknown
;-----------------------------------------------------------------------------
_mtc0_r09: ;  0x09
        test    ecx,   ecx              ; Sel != 0 ?
        jnz     _mtc0_unknown           ; ... then unknown; else:
        mov     CP0_Count, ebx          ; CP0_Count := T ('fast reg')
        jmp     _end_cycle              ; Done
;-----------------------------------------------------------------------------
_mtc0_r10: ;  0x0a
        test    ecx,   ecx              ; Sel != 0 ?
        jnz     _mtc0_unknown           ; ... then unknown; else:
        and     ebx,   ~0x1F00          ; T           := T & ~0x1F00
        mov     Sr(CP0_EntryHi), ebx    ; CP0_EntryHi := T
        jmp     _end_cycle              ; Done
;-----------------------------------------------------------------------------
_mtc0_r11: ;  0x0b
        test    ecx,   ecx              ; Sel != 0 ?
        jnz     _mtc0_unknown           ; ... then unknown; else:
        ClrIRQ  5                       ; Clear IRQ 5
        mov     CP0_Compare, ebx        ; CP0_Compare := T ('fast reg')
        jmp     _end_cycle              ; Done
;-----------------------------------------------------------------------------
_mtc0_r12: ;  0x0c
        test    ecx,   ecx              ; Sel != 0 ?
        jnz     _mtc0_unknown           ; ... then unknown; else:
%define STATUSMASK 0x7D7CFF17
        mov     eax,        CP0_Status   ; eax        := CP0_Status
        and     ebx,        STATUSMASK   ; T          := T   & STATUSMASK
        and     eax,        ~STATUSMASK  ; eax        := eax & ~STATUSMASK
        or      eax,        ebx          ; eax        := eax | T
        mov     CP0_Status, eax          ; CP0_Status := eax
        jmp     _end_cycle               ; Done
;-----------------------------------------------------------------------------
_mtc0_r13: ;  0x0d
        test    ecx,   ecx              ; Sel != 0 ?
        jnz     _mtc0_unknown           ; ... then unknown; else:
%define CAUSEMASK ((1 << 23) | (1 << 22) | (3 << 8))
        mov     eax,        CP0_Cause   ; eax        := CP0_Cause
        and     ebx,        CAUSEMASK   ; T          := T   & CAUSEMASK
        and     eax,        ~CAUSEMASK  ; eax        := eax & ~CAUSEMASK
        or      eax,        ebx         ; eax        := eax | T
        mov     CP0_Cause,  eax         ; CP0_Cause := eax
        jmp     _end_cycle              ; Done
;-----------------------------------------------------------------------------
_mtc0_r14: ;  0x0e
        test    ecx,   ecx              ; Sel != 0 ?
        jnz     _mtc0_unknown           ; ... then unknown; else:
        mov     Sr(CP0_Epc), ebx        ; CP0_Epc := T
        jmp     _end_cycle              ; Done
;-----------------------------------------------------------------------------
_mtc0_r15: ;  0x0f
        jmp     _mtc0_unknown
;-----------------------------------------------------------------------------
_mtc0_r16: ;  0x10
        jmp     _end_cycle              ; Done
;-----------------------------------------------------------------------------
_mtc0_r17: ;  0x11
        jmp     _mtc0_unknown
;-----------------------------------------------------------------------------
_mtc0_r18: ;  0x12
        jmp     _end_cycle              ; Done
;-----------------------------------------------------------------------------
_mtc0_r19: ;  0x13
        jmp     _end_cycle              ; Done
;-----------------------------------------------------------------------------
_mtc0_r20: ;  0x14
        jmp     _mtc0_unknown           ; Done
;-----------------------------------------------------------------------------
_mtc0_r21: ;  0x15 
        jmp     _mtc0_unknown           ; Done
;-----------------------------------------------------------------------------
_mtc0_r22: ;  0x16
        jmp     _mtc0_unknown           ; Done
;-----------------------------------------------------------------------------
_mtc0_r23: ;  0x17
        jmp     _mtc0_unknown           ; Done
;-----------------------------------------------------------------------------
_mtc0_r24: ;  0x18
        jmp     _mtc0_unknown           ; Done
;-----------------------------------------------------------------------------
_mtc0_r25: ;  0x19
        jmp     _mtc0_unknown           ; Done
;-----------------------------------------------------------------------------
_mtc0_r26: ;  0x1a
        jmp     _mtc0_unknown           ; Done
;-----------------------------------------------------------------------------
_mtc0_r27: ;  0x1b
        jmp     _mtc0_unknown           ; Done
;-----------------------------------------------------------------------------
_mtc0_r28: ;  0x1c
        jmp     _mtc0_unknown           ; Done
;-----------------------------------------------------------------------------
_mtc0_r29: ;  0x1d
        jmp     _mtc0_unknown           ; Done
;-----------------------------------------------------------------------------
_mtc0_r30: ;  0x1e
        jmp     _mtc0_unknown           ; Done
;-----------------------------------------------------------------------------
_mtc0_r31: ;  0x1f
        jmp     _mtc0_unknown           ; Done
;-----------------------------------------------------------------------------
_mtc0_unknown:
        EGGOG   "Unknown CP0 Reg Selector in MTC0!"
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; I/O
;-----------------------------------------------------------------------------
_Write_Char:
        PUSHA
        mov     byte [IOBUF], dl
        mov     rdx, 1
        mov     rax, SYS_WRITE
        mov     rsi, IOBUF
        mov     rdi, STDOUT
        syscall
        POPA
        ret

;; return char in IOBUF
_Read_Char_Blocking:
        PUSHA
        mov     rdi, 0      ; STDIN
        mov     rsi, IOBUF  ; where to put
        mov     rdx, 1      ; read one char
        mov     rax, 0
        syscall
        POPA
        Flg_On  UART_Input_Ready ; cock the 'input ready' flag
        ret
;-----------------------------------------------------------------------------


;-----------------------------------------------------------------------------
; Debuggisms
;-----------------------------------------------------------------------------
; _Write_LF:
;         PUSHA
;         mov     rdx, 1
;         mov     rax, SYS_WRITE
;         mov     rsi, _linefeed
;         mov     rdi, STDOUT
;         syscall
;         POPA
;         ret
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Print hex value of EAX to STDOUT.
;-----------------------------------------------------------------------------
; _Dump_Byte:
;         PUSHA
;         mov     ebp, eax
;         shr     eax, 4
;         call    _dig
;         mov     eax, ebp
;         call    _dig
;         POPA
;         ret
; _Dump_EAX:
;         PUSHA
;         mov     ebp, eax
;         mov     ebx, 28
; _do_dig:
;         mov     eax, ebp
;         mov     ecx, ebx
;         shr     eax, cl
;         call    _dig
;         sub     ebx, 4
;         jnc     _do_dig
;         POPA
;         ret
; _dig:
;         and     eax, 0xF
;         mov     rsi, _digits
;         add     rsi, rax
;         mov     rdx, 0x1
;         mov     rax, SYS_WRITE
;         mov     rdi, STDOUT
;         syscall
;         ret
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
; Opcode Tables
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
;-----------------------------------------------------------------------------
section .other progbits  alloc  noexec  nowrite  align=8
;-----------------------------------------------------------------------------
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_I_Table:
        dq _i_000000   ;  0x00 : All R-Type Instructions
        dq _i_000001   ;  0x01 : bltz, bgez, bltzl, bgezl, bltzal, bgezal
        dq _i_j        ;  0x02 : j       (000010??????????????????????????)
        dq _i_jal      ;  0x03 : jal     (000011??????????????????????????)
        dq _i_beq      ;  0x04 : beq     (000100??????????????????????????)
        dq _i_bne      ;  0x05 : bne     (000101??????????????????????????)
        dq _i_blez     ;  0x06 : blez    (000110??????????????????????????)
        dq _i_bgtz     ;  0x07 : bgtz    (000111??????????????????????????)
        dq _i_addi     ;  0x08 : addi    (001000??????????????????????????)
        dq _i_addiu    ;  0x09 : addiu   (001001??????????????????????????)
        dq _i_slti     ;  0x0a : slti    (001010??????????????????????????)
        dq _i_sltiu    ;  0x0b : sltiu   (001011??????????????????????????)
        dq _i_andi     ;  0x0c : andi    (001100??????????????????????????)
        dq _i_ori      ;  0x0d : ori     (001101??????????????????????????)
        dq _i_xori     ;  0x0e : xori    (001110??????????????????????????)
        dq _i_lui      ;  0x0f : lui     (001111??????????????????????????)
        dq _i_010000   ;  0x10 : mfc0, mtc0, tlbwi, tlbwr, tlbp, eret, wait
        dq _bad        ;  0x11 : UNDEFINED
        dq _bad        ;  0x12 : UNDEFINED
        dq _bad        ;  0x13 : UNDEFINED
        dq _i_beql     ;  0x14 : beql    (010100??????????????????????????)
        dq _i_bnel     ;  0x15 : bnel    (010101??????????????????????????)
        dq _i_blezl    ;  0x16 : blezl   (010110??????????????????????????)
        dq _i_bgtzl    ;  0x17 : bgtzl   (010111?????00000????????????????)
        dq _bad        ;  0x18 : UNDEFINED
        dq _bad        ;  0x19 : UNDEFINED
        dq _bad        ;  0x1a : UNDEFINED
        dq _bad        ;  0x1b : UNDEFINED
        dq _i_mul      ;  0x1c : mul     (011100???????????????00000000010)
        dq _bad        ;  0x1d : UNDEFINED
        dq _bad        ;  0x1e : UNDEFINED
        dq _bad        ;  0x1f : UNDEFINED
        dq _i_lb       ;  0x20 : lb      (100000??????????????????????????)
        dq _i_lh       ;  0x21 : lh      (100001??????????????????????????)
        dq _i_lwl      ;  0x22 : lwl     (100010??????????????????????????)
        dq _i_lw       ;  0x23 : lw      (100011??????????????????????????)
        dq _i_lbu      ;  0x24 : lbu     (100100??????????????????????????)
        dq _i_lhu      ;  0x25 : lhu     (100101??????????????????????????)
        dq _i_lwr      ;  0x26 : lwr     (100110??????????????????????????)
        dq _bad        ;  0x27 : UNDEFINED
        dq _i_sb       ;  0x28 : sb      (101000??????????????????????????)
        dq _i_sh       ;  0x29 : sh      (101001??????????????????????????)
        dq _i_swl      ;  0x2a : swl     (101010??????????????????????????)
        dq _i_sw       ;  0x2b : sw      (101011??????????????????????????)
        dq _bad        ;  0x2c : UNDEFINED
        dq _bad        ;  0x2d : UNDEFINED
        dq _i_swr      ;  0x2e : swr     (101110??????????????????????????)
        dq _i_cache    ;  0x2f : cache   (101111??????????????????????????)
        dq _i_ll       ;  0x30 : ll      (110000??????????????????????????)
        dq _bad        ;  0x31 : UNDEFINED
        dq _bad        ;  0x32 : UNDEFINED
        dq _i_pref     ;  0x33 : pref    (110011??????????????????????????)
        dq _bad        ;  0x34 : UNDEFINED
        dq _bad        ;  0x35 : UNDEFINED
        dq _bad        ;  0x36 : UNDEFINED
        dq _bad        ;  0x37 : UNDEFINED
        dq _i_sc       ;  0x38 : sc      (111000??????????????????????????)
        dq _bad        ;  0x39 : UNDEFINED
        dq _bad        ;  0x3a : UNDEFINED
        dq _bad        ;  0x3b : UNDEFINED
        dq _bad        ;  0x3c : UNDEFINED
        dq _bad        ;  0x3d : UNDEFINED
        dq _bad        ;  0x3e : UNDEFINED
        dq _bad        ;  0x3f : UNDEFINED
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_R_Table:
        dq _r_sll      ;  0x00 : sll     (000000????????????????????000000)
        dq _bad        ;  0x01 : UNDEFINED
        dq _r_srl      ;  0x02 : srl     (000000????????????????????000010)
        dq _r_sra      ;  0x03 : sra     (000000????????????????????000011)
        dq _r_sllv     ;  0x04 : sllv    (000000????????????????????000100)
        dq _bad        ;  0x05 : UNDEFINED
        dq _r_srlv     ;  0x06 : srlv    (000000????????????????????000110)
        dq _r_srav     ;  0x07 : srav    (000000????????????????????000111)
        dq _r_jr       ;  0x08 : jr      (000000????????????????????001000)
        dq _r_jalr     ;  0x09 : jalr    (000000????????????????????001001)
        dq _r_movz     ;  0x0a : movz    (000000???????????????00000001010)
        dq _r_movn     ;  0x0b : movn    (000000???????????????00000001011)
        dq _r_syscall  ;  0x0c : syscall (000000????????????????????001100)
        dq _bad        ;  0x0d : UNDEFINED
        dq _bad        ;  0x0e : UNDEFINED
        dq _r_sync     ;  0x0f : sync    (000000????????????????????001111)
        dq _r_mfhi     ;  0x10 : mfhi    (000000????????????????????010000)
        dq _r_mthi     ;  0x11 : mthi    (000000????????????????????010001)
        dq _r_mflo     ;  0x12 : mflo    (000000????????????????????010010)
        dq _r_mtlo     ;  0x13 : mtlo    (000000????????????????????010011)
        dq _bad        ;  0x14 : UNDEFINED
        dq _bad        ;  0x15 : UNDEFINED
        dq _bad        ;  0x16 : UNDEFINED
        dq _bad        ;  0x17 : UNDEFINED
        dq _r_mult     ;  0x18 : mult    (000000????????????????????011000)
        dq _r_multu    ;  0x19 : multu   (000000????????????????????011001)
        dq _r_div      ;  0x1a : div     (000000????????????????????011010)
        dq _r_divu     ;  0x1b : divu    (000000????????????????????011011)
        dq _bad        ;  0x1c : UNDEFINED
        dq _bad        ;  0x1d : UNDEFINED
        dq _bad        ;  0x1e : UNDEFINED
        dq _bad        ;  0x1f : UNDEFINED
        dq _r_add      ;  0x20 : add     (000000????????????????????100000)
        dq _r_addu     ;  0x21 : addu    (000000????????????????????100001)
        dq _r_sub      ;  0x22 : sub     (000000????????????????????100010)
        dq _r_subu     ;  0x23 : subu    (000000????????????????????100011)
        dq _r_and      ;  0x24 : and     (000000????????????????????100100)
        dq _r_or       ;  0x25 : or      (000000????????????????????100101)
        dq _r_xor      ;  0x26 : xor     (000000????????????????????100110)
        dq _r_nor      ;  0x27 : nor     (000000????????????????????100111)
        dq _bad        ;  0x28 : UNDEFINED
        dq _bad        ;  0x29 : UNDEFINED
        dq _r_slt      ;  0x2a : slt     (000000????????????????????101010)
        dq _r_sltu     ;  0x2b : sltu    (000000????????????????????101011)
        dq _bad        ;  0x2c : UNDEFINED
        dq _bad        ;  0x2d : UNDEFINED
        dq _bad        ;  0x2e : UNDEFINED
        dq _bad        ;  0x2f : UNDEFINED
        dq _bad        ;  0x30 : UNDEFINED
        dq _bad        ;  0x31 : UNDEFINED
        dq _bad        ;  0x32 : UNDEFINED
        dq _bad        ;  0x33 : UNDEFINED
        dq _bad        ;  0x34 : UNDEFINED
        dq _bad        ;  0x35 : UNDEFINED
        dq _r_tne      ;  0x36 : tne     (000000????????????????????110110)
        dq _bad        ;  0x37 : UNDEFINED
        dq _bad        ;  0x38 : UNDEFINED
        dq _bad        ;  0x39 : UNDEFINED
        dq _bad        ;  0x3a : UNDEFINED
        dq _bad        ;  0x3b : UNDEFINED
        dq _bad        ;  0x3c : UNDEFINED
        dq _bad        ;  0x3d : UNDEFINED
        dq _bad        ;  0x3e : UNDEFINED
        dq _bad        ;  0x3f : UNDEFINED
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_B_Table:
        dq _b_bltz     ;  0x00 : bltz    (000001?????00000????????????????)
        dq _b_bgez     ;  0x01 : bgez    (000001?????00001????????????????)
        dq _b_bltzl    ;  0x02 : bltzl   (000001?????00010????????????????)
        dq _b_bgezl    ;  0x03 : bgezl   (000001?????00011????????????????)
        dq _bad        ;  0x04 : UNDEFINED
        dq _bad        ;  0x05 : UNDEFINED
        dq _bad        ;  0x06 : UNDEFINED
        dq _bad        ;  0x07 : UNDEFINED
        dq _bad        ;  0x08 : UNDEFINED
        dq _bad        ;  0x09 : UNDEFINED
        dq _bad        ;  0x0a : UNDEFINED
        dq _bad        ;  0x0b : UNDEFINED
        dq _bad        ;  0x0c : UNDEFINED
        dq _bad        ;  0x0d : UNDEFINED
        dq _bad        ;  0x0e : UNDEFINED
        dq _bad        ;  0x0f : UNDEFINED
        dq _b_bltzal   ;  0x10 : bltzal  (000001????x10000????????????????)
        dq _b_bgezal   ;  0x11 : bgezal  (000001????x10001????????????????)
        dq _bad        ;  0x12 : UNDEFINED
        dq _bad        ;  0x13 : UNDEFINED
        dq _bad        ;  0x14 : UNDEFINED
        dq _bad        ;  0x15 : UNDEFINED
        dq _bad        ;  0x16 : UNDEFINED
        dq _bad        ;  0x17 : UNDEFINED
        dq _bad        ;  0x18 : UNDEFINED
        dq _bad        ;  0x19 : UNDEFINED
        dq _bad        ;  0x1a : UNDEFINED
        dq _bad        ;  0x1b : UNDEFINED
        dq _bad        ;  0x1c : UNDEFINED
        dq _bad        ;  0x1d : UNDEFINED
        dq _bad        ;  0x1e : UNDEFINED
        dq _bad        ;  0x1f : UNDEFINED
;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_M_Table:
        dq _bad        ;  0x00 : UNDEFINED
        dq _bad        ;  0x01 : UNDEFINED
        dq _m_tlbwi    ;  0x02 : tlbwi   (01000010000000000000000000000010)
        dq _bad        ;  0x03 : UNDEFINED
        dq _bad        ;  0x04 : UNDEFINED
        dq _bad        ;  0x05 : UNDEFINED
        dq _m_tlbwr    ;  0x06 : tlbwr   (01000010000000000000000000000110)
        dq _bad        ;  0x07 : UNDEFINED
        dq _m_tlbp     ;  0x08 : tlbp    (01000010000000000000000000001000)
        dq _bad        ;  0x09 : UNDEFINED
        dq _bad        ;  0x0a : UNDEFINED
        dq _bad        ;  0x0b : UNDEFINED
        dq _bad        ;  0x0c : UNDEFINED
        dq _bad        ;  0x0d : UNDEFINED
        dq _bad        ;  0x0e : UNDEFINED
        dq _bad        ;  0x0f : UNDEFINED
        dq _bad        ;  0x10 : UNDEFINED
        dq _bad        ;  0x11 : UNDEFINED
        dq _bad        ;  0x12 : UNDEFINED
        dq _bad        ;  0x13 : UNDEFINED
        dq _bad        ;  0x14 : UNDEFINED
        dq _bad        ;  0x15 : UNDEFINED
        dq _bad        ;  0x16 : UNDEFINED
        dq _bad        ;  0x17 : UNDEFINED
        dq _m_eret     ;  0x18 : eret    (01000010000000000000000000011000)
        dq _bad        ;  0x19 : UNDEFINED
        dq _bad        ;  0x1a : UNDEFINED
        dq _bad        ;  0x1b : UNDEFINED
        dq _bad        ;  0x1c : UNDEFINED
        dq _bad        ;  0x1d : UNDEFINED
        dq _bad        ;  0x1e : UNDEFINED
        dq _bad        ;  0x1f : UNDEFINED
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_MFC0_Table:
        dq _mfc0_r00   ;  0x00
        dq _mfc0_r01   ;  0x01
        dq _mfc0_r02   ;  0x02
        dq _mfc0_r03   ;  0x03
        dq _mfc0_r04   ;  0x04
        dq _mfc0_r05   ;  0x05
        dq _mfc0_r06   ;  0x06
        dq _mfc0_r07   ;  0x07
        dq _mfc0_r08   ;  0x08
        dq _mfc0_r09   ;  0x09
        dq _mfc0_r10   ;  0x0a
        dq _mfc0_r11   ;  0x0b
        dq _mfc0_r12   ;  0x0c
        dq _mfc0_r13   ;  0x0d
        dq _mfc0_r14   ;  0x0e
        dq _mfc0_r15   ;  0x0f
        dq _mfc0_r16   ;  0x10
        dq _mfc0_r17   ;  0x11
        dq _mfc0_r18   ;  0x12
        dq _mfc0_r19   ;  0x13
        dq _mfc0_r20   ;  0x14
        dq _mfc0_r21   ;  0x15
        dq _mfc0_r22   ;  0x16
        dq _mfc0_r23   ;  0x17
        dq _mfc0_r24   ;  0x18
        dq _mfc0_r25   ;  0x19
        dq _mfc0_r26   ;  0x1a
        dq _mfc0_r27   ;  0x1b
        dq _mfc0_r28   ;  0x1c
        dq _mfc0_r29   ;  0x1d
        dq _mfc0_r30   ;  0x1e
        dq _mfc0_r31   ;  0x1f
;-----------------------------------------------------------------------------

;-----------------------------------------------------------------------------
align GRAIN, db 0x90
_MTC0_Table:
        dq _mtc0_r00   ;  0x00
        dq _mtc0_r01   ;  0x01
        dq _mtc0_r02   ;  0x02
        dq _mtc0_r03   ;  0x03
        dq _mtc0_r04   ;  0x04
        dq _mtc0_r05   ;  0x05
        dq _mtc0_r06   ;  0x06
        dq _mtc0_r07   ;  0x07
        dq _mtc0_r08   ;  0x08
        dq _mtc0_r09   ;  0x09
        dq _mtc0_r10   ;  0x0a
        dq _mtc0_r11   ;  0x0b
        dq _mtc0_r12   ;  0x0c
        dq _mtc0_r13   ;  0x0d
        dq _mtc0_r14   ;  0x0e
        dq _mtc0_r15   ;  0x0f
        dq _mtc0_r16   ;  0x10
        dq _mtc0_r17   ;  0x11
        dq _mtc0_r18   ;  0x12
        dq _mtc0_r19   ;  0x13
        dq _mtc0_r20   ;  0x14
        dq _mtc0_r21   ;  0x15
        dq _mtc0_r22   ;  0x16
        dq _mtc0_r23   ;  0x17
        dq _mtc0_r24   ;  0x18
        dq _mtc0_r25   ;  0x19
        dq _mtc0_r26   ;  0x1a
        dq _mtc0_r27   ;  0x1b
        dq _mtc0_r28   ;  0x1c
        dq _mtc0_r29   ;  0x1d
        dq _mtc0_r30   ;  0x1e
        dq _mtc0_r31   ;  0x1f
;-----------------------------------------------------------------------------

; _linefeed:  db 0xA, 0
; _digits:    db "0123456789abcdef", 0
