ESP32Sharp.TestKit 0.1.0-beta.1

This is a prerelease version of ESP32Sharp.TestKit.
dotnet add package ESP32Sharp.TestKit --version 0.1.0-beta.1
                    
NuGet\Install-Package ESP32Sharp.TestKit -Version 0.1.0-beta.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ESP32Sharp.TestKit" Version="0.1.0-beta.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ESP32Sharp.TestKit" Version="0.1.0-beta.1" />
                    
Directory.Packages.props
<PackageReference Include="ESP32Sharp.TestKit" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ESP32Sharp.TestKit --version 0.1.0-beta.1
                    
#r "nuget: ESP32Sharp.TestKit, 0.1.0-beta.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ESP32Sharp.TestKit@0.1.0-beta.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ESP32Sharp.TestKit&version=0.1.0-beta.1&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=ESP32Sharp.TestKit&version=0.1.0-beta.1&prerelease
                    
Install as a Cake Tool

ESP32Sharp

A high-performance ESP32 emulator written in C# with NativeAOT optimization for iOS, Android, macOS, and Windows.

Features

  • Multi-ISA Support: Xtensa LX6/LX7 (ESP32, S2, S3) + RISC-V RV32IMC/IMAC (ESP32-C3, C6, H2)
  • NativeAOT Optimized: Zero JIT overhead, runs natively on iOS
  • Hardware Accurate: All register addresses and behavior sourced from Espressif TRMs
  • AOT-Safe Design: No reflection, no dynamic code generation, sealed classes for devirtualization
  • Windowed Register Support: Full Xtensa window overflow/underflow exception handling

Current Status (Phase 16 Complete)

  • 644 tests passing (0 failures; 7 firmware integration tests pass, 3 skip — blockers tracked below)
  • 179+ Xtensa opcodes implemented: full RRR/RRI8/BRI8/BRI12/CALL/RI16/CALLX format coverage + FPU + boolean coprocessor + debug exceptions + atomics
  • 19 peripherals: GPIO, UART, Timer (TIMG0/TIMG1), MWDT/RWDT watchdogs, Interrupts, SPI, I2C, DMA, LEDC, DPORT, eFuse, RTC_CNTL, SHA, ADC (12-bit/18ch), DAC (8-bit/2ch), RMT (8ch), I2S, PCNT, TouchPad, TempSensor
  • Complete ESP32 memory map: DRAM0/1, IRAM0/1, ROM, Flash, RTC FAST/SLOW, eFuse, RTC_CNTL, SHA, TIMG0, TIMG1, IROM, DROM (17 regions)
  • Flash MMU: 64-entry MMU with 64KB pages, IROM/DROM mapping; Esp32FirmwareLoader maps segments via mmu.MapIrom/MapDrom
  • ROM stubs: dispatch table at 0x4000_xxxx; ets_printf, SPIRead, heap_caps_malloc/free, ets_delay_us, Cache_Read_Enable
  • Firmware infra: partition table parser, firmware segment loader, SPIFFS reader, NVS key-value store, UartReplHost (bidirectional REPL I/O), Esp32HeapTracker
  • FPU/Coprocessor: FR0–FR15, BR (16-bit boolean), FCR/FSR; all FP arithmetic, conversions, comparisons, conditional moves

Boot blockers (firmware integration tests skipped until resolved)

Blocker Description Affected tests
B1 eFuse peripheral incomplete — chip-ID/MAC reads from 0x6001A000 not yet wired into boot sequence All IDF firmware
B2 RTC_CNTL reset reason register not initialised on reset All IDF firmware
B3 SHA accelerator computation stub — HMAC/eFuse uses SHA Secure boot
B4 Xtensa instruction coverage gap for IDF startup — audit needed All IDF firmware

Project Structure

