Edicolac64 - il commodore 64 in Italia


Vai ai contenuti

La struttura di un file Tap

Collabora

La struttura di un file tap
di Rosario Saccone per edicolac64.com

Mi sono sempre chiesto come il Commodore 64 memorizzasse i programmi su cassetta. Solo in tempi recenti sono riuscito a soddisfare questa mia curiosità esaminando i file tap che emulano appunto il contenuto di una cassetta con i suoi astrusi impulsi sonori (avete mai provato ad ascoltare una cassetta del C64 nello stereo? Se lo fate abbassate il volume sennò i vicini crederanno che siete in contatto con gli ufo…). Su internet non si trova molto sull'argomento e quel poco che c'è ovviamente è in inglese. Proviamo ad analizzare il contenuto di un file tap di prova (che chiameremo appunto prova.tap) da noi stesso realizzato. Apriamo dunque il nostro fido WinVICE e dal menù File selezioniamo Attach tape image: si aprirà una finestra in cui troveremo il pulsante Create image. Inseriamo "prova" come nome file, quindi premiamo tale pulsante; a questo punto selezioniamo il nostro prova.tap (che ovviamente risulterà vuoto) nel box più grande e clicchiamo su Attach.
Scriviamo un banale programma in basic:

10 PRINT"PROVA"
Salviamolo nel file tap con:

SAVE"PROVA"

Il computer (emulato) ci risponderà con:

PRESS RECORD E PLAY ON TAPE

Dal menù File/Datassette control selezioniamo Record
Dopo pochi secondi il salvataggio terminerà.
A questo punto dobbiamo sapere come il C64 memorizza i programmi basic in memoria ram: su questo argomento si trova molta documentazione per cui mi limito a dire che il nostro brevissimo programma viene tradotto dall' interprete basic nella sequenza di 15 byte (in notazione esadecimale):

0801 0e 08 0a 00 99 22 50 52 ....."PR
0809 4f 56 41 22 00 00 00 OVA"...

Per verificarlo basta entrare nel Monitor di WinVICE e scrivere:

m 0801

Verrà dunque mostrato il dump di memoria delle locazioni a partire da $0801 (inizio memoria destinata ai programmi basic); comunque si intuisce facilmente che $080E è l'indirizzo della prossima linea (nel nostro caso punta a $0000), $0A è il numero di linea (decimale 10), $99 è il token dell'istruzione PRINT, mentre $00 finale indica la fine del programma e gli altri valori sono l'equivalente ASCII di "PROVA". A questo punto possiamo anche chiudere l'emulatore e dare il nostro file prova.tap in pasto a tapclean perché ne effettui la pulitura e la normalizzazione ottenendo il file prova.clean.tap. Questo è il report dopo tale operazione:

GENERAL INFO AND TEST RESULTS

TAP Name : C:\C64\prova.clean.tap
TAP Size : 41942 bytes (40 kB)
TAP Version : 1
Recognized : 100%
Data Files : 4
Pauses : 2
Gaps : 0
Magic CRC32 : E47CB30E
TAP Time : 0:22.47
Bootable : YES (1 part, name: PROVA)
Loader ID : n/a

Overall Result : PASS

Header test : PASS [Sig: OK] [Ver: OK] [Siz: OK]
Recognition test : PASS [41922 of 41922 bytes accounted for] [100%]
Checksum test : PASS [4 of 4 checksummed files OK]
Read test : PASS [0 Errors]
Optimization test : PASS [4 of 4 files OK]


FILE FREQUENCY TABLE

PAUSE (2)
C64 ROM-TAPE HEADER (2)
C64 ROM-TAPE DATA (2)


FILE DATABASE

---------------------------------
File Type: C64 ROM-TAPE HEADER
Location: $0014 -> $6AC8 -> $79C8 -> $79DD
LA: $033C EA: $03FB SZ: 192
File Name: PROVA
Pilot/Trailer Size: 27136/0
Checkbyte Actual/Expected: $4A/$4A PASS
Read Errors: 0
Unoptimized Pulses: 0
CRC32: 2A778BCD
- File ID : FIRST
- DATA FILE type : BASIC
- DATA FILE Load address : $0801
- DATA FILE End address : $0810
- DATA FILE Size (calculated) : 15 bytes

---------------------------------
File Type: C64 ROM-TAPE HEADER
Location: $79DE -> $7AE1 -> $89E1 -> $8A44
LA: $033C EA: $03FB SZ: 192
File Name: PROVA
Pilot/Trailer Size: 79/78
Checkbyte Actual/Expected: $4A/$4A PASS
Read Errors: 0
Unoptimized Pulses: 0
CRC32: 2A778BCD

- File ID : REPEAT
- DATA FILE type : BASIC
- DATA FILE Load address : $0801
- DATA FILE End address : $0810
- DATA FILE Size (calculated) : 15 bytes

---------------------------------
File Type: PAUSE
Location: $8A45 -> $0000 -> $0000 -> $8A48
- Length: 320000 cycles (0.3248 secs)

---------------------------------
File Type: C64 ROM-TAPE DATA
Location: $8A49 -> $9FFD -> $A129 -> $A13E
LA: $0801 EA: $080F SZ: 15
Pilot/Trailer Size: 5376/0
Checkbyte Actual/Expected: $CF/$CF PASS
Read Errors: 0
Unoptimized Pulses: 0
CRC32: 47C6CDBA
- File ID : FIRST

---------------------------------
File Type: C64 ROM-TAPE DATA
Location: $A13F -> $A242 -> $A36E -> $A3D1
LA: $0801 EA: $080F SZ: 15
Pilot/Trailer Size: 79/78
Checkbyte Actual/Expected: $CF/$CF PASS
Read Errors: 0
Unoptimized Pulses: 0
CRC32: 47C6CDBA
- File ID : REPEAT

---------------------------------
File Type: PAUSE
Location: $A3D2 -> $0000 -> $0000 -> $A3D5
- Length: 4926240 cycles (5.0000 secs)


PULSE FREQUENCY TABLE

0x30 (36916)
0x42 (4540)
0x56 (458)

Come si vede per memorizzare un brevissimo programma in basic sono necessari oltre 40k!
Spieghiamo l'arcano.
Anzitutto ogni bit viene rappresentato come una coppia di byte:

($30,$42) indica un bit 0
($42,$30) indica un bit 1

Poiché ogni byte è costituito da 8 bit viene codificato con ben 16 byte nel file tap; in più la coppia ($56,$42) in testa indica l'inizio della sequenza che rappresenta un byte mentre in coda viene aggiunto un ulteriore bit che serve per il controllo di parità (vale 0 oppure 1 a seconda che il numero di bit 1 della sequenza sia in numero pari o dispari): totale 20 byte per rappresentare un singolo byte di memoria.
I valori $30, $42 e $56 rappresentano la durata degli impulsi sonori, rispettivamente corto, medio e lungo, che vengono registrati sul nastro magnetico per cui un bit 0 è rappresentato dal passaggio da un impulso breve ad uno medio mentre un bit 1 dal passaggio inverso, secondo quella che si chiama codifica Manchester. Quindi il nostro breve programma misura nel tap 20x15=300 byte.
Bisogna poi considerare il cosiddetto Header: è in esso che sono memorizzati dati quali il nome del file e l'indirizzo in cui deve essere caricato; grazie ad esso il C64 produce la scritta FOUND … quando si carica un programma. Senza entrare ora nei particolari ricordiamo solo che esso misura sempre 192 byte in memoria ovvero 3840 byte nel file tap.
Sia l'Header che il programma vero e proprio vengono memorizzati due volte perché, con tale ridondanza, il C64 può verificare (ricordate l'istruzione VERIFY del basic?) il corretto salvataggio di un file. Tutto il resto dello spazio nel nostro tape virtuale, a parte 20 byte iniziali, è occupato da impulsi che non rappresentano alcun dato ma che sono fondamentali per la sincronizzazione del registratore col computer affinché il nastro raggiunga la giusta velocità e possa essere letto correttamente; i 20 byte iniziali poi costituiscono un descrittore (ovviamente non presente nelle cassette reali) che serve all'emulatore per riconoscere i tap; vi sono infine alcuni byte che rappresentano le pause tra un file e l'altro. Armiamoci dunque di un editor esadecimale ed andiamo a verificare, in base alle nozioni appena esposte, quanto è descritto nel report di tapclean. Aperto il file prova.clean.tap vediamo i primi byte che includono il descrittore di file tap:

Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

00000000 43 36 34 2D 54 41 50 45 2D 52 41 57 01 00 00 00 C64-TAPE-RAW....
00000010 C2 A3 00 00 30 30 30 30 30 30 30 30 30 30 30 30 £..000000000000


Esso va così interpretato:

$00-$0B: scritta "C64-TAPE-RAW"
$0C : tipo di file tap (tipo 00 oppure tipo 01)
$0D-$0F: non usati (valgono sempre $00)
$10-$13: lunghezza del file in byte escluso il descrittore ($A3C2=41922 dec.)

Dal byte $14 al byte $6A13 vi è una lunghissima sequenza di $30: sono ben 27136 come si evince dal report di Tapclean alla voce Pilot del primo file. Seguono 180 byte che rappresentano il cosiddetto treno di sincronizzazione costituito dai seguenti byte:

$89 $88 $87 $86 $85 $84 $83 $82 $81 Ed infatti risulta:

Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F


00006A10 30 30 30 30 56 42 42 30 30 42 30 42 42 30 30 42 0000VBB00B0BB00B
00006A20 30 42 30 42 42 30 30 42 56 42 30 42 30 42 30 42 0B0BB00BVB0B0B0B
00006A30 42 30 30 42 30 42 30 42 42 30 42 30 56 42 42 30 B00B0B0BB0B0VBB0


Quindi il primo byte, a partire da $6A14, è rappresentato da:

($56,$42) ($42,30) ($30,$42) ($30,$42) ($42,$30) ($30,$42) ($30,$42) ($30,$42) ($42,$30) ($30,$42)=
Start 1 0 0 1 0 0 0 1 0(parità)

La sequenza va letta considerando in prima posizione il bit meno significativo per cui, scrivendo il numero nella maniera convenzionale risulta:

10001001=$89

Essendo il numero di bit 1 uguale a 3 che è dispari il bit di parità vale 0.
Analogamente per il successivo a partire da $6A28:

($56,$42) ($30,42) ($30,$42) ($30,$42) ($42,$30) ($30,$42) ($30,$42) ($30,$42) ($42,$30) ($42,$30)=
Start 0 0 0 1 0 0 0 1 1

Risulta:

10001000=$88

E così via fino alla posizione $6AC8 dove comincia la rappresentazione del primo byte dell'Header. Questo viene immagazzinato nella memoria del C64 a partire dall'indirizzo $033C (buffer di cassetta), occupa come detto 192 byte (sempre in memoria) ed è così definito:

033C: 01 01 08 10 08 50 52 4F 56 41 20 20 20 20 20 20 '.....PROVA '
034C: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 ' '
035C: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 ' '


L'Header va così interpretato:

$033C : tipo di file ($01=programma)
$033D-$033E: indirizzo di inizio ($0801)
$033F-$0340: indirizzo di fine ($0810)
$0341-$0351: nome file (16 caratteri, eventualmente riempiti da spazi se il nome è più corto)

Il resto dell'Header risulta costituito da $20 per un totale di 192 byte in memoria ovvero 3840 byte nel tap. A seguire viene memorizzato un byte di checksum che nel nostro caso vale $4A e viene così calcolato:

0 XOR $01 XOR $01 XOR $08 XOR $10 XOR $08 XOR $50 XOR $52 XOR $4F XOR $56 XOR $41 XOR $20 XOR $20 …

Per verificare che l'operazione restituisca effettivamente il valore $4A potete provare ad inserire tutti i byte dell'Header preceduti da $00 in un editor esadecimale ed usare la funzione per il calcolo del checksum ad 8 bit. A seguire il checksum troviamo un nuovo Pilot, rappresentato da una serie di $30 (appena 78). Troviamo poi memorizzata la seconda copia dell'Header che, per distinguersi dalla prima, viene preceduta dal seguente treno di sincronizzazione:

$09 $08 $07 $06 $05 $04 $03 $02 $01

Dopo la copia dell'Header ed il suo byte di checksum (ovviamente uguale a quello precedente!), alla posizione $8A45 vi è una pausa che nel tap viene rappresentata con 4 byte di cui il primo vale $00 e gli altri tre ne indicano la durata in cicli di clock del processore: nel nostro caso misura $04E200 ovvero 320000 cicli come riportato nel report; segue un'altra lunga sequenza di $30 che costituisce un nuovo Pilot fino al byte $9F49 dove comincia il treno di sincronizzazione del nostro programma. Finalmente a partire dal byte $9FFD troviamo:

($56,$42) ($30,42) ($42,$30) ($42,$30) ($42,$30) ($30,$42) ($30,$42) ($30,$42) ($30,$42) ($30,$42)=
Start 0 1 1 1 0 0 0 0 0

Ovvero:

00001110=$0E

Lo avete riconosciuto? Ebbene sì, è proprio lui, il primo byte del nostro programma! Ad esso ovviamente seguono tutti gli altri codificati nello stesso modo; il blocco si conclude col checksum che vale $CF. Un successivo blocco Pilot annuncia poi la seconda copia del nostro programma, preceduta dal secondo treno di sincronizzazione così come per l'Header; vi è poi il solito byte di checksum che vale sempre $CF ed infine, dopo un breve ulteriore blocco Pilot, il file si conclude con un blocco pausa della durata di $4B2B20 ovvero 4926240 cicli di clock.




minibanner no ads


Torna ai contenuti | Torna al menu