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 ®s.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 }