CHIP-8: Introduzione alla Programmazione – Parte 1

Qualche settimana fa, con mio enorme piacere, sono stato invitato dalla redazione di RetroProgramming Italia a scrivere qualche articolo interessante sul mondo della retroprogrammazione. Ho così proposto una serie di articoli su CHIP-8; l’idea è stata accolta con entusiasmo e quindi mi sono immediatamente messo al’opera, realizzando un primo articolo, che è stato pubblicato sul gruppo il 22 maggio e qui riportato.
Buona lettura!

Il gioco CHIP-8 Space Intercept (Joseph Weisbecker, 1978).
Il gioco CHIP-8 Space Intercept (Joseph Weisbecker, 1978).

Sommario

CHIP-8 è un linguaggio di programmazione interpretato, sviluppato negli anni ’70 da Joseph Weisbecker con lo scopo di facilitare la programmazione di videogiochi. In origine, l’interprete era disponibile per il computer RCA COSMAC VIP, basato sul microprocessore CDP 1802, progettato dallo stesso Weisbecker e introdotto da RCA nel 1976. Successivamente, sono state implementate versioni dell’interprete per praticamente qualsiasi sistema esistente, dalle calcolatrici scientifiche degli anni ’90 ai moderni sistemi operativi; inoltre il linguaggio è stato ampliato e arricchito di funzionalità mediante lo sviluppo di diverse estensioni.

Nonostante la sua diffusione e l’ampia disponibilità, il linguaggio risulta poco conosciuto, anche tra gli appassionati di retrocomputing. Date le caratteristiche di CHIP-8, ritengo che un approfondimento sia particolarmente consigliato per chi volesse provare a scendere di livello rispetto al BASIC in modo graduale, senza doversi scontrare con le complessità dell’Assembly. D’altra parte, l’implementazione di una macchina virtuale CHIP-8 per il proprio sistema preferito potrebbe essere un buon esercizio per coloro che volessero cimentarsi con la realizzazione di un emulatore.

In questa serie di articoli, avrò il piacere di presentare un’introduzione alla programmazione CHIP-8, utilizzando i moderni PC come ambienti di sviluppo. Nel corso della trattazione, arriveremo a implementare una variante del classico videogioco Snake, simile alla popolare versione presente in alcuni modelli di telefono cellulare degli anni ’90.

Il gioco CHIP-8 Breakout (Carmelo Cortez, 1979).
Il gioco CHIP-8 Breakout (Carmelo Cortez, 1979).
Il gioco CHIP-8 Sequence Shoot (Joyce Weisbecker).
Il gioco CHIP-8 Sequence Shoot (Joyce Weisbecker).

La storia di CHIP-8

Negli anni ’70, gli home e personal computer avevano caratteristiche ben differenti rispetto ai computer che avrebbero dominato il decennio successivo e che tutti abbiamo avuto modo di conoscere e apprezzare. I sistemi più economici e quindi accessibili agli hobbisti erano distribuiti come kit di montaggio e raramente erano equipaggiati con più di 1 o 2 KB di costosa memoria programmabile. Inoltre molti di essi non erano dotati di una vera e propria tastiera, ma l’unico dispositivo di input era costituito da un tastierino esadecimale. Per molti aspiranti programmatori, quindi, il BASIC non era una strada percorribile, a causa dei requisiti elevati in termini memoria e di hardware.

Gli pseudolinguaggi macchina interpretati sono stati ideati proprio per colmare questa lacuna, permettendo la programmazione di videogiochi, applicazioni di controllo in tempo reale, software di grafica e sintesi musicale su sistemi con risorse limitate. Si tratta di linguaggi interpretati ad alto livello, le cui istruzioni sono progettate per applicazioni specifiche. Definendo per le istruzioni un formato più simile al linguaggio macchina che al linguaggio naturale, è possibile semplificare la struttura dell’interprete e ridurre drasticamente l’utilizzo di memoria.

Joseph Weisbecker, ingegnere presso RCA, adottò questo approccio nella progettazione dell’architettura COSMAC e nella definizione del linguaggio CHIP-8 per il computer COSMAC VIP (RCA VP-111), rilasciato da RCA nel 1977 e basato sul microprocessore CDP 1802. Al contrario della maggior parte delle macchine contemporanee, orientate al mondo del business, COSMAC VIP era un sistema a basso costo, dedicato agli hobbisti e incentrato sui videogiochi. Era composto da una singola scheda ed era distribuito come kit di montaggio. Era dotato di tastiera esadecimale e, nella configurazione base, di 2 KB di memoria. Poteva essere collegato a un televisore mediante un modulatore RF e utilizzava registratori a cassette come meoria di massa. L’interprete implementato da Weisbecker occupava solamente 512 byte di memoria e molti dei giochi originariamente sviluppati per questo sistema furono realizzati da Joyce Weisbecker, figlia di Joseph. La newsletter amatoriale VIPER, pubblicata tra il 1978 e il 1984, divenne il punto di riferimento per gli utenti COSMAC VIP e gli appassionati di CHIP-8.

