[zxspectrum] Re: AEON demo

  • From: "Alessandro" <alessandro.poppi@xxxxxxxxx>
  • To: <zxspectrum@xxxxxxxxxxxxx>
  • Date: Sun, 11 Jan 2009 13:21:45 +0100

On Sunday, January 11, 2009 12:18 PM [GMT+1],
Eugenio Ciceri <eugenio.ciceri@xxxxxxxxx> wrote:

Aggiungo una stranezza: se lancio la snap modificata funziona ma non
sente piu' l'NMI ne' via seriale ne' via hardware.
E' una conseguenza della modifica del registro?

"Purtroppo" e' cosi'.

Il problema non risiede nella presenza della zxmmc+ sul pettine, ma bensi' nella modifica alla rom originale indispensabile per saltare nella bootrom in caso di NMI. Si tratta di 10 bytes consecutivi a partire dall'indirizzo $67 (l'NMI handler e' a 66, ma il primo byte (che e' un PUSH AF) e' rimasto uguale).

Sono giunto a questa conclusione (che mi ha poi portato a fare le modifiche) in modo semplice: ho flashato la rom n.1 originale del 128K nel banco 7 (la coppia di banchi 6/7 contiene le rom 0,1) e ho scoperto che il ripristino funzionava perfettamente, comprese stelle e via lattea. Quindi: il demo fa qualcosa che utilizza una o piu' locazioni comprese fra $67 e $70, che sono 10 bytes in tutto. NON ci sono altre modifiche nella rom.

Quindi, adesso descrivo nel dettaglio come faccio a saltare nella bootrom in caso di NMI (in realta' e' tutto spiegato nei sorgenti, ma preferisco riscriverlo qui). Vista la gente che bazzica da queste parti non e' difficile che salti fuori un modo piu' furbo.

Premessa:
Nella cpld dello zx-badaloc c'e' un registro a 16 bit che viene costantemente confrontato con l'address bus dello Z80. Se avviene un opcode fetch a $66 (nmi handler), quindi address = 66, mreq = 0, M1 = 0, rd = 0 nella cpld scatta un flag che si chiama 'context_switch'. Questo forza in 0-3FFF il banco che contiene la bootrom, a prescindere da cio' che era attivo fino a quel momento. Forza anche una regione di ram inutilizzata (perche' sovrapposta alla ram doppia porta del video) nell'ultimo KB ($3C00 - $3FFF) ad uso variabili esclusive della bootrom.

Questo e' talmente veloce da avere effetto fin dal byte $66, lo Z80 lo leggera' dalla bootrom e non dalla rom che era in esecuzione. Quindi, il meccanismo non richiede NESSUN intervento di modifica alle rom originali.

Nella zxmmc+ cio' non e' possibile per due importanti ragioni: 1) un registro a 16 bit richiede altre 16 macrocelle e delle 72 disponibili nella XC9572 credo ce ne siano una o due libere; 2) il bus indirizzi non e' collegato completamente, per mancanza di pin (gia' ho dovuto condividere un paio di ingressi kempston con gli indirizzi MSB della ram/flash, tanto non avviene mai contemporaneamente una lettura in memoria e una lettura I/O). E' collegata tutta la parte LSB (A7:A0) in modo da distinguere gli accessi ai registri I/O in modo univoco, oltre ad A15:A14 per sapere quale area di memoria (16K) viene acceduta dallo Z80.

