2010-03-02 10 views
6

Informazioni di base: In definitiva, mi piacerebbe scrivere un emulatore di una macchina reale come Nintendo o Gameboy originali. Tuttavia, ho deciso che ho bisogno di iniziare da qualche parte molto, molto più semplice. Il mio consulente/professore di informatica mi ha offerto le specifiche per un processore immaginario molto semplice che ha creato per emulare prima. C'è un registro (l'accumulatore) e 16 codici operativi. Ogni istruzione consiste di 16 bit, i primi 4 dei quali contengono l'opcode, il resto dei quali è l'operando. Le istruzioni vengono fornite come stringhe in formato binario, ad esempio "0101 0101 0000 1111".Domande relative all'implementazione di un semplice emulatore di CPU

La mia domanda: In C++, qual è il modo migliore per analizzare le istruzioni per l'elaborazione? Per favore, tieni il mio obiettivo finale in mente. Qui ci sono alcuni punti che ho preso in considerazione:

  1. Non posso processo ed eseguire le istruzioni come ho letto loro, perché il codice è auto-modifica: un'istruzione può cambiare un'istruzione più tardi. L'unico modo che posso vedere per aggirare questo sarebbe quello di memorizzare tutte le modifiche e per ogni istruzione per verificare se è necessario applicare una modifica. Ciò potrebbe portare a enormi quantità di confronti con l'esecuzione di ogni istruzione, il che non è buono. E così, penso di dover ricompilare le istruzioni in un altro formato.

  2. Sebbene sia possibile analizzare l'opcode come stringa ed elaborarlo, ci sono casi in cui l'istruzione nel suo insieme deve essere considerata come un numero. L'opcode di incremento, ad esempio, potrebbe modificare anche la sezione opcode di un'istruzione.

  3. Se dovessi convertire le istruzioni in numeri interi, non sono sicuro di come avrei potuto analizzare solo la sezione opcode o operando dell'int. Anche se dovessi ricompilare ogni istruzione in tre parti, l'intera istruzione come un int, l'opcode come un int e l'operando come un int, che comunque non risolverebbe il problema, poiché potrei dover incrementare un'intera istruzione e in seguito analizza l'opcode o l'operando interessato. Inoltre, dovrei scrivere una funzione per eseguire questa conversione, o c'è qualche libreria per C++ che ha una funzione per convertire una stringa in "formato binario" in un numero intero (come Integer.parseInt (str1, 2) in Java)?

  4. Inoltre, vorrei essere in grado di eseguire operazioni quali bit di spostamento. Non sono sicuro di come ciò possa essere realizzato, ma ciò potrebbe influire sul modo in cui implemento questa ricompilazione.

Grazie per qualsiasi aiuto o consiglio si può offrire!

risposta

5

analizzare il codice originale in un array di interi. Questa matrice è la memoria del tuo computer.

utilizzare le operazioni bit per bit per estrarre i vari campi. Ad esempio, questo:

unsigned int x = 0xfeed; 
unsigned int opcode = (x >> 12) & 0xf; 

estrarrà i più alti quattro bit (0xf, qui) da un valore a 16 bit memorizzato in un unsigned int. Puoi quindi utilizzare per es. switch() per ispezionare il codice operativo e intraprendere l'azione appropriata:

enum { ADD = 0 }; 

unsigned int execute(int *memory, unsigned int pc) 
{ 
    const unsigned int opcode = (memory[pc++] >> 12) & 0xf; 

    switch(opcode) 
    { 
    case OP_ADD: 
    /* Do whatever the ADD instruction's definition mandates. */ 
    return pc; 
    default: 
    fprintf(stderr, "** Non-implemented opcode %x found in location %x\n", opcode, pc - 1); 
    } 
    return pc; 
} 

memoria modifica è solo un caso di scrivere nel vostro array di interi, forse anche con un po 'di matematica bit a bit, se necessario.

+0

Speravo che qualcuno menzionasse un concetto come questo. Non l'ho mai usato prima però, quindi dovrò fare ulteriori ricerche. Grazie! –

