1 module dcpu16.emulator.cpu;
2 
3 version = CPUDebuggingMethods;
4 
5 struct Registers
6 {
7     union
8     {
9         ushort[8] asArr;
10 
11         struct
12         {
13             ushort A;
14             ushort B;
15             ushort c;
16             ushort x;
17             ushort y;
18             ushort z;
19             ushort i;
20             ushort j;
21         }
22     }
23 
24     ushort sp; /// stack pointer
25     private ushort pc; /// program counter for internal purposes
26     private ushort _pc; /// program counter available for users through getter/setter
27     ushort ex; /// extra/excess
28     ushort ia; /// interrupt address
29     version(CPUDebuggingMethods) ushort ds; /// debug status
30 
31     /// program counter register
32     ushort PC() const pure @property { return _pc; }
33     /// ditto
34     ushort PC(ushort v) pure @property
35     {
36         _pc = v;
37         pc = v;
38         return _pc;
39     }
40 
41     void reset() pure
42     {
43         this = Registers();
44     }
45 }
46 
47 import std.bitmanip: bitfields;
48 import dcpu16.emulator;
49 import std.string: format;
50 
51 pure struct CPU
52 {
53     import dcpu16.emulator.exception;
54 
55     Registers regs;
56     bool isBurning;
57     private Computer computer;
58     private InteruptQueue intQueue;
59 
60     this(Computer c) pure
61     {
62         computer = c;
63     }
64 
65     ref inout(Memory) mem() inout pure
66     {
67         return computer.mem;
68     }
69 
70     void reset() pure
71     {
72         regs.reset;
73         intQueue.reset;
74     }
75 
76     private void setPcIfInterrupt()
77     {
78         if(intQueue.isTriggeringEnabled && !intQueue.empty)
79         {
80             with(regs)
81             {
82                 push(pc);
83                 push(A);
84                 PC = ia;
85                 A = intQueue.pop;
86             }
87         }
88     }
89 
90     /// Returns: clock cycles cost of executed step
91     ubyte step()
92     {
93         setPcIfInterrupt();
94 
95         Instruction ins = getCurrInstruction;
96         regs.pc++;
97         ubyte cycles = executeInstruction(ins);
98         regs.PC = regs.pc;
99 
100         version(CPUDebuggingMethods)
101         {
102             if(testBreakpoint(regs.PC))
103                 regs.ds |= 1;
104         }
105 
106         return cycles;
107     }
108 
109     Instruction getCurrInstruction() const pure
110     {
111         return Instruction(mem[regs.PC]);
112     }
113 
114     /// Returns: clock cycles cost of executed instruction
115     private ubyte executeInstruction(in Instruction ins)
116     {
117         byte cost;
118 
119         if(!ins.isSpecialOpcode)
120             performBasicInstruction(ins, cost);
121         else
122             performSpecialInstruction(ins, cost);
123 
124         assert(cost > 0);
125 
126         return cost;
127     }
128 
129     private void complainWrongOpcode(in Instruction ins) pure
130     {
131         throw new Dcpu16Exception("Wrong opcode", computer, __FILE__, __LINE__);
132     }
133 
134     private void complainWrongDeviceNum(in Instruction ins, ushort devNum) pure
135     {
136         throw new Dcpu16Exception(
137                 format("Wrong device number %04#x", devNum), computer, __FILE__, __LINE__
138             );
139     }
140 
141     private void performBasicInstruction(in Instruction ins, out byte cost) pure
142     {
143         scope(success)
144         {
145             byte t = opcodesCyclesCost[ins.basic_opcode];
146             assert(t > 0);
147             cost += t;
148         }
149 
150         int r;
151 
152         ushort mutable = ins.a;
153         const ushort a = *decodeOperand(mutable, true, cost);
154         mutable = ins.b;
155         ushort* b_ptr = decodeOperand(mutable, false, cost);
156         const b = *b_ptr;
157 
158         with(Opcode)
159         with(regs)
160         switch(ins.basic_opcode)
161         {
162             case special: assert(false);
163             case SET: r = a; break;
164             case ADD: r = b + a; ex = r >>> 16; break;
165             case SUB: r = b - a; ex = a > b ? 0xffff : 0; break;
166             case MUL: r = b * a; ex = r >>> 16; break;
167             case MLI: r = cast(short) a * cast(short) b; ex = cast(ushort) r >> 16; break;
168             case DIV:
169                 if (a==0)
170                 {
171                     r = 0;
172                     ex = 0;
173                 }
174                 else
175                 {
176                     r = b / a;
177                     ex = ((b << 16) / a) & 0xFFFF;
178                 }
179                 break;
180             case DVI:
181                 if (a==0)
182                 {
183                     r = 0;
184                     ex = 0;
185                 }
186                 else
187                 {
188                     auto _a = cast(short) a;
189                     auto _b = cast(short) b;
190 
191                     r = cast(short) _b / cast(short) _a;
192                     ex = ((_b << 16) / _a) & 0xFFFF;
193                 }
194                 break;
195             case MOD: r = a == 0 ? 0 : b % a; break;
196             case MDI: r = a == 0 ? 0 : cast(short)b % cast(short)a; break;
197             case AND: r = a & b; break;
198             case BOR: r = a | b; break;
199             case XOR: r = a ^ b; break;
200             case SHR: r = b >>> a; ex = ((b<<16)>>>a) & 0xffff; break;
201             case ASR: r = cast(short)b >> a; ex = ((b<<16)>>>a) & 0xffff; break;
202             case SHL: r = b << a; ex = ((b<<a)>>>16) & 0xffff; break;
203             case IFB: if(!((b & a) != 0)) skip(cost); return;
204             case IFC: if(!((b & a) == 0)) skip(cost); return;
205             case IFE: if(!(b == a)) skip(cost); return;
206             case IFN: if(!(b != a)) skip(cost); return;
207             case IFG: if(!(b > a)) skip(cost); return;
208             case IFA: if(!(cast(short)b > cast(short)a)) skip(cost); return;
209             case IFL: if(!(b < a)) skip(cost); return;
210             case IFU: if(!(cast(short)b < cast(short)a)) skip(cost); return;
211             case ADX: r = b + a + ex; ex = (r >>> 16) ? 1 : 0; break;
212             case SBX: r = b - a + ex; auto under = cast(ushort) r >> 16; ex = under ? 0xffff : 0; break;
213             case STI: r = b; i++; j++; break;
214             case STD: r = b; i--; j--; break;
215 
216             default:
217                 complainWrongOpcode(ins);
218         }
219 
220         if(ins.b < 0x1f) // operand is not literal value
221             *b_ptr = cast(ushort) r;
222     }
223 
224     /// Skip (set pc to) next instruction
225     private void skip(ref byte cost) pure
226     {
227         cost++;
228 
229         /// Next instruction
230         auto i = Instruction(mem[regs.pc]);
231 
232         regs.pc++;
233 
234         // Add PC for each "next word" operand
235         if(is5bitNextWordOperand(cast(ubyte) i.a)) regs.pc++;
236         if(is5bitNextWordOperand(cast(ubyte) i.b)) regs.pc++;
237     }
238 
239     private static bool is5bitNextWordOperand(ubyte o) pure
240     {
241         return
242             (o & 0b11110) == 0b11110 || // [next word] or next word (literal)
243             (o & 0b11000) == 0b10000; // [some_register + next word]
244     }
245 
246     private void performSpecialInstruction(in Instruction ins, out byte cost)
247     {
248         scope(success)
249         {
250             byte t = specialOpcodesCyclesCost[ins.spec_opcode];
251             assert(t > 0);
252             cost += t;
253         }
254 
255         ushort mutable_a = ins.a;
256         ushort* a_ptr = decodeOperand(mutable_a, true, cost);
257         ushort a = *a_ptr;
258 
259         with(SpecialOpcode)
260         with(regs)
261         switch(ins.spec_opcode)
262         {
263             case JSR: push(pc); pc = a; return;
264             case INT: addInterruptOrBurnOut(a); return;
265             case IAG: a = ia; break;
266             case IAS: ia = a; return;
267             case RFI:
268                 intQueue.isTriggeringEnabled = true;
269                 A = pop();
270                 pc = pop();
271                 return;
272             case IAQ: intQueue.isTriggeringEnabled = (a == 0); return;
273             case HWN: a = cast(ushort) computer.devices.length; break;
274             case HWQ:
275                 if(a >= computer.devices.length) complainWrongDeviceNum(ins, a);
276                 auto dev = computer.devices[a];
277                 A = cast(ushort) dev.id;
278                 B = dev.id >>> 16;
279                 c = dev.ver;
280                 x = cast(ushort) dev.manufacturer;
281                 y = dev.manufacturer >>> 16;
282                 return;
283             case HWI:
284                 if(a >= computer.devices.length) complainWrongDeviceNum(ins, a);
285                 computer.devices[a].handleHardwareInterrupt(computer);
286                 return;
287             case reserved:
288             default:
289                 complainWrongOpcode(ins);
290         }
291 
292         if(ins.a < 0x1f) // operand is not literal value
293             *a_ptr = cast(ushort) a;
294     }
295 
296     private void push(ushort val) pure
297     {
298         regs.sp--;
299         mem[regs.sp] = val;
300     }
301 
302     private ushort pop() pure
303     {
304         return mem[regs.sp++];
305     }
306 
307     private ushort* decodeRegisterOfOperand(in ushort operand) pure nothrow
308     {
309         assert(operand <= 7);
310 
311         return &regs.asArr[operand];
312     }
313 
314     private ushort* decodeOperand(ref ushort operand, bool isA, ref byte cost) pure
315     {
316         if(operand > 0x3f)
317             throw new Dcpu16Exception("Unknown operand", computer, __FILE__, __LINE__);
318 
319         with(regs)
320         switch(operand)
321         {
322             case 0x00: .. case 0x07: // register
323                 return decodeRegisterOfOperand(operand);
324 
325             case 0x08: .. case 0x0f: // [register]
326                 return &mem[ *decodeRegisterOfOperand(operand & 7) ];
327 
328             case 0x10: .. case 0x17: // [register + next word]
329                 cost++;
330                 return &mem[ *decodeRegisterOfOperand(operand & 7) + mem[regs.pc++] ];
331 
332             case 0x18: // POP / PUSH
333                 return isA ? &mem[sp++] : &mem[--sp];
334 
335             case 0x19: // PEEK
336                 return &mem[sp];
337 
338             case 0x1a: // PICK n
339                 cost++;
340                 return &mem[ sp + mem[pc+1] ];
341 
342             case 0x1b:
343                 return &sp;
344 
345             case 0x1c:
346                 return &pc;
347 
348             case 0x1d:
349                 return &ex;
350 
351             case 0x1e: // [next word]
352                 cost++;
353                 return &mem[ mem[pc++] ];
354 
355             case 0x1f: // next word (literal)
356                 cost++;
357                 return &mem[pc++];
358 
359             default: // literal values
360                 assert(isA, "Bigger than 5 bit b operand");
361                 operand -= 0x21;
362                 return &operand;
363         }
364     }
365 
366     void addInterruptOrBurnOut(ushort intMsg)
367     {
368         isBurning = intQueue.addInterruptOrBurnOut(intMsg);
369 
370         if(isBurning)
371             throw new Dcpu16Exception("CPU is burning!", computer, __FILE__, __LINE__);
372     }
373 
374     version(CPUDebuggingMethods)
375     {
376         size_t[ushort] breakpoints; // TODO: private
377 
378         void setBreakpoint(ushort addr, size_t skipBeforeTriggering)
379         {
380             breakpoints[addr] = skipBeforeTriggering;
381         }
382 
383         private bool testBreakpoint(ushort pc)
384         {
385             if(pc in breakpoints)
386             {
387                 if(breakpoints[pc] > 0)
388                     breakpoints[pc]--;
389                 else
390                     return true;
391             }
392 
393             return false;
394         }
395     }
396 
397     string regsToString() const pure
398     {
399         import std.string;
400 
401         with(regs)
402         {
403             enum fmt = "A:%04x  B:%04x  C:%04x  X:%04x  Y:%04x  Z:%04x  I:%04x  J:%04x  SP:%04x  PC:%04x  EX:%04x  IA:%04x iPC:%04x\n [%04x]  [%04x]  [%04x]  [%04x]  [%04x]  [%04x]  [%04x]  [%04x]   [%04x]   [%04x]   [%04x]   [%04x]   [%04x]";
404 
405             string res = format!fmt
406                 (
407                     A, B, c, x, y, z, i, j, sp, PC, ex, ia, pc,
408                     mem[A], mem[B], mem[c], mem[x], mem[y], mem[z],
409                     mem[i], mem[j], mem[sp], mem[PC], mem[ex], mem[ia], mem[pc]
410                 );
411 
412             version(CPUDebuggingMethods)
413             {
414                 res ~= format("\nDebug status: %04x", ds);
415             }
416 
417             return res;
418         }
419     }
420 
421     string stackDump() const
422     {
423         string ret;
424 
425         if(regs.sp != 0)
426             for(ushort i = 0xffff; i >= regs.sp; i--)
427                 ret ~= format("%04x\n", mem[i]);
428 
429         return ret;
430     }
431 
432     string intQueueDump() const pure
433     {
434         return intQueue.toString;
435     }
436 }
437 
438 private struct InteruptQueue
439 {
440     ushort[] queue; //TODO: It should be replaced by a more faster mechanism
441     bool isTriggeringEnabled = true;
442 
443     bool addInterruptOrBurnOut(ushort msg) pure
444     {
445         if(queue.length > 256)
446             return true;
447 
448         queue ~= msg;
449 
450         return false;
451     }
452 
453     bool empty() const pure
454     {
455         return queue.length == 0;
456     }
457 
458     ushort pop() pure
459     {
460         auto ret = queue[0];
461         queue = queue[1 .. $];
462 
463         return ret;
464     }
465 
466     void reset() pure
467     {
468         this = InteruptQueue();
469     }
470 
471     string toString() const pure
472     {
473         string ret = format("INT queue length %d, messages: ", queue.length);
474 
475         foreach(ref msg; queue)
476             ret ~= format("%d ", msg);
477 
478         return ret;
479     }
480 }
481 
482 enum Opcode : ubyte
483 {
484     special, /// Special opcode
485     SET, /// sets b to a
486     ADD, /// sets b to b+a, sets EX to 0x0001 if there's an overflow, 0x0 otherwise
487     SUB, /// sets b to b-a, sets EX to 0xffff if there's an underflow, 0x0 otherwise
488     MUL, /// sets b to b*a, sets EX to ((b*a)>>16)&0xffff (treats b, a as unsigned)
489     MLI, /// like MUL, but treat b, a as signed
490     DIV, /// sets b to b/a, sets EX to ((b<<16)/a)&0xffff. if a==0, sets b and EX to 0 instead. (treats b, a as unsigned)
491     DVI, /// like DIV, but treat b, a as signed. Rounds towards 0
492     MOD, /// sets b to b%a. if a==0, sets b to 0 instead.
493     MDI, /// like MOD, but treat b, a as signed. (MDI -7, 16 == -7)
494     AND, /// sets b to b&a
495     BOR, /// sets b to b|a
496     XOR, /// sets b to b^a
497     SHR, /// sets b to b>>>a, sets EX to ((b<<16)>>a)&0xffff (logical shift)
498     ASR, /// sets b to b>>a, sets EX to ((b<<16)>>>a)&0xffff (arithmetic shift) (treats b as signed)
499     SHL, /// sets b to b<<a, sets EX to ((b<<a)>>16)&0xffff
500     IFB, /// performs next instruction only if (b&a)!=0
501     IFC, /// performs next instruction only if (b&a)==0
502     IFE, /// performs next instruction only if b==a
503     IFN, /// performs next instruction only if b!=a
504     IFG, /// performs next instruction only if b>a
505     IFA, /// performs next instruction only if b>a (signed)
506     IFL, /// performs next instruction only if b<a
507     IFU, /// performs next instruction only if b<a (signed)
508     unused_0x18,
509     unused_0x19,
510     ADX, /// sets b to b+a+EX, sets EX to 0x0001 if there is an overflow, 0x0 otherwise
511     SBX, /// sets b to b-a+EX, sets EX to 0xFFFF if there is an underflow, 0x0 otherwise
512     unused_0x1c,
513     unused_0x1d,
514     STI, /// sets b to a, then increases I and J by 1
515     STD, /// sets b to a, then decreases I and J by 1
516 }
517 
518 enum SpecialOpcode : ubyte
519 {
520     reserved,
521     JSR, /// pushes the address of the next instruction to the stack, then sets PC to a
522     INT = 0x08, /// triggers a software interrupt with message a
523     IAG, /// sets a to IA
524     IAS, /// sets IA to a
525     RFI, /// disables interrupt queueing, pops A from the stack, then pops PC from the stack
526     IAQ,    /** if a is nonzero, interrupts will be added to the queue instead of triggered.
527                 if a is zero, interrupts will be triggered as normal again */
528     HWN = 0x10, /// sets a to number of connected hardware devices
529     HWQ,    /** sets A, B, C, X, Y registers to information about hardware a
530                 A+(B<<16) is a 32 bit word identifying the hardware id
531                 C is the hardware version
532                 X+(Y<<16) is a 32 bit word identifying the manufacturer
533             */
534     HWI, /// sends an interrupt to hardware a
535 }
536 
537 private immutable byte[] opcodesCyclesCost =
538 [
539     -1, // erroneous cost for special instruction
540     1, // SET
541     2, 2, 2, 2, // addition and multiplication
542     3, 3, 3, 3, // division
543     1, 1, 1, 1, 1, 1, // bit manipulation
544     2, 2, 2, 2, 2, 2, 2, 2, // conditional branching
545     -1, -1, // unused
546     3, 3, // ADX and SBX
547     -1, -1, // unused
548     2,  2, // STI and STD
549 ];
550 
551 static assert(opcodesCyclesCost.length == Opcode.STD + 1);
552 
553 private immutable byte[] specialOpcodesCyclesCost =
554 [
555     -1, // reserved opcode
556     3, // JSR
557     -1, -1, -1, -1, -1, -1, // unused
558     4, 1, 1, 3, 2, // interrupts
559     -1, -1, -1, // unused
560     2, 4, 4, // hardware
561 ];
562 
563 static assert(specialOpcodesCyclesCost.length == SpecialOpcode.HWI + 1);
564 
565 struct Instruction
566 {
567     union
568     {
569         ushort word;
570 
571         mixin(bitfields!(
572             Opcode, "__opcode", 5,
573             ubyte, "b",         5,
574             ubyte, "a",         6,
575         ));
576     }
577 
578     bool isSpecialOpcode() const pure
579     {
580         return __opcode == Opcode.special;
581     }
582 
583     Opcode basic_opcode() const pure
584     {
585         assert(!isSpecialOpcode);
586 
587         return __opcode;
588     }
589 
590     SpecialOpcode spec_opcode() const pure
591     {
592         assert(isSpecialOpcode);
593 
594         return cast(SpecialOpcode) b;
595     }
596 
597     string toString() const pure
598     {
599         import std.conv: to;
600 
601         if(isSpecialOpcode)
602         {
603             return
604                 format!"special opcode=%02x (%s), opA=%02x"
605                 (spec_opcode, spec_opcode.to!string, a);
606         }
607         else
608         {
609             return
610                 format!"opcode=%02x (%s), opA=%02x, opB=%02x"
611                 (basic_opcode, basic_opcode.to!string, a, b);
612         }
613     }
614 }
615 
616 pure unittest
617 {
618     auto comp = new Computer;
619     auto cpu = CPU(comp);
620     cpu.regs.x = 123;
621     comp.mem[123] = 456;
622 
623     byte cost;
624     ushort b1 = 0x03;
625     ushort b2 = 0x0b;
626     assert(*cpu.decodeOperand(b1, false, cost) == 123, "1");
627     assert(*cpu.decodeOperand(b2, false, cost) == 456, "2");
628 
629     // literal values:
630     ushort a1 = 0x20;
631     ushort a2 = 0x3f;
632     assert(*cpu.decodeOperand(a1, true, cost) == cast(ushort) -1);
633     assert(*cpu.decodeOperand(a2, true, cost) == 30);
634 }