Slik styrer han akvariet sitt med XC

Øyvind Teig gir deg full gjennomgang av hvordan han bygde XMOS-systemet.

Ved å programmere XMOS-maskinvare i språket XC kan Øyvind Teig styre akvariet sitt. 📸: Øyvind Teig
Ved å programmere XMOS-maskinvare i språket XC kan Øyvind Teig styre akvariet sitt. 📸: Øyvind Teig Vis mer

Akvariet mitt styres av XC-kode. Lys og varme. Pluss knapper, display, ferromagnetisk RAM og lesing av klokke/kalender.

Sensorer leverer temperaturer fra et varmekammer under akvariet, på utsiden av glasset (isolert fra lufta = vanntemperatur), og måling av lufttemperatur. Med en indre og en ekstern I2C-linje. Kontrollboksen er basert på et ferdig kort fra XMOS i Bristol (startKIT, det selges ikke mer). I tillegg har jeg loddet noen små kort som kjemper om plassen i den lille boksen.

Utviklingssystemet xTIMEcomposer forteller meg at jeg har brukt – og her kommer hele lista (A-G): (A) 8 logiske kjerner av 8, (B) 9 timere av 10, (C) 26 kanal-ender av 32; og at (D) stacken er på 6840, (E) kode 51894 og (F) data på 6078 bytes. Totalt (G) minne 64812 av 65536 bytes.

Puh! Ganske fullt, altså.

Build av akvariekoden som kjøres på et startKIT. 📸: Øyvind Teig
Build av akvariekoden som kjøres på et startKIT. 📸: Øyvind Teig Vis mer

Men det er etter at den første akvariekoden var ferdig. Den brukte (A) en kjerne mindre, (B) en timer mindre, (C) en kanal-ende mer og (G) 13 kB mindre minne. Kanskje var det plass til et radiokort? Det var det! Jeg tok i bruk et SPI-bibliotek fra XMOS og hentet ned radiokode som jeg skrev om. Og lærte meg enda mer XC.

«Med andre ord: Gøy og spennende!»

I tillegg kjøpte jeg et kraftigere kort fra XMOS (xCORE-200 explorer kit) som står og lytter (også i XC) til akvariet som sender opp måledata. Det er også på et slikt kort jeg kjører demokoden til denne artikkelen. Du kan kjøpe et slikt kort for under 2000 kroner og laste ned xTIMEcomposer, som er gratis (men du må registrere deg).

Det er en del ta tak i, og at jeg skal ta for meg både lavnivå og høyere abstraksjonsnivå. Fagområdet er nok nærmest sanntidsprogrammering og språk for parallellitet og samtidighet.

Med andre ord: Gøy og spennende!

Akvarium og styringsboks. 📸: Øyvind Teig
Akvarium og styringsboks. 📸: Øyvind Teig Vis mer

Hva er XC?

XC er et språk som ved første øyekast ser ut som C. Men XMOS har lagt til mye under panseret.

Her er det tasker, som er funksjoner som startes opp med et nytt ord: par. Slike tasker er både mer og mindre enn objekter i objektorientering (OO som i C++). En task er en god abstraksjon, slik at man lettere kan forstå det man bygger. Men vi skal også se at de kan være gode funksjonelle komponenter om man ønsker mer kontroll på responstiden i et system. Hva som er naturlig å legge i tasker, hvordan de kodes innvendig og hvordan de kommuniserer seg i mellom kan til sammen bli et bra kart å brette ut noen år etterpå, når vi skal forstå hva “han” egentlig lagde.

Prosessoren jeg bruker til akvariet har 8 såkalt “logiske kjerner”, uten at den går varm av den grunn.

Disse kjernene deler minne, rent fysisk – mens klokkesyklusene blir delt mellom kjernene på en finurlig måte. Dette gjør det lettere for XC å håndheve at taskene ikke får gå i beina på hverandre. Her er det ikke tråder som får lov til å dele minne i koden. I stedet kommuniserer taskene over kanaler (chan), eller over en type kall (interface-call) med innebygde roller: server eller klient (client).

XC er mer enn OO, fordi hver av dem kan kjøre koden i en egen sløyfe, fordi det finnes et multitask kjøresystem “under XC”, som sørger for at hver sløyfe eller task bare kjører når det er nødvendig. En slik kodesløyfe vil alltid vente på en hendelse (event) før koden blir satt i gang med å håndtere en av de hendelsene den venter på.

