######################################################################## # COMP1521 20T2 --- assignment 1: a cellular automaton renderer # Complex logical statements are marked with horizontal bars for readability. # Maximum and minimum values for the 3 parameters. MIN_WORLD_SIZE = 1 MAX_WORLD_SIZE = 128 MIN_GENERATIONS = -256 MAX_GENERATIONS = 256 MIN_RULE = 0 MAX_RULE = 255 # Characters used to print alive/dead cells. ALIVE_CHAR = '#' DEAD_CHAR = '.' # Maximum number of bytes needs to store all generations of cells. MAX_CELLS_BYTES = (MAX_GENERATIONS + 1) * MAX_WORLD_SIZE .data # `cells' is used to store successive generations. Each byte will be 1 # if the cell is alive in that generation, and 0 otherwise. cells: .space MAX_CELLS_BYTES #cells: .byte 1:MAX_CELLS_BYTES # Very useful for debugging. # Some strings you'll need to use: prompt_world_size: .asciiz "Enter world size: " error_world_size: .asciiz "Invalid world size\n" prompt_rule: .asciiz "Enter rule: " error_rule: .asciiz "Invalid rule\n" prompt_n_generations: .asciiz "Enter how many generations: " error_n_generations: .asciiz "Invalid number of generations\n" .text # REGISTERS IN MAIN # $s0: int world_size (runtime input) # $s1: int rule (runtime input) # $s2: int n_generations (runtime input) # $s3: int reverse (0 or 1) # $s4: int g (loop counters) # $t0 to $t2 (temp variables) # $v0 to $v2 (syscalls and functions) #--------------------------------BEGIN_MAIN------------------------------------- main: # No need to store $ra on the stack until we call our own functions. la $a0, prompt_world_size # printf("Enter world size: "); li $v0, 4 syscall move $s0, $zero # $s0: int world_size = 0; li $v0, 5 # scanf("%d", &world_size); syscall move $s0, $v0 blt $s0, MIN_WORLD_SIZE, if0 # if (world_size < MIN_WORLD_SIZE) bgt $s0, MAX_WORLD_SIZE, if0 # ^|| (world_size > MAX_WORLD_SIZE); b skip0 if0: la $a0, error_world_size # printf("Invalid world size\n"); li $v0, 4 syscall li $v0, 1 # return 1; jr $ra # Despite returning 1, echo $? says '0'. I suspect this is due to spim # returning 0 on the "successful" execution of a program. I am unaware # of how to extract the return value of an emulated mips program. skip0: la $a0, prompt_rule # printf("Enter rule: "); li $v0, 4 syscall move $s1, $zero # $s1: int rule = 0; li $v0, 5 # scanf("%d", &world_size); syscall move $s1, $v0 blt $s1, MIN_RULE, if1 # if (world_size < MIN_RULE) bgt $s1, MAX_RULE, if1 # ^|| (world_size > MAX_RULE); b skip1 if1: la $a0, error_rule # printf("Invalid world size\n"); li $v0, 4 syscall li $v0, 1 # return 1; jr $ra skip1: la $a0, prompt_n_generations # printf("Enter how many generations "); li $v0, 4 syscall move $s2, $zero # $s2: n_generations = 0; li $v0, 5 # scanf("%d", &world_size); syscall move $s2, $v0 blt $s2, MIN_GENERATIONS, if2 # if (n_generations < MIN_GENERATIONS) bgt $s2, MAX_GENERATIONS, if2 # ^|| (n_generations > MAX_GENERATIONS); b skip2 if2: la $a0, error_n_generations # printf("Invalid world size\n"); li $v0, 4 syscall li $v0, 1 # return 1; jr $ra skip2: li $a0, '\n' # putchar('\n'); li $v0, 11 syscall move $s3, $zero # $s3: int reverse = 0; blt $s2, 0, if3 # if (n_generations < 0); b skip3 if3: li $s3, 1 # reverse = 1; mul $s2, $s2, -1 # n_generations = -n_generations; skip3: la $t0, cells # cells[0][world_size / 2] = 1; li $t1, 2 div $s0, $t1 mflo $t1 add $t0, $t0, $t1 li $t2, 1 sb $t2, ($t0) # After this point we begin to use our functions, so save the stack pointer. sub $sp, $sp, 4 sw $ra, ($sp) #-------------------------------FIRST_LOOP_START-------------------------------- li $s4, 1 # for (int g = 1; ($s4: int g = 1) loop0: bgt $s4, $s2, end0 # ^g <= n_generations; (test condition) move $a0, $s0 #run_generation(world_size, g, rule); move $a1, $s4 move $a2, $s1 jal run_generation add $s4, $s4, 1 # g++;); (increment) b loop0 end0: #--------------------------------FIRST_LOOP_END--------------------------------- #--------------------------------------IF--------------------------------------- beqz $s3, skip4 # if (reverse) #------------------------------SECOND_LOOP_START-------------------------------- move $s4, $s2 # for (int g = n_generations; # ($s4: int g = n_generations) loop1: blt $s4, 0, end1 # ^g >= 0; (test condition) move $a0, $s0 # print_generation(world_size, g); move $a1, $s4 jal print_generation sub $s4, $s4, 1 # g--;); (decrement) b loop1 end1: #------------------------------SECOND_LOOP_END---------------------------------- b skip5 #------------------------------------ELSE--------------------------------------- skip4: # else #------------------------------THIRD_LOOP_START--------------------------------- li $s4, 0 # for (int g = 0; ($s4: int g = 0) loop2: bgt $s4, $s2, end2 # ^g <= n_generations; (test_condition) move $a0, $s0 # print_generation(world_size, g); move $a1, $s4 jal print_generation add $s4, $s4, 1 # g++;) (increment) b loop2 end2: #-------------------------------THIRD_LOOP_END---------------------------------- skip5: #----------------------------------ELSE_END------------------------------------- lw $ra, ($sp) # Load $ra from the stack. add $sp, $sp, 4 # Deallocate from the stack, now empty. li $v0, 0 # Return 0. jr $ra #----------------------------------MAIN_END------------------------------------- # Given `world_size', `which_generation', and `rule', calculate # a new generation according to `rule' and store it in `cells'. # REGISTERS IN RUN_GENERATION # $a0: int world_size (passed to function) # $a1: int which_generation (passed to function) # $a2: int rule (passed to function) # $t0: int x (copy of arguments) CHANGED! # $t1: int left (copy of arguments) CHANGED! # $t2: int centre (copy of arguments) CHANGED! # $t3: int right (increment variable) CHANGED! # $t4 to $t5: (temp variables) CHANGED! # $v0: int (syscalls) CHANGED! #-----------------------------BEGIN_FUNCTION_0---------------------------------- run_generation: # In this function we are pressed for variables. We will modify $a variables # but continue to preserve them without using the stack as they are # easily reversible changes which we may keep track of and fix before we # return. #---------------------------------BEGIN_LOOP------------------------------------ li $t0, 0 # for(int x = 0; ($t0: int x = 0) f0loop0: bge $t0, $a0, f0end0 # ^x < world_size li $t1, 0 # $t1: int left = 0; sub $a1, $a1, 1 # which_generation-- sub $t0, $t0, 1 # x-- la $t2, cells # $t3 = &cells[which_generation-1][x-1] mul $t3, $a1, MAX_WORLD_SIZE add $t3, $t2, $t3 add $t3, $t3, $t0 add $t0, $t0, 1 # x++ ble $t0, 0, f0skip0 # if (x > 0) lb $t1, ($t3) # left = cells[which_generation-1][x-1] f0skip0: add $t3, $t3, 1 # ++t3, access [x] not [x-1] lb $t2, ($t3) # $t2 = cells[which_generation-1][x] add $t4, $t3, 1 # copy useful ++$t3 before we use $t3 li $t3, 0 # $t3: int right = 0 sub $a0, $a0, 1 # world_size-- bge $t0, $a0, f0skip1 lb $t3, ($t4) # right = cells[which_generation-1][x+1] f0skip1: add $a0, $a0, 1 # world_size++ add $a1, $a1, 1 # which_generation++ sll $t1, $t1, 2 # int state = left << 2 | centre << 1 sll $t2, $t2, 1 # ^ | right << 0; or $t5, $t1, $t2 or $t5, $t5, $t3 li $t1, 1 sll $t5, $t1, $t5 # int bit = 1 << state; and $t5, $a2, $t5 # int set = rule & bit; la $t2, cells # $t2 = &cells[which_generation][x] mul $t3, $a1, MAX_WORLD_SIZE add $t3, $t2, $t3 add $t3, $t3, $t0 #--------------------------------------IF--------------------------------------- beqz $t5, f0skip2 # if (set) li $t1, 1 # cells[which_generation][x] = 1; sb $t1, ($t3) b f0skip3 f0skip2: #------------------------------------ELSE--------------------------------------- li $t1, 0 # cells[which_generation][x] = 0; sb $t1, ($t3) f0skip3: add $t0, $t0, 1 # x++ (increment) b f0loop0 #-----------------------------------END_ELSE------------------------------------ f0end0: #-----------------------------------END_LOOP------------------------------------ jr $ra # return; #-------------------------------END_FUNCTION_0---------------------------------- # Given `world_size', and `which_generation', print out the # specified generation. # REGISTERS IN PRINT_GENERATION # $a0: int world_size (passed to function) CHANGED! # $a1: int which_generation (passed to function) # $t0: int world_size (copy of arguments) CHANGED! # $t1: int which_generation (copy of arguments) CHANGED! # $t2: int x (0++, loop counter) CHANGED! # $t3 to $t4: (temp variables) CHANGED! # $v0 (syscalls) CHANGED! #-----------------------------BEGIN_FUNCTION_1---------------------------------- print_generation: move $t0, $a0 # Copy arguments, to use syscalls. move $t1, $a1 move $a0, $t1 # printf("%d", which_generation); li $v0, 1 syscall li $a0, '\t' # putchar('\t'); li $v0, 11 syscall #-------------------------------FOR_LOOP_START---------------------------------- li $t2, 0 # for (int x = 0; ($t2: int x = 0) f1loop0: bge $t2, $t0, f1end0 # ^x < world_size; (test condition) #-------------------------------------IF---------------------------------------- la $t3, cells # if(cells[which_generation][x]) mul $t4, $t1, MAX_WORLD_SIZE add $t4, $t4, $t2 add $t4, $t4, $t3 lb $t4, ($t4) beqz $t4, f1skip0 li $a0, ALIVE_CHAR # putchar(ALIVE_CHAR); li $v0, 11 syscall b f1skip1 f1skip0: #------------------------------------ELSE--------------------------------------- li $a0, DEAD_CHAR # putchar(DEAD_CHAR); li $v0, 11 syscall f1skip1: #----------------------------------ELSE_END------------------------------------- add $t2, $t2, 1 # x++;); (increment) b f1loop0 f1end0: #--------------------------------FOR_LOOP_END----------------------------------- li $a0, '\n' # putchar('\n'); li $v0, 11 syscall jr $ra # return; #-------------------------------END_FUNCTION_1----------------------------------