Search code examples
assemblyparameter-passingmipscpu-registersmars-simulator

How does passing into label in this MIPS Assembly program work?


This program is meant to allow the user to put dashes ('-' and '|') to form boxes, connecting vertices of a 3x3 grid. I am wondering how this program is able to mark moves on the game board. In the markMove section, what exactly is going on at this code block:

lb $t0, offset($a0) #Pass $a0 which is integer move 0-9 into offset, then load that data into $t0

#Actually mark now; transfer marker to board through $t1
lb $t1, marker($a0) #Load marker into $t1 after finding corresponding marker
sb $t1, board($t0) #Place the marker at spot in board by storing from $t1

What is actually happening when $a0 and $t0 are passed into the corresponding data types above? I appreciate any help.

Below is the full program for reference. Assembled using MIPS on MARS 4.5

    .data #Data declaration segment
board: .ascii   "\n\n   . . .          . 0 . 1 ."
       .ascii     "\n                  2   3   4"
       .ascii     "\n   . . .          . 5 . 6 ."
       .ascii     "\n                  7   8   9"
       .asciiz    "\n   . . .          . a . b .\n"
offset: .byte     6,   8,  33,  35,  37,  62,  64,  89,  91,  93,  118, 120
marker: .byte    '-', '-', '|', '|', '|', '-', '-', '|', '|', '|', '-', '-'

    .text #Text declaration statement

main:
    li $t0, 6 #Number of loops/moves to make; For counting loop. Whatever this is set to will determine number of moves to make
    jal loop1
    
    #Print game board
    li $v0, 4
    la $a0, board
    syscall
    
    #Exit Program
    li $v0, 10
    syscall
    
loop1:
    #Load return address to main onto stack pointer
    subu $sp, $sp, 4 
    sw $ra, ($sp) #Store $ra into $sp

#Inner loop for loop1 is necessary or else return address to main will be infinitely stored.
inner_loop:
    #Read integer for marking excluding a and b
    li $v0, 5
    syscall
    
    move $a0, $v0 #Set $v0 to $a0
    jal markMove #Jump to markMove; return address is set in $ra
    sub $t0, $t0, 1 #Decrement counter by 1
    bnez $t0, inner_loop #Return to L1 if counter not 0
    
    ##Cut off point for loop
    
    #Once counter is 0, pop return address off stack pointer
    lw $ra, ($sp)
    addu $sp, $sp, 4
    
    jr $ra
    
#Mark a move on the game board
#Input : $a0 (Integer move 0-9)
markMove:
    ##Prepare to pass necessary variables ($v0, $ra) into function.
    

    subu $sp, $sp, 4 #First must subtract 4 to make space on stack pointer. This must always be done before storing
    sw $ra, ($sp) #Store $ra into $sp

    subu $sp, $sp, 4 #First must subtract 4 to make space on stack pointer. This must always be done before storing
    sw $t0, ($sp) #Store $t0 into $sp
    
    lb $t0, offset($a0) #Pass $a0 which is integer move 0-9 into offset, then load that data into $t0
    
    ##Must now check if move has been done already
    jal checkMove
    beq $v0, 0, loop2 #Pass $v0 into loop2... after loop2, if $v0 = 1 (valid); else $v0 = 0 (invalid)
    
    #Actually mark now; transfer marker to board through $t1
    lb $t1, marker($a0) #Load marker into $t1 after finding corresponding marker
    sb $t1, board($t0) #Place the marker at spot in board by storing from $t1

#Clear stack pointer ($sp) and return to $ra; $t0 is popped off of $sp; necessary to see if $t0 = 1 or 0 depending on checkMove result
loop2:
    ##Prepare to take the variables ($t0, $ra) from above.
    lw $t0, ($sp) #Pop $t0 off of stack pointer
    addu $sp, $sp, 4 #First must add 4 to make space on stack pointer. This must always be done before loading
    lw $ra, ($sp) #Pop $ra off of stack pointer
    addu $sp, $sp, 4 #First must add 4 to make space on stack pointer. This must always be done before loading
    
    jr $ra #Return to return address; jump register

#Check if a move is valid or not depending on if it has already been done
checkMove:
    jr $ra

Solution

  • The three instructions you're asking about are all MIPS pseudo instructions.

    Their purpose is to access global arrays declared with labels.  They are indexing into these global arrays by the value in the named register.  For example,

    lb $t0, offset($a0)
    

    Says, given the global label offset, index by the value in register $a0, and then perform the lb operation — load byte.  The fetched byte is (sign extended to 32 bits and) stored in $t0.  The indexing is a runtime addition, so the address of offset is added to the value held in register $a0 to form the effective address of the lb memory load operation.  So, if $a0 held the value 5, then the byte at memory location offset+5 would be loaded into $t0, which would load a 62 assuming memory remained as initialized.

    In a hardware-like description language, what these lb's are doing is:

    ea <- offset + $a0              # compute intermediate result
    $t0 <- SignExtend8To32(Mem[ea]) # where Mem[ea] fetches a byte from memory at addr ea
    

    The other instructions you're citing work the same, another lb using a different label, and the same register (though perhaps with a different actual value — you'd have to run the program to see what is in $a0 at the time).

    And the sb, store byte, instruction stores to memory the byte value at the indexed address, where the index is computed the same.

    ea <- offset + $a0  # compute intermediate result
    Mem[ea] <- $t0[0:7] # where Mem[ea] is a byte store to memory at address ea
    

    Note: while the lb sign extends the byte value fetched from memory (here into $t0), the sb stores only the low 8-bits of the $t0 register into memory.


    This form of instruction is known as a pseudo instruction.  The MIPS processor doesn't actually have this as a single instruction — the assembler expands it into a sequence of two instructions.  You can see the two instructions in the simulator, after assembling the code, if you check it out.  The actual operation uses two MIPS instructions to encode the 32-bit address of the label, and add it to the register in a memory indexing operation.  Two instructions are used to compose 32-bit address — one to load the high 16 bits and one the low 16 bits of the complete 32-bit address of the label.

    Beyond that, if you want to know more details, look at the simulator's expansion of these instructions, and check out the MIPS green sheet to check out the individual instructions.