User Tools

Site Tools


content:retrocomputing:assembly

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:

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é:

  1. il registro X può essere incrementato (riga $0344), a differenza dell'Accumulatore
  2. 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 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:

CondizioneFlagBranch da usareNote
“registro uguale a” Z - ZeroBEQ (branch equal 0)
“registro diverso a” Z - ZeroBNE (branch not equal 0)
“registro minore di”C - CarryBCC (branch carry clear)
“registro maggiore o uguale a”C - CarryBCS (branch carry set)
“registro negativo” (MSB=1)N - NegativeBMI (branch minus)
“registro positivo” (MSB=0)N - NegativeBPL (branch plus)
“overflow numero con segno” (bit 6=1)V - OverflowBVS (branch overflow set)
“no overflow numero con segno” (bit 6=0)V - OverflowBVC (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:

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:

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

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

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

(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
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 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

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

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:

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. 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 256×256=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:

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

Turbo Macro Pro

Turbo Macro Pro (TMP) è un altro assembler/monitor. Si può scaricare da 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à:

Sintassi

Sembra sia fondamentale che, in questa versione, i comandi siano scritti in minuscolo, pena comparsa messaggio d'errore “illegal pseudo-op”:

Per assemblare si usa la “←” freccia a sinistra dell'“1”; in Vice si usa “Fn+cursore dx”:

e poi “S”.

Nel caso specifico si esegue il programma con 'SYS 4096' (che è pari a $1000) per ottenere:

Per ritornare all'editor basta digitare:

SYS 32768

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:

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 qui conviene procedere così:

  1. settare a '0' il valore della prima locazione della nuova area Basic
  2. impostare i valori della nuova area Basic al valore delle prima locazione + 1
  3. 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:

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:

LineaRAM aggiuntivaNote
RAM11 KBNel BLK0
RAM21 KBNel BLK0
RAM31 KBNel BLK0
BLK18 KB
BLK28 KB
BLK38 KB
BLK48 KB

Qui uno schema della memoria di un VIC 20:

Di seguito è spiegato come settare i DIP switch per ottenere le espansioni RAM:

RAM1RAM2RAM3BLK1BLK2BLK3BLK5DiagEspansioneBasic (bytes free)
11100000 3K 6655
00010000 8K 11775
11111000 16K 19967
11111100 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.

content/retrocomputing/assembly.txt · Last modified: 2024/09/10 21:27 by admin