-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMipsCpuEmulator.cs
More file actions
357 lines (315 loc) · 12.4 KB
/
MipsCpuEmulator.cs
File metadata and controls
357 lines (315 loc) · 12.4 KB
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
using System;
using System.IO;
using System.Windows;
namespace ProcessorEmulator.Emulation
{
public class MipsCpuEmulator
{
private const int RegisterCount = 32;
private uint[] registers;
private uint programCounter;
private float[] floatingPointRegisters;
private readonly CP0 _cp0;
private readonly MipsBus _bus;
public MipsCpuEmulator(MipsBus bus, CP0 cp0)
{
_bus = bus;
_cp0 = cp0;
registers = new uint[RegisterCount];
floatingPointRegisters = new float[RegisterCount];
programCounter = 0xBFC00000; // MIPS Reset Vector
}
// Execute a single fetch/decode/execute cycle (or multiple cycles)
public void Step(int count = 1)
{
for (int i = 0; i < count; i++)
{
// Check for and handle pending hardware interrupts before executing an instruction.
if (_cp0.ShouldTriggerInterrupt())
{
TriggerException(0); // 0 is the code for Interrupt
// The exception has changed the PC, so we continue to the next loop iteration
// to fetch from the new interrupt handler address.
}
uint instruction = FetchInstruction();
DecodeAndExecute(instruction);
// Advance the internal timer by one cycle per instruction.
_cp0.UpdateTimer(1);
}
}
private void TriggerException(uint exceptionCode)
{
Console.WriteLine($"--- EXCEPTION: Code {exceptionCode} ---");
// 1. Save current PC to CP0 EPC (Reg 14)
// If in a branch delay slot, EPC should point to the branch instruction, not the delay slot.
// (For simplicity, we are not handling branch delay slot exceptions perfectly here)
_cp0.EPC = programCounter;
// 2. Set Cause register with the exception code
// Clear existing code, then set new one.
_cp0.Cause = (_cp0.Cause & 0xFFFFFF83) | (exceptionCode << 2);
// 3. Set Status.EXL (Exception Level) bit to 1 to prevent nested interrupts
_cp0.Status |= (1 << 1);
// 4. Jump to the General Exception Vector
// If BEV is set, use 0xBFC00380, otherwise use 0x80000180.
if ((_cp0.Status & (1 << 22)) != 0) // Check BEV bit
{
programCounter = 0xBFC00380;
}
else
{
programCounter = 0x80000180;
}
}
private uint FetchInstruction()
{
uint instruction = _bus.Read32(programCounter);
Console.WriteLine($"PC: 0x{programCounter:X8} -> PADDR: 0x{_bus.Translate(programCounter):X8}, INSTR: 0x{instruction:X8}");
programCounter += 4;
return instruction;
}
private uint FetchInstructionAt(uint vaddr)
{
return _bus.Read32(vaddr);
}
private void DecodeAndExecute(uint instruction)
{
uint opcode = (instruction >> 26) & 0x3F;
try
{
switch (opcode)
{
case 0x00: // R-type instructions
ExecuteRType(instruction);
break;
case 0x10: // COP0 instructions
ExecuteCOP0(instruction);
break;
case 0x08: // addi
ExecuteAddImmediate(instruction);
break;
case 0x0C: // andi
ExecuteAndImmediate(instruction);
break;
case 0x0D: // ori
ExecuteOrImmediate(instruction);
break;
case 0x0E: // xori
ExecuteXorImmediate(instruction);
break;
case 0x23: // lw
ExecuteLoadWord(instruction);
break;
case 0x2B: // sw
ExecuteStoreWord(instruction);
break;
case 0x04: // beq
ExecuteBranchEqual(instruction);
break;
case 0x05: // bne
ExecuteBranchNotEqual(instruction);
break;
// ...add more opcodes as needed...
default:
TriggerException(10); // 10 is Reserved Instruction exception
break;
}
}
catch (Exception ex)
{
// Catching emulator-level errors, not guest exceptions.
HandleEmulatorError(ex.Message);
}
}
// MTC0: Move Control to Coprocessor 0 (Write to CP0)
// Format: mtc0 $rt, $rd
public void Execute_MTC0(uint rt, uint rd)
{
uint value = registers[rt]; // Get value from general purpose register
_cp0.WriteRegister((int)rd, value);
}
// MFC0: Move From Coprocessor 0 (Read from CP0)
// Format: mfc0 $rt, $rd
public void Execute_MFC0(uint rt, uint rd)
{
uint value = _cp0.ReadRegister((int)rd);
registers[rt] = value; // Put CP0 value into general purpose register
}
private void ExecuteCOP0(uint instruction)
{
uint rs = (instruction >> 21) & 0x1F;
// Check for ERET instruction
if (rs == 0x10 && (instruction & 0x3F) == 0x18)
{
// ERET: Exception Return
// 1. Clear Status.EXL bit
_cp0.Status &= ~(1u << 1);
// 2. Jump back to where the exception occurred
programCounter = _cp0.EPC;
return;
}
uint rt = (instruction >> 16) & 0x1F;
uint rd = (instruction >> 11) & 0x1F;
switch (rs)
{
case 0x00: // MFC0
Execute_MFC0(rt, rd);
break;
case 0x04: // MTC0
Execute_MTC0(rt, rd);
break;
default:
TriggerException(10); // Reserved Instruction
break;
}
}
private void ExecuteRType(uint instruction)
{
uint funct = instruction & 0x3F;
uint rs = (instruction >> 21) & 0x1F;
uint rt = (instruction >> 16) & 0x1F;
uint rd = (instruction >> 11) & 0x1F;
uint shamt = (instruction >> 6) & 0x1F;
// Register 0 is hardwired to zero
if (rd == 0) return;
switch (funct)
{
case 0x20: // add
registers[rd] = registers[rs] + registers[rt];
break;
case 0x22: // sub
registers[rd] = registers[rs] - registers[rt];
break;
case 0x24: // and
registers[rd] = registers[rs] & registers[rt];
break;
case 0x25: // or
registers[rd] = registers[rs] | registers[rt];
break;
case 0x27: // nor
registers[rd] = ~(registers[rs] | registers[rt]);
break;
case 0x00: // sll
registers[rd] = registers[rt] << (int)shamt;
break;
case 0x02: // srl
registers[rd] = registers[rt] >> (int)shamt;
break;
case 0x08: // jr
ExecuteJumpRegister(instruction);
break;
case 0x0C: // syscall
TriggerException(8); // Syscall exception
break;
default:
TriggerException(10); // Reserved Instruction
break;
};
}
private void ExecuteLoadWord(uint instruction)
{
uint baseReg = (instruction >> 21) & 0x1F;
uint rt = (instruction >> 16) & 0x1F;
int offset = (short)(instruction & 0xFFFF);
uint address = registers[baseReg] + (uint)offset;
if (rt != 0) // writes to R0 are discarded
{
registers[rt] = _bus.Read32(address);
}
}
private void ExecuteStoreWord(uint instruction)
{
uint baseReg = (instruction >> 21) & 0x1F;
uint rt = (instruction >> 16) & 0x1F;
int offset = (short)(instruction & 0xFFFF);
uint address = registers[baseReg] + (uint)offset;
_bus.Write32(address, registers[rt]);
}
private void ExecuteBranchEqual(uint instruction)
{
// Note: This is a simplified implementation that does NOT handle the branch delay slot correctly
// for exceptions. A full implementation would require more complex pipeline management.
uint rs = (instruction >> 21) & 0x1F;
uint rt = (instruction >> 16) & 0x1F;
int offset = (short)(instruction & 0xFFFF);
uint branchPC = programCounter; // PC is already advanced to next instruction
if (registers[rs] == registers[rt])
{
programCounter = (branchPC) + (uint)(offset << 2);
}
}
private void ExecuteBranchNotEqual(uint instruction)
{
// Note: Simplified implementation without correct delay slot exception handling.
uint rs = (instruction >> 21) & 0x1F;
uint rt = (instruction >> 16) & 0x1F;
int offset = (short)(instruction & 0xFFFF);
uint branchPC = programCounter;
if (registers[rs] != registers[rt])
{
programCounter = (branchPC) + (uint)(offset << 2);
}
}
private void ExecuteJumpRegister(uint instruction)
{
// Note: Simplified implementation without correct delay slot exception handling.
uint rs = (instruction >> 21) & 0x1F;
programCounter = registers[rs];
}
private static void HandleEmulatorError(string message)
{
Console.WriteLine($"Emulator Error: {message}");
// In a real app, this might show a dialog or stop the emulation.
}
private void ExecuteAddImmediate(uint instruction)
{
uint rs = (instruction >> 21) & 0x1F;
uint rt = (instruction >> 16) & 0x1F;
int imm = (short)(instruction & 0xFFFF);
if (rt != 0)
{
registers[rt] = registers[rs] + (uint)imm;
}
}
private void ExecuteAndImmediate(uint instruction)
{
uint rs = (instruction >> 21) & 0x1F;
uint rt = (instruction >> 16) & 0x1F;
uint imm = instruction & 0xFFFF;
if (rt != 0)
{
registers[rt] = registers[rs] & imm;
}
}
private void ExecuteOrImmediate(uint instruction)
{
uint rs = (instruction >> 21) & 0x1F;
uint rt = (instruction >> 16) & 0x1F;
uint imm = instruction & 0xFFFF;
if (rt != 0)
{
registers[rt] = registers[rs] | imm;
}
}
private void ExecuteXorImmediate(uint instruction)
{
uint rs = (instruction >> 21) & 0x1F;
uint rt = (instruction >> 16) & 0x1F;
uint imm = instruction & 0xFFFF;
if (rt != 0)
{
registers[rt] = registers[rs] ^ imm;
}
}
public uint GetRegister(int index)
{
if (index < 0 || index >= registers.Length) throw new ArgumentOutOfRangeException(nameof(index));
return registers[index];
}
public void SetRegister(int index, uint value)
{
if (index < 0 || index >= registers.Length) throw new ArgumentOutOfRangeException(nameof(index));
registers[index] = value;
}
public uint ProgramCounter => programCounter;
}
}