Una interessante fonte di informazioni e' il progetto magic1, di sicuro l'avrete gia' visto perche' e' presente nelle cache google. E' un esempio concreto di implementazione a micro codice di una nuova architettura.@z80fannota che anche il suo autore ha previsto lo state_next nel micro codice.
Mai visto prima, ma sembra interessante, soprattutto per le riflessioni e spiegazioni dell'autore.
Caspita, il PowerPC 970 possiede quattro ALU, due FPU e due unità SIMD: goloso
Citazione da: cdimauro - 29 Gennaio 2014, 06:51:13Sì, il punto è come gestire tutto questo casinoquello che spaventa sono le alee sui dati e sul controllo,
Sì, il punto è come gestire tutto questo casino
se si capisce subito di che tipo di istruzione si tratta e se sono noti tutti i suoi parametri e' un conto,
se questi devono essere calcolati (p.e. EA complesso) iniziano a nascere problemi seri,
oltre al fatto che proprio con quei problemi di alee serve una discreta intelligenza per tenere occupati stadi replicati (p.e. 4 ALU) sopratutto con esecuzione fuori ordine.
per il context del segnali di datapath ho nel simulatore tutta sta robaCodice: [Seleziona] cpu_datapath.instruction = instruction; cpu_datapath.index = index_i; cpu_datapath.class = cpu_my_instruction_tab[index_i].class; cpu_datapath.size = instruction.opcode.size; cpu_datapath.rs1 = instruction.opcode.rs1; cpu_datapath.rs2 = instruction.opcode.rs2; cpu_datapath.rt1 = instruction.opcode.rt1; cpu_datapath.is_pre_EA = instruction.opcode.is_pre_EA; cpu_datapath.EA_ra = 0xdeadff00; /* n.a. at this step */ cpu_datapath.EA_in = 0xdeadff11; /* n.a. at this step */ cpu_datapath.EA_out = 0xdeadff22; /* n.a. at this step */ cpu_datapath.source1 = 0xdeadff44; /* n.a. at this step */ cpu_datapath.source2 = 0xdeadff55; /* n.a. at this step */ cpu_datapath.source3 = instruction.const32; cpu_datapath.mode_rt1 = instruction.opcode.mode_rt1; cpu_datapath.mode_rs1 = instruction.opcode.mode_rs1; cpu_datapath.mode_rs2 = instruction.opcode.mode_rs2; cpu_datapath.alu_opp = instruction.opcode.alu_opp; /* * default signals */ cpu_datapath.enable_branch = False; cpu_datapath.enable_update_rs2 = False; /* * instruction specific signals */ cpu_my_instruction_tab[index_i].context();l'obiettivo che mi ero dato inizialmente era di ridurre il + possibile le dimensioni delle instruction specific signals in modo da non dover introdurre altri stati nella fsm del sequenziatore, purtroppo non mi e' possibile ridurli ulteriormente per cui mi tocca prevedere stati specifici di ogni singola istruzione (o gestiti per classi di istruzioni)per gestirli.p.e.la BRA viene costruita e testa cosi' Codice: [Seleziona]private boolean_t test_bra(){ cpu_my_instruction_t instruction; uint32_t s1; uint32_t s2; uint32_t t1; uint32_t addr; uint32_t offset; boolean_t ans; /* * bra */ s1 = 2; /* use register s1 to get source1 */ s2 = 3; /* use register s1 to get source2 */ t1 = 1; /* use register t1 to put result */ addr = 0x20; offset = 0x20000000; instruction = instruction_default_get(); instruction.opcode.code = op_bra; instruction.opcode.size = opp_byte; instruction.opcode.rs1 = s1; instruction.opcode.rs2 = s2; instruction.opcode.rt1 = t1; instruction.opcode.mode_rs1 = is_reg_contents; instruction.opcode.mode_rs2 = is_immediate_data; instruction.opcode.mode_rs2 = is_reg_contents; instruction.opcode.mode_rt1 = is_reg_contents; instruction.opcode.is_pre_EA = True; instruction.opcode.is_pre_EA = False; cpu_softreg[0] = 0; /* register r0 is always equal to zero */ cpu_softreg[s1] = 1; /* rs condition */ cpu_softreg[s2] = addr; /* ra address */ instruction.const32 = offset; cpu.reg.pc = 0; cpu_softreg[reg_PC] = 0; cpu_exec(instruction); printf("bra("); printf("r%u=0x%lx", instruction.opcode.rs1, cpu_softreg[instruction.opcode.rs1]); printf(","); printf("r%u=0x%lx", instruction.opcode.rs2, cpu_softreg[instruction.opcode.rs2]); printf(")->"); printf("r%u=0x%lx", instruction.opcode.rt1, cpu_softreg[instruction.opcode.rt1]); printf(" PC=0x%lx\n", cpu_softreg[reg_PC]); if (cpu_softreg[reg_PC] == (addr + offset)) { ans = True; } else { ans = False; } return ans;}per come e' fatto il datapath giocando con instruction.opcode.mode_rs2 = is_immediate_data; instruction.opcode.mode_rs2 = is_reg_contents; instruction.opcode.is_pre_EA = True; instruction.opcode.is_pre_EA = False;si possono ottenere features diverse, pero' va istruito il context della fsm del sequenziatore dicendole esattamente cosa deve fare allo step tal dei tali
cpu_datapath.instruction = instruction; cpu_datapath.index = index_i; cpu_datapath.class = cpu_my_instruction_tab[index_i].class; cpu_datapath.size = instruction.opcode.size; cpu_datapath.rs1 = instruction.opcode.rs1; cpu_datapath.rs2 = instruction.opcode.rs2; cpu_datapath.rt1 = instruction.opcode.rt1; cpu_datapath.is_pre_EA = instruction.opcode.is_pre_EA; cpu_datapath.EA_ra = 0xdeadff00; /* n.a. at this step */ cpu_datapath.EA_in = 0xdeadff11; /* n.a. at this step */ cpu_datapath.EA_out = 0xdeadff22; /* n.a. at this step */ cpu_datapath.source1 = 0xdeadff44; /* n.a. at this step */ cpu_datapath.source2 = 0xdeadff55; /* n.a. at this step */ cpu_datapath.source3 = instruction.const32; cpu_datapath.mode_rt1 = instruction.opcode.mode_rt1; cpu_datapath.mode_rs1 = instruction.opcode.mode_rs1; cpu_datapath.mode_rs2 = instruction.opcode.mode_rs2; cpu_datapath.alu_opp = instruction.opcode.alu_opp; /* * default signals */ cpu_datapath.enable_branch = False; cpu_datapath.enable_update_rs2 = False; /* * instruction specific signals */ cpu_my_instruction_tab[index_i].context();
private boolean_t test_bra(){ cpu_my_instruction_t instruction; uint32_t s1; uint32_t s2; uint32_t t1; uint32_t addr; uint32_t offset; boolean_t ans; /* * bra */ s1 = 2; /* use register s1 to get source1 */ s2 = 3; /* use register s1 to get source2 */ t1 = 1; /* use register t1 to put result */ addr = 0x20; offset = 0x20000000; instruction = instruction_default_get(); instruction.opcode.code = op_bra; instruction.opcode.size = opp_byte; instruction.opcode.rs1 = s1; instruction.opcode.rs2 = s2; instruction.opcode.rt1 = t1; instruction.opcode.mode_rs1 = is_reg_contents; instruction.opcode.mode_rs2 = is_immediate_data; instruction.opcode.mode_rs2 = is_reg_contents; instruction.opcode.mode_rt1 = is_reg_contents; instruction.opcode.is_pre_EA = True; instruction.opcode.is_pre_EA = False; cpu_softreg[0] = 0; /* register r0 is always equal to zero */ cpu_softreg[s1] = 1; /* rs condition */ cpu_softreg[s2] = addr; /* ra address */ instruction.const32 = offset; cpu.reg.pc = 0; cpu_softreg[reg_PC] = 0; cpu_exec(instruction); printf("bra("); printf("r%u=0x%lx", instruction.opcode.rs1, cpu_softreg[instruction.opcode.rs1]); printf(","); printf("r%u=0x%lx", instruction.opcode.rs2, cpu_softreg[instruction.opcode.rs2]); printf(")->"); printf("r%u=0x%lx", instruction.opcode.rt1, cpu_softreg[instruction.opcode.rt1]); printf(" PC=0x%lx\n", cpu_softreg[reg_PC]); if (cpu_softreg[reg_PC] == (addr + offset)) { ans = True; } else { ans = False; } return ans;}
Citazione da: cdimauro - 29 Gennaio 2014, 18:42:16Non so cosa sono le aleesono condizioni critiche in cui i dati o i controlli si accavallano tra i vari stadi che cercano di processare in pipe istruzioni in parallelo.
Non so cosa sono le alee
Se ti studi il modello multi ciclo del MIPS in forma pipelined trovi esempi molto molto mirati che fanno vedere bene tutti i casini che ci sono e come venirne fuori. Alcune soluzioni sono semplici, altre di meno, il punto e' che in quel modello didattico e' tutto documentato e che quanto racconta non e' solo roba didattica ma faccende che purtroppo tocca risolvere nel real world (con altri casini al seguito)
Citazione da: cdimauro - 29 Gennaio 2014, 18:42:16Bisogna vedere cosa s'intende per complessoqualsiasi cosa che abbia bisogno di due dipendenze o di accessi in memoria e' complessa perché impiega più di uno stadio con problemi di dipendenza sul cammino dei dati.qualsiasi cosa che sia processabile in parallelo con tutte le informazioni che servono gia' presenti e senza alcun accesso alla memoria e' definita semplice, ovvero consumabile in uno solo stadio senza che serva alcun segnale di controllo con impatti sugli stati vicini, p.e. per fermarli per evitare alee.
Bisogna vedere cosa s'intende per complesso
Citazione da: cdimauro - 29 Gennaio 2014, 18:42:16Il simulatore esegue codice C o sbaglio?Il simulatore e' scritto in C, quello che vedi e' il come vengono descritti i segnali di datapath per ogni singola istruzione, nell'esempio vedi la bra condizionata, ovvero if (reg[rs]!=0) { PC=reg[ra]+imm32; } else { PC = PC + sizeof(instruction); }E' scritto in quel modo il simulatore perche' funge da bozza per un successivo simulatore a micro codice dove tutti i segnali che ora vedi con orizzonte temporale piatto avranno invece una profondità nel tempo, ovvero saranno attivati solo nello step giusto e di competenza, questo significa scrivere appunto micro codice, ovvero istruire in modo preciso le uscite dei vari stati che compongono la macchina a stati che dirige la baracca.
Il simulatore esegue codice C o sbaglio?
p.s.il simulatore ha messo in evidenza un primo casino da risolvere, ovvero sequenzializzando --(an) ha bisogno di un precalcolo di an--,
e ha bisogno di una successiva fase di aggiornamento del registro an, (an)++ non ha bisogno del precalcolo pero' deve aggiornare an, e la fase finale del precedente datapath prevedeva solo una reg write quindi ho dovuto aggiungere una seconda fase per aggiornare anche an, tutto questo porta all'aggiunta di due stadi, uno di calcolo pre_EA, l'altro di aggiornamento registro, oltre al segnale di di pre_EA e di update_ra
ho gia' una mini alu + MUX che si preoccupa dell'EACodice: [Seleziona] EA1 = cpu_datapath.source2; EA2 = cpu_datapath.source2 + cpu_datapath.source3;il mux ha per segnale di controllo cpu_datapath.is_pre_EAcio' rende possibile avere per EA- una costante immediata + contenuto registro (compreso R0 che e' sempre zero)- il contenuto di un registro + size- il contenuto di un registro - sizeun altro circuito poi si preoccupa di salvarsi EA2 per darlo in pasto al reg_pool se e' abilitato il segnale update_raovvero ho tutte le modalità di indirizzamento che volevo avere1) costante immediata 32bit2) a registro3) a registro + offset4) a registro pre incementato/decrementato5) a registro post decrementato/incrementatoe per ora mi fermo qui
EA1 = cpu_datapath.source2; EA2 = cpu_datapath.source2 + cpu_datapath.source3;
questo documento Altera racconta come fare moltiplicazioni veloci, anche in 3 colpi di clock.
Per il modello didattico del MIPS R1K, ma in generale per un approccio RISC purista, ti consiglereiCitazioneStruttura e progetto dei calcolatori - Patterson Hennessy, ZanichelliE' un book usato nei corsi di architetture1, lo trovi anche in italiano (ai mie tempi era solo in inglese), ci sono capitolo che partendo dal multi ciclo arrivano alla implementazione pipelined mettendo in evidenza casini e possibili soluzioni, inoltre ci sono capitolo che mostrano la macchina a stati che governa la baracca, come sintetizzarla, e come si arriva (o meglio come si e' arrivati in passato, per semplificarsi la vita) al micro codice.
Struttura e progetto dei calcolatori - Patterson Hennessy, Zanichelli
Citazione da: cdimauro - 29 Gennaio 2014, 23:50:47Qui, invece, stai scrivendo un simulatore, che è ben diverso, visto che deve riprodurre perfettamente tutti questi elementi.Altrimenti il rischio di pensare che a livello ISA si possa fare qualsiasi cosa, p.e. mov.l (r1)+,(r2)+,
Qui, invece, stai scrivendo un simulatore, che è ben diverso, visto che deve riprodurre perfettamente tutti questi elementi.
e' forte e rischia poi di non starci fisicamente in una implementazione semplice. Lavorando in retroazione, tra ISA e sua implementazione invece si vedono i casini e si corre subito ai ripari.L'obiettivo e' semplificare la baracca attorno al datapath!
Citazione da: cdimauro - 29 Gennaio 2014, 23:50:47Aspetta, l'aggiornamento del registro potresti farlo in parallelo a qualche altro stadio. Ad esempio nello stesso stadio che prevede il caricamento di un dato dalla memoria. Così risparmi un ciclo di clock.Il modello prevede che tutto il register pool sia aggiornato alla fine, stadio write_back, anticipare la scrittura e' possibile pero' costa un sacco di stati alla fsm per gestire la faccenda, molto + stati che quanti ne servano in uno stadio in coda al reg_pool write dove fondamentalmente si aggiunge una scrittura.Fino a che non approccio pipelined per me il problema non si pone, ovvero non ho alee, i problemi si farebbero sentire eccome appena si prova ad eseguire istruzioni in parallelo perché se nello stadio register pool read viene letto an_value=reg[an], e poi viene successivamente elaborato an_value+=offset questo valore verra' scritto nel register pool solo alla fine, nello stadio write_back reg[an]=an_value, il che significa che se una istruzione in coda a quella in esecuzione cerca di leggere an si troverà un valore non aggiornato, ovvero non addizionato di +offset.Per fare come suggerisci serve anticipare la write, questa faccenda pero' costa degli stati per gestire la scrittura, questi stati nello stadio write back ci sono gia', spostare la scrittura in altri stadi invece costa il doverli replicare.
Aspetta, l'aggiornamento del registro potresti farlo in parallelo a qualche altro stadio. Ad esempio nello stesso stadio che prevede il caricamento di un dato dalla memoria. Così risparmi un ciclo di clock.
In definitiva: fattibilissimo, ma va contro i miei obiettivi di ridurre al minimo gli stati della fsm che dirige la baracca.Penso che ci debba essere un compromesso, la coperta e' sempre corta: o ti accontenti di una cariola che consuma un colpo di clock in + per farti avere degli stati in meno nella fsm che la dirige, o cerchi di eliminare quel colpo di clock in più aumentando la complessità e gli stati della fsm.A me la scelta, sarebbe bello trovare la formula della moglie ubriaca con la botte piena
Domani ti rispondo, adesso mi compiaccio della parte di simulatore che tiene conto del dynamic bus sizing (1) e dalla generazione dei cicli bus distinguendo tra OPP_SIZE e BUS_SIZE, questa ultima e' figlia di come il device verso cui/da cui la CPU intende fare IO (push/pop, IO_read, IO_store) segnala il suo size pilotando i segnali {size0,size1} per dire che può accettare cicli asincroni {byte, word, long}; ne tengo conto per essere flessibile e sopratutto per considerare anche la casistica del bus error dovuto al controllo bad alignment quando la fsm del bus trova una addr inadeguato.
In quel caso completo il ciclo alzando una flag di exception che verra' poi presa in considerazione in due punti:1) nel registro exception cause, c'e' un bit apposito per il bus error di tipo bad alignment, piuttosto che device time out (se fallisce il dtack), piuttosto che hw failure se qualcuno lo dice esplicitamente2) a prescindere dalla causa se una sola flag di exception e' sollevata all'update del PC viene poi caricato EPC, ovvero si passa a 0x0000000 dove ha inizio una unica exception routine che a quel punto interroga il registro cause per capire cosa e' successo.Versione molto semplificata, per ora, anche perché un altro bel casino sono le exceptions, sopratutto come gestile. Sicuramente non voglio le fsm vettorizzate o autovettorizzate del 68K perché sono troppo complicate!La cosa forte e' che posso sfruttare una mask_cause associata a cause che mi permette di non fare alcun if per capire cosa e' successo, ovvero sfrutto la mask come offset per una cosa tipo bra mask_cause(EPC) -> EPC+=mask_causemask_cause spaia di poco, diciamo che mask_cause = cause << 8, pero' facendo in quel modo poi a EPC+mask_cause infilo una BRA verso la routine specifica caso per casoquindi il giro e' 1) EPC = 0x00000000 con cause=1,2,3,4,5,….2) jump EPC=EPC+(cause<<4)3) ….4) RTI
(1) l'ho preso dalla doc Motorola 68020, il MIPS non prevede una cosa del genere, quantomeno non la mera CPU, ovvero se ne deve occupare una logica esterna e segnalare alla CPU una hardware exception da mappare poi in qualche modo come bad alignment
Ah, ovviamente sotto exception il context switch e' istantaneo poiché servito dall'hw che in questo caso da taskID=0, quindi cambia del tutto l'indice di accesso ai registri, esattamente come nelle shadowed registers machines.