################################################################################
##
## LSU EE 4720 Spring 2024 Homework 1 -- SOLUTION
##
##
 # Assignment https://www.ece.lsu.edu/ee4720/2024/hw01.pdf

################################################################################
## Problem 1 -- SOLUTION
#
        .text
aadd1:  ## Note: Not called by testbench.
        ## Register Usage
        #
        # CALL VALUES
        #  $a0: Address of the output buffer in which to write the result.
        #  $a1: Address of operand 1, an ASCII string.
        #  $a2: Address of operand 2, an ASCII string.
        #  $a3: Length of operands:
        #       Operand 1 length: bits 31:16
        #       Operand 2 length: bits 15:0
        #
        # RETURN VALUE
        #  $v0: Address of start of the result. Must be within output buffer.
        #
        # [✔] Can only modify these registers: $t0-$t9, $a0-$a3,  $v0,$v1
        # [✔] The fewer instructions executed, the better. But not a priority.
        # [✔] Write code clearly, comment for an expert MIPS programmer.
        # [✔] Do not use pseudoinstructions except for nop.

        srl $t8, $a3, 16         # Length of operand 1
        andi $t9, $a3, 0xffff    # Length of operand 2

        ## Problem 1 -- SOLUTION
        #
        #  This code just copies operand 1 into the output buffer.
        #  It does not perform addition. See the Problem 2 solutions
        #  for code that adds to two operands together.

        addi $v0, $a0, 0  # Put address of copied operand (the result) in v0.

TRY_LOOP:
        lb $t0, 0($a1)         # Load a character from operand 1 ..
        sb $t0, 0($a0)         # .. and write it into the output buffer.
        addi $a0, $a0, 1       # Increment output buffer address.
        bne $t0, $0, TRY_LOOP  # Check whether operand 1 ends.
        addi $a1, $a1, 1       # Increment operand 1 address.
        #
        # Note that the addi $a1 above executes whether or not bne is taken.

        jr $ra
        nop


################################################################################
## Problem 2 -- SOLUTION -- Simple
#
        .text
aadd:   ## ASCII Add
        ## Register Usage
        #
        # CALL VALUES
        #  $a0: Address of the output buffer in which to write the result.
        #  $a1: Address of operand 1, an ASCII string.
        #  $a2: Address of operand 2, an ASCII string.
        #  $a3: Length of operands:
        #       Operand 1 length: bits 31:16
        #       Operand 2 length: bits 15:0
        #
        # RETURN VALUE
        #  $v0: Address of start of the result. Must be within output buffer.
        #
        # [✔] Write memory at a0 with string holding sum of a1 and a2 ..
        #     .. all are decimal integers in ASCII string representation.
        # [✔] Do not convert operands 1 and 2 to integer values ..
        #     .. operate on them as ASCII strings.
        #
        # [✔] Testbench should show zero errors.
        # [✔] Can only modify these registers: $t0-$t9, $a0-$a3,  $v0,$v1
        # [ ] The fewer instructions executed, the better. But not a priority.
        # [✔] Write code clearly, comment for an expert MIPS programmer.
        # [✔] Do not use pseudoinstructions except for nop.

        srl $t8, $a3, 16         # t8: Length of operand 1
        andi $t9, $a3, 0xffff    # t9: Length of operand 2

        ## SOLUTION -- Simple
        #
        # This solution was written to be easy to understand rather
        # than to be fast. It is kept simple by having one main loop
        # and by not tossing instructions far from their natural place
        # just for the sake of filling a delay slot. A fast solution
        # (which does toss instructions around) follows this simple
        # one.
        
        ## Performance Comparison
        #
        ## This (Simple) Solution
        #
        # Num Insn:    33  Correct:  1 + 1 = 2 
        # Num Insn:    53  Correct:  45 + 55 = 100 
        # Num Insn:   110  Correct:  999999 + 1 = 1000000 
        # Num Insn:   154  Correct:  765432 + 12345678 = 13111110 
        # Num Insn:   295  Correct:  184737252196092 + 8383352872579977 = 8568090124776069
        #
        ## Fast Solution (Further Below)
        #
        # Num Insn:    30  Correct:  1 + 1 = 2 
        # Num Insn:    45  Correct:  45 + 55 = 100 
        # Num Insn:    62  Correct:  999999 + 1 = 1000000 
        # Num Insn:   113  Correct:  765432 + 12345678 = 13111110 
        # Num Insn:   217  Correct:  184737252196092 + 8383352872579977 = 856809

        ## SOLUTION -- Simple

        # Write LSD of sum near the end of the output buffer. Assume
        # output buffer size is 20 characters.
        #
        addi $v0, $a0, 20    # v0: Pointer into output buffer. Start at end.

        # Write null terminator into output buffer.
        #
        addi $v0, $v0, -1
        sb $0, 0($v0)        # Write sum's null terminator.

        # Compute the address of the null terminator of operands 1 and 2.
        #
        add $t1, $a1, $t8    # t1: Operand 1 pointer.  Set to null pointer addr.
        add $t2, $a2, $t9    # t2: Operand 2 pointer.  Set to null pointer addr.

        # Initialize a carry register and a handy constant.
        #
        addi $t5, $0, 0      # t5: The carry register. Initialize to zero.
        addi $v1, $0, 57     # v1: A handy constant: the ASCII character '9'

        ## Main Loop
        #
        # Each iteration of the main loop adds a digit of operand 1
        # (if any remain) to a digit of operand 2 (if any remain) plus
        # the carry from the previous iteration (carried by t5).
        # 
        # Example values are shown in comments prefixed with Eg. In
        # particular they show the operand 1 digit as '4', the operand
        # 2 digit as '7', and a carry in (t5 is 1).
LOOP:
        beq $t1, $a1, OP2_CHECK  # If no more digits of op 1, check operand 2.
        addi $t3, $0, 48     # Put ASCII '0' in t3. Used only if beq taken.
        addi $t1, $t1, -1    # Decrement operand 1 pointer.
        lb $t3, 0($t1)       # Load the next ASCII digit of operand 1
                             # Eg: t3 <- 52  or  '4' (ASCII)
OP2_CHECK:
        beq $t2, $a2, DIGITS_ADD  # If no more digits of op 2, start adding.
        addi $t4, $0, 0      # Put value 0 in t4. Used only if beq taken.
        addi $t2, $t2, -1    # Decrement operand 2 pointer
        lb $t4, 0($t2)       # Load the next ASCII digit of operand 2
                             # Eg: t4 <- 55  or  '7' (ASCII)
        addi $t4, $t4, -48   # Convert operand 2 from ASCII to an integer.
                             # Eg: t4 <- 55 - 48 = 7 (integer)
DIGITS_ADD:
        add $t3, $t3, $t4    # Add operand 1 (ASCII) to operand 2 (integer).
                             # Eg: t3 <- 52 + 7 = 59  or  '4' + 7 = ';'
        add $t3, $t3, $t5    # Add carry to ASCII digit.
                             # Eg: t3 <- 59 + 1 = 60  or  ';' + 1 = '<'
        slt $t5, $v1, $t3    # Check whether ASCII digit >9 (whether carried)
                             # Eg: t5 <- 57 < 60 = 1 (true)  or  '9' < '<' = 1
        beq $t5, $0, No_CAR  # If so, skip digit adjustment.
        nop                  # Oh my, an unfilled delay slot.
        addi $t3, $t3, -10   # Subtract 10, t5 will carry it to next iteration.
                             # Eg: t3 <- 60 - 10 = 50  or  ';' - 10 = '2'
No_CAR:
        addi $v0, $v0, -1    # Decrement output buffer pointer ..
        bne $t1, $a1, LOOP   # If still more of operand 1, loop.
        sb $t3, 0($v0)       # .. but don't forget to store digit of sum!

        bne $t2, $a2, OP2_CHECK  # If still more of operand 2, loop.
        addi $t3, $0, 48     # Put ASCII '0' in t3. Ignored if not taken

        # At this point all digits of operand 1 and 2 have been
        # examined. Just need to check if there is a final carry out.

        beq $t5, $0, DONE
        addi $t0, $0, 49     # ASCII '1'.  Ignored if taken.

        addi $v0, $v0, -1    # Decrement pointer ..
        jr $ra
        sb $t0, 0($v0)       # .. and write ASCII '1' just as we are returning!

