Programmare la REU con il C64

(Ringrazio Massimiliano ‘WizKid’ De Ruvo per avermi suggerito delle ottime fonti)

Segue la mia traduzione in italiano dell’articolo REU Programming di Robin Harbron.

Nota: questo articolo è apparso in origine sulla “Loadstar Letter #46” pubblicata nel 1997. Grazie al team di Loadstar per aver dato il permesso di pubblicazione sul mio sito (di Robin Harbron, NdT).

Vuoi sapere come usare la REU nei tuoi programmi? I seguenti programmi BASIC e assembly sono esempi di come verificare la presenza di una REU, determinare le sue dimensioni e quindi memorizzarci, prelevarci o scambiarci segmenti di memoria.

La REU è gestita tramite IO2, cioè le locazioni di memoria che vanno da $DF00 a $DFFF. La REU ha effettivamente 7 registri con alcuni registri che sono replicati, quindi tutte le operazioni di PEEK e POKE saranno effettuate sempre e comunque sulle locazioni di memoria che vanno da $DF00 a $DF0A (in decimale da 57088 a 57098).

Come determinare la presenza di una REU? Se la REU è presente, le locazioni che vanno da $DF02 a $DF05 manterranno i valori che sono in esse memorizzati. Non conosco altre periferiche che si comportano in questo modo, quindi le seguenti routine sfruttano questa proprietà. Nella versione BASIC (che usa solo una variabile per essere il più possibile discreta), X sarà uguale a 0 se non è presente una REU, sarà uguale a 1 se la REU è presente.

10 rem detect
20 forx=2to5:poke57088+x,x:nextx
30 forx=5to2step-1
40 ifpeek(57088+x)<>xthenx=1
50 nextx
60 ifx=0thenprint"no reu"

Nella versione assembly (SYS 16384 per far partire la routine da BASIC), l’accumulatore sarà 0 se non è presente una REU, varrà 1 se invece è presente. In BASIC sarà possibile leggere il contenuto dell’accumulatore attraverso PEEK(780) dopo aver chiamato la routine.

         *= $4000
         ; detect REU

         ldx #2
loop1    txa
         sta $df00,x
         inx
         cpx #6
         bne loop1

         ldx #2
loop2    txa
         cmp $df00,x
         bne noreu
         inx
         cpx #6
         bne loop2

         lda #1
         rts

noreu    lda #0
         rts

Ora, quanto è grande la REU installata? Questa è la mia soluzione (di Robin Harbron, NdT), anche se sembra non essere quella ottimale. Questa routine ci metterà un po’ di secondi nella versione BASIC (ovviamente la versione assembly non impiegherà del tempo significativamente percepibile). In parole povere, la routine scrive il numero di banco nel primo byte di ogni banco (tutti i possibili 256 banchi, il che significherebbe avere una REU da 16MB!). I banchi che non sono disponibili sembreranno “coprire” i banchi esistenti, quindi ciò che sarà scritto in un banco non-disponibile finirà per sovrascrivere ciò che è stato scritto in un precedente banco. Quindi andiamo a scorrere nuovamente i banchi cercando quanti valori crescenti e consecutivi riusciamo a trovare. Questo è il numero di banchi effettivamente disponibili. Questa routine è anche non-distruttiva. Legge e memorizza il contenuto originale della REU, quindi lo riscrive dov’era prima quando ha finito. La routine richiede un buffer di 257 byte per fare questo, a cui puntano la variabile S (nella versione BASIC) o la label temp (nella versione assembly). Dopo aver eseguito la versione BASIC, la variabile A conterrà il numero di banchi disponibili (da 64kB ciascuno, NdT). Per esempio, A=8 dopo l’esecuzione del programma significa avere una REU da 512kB. La versione assembly ritorna il numero di banchi all’interno dell’accumulatore.

10 rem size
15 s=49152
20 poke57090,0:poke57091,192
30 poke57092,0:poke57093,0
40 poke57095,1:poke57096,0
50 poke57098,0
60 forb=0to255:poke57094,b
63 pokes,b:poke57089,178
65 pokes+1+b,peek(s):nextb
70 b=0:o=0
80 poke57094,b:poke57089,177
90 n=peek(s)
100 ifn>otheno=n:b=b+1:goto80
110 a=b
120 forb=255to0step-1:poke57094,b
130 pokes,peek(s+1+b):poke57089,176
140 nextb
150 printa
         *= $4000
temp     = $c000
         ;detect reu size

         lda #0
         sta $df04
         sta $df05
         sta $df08
         sta $df0a
         lda #1
         sta $df07

         lda #temp
         sta $df03

         ldx #0
loop1    stx $df06
         stx temp
         lda #178
         sta $df01
         lda temp
         sta temp+1,x
         inx
         bne loop1

         ldy #177
         ldx #0
         stx old
loop2    stx $df06
         sty $df01
         lda temp
         cmp old
         bcc next
         sta old
         inx
         bne loop2
next     stx size
         ldy #176
         ldx #255
loop3    stx $df06
         lda temp+1,x
         sta temp
         sty $df01
         dex
         cpx #255
         bne loop3
         lda size
         rts
old      .byte 0
size     .byte 0

Veniamo ora allo scopo principale di una REU: memorizzare e restituire dati. La routine seguente è un modello che puoi adattare e usare. Può sia memorizzare sulla REU, sia prelevare dati dalla REU che fare swap tra REU e memoria principale, nel caso particolare si userà il contenuto della memoria video in bassa risoluzione come dati da scambiare.

Ecco una descrizione dettagliata dei registri:

$DF02-$DF03 (57090-57091) puntano all’indirizzo base della memoria del computer con la quale vogliamo operare. È memorizzato nel formato standard LO/HI. $0400 è l’indirizzo che ci andiamo a scrivere per operare con la memoria video.

$DF04-$DF06 (57092-57094) puntano all’indirizzo base della memoria nella REU che vogliamo utilizzare. È memorizzato nel formato LO/HI/BANK. Il numero di banco può essere pensato come a un byte “ulteriormente più alto”. Memorizzeremo le informazioni nella locazione $0 del banco 0.

$DF07-$DF08 (57095-57096) determinano la lunghezza in byte del trasferimento. È memorizzata anch’essa nel formato LO/HI. Memorizzando uno zero in questo registro equivale a specificare la lunghezza massima, cioè 64kB.

$DF0A permette di tenere fisso l’indirizzo della memoria del C64 o della REU, per permetterti di leggere o scrivere particolari locazioni più di una volta. Se il bit 7 è acceso, è fissato l’indirizzo della memoria del C64, se è acceso il bit 6 è fissato l’indirizzo della REU. Ciò ha diversi usi: puoi fissare l’indirizzo della REU (memorizzandoci per esempio 0 o 255) e settore la lunghezza a 8000 byte. Puoi quindi riempire un intero schermo bitmap con quel byte in proprio 8000 cicli o trasferendolo dalla REU al C64! Quest’ultimo modo di procedere è 4 volte più veloce rispetto alla stessa operazione fatta facendo uso della sola CPU. Altro esempio: campionare una particolare locazione di memoria; questo è stato fatto per studiare il generatore di numeri casuali del SID.

$DF01 (57089) esegue l’effettiva operazione. È necessario accendere il bit 7, per dire alla REU di eseguire l’operazione di MOVE. Gli altri bit dicono alla REU come effettuare tale operazione di MOVE:

  • Se il bit 5 è acceso, i registri di indirizzo e di lunghezza rimangono intatti dopo l’esecuzione del comando. Ciò è utile se hai intenzione di eseguire più trasferimenti consecutivi identici. Se il bit è spento, i registri di indirizzo punteranno all’indirizzo immediatamente successivo l’ultimo indirizzo a cui si è acceduto, e il registro di lunghezza sarà impostato a 1.
  • Se il bit 4 è acceso, il comando sarà eseguito immediatamente. Se è spento, il comando non sarà eseguito fin quando non verrà scritta la locazione $FF00. Può apparire strano, ma è utile nel caso si vogliano trasferire dati nelle aree di memoria nei dintorni delle ROM o dell’I/O. Questa opzione dà la possibilità di impostare i registri per il trasferimento, quindi fare switch su I/O (per esempio), quindi eseguire il trasferimento. Per far partire il trasferimento basterà un semplice LDA $FF00 : STA $FF00.
  • I bit 1 e 0 definiscono il tipo di trasferimento. 00 è per trasferire da C64 a REU, 01 per trasferire da REU a C64, 10 per effettuare uno swap tra REU e C64 (in questo caso il tempo impiegato è doppio in quanto doppio è il lavoro da fare), e 11 per effettuare un’operazione di compare tra REU e C64. Per usare la funzione di compare, spegnere il bit 5 di $DF00, eseguire il compare (esattamente come qualsiasi altro trasferimento, con la differenza che nessun byte sarà effettivamente spostato). Ora, se il bit 5 di $DF00 è acceso, allora è stata trovata almeno una differenza tra la memoria della REU e quella del C64.

Segue la versione BASIC. Devi solo impostare la variabile X a seconda del commento presente nella linea 60.

10 poke57090,0:poke57091,4:rem c64
20 poke57092,0:poke57093,0:poke57094,0:rem reu
30 poke57095,232:poke57096,3:rem length
40 poke57098,0:rem not fixed addresses
50 poke57089,128+16+x
60 rem x=0 c64->reu, x=1 reu->c64, x=2 swap c64 and reu

Nella versione assembly, bisogna solo impostare le label c64 all’indirizzo base C64, reu all’indirizzo base REU, bank al numero di banco della REU (ho usato 2 perché il mio assemblatore utilizza per sé i banchi 0 e 1), length al numero di byte da trasferire, e action a 0, 1, 2 o 3 a seconda del tipo di trasferimento che vuoi effettuare.

         *= $4000
         ;reu store/swap/get

c64      = $0400
reu      = $00
bank     = $02
length   = 1000
action   = 0

         lda #c64
         sta $df03
         lda #reu
         sta $df05
         lda #bank
         sta $df06
         lda #length
         sta $df08
         lda #0
         sta $df0a
         lda #144+action
         sta $df01
         rts

Riferimenti esterni:

 

Francesco Sblendorio

Francesco Sblendorio nasce nel 1977. Nel 1985 fa conoscenza con il mondo dei computer attraverso un Commodore 16: da quel momento la discesa verso il lato oscuro è inesorabile e si trasforma in un geek impenitente. Nell'ambito del retrocomputing ha un sogno: che i vecchi computer non debbano semplicemente sopravvivere (conservando la loro funzionalità), piuttosto devono vivere, attraverso nuovi software sviluppati oggi per i computer di ieri.

Potrebbero interessarti anche...