################################################################################
##
## LSU EE 4720 Spring 2022 Homework 1
##
##
 ## SOLUTION

 # Assignment https://www.ece.lsu.edu/ee4720/2022/hw01.pdf
 # Solution Discussion https://www.ece.lsu.edu/ee4720/2022/hw01_sol.pdf

################################################################################
## Problem 1
#
#  Instructions: https://www.ece.lsu.edu/ee4720/2022/hw01.pdf

strlen_p1:
        ## Register Usage
        #
        # CALL VALUE
        #  $a0: Address of first character of string.
        #       This address will be a multiple of 4.
        #
        # RETURN
        #  $v0: The length of the string (not including the null).
        #
        # Note:
        #  Can modify registers $t0-$t9, $a0-$a3, $v0, $v1.
        #  DO NOT modify other registers.
        #
        # [✔] The testbench should show 0 errors.
        # [✔] Try to reduce the number of executed instructions.
        # [✔] Do not use pseudoinstructions except for nop.
        #     Do not use: li, la, mov, bgt, blt, etc.
        #
        # [✔] Code should be efficient.
        # [✔] The code should be clearly written.
        # [ ] Comments should be written for an experienced programmer.

        addi $v0, $a0, 0

        # SOLUTION: Prepare masks for two high bytes.
        #
        lui $t6, 0xff00   # t6 ->  0xff000000
        lui $t7, 0xff     # t7 ->    0xff0000
        #
        # In the version using lhu it was possible to mask off each
        # byte using an immediate, such as in "andi $t1, $t0, 0xff".
        # But since immediate values are limited to 16 bits we need to
        # use some other method to get the mask in a register. The
        # method used above is an lui instruction.
        
LOOP:                         # SOLUTION described by comments below.
        lw $t0, 0($a0)        # Change lhu to lw
        and $t1, $t0, $t6     # Mask off most-significant byte ..
        beq $t1, $0, DONE0    # .. if it is zero we are done ..
        and $t1, $t0, $t7     # .. otherwise mask off next byte ..
        beq $t1, $0, DONE1    # .. and if that one is zero we're done ..
        andi $t1, $t0, 0xff00 # .. otherwise mask the next one ......
        beq $t1, $0, DONE2
        andi $t1, $t0, 0xff
        bne $t1, $0, LOOP
        addi $a0, $a0, 4

        sub $v0, $a0, $v0
        jr $ra
        addi $v0, $v0, -1

DONE2:  # SOLUTION: Modify the case for byte 2
        sub $v0, $a0, $v0
        jr $ra
        addi $v0, $v0, 2

DONE1:  # SOLUTION: Add a case for byte 1
        sub $v0, $a0, $v0
        jr $ra
        addi $v0, $v0, 1

DONE0:
        jr $ra
        sub $v0, $a0, $v0

################################################################################
## Problem 2
#

strlen_p2:
        # CALL VALUE
        #  $a0: Address of first character of string.
        #       This address will be a multiple of 4.
        #
        # RETURN
        #  $v0: The length of the string (not including the null).
        #
        # Note:
        #  Can modify registers $t0-$t9, $a0-$a3, $v0, $v1.
        #  DO NOT modify other registers.
        #
        # [✔] Use lw to load from string. (Replace the lbu)
        # [✔] Make use of orc.b insn. Consider using clz, clo.
        # [✔] The testbench should show 0 errors.
        # [✔] Try to reduce the number of executed instructions.
        # [✔] Do not use pseudoinstructions except for nop.
        #     Do not use: li, la, mov, bgt, blt, etc.
        #
        # [✔] Code should be efficient.
        # [✔] The code should be clearly written.
        # [ ] Comments should be written for an experienced programmer.

        ## SOLUTION Approach
        #
        # In main loop check whether there is a null and do so using
        # the minimum number of instructions. The orc.b instruction
        # returns a 0xffffffff if no null is found, and it's easy to
        # check for that. If a null is found, exit the loop and *then*
        # figure out where.

        addi $v0, $a0, 4
        addi $t3, $0, -1  # SOLUTION: Prepare value: 0xffffffff
        
LOOPB:
        lw $t0, 0($a0)
        orc.b $t1, $t0

        # SOLUTION:  Example execution of orc.b:
        #       String at address in $a0: "AB"
        # t0:   A B ∅ X            <-- Value in ASCII. ∅ is null, X unknown.
        # t0: 0x41420099           <-- Value in hex.
        # t0: 0x41 0x42 0x00 0x99  <-- Value separated into four bytes
        # t1: 0xff 0xff 0x00 0xff  <-- Result. Non-zero bytes changed to 0xff
        # t1: 0xffff00ff           <-- Result. Non-zero bytes changed to 0xff

        beq $t1, $t3, LOOPB        # If t1 is 0xffffffff then no null found ..
        addi $a0, $a0, 4           # .. so increment address for next word.

        # A null has been found. Count number  of leading 1's to find out.
        # t1: 0xffff00ff           <-- Continuing with example.
        clo $t2, $t1
        # t2: 16                   <-- Found 16 consecutive 1's starting at MSB.
        sra $t2, $t2, 3            # Effectively divide by 8.
        # t2:  2                   <-- 16 1's means 2 bytes

        sub $v0, $a0, $v0          # Add on number of bytes before null.

        jr $ra
        add $v0, $v0, $t2



