The IBM VGA (“Video Graphics Array”) standard was introduced as part of the IBM PS/2 line of computers; it was not a feature you could purchase or install in older PC, XT or AT-compatible machines. In fact, full VGA support was not even available in all PS/2 models.

Of the first four PS/2 models – the 8086-based Model 30, the 80286-based Model 50 and Model 60, and the 80386-based Model 80 – VGA support was available only in the three higher-end models. The Model 30 came with MCGA (“Multicolor Graphics Array”) video hardware that supported a subset of VGA modes (eg, 640x480 2-color and 320x200 256-color graphics).

It wasn’t until October 1987 that IBM finally introduced an 8-bit ISA card that brought VGA capability to older PCs. The card was called the IBM PS/2 Display Adapter. However, I think the name is a bit confusing, since the card could only be used in PC, XT, and AT-compatible systems. I’ll refer to it here simply as the IBM VGA.

The VGA ROM used here is assumed to have come from an original IBM VGA. It’s unknown if IBM ever made any revisions to the VGA ROM. With the introduction of the PS/2 family and the VGA, IBM decided to no longer publish the source code for its ROMs, so I’ve created some assemblable source code from the IBM VGA ROM here.

I’ve finally started debugging a machine configuration that uses the IBM VGA ROM. Since the VGA and the 80386 are contemporaries, I’m using an 80386 machine configuration. However, I don’t expect the IBM VGA ROM to require any 80386 support or PS/2-specific features.

The first problem I ran into was here:

;
;   Initialize the ROM BIOS Video Mode Options byte @40:0087 (default to color and 256Kb of RAM)
;
mov	byte [0x487],0x60	; 0000008D
;
;   The x100 subroutine alternately enables port 0x3B? and 0x3D? decoding, verifying that there is
;   no response on opposing ports 0x3D? and 0x3B?, respectively; otherwise, it assumes that another
;   video card must exist and attempts to select co-existing settings for the VGA.  For example, if
;   there is an unexpected response on the color ports, the VGA ROM will default to mono operation.
;
call	x100			; 00000092

The Video component installs I/O port handlers for all possible I/O ranges; when a range isn’t being used, the associated I/O operations are redirected to a dummy Card, so that the active Card isn’t affected. Here, however, that was insufficient. If the VGA is the only installed video card, the VGA ROM expects NO RESPONSE on inactive CRTC ports. So I’ve changed the CRTC I/O handlers to check the Card’s fActive flag. This seems like a safe and logical change, but I still have to check for backward-compatibility issues with older ROMs.

Other problems included:

  • Some bugs in Read Mode 1 that caused a memory test failure
  • Horizontal and vertical retrace timing issues (the ROM requires a specific number of intervals per second)
  • Differences between the EGA and VGA in the SWSENSE bit (bit 4) of Input Status Register 0

The last problem was the most puzzling, because the ROM programs a series of values into the first DAC register, and expects the SWSENSE bit of Input Status Register 0 to change in very specific ways, depending on the kind of monitor attached.

I’ve not found any hardware documentation that explains exactly how this should work. IBM’s own Technical Reference material is extremely vague:

"Bit 4: Switch Sense Bit - This bit allows the system microprocessor to read the switch sense line.
This bit allows the power-on self-test to determine if a monochrome or color display is connected to
the system."

I’ve hard-coded a solution that assumes a color monitor. Support for using a monochrome monitor with an EGA was never completed, and this is another related issue that will have to be resolved for the VGA as well.


While debugging and fixing assorted IBM VGA issues, I made a table of all the register values for the video modes commonly used on the IBM VGA. Here’s that table:

INT 0x10 Mode Requested:    0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x0D 0x0E 0x10 0x12 0x13

