====== 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)
L'area di memoria **$033c-$03fb** è il //datassette buffer//, valida per brevi programmi di massimo 192 bytes
Di seguito alcuni **//opcodes//** utilizzati:
{{:content:retrocomputing:opcodes01.png|}}
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:
{{:content:retrocomputing:assembly01.png?400|}}
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:
{{:content:retrocomputing:vice01.png?500|}}
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
{{:content:retrocomputing:vice02.png?500|}}
L'indirizzamento di memoria della **//KERNAL ROM//** va **da $E000 a $FFFF**, come indicato nella **[[https://sta.c64.org/cbm64mem.html|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)
Un indirizzamento di tipo //absolute indexed mode// è limitato a soli 256 byte (il valore del registro X o Y); per incrementare questo limite si può usare un tipo di indirizzamento //[[#indirizzamento_indirect_indexed|Indirect, Indexed]]//
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.
Un'**alternativa a BNE** può essere anche **BCC**, che sfrutta il //flag Carry//; nella fattispecie:
* **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)||
Le istruzioni di //branch// occupano due byte, //operand// compreso. Quindi l'indirizzo di destinazione (1 byte) effettivo è di tipo **relativo con segno (+- 128)**, quindi, nello specificarlo, bisogna tenere conto di questa limitazione
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:
{{:content:retrocomputing:vice03.png?500|}}
==== 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:
{{:content:retrocomputing:vice04.png?500|}}
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//:
{{:content:retrocomputing:vice05.png?500|}}
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:
{{:content:retrocomputing:vice06.png?500|}}
Cancellando il programma Basic (ad esempio con 'NEW') il codice in memoria rimane comunque
==== 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
{{:content:retrocomputing:vice-run-stop.png?500|}}
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.
{{:content:retrocomputing:vice-ascii-0-9.png|}}
Una **tabella completa dei codici ASCII (PETSCII)** per il C64 è presente **[[https://sta.c64.org/cbm64pet.html|qui]]**.
Quindi solo la pressione di numeri 0-9 produrrà un output:
{{:content:retrocomputing:vice-0-9.png?500|}}
=== 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**:
{{:content:retrocomputing:img_20240609_041508.png?400|}}
==== 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).
In realtà, si tratta di due distinte operazioni, che danno come risultato $03C0 e $03C1, che non sono legate tra di loro, se non dal fatto che il //carry// della prima viene riportato con ADC nella seconda
{{:content:retrocomputing:adc-carry01.png?400|}}
(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:
{{:content:retrocomputing:vice-asl.png?400|}}
(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//:
{{:content:retrocomputing:vice-rol01.png|}}
(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
Per effettuare uno **//shift register// a destra** si usano invece **LSR** o **ROR**
==== 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 **[[#esempio_2|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
{{:content:retrocomputing:vice-somma01.png?500|}}
Bisogna fare attenzione con TAX e TXA, in quanto può capitare che il valore riportato in A non sia quello in precedenza salvato in X. L'esempio di seguito ne è privo e preferisce salvare due valori nelle locazioni, invece che nel registro X
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
{{:content:retrocomputing:vice-sottr01.png?500|}}
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
{{:content:retrocomputing:vice-even-odd.png?500|}}
==== 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:
{{:content:retrocomputing:indirect01.png?400|}}
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:
{{:content:retrocomputing:assembly-indirect.png?600|}}
L'indirizzamento di tipo //Indirect// viene usato soprattutto nel codice ROM
==== 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|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
Il metodo di indirizzamento di tipo **//Indirect, Indexed//** consente di raggiungere/modificare tutte le locazioni di memoria perché consente di incrementare il MSB e, tramite il registro Y, il LSB; così facendo si raggiungono 256x256=65536 locazioni di memoria, cioè 64K.
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//:
{{:content:retrocomputing:screen_ram_e_sprite.png?400|}}
{{:content:retrocomputing:screen_ram_e_sprite02.png?400|}}
==== Start & End of Basic ====
Il Start-of-Basic e l'End-of-Basic è definito in questi registri:
{{:content:retrocomputing:c64-memory01.png?600|}}
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**; **[[https://www.youtube.com/watch?v=rFOh_lYcF8A&list=PLU1o_YShTPgoA7_nZ0PutqaPDsitA5RvV&pp=iAQB|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:
{{:content:retrocomputing:vice-sys01.png?600|}}
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://.
===== Turbo Macro Pro =====
**Turbo Macro Pro (TMP)** è un altro assembler/monitor. Si può scaricare da **[[https://csdb.dk/release/?id=182920|qua]]**.
==== Avvio ====
* effettuare un //Attach// del disco .d64 scaricato sopra
* dare LOAD "$",8
* dare LOAD "TMP ...non REU",8,1 (:!: il ",1" finale è fondamentale)
* eseguirlo con SYS 32768
TMP si avvierà:
{{:content:retrocomputing:tmp01.png?600|}}
==== Sintassi ====
Sembra sia fondamentale che, in questa versione, i comandi siano scritti in **minuscolo**, pena comparsa messaggio d'errore //"illegal pseudo-op"//:
{{:content:retrocomputing:tmp02.png?600|}}
Per **assemblare** si usa la "<-" freccia a sinistra dell'"1"; in Vice si usa **"Fn+cursore dx"**:
{{:content:retrocomputing:tmp03.png?600|}}
e poi "S".
Nel caso specifico si esegue il programma con 'SYS 4096' (che è pari a $1000) per ottenere:
{{:content:retrocomputing:tmp04.png?600|}}
Per ritornare all'editor basta digitare:
SYS 32768
===== VIC 20 =====
==== VICMON ====
**VICMON** è un **[[#machine_language_monitor|MLM]]** per VIC20. Per eseguirlo su **VICE** è necessario
* avere il file in **formato .crt**
* impostare la cartuccia come indicato **[[https://sourceforge.net/p/vice-emu/bugs/1447/|qui]]** e cioè:
{{:content:retrocomputing:vicmon01.png?600|}}
==== 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
{{:content:retrocomputing:helloworld01.png?600|}}
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
{{:content:retrocomputing:helloworld02.png?600|}}
=== 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:
{{:content:retrocomputing:helloworld03.png?600|}}
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:
{{:content:retrocomputing:helloworld05.png?600|}}
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:
{{:content:retrocomputing:vice-reset-vector01.png?600|}}
che è l'**indirizzo del reset vector ($FD22 - 64802)**, che può quindi essere richiamato, ad es. da BASIC, per effettuare un reset del computer:
{{:content:retrocomputing:vic-reset-vector02.png?600|}}
La routine Kernal vera e propria presente in $FD22 è abbastanza complessa; incollo qui di seguito le prime istruzioni:
{{:content:retrocomputing:vic-reset-vector03.png?600|}}
=== Start-of-Basic RAM ===
L'inizio della **RAM Basic** è definito, a seconda che il VIC 20 sia o meno espanso, così:
{{:content:retrocomputing:vic-basic-ram01.png?600|}}
tratto da **[[https://techtinkering.com/articles/basic-line-storage-on-the-vic-20/|qui]]**. Il **vettore di inizio Basic è dato dal contenuto di due locazioni, 43 (low byte) e 44 (high byte)**; il valore $1001 è confermato:
{{:content:retrocomputing:vic-basic-ram02.png?600|}}
Quasi tutta la memoria RAM del VIC 20 è assegnata al BASIC; questo consente di scrivere programmi quanto più grandi possibile. **Per ottenere più spazio per i programmi in linguaggio macchina bisogna quindi riassegnare la memoria assegnata al BASIC**
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 **[[https://www.atarimagazines.com/compute/issue38/105_1_PART_III_VISITING_THE_VIC-20_VIDEO.php|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.
Questo consente di avere uno **spazio sufficiente per l'assembly, da $1000 a $13FF**, prima dell'area Basic che va da $1400 a $1DFF, prima a sua volta della memoria video, che inizia a $1E00.
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:
{{:content:retrocomputing:vic-basic-ram03.png?600|}}
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:
{{:content:retrocomputing:vic20-expansion.png?500|}}
I settaggi DIP switch corrispondono alle linee del connettore di espansione del computer:
{{:content:retrocomputing:vic20-expansion02.png?500|}}
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:
{{:content:retrocomputing:memory_map.png?500|}}
{{:content:retrocomputing:memory_map02.png?400|}}
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|
**[[http://blog.tynemouthsoftware.co.uk/2019/09/how-the-vic20-works.html|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.