Home Intro Advice Fudge Paranoia External Indexed Attribute Color Binary Example Masking Back 
Hardware Level VGA and SVGA Video Programming Information Page
Accessing the VGA Registers 
Introduction
        This section discusses methods of manipulating the particular registers present in VGA hardware. Depending upon which register one is accessing, the method of accessing them is different and sometimes difficult to understand. The VGA has many more registers than it has I/O ports, thus it must provide a way to re-use or multiplex many registers onto a relatively small number of ports. All of the VGA ports are accessed by inputting and outputting bytes to I/O ports; however, in many cases it is necessary to perform additional steps to ready the VGA adapter for reading and writing data. Port addresses are given at their hexadecimal address, such as 3C2h.

General Advice
        If a program takes control of the video card and changes its state, it is considered good programming practice to keep track of the original values of any register it changes such that upon termination (normal or abnormal) it can write them back to the hardware to restore the state. Anyone who has seen a graphics application abort in the middle of a graphics screen knows how annoying this can be. Almost all of the VGA registers can be saved and restored in this fashion. In addition when changing only a particular field of a register, the value of the register should be read and the byte should be masked so that only the field one is trying to change is actually changed.

I/O Fudge Factor
        Often a hardware device is not capable handling I/O accesses as fast as the processor can issue them. In this case, a program must provide adequate delay between I/O accesses to the same device. While many modern chipsets provide this delay in hardware, there are still many implementations in existence that do not provide this delay. If you are attempting to write programs for the largest possible variety of hardware configurations, then it is necessary to know the amount of delay necessary. Unfortunately, this delay is not often specified, and varies from one VGA implementation to another. In the interest of performance it is ideal to keep this delay to the minimum necessary. In the interest of compatibility it is necessary to implement a delay independent of clock speed. (Faster processors are continuously being developed, and also a user may change clock speed dynamically via the Turbo button on their case.)

Paranoia
        If one wishes to be extra cautious when writing to registers, after writing to a register one can read the value back and compare it with the original value. If they differ it may mean that the VGA hardware has a stuck bit in one its registers, that you are attempting to modify a locked or unsupported register, or that you are not providing enough delay between I/O accesses. As long as reading the register twice doesn't have any unintended side effects, when reading a registers value, one can read the register twice and compare the values read, after masking out any fields that may change without CPU intervention. If the values read back are different it may mean that you are not providing enough delay between I/O accesses, that the hardware is malfunctioning, or are reading the wrong register or field. Other problems that these techniques can address are noise on the I/O bus due to faulty hardware, dirty contacts, or even sunspots! When perform I/O operations and these checks fail, try repeating the operation, possibly with increased I/O delay time. By providing extra robustness, I have found that my own programs will work properly on hardware that causes less robust programs to fail.

Accessing the External Registers
        The external registers are the easiest to program, because they each have their own separate I/O address. Reading and writing to them is as simple as inputting and outputting bytes to their respective port address. Note, however some, such as the Miscellaneous Output Register is written at port 3C2h, but is read at port 3CCh. The reason for this is for backwards compatibility with the EGA and previous adapters. Many registers in the EGA were write only, and thus the designers placed read-only registers at the same location as write-only ones. However, the biggest complaint programmers had with the EGA was the inability to read the EGA's video state and thus in the design of the VGA most of these write-only registers were changed to read/write registers. However, for backwards compatibility, the read-only register had to remain at 3C2h, so they used a different port.

Accessing the Sequencer, Graphics, and CRT Controller Registers
        These registers are accessed in an indexed fashion. Each of the three have two unique read/write ports assigned to them. The first port is the Address Register for the group. The other is the Data Register for the group. By writing a byte to the Address Register equal to the index of the particular sub-register you wish to access, one can address the data pointed to by that index by reading and writing the Data Register. The current value of the index can be read by reading the Address Register. It is best to save this value and restore it after writing data, particularly so in an interrupt routine because the interrupted process may be in the middle of writing to the same register when the interrupt occurred. To read and write a data register in one of these register groups perform the following procedure:

  1. Input the value of the Address Register and save it for step 6
  2. Output the index of the desired Data Register to the Address Register.
  3. Read the value of the Data Register and save it for later restoration upon termination, if needed.
  4. If writing, modify the value read in step 3, making sure to mask off bits not being modified.
  5. If writing, write the new value from step 4 to the Data register.
  6. Write the value of Address register saved in step 1 to the Address Register.
        If you are paranoid, then you might want to read back and compare the bytes written in step 2, 5, and 6 as in the Paranoia section above. Note that certain CRTC registers can be protected from read or write access for compatibility with programs written prior to the VGA's existence. This protection is controlled via the Enable Vertical Retrace Access and CRTC Registers Protect Enable fields. Ensuring that access is not prevented even if your card does not normally protect these registers makes your

