Exkursion Hardware Programmierung eines IBM kompatiblen Personal Computer

Exemplarisch unter Fokussierung der Grafik Karte im kompatiblen VGA Modus

  • Frage: Wozu eigentlich noch Assembler
  • Antwort: Geschwindigkeitsaspekt, uneingeschränkter Zugriff auf die Hardware
  • Diese Vorlesung fokussiert beides, einmal Hardwareprogrammierung und des weiteren optimierte Routinen


Struktur dieses Dokumentes
  • 1. Assembler oder wie finde ich den optimalen Weg

  • 2. Aufbau und Struktur des Rechners nach dem Start Up des Rechners und vor dem Booten eines Betriebssystems
  •     - BIOS
  •     - Boot Vorgang
  •     - Real Mode
  •     - Speicherorganisation unter DOS (Interrupt Vektor Tabelle / Statusvariablen vom BIOS und DOS)

  • 3. Allgemeine Kommunikationsmöglichkeiten mit Hardware
  •     - Interrupts (Hardware / Software)
  •     - Port Programmierung
  •     - DMA Controller

  • 4. Kurzer Einstieg; Aufbau einer Standard VGA Grafik Karte
  •     - Grafik Speicher
  •     - RAM DAC und CRTC
  •     - Grafikkarten BIOS

  • 5. Beispielhafte Hardwareprogrammierung einer Grafikkarte


1. Assembler oder wie finde ich den optimalen Weg

Die große Diskussion, mit der man sich heutzutage als Assembler Programmierer herumschlagen muß, ist, welche Existenzberechtigung die Assembler Programmierung denn eigentlich noch hat. Die Computer werden von ihrer Rechenleistung immer mächtiger und leistungsfähiger, warum dann noch Assembler...

Als einer der großen Argumente zählte zu früheren Zeiten immer der Geschwindigkeitsaspekt und die geringe Platzgröße des Programmcodes. Es sollte logisch erscheinen, daß insofern eine Person den Einstieg in Assembler gefunden hat und sie sich mit ihrer System Umgebung auskennt, dies die Sprache ist, welche aus Maschinensicht den kürzesten und optimalsten Weg zur Lösung einer Problemstellung bietet.

Eine Frage an dieser Stelle könnte sich auftun, warum Assembler im Vergleich zu Hochsprachen schneller ist, da es teilweise doch bekannt ist, daß Code einer übergeordneten Programmiersprache ja auch letztendlich nach Assembler übersetzt wird. Der Aspekt liegt einfach in der Optimierung, dazu ein kleines Beispiel, welches zwei Variablen auf null setzt:
HLL Code:                               generierter Assembler Code:
var1:=0;                                xor ax,ax
var2:=0;                                mov var1,ax
                                        xor ax,ax
                                        mov var2,ax
Aus Assembler Sicht sieht man an dieser Stelle sofort das überflüssige zweite Nullsetzen des AX Registers und würde sich dies in seinem Code sparen, welches natürlich Auswirkung auf Größe sowie Geschwindigkeit des Programmes hätte.

Viele Compiler arbeiten schon seit etwas längerem mit Optimierung und generieren insofern Code, der in seiner Ausführungsgeschwindigkeit zwar gute Performance auf heutigen schnellen Rechnern liefert, allerdings hat diese Sichtweise des Programmierens zum großen Teil auch erst zu den heutigen Entwicklungen im Hardware Sektor geführt. Die Logik der Compiler bei der Übersetzung einer Hochsprache in die Maschinensprache ist insofern eingeschränkt, als das sie nimmer die Effizienz einer Optimierung aus Menschenhand erkennen und erfahren wird.

Es soll noch einige, wenige Puristen bzw. Fanatiker geben, die auch noch heutzutage darauf schwören, ihre Programme komplett in Assembler zu verfassen. Man sollte an dieser Stelle allerdings auch ein ausgewogenes Gleichgewicht zwischen Effizienz und Nutzen finden. Sicherlich wird heutzutage niemand darüber streiten, daß der Einsatz von Assembler zur Geschwindigkeitsoptimierung Sinn macht, aber gleich deswegen den ganzen Code darin verfassen. Einer der großen Vorteile von Hochsprachen ist der, als daß sie meistens Bibliotheken mitliefern, welche schon auf die Systemumgebung zugeschnitten sind. Assembler greift da sozusagen an einer etwas tieferen Stelle an, als einzige direkte Programmiersprache zur Hardware gesehen, muß hier das Rad des öfteren neu erfunden werden, um seine Assembler Programme in die Systemumgebung zu integrieren.