+0

Ahh, ricordi di progetti universitari! – sdg

+0

+1. Questo è l'approccio di base che dovresti prendere. Il punto chiave qui, Brandon, relativo alla tua domanda, è che per avvicinarti normalmente, devi arrivare a "codice macchina" che sarà la matrice di byte in una matrice che rappresenta lo spazio degli indirizzi del tuo computer virtuale. Quindi se le istruzioni modificano la memoria (codice), non fai niente di speciale, basta seguire le istruzioni e dovrebbero fare la cosa giusta all'interno del tuo grande array di memoria virtuale. IOW, hai bisogno sia dell'assemblatore (lo strumento che traduce le stringhe di testo in byte di istruzioni) che dell'emulatore, la cosa che esegue –

1

Penso che l'approccio migliore sia leggere le istruzioni, convertirle in numeri interi non firmati e archiviarli in memoria, quindi eseguirli dalla memoria.

  1. Dopo aver analizzato le istruzioni e memorizzati nella memoria, auto-modifica è molto più facile che la memorizzazione di un elenco delle modifiche per ciascuna istruzione. Puoi semplicemente cambiare la memoria in quella posizione (presumendo che tu non abbia mai bisogno di sapere quale fosse la vecchia istruzione).

  2. Poiché si stanno convertendo le istruzioni in numeri interi, questo problema è discutibile.

  3. Per analizzare le sezioni opcode e operando, è necessario utilizzare il bit shifting e il mascheramento. Ad esempio, per ottenere il codice operativo, si mascherano i 4 bit superiori e si sposta di 12 bit verso il basso (instruction >> 12). Puoi usare una maschera per ottenere anche l'operando.

  4. Vuoi dire che la tua macchina ha istruzioni per spostare i bit? Questo non dovrebbe influenzare il modo in cui memorizzi gli operandi. Quando si esegue una di queste istruzioni, è sufficiente utilizzare gli operatori di spostamento bit C++ << e >>.

+0

Riguardo a 1: In tal caso, dovrei ancora ricompilare il set di istruzioni prima di eseguirle invece di eseguirle mentre vengono letti, corretto? Non è un problema, mi stavo chiedendo se ci potrebbe essere un buon modo per realizzare il metodo di non ricompilazione. // Riguardo a 4: Giusto, questo è ciò che intendevo. Ho pensato che avrebbe influenzato il modo in cui avrei archiviato i pezzi di istruzioni, dato che dovevo considerare ogni pezzo separatamente. Ma questo po 'di matematica sembra che potrebbe realizzarlo. // Ciò che hai menzionato ha un senso concettualmente. Ci proverò quando torno a casa oggi. Grazie! –

+0

Non so cosa intendi per "ricompilare" qui. Una (semplice) CPU non "ricompila" nulla: ha i bit, e fa quello che dicono. Puoi chiarire cosa intendi con questo? – Ken

+0

La mia comprensione è che gli emulatori funzionano spesso ricompilando le istruzioni in un altro formato che può essere più facilmente elaborato dalla macchina dell'emulatore. Sto usando il termine liberamente qui, ma penso che sia la stessa idea. Mi stavo chiedendo se esiste un modo efficace per eseguire le istruzioni senza dover memorizzare le istruzioni da qualche altra parte in memoria in un altro formato (ad esempio, come in unsigned). –

0

Nel caso in cui sia utile, ecco l'ultimo emulatore di CPU che ho scritto in C++. In realtà, è l'unico emulatore che ho scritto in C++.

lingua del specifica è un po 'eccentrico, ma è una semplice descrizione perfettamente rispettabile, VM, possibilmente abbastanza simile a VM del prof:

http://www.boundvariable.org/um-spec.txt