Fotografia raffigurante il sistema COSMAC VIP. BYTE Magazine, Agosto 1977.
Fotografia raffigurante il sistema COSMAC VIP. BYTE Magazine, Agosto 1977.

Il linguaggio CHIP-8 fu utilizzato anche su altri sistemi basati sul CDP 1802, tra i quali si ricordano Telmac 1800 ed ETI-660, ma non fu un’esclusiva di questa architettura; nel 1979, infatti, una versione dell’interprete fu sviluppata per il computer DREAM 6800, simile al COSMAC VIP ma con microprocessore Motorola 6800. Di pari passo con lo sviluppo di moduli di espansione per il COSMAC VIP e con la trasposizione dell’interprete su macchine differenti, il linguaggio è stato arricchito con varie estensioni, in modo da sfruttare le caratteristiche del nuovo hardware.

Nel 1990, il linguaggio ritornò in auge grazie ad Andreas Gustafsson, che sviluppò CHIP-48, un’implementazione dell’interprete per la calcolatrice scientifica HP 48SX. In seguito, Erik Bryntse modificò CHIP-48, aggiungendo nuove funzionalità, tra cui la modalità grafica ad alta risoluzione e le istruzioni per lo scroll, sia orizzontale sia verticale, dello schermo. Questa versione estesa è nota come SUPER-CHIP o S-CHIP. Per rendere più agevole la programmazione, Hans Christian Egeberg realizzò Chipper, uno psudo-assemblatore CHIP-8. Nel 2007, Martijn Wenting (Revival Studios) realizzò una nuova estensione, chiamata MegaChip8, che offriva nuove istruzioni per modalità video a maggiore risoluzione, grafica a colori e suono digitalizzato. Nel 2014, John Earnest introdusse Octo, un ambiente di sviluppo completo, basato su di un assemblatore a più alto livello rispetto a Chipper e fornito di strumenti utili per il test, il debug e la condivisione dei programmi realizzati. Octo supporta XO-Chip, un insieme di nuove istruzioni che estendono le potenzialità grafiche e sonore della macchina virtuale, rendono più flessibile l’accesso alla memoria e consentono di indirizzare fino a 64 KB.

Nel corso degli anni sono state implementate versioni dell’interprete per la quasi totalità dei sistemi esistenti. Anche il sottoscritto si è cimentato nella realizzazione di un interprete per Sinclair ZX Spectrum, ZX Spectrum Next e Cambridge Z88, chiamato CHIP-OTTO, corredandolo con alcuni programmi. Successivamente, CHIP-OTTO è stato reimplementato in Javascript/HTML5 e reso fruibile tramite browser web.

La macchina virtuale

L’interprete CHIP-8 implementa una macchina virtuale, che esegue il programma un’istruzione alla volta e aggiorna il proprio stato di conseguenza. Questa sezione descrive le specifiche della macchina virtuale CHIP-8.

Memoria

Il linguaggio CHIP-8 può indirizzare 4 KB di memoria RAM, dalla locazione 0x000 alla locazione 0xFFF. L’interprete originale occupava i primi 512 byte di memoria, per cui i programmi solitamente iniziano all’indirizzo 0x200. Altre implementazioni possono prevedere indirizzi differenti; ad esempio, i programmi per ETI-660 iniziano al byte 0x600. Negli interpreti moderni, l’area di memoria al di sotto dell’indirizzo 0x200 è solitamente utilizzata per memorizzare gli sprite che rappresentano le cifre esadecimali.

Registri

CHIP-8 ha 16 registri generici a 8 bit, solitamente identificati con Vx, dove x è una cifra esadecimale. VF è utilizzato come registro di stato e, a seconda dell’istruzione che lo valorizza, può rappresentare il flag di riporto o indicare una collisione tra sprite.

È anche presente un registro a 16 bit, chiamato I, generalmente utilizzato come registro di indirizzo. Lo spazio di indirizzamento è di 4096 byte, per cui solo i 12 bit meno significativi sono effettivamente utilizzati.

Sono inoltre presenti due registri specifici a 8 bit, utilizzati come timer, il cui valore è decrementato di 1 con una frequenza di 60 Hz finché non raggiunge 0:

  • DT (Delay Timer): utilizzato per la sincronizzazione;
  • ST (Sound Timer): se maggiore di zero, l’altoparlante emetterà un suono a una tonalità prestabilita.

