Magyar English

Szimulációtól a valódi hardverig

Az első FPGA futásunk — Fibonacci(20) = 6765 valódi szilíciumon
2026. május 23. · Hocza József Szabolcs

Egy szimulátor mindig hazudik egy kicsit. Nem rosszindulatból — egyszerűen elnéz dolgokat, amiket a valódi szilícium nem. A CLI-CPU mögött 250+ C# teszt és egy teljes cocotb regressziós csomag áll: a referencia-szimulátor zölden futott, a Verilator RTL-szimuláció zölden futott, a Fibonacci(20) végponttól végpontig 6765-öt adott. Papíron kész volt.

Aztán fogtam egy Alinx A7-Lite kártyát egy Xilinx XC7A200T FPGA-val, és kiderült, mit jelent valójában a „kész".

A szimuláció megmondja, hogy a logikád helyes. A hardver megmondja, hogy a logikád valódi.

Mit akartam látni

A cél egyetlen, szándékosan unalmas dolog volt: egy C#-ban írt függvény fusson le natívan, valódi hardveren, JIT, interpreter és operációs rendszer nélkül — és írja ki az eredményt egy soros porton. Ez a CLI-CPU „hello world"-je.

C# (samples/PureMath, Math.FibonacciIterative)
    ↓ dotnet build (Roslyn)
.dll (CIL bytecode)
    ↓ TCliCpuLinker
app.t0  (40 byte: 8 header + 32 kód)
    ↓ openFPGALoader (config-flash @ 0xC00000)
IS25LP128 flash
    ↓ (FPGA üzem közben olvassa)
cilcpu_core  (Fetch → Decode → Microcode → StackCache → ALU)
    ↓ core_halt, return_value = 6765
decimal_printer → uart_tx (115200 8N1)
    ↓
"6765\r\n"  a soros porton

A teljes láncban egyetlen sornyi C kód vagy firmware sincs. A C# függvény CIL bytecode-ja közvetlenül a szilíciumon fut. Ez a vízió lényege — és pontosan ez tette a bring-upot izgalmassá.

Három bug állt a szilícium és köztem

A „papíron kész" és a „villog a LED, jön a 6765" között három hiba húzódott meg. Mindhárom olyan, amit a szimuláció elnéz, a valódi eszköz viszont nem.

1. A szimulátor megengedőbb, mint a szintézis

Az első igazi Vivado-szintézis három Verilog-szabálysértést dobott ki, amit a Verilator évek óta elnézett:

Tanulság: a sim-szintű zöld teszt szükséges, de nem elégséges. A szintézer egy második, szigorúbb lektor — és érdemes minél hamarabb futtatni.

2. Az osztó, ami nem fért bele az órajelbe

A szintézis lefutott, de a timing nem zárt: WNS = −66 ns 50 MHz-en. A kritikus út 329 logikai szint volt, 287 darab CARRY4 egyetlen láncon. A bűnös: a tisztán kombinációs ALU 32-bites előjeles osztása (DIV / REM), amit a szintézer egy 32 szintű kivonó-láncként göngyölített ki.

