8bit-Computer: bo8h

Wechseln zu: Navigation, Suche

von:   Josef Gnadl (bome)

Übersicht

Habe einen 8bit-Rechner entwickelt und als Prototyp-Gerät realisiert auf folgenden FPGA-Boards:

  • Spartan-3A Starter Kit von Xilinx
  • Spartan-3E Starter Board und
  • Nexys2 Board von Digilent
  • DE1 Board / Cyclone2 Starter Board von Terasic/Altera
  • Altera DE0 Board
  • Altera DE0-nano Board

Die Realisierung auf dem Spartan-3A Starter Kit funktioniert auch auf dem Spartan-3AN Starter Kit.

Die CPU ist eine Eigenentwicklung. Merkmale des Gesamtsystems sind der aus mehreren 64KByte-Seiten bestehende Adressraum, das Steckplatz-Konzept, die an die Hardware angepasste Programmiersprache bola und taktgenau berechenbare Programmlaufzeiten. Der Zeichensatz beinhaltet einen hexadezimalen Ziffernsatz, welcher für die Ziffern A..F spezielle Zeichen verwendet.

Das Prototyp-Gerät besitzt Bildschirm und Tastatur, nutzt RS232 zum Daten-Austausch mit PC und hat eine SD-Karte mit selbstgestricktem Dateisystem. Neu 09.Sep.2017:  Der VHDL-Code ist so geschrieben, dass leicht weitere Schnittstellen eingebaut werden können.

  • Informationen und Downloads gibt es auf bo8h.de.
  • Im Forum Codesammlung gibt es dazu den Beitrag 8bit-Computing mit FPGA.
  • Im embedded-projects-Journal 14  gibt es den Artikel Ein 8bit-Rechner auf dem Spartan-3A-Starterkit.

Kopie des Download-Files von bo8h.de: Datei:Bo8h.zip

Die CPU steht unter der hier im Wiki geltenden Creative-Commons-Lizenz zur Verfügung: 8bit-CPU: bo8.

Das Prototyp-Gerät hat 4 freie virtuelle Steckplätze mit je 64KByte, wobei je 32KByte für Software vorgesehen sind. Damit haben Software-Entwickler die Möglichkeit, Steckkarten-Software in Eigen-Regie anzubieten. Zur Software-Entwicklung auf PC gibt es einen in C geschriebenen Cross-Assembler.


Die CPU

Der Punkt in den Mnemonics steht für einen auf der Grundlinie liegenden Bindestrich.

Der Akku A und das Erweiterungsregister B sind 8-bit.
Das Doppelregister AB wird abkürzend mit K bezeichnet.
Das Vorzeichenbit A7 wird abkürzend mit U bezeichnet.

Der Programmzähler P und die Adressregister X, Y, Z sind 16-bit.
Das Register Q erhält bei Sprüngen die Rückkehradresse P+1.
R ist Schleifen-Startadresse, S ist Schleifenzähler.

Einziges Flag ist der Carry V. Bei bedingten Sprüngen kann ausserdem abgefragt werden, ob U/A/K Null sind.

Alle Speicherzugriffe sind 8bit-Zugriffe.
GTMX lädt nach A den Inhalt der Speicherzelle, auf welche X zeigt.
STMX speichert A in der Speicherzelle, auf welche X zeigt.

IXE inkrementiert X und tauscht A und B.
DXE dekrementiert X und tauscht A und B.

Damit lassen sich 2-byte-Speicherzugriffe aufbauen
nach dem Muster GTMX/IXE/GTMX und STMX/DXE/STMX.
Es gibt hierfür die Assembler-Makros GTMXI und STMXD.

GTMXI   lädt eine Adresse von Position X
ST.Y    überträgt die Adresse nach Y

GTMXI   lädt eine Adresse von Position X
AD.X    addiert X
ST.Y    überträgt die zu X relative Adresse nach Y

J..     Sprung zu der Adresse, welche in K steht

GTA 59  lädt den Wert 0059 nach K
AD. 35  addiert 35 zu A
J..     Sprung nach 3559

Das lässt sich kürzer schreiben mit Assembler-Makro
/GTA 3559
J

GTR 59  lädt den Wert P+0059+3 nach K
AD. 35  addiert 35 zu A
J..     relativer Sprung nach P+3559+3

Das lässt sich kürzer schreiben mit Assembler-Makro
/GTR 3559
J 