ESP32Sharp/
├── src/ESP32Sharp/                     # Main library (single NuGet package)
│   ├── Core/                           # ISA-agnostic abstractions
│   │   ├── Memory/                     # IMemoryBus, Ram, Rom, BusInterconnect
│   │   ├── Cpu/                        # IIsaCore, CpuState
│   │   ├── Cache/                      # ICache/DCache simulation
│   │   ├── Mmu/                        # Memory Management Unit
│   │   ├── System/                     # Esp32MemoryMap
│   │   └── Decode/                     # IInstructionDecoder
│   ├── Xtensa/                         # Xtensa LX6/LX7 implementation
│   │   ├── XtensaCore.cs
│   │   ├── XtensaRegisterFile.cs
│   │   ├── Decode/                     # LUT decoder + predecode cache
│   │   ├── Instructions/               # Instruction handlers
│   │   └── Exceptions/                 # Window exceptions, interrupts
│   ├── RiscV/                          # RISC-V implementation (planned)
│   └── Peripherals/                    # Shared peripherals
│       ├── Gpio/
│       ├── Uart/
│       ├── Spi/
│       ├── I2c/
│       ├── Dma/
│       ├── Ledc/
│       ├── Flash/
│       ├── Timer/
│       ├── InterruptController/
│       └── System/                     # DPORT, eFuse, RTC_CNTL, SHA
└── tests/ESP32Sharp.Tests/             # xUnit tests

Getting Started

Prerequisites

Build

dotnet build

Run Tests

dotnet test

Usage Example

using ESP32Sharp.Core.Memory;
using ESP32Sharp.Xtensa;

// Create memory bus and RAM
var ram = new Ram(512 * 1024);  // 512KB
var bus = new BusInterconnect();
bus.RegisterDevice(0x3FFE_0000, ram);  // ESP32 DRAM base

// Create Xtensa CPU core
var core = new XtensaCore(bus, ram, 0x3FFE_0000, 512 * 1024);

// Execute instructions
core.Step();  // Execute one instruction
core.Step();

Architecture Principles

  1. TRM-First: Every register address, bitfield, and hardware behavior is cited from the Espressif TRM or Xtensa ISA Reference
  2. AOT-Safe: No Reflection, dynamic, Emit, or runtime code generation
  3. Zero Hot-Path Allocations: Predecode cache, readonly struct, Span<T>, stackalloc
  4. Sealed Everywhere: All concrete classes are sealed for AOT devirtualization
  5. ISA Isolation: Xtensa code in ESP32Sharp.Xtensa.*, RISC-V in ESP32Sharp.RiscV.*, shared code in ESP32Sharp.Core.*

Development Roadmap

Phase 1: Foundations ✅

  • Project structure with NativeAOT configuration
  • Core memory interfaces (IMemoryBus, Ram, BusInterconnect)
  • Core CPU abstraction (IIsaCore)
  • Xtensa register file with window support
  • XtensaCore stub
  • Basic tests

Phase 2: Decode Pipeline ✅

  • DecodedInsn struct + cache
  • Primary opcode LUT (delegate* array)
  • Basic instructions: ADD, MOV, L32I, S32I, MOVI
  • Test: execute synthetic "hello world" firmware

Phase 3: Window Exceptions ✅

  • Full XtensaRegisterFile with backing store
  • WindowOverflow4/8/12 exception handlers
  • WindowUnderflow4/8/12 exception handlers
  • ENTRY, RETW, ROTW implementation
  • Test: recursive function with window rotation

Phase 4: Essential Peripherals ✅

  • GPIO (output for LED blink)
  • UART (tx/rx basic)
  • Timer (periodic interrupts)
  • Interrupt controller and CPU dispatch

Phase 5: Peripheral Expansion ✅

  • SPI peripheral (SPI0/1/2/3) with transaction support
  • I2C peripheral (I2C0/I2C1) with command queue
  • DMA controller (13 channels, interrupt support)
  • LEDC (LED PWM Controller, 16 channels, hardware fade)

