Pdp 8 Emulator in python, quinta parte.

Introduzione

In questa parte vedremo con svolge l’esecuzione il calcolatore. Dopo l’introduzione delle micro-istruzioni, l’esecuzione non è più stata legata solo alle variabili di controllo, ma anche da una terza che rappresenta il tempo di clock. Infatti l’esecuzione temporale del programma (per capirci, l’iterfaccia utente) è completamente scollegata da quella interna.

Il pdp8 è legato fondamentalmente da un clock a 4 tempi e questo condizionerà tutto il codice che si vedrà in questa parte. Non è una scelta personale, infatti il pdp8 eseguiva una microi-istruzione (alcune possono essere eseguite in “contemporane”, quindi possono esserci più micro-istruzioni per clock) per ciclo temporale (clock) e quindi il “tempo” deve scandire le varie parti che vengono man mano eseguite. Per comodità, non riporterò per filo e per segno quello che accade con ogni istruzione, ma mi concentrerò sull’analisi della parte più importante e dell’organizzazione.

C’è da puntualizzare che il tempo di chiamata (cioè con quanta frequenza vengono richiamate le funzioni di esecuzione) è dato sempre dalla GUI, quindi il “clock” interno del programma è solamente una semplice variabile per determinare certe condizioni (interne) e non corrisponde ad un intervallo temporale determinato (come invece era per il pdp8 vero e proprio).

Codice

def step(self,root=None,codice=None):

        """

        Uno step equivale all'esecuzione dei seguenti cicli:

            - ciclo di fetch

            - ciclo di indirizzamento indiretto (se necessario)

            - ciclo di esecuzione

            - ciclo di interruzione (se necessario)

        """

        try:

            self.breaks = False

            if not self.F and not self.R:

                self.fetch()

            elif not self.F and self.R:

                self.indind()

            elif self.F and not self.R:

                self.execute()

            elif self.F and self.R:

                self.interrupt(root)

            if self.breaks:

                self.S = False

            if self.tempo <3:

                self.tempo += 1

            else:

                self.tempo = 0

        except Exception:

            showwarning("Errore", "Controllare il codice assembly!", parent = codice.master)

            self.S = False

def interrupt(self, root):

        """

        Input da tastiera ed output su video di caratteri ASCII (da 0 a 127)

        """

        if self.MBR == self.Opcodes['INP'] and self.Interrupt: # INP

            if self.tempo is 0:

                self.microistruzioni += '\n'

                self.microistruzioni += "INP --- \n"

                self.microistruzioni += "NOP \n"

            elif self.tempo is 1:

                self.microistruzioni += "NOP \n"

            elif self.tempo is 2:

                self.microistruzioni += "NOP \n"

            elif self.tempo is 3:

                temp = ''

                to = 128

                while len(temp) != 1 or to > 127 and temp == None:

                    if root != None:

                        temp = tkSimpleDialog.askstring("Inserisci","CHAR", parent = root)

                    else:

                        temp = str(input("Inserisci un carattere"))

                    if len(temp) == 1:

                        to = ord(temp)

                    elif temp == '':

                        to = ord(chr(13)) ## Ritorno carrello

                        break

                self.AC = self.binario(to).zfill(16)

                self.F = False

                self.R = False

                self.microistruzioni += "F<- 0 , R <- 0 \n"

                self.microistruzioni += "----- \n"

        elif self.MBR == self.Opcodes['OUT'] and self.Interrupt: # OUT

            if self.tempo is 0:

                self.microistruzioni += '\n'

                self.microistruzioni += "OUT --- \n"

                self.microistruzioni += "NOP \n"

            elif self.tempo is 1:

                self.microistruzioni += "NOP \n"

            elif self.tempo is 2:

                self.microistruzioni += "NOP \n"

            elif self.tempo is 3:

                temp = chr(int(self.AC,2))

                if temp == '\r':

                    temp = '\n'

                self.inout += temp

                self.F = False

                self.R = False

                self.microistruzioni += "F<- 0 , R <- 0 \n"

                self.microistruzioni += "----- \n"

        elif self.MBR == self.Opcodes['ION']:

            if self.tempo is 0:

                self.microistruzioni += '\n'

                self.microistruzioni += "ION --- \n"

                self.microistruzioni += "NOP \n"

            elif self.tempo is 1:

                self.microistruzioni += "NOP \n"

            elif self.tempo is 2:

                self.microistruzioni += "NOP \n"

            elif self.tempo is 3:

                self.Interrupt = True

                self.F = False

                self.R = False

                self.microistruzioni += "F<- 0 , R <- 0 \n"

                self.microistruzioni += "----- \n"

        elif self.MBR == self.Opcodes['IOF']:

            if self.tempo is 0:

                self.microistruzioni += '\n'

                self.microistruzioni += "IOF --- \n"

                self.microistruzioni += "NOP \n"

            elif self.tempo is 1:

                self.microistruzioni += "NOP \n"

            elif self.tempo is 2:

                self.microistruzioni += "NOP \n"

            elif self.tempo is 3:

                self.Interrupt = False

                self.F = False

                self.R = False

                self.microistruzioni += "F<- 0 , R <- 0 \n"

                self.microistruzioni += "----- \n"

        else:

            self.F = False

            self.R = False

def execute(self):

        """

        Esecuzione dell'operazione. Se non presente, F torna a 0 per

        eseguire il fetch dell'istruzione successiva.

        """

        if self.I == '1' and self.OPR == '111':

            self.R = True

        elif self.I == '0' and self.OPR == '111':

            if self.MBR == self.Opcodes['HLT']:

                self._HLT()

            elif self.MBR == self.Opcodes['CLA']:

                self._CLA()

            elif self.MBR == self.Opcodes['CLE']:

                self._CLE()

            elif self.MBR == self.Opcodes['CMA']:

                self._CMA()

            elif self.MBR == self.Opcodes['CME']:

                self._CME()

            elif self.MBR == self.Opcodes['CIR']:

                self._CIR()

            elif self.MBR == self.Opcodes['CIL']:

                self._CIL()

            elif self.MBR == self.Opcodes['INC']:

                self._INC()

            elif self.MBR == self.Opcodes['SPA']:

                self._SPA()

            elif self.MBR == self.Opcodes['SNA']:

                self._SNA()

            elif self.MBR == self.Opcodes['SZA']:

                self._SZA()

            elif self.MBR == self.Opcodes['SZE']:

                self._SZE()

        else:

            if self.OPR == self.Opcodes['AND']:

                self._AND()

            elif self.OPR == self.Opcodes['ADD']:

                self._ADD()

            elif self.OPR == self.Opcodes['LDA']:

                self._LDA()

            elif self.OPR == self.Opcodes['STA']:

                self._STA()

            elif self.OPR == self.Opcodes['BUN']:

                self._BUN()

            elif self.OPR == self.Opcodes['BSA']:

                self._BSA()

            elif self.OPR == self.Opcodes['ISZ']:

                self._ISZ()

            else:

                self.F = False

def indind(self):

        """

        Ciclo di indirizzamento indiretto

        """

        if self.tempo is 0:

            self.MAR = self.MBR[4:]

            self.microistruzioni += '\n'

            self.microistruzioni += "Indirizzamento indiretto : --- \n"

            self.microistruzioni += "MAR <- MBR(AD) \n"

        elif self.tempo is 1:

            self.MBR = self.RAM[self.MAR]

            self.microistruzioni += "MBR <- M \n"

        elif self.tempo is 2:

            self.microistruzioni += "NOP \n"

        elif self.tempo is 3:

            self.F = True

            self.R = False

            self.microistruzioni += "F <- 1 , R <- 0 \n"

            self.microistruzioni += "----- \n"