Itt nem volt felületi javítás — a gyökeret kellett orvosolni. Az egyciklusos kombinációs osztó helyébe egy szekvenciális, 1 bit/ciklus restoring osztó került (~34 ciklus, külön ST_DIV_WAIT állapottal a core-ban, megőrizve a C# osztás-szemantikát: 0 felé csonkítás, a maradék előjele az osztandóé). Az eredmény nem csak timing-zárás, hanem jelentősen kisebb chip is lett:

ErőforrásKombinációsIteratívVáltozás
Slice LUT5 7223 559−37,8%
CARRY4869302−65,2%
Slice FF1 3541 564+15,5%

A végső build WNS = +2,797 ns margóval zárt 50 MHz-en, nulla bukó endpoint-tal. Klasszikus hardver-trade-off: néhány extra flip-flop és néhány ciklus latencia cserébe a kombinációs robbanásért.

3. A flash, ami nem akart négysávosan beszélni

A legalattomosabb bug a legvégére maradt. A bitstream betöltve, a flash felprogramozva, KEY2 megnyomva — és a soros porton 3\r\n jelent meg. Ez a TRAP_INVALID_OPCODE trap kódja: a core szemetet olvasott a kódmemóriából.

A nyom a flash-hez vezetett. A core a 0x6B Quad Output Read paranccsal olvassa a programot az ISSI IS25LP128 flashből — ehhez a flash QE (Quad Enable) bitjének 1-nek kell lennie. Csakhogy ennek a flash-nek a QE bitje gyárilag 0, az A7-Lite MODE-lábai nincsenek SPIx4-re kötve, így az FPGA startup ROM-ja sem állítja be, és az openFPGALoader --enable-quad sem ismeri ezt a variánst. Négysávos parancs, egysávos flash → tiszta szemét.

A megoldás megint a gyökérnél: a cilcpu_qspi_controller reset után egy WREN (0x06) + WRSR (0x01, 0x40) szekvenciával maga állítja perzisztensen 1-re a flash Status Register QE bitjét. Egy QE_INIT_ENABLE paraméter mögé tettem: a szimulációban 0 (a meglévő tesztek viselkedése változatlan marad), a valódi hardveren 1.

Egyik bug sem jött elő szimulációban. A part-select és a begin/end a szintézernek számított, a timing a valódi órajelnek, a flash QE-bit pedig egy fizikai chip gyári alapállapotának. A szimuláció a logikát ellenőrzi — a hardver a feltételezéseket.

A pillanat

Reset (KEY1), majd start (KEY2). A soros terminál (COM3, 115200 8N1) hat bájtot mutatott:

36 37 36 35 0D 0A
=  "6765\r\n"
=  FibonacciIterative(20) = 6765  ✓

Egy C#-ban írt függvény lefordult CIL bytecode-dá, áthaladt a saját linkeremen, leszállt egy flash chipre, és egy általam tervezett processzor — valódi szilíciumon, ~1,1 ms alatt — kiszámolta a helyes választ. Ez a CLI-CPU első igazi életjele a szimuláción túl.

Miért éri meg az FPGA

Ezt a három bugot meg lehetett volna találni a chip legyártása után is. Az viszont drasztikusan másképp néz ki:

Hol bukik el a hibaKöltségIterációs idő
Szimuláció~0 €másodpercek
FPGA~0 € (a kártya megvan)percek (újraflashelés)
ASIC tape-out$1300+ (MPW shuttle)hónapok

Ez az F2.7 fázis tétje: nincs tape-out FPGA-validáció nélkül. A flash QE-bug egy néhány soros RTL-javítás volt FPGA-n. Ugyanez egy legyártott chipen egy halott die és egy elveszett shuttle-kör. Az FPGA az a hely, ahol a feltételezések ingyen omlanak össze.

Egy core — és a nagyobb kép

Ez egyetlen Nano core volt, egyetlen kártyán. A CLI-CPU víziója ennél nagyságrendekkel nagyobb: sok kicsi, független core egyetlen chipben, hardveres mailbox-okkal összekötve — a „Cognitive Fabric". A mostani futás ennek a víziónak az első, kézzelfogható alapköve: nem egy diagram, nem egy szimuláció, hanem működő szilícium.

A teljes toolchain működik: C# → CIL-T0 → bitstream → flash → core → UART. Ez a lánc az, ami eddig csak a fejemben és a tesztekben létezett — most egy fizikai eszközön zár be.

Nyílt forráskód

Az RTL, a szimulátor, a linker és a teljes tesztcsomag publikusan elérhető. A bring-up runbook is benne van a repóban.

A szimuláció megmondja, hogy igazad lehet. A hardver megmondja, hogy igazad van. 2026. május 23-án a CLI-CPU átlépte ezt a határt.