I figuren under foregriper jeg litt. Den viser et task/dataflyt-diagram av demokoden. Taskene er helt innkapslet. Ingen ting slipper inn eller ut, annet enn via kanaler, interface-kall eller porter. Taskene er ikke som ballonger og dartpiler. Innkapslingen er total. Vi ser også at work_task er startet som fire instanser.

Task/dataflyt-diagram av demokoden. 📸: Øyvind Teig
Task/dataflyt-diagram av demokoden. 📸: Øyvind Teig Vis mer

I XC sitt tilfelle vil en slik sløyfe (while loop) ofte starte ved å vente på en hendelse, eller flere hendelser samtidig. I et slik liste kan det være (1) kanaler, (2) interface-kall, (3) timeout (timerafter) eller (4) bevegelse på porter (port). Dette gjøres i et såkalt betinget valg, vha. en select/case. Dette er ikke det samme som en en statisk switch/case, som ikke venter på hendelser.

Men, viktig: når kode blir kjørt på grunn av en hendelse i en select/case-liste, vil de andre mulige årsakene i lista ikke bli med på lasset. Man kan si at koden bare forholder seg til at det ringte på døra. Den oppdager ikke engang at mobilen også ringte, rett etterpå – før den fikk forholdt seg til døra. Så tar den telefonen. Dette forenkler koden, både i en task og mellom dem, siden en task slipper å forholde seg til flere hendelser samtidig. Dermed slipper vi å måtte designe et indre arbeids-kjøresystem – vi slipper å pushe og poppe hendelser internt. At bare en case blir tatt viser likevel en slags likhet med switch/case. Programmeringsspråket Go har også select/case og chan.

«I XC sitt tilfelle vil en slik sløyfe ofte starte ved å vente på en hendelse, eller flere hendelser samtidig.»

Man har mulighet til å få til aktiv polling ved å ta i bruk default i select/case. I så fall forlanger XC da at den tasken må kjøre på en logisk kjerne alene. Jeg har aldri trengt å gjøre dette. Denne task-typen tillater også håndtering av lokale tilstandsvariable “nedenfor” select, før while gjentas. Det kan være greit av og til. Dette er den mest generelle task-typen (normal).

XC har to task-typer til. En hvor flere tasker kan dele en kjerne. Den typen kalles kombinerbar (combinable). Da slår XC sammen select’ene og legger koden for flere tasker på en kjerne. Den tredje task-typen er så enkel at å sende en melding til den, kan gjøres ved et standard funksjonskall, selv om koden likevel må bruke chan eller interface-kall. Den typen kalles distribuerbar (distributable). XC passer på at alle task-typene brukes rett.

XC er mindre enn OO, fordi tasker med vilje er designet slik at de ikke skal kunne arve, slik som OO. Men XC kan parametrisere inn en ny case i en select på klientsiden av et interface, for bruk i bibliotek (extends client interface).

Styringsboks, temperaturfølere og USB-vaktund og releboks (bakerst). 📸: Øyvind Teig
Styringsboks, temperaturfølere og USB-vaktund og releboks (bakerst). 📸: Øyvind Teig Vis mer

Demokoden viser at man i XC kan kode ved å sende over en kanal slik: c_cnt <: cnt; Eller over et interface slik, som et kall: i_worker.async_work_request(); Begge disse kodelinjene er blokkerende. Det vil si at neste linje ikke kjøres før mottakeren av kanalen c_cnt har mottatt verdien. Neste linje kjøres heller ikke før den tasken som lytter på async_work_request har blitt kjørt. Dette er blokkerende, synkron kommunikasjon, som jeg skal komme tilbake til.

"I demokoden har jeg også benyttet guarder, og her har jeg ikke noe norsk uttrykk." 📸: Øyvind Teig
"I demokoden har jeg også benyttet guarder, og her har jeg ikke noe norsk uttrykk." 📸: Øyvind Teig Vis mer