BIOSMODE:                   0x01 0x01 0x03 0x03 0x04 0x04 0x06 0x0D 0x0E 0x10 0x12 0x13
CRTC[0x00]: HTOTAL          0x2D 0x2D 0x5F 0x5F 0x2D 0x2D 0x5F 0x2D 0x5F 0x5F 0x5F 0x5F
CRTC[0x01]: HDISP_END       0x27 0x27 0x4F 0x4F 0x27 0x27 0x4F 0x27 0x4F 0x4F 0x4F 0x4F
CRTC[0x02]: HBLANK_START    0x28 0x28 0x50 0x50 0x28 0x28 0x50 0x28 0x50 0x50 0x50 0x50
CRTC[0x03]: HBLANK_END      0x90 0x90 0x82 0x82 0x90 0x90 0x82 0x90 0x82 0x82 0x82 0x82
CRTC[0x04]: HRETRACE_START  0x2B 0x2B 0x55 0x55 0x2B 0x2B 0x54 0x2B 0x54 0x54 0x54 0x54
CRTC[0x05]: HRETRACE_END    0xA0 0xA0 0x81 0x81 0x80 0x80 0x80 0x80 0x80 0x80 0x80 0x80
CRTC[0x06]: VTOTAL          0xBF 0xBF 0xBF 0xBF 0xBF 0xBF 0xBF 0xBF 0xBF 0xBF 0x0B 0xBF
CRTC[0x07]: OVERFLOW        0x1F 0x1F 0x1F 0x1F 0x1F 0x1F 0x1F 0x1F 0x1F 0x1F 0x3E 0x1F
CRTC[0x08]: PRESET_ROW      0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
CRTC[0x09]: MAX_SCAN        0x4F 0x4F 0x4F 0x4F 0xC1 0xC1 0xC1 0xC0 0xC0 0x40 0x40 0x41
CRTC[0x0A]: CURSOR_START    0x0D 0x0D 0x0D 0x0D 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
CRTC[0x0B]: CURSOR_END      0x0E 0x0E 0x0E 0x0E 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
CRTC[0x0C]: START_ADDR_HI   0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
CRTC[0x0D]: START_ADDR_LO   0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
CRTC[0x0E]: CURSOR_ADDR_HI  0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x00
CRTC[0x0F]: CURSOR_ADDR_LO  0x19 0x19 0x41 0x41 0x19 0x19 0x41 0x19 0x41 0x41 0xE1 0xA2
CRTC[0x10]: VRETRACE_START  0x9C 0x9C 0x9C 0x9C 0x9C 0x9C 0x9C 0x9C 0x9C 0x83 0xEA 0x9C
CRTC[0x11]: VRETRACE_END    0x8E 0x8E 0x8E 0x8E 0x8E 0x8E 0x8E 0x8E 0x8E 0x85 0x8C 0x8E
CRTC[0x12]: VDISP_END       0x8F 0x8F 0x8F 0x8F 0x8F 0x8F 0x8F 0x8F 0x8F 0x5D 0xDF 0x8F
CRTC[0x13]: OFFSET          0x14 0x14 0x28 0x28 0x14 0x14 0x28 0x14 0x28 0x28 0x28 0x28
CRTC[0x14]: UNDERLINE       0x1F 0x1F 0x1F 0x1F 0x00 0x00 0x00 0x00 0x00 0x0F 0x00 0x40
CRTC[0x15]: VBLANK_START    0x96 0x96 0x96 0x96 0x96 0x96 0x96 0x96 0x96 0x63 0xE7 0x96
CRTC[0x16]: VBLANK_END      0xB9 0xB9 0xB9 0xB9 0xB9 0xB9 0xB9 0xB9 0xB9 0xBA 0x04 0xB9
CRTC[0x17]: MODE_CTRL       0xA3 0xA3 0xA3 0xA3 0xA2 0xA2 0xC2 0xE3 0xE3 0xE3 0xE3 0xA3
CRTC[0x18]: LINE_COMPARE    0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF
 GRC[0x00]: SRESET          0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
 GRC[0x01]: ESRESET         0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
 GRC[0x02]: COLORCMP        0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
 GRC[0x03]: DATAROT         0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
 GRC[0x04]: READMAP         0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
 GRC[0x05]: MODE            0x10 0x10 0x10 0x10 0x30 0x30 0x00 0x00 0x00 0x00 0x00 0x40
 GRC[0x06]: MISC            0x0E 0x0E 0x0E 0x0E 0x0F 0x0F 0x0D 0x05 0x05 0x05 0x05 0x05
 GRC[0x07]: COLORDC         0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x0F 0x0F 0x0F 0x0F 0x0F
 GRC[0x08]: BITMASK         0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF
 SEQ[0x00]: RESET           0x03 0x03 0x03 0x03 0x03 0x03 0x03 0x03 0x03 0x03 0x03 0x03
 SEQ[0x01]: CLOCKING        0x08 0x08 0x00 0x00 0x09 0x09 0x01 0x09 0x01 0x01 0x01 0x01
 SEQ[0x02]: MAPMASK         0x03 0x03 0x03 0x03 0x03 0x03 0x01 0x0F 0x0F 0x0F 0x0F 0x0F
 SEQ[0x03]: CHARMAP         0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
 SEQ[0x04]: MEMMODE         0x03 0x03 0x03 0x03 0x02 0x02 0x06 0x06 0x06 0x06 0x06 0x0E
 ATC[0x00]: PAL00           0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
 ATC[0x01]: PAL01           0x01 0x01 0x01 0x01 0x13 0x13 0x17 0x01 0x01 0x01 0x01 0x01
 ATC[0x02]: PAL02           0x02 0x02 0x02 0x02 0x15 0x15 0x17 0x02 0x02 0x02 0x02 0x02
 ATC[0x03]: PAL03           0x03 0x03 0x03 0x03 0x17 0x17 0x17 0x03 0x03 0x03 0x03 0x03
 ATC[0x04]: PAL04           0x04 0x04 0x04 0x04 0x02 0x02 0x17 0x04 0x04 0x04 0x04 0x04
 ATC[0x05]: PAL05           0x05 0x05 0x05 0x05 0x04 0x04 0x17 0x05 0x05 0x05 0x05 0x05
 ATC[0x06]: PAL06           0x14 0x14 0x14 0x14 0x06 0x06 0x17 0x06 0x06 0x14 0x14 0x06
 ATC[0x07]: PAL07           0x07 0x07 0x07 0x07 0x07 0x07 0x17 0x07 0x07 0x07 0x07 0x07
 ATC[0x08]: PAL08           0x38 0x38 0x38 0x38 0x10 0x10 0x17 0x10 0x10 0x38 0x38 0x08
 ATC[0x09]: PAL09           0x39 0x39 0x39 0x39 0x11 0x11 0x17 0x11 0x11 0x39 0x39 0x09
 ATC[0x0A]: PAL0A           0x3A 0x3A 0x3A 0x3A 0x12 0x12 0x17 0x12 0x12 0x3A 0x3A 0x0A
 ATC[0x0B]: PAL0B           0x3B 0x3B 0x3B 0x3B 0x13 0x13 0x17 0x13 0x13 0x3B 0x3B 0x0B
 ATC[0x0C]: PAL0C           0x3C 0x3C 0x3C 0x3C 0x14 0x14 0x17 0x14 0x14 0x3C 0x3C 0x0C
 ATC[0x0D]: PAL0D           0x3D 0x3D 0x3D 0x3D 0x15 0x15 0x17 0x15 0x15 0x3D 0x3D 0x0D
 ATC[0x0E]: PAL0E           0x3E 0x3E 0x3E 0x3E 0x16 0x16 0x17 0x16 0x16 0x3E 0x3E 0x0E
 ATC[0x0F]: PAL0F           0x3F 0x3F 0x3F 0x3F 0x17 0x17 0x17 0x17 0x17 0x3F 0x3F 0x0F
 ATC[0x10]: MODE            0x0C 0x0C 0x0C 0x0C 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x41
 ATC[0x11]: OVERSCAN        0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
 ATC[0x12]: PLANES          0x0F 0x0F 0x0F 0x0F 0x03 0x03 0x01 0x0F 0x0F 0x0F 0x0F 0x0F
 ATC[0x13]: HPAN            0x08 0x08 0x08 0x08 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

In addition, I’ve created a VGA Tests directory to hold VGA test and sample code that PCjs can now successfully run (for the most part). See that directory for more details.

@jeffpar
June 1, 2015 (Updated July 9, 2015)