Ecco il mio codice (un po' sovra-ingegnerizzato), che dovrebbe dare alcune idee. Per esempio si mostra come implementare operatori matematici, nella istruzione switch gigante in um.cpp:

http://www.eschatonic.org/misc/um.zip

Si può forse trovare altre implementazioni per il confronto con una ricerca sul web, dal momento che un sacco di gente è entrata nel contest (Non ero uno di loro: l'ho fatto molto dopo). Anche se non molti in C++, immagino.

Se fossi in te, memorizzerei solo le istruzioni come stringhe da cui partire, se questo è il modo in cui le specifiche della macchina virtuale definiscono le operazioni su di esse. Quindi convertirli in numeri interi in base alle esigenze, ogni volta che si desidera eseguirli. Sarà lento, ma allora? La tua non è una vera macchina virtuale che utilizzerai per eseguire programmi critici per il tempo, e un interprete slow-dog illustra ancora i punti importanti che devi sapere in questa fase.

È possibile però che la VM definisca effettivamente tutto in termini di numeri interi e che le stringhe siano lì solo per descrivere il programma quando viene caricato nella macchina. In tal caso, convertire il programma in numeri interi all'inizio. Se la VM memorizza programmi e dati insieme, con le stesse operazioni che agiscono su entrambi, allora questa è la strada da percorrere.

Il modo per scegliere tra di loro è quello di guardare l'opcode che viene utilizzato per modificare il programma. La nuova istruzione è fornita come intero o come stringa? Qualunque sia, la cosa più semplice da iniziare è probabilmente quella di memorizzare il programma in quel formato. Puoi sempre cambiare dopo una volta che funziona.

Nel caso della messaggistica unificata sopra descritta, la macchina è definita in termini di "piatti" con spazio per 32 bit.Chiaramente questi possono essere rappresentati in C++ come interi a 32 bit, quindi è quello che fa la mia implementazione.

+0

Prima di parlare con il mio professore, ho cercato qualcosa di semplice da emulare e ho trovato quella competizione. Era ancora un po 'troppo complicato per me cominciare, ma quello potrebbe essere il mio prossimo passo. Guarderò il tuo codice per le idee. Grazie! –

0

Ho creato un emulatore per un processore crittografico personalizzato. Ho sfruttato il polimorfismo di C++ creando un albero di classi di base:

struct Instruction // Contains common methods & data to all instructions. 
{ 
    virtual void execute(void) = 0; 
    virtual size_t get_instruction_size(void) const = 0; 
    virtual unsigned int get_opcode(void) const = 0; 
    virtual const std::string& get_instruction_name(void) = 0; 
}; 

class Math_Instruction 
: public Instruction 
{ 
    // Operations common to all math instructions; 
}; 

class Branch_Instruction 
: public Instruction 
{ 
    // Operations common to all branch instructions; 
}; 

class Add_Instruction 
: public Math_Instruction 
{ 
}; 

Ho anche avuto un paio di fabbriche. Sarebbe utile almeno due:

  1. Fabbrica per creare istruzioni dal testo .
  2. fabbrica per creare istruzioni da opcode

Le classi corsi dovrebbero avere metodi per caricare i dati da una sorgente di ingresso (ad esempio std::istream) o testo (std::string). Devono anche essere supportati i metodi di output corollario (come nome dell'istruzione e codice operativo).

Ho avuto l'applicazione creare oggetti, da un file di input e inserirli in un vettore di Instruction. Il metodo dell'esecutore eseguirà il metodo 'execute() `di ogni istruzione nell'array. Questa azione è stata trasferita all'oggetto foglia di istruzione che ha eseguito l'esecuzione dettagliata.

Ci sono altri oggetti globali che potrebbero richiedere anche l'emulazione. Nel mio caso alcuni includevano il bus dati, i registri, l'ALU e le posizioni di memoria.

Si prega di dedicare più tempo a progettare e pensare al progetto prima di codificarlo. L'ho trovato abbastanza difficile, specialmente implementando un debugger e una GUI compatibili con single-step.

Buona fortuna!

+0

BTW, alla mia alma mater, uno dei professori aveva i suoi studenti scrivere funzioni per emulare componenti hardware per la classe * Microprocessor Design *. Abbiamo avuto alcune buone discussioni sull'emulazione del processore. :-) –