DONE:
        jr $ra
        nop



################################################################################
## Problem 2 -- SOLUTION -- Fast
#
#
        .text
faadd:  ## ASCII Add
        ## Register Usage
        #
        # CALL VALUES
        #  $a0: Address of the output buffer in which to write the result.
        #  $a1: Address of operand 1, an ASCII string.
        #  $a2: Address of operand 2, an ASCII string.
        #  $a3: Length of operands:
        #       Operand 1 length: bits 31:16
        #       Operand 2 length: bits 15:0
        #
        # RETURN VALUE
        #  $v0: Address of start of the result. Must be within output buffer.
        #
        # [✔] Write memory at a0 with string holding sum of a1 and a2 ..
        #     .. all are decimal integers in ASCII string representation.
        # [✔] Do not convert operands 1 and 2 to integer values ..
        #     .. operate on them as ASCII strings.
        #
        # [✔] Testbench should show zero errors.
        # [✔] Can only modify these registers: $t0-$t9, $a0-$a3,  $v0,$v1
        # [✔] The fewer instructions executed, the better. But not a priority.
        # [✔] Write code clearly, comment for an expert MIPS programmer.
        # [✔] Do not use pseudoinstructions except for nop.

        srl $t8, $a3, 16         # t8: Length of operand 1
        andi $t9, $a3, 0xffff    # t9: Length of operand 2

        ## SOLUTION -- Fast
        #
        # This solution was written to be fast. The key to making the
        # code fast is the use of three specialized loops. Each
        # specialized loop uses fewer instructions than the
        # all-purpose loop in the simple solution. The first
        # specialized loop is used as long as both operands have
        # digits remaining, and it only checks for the end of operand
        # 2. So if operand 1 is shorter than operand 2 the operands
        # are swapped before entering the first loop. The second
        # specialized loop handles the remaining digits of operand 1
        # until there is no carry. The third specialized loop handles
        # the remaining digits of operand 1 when there is no carry:
        # all it does is copy digits from operand 1 to the output
        # buffer.
        #
        # In addition to the three specialized loops, performance is
        # increased by filling more delay slots.
        #
        # Note that for this assignment, and this assignment ONLY,
        # performance is synonymous with the number of executed
        # instructions.
        
        ## Performance Comparison
        #
        ## Simple Solution (Further Above)
        #
        # Num Insn:    33  Correct:  1 + 1 = 2 
        # Num Insn:    53  Correct:  45 + 55 = 100 
        # Num Insn:   110  Correct:  999999 + 1 = 1000000 
        # Num Insn:   154  Correct:  765432 + 12345678 = 13111110 
        # Num Insn:   295  Correct:  184737252196092 + 8383352872579977 = 8568090124776069
        #
        ## This (Fast) Solution
        #
        # Num Insn:    30  Correct:  1 + 1 = 2 
        # Num Insn:    45  Correct:  45 + 55 = 100 
        # Num Insn:    62  Correct:  999999 + 1 = 1000000 
        # Num Insn:   113  Correct:  765432 + 12345678 = 13111110 
        # Num Insn:   217  Correct:  184737252196092 + 8383352872579977 = 8568090124776069 

        ## Insure that Operand 2 Length is Shorter or Same as Operand 1

        slt $t0, $t8, $t9    # Check whether operand 1 shorter than 1
        beq $t0, $0, OP1_LARGER
        addi $v1, $0, 57     # v1: A handy constant. ASCII character '9'

        # Operand 2 length is *longer* than operand 1 ..
        # .. so the code below will swap them.
        #
        addi $t0, $a1, 0
        addi $a1, $a2, 0         # a1: Address of start of longer operand.
        addi $a2, $t0, 0         # a2: Address of start of shorter operand.
        andi $t8, $a3, 0xffff    # t8: Length of longer operand.
        srl $t9, $a3, 16         # t9: Length of shorter operand.
        #
        # The swap took five instructions. It would only take two
        # instructions if MIPS had a swap instruction.