GT.Q  lädt Rückkehradresse nach K
J     Rücksprung aus Unterprogramm


Wenn ein Unterprogramm selber J.. ausführen will für andere Zwecke als für den Rücksprung, muss es vorher die Rückkehradresse sichern. Dazu kann es die Rückkehradresse aus Q laden, anfangs steht sie aber auch in K und das Laden kann entfallen.

Ausser dem Sprungbefehl J.. gibt es die bedingten Sprünge mit 8bit-Sprungdistanz:
O.cc nn (Vorwärtssprung falls cc) und B.cc nn (Rückwärtssprung falls cc).

Für schnelle Schleifen gibt es die Repeat-Befehle R.cc.
Repeat-Befehle gibt es kombiniert mit Schleifenzähler-Dekrementieren und Adresse-Inkrementieren/Dekrementieren. Die Schleifenstartadresse R wird gesetzt am Schleifenanfang mittels S.RP oder mittels O.RP nn. Bei letzterem wird gleichzeitig ein Vorwärtssprung ausgeführt. Damit sind Schleifen mit Hineinsprung (while-Schleifen) realisierbar.

Bei J.. wird die Zieladresse auch nach R übertragen. Das garantiert, dass R stets eine Adresse im Nahbereich des Programmzählers enthält. Eine Anwendung hiervon ergibt sich beim Sprung in eine Tabelle, welche aus O.WY nn -Befehlen (Vorwärtssprung always) besteht, wobei die O.WY nn alle zur selben Adresse springen. Die dortige Routine kann durch Auswertung von R die Nummer des O.WY-Befehls ermitteln.

Adressierung mehrerer 64K-Seiten:

Zu jeder auf dem Adressbus ausgegebenen Adresse wird auf den Steuerleitungen angezeigt, von welchem der Adressregister P,X,Y,Z sie kommt. Dadurch kann eine externe Logik jedem der Adressregister eine eigene 64K-Speicherseite zuordnen. Die CPU kann spezielle Steuersignale ausgeben, welche die externe Logik zur Memory-Page-Umschaltung für eines der Adressregister veranlassen sollen.

Der Befehl H.. ist der einzige und universell einsetzbare IO-Befehl. Es wird K auf dem Adressbus ausgegeben und der Datenbus nach A eingelesen.

Wenn auf H.. ein J.. folgt, dann wirkt H.. stattdessen als Präfix und bewirkt, dass J.. mit einer Memory-Page-Umschaltung kombiniert wird. So kann die CPU von einer 64K-Seite in eine andere springen, ohne dass es einen gemeinsamen Speicherbereich geben muss.

Bei den Adressregistern X,Y,Z gibt es spezielle Befehle SW.X, SW.Y, SW.Z für die Memory-Page-Umschaltung.


Die Gesamt-Hardware

Es gibt vier Memory-Pages mit je 64KByte:

  • Page 0: Aktiver Steckplatz
  • Page 1: ROM (32K), Text-RAM (16K), Video-RAM (16K)
  • Page 2: Haupt-RAM
  • Page 3: Zusatz-RAM

Der aktive Steckplatz in Page 0 wird durch ein 3bit-Register SLT ausgewählt.

Es gibt die 2bit-Register MP,MX,MY,MZ. Diese Register legen fest, in welche der vier Memory-Pages das betreffende Adressregister P,X,Y,Z zeigt. Ausserdem gibt es die Schattenregister NP,NX,NY,NZ. Das CPU-Signal zur Memory-Page-Umschaltung für ein bestimmtes Adressregister # bewirkt den Austausch von M# und N#, wobei # hier für P,X,Y,Z steht. Beim Sprung mit Memory-Page-Umschaltung  H..  J..  werden also MP und NP getauscht. Die Schattenregister NP,NX,NY,NZ können über IO-Befehle H.. mit einem Wert 0 bis 3 geladen werden.


Die Programmiersprache bola

Es gibt in dieser Sprache kein goto, kein break, kein continue, und in jedem Programm oder Unterprogramm genau 1 return, nämlich am Ende des Programms oder Unterprogramms.

Die Strukturelemente sind folgende:

Bedingte Anweisung:  IF.c  ...  ENDF
Verzweigung:         IF.c  ...  ELSE  ...  ENDE
Case-Struktur:       CASE /nn  ...  NEXT /nn  ...  LAST
Do-while-Schleife:   LOOP  ...  RP.c
While-Schleife:      L.JP  ...  HERE  ...  RP.c