Il linguaggio prevede uno stack, utilizzato per memorizzare gli indirizzi di ritorno delle subroutine, per cui l’interprete dovrà mantenere uno Stack Pointer (SP), tipicamente a 8 bit, che punta al top dello stack.

Infine, per memorizzare l’indirizzo dell’instruzione corrente, l’interprete potrebbe utilizzare un Program Counter (PC) a 16 bit.

Tastiera

L’input avviene mediante una tastiera esadecimale, in cui i tasti hanno la seguente disposizione:

123C
456D
789E
A0BF

Sugli interpreti per computer attuali con tastiera QWERTY, la disposizione è solitamente mappata sui tasti:

1234
QWER
ASDF
ZXCV

Grafica

L’implementazione originale dell’interprete CHIP-8 supporta una risoluzione di 64×32 pixel monocromatici; l’origine è in alto a sinistra.

Organizzazione dello schermo.
BYTE Magazine, Dicembre 1978.
Organizzazione dello schermo. Le coordinate sono espresse in esadecimale.
BYTE Magazine, Dicembre 1978.

L’estensione SUPER-CHIP introduce una modalità con risoluzione di 128×64 pixel, mentre MegaChip8 supporta fino a 265×192 pixel a colori con tavolozza a 8 bit.

Il contenuto è visualizzato sullo schermo mediante sprite con larghezza di 8 pixel e con altezza variabile fino a 15 pixel. Quando uno sprite viene visualizzato sullo schermo, viene effettuata l’operazione di XOR tra i byte che definiscono il pattern dello sprite e il contenuto della memoria video nelle posizioni corrispondenti. Se quindi si sta accendendo un pixel già acceso, il pixel sarà spento; inoltre il registro VF sarà valorizzato a 1. Questo comportamento è utilizzato per il rilevamento delle collisioni.

L’interprete integra un insieme di 16 sprite predefiniti di 5 pixel di altezza, che rappresentano le cifre esadecimali da 0x0 a 0xF.

Suono

L’altoparlante può emettere un suono a una tonalità fissa. La durata dipende dal valore del registro ST.

Il linguaggio

La specifica originale del linguaggio prevede 35 istruzioni, che includono operazioni matematiche e bit a bit, grafica, controllo di flusso. Tutte le istruzioni sono lunghe 2 byte e sono memorizzate in formato big endian. È stato scelto un formato a lunghezza fissa per rendere più semplice la scrittura dei programmi e facilitare la sostituzione di istruzioni in fase di debug.

Nella descrizione delle istruzioni, si applicano le seguenti convenzioni:

  • nnn: rappresenta un valore o un indirizzo di memoria a 12 bit;
  • nn: byte meno significativo dell’istruzione, rappresenta un valore a 8 bit;
  • n: gruppo di 4 bit (nibble) meno significativi dell’istruzione, rappresenta un valore a 4 bit;
  • x: nibble meno significativo del byte più significativo dell’istruzione, utilizzato per identificare un registro;
  • y: nibble più significativo del byte meno significativo dell’istruzione, utilizzato per identificare un registro.

Set di istruzioni CHIP-8

La seguente tabella elenca le istruzioni CHIP-8 e ne descrive il funzionamento. Oltre ai codici operativi esadecimali, utilizzati per l’inserimento delle istruzioni sul COSMAC VIP, sono riportati i codici mnemonici utilizzati dall’assemblatore Chipper.

I codici operativi 8xy3, 8xy6, 8xy7 e 8xyE non erano documentati nella specifica originale del linguaggio. Si noti che in alcune implementazioni il comportamento delle istruzioni 8xy6, 8xyE, Fx1E, Fx55 ed Fx65 è differente rispetto all’interprete originale.