Accessing the Attribute Registers
         The attribute registers are also accessed in an indexed fashion, albeit in a more confusing way. The address register is read and written via port 3C0h. The data register is written to port 3C0h and read from port 3C1h. The index and the data are written to the same port, one after another. A flip-flop inside the card keeps track of whether the next write will be handled is an index or data. Because there is no standard method of determining the state of this flip-flop, the ability to reset the flip-flop such that the next write will be handled as an index is provided. This is accomplished by reading the Input Status #1 Register (normally port 3DAh) (the data received is not important.) This can cause problems with interrupts because there is no standard way to find out what the state of the flip-flop is; therefore interrupt routines require special card when reading this register. (Especially since the Input Status #1 Register's purpose is to determine whether a horizontal or vertical retrace is in progress, something likely to be read by an interrupt routine that deals with the display.) If an interrupt were to read 3DAh in the middle of writing to an address/data pair, then the flip-flop would be reset and the data would be written to the address register instead. Any further writes would also be handled incorrectly and thus major corruption of the registers could occur. To read and write an data register in the attribute register group, perform the following procedure:

  1. Input a value from the Input Status #1 Register (normally port 3DAh) and discard it.
  2. Read the value of the Address/Data Register and save it for step 7.
  3. Output the index of the desired Data Register to the Address/Data Register
  4. Read the value of the Data Register and save it for later restoration upon termination, if needed.
  5. If writing, modify the value read in step 4, making sure to mask off bits not being modified.
  6. If writing, write the new value from step 5 to the Address/Data register.
  7. Write the value of Address register saved in step 1 to the Address/Data Register.
  8. If you wish to leave the register waiting for an index, input a value from the Input Status #1 Register (normally port 3DAh) and discard it.
        If you have control over interrupts, then you can disable interrupts while in the middle of writing to the register. If not, then you may be able to implement a critical section where you use a byte in memory as a flag whether it is safe to modify the attribute registers and have your interrupt routine honor this. And again, it pays to be paranoid. Resetting the flip-flop even though it should be in the reset state already helps prevent catastrophic problems. Also, you might want to read back and compare the bytes written in step 3, 6, and 7 as in the Paranoia section above.
        On the IBM VGA implementation, an undocumented register (CRTC Index=24h, bit 7) can be read to determine the status of the flip-flop (0=address,1=data) and many VGA compatible chipsets duplicate this behavior, but it is not guaranteed. However, it is a simple matter to determine if this is the case. Also, some SVGA chipsets provide the ability to access the attribute registers in the same fashion as the CRT, Sequencer, and Graphics controllers. Because this functionality is vendor specific it is really only useful when programming for that particular chipset. To determine if this undocumented bit is supported, perform the following procedure:
  1. Input a value from the Input Status #1 Register (normally port 3DAh) and discard it.
  2. Verify that the flip-flop status bit (CRTC Index 24, bit 7) is 0. If bit=1 then feature is not supported, else continue to step 3.
  3. Output an address value to the Attribute Address/Data register.
  4. Verify that the flip-flop status bit (CRTC Index 24, bit 7) is 1. If bit=0 then feature is not supported, else continue to step 5.
  5. Input a value from the Input Status #1 Register (normally port 3DAh) and discard it.
  6. Verify that the flip-flop status bit (CRTC Index 24, bit 7) is 0. If bit=1 then feature is not supported, else feature is supported.
