PearPC: l’arte della simulazione

Simulare o emulare: questo è il problema. Quando non abbiamo ancora il nostro target, vale di più simulare o emulare il comportamento del nostro codice che, per via dei tempi di consegna, deve essere sviluppato in maniera parallela all’hardware?

I tempi di consegna del nostro lavoro sono sempre più ravvicinati e non consentono deroghe. Prima di arrivare all’integrazione hardware/software è necessario partire da un software già sufficientemente testato per escludere problemi o congetture. Ora, è chiaro che occorre definire un’attività di verifica preliminare. In mancanza delle risorse fisiche (il nostro target) come possiamo svolgere il nostro lavoro? Può essere opportuno utilizzare strumenti alternativi, quali un simulatore o un emulatore. Dal punto di vista software, probabilmente, lo strumento che meglio soddisfa le nostre esigenze è un simulatore. La simulazione è un’idea fondamentalmente differente rispetto al l’emulazione.

Due concetti strettamente correlati, ma fondamentalmente diversi. La simulazione è un processo che mira a fare apparire come reale ciò che in realtà non lo è, nel nostro caso un processore con le eventuali periferiche. In questo caso, con “simulare” un processore in realtà quello che si vuole ottenere, attraverso un software opportuno, è una rappresentazione virtuale del processore stesso. Infatti, un programma realizzato per una determinata architettura hardware non può funzionare direttamente su una macchina che utilizza un altro insieme di istruzioni, a meno che non si faccia la traduzione delle istruzioni, detta simulazione. In sostanza,  i simulatori consentono di modellare un processore, o un’intera architettura hardware, all’interno di un ambiente software. Il  termine “simulatore” è usato per designare  il programma che realizza questa traduzione. Esiste un limite, cioè un simulatore non processa le informazioni alle stesse caratteristiche reali del modello che si vuole utilizzare. Questo è il vero limite per un sistema dedicato, perché in questo caso si ha necessità di simulare le periferiche in gioco, quali seriali o timer.

Il concetto di simulazione si scontra con quello di emulazione. Un emulatore è un ambiente di test, in cui sono riprodotti tutti i problemi e le caratteristiche del nostro oggetto, processore o periferiche. In questo caso, un emulatore, nel senso più generale possibile, duplica le funzioni di un determinato sistema su un secondo sistema differente dal primo. In sostanza, con emulazione si intende la capacità di uguagliare  il processore stesso per mezzo di opportuni strumenti hardware. Infatti, utilizzando una strumentazione idonea, emulatori, si è in grado di uguagliare il  funzionamento di un processore, anche in assenza di un processore o periferica reale. La differenza tra questi due approcci è sostanzialmente nella strumentazione utilizzata. Nel primo caso, non utilizziamo nessuna strumentazione hardware, ma solo un apposito software in grado di simularne il comportamento. Nel secondo caso, utilizziamo insieme al software anche strumentazione hardware esterna, magari un altro processore, in grado di sostituirsi al processore originale. Così, attraverso un idoneo sistema di emulazione è possibile intraprendere sessioni di test in grado di coinvolgere diverse risorse fisiche. Per esempio, la progettazione di una nuova board, con un processore dedicato, rappresenta la palestra migliore. In questo modo, sostituire  il processore reale con una differente circuiteria hardware che, tra l’altro, oltre a emulare  il processore sia in grado anche di verificare tutta la componentistica a contorno. Oggi, tuttavia, con la presenza sempre più spinta di microcontrollori risultano disponibili onboard un’enorme varietà di periferiche. In pratica, con un sistema di emulazione si utilizza un hardware ester no che si sostituisce, per esempio, al processore fisico, mentre con il processo di simulazione non si utilizza nessuna risorsa fisica esterna perché le funzionalità del processore risultano simulate dal software. Un sistema di emulazione, come quelli commerciali, offre delle prestazioni nettamente superiori. Per esempio, alcuni permettono di definire zone di memoria permettendo così di virtualizzarne l’accesso: il programma  crede di utilizzare zone di memoria on-board (e quindi ancora da testare), ma in realtà sta utilizzando porzioni disponibili sul sistema di emulazione. Personalmente sono quasi sempre stati utilizzati dei simulatori per verificare algoritmi software, mentre con le sessioni di test, o durante le integrazioni hardware/software, diventa necessario utilizzare strumenti più flessibili e un sistema di emulazione probabilmente rappresenta la soluzione migliore. Alcuni utilizzano, tuttavia, i  due termini come sinonimi. In ambito universitario si sono spesso utilizzati programmi software per simulare il  comportamento di determinate architetture hardware, per esempio lo Z8000 o il PDP32. In questo articolo si vuole mettere in risalto la simulazione/emulazione di un PowerPc.

