Pdp 8 Emulator in python, quarta parte.

Introduzione

In questa parte analizzeremo l’istruzione più delicata presente nel pdp8, ovvero quella che carica il contenuto del codice assembly, trasformandolo in codice macchina, nel nostro calcolatore didattico. Vedremo anche alcune funzioni utili per controllare lo stato della macchina durante l’esecuzione.

Ho già apportato le modifiche suggerite nel precedente articolo, di conseguenza il codice dovrebbe essere ancora più comprensibile. Non ho convertito il core per funzionare con gli interi, ma questa scelta è prettamente personale, poiché abbiamo già visto che porterebbe un incremento di prestazioni, che comunque non è rilevante ai fini del programma, del quale mi interessava sopratutto il funzionamento.

Codice

def carica(self,codice,master):
        """
        Carica il codice assembly in memoria.
        Non si conta l'ultimo END, che corrisponde al fine programma.
        La funzione ritorna 1 se il caricamento va a buon fine, None altrimenti.
        """
        self.halt = False # warning se l'istruzione HLT non viene trovata

        temp = codice.rstrip()
        temp = temp.split('\n')
        for x in range(0,len(temp)):
            temp[x] = self.purgestr(temp[x])
        self.purge(temp)
        cod = []

        ### elimino commenti
        for x in temp:
            var = x.split('/')
            cod.append(var[0].rstrip())

        self.purge(cod)
        ### START
        temp = cod[0].split()
        tempRAM = {}

        if temp[0] == 'ORG':
            if int(str(temp[1]),16)>-1 and int(str(temp[1]),16)<4096:
                self.START = int(str(temp[1]),16)
                self.PC = self.binario(self.START).zfill(12)
                cod.pop(0)
            else :
                showwarning("Errore di caricamento", "ORG di inizio file non corretto", parent = master.codice.master)
                return None
        else :
            self.START = 0
            self.PC = self.binario(self.START).zfill(12)

        ### Elimino END
        try:
            end = cod.index('END')
            if end == len(cod)-1:
                cod.pop(end)
                self.purge(cod)
            else:
                raise Exception
        except Exception:
            showwarning("Errore di caricamento", "Fine codice (END) non trovato!", parent = master.codice.master)
            return None

        origin = self.START
        count = 0
        ### RAM temporanea
        for x in range(0,len(cod)):
            temp = cod[x]
            if temp[:3] == 'ORG':
                tt = temp.split()
                if int(str(tt[1]),16)<4096 and int(str(tt[1]),16)>-1:
                    self.START = int(str(tt[1]),16)
                    count = 0
                    continue
                else:
                    showwarning("Errore di caricamento", "ORG non corretto", parent = master.codice.master)
                    return None
            tempRAM[self.START+count] = temp
            count += 1

        self.START = origin
        ### LABEL
        for x,y in sorted(tempRAM.iteritems()):
            if y.find(',')>=0:
                temp = y.split(',')
                self.purge(temp)
                self.LABEL[self.purgestr(temp[0].lstrip())]= self.binario(x).zfill(12)
                tempRAM[x] = temp[1]

        ### RAM
        for x,y in sorted(tempRAM.iteritems()):
            self.RAM[self.binario(x).zfill(12)] = y

        ### DEC e HEX e decodifica codici
        try:
            for x,y in sorted(self.RAM.iteritems()):
                temp = y.split(' ')
                self.purge(temp)
                for z in range(0,len(temp)):
                    temp[z] = self.purgestr(temp[z])
                if len(temp) == 0:
                    continue
                elif temp[0].lstrip() == 'DEC':
                    self.RAM[x] = self.binario(
                        self.range(int(temp[1]))).zfill(16)
                elif temp[0] == 'HEX':
                    self.RAM[x] = self.binario(self.range
                              (int(temp[1],16))).zfill(16)
                elif len(temp)==2:
                    if len(temp[0])!=3 :
                        raise Exception
                    else:
                        self.RAM[x]= '0'+self.decode(temp[0])+self.decode(temp[1])
                elif len(temp)==3:
                    if len(temp[0])!=3 :
                        raise Exception
                    else:
                        self.RAM[x]= '1'+self.decode(temp[0])+self.decode(temp[1])
                else:
                    self.RAM[x]= self.decode(temp[0].lstrip())
        except Exception:
            showwarning("Errore di caricamento", "Correggere : "+str(y), parent = master.codice.master)
            return None

        for x,y in sorted(self.RAM.iteritems()):
            self.BREAKP[x] = False
            if len(x)>12 or len(y)>16:
                return None
            if len(y) != 16:
                self.RAM[x] = y.zfill(16)

        self.nstep = 1

        if not self.halt:
            showwarning("Attenzione !!!",
                        """Istruzione assembly HLT non presente!!!
                        \nQuesto può portare ad un errore il programma, per esempio a causa di un ciclo infinito;
                        \nQuindi si potrebbe uscire inaspettatamente dall'applicazione.""", parent = master.master)
        return 1

    def decode(self,x):
        """
        Ritorna una stringa binaria corrispondente al comando passato
        """
        if self.LABEL.has_key(x):
            return str(self.LABEL[x])
        elif self.Opcodes.has_key(x):
            if x == 'HLT' and self.halt is False:
                self.halt = True
            return str(self.Opcodes[x])
        else:
            if len(x) != 0:
                return self.binario(
                        self.range(
                            int(str(x),16))).zfill(12)

    def setnstep(self,n):
        """
        Setta il numero di cicli da eseguire
        """
        self.nstep = n

    def startCD(self):
        """
        Avvia il Calcolatore Didattico
        """
        self.S = True

    def stopCD(self):
        """
        Arresta il calcolatore di dattico
        """
        self.S = False

    def __str__(self):
        """
        Stampa a video lo stato del Calcolatore Didattico
        """
        stringa = ''
        stringa += self.statusRAM()
        stringa += self.statusREG()
        stringa += self.label()
        stringa += self.uc()
        return stringa

    def statusRAM(self):
        """
        Ritorna una stringa con lo stato della RAM
        """
        stringa = ''
        strlabel = ''
        stropcode = ''
        stringa += 'Ind BIN'+'\t\t'+'B'+'\t'+'Ind HEX'+'\t\t'+'Cod Istr.'+'\t\t '+'LABEL'+'\t'+'Opcode'+'\t'+'Val DEC'+'\n'
        stringa += "--------------------------------------------------------------------------------"+"\n"
        temp = """%s\t\t%s\t%s\t\t%s\t%s\t\t%s\t%s\n"""
        for x,y in sorted(self.RAM.iteritems()):
            if self.BREAKP.has_key(x) and self.BREAKP[x]:
                breakpoint = 'X'
            else :
                breakpoint = '-'
            for z,t in sorted(self.LABEL.iteritems()):
                if t == x:
                    strlabel = z
                    break
                else:
                    strlabel = ''
            for h,j in sorted(self.Opcodes.iteritems()):
                if j == y[1:4]:
                    stropcode = h
                    break
                elif j == y and (j[:4] == '0111' or j[:4] == '1111'):
                    stropcode = h
                    break
                else:
                    stropcode = ''
            stringa += temp % (x, breakpoint,self.esadecimale(int(str(x),2)), y, strlabel,stropcode,
                               str(self.range(int(y,2))))
        stringa += "--------------------------------------------------------------------------------"+"\n"
        return stringa

    def statusREG(self):
        """
        Ritorna una stringa con lo stato dei registri
        """
        stringa = ''
        stringa += ("\n"+"--- REGISTRI ----------------------------"+"\n")
        stringa += ("PC\t= "+str(self.PC)+"\n")
        stringa += ("I\t= "+str(self.I)+"\tOPR\t= "+str(self.OPR)+"\n")
        stringa += ("E\t= "+str(self.E)+"\tAC\t= "+str(self.AC)+"\n")
        stringa += ("MAR\t= "+str(self.MAR)+"\n")
        stringa += ("MBR\t= "+str(self.MBR)+"\n")
        stringa += ("------------------------------------------""\n")
        return stringa

    def label(self):
        """
        Ritorna una stringa con i label memorizzati
        """
        stringa = ''
        stringa += ("\n"+"--- LABELS ------------"+"\n")
        for x,y in sorted(self.LABEL.iteritems()):
            stringa += str(x)+' = '+str(y)+'\n'
        stringa += ("-----------------------""\n")
        return stringa

    def uc(self):
        """
        Ritorna una stringa con lo stato dell'unita' di controllo
        """
        stringa = ''
        stringa += ("\n"+"--- UC ------"+"\n")
        stringa += 'S =\t'+str(self.S)+'\n'
        stringa += 'F =\t'+str(self.F)+'\n'
        stringa += 'R =\t'+str(self.R)+'\n'
        stringa += 'Int =\t'+str(self.Interrupt)+'\n'
        stringa += ("-------------""\n")
        return stringa