I demokoden har jeg også benyttet guarder, og her har jeg ikke noe norsk uttrykk. Dette er et boolsk uttrykk foran case i en select. Det evalueres til sant eller usant, som en bryter eller vender. Hensikten er å ikke måtte reagere på hendelser før koden er klar og dermed slippe å huske på en eller flere avbrytelser. De måtte i så fall hentes fram igjen senere. Dette fører til lavere kompleksitet i koden. Linjene 233, 284 og 299 viser guarder. Pluss linje 320: case allow_button => inP1_button when pinsneq(button_current_val) :> button_current_val:. Her reagerer man på knappetrykk bare når allow_button er sann. Go har ikke guarder, men de kan simuleres ved å vente på nil.

Både språket occam for transputere fra INMOS i Bristol (til 1994), og XC for xCORE-arkitekturen fra XMOS (fra 2005) er i den samme tradisjonen. Occam var (stort sett), og XC er – språk for bare en prosessorarkitektur. Og XMOS-navnet er nok en løs referanse til (ex) INMOS.

Jeg programmerte i occam i flere år på jobb, og det satte spor. Etter at transputeren døde, benyttet jeg en occam-til-C oversetter som jeg lærte mye av å bruke. Da havnet koden på en Texas signalprosessor. Noen sa jeg ble “skadd” av det. Men å kode i C i 20 år etterpå hjalp lite. Jeg har blant annet jobbet med sikkerhetskritiske system, som brannvarsling, i en hel yrkeskarriere. Nå skulle jeg sikre meg at fisker i et lite akvarium hadde et godt liv og ikke ble kokt. Og lære XC og xCORE-arkitekturen. Jeg lærer til og med ved å skrive denne artikkelen. Som at jeg neppe tror at noen kommer til å lage en XC-til-C oversetter, fordi XC er enda nærmere “stålet” enn occam var.

Hvorfor skal man lære seg XC?

Hadde jeg jobbet med innvevde systemer i en bedrift nå, ville jeg ha vurdert XC og xCORE-arkitekturen. Jeg ville i alle fall ha lagd en sammenliknende rapport mot ARM, Atmel/Microchip og ESP32. Pluss sikkert fler.

Softvarearkitekturen som XC representerer er stor-skalerbar. Disse taskene, med kanaler og interface er fine komponenter å gjenbruke. Man kan tenke på en task som det som ofte kalles en lettvektsprosess.

For å underbygge at jeg ikke maler meg helt inn i et hjørne med XC ville jeg ha listet opp system utviklet med beslektede språk. Som Facebook med Erlang-appen WhatsApp, og Google med Go. På Wikipedia er det en lang liste over hva Google og andre bruker Go til. Kanskje til og med nevnt programmeringsspråket Ada, som blant annet brukes mye i rommet. XC, Erlang, Go og Ada har mye til felles når det gjelder tasker (samme hva de kalles) og hvordan de kommuniserer med hverandre.

Som hobbyist valgte jeg ikke Arduino og Raspberry Pi som hovedkort. De er flotte produkter om jeg ønsker meg et kjapt resultat og mest ønsker å tråkle sammen bibliotek som andre har skrevet. Men kanskje måtte kjøre slalåm rundt de forskjellige utgavene av disse bibliotekene. Akkurat det siste kan være ille nok i XC.

Akvariestyring basert på XMOS startKIT pluss mye annet. 📸: Øyvind Teig
Akvariestyring basert på XMOS startKIT pluss mye annet. 📸: Øyvind Teig Vis mer

For meg har veien vært målet, eller målene. Og jeg har kommet fram. Da kan det ta sin tid. Men da jeg skulle teste ut et Wi-Fi kort, så var XMOS sitt for gammelt. Derfor kjøpte jeg Wi-Fi kort som jeg plugget på en Arduino Zero med en fysisk knøttliten ARM Cortex M0 fra Atmel (*), for å teste og oppgradere firmwaren på Wi-Fi kortet. Deretter fikk jeg det på lufta på et xCORE-200 explorer kit. (*) ARM’en trakk, men Arduino sendte ikke med noe multitasking kjøresystem som man kunne skrive hjem om.

Men til syvende og sist endte jeg opp med et pakkekodet og kryptert radiokort, ikke Wi-Fi. Jeg hentet ned basisdriveren og skrev den om i XC. Det var jo XC jeg skulle lære. Nå sender akvariet opp flere data hvert fjerde sekund, og jeg kan lese temperaturen på denne xCORE-200 explorer kit radioklienten.

XC er C pluss X

Man kan si at XC er C pluss X.

