ESP32Sharp 0.1.0-beta.1
dotnet add package ESP32Sharp --version 0.1.0-beta.1
NuGet\Install-Package ESP32Sharp -Version 0.1.0-beta.1
<PackageReference Include="ESP32Sharp" Version="0.1.0-beta.1" />
<PackageVersion Include="ESP32Sharp" Version="0.1.0-beta.1" />
<PackageReference Include="ESP32Sharp" />
paket add ESP32Sharp --version 0.1.0-beta.1
#r "nuget: ESP32Sharp, 0.1.0-beta.1"
#:package ESP32Sharp@0.1.0-beta.1
#addin nuget:?package=ESP32Sharp&version=0.1.0-beta.1&prerelease
#tool nuget:?package=ESP32Sharp&version=0.1.0-beta.1&prerelease
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;
Esp32FirmwareLoadermaps segments viammu.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
- .NET 10 SDK (preview)
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
- TRM-First: Every register address, bitfield, and hardware behavior is cited from the Espressif TRM or Xtensa ISA Reference
- AOT-Safe: No
Reflection,dynamic,Emit, or runtime code generation - Zero Hot-Path Allocations: Predecode cache,
readonly struct,Span<T>,stackalloc - Sealed Everywhere: All concrete classes are
sealedfor AOT devirtualization - ISA Isolation: Xtensa code in
ESP32Sharp.Xtensa.*, RISC-V inESP32Sharp.RiscV.*, shared code inESP32Sharp.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 —
UartReplHostcaptures TX and injects RX bytes for REPL-style test interaction - Heap allocation simulation —
Esp32HeapTrackerrecords alloc/free events, tracks peak usage and counts - SPIFFS filesystem —
Esp32Spiffspage-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 —
TryGetbeforeFetchInstruction; bus traversal only on miss - Single
ReadWordinFetchInstructionslow path — replaces 3 separateReadBytecalls (3 binary searches → 1) -
_debugActiveguard — 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 -
Esp32Benchmark—MeasureMips(),MeasureMipsWarmed(),BenchmarkResultwith cold/warm MIPS reporting -
UpdateDebugActive()called fromWriteSRso 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/NSAUdecoder paths — op2=4, r=14/15 (LLVMXtensaInstrInfo.tdauthoritative encoding) -
SEXT/CLAMPSdecoder paths — op1=3, op2=2/3 (correct register field extraction) -
SIMCALLdistinguished fromSYSCALLby 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.textlives 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 throughEsp32Mmupage-table translation; writes silently discarded (read-only window) -
Esp32MemoryMap.CreateMemorySystem()registersFlashMmuDeviceat IROM/DROM bus windows instead of raw flash; default identity map preserves existing behaviour;Esp32MemorySystemexposesMmuproperty -
Esp32FirmwareLoadermaps IROM/DROM segments viammu.MapIrom/MapDrom(optionalEsp32Mmu?param, backward-compatible); newSegmentsMappedcounter - 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_printf→PrintfOutputevent;ets_delay_us→ CCOUNT advance -
SPIRead(src, dst, size)→Esp32Flash;Cache_Read_Enable→ no-op -
heap_caps_malloc/free→Esp32HeapTracker(viaRegisterStub) -
XtensaCore.SetRomStubshook — single null-check in hot path - NVS peripheral (
Esp32Nvs): in-memory key-value store mirroringesp_err_t - 33 tests — 603 total
Phase 16: ADC, DAC & Sensor Peripherals ✅
Goal: Implement the hardware peripherals that firmware's
machinemodule (MicroPython) and IDF drivers access via memory-mapped registers.
- ADC (12-bit, 18 channels) —
Esp32Adc: SENS register block0x3FF48800; 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 at0x3FF56800; 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;TxSampleevent - PCNT (8 units) —
Esp32Pcnt: 16-bit signed counters; H/L threshold events;InjectPulsesAPI - 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
0x6001A000wired intoXtensaCore.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.HelloWorldonce 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
.binimage throughXtensaCore— no C# simulation. Real.textcode in IROM executes instruction-by-instruction. The REPL runs inside the firmware.
- Load Wokwi MicroPython UF2 into
Esp32FlashviaUf2Loader - Parse partition table → locate app at
0x10000→Esp32FirmwareLoadermaps IROM/DROM - Boot through
Esp32FirmwareHarnessup to>>>prompt on UART0 - Inject
1+1\r\nviaUartReplHost— assert UART0 output contains2 -
machine.Pin(2, OUT).on()via REPL → assert GPIO bit 2 set inEsp32Gpio.OutputState -
machine.ADC(34).read()via REPL → returns integer from ADC1 channel 2 - Un-skip
WokwiFirmwareTests.MicroPythonwhen 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_btAPIs can boot without crashing on missing peripherals.
- WiFi MAC peripheral (
Esp32WifiMac): register block at0x3FF73000(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_startROM stub hooks → hand off toEsp32WifiMac - BT/BLE controller peripheral (
Esp32BtController): register block at0x3FF6E000; HCI command/event FIFO; ACL data path -
esp_bt_controller_enableROM stub hook → hand off toEsp32BtController - LWIP integration: TCP/IP packet loopback via packet injection API (no real networking — packets are injected/captured in tests)
- Test: firmware calling
esp_wifi_initdoes 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) inESP32Sharp.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
XtensaCoreinstance (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 (
Esp32Benchmarksuite) - 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 | Versions 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. |
-
net10.0
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on ESP32Sharp:
| Package | Downloads |
|---|---|
|
ESP32Sharp.TestKit
FluentAssertions-based test kit for integration-testing real firmware (CircuitPython, MicroPython, ESP-IDF) on the ESP32Sharp emulator. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.1.0-beta.1 | 174 | 7/11/2026 |