Conteggio dei cicli con CPU moderne (ad es. ARM)


14

In molte applicazioni, una CPU la cui esecuzione delle istruzioni ha una relazione di temporizzazione nota con stimoli di input previsti può gestire attività che richiederebbero una CPU molto più veloce se la relazione fosse sconosciuta. Ad esempio, in un progetto che ho usato un PSOC per generare video, ho usato il codice per emettere un byte di dati video ogni 16 clock della CPU. Dal momento che testare se il dispositivo SPI è pronto e ramificare in caso contrario IIRC richiederebbe 13 clock, e un caricamento e l'archiviazione sui dati di output richiederebbe 11, non c'era modo di testare la disponibilità del dispositivo tra i byte; invece, mi sono semplicemente arrangiato in modo che il processore eseguisse esattamente 16 cicli di codice per ogni byte dopo il primo (credo di aver usato un carico indicizzato reale, un carico indicizzato fittizio e un archivio). La prima scrittura SPI di ogni riga è avvenuta prima dell'inizio del video, e per ogni successiva scrittura c'era una finestra di 16 cicli in cui la scrittura poteva avvenire senza sovraccarico o sovraccarico del buffer. Il ciclo di ramificazione ha generato una finestra di 13 cicli di incertezza, ma la prevedibile esecuzione di 16 cicli ha significato che l'incertezza per tutti i byte successivi si adattava alla stessa finestra di 13 cicli (che a sua volta si adattava alla finestra di 16 cicli di quando la scrittura poteva accettabilmente si verificano).

Per le CPU meno recenti, le informazioni sui tempi delle istruzioni erano chiare, disponibili e inequivocabili. Per i nuovi ARM, le informazioni sui tempi sembrano molto più vaghe. Capisco che quando il codice viene eseguito da Flash, il comportamento della memorizzazione nella cache può rendere le cose molto più difficili da prevedere, quindi mi aspetto che qualsiasi codice conteggio dei cicli dovrebbe essere eseguito dalla RAM. Anche quando si esegue il codice dalla RAM, tuttavia, le specifiche sembrano un po 'vaghe. L'uso del codice conteggio dei cicli è ancora una buona idea? In tal caso, quali sono le migliori tecniche per farlo funzionare in modo affidabile? In che misura si può presumere in sicurezza che un fornitore di chip non scivoli silenziosamente in un chip "nuovo migliorato" che rade un ciclo dall'esecuzione di determinate istruzioni in determinati casi?

