Table of Contents
Assembly e linguaggio macchina per il C64 e VIC 20
Machine Language Monitor
Un MLM (machine language monitor) in assembly è un tool che consente di:
- visualizzare locazioni di memoria
- scrivere/modificare locazioni di memoria
- eseguire codice da memoria
Per entrare nel VICE Monitor digitare Alt+H (Command+H per i Mac ). Quando è attivo il monitor non è attivo il Basic.
Visualizzare i registri
Visualizzare i registri (con 'R'):
(C:$e5d1) R ADDR A X Y SP 00 01 NV-BDIZC LIN CYC STOPWATCH .;e5d1 00 00 0a f3 2f 37 00100010 000 000 5425056 (C:$e5d1)
Leggere e scrivere nella memoria
Visualizzare il contenuto della memoria (con 'M'):
(C:$1011) M 033c 0348 >C:033c 00 00 00 00 00 00 00 00 00 00 00 00 00 ............. (C:$0349)
Le locazioni $033C-$0348 sono tutte vuote; scriviamo nella memoria (con '>C', per 'change memory') in linguaggio macchina:
(C:$0349) >C:033c ad 80 03 ae 81 03 8d 81 03 8e 80 03 00 (C:$0349)
Di seguito alcuni opcodes utilizzati:
Il programma consiste nello scambiare il contenuto delle locazioni $0380 e $381 (questo è ancora da verificare/modificare).
Per verificare il contenuto della memoria appena scritta:
(C:$0349) M 033c 0348 >C:033c ad 80 03 ae 81 03 8d 81 03 8e 80 03 00 ............. (C:$0349)
Verifichiamo e quindi copiamo dei valori nelle locazioni $0380 e $381:
(C:$0410) M $0380 $0381 >C:0380 00 00 (C:$0382) >C:0380 11 99 (C:$0382) M $0380 $0381 >C:0380 11 99
Adesso la locazione $0380 contiene il valore $11 e la $0381 contiene $99.
Eseguire codice da memoria
Eseguiamo il programma (con 'G') che inverte le locazioni:
(C:$0382) G 033c
Le due locazioni adesso hanno effettivamente il contenuto invertito:
(C:$e5d1) M 0380 0381 >C:0380 99 11
Disassemblare
Per disassemblare (tradurre da linguaggio macchina in assembly) le locazioni dove si è salvato il codice si fa così (con 'D'):
(C:$0382) D 033c 0348 .C:033c AD 80 03 LDA $0380 .C:033f AE 81 03 LDX $0381 .C:0342 8D 81 03 STA $0381 .C:0345 8E 80 03 STX $0380 .C:0348 00 BRK
Di seguito uno schema delle operazioni svolte da/verso registri CPU/memoria:
L'ultimo comando (BRK) presente in $0348 interrompe il Monitor e passa il controllo al Basic nel VICE. Anche qui possiamo verificare il valore, modificato, delle locazioni $0380 (896 in decimale) e $0381 (897):
? PEEK(896) 153 (cioè $99) ? PEEK(897) 17 (cioè $11)
Un altro modo per passare dal monitor al Basic è premendo 'X'.
Assemblare
Per assemblare (scrivere in assembly per poi essere tradotto in linguaggio macchina) si fa così (con 'A'):
(C:$034b) A 033c .033c LDA #$99 .033e STA $0380 .0341 LDX #$98 .0343 STX $0381 .0346 (C:$0346) M 033c 034a >C:033c a9 99 8d 80 03 a2 98 8e 81 03 00 00 00 00 00 ............... (C:$034b)
Alla locazione 0346 è stato digitato Return, per indicare la fine dell'input.
Il carattere '#' indica che il valore da caricare nel registro non è quello di una locazione di memoria, ma un valore immediato (o letterale).
Basic
L'esecuzione può avvenire anche nel Basic del C64:
SYS 828
Dove '828' è il decimale di $033C.
Il programma, che carica i valori $99 e $98 nelle locazioni $0380 e $0381, è stato eseguito:
(C:$e5d1) M 0380 0381 >C:0380 99 98 ..
CHROUT
Per scrivere a video possiamo usare il seguente codice:
(C:$e5cd) >C:0400 3 9 1 f 20 12 f 2 5 12 14 f (C:$e5cd) X
dove $0400 è l'inizio della memoria video del C64; i caratteri che seguono non sono ASCII, ma screen codes (vedi Appendice B della C64 Programmer's Reference Guide).
Il codice produrrà il seguente output:
Invece che scrivere direttamente in memoria, si possono usare le KERNAL subroutines, salvate nella ROM omonima. Uno dei vantaggi di queste è che usano i codici ASCII; ad es. la CHROUT ($FFD2) mostra a video il contenuto del registro A.
(C:$e5cf) A 033c LDA #$41 .033e JSR $FFD2 .0341 LDA #$42 .0343 JSR $FFD2 .0346 LDA #$43 .0348 JSR $FFD2 .034b (C:$034b) G 033c
A video comparirà velocemente la scritta 'ABC' ($41 $42 $43).
Aggiungendo 'RTS' (Return from subroutine):
.034b RTS
è possibile eseguire il comando dal Basic:
SYS 828
L'indirizzamento di memoria della KERNAL ROM va da $E000 a $FFFF, come indicato nella memory map.
Loop
Ipotizziamo di voler ottimizzare il precedente codice memorizzando, invece di 'ABC' la sequenza 'HELLO' in locazioni di memoria consecutive e leggerle tramite un loop.
(C:$e5cd) A 033c .033c LDX #$00 .033e LDA $034A,X .0341 JSR $FFD2 .0344 INX .0345 CPX #$06 .0347 BNE $033E .0349 RTS
Per questo loop andremo ad utilizzare il registro X perché:
- il registro X può essere incrementato (riga $0344), a differenza dell'Accumulatore
- può essere implementato, assieme all'accumulatore, per ottenere un indirizzamento di tipo Absolute Indexed mode (vedi riga $033e)
Le righe $0345 e $0347 possono essere spiegate così: CPX ('compare X') in realtà effettua una sottrazione del valore immediato $06 dal registro X. In questo modo, si ottiene un valore, che se non è zero (BNE, che verifica il flag Zero) comporta un branch, creando così il loop.
Il branch è sempre relativo, con valori +-127 byte rispetto a dove è stato invocato.
Il flag Carry, oltre che dalle operazioni aritmetiche, viene influenzato anche dalle operazioni di comparazione CPX, CPY e CMP, come indicato qui di seguito.
- BCS effettua il branch se il valore nel registro (X,Y o A) è >= del valore comparato (flag Carry=1)
- BCC effettua il branch se il valore nel registro (X,Y o A) è < del valore comparato (flag Carry=0)
Ma in questo caso è quindi necessario cambiare anche la comparazione in CPX #$07, con un valore maggiore di uno rispetto a quello cercato.
Riassumendo, dopo aver effettuato una comparazione, si possono usare i seguenti mnemonics per ottenere dei branch, a seconda delle varie condizioni:
Condizione | Flag | Branch da usare | Note |
---|---|---|---|
“registro uguale a” | Z - Zero | BEQ (branch equal 0) | |
“registro diverso a” | Z - Zero | BNE (branch not equal 0) | |
“registro minore di” | C - Carry | BCC (branch carry clear) | |
“registro maggiore o uguale a” | C - Carry | BCS (branch carry set) | |
“registro negativo” (MSB=1) | N - Negative | BMI (branch minus) | |
“registro positivo” (MSB=0) | N - Negative | BPL (branch plus) | |
“overflow numero con segno” (bit 6=1) | V - Overflow | BVS (branch overflow set) | |
“no overflow numero con segno” (bit 6=0) | V - Overflow | BVC (branch overflow clear) |
Per stampare la stringa voluta, sarà sufficiente memorizzare i valori ASCII esadecimali di HELLO e RETURN a partire dalla locazione $034a. Sono valori che dobbiamo memorizzare come tali, non possiamo assemblarli:
(C:$0368) >C:034a 48 45 4c 4c 4f 0d
Richiamando la subroutine dal Basic otteniamo:
Salvare
Per potere salvare il codice bisogna essere in Basic; bisogna:
- reperire, tramite dei PEEK, i valori sia del codice che i valori di testo di HELLO
- ricrearli poi con dei POKE
Recuperiamo intanto il codice e valori stringa; il codice in assembly è compreso tra $033c e $034f, che corrispondono ai decimali 828 e 847. Quindi:
Si tratta dei valori decimali del codice esadecimale inserito nelle locazioni. Tali valori possono poi essere ricopiati tramite comandi Basic DATA e memorizzati con dei POKE:
Tale codice, che ricrea il codice assembly di prima, si può ovviamente salvare come un qualsiasi programma Basic:
Per salvare su nastro usa il comando File-Create and attach datassette image…. Dopodiché si potrà salvare il listato ed eseguirlo:
GETIN e STOP
Un'altra KERNAL subroutine, oltre alla già vista CHROUT, è GETIN ($FFE4), che testa la pressione di un tasto (simile al comando Basic GET) e lo salva nel registro A in formato ASCII.
C'è anche STOP ($FFE1), che testa la pressione di RUN/STOP.
Il seguente programma testa proprio inizialmente la pressione di RUN/STOP ed in tal caso effettua un opportuno RTS:
(C:$e5d4) A 033C JSR $FFe1 .033f BEQ $0351 ... .0351 RTS
Successivamente chiama la KERNAL routine GETIN e ottiene quindi la pressione di un tasto, che viene salvato in A sotto forma di codice ASCII:
... .0341 JSR $FFe4 .0344 CMP #$30 .0346 BCC $033C .0348 CMP #$3A .034a BCS $033C .034c JSR $FFD2 .034f NOP .0351 RTS
Siccome in questo programma ci interessano solo i numeri da 0 a 9 (codici ASCII da $30 a $39), le righe $0344-$0346 testano se il codice ASCII digitato è minore di 30 (vedi uso BCC sopra) mentre le righe $0348-$034a testano se il codice ASCII digitato è maggiore o uguale di 3A; non essendo questi valori accettabili, in entrambe le condizioni il programma torna all'inizio per un ulteriore verifica del pulsante premuto.
Una tabella completa dei codici ASCII (PETSCII) per il C64 è presente qui.
Quindi solo la pressione di numeri 0-9 produrrà un output:
Esempio 1
Questo altro programma testa la pressione dei tasti '4' oppure RUN/STOP:
.C:033c 20 E1 FF JSR $FFE1 .C:033f F0 0D BEQ $034E .C:0341 20 E4 FF JSR $FFE4 .C:0344 C9 34 CMP #$34 .C:0346 D0 F4 BNE $033C .C:0348 20 D2 FF JSR $FFD2 .C:034b 20 3C 03 JSR $033C .C:034e 60 RTS
Esempio 2
Altro esempio, in cui si vuole consentire la visualizzazione dei soli caratteri alfanumerici (0-9 e A-Z):
(C:$0354) d 033c 0357 .C:033c 20 E1 FF JSR $FFE1 ; verifica RUN/STOP .C:033f F0 1C BEQ $035D ; se premuto, vai RTS .C:0341 20 E4 FF JSR $FFE4 ; testa pulsante .C:0344 C9 30 CMP #$30 ; .C:0346 90 F4 BCC $033C ; se < $30 ('0') vai ad inizio .C:0348 C9 75 CMP #$5B ; .C:034a B0 F0 BCS $033C ; se >= $5B (carattere dopo 'Z') vai ad inizio .C:034c C9 3A CMP #$3A ; .C:034e 90 07 BCC $0357 ; se < $3A ('9') vai a stampa carattere .C:0350 C9 41 CMP #$41 ; .C:0352 B0 03 BCC $033c ; se < $41 ('A') vai ad inizio .C:0357 20 D2 FF JSR $FFD2 ; stampa carattere .C:035a 20 3C 03 JSR $033C ; vai ad inizio .C:035d 60 RTS ; return from subroutine
Il codice sopra si prende cura di non visualizzare i caratteri prima dello '0', dopo la 'Z' e in mezzo tra le cifre e le lettere.
AND, ORA e EOR
Con gli operandi AND, ORA e EOR si possono effettuare operazioni sui singoli bit dell'accumulatore A, senza toccare gli altri bit (non ci sono equivalenti per i registri X o Y).
- per azzerare un bit dell'accumulatore A si può usare AND, funzionando quindi come una mask; ad es. per azzerare il suo LSB (il bit meno significativo) si può usare
AND #$FE
- per settare a 1 un bit dell'accumulatore A si può usare OR; ad es. per rettare a 1 il suo MSB (il bit più significativo,) si può usare
ORA #$80
- per invertire alcuni bit dell'accumulatore A si può usare EOR:
Addizione
Per sommare dei numeri si usa:
- l'accumulatore A (non i registri X e Y) e un byte di memoria
- l'operando ADC “ADd with Carry”
- non esiste un operando di addizione senza Carry, ma è possibile cancellare il flag Carry, prima dell'ADC, con CLC
- il risultato dell'operazione resta nell'accumulatore A
L'esempio seguente mostra la somma di due numeri da 2 byte ciascuno ($03A1:$03A0 + $03B1:$03B0 = $03C1:$03C0).
(C:$034f) d 033c 034c .C:033c 18 CLC ; Carry azzerato .C:033d AD A0 03 LDA $03A0 ; copia valore da locazione $03A0 in A .C:0340 6D B0 03 ADC $03B0 ; somma valore in A con valore in locazione $03B0; qui il carry può essere a 1 .C:0343 8D C0 03 STA $03C0 ; salva somma in A in $03C0 .C:0346 AD A1 03 LDA $03A1 ; copia valore da locazione $03A1 in A .C:0349 6D B1 03 ADC $03B1 ; tiene conto del valore del carry precedente e fa la somma dell'high byte .C:034c 8D C1 03 STA $03C1 ; salva somma in A in $03C1
Qui vengono caricati dei valori nelle locazioni di memoria oggetto di somma:
(C:$03a2) >C:03a0 8d 45 (C:$03a2) >C:03b0 7a 52
Sottrazione
Prima di effettuare una sottrazione conviene settare il carry (borrow) flag con SEC.
Il seguente esempio sottrae due valori (8D-45) e salva il risultato in $0382:
(C:$e5d4) >C:0380 8d 45 ; carica i valori degli operandi in $0380 e $0381 .C:033c 38 SEC .C:033d AD 80 03 LDA $0380 ; A=8D .C:0340 ED 81 03 SBC $0381 ; A=8D-45 .C:0343 8D 82 03 STA $0382 ; $0382=A .C:0346 60 RTS (C:$04c2) m 0382 0383 >C:0382 48 00
Shift register
ASL
Per effettuare uno shift register a sinistra, dell'accumulatore o di una locazione di memoria, si possono usare ASL o ROL:
- ASL: non usa il carry nel primo bit LSB, che imposta a 0
- ROL: usa il carry nel primo bit LSB
Il seguente programma effettua uno shift left di A e salva il risultato nella locazione $0349; dopo 7 iterazioni un bit si sposta dal bit 0 al bit 7:
(C:$034b) d 033c 0347 .C:033c A9 01 LDA #$01 .C:033e A2 07 LDX #$07 .C:0340 0A ASL A .C:0341 CA DEX .C:0342 D0 FC BNE $0340 .C:0344 8D 49 03 STA $0349 .C:0347 00 BRK (C:$0489) m 0349 034a >C:0349 80 00
Ad ogni iterazione, il registro A viene moltiplicato di 2. Infatti, la locazione $0349 contiene, dopo 7 iterazioni, il valore $80.
ROL
Invece, il seguente programma simula l'utilizzo di un numero composto da un low byte ($0351) e da un high byte ($0352); viene caricato il valore $8F nel low byte e, tramite un ASL e subito dopo un ROL, passando per il Carry, dopo 8 iterazioni, il valore finisce spostato nell'high byte:
(C:$0348) d 033c 0352 .C:033c A9 00 LDA #$00 .C:033e 8D 52 03 STA $0352 .C:0341 A9 8F LDA #$8F .C:0343 8D 51 03 STA $0351 .C:0346 A2 08 LDX #$08 .C:0348 0E 51 03 ASL $0351 .C:034b 2E 52 03 ROL $0352 .C:034e CA DEX .C:034f D0 F7 BNE $0348
Subroutine
Con i comandi TAX, TXA, TAY, TYA si copia il contenuto di A in X o Y e viceversa. Qui di seguito un programma che li usa; il programma effettua la somma di due numeri ad una cifra.
La prima parte riprende quanto evidenziato sopra, cioè:
- verifica la pressione di RUN/STOP
- accetta l'input di numeri nel range 0-9
- stampa il numero
- con l'istruzione AND #$0F prende in considerazione la parte destra del codice ASCII, in modo che se il tasto premuto è 0, il codice ASCII è $30 e viene memorizzato $0, cioè il valore binario effettivo:
.C:033c 20 E1 FF JSR $FFE1 .C:033f F0 10 BEQ $0351 .C:0341 20 E4 FF JSR $FFE4 .C:0344 C9 30 CMP #$30 .C:0346 90 F4 BCC $033C .C:0348 C9 3A CMP #$3A .C:034a B0 F0 BCS $033C .C:034c 20 D2 FF JSR $FFD2 .C:034f 29 0F AND #$0F .C:0351 60 RTS
Questa prima parte è una subroutine, che viene richiamata più volte dal codice vero e proprio sottostante.
La seconda parte richiama due volte la subroutine di input ed effettua e stampa la somma di due numeri nel range 0-9:
.C:0352 20 3C 03 JSR $033C ; richiama subroutine di input .C:0355 8D C0 03 STA $03C0 ; salva il valore binario del primo addendo in una locazione .C:0358 A9 2B LDA #$2B ; codice ASCII per '+' .C:035a 20 D2 FF JSR $FFD2 .C:035d 20 3C 03 JSR $033C .C:0360 AA TAX ; salva il valore binario del secondo addendo nel registro X, visto che non viene usato .C:0361 A9 3D LDA #$3D ; codice ASCII per '=' .C:0363 20 D2 FF JSR $FFD2 .C:0366 8A TXA ; riporta il valore binario del secondo addendo in A .C:0367 18 CLC ; non considerare Carry .C:0368 6D C0 03 ADC $03C0 ; somma di A, che contiene il secondo addendo, con la locazione in cui è stato memorizzato il primo addendo .C:036b AA TAX ; salva il valore binario della somma (questa volta) nel registro X, visto che non viene usato .C:036c C9 0A CMP #$0A .C:036e 90 07 BCC $0379 ; se somma <$0A (10) vai a stampa_risultato .C:0370 A9 31 LDA #$31 ; somma è >=$0a: stampa "1" .C:0372 20 D2 FF JSR $FFD2 .C:0375 8A TXA ; copia valore binario somma presente in X in A .C:0376 38 SEC .C:0377 E9 0A SBC #$0A ; A (somma) = A - $0A .C:0379 09 30 ORA #$30 ; stampa risultato; aggiungi '$3' nel nibble di sinistra, così da ottenere il valore ASCII della somma dei due addendi .C:037b 20 D2 FF JSR $FFD2 .C:037e A9 0D LDA #$0D ; stampa Return .C:0380 20 D2 FF JSR $FFD2 .C:0383 60 RTS
Qui di seguito un esempio in cui si effettua una sottrazione di due valori, sempre usando la subroutine presente in $033C:
.C:0352 20 3C 03 JSR $033C .C:0355 8D C0 03 STA $03C0 ; salva minuendo in $03C0 .C:0358 A9 2D LDA #$2D .C:035a 20 D2 FF JSR $FFD2 .C:035d 20 3C 03 JSR $033C ; salva sottraendo in $03C1 .C:0360 8D C1 03 STA $03C1 .C:0363 A9 3D LDA #$3D .C:0365 20 D2 FF JSR $FFD2 .C:0368 AD C0 03 LDA $03C0 .C:036b 38 SEC .C:036c ED C1 03 SBC $03C1 ; effettua $03C0-$03C1 e lascia in A .C:036f 09 30 ORA #$30 .C:0371 20 D2 FF JSR $FFD2 .C:0374 A9 0D LDA #$0D .C:0376 20 D2 FF JSR $FFD2 .C:0379 60 RTS
Qui di seguito un altro esempio che testa se un numero è pari o dispari facendo un LSR (logical shift right) e controllando il carry (usa la stessa subroutine sopra). Se C=1 è dispari, altrimenti pari:
.C:0352 20 3C 03 JSR $033C .C:0355 4A LSR A .C:0356 90 24 BCC $037C ; C=0, vai a stampa ' IS EVEN' .C:0358 A9 20 LDA #$20 ; C=1, stampa 'IS ODD' (spazio) .C:035a 20 D2 FF JSR $FFD2 .C:035d A9 49 LDA #$49 ; 'I' .C:035f 20 D2 FF JSR $FFD2 .C:0362 A9 53 LDA #$53 ; 'S' .C:0364 20 D2 FF JSR $FFD2 .C:0367 A9 20 LDA #$20 ; (spazio) .C:0369 20 D2 FF JSR $FFD2 .C:036c A9 4F LDA #$4F ; 'O' .C:036e 20 D2 FF JSR $FFD2 .C:0371 A9 44 LDA #$44 ; 'D' .C:0373 20 D2 FF JSR $FFD2 .C:0376 A9 44 LDA #$44 ; 'D' .C:0378 20 D2 FF JSR $FFD2 .C:037b 60 RTS .C:037c A9 20 LDA #$20 ; (spazio) .C:037e 20 D2 FF JSR $FFD2 .C:0381 A9 49 LDA #$49 ; 'I' .C:0383 20 D2 FF JSR $FFD2 .C:0386 A9 53 LDA #$53 ; 'S' .C:0388 20 D2 FF JSR $FFD2 .C:038b A9 20 LDA #$20 ; (spazio) .C:038d 20 D2 FF JSR $FFD2 .C:0390 A9 45 LDA #$45 ; 'E' .C:0392 20 D2 FF JSR $FFD2 .C:0395 A9 56 LDA #$56 ; 'V' .C:0397 20 D2 FF JSR $FFD2 .C:039a A9 45 LDA #$45 ; 'E' .C:039c 20 D2 FF JSR $FFD2 .C:039f A9 4E LDA #$4E ; 'N' .C:03a1 20 D2 FF JSR $FFD2 .C:03a4 60 RTS
Indirizzamento Indirect
L'unica istruzione che usa la modalità di indirizzamento Indirect è JMP. La sintassi è la seguente:
JMP ($380)
dove $380 non è la destinazione del jump, come nel caso usuale per questa istruzione; in $380 è contenuto il LSB della destinazione vera e propria (e nel successivo $381 c'è il MSB). $380 è un indirect address (in sostanza, un puntatore) alla destinazione vera e propria:
Il programma completo:
.C:033c 6C 80 03 JMP ($0380)
Qui di seguito vengono caricati in memoria il LSB e MSB dell'indirizzo finale:
(C:$0410) >c:380 84 03 (C:$0410) m 380 >C:0380 84 03 00 00
E da $384 le istruzioni finali:
(C:$0373) d 380 .C:0380 84 03 STY $03 .C:0382 60 RTS .C:0383 EA NOP .C:0384 A9 49 LDA #$49 .C:0386 20 D2 FF JSR $FFD2 .C:0389 A9 4E LDA #$4E .C:038b 20 D2 FF JSR $FFD2 .C:038e A9 44 LDA #$44 .C:0390 20 D2 FF JSR $FFD2 .C:0393 60 RTS
Esecuzione del programma:
Indirizzamento Indirect, Indexed
L'indirizzamento di tipo Indirect, Indexed consente di raggiungere tutte le locazioni di memoria con poche istruzioni. Un limite di questo tipo di indirizzamento è che l'indirect address (quello specificato tra parentesi) deve risiedere nell'area di memoria Zero Page, da $0000 a $00FF, a differenza dell'indirizzamento indirect. Il problema è che la Zero Page è preziosa e ci sono pochissimi indirizzi che possono essere usati senza problemi, ad es. il range da $FC a $FF, che consente l'utilizzo di due indirect address completi (2 byte ciascuno).
Di seguito un esempio che cancella lo schermo; usa lo screen code dello 'spazio' (non ASCII code) per riempire le locazioni della screen RAM ($0400-$07e7):
.C:033c A2 04 LDX #$04 .C:033e A9 00 LDA #$00 .C:0340 85 FB STA $FB .C:0342 A9 04 LDA #$04 .C:0344 85 FC STA $FC ; imposta l'indirect address $FB:FC a $0400 (inizio della memoria video del C64) .C:0346 A9 20 LDA #$20 ; screen code per 'spazio' .C:0348 91 FB STA ($FB),Y; salva lo screen code di 'spazio' al puntatore $FB:FC (inizialmente $0400) + Y (inizialmente 0) .C:034a C8 INY .C:034b D0 FB BNE $0348 ; cicla fino a che Y=255 .C:034d E6 FC INC $FC ; incrementa MSB (inizialmente da $04 a $05) .C:034f CA DEX ; decrementa X, così da fare 4 cicli totali (256x4=1024 byte di memoria video interessata) .C:0350 D0 F6 BNE $0348 ; .C:0352 60 RTS
In realtà le locazioni della screen RAM sono solo 1000, quindi il codice sopra non è ottimizzato, andando a inserire $20 in altre locazioni, tra cui i puntamenti degli sprite:
Start & End of Basic
Il Start-of-Basic e l'End-of-Basic è definito in questi registri:
Da questi valori di default, si capisce che i byte disponibili per Basic, all'accensione del C64, sono
40960-2049 = 38911
Kick Assembler
Un assembler avanzato è Kick Assembler; qui un corso che spiega come creare un gioco usando questo ambiente di sviluppo.
Nel classico comando per eseguire un programma da disco:
LOAD "*",8,1
il parametro '1' indica al computer di caricare in memoria il programma da disco iniziando alla locazione di memoria specificata dai primi due byte del file (programma) da caricare, che solitamente è $0801.
Se si effettua un LIST del programma appena caricato compare solitamente una singola linea:
Che quindi punta alla locazione 2049 ($080b); quella è la locazione da dove parte il programma vero e proprio, quindi abbastanza all'inizio dello Start-of-Basic.
All'inizio di ogni codice di Kick Assembler bisogna inserire la seguente istruzione:
// $801 BasicUpstart2(main) // $810 main:
Quello che fa BasicUpstart2(main) è il seguente piccolo programma in Basic:
10 SYS2064
Quindi, nella posizione $801 viene inserito il programma 10 SYS2064, che quindi rimanderà alla locazione 2064 ($810), dove sarà presente il codice main:.
VIC 20
VICMON
VICMON è un MLM per VIC20. Per eseguirlo su VICE è necessario
- avere il file in formato .crt
- impostare la cartuccia come indicato qui e cioè:
Hello World
Di seguito il codice per stampare a video “HELLO WORLD”.
., 1100 ldx #$00 ., 1102 lda $110e,x ., 1105 beq $110d ., 1107 jsr $ffd2 ., 110a inx ., 110b bne $1102 ., 110d brk
Questi i valori ASCII da memorizzare:
.m 110e .:110e 48 45 4c 4c 4f .:1113 20 57 4f 52 4c .:1118 44 00
Conviene prima della 'H', inserire il valore ASCII $93 per cancellare lo schermo, quindi le locazioni di memoria dove viene salvato il testo cambiano così:
.m 110e .:110e 93 48 45 4c 4c .:1113 4f 20 57 4f 52 .:1118 4c 44 00
Variante senza CHROUT
Una variante prevede l'output a video senza l'uso della subroutine kernal CHROUT, ma effettuando l'output direttamente sulla memoria video del VIC 20 $1E00-$1FFF (7680-8191).
Come prima cosa conviene sfruttare gli screen codes della scritta già presente a video:
Poi basta cambiare l'indirizzo della prima locazione di memoria in $1113 (dando Return ad ogni riga), per sovrascrivere le locazioni con i codici ASCII in Screen codes:
Il codice è stato cambiato, ed è stato necessario modificare sia la mappa schermo che la mappa colore (in rosso), altrimenti le scritte non sarebbero comparse; inoltre, a causa dello scrolling, è stato necessario modificare le locazioni schermo e colore a partire dalla sesta riga (a partire quindi dalle locazioni $1E6E - 7790 per lo schermo e $966E - 38510 per la mappa colore), così che la scritta non scomparisse in alto.
Mappa della memoria
Programmando il VIC 20 in assembly, si va direttamente ad utilizzare/scrivere sulla sua memoria; può essere quindi utili conoscere alcune aree della memoria, così da comprenderne meglio il comportamento del computer.
Reset vector
L'indirizzo del reset vector è nella memoria in KERNAL rom ($E000-$FFFF 57344-65535); è contenuto in due indirizzi di memoria, $FFFD - 65533 (high byte) e $FFFC - 65532 (low byte); facendo un po' di calcoli si ottiene:
che è l'indirizzo del reset vector ($FD22 - 64802), che può quindi essere richiamato, ad es. da BASIC, per effettuare un reset del computer:
La routine Kernal vera e propria presente in $FD22 è abbastanza complessa; incollo qui di seguito le prime istruzioni:
Start-of-Basic RAM
L'inizio della RAM Basic è definito, a seconda che il VIC 20 sia o meno espanso, così:
tratto da qui. Il vettore di inizio Basic è dato dal contenuto di due locazioni, 43 (low byte) e 44 (high byte); il valore $1001 è confermato:
Questo valore e i successivi, potrebbero interferire con il codice assembly che scriviamo; fino ad adesso per questo abbiamo utilizzato $1100 - 4352: un programma Basic potrebbe andare a sovrascriverlo. E' consigliabile quindi spostare in avanti il vettore di Start-of-Basic; come indicato qui conviene procedere così:
- settare a '0' il valore della prima locazione della nuova area Basic
- impostare i valori della nuova area Basic al valore delle prima locazione + 1
- dare 'NEW' in Basic, così lo stesso si posiziona correttamente nella nuova area
Per la nuova locazione dello Start-of-Basic prendiamo l'attuale + 1024, cioè 4096+1024=5120; per ottenere quanto sopra si procede quindi così:
POKE 5120,0 POKE 43,1:POKE 44,20:NEW
Infatti 5121=1+20*256.
Per conferma, si può provare a digitare il seguente programma in BASIC e verificare dove viene memorizzato:
1234PRINT"CIAO"
e controllare quello che viene memorizzato nel nuovo Start-of-Basic a $1400:
dove:
- '00' è il primo byte a $1400
- '140D' è il Next line link
- '04D2' è '1234'
- '99' è il token di PRINT
- poi seguono i PETSCII di “CIAO”
- '00' è il termine della riga
- la prossima riga inizia, come da indicazioni sopra (Next link link), a '140D'
Espansione RAM
Con il VIC 20 è possibile usare una scheda che consente di espandere il computer, così come le originali cartucce da 3K, 8K o 16K. Ad es., una scheda compatibile è la seguente:
I settaggi DIP switch corrispondono alle linee del connettore di espansione del computer:
Dove:
Linea | RAM aggiuntiva | Note |
---|---|---|
RAM1 | 1 KB | Nel BLK0 |
RAM2 | 1 KB | Nel BLK0 |
RAM3 | 1 KB | Nel BLK0 |
BLK1 | 8 KB | |
BLK2 | 8 KB | |
BLK3 | 8 KB | |
BLK4 | 8 KB |
Qui uno schema della memoria di un VIC 20:
Di seguito è spiegato come settare i DIP switch per ottenere le espansioni RAM:
RAM1 | RAM2 | RAM3 | BLK1 | BLK2 | BLK3 | BLK5 | Diag | Espansione | Basic (bytes free) |
---|---|---|---|---|---|---|---|---|---|
1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 3K | 6655 |
0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 8K | 11775 |
1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 16K | 19967 |
1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 24K | 28159 |
Qui viene spiegato meglio l'uso della RAM e delle espansioni. In sostanza quello che viene visto dal Basic come memoria aggiuntiva non è necessario ai programmi, se questi usano il linguaggio macchina.