Este un titlu atrăgător, dar ce înseamnă? Definiția unui sistem în timp real, în forma sa cea mai simplă, este un sistem care se execută în mod determinist și periodic. Determinismul este o cerință de prim ordin pentru sistemele în timp real, deoarece acestea sunt, în general, mașini de control. Nu doriți ca mașina voastră de găurit cu comandă numerică să se deplaseze din punctul A în punctul B în 10 milisecunde (ms) Marți și să efectueze aceeași operațiune în 20 ms Miercuri. De asemenea, sistemul de control al zborului unui pilot ar trebui să controleze suprafețele de zbor exact în același mod, de fiecare dată, în toate condițiile.
Figura 1 ilustrează un sistem determinist. Întreruperile periodice se declanșează, iar rutina de tratare a întreruperilor gestionează codul critic din punct de vedere al timpului.
Figura 1: Exemplu de execuție deterministă ▶
Timpul de execuție al acestui cod trebuie să fie determinist, pentru a nu ajunge la un sistem care se comportă, ca în figura 2, unde actualizările hardware-ului apar aleatoriu în timp.
Există, de asemenea, necesitatea de a aduce resursele oferite de Linux și tot ceea ce înseamnă middleware asociat la sistemele controlate de hardware. Linux necesită o unitate de management a memoriei (MMU) în vederea virtualizării memoriei fizice pentru dezvoltatorul aplicației.
Procesoarele care încorporează o unitate MMU includ, de asemenea, cel puțin o memorie cache L1 și, în majoritatea cazurilor, o memorie cache L2. Memoria cache și determinismul sunt ortogonale între ele, după cum se observă în figura 3. Aici, se poate vedea că ratările L1 sau L2 vor introduce un jitter de execuție prin blocarea pipeline-ului de execuție în timp ce liniile de cache sunt pline. O memorie cache mai mare poate reduce frecvența ratărilor din memoria cache, dar nu le elimină complet. (n. red.: pipeline: bandă de asamblare)
La procesoarele care pot rula Linux, o sursă suplimentară de jitter de execuție este predictorul de ramificații. Nucleele procesoarelor includ un predictor de ramificații pentru a crește performanța la nivel de aplicație. Indiferent de implementare, ramificațiile sunt anticipate și uneori ratate. Atunci când se produce ratarea, pipeline-ul este golit. Ratările duc la un comportament de execuție nedeterminist. În timpul unei rutine de tratare a întreruperii (Interrupt Service Routine – ISR), tabelele cu istoricul ramificațiilor utilizate în predictor au un istoric al ramificațiilor care au legătură cu istoricul execuției codului principal al aplicației, nu cu istoricul execuției ISR în sine. Acest lucru va avea ca rezultat o blocare a pipeline-ului în cadrul ISR, ceea ce duce la un timp de execuție variabil de la ISR la ISR. Utilizarea unui procesor care permite utilizatorului să dezactiveze predictorul de ramificații oferă dezvoltatorului aplicației controlul asupra locului și modului în care determinismul este aplicat în sistem. Pentru un determinism la nivelul întregii aplicații, puteți dezactiva complet predictorii de ramificație. Bineînțeles, predictorii de ramificație sunt puși în aplicare pentru a crește performanța, astfel încât dezactivarea lor va reduce performanța.
Introducere în arhitectura RISC-V PolarFire SoC FPGA
Există procesoare care pot rula Linux, dar nu pot executa codul în mod determinist și există procesoare care pot executa codul în mod determinist, dar nu pot rula Linux. Nu ar fi frumos să aveți în setul de instrumente embedded o arhitectură care să le poată suporta pe amândouă? Recent anunțata arhitectură FPGA SoC bazată pe RISC-V pentru SoC PolarFire de la Microchip face exact acest lucru.
Figura 4 conține patru nuclee RV64GC RISC-V pe 64-biți, care pot rula Linux și un nucleu (RV64IMAC) care nu poate rula Linux. Cu alte cuvinte, RV64IMAC nu conține un MMU, iar cele patru nuclee RV64GC conțin un MMU. Diferențele dintre seturile de instrucțiuni ale RV64IMAC și RV64GC sunt simple; RV64GC conține o unitate în virgulă mobilă cu dublă precizie.
Pentru a crește nivelul de determinism în cadrul arhitecturii, utilizatorul poate dezactiva predictorul de ramificații în orice nucleu, fie după pornire, fie în timpul unui ISR. În plus, pentru toate cele cinci nuclee au fost alese pipeline-uri ordonate pentru a crește determinismul și pentru a evita atacurile Spectre și Meltdown pe mașinile care funcționează în altă ordine.
Până acum, am discutat doar despre determinism referitor la nucleele CPU. Codul trebuie să se execute din memorie, așa că haideți să discutăm despre subsistemul de memorie din PolarFire SoC. În primul rând, tot spațiul de memorie din PolarFire SoC este coerent. Coerența este definită astfel: Orice memorie care are mai multe copii ale datelor este gestionată de managerul de coerență, iar orice memorie care conține o singură copie a datelor este prin natura sa coerentă, deoarece nu există alte copii în ierarhia de memorie. PolarFire SoC are trei subsisteme de memorie: L1, L2 și L3. Subsistemul de memorie L3 integrează un controler de memorie LPDDR3/LPDDR4 și DDR3/DDR4 pe 36-biți. Cei 4 biți suplimentari sunt pentru adăugarea SECEDED la subsistemul de memorie L3 extern.
Subsistemul de memorie L1
Cele patru nuclee de aplicație RV64GC au fiecare un set de 8 căi asociate I$TIM de 32 KB și un set de 8 căi asociate D$TIM de 32 KB. I$ echivalează cu un cache de instrucțiuni, iar TIM semnifică Tightly Integrated Memory. I$TIM și D$TIM sunt configurabile de către utilizator, cu condiția să existe întotdeauna o singură cale de cache pentru I$TIM și D$TIM. Nucleul RV64IMAC Monitor are un set asociativ I$TIM de 16 KB cu două căi și un DTIM de 8 KB. DTIM este o memorie de date de tip scratchpad din care se poate executa codul. Toate funcționalitățile L1 TIM oferă acces determinist cu latență redusă și sunt capabile să corecteze o singură eroare și să detecteze două erori (SECDED).
Subsistemul de memorie L2
Subsistemul de memorie L2 are o dimensiune de 2 MB cu capacitate SECDED și poate fi configurat în trei moduri diferite. O memorie cache asociativă cu set de 16 căi, o memorie LIM (Loosely Integrated Memory) și o memorie scratchpad. Memoria LIM poate fi fixată pe un procesor și poate fi dimensionată în căi de memorie cache – cu alte cuvinte, LIM-urile pot fi construite în pachete de 128 KB (căi) și pot fi alocate cu acces exclusiv la un procesor. Configurat ca LIM, subsistemul de memorie L2 oferă acces determinist la nucleul pe care este fixat și este coerent, deoarece nu sunt partajate alte copii cu subsistemul de memorie L1 și L3. LIM este ideală pentru executarea deterministă a codului atât în aplicația principală, cât și în ISR-uri. Figura 5 ilustrează un sistem determinist atunci când subsistemul de memorie L2 este configurat ca un LIM, iar L1 sunt configurate ca TIM-uri.
Din păcate, din cauza predicției greșite a predictorilor de ramificație, variabilitatea timpului de execuție a ISR există în continuare, chiar dacă L2 este configurat ca LIM. Figura 6 prezintă o aplicație care se execută atunci când L1 este configurată ca TIM și L2 este configurată ca LIM. Axa orizontală indică întreruperile, iar accesul vertical indică ciclul de timp în cadrul ISR. După cum puteți vedea, în timp, execuția pentru ISR variază.
Figura 7 ne oferă determinismul pe care îl căutam prin dezactivarea predictorilor de ramificație.
Ca și LIM, memoria scratchpad poate fi configurată în pachete de 128 KB (moduri) și atribuită nucleelor CPU. Memoria scratchpad este ideală ca resursă de memorie partajată între procesorul care execută codul din LIM și procesoarele care execută codul din subsistemul de memorie L1/L2 și L3 (de obicei Linux). În cazul în care aplicația RV64IMAC scrie date în memoria de tip scratchpad, iar o copie a acelei locații de memorie există în altă parte în subsistemul de memorie L1/L2/L3, managerul de coerență va garanta coerența. În acest fel, o aplicație în timp real poate partaja date în mod coerent cu o aplicație care rulează în spațiul utilizatorului pe Linux.
Figura 8 reprezintă o configurație posibilă a subsistemului microprocesorului PolarFire SoC. În această configurație, RV64IMAC deservește funcția de timp real, în timp ce RV64GC rulează Linux. Dacă funcția în timp real are nevoie de performanță în virgulă mobilă, RV64GC ar putea servi în acest scop, deoarece predictorii de ramificație pot fi dezactivați, iar subsistemul de memorie L1 poate fi configurat ca un TIM.
Rezumat
Determinismul este o cerință crucială pentru sistemele în timp real. Cu toate acestea, pe piață există multe procesoare care pot rula Linux, dar nu pot executa codul în mod determinist iar altele, care pot executa codul în mod determinist, dar nu pot rula Linux. SoC PolarFire dispune de un subsistem de memorie unic și flexibil care permite aplicațiilor hardware de timp real și aplicațiilor Linux să coexiste într-un mod flexibil și coerent. Începeți cu PolarFire SoC de aici.
Autor: Tim Morin,
Director, Marketing strategic
Microchip Technology | https://www.microchip.com