Analisi

– La funzione carica riceve in input il codice da caricare (codice) e la finestra dell’editor (master). Quest’ultima è stata aggiunta in un secondo momento, poiché ora la funzione visualizza un finestra di warning a seconda dell’errore commesso (sempre tramite tkinter).

Il file di testo viene passato alla funzione eliminando gli spazi all’inizio, quindi utilizziamo rstrip per togliere quelli alla fine e memorizziamo il codice in una variabile temporanea.

Una volta diviso il codice per righe tramite split(‘\n’), ripuliamo ogni stringa ed infine anche la lista di stringhe da caratteri indesiderati.

Fatto questo, creiamo una lista cod che conterrà i codici da tradurre. Per inserirli correttamente, eliminiamo i commenti e aggiungiamo solo quello che ci serve alla lista. Finito questo, eliminiamo elementi indesiderati dalla lista.

Ora che abbiamo i nostri codici, controlliamo se all’inizio è presente la stringa ORG e settiamo di conseguenza la variabile START, per indicare da quale indirizzo si inizierà a memorizzare il programma. Se l’ORG non è corretto, si interrompe e si mostra una finestra di warning. Abbiamo anche creato una variabile temporanea per la ram (un dizionario per la precisione).

Il controllo successivo, consiste nel trovare l’END del file, se non presente si ritorna errore come prima. Da notare che END si deve trovare necessariamente nell’ultima posizione, ovvero quella di index n-1 dove n è il numero degli elementi della lista cod. Visto che dovrebbe essere utilizzato solo per indicare la fine del file, non controllo se l’utente lo utilizza nel codice, ma in tal caso ristituirebbe comunque errore perché non si trova solo alla fine del file e l’idex quindi non sarà quello dell’ultimo elemento, ma di un elemento all’interno di cod.

