Ciao a tutti. Ho preparato questo post (un po' lungotto) per proseguire il discorso delle interruzioni e analizzare un po' alcuni aspetti del sistema operativo del c64.
Una fonte che è molto utile per studiare l'architettura del c64, e quindi anche le
interruzioni, è anche il testo "Mapping the Commodore 64", anche questo reperibile fra gli e-text di project 64. Questo libro è molto utile ed interessante in quanto fornisce delle preziose informazioni, tra le altre cose, riguardo agli adattatori di interfaccia. Nel nostro caso ci interessano il VIC-II e i CIA, visto che sono loro le fonti delle interruzioni su c64.
Idem dicasi per l'NMI. NMI ad esempio e' chiamato dal tasto Restore, direttamente collegato al 6510 del C64.
Riguardo alle NMI, oltre che dalla pressione del tasto restore, queste possono essere causate anche dal CIA, come spiega Mapping the C64:
792-793 $318-$319 NMINV
Vector: Non-Maskable Interrupt
This vector points to the address of the routine that will be executed
when a Non-Maskable Interrupt (NMI) occurs (currently at 65095
($FE47)).
There are two possible sources for an NMI interrupt. The first is the
RESTORE key, which is connected directly to the 6510 NMI line. The
second is CIA #2, the interrupt line of which is connected to the 6510
NMI line.
Impostando opportunamente un timer del CIA #2, è possibile generare ad ogni intervallo di tempo un segnale NMI, che va ad interessare ovviamente il pin NMI del 6510.
Il sistema operativo usa le NMI solo per il tasto RESTORE e per l'RS-232, ma ciò non toglie che l'utente possa scrivere un programma che utilizzi le NMI al posto delle IRQ, per dire.
Mi preme ora soffermarmi sulle IRQ.
Un timer del CIA #1 genera, per default ogni 1/60 di secondo circa, un segnale che va ad interessare il pin di IRQ del 6510. Quando il 6510 riceve questo segnale,
cosa succede? E' importante sapere il comportamento hardware del 6510, questo anche per poter comprendere le routine del sistema operativo che gestiscono le interruzioni.
Dal libro di Butterfield (Machine Language for the C64 and other C= Computers, sempre project 64 - il capitolo 7 fornisce una facile introduzione alle interruzioni):
When an interrupt signal occurs, the processor completes the instruction it
is currently working on. Then it takes the PC (the program counter, which
contains the address of the next instruction) and pushes it onto the stack,
high byte first. Finally, it pushes the status register to the stack.
That's a total of three bytes that go to the stack.
The processor then takes its execution address from one of the following
locations:
IRQ or BRK - from $FFFE and $FFFF
NMI - from $FFFA and $FFFB
(citazione da pag. 115)
Alla luce di questo, proviamo ad esempio ad esaminare cosa succede quando avviene una IRQ.
Quando si verifica un segnale di irq, il 6510 completa l'istruzione corrente, quindi colloca (push) nello stack il byte alto e basso del contatore di programma (PC) e il registro di stato. Quindi, salta all'indirizzo contenuto in $FFFE e $FFFF (vettore hardware delle IRQ).
In $FFFE e $FFFF è contenuto l'indirizzo: $FF48. Vediamo cosa fa la routine che inizia da questa locazione.
$FF48/65352: IRQ Entry
FF48: 48 PHA
FF49: 8A TXA
FF4A: 48 PHA
FF4B: 98 TYA
FF4C: 48 PHA
FF4D: BA TSX
FF4E: BD 04 01 LDA $0104,X ; 6510 Hardware Stack Area
FF51: 29 10 AND #$10
FF53: F0 03 BEQ $FF58
FF55: 6C 16 03 JMP ($0316) ; Vector: BRK Instruction Interrupt Address
Jump from $FF53:
FF58: 6C 14 03 JMP ($0314) ; Vector: Hardware IRQ Interrupt Address
Si tratta della routine di interruzione di sistema, quella che per default viene eseguita circa 60 volte al secondo. Siccome l'interruzione consiste nell'interrompere il programma correntemente in esecuzione e nell'eseguire una particolare routine (interrupt job), la prima cosa che bisogna fare è salvare i contenuti dei registri nello stack. Il 6510 salva il PC di modo da sapere poi da dove riprendere il programma interrotto... ma dei registri deve occuparsene il software.
Le istruzioni PHA:TXA:PHA:TYA:PHA servono proprio a collocare i contenuti di A, X e Y nello stack.
Il passo successivo consiste nell'analizzare il contenuto del registro di stato del processore, collocato nello stack dal processore stesso all'atto dell'interruzione, per capire se si tratta di una IRQ hardware oppure della BRK. La BRK, per farsi riconoscere, setta il flag BREAK del registro di stato (bit 4 del registro di stato).
L'istruzione TSX trasferisce il puntatore allo stack (SP) nel registro indice X. La successiva LDA $0104,X preleva il contenuto del registro di stato precedentemente salvato nello stack dal processore. Perché $104? Beh, supponiamo che il SP sia $00. In tal caso, la locazione puntata è $100, ed è libera. Consideriamo la struttura dello stack (figura dal libro di Butterfield):
(guardare la figura a pag. 113)
Come si può vedere, lo stack parte da $01FF e va verso il basso. Il puntatore allo stack rappresentale due cifre più a destra della locazione di memoria corrente dello stack (vedi figura).
Se SP per ipotesi è $00, la locazione puntata è $100. Ora, la routine a $FF48 ha collocato nello stack A, X e Y. Quindi, spostiamoci verso l'alto. In $101 è presente Y, in $102 è presente X, in $103 è presente A... e in $104 è presente il registro di stato, che è stato collocato nello stack dal processore, proprio immediatamente prima dell'inizio della routine a $FF48.
Così, nel caso generale in cui SP è qualsiasi, TSX:LDA $104,X consentono di recuperare il contenuto del registro di stato salvato dal processore nello stack. La routine poi controlla se il flag BRK è 0 (IRQ hardware) o 1 (IRQ software dovuta all'istruzione BRK).
Se l'interruzione è una BRK, avviene il salto indiretto JMP ($0316), altrimenti se si tratta di una IRQ vera e propria, avviene il salto indiretto JMP ($0314). $0314-$0315 costituisce il vettore IRQ del sistema operativo. E' interessante osservare anche la routine puntata da tale vettore, residente a $ea31. Più che altro, mi limito a dire che questa routine gestisce la tastiera e vari altri aspetti del sistema operativo che devono essere "aggiornati" costantemente. E' interessante invece analizzare nello specifico come si conclude questa routine:
EA7E: AD 0D DC LDA $DC0D ; CIA1: Interrupt (IRQ) Control Register
EA81: 68 PLA
EA82: A8 TAY
EA83: 68 PLA
EA84: AA TAX
EA85: 68 PLA
EA86: 40 RTI
LDA $DC0D è molto importante. Serve per ordinare al CIA di far cessare il segnale di IRQ. Infatti, nonostante sia avvenuta l'interruzione, il segnale permane fino a che non si ordina via software, al CIA, di smettere. Diversamente, l'interrupt job verrebbe eseguito in continuazione senza dare modo di riprendere al programma interrotto. L'interruzione di tale segnale viene fatta semplicemente leggendo $DC0D... è una caratteristica del CIA (vedi l'entry di $DC0D in Mapping the 64).
Ora, siccome nella routine puntata da $FFFE e $FFFF avevamo collocato i contenuti dei registri A,X e Y, ora dobbiamo recuperare questi valori e collocarli nei registri (che sono stati modificati dalla routine di interruzione) di modo che il programma interrotto possa riprendere correttamente. PLA:TAY:PLA:TAX:PLA fanno proprio questo. Notare che nello stack vige la regola LAST IN FIRST OUT, quindi se i registri sono stati salvati nell'ordine A, X, Y, questi vengono recuperati nell'ordine Y, X e A.
L'istruzione RTI ordina al microprocessore di ripristinare il registro di stato e il contatore di programma utilizzando i valori salvati nello stack dal processore all'atto dell'interruzione. A questo punto, il programma interrotto riprende tranquillamente.
Un grazie a iAN CooG/HF
ed anche a MarC=ello...
leggendo il forum di Ready64 ho trovato un suo riferimento a Knowledge Base Article #121
che affronta l'argomento tema di questo thread...
Sì, è un'articolo ben fatto. L'ho trovato particolarmente interessante per come spiega le interruzioni generate dal VIC-II. Naturalmente il VIC-II è trattato magistralmente in Mapping the C64, anche per quanto riguarda le interruzioni.