Phase 6: Memory Management & Flash ✅

  • ROM implementation (ESP32 bootloader ROM at 0x40000000)
  • Flash controller (SPI flash interface, 4MB, sector erase)
  • Complete DRAM/IRAM memory map (DRAM0/1, IRAM0/1, 520KB total)
  • DPORT registers for system control
  • Memory aliasing support (DRAM1/IRAM1 shared)
  • RTC memory (FAST + SLOW)
  • Memory Management Unit (MMU) for flash mapping (64 entries, 64KB pages)
  • Cache simulation (ICache/DCache with hit tracking)
  • Test: Boot real ESP32 ROM bootloader (deferred)

Phase 7: Advanced Exception & Interrupt Handling ✅

  • NMI (Non-Maskable Interrupt) support — RaiseNmi/ClearNmi, bypasses INTENABLE, vectors to VECBASE+0x2C0
  • Debug exception handling — DEBUGCAUSE, EPC6/EPS6, debug vector at VECBASE+0x280
  • Watch exceptions (IBREAK, DBREAK) — IBREAKA0/1, DBREAKA0/1, DBREAKC0/1 with load/store tracking
  • BREAK/BREAK.N — fire debug exception with DEBUGCAUSE.BREAK/BREAKN bits
  • RFNMI — return from NMI, restores EPC7/EPS7
  • ICOUNT — decrement counter with configurable level, fires debug exception on wrap
  • Correct SR register dispatch: WINDOWBASE=72, PS=230, DEPC=192, EPS2-7=193-198, EXCCAUSE=232, EXCVADDR=238
  • CCOUNT (SR 234) + CCOMPARE0/1/2 (SR 240-242) cycle counter with timer interrupt firing
  • INTENABLE/INTERRUPT/INTSET/INTCLEAR registers complete (SR 228/226/227)
  • Cross-core interrupts (ESP32 dual-core) — deferred to Phase 14
  • Test: Multi-level interrupt scenarios (30 tests)

Phase 8: Coprocessor & FPU ✅

  • FP register file: FR0–FR15 (InlineArray), BR (16-bit boolean), FCR, FSR
  • FP load/store: LSI, SSI, LSIU, SSIU, LSX, SSX, LSXU, SSXU
  • FP arithmetic: ADD.S, SUB.S, MUL.S, MADD.S, MSUB.S
  • FP conversions: FLOAT.S, UFLOAT.S, TRUNC.S, ROUND.S, FLOOR.S, CEIL.S, UTRUNC.S
  • FP unary/move: ABS.S, NEG.S, MOVE.S, WFR, RFR
  • FP compare→boolean: OEQ.S, UEQ.S, OLE.S, ULE.S, OLT.S, ULT.S, UN.S
  • FP conditional moves: MOVEQZ.S, MOVNEZ.S, MOVLTZ.S, MOVGEZ.S, MOVF.S, MOVT.S
  • Boolean coprocessor: ANDB, ANDBC, ORB, ORBC, XORB, ALL4, ALL8, ANY4, ANY8
  • Boolean AR conditional moves: MOVF, MOVT
  • WUR/RUR for FCR (UR 232) and FSR (UR 233)
  • SR 4 (BR) read/write via RSR/WSR
  • 64 new unit tests covering all FPU and boolean instructions, including NaN edge cases

Phase 9: System Integration & Firmware Boot ✅

  • Complete DRAM/IRAM memory map (DRAM0/1, IRAM0/1, RTC FAST/SLOW)
  • eFuse controller (chip ID, MAC address, calibration) — Esp32Efuse.cs
  • RTC/Sleep controller (power-on reset, APB clock, WDT key) — Esp32RtcCntl.cs
  • MWDT (Main Watchdog Timer) in TIMG0/TIMG1 — write-protect key (0x50F361A3), feed register, stage-0 timeout interrupt (TRM §18.3.5)
  • RWDT (RTC Watchdog Timer) in RTC_CNTL — write-protect key (0x50D83AA1), feed register, stage-0 timeout interrupt, INT_ENA/INT_RAW/INT_CLR (TRM §29.3.8)
  • RTC time counter — 48-bit counter, TIME_UPDATE snapshot, TIME0/TIME1 registers (TRM §29.3.2)
  • TIMG0/TIMG1 registered in Esp32MemoryMap at 0x3FF5_F000/0x3FF6_0000
  • 27 new unit tests covering MWDT, RWDT, RTC time counter, bus integration
  • Test: Boot to ESP-IDF “Hello World” firmware (deferred — requires full IDF boot sequence)

Phase 10: MicroPython Support ✅

  • Load MicroPython/app binary from flash via partition table (Esp32PartitionTable + Esp32FirmwareLoader)
  • REPL over UART — UartReplHost captures TX and injects RX bytes for REPL-style test interaction
  • Heap allocation simulation — Esp32HeapTracker records alloc/free events, tracks peak usage and counts
  • SPIFFS filesystem — Esp32Spiffs page-scanner reads files from SPIFFS-formatted flash partitions
  • 50 new unit tests: partition table parsing, firmware segment loading, SPIFFS file I/O, REPL I/O, heap stats
  • Test: Execute real MicroPython firmware end-to-end (deferred — requires Phase 13 Flash MMU)

Phase 11: Performance & Optimization ✅

  • Cache-first instruction fetch — TryGet before FetchInstruction; bus traversal only on miss
  • Single ReadWord in FetchInstruction slow path — replaces 3 separate ReadByte calls (3 binary searches → 1)
  • _debugActive guard — all three debug check blocks (IBREAK, DBREAK, ICOUNT) are short-circuited to a single branch when no debug SRs are armed
  • RunFor(int n) batch execution — runs N steps in a tight [AggressiveOptimization] loop; avoids repeated managed↔native transitions
  • Esp32BenchmarkMeasureMips(), MeasureMipsWarmed(), BenchmarkResult with cold/warm MIPS reporting
  • UpdateDebugActive() called from WriteSR so the guard flag tracks hardware state exactly
  • 14 new performance regression and benchmark tests (521 total)

Phase 12: Instruction Completeness Audit ✅

  • Audit XtensaExecute opcode table against complete Xtensa LX6 ISA encoding tables
  • Implement missing arithmetic: QUOS, QUOU, REMS, REMU (signed/unsigned divide), MULSH, MULUH, MUL16S, MUL16U
  • Implement missing bit manipulation: EXTUI, SEXT, CLAMPS, NSA, NSAU
  • Implement missing load/store: L8UI, L16SI, L16UI, S8I, S16I
  • Implement MEMW, EXTW (memory barriers), ISYNC, RSYNC, ESYNC, DSYNC
  • Implement SYSCALL, SIMCALL (syscall interface used by QEMU and IDF)
  • Implement remaining narrow (16-bit) instructions: ADD.N, ADDI.N, BEQZ.N, BNEZ.N, ILL.N
  • NSA/NSAU decoder paths — op2=4, r=14/15 (LLVM XtensaInstrInfo.td authoritative encoding)
  • SEXT/CLAMPS decoder paths — op1=3, op2=2/3 (correct register field extraction)
  • SIMCALL distinguished from SYSCALL by s-field in decoder
  • 29 new tests: NSA/NSAU semantics (boundary cases), CLAMPS (byte/short saturation), SIMCALL (NOP), binary decode verification
  • Synthetic Hello World CPU test — hand-encoded Xtensa (L8UI/BEQ/S8I/ADDI/J) drives UART output end-to-end (551 total tests)

Phase 13: Flash MMU / IROM+DROM Mapping ✅

