Nel video precedente di questa serie avevamo implementato un primo test VGA a 640×480 a 60 Hz su CPLD Altera MAX 2, come primo passo per verificare la comprensione dele tempistiche dello standard e per verificare la loro implementazione mediante contatori. Il risultato era soddisfacente dal punto di vista del monitor, ma quel test ignorava deliberatamente un vincolo fondamentale: il sistema che dovrà pilotare la memoria video è uno Z80 a meno di 4 MHz.
Per capire perché il 640×480 sia impraticabile, basta guardare i numeri. Quella modalità richiede un pixel clock di 25.175 MHz. Lo Z80 su Alex80 gira sotto i 4 MHz. Ma il problema non è solo la frequenza della CPU: le RAM statiche che si usano in questo tipo di sistema — le classiche 62256 e simili — hanno un tempo di risposta di circa 70 nanosecondi. A 25 MHz un pixel clock dura 40 nanosecondi. La RAM non fa in tempo a rispondere. È un vincolo fisico, non aggirabile.
C’è poi il problema della memoria. Anche in modalità monocromatica pura — un bit per pixel, solo bianco o nero — una framebuffer a 640×480 occuperebbe 38.400 byte. Più di 32 KB. Con Alex80, che ha 32 KB di ROM e 32 KB di RAM su uno spazio di indirizzamento totale di 64 KB, non c’è spazio per nessuna memoria video a quella risoluzione. E stiamo parlando di zero colori.
La scelta della risoluzione: 320×200
La soluzione non è mettere un microcontrollore più potente a fare da coprocessore grafico. Sarebbe tecnicamente fattibile, ma snaturerebbe completamente la filosofia del progetto: realizzare un sistema coerente con i computer 8 bit dell’epoca, nei limiti del ragionevole.
Il punto di partenza è una modalità VGA meno nota rispetto al classico 640×480: il 640×400 a 70 Hz. Questa modalità ha una particolarità interessante rispetto a quella precedente — il segnale di sincronismo verticale è positivo (normalmente basso, va alto durante l’impulso), mentre quello orizzontale rimane negativo come al solito. Va tenuto a mente nella logica di generazione dei segnali.
Da quella base, si opera una doppia riduzione:
- Il pixel clock viene dimezzato da 25 MHz a 12.5 MHz. Per mantenere le stesse tempistiche orizzontali, si dimezza anche il numero di pixel per riga: da 640 a 320. Il tempo per percorrere una riga rimane invariato.
- Sul verticale, si lavora con sole 200 linee in memoria e si duplica ogni riga prima di inviarla al monitor. Il monitor vede 400 linee, noi ne gestiamo 200. È esattamente quello che facevano i computer 8 bit dell’epoca con le modalità grafiche a bassa risoluzione: i pixel erano rettangolari, alti il doppio rispetto alla larghezza.
Il risultato è una risoluzione effettiva di 320×200 con un pixel clock di 12.5 MHz, pari a un periodo di 80 nanosecondi. Le RAM da 70 ns ce la fanno. E una framebuffer a 320×200 occupa solo 64.000 bit — meno di 8 KB in monocromatico, con margine per qualche bit di colore per pixel. Si rientra comodamente nei 32 KB. Su Alex80 possiamo liberare una finestra di 16KB dimezzando la ROM e accedendo ai 32K di RAM video come due blocchi da 16 KB.
Le tempistiche risultanti sono pienamente compatibili con lo standard VGA: la riga orizzontale viene completata in 32 µs (contro i 31.77 µs del riferimento), e le 400 linee verticali coprono circa 12.8 ms (il riferimento è 12.71 ms). Il monitor non ha problemi a riconoscere il segnale.
Schema logico del controller VGA
Il controller è strutturato attorno a due contatori da 9 bit: uno orizzontale e uno verticale.
Il contatore orizzontale conta da 0 a 399 (metà degli 800 totali del 640×400 standard) e si resetta automaticamente. L’enable è sempre attivo. Il contatore verticale conta da 0 a 448 e viene abilitato — per un solo colpo di clock — quando il contatore orizzontale si azzera, cioè a fine riga.
La logica combinatoria a valle dei due contatori genera:
- Hsync (negativo): attivo basso tra i conteggi 328 e 375
- Vsync (positivo): attivo alto tra i conteggi 412 e 413 del verticale
- Blank: zero quando siamo fuori dall’area visibile (H > 319 oppure V > 399), uno quando dobbiamo visualizzare
Un dettaglio importante: il reset dei contatori deve essere sincrono. Se fosse asincrono, il segnale di reset verrebbe letto e applicato in modo istantaneo, senza rispettare il tempo di permanenza al valore 399. Con il reset sincrono, il contatore legge la condizione sul fronte di salita del clock e si azzera al ciclo successivo, come deve essere.
Implementazione VHDL su Spartan 6
Il codice VHDL è composto da due entità principali: un modulo counter parametrizzato a 9 bit, istanziato due volte nel VGA_controller, e il controller stesso che gestisce la divisione del clock e la logica combinatoria.
Il clock di partenza è quello della scheda AX309, a 50 MHz. Viene diviso per 4 tramite un contatore a 2 bit, ottenendo i 12.5 MHz del pixel clock. Per far capire al tool di sintesi ISE (usato per Spartan 6, che supporta bene il VHDL93) che quel segnale è un clock — e quindi deve essere instradato sulle linee dedicate del dispositivo — si usa il componente BUFG della libreria Xilinx UNISIM. È un piccolo accorgimento che migliora la qualità del placement e la stabilità del clock su tutto il chip.
La logica delle barre colorate per il test è elementare: vengono presi i bit 3, 4 e 5 del contatore orizzontale per generare tutte le otto combinazioni possibili di rosso, verde e blu, ciascuna larga 8 pixel. Il risultato atteso — e poi verificato sul monitor — è una sequenza di 40 barre verticali che scorrono con i colori: nero, blu, verde, ciano, rosso, magenta, giallo, bianco.
Il test su monitor
Dopo la sintesi, il place and route e la generazione del bitstream, il progetto viene programmato sull’FPGA tramite Impact con USB Blaster. Il monitor riconosce il segnale e visualizza correttamente le barre colorate. La frequenza verticale misurata è di circa 70 Hz, quella orizzontale di 31.2 kHz — coerente con le tempistiche calcolate.
Contando le barre: 8 colori per 5 gruppi da 8 pixel fanno 40 barre, 320 pixel totali. I conti tornano.
Questo video chiude la fase di validazione delle tempistiche. Nel prossimo passo si introdurrà la memoria video vera e propria, e con essa si comincerà a definire come lo Z80 potrà scriverci durante il blanking verticale — il tempo morto di circa 35 linee in cui il monitor non mostra nulla e il bus è libero.
