Articolo pubblicato sul n. 57 di MCmicrocomputer (Edizioni Technimedia Srl - Roma) nel novembre 1986

MCmicrocomputer


Commodore 128:
Banking da linguaggio macchina

di Andrea de Prisco

Dopo aver visto il mese scorso come il Commodore 128 mette a disposizione tutta la sua memoria (ram e rom) al livello del linguaggio di programmazione Basic, questo mese ci addentreremo maggiormente nel merito mostrando come siano possibili analoghi "smanettamenti" anche da linguaggio macchina.  Copertina del numero di MCmicrocomputer contenente l'articolo

 

Il Monitor

 

Per programmare in linguaggio macchina occorre un buon monitor: ovviamente i fosfori in questo caso non c'entrano proprio nulla. Ci serve un monitor per il linguaggio macchina, che stavolta mamma Commodore s'è ricordata di infilare dentro alle rom di sistema, come aveva già fatto tanti anni fa col dottor Pet. Un monitor di linguaggio macchina è dunque uno strumento indispensabile per poter manipolare facilmente le celle di memoria o per essere più precisi i contenuti di queste. Il monitor del 128 fa anche qualcosa in più: assembla e disassembla codici mnemonici e porzioni di memoria, converte con estrema facilità numeri da una base a un'altra non senza permettere una ragionevole interazione tra computer e la più importante delle periferiche, l'unità a dischi, almeno per quanto riguarda directory e comandi DOS.

  Tutto ciò è comunque ben illustrato sul manuale fornito con la macchina al quale naturalmente vi rimandiamo qualora non aveste mai usato questa feature.

 

Il registro CR della MMU

 

Ogni manipolazione della configurazione della memoria coinvolge sempre la MMU del 128 il cui acronimo sta appunto per memory management unit. Per dialogare con questa unità, come avviene anche per gli altri processori interni al 128, si usano dei registri mappati in alcune celle di memoria.

 Noi faremo riferimento, per tutto quest'articolo, al registro CR (già nominato il mese scorso) locato all'indirizzo esadecimale $FF00. Sempre come già detto, tale registro è –ovviamente– accessibile da qualsiasi configurazione di memoria dato che deve essere sempre possibile passare da un banco ad un altro: se in qualche banco questo non fosse accessibile, una volta selezionato quel banco non si potrebbe più "venir via" non potendo dialogare con la MMU.

  Da ciò si evince che per passare da un banco ad un altro, da linguaggio macchina basta infilare qualcosa nel registro CR per ottenere il voluto. Quasi.

Il problema infatti è un po' più complesso: infatti anche il flusso di controllo del programma in corso verrebbe catapultato indesideratamente sul banco desiderato. Facciamo un esempio: immaginiamo di aver scritto un programma in linguaggio macchina nel banco 0 della memoria. Sempre per ipotesi poniamo il caso in cui a un certo punto ci serve la il contenuto della locazione 4000 del banco 1, interamente riempito di dati (ovvero non-programmi). La situazione è mostrata in figura 1: dopo qualche operazione si pone $7F nel registro CR per cambiare banco prima di prelevare col LDA la locazione 4000. Abbiamo combinato un bel pasticcio: infatti i cambiamenti di banco non riguardano solo gli accessi ai dati da parte delle istruzioni in linguaggio macchina ma quanto effettivamente il processore preleva per eseguire. Nella fattispecie, l'aver eseguito la sequenza:

 

LDX #$7F

STX $FF00

corrisponde in pratica (solo quelle due istruzioni) ad aver effettuato un salto, un JMP, all'altro banco di memoria, con le catastrofiche conseguenze che possiamo supporre: sicuramente il blocco del sistema fino a nuovo Reset. Questo perché siamo saltati in mezzo ai dati (leggi: numeri a casaccio per il processore) e, si sa, non tutti i numeri compresi tra 0 e 255 sono codici operativi di istruzioni di macchina.

Considerato poi che la situazione di cui sopra è tutt'altro che irreale, se non ci fosse una soluzione sarebbe davvero un bel casotto. Fortunatamente di soluzioni ce ne sono due, una hardware e l'altra software.

Spegnete pure il saldatore, non dobbiamo fare nessuna modifica è tutto compreso nel prezzo. Hardware nel senso che ci riferiremo al modo come è stato costruito il 128 e Software rifacendoci ad opportune routine di sistema operativo. In ogni caso farina di mamma Commodore.

 

Prima soluzione

 

Alla base di tutti i cambiamenti di banco, oltre al registro CR esiste un altro componente altrettanto importante: il primo K di memoria RAM comune sia a RAM 0 che a RAM 1: le due pagine di memoria disponibili sul 128. Ciò vuol dire essenzialmente che quanto mostrato in figura 1, se l'avessimo effettuato nelle prime 1024 locazioni di memoria non avrebbe provocato nulla di paranormale.