“X for ukjent”, for man må installere xTIMEcomposer for å finne alle ‘X’. Jeg har funnet ganske mange. Det er slikt som ikke står i XMOS sine manualer. Ennå?

«Man kan si at XC er C pluss X. X for ukjent.»

Men, hva er noe av det de har designet bort med XC? Pekere som kan med peke rett inn i en annen tråd. Pekerne bruker man ofte til å flytte data (helst ikke som trojanske hester eller dartpiler), beskyttet av semaforer. Men XC har ikke minst styrt unna asynkrone meldingsbuffere som kan flyte over, ofte uten kontroll – og kræsje systemet.

“X for ukjent” kom tydelig fram for meg da jeg utvidet akvariekoden til å få med radio, og jeg endte med å bruke en kanal-ende mindre. Det ble en bratt lærekurve om kombinerbare og distribuerbare tasker.

Hardware

Men hva betyr “8 kjerner av 8″ som jeg nevnte i introen?

At jeg har brukt alle de 8 kjernene. Men akvariekoden består likevel av 16 tasker, og det vil være plass til fler. Dette er mulig siden taskene er av litt forskjellig type, og XC legger dem sammen og fordeler dem utover kjernene. På den måten vil de også få forskjellige sanntidsegenskaper. Men ingen har prioritet. De vil kunne betjene f.eks. en port etter en garantert minstetid, men den tida vil bli kortere om en task har en kjerne alene. Dette er mulig fordi klokkesyklusene blir delt mellom kjernene, omtrent som tikkinga fra et lykkehjul som bare går og går og ikke bremses. Det stopper ikke opp og bruker ekstra tid på noe passerende tall. Hver task får ikke gjøre seg ferdig, men må greie seg med å kjøre bare en klokkesyklus om det er noe å gjøre i en annen task. XMOS sier at koden blir deterministisk fordi responstiden og hvor lenge det vil ta å betjene en hendelse kan regnes ut.

Altså, en task blir ikke kjørt til den kommer til et synkroniseringspunkt (mer om det nedenfor) før andre tasker slipper til. Det dreier som om en enkelt klokkesyklus om gangen. xCORE-arkitekturen har heller ikke caching, og alle instruksjoner kjøres bare pent etter hverandre. Dermed er en slik arkitektur mulig. Og siden man dermed ikke køer opp på interface eller kanaler, så har ikke kjøresystemet noen køer. Ingen køer, ingen køoverflyt.

Så vidt jeg forstår er det en flankes bufring på porter. Hver port har en egen timer, og XC har et sett med primitive operasjoner på porter.

Denne xCORE-arkitekturen er ulikt alt jeg har sett. Akvarieprosessoren har en “flis” (tile). Det er den som har disse 8 logiske kjernene, 10 timere og 32 kanal-ender. Men her er et build av radioklienten som kjøres på et xCORE-200 explorer kit. Den har to fliser:

Build av radioklienten som kjøres på et xCORE-200 explorer kit. 📸: Øyvind Teig
Build av radioklienten som kjøres på et xCORE-200 explorer kit. 📸: Øyvind Teig Vis mer

Men hvorfor 10 kjerner og ikke 12, 10 timere og ikke ..en, hvorfor 32 kanaleender og ikke 40? Hvorfor er de i HW og ikke i SW som på transputer? Her må jeg bare anta. Prosessorarkitektene hos XMOS (jeg tror, i hovedsak David May og Ali Dixon), har nok brukt mye tid på å analysere sannsynlige scenarioer, silisiumareal for forskjellige alternativ, og priser.

Det jeg savner mest er en oversikt over antatt kodestørrelse og forbruk av disse HW-resursene. Jeg har ikke funnet noen slik oversikt. Det er jo en viss risiko for at man ikke får inn koden sin fordi task/timer/kanal-ender/-tilbudet overskrides. Men min erfaring er altså at når jeg begynte å skvise koden og lærte meg metodikken så fikk jeg mer å rutte med. Det ble minnet som endte opp som begrensende faktor, og det har jeg levd med i alle år med helt andre arkitekturer.

Timere

Og “9 timere av 10″?

At en xCORE flis har 10 timere (timer), hver på 32 bit og som teller til 100 på 1 mikrosekund. Da flyter de over på ca. 42,9 sekunder.

