Assembler Tipps und Tricks

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

Assembler Tipps und Tricks

Beitrag von PokeMon » 28.04.2013, 22:55

Möchte mal einen neuen Thread eröffnen weil mir gerade danach ist.
Geht um Assembler Tipps, Einsprungadresse im ROM usw.
Einfach eine lose Sammlung, kann sicher jeder mal was beisteuern.
Hier kann man dann mal nachschlagen.
Wer seinen Computer ehrt, lebt nicht verkehrt.

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

FAST / SLOW Mode umschalten

Beitrag von PokeMon » 28.04.2013, 22:59

FAST /SLOW Mode umschalten:
Ich nutze das z.B. für meinen Fastloader, aber auch um Messungen mit dem Oszilloskop zu machen und wenn ich allgemein 100% Rechenleistung haben will.
Das geht am simpelsten so:

Code: Alles auswählen

OUT ($FD),A ; schaltet den NMI Generator ab - identisch mit FAST Mode
<mein Programm>
OUT ($FE),A ; schaltet NMI wieder ein - identisch mit SLOW Mode
RET
In der ZX-IDE sieht das dann z.B. so als Programm aus:

Code: Alles auswählen

20      REM _asm
        OUT ($FD),A

        ; do something here

        OUT ($FE),A
        RET
        END _asm

30      RAND USR #20
Zuletzt geändert von PokeMon am 28.04.2013, 23:39, insgesamt 2-mal geändert.
Wer seinen Computer ehrt, lebt nicht verkehrt.

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

Tastaturabfrage "any key"

Beitrag von PokeMon » 28.04.2013, 23:05

Auch das nutze ich gerne in meinen Programmen.
Auch Testprogramme will man mal beenden. :wink:
Dabei werden eigentlich alle Keyboard rows in einem IN abgefragt und das Programm beendet sobald irgendeine Taste gedrückt wurde.

Code: Alles auswählen

.loop:
        ; do something here

        XOR A ; die abzufragenden Keyboardzeilen, hier alle (auf 0)
        IN A,($FE) ; Tasten einlesen
        OR $E0 ; Bits markieren, die keine Tasten sind (oberen 3 bits auf 1)
        INC A ; schauen ob irgendeine Taste gedrückt wurde (irgendeine keyboard row != 1) wenn keine Taste A=$FF
        JR NZ,.end ; falls ja ist hier feierabend
        JP .loop ; sonst weitermachen wie gehabt  :lol: 
.end:
Man muss nur drauf achten, dass man die Tastaturabfrage in eine loop reinpackt, reicht aus wenn man alle paar sekunden mal abfrägt. Muss man dann halt länger auf der Tastatur bleiben. :mrgreen:


Und so sieht die von mir häufig verwendete Kombination mit dem FAST Modus dann aus:

Code: Alles auswählen

20      REM _asm
        OUT ($FD),A
.loop:
        ; do something here
        XOR A
        IN A,($FE)
        OR $E0
        INC A
        JR NZ,.end
        JP .loop
.end:
        OUT ($FE),A
        RET
        END _asm

30      RAND USR #20
Wer seinen Computer ehrt, lebt nicht verkehrt.

Benutzeravatar
ZX-Heinz
User
Beiträge: 1262
Registriert: 05.12.2011, 14:45

Re: Assembler Tipps und Tricks

Beitrag von ZX-Heinz » 29.04.2013, 13:01

Hallo Pokemon,

die Idee dieses Threads "Assembler-Tricks" ist sehr gut, insbesondere für Zeddyneulinge wie mich.
Wo finde ich ein empfehlenswertes ROM-Disassembly? Am besten online? In 8bit-WIKI gibt es leider keins.

Gruß, Heinz.

Benutzeravatar
msch
User
Beiträge: 3295
Registriert: 05.02.2013, 15:42
Wohnort: Hessen, Rhein-Main-Gebiet

Re: Assembler Tipps und Tricks

Beitrag von msch » 29.04.2013, 13:49

manche meinen lechts und rinks kann man nicht velwechsern.
werch ein illtum! (Lichtung, Ernst Jandl)

Benutzeravatar
ZX-Heinz
User
Beiträge: 1262
Registriert: 05.12.2011, 14:45

Re: Assembler Tipps und Tricks

Beitrag von ZX-Heinz » 29.04.2013, 14:00

DANKE!

christoph
User
Beiträge: 392
Registriert: 26.08.2012, 18:16
Wohnort: Oschersleben
Kontaktdaten:

Re: Assembler Tipps und Tricks

Beitrag von christoph » 07.06.2013, 20:31

Hallo,
kleine Frage, was ist ein NMI. Das kenn ich noch nicht.
Gruß,
Christoph
Klug ist, wer nur die Hälfte von dem glaubt, was er hört, weise ist derjenige, der die richtige Hälfte wählt.