Wenn man sich ein Programm genauer unter die Lupe nimmt, sozusagen ein kleines Profiling auf sein Programm anwendet, so werden immer gewisse Merkmale auffällig. Programme sind von der Struktur nämlich nicht so aufgebaut, als daß jede Stelle im Code immer zu gleichen, gewichtigen Teilen durchgelaufen wird, sondern es lassen sich viel eher Stellen heraus filtern, die aus Sicht der Programmausführung nur einige, wenige Male angesprungen werden und es lassen sich auch die sogenannten inneren Schleifen finden, auf denen zu einem sehr großen Prozentsatz die Ausführungslast liegt.

Meistens spiegelt sich das gerade angesprochene in dem E-V-A Diagramm wieder, eine Eingaberoutine, welche nur einmalig zur Eingabe an Benutzerdaten durchlaufen wird, braucht nur in den seltensten Fällen optimiert zu werden. Bei der Passage der Verarbeitung der Daten wird es auffälliger, hier befinden sich des öfteren nicht all zu wenige Schleifen, die Daten verarbeiten und Code Stellen, welche immer wieder durchlaufen werden, dies ist sicherlich ein geeigneter Ort zur effizienten Optimierung. Später stolpert man noch über die Ausgabe, die auch im großem Prozentsatz eher zu vernachlässigen ist.

Ein weiteres Beispiel zur Untermauerung dieser Theorie liefern die heutigen Betriebssysteme selber. Der größte Anteil an Programmiercode eines Betriebssystems ist in einer Hochsprache verfaßt. Wenn man sich zum Beispiel den Programmiercode des Linux Kernels anschaut, welcher ja in den meisten Distributionen offen liegt, so kann man hier neben der verwendeten Hochsprache auch viele Stellen wiederfinden, welche komplett in Assembler verfaßt wurden. So gehört zum Beispiel zu jedem Multi Tasking Betriebssystem ein Task Manager, der die einzelnen Prozesse untereinander verwaltet. Dieser Task Manager wird zu hauf innerhalb kürzester Zeit aufgerufen, um die Ausführung von einem Prozess zum nächsten zu verlagern, an dieser Stelle macht Assembler im Sinne der Geschwindigkeitsoptimierung durchaus Sinn.

Wenn an dieser Stelle nicht optimalst programmiert werden würde, würde sich hier Zeit verlieren, die in der Summe die Performance des Betriebsystems rapide senken lassen würde. Gleiches Bild auch bei Schedulern, die nicht nur alle paar Minuten oder Stunden aufgerufen werden, es gibt auch eigene Tasks im Betriebssystemen, die alle Millisekunden durchgeführt werden müssen. Wenn man nun versucht, sich dies als Schleife vorzustellen, so kann man schon erkennen, das Aufrufe aller Millisekunden ziemlich häufig sind und sich diese im Sinne der aufsummierten Ausführungszeit auf die gesamte Performance negativ auswirken.

Weitere Argumente zu Assembler

Weiter oben läßt sich die Formulierung wiederfinden: "wer sich ein wenig mit Assembler und seinem System auskennt", der, so könnte man sagen, lernt schnell Strukturen und Systematiken kennen, wie ein Computer auf Hardware Ebene abläuft und kommuniziert. Der findet aus Programmierer Sicht Praktiken, sich in Systematiken und Strukturen wieder spiegelnd, die sich, insofern einmal erkannt und aufgenommen, auf fast alle High Level orientierten Sprachen anwenden lassen. Tatsächlich soll hier die Behauptung aufgestellt werden, das im übertragenem Sinne Assembler wie das Latein der Fremdsprachen ist, sozusagen als Ursprung und damit Mutter aller Programmiersprachen anzusehen sei.

Noch ein anderer Aspekt ist der, daß Hochsprachen den Programmierer, zumindest in heutigen 32Bit Systemumgebungen nicht an die Hardware und derer Programmierung heran lassen. Da macht es leider auch wenig Sinn, mit Assembler ein 32Bit Programm zu schreiben, um die Hardware zu erreichen, da dies vom System her geblockt werden würde. An dieser Stelle müßte man sich immense Gedanken über Treiber Programmierung machen, mit dem man dann über eine Schnittstelle von seiner Hochsprache aus kommunizieren kann. Viel interessanter ist allerdings bei den meisten Betriebsystemen die Möglichkeit, eine gesicherte Umgebung zu starten, welche Zugriff auf (nur) 16Bit Programme ermöglicht. In dieser Umgebung besteht die Möglichkeit, Hardware anzusprechen und dementsprechend auch die nötigen Befehle dazu, welche nicht in Kollision mit dem Nutzen vom Betriebssystem selber stehen.

2. Aufbau und Struktur des Rechners nach dem Start Up des Rechners und vor dem Booten eines Betriebssystems

Wie sieht das eigentlich bei einem AT System aus, was passiert nach dem Einschalten, wie läuft so ein System an, wo sind die Punkte, an denen wir ansetzen können...:

Beim Start des Rechners liegt die Kontrolle über das System beim BIOS.
-> Basic Input Output System

Das BIOS durchläuft zuerst das POST -> Power On Self Test, es werden alle im BIOS angemeldeten Geräte sozusagen auf Funktionalität geprüft, der Speichertest, Floppy Laufwerke, Initialisierung der angemeldeten Festplatten, Abgabe der Systemsteuerung an eingebaute Erweiterungskarten wie z.B. SCSI Controller mit nachfolgender Rückkehr zum BIOS u.s.w.

Danach wird je nach Koordination des BIOS gebootet. Der Boot Sektor eines Wechselmediums, angemeldet im BIOS, oder eines festen Datenträgers, ebenfalls über das BIOS identifiziert, in den Speicher geladen und gestartet. Dies ist der Punkt, wo die Betriebssysteme ansetzen und die Steuerung an ihrerseits abgegeben wird.

Ein AT System befindet sich zu diesem Zeitpunkt im REAL Modus, das heißt, daß das System im 16Bit Modus von den Registern und der Speicher Adressierung her gefahren wird. Die Speicheradressierung läuft bekannt von Assembler 1 in Form von Segmentierung und Offset Adressierung ab, die Vorzüge eines 32Bit Systems, d.h. zum Beispiel Zugriff auf die 32Bit Register der CPU oder Ansprechen des Speichers überhalb 1MB sind zu dieser Laufzeit nicht nutzbar, dafür muß erst in den Protected Mode umgeschaltet werden.

Die meisten heutigen Betriebssysteme nutzen ein Boot Loader Programm, um ihr System in den Speicher zu laden und in den 32Bit Betriebssystem Modus umzuschalten. Bei älteren Systemen, wie zum Beispiel das alt bekannte DOS werden nur Routinen in den Speicher geladen, um mit Datenträgern zu hantieren. Daher auch der Name Disk Operating System. DOS selber hält den Real Modus aufrecht und liefert Systemprogramme für allgemeine Peripherie und unter anderem zur Speicherverwaltung, um den erweiterten Speicher über 1MB zu adressieren, da dieser über die normale Adressierung aus Sicht von Segment und Offset nicht erreichbar ist.

Zur Kommunikation von Software mit den Hardware Eigenschaften des Rechners hinterlegt das BIOS noch vor Abgabe der Steuerung einige Informationen und Möglichkeiten im Arbeitsspeicher. Der Aufbau sieht unter anderem wie folgt aus:

Adresse 0000:0000h INTERRUPT VEKTOR TABLE:
Hier befindet sich eine Liste mit 255 Einträgen, die in Form von Pointern aufgebaut ist. Sie dient zur Ereignisbehandlung von Hardwarekomponenten und um Routinen zum Handling mit dem Betriebssystem und eventuellen Erweiterungen zur Verfügung zu stellen...

Adresse 0000:0400h BIOS DATA ARRAY:
256 Bytes zur Beschreibung an BIOS Konfigurationsdaten, wie zum Beispiel Konfiguration der parallelen sowie seriellen Schnittstellen und weiteres...

3. Allgemeine Kommunikationsmöglichkeiten mit Hardware

Zur Kommunikation des Systems mit eingebauten Erweiterungskarten bietet der Befehlssatz der CPU einige Möglichkeiten, die hier näher erläutert werden sollen:

Die Interrupts:

Das Interrupts, wie der Name schon sagt, Unterbrechungsebenen darstellen, sollte bekannt sein. An dieser Stelle wird im Detail allerdings noch zwischen Software und Hardware Interrupts unterschieden.

  • 1. Ein Software Interrupt wird von dem laufendem Programm ausgelöst und stellt nichts weiteres da, als eine Unterbrechung des Programmes zur Ausführung eines anderen Unterprogrammes, wie zum Beispiel die des Betriebssystems.
  • 2. Ein Hardware Interrupt kommt, wie der Name schon sagt, von der Hardware her und stellt eine Unterbrechung des laufenden Programmes dar, angefordert von der Hardware, zur Übermittlung eines Ereignisses ihrerseits, so löst zum Beispiel jeder Tastendruck auf der Tastatur einen Interrupt aus.
Das interessante an dieser Stelle ist, daß man sich über die Interrupt Vektor Tabelle selber einklinken kann.

  • 1. Dies bietet einmal aus der Sicht der Software Interrupts die Möglichkeit, selber Unterprogramme für andere Programme zur Verfügung zu stellen. Der Weg dahin ist etwas schwierig und läuft über das Stichwort TSR ab. Terminate & stay resistent bedeutet an dieser Stelle, daß das Programm seine gelieferten Schnittstellen in die Interrupt Vektor Tabelle einträgt und sich nach seiner Ausführung beendet. Allerdings hinterbleibt der Code selber zur Ausführung der Unterprogramme im Speicher.
  • 2. Man kann sich auch in die Hardware Interrupts einklinken, um zum Beispiel Tastendrücke abzufangen oder Mausbewegungen selber aufzuzeichnen. An dieser Stelle läuft der Weg, wie weiter oben schon angedeutet, insoweit anders ab, als das die Hardware ein Ereignis über einen programmierbaren Interrupt Controller auslöst, der seinerseits das System dazu veranlaßt eine Softwareroutine anzuspringen, eingetragen in der Interrupt Vektor Tabelle.
Sobald ein Interrupt von der Software bzw. von der Hardware ausgelöst wird, werden alle Register, die Flags sowie der Instruction Pointer auf dem Stack gesichert, um zu der Routine, identifiziert über ihren Vektor aus der Interrupt Tabelle zu springen. Diese Sicherung der Systemumgebung dient dazu, daß man nach Beendigung der Interrupt Routine den Einsprung in das eigentliche Programm ohne eine merkliche Änderung zurück findet. Zu beachten sei an dieser Stelle, das bei einem Hardwareinterrupt das Ende der Bearbeitung des Software Handlers an einem Controller, den programable Interrupt Controller, signalisiert werden muß.

In einem Standard AT System sind zwei PICs eingebaut, welche die Hardware Interrupts des Rechners steueren. Diese beiden PICs belegen die Interrupts von 0 bis 15, wobei der zweite PIC über den Interrupt 2 des ersten kaskadiert ist, für die Hardware ergibt sich daraus nachfolgende Tabelle:
IRQ 0                Interval Timer
IRQ 1                Tastatur Interrupt
IRQ 2                kaskadiert 2. Interrupt Ccontroller
IRQ 3                2. Serielle Schnittstelle
IRQ 4                1. Serielle Schnittstelle
IRQ 5                2. Paralleler Port
IRQ 6                Disketten Controller
IRQ 7                1. Paralleler Port
IRQ 8                Real Time Clock
IRQ 9                Software emulierter IRQ2
IRQ10                nicht benutzt
IRQ11                nicht benutzt
IRQ12                Maus oder andere Eingabeperipherie
IRQ13                Coprozessor Interrupt
IRQ14                Festplatten Controller
IRQ15                nicht benutzt
Interessant an dieser Stelle ist die Priorität eines Interrupts, also die "Behandlungsreihenfolge", da der zweite PIC über IRQ2 in den ersten eingebunden ist, sind alle Interrupts von 8 bis 15 ihrer Priorität her höher denn denen von 3 bis 7...

Eine Ausnahme stellen an dieser Stelle die NMIs dar, was für non maskable Interrupt steht. Diese lassen sich über die PICs nicht maskieren und werden auch nur von dem System ausgelöst, zum Beispiel vom BIOS oder wenn ein Parity Speicher Fehler auftritt.

Noch mehr zur Hardware, die Ports:

Interrupts sind jedoch nicht die einzige Möglichkeit, mit der Hardware zu kommunizieren. Des weiteren wird auch von vielerlei Hardware der Kommunikationsumfang über die sogenannten Ports erweitert bzw. genutzt. Ports sind nichts einfacheres, als kleine Schnittstellen zur Hardware, in denen man Byte oder auch Word große Daten lesen sowie schreiben kann. Aus dieser Sicht werden die Ports zur Statusübermittlung vorgezogen oder eingesetzt, um "einfache" Modien bzw. Zustände der Hardware zu ändern oder zu ermitteln. So zum Beispiel läßt sich das parallel Interface komplett über Ports steuern. Ports eingebauter Erweiterungskarten werden von dem BIOS in das System integriert und lassen sich ohne weiteren Verwaltungsaufwand über einfache CPU Befehle ansprechen.

Hardware, Hardware, Hardware, DMA Controller:

Der Vollständigkeit halber soll an dieser Stelle kurz noch der DMA Controller erwähnt werden, auf den wir allerdings im weiteren Verlauf dieser Ausarbeitung keinen Zugriff ausüben werden. Dieses Akronym steht für Direct Memory Access und dient dafür, um auf schnellem Wege eine große Menge an Daten zwischen Peripherie und Speicher auszutauschen. Das besondere an dem DMA Controller liegt in der Beschaffenheit, daß dieser die CPU nicht belastet und daher sozusagen parallel zum Programm selber läuft. In einem Standard AT System sind zwei DMA Controller eingebaut, jeweils für 8Bit bzw. 16 Bit Datentransfers. Beide Controller sind miteinander kaskadiert, jeder Controller stellt vier Kanäle zur Verfügung, die von der Hardware belegbar sind.

