Magyar English

187 teszttől a szilíciumig

Tesztvezérelt hardverfejlesztés
2026. augusztus 24. · Hocza József Szabolcs

A szoftverben a tesztvezérelt fejlesztés hétköznapi dolog. Megírod a bukó tesztet, megírod a kódot, ami zöldre fordítja, aztán refaktorálsz. Fejlesztők milliói csinálják nap mint nap.

A hardverben ritka, és ennek szerkezeti oka van: a visszacsatolási hurok hosszú, az utolsó iteráció pedig drága. Egy fordítási hiba másodpercekbe kerül. Egy szintézis-hiba percekbe. Egy legyártott chipen talált hiba egy shuttle-helybe és több hónapba. Vagyis amit a szoftverben stílus-kérdésként lehet kezelni, az a szilíciumban az egyetlen megfizethető munkamódszer.

A CLI-CPU egy processzor, ami a .NET CIL bájtkódot natívan futtatja, JIT és interpreter nélkül. Kezdettől teszt-előbb épült. Így néz ki ez a gyakorlatban, a repó mai állapotán mérve.

A teszt-készlet nem minőségi szertartás. Egy chip-projektben ez az egyetlen mechanizmus, ami megmondja, hogy az RTL még mindig azt jelenti-e, amit a specifikáció jelentett.

A specifikáció egy futtatható program

Még mielőtt egyetlen sor Verilog létezett volna, a CIL-T0 utasításkészletnek volt egy referencia-implementációja: a CilCpu.Sim, a Nano core C# szimulátora. Ez nem dokumentációs melléktermék. Ez a kanonikus definíciója annak, hogy melyik opkód mit csinál a kiértékelő veremmel, a keret-mutatóval, a hívási mélységgel és a trap-feltételekkel.

A szimulátor mögött ma 187 zöld xUnit teszt áll, a szimulátor-magra, a CIL-T0 linkerre, a futtatóra, a tracerre és a trap-szemantikára:

$ dotnet test src/CilCpu.Sim.Tests/CilCpu.Sim.Tests.csproj

Passed!  - Failed: 0, Passed: 187, Skipped: 0, Total: 187

Az arányok érdekesek. A szimulátor 3 684 sor C#; a teszt-készlete 3 391 sor. Nagyjából egy sor teszt minden sor implementációra — és a teszt volt előbb.

Egy szabály formálta ezeket a teszteket minden másnál jobban: a tesztprogramok valódi C# forrásból, Roslynnal fordulnak, sosem kézzel összerakott bájtkód-literálként. Egy kézzel írt opkód-sorozat azt teszteli, amit én hiszek a fordítóról. Egy Roslynnal fordított azt teszteli, amit a fordító ténylegesen kibocsát — beleértve azokat az eseteket is, amiket kézzel eszembe sem jutott volna leírni.

Az arany vektor: mihez van kötve az RTL

Amikor a Verilog már létezik, az érdekes kérdés nem az, hogy „átmennek-e az RTL tesztek". Hanem az, hogy „ugyanazt jelenti-e az RTL, mint a referencia". Ez két különböző állítás, és gyártás előtt csak a második számít.

Az arany vektor harness közvetlenül erre válaszol. A C# szimulátor egy JSONL végrehajtási nyomvonalat állít elő — utasításonként egy bejegyzést, még az utasítás végrehajtása előtt rögzítve. A cocotb testbench ugyanazt a programot lefuttatja a Verilog magon Verilator alatt, és minden ST_DECODE ciklusban hat architekturális jelet vet össze a nyomvonal megfelelő lépésével:

RTL jelJelentés
r_pcutasításszámláló
r_spveremmutató (a frame-end konvencióra normalizálva)
r_fpkeret-mutató
r_call_depthhívási mélység
r_arg_countaz aktív keret argumentum-száma
r_local_countaz aktív keret lokális-száma

Eltérés esetén a teszt mindkét oldalt kiírja — a nyomvonal pc/sp/fp/cd/ac/lc értékeit az RTL pc/sp/fp/cd/ac/lc értékeivel szemben, megnevezett lépésnél és ciklusnál. Nincs értelmezési lépés, nincs „nagyjából stimmel". Vagy ugyanazon az úton ment végig a hardver, mint a referencia, vagy a teszt megnevezi a pontos utasítást, ahol elváltak.

Ez az a mechanizmus, amitől a szimulátorból foggal-körömmel érvényesített specifikáció lesz. A CALL/RET arany teszt például egy 1 → 2 → 1 hívásmélység-átmenetet követ 0 → 2 → 0 argumentum-számmal, nyolc lépésen át. Bármelyik RTL-módosítás, ami csendben elrontja a keret-könyvelést, ott bukik el, azonnal, a hibás lépés megnevezésével.

Amit a hardveres készlet ma mér

Az RTL oldalt a cocotb verifikálja Verilator felett: 322 tesztfüggvény 24 testbench modulban, lefedve az ALU-t, a dekódert, a mikrokód-szekvenszert, a stack cache-t, a szorzót, az osztót, a QSPI vezérlőt, a mailboxot, a GPIO-t, a trace MUX-ot, az UART adót és vevőt, a boot vezérlőt, a decimális printert és az összeállított SoC-ot.