Benutzeravatar
Peter Liebert-Adelt
User
Beiträge: 494
Registriert: 13.03.2004, 23:07
Wohnort: D-31246 Ilsede-Oberg
Kontaktdaten:

Re: Assembler Tipps und Tricks

Beitrag von Peter Liebert-Adelt » 07.06.2013, 21:16

Das ist der NonMaskableInterrupt.
Der ist im Gegensatz zum normalen Interrupteingang nicht per Software zu deaktivieren.

Peter

Benutzeravatar
tofro
User
Beiträge: 752
Registriert: 14.06.2012, 16:45

Re: Assembler Tipps und Tricks

Beitrag von tofro » 07.06.2013, 21:26

Peter Liebert-Adelt hat geschrieben: Der ist im Gegensatz zum normalen Interrupteingang nicht per Software zu deaktivieren.
Außer man hat einen per Software deaktivierbaren NMI-Generator ;)

Ja, das ist verwirrend.

Eigentlich kann man die NMI-Bearbeitung nicht unterbinden, bei allen anderen Interrupts geht das im Z80.
Im ZX-81 kann man die NMI-Erzeugung (de)aktivieren.

Tobias
(Der Krümelkacker ;) )
"On two occasions I have been asked, 'Pray, Mr. Babbage, if you put into the machine wrong figures, will the right answers come out?' ... I am not able rightly to apprehend the kind of confusion of ideas that could provoke such a question."

Benutzeravatar
Paul
User
Beiträge: 2438
Registriert: 10.03.2010, 12:01
Wohnort: Germanys west end

Re: Assembler Tipps und Tricks

Beitrag von Paul » 08.06.2013, 07:11

Na das sollten wir doch besser erklären können?
Beim Z80 Prozessor gibt es die Möglichkeit per interrupt die Arbeit zwischendurch zu unterbrechen
Dabei wird quasi mittendrin ein call zwischen geschoben. Die Subroutine wird dann mit reti beendet.
Es gibt maskierbare interrupts bei denen von außen eine Adresse angelegt wird wohin der z80 springen soll. (Genauer gesagt einen Vektor über den in einer Tabelle nachgesehen werden soll was man tun soll).
Diese interrupts kann man ein und aus schalten.
Bei vielen Computern wird so was zb für das Handlung von Tastatur oder serieller Schnittstelle verwendet. ZX Computer nutzen dies nicht.
Zusätzlich gibt es einen Non maskable interrupt der nicht im z80 abgeschaltet werden kann.
Wenn der nmi Eingang des z80 ausgelöst wird so springt der Z80 zur Adresse 066hex und führt dort die unterroutine aus.
Beim Sinclair ZX81 steht dort die Routine zur Bildausgabe.
Mit dem Basic Befehl SLOW wird eine Schaltung aktiviert mit der regelmäßig ein nmi ausgelöst wird so das der ZX81 im Slow Modus immer ein Fernsehbild anzeigt. Mit FAST wird die Schaltung abgeschaltet.
Der zx80 hat keine solche schaltung und zeigt deshalb nur dann ein Bild wenn er nix zu tun hat und auf eine Taste wartet.
Daher ist im ZX80 ROM an Adresse 066h auch keine Routine für eine nmi Behandlung. Bei Aktivierung des nmi stürzt der ZX80 ab.
Liebe Grüße
Paul
Theoretisch ist zwischen Theorie und Praxis kein Unterschied.
Praktisch allerdings wohl.

Benutzeravatar
medusa
User
Beiträge: 267
Registriert: 13.01.2013, 19:34
Wohnort: Köln

Re: Assembler Tipps und Tricks

Beitrag von medusa » 08.06.2013, 10:49

Gut erklärt Paul,

jetzt muß nur noch gesagt werden, warum man Interrupts abschalten können muß - eine Programmroutine, die z.B. eine Tastatureingabe abarbeitet, sollte nicht nochmals unterbrochen werden, während sie noch dabei ist die aktuelle Taste zu bearbeiten. Sonst gehen u.U. einer oder beide Tastendrücke verloren. Dafür gibt es die Möglichkeit, Interrupts zu maskieren - dann werden sie schlicht ignoriert, solange die Maskierung aktiv ist. Umgangssprachlich ungefähr so:

"Oh jemand hat ne Taste gedrückt. STÖRT MICH JETZT NICHT!"
<nimmt den Tastenwert vom Tastaturport und schreibt ihn in was auch immer für einen Eingabepuffer des Systemkerns>
"Ok, FERTIG. Wo war ich stehengeblieben? War was zwischendurch?"

Der Zettie macht sowas nicht, sondern fragt regelmäßig den Tastaturport ab ob eine Taste gedrückt ist ("polling"). Das verschwendet natürlich Rechenzeit, ist aber einfacher zu programmieren. Es gibt uns natürlich die Möglichkeit, Interrupts für eigene Projekte zu verwenden.
Z.B. könnte eine Echtzeituhr auf dem Zettie laufen, indem eine Zusatzplatine mit Timerbaustein jede Sekunde einen INT/ auslöst, während dem der Sekundenzähler erhöht wird. Hat bestimmt schon mal jemand gemacht. Man darf sich dabei nur nicht mit dem NMI ins Gehege kommen, denn der kann ja, wie sein Name sagt, eben nicht maskiert werden. :wink:

~medusa.
PDP-11: 012700 176500 012760 000100 000000 000777.
68000: 4E71?
VAX: D450 908051 11FB!
Alpha: 47E8404 A4240000 A4440008 40220400 B404000C...

Benutzeravatar
siggi
User
Beiträge: 2058
Registriert: 06.12.2005, 08:34
Wohnort: D, Hessen, tiefste Werreraa
Kontaktdaten:

Re: Assembler Tipps und Tricks

Beitrag von siggi » 08.06.2013, 14:26

medusa hat geschrieben: Der Zettie macht sowas nicht, sondern fragt regelmäßig den Tastaturport ab ob eine Taste gedrückt ist ("polling"). Das verschwendet natürlich Rechenzeit, ist aber einfacher zu programmieren. Es gibt uns natürlich die Möglichkeit, Interrupts für eigene Projekte zu verwenden.
Z.B. könnte eine Echtzeituhr auf dem Zettie laufen, indem eine Zusatzplatine mit Timerbaustein jede Sekunde einen INT/ auslöst, während dem der Sekundenzähler erhöht wird. Hat bestimmt schon mal jemand gemacht.
Nein, das hat wohl noch keiner gemacht, weil das beim Zeddy nicht so trivial ist wie bei anderen Z80-Systemen: dort kann man den /INT-Eingang auf LOW ziehen und so lange da halten, bis die Interrupt-Routine den INT bearbeitet. Beim Zeddy, wo /INT mit A6 verbunden ist, schließt man dabei aber diese Adressleitung kurz, was zum Absturz führen würde.

Ich hatte sowas mal geplant, als ich meinen Zeddy für CP/M nutzen wollte: da habe diese Verbindung von A6 zu /INT durch ein Gatter "abtrennbar" gemacht, sodaß dann /INT hätte genutzt werden können.

Siggi
Mein ZX81-Web-Server: online seit 2007
http://zx81-siggi.endoftheinternet.org/index.html

Benutzeravatar
siggi
User
Beiträge: 2058
Registriert: 06.12.2005, 08:34
Wohnort: D, Hessen, tiefste Werreraa
Kontaktdaten:

Re: Assembler Tipps und Tricks

Beitrag von siggi » 08.06.2013, 14:31

tofro hat geschrieben:
Peter Liebert-Adelt hat geschrieben: Der ist im Gegensatz zum normalen Interrupteingang nicht per Software zu deaktivieren.
Außer man hat einen per Software deaktivierbaren NMI-Generator ;)

Ja, das ist verwirrend.

Eigentlich kann man die NMI-Bearbeitung nicht unterbinden, bei allen anderen Interrupts geht das im Z80.
Im ZX-81 kann man die NMI-Erzeugung (de)aktivieren.

Tobias
(Der Krümelkacker ;) )
Aber man kann die die NMI-Bearbeitung wenigens unterbrechen, wenn man darin die /INTs enabled :D
Siggi
Mein ZX81-Web-Server: online seit 2007
http://zx81-siggi.endoftheinternet.org/index.html

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

ZX81: Registernutzung in USR Programmen (Assembler)

Beitrag von PokeMon » 01.07.2013, 22:47

Weil ich mich eine Zeit lang mit den Video Routinen beschäftigt habe und weil da falsche Informationen im Handbuch stehen, will ich mal kurz auflisten welche Register man benutzen kann/darf und welche man überschreiben darf.

1. SLOW Mode
Der SLOW Mode zeichnet sich dadurch aus, dass während der Programmabarbeitung das Bild dargestellt wird. Das USR Programm wird nur in den Margin Zeilen (Bildschirmzeilen ohne Inhalt) ausgeführt. Das passiert 2x pro Frame für 55 Bildzeilen (oberer und unterer Margin Bereich). Die effektive Rechenzeit beträgt in etwa 110 von 312 Zeilen wobei man die Rechenzeit für den NMI abziehen muss. Grob geschätzt hat das Programm etwa 25% der Rechenleistung gegenüber dem FAST Modus wenn man die Zeit für das Abarbeiten des NMIs berücksichtigt (58 Taktzyklen von 207 Zyklen pro Bildzeile). Oder etwa nur 80 Zeilen von 312 Bildzeilen pro Frame an Rechenzeit.

Folgende Register können beliebig verwendet werden (brauchen nicht gesichert zu werden):
A, Flagregister
BC
DE
HL
ebenso die "Strich" Register, BC' DE' und HL' da der Befehl EXX während der Bildschirmausgabe nicht benutzt wird.

AF' darf nicht benutzt werden, da dort der aktuelle Zähler für die ausgegebenen Margin Bildzeilen gespeichert wird. Der Befehl EX AF,AF' ist im Slow Mode tabu. :wink:

Das IX Register kann verändert werden um die Bildschirmausgabe zu manipulieren. Damit kann man eine eigene Bildverarbeitung realisieren. Nach dem Ende des Margin Bereichs springt der NMI dorthin, um die Bildwiedergabe fortzusetzen. Eine Einsatzmöglichkeit wäre z.B. den Anzeigebereich einzuschränken und die Darstellung einzugrenzen auf weniger als 24 Bildschirmzeilen und eine Erhöhung der Margin Werte (NMI). Das ist sowohl symmetrisch als auch asymmetrisch möglich. Wenn man das Display auf 12 Zeilen reduzieren kann oder will, hat man 96 Zeilen mehr für die Programmausführung. Damit kann man die Rechenleistung trotz Bildschirmausgabe fast verdoppeln. :wink:

Das I Register kann man im Programm kurzfristig benutzen, muss man aber am Ende wieder herstellen und kann sich in kurzfristigen Bildstörungen bemerkbar machen. Allerdings stürzt das Programm dadurch nicht ab. Hier wird lediglich der Pointer auf den Zeichensatz verändert.

Das R Register darf man zerstören, allerdings ist die produktive Nutzung so gut wie unmöglich, da das Register nicht nur durch jede Instruktion sondern auch in der Bildausgaberoutine neu gesetzt wird. Das IY Register sollte man im SLOW Mode nicht verwenden, es sei denn man ersetzt die Bildverarbeitung (siehe IX Register) durch eine eigene und verzichtet auf die Nutzung des IY Register in folgendem Abschnitt:

Code: Alles auswählen

                                        ; ---------------------------------
                                        ; THE 'DISPLAY BLANK LINES' ROUTINE 
                                        ; ---------------------------------
                                        ;   This subroutine is called twice (see above) to generate first the blank 
                                        ;   lines at the top of the television display and then the blank lines at the
                                        ;   bottom of the display. 
                                        
                                        ;; DISPLAY-3
0292: [0292] DD E1                      L0292:  POP     IX              ; pop the return address to IX register.
                                                                        ; will be either L0281 or L028F - see above.
                                        
0294: [0294] FD 4E 28                           LD      C,(IY+$28)      ; load C with value of system constant MARGIN.
0297: [0297] FD CB 3B 7E                        BIT     7,(IY+$3B)      ; test CDFLAG for compute and display.
029B: [029B] 28 0C                              JR      Z,L02A9         ; forward, with FAST mode, to DISPLAY-4
                                        
029D: [029D] 79                                 LD      A,C             ; move MARGIN to A  - 31d or 55d.
029E: [029E] ED 44                              NEG                     ; Negate
02A0: [02A0] 3C                                 INC     A               ;
02A1: [02A1] 08                                 EX      AF,AF'          ; place negative count of blank lines in A'
                                        
02A2: [02A2] D3 FE                              OUT     ($FE),A         ; enable the NMI generator.
                                        

2. FAST Mode
Beim FAST Mode wird die Bilddarstellung komplett abgeschaltet während der Ausführung von Programmen. Hier dürfen alle Register verwendet werden, selbst das AF' Register. Bis auf den Stackpointer (SP) braucht man kein Register zu sichern bis auf das I Register (Zeiger auf den Zeichensatz im ROM oder RAM). IX und IY können uneingeschränkt verwendet werden und in beschränktem Umfang auch das R Register. Es wird zwar auch weitergezählt mit jedem Befehl (M1 Zyklus) aber nicht neu gesetzt und Bit 7 bleibt immer unverändert. In beschränktem Umfang kann es daher sogar auch für (kleine) sinnvolle Aufgaben genutzt werden.

Ausserdem darf das IY Register beliebig verändert werden, der Hinweis im ZX81 Basic Handbuch, dass es wieder auf $4000 gesetzt werden muss, kann man getrost ignorieren. Nach Beendigung des USR Programms wird es vom BASIC selbst restauriert:

Code: Alles auswählen

                                        ; -------------------------
                                        ; THE 'STACK-BC' SUBROUTINE
                                        ; -------------------------
                                        ; The ZX81 does not have an integer number format so the BC register contents
                                        ; must be converted to their full floating-point form.
                                        
                                        ;; STACK-BC
1520: [1520] FD 21 00 40                L1520:  LD      IY,$4000        ; re-initialize the system variables pointer.
1524: [1524] C5                                 PUSH    BC              ; save the integer value.
                                        
Vor der Rückkehr müssen lediglich folgende Register auf jeden Fall wiederhergestellt werden, wenn sie verändert wurden: SP und I
Wer seinen Computer ehrt, lebt nicht verkehrt.

Benutzeravatar
siggi
User
Beiträge: 2058
Registriert: 06.12.2005, 08:34
Wohnort: D, Hessen, tiefste Werreraa
Kontaktdaten:

Re: ZX81: Registernutzung in USR Programmen (Assembler)

Beitrag von siggi » 01.07.2013, 23:06

PokeMon hat geschrieben: Ausserdem darf das IY Register beliebig verändert werden, der Hinweis im ZX81 Basic Handbuch, dass es wieder auf $4000 gesetzt werden muss, kann man getrost ignorieren. Während der Bildschirmausgabe wird es nicht benötigt
Das ist definitiv falsch! Nicht ohne Grund hat Stefano (einer der Entwickler des Z88DK) dem Compiler für den ZX81 eine eigene Videoroutine spendiert, die ohne Verwendung des IY-Registers auskommt, weil dieses (bzw. IX nach dem SWAP-Trick im Z88DK) vom Compiler in seinen Bibliotheken benutzt wird, was dann beim Zeddy zum Absturz führte. Im ROM-Listing des ZX81 steht klar und deutlich

Code: Alles auswählen

; ---------------------------------
; THE 'DISPLAY BLANK LINES' ROUTINE 
; ---------------------------------
;   This subroutine is called twice (see above) to generate first the blank 
;   lines at the top of the television display and then the blank lines at the
;   bottom of the display. 

;; DISPLAY-3
L0292:  POP     IX              ; pop the return address to IX register.
                                ; will be either L0281 or L028F - see above.

        LD      C,(IY+$28)      ; load C with value of system constant MARGIN.
        BIT     7,(IY+$3B)      ; test CDFLAG for compute and display.

daß das IY gelesen wird und somit immer (im Slow-Mode) auf $4000 sitzen muß, will man keinen Absturz oder korruptes Display haben.

Siggi
Mein ZX81-Web-Server: online seit 2007
http://zx81-siggi.endoftheinternet.org/index.html

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

Re: Assembler Tipps und Tricks

Beitrag von PokeMon » 01.07.2013, 23:12

Ja habe ich ja auch gleich korrigiert, dass das nur im FAST Modus gilt. :D
Und in der Tat kann es Komplettabstürze in der Form zur Folge haben, dass das USR Programm gar nicht mehr ausgeführt wird, wenn der Zeddy dann plötzlich in die FAST Mode Routinen springt. :shock:
Wer seinen Computer ehrt, lebt nicht verkehrt.

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

Aktuelle Laufadresse (PC) ermitteln

Beitrag von PokeMon » 23.07.2013, 20:02

Ein Trick von MatthiasS zur Bestimmung der Laufadresse eines Programms, wenn diese nicht bekannt ist, weil es eventuell im Speicher verschoben wurde:

Die Routine befindet sich im ROM.

Code: Alles auswählen

                                        ; --------------------------
                                        ; THE 'UNSTACK-Z' SUBROUTINE
                                        ; --------------------------
                                        ; This subroutine is used to return early from a routine when checking syntax.
                                        ; On the ZX81 the same routines that execute commands also check the syntax
                                        ; on line entry. This enables precise placement of the error marker in a line
                                        ; that fails syntax.
                                        ; The sequence CALL SYNTAX-Z ; RET Z can be replaced by a call to this routine
                                        ; although it has not replaced every occurrence of the above two instructions.
                                        ; Even on the ZX-80 this routine was not fully utilized.
                                        
                                        ;; UNSTACK-Z
0AC5: [0AC5] CD A6 0D                   L0AC5:  CALL    L0DA6           ; routine SYNTAX-Z resets the ZERO flag if
                                                                        ; checking syntax.
0AC8: [0AC8] E1                                 POP     HL              ; drop the return address.
0AC9: [0AC9] C8                                 RET     Z               ; return to previous calling routine if 
                                                                        ; checking syntax.
                                        
0ACA: [0ACA] E9                                 JP      (HL)            ; else jump to the continuation address in
                                                                        ; the calling routine as RET would have done.
                                        
Der Einsprungspunkt ist $0AC8 und das Z-Flag sollte nicht gesetzt sein. Andernfalls wird das eigene Programm beendet, sofern es mit CALL aufgerufen wurde.
Nach dem Ausführen von CALL $0AC8 steht die Laufadresse (die des auf CALL folgenden Befehls) im HL Register und man kann dann Korrekturen an CALLs, JPs oder Zugriff auf Variablen errechnen. Also der betreffende Code muss schon darauf ausgerichtet sein, diesen Korrekturwert zu verarbeiten. :wink:
Wer seinen Computer ehrt, lebt nicht verkehrt.

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

ZX81 ROM Routinen

Beitrag von PokeMon » 19.08.2013, 14:54

Ich habe mal in der ZX-IDE angefangen ein paar nützliche Einsprungadressen als alias zu pflegen (ZX81DEF.INC in Unterverzeichnis SINCL-ZX).

Code: Alles auswählen

//
// 	ZX81 ROM Routines
//

	ZX81ROM_BASERR	EQU	$0008	
					// parameters:	byte error_code-1 follows call
					//		$FF = no error
					// destroyed:	return to BASIC context
					// remarks:	short form RST $08

	ZX81ROM_PRTCHAR	EQU	$0010	
					// parameters:	A=<char to print>
					// destroyed:	AF,BC,DE,HL
					// remarks:	short form RST $10

	ZX81ROM_CLS	EQU	$0A2A   
					// parameters:	no
					// destroyed:	AF,BC,DE,HL

	ZX81ROM_PRINTAT	EQU	$08F5  	
					// parameters:	B=<row>,C=<column> start with 0
					// destroyed:	AF,BC,DE,HL
Die PRTCHAR routine dürfte wohl allgemein bekannt sein.
Das Zeichen in A wird an die aktuelle Position auf dem Bildschirm ausgegeben und der Cursor ein Zeichen weitergerückt.
Üblicherweise wird die Ein-Byte Kurzform RST $10 anstelle von CALL $0010 benutzt.

Die BASERR routine springt in die BASIC Fehlerroutine und beendet das laufende Programm.
Das auf RST $08 folgende Byte wird als "Fehlercode +1" interpretiert.
Die Fehlermeldung erscheint als <Fehlercode>/<Zeilennummer>.
Zeilennummer ist die Zeilennummer, die das Assemblerprogramm gestartet hat (RAND USR oder PRINT USR).
Werte zwischen $00 und $22 entsprechen Fehlermeldungen 0-9 und dann A-Z.
$FF bedeutet kein Fehler (0), das laufende Programm wird dennoch beendet.

CLS löscht den gesamten Bildschirm und erfordert keine Parameter.

PRINTAT setzt den Cursor in die Zeile und Spalte, die in B und C Register übergeben werden (beginnend mit 0).
PRINTAT macht Sinn zusammen mit PRTCHAR um an gezielte Stellen auf den Bildschirm zu schreiben.

Die Bildschirmfunktionen funktionieren mit viel und wenig Speicher (also auch mit dem collapsed display) und sind deutlich schneller als die BASIC Pendants (wo Zahlenwerte erst noch in 16 oder 8 Bit Integer umgewandelt werden müssen).

Wer noch mehr Kandidaten hat, kann sie gerne mit Beschreibung und Namensvorschlag hier listen, ich werde dann die IDE entsprechend erweitern. :wink:
Wer seinen Computer ehrt, lebt nicht verkehrt.

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

zusätzliche 16 Bit Register

Beitrag von PokeMon » 01.09.2013, 20:07

Manchmal kommt man in der Assembler Programmierung nicht weit und die drei 16 Bit Register BC,DE und HL sind zu wenig.
In der ZX81 Umgebung kann man die zusätzlichen Register IX und IY leider im SLOW Mode auch nicht nutzen, da sie bei der Bildschirmausgabe genutzt werden. Will man den zweiten Registersatz auch vermeiden (EXX) dann bietet sich die Nutzung des Stapels (Stackpointer SP) als viertes 16 Bit Register an.

Ein typisches Beispiel sind die Befehle LDIR oder LDDR, wo allein für die Befehle bereits 3 Register festgelegt sind:
DE - Zielregister
HL - Quellregister
BC - Counter

Sofern man Rechenoperationen anstellen muss, zum Beispiel bei unterbrochenen Blocktransfers wie beim Beschreiben von EEPROMs, ist der Austausch des kompletten Registersatzes mit EXX auch nicht effektiv, weil die Register plötzlich andere Werte haben. Um zu rechnen müsste man dann wieder vorher Werte mit PUSH sichern und nach dem EXX mit POP zurückholen und ggf. wieder umgekehrt.

Hier bietet sich wunderbar folgender Befehl an:
EX (SP),HL

Der unspektakulär wirkende Befehl hat es in sich und läßt den aktuellen Stack fast so effizient nutzen wie ein EX DE,HL Befehl. Er ist schneller und kürzer als eine PUSH und POP Kombination zusammen (1 Byte / 19 Takte gegenüber 2 Bytes / 21 Takte) und sichert zusätzlich noch den alten Wert von HL (den man durch ein zweites EX (SP),HL anschließend wiederherstellen kann. Insbesondere da die 16 Bit Rechenoperationen nur mit dem HL Register zusammen funktionieren (ADD/ADC/SBC HL,...).

Code: Alles auswählen

ee_writeblock:
        PUSH  BC                // imagesize to write
        LD    BC,64             // max. blocksize 64 bytes
        LD    A,E
        AND   63                // check boundary, block must begin with address "0"
        JR    Z,.boundok
        LD    C,A
        LD    A,64
        SUB   C
        LD    C,A               // adjust C to first blocksize if not at boundary 0
.boundok:
        EX    (SP),HL
        SBC   HL,BC
        ADD   HL,BC             // check last block size if end is not on boundary
        JP    P,.blocksizeok
        LD    B,H               // store size of last block
        LD    C,L
.blocksizeok:
        EX    (SP),HL           // restore HL
        PUSH  BC                // save blocksize for later check of end
        LD    A,C
        AND   A
        JR    Z,.skipaction     // skip empty block
        CALL  ee_enable         // enable EEPROM for write
        LDIR                    // write data block
        CALL  ee_writetimer     // start write timer
.skipaction:
        POP   BC                // restore size of written block
        EX    (SP),HL           // read remaining imagesize and store HL for next iteration
        SBC   HL,BC             // adjust remaining imagesize
        LD    B,H               // again in BC for next call
        LD    C,L
        POP   HL                // restore HL for next iteration
        RET
Hier wird für einen Blocktransfer BC,DE und HL benötigt und es sind mehrere 16 Bit Rechenoperationen zwischendrin erforderlich, die Bezug zu den aktuellen Registerinhalten haben und gleichzeitig sollen die Register erhalten bleiben, damit die Funktion in einer Schleife iterativ aufgerufen werden kann. Sie schreibt letztlich immer fortlaufend Blöcke in 64 Byte Portionen (mehr kann das EEPROM in einem Zug nicht wegschreiben) und prüft auch ob das Boundary eingehalten wird. Die zweite Bedingung lautet nämlich, dass die 64 Byte zusammenhängen und in einem Block (Page) liegen müssen. Sofern man also aus verschiedenen Gründen in der Mitte einer Page anfängt zu schreiben, reduziert sich die erste Page auf weniger als 64 Byte und ggf. kann das auch bei der letzten Page sein.

Alternativ könnte man das sonst nur mittels aufwändiger PUSH und POP Orgien lösen oder mit LD (nn),xx und LD xx,(nn) Befehlen, die aber sehr teuer und inperformant sind. Ein solcher Befehl braucht alleine 4 Byte und 20 Taktzyklen. Bei 2 oder gar 4 zusammenhängenden Befehlen (Store und Neu laden) verbrät man so sonst schnell 8-16 Byte oder 40-80 Taktzyklen.

Weitere gute Kandidaten sind auch EX (SP),IX oder EX(SP),IY - natürlich nur wenn die Register frei sind. Da kann man dann auch gleich mit Displacements arbeiten. Oder sofern man noch weitere Register braucht auch 2 oder 3 Datensätze auf den Stack legen und mit 2x DEC SP oder 2x INC SP mit jeweils 12 Takten insgesamt hin und herschalten.

Es muss nicht immer PUSH und POP sein wenn man mit dem Stack arbeiten will. :wink:
Wer seinen Computer ehrt, lebt nicht verkehrt.

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

Re: ZX81: Registernutzung in USR Programmen (Assembler)

Beitrag von PokeMon » 01.01.2016, 22:52

PokeMon hat geschrieben:Folgende Register können beliebig verwendet werden (brauchen nicht gesichert zu werden):
A, Flagregister
BC
DE
HL
ebenso die "Strich" Register, BC' DE' und HL' da der Befehl EXX während der Bildschirmausgabe nicht benutzt wird.
Hmmm - ich glaube ich muss mich in dem Punkt korrigieren.
Ich habe gerade ein interessantes Phänomen welches belegt, dass die Strichregister, hier im Beispiel DE' und HL' nicht verändert werden sollten. Ein Rücksprung ins BASIC bringt hier eine komische Fehlermeldung. :shock:

Code: Alles auswählen

10      REM 'EXX Register Test'
20      REM _hide _asm
exx1:
        EXX
        LD      HL,$5555
        LD      DE,$2AAA
        EXX
        RET
exx2:
        LD      HL,$5555
        LD      DE,$2AAA
        RET
        END _asm
30      LET L=USR #exx1
40      STOP
50      LET L=USR #exx2
In diesem Fall bringt ein ZX81 mit 16k RAM den Fehlercode 4 (not enough room) nach einiger Zeit, in einem anderen Programm von mir auch mal den Fehler 6 (arithmetic overflow) was bei der USR Funktion die das BC Register zurückliefert, also einen Wert zwischen 0 und 65535, nicht passieren darf. Ich hatte auch schon mal (je nach Kontext) den Fehler 8. Dieses vereinfachte Beispiel liefert nun den Fehlercode 4, dürfte eigentlich für Andere nachvollziehbar sein.

Bisher war ich immer davon ausgegangen, dass man die EXX Register gefahrlos in einem USR Programm beliebig ändern und beschreiben kann. Das scheint de facto nicht grundsätzlich zu gelten oder eventuell auch abhängig davon wie man die Register zurückläßt. Mit diesen Werten (stammen aus meinem Flash Programmer) gibt es jedenfalls Probleme. RUN oder GOTO 30 liefert Fehlercode 4, GOTO 50 liefert Fehlercode 0 (kein Fehler).

Sichere ich die EXX Register mit PUSH/POP Befehlen, läuft mein Programm fehlerfrei.

Hat jemand schon ähnliche Erfahrungen gemacht ?
Wer seinen Computer ehrt, lebt nicht verkehrt.

christoph
User
Beiträge: 392
Registriert: 26.08.2012, 18:16
Wohnort: Oschersleben
Kontaktdaten:

Re: Assembler Tipps und Tricks

Beitrag von christoph » 02.01.2016, 08:19

in dem Buch: "ZX81 programmieren in BASIC und Maschinencode" stand einmal drin, dass man Geister aus der Schattenwelt nicht beschwören sollte.
Was kann denn schlimmstenfalls passieren? Eigentlich bloß ein Absturz des Programms oder?
Klug ist, wer nur die Hälfte von dem glaubt, was er hört, weise ist derjenige, der die richtige Hälfte wählt.

Benutzeravatar
siggi
User
Beiträge: 2058
Registriert: 06.12.2005, 08:34
Wohnort: D, Hessen, tiefste Werreraa
Kontaktdaten:

Re: Assembler Tipps und Tricks

Beitrag von siggi » 02.01.2016, 10:50

christoph hat geschrieben:in dem Buch: "ZX81 programmieren in BASIC und Maschinencode" stand einmal drin, dass man Geister aus der Schattenwelt nicht beschwören sollte.
Was kann denn schlimmstenfalls passieren? Eigentlich bloß ein Absturz des Programms oder?
Kommt darauf an :mrgreen:
Wenn ein USB-Stick gesteckt ist und der Zeddy beim Absturz des Programms durch die USB-Stick-Schreibroutine durchfegt, kann der USB-Stick zerschrieben werden.
Wenn eine MMC-Karte gesteckt ist und der Zeddy beim Absturz des Programms durch die MEFISDOS-Schreibroutine durchfegt, kann die MEFISDOS-MMC-Karte zerschrieben werden.
Wenn ein AKW am Edge-Connector angeschlossen ist .......

:mrgreen:
Gruß und 1 frohes neues Jahr
Siggi
Mein ZX81-Web-Server: online seit 2007
http://zx81-siggi.endoftheinternet.org/index.html

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

Re: ZX81: Registernutzung in USR Programmen (Assembler)

Beitrag von PokeMon » 02.01.2016, 11:28

PokeMon hat geschrieben: Sichere ich die EXX Register mit PUSH/POP Befehlen, läuft mein Programm fehlerfrei.
So wie ich das mal auf die Schnelle im ROM Code überflogen habe, liegt das Problem im Floatingpoint Calculator, welcher das HL' Register verwendet beim Parsen von Funktionen resp. Zuweisungen. USR ist ja eine solche Funktion. Wird das HL' Register verändert, verliert der Zeddy die Orientierung, interessanterweise berappelt er sich aber und stürzt nicht zwangsläufig ab. Das DE Register ist nach meinen Recherchen wohl nicht betroffen.
Wer seinen Computer ehrt, lebt nicht verkehrt.

bokje
User
Beiträge: 51
Registriert: 02.05.2015, 15:38

Re: Assembler Tipps und Tricks

Beitrag von bokje » 02.01.2016, 11:29

Interessant: USR -> 0x1520: 'stack-bc' -> 0x199d: 'calculate' -> 0x19a5: return address stored in H'L' ? -> gibt möglichkeiten :)

bokje
User
Beiträge: 51
Registriert: 02.05.2015, 15:38

Re: Assembler Tipps und Tricks

Beitrag von bokje » 02.01.2016, 13:59

Beispiel - opcode am Anfang :wink:

Code: Alles auswählen

org 16514
exx
ld hl,fp
exx
ret
fp:
db 0x28
db 0x34
jp 0x10de

Antworten