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.