aboutsummaryrefslogtreecommitdiffstats
path: root/src/ymh15/mips_cpu.cpp
blob: 37bf08ac66e977dc3384963e5e7f2be9b4386b41 (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
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
#include "../../include/mips.h"
#include "mips_cpu_ymh15.hpp"

struct mips_cpu_impl {
    mips_mem_h mem;
    uint32_t regs[32];
    // lo and hi registers
    uint32_t lo, hi;
    // program counter
    uint32_t pc;
    // next pc used for branches and set the pc correctly
    uint32_t next_pc;
    // delay slot that makes branches work as expected
    uint32_t delay_slot;

    // sets the debug level so that you can print more information
    int debug_level;
    FILE *debug_type;
};

mips_cpu_h mips_cpu_create(mips_mem_h mem) {
    // creates new instance of a cpu explicitly
    mips_cpu_impl* state = new mips_cpu_impl;

    // reset all the variables
    state->mem = mem;
    state->pc = 0;
    state->next_pc = state->pc + 4;
    state->delay_slot = 0;
    state->lo = 0;
    state->hi = 0;
    
    state->debug_level = 0;
    state->debug_type = NULL;

    // sets all registers to 0
    for(int i = 0; i < 32; ++i) {
        state->regs[i] = 0;
    }

    return state;
}

mips_error mips_cpu_reset(mips_cpu_h state) {
    // resets both program counters
    state->pc = 0;
    state->next_pc = state->pc + 4;
    state->delay_slot = 0;
    state->hi = 0;
    state->lo = 0;

    // and registers
    for(int i = 0; i < 32; ++i) {
        state->regs[i] = 0;
    }

    return mips_Success;
}

mips_error mips_cpu_get_register(mips_cpu_h state, unsigned index,
                                 uint32_t *value) {
    // get the state of a current register by using the provided pointer;
    *value = state->regs[index];
    
    return mips_Success;
}

mips_error mips_cpu_set_register(mips_cpu_h state, unsigned index,
                                 uint32_t value) {
    if(index > 31 || index < 1) {
        return mips_ErrorInvalidArgument;
    }
    // set reg state
    state->regs[index] = value;
    
    return mips_Success;
}


mips_error mips_cpu_set_pc(mips_cpu_h state, uint32_t pc) {
    // set the pc and next_pc to the right values
    state->pc = pc;
    state->next_pc = state->pc+4;

    return mips_Success;
}

mips_error mips_cpu_get_pc(mips_cpu_h state, uint32_t *pc) {
    // get the value of the current pc
    *pc = state->pc;

    return mips_Success;
}

// This function executes the code that is found in memory at the address
// of the program counter
mips_error mips_cpu_step(mips_cpu_h state) {
    // gets the instruction from memory
    uint32_t inst;
    mips_mem_read(state->mem, state->pc, 4, (uint8_t*)&inst);
    
    // change the instruction back from big endian as it was read as little endian
    inst = (inst<<24) | ((inst>>8)&0xff00) | ((inst<<8)&0xff0000) | (inst>>24);

    // it then executes the instruction by decoding it first and returns
    // the state of the instruction
    mips_error mips_err = exec_instruction(state, inst);


    if(state->debug_level > 0) {
        fprintf(state->debug_type, "pc: %d\tpc_next: %d\tinst: %#10x\tdelay_slot: %d\n", state->pc, state->next_pc, inst, state->delay_slot);
    }

    // if the debug level is 2 or higher it will print the register state
    if(state->debug_level > 1) {
        fprintf(state->debug_type, "\nCPU register state:\n");
        for(unsigned i = 0; i < 32; ++i) {
            fprintf(state->debug_type, "R%d:\t%#10x\n", i,
                    state->regs[i]);
        }
        fprintf(state->debug_type, "\n\n");
    }

    if(state->debug_level > 2) {
        char c = getchar();
    }
    
    // it then updates the pc to next_pc which could have been changed
    // by a branch, it only does this if it is not in a delay slot   
    if(!state->delay_slot) {
        state->pc = state->next_pc;
        state->next_pc += 4;
    } else {
        state->pc += 4;
        --state->delay_slot;
    }

    // returns the operation error such as arithmetic overflow
    return mips_err;
}

mips_error mips_cpu_set_debug_level(mips_cpu_h state, unsigned level,
                                    FILE *dest) {
    if(!state) {
        return mips_ErrorInvalidArgument;
    }

    // just sets the cpu values for the debug level
    state->debug_level = level;
    state->debug_type = dest;
    
    return mips_Success;
}

void mips_cpu_free(mips_cpu_h state) {
    // deletes the state pointer of mips_cpu_impl* type
    if(state) {
        delete state;
    }
}

// The following functions are not present in the api as they are
// functions defined in a private header file

mips_error exec_instruction(mips_cpu_h state, uint32_t inst) {
    // creates var that holds all the information of the instruction
    uint32_t var[8];

    // prints pc and instruction every clock cycle
    
    for(int i = 0; i < 8; ++i) {
        var[i] = 0;
    }

    // decodes the instruction

    // if first 6 bits are 0 then it is a R instruction
    if(((inst >> 26)&0x3f) == 0) {
        // R Type: set all necessary variables
        var[REG_S] = inst >> 21;
        var[REG_T] = (inst >> 16)&0x1f;
        var[REG_D] = (inst >> 11)&0x1f;
        var[SHIFT] = (inst >> 6)&0x1f;
        var[FUNC] = inst&0x3f;

        // execute R instruction to perform operation
        return exec_R(state, var);
    } else if(((inst >> 26)&0x3f) == J || ((inst >> 26)&0x3f) == JAL) {    // as opcode is < 4
        var[OPCODE] = inst >> 26;           // it has to be J type
        var[MEM] = inst&0x3ffffff;
        return exec_J(state, var);
    } else {                                // otherwise it is an I type
        var[OPCODE] = inst >> 26;      
        var[REG_S] = (inst >> 21)&0x1f;
        var[REG_D] = (inst >> 16)&0x1f;
        var[IMM] = (uint32_t)(int16_t)(inst&0xffff);
        
        return exec_I(state, var);
    }
}

mips_error exec_R(mips_cpu_h state, uint32_t var[8]) {
    // if the function code is between 0x1f and 0x24 then it is addition
    // or subtraction
    if((var[FUNC]&0xf0) == ADD && (var[FUNC]&0xf) < 4) {
        // calls add with -1 if it is a subtractin and 1 if it is addition
        return add_sub(state, var, ((int32_t)-(var[FUNC]&0xf)/2)*2+1, 0);
        
    } else if((var[FUNC]&0xf0) == 0x20 && (var[FUNC]&0xf) < 7) {
        // else if the number is between 0x23 and 0x27 which means it
        // is a bitwise operation
        return bitwise(state, var, 0);
        
    } else if(var[FUNC] == JR) {
        // jr
        return jump(state, var, 0);
        
    } else if(var[FUNC] == JALR) {
        // jalr
        return jump(state, var, 1);
        
    } else if(var[FUNC] >= MFHI && var[FUNC] <= MTLO) {
        // mfhi mthi mflo mtlo
        return move(state, var);
        
    } else if(var[FUNC] >= MULT && var[FUNC] <= DIVU) {
        // mult multu div divu
        return mult_div(state, var);
        
    } else if(var[FUNC] == SLT || var[FUNC] == SLTU) {
        return set(state, var, 0);
        
    } else if(var[FUNC] == SLL || var[FUNC] == SLLV || var[FUNC] == SRA || var[FUNC] == SRAV || var[FUNC] == SRL || var[FUNC] == SRLV) {
        return shift(state, var);
        
    } else if(var[OPCODE] == 0 && var[REG_S] == 0 && var[REG_D] == 0 && var[REG_T] == 0 && var[SHIFT] == 0 && var[FUNC] == NOOP) {
        return mips_Success; 
        
    } else {
        // otherwise return that it was an invalid instruction
        return mips_ExceptionInvalidInstruction;
    }
}

mips_error exec_J(mips_cpu_h state, uint32_t var[8]) {
    switch(var[OPCODE]) {
    case J: // j
        return jump(state, var, 0);
    case JAL: // jal
        return jump(state, var, 1);
    default:
        return mips_ExceptionInvalidInstruction;
    }
}

mips_error exec_I(mips_cpu_h state, uint32_t var[8]) {
    switch(var[OPCODE]) {
    case BGEZ: // bgez, bgezal, bltz, bltzal
        return branch(state, var);
    case BEQ: // beq
        return branch(state, var);
    case BNE: // bne
        return branch(state, var);
    case BLEZ: // blez
        return branch(state, var); 
    case BGTZ: // bgtz
        return branch(state, var);
    case ADDI: // addi
        return add_sub(state, var, 1, 1);
    case ADDIU: // addiu
        return add_sub(state, var, 1, 2);
    case SLTI:
        return set(state, var, 1);
    case SLTIU:
        return set(state, var, 1);
    case ANDI:
        return bitwise(state, var, 1);
    case ORI: // ori
        return bitwise(state, var, 1);
    case XORI: // xori
        return bitwise(state, var, 1);
    case LUI: // lui
        return load(state, var);
    case LB: // lb
        return load(state, var);
    case LH: // lh
        return load(state, var);
    case LWL: // lwl
        return load(state, var);
    case LW: // lw
        return load(state, var);
    case LBU: // lbu
        return load(state, var);
    case LHU: // lhu
        return load(state, var);
    case LWR: // lwr
        return load(state, var);
    case SB: // sb
        return store(state, var);
    case SH: // sh
        return store(state, var);
    case SW: // sw
        return store(state, var);
    default:
        return mips_ExceptionInvalidInstruction;
    }
}

mips_error add_sub(mips_cpu_h state, uint32_t var[8], int32_t add_sub,
                   int32_t imm) {
    // sets initial values of the registers as local variables
    int32_t reg_s, reg_t, reg_d;

    // set the locals to the correct value from the register
    mips_cpu_get_register(state, var[REG_S], (uint32_t*)&reg_s);
    if(imm > 0) {
        reg_t = (int32_t)var[IMM];
    } else {
        mips_cpu_get_register(state, var[REG_T], (uint32_t*)&reg_t);
    }

    // perform the addition or subtraction
    reg_d = reg_s + add_sub*reg_t;

    // check for overflow conditions and if it is an unsigned operation
    if((var[FUNC]&0x1) == 1 || imm > 1 || !((reg_s > 0 && add_sub*reg_t > 0 && reg_d < 0) || (reg_s < 0 && add_sub*reg_t < 0 && reg_d > 0))) {

        // sets the destination register to the right answer
        mips_cpu_set_register(state, var[REG_D], (uint32_t)reg_d);
        return mips_Success;
    }
    
    return mips_ExceptionArithmeticOverflow;
}

mips_error bitwise(mips_cpu_h state, uint32_t var[8], unsigned imm) {
    // selects the right bitwise operation to perform then sets the register to the right value
    uint32_t reg_s, reg_t, reg_d;
    
    mips_cpu_get_register(state, var[REG_S], &reg_s);
    if(!imm) {
        mips_cpu_get_register(state, var[REG_T], &reg_t);
    } else {
        reg_t = var[IMM]&0x0000ffff;
    }

    // performs correct bitwise operation and sets register;
    if(var[FUNC] == AND || var[OPCODE] == ANDI) {
        reg_d = reg_s & reg_t;
    }
    else if(var[FUNC] == OR || var[OPCODE] == ORI) {
        reg_d = reg_s | reg_t;
    }
    else {
        reg_d = reg_s ^ reg_t;
    }

    // set register to the answer
    mips_cpu_set_register(state, var[REG_D], reg_d);
    
    return mips_Success;
}

mips_error branch(mips_cpu_h state, uint32_t var[8]) {
    if(var[OPCODE] == 1 && (var[REG_D] == 0x11 || var[REG_D] == 0x10)) {
        state->regs[31] = state->pc+8;
    }

    if((state->regs[var[REG_S]] == state->regs[var[REG_D]] && var[OPCODE] == BEQ) ||
       (state->regs[var[REG_S]] != state->regs[var[REG_D]] && var[OPCODE] == BNE) || 
       (((int32_t)state->regs[var[REG_S]] >= 0) && (var[OPCODE] == BGEZ) && (var[REG_D]&1) == 1) ||
       ((int32_t)state->regs[var[REG_S]] < 0 && var[OPCODE] == BLTZ && (var[REG_D]&1) == 0) ||
       ((int32_t)state->regs[var[REG_S]] > 0 && var[OPCODE] == BGTZ && (var[REG_D]&1) == 0) ||
       ((int32_t)state->regs[var[REG_S]] <= 0 && var[OPCODE] == BLEZ && (var[REG_D]&1) == 0)) {
        
        state->next_pc += (var[IMM]<<2);
        state->delay_slot += 1;
    }
    
    return mips_Success;
}

mips_error jump(mips_cpu_h state, uint32_t var[8], uint8_t link) {
    uint8_t reg_d;
    uint32_t jump_loc;
    if(var[FUNC] == JALR || var[FUNC] == JR) {
        jump_loc = state->regs[var[REG_S]];
        reg_d = var[REG_D];
        state->next_pc = jump_loc;
    } else {
        jump_loc = var[MEM]<<2;
        reg_d = 31;
        state->next_pc = (state->next_pc&0xf0000000) | (jump_loc&0xfffffff);
    }
    
    if(link) {
        state->regs[reg_d] = state->pc+8;
    }
    
    state->delay_slot += 1;

    return mips_Success;
}

mips_error load(mips_cpu_h state, uint32_t var[8]) {
    uint32_t addr = 0;
    uint8_t mem_byte = 0;
    uint16_t mem_halfword = 0;
    uint32_t mem_word = 0;
    unsigned i = 0;
    
    addr = state->regs[var[REG_S]] + var[IMM];

    if(var[OPCODE] == LB || var[OPCODE] == LBU) {
        mips_mem_read(state->mem, addr, 1, &mem_byte);
    } else if(var[OPCODE] == LH || var[OPCODE] == LHU) {
        if((addr&0b1) == 1) {
            return mips_ExceptionInvalidAlignment;
        }
        mips_mem_read(state->mem, addr, 2, (uint8_t*)&mem_halfword);
        mem_halfword = mem_halfword>>8 | mem_halfword<<8;
    } else if(var[OPCODE] == LW) {
        if((addr&0b1) == 1 || (addr&0b10)>>1 == 1) {
            return mips_ExceptionInvalidAlignment;
        }
        mips_mem_read(state->mem, addr, 4, (uint8_t*)&mem_word);
        mem_word = (mem_word<<24) | ((mem_word>>8)&0xff00) | ((mem_word<<8)&0xff0000) | (mem_word>>24);
    } else if(var[OPCODE] == LWL) {
        i = 3;
        while((addr&3) != 0) {
            mips_mem_read(state->mem, addr, 1, &mem_byte);
            mem_word = mem_word | ((uint32_t)mem_byte<<(i*8));
            ++addr;
            --i;
        }
    } else if(var[OPCODE] == LWR) {
        i = 0;
        while((addr&3) != 3) {
            mips_mem_read(state->mem, addr, 1, &mem_byte);
            mem_word = mem_word | ((uint32_t)mem_byte<<(i*8));
            --addr;
            ++i;
        }
    }
    
    switch(var[OPCODE]) {
    case LUI:
        state->regs[var[REG_D]] = var[IMM]<<16;
        return mips_Success;
    case LB:
        state->regs[var[REG_D]] = (uint32_t)(int8_t)mem_byte;
        return mips_Success;
    case LH:
        state->regs[var[REG_D]] = (uint32_t)(int16_t)mem_halfword;
        return mips_Success;
    case LWL:
        state->regs[var[REG_D]] = (state->regs[var[REG_D]]&(0xffffffff>>((3-i)*8))) | (mem_word&(0xffffffff<<((i+1)*8)));
        return mips_Success;
    case LW:
        state->regs[var[REG_D]] = mem_word;
        return mips_Success;
    case LBU:
        state->regs[var[REG_D]] = (uint32_t)mem_byte;
        return mips_Success;
    case LHU:
        state->regs[var[REG_D]] = (uint32_t)mem_halfword;
        return mips_Success;
    case LWR:
        state->regs[var[REG_D]] = (state->regs[var[REG_D]]&(0xffffffff<<(i*8))) | (mem_word&(0xffffffff>>((4-i)*8)));
        return mips_Success;
    default:
        return mips_ExceptionInvalidInstruction;
    }
}

mips_error store(mips_cpu_h state, uint32_t var[8]) {
    uint32_t addr;
    uint32_t word;
    uint16_t half_word;
    uint8_t byte;
    
    addr = state->regs[var[REG_S]] + var[IMM];
    word = state->regs[var[REG_D]];
    half_word = (uint16_t)state->regs[var[REG_D]];
    byte = (uint8_t)state->regs[var[REG_D]];
    
    if(var[OPCODE] == SB) {
        mips_mem_write(state->mem, addr, 1, &byte);
    } else if(var[OPCODE] == SH) {
        if((addr&0b1) == 1) {
            return mips_ExceptionInvalidAlignment;
        }
        uint16_t tmp = half_word << 8 | half_word >> 8;
        mips_mem_write(state->mem, addr, 2, (uint8_t*)&tmp);
    } else if(var[OPCODE] == SW) {
        if((addr&0b1) == 1 || (addr&0b10)>>1 == 1) {
            return mips_ExceptionInvalidAlignment;
        }
        uint32_t tmp = word<<24 | ((word>>8)&0xff00) | ((word<<8)&0xff0000) | word>>24;
        mips_mem_write(state->mem, addr, 4, (uint8_t*)&tmp);
    } else {
        return mips_ExceptionInvalidInstruction;
    }
    return mips_Success;
}

mips_error move(mips_cpu_h state, uint32_t var[8]) {
    switch(var[FUNC]) {
    case MFHI:
        return mips_cpu_set_register(state, var[REG_D], state->hi);
    case MTHI:
        return mips_cpu_get_register(state, var[REG_S], &state->hi);
    case MFLO:
        return mips_cpu_set_register(state, var[REG_D], state->lo);
    case MTLO:
        return mips_cpu_get_register(state, var[REG_S], &state->lo);
    default:
        return mips_ExceptionInvalidInstruction;
    }
    return mips_Success;
}

mips_error mult_div(mips_cpu_h state, uint32_t var[8]) {
    uint64_t unsigned_result, signed_result;
    switch(var[FUNC]) {
    case MULT: 
        signed_result = ((int64_t)(int32_t)state->regs[var[REG_S]])*((int64_t)(int32_t)state->regs[var[REG_T]]);
        state->lo = (uint32_t)signed_result;
        state->hi = (uint32_t)(signed_result>>32);
        return mips_Success;
        
    case MULTU:
        unsigned_result = ((uint64_t)state->regs[var[REG_S]])*((uint64_t)state->regs[var[REG_T]]);
        state->lo = (uint32_t)unsigned_result;
        state->hi = (uint32_t)(unsigned_result>>32);
        return mips_Success;
        
    case DIV:
        if(var[REG_T] == 0) {
            state->lo = 0;
            state->hi = 0;
            return mips_Success;
        }
        
        state->lo = ((int32_t)state->regs[var[REG_S]])/((int32_t)state->regs[var[REG_T]]);
        state->hi = ((int32_t)state->regs[var[REG_S]])%((int32_t)state->regs[var[REG_T]]);
        return mips_Success;
        
    case DIVU:
        if(var[REG_T] == 0) {
            state->lo = 0;
            state->hi = 0;
            return mips_Success;
        }
        
        state->lo = (state->regs[var[REG_S]])/(state->regs[var[REG_T]]);
        state->hi = (state->regs[var[REG_S]])%(state->regs[var[REG_T]]);
        return mips_Success;
        
    default:
        return mips_ExceptionInvalidInstruction;
    }
}

mips_error shift(mips_cpu_h state, uint32_t var[8]) {
    if(var[FUNC] == SLL && var[OPCODE] == 0) {
        state->regs[var[REG_D]] = state->regs[var[REG_T]] << var[SHIFT];
    } else if(var[FUNC] == SLLV) {
        state->regs[var[REG_D]] = state->regs[var[REG_T]] << state->regs[var[REG_S]];
    } else if(var[FUNC] == SRA) {
        state->regs[var[REG_D]] = (int32_t)state->regs[var[REG_T]] >> var[SHIFT];
    } else if(var[FUNC] == SRAV) {
        state->regs[var[REG_D]] = ((int32_t)state->regs[var[REG_T]]) >> state->regs[var[REG_S]];
    } else if(var[FUNC] == SRL) {
        state->regs[var[REG_D]] = state->regs[var[REG_T]] >> var[SHIFT];
    } else if(var[FUNC] == SRLV) {
        state->regs[var[REG_D]] = state->regs[var[REG_T]] >> state->regs[var[REG_S]];
    }
    return mips_Success;
}

mips_error set(mips_cpu_h state, uint32_t var[8], uint32_t imm) {
    uint32_t reg_s, reg_t, reg_d;

    mips_cpu_get_register(state, var[REG_S], &reg_s);
    
    if(!imm) {
        mips_cpu_get_register(state, var[REG_T], &reg_t);
    } else {
        reg_t = var[IMM];
    }

    if(var[FUNC] == SLT || var[OPCODE] == SLTI) {
        if(((int32_t)reg_s) < ((int32_t)reg_t)) {
            reg_d = 1;
        } else {
            reg_d = 0;
        }
    } else if(var[FUNC] == SLTU || var[OPCODE] == SLTIU) {
        if(reg_s < reg_t) {
            reg_d = 1;
        } else {
            reg_d = 0;
        }
    } else {
        return mips_ExceptionInvalidInstruction;
    }

    mips_cpu_set_register(state, var[REG_D], reg_d);

    return mips_Success;
}