-
Notifications
You must be signed in to change notification settings - Fork 141
/
Copy pathvideo.js
8675 lines (8168 loc) · 410 KB
/
video.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 Implements the PCx86 Video component
* @author Jeff Parsons <[email protected]>
* @copyright © 2012-2025 Jeff Parsons
* @license MIT <https://www.pcjs.org/LICENSE.txt>
*
* This file is part of PCjs, a computer emulation software project at <https://www.pcjs.org>.
*/
import ChipSet from "./chipset.js";
import { Controller } from "./bus.js";
import Keyboardx86 from "./keyboard.js";
import Memoryx86 from "./memory.js";
import MESSAGE from "./message.js";
import Mouse from "./mouse.js";
import CharSet from "./charset.js";
import Component from "../../../modules/v2/component.js";
import DumpAPI from "../../../modules/v2/dumpapi.js";
import State from "../../../modules/v2/state.js";
import StrLib from "../../../modules/v2/strlib.js";
import WebLib from "../../../modules/v2/weblib.js";
import { APPCLASS, DEBUG, DEBUGGER, MAXDEBUG, globals } from "./defines.js";
/**
* MDA/CGA Support
* ---------------
*
* Since there's a lot of similarity between the MDA and CGA (eg, their text-mode video buffer
* format, and their use of the 6845 CRT controller), since the MDA ROM contains the fonts used
* by both devices, and since the same ROM BIOS supports both (in fact, the BIOS indiscriminately
* initializes both, regardless which is actually installed), this same component emulates both
* devices.
*
* When no model is specified, this component supports the ability to dynamically switch between
* MDA and CGA emulation, by simply toggling the SW1 motherboard "monitor type" switch settings
* and resetting the machine. In that model-less configuration, we install I/O port handlers for
* both MDA and CGA cards, regardless which monitor type is initially selected.
*
* To simulate an IBM PC containing both an MDA and CGA (ie, a "dual display" system), the machine
* configuration simply defines two video components, one with model "mda" and the other with model
* "cga", resulting in two displays; setting a specific model forces each instance of this component
* to register only those I/O ports belonging to that model.
*
* In a single-display system, dynamically switching cards (ie, between MDA and CGA) creates some
* visual challenges. For one, the MDA prefers a native screen size of 720x350, as it supports only
* one video mode, 80x25, with a 9x14 cell size. The CGA, on the other hand, has an 8x8 cell size,
* so when using an MDA-size screen, an 80x25 CGA screen will end up with 40-pixel borders on the
* left and right, and 75-pixel borders on the top and bottom. The result is a rather tiny CGA font
* surrounded by lots of wasted space, so it's best to turn on font scaling (see the "scale" property)
* and go with a larger screen size of, say, 960x400 (50% larger in width, 100% larger in height).
*
* I've also added support for font-doubling in createFont(). We use the 8x8 font for 80-column
* modes and the "doubled" 16x16 font for 40-column modes OR whenever the screen is large enough
* to use the 16x16 font, since font rendering without scaling provides the sharpest results.
* In fact, there's special logic in setDimensions() to ignore fScaleFont in certain cases (eg,
* 40-column modes, to improve sharpness and avoid stretching the font beyond readability).
*
* Graphics modes, on the other hand, are always scaled to the screen size. Pixels are captured
* in an off-screen buffer, which is then drawn to match the size of the virtual screen.
*
* TODO: Whenever there are borders, they should be filled with the CGA's overscan colors. However,
* in the case of graphics modes (and text modes whenever font scaling is enabled), we don't reserve
* any space for borders, so if borders are important, explicit border support will be required.
*
* EGA Support
* -----------
*
* EGA support piggy-backs on the existing MDA/CGA support. All the existing MDA/CGA port handlers
* now refer to either cardMono or cardColor (instead of directly to cardMDA or cardCGA), enabling
* the handlers to be redirected to cardMDA, cardCGA or cardEGA as appropriate.
*
* Note that an MDA card supported only a Monochrome Display and a CGA card supported only a Color
* Display (well, OK, *or* a TV monitor, which we don't currently support), but the EGA is much
* more flexible: the Enhanced Color Display was the preferred display, but the EGA also supported
* older displays; a Color Display on EGA wasn't ideal (same low resolutions but with more colors),
* but the EGA also brought high-resolution graphics to Monochrome displays, which was nice. Anyway,
* while all those EGA/monitor combinations will be nice to support, our virtual display support
* will focus initially on the Enhanced Color Display.
*
* TODO: Add support for jumpers P1 and P3 (see EGA TechRef p.85). P1 selects either 5-color-output
* for a CGA monitor or 6-color-output for an EGA monitor; we would presumably use this only to
* control certain assumptions about the virtual display's capabilities (ie, Color Display vs. Enhanced
* Color Display). P3 can switch all the I/O ports from 0x3nn to 0x2nn; the default is 0x3nn, and
* that's the only port range the EGA ROM supports as well.
*
* For quick reference, IBM EGA register values for the standard EGA modes, from pages 63-68 of the
* "IBM Enhanced Graphics Adapter" (http://minuszerodegrees.net/oa/OA - IBM Enhanced Graphics Adapter.pdf).
*
* WARNING: Some of these value are not programmed exactly as-is; for example, the CURSCANB values are
* adjusted +1 by the ROM BIOS in most cases, due to an EGA idiosyncrasy that IBM may not have intended.
*
* INT 0x10 Mode Requested: 00 01 02 03 04 05 06 07 0D 0E 0F 10 0F^ 10^ 00* 01* 02* 03*
*
* BIOSMODE: 01 01 03 03 04 04 06 07 0D 0E 0F 10 0F 10 01 01 03 03
* CRTC[0x00]: HTOTAL 37 37 70 70 37 37 70 60 37 70 60 5B 60 5B 2D 2D 5B 5B
* CRTC[0x01]: HDEND 27 27 4F 4F 27 27 4F 4F 27 4F 4F 4F 4F 4F 27 27 4F 4F
* CRTC[0x02]: HBSTART 2D 2D 5C %C 2D 2D 59 56 2D 56 56 53 56 53 2B 2B 53 53
* CRTC[0x03]: HBEND 37 37 2F 2F 37 37 2D 3A 37 2D 1A 17 3A 37 2D 2D 37 37
* CRTC[0x04]: HRSTART 31 31 5F 5F 30 30 5E 51 30 5E 50 50 50 52 28 28 51 51
* CRTC[0x05]: HREND 15 15 07 07 14 14 06 60 14 06 E0 BA 60 00 6D 6D 5B 5B
* CRTC[0x06]: VTOTAL 04 04 04 04 04 04 04 70 04 04 70 6C 70 6C 6C 6C 6C 6C
* CRTC[0x07]: OVERFLOW 11 11 11 11 11 11 11 1F 11 11 1F 1F 1F 1F 1F 1F 1F 1F
* CRTC[0x08]: PRESCAN 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
* CRTC[0x09]: MAXSCAN 07 07 07 07 01 01 01 0D 00 00 00 00 00 00 0D 0D 0D 0D
* CRTC[0x0A]: CURSCAN 06 06 06 06 00 00 00 0B 00 00 00 00 00 00 0B 0B 0B 0B
* CRTC[0x0B]: CURSCANB 07 07 07 07 00 00 00 0C 00 00 00 00 00 00 0C 0C 0C 0C
* CRTC[0x0C]: STARTHI -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
* CRTC[0x0D]: STARTLO -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
* CRTC[0x0E]: CURSORHI -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
* CRTC[0x0F]: CURSORLO -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
* CRTC[0x10]: VRSTART E1 E1 E1 E1 E1 E1 E0 5E E1 E0 5E 5E 5E 5E 5E 5E 5E 5E
* CRTC[0x11]: VREND 24 24 24 24 24 24 23 2E 24 23 2E 2B 2E 2B 2B 2B 2B 2B
* CRTC[0x12]: VDEND C7 C7 C7 C7 C7 C7 C7 5D C7 C7 5D 5D 5D 5D 5D 5D 5D 5D
* CRTC[0x13]: OFFSET 14 14 28 28 14 14 28 28 14 28 14 14 28 28 14 14 28 28
* CRTC[0x14]: UNDERLINE 08 08 08 08 00 00 00 0D 00 00 0D 0F 0D 0F 0F 0F 0F 0F
* CRTC[0x15]: VBSTART E0 E0 E0 E0 E0 E0 DF 5E E0 DF 5E 5F 5E 5F 5E 5E 5E 5E
* CRTC[0x16]: VBEND F0 F0 F0 F0 F0 F0 EF 6E F0 EF 6E 0A 6E 0A 0A 0A 0A 0A
* CRTC[0x17]: MODECTRL A3 A3 A3 A3 A2 A2 C2 A3 E3 E3 8B 8B E3 E3 A3 A3 A3 A3
* CRTC[0x18]: LINECOMP FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
* GRC[0x00]: SRESET 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
* GRC[0x01]: ESRESET 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
* GRC[0x02]: COLORCOMP 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
* GRC[0x03]: DATAROT 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
* GRC[0x04]: READMAP 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
* GRC[0x05]: MODE 10 10 10 10 30 30 00 10 00 00 10 10 00 00 10 10 10 10
* GRC[0x06]: MISC 0E 0E 0E 0E 0F 0F 0D 0A 05 05 07 07 05 05 0E 0E 0E 0E
* GRC[0x07]: COLORDC 00 00 00 00 00 00 00 00 0F 0F 0F 0F 0F 0F 00 00 00 00
* GRC[0x08]: BITMASK FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
* SEQ[0x00]: RESET 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03
* SEQ[0x01]: CLKMODE 0B 0B 01 01 0B 0B 01 00 0B 01 05 05 01 01 0B 0B 01 01
* SEQ[0x02]: MAPMASK 03 03 03 03 03 03 01 03 0F 0F 0F 0F 0F 0F 03 03 03 03
* SEQ[0x03]: CHARMAP 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
* SEQ[0x04]: MEMMODE 03 03 03 03 02 02 06 03 06 06 00 00 06 06 03 03 03 03
* ATC[0x00]: PAL00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
* ATC[0x01]: PAL01 01 01 01 01 13 13 17 08 01 01 08 01 08 01 01 01 01 01
* ATC[0x02]: PAL02 02 02 02 02 15 15 17 08 02 02 00 00 00 02 02 02 02 02
* ATC[0x03]: PAL03 03 03 03 03 17 17 17 08 03 03 00 00 00 03 03 03 03 03
* ATC[0x04]: PAL04 04 04 04 04 02 02 17 08 04 04 18 04 18 04 04 04 04 04
* ATC[0x05]: PAL05 05 05 05 05 04 04 17 08 05 05 18 07 18 05 05 05 05 05
* ATC[0x06]: PAL06 06 06 06 06 06 06 17 08 06 06 00 00 00 06 14 14 14 14
* ATC[0x07]: PAL07 07 07 07 07 07 07 17 08 07 07 00 00 00 07 07 07 07 07
* ATC[0x08]: PAL08 10 10 10 10 10 10 17 10 10 10 00 00 00 38 38 38 38 38
* ATC[0x09]: PAL09 11 11 11 11 11 11 17 18 11 11 08 01 08 39 39 39 39 39
* ATC[0x0A]: PAL0A 12 12 12 12 12 12 17 18 12 12 00 00 00 3A 3A 3A 3A 3A
* ATC[0x0B]: PAL0B 13 13 13 13 13 13 17 18 13 13 00 00 00 3B 3B 3B 3B 3B
* ATC[0x0C]: PAL0C 14 14 14 14 14 14 17 18 14 14 00 04 00 3C 3C 3C 3C 3C
* ATC[0x0D]: PAL0D 15 15 15 15 15 15 17 18 15 15 18 07 18 3D 3D 3D 3D 3D
* ATC[0x0E]: PAL0E 16 16 16 16 16 16 17 18 16 16 00 00 00 3E 3E 3E 3E 3E
* ATC[0x0F]: PAL0F 17 17 17 17 17 17 18 17 17 00 00 00 3F 3F 3F 3F 3F 3F
* ATC[0x10]: MODE 08 08 08 08 01 01 01 0E 01 01 0B 0B 0B 01 08 08 08 08
* ATC[0x11]: OVERSCAN 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
* ATC[0x12]: PLANES 0F 0F 0F 0F 03 03 01 0F 0F 0F 05 05 05 0F 0F 0F 0F 0F
* ATC[0x13]: HPAN 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
* VGA Support
* -----------
*
* VGA support further piggy-backs on the existing EGA support, by adding the extra registers, I/O port
* handlers, etc, that the VGA requires; any differences in registers common to both EGA and VGA are handled on
* a case-by-case basis, usually according to the Videox86.CARD value stored in nCard.
*
* More will be said here about PCjs VGA support later. But first, a word from IBM: "Video Graphics Array [VGA]
* Programming Considerations":
*
* Certain internal timings must be guaranteed by the user, in order to have the CRTC perform properly.
* This is due to the physical design of the chip. These timings can be guaranteed by ensuring that the
* rules listed below are followed when programming the CRTC.
*
* 1. The Horizontal Total [HTOTAL] register (R0) must be greater than or equal to a value of
* 25 decimal.
*
* 2. The minimum positive pulse width of the HSYNC output must be four character clock units.
*
* 3. Register R5, Horizontal Sync End [HREND], must be programmed such that the HSYNC
* output goes to a logic 0 a minimum of one character clock time before the 'horizontal display enable'
* signal goes to a logical 1.
*
* 4. Register R16, Vsync Start [VRSTART], must be a minimum of one horizontal scan line greater
* than register R18 [VDEND]. Register R18 defines where the 'vertical display enable' signal ends.
*
* When bit 5 of the Attribute Mode Control register equals 1, a successful line compare (see Line Compare
* [LINECOMP] register) in the CRT Controller forces the output of the PEL Panning register to 0's until Vsync
* occurs. When Vsync occurs, the output returns to the programmed value. This allows the portion of the screen
* indicated by the Line Compare register to be operated on by the PEL Panning register.
*
* A write to the Character Map Select register becomes valid on the next whole character line. No deformed
* characters are displayed by changing character generators in the middle of a character scan line.
*
* For 256-color 320 x 200 graphics mode hex 13, the attribute controller is configured so that the 8-bit attribute
* stored in video memory for each PEL becomes the 8-bit address (P0 - P7) into the integrated DAC. The user should
* not modify the contents of the internal Palette registers when using this mode.
*
* The following sequence should be followed when accessing any of the Attribute Data registers pointed to by the
* Attribute Index register:
*
* 1. Disable interrupts
* 2. Reset read/write flip/flop
* 3. Write to Index register
* 4. Read from or write to a data register
* 5. Enable interrupts
*
* The Color Select register in the Attribute Controller section may be used to rapidly switch between sets of colors
* in the video DAC. When bit 7 of the Attribute Mode Control register equals 0, the 8-bit color value presented to the
* video DAC is composed of 6 bits from the internal Palette registers and bits 2 and 3 from the Color Select register.
* When bit 7 of the Attribute Mode Control register equals 1, the 8-bit color value presented to the video DAC is
* composed of the lower four bits from the internal Palette registers and the four bits in the Color Select register.
* By changing the value in the Color Select register, software rapidly switches between sets of colors in the video DAC.
* Note that BIOS does not support multiple sets of colors in the video DAC. The user must load these colors if this
* function is to be used. Also see the Attribute Controller block diagram on page 4-26. Note that the above discussion
* applies to all modes except 256 Color Graphics mode. In this mode the Color Select register is not used to switch
* between sets of colors.
*
* An application that saves the "Video State" must store the 4 bytes of information contained in the system microprocessor
* latches in the graphics controller subsection. These latches are loaded with 32 bits from video memory (8 bits per map)
* each time the system microprocessor does a read from video memory. The application needs to:
*
* 1. Use write mode 1 to write the values in the latches to a location in video memory that is not part of
* the display buffer. The last location in the address range is a good choice.
*
* 2. Save the values of the latches by reading them back from video memory.
*
* Note: If in a chain 4 or odd/even mode, it will be necessary to reconfigure the memory organization as four
* sequential maps prior to performing the sequence above. BIOS provides support for completely saving and
* restoring video state. See the IBM Personal System/2 and Personal Computer BIOS Interface Technical Reference
* for more information.
*
* The description of the Horizontal PEL Panning register includes a figure showing the number of PELs shifted left
* for each valid value of the PEL Panning register and each valid video mode. Further panning beyond that shown in
* the figure may be accomplished by changing the start address in the CRT Controller registers, Start Address High
* and Start Address Low. The sequence involved in further panning would be as follows:
*
* 1. Use the PEL Panning register to shift the maximum number of bits to the left. See Figure 4-103 on page
* 4-106 for the appropriate values.
*
* 2. Increment the start address.
*
* 3. If you are not using Modes 0 + , 1 + , 2 + , 3 + ,7, or7 + , set the PEL Panning register to 0. If you
* are using these modes, set the PEL Panning register to 8. The screen will now be shifted one PEL left
* of the position it was in at the end of step 1. Step 1 through Step 3 may be repeated as desired.
*
* The Line Compare register (CRTC register hex 18) should be programmed with even values in 200 line modes when
* used in split screen applications that scroll a second screen on top of a first screen. This is a requirement
* imposed by the scan doubling logic in the CRTC.
*
* If the Cursor Start register (CRTC register hex 0A) is programmed with a value greater than that in the Cursor End
* register (CRTC register hex 0B), then no cursor is displayed. A split cursor is not possible on the VGA.
*
* In 8-dot character modes, the underline attribute produces a solid line across adjacent characters, as in the IBM
* Color/Graphics Monitor Adapter, Monochrome Display Adapter and the Enhanced Graphics Adapter. In 9-dot modes, the
* underline across adjacent characters is dashed, as in the IBM 327X display terminals. In 9-dot modes, the line
* graphics characters (C0 - DF character codes) have solid underlines.
*
* For compatibility with the IBM Enhanced Graphics Adapter (EGA), the internal VGA palette is programmed the same
* as the EGA. The video DAC is programmed by BIOS so that the compatible values in the internal VGA palette produce
* a color compatible with what was produced by EGA. Mode hex 13 (256 colors) is programmed so that the first 16
* locations in the DAC produce compatible colors.
*
* Summing: When BIOS is used to load the video DAC palette for a color mode and a monochrome display is connected
* to the system unit, the color palette is changed. The colors are summed to produce shades of gray that allow
* color applications to produce a readable screen.
*
* There are 4 bits that should not be modified unless the sequencer is reset by setting bit 1 of the Reset register
* to 0. These bits are:
*
* • Bit 3, or bit 0 of the Clocking Mode register
* • Bit 3, or bit 2 of the Miscellaneous Output register
*
* Also, for quick reference, IBM VGA register values for the standard VGA modes (from http://www.pcjs.org/blog/2015/06/01/):
*
* INT 0x10 Mode Requested: 00 01 02 03 04 05 06 0D 0E 10 12 13
*
* BIOSMODE: 01 01 03 03 04 04 06 0D 0E 10 12 13
* CRTC[0x00]: HTOTAL 2D 2D 5F 5F 2D 2D 5F 2D 5F 5F 5F 5F
* CRTC[0x01]: HDEND 27 27 4F 4F 27 27 4F 27 4F 4F 4F 4F
* CRTC[0x02]: HBSTART 28 28 50 50 28 28 50 28 50 50 50 50
* CRTC[0x03]: HBEND 90 90 82 82 90 90 82 90 82 82 82 82
* CRTC[0x04]: HRSTART 2B 2B 55 55 2B 2B 54 2B 54 54 54 54
* CRTC[0x05]: HREND A0 A0 81 81 80 80 80 80 80 80 80 80
* CRTC[0x06]: VTOTAL BF BF BF BF BF BF BF BF BF BF 0B BF
* CRTC[0x07]: OVERFLOW 1F 1F 1F 1F 1F 1F 1F 1F 1F 1F 3E 1F
* CRTC[0x08]: PRESCAN 00 00 00 00 00 00 00 00 00 00 00 00
* CRTC[0x09]: MAXSCAN 4F 4F 4F 4F C1 C1 C1 C0 C0 40 40 41
* CRTC[0x0A]: CURSCAN 0D 0D 0D 0D 00 00 00 00 00 00 00 00
* CRTC[0x0B]: CURSCANB 0E 0E 0E 0E 00 00 00 00 00 00 00 00
* CRTC[0x0C]: STARTHI 00 00 00 00 00 00 00 00 00 00 00 00
* CRTC[0x0D]: STARTLO 00 00 00 00 00 00 00 00 00 00 00 00
* CRTC[0x0E]: CURSORHI 01 01 01 01 01 01 01 01 01 01 01 00
* CRTC[0x0F]: CURSORLO 19 19 41 41 19 19 41 19 41 41 E1 A2
* CRTC[0x10]: VRSTART 9C 9C 9C 9C 9C 9C 9C 9C 9C 83 EA 9C
* CRTC[0x11]: VREND 8E 8E 8E 8E 8E 8E 8E 8E 8E 85 8C 8E
* CRTC[0x12]: VDEND 8F 8F 8F 8F 8F 8F 8F 8F 8F 5D DF 8F
* CRTC[0x13]: OFFSET 14 14 28 28 14 14 28 14 28 28 28 28
* CRTC[0x14]: UNDERLINE 1F 1F 1F 1F 00 00 00 00 00 0F 00 40
* CRTC[0x15]: VBSTART 96 96 96 96 96 96 96 96 96 63 E7 96
* CRTC[0x16]: VBEND B9 B9 B9 B9 B9 B9 B9 B9 B9 BA 04 B9
* CRTC[0x17]: MODECTRL A3 A3 A3 A3 A2 A2 C2 E3 E3 E3 E3 A3
* CRTC[0x18]: LINECOMP FF FF FF FF FF FF FF FF FF FF FF FF
* GRC[0x00]: SRESET 00 00 00 00 00 00 00 00 00 00 00 00
* GRC[0x01]: ESRESET 00 00 00 00 00 00 00 00 00 00 00 00
* GRC[0x02]: COLORCOMP 00 00 00 00 00 00 00 00 00 00 00 00
* GRC[0x03]: DATAROT 00 00 00 00 00 00 00 00 00 00 00 00
* GRC[0x04]: READMAP 00 00 00 00 00 00 00 00 00 00 00 00
* GRC[0x05]: MODE 10 10 10 10 30 30 00 00 00 00 00 40
* GRC[0x06]: MISC 0E 0E 0E 0E 0F 0F 0D 05 05 05 05 05
* GRC[0x07]: COLORDC 00 00 00 00 00 00 00 0F 0F 0F 0F 0F
* GRC[0x08]: BITMASK FF FF FF FF FF FF FF FF FF FF FF FF
* SEQ[0x00]: RESET 03 03 03 03 03 03 03 03 03 03 03 03
* SEQ[0x01]: CLKMODE 08 08 00 00 09 09 01 09 01 01 01 01
* SEQ[0x02]: MAPMASK 03 03 03 03 03 03 01 0F 0F 0F 0F 0F
* SEQ[0x03]: CHARMAP 00 00 00 00 00 00 00 00 00 00 00 00
* SEQ[0x04]: MEMMODE 03 03 03 03 02 02 06 06 06 06 06 0E
* ATC[0x00]: PAL00 00 00 00 00 00 00 00 00 00 00 00 00
* ATC[0x01]: PAL01 01 01 01 01 13 13 17 01 01 01 01 01
* ATC[0x02]: PAL02 02 02 02 02 15 15 17 02 02 02 02 02
* ATC[0x03]: PAL03 03 03 03 03 17 17 17 03 03 03 03 03
* ATC[0x04]: PAL04 04 04 04 04 02 02 17 04 04 04 04 04
* ATC[0x05]: PAL05 05 05 05 05 04 04 17 05 05 05 05 05
* ATC[0x06]: PAL06 14 14 14 14 06 06 17 06 06 14 14 06
* ATC[0x07]: PAL07 07 07 07 07 07 07 17 07 07 07 07 07
* ATC[0x08]: PAL08 38 38 38 38 10 10 17 10 10 38 38 08
* ATC[0x09]: PAL09 39 39 39 39 11 11 17 11 11 39 39 09
* ATC[0x0A]: PAL0A 3A 3A 3A 3A 12 12 17 12 12 3A 3A 0A
* ATC[0x0B]: PAL0B 3B 3B 3B 3B 13 13 17 13 13 3B 3B 0B
* ATC[0x0C]: PAL0C 3C 3C 3C 3C 14 14 17 14 14 3C 3C 0C
* ATC[0x0D]: PAL0D 3D 3D 3D 3D 15 15 17 15 15 3D 3D 0D
* ATC[0x0E]: PAL0E 3E 3E 3E 3E 16 16 17 16 16 3E 3E 0E
* ATC[0x0F]: PAL0F 3F 3F 3F 3F 17 17 17 17 17 3F 3F 0F
* ATC[0x10]: MODE 0C 0C 0C 0C 01 01 01 01 01 01 01 41
* ATC[0x11]: OVERSCAN 00 00 00 00 00 00 00 00 00 00 00 00
* ATC[0x12]: PLANES 0F 0F 0F 0F 03 03 01 0F 0F 0F 0F 0F
* ATC[0x13]: HPAN 08 08 08 08 00 00 00 00 00 00 00 00
*/
/**
* Supported Monitors
*
* The MDA monitor displays 350 lines of vertical resolution, 720 lines of horizontal resolution, and refreshes
* at ~50Hz. The CGA monitor displays 200 lines vertically, 640 horizontally, and refreshes at ~60Hz.
*
* Based on actual MDA timings (see http://diylab.atwebpages.com/pressureDev.htm), the total horizontal
* period (drawing a line and retracing) is ~54.25uSec (1000000uSec / 18432) and the horizontal retrace interval
* is about 15% of that, or ~8.14uSec. Vertical sync occurs once every 370 horizontal periods. Of those 370,
* only 354 represent actively drawn lines (and of those, only 350 are visible); the remaining 16 horizontal
* periods, or 4% of the 370 total, represent the vertical retrace interval.
*
* I don't have similar numbers for the CGA or EGA, so for now, I assume similar percentages; ie, 15% of
* the horizontal period will represent horizontal retrace, and 4% of the vertical pixel maximum (262) will
* represent vertical retrace. However, 24% of the CGA's 262 vertical maximum represents non-visible lines,
* whereas only 5% of the MDA's 370 maximum represents non-visible lines; is there really that much "overscan"
* on the CGA?
*
* For each monitor type, there's a MonitorSpecs object that describes the horizontal and vertical
* timings, along with my assumptions about the percentage of time that drawing is "active" within those periods,
* and then based on the selected monitor type, I compute the number of CPU cycles that each period lasts,
* as well as the number of CPU cycles that drawing lasts within each period, so that the horizontal and vertical
* retrace status flags can be quickly calculated.
*
* For reference, here are some important numbers to know (from https://github.com/reenigne/reenigne/blob/master/8088/cga/register_values.txt):
*
* CGA MDA
* Pixel clock 14.318 MHz 16.257 MHz (aka "maximum video bandwidth", as IBM Tech Refs sometimes call it)
* Horizontal 15.700 KHz 18.432 KHz (aka "horizontal drive", as IBM Tech Refs sometimes call it)
* Vertical 59.923 Hz 49.816 Hz
* Usage 53.69% 77.22%
* H pix 912 = 114*8 882 = 98*9
* V pix 262 370
* Dots 238944 326340
*/
/**
* @typedef {Object} MonitorSpecs
* @property {number} nHorzPeriodsPerSec
* @property {number} nHorzPeriodsPerFrame
* @property {number} percentHorzActive
* @property {number} percentVertActive
*/
/**
* @class Card
* @property {Debuggerx86} dbg
* @unrestricted (allows the class to define properties, both dot and named, outside of the constructor)
*/
export class Card extends Controller {
/**
* MDA Registers (ports 0x3B4, 0x3B5, 0x3B8, and 0x3BA)
*
* NOTE: All monochrome cards (at least all IBM cards) included a parallel interface at ports 0x3BC/0x3BD/0x3BE;
* for the same functionality in PCx86, you must include a properly configured ParallelPort component.
*/
static MDA = {
CRTC: {
INDX: {
PORT: 0x3B4, // NOTE: the low byte of this port address (0xB4) is mirrored at 40:0063 (0x0463)
MASK: 0x1F
},
DATA: {
PORT: 0x3B5
}
},
MODE: {
PORT: 0x3B8, // Mode Select Register, aka CRT Control Port 1 (write-only); the BIOS mirrors this register at 40:0065 (0x0465)
HIRES: 0x01,
VIDEO_ENABLE: 0x08,
BLINK_ENABLE: 0x20
},
STATUS: {
PORT: 0x3BA,
HDRIVE: 0x01,
BWVIDEO: 0x08
}
};
/**
* CGA Registers (ports 0x3D4, 0x3D5, 0x3D8, 0x3D9, and 0x3DA)
*/
static CGA = {
CRTC: {
INDX: {
PORT: 0x3D4, // NOTE: the low byte of this port address (0xB4) is mirrored at 40:0063 (0x0463)
MASK: 0x1F
},
DATA: {
PORT: 0x3D5
}
},
MODE: {
PORT: 0x3D8, // Mode Select Register (write-only); the BIOS mirrors this register at 40:0065 (0x0465)
_80X25: 0x01,
GRAPHIC_SEL: 0x02,
BW_SEL: 0x04,
VIDEO_ENABLE: 0x08, // same as MDA.MODE.VIDEO_ENABLE
HIRES_BW: 0x10,
BLINK_ENABLE: 0x20 // same as MDA.MODE.BLINK_ENABLE
},
COLOR: {
PORT: 0x3D9, // Color Select Register, aka Overscan Register (write-only)
BORDER: 0x07,
BRIGHT: 0x08,
BGND_ALT: 0x10, // alternate, intensified background colors in text mode
COLORSET1: 0x20 // selects aCGAColorSet1 colors for 320x200 graphics mode; aCGAColorSet0 otherwise
},
STATUS: {
PORT: 0x3DA, // read-only; same for EGA (although the EGA calls this STATUS1, to distinguish it from STATUS0)
RETRACE: 0x01,
PEN_TRIGGER: 0x02,
PEN_ON: 0x04,
VRETRACE: 0x08 // when set, this indicates the CGA is performing a vertical retrace
},
/**
* TODO: Add support for light pen port(s) someday....
*/
CLEAR_PEN: {
PORT: 0x3DB
},
PRESET_PEN: {
PORT: 0x3DC
}
};
/**
* Common CRT hardware registers (ports 0x3B4/0x3B5 or 0x3D4/0x3D5)
*
* NOTE: In this implementation, because we have to make at least two of the registers readable (CURSORHI and CURSORLO),
* we end up making ALL the registers readable, otherwise we would have to explicitly block any register marked write-only.
* I don't think making the CRT registers fully readable presents any serious compatibility issues, and it actually offers
* some benefits (eg, improved debugging).
*
* However, some things are broken: the (readable) light pen registers on the EGA are overloaded as (writable) vertical retrace
* registers, so the vertical retrace registers cannot actually be read that way. I'm sure the VGA solved that problem, but I haven't
* looked into it yet.
*/
static CRTC = {
HTOTAL: 0x00, // Horizontal Total
HDISP: 0x01, // Horizontal Displayed
HSPOS: 0x02, // Horizontal Sync Position
HSWIDTH: 0x03, // Horizontal Sync Width
VTOTAL: 0x04, // Vertical Total
VTOTADJ: 0x05, // Vertical Total Adjust
VDISP: 0x06, // Vertical Displayed
VSPOS: 0x07, // Vertical Sync Position
ILMODE: 0x08, // Interlace Mode
MAXSCAN: 0x09, // Max Scan Line Address
CURSCAN: 0x0A, // Cursor Scan Line Top
CURSCAN_SLMASK: 0x1F, // Scan Line Mask
/**
* I don't entirely understand the cursor blink control bits. Here's what the MC6845 datasheet says:
*
* Bit 5 is the blink timing control. When bit 5 is low, the blink frequency is 1/16 of the vertical field rate,
* and when bit 5 is high, the blink frequency is 1/32 of the vertical field rate. Bit 6 is used to enable a blink.
*/
CURSCAN_BLINKON: 0x00, // (supposedly, 0x40 has the same effect as 0x00?)
CURSCAN_BLINKOFF: 0x20, // if blinking is disabled, the cursor is effectively hidden (TODO: CGA and VGA only?)
CURSCAN_BLINKFAST: 0x60, // default is 1/16 of the frame rate; this switches to 1/32 of the frame rate (TODO: CGA only?)
CURSCANB: 0x0B, // Cursor Scan Line Bottom
STARTHI: 0x0C, // Start Address High
STARTLO: 0x0D, // Start Address Low
CURSORHI: 0x0E, // Cursor Address High
CURSORLO: 0x0F, // Cursor Address Low
PENHI: 0x10, // Light Pen High
PENLO: 0x11, // Light Pen Low
TOTAL_REGS: 0x12, // total CRT registers on MDA/CGA
EGA: {
HDEND: 0x01,
HBSTART: 0x02,
HBEND: 0x03,
HRSTART: 0x04,
HREND: 0x05,
VTOTAL: 0x06,
OVERFLOW: {
INDX: 0x07,
VTOTAL_BIT8: 0x01, // bit 8 of register 0x06
VDEND_BIT8: 0x02, // bit 8 of register 0x12
VRSTART_BIT8: 0x04, // bit 8 of register 0x10
VBSTART_BIT8: 0x08, // bit 8 of register 0x15
LINECOMP_BIT8: 0x10, // bit 8 of register 0x18
CURSCAN_BIT8: 0x20, // bit 8 of register 0x0A (EGA only; TODO: What is this for? The CURSCAN register doesn't even use bit 7, so why would it need a bit 8?)
VTOTAL_BIT9: 0x20, // bit 9 of register 0x06 (VGA only)
VDEND_BIT9: 0x40, // bit 9 of register 0x12 (VGA only, unused on EGA)
VRSTART_BIT9: 0x80 // bit 9 of register 0x10 (VGA only, unused on EGA)
},
PRESCAN: 0x08,
/**
* NOTE: EGA/VGA CRTC registers 0x09-0x0F are the same as the MDA/CGA CRTC registers defined above
*/
MAXSCAN: {
INDX: 0x09, // (same as MDA/CGA)
SLMASK: 0x1F, // Scan Line Mask
VBSTART_BIT9: 0x20, // (VGA only)
LINECOMP_BIT9: 0x40, // (VGA only)
CONVERT400: 0x80 // 200-to-400 scan-line conversion is in effect (VGA only)
},
CURSCAN: 0x0A, // (same as MDA/CGA)
CURSCANB: 0x0B, // (same as MDA/CGA)
STARTHI: 0x0C, // (same as MDA/CGA)
STARTLO: 0x0D, // (same as MDA/CGA)
CURSORHI: 0x0E, // (same as MDA/CGA)
CURSORLO: 0x0F, // (same as MDA/CGA)
VRSTART: 0x10, // (formerly PENHI on MDA/CGA)
VREND: { // (formerly PENLO on MDA/CGA; last register on the original 6845 controller)
INDX: 0x11,
HSCAN: 0x0F, // the horizontal scan count value when the vertical retrace output signal becomes inactive
UNCLEAR_VRINT: 0x10, // clear vertical retrace interrupt if NOT set
DISABLE_VRINT: 0x20 // enable vertical retrace interrupt if NOT set
},
VDEND: 0x12,
/**
* The OFFSET register (bits 0-7) specifies the logical line width of the screen. The starting memory address
* for the next character row is larger than the current character row by two or four times this amount.
* The OFFSET register is programmed with a word address. Depending on the method of clocking the CRT Controller,
* this word address is [effectively] either a word or double-word address. #IBMVGATechRef
*/
OFFSET: 0x13,
UNDERLINE: {
INDX: 0x14,
SLMASK: 0x1F,
COUNT_BY_4: 0x20, // (VGA only)
DWORD: 0x40 // (VGA only)
},
VBSTART: 0x15,
VBEND: 0x16,
MODECTRL: {
INDX: 0x17,
COMPAT_MODE: 0x01, // Compatibility Mode Support (CGA A13 control)
SEL_ROW_SCAN: 0x02, // Select Row Scan Counter
SEL_HRETRACE: 0x04, // Horizontal Retrace Select
COUNT_BY_2: 0x08, // Count By Two
OUTPUT_CTRL: 0x10, // Output Control
ADDR_WRAP: 0x20, // Address Wrap (in Word mode, 1 maps A15 to A0 and 0 maps A13; use the latter when only 64Kb is installed)
BYTE_MODE: 0x40, // Byte Mode (1 selects Byte Mode; 0 selects Word Mode)
HARD_RESET: 0x80 // Hardware Reset
},
LINECOMP: 0x18,
TOTAL_REGS: 0x19 // total CRT registers on EGA/VGA
}
};
/**
* TODO: These mask tables need to be card-specific. For example, the STARTHI and CURSORHI registers used to be
* limited to 0x3F, because the MC6845 controller used with the original MDA and CGA cards was limited to 16Kb of RAM,
* whereas later cards like the EGA and VGA had anywhere from 64Kb to 256Kb, so all the bits of those registers were
* significant. Currently, I'm doing very little masking, which means most CRTC registers are treated as full 8-bit
* registers (and fully readable as well), which might cause some compatibility problems for any MDA/CGA apps that
* were sloppy about how they programmed registers.
*
* I do make an exception, however, in the case of STARTHI and CURSORHI, due to the way the MC6845 controller wraps
* addresses around to the beginning of the buffer, because that seems like a high-risk case. See the card-specific
* variable addrMaskHigh.
*/
static CRTCMASKS = {
[Card.CRTC.HTOTAL]: 0xFF, // R0
[Card.CRTC.HDISP]: 0xFF, // R1
[Card.CRTC.HSPOS]: 0xFF, // R2
[Card.CRTC.HSWIDTH]: 0x0F, // R3
[Card.CRTC.VTOTAL]: 0x7F, // R4
[Card.CRTC.VTOTADJ]: 0x1F, // R5
[Card.CRTC.VDISP]: 0x7F, // R6
[Card.CRTC.VSPOS]: 0x7F, // R7
[Card.CRTC.ILMODE]: 0x03, // R8
[Card.CRTC.MAXSCAN]: 0x1F, // R9
[Card.CRTC.CURSCAN]: 0x7F, // R10
[Card.CRTC.CURSCANB]: 0x1F, // R11
[Card.CRTC.STARTHI]: 0x3F, // R12
[Card.CRTC.STARTLO]: 0xFF, // R13
[Card.CRTC.CURSORHI]: 0x3F, // R14
[Card.CRTC.CURSORLO]: 0xFF, // R15
[Card.CRTC.PENHI]: 0x3F, // R16
[Card.CRTC.PENLO]: 0xFF // R17
};
/**
* EGA/VGA Input Status 1 Register (port 0x3DA)
*
* STATUS1 bit 0 has confusing documentation: the EGA Tech Ref says "Logical 0 indicates the CRT raster is in a
* horizontal or vertical retrace interval", whereas the VGA Tech Ref says "Logical 1 indicates a horizontal or
* vertical retrace interval," but then clarifies: "This bit is the real-time status of the INVERTED display enable
* signal". So, instead of calling bit 0 DISP_ENABLE (or more precisely, DISP_ENABLE_INVERTED), it's simply RETRACE.
*
* STATUS1 diagnostic bits 5 and 4 are set according to the Card.ATC.PLANES.MUX bits:
*
* MUX Bit 5 Bit 4
* --- ---- ----
* 00: Red Blue
* 01: SecBlue Green
* 10: SecRed SecGreen
* 11: unused unused
*/
static STATUS1 = {
PORT: 0x3DA,
RETRACE: 0x01, // bit 0: logical OR of horizontal and vertical retrace
VRETRACE: 0x08, // bit 3: set during vertical retrace interval
DIAGNOSTIC: 0x30, // bits 5,4 are controlled by the Card.ATC.PLANES.MUX bits
RESERVED: 0xC6
};
/**
* EGA/VGA Attribute Controller Registers (port 0x3C0: regATCIndx and regATCData)
*
* The current ATC INDX value is stored in cardEGA.regATCIndx (including the Card.ATC.INDX_ENABLE bit), and the
* ATC DATA values are stored in cardEGA.regATCData. The state of the ATC INDX/DATA flip-flop is stored in fATCData.
*
* Note that the ATC palette registers (0x0-0xf) all use the following 6 bit assignments, with bits 6 and 7 unused:
*
* 0: Blue
* 1: Green
* 2: Red
* 3: SecBlue (or mono video)
* 4: SecGreen (or intensity)
* 5: SecRed
*/
static ATC = {
PORT: 0x3C0, // ATC Index/Data Port
INDX_MASK: 0x1F,
INDX_PAL_ENABLE: 0x20, // must be clear when loading palette registers
PALETTE: {
INDX: 0x00, // 16 registers: 0x00 - 0x0F
MASK: 0x3f,
BLUE: 0x01,
GREEN: 0x02,
RED: 0x04,
SECBLUE: 0x08,
BRIGHT: 0x10, // NOTE: The IBM EGA manual (p.56) also calls this the "intensity" bit
SECGREEN: 0x10,
SECRED: 0x20
},
PALETTE_REGS: 0x10, // 16 total palette registers
MODE: {
INDX: 0x10, // ATC Mode Control Register
GRAPHICS: 0x01, // bit 0: set for graphics mode, clear for alphanumeric mode
MONOEM: 0x02, // bit 1: set for monochrome emulation mode, clear for color emulation
TEXT_9DOT: 0x04, // bit 2: set for 9-dot replication in character codes 0xC0-0xDF
BLINK_ENABLE: 0x08, // bit 3: set for text/graphics blink, clear for background intensity
RESERVED: 0x10, // bit 4: reserved
PANCOMPAT: 0x20, // bit 5: set for pixel-panning compatibility
PELWIDTH: 0x40, // bit 6: set for 256-color modes, clear for all other modes
COLORSEL_ALL: 0x80 // bit 7: set to enable all COLORSEL bits (ie, COLORSEL.DAC_BIT5 and COLORSEL.DAC_BIT4)
},
OVERSCAN: {
INDX: 0x11 // ATC Overscan Color Register
},
PLANES: {
INDX: 0x12, // ATC Color Plane Enable Register
MASK: 0x0F,
MUX: 0x30,
RESERVED: 0xC0
},
HPAN: {
INDX: 0x13, // ATC Horizontal PEL Panning Register
SHIFT_LEFT: 0x0F // bits 0-3 indicate # of pixels to shift left
},
COLORSEL: {
INDX: 0x14, // ATC Color Select Register (VGA only)
DAC_BIT7: 0x08, // specifies bit 7 of DAC values (ignored in 256-color modes)
DAC_BIT6: 0x04, // specifies bit 6 of DAC values (ignored in 256-color modes)
DAC_BIT5: 0x02, // specifies bit 5 of DAC values (if ATC.MODE.COLORSEL_ALL is set; ignored in 256-color modes)
DAC_BIT4: 0x01 // specifies bit 4 of DAC values (if ATC.MODE.COLORSEL_ALL is set; ignored in 256-color modes)
},
TOTAL_REGS: 0x14
};
/**
* EGA/VGA Feature Control Register (port 0x3BA or 0x3DA: regFeat)
*
* The EGA BIOS writes 0x1 to Card.FEAT_CTRL.BITS and reads Card.STATUS0.FEAT, then writes 0x2 to
* Card.FEAT_CTRL.BITS and reads Card.STATUS0.FEAT. The bits from the first and second reads are shifted
* into the high nibble of the byte at 40:88h.
*/
static FEAT_CTRL = {
PORT_MONO: 0x3BA, // write port address (other than the two bits below, the rest are reserved and/or unused)
PORT_COLOR: 0x3DA, // write port address (other than the two bits below, the rest are reserved and/or unused)
PORT_READ: 0x3CA, // read port address (VGA only)
BITS: 0x03 // feature control bits
};
/**
* EGA/VGA Miscellaneous Output Register (port 0x3C2: regMisc)
*/
static MISC = {
PORT_WRITE: 0x3C2, // write port address (EGA and VGA)
PORT_READ: 0x3CC, // read port address (VGA only)
IO_SELECT: 0x01, // 0 sets CRT ports to 0x3Bn, 1 sets CRT ports to 0x3Dn
ENABLE_RAM: 0x02, // 0 disables video RAM, 1 enables
CLOCK_SELECT: 0x0C, // 0x0: 14Mhz I/O clock, 0x4: 16Mhz on-board clock, 0x8: external clock, 0xC: unused
DISABLE_DRV: 0x10, // 0 activates internal video drivers, 1 activates feature connector direct drive outputs
PAGE_ODD_EVEN: 0x20, // 0 selects the low 64Kb page of video RAM for text modes, 1 selects the high page
HPOLARITY: 0x40, // 0 selects positive horizontal retrace
VPOLARITY: 0x80 // 0 selects positive vertical retrace
};
/**
* EGA/VGA Input Status 0 Register (port 0x3C2: regStatus0)
*/
static STATUS0 = {
PORT: 0x3C2, // read-only (aka STATUS0, to distinguish it from PORT_CGA_STATUS)
RESERVED: 0x0F,
SWSENSE: 0x10,
SWSENSE_SHIFT: 4,
FEAT: 0x60, // VGA: reserved
INTERRUPT: 0x80 // 1: video is being displayed; 0: vertical retrace is occurring
};
/**
* VGA Subsystem Enable Register (port 0x3C3: regVGAEnable)
*/
static VGA_ENABLE = {
PORT: 0x3C3,
ENABLED: 0x01, // when set, all VGA I/O and memory decoding is enabled; otherwise disabled (TODO: Implement)
RESERVED: 0xFE
};
/**
* EGA/VGA Sequencer Registers (ports 0x3C4/0x3C5: regSEQIndx and regSEQData)
*/
static SEQ = {
INDX: {
PORT: 0x3C4, // Sequencer Index Port
MASK: 0x07
},
DATA: {
PORT: 0x3C5 // Sequencer Data Port
},
RESET: {
INDX: 0x00, // Sequencer Reset Register
ASYNC: 0x01,
SYNC: 0x02
},
CLKMODE: {
INDX: 0x01, // Sequencer Clocking Mode Register
DOTS8: 0x01, // 1: 8 dots; 0: 9 dots
BANDWIDTH: 0x02, // 0: CRTC has access 4 out of every 5 cycles (for high-res modes); 1: CRTC has access 2 out of 5 (VGA: reserved)
SHIFTLOAD: 0x04,
DOTCLOCK: 0x08, // 0: normal dot clock; 1: master clock divided by two (used for 320x200 modes: 0, 1, 4, 5, and D)
SHIFT4: 0x10, // VGA only
SCREEN_OFF: 0x20, // VGA only
RESERVED: 0xC0
},
MAPMASK: {
INDX: 0x02, // Sequencer Map Mask Register
PL0: 0x01,
PL1: 0x02,
PL2: 0x04,
PL3: 0x08,
MAPS: 0x0F,
RESERVED: 0xF0
},
CHARMAP: {
INDX: 0x03, // Sequencer Character Map Select Register
SELB: 0x03, // 0x0: 1st 8Kb of plane 2; 0x1: 2nd 8Kb; 0x2: 3rd 8Kb; 0x3: 4th 8Kb (used when attribute bit 3 is 0)
SELA: 0x0C, // 0x0: 1st 8Kb of plane 2; 0x4: 2nd 8Kb; 0x8: 3rd 8Kb; 0xC: 4th 8Kb (used when attribute bit 3 is 1)
SELB_HI: 0x10, // VGA only
SELA_HI: 0x20 // VGA only
},
MEMMODE: {
INDX: 0x04, // Sequencer Memory Mode Register
ALPHA: 0x01, // set for alphanumeric (A/N) mode, clear for graphics (APA or "All Points Addressable") mode (EGA only)
EXT: 0x02, // set if memory expansion installed, clear if not installed
SEQUENTIAL: 0x04, // set for sequential memory access, clear for mapping even addresses to planes 0/2, odd addresses to planes 1/3
CHAIN4: 0x08 // VGA only: set to select memory map (plane) based on low 2 bits of address
},
TOTAL_REGS: 0x05
};
/**
* VGA Digital-to-Analog Converter (DAC) Registers (regDACMask, regDACState, regDACAddr, and regDACData)
*
* To write DAC data, write an address to DAC.ADDR.PORT_WRITE, then write 3 bytes to DAC.DATA.PORT; the low 6 bits
* of each byte will be concatenated to form an 18-bit DAC value (red is least significant, followed by green, then blue).
* When the final byte is received, the 18-bit DAC value is updated and regDACAddr is auto-incremented.
*
* To read DAC data, the process is similar, but the initial address is written to DAC.ADDR.PORT_READ instead.
*
* DAC.STATE.PORT and DAC.ADDR.PORT_WRITE can be read at any time and will not interfere with a read or write operation
* in progress. To prevent "snow", reading or writing DAC values should be limited to retrace intervals (see regStatus1),
* or by using the SCREEN_OFF bit in the SEQ.CLKMODE register.
*/
static DAC = {
MASK: {
PORT: 0x3C6, // initialized to 0xFF and should not be changed
DEFAULT: 0xFF
},
STATE: {
PORT: 0x3C7,
MODE_WRITE: 0x00, // the DAC is in write mode if bits 0 and 1 are clear
MODE_READ: 0x03 // the DAC is in read mode if bits 0 and 1 are set
},
ADDR: {
PORT_READ: 0x3C7, // write to initiate a read
PORT_WRITE: 0x3C8 // write to initiate a write; read to determine the current ADDR
},
DATA: {
PORT: 0x3C9
},
TOTAL_REGS: 0x100
};
/**
* EGA/VGA Graphics Controller Registers (ports 0x3CE/0x3CF: regGRCIndx and regGRCData)
*
* The VGA added Write Mode 3, which is described as follows:
*
* "Each map is written with 8 bits of the value contained in the Set/Reset register for that map
* (the Enable Set/Reset register has no effect). Rotated system microprocessor data is ANDed with the
* Bit Mask register data to form an 8-bit value that performs the same function as the Bit Mask register
* does in write modes 0 and 2."
*/
static GRC = {
POS1_PORT: 0x3CC, // EGA only, write-only
POS2_PORT: 0x3CA, // EGA only, write-only
INDX: {
PORT: 0x3CE, // GRC Index Port
MASK: 0x0F
},
DATA: {
PORT: 0x3CF // GRC Data Port
},
SRESET: {
INDX: 0x00 // GRC Set/Reset Register (write-only; each bit used only if WRITE.MODE0 and corresponding ESR bit set)
},
ESRESET: {
INDX: 0x01 // GRC Enable Set/Reset Register
},
COLORCOMP: {
INDX: 0x02 // GRC Color Compare Register
},
DATAROT: {
INDX: 0x03, // GRC Data Rotate Register
COUNT: 0x07,
AND: 0x08,
OR: 0x10,
XOR: 0x18,
FUNC: 0x18,
MASK: 0x1F
},
READMAP: {
INDX: 0x04, // GRC Read Map Select Register
NUM: 0x03
},
MODE: {
INDX: 0x05, // GRC Mode Register
WRITE: {
MODE0: 0x00, // write mode 0: each plane written with CPU data, rotated as needed, unless SR enabled
MODE1: 0x01, // write mode 1: each plane written with contents of the processor latches (loaded by a read)
MODE2: 0x02, // write mode 2: memory plane N is written with 8 bits matching data bit N
MODE3: 0x03, // write mode 3: VGA only
MASK: 0x03
},
TEST: 0x04,
READ: {
MODE0: 0x00, // read mode 0: read map mode
MODE1: 0x08, // read mode 1: color compare mode
MASK: 0x08
},
EVENODD: 0x10,
SHIFT: 0x20,
COLOR256: 0x40 // VGA only
},
MISC: {
INDX: 0x06, // GRC Miscellaneous Register
GRAPHICS: 0x01, // set for graphics mode addressing, clear for text mode addressing
CHAIN: 0x02, // set for odd/even planes selected with odd/even values of the processor AO bit
MAPMEM: 0x0C, //
MAPA0128: 0x00, //
MAPA064: 0x04, //
MAPB032: 0x08, //
MAPB832: 0x0C //
},
COLORDC: {
INDX: 0x07 // GRC Color "Don't Care" Register
},
BITMASK: {
INDX: 0x08 // GRC Bit Mask Register
},
TOTAL_REGS: 0x09
};
/**
* EGA Memory Access Functions
*
* Here's where we define all the getMemoryAccess() functions that know how to deal with "planar" EGA memory,
* which consists of 32-bit values for every byte of address space, allowing us to internally store plane 0
* bytes in bits 0-7, plane 1 bytes in bits 8-15, plane 2 bytes in bits 16-23, and plane 3 bytes in bits 24-31.
*
* All our functions have slightly more overhead than the standard Bus memory access functions, because the
* offset (off) parameter is block-relative, which we must transform into a buffer-relative offset. Fortunately,
* all our Memory objects know this and have already recorded their buffer-relative offset in "this.offset".
*
* Also, the EGA includes a set of latches, one for each plane, which must be updated on most reads/writes;
* we rely on the Memory object's "this.controller" property to give us access to the Card's state.
*
* And we take a little extra time to conditionally set DIRTY on writes, meaning if a write did not actually
* change the value of the memory, we will not set DIRTY. The default write functions in memory.js don't take
* that performance hit, but here, it may be worthwhile, because if it results in fewer dirty blocks, display
* updates may be faster.
*
* Note that we don't have to worry about dealing with word accesses that straddle block boundaries, because
* the Bus component automatically breaks those accesses into separate byte requests. Similarly, byte and word
* values for the write functions have already been pre-masked by the Bus component to 8 and 16 bits, respectively.
*
* My motto: Be paranoid, but also be careful not to do any more work than you absolutely have to.
*
*
* CGA Emulation on the EGA
*
* Modes 4/5 (320x200 low-res graphics) emulate the same buffer format that the CGA uses. To recap: 1 byte contains
* 4 pixels (pixel 0 in bits 7-6, pixel 1 in bits 5-4, etc), and thus one row of pixels is 80 (0x50) bytes long.
* Moreover, all even rows are stored in the first 8K of the video buffer (at 0xB8000), and all odd rows are stored
* in the second 8K (at 0xBA000). Of each 8K, only 8000 (0x1F40) bytes are used (80 bytes X 100 rows); the remaining
* 192 bytes of each 8K are unused.
*
* For these modes, the EGA's GRC.MODE is programmed with 0x30: Card.GRC.MODE.EVENODD and Card.GRC.MODE.SHIFT.
* The latter claims to work by forming each 2-bit pixel with even bits from plane 0 and odd bits from plane 1;
* however, I'm unclear how that works if even bytes are only written to plane 0 and odd bytes are only written to
* plane 1, as Card.GRC.MODE.EVENODD implies, because plane 0 would never have any bits for the odd bytes, and
* plane 1 would never have any bits for the even bytes. TODO: Figure this out.
*
*
* Even/Odd Memory Access Functions
*
* The "EVENODD" functions deal with the EGA's default text-mode addressing, where EVEN addresses are mapped to
* plane 0 (and 2) and ODD addresses are mapped to plane 1 (and 3). This occurs when SEQ.MEMMODE.SEQUENTIAL is
* clear (and GRC.MODE.EVENODD is set), turning address bit 0 (A0) into a "plane select" bit. Whether A0 is also
* used as a memory address bit depends on CRTC.MODECTRL.BYTE_MODE: if it's set, then we're in "Byte Mode" and A0 is
* used as-is; if it's clear, then we're in "Word Mode", and either A15 (when CRTC.MODECTRL.ADDR_WRAP is set) or A13
* (when CRTC.MODECTRL.ADDR_WRAP is clear, typically when only 64Kb of EGA memory is installed) is substituted for A0.
*
* Note that A13 remains clear until addresses reach 8K, at which point we've spanned 32Kb of EGA memory, so it makes
* sense to propagate A13 to A0 at that point, so that the next 8K of addresses start using ODD instead of EVEN bytes,
* and no memory is wasted on a 64Kb EGA card.
*
* These functions, however, don't yet deal with all those subtleties: A0 is currently used only as a "plane select"
* bit and set to zero for addressing purposes, meaning that only the EVEN bytes in EGA memory will ever be used.
* TODO: Implement the subtleties.
*/
/**
* Card(video, nCard, data, cbMemory)
*
* Creates an object representing an initial video card state;
* can also restore a video card from state data created by saveCard().
*
* WARNING: Since Card objects are low-level objects that have no UI requirements,
* they do not inherit from the Component class, so you should only use class methods
* of Component, such as Component.assert(), or methods of the parent (video) object.
*
* @this {Card}
* @param {Videox86} [video]
* @param {number} [nCard] (see Videox86.CARD.*)
* @param {Array|null} [data]
* @param {number} [cbMemory] is specified if the card must allocate its own memory buffer
*/
constructor(video, nCard, data, cbMemory)
{
super();
/**
* If a card was originally not present (eg, EGA), then the state will be empty,
* so we need to detect that case and continue indicating that the card is not present.
*/
if (nCard !== undefined && (!data || data.length)) {
this.video = video;