La figura 2 mostra come stanno i fatti: si nota come la cosiddetta Common RAM sia indipendente dal banco selezionato. Allocando le nostre routine nel primo k di memoria (figura 3), non si hanno di tali problemi: di fatto la Commodore stessa usa questa zona per effettuare le sue interazioni tra pagine, a livello di sistema operativo e/o basic 7.0.

L'unico problema è dato dal fatto che in questo primo k non troviamo molto spazio libero per i nostri programmi essendo quasi interamente occupato da variabili e sottoprogrammi di sistema operativo. In altre parole, se ci serve qualche buco qua e là otteniamo facilmente il nostro scopo, ma se servono grosse zone di memoria non possiamo non ricorrere al metodo software che andiamo subito ad illustrare.

 

Soluzione software

 

Lasciamo dunque il nostro primo K di memoria e scriviamo comodamente il nostro programma in linguaggio macchina dove ci pare (o quasi). Per interagire con gli altri banchi possiamo usare alcune routine di sistema operativo atte allo scopo.

  La tabella 1 riassume tali routine che, come vedremo, sono tutte piuttosto semplici sia da capire che da usare. A tale scopo ci riferiremo anche ai listati in linguaggio macchina che le descrivono, presenti in queste pagine.

  La prima ruotine serve per trasformare il numero di un banco (0-15) nel corrispondente valore da "pok-are" nel registro CR della MMU. Per usarla è sufficiente mettere in X il banco desiderato ed effettuare un JSR all'indirizzo $F7EC del banco 15. Al ritorno da questa, troveremo nel registro A il valore corrispondente. Dando uno sguardo al Listato 1 possiamo notare quanto sia banale tale trasformazione che non è altro che la lettura di un valore in una tabella posta a partire dall'indirizzo $F7F0.