OP1_LARGER:
        # Operand 1 is now longer, perhaps because we swapped them.

        # Write LSD of sum near the end of the output buffer. Assume
        # output buffer size is 20 characters.
        #
        addi $t7, $a0, 19    # t7: Output buffer pointer. Init to LSD address.
        sb $0, 1($t7)        # Write sum's null terminator.

        # Compute the address of the null terminator of operands 1 and 2.
        #
        add $t1, $a1, $t8    # t1: Operand 1 pointer.  Set to null pointer addr.
        add $t2, $a2, $t9    # t2: Operand 2 pointer.  Set to null pointer addr.

        # Initialize a carry register.
        #
        addi $t5, $0, 0      # t5: The carry register. Initialize to zero.

        ## Main Loop
        #
        # Each iteration of the main loop adds a digit of operand 1 to
        # a digit of operand 2. The number of iterations of the main
        # loop is equal to the length of operand 2 (which can't be
        # longer than operand 1).
        #
        # Comments prefixed with Eg: show example values. In
        # particular they show the operand 1 digit as '4', the operand 2
        # digit as '7', and a carry in (t5 is 1).
MLOOP:
        addi $t1, $t1, -1    # Decrement operand 1 pointer.
        addi $t2, $t2, -1    # Decrement operand 2 pointer
        lb $t3, 0($t1)       # Load next ASCII digit of operand 1
                             # Eg: t3 <- 52  or  '4' (ASCII)
        lb $t4, 0($t2)       # Load next ASCII digit of operand 2
                             # Eg: t4 <- 55  or  '7' (ASCII)
        addi $t4, $t4, -48   # Convert operand 2 from ASCII to an integer.
                             # Eg: t4 <- 7
        add $t3, $t3, $t4    # Add operand 1 (ASCII) to operand 2 (integer).
                             # Eg: t3 <- 52 + 7 = 59  or  '4' + 7 = ';'
        add $t3, $t3, $t5    # Add carry to ASCII digit.
                             # Eg: t3 <- 59 + 1 = 60  or  ';' + 1 = '<'
        slt $t5, $v1, $t3    # Check whether ASCII digit >9 (whether carried)
                             # Eg: t5 <- 57 < 60 = 1 (true)  or  '9' < '<' = 1
        beq $t5, $0, NO_CAR  # If no carry, skip digit adjustment.
        addi $t7, $t7, -1    # Either way, decrement output buffer pointer.
        addi $t3, $t3, -10   # Digit adjustment: remove carried 10.
                             # Eg: t3 <- 60 - 10 = 50  or  ';' - 10 = '2'
NO_CAR:
        bne $t2, $a2, MLOOP  # If still more of operand 2, loop.
        sb $t3, 1($t7)       # Either way, store digit of sum.
        #
        # Notice that the offset in "sb $t3, 1($t7)" is 1. That's
        # because t7 is decremented *before* the sb is executed but we
        # want to use the value in t7 before the decrement. To get
        # that we add 1 (undoing the decrement).

        ## No More Operand 2 Digits

        # If there is no carry out, jump to code that copies the
        # remainder of operand 1 (if any) to the output buffer.
        #
        beq $t5, $0, OP1_COPY_ITER_TEST  # Jump to middle of loop.
        addi $t0, $0, 48     # t0: Prepare ASCII '0'. Not needed if taken.

        # If there are no more operand 1 digits, jump to the code that
        # writes the final carry out digit (a '1').
        # 
        beq $t1, $a1, ULTIMATE_CARRY
        addi $t1, $t1, -1    # Decrement operand pointer. Not needed if taken.


        ## Operand 1 Carry Loop
        #
        # We're past the end of operand 2 and we know there is a carry
        # in. Each iteration of the Operand 1 Carry Loop adds the
        # carry to a digit of operand 1 and writes the sum to the
        # output buffer. The loop ends when there is no carry or we
        # reach the MSD of operand 1, whichever happens first.
        #