Codice operativoCodice mnemonicoSpiegazione
0nnnSYS nnnRichiama la routine in linguaggio macchina all’indirizzo nnn.
Ignorata dagli interpreti moderni.
00E0CLSCancella lo schermo.
00EERETRitorna da una subroutine.
1nnnJP nnnSalta all’indirizzo nnn.
2nnnCALL nnnRichiama la subroutine all’indirizzo nnn.
3xnnSE Vx, nnSalta la prossima istruzione se Vx è uguale a nn.
4xnnSNE Vx, nnSalta la prossima istruzione se Vx è diverso da nn.
5xy0SE Vx, VySalta la prossima istruzione se Vx è uguale a Vy.
6xnnLD Vx, nnCarica nel registro Vx il valore nn.
7xnnADD Vx, nnSomma nn al valore del registro Vx e memorizza il risultato in Vx.
8xy0LD Vx, VyMemorizza il valore del registro Vy nel registro Vx.
8xy1OR Vx, VyEsegue l’OR bit a bit dei valori in Vx e Vy e memorizza il risultato in Vx.
8xy2AND Vx, VyEsegue l’AND bit a bit dei valori in Vx e Vy e memorizza il risultato in Vx.
8xy3XOR Vx, VyEsegue OR esclusivo bit a bit dei valori in Vx e Vy e memorizza il risultato in Vx.
8xy4ADD Vx, VySomma i valori in Vx e Vy e memorizza il risultato in Vx. In caso di riporto, VF sarà valorizzato a 1, altrimenti a 0.
8xy5SUB Vx, VySottrae Vy da Vx e memorizza il risultato in Vx. In caso di prestito, VF sarà valorizzato a 0, altrimenti a 1.
8xy6SHR Vx {, Vy}Memorizza il bit meno significativo di Vy in VF ed esegue lo shift del contenuto di Vy a destra di una posizione. Il risultato è salvato in Vx e Vy resta inalterato.
Nelle implementazioni CHIP-48, SUPER-CHIP e derivate, l’oggetto dell’operazione di shift è Vx.
8xy7SUBN Vx, VySottrae Vx da Vy e memorizza il risultato in Vx. In caso di prestito, VF sarà valorizzato a 0, altrimenti a 1.
8xyESHL Vx {, Vy}Se il bit più significativo di Vx è 1, valorizza VF a 1, altrimenti a 0. Esegue lo shift a sinistra del contenuto di Vy di una posizione. Il risultato è salvato in Vx e Vy resta inalterato.
Nelle implementazioni CHIP-48, SUPER-CHIP e derivate, l’oggetto dell’operazione di shift è Vx.
9xy0SNE Vx, VySalta la prossima istruzione se Vx è diverso da Vy.
AnnnLD I, nnnAssegna a I il valore nnn.
BnnnJP V0, nnnSalta alla locazione V0 + nnn.
CxnnRND Vx, nnEsegue l’AND bit a bit tra nn e un numero pseudocasuale (tipicamente nell’intervallo [0, 255]) e memorizza il risultato in Vx.
DxynDRW Vx, Vy, nDisegna lo sprite di dimensioni 8xn definito nelle locazioni di memoria [I, I+n-1] alle coordinate(Vx, Vy).
In caso di collisione, VF sarà valorizzato a 1, altrimenti a 0.
Ex9ESKP VxSalta la prossima istruzione se il tasto associato al valore di Vx è premuto.
ExA1SKNP VxSalta la prossima istruzione se il tasto associato al valore di Vx non è premuto.
Fx07LD Vx, DTAssegna a Vx il valore del timer DT.
Fx0ALD Vx, KAttende la pressione di un tasto e memorizza il valore associato al tasto premuto in Vx.
Fx15LD DT, VXAssegna a DT il valore del registro Vx.
Fx18LD ST, VxAssegna al timer sonoro il valore di Vx.
Fx1EADD I, VxSomma i valori di I e Vx e memorizza il risultato in I. Normalmente, VF non è alterato; tuttavia nell’implementazione dell’interprete per Amiga, VF è impostato a 1 in caso di overflow (I+VX>0xFFF) e a 0 altrimenti. Il gioco Spacefight 2091! richiede questo comportamento.
Fx29LD F, VxImposta I all’indirizzo della locazione iniziale dello sprite raffigurante la cifra esadecimale corrispondente al valore Vx.
Fx33LD B, VxMemorizza la rappresentazione BCD del valore di Vx nelle locazioni di memoria I, I+1 e I+2.
Sostanzialmente, la cifra delle centinaia sarà memorizzata nella locazione I, la cifra delle decine in I+1 e quella delle unità in I+2.
Fx55LD [I], VxCopia i valori dei registri da V0 a Vx in memoria, a partire dalla locazione specificata dall’indirizzo I.
Nell’implementazione originale, al termine dell’operazione I sarà valorizzato a I + X + 1; in SUPER-CHIP e implementazioni derivate, il valore di I rimane inalterato.
Fx65LD Vx, [I]Copia gli x+1 valori in memoria a partire dalla locazione specificata dall’indirizzo I nei registri da V0 a Vx.
Nell’implementazione originale, al termine dell’operazione I sarà valorizzato a I + X + 1; in SUPER-CHIP e implementazioni derivate, il valore di I rimane inalterato.

Istruzioni SUPER-CHIP

L’estensione SUPER-CHIP definisce nuove istruzioni per gestire la modalità grafica ad alta risoluzione (128×64 pixel), effettuare lo scroll orizzontale e verticale dello schermo ed interagire col sistema host. L’interprete include 10 nuovi sprite, alti 10 pixel, per rappresentare le cifre decimali.