##############################################################################
 #

strlen_ref:
        ## Register Usage
        #
        # $a0: Address of first character of string.
        # $v0: Return value, the length of the string.

        addi $v0, $a0, 1        # Set aside a copy of the string start + 1.
        
REF_LOOP:
        lbu $t0, 0($a0)         # Load next character in string into $t0
        bne $t0, $0, REF_LOOP   # If it's not zero, continue
        addi $a0, $a0, 1        # Increment address. (Note: Delay slot insn.)
                
        jr $ra
        sub $v0, $a0, $v0


##############################################################################
#
 ## Test Code
#
#  The code below calls the strlen routines.

        .data
str:
        .align 2
        .asciiz "1"
        .align 2, -1
        .asciiz "12"
        .align 2, -1
        .asciiz "123"
        .align 2, -1
        .asciiz "1234"
        .align 2, -1
        .asciiz "12345"
        .align 2, -1
        .asciiz ""
        .align 2, -1
        .asciiz "\"Per aspera, ad astra!\""
        .align 2, -1
        .ascii "eighteen quintillion, four hundred forty six quadrillion, "
        .ascii "seven hundred forty four trillion, seventy three billion, "
        .ascii "seven hundred nine million, five hundred fifty one thousand, "
        .asciiz "six hundred sixteen"
msg:
        .asciiz "String %/s2/2d: Length %/v1/3d is "
msg_good:
        .asciiz "correct. Took %/s4/3d insn or %/f6/.3f char/insn\n"
msg_bad:
        .asciiz "wrong. Should be %/s7/3d.\n"

mut_strlen_p1:
        .word strlen_p1
        .asciiz "strlen_p1  (Problem 1 - Bit Ops)"
mut_strlen_p2:
        .word strlen_p2
        .asciiz "strlen_p2  (Problem 2 - RISC V orc insn)"
mut_strlen_ref:
        .word strlen_ref
        .asciiz "strlen_ref (Simple strlen routine.)"
muts:
        .word mut_strlen_p1
        .word mut_strlen_p2
        .word mut_strlen_ref
        .word 0

mut_msg:
        .asciiz "\n** Starting Test of Routine \"%/t0/s\" **\n"

        .text
        .globl __start
__start:
        mtc0 $0, $22            # Pause tracing.
        addi $s1, $0, 0
TBOUTER:
        la $t0, muts
        sll $t1, $s1, 2
        add $t1, $t1, $t0
        lw $s0, 0($t1)

        bne $s0, $0, TB_MORE
        nop

        addi $v0, $0, 10        # System call code for exit.
        syscall
TB_MORE:

        la $a0, mut_msg
        addi $t0, $s0, 4
        addi $v0, $0, 11
        syscall

        la $a0, str
        addi $s6, $a0, 0        # Save copy of string starting address.
        addi $s2, $0, 0
TBLOOP:
        addi $a0, $s6, 0
        jal strlen_ref
        addi $s2, $s2, 1
        addi $s7, $v0, 0

        addi $a0, $s6, 0
        addi $v0, $0, -1
        lw $t0, 0($s0)
        mtc0 $v0, $22           # Resume tracing. (No effect if not stepping.)
        jalr $t0
        mfc0 $s5, $9            # Copy current instruction count. (Before.)
        mfc0 $s4, $9            # Copy current instruction count. (After.)
        mtc0 $0, $22            # Pause tracing.
        addi $s4, $s4, -1
        sub $s4, $s4, $s5
        mtc1 $s4, $f4
        cvt.d.w $f4, $f4
        mtc1 $v0, $f0
        cvt.d.w $f0, $f0
        div.d $f6, $f0, $f4
        
        addi $v1, $v0, 0        # Move length of string to $v1
        addi $v0, $0, 11        # System call code for message.
        la $a0, msg             # Address of message.
        syscall

        la $a0, msg_good
        beq $v1, $s7  TB_CONTINUE
        nop
        la $a0, msg_bad
TB_CONTINUE:
        syscall

        add $s6, $s6, $s7
        ori $s6, $s6, 0x3
        addi $s6, $s6, 1
        la $a0, msg
        slt $t0, $s6, $a0
        bne $t0, $0, TBLOOP
        nop

        j TBOUTER
        addi $s1, $s1, 1