Supponendo che il ciclo seguente inizi su un confine di parola, come si determinerebbe in base alle specifiche esattamente quanto tempo impiegherebbe (supponiamo che Cortex-M3 con memoria zero-wait-state; nient'altro sul sistema dovrebbe importare per questo esempio).

myloop:
  mov r0, r0; Brevi semplici istruzioni per consentire il prefetch di più istruzioni
  mov r0, r0; Brevi semplici istruzioni per consentire il prefetch di più istruzioni
  mov r0, r0; Brevi semplici istruzioni per consentire il prefetch di più istruzioni
  mov r0, r0; Brevi semplici istruzioni per consentire il prefetch di più istruzioni
  mov r0, r0; Brevi semplici istruzioni per consentire il prefetch di più istruzioni
  mov r0, r0; Brevi semplici istruzioni per consentire il prefetch di più istruzioni
  aggiunge r2, r1, # 0x12000000; Istruzione di 2 parole
  ; Ripeti quanto segue, possibilmente con diversi operandi
  ; Continuerà ad aggiungere valori fino a quando si verifica un carry
  ITCC
  aggiungecc r2, r2, # 0x12000000; Istruzione di 2 parole, più "parola" extra per itcc
  ITCC
  aggiungecc r2, r2, # 0x12000000; Istruzione di 2 parole, più "parola" extra per itcc
  ITCC
  aggiungecc r2, r2, # 0x12000000; Istruzione di 2 parole, più "parola" extra per itcc
  ITCC
  aggiungecc r2, r2, # 0x12000000; Istruzione di 2 parole, più "parola" extra per itcc
; ... ecc., con più istruzioni condizionali di due parole
  sub r8, r8, # 1
  bpl myloop

Durante l'esecuzione delle prime sei istruzioni, il core avrebbe avuto il tempo di recuperare sei parole, di cui tre sarebbero state eseguite, quindi potrebbero esserci fino a tre pre-recuperate. Le istruzioni successive sono tutte e tre le parole ciascuna, quindi non sarebbe possibile per il core recuperare le istruzioni più velocemente di quanto vengano eseguite. Mi aspetto che alcune delle istruzioni "it" richiedano un ciclo, ma non so come prevedere quali.

Sarebbe bello se ARM potesse specificare determinate condizioni in cui la tempistica dell'istruzione "it" sarebbe deterministica (ad es. Se non ci sono stati di attesa o contesa del bus di codice, e le due precedenti istruzioni sono istruzioni di registro a 16 bit, ecc.) ma non ho visto nessuna di queste specifiche.

Applicazione di esempio

Supponiamo che uno stia cercando di progettare una scheda figlia per un Atari 2600 per generare output video componente a 480P. Il 2600 ha un clock pixel da 3,579 MHz e un clock CPU da 1,19 MHz (dot clock / 3). Per i video componente 480P, ogni linea deve essere emessa due volte, il che implica un'uscita dot clock a 7.158 MHz. Poiché il chip video (TIA) di Atari emette uno dei 128 colori usando come segnale luma a 3 bit più un segnale di fase con una risoluzione di circa 18 ns, sarebbe difficile determinare con precisione il colore semplicemente guardando le uscite. Un approccio migliore sarebbe quello di intercettare le scritture nei registri dei colori, osservare i valori scritti e alimentare ciascun registro nei valori di luminanza TIA corrispondenti al numero di registro.

Tutto ciò potrebbe essere fatto con un FPGA, ma alcuni dispositivi ARM piuttosto veloci possono essere molto più economici di un FPGA con RAM sufficiente per gestire il buffering necessario (sì, lo so che per i volumi di una cosa del genere potrebbe essere prodotto il costo non è ' t un fattore reale). Richiedere a ARM di guardare il segnale di clock in entrata, tuttavia, aumenterebbe in modo significativo la velocità della CPU richiesta. Il conteggio dei cicli prevedibile potrebbe rendere le cose più pulite.

Un approccio di progettazione relativamente semplice sarebbe quello di fare in modo che un CPLD guardi la CPU e il TIA e generi un segnale di sincronizzazione RGB + a 13 bit, quindi fare in modo che DMA ARM acquisisca valori a 16 bit da una porta e li scriva su un altro con tempismo adeguato. Sarebbe una sfida progettuale interessante vedere se un ARM economico potesse fare tutto. Il DMA potrebbe essere un aspetto utile di un approccio all-in-one se si potessero prevedere i suoi effetti sul conteggio dei cicli della CPU (soprattutto se i cicli DMA potrebbero verificarsi in cicli in cui il bus di memoria era altrimenti inattivo), ma a un certo punto del processo l'ARM dovrebbe svolgere le sue funzioni di ricerca da tavolo e di sorveglianza del bus. Si noti che a differenza di molte architetture video in cui i registri dei colori vengono scritti durante gli intervalli di blanking, l'Atari 2600 scrive spesso nei registri dei colori durante una porzione visualizzata di un fotogramma,

Forse l'approccio migliore sarebbe quello di utilizzare un paio di chip a logica discreta per identificare le scritture dei colori e forzare i bit inferiori dei registri dei colori sui valori corretti, quindi utilizzare due canali DMA per campionare il bus della CPU in entrata e i dati di uscita TIA, e un terzo canale DMA per generare i dati di output. La CPU sarebbe quindi libera di elaborare tutti i dati da entrambe le fonti per ciascuna linea di scansione, eseguire la traduzione necessaria e bufferizzarli per l'output. L'unico aspetto delle funzioni dell'adattatore che dovrebbe avvenire in "tempo reale" sarebbe l'override dei dati scritti su COLUxx, e questo potrebbe essere curato utilizzando due chip logici comuni.

Risposte:


7

Voto per DMA. È davvero flessibile in Cortex-M3 e versioni successive - e puoi fare qualsiasi tipo di cose folli come ottenere automaticamente i dati da un posto e trasmetterli in un altro con velocità specificata o in alcuni eventi senza spendere NESSUN ciclo di CPU. DMA è molto più affidabile.

Ma potrebbe essere abbastanza difficile da capire nei dettagli.

Un'altra opzione è rappresentata dai soft-core su FPGA con l'implementazione hardware di questi elementi rigidi.


1
Mi piace l'idea di DMA. Tuttavia, non credo che il core Cortex M3 abbia alcun DMA - questa è una funzione dei chip dei singoli produttori e tutti sembrano implementarlo in modo diverso. Una cosa che trovo fastidiosa con almeno l'implementazione con cui ho effettivamente giocato (STM32L152), è che non riesco a trovare un modo per avere uno strobo quando vengono emessi dati DMA. Inoltre, non è chiaro quali fattori possano influenzare la tempestività del DMA.
supercat

1
In ogni caso, per quanto riguarda una delle prime applicazioni che stavo meditando per un preciso ciclo di cicli, ho pubblicato ulteriori informazioni nella domanda originale. Sono curioso di quello che pensi. Un'altra situazione in cui stavo riflettendo sul ciclo del ciclismo sarebbe stata l'invio di dati di visualizzazione a un LCD a colori. I dati verrebbero bufferizzati nella RAM usando colori a 8 bit, ma il display richiede colori a 16 bit. Il modo più veloce a cui avevo pensato di produrre dati sarebbe stato quello di utilizzare l'hardware per generare i flash stroboscopici, quindi la CPU avrebbe dovuto solo sincronizzare i dati. Sarebbe bello tradurre 8-> 16 bit in un piccolo buffer ...
supercat

1
... e quindi organizzare DMA per trasferirlo, o quale sarebbe l'approccio migliore?
supercat

4

Le informazioni sui tempi sono disponibili, ma, come hai sottolineato, a volte possono essere vaghe. Ci sono molte informazioni sui tempi nella Sezione 18.2 e nella Tabella 18.1 del Manuale tecnico di riferimento per Cortex-M3, ad esempio ( pdf qui ), e un estratto qui:

estratto di 18.2

che forniscono un elenco di condizioni per la massima tempistica. Il tempismo per molte istruzioni dipende da fattori esterni, alcuni dei quali lasciano ambiguità. Ho evidenziato ciascuna delle ambiguità che ho trovato nel seguente estratto da quella sezione:

[1] I rami eseguono un ciclo per l'istruzione, quindi ricaricano la pipeline per l'istruzione target. I rami non presi sono un totale di 1 ciclo. I rami presi con un immediato sono normalmente 1 ciclo di ricarica della pipeline (2 cicli in totale). Le diramazioni prese con operando di registro sono normalmente 2 cicli di ricarica della pipeline (3 cicli in totale). Il ricaricamento della pipeline è più lungo [Quanto più a lungo?] Quando si ramifica in istruzioni a 32 bit non allineate oltre agli accessi alla memoria più lenta. Un suggerimento di diramazione viene emesso sul bus di codice che consente il precaricamento di un sistema più lento [Quanto più lento?]. Questo può [È facoltativo?] Ridurre [Di quanto?] La penalità del bersaglio del ramo per memoria più lenta, ma mai inferiore a quanto mostrato qui.

[2] Generalmente, le istruzioni di caricamento del magazzino richiedono due cicli per il primo accesso e un ciclo per ciascun accesso aggiuntivo. I negozi con scostamenti immediati richiedono un ciclo.

[3] UMULL / SMULL / UMLAL / SMLAL utilizzano la terminazione anticipata in base alla dimensione dei valori di origine [Quali dimensioni?]. Questi sono interrompibili (abbandonati / riavviati), con la latenza nel caso peggiore di un ciclo. Le versioni MLAL richiedono da quattro a sette cicli e le versioni MULL richiedono da tre a cinque cicli . Per MLAL, la versione firmata è più lunga di un ciclo rispetto a quella non firmata.

[4] Le istruzioni IT possono essere piegate . [Quando? Vedi commenti.]

[5] I tempi dei DIV dipendono dal dividendo e dal divisore . [Stesso problema di MUL] DIV è interrompibile (abbandonato / riavviato), con la latenza nel caso peggiore di un ciclo. Quando dividendo e divisore hanno dimensioni simili [Quanto simili?], La divisione termina rapidamente. Il tempo minimo è per i casi di divisore maggiore del dividendo e divisore pari a zero. Un divisore di zero restituisce zero (non un errore), sebbene sia disponibile una trap di debug per rilevare questo caso. [Quali sono gli intervalli, che sono stati dati per MUL?]

[6] Il sonno è un ciclo per l'istruzione più il numero di cicli di sonno appropriato. WFE utilizza un solo ciclo quando l'evento è passato. WFI è normalmente più di un ciclo, a meno che un interrupt capiti esattamente quando si entra in WFI.

[7] ISB richiede un ciclo (funge da filiale). DMB e DSB eseguono un ciclo a meno che i dati non siano in sospeso nel buffer di scrittura o LSU. Se si verifica un allarme durante una barriera, questo viene abbandonato / riavviato.

Per tutti i casi d'uso, sarà più complesso del "Questa istruzione è un ciclo, questa istruzione è due cicli, questo è un ciclo ..." conteggio possibile in processori più semplici, più lenti e più vecchi. Per alcuni casi d'uso, non incontrerai alcuna ambiguità. Se incontri ambiguità, suggerisco:

  1. Contatta il tuo fornitore e chiedi loro quali sono le tempistiche delle istruzioni per il tuo caso d'uso.
  2. Test per specificare il comportamento ambiguo
  3. Testare nuovamente eventuali revisioni del processore e in particolare quando si verificano modifiche al fornitore.

Questi requisiti probabilmente forniscono la risposta alla tua domanda "No, non è una buona idea, a meno che le difficoltà incontrate valgano il costo" - ma lo sapevi già.


1
Considererei vago quanto segue: "Il ricaricamento della pipeline è più lungo quando si ramifica in istruzioni a 32 bit non allineate oltre agli accessi a memoria più lenta" non dice se aggiunge esattamente un ciclo e "Le istruzioni IT possono essere piegate" non specifica a quali condizioni saranno o non saranno.
Supercat,

1
Il tempismo "IT" sembrerebbe particolarmente preoccupante, poiché si tratta di un'istruzione che verrebbe spesso utilizzata all'interno di un ciclo conteggio dei cicli ristretto, e sono abbastanza certo che non può sempre essere piegato. Immagino che se uno si ramifica sempre all'inizio di un loop sensibile al tempo, forza il loop ad avviarsi al limite di una parola, evita qualsiasi carico condizionale o memorizza all'interno del loop e non inserisce immediatamente alcuna istruzione "IT" dopo il caricamento o l'archivio di aggiornamento dei registri, i tempi "IT" sarebbero coerenti, ma le specifiche non lo chiariscono.
Supercat,

1
La mia ipotesi sarebbe che l'IT potrebbe probabilmente (sinceramente) notare qualcosa del tipo: "In assenza di stati di attesa o contese del bus di codice, il ripiegamento IT è garantito se (1) l'istruzione precedente era un'istruzione a 16 bit a cui non si accedeva memoria o il contatore del programma; e (2) l'istruzione successiva è un'istruzione a 16 bit o l'istruzione precedente non era la destinazione di un ramo "non allineato". La piegatura IT può anche verificarsi in altre circostanze non specificate. " Tale specifica consentirebbe di scrivere programmi con tempi di istruzione IT prevedibili assicurando che il codice fosse organizzato come indicato.
Supercat,

1
Caspita - confesso di aver superato solo i semplici conteggi del ciclo peggiore, piuttosto che lottare con gli avvertimenti sotto il tavolo. La mia risposta aggiornata evidenzia alcune altre ambiguità.
Kevin Vermeer,

1
Esistono molte situazioni in cui si è interessati ai conteggi dei casi peggiori e un numero equo in cui si è interessati ai conteggi dei casi migliori (ad esempio se una porta SPI può generare un byte ogni 16 cicli, la generazione di ogni byte richiederebbe 14 cicli nel migliore dei casi, e il controllo della prontezza richiederebbe 5 cicli, il controllo della prontezza ogni byte limiterebbe la velocità a una volta ogni 19 cicli nella migliore delle ipotesi; scrivere alla cieca con due NOP aggiunti consentirebbe una velocità di un byte ogni 16 cicli nella migliore delle ipotesi ). I casi in cui sono necessari tempi precisi non sono così comuni, ma possono insorgere.
supercat

3

Un modo per aggirare questo problema è utilizzare dispositivi con tempistiche deterministiche o prevedibili, come i chip Parallax Propeller e XMOS:

http://www.parallaxsemiconductor.com/multicoreconcept

http://www.xmos.com/

Il conteggio dei cicli funziona molto bene con l'elica (è necessario utilizzare il linguaggio assembly), mentre i dispositivi XMOS hanno un'utilità software molto potente, XMOS Timing Analyzer, che funziona con applicazioni scritte nel linguaggio di programmazione XC:

https://www.xmos.com/download/public/XMOS-Timing-Analyzer-Whitepaper%281%29.pdf


1
Sto iniziando a pensare che Leon abbia quote in XMOS ... ;-)
Federico Russo,

1
Mi piacciono le loro patatine e le persone che ci lavorano. Parallax è anche una bella compagnia con buoni prodotti.
Leon Heller,

1
Sì, senza offesa. Mi sembra solo che tutte le risposte (tranne una) in cui è menzionato XMOS provengano da te. Non c'è niente di sbagliato nell'essere entusiasti di qualcosa.
Federico Russo,

@Federico, @Leon - Questo è esattamente ciò che mi preoccupa un po 'di XMOS: perché c'è solo 1 utente al mondo (almeno è quello che sembra)? Se è così bello, perché non è il parlare della città? Non ho mai sentito nessuno parlarne, meno usarlo.
Stevenvh,

Prova i forum XMOS: xcore.com
Leon Heller,

2

Il conteggio dei cicli diventa più problematico man mano che ti allontani dai microcontrollori di basso livello e nei processori di elaborazione più generici. Il primo di solito ha un tempismo di istruzione ben specificato, in parte per i motivi del tuo sito. È anche perché la loro architettura è abbastanza semplice, quindi i tempi di istruzione sono fissi e conoscibili.

Un buon esempio di questo sono la maggior parte dei PIC Microchip. Le serie 10, 12, 16 e 18 hanno tempi di istruzione molto ben documentati e prevedibili. Questa può essere una funzione utile nel tipo di piccole applicazioni di controllo a cui sono destinati questi chip.

Man mano che ti allontani da costi estremamente bassi e il progettista può quindi spendere un po 'più di chip per ottenere una maggiore velocità da un'architettura più esotica, ti allontani anche dalla prevedibilità. Dai un'occhiata alle moderne varianti x86 come esempi estremi di questo. Esistono diversi livelli di cache, vitualizzazione della memoria, lookahead fetch, pipelining e altro, che rendono quasi impossibile il conteggio dei cicli di istruzione. In questa applicazione non importa però poiché il cliente è interessato all'alta velocità, non alla prevedibilità dei tempi delle istruzioni.

Puoi persino vedere questo effetto al lavoro nei modelli Microchip superiori. Il core a 24 bit (serie 24, 30 e 33) ha tempistiche di istruzione ampiamente prevedibili, ad eccezione di alcune eccezioni in presenza di contese del bus di registro. Ad esempio, in alcuni casi la macchina inserisce uno stallo quando l'istruzione successiva utilizza un registro con alcune modalità di indirizzamento indiretto il cui valore è stato modificato nell'istruzione precedente. Questo tipo di stallo è insolito su un dsPIC e la maggior parte delle volte puoi ignorarlo, ma mostra come queste cose si insinuano a causa dei progettisti che cercano di darti un processore più veloce e più capace.

Quindi la risposta di base è che fa parte del compromesso quando si sceglie un processore. Per le applicazioni di controllo di piccole dimensioni è possibile scegliere qualcosa di piccolo, economico, a bassa potenza e con tempi di istruzione prevedibili. Man mano che si richiede maggiore potenza di elaborazione, l'architettura cambia in modo da dover rinunciare a tempi di istruzione prevedibili. Fortunatamente, questo è meno un problema quando si arriva ad applicazioni ad uso intensivo di calcolo e per scopi generali, quindi penso che i compromessi funzionino ragionevolmente bene.


Concordo sul fatto che, in generale, le applicazioni a maggiore intensità di calcolo diventano meno sensibili ai tempi microscopici, ma ci sono alcuni scenari in cui si potrebbe aver bisogno di un po 'più di elaborazione rispetto al PIC-18 ma anche di prevedibilità. Mi chiedo fino a che punto dovrei cercare di imparare cose come le architetture PIC a 16 bit, o fino a che punto dovrei immaginare che ARM sarà probabilmente adeguato.
supercat

0

Sì, puoi ancora farlo, anche su un ARM. Il problema più grande con questo su un ARM è che ARM vende i core non i chip, e il tempismo del core è noto, ma ciò che il venditore di chip avvolge attorno a esso varia da fornitore a fornitore e talvolta da famiglia di chip a un altro all'interno del fornitore. Quindi un determinato chip di un determinato fornitore può essere abbastanza deterministico (se non si usano le cache per esempio), ma diventa più difficile portarlo. Quando si hanno a che fare con 5 orologi qui e 11 orologi lì usando i timer è problematico in quanto il numero di istruzioni necessarie per campionare il timer e capire se il timeout è scaduto. Dai suoni della tua esperienza di programmazione passata, sono disposto a scommettere che probabilmente eseguirai il debug con un oscilloscopio come faccio io, quindi puoi provare un loop stretto sul chip alla frequenza di clock, guardare lo spi o i2c o qualunque forma d'onda, aggiungere o rimuovere nops, cambia il numero di volte attraverso il loop e sostanzialmente sintonizza. Come con qualsiasi piattaforma, non usare gli interrupt aiuta notevolmente la natura deterministica dell'esecuzione dell'istruzione.

No, non è semplice come un PIC, ma è comunque abbastanza fattibile, specialmente se il ritardo / tempismo si avvicina alla frequenza di clock del processore. Un certo numero di venditori basati su ARM ti consente di moltiplicare la frequenza di clock e dire 60MHz fuori da un riferimento di 8 mhz, quindi se hai bisogno di un'interfaccia da 2mhz invece di fare qualcosa ogni 4 istruzioni, puoi aumentare l'orologio (se hai il power budget), quindi utilizza un timer e concediti un sacco di orologi per fare anche altre cose.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.