Kurze Zusammenfassung bis hierher:

Interrupts zur Unterbrechung der Programmausführung, um Ereignisse zu signalisieren oder bei Software Interrupts, um globale Unterprogramme aufzurufen...

Ports zur Übermittlung oder zum Abfragen an Status Bytes, die Programmausführung wird nicht explizit unterbrochen...

DMAs zum Bewegen großer Speicherblöcke zwischen Hauptspeicher und Peripherie und umgekehrt...

4. Kurzer Einstieg, Aufbau einer Standard VGA Grafik Karte

Mit dieser Ausarbeitung soll sich der Programmierung von Hardware genähert werden, um praxisbezogen letztendlich mit Hilfe von Assembler einmal exemplarisch eine Grafik Karte angesprochen und programmiert zu haben. Von daher möchte ich kurz auf den Aufbau einer Grafik Karte eingehen, um den Funktionsablauf darzustellen und Euch den Informationsfluß von digital hinterlegten Bildern im Speicher bis hin zur Ausgabe auf einen analogen Monitor vor Augen zu spiegeln.

Der Aufbau im Groben gesehen läßt sich schnell erklären. Die Bildinformationen sind digital im Speicher der Grafik Karte hinterlegt, wobei ein Eintrag im Grafikspeicher einen Farbwert widerspiegelt. Aus dem Speicher wird mit Hilfe des RAM DACs von digitale in analoge Signale umgesetzt, so daß die analogen Signale zur Darstellung vorbereitet wurden.

Etwas genauer betrachtet wird der Monitor selber vom CRTC (Cathod Ray Tube Controller) der Grafik Karte angesteuert und ist für die Generierung des Videosignals zuständig. Der Bildaufbau läuft über den Kathodenstrahl ab, der in der eingestellten Bildwiederholfrequenz je nach konfigurierter Grafik Auflösung mit seinem Strahl den Bildschirm Zeile für Zeile abtastet, um die einzelnen, jetzt analogen Grafik "Pixel" darzustellen. An dieser Stelle gibt es vom Kathodenstrahl zwei Rücklaufe. Zur Positionierung des Strahls am Anfang der nächsten Bildschirmzeile wird ein horizontaler Retrace ausgeführt, zur Positionierung des Strahles am Beginn des Bildes gibt es den vertikalen Retrace, der etwas mehr Zeit in Anspruch nimmt und auf den wir später noch genauer eingehen werden.

Ein wenig mehr Gedanken kann man sich über die Farbtiefe machen. Die Farbdarstellung verläuft von monochrom bis hin zu 32Bit Farbtiefe. Bei den ersten Farbtiefen von 1Bit bis hin zu 8Bit Farbdarstellung spiegelt ein Eintrag im Grafikspeicher eine Referenz auf eine Farbtabelle der Grafik Karte wieder. Dies entspricht dem Standard VGA Modus, der heutzutage eigentlich kaum mehr eine Anwendung findet. Die nächsten Farbtiefen belegen entweder 16Bit, 24Bit oder 32Bit und enthalten in ihrer Wertigkeit die direkten Farbeinträge.

Aus dem allgemeinen Umgang mit Grafiken sollte der Algorithmus zur Darstellung eines Farbwertes bekannt sein. Ein Farbwert wird aus den drei Komplementärfarben rot, grün und blau gemischt, woraus sich jegliche erdenkbare Farbe darstellen läßt. Die jeweilige Farbintensität ergibt sich aus dem zugeordneten Ordinalwert, welcher zumindest bis zur 16Bit Tiefe maximal eine Größe von 6Bit annehmen kann. Um so höher der Wert der Komplimentärfarbe, desto intensiver der ihrige Farbton. Mischt man alle drei Komplementärfarben im gleichen Verhältnis, so erhält man die Graufarbenabstufungen.

Kommunikation mit der Grafik Karte

Alle Grafik Karten sind heutzutage standardmäßig mit einem eigenen BIOS ausgerüstet. Über dieses BIOS werden Subroutinen zur Verfügung gestellt, um eventuelle Grafik oder Text Modien zu selektieren, einfache Ausgaben im Speicher der Grafik Karte vorzunehmen oder noch einige andere Einstellungen wie zum Beispiel die der Farbpalette zu bewirken. Das BIOS läßt sich normalerweise über den Interrupt 10 ansprechen, eine komplette Referenz über die implementierten Funktionen kann man sich aus der Interrupt Liste von dem Herrn Ralf Brown beziehen.
Ein großes Manko an dieser Stelle stellt die allgemeine Kommunikation über Interrupts da. Diese verbrauchen, da sie eine Unterbrechung des aktuellen Programmes anfordern, relativ viel Rechenzeit, so daß wenn man zeitintensive Berechnungen ausführt, sich mittels Kommunikation via Interrupt an dieser Stelle ein großer "Flaschenhals" in Bezug auf die Performance aufbaut.