Codice operativoCodice mnemonicoSpiegazione
00CnSCD nEsegue lo scroll dello schermo di n pixel in basso.
00FBSCREsegue lo scroll dello schermo di 4 pixel a destra.
00FCSCLEsegue lo scroll dello schermo di 4 pixel a sinistra.
00FDEXITEsce dall’interprete CHIP-8.
00FELOWDisabilita la modalità estesa ad alta risoluzione.
00FFHIGHAbilita la modalità estesa ad alta risoluzione.
In questa modalità, aumenta anche la velocità di esecuzione dell’interprete.
Dxy0DRW Vx, Vy, 0Estensione dell’istruzione CHIP-8 Dxyn.
Se in modalità estesa e se n = 0, disegna lo sprite di dimensioni 16×16 definito in memoria a partire dall’indirizzo I alle coordinate (Vx, Vy).
Fx30LD HF, VxImposta I all’indirizzo della locazione iniziale dello sprite, di dimensioni 8×10, raffigurante la cifra decimale corrispondente al valore Vx.
Fx75LD R, VxMemorizza i valori dei registri da V0 a Vx (con x <= 7) negli RPL user flags.
Fx85LD Vx, RLegge i valori dei registri da V0 a Vx (con x <= 7) dagli RPL user flags.

Gli RPL user flags sono un’area di memoria di 64 bit delle calcolatrici programmabili HP 48, a disposizione del programma utente. Si ricorda, infatti, che CHIP-48 e SUPER-CHIP furono sviluppati per la calcolatrice HP 48SX.

Preparazione dell’ambiente di sviluppo

Un metodo pratico per chi oggi vuole programmare in CHIP-8 è scrivere il codice sorgente con un text editor, assemblarlo utilizzando Chipper e infine eseguirlo in un interprete, tutto su di un moderno personal computer. In questa sezione, si assume che il sistema operativo utilizzato sia Windows; tuttavia, gli strumenti di sviluppo per altre piattaforme sono ampiamente disponibili e facilmente reperibili mediante una semplice ricerca sul web.

Fish ‘N’ Chips è un ottimo interprete, in grado di emulare la CPU RCA 1802 e quindi di eseguire i programmi esattamente come sul COSMAC VIP. L’ultima versione nota è la 2.1.5, del 2008. Esistono inoltre interpreti che offrono funzionalità utili per il debug, come la possibilità di ispezionare e alterare il contenuto della memoria e dei registri o l’esecuzione di un’istruzione per volta.

L’assemblatore Chipper è stato sviluppato in C, per cui è estremamente portabile. Inoltre, è stato anche tradotto in altri linguaggi. Una versione già compilata ed immediatamente utilizzabile è quella contenuta nel MegaChip8 Devkit di Revival Studios. Questa versione supporta sia il linguaggio originale sia le estensioni SUPER-CHIP e MegaChip8 e include un esempio di gioco minimale, che può essere utilizzato come punto di partenza.

E’ possibile automatizzare build ed esecuzione del programma che si sta sviluppando creando un semplice file batch, sulla falsariga del file build.bat incluso nel MegaChip8 Devkit.

Il comando per assemblare il programma è:

mchipper program.ch8 program.src

dove program.src è il nome del file di testo contenente il codice sorgente e program.ch8 è il nome del file binario di output che conterrà il programma CHIP-8 assemblato.

Per eseguire, è sufficiente passare il file binario come argomento all’eseguibile di Fish ‘N’ Chips:

"Fish 'N' Chips.exe" program.ch8

Fish ‘N’ Chips supporta anche il drag and drop dei file CHIP-8 sulla finestra principale dell’interprete o sul file eseguibile.

Per maggiori dettagli, si rimanda alla documentazione dei succitati programmi.

Il primo programma CHIP-8

Un primo esempio per approcciare la programmazione CHIP-8 potrebbe essere un semplice programma che disegna uno sprite e lo muove sullo schermo in base all’input ricevuto dall’utente.

Utilizzo dei registri

Per questo scopo, sono sufficienti tre registri della macchina virtuale CHIP-8: due per memorizzare le coordinate x e y dello sprite (V0 e V1) e uno per registrare il tasto premuto dall’utente (V2). È buona norma documentare l’utilizzo dei registri nel file contenente il codice sorgente, mediante i commenti. I commenti sono preceduti da un carattere ‘;‘. Chipper ignorerà tutto il contenuto dopo il carattere ‘;‘ fino alla fine della linea in cui compare.

; Utilizzo registri:
; V0:	coordinata x dello smile
; V1:	coordinata y dello smile
; V2:	tasto eventualmente premuto

Direttive di Chipper

Prima dell’inizio del programma vero e proprio, è opportuno impostare il formato binario del file di output mediante la direttiva OPTION BINARY. Il formato binario è quello comunemente utilizzato negli interpreti moderni; altri formati sono indicati per sistemi specifici quali la calcolatrice HP 48SX.

OPTION BINARY ; Direttiva Chipper per impostare il formato binario 
              ; per l'output.

Un’altra direttiva comunemente utilizzata è ALIGN OFF. Normalmente, Chipper allinea ciascuna riga a una word, o 2 byte. Se si specificano i valori di alcuni byte mediante la direttiva DB, questo comportamento causerebbe l’inserimento di byte non previsti per garantire l’allineamento, per cui è opportuno disabilitarlo.

ALIGN OFF     ; Direttiva Chipper per disabilitare l'allineamento
              ; a word di ciscuna linea.

Definizione e animazione dello sprite

Lo sprite utilizzato sarà uno smile di dimensioni 8×8.

Smile sprite
Smile sprite

Il pattern è specificato mediante una sequenza di 8 direttive DB, che definiscono, per ciascuna riga dello sprite, quali pixel devono essere accesi e quali spenti. Il carattere ‘$‘ permette di specificare un valore numerico in formato binario (‘.’ è sinonimo di ‘0’).

SMILE_SPRITE:	; Label che identifica l'indirizzo a partire dal quale
		; sono memorizzati i dati dell'immagine dello sprite.
  ; Essendo la dimensione dello smile 8x8, lo sprite è definito da 8 byte.
  ; DB definisce il valore di un byte all'indirizzo corrente. '$' permette
  ; di sepcificare un valore in binario; il punto '.' è sinonimo di '0'.
  DB $..1111..
  DB $.111111.
  DB $11.11.11
  DB $11.11.11
  DB $11111111
  DB $11.11.11
  DB $.11..11.
  DB $..1111..

La label “SMILE_SPRITE” consente di avere un riferimento alla locazione di memoria a partire dalla quale è memorizzato il pattern dello sprite.

Lo sprite è inizialmente disegnato mediante l’istruzione DRW, dopo aver caricato in I l’indirizzo corrispondente alla label “SMILE_SPRITE”. Queste istruzioni saranno eseguite ogni volta che lo sprite deve essere cancellato e ridisegnato nella nuova posizione, pertanto possono essere accorpate in una subroutine, chiamata “DRAW_SMILE”:

DRAW_SMILE:

  LD I, SMILE_SPRITE  ; Imposto I all'indirizzo di memoria a partire dal quale
	              ; sono memorizzati i dati dell'immagine dello sprite.
		      ; Tale indirizzo è specificato dalla label "SMILE_SPRITE".
  DRW V0, V1, 8       ; Disegna (o nascondi, se già disegnato) lo sprite
		      ; (con altezza 8) alle coordinate V0, V1.
  RET		      ; Esce dalla subroutine e ritorna al chiamante.

L’input

Nella maggior parte dei giochi CHIP-8, i tasti ‘2’, ‘8’, ‘4’ e ‘6’ sono utilizzati per impostare la direzione del movimento del giocatore rispettivamente verso l’alto, il basso, sinistra e destra. In questo esempio si adotterà lo stesso schema.

Per modificare la direzione in base al tasto premuto, dapprima si caricherà in V2 il valore del tasto da controllare. Successivamente, si utilizzerà l’istruzione SKNP V2 per verificare se il tasto è attualmente premuto; in caso affermativo, sarà eseguita l’istruzione seguente, che conterrà l’incremento o decremento della posizione x o y dello sprite. Se, invece, il tasto non è premuto, l’istruzione successiva sarà saltata e il programma proseguirà senza aggiornare la posizione dello sprite.

Ad esempio, se è premuto il tasto ‘2’, lo sprite sarà spostato verso l’alto di una posizione, decrementando di 1 il valore di V1. Il decremento è effettuato sommando -1, che è rappresentato dal valore 255 (esadecimale 0xFF) in complemento a 2. In Chipper, i valori esadecimali sono espressi anteponendo un carattere ‘#‘.

	        ; Se è premuto il tasto 2, muovo lo sprite verso l'alto.
  LD V2, 2	; Assegno il valore 2 a V2.
  SKNP V2	; Se il tasto corrispondente a V2 non è premuto, 
		; salto la prossima istruzione.
  ADD V1, #FF	; Se sono qui, significa che il tasto corrispondente a V2 
		; è premuto. Decremento di 1 la coordinata y dello smile.

Si ricorda che su una normale tastiera QWERTY, i tasti corrispondenti ai tasti CHIP-8: ‘2’, ‘8’, ‘4’ e ‘6’ sono rispettivamente: ‘2’, ‘S’, ‘Q’, ‘E’.

Il loop principale

La struttura principale di ciascun videogioco è il cosiddetto game loop, vale a dire un ciclo che controlla il flusso del gioco ripetendo continuamente la stessa sequenza di azioni, fino alla fine della partita. Le azioni, tipicamente, includono: controllo dell’input del giocatore, movimento dei nemici, rilevamento delle collisioni, visualizzazione della situazione aggiornata.

In questo esempio, si utilizzerà un semplice loop, così strutturato:

  • Richiama la subroutine DRAW_SMILE per nascondere lo sprite, che si trova alle coordinate V0, V1;

  • Se è premuto ‘2’ (alto):

    • Decrementa V1 di 1;

  • Se è premuto ‘8’ (basso):

    • Incrementa V1 di 1;

  • Se è premuto ‘4’ (sinistra):

    • Decrementa V0 di 1;

  • Se è premuto ‘6’ (destra):

    • Incrementa V0 di 1;

  • Richiama la subroutine DRAW_SMILE per ridisegnare lo sprite alle coordinate V0, V1;

  • Salta all’inizio del loop.

Il programma completo

È di seguito riportato il codice sorgente completo del programma di esempio, che può essere utilizzato come punto di partenza per sviluppare un videogioco vero e proprio.

; Movimento di un semplice sprite a forma di smile.

; Utilizzo registri:
; V0:	coordinata x dello smile
; V1:	coordinata y dello smile
; V2:	tasto eventualmente premuto

OPTION BINARY ; Direttiva Chipper per impostare il formato binario
              ; per l'output.
ALIGN OFF     ; Direttiva Chipper per disabilitare l'allineamento
              ; a word di ciscuna linea.
	
;******************************************
; Inizio del programma
;******************************************

  CLS		   ; Pulisco lo schermo.
	
		   ; Inizializzo le coordinate dello sprite.
  LD V0, 0	   ; Inizializzo la coordinata x a 0.
  LD V1, 0	   ; Inizializzo la coordinata y a 0.
	
  CALL DRAW_SMILE  ; Richiamo la routine per visualizzare lo sprite 
		   ; alle coordinate V0, V1.
	
LOOP:		   ; Label che identifica l'inizio del loop.
	
  CALL DRAW_SMILE  ; Richiamo nuovamente la routine per visualizzare lo sprite.
	           ; Essendo lo srite già visualizzato alle coordinate V0, V1,
		   ; l'effetto sarà quello di nascondere lo sprite.

		   ; Se è premuto il tasto 2, muovo lo sprite verso l'alto.
  LD V2, 2	   ; Assegno il valore 2 a V2.
  SKNP V2	   ; Se il tasto corrispondente a V2 non è premuto,
    		   ; salto la prossima istruzione.
  ADD V1, #FF	   ; Se sono qui, significa che il tasto corrispondente a V2
		   ; è premuto. Decremento di 1 la coordinata y dello smile.

		   ; Se è premuto il tasto 8, muovo lo sprite verso il basso.
  LD V2, 8	   ; Assegno il valore 8 a V2.
  SKNP V2	   ; Se il tasto corrispondente a V2 non è premuto, 
		   ; salto la prossima istruzione.
  ADD V1, 1	   ; Se sono qui, significa che il tasto corrispondente a V2
		   ; è premuto. Incremento di 1 la coordinata y dello smile.
	
		   ; Se è premuto il tasto 4, muovo lo sprite verso sinistra.
  LD V2, 4	   ; Assegno il valore 4 a V2.
  SKNP V2	   ; Se il tasto corrispondente a V2 non è premuto,
		   ; salto la prossima istruzione.
  ADD V0, #FF	   ; Se sono qui, significa che il tasto corrispondente a V2
		   ; è premuto. Decremento di 1 coordinata x dello smile.
	
		   ; Se è premuto il tasto 6, muovo lo sprite verso destra.
  LD V2, 6	   ; Assegno il valore 6 a V2.
  SKNP V2	   ; Se il tasto corrispondente a V2 non è premuto,
		   ; salto la prossima istruzione.
  ADD V0, 1	   ; Se sono qui, significa che il tasto corrispondente a V2
    		   ; è premuto. Incremento di 1 coordinata x dello smile.
	
  CALL DRAW_SMILE  ; Richiamo nuovamente la routine per visualizzare lo sprite.
		   ; Essendo lo srite attualmente non visualizzato,
		   ; l'effetto sarà quello di visualizzare lo sprite.
  
  JP LOOP	   ; Salto all'inizio del loop, identificato dalla label "LOOP".

;******************************************
; Routine per disegnare/cancellare lo smile
;******************************************