Successivamente memorizziamo i codici nella ram temporanea in base al loro indirizzo. Essendo prevista la presenza di più ORG, utilizzo un contatore (count) per determinare la cella del codice da scrivere partendo da start. Origin terrà traccia del valore iniziale di START, poiché sarà soggetto a mutamenti e quindi dovrà essere ripristinato alla fine.

Una volta creata la ram temporanea possiamo riempire la tabella dei Labels, associando ogni etichetta al suo indirizzo fisico (si parla sempre della memoria del nostro calcolatore). Eliminiamo dunque le etichette dalla ram temporanea, lasciando solo i codici veri e propri (tempRAM[x]=temp[1]).

Una volta copiata la ram temporanea nella ram vera e propria (si intende sempre quella del calcolatore didattico), si procede all’ultima conversione dei codici assembly in stringhe binarie. Di norma le stringhe avranno due o tre istruzioni e quindi verranno tradotte in base a quello che è presente nella stringa. Per prima però si controlla se indicano un numero espresso in valore decimale (DEC) oppure in esadecimale (HEX).

Si conclude estendendo le stringhe nella ram a 16 bit (se rappresentate con meno) ed aggiornando la ‘tabella’ dei breakpoints. Infine si controlla se l’istruzione HLT è stata trovata.

Decode trasforma in binario i codici passati, controllando prima se sono indirizzi presenti nei labels, poi se sono istruzioni, altrimenti tratta quello che trova come se fosse un numero esadecimale e lo converte in binario. In questa funzione viene anche gestito il ritrovamento di HLT, che tecnicamente è solo un avvertimento, visto che il warning per HLT non blocca lesecuzione ma ritorna successivamente 1 (nella funzione carica carica).

– Le altre funzioni dovrebbero essere semplici da capire, visto che hanno solo il compito di stampare a video quello che è presente nel pdp8. Se avete provato il programma, noterete che l’unica funzione necessaria è statusRAM (per quel che riguarda l’interfaccia grafica), le altre sono solo necessarie se si utilizza la riga di comando. Infatti, durante la stesura del programma, controllavo la sua esecuzione tramite queste funzioni, visto che ancora non avevo implementato un’interfaccia grafica.

Conclusioni

Questa parte è stata la più delicata e fondamentale per tutto il progetto. Risente delle scelte prese inizialmente e condiziona pesantemente i passi successivi. Spero di essere stato comprensibile e di avervi dato un’idea di quello che fa l’emulatore per caricare un file assembly. Badate bene che questo è molto lontano dal lavoro che svolge un vero e proprio assembler, ma comunque ci si avvicina, se non altro come logica. Nel prossimo articolo vedremo come è gestita l’esecuzione del pdp8 per poi passare all’interfaccia grafica.

Press ESC to close