def fetch(self):

        """

        Ciclo di fetch

        """

        if self.tempo is 0:

            if self.BREAKP[self.PC] == True:

                self.breaks = True

            self.MAR = self.PC

            line = 1

            for x in sorted(self.RAM):

                if x == self.PC:

                    self.nextistr = line

                    break

                line += 1

            if int(self.PC,2) == self.START:

                self.previstr = self.nextistr-1

            self.microistruzioni += '\n'

            self.microistruzioni += "++++++++++++++++++++++++++++++++++++++++++++++++++++++\n"

            self.microistruzioni += "Fetch :  \n"

            self.microistruzioni += "MAR <- PC \n"

        elif self.tempo is 1:

            temp = int(self.PC,2)

            temp += 1

            self.PC = self.binario(temp).zfill(12)

            self.MBR = self.RAM[self.MAR]

            self.microistruzioni += "MBR <- M , PC <- PC+1 \n"

        elif self.tempo is 2:

            self.OPR = self.MBR[1:4]

            self.I = self.MBR[0]

            self.microistruzioni += "OPR <- MBR(OP) , I <- MBR(I) \n"

        elif self.tempo is 3:

            if self.I == '1' and self.OPR != '111':

                self.R = True

                self.microistruzioni += "R <- 1 \n"

            else :

                self.F = True

                self.microistruzioni += "F <- 1 \n"

– def step() :

Questa funzione determina tutta l’esecuzione del programma. Step incrementa la variabile “tempo” che rappresenta il clock interno del processore e richiama i vari cicli in base alle variabili di controllo F ed R. Si occuperò anche di bloccare l’esecuzione se presente un break e di uscire dall’esecuzione si si presentano degli errori. In questo caso si esce per qualsiasi errore che si presenta, ma in precedenza questo except veniva utilizzato per debuggare il programma e controllare la sua esecuzione.

Come potete immaginare, questa sarà la funzione che verrà richiamata dalla gui per eseguire l’emulazione. Questo ci permette di spostare l’attenzione al solo file pdp8 in caso di malfunzionamenti e di escludere eventuali problematiche derivate dall’interfaccia, visto che l’esecuzione è completamente staccata da quest’ultima.

Le altre funzioni (fetch, indind (indirizzamento indiretto), execute, interrupt) non dovrebbero richiedere particolari spiegazioni, la loro struttura è lineare e rispechia tutte le operazioni che effettuerebbe il calcolatore. L’unica cosa in più è che devono aggiornare le microistruzioni (una stringa) per stampare a video le operazioni eseguite.

Anche se non sono presenti le altre istruzioni, potete dedurre facilmente che queste saranno legate alla variabile tempo (come in fetch per esempio, oppure l’istruzione INP). Le uniche operazioni che possiamo calcolare esternamente con la GUI (e che vedremo nei prossimi articoli), sono i vari tipi di step disponibili. Uno step equivale all’esecuzione completa di un’istruzione, quindi di una riga di codice; un mini step comprende solo un ciclo dei quattro presenti, mentre un micro-step esegue solo una microi-istruzione (corrisponde ad un clock, quindi al variare di una singola unità di tempo). Queste operazioni sono facilmente controllabili dall’esterno, visto che toccano solamente la variabile temporale.

Prima di concludere vorrei fare presente che il ciclo di interrupt non è proprio veritiero, quindi non prendete per buono quello che accade a livello di micro-istruzioni, visto che non simulo nè le periferiche nè l’iterfaccia di collegamento (anche se per il pdp8 sarebbe proprio il processore che si occupa di colloquiare con le periferiche esterne). Le altre istruzioni che trovate nei file sorgente invece sono effettivamente quelle che eseguiva l’originale calcolatore (per quanto riguarda le micro-istruzioni per clock), naturalmente adattate con costrutti python tenendo sempre presente di gestire delle stringhe.

Conclusioni

Credo che questa parte sia molto semplice da comprendere, visto che non ci possono essere molte varianti, ma si deve solamente cercare di simulare l’esecuzione vera e propria del calcolatore. Mi dovete scusare se non ho speso molte parole per analizzare il codice, ma non ci sono scelte particolari od operazioni complesse che si eseguono e quindi preferisco ricevere qualche commento su parti magari più oscure per spiegarle (sempre che ce ne siano) che descrivere ancora più minuziosamente operazioni semplici come quelle che avete letto.

Nel prossimo articolo cominceremo a vedere l’interfaccia grafica, così da poter capire meglio come quest’ultima interagisce con il programma vero e proprio. Spero che sia utile vedere il funzionamento di tkinter, perché oltre ad essere integrata in python, rispecchia logicamente il funzionamento delle librerie più ricche come GTK e QT.

Press ESC to close