8bit-Computer: bo8h

Aus der Mikrocontroller.net Artikelsammlung, mit Beiträgen verschiedener Autoren (siehe Versionsgeschichte)
Version vom 17. April 2016, 06:25 Uhr von Bome (Diskussion | Beiträge) (Die Programmiersprache)
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

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.

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.

Es gibt eine Schnittstelle zum Anschluss von Zusatz-Hardware. Sie hat einen 9bit-breiten Ausgangsport und einen 8bit-breiten Eingangsport und zwei Handshake-Leitungen für asynchronen Betrieb. Damit sollte zB. der Anschluss eines uC möglich sein. 8 der 9 Ausgänge sind Tristate und können mit den 8 Eingängen zusammengelegt werden.


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ückkehr-
adresse 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 wird 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 Adress-
register 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  IF.c ... ELSE ... ENDE  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 zunächt als uninstalliert
      vereinbart sind, 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 =PLPr, =PLZr, =PLSr ist es möglich,
solche Bedingungen in einer 1- oder 2-Byte-Variablen zu
speichern, zu ver-AND-en und zu ver-OR-en. 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.