VGA Grafik Karten bieten jedoch eine Menge an Ports an, mit deren Hilfe man direkt mit der Hardware und den einzelnen "Chipabteilungen", wie zum Beispiel mit dem RAM DAC oder dem CRTC kommunizieren kann. So liegt es teilweise frei, Einstellungen via Port Programmierung oder via Interrupts vorzunehmen oder abzufragen. Der Vorteil der Ports liegt ganz klar in ihrer Verarbeitungszeit, diese verbrauchen wesentlich weniger Taktzyklen als wie die Behandlung eines Interrupts. Im folgenden werden einige wichtige bzw. interessante Interrupts sowie Ports der Grafik Karte aufgeführt, die in der Praxis bezüglich Grafik Programmierung weiterhelfen sollen.
INT 10 - VIDEO - SET VIDEO MODE
-------------------------------
AH = 00h
AL = desired video mode (see #0009)
Return:
AL = video mode flag (Phoenix, AMI BIOS)
     20h mode > 7
     30h modes 0-5 and 7
     3Fh mode 6
AL = CRT controller mode byte (Phoenix 386 BIOS v1.10)

Desc:  specify the display mode for the currently active display adapter
Notes: IBM standard modes do not clear the screen if the high bit of AL is set

(Table 0009)
Values for video mode:
     text/ text         pixel pixel          colors  disply scrn  system
     grph resol         box   resolution             pages  addr
 02h = T  80x25         8x8   640x200        16gray  4      B800  CGA,PCjr,Tandy
     = T  80x25         8x14  640x350        16gray  8      B800  EGA
     = T  80x25         8x16  640x400         16     8      B800  MCGA
     = T  80x25         9x16  720x400         16     8      B800  VGA
 03h = T  80x25         8x8   640x200         16     4      B800  CGA,PCjr,Tandy
     = T  80x25         8x14  640x350         16/64  8      B800  EGA
     = T  80x25         8x16  640x400         16     8      B800  MCGA
     = T  80x25         9x16  720x400         16     8      B800  VGA
     = T  80x43         8x8   640x350         16     4      B800  EGA,VGA [17]
     = T  80x50         8x8   640x400         16     4      B800  VGA [17]
 0Dh = G  40x25         8x8   320x200         16     8      A000  EGA,VGA
 12h = G  80x30         8x16  640x480         16     .      A000  VGA
 13h = G  40x25         8x8   320x200        256     .      A000  VGA,MCGA

INT 10 - VIDEO - GET CURRENT VIDEO MODE
---------------------------------------
AH = 0Fh
Return:
AH = number of character columns
AL = display mode (see #0009 at AH=00h)
BH = active page (see AH=05h)

Note: if mode was set with bit 7 set ("no blanking"), the returned mode will
also have bit 7 set

Port 03dah - Input Status Register 1
------------------------------------
Bit 0        Display enable complement
Bit 1,2      reserviert
Bit 3        Vertical Retrace
Bit 4,5      Herstellerspezifische Test Bits
Bit 6,7      reserviert

Port 03c8h - Pixel Write Adress
-------------------------------
Ein ordinaler lesender bzw. schreibender Zugriff auf diesen Port
ermittelt bzw. selektiert die angegebene Farbe aus der Palette.

Port 03c9h - Pixel Color Value
------------------------------
Drei Lese- bzw. Schreibzugriffe auf diesen Port ermittelt bzw.
konfiguriert die zuvor selektierte Farbe aus der Palette gemäß
ihren Komplimentärwerten aus Rot, Grün und Blau.

5. Beispielhafte Hardwareprogrammierung einer Grafikkarte

Bildverarbeitung auf einem Computer ist allgemein und im speziellen mit Assembler so einfach, als wie wenn man sich eine Leinwand nehme, etwas Farbe anmischt und mit einem Pinsel anfängt zu malen.

Zuerst bereite man die Zeichenfläche vor, dazu initialisiert man über den Interrupt 10h einen Grafikmodus, identifiziert über das AL Register.
mov         ax,0013h
int         10h
Nach erfolgreicher Initialisierung ist der Grafik Bildschirm ansprechbar und in diesem Fall fiel die Entscheidung auf den Grafik Modus 13h. In den VGA Grafik Modien ist der Bildschirmspeicher ab dem Segment 0A000h erreichbar. Der Bildschirmspeicher ist hier bei 256 Farben linear aufgebaut, d. h. das erste Pixel fängt ab dem Offset 0000h an und eine Zeile hat eine Länge von 320 Pixeln. Die nächste Zeile beginnt damit sozusagen ab Pixel 321, dies entspricht einem Offset von 0140h. Ein Byte Eintrag in diesem zweidimensionalem Array, der Bildschirm hat im Modus 13h eine Anzahl von 200 Zeilen, spiegelt einen Eintrag aus der Farbpalette wieder.

Als nächstes baut man sich seine Farbpalette auf. Auf dem Port 03c8 wird die Startfarbe gewählt. Eine Palette im Grafik Modus 13h bietet uns 256 verschiedene Einträge. Nach Initialisierung der Startfarbe fängt man an, über Port 03c9h einmal ein Byte für den roten Farbanteil, ein zweites für den grünen und ein drittes für den blauen Farbanteil auszugeben. Sobald ein Eintrag in Form seiner drei Komplimentärfarben erfolgt ist, wird automatisch die Farbnummer an Port 03c8h um einen Eintrag hoch gezählt.

Port Programmierung erfolgt unter anderem über folgende Befehle.
out         dx,al
out         dx,ax
An dieser Stelle beschreibt das DX Register die Port Adresse und das AL bzw. AX Register den auszugebenden Wert. Analog der gleiche Aufbau zum Einlesen einer Wertigkeit.
in          dx,al
in          dx,ax                  
Hier wird von der Port Adresse über DX identifiziert ein 8bit bzw. 16 bit Ordinalwert in den Akkumulator eingelesen, folgender Abschnitt zeigt einmal das Initialisieren eines Farbwertes über die Ports des RAM DAC einer Grafik Karte. Dabei wird die Nummer der Farbe sowie ihre drei Komplimentärfarben übergeben:
mov         al,farbnummer           ; Nummer innerhalb Farbpalette einlesen
mov         dx,03c8h                ; Port in DX Register laden
out         dx,al                   ; auf dem Port ausgeben
mov         dx,03c9h                ; Port in DX Register laden
mov         al,rotwertigkeit        ; Farbwert einlesen
out         dx,al                   ; und auf dem Port ausgeben
mov         al,gruenwertigkeit      ; weiteres Mal Farbwert einlesen
out         dx,al                   ; und als Komplimentär auf dem Port mischen
mov         al,blauwertigkeit       ; dasselbe für den blauwertigkeit
out         dx,al                   ; auf dem Port ausgeben
Allgemein gesehen muß bei dem Umgang mit grafischen Abbildern zur Darstellung immer eine große Menge an Daten von einer Stelle aus dem Speicher hin zum Grafik Speicher kopiert werden. Für solche Operationen bieten sich die String Operation Befehle aus dem CPU Befehlssatz an, aus denen hier ein kleiner Ausschnitt vorgestellt werden soll.
CLD                Löscht das Direction Flag.
STD                Setzt das Direction Flag.
LODSB              Lädt ein Byte, adressiert über das Registerpaar DS:SI in den Akkumulator.
                   Dabei wird das Index Register um einen Zähler relativ zum Direction Flag weiter gezählt.
LODSW              Analog zu LODSB, nur abgebildet auf Word Daten Größe.
STOSB              Schreibt ein Byte, adressiert über das Registerpaar ES:DI aus dem Akkumulator.
                   Dabei wird das Index Register um einen Zähler relativ zum Direction Flag weiter gezählt.
STOSW              Analog zu STOSB, nur abgebildet auf Word Daten Größe.
MOVSB              Kopiert ein Byte, adressiert über DS:SI an die über ES:DI angegebene Speicherzelle.
                   Dabei werden das Source sowie Destination Index Register relativ zum Direktion Flag
                   weiter  gezählt.
REP                Wiederholungsanweisung der CPU. Ein nachfolgender Befehl wird solange wiederholt,
                   wie das CX Register ungleich null ist. Bei jedem Wiederholungsschritt wird das
                   CX Register um einen Zähler runter gezählt.
Näher sollte das Direction Flag erklärt werden. String Befehle sind allgemein gesehen dazu geeignet, Operationen auf eindimensionale Arrays vorzunehmen. Das Array wird immer über Segment und Offset adressiert, nur die Bearbeitungsrichtung ist relativ. Das heißt, wenn das Direction Flag gelöscht ist, wird das Index Register mit dem enthaltenen Offset des Arrays um einen Zähler erhöht, sollte das Direction Flag gesetzt sein, so wird das Offset um einen Zähler verringert.

Die starke Ausdrucksmöglichkeit der String Operation Befehle kann man sich zu nutze machen, um zum Beispiel einmal exemplarisch den Bildschirmspeicher zu löschen.
mov         ax,0a000h        ; Segment Grafik Speicher einlesen
mov         es,ax            ; in Segment Register für String Befehl schreiben
xor         di,di            ; Offset bestimmen
mov         cx,320*200       ; Anzahl der Bytes bestimmen
xor         ax,ax            ; auszugebenden Inhalt vorbereiten
cld                          ; Direction Flag festlegen
rep         stosb            ; Anzahl Bytes ausgeben
Ein weiteres Beispiel, welches in den Grafik Speicher einen virtuellen Bildschirm kopiert, der über einen Pointer identifiziert ist und mit den gleichen Dimensionen wie der Grafik Modus 13h aufgebaut ist.
mov         ax,0a000h        ; Segment des Grafik Speicher laden
mov         es,ax            ; in Segment Register für String Befehl schreiben
xor         di,di            ; Index Register als Offset auf den Start setzen
lds         si,vscreen       ; Position der Quelle für String Befehl laden
mov         cx,320*200       ; Anzahl der zu kopierenden Zeichen ermitteln
shr         cx,1             ; da Worddaten Transfer nur die Hälfte als wie Bytes
cld                          ; Direction Flag bestimmen
rep         movsw            ; Anzahl Word Daten kopieren
Diese Routine implementiert einen Zeichenpinsel, der auf dem Grafik Bildschirm an die übergebenen Koordinaten einen Punkt mit der überlieferten Farbe setzt. Sie zeigt das Positionieren innerhalb des Grafik Speichers an, um ein Verständnis über die Adressierung eines Farbpixels aufzubauen.
mov         ax,0a000h        ; Segment des Grafikspeichers
mov         es,ax            ; in das ES Register zum Adressieren laden
mov         ax,320           ; Pixel Anzahl pro Zeile für Multiplikation
mul         ypos             ; mit Anzahl Zeilen multiplizieren, Word Größe
add         ax,xpos          ; X Position dazu addieren
mov         di,ax            ; In Index Register als Offset schreiben
mov         es:[di],farbwert ; Farbwert an die errechnete Position ausgeben
Zu guter letzt soll noch eine Funktion vorgestellt werden, mit deren Hilfe wir uns dem Schritt der Animation nähern. Bekanntlich von der Fernsehtechnologie, "als die Bilder anfingen, laufen zu lernen" besteht eine Filmsequenz aus vielen, kleinen Einzelbilder, welche nur in einer angepaßten Geschwindigkeit auf einer Leinwand abgespielt werden müssen, um den Effekt einer flüssigen Animation zu erzielen. Nun, in der Lage sind wir eigentlich auch schon dazu, der Grafik Speicher repräsentiert unsere Leinwand und beliebig viele Einzelbilder können wir als virtuelle Screens im Speicher unterbringen bzw. Berechnen.

Um jetzt die Animation hin zu bekommen gibt es nur eine kleine Tücke, welche zu beachten sei. Da der Monitor, gesteuert vom CRTC der Grafik Karte, sein eigenes Timing zur Darstellung der Bildinformationen enthält, muß man zu diesem Timing synchronisieren. Der Seiteneffekt ohne passende Synchronisation wäre der, das ansonsten ein Flackern oder ein zerrissenes Bild dargestellt werden würde, da es passieren kann, das der Kathodenstrahl uns beim Schreiben in den Grafik Speicher überholt. Um dies zu verhindern, wurde schon die Möglichkeit der Grafik Karte angesprochen uns über einen Kathodenstrahl Rücklauf zur nächsten Zeile bzw. zum Anfang der Bildröhre zu benachrichtigen. Der Rücklauf in die nächste Zeile geht relativ schnell von statten und kann an dieser Stelle nicht wirklich weiter helfen. Allerdings kann man sich den Rücklauf zum Anfang des Bildschirmes zu nutze machen, da dieser relativ viel Zeit verschlingt und auch dann erst mit den Aufbau eines komplett neuen Bilddurchlaufes anfängt. Dazu ermittelt man am Port 03dah das 3te Bit und überprüft solange, bis ein Strahlenrücklauf eintritt. Eine Besonderheit an der nachfolgenden Routine ist das vorige Abwarten eines eventuell gerade prozessierenden Strahlenrücklaufes, da dieser schon zu weit fortgeschritten sein kann, um die Zeit, die er liefert, auszunutzen.
mov         dx,03dah         ; Port Nummer initialisieren
@loop1:
in          al,dx            ; Status Byte vom Port einlesen
test        al,8             ; Auf gesetztes Bit 3 prüfen
jnz         @loop1           ; solange das Bit gesetzt ist, wiederholen
@loop2:
in          al,dx            ; erneut vom Port den Status ermitteln
test        al,8             ; auf Bit 3 überprüfen
jz          @loop2           ; wenn jetzt das Biot gesetzt ist fängt der vertical
                             ; Retrace gerade an