OP1_CARRY_LOOP:
        lb $t3, 0($t1)       # Load next digit from operand 1.
        # If digit is not '9' (v1) then prepare to enter operand 1 copy loop.
        bne $t3, $v1, OP1_COPY_PROLOGUE
        addi $t7, $t7, -1
        sb $t0, 1($t7)       # Write digit '0' (t0).
        bne $t1, $a1, OP1_CARRY_LOOP  # Check whether there's more of operand 1.
        addi $t1, $t1, -1

ULTIMATE_CARRY:              # One final carry out to write.
        addi $t0, $0, 49     # ASCII '1'
        sb $t0, 0($t7)       # Write ASCII '1'.
        jr $ra
        addi $v0, $t7, 0

        # We can arrive at this loop from the OP1_CARRY_LOOP. Before
        # entering the OP1_COPY_LOOP we need to store a leftover sum
        # digit from the carry loop.
        #
OP1_COPY_PROLOGUE:
        addi $t3, $t3, 1     # Add carry on to digit.
        sb $t3, 1($t7)       # Store that leftover digit from the carry loop.
        addi $t7, $t7, -1
        bne $t1, $a1, OP1_COPY_LOOP  # Enter copy loop if there is more to copy.
        addi $t1, $t1, -1

        jr $ra
        addi $v0, $t7, 2

        ## Operand 1 Copy Loop
        #
        # At this point there is no carry in, and so there can't be a
        # carry out so just copy the remainder of operand 1 to the
        # output buffer.
OP1_COPY_LOOP:
        lb $t3, 0($t1)
        sb $t3, 1($t7)
OP1_COPY_ITER_TEST:
NO_MORE_CARRYS_EVER_CHECK_OP1:
        addi $t7, $t7, -1
        bne $t1, $a1, OP1_COPY_LOOP
        addi $t1, $t1, -1

        jr $ra
        addi $v0, $t7, 2




##############################################################################
#
 ## Test Code
#
#  The code below calls the lookup routine.

.text

tb_strlen:
        ## 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.
STRLEN_LOOP:
        lbu $t0, 0($a0)           # Load next character in string into $t0
        bne $t0, $0, STRLEN_LOOP  # If it's not zero, continue
        addi $a0, $a0, 1          # Increment address. (Note: Delay slot insn.)
        jr $ra
        sub $v0, $a0, $v0

strnext:
        ## Not written for general use.
        # CALL VALUE
        #  $s4: Address of a string.
        # RETURN VALUE
        #  $s4: One plus the address of the null terminator.
SN_LOOP:
        lb $t0, 0($s4)
        bne $t0, $0, SN_LOOP
        addi $s4, $s4, 1
        jr $ra
        nop


        .data

.globl table_numbers
table_numbers:
        .asciiz "12321", "0", "12321"
        .asciiz "1", "1", "2"
        .asciiz "45", "55", "100"
        .asciiz "9007", "2", "9009"
        .asciiz "5107", "8", "5115"
        .asciiz "3", "9002",  "9005"
        .asciiz "789", "67", "856"
        .asciiz "67", "789", "856"
        .asciiz "999999", "1", "1000000"
        .asciiz "765432", "12345678", "13111110"
        .asciiz "184737252196092", "8383352872579977", "8568090124776069"
table_numbers_end:
        .byte 0,0,0

        .align 4
buffer_result:
        .space 20
buffer_result_end:
        .space 4

text_v0_too_low:
        .asciiz "** $v0 too low **"
text_v0_too_high:
        .asciiz "** $v0 too high **"
text_correct:
        .asciiz "Correct"
text_wrong:
        .asciiz "Wrong  "

msg_result:
        .asciiz "Num Insn: %/s3/5d  %/t0/s:  %/s1/s + %/s2/s = %/s0/s \n"
msg_end:
        .asciiz "TOTALS:   Correct: %/s6/3d    Wrong: %/s5/2d\n"

aadd_name:
        .asciiz "Routine aadd - Simple"
faadd_name:
        .asciiz "Routine faadd - Fast"
msg_routine:
        .asciiz "\n** Running %/t3/s **\n"

rut_data: # Routine Under Test
        .word aadd
        .word aadd_name
        .word faadd
        .word faadd_name
        .word 0
        .word 0

        .align 4
rut_pos:
        .word rut_data

        .align 4