c steht hier für die Sprungbedingung, nn steht für eine Zahl fallend bis 00, NEXT /nn ist entsprechend mehrmals anzugeben.
RP.c steht für wiederhole falls c.

Ebenso wie die Verzweigung zählt die Schleife mit Hineinsprung

L.JP ... HERE ... RP.c

als ein Block mit zwei Unterblöcken, die strenge Blockstruktur wird durch den Hineinsprung nicht durchbrochen.

Die Blockstruktur wird bei folgenden Operationen gebraucht:

ACQU  ...  UACQ
FASN  ...  UFAS
LN.1  ...  RELS    //    LN.2  ...  RELS
LX.1  ...  RELS    //    LX.2  ...  RELS

ACQU steht für Aquirieren von Residenten Variablen
UACU macht das Aquirieren rückgängig

Residente Variable sind Variable, zB. vom Typ databox, welche ausserhalb des Programms dauerhaft im RAM liegen. Sie werden über ihre Nummer adressiert. ACQU bindet freie Zeiger, welche im Programm vereinbart sind, an diese externen Variablen. Dadurch sind diese Variablen unter Umgehung der Nummer wie gewöhnliche im Programm vereinbarte Variable verfügbar.

FASN vergibt den Status "befestigt" an Variable vom Typ databox
UFAS macht die Vergabe rückgängig.

Variable vom Typ databox, welche beim Start des Programms automatisch installiert werden, sind von sich aus "befestigt". Das installieren kann aber auch durch Steckkarten-Kommandos erfolgen, zB. wenn die Größe der databox erst zur Laufzeit des Programms bekannt wird. Dann sind sie zunächst unbefestigt.

LN.1 leiht den von einer databox in RAM1 belegten Speicherplatz an mehrere Variable aus, welche vorher als nicht installiert vereinbart wurden und nach dem Ausleihen den Speicherplatz der databox überdecken.

LX.1 leiht einen Teil des Speicherplatzes einer databox ab einem Startindex an mehrere Variable aus.

Entsprechend LN.2 und LX.2 für RAM2. In RAM1 sind die mehreren Variablen kurze Variable vom Typ data, oder selber wieder vom Typ databox, in RAM2 sind sie stets wieder vom Typ databox.

Das Ausleihen aus einer databox setzt voraus, dass die databox befestigt ist, andernfalls wäre sie verschiebbar oder könnte uninstalliert werden, so dass Schreibzugriffe auf die durch das Ausleihen erzeugten Variablen Unheil anrichten könnten.

RELS macht das Ausleihen rückgängig.

Bei den Operationen ACQU, FASN, LN/LX garantiert der Compiler, dass das zugehörige UACQ, UFAS, RELS im selben Block liegt. Die jeweilige Operation und das zugehörige Rückgängigmachen bilden selber wieder einen Block.


Ergänzung zu Sprungbedingungen:

Es kann abgefragt werden, ob eine 1-oder 2-Byte-Variable Null ist und ob sie gleich ff bzw. ffff ist.

Die Kommandos =TT.Z und =TT.M können diese Bedingungen speichern in einem speziellen Byte im RAM, dem TF-Merker. Dazu schieben sie eine 1 oder 0 von oben auf den TF-Merker. In gleicher Weise können Steckkarten-Kommandos nCOMMAND beliebige Bedingungen ermitteln und speichern.

Es gibt bedingte Sprünge, welche die obersten 3 Bit des TF-Merker mittels einer Wahrheitstabelle abfragen. Dabei können bis zu 3 Bits wieder vom TF-Merker entfernt werden.

Mittels der Routinen =PP.r, =PZ.r, =PS.r ist es möglich, solche Bedingungen in einer 1- oder 2-Byte-Variablen zu speichern, zu verANDen und zu verORen. Damit lassen sich beliebig komplexe Sprungbedingungen berechnen.


Unterprogramme:

In der Programmiersprache gibt es keine functions mit Rückgabewert, sondern nur einfache Unterprogramme, bei welchen alle Parameter gleichberechtigt sind. Es werden ausschließlich Zeiger übergeben.


Variablen vom Typ FIX:

Ihre Werte sind Teil des Programmcodes und liegen dort am Ende. Der gesamte Speicherbereich dieser Werte kann als "FIXBOX" vom Programm abgetrennt werden und dann mittels eines speziellen Werkzeugs bearbeitet werden.