Programmare direttamente l’hardware dell’Amiga non era una cattiva pratica!

Fiumi di parole sono state versati nei social media a causa dell’ultimo articolo sulle cattive pratiche di di diversi sviluppatori, sul cui argomento non voglio tornare (essendo già stato abbastanza sviscerato), ma in alcuni casi sono emersi alcuni commenti non propriamente favorevoli alla programmazione diretta dell’hardware, che viene vista da alcuni come un qualcosa di “sporco”, che non si sarebbe dovuto fare, dovendo passare sempre dal s.o..

Posto che dovremmo essere tutti d’accordo che l’utilizzo del s.o. dovrebbe essere sempre la prima scelta quando si realizza qualcosa (perché è quello che garantisce la maggior compatibilità. Oltre a offrire pure diversi strumenti che rendono la vita molto più facile ai programmatori), non posso però condividere il diktat generale di abiura (e, peggio ancora, disprezzo) della cosiddetta programmazione “bare metal“, non foss’altro perché non era stata pubblicamente deprecata (con indicazioni del tipo: “non funzionerà più in futuro, per cui questo sarebbe meglio non farlo più”) o addirittura proibita.

Quindi se da una pare si potrebbe dire giustamente che: “se qualcosa non è proibito, posso farlo”, dall’altra la libertà concessa dovrebbe sempre essere accompagnata dal buon senso nelle scelte che si fanno, non dovendo necessariamente andare a cercare qualunque soluzione che la mente possa partorire.

D’altra parte non v’era nemmeno libertà assoluta riguardo la programmazione diretta dell’hardware dell’Amiga, come già anticipato e parzialmente discusso nel precedente articolo, anche se i paletti non sono stati fissati tutti da Commodore fin dall’inizio (una buona parte sì), ma alcuni sono arrivati “in corso d’opera”, man mano che il tempo passava e le sue pubblicazioni ricevevano correzioni, aggiornamenti e nuove edizioni.

Questa potrebbe essere considerata la pecca principale del comportamento della casa madre, ma ciò non esime, però, chi doveva sviluppare software per la piattaforma dal conoscere e rispettare tali regole iniziali, e quelle arrivate successivamente.

L’evoluzione delle linee guida

Di fondamentale importanza in tutto ciò sono, per l’appunto, le linee guida, che avrebbero dovuto dirigere i programmatori nella scrittura di codice corretto e che funzionasse ovunque, qualunque fosse la configurazione della macchina dell’utente. Purtroppo, e come già detto, non sono arrivate tutte in una volta, ed erano anche sparpagliate in posti diversi.

Cominciando con la prima edizione dell’Amiga Hardware Reference Manual (d’ora in poi chiamato Hardware Manual per semplicità), infatti, si può notare come il primo capitolo, che introduce alla macchina, manchi di qualunque indicazione in merito (che invece arriveranno già a partire dalla seconda) e venga presentato come una guida per conoscere il funzionamento interno dei chip custom e un tutorial su come programmarli, per chi avesse bisogno di ottenere prestazioni più elevate (rispetto a quanto offerto dal s.o.), o per chi avesse bisogno di realizzare nuove periferiche.

In realtà e andando avanti con la lettura si scopre come diverse regole si trovino sparse in tutto il libro, dai vari capitoli fino alle appendici, e che vengano citate più o meno implicitamente dove capiti, a seconda del particolare contesto. Una buona parte le ho raccolte ed elencate di seguito:

  • si possono aggiungere periferiche e altra memoria tramite il connettore esterno (pag. 6. Si fa riferimento alle pagine del PDF per comodità di accesso e controllo, con pag. 1 del libro in formato cartaceo corrispondente a pag. 15 nel PDF).
  • l’istruzione MOVE del Copper è usata per impostare un valore in uno dei registri dei chip custom (pag. 23), che sono elencati nell’appendice B (pag. 24).
  • nell’istruzione MOVE del Copper ci sono dei bit inutilizzati che devono essere impostati a zero (pag. 23).
  • il Copper non può scrivere nei primi 8 registri (fino all’indirizzo $10), oppure nei primi 16 (fino a $20) a seconda che il bit di Copper Danger sia impostato o meno (pag. 28).
  • la modalità HAM usa 6 bitplane (pag. 92).
  • il periodo minimo per i canali audio è di 124 (pag. 152).
  • il data fetch start del controllore video dev’essere un valore multiplo di 8 (pag. 200).
  • la tabella che mostra la pipeline del Blitter per diversi casi è puramente illustrativa e Commodore si riserva il diritto di cambiarne il funzionamento (pag. 204).
  • L’esempio delle pagg. 205 e 206 mostra come si programma il Blitter e la procedura di waitblit per aspettarne il completamento della precedente operazione.
  • i bit 3 e 13 di INTENA sono riservati per sorgenti di interruzioni esterne (pag. 221).
  • l’hardware richiede una sequenza speciale per far partire un’operazione col disco (pag. 242).
  • dopo la ricezione di un byte dalla tastiera il processore deve impostarne a zero la linea SP per almeno 75us (pag. 247).
  • l’appendice B riporta che ci sono registri usati soltanto dal DMA (pag. 267).
  • I registri sono a sola lettura o a sola scrittura (pag. 268).
  • la mappa di memoria (pag. 276) mostra blocchi di indirizzi da non usare o riservati per usi futuri (fra cui $C00000 per la famosa memoria Slow).
  • sempre la mappa di memoria mostra che l’inizio della ROM non è stato ancora definito: $FC0000 è indicato soltanto come una possibilità.
  • quando si accede ai due chip CIA come byte (e quindi non come word = 16 bit = 2 byte), il bit 0 dell’indirizzo dev’essere sempre a 0 per il CIAA e a 1 per il CIAB (pag. 294).

Ho omesso tutte le pagine in cui venivano riportati casi di bit inutilizzati nei registri, o che dovevano essere a zero, come pure tutti i puntatori dei canali DMA che devono avere il bit 0 a zero (perché si accede sempre a word = 2 byte), perché altrimenti la lista non finiva più.

L’unica eccezione in questo caso è rappresentata dai registri dei due chip di I/O, i suddetti CIA: i bit inutilizzati sono ignorati in scrittura e restituiti come zero in lettura (pag. 299), per cui si può fare sostanzialmente ciò che si vuole (anche se sarebbe sempre saggio non andare a toccarli).

Comunque le implicazioni che derivano dai vari punti dell’elenco dovrebbero essere ovvie e quindi le ho omesse per non allungare troppo il discorso, ma faccio giusto un esempio col primo. Il quale afferma che la configurazione del sistema possa cambiare sfruttando il connettore di espansione, che significa più memoria (Fast, in questo caso) e/o nuovo periferiche (che includono anche un nuovo processore). Conseguenza di ciò dovrebbe essere che i programmatori avrebbero dovuto ovviamente (!) tenerne conto.

Giusto per essere chiari e fugare ogni dubbio, si tratta di un elenco di vincoli che, in quanto tali, dovrebbero essere rispettati quando si scrive codice che tocca queste precise aree dell’hardware. Ma non sono i soli, come vedremo.

I tre modelli di programmazione

Anche perché questo manuale è prodigo di dettagli sull’hardware e indicazioni su come si dovrebbero programmare le sue componenti, ma rimane un mistero su… come cominciare! In che modo, insomma, mettere a frutto tutte queste conoscenze acquisite.

In generale, con l’Amiga ci sono sostanzialmente tre modelli per realizzare software per questa piattaforma:

  • sfruttando le API del s.o. –> usando esclusivamente il s.o.;
  • programmando direttamente l’hardware –> facendo fuori il s.o.;
  • sfruttando le API del s.o. e programmando direttamente l’hardware quando necessario –> modalità che potremmo definire “ibrida”, perché unisce i due “mondi” (potremmo dire il meglio dei due).

Il primo e terzo caso sono di dominio del s.o., per cui vanno seguite le linee guide per il corretto sviluppo che si trovano negli altri manuali della collana Amiga Technical Reference Series (Amiga ROM Kernel Reference Manual: Exec, Amiga ROM Kernel Reference Manual: Libraries and Devices, Amiga Intuition Reference Manual). Il terzo caso, però, è ancora più ristretto, perché impone di seguire anche quelle dell’Hardware Manual, per ovvie ragioni.

Il secondo caso dovrebbe, in linea teorica, affidarsi esclusivamente a quest’ultimo, ma non è esattamente così, perché dipende anche dagli altri, e non soltanto per una questione di appartenenza alla collana che li raccoglie tutti, come vedremo più avanti.

Infatti il motivo principale è che, mentre per realizzare un’applicazione che giri appoggiandosi al s.o. bastano tutte le informazioni che si trovano nei tre manuali summenzionati, non c’è niente nel manuale dell’hardware che ci dica come svilupparne una che tagli fuori il s.o. consentendo di prendere il pieno nonché esclusivo controllo della macchina partendo dal dischetto infilatovi prima dell’avvio (o a seguito di un reset).

Dunque questo manuale, da solo, non servirebbe assolutamente a niente nel caso volessimo realizzare qualcosa nel secondo dei tre casi. Nel terzo, invece, darebbe ovviamente il suo contributo, perché la programmazione diretta dell’hardware avverrebbe all’interno dell’ecosistema rappresentato dal s.o., e seguendone in primis le sue regole.

Infatti nel terzo caso si potrebbe preparare un dischetto standard Amiga, che alla partenza caricasse subito un eseguibile per prendere pieno controllo del sistema, e utilizzare poi un proprio track loader (codice che si occupa della lettura delle tracce dai dischi. Nel s.o. è rappresentato dal componente trackdisk.device, ma i giochi che prendono il completo controllo del sistema devono riscriverselo) per leggere i dati.

Questo stratagemma consente di ottenere un approccio ibrido, per l’appunto, sfruttando in parte il s.o. per la partenza e il successivo avvio del codice vero e proprio. Ma comporta un po’ di complicazioni (ad esempio il primo disco dovrebbe servire soltanto a quello, magari caricando soltanto l’introduzione del gioco e lasciando gli altri dischi nel formato proprietario) e non rappresenta il metodo diffuso all’epoca, cioè dischetti di giochi o demo che si avviavano immediatamente una volta inseriti nel lettore.

Questi, infatti, in gergo erano chiamati NDOS (Non-DOS. L’AmigaDOS era la parte di gestione più ad alto livello di dischi et similia. Giusto per semplificare), e funzionavano molto diversamente, perché in genere il loro formato non aveva nulla a che fare con quello dei dischetti standard dell’Amiga. Ad esempio, a volte soltanto la prima traccia era leggibile dal s.o., giusto per consentire l’avvio del codice del gioco, mentre tutte le altre potevano essere totalmente illeggibili (magari perché usavano un particolare formato MFM per la protezione). Rimane, però, un mistero su come funzionasse il tutto.

Il bootstrap

Il dubbio viene dissipato dalla lettura di quello che è il primo manuale per importanza quando si parla di Amiga: Amiga ROM Kernel Reference Manual: Exec. Infatti scorrendo le sue pagine si arriva finalmente alla penultima appendice (la C), la quale a partire dalla pagina 265 (anche qui, si fa riferimento alle pagine del PDF, con pag. 1 della versione cartacea corrispondente alla 21 del PDF) comincia a parlare esplicitamente di accesso diretto all’hardware.

In quella successiva arrivano le prime indicazioni per chi volesse prendere pieno controllo della macchina all’avvio, subito dopo l’avvio del Kickstart, con a seguire alcune pagine riguardo la mappa di memoria del sistema e l’elenco dei registri.

L’agognata informazione, invece, si trova nelle pagine 273 e 274, le quali riportano i dettagli relativi alla fase di boot del sistema, con la descrizione delle strutture dati che devono essere presenti nei primi due settori della prima traccia del disco, di dove deve trovarsi il codice da eseguire, cosa si trovi in alcuni registri, e cosa restituire al Kickstart per completare la procedura di avvio.

Vengono inoltre riportate informazioni sul formato fisico del dischetto, cioè in che modo risulta strutturata una traccia e il preciso formato dei singoli byte che costituisco i suoi settori. L’ultima parte è, invece, dedicata ai sorgenti in C di un paio di routine per codificare e decodificare dati in formato MFM.

A questo punto c’è veramente tutto, ma è stato necessario arrivare in fondo a un manuale che, sulla carta, sarebbe dovuto servire a tutt’altro, e che invece è divenuto non soltanto importante, ma addirittura indispensabile e obbligatorio per poter sviluppare software con la seconda tipologia, quindi programmando direttamente l’hardware e tagliando fuori l’intero sistema.

E’ interessante notare come lo scorrere delle pagine di questo manuale porti a ulteriori linee guida (sparse fra le pagine 10 e 11), le quali non servono soltanto a scrivere codice corretto quando il software gira col/nel s.o., ma ci sono anche indicazioni generali e che includono, ad esempio, anche quelle per processori diversi dal 68000 (vengono menzionati, in particolare, 68010 e 68020, e le differenze che esistono col 68000 riguardo la famigerata istruzione MOVE SR. Che risulta liberamente utilizzabile in quest’ultimo modello, ma privilegiata negli altri due).

La sintesi di tutto ciò è che le linee guida ci sono e servono a mettere diversi paletti per la programmazione della macchina, in qualunque modo lo si fa faccia. Quello che emerge, soprattutto, è come l’Amiga si distacchi molto dalla concezione di hardware unico, praticamente immutabile, che ci era stata consegnata dalle console e, in particolare, dagli home computer della precedente “era a 8 bit” che la nostra stupenda piattaforma ha contribuito a chiudere (per inaugurare quella dei “16 bit”), come già anticipato.

Con tali regole sarebbe, quindi, stato possibile scrivere codice con accesso diretto ed esclusivo all’hardware, e in grado di girare su tutte le macchine Amiga, anche future e/o aventi configurazioni diverse, potendo contare su un minimo comune denominatore ben delineato (nel manuale dell’hardware), ma senza per questo comprometterne lo sfruttamento delle maggiori risorse a disposizione (in particolare quantità di memoria, frequenze, e nuovi modelli di processori) se gli sviluppatori avessero voluto (e in alcuni casi l’hanno fatto: ci sono giochi che ne hanno tratto giovamento).

“L’origine di tutti i mali”: il codice automodificante

Una particolare menzione merita, però, il famigerato codice automodificante, il quale ha generato parecchie diatribe, poiché diversi lo considerano “sporco” (tanto per cambiare!) e addirittura illegale: da annoverarsi fra le cattive pratiche di programmazione che non si sarebbero mai dovute seguire, perché avrebbe potuto comprometterne l’esecuzione su macchine dotate di memoria cache.

Evidentemente si tratta di gente che non ha mai approfondito il funzionamento dei processori, delle loro architetture e microarchitetture, sconoscendo il concetto di funzionalità (quella dell’avere a disposizione delle cache, ad esempio) e del suo utilizzo o meno ai fini degli obiettivi che si pone il software. Probabilmente è stato loro sufficiente imparare l’uso di un po’ di istruzioni, giusto quanto basti per buttare giù delle righe di codice, ignorando tutto il resto. Ma l’ignoranza non è mai una giustificazione: semmai un’aggravante per chi si cimenti in certe affermazioni arbitrarie nonché prive di fondamento.

In ogni caso la situazione in questo particolare caso non è mai stata chiara nemmeno in casa Commodore. Mentre lo era, invece, per Motorola, che con l’arrivo di nuovi modelli per la sua gloriosa famiglia 68000 ha aggiornato le direttive per il loro corretto utilizzo. Oltre al fatto che i processori dotati di cache l’avevano disabilitata all’avvio (per questioni di compatibilità). D’altra parte, e come già detto, si tratta di pur sempre di una funzionalità, abilitabile come e quando si vuole a seconda delle particolari esigenze del momento.

In questo caso il problema è rappresentato dal fatto che la prima edizione dell’Hardware Manual e di quello di Exec (ricordo che non esistono API per gestire le cache, le quali verranno introdotte soltanto nel 1990, con la versione 2.04 del s.o.) fanno completamente scena muta riguardo questo preciso argomento, dando di fatto il via libera all’utilizzo di questa pratica senza restrizione alcuna.

Fermo restando che, essendo ben nota la possibile presenza di elementi diversi dal 68000, si sarebbe in ogni caso dovuto gestire correttamente il processore su cui gira il codice. Qui, ovviamente, fanno fede i manuali di Motorola su come programmare correttamente i suoi processori (Commodore non può certo sostituirvisi integralmente in tal compito!).