stack:
        .space 256
stack_end:

        .text
        .globl __start

        # s0: result str to show in output.
        # s1: first operand
        # s2: second operand
        # s3: insn count before call
        # s4: pointer into table_numbers
        # s5: n errs
        # s6: n corr
        # s7: aadd output

__start:
        mtc0 $0, $22            # Pause tracing.

RUT_LOOP:
        la $t0, rut_pos
        lw $t1, 0($t0)
        lw $t2, 0($t1)
        beq $t2, $0, RUT_EXIT
        lw $t3, 4($t1)
        sw $t2, 16($sp)

        addi $t1, $t1, 8
        sw $t1, 0($t0)

        la $a0, msg_routine
        addi $v0, $0, 11
        syscall

        la $s4, table_numbers
        addi $s5, $0, 0         # Number of errors.
        addi $s6, $0, 0        # Number correct.

TB_LOOP:
        # Put address of next operands in $a1 and $a2
        jal strnext
        addi $s1, $s4, 0
        addi $s2, $s4, 0

        # Write output buffer with X's.
        #
        la $t3, buffer_result
        la $t4, buffer_result_end
        lui $t1, 0x5858       # Two upper-case Xs.
        ori $t1, $t1, 0x5858  # Two upper-case Xs.
TB_ZLOOP:
        sw $t1, 0($t3)
        bne $t3, $t4, TB_ZLOOP
        addi $t3, $t3, 4
        sw $0, -4($t3)

        # Compute length of operands.
        jal tb_strlen
        addi $a0, $s1, 0
        sll $a3, $v0, 16  # Length of first operand in upper 16 bits.
        jal tb_strlen
        addi $a0, $s2, 0
        or $a3, $a3, $v0  # Length of second operand.

        la $a0, buffer_result
        addi $a1, $s1, 0
        addi $a2, $s2, 0
        lw $t0, 16($sp)
        addi $v0, $0, -1
        mtc0 $v0, $22           # Resume tracing. (No effect if not stepping.)
        jalr $t0
        mfc0 $s3, $9            # Copy current instruction count. (Before.)
        mfc0 $t0, $9            # Copy current instruction count. (After.)
        mtc0 $0, $22            # Pause tracing.
        sub $s3, $t0, $s3
        addi $s7, $v0, 0        # Make a copy of the result starting point.

        # Get address of correct result. (Will be in $s4)
        #
        jal strnext
        nop

        la $t1, buffer_result
        sub $t0, $s7, $t1
        bgez $t0, TB_V0_LOW_OK
        nop

        la $s0, text_v0_too_low
        j TB_FOUND_ERR
        nop

TB_V0_LOW_OK:
        la $t1, buffer_result_end
        sub $t0, $t1, $s7
        bgez $t0, TB_V0_HIGH_OK
        nop

        la $s0, text_v0_too_high
        j TB_FOUND_ERR
        nop

TB_V0_HIGH_OK:
        addi $s0, $s7, 0
        # Check result.
        addi $t0, $s4, 0  # Correct value from table.
        addi $a0, $s7, 0  # Value of v0 returned by aad.
TB_CHLOOP:
        lb $t1, 0($t0)  # From table of correct results.
        lb $t2, 0($a0)  # Written by aadd
        bne $t1, $t2, TB_FOUND_ERR
        addi $t0, $t0, 1
        bne $t1, $0, TB_CHLOOP
        addi $a0, $a0, 1

        # Result is correct!
        la $t0, text_correct
        j TB_REPORT_RESULT
        addi $s6, $s6, 1

TB_FOUND_ERR:
        la $t0, text_wrong
        addi $s5, $s5, 1

TB_REPORT_RESULT:
        la $a0, msg_result
        addi $v0, $0, 11
        syscall

        # Move to first operand of next problem.
        jal strnext
        nop

        la $t1, table_numbers_end
        slt $t0, $s4, $t1
        bne $t0, $0, TB_LOOP
        nop

        # Report final results
        #
        la $a0, msg_end
        addi $v0, $0, 11
        syscall

        j RUT_LOOP
        nop

RUT_EXIT:
        addi $v0, $0, 10
        syscall
        nop