Goal: Map IROM (0x400D_0000) and DROM (0x3F40_0000) segments directly from flash pages. This is the single biggest blocker for booting real firmware — MicroPython's .text lives in IROM.

  • Esp32Mmu.MapIrom(loadVirtualAddr, flashDataOffset, dataLen) — maps 64KB flash pages into IROM virtual window (ESP32 TRM §3.4, §7.3.14)
  • Esp32Mmu.MapDrom(loadVirtualAddr, flashDataOffset, dataLen) — same for DROM virtual window
  • FlashMmuDevice : IMemoryMappedDevice — sealed wrapper that reconstructs virtual addresses and delegates reads through Esp32Mmu page-table translation; writes silently discarded (read-only window)
  • Esp32MemoryMap.CreateMemorySystem() registers FlashMmuDevice at IROM/DROM bus windows instead of raw flash; default identity map preserves existing behaviour; Esp32MemorySystem exposes Mmu property
  • Esp32FirmwareLoader maps IROM/DROM segments via mmu.MapIrom/MapDrom (optional Esp32Mmu? param, backward-compatible); new SegmentsMapped counter
  • 19 new tests: MapIrom/MapDrom page-table correctness, FlashMmuDevice translation, MemorySystem integration, FirmwareLoader end-to-end — 570 total tests

Phase 14: ROM System Call Stubs ✅

Goal: Stub the ESP32 ROM functions that early-boot code calls before the IDF heap is set up.

  • Stub dispatch table at ROM addresses (0x4000_xxxx) — return safe defaults
  • ets_printfPrintfOutput event; ets_delay_us → CCOUNT advance
  • SPIRead(src, dst, size)Esp32Flash; Cache_Read_Enable → no-op
  • heap_caps_malloc/freeEsp32HeapTracker (via RegisterStub)
  • XtensaCore.SetRomStubs hook — single null-check in hot path
  • NVS peripheral (Esp32Nvs): in-memory key-value store mirroring esp_err_t
  • 33 tests — 603 total

Phase 16: ADC, DAC & Sensor Peripherals ✅

Goal: Implement the hardware peripherals that firmware's machine module (MicroPython) and IDF drivers access via memory-mapped registers.

  • ADC (12-bit, 18 channels) — Esp32Adc: SENS register block 0x3FF48800; ADC1 (ch0–7) + ADC2 (ch0–9); channel injection API
  • DAC (8-bit, 2 channels) — Esp32Dac: GPIO25/26; SENS_SAR_DAC_CTRL2_REG
  • RMT (8 channels) — Esp32Rmt: 64-word RAM/ch at 0x3FF56800; TX-start event; INT management
  • Touch sensor (10 pads) — Esp32TouchPad: threshold + raw-count registers; IsTouched(pad) API
  • I2S (2 units) — Esp32I2s: 64-deep TX/RX FIFO; CLKM/SAMPLE_RATE/FIFO conf; TxSample event
  • PCNT (8 units) — Esp32Pcnt: 16-bit signed counters; H/L threshold events; InjectPulses API
  • Temperature sensor — Esp32TempSensor: SENS_SAR_TSENS_CTRL_REG; ReadCelsius()
  • 31 tests — 644 total

Phase 17: Firmware Boot Blockers

Goal: Resolve the 4 blockers preventing real IDF firmware from booting. No C# simulation — only real peripheral and instruction implementations.

  • B1 — Complete eFuse boot sequence: chip-ID/MAC read path from 0x6001A000 wired into XtensaCore.Reset() + early-boot flow
  • B2 — RTC_CNTL reset reason register initialised on reset so esp_reset_reason() returns a valid value
  • B3 — SHA accelerator: implement SHA-256 computation registers (not just the register skeleton)
  • B4 — Xtensa instruction audit: trace IDF Hello World startup, identify and implement any missing opcodes
  • Un-skip WokwiFirmwareTests.HelloWorld once all 4 blockers are resolved
  • Baseline: IDF Hello World prints to UART0 and halts cleanly

Phase 18: Real MicroPython Firmware Boot

