Partiamo subito analizzando il cuore dell’applicazione, cioè la parte che simula il calcolatore pdp8, ma prima devo ringraziare Walter Valentini per aver trasformato in un package python il programma, cosa che non avevo il tempo di fare (questa è la cosa più bella dei progetti open source, cioè che più persone possono contribuire per migliorare l’applicazione).
Il file del quale sto parlando è pdp8.py, il cuore pulsante dell’emulatore. La classe pdp8 contenuta in questo file, rappresenta il nostro calcolatore didattico.
Codice
def __init__ (self,codice=None): """ Inizializza i registri e le variabili di controllo S,F ed R """ self.PC = '000000000000' self.I = '0' self.OPR = '000' self.E = '0' self.AC = '0000000000000000' self.MAR = '000000000000' self.MBR = '0000000000000000' self.S = False self.F = False self.R = False self.Interrupt = True self.START = '0' self.RAM = {} self.LABEL = {} self.nstep = 1 self.microistruzioni = '' self.inout = '' self.nextistr = '' self.previstr = '' self.Opcodes = { 'CLA':'0111100000000000', 'CLE':'0111010000000000', 'CMA':'0111001000000000', 'CME':'0111000100000000', 'CIR':'0111000010000000', 'CIL':'0111000001000000', 'INC':'0111000000100000', 'SPA':'0111000000010000', 'SNA':'0111000000001000', 'SZA':'0111000000000100', 'SZE':'0111000000000010', 'HLT':'0111000000000001', 'INP':'1111100000000000', 'OUT':'1111010000000000', 'SKI':'1111001000000000', 'SKO':'1111000100000000', 'ION':'1111000010000000', 'IOF':'1111000001000000', 'AND':'000', 'ADD':'001', 'LDA':'010', 'STA':'011', 'BUN':'100', 'BSA':'101', 'ISZ':'110' } if codice is not None: self.carica(codice.lstrip())
Analisi
- Osserviamo per prima cosa come viene inizializzata la macchina. Come ho già spiegato nel precedente articolo, tratto le informazioni contenute all’interno del processore come delle stringhe ed è per questo che tutti i registri sono inizializzati a 0 con una stringa di zeri lunga quanti sono i bit del registro.
- Le variabili di controllo S, F ed R, compresa quella di interruzione (Interrupt) sono trattate come variabili booleane.
- La variabile START serve per stabilire gli indirizzi delle istruzioni all’interno della ram. Di default, il programma viene caricato partendo dalla prima cella (0), altrimenti si inizia dal valore indicato dalla pseudo istruzione ORG, che specifica da quale cella di memoria si deve iniziare a caricare il programma. Il valore di ORG è espresso con un numero esadecimale e non decimale, quindi ORG 10 vuol dire dalla cella 10 HEX, in decimale corrisponderà alla cella 16.
- Il dizionario RAM conterrà le istruzioni caricate (in codice macchina) con i rispettivi indirizzi, mentre LABEL contiene le etichette utilizzate nel programma e la cella di memoria a cui si riferiscono (è necessario durante la conversione per tenere traccia delle celle di memoria utilizzate, sostituendole con il loro indirizzo in binario).
- nstep, è un contatore per tenere conto dei passi eseguiti, così se si vuole eseguire solo un certo numero di cicli, lo si può indicare con questa variabile.
- inout rappresenta la stringa di output di sistema, in poche parole il nostro schermo virtuale, che poi verrà stampato tramite l’interfaccia grafica.
- nextistr e previstr servono per tenere traccià dell’istruzione che verrà eseguita successivamente e di quella corrente. Queste due variabili sono necessarie solo per la parte grafica; sono state aggiunte in un secondo momento quando il core era già completo.
- Opcodes è un dizionario che contiene tutti i codici macchina delle istruzioni disponibili.
- Se alla classe viene passato subito il codice da memorizzare, si richiama il metodo carica che effetuerà il parsing della stringa passata (eliminando gli spazi bianchi presenti all’inizio tramite il metodo lstrip() delle stringhe).
Conclusioni
Come potete vedere, la macchina è abbastanza banale. Successivamente analizzerò tutti i vari metodi presenti, a partire da quelli statici che sono di grande importanza per il parsing del codice. Infatti avrete notato che già si cerca di eliminare eventuali impurità quando il codice viene passato utilizzando lstrip(); il prossimo passo sarà quello di dividere il codice per righe e di ripurirlo fino all’osso per non avere equivoci ed è quello che vedremo nella prossima parte.