DRAW_SMILE:

  LD I, SMILE_SPRITE ; Imposto I all'indirizzo di memoria a partire dal quale sono
	             ; memorizzati i dati dell'immagine dello sprite.
		     ; Tale indirizzo è specificato dalla label "SMILE_SPRITE".
  DRW V0, V1, 8	     ; Disegno (o nascondo, se già disegnato) lo sprite
	             ; (con altezza 8) alle coordinate V0, V1.
  RET		     ; Esco dalla subroutine e ritorna al chiamante.
	
;******************************************
; Dati dello sprite dello smile
;******************************************
	
SMILE_SPRITE:   ; Label che identifica l'indirizzo a partire dal quale sono 
	        ; memorizzati i dati dell'immagine dello sprite.
  ; Essendo la dimensione dello smile 8x8, lo sprite è definito da 8 byte.
  ; DB definisce il valore di un byte all'indirizzo corrente. '$' permette
  ; di sepcificare un valore in binario; il punto '.' è sinonimo di '0'.
  DB $..1111..
  DB $.111111.
  DB $11.11.11
  DB $11.11.11
  DB $11111111
  DB $11.11.11
  DB $.11..11.
  DB $..1111..
Il programma in esecuzione su Fish ‘N’ Chips.

Conclusioni

In questo articolo è stata presentata una panoramica sul linguaggio CHIP-8, nato per facilitare la programmazione di videogiochi sui computer per hobbisti degli anni ’70, tornato in voga sulle calcolatrici scientifiche degli anni ’90 e ancora oggetto di interesse per molti appassionati di retrocomputing e retroprogrammazione. È stata fornita una descrizione esauriente delle istruzioni del linguaggio e dell’estensione SUPER-CHIP e sono state presentate le caratteristiche dell’interprete. Sono infine stati descritti alcuni strumenti per programmare giochi in CHIP-8 ed eseguirli sugli attuali personal computer, fornendo un semplice esempio pratico.

Nel prossimo articolo, sarà trattata l’implementazione in CHIP-8 di una variante del classico videogioco Snake, simile alla popolare versione presente in alcuni modelli di telefono cellulare degli anni ’90.

Bibliografia e riferimenti

Joseph Weisbecker, “An Easy Programming System”. BYTE magazine, December 1978, pp. 108–122. https://archive.org/details/byte-magazine-1978-12-rescan

Joseph Weisbecker, “COSMAC VIP, the RCA Fun Machine”. BYTE magazine, August 1977, p. 30. https://archive.org/details/byte-magazine-1977-08

CHIP-8 Wikipedia entry. https://en.wikipedia.org/wiki/CHIP-8 COSMAC VIP Wikipedia entry. https://en.wikipedia.org/wiki/COSMAC_VIP RCA 1802 Wikipedia entry. https://en.wikipedia.org/wiki/RCA_1802 Joseph Weisbecker Wikipedia entry. https://en.wikipedia.org/wiki/Joseph_Weisbecker

COSMAC ELF – The CDP1802’s Place in Microcomputing History. http://cosmacelf.com/

Michael J Bauer, the official DREAM 6800 Archive Site. http://www.mjbauer.biz/DREAM6800.htm

Andreas Gustafsson’s home page. http://www.gson.org/

Erik Bryntse su hpcalc.org. https://www.hpcalc.org/authors/312

Hans Christian Egeberg su hpcalc.org. https://www.hpcalc.org/authors/374

Revival Studios – Classic Indie Retro Game Development. http://www.revival-studios.com/

Revival Studios – MegaChip8 Devkit. http://www.revival-studios.com/downloads/chip8/RS-M8002%20-%20MegaChip%20Devkit%201.0b.zip

John Earnest, Octo – A Chip8 IDE. https://github.com/JohnEarnest/Octo

Thomas P. Greene, “Cowgod’s Chip-8 Technical Reference”. http://devernay.free.fr/hacks/chip8/C8TECH10.HTM

Matthew Mikolay, CHIP-8 wiki. https://github.com/mattmikolay/chip-8/wiki

Eduardo M Kalinowski, “Programming in User RPL”. https://www.hpcalc.org/details/1771

hap, Fish ‘N’ Chips 2.1.5: emulatore RCA COSMAC VIP e interprete CHIP-8/SCHIP per Microsoft Windows. www.emu-france.com/?wpfb_dl=2539

Marco Varesio, CHIP-OTTO: una macchina virtuale CHIP-8 per ZX Spectrum (Next) e Cambridge Z88. https://somebitsofme.altervista.org/blog/chip-otto/

Marco Varesio, WEB-OTTO: una macchina virtuale CHIP-8 per browser web (HTML5). https://somebitsofme.altervista.org/misc/chip8/webotto/


Attribuzione - Non commerciale - Condividi allo stesso modo 4.0 Internazionale (CC BY-NC-SA 4.0)
Licenza CC BY-NC-SA 4.0

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.