Esercizi di Microprogrammazione (Parte 1: MIC-1) Esercizio MIC-1.1 Supponendo di avere nei registri TOS e SP memorizzati dei numeri rappresentati in complemento a due, scrivere un programma Mic1 che memorizzi nel registro OPC il valore 2(A - B). Possibile Soluzione H = TOS H = SP = SP - H OPC = SP + H Esercizio MIC-1.2 Implementare l istruzione SIPUSH, che ha un comportamento simile alla BIPUSH, ma combina i 2 byte costanti che seguono il codice operativo in una costante a 16 bit che viene posta sullo stack. sipush1 SP = MAR = SP +1 // Aggiorna SP e prepara l indirizzo // di scrittura del risultato sipush2 PC = PC + 1; fetch // Legge il secondo byte (il primo è // già in MBR) sipush3 H = MBR << 8 // Carica il primo byte in H e //prepara lo spazio per il secondo sipush4 PC = PC + 1; fetch // Fetch della prossima istruzione sipush5 MDR=TOS=MBRU OR H;wr; goto Main1 // Completa la ricostruzione della // costante a 16 bit, scrive in TOS // e nella posizione corrente nello // stack Esercizio MIC-1.3 Si consideri il seguente codice per una ipotetica nuova microistruzione: i1 i2 i3 MDR=TOS MAR=SP=SP+1; wr MAR=SP=SP+1;wr; goto Main1 a) Quale operazione svolge? 1
b) Si consideri ora il seguente frammento di codice e si dica se consente di ottenere lo stesso risultato: i1 MAR=SP; rd i2 MAR=SP=SP+1; wr // Attenzione: scrivo il NUOVO valore di // MDR (quello // prodotto da i1), dato che la read // termina durante il secondo ciclo e // comunque prima della write // (non sto usando MDR per il salvataggio // nei registri, quindi non devo // attendere un ciclo a vuoto per // utilizzare il valore letto in i1) i3 MAR=SP=SP+1;wr; goto Main1 Soluzione a) La microistruzione crea due copie addizionali dell elemento in cima allo stack, aggiornando lo stack pointer di conseguenza: DUP DUP b) Il risultato ottenuto è lo stesso, al costo di una lettura in più dalla memoria, che è di fatto inutile dato che l elemento da replicare è già contenuto in TOS Esercizio MIC-1.4 (Tanenbaum 4.2) Nel MIC-1 ci vuole 1 ns per preparare MIR, 1 ns per mettere il contenuto di un registro sul bus B, 3 ns per far funzionare l'alu e lo shifter, e 1 ns perchè i risultati ritornino ai registri. La larghezza dell'impulso di clock è di2ns. Questa macchina può funzionare a 100 MHz? E a 150 MHz? Soluzione: Il data path occupa 6 ns così ripartiti: - 1 ns per preparare MIR; - 1 ns per mettere il contenuto di un registro sul bus B; - 3 ns per far funzionare l'alu e lo shifter; - 1 ns perchè i risultati ritornino ai registri. Ai 6 ns del data path bisogna aggiungere i 2 ns della larghezza dell'impulso di clock. Quindi, per far funzionare correttamente la macchina, il periodo del clock deve essere superiore a 8 ns. A 100 MHz, il periodo del clock è: T = 1/(100*10^6) = 10 nsec ed è quindi sufficiente a far funzionare correttamente la macchina. A 150 MHz, il periodo del clock è: T = 1/(150*10^6) = (1/1.5) *10^(-8) = 6,667 ns e non è sufficiente a far funzionare correttamente la macchina. 2
Esercizio Mic1.5 (Tanenbaum 4.11 ) Quanto tempo occorre (espresso in ns) perchè una macchina MIC-1 con clock a 200 MHz esegua l'istruzione JAVA: i = j + k L'istruzione Java i = j + k corrisponde alle seguenti istruzioni IJVM : ILOAD j ILOAD k IADD ISTORE i Vedendo poi le istruzioni mic1 di cui sono composte queste istruzioni IJVM si può notare che: ILOAD è formato da 5 istruzioni mic1 IADD è formato da 3 istruzioni mic1 ISTORE è formato da 6 istruzioni mic1 Quindi il codice sovrastante è costituito da 5 + 5 + 3 + 6 = 19 istruzioni mic1, a cui vanno aggiunte altre 4 istruzioni. Queste vanno aggiunte perchè prima di eseguire un' istruzione IJVM si deve eseguire l'istruzione mic1 dell'indirizzo Main1 (cioè PC = PC + 1; fetch; goto (MBR) ) Il numero di istruzioni mic1 totale da eseguire è quindi 19 + 4 = 23 Sapendo che il clock è di 200 Mhz, la durata di un impulso di clock è 5 nsec. Quindi, poichè ogni istruzione mic1 viene eseguita in un ciclo di clock, il tempo per eseguire i = j + k è di 5*23 = 115 nsec Esercizio Mic-1.6 (Tanenbaum 4.14) L istruzione ISHR (arithmetic shift right integer) esiste in JVM ma non in IJVM. Essa utilizza i due valori posti sulla cima dello stack sostituendoli con un valore singolo, il risultato. La parola che si trova al secondo posto in cima allo stack è l operando da spostare. Il suo contenuto viene spostato verso destra di un valore che si trova fra 0 e 31 inclusi, a seconda del valore dei 5 bit meno significativi della parola in cima allo stack (gli altri 27 bit della parola in cima allo stack vengono ignorati). Il bit di segno viene ripetuto sulla destra per tanti bit quanti sono i bit da spostare. L opcode per ISHR è 122 (0x7a) a) Qual è l operazione aritmetica corrispondente allo spostamento e destra di 2 bit? b) Estendere il microcodice di MIC-1 per eseguire questa istruzione in IJVM Traccia di possibile soluzione a)l'operazione aritmetica equivalente allo shift a destra di 2 bit di un registro (per esempio H) è il quoziente della divisione H / 4 b) Il microcodice MIC-1 per l''istruzione ISHR è : // Creo in H la maschera con i 5 bit meno significativi a 1 3
122: ISHR1 H = -1 // H = 0xFFFFFFFF ISHR2 H = H << 8 // H = 0xFFFFFF00 ISHR3 H = H >> 1 // H = 0xFFFFFF80 ISHR4 H = H >> 1 // H = 0xFFFFFFC0 ISHR5 H = H >> 1 // H = 0xFFFFFFE0 ISHR6 H = H' // H = 0x0000001F //H' sta per "H negato" ISHR7 MAR = SP; rd // Preparo la lettura del primo // operando ISHR8 MAR = SP = SP - 1; rd // Preparo la lettura del secondo // operando ISHR9 OPC = H AND MDR // ignora i 27 bit più //significativi di MDR (che // ora contiene l operando da //spostare) ISHR10 Z = OPC; if (Z) goto ISHR13; else goto ISHR 11 ISHR11 MDR = MDR >> 1 // Ciclo di shift aritmetici a dx // in base al contanuto di OPC ISHR12 OPC = OPC - 1; goto ISHR10 ISHR13 TOS = MDR; wr; goto Main1 Nella soluzione è possibile eliminare una microistruzione, ovvero quella relativa alla lettura della prima parola contenuta nella stack (riga ISHR7) in quanto questa è già contenuta nel registro TOS. Bisogna però modificare la successiva operazione di AND: la stessa operazione dovrà essere effettuata tra H e il registro TOS (OPC=H AND TOS). Per il resto resta tutto uguale. Soluzione alternativa Il codice operativo di ciascuna istruzione corrisponde all indirizzo della prima microistruzione e nel caso di ISHR tale indirizzo è 122. A tale indirizzo si salta dopo la fase di fetch effettuata in Main1: Main1: PC=PC+1; fetch; goto(mbr) 122:ISHR1... // Qui MBR conterrà il valore 122 fino alla fine del ciclo // e solo alla fine del ciclo viene aggiornato con il contenuto della nuova //cella puntata da PC Possiamo sfruttare questa caratteristica per costruire la maschera di bit in modo più elegante, ottenendo nel contempo una riduzione nel numero complessivo di microistruzioni: 122: ISHR1 H = MBRU >>1 // H contiene 61 (Shift a dx aritmetico di un //valore positivo) ISHR2 H = H >> 1 // H contiene 30 ISHR3 H = H + 1 // H contiene 31 ISHR4 MAR = SP = SP 1; rd // Preparo la lettura del secondo //operando ISHR5 OPC = H AND TOS // Applica la maschera al primo operando e //salva il risultato in OPC ISHR7 Z = OPC; if (Z) goto ISHR10; else goto ISHR8 ISHR8 MDR = MDR >> 1 ISHR9 OPC = OPC - 1; goto ISHR7 ISHR10 TOS = MDR; wr; goto Main1 // Ciclo di shift aritmetici a dx // in base al contenuto di OPC 4
Lo svantaggio di questo approccio è la dipendenza dal codice operativo (se cambia non funziona più nulla). Esercizio Mic-1.7 (Tanenbaum 4.17) Scrivere il microcodice per MIC-1 per implementare l istruzione IJVM POPTWO. Questa istruzione toglie due parole dalla cima dello stack. Possibile soluzione: POP2.1 SP=SP-1 // Sposta lo stack pointer una prima volta POP2.2 MAR=SP=SP-1;rd // Sposta lo stack pointer la seconda volta e // legge il contenuto POP2.3 POP2.4 TOS=MDR;goto Main1 // Aggiorna il contenuto di TOS Soluzione alternativa: POP2.1 SP=SP-1; goto pop1 // Riusa il codice esistente di POP 5
Esercizi di Microprogrammazione (Parte 2: MIC-2) Esercizio Mic-2.1 Scrivere il microcodice per MIC-2 per implementare l istruzione IJVM IMAX. Questa istruzione consuma le due parole in cima alla pila e vi deposita il valore del più grande tra i due (la parola A è sulla cima della pila, B è nella posizione successiva). Possibile soluzione: 1. MAR=SP=SP-1;rd // Prepara la lettura del secondo // operando e riposiziona lo stack // pointer 2. // ciclo di attesa (per caricare MDR) 3. H=TOS-MDR; if (N) goto 5 // A>B? 4. MDR=TOS;wr;goto(MBR1) // A>B: Scrivo A in cima alla pila 5. TOS=MDR;goto(MBR1) // A<=B Scrivo B in cima alla pila e // aggiorno TOS Esercizio Mic-2.2 Scrivere il microcodice per MIC-2 per implementare le istruzioni IJVM ISET 32-bit-mask ICLEAR 32-bit-mask L istruzione ISET istruzione utilizza una maschera a 32 bit che segue il codice operativo e applica la maschera all elemento che si trova sulla cima dello stack, forzando a 1 i bit che sono 1 nella mask e lasciando intatti gli altri. L istruzione ICLEAR utilizza una maschera a 32 bit che segue il codice operativo e applica la maschera all elemento che si trova sulla cima dello stack, azzerando i bit che sono 1 nella mask e lasciando intatti gli altri. Possibile soluzione: a) ISET 1. H=MBR2U<<8 // Carica in H la parte alta della maschera //(primi 16 bit che seguono il codice // operativo) 2. H=H<<8 3. H=H or MBR2U // Carica in H la parte bassa della maschera 4. MDR=TOS=TOS or H // Applica la maschera al valore sulla 6
// cima dello stack 5. MAR=SP;wr;goto(MBR1) // Riscrive il nuovo valore sulla cima // dello stack b) ICLEAR 1. H=MBR2U<<8 // Carica in H la parte alta della maschera //(primi 16 bit che seguono il codice // operativo) 2. H=H<<8 3. H=H or MBR2U // Carica in H la parte bassa della maschera 4. H = not H //Complementa tutti i bit della maschera (per azzerare i bit a 1 nella maschera iniziale devo creare una nuova maschera con i bit a zero nelle stesse posizioni e poi applicare la maschera con una AND 5. MDR=TOS=TOS AND H // Applica la maschera 6. MAR=SP;wr;goto(MBR1) // Riscrive il nuovo valore sulla cima // dello stack Esercizio Mic-2.3 Scrivere il microcodice per MIC-2 per implementare l istruzione IJVM IXOR. Questa istruzione effettua lo XOR logico fra gli operandi a e b che si trovano nelle prime due posizioni dello stack (Si ricorda che a XOR b = a and (not b) or (not a) and b). Possibile soluzione: Esistono diverse soluzioni possibili, che si differenziano in base al metodo scelto per calcolare lo XOR. In particolare, una implementazione economica dell operazione XOR si basa sulla proprietà che a xor b = (a or b) and not (a and b). 1. MAR=SP=SP-1;rd // Preparo il nuovo valore per SP e leggo il // secondo operando 2. 3. OPC=TOS or MDR // OPC = a or b 4. H=TOS and MDR // H = a and b 5. H=not H // H = not (a and b) 6. MDR=TOS=OPC and H;wr; goto(mbr1) // Scrivo il risultato il TOS e //nella nuova cima della pila 7
Esercizio Mic-2.4 Scrivere l istruzione IJVM MIC-2 IFGREQ label che consente di saltare all etichetta indicata dopo il codice operativo se l elemento in cima alla pila è 0. Possibile soluzione L implementazione è una variante della IFLT offset (cambia solo l ultima istruzione) 1. MAR=SP=SP-1;rd // Aggiorno lo stack pointer 2. OPC = TOS // Salvo in OPC il valore da controllare 3. TOS = MDR // Aggiorno il contenuto di TOS 4. N = OPC; if (N) goto F else goto T // Se OPC è negativo non salto dove F e T sono etichette nel microprogramma standard di MIC-2: T H = PC 1; goto goto2 // Prepara il registro H F H = MBR2 // Legge il contenuto di MBR2 per // scartarlo F2 goto(mbr1) goto1 H = PC 1 goto2 PC = H + MBR2 goto3 goto4 goto(mbr1) // Carica in H l indirizzo dell istruzione // Somma l offset e scrive il PC // Attesa per il fetch del nuovo opcode Esercizio Mic-2.5 IMUL Implementazione per mic-2. Il moltiplicando si trova nella penultima posizione nello stack, mentre il moltiplicatore è sulla cima dello stack. Soluzione 1 (il risultato è corretto solo se il moltiplicatore è positivo!!) 1. MAR=SP=SP-1;rd // Prepara il caricamento in MDR del // moltiplicando 2. OPC=0 //Azzera il risultato 3. Z=TOS; if(z) goto 7 // Se moltiplicatore==0 ho finito 4. N=TOS; if(n) goto 8 // Se moltiplicatore negativo goto 8 5. OPC=OPC+MDR // Somma il moltiplicando al risultato 6. TOS=TOS-1; if (Z) goto 7 else goto 5 // Decrementa il moltiplicatore. Salta a 7 se 0 altrimenti a 5 7. MDR=TOS=OPC;wr;goto(MBR1) // Scrive il risultato finale nella cima dello stack e aggiorna TOS 8. OPC=OPC+MDR // Somma il moltiplicando al risultato 9. TOS=TOS+1; if (Z) goto 7 else goto 8 8
// Incrementa il moltiplicatore. Se è 0 ho finito (salta a 7) altrimenti salta a 8. Problemi dell algoritmo proposto: a) Prestazioni: se mem(sp-1) è 0 facciamo un ciclo di somme nulle! b) Se c è un overflow non viene segnalato. c) Il risultato è corretto soltanto se il moltiplicatore è positivo. Consideriamo separatamente i diversi casi: 1. Moltiplicando e moltiplicatore positivi: il risultato è corretto 2. Moltiplicando e moltiplicatore negativi: E corretto procedere incrementando il moltiplicatore fino a zero, ma il risultato finale ha il segno negativo e non positivo come invece dovrebbe essere. 3. Moltiplicando positivo e moltiplicatore negativo e: Il risultato ha il segno positivo (sbagliato: dovrebbe essere negativo) 4. Moltiplicando negativo e moltiplicatore positivo: Il risultato è corretto Per risolvere il problema c) si può procedere nel modo seguente: 1. Se il moltiplicatore è positivo si procede come prima 2. Se il moltiplicatore è negativo si cambia segno al moltiplicando e si procede come prima. Così facendo a. Se il moltiplicando era positivo si ricade nel caso 4 (- x -) e si ottiene un risultato negativo, che è corretto b. Se il moltiplicando era negativo (- x -) si ricade nel caso 3 (+ x -) ottenendo un risultato positivo, ancora corretto. Il nuovo microprogramma è il seguente: 1. MAR=SP=SP-1;rd // Carica in MAR il moltiplicando 2. OPC=0 //Azzera il risultato 3. Z=TOS; if(z) goto 7 // Se moltiplicatore==0 ho finito. 4. N=TOS; if(n) goto 4bis else goto 5 4bis. MDR=-MDR // Cambia segno al moltiplicando. Con // MIC-1 avremmo dovuto scrivere due // istruzioni separate: H=MDR e MDR=-H 5. OPC=OPC+MDR // Somma il moltiplicando al risultato 6. TOS=TOS-1; if (Z) goto 7 else goto 5 // Decrementa il // moltiplicatore. Salta //a fine se 0 altrimenti a 5 7. MDR=TOS=OPC;wr;goto(MBR1) // Scrive il risultato finale nella cima dello stack e aggiorna TOS 8. OPC=OPC+MDR // Somma il moltiplicando al risultato 9. TOS=TOS+1; if (Z) goto 7 else goto 8 // Incrementa il moltiplicatore. Se è 0 ho finito (salta a 7) altrimenti salta a 8. 9
Esercizio Mic-2.6 Scrivere del microcodice Mic-2 che interpreti l'istruzione NOEXECZ che vogliamo aggiungere ad IJVM. Si suppone che le 10 istruzioni che seguono NOEXECZ in ogni programma siano tutte istruzioni senza argomenti (cioe' formate da un solo byte, il codice operativo (come IADD, per intenderci) tra cui c'e' almeno un'istruzione che ha il codice operativo che inizia con un 1. La semantica di NOEXECZ e' la seguente: salta tutte le prossime istruzioni che precedono la prima il cui codice operativo inizia con un 1 e salta anche quest'ultima. In altre parole l'istruzione che dovra' essere eseguita dopo NOEXECZ e' quella successiva alla prima istruzione dopo NOEXECZ che ha il codice operativo che inizia con un 1. Possibili soluzioni Soluzione NON corretta noexecz1 noexecz2 N=MBR1; if (N) goto(mbr1) goto noexecz1 La soluzione non è corretta perchè in questo caso non si salta anche l istruzione il cui codice operativo comincia per 1. Possibile soluzione corretta noexecz1 noexecz2 noexecz2 N=MBR1; if (N) goto noexecz2 else goto noexecz3 goto(mbr1) goto noexecz1 Notare come il test viene effettuato su MBR1 ( N=MBR1) e non su MBR1U (N=MBR1U). Considerazioni: Il codice scritto non fa alcun controllo per stabilire se nelle prossime 10 istruzioni effettivamente una ha un codice operativo che inizia per 1. Se tale istruzione non è presente il microcodice continua a scandire la memoria senza mai fermarsi. Per rendere un po più robusta l implementazione si potrebbe inserire un contatore inizializzato a 10. Se nelle prossime 10 istruzioni non è presente una istruzione con il codice operativo che inizia per 1 si salta semplicemente all undicesima. noexecz0 goto inizializza noexecz1 Z=H; if (Z) goto(mbr1) noexecz1 N=MBR1; if (N) goto noexecz2 else goto noexecz3 noexecz2 goto(mbr1) noexecz3 H = H 1; goto noexecz2 inizializza H=1 H << 8 // H = 256 H >> 1 // H = 128 H >> 1 // H = 64 H >> 1 // H = 32 10
H >> 1 // H = 16 H >> 1 // H = 8 H = H + 1 // H = 9 H = H + 1 // H = 10 goto noexecz1 Ovviamente l inizializzazione del registro H si può fare in altro modo (magari più efficiente ed elegante di questo) Esercizio Mic-2.7 (Tanenbaum 4.12) Quanto tempo occorre (espresso in ns) perchè una macchina MIC-2 con clock a 200 MHz esegua l'istruzione JAVA: i = j + k Basandosi su questo calcolo, quanto tempo impiegherebbe un programma che richiede per funzionare 100 sec su Mic-1 se lo si eseguisse su Mic-2? Soluzione L'istruzione Java i = j + k corrisponde alle seguenti istruzioni IJVM : ILOAD j ILOAD k IADD ISTORE i Vedendo poi le istruzioni mic2 di cui sono composte queste istruzioni IJVM si può notare che: ILOAD è formato da 3 istruzioni mic2 IADD è formato da 3 istruzioni mic2 ISTORE è formato da 5 istruzioni mic2 Quindi il codice sovrastante è costituito da 3 + 3 + 3 + 5 = 14 istruzioni mic2 Sapendo che il clock è di 200 Mhz, la durata di un impulso di clock è 5 nsec. Quindi, poichè ogni istruzione mic2 viene eseguita in un ciclo di clock, il tempo per eseguire i = j + k è di 5*14 = 70 nsec. Quindi avendo un programma che viene eseguito su mic1 in 100 nsec, in mic2 sarà eseguito in (100*70)/115 = 60,8 nsec (ricordando che 115 nsec è il tempo ottenuto dall'esercizio Tanenbaum 4.11) 11
Esercizio Mic-3 (Tratto da Tanenbaum 4.18) Implementare una nuova istruzione JVM DLOAD x. Tale istruzione usa un indice di 1 byte che segue il codice operativo e mette la variabile locale indicata dall indice e quella seguente sullo stack. 1. Definire il microcodice per questa nuova istruzione per la macchina MIC-1 e per la macchina MIC-2 e confrontare i tempi di esecuzione nelle due versioni. 2. Si implementi il microcodice per l esecuzione della istruzione DLOAD sulla macchina MIC-3 Possibile soluzione a) Microcodice per la macchina MIC-1 dload1 H = LV // salva LV in H dload2 H = MAR = H + MBRU; rd // Calcola l indirizzo della prima variabile locale //e carica il valore in MDR dload3 H = H + 1; // Scrive in H l indirizzo della seconda variabile // locale dload4 MAR = SP = SP + 1; wr // Scrive il valore della variabile locale nella //nuova cima dello stack dload5 MAR = H ; rd // Carica in MAR l indirizzo della variabile locale //successiva e richiede la lettura del suo valore dload6 PC = PC + 1; fetch // Prepara la scrittura della seconda variabile // sulla nuova cima dello stack. dload5 MAR = SP = SP + 1; wr // Scrive la seconda variabile // sulla nuova cima dello stack. dload6 TOS = MDR; goto (MBR1)// Aggiorna il contenuto di TOS e continua //con la prossima istruzione. b) Microcodice per la macchina MIC-2 dload1 H = MAR = LV + MBR1U; rd // Calcola l indirizzo della prima variabile locale //e carica il valore in MDR dload2 H = H + 1; // Scrive in H l indirizzo della seconda variabile // locale dload3 MAR = SP = SP + 1; wr // Scrive il valore della variabile locale nella //nuova cima dello stack dload4 MAR = H ; rd // Carica in MAR l indirizzo della variabile locale //successiva e richiede la lettura del suo valore dload5 MAR = SP = SP + 1 // Prepara la scrittura della seconda variabile // sulla nuova cima dello stack. dload6 TOS = MDR; wr; goto (MBR1)// Aggiorna il contenuto di TOS e continua //con la prossima istruzione. Confronto: L esecuzione dell istruzione DLOAD <x> sulla macchina MIC-1 richiede 8 cicli macchina, contro i 6 necessari per l esecuzione sulla machina MIC-3 12
c) Microcodice per la macchina MIC-3 H=MAR=LV+MBR1U ;rd H=H+1 MAR=SP=SP+1;wr MAR=H;rd MAR=SP=SP+1 TOS=MDR;wr;goto(MBR1) 1 A=MBR1U; B=LV 2 C=A+B 3 H=MAR=C; rd 4 MDR = mem B=H 5 C=B+1 B=SP 6 H=C C=B+1 B=H 7 MAR=SP=C; wr C=B B=SP 8 mem=mdr MAR=C;rd C=B+1 9 MDR=mem MAR=SP=C 10 B=MDR 11 C=B 12 TOS=C;wr 13 mem=mdr; goto MBR1 Ipotizzando che sulla macchina MIC-3 il ciclo di clock sia tre volte più veloce rispetto a MIC-2 si ha: MIC-3: 13 cicli di clock MIC2: 6 x 3 = 18 cicli di clock Guadagno = NcicliMIC2/NcicliMIC3 = 18/13 = 1,38 13