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: