Monday, May 13, 2013

Commodore 64: Ritorno alle origini!


Se qualcuno mi chiedesse qual è il computer che ha maggiormente segnato l'home computing in Italia, non esiterei a rispondere: il Commodore 64!
A metà degli anni ottanta, macchine come il Vic 20 ed il Commodore 64 erano le protagoniste indiscusse del panorama informatico italiano di quegli anni.
Anzi, per dirla tutta, il Commodore 64 non veniva considerato come UN computer ma come IL computer (con buona pace per tutti i fan Sinclair!), più vicino ad un oggetto di cult che ad una semplice macchina. Inoltre, dato l'enorme successo riscosso, fiorivano riviste mitiche quali: Superfloppy 64, Commodore Computer Club, Commodore Gazzette, Bit, MC Micro Computer, Papersoft e tante altre.
Insomma la Commodore ha scritto la storia dell'informatica in Italia (e, forse, nel mondo), facendo appassionare all'informatica tantissime persone, contribuendo anche a realizzare il sogno di Bill Gates: un computer in ogni casa.
Oggi, ad oltre trent'anni di distanza, il ricordo di quel periodo è sempre vivo in chi, come il sottoscritto, lo ha vissuto.
Me ne rendo conto navigando su internet dove è possibile scaricare in modo gratuito tanti emulatori del Commodore 64 e moltissimo software ad esso dedicato.
Ancora oggi ci sono tantissimi appassionati che si dilettano a realizzare programmi per questo meraviglioso computer.
Un'ottima testimonianza è costituita dall'intervento del bravo Giovanni Simotti che, al codemotion 2012, ha efficacemente illustrato come ancora oggi, ad oltre trent'anni dalla sua nascita, sia possibile programmare il Commodore 64 con strumenti moderni.
Così anch'io mi sono detto: perchè non riprovarci? Ed allora, di buona lena, ho deciso di mettermi all'opera ed ho deciso di scrivere un'implementazione dell'algoritmo per lo sviluppo di sistemi integrali del totocalcio, presentato nel primo topic di questo blog.
La prima cosa che ho fatto è stata quella di procurarmi un buon emulatore.
La scelta è caduta sull'ottimo VICE che, oltre ad emulare diversi tipi di macchina di casa Commodore, dispone anche di un ottimo debbugger le cui modalità di utilizzo sono descritte abbastanza bene su questo link.
La seconda è stata quella di procurarmi un buon IDE che semplificasse la scrittura del programma da eseguire con l'emulatore.
La scelta è caduta sull'ottimo CBM .prg Studio di Arthur Jordison che permette di scrivere in modo ottimale programmi per vari tipi di computer di casa Commodore quali: VIC 20, Commodore 64, Commodore 128, Commodore 16, Plus/4 e Pet.
Una volta avuti a disposizione tali strumenti di sviluppo, ho provveduto a scrivere una prima stesura in basic dell'algoritmo che desideravo implementare.
Il codice che ho sviluppato è il seguente:

10 input"{clear}quante parite";n
20 if n<1 or n>16 then 10
30 dim bs(n): cs=1: pr$="": print: for i=1 to n
40 print"partita n.";i;"{left}";: input p$
50 cs=cs*len(p$): bs(i)=len(p$): pr$=pr$+p$
60 next: print: print"{down}colonne da sviluppare:"; cs
70 print: for i=1 to cs: gosub 80: print cl$: next: end
80 cl$="": pt=len(pr$)-bs(n): nc=i-1: for j=n to 1 step -1
90 cl$=mid$(pr$, nc-int(nc/bs(j))*bs(j)+pt+1, 1)+cl$
100 nc=int(nc/bs(j)):if j>1 then pt=pt-bs(j-1)
110 next: return

Tale programma, sebbene sia di dimensioni molto ridotte ed eseguibile praticamente su tutte le macchine di casa Commodore, ha il difetto di non essere facilmente "traducibile" in assembler.
Il motivo di tale difficoltà deriva dal fatto che non è semplice eseguire un conteggio per numeri maggiori di 255 su macchine a 8bit.
Per tale motivo, per ovviare a tale difficoltà, ho riscritto il programma in modo tale che il conteggio non sia più eseguito con un ciclo ma mediante operazioni più "semplici".
Per fare ciò, mi sono "ispirato" ad un contatore eseguito su una macchina di Turing. Chiunque voglia vedere come questo funzioni su una tale macchina, può visitare il sito, oppure scaricare il simulatore dal sito.
Il nuovo codice sviluppato, di cui lascio ai volenterosi il compito di capirne i dettagli implementativi, è il seguente:

10 input"{clear}quante parite";n
20 if n<1 or n>16 then 10
30 dim a(n), bs(n): cs=1: pr$="": print: for i=1 to n
40 print"partita n.";i;"{left}";: input p$
50 cs=cs*len(p$): bs(i)=len(p$): pr$=pr$+p$
60 next: print: print"{down}colonne da sviluppare:"; cs
70 ps=n: dr=0: print: for i=1 to n: a(i)=0: next
80 gosub 170
90 a(ps)=a(ps)+1
100 if a(ps)
110 dr=1
120 ps=ps-1: if ps>0 then a(ps)=a(ps)+1
130 if ps>0 and a(ps)=bs(ps) and dr=1 then 120
140 if a(1)=bs(1) then end
150 for i=ps+1 to n: a(i)=0: next
160 ps=n: dr=0: goto 100
170 pt=0: for i=1 to n: print mid$(pr$, a(i)+pt+1, 1);: pt=pt+bs(i): next
180 print: return

A questo punto, mi proposi di "tradurre" tale programma in assembler, ma desideravo che il codice che sarei andato a realizzare fosse multipiattaforma; ossia che potesse essere eseguito non solo su Commodore 64 ma anche su altre macchine quali: Vic 20, Commodore 128, Commodore 16 e Plus4.
Per fare ciò, ho fatto largo utilizzo dell'indirizzamento indiretto indicizzato basandolo sulle locazioni 251($FB) e 252($FC) di pagina zero. La scelta è caduta su di esse in quanto le locazioni da 251 a 254 in pagina zero sono libere su tutte le macchine cbm.
Inoltre, ho dovuto rinunciare all'utilizzo dell'istruzione JMP basando i salti all'interno del codice sulle istruzioni CLC e BCC.
Ho cercato di eseguire la "traduzione" in assembler, per quanto possibile, riga per riga e commentando il codice indicando anche l'inizio della corrispettiva riga in basic (relativa al codice precedente).
Il codice che ho scritto, che anche in questo caso lascio ai volenterosi il compito di capirne i dettagli implementativi, è il seguente:

; Tot16

*=$C000

start  LDY #$0
       LDA ($FB),Y
       TAY
       LDA #$0
loop1  STA ($FB),Y
       DEY
       BNE loop1
       LDA ($FB),Y
       TAY         ; y=n
       LDX #$0     ; x=0
cllprt JSR subprt  ; Chiama la subroutine per stampare la colonna   ** Riga 80 **
       CLC
       LDA ($FB),Y ; a(y)=a(y)+1
       ADC #$1
       STA ($FB),Y
loop2  PHA         ; tmp=a(y)
       TYA         ; y=y+n
       CLC
       LDY #$0
       ADC ($FB),Y
       TAY
       PLA         ; a=tmp
       CMP ($FB),Y ; if a(y)==b(y) || x==1 then chgdr   ** Riga 100 **
       BEQ chgdr
       CPX #$1
       BEQ chgdr
       PHA         ; tmp=a
       TYA         ; y=y-n
       SEC
       LDY #$0
       SBC ($FB),Y
       TAY
       PLA         ; a=tmp
       CLC         ; JMP cllprt
       BCC cllprt
chgdr  LDX #$1     ; x=1     ** Riga 110 **
       PHA         ; tmp=a
       TYA         ; y=y-n
       SEC
       LDY #$0
       SBC ($FB),Y
       TAY
       PLA         ; a=tmp
loop3  DEY         ; y--     ** Riga 120 **
       CPY #$0
       BEQ chkend
       CLC         ;         ** Riga 130 **
       LDA ($FB),Y ; a=a(y)+1
       ADC #$1
       STA ($FB),Y
       PHA         ; tmp=a
       TYA         ; y=y+n
       LDY #$0
       ADC ($FB),Y
       TAY
       PLA         ; a=tmp
       CMP ($FB),Y ; if a!=b(y)
       BNE chkend
       CPX #$1
       BNE chkend
       PHA         ; tmp=a
       TYA         ; y=y-n
       LDY #$0
       SEC
       SBC ($FB),Y
       TAY
       PLA         ; a=tmp
       CLC         ; JMP loop3
       BCC loop3
jmplp2 CLC         ; JMP loop2
       BCC loop2
chkend TYA         ; y=y-n   ** Riga 140 **
       LDY #$0
       SEC
       SBC ($FB),Y
       TAY         ; y=a
       PHA         ; tmp=a
       LDY #$0
       LDA ($FB),Y
       TAY
       INY
       LDA ($FB),Y ; a=b[1]
       LDY #$1
       CMP ($FB),Y
       BEQ end
       PLA         ; a=tmp
       TAY
loop4  INY         ; ** Riga 150 **
       TYA         ; a=y
       PHA         ; tmp=a
       LDA #$0
       STA ($FB),Y
       PLA         ; a=tmp
       LDY #$0
       CMP ($FB),Y
       BEQ endres
       TAY         ; y=a
       CLC         ; JMP loop4
       BCC loop4
endres LDX #$0     ; ** Riga 160 **
       TXA
       TAY
       LDA ($FB),Y
       TAY
       LDA ($FB),Y ; a=a[n]
       CLC         ; JMP loop2: è necessario un salto più "piccolo" a jmpl2
       BCC jmplp2
end    PLA          ; Svuota lo stack
       RTS
; Subroutine per stampare la colonna
subprt PHA         ; Salva l'accumulatore nello stack
       TXA
       PHA         ; Salva il registro x nello stack
       TYA
       PHA         ; Salva il registro y nello stack
       LDX #$0     ; x=0
       LDY #$0     ; y=0
       TYA         ; tmp=y
       PHA
loop5  PLA         ; y=tmp
       TAY
       INY         ; y=y+1
       TYA         ; tmp=y
       PHA
       TXA         ; a=x+a(y)+1+n*2
       CLC
       ADC ($FB),Y
       ADC #$1
       LDY #$0
       ADC ($FB),Y
       ADC ($FB),Y
       TAY         ; y=a
       LDA ($FB),Y ; stampa il carattere
       JSR $FFD2
       PLA         ; a=y(originario corrispondente allo i-esimo)
       PHA
       LDY #$0     ; x=x+a(y+n)
       ADC ($FB),Y
       TAY       
       LDA ($FB),Y
loop6  INX
       SEC
       SBC #$1
       BNE loop6
       LDY #$0     ; Compara l'y originario con n
       PLA
       PHA
       CMP ($FB),Y
       BNE loop5   ; Se non coincide continua con la stampa
       LDA #$D
       JSR $FFD2
       PLA         ; Svuota lo stack
       PLA         ; Recupera il registro y dallo stack
       TAY
       PLA         ; Recupera il registro x dallo stack
       TAX
       PLA         ; Recupera l'accumulatore dallo stack
       RTS         ; Esce dalla subroutine

Una volta scritto tale codice, restava da scriverne il loader che si occupasse di caricarlo in memoria, rilocandolo a seconda della macchina su cui viene eseguito.
Gli unici due problemi da risolvere erano i seguenti: individuare il tipo di macchina in cui il codice viene eseguito e determinare la posizione dove allocare il programma assembler nella memoria di tale macchina.
In merito al primo problema, su internet ho trovato la seguente discussione da cui ho estratto lo snippet che ho utilizzato nel mio codice.
Per quanto riguarda il secondo, invece, ho pensato di allocare il codice 2k dopo l'inizio dell'area dedicata ad ospitare il codice basic che su tutte le macchine cbm può essere individuata grazie alle locazioni 43 e 44.
Per il Commodore 64, sebbene non necessario, ho preferito scegliere in modo deterministico l'area di memoria che inizia all'indirizzo 49152($C000).
Infine, faccio esplicitamente osservare che, limitatamente alla sola istruzione "JSR subprt", si è reso necessario modificare 2 byte del codice assembler in funzione dell'indirizzo di memoria determinato per l'inizio del programma.
Il codice che ho scritto per fare ciò è il seguente:

10 cbm=peek(65534)
20 if peek(1177)=63 then poke1177,62: cbm=peek(65534): poke1177,63
30 if cbm=72 or cbm=114 or cbm=23 or cbm=179 then 50
40 print "solo per: c64, vic20, c128, c16, plus4": end
50 if cbm=72 then sa=49152: goto 70
60 sa=peek(44)*256+peek(43)+1024*2
70 o=235: for i=0 to o-1: read a: poke sa+i,a: next
80 input"{clear}quante parite";n
90 if n<1 or n>16 then 80
100 poke sa+o,n: cs=1: pr$="": print
110 for i=1 to n
120 print"partita n.";i;"{left}";: input p$
130 cs=cs*len(p$): poke sa+o+n+i,len(p$): pr$=pr$+p$
140 next: print: print"{down}colonne da sviluppare:"; cs
150 for i=1 to len(pr$): poke sa+o+n*2+i,asc(mid$(pr$, i, 1)): next
160 hi=int((sa+o)/256): lo=(sa+o)-hi*256: poke 251,lo: poke 252,hi
170 rem modifica nel codice l'indirizzo della chiamata a subprt
180 hi=int((sa+166)/256): lo=(sa+166)-hi*256: poke sa+18,lo: poke sa+19,hi
190 sys sa
200 end
210 data 160,0,177,251,168,169,0,145
220 data 251,136,208,251,177,251,168,162
230 data 0,32,166,192,24,177,251,105
240 data 1,145,251,72,152,24,160,0
250 data 113,251,168,104,209,251,240,16
260 data 228,1,240,12,72,152,56,160
270 data 0,241,251,168,104,24,144,217
280 data 162,1,72,152,56,160,0,241
290 data 251,168,104,136,192,0,240,38
300 data 24,177,251,105,1,145,251,72
310 data 152,160,0,113,251,168,104,209
320 data 251,208,19,224,1,208,15,72
330 data 152,160,0,56,241,251,168,104
340 data 24,144,216,24,144,173,152,160
350 data 0,56,241,251,168,72,160,0
360 data 177,251,168,200,177,251,160,1
370 data 209,251,240,32,104,168,200,152
380 data 72,169,0,145,251,104,160,0
390 data 209,251,240,4,168,24,144,238
400 data 162,0,138,168,177,251,168,177
410 data 251,24,144,199,104,96,72,138
420 data 72,152,72,162,0,160,0,152
430 data 72,104,168,200,152,72,138,24
440 data 113,251,105,1,160,0,113,251
450 data 113,251,168,177,251,32,210,255
460 data 104,72,160,0,113,251,168,177
470 data 251,232,56,233,1,208,250,160
480 data 0,104,72,209,251,208,210,169
490 data 13,32,210,255,104,104,168,104
500 data 170,104,96

Tale codice ha il notevole pregio di poter essere eseguito senza alcuna modifica su molte macchine di casa Commodore.
Che altro dire?
Anche a distanza di anni il Commodore 64 continua ad affascinare ed interessare tanti suoi fan, per cui non mi resta che augurare buona programmazione a tutti coloro che vorranno cimentarsi ancora una volta a realizzare programmi per quella che è stata, e resta, una macchina mitica!

0 Comments:

Post a Comment

<< Home