forked from JamesHagerman/nankervis-pdp11-js
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathpdp11.js
executable file
·2149 lines (2069 loc) · 115 KB
/
pdp11.js
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
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Javascript PDP 11/70 Emulator v4.0
// written by Paul Nankervis
// Please send suggestions, fixes and feedback to [email protected]
//
// This code may be used freely provided the original author name is acknowledged in any modified source code
//
//
// This code emulates the function of a PDP 11/70 CPU.
//
//
const
IOBASE_VIRT = 0o160000,
IOBASE_18BIT = 0o760000,
IOBASE_UNIBUS = 0o17000000,
IOBASE_22BIT = 0o17760000,
MAX_MEMORY = IOBASE_UNIBUS - 16384, // Maximum memory address (need less memory for BSD 2.9 boot)
MMU_READ = 16, // READ & WRITE bits used to indicate access type in memory operations
MMU_WRITE = 32, // but beware lower 4 bits used as auto-increment length when getting virtual address
MMU_LENGTH_MASK = 0xf, // Mask for operand length (which can be up to 8 for FPP)
MMU_LENGTH_EVEN = 0xe, // Mask for SP operand length (must be even)
MMU_BYTE = 1, // Byte length in 4 bits - also used as byte test mask
MMU_WORD = 2, // Word length
MMU_BYTE_READ = MMU_READ | MMU_BYTE, // Read flag with byte length
MMU_WORD_READ = MMU_READ | MMU_WORD,
MMU_BYTE_WRITE = MMU_WRITE | MMU_BYTE,
MMU_WORD_WRITE = MMU_WRITE | MMU_WORD,
MMU_BYTE_MODIFY = MMU_READ | MMU_WRITE | MMU_BYTE,
MMU_WORD_MODIFY = MMU_READ | MMU_WRITE | MMU_WORD, // Read & write flags with word length
STATE_RUN = 0, // Define legal values for CPU.runState (run, reset, wait, halt)
STATE_RESET = 1,
STATE_WAIT = 2,
STATE_HALT = 3,
STATE_STEP = 4;
// Below are the CPU registers. At simplest level a PDP 11 program has 8 registers (0-5 are general, 6 is the stack
// pointer, and 7 is the PC), 4 condition codes (Negative, Zero, Overflow and Carry), up to 28K words of memory,
// and 4K of I/O page address space. All device I/O and access to other features (including memory management)
// is done through reference to the I/O page at the top of physical memory.
// Memory management enables 3 modes (Kernel, Supervisor and User) each of which have their own mapping of memory
// from a 17 bit virtual address (16 bits for instruction and 16 for data), to 22 bits of physical bus space.
// Thus a program virtual address space can be up to 32K words of instruction space and 32K words of data space.
// The distinction between these spaces is that references based on register 7 (the program counter) refer to
// instruction space, while all other references are to data space.
// I/O and control of devices is done by writing to device registers in the I/O page at the top 4K of
// physical memory. That is implemented here by calling the iopage.access() function in module iopage.js.
// For example to send a character to the console terminal a program would write to the console transmit buffer at
// virtual address 177566 - assuming that this is mapped to physical address 17777566. Also located in the I/O page
// are things like the Program Status Word (PSW which contains CPU priority, memory management mode, condition
// codes etc), Memory Management registers, the Stack limit register, Program Interrupt register, each memory
// management mode stack pointer (R6), as well as two sets of general registers (selection by program status).
// Floating point arithmetic is handled by a separate module. It is implemented by calling the executeFPP()
// function in module fpp.js whenever a floating point instruction is encountered.
// Traps are implemented by the trap() function below. Traps read a new PC and PSW from a vector in kernel data
// space, and then save the old values on to the new mode stack. Software can resume processing at the end of an
// interrupt service routine by using an RTT or RTI instruction to restore the PC and PSW.
// The trap vector depends on the kind of trap, for example 4 for an odd address, 10 for an invalid instruction,
// or 20 when an IOT instruction is encountered.
// I/O traps occur when a device needs to signal attention, for example at the completion of an operation. Device
// interrupts are handled in the iopage module which flags when interrupt priorities need to be re-examined, by
// setting the interruptRequested flag to indicate that a call back to iopage.poll() is required.
var CPU = {
CPU_Error: 0,
MMR0: 0, // MMU control registers
MMR1: 0,
MMR2: 0,
MMR3: 0,
PIR: 0, // Programmable interrupt register
PSW: 0xf, // PSW less flags C, N, V & Z
displayAddress: 0, // Address display for console operations
displayBusReg: 0, // Bus Register display (we don't really have one)
displayDataPaths: 0, // Console display data path (random except in console operations or non-run state)
displayMicroAdrs: 0, // Micro Address display (we don't really have one)
displayPhysical: 0, // Physical address display for console operations
displayRegister: 0, // Console display lights register (set by software)
flagC: NaN, // PSW C bit (when not in PSW)
flagNZ: NaN, // PSW NZ status (when not in PSW)
flagV: 0x8000, // PSW V bit (when not in PSW)
interruptRequested: 1, // flag to mark if an interrupt has been requested
memory: new Uint16Array(MAX_MEMORY >>> 1), // Main memory (in words - addresses must be halved for byte indexing)
mmuEnable: 0, // MMU enable mask for MMU_READ and/or MMU_WRITE
mmuLastPage: 0, // last used MMU page for MMR0 - 2 bits of mode and 4 bits of I/D page - used as an index into PAR/PDR
mmuMode: 0, // current memory management mode (0=kernel,1=super,2=undefined,3=user)
mmuPAR: new Uint16Array(64), //mmu par register by kernel (8 i and 8 d pages), super, illegal & user
mmuPDR: new Uint16Array(64), //mmu pdr register by kernel (8 i and 8 d pages), super, illegal & user
mmuPageMask: 0, // MMR3 I/D page mask
modifyAddress: 0, // Remember memory physical address
modifyRegister: -1, // Remember the address of a register in a read/write (modify) cycle
registerAlt: new Uint16Array(6), // Alternate registers R0 - R5
registerVal: new Uint16Array(8), // Current registers R0 - R7
runState: STATE_HALT, // current machine state STATE_RUN, STATE_STEP, STATE_RESET, STATE_WAIT or STATE_HALT
stackLimit: 0xff, // stack overflow limit
stackPointer: new Uint16Array(4), // Alternate R6 (kernel, super, illegal, user)
statusLights: 0x3000, // Need to remember console address error light status
switchRegister: 0, // console switch register
trapMask: 0, // Mask of traps to be taken at the end of the current instruction
trapPSW: -1, // PSW when first trap invoked - for tackling double traps
unibusMap: new Uint32Array(32) // 32 double word unibus mapping registers
};
// Instruction logging stuff - useful ONLY in browser debugging mode!!
var log = {
limit: 0, // Size of instruction log (0 is off)
debugPC: 0, // PC for debugging - used for setting browser breakpoint when the PC reaches this value
ring: [] // Data for instruction logging (for debugging)
};
// Debug routine to print the contents of the instruction log
function log_print() {
"use strict";
var R = ["r0", "r1", "r2", "r3", "r4", "r5", "sp", "pc"];
function toOct(n, l) {
var o = n.toString(8);
return "0".repeat((typeof l !== "undefined" ? l : 6) - o.length) + o;
}
function opr(pc, i, a) { // Print an operand
var r = i & 7;
switch ((i >>> 3) & 7) {
case 0: // R Register
return R[r];
case 1: // (R) Register Deferred
return "(" + R[r] + ")";
case 2: // (R)+ Autoincrement
if (r == 7 && typeof a !== "undefined") {
return "#" + a.toString(8);
} else {
return "(" + R[r] + ")+";
}
case 3: // @(R)+ Autoincremenet Deferred
if (r == 7 && typeof a !== "undefined") {
return "@#" + a.toString(8);
} else {
return "@(" + R[r] + ")+";
}
case 4: // -(R) Autodecrement
return "-(" + R[r] + ")";
case 5: // @-(R) Autodecrement Deferred
return "@-" + R[r];
case 6: // x(R) Index
if (r == 7 && typeof a !== "undefined") {
return ((pc + a) & 0xffff).toString(8);
} else {
return (typeof a !== "undefined" ? a.toString(8) : "x") + "(" + R[r] + ")";
}
case 7: // @x(R) Index Deferred
if (r == 7 && typeof a !== "undefined") {
return "@" + ((pc + a) & 0xffff).toString(8);
} else {
return "@" + (typeof a !== "undefined" ? a.toString(8) : "x") + "(" + R[r] + ")";
}
}
}
console.log("Flags PC Instruction");
for (let i = 0; i < log.ring.length; i++) {
let e = log.ring[i];
let l = toOct(e[0]) + " " + toOct((e[1] - 2) & 0xffff) + " " + toOct(e[2]) + " " + e[3];
switch (e[4]) { // Instruction format...
case 1: // xxxxSS Single operand
l += " " + opr(e[1], e[2], e[5]);
break;
case 2: // xxSSDD Double operand
l += " " + opr(e[1], e[2] >>> 6, e[5]) + "," + opr(e[1], e[2], e[Math.max(5, e.length - 1)]);
break;
case 3: // xxxxNN Branch
l += " " + ((e[1] + ((e[2] & 0x80 ? e[2] | 0xff00 : e[2] & 0xff) << 1)) & 0xffff).toString(8);
break;
case 4: // xxxRNN SOB with branch back
l += " " + R[(e[2] >> 6) & 7] + "," + ((e[1] - ((e[2] & 0o77) << 1)) & 0xffff).toString(8);
break;
case 5: // xxxxxR Register
l += " " + R[(e[2] >> 6) & 7];
break;
case 6: // xxxRSS Register plus operand
l += " " + R[(e[2] >> 6) & 7] + "," + opr(e[1], e[2], e[5]);
break;
case 7: // xxxASS Floating accumulator plus operand
l += " ac" + ((e[2] >> 6) & 3) + "," + opr(e[1], e[2], e[5]);
break;
default:
if (e[4] & 0x100) { // xxxNNN Literal value in instruction - eg SPL, CC codes, Mark, etc
l += " " + (e[2] & (e[4] & 0xff)).toString(8);
}
}
console.log(l);
}
return "";
}
// Adds PC word to current debug log entry - eg the x in 'MOV #x,R5'
function LOG_OPERAND(pcWord) {
"use strict";
if (log.limit) {
log.ring[log.ring.length - 1].push(pcWord);
}
}
// Add an intruction log debug entry
function LOG_INSTRUCTION(instruction, name, format) {
"use strict";
if (log.limit) { // Only do debug stuff if there is a log limit
log.ring.push([readPSW(), CPU.registerVal[7], instruction, name, format]);
while (log.ring.length > log.limit) {
log.ring.shift();
}
if (CPU.registerVal[7] - 2 == log.debugPC) { // Set browser breakpoint here to stop at debug PC
console.log(readPSW().toString(8) + " " + CPU.registerVal[7].toString(8) + " " + instruction.toString(8) + " " + name);
}
}
}
function setMMUmode(mmuMode) {
"use strict";
CPU.mmuPageMask = ((CPU.MMR3 << (mmuMode & 2 ? mmuMode : mmuMode + 1)) & 8) | 0x37;
CPU.mmuMode = mmuMode;
}
// writePSW() is used to update the CPU Processor Status Word. The PSW should generally
// be written through this routine so that changes can be tracked properly, for example
// the correct register set, the current memory management mode, etc. Note that for
// performance reasons the N, Z, V, and C flags may be stored outside the PSW (CPU.PSW)
// when CPU.flagNZ, CPU.flagV, and CPU.flagC contain a value other than NaN. Also
// CPU.mmuMode mirrors the current processor mode in bits 14 & 15 of the PSW, except
// when being manipulated by instructions which work across modes (MFPD, MFPI, MTPD,
// MTPI, and function trap()).
//
// CPU.PSW 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
// CM | PM |RS| |PRIORITY| T| N| Z| V| C
// mode 0 kernel 1 super 2 illegal 3 user
function writePSW(newPSW) {
"use strict";
if ((newPSW ^ CPU.PSW) & 0x0800) { // register set change?
let i, temp;
for (i = 0; i <= 5; i++) {
temp = CPU.registerVal[i];
CPU.registerVal[i] = CPU.registerAlt[i];
CPU.registerAlt[i] = temp; // swap the active register sets
}
}
setMMUmode(newPSW >>> 14); // set mmuMode
if ((newPSW ^ CPU.PSW) & 0xc000) { // mode change?
CPU.stackPointer[CPU.PSW >>> 14] = CPU.registerVal[6];
CPU.registerVal[6] = CPU.stackPointer[newPSW >>> 14]; // swap to new mode SP
}
if ((newPSW & 0xe0) < (CPU.PSW & 0xe0)) { // priority lowered?
CPU.interruptRequested = 1; // trigger a check of priority levels
}
CPU.PSW = newPSW & 0xf8ff;
CPU.flagNZ = NaN; // NZV flags are inside the PSW
CPU.flagC = NaN; // C is also in the PSW
}
function testN() { // Test N
"use strict";
if (isNaN(CPU.flagNZ)) {
return CPU.PSW & 8;
} else {
return CPU.flagNZ & 0x8000;
}
}
function testZ() { // Test Z
"use strict";
if (isNaN(CPU.flagNZ)) {
return CPU.PSW & 4;
} else {
return !(CPU.flagNZ & 0xffff);
}
}
function testV() { // Test V
"use strict";
if (isNaN(CPU.flagNZ)) {
return CPU.PSW & 2;
} else {
return CPU.flagV & 0x8000;
}
}
function testC() { // Test C
"use strict";
if (isNaN(CPU.flagC)) {
return CPU.PSW & 1;
} else {
return CPU.flagC & 0x10000;
}
}
function testNxV() { // Test N xor V
"use strict";
if (isNaN(CPU.flagNZ)) {
return ((CPU.PSW >>> 2) ^ CPU.PSW) & 2; // N ^ V
} else {
return (CPU.flagNZ ^ CPU.flagV) & 0x8000;
}
}
// readPSW() reassembles the N, Z, V, and C flags back into the PSW (CPU.PSW)
function readPSW() {
"use strict";
if (!isNaN(CPU.flagNZ)) {
let flags = 0;
if (CPU.flagNZ & 0xffff) {
if (CPU.flagNZ & 0x8000) {
flags = 8;
}
} else {
flags = 4;
}
if (CPU.flagV & 0x8000) {
flags |= 2;
}
CPU.flagNZ = NaN; // NZV flags are now inside the PSW
if (isNaN(CPU.flagC)) {
CPU.PSW = (CPU.PSW & 0xfff1) | flags;
} else {
if (CPU.flagC & 0x10000) {
flags |= 1;
}
CPU.flagC = NaN; // C is also in the PSW
CPU.PSW = (CPU.PSW & 0xfff0) | flags;
}
}
return CPU.PSW;
}
// All condition setting code abstracted from instruction routines to here
// (to enable experimentation with other approaches).
function setFlags(mask, value) { // Set or clear selected flags in mask
"use strict";
mask &= 0xf;
CPU.PSW = (readPSW() & ~mask) | (value & mask);
}
function setZero() { // Set flags for 0 value (Z becomes 1)
"use strict";
CPU.PSW = (CPU.PSW & 0xfff0) | 4;
CPU.flagNZ = NaN; // NZV flags are inside the PSW
CPU.flagC = NaN; // C is also in the PSW
}
function setNZ(result) { // Set N & Z clearing V (C unchanged)
"use strict";
CPU.flagNZ = result;
CPU.flagV = 0;
}
function setNZV(result, flagV) { // Set N, Z & V (C unchanged)
"use strict";
CPU.flagNZ = result;
CPU.flagV = flagV;
}
function setNZC(result) { // Set N, Z & C clearing V
"use strict";
CPU.flagNZ = CPU.flagC = result;
CPU.flagV = 0;
}
function setNZVC(result, flagV) { // Set all flag conditions
"use strict";
CPU.flagNZ = CPU.flagC = result;
CPU.flagV = flagV;
}
function setByteNZ(result) { // Set N & Z clearing V (C unchanged) (byte)
"use strict";
CPU.flagNZ = result << 8;
CPU.flagV = 0;
}
function setByteNZV(result, flagV) { // Set N, Z & V (C unchanged) (byte)
"use strict";
CPU.flagNZ = result << 8;
CPU.flagV = flagV << 8;
}
function setByteNZC(result) { // Set N, Z & C clearing V (byte)
"use strict";
CPU.flagNZ = CPU.flagC = result << 8;
CPU.flagV = 0;
}
function setByteNZVC(result, flagV) { // Set all flag conditions (byte)
"use strict";
CPU.flagNZ = CPU.flagC = result << 8;
CPU.flagV = flagV << 8;
}
// trap() handles all the trap/abort functions. It reads the trap vector from kernel
// D space, changes mode to reflect the new PSW and PC, and then pushes the old PSW and
// PC onto the new mode stack. trap() returns a -1 which is passed up through function
// calls to indicate that a trap/abort has occurred (to terminate the current instruction)
// CPU.trapPSW records the first PSW for double trap handling. The special value of -2
// allows console operations to propagate an abort without trapping to the new vector.
function trap(vector, errorMask) {
"use strict";
let newPC, newPSW, doubleTrap = 0;
if (CPU.trapPSW > -2) { // console mode doesn't actually do all the regular trap stuff
if (CPU.trapPSW < 0) {
CPU.trapMask = 0; // No other traps unless we cause one here
CPU.trapPSW = readPSW(); // Remember original PSW
} else {
if (!CPU.mmuMode) {
vector = 4;
doubleTrap = 1;
}
}
//LOG_INSTRUCTION(vector, "-trap-", 0x1ff);
if (!(CPU.MMR0 & 0xe000)) {
CPU.MMR1 = 0xf6f6;
CPU.MMR2 = vector;
}
setMMUmode(0); // read from kernel D space (mode 0)
if ((newPC = readWordByVirtual(vector | 0x10000)) >= 0) {
if ((newPSW = readWordByVirtual(((vector + 2) & 0xffff) | 0x10000)) >= 0) {
writePSW((newPSW & 0xcfff) | ((CPU.trapPSW >>> 2) & 0x3000)); // set new CPU.PSW with previous mode
if (doubleTrap) {
CPU.CPU_Error |= 4; // Double trap treated as red zone error
CPU.registerVal[6] = 4; // Reset stack
}
if (pushWord(CPU.trapPSW, doubleTrap) >= 0 && pushWord(CPU.registerVal[7], doubleTrap) >= 0) {
CPU.registerVal[7] = newPC;
}
}
}
CPU.trapPSW = -1; // reset flag that we have a trap within a trap
}
if (errorMask) { // Check if this trap sets any CPU error flags
CPU.displayPhysical |= 0x400000; // All CPU error flags set ADRS ERR light
CPU.CPU_Error |= errorMask & 0xfc;
}
return -1; // signal that a trap has occurred
}
// Functions to read and write memory by a 22 bit physical address
function readWordByPhysical(physicalAddress) {
"use strict";
if (physicalAddress < IOBASE_UNIBUS) {
return CPU.memory[physicalAddress >>> 1];
} else {
return iopage.access(physicalAddress, -1, 0);
}
}
function writeWordByPhysical(physicalAddress, data) {
"use strict";
if (physicalAddress < IOBASE_UNIBUS) {
return (CPU.memory[physicalAddress >>> 1] = data);
} else {
return iopage.access(physicalAddress, data, 0);
}
}
function readByteByPhysical(physicalAddress) {
"use strict";
if (physicalAddress < IOBASE_UNIBUS) {
if (physicalAddress & 1) {
return (CPU.memory[physicalAddress >>> 1] >>> 8);
} else {
return (CPU.memory[physicalAddress >>> 1] & 0xff);
}
} else {
return iopage.access(physicalAddress, -1, 1);
}
}
function writeByteByPhysical(physicalAddress, data) {
"use strict";
if (physicalAddress < IOBASE_UNIBUS) {
let memoryIndex = physicalAddress >>> 1;
if (physicalAddress & 1) {
return (CPU.memory[memoryIndex] = (data << 8) | (CPU.memory[memoryIndex] & 0xff));
} else {
return (CPU.memory[memoryIndex] = (CPU.memory[memoryIndex] & 0xff00) | data);
}
} else {
return iopage.access(physicalAddress, data, 1);
}
}
// mapVirtualToPhysical() does memory management by converting a 17 bit I/D virtual
// address to a 22 bit physical address.
// A real PDP 11/70 memory management unit can be enabled separately for read and
// write for diagnostic purposes. This is handled here by having by having an
// enable mask (CPU.mmuEnable) which is tested against the operation access mask
// (accessMask). If there is no match then the virtual address is simply mapped
// as a 16 bit physical address with the upper page going to the IO address space.
// Access bit mask values are MMU_READ and MMU_WRITE with the lower 4 bits contaning
// the operand length; used for auto-increment calculation and to indicate byte mode
// access.
//
// As an aside it turns out that it is the memory management unit that does odd address
// and non-existent memory trapping for main memory: who knew? :-) I thought these would
// have been handled at access time similar to IO page accesses.
//
// When doing mapping CPU.mmuMode selects which address space is to be used:
// 0 = kernel, 1 = supervisor, 2 = illegal, 3 = user. Normally CPU.mmuMode is
// set by the writePSW() function but there are exceptions for instructions which
// move data between address spaces (MFPD, MFPI, MTPD, and MTPI), and function trap().
// These will modify CPU.mmuMode outside of writePSW() and then restore it again if
// all worked. If however something happens to cause a trap then no restore is done
// as writePSW() will have been invoked as part of the trap to resynchronize the
// value of CPU.mmuMode
//
// A PDP 11/70 is different to other PDP 11's in that the highest 18 bit space (017000000
// & above) map directly to unibus space - including low memory. This doesn't appear to
// be particularly useful as it restricts maximum system memory size - however it does
// allow software testing of the unibus map. This feature also appears to confuse some
// OSes which test consecutive memory locations to find maximum memory - and on a full
// memory system find themselves accessing low memory again at high addresses.
//
// 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 MMR0
//nonr leng read trap unus unus ena mnt cmp -mode- i/d --page-- enable
//
// Map a 17 bit I/D virtual address to a 22 bit physical address
function mapVirtualToPhysical(virtualAddress, accessMask) {
"use strict";
var physicalAddress;
CPU.displayAddress = virtualAddress; // Remember virtual address for display purposes
if (!(accessMask & CPU.mmuEnable)) { // This access does not require the MMU
physicalAddress = virtualAddress & 0xffff; // virtual address without MMU is 16 bit (no I/D)
if (physicalAddress >= IOBASE_VIRT) {
physicalAddress |= IOBASE_22BIT;
} else { // no max_memory check in 16 bit mode
if ((physicalAddress & 1) && !(accessMask & MMU_BYTE)) { // odd address check
return trap(0o4, 0x40); // Trap 4 - 0x40 Odd address error
}
}
} else { // This access is mapped by the MMU
let errorMask = 0;
let page = ((virtualAddress >>> 13) | (CPU.mmuMode << 4)) & CPU.mmuPageMask;
let pdr = CPU.mmuPDR[page];
physicalAddress = ((virtualAddress & 0x1fff) + (CPU.mmuPAR[page] << 6)) & 0x3fffff;
if (!(CPU.MMR3 & 0x10)) { // 18 bit mapping needs extra trimming
physicalAddress &= 0x3ffff;
if (physicalAddress >= IOBASE_18BIT) {
physicalAddress |= IOBASE_22BIT;
}
}
if (physicalAddress < MAX_MEMORY) { // Ordinary memory space only needs an odd address check
if ((physicalAddress & 1) && !(accessMask & MMU_BYTE)) {
return trap(0o4, 0x40); // Trap 4 - 0x40 Odd address error
}
CPU.mmuLastPage = page;
} else { // Higher addresses may require unibus mapping and a check if non-existent
if (physicalAddress < IOBASE_22BIT) {
if (physicalAddress >= IOBASE_UNIBUS) {
physicalAddress = mapUnibus(physicalAddress & 0x3ffff); // 18bit unibus space
if (physicalAddress >= MAX_MEMORY && physicalAddress < IOBASE_22BIT) {
return trap(0o4, 0x10); // Trap 4 - 0x10 Unibus time-out - KB11-EM does this after ABORT handling - KB11-CM before
}
} else {
return trap(0o4, 0x20); // Trap 4 - 0x20 Non-existent main memory
}
}
if (physicalAddress !== 0o17777572 || CPU.mmuMode) { // MMR0 is 017777572 and doesn't affect MMR0 bits
CPU.mmuLastPage = page;
}
}
switch (pdr & 0x7) { // Check the Access Control Field (ACF) - really a page type
case 0:
case 3:
default:
errorMask = 0x8000; // non-resident abort
break;
case 1: // read-only with trap
errorMask = 0x1000; // MMU trap - then fall thru
case 2: // read-only
if (!(pdr & 0x80)) {
CPU.mmuPDR[page] = pdr | 0x80; // Set A bit
}
if (accessMask & MMU_WRITE) {
errorMask = 0x2000; // read-only abort
}
break;
case 4: // read-write with read-write trap
errorMask = 0x1000; // MMU trap - then fall thru
case 5: // read-write with write trap
if (accessMask & MMU_WRITE) {
errorMask = 0x1000; // MMU trap - then fall thru
}
case 6: // read-write
if ((pdr & 0xc0) !== 0xc0) { // if A & W already set skip
CPU.mmuPDR[page] = pdr | (accessMask & MMU_WRITE ? 0xc0 : 0x80); // Set A & W bits
}
break;
}
switch (pdr & 0x7f08) { // check page length
case 0x0008: // ignore full length downward page
case 0x7f00: // ignore full length upward page
break;
default:
if (pdr & 0x8) { // page expands downwards
if (((virtualAddress << 2) & 0x7f00) < (pdr & 0x7f00)) {
errorMask |= 0x4000; // page length error abort
}
} else { // page expand upwards
if (((virtualAddress << 2) & 0x7f00) > (pdr & 0x7f00)) {
errorMask |= 0x4000; // page length error abort
}
}
}
// aborts and traps: log FIRST trap and MOST RECENT abort
if (errorMask) {
if (errorMask & 0xe000) {
if (CPU.trapPSW >= 0) {
errorMask |= 0x80; // Instruction complete
}
if (!(CPU.MMR0 & 0xe000)) {
CPU.MMR0 |= errorMask | (CPU.mmuLastPage << 1);
}
return trap(0o250, 0x01); // Trap 250 - 0x00 MMU trap and Set ADRS ERR light 0x01
}
if (!(CPU.MMR0 & 0xf000)) {
if (physicalAddress < 0o17772200 || physicalAddress > 0o17777677) { // 017772200 - 017777677
CPU.MMR0 |= 0x1000; // MMU trap flag
if (CPU.MMR0 & 0x0200) {
CPU.trapMask |= 2; // MMU trap
}
}
}
}
}
return (CPU.displayPhysical = physicalAddress);
}
function readWordByVirtual(virtualAddress) { // input address is 17 bit (I/D)
"use strict";
let physicalAddress;
if ((physicalAddress = mapVirtualToPhysical(virtualAddress, MMU_WORD_READ)) < 0) {
return physicalAddress;
}
return readWordByPhysical(physicalAddress);
}
function writeWordByVirtual(virtualAddress, data) { // input address is 17 bit (I/D)
"use strict";
let physicalAddress;
if ((physicalAddress = mapVirtualToPhysical(virtualAddress, MMU_WORD_WRITE)) < 0) {
return physicalAddress;
}
return writeWordByPhysical(physicalAddress, data);
}
// Stack limit checks only occur for Kernel mode and are either a yellow warning trap
// after instruction completion, or a red abort which stops the current instruction.
function stackCheck(virtualAddress) {
"use strict";
if (!CPU.mmuMode) { // Kernel mode 0 checking only
let checkAddress = virtualAddress & 0xffff;
if (checkAddress <= CPU.stackLimit || checkAddress >= 0xfffe) {
if (checkAddress + 32 <= CPU.stackLimit || checkAddress >= 0xfffe) {
CPU.registerVal[6] = 4; // Reset SP
return trap(0o4, 0x04); // Trap 4 - 0x04 Red zone stack limit
}
CPU.trapMask |= 4; // Yellow zone stack limit
}
}
return virtualAddress;
}
function pushWord(data, skipLimitCheck) {
"use strict";
let virtualAddress = (CPU.registerVal[6] = (CPU.registerVal[6] + 0xfffe) & 0xffff) | 0x10000;
if (!(CPU.MMR0 & 0xe000)) {
CPU.MMR1 = (CPU.MMR1 << 8) | 0xf6;
}
if (!skipLimitCheck) {
if ((virtualAddress = stackCheck(virtualAddress)) < 0) {
return virtualAddress;
}
}
return writeWordByVirtual(virtualAddress, data);
}
function popWord() {
"use strict";
let data;
if ((data = readWordByVirtual(CPU.registerVal[6] | 0x10000)) >= 0) {
CPU.registerVal[6] = (CPU.registerVal[6] + 2) & 0xffff;
}
return data;
}
// getVirtualByMode() maps a six bit instruction operand to a 17 bit I/D virtual
// address space. Instruction operands are six bits in length - three bits for the
// mode and three for the register. The 17th I/D bit in the resulting virtual
// address represents whether the reference is to Instruction space or Data space,
// which depends on the combination of the operand mode and whether the register is
// the Program Counter (register 7).
//
// The eight instruction addressing modes are:-
// 0 R no valid virtual address (error)
// 1 (R) operand from I/D depending if R = 7
// 2 (R)+ operand from I/D depending if R = 7
// 3 @(R)+ address from I/D depending if R = 7 and operand is from D space
// 4 -(R) operand from I/D depending if R = 7
// 5 @-(R) address from I/D depending if R = 7 and operand is from D space
// 6 x(R) x from I space but operand from D space
// 7 @x(R) x from I space but address and operand from D space
//
// Kernel mode stack limit checks are implemented for addressing modes 1, 2, 4 & 6 (!)
// (the other modes can't write relative to SP!)
//
// The accessMode field specifies two bit flags for read or write, or both for a modify.
// Mask values for these are constants MMU_READ and MMU_WRITE which are used by the MMU
// to indicate the data access type (determines whether page access is allowed, whether to
// mark the page as modified, etc).
// In addition the lower four bits specify the operand length. This is 1 for a byte
// or 2 for a word - however the FPP processor may also use lengths of 4 or 8. Thus if
// autoincrement is used for an FPP double word the register will autoincrement by 8.
// The length component is always required here for autoincrement/decrement, but the
// MMU_READ and MMU_WRITE flags are not required if no operand access is intended
// (eg getting the destination address for a JSR instruction jump or locating the virtual
// address of a FPP operand).
//
// Just to keep us on our toes the mode (PC)+ (immediate mode, octal 27) ALWAYS increments
// by 2 no matter what type of operand is used, and SP is never incremented or decremented
// by odd (byte) amounts.
//
// Also CPU.MMR1 must be updated to track which registers have been incremented and
// decremented. This allows software to backout any changes and restart an instruction
// when a page fault occurs.
//
// Convert a six bit instruction operand to a 17 bit I/D virtual address
function getVirtualByMode(addressMode, accessMode) {
"use strict";
let virtualAddress, autoIncrement, reg = addressMode & 7;
switch ((addressMode >>> 3) & 7) {
case 0: // Mode 0: R Registers don't have a virtual address so trap!
return trap(0o4, 0x00); // Trap 4 - 0x00 Illegal addressing mode
case 1: // Mode 1: (R)
switch (reg) {
case 6: // (SP)
virtualAddress = CPU.registerVal[reg] | 0x10000; // D space
if (accessMode & MMU_WRITE) {
if ((virtualAddress = stackCheck(virtualAddress)) < 0) {
return virtualAddress;
}
}
break;
case 7: // (PC)
virtualAddress = CPU.registerVal[reg]; // I space
break;
default: // (Rx)
virtualAddress = CPU.registerVal[reg] | 0x10000; // D space
}
return virtualAddress; // (R) can be either space
case 2: // Mode 2: (R)+ including immediate operand #x
switch (reg) {
case 6: // (SP)+}
autoIncrement = (accessMode + 1) & MMU_LENGTH_EVEN;
virtualAddress = CPU.registerVal[reg] | 0x10000; // D space
if (accessMode & MMU_WRITE) {
if ((virtualAddress = stackCheck(virtualAddress)) < 0) {
return virtualAddress;
}
}
break;
case 7: // (PC)+ aka #d
autoIncrement = 2;
virtualAddress = CPU.registerVal[reg]; // I space
break;
default: // (Rx)+space
autoIncrement = accessMode & MMU_LENGTH_MASK;
virtualAddress = CPU.registerVal[reg] | 0x10000; // D space
}
break; // (R)+ can be either space
case 3: // Mode 3: @(R)+
if ((virtualAddress = readWordByVirtual(reg === 7 ? CPU.registerVal[7] : CPU.registerVal[reg] | 0x10000)) < 0) {
return virtualAddress;
}
//if (reg === 7) {
// LOG_OPERAND(virtualAddress);
//}
autoIncrement = 2;
virtualAddress |= 0x10000; // D space
break; // @(R)+ D space
case 4: // Mode 4: -(R)
switch (reg) {
case 6: // -(SP)
autoIncrement = 0x10000 - ((accessMode + 1) & MMU_LENGTH_EVEN);
virtualAddress = (CPU.registerVal[6] + autoIncrement) | 0x10000; // D space
if (accessMode & MMU_WRITE) {
if ((virtualAddress = stackCheck(virtualAddress)) < 0) {
return virtualAddress;
}
}
break;
case 7: // -(PC) How would you use that?
autoIncrement = 0xfffe; // -2
virtualAddress = (CPU.registerVal[7] + autoIncrement) & 0xffff; // I space
break;
default: // -(Rx)
autoIncrement = 0x10000 - (accessMode & MMU_LENGTH_MASK);
virtualAddress = (CPU.registerVal[reg] + autoIncrement) | 0x10000; // D space
}
break; // -(R) can be either space
case 5: // Mode 5: @-(R)
autoIncrement = 0xfffe; // -2
virtualAddress = (CPU.registerVal[reg] + autoIncrement) | 0x10000; // D space
if ((virtualAddress = readWordByVirtual(reg === 7 ? virtualAddress & 0xffff : virtualAddress)) < 0) {
return virtualAddress;
}
virtualAddress |= 0x10000; // D space
break; // @-(R) D space
case 6: // Mode 6: d(R)
if ((virtualAddress = readWordByVirtual(CPU.registerVal[7])) < 0) { // I space
return virtualAddress;
}
//LOG_OPERAND(virtualAddress);
CPU.registerVal[7] = (CPU.registerVal[7] + 2) & 0xffff;
virtualAddress = (CPU.registerVal[reg] + virtualAddress) | 0x10000; // D space
if (reg === 6 && (accessMode & MMU_WRITE)) {
if ((virtualAddress = stackCheck(virtualAddress)) < 0) {
return virtualAddress;
}
}
return virtualAddress; // d(R) D space
case 7: // Mode 7: @d(R)
if ((virtualAddress = readWordByVirtual(CPU.registerVal[7])) < 0) { // I space
return virtualAddress;
}
//LOG_OPERAND(virtualAddress);
CPU.registerVal[7] = (CPU.registerVal[7] + 2) & 0xffff;
if ((virtualAddress = readWordByVirtual((CPU.registerVal[reg] + virtualAddress) | 0x10000)) < 0) {
return virtualAddress;
}
return virtualAddress | 0x10000; // @d(R) D space
}
CPU.registerVal[reg] = (CPU.registerVal[reg] + autoIncrement) & 0xffff;
if (!(CPU.MMR0 & 0xe000)) {
CPU.MMR1 = (((CPU.MMR1 << 5) | (autoIncrement & 0x1f)) << 3) | reg;
}
return virtualAddress;
}
// Convert an instruction operand into a 17 bit I/D virtual address and then into a
// 22 bit physical address.
// Note: attempting to get the physical address of a register is an error!
function mapPhysicalByMode(addressMode, accessMode) {
"use strict";
let virtualAddress;
if ((virtualAddress = getVirtualByMode(addressMode, accessMode)) < 0) {
return virtualAddress;
}
return mapVirtualToPhysical(virtualAddress, accessMode);
}
function readWordByMode(addressMode) {
"use strict";
if (!(addressMode & 0o70)) { // If register mode just get register value
return CPU.registerVal[addressMode & 7];
} else {
let physicalAddress;
if ((physicalAddress = mapPhysicalByMode(addressMode, MMU_WORD_READ)) < 0) {
return physicalAddress;
}
return readWordByPhysical(physicalAddress);
//if ((addressMode & 0o77) == 0o27) {
// LOG_OPERAND(data);
//}
}
}
function writeWordByMode(addressMode, data) {
"use strict";
if (!(addressMode & 0o70)) { // If register mode write to the register
return (CPU.registerVal[addressMode & 7] = data & 0xffff);
} else {
let physicalAddress;
if ((physicalAddress = mapPhysicalByMode(addressMode, MMU_WORD_WRITE)) < 0) {
return physicalAddress;
}
return writeWordByPhysical(physicalAddress, data & 0xffff);
}
}
function modifyWordByMode(addressMode) {
"use strict";
if (!(addressMode & 0o70)) { // If register mode get register value and remember which register
return CPU.registerVal[CPU.modifyRegister = addressMode & 7];
} else {
let physicalAddress;
if ((physicalAddress = mapPhysicalByMode(addressMode, MMU_WORD_MODIFY)) < 0) {
return physicalAddress;
}
CPU.modifyRegister = -1; // Remember that a physical address was used
return readWordByPhysical(CPU.modifyAddress = physicalAddress);
}
}
function modifyWord(data) {
"use strict";
if (CPU.modifyRegister >= 0) { // Modify the last register or memory address accessed
return (CPU.registerVal[CPU.modifyRegister] = data & 0xffff);
} else {
return writeWordByPhysical(CPU.modifyAddress, data & 0xffff);
}
}
function readByteByMode(addressMode) {
"use strict";
if (!(addressMode & 0o70)) { // If register mode just get register value
return (CPU.registerVal[addressMode & 7] & 0xff);
} else {
let physicalAddress;
if ((physicalAddress = mapPhysicalByMode(addressMode, MMU_BYTE_READ)) < 0) {
return physicalAddress;
}
return readByteByPhysical(physicalAddress);
//if ((addressMode & 0o77) == 0o27) {
// LOG_OPERAND(data);
//}
}
}
function writeByteByMode(addressMode, data) {
"use strict";
if (!(addressMode & 0o70)) { // If register mode write to the register
return (CPU.registerVal[addressMode & 7] = (CPU.registerVal[addressMode & 7] & 0xff00) | (data & 0xff));
} else {
let physicalAddress;
if ((physicalAddress = mapPhysicalByMode(addressMode, MMU_BYTE_WRITE)) < 0) {
return physicalAddress;
}
return writeByteByPhysical(physicalAddress, data & 0xff);
}
}
function modifyByteByMode(addressMode) {
"use strict";
if (!(addressMode & 0o70)) { // If register mode get register value and remember which register
return (CPU.registerVal[CPU.modifyRegister = addressMode & 7] & 0xff);
} else {
let physicalAddress;
if ((physicalAddress = mapPhysicalByMode(addressMode, MMU_BYTE_MODIFY)) < 0) {
return physicalAddress;
}
CPU.modifyRegister = -1; // Remember that a physical address was used
return readByteByPhysical(CPU.modifyAddress = physicalAddress);
}
}
function modifyByte(data) {
"use strict";
if (CPU.modifyRegister >= 0) { // Modify the last register or memory address accessed
return (CPU.registerVal[CPU.modifyRegister] = (CPU.registerVal[CPU.modifyRegister] & 0xff00) | (data & 0xff));
} else {
return writeByteByPhysical(CPU.modifyAddress, data & 0xff);
}
}
// Most instruction read operations use a 6 bit instruction operand via
// a ByMode function such as readWordByMode(). A negative function
// return indicates that something has failed and a trap or abort has
// been invoked. The coding template would be:
//
// if ((src = readWordByMode(instruction >>> 6)) >= 0) {
// success - use the src value
//
// Likewise write operations use function writeWordByMode() to write a
// result to a register or memory and return a negative value if a failure
// occurs (non-existant memory, page fault, non-existant device, etc).
// In this case further instrucion processing should be aborted. The
// coding template is:
//
// if (writeWordByMode(instruction, data) >= 0) {
// continue the instruction
//
// For each Word function there are generally corresponding Byte functions,
// eg readByteByMode() - however there are no byte functions for accessing
// bytes by virtual address as they are not required.
//
// Read/Write operations require two functions to retrieve and then update
// the value. The first function requests memory mapping with modify access,
// stores the register number or physical address for the second function, then
// returns the operand. The second function simply writes the updated value
// back to the remembered location. If either function returns a negative
// value then an error condition has been encountered. The coding template
// is:
//
// if ((dst = modifyByteByMode(instruction)) >= 0) {