La seconda routine, mappata nel primo k (guardacaso) a partire dall'indirizzo esadecimale $02CD permette di chiamare una subroutine posta in qualsiasi banco a condizione però che la chiamata avvenga dal banco 15 (a tal proposito fra un po' dirò qualcosa). Prima di utilizzarla è necessario caricare un po' di celle di memoria con il desiderato, nella fattispecie metteremo nella cella $02 il numero del banco desiderato (0-15), nelle due successive l'indirizzo dove è posta la nostra subroutine, nelle celle $05 e seguenti rispettivamente il registro di stato e i registri A-X-Y coi quali desideriamo che venga eseguita la subroutine. Settati tali parametri, possiamo eseguire dal nostro banco (qualsiasi) JSR $02CD per ottenere quanto voluto. I risultati dell'esecuzione della subroutine li ritroveremo nelle celle $05-$09 come mostrato in tabella 1.

 Proviamo ora a commentare quanto succede al momento della chiamata a $02CD, tenendo sott'occhio il listato 2. La prima operazione è una chiamata di subroutine all'indirizzo $02E3 (per la cronaca questo è l'indirizzo della prossima routine che mostreremo). Essenzialmente lì succede che le celle contenenti indirizzo e status vengono infilate nello stack (vedremo tra poco perché); di seguito a questo è effettuata la trasformazione da numero di banco desiderato a corrispondente valore di CR (JSR $FF6B corrisponde a saltare al listato 1, anche se con un passaggio in più) per poi aggiornarlo ovvero switch-are banco; infine sono caricati i registri A-X-Y coi corrispondenti valori immessi precedentemente alla chiamata nelle celle $06-$08.

L'RTI di fondo quasi per magia effettua il salto alla subroutine desiderata, nonostante non vi sia stato alcun interrupt: è solo che alla Commodore sono molto furbi. Infatti l'RTI non fa altro che scaricare dallo stack un primo valore per porlo come status, di seguito a questo preleva i due successivi valori e, intepretati come un indirizzo di 16 bit, salta alla locazione così ottenuta.

Badaben-badaben-badaben che la subroutine a cui siamo diretti prima o poi terminerà con un RTS (se no, che subroutine è?) e la domanda da porci è naturalmente: dopo tutti questi spippolamenti dove mai torneremo?

Semplice, a $02D0 ovvero all'istruzione successiva al JSR $02E3 che abbiamo effettuato qualche centinaio di parole fa. In effetti ancora non è finita: occorre mettere in $06-$08 il contenuto dei registri A-X-Y, in $05 lo status e in $FF00 il valore 0 per ripristinare il banco chimamante, 15.

Apriamo, prima di continuare, una piccola parentesi: per la scrittura di articoli specifici come questi, è d'obbligo una massiccia documentazione in tema prima di cimentarsi in tali "prodezze". Nel caso di 128 da zero, dato che di programmer's references guide della Commodore, fino a questo momento manco a parlarne, il "rifermimento" è un libro edito dalla Abacus Software denominato Tricks and Tips. Peccato che se non si sta veramente attenti a quello che c'è scritto si finisce per confondersi le idee più che senza libri affatto. Tanto per citarne una, la routine appena commentata è spacciata per "chiamata di subroutine da qualsiasi banco in qualsiasi banco" con tanto di cella $09 atta a contenere il banco chiamante... Oppure i 128 americani sono diversi... oppure è diverso il 128 del sottoscritto...

Bene, tornando a tabella 1, la successiva routine permette di saltare a una qualsiasi locazione posta in qualsiasi banco. Inutile dirvi che non commenteremo il listato dato che esso corrisponde alla porzione $02E3-$02FB che abbiamo già mostrato. Passiamo oltre: tocca a INFET, mappata a partire dall'indirizzo $F7D0 (vedi listato 3) che permette di leggere il contenuto di una cella posta in qualsiasi banco. Come al solito tutta la complessità sta nel settare i parametri, tra cui il puntatore al puntatore (!) al byte cercato da porre nel registro A. Ovvero ci scegliamo due celle contigue in pagina zero e posto in esse l'indirizzo voluto, scriviamo in A l'indirizzo della prima di queste due celle. Come è consuetudine del linguaggio macchina, indirizzi a 16 bit vengono spezzati mettendo la parte bassa nella prima delle due celle e la parte alta nella seconda. Caricheremo poi in X il banco desiderato e in Y un eventuale offset per accessi indiciati. Al ritorno da questa routine troveremo in A il byte cercato.

Di fattura assai simile, la successiva routine, mappata all'indirizzo $F7DA permette di scrivere un byte in una cella posta in qualsiasi banco. Metteremo come prima in X il banco desiderato, in Y un eventuale offset mentre in A il Byte da scrivere e nella cella $02B9 (come prima in A) il puntatore al puntatore alla cella. Semplice, no?

Stiamo finendo: l'ultima routine permette di confrontare l'accumulatore col contenuto di una cella posta in qualsiasi banco. Il risultato lo otterremo naturalmente nello status register come per qualsiasi altra comparazione semplice. Come prima in $02C8 va messo il puntatore al puntatore alla cella e in X il banco desiderato.

 

Per chi non sa

 

Brevemente dedicheremo questo riquadro a coloro i quali non conoscono (ancora) il linguaggio macchina del 6502 e gentile famiglia, tra cui il processore del 128 l'8502. Ovviamente non faremo un corso d'assembler neppure alla lontana, ci limiteremo semplicemente a spiegare le istruzioni che il processore è in grado di eseguire.

 LDA, LDX, LDY, STA, STX, STY, servono rispettivamente per caricare un byte in uno dei tre registri dell'8502 (LD sta per load) o per scaricare il contenuto di uno di questi tre registri in una cella di memoria (ST sta per store).

PHP, PHA, PLP, PLA servono per immettere nella o togliere dallo stack il puntatore al top dello stack stesso o il contenuto del registro A. Esistono poi istruzioni per eseguire trasferimenti tra registri come TSX, TXS, TAX, TXA, TAY, TYA che spostano i contenuti dei registri A, X, Y e S detto anche status register.

 A questi di aggiungono operazioni per incrementare un registro (INX, INY) o per il decremento (DEX, DEY) o riferite a una qualsiasi cella di memoria (INC, DEC). è possibile confrontare un registro con un dato (CMP, CPX, CPY, BIT) così come effettuare un salto a seconda di una condizione verificatasi precedentemente la quale ha settato o resettato uno dei bit (C, Z, N, V) dello status register. Tali operazioni di salto condizionato sono: BCC, BCS, BVC, BVS, BNE, BEQ, BMI, BPL.

 A queste aggiungiamo un paio di operazioni per eseguire somma e sottrazioni di byte (ADC, SBC), operazioni logiche come l'and, l'or inclusivo, l'or esclusivo (AND, ORA, EOR), shift a destra e a sinistra di una posizione (ASL, LSR), rotazioni di byte (ROR, ROL), manipolazione del registro di stato (SEC, CLC, SED, CLD, SEI, CLI, CLV), salto incondizionato (JMP), interrupt da programma (BRK), salto a sottoprogramma (JSR) e relativo ritorno (RTS), ritorno da routine di manipolazione delle interruzioni (RTI) e per ultima l'operazione NOP che non fa un bel nulla.

 


Impaginato originale...


  Clicca per ingrandire...
Clicca per ingrandire... Clicca per ingrandire...
Clicca per ingrandire...  

Articolo pubblicato su www.digiTANTO.it - per ulteriori informazioni clicca qui