A CPU core-ban ket lenyeges es egy kevesse lenyeges (de azert optimalizaciora okot ado) fejlesztes volt soron:
- 1. Dual+ core eseten mar nincs garancia hogy akar az intruction fetch, akar a data memory read/write lefut egy orajel alatt. Vagyis, amikor a ket vagy tobb CPU egyszerre, ugyanazon orajel alatt akarja elerni az adott memoriat (program memorat, adatmemoriat) akkor bizony lehet hogy az egyiknek-masiknak varnia kell. Vagyis, kell egy xmem_ready jellegu drotmadzag is a memoria felol es a CPU marad LD(_WAIT)/ST(_WAIT) allapotban amig nem kap az xmem_ready-n keresztul egy visszajelzest hogy "kesz vagyok es/vagy itt az adat". Hasonloan, az execute/writeback stage is kaphat immaron egy non-valid instruction-t akkor ha elotte nem sikerult az instruction fetch hasonlo "arbitration lost" okokbol. Ez mikroarchitekturalisan ugyanaz mint a NOP, csak nem ugrik a program counter.
- 2. Kell valami atomi, read-modify-write (RMW) utasitas is hogy egyszeru spinlock-okat tudjunk csinalni a ket+ CPU-val. Halistennek az AVR ISA tartalmaz effele utasitasokat (XCH, LAC, LAS, LAT), csak engedelyezni kell, ld: -mrmw. Es engedelyezes fuggetlen az egyeb architekturalis kapcsoloktol, igy egy lightweight (avr2, avr35) proci is tud(hat)ja teljesen szabalyosan es az avr-gcc altal tamogatottan. Itt meg annyi van hogy az arbiternek tudnia kell a load (R) allapotban hogy a kovetkezo utemet (store, W) is ugyanennek a CPU-nak kell nyernie - kulonben nem lesz hardveresen atomi az utasitas. Erre vagy csinalunk egy xmem_lock vagy hasonlo drotmadzagot, vagy csak annyi van hogy az abiter megjegyzi hogy az adott utemet ki nyerte es ha egzaktul a kovetkezo orajelben is jon keres, akkor azt ugyanannak a downstream busznak kell nyernie. Szerencsere itt ezutobbi is eleg, noha az xmem_lock allapot sem bonyolult (amit a CPU ugy csinal hogy az atomi utasitas load fazisaban 1, egyebkent 0).
- 3. Ez a 6 bit mely, 8 bit szeles I/O busz (io_*) immaron mint "core coupled" busz jelenik meg es lenyegeben a CPU-bol kifele nezve elveszti jeletoseget. Azaz ugyan kivezetheto, sot, ossze is kotheto (szinten valami arbiterrel) ha nagyon akarjuk de ennek valojaban mar semmi ertelme. Egyreszt hamar kozositeni kell a periferiakat (marpedig az SMP miatt erdemes) akkor arra ott az adatmemoria (ugy mint ahogy az ATmega sorozattol kezdve megjelenik a bolti MCU-knal), masreszt meg az kicsit veszelyes lehet hogy ezen a buszon vannak olyan CPU regiszterek is mint az SREG vagy az SP. Ha nem vezetjuk ki akkor lenyegeben az osszes also 32 regiszterre vonatko utasitas (SBIC, SBIS, CBI, SBI) is ertelmet veszti: azaz azok valid utasitasok maradnak de egy NOP-hoz hasonloan nem csinalnak semmit - ez pedig egyszerusit valamennyit a mikroarchietkturan. Es akkor itt, ebbe az I/O terben el lehet helyezni egy CPUID regisztert, amit a szintezis soran beleeg a rendszerbe - igy egy sima IN rxx, CPUID modon tudja a program hogy epp melyik CPU-n fut:
#define __IOR(x) (*(volatile uint8_t *)(0x20+(x)))
#define CPUID __IOR(60)Ez a hardmadik I/O buszos dolog valojaban nem is kotelezo, megoldhato az egesz ugy hogy ehhez a komponenshez nem kell belenyulni (ekkor a szintezis soran a CPUID erteke is "kivulrol" jon a CPU fele), de lehet egy kicsit erdemes az optimalizalas miatt. Barhogyis, CPU szinten ezek a valtoztatasok kellettek. Es halistennek mind a harom olyan modositas hogy szintezis soran egyszeruen vissza tudunk allni a single core uzembe (bedrotozott xmem_ready = 1, az RMW-k meg a tobbi architektura-fuggo utasitashoz hasonloan tilthatoak es/vagy nem okoznak kart - azaz nem no a rendszer komplexitasa es nem romlik a hatekonysaga).
SoC-szinten persze meg van par dolog ami kell a multi-core valtozathoz:
- 1. A CPU felol az instruction bus maradt 16 bit szeles, de bekerult ele egy 32 bites instruction cache. Az instruction memory az igy 32 bit szeles lett a korabbi 16 bit szeles memoria helyett, es a ket proci utani 32 bites cache downstream buszat egy sima round-robin arbiter koti be az instruction memory iranyaba. Igy a kezdeti beallast leszamitva a szekvencialis utasitas-vegrehajtas eseten nincs semmifele orajel/hatekonysag vesztes. Ugras eseten persze elofordulhat, atlagosan talan 1/2 orajelnyi(?) veszteseg. Quad core eseten meg 64 bit szeles programmemoria es 64 bites per-CPU instruction cache kell majd.
- 2. A data memory arbiter-nek tamogatnia kell az RMW utasitasokat, ld. fentebb.
- 3. A periferiak is a data memory buszon lognak ugy hogy mindket CPU ugyanugy lassa (lasd: valodi SMP), az I/O busz kifele pedig lenyegeben a levegoben log.
- 4. Kell valami faek egyszerru interrupt controller (programmable interrupt controller, PIC) hogy a kulso megszakitastokat csak az egyik vagy masik, elore megadott CPU fele terelje. Illetve a klasszikus PIC feladatok mellett ennek meg az is a feladata hogy a CPU-k egymasnak is tudjanak megszakitasokat kuldeni. Egyelore maradjunk a 8-bites architekturanal itt is, egy sima 8 megszakitasos valtozat keszult el, ami 5 kulso ertelmes hardveres megszakitast kiszolgalni (a maradek 3 egyike a reset vector, a masik ez az elobbi egymasnak uzengetos IPI/PendSV, a harmadik pedig "reserved for future usage" jelleggel inkabb valami szinkron interrupt vagy trap vagy NMI kellene hogy legyen, szinten core coupled uzemben... ilyet az alap AVR nem tamogat tudtommal, de valojaban megoldhato - legalabbis ISA/ABI valtoztatassal nem jar, az tuti).
Ugyhogy mukodik a dolog, egyelore a dual core valtozatot tesztelgetem egy UP5K-s FPGA-n. Ebben van a 30 x 256 x 16bit (15kbyte) EBR mellett meg 4 x 16k x 16bit (azaz 128k) SPRAM is, es hat tudjuk hogy 640k-ba 2x64k-ba minden belefer. Egyebkent meg jelenleg a LC-k ~80%-at hasznalja ki a dual core AVR3.5 + PIC + SPI + UART + SysTick + az osszes egyeb zubehor ami kell (ezek icipici instruction cache-k es arbiter-ek). Raadasul a 30 EBR cellabol csak 2x2 kell a CPU regisztereinek es a 4 cella eleg a stage 1 bootloader-nek (ami az FPGA flash-ebol berantja a max 64k-nyi programot az SPRAM-ba), szoval meg boven van tartalek egyeb ertelmes periferiaknak is meg memorianak is.
A "lenyegi" resz, avagy "hogyan csinaljunk copy-paste modon tobb processzort ha epp tobb kell" meg igy fest:
avr_core core0
( clk, 1'b0,
pmem_c0_ce, pmem_c0_a, pmem_c0_d, pmem_c0_ready,
spm_c0_ce, spm_c0_a, spm_c0_d, spm_c0_ready,
dmem_c0_re, dmem_c0_we, dmem_c0_a, dmem_c0_di, dmem_c0_do, dmem_c0_ready, dmem_c0_alock,
io_c0_re, io_c0_ra, io_c0_we, io_c0_wa, io_c0_di, io_c0_do,
iflag0, ivect0,
core0_mode,
ieack_c0
);
defparam core0.pmem_depth = pmem_depth;
defparam core0.dmem_depth = dmem_depth;
defparam core0.interrupt = 1;
defparam core0.intr_depth = 3;
defparam core0.lsb_call = 0;
defparam core0.ld_st_io_reg = 1;
defparam core0.avr_cpuid_reg = 60;
defparam core0.avr_cpuid_value = 0;
defparam core0.pc_inital = 16'h7C00;
defparam core0.twoword_interrupts = 1;
avr_core core1
( clk, 1'b0,
pmem_c1_ce, pmem_c1_a, pmem_c1_d, pmem_c1_ready,
spm_c1_ce, spm_c1_a, spm_c1_d, spm_c1_ready,
dmem_c1_re, dmem_c1_we, dmem_c1_a, dmem_c1_di, dmem_c1_do, dmem_c1_ready, dmem_c1_alock,
io_c1_re, io_c1_ra, io_c1_we, io_c1_wa, io_c1_di, io_c1_do,
iflag1, ivect1,
core1_mode,
ieack_c1
);
defparam core1.pmem_depth = pmem_depth;
defparam core1.dmem_depth = dmem_depth;
defparam core1.interrupt = 1;
defparam core1.intr_depth = 3;
defparam core1.lsb_call = 0;
defparam core1.ld_st_io_reg = 1;
defparam core1.avr_cpuid_reg = 60;
defparam core1.avr_cpuid_value = 1;
defparam core1.pc_inital = 16'h7C00;
defparam core1.twoword_interrupts = 1;Marcsak valami ertelmes szoftvert kellett ra kanyaritani. Elso kozelitesben a FreeRTOS SMP portjat neztem meg mint osszetett, ertelmes feladatokat tamogato keretrendszer alapjat. Sajna a "hogyan portoljunk FreeRTOS-t SMP architekturara" dolog nem igazan dokumentalt, igy maradt a ket letezo pelda tanulmanyozasa. Ezekbol az RPi Pico-ban is hasznalt RP2040-es dual core ARM Cortex-M0+ tunt jo kiindulasnak, mert ott mind a FreeRTOS portnak, mind pedig maganak a dual core MCU-nak a dokumentacioja nagyon jo es sok pelda is kering a neten hogy akkor igy. Ezalapjan a sajat port is eletkepesnek tunik, de azert itt-ott meg vannak benne bugok. Leginkabb az tunik furcsanak hogy sok esetben egyik vagy masik CPU-nak aludnia kellene es akkor nem mindig szeret elmenni aludni. Ettol fuggetlenul teszi a dolgat csak (neha) kicsit nagyobb fogyasztassal. Azaz itt meg van tennivalo, es az ISA szimulator szerint a szoftver (joesetben a FreeRTOS port vagy a CPU-k beinditasat vegzo kodreszlet, rosszesetben a FreeRTOS maga) bugos valahol. De mivel ezt picit nehez debuggolni eleve es kb mindenben lehet hiba (CPU bug, SoC bug, OS bug, ISA szimulator bug) ezert ez annyira nem egyszeru. Folyt kov valamikor.
- apal blogja
- A hozzászóláshoz be kell jelentkezni
- 434 megtekintés
Hozzászólások
komoly ez a mese ;)
- A hozzászóláshoz be kell jelentkezni
Lehet hogy a mese (egyik) folytatasa RISC-V lesz... ha sikerül két magvat beleszuszakolnom majd egy UP5K-ba... :)
- A hozzászóláshoz be kell jelentkezni
egyik vagy masik CPU-nak aludnia kellene es akkor nem mindig szeret elmenni aludni.
Én se midnig szeretek elmenni aludni, amikor kéne... Megértem a CPU-idat.
- A hozzászóláshoz be kell jelentkezni
Majd alvas kozben en is megalmodom miert is lehet vajon ilyen kontraintuitiv a FreeRTOS idle time managementje :)
- A hozzászóláshoz be kell jelentkezni