Visualizzazione post con etichetta C64. Mostra tutti i post
Visualizzazione post con etichetta C64. Mostra tutti i post

martedì 22 gennaio 2019

Emulatore in Pascal per Commodore 64

Mi sono imbattuto per caso in questo progetto ospitato su GitHub (che rimanda pero' ad un progetto del 2008) in cui viene programmato un semplice emulatore per Commodore 64 e lo ho trovato molto istruttivo perche' il codice (forse perche' in Pascal) e' molto semplice da seguire ed offre uno spaccato della programmazione di una macchina di emulazione


Emulatore in esecuzione in shell su Linux


Si parte semplicemente creando un array di 64K byte che rappresentano la memoria di un C64 (si vedra' in seguito che parte di questi 64Kb saranno occupati da ROM)

Ognuna delle celle dell'array rappresenta una locazione di memoria che puo' essere letta o scritta con un semplice accesso all'array mediante puntatore

Si definiscono quindi le variabili byte A (accumulatore),X,Y,S (stack pointer varia da 0x01FF a 0x0100) ,P (processor status usato per contenere delle flags) ed IR (instruction register, contenuto di memoria che si utilizza) che rappresentano i registri della cpu 6510 con il registro PC (Program counter) che e' di lunghezza word 16 bit (dato che deve contenere un numero 0.65535 )

Il ciclo base dell'emulatore semplicemente legge il contenuto della memoria puntato da PC, interpreta l'istruzione macchine ed modifica il Program Counter per andare alla successiva istruzione

=================================
while true do
begin
    IR := peek(PC);
    inc(PC);
    case IR of
        $00 : interpret instruction $00
        $01 : interpret instruction $01
    else : unknown instruction encountered
end;
end.

=================================

Si devono quindi implementare tutti i codici macchina e come questi modificano lo stato dei registri e le locazioni di memoria ed il flusso di programma

Il 6510 ha circa una ottantina di Opcode da implementare per realizzare un emulatore

Il 6510 usa come base un sistema little endian per cui il byte meno significativo di una word e' registrato nella prima locazione di memoria (o locazione di memoria piu' bassa)

Impostata l'emulazione del processore il passo successivo e' quello di inizializzare le ROM del C64 che, al minimo, devono contenere il Kernal ma puo' essere utile anche avere il Basic. Si prendono i due file del Kernal e del Basic, si legge il contenuto e lo si copia rispettivamente alle locazioni di memoria $A000...$BFFF e $E000...$FFFF. (un altro settore di memoria destinato alla ROM e' compreso tra $D000 e $DFFF....tutto il resto della memoria..anche se frammentato e' considerata RAM)

Il boot della macchina inizia leggendo il valore della locazione $FFFC-$FFFD ed impostando il program counter (una word) a questa locazione (in pratica viene lanciato un Cold Reset della macchina. Di default il valore letto e' $FCE2 (ovvero il mitico SYS 64738)

Il codice ROM si aspetta di dover ricevere informazioni dal VIC e dalla tastiera ma dato che questi non sono ancora emulati al capitolo 3 dell'emulatore viene inviato un segnale 

Nel capitolo 4 viene introdotta la lettura da tastiera e l'output in modalita' testo
Per la tastiera, ogni volta che viene premuto un tasto (if keypressed) viene letta la lunghezza del buffer di tastiera alla locazione $C6 (il buffer di tastiera puo' contenere al massimo 10 caratteri), se il numero e' inferiore a 10 inserisce il nuovo carattere nel buffer che si trova tra $0277 e $0280 ed incrementa la locazione $C6

Per l'output a video viene letta la memoria video testuale che e' compresa tra $400 e $7E7 (1Kb). Nell'emulatore la schermata viene ricreata ogni volta che c'e' un accesso in lettura nell'area di memoria $400-$7E7

Per la posizione del cursore vengono lette le locazioni di memoria $00D3 e $00D6

Dal capitolo 5 viene gestita la creazione degli interrupt. In pratica un interrupt e' un evento che interrompe momentaneamente il ciclo di base del processore While True Do. Gli interrupt possono essere mascherabili e non mascherabili: di fatto viene messo nello stack il valore del program counter  ed il contenuto del registro delle flags (in modo da recuperarlo quando si esce dall'interrupt) e poi si passa il controllo dell'esecuzione al codice dell'interrupt puntato dalle locazioni di memoria $FFFE-$FFFF (essendo un indirizzo di memoria e' una word) se e' mascherabile oppure $FFFA-$FFFB se non e' mascherabile

GLi interrupt sono generati da un dispositivo hardware ma esiste la possibilita' di avere interrupt software via  BRK

Su C64 viene generato un IRQ con un clock di 50 Hz

Il codice Pascal si compila tranquillamente, almeno fino al capitolo 5, su Linux usando  FreePascal


mercoledì 1 agosto 2018

Moltiplicazioni binarie solo con addizioni e bit shift

Una delle cose che mi ha piu' spiazzato nell'imparare l'assembler per C64 e' l'assenza nel set di istruzione del 6502/6510 di un opcode per moltiplicare. anche i soli interi

La soluzione per fare una moltiplicazione con soli addizioni e bit shift e' in realta' molto semplice usando la matematica binaria

Si parte da un esempio semplice 9 (1001b) x 8 (1000b). Se si usa la regola della moltiplicazione in colonna che si impara alle elementari

            1001x 
       1000=
      ------
       0000
      0000
     0000
    1001
    ------------
    1001000   (72d)

Come si vede quando si ha un bit zero e' inutile effettuare la moltiplicazione, se si ha un bit 1 basta ripetere il valore dell'altro moltiplicando con lo shift bit di tante posizioni quanto e' il bit del primo moltiplicatore. Un algoritmo ritrovato a questo sito  riporta una variante....invece di spostare l'indice del bit di test viene effettuato sempre il testo sul bit alla posizione 0 (quella piu' sinistra) ma ogni volta viene fatto un bit shift a destra per scorrete tutto il numero. Se il bit in posizione zero e' nullo si fa uno shift a sinistra dell'altro operatore ed uno shift a destra del moltiplicatore. Se il bit alla posizione zero e' 1 allora il valore viene sommato ad una variabile accumulatore



giovedì 26 luglio 2018

Mandelbrot su C64 in GPascal in matematica intera

Sempre per la serie "Cosa mi sono perso negli anni 80" ho cercato se esistevano compilatori Pascal per il C64 (compilatori veri...non cross-compilatori)....io ho fatto il mio primo ed unico corso di programmazione nel 1987 su TurboPascal 5.0 su un Olivetti M24 ed usavo solo Basic su C64



Con mia grande sorpresa ho trovato diversi compilatori con caratteristiche peraltro differenti

Pascal 64 : non sono riuscito ad usarlo su VICE. Si tratta comunque di un compilatore evoluto con linker esterno. Gestisce la alta risoluzione grafica ed ha il tipo dati real. Un po' macchinoso da usare perche' si deve scrivere il file in Pascal come se si stesse editando in Basic (con i numeri di linea compresi)...si salva il file, si compila, si linka e si lancia...non proprio comodo

Oxford Pascal

GPascal : ho trovato molto comodo ed intuitivo l'editor...in 10 minuti gia' gestivo l'editing con i tasti fondamentali. Si scrive nell'editor, si compila (in PCode), si lancia ed in caso di problemi RunStop/Restore e si torna all'editor. Molto comodo. Non sono riuscito a capire come funziona il debug ed il trace. Molto intuitivo ha pero' il limite di avere solo il tipo dati numerico Integer che ha peraltro dei limiti curioso (. Si trova sia il manuale che i dischi a questo indirizzo (che dovrebbe corrispondere al sito dello sviluppatore originale)

Per mettere la prova GPascal ho provato a scrivere un programma per generare l'insieme di Mandelbrot. L'assenza di un tipo dati real mi ha costretto ad usare routine di calcolo in matematica intera...inoltre ho dovuto mantenere il moltiplicatore a 256 perche' se aumentavo la precisione di calcolo sforavo il limite degli interi che devono essere compresi tra -8388608 e + 8388607.
C'e' inoltre da segnalare che il compilatore permette di disattivare l'output video in modo da velocizzare i calcoli (con il comando graphics(6,0)




mercoledì 25 luglio 2018

Blitz Compiler

Tutto cio' che mi sono perso negli anni 80...questo per esempio Blitz , un compilatore PCode per C64 che velocizza il codice Basic


Per esempio un programma per la ricerca dei numeri primi minori di 250 impiega circo 80 secondi per essere completato con l'interprete BASIC standard mentre circa 51 quando viene eseguito mediante Blitz

L'operazione peraltro e' semplice: si scrive un programma in Basic, lo si testa. lo si salva sul disco e poi lo si converte con Blitz

venerdì 13 luglio 2018

Mandelbrot (per pigri) modalita' grafica con CC65


Sono riuscito ad attivare la modalita' grafica su CC65. Compilando il codice ed usando gli esempi senza modifiche avevo sempre l'errore


Error #2 initialising graphics
Cannot load driver

quando cercavo di usare TGI. Grazie al suggerimento degli sviluppatore basta flaggare a zero

#  define DYN_DRV       1





e si usa il driver in modalita' statica. La prima prova e' andata bene ma non troppo....Su VICE in modalita' velocita' 100% ho impiegato 3659.9 secondi !!!!!! (quasi quasi ritorno a Simons Basic)...non capisco ....CC65 e' compilato mentre Simons Basic e' interpretato e non c'e' un guadagno sensibile e c'e' pure il trucco della integer math contro i float si Simons...gia' la integer math...se si prova a disegnare le bande di fuga si vede che queste vengono a cuspide mentre dovrebbero essere tondeggianti e cio' deriva dalla approssimazione apportata da matematica per interi


Poi capito su questo sito http://retro64.altervista.org/blog/the-mandelbrot-set-c64-assembly-program-to-draw-it-fast-using-integer-math-part-1/ con un esempio in Assembler che genera Mandelbrot in  12 minuti ....tempo per me di attaccare la tastiera al chiodo

cl65 -O -l man.lst -vm -m man -o man.prg -t c64 man.c

man.c
--------------------------------------------------

#include <stdlib.h>
#include <conio.h>
#include <cc65.h>
#include <tgi.h>


#define SCREEN_X        320
#define SCREEN_Y        200
#define MAXCOL          2


#define maxiterations   25
#define fpshift         (14)
#define tofp(_x)        ((_x)<<fpshift) // bitwise shift left
#define fromfp(_x)      ((_x)>>fpshift)
#define fpabs(_x)       (abs(_x))

#define mulfp(_a,_b)    ((((signed long)_a)*(_b))>>fpshift)
#define divfp(_a,_b)    ((((signed long)_a)<<fpshift)/(_b))


#pragma static-locals (1);



void mandelbrot (signed short x1, signed short y1, signed short x2,
                 signed short y2)
{
    // le variabili register sono quelle che il compilatore mette in pagina zero
    // e quindi quelle a piu' rapido accesso
    register unsigned char count;
    register signed short r, r1, i;
    register signed short xs, ys, xx, yy;
    register signed short x, y;

    /* Calc stepwidth */
    xs = ((x2 - x1) / (SCREEN_X));
    ys = ((y2 - y1) / (SCREEN_Y));

    yy = y1;
    for (y = 0; y < (SCREEN_Y); y++) {
        yy += ys;
        xx = x1;
        for (x = 0; x < (SCREEN_X); x++) {
            xx += xs;
            /* Do iterations */
            r = 0;
            i = 0;
            for (count = 0; (count < maxiterations) &&
                 (fpabs (r) < tofp (2)) && (fpabs (i) < tofp (2));
                 ++count) {
                r1 = (mulfp (r, r) - mulfp (i, i)) + xx;
                /* i = (mulfp(mulfp(r,i),tofp(2)))+yy; */
                i = (((signed long) r * i) >> (fpshift - 1)) + yy;
                r = r1;
            }
            if (count == maxiterations) {
                 tgi_setcolor (0);
            } else {
                 tgi_setcolor (count%2);
          }
            tgi_setpixel (x, y);
        }
    }
}


int main (void)
{
    tgi_install (tgi_static_stddrv);
    tgi_init ();
    tgi_clear ();
    
    mandelbrot (tofp (-2), tofp (-2), tofp (2), tofp (2));
    cgetc ();
    tgi_uninstall ();
    return 0;
}

CC65 Mandelbrot (per pigri) modalita' testo

Devo ammettere che scoprire CC65 e' stato un bel passo avanti...prima avevo due opzioni sul C64: o fare i programmi in BASIC (molto molto lenti) oppure farli in Assembler. CC65 e' invece un cross compiler indirizzato al 6503 che usa la sintassi C. Il vantaggio e' che mentre il BASIC e' un linguaggio interpretato (quindi il sistema deve ogni volta effettuare la conversione in linguaggio macchina nonostante il codice sia in ogni caso tokenizzato), con CC65 si ha gia' codice macchina (anche se ovviamente non ottimizzato)


Mandelbrot modalita' testuale su C64Debugger

per esempio si puo' fare il classico Hello World e compilarlo in questo modo

cl65 -O -l hello.lst -vm -m hello -o hello.prg -t c64 hello.c

il codice generato non e' esattamente compatto (sono circa 3Kb). Inoltre e' da notare che non e' disponibile il tipo dati float

In ogni caso con poco sforzo e' possibile generare un insieme di Mandelbrot (non sono ancora riuscito a gestire la modalita' grafica via TGI)

------------------------------------------------------

#include <stdlib.h>
#include <conio.h>
#include <cc65.h>

#define SCREEN_X        40
#define SCREEN_Y        25

#define maxiterations   25
#define fpshift         (10)
#define tofp(_x)        ((_x)<<fpshift)  bitwise shift left
#define fromfp(_x)      ((_x)>>fpshift)
#define fpabs(_x)       (abs(_x))

#define mulfp(_a,_b)    ((((signed long)_a)*(_b))>>fpshift)
#define divfp(_a,_b)    ((((signed long)_a)<<fpshift)/(_b))


#pragma static-locals (1);



void mandelbrot (signed short x1, signed short y1, signed short x2,
                 signed short y2)
{
    // le variabili register sono quelle che il compilatore mette in pagina zero
    // e quindi quelle a piu' rapido accesso
    register unsigned char count;
    register signed short r, r1, i;
    register signed short xs, ys, xx, yy;
    register signed short x, y;

    /* Calc stepwidth */
    xs = ((x2 - x1) / (SCREEN_X));
    ys = ((y2 - y1) / (SCREEN_Y));

    yy = y1;
    for (y = 0; y < (SCREEN_Y); y++) {
        yy += ys;
        xx = x1;
        for (x = 0; x < (SCREEN_X); x++) {
            xx += xs;
            /* Do iterations */
            r = 0;
            i = 0;
            for (count = 0; (count < maxiterations) &&
                 (fpabs (r) < tofp (2)) && (fpabs (i) < tofp (2));
                 ++count) {
                r1 = (mulfp (r, r) - mulfp (i, i)) + xx;
                /* i = (mulfp(mulfp(r,i),tofp(2)))+yy; */
                i = (((signed long) r * i) >> (fpshift - 1)) + yy;
                r = r1;
            }
            if (count == maxiterations) {
                 cprintf("*");
            } else {
                 cprintf(" ");
          }
            /* Set pixel */
            //tgi_setpixel (x, y);
        }
    }
}

int main (void)
{
    clrscr ();
    mandelbrot (tofp (-2), tofp (-2), tofp (2), tofp (2));
    cgetc ();
    return 0;
}

mercoledì 11 luglio 2018

PRG in Vice

A volte puo' essere comodo lanciare un PRG dentro a VICE ma, almeno su Windows, il drag and drop non funziona. Vengono accettate solo immagini disco, cassetta o cartuccia


Un metodo per ovviare al problema e' creare una immagine disco da una directory da Impostazioni/Impostazioni periferiche/Directory

A questo punto la directory viene vista come un disco

LOAD"$",8
LIST

e si avra' il contenuto della directory selezionata. A questo punto basta caricare il PRG

C64 Joystick Assembler

Esempio commentato di come leggere lo stato del joystick di un C64 per muovere un carattere sullo schermo




Da notare che per la scrittura a schermo vengono usate due tecniche. La prima, nel clear screen, prevede la scrittura diretta in memoria video (che si trova a partire da $0400 ed ha le dimensioni di 1Kb, 1000 bytes per una dimensione di 40x25 celle); nella soconda viene effettuata una chiamata diretta al KERNAL

Un metodo alternativo per ripulire la memoria video con ACME e' il comando
*=$0400
!fill 1000,$20

---------------------------------------------------------------------------------------------
!cpu 6502
!to "joy.prg",cbm

; per simulare  il joystick in C64 Debugger premere CTRL+Y 
; nella finestra C64 e premere i tasti freccia

JOY = $DC00

!macro stampa .carattere{
  JSR $E50C
  lda #.carattere
  JSR $FFD2
 
}

* = $c000
; pulisce lo schermo e lo mette nero con carattere blu
lda #$00
sta $d020
sta $d021
tax
lda #$20
loop
sta $0400,x
sta $0500,x
sta $0600,x
sta $0700,x
dex
bne loop


ldx #$14       ; lo schermo testo di C64 e' 40x25 caratteri
ldy #$0c      ; qui siamo piu' o meno al centro
+stampa 65

lda #$0 ; inizializza il valore della locazione 2 a zero
sta $02

start    lda $02        ; viene usata la locazione di memoria 2 della pagina zero 
; che non e' usata dal sistema operativa
         cmp JOY      ; in DC00 c'e' la porta del joystick 2 
          ; a seconda della bitmask si capisce se e' stato
          ; cliccato verso l'alto,basso,destra,sinistra o fire
          ; bit 0 attivato = alto
          ; bit 1 attivato = basso
          ; bit 2 attivato = sinistra
          ; bit 3 attivato = destra
          ; bit 4 attivato = fire
          ; per esempio il valore 17 (10001b) indica che il joystickhelpsys 
          ; e' in alto ed e' premuto il fire
         beq start      ; loop until the joystick register changes.

         lda JOY      ; salva lo stato del joystick nella locazione 2
         sta $02

         lda #000001 ; controlla se e'attivato il primo bit
         bit JOY      ; 
         bne cont1      ; se non e' attivato segue
         +stampa 32
         inx ; altrimenti incrementa X
         +stampa 65    ; e sposta il cursore con il KERNAL

cont1    lda #000010 ; continua con gli altri bit 
         bit JOY      ; joystick giu'
         bne cont2      ; 
         +stampa 32
         dex            ; 
         +stampa 65    ;

cont2    lda #000100 ; joystick a sinistra
         bit JOY      ; 
         bne cont3      ; 
         +stampa 32
         iny            ; incrementa il registro Y
         +stampa 65

cont3    lda #001000 ; joystick a destra 
         bit JOY      ; 
         bne cont4      ; 
         +stampa 32
         dey            ; 
         +stampa 65
         

cont4    lda #010000 ; fire 
         bit JOY      ; 
         bne start      ; esce se e' stato premuto Fire altrimenti torna all'inizio 
         rts            ; back to basic

martedì 10 luglio 2018

C64 Autostart Assembly

Un metodo per lanciare un codice Assembler su C64 senza usare un loader basic (e gli  svariati DATA). Questo codice, a differenza della macro start_at_address vista in questo post, non lancia diretttamente il codice quando finito il caricamento ma non e' necessario digitare nessun comando SYS, basta il comando RUN



Il codice e' il seguente

*=$801
BASIC: !BYTE $0B,$08,$01,$00,$9E,$32,$30,$36,$31,$00,$00,$00
 
             LDA........
             altro codice assembler


il comando precedente si traduce come
1 SYS 2061

ma come si traduce?? I comandi BASIC vengono inseriti in memoria nel C64 come token. Il codice $9E costituisce il token del comando SYS. In seguito $32, $30,$36,$31 sono i valori esadecimali dei caratteri che corrisponde in ASCII CODE al numero 2061

I due byte precedente $01,$00 indicano il numero di riga (byte basso, byte alto). In questo caso il numero di riga e' 1. I primi due byte in assoluto sono il puntatore alla prossima riga BASIC nel solito ordine byte basso/byte alto ... questo caso il puntatore e' a $80D ovvero a dove inizia il codice assembler

il valore 2061 corrisponde all'esadecimale $80D. In pratica la riga in codice BASIC fa puntare l'esecuzione del programma al primo byte dopo la stringa di inizializzione ($801 (2049) e' la base del codice basic in C64 + 12 byte della lunghezza  launcher, ovvero la stringa BASIC,  e' uguale a 2061)

I tre zeri finali corrispondo a : il terz'ultimo zero indica il terminatore di una riga BASIC. Gli ultimi due zeri indicano la fine del programma BASIC.


Recensione Retro Game Dev di Derek Morris

In un momento di esaltazione mi sono comprato il libro Retro Game Dev di Derek Morris sperando che fosse un sunto organizzato di quanto presente nei vari blog e forum sparsi per internet sull'argomento


E' stata una grossa delusione (non costosa perche' fortunatamente Amazon ha accettato il reso)...12 euro per scoprire in 120 pagine che praticamente non ci sono spiegazioni ma viene tutto rimandato a file da scaricare con codice commentato ma in ogni caso difficile da seguire...tanto vale fare da soli

lunedì 9 luglio 2018

Sprites su C64

Un modifica del codice di MoveSprite.asm (qui su GitHub) che mostra due sprite. Il primo sprite
viene ubicato in una coordinata casuale con una routine random del SID. Il primo sprite si puo' muovere e quando tocca il secondo sprite il VIC sente la collisione ed il programma si arresta



Il codice ha una autoload in modo da non essere necessario digitare nessun comando SYS (che in ogni caso e' SYS 2304). Il codice dovrebbe essere sufficientemente commentato

Sviluppato con RelaunchC64 come IDE ed ACME come crosscompiler assembler. Lo sprite e' stato generato con http://www.spritemate.com/  che permette l'export sia in formato ACME che KickAssembler


-----------------------------------------------------------------------------

!cpu 6502
!to "move_2sprite.prg",cbm
; SYS 2034
;*=$0900

; LA DEFINIZIONE DELLE VARIABILI E' IN CODA AL CODICE

!macro start_at .address {
  * = $0801
  !byte $0c,$08,$00,$00,$9e
  !if .address >= 10000 { !byte 48 + ((.address / 10000) % 10) }
  !if .address >=  1000 { !byte 48 + ((.address /  1000) % 10) }
  !if .address >=   100 { !byte 48 + ((.address /   100) % 10) }
  !if .address >=    10 { !byte 48 + ((.address /    10) % 10) }
  !byte $30 + (.address % 10), $00, $00, $00
  * = .address
}

!macro sid_random{
; calcola un valore casuale usando il SID
; il byte random viene salvato nell'accumulatore
LDA #$FF
STA $D40E
STA $D40F
LDA  #$80
STA $D412
LDA $D41B
}

;------------------------------------------------------------------------------------
!macro new_position{  ; cambia la posizione dello sprite 0 in modo casuale

LDA #0 ;stick with x0-255
STA $D010   ; la coordinata X puo' essere maggiore di 255 e non puo' essere
            ; contenuta in un byte. La parte alta e' inserita nella locazione $D010
            ; in questo esempio la parte alta e' settata a zero per imposizione 

+sid_random
TAX
 
ricalcola
+sid_random
CMP #200 ; compara l'accumulatore con 200
BPL ricalcola
; qui c'e' il problema contrario rispetto a X
; la coordinata Y non puo' essere maggiore di 200 mentre un byte puo' essere 255
; quindi cicla nel calcolare numeri casuali fino a che non e' minore di 200

TAY 

STX $D000 ; che contengono la posizione dello sprite 0
STY $D001
}

;------------------------------------------------------------------------------------
+start_at $0900
JSR $E09A


JSR $E544  ; vai alla subroutine che cancella lo schermo 

LDA #13    ; 
STA $C202

LDA #$0D ;ad $7F8 c'e' il puntatore dello sprite 0
STA $7F8    

LDA #$0D ;ad $7F9 c'e' il puntatore dello sprite 1
STA $7F9    


LDA #3 ;abilita lo sprite 0, e' una bit mask
STA $D015   ;che serve ad abilitare tutti gli sprite
;abilitare sprite 0 ed 1 si inserisce 3 (11b)

LDA #02 ; in D027 c'e' il colore dello sprite 0
STA $D027   ; viene inserito il valore 2 ovvero rosso

; setta il colore dello sprite 1
LDA #03
STA $D028

LDX #0
LDA #0

;a $0340 c'e' la locazione di memoria dello sprite 0
;ogni sprite e' lungo 63 byte (piu' un byte non utilizzato)
;il codice successivo cicla per pulire l'area di memoria
CLEANUP0 STA $0340,X  
INX
CPX #63
BNE CLEANUP0

;ripulisce l'area dello sprite 1
LDX #0
CLEANUP1 STA $0340,X  
INX
CPX #63
BNE CLEANUP1



;dopo aver ripulito l'area di memoria la riempie con
;la matrice DATA
LDX #0
BUILD LDA DATA,X
STA $0340,X
INX
CPX #63
BNE BUILD

;position
+new_position

;setta le variabili dello sprite 1
LDX #100 ; 
LDY #100  ;  
STX $D002 ;
STY $D003


SCAN JSR $FF9F ;richiede al Kernal lo stato della tastiera
JSR $FFE4 ;richiede al Kernal il valore del default input
        
;sposta lo sprite a seconda dello stato tastiera
START CMP #87 ;W - up
BEQ UP

CMP #83 ;S - down
BEQ DOWN

CMP #65 ;A - left
BEQ LEFT

CMP #68 ;D - right
BEQ RIGHT

CMP $C202 ;end if enter clicked
BEQ END

; controlla se ci sono state collision
LDA $D01E ; byte di collisione hardware tra sprite 
CMP #0
BNE END

JMP SCAN

UP LDY $D001 ; carica la posizione Y dello sprite in Y
DEY       ; decrementa Y (il sistema di riferimento e' rovesciato)
STY $D001 ; ed aggiorna la posizione dello sprite
;+new_position
JMP SCAN

DOWN         LDY $D001
INY
STY $D001
JMP SCAN

LEFT         LDX $D000
DEX
STX $D000
CPX #255
BNE SCAN
LDA #0
STA  $D010
JMP SCAN

RIGHT         LDX $D000
INX
STX $D000
CPX #255
BNE SCAN
LDA #1
STA  $D010
JMP SCAN


;clean up at the end
END JSR $E544 ; pulisce lo schermo
LDA #0    ; disabilita lo sprite 0
STA $D015
RTS       ; ritorna al prompt

;define the sprite
DATA !byte $00,$00,$00,$00,$00,$00,$00,$00
!byte $00,$00,$00,$00,$00,$00,$00,$00
!byte $38,$00,$00,$fc,$00,$00,$f2,$00
!byte $01,$c3,$00,$01,$89,$80,$03,$80
!byte $c0,$03,$83,$c0,$03,$82,$00,$07
!byte $03,$00,$07,$01,$80,$00,$c0,$80
!byte $00,$79,$80,$00,$0f,$00,$00,$00
!byte $00,$00,$00,$00,$00,$00,$00,$08

giovedì 14 giugno 2018

SetPixel su C64

Questo e' solo un promemoria....il codice e' stato preso su Internet...ho provato solo a capirci qualcosa senza neanche molto successo


Debug di VICE


Per accendere un pixel di posizione X,Y nella modalita' in alta risoluzione (320x200x2colori) del C64 la formula e' la seguente

indirizzo_pixel = base_mem_video +( int(y/8) *320) +(y AND 7) + (int(x/8)*8)

il motivo di questa curiosa formula deriva da come e' gestita la modalita' grafica. Al contrario di come si potrebbe fare con VGA (con un flusso continuo di bit a descrivere la pagina) in C64 lo schermo e' diviso in porzioni di 8x8 pixels.

Ho trovato su GitHub questo codice che funziona decisamente bene e si compila con ACME (per lanciarlo SYS 32768) ed ho provato a commentarlo con risultati decisamente alterni

------------------------------------------------------
!cpu 6502
!to "plot-asm.prg",cbm

base = $2000
SCROLY = $D011
VMCSB = $D018
colmap = $0400
C2DDRA = $DD02
CI2PRA = $DD00

scrlen = 8000
maplen = 1000

xcoord = $fb
ycoord = $fd

tabptr = xcoord
tabsiz = $9000
filval = tabsiz+2

; queste sono variabili in pagina zero
bmpage = $ff
mask = $59
loc = $5a
store = $5c

* = $8000 ; start address for 6502 code
; sys 32768


jmp start


; address = base + int(y/8) * 320 + (y and 7) + int(x/8) * 8
plotbit lda xcoord  ; mette il valore della coordinata X in accumulatore
        ; questa e' il primo pezzo ovvero (X AND 7)
and #7      ; 7 = 111
tax         ; mette (xcoord and 7) in X
                    ; questo serve per il numero di cicli di SHIFT
sec         ; setta il carry questo dovrebbe servire per il ror successivo
                           
lda #0      ; azzera l'accumulatore
sta loc     ; mette la locazione $5c di x and 7
       
shift ror         ; ruota right bits dell'accumulatore
                    ; (divide per 2 l'accumulatore)
dex         ; decrementa x
bpl shift   ; salta se x e' positivo o zero
                    ; ror e' come dividere per 2 ogni ciclo
sta mask    ; salva il valore in  $59
       
lda xcoord  ; carica l'accumulatore di xcoord
and #$f8    ; $f8=248 = 11111000
sta store   ; metti in valore in store = $5c = 92 = 1011100
       
       
lda ycoord  ; carica il valore di y
lsr         ; shift left divide y/2
        ; nel carry c'e' il resto della divisione
lsr         ; shift left divide y/4
lsr         ; shift left divide y/8
sta loc+1   ;salva il $5a+1=$5b
lsr         ; shift left
ror loc     ; moltiplica * 320 ?????
  lsr
ror loc
adc loc+1   ; salva la parte alta
sta loc+1
       
lda ycoord ; carica il valore di y
and #7     ; Y AND 7
                   ; somma gli addendi
adc loc    ; aggiungi LOC
adc store  ; aggiungi STORE
sta loc    ; salva tutto in STORE
lda loc+1 
adc xcoord+1
adc bmpage ; aggiunge la base della memoria (ovvero base + .....)
sta loc+1
ldy #0
lda (loc),y
ora mask,y
sta (loc),y
rts

:--------------------------------------------------------------------------------
; fill routine
blkfil lda filval  ; metti nell'accumulatore il valore della cella $9002 ovvero $10
ldx tabsiz + 1  ; carica in x $9001  ovvero
beq partpg  ; salta alla label partpg se la flag di zero e' clear
ldy #0  ; carica Y con zero
fullpg sta (tabptr),y ; carica la cella puntata da $fb $400 con il valore zero inizialmente, poi cicla su fullpg su per $20
                       ; indirect-indexed carica il valore zero dalla memoria puntata da $tabptr
                       ; e' un modo che e' comune per realizzare array con y come indice ed il puntatore alla zona di memoria
iny            ; incrementa Y
bne fullpg      ; salta alla label fullpg
inc tabptr+1    ; incrementa la locazione $FC
dex             ; decrementa X
bne fullpg       ; ritorna a
partpg         ldx tabsiz
beq fini
ldy #0
partlp sta (tabptr),y
iny
dex
bne partlp
fini rts


;---------------------------------------------------------------------------------
; main routine
; define bit map and enable high-res
start lda #$20
sta bmpage ; mette il valore $20 nella pagina zero a $FF
lda #$18
sta VMCSB  ; seleziona lo screen mode $d018=53272
lda SCROLY ;
ora #32    ; attiva la risuoluzione 320x200 con OR 32 la locazione $d011=53265
sta SCROLY ;

; select graphics bank 1
lda C2DDRA ; $DD02
ora #$03   ; seleziona i banco VIC 1
sta C2DDRA ; ; $DD02

lda CI2PRA ; $DD00
ora #$03   ; $DD00 = %xxxxxx10 -> bank1: $4000-$7fff
sta CI2PRA


; clear bit map
lda #0 ; carica accumulatore con zero
sta filval ; tabsiz+2=$9002
lda #<base ; $2000 base della memoria schermo
sta tabptr ; metti il valore dell'accumulatore in $fb; locazione non usata di pagina zero
                   ; qui e nella locazione successiva c'e' il puntatore alla zona di memoria tabptr
lda #>base ; mette $20
sta tabptr+1 ; carica il valore alto in $fc locazione pagina alta non utilizzata

lda #<scrlen ; mette la parte bassa basso $40 scrlen = 8000 byte
sta tabsiz  ; mettilo alla locazione 9000
lda #>scrlen ; mette la parte alta $1F
sta tabsiz+1 ; dentro a $9001
jsr blkfil ; vai alla subroutune


; set bg and line colors
lda #$10      ; carica accumulatore $10
sta filval    ; lo mette in $9002
lda #<colmap  ; lda #$00
sta tabptr    ; lo mette in $FB
lda #>colmap  ; lda $04
sta tabptr+1  ; mette if $FC
lda #<maplen  ;
sta tabsiz
lda #>maplen
sta tabsiz+1
jsr blkfil ; vai alla subroutine


; set horizontal and vertical position
lda #<160   ; visto che x puo' essere> 255 prende sia la parte bassa che alta
sta xcoord  ; mette il valore 160 in xcoord
lda #>160
sta xcoord+1
lda #100    ; mette il valore di 100 in ycoord
sta ycoord 
jsr plotbit  ; vai alla routine di stampa bit per stampare il punto 160,100

inf jmp inf  ; cicla qui all'infinito

------------------------------------------------------

Una cosa interessante che ho scoperto mentre cercavo di capire qualcosa del codice e' che VICE, l'emulatore di C64, e' dotato di un fantastico monitor in cui si puo' fare debug passo passo.
Si apre VICE e ci si trascina sopra il programma compilato in PRG. Poi si apre la finestra di monitor (ALT+H od ALT+M dipende dal sistema operativo)....si apre una consolle in cui si devono digitare comandi (per un elenco completo digitare help)

Dato che so che il codice inizia a $8000 e' stato sufficiente settare un breakpoint (BREAK $8000...tutti gli indirizzi devono essere messi in esadecimale) e poi lanciare il programma con SYS 32768. A questo punto si possono aprire le varie sottofinestre (per esempio dei registri...si puo' visualizzare lo stato anche con REGISTERS) e si pu' fare fare debug passo passo con STEP (oppure il tasto Z)


Un altro esempio per stampare punti in alta risoluzione puo' essere estratto da questo esempio in BASIC (e' un semplice PAINT che funziona con il joystick) (questa la fonte)
Il programma in Assembler funziona ... questo non lo ho testato
--------------------------------------------------------
0       POKE 56, 32 : POKE 52, 32 : CLR : REM PROTECT SCREEN FROM BASIC   :rem 108
1 POKE 53280, 1 : PRINT "{CLR}{WHT}" : GOTO 100      :rem 102
2 GOSUB 26 : BASE = 2 * 4096 : REM START ADDRESS OF HIRES SCREEN   :rem 93
3 POKE 53272, PEEK(53272) OR 8 : REM BIT MAP AT 8192     :rem 39
4 POKE 53265, PEEK(53265)OR 32 : REM BIT MAP ON      :rem 141
5 SYS 49152 : REM CLR HIRES SCREEN        :rem 115
6 SYS 49173 : REM SET SCREEN COLOR (BITS THAT ARE OFF)    :rem 237
7 X = 160 : Y = 100 : REM X & Y START POSITIONS      :rem 15
8 GO SUB 13 : REM READ JOYSTICK        :rem 198
9 REM UPDATE SCREEN           :rem 160
10 CH = INT(X/8) : RO = INT(Y/8) : LN = YAND7 : BY = BA SE + RO * 320 + 8 * CH + LN : BI = 7 - (X AND 7) :rem 90
11 POKE BY, PEEK(BY) OR (2↑BI) : GOTO 8       :rem 33
12 REM READ JOYSTICK           :rem 211
13 JV = PEEK(56320) : FR = JV AND 16        :rem 160
15 X = X + ((JV AND 4) = 0) - ((JV AND 8) = 0)      :rem 27
16 Y = Y + ((JV AND 1) = 0) - ((JV AND 2) = 0)      :rem 21
19 IF FR = 0 THEN 5           :rem 98
20 IF X > 319 THEN X = 319          :rem 133
21 IF Y > 199 THEN Y = 199          :rem 148
22 IF X < 0 THEN X = 0          :rem 171
23 IF Y < 0 THEN Y = 0          :rem 174
24 GET A$ : IF A$ < > "Q" THEN RETURN        :rem 247
25 POKE 56, 160 : POKE 52, 160 : POKE 53272, 21 : POKE 53265, 27 : PRINT "{CLR}" : END    :rem 4
26 PRINT "{CLR}" TAB(18)"{DOWN}MENU{DOWN}{4 LEFT}[<4 Y>]"    :rem 72
27 PRINT "{DOWN}" TAB(16) "Q{2 SPACES} - QUIT"      :rem 223
28 PRINT "{DOWN}" TAB(9) "FIRE BUTTON - CLR SCREEN"     :rem 193
29 PRINT "{DOWN}" TAB(10) "JOYSTICK MOVES LINE."      :rem 106
30 PRINT "{3 DOWN}{7 RIGHT} ENTER BORDER COLOR (0 TO 15)." : PRINT SPC(18); :rem 71
31 INPUT BC : POKE 53280, BC AND 15        :rem 206
32 PRINT"{3 DOWN}{7 RIGHT} ENTER SCREEN COLOR (0 TO 15)." : PRINT SPC(18); :rem 75
33 INPUT SC : POKE 49174, SC AND 15 : RETURN      :rem 19
94 END : REM ELECTRIC ERASER         :rem 111
95 A = PEEK(61) + 256 * PEEK(62) + 3 : POKE 786, INT(A/256) : POKE 785,A - 256 * PEEK(786)   :rem 3
96 POKE A - 2, 0 : POKE A - 1,0 : POKE 45, PEEK(785) : POKE 46, PEEK(786) : CLR : GOTO95   :rem 44
100 FOR I = 0 TO 42 : READ J : POKE 49152 + I, J : NEXT I : GOTO 2       :rem 150
101 DATA 169, 0, 162, 32, 160, 0, 132, 33, 134, 34, 145, 33, 200, 208, 251, 232, 224, 64, 208, 244 :rem 17
102 DATA 96, 169, 1, 162, 4, 160, 0, 132, 33, 134, 34, 145, 33, 200, 208, 251, 232, 224, 8, 208, 244 :rem 75
103 DATA 96, 0 

lunedì 11 giugno 2018

Floating point numbers in C64

Continua l'esplorazione di tutto quanto mi sono perso su C64

Quando ho iniziato a studiare l'assembler avevo Olivetti M24 su processore 8086 e sul C64 usavo solo BASIC (al limite integrato con Simon Basic per qualche routine grafica). L'assembler 8086 non e' banalissimo ma la cosa che piu' mi infastidiva era quella di non poter gestire in modo nativo la matematica dei numeri float ....a meno che di non possedere un costosissimo coprocessore matematico (che e' stato integrato solo dalla serie x486)

Con sorpresa ho scoperto che usare i numeri a virgola mobile e fare conti su C64 e' incredibilmente banale anche in assembler.

Per prima cosa i numeri float hanno una struttura di 5 byte e sono nel formato esponenziale base 2 ...quindi qualcosa del tipo x.xxx 2^y

byte 1 : esponente : il valore dell'esponente e' nel formato exp-129 (quindi 2^0 avra' il valore 81, valore piu' bassi indicano esponenti negativi)

byte 2-5 : mantissa

Convertire da formato float decimale a float C64

per fare poca fatica conviene usare direttamente la conversione di C64

Per esempio si digiti da BASIC
NEW
X = 3.14159

IND = PEEK(71)+256*PEEK(72)

PRINT PEEK(IND)
PRINT PEEK(IND+2)
PRINT PEEK(IND+3)
PRINT PEEK(IND+4)
PRINT PEEK(IND+5)

quale e' il significato. Diciamo al BASIC di salvare il valore di float di pi greco in una variabile. Nelle locazioni a pagina zero 71 e 72 c'e' il puntato alla zona di memoria dove viene inserito effettivanemente il numero (gia' decodificato in formato C64). Nel mio caso l'indirizzo e' $805 (esadecimale) se sei aggiungono altre variabile si vedra' che il puntatore si aggiorna

La risposta al comando e'
$82 $49 $0F $CF $82

ovvero la traduzione in formato float C64
Per fare la riprova si puo' usare questo programma assembler in ACME
in pratica si popola la'accumulatore di float FAC con il valore di pi greco, si chiama il kernal per la conversione del valore in una stringa ($BDDD) e poi si mostra la stringa a schermo
-----------------------------------------------
Float

!source "./std.a"
!to "float.prg", cbm
!sl "float.map"
!cpu 6510

*=$1000

LDFAC = $bba2


!macro LoadFAC .addr {
lda #<.addr
ldy #>.addr
jsr LDFAC
}

+LoadFAC pi

MOVFA ; sposta FAC in ARG


;JSR $E264 ;COSENO NON UTILIZZATA

JSR $BDDD ;Convert FAC to zero-terminated string representation at memory addresses $0100-$010A.

 LDY   #0

loop:   LDA   $100,Y
        BEQ   done
        JSR   $FFD2        
        INY
        BNE   loop          
done:

RTS

pi !by $82, $49, $0F, $CF, $82
-----------------------------------------------

Per la riconversione l'algoritmo (preso da qui) e' il seguente

  • Exponent: exp-128
  • Mantissa: (m4 >= 128 ? -1 : +1) * ((m4 | 0x80) >> 8 + m3 >> 16 + m2 >> 24 + m1 >> 32) 

La cosa interessante e' che da Assembler si possono chiamare anche le funzioni trigonometriche, le stesse condivise con BASIC, ma che risultano decisamente piu' veloci (vedi la chiama alla subroutine in $E264 corrispondente al coseno)

c'e' da osservare che la precisione dell'arrotondamento delle routine non e' ottimale (vedi questo link)

Opencv camera calibration in cpp

Oltre che con uno script Python come visto qui la calibrazione della camera si puo' fare anche con il programma in CPP Questo il proce...