Goal: Boot the real MicroPython .bin image through XtensaCore — no C# simulation. Real .text code in IROM executes instruction-by-instruction. The REPL runs inside the firmware.

  • Load Wokwi MicroPython UF2 into Esp32Flash via Uf2Loader
  • Parse partition table → locate app at 0x10000Esp32FirmwareLoader maps IROM/DROM
  • Boot through Esp32FirmwareHarness up to >>> prompt on UART0
  • Inject 1+1\r\n via UartReplHost — assert UART0 output contains 2
  • machine.Pin(2, OUT).on() via REPL → assert GPIO bit 2 set in Esp32Gpio.OutputState
  • machine.ADC(34).read() via REPL → returns integer from ADC1 channel 2
  • Un-skip WokwiFirmwareTests.MicroPython when passing
  • Any blocker (missing instruction, missing peripheral) → implement it, not a workaround

Phase 19: WiFi & Bluetooth MAC Emulation

Goal: Emulate the ESP32 WiFi and Bluetooth MAC layer so firmware using esp_wifi / esp_bt APIs can boot without crashing on missing peripherals.

  • WiFi MAC peripheral (Esp32WifiMac): register block at 0x3FF73000 (TRM §10); WIFI_MAC_CTRL_REG, TX/RX queue registers, interrupt delivery
  • WiFi PHY stub: packet injection API for test injection of 802.11 frames; PHY register block at 0x3FF70000
  • esp_wifi_init / esp_wifi_start ROM stub hooks → hand off to Esp32WifiMac
  • BT/BLE controller peripheral (Esp32BtController): register block at 0x3FF6E000; HCI command/event FIFO; ACL data path
  • esp_bt_controller_enable ROM stub hook → hand off to Esp32BtController
  • LWIP integration: TCP/IP packet loopback via packet injection API (no real networking — packets are injected/captured in tests)
  • Test: firmware calling esp_wifi_init does not crash; WiFi station connect sequence captured as packet trace
  • Test: BLE advertisement packet injected → firmware callback fires

Phase 20: RISC-V Support (ESP32-C3/C6/H2)

  • RiscVCore : IIsaCore (RV32IMC decoder + executor) in ESP32Sharp.RiscV.*
  • RISC-V interrupt controller (CLIC for ESP32-C3/C6)
  • Reuse ESP32Sharp.Peripherals.* (GPIO, UART, SPI, I2C, Timer)
  • Test: ESP32-C3 Hello World firmware boots to UART output

Phase 21: Dual-Core Emulation (PRO_CPU + APP_CPU)

  • Second XtensaCore instance (APP_CPU)
  • DPORT dual-core control registers (CPU stall, reset, launch)
  • Inter-processor communication (IPC) and dual-core interrupt routing
  • Cache coherency protocol simulation
  • Spinlock and atomic operations (S32C1I — SR 12 SCOMPARE1) ✅
  • Test: Producer-consumer across cores

Phase 22: Advanced Peripherals

  • TWAI (CAN bus 2.0, up to 1 Mbps)
  • MCPWM (Motor Control PWM, 6 outputs)
  • SDMMC/SDIO host controller
  • Ethernet MAC (RMII interface)

Phase 23: Security & Cryptography

  • Flash encryption (AES-256)
  • Secure boot verification chain
  • AES accelerator (128/192/256-bit)
  • RSA accelerator (up to 4096-bit)
  • TRNG (True Random Number Generator)
  • eFuse read/write protection

Phase 24: Power Management & Debugging

  • Deep/light sleep modes with wake sources
  • ULP coprocessor (FSM and RISC-V variants)
  • GDB stub (remote debugging protocol)
  • Instruction trace export

Phase 25: Production & Distribution

  • CI/CD pipeline (GitHub Actions)
  • Performance regression tests (Esp32Benchmark suite)
  • NuGet package publishing
  • Sample applications (LED blink, IDF Hello World, MicroPython REPL over UART)

References

License

MIT © Iván Montiel

Contributing

This project uses AI-assisted development with custom GitHub Copilot agents. See .github/copilot-instructions.md for agent routing guidelines.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.0-beta.1 80 7/11/2026