aboutsummaryrefslogtreecommitdiff
path: root/comp1521/smips/smips.c
blob: 19acd731e85010ba06e134c6c2383b0b90478c59 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
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;
}