Ho dovuto quindi trovare il modo di cambiare banco modificando il meno possibile la rom originale. Notare che se e' in esecuzione la ROM 0 mentre arriva l'NMI, non c'e' niente da fare perche' i dementi hanno messo nel bel mezzo roba che non c'entra nulla (verrebbe eseguito un operando, vedi sorgente della rom 0 del 128K) e a questo non ho mai posto rimedio (niente snapshot se e' attiva la rom 0).

Veniamo dunque alla rom originale del 16/48K (che sotto questo aspetto non differisce dalla rom 1 del 128K):

0066    f5            push af
0067    e5           push hl
0068    2ab05c   ld hl,(5cb0h)
006b    7c           ld a,h
006c    b5          or l
006d    2001      jr nz,0070h
006f     e9          jp (hl)
0070    e1          pop hl
0071    f1           pop af
0072    ed45      retn

Il problema da risolvere era il seguente: salvare l'attuale contenuto di $7F (fastpage) e caricarvi il valore necessario a far subentrare la bootrom, ovvero $DF (banco 31 in ram, come detto in precedenza). Occorre altresi' che ci sia un punto di rientro utile a terminare il servizio NMI stando gia' nella rom originale, ovvero dopo che $7F e' stato ripristinato (altrimenti non c'e' modo di continuare ad eseguire il programma che era in esecuzione, se la rom e' diventata un altra). Entrambi gli scambi devono avvenire in modo che l'esecuzione possa proseguire nell'altro banco senza discrepanze, quindi c'e' una stretta corrispondenza nel piazzare le istruzioni in entrambe le rom.

La mia soluzione attuale per la rom originale e' la seguente, descritta nel file di testo incluso con il sorgente:

0066    f5         push af      ; same as original ROM
0067   db7f      in a,(7fh)
0069   f5          push af      ; save FASTPAGE content onto stack
006a   3edf      ld a,0dfh    ; RAM bank +1F (bootrom)
006c d37f out (7fh),a ; last instruction fetched from this ROM: bank switch takes place
006e   00        nop            ; 2 never executed NOPs
006f    00        nop
0070 f1 pop af ; never executed POP AF (second push af balance if no zxmmc+) 0071 f1 pop af ; RETURN POINT for handler termination, same as original
0072   ed45    retn

Il POP AF all'indirizzo $70 non viene mai eseguito perche' ci si trova ancora nella bootrom (vedi piu' avanti), ma serve a bilanciare il secondo PUSH AF a $69 nel caso la zxmmc+ non fosse presente: questa rom non andrebbe in crash.

Il POP AF all'indirizzo $71 e' il punto in cui si rientra dalla bootrom alla fine dell'handler: e' fondamentale che sia identico alla rom originale, in questo modo e' possibile ripristinare uno snapshot anche saltando in una rom originale e non una modificata.

Riassumendo, i requisiti sono: salvare e settare $7F, NON aumentare le dimensioni della routine, concatenare l'esecuzione con il codice in bootrom.

La bootrom, a quelle locazioni, contiene il seguente codice:

0066   push af         ; NOT executed bytes: still running original bank
0067   in a,($7F)     ; but if not, perform same action
0069   push af         ; fastpage is saved as the patched 48K rom does
006A  nop              ; patched rom takes 8 bytes to jump here:
006B  nop              ; these 4 NOPs balance the length
006C  nop
006D  nop
; total time HERE = 49T-states; from PATCHED-ROM = 51T-states

006E  defb $18        ; FIRST BYTE EXECUTED
; the OUT instruction ($D3) which will lead the jump to address $43.

nmi_handler_exit:
006F  out ($7F),a    ; back to original ROM, first executed byte = 0071
                              ; (pop af, retn) to terminate gracefully

La routine modificata nella rom originale richiede 8 bytes per salvare 7F e modificarlo in modo da saltare nella bootrom. La prima locazione realmente eseguita dalla bootrom e' 006E. Sfortunatamente, per rientrare nella rom originale alla fine dell'esecuzione dei servizi NMI occorre che il POP AF ed il RETN siano al loro posto nella rom originale stessa. Occorre altresi' che la bootrom esegua l'ultima operazione (cambio banco, ovvero OUT $7F) nei due bytes che precedono il punto di attacco (altrimenti non c'e' continuita' fra le due rom). Dato che il POP AF/RETN non puo' essere spostato da 0071, l'OUT $7F deve essere a 006F (occupa due bytes). Questo porta al fatto che resta UN SOLo byte libero a 006F come primo byte eseguito in bootrom nella fase di ingresso nell'handler.

Fortunatamente, mettendo un $18 (JR) l'opcode che segue ($D3 dell'istruzione OUT) provoca un salto all'indietro, precisamente all'indirizzo 0043 che e' totalmente libero nella bootrom. Da quel punto, l'handler procede alle proprie operazioni.

Tutte queste istruzioni sono cronometrate come T-states in quanto se l'NMI e' arrivato dalla seriale, stiamo ricevendo il START BIT e quindi per sincronizzare la ricezione del byte occorre sapere quanto tempo e' andato perso. Per fortuna lo Z80 a 3,5MHz ce la fa e cade giusto nel mezzo del bit.

Ricapitolando: all'arrivo di un NMI accadono le seguenti cose:

La rom originale modificata salva sullo stack il contenuto di fastpage ($7F) e vi carica il valore necessario a saltare nella bootrom. Il cambio di banco avviene dopo 8 istruzioni, il primo byte letto dalla bootrom sara' all'indirizzo 006E. Qui si trova un JR ($18) che combinato con l'opcode dell'istruzione che si trova a 006F OUT (n),A provoca un salto all'indietro a 0043 dove la routine vera e propria prende il controllo.

Quando si deve uscire dall'handler (perche' si preme LogOut su zx-com ma anche quando si conclude il caricamento di una snapshot da sd-card), il programma carica il banco originario (che era stato salvato sullo stack) nel registro A e poi salta in 'nmi_handler_exit (006F).

In 006F si trova un bell'OUT ($7F),A che provoca di fatto lo scambio di banco: a partire dalla locazione 0071 l'esecuzione riprende nella rom originale. In quel punto, la rom originale contiene POP AF, RETN ovvero proprio quello che ci vuole per proseguire come se nulla fosse mai accaduto, stando gia' nella rom necessaria.

Per qualche motivo, uno o piu' di questi 10 bytes modificati provocano un problema ad Aeon.

L'indagine e' semplice: adesso provo a cambiarli uno per volta. :-) Se vi viene in mente un metodo alternativo per gestire il cambio di banco meno invasivo, fatevi sotto.

Spiegazione della modifica che "risolve" Aeon demo:

Nello snapshot sono presenti i dati da caricare in tutti i registri, compreso il 7F.

Se lo snap viene eseguito dalla zxmmc+, questo contiene il valore che era stato trovato.

Se invece e' dal badaloc, il default e' 00 per limitare i problemi in caso di cambiamento in uno dei due sistemi. In fase di ripristino sulla zxmmc+, se quel byte e' = 0 viene scelto un 'valore piu' probabile' che e' $62 se lo 'spectrum_mode' e' 16/48K (banco rom 2 = 16/48K) oppure $67 se e' modo 128K (rom 1 del 128K).

Adesso c'e' un nuovo confronto: se il byte contiene 1F (che e' un valore insensato, dato che e' la bootrom ma senza aver attivato i bit di paging), nel registro $7F viene caricato ZERO: cio' corrisponde ad eseguire il ripristino saltando poi nella rom originale VERA, cioe' quella interna.

Ciao!


Other related posts: