Assemblerprogrammierung mit BBC BASIC

 

BBC BASIC ist ein BASIC-Interpreter, ursprünglich für die Acorn BBC-Rechner entworfen, welche im britischen Schulfernsehen verwendet wurden, wurde BBC BASIC auch auf den Z80 umgeschrieben und letzten Endes auf den Acorn Archimedes. Die Z80-Version ist im NC100 eingebaut, es gibt eine auf den CPC angepasste Version die dessen Grafik- und Soundfähigkeiten unterstützt, aber auch eine Version, die auf jedem CP/M-Rechner läuft - also auch auf dem PCW.

Vorweg ein paar LINKs:

BBC BASIC Seite

Generic CP/M Version von BBC BASIC 3.00

Download - BBC for CP/M

Offline LINK der JOYCiE-CD

BBC BASIC hat gegenüber dem Mallard BASIC einige Vorzüge: Durch die Möglichkeit, Prozeduren und Funktionen (und darin lokale Variablen!) zu verwenden, erübrigt sich GOTO und GOSUB weitgehend. Daher editiere ich meine Listings auch nicht direkt im BBC BASIC Interpreter, sondern mit einem Editor, CONVERT.COM übersetzt diese Datei dann in BBC BASIC-Tokens und fügt die Zeilennummern hinzu.

Eine weitere Besonderheit ist der eingebaute Assembler, den ich hier ausführliche behandeln möchte.

Damit es nicht zu trocken wird, hier ein erstes Beispielprogramm:

HIMEM=&BFFF
DIM mc% 1024

scr_run_routine   = &00E9
te_reset          = &00C2
userf             = &FC5A
roller            = &B600

FOR pass%=0 TO 3 STEP 3
 P%=mc%
 [        OPT pass%

          LD BC,mirror:CALL userf:DEFW scr_run_routine ;Spiegelroutine aufrufen
          RET                ; Zurueck ins BASIC

 .scrini call userf:DEFW te_reset ;Bildschirm zuruecksetzen
          ret                ; Zurueck ins BASIC

 ]:P%=&C000:[ OPT pass%

  .mirror ld de,puffer       ;Adresse des Zwischenspeichers in DE laden
          ld hl,roller+&1FE  ;HL zeigt auf den letzten Eintrag im Roller-RAM
          DEFB &DD:ld l,0    ;Lower-Byte von IX dient als Backcouner

  .loop ldi:ldi              ;Eintrag aus dem Roller-RAM uebertragen
          dec hl:dec hl:dec hl:dec hl ;HL auf den Eintrag davor setzen
          DEFB &DD:dec l     ;zum naechsten Schleifendurchlauf bzw. -ende
          jr nz,loop         ;/
          ld hl,puffer       ;neues Roller-RAM aus Zwischenspeicher uebertragen
          ld de,&B600        ;  /
          ld bc,&0200        ; /
          ldir               ;/
          ret                ;Routine beenden
  .puffer nop                ;Hier beginnt der Zwischenspeicher
 ]
NEXT pass%
CALL mc%
END

DEF PROCscrinit
 CALL scrini
ENDPROC

Es gibt zwei Möglichkeiten, wie sich unter BBC BASIC Speicher für Maschinensprache reservieren lässt:

Die erste wäre, den HIMEM herunterzusetzen. Das habe ich hier für jenen Programmteil gemacht, der im Common Memory abgelegt werden muss.

Für den Programmteil bei dem es egal ist, wo im Speicher er steht habe ich jedoch die für BBC BASIC gebräuchliche Methode verwendet, mir einen Speicherteil zuweisen zu lassen. Hier wird mir den Befehl DIM mc% 1024 in der Variable mc% die Startadresse eines 1024 Bytes großen Blocks zuweisen.

Für den Assembler sind zwei Durchläufe nötig:
- Der erste, um die Position der Labels festzulegen.
- Der zweite, um dann das Programm zu generieren.
Deshalb auch die FOR-NEXT-Schleife.

Mit der OPT-Anweisung werden die jeweiligen Durchlaufbedingungen festgelegt:

0 Fehler unterdrücken, kein Listing, Output = Startadresse = P%
1 Fehler unterdrücken, Listing ausgeben, Output = Startadresse = P%
2 Fehler ausgeben, kein Listing, Output = Startadresse = P%
3 Fehler ausgeben, Listing ausgeben, Output = Startadresse = P%
 
4 Fehler unterdrücken, kein Listing, Output = O%, Startadresse = P%
5 Fehler unterdrücken, Listing ausgeben, Output = O%, Startadresse = P%
6 Fehler ausgeben, kein Listing, Output = O%, Startadresse = P%
7 Fehler ausgeben, Listing ausgeben, Output = O%, Startadresse = P%

