CBM Commodore C64 Tape Routines Exposed
CBM Commodore C64 Tape Routines Exposed
Revision 3
This document was created in order to gain a complete understanding of the tape routines, to correct errors
in
other documents, and for fun.
;---------------
;---------------
;STATUS Bits for Tape End Or Identify (EOI)
;---------------
;Tape Header Format
;---------------
;Pulse format
Measured on PAL
According to code
;---------------
(L,M) (?,?) (?,?) (?,?) (?,?) (?,?) (?,?) (?,?) (?,?) (?,?)
| | | | | | | | | |
| bit0 bit1 bit2 bit3 bit4 bit5 bit6 bit7 |
| |
new-data marker parity
Parity for the data bits is 1 xored will all other data bits in the byte.
The data stream is terminated when a (L,S) pair is received after a data byte.
Each block also has a checksum which is the last byte in the block.
The first header block in a pair has the sync sequence $89,$88,$87,$86,$85,$84,$83,$82,$81
;---------------
;Notes
The error checking/correction code can only log and fix up to $3f bytes, this could be improved upon. More
complex code could provide better error correction across both copies of the files, and a larger number of
errors.
Pilot/Leader is not assumed to be bit pairs, syncing occurs normally with a new data marker.
This disassembly is believed to be correct, there are possible errors. Most other documentation of the code
has naming errors, or is flat out wrong. Newer versions with any corrections will be forthcoming. Please
contact Fungus if you spot an error(s).
;---------------
;Disassembly
.C:f0a4 48 PHA
.C:f0a5 AD A1 02 LDA $02A1
.C:f0a8 F0 11 BEQ $F0BB
.C:f0aa AD A1 02 LDA $02A1
.C:f0ad 29 03 AND #$03
.C:f0af D0 F9 BNE $F0AA
.C:f0b1 A9 10 LDA #$10
.C:f0b3 8D 0D DD STA $DD0D
.C:f0b6 A9 00 LDA #$00
.C:f0b8 8D A1 02 STA $02A1
.C:f0bb 68 PLA
.C:f0bc 60 RTS
CHRIN
not keyboard
must be tape
CHRIN Exit
GETTCHR
.C:f1b4 60 RTS
CHKSTOP
GETHEADER
VALIDFILE
INCTPTR
PLAYWAIT
PLAYTEST
RECORDWAIT
TAPREAD
TAPWRITE
TAPHDRWRITE
TAPIRQINIT
.C:f8e2 86 B1 STX $B1 ;Set Tape Pulse Length High (constant for timeout)
.C:f8e4 A5 B0 LDA $B0 ;Get Tape Pulse Length Low
.C:f8e6 0A ASL A ;Multiply by 4
.C:f8e7 0A ASL A
.C:f8e8 18 CLC
.C:f8e9 65 B0 ADC $B0 ;Add Tape Pulse Length Low, Total Multiplication by 5
.C:f8eb 18 CLC
.C:f8ec 65 B1 ADC $B1 ;Add Tape Pulse Length High (constant for timeout)
.C:f8ee 85 B1 STA $B1 ;Set Tape Pulse Length High (constant for timeout)
.C:f8f0 A9 00 LDA #$00
.C:f8f2 24 B0 BIT $B0 ;Check High Bit of Tape Pulse Length Low
.C:f8f4 30 01 BMI $F8F7 ;Branch if set (no adjustment)
.C:f8f6 2A ROL A ;Rotate Carry into A Low Bit and High Bit into Carry (adjustment plus)
.C:f8f7 06 B1 ASL $B1 ;Shift Carry bit into Tape Pulse Length High Low Bit and High Bit Into
Carry (multiply correction by 4)
.C:f8f9 2A ROL A ;Rotate Carry into A Low Bit and High Bit into Carry
.C:f8fa 06 B1 ASL $B1 ;Shift Carry bit into Tape Pulse Length High Low Bit and High Bit Into
Carry
.C:f8fc 2A ROL A ;Rotate Carry into A Low Bit and High Bit into Carry
.C:f8fd AA TAX ;Temp Save Adjustment in X
.C:f8fe AD 06 DC LDA $DC06 ;Get Timer B Low
.C:f901 C9 16 CMP #$16 ;Check if less than 22 uS (make sure there's enough cycles left before
rollover)
.C:f903 90 F9 BCC $F8FE ;Loop if too short
.C:f905 65 B1 ADC $B1 ;Add Tape Pulse Length High (calculate and store adjusted time)
.C:f907 8D 04 DC STA $DC04 ;Set Timer A Low
.C:f90a 8A TXA ;Restore Adjustment from X
.C:f90b 6D 07 DC ADC $DC07 ;Add with Timer B High (Adjust for high time count)
.C:f90e 8D 05 DC STA $DC05 ;Set Timer A High Adjusted
.C:f911 AD A2 02 LDA $02A2 ;Get TAPCTRLA
.C:f914 8D 0E DC STA $DC0E ;Set CIA Timer Control A
.C:f917 8D A4 02 STA $02A4 ;Set TAPCTRLB (stupid in source code hah), Non Zero means a /flag irq
has not occured
.C:f91a AD 0D DC LDA $DC0D ;Read CIA Interrupt Control Register
.C:f91d 29 10 AND #$10 ;Check if an IRQ was triggered
.C:f91f F0 09 BEQ $F92A ;Branch not triggered
TAPREADIRQ
.C:f92c AE 07 DC LDX $DC07 ;Read Timer B High (time since last irq)
.C:f92f A0 FF LDY #$FF ;Set Y to $ff = Max Timer Value High
.C:f931 98 TYA ;Set A to $ff = Max Timer Value Low
.C:f932 ED 06 DC SBC $DC06 ;Subtract with Timer B Low (Pulse Length Low)
.C:f935 EC 07 DC CPX $DC07 ;Compare X with Timer B High (Pulse Length High)
.C:f938 D0 F2 BNE $F92C ;Loop if not equal (Stabilize the pulse width if timer low rolled over
while reading values, a few
;cycles won't matter)
.C:f98b A5 B4 LDA $B4 ;Get Tape Bit In/Out Flag (0 = sync, 1 = byte start)
.C:f98d F0 1D BEQ $F9AC ;Branch if Not Syncing
.C:f999 38 SEC
.C:f99a E9 13 SBC #$13 ;Subtract 19 cycles
.C:f99c E5 B1 SBC $B1 ;Subtract Tape Pulse Length High
.C:f99e 65 92 ADC $92 ;Add Tape Timing Constant
.C:f9a0 85 92 STA $92 ;Set Tape Timing Constant, calculate if constant needs changed (software
servo)
.C:f9a2 A5 A4 LDA $A4 ;Get Tape Bit Pair (dipole)
.C:f9a4 49 01 EOR #$01 ;Flip Bit 1
.C:f9a6 85 A4 STA $A4 ;Set Tape Bit Pair (dipole)
.C:f9a8 F0 2B BEQ $F9D5 ;Branch if 0 (check if 1st or 2nd bit of pair)
.C:f9b7 AD A4 02 LDA $02A4 ;Load TAPCTRLB (Timer A didn't time out) (stupid in source code hah)
.C:f9ba D0 16 BNE $F9D2 ;Branch if not start
.C:f9f3 85 96 STA $96 ;Set Tape Bit Pair Count Flag (between blocks)
.C:f9f5 B0 B5 BCS $F9AC ;Branch always (Exit IRQ)
.C:fa10 A5 96 LDA $96 ;Get Tape Leader Bit Pair Count Flag (sync)
.C:fa12 F0 04 BEQ $FA18 ;Branch if 0 (no sync)
.C:fa33 A5 96 LDA $96 ;Get Tape Leader Bit Pair Count Flag (throw away data until sync)
.C:fa35 F0 26 BEQ $FA5D ;Branch if 0 (No Sync)
;Load
.C:faeb A5 B6 LDA $B6 ;Get Error Flag
.C:faed F0 4B BEQ $FB3A ;Branch if 0
.C:fb64 85 BE STA $BE ;Set Tape Block Copy Count $00 if no Errors
.C:fb66 F0 23 BEQ $FB8B ;Branch always
TAPINITZP
TAPOUTPUTBIT
;Send Pulse
.C:fbaf A2 00 LDX #$00 ;High Byte of Pulse Length
TAPWRITEIRQ2
.C:fc16 20 97 FB JSR $FB97 ;Tape Init ZP (Set Tape Bits Count to 8, Init Tape Bit Pair to 0, Set
Tape Error to 0, Set Tape
;Parity to 0, Set Tape Bit Pair Count to 0)
.C:fc19 58 CLI ;Enable Interrupts
.C:fc1a A5 A5 LDA $A5 ;Get Tape Sync Countdown
.C:fc1c F0 12 BEQ $FC30 ;Branch if Countdown is Completed, Write File
.C:fc1e A2 00 LDX #$00
.C:fc20 86 D7 STX $D7 ;Set Tape File Checksum to 0
.C:fc22 C6 A5 DEC $A5 ;Decrement Tape Sync Countdown
.C:fc24 A6 BE LDX $BE ;Get Tape Block Copy Count
.C:fc26 E0 02 CPX #$02 ;Check if First Block Copy
.C:fc28 D0 02 BNE $FC2C ;Branch if First Block
.C:fc2a 09 80 ORA #$80 ;Flip Bit 7 on for Second Block
.C:fc2c 85 BD STA $BD ;Set Tape Byte Out
.C:fc2e D0 D9 BNE $FC09 ;Branch Always
;Write File
.C:fc30 20 D1 FC JSR $FCD1 ;Check Tape EOF
.C:fc33 90 0A BCC $FC3F ;Branch if not EOF
.C:fc35 D0 91 BNE $FBC8 ;Branch if not Rollover
.C:fc37 E6 AD INC $AD ;Increment Tape Load/Save Address High
.C:fc39 A5 D7 LDA $D7 ;Get Tape File Checksum
.C:fc3b 85 BD STA $BD ;Set Tape Byte Out
.C:fc3d B0 CA BCS $FC09 ;Branch if EOF (Exit IRQ)
.C:fc3f A0 00 LDY #$00 ;Set Y Index to 0
.C:fc41 B1 AC LDA ($AC),Y ;Get Tape File Byte from Memory
.C:fc43 85 BD STA $BD ;Set Tape Byte Out
.C:fc45 45 D7 EOR $D7 ;Calculate Tape File Checksum
.C:fc47 85 D7 STA $D7 ;Store Tape File Checksum
.C:fc49 20 DB FC JSR $FCDB ;Set IRQ Vector
.C:fc4c D0 BB BNE $FC09 ;Branch Always (Exit IRQ)
TAPRWRITEIRQ1
;Send Leader/Pilot
.C:fc6a A9 78 LDA #$78 ;Very Short Pulse Length + $14 cycles for setup time + 36 cycles IRQ
overhead = $B0 * 2 for
;negative edge on FLAG = $160 (352 cycles/uS)
.C:fc6c 20 AF FB JSR $FBAF ;Send Pulse
.C:fc6f D0 E3 BNE $FC54 ;Branch if Tape IO state not 0 (used as dipole) Exit IRQ
.C:fc71 C6 A7 DEC $A7 ;Decrement Tape Read Pass Count (leader/pilot)
.C:fc73 D0 DF BNE $FC54 ;Branch if not 0, Exit IRQ
.C:fc75 20 97 FB JSR $FB97 ;Tape Init ZP (Set Tape Bits Count to 8, Init Tape Bit Pair to 0, Set
Tape Error to 0, Set Tape
;Parity to 0, Set Tape Bit Pair Count to 0)
.C:fc78 C6 AB DEC $AB ;Decrement Tape Short Count MSB (leader/pilot)
.C:fc7a 10 D8 BPL $FC54 ;Branch if Positive, Exit IRQ
.C:fcb8 20 93 FC JSR $FC93 ;Disable Tape IRQ and Restore Normal IRQ
.C:fcbb F0 97 BEQ $FC54 ;Exit IRQ
.C:fcd1 38 SEC
.C:fcd2 A5 AC LDA $AC
.C:fcd4 E5 AE SBC $AE
.C:fcd6 A5 AD LDA $AD
.C:fcd8 E5 AF SBC $AF
.C:fcda 60 RTS
.C:ff43 08 PHP
.C:ff44 68 PLA
.C:ff45 29 EF AND #$EF
.C:ff47 48 PHA
HWIRQ
;7 for HW IRQ
.C:ff48 48 PHA ;3
.C:ff49 8A TXA ;2
.C:ff4a 48 PHA ;3
.C:ff4b 98 TYA ;2
.C:ff4c 48 PHA ;3
.C:ff4d BA TSX ;2
.C:ff4e BD 04 01 LDA $0104,X ;Get BRK Flag ;4
.C:ff51 29 10 AND #$10 ;2
.C:ff53 F0 03 BEQ $FF58 ;3
.C:ff55 6C 16 03 JMP ($0316) ;JMP through BRK vector
.C:ff58 6C 14 03 JMP ($0314) ;JMP through IRQ vector ;5
;IRQ overhead = 36 cycles