Derfor må lengre tidshåndtering gjøres ved å telle opp sekunder. Jeg gjør både det, og benytter en egen klokke/kalender krets. Men i XC kan man f.eks. fint gjøre noe dersom noe annet ikke hendte på 20,1 mikrosekunder.

Også timere kan skvises, XC er i stand til å benytte færre timere enn koden ved første øyekast skulle tilsi.

Kanal-ender og kommunikasjon

Så har vi “26 kanal-ender av 32″.

Som nevnt har xCORE både et multitasking kjøresystem, timere og kanaler implementert i hardware. Oppå dette ligger det egne assemblerinstruksjoner som kompilatoren setter opp for kjøring, avhengig av hva som står i XC-koden. Men xTIMEcomposer har også en “mapper” som legger koden riktig ut over de forskjellige kjernene.

En kanal har to ender som er synkroniseringspunkt for koden. En task som skal sende må passivt vente dersom en potensiell mottaker ikke er klar ennå. Og motsatt for en mottaker. Når den andre parten er klar, flyttes dataene over, og førstemann til synkroniseringspunktet får også kjøre igjen. Ikke før. Synkronisering og kommunikasjon er to sider av samme sak.

Denne mekanismen benyttes også ved asynkrone transaksjoner. Da er det tre faser som kompilatoren og kjøresystemet passer på blir rett. Det er her XC sitt typedef interface kommer inn. Man kan definere et interfacekall for å starte noe, dvs. be en annen task om å gjøre noe, uten at den som starter det hele (klienten) venter på svaret der og da. I stedet kan klienten gjøre noe annet, og så får klienten en melding fra server om at nå er resultatet klart. Da klienten kjøre et avsluttende interfacekall for å hente data fra server. Først etter det låser kjøresystemet opp serveren slik at den blir klar for nye oppgaver. Dette viser jeg i kodeeksemplet.

Og nye oppgaver køes ikke opp i tradisjonell forstand. Det er den aktuelle brukertasken eller klienten som vet at den skal starte en jobb, og som pent må vente til arbeidertasken eller serveren er ledig.

To xCORE-200 explorer kit med radio lytteklient (bak) og kortet jeg kjører demokoden på (til høyre). 📸: Øyvind Teig
To xCORE-200 explorer kit med radio lytteklient (bak) og kortet jeg kjører demokoden på (til høyre). 📸: Øyvind Teig Vis mer

Dette utgjør et vranglåsfritt transaksjonsmønster. Vrånglås får man for eksempel om tasker venter på hverandre i en liten eller stor og uoversiktlig sirkel, og ingen kan gå videre. Eller, tradisjonelt, om flere semaforer håndteres i feil rekkefølge. Den modellen som ligger til grunn for XC kan analyseres for vranglås. Men etter hvert lærer man seg å kode uten vranglås. Helt til koden stopper og jeg er forbauset over at jeg ikke så vranglåsen. Men den stoppet i alle fall. Noen kaller det “fail-fast”.

Nær portene (eller nær UART, USB, ADC, Ethernet-interface etc.) er det naturlig å kunne håndtere asynkrone situasjoner, om man kan kalle dem det. Meldinger som sparker inn døra uten å ringe på først. Man kan bygge task-buffere som sender ut det samme som de mottok ved å legge flere tasker etter hverandre, som perler på en snor. Men man kan også bygge en task og la den internt håndtere et buffer. Men kanskje kommer det for mange asynkrone datapakker inn, eller man rekker ikke å sende ut så mange som man burde, slik at bufferne går fulle. XC sine synkrone basismekanisme er en fin byggekloss. Man kan detektere og varsle om at data er tapt. Og man kan prioritere forskjellige typer meldinger, som at brannvarsel er viktigere enn forvarsel. Da kan man tillate å miste et forvarsel. Men før eller senere kan man miste alt, om man da ikke bygger redundante dataveier. Men selv med flymodus påslått på mobilen ønsker vi vel at noen bufrer mens vi er i lufta.

Det må også nevnes at XC har flere typer pekere. Her skal jeg bare nevne flyttbare (movable) pekere som kan sendes fra en task til en annen. Da flyttes eierskapet til dataene, og de går ut av skop hos senderen. På denne måten kan man få til null-kopiering av data ved at dataene ligger i ro mens eierskapet flyttes.

Ellers har XC også referanser, akkurat som C++.

Stacken oppgis på byte’en

Tilbake til akvariekoden.

Stacken er på 6840″ byte. Det er ikke vanlig å oppgi den som et presist tall, som man bare trenger å vite hvor stort er – som ikke skal brukes til noe. Stacken er minne som brukes til returadresser, parametere ved funksjonskall og lokale data i en kalt funksjon. Men ofte kommer avbrudd og rekursive kall inn, og de har man mindre kontroll på. De forlanger å bruke “mer” av stacken. Men hvor mye?

XC sin stack kan ikke skrives over siden verken interrupt eller rekursive kall finnes. Tallet gjelder.

På den andre siden, om man f.eks. bruker en Atmel prosessor og koder i C, må man legge til litt ekstra for dette behovet, og lage kode som tester på om den øverste cella i den avsatte stacken er skrevet over. Man får et tall av systemet og legger til etter skjønn, og det er denne verdien som settes som størrelse på stacken. Men om koden får stacken til å gro over avsatt størrelse, er det bare å restarte – for da vet man ikke hvilke variable som ble ødelagt. Men det er ikke “bare” å restarte. Da er det fint om denne løsningen ikke sitter i et fly. Eller en drone rett over hodet ditt.

«Men man kan ikke teste seg til feilfrihet.»

Men i så fall har designerne forhåpentligvis testet ganske mye for å være på den sikre siden. Men man kan ikke teste seg til feilfrihet. Når det finnes løsninger hvor man ikke trenger å ofre dette en tanke, så er det et tankekors at den mest brukte metoden er avhengig av testing.

For i stedet for interrupt vil man i XC la en helt standard task håndtere f.eks. en port. Som nevnt gir xTIMEcomposer mulighet til å analysere hvor kjapt koden kan betjene noe og hvor lang tid det tar. Dette er uvanlig. Og har man ikke interrupt, så har man heller ikke interrupt på interrupt (på interrupt (på ..(..))).

I stedet for rekursive kall, det vil si at en funksjon kaller seg selv i en while eller for sløyfe, så velger man kanskje en iterativ løsning som ikke er rekursiv. Rekursjon er for eksempel kjekt ved håndtering av trestrukturer. Dette er et eget fagfelt som jeg har lite greie på. Men det måtte nevnes. Men jeg har vært borti et simuleringsspråk som bare hadde rekursjon, hvor de innførte sekvens etter noen år. Det var vanskelig for en som er vant til å tenke prosedyremessig eller i parallellitet. Altså mangler jeg Lisp og Haskell.

Kodeeksemplet

Jeg har som nevnt skrevet et kodeeksempel til denne artikkelen.

Jeg tror eksemplet er noe utypisk for de som velger å bruke en slik prosessor, ved at 4 arbeidstasker utfører arbeid for en klienttask. En trykknapp leses også, og to LED blinker. Bilde ovenfor. Typisk bruk er nok mer IO-intensive jobber. Og jeg ville ha brukt den i sikkerhetskritiske applikasjoner om jeg ble spurt. Jeg lar kodeeksemplet på ca. 350 linjer tale for seg.

Koden og mye mer finner du i dette notatet:

https://www.teigfam.net/oyvind/home/technology/202-kode24/

Nedenfor er “Task Viewer” slik xTIMEcomposer lager den. Rød tekst og listingen øverst til høyre har jeg lagt inn:

[[combine]] i linje 339 i koden. 📸: Øyvind Teig
[[combine]] i linje 339 i koden. 📸: Øyvind Teig Vis mer

I kodeeksemplet har jeg benyttet en transaksjon som ved hjelp av tre synkrone kall utfører en sikker, asynkron transaksjon. Det blir som om client_task sier til fire worker_task: “kan dere gjøre dette for meg, men si i fra når dere er ferdige. Imens gjør jeg noe annet.” Jeg har beskrevet metodikken mer detaljert lengre opp i artikkelen.

Blokkerende kommunikasjon er, om man blir vant til det, like grei å forholde seg til som at en kodelinje kommer etter en annen. Men da må man sørge for at en task ikke skulle ha gjort noe annet mens den var blokkert. Slikt “noe annet” putter man inn i en egen task i stedet.

