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:
- Input the value of the Address Register and save it for step 6
- Output the index of the desired Data Register to the Address Register.
- Read the value of the Data Register and save it for later restoration upon
termination, if needed.
- If writing, modify the value read in step 3, making sure to mask off bits
not being modified.
- If writing, write the new value from step 4 to the Data register.
- 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:
- Input a value from the Input Status #1 Register (normally port 3DAh) and
discard it.
- Read the value of the Address/Data Register and save it for step 7.
- Output the index of the desired Data Register to the Address/Data Register
- Read the value of the Data Register and save it for later restoration upon
termination, if needed.
- If writing, modify the value read in step 4, making sure to mask off bits
not being modified.
- If writing, write the new value from step 5 to the Address/Data register.
- Write the value of Address register saved in step 1 to the Address/Data
Register.
- 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:
- Input a value from the Input Status #1 Register (normally port 3DAh) and
discard it.
- 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.
- Output an address value to the Attribute Address/Data register.
- 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.
- Input a value from the Input Status #1 Register (normally port 3DAh) and
discard it.
- 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:
- Read the DAC State Register and save the value for use in step 8.
- Read the PEL Address Write Mode Register for use in step 8.
- Output the value of the first color entry to be read to the PEL Address
Read Mode Register.
- Read the PEL Data Register to obtain the red component value.
- Read the PEL Data Register to obtain the green component value.
- Read the PEL Data Register to obtain the blue component value.
- If more colors are to be read, repeat steps 4-6.
- 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.