forked from jeffpar/pcjs.v1
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcpu.js
3899 lines (3628 loc) · 130 KB
/
cpu.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
/**
* @fileoverview This file implements the C1Pjs 6502 CPU component.
* @author <a href="mailto:[email protected]">Jeff Parsons</a>
* @copyright © 2012-2020 Jeff Parsons
*
* This file is part of PCjs, a computer emulation software project at <https://www.pcjs.org>.
*
* PCjs is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCjs. If not,
* see <http://www.gnu.org/licenses/gpl.html>.
*
* You are required to include the above copyright notice in every modified copy of this work
* and to display that copyright notice when the software starts running; see COPYRIGHT in
* <https://www.pcjs.org/modules/shared/lib/defines.js>.
*
* Some PCjs files also attempt to load external resource files, such as character-image files,
* ROM files, and disk image files. Those external resource files are not considered part of PCjs
* for purposes of the GNU General Public License, and the author does not claim any copyright
* as to their contents.
*/
"use strict";
if (typeof module !== "undefined") {
var Str = require("../../shared/lib/strlib");
var Usr = require("../../shared/lib/usrlib");
var Web = require("../../shared/lib/weblib");
var Component = require("../../shared/lib/component");
}
/**
* TODO: The Closure Compiler treats ES6 classes as 'struct' rather than 'dict' by default,
* which would force us to declare all class properties in the constructor, as well as prevent
* us from defining any named properties. So, for now, we mark all our classes as 'unrestricted'.
*
* @unrestricted
*/
class C1PCPU extends Component {
/**
* C1PCPU(parmsCPU)
*
* The C1PCPU object has one component-specific initialization property:
*
* autoStart: true to automatically start, false to not, or null (default)
* to make the autoStart decision based on whether or not a Debugger is
* installed (if there's no Debugger AND no "Run" button, then auto-start,
* otherwise don't)
*
* It is hard-coded to simulate a 6502 microprocessor, but it also contains
* hooks into other components for communication with the outside world (eg,
* Panel and Debugger components). This is a logical simulation, not a physical
* simulation, and performance is important, so we take lots of liberties; any
* idiosyncrasies of actual 6502 hardware may not be simulated here, unless it
* affects the accuracy of the simulation when running actual 6502 software.
*
* @this {C1PCPU}
* @param {Object} parmsCPU
*/
constructor(parmsCPU)
{
super("C1PCPU", parmsCPU);
this.clearRegs();
this.flags.powered = false;
this.flags.running = false;
this.fAutoStart = parmsCPU["autoStart"];
/*
* speed is a number from 0 to 2, where 0 means run as close to 1Mhz as possible,
* 1 means run at the fastest safe speed, and 2 means run at maximum speed.
*
* It's updated via the setSpeed() function, which the Debugger's "option" command
* uses to adjust the virtual speed (eg, "o slow", "o fast"). There may also
* be a button present to control the speed as well (using the "setSpeed" binding).
*/
this.SPEED_SLOW = 0; // see this.mhzSlow
this.SPEED_FAST = 1; // see this.mhzFast
this.SPEED_MAX = 2;
this.speed = this.SPEED_SLOW;
this.nCyclesPerSecond = 1000000;
/*
* Additional values that control the overall speed of the simulated hardware,
* and the frequency at which various updates should occur. There are no UI
* mechanisms for tweaking these values (yet).
*
* NOTE: Use of the term "second" below refers to a virtual CPU second, consisting of
* 1 million simulated cycles. The values below are used to divide those 1 million
* cycles into intervals of "work", and as long we are limiting the simulation to 1Mhz
* per ACTUAL second, then 1 virtual second == 1 real second.
*
* However, if the setSpeed() function is used to lift the 1Mhz limit, then 1 virtual
* second may become much shorter, which is why you may briefly notice the video and/or
* status (control panel) updates occurring more frequently. To compensate, calcCycles()
* will automatically scale these values if a recent speed recalculation reveals that
* we're running significantly faster than 1Mhz.
*/
this.nYieldsPerSecond = 30;
this.nVideoUpdatesPerSecond = 30;
this.nStatusUpdatesPerSecond = 5;
this.mhzSlow = 1;
this.mhzFast = 8;
this.aSpeeds = ["Slow", "Fast", "Max"];
this.aSpeedDescs = ["(" + this.mhzSlow + "Mhz)", "(up to " + this.mhzFast + "Mhz)", "(unlimited)"];
/*
* Lists of notification handlers: aReadNotify and aWriteNotify are lists (ie, Arrays)
* of 4-element sub-arrays that, in turn, contain:
*
* [0]: starting address of memory range to monitor
* [1]: ending address of memory range to monitor (inclusive)
* [2]: registered component
* [3]: registered function to call for every read/write from/to memory in that range
*
* The virtual Serial Port and virtual Keyboard components use these handlers to trap
* references to their respective memory-based "ports". Also, the ROM component uses it
* to "repair" any writes to its address range, since memory is one big array, and arrays
* don't support "write-only" regions.
*
* NOTE: the Video component does NOT use notification handlers, because video memory
* is written (and occasionally read) far too frequently for that to be efficient. We
* just let the CPU pound on it like any other chunk of memory, and then make periodic
* calls directly to the Video component to refresh all portions of the video buffer
* that have changed since the last refresh. See displayVideo() for more details.
*
* WARNING: Write notifications currently do not catch STACK writes (ie, BRK, JSR, PHA and
* PHP instructions), because I simply haven't added the necessary code. Besides, JSR is
* one of the most-executed instructions, so I'd rather not slow it down. Note that this
* STACK write limitation affects both the CPU's write-notification handlers AND the Debugger's
* write breakpoints.
*/
this.aReadNotify = [];
this.aWriteNotify = [];
/*
* To speed up the processing of read and write notification handlers, we keep track of
* lower and upper address bounds for each set. These variables maintain those bounds.
* They are initialized to values outside the accessible range of addresses.
*/
this.addrReadLower = 0x10000;
this.addrReadUpper = 0x0;
this.addrWriteLower = 0x10000;
this.addrWriteUpper = 0x0;
/*
* Processor status register (P) flag masks
*/
this.BIT_PN = 0x80; // N = sign
this.BIT_PV = 0x40; // V = overflow
this.BIT_PB = 0x10; // B = break
this.BIT_PD = 0x08; // D = decimal
this.BIT_PI = 0x04; // I = interrupt
this.BIT_PZ = 0x02; // Z = zero
this.BIT_PC = 0x01; // C = carry
// this.VECTOR_NMI = 0xfffa;
this.VECTOR_RESET = 0xfffc;
// this.VECTOR_IRQ = 0xfffe;
/*
* Popular opcodes
*/
this.OP_JSR = 0x20;
/*
* opSim operation codes
*/
this.OP_SIM = 0x02;
this.SIMOP_HLT = 0x00;
this.SIMOP_MSG = 0x01;
/*
* This 256-entry array of opcode functions is at the heart of the CPU engine: step(n).
*
* It might be worth trying a switch() statement instead, to see how the performance compares,
* but I suspect that will vary quite a bit across JavaScript engines; for now, I'm putting my
* money on array lookup.
*/
this.aOpcodeFuncs = [
this.opBRK, // 0x00
this.opORAindx, // 0x01
this.opSim, // 0x02
this.opUndefined, // 0x03
this.opUndefined, // 0x04
this.opORAzp, // 0x05
this.opASLzp, // 0x06
this.opUndefined, // 0x07
this.opPHP, // 0x08
this.opORAimm, // 0x09
this.opASLacc, // 0x0a
this.opUndefined, // 0x0b
this.opUndefined, // 0x0c
this.opORAabs, // 0x0d
this.opASLabs, // 0x0e
this.opUndefined, // 0x0f
this.opBPL, // 0x10
this.opORAindy, // 0x11
this.opUndefined, // 0x12
this.opUndefined, // 0x13
this.opUndefined, // 0x14
this.opORAzpx, // 0x15
this.opASLzpx, // 0x16
this.opUndefined, // 0x17
this.opCLC, // 0x18
this.opORAabsy, // 0x19
this.opUndefined, // 0x1a
this.opUndefined, // 0x1b
this.opUndefined, // 0x1c
this.opORAabsx, // 0x1d
this.opASLabsx, // 0x1e
this.opUndefined, // 0x1f
this.opJSRabs, // 0x20
this.opANDindx, // 0x21
this.opUndefined, // 0x22
this.opUndefined, // 0x23
this.opBITzp, // 0x24
this.opANDzp, // 0x25
this.opROLzp, // 0x26
this.opUndefined, // 0x27
this.opPLP, // 0x28
this.opANDimm, // 0x29
this.opROLacc, // 0x2a
this.opUndefined, // 0x2b
this.opBITabs, // 0x2c
this.opANDabs, // 0x2d
this.opROLabs, // 0x2e
this.opUndefined, // 0x2f
this.opBMI, // 0x30
this.opANDindy, // 0x31
this.opUndefined, // 0x32
this.opUndefined, // 0x33
this.opUndefined, // 0x34
this.opANDzpx, // 0x35
this.opROLzpx, // 0x36
this.opUndefined, // 0x37
this.opSEC, // 0x38
this.opANDabsy, // 0x39
this.opUndefined, // 0x3a
this.opUndefined, // 0x3b
this.opUndefined, // 0x3c
this.opANDabsx, // 0x3d
this.opROLabsx, // 0x3e
this.opUndefined, // 0x3f
this.opRTI, // 0x40
this.opEORindx, // 0x41
this.opUndefined, // 0x42
this.opUndefined, // 0x43
this.opUndefined, // 0x44
this.opEORzp, // 0x45
this.opLSRzp, // 0x46
this.opUndefined, // 0x47
this.opPHA, // 0x48
this.opEORimm, // 0x49
this.opLSRacc, // 0x4a
this.opUndefined, // 0x4b
this.opJMPimm16, // 0x4c
this.opEORabs, // 0x4d
this.opLSRabs, // 0x4e
this.opUndefined, // 0x4f
this.opBVC, // 0x50
this.opEORindy, // 0x51
this.opUndefined, // 0x52
this.opUndefined, // 0x53
this.opUndefined, // 0x54
this.opEORzpx, // 0x55
this.opLSRzpx, // 0x56
this.opUndefined, // 0x57
this.opCLI, // 0x58
this.opEORabsy, // 0x59
this.opUndefined, // 0x5a
this.opUndefined, // 0x5b
this.opUndefined, // 0x5c
this.opEORabsx, // 0x5d
this.opLSRabsx, // 0x5e
this.opUndefined, // 0x5f
this.opRTS, // 0x60
this.opADCindx, // 0x61
this.opUndefined, // 0x62
this.opUndefined, // 0x63
this.opUndefined, // 0x64
this.opADCzp, // 0x65
this.opRORzp, // 0x66
this.opUndefined, // 0x67
this.opPLA, // 0x68
this.opADCimm, // 0x69
this.opRORacc, // 0x6a
this.opUndefined, // 0x6b
this.opJMPabs16, // 0x6c
this.opADCabs, // 0x6d
this.opRORabs, // 0x6e
this.opUndefined, // 0x6f
this.opBVS, // 0x70
this.opADCindy, // 0x71
this.opUndefined, // 0x72
this.opUndefined, // 0x73
this.opUndefined, // 0x74
this.opADCzpx, // 0x75
this.opRORzpx, // 0x76
this.opUndefined, // 0x77
this.opSEI, // 0x78
this.opADCabsy, // 0x79
this.opUndefined, // 0x7a
this.opUndefined, // 0x7b
this.opUndefined, // 0x7c
this.opADCabsx, // 0x7d
this.opRORabsx, // 0x7e
this.opUndefined, // 0x7f
this.opUndefined, // 0x80
this.opSTAindx, // 0x81
this.opUndefined, // 0x82
this.opUndefined, // 0x83
this.opSTYzp, // 0x84
this.opSTAzp, // 0x85
this.opSTXzp, // 0x86
this.opUndefined, // 0x87
this.opDEY, // 0x88
this.opUndefined, // 0x89
this.opTXA, // 0x8a
this.opUndefined, // 0x8b
this.opSTYabs, // 0x8c
this.opSTAabs, // 0x8d
this.opSTXabs, // 0x8e
this.opUndefined, // 0x8f
this.opBCC, // 0x90
this.opSTAindy, // 0x91
this.opUndefined, // 0x92
this.opUndefined, // 0x93
this.opSTYzpx, // 0x94
this.opSTAzpx, // 0x95
this.opSTXzpy, // 0x96
this.opUndefined, // 0x97
this.opTYA, // 0x98
this.opSTAabsy, // 0x99
this.opTXS, // 0x9a
this.opUndefined, // 0x9b
this.opUndefined, // 0x9c
this.opSTAabsx, // 0x9d
this.opUndefined, // 0x9e
this.opUndefined, // 0x9f
this.opLDYimm, // 0xa0
this.opLDAindx, // 0xa1
this.opLDXimm, // 0xa2
this.opUndefined, // 0xa3
this.opLDYzp, // 0xa4
this.opLDAzp, // 0xa5
this.opLDXzp, // 0xa6
this.opUndefined, // 0xa7
this.opTAY, // 0xa8
this.opLDAimm, // 0xa9
this.opTAX, // 0xaa
this.opUndefined, // 0xab
this.opLDYabs, // 0xac
this.opLDAabs, // 0xad
this.opLDXabs, // 0xae
this.opUndefined, // 0xaf
this.opBCS, // 0xb0
this.opLDAindy, // 0xb1
this.opUndefined, // 0xb2
this.opUndefined, // 0xb3
this.opLDYzpx, // 0xb4
this.opLDAzpx, // 0xb5
this.opLDXzpy, // 0xb6
this.opUndefined, // 0xb7
this.opCLV, // 0xb8
this.opLDAabsy, // 0xb9
this.opTSX, // 0xba
this.opUndefined, // 0xbb
this.opLDYabsx, // 0xbc
this.opLDAabsx, // 0xbd
this.opLDXabsy, // 0xbe
this.opUndefined, // 0xbf
this.opCPYimm, // 0xc0
this.opCMPindx, // 0xc1
this.opUndefined, // 0xc2
this.opUndefined, // 0xc3
this.opCPYzp, // 0xc4
this.opCMPzp, // 0xc5
this.opDECzp, // 0xc6
this.opUndefined, // 0xc7
this.opINY, // 0xc8
this.opCMPimm, // 0xc9
this.opDEX, // 0xca
this.opUndefined, // 0xcb
this.opCPYabs, // 0xcc
this.opCMPabs, // 0xcd
this.opDECabs, // 0xce
this.opUndefined, // 0xcf
this.opBNE, // 0xd0
this.opCMPindy, // 0xd1
this.opUndefined, // 0xd2
this.opUndefined, // 0xd3
this.opUndefined, // 0xd4
this.opCMPzpx, // 0xd5
this.opDECzpx, // 0xd6
this.opUndefined, // 0xd7
this.opCLD, // 0xd8
this.opCMPabsy, // 0xd9
this.opUndefined, // 0xda
this.opUndefined, // 0xdb
this.opUndefined, // 0xdc
this.opCMPabsx, // 0xdd
this.opDECabsx, // 0xde
this.opUndefined, // 0xdf
this.opCPXimm, // 0xe0
this.opSBCindx, // 0xe1
this.opUndefined, // 0xe2
this.opUndefined, // 0xe3
this.opCPXzp, // 0xe4
this.opSBCzp, // 0xe5
this.opINCzp, // 0xe6
this.opUndefined, // 0xe7
this.opINX, // 0xe8
this.opSBCimm, // 0xe9
this.opNOP, // 0xea
this.opUndefined, // 0xeb
this.opCPXabs, // 0xec
this.opSBCabs, // 0xed
this.opINCabs, // 0xee
this.opUndefined, // 0xef
this.opBEQ, // 0xf0
this.opSBCindy, // 0xf1
this.opUndefined, // 0xf2
this.opUndefined, // 0xf3
this.opUndefined, // 0xf4
this.opSBCzpx, // 0xf5
this.opINCzpx, // 0xf6
this.opUndefined, // 0xf7
this.opSED, // 0xf8
this.opSBCabsy, // 0xf9
this.opUndefined, // 0xfa
this.opUndefined, // 0xfb
this.opUndefined, // 0xfc
this.opSBCabsx, // 0xfd
this.opINCabsx, // 0xfe
this.opUndefined // 0xff
];
/*
* This is a 256-byte array of cycle counts, indexed by opcode.
* Obviously, true cycle counts are a bit more complicated, but this
* gets us most of the way to an authentic-feeling simulation.
*
* NOTE: BCD functions now account for an extra cycle, and branches
* now account for an extra cycle whenever the branch is taken.
* However, branches still don't add an extra cycle whenever the branch
* crosses a page boundary.
*
* The other gaping hole in our cycle-counting is accounting for all
* page-boundary penalties. Ideally, that's just a matter of checking
* MODE_ABSX, MODE_ABSY, and MODE_INDY instructions for EA straddling
* a page boundary--but is it more complicated than that? What if the
* criteria is not the final EA, but whether the pre-indexing and
* post-indexing EAs are in different pages? I also need to confirm
* whether any other situations merit checking (eg, when a 2 or 3-byte
* instruction straddles a page boundary).
*/
this.aOpcodeCycles = [
7,6,0,0,0,3,5,0,3,2,2,0,0,4,6,0,
2,5,0,0,0,4,6,0,2,4,0,0,0,4,7,0,
3,6,0,0,3,3,5,0,4,2,2,0,4,4,6,0,
2,5,0,0,0,4,6,0,2,4,0,0,0,4,7,0,
6,6,0,0,0,3,5,0,3,2,2,0,3,4,6,0,
2,5,0,0,0,4,6,0,2,4,0,0,0,4,7,0,
6,6,0,0,0,3,5,0,4,2,2,0,5,4,6,0,
2,5,0,0,0,4,6,0,2,4,0,0,0,4,7,0,
0,6,0,0,3,3,3,0,2,0,2,0,4,4,4,0,
2,5,0,0,4,4,4,0,2,4,2,0,0,4,0,0,
2,6,2,0,3,3,3,0,2,2,2,0,4,4,4,0,
2,5,0,0,4,4,4,0,2,4,2,0,4,4,4,0,
2,6,0,0,3,3,5,0,2,2,2,0,4,4,6,0,
2,5,0,0,0,4,6,0,2,4,0,0,0,4,7,0,
2,6,0,0,3,3,5,0,2,2,2,0,4,4,6,0,
2,5,0,0,0,4,6,0,2,4,0,0,0,4,7,0
];
}
/**
* reset(fPowerOn)
*
* Note that we follow the same model here as other selected reset() handlers; for example, Video.reset()
* accepts an fPowerOn parameter to govern what's initially displayed on the video screen.
*
* @this {C1PCPU}
* @param {boolean|undefined} fPowerOn is true for the initial reset, so that if the Debugger isn't
* loaded, we can elect to start running. Under any other circumstances (such as whenever Computer.reset()
* is called), "auto-run" is not a good idea, and can actually introduce bugs (eg, multiple run() timers).
*/
reset(fPowerOn)
{
if (this.flags.running) {
this.halt();
}
this.clearRegs();
this.regPC = this.getWord(this.VECTOR_RESET);
this.clearError(); // clear any fatal error/exception
/*
* If there's a Debugger, notify Debugger.reset(); otherwise, start running
*/
if (DEBUGGER && this.dbg) {
this.dbg.reset();
}
else if (fPowerOn) {
if (this.fAutoStart === true || this.fAutoStart === null && (!DEBUGGER || !this.dbg) && this.bindings["run"] === undefined) {
this.run(); // start running automatically on the initial power-up, assuming there's no Debugger
}
}
}
/**
* @this {C1PCPU}
* @param {string} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea")
* @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "run")
* @param {HTMLElement} control is the HTML control DOM object (eg, HTMLButtonElement)
* @param {string} [sValue] optional data value
* @return {boolean} true if binding was successful, false if unrecognized binding request
*/
setBinding(sHTMLType, sBinding, control, sValue)
{
var fBound = false;
switch(sBinding) {
case "run":
this.bindings[sBinding] = control;
control.onclick = function(cpu) {
return function() {
if (!cpu.flags.running) {
cpu.run();
} else {
cpu.halt();
}
};
}(this);
fBound = true;
break;
case "A": case "X": case "Y": case "S": case "PC":
case "C": case "Z": case "I": case "D": case "B": case "V": case "N":
case "speed":
this.bindings[sBinding] = control;
fBound = true;
break;
case "setSpeed":
this.bindings[sBinding] = control;
control.onclick = function(cpu) {
return function() {
var speed = (cpu.speed >= cpu.SPEED_MAX? cpu.SPEED_SLOW : cpu.speed+1);
cpu.setSpeed(speed, true);
};
}(this);
fBound = true;
break;
default:
break;
}
return fBound;
}
/**
* @this {C1PCPU}
* @param {Array} abMemory
* @param {number} start
* @param {number} end
*/
setBuffer(abMemory, start, end)
{
this.abMem = abMemory;
this.offMem = start;
this.cbMem = end - start + 1;
this.offLimit = this.offMem + this.cbMem;
if (this.offMem) {
/*
* It's not that we couldn't support an address buffer that starts at a non-zero offset;
* we simply have lots of code (eg, all the opcode handlers) that assumes offMem is zero,
* and therefore that abMem can be indexed by any of the CPU registers without adding offMem.
* All that code would have to be changed (at a slight performance penalty) if we couldn't
* make this assumption.
*/
Component.error("unsupported CPU address buffer offset (" + this.offMem + ")");
return;
}
this.setReady();
}
/**
* @this {C1PCPU}
* @param {boolean} fOn
* @param {C1PComputer} cmp
*/
setPower(fOn, cmp)
{
if (fOn && !this.flags.powered) {
this.cmp = cmp;
/*
* Attach the Debugger, if any, to the CPU, so that the CPU can periodically
* notify it as needed (when the CPU starts, stops, and executes instructions)
*/
if (DEBUGGER) {
this.dbg = cmp.getComponentByType("debugger");
if (this.dbg)
this.dbg.init();
}
/*
* Attach the Video device to the CPU, so that the CPU can periodically update
* the video display via displayVideo(), as cycles permit.
*/
var video = cmp.getComponentByType("video");
if (video) {
this.displayVideo = function(v) {
return function() {
v.updateScreen();
};
}(video);
this.setFocus = function(v) {
return function() {
v.setFocus();
};
}(video);
}
this.flags.powered = true;
this.reset(true);
this.update();
}
}
/**
* Add a memory read-notification handler to the CPU's list of such handlers.
*
* @this {C1PCPU}
* @param {number} start address
* @param {number} end address
* @param {Component} component
* @param {function(number,number)} fn is called with the EA and PC values at the time of the write
*/
addReadNotify(start, end, component, fn)
{
if (this.findNotify(this.aReadNotify, start, end, component, fn) < 0) {
if (this.addrReadLower > start)
this.addrReadLower = start;
if (this.addrReadUpper < end)
this.addrReadUpper = end;
this.aReadNotify.push([start, end, component, fn]);
if (DEBUG) this.log("addReadNotify(" + Str.toHexWord(start) + "," + Str.toHexWord(end) + "," + component.id + "): new read range: " + Str.toHexWord(this.addrReadLower) + "-" + Str.toHexWord(this.addrReadUpper));
}
}
/**
* @this {C1PCPU}
* @param {number} addrRead is the EA value at the time of the read
* @param {number} [addrFrom] is the PC value at the time of the read;
* this will be undefined for read notifications triggered by assorted Debugger commands,
* so all handlers should be prepared for that as well.
*/
checkReadNotify(addrRead, addrFrom)
{
for (var i=0; i < this.aReadNotify.length; i++) {
if (addrRead >= this.aReadNotify[i][0] && addrRead <= this.aReadNotify[i][1]) {
this.aReadNotify[i][3].call(this.aReadNotify[i][2], addrRead, addrFrom);
}
}
}
/**
* Remove a memory read-notification handler from the CPU's list of such handlers.
*
* @this {C1PCPU}
* @param {number} start address
* @param {number} end address
* @param {Component} component
* @param {function(number,number)} fn of previously added handler
* @return {boolean} true if remove was successful, false if the handler was not found
*/
removeReadNotify(start, end, component, fn)
{
var aBounds = this.removeNotify(this.aReadNotify, start, end, component, fn);
if (aBounds.length == 4) {
this.addrReadLower = aBounds[2];
this.addrReadUpper = aBounds[3];
if (DEBUG) this.log("removeReadNotify(" + Str.toHexWord(start) + "," + Str.toHexWord(end) + "," + component.id + "): new read range: " + Str.toHexWord(this.addrReadLower) + "-" + Str.toHexWord(this.addrReadUpper));
return true;
}
return false;
}
/**
* Add a memory write-notification handler to the CPU's list of such handlers.
*
* @this {C1PCPU}
* @param {number} start address
* @param {number} end address
* @param {Component} component
* @param {function(number,number)} fn is called with the EA and PC values at the time of the write
*/
addWriteNotify(start, end, component, fn)
{
if (this.findNotify(this.aWriteNotify, start, end, component, fn) < 0) {
if (this.addrWriteLower > start)
this.addrWriteLower = start;
if (this.addrWriteUpper < end)
this.addrWriteUpper = end;
this.aWriteNotify.push([start, end, component, fn]);
if (DEBUG) this.log("addWriteNotify(" + Str.toHexWord(start) + "," + Str.toHexWord(end) + "," + component.id + "): new write range: " + Str.toHexWord(this.addrWriteLower) + "-" + Str.toHexWord(this.addrWriteUpper));
}
}
/**
* @this {C1PCPU}
* @param {number} addrWrite is the EA value at the time of the write
* @param {number} [addrFrom] is the PC value at the time of the write;
* this will be undefined for write notifications triggered by assorted Debugger commands,
* so all handlers should be prepared for that as well.
*/
checkWriteNotify(addrWrite, addrFrom)
{
for (var i=0; i < this.aWriteNotify.length; i++) {
if (addrWrite >= this.aWriteNotify[i][0] && addrWrite <= this.aWriteNotify[i][1]) {
this.aWriteNotify[i][3].call(this.aWriteNotify[i][2], addrWrite, addrFrom);
}
}
}
/**
* Remove a memory write-notification handler from the CPU's list of such handlers.
*
* @this {C1PCPU}
* @param {number} start address
* @param {number} end address
* @param {Component} component
* @param {function(number,number)} fn of previously added handler
* @return {boolean} true if remove was successful, false if the handler was not found
*/
removeWriteNotify(start, end, component, fn)
{
var aBounds = this.removeNotify(this.aWriteNotify, start, end, component, fn);
if (aBounds.length == 4) {
this.addrWriteLower = aBounds[2];
this.addrWriteUpper = aBounds[3];
if (DEBUG) this.log("removeWriteNotify(" + Str.toHexWord(start) + "," + Str.toHexWord(end) + "," + component.id + "): new write range: " + Str.toHexWord(this.addrWriteLower) + "-" + Str.toHexWord(this.addrWriteUpper));
return true;
}
return false;
}
/**
* Find a memory notification handler from the given array of handlers
*
* @this {C1PCPU}
* @param {Array} aNotify array of handlers
* @param {number} start address
* @param {number} end address
* @param {Component} component
* @param {function(number,number)} fn of previously added handler
* @return {number} index of the matching handler, or -1 if not found
*/
findNotify(aNotify, start, end, component, fn)
{
for (var i=0; i < aNotify.length; i++) {
if (aNotify[i][0] == start && aNotify[i][1] == end && aNotify[i][2] == component && aNotify[i][3] == fn) {
return i;
}
}
return -1;
}
/**
* Remove a memory notification handler from the given array of handlers
*
* @this {C1PCPU}
* @param {Array} aNotify array of handlers
* @param {number} start address
* @param {number} end address
* @param {Component} component
* @param {function(number,number)} fn of previously added handler
* @return {Array} bounds of previous handler ([0] and [1]) and new lower and upper address bounds ([2] and [3])
*/
removeNotify(aNotify, start, end, component, fn)
{
var aBounds = [];
var i = this.findNotify(aNotify, start, end, component, fn);
if (i >= 0) {
aBounds.push(aNotify[i][0]);
aBounds.push(aNotify[i][1]);
aNotify.splice(i, 1);
var addrLower = 0x10000, addrUpper = 0x0;
for (i=0; i < aNotify.length; i++) {
if (addrLower > aNotify[i][0])
addrLower = aNotify[i][0];
if (addrUpper < aNotify[i][1])
addrUpper = aNotify[i][1];
}
aBounds.push(addrLower);
aBounds.push(addrUpper);
}
return aBounds;
}
/**
* @this {C1PCPU}
* @param {number} [speed] is one of: 0 (slow), 1 (fast) or 2 (maximum)
* @param {boolean} [fOnClick] is true if called from a click handler that might have stolen focus
* @desc Whenever the speed is changed, the running cycle count and corresponding start time must be reset,
* so that the next effective speed calculation obtains sensible results. In fact, when run() initially calls
* setSpeed() with no parameters, that's all this function does (it doesn't change the current speed setting).
*/
setSpeed(speed, fOnClick)
{
if (speed !== undefined) {
this.speed = speed;
if (this.bindings["setSpeed"])
this.bindings["setSpeed"].innerHTML = this.aSpeeds[speed >= 2? 0 : speed+1];
this.println("running at " + this.aSpeeds[speed].toLowerCase() + " speed " + this.aSpeedDescs[speed]);
if (fOnClick) this.setFocus();
}
this.nRunCycles = 0;
this.msRunStart = Usr.getTime();
this.calcCycles();
}
/**
* @this {C1PCPU}
* @param {number} nCycles
* @param {number} msElapsed
*/
calcSpeed(nCycles, msElapsed)
{
if (msElapsed) {
this.mhz = Math.round(nCycles / ( msElapsed * 100)) / 10;
if (msElapsed >= 86400000)
this.setSpeed(); // reset all our counters once per day so that we never have to worry about overflow
}
}
/**
* @this {C1PCPU}
*/
displayVideo()
{
// Nothing to do until setPower() installs a replacement function
}
/**
* @this {C1PCPU}
*/
setFocus()
{
// Nothing to do until setPower() installs a replacement function
}
/**
* @this {C1PCPU}
* @param {string} sReg
* @param {number} vReg
* @param {number} [len]
*/
displayReg(sReg, vReg, len)
{
if (this.bindings[sReg] !== undefined) {
if (len === undefined) len = 1;
var s = "0000" + vReg.toString(16);
this.bindings[sReg].innerHTML = s.slice(s.length-len).toUpperCase();
}
}
/**
* @this {C1PCPU}
*/
displayStatus()
{
this.displayReg("A", this.regA, 2);
this.displayReg("X", this.regX, 2);
this.displayReg("Y", this.regY, 2);
var regP = this.getRegP();
this.displayReg("C", (regP & this.BIT_PC)? 1 : 0);
this.displayReg("Z", (regP & this.BIT_PZ)? 1 : 0);
this.displayReg("I", (regP & this.BIT_PI)? 1 : 0);
this.displayReg("D", (regP & this.BIT_PD)? 1 : 0);
this.displayReg("B", (regP & this.BIT_PB)? 1 : 0);
this.displayReg("V", (regP & this.BIT_PV)? 1 : 0);
this.displayReg("N", (regP & this.BIT_PN)? 1 : 0);
this.displayReg("S", this.regS, 4);
this.displayReg("PC", this.regPC, 4);
if (this.bindings["speed"] && this.mhz) {
this.bindings["speed"].innerHTML = this.mhz.toFixed(1) + "Mhz";
}
}
/**
* @this {C1PCPU}
* @return {boolean}
*/
isRunning()
{
return this.flags.running;
}
/**
* Calculate the number of cycles to process for each "burst" of CPU activity. The size of a burst
* is driven by the following values:
*
* nYieldsPerSecond (eg, 30)
* nVideoUpdatesPerSecond (eg, 30)
* nStatusUpdatesPerSecond (eg, 5)
*
* The largest of the above values forces the size of the burst to its smallest value. Let's say that
* largest value is 30. Assuming nCyclesPerSecond is 1,000,000, that results in bursts of 33,333 cycles.
*
* At the end of each burst, we subtract burst cycles from yield, video, and status cycle "threshold"
* counters. Whenever the "next yield" cycle counter goes to (or below) zero, we compare elapsed time
* to the time we expected the virtual hardware to take (eg, 1000ms/50 or 20ms), and if we still have time
* remaining, we sleep the remaining time (or 0ms if there's no remaining time), and then restart run().
*
* Similarly, whenever the "next video update" cycle counter goes to (or below) zero, we call displayVideo(),
* and whenever the "next status update" cycle counter goes to (or below) zero, we call displayStatus().
*
* @this {C1PCPU}
* @param {boolean} [fRecalc] is true if the caller wants to recalculate thresholds based on the
* most recent mhz calculation (see calcSpeed)
*/
calcCycles(fRecalc)
{
/*
* Calculate the most cycles we're allowed to execute in a single "burst"
*/
var nMostUpdatesPerSecond = this.nYieldsPerSecond;
if (nMostUpdatesPerSecond < this.nVideoUpdatesPerSecond) nMostUpdatesPerSecond = this.nVideoUpdatesPerSecond;
if (nMostUpdatesPerSecond < this.nStatusUpdatesPerSecond) nMostUpdatesPerSecond = this.nStatusUpdatesPerSecond;
/*
* Calculate "per" values for the yield, video update, and status update cycle counters
*/
var vMultiplier = 1;
if (fRecalc && this.speed > this.SPEED_SLOW && this.mhz) vMultiplier = this.mhz;
if (vMultiplier > this.mhzFast && this.speed < this.SPEED_MAX) vMultiplier = this.mhzFast;
this.msPerYield = Math.round(1000/this.nYieldsPerSecond);
this.nCyclesPerBurst = Math.floor(this.nCyclesPerSecond / nMostUpdatesPerSecond * vMultiplier);
this.nCyclesPerYield = Math.floor(this.nCyclesPerSecond / this.nYieldsPerSecond * vMultiplier);
this.nCyclesPerVideoUpdate = Math.floor(this.nCyclesPerSecond / this.nVideoUpdatesPerSecond * vMultiplier);
this.nCyclesPerStatusUpdate = Math.floor(this.nCyclesPerSecond / this.nStatusUpdatesPerSecond * vMultiplier);
/*
* And initialize "next" yield, video update, and status update cycle "threshold" counters to those "per" values
*/
if (!fRecalc) {
this.nCyclesNextYield = this.nCyclesPerYield;
this.nCyclesNextVideoUpdate = this.nCyclesPerVideoUpdate;
this.nCyclesNextStatusUpdate = this.nCyclesPerStatusUpdate;
}
this.nRecalcCycles = 0;
}
/**
* @this {C1PCPU}
*/
calcStartTime()
{
if (this.nRecalcCycles >= this.nCyclesPerSecond) {
this.calcCycles(true);
}
this.nCyclesThisRun = 0;
this.msStartThisRun = Usr.getTime();
}
/**
* @this {C1PCPU}
* @return {number}
*/
calcRemainingTime()
{
var msCurrent = Usr.getTime();
var msYield = this.msPerYield;
if (this.nCyclesThisRun) {
/*
* Normally, we would assume we executed a full quota of work over msPerYield, but since the CPU
* now has the option of calling yieldCPU(), that might not be true. If nCyclesThisRun is correct, then
* the ratio of nCyclesThisRun/nCyclesPerYield should represent the percentage of work we performed,
* and so applying that percentage to msPerYield should give us a better estimate of work vs. time.
*/
msYield = Math.round(msYield * this.nCyclesThisRun / this.nCyclesPerYield);
// if (msYield < this.msPerYield) this.println("scaling msPerYield (" + this.msPerYield + ") to msYield (" + msYield + ")");
}
var msElapsedThisRun = msCurrent - this.msStartThisRun;
var msRemainsThisRun = msYield - msElapsedThisRun;
/*
* We could pass only "this run" results to calcSpeed():
*
* nCycles = this.nCyclesThisRun;
* msElapsed = msElapsedThisRun;
*
* but it seems preferable to use longer time periods and hopefully get a more accurate speed.
*
* Also, if msRemainsThisRun >= 0 && this.speed == this.SPEED_SLOW, we could pass these results instead:
*
* nCycles = this.nCyclesThisRun;