I flere språk er basiskommunikasjonen asynkron, slik at senderen f.eks. går videre og kan gjøre andre ting etter en sending (send and forget). Meldingen havner da i et buffer av ukjent størrelse, og man kan i teorien få overflyt om det produseres mer input enn det man kan behandle over lang tid. Man går fra et mulig vranglås-problem til mulig bufferoverflyt, og man må vurdere hva som er lettest å leve med. Men være klar over at vranglås også kan oppstå ved asynkrone system. Begge deler kan man analysere seg ut av. Men her tror jeg at tankekorset med test vs. analyse også vil slå inn. I XC vil man, som nevnt, konstruere asynkrone løsninger med interface-kall eller egne tasker med interne buffer under full kontroll.

Dypt under panseret vil koden som blir generert av XC-kompilatoren og mapperen stort sett bruke to kanal-ender per kanal, og noen kanal-ender for en transaksjonsinterface. Men den den genererte koden kan også bruke tilstandsvariable og låser (lock). Dette er avhengig av hvilke task-typer som skal kjøres og kommunisere med hverandre. Jeg har funnet eksempler hvor et og samme program, kjørt på forskjellig konfigurasjon, trenger fra seks til ingen kanal-ender. XMOS har ikke lagd noen oppskrift på hvordan man kan minimalisere. Man må prøve og se. I kodeeksempelet kan man beholde eller kommentere bort [[combine]] foran par, i main (linje 339). Da vil du se at også timere er gjenstand for denne optimaliseringen.

"Vær forberedt på en “ny verden” hvor teknologien kanskje vil overraske mest" skriver Øyvind Teig. 📸: Privat
"Vær forberedt på en “ny verden” hvor teknologien kanskje vil overraske mest" skriver Øyvind Teig. 📸: Privat Vis mer

Sluttord

Jeg har hatt det veldig gøy med å bruke disse små kortene, selv om xCORE-200 explorer kit er mye dyrere enn startKIT’et som jeg brukte i akvariet.

xTIMEcomposer er basert på en eldre versjon av Eclipse (4.3, 2013), så man kan bruke en del tredjeparts programvare fra Eclipse Marketplace, så vidt jeg skjønner. Nyeste utgave av xTIMEcomposer er 14.4.1 fra desember 2019. Som nevnt i introen kan den lastes ned gratis (men man må registrere seg), så man kan komme i gang ganske rimelig.

Vær forberedt på en “ny verden” hvor teknologien kanskje vil overraske mest. Og du får nok hjelp på XCore Exchange forum. Eller mail meg. xTIMEcomposer kompilerer også C og C++, men da må XC brukes som rammeverk. XMOS porterer nå også FreeRTOS til XCORE-arkitekturen. For min del tenker jeg at jo mindre XC, desto mindre å lære om denne uvanlige arkitekturen.

Det er også pragmatiske sider ved XC som jeg ikke har kommet inn på. Som unsafe peker eller region og makroer som SET/GET_SHARED_GLOBAL. Unntak må være mulig. Men de bør være dartpiler som treffer blink.

Til slutt må jeg nevne at mye av tankesettet til xCORE-arkitekturen er basert på CSP (Communicating Sequential Processes). C.A.R. Hoare publiserte CSP i 1978/1985 i en av verdens mest siterte vitenskapelige tekster, i følge engelsk Wikipedia. Hoare og skaperene av OO ved Norsk Regnesentral, med språket Simula, Ole-Johan Dahl og Kristen Nygaard tilhørte mye av det samme miljøet. Men XMOS har ikke brukt dette i de presentasjonene jeg har sett, helt motsatt av hva Google har gjort (“Go concurrency patterns” av Rob Pike, Google I/O 2012).

Tusen takk til Sverre Hendseth (førsteamanuensis, NTNU i Trondheim) for uvurderlig bidrag ved kritisk lesning av de første utgavene av denne artikkelen. Han underviser i fag som dekker flere av poengene i artikkelen.

Jeg har blogget og publisert artikler om det som er behandlet i denne artikkelen.

Jeg har ingen ting å gjøre med XMOS. Jeg har dekket XMOS-produkter i flere omfattende bloggnotat. Jeg har også skrevet om flere problemer, og hatt dialog med XMOS om disse. Mye av dette kan man finne på XCore Exchange forum (jeg er “aclassifier”).