A méretarány az, ami a szoftver felől érkezőket meglepi:

RétegSor
Verilog RTL (rtl/src/*.v)6 480
cocotb testbench-ek (rtl/tb/*.py)10 457
C# referencia-szimulátor3 684
C# teszt-készlet3 391

1,6-szor több testbench-kód van, mint RTL. Ez az arány nem a túltesztelés mellékterméke; ennyibe kerül futtatható formában kimondani, hogy egy hardver-darabnak mit kell csinálnia minden határeset minden ciklusában.

Ugyanaz a függvény, három rétegen át

A végponttól végpontig teszt szándékosan unalmas: egy Fibonacci függvény, hétköznapi C#-ban, a samples/PureMath-ban. Attól hasznos, hogy egyetlen forrásfájl táplál három független verifikációs réteget, mindegyik közelebb a fizikai valósághoz, mint az előző.

C# forrás (samples/PureMath, Math.cs)
    ↓ Roslyn
.dll (CIL bájtkód)
    ↓ TCliCpuLinker
CIL-T0 bináris
    ↓
│ 1. réteg  C# referencia-szimulátor    Assert.Equal(6765, result.Result)
│ 2. réteg  Verilog core / Verilator     return_value == 55  (rekurzív Fib(10))
└ 3. réteg  XC7A200T FPGA, valódi UART   36 37 36 35 0D 0A  =  "6765\r\n"

Az 1. réteg a RunBinary_Fibonacci20_Returns6765 az xUnit készletben: az iteratív Fibonacci bináris a referencia-szimulátoron, 6765-öt állítva n = 20-ra.

A 2. réteg a rekurzív változatot veszi, ugyanazzal a toolchainnel linkeli, és a Verilog magon bootolja Verilator alatt, 55-ös visszatérési értéket követelve n = 10-re. A rekurzív alak itt szándékos választás: ez terheli legjobban a keret-könyvelést — a hívó kiértékelési mélységének megőrzése egymásba ágyazott CALL/RET között, a stack cache kiürítése és újratöltése, a fejléc fenntartott mezői. Egy mag, ami az iterációt hibátlanul kezeli, a rekurzióban még lehet csendben rossz — és ez a teszt az, ahol ez kiderül.

A 3. réteg a fizikai kártya: ugyanabból az osztályból származó bináris a konfigurációs flashben, valódi soros portra írva. Maga a decimális átalakítás külön RTL-tesztet kap — a printer modul a 6765-ből a "6765\r\n" bájtsorozatot állítja elő, cocotb-ban verifikálva, a fölötte lévő rétegektől függetlenül.

Egy függvény, három réteg. Ha egy változtatás elrontja a szemantikát, a legolcsóbb rétegben romlik el először — pontosan ez a lényeg.

Miért fizetődik ki a fegyelem

Egy hardveres hiba költsége nem lineáris. Lépcsős, és minden lépcső nagyjából egy nagyságrend:

Hol bukkan fel a hibaKöltségIterációs idő
C# referencia-szimulátor~0 €~1 másodperc
cocotb / Verilator~0 €másodpercek-percek
FPGA~0 € (a kártya megvan)percek (újraszintézis + újraflashelés)
ASIC tape-out1 500–3 000 € (Tiny Tapeout shuttle)hónapok

Ezért teszi a projekt végrehajtási sorrendje az FPGA-validációt (F2.7) a szilícium-szintézis elé, és ezért mondja ki a roadmap nyíltan a szabályt: nincs szilícium tape-out olyan tervvel, ami nem futott FPGA-n. Az FPGA-n talált hiba ingyen van. Ugyanaz a hiba egy legyártott lapkán egy halott chip és egy elvesztett shuttle-hely.

Az első FPGA futás ezt kézzelfoghatóvá tette: három hiba, amin minden szimuláció tisztán átment, órákon belül felszínre került valódi hardveren — egy csak szintézisben számító part-select, egy időzítési kényszer, és egy flash chip, aminek a Quad Enable bitje gyárilag nulla. Egyik sem logikai hiba volt. Mindegyik feltételezés volt. A szimuláció a logikádat ellenőrzi, a hardver a feltételezéseidet — és a teszt-készlet az, amitől a kettőt meg tudod különböztetni.

Mit ad a TDD, és mit nem

Nem bizonyítja, hogy a terv helyes. Ezt egyetlen teszt-készlet sem teszi. Amit ad, az szűkebb és hasznosabb: ha valami elromlik, másodperceken belül tudod, melyik réteg rontotta el. Egy bukó xUnit teszt azt jelenti, hogy a specifikáció változott. Egy bukó arany vektor teszt azt, hogy az RTL elcsúszott a specifikációtól. Egy bukó FPGA futás azt, hogy a fizikai világról alkotott feltételezés volt hibás.

Egy kis csapatnak, ami nyílt toolchainnel és szerény büdzsével épít processzort, ez a szétválasztás nem luxus. Ez a különbség egy olyan terv között, amit még lehet változtatni, és egy olyan között, amit már csak csodálni lehet.

Nyílt forráskód

Az RTL, a referencia-szimulátor, a linker és a teljes teszt-készlet nyilvános. A cikkben szereplő minden szám reprodukálható a repóból.