Arc Doors/Schnelllader
<< zurück zu Arc Doors
Arc Doors/Schnelllader: Die folgenden Abschnitte stellen den disassemblierten Floppy-Schnelllader des Spiels Arc Doors dar. Sie sind gegliedert in einzelne Codeblöcke, die gemäß ihrer Funktion vor dem und während des Ladens gruppiert sind.
Floppy-seitige Schnelllade-Routine[Bearbeiten | Quelltext bearbeiten]
Die folgenden Routine P_AA
liest den nachzuladenden Programmteil Sektor für Sektor in Puffer 4 (ab Adresse $0700) ein und folgt dabei den Verkettungszeigern in den ersten beiden Bytes jedes Sektors. Jeder Sektor wird dann vollständig an den C64 gesandt, da dieser anhand der ersten beiden Bytes sowohl den letzten Block als auch die Anzahl der gültigen Datenbytes im letzten Block erkennt.
Das Versenden findet parallel auf der CLOCK- und DATA-Leitung statt, wobei jedes Datenbyte in 4 Bitpaare aufgeteilt wird, die jeweils — beginnend mit den niederwertigsten beiden Bits — im Abstand von 8 Systemtakten (8 μs) auf die Leitungen gelegt werden. Die resultierende Datenrate von kurzzeitig 250 kBit/s wird nur durch die Verwendung des illegalen Opcode SAX
erreicht, der während des Schreibzugriffs auf Port B von VIA1 alle nicht benötigten Bits im Akkumulator mit Hilfe einer Bitmaske im X-Register ausblendet.
Jeder Übertragung folgt dann noch ein Pegelwechsel auf der DATA-Leitung, der dem C64 eine Driftkompensation ermöglicht, bevor direkt zum nächsten Datenbyte übergegangen wird.
DEVICE EQU $08 ; Geräteadresse 8 SOURCE1: P_AA: SEI ; Jobschleife deaktivieren LDX #$08 ; DATA high, CLOCK low, ATN automatisch beantworten STX $1800 LDA #$14 ; Rate des Jobschleifen-Interrupt erhöhen (alle 5 ms statt alle 14 ms) STA $1C07 LDX *$18 ; Spur des zuletzt gelesenen Blocks LDA *$19 ; Sektor des zuletzt gelesenen Blocks AA00: STX *$0E ; Spur für Puffer 4 ($0700-$07FF) setzen STA *$0F ; Sektor für Puffer 4 setzen LDA #$80 ; Jobcode für "Sektor lesen" STA *$04 ; als Job für Puffer 4 setzen CLI ; Jobschleife aktivieren AA01: BIT *$04 ; Warten bis Job erledigt (gewünschter Block gelesen) BMI AA01 SEI ; Jobschleife wieder deaktivieren AA02: LDA $1800 ; Warten auf DATA low AND #$01 BNE AA02 TAY ; Y=%00000000 STY $1800 ; DATA und CLOCK high LDX #$18 ; X=%00011000, 2 Systemtakte Verzögerung CMP *$00,X ; +4 Systemtakte Verzögerung STX $1800 ; DATA high, CLOCK low CMP ($00),Y ; 6 Systemtakte Verzögerung STY $1800 ; DATA und CLOCK high CMP *$00,X ; 4 Systemtakte Verzögerung STX $1800 ; DATA high, CLOCK low NOP ; 2 Systemtakte Verzögerung STY $1800 ; DATA und CLOCK high AA03: LDA $0700,Y ; Byte aus Datenpuffer holen ASL A ; Bit 0 und 1 nach Bitposition 3 und 4 verschieben ROL A ROL A SAX $1800 ; Illegaler Opcode, speichert (A AND X) ROR A ; Bit 2 und 3 nach Bitposition 3 und 4 verschieben ROR A SAX $1800 ; Illegaler Opcode, speichert (A AND X) ROR A ; Bit 5 und 6 nach Bitposition 3 und 4 verschieben LSR A SAX $1800 ; Illegaler Opcode, speichert (A AND X) LSR A ; Bit 7 und 8 nach Bitposition 3 und 4 verschieben LSR A SAX $1800 ; Illegaler Opcode, speichert (A AND X) LDA #$08 NOP STX $1800 ; DATA high, CLOCK low (zur Driftkorrektur) STA $1800 ; DATA low, CLOCK high INY ; Schreibzeiger auf nächstes Byte richten BNE AA03 ; Rücksprung falls noch nicht alle Bytes übertragen LDA $0701 ; Sektornummer des nächsten Blocks LDX $0700 ; Spurnummer des nächsten Blocks BNE AA00 ; Rücksprung falls nicht letzter Block LDA #$01 STA *$1C ; Flag für "Diskette initialisieren" setzen RTS
Startroutinen[Bearbeiten | Quelltext bearbeiten]
Die nachfolgende Routine P_AC
überträgt die Floppy-seitigen Schnelllade-Routinen mittels des "M-W"-Befehls ("Memory-Write") in Abschnitten zu jeweils 32 Bytes in das RAM der Floppy ab Adresse TARGET1=$0500
. Anschließend wird der Schnelllader dort mittels "UC" (User command 3) gestartet, bevor der C64 nahtlos in seine eigene Schnelllade-Routine übergeht.
; Befehlskanal für Floppy öffnen P_AC: LDA #DEVICE ; Geräteadresse JSR $FFB1 ; LISTEN senden LDA #$6F ; Sekundäradresse 15 (Befehlskanal) JMP $FF93 ; Sekundäradresse nach LISTEN senden ; Anzahl Headerbytes setzten und Ladeadresse umkopieren P_AD: STA $09A2 ; Anzahl Headerbytes (4 Bytes im ersten Block, sonst 2 Bytes) am Anfang des Puffers setzen LDA *$AE ; Low-Byte der Ladeadresse STA *$AC ; merken LDA *$AF ; High-Byte der Ladeadresse STA *$AD ; merken RTS ; Y als zweites Zeichen im Namen des nachzuladenden Programmteils setzen; Sprites ausschalten P_AE: STY AB18+$01 ; Y als zweites Zeichen im Namen des nachzuladenden Programmteils setzen LDA #$00 STA $D015 ; Alle Sprites ausschalten (vermeidet DMA-Zugriffe des VIC) RTS
; Nachzuladende Programmdatei zum Lesen öffnen und Ladeadresse holen P_AB: STX AB18+$00 ; X als erstes Zeichen im Namen des nachzuladenden Programmteils setzen JSR $7E37 ; Y als zweites Zeichen im Namen des nachzuladenden Programmteils setzen, Sprites ausschalten AB00: LDA #DEVICE ; Geräteadresse STA *$BA ; merken LDA #<AB18 ; Low-Byte der Anfangsadresse des Dateinamens STA *$BB ; merken LDA #>AB18 ; High-Byte der Anfangsadresse des Dateinamens STA *$BC ; merken LDA #$03 ; Länge des Dateinamens STA *$B7 ; merken LDX #$00 LDA #$60 ; Sekundäradresse STA *$B9 ; merken JSR $F3D5 ; Datei öffnen LDA #DEVICE ; Geräteadresse JSR $ED09 ; TALK senden LDA #$60 ; Sekundäradresse JSR $EDC7 ; Sekundäradresse nach TALK ausgeben JSR $EE13 ; Low-Byte der Ladeadresse vom IEC-Bus holen (IECIN) STA *$AE ; und merken LDA *$90 ; Statusvariable ST holen LSR A LSR A ; Bit 6 ins CF BCS AB00 ; Sprung zum erneuten Leseversuch falls kein Byte gelesen JSR $EE13 ; High-Byte der Ladeadresse vom IEC-Bus holen (IECIN) STA *$AF ; und merken LDA #$04 ; Anzahl der Headerbytes im ersten Block JSR P_AD ; setzen und Ladeadresse umkopieren LDA #$FC ; Anzahl gültiger Datenbytes im ersten Block STA AB13+$01 ; in Code einarbeiten (Selbstmodifikation) ; Floppy-seitige Schnelllade-Routine ins Floppy-RAM ab Adresse $0500 kopieren (kopiert unnötigerweise auch die ersten 18 Bytes von P_AB) LDY #$00 ; Lese- und Schreibzeiger AB01: STY AB19+$02 ; als Low-Byte der Zieladresse in "M-W"-Befehl einarbeiten JSR P_AC ; Befehlskanal für Floppy öffnen LDX #$05 ; "Memory-Write" ("M-W")-Befehl samt Argumenten besteht aus 5+1 Bytes AB02: LDA AB19,X ; Befehl zeichenweise lesen JSR $EDDD ; und an Floppy senden DEX ; Lesezeiger erniedrigen BPL AB02 ; Rücksprung wenn noch nicht alle Zeichen übertragen AB03: LDA SOURCE1,Y ; Floppy-seitige Schnelllade-Routine lesen JSR $EDDD ; und byteweise an "M-W"-Befehl anhängen INY ; Lesezeiger erhöhen TYA AND #$1F ; Vielfaches von 32 Byte übertragen? BNE AB03 ; Rücksprung falls noch nicht Vielfaches von 32 Bytes übertragen JSR $EDFE ; UNLISTEN senden CPY #$80 ; Schon 4*32=128 Bytes ins RAM der Floppy übertragen? BNE AB01 ; Rücksprung falls nicht letzter 32 Byte-Block ; Floppy-seitige Schnelllade-Routine mit Befehl "UC" ab Adresse $0500 starten JSR P_AC ; Befehlskanal der Floppy öffnen LDA #$55 ; 'U' JSR $EDDD ; auf IEC-Bus ausgeben (IECOUT) LDA #$43 ; 'C' JSR $EDDD ; auf IEC-Bus ausgeben (IECOUT) JSR $EDFE ; UNLISTEN senden
C64-seitige Schnelllade-Routinen[Bearbeiten | Quelltext bearbeiten]
Für die hohen Datenraten des Schnellladers ist eine exakte Synchronisation wichtig. Diese wird vor der Übertragung jedes Datenblocks sichergestellt, indem die Floppy eine Folge von immer dichter aufeinanderfolgenden Pegelwechseln im Abstand von 10, 10, 8 und 6 Systemtakten auf der CLOCK-Leitung erzeugt, an denen sich der C64-seitige Teil der Schnelllade-Routine ausrichten kann. Anschließend überträgt die Floppy ein Datenbyte in Form von 4 Bitpaaren, die jeweils für 8 Systemtakte auf den DATA- und CLOCK-Leitungen liegen.
Da die Taktfrequenz des C64 um rund 1,5% niedriger ist als diejenige der Floppy, würden ohne weitere Maßnahmen die Sende- und Empfangsroutinen zeitlich auseinanderdriften. Auf die 4 Bitpaare folgt daher jeweils ein Pegelwechsel auf der DATA-Leitung, anhand dessen der C64 bei Bedarf die Empfangsroutine um 1 Systemtakt verzögern kann (bedingter Sprung zum Label AB12
).
Nach jeder Übertragung eines vollständigen Sektors kopiert der C64 die Datenbytes (ab Offset 4 im ersten, ab Offset 2 in allen weiteren Sektoren) von einem Zwischenpuffer an Adresse $0400 an die endgültige Ladeadresse, während die Floppy den nächsten Sektor von Diskette einliest. Anschließend startet die Übertragung dieses nächsten Blocks mit einer erneuten Synchronisation. An einer Spurnummer von 0 im Verkettungszeiger (im ersten Byte des Sektors) erkennen sowohl Floppy als auch C64 das Dateiende.
SEI ; Interrupts verbieten LDA #$23 STA $DD00 ; DATA low, CLOCK high LDA #$0B STA $D011 ; Bildschirm abschalten AB04: LDA $D012 ; Warten, bis keine Badlines mehr erzeugt werden können BNE AB04 AB05: BIT $DD00 ; Warten auf CLOCK low BVS AB05 AB06: LDA #$00 STA $DD00 ; DATA und CLOCK high LDY #$00 AB07: BIT $DD00 ; Warten auf CLOCK high BVC AB07 AB08: BIT $DD00 ; Warten auf CLOCK high BVC AB08 BIT *$93 ; 5 Systemtakte Verzögerung NOP BIT $DD00 BVS AB09 ; 3 Systemtakte Verzögerung bei CLOCK high, 2+3 Systemtakte bei CLOCK low CMP *$00 ; 3 Systemtakte Verzögerung AB09: BVS AB10 ; 3 Systemtakte Verzögerung bei CLOCK high, 2 Systemtakte bei CLOCK low AB10: CMP ($00,X) CMP ($00,X) CMP ($00,X) CMP *00,X ; 4 Systemtakte Verzögerung AB11: LDA $DD00 ; Datenbit 1 und 0 an Bitposition 7 und 6 LSR A LSR A ; Datenbit 1..0 nun an Bitposition 5..4 ORA $DD00 ; Datenbit 3 und 2 an Bitposition 7 und 6 LSR A LSR A ; Datenbit 3..0 nun an Bitposition 5..2 ORA $DD00 ; Datenbit 5 und 4 an Bitposition 7 und 6 LSR A LSR A ; Datenbit 5..0 nun an Bitposition 5..0 ORA $DD00 ; Datenbit 7 und 6 an Bitposition 7 und 6 EOR #$FF ; Empfangenes Byte invertieren CMP *$00,X ; 4 Systemtakte Verzögerung BIT $DD00 ; Resynchronisation BPL AB12 ; 3 Systemtakte Verzögerung bei DATA low, 2 Systemtakte Verzögerung bei DATA high AB12: CMP #$00 ; 2 Systemtakte Verzögerung STA $0400,Y ; Empfangenes Byte in Puffer schreiben CMP #$00 ; 2 Systemtakte Verzögerung INY ; Schreibzeiger erhöhen BNE AB11 ; Rücksprung falls noch nicht 256 Bytes übertragen LDA #$20 STA $DD00 ; DATA low, CLOCK high AB13: LDX #$FC ; Anzahl gültiger Datenbytes im ersten Block (wird später angepasst) LDA $0400 ; Spurnummer des nächsten Blocks BNE AB15 ; Sprung falls nicht letzter Block LDX $0401 ; X=Anzahl gültiger Datenbytes im Block LDA AB16+$01 ; Low-Byte der Adresse des ersten Datenbyte CMP #$02 ; Nicht erster Block? BEQ AB14 ; Sprung falls nicht erster Block DEX ; Ladeadresse nicht umkopieren DEX AB14: DEX ; Anzahl gültiger Datenbytes im Sektor um Offset korrigieren AB15: LDY #$00 ; Lese- und Schreibzeiger initialisieren AB16: LDA $0404,Y ; Datenbyte aus Puffer holen (Adresse wird später angepasst) STA ($AE),Y ; und an Zieladresse schreiben INY ; Lese- und Schreibzeiger erhöhen DEX ; Anzahl umzukopierender Datenbytes vermindern BNE AB16 ; Rücksprung falls noch nicht alle Datenbytes umkopiert LDX #$FE ; Anzahl gültiger Datenbytes ab dem zweiten Datenblock STX AB13+$01 ; in Code einarbeiten (Selbstmodifikation) LDX #$02 ; Anzahl Headerbytes ab dem zweiten Datenblock STX AB16+$01 ; in Code einarbeiten (Selbstmodifikation) TYA ; A=Anzahl umkopierter Datenbytes CLC ADC *$AE ; zum Low-Byte der Schreibadresse addieren STA *$AE BCC AB17 ; Sprung falls kein Additionsübertrag INC *$AF ; sonst High-Byte der Schreibadresse erhöhen LDX $0400 ; Spurnummer des nächsten Blocks BEQ AB17 ; Sprung falls letzter Block JMP AB06 ; Abschluss AB17: LDA #$07 STA $DD00 ; Port wieder auf Standardwert LDA #$0B STA $D011 ; Bildschirm bleibt abgeschaltet (unnötig) CLI ; Interrupts wieder erlauben RTS AB18: DB "IN*" AB19: DB $20,$05,$00,"W-M"