Accessing the Color Registers
     The color registers require an altogether different technique; this is because the 256-color palette requires 3 bytes to store 18-bit color values. In addition the hardware supports the capability to load all or portions of the palette rapidly. To write to the palette, first you must output the value of the palette entry to the PEL Address Write Mode Register (port 3C8h.) Then you should output the component values to the PEL Data Register (port 3C9h), in the order red, green, then blue. The PEL Address Write Mode Register will then automatically increment, allowing the component values of the palette entry to be written to the PEL Data Register. Reading is performed similarly, except that the PEL Address Read Mode Register (port 3C7h) is used to specify the palette entry to be read, and the values are read from the PEL Data Register. Again, the PEL Address Read Mode Register auto-increments after each triplet is written. The current index for the current operation can be read from the PEL Address Write Mode Register. Reading port 3C7h gives the DAC State Register, which specifies whether a read operation or a write operation is in effect. As in the attribute registers, there is guaranteed way for an interrupt routine to access the color registers and return the color registers to the state they were in prior to access without some communication between the ISR and the main program. For some workarounds see the Accessing the Attribute Registers section above. To read the color registers:
  1. Read the DAC State Register and save the value for use in step 8.
  2. Read the PEL Address Write Mode Register for use in step 8.
  3. Output the value of the first color entry to be read to the PEL Address Read Mode Register.
  4. Read the PEL Data Register to obtain the red component value.
  5. Read the PEL Data Register to obtain the green component value.
  6. Read the PEL Data Register to obtain the blue component value.
  7. If more colors are to be read, repeat steps 4-6.
  8. Based upon the DAC State from step 1, write the value saved in step 2 to either the PEL Address Write Mode Register or the PEL Address Read Mode Register.
Note: Steps 1, 2, and 8 are hopelessly optimistic. This in no way guarantees that the state is preserved, and with some DAC implementations this may actually guarantee that the state is never preserved. See the DAC Operation page for more details.

Binary Operations
        In order to better understand dealing with bit fields it is necessary to know a little bit about logical operations such as logical-and (AND), logical-or (OR), and exclusive-or(XOR.) These operations are performed on a bit by bit basis using the truth tables below. All of these operations are commutative, i.e. A OR B = B OR A, so you look up one bit in the left column and the other in the top row and consult the intersecting row and column for the answer.
 

AND OR XOR
0 1 0 1 0 1
0 0 0 0 0 1 0 0 1
1 0 1 1 1 1 1 1 0

Example Register
        The following table is an example of one particular register, the Mode Register of the Graphics Register. Each number from 7-0 represents the bit position in the byte. Many registers contain more than one field, each of which performs a different function. This particular chart contains four fields, two of which are two bits in length. It also contains two bits which are not implemented (to the best of my knowledge) by the standard VGA hardware.
 
Mode Register (Index 05h)
7 6 5 4 3 2 1 0
Shift Register Odd/Even RM Write Mode

Masking Bit-Fields
        Your development environment may provide some assistance in dealing with bit fields. Consult your documentation for this. In addition it can be performed using the logical operators AND, OR, and XOR (for details on these operators see the Binary Operations section above.) To change the value of the Shift Register field of the example register above, we would first mask out the bits we do not wish to change. This is accomplished by performing a logical AND of the value read from the register and a binary value in which all of the bits we wish to leave alone are set to 1, which would be 10011111b for our example. This leaves all of the bits except the Shift Register field alone and set the Shift Register field to zero. If this was our goal, then we would stop here and write the value back to the register. We then OR the value with a binary number in which the bits are shifted into position. To set this field to 10b we would OR the result of the AND with 01000000b. The resulting byte would then be written to the register. To set a bitfield to all ones the AND step is not necessary, similar to setting the bitfield to all zeros using AND. To toggle a bitfield you can XOR a value with a byte with a ones in the positions to toggle. For example XORing the value read with 01100000b would toggle the value of the Shift Register bitfield. By using these techniques you can assure that you do not cause any unwanted "side-effects" when modifying registers.

Notice: All trademarks used or referred to on this page are the property of their respective owners.
All pages are Copyright 1997, 1998, J. D. Neal, except where noted. Permission for utilization and distribution is subject to the terms of the FreeVGA Project Copyright License.