====== 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.