Die OPT-Codes 4-7 sind für den Fall da, dass das Programm an eine andere Adresse assembliert wird, als gestartet, beispielsweise wenn der Maschinencode erst nach der Assemblierung an die endgültige Adresse verschoben werden soll oder wenn der Code abgespeichert wird, um dann als Teil eines Programms an eine festgelegte Adresse geladen zu werden. O% und P% werden parallel hochgezählt.

Zwischen den eckigen Klammern ("[" und "]") liegt der Assemblercode. Deutsche PCWs haben diese Zeichen jedoch mit Umlauten belegt, daher setzte ich unter CP/M den Befehl LANGUAGE 0 ein - es liest sich so einfach besser.

Hier gibt es die Möglichkeit, das Assemblieren zu unterbrechen, etwa so:

]:PRINT"Hauptprogramm wird jetzt assembliert": [ OPT pass%

Oben verwende ich eine solche Unterbrechung, um den Pointer P% auf den Common Memory zu setzen.

Wichtig bei der hier vorliegenden BBC BASIC Version 3.00, dass nach der [ wieder ein OPT folgt - bei späteren Versionen der Programmiersprache wäre dieses weitere OPT nicht mehr nötig.

Labels sind Variablen und Variablen sind Labels, daher kann dann auch einfach mit CALL mc% die Maschinenspracheroutine aufgerufen werden. Das vereinfacht auch die Verbindung von BASIC und Maschinensprache. Der in vielen Assemblern verwendete Befehl EQU existiert hier nicht, statt dessen wird in dem Label einfach mit variable = wert ein Wert zu gewiesen.

Das ersten Beispielprogramm stellt die Einträge des Roller-RAMs auf den Kopf und damit das Bild, was auch die Joyce ein wenig durcheinanderbringt. Nun einfach PROCscrinit eintippen, das muss leider halbblind geschehen, dann kommt alles wieder in Ordnung. Mit *bye wird BBC BASIC beendet.

Nun kann es vorkommen, dass man ein paar Werte an eine Routine übergeben will. Hierzu bieten sich die Variablen A%, B%, C%, D%, E%, F%, H% und L% an, da mit dem Befehl CALL ihre Werte an die Z80-Register A, B, C, D, E, F, H und L übergeben werden.

Hier ein Beispiel:

DIM mc% 100
DIM result 1

FOR pass% = 0 TO 3 STEP 3

 P% = mc%
 [
    opt pass%

    add a,b
    ld (result),a
    ret
 ]

NEXT

A%=8
B%=2
CALL mc%
PRINT "Das Ergebnis: ";?result

In den Variablen A% und B% werden die beiden zu addierenden Zahlen an die Routine übergeben, diese zählt die beiden zusammen und legt dann das im Akku gespeicherte Ergebnis an der in der Variable result gespeicherten Adresse ab.

BBC BASIC kennt aber kein PEEK, diese Funktion übernimmt das Fragezeichen, ?result ist also gleichbedeutend mit dem Mallard BASIC Befehl PEEK(result) des CPC.

!adresse liefert ein 32-Bit-Wort und $adresse einen String, wobei das Byte 13 als Endmarkierung des Strings erkannt wird. Alles das funktioniert sowohl zum Lesen als auch zum Schreiben!!!

Ein weiterer Weg, Daten von einer Maschinenspracheroutinen zu übernehmen ist die Funktion USR(adresse). Die mit Adresse bezeichnete Routine wird aufgerufen und zurückgeliefert wird ein 32-Bit-Wert bestehend aus HL' und HL, wobei HL das höherwertigere Registerpaar bildet.

Auch hierzu ein Beispiel:

DIM mc% 100 
 
FOR pass% = 0 TO 3 STEP 3 
 
  P% = mc% 
 
  [ OPT pass% 
 
    ld   h,b : ld l,c 
    push de  : exx    : pop hl : exx 
    ret 
 
  ] 
 
NEXT pass% 
 
B%=&12 
C%=&34 
D%=&56 
E%=&78 
PRINT ~USR(mc%)

Dieses Programm macht nichts anderes, als den in BC übergebenen Wert in HL und den in DE übergeben Wert in HL' zu laden, PRINT ~USR(mc%) ruft die Routine auf und gibt das aus den beiden HL-Registerpaaren bestehende Ergebnis aus.

Die Tilde "~" bewirkt übrigens, dass bei der Ausgabe statt der Dezimalschreibweise Hexdezimalverwendet wird.

Wer mehr über BBC BASIC wissen will, dem sei die Site http://www.bbcbasic.com empfohlen. Die Generic CP/M Version von BBC BASIC 3.00 findet Ihr auf http://www.rtrussell.co.uk.

Stephan Sommer \\ Oktober 2004