Simulatori o emulatori

A questo punto siamo arrivati a definire la linea di separazione tra questi due ambienti di lavoro, almeno nelle applicazioni embedded. Così, l’uso di un emulatore diventa di fondamentale importanza per iniziare e condurre il troubleshooting nelle attività di integrazione Hw/Sw, mentre un simulatore, soprattutto se ben fatto con modelli di simulazione delle parti hardware, è un ottimo strumento per la verifica del codice. Nel campo della simulazione esistono differenti proposte, ognuna pensata per soddisfare una specifica esigenza. Personalmente, in passato, si è tentato di utilizzare GDB, il  debugger GNU, configurato come simulatore. Infatti, GDB, in base a una specifica configurazione, può essere utilizzato come debugger o come simulatore. La versione standard di GDB non prevede un’interfaccia grafica, ma esistono una gran varietà di opzioni in questo senso. Per esempio è possibile utilizzare l’interfaccia grafica di Insight o il DDD (Data Display Debugger).  Il listato 1 e il listato 2 mostrano le differenti opzioni di configurazioni per GDB utilizzate per generare un debugger o un simulatore, mentre il listato 3 pone in evidenza l’inizio di una tipica sessione di simulazione utilizzando GDB.

/GDB-5.0/configure \
— target = powerpc-eabi \
— prefix = /tools \
— exec-prefix=/tools/H-i686-pc-cygwin \
- v 2>&1 | tee configure.out
make –w 2>&1 | tee make.out
make –w all install 2>&1 | tee install.out
Path=$path:/tools/H-i686-pc-cygwin\bin
export path
Listato 1 – Debugger per PowerPc
/GDB-5.0/configure \
— enable-sim-powerpc \
— prefix=/tools/simulator \
— exec-pefix= /tools/simulator \
— target = powerpc-eabi \
-v 2>&1 | tee configure.out
make –w 2>&1 | tee make.out
make –w all install 2>&1 | tee install.out
Path = $path/tools/simulator
Listato 2 – Simulatore con GDB
<<Seleziona il simulatore come target GDB>>
(gdb) target sim
Connected to the simulator.
<<Carica il programma nella memoria del simulatore>>
(gdb) load calc.elf
Loading section .page0, size 0x20 lma 0x0
Loading section .text, size 0xeba lma 0x8000
Loading section .eh_frame, size 0x1fc lma 0x8eba
Loading section .rodata, size 0x295 lma 0x90b6
Loading section .vectors, size 0x40 lma 0xffc0
Start address 0x8e8f
Transfer rate: 40280 bits in <1 sec.
<<definisci alcuni breakpoint>>
(gdb) b _start
Breakpoint 1 at 0x8e8f
(gdb) b *0x8e95
Breakpoint 2 at 0x8e95
(gdb) b main
Breakpoint 3 at 0x89dc: file calc.c, line 525.
(gdb) b calc.c:318
Breakpoint 4 at 0x8552: file calc.c, line 318.
<<Esegue il programma. Il simulatore carica il Program Counter>>
(gdb) run
Breakpoint 1, 0x8e8f in _start ()
<<mostra il codice macchina con il commando x>>
(gdb) x/4i $pc
(gdb) x/4i $pc
0x8e8f <_start>: lds #0x7fff
0x8e92 <_start+3>: jsr 0x8a67 <__premain>
0x8e95 <__map_data_sections>: ldx #0x934b
0x8e98 <__map_data_sections+3>: ldy #0x1100 <values>
<<riprende l’esecuzione dal breakpoint>>:
(gdb) c
Continuing.
Breakpoint 2, 0x8e95 in __map_data_sections ()
(gdb) c
<<continua>>
Breakpoint 3, main () at calc.c:525
525 _serial_init ();
<< mostra, con list, il contenuto del codice sorgente>>
(gdb) list
314
315 /* Print the top of the stack if no index is specified. */
316 if (which < 0)
317 {
318 which = stack->top - 1;
319 if (which < 0)
320 {
<< visualizza il contenuto dello stack >>
(gdb) print *stack
$3 = {values = 0x7fd3, top = 0, max = 10, mode = PRINT_DEC}
<<mostra I registri dell CPU simulate>>
(gdb) sim info-cpu
M68HC11:
HPRIO @ 0x103c 0x00
CONFIG @ 0x103f 0xff NOSEC NOCOP ROMON EEON
OPTION @ 0x1039 0x00
INIT @ 0x103d 0x01 Ram = 0x0000 IO = 0x1000
CPU info:
Absolute cycle: 800638
Syscall emulation: yes, via 0xcd <n>
Memory errors detection: no
Stop on interrupt: no
Interrupts Info:
Interrupts raised: 0
Min interrupts masked sequence: 13 cycles
Max interrupts masked sequence: 13 cycles
<< visualizza il contentuto delle risorse fisiche>>
(gdb) sim info-timer
...
(gdb) sim info-sio
...
(gdb) sim info-eeprom
...
Listato 3 - Sessione di simulazione con GDB

Alcuni simulatori

Oggi la proposta risulta abbastanza variegata. Possiamo per esempio ricordare PSIM, PearPC, QEMU o lo stesso GDB in modalità simulazione. È anche possibile utilizzare PSIM all’interno di GDB. PearPC, ovvero PowerPC Architecture Emulator, è uno strumento diverso e assolutamente interessante, ma non particolarmente adatto per applicazioni di tipo embedded. Infatti, PearPC è un’applicazione software in grado di eseguire una gran parte di sistemi operativi basati su PowerPc, tra cui Mac Os, Darwin o Linux. La macchina che può ospitare PearPC può essere, indifferentemente, basata su Microsoft Windows, Linux o FreeBSD.  Il funzionamento di PearPC è così spiegato. Il  processore host, o almeno l’applicazione presente sul processore host, dinamicamente  trasla il codice oggetto PowerPc in un equivalente codice x86. Nel caso di istruzioni non permesse perché l’architettura hardware è differente, il codice è sostituito con una porzione di codice equivalente. Il primo passo da affrontare è quello di configurare e inizializzare PearPC sulla macchina ospite: l’operazione è svolta modificando il suo file di configurazione. In questo caso, è necessario aprire il  file (per esempio ppccfg.config) e apportarvi le necessarie modifiche, vale a dire specificare l’immagine e posizionarla nell’hard disk e dimensionare la memoria disponibile. QEMU, invece, permette di simulare una grande varietà di macchine basate su differenti processori e può essere utilizzato come emulatore o come macchina virtuale. Quando è utilizzato come emulatore, QEMU è in grado di eseguire sistemi operativi e programmi per una particolare macchina (ad esempio su una board ARM) o su una macchina differente (per esempio un PC). Come PearPC, anche QEMU effettua la traslazione dinamica delle istruzioni. Viceversa, quando è utilizzato come macchina virtuale, QEMU esegue il  codice target direttamente su un PC host. QEMU gestisce diversi processori, tra cui ARM, PowerPc, Intel 0x86 o Sparc. In modalità PowerPC, consente l’emulazione piena del processore in configurazione 32 bit inclusa la parte FPU, MMU e le istruzioni privilegiate. La soluzione su QEMU è quella che meglio risponde alle nostre esigenze. L’altra alternativa libera da costi cade sicuramente su GDB in configurazione simulatore o PSIM.

PSIM

PSIM è un programma che emula l’Instruction Set Architecture di un PowerPc. Occorre a questo punto soffermarsi su alcuni aspetti di tale processore. La pubblicazione “The PowerPC Architecture: A specification for a new family of RISC processors” descrive l’ISA (Instruction Set Architecture) di un PowerPC.  Leggendo il libro possiamo scoprire che l’architettura PowerPc è definita in tre livelli che corrispondono a tre ambienti di programmazione. Questa stratificazione dell’architettura fornisce una maggiore flessibilità al sistema, consentendo un maggior grado di compatibilità software tra un’ampia gamma di implementazioni.  I tre livelli sono definiti in questo modo:

» User Instruction Set Architecture (UISA): definisce il livello di architettura a cui il software user-level deve essere confor me. UISA specifica così l’insieme base delle istruzioni per l’utente, i registri a cui può accedere, le convenzioni per la memorizzazione degli operandi in virgola mobile e il modello delle eccezioni visto dall’utente. Si può accedere alle risorse definite a questo livello in modalità user. Inoltre, si può accedere ai registri di questo insieme sia da istruzioni user-level che da quelle supervisor-level. L’accesso ai registri può avvenire in modo esplicito, cioè inserendo  il registro nel codice dell’istruzione, o implicito, cioè come parte dell’esecuzione di un’istruzione.

» Virtual Environment Architecture (VEA): a questo livello sono definite delle funzioni addizionali che esulano dai requisiti software del livello utente. VEA descrive il modello della memoria in un ambiente in cui possono accedere molti dispositivi e definisce i servizi time base dal punto di vista dell’utente. Si può accedere alle risorse definite a questo livello in modalità user. In pratica, il modello descrive un modello di memoria visto dai processi software includendo il modello della cache e le istruzioni per il suo controllo, i registri per la gestione del time base e la corretta gestione del Symetric multi-processing. Così, a questo livello sono definite le istruzioni per il controllo del processore, come l’istruzione mftb utilizzata per leggere il valore dei registri time base definiti a livello architetturale VEA. Oppure le istruzioni di sincronizzazione della memoria, entrambe di livello user: eieio (funzione di ordinamento per gli effetti di load e store) e isync (che garantisce l’esecuzione del flusso delle istruzioni precedenti);

» Operating Environment Architecture (OEA): definisce le risorse di livello supervisore tipicamente richieste dal sistema operativo. OEA definisce  il modello di gestione della memoria, i registri supervisor level, i requisiti  di sincronizzazione e il modello delle eccezioni. Si può accedere alle risorse definite a questo livello esclusivamente in modalità supervisor. Questo livello architetturale include la struttura per la gestione della memoria, i registri speciali di livello supervisore  (quelli cioè non visibili a livello utente) e il modello di gestione delle eccezioni. Le implementazioni che sono conformi a OEA lo sono anche a UISA e VEA. Tutte le architetture PowerPC sono conformi a UISA, offrendo così compatibilità tra le applicazioni. Esistono invece varie versioni di OEA e VEA dipendenti dalle specifiche implementazioni dello standard PowerPC. PSIM implementa tutti i livelli previsti dall’ISA e, per ogni livello, include un ambiente di run-time simulato. È necessario configurare PSIM prima di iniziare  il processo di simulazione, a questo proposito il listato 4 mostra una parte delle opzioni. Con PSIM è possibile definire, per esempio, quale processore utilizzare nella simulazione, definire il contenuto dei registri o impostare la modalità SMP. Attualmente PSIM è distribuito insieme a GDB e per utilizzarlo è necessario configurare GDB con PSIM o scaricare una versione PSIM per il nostro target.

/#address-cells 0x1
/aliases
/options
/options/little-endian? false
/chosen
/chosen/stdin */openprom/pal
/chosen/stdout !/chosen/stdin
/chosen/memory */memory
/packages
/cpus
/cpus/cpu@0
/cpus/cpu@0/cpu-nr 0x0
/openprom
/openprom/init
/openprom/init/register
/openprom/init/register/0.pc 0x100
/openprom/init/register/sp 0x3feff0
/openprom/init/register/msr 0x02
/openprom/init/stack
/openprom/init/stack/stack-type “ppc-elf”
/openprom/options
/openprom/options/oea-memory-size 0x400000
/openprom/options/oea-interrupt-prefix 0x1
/openprom/options/smp 0x1
/openprom/options/env “oea”
/openprom/options/os-emul “bug”
/openprom/options/strict-alignment? false
/openprom/options/floating-point? true
/openprom/options/use-stdio? true
/openprom/options/model “604”
/openprom/options/model-issue 0xffffffff
/openprom/pal
/memory@0
/memory@0/reg 0 0x400000
/memory@0/available 0x300 0x3fd00
Listato 4 - Possibile configurazione di PSIM

3 Commenti

  1. Giovanni Di Maria Giovanni Di Maria 17 dicembre 2018
  2. Stefano Lovati Stefano Lovati 17 dicembre 2018

Scrivi un commento

EOS-Academy