View Full Version : [Asm] Progettare una architettura di processore
Salve a tutti!
Apro questo thread per discutere su una nuova architettura di processore, costruita partendo da zero, definendo il set di istruzioni, i registri, le modalità ecc...
Quando avremo deciso queste cose (o anche durante), possiamo implementare l'architettura usando l'ottimo simulatore Hades (http://tams-www.informatik.uni-hamburg.de/applets/hades/webdemos/index.html), così da poter far girare qualche programma che scriveremo.
Io ho già in mente qualche idea, tipo un' architettura RISC con 16 registri, ma con alcune istruzioni comode e tipiche di un CISC in modo da migliorare la densità del codice. Sono però indeciso sul formato delle istruzioni, se comprimerlo a 16 bit o lasciarlo largo a 32 bit e metterci più funzionalità.
Fatemi sapere cosa ne pensate, chiunque più dire la sua, anche se non è un esperto; siamo qui anche per imparare ;)
cdimauro
18-06-2010, 22:00
Io da tempo sto meditando sui dettagli di un'ISA CRISP a 64 bit fortemente ispirata ai Motorola 68000, ma di realizzarne il circuito non se ne parla proprio: sarebbe troppo complesso come progetto.
I RISC, poi, non riesco a farmeli digerire. :stordita:
Io mi iscrivo... non so quanto riuscirò a fare, più che altro vorrei imparare qualcosa :D
Io da tempo sto meditando sui dettagli di un'ISA CRISP a 64 bit fortemente ispirata ai Motorola 68000, ma di realizzarne il circuito non se ne parla proprio: sarebbe troppo complesso come progetto.
I RISC, poi, non riesco a farmeli digerire. :stordita:
Bene, se hai voglia, sarei interessato a sentire quali specifiche hai pensato. Per il circuito, non bisogna farlo a livello di transistor, il simulatore ci mette a disposizione molti blocchi già pronti (come alu, registri, rom, ram...), quindi potrebbe essere un po' più semplice. Poi se è veramente così complesso, almeno avremo discusso su una possibile architettura, che è già interessante per se.
Io mi iscrivo... non so quanto riuscirò a fare, più che altro vorrei imparare qualcosa :D
Ecco, questo è lo spirito ;)
cdimauro
19-06-2010, 10:01
Bene, se hai voglia, sarei interessato a sentire quali specifiche hai pensato. Per il circuito, non bisogna farlo a livello di transistor, il simulatore ci mette a disposizione molti blocchi già pronti (come alu, registri, rom, ram...), quindi potrebbe essere un po' più semplice. Poi se è veramente così complesso, almeno avremo discusso su una possibile architettura, che è già interessante per se.
OK, riorganizzo le mie idee e mi metto a descrivere l'architettura interna e la tabella degli opcode con le istruzioni mappate.
Mi ci vorranno un po' di giorni, comunque.
OK, riorganizzo le mie idee e mi metto a descrivere l'architettura interna e la tabella degli opcode con le istruzioni mappate.In cosa consistono queste due operazioni?
So giusto cosa sono gli opcode.
OK, riorganizzo le mie idee e mi metto a descrivere l'architettura interna e la tabella degli opcode con le istruzioni mappate.
Mi ci vorranno un po' di giorni, comunque.
Prenditi pure tutto il tempo che vuoi, grazie della partecipazione :)
In cosa consistono queste due operazioni?
So giusto cosa sono gli opcode.
Intende dire che deve definire quanti/quali registri, la loro larghezza, il datapath... , e poi descrivere le istruzioni, cioè il loro formato, i modi di indirizzamento ecc...
Avendo studiato un'unica architettura(MIPS), non penso di poter fare molto. Qualsiasi cosa io dica, penso sarebbe influenzata da quell'unico, solo punto di vista.
cdimauro
19-06-2010, 16:23
E io che sono influenzato dal 68000, allora? :D
Avendo studiato un'unica architettura(MIPS), non penso di poter fare molto. Qualsiasi cosa io dica, penso sarebbe influenzata da quell'unico, solo punto di vista.
E io che sono influenzato dal 68000, allora? :D
Appunto, siamo qui per imparare, nessuno può conoscere tutti i processori esistenti, ognuno argomenta su quello che sa, e coglie l'occasione per imparare quello che non sa ;)
lupoxxx87
19-06-2010, 18:18
un doppio array di registri a 32 bit, in modo da poter operare sui 64 non lo mettiamo in conto ?
secondo me tra 32 e 16, il 16 è più snello e leggero...ma se prendiamo in considerazione tutte e 3 le opzioni, un idea sulla struttura di un 64 io ce la butterei giù
un doppio array di registri a 32 bit, in modo da poter operare sui 64 non lo mettiamo in conto ?
secondo me tra 32 e 16, il 16 è più snello e leggero...ma se prendiamo in considerazione tutte e 3 le opzioni, un idea sulla struttura di un 64 io ce la butterei giù
Certo, si può fare tranquillamente: un vantaggio di quel simulatore è che si possono modificare i parametri di registri/alu/memorie in modo da avere qualsiasi larghezza di parola. La dimensione non è una complicazione, se vuoi possiamo anche farlo a 128 bit :D
Da crasso ignorante della materia, io mi pongo alcune domande. A partire proprio dai registri.
A volerla fare semplice, i registri altro non sono che delle aree di memoria ad accesso *estremamente* veloce (direi immediata). Quindi, anziché andare a definire singolarmente i registri, la mia idea era quella di definire direttamente un'area di memoria ad accesso estremamente veloce, e di indirizzarla con una serie di codici specifici.
Poniamo, che so, di avere un'area di 128 byte come "memoria di registro". Possiamo usarla come suddivisa in 16 registri a 64 bit, oppure 32 registri a 32 bit, oppure come 64 registri a 16 bit, o come 128 registri a 8 bit. O anche 8 registri a 128 bit...
Con 8 bit possiamo completamente indicare ogni singola porzione di memoria di quest'area. Ad esempio, per indicare un registro a 8 bit, lo indichiamo mettendo a 1 il bit 0 ed usando gli altri 7 per indicare quale dei 128 registri a 8 bit usiamo.
Per i registri a 16 bit, poniamo i bit 0 a 0 ed il bit 1 ad 1, usando gli altri 6 bit per indicare quale dei registri a 16 bit stiamo usando. E così via...
Esempio:
0011010 1 indica il registro a 8 bit numero 26 (offset 25 dell'area dei registri)
011000 10 indica il registro a 16 bit numero 24 (offset 47)
0110 1000 indica il registro a 64 bit numero 6 (offset 47, come sopra)
Pare una minchiata? O troppo complesso?
cdimauro
19-06-2010, 22:51
Mi sembra estremamente complicato, difficilmente implementabile e poco scalabile. :D
Puoi scendere più nel dettaglio? Altrimenti non capisco nulla... :boh:
La mia idea (che in realtà ho da quando ero un ragazzino che smanettava con l'assembly) è mutuata da quello che si fa con i registri AX, BX, CX e DX dell'x86, che vengono automaticamente suddivisi in due registri a 8 bit: AH, AL, poi BH e BL e così via.
Rendere più generale questo concetto è così complicato?
cdimauro
19-06-2010, 23:18
Richiede un decodificatore per recuperare dimensione e posizione del registro da utilizzare, e già questo è un grosso impedimento (tutt'altra cosa è chiedere: "voglio AH", oppure "voglio RAX"; si tratta di scelte "immediate" perché già "decodificate").
Inoltre, fatto questo, serve logica addizionale per selezionare e mascherare opportunamente le parti interessate.
Richiede un decodificatore per recuperare dimensione e posizione del registro da utilizzare, e già questo è un grosso impedimento (tutt'altra cosa è chiedere: "voglio AH", oppure "voglio RAX"; si tratta di scelte "immediate" perché già "decodificate").Ma a dire il vero anche queste sarebbero già "decodificate", solo che anziché una manciata di codifiche qui ce ne sono 256 (anzi, anche qualcuna di meno), ognuna già pronta con dimensione e posizione del registro.
Il mio discorso si riferiva al fatto che bastano 8 bit per indicare tutti i registri possibili.
lupoxxx87
20-06-2010, 00:18
provo a spiegare la mia idea sui registri 32/64 bit.
adesso provo ad esporre un esempio supponendo i registri a 4bit, che possono essere sdoppiati ad 8bit, per ovvie semplicità di calcolo.
supponiamo di avere un array di 8 registri, e immaginiamolo con la seguente struttura, a "doppia colonna":
riga0: R0: xxxx R1: xxxx
riga1: R2: xxxx R3: xxxx
riga2: R4: xxxx R5: xxxx
riga3: R6: xxxx R7: xxxx
• R0 lo riserviamo come consuetudine come accumulatore.
• R1 lo chiamiamo "registro di sdoppiamento", ed ogni bit del registro è un valore di verità riferito ad una delle righe della struttura a doppia colonna.
il bit più significativo se sarà 0 indicherà che la modalità "ad estensione di registri" è disattivata, 1 altrimenti;
se l'i-esimo bit è 1, i due registri della riga associata andranno letti in sequenza come un unico registro da 8 bit, se invece è 0 si potrà avere accesso ad entrambi i registri contententi valori a 4 bit.
con semplici (almeno in linea teorica) cambi di notazione si può risolvere il tutto con degli shift o degli or/xor sui bit dei registri.
se tutti i registri sono implementati come un unico array di bit, la lunghezza di due registri sequenziali o di uno solo ha complessità equivalente, e una richiesta su un determinato registro equivale solo ad operare su n bit (nell'esempio qui sopra 4) consecutivi a partire da un determinato bit, ben identificabile...
che dite ?!
• R0 lo riserviamo come consuetudine come accumulatore.Ecco, questa è una cosa che non ho mai ben capito dei processori, almeno quelli moderni. Cioè, dover attribuire per forza un "compito" specifico ai registri.
Come i vecchi AX, BX, CX e DX che erano "Accumulatore", "Base", "Contatore" e "Dati", in un modo che aiutava anche a ricordare il compito con le iniziali. Poi c'erano SI e DI, e ancora SP, BP, IP ed infine DS, ES, SS e CS. Tutti con compiti specifici, tanto che alcune istruzioni funzionavano con alcuni registri e con altri no. Mica si può fare MOV [AX],DS ...
Ma in fondo sono tutti registri a 16 bit! Né più né meno. Capisco che, un tempo, per semplificare la complessità del processore, fosse necessario operare in questi termini, ma oggi penso che si siano abbondantemente superate queste barriere.
Io vorrei "liberalizzare" questa filosofia, con la mia idea esposta di sopra.
se l'i-esimo bit è 1, i due registri della riga associata andranno letti in sequenza come un unico registro da 8 bit, se invece è 0 si potrà avere accesso ad entrambi i registri contententi valori a 4 bit.E se il bit 0 di R1 è pari a 1 che succede?
lupoxxx87
20-06-2010, 00:52
Ecco, questa è una cosa che non ho mai ben capito dei processori, almeno quelli moderni. Cioè, dover attribuire per forza un "compito" specifico ai registri.
....
Capisco che, un tempo, per semplificare la complessità del processore, fosse necessario operare in questi termini, ma oggi penso che si siano abbondantemente superate queste barriere.
Io vorrei "liberalizzare" questa filosofia, con la mia idea esposta di sopra.
E se il bit 0 di R1 è pari a 1 che succede?
se come bit 0 intendi il meno significativo, la risposta ce l'hai già. altrimenti...ce l'hai già lo stesso :p
• R1 lo chiamiamo "registro di sdoppiamento", .....
il bit più significativo se sarà 0 indicherà che la modalità "ad estensione di registri" è disattivata, 1 altrimenti;
il fatto di avere un registro fisso come accumulatore è più semplice, almeno a mio dire, da realizzare sul circuito del processore.
comunque, imho, è più divertente sperimentare nuove modifiche su tecnologie abbondantemente testate che ribaltare tutto senza avere nessuna base su cui appoggiarsi
Insomma la "riga 0" non si può leggere come un unico registro ad 8 bit, ma sempre e solo come R0 e R1...
lupoxxx87
20-06-2010, 01:19
in effetti.....sorgerebbe un problema .... in quanto operazioni tra due coppie di registri andrebbero in overflow.
bisognerebbe usare una coppia di accumulatori
cdimauro
20-06-2010, 07:13
Ma a dire il vero anche queste sarebbero già "decodificate", solo che anziché una manciata di codifiche qui ce ne sono 256 (anzi, anche qualcuna di meno), ognuna già pronta con dimensione e posizione del registro.
Ma non sono già decodificate, perché utilizzano un codice prefisso per ricavare prima la dimensione del registro, e poi quale usare.
Per avere "già pronti" dimensione e posizione devi pertanto operare nel seguente modo:
- estrarre i 4 primi bit;
- usando una LUT ricavare la dimensione (valore a 3 bit, magari da 0 a 4: 0-> 8 bit, 1 -> 16 bit, ecc.) e la maschera (7 bit) da usare per estrarre la posizione;
- usare gli ultimi 7 bit facendo un and logico con la maschera del precedente punto per ricavare la posizione.
Questo se vogliamo risparmiare spazio, visto che le LUT costano.
Per averli già pronti servirebbe una LUT da 8 bit che ritorni due dati, a 3 e 7 bit, che però è ben più costosa.
Ma magari mi sto facendo delle seghe mentali per lo spazio, visto che ormai abbiamo chip da miliardi di transistor. :D
Comunque rimane il fatto che per poter disporre dei dati che ci servono è necessario un passo in più, per estrarli.
Il mio discorso si riferiva al fatto che bastano 8 bit per indicare tutti i registri possibili.
Sì, però mi sembra un grosso spreco di spazio (per l'opcode). Tanto per fare un esempio, nella mia implementazione sto usando 2 bit per la dimensione (da 8 a 64 bit) e 3 per il registro, e con gli opcode che occupano 16 bit sono già con l'acqua alla gola. :D
Però potrebbe essere interessante il tuo schema per avere più registri sfruttando lo spazio a disposizione.
Può darsi che aggiungendo un solo stadio nella pipeline al solo scopo di estrarre questi dati si possa ottenere comunque un vantaggio. Ma dipende strettamente dall'uso che ne farebbe un compilatore e/o un programmatore. Perché se è vero che 128 registri a 8 bit sono tantissimi e fanno venire l'acquolina in bocca, è anche vero che pensare di sfruttarli tutti è pura utopia.
Quindi nella scelta della caratteristiche da implementare serve anche un po' di sano pragmatismo. :D
cdimauro
20-06-2010, 07:17
il fatto di avere un registro fisso come accumulatore è più semplice, almeno a mio dire, da realizzare sul circuito del processore.
Sarà più semplice, ma è decisamente obsoleto.
Tanto per dire, i 68000 hanno 8 registri dati indistinti: con ognuno di essi puoi fare esattamente le stesse cose. E i compilatori (e i programmatori pure, che non devono ricordarsi "eccezioni" al modello) ringraziano per l'ortogonalità. :D
Ma magari mi sto facendo delle seghe mentali per lo spazio, visto che ormai abbiamo chip da miliardi di transistor. :DPiù che altro pensavo a questo fattore, visto che se vogliamo fare un processore possiamo operare in una logica più moderna.
D'altra parte, se vogliamo un processore moderno allora bisognerebbe metterci qualche altra corbelleria tipo la gestione OOO (e qui so' cazzi :asd:).
Più che altro mi pareva un'idea molto flessibile, tutto qui :)
Wow, avete già fatto 2 pagine! :eek:
x MaxArt:
è un'idea interessante, molto utile per usare ogni byte a disposizione, ma decisamente complesso per decodificare la dimensione (come ha già spiegato bene Cesare). Invece di usare campi di bit "variabili" per identificare la dimensione (1 = 64, 10 = 32, 100 = 16 ecc...), puoi usare 2 bit "fissi", che ti danno quelle 4 combinazioni (8,16,32,64) che ti servono, e usare gli altri per definire il registro vero e proprio. La differenza, in questo caso, è che avrai la stessa quantità di registri da 8, da 16 (ecc) bit, e la cosa può piacere o no.
Ecco, questa è una cosa che non ho mai ben capito dei processori, almeno quelli moderni. Cioè, dover attribuire per forza un "compito" specifico ai registri.
Veramente, solo l'architettura x86 ancora oggi continua a imporre registri specifici per ogni operazione. Tutti i Risc lasciano ampia libertà al programmatore (a parte un paio di registri, come il program counter e il link register), anche un cisc come il 68k fornisce molta libertà!
x lupoxxx87:
il bit più significativo se sarà 0 indicherà che la modalità "ad estensione di registri" è disattivata, 1 altrimenti;
Mi sembra più un' idea da "retrocompatibilità": avevi già un uP a 4 bit, e lo vuoi portare a 8 bit. Se il nuovo programma è a 8 bit, si imposta il bit di controllo ed entra in 8 bit. Un po' come cambiare tra modalità reale->protetta->long. La dimensione dell'operando andrebbe messa nell'istruzione, perchè metti che il tuo codice stà lavorando su una lista di elementi, ognuno di questi a 8 bit (e gli indirizzi sono a 64): dovresti continuamente cambiare modalità per prima operare sul dato (8), e poi lavorare sui puntatori (64) per scorrere i nodi della lista.
il fatto di avere un registro fisso come accumulatore è più semplice, almeno a mio dire, da realizzare sul circuito del processore.
Non necessariamente. ;)
Cmq, e se avessimo tipo 16/32 registri, tutti a 64 bit, ma che posso specificare al volo la dimensione da contenere? Cioè, ad un registro posso far contenere un dato a 8, 16, 32 o 64 bit, se è a 8 il dato occuperà il byte meno significativo dei 64, se è a 16 occuperà i 2 meno significativi ecc..., e poi nell'istruzione decidiamo quanto usarne. Vantaggio: è molto più semplice da costruire (basta aggiungere una riga di AND per mascherare solo i dati che ci interessano, sia in ingresso sia in uscita dal banco registri); svantaggio: se usi dati piccoli, sprechi la parte alta dei registri (ma se ti sta a cuore lo spazio, puoi usare degli shift per caricare anche in alto, poi possiamo dire al processore di non azzerare automaticamente la parte alta del registro quando carichiamo un nuovo dato piccolo).
Una cosa che mi sarebbe piaciuta implementare, è una serie di istruzioni che fanno il cmp e poi il salto condizionato adatto, p.es:
cje R0, R1, etichetta ; compara e salta se uguale
cjne R0, R2, etichetta ; compara e salta se non uguale
ecc...
Si migliorerebbe la densità del codice, visto che i salti sono quasi sempre accompagnati da un test.
Se rimane spazio si possono anche creare le istruzioni con i dati immediati
cje R0, 5, etichetta ; compara e salta se uguale
cjne R0, 26, etichetta ; compara e salta se non uguale
ecc...
(bisognerà però mettere un limite sulla costante, sennò non ci entra nell'istruzione insieme alla destinazione del salto, che sarà sicuramente un offset dall'attuale)
cdimauro
20-06-2010, 19:59
Più che altro pensavo a questo fattore, visto che se vogliamo fare un processore possiamo operare in una logica più moderna.
Secondo me non è il contesto giusto. Avevo avuto la stessa idea una venticinquina d'anni fa, quando stavo progettando l'ISA del mio microprocessore a 1024 bit (non scherzo). :asd:
Però per i registri non va bene. Per altro, invece, è utile; infatti ne ho adottato una codifica particolare per un'istruzione simile alla IF degli ARM, per l'esecuzione condizionata di un certo numero di istruzioni.
D'altra parte, se vogliamo un processore moderno allora bisognerebbe metterci qualche altra corbelleria tipo la gestione OOO (e qui so' cazzi :asd:).
E' da un pezzo che ci penso, ma francamente non è chiaro che tipo di accelerazione potrebbe giovare. La vTable, alla fine, è una tabella di puntatori, e si gestisce già; le variabili d'istanza sono campi di un record / struct, e si gestiscono già.
Più che altro mi pareva un'idea molto flessibile, tutto qui :)
Lo è, ma applicata ad altri contesti, come dicevo prima.
Una cosa che mi sarebbe piaciuta implementare, è una serie di istruzioni che fanno il cmp e poi il salto condizionato adatto, p.es:
cje R0, R1, etichetta ; compara e salta se uguale
cjne R0, R2, etichetta ; compara e salta se non uguale
ecc...
Si migliorerebbe la densità del codice, visto che i salti sono quasi sempre accompagnati da un test.
Nell'attuale ISA "short" a 16 bit non l'ho prevista, perché sono già con l'acqua alla gola, in quanto lo spazio è quasi del tutto esaurito (ebbene sì: alle 3 di notte ero ancora a cazzeggiare coi pattern delle istruzioni :asd:), ma nell'opcode a 32 bit dovrebbe esserci abbastanza spazio (ho già messo una DBcc per eseguire loop condizionati, ma con contatore anche a 64 bit; Motorola-style anche questo ovviamente :D).
Se rimane spazio si possono anche creare le istruzioni con i dati immediati
cje R0, 5, etichetta ; compara e salta se uguale
cjne R0, 26, etichetta ; compara e salta se non uguale
ecc...
(bisognerà però mettere un limite sulla costante, sennò non ci entra nell'istruzione insieme alla destinazione del salto, che sarà sicuramente un offset dall'attuale)
Esatto. Negli opcode a 16 bit ho messo una QCMP che accetta soltanto valori da -1 a 2: non c'è altro spazio; comunque si tratta di valori molto comuni per i confronti, per cui dovrebbe bastare. Per le altre costanti c'è sempre la classica CMP con valore immediato:
CMP.Q $123456789ABCDEF0,D0
:cool:
Secondo me non è il contesto giusto. Avevo avuto la stessa idea una venticinquina d'anni fa, quando stavo progettando l'ISA del mio microprocessore a 1024 bit (non scherzo). :asd:
Caspita, ti servivano 1,559250242e290 EiB di memoria? Di sicuro avevi spazio per gli opcode :asd:
E' da un pezzo che ci penso, ma francamente non è chiaro che tipo di accelerazione potrebbe giovare. La vTable, alla fine, è una tabella di puntatori, e si gestisce già; le variabili d'istanza sono campi di un record / struct, e si gestiscono già.
Ti sei fregato anche te, penso che MaxArt intendesse dire "Out Of Order", non "Object Oriented" :asd:
Nell'attuale ISA "short" a 16 bit non l'ho prevista, perché sono già con l'acqua alla gola, in quanto lo spazio è quasi del tutto esaurito (ebbene sì: alle 3 di notte ero ancora a cazzeggiare coi pattern delle istruzioni :asd:), ma nell'opcode a 32 bit dovrebbe esserci abbastanza spazio (ho già messo una DBcc per eseguire loop condizionati, ma con contatore anche a 64 bit; Motorola-style anche questo ovviamente :D).
Esatto. Negli opcode a 16 bit ho messo una QCMP che accetta soltanto valori da -1 a 2: non c'è altro spazio; comunque si tratta di valori molto comuni per i confronti, per cui dovrebbe bastare. Per le altre costanti c'è sempre la classica CMP con valore immediato:
CMP.Q $123456789ABCDEF0,D0
:cool:
Deduco che vuoi fare un'architettura con opcode a lunghezza differente. Penso che si possa fare anche con opcode a lunghezza fissa, e usando dei "trucchi" e pseudo-istruzioni si può risparmiare qualche istruzione (ad esempio, se il program counter è accessibile, si può risparmiare una istruzione sul caricamento di un immediato con una istruzione tipo mov registro, [PC++]. Ovviamente il ++ aumenterà di tanto quanto è la costante caricata. Oppure, per creare uno stack si usa mov reg, [SP++] e mov [--SP], reg , si risparmia su push e pop). Cosa ne pensi?
cdimauro
20-06-2010, 21:08
Caspita, ti servivano 1,559250242e290 EiB di memoria? Di sicuro avevi spazio per gli opcode :asd:
Ero giovane e immaturo. Figurati che avevo disegnato pure una tastiera gigantesca con centinaia e centinaia di tasti (tutti quelli che avevo visto in altre tastiere e altri che ritenevo utili). :asd:
Ti sei fregato anche te, penso che MaxArt intendesse dire "Out Of Order", non "Object Oriented" :asd:
Hum. Hai ragione. Colpa della OOP, che mi fa stravedere. :D
Deduco che vuoi fare un'architettura con opcode a lunghezza differente.
Io sono un amante dei CISC. Fai tu. :p
Penso che si possa fare anche con opcode a lunghezza fissa, e usando dei "trucchi" e pseudo-istruzioni si può risparmiare qualche istruzione (ad esempio, se il program counter è accessibile, si può risparmiare una istruzione sul caricamento di un immediato con una istruzione tipo mov registro, [PC++]. Ovviamente il ++ aumenterà di tanto quanto è la costante caricata.
Che però dovrebbe essere allineata sempre a 32 bit, ad esempio (supponendo che gli opcode a dimensione fissa siano di 4 byte, appunto).
In ogni caso il comportamento è esattamente come quello di un CISC con opcode a dimensione variabile. :read:
Oppure, per creare uno stack si usa mov reg, [SP++] e mov [--SP], reg , si risparmia su push e pop). Cosa ne pensi?
Considera che Motorola coi 68000 faceva già così, usando A7 (l'ultimo dei registri indirizzi) come SP.
Io, invece, ho dedicato un apposito registro. Per due motivi: il primo è che così libero un registro indirizzi. Il secondo è che il set di registri diventa completamente general purpose (nessun registro specializzato), per cui compilatori e programmatori ringraziano.
Altra cosa, in questo modo SP lo posso gestire come voglio in termini di allineamento.
cdimauro
20-06-2010, 22:57
Penso di aver completato la tabella degli opcode a 16 bit.
Appena ho tempo (devo sospendere perché mercoledì ho il solito appuntamento con AD :D) mi metto a lavoro su quella 32 bit (che è ben più complessa).
Che però dovrebbe essere allineata sempre a 32 bit, ad esempio (supponendo che gli opcode a dimensione fissa siano di 4 byte, appunto).
Non necessariamente, è la pigrizia dei progettisti RISC a imporre questa limitazione ;)
Considera che Motorola coi 68000 faceva già così, usando A7 (l'ultimo dei registri indirizzi) come SP.
Io, invece, ho dedicato un apposito registro. Per due motivi: il primo è che così libero un registro indirizzi. Il secondo è che il set di registri diventa completamente general purpose (nessun registro specializzato), per cui compilatori e programmatori ringraziano.
Altra cosa, in questo modo SP lo posso gestire come voglio in termini di allineamento.
Ma se non usi le istruzione apposite, puoi creare anche più di uno stack contemporaneamente, perchè puoi usare qualsiasi registro come SP. E anche in questo caso puoi gestire te l'allineamento, perchè si può decidere nelle istruzioni se pre/post inc/decrementare di 1,2,4,8 byte. Tu dici che lo SP è riservato perchè quando esegui "call", il uP salva l'indirizzo di ritorno sullo stack. So che non ti piacerà (;)), ma usando il link register, risparmi un accesso alla memoria sia in call sia in ret, e la funzione sa da sola se deve salvarsi il ritorno (in caso debba fare essa stessa una chiamata). Oppure, senza link, si dice a call e a ret quale registro usare come stack su cui salvare o ripristinare l'indirizzo. Questo ha 2 vantaggi: si possono usare 2 stack, uno per i parametri e uno per gli indirizzi di ritorno. Ovviamente le funzioni devono usare gli stessi registri (almeno sullo stesso sistema).
Poi, c'è un vantaggio a usare differenti registri per i dati e per gli indirizzi? Perchè in questo caso stai dando una limitazione ai compilatori e ai programmatori: metti che devi unire 10 vettori in uno ordinato, finiscono i registri indirizzo. O è solo per risparmiare un bit nell'istruzione? :D
Comincio a buttare giù la mia versione del set istruzioni, così possiamo discutere meglio.
Invece di usare campi di bit "variabili" per identificare la dimensione (1 = 64, 10 = 32, 100 = 16 ecc...), puoi usare 2 bit "fissi", che ti danno quelle 4 combinazioni (8,16,32,64) che ti servono, e usare gli altri per definire il registro vero e proprio. La differenza, in questo caso, è che avrai la stessa quantità di registri da 8, da 16 (ecc) bit, e la cosa può piacere o no.L'idea alla base era la flessibilità. Con 2 bit per definire la lunghezza dei registri impedisce di fatto in un futuro di definire registri più grandi. Nell'altro metodo sarebbe più veloce?
Veramente, solo l'architettura x86 ancora oggi continua a imporre registri specifici per ogni operazione.E dici poco, sono i più diffusi al mondo (nonché quelli che conosco meglio).
Mi ricordo anche che gli Z80 avevano registri specifici, e tu di certo puoi confermarlo o smentirlo. Beh, gli Z80 si usano ancora :D
Non necessariamente. ;)
Però per i registri non va bene. Per altro, invece, è utile; infatti ne ho adottato una codifica particolare per un'istruzione simile alla IF degli ARM, per l'esecuzione condizionata di un certo numero di istruzioni.Puoi spiegare meglio questo punto?
Quali contesti ne gioverebbero e perché?
Ti sei fregato anche te, penso che MaxArt intendesse dire "Out Of Order", non "Object Oriented" :asd:Già... :stordita:
Penso di aver completato la tabella degli opcode a 16 bit.Porca miseria, hai fatto tutto tu...
Ma si può vedere qualcosa? Altrimenti come faccio ad imparare?
L'idea alla base era la flessibilità. Con 2 bit per definire la lunghezza dei registri impedisce di fatto in un futuro di definire registri più grandi. Nell'altro metodo sarebbe più veloce?
Si, sarebbe più veloce perchè basterebbe prendere i 2 bit così come sono e portarli al circuito adatto di selezione registri. Nel tuo metodo, invece, bisogna prima decodificare il valore del byte, e poi inviarlo alla selezione. Cmq, non è impossibile da implementare la tua versione: non è necessaria una LUT come ha detto Cesare, basta solo un decoder a priorità (posso farvi il circuito se volete capire meglio).
Cmq l'idea non è cattiva, per niente, ma la limitazione c'è lo stesso: se usi 8 bit per il campo registri, arriveresti al massimo a 2 registri da 512 bit, perchè poi non hai lo spazio fisico nell'istruzione per espandere il suddetto campo (sempre se vuoi mantenere la retrocompatibilità con i precedenti programmi)
E dici poco, sono i più diffusi al mondo (nonché quelli che conosco meglio).
Mi ricordo anche che gli Z80 avevano registri specifici, e tu di certo puoi confermarlo o smentirlo. Beh, gli Z80 si usano ancora :D
Certo, anche lo Z80 aveva una marea di registri riservati (A accumulatore, coppia HL per puntatore in memoria, coppie BC e DE se volevi per puntatori a memoria, ma solo se per destinazione/sorgente avevi A, IX e IY per puntare la memoria con offset a 8bit.), ma è stato fatto per mantenere la retrocompatibilità con l'8080. Difatti, il povero Z8000 (http://en.wikipedia.org/wiki/Zilog_Z8000), che non era stato pensato per essere retrocompatibile, aveva un set di istruzioni completamente ortogonale (o quasi).
Porca miseria, hai fatto tutto tu...
Ma si può vedere qualcosa? Altrimenti come faccio ad imparare?
Calma, calma :D
Cmq l'idea non è cattiva, per niente, ma la limitazione c'è lo stesso: se usi 8 bit per il campo registri, arriveresti al massimo a 2 registri da 512 bit, perchè poi non hai lo spazio fisico nell'istruzione per espandere il suddetto campo (sempre se vuoi mantenere la retrocompatibilità con i precedenti programmi)Sticazzi, quando ci sarà bisogno di registri a 512 bit si rifà un'ISA tutta daccapo :asd:
Guarda un po' che fatica che si sta facendo a passare ai 64 bit... (e non è solo una questione di x86).
Calma, calma :DCalma perché? Io sono curioso! :read:
Guarda un po' che fatica che si sta facendo a passare ai 64 bit... (e non è solo una questione di x86).
Si, è solo questione di x86 e retrocompatibilità :)
Basta solo ricompilare le applicazioni, e si avrebbe già tutto a 64 bit (beh, non si avrebbero da subito tutte le capacità, perchè bisogna anche un po' ottimizzare il sorgente, tipo sulla dimensione delle variabili). Non si passa in bomba ai 64 (almeno su x86 e Windows) perchè ci sono ancora tantissimi computer con cpu a 32bit, e perchè molti produttori sono pigri (puoi farti una distro Linux con tutti i programmi a 64 bit, semprechè non ti interessi avere Flash in Firefox :rolleyes:)
No, non è solo questione di x86. A parte il casino che c'è stato coi G5 a "64 bit" (per poi essere in realtà solo un lontano parente di un processore a 64 bit), il problema sta anche nei sistemi operativi che non si aggiornano ma soprattutto negli interi più lunghi che ti obbligano ad avere:
- assai più RAM;
- dischi più grandi e più veloci;
- bus più ampi e veloci.
Per molte applicazioni anche 32 bit sono troppi: prendine una, che so, che ti fa da sveglia...
Un editor di testo non vedo che necessità abbia dei 64 bit (file di testo più grandi di 4 GB? ORLY? :D) . E si può continuare. Da qui l'inerzia per passare ad architetture più complesse.
No, non è solo questione di x86. A parte il casino che c'è stato coi G5 a "64 bit" (per poi essere in realtà solo un lontano parente di un processore a 64 bit), il problema sta anche nei sistemi operativi che non si aggiornano ma soprattutto negli interi più lunghi che ti obbligano ad avere:
- assai più RAM;
- dischi più grandi e più veloci;
- bus più ampi e veloci.
Non è detto che un'applicazione compilata per 64 bit debba per forza usare interi a 64 bit: le opzioni standard di compilazione su un amd64 sono di avere int a 32 bit, quindi non cambia nulla. Se vuoi un dato grande, usi un long. I puntatori invece diventano tutti a 64 bit. Poi dipende dall'architettura: se per aggiungere le istruzioni a 64 bit hanno messo diecimila byte di prefisso, è ovvio che i programmi son più grandi :)
Riguardo ai bus: già dal pentium 4 abbiamo avuto bus dati a 64 bit (se non addirittura prima) ;)
Per molte applicazioni anche 32 bit sono troppi: prendine una, che so, che ti fa da sveglia...
Un editor di testo non vedo che necessità abbia dei 64 bit (file di testo più grandi di 4 GB? ORLY? :D) . E si può continuare. Da qui l'inerzia per passare ad architetture più complesse.
Non è solo per la quantità di memoria indirizzabile che si passa ai 64 bit: si può usare la maggior banda per poter spostare efficacemente grande quantità di dati (visto che i pc non hanno un dma che opera memoria-memoria). Se parliamo di Blocco note, ovvio la velocità non si sente, ma se prendiamo software un pochino più su, che permettono di gestire immagini, font, colori ... il calcolo della posizione degli oggetti, o il render della pagina attuale la deve fare la cpu (a meno che non si modifichi radicalmente l'applicativo e gli si faccia usare la GPU, ma allora già che ci sono possono cmq portarlo a 64 bit ;))
Cmq, in ultima analisi, la colpa è anche del corrente set di istruzioni, che riprende dall'x86: se avessero tagliato completamente, ridisegnato il set, e relegato l'emulazione ad un altro strato, in un futuro si sarebbe potuto togliere completamente il supporto al 32 bit e lasciare solo il sistema a 64. Invece ci tocca cmq all'avvio del sistema passare dalla modalità 32 bit, e già questo ci blocca dal poterla togliere.
cdimauro
21-06-2010, 13:39
Non necessariamente, è la pigrizia dei progettisti RISC a imporre questa limitazione ;)
No, non è pigrizia: è una filosofia. Coi RISC si cerca di risparmiare al massimo sull'architettura introducendo eventualmente vincoli (anche rigidi). Quello degli opcode tutti allineati alla dimensione degli stessi è un classico esempio; d'altra parte secondo te avrebbe senso avere che fossero non allineati, quando la dimensione rimane sempre la stessa? :fagiano:
Ma se non usi le istruzione apposite, puoi creare anche più di uno stack contemporaneamente, perchè puoi usare qualsiasi registro come SP. E anche in questo caso puoi gestire te l'allineamento, perchè si può decidere nelle istruzioni se pre/post inc/decrementare di 1,2,4,8 byte.
No, perché, ad esempio, su 68000 per questioni di efficienza il "push" di un byte sullo stack (A7) salva comunque una word (16 bit), mentre con gli altri registri indirizzo viene preservata la giusta dimensione.
Tu dici che lo SP è riservato perchè quando esegui "call", il uP salva l'indirizzo di ritorno sullo stack.
Anche per questo; di fatto SP/A7 è già specializzato. In ogni caso un processore deve sempre avere uno stack definito.
Tanto vale, a questo punto, fornirgli un apposito registro, e liberare un prezioso registro per un uso più generale.
So che non ti piacerà (;)), ma usando il link register, risparmi un accesso alla memoria sia in call sia in ret, e la funzione sa da sola se deve salvarsi il ritorno (in caso debba fare essa stessa una chiamata). Oppure, senza link, si dice a call e a ret quale registro usare come stack su cui salvare o ripristinare l'indirizzo. Questo ha 2 vantaggi: si possono usare 2 stack, uno per i parametri e uno per gli indirizzi di ritorno. Ovviamente le funzioni devono usare gli stessi registri (almeno sullo stesso sistema).
Sì, come soluzione quella del registro di link non mi piace molto, perché puoi salvare un solo indirizzo di ritorno alla volta: dopo ti costringe a eseguire comunque un push sullo stack, e poi un restore.
Se consideri che molti processori moderni hanno una sezione di call-stack, capisci bene che il caso comune non è certo quello di un insieme di funzioni flat che poi non richiamano nessuno. Per questo ho preferito usare il classico stack per l'indirizzo di ritorno.
Ma quella del registro link è un'idea che potrebbe essere presa in considerazione. D'altra parte ho pure aggiunto un registro per il Frame Pointer (FP) e apposite istruzioni (LINK, UNLINK, PUSH FP, POP FP, MOVE FP,An, MOVE An,FP) per poterlo gestire, per cui si potrebbe fare la stessa cosa (anche se ormai nella tabella degli opcode a 16 bit sono rimasti una decina di posti :D).
Poi, c'è un vantaggio a usare differenti registri per i dati e per gli indirizzi?
Sì, perché ti permette di realizzare delle ALU ottimizzate.
Ad esempio per aggiornare un registro indirizzi basta che l'ALU dedicata implementi le operazioni di somma, differenza, confronto, e hai finito. In questo modo l'ALU che si occupa di effettuare il calcolo dell'indirizzo si può usare indifferentemente per la sezione AGU e per l'aggiornamento di un registro indirizzi.
Perchè in questo caso stai dando una limitazione ai compilatori e ai programmatori: metti che devi unire 10 vettori in uno ordinato, finiscono i registri indirizzo.
Qualunque tu ponga al numero di registri, troverai sempre un pezzo di codice che ne richiede di più.
O è solo per risparmiare un bit nell'istruzione? :D
Anche per quello.
Comincio a buttare giù la mia versione del set istruzioni, così possiamo discutere meglio.
OK
Puoi spiegare meglio questo punto?
Quali contesti ne gioverebbero e perché?
Stasera ti posto la struttura dell'istruzione IF e ti faccio vedere come ho utilizzato il concetto di codice prefisso per definire le 8 modalità in cui opera.
Un esempio "sul campo" penso che valga meglio di qualunque spiegazione.
Porca miseria, hai fatto tutto tu...
Sono stato facilitato dalla mia esperienza con le architetture degli elaboratori, ma considera che sono anni che ho in mente una mia ISA (erede dei 68000 :D).
Diciamo che l'aperto di questo thread mi ha dato la possibilità di riversare su un progetto concreto tutte le masturb, ehm, le idee che mi sono fatto in questo tempo.
Ma si può vedere qualcosa? Altrimenti come faccio ad imparare?
Certo. Stasera posto la prima versione, con l'intera opcode table a 16 bit, con l'aggiunta di qualche opcode a 32 bit.
Considera che al momento mi sono concentrato quasi esclusivamente sul funzionamento dello user mode. Per il supervisor mode ho già un po' di idee (mi piace molto il modello ARM), ma ci penserò dopo.
Si, sarebbe più veloce perchè basterebbe prendere i 2 bit così come sono e portarli al circuito adatto di selezione registri. Nel tuo metodo, invece, bisogna prima decodificare il valore del byte, e poi inviarlo alla selezione. Cmq, non è impossibile da implementare la tua versione: non è necessaria una LUT come ha detto Cesare, basta solo un decoder a priorità (posso farvi il circuito se volete capire meglio).
Una LUT è, però, estremamente veloce (e considerata la dimensione della tabelle, è pure economica) e semplicissima da realizzare.
Sticazzi, quando ci sarà bisogno di registri a 512 bit si rifà un'ISA tutta daccapo :asd:
Non credo che avremo ISA a 128 bit, né tanto meno a 512. :D
No, non è solo questione di x86. A parte il casino che c'è stato coi G5 a "64 bit" (per poi essere in realtà solo un lontano parente di un processore a 64 bit),
I G5 erano a 64 bit a tutti gli effetti. Certo, perdevi il 10-15% di prestazioni in questa modalità, ed è il motivo per cui da un lato Apple la pubblicizzava (sempre per una questione di puro marketing: "64 bit" è più lungo di "32 bit"), dall'altro consigliava agli sviluppatori di usarli solo dopo attenta valutazione dei pro e dei contro.
Non è detto che un'applicazione compilata per 64 bit debba per forza usare interi a 64 bit: le opzioni standard di compilazione su un amd64 sono di avere int a 32 bit, quindi non cambia nulla. Se vuoi un dato grande, usi un long. I puntatori invece diventano tutti a 64 bit. Poi dipende dall'architettura: se per aggiungere le istruzioni a 64 bit hanno messo diecimila byte di prefisso, è ovvio che i programmi son più grandi :)
Sono leggermente più grandi, dai. ;)
Riguardo ai bus: già dal pentium 4 abbiamo avuto bus dati a 64 bit (se non addirittura prima) ;)
Con gli x86 è arrivato addirittura col primo Pentium. :)
Non è solo per la quantità di memoria indirizzabile che si passa ai 64 bit: si può usare la maggior banda per poter spostare efficacemente grande quantità di dati (visto che i pc non hanno un dma che opera memoria-memoria). Se parliamo di Blocco note, ovvio la velocità non si sente, ma se prendiamo software un pochino più su, che permettono di gestire immagini, font, colori ... il calcolo della posizione degli oggetti, o il render della pagina attuale la deve fare la cpu (a meno che non si modifichi radicalmente l'applicativo e gli si faccia usare la GPU, ma allora già che ci sono possono cmq portarlo a 64 bit ;))
Per molte cose ci sono le unità SIMD, ed è il motivo per cui QUI servono registri con più di 64 bit.
Cmq, in ultima analisi, la colpa è anche del corrente set di istruzioni, che riprende dall'x86: se avessero tagliato completamente, ridisegnato il set, e relegato l'emulazione ad un altro strato, in un futuro si sarebbe potuto togliere completamente il supporto al 32 bit e lasciare solo il sistema a 64. Invece ci tocca cmq all'avvio del sistema passare dalla modalità 32 bit, e già questo ci blocca dal poterla togliere.
Nessuno t'impedisce di realizzare un processore con ISA AMD64-only. Anzi, personalmente è quel che farei se fossi nei panni di AMD o Intel.
Comunque ad AMD rimprovero un'altra cosa: AMD64 non è compatibile a livello binario con x86, per cui sei costretto a ricompilare i sorgenti e ad adattarli (poco, per fortuna). Tanto valeva sistemare l'ISA a 64 bit razionalizzando la tabella degli opcode (tanto per cambiare, avrei utilizzato una opcode table a 16 bit :D).
No, perché, ad esempio, su 68000 per questioni di efficienza il "push" di un byte sullo stack (A7) salva comunque una word (16 bit), mentre con gli altri registri indirizzo viene preservata la giusta dimensione.
Ok, tu vuoi fare in modo che sia il uP a decidere come disporre lo stack in modo da adattarsi ai suoi bisogni e velocizzare l'operazione.
Sì, come soluzione quella del registro di link non mi piace molto, perché puoi salvare un solo indirizzo di ritorno alla volta: dopo ti costringe a eseguire comunque un push sullo stack, e poi un restore.
Se consideri che molti processori moderni hanno una sezione di call-stack, capisci bene che il caso comune non è certo quello di un insieme di funzioni flat che poi non richiamano nessuno. Per questo ho preferito usare il classico stack per l'indirizzo di ritorno.
Si potrebbe unire l'utilità del link e dello stack in questo modo: la call mette l'indirizzo nello stack e nel link, se poi la funzione non richiama altre cose, può ritornare mediante un'istruzione che semplicemente usa il link per ripristinare il pc, e decrementa lo sp di tot (in modo da simulare il pop).
Ma quella del registro link è un'idea che potrebbe essere presa in considerazione. D'altra parte ho pure aggiunto un registro per il Frame Pointer (FP) e apposite istruzioni (LINK, UNLINK, PUSH FP, POP FP, MOVE FP,An, MOVE An,FP) per poterlo gestire, per cui si potrebbe fare la stessa cosa (anche se ormai nella tabella degli opcode a 16 bit sono rimasti una decina di posti :D).
E se invece di aggiungere registri specifici, e istruzioni adatte per gestirli, si potrebbe riservare un altro bit per selezionare il registro indirizzi, in modo da espanderli a 16, e usare i trucchi "risc-eschi" per risparmiare sulle altre istruzioni (e usare pseudo-istruzioni). Ad esempio, A15 è PC, A14 è SP, la call sarà un'istruzione discreta, ma push, pop, carica immediato, ret, salto incondizionato possono essere tranquillamente semplificate in
mov r, [++SP]
mov [SP--],r
mov r, [PC++]
mov PC, [SP--]
mov PC, [PC++]
Si semplificherebbe il tutto, mantenendo ortogonalità e lasciando al programmatore possibilità di abbinare le istruzioni per far venire fuori qualche altro "trucchetto".
Sì, perché ti permette di realizzare delle ALU ottimizzate.
Ad esempio per aggiornare un registro indirizzi basta che l'ALU dedicata implementi le operazioni di somma, differenza, confronto, e hai finito. In questo modo l'ALU che si occupa di effettuare il calcolo dell'indirizzo si può usare indifferentemente per la sezione AGU e per l'aggiornamento di un registro indirizzi.
Possiamo cmq usare 2 alu per operare sugli stessi registri: se ad esempio abbiamo una istruzione tipo
add R1, [R2+R3]
R2 e R3 verranno prelevati dall'AGU, e il valore prelevato andrà sommato con R1 nell'alu normale; oppure
add R1, [R2++]
R2 viene prelevato dall'AGU, che manda il suo valore in uscita e salva pure il valore aggiornato, e R1 viene prelevato dall'ALU.
Una LUT è, però, estremamente veloce (e considerata la dimensione della tabelle, è pure economica) e semplicissima da realizzare.
Anche col codificatore a priorità non c'è troppa circuiteria: il segnale al massimo deve passare 3 porte logiche prima di passare in uscita, mi bastano qualche AND e OR a molteplici ingressi, e tante NOT. Se invece vogliamo la versione economica ma più lenta, servono molte meno NOT ma qualche AND in più.
Nessuno t'impedisce di realizzare un processore con ISA AMD64-only. Anzi, personalmente è quel che farei se fossi nei panni di AMD o Intel.
Comunque ad AMD rimprovero un'altra cosa: AMD64 non è compatibile a livello binario con x86, per cui sei costretto a ricompilare i sorgenti e ad adattarli (poco, per fortuna). Tanto valeva sistemare l'ISA a 64 bit razionalizzando la tabella degli opcode (tanto per cambiare, avrei utilizzato una opcode table a 16 bit :D).
Beh, sono solo state tolte qualche istruzioni veramente arcaiche per riassegnarle ad un altro scopo, ma è ancora compatibile (di default le istruzioni lavorano su 32bit, per usare le 64 bisogna anteporre il prefisso REX).
Cmq concordo che ridisegnare la tabella di opcode sarebbe stato decisamente meglio.
Ultima cosa: come pensi di far riconoscere al processore istruzioni a 16 o a 32 bit? Queste ultime sono solo normali 32 bit ma con un prefisso, o usi un bit dell'istruzione per dire "questa è a 32, carica anche l'altra word" ?
Stavo anche ponderando sull'utilità di ADC (add con carry): è ancora utile ora che abbiamo registri a 64 bit?
Edit: ed è utile avere la somma/sottrazione che lavorano su 3 registri (Ra=Rb+Rc)? Perchè nel mergesort MIPS lo ho usato solo una volta (per fare center = left+right )
Tesinevb
21-06-2010, 19:32
...Ultima cosa: come pensi di far riconoscere al processore istruzioni a 16 o a 32 bit? Queste ultime sono solo normali 32 bit ma con un prefisso, o usi un bit dell'istruzione per dire "questa è a 32, carica anche l'altra word" ?
Campo Opcode
Contenuto x x x x x x d w
Posizi. bit 7 6 5 4 3 2 1 0
Se w=0, gli operandi sono a 8 bit; se, invece, w=1, gli operandi sono a 16 bit se invece w=2sono a 32bit ecosì via...
Campo Opcode
Contenuto x x x x x x d w
Posizi. bit 7 6 5 4 3 2 1 0
Se w=0, gli operandi sono a 8 bit; se, invece, w=1, gli operandi sono a 16 bit se invece w=2sono a 32bit ecosì via...
Ok, quello lo avevo capito. Mi chiedevo come il processore fa a capire se (durante il fetch) si trova davanti un'istruzione con opcode lungo 16 bit o 32 bit, perchè (se non ho capito male), Cesare ha una tabella con opcode lunghi 16 bit, e un'altra con opcode lunghi 32 bit. Quindi il processore si carica una word dalla memoria: come fa a capire se quella word gli basta (perchè è un'istruzione a 16 bit), oppure deve caricare un'altra word che continene il resto dell'istruzione (perchè è un'istruzione a 32 bit) ?
Ok, quello lo avevo capito. Mi chiedevo come il processore fa a capire se (durante il fetch) si trova davanti un'istruzione con opcode lungo 16 bit o 32 bit, perchè (se non ho capito male), Cesare ha una tabella con opcode lunghi 16 bit, e un'altra con opcode lunghi 32 bit. Quindi il processore si carica una word dalla memoria: come fa a capire se quella word gli basta (perchè è un'istruzione a 16 bit), oppure deve caricare un'altra word che continene il resto dell'istruzione (perchè è un'istruzione a 32 bit) ?La butto lì: forse perché in realtà gli opcode non sono a 16 e 32 bit ma in sostanza a 15 e 31? :stordita:
La butto lì: forse perché in realtà gli opcode non sono a 16 e 32 bit ma in sostanza a 15 e 31? :stordita:
Si, quello che avevo pensato io (cioè il discorso di Tesinevb però applicato alle istruzioni): il uP carica la prima word, e controlla subito un bit; se quel bit è attivo, intende dire "hey, uP, carica un'altra word perchè sennò sono incompleto!".
Vedo che ci siamo capiti :D
cdimauro
21-06-2010, 21:42
Ok, tu vuoi fare in modo che sia il uP a decidere come disporre lo stack in modo da adattarsi ai suoi bisogni e velocizzare l'operazione.
Sì. Ho specificato, infatti, un apposito campo per regolare il funzionamento dello stack.
Si potrebbe unire l'utilità del link e dello stack in questo modo: la call mette l'indirizzo nello stack e nel link, se poi la funzione non richiama altre cose, può ritornare mediante un'istruzione che semplicemente usa il link per ripristinare il pc, e decrementa lo sp di tot (in modo da simulare il pop).
Per me non ha senso, perché la push del registro c'è comunque; per una POP in meno non vedo questo gran vantaggio.
E se invece di aggiungere registri specifici, e istruzioni adatte per gestirli, si potrebbe riservare un altro bit per selezionare il registro indirizzi, in modo da espanderli a 16, e usare i trucchi "risc-eschi" per risparmiare sulle altre istruzioni (e usare pseudo-istruzioni).
Lo sai che non mi piacciono i RISC. :D
Ad esempio, A15 è PC, A14 è SP, la call sarà un'istruzione discreta, ma push, pop, carica immediato, ret, salto incondizionato possono essere tranquillamente semplificate in
mov r, [++SP]
mov [SP--],r
mov r, [PC++]
mov PC, [SP--]
mov PC, [PC++]
Si semplificherebbe il tutto, mantenendo ortogonalità e lasciando al programmatore possibilità di abbinare le istruzioni per far venire fuori qualche altro "trucchetto".
I trucchetti lasciamoli perdere quando si ha a che fare con PC e SP. :D
Comunque col 68000 e per SP funziona già così.
In ogni caso ci sono 2 considerazioni da fare. La prima è che stai usando 2 registri, per cui te ne rimarranno 14 "general purpose".
La seconda è che con questi trucchetti usando il PC rischi di limitare la scalabilità del processore, visto che il PC è l'elemento che cambia più spesso, ed è determinante per proseguire col flusso delle istruzioni.
Possiamo cmq usare 2 alu per operare sugli stessi registri: se ad esempio abbiamo una istruzione tipo
add R1, [R2+R3]
R2 e R3 verranno prelevati dall'AGU, e il valore prelevato andrà sommato con R1 nell'alu normale; oppure
add R1, [R2++]
R2 viene prelevato dall'AGU, che manda il suo valore in uscita e salva pure il valore aggiornato, e R1 viene prelevato dall'ALU.
Mumble. Non ho capito questi due esempi in base al contesto di cui stavamo discutendo. :stordita:
Ultima cosa: come pensi di far riconoscere al processore istruzioni a 16 o a 32 bit? Queste ultime sono solo normali 32 bit ma con un prefisso, o usi un bit dell'istruzione per dire "questa è a 32, carica anche l'altra word" ?
Rispondo dopo.
Stavo anche ponderando sull'utilità di ADC (add con carry): è ancora utile ora che abbiamo registri a 64 bit?
Sì, perché si possono fare operazioni a 128 o più bit. :D
Comunque al momento non l'ho aggiunta. Lo farò nella tabella degli opcode a 32 bit, perché si tratta di un'istruzione più rara.
Edit: ed è utile avere la somma/sottrazione che lavorano su 3 registri (Ra=Rb+Rc)? Perchè nel mergesort MIPS lo ho usato solo una volta (per fare center = left+right )
Avendo un buon numero di registri e la possibilità di fare accessi in memoria diventa meno utile.
Diciamo che se c'è spazio nella tabella degli opcode, si può pensare di aggiungere questo tipo di struzioni. :D
Ok, quello lo avevo capito. Mi chiedevo come il processore fa a capire se (durante il fetch) si trova davanti un'istruzione con opcode lungo 16 bit o 32 bit, perchè (se non ho capito male), Cesare ha una tabella con opcode lunghi 16 bit, e un'altra con opcode lunghi 32 bit. Quindi il processore si carica una word dalla memoria: come fa a capire se quella word gli basta (perchè è un'istruzione a 16 bit), oppure deve caricare un'altra word che continene il resto dell'istruzione (perchè è un'istruzione a 32 bit) ?
La butto lì: forse perché in realtà gli opcode non sono a 16 e 32 bit ma in sostanza a 15 e 31? :stordita:
No.
Si, quello che avevo pensato io (cioè il discorso di Tesinevb però applicato alle istruzioni): il uP carica la prima word, e controlla subito un bit; se quel bit è attivo, intende dire "hey, uP, carica un'altra word perchè sennò sono incompleto!".
Sì, ci siamo quasi.
Innanzitutto il processore può benissimo caricare entrambe le word e comporre, quindi, un opcode sempre a 32 bit che andrà poi a decodificare.
Questo sistema è utile per semplificare il decoder, che non dove prendere prima una word, controllarla, e poi eventualmente aggiungere l'altra. Al limite non userà la seconda word.
L'idea è quella di "intercettare" i 4 bit alti dell'opcode a 16 bit. Se sono tutti a 1, allora l'opcode è in realtà a 32 bit, altrimenti è a 16 bit (e verrà semplicemente ignorata la seconda word in fase di decodifica).
E' chiaro che in questo modo i 4 bit alti di un opcode a 32 bit sono sempre a 1111 e, quindi, inutilizzabili per mappare qualche campo. Di fatto gli opcode a 32 bit in realtà sono a 28 bit (che sono i bit utilizzabili a tutti gli effetti).
Di seguito scrivo 3 messaggi con una descrizione dell'ISA, la tabella degli opcode a 16 bit, e quella degli opcode a 32 bit (quest'ultima è ancora in lavorazione, per cui i bit pattern usati sono sballati e da sistemare, e mancano ancora diverse istruzioni).
cdimauro
21-06-2010, 21:44
64K (64000) ISA
8 64 bits data registers: D0..D7
8 64 bits address registers: A0..A7
1 64 bits Program Counter: PC
1 64 bits Stack Pointer: SP
1 64 bits Frame Pointer: FP
1 64 bits Status Register: SR
All not specified bits are RESERVED (SHOULD BE ZERO; SBZ).
****************************************************************
SR format (showed as a byte mask)
7 6 5 4 3 2 1 0
SET|SBZ|SBZ|IF|FR3|FR2|FR1|FR0
FR0 = Current Flags
FR1 = Previous Flags
FR2 = Pre-Previous Flags
FR3 = Pre-Pre-Previous Flags
IF = IF Skip Mask
SET = Settings
SBZ = Should Be Zero (RESERVED)
Flags byte:
7 6 5 4 3 2 1 0
X N Z V C
C -> Carry
V -> Overflow
Z -> Zero
N -> Negative
X -> Extended (sometimes carry is copied on, other times is user defined)
Every time that an instruction changes the flags,
FR3 is discarded and FR2, FR1 and FR0 are moved to the left
(FR0 is now free to get the new flags).
IF byte holds a 4 bits mask which reports the skip flag for each instruction:
7 6 5 4 3 2 1 0
|Sk.3|Sk.2|Sk.1|Sk.0|
Every time that an instruction is executed, Sk.0 is checked to see if it must be skipped.
If true, the instruction is skipped, otherwise, it will be executed normally.
After that, Sk.0 is discarded, and Sk.3, Sk.2, Sk.1 are moved to the right,
so that Sk.1 is now Sk.0, and Sk.3 is 0 (no skip: the instruction will be executed).
SET byte:
7 6 5 4 3 2 1 0
EAZ |AA |BE |MSA |
EAZ -> Exception on Address Zero
If a memory location is referenced through an address register,
an exception is raised if it was zero (null pointer)
AA -> Aligned Address
An exception is raised if the referenced memory location
wasn't properly aligned (word to 16 bits, longs to 32 bits,
and quads to 64 bits).
BE -> Big Endian
Datas are read and written are big endian (little endian if not set)
MSA -> Minimum Stack Alignament defines as a Size field
(1, 2, 4 or 8 bytes pushed AT LEAST for each data)
cdimauro
21-06-2010, 21:45
Size fields are defined by 2 bits:
00 (B) -> Byte
01 (W) -> Word (16 bits)
10 (L) -> Long (32 bits)
11 (Q) -> Quad (64 bits)
****************************************************************
Condition Code:
0000 XC -> X Clear
0001 XS -> X Set
0010 HI -> High (> for unsigned integers)
0011 LS -> Low or Same (<= for unsigned integers)
0100 CC -> Carry Clear (>= for unsigned integers)
0101 CS -> Carry Set (< for unsigned integers)
0110 NE -> Not Equal (!= 0)
0111 EQ -> Equal (== 0)
1000 VC -> Overflow Clear
1001 VS -> Overflow Set
1010 PL -> Plus (Not negative)
1011 MI -> Minus (Negative)
1100 GE -> Greather or Equal (>= for unsigned integers)
1101 LT -> Less Than (< for unsigned integers)
1110 GT -> Greather Than (> for unsigned integers)
1111 LE -> Less or Equal (<= for unsigned integers)
****************************************************************
Scale factor fields are defined by 2 bits:
00 -> 1
01 -> 2
10 -> 4
11 -> 8
****************************************************************
Addressing Modes (3 bits mode)
Mode | Register | Syntax
000 | Reg. no | Dn | Data register
001 | Reg. no | An | Address register
010 | Reg. no | [An] | Indirect address
011 | Reg. no | [An, SInt16] | Indirect address with signed 16 bits offset
100 | Reg. no | [An]+ | Indirect address with postincrement
101 | Reg. no | -[An] | Indirect address with predecrement
110 | Reg. no | [An, Xn.Size * Scale, SInt8] | Indirect address with sign-extended scaled index and signed 8 bits offset
111 | 000 | [PC, SInt16] | Indirect PC with signed 16 bits offset
111 | 001 | [PC, Xn.Size * Scale, SInt8] | Indirect PC with sign-extended scaled index and signed 8 bits offset
111 | 010 | [SP, UInt16] | Indirect SP with unsigned 16 bits offset
111 | 011 | [SP, Xn.Size * Scale, UInt8] | Indirect SP with zero-extended scaled index and unsigned 8 bits offset
111 | 100 | [FP, UInt16] | Indirect FP with unsigned 16 bits offset
111 | 101 | Value | Immediate
111 | 110 | [SP]+ | Indirect SP with postincrement
111 | 111 | -[SP] | Indirect SP with predecrement
Xn = Dn or An
Extension word (for scaled index addressing modes):
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
D/A |Register |Size |Scale | 8 bits offset |
D/A -> 0 = Data, 1 = Address
****************************************************************
16 bits Opcode Table
MOVE.Size Source EA, Destination EA
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 0 | Size |Dest. Mode |Dest. Register|Source Mode |Source Reg.|
ADD.Size Source EA, Dn
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 1 0 0 0 |Dn |Size |Source Mode |Source Reg.|
ADD.Size Dn, Destination EA
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 1 0 0 1 |Dn |Size |Dest. Mode |Dest. Reg. |
SUB.Size Source EA, Dn
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 1 0 1 0 |Dn |Size |Source Mode |Source Reg.|
SUB.Size Dn, Destination EA
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 1 0 1 1 |Dn |Size |Dest. Mode |Dest. Reg. |
CMP.Size Source EA, Dn
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 1 1 0 0 |Dn |Size |Source Mode |Source Reg.|
QMOVEU UInt8, Dn
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 1 1 0 1 |Dn |UInt8 |
# Dn.Q = Zero-extended UInt8
AND.Size Source EA, Dn
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 1 1 1 0 |Dn |Size |Source Mode |Source Reg.|
AND.Size Dn, Destination EA
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 1 1 1 1 |Dn |Size |Dest. Mode |Dest. Reg. |
OR.Size Source EA, Dn
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 0 0 0 0 |Dn |Size |Source Mode |Source Reg.|
OR.Size Dn, Destination EA
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 0 0 0 1 |Dn |Size |Dest. Mode |Dest. Reg. |
XOR.Size Source EA, Dn
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 0 0 1 0 |Dn |Size |Source Mode |Source Reg.|
XOR.Size Dn, Destination EA
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 0 0 1 1 |Dn |Size |Dest. Mode |Dest. Reg. |
QShift.Size UInt3, Destination EA
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 0 1 |Shift |UInt3 |Size |Dest. Mode |Dest. Reg. |
Shift = LSL (Logical Shift Left), LSR (Logical Shift Right),
ASL (Aritmetic Shift Left), ASR (Aritmetic Shift Right)
UInt3 = 1..7, 0 -> 8.
# QASR gives 0 if Value / (2 ^ Shift) is zero
Bcc SInt8 (* 2)
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 0 0 | Condition Code |SInt8 |
# Branch on Condition Code.
# If CC, PC = PC + SInt8 * 2; otherwise PC = PC + 2
QADD.Size UInt3, Destination EA
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 0 1 0 |UInt3 |Size |Dest. Mode |Dest. Reg. |
UInt3 = 1..7, 0 -> 8.
QSUB.Size UInt3, Destination EA
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 0 1 1 |UInt3 |Size |Dest. Mode |Dest. Reg. |
UInt3 = 1..7, 0 -> 8.
QCMP.Size SInt2, Destination EA
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 0 0 |SInt2 |Size |Dest. Mode |Dest. Reg. |
SInt2 = 0..2, 3 -> -1.
LEA Source EA, An
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 0 1 0 |An |Source. Mode |Sour. Reg. |
# Load Effective Address (calculates the EA address and saves it into An)
XXX Source EA, An *** RESERVED! ***
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 0 1 1 |An |Source. Mode |Sour. Reg. |
NEG.Size Destination EA
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 0 0 0 |Size |Dest. Mode |Dest. Reg. |
# NEGate. [EA] = - [EA]
NOT.Size Destination EA
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 0 0 1 |Size |Dest. Mode |Dest. Reg. |
XXX.Size Source EA *** RESERVED! ***
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 0 1 0 |Size |Source. Mode |Sour. Reg. |
XXX.Size Source EA *** RESERVED! ***
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 0 1 1 |Size |Source. Mode |Sour. Reg. |
BRA SInt8 (* 2)
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 0 0 |SInt8 |
#PC = PC + SInt8 * 2
JMP Source EA
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 0 1 0 0 |Source. Mode |Sour. Reg. |
# JuMP to EA
JSR Source EA
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 0 1 0 1 |Source. Mode |Sour. Reg. |
# Jump to SubRoutine. Pushes next instruction address on stack. Jump to EA
PEA Source EA
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 0 1 1 0 |Source. Mode |Sour. Reg. |
# Push Effective Address. Pushes EA address on stack
XXX Source EA *** RESERVED! ***
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 0 1 1 1 |Source. Mode |Sour. Reg. |
IFcc ConditionMask
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 1 0 0 |Cond. Mask |Condition Code |
Cond. Mask:
000 -> 3 instructions, TFF
001 -> 3 instructions, TFT
010 -> 3 instructions, TTF
011 -> 3 instructions, TTT
100 -> 2 instructions, TF
101 -> 2 instructions, TT
110 -> 1 instruction, T
111 -> 4 instructions, TTTT
# Conditional executes up to 4 instructions, based upon the condition.
# The condition is evaluated and IF mask on SR is loaded based on
# the Condition Mask.
# So a T flag means that the corrisponding instruction will be executed,
# while an F flag means that it will be skipped.
# 1 instruction: the condition is forced to True,
# so it will be executed only if the condition is true.
# Example:
# IFEQ T -> for the next instruction the EQ condition must match
# 2 instructions: for the first instruction the condition must be true,
# the second can be True or false.
# Examples:
# IFEQ TF
# IFEQ TT
# 3 instructions: for the first instruction the condition must be true,
# the second and third can be True or false.
# Examples:
# IFEQ TFF
# IFEQ TFT
# IFEQ TTF
# IFEQ TTT
# 4 instructions: for all 4 instructions the condition must be true.
# Example:
# IFEQ TTTT
XCHG Dx, Dy
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 1 0 1 0 |Dx |Dy |
XCHG Ax, Ay
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 1 0 1 1 |Ax |Ay |
QMOVES SInt3, Dn
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 1 1 0 0 |Dn |SInt3 |
SInt3
# Dn.Q = Sign-extended SInt3
MOVE.Size SR, Dn
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 1 1 0 1 0 |Size |Dn |
MOVE.Size Dn, SR
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 1 1 0 1 1 |Size |Dn |
SWAP.Size Dn
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 1 1 1 0 0 0 |Size|Dn |
Size: 0 -> Long, 1 = Quad
# SWAPs the two words for the (lower) Long, or the two longs for the Quad
BSWAP.Size Dn
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 1 1 1 0 0 1 |Size|Dn |
Size: 0 -> Long, 1 = Quad
# Byte SWAP. Swaps the bytes for the Long, or the Quad
# For Long: B3 B2 B1 B0 -> B0 B1 B2 B3
# For Quad: B7 B6 B5 B4 B3 B2 B1 B0 -> B0 B1 B2 B3 B4 B5 B6 B7
TRAPcc
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 1 1 1 0 1 0 |Condition Code |
# Calls the trap vector if the Condition code is satisfied
MOVE FP, An
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 1 1 1 0 1 1 0 |An |
MOVE An, FP
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 1 1 1 0 1 1 1 |An |
BSWAP.W Dn
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 1 1 1 1 0 0 0 |Dn |
# Byte SWAP. Swaps the bytes for the Word: B1 B0 -> B0 B1
EXT.B Dn
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 1 1 1 1 0 0 1 |Dn |
# Sign-extend the Byte in Dn to the Quad
EXT.W Dn
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 1 1 1 1 0 1 0 |Dn |
# Sign-extend the Word in Dn to the Quad
EXT.L Dn
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 1 1 1 1 0 1 1 |Dn |
# Sign-extend the Long in Dn to the Quad
PUSH SR
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 1 1 1 1 1 0 0 0 |Size |
POP SR
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 1 1 1 1 1 0 0 1 |Size |
PUSH FP
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 1 1 1 1 1 0 0 1 0 0
POP FP
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 1 1 1 1 1 0 0 1 0 1
RTS
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 1 1 1 1 1 0 0 1 1 0
# ReTurn from Subroutine
RTR
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 1 1 1 1 1 0 0 1 1 1
# ReTurn from subroutine, restoring SR
ILLEGAL
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 1 1 1 1 1 0 1 0 0 0
# Calls the ILLEGAL vector
NOP
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 1 1 1 1 1 0 1 0 0 1
UNLINK
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 1 1 1 1 1 0 1 0 1 0
# Unlinks the stack (freeing allocated stack space for locals). The steps are:
# 1) SP = FP
# 2) POP FP (restores the old FP from stack, removing it from stack)
BKPT
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 1 1 1 1 1 0 1 0 1 1
# Calls the BreaKPoinT vector
SETFR FRn
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 1 1 1 1 1 0 1 1 |FRn |
# Sets the Flags Register. FR0 = FRn
*** RESERVED! ***
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 1 1 1 1 1 1 0 * * *
RESET
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 1 1 1 1 1 1 1 0 0 0
STOP
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 1 1 1 1 1 1 1 0 0 1
# Stops execution until interrupt
RTE
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 1 1 1 1 1 1 1 0 1 0
# ReTurn from Exception
*** RESERVED! ***
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 1 1 1 1 1 1 1 0 1 1
*** RESERVED! ***
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 1 1 1 1 1 1 1 1 * *
cdimauro
21-06-2010, 21:46
Scale(3) factor fields are defined by 3 bits:
000 -> 1
001 -> 2
010 -> 4
011 -> 8
100 -> 16
101 -> 32
110 -> 64
111 -> 128
****************************************************************
Addressing Modes (4 bits mode)
Mode | Register | Syntax
0000 | Reg. no | Dn | Data register
0001 | Reg. no | An | Address register
0010 | Reg. no | [An] | Indirect address
0011 | Reg. no | [An, SInt16] | Indirect address with signed 16 bits offset
0100 | Reg. no | [An]+ | Indirect address with postincrement
0101 | Reg. no | -[An] | Indirect address with predecrement
0110 | Reg. no | [An, Xn.Size * Scale, SInt8] | Indirect address with sign-extended scaled index and signed 8 bits offset
0111 | 000 | [PC, UInt16] | Indirect PC with signed 16 bits offset
0111 | 001 | [PC, Xn.Size * Scale, SInt8] | Indirect PC with sign-extended scaled index and signed 8 bits offset
0111 | 010 | [SP, UInt16] | Indirect SP with unsigned 16 bits offset
0111 | 011 | [SP, Xn.Size * Scale, UInt8] | Indirect SP with zero-extended scaled index and unsigned 8 bits offset
0111 | 100 | [FP, UInt16] | Indirect FP with unsigned 16 bits offset
0111 | 101 | Int8*/16/32/64 | Immediate
0111 | 110 | [SP]+ | Indirect SP with postincrement
0111 | 111 | -[SP] | Indirect SP with predecrement
1000 | Reg. no | [An, Xn.Size * Scale, SInt8]+ | Indirect address with sign-extended scaled index and signed 8 bits offset
1001 | Reg. no | [An, Xn.Size * Scale3, SInt23]+ | Indirect address with sign-extended scaled3 index and signed 23 bits offset
1010 | Reg. no | [An, SInt32]+ | Indirect address with signed 32 bits offset
1011 | Reg. no | [An, SInt32] | Indirect address with signed 32 bits offset
1100 | Reg. no | [An]- | Indirect address with postdecrement
1101 | Reg. no | +[An] | Indirect address with preincrement
1110 | Reg. no | [An, Xn.Size * Scale3, SInt23] | Indirect address with sign-extended scaled3 index and signed 23 bits offset
1111 | 000 | [PC, SInt32] | Indirect PC with signed 32 bits offset
1111 | 001 | [PC, Xn.Size * Scale3, SInt23] | Indirect PC with sign-extended scaled3 index and signed 23 bits offset
1111 | 010 | [SP, UInt32] | Indirect SP with unsigned 32 bits offset
1111 | 011 | [SP, Xn.Size * Scale3, UInt23] | Indirect SP with zero-extended scaled3 index and unsigned 8 bits offset
1111 | 100 | [FP, UInt32] | Indirect FP with unsigned 32 bits offset
1111 | 101 | | RESERVED
0111 | 110 | [UInt32] | Absolute unsigned int 32 bits
0111 | 111 | [UInt64] | Absolute unsigned int 64 bits
Xn = Dn or An
*Int8 is always stored as word (16 bits), with the upper byte that SBZ.
Addressing modes 1000, 1001 and 1010 update the An register with the EA. For example:
[An, Xn.Size * Scale, SInt8]+ -> An = An + Xn.Size * Scale + SInt8
Extension word (for scaled index addressing modes):
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
D/A |Register |Size |Scale3 |7 high bits offset |
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
16 low bits offset
D/A -> 0 = Data, 1 = Address
****************************************************************
32 bits Opcode Table
BINOP1[S|Z] Dn, Source EA.Size, Destination EA.Size
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 1 0 0 |Dn |Dest. Mode |D. Register|
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
SExt|Binary Operation 1 |Dest. Size|Src. Size|Source Mode |Source Reg.|
Src. Size: Source Size
Dest. Size: Destination Size
SExt: 0 -> Zero extend operand; 1 -> Sign extend operand
Binary Operation 1:
0000 SUB
0001 SUBX
0010 RSUB
0011 RSUBX
0100 DIV # SExt is used to calculate signed or unsigned division
0101 RDIV # SExt is used to calculate signed or unsigned division
0110 MOD # SExt is used to calculate signed or unsigned modulo
0111 RMOD # SExt is used to calculate signed or unsigned modulo
1000 AND
1001 NAND
1010 OR
1011 XOR
1100 ADD
1101 ADDX
1110 MUL # SExt is used to calculate signed or unsigned multiplication
1111 BOOLMUL # "Boolean" multiplication. Returns 0 if Dn is 0, Source EA otherwise
# Example: RSUBS D0, [A0, D1.Q * 128, $7F0123]+.W, -[SP].L
# Takes the word (.W) at address A0 + D1.Q * 128 + $7F0123,
# saves the target address in A0 ([]+ address mode),
# sign extend (S instruction suffix) the word to long (.L)
# subtract Dn.L (destination .L is shared with Dn) to the long
# and pushes the result into the stack (-[SP])
# If the stack granularity is quad, a quad word is pushed instead
# (the high 32 bits will be zeroed).
BINOP2[S|Z] Dn, Source EA.Size, Destination EA.Size
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 1 0 1 |Dn |Dest. Mode |D. Register|
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
SExt|Binary Operation 2 |Dest. Size|Src. Size|Source Mode |Source Reg.|
Src. Size: Source Size
Dest. Size: Destination Size
SExt: 0 -> Zero extend operand; 1 -> Sign extend operand
Binary Operation 2:
0000 LSL
0001 LSR
0010 ASL
0011 ASR
0100 ROL
0101 ROR
0110 ROLX
0111 RORX
1000 MIN
1001 MAX
1010
1011
1100
1101
1110
1111
QBINOP1[S|Z] Dn, Source EA.Size, Destination EA.Size
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 1 1 0 |Dn |0 |SInt6 |
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
SExt|Binary Operation 1 |Dest. Size|Src. Size|Source Mode |Source Reg.|
# "Quick" version of BINOP1.
# SInt6: -4..59
QBINOP2[S|Z] Dn, Source EA.Size, Destination EA.Size
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 1 1 0 |Dn |1 |SInt6 |
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
SExt|Binary Operation 2 |Dest. Size|Src. Size|Source Mode |Source Reg.|
# "Quick" version of BINOP2.
# SInt6: -4..59
UNOP[S|Z] Source EA.Size, Destination EA.Size
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 1 1 1 0 0 0 |Dest. Mode |Dest. Reg. |
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
SExt|Unary Operation |Dest. Size|Src. Size|Source Mode |Source Reg.|
Src. Size: Source Size
Dest. Size: Destination Size
SExt: 0 -> Zero extend operand; 1 -> Sign extend operand
Un. Operation:
0000 MOVE
0001 NOT
0010 BOOL # Returns 0 if the value is 0, 1 otherwise
0011 MASK # Returns 0 if the value is 0, -1 otherwise
0100 SIGN # Returns -1, 0, or 1 based on the source value
0101 ABS
0110 NEG
0111 NEGX # - Source EA - XFlag
1000 POPCNT # Number of bits set.
1001 LZCNT # Number of leading zero bits
1010
1011
1100
1101
1110
1111
CMPOP[S|Z] Source EA.Size, Destination EA.Size
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 1 1 1 0 0 1 0 0 0 0 |Dest. Reg. |
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
SExt|Compare Operation |Dest. Size|Src. Size|Source Mode |Source Reg.|
Src. Size: Source Size
Dest. Size: Destination Size
SExt: 0 -> Zero extend operand; 1 -> Sign extend operand
Compare Operation:
0000 CHK # Checks if 0 <= Source EA < Dn; sets carry and X if not in bounds
0001 TRAPCHK # Sames as CHK, but calls the check vector if outbounds
0010 CMP
0011 TESTMASK # Destination register Binary And Source EA
0100
0101
0110
0111
1000
1001
1010
1011
1100
1101
1110
1111
LINK UInt16
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 1 1 1 0 0 1 0 0 0 1 0 0 0
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
UInt16
# Links the FP to the stack, adding space for locals. The steps are:
# 1) PUSH FP (saves old FP)
# 2) SP = FP
# 3) SP = SP - UInt16 * MSA (allocates space for locals)
RTD UInt16
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 1 1 1 0 0 1 0 0 0 1 0 0 1
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
UInt16
# ReTurn from subroutine and Deallocate (space). The steps are:
# PC = [SP]+
# SP = SP + UInt16 * MSA
SYSCALL UInt16
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 1 1 1 0 0 1 0 0 0 1 0 1 0
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
UInt16
# SYStem CALL entry point UInt16
BITOPI UInt6, Source EA
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 1 1 1 0 0 1 0 0 0 1 0 1 1
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 |UInt6 |Bit Op. |Dest Mode |Dest. Reg. |
Bit Op.:
00 BTST # Bit TeST
01 BCHG # Bit CHanGe
10 BCLR # Bit CLeaR
11 BSET # Bit SET
BITOP Dn, Source EA
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 1 1 1 0 0 1 0 0 0 1 0 1 1
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 0 0 0 |Dn |Bit Op. |Dest Mode |Dest. Reg. |
Bit Op.:
00 BTST # Bit TeST
01 BCHG # Bit CHanGe
10 BCLR # Bit CLeaR
11 BSET # Bit SET
SMOVE Register, Sn
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 1 1 1 0 0 1 0 0 0 1 0 1 1
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 0 0 1 0 |D/A |Register | |
# Move Register to Supervisor register (0..127)
D/A -> 0 = Data, 1 = Address
SMOVE Sn, Rn
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 1 1 1 0 0 1 0 0 0 1 0 1 1
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 0 0 1 1 |D/A |Register | |
# Move Supervisor register (0..127) to Register
D/A -> 0 = Data, 1 = Address
*** RESERVED! ***
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 1 1 1 0 0 1 0 0 0 1 0 1 1
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 0 1 *
SETcc.Size Destination EA.Size
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 1 1 1 0 0 1 0 0 0 1 0 1 1
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 0 |Condition Code |Dest.Size|Dest Mode |Dest. Reg. |
# SETs Destination EA to 1 if condition matches, to 0 otherwise
XCHG.Size Register, Destination EA.Size
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 1 1 1 0 0 1 0 0 0 1 0 1 1
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 |D/A |Register |Dest.Size|Dest Mode |Dest. Reg. |
# eXCHange Register with Destination EA
D/A -> 0 = Data, 1 = Address
*** RESERVED! ***
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 1 1 1 0 0 1 0 0 0 1 1 * *
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
DBcc.Size Dn, UInt10 (* 2)
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 1 1 1 0 0 1 0 0 1 0 |Register |
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Size |Condition Code |UInt10 |
#Decrement Dn and branch if Dn != 0 or Condition Code matches.
#PC = PC + UInt10 * 2 if branch taken
DBRA.Size Dn, UInt14 (* 2)
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 1 1 1 0 0 1 0 0 1 1 |Register |
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Size |UInt14 |
#Decrement Dn and branch if Dn != 0.
#PC = PC + UInt14 * 2 if branch taken
*** RESERVED! ***
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 1 1 1 0 0 1 0 1 * * * * *
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
QCMPOP[S|Z] Source EA.Size, Destination EA.Size
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 1 1 1 0 0 1 1 |SInt6 |
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
SExt|Compare Operation |Dest. Size|Src. Size|Source Mode |Source Reg.|
# "Quick" version of CMPOP.
# SInt6: -4..59
MOVEM Register List, Destination EA
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 1 1 1 0 1 0 |Dest. Mode |Dest. Reg. |
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Register List = 16 bits mask
# MOVE Multiple registers to Destination EA
MOVEM Source EA, Register List
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 1 1 1 0 1 1 |Source. Mode |Sour. Reg. |
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Register List = 16 bits mask
# MOVE Multiple registers from Source EA
*** RESERVED! ***
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 1 1 1 1 0 * * * * * * * *
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
*** COPROCESSOR INTERFACE ***
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 1 1 1 1 1 * * * * * * * *
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
cdimauro
21-06-2010, 23:03
Beh, è la lingua franca dell'informatica. Anche nei miei sorgenti i commenti sono quasi sempre in inglese (al limite con qualche frase in siciliano stretto :asd:).
E' un problema (a parte gli orrori grammaticali)? :stordita:
No, non è un problema ma non so nemmeno da dove cominciare a fare le domande... :stordita:
cdimauro
21-06-2010, 23:51
In genere si parte dall'ISA, ma... domani, perché sto cascando dal sonno. :(
'Notte a tutti. :)
Prima di discutere su registri e funzionalità dei registri, non sarebbe meglio discutere delle modalità di indirizzamento e del conseguente formato degli opcode. La definizione dei registri sarà creata di conseguenza.
Ad esempio: esisteranno opcode con operatori a 16, 32 e 64 bit ?
Wow, un set di istruzioni veramente impressionante! :eek:
Lo sai che non mi piacciono i RISC. :D
Io ci provo :D
In ogni caso ci sono 2 considerazioni da fare. La prima è che stai usando 2 registri, per cui te ne rimarranno 14 "general purpose".
Sono sempre più di 8 :p
La seconda è che con questi trucchetti usando il PC rischi di limitare la scalabilità del processore, visto che il PC è l'elemento che cambia più spesso, ed è determinante per proseguire col flusso delle istruzioni.
Hai assolutamente ragione. Mi hai convinto a separare i registri :D
Mumble. Non ho capito questi due esempi in base al contesto di cui stavamo discutendo. :stordita:
Intendevo dire che anche se abbiamo 2 alu separate (una per indirizzi e una per dati), possiamo cmq dargli in pasto qualsiasi registro, non serve per forza dividerli in 2 gruppi dati-indirizzi.
L'idea è quella di "intercettare" i 4 bit alti dell'opcode a 16 bit. Se sono tutti a 1, allora l'opcode è in realtà a 32 bit, altrimenti è a 16 bit (e verrà semplicemente ignorata la seconda word in fase di decodifica).
E' chiaro che in questo modo i 4 bit alti di un opcode a 32 bit sono sempre a 1111 e, quindi, inutilizzabili per mappare qualche campo. Di fatto gli opcode a 32 bit in realtà sono a 28 bit (che sono i bit utilizzabili a tutti gli effetti).
Ah bene, un po' come avevo pensato io per la mia codifica simil-risc a 16 bit:
Formato 16 bit:
(Ra, Rb, Rc = registri operandi)
Tipo 0:
+---------+---------+
| opcode |
+---------+---------+
Tipo 1:
+---------+----+----+
| opcode | Ra |
+---------+----+----+
Tipo 2:
+---------+----+----+
| opcode | Rb | Ra |
+---------+----+----+
Tipo 3:
+----+----+----+----+
| op | Rc | Rb | Ra |
+----+----+----+----+
Nota:
Gli opcode 1111, 1110, 1101 del tipo 3 sono riservati, per scegliere gli altri 3 tipi di modalità:
1111 -> opcode tipo 0
1110 -> opcode tipo 1
1101 -> opcode tipo 2
Volevo cercare di identificare dei blocchetti fissi (in questo caso 4 bit), in modo da semplificare la decodifica, ma mi sono imbattuto negli ovvi limiti di spazio :stordita:
Prima di discutere su registri e funzionalità dei registri, non sarebbe meglio discutere delle modalità di indirizzamento e del conseguente formato degli opcode. La definizione dei registri sarà creata di conseguenza.
Ad esempio: esisteranno opcode con operatori a 16, 32 e 64 bit ?
E' una buona idea, ma visto che Cesare aveva già fatto abbastanza roba, volevo prima vedere cosa aveva già fatto.
Cmq, si, esistono opcode con dati anche da 8 bit, e se c'è spazio, anche singoli bit ;)
Ora comincio a scrivere le molteplici domande sulle istruzioni :D
64K (64000) ISA
Guarda, non si capisce proprio da dove hai preso ispirazione :D
FR0 = Current Flags
FR1 = Previous Flags
FR2 = Pre-Previous Flags
FR3 = Pre-Pre-Previous Flags
Accidenti, io volevo i flag di cinque istruzioni fa :Prrr:
Extension word (for scaled index addressing modes):
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
D/A |Register |Size |Scale | 8 bits offset |
D/A -> 0 = Data, 1 = Address
L'estensione viene messa subito dopo l'opcode ma subito prima di altri immediati? Quindi praticamente è come un opcode a 32 bit?
Bcc SInt8 (* 2)
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 0 0 | Condition Code |SInt8 |
# Branch on Condition Code.
# If CC, PC = PC + SInt8 * 2; otherwise PC = PC + 2
E se qualcuno deve saltare ad un indirizzo ad un offset dispari?
Cioè (è un esempio stupido):
<codice>
1000 BEQ etich
1002 MOV.B 5, D0
1005 MOV.W 47, D1
1009 MOV.W 136, D2
1013 MOV.W 234, D3
1017 MOV.W 262, D4
1021 etich: <codice>
In questo caso l'etichetta è a 21 byte dal branch. Oppure il MOV.B deve essere cmq seguito da un'intera word di dati, di cui solo 8 bit sono presi?
000 -> 3 instructions, TFF
001 -> 3 instructions, TFT
010 -> 3 instructions, TTF
011 -> 3 instructions, TTT
100 -> 2 instructions, TF
101 -> 2 instructions, TT
110 -> 1 instruction, T
111 -> 4 instructions, TTTT
C'è un ragionamento sotto questa disposizione? Perchè io metterei
000 -> 4 instructions, TTTT
001 -> 1 instruction, T
010 -> 2 instructions, TF
011 -> 2 instructions, TT
100 -> 3 instructions, TFF
101 -> 3 instructions, TFT
110 -> 3 instructions, TTF
111 -> 3 instructions, TTT
I bit così rappresentano meglio la verità/falsità delle istruzioni (secondo me)
BSWAP.Size Dn
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 1 1 1 0 0 1 |Size|Dn |
Size: 0 -> Long, 1 = Quad
# Byte SWAP. Swaps the bytes for the Long, or the Quad
# For Long: B3 B2 B1 B0 -> B0 B1 B2 B3
# For Quad: B7 B6 B5 B4 B3 B2 B1 B0 -> B0 B1 B2 B3 B4 B5 B6 B7
Che istruzione curiosa, in cosa potrebbe essere utile?
MOVEM
PUSHM
POPM
Sono istruzioni veramente complesse. Bisognerebbe implementare dei cicli all'interno del microcodice e, in caso di pipeline, bisognerebbe bloccarla nello stadio di scrittura/lettura memoria. Della loro comodità ovviamente non si discute.
Mi puoi anche chiarire il significato dei BTST, BCHG, BCLR, BSET che usano Dn come parametro? sopratutto, cosa contiene Dn?
:cry: non riesco a far stare le istruzioni nella mia codifica a 16 bit! :cry:
(almeno ho fatto una MOV veramente potente :D)
cdimauro
22-06-2010, 13:43
Prima di discutere su registri e funzionalità dei registri, non sarebbe meglio discutere delle modalità di indirizzamento e del conseguente formato degli opcode. La definizione dei registri sarà creata di conseguenza.
Io già fatto tutto. :D
Ad esempio: esisteranno opcode con operatori a 16, 32 e 64 bit ?
Nella mia ISA sì: puoi manipolare dati del "taglio" che ti serve. Quindi non esclusivamente a 64 bit.
Wow, un set di istruzioni veramente impressionante! :eek:
Sarà più impressionante la tabella degli opcode a 32 bit, quando sarà finita. :D
Io ci provo :D
Ma con le idee che hai realizzerai un CISC, e non un RISC.
CISC = opcode a lunghezza variabile. RISC = opcode a lunghezza fissa. Non si scappa. :fagiano:
Sono sempre più di 8 :p
Quello era scontato, ma a mio avviso 20 registri, di cui 16 ortogonali due a due, è un ottimo compromesso (32 registri sarebbero troppi).
Intendevo dire che anche se abbiamo 2 alu separate (una per indirizzi e una per dati), possiamo cmq dargli in pasto qualsiasi registro, non serve per forza dividerli in 2 gruppi dati-indirizzi.
Questo è ovvio. Le ALU ricevono di dati da elaborare, ma non conoscono la fonte.
Ah bene, un po' come avevo pensato io per la mia codifica simil-risc a 16 bit:
Formato 16 bit:
(Ra, Rb, Rc = registri operandi)
Tipo 0:
+---------+---------+
| opcode |
+---------+---------+
Tipo 1:
+---------+----+----+
| opcode | Ra |
+---------+----+----+
Tipo 2:
+---------+----+----+
| opcode | Rb | Ra |
+---------+----+----+
Tipo 3:
+----+----+----+----+
| op | Rc | Rb | Ra |
+----+----+----+----+
Nota:
Gli opcode 1111, 1110, 1101 del tipo 3 sono riservati, per scegliere gli altri 3 tipi di modalità:
1111 -> opcode tipo 0
1110 -> opcode tipo 1
1101 -> opcode tipo 2
Volevo cercare di identificare dei blocchetti fissi (in questo caso 4 bit), in modo da semplificare la decodifica, ma mi sono imbattuto negli ovvi limiti di spazio :stordita:
Tranquillo: si fa sempre così. Altrimenti si impazzirebbe. ;)
E' una buona idea, ma visto che Cesare aveva già fatto abbastanza roba, volevo prima vedere cosa aveva già fatto.
Cmq, si, esistono opcode con dati anche da 8 bit, e se c'è spazio, anche singoli bit ;)
Le istruzioni BTST, BCHG, BSET e BCLR manipolano singoli bit, infatti (Motorola docet). ;)
Ora comincio a scrivere le molteplici domande sulle istruzioni :D
GLOM. :|
Guarda, non si capisce proprio da dove hai preso ispirazione :D
:angel:
FR0 = Current Flags
FR1 = Previous Flags
FR2 = Pre-Previous Flags
FR3 = Pre-Pre-Previous Flags
Accidenti, io volevo i flag di cinque istruzioni fa :Prrr:
Aggiungi un byte al registro SR allora. :sborone:
Extension word (for scaled index addressing modes):
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
D/A |Register |Size |Scale | 8 bits offset |
D/A -> 0 = Data, 1 = Address
L'estensione viene messa subito dopo l'opcode ma subito prima di altri immediati? Quindi praticamente è come un opcode a 32 bit?
L'estensione (estensioni nel caso della MOVE, perché supporta un EA sorgente e un EA destinazione) viene aggiunta subito dopo l'opcode; nel caso di due EA, prima viene l'estensione sorgente e poi quella destinazione.
Bcc SInt8 (* 2)
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 0 0 | Condition Code |SInt8 |
# Branch on Condition Code.
# If CC, PC = PC + SInt8 * 2; otherwise PC = PC + 2
E se qualcuno deve saltare ad un indirizzo ad un offset dispari?
Cioè (è un esempio stupido):
<codice>
1000 BEQ etich
1002 MOV.B 5, D0
1005 MOV.W 47, D1
1009 MOV.W 136, D2
1013 MOV.W 234, D3
1017 MOV.W 262, D4
1021 etich: <codice>
In questo caso l'etichetta è a 21 byte dal branch. Oppure il MOV.B deve essere cmq seguito da un'intera word di dati, di cui solo 8 bit sono presi?
Esatto. Gli opcode sono multipli di 16 bit, quindi le istruzioni sono sempre allineate a 2 byte (anche qui, Motorola docet).
Solo che, a differenza di Motorola, io sfrutto quest'informazione per amplicare il range dell'offset (qui, invece, è wpython docet :D).
000 -> 3 instructions, TFF
001 -> 3 instructions, TFT
010 -> 3 instructions, TTF
011 -> 3 instructions, TTT
100 -> 2 instructions, TF
101 -> 2 instructions, TT
110 -> 1 instruction, T
111 -> 4 instructions, TTTT
C'è un ragionamento sotto questa disposizione?
Sì: ho applicato un algoritmo gready. :p
Perchè io metterei
000 -> 4 instructions, TTTT
001 -> 1 instruction, T
010 -> 2 instructions, TF
011 -> 2 instructions, TT
100 -> 3 instructions, TFF
101 -> 3 instructions, TFT
110 -> 3 instructions, TTF
111 -> 3 instructions, TTT
I bit così rappresentano meglio la verità/falsità delle istruzioni (secondo me)
A me sembra uguale. Perché tanto quello che fa saltare l'omogeneità è il caso delle 4 istruzioni tutte True.
Comunque stamattina m'è venuto in mente un altro modo, molto più efficiente, di implementare quest'istruzione (che rimane esattamente la stessa come opcode; cambia l'apposita porzione del registro SR e come la si usa).
BSWAP.Size Dn
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 1 1 1 0 0 1 |Size|Dn |
Size: 0 -> Long, 1 = Quad
# Byte SWAP. Swaps the bytes for the Long, or the Quad
# For Long: B3 B2 B1 B0 -> B0 B1 B2 B3
# For Quad: B7 B6 B5 B4 B3 B2 B1 B0 -> B0 B1 B2 B3 B4 B5 B6 B7
Che istruzione curiosa, in cosa potrebbe essere utile?
Per convertire un valore da big endian a little endian, o viceversa. Ad esempio, se stai lavorando in little endian ma devi leggere dati memorizzati in big endian.
MOVEM
PUSHM
POPM
Sono istruzioni veramente complesse. Bisognerebbe implementare dei cicli all'interno del microcodice e, in caso di pipeline, bisognerebbe bloccarla nello stadio di scrittura/lettura memoria. Della loro comodità ovviamente non si discute.
Sì, la pipeline in questi casi viene bloccata (non svuotata, che è quello che interessa di più), ma perché c'è un vantaggio notevole nell'usarle.
Non credere, comunque, che si tratti di roba esclusiva CISC: le MOVEM sono presenti in tutti i PowerPC e in tutti gli ARM.
Mi puoi anche chiarire il significato dei BTST, BCHG, BCLR, BSET che usano Dn come parametro? sopratutto, cosa contiene Dn?
Dn contiene il numero del bit da testare o modificare.
:cry: non riesco a far stare le istruzioni nella mia codifica a 16 bit! :cry:
(almeno ho fatto una MOV veramente potente :D)
Addio RISC...
Ma con le idee che hai realizzerai un CISC, e non un RISC.
CISC = opcode a lunghezza variabile. RISC = opcode a lunghezza fissa. Non si scappa. :fagiano:
Non sono un estremista, mi basta che "opcode a lunghezza fissa" significhi "pochi o meglio nessun byte di prefisso o suffisso" (fatta eccezione per i dati immediati). E' per questo che il tuo set mi è piaciuto subito :D
(Poi, tecnicamente, non è detto che un RISC debba avere istruzioni della stessa lunghezza, basta che ogni istruzione sia breve e veloce nell'esecuzione, no?)
Esatto. Gli opcode sono multipli di 16 bit, quindi le istruzioni sono sempre allineate a 2 byte (anche qui, Motorola docet).
Solo che, a differenza di Motorola, io sfrutto quest'informazione per amplicare il range dell'offset (qui, invece, è wpython docet :D).
Molto bene.
A me sembra uguale. Perché tanto quello che fa saltare l'omogeneità è il caso delle 4 istruzioni tutte True.
Ma per quello basta una AND a 3 ingressi: se l' and dice di no, allora si possono sparare direttamente i bit nell' opcode direttamente in quelli dello SR, senza bisogno di decodificarli.
Comunque stamattina m'è venuto in mente un altro modo, molto più efficiente, di implementare quest'istruzione (che rimane esattamente la stessa come opcode; cambia l'apposita porzione del registro SR e come la si usa).
Aspetto con pazienza :D
Non credere, comunque, che si tratti di roba esclusiva CISC: le MOVEM sono presenti in tutti i PowerPC e in tutti gli ARM.
No no, non mi preoccupavo della religione dell'architettura (:D), mi preoccupavo come effettivamente implementarla.
Addio RISC...
Veramente ho preso ispirazione dalle tue modalità (ops, volevo dire, dalle modalità Motorola :Prrr:), solo estendendo i campi registro ad un bit in più, in modo da eliminare la distinzione D/A :stordita:
Edit:
Ops, ho visto ora un'istruzione che mi era sfuggita:
XXX Source EA *** RESERVED! ***
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 0 1 1 1 |Source. Mode |Sour. Reg. |
Bella un'istruzione con solo la sorgente e non la destinazione :D
Edit2: Altra domanda: non si può usare un cmp solo? Basterebbe solo dopo modificare l'eventuale salto in modo da rispecchiare la differenza degli elementi. A meno che non ci sia qualche codice dove usi un cmp per modificare i flag in modo da variare successive istruzioni (che non siano salti), ma al momento non mi viene in mente nulla.
cdimauro
22-06-2010, 20:58
Non sono un estremista, mi basta che "opcode a lunghezza fissa" significhi "pochi o meglio nessun byte di prefisso o suffisso" (fatta eccezione per i dati immediati). E' per questo che il tuo set mi è piaciuto subito :D
Grazie. :)
Un altro vantaggio, rispetto a 68000 e x86, è che dalla decodifica dell'opcode a 32 bit (quello a 16 bit si può sempre vedere come uno a 32 bit, come detto prima) si conosce immediatamente la lunghezza dell'intera istruzione.
Comunque vorrei, sempre per agevolare il decoder, mettere un vincolo alla lunghezza massima di un'istruzione imponendo un massimo di 16 byte (8 word a 16 bit). Attualmente il massimo che si può avere è: opcode a 32 bit + valore immediato a 64 bit + indirizzo assoluto a 64 bit. Non penso che sia una restrizione così grave, perché i casi di istruzioni così lunghe non sono frequenti.
(Poi, tecnicamente, non è detto che un RISC debba avere istruzioni della stessa lunghezza, basta che ogni istruzione sia breve e veloce nell'esecuzione, no?)
La lunghezza, variabile o fissa, degli opcode è proprio LA discriminante fra un CISC e un RISC. :D
Un altro elemento di netta discriminazione è la presenza delle sole istruzioni di LOAD / STORE per interfacciarsi con la memoria, mentre i CISC permettono di avere istruzioni più complesse (aritmentiche, logiche, ecc.) che possono andare al contempo a leggere / scrivere dalla memoria.
Ma per quello basta una AND a 3 ingressi: se l' and dice di no, allora si possono sparare direttamente i bit nell' opcode direttamente in quelli dello SR, senza bisogno di decodificarli.
Io sono sempre orientato a una LUT per implementare il tutto velocemente e senza pensieri. :D
Comunque col tuo schema si può effettivamente ricavare la maschera degli IF con pochi gate.
Aspetto con pazienza :D
Ho aggiornato il primo messaggio con la nuova struttura del registro SR e la spiegazione di come vengono eseguite le istruzione, e il secondo con l'aggiornamento della parte relativa all'istruzione IF.
No no, non mi preoccupavo della religione dell'architettura (:D), mi preoccupavo come effettivamente implementarla.
Microcodice. :fagiano:
Veramente ho preso ispirazione dalle tue modalità (ops, volevo dire, dalle modalità Motorola :Prrr:), solo estendendo i campi registro ad un bit in più, in modo da eliminare la distinzione D/A :stordita:
Sì, ma con opcode a 16 bit ti mangi tutto lo spazio così.
Io con 3 soli bit per i registri ho già la tabella quasi completamente satura (c'è giusto spazio per una decina di istruzioni non dotate di campi).
Edit:
Ops, ho visto ora un'istruzione che mi era sfuggita:
XXX Source EA *** RESERVED! ***
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
1 1 1 0 1 1 0 1 1 1 |Source. Mode |Sour. Reg. |
Bella un'istruzione con solo la sorgente e non la destinazione :D
Questo è uno spazio che ho lasciato vuoto per un'istruzione a cui serva soltanto un operando che ho supposto essere di sola lettura / sorgente (ma potrebbe essere cambiato in scrittura / destinazione).
Comunque le istruzioni jump hanno tutte una sorgente, ma nessuna destinazione. :D
Nella letteratura Motorola serve a distinguere in che modo interpretare la modalità d'indirizzamento. Questo perché per la "sorgente" si può sfruttare qualunque modalità, mentre per quella destinazione viene sollevata un'eccezione in presenza di indirizzamento immediato oppure di uno che utilizzi il PC come registro indirizzo.
Edit2: Altra domanda: non si può usare un cmp solo?
In che senso?
Basterebbe solo dopo modificare l'eventuale salto in modo da rispecchiare la differenza degli elementi. A meno che non ci sia qualche codice dove usi un cmp per modificare i flag in modo da variare successive istruzioni (che non siano salti), ma al momento non mi viene in mente nulla.
Negli opcode a 32 bit introdurrò una speciale istruzione di confronto + salto condizionato. Il confronto servirà soltanto per "sbrogliare" il salto, quindi senza modificare i flag.
Inoltre penso a una versione "quick" del confronto + salto, con un valore immediato "corto" (similmente a QCMP, che accetta costanti da -1 a 2).
Questo sempre per includere dei casi comuni e che possono mettere in crisi la pipeline (se un'istruzione deve dipendere dall'analisi dei flag generati da un'altra).
Un altro vantaggio, rispetto a 68000 e x86, è che dalla decodifica dell'opcode a 32 bit (quello a 16 bit si può sempre vedere come uno a 32 bit, come detto prima) si conosce immediatamente la lunghezza dell'intera istruzione.
Comunque vorrei, sempre per agevolare il decoder, mettere un vincolo alla lunghezza massima di un'istruzione imponendo un massimo di 16 byte (8 word a 16 bit). Attualmente il massimo che si può avere è: opcode a 32 bit + valore immediato a 64 bit + indirizzo assoluto a 64 bit. Non penso che sia una restrizione così grave, perché i casi di istruzioni così lunghe non sono frequenti.
Bene, così possiamo provvedere ad un buffer interno di 16 byte, e la seconda lettura a 64 bit viene eseguita durante la decodifica dell'istruzione; sarà poi quest'ultima a dire quanti bit estrarre dal buffer, i rimanenti verranno shiftati in modo da avere già pronta la prossima istruzione.
La lunghezza, variabile o fissa, degli opcode è proprio LA discriminante fra un CISC e un RISC. :D
Questo perchè tutti i processori risc sono nati così (sempre per semplificare il fetch/decode), ma non è LA principale differenza: nulla vieta di fare un RISC a lunghezza variabile (ovviamente sarebbe una pazzia perchè tutto il tempo risparmiato nelle istruzioni lo perdi nel fetch/decode), e nulla vieta di fare un CISC con opcode a lunghezza fissa (pure i uP moderni sono risc dentro: cisc con una istruzione da 118 bit :eek: )
Un altro elemento di netta discriminazione è la presenza delle sole istruzioni di LOAD / STORE per interfacciarsi con la memoria, mentre i CISC permettono di avere istruzioni più complesse (aritmentiche, logiche, ecc.) che possono andare al contempo a leggere / scrivere dalla memoria.
Questo è già più distintivo delle due architetture.
Microcodice. :fagiano:
Il problema è che devo aggiungere logica aggiuntiva per poter eseguire salti condizionati all'interno del microcodice stesso, troppa roba per solo 2 istruzioni; molto meglio creare una unità apposita.
Sì, ma con opcode a 16 bit ti mangi tutto lo spazio così.
Io con 3 soli bit per i registri ho già la tabella quasi completamente satura (c'è giusto spazio per una decina di istruzioni non dotate di campi).
Me ne sono accorto :cry: ho dovuto rimuovere le modalità di indirizzamento indiretto in modo da ridurre il bit di scelta modalità a 1 solo...
In che senso?
Nel senso che hai CMP.Size Source EA, Dn e CMP.Size Dn, Destination EA, non basterebbe solo la seconda?
Negli opcode a 32 bit introdurrò una speciale istruzione di confronto + salto condizionato. Il confronto servirà soltanto per "sbrogliare" il salto, quindi senza modificare i flag.
Inoltre penso a una versione "quick" del confronto + salto, con un valore immediato "corto" (similmente a QCMP, che accetta costanti da -1 a 2).
Questo sempre per includere dei casi comuni e che possono mettere in crisi la pipeline (se un'istruzione deve dipendere dall'analisi dei flag generati da un'altra).
Bene. è molto utile poter risolvere tutto con solo un'istruzione.
Cmq, pensavo: ha senso avere l' estensione per le modalità di indirizzamento più complesse? Praticamente l'opcode a 16 bit diventa a 32. Non sarebbe meglio integrare queste istruzioni nella tabella a 32 bit?
Ho provato a leggere questo thread ma mi è esploso il cervello :D
Però vi stimo parecchio per la dedizione!
cdimauro
22-06-2010, 23:25
Questo perchè tutti i processori risc sono nati così (sempre per semplificare il fetch/decode), ma non è LA principale differenza: nulla vieta di fare un RISC a lunghezza variabile (ovviamente sarebbe una pazzia perchè tutto il tempo risparmiato nelle istruzioni lo perdi nel fetch/decode), e nulla vieta di fare un CISC con opcode a lunghezza fissa
Beh, nella letteratura informatica la tipologia di opcode è stata sempre considerata la discriminante principale per le due macrofamiglie. :stordita:
(pure i uP moderni sono risc dentro: cisc con una istruzione da 118 bit :eek: )
Questa è una cosa diversa. L'ISA rimane CISC. Mentre l'unità di esecuzione interna è un RISC.
Il problema è che devo aggiungere logica aggiuntiva per poter eseguire salti condizionati all'interno del microcodice stesso, troppa roba per solo 2 istruzioni; molto meglio creare una unità apposita.
Non ho esperienza di design di un'architettura, per cui... va bene così. :D
Nel senso che hai CMP.Size Source EA, Dn e CMP.Size Dn, Destination EA, non basterebbe solo la seconda?
:doh: Il copia & incolla è la principale fonte di bug. La seconda è il sonno. La combinazione dei due, poi, è micidiale. :muro:
Grazie per la segnalazione.
A questo punto utilizzo lo spazio della seconda CMP per mappare qui la QMOVEU (così ho una costante a 8 bit a disposizione). Mentre marco come riservato il precedente spazio della QMOVEU, che a questo punto può benissimo mappare un'istruzione tipo la LEA, cioé con un registro e una modalità d'indirizzamento.
Ottimo. Lo spazio nella tabella degli opcode a 16 bit è preziosissimo. :)
Bene. è molto utile poter risolvere tutto con solo un'istruzione.
Soprattutto è un caso comune, e pure un collo di bottiglia per le prestazioni.
Cmq, pensavo: ha senso avere l' estensione per le modalità di indirizzamento più complesse? Praticamente l'opcode a 16 bit diventa a 32. Non sarebbe meglio integrare queste istruzioni nella tabella a 32 bit?
No, perché, come dicevo prima, ci possono essere due estensioni: una per l'operando sorgente, e l'altro per quello di destinazione.
Inoltre gli opcode a 32 bit hanno modalità d'indirizzamento con estensione a 32 bit (quelle con offset da 23 bit).
Per cui in ogni caso non ci rientreremmo.
@Tommo: grazie. :)
Mattyfog
23-06-2010, 09:38
@Z80Fan: tu non fai ancora l'università giusto? quindi tutte queste cose come le hai imparate?
Ho provato a leggere questo thread ma mi è esploso il cervello :D
Però vi stimo parecchio per la dedizione!Grazie :)
:doh: Il copia & incolla è la principale fonte di bug. La seconda è il sonno. La combinazione dei due, poi, è micidiale. :muro:
Grazie per la segnalazione.
A questo punto utilizzo lo spazio della seconda CMP per mappare qui la QMOVEU (così ho una costante a 8 bit a disposizione). Mentre marco come riservato il precedente spazio della QMOVEU, che a questo punto può benissimo mappare un'istruzione tipo la LEA, cioé con un registro e una modalità d'indirizzamento.
Ottimo. Lo spazio nella tabella degli opcode a 16 bit è preziosissimo. :)Che bello, mi piace ottimizzare :D
Soprattutto è un caso comune, e pure un collo di bottiglia per le prestazioni.Stavo giusto pensando che il controllo se eseguire o no un salto condizionato si potrebbe far fare all'unità di fetch, in modo da al massimo avere una sola "bolla" (quella che si viene a creare quando il fetch scarta l'istruzione attuale e impiega un altro ciclo per caricare la nuova, mentre la pipeline va avanti). In questo modo possiamo anche evitare che istruzioni eseguite a metà modifichino lo stato dell' architettura, che poi noi dovremmo risettare ecc...
No, perché, come dicevo prima, ci possono essere due estensioni: una per l'operando sorgente, e l'altro per quello di destinazione.
Inoltre gli opcode a 32 bit hanno modalità d'indirizzamento con estensione a 32 bit (quelle con offset da 23 bit).
Per cui in ogni caso non ci rientreremmo.Accidenti, ora impiegherò giorni e giorni per riuscire a codificare queste istruzioni, e alla fine scarterò tutto il lavoro (perchè non ci sono riuscito), e useremo la tua :D
@Z80Fan: tu non fai ancora l'università giusto? quindi tutte queste cose come le hai imparate?
Ehm, veramente non lo so neanche io :stordita:
Penso attraverso internet, visto che non ho libri che trattano questo argomento, e poi tramite inventiva personale :D (tante volte mi sembrava di aver inventato qualcosa, per poi vedere che esisteva già ;))
cdimauro
23-06-2010, 13:22
Che bello, mi piace ottimizzare :D
A me piacerebbe dormire un po' di più. :stordita:
Stavo giusto pensando che il controllo se eseguire o no un salto condizionato si potrebbe far fare all'unità di fetch, in modo da al massimo avere una sola "bolla" (quella che si viene a creare quando il fetch scarta l'istruzione attuale e impiega un altro ciclo per caricare la nuova, mentre la pipeline va avanti). In questo modo possiamo anche evitare che istruzioni eseguite a metà modifichino lo stato dell' architettura, che poi noi dovremmo risettare ecc...
Mi sembra che sia l'approccio di Motorola col 68060. Ne ho parlato nell'apposito articolo su AD.
Accidenti, ora impiegherò giorni e giorni per riuscire a codificare queste istruzioni, e alla fine scarterò tutto il lavoro (perchè non ci sono riuscito), e useremo la tua :D
Sicuro? Non hai ancora visto la struttura degli opcode a 32 bit. :fiufiu:
Ora cdimauro mi fucilerà, ma da un punto di vista implementativo un'architettura RISC non sarebbe stata più adatta allo scopo di questo thread ?
Prima di tutto perché la progettazione dell'ISA è più semplice, poi perché ci si può sbizzarrire sulla progettazione delle pipeline senza dover progettare prima unità di decodifica veramente complesse.
Mi sembra che sia l'approccio di Motorola col 68060. Ne ho parlato nell'apposito articolo su AD.Ecco, ogni volta che ho una buona idea, è già stata pensata!
Sicuro? Non hai ancora visto la struttura degli opcode a 32 bit. :fiufiu:Mi fido :D
Ora cdimauro mi fucilerà, ma da un punto di vista implementativo un'architettura RISC non sarebbe stata più adatta allo scopo di questo thread ?
Prima di tutto perché la progettazione dell'ISA è più semplice, poi perché ci si può sbizzarrire sulla progettazione delle pipeline senza dover progettare prima unità di decodifica veramente complesse.Quello che più mi preoccupa sono le modalità di indirizzamento decisamente complesse, che penso si possano togliere dagli opcode a 16 bit. Sto provando a semplificare questi ultimi usando solo gli indirizzamenti più semplici (troppo semplici per Cesare :D), o quelli che mi sembrano più utili o che ho usato più spesso nella mia esperienza assembly; pianifico di lasciare le cose più complesse (tipo trasferimenti memoria-memoria) nella tabella a 32 bit.
cdimauro
23-06-2010, 19:04
Ora cdimauro mi fucilerà, ma da un punto di vista implementativo un'architettura RISC non sarebbe stata più adatta allo scopo di questo thread ?
Prima di tutto perché la progettazione dell'ISA è più semplice, poi perché ci si può sbizzarrire sulla progettazione delle pipeline senza dover progettare prima unità di decodifica veramente complesse.
Questo è senza dubbio vero, ma non mi sembra ci fossero dei requisiti stringenti da questo punto di vista. Sentendomi "in libertà" ho tirato fuori dal cassetto l'ISA a cui sto lavorando da anni.
Comunque l'n-esimo progetto di CPU RISC non mi esalta. Le architetture a opcode fissi sono troppo limitate e uccidono la creatività. Alla fine si arriva a definire architetture abbastanza simili ad altre, che fanno più o meno le stesse cose.
Coi CISC (o, al limite, i CRISP), invece, uno può tirare fuori la propria immaginazione, la creatività che è tipica dei programmatori. :)
Quello che più mi preoccupa sono le modalità di indirizzamento decisamente complesse, che penso si possano togliere dagli opcode a 16 bit. Sto provando a semplificare questi ultimi usando solo gli indirizzamenti più semplici (troppo semplici per Cesare :D),
In tutta onestà, le 8 modalità d'indirizzamento del 68000 che ho "preso in prestito" mi sembravano decisamente semplici.
Perfino i RISC più moderni hanno la possibilità di utilizzare un registro base, uno indice scalato, a cui viene aggiunto anche un offset. Gli ARM e PowerPC, tra l'altro, hanno anche la possibilità di aggiornare il contenuto del registro base col calcolo dell'indirizzo effettuato (quindi ben più complesso di roba come An+ o -An).
o quelli che mi sembrano più utili o che ho usato più spesso nella mia esperienza assembly; pianifico di lasciare le cose più complesse (tipo trasferimenti memoria-memoria) nella tabella a 32 bit.
Ah, allora stai optando per un CISC: opcode a 16 bit + a 32 bit. :D
In tutta onestà, le 8 modalità d'indirizzamento del 68000 che ho "preso in prestito" mi sembravano decisamente semplici.
Perfino i RISC più moderni hanno la possibilità di utilizzare un registro base, uno indice scalato, a cui viene aggiunto anche un offset. Gli ARM e PowerPC, tra l'altro, hanno anche la possibilità di aggiornare il contenuto del registro base col calcolo dell'indirizzo effettuato (quindi ben più complesso di roba come An+ o -An).
Si, ma loro hanno parole da 32 bit e possono usare tutti i registri come operandi ;) Io sto cercando di fare il secondo con metà del primo... quindi per "semplice" intendo veramente semplice, come Rx, [Rx], [Rx+Ry], [Rx+off]. Ad esempio ho pensato che gli indirizzamenti più comuni potrebbero essere [Rx+Ry] per i vettori e [FP+off] per accedere a variabili locali. Anche l'indice scalato ecc... sono più complessi, perchè uno può scorrere un vettore di word, e usare il qadd per posizionarsi nell'elemento successivo, senza troppe complicanze.
Ah, allora stai optando per un CISC: opcode a 16 bit + a 32 bit. :D
No, stò facendo un RISC solo per dimostrarti che anche i RISC possono avere opcode a lunghezza variabile! :D
cdimauro
23-06-2010, 21:22
Si, ma loro hanno parole da 32 bit e possono usare tutti i registri come operandi ;) Io sto cercando di fare il secondo con metà del primo... quindi per "semplice" intendo veramente semplice, come Rx, [Rx], [Rx+Ry], [Rx+off]. Ad esempio ho pensato che gli indirizzamenti più comuni potrebbero essere [Rx+Ry] per i vettori e [FP+off] per accedere a variabili locali.
OK, così effettivamente sono molto semplici: al più hai una somma da fare.
Anche l'indice scalato ecc... sono più complessi, perchè uno può scorrere un vettore di word, e usare il qadd per posizionarsi nell'elemento successivo, senza troppe complicanze.
Ti serve un'istruzione in più così, quindi è meno performante.
No, stò facendo un RISC solo per dimostrarti che anche i RISC possono avere opcode a lunghezza variabile! :D
RISC and CISC definitions (http://www.cpushack.com/CPU/cpuAppendA.html)
At the time of introduction, RISC and "load-store" were often synonymous, but RISC usually referred to a list of features:
All instructions are a single, fixed length.
Operations are simple enough to execute in a single cycle, using an execution pipeline.
Operations are performed on registers only, the only memory operations are load and store.
No microcode is needed, because of the simpler design.
A large number of registers are available.
Register windows are used to speed subroutine calls.
Quindi... CISC rulez. :cool:
Ti serve un'istruzione in più così, quindi è meno performante.Ma l'incremento dell'indice (+1) lo avresti dovuto fare lo stesso no?
RISC and CISC definitions (http://www.cpushack.com/CPU/cpuAppendA.html)
At the time of introduction, RISC and "load-store" were often synonymous, but RISC usually referred to a list of features:
All instructions are a single, fixed length.
Operations are simple enough to execute in a single cycle, using an execution pipeline.
Operations are performed on registers only, the only memory operations are load and store.
No microcode is needed, because of the simpler design.
A large number of registers are available.
Register windows are used to speed subroutine calls.
Quindi... CISC rulez. :cool:
Io rimango dell'idea che quella non è la principale caratteristica che delinea un Risc; se lo fosse stata, li avrebbero chiamati SSISC (single-size instruction set computer) ;)
Quindi, per controprova, oltre a un Risc con opcode a lunghezza variabile ti progetto anche un cisc con opcode a lunghezza fissa!
la creatività che è tipica dei programmatori. :)
Ho appena scoperto di non essere un programmatore :sob:
Come procede? Nei CISC si usa la pipeline? No, vero?
cdimauro
23-06-2010, 21:47
Ma l'incremento dell'indice (+1) lo avresti dovuto fare lo stesso no?
Certo, ma lo faccio in parallelo grazie a un'ALU dedicata per il calcolo degli indirizzi. ;)
Tu, invece, sei costretto a usare (quindi più spazio) ed eseguire (prestazioni più scarse) un'istruzione in più.
Quanto meno dal punto di vista della compattezza del codice, i CISC vincono a mani basse.
Per le prestazioni, ormai che viaggiamo sui miliardi di transistor, un'unità di decodifica non è un problema né di costi né di consumi.
Io rimango dell'idea che quella non è la principale caratteristica che delinea un Risc; se lo fosse stata, li avrebbero chiamati SSISC (single-size instruction set computer) ;)
Quindi, per controprova, oltre a un Risc con opcode a lunghezza variabile ti progetto anche un cisc con opcode a lunghezza fissa!
Storicamente è così. Al limite chiedi a Pleg, che lavora alla Stanford University. :fagiano:
P.S. Adesso vado a nanna però, che stanotte ho dormito pochissimo a causa del piccolo. :cry:
Ho appena scoperto di non essere un programmatore :sob:
Come procede? Nei CISC si usa la pipeline? No, vero?
Sì: non è esclusiva dei RISC. Anche il buon 68000 ne aveva una, e il 6502 del Commodore Vic 20 pure! :D
Sì: non è esclusiva dei RISC. Anche il buon 68000 ne aveva una, e il 6502 del Commodore Vic 20 pure! :D
E il vostro ne fa uso? Come risolvete il conflitto dei dati nella pipeline? E nei salti condizionati? Provo a imparare qualcosa :D
cdimauro
24-06-2010, 07:35
Mi sembra ancora presto per parlarne. Intanto definiamo l'ISA, e poi vediamo come implementarla e le soluzioni che sono presenti in letteratura per risolvere questi problemi. :fagiano:
Questo è senza dubbio vero, ma non mi sembra ci fossero dei requisiti stringenti da questo punto di vista. Sentendomi "in libertà" ho tirato fuori dal cassetto l'ISA a cui sto lavorando da anni.
Per carità, hai fatto bene. L'ISA è molto bella tra l'altro ;)
cdimauro
24-06-2010, 08:29
Il bello verrà dopo, con gli opcode a 32 bit. :D
Certo, ma lo faccio in parallelo grazie a un'ALU dedicata per il calcolo degli indirizzi. ;)
Tu, invece, sei costretto a usare (quindi più spazio) ed eseguire (prestazioni più scarse) un'istruzione in più.
Forse non mi sono spiegato bene, intendevo dire una situazione del genere:
MOV.L [A1, D3.Q*4, 2], D0
QADD.Q 1, D3Questa è la versione tua, la mia la intendevo così:MOV.L [R9 + R3], R0
QADD.Q 6, R3
Che secondo me sono equivalenti. Se invece prendiamo gli incrementi di 1 byte ( [An]+ ), allora si che c'è il vantaggio di un'istruzione in meno.
Storicamente è così. Al limite chiedi a Pleg, che lavora alla Stanford University. :fagiano:
Certo, non ho dubbi che tutte le architetture risc usino opcode a lunghezza fissa: il loro obiettivo è proprio quello di semplificare al massimo le istruzioni, e avere opcode a lunghezza variabile le complicherebbe. Quello che volevo intendere è che un Risc non è obbligatorio che abbia opcode a lunghezza fissa, l'importante è che abbia istruzioni molto semplici e veloci (e questo anche preclude gli indirizzamenti complessi).
Sì: non è esclusiva dei RISC. Anche il buon 68000 ne aveva una, e il 6502 del Commodore Vic 20 pure! :DHey, non dimentichiamo lo Z80 che sovrapponeva i cicli macchina tra le istruzioni! :cool:
Per carità, hai fatto bene. L'ISA è molto bella tra l'altro ;)Già. Mi vergogno un po' a presentare la mia codifica, visto che toglie molto dando solo la possibilità di usare qualsiasi registro :(Il bello verrà dopo, con gli opcode a 32 bit. :DVisto che in 16 bit ci sto veramente male, appena la finisco pubblico la mia codifica interamente a 32 bit, che oltre a poter usare qualsiasi registro mette a disposizione anche tutte le modalità di indirizzamento; magari possiamo prendere un po' da entrambe.
E il vostro ne fa uso? Come risolvete il conflitto dei dati nella pipeline? E nei salti condizionati? Provo a imparare qualcosa :DSi, prevedo di usarla sicuramente una pipeline. Come già detto, vedremo in seguito gli stalli e le dipendenze da risolvere. Intanto abbiamo un nuovo "allievo" :D
Ora che ci penso, dove è finito MaxArt? E' scappato via urlando dopo aver visto il set istruzioni? ;)
cdimauro
24-06-2010, 14:40
Forse non mi sono spiegato bene, intendevo dire una situazione del genere:
MOV.L [A1, D3.Q*4, 2], D0
QADD.Q 1, D3Questa è la versione tua, la mia la intendevo così:MOV.L [R9 + R3], R0
QADD.Q 6, R3
Che secondo me sono equivalenti. Se invece prendiamo gli incrementi di 1 byte ( [An]+ ), allora si che c'è il vantaggio di un'istruzione in meno.
Parlavo di questa, ma anche della versione presente negli opcode a 32 bit, che alla fine dell'operazione aggiorna An con l'indirizzo calcolato (quindi An <- A1 + D3.Q*4 +2).
Certo, non ho dubbi che tutte le architetture risc usino opcode a lunghezza fissa: il loro obiettivo è proprio quello di semplificare al massimo le istruzioni, e avere opcode a lunghezza variabile le complicherebbe. Quello che volevo intendere è che un Risc non è obbligatorio che abbia opcode a lunghezza fissa, l'importante è che abbia istruzioni molto semplici e veloci (e questo anche preclude gli indirizzamenti complessi).
Ho capito, ma guarda che per i RISC non vale nemmeno questo da tempo: basti vedere PowerPC e ARM, ad esempio, col primo che ha pure del microcodice che esegure per le istruzioni di Load/Store multiple. :D
Quindi istruzioni complesse e lente anche per i RISC.
Diciamo che finora è la prima volta che sento parlare di un RISC con opcode a lunghezza variabile, ma dopo il Thumb di ARM mi sono dovuto ricredere. :p
Hey, non dimentichiamo lo Z80 che sovrapponeva i cicli macchina tra le istruzioni! :cool:
Io sono un MOSsiano. 6502 rulez. :cool:
Già. Mi vergogno un po' a presentare la mia codifica, visto che toglie molto dando solo la possibilità di usare qualsiasi registro :(
Era normale che finisse così: c'è troppo poco spazio con 16 bit. Ecco perché, sembrerà una stupidaggine, ma con registri indirizzi e dati separati il risparmio anche di un solo bit si fa sentire ed è decisamente pesante.
Visto che in 16 bit ci sto veramente male, appena la finisco pubblico la mia codifica interamente a 32 bit, che oltre a poter usare qualsiasi registro mette a disposizione anche tutte le modalità di indirizzamento; magari possiamo prendere un po' da entrambe.
Io intanto vorrei finire la mia ISA aggiungendo gli opcode a 32 bit, però. Ormai che mi hai provocato facendola uscire dal cassetto, devi prenderti le tue responsabilità. :O
Si, prevedo di usarla sicuramente una pipeline. Come già detto, vedremo in seguito gli stalli e le dipendenze da risolvere. Intanto abbiamo un nuovo "allievo" :D
Ora che ci penso, dove è finito MaxArt? E' scappato via urlando dopo aver visto il set istruzioni? ;)
Mi spiacerebbe. Se servono spiegazioni su punti oscuri / dubbi, io sono a disposizione (nei limiti di tempo che ho).
Dopo aver letto la news su Tilera... Pensare a qualche ISA VLIW con obiettivo il massimo sfruttamento delle unità di esecuzione ?
Il mercato si sta spostando fortemente verso il multi-threading. Costruire un core semplice RISC o VLIW con sincronizzazione esplicita fra thread non sarebbe secondo me un brutta idea.
Parlavo di questa, ma anche della versione presente negli opcode a 32 bit, che alla fine dell'operazione aggiorna An con l'indirizzo calcolato (quindi An <- A1 + D3.Q*4 +2).
Mi rimane ancora oscuro come tu riesca a fare il primo esempio con solo un'istruzione (ovviamente aumentando solo l'indice e non tutto il puntatore).
Ho capito, ma guarda che per i RISC non vale nemmeno questo da tempo: basti vedere PowerPC e ARM, ad esempio, col primo che ha pure del microcodice che esegure per le istruzioni di Load/Store multiple. :D
Quindi istruzioni complesse e lente anche per i RISC.
Già, al giorno d'oggi non è che si riesca più tanto a parlare di CISC e RISC, solo di "mescolanze", ovviamente parlando dal lato tecnico; poi se il marketing decide, riuscirebbe a etichettare "CISC" anche un PIC ;)
6502 rulez. :cool:
Argh !!!
Io intanto vorrei finire la mia ISA aggiungendo gli opcode a 32 bit, però. Ormai che mi hai provocato facendola uscire dal cassetto, devi prenderti le tue responsabilità. :O
Certo, fai pure. Mi piacerebbe vedere anche la 32 completa :)
Dopo aver letto la news su Tilera... Pensare a qualche ISA VLIW con obiettivo il massimo sfruttamento delle unità di esecuzione ?
Il mercato si sta spostando fortemente verso il multi-threading. Costruire un core semplice RISC o VLIW con sincronizzazione esplicita fra thread non sarebbe secondo me un brutta idea.
VLIW forse no, ma una serie di istruzioni per implementare facilmente semafori ecc... le mettiamo sicuramente. Se poi ci rimane spazio per le sincronizzazioni interprocessore, ben venga :)
Vi faccio un'anteprima sul set istruzioni a 32 bit:
Poiché volevo semplificare il design ed evitare la parola di estensione, ho inglobato nell' opcode alcuni campi che erano su quest'ultima (come ad esempio il registro indice e la scala). Il problema è che in tutte le altre modalità di indirizzamento questi campi rimanevano inutilizzati, quindi per sfruttarli al massimo ho aperto la fantasia e ho riempito una nuova tabella di modi di indirizzamento.
Mode| Index Reg. |Operand Reg | Syntax
000 |Index Reg no|Op. Reg. no | [Rn, Ri * Scale] | Indirect address with scaled index
001 |Index Reg no|Op. Reg. no | [Rn, Ri * Scale]+ | Indirect address with scaled index and writeback
010 |Index Reg no|Op. Reg. no | [Rn, Ri * Scale, SInt16] | Indirect address with scaled index and signed 16 bits offset
011 |Index Reg no|Op. Reg. no | [Rn, Ri * Scale, SInt16]+ | Indirect address with scaled index, writeback and signed 16 bits offset
100 |Index Reg no|Op. Reg. no | [Rn, Ri * Scale, SInt32] | Indirect address with scaled index and signed 32 bits offset
101 |Index Reg no|Op. Reg. no | [Rn, Ri * Scale, SInt32]+ | Indirect address with scaled index, writeback and signed 32 bits offset
110 |Index Reg no|Op. Reg. no | [Rn, Ri * Scale, SInt64] | Indirect address with scaled index and signed 64 bits offset
111 | 0000 |Op. Reg. no | Rn + Uint3 | Register plus Uint3 (in the "scale" field) <--- potentissima feature
111 | 0001 |Op. Reg. no | Rn - Uint3 | Register less Uint3 (in the "scale" field)
111 | 0010 |Op. Reg. no | [Rn] + Uint3 | Indirect address with postadd of "Uint3"
111 | 0011 |Op. Reg. no | [Rn] - Uint3 | Indirect address with postsubtract
111 | 0100 |Op. Reg. no | Uint3 + [Rn] | Indirect address with preadd
111 | 0101 |Op. Reg. no | Uint3 - [Rn] | Indirect address with presubtract
111 | 0110 |Op. Reg. no | [Rn, Uint3] | Indirect address with 3 bits unsigned offset
111 | 0111 |Op. Reg. no | [Rn, Uint3]+ | Indirect address with 3 bits unsigned offset and writeback
111 | 1000 |Op. Reg. no | [Rn, SInt16] | Indirect address with signed 16 bits offset
111 | 1001 |Op. Reg. no | [Rn, SInt16]+ | Indirect address with signed 16 bits offset and writeback
111 | 1010 |Op. Reg. no | [Rn, SInt32] | Indirect address with signed 32 bits offset
111 | 1011 |Op. Reg. no | [Rn, SInt32]+ | Indirect address with signed 32 bits offset and writeback
111 | 1100 |Op. Reg. no | [Rn, SInt64] | Indirect address with signed 64 bits offset
111 | 1101 |Op. Reg. no | [Rn, SInt64]+ | Indirect address with signed 64 bits offset and writeback
111 | 1110 |Op. Reg. no | **** | Reserved
111 | 1111 | 0000 | **** | Reserved
111 | 1111 | 0001 | [PC, SInt16] | Indirect PC with signed 16 bits offset
111 | 1111 | 0010 | [PC, SInt32] | Indirect PC with signed 32 bits offset
111 | 1111 | 0011 | [PC, SInt64] | Indirect PC with signed 32 bits offset
111 | 1111 | 0100 | [SP]+ | Indirect SP with postincrement <--- potentissima feature (POP)
111 | 1111 | 0101 | [SP, UInt16] | Indirect SP with unsigned 16 bits offset
111 | 1111 | 0110 | [SP, UInt32] | Indirect SP with unsigned 32 bits offset
111 | 1111 | 0111 | -[SP] | Indirect SP with predecrement <--- potentissima feature (PUSH)
111 | 1111 | 1000 | [FP, UInt3] | Indirect FP with UNsigned 3 bits offset
111 | 1111 | 1001 | [FP, SInt16] | Indirect FP with signed 16 bits offset
111 | 1111 | 1010 | [FP, SInt32] | Indirect FP with signed 32 bits offset
111 | 1111 | 1011 | [FP, SInt64] | Indirect FP with signed 64 bits offset
111 | 1111 | 1100 | [UInt16] | Absolute zero-extended 16 bits address
111 | 1111 | 1101 | [UInt32] | Absolute zero-extended 32 bits address
111 | 1111 | 1110 | [UInt64] | Absolute 64 bits address
111 | 1111 | 1111 | Value | ImmediateE questo è il Super MOV:
MOV.s Md, Ms
|31 30 |29 28 27 |26 25 24 |23 22 21 20 |19 18 |17 16 15 |14 13 12 |11 10 9 8 | 7 6 5 4 | 3 2 1 0 |
| 0 0 | Scale s | modalità s| Registro i s | Size | Scale d | modalità d| Registro i d | Registro s | Registro D |
# Muove qualsiasi cosa in qualsiasi cosa :D
Alla faccia del CISC ! :sofico:
Il mio intervento era per dire che, vista l'esperienza che mi sembra che abbiate, sarebbe bene provare ad implementare qualcosa di innovativo che una architettura CISC, comoda per il programmatore, ma scomoda per il progettista ;)
Anche solo per vedere qualcosa che almeno io non ho mai visto. Nei vari corsi di studio ho sia progettato processori CISC (x86) che RISC, ma mai un VLIW.
Tra l'altro quella CPU Tilera dovrebbe avere una feature molto interessante, cioè una specie di SMT al contrario: più core possono lavorare contemporaneamente sullo stesso thread. Sarebbe quindi interessante esplorare questi lidi ;)
Il mio intervento era per dire che, vista l'esperienza che mi sembra che abbiate, sarebbe bene provare ad implementare qualcosa di innovativo che una architettura CISC, comoda per il programmatore, ma scomoda per il progettista ;)
Anche solo per vedere qualcosa che almeno io non ho mai visto. Nei vari corsi di studio ho sia progettato processori CISC (x86) che RISC, ma mai un VLIW.
Tra l'altro quella CPU Tilera dovrebbe avere una feature molto interessante, cioè una specie di SMT al contrario: più core possono lavorare contemporaneamente sullo stesso thread. Sarebbe quindi interessante esplorare questi lidi ;)
Mmmh... potrei provare a mettere giù un set di istruzioni decisamente risc, che poi possiamo inglobare in una parola lunga magari da 128 bit. Sarebbe molto semplice da realizzare il circuito e anche la pipeline, perchè possiamo tralasciare il controllo di dipendenza dei dati e lasciarlo fare al compilatore (cioè come fanno tutti i VLIW ;)), ma in questo caso dovremo aprire un'altro thread: "Costruire un compilatore" :D
Una specie di "VLIW" (che "very long" non è, però ci sono più istruzioni in un bundle da 32 bit) è questo: http://www.6502.org/users/dieter/tinst/tinst_0.htm
Oppure, possiamo fare una cpu che usa la pipeline allo stesso modo dell' ELEA 9003: ogni blocco della pipeline non lavora su un pezzo dell'istruzione seguente dello stesso thread, ma lavora su istruzioni di thread diversi (l'ELEA, che aveva 3 stadi di pipeline, poteva lavorare su 3 thread contemporaneamente).
cdimauro
24-06-2010, 22:01
Dopo aver letto la news su Tilera... Pensare a qualche ISA VLIW con obiettivo il massimo sfruttamento delle unità di esecuzione ?
Il mercato si sta spostando fortemente verso il multi-threading. Costruire un core semplice RISC o VLIW con sincronizzazione esplicita fra thread non sarebbe secondo me un brutta idea.
Bisogna definire un obiettivo preciso, allora, perché un VLIW è fortemente orientato all'esecuzione parallela (e assistita dal compilatore), mentre una CPU tradizionale (sia essa CISC o RISC) decisamente no (è più orientata al calcolo general purpose con ottimizzazione a runtime).
Mi rimane ancora oscuro come tu riesca a fare il primo esempio con solo un'istruzione (ovviamente aumentando solo l'indice e non tutto il puntatore).
Se l'obiettivo è quello di scorrere una struttura, non c'è bisogno di incrementare l'indice, che rimane invariato, ma soltanto il puntatore.
Basta porre A0 = Indirizzo di inizio + 2 (era questo l'offset che avevi messo prima), e sfruttare poi quell'istruzione con l'update di A0 con l'indirizzo calcolato dall'AGU.
Già, al giorno d'oggi non è che si riesca più tanto a parlare di CISC e RISC, solo di "mescolanze", ovviamente parlando dal lato tecnico; poi se il marketing decide, riuscirebbe a etichettare "CISC" anche un PIC ;)
Proprio per questo la lunghezza degli opcode rimane uno dei punti di riferimento per distinguere le due macrofamiglie.
Sul resto concordo. :D
Certo, fai pure. Mi piacerebbe vedere anche la 32 completa :)
Mi ci vuole ancora un po' di tempo. Mancano un po' di istruzioni.
VLIW forse no, ma una serie di istruzioni per implementare facilmente semafori ecc... le mettiamo sicuramente. Se poi ci rimane spazio per le sincronizzazioni interprocessore, ben venga :)
Hum... Non sono molto convinto. Al momento ho strutturato l'ISA in modo da avere al più 1 lettura e 1 scrittura in memoria.
Queste saranno le ultime istruzioni a cui penserò.
Vi faccio un'anteprima sul set istruzioni a 32 bit:
Poiché volevo semplificare il design ed evitare la parola di estensione, ho inglobato nell' opcode alcuni campi che erano su quest'ultima (come ad esempio il registro indice e la scala). Il problema è che in tutte le altre modalità di indirizzamento questi campi rimanevano inutilizzati, quindi per sfruttarli al massimo ho aperto la fantasia e ho riempito una nuova tabella di modi di indirizzamento.
Mode| Index Reg. |Operand Reg | Syntax
000 |Index Reg no|Op. Reg. no | [Rn, Ri * Scale] | Indirect address with scaled index
001 |Index Reg no|Op. Reg. no | [Rn, Ri * Scale]+ | Indirect address with scaled index and writeback
010 |Index Reg no|Op. Reg. no | [Rn, Ri * Scale, SInt16] | Indirect address with scaled index and signed 16 bits offset
011 |Index Reg no|Op. Reg. no | [Rn, Ri * Scale, SInt16]+ | Indirect address with scaled index, writeback and signed 16 bits offset
100 |Index Reg no|Op. Reg. no | [Rn, Ri * Scale, SInt32] | Indirect address with scaled index and signed 32 bits offset
101 |Index Reg no|Op. Reg. no | [Rn, Ri * Scale, SInt32]+ | Indirect address with scaled index, writeback and signed 32 bits offset
110 |Index Reg no|Op. Reg. no | [Rn, Ri * Scale, SInt64] | Indirect address with scaled index and signed 64 bits offset
111 | 0000 |Op. Reg. no | Rn + Uint3 | Register plus Uint3 (in the "scale" field) <--- potentissima feature
111 | 0001 |Op. Reg. no | Rn - Uint3 | Register less Uint3 (in the "scale" field)
111 | 0010 |Op. Reg. no | [Rn] + Uint3 | Indirect address with postadd of "Uint3"
111 | 0011 |Op. Reg. no | [Rn] - Uint3 | Indirect address with postsubtract
111 | 0100 |Op. Reg. no | Uint3 + [Rn] | Indirect address with preadd
111 | 0101 |Op. Reg. no | Uint3 - [Rn] | Indirect address with presubtract
111 | 0110 |Op. Reg. no | [Rn, Uint3] | Indirect address with 3 bits unsigned offset
111 | 0111 |Op. Reg. no | [Rn, Uint3]+ | Indirect address with 3 bits unsigned offset and writeback
111 | 1000 |Op. Reg. no | [Rn, SInt16] | Indirect address with signed 16 bits offset
111 | 1001 |Op. Reg. no | [Rn, SInt16]+ | Indirect address with signed 16 bits offset and writeback
111 | 1010 |Op. Reg. no | [Rn, SInt32] | Indirect address with signed 32 bits offset
111 | 1011 |Op. Reg. no | [Rn, SInt32]+ | Indirect address with signed 32 bits offset and writeback
111 | 1100 |Op. Reg. no | [Rn, SInt64] | Indirect address with signed 64 bits offset
111 | 1101 |Op. Reg. no | [Rn, SInt64]+ | Indirect address with signed 64 bits offset and writeback
111 | 1110 |Op. Reg. no | **** | Reserved
111 | 1111 | 0000 | **** | Reserved
111 | 1111 | 0001 | [PC, SInt16] | Indirect PC with signed 16 bits offset
111 | 1111 | 0010 | [PC, SInt32] | Indirect PC with signed 32 bits offset
111 | 1111 | 0011 | [PC, SInt64] | Indirect PC with signed 32 bits offset
111 | 1111 | 0100 | [SP]+ | Indirect SP with postincrement <--- potentissima feature (POP)
111 | 1111 | 0101 | [SP, UInt16] | Indirect SP with unsigned 16 bits offset
111 | 1111 | 0110 | [SP, UInt32] | Indirect SP with unsigned 32 bits offset
111 | 1111 | 0111 | -[SP] | Indirect SP with predecrement <--- potentissima feature (PUSH)
111 | 1111 | 1000 | [FP, UInt3] | Indirect FP with UNsigned 3 bits offset
111 | 1111 | 1001 | [FP, SInt16] | Indirect FP with signed 16 bits offset
111 | 1111 | 1010 | [FP, SInt32] | Indirect FP with signed 32 bits offset
111 | 1111 | 1011 | [FP, SInt64] | Indirect FP with signed 64 bits offset
111 | 1111 | 1100 | [UInt16] | Absolute zero-extended 16 bits address
111 | 1111 | 1101 | [UInt32] | Absolute zero-extended 32 bits address
111 | 1111 | 1110 | [UInt64] | Absolute 64 bits address
111 | 1111 | 1111 | Value | ImmediateE questo è il Super MOV:
MOV.s Md, Ms
|31 30 |29 28 27 |26 25 24 |23 22 21 20 |19 18 |17 16 15 |14 13 12 |11 10 9 8 | 7 6 5 4 | 3 2 1 0 |
| 0 0 | Scale s | modalità s| Registro i s | Size | Scale d | modalità d| Registro i d | Registro s | Registro D |
# Muove qualsiasi cosa in qualsiasi cosa :D
Mi pare proprio un bel design.
Tra l'altro mi hai fornito lo spunto per eliminare le istruzioni di PUSH e POP, sfruttando due delle tre modalità d'indirizzamento che erano rimaste libere, così da avere -[SP] e [SP]+.
In questo modo ho pure recuperato 9 bit dall'opcode table a 16 bit. Grazie! :D
Alla faccia del CISC ! :sofico:
Appunto: quello tutto lo puoi chiamare, tranne che RISC. :p
Il mio intervento era per dire che, vista l'esperienza che mi sembra che abbiate, sarebbe bene provare ad implementare qualcosa di innovativo che una architettura CISC, comoda per il programmatore, ma scomoda per il progettista ;)
Ma alla fine la CPU chi la usa, il progettista o il programmatore? :D
Comunque i design che stiamo tirando fuori non sono complicati da implementare a livello di decoder, perché sono abbastanza lineari e ortogonali, senza eccezioni (a parte per i "buchi" lasciati liberi dalle istruzioni non implementate).
Anche solo per vedere qualcosa che almeno io non ho mai visto. Nei vari corsi di studio ho sia progettato processori CISC (x86) che RISC, ma mai un VLIW.
Tra l'altro quella CPU Tilera dovrebbe avere una feature molto interessante, cioè una specie di SMT al contrario: più core possono lavorare contemporaneamente sullo stesso thread. Sarebbe quindi interessante esplorare questi lidi ;)
Quello sicuramente, non foss'altro per cultura personale. :fagiano:
cdimauro
25-06-2010, 08:04
Mmmh... potrei provare a mettere giù un set di istruzioni decisamente risc, che poi possiamo inglobare in una parola lunga magari da 128 bit. Sarebbe molto semplice da realizzare il circuito e anche la pipeline, perchè possiamo tralasciare il controllo di dipendenza dei dati e lasciarlo fare al compilatore (cioè come fanno tutti i VLIW ;)), ma in questo caso dovremo aprire un'altro thread: "Costruire un compilatore" :D
Una specie di "VLIW" (che "very long" non è, però ci sono più istruzioni in un bundle da 32 bit) è questo: http://www.6502.org/users/dieter/tinst/tinst_0.htm
Mah. Ho dato un'occhiata a questo progetto e potevano farlo molto meglio.
Non hanno tenuto conto di alcune cose che avrebbero permesso di ottimizzare e sfruttare come si deve l'opcode a 32 bit.
Bisogna definire un obiettivo preciso, allora, perché un VLIW è fortemente orientato all'esecuzione parallela (e assistita dal compilatore), mentre una CPU tradizionale (sia essa CISC o RISC) decisamente no (è più orientata al calcolo general purpose con ottimizzazione a runtime).
Invece secondo me sarebbe molto interessante lavorare su un RISC con parallelizzazione esplicita e con indicazione esplicita delle dipendenze.
Ovviamente allungherebbe l'opcode, ma semplificherebbe altre cose. Ad esempio l'esecuzione potrebbe essere in-order, o al limite anche out-of-order ma in base al raggruppamento.
Consideriamo anche che essendo RISC l'analisi delle dipendenze da parte del compilatore è molto più semplice visto il numero ridotto di opcode...
Faccio un esempio: supponiamo di avere due LOAD/STORE pipeline...
grp dep
1 0 LOAD R0, op1
1 0 LOAD R1, op2
2 1 LOAD R2, op3
3 1 ADD R0, R1
3 2 SUB R0, R2
4 3 SHR R0, 6
1 0 LOAD R3, op4
1 4 STORE op1, R0
Credo che con 8 bit per il gruppo e 8 per la dipendenza siano sufficienti.
Se l'obiettivo è quello di scorrere una struttura, non c'è bisogno di incrementare l'indice, che rimane invariato, ma soltanto il puntatore.
Basta porre A0 = Indirizzo di inizio + 2 (era questo l'offset che avevi messo prima), e sfruttare poi quell'istruzione con l'update di A0 con l'indirizzo calcolato dall'AGU.Quindi l' Xn*scale deve essere inteso come un campo che indica la lunghezza di ogni struttura? Quindi un vettore di quad lo scorri così: MOV.Q vett, A0
QMOVEU 8, D7
ciclo: MOV.Q [A0, D7, 0]+, D0
<elabora>
Bcc ciclo?
i pare proprio un bel design.
Tra l'altro mi hai fornito lo spunto per eliminare le istruzioni di PUSH e POP, sfruttando due delle tre modalità d'indirizzamento che erano rimaste libere, così da avere -[SP] e [SP]+.
In questo modo ho pure recuperato 9 bit dall'opcode table a 16 bit. Grazie! :D
Di questo passo non ci servirà neanche la codifica a 32 bit! :cool:
Comunque i design che stiamo tirando fuori non sono complicati da implementare a livello di decoder, perché sono abbastanza lineari e ortogonali, senza eccezioni (a parte per i "buchi" lasciati liberi dalle istruzioni non implementate).
Concordo, il design è lineare; sarà quando dobbiamo scrivere il microcodice per l'unità di calcolo indirizzi che ci sarà da ridere! ;)
Faccio un esempio: supponiamo di avere due LOAD/STORE pipeline...
grp dep
1 0 LOAD R0, op1
1 0 LOAD R1, op2
2 1 LOAD R2, op3
3 1 ADD R0, R1
3 2 SUB R0, R2
4 3 SHR R0, 6
1 0 LOAD R3, op4
1 4 STORE op1, R0
Credo che con 8 bit per il gruppo e 8 per la dipendenza siano sufficienti.
Non sono un esperto in queste cose, il gruppo indica quali istruzioni possono essere riordinate ed eseguite fuori ordine in sicurezza? Perchè penso (tenendo conto del lato progettistico e avendo una cpu in-order) si possa fare tutto con 1 numero solo:
0 LOAD R0, op1
1 LOAD R1, op2
2 LOAD R2, op3
1 ADD R0, R1
2 SUB R0, R2
2 SHR R0, 6
0 LOAD R3, op4
2 STORE op1, R0
In questo caso, ad ogni istruzione è assegnato il numero uguale alla più vicina istruzione precedente che scrive un dato dipendente.
Ad esempio, ADD R0, R1 dipende da R0 e R1, la più vicina istruzione che scrive un dato dipendente è LOAD R1, op2 (aggiorna/scrive R1), alla quale è stato assegnato il numero 1, quindi all'ADD sarà assegnato lo stesso 1.
Il lavoro della pipeline è questo: quando entra un'istruzione, controlla il suo numero con i numeri di tutte le istruzioni in quel momento sulla pipeline. Se è presente lo stesso numero, l'istruzione è bloccata finchè l'istruzione non esce o supera lo stadio di scrittura-risultato (che tipicamente è l'ultimo). Possiamo usare il numero speciale 0 per indicare un'istruzione che può essere eseguita tranquillamente.
Il problema però nella dipendenza esplicita, è che i programmi possono diventare incompatibili cambiando processore, anche della stessa architettura: se un programma è stato ottimizzato per un processore con al più 5 stadi di pipeline, in un processore da 6-7-8 potrebbe non funzionare più, perchè dati dipendenti potrebbero essere ancora dentro la pipeline.
Tutte le istruzioni del gruppo possono essere eseguite in parallelo (quindi sì, possono essere eseguite fuori ordine per sicurezza).
La dipendenza indica quale gruppo di istruzioni deve essere eseguito prima di dover eseguire quella istruzione del gruppo. 0 indica che non ci sono dipendenze.
Mi sono accorto di aver inserito un errore, volevo scrivere un'altra cosa :D
grp dep
1 0 LOAD R0, op1
1 0 LOAD R1, op2
2 1 LOAD R2, op3
3 1 ADD R0, R1
3 2 SUB R2, 2
4 3 SUB R0, R2
5 4 SHR R0, 6
1 0 LOAD R3, op4
1 5 STORE op1, R0
Ad esempio, così come l'hai messo tu le prime due LOAD non possono essere eseguite in parallelo.
Così come l'ho messo io, supponendo di avere 2 unità LOAD/STORE e 2 ALU è possibile eseguire le prime due LOAD contemporaneamente.
Poi possono essere allocate tutte le unità di esecuzione contemporaneamente potendo eseguire:
2 1 LOAD R2, op3
3 1 ADD R0, R1
3 2 SUB R2, 2
1 0 LOAD R3, op4
La cosa dovrebbe essere anche piuttosto semplice da gestire. Una fase di prefetch (potrebbe essere realizzata anche a frequenza più alta di quella della CPU, vista la semplicità) analizza grp, dep e il tipo di unità di esecuzione.
Supponendo che un gruppo sia composto da istruzioni in sequenza e che all'interno del gruppo le istruzioni sano ordinate per dipendenza, è possibile assegnare l'unità di esecuzione in modo relativamente semplice.
grp dep
1 0 LOAD R0, op1
1 0 LOAD R1, op2
2 1 LOAD R2, op3
3 1 ADD R0, R1
3 2 SUB R2, 2
4 3 SUB R0, R2
5 4 SHR R0, 6
1 0 LOAD R3, op4
1 5 STORE op1, R0
Mmmh, non capisco bene, provo a dirti cosa sto pensando:
La cpu legge il primo load, ha dipendenza 0 quindi lo manda nella pipeline load/store n°1; legge la seconda load, non ha dipendenze, va in load/store n°2. Legge la 3^ load, ha dipendenza 1, quindi attende che le 2 istruzioni (perchè sono tutte gruppo 1) finiscano, quindi la manda nel load/store 1. prende l'add, la dipendenza 1 è soddisfatta, la manda all'alu 1. prende la sub, attende che il load r2 finisca (se non lo è già), e quindi la manda in alu 2, e così via. è giusto?
Ad esempio, così come l'hai messo tu le prime due LOAD non possono essere eseguite in parallelo.
Così come l'ho messo io, supponendo di avere 2 unità LOAD/STORE e 2 ALU è possibile eseguire le prime due LOAD contemporaneamente.
Beh, forse si:
0 LOAD R0, op1
1 LOAD R1, op2
2 LOAD R2, op3
1 ADD R0, R1
2 SUB R2, 2
2 SUB R0, R2
2 SHR R0, 6
0 LOAD R3, op4
2 STORE op1, R0
La cpu prende Load R0, esegue in load/store 1. Prende anche load r1, tra di loro non hanno dipendenze, quindi la manda anch'essa in parallelo in load/store 2. Preleva Load r2, non ha spazio nelle pipeline, la mette in attesa. Se poi è out-of-order, può cominciare subito a prendere l'add, e la esegue (presumiamo che ormai le load siano uscite). Dopo aver elaborato l'add, ricontrolla il load r2, e la assegna alla pipeline load/store 1 (poichè è libera). Ora controlla la Sub R2, ma non la può eseguire. Controlla Sub R0, ma non la può eseguire, e così anche shr. Trova l'ultimo load e lo esegue perchè non ha dipendenze. Ricontrolla il sub, ora sicuramente il load r2 è terminato e quindi esegue il sub. Ora gli rimangono da fare l'altro sub, il shr e lo store, ma sono tutte dipendenti, quindi le deve fare in sequenza.
2 1 LOAD R2, op3
3 1 ADD R0, R1
3 2 SUB R2, 2
1 0 LOAD R3, op4
Mmh, sub r2, 2 non lo puoi fare in contemporanea a load r2, op3, c'è una dipendenza dei dati.
La cosa dovrebbe essere anche piuttosto semplice da gestire. Una fase di prefetch (potrebbe essere realizzata anche a frequenza più alta di quella della CPU, vista la semplicità) analizza grp, dep e il tipo di unità di esecuzione.
Supponendo che un gruppo sia composto da istruzioni in sequenza e che all'interno del gruppo le istruzioni sano ordinate per dipendenza, è possibile assegnare l'unità di esecuzione in modo relativamente semplice.
Forse dal lato algoritmico è semplice, ma bisogna poi implementarlo in Hardware ;)
Sì, hai capito...
Beh, forse si:
0 LOAD R0, op1
1 LOAD R1, op2
2 LOAD R2, op3
1 ADD R0, R1
2 SUB R2, 2
2 SUB R0, R2
2 SHR R0, 6
0 LOAD R3, op4
2 STORE op1, R0
La cpu prende Load R0, esegue in load/store 1. Prende anche load r1, tra di loro non hanno dipendenze, quindi la manda anch'essa in parallelo in load/store 2. Preleva Load r2, non ha spazio nelle pipeline, la mette in attesa. Se poi è out-of-order, può cominciare subito a prendere l'add, e la esegue (presumiamo che ormai le load siano uscite). Dopo aver elaborato l'add, ricontrolla il load r2, e la assegna alla pipeline load/store 1 (poichè è libera). Ora controlla la Sub R2, ma non la può eseguire. Controlla Sub R0, ma non la può eseguire, e così anche shr. Trova l'ultimo load e lo esegue perchè non ha dipendenze. Ricontrolla il sub, ora sicuramente il load r2 è terminato e quindi esegue il sub. Ora gli rimangono da fare l'altro sub, il shr e lo store, ma sono tutte dipendenti, quindi le deve fare in sequenza.
Ho capito cosa intendi, in pratica come fai tu mi sembra di capire che vengano eseguite in sequenza istruzioni consecutive con lo stesso indice ed in parallelo istruzioni consecutive con indici diversi. In prima lettura mi sembra corretto.
Mmh, sub r2, 2 non lo puoi fare in contemporanea a load r2, op3, c'è una dipendenza dei dati.
L'ho riscritta ed ho fatto ancora più casino, comunque credo tu abbia capito.
Ho capito cosa intendi, in pratica come fai tu mi sembra di capire che vengano eseguite in sequenza istruzioni consecutive con lo stesso indice ed in parallelo istruzioni consecutive con indici diversi. In prima lettura mi sembra corretto.
Si, esatto.
L'ho riscritta ed ho fatto ancora più casino, comunque credo tu abbia capito.
Si.
marko.fatto
25-06-2010, 17:21
mi sono letto tutto il thread adesso, mi sembrava di essere di nuovo a lezione di architettura xD
non riuscirò ad intervenire ma seguirò la discussione :D
mi sono letto tutto il thread adesso, mi sembrava di essere di nuovo a lezione di architettura xD
non riuscirò ad intervenire ma seguirò la discussione :D
:D
cdimauro
25-06-2010, 21:43
Invece secondo me sarebbe molto interessante lavorare su un RISC con parallelizzazione esplicita e con indicazione esplicita delle dipendenze.
Ovviamente allungherebbe l'opcode, ma semplificherebbe altre cose. Ad esempio l'esecuzione potrebbe essere in-order, o al limite anche out-of-order ma in base al raggruppamento.
Consideriamo anche che essendo RISC l'analisi delle dipendenze da parte del compilatore è molto più semplice visto il numero ridotto di opcode...
Faccio un esempio: supponiamo di avere due LOAD/STORE pipeline...
grp dep
1 0 LOAD R0, op1
1 0 LOAD R1, op2
2 1 LOAD R2, op3
3 1 ADD R0, R1
3 2 SUB R0, R2
4 3 SHR R0, 6
1 0 LOAD R3, op4
1 4 STORE op1, R0
Credo che con 8 bit per il gruppo e 8 per la dipendenza siano sufficienti.
Mi ricorda molto questo: http://en.wikipedia.org/wiki/Itanium
e non è che brilli (come molti VLIW) per prestazioni GP... :stordita:
Quindi l' Xn*scale deve essere inteso come un campo che indica la lunghezza di ogni struttura? Quindi un vettore di quad lo scorri così: MOV.Q vett, A0
QMOVEU 8, D7
ciclo: MOV.Q [A0, D7, 0]+, D0
<elabora>
Bcc ciclo?
Esattamente. :) L'indice non serve, ma è sufficiente l'offset per scorrere tutti gli elementi che ci servono.
Di questo passo non ci servirà neanche la codifica a 32 bit! :cool:
No, quella serve. Mica hai visto quello che ho combinato con quei preziosi 28 bit. :fiufiu: :D
Concordo, il design è lineare; sarà quando dobbiamo scrivere il microcodice per l'unità di calcolo indirizzi che ci sarà da ridere! ;)
In tutte le implementazioni più avanzate della famiglia 68000 (quindi dal 68040 in poi), c'è un apposito stadio della pipeline che fa solo quello. Il 68060 per quasi tutti gli indirizzamenti semplici (cioè non quelli che hanno la DOPPIA indirezione in memoria) non ha nessun ciclo di clock aggiuntivo.
Il 68050 (chiamato N050) del progetto Natami a cui stanno lavorando impiega anch'esso un singolo ciclo di clock nello stadio di calcolo dell'EA della pipeline per queste modalità, tranne appunto quelle con doppia indirezione (che anche noi non abbiamo).
Il fatto è che i CISC complicati di un tempo non avevano pipeline efficienti come quelle più moderne, ovviamente, ma a meno di non esagerare non ci dovrebbero essere difficoltà. :fagiano:
Esattamente. :) L'indice non serve, ma è sufficiente l'offset per scorrere tutti gli elementi che ci servono.
Allora, in modo da semplificare l'AGU, si potrebbero omettere le istruzioni che usano indice * scala? Così non dobbiamo neanche controllare per il fetch dell'estensione...
No, quella serve. Mica hai visto quello che ho combinato con quei preziosi 28 bit. :fiufiu: :D
Ho paura (in senso buono :D )
tranne appunto quelle con doppia indirezione (che anche noi non abbiamo).
Come no?
MOVE.Size Source EA, Destination EA
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 0 | Size |Dest. Mode |Dest. Register|Source Mode |Source Reg.|
In questa non puoi anche specificare 2 indirizzi di memoria? O intendi dire "prelevare dalla memoria il puntatore al dato" (in C: *(*ptr) ) ?
cdimauro
25-06-2010, 22:51
Allora, in modo da semplificare l'AGU, si potrebbero omettere le istruzioni che usano indice * scala? Così non dobbiamo neanche controllare per il fetch dell'estensione...
Ma perché ti darebbe fastidio l'estensione?
Ho paura (in senso buono :D )
Fai bene ad averne. :asd:
Come no?
MOVE.Size Source EA, Destination EA
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 0 | Size |Dest. Mode |Dest. Register|Source Mode |Source Reg.|
In questa non puoi anche specificare 2 indirizzi di memoria? O intendi dire "prelevare dalla memoria il puntatore al dato" (in C: *(*ptr) ) ?
La seconda che hai detto. Che come programmatore mi fa venire l'acquolina in bocca (e col 68EC020 del mio fido Amiga 1200 le ho usate un bel po' di volte), ma a livello di implementazione mi rendo perfettamente conto che è un vero inferno. :(
EDIT: qui (http://www.natami.net/knowledge.php?b=6¬e=22344&order=&x=6) c'è il commento di Gunnar delle 18:53 in cui mostra come funziona la pipeline dell'N050 a cui sta lavorando.
Mi ricorda molto questo: http://en.wikipedia.org/wiki/Itanium
e non è che brilli (come molti VLIW) per prestazioni GP... :stordita:
E' chiaro che te lo ricordi, dopo tutto c'è parallelismo esplicito. La differenza è che Itanium ha un set di istruzioni complesso e 3 istruzioni per opcode, mentre questo ha un set di istruzioni simil RISC. L'idea era appunto quella di creare una CPU con molte unità di esecuzione, ma con pipeline relativamente corta e semplice. Quindi critical path corto e frequenze alte.
Magari assegnando l'unità di calcolo ancora prima della fase di decodifica in base a quel numerino e al tipo di istruzione, semplificando quindi ulteriormente la gestione interna del decoder.
L'assegnazione delle unità di calcolo può essere fatto tramite una specie di priorità rotante.
Ad esempio dovrebbe funzionare un algoritmo di questo tipo (utilizzando l'idea di Z80Fan per la precedenza):
ALU(i) = ALU(i-1) + prec(i) - prec(i-1)
LSU(i) = ALU(i-1) + prec(i) - prec(i-1)
Secondo me può essere tranquillamente fatto in fase di fetch.
Ovviamente è un conto che andrebbe fatto tutto in modulo (è più semplice farlo in hardware).
Dopo la fase di decode si duplicano le varie unità. Si bloccano le unità che hanno indici di precedenza uguali a quelli in esecuzione.
In fase di esecuzione ricordiamoci che siamo sicuri che non ci siano dipendenze, quindi si possono scrivere direttamente i risultati nei registri e liberare le istruzioni dipendenti già nella fase di esecuzione (ovviamente solo se la destinazione è un registro).
cdimauro
28-06-2010, 08:00
Personalmente non mi preoccuperei molto del decoder: ormai anche gli FPGA mettono a disposizione centinaia di migliaia di gate, uno più controller DDR2/3 di memoria, ecc. ecc.
Se l'obiettivo è avere un decoder semplicissimo, non credo che a 2010 inoltrato possa essere un problema.
Dobbiamo comunque mettere un punto per lo meno i requisiti di base: CPU con elevato ILP? Con architettura massicciamente parallela? In-order, out-order, o VLIW? ISA orientata al programmatore o al designer? "Bella" o esclusivamente funzionale alle prestazioni?
Scusate se non ho risposto di recente, ma mi sono trovato molto impegnato :)
Ma perché ti darebbe fastidio l'estensione?Perchè secondo me rompe la linearità dell'istruzione: non possiamo chiamarlo "opcode a 16 bit", quando in realtà ne occupa 32 (perchè anche i campi dell'estensione sono necessari a descrivere l'operazione), quindi preferirei avere istruzioni "a 16 bit" che sono veramente a 16 bit (tralasciando eventuali dati immediati), e se è necessario maggior spazio, si integra nella tabella "a 32 bit".
Senza estensione, il programmatore non nota nulla, ma il designer ringrazia :)
L'idea era appunto quella di creare una CPU con molte unità di esecuzione, ma con pipeline relativamente corta e semplice. Quindi critical path corto e frequenze alte.
Magari assegnando l'unità di calcolo ancora prima della fase di decodifica in base a quel numerino e al tipo di istruzione, semplificando quindi ulteriormente la gestione interna del decoder.
L'assegnazione delle unità di calcolo può essere fatto tramite una specie di priorità rotante.
Ad esempio dovrebbe funzionare un algoritmo di questo tipo (utilizzando l'idea di Z80Fan per la precedenza):
ALU(i) = ALU(i-1) + prec(i) - prec(i-1)
LSU(i) = ALU(i-1) + prec(i) - prec(i-1)
Secondo me può essere tranquillamente fatto in fase di fetch.
Ovviamente è un conto che andrebbe fatto tutto in modulo (è più semplice farlo in hardware).
Dopo la fase di decode si duplicano le varie unità. Si bloccano le unità che hanno indici di precedenza uguali a quelli in esecuzione.
In fase di esecuzione ricordiamoci che siamo sicuri che non ci siano dipendenze, quindi si possono scrivere direttamente i risultati nei registri e liberare le istruzioni dipendenti già nella fase di esecuzione (ovviamente solo se la destinazione è un registro).
Mi intriga la storia. Ci faremo un pensierino sicuramente.
Dobbiamo comunque mettere un punto per lo meno i requisiti di base: CPU con elevato ILP? Con architettura massicciamente parallela? In-order, out-order, o VLIW? ISA orientata al programmatore o al designer? "Bella" o esclusivamente funzionale alle prestazioni?
Secondo me, parlando anche da progettista, preferirei un'architettura in-order, estremamente ortogonale, con istruzioni semplici ma non minimali, preferibilmente a opcode fissi; un'isa con opcode a lunghezza fissa, pipeline corta e ortogonale come un risc, ma con funzionalità utili tipo cisc. C'è da dire anche che il progettista progetta una volta sola, ma il programmatore programma tante volte, quindi quest'ultimo dovrebbe essere privilegiato; ciò non significa che si possono buttare dentro tutte le "features" (anche di dubbia utilità), solo perchè "fanno comodo" per risparmiare 1 istruzione in un caso su 1000.
Poi, se riusciamo a renderla semplice, bella e veloce, siamo dei geni (e cesare è già sulla buona strada :))
Mi piacerebbe anche provarla in un fpga, qualcuno ha idea di come si lavori con 'sti dispositivi?
In ultimo, non è detto che dobbiamo fare solo un'architettura: già avendo un prototipo funzionante di una, possiamo pensarne ad un'altra anche completamente differente, e ognuno lavora sulla sua preferita.
Mi piacerebbe anche provarla in un fpga, qualcuno ha idea di come si lavori con 'sti dispositivi?
Eheheh...corri troppo :D
http://www.altera.com/products/devkits/kit-dev_platforms.jsp
Si dovrebbero programmare in VHDL. Il procedimento dovrebbe essere simile alla realizzazione di un ASIC, tranne che per la fase di ottimizzazione. Le librerie intervengono direttamente per sintetizzare la parte relativa alla FPGA e generano i dati da scrivere sul chip tramite la scheda.
Il problema fondamentale è un altro: a seconda di quante porte logiche e rom servono il development kit può costare anche diverse migliaia di dollari.
cdimauro
01-07-2010, 08:08
Scusate se non ho risposto di recente, ma mi sono trovato molto impegnato :)
A chi lo dici. :muro:
Perchè secondo me rompe la linearità dell'istruzione: non possiamo chiamarlo "opcode a 16 bit", quando in realtà ne occupa 32 (perchè anche i campi dell'estensione sono necessari a descrivere l'operazione), quindi preferirei avere istruzioni "a 16 bit" che sono veramente a 16 bit (tralasciando eventuali dati immediati), e se è necessario maggior spazio, si integra nella tabella "a 32 bit".
Senza estensione, il programmatore non nota nulla, ma il designer ringrazia :)
Questa è un'ottica RISC, però. E non sarei d'accordo nemmeno di fare un'eccezione per i dati immediati, perché sempre di un'eccezione si tratta: rompe la "linearità" dell'istruzione. :D
Comunque l'estensione non serve a descrivere l'operazione, perché questa è già nota grazie ai campi mode & register. L'estensione nasce per prelevare le informazioni descritte in questi due campi, esattamente come il valore immediato, che non è "incluso" direttamente nella word dell'opcode, ma di cui "si sa" che serve leggerlo.
Poi è chiaro che il designer si trova un po' in difficoltà, ma si tratta di problemi ampiamente superabili, come dimostrano l'evoluzione dei CISC.
Se prendi il progetto Natami, delle CPU 68020+ a cui stanno lavorando, il designer non si lamenta affatto dell'estensione, anzi, ne esalta le peculiarità! :D E considera che è un ingegnere che lavora per IBM; quindi ha a che fare con PowerPC. :P L'unica cosa che gli dà fastidio è la doppia indirezione delle modalità aggiunte da Motorola col 68020.
Secondo me, parlando anche da progettista, preferirei un'architettura in-order, estremamente ortogonale, con istruzioni semplici ma non minimali, preferibilmente a opcode fissi; un'isa con opcode a lunghezza fissa, pipeline corta e ortogonale come un risc, ma con funzionalità utili tipo cisc.
Un ARM, insomma. :D Anche se i Cortex-A9 hanno introdotto l'OoO.
C'è da dire anche che il progettista progetta una volta sola, ma il programmatore programma tante volte, quindi quest'ultimo dovrebbe essere privilegiato;
Ottimo, ed è questo che normalmente faccio.
ciò non significa che si possono buttare dentro tutte le "features" (anche di dubbia utilità), solo perchè "fanno comodo" per risparmiare 1 istruzione in un caso su 1000.
Questo dipende anche da noi: siamo in grado di valutare se una funzionalità ha senso d'esser implementata oppure no.
Poi, se riusciamo a renderla semplice, bella e veloce, siamo dei geni (e cesare è già sulla buona strada :))
Semplice e bella ci può stare, ma per renderla veloce, beh, dipende dalle capacità del designer. :D
Io più che metterci qualche mezzo per facilitargli la vita non posso, ed è quello a cui sto lavorando al momento con la opcode table a 32 bit (sto cercando di renderla quanto più ortogonale possibile, raggruppando in un certo modo le istruzioni).
Mi piacerebbe anche provarla in un fpga, qualcuno ha idea di come si lavori con 'sti dispositivi?
Da quel poco che ho visto non sono difficili da programmare, ma a causa delle mie limitate conoscenze mi fermerei al puro codice. E il solo codice non basta per realizzare un'intera CPU.
Comunque potremmo pensare di adottare qualcosa di più comodo per programmarli (http://www.myhdl.org/doku.php/start). :fiufiu:
In ultimo, non è detto che dobbiamo fare solo un'architettura: già avendo un prototipo funzionante di una, possiamo pensarne ad un'altra anche completamente differente, e ognuno lavora sulla sua preferita.
Facciamo così, che secondo me è la soluzione migliore. Perché ognuno in questo modo contribuirà con delle idee, che gli altri potrebbero prendere in considerazione e/o ispirarsi per far evolvere la propria architettura.
Eheheh...corri troppo :D
http://www.altera.com/products/devkits/kit-dev_platforms.jsp
Si dovrebbero programmare in VHDL. Il procedimento dovrebbe essere simile alla realizzazione di un ASIC, tranne che per la fase di ottimizzazione. Le librerie intervengono direttamente per sintetizzare la parte relativa alla FPGA e generano i dati da scrivere sul chip tramite la scheda.
Il problema fondamentale è un altro: a seconda di quante porte logiche e rom servono il development kit può costare anche diverse migliaia di dollari.
Hum. Dipende. Altera e Xilinx non mi sembrano siano costose a livello di DK, e tra l'altro rilasciano documentazione e simulatori, perché hanno tutto l'interesse a espandere l'utilizzo delle loro soluzioni.
Poi all'inizio penso che una soluzione economica potrebbe bastare per un prototipo assolutamente non ottimizzato.
Poi all'inizio penso che una soluzione economica potrebbe bastare per un prototipo assolutamente non ottimizzato.
Dipende tutto dal numero di porte... Ci sono Dev-Kit anche da 49€, ma possono avere fino a 15K porte logiche. Non so se siano sufficienti ;)
La scheda costa anche poco...
http://www.digilentinc.com/Products/Detail.cfm?NavPath=2,400,789&Prod=NEXYS2
Peccato che ci vogliano più di 2000€ per il software di design ;) A meno che voi non abbiate una licenza per Cadence o Synopsys :D
cdimauro
01-07-2010, 13:09
Dipende tutto dal numero di porte... Ci sono Dev-Kit anche da 49€, ma possono avere fino a 15K porte logiche. Non so se siano sufficienti ;)
Hum. Adesso non ricordo se la prima versione del 68050+ di Natami utilizzasse 6 mila gate.
Ma parliamo di numeri bassi, comunque.
La scheda costa anche poco...
http://www.digilentinc.com/Products/Detail.cfm?NavPath=2,400,789&Prod=NEXYS2
Peccato che ci vogliano più di 2000€ per il software di design ;) A meno che voi non abbiate una licenza per Cadence o Synopsys :D
GLOM. :cry:
Potremmo accontentarci del simulatore, al momento.
La scheda costa anche poco...
http://www.digilentinc.com/Products/Detail.cfm?NavPath=2,400,789&Prod=NEXYS2
Peccato che ci vogliano più di 2000€ per il software di design ;) A meno che voi non abbiate una licenza per Cadence o Synopsys :D
?
C'è scritto che costa 129$ e il software interfaccia è gratuito...
mi sfugge qualcosa :asd:
?
C'è scritto che costa 129$ e il software interfaccia è gratuito...
mi sfugge qualcosa :asd:
Solo il software di INTERFACCIA è gratuito :D Non il software per la progettazione.
Le librerie ci sono solo per Synopsys, Cadence e, ovviamente, per il software Xilinx.
Serve questo: http://avnetexpress.avnet.com/store/em/EMController?langId=-1&storeId=500201&catalogId=500201&action=products&N=0&mfr=XLX&hrf=http://www.xilinx.com/onlinestore/design_resources.htm&term=EF-ISE-EMBD-NL
Questa è un'ottica RISC, però. E non sarei d'accordo nemmeno di fare un'eccezione per i dati immediati, perché sempre di un'eccezione si tratta: rompe la "linearità" dell'istruzione. :D
Comunque l'estensione non serve a descrivere l'operazione, perché questa è già nota grazie ai campi mode & register. L'estensione nasce per prelevare le informazioni descritte in questi due campi, esattamente come il valore immediato, che non è "incluso" direttamente nella word dell'opcode, ma di cui "si sa" che serve leggerlo.
Non sono del tutto d'accordo: nell'estensione (tralasciando l'immediato a 8 bit) ci sono "istruzioni" su quale registro scegliere ad esempio, quindi li conto come opcode. I dati immediati invece sono appunto "dati". Quindi l'unità di fetch si prende i 32 bit dell'istruzione, poi gli altri stadi della pipeline accedono alla memoria per prendere gli immediati (come se dovessero accedere per qualsiasi altro dato).
Visto che il fetch si prende 64 bit in un colpo, dentro ci sta tutta un'istruzione a 32 bit, che sia essa completamente a 32 bit, che divisa in 16 bit + estensione. Quindi preferivo che i campi dell'estensione siano inseriti in un opcode a 32 bit, che magari poi si riesce a ottimizzare in modo migliore rispetto ad avere opcode e estensione come 2 unità separate, e mi viene più semplice da decodificare (devo solo distinguere tra 16/32 bit, e non tra 16/16+e/32/32+e)
Purtroppo ho un lavoro estivo che mi tiene impegnato tutto il giorno, e non riesco a completare la mia codifica a 32 bit :(
Se prendi il progetto Natami, delle CPU 68020+ a cui stanno lavorando, il designer non si lamenta affatto dell'estensione, anzi, ne esalta le peculiarità! :D
Allora sono in disaccordo con lui ;) A parte questo, ma il Natami comprende anche la costruzione dell'intero processore? Non ne usano uno già distribuito da Freescale?
Un ARM, insomma. :D Anche se i Cortex-A9 hanno introdotto l'OoO.
Ooops :doh: (;) )
Semplice e bella ci può stare, ma per renderla veloce, beh, dipende dalle capacità del designer. :D
Anche l'ISA ci mette un po' di suo ;) (*cough*eliminare estensione*cough* :D)
Comunque potremmo pensare di adottare qualcosa di più comodo per programmarli (http://www.myhdl.org/doku.php/start). :fiufiu:
Chissà come mai, me lo aspettavo :D
La scheda costa anche poco...
http://www.digilentinc.com/Products/Detail.cfm?NavPath=2,400,789&Prod=NEXYS2
Peccato che ci vogliano più di 2000€ per il software di design ;) A meno che voi non abbiate una licenza per Cadence o Synopsys :D
Il "software di design" è il "compilatore" che traduce il VHDL nello schema proprietario di ogni dispositivo?
E poi, che differenza c'è tra un fpga e un cpld?
(Direi che a 2 k$, stiamo prima a farcelo con i 74LSxx ;))
Che ne pensate di questa?
http://www.opalkelly.com/products/xem3001/
cdimauro
01-07-2010, 23:43
Non sono del tutto d'accordo: nell'estensione (tralasciando l'immediato a 8 bit) ci sono "istruzioni" su quale registro scegliere ad esempio, quindi li conto come opcode. I dati immediati invece sono appunto "dati". Quindi l'unità di fetch si prende i 32 bit dell'istruzione, poi gli altri stadi della pipeline accedono alla memoria per prendere gli immediati (come se dovessero accedere per qualsiasi altro dato).
Ho capito cosa intendi, e da questo punto di vista hai ragione.
Considera, comunque, una cosa: devi conoscere quant'è lunga l'istruzione e avere (fetch) tutti i suoi dati prima di procedere alla decodifica e poi all'esecuzione. Da questo punto di vista non c'è differenza fra un dato immediato o una extension word: sono comunque già presenti all'atto della decodifica.
Visto che il fetch si prende 64 bit in un colpo, dentro ci sta tutta un'istruzione a 32 bit, che sia essa completamente a 32 bit, che divisa in 16 bit + estensione.
Hum. Io preferirei avere fetch da 128 bit (16 byte), e una coda interna che può contenere fino a 32 byte (due blocchi da 16 byte) in modo da avere la possibilità di memorizzare l'istruzione più lunga interamente nella coda.
Quindi preferivo che i campi dell'estensione siano inseriti in un opcode a 32 bit, che magari poi si riesce a ottimizzare in modo migliore rispetto ad avere opcode e estensione come 2 unità separate, e mi viene più semplice da decodificare (devo solo distinguere tra 16/32 bit, e non tra 16/16+e/32/32+e)
Chiaro, ma nella mia ISA non puoi inserire la "extension" dentro l'opcode a 32 bit: non c'è proprio spazio.
Purtroppo ho un lavoro estivo che mi tiene impegnato tutto il giorno, e non riesco a completare la mia codifica a 32 bit :(
Idem, ma c'ho lavorato questa sera e sono riuscito a finire (almeno per adesso) la tabella degli opcode a 32 bit per quanto riguarda la modalità utente. Ho dato anche una sistemata alla tabella degli opcode a 16 bit.
Puoi dargli un'occhiata se hai tempo, perché ho aggiornato i 3 messaggi della prima pagina del thread. Così vedi un po' com'è venuta l'opcode table a 32 bit (ma non mi svenire per la complessità :D).
Al momento manca pure la parte relativa al coprocessore (penso a una soluzione SIMD: ormai un'FPU tradizionale ha poco senso), ma ho riservato gli ultimi 24 bit della opcode table a 32 bit per utilizzarli allo scopo in futuro.
Allora sono in disaccordo con lui ;) A parte questo, ma il Natami comprende anche la costruzione dell'intero processore? Non ne usano uno già distribuito da Freescale?
No no: stanno progettando un nuovo processore basato su 68020, e con moderne tecniche (branch free, no latency cache, e roba del genere).
Lo stanno anche carrozzando con le estensioni dei ColdFire, e aggiungendo altre istruzioni (utili) ideate da loro o suggerite dalla gente. CISC rulez. :D
In questo periodo stanno valutando anche l'implementazione di una superpipeline, e sono soddisfattissimi del progetto.
Se consideri che un 68020+ ha un decoder decisamente complicato (soltanto x86 lo batte in complessità), con istruzioni lunghe fino a 22 byte, e che necessita di leggere diverse word (oltre all'opcode principale a 16 bit) per risalire alla lunghezza complessiva dell'istruzione, puoi immaginare che non sia affatto facile. Eppure stanno lavorando su un FPGA (dove il 68050+ è uno dei componenti più piccoli :D).
Anche l'ISA ci mette un po' di suo ;) (*cough*eliminare estensione*cough* :D)
Ho capito, ho capito. Intanto dalle un'occhiata, e poi se ne parla.
Chissà come mai, me lo aspettavo :D
Potrebbe essere utile / comodo. Specialmente per trascinare i programmatori nel lato oscuro della forza. :fagiano:
Il "software di design" è il "compilatore" che traduce il VHDL nello schema proprietario di ogni dispositivo?
E poi, che differenza c'è tra un fpga e un cpld?
(Direi che a 2 k$, stiamo prima a farcelo con i 74LSxx ;))
Che ne pensate di questa?
http://www.opalkelly.com/products/xem3001/
Non è nemmeno cara per quello che offre. E con 400 mila gate, altro che decoder con estensioni! :D
Ma ad oggi, che senso ha un'architettura CISC?
Da quello che ho capito a fronte di svantaggi concreti come maggior costo, maggiore complessità, maggior sforzo di manutenzione, difficoltà nello scrivere un compilatore, offre solo una comodità molto maggiore per il programmatore.
Ma il programmatore oggi usa sempre e comunque un compilatore, quindi l'unico vantaggio è nullo.
O no?
cdimauro
02-07-2010, 07:48
Ma ad oggi, che senso ha un'architettura CISC?
Piuttosto ci si potrebbe chiedere: ma ha senso un'architettura RISC? :D
I CISC dominano e hanno prestazioni mediamente più elevate dei RISC. Quelli che erano ritenuti le Cenerentole destinate a venire soppiantate dai più efficienti RISC, alla fine li hanno seppelliti.
Da quello che ho capito a fronte di svantaggi concreti come maggior costo, maggiore complessità, maggior sforzo di manutenzione,
Ma neanche tanto: dipende da come hai strutturato l'ISA.
E' chiaro che un processore x86 non è l'esempio ottimale da questo punto di vista, considerato che si trascina montagne di roba legacy, ma nonostante tutto è riuscito a imporsi addirittura nella prestigiosa classifica TOP500 (prima di dominio assoluto dei RISC: non esisteva nessun CISC).
difficoltà nello scrivere un compilatore, offre solo una comodità molto maggiore per il programmatore.
Ma il programmatore oggi usa sempre e comunque un compilatore, quindi l'unico vantaggio è nullo.
O no?
Ci sono dei vantaggi non indifferenti anche per il compilatore, posto che sappia sfruttare le peculiarità dell'ISA (e non è nemmeno così difficile).
Ma a parte questo, la maggior complessità delle istruzioni consente a un CISC di poter eseguire più "lavoro utile" (per ciclo di clock) rispetto a un RISC, che richiederebbe invece più istruzioni e, ad esempio, una superpipeline per poter competere a livello puramente prestazionale.
Oltre a ciò hanno codice più compatto e, quindi, un minor uso della cache per il codice, e della bandwidth verso le cache e la memoria.
Quindi il maggior costo del decoder incide poi positivamente per i tre punti (prestazioni, spazio, banda).
Considera, tra l'altro, che alcuni RISC implementano anche istruzioni complesse (come le load/store di più registri) proprio perché sono intrinsecamente utili, abbracciando di fatto la filosofia CISC.
Parlo di PowerPC e ARM; e quest'ultimo ha tante di quelle istruzioni e modalità di funzionamento che lo possiamo considerare il CISC dei RISC. :D
Ma non sono i soli. In ambito microcontroller anche case come la MIPS hanno presentato versioni "compatte" della loro ISA, e a volte pure a lunghezza variabile.
Insomma, col tempo c'è stata una convergenza. Ed è una fatto positivo, perché ha permesso ad entrambe le famiglie di avvantaggiarsene e diventare più competitive.
Non è un caso che i CISC moderni facciano uso di un'unità d'esecuzione interna RISC: hanno, in questo modo, conciliato l'esigenza di codice più compatto / efficiente, con la maggior semplicità di decodifica & velocità d'esecuzione dei RISC.
Il viceversa è più difficile, ma, come dicevo, anche alcuni RISC si sono lasciati andare a istruzioni più complesse e/o con opcode a lunghezza variabile.
Il "software di design" è il "compilatore" che traduce il VHDL nello schema proprietario di ogni dispositivo?
Esatto.
Il CPLD non l'ho mai visto. Non ne ho idea.
Piuttosto ci si potrebbe chiedere: ma ha senso un'architettura RISC? :D
I CISC dominano e hanno prestazioni mediamente più elevate dei RISC. Quelli che erano ritenuti le Cenerentole destinate a venire soppiantate dai più efficienti RISC, alla fine li hanno seppelliti.
Ci si può anche chiedere il perché dominano. Dominano, secondo me, non per bontà intrinseca dell'istruction set, ma per motivi legacy e perché i produttori hanno un dominio tecnologico impressionante rispetto ai concorrenti. Per mantenere una unità di decodifica che occupa il 30% del die della CPU (cache esclusa) bisogna ad ogni costo produrre meglio degli altri per essere in vantaggio sia dal punto di vista delle prestazioni, che dal punto di vista della potenza dissipata.
Se i CISC dominano perché sono migliori...mi domando perché ora nei nostri PC non abbiamo un 680x0.
Il mercato ha scelto i CISC secondo me per fattori diversi del migliore/peggiore. Ha continuato a mantenere i CISC solo per legacy e per il vantaggio tecnologico dei produttori.
Supponendo di essere una piccola azienda che vuole entrare nel mondo dei processori, ci sarebbe da chiedersi quale ambito d'utilizzo sarebbe più proficuo attualmente.
Non è assolutamente possibile fare concorrenza ad AMD e ad Intel, chiunque ci abbia provato si è dovuto ridimensionare andando ad occupare nicchie di mercato.
Secondo me possibilità di concorrenza c'è nei dispositivi embedded e a basso consumo. Certo...ci sono ARM e Mips, ma competere con loro dal punto di vista dell'ottimizzazione architetturale secondo me non è un'utopia.
Anche loro sono in debito di retro-compatibilità e grazie a questo sono costretti a scelte architetturali ben precise.
Entrando sul mercato con una CPU completamente nuova è secondo me possibile fargli concorrenza soprattutto sul parametro IPC/Watt.
cdimauro
03-07-2010, 06:20
Ci si può anche chiedere il perché dominano. Dominano, secondo me, non per bontà intrinseca dell'istruction set, ma per motivi legacy e perché i produttori hanno un dominio tecnologico impressionante rispetto ai concorrenti.
Non credo al dominio tecnologico che, a mio avviso, è sempre stato e rimane in mano ai progettisti di processori RISC, che hanno tirato fuori vagonate di idee da 30 anni a questa parte, poi copiate "spudoratamente" dai progettisti CISC.
Per mantenere una unità di decodifica che occupa il 30% del die della CPU (cache esclusa)
Questi numeri non ci sono più, come ho scritto nell'articolo in cui ho confrontato ARM e x86 (http://www.appuntidigitali.it/4375/arm-vs-intel-i-cut-the-power/) e in quello in cui mi sono espressamente dedicato al decoder x86 e alle implicazioni che ne derivano (http://www.appuntidigitali.it/5393/la-guerra-dellisa-x86-limpatto-su-decoder-compilatori-e-implicazioni-varie/).
bisogna ad ogni costo produrre meglio degli altri per essere in vantaggio sia dal punto di vista delle prestazioni, che dal punto di vista della potenza dissipata.
Se per "produrre meglio" ti riferisci all'adozione di processi produttivi più raffinati, hai in parte ragione. Da questo punto di vista Intel "galoppa", ma AMD (che hai citato dopo) è costretta a inseguire.
Mentre colossi come IBM non hanno proprio nulla da invidiare a Intel, e producono esclusivamente RISC (con la famiglia POWER progettata esclusivamente per i server).
Se i CISC dominano perché sono migliori...mi domando perché ora nei nostri PC non abbiamo un 680x0.
Perché Motorola ha voluto cavalcare il comodo treno dei PowerPC, abbandonando la famiglia di processori che le aveva dato fasti e onori (oltre a vagonate di soldi).
E questa è una cosa che ancora oggi molti affezionati amighisti le rinfacciano.
Comunque i 68000 hanno avuto maggiori problemi di scalabilità dovuti all'ISA troppo complicata.
Ricordo un articolo su MC MicroComputer in cui si confrontavano 3 microprocessori: SPARC, 80386 e 68020, assegnando loro un "coefficiente CISC" in base alla complessità dell'ISA (non chiedermi su quale base fosse calcolato, perché non me lo ricordo proprio). Il risultato era di 1.0, 1.1 e 1.2.
Ora, se consideri che gli x86 sono fra i più complicati processori, puoi immaginare quanto potesse esserlo un 68020+.
Debbo dire che i motivi principali della maggior complessità di 68020 & successori sono due. In ordine di priorità: la presenza di modalità d'indirizzamento mostruosamente complesse (quelle con DOPPIA indirezione verso la memoria; sogno di programmatori e compilatori OOP, ma terrore dei designer di microprocessori) e di istruzioni altrettanto complesse (ricordo adesso CALLM/RTM, MOVEP, CHK2, CMP2, CAS, CAS2, ma in particolare quelle per manipolare campi di bit).
Per queste ultime in parte Motorola cominciò a porre rimedio, eliminandone alcune (dal 68060) ed emulandole intercettandolo le eccezioni di istruzione illegale (erano comunque istruzioni rare, per cui l'approccio aveva un senso), ma non poteva fare lo stesso con la doppia indirezione.
Il mercato ha scelto i CISC secondo me per fattori diversi del migliore/peggiore. Ha continuato a mantenere i CISC solo per legacy e per il vantaggio tecnologico dei produttori.
Sul "legacy" concordo.
Supponendo di essere una piccola azienda che vuole entrare nel mondo dei processori, ci sarebbe da chiedersi quale ambito d'utilizzo sarebbe più proficuo attualmente.
Non è assolutamente possibile fare concorrenza ad AMD e ad Intel, chiunque ci abbia provato si è dovuto ridimensionare andando ad occupare nicchie di mercato.
Secondo me possibilità di concorrenza c'è nei dispositivi embedded e a basso consumo. Certo...ci sono ARM e Mips, ma competere con loro dal punto di vista dell'ottimizzazione architetturale secondo me non è un'utopia.
Anche loro sono in debito di retro-compatibilità e grazie a questo sono costretti a scelte architetturali ben precise.
Vero. Però anche ARM ha dimostrato di poter tagliare col passato. Intanto la vecchia architettura a 26 bit è sparita già da anni, rimpiazzata dalla nuova a 32 bit; e poi qualche altra cosa l'ha rimossa.
Comunque sostanzialmente l'architettura è la stessa da un po' di anni a questa parte, e il decoder non è certamente dei più semplici, specialmente con roba come Thumb/2 che ormai è "mandatory".
Anzi, addirittura ci sono ARM orientati a sistemi embedded / microcontrollori che implementano soltanto il set Thumb-2 (che è sostanzialmente un CISC, usando opcode a lunghezza variabile, di 16 o 32 bit); il che è tutto dire. :D
Entrando sul mercato con una CPU completamente nuova è secondo me possibile fargli concorrenza soprattutto sul parametro IPC/Watt.
In questo caso punterei su un VLIW, senza alcun dubbio (e sarebbe interessante a questo punto la CPU VLIW con opcode a 32 bit di cui ha passato il link Z80Fan prima).
Solo che gli ambiti applicativi sono quelli del calcolo parallelo, e non general purpose.
Ogni ISA è frutto di compromessi ed è necessariamente orientata a un tipo di calcolo piuttosto che un altro.
Non credo al dominio tecnologico che, a mio avviso, è sempre stato e rimane in mano ai progettisti di processori RISC, che hanno tirato fuori vagonate di idee da 30 anni a questa parte, poi copiate "spudoratamente" dai progettisti CISC.
Per dominio tecnologico mi riferivo al dominio dal punto di vista della capacità e tecnologia di produzione, che è l'unico motivo che ha permesso ai CISC di restare avanti dal punto di vista delle prestazioni.
IBM non è mai stata interessata alla produzione di CPU su larghissima scala, altrimenti avrebbe potuto comprare Intel (anche in tempi più recenti).
Appunto proponevo una strada diversa dal VLIW (sinceramente non mi è mai piaciuta l'idea di pacchettizzare più istruzioni insieme), mantenendo sempre il parallelismo implicito, ma sfruttando un'architettura RISC interna.
Cosa che di fatto dovrebbe unire una più genericità dell'uso della CPU ed una semplicità nell'implementazione dei compilatori.
All'aumentare del numero di unità di esecuzione basta poi cambiare solamente alcuni parametri nei compilatori, pur mantenendo la retrocompatibilità con CPU con meno unità di calcolo.
Ho capito cosa intendi, e da questo punto di vista hai ragione.
Considera, comunque, una cosa: devi conoscere quant'è lunga l'istruzione e avere (fetch) tutti i suoi dati prima di procedere alla decodifica e poi all'esecuzione. Da questo punto di vista non c'è differenza fra un dato immediato o una extension word: sono comunque già presenti all'atto della decodifica.
Non è necessario avere i dati per poter eseguire la decodifica, e tra questa e l'esecuzione ci può essere uno stadio che preleva i dati dalla memoria, e se questi erano costanti immediate, semplicemente li preleva usando il program counter come puntatore. Poi che i dati siano già nel processore perchè sono stati prelevati da un buffer, è un altro discorso.
Cmq la mia idea era solo di "etichettare" l'estensione come facente parte dell'opcode, se vuoi tenerla, io te la implemento.
Hum. Io preferirei avere fetch da 128 bit (16 byte), e una coda interna che può contenere fino a 32 byte (due blocchi da 16 byte) in modo da avere la possibilità di memorizzare l'istruzione più lunga interamente nella coda.
Si, io parlavo di 64 bit per ogni singola transizione sul bus dati; le code interne le possiamo fare anche più lunghe.
Idem, ma c'ho lavorato questa sera e sono riuscito a finire (almeno per adesso) la tabella degli opcode a 32 bit per quanto riguarda la modalità utente. Ho dato anche una sistemata alla tabella degli opcode a 16 bit.
Puoi dargli un'occhiata se hai tempo, perché ho aggiornato i 3 messaggi della prima pagina del thread. Così vedi un po' com'è venuta l'opcode table a 32 bit (ma non mi svenire per la complessità :D).
La tabella a 16 la ho controllata, e mi sembra veramente bella. Quella a 32 non la ho ancora esaminata a fondo.
Ma ad oggi, che senso ha un'architettura CISC?
Piuttosto ci si potrebbe chiedere: ma ha senso un'architettura RISC? :D
La domanda giusta è: ma ha senso usare un full-CISC o un full-RISC? ;)
Proprio perchè i processori di oggi prendono il bene che c'è su entrambe architetture (come ha già spiegato Cesare), non abbiamo più processori come il VAX o i primi MIPS.
I CISC dominano e hanno prestazioni mediamente più elevate dei RISC
Precisando: a parità di clock.
E' chiaro che un processore x86 non è l'esempio ottimale da questo punto di vista, considerato che si trascina montagne di roba legacy, ma nonostante tutto è riuscito a imporsi addirittura nella prestigiosa classifica TOP500 (prima di dominio assoluto dei RISC: non esisteva nessun CISC).
Su questo penso anche io che sia più per una questione di costo e di facile procurabilità; avendo un supercomputer basato su powerpc (per esempio), costerebbe minimo il triplo di un normale supercomputer con AMD (a parità di prestazioni; può essere anche più piccolo), e anche il costo per l'aggiornamento non sarebbe indifferente. Forse anche il fatto che Linux è maggiormente supportato su questa categoria di cpu aumenta l'interesse (dato che il 91% della TOP500 usa Linux).
Oggi lavoro un po' al kernel, quindi non mi dedico troppo alla cpu, ma voglio solo segnalare 2 punti:
1) DBcc e DBRA non dovrebbero prendere un signed int invece di un unsigned? per fare i salti all'indietro?
2)...
RSUBS D0, [A0, D1.Q * 128, $7F0123]+.W, -[SP].L !!!!
Va bene il CISC, ma non stai un po' esagerando ????
(Ovvero, è una istruzione talmente utilizzata che è necessario includerla? O si può emulare con 2-3 istruzioni elementari per quei casi eccezionali che la richiedono?)
cdimauro
03-07-2010, 20:48
Per dominio tecnologico mi riferivo al dominio dal punto di vista della capacità e tecnologia di produzione, che è l'unico motivo che ha permesso ai CISC di restare avanti dal punto di vista delle prestazioni.
IBM non è mai stata interessata alla produzione di CPU su larghissima scala, altrimenti avrebbe potuto comprare Intel (anche in tempi più recenti).
Ma aveva, ha e avrà tutto il know how per la tecnologia di produzione, come pure ha capacità produttiva (pensiamo alle console, ad esempio: montano tutte processori di derivazione Power/PowerPC).
Col mercato desktop c'ha provato col PPC970 (aka G5), e le è andata buca...
Appunto proponevo una strada diversa dal VLIW (sinceramente non mi è mai piaciuta l'idea di pacchettizzare più istruzioni insieme), mantenendo sempre il parallelismo implicito, ma sfruttando un'architettura RISC interna.
Cosa che di fatto dovrebbe unire una più genericità dell'uso della CPU ed una semplicità nell'implementazione dei compilatori.
Però, se non ho capito male gli esempi e le discussioni di un po' di messaggi fa, le istruzioni devono contenere comunque delle informazioni che permettono all'unità di esecuzione di schedularle ed eseguirle correttamente.
Ciò richiede spazio nell'opcode, ed è per questo motivo che col tempo s'è preferito raggruppare le istruzioni sfruttando il modello VLIW: le informazioni di schedulazione erano fisse e occupavano un certo quantitativo di bit, mentre tutti gli altri erano equamente divisi in base al numero di istruzioni che un bundle / macroopcode poteva contenere.
All'aumentare del numero di unità di esecuzione basta poi cambiare solamente alcuni parametri nei compilatori, pur mantenendo la retrocompatibilità con CPU con meno unità di calcolo.
Hum. Questo non mi piace molto. Un classico processore OoO non ha di questi problemi.
Non è necessario avere i dati per poter eseguire la decodifica,
Un'estensione non ha bisogno di essere "decodificata": non fa parte dell'opcode vero e proprio, ma riguarda l'unità di calcolo EA; è, quindi, sufficiente che tutte le informazioni siano presenti quando l'istruzione arriva a questo stadio della pipeline.
e tra questa e l'esecuzione ci può essere uno stadio che preleva i dati dalla memoria, e se questi erano costanti immediate, semplicemente li preleva usando il program counter come puntatore.
E' quello che nei moderni processori 680x0 viene chiamato stadio EA.
Poi che i dati siano già nel processore perchè sono stati prelevati da un buffer, è un altro discorso.
Cmq la mia idea era solo di "etichettare" l'estensione come facente parte dell'opcode, se vuoi tenerla, io te la implemento.
No, fermati: tu sviluppa la tua ISA, e io la mia. E prova a implementare la tua.
Io, quando avrò un po' di tempo, proverò a modificare il tuo codice per implementare man mano la mia ISA.
Non è necessario lavorare a una sola ISA: si può procedere in maniera indipendente.
Precisando: a parità di clock.
Hum. Difficile confrontare due processori, anche della stessa "razza" (entrambi RISC, o entrambi CISC), semplicemente fissando il clock.
Le particolari implementazioni possono, e sono, essere pensate per incrementare l'ILP a danno della frequenza, e viceversa.
A mio avviso si potrebbe prendere il modello con la frequenza massima raggiunta con un determinato processo produttivo e all'incirca lo stesso voltaggio.
Su questo penso anche io che sia più per una questione di costo e di facile procurabilità; avendo un supercomputer basato su powerpc (per esempio), costerebbe minimo il triplo di un normale supercomputer con AMD (a parità di prestazioni; può essere anche più piccolo), e anche il costo per l'aggiornamento non sarebbe indifferente.
Sui supercomputer ci vanno i processori studiati per i server, non le versioni desktop, e lì i costi sono elevati anche per gli x86.
Forse anche il fatto che Linux è maggiormente supportato su questa categoria di cpu aumenta l'interesse (dato che il 91% della TOP500 usa Linux).
Non credo che il s.o. faccia la differenza per un supercomputer (di Windows esiste anche una versione HPC).
Oggi lavoro un po' al kernel, quindi non mi dedico troppo alla cpu, ma voglio solo segnalare 2 punti:
1) DBcc e DBRA non dovrebbero prendere un signed int invece di un unsigned? per fare i salti all'indietro?
Sì, sono usate esclusivamente per implementare cicli, per cui saltano soltanto all'indietro.
2)...
RSUBS D0, [A0, D1.Q * 128, $7F0123]+.W, -[SP].L !!!!
Va bene il CISC, ma non stai un po' esagerando ????
(Ovvero, è una istruzione talmente utilizzata che è necessario includerla? O si può emulare con 2-3 istruzioni elementari per quei casi eccezionali che la richiedono?)
Certo che si può implementare con istruzioni più elementari, ma non è questo il punto. Non farti impressionare dall'esempio che ho riportato (che non è nemmeno il più complesso :D), che serviva soltanto per far colpo. :fagiano:
Quell'istruzione devi vederla in questo modo:
Target.DSize = Dn.DSize BINOP Source.SSize.ExtensionType
L'obiettivo è quello di eseguire un'operazione su interi che possono essere con segno o no, ma di dimensione diversa (ad esempio sto sommando un intero a 8 bit a uno a 32 bit), e da qui l'esigenza di specificare la dimensione del dato sorgente e di quello di destinazione.
ExtensionType, che corrisponde al suffisso S o Z, a seconda che sia con segno o no, serve a esplicitare in che modo estendere opportunamente il dato sorgente in modo da fargli avere la stessa dimensione di quello di destinazione.
Questo permette di risolvere in maniera elegante e, soprattutto, efficiente il problema del casting (implicito o esplicito) fra due dati dello stesso tipo, ma di dimensione diversa.
Si tratta di un'operazione che capita (a volte implicitamente, perché ci pensa il compilatore), e che per un RISC o un CISC richiede diverse operazioni:
Caricare il dato
Estenderlo
Eseguire l'operazione
Memorizzarlo
Tutto ciò col mio processore viene gestito con una sola istruzione, con un netto risparmio di registri interessati, memoria cache e banda di memoria.
Per fare un esempio meno appariscente:
RSUBS D0, D1.B, D2.Q
Preleva il byte in D1, lo estende con segno a quad, gli sottrae la quad in D0 (perché è una RSUB; le istruzioni "reversed" le trovi anche in altre architetture; qui serve per scambiare il registro dati con il Source EA, visto che tutte le operazioni binarie utilizzano sempre un registro dati come elemento "a sinistra"), e memorizza la quad risultante in D2.
Non mi pare così complessa.
Hum. Questo non mi piace molto. Un classico processore OoO non ha di questi problemi.
In effetti... Bisognerebbe provare, forse non è necessario.
Mmmmhhh... Intanto mi faccio qualche domanda, magari pensate anche voi alle possibili risposte.
Bisognerebbe "provare" su una CPU con un numero di unità di esecuzione basso, fissando un numero massimo di flussi di istruzioni parallele contemporaneo e generando il codice assembly come se fossero disponibili un numero di unità di esecuzione pari al numero di flussi possibili.
Inoltre come sarebbe possibile limitare il "riutilizzo" di un indicatore di precedenza ?
Sul fatto di pacchettizzare le istruzioni, come nei VLIW, secondo me però potrebbe limitare lo sfruttamento delle unità di calcolo all'aumento di queste.
Bisognerebbe pensare a qualcosa scalabile anche all'aumento delle unità di calcolo, in modo da poter "produrre" versioni diverse della CPU con un numero variabile di unità di calcolo.
Un'estensione non ha bisogno di essere "decodificata": non fa parte dell'opcode vero e proprio, ma riguarda l'unità di calcolo EA; è, quindi, sufficiente che tutte le informazioni siano presenti quando l'istruzione arriva a questo stadio della pipeline.
Uhmm, potresti avermi convinto... (;))
No, fermati: tu sviluppa la tua ISA, e io la mia. E prova a implementare la tua.
Io, quando avrò un po' di tempo, proverò a modificare il tuo codice per implementare man mano la mia ISA.
Ehm... non penso ci sia un codice da modificare... non lo creerò in VHDL (o forse si, in un momento successivo, quando lo imparerò meglio), ma collegherò direttamente l'hardware come se stessi realizzando un circuito elettrico con le porte logiche (che è la funzione principale del simulatore, ovviamente gli elementi possono essere riuniti in un blocco maggiore per semplicità)
Non credo che il s.o. faccia la differenza per un supercomputer (di Windows esiste anche una versione HPC).
Beh, non penso che facciano girare il DOS su un Cray, non pensi? ;)
E poi, il suddetto Windows HPC edition è usata su 5 computer in tutto :D
Sì, sono usate esclusivamente per implementare cicli, per cui saltano soltanto all'indietro.
Potrebbero ottenere maggiori funzionalità permettendo un dato signed (magari qualche codice "strano", tipo "salta le prossime x istruzioni se il contatore è maggiore di zero e la condizione è verificata")
Certo che si può implementare con istruzioni più elementari, ma non è questo il punto. Non farti impressionare dall'esempio che ho riportato (che non è nemmeno il più complesso :D), che serviva soltanto per far colpo. :fagiano:
Quell'istruzione devi vederla in questo modo:
Target.DSize = Dn.DSize BINOP Source.SSize.ExtensionType
...
Per fare un esempio meno appariscente:
RSUBS D0, D1.B, D2.Q
Preleva il byte in D1, lo estende con segno a quad, gli sottrae la quad in D0 (perché è una RSUB; le istruzioni "reversed" le trovi anche in altre architetture; qui serve per scambiare il registro dati con il Source EA, visto che tutte le operazioni binarie utilizzano sempre un registro dati come elemento "a sinistra"), e memorizza la quad risultante in D2.
Non mi pare così complessa.
No certo, non parlavo della complessità del calcolo in sè, parlavo del calcolo dell'indirizzo, secondo me un po' eccessivo. Magari si poteva risolvere con un:
ADD A0, $7F0123, A0
ADD A0, D0*128, A0 ; sfruttando un barrel shifter
RSUBS D0, [A0].W, -[SP].L
oppure
ADD A0, D0*128, A0 ; sfruttando un barrel shifter
RSUBS D0, [A0, $7F0123]+.W, -[SP].L
Certo, usa 3/2 istruzioni invece di 1, ma ciò a vantaggio della codifica delle istruzioni (meno spazio richiesto per le codifiche avanzate), e complessità dell'accesso alla memoria.
Butto là un pensiero completamente OT :D
Nelle architetture moderne è comprovato che per sfruttare la legge di Moore c'è la necessità di far girare sempre più threads in parallelo.
Però il programmatore si scontra con l'evidente difficoltà di far scalare manualmente il numero di threads a seconda della macchina su cui sta lavorando...
l'ideale sarebbe che il programmatore potesse considerare di lavorare su "tanti" threads, nell'ordine delle migliaia, perchè il parallelismo abbia un senso.
Allo stesso tempo vogliamo che i task seriali siano eseguiti con tutte le risorse del processore.
Non potrebbe essere possibile montare un dispatcher OoO tra il programmatore ed un'ALU composta da un numero qualsiasi di core indipendenti, in modo da simulare la presenza di un numero di core nativi a piacimento?
In questo modo il programmatore specifica un numero enorme (il numero di pixel in un'immagine per esempio) di thread simili, e poi l'OoO sarebbe in grado di riempire costantemente le pipeline piuttosto profonde delle ALU sfruttando l'isolamento di questi sotto threads.
Allo stesso tempo però sarebbe più veloce delle architetture VLIW perchè in casi critici come un branch potrebbe comportarsi come un processore normale senza usare masks o roba varia.
Però probabilmente non è vero niente :asd:
cdimauro
04-07-2010, 06:50
In effetti... Bisognerebbe provare, forse non è necessario.
Mmmmhhh... Intanto mi faccio qualche domanda, magari pensate anche voi alle possibili risposte.
Bisognerebbe "provare" su una CPU con un numero di unità di esecuzione basso, fissando un numero massimo di flussi di istruzioni parallele contemporaneo
Con flusso intendi un gruppo di istruzioni eseguibili contemporaneamente?
e generando il codice assembly come se fossero disponibili un numero di unità di esecuzione pari al numero di flussi possibili.
Hum. Da qui sembrerebbe, invece, indicare il numero di istruzioni eseguibili.
Inoltre come sarebbe possibile limitare il "riutilizzo" di un indicatore di precedenza ?
Questo è difficile da realizzare a causa delle catene di dipendenze che si vengono a generare in maniera naturale.
Detto in altri termini, se il codice fosse largamente indipendente, avremmo processori massicciamente paralleli. In realtà quelli moderni sono tarati per eseguire 3, massimo 4 istruzioni per ciclo di clock.
E per ottenere i migliori risultati arrivano a mantenere "in volo" anche un centinaio di istruzioni (conservando le relative dipendenze).
Ecco perché è difficile parametrizzare il numero di bit necessari negli opcode per codificare le informazioni di dipendenza / appartenenza a un gruppo. Inoltre possono portare via parecchio spazio.
Sul fatto di pacchettizzare le istruzioni, come nei VLIW, secondo me però potrebbe limitare lo sfruttamento delle unità di calcolo all'aumento di queste.
Bisognerebbe pensare a qualcosa scalabile anche all'aumento delle unità di calcolo, in modo da poter "produrre" versioni diverse della CPU con un numero variabile di unità di calcolo.
Di fatti i VLIW per me sono ottime soluzioni quando hai fissato in maniera precisa il numero di unità di esecuzioni e la tipologia di codice esegutio.
Diversamente le soluzioni OoO, sebbene molto più costose, sono quelle che risolvono a valle il problema, ottimizzando meglio di qualunque ISA e/o compilatore l'uso delle risorse a disposizione in un determinato chip; tra l'altro col pregevole vantaggio di non dipendere da una determinata ISA, e di calcolare al volor e conservare internamente tutte le informazioni sulle dipendenze.
Uhmm, potresti avermi convinto... (;))
:cool:
Ehm... non penso ci sia un codice da modificare... non lo creerò in VHDL (o forse si, in un momento successivo, quando lo imparerò meglio), ma collegherò direttamente l'hardware come se stessi realizzando un circuito elettrico con le porte logiche (che è la funzione principale del simulatore, ovviamente gli elementi possono essere riuniti in un blocco maggiore per semplicità)
Ho capito. Va bene, mi servirà comunque come spunto.
Beh, non penso che facciano girare il DOS su un Cray, non pensi? ;)
E poi, il suddetto Windows HPC edition è usata su 5 computer in tutto :D
Sì, ma volevo soltanto sottolineare che un supercomputer non dipende dal s.o. usato. :fagiano:
Potrebbero ottenere maggiori funzionalità permettendo un dato signed (magari qualche codice "strano", tipo "salta le prossime x istruzioni se il contatore è maggiore di zero e la condizione è verificata")
Se è strano, coprirà casi molto rari. Io preferisco accelerare la soluzione più comune. :)
No certo, non parlavo della complessità del calcolo in sè, parlavo del calcolo dell'indirizzo, secondo me un po' eccessivo. Magari si poteva risolvere con un:
ADD A0, $7F0123, A0
ADD A0, D0*128, A0 ; sfruttando un barrel shifter
RSUBS D0, [A0].W, -[SP].L
oppure
ADD A0, D0*128, A0 ; sfruttando un barrel shifter
RSUBS D0, [A0, $7F0123]+.W, -[SP].L
Certo, usa 3/2 istruzioni invece di 1, ma ciò a vantaggio della codifica delle istruzioni (meno spazio richiesto per le codifiche avanzate), e complessità dell'accesso alla memoria.
Veramente quelle modalità servono proprio a compattare al meglio lo spazio. :)
Comunque ti faccio notare un paio di cose: tutti processori moderni hanno modalità di accesso indiretto alla memoria che fa uso di un registro base, di un registro scalato, e di un offset. Io, quindi, non ho aggiunto nulla di nuovo in questo senso. :stordita:
Altra cosa, anche tu, nella tua ISA, hai introdotto le stesse modalità. :read: :D
EDIT: guarda qui (http://www.natami.net/knowledge.php?b=2¬e=22888&x=1) (commento del 01 Jul 2010 23:11, di Gunnar von Boehn) cosa si può fare con un 68050+.
Interessanti sono anche i commenti 03 Jul 2010 07:32 e 03 Jul 2010 09:43 dello stesso.
Butto là un pensiero completamente OT :D
Nelle architetture moderne è comprovato che per sfruttare la legge di Moore c'è la necessità di far girare sempre più threads in parallelo.
Però il programmatore si scontra con l'evidente difficoltà di far scalare manualmente il numero di threads a seconda della macchina su cui sta lavorando...
l'ideale sarebbe che il programmatore potesse considerare di lavorare su "tanti" threads, nell'ordine delle migliaia, perchè il parallelismo abbia un senso.
Allo stesso tempo vogliamo che i task seriali siano eseguiti con tutte le risorse del processore.
Non potrebbe essere possibile montare un dispatcher OoO tra il programmatore ed un'ALU composta da un numero qualsiasi di core indipendenti, in modo da simulare la presenza di un numero di core nativi a piacimento?
In questo modo il programmatore specifica un numero enorme (il numero di pixel in un'immagine per esempio) di thread simili, e poi l'OoO sarebbe in grado di riempire costantemente le pipeline piuttosto profonde delle ALU sfruttando l'isolamento di questi sotto threads.
E' quello che fanno le GPU, ma eseguono compiti abbastanza diversi da una CPU "tradizionale". :D
Allo stesso tempo però sarebbe più veloce delle architetture VLIW perchè in casi critici come un branch potrebbe comportarsi come un processore normale senza usare masks o roba varia.
Ehm, questa non l'ho capita.
Però probabilmente non è vero niente :asd:
No, è che secondo me lavori troppo con le GPU e ti fai influenzare dal loro modello di funzionamento. :D
x cionci: è interessante anche il commento 01 Jul 2010 22:02 del link che ho riportato prima.
Con flusso intendi un gruppo di istruzioni eseguibili contemporaneamente?
Yes
Hum. Da qui sembrerebbe, invece, indicare il numero di istruzioni eseguibili.
Sì, l'avevo pensata così, però riflettendoci, potrebbe non perdere generalità anche "allocando" più unità di esecuzione logiche di quelle fisiche disponibili.
Il problema è che mentre stavo postando una prova, ho trovato il limite della rappresentazione della precedenza di Z80Fan...l'avevo detto che c'era qualcosa che non mi tornava...
Z80: riordina e metti la precedenza a queste operazioni sequenziali:
LOAD R0, A[0]
LOAD R1, A[1]
LOAD R2, A[2]
LOAD R3, A[3]
LOAD R4, B[0]
LOAD R5, B[1]
LOAD R6, B[2]
LOAD R7, B[3]
IMUL R4, R0
IMUL R5, R1
IMUL R6, R2
IMUL R7, R3
LOAD R8, $0
ADD R8, R7
ADD R8, R6
ADD R8, R5
ADD R8, R4
STORE R8, xyz
E' quello che fanno le GPU, ma eseguono compiti abbastanza diversi da una CPU "tradizionale". :D
Beh il comportamento interno veramente sarebbe piuttosto differente... le GPU ottengono il parallelismo usando il VLIW con parole lunghe fino a 16+16 floats su alcune decine di ALU.
inoltre praticamente non hanno alcuna pipeline, i branch sono solo simulati, e un thread è eseguito su un solo slot di un solo multiprocessore.
Quello che dico è costruire un array di processori in order con una pipeline molto molto profonda, a cui uno scheduler assegna istruzioni prendendole da un vastissimo pool di threads.
Quindi i core effettivi portebbero essere pure 4, per dire, e in una situazione ideale dove non ci sono chain-dependencies il core dove viene eseguita una data istruzione di un dato thread è del tutto arbitrario.
Che ne so, nel thread A add va su core 1, load va sul core 2 per riempire lo stallo dovuto ad un jump nel thread B.
Così dati un migliaio di threads indipendenti (al contrario della GPU che richiede la sincronia), in situazioni normali si eliminano tutti gli stalli semplicemente riempendoli con istruzioni degli altri threads (che essendo "tanti" possiamo considerare sempre presenti).
Ora vado da Intel a dirglielo :D :asd:
Se è strano, coprirà casi molto rari. Io preferisco accelerare la soluzione più comune. :)
Beh, non è una limitazione così grave avere la costante signed: non penso ci sia un ciclo che occupa più di 512 word.
Altra cosa, anche tu, nella tua ISA, hai introdotto le stesse modalità. :read: :D
Era per uniformarmi con la tua ;)
Il problema è che mentre stavo postando una prova, ho trovato il limite della rappresentazione della precedenza di Z80Fan...l'avevo detto che c'era qualcosa che non mi tornava...
Z80: riordina e metti la precedenza a queste operazioni sequenziali:
...
0 LOAD R0, A[0]
1 LOAD R1, A[1]
2 LOAD R2, A[2]
3 LOAD R3, A[3]
4 LOAD R4, B[0]
5 LOAD R5, B[1]
6 LOAD R6, B[2]
7 LOAD R7, B[3]
8 LOAD R8, $0
4 IMUL R4, R0
5 IMUL R5, R1
6 IMUL R6, R2
7 IMUL R7, R3
4 ADD R8, R4 ; riordinato
4 ADD R8, R5
4 ADD R8, R6
4 ADD R8, R7
4 STORE R8, xyz
Non vedo problemi, tutte le LOAD e le IMUL vengono eseguite in parallelo. Possiamo anche usare un altro registro per migliorare la situazione nell'ultima parte (anche se non è granchè, visto che il codice è inevitabilmente sequenziale):
0 LOAD R0, A[0]
1 LOAD R1, A[1]
2 LOAD R2, A[2]
3 LOAD R3, A[3]
4 LOAD R4, B[0]
5 LOAD R5, B[1]
6 LOAD R6, B[2]
7 LOAD R7, B[3]
8 LOAD R8, $0
9 LOAD R9, $0
4 IMUL R4, R0
5 IMUL R5, R1
6 IMUL R6, R2
7 IMUL R7, R3
4 ADD R9, R4
5 ADD R8, R5
4 ADD R9, R6
5 ADD R8, R7
5 ADD R8, R9
5 STORE R8, xyz
Se il calcolo che volevi fare è: xyz = A[0]*B[0] + A[1]*B[2] + A[2]*B[2] + A[3]*B[3], si possono risparmiare entrambi R8 e R9:
0 LOAD R0, A[0]
1 LOAD R1, A[1]
2 LOAD R2, A[2]
3 LOAD R3, A[3]
4 LOAD R4, B[0]
5 LOAD R5, B[1]
6 LOAD R6, B[2]
7 LOAD R7, B[3]
8 LOAD R8, $0
9 LOAD R9, $0
4 IMUL R4, R0
5 IMUL R5, R1
6 IMUL R6, R2
7 IMUL R7, R3
5 ADD R4, R5
7 ADD R6, R7
7 ADD R4, R6
7 STORE R4, xyz
Io direi che il valore massimo del campo dipendenza è il numero dei registri.
Quello che dico è costruire un array di processori in order con una pipeline molto molto profonda, a cui uno scheduler assegna istruzioni prendendole da un vastissimo pool di threads.
Quindi i core effettivi portebbero essere pure 4, per dire, e in una situazione ideale dove non ci sono chain-dependencies il core dove viene eseguita una data istruzione di un dato thread è del tutto arbitrario.
Che ne so, nel thread A add va su core 1, load va sul core 2 per riempire lo stallo dovuto ad un jump nel thread B.
Forse qualcosa come i Transputer (http://en.wikipedia.org/wiki/Transputer)?
Se il calcolo che volevi fare è: xyz = A[0]*B[0] + A[1]*B[2] + A[2]*B[2] + A[3]*B[3], si possono risparmiare entrambi R8 e R9:
Non stare a guardare il capello :D
Cosa ti garantisce in quello che hai scritto che
5 IMUL R5, R1
venga eseguite prima di
4 ADD R8, R5 ?
Inoltre le LOAD e le IMUL non possono essere svolte in parallelo...
Non stare a guardare il capello :D
Cosa ti garantisce in quello che hai scritto che
5 IMUL R5, R1
venga eseguite prima di
4 ADD R8, R5 ?
Ho capito. Le moltiplicazioni sono in una pipeline separata ma sono più lente...
Possiamo mettere un'istruzione che blocca tutto finchè tutte le istruzioni precedenti sono state eseguite. Il problema è che si blocca per il tempo dell'istruzione più lunga ancora in esecuzione (in questo caso, imul r7, r3). Bisogna vedere se questo, unito alla parallelizzazione, porta cmq a un vantaggio o se conveniva fare tutto in-order (io penso la prima).
Inoltre le LOAD e le IMUL non possono essere svolte in parallelo...
Sulle LOAD potrei concordare (c'è un solo bus), ma le imul possono essere parallelizzate se abbiamo più pipeline.
Ho capito. Le moltiplicazioni sono in una pipeline separata ma sono più lente...
Possiamo mettere un'istruzione che blocca tutto finchè tutte le istruzioni precedenti sono state eseguite. Il problema è che si blocca per il tempo dell'istruzione più lunga ancora in esecuzione (in questo caso, imul r7, r3). Bisogna vedere se questo, unito alla parallelizzazione, porta cmq a un vantaggio o se conveniva fare tutto in-order (io penso la prima).
Di fatto la CPU è in-order, solo che l'ordinamento è prestabilito nell'opcode.
Sulle LOAD potrei concordare (c'è un solo bus), ma le imul possono essere parallelizzate se abbiamo più pipeline.
No, intendevo dire che le IMUL non possono essere fatte in parallelo alle LOAD perché dipendono da queste ;)
Imho l'unico modo è dover reinserire gruppo e precedenza (3 bit per ciascuno):
1 1 0 LOAD R8, $0
2 1 0 LOAD R0, A[0]
3 1 0 LOAD R1, A[1]
4 1 0 LOAD R2, A[2]
5 1 0 LOAD R3, A[3]
6 2 1 LOAD R4, B[0]
7 3 1 LOAD R5, B[1]
8 4 1 LOAD R6, B[2]
9 5 1 LOAD R7, B[3]
10 6 2 IMUL R4, R0
11 7 3 IMUL R5, R1
12 1 4 IMUL R6, R2
13 2 5 IMUL R7, R3
14 3 2 ADD R8, R4
15 4 3 ADD R8, R5
16 5 4 ADD R8, R6
17 6 5 ADD R8, R7
18 7 6 STORE R8, xyz
In una ipotetica ALU con 3 unità di esecuzione e 2 LOAD/STORE unit:
9 7 5 3 1 L/S1
18 8 6 4 2 L/S2
16 13 10 ALU1
17 14 11 ALU2
15 12 ALU3
Il numero dei registri mi sembra un numero ragionevole per le possibili precedenze e gruppi. Sono però 10 bit, non certo pochi, ma l'instruction set RISC potrebbe permettere di contenere la dimensione dell'opcode.
...
In una ipotetica ALU con 3 unità di esecuzione e 2 LOAD/STORE unit:
9 7 5 3 1 L/S1
18 8 6 4 2 L/S2
16 13 10 ALU1
17 14 11 ALU2
15 12 ALU3
Il numero dei registri mi sembra un numero ragionevole per le possibili precedenze e gruppi. Sono però 10 bit, non certo pochi, ma l'instruction set RISC potrebbe permettere di contenere la dimensione dell'opcode.
Possiamo sempre avere solo 16 registri (più che sufficienti), e quindi risparmiamo 2 bit.
Cmq non si dovrebbe perdere molto con la "barriera" come ho detto precedentemente:
Mio Codice:
0 | 0 LOAD R0, A[0]
1 | 1 LOAD R1, A[1]
2 | 2 LOAD R2, A[2]
3 | 3 LOAD R3, A[3]
4 | 4 LOAD R4, B[0]
5 | 5 LOAD R5, B[1]
6 | 6 LOAD R6, B[2]
7 | 7 LOAD R7, B[3]
8 | 8 LOAD R8, $0
9 | 4 IMUL R4, R0
10 | 5 IMUL R5, R1
11 | 6 IMUL R6, R2
12 | 7 IMUL R7, R3
13 | 0 BREAK
14 | 4 ADD R8, R4 ; riordinato
15 | 4 ADD R8, R5
16 | 4 ADD R8, R6
17 | 4 ADD R8, R7
18 | 4 STORE R8, xyz
Pipeline:
Ogni * corrisponde ad un ciclo della pipeline occupata, - libera
operazione l/s : 2 cicli
add : 2 cicli
mul : 5 cicli
L/S1 0*2*4*6*8*----------------8*
L/S2 1*3*5*7*--------------------
ALU1 --------9****2****4*5*6*7*--
ALU2 --------0****3--------------
ALU3 --------1****---------------
intendo "10"-^
Totale: 28 cicli
Tua pipeline (con il tuo esempio):
L/S1 0*2*4*6*8*------------------
L/S2 1*3*--5*7*----------------7*
ALU1 --------9****2****----5*----
ALU2 --------0****-----3*----6*--
ALU3 ----------1****-----4*------
Praticamente su entrambi il mio e il tuo processore le operazioni finiscono in 28 cicli, solo che nel mio c'è un'istruzione in più, ma un campo in meno in tutte le istruzioni. Invece di semplicemente indicare le dipendenze e lasciare che la cpu si blocchi al momento opportuno, indico esplicitamente quando bloccarsi. Anche se viene fuori una cpu più potente, ad esempio che fa le moltiplicazioni in 2 cicli, l'istruzione in più non causa problemi:
L/S1 0*2*4*6*8*----------8*
L/S2 1*3*5*7*--------------
ALU1 --------9*2*4*5*6*7*--
ALU2 --------0*3-----------
ALU3 --------1*------------
Totale: 22 cicli
Non mi torna ancora qualcosa:
0 | 0 LOAD R0, A[0]
1 | 1 LOAD R1, A[1]
2 | 2 LOAD R2, A[2]
3 | 3 LOAD R3, A[3]
4 | 4 LOAD R4, B[0]
5 | 5 LOAD R5, B[1]
6 | 6 LOAD R6, B[2]
7 | 7 LOAD R7, B[3]
8 | 8 LOAD R8, $0
9 | 4 IMUL R4, R0
10 | 5 IMUL R5, R1
11 | 6 IMUL R6, R2
12 | 7 IMUL R7, R3
13 | 0 BREAK
14 | 4 ADD R8, R4
15 | 4 ADD R8, R5
16 | 4 ADD R8, R6
17 | 4 ADD R8, R7
18 | 4 STORE R8, xyz
Quale garanzia hai che la LOAD 0 sia finita prima della IMUL 9 ?
Se tutti gli altri dati sono in cache, mentre quello no ?
Bisogna quindi aggiungere un altro BREAK dopo l'istruzione 7.
Imho non dovresti nemmeno fare supposizioni sul tempo di esecuzione, altrimenti perdi di generalità.
Usando il tuo metodo ed il break si perde di espressività e parallelismo, almeno così mi sembra. Ci sono sicuramente situazioni in cui con il tuo metodo bisogna attendere che termini l'esecuzione con il break o bisogna mettere molte istruzioni in sequenza, mentre con il mio no. In generale mi pare di capire che succeda quando ci sono dipendenze multiple.
Prova questo:
1 0 LOAD R0, $5
2 1 LOAD R1, $10
3 0 LOAD R2, $10
4 3 LOAD R3, $15
5 0 LOAD R4, $5
5 2 SHR R0, R1
5 3 SHR R3, R2
6 5 ADD R4, R1
6 5 ADD R4, R2
7 5 IDIV R3, R1
8 6 IMUL R4, R0
9 7 ADD R2, R3
10 8 STORE R4, x
10 9 STORE R2, y
Con il tuo ?
Bisogna quindi aggiungere un altro BREAK dopo l'istruzione 7.
Si, è corretto.
Ci sono sicuramente situazioni in cui con il tuo metodo bisogna attendere che termini l'esecuzione con il break o bisogna mettere molte istruzioni in sequenza, mentre con il mio no.
Questo non lo ho capito: i nostri due processori sono equivalenti, solo che tu lasci al processore il compito di decidere se interrompersi e attendere la fine di quelle istruzioni per poi eseguire le altre, invece nel mio lo indico esplicitamente.
Prova questo:
1 0 LOAD R0, $5
2 1 LOAD R1, $10
3 0 LOAD R2, $10
4 3 LOAD R3, $15
5 0 LOAD R4, $5
5 2 SHR R0, R1
5 3 SHR R3, R2
6 5 ADD R4, R1
6 5 ADD R4, R2
7 5 IDIV R3, R1
8 6 IMUL R4, R0
9 7 ADD R2, R3
10 8 STORE R4, x
10 9 STORE R2, y
Con il tuo ?
0 LOAD R0, $5
1 LOAD R1, $10
2 LOAD R2, $10
3 LOAD R3, $15
4 LOAD R4, $5
BREAK
1 SHR R0, R1
3 SHR R3, R2
4 ADD R4, R1
BREAK
4 ADD R4, R2
3 IDIV R3, R1
BREAK
4 IMUL R4, R0
3 ADD R2, R3
BREAK
4 STORE R4, x
3 STORE R2, y
Mi è però venuto in mente un nuovo metodo: i numeri, invece che indicare quali istruzioni NON possono essere parallelizzate, indica quali istruzioni POSSONO essere parallelizzate:
1 LOAD R0, $5
1 LOAD R1, $10
1 LOAD R2, $10
1 LOAD R3, $15
1 LOAD R4, $5
2 SHR R0, R1
2 SHR R3, R2
2 ADD R4, R1
3 ADD R4, R2
3 IDIV R3, R1
4 IMUL R4, R0
4 ADD R2, R3
5 STORE R4, x
5 STORE R2, y
In questo modo, la cpu si interrompe automaticamente quando passa da un gruppo ad un altro. Se poi viene aggiunta logica supplementare, la nuova cpu, invece di bloccarsi completamente, prova a eseguire l'istruzione, ma se trova un operando in uso, si blocca da sola. Ad esempio, nel primo caso, la cpu si fermerebbe prima del shr R0, R1 aspettando la fine di load r4; nel secondo caso, la cpu vede che il load r0 e load r1 sono già stati completati, quindi esegue il shr r0,r1.
Questo non lo ho capito: i nostri due processori sono equivalenti, solo che tu lasci al processore il compito di decidere se interrompersi e attendere la fine di quelle istruzioni per poi eseguire le altre, invece nel mio lo indico esplicitamente.
0 LOAD R0, $5
1 LOAD R1, $10
2 LOAD R2, $10
3 LOAD R3, $15
4 LOAD R4, $5
BREAK
1 SHR R0, R1
3 SHR R3, R2
4 ADD R4, R1
BREAK
4 ADD R4, R2
3 IDIV R3, R1
BREAK
4 IMUL R4, R0
3 ADD R2, R3
BREAK
4 STORE R4, x
3 STORE R2, y
Il risultato non è lo stesso però.
Queste istruzioni nel mio caso possono essere svolte contemporaneamente:
6 5 ADD R4, R1
6 5 ADD R4, R2
7 5 IDIV R3, R1
8 6 IMUL R4, R0
Nel tua caso non si ha mai un impegno ottimale dell'ALU (supponendo di avere 4 o più unità, ovviamente).
Dopo magari scrivo l'impegno effettivo delle unità di esecuzione...
Mi è però venuto in mente un nuovo metodo: i numeri, invece che indicare quali istruzioni NON possono essere parallelizzate, indica quali istruzioni POSSONO essere parallelizzate:
1 LOAD R0, $5
1 LOAD R1, $10
1 LOAD R2, $10
1 LOAD R3, $15
1 LOAD R4, $5
2 SHR R0, R1
2 SHR R3, R2
2 ADD R4, R1
3 ADD R4, R2
3 IDIV R3, R1
4 IMUL R4, R0
4 ADD R2, R3
5 STORE R4, x
5 STORE R2, y
In questo modo, la cpu si interrompe automaticamente quando passa da un gruppo ad un altro. Se poi viene aggiunta logica supplementare, la nuova cpu, invece di bloccarsi completamente, prova a eseguire l'istruzione, ma se trova un operando in uso, si blocca da sola. Ad esempio, nel primo caso, la cpu si fermerebbe prima del shr R0, R1 aspettando la fine di load r4; nel secondo caso, la cpu vede che il load r0 e load r1 sono già stati completati, quindi esegue il shr r0,r1.
Tanto vale fare un'esecuzione OoO, non pensi ?
Inoltre le unità non impegnate nel mio caso potrebbero essere impegnate con istruzioni di altri thread, per ottenere una specie di SMT (ovviamente duplicando il register file ed altre risorse).
Il risultato non è lo stesso però.
Queste istruzioni nel mio caso possono essere svolte contemporaneamente:
6 5 ADD R4, R1
6 5 ADD R4, R2
7 5 IDIV R3, R1
8 6 IMUL R4, R0
Nel tua caso non si ha mai un impegno ottimale dell'ALU (supponendo di avere 4 o più unità, ovviamente).
Dopo magari scrivo l'impegno effettivo delle unità di esecuzione...
? Ma la 1^, 2^ e 4^ non dipendono tra di loro? Per il risultato di R4?
Inoltre le unità non impegnate nel mio caso potrebbero essere impegnate con istruzioni di altri thread, per ottenere una specie di SMT (ovviamente duplicando il register file ed altre risorse).
Non penso sia impossibile anche nel mio caso.
Io continuo ancora a pensare che i nostri due metodi siano equivalenti.
Magari tu che hai più esperienza hai già in mente una serie di casi che conosci già e cerchi di evitare, ma io al momento non vedo nessuna differenza :)
? Ma la 1^, 2^ e 4^ non dipendono tra di loro? Per il risultato di R4?
Vero, ci ho messo anche la dipendenza :D
Fossero equivalenti dovrebbero portare allo stesso risultato, giusto ?
1 1 0 LOAD R0, $5
2 2 1 LOAD R1, $10
3 3 0 LOAD R2, $10
4 4 3 LOAD R3, $15
5 5 0 LOAD R4, $5
6 5 2 SHR R0, R1
7 5 4 SHR R3, R2 <-- avevo fatto un errorino, ma non cambia niente
8 6 5 ADD R4, R1
9 6 5 ADD R4, R2
10 7 5 IDIV R3, R1
11 8 6 IMUL R4, R0
12 9 7 ADD R2, R3
13 10 8 STORE R4, x
14 10 9 STORE R2, y
L/S1 1 2 5 13
L/S2 3 4 14
ALU1 6 8 11
ALU2 7 9 12
ALU3 10
La tua:
1 0 LOAD R0, $5
2 1 LOAD R1, $10
3 2 LOAD R2, $10
4 3 LOAD R3, $15
5 4 LOAD R4, $5
BREAK
6 1 SHR R0, R1
7 3 SHR R3, R2
8 4 ADD R4, R1
BREAK
9 4 ADD R4, R2
10 3 IDIV R3, R1
BREAK
11 4 IMUL R4, R0
12 3 ADD R2, R3
BREAK
13 4 STORE R4, x
14 3 STORE R2, y
L/S1 1 3 5 13
L/S2 2 4 14
ALU1 6 9 11
ALU2 7 10 12
ALU3 8
Comunque sono sicuro che si possano fare esempi molto più convincenti ;)
Vero, ci ho messo anche la dipendenza :D
Fossero equivalenti dovrebbero portare allo stesso risultato, giusto ?
1 1 0 LOAD R0, $5
2 2 1 LOAD R1, $10
3 3 0 LOAD R2, $10
4 4 3 LOAD R3, $15
5 5 0 LOAD R4, $5
6 5 2 SHR R0, R1
7 5 4 SHR R3, R2 <-- avevo fatto un errorino, ma non cambia niente
8 6 5 ADD R4, R1
9 6 5 ADD R4, R2
10 7 5 IDIV R3, R1
11 8 6 IMUL R4, R0
12 9 7 ADD R2, R3
13 10 8 STORE R4, x
14 10 9 STORE R2, y
Solo una cosa: il primo numero è il gruppo e il secondo la dipendenza; perchè il secondo load dipende dal primo? così come il 4 con il terzo?
L/S1 1 2 5 13
L/S2 3 4 14
ALU1 6 8 11
ALU2 7 9 12
ALU3 10
L/S1 1 3 5 13
L/S2 2 4 14
ALU1 6 9 11
ALU2 7 10 12
ALU3 8
Comunque sono sicuro che si possano fare esempi molto più convincenti ;)
Accidenti, il ciclo mi ha fregato!
Ok, foooorse potresti avere ragione te :D
Pensavo anche ad una cosa: usare direttamente il campo "registro" di ogni istruzione come indicazione della dipendenza? Sarebbe molto più semplice che se non creare campi apposta, e potremo usare la mia seconda idea (quella di dare lo stesso numero per le istruzioni parallelizzabili) solo come un aiutino per la decodifica (che se vede numeri uguali, non ha dubbi e spara l'istruzione nella pipeline, altrimenti procede a fare i controlli di dipendenza):
1 LOAD R0, $5 (dip: 0)
1 LOAD R1, $10 (1)
1 LOAD R2, $10 (2)
1 LOAD R3, $15 (3)
1 LOAD R4, $5 (4)
2 SHR R0, R1 (0 e 1)
2 SHR R3, R2 (3 e 2)
2 ADD R4, R1 (4 e 1)
3 ADD R4, R2 (4 e 2)
3 IDIV R3, R1 (3 e 1)
4 IMUL R4, R0 (4 e 0)
4 ADD R2, R3 (2 e 3)
5 STORE R4, x (4)
5 STORE R2, y (2)
Anzi, penso anche che in questo caso il gruppo non serva più.
Solo una cosa: il primo numero è il gruppo e il secondo la dipendenza; perchè il secondo load dipende dal primo? così come il 4 con il terzo
Per esprimere la doppia dipendenza che l'istruzione 5 ha sia dalla 1 che dalla 2 ;)
Pensavo anche ad una cosa: usare direttamente il campo "registro" di ogni istruzione come indicazione della dipendenza? Sarebbe molto più semplice che se non creare campi apposta, e potremo usare la mia seconda idea (quella di dare lo stesso numero per le istruzioni parallelizzabili) solo come un aiutino per la decodifica (che se vede numeri uguali, non ha dubbi e spara l'istruzione nella pipeline, altrimenti procede a fare i controlli di dipendenza):
1 LOAD R0, $5 (dip: 0)
1 LOAD R1, $10 (1)
1 LOAD R2, $10 (2)
1 LOAD R3, $15 (3)
1 LOAD R4, $5 (4)
2 SHR R0, R1 (0 e 1)
2 SHR R3, R2 (3 e 2)
2 ADD R4, R1 (4 e 1)
3 ADD R4, R2 (4 e 2)
3 IDIV R3, R1 (3 e 1)
4 IMUL R4, R0 (4 e 0)
4 ADD R2, R3 (2 e 3)
5 STORE R4, x (4)
5 STORE R2, y (2)
Anzi, penso anche che in questo caso il gruppo non serva più.
Ci dovrei pensare...ma non mi sembra che possa funzionare. Ci dovrebbe essere un legame fra il gruppo e i registri, altrimenti è impossibile stabilire una sequenza.
Mettiamo di avere 3 istruzioni:
1 ADD R0, R1
2 ADD R2, R1
3 ADD R0, R2
4 STORE R0, x
La prima add è appena finita, come fa la store, pronta per essere eseguita, a sapere che deve aspettare ancora la terza add ?
Ci dovrei pensare...ma non mi sembra che possa funzionare. Ci dovrebbe essere un legame fra il gruppo e i registri, altrimenti è impossibile stabilire una sequenza.
Mettiamo di avere 3 istruzioni:
1 ADD R0, R1
2 ADD R2, R1
3 ADD R0, R2
4 STORE R0, x
La prima add è appena finita, come fa la store, pronta per essere eseguita, a sapere che deve aspettare ancora la terza add ?
Perchè l'add r0, r2 indica r0 e r2 come dipendenza "sorgente", e poi indica che R0 lo modificherà. lo store vede che c'è una istruzione che sta modificando i suoi operandi sorgente, e si blocca.
(cmq la sequenza corretta è così:
1 ADD R0, R1
1 ADD R2, R1
2 ADD R0, R2
3 STORE R0, x
Tramite il "gruppo" gli si indica subito al processore che quelle istruzioni non possono essere tranquillamente eseguite parallelamente, ma deve prima fare un controllo, o con il vecchio metodo:
1 ADD R0, R1
2 ADD R2, R1
1 ADD R0, R2
1 STORE R0, x)
Perchè l'add r0, r2 indica r0 e r2 come dipendenza "sorgente", e poi indica che R0 lo modificherà. lo store vede che c'è una istruzione che sta modificando i suoi operandi sorgente, e si blocca.
(cmq la sequenza corretta è così:
1 ADD R0, R1
1 ADD R2, R1
2 ADD R0, R2
3 STORE R0, x
Tramite il "gruppo" gli si indica subito al processore che quelle istruzioni non possono essere tranquillamente eseguite parallelamente, ma deve prima fare un controllo, o con il vecchio metodo:
1 ADD R0, R1
2 ADD R2, R1
1 ADD R0, R2
1 STORE R0, x)
Probabilmente si può fare, ma alla fine l'informazione del gruppo a cosa serve ? Sembra un normale rilevamento dei conflitti ;)
Probabilmente si può fare, ma alla fine l'informazione del gruppo a cosa serve ? Sembra un normale rilevamento dei conflitti ;)
Sapevo che la soluzione più semplice è la migliore :D
Sapevo che la soluzione più semplice è la migliore :D
Ma ripeto, è un normale rilevamento di conflitti, quello che viene fatto prima del re-order buffer in molte architetture ;)
Ma ripeto, è un normale rilevamento di conflitti, quello che viene fatto prima del re-order buffer in molte architetture ;)
Evidentemente è sufficiente questa informazione per riuscire a prevenire stalli; per questo nessuna (o poche) architetture prevedono la descrizione esplicita della dipendenza.
Bisogna vedere se ci sono casi in cui avere solo le informazioni sui registri non è sufficiente per prevenire stalli, oppure se le informazioni aggiuntive sono solo ripetizioni a quello che si può già ricavare.
Evidentemente è sufficiente questa informazione per riuscire a prevenire stalli; per questo nessuna (o poche) architetture prevedono la descrizione esplicita della dipendenza.
Bisogna vedere se ci sono casi in cui avere solo le informazioni sui registri non è sufficiente per prevenire stalli, oppure se le informazioni aggiuntive sono solo ripetizioni a quello che si può già ricavare.
Lo scopo non è solo prevenire stalli, ma avere la massima parallelizzazione mantenendo un design semplice in modo da avere un critical path il più possibile corto.
Insieme ad una prevenzione come quella sopra si attua quasi sempre un politica di code per le istruzioni in attesa sulle varie unità di calcolo e un riordino delle stesse. Queste cose sono quelle appunto che io cercavo di evitare per semplificare lo stage prima dell'esecuzione.
Mi sono ricordato di una domanda che volevo fare a Cesare:
Tu non hai previsto istruzioni di I/O; preferisci fare tutto sempre memory-mapped ? Secondo me tenere separati la memoria centrale dall'I/O è una cosa che facilita molto, sia il progettista del sistema, sia il sistema operativo (tralasciando ovviamente quelle periferiche tipo la scheda video)
cdimauro
11-07-2010, 22:30
Se cominci con le eccezioni, dovrai farne anche per altri dispositivi (ad esempio per le schede audio, che gestiscono anch'esse buffer in memoria per i sample).
Se il vecchio sistema delle istruzioni è stato abbandonato, il motivo è semplice: non è flessibile. Devi cominciare a fare delle eccezioni e, soprattutto, non hai un controllo più fine sul modo in cui puoi utilizzare zone di memoria come un framebuffer, appunto.
Meglio mappare direttamente in memoria tutto l'I/O, e delegare all'MMU la definizione degli attributi che la caratterizza.
P.S. Sono stato assente per un po' a causa di impegni vari. Appena posso mi rimetto in carreggiata, analizzando anche la proposta di Tommo. :fagiano:
Se il vecchio sistema delle istruzioni è stato abbandonato, il motivo è semplice: non è flessibile. Devi cominciare a fare delle eccezioni e, soprattutto, non hai un controllo più fine sul modo in cui puoi utilizzare zone di memoria come un framebuffer, appunto.
Non ho capito cosa intendi per "vecchio sistema delle istruzioni": parli delle istruzioni in/out del processore, o del modo in cui configuri le periferiche? Idem per il "controllo fine": è perchè non puoi usare tutti i modi di indirizzamento?
Meglio mappare direttamente in memoria tutto l'I/O, e delegare all'MMU la definizione degli attributi che la caratterizza.
Invece penso che l' I/O isolato porti molti vantaggi:
- Non serve decodificare gli indirizzi anche quando la cpu vuole accedere alla RAM: se è attivo il segnale MREQ allora il circuito esterno deve solo inoltrare la richiesta alla ram, anzi, non serve neanche che passi per questo circuito, a vantaggio della velocità;
- Si possono usare bus dati e indirizzi separati, con il bus i/o dotato di una coda per l'output in modo che al processore sembri di inviare dati in Out tanto velocemente come in RAM.
- Non devi regolare la cache in modo che non inserisca i dati provenienti dall'area di I/O
La questione delle eccezioni è facilmente sormontabile: tralasciando il modo comune di fare le istruzioni in e out, cioè con limitate possibilità e indirizzamenti, ma permettendo di usare tutti i modi o un set molto ampio, si potrebbe direttamente mettere tutto il framebuffer nello spazio di I/O. Questo a vantaggio della progettazione del sistema (come detto prima, gli accessi alla ram non devono essere controllati ulteriormente al di fuori del processore), e l'ortogonalità del sistema: ram da una parte, periferiche dall'altra.
Con le schede video che oggi montano 1 GB di RAM, secondo me ha poco senso il MM I/O.
Su un sistema operativo a 32 bit, 1/4 dello spazio di indirizzamento totale è riservato per il MM I/O della scheda video, un altro quarto per il kernel ed i device driver. Diventa un po' ridicolo...
Se si pensa ad una cpu a basso consumo, e quindi collegata a periferiche più "contenute", il memory mapped I/O mantiene un certo appeal...
cdimauro
12-07-2010, 13:49
Non ho capito cosa intendi per "vecchio sistema delle istruzioni": parli delle istruzioni in/out del processore,
Sì.
o del modo in cui configuri le periferiche?
Questo si può risolvere utilizzando meglio il cervello. :D
Idem per il "controllo fine": è perchè non puoi usare tutti i modi di indirizzamento?
Sì.
Invece penso che l' I/O isolato porti molti vantaggi:
- Non serve decodificare gli indirizzi anche quando la cpu vuole accedere alla RAM: se è attivo il segnale MREQ allora il circuito esterno deve solo inoltrare la richiesta alla ram, anzi, non serve neanche che passi per questo circuito, a vantaggio della velocità;
Gli indirizzi devi decodificarli comunque, perché non esiste una mappa di memoria unica e continua.
- Si possono usare bus dati e indirizzi separati, con il bus i/o dotato di una coda per l'output in modo che al processore sembri di inviare dati in Out tanto velocemente come in RAM.
Bus separati aumentano la complessità delle schede madri. Inoltre non li usi spesso come fai con la RAM.
- Non devi regolare la cache in modo che non inserisca i dati provenienti dall'area di I/O
A questo ci pensa l'MMU, che permette un controllo più fine appunto: può marcare certe aree come cacheabili o no, in lettura e/o scrittura, e le politiche di scrittura.
La questione delle eccezioni è facilmente sormontabile: tralasciando il modo comune di fare le istruzioni in e out, cioè con limitate possibilità e indirizzamenti, ma permettendo di usare tutti i modi o un set molto ampio, si potrebbe direttamente mettere tutto il framebuffer nello spazio di I/O. Questo a vantaggio della progettazione del sistema (come detto prima, gli accessi alla ram non devono essere controllati ulteriormente al di fuori del processore), e l'ortogonalità del sistema: ram da una parte, periferiche dall'altra.
Questo è vero. Avevo anche pensato di allocare l'ultimo slot delle modalità d'indirizzamento per indicare l'I/O (con un indirizzo di I/O a seguire), ma non ne vale la pena proprio perché non posso definire un controllo fine sul come sfruttare lo spazio di I/O.
Alla fine, se devo emulare il funzionamento dell'MMU con la RAM, tanto vale mappare anche l'I/O in memoria.
Con le schede video che oggi montano 1 GB di RAM, secondo me ha poco senso il MM I/O.
Su un sistema operativo a 32 bit, 1/4 dello spazio di indirizzamento totale è riservato per il MM I/O della scheda video, un altro quarto per il kernel ed i device driver. Diventa un po' ridicolo...
Beh, per me è ridicolo pensare a un s.o. a 32 bit nel 2010, e con quasi tutti i PC venduti con CPU a 64 bit ormai. :D
Se si pensa ad una cpu a basso consumo, e quindi collegata a periferiche più "contenute", il memory mapped I/O mantiene un certo appeal...
A mio avviso aiuta anche a semplificare il layout della scheda madre e la sezione di decodifica degli indirizzi che rimane unica (anche lo spazio di I/O avrebbe bisogno di essere partizionato).
Oltre al fatto che il programmatore si trova con un sistema omogeneo.
Gli indirizzi devi decodificarli comunque, perché non esiste una mappa di memoria unica e continua.
Se il progettista è tanto idiota da mettere un gb quì, un gb là lasciando spazi vuoti tra uno e l'altro, allora non ci possiamo fare niente...
A questo ci pensa l'MMU, che permette un controllo più fine appunto: può marcare certe aree come cacheabili o no, in lettura e/o scrittura, e le politiche di scrittura.
...
Questo è vero. Avevo anche pensato di allocare l'ultimo slot delle modalità d'indirizzamento per indicare l'I/O (con un indirizzo di I/O a seguire), ma non ne vale la pena proprio perché non posso definire un controllo fine sul come sfruttare lo spazio di I/O.
...
Alla fine, se devo emulare il funzionamento dell'MMU con la RAM, tanto vale mappare anche l'I/O in memoria.
Quindi tu praticamente vorresti poter usare la paginazione anche sulle aree di I/O... Non sembra una brutta idea, ma bisognerebbe provvedere secondo me a pagine più piccole, perchè non tutti i dispositivi usano 4 KiB di porte. In questo caso, direi che una granularità di 256 byte sarebbe già più accettabile.
Il meglio sarebbe avere aree di lunghezza variabile, come dei segmenti, dove si specifica l'indirizzo di I/O di partenza, la lunghezza in byte dell'area, e la posizione virtuale a cui mapparlo. Questa traduzione potrebbe essere più complessa di quella della pagine della ram, quindi differenziare i due tipi di accesso sarebbe utile in modo di evitare molti calcoli anche se non serve.
Bus separati aumentano la complessità delle schede madri. Inoltre non li usi spesso come fai con la RAM.
A mio avviso aiuta anche a semplificare il layout della scheda madre e la sezione di decodifica degli indirizzi che rimane unica (anche lo spazio di I/O avrebbe bisogno di essere partizionato).
Oltre al fatto che il programmatore si trova con un sistema omogeneo.
Ok, senza usare bus separati, ma almeno 1 filo in più per indicare l'accesso all'I/O non è così dispendioso. La mia idea era quella di collegare il processore alla ram direttamente, senza nessun intermezzo, neanche del northbridge. La ram viene controllata dal segnale MREQ del processore, e rimane sempre "in ascolto" di default, così se il processore inizia un trasferimento, la ram è già pronta a riceverlo. Se invece si inizia un trasferimento di I/O, ci si può permettere di disattivare la ram, attivare il circuito di decodifica che selezionerà la periferica. Tutto ciò per dire che, avendo due segnali differenziati, la circuiteria esterna può essere semplificata e/o ottimizzata per gestire i due differenti tipi di accesso, che per forza sono diversi, sia nelle modalità sia nelle tempistiche.
cdimauro
16-07-2010, 04:59
Se il progettista è tanto idiota da mettere un gb quì, un gb là lasciando spazi vuoti tra uno e l'altro, allora non ci possiamo fare niente...
Esatto. Però considera che gli spazi vuoti vengono lasciati a volte per eventuali estensioni future.
Quindi tu praticamente vorresti poter usare la paginazione anche sulle aree di I/O... Non sembra una brutta idea, ma bisognerebbe provvedere secondo me a pagine più piccole, perchè non tutti i dispositivi usano 4 KiB di porte. In questo caso, direi che una granularità di 256 byte sarebbe già più accettabile.
Il meglio sarebbe avere aree di lunghezza variabile, come dei segmenti, dove si specifica l'indirizzo di I/O di partenza, la lunghezza in byte dell'area, e la posizione virtuale a cui mapparlo. Questa traduzione potrebbe essere più complessa di quella della pagine della ram, quindi differenziare i due tipi di accesso sarebbe utile in modo di evitare molti calcoli anche se non serve.
La mia idea è che il codice di autoconfigurazione del kernel dovrebbe mettere sulla stessa pagina quanti più spazi di I/O simili.
Quindi supponendo di avere pagine da 4KB e, ad esempio, i seguenti tipi di spazi di I/O:
- non cacheabile;
- cacheabile, no write-through;
- cacheabile, write-through;
dovrebbe creare soltanto 3 pagine da 4KB con le suddette caratteristiche, provvedendo poi a raggruppare tutti gli spazi di I/O di quel tipo, mappandoli opportunamente all'interno di ogni apposita pagina.
Ok, senza usare bus separati, ma almeno 1 filo in più per indicare l'accesso all'I/O non è così dispendioso.
Sì, una sola linea non crea certo problemi.
La mia idea era quella di collegare il processore alla ram direttamente, senza nessun intermezzo, neanche del northbridge. La ram viene controllata dal segnale MREQ del processore, e rimane sempre "in ascolto" di default, così se il processore inizia un trasferimento, la ram è già pronta a riceverlo.
Tipo Athlon64, insomma. :D
Se invece si inizia un trasferimento di I/O, ci si può permettere di disattivare la ram, attivare il circuito di decodifica che selezionerà la periferica. Tutto ciò per dire che, avendo due segnali differenziati, la circuiteria esterna può essere semplificata e/o ottimizzata per gestire i due differenti tipi di accesso, che per forza sono diversi, sia nelle modalità sia nelle tempistiche.
Nulla vieta di riservare un bit nelle pagine dell'MMU, anziché richiedere espressamente l'introduzione di istruzioni di I/O, che trovo troppo poco flessibili. ;)
Accidenti, mi sono completamente dimenticato di rispondere al tuo post, scusa :)
Esatto. Però considera che gli spazi vuoti vengono lasciati a volte per eventuali estensioni future.
Beh, sarebbe meglio lasciare gli spazi vuoti alla fine della memoria attuale, non a metà.
La mia idea è che il codice di autoconfigurazione del kernel dovrebbe mettere sulla stessa pagina quanti più spazi di I/O simili.
Io stavo pensando a robe tipo gli indirizzi fissi e immutabili del PC IBM, l'Amiga ti abitua troppo bene :D
Quindi supponendo di avere pagine da 4KB e, ad esempio, i seguenti tipi di spazi di I/O:
- non cacheabile;
- cacheabile, no write-through;
- cacheabile, write-through;
dovrebbe creare soltanto 3 pagine da 4KB con le suddette caratteristiche, provvedendo poi a raggruppare tutti gli spazi di I/O di quel tipo, mappandoli opportunamente all'interno di ogni apposita pagina.
Ok, però io pensavo alle pagine più piccole perchè così potevo dare ad ogni modulo, ognuno in uno spazio virtuale diverso, solo gli indirizzi di cui ha bisogno. Per questo pensavo alle pagine più piccole, altrimenti bisogna cmq dare 4KiB di porte ad ogni periferica; cmq avendo 16 EiB di indirizzamento, non dovrebbe essere un problema :D
Sì, una sola linea non crea certo problemi.
Ho pensato ad un'altro modo per ottenere l'I/O "isolato" simulandolo con l'mmu: usiamo il bit 63 del bus indirizzi come A63/IORQ, e dividiamo lo spazio di indirizzi in due blocchi (da 8 EiB l'uno), dove il progettista del sistema può decidere di mettere memoria, o farne uno spazio di I/O.
Stavo riflettendo sulla dimensione delle pagine di memoria: quasi tutte le maggiori architetture supportano pagine da 4KiB (l'Alpha ha pagine da 8KiB), però, penso siano veramente piccole per gli standard moderni. Le pagine da 4MiB dell'x86 invece penso siano troppo grandi per essere usate esclusivamente.
Quindi, quale può essere una misura "ottimale" per delle pagine?
Io ho pensato a 64KiB, perchè è una misura "tonda" (16 bit, è anche multiplo di byte), e non è troppo grande da sprecare tanto spazio (gli eseguibili che ci sono nella mia macchina Ubuntu sono la maggior parte inferiori a 100KiB). Ci sarebbero numerosi vantaggi ad usare questa dimensione: Le tabelle di paginazione sarebbero più piccole (16 volte + piccole rispetto alle 4KiB), ci sarebbero meno miss nel TLB, e si velocizzerebbe il task-switch.
cdimauro
10-12-2010, 20:20
Ho il task manager di Windows XP a 32 bit qui davanti, e ho ordinato l'elenco dei processi per la memoria utilizzata.
Il risultato è che ci sono 12 processi che occupano meno di 1MB, 10 occupano meno di 2MB, 7 meno di 4MB, 12 meno di 16MB, 1 da 31MB, 1 da 57MB, 1 da 135MB, e infine l'ultimo da 278MB.
A conti fatti, anche se utilizzassimo pagine da 4MB, ci sarebbe uno spreco sicuramente, ma non così elevato.
Per contro, i problemi di TLB miss sarebbero praticamente nulli, non ci sarebbero alberi delle pagine da attraversare (c'è un solo livello, cioè una lista), i context switch sarebbero più veloci, il working set del processore quasi sempre a disposizione, gli eventuali swap su disco più veloci (scrivi 4MB alla volta, anziché tante, numerose, scritture di 4KB).
E stiamo parlando di un s.o. a 32 bit (con 2GB di memoria installata).
Con un'architettura a 64 bit direi che l'uso di pagine di grossa dimensione sia altamente consigliabile, in modo da ridurre il numero di livelli dell'albero delle pagine e, quindi, il costo dell'attraversamento completo.
Ho il task manager di Windows XP a 32 bit qui davanti, e ho ordinato l'elenco dei processi per la memoria utilizzata.
Il risultato è che ci sono 12 processi che occupano meno di 1MB, 10 occupano meno di 2MB, 7 meno di 4MB, 12 meno di 16MB, 1 da 31MB, 1 da 57MB, 1 da 135MB, e infine l'ultimo da 278MB.
A conti fatti, anche se utilizzassimo pagine da 4MB, ci sarebbe uno spreco sicuramente, ma non così elevato.
Bisogna però anche tener conto che quella memoria potrebbe essere divisa in più sezioni, che hanno bisogno di flag diversi. Ad esempio, quei processi da te elencati potrebbero avere un codice di pochi KiB, e avere solo memoria allocata dinamicamente. Quindi, se abbiamo un programma da 512KiB, sprechiamo 3.4 MiB (che dobbiamo cmq allocare per fare la pagina intera). Ho scelto 64KiB perchè mi sembrava una scelta adatta, e siamo cmq a 1/16 delle pagine di prima, che è già un bel vantaggio.
Anche pagine da 128KiB non sarebbero male, ma non penso sia bene andare oltre i 256KiB per pagina.
ingframin
11-12-2010, 15:38
Io mi permetto di fare una proposta in netta controtendenza:
- opcode a 8 bit con 1,2 o 3 operandi
- instruction register a 128 bit in modo da caricare 4 istruzioni per volta
- bus istruzioni e bus indirizzi istruzioni separati da bus dati e bus indirizzi dati, 4 bus in tutto
- register pool da 64 registri a 64 bit
- floating point unit separata dalla ALU per interi
Ancora devo definire alcune cose tipo DMA, cache, MMU, ecc... ma ci sto lavorando :cool:
Per ora vi posto la mia prima proposta di ISA:
hex Istruzione Operando1 Operando2 Operando3 Descrizione
00 halt - - - Ferma l'esecuzione del programma
00 fetch - - - Carica un'istruzione dalla memoria
01 reset - - - Riporta a 0 il program counter
02 load M R - Carica una locazione di memoria all'indirizzo M in R
03 load @R1 R2 - Carica una locazione di memoria all'indirizzo contenuto in R1 in R2
04 load +/-k R - Carica una locazione di memoria a k posizioni di distanza dalla locazione attuale in R
05 store R M - Copia il contenuto di R nella locazione di memoria indirizzata da M
06 store R1 @R2 - Copia il contenuto di R nella locazione di memoria il cui indirizzo è contenuto in R2
07 store R +/-k - Copia il contenuto di R nella locazione di memoria a k posizioni di distanza da quella attuale
08 push R - - Copia il contenuto di R in cima allo stack
09 pop R - - Copia la cima dello stack in R
0A clstack - - - Svuota lo stack resettandone i puntatori
0B svregstat M - - Copia il contenuto dei registri in memoria a partire dalla locazione M
0C ldregstat M - - Ripristina il contenuto dei registri a partire dalla locazione M
0D move R1 R2 - copia il contenuto di R1 in R2
0E memcpy M1 M2 k copia k posizioni di memoria a partire da M1 nell'area che parte da M2. Se M2-M1 <k scatena un'interrupt
0F memcpy @R1 @R2 k copia k posizioni di memoria a partire da @R1 nell'area che parte da @R2. Se R2-R1 <k scatena un'interrupt
10 call M - - Copia il contenuto dei registri nello stack e scrive M nel program counter
11 return - - - Riporta il contenuto dei registri dallo stack al loro posto e mette i valori di ritorno in cima allo stack
12 retint - - - Riporta il contenuto dei registri dallo stack al loro posto quando si ritorna dalla routine di interrupt
13 jmp M - - Scrive M nel program counter
14 jmp R - - Scrive il contenuto di R nel program counter
15 je R1 R2 M Scrive M nel program counter se R1 = R2
16 jle R1 R2 M Scrive M nel program counter se R1<=R2
17 jge R1 R2 M Scrive M nel program counter se R1>=R2
18 jg R1 R2 M Scrive M nel program counter se R1 > R2
19 jl R1 R2 M Scrive M nel program counter se R1 < R2
1A jne R1 R2 M Scrive M nel program counter se R1 != R2
1B add64 R1 R2 - Somma a 64 bit: R1+R2 e mette il risultato nel registro accumulatore
1C sub64 R1 R2 - Sottrazione a 32bit: R1-R2 e mette il risultato nel registro accumulatore
1D fadd R1 R2 - Somma floating point a 64 bit
1E fsub R1 R2 - Sottrazione floating point a64 bit
1F mul R1 R2 - Moltiplicazione a 64 bit: R1 x R2 e mette il risultato nel registro accumulatore
20 div R1 R2 - Divisione intera a 64 bit: R1/R2
21 fmul R1 R2 - Moltiplicazione a 64 bit floating point
22 fdiv R1 R2 - Divisione intera a 64 bit floating point: R1/R2
23 logic R1 R2 op Operazione logica bit a bit:0 and, 1 or, 2 xor, 3 nand, 4 nor, 5 xnor. Il risultato va nell'accumulatore
24 shift R n d/s Shift sinistro o destro del numero contenuto in R
25 nop - - - Salta un ciclo di clock
Mi permetto anche di consiglia re l'acquisto di un libro :-)
"Reti logiche e calcolatori" di Fabrizio Luccio e Linda Pagli, ed. Bollati Boringhieri
Io mi permetto di fare una proposta in netta controtendenza:
E' più o meno lo scopo del thread :asd:
- opcode a 8 bit con 1,2 o 3 operandi
- instruction register a 128 bit in modo da caricare 4 istruzioni per volta
- bus istruzioni e bus indirizzi istruzioni separati da bus dati e bus indirizzi dati, 4 bus in tutto
- register pool da 64 registri a 64 bit
- floating point unit separata dalla ALU per interi
Ancora devo definire alcune cose tipo DMA, cache, MMU, ecc... ma ci sto lavorando :cool:
Cosa intendi unità floating separata dall'ALU? In tutti i processori è già così, e ha anche registri separati; nel motorola 88000, invece, i registri erano comuni tra l'ALU e l'unità floating point.
Per ora vi posto la mia prima proposta di ISA:
hex Istruzione Op1 Op2 Op3 Descrizione
00 halt - - - Ferma l'esecuzione del programma
00 fetch - - - Carica un'istruzione dalla memoria
01 reset - - - Riporta a 0 il program counter
02 load M R - Carica una locazione di memoria all'indirizzo M in R
03 load @R1 R2 - Carica una locazione di memoria all'indirizzo contenuto in R1 in R2
04 load +/-k R - Carica una locazione di memoria a k posizioni di distanza dalla locazione attuale in R
05 store R M - Copia il contenuto di R nella locazione di memoria indirizzata da M
06 store R1 @R2 - Copia il contenuto di R nella locazione di memoria il cui indirizzo è contenuto in R2
07 store R +/-k - Copia il contenuto di R nella locazione di memoria a k posizioni di distanza da quella attuale
08 push R - - Copia il contenuto di R in cima allo stack
09 pop R - - Copia la cima dello stack in R
0A clstack - - - Svuota lo stack resettandone i puntatori
0B svregstat M - - Copia il contenuto dei registri in memoria a partire dalla locazione M
0C ldregstat M - - Ripristina il contenuto dei registri a partire dalla locazione M
0D move R1 R2 - copia il contenuto di R1 in R2
0E memcpy M1 M2 k copia k posizioni di memoria a partire da M1 nell'area che parte da M2. Se M2-M1 <k scatena un'interrupt
0F memcpy @R1 @R2 k copia k posizioni di memoria a partire da @R1 nell'area che parte da @R2. Se R2-R1 <k scatena un'interrupt
10 call M - - Copia il contenuto dei registri nello stack e scrive M nel program counter
11 return - - - Riporta il contenuto dei registri dallo stack al loro posto e mette i valori di ritorno in cima allo stack
12 retint - - - Riporta il contenuto dei registri dallo stack al loro posto quando si ritorna dalla routine di interrupt
13 jmp M - - Scrive M nel program counter
14 jmp R - - Scrive il contenuto di R nel program counter
15 je R1 R2 M Scrive M nel program counter se R1 = R2
16 jle R1 R2 M Scrive M nel program counter se R1<=R2
17 jge R1 R2 M Scrive M nel program counter se R1>=R2
18 jg R1 R2 M Scrive M nel program counter se R1 > R2
19 jl R1 R2 M Scrive M nel program counter se R1 < R2
1A jne R1 R2 M Scrive M nel program counter se R1 != R2
1B add64 R1 R2 - Somma a 64 bit: R1+R2 e mette il risultato nel registro accumulatore
1C sub64 R1 R2 - Sottrazione a 32bit: R1-R2 e mette il risultato nel registro accumulatore
1D fadd R1 R2 - Somma floating point a 64 bit
1E fsub R1 R2 - Sottrazione floating point a64 bit
1F mul R1 R2 - Moltiplicazione a 64 bit: R1 x R2 e mette il risultato nel registro accumulatore
20 div R1 R2 - Divisione intera a 64 bit: R1/R2
21 fmul R1 R2 - Moltiplicazione a 64 bit floating point
22 fdiv R1 R2 - Divisione intera a 64 bit floating point: R1/R2
23 logic R1 R2 op Operazione logica bit a bit:0 and, 1 or, 2 xor, 3 nand, 4 nor, 5 xnor. Il risultato va nell'accumulatore
24 shift R n d/s Shift sinistro o destro del numero contenuto in R
25 nop - - - Salta un ciclo di clock
Ho qualche piccola domanda...
1- Fetch? Non ho capito bene il suo funzionamento; in più gli hai assegnato lo stesso opcode dell'halt, è una cosa voluta o un errrore?
2- Istr. 04: la "posizione attuale" è il program counter?
3- Il "registro accumulatore" è separato dai 64? Come lo si indirizza?
Mi permetto anche di consigliare l'acquisto di un libro :-)
"Reti logiche e calcolatori" di Fabrizio Luccio e Linda Pagli, ed. Bollati Boringhieri
Grazie del suggerimento :)
Io ho "Struttura e progetto dei calcolatori" di David A. Patterson e John L. Hennessy, e lo trovo veramente ben fatto.
ingframin
11-12-2010, 16:28
Cosa intendi unità floating separata dall'ALU? In tutti i processori è già così, e ha anche registri separati; nel motorola 88000, invece, i registri erano comuni tra l'ALU e l'unità floating point.
Ok, intendevo usare sempre gli stessi registri ma con una FPU che può lavorare in parallelo
rispetto all'ALU per interi.
Ho qualche piccola domanda...
1- Fetch? Non ho capito bene il suo funzionamento; in più gli hai assegnato lo stesso opcode dell'halt, è una cosa voluta o un errrore?
2- Istr. 04: la "posizione attuale" è il program counter?
3- Il "registro accumulatore" è separato dai 64? Come lo si indirizza?
1- halt e fetch hanno lo stesso opcode perché in realtà possono sfruttare lo stesso microcodice. Ricordo questa cosa dal corso di calcolatori in cui avevo anche un esempio,
solo che non riesco a ritrovare gli appunti. Appena li ritrovo posto la soluzione che ci disse il prof. In pratica la fetch carica una singola istruzione dalla RAM, mentre halt dà lo stop al program counter. Il professore ci disse quindi che poteva valere la pena assegnare lo stesso opcode e aggiungere un bit di stop da qualche parte tirato su
all'accensione e tirato a giù quando si chiama la halt.
2- mi riferisco al contenuto attuale del memory address register. Si prende il valore ci si somma k e si rimette nel MAR per poi leggere nell'MBR il valore alla locazione MAR vecchio più k ovvero k locazioni più in là di dove sta ora.
In questo modo si può indirizzare molta ram anche senza indicare l'indirizzo per esteso e si risparmia memoria tra le istruzioni. Altrimenti bisognerebbe fare molte letture per leggere opcode, operandi e tutti i byte di indirizzo. Con l'indirizzamento relativo invece si limita il numero di byte da leggere. Ovviamente se devi fare un salto molto grande conviene passare direttamente l'indirizzo di memoria per intero, ma per leggere tipo 255 locazioni più in là basta un solo byte per comporre l'indirizzo intero.
3- Pensavo di usare un registro separato con un nome particolare oppure il registro 0 del register pool. Tipo nei microcontrollori Atmel AVR si usano i primi 2 registri del pool in modo da poter fare operazioni a 16 bit. Sinceramente è una delle cose a cui non ho ancora pensato :-)
:sofico:
Inoltre alla mia proposta mancano mille altre cose tipo la gestione degli interrupt o di cpu parallele. Ci devo pensare, mano mano che vado avanti posto le mie follie e cerco di stare dietro al discorso.
Io ho "Struttura e progetto dei calcolatori" di David A. Patterson e John L. Hennessy, e lo trovo veramente ben fatto.
Concordo al 100%, infatti sarà uno dei miei prossimi acquisti.
Io, oltre a quello già citato, ho quello di Tanenbaum ma non mi ha esaltato. Per carità è piacevolissimo da leggere e molto chiaro però manca di taglio pratico secondo me.
Un'altra proposta che lancio è quella di provare a fare il circuito digitale, magari descrivendolo in Verilog o in VHDL e simulandolo con un simulatore tipo icarus o freehdl.
http://www.icarus.com/eda/verilog/
http://www.freehdl.seul.org/
1- halt e fetch hanno lo stesso opcode perché in realtà possono sfruttare lo stesso microcodice. Ricordo questa cosa dal corso di calcolatori in cui avevo anche un esempio,
solo che non riesco a ritrovare gli appunti. Appena li ritrovo posto la soluzione che ci disse il prof. In pratica la fetch carica una singola istruzione dalla RAM, mentre halt dà lo stop al program counter. Il professore ci disse quindi che poteva valere la pena assegnare lo stesso opcode e aggiungere un bit di stop da qualche parte tirato su
all'accensione e tirato a giù quando si chiama la halt.
Non ho però ancora capito a cosa serve effettivamente, un'istruzione che dice "preleva un'altra istruzione dalla memoria" è praticamente un nop.
2- mi riferisco al contenuto attuale del memory address register. Si prende il valore ci si somma k e si rimette nel MAR per poi leggere nell'MBR il valore alla locazione MAR vecchio più k ovvero k locazioni più in là di dove sta ora.
Però il MAR è un registro nascosto, e come tale il programmatore non lo può controllare. Quindi l'offset verrà applicato sempre all'ultimo valore che contiene il MAR: in questo caso, penso possa essere l'indirizzo della costate K stessa, che è stata appena letta nell'MDR (memory Data register), quindi @ program counter, e se non proprio il PC, qualche byte lì intorno.
Inoltre alla mia proposta mancano mille altre cose tipo la gestione degli interrupt o di cpu parallele. Ci devo pensare, mano mano che vado avanti posto le mie follie e cerco di stare dietro al discorso.
Non ti preoccupare, anche quella di Cesare e la mia mancano ancora in molte parti ;)
Un'altra proposta che lancio è quella di provare a fare il circuito digitale, magari descrivendolo in Verilog o in VHDL e simulandolo con un simulatore tipo icarus o freehdl.
http://www.icarus.com/eda/verilog/
http://www.freehdl.seul.org/
Aggiungo, per chi non conoscesse il VHDL, un simulatore grafico e carino scritto in Java, che ha tutto tranne il poter usare il VHDL (o Verilog o qualsiasi altro linguaggio), che secondo me lo farebbe diventare proprio il massimo.:
http://tams-www.informatik.uni-hamburg.de/applets/hades/webdemos/index.html
ingframin
11-12-2010, 18:40
Non ho però ancora capito a cosa serve effettivamente, un'istruzione che dice "preleva un'altra istruzione dalla memoria" è praticamente un nop.
No, non è un nop :)
in pratica dipende dall'implementazione. Sul libro di tanenbaum ad esempio ogni istruzione aveva in coda al microcodice il pezzettino che richiamava l'istruzione successiva, in quella del mio prof di calcolatori invece c'era un'istruzione fittizia che non veniva mai richiamata nell'assembler ma richiamata in automatico ad ogni ciclo del program counter... sostanzialmente è una sottigliezza, alla fine sarà una logica cablata, non microprogrammata.
Però il MAR è un registro nascosto, e come tale il programmatore non lo può controllare. Quindi l'offset verrà applicato sempre all'ultimo valore che contiene il MAR: in questo caso, penso possa essere l'indirizzo della costate K stessa, che è stata appena letta nell'MDR (memory Data register), quindi @ program counter, e se non proprio il PC, qualche byte lì intorno.
In pratica l'ho intesa così:
leggo un dato da una locazione di memoria e poi ne leggo un'altro 10 posizioni più in la:
load 0x09857267, R0 #leggo alla posizione 0x09857267 e metto il risultato in R0
load +10, R1 #leggo in 0x09857277 e metto il risultato in R1
Penso che sia più chiaro cosa intendevo così :)
Il programmatore non vede nulla.
Già che oramai siamo in tema di libri e link ne butto giù un altro un po' più complesso
ma molto molto valido per chi si cimenta nella progettazione digitale:
http://bwrc.eecs.berkeley.edu/icbook/
Mi è costato un occhio :cry: ma ne è valsa la pena, ho passato l'esame con 27 :)
Un'altra proposta che lancio è quella di provare a fare il circuito digitale, magari descrivendolo in Verilog o in VHDL e simulandolo con un simulatore tipo icarus o freehdl.
http://www.icarus.com/eda/verilog/
http://www.freehdl.seul.org/
Ma l'ISE della Xilinx è gratuito, io l'ho usato per diversi progetti (anche relativamente grossi) e l'ho sempre preso dal loro sito senza pagare una lira! oltretutto l'ultima versione mi è piaciuta un sacco, il simulatore mi sembra abbastanza decente per quello che l'ho usato :P
Comunque su una spartan 3E, che costa credo 2 soldi, ci ho fatto entrare senza problemi 50k gate...poi vabbè, se volete farlo a 64 bit probabilmente non basterà neanche una spartan 6 :stordita:
Ad ogni modo, parere personale, per un progetto del genere consiglierei il SystemC...perchè a quanto ho capito i programmatori la fanno da padrone quì.
No, non è un nop :)
in pratica dipende dall'implementazione. Sul libro di tanenbaum ad esempio ogni istruzione aveva in coda al microcodice il pezzettino che richiamava l'istruzione successiva, in quella del mio prof di calcolatori invece c'era un'istruzione fittizia che non veniva mai richiamata nell'assembler ma richiamata in automatico ad ogni ciclo del program counter... sostanzialmente è una sottigliezza, alla fine sarà una logica cablata, non microprogrammata.
Ah ok, quindi è una cosa più "interna" al funzionamento, per questo che non capivo, pensavo fosse solo il set più "esterno" di istruzioni.
In pratica l'ho intesa così:
leggo un dato da una locazione di memoria e poi ne leggo un'altro 10 posizioni più in la:
load 0x09857267, R0 #leggo alla posizione 0x09857267 e metto il risultato in R0
load +10, R1 #leggo in 0x09857277 e metto il risultato in R1
Penso che sia più chiaro cosa intendevo così :)
Il programmatore non vede nulla.
Ok, non proprio il MAR, ma un registro un poco + sopra, sempre nascosto cmq.
Potrebbe essere un'idea, però io la vedrei più in un'architettura con pochi registri; con 64 registri (se si ha bisogno di molti accessi), si può avere un puntatore all'inizio della zona dati e posizionare le variabili di uso frequente verso l'inizio, così basta un offset a 8 bit, e quelle più usate più in là, così si usa un offset a 16 o 32 bit.
Aggiungerei quindi anche un "load @Rx+k, Ry" al tuo set, indipendentemente dalle scelte sopra.
ingframin
12-12-2010, 11:51
Ok, non proprio il MAR, ma un registro un poco + sopra, sempre nascosto cmq.
Potrebbe essere un'idea, però io la vedrei più in un'architettura con pochi registri; con 64 registri (se si ha bisogno di molti accessi), si può avere un puntatore all'inizio della zona dati e posizionare le variabili di uso frequente verso l'inizio, così basta un offset a 8 bit, e quelle più usate più in là, così si usa un offset a 16 o 32 bit.
Aggiungerei quindi anche un "load @Rx+k, Ry" al tuo set, indipendentemente dalle scelte sopra.
Si, ok, concordo in pieno :)
Ma l'ISE della Xilinx è gratuito, io l'ho usato per diversi progetti (anche relativamente grossi) e l'ho sempre preso dal loro sito senza pagare una lira! oltretutto l'ultima versione mi è piaciuta un sacco, il simulatore mi sembra abbastanza decente per quello che l'ho usato :P
Comunque su una spartan 3E, che costa credo 2 soldi, ci ho fatto entrare senza problemi 50k gate...poi vabbè, se volete farlo a 64 bit probabilmente non basterà neanche una spartan 6
Ad ogni modo, parere personale, per un progetto del genere consiglierei il SystemC...perchè a quanto ho capito i programmatori la fanno da padrone quì.
Dunque... Io preferirei usare Verilog o VHDL perché sono molto più richiesti nel mondo del lavoro e poter mettere a cv un progettino anche amatoriale fatto in uno dei due può fare la differenza. Per quanto riguarda l'FPGA... Forse riesco a procurarmi un campione di spartan 3 AN a lavoro che ha già la flash interna e 200k gate.
Un altro tentativo che posso fare è chiedere ai miei colleghi del reparto asic/fpga se mi fanno provare sulla demoboard della Stratix 4 che hanno preso da poco ma questo dipende dai loro impegni / buon cuore :fagiano:
La spartan 3 AN comunque costa una trentina di €, non tanto, e ha il package a 144 pin TQFP che si riesce a saldare con un po'di cura.
Come adattatore ho trovato questo:
http://www.pcb-proto.com/index.php?action=stdprod&page=prodlist&idcat=1
e il resto del circuito poi Dio ci pensa :)
Sasa83 ma sei un elettronico anche tu?
ingframin
12-12-2010, 11:58
Dunque... Io preferirei usare Verilog o VHDL perché sono molto più richiesti nel mondo del lavoro e poter mettere a cv un progettino anche amatoriale fatto in uno dei due può fare la differenza. Per quanto riguarda l'FPGA... Forse riesco a procurarmi un campione di spartan 3 AN a lavoro che ha già la flash interna e 200k gate.
Ho detto una fesseria, è la spartan 3 A che ha 200k gate, non la AN.:doh:
Mi devo studiare il datasheet, non so se la A ha la flash interna.
Sasa83 ma sei un elettronico anche tu?
Eh purtroppo si :p
Scherzi a parte, io preferisco ovviamente il vhdl al systemc, ma per certe cose il systemc potrebbe essere una buona scelta, almeno all'inizio: si può progettare ad un livello di astrazione più elevato e vedere se il progetto e buono per poi passare all'RTL in un secondo momento....e poi penso che tutti quì conoscano il C++, lo stesso non direi per il VHDL.
Poi potrei anche consigliarvi di partire da un diagramma UML (SysML)....sono solo consigli comunque, poi è ovvio che farete come meglio preferite ;)
Ecco, visto che siete elettronici (:asd:) vorrei un piccolo consiglio:
1- Parlando di Spartan, io non sono pratico di FPGA, ma mi interesserebbe molto provarle. C'è questo starter kit che sembra molto interessante:
http://www.xilinx.com/products/devkits/HW-SPAR3AN-SK-UNI-G.htm
Che differenza c'è tra la A e la AN? Anche perchè hanno poca differenza di prezzo. Conoscete un negozio possibilmente italiano da dove comprarle? O mi consigliate altri dispositivi?
2- Mi servirebbe fare dei circuiti stampati, ma non ho voglia di comprare l'attrezzatura e fare il piccolo chimico. Conoscete qualche azienda che li fa su ordinazione? Possibilmente anche qui italiana, ma va bene tutto ;)
2- Mi servirebbe fare dei circuiti stampati, ma non ho voglia di comprare l'attrezzatura e fare il piccolo chimico. Conoscete qualche azienda che li fa su ordinazione? Possibilmente anche qui italiana, ma va bene tutto ;)
bè anch'io ho fatto sempre così..nella mia zona c'erano 2 aziende che lo facevano, non so a pordenone, ma una veloce ricerca mi ha dato questo: http://www.tradenordest.com/Elettronica/Circuiti_Stampati.php
però ovvio che non si scomodavano per una scheda, e comunque volevano i gerber del circuito...quindi teoricamente serviva una licenza OrCAD/Allegro.
ps. avete fatto bene a rimarcarlo, per FPGA intendevo la scheda completa, non la sola FPGA...io ad esempio ho sempre usato quelle della Digilent.
ingframin
12-12-2010, 16:37
Mah... io prima di comprare l'FPGA con relativo kit di sviluppo vorrei farmi un'idea di cosa serve. Per quanto riguarda la programmazione sull'apparato fatto da me a lavoro ce ne sono due programmate dal micro e una Spartan 3 AN che abbiamo caricato col JTAG.
Alla fine il caricamento da una flash esterna le FPGA della Xilinx lo fanno in automatico.
Ecco, visto che siete elettronici () vorrei un piccolo consiglio:
1- Parlando di Spartan, io non sono pratico di FPGA, ma mi interesserebbe molto provarle. C'è questo starter kit che sembra molto interessante:
http://www.xilinx.com/products/devki...N-SK-UNI-G.htm
Che differenza c'è tra la A e la AN? Anche perchè hanno poca differenza di prezzo. Conoscete un negozio possibilmente italiano da dove comprarle? O mi consigliate altri dispositivi?
2- Mi servirebbe fare dei circuiti stampati, ma non ho voglia di comprare l'attrezzatura e fare il piccolo chimico. Conoscete qualche azienda che li fa su ordinazione? Possibilmente anche qui italiana, ma va bene tutto
Hai provato a vedere se trovi un kit della Lattice Semiconductors?
Costano molto meno delle Xilinx.
Per i circuiti stampati in piccole serie puoi provare a cercare PCB-proto ad esempio.
Comunque cerco da lavoro e ti faccio sapere.
Direi di concentrarci prima sul progetto dell'ISA e sulla struttura del processore.
Poi all'implementazione ci pensiamo quando abbiamo le idee chiare :-)
ingframin
12-12-2010, 16:58
http://www.mirifica.it/store/65-oho-elektronik-godil50xc3s500e.html
questo è disponibile per noi italici, costa abbastanza poco, è facilmente utilizzabile e ha una buona FPGA a bordo :)
cdimauro
13-12-2010, 05:38
Bisogna però anche tener conto che quella memoria potrebbe essere divisa in più sezioni, che hanno bisogno di flag diversi.
Vero, ma al più abbiamo due sezioni: codice e dati.
Ad esempio, quei processi da te elencati potrebbero avere un codice di pochi KiB, e avere solo memoria allocata dinamicamente. Quindi, se abbiamo un programma da 512KiB, sprechiamo 3.4 MiB (che dobbiamo cmq allocare per fare la pagina intera). Ho scelto 64KiB perchè mi sembrava una scelta adatta, e siamo cmq a 1/16 delle pagine di prima, che è già un bel vantaggio.
Anche pagine da 128KiB non sarebbero male, ma non penso sia bene andare oltre i 256KiB per pagina.
Sì, è chiaro che pagine di 4MB provochino un grosso spreco, ma bisogna anche definire il target del s.o..
Oggi abbiamo macchine da 4 e più GB (io ne ho 8 nel PC principale :D), per cui il problema è decisamente meno sentito, e lo considero trascurabile rispetto ai benefici.
C'è da dire che il funzionamento del processore in modalità supervisore potrebbe anche cambiare in base al target. Ad esempio, per sistemi embedded si potrebbe implementare un'MMU con pagine da 4KB e indirizzi a 32 bit, mentre per una "desktop" le pagine potrebbero essere di 4MB e gli indirizzi a 64 bit.
Cerchiamo di non vincolarci dal fatto che un'architettura debba per forza avere lo stesso supervisor model qualunque sia la sua implementazione / modello.
cdimauro
13-12-2010, 05:41
Io mi permetto di fare una proposta in netta controtendenza:
- opcode a 8 bit con 1,2 o 3 operandi
- instruction register a 128 bit in modo da caricare 4 istruzioni per volta
Quindi istruzioni a lunghezza fissa a 32 bit, se ho capito bene. Un RISC "tradizionale" (anche se qualche istruzione è molto complessa).
Non ho capito se le 4 istruzioni fanno parte di un bundle (VLIW) oppure sono liberamente eseguibili (singola istruzione, n istruzioni in sequenza, o n fuori ordine).
cdimauro
13-12-2010, 05:45
Non ti preoccupare, anche quella di Cesare e la mia mancano ancora in molte parti ;)
Io da un po' sto pensando di cambiare architettura. Non integralmente, ma leggendo nel forum di Natami c'è il progettista della CPU che ha tirato fuori un modo per aggiungere altri 8 registri sfruttando alcuni campi dell'ISA 68000.
Per cui, con alcune limitazioni su questi 8 registri e sugli 8 registri indirizzi (soltanto poche, selezionatissime, istruzioni ad hoc li potrebbero manipolare), potrei arrivare ad avere 16 registri dati (sempre a 64 bit), 8 registri indirizzi, e 4 registri speciali (PC, SP, FP, e SR).
Devo lavorarci per riorganizzare la tabella degli opcode, ma come potete capire dalla mia latitanza dal forum, ho veramente poco tempo. :(
cdimauro
13-12-2010, 05:50
Ma l'ISE della Xilinx è gratuito, io l'ho usato per diversi progetti (anche relativamente grossi) e l'ho sempre preso dal loro sito senza pagare una lira! oltretutto l'ultima versione mi è piaciuta un sacco, il simulatore mi sembra abbastanza decente per quello che l'ho usato :P
Comunque su una spartan 3E, che costa credo 2 soldi, ci ho fatto entrare senza problemi 50k gate...poi vabbè, se volete farlo a 64 bit probabilmente non basterà neanche una spartan 6 :stordita:
Ad ogni modo, parere personale, per un progetto del genere consiglierei il SystemC...perchè a quanto ho capito i programmatori la fanno da padrone quì.
Da programmatore e tempo permettendo, io mi fermerei a un'emulatore della CPU, per vedere come funziona.
Sarebbe anche interessante modificare un compilatore esistente aggiungendo il back-end per fargli generare codice specifico, in modo da vedere come si comporta l'ISA col "codice di tutti i giorni".
L'unico problema concreto in quest'ultimo caso è che probabilmente molte caratteristiche a cui ho pensato non si potrebbero utilizzare, o comunque richiederebbero un enorme lavoro dietro, e non penso che ne valga la pena.
ingframin
13-12-2010, 10:04
Quindi istruzioni a lunghezza fissa a 32 bit, se ho capito bene. Un RISC "tradizionale" (anche se qualche istruzione è molto complessa).
Non ho capito se le 4 istruzioni fanno parte di un bundle (VLIW) oppure sono liberamente eseguibili (singola istruzione, n istruzioni in sequenza, o n fuori ordine).
In realtà ero partito sparato con l'approccio VLIW ma poi mi sono fermato.
Vorrei studiarmi bene la questione prima e vantaggi e svantaggi.
In particolare vorrei studiare bene come funzionano le architetture SPARC e PowerPC. Un'altra architettura molto interessante è quella dell'AVR32.
Poi vorrei farmi fare un po' di ripetizioni dal gruppo di elettronica digitale qui a lavoro, sono molto bravi e mi sapranno sicuramente dare qualche dritta.
Infine voglio recuperare i miei appunti dell'università.
Ieri preso dall'entusiasmo mi sono sbilanciato parecchio, però forse è meglio fare le cose con più calma e un po'di cognizione di causa, senza mettere da parte l'entusiasmio ovviamente :yeah:
la AN ha sicuramente una memoria flash integrata a differenza della A, per il resto credo che non ci siano differenze o quasi. la scheda che hai postato nel link è da 700mila Gate (XC3S700). Per quanto riguarda dove comprarle, in azienda si lavora con le demo board della AVNET.. Ah, ho capito :)
bè anch'io ho fatto sempre così..nella mia zona c'erano 2 aziende che lo facevano, non so a pordenone, ma una veloce ricerca mi ha dato questo: http://www.tradenordest.com/Elettronica/Circuiti_Stampati.php
Grazie, cmq non mi serviva proprio in provincia, bastava almeno che me le spedissero ;)
Per i circuiti stampati in piccole serie puoi provare a cercare PCB-proto ad esempio.
Sembrano buoni, darò un'occhiata alle condizioni di vendita ecc...
consigliare un dispositivo dipende dall'utilizzo... per attività di "scoperta", ti consiglio quella che costa di meno, tanto una vale l'altra all'inizio :P
Mah... io prima di comprare l'FPGA con relativo kit di sviluppo vorrei farmi un'idea di cosa serve. Per quanto riguarda la programmazione sull'apparato fatto da me a lavoro ce ne sono due programmate dal micro e una Spartan 3 AN che abbiamo caricato col JTAG.
Alla fine il caricamento da una flash esterna le FPGA della Xilinx lo fanno in automatico.
Hai provato a vedere se trovi un kit della Lattice Semiconductors?
Costano molto meno delle Xilinx.
Io ero intenzionato a comprare una scheda di valutazione non apposta per fare il processore, ma per farci di tutto e di più ;) Sopratutto mi ispirava il "VGA connector capable of 4096 colors"... :asd:
Poi non è così elevato il prezzo per la Xilinx, su mirifica viene 167 €.
Ho provato invece a guardare su RS Elettronica, giusto perchè così potevo comprare anche altri componenti, ma viene 300€ :eek: vorrà dire che farò 2 account...
Sì, è chiaro che pagine di 4MB provochino un grosso spreco, ma bisogna anche definire il target del s.o..
Oggi abbiamo macchine da 4 e più GB (io ne ho 8 nel PC principale :D), per cui il problema è decisamente meno sentito, e lo considero trascurabile rispetto ai benefici.
E' vero, anche se sapere che sto usando 4 MiB quando ne potrei usare solo 1/4 mi infastidisce ;) Ecco perchè cercavo di trovare un compromesso accettabile, sopratutto se si hanno molti processi piccoli.
C'è da dire che il funzionamento del processore in modalità supervisore potrebbe anche cambiare in base al target. Ad esempio, per sistemi embedded si potrebbe implementare un'MMU con pagine da 4KB e indirizzi a 32 bit, mentre per una "desktop" le pagine potrebbero essere di 4MB e gli indirizzi a 64 bit.
Cerchiamo di non vincolarci dal fatto che un'architettura debba per forza avere lo stesso supervisor model qualunque sia la sua implementazione / modello.Beh pensavo fosse ovvio :D Si non lo ho specificato, ma parlavo in ambito desktop...
E' vero però che se dobbiamo progettare 1 processore, dobbiamo decidere un numero ristretto di valori, per non complicare il circuito e magari annullare i vantaggi ottenuti. Magari che si possa scegliere tra 8KiB, 64KiB, 512Kib, 4Mib
cdimauro
13-12-2010, 20:01
In realtà ero partito sparato con l'approccio VLIW ma poi mi sono fermato.
Vorrei studiarmi bene la questione prima e vantaggi e svantaggi.
In particolare vorrei studiare bene come funzionano le architetture SPARC e PowerPC. Un'altra architettura molto interessante è quella dell'AVR32.
Poi vorrei farmi fare un po' di ripetizioni dal gruppo di elettronica digitale qui a lavoro, sono molto bravi e mi sapranno sicuramente dare qualche dritta.
Infine voglio recuperare i miei appunti dell'università.
Ieri preso dall'entusiasmo mi sono sbilanciato parecchio, però forse è meglio fare le cose con più calma e un po'di cognizione di causa, senza mettere da parte l'entusiasmio ovviamente :yeah:
Diciamo che è meglio definire prima il tipo di codice che s'intende eseguire su queste CPU.
In ambito "desktop" non vedo bene i VLIW, perché generalmente funzionano bene soltanto su codice senza dipendenze e, quindi, internamente parallelizzabile. Ma richiedono anche compilatori ad hoc.
Dell'AVR32 ho visto oggi il manuale, e mi sembra un RISC ben fatto. Ha soltanto 17 registri (16 + SR), e 3 sono specializzati; assomiglia molto all'ARM da questo punto di vista, ma dal sito della casa madre affermano che ha codice più denso (supporta un modello ibrido degli opcode, a 16 bit per gli usi più comuni, oppure a 32 bit per le istruzioni più "estese") e migliori prestazioni.
Questo mi fa pensare che forse l'ISA che ho progettato, coi suoi 8 registri dati, 8 indirizzo, e i 4 speciali elencati prima, forse andrebbe bene già così. Servirebbe un emulatore e un compilatore che ne generi codice binario, a questo punto, per verificarne la bontà.
cdimauro
13-12-2010, 20:09
E' vero, anche se sapere che sto usando 4 MiB quando ne potrei usare solo 1/4 mi infastidisce ;) Ecco perchè cercavo di trovare un compromesso accettabile, sopratutto se si hanno molti processi piccoli.
Sei sicuro che sia uno scenario reale?
Beh pensavo fosse ovvio :D Si non lo ho specificato, ma parlavo in ambito desktop...
In questo caso non penso che si avranno tanti processi piccoli, appunto. ;)
E' vero però che se dobbiamo progettare 1 processore, dobbiamo decidere un numero ristretto di valori, per non complicare il circuito e magari annullare i vantaggi ottenuti. Magari che si possa scegliere tra 8KiB, 64KiB, 512Kib, 4Mib
A questo punto proporrei un paio di soluzioni di compromesso: 64KB e 2MB.
64KB -> albero dell'MMU a 4 livelli, da 16 bit ciascuno.
2MB -> albero dell'MMU a 3 livelli, da 22 (parte alta dell'indirizzo virtuale), 21 e 21 bit (parte bassa) ciascuno.
Questo sempre per architetture a 64 bit.
ingframin
13-12-2010, 21:18
La butto li... Ma una cosa davvero innovativa non sarebbe escogitare un sistema per regolare dinamicamente la paginazione della memoria?
Che ne so... Magari che a seconda del processo carica blocchi a multipli di 64K per esempio... In pratica caricare pagine tra una dimensione minima e una massima.
cdimauro
14-12-2010, 06:19
Quando c'è un processo attivo, ha la sua MMU root caricata, e non c'è nulla che impedisca di avere pagine da 64KB per alcuni processi, e da 2MB per altri.
Si complica un po' il kernel, ma non mi pare nulla di trascendentale.
Forse il casino maggiore può derivare dal mapping delle aree dati comuni. In questo caso possiamo magari utilizzare l'MSB (il bit #63) per specificare se si tratta di un indirizzo globale oppure locale, e utilizzare l'apposito mapping dell'MMU.
Fortunatamente con indirizzi virtuali a 64 bit c'è parecchio margine per "giocare". :fagiano:
ingframin
14-12-2010, 11:11
A questo punto la butto li:
Perché non raccogliamo le adesioni e non facciamo un progettino completo?
Magari il processore potrebbe chiamarsi HWU 01 :cool:
Potremmo fare un sito su Altervista e metterci tutto in modo anche da avere qualcosa da esporre all'esterno.
Secondo me potrebbe essere una cosa interessante :)
Questo mi fa pensare che forse l'ISA che ho progettato, coi suoi 8 registri dati, 8 indirizzo, e i 4 speciali elencati prima, forse andrebbe bene già così. Servirebbe un emulatore e un compilatore che ne generi codice binario, a questo punto, per verificarne la bontà.
Si, 16 registri sono sufficienti per tutti (cit) ;)
Ovviamente, la mia è "più meglio" perchè i 16 registri sono tutti general purpose :sofico:
Seriamente, quale compilatore open source è abbastanza semplice da poter essere portato in tempi umani? Perchè di riscriverlo non ci penso :D
In questo caso non penso che si avranno tanti processi piccoli, appunto. ;)
Dipende.
Dipende anche da come è fatto il sistema operativo: metti che sfrutta tanti programmi diversi per gestire diverse parti dell'interfaccia grafica ad esempio...
A questo punto proporrei un paio di soluzioni di compromesso: 64KB e 2MB.
64KB -> albero dell'MMU a 4 livelli, da 16 bit ciascuno.
2MB -> albero dell'MMU a 3 livelli, da 22 (parte alta dell'indirizzo virtuale), 21 e 21 bit (parte bassa) ciascuno.
Sembra una buona idea; ti faccio notare però che con quella suddivisione una tabella non sta in una pagina sola, come ad esempio fa l'x86; potrebbe essere una complicazione (o una feature :D)
Quando c'è un processo attivo, ha la sua MMU root caricata, e non c'è nulla che impedisca di avere pagine da 64KB per alcuni processi, e da 2MB per altri.
Si complica un po' il kernel, ma non mi pare nulla di trascendentale.
Io riflettevo anche sulla complessità della traduzione indirizzi, che si ritrova a dover gestire più dimensioni diverse; ad esempio, se passiamo da un processo 64K a uno 2M, dobbiamo ricaricare tutto il TLB, anche per le pagine comuni del kernel, a meno che non si possano usare le due dimensioni contemporaneamente, il che introdurrebbe ancora maggiori difficoltà.
Forse il casino maggiore può derivare dal mapping delle aree dati comuni. In questo caso possiamo magari utilizzare l'MSB (il bit #63) per specificare se si tratta di un indirizzo globale oppure locale, e utilizzare l'apposito mapping dell'MMU.
Fortunatamente con indirizzi virtuali a 64 bit c'è parecchio margine per "giocare". :fagiano:
Non ho proprio ben capito questo funzionamento
A questo punto la butto li:
Perché non raccogliamo le adesioni e non facciamo un progettino completo?
Magari il processore potrebbe chiamarsi HWU 01 :cool:
Potremmo fare un sito su Altervista e metterci tutto in modo anche da avere qualcosa da esporre all'esterno.
Secondo me potrebbe essere una cosa interessante :)
Si, decisamente interessante :)
Si può anche hostare su sourceforge, che oltre a mettere a disposizione l'host per il sito, ci da anche il "repository" dove caricare tutti i documenti.
cdimauro
15-12-2010, 13:10
A questo punto la butto li:
Perché non raccogliamo le adesioni e non facciamo un progettino completo?
Magari il processore potrebbe chiamarsi HWU 01 :cool:
Potremmo fare un sito su Altervista e metterci tutto in modo anche da avere qualcosa da esporre all'esterno.
Secondo me potrebbe essere una cosa interessante :)
Sì, ma quale architettura implementi? Penso che ognuno sia leggermente geloso della propria. :D
cdimauro
15-12-2010, 13:20
Si, 16 registri sono sufficienti per tutti (cit) ;)
Ovviamente, la mia è "più meglio" perchè i 16 registri sono tutti general purpose :sofico:
Seriamente, quale compilatore open source è abbastanza semplice da poter essere portato in tempi umani? Perchè di riscriverlo non ci penso :D
Certamente non GCC, che è un bordello immane.
Personalmente proverei con FreePascal, ma il Pascal è un linguaggio che non piacerà a molti. Altra soluzione potrebbe essere CLang/LLVM, che a quanto ho letto si presta molto bene nell'aggiunta di nuovi back-end.
Dipende.
Dipende anche da come è fatto il sistema operativo: metti che sfrutta tanti programmi diversi per gestire diverse parti dell'interfaccia grafica ad esempio...
Hum. Mi pare poco plausibile, francamente.
Sembra una buona idea; ti faccio notare però che con quella suddivisione una tabella non sta in una pagina sola, come ad esempio fa l'x86; potrebbe essere una complicazione (o una feature :D)
Feature. :)
Io riflettevo anche sulla complessità della traduzione indirizzi, che si ritrova a dover gestire più dimensioni diverse; ad esempio, se passiamo da un processo 64K a uno 2M, dobbiamo ricaricare tutto il TLB, anche per le pagine comuni del kernel, a meno che non si possano usare le due dimensioni contemporaneamente, il che introdurrebbe ancora maggiori difficoltà.
La cache del TLB dev'essere comunque ricaricata (non subito, magari alla bisogna) a seguito di un context-switch.
Non ho proprio ben capito questo funzionamento
Gli indirizzi virtuali sono a 64 bit, ma abbiamo l'esigenza di mappare pagine da 64K o 2M.
Allora la mia idea è dividere questo spazio esattamente a metà.
Gli indirizzi "alti" (MSB = 1) indicano al decodificatore che si tratta di un indirizzo virtuale che fa uso di pagine di 2MB, per cui sa che ci sono 3 livelli e deve attraversare l'albero col pattern 21 + 21 + 21 (il primo non è più 22 perché l'MSB è usato per altri scopi, appunto).
Gli indirizzi "bassi" (MSB = 0) indicano al decodificatore che si tratta di un indirizzo virtuale che fa uso di pagine di 64KB, per cui sa che ci sono 4 livelli e deve attraversare l'albero col pattern 15 + 16 + 16 + 16
Certamente non GCC, che è un bordello immane.
Personalmente proverei con FreePascal, ma il Pascal è un linguaggio che non piacerà a molti. Altra soluzione potrebbe essere CLang/LLVM, che a quanto ho letto si presta molto bene nell'aggiunta di nuovi back-end.
Si vedrà. In emergenza si può fare un convertitore che prende l'asm di un'architettura e lo converte alla nostra, ma viene fuori un orrore per quanto riguarda l'ottimizzazione.
Hum. Mi pare poco plausibile, francamente.
Abbiamo processori da millemila core, usiamoli no!? Io già pensavo di mettere nel mio OS un'interfaccia che usa thread per tutto, tipo per animare un menù... Diversi processi che animano gli indicatori della traybar non mi sembrano poi così astrusi.
La cache del TLB dev'essere comunque ricaricata (non subito, magari alla bisogna) a seguito di un context-switch.
Ok, ma avendo pagine tutte uguali non ti tocca anche ricaricare quelle pagine fisse che possono comporre l'area del kernel. In più, ti tocca tenere 2 diverse tabelle per il kernel, e questo può essere un problema per la gestione della memoria lato kernel (che deve per forza tener conto di poter lavorare in due diverse modalità, a differenza delle applicazioni).
Gli indirizzi virtuali sono a 64 bit, ma abbiamo l'esigenza di mappare pagine da 64K o 2M.
Allora la mia idea è dividere questo spazio esattamente a metà.
Gli indirizzi "alti" (MSB = 1) indicano al decodificatore che si tratta di un indirizzo virtuale che fa uso di pagine di 2MB, per cui sa che ci sono 3 livelli e deve attraversare l'albero col pattern 21 + 21 + 21 (il primo non è più 22 perché l'MSB è usato per altri scopi, appunto).
Gli indirizzi "bassi" (MSB = 0) indicano al decodificatore che si tratta di un indirizzo virtuale che fa uso di pagine di 64KB, per cui sa che ci sono 4 livelli e deve attraversare l'albero col pattern 15 + 16 + 16 + 16
Ok, ho capito. Però, se volessimo cambiare la paginazione di un programma in corsa, lo dovremo ricopiare e rilinkare nell'altra zona, che però non possiamo fare perchè gli scombiniamo i puntatori.
Pensavo a una disposizione unica con pagine da un MiB con indirizzo diviso in 10+17+17+20: le tabelle centrali stanno tutte in una pagina, così possono essere gestite con facilità; la tabella da 8KiB per gli ultimi 10 bit può essere tenuta tra i dati del kernel, perchè tanto per qualche anno si dovrà intervenire solo sulla prima entry ;)
http://llvm.org/docs/WritingAnLLVMBackend.html
Questa è una guida per portare LLVM su un'altra architettura: da un'occhiata superficiale sembra abbastanza semplice, però dovremmo scrivere lo stesso un assemblatore.
cdimauro
16-12-2010, 04:32
Si vedrà. In emergenza si può fare un convertitore che prende l'asm di un'architettura e lo converte alla nostra, ma viene fuori un orrore per quanto riguarda l'ottimizzazione.
Orrore sì: assolutamente da evitare. :D
Abbiamo processori da millemila core, usiamoli no!? Io già pensavo di mettere nel mio OS un'interfaccia che usa thread per tutto, tipo per animare un menù... Diversi processi che animano gli indicatori della traybar non mi sembrano poi così astrusi.
Diversi sì, ma non penso che saranno centinaia. E nemmeno decine. :p
Poi i thread condividono almeno il codice, quindi la ridondanza dei segmenti si riduce.
Ok, ma avendo pagine tutte uguali non ti tocca anche ricaricare quelle pagine fisse che possono comporre l'area del kernel. In più, ti tocca tenere 2 diverse tabelle per il kernel, e questo può essere un problema per la gestione della memoria lato kernel (che deve per forza tener conto di poter lavorare in due diverse modalità, a differenza delle applicazioni).
Taglierei la testa al toro, e il kernel lo mapperei sempre su pagine da 2MB. In questo modo almeno "l'ambiente operativo" sarà omogeneo.
Ok, ho capito. Però, se volessimo cambiare la paginazione di un programma in corsa, lo dovremo ricopiare e rilinkare nell'altra zona, che però non possiamo fare perchè gli scombiniamo i puntatori.
Infatti non prevedevo di farlo: la paginazione non si tocca, una volta utilizzata.
Avevo in mente due soluzioni. Una era quella di segnare l'eseguibile con un flag che indicava quale modello di paginazione utilizzare; in questo caso è lo stesso sviluppatore che se ne occupa, impostandolo da progetto.
Un'altra era quella di analizzare lo spazio richiesto da codice e dati al momento del caricamento, e decidere quale tipo di paginazione assegnare. Inoltre il s.o. si prenderebbe carico di monitorare il carico di una certa applicazione, e memorizzare in un registro di sistema il suo profilo, per decidere poi la prossima volta che viene rilanciato, quale adottare. Monitoraggio per me significa una cosa semplice: alla chiusura del processo si vede quanto spazio d'indirizzamento virtuale s'è consumato, decidendo se era meglio uno "fat" oppure "slim".
Pensavo a una disposizione unica con pagine da un MiB con indirizzo diviso in 10+17+17+20: le tabelle centrali stanno tutte in una pagina, così possono essere gestite con facilità; la tabella da 8KiB per gli ultimi 10 bit può essere tenuta tra i dati del kernel, perchè tanto per qualche anno si dovrà intervenire solo sulla prima entry ;)
Mi pare una buona idea.
http://llvm.org/docs/WritingAnLLVMBackend.html
Questa è una guida per portare LLVM su un'altra architettura: da un'occhiata superficiale sembra abbastanza semplice, però dovremmo scrivere lo stesso un assemblatore.
Un assemblatore sarebbe il minimo ed era previsto.
Comunque c'è sempre parecchio lavoro da fare. Forse sarebbe bene partire da un'ISA simile (68000 nel mio caso) e adattarla velocemente alla nuova.
ingframin
16-12-2010, 10:07
Mi permetto di segnalare http://opencores.org/
ingframin
16-12-2010, 11:34
Riguardo la dimensione delle istruzioni.
Stamattina ho dato uno sguardo alle specifiche delle ram DDR e mi sono accorto che prima di stabilire le dimensioni delle istruzioni è bene studiare a fondo la struttura di un pc.
Una cosa infatti che a me era sfuggita è che le ram moderne trasferiscono su un bus a 64 bit e lo fanno in DDR ovvero mandano due parole per ciclo di clock.
Questo significa che si possono caricare 128bit per colpo di clock!
Quindi ha senso fare istruzioni molto corte quando si potrebbero sfruttare benissimo istruzioni a 128 bit?
128 bit sono tantissimi! Significa che si può fare l'indirizzamento assoluto su tutta la memoria sfruttando 64 bit come indirizzo!
Non solo... sfruttare delle memorie standard semplifica di molto il progetto non dovendosi sbattere per studiare come mettere assieme i chip, ecc...
Piazzi un connettore nello schema col relativo controller e tutto funziona :)
Nei prossimi giorni mi faccio passare uno schema elettrico di interfacciamento alle memorie DDR e mi ristudio come costruire un ISA che sfrutta 128 bit e non solo 32.
Saluti :)
Riguardo la dimensione delle istruzioni.
Stamattina ho dato uno sguardo alle specifiche delle ram DDR e mi sono accorto che prima di stabilire le dimensioni delle istruzioni è bene studiare a fondo la struttura di un pc.
Una cosa infatti che a me era sfuggita è che le ram moderne trasferiscono su un bus a 64 bit e lo fanno in DDR ovvero mandano due parole per ciclo di clock.
Questo significa che si possono caricare 128bit per colpo di clock!
Quindi ha senso fare istruzioni molto corte quando si potrebbero sfruttare benissimo istruzioni a 128 bit?
128 bit sono tantissimi! Significa che si può fare l'indirizzamento assoluto su tutta la memoria sfruttando 64 bit come indirizzo!
Non solo... sfruttare delle memorie standard semplifica di molto il progetto non dovendosi sbattere per studiare come mettere assieme i chip, ecc...
Piazzi un connettore nello schema col relativo controller e tutto funziona :)
Nei prossimi giorni mi faccio passare uno schema elettrico di interfacciamento alle memorie DDR e mi ristudio come costruire un ISA che sfrutta 128 bit e non solo 32.
Saluti :)
Invece fare istruzioni più piccole è un vantaggio, perchè così puoi caricare più istruzioni in un colpo solo ;)
Se cmq tu guardi il set di Cesare, vedi come un'istruzione non sono solo i 16 o i 32 bit "principali", ma ci sono anche i dati immediati eventualmente a seguito. Quindi l'"indirizzamento assoluto su tutta la memoria" lo fai lo stesso, con un'istruzione da 16/32 bit prima che specifica un valore addizionale a 64 bit che indica l'indirizzo assoluto.
Anche adesso le cpu x86 hanno istruzioni variabili anche da singolo byte, ma i 128 bit/ciclo li sfruttano lo stesso ;)
cdimauro
16-12-2010, 21:01
Mi permetto di segnalare http://opencores.org/
http://opencores.org/project,tg68 :cool:
Invece fare istruzioni più piccole è un vantaggio, perchè così puoi caricare più istruzioni in un colpo solo ;)
Se cmq tu guardi il set di Cesare, vedi come un'istruzione non sono solo i 16 o i 32 bit "principali", ma ci sono anche i dati immediati eventualmente a seguito. Quindi l'"indirizzamento assoluto su tutta la memoria" lo fai lo stesso, con un'istruzione da 16/32 bit prima che specifica un valore addizionale a 64 bit che indica l'indirizzo assoluto.
Anche adesso le cpu x86 hanno istruzioni variabili anche da singolo byte, ma i 128 bit/ciclo li sfruttano lo stesso ;)
Esattamente. Il mio set d'istruzioni l'ho realizzato a lunghezza variabile da 2 a 16 byte (si potrebbero superare i 16 byte, ma ho voluto mettere questo limite per semplificare il decoder), per cui in 128 bit possono starci da 1 a 8 istruzioni.
Per me gli obiettivi principali della CPU che ho progettato sono due: ottenere un'elevata densità di codice ed eseguire più "microoperazioni" possibili per singola istruzione, avendo ALU o mini-ALU specializzate dedicate.
Altra cosa, anche se di minore importanza, è rendere più facile la programmazione, con un'ISA semplice, ma potente allo stesso, come da tradizione Motorola 68000.
Comunque l'ISA l'ho progettata anche per semplificare al massimo il decoder. Considerate che è sufficiente eseguire il fetch di 32 bit (anche se dovessero contenere due istruzioni diverse, oppure un'istruzione a 16 bit e i primi 16 bit di un'istruzione più lunga) per conoscere la lunghezza dell'istruzione.
Non serve, quindi, decodificare prima un certo numero di byte, e in base al risultato vedere se ci sono altri byte da analizzare per determinare la lunghezza complessiva (e le cose da fare), com'è necessario coi 680x0 e gli x86.
Infine, ho anche pensato alla possibilità di "fondere" due istruzioni ed eseguirle come se si trattasse di un'unica, più grande, istruzione, in modo da migliorare le prestazioni ed eliminare dipendenze, senza per questo richiedere un processore Out-of-Order (e nemmeno In-Order: per il decoder è come se si trattasse di un'unica istruzione che è riuscito a decodificare).
Esempio:
QSUB.Q #1,D0
BNE Loop
Sono due istruzioni a 16 bit, che possono essere trattate come una macroistruzione a 32 bit.
Altro esempio:
QCMP.W #5,D0
BEQ Found
Sempre 16 + 16 -> 32 bit.
Altri pattern utili e/o ricorrenti si possono trovare e implementare.
L'idea è estendibile anche a macroistruzioni più lunghe di 32 bit, in quanto l'ISA è già a lunghezza variabile e supporta istruzioni lunghe fino a 16 byte, appunto.
L'importante è che la prima sia a 16 bit esatti, mentre la seconda abbia l'opcode "principale" a 16 bit e a seguire gli altri suoi dati. Questo perché il decoder, tramite l'analisi dei 16 + 16 bit, deve riuscire a ottenere subito la lunghezza completa della macroistruzione, e quali risorse deve impegnare (i registri, in particolare).
http://opencores.org/project,tg68 :cool:
Oh beh, http://opencores.org/project,t80 :O
Segnalo questo bella serie di video (6):
Reverse Engineering the MOS 6502 CPU
http://www.youtube.com/watch?v=HW9AWBFH1sA
Stavo sistemando la mia architettura quando mi è venuto in mente di implementare quelle istruzioni test-and-set per le operazioni atomiche.
Che istruzioni vengono usate di solito? Quali sarebbe bene implementare?
Salve a tutti!
Apro questo thread per discutere su una nuova architettura di processore, costruita partendo da zero, definendo il set di istruzioni, i registri, le modalità ecc...
Quando avremo deciso queste cose (o anche durante), possiamo implementare l'architettura usando l'ottimo simulatore Hades (http://tams-www.informatik.uni-hamburg.de/applets/hades/webdemos/index.html), così da poter far girare qualche programma che scriveremo.
Io ho già in mente qualche idea, tipo un' architettura RISC con 16 registri, ma con alcune istruzioni comode e tipiche di un CISC in modo da migliorare la densità del codice. Sono però indeciso sul formato delle istruzioni, se comprimerlo a 16 bit o lasciarlo largo a 32 bit e metterci più funzionalità.
Fatemi sapere cosa ne pensate, chiunque più dire la sua, anche se non è un esperto; siamo qui anche per imparare ;)
Non sarebbe meglio iniziare con un formato delle istruzioni da soli 8 bit?
Non sarebbe meglio iniziare con un formato delle istruzioni da soli 8 bit?
Ti dirò, ogni volta che ho provato a fare un set di istruzioni a 8 bit (di recente per aiutare un mio amico), mi sono sempre trovato a corto di spazio, perchè i miei "minimo 8 registri" e "set di istruzioni ortogonale" non vanno molto d'accordo con questa lunghezza...
E poi, che difficoltà c'è tra 8, 16, 32 o 64 bit? Aumenta solo il numero di bit ;)
Ti dirò, ogni volta che ho provato a fare un set di istruzioni a 8 bit (di recente per aiutare un mio amico), mi sono sempre trovato a corto di spazio, perchè i miei "minimo 8 registri" e "set di istruzioni ortogonale" non vanno molto d'accordo con questa lunghezza...
E poi, che difficoltà c'è tra 8, 16, 32 o 64 bit? Aumenta solo il numero di bit ;)
Con 8 bit bisogna pensare solo a 256 istruzioni, per iniziare può bastare, perché io per passatempo avevo calcolato (forse sbagliando, non sono un esperto di architetture degli elaboratori) che potevano bastare 64 istruzioni, se al posto della sottrazione si usava l'addizione e si usava una sola istruzione di salto condizionale (J >, salta se è >). Da 64 a 256 ci sono 192 istruzioni in più, con le istruzioni di sottrazione e di divisione (8) e una trentina di salti condizionali, più altre istruzioni per semplificare il tutto, potrebbero anche avanzare.
ingframin
07-06-2011, 08:24
Mi sa che state parlando di cose diverse.
In teoria per fare uno processore pressoche' completo ce la fai anche solo con una trentina di istruzioni, ma la lunghezza dell'istruzione non dipende solo da quante istruzioni hai.
un'istruzione e 'composta dall'opcode e dai paramteri.
Un formato di esempio potrebbe essere:
[opcode opzioni parametro1 parametro2]
Se tutti sono di 1 byte hai raggiunto i 32 bit con opcode di 8 bit.
Ma puoi anche usare formati diversi, soprattutto finche' si rimane nel
mondo dei sogni (ovvero non si mette mano al silicio) non c'e' molta differenza, la complicazione e' la stessa.
Per quanto riguarda i confronti puoi parallelizzare la logica, ad esempio
puoi pensare ad un'istruzione cosi':
cmp R0,R1
e ad un registro dei flag in cui si alza un flag se:
R0 = R1
R0>R1
R0<R1
Dal punto di vista del circuito fare questi 3 confronti contemporaneamente
e' facilissimo, basta fare una differenza e confrontare il risultato con 0 e mettere il bit di segno in R0<R1 e il suo negato in R0>R1.
Perche' allora non si puo'usare un solo bit per > e <?
Perche' cosi' ti risparmi un sacco di inverter, e siccome il silicio va a 20'000 euro al centimetro quadrato e' meglio andare a risparmio XD
Con 8 bit bisogna pensare solo a 256 istruzioni, per iniziare può bastare, perché io per passatempo avevo calcolato (forse sbagliando, non sono un esperto di architetture degli elaboratori) che potevano bastare 64 istruzioni, se al posto della sottrazione si usava l'addizione e si usava una sola istruzione di salto condizionale (J >, salta se è >). Da 64 a 256 ci sono 192 istruzioni in più, con le istruzioni di sottrazione e di divisione (8) e una trentina di salti condizionali, più altre istruzioni per semplificare il tutto, potrebbero anche avanzare.
Come dice ingframin, bisogna vedere come intendi e come conti le istruzioni, perchè se conti ad esempio ADD come una sola istruzione, allora si, ti verranno molto poche e 256 sono molte. Ma, se in 8 bit ci devi fare stare anche altri parametri (tra cui sicuramente almeno un registro su cui operare), la storia è un po' diversa; ad esempio, con un formato del genere:
| 8 7 6 5 4 3 | 2 1 0 |
| opcode | reg |
Se ogni istruzione ammette i 3 bit per specificare il registro, allora avrai non un' ADD sola, ma 8: ADD R0, ADD R1 ... ADD R7.
Alla fine avrai un campo opcode che basta per 64 valori; se, come dici, sono ancora sufficienti, bene, ma se poi uno vuole inserire istruzioni in più (non necessariamente strane e complesse, istruzioni semplici e comuni, tipo operazioni tra due registri, operazioni con dati immediati, con valori in memoria, oltre a tutte quelle istruzioni per il controllo della macchina), istruzioni di soli 8 bit non bastano. Bisogna così passare a istruzioni di lunghezza variabile (cioè byte supplementari che non sono dati immediati, ma servono per specificare meglio l'operazione), ma a questo punto, se non ci sono stringenti limitazioni di memoria, la difficoltà aggiunta per la decodifica variabile non viene bilanciata da nessun motivo, e andare a istruzioni di 16 bit (parlando sempre solo dell'istruzione con parametri) ci permette maggior spazio con cui lavorare e semplifica la decodifica.
In ultimo, ovviamente dipende tutto poi dal cosa deve fare l'architettura: è pure possibile costruire cpu a 4 bit con 16 o meno istruzioni, ma lo si fa se si vuole realizzare effettivamente la cpu con qualche integrato standard per poi lasciarla in vetrina perchè l'esercizio lo hai finito.
Quindi, per rispondere alla domanda iniziale: certo, si può fare tranquillamente a 8 bit, solo che, per le idee che ho, non mi sono sufficienti; è sempre un'idea personale :).
Perche' cosi' ti risparmi un sacco di inverter, e siccome il silicio va a 20'000 euro al centimetro quadrato e' meglio andare a risparmio XD
20'000 ? Quel centimetro quadrato deve essere molto, MOLTO spesso :D.
ingframin
07-06-2011, 14:48
20'000 ? Quel centimetro quadrato deve essere molto, MOLTO spesso :D.
Il problema e' la lavorazione piu' che il silicio in se.
Un wafer di silicio di grado elettronico costa sui 56$ al kg mediamente,
se ci pensi non e' tanto. Il problema e' far realizzare l'integrato e mettere in piedi la linea di produzione. Soprattutto se pretendi di usare tecnologie non
standard (vedi i FinFET di INTEL o i mosfet al SiGe).
Tipicamente per piccole serie i vari integrati vengono raccolti tra piu' ditte
e poi realizzati tutti assieme, esattamente come si fa con i circuiti stampati.
Dove sto lavorando io ad esempio si fanno piccole serie di integrati
per lo piu' analogici e ci si appoggia di volta in volta a chi organizza questi pool per abbattere i costi.
I microprocessori moderni comunque non sono piu' fatti con tecnologie
cmos standard, ci vuole un team di ingegneri solo per progettare il singolo
transistor che poi verra' sfruttato dai designer per fare i circuiti.
http://download.intel.com/newsroom/kits/22nm/pdfs/22nm-Details_Presentation.pdf
Per esempio in questa presentazione si parla della tecnologia a 22nm:read:
http://download.intel.com/technology/itj/q31998/pdf/trans.pdf
Questa sopra e' una bella spiegazione, seppure un po'datata, che mi fu utilissima 3 anni fa per la tesi :read:
Perdonate questo piccolo off-topic, non e' prettamente un argomento informatico ma va fatto rilevare che molti dei progressi degli attuali processori e molte delle cose che si possono fare sono strettamente legate
alla tecnologia che sta alla base.
Sapere unpo' di quello che sta sotto aiuta anche a rendersi conto perche' qualcosa e' fattibile e qualcosa no sia a livello di ISA che di tutto il resto :sofico:
banryu79
07-06-2011, 15:29
http://download.intel.com/technology/itj/q31998/pdf/trans.pdf
Questa sopra e' una bella spiegazione, seppure un po'datata, che mi fu utilissima 3 anni fa per la tesi :read:
[OT]
Stavo consultando quel documento, mi piacerebbe sapere la sua datazione, perchè nell'abstract ad un certo punto si legge del 2002 in termini di data futura:
MOS transistor limits will be reached for 0.13mm process technologies in production during 2002
Sai a quando risale?
ingframin
07-06-2011, 18:54
Non mi vorrei sbagliare ma mi pare che sia un'articolo del 1999. Comunque da allora ne è passata acqua sotto i ponti... In questi giorni un amico mi raccontava un sacco di cose interessanti su dei nuovi transistor intel che sta collaudando e sono molto veloci.
C'è un gran fermento dal punto di vista tecnologico, soprattutto nei centri di ricerca tipo IMEC dove si vedono un sacco di cose in anteprima assoluta. Alcune cose arrivano poi sul mercato, per altre, come nel caso dei transistor della mia tesi, tutto il progetto viene buttato a mare :doh:
Come dice ingframin, bisogna vedere come intendi e come conti le istruzioni, perchè se conti ad esempio ADD come una sola istruzione, allora si, ti verranno molto poche e 256 sono molte. Ma, se in 8 bit ci devi fare stare anche altri parametri (tra cui sicuramente almeno un registro su cui operare), la storia è un po' diversa; ad esempio, con un formato del genere:
[...]
Alla fine avrai un campo opcode che basta per 64 valori; se, come dici, sono ancora sufficienti, bene, ma se poi uno vuole inserire istruzioni in più (non necessariamente strane e complesse, istruzioni semplici e comuni, tipo operazioni tra due registri, operazioni con dati immediati, con valori in memoria, oltre a tutte quelle istruzioni per il controllo della macchina), istruzioni di soli 8 bit non bastano.
[...]
La ADD la conto come una sola, non metto parametri negli 8 bit, anzi in realtà ADD su 8 bit, su 16 e su 32 sia tra due registri, che registro più numero, quindi sono 6.
Non sarebbe meglio partire da una CPU RISC, facendo a meno delle istruzioni con valori in memoria (es: ADD registro - memoria)?
jappilas
11-06-2011, 21:59
La ADD la conto come una sola, non metto parametri negli 8 bit, e la selezione del registro operando e di quello destinazione come la gestisci?
quello a cui si riferiva z80fan in pratica era:
ho 8 (che già sono pochi) registri general purpose => avrò almeno 8 varianti distinte della ADD, una per ogni registro
e questo nel caso in cui il registro sia usato come operando, la destinazione sia implicita e l' operazione sia distruttiva - cioè la destinazione sia anche uno dei due operandi, modificato permanentemente (a=a+b invece di a=a+b), che però è un concetto tutto fuorchè RISC...
anzi in realtà ADD su 8 bit, su 16 e su 32 sia tra due registri, che registro più numero, quindi sono 6.sempre nel caso minimo di cui sopra, la ADD tra due registri necessita come minimo di 6 bit (3 per l' encoding di "ADD su GP0/1/.../7" e 3 per selezionare il secondo operando)
Non sarebbe meglio partire da una CPU RISC, facendo a meno delle istruzioni con valori in memoria (es: ADD registro - memoria)?così avresti quantomeno una code density minore, un' occupazione di memoria maggiore, e prestazioni minori a parità di caratteristiche implementative - le 3 o 4 istruzioni (LOAD del o degli operandi, ADD, STORE del risultato in memoria) che avresti al posto della sola ADD andrebbero comunque caricate (dalla cache se va bene, dalla memoria altrimenti) decodificate (una per ciclo) ed eseguite serialmente
inoltre per restare in linea con la filosofia RISC dovresti prevedere istruzioni a lunghezza fissa e pari a quella della machine word e alla dimensione dei registri (per semplificare la logica di fetch), campi ben separati per opcode, operandi, ecc (per semplificare la logica di decode), operazioni il più possibile ortogonali rispetto agli operandi e alle modalità operative in cui la macchina si potrà trovare (per semplificare la progettazione in accordo ai precedenti), registri general purpose in numero molto maggiore, 16, 32, ecc (per massimizzare il riuso di dati già caricati dalla memoria, quindi evitare che il disaccoppiamento tra il loro prelievo e il loro utilizzo si trasformi in uno svantaggio), e operazioni aritmetiche possibilmente basate su logica non distruttiva...
e in ogni caso, aspetto che mi faceva notare un amico (sviluppatore assembly in ambito embedded), avresti meno chiarezza dal punto di vista appunto del programmatore assembly - per il quale un indirizzo di memoria indicato come operando, è effetti assimilabile a un identificatore di variabile univoco a livello di intero programma, mentre un registro identifica una variabile logica, solo per la "vita" (di cui tenere traccia a mano) del dato che contiene, finchè non è sovrascritto con uno caricato da una nuova locazione di memoria
Segnalo:
http://www.strchr.com/x86_machine_code_statistics
e la selezione del registro operando e di quello destinazione come la gestisci?
quello a cui si riferiva z80fan in pratica era:
ho 8 (che già sono pochi) registri general purpose => avrò almeno 8 varianti distinte della ADD, una per ogni registro
e questo nel caso in cui il registro sia usato come operando, la destinazione sia implicita e l' operazione sia distruttiva - cioè la destinazione sia anche uno dei due operandi, modificato permanentemente (a=a+b invece di a=a+b), che però è un concetto tutto fuorchè RISC...
sempre nel caso minimo di cui sopra, la ADD tra due registri necessita come minimo di 6 bit (3 per l' encoding di "ADD su GP0/1/.../7" e 3 per selezionare il secondo operando)
così avresti quantomeno una code density minore, un' occupazione di memoria maggiore, e prestazioni minori a parità di caratteristiche implementative - le 3 o 4 istruzioni (LOAD del o degli operandi, ADD, STORE del risultato in memoria) che avresti al posto della sola ADD andrebbero comunque caricate (dalla cache se va bene, dalla memoria altrimenti) decodificate (una per ciclo) ed eseguite serialmente
inoltre per restare in linea con la filosofia RISC dovresti prevedere istruzioni a lunghezza fissa e pari a quella della machine word e alla dimensione dei registri (per semplificare la logica di fetch), campi ben separati per opcode, operandi, ecc (per semplificare la logica di decode), operazioni il più possibile ortogonali rispetto agli operandi e alle modalità operative in cui la macchina si potrà trovare (per semplificare la progettazione in accordo ai precedenti), registri general purpose in numero molto maggiore, 16, 32, ecc (per massimizzare il riuso di dati già caricati dalla memoria, quindi evitare che il disaccoppiamento tra il loro prelievo e il loro utilizzo si trasformi in uno svantaggio), e operazioni aritmetiche possibilmente basate su logica non distruttiva...
e in ogni caso, aspetto che mi faceva notare un amico (sviluppatore assembly in ambito embedded), avresti meno chiarezza dal punto di vista appunto del programmatore assembly - per il quale un indirizzo di memoria indicato come operando, è effetti assimilabile a un identificatore di variabile univoco a livello di intero programma, mentre un registro identifica una variabile logica, solo per la "vita" (di cui tenere traccia a mano) del dato che contiene, finchè non è sovrascritto con uno caricato da una nuova locazione di memoria
Purtroppo mi intendo poco di architetture, e con RISC avevo in mente un'architettura che non prevede operazioni memoria-memoria e che non svolge operazioni aritmetiche con operandi in memoria.
ingframin
28-08-2011, 10:05
Volevo segnalare questo:
http://www.homebrewcpu.org/
:read: Un pazzo con tanto tempo da perdere :D
Volevo segnalare questo:
http://www.homebrewcpu.org/
:read: Un pazzo con tanto tempo da perdere :D
Già, è uno dei pochi che è arrivato "fino in fondo", e che ancora ci giochicchia! :D
Per rimanere in tema:
http://web.cecs.pdx.edu/~harry/Relay/
Ma sopratutto, il nuovissimo:
http://www.youtube.com/watch?v=-ReqdyCxZ9I
Cmq, anche se non ci ho lavorato molto, posterò la mia ultima versione della tabella degli opcode, magari per far girare un po' il thread, ma sopratutto perchè mi è venuta l'ennesima pazza idea! :p
Già, è uno dei pochi che è arrivato "fino in fondo", e che ancora ci giochicchia! :D
Per rimanere in tema:
http://web.cecs.pdx.edu/~harry/Relay/
Ma sopratutto, il nuovissimo:
http://www.youtube.com/watch?v=-ReqdyCxZ9I
Cmq, anche se non ci ho lavorato molto, posterò la mia ultima versione della tabella degli opcode, magari per far girare un po' il thread, ma sopratutto perchè mi è venuta l'ennesima pazza idea! :p
Ma vorresti anche realizzarlo in futuro tramite un FPGA (per modificarlo quando vorrai cambiare architettura)?
Ma vorresti anche realizzarlo in futuro tramite un FPGA (per modificarlo quando vorrai cambiare architettura)?
Certo. E' una buona occasione per imparare il VHDL/Verilog. E poi schede di valutazione non costano tanto.
Ecco la tabella corrente:
Tenete conto che non è assolutamente finita, mancano diverse istruzioni.
Ho ridato una veloce occhiata, ma son sicuro che ci sarà qualche errore in giro.
Un'altra piccola nota: le modalità di indirizzamento della tabella iniziale non devono per forza essere implementate tutte: ho cercato solo di riempire tutta la tabella.
Mode| Index Reg. |Operand Reg | Syntax
00 |Index Reg no|Op. Reg. no | [Rn + Ri * Scale] | Indirect address with scaled index
01 |Index Reg no|Op. Reg. no | [Rn + Ri * Scale + SInt16] | Indirect address with scaled index and signed 16 bits offset
10 |Index Reg no|Op. Reg. no | [Rn + Ri * Scale + SInt32] | Indirect address with scaled index and signed 32 bits offset
11 | 0000 |Op. Reg. no | Rn + Uint3 | Register plus Uint3 (in the "scale" field) <--- potentissima feature
11 | 0001 |Op. Reg. no | Rn - Uint3 | Register less Uint3 (in the "scale" field)
11 | 0010 |Op. Reg. no | [Rn] + Uint3 | Indirect address with postadd of Uint3*size
11 | 0011 |Op. Reg. no | [Rn] - Uint3 | Indirect address with postsubtract of Uint3*size
11 | 0100 |Op. Reg. no | Uint3 + [Rn] | Indirect address with preadd of Uint3*size
11 | 0101 |Op. Reg. no | Uint3 - [Rn] | Indirect address with presubtract of Uint3*size
11 | 0110 |Op. Reg. no | Rn >> Uint3 | Shift Right (sign-fill) of Uint3 bits
11 | 0111 |Op. Reg. no | Rn << Uint3 | Shift Left of Uint3 bits
11 | 1000 |Op. Reg. no | [Rn * scale + SInt16] | Indirect address with signed 16 bits offset
11 | 1001 |Op. Reg. no | [Rn * scale + SInt16]= | Indirect address with signed 16 bits offset and writeback
11 | 1010 |Op. Reg. no | [Rn * scale + SInt32] | Indirect address with signed 32 bits offset
11 | 1011 |Op. Reg. no | [Rn * scale + SInt32]= | Indirect address with signed 32 bits offset and writeback
11 | 1100 |Op. Reg. no | [Rn * scale + SInt64] | Indirect address with signed 64 bits offset
11 | 1101 |Op. Reg. no | [Rn * scale + SInt64]= | Indirect address with signed 64 bits offset and writeback
11 | 1110 |Op. Reg. no | Rn<x,y> | Bit Field (From most significant bit x included to less significant bit y included)
11 | 1111 | 0000 | [PC + SInt16] | Indirect PC with signed 16 bits offset
11 | 1111 | 0001 | [PC + SInt32] | Indirect PC with signed 32 bits offset
11 | 1111 | 0010 | [PC + SInt64] | Indirect PC with signed 32 bits offset
11 | 1111 | 0011 | [PC + UInt3*4] | Indirect PC with UNsigned 3 bits offset (offset is multiplied by four before the addition)
11 | 1111 | 0100 | [SP + UInt16] | Indirect SP with unsigned 16 bits offset
11 | 1111 | 0101 | [SP + UInt32] | Indirect SP with unsigned 32 bits offset
11 | 1111 | 0110 | [SP]++ | Indirect SP with postincrement <--- potentissima feature (POP)
11 | 1111 | 0111 | --[SP] | Indirect SP with predecrement <--- potentissima feature (PUSH)
11 | 1111 | 1000 | [FP + SInt16] | Indirect FP with signed 16 bits offset
11 | 1111 | 1001 | [FP + SInt32] | Indirect FP with signed 32 bits offset
11 | 1111 | 1010 | [FP + SInt64] | Indirect FP with signed 64 bits offset
11 | 1111 | 1011 | [FP + UInt3*4] | Indirect FP with UNsigned 3 bits offset (offset is multiplied by four before the addition)
11 | 1111 | 1100 | [UInt16] | Absolute zero-extended 16 bits address
11 | 1111 | 1101 | [UInt32] | Absolute zero-extended 32 bits address
11 | 1111 | 1110 | [UInt64] | Absolute 64 bits address
11 | 1111 | 1111 | Value | Immediate (size depends on opcode, 8-bit immediates are always 16-bit words with the high byte that MBZ)
Modes 11-0000 and 11-0001, if used as destinations, are only Rn (without the +/- Uint3)
Mode 11-0111 uses this additional word to specify "x" and "y" values:
|15 14 |13 12 11 10 9 8 | 7 6 | 5 4 3 2 1 0 |
| MBZ | x | MBZ | y |
MBZ = Must be zero
.s indica il campo size (B=00, W=01, D=10, Q=11)
Ri = Registro offset
Rs = Registro sorgente
Rd = Registro destinazione (o sorgente e destinazione in caso di parametro singolo)
Ms = Viene specificata una modalità di indirizzamento per quell'operando sorgente
Md = Viene specificata una modalità di indirizzamento per quell'operando destinazione
scale = 1,2,4,8,16,32,64,128, oppure (se la modalità non richiede la scala) il campo contiene un piccolo valore a 3 bit (Uint3)
NOP
|31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 |
| 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 |
# No operation
MOV.s Md, Ms
|31 30 |29 28 |27 26 |25 24 23 |22 21 |20 19 18 17 |16 15 14 13 |12 11 10 | 9 8 | 7 6 5 4 | 3 2 1 0 |
| 0 0 | Size | 0 1 | Scale S | mod S | Registro i S | Registro S | Scale D | mod.D | Registro i D | Registro D |
# Muove qualsiasi cosa in qualsiasi cosa :D
XCHG.s Md, Ms
|31 30 |29 28 |27 26 |25 24 23 |22 21 |20 19 18 17 |16 15 14 13 |12 11 10 | 9 8 | 7 6 5 4 | 3 2 1 0 |
| 0 0 | Size | 1 0 | Scale S | mod S | Registro i S | Registro S | Scale D | mod.D | Registro i D | Registro D |
# scambia i due valori
###############################################
opr.s Rd, Ms
|31 30 |29 28 |27 26 25 24 23 22 |21 |20 19 18 17 |16 15 14 13 |12 11 10 | 9 8 | 7 6 5 4 | 3 2 1 0 |
| 0 1 | Size | 0 0 0 0 0 0 | 0 | Opr | Registro S | Scale S | mod.S | Registro i S | Registro D |
opr.s Md, Rs
|31 30 |29 28 |27 26 25 24 23 22 |21 |20 19 18 17 |16 15 14 13 |12 11 10 | 9 8 | 7 6 5 4 | 3 2 1 0 |
| 0 1 | Size | 0 0 0 0 0 0 | 1 | Opr | Registro S | Scale D | mod.D | Registro i D | Registro D |
Opr:
0000 ADD
0001 SUB
0010 MUL
0011 DIV
0100 AND
0101 OR
0110 XOR
0111 NOT (nega la sorgente e la mette nella destinazione)
1000 XCHG
1001
1010
1011
1100
1101
1110
1111
CJ.cc.s Md, Rs, Sint16 # compare and jump
|31 30 |29 28 |27 26 25 24 23 22 21 |20 19 18 17 |16 15 14 13 |12 11 10 | 9 8 | 7 6 5 4 | 3 2 1 0 |
| 0 1 | Size | 0 0 0 0 0 1 0 | Condizione | Registro s | Scale | mod. | Registro i | Registro D |
# La word successiva deve contenere la distanza dell' istruzione a cui saltare in _Word_, cioè la distanza in byte diviso 2.
CRJ.cc.s Rd, Rs, Sint10 # compare registers and jump
|31 30 |29 28 |27 26 25 24 23 22 21 |20 19 18 17 |16 15 14 13 |12 11 10 9 8 7 6 5 4 | 3 2 1 0 |
| 0 1 | Size | 0 0 0 0 0 1 1 | Condizione | Registro s | Offset / 2 | Registro D |
# Versione più corta di CJcc se si testano solo registri.
# Il campo offset deve contenere la distanza dell' istruzione a cui saltare in _Word_, cioè la distanza in byte diviso 2.
CQJ.cc.s Rd, Sint4, Sint10 # compare register-quick and jump
|31 30 |29 28 |27 26 25 24 23 22 21 |20 19 18 17 |16 15 14 13 |12 11 10 9 8 7 6 5 4 | 3 2 1 0 |
| 0 1 | Size | 0 0 0 0 1 0 0 | Condizione | Sint4 | Offset / 2 | Registro D |
# Il campo offset deve contenere la distanza dell' istruzione a cui saltare in _Word_, cioè la distanza in byte diviso 2.
DJNZC.cc.s Rd, Sint10 # decrement and jump non-zero or condition
|31 30 |29 28 |27 26 25 24 23 22 21 |20 19 18 17 |16 15 14 13 12 11 10 9 8 7 6 5 4 | 3 2 1 0 |
| 0 1 | Size | 0 0 0 0 1 0 1 | Condizione | Offset / 2 | Registro D |
# Il campo offset deve contenere la distanza dell' istruzione a cui saltare in _Word_, cioè la distanza in byte diviso 2.
###
Shift.s Md, quantità
|31 30 |29 28 |27 26 25 24 23 22 21 |20 |19 18 17 |16 15 14 13 |12 11 10 | 9 8 | 7 6 5 4 | 3 2 1 0 |
| 0 1 | Size | 0 0 0 1 0 0 0 | 0 | Shift | Quantità | Scale | mod. | Registro i | Registro D |
# Shift = LSL (Logical Shift Left), LSR (Logical Shift Right), ASL (Aritmetic Shift Left), ASR (Aritmetic Shift Right),
# ROL (rotate left), ROR (rotate right)
ShiftR.s Md, Rs
|31 30 |29 28 |27 26 25 24 23 22 21 |20 |19 18 17 |16 15 14 13 |12 11 10 | 9 8 | 7 6 5 4 | 3 2 1 0 |
| 0 1 | Size | 0 0 0 1 0 0 1 | 1 | Shift | Registro S | Scale | mod. | Registro i | Registro D |
# Shift = LSL (Logical Shift Left), LSR (Logical Shift Right), ASL (Aritmetic Shift Left), ASR (Aritmetic Shift Right),
# ROL (rotate left), ROR (rotate right)
# Rs contiene l'ammontare dello shift
DJNZ.s Rd, Sint10 # decrement and jump non-zero
|31 30 |29 28 |27 26 25 24 23 22 21 20 19 18 17 |16 15 14 13 12 11 10 9 8 7 6 5 4 | 3 2 1 0 |
| 0 1 | Size | 0 0 0 1 0 1 0 0 0 0 0 | Offset / 2 | Registro D |
# Il campo offset deve contenere la distanza dell' istruzione a cui saltare in _Word_, cioè la distanza in byte diviso 2.
LEA.s Rd, Ms
|31 30 |29 28 |27 26 25 24 23 22 21 20 19 18 17 |16 15 14 13 |12 11 10 | 9 8 | 7 6 5 4 | 3 2 1 0 |
| 0 1 | Size | 0 0 0 1 0 1 0 0 0 0 1 | Registro S | Scale S | mod.S | Registro i S | Registro D |
CMP.s Md, Rs
|31 30 |29 28 |27 26 25 24 23 22 21 20 19 18 17 |16 15 14 13 |12 11 10 | 9 8 | 7 6 5 4 | 3 2 1 0 |
| 0 1 | Size | 0 0 0 1 0 1 0 0 0 1 0 | Registro S | Scale | mod. | Registro i | Registro D |
BSWAP.s Rd
|31 30 |29 28 |27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 | 3 2 1 0 |
| 0 1 | Size | 0 0 0 1 0 1 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 | Registro D |
# Byte SWAP. Swaps the bytes:
# For Word: B1 B0 -> B0 B1
# For DWord: B3 B2 B1 B0 -> B0 B1 B2 B3
# For Quad: B7 B6 B5 B4 B3 B2 B1 B0 -> B0 B1 B2 B3 B4 B5 B6 B7
# For Byte, it has no effect.
###############################################
J.cc SInt17 # jump on condition
|31 30 29 28 27 26 25 24 23 22 21 |20 19 18 17 |16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 |
| 1 1 0 0 0 0 0 0 0 0 0 | Condizione | Offset / 2 |
# Il campo offset deve contenere la distanza dell' istruzione a cui saltare in _Word_, cioè la distanza in byte diviso 2.
LINK UInt17
|31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 |16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 |
| 1 1 0 0 0 0 0 0 0 0 1 0 0 0 0 | UInt17 |
# Links the FP to the stack, adding space for locals. The steps are:
# 1) PUSH FP (saves old FP)
# 2) FP = SP
# 3) SP = SP - UInt17 (allocates space for locals)
JMP UInt17
|31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 |16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 |
| 1 1 0 0 0 0 0 0 0 0 1 0 0 0 1 | UInt17 |
# Il campo UInt17 deve contenere la distanza dell' istruzione a cui saltare in _Word_, cioè la distanza in byte diviso 2.
JMP SInt17 # unconditional jump relative
|31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 |16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 |
| 1 1 0 0 0 0 0 0 0 0 1 0 0 1 0 | UInt17 |
# La word successiva deve contenere la distanza dell' istruzione a cui saltare in _Word_, cioè la distanza in byte diviso 2.
MOVSYS sysReg, Rs # move register to system register
|31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 |11 10 9 8 7 6 5 4 | 3 2 1 0 |
| 1 1 0 0 0 0 0 0 0 0 1 0 0 1 1 0 0 0 0 0 | sysReg | Registro S |
MOVSYS Rd, sysReg # move system register to register
|31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 |11 10 9 8 7 6 5 4 | 3 2 1 0 |
| 1 1 0 0 0 0 0 0 0 0 1 0 0 1 1 0 0 0 0 1 | sysReg | Registro D |
SYSCALL UInt8
|31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 | 7 6 5 4 3 2 1 0 |
| 1 1 0 0 0 0 0 0 0 0 1 0 0 1 1 0 0 0 1 0 0 0 0 0 | Uint8 |
# SYStem CALL entry point UInt8
# Pushes PC & FLAG, switches to system stack, sets user flag to 0 and then jumps to (Uint8*64 + SYSCALL_BASE_ADDR)
# (SYSCALL_BASE_ADDR is a system register)
JMPR Rd # jump register relative
|31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 | 3 2 1 0 |
| 1 1 0 0 0 0 0 0 0 0 1 0 0 1 1 0 0 0 1 0 0 0 0 1 0 0 0 0 | Registro D |
# il registro D contiene la distanza in Word dal PC corrente a cui saltare.
JMPRA Rd # jump register absolute
|31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 | 3 2 1 0 |
| 1 1 0 0 0 0 0 0 0 0 1 0 0 1 1 0 0 0 1 0 0 0 0 1 0 0 0 1 | Registro D |
# il registro D contiene l'indirizzo assoluto a cui saltare. Valgono le stesse considerazioni sull'indirizzo di JMP Uint64
JMP SInt32 # unconditional jump relative
|31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 |
| 1 1 0 0 0 0 0 0 0 0 1 0 0 1 1 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 1 |
# La doppia word successiva deve contenere la distanza dell' istruzione a cui saltare in _Word_, cioè la distanza in byte diviso 2.
JMPA UInt64 # unconditional jump absolute
|31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 |
| 1 1 0 0 0 0 0 0 0 0 1 0 0 1 1 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 1 0 |
# La quad word successiva deve contenere l'indirizzo assoluto a cui saltare, che DEVE essere pari (cioè con Bit.0 = 0);
# se questa condizione non è soddisfatta viene generata "Unaligned Address Exception" e il salto non viene eseguito.
RET # return from subroutine
|31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 |
| 1 1 0 0 0 0 0 0 0 0 1 0 0 1 1 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 1 1 |
# POP PC
RETU # return to userspace
|31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 |
| 1 1 0 0 0 0 0 0 0 0 1 0 0 1 1 0 0 0 0 0 0 0 0 1 0 0 1 0 0 1 0 0 |
# switches to user stack, sets the user flag to 1, pops FLAG & PC.
# If used in user code, it throws a "priviledged instruction in user code" exception
Bene, oltre a questo, ecco la mia idea pazza: un neo-retro-computer (cioè un computer costruito con la tecnologia di oggi, ma ispirato ai computer del passato), costruito interamente a transistor e componenti discreti. :read:
Ho già in mente una mezza ideuzza sul formato delle istruzioni.
ingframin
30-08-2011, 09:34
...
Bene, oltre a questo, ecco la mia idea pazza: un neo-retro-computer (cioè un computer costruito con la tecnologia di oggi, ma ispirato ai computer del passato), costruito interamente a transistor e componenti discreti. :read:
Ho già in mente una mezza ideuzza sul formato delle istruzioni.
Preparati ad aumentare il contratto ENEL :rolleyes:
Preparati ad aumentare il contratto ENEL :rolleyes:
Lo alimenterò con il Mr. Fusion, spero solo di rimanere entro i 1.21 Gigowatt! :read: :D
ingframin
30-08-2011, 16:47
http://www.microsoft.com/presspass/Features/2011/aug11/08-17Fusion.mspx
Puoi farti sempre aiutare da mamma Microsoft XD
Altro che ritorno al futuro :sofico:
cdimauro
31-08-2011, 13:09
Ecco la tabella corrente:
Tenete conto che non è assolutamente finita, mancano diverse istruzioni.
Ho ridato una veloce occhiata, ma son sicuro che ci sarà qualche errore in giro.
Un'altra piccola nota: le modalità di indirizzamento della tabella iniziale non devono per forza essere implementate tutte: ho cercato solo di riempire tutta la tabella.
Ah, ecco, perché mi sembravano troppo esagerate alcune. Comunque le commento lo stesso.
Mode| Index Reg. |Operand Reg | Syntax
11 | 0010 |Op. Reg. no | [Rn] + Uint3 | Indirect address with postadd of Uint3*size
11 | 0011 |Op. Reg. no | [Rn] - Uint3 | Indirect address with postsubtract of Uint3*size
Con questo uccidi la pipeline, perché stai introducendo nella fase di valutazione dell'indirizzo una dipendenza da un dato in memoria.
11 | 0100 |Op. Reg. no | Uint3 + [Rn] | Indirect address with preadd of Uint3*size
11 | 0101 |Op. Reg. no | Uint3 - [Rn] | Indirect address with presubtract of Uint3*size
La differenza col precedente non è chiara.
11 | 1110 |Op. Reg. no | Rn<x,y> | Bit Field (From most significant bit x included to less significant bit y included)
E' carina come idea, perché generalizzi il concetto di bitfield, ma pesantissima da implementare.
Intanto complicheresti notevolmente l'unità di load per l'estrazione del campo di bit. Poi se, come immagino, vorresti non soltanto leggere, ma anche scrivere in quel campo di bit (quando la memoria è la destinazione), complicheresti a sua volta anche lo stadio di write della pipeline.
Il tutto per codice che verrà utilizzato poco o niente (non è che si estraggono campi di bit in tutte le applicazioni; anzi).
Diciamo che non è un concetto generalizzabile a qualunque istruzione, ma sarebbe meglio provvedere ad apposite istruzioni per manipolare campi di bit.
opr.s Rd, Ms
|31 30 |29 28 |27 26 25 24 23 22 |21 |20 19 18 17 |16 15 14 13 |12 11 10 | 9 8 | 7 6 5 4 | 3 2 1 0 |
| 0 1 | Size | 0 0 0 0 0 0 | 0 | Opr | Registro S | Scale S | mod.S | Registro i S | Registro D |
opr.s Md, Rs
|31 30 |29 28 |27 26 25 24 23 22 |21 |20 19 18 17 |16 15 14 13 |12 11 10 | 9 8 | 7 6 5 4 | 3 2 1 0 |
| 0 1 | Size | 0 0 0 0 0 0 | 1 | Opr | Registro S | Scale D | mod.D | Registro i D | Registro D |
Opr:
0000 ADD
0001 SUB
0010 MUL
0011 DIV
0100 AND
0101 OR
0110 XOR
0111 NOT (nega la sorgente e la mette nella destinazione)
1000 XCHG
1001
1010
1011
1100
1101
1110
1111
Hai già un'istruzione XCHG.
CJ.cc.s Md, Rs, Sint16 # compare and jump
|31 30 |29 28 |27 26 25 24 23 22 21 |20 19 18 17 |16 15 14 13 |12 11 10 | 9 8 | 7 6 5 4 | 3 2 1 0 |
| 0 1 | Size | 0 0 0 0 0 1 0 | Condizione | Registro s | Scale | mod. | Registro i | Registro D |
# La word successiva deve contenere la distanza dell' istruzione a cui saltare in _Word_, cioè la distanza in byte diviso 2.
CRJ.cc.s Rd, Rs, Sint10 # compare registers and jump
|31 30 |29 28 |27 26 25 24 23 22 21 |20 19 18 17 |16 15 14 13 |12 11 10 9 8 7 6 5 4 | 3 2 1 0 |
| 0 1 | Size | 0 0 0 0 0 1 1 | Condizione | Registro s | Offset / 2 | Registro D |
# Versione più corta di CJcc se si testano solo registri.
# Il campo offset deve contenere la distanza dell' istruzione a cui saltare in _Word_, cioè la distanza in byte diviso 2.
CQJ.cc.s Rd, Sint4, Sint10 # compare register-quick and jump
|31 30 |29 28 |27 26 25 24 23 22 21 |20 19 18 17 |16 15 14 13 |12 11 10 9 8 7 6 5 4 | 3 2 1 0 |
| 0 1 | Size | 0 0 0 0 1 0 0 | Condizione | Sint4 | Offset / 2 | Registro D |
# Il campo offset deve contenere la distanza dell' istruzione a cui saltare in _Word_, cioè la distanza in byte diviso 2.
Queste sono interessanti.
Comunque la CALL/JumpToSubroutine dov'è? E la UNLINK? :stordita:
Per il resto mi sembra un buona ISA CRISP. In ogni caso CISC. :cool:
Bene, oltre a questo, ecco la mia idea pazza: un neo-retro-computer (cioè un computer costruito con la tecnologia di oggi, ma ispirato ai computer del passato), costruito interamente a transistor e componenti discreti. :read:
Ho già in mente una mezza ideuzza sul formato delle istruzioni.
Troppo pazza. Perché perdere tempo così quando ci sono dei comodissimi FPGA?
Con questo uccidi la pipeline, perché stai introducendo nella fase di valutazione dell'indirizzo una dipendenza da un dato in memoria.
No, perchè quel campo è contenuto nell'istruzione stessa, nel campo "scale" da 3 bit (come è specificato sotto la tabella), che non è utilizzato.
Cmq, magari la sintassi non è delle migliori, ma quelle modalità sono equivalenti allo pseudocodice:
val = memoria[ Rx ]
Rx += Uint3;e
Rx += Uint3;
val = memoria[ Rx ]
E' carina come idea, perché generalizzi il concetto di bitfield, ma pesantissima da implementare.
C'è un po' di logica di cui tener conto, ma pesantissima è un po' troppo.
Bisogna spostare un po' più di dati nella pipeline, questo si.
Intanto complicheresti notevolmente l'unità di load per l'estrazione del campo di bit.
"Basta" un barrel-shifter in linea con il percorso dati, e un'unità per mascherare via i bit in eccesso.
Poi se, come immagino, vorresti non soltanto leggere, ma anche scrivere in quel campo di bit (quando la memoria è la destinazione), complicheresti a sua volta anche lo stadio di write della pipeline.
In questo caso bisogna fare un'operazione di lettura del dato di destinazione (cosa cmq prevista dall'architettura), e ricomporre il risultato con una tecnica simile a prima, o nello stadio di esecuzione, o nello stadio di scrittura risultato.
Il tutto per codice che verrà utilizzato poco o niente (non è che si estraggono campi di bit in tutte le applicazioni; anzi).
Io lo ho detto che ho messo tutto quello che mi veniva in mente! :D
Hai già un'istruzione XCHG.
Vero.
Comunque la CALL/JumpToSubroutine dov'è? E la UNLINK? :stordita:
Strano, la call mi sembrava di averla messa... oh beh :doh:
Troppo pazza. Perché perdere tempo così quando ci sono dei comodissimi FPGA?
Perchè posso! :ciapet:
E poi con gli FPGA non c'è divertimento: è come programmare un software.
Diverso è il discorso costruendo tutto partendo dagli elementi base, siano essi transistor, porte logiche, relè o torce di redstone (http://www.youtube.com/watch?v=LGkkyKZVzug). :D
Farlo in transistor sarebbe veramente figo, ma mi sa che alla fine ripiegherò sui 74LS, come fanno tutti... :ciapet:
cdimauro
01-09-2011, 06:12
No, perchè quel campo è contenuto nell'istruzione stessa, nel campo "scale" da 3 bit (come è specificato sotto la tabella), che non è utilizzato.
Cmq, magari la sintassi non è delle migliori, ma quelle modalità sono equivalenti allo pseudocodice:
val = memoria[ Rx ]
Rx += Uint3;
Il problema sta proprio qua: crei una dipendenza dalla memoria nella fase di load, perché la successiva somma dipende dal dato caricato dalla memoria.
Questa è un'operazione normale in una pipeline in cui hai una da fare la sola LOAD e poi l'operazione vera e propria che impegna la ALU.
Qui, invece, è come se avessi da fare una LOAD, un'operazione che richiede una ALU, e infine dare tutto in pasto all'ALU vera e propria. Si complica e si allunga la pipeline.
e
Rx += Uint3;
val = memoria[ Rx ]
Questo, invece, è normale amministrazione per un'AGU.
C'è un po' di logica di cui tener conto, ma pesantissima è un po' troppo.
Bisogna spostare un po' più di dati nella pipeline, questo si.
"Basta" un barrel-shifter in linea con il percorso dati, e un'unità per mascherare via i bit in eccesso.
Tutto ciò richiede tempo, e quindi alcuni stadi di pipeline in più. Un barrel shifter, inoltre, può essere costoso.
In questo caso bisogna fare un'operazione di lettura del dato di destinazione (cosa cmq prevista dall'architettura), e ricomporre il risultato con una tecnica simile a prima, o nello stadio di esecuzione, o nello stadio di scrittura risultato.
Purtroppo devi conservarti necessariamente il dato che hai letto prima dalla memoria, quando hai poi estratto il campo di bit.
E ovviamente aggiungere appositi stadi per ricavare il dato finale da spedire alla memoria o al registro.
Perchè posso! :ciapet:
E poi con gli FPGA non c'è divertimento: è come programmare un software.
Diverso è il discorso costruendo tutto partendo dagli elementi base, siano essi transistor, porte logiche, relè o torce di redstone (http://www.youtube.com/watch?v=LGkkyKZVzug). :D
Farlo in transistor sarebbe veramente figo, ma mi sa che alla fine ripiegherò sui 74LS, come fanno tutti... :ciapet:
Ho un diploma di automatica elettronica, ma non parlarmi di roba come transistor et similia.
Preferisco di gran lunga dedicarmi al software. Anche perché è molto più comodo risolvere problemi, e nello specifico l'obiettivo è implementare l'ISA di un processore, senza perderci anni.
Una volta era un fan delle architetture complesse, stile CISC. Amavo i processori con assembly che permettesse operazioni complesse come la MOSVD o la REPNE SCASB.
Ma questo perche' l'assembly era un linguaggio effettivamente usato anche a livello professionale.
Poiche' oggi invece non e' piu' cosi', preferirei una architettura piu' RISC.
Poche e semplici operazioni in grado di fare tutto, e lascerei il compito dell'organizzazione, esecuzione, parallelizzazione delle operazioni al dispatcher tra pipeline, alla esecuzione not-in-order di una pipeline ed in ultima risorsa al compilatore del caso.
E a vedere quante macrooperazioni sono state abbandonate nella migrazione tra x86 a x64 sembra anche essere il ragionamento piu' effettivamente adottato.
cdimauro
01-09-2011, 12:58
Una volta era un fan delle architetture complesse, stile CISC. Amavo i processori con assembly che permettesse operazioni complesse come la MOSVD o la REPNE SCASB.
Ma questo perche' l'assembly era un linguaggio effettivamente usato anche a livello professionale.
Poiche' oggi invece non e' piu' cosi', preferirei una architettura piu' RISC.
Poche e semplici operazioni in grado di fare tutto, e lascerei il compito dell'organizzazione, esecuzione, parallelizzazione delle operazioni al dispatcher tra pipeline, alla esecuzione not-in-order di una pipeline ed in ultima risorsa al compilatore del caso.
E a vedere quante macrooperazioni sono state abbandonate nella migrazione tra x86 a x64 sembra anche essere il ragionamento piu' effettivamente adottato.
In realtà ne sono state tolte alcune legacy, ma ne vengono continuamente aggiunte altre, anche complesse.
Inoltre anche i RISC sono stati presi dalla corsa all'aggiunta di nuove istruzioni, e difatti quelli più moderni ne hanno anche centinaia (per non parlare di quelle aggiunte per i calcoli SIMD).
Personalmente mi piace l'idea di un CISC moderno, che ne conservi gli elementi fondanti (opcode a lunghezza variabile, operazioni con riferimento diretto alla memoria), senza portarsi dietro robaccia quali istruzioni esotiche (BCD, packing di dati, ecc.) o flag che lavorano in maniera strana.
Mi piacerebbe, comunque, che fossero aggiunte istruzioni di operazioni su blocchi di memoria, ma con una struttura flessibile. Spesso capita di dover copiare memoria, riempirla con un certo valore, confrontare due aree, ricercare un valore.
Sono operazioni comuni e parecchio impegnative, la cui accelerazione potrebbe permettere significativi vantaggi prestazioni e, soprattutto, evitare mal di testa quali pensare all'allineamento degli indirizzi e alla dimensione del bus e/o linea di cache per trasferire dati.
Non le ho messe nella mia ISA perché mi sono riservato di aggiungerle dopo aver pensato alla sezione SIMD.
Credo che la corsa al CISC sia dovuta al fatto che paradossalmente, i CISC sono più efficienti dal punto di vista energetico :D
Nel momento in cui è possibile spegnere zone arbitrarie di un chip e riaccenderle quando viene chiesta l'operazione impegnativa di turno, il consumo ne guadagna in entrambi i casi.
Inoltre così si slega l'efficienza di un processore dal suo clock, delegandola al numero di transistor che implementano le varie funzioni avanzate tipo decoders/randomizer/SIMD/etc.
Alla fine, è l'idea alla base della vetusta ma mai superata Fixed Function :D
cdimauro
01-09-2011, 17:01
Non è facile spegnere così tante piccole parti di un chip, per abilitarle quando serve (il che significa... subito nel caso di istruzioni eseguite).
Comunque i CISC hanno una minor efficienza energetica perché in genere sono dotati di un decoder più complesso, che dev'essere accesso praticamente sempre (per semplificare il discorso, eh!).
In realtà anche i RISC, per quanto detto, si stanno complicando sempre più, e inoltre hanno adottato anche nuove ISA con opcode di lunghezza variabile per far fronte al problema più grosso che hanno (una minor densità di codice, che comporta quindi un maggior uso di cache e di banda verso la memoria).
Tutto sommato quella che era considerata il brutto anatroccolo alla fine ha dominato e, anzi, sta dando lezioni ai "cigni". :cool:
Qui, invece, è come se avessi da fare una LOAD, un'operazione che richiede una ALU, e infine dare tutto in pasto all'ALU vera e propria. Si complica e si allunga la pipeline.
Ma non è vero, non è diversa da un normale generazione dell'indirizzo qualsiasi. Cambia solo il momento in cui si scrive nel registro indice.
E non c'è nessuna dipendenza di memoria, il valore Uint3 ce lo ho perchè è parte integrante dell'istruzione (che per forza è già caricata!), e Rx è un registro interno al processore; non c'è nessun altro dato da caricare, se non il dato stesso richiesto per l'istruzione.
Il tuo discorso piuttosto di può applicare alla modalità
Rn + Uint3
In questo caso si che ci sono le due operazioni, la "presomma", e poi l'operazione dell'ALU vera e propria. Ma anche qua, nessuna paura, perchè i blocchi necessari per la somma ci sono già.
Questo, invece, è normale amministrazione per un'AGU.
Infatti. Ma qui invece si che c'è una dipendenza, e cioè la somma di Rx con l'Uint3; il percorso che i dati devono compiere è addirittura maggiore del caso precedente, in cui la somma e il caricamento da memoria (quest'ultimo cmq più lento) possono essere fatti in parallelo.
Tutto ciò richiede tempo, e quindi alcuni stadi di pipeline in più. Un barrel shifter, inoltre, può essere costoso.
(Premetto: è ovvio che bisognerebbe fare uno studio più accurato, avendo un'implementazione pronta su cui fare test)
Si può mettere il barrel nello stadio di esecuzione, secondo me saranno cmq gli stadi di accesso alla memoria che impiegheranno il maggior tempo, quindi non dovremmo perdere nulla. Il fatto che sia costoso: si, vista la larghezza necessaria è possibile, ma gli ARM ad esempio lo hanno sempre avuto, segno probabilmente che non è la parte più costosa. ;)
Infatti avevo inserito le modalità di barrel shifting, probabilmente ispirato a quest'ultima architettura.
Purtroppo devi conservarti necessariamente il dato che hai letto prima dalla memoria, quando hai poi estratto il campo di bit.
Si infatti, è di questo che parlavo quando dicevo "spostare più dati nella pipeline". Anche qui, bisogna vedere: magari si riutilizzano path che durante quella istruzione non vengono usati.
E ovviamente aggiungere appositi stadi per ricavare il dato finale da spedire alla memoria o al registro.
Questa è l'unica cosa che bisogna effettivamente aggiungere. Si può cmq decidere che non si può usare per la scrittura, ma solo in lettura.
Ma ripeto: io ho messo tutte le modalità che mi venivano in mente, per riempire la tabella che, istruzioni presenti o assenti, ha sempre la stessa dimensione.
Ho un diploma di automatica elettronica, ma non parlarmi di roba come transistor et similia.
Preferisco di gran lunga dedicarmi al software. Anche perché è molto più comodo risolvere problemi, e nello specifico l'obiettivo è implementare l'ISA di un processore, senza perderci anni.
Mi dispiace che non ti piaccia l'hardware, ma quello che non piace a te non è per forza quello che non piace a tutti. E in modo simile ci sono persone che pensano molto meglio in termini di circuiti rispetto al software.
Poi si possono fare cose che anche non hanno uno scopo, o che vengono definite "perdite di tempo" da gente che pensa solo allo scopo finale dell'oggetto (e sai quanti commenti del genere vedo su Hack a Day!).
Perchè voglio costruirmi un processore con transistor o (più realisticamente) con componenti di base?
Non guadagnerò nulla, perderò tempo, quindi perchè?
Perchè si, non c'è un motivo, io posso e voglio farlo, e mi piace farlo.
Lo stesso ragionamento che hanno fatto queste persone:
http://homebrewcpu.org/
http://www.mycpu.eu/
http://www.6502.org/users/dieter/
http://web.cecs.pdx.edu/~harry/Relay/index.html
http://www.ibmsystem3.nl/hjs22/
http://www.nablaman.com/relay/
http://www.pilawa.org/computer/about
E si potrebbe continuare! :D
Basta che fai un giro sul ring, con i tasti in fondo a ogni pagina (conviene usare il tasto "precedente", perchè pare che andando avanti si perdono molti siti).
E non necessariamente ci perdi anni: un'architettura semplice la butti giù in qualche giorno di ragionamento, se vuoi essere proprio sicuro fai delle simulazioni, che ti porteranno via una settimana, poi lo costruisci e lo testi tutto in altre poche settimane (1 mese se fai con calma).
[...]
Poi si possono fare cose che anche non hanno uno scopo, o che vengono definite "perdite di tempo" da gente che pensa solo allo scopo finale dell'oggetto (e sai quanti commenti del genere vedo su Hack a Day!).
Perchè voglio costruirmi un processore con transistor o (più realisticamente) con componenti di base?
Non guadagnerò nulla, perderò tempo, quindi perchè?
Perchè si, non c'è un motivo, io posso e voglio farlo, e mi piace farlo.
Lo stesso ragionamento che hanno fatto queste persone:
http://homebrewcpu.org/
http://www.mycpu.eu/
http://www.6502.org/users/dieter/
http://web.cecs.pdx.edu/~harry/Relay/index.html
http://www.ibmsystem3.nl/hjs22/
http://www.nablaman.com/relay/
http://www.pilawa.org/computer/about
E si potrebbe continuare! :D
Basta che fai un giro sul ring, con i tasti in fondo a ogni pagina (conviene usare il tasto "precedente", perchè pare che andando avanti si perdono molti siti).
E non necessariamente ci perdi anni: un'architettura semplice la butti giù in qualche giorno di ragionamento, se vuoi essere proprio sicuro fai delle simulazioni, che ti porteranno via una settimana, poi lo costruisci e lo testi tutto in altre poche settimane (1 mese se fai con calma).
Non è solo una questione di tempo, secondo me è anche una questione economica. Probabilmente con l'FPGA risparmi parecchio, inoltre Verilog e VHDL potrebbero servirti nella vita per lavoro; se vuoi proprio dedicare due mesi di tempo, prepara un'architettura più complessa ed eventualmente, se te ne intendi, anche un compilatore, e perché no installarci Linux sopra.
Il discorso invece cambia se trovi altre persone con cui condividi la passione: in questo modo vi suddividete tempo e denaro.
cdimauro
01-09-2011, 21:56
Ma non è vero, non è diversa da un normale generazione dell'indirizzo qualsiasi. Cambia solo il momento in cui si scrive nel registro indice.
E non c'è nessuna dipendenza di memoria, il valore Uint3 ce lo ho perchè è parte integrante dell'istruzione (che per forza è già caricata!), e Rx è un registro interno al processore; non c'è nessun altro dato da caricare, se non il dato stesso richiesto per l'istruzione.
OK, allora avevo capito male io. Pensavo che si dovesse leggere dalla memoria all'indirizzo puntato dal registro, per poi sommare la costante a 3 bit, e infine passare questo dato all'ALU.
Come non detto allora.
Il tuo discorso piuttosto di può applicare alla modalità
Rn + Uint3
In questo caso si che ci sono le due operazioni, la "presomma", e poi l'operazione dell'ALU vera e propria. Ma anche qua, nessuna paura, perchè i blocchi necessari per la somma ci sono già.
Sì, infatti. Questa non è affatto critica: è roba normalissima per un'AGU.
Infatti. Ma qui invece si che c'è una dipendenza, e cioè la somma di Rx con l'Uint3; il percorso che i dati devono compiere è addirittura maggiore del caso precedente, in cui la somma e il caricamento da memoria (quest'ultimo cmq più lento) possono essere fatti in parallelo.
Vedi sopra. Era un misunderstanding.
(Premetto: è ovvio che bisognerebbe fare uno studio più accurato, avendo un'implementazione pronta su cui fare test)
Si può mettere il barrel nello stadio di esecuzione, secondo me saranno cmq gli stadi di accesso alla memoria che impiegheranno il maggior tempo, quindi non dovremmo perdere nulla. Il fatto che sia costoso: si, vista la larghezza necessaria è possibile, ma gli ARM ad esempio lo hanno sempre avuto, segno probabilmente che non è la parte più costosa. ;)
Infatti avevo inserito le modalità di barrel shifting, probabilmente ispirato a quest'ultima architettura.
Io invece pensavo al P4 che o non aveva il barrel shifter, oppure aveva delle forti limitazioni nel suo utilizzo.
Si infatti, è di questo che parlavo quando dicevo "spostare più dati nella pipeline". Anche qui, bisogna vedere: magari si riutilizzano path che durante quella istruzione non vengono usati.
Qui servirebbe un'implementazione del processore, e al momento non c'è. :D
Questa è l'unica cosa che bisogna effettivamente aggiungere. Si può cmq decidere che non si può usare per la scrittura, ma solo in lettura.
Hum. Perderesti la simmetria/ortogonalità, che in genere aiuta sia gli esseri umani che i compilatori.
Ma ripeto: io ho messo tutte le modalità che mi venivano in mente, per riempire la tabella che, istruzioni presenti o assenti, ha sempre la stessa dimensione.
OK, OK. :)
Mi dispiace che non ti piaccia l'hardware, ma quello che non piace a te non è per forza quello che non piace a tutti. E in modo simile ci sono persone che pensano molto meglio in termini di circuiti rispetto al software.
Non mi piace l'elettronica analogica, per la precisione. La digitale di già la trovo molto più digeribile.
Comunque preferisco il software, e ne parlo meglio dopo.
Poi si possono fare cose che anche non hanno uno scopo, o che vengono definite "perdite di tempo" da gente che pensa solo allo scopo finale dell'oggetto (e sai quanti commenti del genere vedo su Hack a Day!).
Perchè voglio costruirmi un processore con transistor o (più realisticamente) con componenti di base?
Non guadagnerò nulla, perderò tempo, quindi perchè?
Perchè si, non c'è un motivo, io posso e voglio farlo, e mi piace farlo.
Lo stesso ragionamento che hanno fatto queste persone:
http://homebrewcpu.org/
http://www.mycpu.eu/
http://www.6502.org/users/dieter/
http://web.cecs.pdx.edu/~harry/Relay/index.html
http://www.ibmsystem3.nl/hjs22/
http://www.nablaman.com/relay/
http://www.pilawa.org/computer/about
E si potrebbe continuare! :D
Basta che fai un giro sul ring, con i tasti in fondo a ogni pagina (conviene usare il tasto "precedente", perchè pare che andando avanti si perdono molti siti).
E non necessariamente ci perdi anni: un'architettura semplice la butti giù in qualche giorno di ragionamento, se vuoi essere proprio sicuro fai delle simulazioni, che ti porteranno via una settimana, poi lo costruisci e lo testi tutto in altre poche settimane (1 mese se fai con calma).
Per me il problema è il tempo. Una soluzione software mi permette di arrivare MOLTO più velocemente a un'implementazione e, quindi, a testarla e utilizzarla.
Altra cosa, un simulatore o almeno un emulatore (con debugger) devi comunque svilupparlo in software per lo sviluppo oppure per eseguire precisissimi calcoli prestazionali che ti consentono di capire come meglio calibrare l'architettura sulla scorta delle simulazioni effettuate eseguendo diverti tipi di algoritmi.
Per questo pensavo di scrivere un primo emulatore in Python, in modo da avere qualcosa di concreto prima possibile. ;)
Poi se hai tempo libero e ti vuoi divertire costruendoti il processore con transistor & compagnia, che ti posso dire: beato tu che hai tutto questo tempo. :p
Bene son contento che ci siamo capiti. :)
Hum. Perderesti la simmetria/ortogonalità, che in genere aiuta sia gli esseri umani che i compilatori.
Proprio per questo non volevo imporre questa condizione; o tutto o niente.
Per me il problema è il tempo. Una soluzione software mi permette di arrivare MOLTO più velocemente a un'implementazione e, quindi, a testarla e utilizzarla.
Altra cosa, un simulatore o almeno un emulatore (con debugger) devi comunque svilupparlo in software per lo sviluppo oppure per eseguire precisissimi calcoli prestazionali che ti consentono di capire come meglio calibrare l'architettura sulla scorta delle simulazioni effettuate eseguendo diverti tipi di algoritmi.
Per questo pensavo di scrivere un primo emulatore in Python, in modo da avere qualcosa di concreto prima possibile. ;)
Un attimo: questa architettura è ovvio che la implementerò in un FPGA, è troppo complessa per essere fatta "a mano".
Quest'altro progetto di cui parlo si riferisce a un'altra architettura, moolto più semplice, sullo stile del PDP-11 ad esempio (architettura che stimo moltissimo).
E qui poi ci andrà un contenitore, con front panel etc... appunto, un neo-retro-computer. :)
L'architettura non è un problema; nel passato ho riempito interi blocchi-note con schemi vari e istruzioni. :sofico:
Anche per rispondere a LS1987, alla fine son i soliti componenti standard, della serie 74LS (o 74HCT), si può costruire un sistema senza spendere una fortuna. Certo, 170€ di scheda di valutazione FPGA e 170€ di 74LS non si possono paragonare, sia per l'hardware che ti porti a casa, sia per le possibilità di riuso.
Ma, come dicevo prima, son quelle cose che, quando si decide di farle, si fanno. Ovvio che se uno deve farlo per avere un profitto, o se deve produrlo in serie, fa un ragionamento diverso.
Ma non mi dite che nella vostra vita non avete mai fatto qualcosa solo perchè vi andava di farla, senza una precisa logica. ;)
Poi se hai tempo libero e ti vuoi divertire costruendoti il processore con transistor & compagnia, che ti posso dire: beato tu che hai tutto questo tempo. :p
Ho ancora il vantaggio della giovane età. :ciapet:
cdimauro
02-09-2011, 05:29
Ti posso capire, perché anch'io ho speso VAGONATE di tempo per fare quello che mi passava per la testa.
Comunque ai miei tempi c'erano solo i 74LS e le famigerate breadboard. Altro che FPGA. :stordita:
P.S. Il PDP-11 mi pare abbia un'ISA abbastanza complessa.
P.S. Il PDP-11 mi pare abbia un'ISA abbastanza complessa.
Alla fine neanche tanto... cioè, ha si molte istruzioni, anche un po' "complesse" (tipo l'indirizzamento con pre o post incremento), però costruendo un'implementazione microprogrammata e senza pipeline, non viene fuori un circuito molto complesso, perchè molte cose vengono riusate (ad esempio l'alu si usa per calcolare l'indirizzo, o per incrementare il PC mentre avviene il ciclo di lettura dalla RAM).
Alla fine diventa proprio un problema software, cioè scrivere il microprogramma! :D
ingframin
02-09-2011, 21:35
Se devi usare le porte logiche almeno usa la serie 4000:
http://www.febat.com/Elettronica/Elettronica_serie_CD4000.html
O comunque cercati integrati LVCMOS che vanno a 2,5V o anche meno.
Se usi le TTL ti serve un frigorifero e tanta corrente!
Se devi usare le porte logiche almeno usa la serie 4000:
http://www.febat.com/Elettronica/Elettronica_serie_CD4000.html
O comunque cercati integrati LVCMOS che vanno a 2,5V o anche meno.
Se usi le TTL ti serve un frigorifero e tanta corrente!
Ma i 4000 hanno dei tempi di propagazione eterni, quasi 10 volte superiori ai corrispondenti 74LS !
Piuttosto uso dei 74HCT, che sono lo stesso CMOS, ma compatibili TTL come fasce di tensione.
Per il consumo:
http://k1.dyndns.org/Develop/Hardware/K1-Computer/
Da quanto si vede dalle foto, son tutti AC e HC (quindi cmos).
Power drawn by the CPU is ~350mA@5V when halted and ~800mA@5V when running at 16MHz.
4 W, direi che è accettabile. :ciapet:
sviluppare su FPGA non vuol dire programmare un software come hai scritto. Anzi, lavorare su FPGA come se stessi programmando un software è l'errore fondamentale che si commette. il VHDL è un linguaggio di descrizione hardware, non di programmazione. ti permette di descrivere ad un livello più alto le funzionalità che vorresti realizzare tu "a mano" con transistor e componenti discreti. che poi, se lo vuoi sintetizzare, quel "alto livello" assume un significato del tutto relativo...
comunque, la realizzazione di una architettura di quel tipo a componenti discreti, imho, è infattibile, per questioni di complessità.
Concordo: FPGA != programmare software && più economico.
sviluppare su FPGA non vuol dire programmare un software come hai scritto. Anzi, lavorare su FPGA come se stessi programmando un software è l'errore fondamentale che si commette. il VHDL è un linguaggio di descrizione hardware, non di programmazione. ti permette di descrivere ad un livello più alto le funzionalità che vorresti realizzare tu "a mano" con transistor e componenti discreti. che poi, se lo vuoi sintetizzare, quel "alto livello" assume un significato del tutto relativo...
Questo non cambia il discorso, perchè alla fine stai descrivendo il circuito con un linguaggio particolare. Non c'è nulla di hardware in tutto questo, l'unica cosa che c'è è lo scaricare il nuovo binario (o come si chiama) nell'fpga, ed è finito.
comunque, la realizzazione di una architettura di quel tipo a componenti discreti, imho, è infattibile, per questioni di complessità.
Un PDP-11 è troppo complesso? Allora devono essere stati proprio bravi gli ingegneri che lo hanno progettato! :D
Mamma mia... che discussione interessantissssssssssssssssssssssssssima :eek:
Vi seguo sempre da quando ho scoperto sto topic... non intervengo perchè non sono al livello :)
Comunque mi fermo un attimo per farvi i complimenti, siete grandi...
Questo non cambia il discorso, perchè alla fine stai descrivendo il circuito con un linguaggio particolare. Non c'è nulla di hardware in tutto questo, l'unica cosa che c'è è lo scaricare il nuovo binario (o come si chiama) nell'fpga, ed è finito.
Penso stia partendo col piede sbagliato. Scusa se lo chiedo, ma hai mai usato VHDL e/o Verilog? Ho capito che sono dei linguaggi, ma non stai programmando un software, stai descrivendo un hardware e collegando vari componenti (l'ho fatta un po' semplice). L'approccio da seguire è un tantino diverso dai linguaggi software...ho visto molti programmatori che partivano con questa mentalità (tanto è un linguaggio come un altro) e non riuscivano a cavare un ragno dal buco.
Comunque come diceva Antonio23 non si può pensare di descrivere un'architettura del genere da zero in VHDL o Verilog, bisognerebbe usare prima qualcosa di più alto livello e poi rifinire.
PS. si chiama bitstream quello che carichi sull'FPGA.
ehm, lo cambia eccome. descrivere il circuito non vuol dire mica programmare. è solo hardware, non c'è niente di software. il "binario" che scarichi, il bitstream, non è mica un eseguibile che viene eseguito dalla FPGA :mbe: il bitstream programma le connessioni tra i diversi blocchi logici disponibili nelle celle elementari per realizzare delle funzioni complesse tra ingressi e uscite, punto.
Non mi sembra di aver scritto cose complicate, ma ripetiamole lo stesso:
Lavorare con un FPGA:
1) Descrivere il circuito desiderato mediante un certo linguaggio (che può essere testuale, come VHDL o Verilog, o magari un tool che fa la sintesi direttamente dallo schema elettrico);
2) Sintetizzare la descrizione fatta al punto 1 in un formato capibile dal determinato fpga che si ha sotto mano;
3) Collegare la scheda con l'fpga al computer mediante usb o rs232;
4) Inviare il bitstream generato all'fpga;
5) Avviare l'fpga (che prenderà il bitstream dalla sua memoria e imposterà i path e i gate interni, costruendo così il circuito desiderato).
Ora, mi sembra che gli unici momenti in cui si lavora con dell'hardware sono il punto 3 e il punto 5; durante i punti 1, 2 e 4 si lavora con un PC. Ed è in QUESTO che lo sviluppo di un fpga è simile allo sviluppo di un software, non nei linguaggi o nei tool usati.
Io invece voglio costruire l'hardware con le mie forze, collegando le porte logiche e i blocchi fondamentali a mano. Posso indicare un integrato e dire: "guarda, questo è l'accumulatore", oppure "questo è il buffer three-state che regola l'accesso al bus".
Con un fpga tutto questo è nascosto all'interno del chip.
Ci siamo capiti meglio ora?
infattibile. dove per fattibile intendo ottenere un risultato utile in un tempo umano... in un tempo tendente a infinito puoi fare qualsiasi cosa, però ti stuferai sicuramente prima.
Io in un post precedente ho pure indicato link di diverse persone che hanno costruito cpu partendo da integrati di logica standard.
Prendiamo ad esempio, il più complesso di tutti, il Magic-1 (http://www.homebrewcpu.com/): in 6-7 anni ha:
- Progettato un'architettura a 16 bit, con anche tecniche come paginazione etc.
- Implementato tale architettura in un processore realizzato con logica ttl standard e wire-wrap
- Scritto un assemblatore, portato un compilatore C e scritto un semplice sistema operativo.
- Portato Minix 2
- Collegato a internet (quindi ha uno stack tcp-ip)
- Ha un accesso telnet e un server http.
Io ho già molte idee in mente (son già diversi anni che ho un'idea del genere, e mi è tornata quando un mio amico ha deciso anche lui di iniziare un progetto simile), quindi penso di riuscire a ottenere una macchina funzionante in qualche mese, se riesco a mettermi d'accordo sul set di istruzioni! :D
Sarebbe una bella coppia, il minicomputer stile anni '70 abbinato al mio computer Z80 che fa da terminale, accanto all'fpga con il sistema a 64 bit che fa girare il mio sistema operativo! :sofico:
A parte gli scherzi, intanto metto giù il set di istruzioni, così rimaniamo in-topic. ;)
(il design digitale) dove mi sembra che tu non abbia conoscenze
Anche se non ti sembra, ho molta esperienza in questo campo.
Non mi sembra di aver scritto cose complicate, ma ripetiamole lo stesso:
Lavorare con un FPGA:
1) Descrivere il circuito desiderato mediante un certo linguaggio (che può essere testuale, come VHDL o Verilog, o magari un tool che fa la sintesi direttamente dallo schema elettrico);
2) Sintetizzare la descrizione fatta al punto 1 in un formato capibile dal determinato fpga che si ha sotto mano;
3) Collegare la scheda con l'fpga al computer mediante usb o rs232;
4) Inviare il bitstream generato all'fpga;
5) Avviare l'fpga (che prenderà il bitstream dalla sua memoria e imposterà i path e i gate interni, costruendo così il circuito desiderato).
Ora, mi sembra che gli unici momenti in cui si lavora con dell'hardware sono il punto 3 e il punto 5; durante i punti 1, 2 e 4 si lavora con un PC. Ed è in QUESTO che lo sviluppo di un fpga è simile allo sviluppo di un software, non nei linguaggi o nei tool usati.
Io invece voglio costruire l'hardware con le mie forze, collegando le porte logiche e i blocchi fondamentali a mano. Posso indicare un integrato e dire: "guarda, questo è l'accumulatore", oppure "questo è il buffer three-state che regola l'accesso al bus"
[...]
Se proprio ti piace, perché non utilizzi un software con cui posizioni i singoli elementi 74* , invece di farlo a mano con componenti fisici? Al limite, se proprio vuoi manipolare, fallo con un tablet con touchscreen. Secondo me anche in questo modo ti risparmieresti lavoro. A partire da quel disegno realizzi una CPU mediante FPGA (non conosco il software che lo implementa): a questo punto potrai far vedere ALU, accumulatore ed altro, sul disegno, magari utilizzando un software che te lo mostra in 3 dimensioni.
Se devi prendere una breadboard hai bisogno di spazio e di collegare a mano i fili, in questo modo ti salvi almeno queste due parti (e già sarebbe meglio).
Per migliorare le prestazioni, si possono raddoppiare i registri in modo da memorizzare anche il contesto (in realtà la parte di contesto che viene memorizzata nei registri della CPU) di un altro processo, in modo da velocizzare le commutazioni di contesto?
Implementando una commutazione di contesto più efficiente, migliorano le prestazioni della CPU (a meno che non lavori con i batch monotask).
in 6-7 anni, appunto... poi oh che ti devo dire, ripeto, fai quello che ti diverte di più, a 18 anni il tempo sembra non finire mai...
Eh già, d'altronde ha solo costruito un sottobicchiere di carta... :rolleyes:
Se devi prendere una breadboard hai bisogno di spazio e di collegare a mano i fili
Ecco, dai che pian pianino ci arriviamo... :asd:
Per migliorare le prestazioni, si possono raddoppiare i registri in modo da memorizzare anche il contesto (in realtà la parte di contesto che viene memorizzata nei registri della CPU) di un altro processo, in modo da velocizzare le commutazioni di contesto?
Implementando una commutazione di contesto più efficiente, migliorano le prestazioni della CPU (a meno che non lavori con i batch monotask).
Si, si possono mettere più set di registri diversi in modo che ognuno contenga lo stato di un thread. Ma al massimo questa cosa va bene con solo un altro set supplementare, magari per gestire gli interrupt con la massima velocità, non tanto per i thread normali. Questo perchè:
- E' sempre un numero che sarà minore del massimo numero di thread contemporanei, quindi il sistema operativo dovrà lo stesso provvedere a salvare set di registri più vecchi qualora venissero a mancare.
- Il blocco registri diventa una vera e propria memoria a se, quindi dovrai usare dei decoder più grandi per selezionare li vari registri. Ciò implica un percorso più lungo per i segnali di controllo, e limitano la velocità del processore in generale, sopratutto per quei registri che vengono usati spesso (es. il PC).
- Fare tutti quei registri con memoria statica sarebbe molto costoso e consumerebbe corrente.
- Farli in memoria dinamica implicherebbe un sistema di refresh.
- Le moderne architetture out-of-order già usano vari set di registri nascosti per poter avere le istruzioni contemporaneamente in esecuzione. Ciò significa tanti altri registri per ogni set di registri "visibili", o circuiteria complessa per raggruppare le istruzioni ogni volta che si cambia set, e fare in modo che siano sempre consistenti.
Alla fine, con cache e addirittura istruzioni dedicate, il salvataggio dei registri non è un'operazione così pesante, e (nell'x86) di sicuro non è quella che porta via il maggior tempo durante un context switch.
---
Ecco il set di istruzioni che penso di usare per quel mio minicomputer di cui parlavo. Lo ho finito tra domenica e lunedì, lo posto solo oggi perchè son pigro. :D
Come vedrete, ho cercato (anche pensando a doverlo implementare con meno logica possibile) di tenere i campi delle istruzioni il più possibile fissi, questo agevola anche l'assemblazione a mano.
Ciò ha limitato alcune istruzioni (ad esempio CMPJ.cond Rx, imm), ma non ho dovuto fare troppi sacrifici, e c'è ancora molto spazio per istruzioni aggiuntive.
### Descrizione ###
- 8 Registri a 16 bit, usabili anche a 8 bit (viene preso in considerazione il byte meno significativo)
- modalità supervisore e utente
- disponibile I/O isolato
######################
mod | reg | syntax
00 | Ry | Ry
01 | Ry | [Ry]
10 | Ry | [Ry + imm]
11 | 000 | imm # il dato caricato è sempre una word, anche se la dimensione indicata in s è "byte"
11 | 001 | [abs]
11 | 010 | [SP]
11 | 011 | [SP + imm]
11 | 100 | [SP++]
11 | 101 | [--SP]
11 | 110 | SP
11 | 111 | PC
# s=0 -> 8 bit
# s=1 -> 16 bit
######### MOV #########
MOV.s Mz, My
XCHG.s Mz, My
|15 |14 13 12 11 |10 9 | 8 7 6 | 5 4 3 | 2 1 0 |
| s | 0 0 0 0 | Mod Z | Mod Y | t | Rz | Ry |
# fa anche da NOP (tutto 0 = MOV.B R0, R0)
# t=0 -> MOV
# t=1 -> XCHG
MOV.s Rx, [Ry + Rz]
|15 |14 13 12 11 |10 9 | 8 7 6 | 5 4 3 | 2 1 0 |
| s | 0 0 0 1 | 0 0 | Rx | Rz | Ry |
# Rx = mem[Ry + Rz]
MOV.s [Ry + Rz], Rx
|15 |14 13 12 11 |10 9 | 8 7 6 | 5 4 3 | 2 1 0 |
| s | 0 0 0 1 | 0 1 | Rx | Rz | Ry |
######### ALU #########
opr.s Mx, Ry
|15 |14 13 12 11 |10 9 | 8 7 6 | 5 4 3 | 2 1 0 |
| s | 0 0 1 0 | Mod X | Rx | opr | Ry |
opr.s Ry, Mx
|15 |14 13 12 11 |10 9 | 8 7 6 | 5 4 3 | 2 1 0 |
| s | 0 0 1 1 | Mod X | Rx | opr | Ry |
# opr:
000 ADD
001 SUB
010 ADC
011 SBC
100 AND
101 OR
110 XOR
111 CMP
################
shift.s Mx, n
|15 |14 13 12 11 |10 9 | 8 7 6 | 5 4 3 | 2 1 0 |
| s | 0 1 0 0 | Mod X | Rx | shift | n |
shift.s Mx, Ry
|15 |14 13 12 11 |10 9 | 8 7 6 | 5 4 3 | 2 1 0 |
| s | 0 1 0 1 | Mod X | Rx | shift | 0 | Ry |
# shift:
00 SHL
01 SHR
10 ROL
11 ROR
######## JUMP ########
# nota: tutte le istruzioni di salto lavorano relativamente al PC,
# cioè il nuovo PC è il PC corrente + il valore di My.
# Per i salti assoluti si usano le MOV, quindi il mnemonico
# "JMP" sarà tradotto in una MOV appropriata
JMPR.cond My
|15 |14 13 12 11 |10 9 | 8 7 6 | 5 4 3 | 2 1 0 |
| c | 0 1 1 0 | Mod Y | 0 0 0 | cond | Ry |
JMPR.cond rel7
|15 |14 13 12 11 |10 9 | 8 7 6 | 5 4 3 | 2 1 0 |
| c | 0 1 1 1 | 0 f | cond | rel6 |
# f indica se il salto deve essere fatto avanti (PC += rel6),
# o indietro (PC -= rel6). Può essere pensato come il segno di rel6.
# rel6 contiene la distanza a cui saltare in _word_, cioè la distanza in byte / 2.
# visto che il campo size non serve, lo si usa per estendere le condizioni
c cond|
0 000 | not equal (Z=0)
0 001 | equal (Z=1)
0 010 | greater that or equal (S|V = 0)
0 011 | less than (S|V = 1)
0 100 | less than or equal (S^V = 0)
0 101 | greater than (S^V = 1)
0 110 | higher than (C|Z = 0)
0 111 | lower or same (C|Z = 1)
1 000 | plus (S=0)
1 001 | minus (S=1)
1 010 | overflow clear (V=0)
1 011 | overflow set (V=1)
1 100 | carry clear (C=0)
1 101 | carry set (C=1)
1 110 | never
1 111 | always # normale jump relativo incondizionato, è la modalità di default se non si indica una condizione
DJNZ.s Rx, rel7
|15 |14 13 12 11 |10 9 | 8 7 6 | 5 4 3 | 2 1 0 |
| s | 0 1 1 1 | 1 f | Rx | rel6 |
#decrement and jump non zero
# valgono le considerazioni per JMPR riguardo il rel6 e f
CMPJ.cond.s Rz, Ry, rel16
|15 |14 13 12 11 |10 9 | 8 7 6 | 5 4 3 | 2 1 0 |
| s | 1 0 0 0 | 0 0 | cond2 | Rz | Ry |
# compare and jump relative on condition
CMPJ.cond.s Rz, imm3, rel16
|15 |14 13 12 11 |10 9 | 8 7 6 | 5 4 3 | 2 1 0 |
| s | 1 0 0 0 | 0 1 | cond2 | Rz | imm3 |
# imm3 è signed
# cond2:
000 not equal (Z=0)
001 equal (Z=1)
010 greater that or equal (S|V = 0)
011 less than (S|V = 1)
100 less than or equal (S^V = 0)
101 greater than (S^V = 1)
110 higher than (C|Z = 0)
111 lower or same (C|Z = 1)
INC.s Rx, qty
|15 |14 13 12 11 |10 9 | 8 7 6 | 5 4 3 | 2 1 0 |
| s | 1 0 0 0 | 1 0 | Rx | qty |
# qty è unsigned
DEC.s Rx, qty
|15 |14 13 12 11 |10 9 | 8 7 6 | 5 4 3 | 2 1 0 |
| s | 1 0 0 0 | 1 1 | Rx | qty |
####### CALL #########
CALL.cond My
|15 |14 13 12 11 |10 9 | 8 7 6 | 5 4 3 | 2 1 0 |
| c | 0 1 1 0 | Mod Y | 0 0 1 | cond | Ry |
RET.cond qty # POP PC; SP = SP + qty*2; RET
|15 |14 13 12 11 |10 9 | 8 7 6 | 5 4 3 | 2 1 0 |
| c | 1 0 0 1 | 0 0 | cond | qty |
# qty è unsigned.
# "c" ha lo stesso significato del jump
SYSCALL.cond num
|15 |14 13 12 11 |10 9 | 8 7 6 | 5 4 3 | 2 1 0 |
| c | 1 0 0 1 | 0 1 | cond | qty |
PUSHA
|15 |14 13 12 11 |10 9 | 8 7 6 | 5 4 3 | 2 1 0 |
| 0 | 1 1 0 0 | 0 0 | 0 0 0 | x x x | x x x |
# Push tutti i registri
POPA
|15 |14 13 12 11 |10 9 | 8 7 6 | 5 4 3 | 2 1 0 |
| 0 | 1 1 0 0 | 0 0 | 0 0 1 | x x x | x x x |
# Pop tutti i registri
NOT.s My
|15 |14 13 12 11 |10 9 | 8 7 6 | 5 4 3 | 2 1 0 |
| s | 1 1 0 1 | Mod Y | 0 0 0 | x x x | Ry |
# complemento a 1 (not bitwise)
NEG.s My
|15 |14 13 12 11 |10 9 | 8 7 6 | 5 4 3 | 2 1 0 |
| s | 1 1 0 1 | Mod Y | 0 0 1 | x x x | Ry |
# complemento a 2 (negazione aritmetica)
ADD SP, imm8
|15 14 13 12 11 |10 9 | 8 7 6 | 5 4 3 2 1 0 |
| 0 1 1 1 0 | 0 0 | 0 | imm8 |
SUB SP, imm8
|15 14 13 12 11 |10 9 | 8 7 6 | 5 4 3 2 1 0 |
| 0 1 1 1 0 | 0 0 | 1 | imm8 |
# imm8 è unsigned
############ istruzioni privilegiate ##############
IN Rx, (port)
|15 |14 13 12 11 |10 9 | 8 7 6 | 5 4 3 | 2 1 0 |
| 0 | 1 1 1 1 | 0 0 | Rx | port |
OUT (port), Rx
|15 |14 13 12 11 |10 9 | 8 7 6 | 5 4 3 | 2 1 0 |
| 0 | 1 1 1 1 | 0 1 | Rx | port |
IN Rz, (Ry)
|15 |14 13 12 11 |10 9 | 8 7 6 | 5 4 3 | 2 1 0 |
| 0 | 1 1 1 1 | 1 0 | 0 0 0 | Rz | Ry |
OUT (Ry), Rz
|15 |14 13 12 11 |10 9 | 8 7 6 | 5 4 3 | 2 1 0 |
| 0 | 1 1 1 1 | 1 0 | 0 0 1 | Rz | Ry |
MOVS Rx, sysreg
|15 |14 13 12 11 |10 9 | 8 7 6 | 5 4 3 | 2 1 0 |
| 1 | 1 1 1 1 | 0 0 | Rx | sysreg |
MOVS sysreg, Rx
|15 |14 13 12 11 |10 9 | 8 7 6 | 5 4 3 | 2 1 0 |
| 1 | 1 1 1 1 | 0 1 | Rx | sysreg |
DI # disable interrupts
|15 14 13 12 11 |10 9 | 8 7 6 | 5 4 3 | 2 1 0 |
| 1 1 1 1 1 | 1 0 | 0 0 0 | x x x | x x x |
EI # enable interrupts
|15 14 13 12 11 |10 9 | 8 7 6 | 5 4 3 | 2 1 0 |
| 1 1 1 1 1 | 1 0 | 0 0 1 | x x x | x x x |
RETU # return to user
|15 14 13 12 11 |10 9 | 8 7 6 | 5 4 3 | 2 1 0 |
| 1 1 1 1 1 | 1 0 | 0 1 0 | x x x | x x x |
JMPU Ry # Jump to user code
|15 14 13 12 11 |10 9 | 8 7 6 | 5 4 3 | 2 1 0 |
| 1 1 1 1 1 | 1 0 | 0 1 1 | x x x | Ry |
# Ry indica un indirizzo assoluto
HALT x
|15 14 13 12 11 |10 9 | 8 7 6 | 5 4 3 | 2 1 0 |
| 1 1 1 1 1 | 1 1 | x x x | x x x | x x x |
# x = don't care. Vengono ignorati dal processore.
# Il campo può essere usato come indicatore per l'operatore che
# può vedere il contenuto del campo attraverso il pannello frontale
Possibile uso per ogni campo:
|15 14 13 12 11 10 9 8 7 6 | 5 4 3 | 2 1 0 |
| Microcodice | Registro | Registro |
| Microcodice | Oper. ALU | costante |
| Microcodice | condizione| |
| Microcodice | shift | costante |
| Microcodice | costante |
| Microcodice | costante |
Le istruzioni potrebbero avere degli opcode un po "casuali" perchè ho fatto molto taglia e incolla mentre la costruivo, quello lo devo rifinire per la versione finale; a meno di altri errori, il set così com'è funziona tranquillamente (intendo dire che non ci sono istruzioni con lo stesso opcode etc).
Una cosa in cui son sicuro, è che le istruzioni privilegiate hanno il campo opcode principale (4 bit 14-11) posto a 0xF: in questo modo mi semplifica la circuiteria per il controllo dei permessi e la generazione dell'eccezione, subito dopo la fine del fetch e subito prima dell'execute.
cdimauro
17-09-2011, 09:23
Alcune considerazioni, al solito.
Manca la modalità [PC + imm8/16], che è molto utile per referenziare costanti e/o variabili "locali" al codice che si sta eseguendo (prima o dopo la routine in oggetto).
Personalmente sfrutterei la modalità PC per implementare almeno la [PC + imm8], relegando alle sole istruzioni di salto / ritorno la sua manipolazione. Tanto ci sono già.
Idem per la modalità SP: difficilmente la si usa, perché generalmente l'accesso allo stack lo si fa specificando una costante. E poi ci sono già le istruzioni di add e sub con valori immediati per l'SP. Quindi si potrebbe sfruttare per la [PC + imm16] oppure per una più utile [FP + imm8] nel caso volessi aggiungere un registro per gestire gli stack frame.
Toglierei di mezzo la condizione dalle CALL, RET e SYSCALL, perché si usano molto raramente. Questo consentirà di ottenere altro spazio nella tabella degli opcode.
Eliminerei anche le istruzioni IN e OUT, perché sono un retaggio del passato: ormai si usa l'I/O memory mapped, che è più comodo da gestire.
Manca la modalità [PC + imm8/16], che è molto utile per referenziare costanti e/o variabili "locali" al codice che si sta eseguendo (prima o dopo la routine in oggetto).
Vero, me ne sono proprio dimenticato.
Personalmente sfrutterei la modalità PC per implementare almeno la [PC + imm8], relegando alle sole istruzioni di salto / ritorno la sua manipolazione. Tanto ci sono già.
La ho messa per due motivi: 1: perchè non sapevo cosa altro mettere :D; 2: per comodità. In questo modo, non devo mettere istruzioni per i salti assoluti (si sfrutta la mov mx, my) e il return normale (mov.w pc, [sp++]).
Idem per la modalità SP: difficilmente la si usa, perché generalmente l'accesso allo stack lo si fa specificando una costante.
E poi ci sono già le istruzioni di add e sub con valori immediati per l'SP.
Se intendi [SP]: si, è vero. Conviene lasciare solo la [SP+imm].
Se intendi SP (accesso al registro SP): anche qui, come per PC, è una questione di comodità. Le operazioni add/sub sp, imm le ho messe perchè non potrei farle in altro modo con le istruzioni precedenti; potrei al massimo fare add/sub sp, rx, ma non posso usare costanti con i registri speciali.
Quindi si potrebbe sfruttare per la [PC + imm16] oppure per una più utile [FP + imm8] nel caso volessi aggiungere un registro per gestire gli stack frame.
Rimuovo [SP] e la sostituisco con [PC+imm16].
Toglierei di mezzo la condizione dalle CALL, RET e SYSCALL, perché si usano molto raramente. Questo consentirà di ottenere altro spazio nella tabella degli opcode.
Inizialmente non le volevo mettere neanche io; però, vedendo che mi venivano quasi "gratis" (sopratutto con una vecchia codifica), le ho lasciate.
Non ci sono problemi di spazio: ho ancora diverse centinaia di opcode liberi, ognuno con due eventuali parametri a 3 bit (quindi 2 registri, un immediato a 6 bit etc.). Basta vedere PUSHA e POPA: tutta la classe 1100 è praticamente vuota (e son sicuro che sistemando meglio gli opcode ne salta fuori un'altra ancora!).
Eliminerei anche le istruzioni IN e OUT, perché sono un retaggio del passato
Allora l'architettura rispetta gli obiettivi prefissati! :D
Preferivo anche io un I/O memory mapped, ma:
- il sistema ha solo 64KB di indirizzamento, e al momento non ha MMU;
- niente impedisce a un sistema con I/O dedicato di inserire dispositivi mappati in memoria.
Probabilmente in futuro inserirò un'unità MMU: a quel punto si potranno usare le istruzioni di I/O per comandare quest'ultima, e mettere le periferiche in memoria.
Poi un I/O dedicato rientra nell'ambito storico in cui si vuole posizionare questo neo-retro computer; è già tanto se non ho messo le istruzioni "load from tape"! :ciapet:
---
Ho buttato giù uno schemino a blocchi dell'organizzazione generale della macchina:
http://z80fan.altervista.org/immagini/minicomp/t/cpu16-schema-blocchi.png (http://z80fan.altervista.org/immagini/minicomp/cpu16-schema-blocchi.png)
Ora devo fare uno schema per l'unità di controllo, per definire quanti segnali mi servono dal microcodice.
A questo punto posso realizzare una simulazione (per testare la funzionalità di base dell'architettura), e poi posso passare alla progettazione e realizzazione hardware.
---
mah, forse non leggi bene quello che ti scrivo, non c'è bisogno di nessuna faccina.
A me sembri te quello che non legge.
Visto che a te 6-7 anni sembrano tanti, nonostante il tipo in questione sia sposato, abbia dei figli, abbia un lavoro, e abbia (si lo riscrivo):
- Progettato un'architettura a 16 bit, con anche tecniche come paginazione etc.;
- Implementato tale architettura in un processore realizzato con logica ttl standard e wire-wrap;
- Scritto un assemblatore, portato un compilatore C e scritto un semplice sistema operativo;
- Portato Minix 2;
- Collegato a internet (quindi ha uno stack tcp-ip);
- Ha un accesso telnet e un server http;
allora guarda questo:
http://www.holmea.demon.co.uk/Mk1/Architecture.htm
Citazione:
"Working evenings and weekends, the Mark 1 took a month to design, 4 months to build and a month to program.".
6 mesi dal nulla al funzionante, lavorando nel tempo libero.
Oppure:
http://www.youtube.com/watch?v=qYvr0b8jqbg
Anche lui ci ha messo intorno ai 6 mesi, e ci sono pure i video a provarlo.
Se ancora pensi sia un'impresa impossibile... d'accordo, mica posso costringerti a cambiare idea. :)
mi sembra il topic sul sistema operativo da zero, sul motore 3D tripla A da zero... dopo anni non mi sembra che siano arrivati risultati.
Se stai parlando del mio sistema operativo: è vero, negli ultimi tempi non ho potuto lavorarci molto, ma solo perchè stavo finendo un altro progetto (un computer Z80 che ho usato come tesi per la maturità). Infatti se guardi i commit nel repository, ne ho fatti diversi nell'ultimo periodo che hanno salvato tutti quei piccoli aggiustamenti che avevo fatto nel corso dell'anno.
E non mi sembra che non ci siano dei risultati.
cdimauro
19-09-2011, 05:29
La ho messa per due motivi: 1: perchè non sapevo cosa altro mettere :D; 2: per comodità. In questo modo, non devo mettere istruzioni per i salti assoluti (si sfrutta la mov mx, my) e il return normale (mov.w pc, [sp++]).
Se intendi [SP]: si, è vero. Conviene lasciare solo la [SP+imm].
Se intendi SP (accesso al registro SP): anche qui, come per PC, è una questione di comodità. Le operazioni add/sub sp, imm le ho messe perchè non potrei farle in altro modo con le istruzioni precedenti; potrei al massimo fare add/sub sp, rx, ma non posso usare costanti con i registri speciali.
Chiaro, ma PC ed SP vanno comunque trattati in maniera diversa rispetto agli altri registri. Non credo che, ad esempio, un'istruzione EOR su di loro abbia qualche utilità pratica (devi inventarti un esempio ad hoc per dargli significato).
Molto meglio poche e comode istruzioni dedicate per questi registri, specialmente se hai un'ISA con soli 8 registri, che sono veramente pochi.
Inizialmente non le volevo mettere neanche io; però, vedendo che mi venivano quasi "gratis" (sopratutto con una vecchia codifica), le ho lasciate.
Non ci sono problemi di spazio: ho ancora diverse centinaia di opcode liberi, ognuno con due eventuali parametri a 3 bit (quindi 2 registri, un immediato a 6 bit etc.). Basta vedere PUSHA e POPA: tutta la classe 1100 è praticamente vuota (e son sicuro che sistemando meglio gli opcode ne salta fuori un'altra ancora!).
Meglio non mettere istruzioni che non verranno mai utilizzate, anche se c'è spazio. Magari poi ti viene una buona idea, e non puoi implementarla perché non hai spazio.
Allora l'architettura rispetta gli obiettivi prefissati! :D
Preferivo anche io un I/O memory mapped, ma:
- il sistema ha solo 64KB di indirizzamento, e al momento non ha MMU;
- niente impedisce a un sistema con I/O dedicato di inserire dispositivi mappati in memoria.
Probabilmente in futuro inserirò un'unità MMU: a quel punto si potranno usare le istruzioni di I/O per comandare quest'ultima, e mettere le periferiche in memoria.
Poi un I/O dedicato rientra nell'ambito storico in cui si vuole posizionare questo neo-retro computer; è già tanto se non ho messo le istruzioni "load from tape"! :ciapet:
Non ti serve l'MMU per gestire l'I/O memory mapped. Persino l'Amiga col 68000 non l'aveva, e mappava tutto in memoria. :D
Se vuoi proteggere l'accesso all'I/O dalla modalità utente, ti basta inviare alle periferiche sia l'indirizzo che il flag che indica se stai lavorando in modalità supervisore. Sarà quindi il decoder degli indirizzi di memoria a gestire opportunamente il tutto.
Quando ci sarà l'MMU, sarà ancora meglio.
---
Ho buttato giù uno schemino a blocchi dell'organizzazione generale della macchina:
http://z80fan.altervista.org/immagini/minicomp/t/cpu16-schema-blocchi.png (http://z80fan.altervista.org/immagini/minicomp/cpu16-schema-blocchi.png)
Ora devo fare uno schema per l'unità di controllo, per definire quanti segnali mi servono dal microcodice.
A questo punto posso realizzare una simulazione (per testare la funzionalità di base dell'architettura), e poi posso passare alla progettazione e realizzazione hardware.
Meglio. Così eviti di fare del lavoro da pazzi andando a caccia del bug sulla piastra. :p
Molto meglio poche e comode istruzioni dedicate per questi registri, specialmente se hai un'ISA con soli 8 registri, che sono veramente pochi.
Ma inserirli in quel modo non mi porta via funzioni o cosa, anzi, le modalità eccedenti sarebbero inutilizzate.
Non ti serve l'MMU per gestire l'I/O memory mapped. Persino l'Amiga col 68000 non l'aveva, e mappava tutto in memoria. :D
Si, ma lui aveva 16 MB di spazio di indirizzamento. ;)
Io ho solo 64KB (o 128, con una modifica che non so se farò).
E cmq la comodità del memory-mapped viene quando devi separare i driver in spazi virtuali separati, perchè li fai gestire dalla MMU, o quando devi gestire periferiche come la scheda video per accedere alla memoria video, che è, appunto, una "memoria".
Per gestire una tastiera ad esempio non fa molta differenza. ;)
cdimauro
22-09-2011, 06:02
Ma inserirli in quel modo non mi porta via funzioni o cosa, anzi, le modalità eccedenti sarebbero inutilizzate.
Il modo di usarle si troverebbe. :D
Si, ma lui aveva 16 MB di spazio di indirizzamento. ;)
Ed era un peccato, perché poteva indirizzare 4GB.
Io ho solo 64KB (o 128, con una modifica che non so se farò).
Nah. Lasciala così. Al limite per aggiungere il banking, alla Z8000 o 65816, c'è sempre tempo.
E cmq la comodità del memory-mapped viene quando devi separare i driver in spazi virtuali separati, perchè li fai gestire dalla MMU, o quando devi gestire periferiche come la scheda video per accedere alla memoria video, che è, appunto, una "memoria".
Per gestire una tastiera ad esempio non fa molta differenza. ;)
E' uguale, perché si tratta sempre di locazioni di memoria. Come su Amiga, dove per gestire le periferiche si faceva ricorso ai CIA, che era sempre mappati in memoria.
Il modo di usarle si troverebbe. :D
Purtroppo mi sono imbattuto in un bel problema per quanto riguarda il microcodice e le restrizioni che mi son imposto (indicate dall'hardware che ho a disposizione), e riguarda proprio le modalità di indirizzamento.
Per risolverlo, per il parametro di destinazione ho rimosso le modalità immediata, SP e PC. L'uso di PC mi andava bene però proprio perchè lo si poteva usare come destinazione, a questo punto è inutile tenerle nella tabella "principale" (cioè quella che ho già presentato qui).
Se hai qualche idea di cosa metterci dentro (ricordando sempre che queste altre 2 modalità possono essere usate solo come sorgente), fammelo sapere. :)
E' da ricordare anche che il campo modalità va (quasi) direttamente dentro il microcodice, quindi si possono cambiare le modalità da istruzione a istruzione (se fosse necessario).
Nah. Lasciala così. Al limite per aggiungere il banking, alla Z8000 o 65816, c'è sempre tempo.
No, non è questione di banking, è il fatto che le mie istruzioni sono sempre allineate a word, perciò il LSB del PC è sempre a 0. Visto che cmq il registro hardware è a 16 bit, potrei fare come se virtualmente fosse a 17 bit, quindi poter indirizzare 128 KB di codice. I dati però possono accedere solo a 64 KB (perchè è possibile indirizzare anche byte); potrei fare che i primi 64KB sono solo per codice, mentre i successivi 64KB sono per codice e dati. Potrebbe anche semplificarmi la creazione di una MMU successivamente.
cdimauro
23-09-2011, 20:04
Per adesso lascia perdere quest'aspetto e concentrati sul resto dell'ISA, che è più importante. Ci sarà tempo, man mano che il progetto va avanti, per pensare a come sfruttare questa possibilità.
Riguardo alle due modalità per la sola lettura, per me è scontato come impiegarle: [PC + imm8] e [PC + imm16]. Cambierei anche la [SP] con una [SP + imm8].
Riguardo alle due modalità per la sola lettura, per me è scontato come impiegarle: [PC + imm8] e [PC + imm16]. Cambierei anche la [SP] con una [SP + imm8].
Avevo già sostituito la [SP] con [SP + imm16].
La differenza imm8/imm16 è inutile perché le istruzioni sono allineate a word, quindi un immediato è sempre una word.
Sarebbe bello invece fare un'istruzione con almeno [SP+imm] come indirizzamento implicito e mettere "imm" come parte dell'istruzione.
Ci posso pensare successivamente, vedendo se scrivendo qualche programma uso quella modalità così spesso.
Queste sono le due tabelle di modalità:
mod | reg | syntax
00 | Ry | Ry
01 | Ry | [Ry]
10 | Ry | [Ry + imm16]
11 | 000 | imm16 # il dato caricato è sempre una word, anche se la dimensione indicata in s è "byte"
11 | 001 | [imm16]
11 | 010 | [SP++]
11 | 011 | [--SP]
11 | 100 | [SP + imm16]
11 | 101 | [PC + imm16]
11 | 110 | SP ?
11 | 111 | PC ?
mod2 | - usata internamente
000 | Ry
001 | [Ry]
010 | [Ry + imm16]
011 | [imm16]
100 | [SP++]
101 | [--SP]
110 | [SP + imm16]
111 | [PC + imm16]
Specifico: mod2 è generata internamente partendo da "mod" e "reg" della prima tabella. Probabilmente cambierò un po' l'ordine nella prima per semplificare la traduzione.
Un'altra cosa: la seconda tabella, con la codifica attuale che mi sa non potrò cambiare, è usata anche nelle istruzioni "JMPR.cond My", "CALL.cond My" e "opr.s Rx, My".
Queste sono le uniche istruzioni che mi danno un po' di problemi, perché non posso usare più My per codificare il dato immediato. Allora per queste 3 istruzioni, la modalità [--SP] (che è un push praticamente) lo uso per codificare l'immediato (tanto è tutto a livello di microcodice).
Non penso che questa modifica impatti troppo sull'ortogonalità del set, perché cmq, chi andrebbe mai a fare un jmpr [--SP] ?
Cmq le modifiche che ho fatto per questo problema (che alla fine sono solo spostare dei campi in posizioni diverse) le pubblico in seguito, prima pulisco meglio la codifica.
cdimauro
24-09-2011, 06:00
Avevo già sostituito la [SP] con [SP + imm16].
La differenza imm8/imm16 è inutile perché le istruzioni sono allineate a word, quindi un immediato è sempre una word.
Sarebbe bello invece fare un'istruzione con almeno [SP+imm] come indirizzamento implicito e mettere "imm" come parte dell'istruzione.
Ci posso pensare successivamente, vedendo se scrivendo qualche programma uso quella modalità così spesso.
Queste sono le due tabelle di modalità:
mod | reg | syntax
00 | Ry | Ry
01 | Ry | [Ry]
10 | Ry | [Ry + imm16]
11 | 000 | imm16 # il dato caricato è sempre una word, anche se la dimensione indicata in s è "byte"
11 | 001 | [imm16]
11 | 010 | [SP++]
11 | 011 | [--SP]
11 | 100 | [SP + imm16]
11 | 101 | [PC + imm16]
11 | 110 | SP ?
11 | 111 | PC ?
mod2 | - usata internamente
000 | Ry
001 | [Ry]
010 | [Ry + imm16]
011 | [imm16]
100 | [SP++]
101 | [--SP]
110 | [SP + imm16]
111 | [PC + imm16]
Specifico: mod2 è generata internamente partendo da "mod" e "reg" della prima tabella. Probabilmente cambierò un po' l'ordine nella prima per semplificare la traduzione.
OK. Allora sicuramente potrebbe essere utile una [PC + Ry * 1/2 + Imm12] in modo da referenziare strutture dati relativamente al PC.
Per l'altra si potrebbe fare lo stesso, ma con SP. Il problema, in questo caso, è che risulta frequente scrivere sulla destinazione, per cui sarebbe una modalità azzoppata.
Un'altra cosa: la seconda tabella, con la codifica attuale che mi sa non potrò cambiare, è usata anche nelle istruzioni "JMPR.cond My", "CALL.cond My" e "opr.s Rx, My".
Queste sono le uniche istruzioni che mi danno un po' di problemi, perché non posso usare più My per codificare il dato immediato. Allora per queste 3 istruzioni, la modalità [--SP] (che è un push praticamente) lo uso per codificare l'immediato (tanto è tutto a livello di microcodice).
Non penso che questa modifica impatti troppo sull'ortogonalità del set, perché cmq, chi andrebbe mai a fare un jmpr [--SP] ?
E invece sarebbe comoda. :D Prelevo dallo stack l'indirizzo a cui saltare, e ci salto. Se devi togliere di mezzo una modalità, è meglio [SP++], perché in questo contesto è sicuramente inutile.
Cmq le modifiche che ho fatto per questo problema (che alla fine sono solo spostare dei campi in posizioni diverse) le pubblico in seguito, prima pulisco meglio la codifica.
OK
OK. Allora sicuramente potrebbe essere utile una [PC + Ry * 1/2 + Imm12] in modo da referenziare strutture dati relativamente al PC.
Non si può, perchè il campo in cui risiede "Ry" è già usato per codificare la modalità (110 e 111 nella colonna "reg"), perciò si possono inserire solo modalità che non fanno uso dei registri generali.
E invece sarebbe comoda. :D Prelevo dallo stack l'indirizzo a cui saltare, e ci salto. Se devi togliere di mezzo una modalità, è meglio [SP++], perché in questo contesto è sicuramente inutile.
Uhm no. È [SP++] che esegue il pop (preleva dallo stack), non il [--SP], che è il push (inserisci nello stack).
Se usassi [--SP] come sorgente, prima di tutto SP verrebbe decrementato ("aumentando" la dimensione dello stack, perchè cresce al contrario) e punterebbe a un valore "indefinito"; la successiva operazione [SP] leggerebbe questo valore indefinito e salterebbe... a caso!
cdimauro
24-09-2011, 16:39
Non si può, perchè il campo in cui risiede "Ry" è già usato per codificare la modalità (110 e 111 nella colonna "reg"), perciò si possono inserire solo modalità che non fanno uso dei registri generali.
Veramente pensavo di usare la word a 16 bit che segue l'istruzione per contenere tutto ciò che serve: costante da 12 bit, indice del registro da 3 bit, e un bit per lo scaling (1x o 2x). :cool:
Uhm no. È [SP++] che esegue il pop (preleva dallo stack), non il [--SP], che è il push (inserisci nello stack).
Se usassi [--SP] come sorgente, prima di tutto SP verrebbe decrementato ("aumentando" la dimensione dello stack, perchè cresce al contrario) e punterebbe a un valore "indefinito"; la successiva operazione [SP] leggerebbe questo valore indefinito e salterebbe... a caso!
Oops. Vero; sono un po' arrugginito con l'assenbly. :D
Come non detto allora. Va bene la scelta che avevi fatto.
Veramente pensavo di usare la word a 16 bit che segue l'istruzione per contenere tutto ciò che serve: costante da 12 bit, indice del registro da 3 bit, e un bit per lo scaling (1x o 2x). :cool:
Già, scusa non mi ero accorto di quel "Imm12".
Si potrebbe fare... ma servirebbe un po' di logica supplementare (sia per separare i bit di controllo dalla costante, sia per interfacciarsi con il microcodice, come il bit di scaling, che non può essere messo come ingresso al microcodice, ma deve essere combinato con le uscite).
Cmq penso che una modalità così sia già abbastanza complessa, considerando il target della macchina. Già così si può dire che ci sono istruzioni che superano lo scopo di questa macchina...
Vedrò in futuro se sarà così necessario da fare lo sforzo di includerle.
cdimauro
24-09-2011, 19:04
Considera che nemmeno i 68000 avevano lo scaling, per gli stessi motivi. Magari al momento lascia il bit sempre a zero (tanto vale come moltiplicazione per 1): ci sarà tempo, eventualmente, per usarlo e moltiplicare per due.
Considera, comunque, che sommare un registro e una costante a PC o SP per accedere alla memoria è un pattern abbastanza comune e semplificherebbe sicuramente la programmazione del processore.
ingframin
26-09-2011, 13:44
Pythonisti amanti dell'elettronica digitale in delirio!
http://www.myhdl.org/doku.php
:D
Piu' tardi lo mostro in azienda, spero di riuscire a farci qualcosa,
magari su FPGA prima che su silicio ;)
vBulletin® v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.