La seconda edizione tocca finalmente quest’argomento e lo chiarisce (a pag. 28 del PDF e 10 nel formato cartaceo) in maniera a dir poco lapidaria: non si deve utilizzare codice automodificante. Non ci sono speciali deroghe o casi d’uso: non si deve usare e basta!

Una posizione francamente assurda, per due motivi. Primo perché è esattamente opposta alla precedente, il che contribuisce a generare il caos negli sviluppatori che avessero deciso di utilizzare tale tecnica (visto che prima non era proibita).

La seconda, altrettanto importante, è che mostra come non avessero chiaro anche loro il funzionamento dei processori (come già illustrato all’inizio di questa sezione), affermando che il codice automodificante può essere “vanificato” (immagino che sia inteso come il non sortire effetto) dalle capacità di cache dei processori. Può succedere, è vero, ma non è affatto detto che ciò si verifichi, in quanto ci sono i modi (da notare il plurale) per gestirlo correttamente.

Il buon senso arriva soltanto un paio di anni dopo (nel 1991), con la terza e ultima edizione del manuale dell’Hardware, la quale scoraggia l’uso di questa pratica (pag. 28 del PFD e 13 del cartaceo), ma non la proibisce, fornendo al contempo preziose indicazioni su come comportarsi in questi casi.

Si sofferma, in particolare, su come effettuare lo svuotamento (flush, in gergo tecnico) delle cache sfruttando l’apposita API del s.o., e fornendo anche uno spezzone di codice nel caso in cui si sia, invece, preso pieno controllo dell’hardware (quindi con l’impossibilità di chiamare la nuova API), mettendo giustamente in guardia dalla possibilità che non funzioni su futuri modelli della famiglia (sono citati 68020, 68030 e 68040. All’appello manca il 68060, che non era stato ancora commercializzato).

Un bel passo avanti che mette finalmente le cose al posto giusto, lasciando nuovamente libertà di scelta (ma non assoluta: rimangono pur sempre le direttive da seguire, e bisogna sempre fare attenzione alla logica di prefetch dei processori) agli sviluppatori sull’utilizzo o meno di codice automodificante.

In realtà il quadro non è affatto completo, poiché non viene menzionato lo scenario che garantisce l’assoluta compatibilità con qualunque processore, anche futuro: disabilitare completamente le cache!

Il manuale, infatti, si è soffermato esclusivamente su quello in cui le cache siano sempre abilitate, e che giustamente abbiano bisogno di essere svuotate a seguito della generazione del codice automodificante. Questo è certamente quello più importante e a cui si dovrebbe puntare sempre, se possibile, poiché le cache danno un notevole contributo al miglioramento delle prestazioni del processore, ma… va gestito in maniera corretta, e ciò potrebbe non essere possibile.

Il problema più grosso qui è dovuto esclusivamente a Motorola, la quale è stata dedita a cambiare le specifiche delle architetture dei suoi processori, rendendoli incompatibili (il successore è stato sempre incompatibile col precedente a causa di qualche modifica). Dunque non è affatto garantito che i futuri processori avrebbero messo a disposizione quella precisa istruzione per lo svuotamento delle cache, per cui Commodore ha dovuto mettere le mani avanti con quella porzione di codice che ha fornito.

Ma la soluzione definitiva al problema l’ho fornita qui sopra: disabilitare del tutto le cache rende il codice automodificante a prova di futuro (sempre tenendo conto del prefetch! Ma è poca roba = pochi byte con cui dover avere a che fare). Si tratta di una soluzione certamente più onerosa in termini computazionali rispetto al loro svuotamento, ma almeno è sicura.

A questo punto la palla passa nuovamente ai programmatori, i quali devono decidere in che modo sfruttare questo scenario (disabilitare permanentemente le cache, o disabilitarle e riabilitarle in maniera programmatica), a secondo delle loro specifiche esigenze di progetto.

Nel caso di una gestione programmatica la soluzione migliore è quella di conservare il valore del registro CACR all’avvio, crearne una versione avente delle cache disabilitare (soltanto quella per il codice, ad esempio, lasciando intatta quella per i dati), e caricarvi uno di questi due valori per abilitare o disabilitare le cache all’occorrenza, in modo da ridurre al minimo l’impatto prestazionale (maggiori informazioni nel sito di WHDLoad).

Conclusioni

Dipanata la matassa anche per quanto riguarda la pratica più controversa e contestata, è tempo di tirare le somme. A mio avviso la contrapposizione fra sviluppatori che hanno programmato direttamente l’hardware e quelli che, invece, si sono affidati esclusivamente al s.o., non ha motivo di esistere: entrambe sono scelte dettate dalla visione dei programmatori e, in ultima analisi, dallo scopo dei progetti a cui hanno lavorato. Con l’aggiunta dello scenario ibrido rappresentato dal software scritto per il s.o., ma che alcune volte richiede ed esegue l’accesso diretto all’hardware, il quale rappresenta il (delicato) ponte di giunzione fra i due mondi completamente diversi.

Non c’è, quindi, da biasimare chi abbia scelto uno dei tre modelli, perché si arriverebbe a questioni puramente ideologiche, dettate da una visione se non miope, certamente molto limitata di quello che era l’Amiga e delle esigenze dell’epoca. Infatti sarei veramente curioso di sapere, altrimenti, in che modo si sarebbero potuti realizzare Fighin’ Spirit e USA Racing (i giochi a cui ho lavorato. Quest’ultimo non rilasciato) utilizzando soltanto il s.o., tanto per fare un paio di esempi a me cari.

L’unica discriminante, in parte già citata nel precedente articolo, dovrebbe essere soltanto l’aver seguito o meno le linee guida di Commodore in modo da rendere il software robusto e resistente alle innovazioni future, tanto da parte del software di sistema quanto da quello di hardware delle macchine & periferiche, e quindi in grado di girare su qualunque modello in qualunque configurazione.

La non omogenea nonché sparsa documentazione non ha aiutato, e di questo la responsabilità principale ricade sulla casa madre, che comunque si è trovata davanti a un prodotto nuovo e rivoluzionario che ha cambiato il modo in cui era concepito l’home computing fino ad allora, gettando le basi dei sistemi moderni che ancora oggi usiamo proficuamente.

Ma c’era! La documentazione era presente, e anche molto prima che fosse disponibile la collana Amiga Technical Reference a tutto il pubblico. Di ciò esistono tracce nell’Hardware Manual, dove a pag. 301 viene chiaramente riportato come l’appendice G (relativa all’autoconfigurazione), la quale era stata pubblicata nelle versioni (al plurale!) iniziali, fosse stata invece rimossa nella prima edizione. E ancora meglio lo si legge nel manuale di Exec (sempre prima edizione), dove a pag. 267 si legge che le linee guida per l’autoconfigurazione sarebbero state pubblicate nel Dicembre del 1985.

E non poteva essere altrimenti, se teniamo conto del fatto che già all’introduzione dell’Amiga 1000 fossero disponibili applicazioni e giochi: le software house hanno dovuto attingere a delle documentazione per poterli scrivere e pubblicarli!

Una nota finale sui “fedelissimi” dello sviluppo usando soltanto il s.o.: è comprensibile e ho già riportato che sarebbe la soluzione migliore da seguire, se possibile, in quanto è quella che garantisce una maggior capacità di coprire l’intero parco macchine, anche futuro. Ma non per questo dev’essere considerata l’unica accettabile e si debba mettere in croce e biasimare chi, per scelta o necessità, abbia deciso di seguire la strada più complicata (da questo punto di vista).

Per alcuni tale presa di posizione è dovuta al fatto che generalmente non è possibile, o è molto difficile (pur avendo i sorgenti), portare sulle “evoluzioni/riscritture” (AmigaOS4, MorphOS, AROS) il software che accede direttamente all’hardware. Obiezione ragionevole, ma irrilevante: quelle macchine non sono Amiga, e il software di cui abbiamo avuto modo di godere girava sulle macchine di casa Commodore che potevano fregiarsi di quel nome. La soluzione, in questi casi, rimane quella di affidarsi al buon, vecchio, UAE, e il problema è risolto.

Per chi, invece, continuasse ancora ad avere una visione religiosa sulla questione e fosse fermamente convinto che l’unica strada sarebbe dovuta essere quella di utilizzare il s.o. e basta, non avrebbe che da… dimostrarlo!

Press ESC to close