// 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; }