diff options
| author | Nicolas James <Eele1Ephe7uZahRie@tutanota.com> | 2025-02-13 18:00:17 +1100 |
|---|---|---|
| committer | Nicolas James <Eele1Ephe7uZahRie@tutanota.com> | 2025-02-13 18:00:17 +1100 |
| commit | 98cef5e9a772602d42acfcf233838c760424db9a (patch) | |
| tree | 5277fa1d7cc0a69a0f166fcbf10fd320f345f049 /comp1521 | |
initial commit
Diffstat (limited to 'comp1521')
| -rw-r--r-- | comp1521/cellular/cellular.s | 362 | ||||
| -rw-r--r-- | comp1521/cellular/reference.c | 218 | ||||
| -rw-r--r-- | comp1521/smips/helper.c | 54 | ||||
| -rw-r--r-- | comp1521/smips/input.c | 52 | ||||
| -rw-r--r-- | comp1521/smips/input.h | 12 | ||||
| -rw-r--r-- | comp1521/smips/instr.c | 160 | ||||
| -rw-r--r-- | comp1521/smips/instr.h | 103 | ||||
| -rw-r--r-- | comp1521/smips/smips.c | 243 | ||||
| -rw-r--r-- | comp1521/smips/smips.h | 20 |
9 files changed, 1224 insertions, 0 deletions
diff --git a/comp1521/cellular/cellular.s b/comp1521/cellular/cellular.s new file mode 100644 index 0000000..3bdd7aa --- /dev/null +++ b/comp1521/cellular/cellular.s @@ -0,0 +1,362 @@ +######################################################################## +# 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---------------------------------- diff --git a/comp1521/cellular/reference.c b/comp1521/cellular/reference.c new file mode 100644 index 0000000..217275f --- /dev/null +++ b/comp1521/cellular/reference.c @@ -0,0 +1,218 @@ +//////////////////////////////////////////////////////////////////////// +// COMP1521 20T2 --- assignment 1: a cellular automaton renderer +// +// This code runs a one-dimensional, three-neighbour cellular automaton. +// It examines its neighbours and its value in the previous generation +// to derive the value for the next generation. +// +// . . . . # . . . . +// . . . # # # . . . +// . . # # . . # . . +// . # # . # # # # . +// # # . . # . . . # +// +// Here, we using '#' to indicate a cell that's alive; and '.' to +// indicate a cell that is not. +// +// Given we examine three neighbours, there are eight states that the +// prior cells could be in. They are: +// +// . . . . . # . # . . # # # . . # . # # # . # # # +// 0 1 2 3 4 5 6 7 +// +// For each one, we decide what action to take. For example, we might +// choose to have the following `rule': +// +// . . . . . # . # . . # # # . . # . # # # . # # # +// . # # # # . . . +// +// We apply this rule to every cell, to determine whether the next state +// is alive or dead; and this forms the next generation. If we print +// these generations, one after the other, we can get some interesting +// patterns. +// +// The description of the rule above --- by example, showing each case +// and how it should be handled --- is inefficient. We can abbreviate +// this rule by reading it in binary, considering live cells as 1's and +// dead cells as 0s; and if we consider the prior states to be a binary +// value too --- the above rule could be 0b00001110, or 30. +// +// To use that rule, we would mix together the previous states we're +// interested in --- left, middle, and right --- which tells us which +// bit of the rule value gives our next state. +// +// The world size, rule and the number of generations, are read from stdin: +// +// $ ./cellular +// Enter world size: 60 +// Enter rule: 30 +// Enter how many generations: 10 +// +// 0 ..............................#............................. +// 1 .............................###............................ +// 2 ............................##..#........................... +// 3 ...........................##.####.......................... +// 4 ..........................##..#...#......................... +// 5 .........................##.####.###........................ +// 6 ........................##..#....#..#....................... +// 7 .......................##.####..######...................... +// 8 ......................##..#...###.....#..................... +// 9 .....................##.####.##..#...###.................... +// 10 ....................##..#....#.####.##..#................... +// +// $ ./cellular +// Enter world size: 60 +// Enter rule: 30 +// Enter how many generations: -10 +// +// 10 ....................##..#....#.####.##..#................... +// 9 .....................##.####.##..#...###.................... +// 8 ......................##..#...###.....#..................... +// 7 .......................##.####..######...................... +// 6 ........................##..#....#..#....................... +// 5 .........................##.####.###........................ +// 4 ..........................##..#...#......................... +// 3 ...........................##.####.......................... +// 2 ............................##..#........................... +// 1 .............................###............................ +// 0 ..............................#............................. + +#include <stdio.h> +#include <stdint.h> + +// maximum and minimum values for the 3 parameters + +#define MIN_WORLD_SIZE 1 +#define MAX_WORLD_SIZE 128 +#define MIN_GENERATIONS -256 +#define MAX_GENERATIONS 256 +#define MIN_RULE 0 +#define MAX_RULE 255 + +// characters used to print alive/dead cells + +#define ALIVE_CHAR '#' +#define DEAD_CHAR '.' + +// `cells' is used to store successive generations. Each byte will be 1 +// if the cell is alive in that generation, and 0 otherwise. + +static int8_t cells[MAX_GENERATIONS + 1][MAX_WORLD_SIZE]; + +static void run_generation(int world_size, int which_generation, int rule); +static void print_generation(int world_size, int which_generation); + +int main(int argc, char *argv[]) { + + // read 3 integer parameters from stdin + + printf("Enter world size: "); + int world_size = 0; + scanf("%d", &world_size); + if (world_size < MIN_WORLD_SIZE || world_size > MAX_WORLD_SIZE) { + printf("Invalid world size\n"); + return 1; + } + + printf("Enter rule: "); + int rule = 0; + scanf("%d", &rule); + if (rule < MIN_RULE || rule > MAX_RULE) { + printf("Invalid rule\n"); + return 1; + } + + printf("Enter how many generations: "); + int n_generations = 0; + scanf("%d", &n_generations); + if (n_generations < MIN_GENERATIONS || n_generations > MAX_GENERATIONS) { + printf("Invalid number of generations\n"); + return 1; + } + + putchar('\n'); + + // negative generations means show the generations in reverse + int reverse = 0; + if (n_generations < 0) { + reverse = 1; + n_generations = -n_generations; + } + + // the first generation always has a only single cell which is alive + // this cell is in the middle of the world + cells[0][world_size / 2] = 1; + + for (int g = 1; g <= n_generations; g++) { + run_generation(world_size, g, rule); + } + + if (reverse) { + for (int g = n_generations; g >= 0; g--) { + print_generation(world_size, g); + } + } else { + for (int g = 0; g <= n_generations; g++) { + print_generation(world_size, g); + } + } + + return 0; +} + +// +// Calculate a new generation using rule and store it in cells +// +static void run_generation(int world_size, int which_generation, int rule) { + for (int x = 0; x < world_size; x++) { + + // Get the values in the left and right neighbour cells. + // This requires some care, otherwise we could read beyond the + // bounds of the array. In the cases we are at the limits of + // the function, we consider those out-of-bounds cells zero. + + int left = 0; + if (x > 0) { + left = cells[which_generation - 1][x - 1]; + } + + int centre = cells[which_generation - 1][x]; + + int right = 0; + if (x < world_size - 1) { + right = cells[which_generation - 1][x + 1]; + } + + // Convert the left, centre, and right states into one value. + int state = left << 2 | centre << 1 | right << 0; + + // And check whether that bit is set or not in the rule. + // by testing the corresponding bit of the rule number. + int bit = 1 << state; + int set = rule & bit; + + if (set) { + cells[which_generation][x] = 1; + } else { + cells[which_generation][x] = 0; + } + } +} + +// +// Print out the specified generation +// +static void print_generation(int world_size, int which_generation) { + printf("%d", which_generation); + putchar('\t'); + + for (int x = 0; x < world_size; x++) { + if (cells[which_generation][x]) { + putchar(ALIVE_CHAR); + } else { + putchar(DEAD_CHAR); + } + } + + putchar('\n'); +} diff --git a/comp1521/smips/helper.c b/comp1521/smips/helper.c new file mode 100644 index 0000000..ebe798b --- /dev/null +++ b/comp1521/smips/helper.c @@ -0,0 +1,54 @@ +#include "helper.h" + +// Returns the n'th bit of a uint32_t value. +uint32_t get_nth_bit(const uint32_t value, const int n) { + return (value & (1u << n)) >> n; +} + +// Prints the bits of a uint32_t value. +void print_bits(const uint32_t value) { + for (int i = (int)sizeof(uint32_t)*8-1; i >= 0; --i) { + printf("%d", get_nth_bit(value, i)); + } + printf("\n"); +} + +// Skips past the next newline. +// If EOF, stays on EOF so that subsequent fgetc() calls return EOF. +void skip_past_newline(FILE *const fptr) { + int c = 0; + c = fgetc(fptr); + while (true) { + if (c == EOF) { + fseek(fptr, -1L, SEEK_CUR); + return; + } + else if (c == '\n') { + printf("newline\n"); + return; + } else { + fseek(fptr, -1L, SEEK_CUR); + return; + } + printf("skipping char\n"); + c = fgetc(fptr); + } +} + +// Converts decimal values to hex obviously. +uint32_t hex_to_decimal(const uint32_t decimal) { + uint32_t ret = 0; + for (size_t i = 0; i < sizeof(uint32_t); ++i) { // Iterate through 4 bytes. + uint32_t tmp = (decimal & (0xFFu << 8*i)) >> 8*i; + if (isalpha(tmp)) { + tmp -= 'a'; + tmp += 10; + } + else if (isdigit(tmp)) { + tmp -= '0'; + } + // There should be no other cases if the input is hex. + ret |= (tmp << 4*i); + } + return ret; +} diff --git a/comp1521/smips/input.c b/comp1521/smips/input.c new file mode 100644 index 0000000..0932978 --- /dev/null +++ b/comp1521/smips/input.c @@ -0,0 +1,52 @@ +#include "input.h" + +// Converts decimal values of a uint32_t argument to hex. +uint32_t hex_to_decimal(const uint32_t decimal) { + uint32_t ret = 0; + for (size_t i = 0; i < sizeof(uint32_t); ++i) { // Iterate through 4 bytes. + uint32_t tmp = (decimal & (0xFFu << 8 * i)) >> 8 * i; + if (isalpha(tmp)) { + tmp -= 'a'; + tmp += 10; + } else if (isdigit(tmp)) { + tmp -= '0'; + } + // There should be no other cases if the input is hex. + ret |= (tmp << 4 * i); + } + return ret; +} + +// Returns the next instruction in hex. Returns 0 if EOF reached. +uint32_t get_next_instruction(FILE *const iptr) { + uint32_t ret = 0; + int i = 0; + for (int c = fgetc(iptr); c != '\n'; c = fgetc(iptr), ++i) { + if (c == EOF) { + return 0u; + } + uint32_t tmp = hex_to_decimal((uint32_t)c); + ret |= (tmp << (28 - i * 4)); + } + // We have to take into account the potential for the hex value to not be + // "full", ie we may read abc instead of 00000abc. + ret >>= (32 - i * 4); + return ret; +} + +// Sets the iptr to the previous instruction, which sits on the newline so that +// later calls to get_next_instruction work as expected. +// Also, if unable to find a previous instruction, sets the iptr to EOF. +void goto_previous_instruction(FILE *const iptr) { + int c = 0; + fseek(iptr, -2L, SEEK_CUR); + while (ftell(iptr)) { + c = fgetc(iptr); + if (c == '\n') { + return; + } + fseek(iptr, -2L, SEEK_CUR); + } + // Start of file reached, out of bounds, so set EOF. + fseek(iptr, 0L, SEEK_END); +} diff --git a/comp1521/smips/input.h b/comp1521/smips/input.h new file mode 100644 index 0000000..44ac0c9 --- /dev/null +++ b/comp1521/smips/input.h @@ -0,0 +1,12 @@ +#ifndef INPUT_H_ +#define INPUT_H_ + +#include <ctype.h> +#include <stdint.h> +#include <stdio.h> + +uint32_t hex_to_decimal(const uint32_t decimal); +uint32_t get_next_instruction(FILE *const iptr); +void goto_previous_instruction(FILE *const iptr); + +#endif diff --git a/comp1521/smips/instr.c b/comp1521/smips/instr.c new file mode 100644 index 0000000..645fbae --- /dev/null +++ b/comp1521/smips/instr.c @@ -0,0 +1,160 @@ +#include "instr.h" + +// Gets the immediate bits, clearing others and shifting right. +uint32_t get_i(const uint32_t instr) { return instr & IMMEDIATE_BITS; } + +// Gets the pattern bits, clearing others and shifting right. +uint32_t get_p(const uint32_t instr) { return (instr & PATTERN_BITS) >> 26; } + +// Gets the d bits, clearing others and shifting right. +uint32_t get_d(const uint32_t instr) { return (instr & D_BITS) >> 11; } + +// Gets the t bits, clearing others and shifting right. +uint32_t get_t(const uint32_t instr) { return (instr & T_BITS) >> 16; } + +// Gets the s bits, clearing others and shifting right. +uint32_t get_s(const uint32_t instr) { return (instr & S_BITS) >> 21; } + +// $d = $s + $t. +void mips_add(const uint32_t instr, uint32_t registers[32]) { + int d = (int16_t)get_d(instr); + int t = (int16_t)get_t(instr); + int s = (int16_t)get_s(instr); + registers[d] = registers[s] + registers[t]; +} + +// $d = $s - $t. +void mips_sub(const uint32_t instr, uint32_t registers[32]) { + int d = (int16_t)get_d(instr); + int t = (int16_t)get_t(instr); + int s = (int16_t)get_s(instr); + registers[d] = registers[s] - registers[t]; +} +// $d = $s & $t. +void mips_and(const uint32_t instr, uint32_t registers[32]) { + int d = (int16_t)get_d(instr); + int t = (int16_t)get_t(instr); + int s = (int16_t)get_s(instr); + registers[d] = registers[s] & registers[t]; +} + +// $d = $s | $t. +void mips_or(const uint32_t instr, uint32_t registers[32]) { + int d = (int16_t)get_d(instr); + int t = (int16_t)get_t(instr); + int s = (int16_t)get_s(instr); + registers[d] = registers[s] | registers[t]; +} + +// $d = ($s < $t). +void mips_slt(const uint32_t instr, uint32_t registers[32]) { + int d = (int16_t)get_d(instr); + int t = (int16_t)get_t(instr); + int s = (int16_t)get_s(instr); + registers[d] = registers[s] < registers[t]; +} + +// $d = $s * $t. +void mips_mul(const uint32_t instr, uint32_t registers[32]) { + int d = (int16_t)get_d(instr); + int t = (int16_t)get_t(instr); + int s = (int16_t)get_s(instr); + registers[d] = registers[s] * registers[t]; +} + +// If ($s == $t) PC += i. +void mips_beq(FILE *const fptr, const uint32_t instr, uint32_t registers[32]) { + int i = (int16_t)get_i(instr); + int t = (int16_t)get_t(instr); + int s = (int16_t)get_s(instr); + if (registers[s] != registers[t]) { + return; + } + if (i >= 0) { + for (int j = 0; j <= i; ++j) { + get_next_instruction(fptr); + } + } else { + for (int j = 0; j <= -1 * i; ++j) { + goto_previous_instruction(fptr); + } + } +} + +// If ($s != $t) PC += i. +void mips_bne(FILE *const fptr, const uint32_t instr, uint32_t registers[32]) { + int i = (int16_t)get_i(instr); + int t = (int16_t)get_t(instr); + int s = (int16_t)get_s(instr); + if (registers[s] == registers[t]) { + return; + } + if (i >= 0) { + for (int j = 0; j <= i; ++j) { + get_next_instruction(fptr); + } + } else { + for (int j = 0; j <= -1 * i; ++j) { + goto_previous_instruction(fptr); + } + } +} + +// $t = $s + i. +void mips_addi(const uint32_t instr, uint32_t registers[32]) { + int i = (int16_t)get_i(instr); + int t = (int16_t)get_t(instr); + int s = (int16_t)get_s(instr); + registers[t] = registers[s] + (uint32_t)i; +} + +// $t = $s < i. +void mips_slti(const uint32_t instr, uint32_t registers[32]) { + int i = (int16_t)get_i(instr); + int t = (int16_t)get_t(instr); + int s = (int16_t)get_s(instr); + registers[t] = registers[s] < (uint32_t)i; +} + +// $t = $s & i. +void mips_andi(const uint32_t instr, uint32_t registers[32]) { + int i = (int16_t)get_i(instr); + int t = (int16_t)get_t(instr); + int s = (int16_t)get_s(instr); + registers[t] = registers[s] & (uint32_t)i; +} + +// $t = $s | i. +void mips_ori(const uint32_t instr, uint32_t registers[32]) { + int i = (int16_t)get_i(instr); + int t = (int16_t)get_t(instr); + int s = (int16_t)get_s(instr); + registers[t] = registers[s] | (uint32_t)i; +} + +// $t = i << 16u. +void mips_lui(const uint32_t instr, uint32_t registers[32]) { + int i = (int16_t)get_i(instr); + int t = (int16_t)get_t(instr); + registers[t] = (uint32_t)i << 16u; +} + +// Syscall function which returns non-zero if execution should stop. +int mips_syscall(uint32_t registers[32]) { + uint32_t v0 = registers[2]; + uint32_t a0 = registers[4]; + switch (v0) { + case 1: + printf("%u", a0); + break; + case 11: + printf("%c", a0 & 0xFFu); + break; + case 10: + return 1; + default: + printf("Unknown system call: %d\n", v0); + return 1; + } + return 0; +} diff --git a/comp1521/smips/instr.h b/comp1521/smips/instr.h new file mode 100644 index 0000000..720d32f --- /dev/null +++ b/comp1521/smips/instr.h @@ -0,0 +1,103 @@ +#ifndef INSTR_H_ +#define INSTR_H_ + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> + +#include "input.h" + +// We have to use bit masks to identify the instruction we are to perform. +// Instructions are stored in a uint32_t, and inputted from hex. +// The format of the bit masks vary between +// +// PPPPPP SSSSS TTTTT DDDDD IIIIIIIIIII +// 6 5 5 5 11 +// +// PPPPPP SSSSS TTTTT IIIIIIIIIIIIIIII +// 6 5 5 16 +// +// P = pattern_mask +// S = five bit register number +// T = five bit register number +// D = five bit register number +// I = immediate mask (size can very between instructions) + +// Definitions for 32 bit instruction bits. +#define PATTERN_BITS 0xFC000000 // 11111100000000000000000000000000 +#define IMMEDIATE_BITS 0xFFFF // 00000000000000001111111111111111 +#define SHORT_IMMEDIATE_BITS 0x7FF // 00000000000000000000011111111111 +#define S_BITS 0x3e00000 // 00000011111000000000000000000000 +#define T_BITS 0x1f0000 // 00000000000111110000000000000000 +#define D_BITS 0xf800 // 00000000000000001111100000000000 + +// Contains bit masks for instructions for the immediate field. +enum immediate_mask { + im_add = 0x20u, // 100000 + im_sub = 0x22u, // 100010 + im_and = 0x24u, // 100100 + im_or = 0x25u, // 100101 + im_slt = 0x2au, // 101010 + im_mul = 0x2u, // 000010 + im_syscall = 0xcu // 001100 +}; + +// Contains bit masks for the pattern field. +enum pattern_mask { + pm_add = 0x0u, // 000000 + pm_sub = 0x0u, // 000000 + pm_and = 0x0u, // 000000 + pm_or = 0x0u, // 000000 + pm_slt = 0x0u, // 000000 + pm_mul = 0x1cu, // 011100 + pm_beq = 0x4u, // 000100 + pm_bne = 0x5u, // 000101 + pm_addi = 0x8u, // 001000 + pm_slti = 0xau, // 001010 + pm_andi = 0xcu, // 001100 + pm_ori = 0xdu, // 001101 + pm_lui = 0xfu, // 001111 +}; + +// Contains an enum representing instructions. +enum instructions { + add, + sub, + and, + or, + slt, + mul, + beq, + bne, + addi, + slti, + andi, + ori, + lui, + syscall +}; + +// Functions which extract bitfields from an instruction. +uint32_t get_i(const uint32_t instr); +uint32_t get_p(const uint32_t instr); +uint32_t get_d(const uint32_t instr); +uint32_t get_t(const uint32_t instr); +uint32_t get_s(const uint32_t instr); + +// Functions which emulate mips instructions. +void mips_add(const uint32_t instr, uint32_t registers[32]); +void mips_sub(const uint32_t instr, uint32_t registers[32]); +void mips_and(const uint32_t instr, uint32_t registers[32]); +void mips_or(const uint32_t instr, uint32_t registers[32]); +void mips_slt(const uint32_t instr, uint32_t registers[32]); +void mips_mul(const uint32_t instr, uint32_t registers[32]); +void mips_beq(FILE *const fptr, const uint32_t instr, uint32_t registers[32]); +void mips_bne(FILE *const fptr, const uint32_t instr, uint32_t registers[32]); +void mips_addi(const uint32_t instr, uint32_t registers[32]); +void mips_slti(const uint32_t instr, uint32_t registers[32]); +void mips_andi(const uint32_t instr, uint32_t registers[32]); +void mips_ori(const uint32_t instr, uint32_t registers[32]); +void mips_lui(const uint32_t instr, uint32_t registers[32]); +int mips_syscall(uint32_t registers[32]); + +#endif diff --git a/comp1521/smips/smips.c b/comp1521/smips/smips.c new file mode 100644 index 0000000..19acd73 --- /dev/null +++ b/comp1521/smips/smips.c @@ -0,0 +1,243 @@ +// July 2020. +// Simple mips emulator which only reads hex instructions. + +#include "smips.h" + +// Prints an instruction and its variables based off a supplied type. +static void print_wrapper(const uint32_t instr, const char *name, const int type) { + int i = (int16_t)get_i(instr); + int d = (int16_t)get_d(instr); + int t = (int16_t)get_t(instr); + int s = (int16_t)get_s(instr); + switch (type) { + case DST: + printf("%-4s $%d, $%d, $%d", name, d, s, t); + break; + case STI: + printf("%-4s $%d, $%d, %d", name, s, t, i); + break; + case TSI: + printf("%-4s $%d, $%d, %d", name, t, s, i); + break; + case TI: + printf("%-4s $%d, %d", name, t, i); + break; + } +} + +// Returns the instruction type as defined in the instr enum. +// Returns >= 0 if the instruction was found. +static int query_instruction(const uint32_t instr) { + uint32_t pattern = get_p(instr); + uint32_t immediate = get_i(instr); + + // Syscall, mul and lui are special cases which should be dealt with before + // we move into the switch statatement. + if (pattern == pm_mul && (immediate & SHORT_IMMEDIATE_BITS) == im_mul) { + return mul; + } else if (instr == im_syscall) { + return syscall; + } else if (get_p(instr) == pm_lui && get_s(instr) == 0) { + return lui; + } + + // If the pattern bits are empty, we are dealing with instructions between + // add and slt inclusive. This is only true after the special cases of mul, + // syscall and lui are dealt with. The others can be encapsulated in an + // else. + if (!pattern) { + switch (immediate & SHORT_IMMEDIATE_BITS) { + case im_add: + return add; + case im_sub: + return sub; + case im_and: + return and; + case im_or: + return or ; + case im_slt: + return slt; + default: + return -1; + } + } else { + switch (pattern) { + case pm_beq: + return beq; + case pm_bne: + return bne; + case pm_addi: + return addi; + case pm_slti: + return slti; + case pm_andi: + return andi; + case pm_ori: + return ori; + default: + return -1; + } + } + return 0; +} + +// Calls print wrapper with the correct name char *. +static void print_instruction(const uint32_t instr) { + switch (query_instruction(instr)) { + case mul: + print_wrapper(instr, "mul", DST); + break; + case lui: + print_wrapper(instr, "lui", TI); + break; + case syscall: + printf("syscall"); + break; + case add: + print_wrapper(instr, "add", DST); + break; + case sub: + print_wrapper(instr, "sub", DST); + break; + case and: + print_wrapper(instr, "and", DST); + break; + case or: + print_wrapper(instr, "or", DST); + break; + case slt: + print_wrapper(instr, "slt", DST); + break; + case beq: + print_wrapper(instr, "beq", STI); + break; + case bne: + print_wrapper(instr, "bne", STI); + break; + case addi: + print_wrapper(instr, "addi", STI); + break; + case slti: + print_wrapper(instr, "slti", TSI); + break; + case andi: + print_wrapper(instr, "andi", TSI); + break; + case ori: + print_wrapper(instr, "ori", TSI); + break; + } +} + +// Executes a mips_? function based off the enum returned from the +// query_instruction function. Non-zero values indicates execution should stop. +static int execute_instruction(FILE *const iptr, const uint32_t instr, + uint32_t registers[32]) { + // At this point, guaranteed to be known instructions. + switch (query_instruction(instr)) { + case add: + mips_add(instr, registers); + break; + case sub: + mips_sub(instr, registers); + break; + case and: + mips_and(instr, registers); + break; + case or: + mips_or(instr, registers); + break; + case slt: + mips_slt(instr, registers); + break; + case mul: + mips_mul(instr, registers); + break; + case beq: + mips_beq(iptr, instr, registers); + break; + case bne: + mips_bne(iptr, instr, registers); + break; + case addi: + mips_addi(instr, registers); + break; + case slti: + mips_slti(instr, registers); + break; + case andi: + mips_andi(instr, registers); + break; + case ori: + mips_ori(instr, registers); + break; + case lui: + mips_lui(instr, registers); + break; + case syscall: + return mips_syscall(registers); + break; + } + // We need to restore $0, which should always be zero. So after each + // instruction, simply set it back to 0. Syscalls do not touch $0! + registers[0] = 0; + return 0; +} + +int main(const int argc, const char *argv[]) { + if (argc < 2) { + printf("Not enough arguments!\n"); + return EXIT_FAILURE; + } + FILE *iptr = fopen(argv[1], "r"); + if (iptr == NULL) { + printf("File does not exist!\n"); + return EXIT_FAILURE; + } + // We will read through program first, then perform a loop which + // reads out the instructions, loop which executes the instructions, then + // finally a loop which prints changed registers. + int i = 1; + uint32_t instr = 0; + while ((instr = get_next_instruction(iptr))) { // Proofread loop. + if (query_instruction(instr) == -1) { + printf("%s:%d: invalid instruction code: %08x\n", argv[1], i, + instr); + return EXIT_FAILURE; + } + ++i; + } + rewind(iptr); + + i = 0; + printf("Program\n"); + while ((instr = get_next_instruction(iptr))) { // Print loop. + printf("%3d: ", i); + print_instruction(instr); + printf("\n"); + ++i; + } + rewind(iptr); + + uint32_t registers[32] = {0}; + printf("Output\n"); + while ((instr = get_next_instruction(iptr))) { // Execution loop. + // The iptr may change after execute_instruction runs due to branch + // instructions. + // In addition, execute instruction returns a non-zero if execution + // should stop due to syscalls, etc. + if (execute_instruction(iptr, instr, registers)) { + break; + } + } + + printf("Registers After Execution\n"); // Print registers loop. + for (size_t j = 0; j < sizeof(registers) / sizeof(uint32_t); ++j) { + if (registers[j]) { + printf("$%-2d = %u\n", (int)j, registers[j]); + } + } + + fclose(iptr); + return EXIT_SUCCESS; +} diff --git a/comp1521/smips/smips.h b/comp1521/smips/smips.h new file mode 100644 index 0000000..94edf90 --- /dev/null +++ b/comp1521/smips/smips.h @@ -0,0 +1,20 @@ +#ifndef SMIPS_H_ +#define SMIPS_H_ + +#include <ctype.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> + +#include "input.h" // Contains functions for input and file processing. +#include "instr.h" // Contains mips related instructions and bitmasks. + +// Definitions for printing multipliers (read print_wrapper). +#define DST 1 +#define STI 2 +#define TSI 3 +#define TI 4 +#define SYSCALL 5 + +#endif |
