Introduction
This section is intended
to give one a general background in low-level programming, specifically
related to video programming. It assumes that you already know how to program
in your intended programming environment, and answers questions such as:
Why write hardware-level code?
One reason for writing hardware-level
code is to develop drivers for operating systems or applications. Another
reason is when existing drivers do not provide the required performance
or capabilities for your application, such as for programming games or
multimedia. Finally, the most important reason is enjoyment. There is a
whole programming scene dedicated to producing "demos" of what the VGA/SVGA
can do.
Do I need to know assembly language?
No, but it helps. Assembly
language is the basis all CPU operations. All functions of the processor
and computer are potentially accessible from assembly. With crafty use
of assembly language, one can write software that exceeds greatly the potential
of higher level languages. However, any programming environment that provides
direct access to I/O and memory will work.
What are hex and binary numbers?
Humans use a number system
using 10 different digits (0-9), probably because that is the number of
fingers we have. Each digit represents part of a number with the rightmost
digit being ones, the second to right being tens, then hundreds, thousands
and so on. These represent the powers of 10 and is called "base 10" or
"decimal."
Computers are digital (and
don't usually have fingers) and use two states, either on or off to represent
numbers. The off state is represented by 0 and the on state is represented
by 1. Each digit (called a bit, short for Binary digIT) here represents
a power of 2, such as ones, twos, fours, and doubling each subsequent digit.
Thus this number system is called "base 2" or "binary."
Computer researchers realized
that binary numbers are unwieldy for humans to deal with; for example,
a 32-bit number would be represented in binary as 11101101010110100100010010001101.
Converting decimal to binary or vice versa requires multiplication or division,
something early computers performed very slowly, and researchers instead
created a system where the numbers were broken into groups of 3 (such as
11 101 101 010 110 100 100 010 010 001 101) and assigned a number from
0-7 based on the triplet's decimal value. This number system is called
"base 8" or "octal."
Computers deal with numbers
in groups of bits usually a length that is a power of 2, for example, four
bits is called a "nibble", eight bits is called a "byte", 16 bits is a
"word", and 32 bits is a "double word." These powers of two are not equally
divisible by 3, so as you see in the divided example a "double word" is
represented by 10 complete octal digits plus two-thirds of an octal digit.
It was then realized that by grouping bits into groups of four, a byte
could be accurately represented by two digits. Because a group of four
bits can represent 16 decimal numbers, they could not be represented by
simply using 0-9, so they simply created more digits, or rather re-used
the letters A-F (or a-f) to represent 10-15. So for example the rightmost
digits of our example binary number is 1101, which translates to 13 decimal
or D in this system, which is called "base 16" or "hexadecimal."
Computers nowadays usually
speak decimal (multiplication and division is much faster now) but when
it comes to low-level and hardware stuff, hexadecimal or binary is usually
used instead. Programming environments require you to explicitly specify
the base a number is in when overriding the default decimal. In most assembler
packages this is accomplished by appending "h" for hex and "b" for binary
to end of the number, such as 2A1Eh or 011001b. In addition, if the hex
value starts with a letter, you have to add a "0" to the beginning of the
number to allow it to distinguish it from a label or identifier, such as
0BABEh. In C and many other languages the standard is to append "0x" to
the beginning of the hex number and to append "%" to the beginning of a
binary number. Consult your programming environment's documentation for
how specifically to do this.
Historical Note: Another
possible explanation for the popularity of the octal system is that early
computers used 12 bit addressing instead of the 16 or 32 bit addressing
currently used by modern processors. Four octal digits conveniently covers
this range exactly, thus the historical architecture of early computers
may have been the cause for octal's popularity.
What are the numerical conventions used
in this reference?
Decimal is used often
in this reference, as it is conventional to specify certain details about
the VGA's operation in decimal. Decimal numbers have no letter after them.
An example you might see would be a video mode described as 640x480z256.
This means that the video mode has 640 pixels across by 480 pixels down
with 256 possible simultaneous colors. Similarly an 80x25 text mode means
that the text mode has 80 characters wide (columns) by 25 characters high
(rows.) Binary is frequently used to specify fields within a particular
register and also for bitmap patterns, and has a trailing letter b (such
as 10011100b) to distinguish it from a decimal number containing only 0's
and 1's. Octal you are not likely to encounter in this reference, but if
used has a trailing letter o (such as 145o) to distinguish it from a decimal.
Hexadecimal is always used for addressing, such as when describing I/O
ports, memory offsets, or indexes. It is also often used in fields longer
than 3 bits, although in some cases it is conventional to utilize decimal
instead (for example in a hypothetical screen-width field.)
Note: Decimal numbers in
the range 0-1 are also binary digits, and if only a single digit is present,
the decimal and binary numbers are equivalent. Similarly, for octal the
a single digit between 0-7 is equivalent to the decimal numbers in the
same range. With hexadecimal, the single-digit numbers 0-9 are equivalent
to decimal numbers 0-9. Under these circumstances, the number is often
given as decimal where another format would be conventional, as the number
is equivalent to the decimal value.
How do memory and I/O ports work?
80x86 machines have both
a memory address space and an input/output (I/O) address space. Most of
the memory is provided as system RAM on the motherboard and most of the
I/O devices are provided by cards (although the motherboard does provide
quite a bit of I/O capability, varying on the motherboard design.) Also
some cards also provide memory. The VGA and SVGA display adapters provide
memory in the form of video memory, and they also handle I/O addresses
for controlling the display, so you must learn to deal with both. An adapter
card could perform all of its functions using solely memory or I/O (and
some do), but I/O is usually used because the decoding circuitry is simpler
and memory is used when higher performance is required.
The original PC design was
based upon the capabilities of the 8086/8088, which allowed for only 1
MB of memory, of which a small range (64K) was allotted for graphics memory.
Designers of high-resolution video cards needed to put more than 64K of
memory on their video adapters to support higher resolution modes, and
used a concept called "banking" which made the 64K available to the processor
into a "window" which shows a 64K chunk of video memory at once. Later
designs used multiple banks and other techniques to simplify programming.
Since modern 32-bit processors have 4 gigabytes of address space, some
designers allow you to map all of the video memory into a "linear frame
buffer" allowing access to the entire video memory at once without having
to change the current window pointer (which can be time consuming.) while
still providing support for the windowed method.
Memory can be accessed most
flexibly as it can be the source and/or target of almost every machine
language instruction the CPU is capable of executing, as opposed to a very
limited set of I/O instructions. I/O space is divided into 65536 addresses
in the range 0-65535. Most I/O devices are configured to use a limited
set of addresses that cannot conflict with another device. The primary
instructions for accessing I/O are the assembly instructions "IN" and "OUT",
simply enough. Most programming environments provide similarly named instructions,
functions, or procedures for accessing these.
Memory can be a bit confusing
though, because the CPU has two memory addressing modes, Real mode and
Protected mode. Real mode was the only method available on the 8086 and
is still the primary addressing mode used in DOS. Unfortunately, real mode
only provides access to the first 1 MB of memory. Protected mode is used
on the 80286 and up to allow access to more memory. (There are also other
details such as protection, virtual memory, and other aspects not particularly
applicable to this discussion.) Memory is accessed by the 80x86 processors
using segments and offsets. Segments tell the memory management unit where
in memory is located, and the offset is the displacement from that address.
In real mode offsets are limited to 64K, because of the 16-bit nature of
the 8086. In protected mode, segments can be any size up to the full address
capability of the machine. Segments are accessed via special segment registers
in the processor. In real mode, the segment address is shifted left four
bits and added to the offset, allowing for a 20 bit address (20 bits =
1 MB); in protected mode segments are offsets into a table in memory which
tells where the segment is located. Your particular programming environment
may create code for real and/or protected mode, and it is important to
know which mode is being used. An added difficulty is the fact that protected
mode provides for I/O and memory protection (hence protected mode), in
order to allow multiple programs to share one processor and prevent them
from corrupting other processes. This means that you may need to interact
with the operating system to gain rights to access the hardware directly.
If you write your own protected mode handler for DOS or are using a DOS
extender, then this should be simple, but it is much more complicated under
multi-tasking operating systems such as Windows or Linux.
How do I access these from my programming environment?
That is a very important
question, one that is very difficult to answer without knowing all of the
details of your programming environment. The documentation that accompanies
your particular development environment is best place to look for this
information, in particular the compiler, operating system, and/or the chip
specifications for the platform.
Details for some common programming environments are given in: