ZX-IDE in der Praxis, Tipps & Tricks für ZX81,ZX80

ZX-Team Forum
Benutzeravatar
PokeMon
User
Beiträge: 4490
Registriert: 31.08.2011, 23:41

ZX-IDE in der Praxis, Tipps & Tricks für ZX81,ZX80

Beitrag von PokeMon » 17.12.2012, 00:13

Intro:

Hier im allerersten Beitrag des Threads wird ab sofort die aktuelle Version angefügt sein (Attachement) und ggf. bei Neuerungen ausgetauscht und gleichzeitig auch ein Inhaltsverzeichnis für die einzelnen Themen zur ZX-IDE gepflegt. So kann man einfacher mal was nachschlagen oder auch nur bestimmte aktuell wichtige oder interessante Beiträge lesen und muss diesen Thread nicht von A-Z durcharbeiten. Sollte das Leben einfacher machen. Betreffend der Version der ZX-IDE brauchen wir ja keine Archivierung aller Entwicklungsstände sondern nur das jeweils finale Endprodukt (= letzte Version).

Inhaltsverzeichnis zur ZX-IDE:

>>> ASSEMBLER & ALLGEMEINES <<<
=> Editor und Assemblieren (Compile)
=> Audio Schnittstelle für Dateiübertragung
=> Listing
=> Addressing Spaces I
=> Datendefinitionen I
=> Datendefinitionen II (Blocks & more)
=> Variablen und Konstanten
=> Labels oder Sprungmarken
=> Media Player
=> ZX Emulator EightyOne
=> ZX Emulator EightyOne "Tipps & Tricks"
=> Z80 Assembler - Undocumented Opcodes
=> ROM Images / Kompositionen erstellen (ZX80CORE)
=> Programme aus dem ROM laden
=> ZX81 Power BASIC
=> ROM Patches - am Beispiel MS HRG / virtual adress space

>>>ZX81 BASIC<<<
=> ZX81 BASIC, REM, _asm
=> ZX81 BASIC, Zeilennummern, Labels
=> ZX81 BASIC, RAND, USR
=> ZX81 BASIC, format, AUTORUN, _noedit
=> ZX81 BASIC, _hide
=> ZX81 Kontext Unterschied BASIC und ASSEMBLER
=> ZX81 Kontext Unterschied BASIC und ASSEMBLER (2)
=> ZX81 PRINT, Zahlenformate, Berechnungen
=> ZX81 Mathematik, Funktionen
=> ZX81 Startup Variablen
=> ZX81 BASIC, Variablen, LET
=> ZX81 BASIC, Labels als Zeilennummern (GOTO, etc.)
=> ZX81 BASIC, graphische Zeichen, Sonderzeichen, Zeichensatz
=> ZX81 BASIC, IF-THEN Konstrukt, Vergleiche, STOP
=> ZX81 BASIC, FOR-NEXT Schleife, STEP, FAST, SLOW
=> ZX81 BASIC, Variablen Definition im Speicher I
=> ZX81 BASIC, Variablen Definition im Speicher II
=> ZX81 P-File Konverter

>>>ZX80 BASIC<<<
=> ZX80 BASIC, Zeilennummern, REM, _asm
=> ZX80 BASIC, RAND, RANDOMISE, USR, labels
=> ZX80 BASIC, AUTORUN, _noedit, _hide
=> ZX80 BASIC, Übersicht Befehlsimplementierung

Hier der ursprüngliche Einführungsbeitrag:

Erfreulicherweise ist die Installation recht unkompliziert. Das ZIP kann in einen beliebigen Ordern entpackt werden und enthält derzeit 3 EXE Files, FASMWZ80.EXE, LISTZ80.EXE und EIGHTYONE.EXE (ZX Emulator) sowie eine Datei FASTLOAD.BIN die einen vorkonfigurierten Teil des 2-Stage Fastloader enthält. Dazu später mehr. Im Hauptverzeichnis sind noch zwei Beispielprogramme, ein kurzes ZX81DEMO.ASM für die Erstellung eines eigenen Programms und das ZX81 ROM als Quellcode um mal ein größeres Beispiel zu laden oder wenn jemand selbst Modifikationen am ROM vornehmen möchte und weiß wie er das dann in seinen Zeddy bekommt. :wink:

Das Unterverzeichnis SINCL-ZX enthält Include Dateien, die spezielle Datenstrukturen für den ZX81 oder ZX80 bereitstellen und in die IDE laden.

Hintergrund für die Entwicklung der IDE war der Einsatz einer komfortablen und integrierten Entwicklungsumgebung anstelle der sonst üblichen Kommandozteilen Tools, die über Dateien kommunizieren und mehr oder weniger aufwändig zu konfigurieren sind mit Eidtoren, Linkern oder kompletten Entwicklungssuiten wie Eclipse. Das wird Anfänger sicher eher abschrecken aber auch für Profis hat die IDE Vorteile. Ich habe verschiedene weitere Tools geplant, die so nach und nach integriert werden können. In Kürze soll das komplette BASIC integriert sein und dann eine gemischte Verwendung von Assembler und BASIC ganz easy von der Hand gehen.

Das Ding ist natürlich nicht komplett neu entwickelt sondern angepaßt an den Z80. Grundlage ist das Projekt flatassembler, unten sind auch Links dorthin zu finden. Flatassembler wurde ursprünglich für x86 Prozessoren entwickelt und mittlerweile auch nach ARM portiert, die Z80 Portierung musste ich selbst vornehmen, also der ganze Befehlssatz. Aber die Grundstrukturen von flatassembler werden genutzt, insbesondere auch die umfangreichen Makro Definitionen und zahlreiche andere nützlichen Direktiven und Datentypen. Eins vielleicht an der Stelle vorweg, es werden derzeit nur die offiziellen Z80 Befehle unterstützt, also nicht die undokumentierten Opcodes oder die neuen Opcodes für die Z84xx Modelle. Normalerweise braucht man das aber für den ZX81 oder ZX80 nicht. Vielleicht ergänze ich das auch mal, wenn ich Zeit habe.

Dieser Thread ist dann sozusagen auch als Handbuch zu verstehen. :D

Dokumentation zu flatassembler:
http://flatassembler.net/docs.php?article=manual

Diskussionsforum von flatassembler:
http://board.flatassembler.net/index.php

Aktuell angehängte Version der ZX-IDE:
1.71.01q.Z80 (01/2016)
Dateianhänge
FASMW-ZX.zip
(1.86 MiB) 185-mal heruntergeladen
Zuletzt geändert von PokeMon am 05.01.2016, 01:52, insgesamt 40-mal geändert.
Wer seinen Computer ehrt, lebt nicht verkehrt.

Benutzeravatar
PokeMon
User
Beiträge: 4490
Registriert: 31.08.2011, 23:41

Editor und Assemblieren (Compile)

Beitrag von PokeMon » 17.12.2012, 01:39

Legen wir mal los und erstellen mal das ZX81DEMO Programm und laden es über das File Menü.
Darüber kann man auch speichern.
Die IDE kann theoretisch beliebig viele weitere Dateien öffnen, die in einzelnen Tabs mit dem Dateinamen unten erscheinen, da kann man dann auch umschalten zwischen den einzelnen Dateien. Normalerweise hat man nur ein Quellcode zu bearbeiten, könnte aber z.B. eine der Include Dateien zusätzlich öffnen.

Wenn die IDE beim Assemblieren (nennt sich bei FASM aber Compile) einen Fehler feststellt, wird die betreffende Datei geöffnet und der Compiler zeigt eine recht konkrete Fehlermeldung und markiert die betreffende Zeile. Bei mehreren Fehlern wird der erste festgestellte angezeigt. Das ist z.B. ein weiterer Vorteil gegenüber Commandline Tools. Man kann den Fehler sofort beheben. Wenn man den Fehler korrigiert hat, sollte man nicht vergessen unten in den "Tab's" wieder auf das Hauptfenster oder Tab zurückzustellen. Sonst compiliert man nur die aktuell geöffnete Datei, also z.B. die Include Datei.

Da das auf Dauer ein bischen umständlich ist, kann man eine Datei auch als Hauptdatei markieren mit "Assign to compiler". Wenn man unten im Tab mit der rechten Maustaste klickt, öffnet sich ein entsprechendes Kontextmenü, mit dem man auch nicht mehr benötigte Dateien schließen kann.

Beim Kompilieren werden übrigens alle Dateien in der IDE automatisch gespeichert. Das separate Speichern kann man sich daher ersparen, außer man schreibt vielleicht stundenlang Quellcode und will sicherheitshalber speichern.

Im Menü Run bzw. mit CTRL-F9 kann man die Datei kompilieren und es wird das Z80 binary erstellt. Das ZX81DEMO.ASM erzeugt ein Beispielprogramm als ZX81DEMO.p welches direkt auf dem ZX81 lauffähig ist.
Wer seinen Computer ehrt, lebt nicht verkehrt.

Benutzeravatar
PokeMon
User
Beiträge: 4490
Registriert: 31.08.2011, 23:41

Audio Schnittstelle für Dateiübertragung

Beitrag von PokeMon » 17.12.2012, 21:48

Das Compilat landet nach dem Compilieren als .p Datei in dem gleichen Directory, in dem auch die Assembler Quelle liegt. Von dort muss es jetzt noch auf den ZX81 gebracht werden. Mit dem Emulator kann man die .p Datei direkt laden, wer ein ZXpand hat oder ähnliches, kann diese auf ein Speichermedium kopieren. Wer beides nicht hat, kann immer noch die integrierte Audio Schnittstelle (Tape Interface) nutzen. Dabei wird die Datei über die Soundkarte des PC auf den ZX81 übertragen.

Damit das mit der Audio Schnittstelle funktioniert, ist vor allem die Verkabelung wichtig: Der Ausgang der Soundkarte wird mit einem MONOKABEL (!!!) mit der Buchse am ZX81 verbunden, die mit EAR beschriftet ist. Klingt komisch, ist aber so. Sinclair hat die Anschlüsse wohl danach beschriftet, was dort angeschlossen wird (also EAR mit dem EAR Ausgang eines Kassettenrecorders). Anstelle eines Monokabels ist auch ein Stereokabel mit Mono Adaptern möglich, die aber dann auf beiden Seiten sein müssen.

Der nächste wichtige Punkt ist die Lautstärke oder der Ausgangspegel. Meistens reicht 75-80 % der Lautstärke einer Soundkarte, 100% schaden aber im Zweifel auch nicht. Wenn der Pegel zu niedrig ist, bekommt der Zeddy nichts mit oder hängt sich ggf. auch während des Ladens auf. Ein recht gutes Indiz ist das klassische Streifenmuster, welches sich beim Anliegen eines Audiosignals ändert. Falls keine Änderung der Streifen erkennbar ist, kommt nichts oder nicht genug an und man sollte den Pegel erhöhen. Das Streifenmuster ist meist nur noch auf alten CRT (Röhrenfernseher/monitor) zu sehen, neue LCD Fernseher zeigen das eigentlich ungültige Videosignal nicht an, so wie auch Bildrauschen unterdrückt wird. Abhilfe schafft hier übrigens das Videomodul ZX81SCP, welches ggf. eigene Syncsignale erzeugt. Nach diversen Tests kann ich sagen, dass die Lautstärke meiner Soundkarte ungefähr 20% höher ist als der Kopfhörerausgang des angeschlossenen Soundsystems mit Boxen. Sollte man daher pegelmäßig einfach ausprobieren. Nach meinen Messungen wird ein Ausgangspegel von etwa 3 bis 3,5 Volt Spitze-Spitze benötigt.

Die Übertragung aus der IDE kann auf Knopfdruck erfolgen mit F9 oder über das Run Menü (Transfer). Dabei wird das Programm automatisch kompiliert und der Loader gestartet, sofern der Compiler keine Fehler feststellt. Eine vorgeschaltete MessageBox wartet auf das Starten des LOAD "" Kommandos auf dem ZX81 und nach Bestätigen der MessageBox startet die Übertragung. Bei dem ZX81DEMO.ASM dauert es etwa 5 Sekunden bis die Übertragung abgeschlossen ist. Es handelt sich aber auch um ein sehr kleines Programm mit knapp 200 Byte. Die Geschwindigkeit der Audio Schnittstelle von Sinclair überträgt die Daten nur mit etwa 40 Byte pro Sekunde und ist auf die damals üblichen Kassetten und deren Audioqualität abgestimmt. Dennoch war das Laden auch damals nicht immer problemlos von Kassette. Nach dem Laden sollte 0/0 in der untersten Zeile erscheinen und der Cursor (K). Durch Drücken der NEWLINE Taste erscheint das Listing des Programms auf dem ZX81 und zeigt als Demoprogramm 2 Zeilen an:

Code: Alles auswählen

1 REM |? TAN
2 PRINT USR 16514
Die Grafikzeichen kann man hier natürlich nicht richtig darstellen. Sofern AUTOSTART aktiviert ist (dazu später mehr) kann unten auch 0/2 stehen. Dabei bedeutet 0 kein Fehler und 2 die letzte ausgeführte Zeile.

Damit man auch andere Programme übertragen kann, gibt es die Funktion "Transfer other file" im Run Menü oder auch kurz mit F10. Dabei werden automatisch ZX81 oder ZX80 Dateien gefiltert, also mit Extension .p bzw. .81 oder für den ZX80 mit .o oder .80. Das Format (ZX80 oder ZX81) wird automatisch erkannt (an der Dateiendung) und die Übertragung angepaßt. Beim ZX81 wird nämlich noch der Name des Programms mit übertragen. Es empfiehlt sich aber einfach LOAD ohne Programmname auch für den ZX81 zu verwenden. Wer Programme nicht direkt übertragen will kann auch einfach nur ein WAV File erstellen mit F11 oder "Create Wav from other file" im Run Menü. Wer immer automatisch ein WAV erstellen möchte beim Kompilieren oder Übertragen von anderen Dateien kann auch die Option "Auto Create WAV" im Run Menü anklicken.

Es gibt auch einen "Fast Loader" den man nutzen kann und der insbesondere für große Programme sinnvoll ist. Die Übertragung von einem 8k Programm dauert mit dem normalen Übertragungsmodus etwa 200 Sekunden, also etwa dreieinhalb Minuten. Der Fastloader kann solche Programme in etwa 13 Sekunden übertragen. Die Übertragungsgeschwindigkeit ist etwa 1 kByte pro Sekunde. Allerdings muss der ZX81 zunächst etwas mit dem anderen Audiosignal anzufangen wissen. Daher wird zuerst ein Fastloader Code übertragen und ausgeführt der dann die schnelle Übertragung leistet. Die Formel lautet daher 5 Sekunden Grundladezeit plus 1 Sekunde pro kByte Programm.

Der Fastloader sollte mit allen ZX81 Modellen kompatibel sein, nistet sich aber in den Variablen Bereich der Programme ein um keinen Platz zu verschwenden. Daher kann der maximal Speicherplatz ausgenutzt werden und der Fastloader ist auch mit dem 1k Speicher kompatibel. Es gibt jedoch die sogenannten 1k Programme, die mit dem extrem knappen Platz auch nur auskommen, in dem sie den Variablenbereich mit nutzen. Diese Programme lassen sich im Fast Modus dann nicht übertragen weil ein wichtiger Teil des Programms durch den Fast Loader überschrieben wird. In dem Fall ist der Fast Loader auszuschalten, aber auch verschmerzbar, da die Übertragungszeit im normalen Modus mit 25 Sekunden verschmerzbar ist. Dennoch bringt er bei selbstgeschriebenen Programmen auch Vorteile wenn diese im normalen BASIC laufen und nicht hoch optimiert sind. Die Übertragung dauert dann nur 6 Sekunden (5+1).

Es gibt allerdings noch einen weiteren Aspekt zu beachten. Durch die höheren Frequenzen sinkt der Pegel im Fast Loader Modus spürbar ab, es sind vielleicht nur noch etwa 80% des Signals. Die meisten Soundkarten scheinen da bei den Höhen etwas frühzeitig abzuriegeln (genutzte Frequenz etwa 15kHz). Gerade auch bei dazwischen geschalteten externen Soundsystemen, die von Haus aus nur etwa 80% des Signalpegels einer guten Soundkarte bringen, macht sich der Effekt bemerkbar (80% von 80% sind eben nur noch 64% und da wird es dann eng auch bei voller Lautstärke). Sollte es daher im normalen Modus klappen, im Fast Modus aber nicht, liegt es sehr wahrscheinlich daran.

Ich werde in Kürze (sagen wir mal Anfang 2013) einen erweiterten Fast Loader programmieren und dann diesen Punkt bei der Optimierung berücksichtigen. Eine Möglichkeit wäre den Audio Ausgang anders zu beschalten, eine andere einen kleinen Audioverstärker davorzuschalten. Ideal wäre es, wenn man mit der Soundkarte Rechtecksignale erzeugen könnte. Das ist aber noch Experimentierfeld, nur eine kleine Vorankündigung.

Tja, damit wäre das Thema Audio Schnittstelle und Dateiübertragung für den Moment mal erschöpfend behandelt. Zumindest aus Sicht der IDE.
Übrigens darf man hier auch gerne Fragen stellen, Anregungen schreiben, usw.
Soll jetzt kein Monolog werden.
Mehr so ein interaktives Handbuch. :D
Wer seinen Computer ehrt, lebt nicht verkehrt.

Benutzeravatar
PokeMon
User
Beiträge: 4490
Registriert: 31.08.2011, 23:41

Listing

Beitrag von PokeMon » 19.12.2012, 11:33

Bei der Fehlersuche oder auch zu Dokumentationszwecken ist ein Listing des Programms recht hilfreich.
Das Listing kann auf Knopfdruck (CTRL-F8) erzeugt werden - genau genommen wird dadurch ein Symbol File erstellt, mit dem dann das Listing Programm gefüttert wird. Das Symbol File ist genau dokumentiert und enthält alle notwendigen Informationen über Befehle, Labels, Datenstrukturen. Das im Einzelnen zu erörtern würde zu weit führen, wen es interessiert und wer das Symbol File eventuell für eigene Zwecke nutzen will sei auf diese Dokumentation verwiesen:
http://flatassembler.net/docs/fas.zip

Schauen wir uns mal die Assembler Quelle an (ZX81DEMO.ASM):

Code: Alles auswählen

format binary as "p"
;LISTOFF

        ; hardware options to be set and change defaults in ZX81DEF.INC
        MEMAVL     EQU     MEM_16K          ; can be MEM_1K, MEM_2K, MEM_4K, MEM_8K, MEM_16K, MEM_32K, MEM_48K
                                           ; default value is MEM_16K
        DFILETYPE  EQU     COLLAPSED       ; COLLAPSED or EXPANDED
        STARTUPMSG EQU    'CREATED WITH ZX81-IDE' ; any message will be shown on screen after loading, max. 32 chars

        include 'SINCL-ZX\ZX81.INC'           ; definitions of constants
;LISTON

AUTORUN:
        REM 1,asmcode_st,asmcode_end

asmcode_st:
; ##################################################
        ; place own assembler code here
; ##################################################

        LD      BC,$55
        RET

; ##################################################
        ; end of section for own assembler code
; ##################################################
asmcode_end:

        db      NEWLINE

LINE2:  ; this is a static PRINT USR 16514 command
        db      0,2,$0E,0,$F5,$D4
        dbzx    '16514'
        db      $7E,$8F,1,4,0,0,NEWLINE

;LISTOFF
        include 'SINCL-ZX\ZX81POST.INC'          ; include D_FILE and needed memory areas

assert ($-MEMST)<MEMAVL
; end of program
Nach Erzeugen des Listings mit CTRL-F8 (oder über Run Menü) wird das nach dem Bestätigen des Abschlussdialogs in einem neuen Tab in der IDE automatisch geöffnet und im aktuellen Verzeichnis auch gespeichert als <QUELLE>.LST. Beim Neukompilieren wird es selbstverständlich in der IDE auch aktualisiert. Vor dem Erstellen wird die aktuelle Quelldatei immer neu kompiliert, sollten Fehler angezeigt werden so müssen diese zunächst behoben werden.

Das Listing erfasst die komplette Source inklusive aller Include Files. Da so ein Programm recht lang sein kann, kann man das Listing einschränken und nur bestimmte Teile im Listing anzeigen lassen. Dadurch wird das übersichtlicher und man muss sich nur mit dem Teil beschäftigen, an dem man gerade arbeitet. Es gibt zwei Kommandos, die das Listing ein- oder ausschalten:

Code: Alles auswählen

;LISTON
;LISTOFF
Wichtig ist das Semikolon am Anfang und dass dieser "Kommentar" immer am Anfang einer Zeile steht. Die Default Einstellung ist LISTON, also Listing aktiviert. Im Beispiel oben habe ich die Kommentare so gesetzt, dass nur der eigentliche BASIC Teil im Listing erscheint. Das sieht dann so aus:

Code: Alles auswählen

                                        format binary as "p"
                                        ;LISTON
                                        
                                        AUTORUN:
0074: [407D] 00 01 06 00 EA                     REM 1,asmcode_st,asmcode_end
                                        
                                        asmcode_st:
                                        ; ##################################################
                                                ; place own assembler code here
                                        ; ##################################################
                                        
0079: [4082] 01 55 00                           LD      BC,$55
007C: [4085] C9                                 RET
                                        
                                        ; ##################################################
                                                ; end of section for own assembler code
                                        ; ##################################################
                                        asmcode_end:
                                        
007D: [4086] 76                                 db      NEWLINE
                                        
                                        LINE2:  ; this is a static PRINT USR 16514 command
007E: [4087] 00 02 0E 00 F5 D4                  db      0,2,$0E,0,$F5,$D4
0084: [408D] 1D 22 21 1D 20                     dbzx    '16514'
0089: [4092] 7E 8F 01 04 00 00 76               db      $7E,$8F,1,4,0,0,NEWLINE
                                        
In der ganz linken Spalte steht die Adresse im Programm File <QUELLE>.p - für das Fall dass man vielleicht gerne mal was patcht. :mrgreen:
Es fängt oben im Listing erst mit Offset 74 an, davor findet man die Variablen des ZX81 die aber für das Listing ausgeblendet wurden.
Interessanter und wichtiger ist eigentlich die zweite Spalte, die Zieladresse. Das ist die Adresse, auf der der Code später läuft. So ein Assembler Code in der ersten REM Zeile wird überlicherweise mit RAND USR 16514 oder PRINT USR 16514 aufgerufen, gibt aber noch weitere Möglichkeiten. Die dezimale Adresse 16514 entspricht hexadezimal $4082, das lässt sich im Beispiel Listing recht gut verfolgen. In der dritten Spalte steht der erzeugte Code an dieser Adresse, ebenfalls hexadezimal. In der vierten Spalte erscheint die Zeile so wie sie im Listing vorkommt.

Hier kann man auch recht gut sehen, dass der Z80 im Little-Endian Format arbeitet, also bei Zahlen größer als ein Byte wird das niederwertige Byte zuerst gespeichert.
Man kann das Listing Programm auch separat aufrufen, dazu wird das (aktuelle) Symbol File <QUELLE>.fas benötigt und das Programm LISTZ80.EXE.
So wird das im Grunde auch intern von der IDE verarbeitet.

So das wars erstmal mit den Basics zur IDE, weiter mit entsprechend nützlichen Direktiven und Erläuterung des Beispielprogramms gehts erst nach Weihnachten.
Ihr sollte ja auch erstmal üben. :mrgreen:
Wer seinen Computer ehrt, lebt nicht verkehrt.

Benutzeravatar
PokeMon
User
Beiträge: 4490
Registriert: 31.08.2011, 23:41

Addressing Spaces I

Beitrag von PokeMon » 30.12.2012, 02:36

Nachdem die Grundbedienung der IDE nun klar sein dürfte (also wie man kompiliert resp. assembliert, Listings erzeugt und Programme überträgt) geht es nun etwas mehr in die Assembler Grundsteuerung. Dabei gibt es viele Gemeinsamkeiten der Assembler, aber auch teilweise spezifische Besonderheiten.

Es geht hier um sog. Address Spaces, also Definition von Speicherbereichen. Der Z80 ist da etwas einfacher gehalten, wenngleich der flatassembler auch verschiedene Formate wie ELF, PE oder COFF unterstützt, die möglicherweise sogar mit der IDE funktionieren. Das möge halt der eine oder andere Experte für sich herausfinden. Normalerweise werden Programme für den ZX81 im Flat Format erstellt, also ein binäres Ausgabefile in dem das Programm sequentiell reingeschrieben wird. Es gibt also nur einen Anfang und ein Ende.

Das Format wird mit der ersten Direktive im Beispielprogramm ZX81DEMO.ASM festgelegt:

Code: Alles auswählen

format binary as "p"
Binary ist die Default Einstellung und im Grunde obsolet allerding erstellt die IDE dann standardmäßig Ausgabedateien mit Namen <QUELLE>.BIN.
Durch den Zusatz as "p" kann man die gewünschte Extension angeben, die nur als Option zur Direktive format verfügbar ist.
Direktiven sind übrigens alle Kommandos zur Steuerung des Assemblers, die keine Z80 Befehle sind. :wink:
Im Falle des ZX80 könnte da zum Beispiel auch stehen:

Code: Alles auswählen

format binary as "o"
Nur braucht man dann aber auch andere Include Dateien aufgrund unterschiedliche Speicherstrukturen. Dazu später mehr, der Paul arbeitet noch dran. :D
Also nur mal als generelles Beispiel.

Ein solches p-File für den ZX81 wird bekanntlich vom Loader (LOAD "" Kommando) an die Adresse 16393 dezimal oder $4009 hexadezimal geladen.
Dort sind Variablen definiert und danach folgt direkt der BASIC Bereich. Die genauen Adressen sind im ZX81 BASIC Programming Handbuch zu finden, aber auch in den Include Dateien für den ZX81, hier ZX81VARS.INC.

Code: Alles auswählen

                                                ORG     SAVE_BEGIN
                                        
0000: [4009] 00                                 VERSN   db 0            ; version, 0=ZX81 BASIC
0001: [400A] 00 00                              E_PPC   dw 0            ; number of current line with cursor (in listing)
0003: [400C] 99 40                              D_FILE  dw DFILE_ADDR   ; begin of D_FILE (display file) in memory
0005: [400E] 9A 40                              DF_CC   dw DFILE_ADDR+1 ; current cursor position in D_FILE (used for print routine)
0007: [4010] CC 40                              VARS    dw VARS_ADDR    ; begin of variable section in memory
0009: [4012] 00 00                              DEST    dw 0            ; address of variable in assignment
000B: [4014] CD 40                              E_LINE  dw WORKSPACE    ; address of workspace
Also das erste Byte im Programm File wird an Adresse 4009 geladen und das muss man dem Assembler mitteilen. Die Definition der sog. Laufadresse ist eigentlich fast immer zwingend erforderlich. Wenn ich im Programm auf die Variable D_FILE mit der Anweisung

Code: Alles auswählen

LD BC,D_FILE
referenzieren will würde ich ohne die Laufadresse die Adresse 0003 erhalten, weil es das 4. Byte im Ausgabefile ist, bei der späteren Ausführung auf dem ZX81 dann aber auf eine Adresse im ROM verweisen würde ($0003). Tatsächlich befindet sich die Variable D_FILE aber an Adresse $400C oder dezimal 16396, wie dem Handbuch zu entnehmen ist.

Die Anweisung ORG legt die aktuelle Laufadresse für alle nachfolgenden Instruktionen und Datendefinition fest, unabhängig an welcher Stelle der Programmcode im Ausgabefile steht:

Code: Alles auswählen

ORG     SAVE_BEGIN
SAVE_BEGIN ist wiederum eine Art Variable, die an anderer Stelle als Label definiert ist. Könnte aber auch ein direkter Wert sein wie $4009 oder 16393:

Code: Alles auswählen

ORG     $4009
ORG     16393
ORG kann man im Grunde beliebig oft im Programm verwenden. Es macht aber nur dann Sinn, wenn man den folgenden Programmteil zum Beispiel durch das Assemblerprogramm selbst an eine andere Laufadresse kopiert. Dann muss man sich im Programm aber auch selbst darum kümmern, den Programmteil dorthin zu verfrachten. Der ZX81 macht das von Haus aus natürlich nicht.

ORG ist vom englischen Begriff Origin (=Ursprung) abgeleitet und macht nichts anderes, als den Ursprung oder Referenzpunkt festzulegen. Es ist nicht die Aufgabe von ORG irgendwelche notwendigen Füllbytes unterzubringen. Dafür gibt es andere Direktiven.

Auf Wunsch habe ich auch die Direktiven PHASE und DEPHASE integriert, die ebenfalls die Funktion von ORG haben, jedoch den Adresskontext nur temporär wechseln (mit PHASE) und in den alten Kontext wieder zurückspringen können (mit DEPHASE). Ein Beispiel dazu wäre das folgende kleine Programm:

Code: Alles auswählen

                                        format binary as "p"
                                        
                                                org     $4000
                                        
0000: [4000] 21 00 20                           LD      HL,reloc_start
0003: [4003] 11 00 20                           LD      DE,$2000
0006: [4006] 01 03 00                           LD      BC,reloc_end-reloc_start
0009: [4009] ED B0                              LDIR
000B: [400B] C3 11 40                           JP      weiter
                                        
                                                PHASE   $2000
                                        reloc_start:
000E: [2000] 78                                 LD      A,B
000F: [2001] 81                                 ADD     A,C
0010: [2002] C9                                 RET
                                        reloc_end:
                                                DEPHASE
                                        
                                        weiter:
0011: [4011] 3C                                 INC     A
0012: [4012] 05                                 DEC     B
Im Programm wird die Adresse für ein Relocation Teil geändert, der ein Programmteil auf Adresse $2000 kopiert und anschließend im Programm nach DEPHASE wieder auf Adresse $4011 weitermacht. Generell ist es eher empfehlenswert solche Programmteile gesondert zu definieren an einer anderen Stelle (z.B. am Ende des Hauptprogramms und nicht mittendrin). Möglicherweise gibt es Gründe dafür und ansonsten wüßte ich nicht wozu man PHASE und DEPHASE wirklich brauchen kann. Aber sei es drum. Intern macht die ORG-Erweiterung sowieso nichts anderes als sich die aktuelle Adresse zu merken, die neu erzeugten Bytes zu kalkulieren und dann den Adressbereich wieder zurückzuschalten. PHASE und DEPHASE sind sozusagen nur spezielle ORG Direktiven bzw. erzeugen die notwendigen ORG Statements intern. Es ist vorgesehen, PHASE bei Bedarf auch zu verschachteln, bis zu 32 Stufen. DEPHASE schaltet dann jeweils auf den zurückliegenden Kontext zurück.

Hier mal ein Beispiel was man mit PHASE und DEPHASE anstellen kann - wenns Spaß macht. :mrgreen:

Code: Alles auswählen

                                        format binary as "p"
                                        
                                        lab1:
0000: [0000] 41 42 43 44 45 46 47 78            db 'ABCDEFGx'
                                        lab2:
0008: [0008] 26 27 28 29 2A 2B 2C               dbzx 'ABCDEFG'
                                        
                                                org $4000
                                        
                                        label1:
000F: [4000] 03                                 INC BC
0010: [4001] C3 00 40                           JP label1
0013: [4004] FF FF                              dw -1
                                        label1e:
                                        
                                                phase $5000+2*10+label1e-label1
                                        label2:
0015: [501A] 13                                 INC DE
0016: [501B] C3 1A 50                           JP label2
                                        label2e:
                                                phase $6000
0019: [6000] 00                                 db 0
                                                phase $7000
001A: [7000] 00 00                              dw 0
                                                phase $8000
001C: [8000] 00 00 00 00                        dd 0
                                                phase $9000
0020: [9000] 00 00 00 00 00 00 00 00            dq 0
                                        
                                                dephase
0028: [800C] 00 00 00 00 00 00 00 00            dq 0
                                                dephase
0030: [7016] 00 00 00 00                        dd 0
                                                dephase
0034: [601B] 00 00                              dw 0
                                                dephase
0036: [503B] 00                                 db 0
                                        label3:
0037: [503C] 23                                 INC HL
0038: [503D] C3 3C 50                           JP label3
                                        label3e:
Es gibt auch noch virtuelle Adress Spaces, dazu später hier im Tutorial mehr. Das ist eher etwas für Fortgeschrittene.
Wer die Funktion von ORG wirklich verstanden hat, hat schon mal viel gewonnen bei der Assemblerprogrammierung. 8)
Wer seinen Computer ehrt, lebt nicht verkehrt.

Benutzeravatar
PokeMon
User
Beiträge: 4490
Registriert: 31.08.2011, 23:41

Datendefinitionen I

Beitrag von PokeMon » 31.12.2012, 17:58

So kurz bevor die Knallerei losgeht noch eine weitere Einführung. :mrgreen:

Neben Assembler Anweisung sind häufig auch Datenstrukturen in Programmen zu definieren. Dafür bietet der Flatassembler alias ZX81-IDE reichlich Möglichkeiten. Viele sind für einen 8 Bit Prozessor allerdings auch wenig brauchbar, was sollen da Definitionen von double words (32 bit) oder gar quad words (64 bit). Es sind eigentlich alle Datentypen da, meistens wird man mit Byte oder Word auskommen.

Code: Alles auswählen

db = 8bit (define byte)
dw = 16bit (define word)
Schauen wir uns dazu mal die Variablendefinitionen aus dem ZX81DEMO.ASM an (Listing):

Code: Alles auswählen

0011: [401A] CF 40                              STKBOT  dw WORKSPACE    ; stack botton (top-down)
0013: [401C] CF 40                              STKEND  dw WORKSPACE    ; end of stack (top)
0015: [401E] 00                                 BERG    db 0            ; calculators b register, possibly write error, should be BREG :-)
0016: [401F] 5D 40                              MEM     dw MEMBOT       ; address for calculator's memory
0018: [4021] 00                                 UNUSED1 db 0            ; unused variable / space
0019: [4022] 02                                 DF_SZ   db 2            ; no. of lines in lower part of screen including 1 blank line
001A: [4023] 00 00                              S_TOP   dw 0            ; no. of top program line in automatic listings
001C: [4025] 00 00                              LAST_K  dw 0            ; last key pressed
001E: [4027] 00                                 DEBOUNCE db 0           ; debounce status of keyboard
001F: [4028] 37                                 MARGIN  db PAL          ; margin value, could be used PAL or NTSC
Man kann mit db oder dw auch mehrere Werte in einer Liste angeben, dann werden entsprechende Byte oder Worte erzeugt:

Code: Alles auswählen

007E: [4087] 01 02 03                           db      1,2,3
0081: [408A] 04 00 05 00 06 00                  dw      4,5,6
Auch negative Werte sind möglich und werden entsprechend der Datenbreite erzeugt, -1 entspricht $FE und -4 entspricht $FFFC

Code: Alles auswählen

0087: [4090] FF FE FD                           db      -1,-2,-3
008A: [4093] FC FF FB FF FA FF                  dw      -4,-5,-6
Neben dezimalen Werten lassen sich auch hexadezimale Werte im C oder Pascal Stil angeben, oktale oder auch binäre Angaben.
Nachfolgend verschiedene Schreibweisen für das gleiche "Datum" 255 oder $FF.

Code: Alles auswählen

0090: [4099] FF                                 db      255
0091: [409A] FF                                 db      $ff
0092: [409B] FF                                 db      0ffh
0093: [409C] FF                                 db      0xff
0094: [409D] FF                                 db      377o
0095: [409E] FF                                 db      11111111b
0096: [409F] FF                                 db      %11111111
Wichtig ist immer, dass am Anfang eine Ziffer oder alternativ das $ Zeichen kommt. Sofern man die Schreibweise mit h am Ende bevorzugt ist das wichtig. 10h wird erkannt, aah allerdings nicht, in dem Fall müsste 0aah geschrieben werden. Oktale Schreibweisen wird man selten finden, die binären Schreibweisen kommen aber zum Beispiel im ZX81 ROM Listing vor in den Charset Tabellen, zumindest die letztere Schreibweise mit dem alternativen vorangestellten % Zeichen.
Siehe auch http://www.wearmouth.demon.co.uk/zx81.htm
Wer öfters umrechnen muss, findet in der ZX81-IDE unter Help auch einen Calculator, der sofort alle Eingaben in hexadezimal, oktal und binär umrechnet.
Sollte man ggf. mal ausprobieren. :wink:

Man kann mit db auch einzelne Character angeben, z.B. bei eine Abfrage in einem Programm oder auch ganze Zeichenketten:

Code: Alles auswählen

0097: [40A0] 2E                                 db      '.'
0098: [40A1] 29                                 db      ')'
0099: [40A2] 41 42 43 44 45 46 47               db      'ABCDEFG'
00A0: [40A9] 1B                                 dbzx    '.'
00A1: [40AA] 11                                 dbzx    ')'
00A2: [40AB] 26 27 28 29 2A 2B 2C               dbzx    'ABCDEFG'
db definiert dabei Character standardmäßig im ASCII Zeichensatz, dbzx bezieht eine Codetabelle mit ein und wandelt die Zeichen entsprechend in den ZX Zeichensatz um. Damit kann man dann im Klartext programmieren, so dass der Zeddy das dann auch darstellen kann.

Um im Programm auch die Zeichensatzwandlung zu benutzen, habe ich ein Pseudokommando mit doppelten Kommas integriert. Im ersten Fall wird das Register A mit dem ASCII Zeichen '.' geladen, im zweiten Fall mit dem ZX-Zeichen '.'

Code: Alles auswählen

00A9: [40B2] 3E 2E                              LD      A,'.'
00AB: [40B4] 3E 1B                              LD      A,,'.'
Es ist auch eine gemischte Schreibweise mit Zahlen, Characters und Zeichenketten möglich:

Code: Alles auswählen

00A9: [40B2] 45 72 72 6F 72 20 6D 65            db      'Error message',0dh,0adh
             73 73 61 67 65 0D AD       
00B8: [40C1] 27 31 2E 33 29 39 2A 3D            dbzx    'Blindtext',0
             39 00                      
Hier wird eine Zeichenkette mit einem zugehörigen Abschlusszeichen dargestellt, z.B. ein Null Zeichen oder ein Zeilenumbruch in ASCII (CR-LF).

Soviel erstmal Teil 1 zum Thema Datendefinitionen, morgen gehts dann weiter mit Teil 2 und Definition von Datenblöcken.
Wer seinen Computer ehrt, lebt nicht verkehrt.

Benutzeravatar
PokeMon
User
Beiträge: 4490
Registriert: 31.08.2011, 23:41

Datendefinitionen II (Blocks & more)

Beitrag von PokeMon » 01.01.2013, 19:34

So weiter geht es mit Defintion von Daten als Blöcke. Dazu gibt es mehrere Möglichkeiten. Zunächst kann man Platz einfach reservieren mit "reserve bytes" analog zu "data bytes". Diese Daten werden im Flatfile Format mit "0" initialisiert, bei anderen Formaten werden lediglich Speicherbereiche definiert, die erst beim Starten des Programms belegt bzw. angefordert werden. So kann man z.B. einen 32kb Block mit reserve bytes definieren, ohne dass dieser im Ausgabefile erscheint, das eigentliche Programm eben nicht unnötig aufbläht. Der Nachteil ist, dass diese Blöcke ggf. manuell zu initialisieren sind oder ansonsten Zufallsdaten enthalten (was ggf. gerade so vorher im Speicher war).

Aber im "binary" format interessiert das natürlich nicht und es wird im Programm der entsprechende Platz belegt und mit "0" initialisiert.

Code: Alles auswählen

007E: [4087] 01 55 00                           LD      BC,$55
0081: [408A] C9                                 RET
0082: [408B] 00 00 00 00 00 00 00 00            rb      16
             00 00 00 00 00 00 00 00    
0092: [409B] 00 00 00 00 00 00 00 00            rw      16
             00 00 00 00 00 00 00 00    
             00 00 00 00 00 00 00 00    
             00 00 00 00 00 00 00 00    
Hier werden einmal 16 byte (rb) und einmal 16 words (=32 bytes, rw) am Ende des Assemblerprogramms als Datenbereiche definiert.

Es gibt noch eine weitere Methode, wenn man die Daten anders als mit 0 vorinitialisieren möchte:

Code: Alles auswählen

00B2: [40BB] 76 76 76 76 76 76 76 76            db      25 dup(NEWLINE)
             76 76 76 76 76 76 76 76    
             76 76 76 76 76 76 76 76    
             76                         
Hier haben wir z.B. ein sog. collapsed D_FILE initialisiert (25 Newline).

Man kann auch ganze Byte Sequenzen initialisieren:

Code: Alles auswählen

00CB: [40D4] 01 02 03 04 05 01 02 03            db      5 dup (1,2,3,4,5)
             04 05 01 02 03 04 05 01    
             02 03 04 05 01 02 03 04    
             05                         
00E4: [40ED] 3F 3D 24 1D 3F 3D 24 1D            dbzx    4 dup ('ZX81')
             3F 3D 24 1D 3F 3D 24 1D    
00F4: [40FD] 00 40 00 40 00 40 00 40            db      $16 dup (0,$40)
             00 40 00 40 00 40 00 40    
             00 40 00 40 00 40 00 40    
             00 40 00 40 00 40 00 40    
             00 40 00 40 00 40 00 40    
             00 40 00 40                
0120: [4129] 00 00 00 00 00 00 00 00            db      32 dup(0),NEWLINE
             00 00 00 00 00 00 00 00    
             00 00 00 00 00 00 00 00    
             00 00 00 00 00 00 00 00    
             76                         
Das erste Beispiel erzeugt 5 mal die Bytefolge 1,2,3,4,5.
Das zweite Beispiel erzeugt 4 mal nacheinander den Text "ZX81".
Das dritte Beispiel initialisiert einen Datenbereich mit einem Word, Adresse $4000.
Das vierte Beispiel erzeugt eine Zeile im Bildschirmspeicher D_FILE (32 Leerzeichen plus NEWLINE).

Man kann auch Daten aus externen Dateien einbinden, dazu gibt es die "file" Direktive:

Code: Alles auswählen

0141: [414A] 21 00 20 11 00 20 01 03            file    '1.p'
             00 ED B0 C3 11 40 78 81    
             C9 3C 05                   
0154: [415D] 40 3C 28 28 ED 58 F2 4A            file    'FASTLOAD.BIN':$40,32
             40 FE 08 3F CB 12 30 EB    
             A0 28 06 23 72 04 50 18    
             E2 05 AE 7A 16 01 E2 6F    
Das erste Beispiel liest die vollständige Datei "1.p" an die aktuelle Stelle im Sourcecode ein.
Das zweite Beispiel liest aus der Datei "FASTLOAD.BIN" 32 Bytes ab dem Offset $40 innerhalb der Datei.

Alignment. Das ist auch so ein Thema. :wink: Häufig möchte man Datenbereiche in speziellen Portionen definieren und am Ende ggf. notwendige Füllbytes unterbringen. Typische alignments sind je nach Arbeitsweise des Prozessors 2 wenn z.B. technisch ein Prozessor immer in Wordgröße liest also z.B. 16 Bit Datenbus hat. Wenn nun ein Wert in ein Register zu schreiben ist (Speichervariable) und diese auf einer ungeraden Adresse liegt ist das ungünstig, weil so immer 2 Lesezyklen ausgelöst werden. Einmal die Adresse-1 und dort nur das höherwertige Byte und einmal die Adresse+1 und dort das niederwertige Byte. Durch Alignment von 2 kann man festlegen, dass die nachfolgende Definition (meist eine oder mehrere Variablen oder ein Speicherbereich) an einer geraden Adresse anfangen und ggf. ein Füllbyte (in der Regel ein NOP) eingefügt wird.

Bei einem 32 Bit Prozessor würde man idealerweise ein Alignment von 4 wählen. Gängige Alignments sind auch Paragraphen (=16 Byte), auch aus historischen Gründen als man beim x86 mit Segment und Offset gearbeitet hat. Aber auch moderne 64 Bit Prozessoren verlangen mittlerweile wieder explizite Alignments und können andernfalls beim Zugriff auf nicht "alignte" Daten sogar Exceptions auslösen.

Nun mal 2 Beispiele für den Z80 Prozessor:

Code: Alles auswählen

0154: [415D] 40 3C 28 28 ED 58 F2 4A            file    'FASTLOAD.BIN':$40,32
             40 FE 08 3F CB 12 30 EB    
             A0 28 06 23 72 04 50 18    
             E2 05 AE 7A 16 01 E2 6F    
0174: [417D] 00 00 00                           align   $10
0177: [4180] 00 00 00 00 00 00 00 00            align   $100
             00 00 00 00 00 00 00 00    
             00 00 00 00 00 00 00 00    
             00 00 00 00 00 00 00 00    
             00 00 00 00 00 00 00 00    
             00 00 00 00 00 00 00 00    
             00 00 00 00 00 00 00 00    
             00 00 00 00 00 00 00 00    
             00 00 00 00 00 00 00 00    
             00 00 00 00 00 00 00 00    
             00 00 00 00 00 00 00 00    
             00 00 00 00 00 00 00 00    
             00 00 00 00 00 00 00 00    
             00 00 00 00 00 00 00 00    
             00 00 00 00 00 00 00 00    
             00 00 00 00 00 00 00 00    

Als erstes wird ein 16-Byte Alignment definiert mit align $10. Das Programm befindet sich aktuell an Adresse $417D und der nächste "Paragraph" wäre $4180, also werden durch das align die erforderlichen 3 Füllbytes* (NOP's) eingefügt. Danach wird als Beispiel ein align auf $100 ausgelöst, also um einen 256 Byte Block zu füllen. Das Programm befindet sich an Adresse $4180 und somit müssen $80 (128) Füllbytes generiert werden um dann auf Adresse $4200 zu kommen.

Ein weiteres Beispiel:

Code: Alles auswählen

                                                PHASE   0
01F7: [0000] C3 20 42                           JP      somewhere
01FA: [0003] 00 00 00 00 00                     align   8
01FF: [0008] C3 20 42                           JP      somewhere
0202: [000B] 00 00 00 00 00                     align   8
0207: [0010] C3 20 42                           JP      somewhere
020A: [0013] 00 00 00 00 00                     align   8
020F: [0018] C3 20 42                           JP      somewhere
0212: [001B] 00 00 00 00 00                     align   8
                                                DEPHASE
Hier wird für den Z80 Prozessor der Bereich der Restart Vektoren (RST0, RST8, usw) mit Align gefüllt, jede neue Restart Vektor fängt alle 8 Byte an.

Gerne genutzt wird das Alignment auch um Sektoren aufzufüllen (512 Byte).

* Der Füllwert von Align wurde gerade von mir geändert von $90 auf 0. $90 ist der NOP für den x86 Prozessor vom flatassembler. Es wird also im nächsten veröffentlichten Release erst mit 0 initialisiert. Update kommt in Kürze.
Wer seinen Computer ehrt, lebt nicht verkehrt.

Benutzeravatar
PokeMon
User
Beiträge: 4490
Registriert: 31.08.2011, 23:41

Variablen und Konstanten

Beitrag von PokeMon » 02.01.2013, 18:59

Häufig benötigt die Verarbeitung von Code auch Variablen und Konstanten. Mit Variablen sind dabei keine Speicherplätze im eigentlichen Programm gemeint sondern Variablen, die den Ablauf des Assemblers beeinflussen sollen. Sie sind nicht ganz so effektiv wie Makros aber dafür einfacher zu handhaben. Mit Konstanten sind hier symbolische Konstanten gemeint und nicht zugewiesene Werte oder Zeichenketten. Beim flatassembler sind dabei die Konstanten im Grunde auch Variablen und nur aus historischen Gründen "symbolic constants" genannt.

Die bekannteste Form sind EQU (equates) und DEFINE's.
So taucht z.B. im Original ROM Listing von Wearmouth folgende Definition auf:

Code: Alles auswählen

; $3F - Character: 'Z'          CHR$(63)

        DEFB    %00000000
        DEFB    %01111110
        DEFB    %00000100
        DEFB    %00001000
        DEFB    %00010000
        DEFB    %00100000
        DEFB    %01111110
        DEFB    %00000000

.END                                ;TASM assembler instruction.
DEFB wird z.B. von Borland's Turbo Assembler TASM nicht verstanden. Dort muss es .BYTE heißen oder .WORD für DEFW.
Wenn man eine SOURCE mit einem anderen Assembler verarbeiten will, gibt man dann im Kopf der Quelle folgende Definition ein:

Code: Alles auswählen

#define DEFB .BYTE      ; TASM cross-assembler definitions
#define DEFW .WORD
#define EQU  .EQU
Das veranlasst den Assembler überall den Begriff DEFB gegen .BYTE auszutauschen und zwar bevor der eigentlich Assemblierungsvorgang beginnt. Diese Phase nennt man auch Präprozessor (oder englisch preprocessor) weil sie dem Assembler vorgeschaltet ist. Jetzt ist das aber ein Beispiel zugeschnitten auf den TASM von Borland, bei der IDE (flatassembler) muss eine solche Definition so aussehen:

Code: Alles auswählen

define DEFB db
define DEFW dw
define EQU  .EQU
define .END
Zunächst kann der Assembler mit der Raute am Anfang #define nichts anfangen, das Statement heißt define ohne Raute. Dann wird auch das Ende Symbol für den TASM (.END) gelöscht durch die "Leerdefinition" in der letzten Zeile. Der flatassembler braucht keine Definition des Programmende, wenn die Source zu Ende ist ist gleichzeitig auch das Programm zu Ende. :wink:

Der Flatassembler hat noch eine weitere Direktive mit dem Namen fix zur Syntaxanpassung des Preprocessors. Diese ist mit EQU nicht möglich. Man sollte dabei sehr sparsam mit der Verwendung von fix umgehen und möglichst nur bei Namenskollisionen anwenden, die größere Bearbeitung des Sourcecodes erfordern würden. Hier ein Beispiel:

Code: Alles auswählen

0082: [408B] 00 00 00 00 00 00 00 00            rb      16
             00 00 00 00 00 00 00 00    
                                                EQT     fix EQU
                                                DEFB    EQT db
0092: [409B] 01                                 DEFB    1
Hier wird die Präprozessor Direktive EQU in EQT umgetauft.
define ist intern genauso wie EQU, lediglich die Syntax ist unterschiedlich. define erwartet als Parameter den alten und dann den neuen Begriff, EQU steht in der Mitte von beiden. Die Verwendung ist eher eine persönliche Geschmackssache des Programmierers.

Code: Alles auswählen

                                                define  DEFB db
0092: [409B] 41                                 DEFB    'A'
                                                DEFB    EQU dbzx
0093: [409C] 26                                 DEFB    'A'
Das Beispiel oben zeigt auch, dass die "symbolische Konstante" DEFB nicht wirklich eine Konstante ist sondern innerhalb des Präprozessors auch als Variable benutzt werden kann. Am Anfang verwendet DEFB z.B. den ASCII Zeichensatz, später dann den ZX Zeichensatz (durch Umdefinition). Der wesentliche Unterschied zu Assembler Variablen gegenüber dem Preprocessor ist die fehlende Vorwärtsreferenzierung. Die entsprechende Konstante muss zwingend vor der Verwendung richtig gesetzt sein und es gibt im Gegensatz zum Assembler auch nicht mehrere Durchläufe (resolving) sondern der gesamte Quellcode wird einmal sequentiell von Anfang bis Ende durchlaufen und daher sollten in dieser Phase nach Möglichkeit mit symbolischen Konstanten nur notwendige Korrekturen am Quellcode vorgenommen werden. Der Präprozessor verarbeitet auch Makros, dazu jedoch später mal ein Beitrag hier im Thread.

Das nächste Beispiel zeigt die Verwendung von Assembler Variablen.

Code: Alles auswählen

0094: [409D] 28                                 db      var1
0095: [409E] 00                                 db      0
                                                var1=$28
Hier wird die Variable var1 vor der Definition verwendet bzw. erst nach der Verwendung definiert. Das ist in Assembler Programmen möglich und insbesondere bei jumps (JR,JP) oder auch bei calls (CALL) unumgänglich. Der Assembler macht dann mehrere Durchläufe um Abhängigkeiten aufzulösen, das obige Beispiel erzeugt schon mal von Haus aus mindestens 2 Durchläufe (passes) wie man auch dem Kontrollfenster am Ende der Assemblierung entnehmen kann.

Es gibt auch Fälle wo die Auflösung des Codes nicht möglich ist und der Assembler mit der Fehlermeldung "Code could not be generated" nach etlichen Versuchen abbricht. :mrgreen:

Code: Alles auswählen

var1=var2+1
var2=var1
Prinzipiell kann man auch die Variable umdefinieren (z.B. unter bestimmten Bedingungen, if-else Konstrukte):

Code: Alles auswählen

        db      var1
        db      0
        var1=$28
        db      var1
        var1=$29
Allerdings ist die Umdefinierung nur möglich wenn die Variable zuvor noch nicht verwendet wurde. Das obige Beispiel bringt eine Fehlermeldung "symbol 'var1' out of scope". Wenn die erste Zeile gelöscht wird, kann der Assembler jedoch mit der Umdefinition umgehen. Hier liegt das Problem in der Vorwärtsreferenzierung. Ein solche Umdefinition wäre mit EQU dagegen lösbar:

Code: Alles auswählen

                                                var1    EQU     $28
0094: [409D] 28                                 db      var1
                                                var1    EQU     $29
0095: [409E] 29                                 db      var1
 
Der Grund für die mögliche Auflösung liegt hier in der stringenten Bearbeitung des Quellcodes, da Vorwärtsreferenzierungen gar nicht möglich sind. Wenn man die unterschiedlichen Arbeitsweisen von Präprozessor und Assembler erstmal verstanden hat, wird der Einsatz von Zuweisungen wie EQU und "=" klarer. Das ist ein häufiges Anfängerproblem und als logisches Problem zu verstehen und tritt noch verstärkt beim Einsatz mit Makros auf. Die korrekte Programmierung von Makros ist sozusagen die Königsdisziplin beim Einsatz eines Assemblers. Dafür sind Makros extrem leistungsfähig in der Codegenerierung.

Allgemein sollte man EQU immer dann benutzen, wenn der Wert statisch ist und Variablen bei dynamischen Werten. Kann manchmal aber auch andersrum sinnvoll sein unter Berücksichtigung der Vorwärtsreferenzen. Hier ein typisches Beispiel für EQU im Assemblerprogramm:

Code: Alles auswählen

                                                NO      EQU     0
                                                YES     EQU     1
                                                NEWLINE EQU     $76     ; character for NEWLINE
                                                SPACE   EQU     0       ; character for SPACE
Hier werden tatsächlich Konstanten verwendet, also symbolische Namen für bestimmte Werte wie NEWLINE oder SPACE. Man erinnert sich später leichter an den Begriff NEWLINE als an dessen Wert (118 dezimal oder $76). Auf der anderen Seite wird dadurch Software auch portabler auf andere Systeme. So könnte man das für ein DOS oder WIN System auch recht einfach ändern:

Code: Alles auswählen

                                                NO      EQU     0
                                                YES     EQU     1
                                                NEWLINE EQU     $0D,$0A     ; character for NEWLINE
                                                SPACE   EQU     $20       ; character for SPACE
Wer seinen Computer ehrt, lebt nicht verkehrt.

Benutzeravatar
PokeMon
User
Beiträge: 4490
Registriert: 31.08.2011, 23:41

Labels oder Sprungmarken

Beitrag von PokeMon » 04.01.2013, 00:47

Neben den Variablen und Konstanten sind für ein Assembler Programm Labels (neudeutsch Sprungmarken :mrgreen: ) erforderlich. Labels werden auf der Assembler Ebene ausgewertet (der Präprozessor kennt keine Labels) und erlauben eine Vorwärtsreferenzierung. Das hatte ich schon mal angesprochen. D.h. man kann z.B. einen Sprung (JR, JP) oder den Aufruf einer Unterroutine (CALL) auf einen FOLGENDEN Code ermöglichen. Der Assembler ermittelt die Adresse dann in einem zweiten Durchlauf oder auch weiteren Durchläufen je nach Abhängigkeit. Ansonsten könnte man in einem Programm immer nur "rückwärts" springen. :wink:

Ein Label kann an beliebiger Stelle vor einem Befehl stehen oder einer Datendefinition stehen (dann kann man das quasi als Variable benutzen):

Code: Alles auswählen

                                        main_begin:
007E: [4087] 3A 8E 40                           LD      A,(var1)
0081: [408A] 21 8F 40                           LD      HL,var2
0084: [408D] C9                                 RET
0085: [408E] 10                         var1    db      $10
0086: [408F] 76                         var2    db      NEWLINE
                                        main_end:
0087: [4090] 09 00                              dw      main_end-main_begin
Die Marken "main_begin" und "main_end" sind einfache Sprungmarken.
var1 und var2 sind Labels zur Benutzung von Variablen.
Der Befehl "LD A,[var1]" greift auf die Variable zu und lädt den Inhalt in das Register A.
Der Befehl "LD HL,var2 lädt die Adresse von var2 in das Register HL (nicht den Inhalt der Variable).
Der Compiler prüft beim Assemblieren, ob die Operandengröße zulässig ist. So kann mit dem Zugriff von HL nicht direkt der Inhalt der Variable gelesen werden, da es dann eine 16 bit Variable sein müsste (var2 dw NEWLINE). Es ist aber der anschließende Zugriff mit LD A,(HL) auf den Inhalt von var2 (NEWLINE) möglich.

Die letzte Zeile im Beispiel ermittelt die Größe des assemblierten Blocks (und zugehörige Variablen) in Bytes (9). Man kann mit Labels beliebige numerische Ausdrücke bilden und rechnen was das Zeug hält. :wink:

Es gibt auch noch spezielle Assembler Variablen $ und $$:

Code: Alles auswählen

0089: [4092] 92 40                              dw      $
008B: [4094] 09 40                              dw      $$
008D: [4096] 8D 00                              dw      $-$$
$ ist die aktuelle Adresse (Runtime-Adresse) des Codes, hier $4092.
$$ ist der aktuelle Origin, $4009 - das ist die Adresse, aber der der ZX81 das Programm <QUELLE>.p in den Arbeitsspeicher lädt mit LOAD.
Mit $-$$ kann man die aktuelle Adresse im Ausgabefile ermitteln oder wieviele Bytes das Programm bis hierhin hat. :wink:

Man kann daher auch bei dem 1. Programmbeispiel auf das Label main_end verzichten, indem man die Berechnung mit $ (aktuelle Code Adresse) durchführt:

Code: Alles auswählen

                                        main_begin:
007E: [4087] 3A 8E 40                           LD      A,(var1)
0081: [408A] 21 8F 40                           LD      HL,var2
0084: [408D] C9                                 RET
0085: [408E] 10                         var1    db      $10
0086: [408F] 76                         var2    db      NEWLINE
0087: [4090] 09 00                              dw      $-main_begin
Die oben definierten Labels sind global definiert und dürfen nur einmal im gesamten Sourcecode vorkommen. Das ist manchmal unbequem, weil man sich ständig neue Variablennamen oder Sprungziele ausdenken muss. Hilfreicher sind hier lokale Variablen die mehrmals vorkommen dürfen und einem vorhergehenden globale Label zugeordnet werden:

Code: Alles auswählen

                                        main_begin:
007E: [4087] 21 8B 40                           LD      HL,.var1
0081: [408A] C9                                 RET
0082: [408B] 10                         .var1    db     $10
                                        main_2:
0083: [408C] 21 90 40                           LD      HL,.var1
0086: [408F] C9                                 RET
0087: [4090] 20                         .var1    db     $20
                                        main_3:
0088: [4091] 21 8B 40                           LD      HL,main_begin.var1
008B: [4094] C9                                 RET
008C: [4095] 30                         .var1   db      $30
Hier wird .var1 als lokale Variable definiert, einmal im Abschnitt main_begin und einmal im Abschnitt main_2 sowie im Abschnitt main_3.
Dabei greift LD HL,.var1 immer auf die "eigene" Variable zurück, also die zum globalen Label gehört (main_begin oder main_2).
Technisch gesehen bekommt dabei die Variable den Namen <GLOBAL>.<LOKAL>
Diese Eigenschaft macht sich Beispiel 3 zu nutze und lädt im Programmteil main_3 explizit die lokale Variable .var1 aus Abschnitt main_begin.
Denkbar sind auch Sprünge oder Unterprogramme so aufzurufen mit JP <GLOBAL>.<LOKAL> bzw. CALL <GLOBAL>.<LOKAL>

Als dritte wichtige Variante gibt es noch anonyme Labels für kurzfristige Sprünge im Programm. Man kennt das, man will mal eben ein paar Zeilen weiterspringen und das Durchnummerieren der Sprungziele macht Arbeit und ist fehlerträchtig. Hier helfen die anonymen Labels aus:

Code: Alles auswählen

                                        main_3:
0088: [4091] 21 8B 40                           LD      HL,main_begin.var1
008B: [4094] C9                                 RET
008C: [4095] 30                         .var1   db      $30
                                        
008D: [4096] 3A 95 40                           LD      A,(.var1)
0090: [4099] B0                                 OR      B
0091: [409A] 0E FE                              LD      C,$FE
0093: [409C] 28 02                              JR      Z,@f
0095: [409E] 0E F8                              LD      C,$F8
0097: [40A0] ED 78                      @@:     OUT     A,(C)
0099: [40A2] 16 08                              LD      D,8
009B: [40A4] ED 78                      @@:     IN      A,(C)
009D: [40A6] 17                                 RLA
009E: [40A7] CB 13                              RL      E
00A0: [40A9] 10 F9                              DJNZ    @b
Die anonymen Labels @@ dürfen beliebig oft vorkommen und @f referenziert das nächste anonyme Label (@@) und @b referenziert das letzte zurückliegende Label @@. Für solche kurzen Sprünge sind die anonymen Labels hilfreich und effizient, solange die Sprungweiten überschaubar sind. Eine kleine Gefahr lauert hier beim (testweise) Auskommentieren von Zeilen mit anonymen Labels da sich hier die Sprungziele verändern. Wenn die Zeile OUT A,(C) auskommentiert wird, geht der erste Sprung zu IN A,(C) oder beim Auskommentieren von IN A,(C) springt die Schleife (DJNZ) zum OUT Befehl.

Neben diesen offensichtlichen Fehlern können bei langen Sprungzielen Probleme auftauchen, wenn man vor einem anonymen Label ein weiteres anonymes Label ergänzt. Daher sollte der Sprungbereich optisch immer überschaubar und leicht nachvollziehbar sein, ansonten ist die Verwendung lokaler Variablen sinnvoller.

Es gibt noch eine weitere Variante Labels zu definieren mit der Direktive "LABEL":

Code: Alles auswählen

                                        file_block:
00A2: [40AB] 40 3C 28 28 ED 58 F2 4A    file    'FASTLOAD.BIN':$40,$40
             40 FE 08 3F CB 12 30 EB    
             A0 28 06 23 72 04 50 18    
             E2 05 AE 7A 16 01 E2 6F    
             40 BA 28 D7 18 03 BA 20    
             D2 C3 13 04 ED 79 01 00    
             00 C3 07 02 00 01 04 00    
             EA 18 B2 76 00 00 10 00    
                                        
                                        label   adr1 byte at file_block+12
                                        label   adr2 word at file_block+24
00E2: [40EB] 3A B7 40                           LD   A,(adr1)
00E5: [40EE] 2A C3 40                           LD   HL,(adr2)
Hier kann man z.B. Labels einem (vordefinierten) Speicherbereich (auch nachträglich) zuordnen. Im Beispiel lesen wir einen Datenblock aus einem externen File (FASTLOAD.BIN, siehe auch unter Datendefinion Blöcke) und wollen Labels speziellen Offsets zuordnen. adr1 soll ein Label für das Offset an Adresse 12 sein, adr2 Offset für Adresse 24 relativ bezogen auf die eingelesene Datei.
Wer seinen Computer ehrt, lebt nicht verkehrt.

Benutzeravatar
PokeMon
User
Beiträge: 4490
Registriert: 31.08.2011, 23:41

Media Player

Beitrag von PokeMon » 17.01.2013, 22:00

Die neueste Version der ZX81-IDE (1.71.01b.Z80) kann hier downgeloaded werden:
viewtopic.php?f=2&t=632&start=25

Aus verschiedenen Gründen habe ich eine Alternative zur bisherigen Übertragung mit der Windows-Funktion PlaySound gesucht. Diese läuft nur im Hintergrund und hat 2 Nachteile. Erstens kann man sie nicht so einfach stoppen wenn man sie mal aus Versehen gestartet hat oder der ZX81 noch nicht bereit war. Zweitens sieht man überhaupt nicht, wie lange die Übertragung insbesondere bei großen Dateien ggf. ohne Fastmode dauert. Selbst wollte ich keinen Player schreiben oder gar eine Fortschrittsanzeige. In jedem Windows System ist eigentlich von Haus aus der Windows Media Player verfügbar und der ist dafür wunderbar geeignet. Generell ist die Software so ausgelegt, dass ein beliebiger Player gestartet werden kann. Die Zuordnung des Players (falls auf dem System mehrere verfügbar sind) erfolgt über das Dateisystem, in dem man eine WAV Datei anklickt, auf Eigenschaften geht und dann wie im Bild unten das betreffende Programm auswählt.
mediaplayer-auswahl.jpg
mediaplayer-auswahl.jpg (51.42 KiB) 15623 mal betrachtet
Theoretisch kann ein beliebiger Player ausgewählt werden, jedoch muss man sehr vorsichtig bei diversen Audio Optionen sein. Diese können das Signal auch wieder verhunzen. Keinen Erfolg hatte ich mit dem sonst recht beliebten VLC Media Player, der aber auch ein paar Dutzend Audio Optionen hat zur "Verbesserung" des Signals. Der Windows Media Player spielt das Audio File offenbar "plain" - so wie es sein soll. Also im Zweifel ausprobieren, am Besten zum Vergleich die Übertragung im Hintergrund sollte mit einem kleinen Testprogramm (z.B. ZX81DEMO.ASM) einwandfrei laufen und dann den Media Player austesten. Ausgewählt werden die Optionen im Run Menü:
fasm_options.jpg
fasm_options.jpg (21.06 KiB) 15623 mal betrachtet
Hier eine weitere wichtige Information zum Verständnis. Der interne Player (PlaySound) arbeitet mit einem Buffer (im memory). Das Audiofile wird also einfach generiert und zum ZX81 übertragen - egal ob man "Auto Create WAV" ausgewählt hat oder nicht. Diese Option legt nur fest ob das WAV File automatisch auch im Dateisystem abgelegt wird, um es später auch ohne IDE zu verwenden oder weiterzugeben. Der Media Player braucht dagegen zwingend eine Datei zum Abspielen und kann mit dem Buffer nichts anfangen. Um hier Fehlbedienung zu vermeiden, wurden die Optionen Auto Create WAV und Use Media Player gekoppelt. Wenn man Use Media Player einschaltet, wird automatisch auch Auto Create WAV aktiviert. Wenn man Auto Create WAV deaktiviert, wird damit automatisch auch der Media Player deaktiviert.

Es gibt noch einen weiteren Punkt zu beachten, der mich Einiges an Nerven gekostet hat und einige Stunden Fehlersuche. Wenn man ein Security Paket mit Sandbox nutzt, sollte diese für das Programm FASMW-ZX.EXE bzw. das Directory unbedingt deaktiviert sein. Oder ggf. die betreffende WAV Datei vor dem Start des Programms FASMW-ZX.EXE gelöscht werden. Andernfalls kann die WAV Datei durch die Sandbox Funktion nicht vom Programm gelöscht oder überschrieben werden. Das neue Anlegen von Dateien scheint dagegen in der Sandbox erlaubt zu sein. Sofern man dagegen die interne Funktion mit PlaySound nutzt, funktioniert die richtige Übertragung auch innerhalb eine Sandbox, weil die Daten aus dem Hauptspeicher abgespielt werden. Nur das WAV File wird dann in der Sandbox nicht aktualisiert, es sei denn es wurde vorher gelöscht. So passiert bei mir mit COMODO und einem anderen genutzten Feature bei Änderung der IDE.

Die Übertragung (mit F9 das Quellprogramm oder ein fertiges Programm aus dem Filesystem mit F10) sieht dann in etwa so aus:
FASM_Screen2.jpg
FASM_Screen2.jpg (52.19 KiB) 15623 mal betrachtet
Wer seinen Computer ehrt, lebt nicht verkehrt.

Benutzeravatar
PokeMon
User
Beiträge: 4490
Registriert: 31.08.2011, 23:41

Media Player Teil 2

Beitrag von PokeMon » 17.01.2013, 22:08

So geht hier noch ein Stück weiter, weil leider in einem Beitrag nur 3 Bilder hochgeladen werden können.

Sollte der Media Player in der "big version" starten, kann man ihn durch einen Klick wie auf dem folgenden Bild gezeigt auch in der kompakten Größe anzeigen. Diese Einstellung behält der Windows Media Player dann für den externen Aufruf in Zukunft bei. Sofern man ihn vom Windows Menü startet, ist er automatisch immer groß. :wink:
mediaplayer-klein.jpg
mediaplayer-klein.jpg (54.53 KiB) 15623 mal betrachtet
So sieht er dann in klein aus: :mrgreen:
mediaplayer-mini.jpg
mediaplayer-mini.jpg (11.07 KiB) 15623 mal betrachtet
Wer seinen Computer ehrt, lebt nicht verkehrt.

Benutzeravatar
PokeMon
User
Beiträge: 4490
Registriert: 31.08.2011, 23:41

ZX Emulator EightyOne

Beitrag von PokeMon » 17.01.2013, 22:21

Aufgrund eines anderen Postings im englischen Nachbarforum kam mir neulich die Idee, dass man den Emulator EightyOne doch recht simpel mit der IDE verknüpfen kann. Das funktioniert eigentlich sensationell einfach auf Knopfdruck mit F8 aus der IDE. Das betreffende Programm (Assembler/BASIC Source) wird dabei aktuell kompiliert und automatisch mit dem Emulator gestartet. Dieser lädt das erzeugte .p File auch automatisch mit einem simulierten LOAD Kommando. :mrgreen: So kann man Änderungen am Programm noch einfacher testen. Nutzen ja sowieso die meisten hier den Emulator, insofern wird das vermutlich ein sehr nützliches Feature sein.
FASM_Screen1.jpg
FASM_Screen1.jpg (63.72 KiB) 15622 mal betrachtet
In der Minimalversion benötigt man nur die Datei EightyOne.EXE im IDE Verzeichnis und das ROM Verzeichnis mit den Dateien zx81.rom und zx81.rom.sym. Der Emulator startet dabei auf Nachfrage im englischen Forum automatisch mit der letzten Konfiguration (Speicher/Hardware/Module). Wenn man weitere Optionen braucht, kann man den Emulator auch komplett installieren. In dem ZIP der aktuellen Version ist das ROM Verzeichnis vorhanden, die Datei EightyOne.EXE jedoch aus Platzgründen (Beschränkung hier auf 1 MB) nicht. Der Emulator hat leider selbst 4,5 MB unkomprimiert bzw. 1,1 MB komprimiert.

Der Emulator kann hier downgeloaded werden:
http://www.chuntey.com/

Es ist auch möglich, die Dateien von der IDE in das Emulatorverzeichnis zu kopieren, sind ja nur eine Handvoll sowie das Verzeichnis SINCL-ZX mit den Include Dateien für den ZX81. Ggf. wäre es möglich auf Anfrage auch die Programme getrennt zu handhaben und den Pfad zum Emulator zu pflegen, habe ich mir aber jetzt erstmal gespart. Will erstmal sehen, wer die IDE überhaupt nutzt außer mir. Und ich selbst brauche es ehrlich gesagt nicht. :mrgreen:

Womit wir beim nächsten Thema wären - gerne kann hier jemand auch Anforderungen posten, was die IDE eigentlich sonst noch so alles können soll. Also außer bügeln, putzen und Kaffee kochen. :lol:
Zuletzt geändert von PokeMon am 25.01.2013, 18:29, insgesamt 1-mal geändert.
Wer seinen Computer ehrt, lebt nicht verkehrt.

Benutzeravatar
PokeMon
User
Beiträge: 4490
Registriert: 31.08.2011, 23:41

ZX81 BASIC, REM, _asm

Beitrag von PokeMon » 18.01.2013, 22:16

Kommen wir nun zur eigentlichen Erweiterung der IDE, die direkte Unterstützung von BASIC. Gut im ersten Moment kann sie noch nicht soooo viel, nur 2 Statements aber das sind für die Definitinion und den Start von Assemblerprogrammen auch erstmal die wichtigsten.

Es gibt mehrere Möglichkeiten, Assemblerprogramme (Maschinencode) auf dem ZX81 unterzubringen. Die einfachste und meistgenutzte Form sind in einer REM (Remark) Zeile.

Als zweites gibt es noch die Möglichkeit Programme in einem String unterzubringen. Nachteil ist, dass Strings in Variablen im Speicher wandern können und daher aufwändiger die Startadresse ermittelt werden muss. Strings lassen sich aber auch genauso gut im Programm selbst unterbringen als Parameter. Z.B. 40 PRINT "<Maschinencodeblock>". Die Ausführung des PRINT Befehls macht vermutlich keinen Sinn, läßt sich im Programm aber auch vermeiden. Es geht ja primär um die Definition.

Die dritte Variante ist das Herabsetzen der Speicherobergrenze (RAMTOP) um die benötigte Größe und ein kleines Programm zu schreiben, dass den Code dorthin kopiert. Der Vorteil dieser Variante ist, dass der Maschinencode resistent ist gegen NEW und dauerhaft zur Verfügung steht. Leider ist diese Methode nicht resistent gegen einen RESET. :wink:

Allen Varianten gemeinsam ist die schwierige Ermittlung der Startadresse des Maschinencodes. Der ist von anderen Faktoren abhängig und läßt sich manchmal einfacher (Variante mit RAMTOP), manchmal aber auch sehr schwierig ermitteln (REM Zeile mitten in einem Programm oder mitten im Variablenspeicher). Fix ist dagegen die Adresse der ersten REM Zeile im Programm, dort hinterlegter Code beginnt dann an Adresse 16514 oder $4082. Daher ist diese Variante auch die verbreitetste.

Schwierig ist auch entsprechend Platz für Maschinencode in einer REM Zeile zu reservieren, siehe dazu auch das diesen Beitrag/Thread von ali:
viewtopic.php?f=2&t=786&start=50#p7187
Mal völlig davon abgesehen, dass es noch schwieriger ist, anschließend den Maschinencode dorthin zu bekommen, ohne eine POKE Orgie zu veranstalten. Das alles kann die ZX-IDE völlig locker und entspannt inklusive Berechnung der Startadressen. :D

Betrachten wir daher erstmal die Standardverwendung von REM, nämlich um wirklich Kommentare in einem Programm zu machen. Die Syntax von REM ist dabei exakt wie bei der direkten Eingabe im ZX81. Einzige Ausnahme ist die Definition des Maschinencodes, die in der IDE in Klartextassembler erfolgt und nicht mit komischen Pseudosymbolen wie TAN, USR, DIM, Grafikzeichen und vieles mehr.

Code: Alles auswählen

;labelusenumeric
10      REM NUR EIN KLEINES TESTPROGRAMM
20      REM 123456789
30      REM INPUT=START-ENDE
40      REM '(C) by PokeMon'
Das sind allesamt legale Definitionen, wobei die Zeile 30 nicht ganz unkritisch ist. Hintergrund ist das generelle "Parsen" der BASIC Anweisungen, die nicht ganz kontextgebunden erfolgen. Wenn man sich dieses Programm auf einem normalen ZX81 anschaut, wird es so dargestellt:
EMU1.jpg
EMU1.jpg (24.64 KiB) 15589 mal betrachtet
Wie man sieht, fehlt hier das "=" Zeichen in Zeile 30. Rein prinzipiell wird das nämlich interpretiert und kann bei Sonderzeichen auch nicht korrekte Ergebnisse generieren. Daher sollten komplizierte Darstellungen in '<beliebig formatierter Text mit Sonderzeichen>' einfachen Hochkommas gesetzt werden. Der "40. Geburtstag des ZX81" kann dagegen normal eingegeben werden - hat aber auch noch ein bischen Zeit. :mrgreen: Die Zeile vorab ;labelusenumeric
ist erstmal erforderlich und wird aus didaktischen Gründen später erläutert.

Um beliebigen Assemblercode einzugeben gibt es zusätzliche BASIC Direktiven, die zur Unterscheidung ein _ Underline vorne erhalten. Damit es z.B. keine ungewollten Namenskollisionen gibt. Schauen wir uns dazu folgendes Beispiel an:

Code: Alles auswählen

10      REM _asm

        LD  A,$10
        LD  B,(HL)
        LD  C ,(IX)
        INC BC
        RET

        END _asm

20      RAND USR 16514

30      REM Hauptprogramm BASIC
Hier wird in Zeile 20 an die REM Zeile ein (kleines) Assembler Programm angehängt. Dazu wird der Assemblerblock in der REM Zeile mit _asm eingeleitet. Dieser Block muss abgeschlossen werden, bevor man in den BASIC Kontext zurückgelangt. Sofern man mit PHASE innerhalb des Assemblerblock arbeitet, muss dieser ebenfalls abgeschlossen werden. Der Assembler bringt ggf. entsprechende Hinweise in Form von Fehlermeldungen beim Ausführen, wenn das Abschließen des Assemblerblocks oder von PHASE (mit DEPHASE) vergessen wurde.

So sieht das Programm dann im Emulator aus:
EMU2.jpg
EMU2.jpg (22.28 KiB) 15589 mal betrachtet
Der Vorteil ist die Definition beliebig großer Assemblerblöcke mit exakt dem richtigen Assemblercode (ohne POKE).

So sieht dann der erzeugte Code im Listing (CTRL-F8) aus:

Code: Alles auswählen

0074: [407D] 00 0A 0A 00 EA             10      REM _asm
                                        
0079: [4082] 3E 10                              LD  A,$10
007B: [4084] 46                                 LD  B,(HL)
007C: [4085] DD 4E 00                           LD  C ,(IX)
007F: [4088] 03                                 INC BC
0080: [4089] C9                                 RET
                                        
0081: [408A] 76                                 END _asm
                                        
0082: [408B] 00 14 0E 00 F9 D4 1D 22    20      RAND USR 16514
             21 1D 20 7E 8F 01 04 00    
             00 76                      
                                        
0094: [409D] 00 1E 15 00 EA 2D 26 3A    30      REM Hauptprogramm BASIC
             35 39 35 37 34 2C 37 26    
             32 32 00 27 26 38 2E 28    
             76                         
Hier sieht man wie die BASIC Zeilen genau erzeugt werden. Genau genommen werden mit dem Zusatz _asm die REM Zeilen offen gehalten, es folgt der Assemblercode und beim Abschluss wird das erforderliche NEWLINE gesetzt und die Länge korrekt berechnet.

Soweit so gut zu REM. :D
Wer seinen Computer ehrt, lebt nicht verkehrt.

Benutzeravatar
PokeMon
User
Beiträge: 4490
Registriert: 31.08.2011, 23:41

ZX81 BASIC, Zeilennummern, Labels

Beitrag von PokeMon » 18.01.2013, 23:08

Zeilennummern sind bei der BASIC Programmierung im Grunde obsolet und nur aus Kompatiblitätsgründen vorhanden. Der ZX81 braucht Zeilennummern als Sprungziel und um einzelne Zeilen Editieren zu können. Beim Programmieren sind Zeilennummern eigentlich eher hinderlich, zumindest wenn man mit einem modernen Editor arbeitet und Zeilen kopiert oder nachträglich Zeilen einfügen will. Das Problem kennt man auch vom realen Zeddy. Früher hat man immer mit Sprungweiten von 10 gearbeitet, um ggf. noch weitere Zeilen irgendwo einfügen zu können, also bewußt in der Nummerierung Platz gelassen.

Aus Sicht der IDE oder des Assemblers bzw. Parsers sind Zeilennummern nichts anderes als Labels, eben einfach nur numerische Labels. Die sind normalerweise nicht erlaubt und auch nicht unproblematisch im Handling. Bei der Definition des Labels ist es noch relativ einfach handhabbar, bei der Verwendung von numerischen Labels wird es schwierig, weil man nicht weiß ob z.B. die Zahl 20 gemeint ist oder das Label. Insofern war der Umbau der IDE mit diesem Feature kein leichtes unterfangen, ich hielt es aber für notwendig, damit man ggf. BASIC Programme im Klartext auch einfach mit der IDE als .p File erstellen kann und weil BASIC Programmierer gewohnt sind, mit Zeilennummern zu arbeiten.

Um der IDE nun beizubringen, dass Zeilennumern entsprechend zu interpretieren sind, gibt es eine Pseudo Direktive ;labelusenumeric. Diese muss im Programm als Kommentarzeile (mit ;) definiert werden, am Besten am Anfang des Programms. Dieser Kommentar muss zwingend am Zeilenanfang und alleine stehen. Ich habe das Feature bewußt abschaltbar gemacht (durch Weglassen der Kommentarzeile) um mögliche Seiteneffekte zu vermeiden wenn man nur Assembler programmieren will. Man weiß ja nie.

Code: Alles auswählen

;labelautodetect
;labelusenumeric
In der Vorgängerversion war zusätzlich die Direktive ;labelautodetect erforderlich. Das ist jetzt nicht mehr der Fall und auch nicht zu empfehlen. Normalerweise werden Labels immer speziell definiert mit der Direktive LABEL oder durch einen Doppelpunkt nach dem Namen (label1:). Da es ungewohnt und für vorhandene Listings nicht kompatibel ist (sonst müsste jede Zeilennummer von einem Doppelpunkt gefolgt werden) und weil ich alte ROM Listings im Internet gefunden habe, bei denen teilweise auch Doppelpunkte bei Labels fehlen, habe ich diese Direktive erfunden. Die hat es aber in sich und ist bei fertigen Listings brauchbar, führt aber bei frisch geschriebenem Quelltext zu Problemen. Alles was keine Anweisung, Instruktion oder Direktive ist, wird dadurch automatisch als Label behandelt. Mit der Folge dass ein Schreibfehler bei einem Befehl dann plötzlich ein neues Label erzeugt anstelle des Befehls und solche Fehler schwierig zu finden sind. Aus diesem Grund arbeiten beide Funktionen voneinander unabhängig und ;labelusenumeric hat automatisch auch die ;labelautodetect Funktion (also ohne Doppelpunkt) allerdings beschränkt auf rein numerische Labels. :wink:

War mir wichtig das klarzustellen, denn wenn man die Zusammenhänge versteht leuchtet die Verwendung eher ein, außerdem wollte ich auf das Problem von ;labelautodetect hinweisen.

Betrachten wir mal als Beispiel ein Programm mit 5 REM Zeilen und expliziter Definition der Zeilennummern:

Code: Alles auswählen

10      REM ZEILE 1
20      REM ZEILE 2
30      REM ZEILE 3
40      REM ZEILE 4
50      REM ZEILE 5
So sieht das Listing aus:

Code: Alles auswählen

0074: [407D] 00 0A 09 00 EA 3F 2A 2E    10      REM ZEILE 1
             31 2A 00 1D 76             
0081: [408A] 00 14 09 00 EA 3F 2A 2E    20      REM ZEILE 2
             31 2A 00 1E 76             
008E: [4097] 00 1E 09 00 EA 3F 2A 2E    30      REM ZEILE 3
             31 2A 00 1F 76             
009B: [40A4] 00 28 09 00 EA 3F 2A 2E    40      REM ZEILE 4
             31 2A 00 20 76             
00A8: [40B1] 00 32 09 00 EA 3F 2A 2E    50      REM ZEILE 5
             31 2A 00 21 76             
Wer den Aufbau einer ZX81 BASIC Zeile kennt, kann die Zeilennummern nachvollziehen. Die ersten beiden Bytes sind die Zeilenummern im Big Endian Format (also höherwertiges Byte zuerst) und dann die Länge der Zeile in zwei Bytes im Z80 typischen Little Endian Format (niederwertiges Byte zuerst), gefolgt von dem Kommando (EA='REM'). Der Grund für die Verwendung vom abweichendem Big Endian Format ist mir unbekannt und unklar, wenn es jemand weiß, kann er es hier gerne posten. :D

Wir sehen hier also schon die Zeilennumern in hexadezimaler Darstellung:
0A=10
14=20
1E=30
28=40
32=50

Wenn man eine Zeile zwischen Zeile 20 und Zeile 30 einfügen möchte, geht die schöne Nummerierung sofort flöten. Außerdem macht das Erfassen von Zeilennummern Arbeit. Die habe ich dem Assembler aufgehalst. Man kann sie vollständig weglassen und erhält das gleiche Ergebnis. Oder man kann mit der Direktive AUTOLINE eine andere Schrittweite einstellen. Z.B. 5 oder 100.

Code: Alles auswählen

        AUTOLINE 16
        REM ZEILE 1
        REM ZEILE 2
        REM ZEILE 3
        REM ZEILE 4
        REM ZEILE 5
So kann man ungewöhnliche Programme schreiben mit ungewöhnlichen Zeilennummern. Hier haben wir die Schrittweite 16 oder $10. Das sieht dann im Listing schöner aus:

Code: Alles auswählen

0074: [407D] 00 10 09 00 EA 3F 2A 2E            REM ZEILE 1
             31 2A 00 1D 76             
0081: [408A] 00 20 09 00 EA 3F 2A 2E            REM ZEILE 2
             31 2A 00 1E 76             
008E: [4097] 00 30 09 00 EA 3F 2A 2E            REM ZEILE 3
             31 2A 00 1F 76             
009B: [40A4] 00 40 09 00 EA 3F 2A 2E            REM ZEILE 4
             31 2A 00 20 76             
00A8: [40B1] 00 50 09 00 EA 3F 2A 2E            REM ZEILE 5
             31 2A 00 21 76             
Man kann AUTOLINE auch mehrmals im Programm verwenden, z.B. am Anfang mit Schrittweite 10, dann mit Schrittweite 100. 10 ist übrigens der Standardwert, dann kann man AUTOLINE auch weglassen.

Code: Alles auswählen

0074: [407D] 00 0A 09 00 EA 3F 2A 2E            REM ZEILE 1
             31 2A 00 1D 76             
0081: [408A] 00 14 09 00 EA 3F 2A 2E            REM ZEILE 2
             31 2A 00 1E 76             
008E: [4097] 00 1E 09 00 EA 3F 2A 2E            REM ZEILE 3
             31 2A 00 1F 76             
                                                AUTOLINE 100
009B: [40A4] 00 82 09 00 EA 3F 2A 2E            REM ZEILE 4
             31 2A 00 20 76             
00A8: [40B1] 00 E6 09 00 EA 3F 2A 2E            REM ZEILE 5
             31 2A 00 21 76             
Die Zeilennummern:
0A=10
14=20
1E=30
82=130
E6=230

Man kann auch automatische und manuelle Zeilennummern beliebig mischen, um z.B. eine 10er Schrittweite beizubehalten, aber zwischendrin alle weiteren Zeilen ab Zeile 100 zu definieren, wer also "schöne" Einsprungspunkte definieren will:

Code: Alles auswählen

        REM ZEILE 1
        REM ZEILE 2
        REM ZEILE 3
100     REM ZEILE 4
        REM ZEILE 5
So sieht dann der erzeugte Code aus:

Code: Alles auswählen

0074: [407D] 00 0A 09 00 EA 3F 2A 2E            REM ZEILE 1
             31 2A 00 1D 76             
0081: [408A] 00 14 09 00 EA 3F 2A 2E            REM ZEILE 2
             31 2A 00 1E 76             
008E: [4097] 00 1E 09 00 EA 3F 2A 2E            REM ZEILE 3
             31 2A 00 1F 76             
009B: [40A4] 00 64 09 00 EA 3F 2A 2E    100     REM ZEILE 4
             31 2A 00 20 76             
00A8: [40B1] 00 6E 09 00 EA 3F 2A 2E            REM ZEILE 5
             31 2A 00 21 76             
Die Zeilennummern:
0A=10
14=20
1E=30
64=100
6E=110

Die einzige wichtige Regel ist, dass Zeilennummern zwingend aufsteigend sein müssen. Es ist nicht zulässig kleinere Zeilennummern als die letzte (vorhergehende) Zeilennummer. Ansonsten wird man mit einer Meldung wie "line number conflict, check AUTOLINE" konfrontiert. 8)

Zum Abschluss noch ein kleines Beispiel mit kryptischer Zeilennummernerzeugung:

Code: Alles auswählen

        AUTOLINE $-$$
        REM ZEILE 1
        AUTOLINE $-$$
        REM ZEILE 2
        AUTOLINE $-$$
        REM ZEILE 3
        AUTOLINE $-$$
        REM ZEILE 4
        AUTOLINE $-$$
        REM ZEILE 5
EMU3.jpg
EMU3.jpg (28.93 KiB) 15588 mal betrachtet
Wer seinen Computer ehrt, lebt nicht verkehrt.

Benutzeravatar
PokeMon
User
Beiträge: 4490
Registriert: 31.08.2011, 23:41

ZX81 BASIC, RAND, USR

Beitrag von PokeMon » 19.01.2013, 22:41

Damit man Assembler Code nicht nur definieren sondern auch starten kann, ist das zweitwichtigste Statement RAND. Naja nicht ganz, genau genommen werden Assemblerprogramme mit einer Funktion gestartet, USR. Sofern man keinen Rückgabewert benötigt, tut es die Funktion RAND, die den Rückgabewert überhaupt nicht verarbeitet. Naja, auch wieder nicht so ganz, sie verarbeitet ihn nicht sichtbar. Rein prinzipiell kann man Assemblerprogramme mit beliebigen Befehlen starten, die eine Funktion als Parameter erlauben. PRINT USR <programm> z.B. könnte den Rückgabewert ausgeben, mit LET L=USR <programm> den Wert auch recht einfach weiterverarbeiten im BASIC Programm.

Wie auch immer, in dieser allerersten BASIC Version wird erstmal nur RAND USR unterstützt. Als Parameter bekommt es eine Adresse.
Der gängigste Aufruf ist:

Code: Alles auswählen

10      REM _asm
        LD  A,$10
        LD  B,(HL)
        LD  C ,(IX)
        INC BC
        RET
        END _asm

20      RAND USR 16514
Das ist der erzeugte Code aus dem Beispielprogramm:

Code: Alles auswählen

0074: [407D] 00 0A 0A 00 EA             10      REM _asm
0079: [4082] 3E 10                              LD  A,$10
007B: [4084] 46                                 LD  B,(HL)
007C: [4085] DD 4E 00                           LD  C ,(IX)
007F: [4088] 03                                 INC BC
0080: [4089] C9                                 RET
0081: [408A] 76                                 END _asm
                                        
0082: [408B] 00 14 0E 00 F9 D4 1D 22    20      RAND USR 16514
             21 1D 20 7E 8F 01 04 00    
             00 76                      
Die Einsprungadresse 16514 ist allgemein bekannt und nur gültig für Assemblercode, der in der ersten REM Zeile steht. Denn die Anfangsadresse eines BASIC Programms steht immer fest und die Länge der REM Zeile bis zum Beginn des Codes auch. 16514 dezimal entspricht $4082 hexadezimal und ist genau der Beginn des Codes (LD A,$10).

Wenn die REM Zeile mit dem Code nun nicht die erste Zeile ist oder man auch weitere Einsprungspunkte definieren möchte, kann man auch mit RAND und Labels arbeiten, das macht die Sache einfacher.

Code: Alles auswählen

        REM _asm
simple:
        AND A
        LD  DE,$4000
        SBC HL,DE
        RET
printch:
        LD  A,,'Z'
        RST $10
        LD  A,,'X'
        RST $10
        LD  A,,'8'
        RST $10
        LD  A,,'1'
        RST $10
        RET
error:
        RST 8
        db  $0A
reset:
        JP  0
        END _asm

        RAND USR #simple
        RAND USR #reset
        RAND USR #error
        RAND USR #printch
Das folgende Programm definiert 4 Einsprungspunkte für den Assemblerblock in den Zeilen 20 - 50. RUN 50 schreibt den Text ZX81 auf den Schirm, RUN 40 erzeugt eine BASIC Fehlermeldung und RUN 30 - probierts einfach aus: :mrgreen:

Bei der Angabe von Labels als Sprungziel ist zwingend ein '#' vorne ranzustellen, da das Label ansonsten als BASIC Variable interpretiert werden würde. Das gilt nur für BASIC Parameter. Im Assemblerkontext ist das Label ohne '#' zu verwenden (Beispiel JP label).
EMU4.jpg
EMU4.jpg (35.57 KiB) 15573 mal betrachtet
Das obige Beispiel war jetzt ohne Zeilennummern. Anstelle von Labels akzeptiert die Funktion USR auch eine Zeilennummernangabe, in dem man eine '#' voranstellt und die Zahl mit einer '#' abschließt.. Dabei wird die Zeilennummer als Label genommen und automatisch der Offset 5 addiert. So erspart man sich die Definition zusätzlicher Labels.

Code: Alles auswählen

10      REM 'Testprogramm USR mit Zeilennummer'

20      REM _asm
        LD  A,,'Z'
        RST $10
        LD  A,,'X'
        RST $10
        LD  A,,'8'
        RST $10
        LD  A,,'1'
        RST $10
        RET
        END _asm

30      RAND USR #20#
Hier das Listing dazu:

Code: Alles auswählen

0074: [407D] 00 0A 23 00 EA 39 2A 38    10      REM 'Testprogramm USR mit Zeilennummer'
             39 35 37 34 2C 37 26 32    
             32 00 3A 38 37 00 32 2E    
             39 00 3F 2A 2E 31 2A 33    
             33 3A 32 32 2A 37 76       
                                        
009B: [40A4] 00 14 0F 00 EA             20      REM _asm
00A0: [40A9] 3E 3F                              LD  A,,'Z'
00A2: [40AB] D7                                 RST $10
00A3: [40AC] 3E 3D                              LD  A,,'X'
00A5: [40AE] D7                                 RST $10
00A6: [40AF] 3E 24                              LD  A,,'8'
00A8: [40B1] D7                                 RST $10
00A9: [40B2] 3E 1D                              LD  A,,'1'
00AB: [40B4] D7                                 RST $10
00AC: [40B5] C9                                 RET
00AD: [40B6] 76                                 END _asm
                                        
00AE: [40B7] 00 1E 0E 00 F9 D4 1D 22    30      RAND USR #20#
             21 21 1F 7E 8F 01 52 00    
             00 76                      
Hier das Programm im Emulator, das die korrekte Adresse ausspuckt. Puristen können aber auch die numerische Darstellung 8F 01 52 00 00 im Kopf umrechnen. :lol:
EMU5.jpg
EMU5.jpg (31.89 KiB) 15573 mal betrachtet
Die ermittelte Adresse 16553 = $40A9 also korrekt die erste Assembleranweisung LD A,,'Z'. Übrigens kann man mit ,, (Doppelkomma) den ZX81 Zeichensatz angeben, also das Z für die ZX81 Darstellung.
Zuletzt geändert von PokeMon am 29.08.2013, 22:30, insgesamt 2-mal geändert.
Wer seinen Computer ehrt, lebt nicht verkehrt.

Benutzeravatar
PokeMon
User
Beiträge: 4490
Registriert: 31.08.2011, 23:41

ZX81 BASIC, format, AUTORUN, _noedit

Beitrag von PokeMon » 19.01.2013, 23:33

Zum Abschluss des ersten Teils stellen wir noch 4 weitere Direktiven vor.

format legt das Ausgabeformat fest. Im Flatassembler gibt es dazu verschiedene Direktiven wie ELF, COFF, PE usw. Für den ZX81 wird eigentlich nur das Format binary genutzt. Das binary Format erzeugt automatisch ein Programm mit der Endung .bin also z.B. ZX81DEMO.BIN. Um eine gezielte Extension zu verwenden, kann man den Parameter as "p" angeben um ein ZX81DEMO.p automatisch zu erzeugen.

Code: Alles auswählen

format binary as "p"
Um verschiedene Modelle mit der Z80 Assembler (BASIC) Version zu unterstützen, wurde die format Direktive von mir angepaßt und kann nun einfach als format zx81 definiert werden. In dem Fall wird automatisch die Extension auf "p" gesetzt. Später kann man auch einfach format ZX80 wählen, um eine Datei mit "o" (ZX80DEMO.o) zu erstellen. ZX80 ist aber derzeit noch nicht implementiert. Auch die internen Codetabellen werden darüber gesteuert, der ZX80 hat einen anderen Zeichensatz als der ZX81.

Code: Alles auswählen

format zx81
Wer dennoch eine andere Extension bevorzugt (z.B. "81") kann die format Direktive mit AS ergänzen.

Code: Alles auswählen

format zx81 as "81"


AUTORUN startet das Programm automatisch mit einer beliebigen Zeilennummer nach dem Laden (LOAD). AUTORUN ist als Label konzipiert und wenn vorhanden wird die entsprechende Systemvariable angepaßt. AUTORUN kann nur einmal im Programm vorkommen. Es wird direkt vor die betreffende BASIC Zeile gesetzt. Wer kein AUTORUN möchte kann das Label entweder hinter die letzte BASIC Zeile setzen oder ggf. auskommentieren (mit Semikolon vorne, ;AUTORUN).

Hier ein Beispiel:

Code: Alles auswählen

        REM _asm
printch:
        LD  A,,'Z'
        RST $10
        LD  A,,'X'
        RST $10
        LD  A,,'8'
        RST $10
        LD  A,,'1'
        RST $10
        RET
error:
        RST 8
        db  $0A
        END _asm

        RAND USR #error
AUTORUN:
        RAND USR #printch
EMU7.jpg
EMU7.jpg (47.94 KiB) 15572 mal betrachtet
_noedit setzt eine Zeilennummer auf 0 damit die betreffende Zeile nicht mehr editiert werden kann. Das wird häufiger für die erste REM Zeile in einem Programm genutzt, da die entsprechenden POKE Adressen für die erste Zeile statisch sind.

Code: Alles auswählen

        REM _noedit _asm
printch:
        LD  A,,'Z'
        RST $10
        LD  A,,'X'
        RST $10
        LD  A,,'8'
        RST $10
        LD  A,,'1'
        RST $10
        RET
error:
        RST 8
        db  $0A
        END _asm

        RAND USR #error
AUTORUN:
        RAND USR #printch
EMU8.jpg
EMU8.jpg (29.54 KiB) 15572 mal betrachtet
_noedit kann aber durchaus mehrfach eingesetzt werden, um mehrere Zeilen zu schützen. Zum Beispiel REM Zeilen mit Programmcode aber auch RAND USR Zeilen mit Einsprungadressen. Im Extremfall kann _noedit sogar für jede Zeile gesetzt werden. Damit ist dann null-komma-nix mehr editierbar. :wink:
Dennoch kann das Programm mit RUN gestartet werden (von Anfang an, also ab der 1. Zeile) oder auch mit AUTORUN (ab einer beliebigen Zeile). RUN oder GOTO mit Zeilennummer führen aber ins Leere, weil der ZX81 die Zeilennummern nicht findet. Das kann man z.B. auch nutzen um ein Programm nur direkt nach dem Laden zu starten aber den erneuten Programmstart mit RUN oder GOTO zu unterbinden. Ich bin mir sicher, dass das irgendjemand mal einsetzen wird. :mrgreen:

Code: Alles auswählen

        REM _noedit _asm
printch:
        LD  A,,'Z'
        RST $10
        LD  A,,'X'
        RST $10
        LD  A,,'8'
        RST $10
        LD  A,,'1'
        RST $10
        RET
error:
        RST 8
        db  $0A
        END _asm

        RAND USR _noedit #error
AUTORUN:
        RAND USR _noedit #printch

        REM _noedit '(C) by PokeMon'
EMU9.jpg
EMU9.jpg (30.24 KiB) 15572 mal betrachtet
Zuletzt geändert von PokeMon am 13.08.2013, 20:15, insgesamt 1-mal geändert.
Wer seinen Computer ehrt, lebt nicht verkehrt.

Benutzeravatar
PokeMon
User
Beiträge: 4490
Registriert: 31.08.2011, 23:41

ZX81 BASIC, _hide

Beitrag von PokeMon » 20.01.2013, 00:06

So die vorerst letzte Direktive heißt _hide und der Name ist auch hier das Programm. 8)
Damit kann man eine Zeile oder ein ganzes Programm verstecken oder auch Zahlenangaben verschweigen. Darauf gekommen bin ich durch ein Posting von ali zu dem Thema bzw. auch in einem alten ZX Team Magazin.

Der Klassiker ist sicherlich das Verstecken der REM Zeile mit dem Programmcode.

Code: Alles auswählen

        REM _hide _asm
printch:
        LD  A,,'Z'
        RST $10
        LD  A,,'X'
        RST $10
        LD  A,,'8'
        RST $10
        LD  A,,'1'
        RST $10
        RET
error:
        RST 8
        db  $0A
        END _asm

        RAND USR  #error
AUTORUN:
        RAND USR  #printch
Wenn man sich das Listing anschaut, wird hier einfach ein NEWLINE $FF als erste Zeichen an das REM Statement angehängt.

Code: Alles auswählen

0074: [407D] 00 0A 13 00 EA 76 FF               REM _hide _asm
                                        printch:
007B: [4084] 3E 3F                              LD  A,,'Z'
007D: [4086] D7                                 RST $10
007E: [4087] 3E 3D                              LD  A,,'X'
0080: [4089] D7                                 RST $10
0081: [408A] 3E 24                              LD  A,,'8'
0083: [408C] D7                                 RST $10
0084: [408D] 3E 1D                              LD  A,,'1'
0086: [408F] D7                                 RST $10
0087: [4090] C9                                 RET
                                        error:
0088: [4091] CF                                 RST 8
0089: [4092] 0A                                 db  $0A
008A: [4093] 76                                 END _asm
                                        
008B: [4094] 00 14 0E 00 F9 D4 1D 22            RAND USR  #error
             21 1E 25 7E 8F 01 22 00    
             00 76                      
                                        AUTORUN:
009D: [40A6] 00 1E 0E 00 F9 D4 1D 22            RAND USR  #printch
             21 1D 22 7E 8F 01 08 00    
             00 76                      
