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