Das hat den Nachteil, dass sich der Offest um 2 Byte verändert, wenn man bei RAND USR mit Zeilennummern (RAND USR #10#) arbeitet. Das kann man durch Definition und Nutzung von expliziten Labels vermeiden bzw. umgehen. Vielleicht passe ich das auch in einer künftigen Version mal an, war mir jetzt zu viel Aufwand das einzubauen. Bei Vorwärtsreferenzen kann man nicht so einfach auf den noch nicht erzeuten Code zugreifen und spezielle Strukturen oder einen zusätzliche "end-pass" zu definieren fand ich jetzt nicht so angemessen. Vielleicht muss ich da aber im Zuge weitere BASIC Statements nochmal ran. Das lasse ich jetzt mal offen. :wink:

Wenn man das Programm mit LIST aufruft, wird das Listing nach der Anzeige der ersten Zeile 10 REM allerdings komplett abgebrochen. Zumindest wird aber der "Käse" in einer REM Zeile (kryptische Zeichen für den Assemblercode) nicht mehr angezeigt. In Verbindung mit _noedit kann man sie noch nicht mal editieren, ist der Code also in Stein gemeißelt. Der findige BASIC Programmierer umgeht das jedoch und kann mit LIST 11 den Rest des Programms anzeigen.

Optisch schöner ist es, wenn wir das Programm einfach komplett umbauen und die REM Zeile ausschneiden und als letzte Zeile ins Programm kopieren. Die Startadressen werden ja automatisch ausgerechnet. :wink: Vorteilhaft ist hier auch die Vermeidung von Zeilennummern, da man so keine Zeilennummern durch Verschieben abändern muss. Wieder was gespart. :D

Code: Alles auswählen

        RAND USR  #error
AUTORUN:
        RAND USR  #printch

        REM '(C) by PokeMon'

        REM _hide _asm
printch:
        LD  A,,'Z'
        RST $10
        LD  A,,'X'
        RST $10
        LD  A,,'8'
        RST $10
        LD  A,,'1'
        RST $10
        RET
error:
        RST 8
        db  $0A
        END _asm
EMU9a.jpg
EMU9a.jpg (25.52 KiB) 15573 mal betrachtet
Man kann mit _hide noch mehr verstecken, nämlich den Wert einer Zahlenangabe im Programm. Z.B. lässt sich die Adresse bei RAND USR für die Anzeige manipulieren. Hintergrund ist hier die doppelte Angabe einer Zahl, einmal sichtbar als Zeichen (String) und einmal codiert als Integer.

Code: Alles auswählen

        RAND USR  #error
AUTORUN:
        RAND USR  _hide #printch

Code: Alles auswählen

0074: [407D] 00 0A 0E 00 F9 D4 1D 22            RAND USR  #error
             21 24 1D 7E 8F 01 8A 00    
             00 76                      
                                        AUTORUN:
0086: [408F] 00 14 0A 00 F9 D4 1C 7E            RAND USR  _hide #printch
             8F 01 70 00 00 76          
Wenn man das generierte zweite Statement zerlegt, finden wir hier:
2 Byte Zeilennummer Big Endian (20)
2 Byte Zeilenlänge Little Endian ($0A=10)
1 Byte BASIC Kommando RAND ($F9)
1 Byte BASIC Funktion USR ($D4)
1 Byte String ($1C=0)
1 Integer Identifier ($7E)
5 Byte Integer ($8F,$01,$70,$00,$00 = 16572)
1 Byte Endezeichen HALT ($76)

Und live sieht das dann so aus:
EMU9b.jpg
EMU9b.jpg (23.87 KiB) 15573 mal betrachtet
So das war jetzt mal Teil 1 des BASIC.
Wünsche viel Erfolg und frohes Umsetzen, die nächste Version kommt bestimmt. :D
Zuletzt geändert von PokeMon am 13.08.2013, 20:18, insgesamt 1-mal geändert.
Wer seinen Computer ehrt, lebt nicht verkehrt.

Benutzeravatar
PokeMon
User
Beiträge: 4490
Registriert: 31.08.2011, 23:41

Re: ZX81-IDE in der Praxis, Tipps & Tricks (auch für ZX80)

Beitrag von PokeMon » 25.01.2013, 18:36

Kurze Mitteilung betreffend dieses Threads:

Nach Rücksprache mit t0m (Admin des Forums) aufgrund von Boardbeschränkungen werde ich künftig nicht mehr die neuen Versionen in neuen Beiträgen hochladen sondern die aktuelle Version der ZX-IDE ist ab sofort immer im ersten Beitrag zu finden. Außerdem gibt es im ersten Beitrag ein Inhaltsverzeichnis mit Verlinkung der betreffenden Beiträge, damit man schneller mal was nachschlagen kann oder sich gezielt über ein Thema informieren kann. So wird dieser Thread mehr dem Anspruch eines "manuals" gerecht. :D

Aktuell ist noch keine Änderung erfolgt, nur das Inhaltsverzeichnis erstellt und die aktuelle Version im ersten Beitrag hochgeladen worden.
Irgendwann geht's hier aber auch wieder weiter. :mrgreen:
Wer seinen Computer ehrt, lebt nicht verkehrt.

Benutzeravatar
PokeMon
User
Beiträge: 4490
Registriert: 31.08.2011, 23:41

ZX Emulator EightyOne "Tipps & Tricks"

Beitrag von PokeMon » 23.05.2013, 19:52

So geht weiter mit der IDE, vorerst Ausbau der BASIC Fähigkeiten. :wink:

Aktuell hatte ich ein Problem mit dem Emulator, da ein paar Optionen verstellt waren. Der Emulator wurde zwar mit F8 von der ZX-IDE aufgerufen aber das Programm wurde nicht wie eigentlich gewünscht geladen. Daher hier nochmal zwei wichtige Hinweise:

Zuerst muss die Hardware des Emulators richtig konfiguriert werden. Dazu ruft man den Emulator entweder über das Dateisystem oder auch mit F8 auf. Sofern er sich durch Laden des falschen Programms (Konfiguration ZX80 aber Aufruf mit Programm für ZX81) verhaspelt, empfiehlt sich der Aufruf des Emulators über das Filesystem ohne Programm.

Die Einstellung des Systems wird unter "Options->Hardware (F6)" vorgenommen. Dort kann man wahlweise ZX80 oder ZX81 auswählen (die anderen unterstützt der Emulator nicht) sowie die zur Verfügung stehenden Hardware Optionen (Speicher, etc.). Unter Advanced Settings sollte man auch ggf. die geladene ROM Version prüfen (ZX80 oder ZX81) falls man da mal mit anderen ROMs experimentiert. Diese Hardware Einstellungen merkt sich der Emulator solange, bis sie manuell geändert werden. Das ist also wichtig, wenn man zwischen den Systemen ZX80 und ZX81 öfters mal wechselt.

Die RAM Einstellung sollte mit der Einstellung MEMAVL in der IDE korrespondieren. So kann die IDE wie im Beispielprogramm ZX81DEMO.ASM prüfen, ob das Programm in den Speicher überhaupt reinpaßt und ggf. automatisch eine Fehlermeldung erzeugen, wenn das Programm zu groß ist. Die Direktive assert prüft ob eine Bedingung wahr ist (bzw. stellt sicher dass sie wahr ist oder bleibt) und erzeugt eine Fehlermeldung beim Kompilieren wenn es nicht so sein sollte. Man kann mit assert auch andere Bedingungen prüfen, die im Programm sichergestellt werden sollen. :wink:

Code: Alles auswählen

        MEMAVL     =       MEM_1K          ; can be MEM_1K, MEM_2K, MEM_4K,
...
...
...
      assert ($-MEMST)<MEMAVL ; (letzte Zeile im Programm)
emu00.gif
emu00.gif (96.06 KiB) 14816 mal betrachtet

Die zweite wichtige Einstellung erfolgt im Tape Manager. Dort muss unter "Tools->Tape Manager (CTRL-F9)" im Tape Manager die Option "Auto LOAD on Insert" ausgewählt werden (siehe Bild). Auch das merkt sich der Eighty One. Diese Einstellung ist nur notwendig, wenn man mit dem Tape Manager mal etwas rumgespielt hat oder bei der Erstkonfiguration. Ansonsten wird das Programm nicht automatisch geladen, wenn der Emulator mit F8 aufgerufen wird.
emu01.gif
emu01.gif (103.07 KiB) 14816 mal betrachtet
Wer seinen Computer ehrt, lebt nicht verkehrt.

Benutzeravatar
PokeMon
User
Beiträge: 4490
Registriert: 31.08.2011, 23:41

ZX80 BASIC, Zeilennummern, REM, _asm

Beitrag von PokeMon » 30.05.2013, 20:22

Die aktuelle Version 1.71.01c.Z80 der ZX-IDE kann nun auch rudimentäre ZX80 BASIC Programme erstellen. Im Grunde habe ich den Funktionsumfang des ZX81 auf dem ZX80 nachgezogen. Dadurch werden derzeit zwar nur die Statements REM, RAND sowie die Funktion USR unterstützt aber für die Erstellung von Assemblerprogrammen und deren Start mit RUN oder GOTO ist das zunächst ausreichen. Parallel werde ich nun in nächster Zeit alle fehlenden ZX81 BASIC Statements nachrüsten und dann wieder eine aktualisierte Version posten.

Aber erst mal zurück zum ZX80.
Im Verzeichnis der IDE ist ein Beispielprogramm ZX80DEMO.ASM beigefügt.
Die wichtigste Zeile ist dabei die erste, die das Zielsystem ZX81 oder ZX80 festlegt.
Außerdem müssen die richtigen Include Dateien eingebunden werden, also je nach Zielsystem ZX80.INC oder ZX81.INC sowie ZX80POST.INC oder ZX81POST.INC.

Code: Alles auswählen

format zx80
;labelusenumeric
;LISTOFF
        ; hardware options to be set and change defaults in ZX81DEF.INC
        MEMAVL     =       MEM_1K          ; can be MEM_1K, MEM_2K, MEM_4K,
                                           ; default value is MEM_1K

        include 'SINCL-ZX\ZX80.INC'           ; definitions of constants
;LISTON
10    REM
        include 'SINCL-ZX\ZX80POST.INC'          ; include D_FILE and needed memory areas

assert ($-MEMST)<MEMAVL
; end of program
Hintergrund sind eine andere Speicherstruktur, weniger Systemvariablen, ein anderes Ausgabeformat sowie teilweise unterschiedliche Zeichensätze.
Durch format zx80 wird automatisch eine .o Datei angelegt (anstelle der .p Datei für den ZX81) mit dem korrekten Speicherformat.
Ein weiterer Unterschied liegt in der Übertragung der Audio Dateien mit LOAD und SAVE.
Hier kennt der ZX80 zum Beispiel keinen Programm Namen.
Ein LOAD "datei" oder SAVE "datei" gibt es daher nicht.

Der folgende Screenshot zeigt das Beispielprogramm ZX80DEMO.ASM (kompiliert, also eigentlich ZX80DEMO.o).
clip1.gif
clip1.gif (98.06 KiB) 14684 mal betrachtet
Betreffend Zeilennummern gelten die gleichen Optionen und Einschränkungen wie für den ZX81.
Manuell kann man Zeilen von 1 bis 9999 angeben.
Zeilennummern > 10000 lassen sich bei Programmerstellung durch die IDE aber auch nutzen (bis 16383).
Das war zwar sicher nicht offiziell so vorgesehen, lässt sich aber nutzen. Die Darstellung erfolgt nur für die letzten 3 Stellen richtig. Die Tausenderstelle wird bis 9000 richtig angezeigt, danach kommt dann A für 10000 bis G für 16000. Alle Zeilen bis 16383 werden angezeigt und ggf. ausgeführt, bei RUN oder GO TO muss man dann die dezimale Schreibweise wählen (also z.B. 12345 für C345). Zeilennummern > 9999 werden angezeigt, können aber nicht durch den Anwender editiert werden. Daher eignen sie sich unter anderem z.B. für Copyright Hinweis o.ä.
Clipboard11a.gif
Clipboard11a.gif (98.28 KiB) 12796 mal betrachtet
Es gelten ansonsten die gleichen Hinweise wie beim ZX81. Zeilennummern sind entbehrlich und werden ggf. automatisch von der IDE nummeriert. Den Abstand der Zeilennummern kann man mit AUTOLINE <Wert> festlegen (Default ist 10). Automatische und manuelle Zeilennummern können beliebig gemischt werden, es muss nur eine aufsteigende Reihenfolge eingehalten werden ansonsten bricht die IDE beim Assemblieren mit einer Fehlermeldung ab.

Hier das vollständige Programm zu dem Listing (erster Bildanhang) zum Vergleich der resultierenden Zeilennummern:

Code: Alles auswählen

10      REM NUR EIN KLEINES TESTPROGRAMM
20      REM 123456789
30      REM INPUT=START-ENDE
40      REM '(C) by PokeMon'
50      REM

        AUTOLINE 100

        REM _asm
        dbzx 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
        dbzx 'abcdefghijklmnopqrstuvwxyz'
        dbzx ' "$:?(.)><=+-*/;,.'''
        END _asm
        REM

        RAND USR(#lab_1000)
        REM

9999    REM noch editierbar
10000   REM nicht mehr editierbar
10001   REM 'ideal f. (C)opyright'

16000   REM _asm
        db $76,$76
lab_1000:
        LD A,-1
        RET
label1  db $10
        dbzx 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
        dbzx 'abcdefghijklmnopqrstuvwxyz'
        dbzx ' "$:?(.)><=+-*/;,.'''
        END _asm
Assembler Codeblöcke werden am Einfachsten wie beim ZX81 in REM Zeilen untergebracht. Allerdings weicht das Format des ZX80 ab, dort gibt es keine Information über die Länge einer Zeile. Eine Zeile endet mit einem $76 (NEWLINE). Druckbare/darstellbare Zeichen wie in Zeile 150 sind als "Datenbereich" daher unkompliziert, Assembler Programme oder auch sonstige Daten können dagegen problematisch sein, da der ZX80 möglicherweise mitten in einer solchen REM Zeile den Beginn einer neuen Zeile "vermutet".

Es gibt aber die Möglichkeit, Programmzeilen festzulegen, die nicht im Listing erscheinen oder besser gesagt das Listing beenden. Die Zeile 16.000 im obigen Listing ist ein solches Beispiel:

Code: Alles auswählen

16000   REM _asm
        db $76,$76
lab_1000:
        LD A,-1
        RET
label1  db $10
        dbzx 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
        dbzx 'abcdefghijklmnopqrstuvwxyz'
        dbzx ' "$:?(.)><=+-*/;,.'''
        END _asm
Mit REM _asm und dem Endemarker END _asm wird der Assemblerblock eingeleitet. Die ersten beiden Bytes $76 (NEWLINE) beenden das Listing. Das erste Newline erkennt der ZX80 als Zeilenende, also Ende der REM Zeile. Die folgende Zeile beginnt dann mit einem $76 als höherwertiges Byte der Zeilennummer und damit das "Ende" des Listings. Genau genommen werden alle Zeilennummern mit $40 als höherwertiges Byte oder größer als Ende erkannt.

In der Folge listet der ZX80 daher nur noch das REM ohne Daten. Es können danach noch weitere Zeilen folgen (z.B. weitere Assemblerblöcke) jedoch werden die folgenden Zeilen weder angezeigt noch (als BASIC) ausgeführt. Insofern spricht vieles dafür Assemblercode zum Schluss eines Programms mit hohen Zeilennummern einzugeben. Der ZX80 tut zwar so mit seiner Meldung 0/<Zeilennummer>, als würden nachfolgende Zeilen noch ausgeführt, ist jedoch nicht der Fall. Dennoch kann man dort Assemblercode platzieren und ihn mit (vorher eingegebenen) RANDOMISE USR(..) Befehlen starten.
Zuletzt geändert von PokeMon am 08.09.2013, 23:02, insgesamt 2-mal geändert.
Wer seinen Computer ehrt, lebt nicht verkehrt.

Benutzeravatar
PokeMon
User
Beiträge: 4490
Registriert: 31.08.2011, 23:41

ZX80 BASIC, RAND, RANDOMISE, USR, labels

Beitrag von PokeMon » 30.05.2013, 23:15

Der ZX80 hat zwar wie der ZX81 auch Keywords auf den Tasten belegt, die automatisch am Anfang einer Zeile als Statement erscheinen. Allerdings gibt es in diesem Sinne keine Funktionen, die mit einem einfachen Tastendruck (und der Funktionstaste, Cursor F) aufgerufen werden können. Funktionen wie CHR$() oder USR() werden daher ausgeschrieben und erfordern zwingend Klammern.

Programmcode wird durch das Keyword RANDOMISE in Verbindung mit der Funktion USR(..) aufgerufen. Die ZX-IDE erlaubt wahlweise die Kurzform RAND wie beim ZX81 oder die ausgeschriebene Variante RANDOMISE. Als Argument der Funktion USR(..) wird die Adresse des aufzurufenden Programms mitgegeben. Durch das Keyword RANDOMISE kann der Rückgabewert jedoch nicht so einfach weiterverarbeitet werden. Dazu sind Varianten wie LET A=USR(..) geeigneter, das Statement jedoch derzeitig noch nicht implementiert.

Es gibt 3 verschiedene Varianten, um Programmcode mit RANDOMISE USR(..) zu starten:

Code: Alles auswählen

10      REM NUR EIN KLEINES TESTPROGRAMM

        AUTOLINE 100

        RAND USR 8
        RAND USR(#lab_1000)
        RAND USR #16100#
        REM

10001   REM '(C)opyright PokeMon'

16000   REM _asm
        db $76,$76
lab_1000:
        LD A,-1
        RET
label1  db $10
        END _asm

16100   REM _asm
          LD B,5
          ADD A,B
          RET
          END _asm

        include 'SINCL-ZX\ZX80POST.INC'          ; include D_FILE and needed memory areas
So sieht das dann im Emulator aus:
clip2.gif
clip2.gif (95.01 KiB) 14681 mal betrachtet
Es sind 2 verschiedene USR Aufrufe im Quelltext erkennbar. Der erste ruft direkt eine dezimal angegebene Adresse auf, RAND USR(8) springt die Routine im ROM des ZX80 BASIC an, die einen Fehlercode ausgibt.

Die zweite Variante springt einen Programmcode mit einem Label an. Labels sind im Grunde Namen für Adressen, die man sich sonst schlecht merken kann. Das Label "print_stack" läßt sich zum Beispiel leichter merken als die Adresse 17238. :wink: Labels werden direkt vor eine Assembleranweisung gesetzt. Im obigen Beispiel springt USR(#lab_1000) direkt zum Assemblerbefehl LD A,-1.

So sieht das Listing dazu aus (CTRL-F8):

Code: Alles auswählen

0028: [4028] 00 0A FE 33 3A 37 00 2A    10      REM NUR EIN KLEINES TESTPROGRAMM
             2E 33 00 30 31 2A 2E 33    
             2A 38 00 39 2A 38 39 35    
             37 34 2C 37 26 32 32 76    
                                        
                                                AUTOLINE 100
                                        
0048: [4048] 00 6E EF 3A 38 37 DA 24            RAND USR 8
             D9 76                      
0052: [4052] 00 D2 EF 3A 38 37 DA 1D            RAND USR(#lab_1000)
             22 21 1E 22 D9 76          
0060: [4060] 01 36 EF 3A 38 37 DA 1D            RAND USR #16100#
             22 21 1F 20 D9 76          
006E: [406E] 01 9A FE 76                        REM
                                        
0072: [4072] 27 11 FE DA 28 D9 34 35    10001   REM '(C)opyright PokeMon'
             3E 37 2E 2C 2D 39 00 35    
             34 30 2A 32 34 33 76       
                                        
0089: [4089] 3E 80 FE 76 FF             16000   REM _hide _asm
                                        lab_1000:
008E: [408E] 3E FF                              LD A,-1
0090: [4090] C9                                 RET
0091: [4091] 10                         label1  db $10
0092: [4092] 76                                 END _asm
                                        
0093: [4093] 3E E4 FE                   16100   REM _asm
0096: [4096] 06 05                              LD B,5
0098: [4098] 80                                 ADD A,B
0099: [4099] C9                                 RET
009A: [409A] 76                                 END _asm
                                        
                                                include 'SINCL-ZX\ZX80POST.INC'          ; include D_FILE and needed memory areas
Man kann bei den 3 RAND USR Statements die Zeilennummern im Listing entnehmen. Klammern sind dabei optional verwendbar, werden von der IDE bei der Codegenerierung aber automatisch erzeugt ($DA .... $D9). Der entsprechende Zahlenwert folgt direkt in ASCII, eine zusätzliche Floating Point Notation wie beim ZX81 gibt es beim ZX80 nicht. $24 entspricht der Zahl 8 (erstes RAND USR Statement).

Beim Label finden wir $1D $22 $21 $1E $22 = 16526 (dezimal) = $408E (hexadezimal). An dieser Adresse steht im Listing der Befehl LD A,-1 bzw. das Label lab_1000.
Zuletzt geändert von PokeMon am 08.09.2013, 23:10, insgesamt 5-mal geändert.
Wer seinen Computer ehrt, lebt nicht verkehrt.

Benutzeravatar
PokeMon
User
Beiträge: 4490
Registriert: 31.08.2011, 23:41

ZX80 BASIC AUTORUN, _noedit, _hide

Beitrag von PokeMon » 30.05.2013, 23:49

Kommen wir zu den letzten 3 Statements. Das erste AUTORUN ist recht kurz behandelt, der ZX80 kennt das nicht und kann sozusagen ein Programm nicht automatisch nach dem Laden starten. Schade eigentlich. :?

Das Statement _noedit ist verfügbar und sollte immer nach einem Statement / Basiskeyword wie REM oder RAND(OMISE) erfolgen. Damit wird die betreffende Zeilennummer auf 0 gesetzt und ist nicht editierbar. Allerdings läßt sich eine Zeile mit Zeilennummer 0 auch nicht direkt ausführen. Insbesondere muss man ein bischen aufpassen wenn Zeilen zwischendrin auf _noedit gesetzt werden. Sofern das Programm nicht sequentiell abgearbeitet werden kann, können Abstürze auftreten. Man sollte daher das Statement _noedit nur für die ersten oder letzten Zeilen eines Programms verwenden.
Eine Alternative bei den letzten Zeilen ist die Verwendung Zeilennummern > 9999, da diese auch nicht editierbar sind.

Code: Alles auswählen

10      REM _noedit NUR EIN KLEINES TESTPROGRAMM

        AUTOLINE 100

        RAND _noedit USR 8
        RAND USR(#lab_1000)
        RAND _noedit USR #16100#
        REM

10001   REM '(C)opyright PokeMon'

16000   REM _hide _asm
lab_1000:
        LD A,-1
        RET
label1  db $10
        END _asm
clip3.gif
clip3.gif (95.37 KiB) 14678 mal betrachtet
_hide ist ebenfalls mit Vorsicht zu genießen. Damit wird die betreffende Zeile (z.B. REM Zeile) versteckt aber gleichzeitig auch alle folgenden Zeilen. Die versteckten Zeilen können nur für Assembler Codeblöcke benutzt werden, da die Zeilen nicht mehr im BASIC Kontext ausgeführt werden.
Allerdings kann man REM _hide auch bequem nutzen um die folgenden Assemblerblöcke aus dem Listing/Darstellung zu entfernen und spart sich das Anlegen von zusätzlichen Labels. Siehe Beispiel vorhergehendes Posting, da läßt sich der Block in Zeile 16000 auch ohne Label mit RAND USR #16000# ausführen, in dem man vorher eine Zeile 15999 REM _hide einfügt. Faktisch macht _hide in Verbindung mit REM nichts Anderes, als das Statement (oder den Assemblerblock) mit $76,$FF zu beginnen (wie beim ZX81).

Code: Alles auswählen

        AUTOLINE 100

        RAND USR 8
        RAND USR(#lab_1000)
        RAND USR #16100#
        REM

10001   REM '(C)opyright PokeMon'

15999   REM _hide
16000   REM _asm
lab_1000:
        LD A,-1
        RET
label1  db $10
        END _asm

16100   REM _asm
        LD B,5
        ADD A,B
        RET
        END _asm
_hide macht dagegen bei Zahlenangaben wenig Sinn, da diese nur als ASCII Wert gespeichert werden und nicht wie beim ZX81 zusätzlich als Floating Point und der ASCII Wert für die Anzeige und der Floating Point für die Verarbeitung herangezogen wird.
Zuletzt geändert von PokeMon am 13.08.2013, 20:21, insgesamt 1-mal geändert.
Wer seinen Computer ehrt, lebt nicht verkehrt.

Benutzeravatar
bodo
User
Beiträge: 294
Registriert: 14.02.2007, 17:21
Kontaktdaten:

Re: ZX-IDE in der Praxis, Tipps & Tricks für ZX81,ZX80

Beitrag von bodo » 31.05.2013, 18:51

Sehr, sehr cool, das Ganze! Die gesammelten Fähigkeiten werden mich sicher beim nächsten Projekt (nächstes Jahr, übernächstes Jahr, wer weiß das schon?) zu deiner IDE greifen lassen.

In welcher Sprache hattest du das nochmal programmiert? Könnte es davon auch eine Linux- bzw. systemübergreifende Version geben? Ansonsten werde ich einmal WINE darauf ansetzen.

Zum Schluss noch eine Verständnisfrage zu den letzten Beispielen: warum gibt es dort die Zeile "label1 db $10"? Ich habe keinen Sinn in ihr gefunden...
B0D0: Real programmers do it in hex.

Benutzeravatar
PokeMon
User
Beiträge: 4490
Registriert: 31.08.2011, 23:41

Re: ZX-IDE in der Praxis, Tipps & Tricks für ZX81,ZX80

Beitrag von PokeMon » 31.05.2013, 19:19

bodo hat geschrieben:In welcher Sprache hattest du das nochmal programmiert? Könnte es davon auch eine Linux- bzw. systemübergreifende Version geben? Ansonsten werde ich einmal WINE darauf ansetzen.
Es ist in Assembler (x86) programmiert.
Es ist möglich den Compiler mit den meisten Features auch als DLL oder auch als lib für LINUX zu benutzen.
Das ist im Grunde recht easy, wenn man mit Kommandozeilen Interface arbeiten möchte.
Die anderen Sachen wie Verbindung zum Emulator EightyOne, Listing, LOAD/SAVE Interface sind Bestandteile der (WIN) IDE.

Die Sourcen dazu findest Du übrigens hier, ist jetzt nicht die allerletzte Version werde ich aber wieder mal updaten wenn der ZX81 BASIC Befehlssatz komplett ist.
http://board.flatassembler.net/topic.php?t=15062

Ansonsten bei Bedarf auch gerne per PN.

Flatassembler ist ursprünglich ein x86 Assembler und wurde von mir auf Z80 / ZX BASIC portiert.
Es gibt auch eine ARM Version.

bodo hat geschrieben: Zum Schluss noch eine Verständnisfrage zu den letzten Beispielen: warum gibt es dort die Zeile "label1 db $10"? Ich habe keinen Sinn in ihr gefunden...
Für künftige Erweiterungen - man weiß ja nie. :mrgreen:
Ich habe da wahrscheinlich was Anderes rausgelöscht und die Zeile vergessen. :wink:
Wer seinen Computer ehrt, lebt nicht verkehrt.

Benutzeravatar
PokeMon
User
Beiträge: 4490
Registriert: 31.08.2011, 23:41

ZX81 Kontext Unterschied BASIC und ASSEMBLER

Beitrag von PokeMon » 13.08.2013, 22:11

So die ZX-IDE ist weiter fortgeschritten. Wobei das ZX80 Projekt ordentlich Zeit geraubt hat. Eigentlich wollte ich das nächste Release erst vorstellen, wenn das BASIC komplett implementiert ist. Da einige (kleinere) strukturelle Änderungen notwendig waren, gibt es jetzt doch ein Zwischenupdate. Auch weil das Programm zum Beschreiben von EEPROMs auf diesen neuen Stand aufsetzt. :wink:

Das neue Release ist bereits im ersten Posting dieses Threads aktualisiert und es handelt sich um die Version 1.71.01d.Z80.
Die gemeinsame Verwendung von BASIC und Assembler in einer Programmquelle macht eine Trennung der Kontexte notwendig, wie ich an folgendem Beispiel zeigen möchte.

Code: Alles auswählen

10      REM TESTPROGRAMM;

20      REM _asm
LAB1:
        LD BC,0
        RET
LAB2:
        LD BC,1
        RET
        END _asm

30      PRINT USR LAB1
40      PRINT "PROGRAMMLAENGE:" LAB2-LAB1
In Zeile 30 finden wir eine zweideutige Angabe. LAB1 soll die Adresse des Labels LAB1 sein, könnte aber genausogut eine BASIC Variable sein. Insofern ist das Angeben von Labels als Adresse nicht so ohne Weiteres möglich, zumindest nicht wenn man die übliche BASIC Schreibweise erhalten möchte. In dem Fall hier sind das also BASIC Variablen. Wenn man die Verbindung zum Assembler Kontext herstellen will, ist daher die Verwendung von dem Zusatz '#' nötig. Das ist bereits bekannt durch die Verwendung von RAND in Verbindung mit einer Zeilennummer (RAND 8 versus RAND #8). Siehe auch das folgende Listing im Emulator.
ZX81BASIC1.gif
ZX81BASIC1.gif (100.3 KiB) 13000 mal betrachtet

Code: Alles auswählen

10      REM TESTPROGRAMM;

20      REM _asm
LAB1:
        LD BC,0
        RET
LAB2:
        LD BC,1
        RET
        END _asm

30      PRINT USR #LAB1
40      PRINT "PROGRAMMLAENGE:",#LAB2-LAB1
In diesem Listing wird LAB1 als Assembler Variable interpretiert und beim Assemblieren aufgelöst und durch die tatsächliche Adresse des Labels ersetzt (also keine Variable mehr). In Zeile 40 wird der Ausdruck LAB2-LAB1 ebenfalls ausgewertet und die tatsächliche Zahl in die PRINT Anweisung eingesetzt.
Siehe folgendes Listing:
ZX81BASIC2.gif
ZX81BASIC2.gif (100.79 KiB) 13000 mal betrachtet
Sofern man Parameter teilweise in BASIC und teilweise in Assembler auflösen lassen will, kann man auch innerhalb solcher Ausdrücke das Zeichen '#' setzen, ggf. kann mit einer Klammerung gearbeitet werden wie in Zeile 50. Ansonsten wird der komplette Ausdruck bei Angabe von '#' ausgewertet wie im vorangegangenen Beispiel.

Code: Alles auswählen

10      REM TESTPROGRAMM;

20      REM _asm
LAB1:
        LD BC,0
        RET
LAB2:
        LD BC,1
        RET
        END _asm

30      PRINT USR #LAB1
40      PRINT "PROGRAMMLAENGE:",LAB2-#LAB1
50      PRINT "PROGRAMMLAENGE:",(#LAB2)-LAB1
So sieht das Ganze dann im Emulator aus:
ZX81BASIC3.gif
ZX81BASIC3.gif (100.94 KiB) 13000 mal betrachtet
Zuletzt geändert von PokeMon am 14.08.2013, 00:12, insgesamt 1-mal geändert.
Wer seinen Computer ehrt, lebt nicht verkehrt.

Antworten